跳到主要内容

Whale 是什么 · 全景 · 阅读地图

30 秒导读: Whale 是一个跑在终端里、专为 DeepSeek 打造的编码 agent(你打字提问,它读文件、跑命令、改代码)。它用 Go 写成,招牌是把提示词缓存命中率顶到 ~98%——同样一段活,别人每回合都为整个上下文重新付费,它几乎只为新增的那点内容付费。本章讲清"它是什么、大盘怎么转、精华在哪、该按什么顺序读后面几章"。


1. 这是什么(零基础也能懂)

一句话定义: Whale 是一个终端里的 AI 编码助手——你在命令行跟它对话,它能读你的代码库、执行 shell 命令、编辑文件、搜网,像一个会用工具的结对程序员。

给谁用 / 解决什么问题。 假设你在终端里,想让 AI 帮你在一个大项目里改一处 bug。你不想开 IDE、不想复制粘贴,只想说一句"把 X 修好",让它自己去读相关文件、想清楚、动手改、再验证。Whale 就是干这个的。

它和别的编码 agent 有什么不同? 一句话:它不是通用多模型外壳,而是死磕 DeepSeek 一家。 这个"偏执"换来两样具体好处:

  • 省钱到极致。 DeepSeek 本身单价低,Whale 又把提示词缓存命中率做到 ~98%(见 第 02 章)——低单价 × 高缓存 = 能长时间、大规模地让 AI 帮你写代码而不心疼账单。
  • 吃满长上下文。 面向 DeepSeek 的百万级 token 上下文、工具调用与定价特性做优化,而不是套一层"什么模型都能插"的适配层(这也是它明确写下的 non-goal)。

它能做什么(功能一览):

  • 交互式终端 UI(TUI):聊天式地读代码、审阅、迭代。
  • 一次性执行:whale exec "...",问一句、跑一个命令就走,--json 输出可喂给脚本。
  • 工具集:读/写/编辑文件、grep/查找、跑 shell、抓网页、搜网。
  • 权限与安全:规则策略 + 一个 LLM 自动审查分类器,危险动作先拦一道。
  • 扩展:MCP 服务器(号称可插 1000+ 工具)、Skills、Plugins、Hooks。
  • 动态工作流:用 JavaScript 脚本编排多个 agent(fan-out 研究、多视角评审、流水线)。

用起来什么样(最小真实示例):

# 一次性:装好后先存下 DeepSeek API key
npm install -g @usewhale/whale
whale setup # 提示你输入并保存 DEEPSEEK_API_KEY

# 启动交互式 TUI —— 然后直接打字提问即可
whale

whale setup 的短描述就是"Save your DeepSeek API key for future Whale sessions"(internal/ui/cli/cmd/commands.go:138 newSetupCmd);裸跑 whale 进入 TUI 主循环(internal/ui/cli/cmd/root.go:294 的 root 命令,RunE 里调 runLoop)。

对源码诚实的一点提醒。 README 的宣传里写了 whale ask "..."whale --headless,但在本文锁定的提交里,真实的一次性入口是子命令 whale exec [prompt](internal/ui/cli/cmd/commands.go:26 newExecCmd,--json 走机器可读输出、可从 stdin 读 prompt),并没有名为 ask 的子命令或 --headless 顶层开关。另有一个隐藏的 app-server 子命令(stdio 协议,面向桌面端)。本系列以源码为准。

一句话直觉: 把 Whale 想成"一个装了工具箱、还带着一份永不改动的作业须知的实习生"——那份须知(系统前缀)每次原样递过去,DeepSeek 一眼认出"这我读过了",于是几乎免费;实习生只需要看你新说的那句话。

本节到此不碰底层。目标达到:你现在知道"这是干嘛的、和别人差在哪"。


2. 顶层全景(它大概怎么转)

2.1 组件结构图

先给一句"怎么读这张图":从上到下是一次请求的经过——CLI 入口挑一种界面 → internal/app 把一切装配起来 → internal/agent 跑回合循环 → 循环两头分别连着 DeepSeek(要 token)和工具集(落地动作);右侧是挂在循环上的横切能力。

你在终端敲字

┌───────────▼─────────────────────────────────────────────┐
│ cmd/whale/main.go → internal/ui/cli/cmd (Cobra 命令) │
│ whale (TUI) · whale exec (一次性) · app-server(隐藏) │
└───────────┬─────────────────────────────────────────────┘

┌───────────▼─────────────────────────────────────────────┐
│ internal/app —— 编排装配层(约 85 个非测试文件) │
│ app_new.go / app_tools_init.go / app_runtime_init.go │
│ 把 provider、工具、策略、会话、TUI 全部接线组装 │
└───────────┬─────────────────────────────────────────────┘
│ 组装好 Agent
┌───────────▼─────────────────────────────────────────────┐
│ internal/agent —— 回合循环(核心价值在这) │
│ turn_loop.go: 组装前缀+历史 → 流式调用 → 执行工具 │
│ → 回灌结果 → 直到 end_turn │
└──┬─────────────────────────┬──────────────────┬──────────┘
│ 要 token │ 记忆布局 │ 落地动作
┌──▼───────────────┐ ┌──────▼────────┐ ┌──────▼──────────┐
│ internal/llm/ │ │ internal/ │ │ internal/tools │
│ deepseek │ │ memory │ │ 读写/编辑/grep/ │
│ (Provider,SSE 流)│ │ Prefix+Log │ │ shell/fetch/搜 │
└──────────────────┘ │ ~98% 缓存 │ └─────────────────┘
└───────────────┘
┌───────────── 挂在循环上的横切能力 ─────────────┐
│ internal/tui Bubble Tea 终端界面 │
│ internal/policy 权限规则 + 执行边界 │
│ internal/agent(分类器) LLM 自动审查 │
│ internal/tasks 子代理(parallel/Runner) │
│ internal/workflow QuickJS 动态工作流 │
│ internal/mcp+skills+plugins 扩展面 │
└────────────────────────────────────────────────┘

2.2 部件一句话职责表

部件干什么关键文件
cmd/whale极薄入口:分发到 CLI,或在 wrapper 模式下执行子进程边界cmd/whale/main.go
internal/ui/cli/cmdCobra 命令树:TUI / exec / setup / doctor / resume / plugininternal/ui/cli/cmd/root.gocommands.go
internal/app编排装配层:把 provider、工具、策略、会话、TUI 接线成一个可跑的 App(海量 glue 代码集中于此)app_new.goapp_tools_init.goapp_runtime_init.go
internal/agent回合循环:一次输入从流式调用到工具执行的主线,含防循环、压缩、恢复turn_loop.gostream.goagent.go
internal/memory记忆布局:不可变系统前缀 + 只追加日志,支撑缓存命中prefix.goruntime.go
internal/llm/deepseekDeepSeek Provider:构造请求、SSE 流式解析、上报缓存命中 tokenclient.go
internal/tools工具集:目录组装、模糊编辑、grep、shell 沙箱、web fetch/searchtoolset.goedit.goshell.go
internal/tuiBubble Tea 终端界面(约 74 个非测试文件)internal/tui/run.go
internal/policy权限:规则策略、Shell 执行分段、执行边界policy.goapproval.go
internal/tasks子代理:parallel_reason、Runner、子代理定义与预算runner.gotools.go
internal/workflow动态工作流:QuickJS 运行时编排多 agent(Claude Code 兼容)js_runtime.goscript_runner.go
internal/mcp / skills / plugins扩展面:MCP 服务器(含 deferred 工具搜索)、Skills、Pluginsmcp/deferred.goskills/skills.goplugins/plugin.go

2.3 主线走一遍(高层,不进代码)

一次用户输入的旅程,概括成七步:

  1. 入口选界面。 whale 进 TUI,whale exec 走一次性。二者最终都调到 internal/agentRunStream* 系列入口。
  2. 落盘用户消息 + 拉历史。 把你这句话写进会话存储,再把整段历史读出来。
  3. 组装请求 = 稳定前缀 + 运行时块 + 历史。 前缀(系统提示、工具契约)刻意做成每回合逐字不变,这是 ~98% 缓存命中的地基。
  4. 流式调用 DeepSeek。 边收 SSE 边把文字增量、推理增量、工具调用增量吐给界面。
  5. 执行工具。 模型要调工具时,先过权限/分类器,再真正读文件、跑 shell、改代码,拿到结果。
  6. 回灌结果,再来一轮。 工具结果作为新消息追加进历史,循环回到第 3 步——注意前缀原样不动,只在尾部追加。
  7. 直到 end_turn。 模型不再要工具、给出最终文字,循环判定 FinishReason == end_turn,收尾输出。

这条主线的真实代码在 第 01 章 逐行走读;这里只要看懂"前缀不动、尾部追加、工具进出"这个骨架。


3. 巧妙之处清单(每条指向后面某章)

这是读者要带走的"精华"。每条先白话点出妙在哪,再指去哪深读。

  • 前缀指纹稳定缓存。 系统前缀被封成 ImmutablePrefix,内容取 SHA-256 当"指纹";每回合开头 VerifyFingerprint() 校验它没被改动(internal/memory/prefix.go:34internal/agent/turn_loop.go:209)。前缀逐字不变,DeepSeek 就能命中缓存——这是 ~98% 的技术根因。→ 第 02 章

  • LLM 自动审查(分类器)。 危险工具调用在执行前先交给一个独立的分类模型判 allow/warn/block(internal/agent/classifier.go:98 Review,规则见 classifier_system_prompt.txt),移植自 Claude Code 的 yolo 分类器,并带熔断器防误伤。→ 第 04 章

  • storm / redundant 双重防循环。 主 agent 不设回合上限,靠两个重复信号兜底:一整轮工具全被 storm 拦截(模型重发相同调用),或进度守卫判为"冗余轮"(同目标、变参数、无新进展)。分别在 3 轮 / 6 轮触发强制收尾(internal/agent/turn_loop.go:298force_summary.go:16progress_guard.go:45),外加 500 轮的最后底线。→ 第 01 章

  • deferred tool search 扛 1000+ MCP 工具。 把海量 MCP 工具先不塞进 schema,只放一份轻量目录;模型需要时调 tool_search 按名字/关键词/正则检索并即时加载(internal/mcp/deferred.go:97 Search,internal/tools/catalog_mcp.go:46 NewToolSearchTool)。避免上千工具撑爆上下文。→ 第 06 章

  • QuickJS 动态工作流,兼容 Claude Code。 用纯 Go 的 QuickJS 绑定跑用户的 JS 编排脚本(agent()parallel() 等原语),带内存/栈/超时看门狗沙箱(internal/workflow/js_runtime.go)。为 Claude Code 写的工作流脚本可原样运行。→ 第 05 章

  • 前缀完成(prefix completion)做定向恢复。 DeepSeek 的 prefix 模式让 Whale 能"替模型把话头起好",用于计划终稿等恢复场景(internal/llm/deepseek/client.go:191 StreamResponseWithPrefix)。→ 第 01 章

  • 泄漏工具调用的自愈。 模型偶尔把工具调用写成纯文本 <tool_calls>…(不会真执行),循环会把这段从历史里擦掉、并 nudge 一次让它改用结构化通道,有次数上限防误伤真答案(internal/agent/turn_loop.go:408)。→ 第 01 章


4. 代码地图(导航索引)

想直接跳进源码时,按主题查这张三列表(符号名比行号抗漂移,可直接 grep):

主题文件关键符号
进程入口cmd/whale/main.gomain
CLI 命令树internal/ui/cli/cmd/root.gonewRootCmd(root Use:"whale")
一次性执行internal/ui/cli/cmd/commands.gonewExecCmdrunExec
App 装配internal/app/app_new.goNew
工具装配internal/app/app_tools_init.goinitAppTools
运行时装配internal/app/app_runtime_init.goinitAppRuntime
回合循环internal/agent/turn_loop.gorunStreamWithNewMessages
单回合流式+工具internal/agent/stream.gostreamAndHandle
系统前缀组装internal/agent/system_prompt.gobuildImmutableSystemBlocksWithToolsbuildRuntimeSystemBlocks
不可变前缀 / 指纹internal/memory/prefix.goImmutablePrefixVerifyFingerprintfingerprintSystemBlocks
运行时记忆态internal/memory/runtime.goRuntimeStateBuildProviderHistory
DeepSeek Providerinternal/llm/deepseek/client.goClient.streamstreamPrefixPromptCacheHitTokens
防循环常量internal/agent/force_summary.goprogress_guard.gomaxConsecutiveStormRoundsmaxConsecutiveRedundantRoundsmainAgentToolIterBackstop
权限分类器internal/agent/classifier.goClassifier.ReviewClassifier.classify
工具集组装internal/tools/toolset.goNewToolsetToolset.Tools
模糊编辑internal/tools/edit.go(编辑工具)
MCP deferred 目录internal/mcp/deferred.goDeferredToolCatalog.Search
tool_search 工具internal/tools/catalog_mcp.goNewToolSearchTool
子代理并行internal/tasks/tools.gorunner.goparallelReasonToolRunner
QuickJS 工作流internal/workflow/js_runtime.gonewWorkflowJSRuntime
TUI 入口internal/tui/run.goRunTUI

边界说明: internal/app(约 85 个非测试文件)与 internal/tui(约 74 个)里有海量装配/界面 glue 代码。本系列只在全景图与本代码地图里点到它们,不为它们单设章节——理解主线不需要逐一读这些接线文件。


5. 阅读地图(建议顺序)

后面六章按"由浅入深、先主线后横切"排。推荐顺序就是编号顺序:

  1. 01-turn-loop.md — 核心回合循环。 先读这章:它是 Whale 的心脏,把"一次输入 → 流式调用 → 执行工具 → 回灌 → end_turn"的主线和双重防循环讲透。读懂它,后面都好接。
  2. 02-prompt-cache-memory.md — 招牌:提示词缓存的记忆布局。 紧接主线看"为什么便宜":前缀不可变 + 只追加日志 + 指纹校验如何顶起 ~98% 命中。
  3. 03-tools-and-edit.md — 工具系统。 主线的"手脚":工具目录怎么组装、模糊编辑怎么把模型的话落到文件、Shell 怎么被沙箱住。
  4. 04-safety-permissions.md — 安全与权限。 工具执行前的两道闸:规则策略 + LLM 自动审查分类器。
  5. 05-subagents-workflows.md — 子代理与动态工作流。 从单 agent 走向多 agent:并行子查询、Runner,以及 QuickJS 编排。
  6. 06-provider-and-extensibility.md — Provider 与扩展面。 收尾看"接口与生态":DeepSeek Provider 细节,以及 MCP / Skills / Plugins 怎么把外部能力接进来。

赶时间只读两章?01 + 02——回合循环 + 缓存记忆,就是 Whale 的立身之本。