数据截至 (上游 commit 676a0a228882)
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:160 newSetupCmd);裸跑 whale 进入 TUI 主循环(internal/ui/cli/cmd/root.go:299 的 root 命令,RunE 里调 runLoop)。
对源码诚实的一点提醒。 README 的宣传里写了
whale ask "..."和whale --headless,但在本文锁定的提交里,真实的一次性入口是子命令whale exec [prompt](internal/ui/cli/cmd/commands.go:29newExecCmd,--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/cmd | Cobra 命令树:TUI / exec / setup / doctor / resume / plugin | internal/ui/cli/cmd/root.go、commands.go |
internal/app | 编排装配层:把 provider、工具、策略、会话、TUI 接线成一个可跑的 App(海量 glue 代码集中于此) | app_new.go、app_tools_init.go、app_runtime_init.go |
internal/agent | 回合循环:一次输入从流式调用到工具执行的主线,含防循环、压缩、恢复 | turn_loop.go、stream.go、agent.go |
internal/memory | 记忆布局:不可变系统前缀 + 只追加日志,支撑缓存命中 | prefix.go、runtime.go |
internal/llm/deepseek | DeepSeek Provider:构造请求、SSE 流式解析、上报缓存命中 token | client.go |
internal/tools | 工具集:目录组装、模糊编辑、grep、shell 沙箱、web fetch/search | toolset.go、edit.go、shell.go |
internal/tui | Bubble Tea 终端界面(约 74 个非测试文件) | internal/tui/run.go |
internal/policy | 权限:规则策略、Shell 执行分段、执行边界 | policy.go、approval.go |
internal/tasks | 子代理:parallel_reason、Runner、子代理定义与预算 | runner.go、tools.go |
internal/workflow | 动态工作流:QuickJS 运行时编排多 agent(Claude Code 兼容) | js_runtime.go、script_runner.go |
internal/mcp / skills / plugins | 扩展面:MCP 服务器(含 deferred 工具搜索)、Skills、Plugins | mcp/deferred.go、skills/skills.go、plugins/plugin.go |
2.3 主线走一遍(高层,不进代码)
一次用户输入的旅程,概括成七步:
- 入口选界面。
whale进 TUI,whale exec走一次性。二者最终都调到internal/agent的RunStream*系列入口。 - 落盘用户消息 + 拉历史。 把你这句话写进会话存储,再把整段历史读出来。
- 组装请求 = 稳定前缀 + 运行时块 + 历史。 前缀(系统提示、工具契约)刻意做成每回合逐字不变,这是 ~98% 缓存命中的地基。
- 流式调用 DeepSeek。 边收 SSE 边把文字增量、推理增量、工具调用增量吐给界面。
- 执行工具。 模型要调工具时,先过权限/分类器,再真正读文件、跑 shell、改代码,拿到结果。
- 回灌结果,再来一轮。 工具结果作为新消息追加进历史,循环回到第 3 步——注意前缀原样不动,只在尾部追加。
- 直到 end_turn。 模型不再要工具、给出最终文字,循环判定
FinishReason == end_turn,收尾输出。
这条主线的真实代码在 第 01 章 逐行走读;这里只要看懂"前缀不动、尾部追加、工具进出"这个骨架。
3. 巧妙之处清单(每条指向后面某章)
这是 读者要带走的"精华"。每条先白话点出妙在哪,再指去哪深读。
-
前缀指纹稳定缓存。 系统前缀被封成
ImmutablePrefix,内容取 SHA-256 当"指纹";每回合开头VerifyFingerprint()校验它没被改动(internal/memory/prefix.go:34、internal/agent/turn_loop.go:210)。前缀逐字不变,DeepSeek 就能命中缓存——这是 ~98% 的技术根因。→ 第 02 章 -
LLM 自动审查(分类器)。 危险工具调用在执行前先交给一个独立的分类模型判 allow/warn/block(
internal/agent/classifier.go:98Review,规则见classifier_system_prompt.txt),移植自 Claude Code 的 yolo 分类器,并带熔断器防误伤。→ 第 04 章 -
storm / redundant 双重防循环。 主 agent 不设回合上限,靠两个重复信号兜底:一整轮工具全被 storm 拦截(模型重发相同调用),或进度守卫判为"冗余轮"(同目标、变参数、无新进展)。分别在 3 轮 / 6 轮触发强制收尾(
internal/agent/turn_loop.go:303、force_summary.go:16、progress_guard.go:45),外加 500 轮的最后底线。→ 第 01 章 -
deferred tool search 扛 1000+ MCP 工具。 把海量 MCP 工具先不塞进 schema,只放一份轻量目录;模型需要时调
tool_search按名字/关键词/正则检索并即时加载(internal/mcp/deferred.go:97Search,internal/tools/catalog_mcp.go:46NewToolSearchTool)。避免上千工具撑爆上下文。→ 第 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:223StreamResponseWithPrefix)。→ 第 01 章 -
泄漏工具调用的自愈。 模型偶尔把工具调用写成纯文本
<tool_calls>…(不会真执行),循环会把这段从历史里擦掉、并 nudge 一次让它改用结构化通道,有次数上限防误伤真答案(internal/agent/turn_loop.go:413)。→ 第 01 章
4. 代码地图(导航索引)
想直接跳进源码时,按主题查这张三列表(符号名比行号抗漂移,可直接 grep):
| 主题 | 文件 | 关键符号 |
|---|---|---|
| 进程入口 | cmd/whale/main.go | main |
| CLI 命令树 | internal/ui/cli/cmd/root.go | newRootCmd(root Use:"whale") |
| 一次性执行 | internal/ui/cli/cmd/commands.go | newExecCmd、runExec |
| App 装配 | internal/app/app_new.go | New |
| 工具装配 | internal/app/app_tools_init.go | initAppTools |
| 运行时装配 | internal/app/app_runtime_init.go | initAppRuntime |
| 回合循环 | internal/agent/turn_loop.go | runStreamWithNewMessages |
| 单回合流式+工具 | internal/agent/stream.go | streamAndHandle |
| 系统前缀组装 | internal/agent/system_prompt.go | buildImmutableSystemBlocksWithTools、buildRuntimeSystemBlocks |
| 不可变前缀 / 指纹 | internal/memory/prefix.go | ImmutablePrefix、VerifyFingerprint、fingerprintSystemBlocks |
| 运行时记忆态 | internal/memory/runtime.go | RuntimeState、BuildProviderHistory |
| DeepSeek Provider | internal/llm/deepseek/client.go | Client.stream、streamPrefix、PromptCacheHitTokens |
| 防循环常量 | internal/agent/force_summary.go、progress_guard.go | maxConsecutiveStormRounds、maxConsecutiveRedundantRounds、mainAgentToolIterBackstop |
| 权限分类器 | internal/agent/classifier.go | Classifier.Review、Classifier.classify |
| 工具集组装 | internal/tools/toolset.go | NewToolset、Toolset.Tools |
| 模糊编辑 | internal/tools/edit.go | (编辑工具) |
| MCP deferred 目录 | internal/mcp/deferred.go | DeferredToolCatalog.Search |
| tool_search 工具 | internal/tools/catalog_mcp.go | NewToolSearchTool |
| 子代理并行 | internal/tasks/tools.go、runner.go | parallelReasonTool、Runner |
| QuickJS 工作流 | internal/workflow/js_runtime.go | newWorkflowJSRuntime |
| TUI 入口 | internal/tui/run.go | RunTUI |
边界说明:
internal/app(约 85 个非测试文件)与internal/tui(约 74 个)里有海量装配/界面 glue 代码。本系列只在全景图与本代码地图里点到它们,不为它们单设章节——理解主线不需要逐一读这些接线文件。
5. 阅读地图(建议顺序)
后面六章按"由浅入深、先主线后横切"排。推荐顺序就是编号顺序:
- 01-turn-loop.md — 核心回合循环。 先读这章:它是 Whale 的心脏,把"一次输入 → 流式调用 → 执行工具 → 回灌 → end_turn"的主线和双重防循环讲透。读懂它,后面都好接。
- 02-prompt-cache-memory.md — 招牌:提示词缓存的记忆布局。 紧接主线看"为什么便宜":前缀不可变 + 只追加日志 + 指纹校验如何顶起 ~98% 命中。
- 03-tools-and-edit.md — 工具系统。 主线的"手脚":工具目录怎么组装、模糊编辑怎么把模型的话落到文件、Shell 怎么被沙箱住。
- 04-safety-permissions.md — 安全与权限。 工具执行前的两道闸:规则策略 + LLM 自动审查分类器。
- 05-subagents-workflows.md — 子代理与动态工作流。 从单 agent 走向多 agent:并行子查询、Runner,以及 QuickJS 编排。
- 06-provider-and-extensibility.md — Provider 与扩展面。 收尾看"接口与生态":DeepSeek Provider 细节,以及 MCP / Skills / Plugins 怎么把外部能力接进来。
赶时间只读两章?01 + 02——回合循环 + 缓存记忆,就是 Whale 的立身之本。