AI Agent 工程实践(39):第一次实现——先做一个最小 Agent

发布时间:2026-09-1

标签:AI Agent|工程实践|MVP|最小实现


架构画得再漂亮,也只是纸上的东西。

上一篇我画了四个节点:Task Router、Planner、Analyzer、Reviewer。听起来很完整,对吧?

但我决定,一个都不实现。

这一篇,我只写最窄的一条通路:用户问 → 调一个工具 → 读结果 → 回答

原因很简单:我想看看,这个"最小可跑"的版本,到底会怎么死。


系列导航


问题背景

这是第五阶段的第四篇,也是第一次真正写代码。

很多人会犯一个错:架构图里画了四个节点,就非得把四个都写出来才罢休。但我的经验是------先做一个故意很蠢的最小版本,让它跑起来,然后用它去暴露问题。

这个"最小版本"有个学名叫 MVP(Minimum Viable Product),但在这里,它的意义不是"证明能跑",而是"用最快的速度暴露它会怎么死"。

为什么暴露死亡这么重要?因为 Agent 项目最大的风险,不是"写不出来",而是"写了很多,但里面的假设全是错的"。你架构图里画的 Planner、Reviewer,可能根本解决不了真实问题------而这些问题,只有让最小版本跑起来、撞上真实问句,才会显现。

所以我这一篇,砍掉 Task Router、砍掉 Planner、砍掉 Reviewer,只保留一个 LLM + 两个工具(grep 和 read_file),让整条链路能跑通。


错误尝试

我差点犯了两个反方向的错。

第一个错:想一步到位。 想把四个节点、六个工具、Memory、评估集全部写完再跑。结果就是写了一个月,一行能跑的代码都没有,还积累了一堆"我以为对的"假设。

这一堆"以为对的假设"是最贵的。比如我以为"LLM 会自己选对工具",直到真实跑起来才发现它经常选错;我以为"读到文件就能定位 bug",直到真实跑起来才发现它会编造不存在的函数。这些假设,只有让代码真的跑起来才会被证伪------而一步到位的写法,让你把所有假设都攒到最后一起爆。

第二个错:觉得太简单不值得跑。 "一个 LLM + 两个工具,这有什么好跑的?" 但我告诉你,恰恰是这个最简单的版本,暴露了后面整整十篇要解决的问题。你只有真的跑起来,才能看到它选错工具、读错文件、凭空编造结论的样子。

两个错误殊途同归:都推迟了"第一次看到真实失败"的时间点。

这里我要特别强调一个心态:在 Agent 开发里,"先跑起来"的优先级,高于"架构正确"。 因为 Agent 的行为极度依赖真实执行环境,你画在纸上的架构,有一半会在第一次真实运行时被推翻。与其花一个月搭一个"看起来正确"的架构,不如花两天搭一个"一定能跑"的最小版,然后用真实失败去修正架构。


关键观察

所以这篇的核心动作就一个:用最短的路径,让 Agent 第一次真正跑起来,然后诚实地记录它为什么不能直接上线。

最小版本长这样:

没有任何花哨的东西。一个循环:LLM 决定调工具 → 工具执行 → 结果塞回上下文 → LLM 继续,直到它觉得该回答了。

核心洞察:

MVP 的意义不是证明能跑,而是用最快的速度暴露它会怎么死。

这个"最小版"的价值,不在于它能做什么,而在于它把"Agent 运行的每一个环节"都摊开在你面前:LLM 决策、工具选择、参数传递、结果解析、终止判断------每一个环节都可能出问题,而这些问题,只有最小版能让你一个一个看清楚。


最终方案:100 行的最小 Agent

下面是 Repo Doctor v0 的完整实现,用 Python 手写一个 tool-calling loop,不依赖任何框架,就是为了看清每一环:

复制代码
# repo_doctor/v0/main.py ------ 最小可跑版本,约 100 行
import subprocess, json
from openai import OpenAI

client = OpenAI(base_url="https://api.deepseek.com", api_key="...")

SYSTEM = """你是仓库诊断助手。你可以调用工具来调查代码库。
可用工具:
- grep(keyword): 在仓库中搜索关键字,返回匹配的文件和行
- read_file(path): 读取指定文件内容
调查充分后,直接输出结论。"""

TOOLS = [
    {"type": "function", "function": {
        "name": "grep",
        "description": "在仓库中搜索关键字,返回匹配的文件和行",
        "parameters": {"type": "object", "properties": {
            "keyword": {"type": "string"}}, "required": ["keyword"]}}},
    {"type": "function", "function": {
        "name": "read_file",
        "description": "读取指定文件内容",
        "parameters": {"type": "object", "properties": {
            "path": {"type": "string"}}, "required": ["path"]}}},
]

def call_tool(name, args, repo):
    if name == "grep":
        r = subprocess.run(["grep", "-rn", args["keyword"], repo],
                           capture_output=True, text=True, timeout=10)
        return r.stdout[:3000] or "(无匹配)"
    if name == "read_file":
        with open(f"{repo}/{args['path']}", encoding="utf-8", errors="ignore") as f:
            return f.read()[:3000]
    return "(未知工具)"

def run_agent(query, repo, max_steps=6):
    messages = [{"role": "system", "content": SYSTEM},
                {"role": "user", "content": f"仓库路径 {repo},问题:{query}"}]
    for _ in range(max_steps):
        resp = client.chat.completions.create(
            model="deepseek-chat", messages=messages, tools=TOOLS)
        msg = resp.choices[0].message
        if msg.tool_calls:
            messages.append(msg)
            for tc in msg.tool_calls:
                result = call_tool(tc.function.name,
                                   json.loads(tc.function.arguments), repo)
                messages.append({"role": "tool", "tool_call_id": tc.id,
                                 "content": result})
        else:
            return msg.content
    return "(达到最大步数仍未完成)"

if __name__ == "__main__":
    print(run_agent("这个项目里支付相关的逻辑在哪?", "/path/to/hello-agents"))

跑一次,输出大概长这样:

复制代码
结论:支付相关逻辑在 payment.py 中,核心函数是 payment_process(),
它负责订单支付和回调处理。建议查看该函数附近的 payment_callback()。

这一版能跑。 但先别急着高兴,我们仔细看这个结论------它有一个致命的问题:payment_process() 这个函数,仓库里根本不存在。 它是 Agent 编出来的。下一篇会专门解剖这个失败。

在进入"为什么不能上线"之前,先把这 100 行代码的每个关键环节点一遍,你就知道"最小版"到底暴露了哪些环节:

代码片段 环节 潜在问题(最小版就埋着)
SYSTEM + TOOLS 提示与工具描述 工具描述含糊,LLM 会选错工具(第 41 篇修)
call_tool 工具执行 参数不校验、结果截断 3000 字符(第 44 篇修)
for _ in range(max_steps) 终止控制 最大 6 步,可能"没查完就停"或"烧光"(第 41 篇修)
messages.append 上下文管理 无限增长,长任务爆上下文(第 45 篇修)
return msg.content 输出 结论无证据校验,幻觉直接进答案(第 40、42 篇修)

这段 100 行代码,每一行都对应着后面一篇要解决的问题。 这就是最小版的价值------它不是"最终产品的阉割版",而是"问题清单的具象化"。


架构图 / 流程图

把上面代码的执行流程画出来,你会看到它的"朴素":

看着很正常,对吧?但问题就藏在最后一步------那个 payment_process() 到底存不存在,Agent 根本没验证。

第二张图:这个循环的"隐患标注版"(发布提示:可用 draw.io 重画成正式图,与 Mermaid 图形成双图组合):

复制代码
   用户问句
     │
     ▼
┌────────────┐   ① 工具描述含糊 → 可能选错工具(40/41 篇)
│ Agent(LLM) │──────────────────────────────┐
└─────┬──────┘                              │
      │ ② 参数不校验 → 可能传错参数(41 篇)   │
      ▼                                      │
┌────────────┐   ③ 结果截断 3000 字符          │
│ call_tool  │    可能丢关键信息(44 篇)       │
└─────┬──────┘                              │
      │ ④ 结果塞回上下文,无校验                │
      ▼                                      │
┌────────────┐   ⑤ 结论无证据检查              │
│ Agent(LLM) │   幻觉直接进答案(40/42 篇)     │
└─────┬──────┘                              │
      │ ⑥ 输出结论                            │
      ▼                                      │
    答案 ◄───────────────────────────────────┘
    (payment_process() 可能是编的!)

这张图把"最小版"的每一个薄弱环节都标了出来。后面整整十篇,就是逐个把这些"隐患标注"换成"已修复"。


为什么不能直接上线

这一版跑通了,但我把它能跑和能上线分得很清。下面这张清单,就是我"故意留的技术债",也是后面整整十一篇的伏笔:

# 技术债 后果 后面哪篇解决
1 没有 Task Router,三种任务混在一起 该定位时去解释 47
2 没有 Planner,"查够了没"全凭感觉 草率下结论 / 烧 Token 40、41
3 工具描述太模糊,参数没约束 选错工具、传错参数 41、42
4 没有 Reviewer,结论无证据链 幻觉成灾,payment_process() 可能不存在 40、42
5 没有 State,调查过程不记录 无法回溯"为什么这样想" 42
6 没有评估集,改完好坏不知道 越改越玄学 43
7 上下文无上限,读文件会撑爆 长文件直接溢出 44、49
8 出错就死,无兜底 工具报错整个流程崩 44
9 无法复现(模型/Prompt/工具都没锁版本) 同一个问题两次答案不同 45
10 没有成本/延迟监控 烧多少 Token 全靠猜 46

这一篇的价值,就是把上面这十个坑提前摆在明面上。它们不是"以后可能会出问题",而是"现在就已经埋下了,只等真实问句来引爆"。


设计权衡

候选方案 优点 缺点 为什么不选
一步到位写完整架构 一次成型 一个月跑不起来,积累一堆未验证假设 推迟了看到真实失败的时间
用 LangGraph 框架搭 省事、规范 掩盖底层细节,出了问题看不懂 先手写 loop,看清每一环
100 行手写最小版 快、透明、暴露问题 功能残缺 最快暴露它会怎么死

关于"为什么不用 LangGraph"值得单独说:不是 LangGraph 不好,而是在这个阶段,你需要的不是框架的便利,而是对每一环的可见性。手写 loop,你能精确看到"LLM 这次调了什么工具、传了什么参数、工具回了什么"。等这套东西你想清楚了,第 49 篇再谈要不要换成框架。


常见误区(FAQ)

Q1:MVP 越少越好,是不是连工具都只留一个?

最少两个。一个工具(比如只留 read_file)无法暴露"工具选择"这个环节的问题------而工具选错恰恰是 Agent 最常见的失败之一(第 40 篇的 Tool Selection Error)。

Q2:为什么不用 LangGraph 搭 MVP?

对初学者来说,框架会掩盖"每一环发生了什么"。手写 100 行 loop,你被迫面对工具调用、参数解析、结果回填这些最底层的问题------这些问题在框架里被封装了,你直到出 bug 才知道它们存在。

Q3:这 100 行代码最后会被扔掉吗?

不会全扔。它的"工具调用循环"骨架会被保留,后续的 Task Router、Planner、Reviewer 都是在这个骨架上"加节点",而不是推倒重来。MVP 不是一次性用品,是后续版本的"可运行的基线"。

Q4:怎么判断 MVP"够了"?

一个简单标准:它能完整跑完一次端到端任务(哪怕结果错误)。能跑 + 会错,就是最好的 MVP 状态------因为下一步就是去解剖"它怎么错的"(第 40 篇)。


总结

✅ 先做最小可跑版本,故意砍掉所有"高级节点"。

✅ 100 行手写 loop,就是为了看清每一环。

✅ 这 100 行里,每一行都对应一篇后续要解决的问题。

✅ 这一版能跑,但埋了 10 个技术债。

✅ 铁律:MVP 的意义不是证明能跑,而是最快暴露它会怎么死。

✅ 下一篇,让这个最小版跑真实案例,看它第一次做错。


参考资料

  • OpenAI Function Calling 文档 → 为什么引用:tool-calling loop 的 API 用法,是这 100 行的基础。
  • 《The Pragmatic Programmer》"tracer bullet" 概念 → 为什么引用:先打通一条最小链路再扩展,正是本篇的方法论来源。

系列导航

本文是 AI Agent 工程实践 系列的第 39 篇。

相关推荐
dfbz12345612 分钟前
长沙中秋礼品市场数据分析:文创属性如何驱动复购率和口碑传播
大数据·经验分享·数据挖掘·数据分析
茉莉玫瑰花茶18 分钟前
RAG 数据检索
人工智能·算法·机器学习
土拨鼠61819 分钟前
Harness vs 原生 ReAct Agent 对比
人工智能
Zentceh23 分钟前
全彩夜视 vs 黑白夜视:信息密度、AI识别准确率对比
人工智能·科技·计算机视觉·车载系统·无人机·量子计算·视频
playboy13424 分钟前
NAS不能访问共享文件夹数据恢复
大数据·计算机外设
Days205029 分钟前
第二章 社会老年学的概念和理论框架
大数据·人工智能
力学与人工智能31 分钟前
AI赋能飞行器设计:多智能体协同的飞行器“极智”设计平台
人工智能·多智能体·飞行器设计·智能自主设计平台
算了吧956935 分钟前
2026年GEO服务商深度测评:答序科技“诊断型”全栈闭环的技术架构与行业价值
大数据·人工智能
爱学堂IT分享42 分钟前
Text2SQL智能体基础到实战 - 网易云课堂
人工智能