首页 > 教程攻略 > ai教程 >花 3 个月从零写了个 AI Coding Agent,HumanEval 跑出 87.8%,和 GPT-4o 持平

花 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%) 的成绩: ![花 3 个月从零写了个 AI Coding Agent,HumanEval 跑出 87.8%,和 GPT-4o 持平](http://img.318050.com/uploads/20260713/17839023206a543070aa328437711792.webp) | 对比 | 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 架构,这里面有不少值得参考的设计思路。