Skip to content

大型项目源码阅读技巧——以hermes-agent为例 #70

@zhuzhh

Description

@zhuzhh

大型项目源码阅读技巧

以 hermes-agent 为实例总结的实用技巧。


核心原则

不要试图理解全部代码,先建立"地图",需要时再深入具体地形。


技巧 1: 先抓骨架,不看细节

找核心类的 __init__ 和主入口方法,画出调用链。

# 定位核心类
grep -n "class AIAgent" run_agent.py

# 找关键方法
grep -n "def __init__\|def run_conversation\|def chat" run_agent.py | head -20

输出示例:

1028:class AIAgent:
1051:    def __init__(
11613:    def run_conversation(
15469:    def chat(self, message: str) -> str:

目的: 建立"这个文件有哪些重要结构"的地图。


技巧 2: 用 grep 做"点跳"

像 IDE 的"跳转到定义",顺着函数名/变量名跳转。

# 某个功能在哪里被处理?
grep -n "tool_calls\|handle_function_call" run_agent.py | head -30

# 某个变量的所有使用点
grep -n "_budget_grace_call\|final_response" run_agent.py

# 某个配置项的影响范围
grep -n "compression_enabled\|max_iterations" run_agent.py

注意: 用 head -N 限制输出,避免被淹没。


技巧 3: 读代码块而非整文件

offset + limit 精确读取关键段落。

# 读 __init__ 参数列表
read_file(path="run_agent.py", offset=1051, limit=200)

# 读核心 while 循环
read_file(path="run_agent.py", offset=12038, limit=250)

目的: 避免上下文爆炸,专注当前要理解的逻辑块。


技巧 4: 追踪数据流向

关注"输入什么 → 经过什么变换 → 输出什么"。

示例: 消息流

user_message (str)
  ↓
run_conversation(messages: list)
  ↓
_build_system_prompt() → system_prompt (str)
  ↓
api_messages = [system] + messages
  ↓
_build_api_kwargs() → kwargs (dict)
  ↓
API response
  ↓
if tool_calls: execute → results → append to messages
  ↓
final_response (str)

技巧: 给每个阶段标注类型,帮助理解边界。


技巧 5: 找"决策点"和"分支点"

大型项目的核心是"在什么条件下走哪条路"。

# 找条件分支
grep -n "if.*tool_calls\|elif\|else:" run_agent.py | head -40

# 找循环控制
grep -n "while\|for\|continue\|break" run_agent.py | head -40

# 找 return 点(函数有几个出口)
grep -n "return {" run_agent.py | head -20

目的: 理清"正常路径"和"异常/边界路径"。


技巧 6: 利用结构性注释

大型项目通常有分隔线注释,它们是逻辑块的边界。

# ── Stable tier ────────────────────────────────────────────────
# ── Pre-API-call /steer drain ──────────────────────────────────
# ── Concurrent execution ─────────────────────────────────────────
# ── Logging / callbacks ──────────────────────────────────────────

技巧: 快速扫这些注释,定位到感兴趣的块。


技巧 7: 对比阅读(相似函数对比)

多个相似实现时,并排看差异。

# 找相似命名的函数
grep -n "def _execute_tool_calls" run_agent.py

输出:

10283:    def _execute_tool_calls(self, ...):
10417:        tool_calls = assistant_message.tool_calls
10564:    def _execute_tool_calls_concurrent(self, ...):
10913:    def _execute_tool_calls_sequential(self, ...):

然后对比 _execute_tool_calls_concurrent vs _execute_tool_calls_sequential:

  • 并发版多了 ThreadPoolExecutor
  • 核心工具调用都是 _invoke_tool()
  • 差异在调度方式,不在核心逻辑

技巧 8: 从"异常"和"重试"反推正常逻辑

读错误处理代码,反推正常流程的预期。

grep -n "except\|retry\|Error\|failed" run_agent.py | head -30

示例:

_invalid_tool_retries < 3  → 工具名校验有 3 次重试
_empty_content_retries     → 空响应有重试机制
max_retries = self._api_max_retries  → API 调用有重试

技巧 9: 关注"配置"和"开关"

feature flag 能快速理解能力边界。

# 环境变量开关
grep -n "HERMES_\|os.getenv\|os.environ" run_agent.py | head -30

# 配置项
grep -n "config\|enabled\|disabled" run_agent.py | head -30

示例:

HERMES_DUMP_REQUESTS  → 打印 API 请求 debug
HERMES_REDACT_SECRETS → 敏感信息脱敏开关
compression_enabled   → 上下文压缩开关

技巧 10: 善用工具链配合

组合使用 grep → read_file → grep 循环。

# 1. grep 找行号
grep -n "def run_conversation" run_agent.py
# → 11613

# 2. read_file 读该方法
read_file(path="run_agent.py", offset=11613, limit=200)

# 3. 发现它调用了 _build_system_prompt,继续 grep
grep -n "_build_system_prompt\|_build_api_kwargs" run_agent.py

# 4. 重复直到理解完整链路

技巧 11: 找"入口点"和"出口点"

每个函数/模块搞清楚三个问题:

  1. 谁调用它?(入口)
  2. 它调用谁?(内部依赖)
  3. 它返回什么?(出口)
# 找谁调用了 run_conversation
grep -n "run_conversation\|\.chat(" cli.py | head -20

# 找它调用了哪些内部方法
grep -n "self\._\|self\." run_agent.py | grep "def " | head -30

技巧 12: 画调用图

用 ASCII 图记录调用关系,帮助记忆。

run_conversation()
  ├── _build_system_prompt()
  │     ├── load_soul_md()
  │     ├── build_skills_system_prompt()
  │     └── build_environment_hints()
  ├── _build_api_kwargs()
  │     └── _get_transport()
  ├── _interruptible_streaming_api_call()
  ├── _execute_tool_calls()
  │     ├── _execute_tool_calls_sequential()
  │     └── _execute_tool_calls_concurrent()
  │           └── _invoke_tool()
  │                 └── handle_function_call() [model_tools.py]
  │                       └── registry.dispatch()
  └── _compress_context()

Hermes 特定技巧

忽略 Hook 调用

代码里到处是 invoke_hook(),这是插件扩展点,读核心逻辑时可以跳过。

# 快速定位主逻辑,跳过 hook
grep -n "invoke_hook\|plugin" run_agent.py | wc -l  # 看有多少处

先理解一种 API 模式

_build_api_kwargs() 里有大量 if self.api_mode == "...":

  • 先理解 chat_completions(默认)
  • 再看 anthropic_messages / codex_responses 的差异

Transport 抽象

不同 provider 的差异封装在 agent/ 下的 transport 文件:

  • agent/anthropic_adapter.py
  • agent/codex_responses_adapter.py
  • agent/bedrock_adapter.py

理解 _get_transport() 返回什么,就能明白 API 差异。


检查清单

读一个新模块时,按这个顺序:

  1. grep -n "class\|def " xxx.py | head -30 — 抓结构
  2. __init__ — 理解状态和依赖
  3. 找主入口方法 — 理解输入输出
  4. 找 while/for 循环 — 理解核心循环
  5. 找 if/else 分支 — 理解决策点
  6. 找 return — 理解出口
  7. 找 except/retry — 理解容错
  8. 找 config/env — 理解开关

工具对比

场景 工具 示例
定位函数/类 grep -n grep -n "def run_conversation" run_agent.py
读代码块 read_file read_file(path, offset, limit)
找文件 find/rg find . -name "*.py" | xargs grep -l "xxx"
看调用关系 grep 函数名 grep -n "run_conversation" *.py
理解结构 目录树 tree -L 2ls -la

总结

核心心法:

  1. 地图优先 — 先建立结构认知,不纠结细节
  2. 点跳追踪 — 顺着函数名/变量名跳转
  3. 数据流向 — 标注每步的类型变化
  4. 分支决策 — 找 if/else 理解条件逻辑
  5. 异常反推 — 从错误处理理解正常预期

应用这些技巧,15,000 行的 run_agent.py 可以在 2-3 小时内建立完整认知。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions