花 3 个月从零写了个 AI Coding Agent,HumanEval 跑出 87.8%,和 GPT-4o 持平
来源:互联网
时间:2026-07-18 07:18:20
# 从零构建 AI Agent:6 个让我重新理解"智能体"的工程决策
说实话,现在市面上关于 AI Agent 的框架已经多到让人有点眼花缭乱了。LangChain、CrewAI、AutoGPT……每个都号称"几行代码就能搭一个 Agent"。但当你真正想搞清楚一个 Agent 的内部机制——不是怎么用,而是怎么造——你会发现,大多数框架把核心复杂度都藏在了抽象层后面。
## 先看结果
在 HumanEval 基准(164 道 Python 函数补全题)上,OmniAgent 驱动的 deepseek-v4-pro 跑出了 144/164 (87.8%) 的成绩:

| 对比 | HumanEval pass@1 |
| --- | --- |
| Claude 3.5 Sonnet | ~92% |
| GPT-4o | ~90% |
| OmniAgent + v4-pro | 87.8% |
| GPT-4 | ~87% |
| DeepSeek V3 | ~87% |
| CodeLlama 34B | ~56% |
但分数只是结果。更值得聊的,是这个 Agent 背后的设计思路。
## 为什么写这篇文章
2026 年的今天,AI Agent 框架已经多到让人麻木。但当你真正想理解一个 Agent 的内部机制——不是怎么用,而是怎么造——会发现大多数框架把复杂度藏在了抽象层后面。
我花了几个月从零写了一个终端 AI Agent(OmniAgent),不是为了跟 Claude Code 竞争,是为了把 Agent 核心机制做透、看懂。下面分享 6 个关键设计决策。
## 决策 1:引擎是类,不是 Prompt
第一反应可能是:切换 Agent 行为 = 换 system prompt。加一句"你要先做计划再执行"就行。
但实际问题是:Prompt 只能影响 LLM 输出内容,不能改变控制流。
ReAct 需要 `observe → think → act → observe` 循环,Plan-Execute 需要 `decompose → topological_sort → parallel_execute → synthesize`,Reflection 需要 `execute → critic_review → revise → critic_review` 双模型多轮。
这三个的代码结构完全不同。把它们压在 prompt 里,等于让 LLM 自己管理控制流——而 LLM 最不擅长的就是保持状态一致性。
```
omniagent/engine/
├── direct_engine.py # 一条路走到黑
├── react_engine.py # 循环直到 finish
├── plan_execute_engine.py # DAG + 拓扑排序
├── reflection_engine.py # 双模型互审
├── novel_engine.py # 创意写作专用
├── plan_react_engine.py # 计划 → ReAct
├── plan_reflection_engine.py # 计划 → 审查
└── react_reflection_engine.py # ReAct → 审查
```
每个引擎是独立的类,有自己的 `run()` 方法。切换范式不是改 prompt 文本——是 `EngineFactory.create(mode)` 换一个类。
## 决策 2:断路器不应该"一次挂、次次挂"
直觉上,工具调用失败 → 捕获异常 → 返回错误给 LLM → LLM 决定下一步。
但实际问题是:LLM 会反复用同样的参数调同一个失败的工具。你见过 LLM 在循环里连续 5 次 `read_file("不存在的路径")` 吗?我见过。
标准答案是加个重试装饰器。但 Agent 的真正问题是:有些失败是可恢复的(网络抖动),有些是终局性的(文件不存在)。重试机制不分青红皂白统一退避,浪费 token 和时间。
我做的:
```python
# 每个工具有独立的断路器实例
breaker = CircuitBreaker(
failure_threshold=3, # 3 次连续失败 → 熔断
cooldown_seconds=30, # 30s 后进 half_open
backoff_multiplier=2, # 再失败冷却翻倍,上限 600s
)
```
关键不是"熔断"本身——是这个状态跨 LLM 轮次持久化。`GLOBAL_BREAKERS` 字典在 Agent 的整个生命周期存活。
## 决策 3:上下文压缩不是"取最后 N 条"
第一反应:对话太长 → 只保留最近 10 条消息。
但实际问题是:最近 10 条可能全是工具输出(`read_file` 返回的 200 行代码),而 15 条之前的 user prompt 才是真正需要的语义信息。
我实现的是 6 步压缩流水线:
1. 摘要 → LLM 生成对话摘要
2. 精简 → 工具输出超过 20 行部分用 [N lines truncated] 替换
3. 去重 → 连续相同的工具调用合并为 "[N repeated calls]"
4. 评分 → 按语义密度给每条消息打分
5. 裁剪 → 保留 Top-N 条高分消息
6. 重组 → 摘要 + 裁剪结果拼回 messages 列表
触发条件不是"消息数 > N",而是 Token 窗口达 80% 。
## 决策 4:花钱也要分阶段
Agent 在任务尾声还在调 `list_files` 和 `search_files`——Token 被浪费在无意义的浏览上。
解决:三阶段软预算。
| 阶段 | Token 配额 | 可用工具 |
| --- | --- | --- |
| EXPLORE | 25% | 全部 20 个 |
| EXECUTE | 50% | 全部 20 个 |
| CONVERGE | 25% | 禁用 7 个纯探索工具 |
## 决策 5:MCP 子进程管理不是 `subprocess.run`
有几个坑必须踩过才知道:
1. `readline()` 会无限阻塞
2. 进程崩溃变僵尸
3. 守护进程挂了 Agent 不知道
解决:`select` + 墙钟超时 + `terminate()` + 兜底 `kill()`。守护进程崩溃自动重启(最多 3 次)。
## 决策 6:空输入检测不能只靠 LLM
LLM 有时候会返回一个看似有内容实则空洞的回答——实际什么都没做。HollowDetector 用 15 个正则 + 组合判定来识别空洞回答,靠模式匹配 + 工具调用记录交叉校验。
## 这些决策的通用性
上面 6 个设计决策不绑定 OmniAgent。无论你用什么框架——LangChain、CrewAI、自研——只要你的 Agent 需要:
- 多种推理模式 → 把范式差异放在控制流层,不要全压在 prompt 上
- 可靠执行 → 加断路器,且状态要跨轮次持久化
- 长对话 → 上下文压缩要按语义密度裁剪,不是简单截断
- 预算控制 → 分阶段限制可用工具,收束阶段裁掉探索型工具
- MCP 集成 → 用 select + 超时而不是 readline
- 输出质量 → 空洞检测基于模式匹配,不要靠 LLM 自检
19K 行 Python,MIT 开源,1000+ 测试,HumanEval 87.8%。如果你也在学习 Agent 架构,这里面有不少值得参考的设计思路。 -
- 关于宇宙的好的网名有哪些
- 角色扮演 | 1
- 网名