第 9 章:源码的地图

个人公众号
1
2
3
4
卷一:追踪请求                    卷二:理解设计
[1] HTTP -> [2] Runner -> ... -> [8] 响应归途 ──┐
v
[9] 源码的地图 <- you are here

前八章我们跟一个请求走完了完整生命周期。你已经见过 FastAPI、Runner、Agent、Provider、Tool 这些组件。但在你脑海中的地图可能还是零散的——它们在源码里的哪里?谁依赖谁?哪些文件最重要?这一章,我们拉到 30000 英尺高空,鸟瞰整个源码。


问题

src/qwenpaw/ 下有 411 个 Python 文件、约 13 万行代码。怎么不迷路?哪些是核心、哪些是外围?改一个功能要改哪些文件?

术语其实很简单

术语:依赖图(Dependency Graph)
想象城市地图——“火车站”到”机场”有道路连接。依赖图就是模块之间的”道路图”——A 模块 import B 模块,就说”A 依赖 B”。顺着依赖图走,能找到代码的调用关系。

术语:入口点(Entry Point)
想象一个主题公园的入口——所有游客从这里进。入口点是程序开始执行的地方——qwenpaw 命令就是入口点,所有代码从这里开始被调用。

探索

源码的 15 个街区

src/qwenpaw/ 下有 15 个子包(目录),每个是一个”街区”,负责一类功能:

文件数职责
agents/164核心:Agent 运行时、ReAct 循环、工具、技能、记忆
app/131FastAPI 应用:路由、17 个聊天渠道、Runner
cli/30命令行:qwenpaw initappdoctor
security/18安全:工具调用守卫、技能扫描、密钥存储
providers/14LLM 提供商:OpenAI、Anthropic、Gemini、Ollama 等
backup/13备份恢复:配置和 Agent 状态
utils/8工具函数:日志、系统信息
local_models/6本地模型:llama.cpp 后端管理
plugins/6插件系统:加载器、注册表
config/5配置:加载、保存、验证
token_usage/3Token 用量:记录、统计
tunnel/3隧道:Cloudflare 内网穿透
envs/2环境变量:持久化存储
agent_stats/3Agent 统计:每日用量聚合

另外还有 4 个顶层模块:constant.py(常量)、exceptions.py(异常层次)、__version__.py(版本号)、__main__.py(入口)。

依赖层次——谁依赖谁

从底层到顶层,代码分 7 层:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
层 0(叶子节点,无内部依赖):
config/ utils/ token_usage/ tunnel/ plugins/ agent_stats/

层 1:
constant/ exceptions/ security/

层 2:
providers/ envs/

层 3:
local_models/ (注意:与 providers 有循环依赖)

层 4:
app/ backup/

层 5:
agents/

层 6:
cli/

核心规律:越底层的包越独立,越上层的包依赖越多config/utils/ 不依赖任何内部包——它们被所有其他包使用。agents/ 是最核心的包,它依赖 app/(上下文)、config/(配置)、security/(安全检查)。

最重要的 10 个文件

按”改动频率高、理解系统必须读”排序:

文件行数为什么重要
agents/react_agent.py1231Agent 核心——初始化、推理、媒体过滤
app/_app.py~400FastAPI 应用——中间件、路由注册、启动流程
app/runner/runner.py900请求处理——创建 Agent、Session 管理
agents/model_factory.py1051模型工厂——Provider 查找、包装链
providers/provider_manager.py1747Provider 注册表——20+ 个提供商
agents/tool_guard_mixin.py933安全拦截——工具调用守卫
config/config.py1728配置模型——所有配置结构的定义
agents/prompt.py~300提示词拼装——AGENTS/SOUL/PROFILE
agents/skills_manager.py3479技能管理——安装、加载、生命周期
providers/retry_chat_model.py477重试限流——LLM 调用的韧性保障

理解了这 10 个文件,就理解了 QwenPaw 70% 的核心逻辑。

三个入口点

用户通过三种方式与 QwenPaw 交互:

1
2
3
4
5
6
7
8
1. 命令行入口:python -m qwenpaw 或 qwenpaw
__main__.py -> cli/main.py -> 各子命令

2. Web 入口:http://127.0.0.1:8088
app/_app.py -> FastAPI -> routers/ -> runner/ -> agents/

3. 聊天平台入口:钉钉/飞书/微信/Telegram 等
app/channels/<platform>/ -> runner/ -> agents/

所有入口最终汇聚到 agents/react_agent.py——QwenPawAgent 是整个系统的核心。

agents/ 的内部结构

agents/ 是最大的包(164 个文件),内部结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
agents/
|-- react_agent.py # QwenPawAgent 核心
|-- model_factory.py # 模型工厂
|-- tool_guard_mixin.py # 安全 Mixin
|-- prompt.py # 提示词拼装
|-- command_handler.py # 斜杠命令处理
|-- skills_manager.py # 技能管理器
|-- tools/ # 18 个内置工具
| |-- shell.py # Shell 命令
| |-- file_io.py # 文件读写
| |-- file_search.py # grep/glob 搜索
| |-- browser_control.py # 浏览器自动化
| +-- ...
|-- skills/ # 预打包技能
| |-- browser/
| |-- pdf/
| |-- office/
| +-- ... (34 个技能)
|-- hooks/ # 生命周期钩子
| |-- bootstrap.py # 首次引导
| +-- memory_compaction.py # 记忆压缩
|-- memory/ # 记忆管理
|-- acp/ # Agent 通信协议
+-- mission/ # 任务系统

app/ 的内部结构

app/ 是第二大的包(131 个文件):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
app/
|-- _app.py # FastAPI 应用创建
|-- auth.py # 认证中间件
|-- multi_agent_manager.py # Agent 调度
|-- routers/ # REST API 路由
| |-- console.py # /api/console/chat
| |-- agents.py # /api/agents/*
| |-- providers.py # /api/providers/*
| +-- ... (25 个路由文件)
|-- channels/ # 聊天平台集成
| |-- console/ # Web 控制台
| |-- dingtalk/ # 钉钉
| |-- feishu/ # 飞书
| |-- weixin/ # 微信
| |-- telegram/ # Telegram
| |-- discord/ # Discord
| +-- ... (17 个渠道)
|-- runner/ # 请求执行
| |-- runner.py # AgentRunner
| |-- session.py # SafeJSONSession
| +-- task_tracker.py # SSE 流管理
|-- mcp/ # MCP 客户端
|-- crons/ # 定时任务
+-- workspace/ # 工作区管理

渠道集成文件(app/channels/*/channel.py)通常是最大的文件——钉钉渠道有 3625 行,飞书有 2267 行。这是因为每个聊天平台的 API 各不相同,适配逻辑很复杂。

按功能找文件

常见任务对应的文件:

“我想了解……”去看这些文件
HTTP 请求处理app/_app.py, app/routers/console.py
Agent 创建和推理agents/react_agent.py
系统提示词agents/prompt.py
工具执行agents/tools/*.py, agents/tool_guard_mixin.py
模型调用agents/model_factory.py, providers/*.py
重试和限流providers/retry_chat_model.py, providers/rate_limiter.py
安全检查security/tool_guard/*.py
技能系统agents/skills_manager.py
配置config/config.py
记忆管理agents/memory/*.py
新增聊天平台app/channels/<platform>/channel.py

实验

findwc 验证本章的数据:

  1. 统计 Python 文件数:find src/qwenpaw -name "*.py" | wc -l
  2. 统计代码行数:find src/qwenpaw -name "*.py" -exec cat {} + | wc -l
  3. 找到最大的 10 个文件:find src/qwenpaw -name "*.py" -exec wc -l {} + | sort -rn | head -11

预期输出:约 411 个文件、约 13 万行,最大的文件是 agents/tools/browser_control.py(3647 行)。

工程权衡

为什么 agents/ 和 app/ 这么大?

agents/(164 文件)包含 34 个预打包技能,每个技能有中英文两个版本。app/(131 文件)包含 17 个聊天渠道适配器,每个渠道的 API 适配逻辑都不同。这两个包看起来大,但大部分是”模板化”的代码——理解了一个工具的实现,就理解了所有工具;理解了一个渠道的适配器,就理解了所有渠道。

为什么用扁平的包结构而非深层嵌套?

QwenPaw 只有两层包结构(qwenpaw.agents.tools.shell),没有更深的嵌套。这简化了 import 路径,也方便 grep 搜索。深层嵌套虽然更”整洁”,但在实际开发中增加了认知负担。

常见误区

误区:411 个文件都要读才能理解 QwenPaw?

不需要。核心文件只有 10 个左右。渠道适配器(17 个)、预打包技能(34 个)、CLI 命令(30 个)都是”量大同质”的代码——理解了一个,就理解了全部。建议从 react_agent.py 开始,沿着调用链往外扩展。

动手环节

任务:在 IDE 中建立源码的心智地图。

步骤

  1. 在 IDE 中打开 src/qwenpaw/ 目录
  2. 折叠所有子目录,只看第一层
  3. 打开 agents/react_agent.py,搜索 class QwenPawAgent
  4. 打开 app/_app.py,搜索 app = FastAPI

预期输出:能看到源码的 15 个子包,以及两个核心入口(Agent 类和 FastAPI 应用)。

自检

  • 知道 agents/ 是最大的包,包含 164 个文件
  • 知道 config/utils/ 是最底层的包,不依赖任何内部包
  • 知道所有入口最终汇聚到 QwenPawAgent

源码地图画好了。接下来的五章,我们从”追踪请求”切换到”理解设计”——为什么 Agent 用 Mixin?为什么 Provider 用抽象基类?为什么 Channel 用适配器模式?下一章我们从 Agent 的”身世”开始,理解 Mixin 和 MRO。