跳到主要内容

数据截至 (上游 commit d87b272aec54)

Qwen Code — 架构与原理

30 秒导读: Qwen Code 是阿里通义团队开源的终端编码 agent——在你的项目目录里敲 qwen,用自然语言提需求,它读代码、改文件、跑命令,直到把活干完。它从 Google 的 gemini-cli 分叉而来,但把最关键的一层换掉了:模型协议被抽象成一个可插拔的 ContentGenerator 接口,同一套回合循环可以跑在 OpenAI 兼容 API、Anthropic API、Gemini API 或 Qwen OAuth 之上。第二个值得学的地方是工具执行前的多层闸门——工具自带默认权限、用户规则引擎、审批模式,然后才轮到钩子与人工确认;其中审批模式的 AUTO 一档还压着一个两阶段 LLM 分类器,判断「这条命令该不该拦」。


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

一句话定义: 一个跑在你本机终端里的命令行程序,背后接大模型,能读写你的项目文件、执行 shell 命令,多轮自主地完成编码任务。

解决什么问题 / 给谁用:

假设你接手了一个不熟的仓库,要「把这个测试修好」。你得先搜代码、读几个文件、试着改、跑测试、看报错、再改。Qwen Code 就是把这一串来回交给模型自己走:你只说目标,它自己决定下一步该 grep 还是该 edit,自己跑 npm test 看结果,错了自己再改。

它和同类(Claude Code、Codex CLI、opencode)最不一样的一点是不锁模型厂商:官方 Qwen、任何 OpenAI 兼容端点(含 Ollama / vLLM 本地模型)、Anthropic、Gemini,运行时就能切。

它能做什么:

  • 多轮对话式完成编码任务,自主选工具、自主决定何时收尾;
  • 内置约 35 个工具:读写文件、精确编辑、grep/glob、shell、LSP、web fetch、待办清单、定时任务等;
  • 五档审批模式(plan / default / auto-edit / auto / yolo),配可写规则的权限引擎;
  • 钩子系统(20 种事件)让你用外部脚本拦截或增补 agent 的每一步;
  • 上下文快满时自动压缩历史;跨会话的自动记忆;技能(skills)按需装载;
  • 子代理、工作流编排、多 agent 团队;MCP 客户端;IDE 插件、桌面端、SDK、IM 机器人。

用起来什么样:

$ qwen
› packages/core 里的 editHelper 测试挂了,修一下

• grep_search "editHelper" packages/core [自动放行]
• read_file packages/core/src/utils/editHelper.ts
• edit editHelper.ts (-3 +5) [需要确认 y/n]
• run_shell_command npx vitest editHelper [AUTO 模式:分类器放行]
Test Files 1 passed (1)
修好了:normalizeLineForComparison 之前没处理行尾空白。

每一行 都是模型主动发起的工具调用,Qwen Code 负责过闸门、执行、把结果喂回模型,循环直到模型不再要求调工具。

一句话直觉: 把它想成一个只会说话的大脑 + 一套本地的手 + 一道门禁。大脑在远端(可换品牌),手是本地工具,门禁决定每只手伸出去之前要不要按铃问你。这份代码库的绝大部分工程量,花在门禁怎么让不同品牌的大脑都能指挥同一套手上。

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

2.1 部件怎么摆

代码是一个 npm workspace(package.json:1,17 个包),但价值高度集中在两个包:packages/cli(终端界面与非交互入口)和 packages/core(引擎)。其余是卫星:SDK、IDE 插件、桌面端、ACP 桥、IM 渠道。

怎么读这张图: 从上到下是一次请求的方向,左右分叉是「向模型」和「向本地」两条腿。

┌──────────────────────────────────────────┐
│ 前端:TUI / 非交互 -p / SDK / IDE / 桌面 │ packages/cli, sdk-*, ...
└───────────────────┬──────────────────────┘
│ 用户输入

┌──────────────────────────────────────────┐
│ GeminiClient —— 会话与回合的总调度 │ core/client.ts
│ (拼上下文、发一轮、收事件、管压缩) │
└──────┬────────────────────────┬──────────┘
│ 采样 │ 工具请求
▼ ▼
┌──────────────────────┐ ┌────────────────────────────┐
│ ContentGenerator │ │ CoreToolScheduler │
│ 四路协议适配(可换) │ │ 闸门 → 执行 → 结果 │
└──────────┬───────────┘ └──────────┬─────────────────┘
│ │
▼ ▼
远端大模型 ToolRegistry 里的工具
OpenAI / Anthropic / edit / shell / grep /
Gemini / Qwen OAuth agent / MCP tools …

2.2 部件一句话职责

部件干什么在哪个文件
GeminiClient会话总调度:拼请求、发一轮、转发事件、触发压缩与钩子packages/core/src/core/client.ts:217
GeminiChat历史管理与真正的流式发送;自动压缩就发生在这里packages/core/src/core/geminiChat.ts:1461
Turn一次「模型说话」的解析器:把流切成 内容/思考/工具请求/结束 事件packages/core/src/core/turn.ts:376
ContentGenerator模型协议接口,四个实现分别对接 OpenAI / Anthropic / Gemini / Qwenpackages/core/src/core/contentGenerator.ts:37
ToolRegistry工具注册表,惰性工厂 + 「按需披露」的延迟工具集packages/core/src/tools/tool-registry.ts:184
CoreToolScheduler工具调度器:验参 → 过权限闸门 → 等确认 → 执行 → 回填结果packages/core/src/core/coreToolScheduler.ts:1080
PermissionManager + 分类器规则匹配、AUTO 模式的快速通道与两阶段 LLM 判定packages/core/src/permissions/
HookSystem20 种生命周期事件,外部脚本/HTTP 可拦截或注入上下文packages/core/src/hooks/hookSystem.ts:56

2.3 主线走一遍

一次「你敲了一句话」到「屏幕上出现答案」,走这 6 步:

  1. 入口收话。 交互式 TUI 走 useGeminiStream(packages/cli/src/ui/hooks/useGeminiStream.ts:405),非交互 qwen -prunNonInteractive(packages/cli/src/nonInteractiveCli.ts:1314 是那个 while (true) 主循环)。
  2. 组装一轮。 GeminiClient.sendMessageStream(client.ts:1805)先跑 UserPromptSubmit 钩子、检查回合上限(MAX_TURNS = 100,client.ts:144)和会话 token 上限,再把 IDE 上下文、系统提醒拼到用户消息前面。
  3. 采样。 交给 GeminiChat.sendMessageStream(geminiChat.ts:1836);这一层内部做自动压缩,压了就往流里发一个 compressed 事件。真正发请求的是当前挂着的 ContentGenerator
  4. 解析流。 Turn.run(turn.ts:387)把返回的流切成事件:Content(正文)、Thought(思考)、ToolCallRequest(要调工具)、Finished。要调的工具攒在 turn.pendingToolCalls
  5. 过闸门再执行。 工具请求交给 CoreToolScheduler,它按 L3→L4→L5 的顺序判权限(coreToolScheduler.ts:2138 那段注释就是这么写的),该问就弹确认框,放行才真跑。
  6. 回灌,再来一轮。 工具结果被包成 functionResponse 重新提交:交互式在 useGeminiStream.ts:3229submitQuery(..., SendMessageType.ToolResult),非交互式在 nonInteractiveCli.ts:1480currentMessages 换成工具响应后进入下一圈。没有工具请求了,循环才结束。

一句话记住: 这是一个「外层由前端驱动的 while 循环,内层由 Turn 解析一次采样」的双层结构——回合的终止条件不是模型说「我做完了」,而是这一轮没有再要求调工具

3. 阅读地图

建议按编号顺序读;每章都能单独进,但 01 是其他章的地基。

章节讲什么什么时候该读它
01 主循环一次输入怎么走完「模型说话 → 跑工具 → 结果回灌」:双层循环、Turn 事件流、终止与中断、循环检测想抄一个 agent 主循环
02 多协议模型层一个接口接住 OpenAI / Anthropic / Gemini / Qwen:ContentGenerator 接口、四个实现、请求/响应双向转换、厂商方言要给自己的 agent 做「换模型不换代码」
03 工具层声明式工具、按需披露,以及「把话落到文件上」:DeclarativeTool 两段式设计、惰性注册、tool_search 延迟披露、编辑的多级容错匹配要设计工具协议或做 edit application
04 安全护栏钩子、规则引擎、AUTO 模式分类器与沙箱:L3/L4/L5 权限流、AUTO 快速通道、两阶段分类器、钩子 20 事件、容器/seatbelt 沙箱要给 agent 加「能自动跑但不闯祸」的护栏
05 上下文工程系统提示、QWEN.md、自动记忆、技能与压缩:系统提示怎么拼、QWEN.md 层级加载、自动记忆抽取与召回、技能装载、压缩阈值关心「上下文窗口怎么花」
06 多智能体子代理、工作流编排、团队与竞技场:agent 工具、子代理配置、工作流编排器、团队邮箱、arena要做 multi-agent 或并行子任务

4. 巧妙之处(可借鉴的技术)

下面五条是读完源码最值得带走的设计。

① 内部只说一种「话」,边界上做双向翻译。 整个引擎内部统一使用 Google @google/genai 的数据结构(Content / GenerateContentResponse),四个协议实现各自在边界做转换:OpenAI 侧是 convertGeminiRequestToOpenAI(packages/core/src/core/openaiContentGenerator/converter.ts:376)和 convertOpenAIResponseToGemini(同文件 :1087),Anthropic 侧是 AnthropicContentConverter(packages/core/src/core/anthropicContentGenerator/converter.ts:105)。好处是回合循环、压缩、工具调度全都不需要知道当前接的是谁;代价是 Gemini 的类型成了事实上的中间表示(IR),Anthropic 的 thinking 块之类特性得靠转换器补。

② 协议之下还有一层「厂商方言」分发。 OpenAI 兼容不等于行为一致。determineProvider(packages/core/src/core/openaiContentGenerator/index.ts:57)按 baseURL/配置嗅探出 DashScope / DeepSeek / MiMo / ModelScope / MiniMax / Mistral,各给一个子类去改请求头、缓存控制、采样参数。这是「一个接口 + 一堆真实世界补丁」的干净落法。

③ 工具「注册但不披露」,靠 tool_search 按需拉出来。 createToolRegistry(packages/core/src/config/config.ts:5780)注册的是惰性工厂——registerFactory 存的是一个 async () => import(...),模块要到工具第一次被用时才真加载。更进一步,工具可以标 shouldDefer = true(packages/core/src/tools/tools.ts:214),默认不出现在发给模型的函数声明列表里;模型需要时调 tool_search(packages/core/src/tools/tool-search.ts:449)按关键词或 select:Name 查出来,命中的工具被 revealDeferredTool(tool-registry.ts:703)标记,下一轮请求才带上它的完整 schema。MCP 工具一律走这条路——这直接解决了「装了 20 个 MCP server 后系统提示爆炸」的问题。

④ AUTO 模式:先用便宜的确定性规则挡,挡不住才花钱问模型。 evaluateAutoMode(packages/core/src/permissions/autoMode.ts:646)的顺序很讲究:

  • L5.1 工作区内的编辑走 passesAcceptEditsFastPath 直接放行;
  • L5.2 命中硬编码安全工具白名单 SAFE_TOOL_ALLOWLIST(autoMode.ts:61,read/grep/ls 之类)直接放行;
  • L5.2.5 正则的破坏性命令硬拦截跑在 LLM 之前——注释写得很直白:这样「API 挂了或分类器判错都不可能放过破坏性 git/IaC 命令」;
  • 都没命中,才调 classifyAction(packages/core/src/permissions/classifier.ts:138):阶段 1 一个只输出 {shouldBlock: boolean}maxOutputTokens: 32、超时 10 秒的快判;只有判「该拦」才进阶段 2 出理由的慢判。
  • 全程 fail-closed:分类器构造提示失败、超时、报错,一律当作「不可用 → 拦」。

这套「快路 → 确定性硬规则 → 廉价 LLM → 昂贵 LLM」的阶梯,是整个仓库最值得抄的一段。

⑤ 编辑匹配的多级降级,逐级放宽「像不像」。 模型给的 old_string 几乎从不和文件里的字节一模一样。findMatchedSlice(packages/core/src/utils/editHelper.ts:241)按顺序试:字面量精确匹配 → Unicode 等价归一化后匹配(把各种破折号、弯引号、全角空格映射回 ASCII,见 editHelper.ts:20UNICODE_EQUIVALENT_MAP)→ 逐行序列匹配(findLineBasedMatch)→ 行尾空白归一化后再试 → 容忍模式末尾多一个空行。命中即停,且返回的是原文里的真实切片,替换用原文而不是模型给的近似文本。

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

按符号名 grep 比按行号更抗漂移;行号 as-of f11bb31

主题文件路径符号名
会话总调度 / 一轮的组装packages/core/src/core/client.ts:208,1647GeminiClientGeminiClient.sendMessageStream
历史管理 + 自动压缩入口packages/core/src/core/geminiChat.ts:1360,1723GeminiChatGeminiChat.sendMessageStream
一次采样的事件解析packages/core/src/core/turn.ts:357,368,51TurnTurn.runGeminiEventType
非交互主循环packages/cli/src/nonInteractiveCli.ts:1242,1408runNonInteractive 内的 while (true)、工具结果回灌
交互式流驱动packages/cli/src/ui/hooks/useGeminiStream.ts:392,3002useGeminiStreamsubmitQuery
模型协议接口packages/core/src/core/contentGenerator.ts:37,55,343ContentGeneratorAuthTypecreateContentGenerator
OpenAI 路 + 厂商方言packages/core/src/core/openaiContentGenerator/index.ts:42,57createOpenAIContentGeneratordetermineProvider
OpenAI ↔ Gemini 双向转换packages/core/src/core/openaiContentGenerator/converter.ts:376,1087convertGeminiRequestToOpenAIconvertOpenAIResponseToGemini
Anthropic 路packages/core/src/core/anthropicContentGenerator/AnthropicContentGeneratorAnthropicContentConverter
Qwen OAuth 路packages/core/src/qwen/qwenContentGenerator.ts:27QwenContentGenerator(继承 OpenAI 实现)
所有请求的日志包装层packages/core/src/core/loggingContentGenerator/loggingContentGenerator.ts:103LoggingContentGenerator
工具基类(声明 vs 调用两段式)packages/core/src/tools/tools.ts:194,389,82DeclarativeToolBaseDeclarativeToolBaseToolInvocation
工具注册表 / 延迟披露packages/core/src/tools/tool-registry.ts:184,287,703ToolRegistryregisterFactoryrevealDeferredTool
惰性注册全部核心工具packages/core/src/config/config.ts:5780Config.createToolRegistry
工具名清单packages/core/src/tools/tool-names.ts:20ToolNames
按需搜工具packages/core/src/tools/tool-search.ts:449ToolSearchTool
编辑工具 / 容错匹配packages/core/src/tools/edit.ts:754packages/core/src/utils/editHelper.ts:241,313EditToolfindMatchedSlicenormalizeEditStrings
Shell 工具packages/core/src/tools/shell.ts:4801,1525ShellToolShellToolInvocation
工具调度与闸门顺序packages/core/src/core/coreToolScheduler.ts:1070,1804,2049CoreToolScheduler_scheduleL3→L4→L5 Permission Flow
权限流(工具默认值 + 规则引擎)packages/core/src/core/permissionFlow.ts:56,111evaluatePermissionFlowneedsConfirmation
审批模式枚举packages/core/src/config/config.ts:233ApprovalMode(plan/default/auto-edit/auto/yolo)
AUTO 模式快速通道packages/core/src/permissions/autoMode.ts:61,445,646SAFE_TOOL_ALLOWLISTpassesAcceptEditsFastPathevaluateAutoMode
两阶段安全分类器packages/core/src/permissions/classifier.ts:138,43,45classifyActionSTAGE1_TIMEOUT_MSSTAGE2_TIMEOUT_MS
破坏性命令硬拦截packages/core/src/permissions/destructive-commands.tsisDestructiveCommand
钩子系统packages/core/src/hooks/hookSystem.ts:56packages/core/src/hooks/types.ts:22HookSystemHookEventName(20 种事件)
工具前后钩子触发点packages/core/src/core/toolHookTriggers.ts:107,227firePreToolUseHookfirePostToolUseHook
沙箱启动(容器 / macOS seatbelt)packages/cli/src/utils/sandbox.ts:177start_sandboxpackages/cli/src/utils/sandbox-macos-*.sb
系统提示与压缩提示packages/core/src/core/prompts.ts:110,392getCoreSystemPromptgetCompressionPrompt
QWEN.md 层级加载packages/core/src/utils/memoryDiscovery.ts:462loadServerHierarchicalMemory
自动记忆packages/core/src/memory/manager.ts:411packages/core/src/memory/recall.ts:255MemoryManagerselectRelevantAutoMemoryDocuments
技能装载packages/core/src/skills/skill-manager.ts:75skill-activation.ts:80SkillManagerSkillActivationRegistry
上下文压缩packages/core/src/services/chatCompressionService.ts:311,55ChatCompressionServiceDEFAULT_PCT(0.7 触发线)
子代理packages/core/src/tools/agent/agent.ts:414packages/core/src/subagents/subagent-manager.ts:99AgentToolSubagentManager
工作流编排packages/core/src/agents/runtime/workflow-orchestrator.ts:1551WorkflowOrchestrator
多 agent 团队packages/core/src/agents/team/TeamManager.ts:136TeamManager
竞技场(多 agent 对比)packages/core/src/agents/arena/ArenaManager.tsArenaManagerArenaAgentClient
循环检测packages/core/src/services/loopDetectionService.tsLoopDetectionService
MCP 客户端packages/core/src/tools/mcp-client-manager.tsmcp-tool.tsMcpClientManagerDiscoveredMCPTool