第 9 章:源码的地图

1 | 卷一:追踪请求 卷二:理解设计 |
前八章我们跟一个请求走完了完整生命周期。你已经见过 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/ | 131 | FastAPI 应用:路由、17 个聊天渠道、Runner |
cli/ | 30 | 命令行:qwenpaw init、app、doctor 等 |
security/ | 18 | 安全:工具调用守卫、技能扫描、密钥存储 |
providers/ | 14 | LLM 提供商:OpenAI、Anthropic、Gemini、Ollama 等 |
backup/ | 13 | 备份恢复:配置和 Agent 状态 |
utils/ | 8 | 工具函数:日志、系统信息 |
local_models/ | 6 | 本地模型:llama.cpp 后端管理 |
plugins/ | 6 | 插件系统:加载器、注册表 |
config/ | 5 | 配置:加载、保存、验证 |
token_usage/ | 3 | Token 用量:记录、统计 |
tunnel/ | 3 | 隧道:Cloudflare 内网穿透 |
envs/ | 2 | 环境变量:持久化存储 |
agent_stats/ | 3 | Agent 统计:每日用量聚合 |
另外还有 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.py | 1231 | Agent 核心——初始化、推理、媒体过滤 |
app/_app.py | ~400 | FastAPI 应用——中间件、路由注册、启动流程 |
app/runner/runner.py | 900 | 请求处理——创建 Agent、Session 管理 |
agents/model_factory.py | 1051 | 模型工厂——Provider 查找、包装链 |
providers/provider_manager.py | 1747 | Provider 注册表——20+ 个提供商 |
agents/tool_guard_mixin.py | 933 | 安全拦截——工具调用守卫 |
config/config.py | 1728 | 配置模型——所有配置结构的定义 |
agents/prompt.py | ~300 | 提示词拼装——AGENTS/SOUL/PROFILE |
agents/skills_manager.py | 3479 | 技能管理——安装、加载、生命周期 |
providers/retry_chat_model.py | 477 | 重试限流——LLM 调用的韧性保障 |
理解了这 10 个文件,就理解了 QwenPaw 70% 的核心逻辑。
三个入口点
用户通过三种方式与 QwenPaw 交互:1
2
3
4
5
6
7
81. 命令行入口: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
24agents/
|-- 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
24app/
|-- _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 |
实验
用 find 和 wc 验证本章的数据:
- 统计 Python 文件数:
find src/qwenpaw -name "*.py" | wc -l - 统计代码行数:
find src/qwenpaw -name "*.py" -exec cat {} + | wc -l - 找到最大的 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 中建立源码的心智地图。
步骤:
- 在 IDE 中打开
src/qwenpaw/目录 - 折叠所有子目录,只看第一层
- 打开
agents/react_agent.py,搜索class QwenPawAgent - 打开
app/_app.py,搜索app = FastAPI
预期输出:能看到源码的 15 个子包,以及两个核心入口(Agent 类和 FastAPI 应用)。
自检:
- 知道
agents/是最大的包,包含 164 个文件 - 知道
config/和utils/是最底层的包,不依赖任何内部包 - 知道所有入口最终汇聚到
QwenPawAgent
源码地图画好了。接下来的五章,我们从”追踪请求”切换到”理解设计”——为什么 Agent 用 Mixin?为什么 Provider 用抽象基类?为什么 Channel 用适配器模式?下一章我们从 Agent 的”身世”开始,理解 Mixin 和 MRO。