一、背景:为什么要"自己写"Agent
很多团队一上来就装 LangChain,结果被抽象层、版本变动和隐式行为拖垮,调试时根本不知道模型在想什么。对"快速开发"而言,最快的路有时是自己实现那不到 200 行的核心循环:工具注册表 + 规划器 + 执行器 + 记忆。它零依赖、可单步打印、易嵌入老系统,也方便后面做审计与护栏。
本文给出的 build_agent.py 是一个完整可运行的最小 Agent:自带三个业务工具(查知识库、查数据库、发通知),用关键词路由模拟"大模型决策",跑一条真实任务并逐行打印 Think/Act/Observe。把它替换成真 LLM 只需改一个函数。
判断该不该自建,有个简单标准:如果你的 Agent 只是"调两三个内部 API + 拼一段话",自研核心循环比引入整套框架更快;如果要做复杂检索、多智能体编排、数十个工具,再上 LlamaIndex/LangChain 才划算。别为小需求背负大依赖------"快速"的敌人往往是过度工程。
二、Agent 的内部架构
一个最小可用 Agent 由四块组成:工具注册表 登记所有可调用的外部能力;规划器 决定下一步调哪个工具;执行器 真正运行工具并捕获异常;记忆保存历史,供下一步决策参考。四块解耦,才能分别测试与替换。
之所以强调解耦,是因为它们各自会独立演进:工具随业务增长、规划器未来要换更强的模型、记忆要接向量库做长期记忆。四块边界清晰,替换任意一块都不必动其他代码------这正是"快速开发"能持续快速的根本。把核心循环写成纯函数,测试时传入假工具即可单测,无需起真模型。

三、工具定义规范
Agent 好不好用,七分看工具。每个工具必须暴露三件事:名字 (唯一)、自然语言描述 (给模型看,决定何时调用)、执行函数(带异常处理)。描述写不清,模型就会乱调;函数不兜底,一个超时就能让整个 Agent 崩。
registry = ToolRegistry()
@registry.register(name="search_kb", desc="检索企业内部知识库")
def search_kb(query: str) -> str:
# 真实场景接向量库;此处返回模拟片段
return f"[KB] 关于『{query}』的要点:SLA 为 99.9%"
举个反例:某工具描述为"处理数据",模型永远不知道何时该调它;改成"查询 MySQL 订单表,返回指定单号的 status 与金额",命中率立刻提升。描述里要明确"做什么、输入输出什么、何时该用",最好附一句触发示例,例如"当用户提到订单号或物流时调用"。
四、完整可运行 Agent(build_agent.py)
下面是脚本核心骨架:规划器用关键词路由模拟 LLM 选工具,执行器用 try/except 兜底,记忆把每步结果回写,循环带 max_steps 终止条件防止死循环。把 planner() 换成真正的模型调用即可上线。
def planner(task, memory):
# 真实场景:call_llm(system_prompt + tools_desc + memory)
text = (task + memory).lower()
if "知识库" in text or "sla" in text:
return "search_kb", task
if "数据库" in text or "订单" in text:
return "query_db", task
if "通知" in text or "邮件" in text:
return "notify", task
return None, "" # 无工具 -> 任务完成
def run(task, max_steps=6):
memory = ""
for i in range(1, max_steps + 1):
name, arg = planner(task, memory)
if name is None:
return f"完成:{memory.strip() or task}"
print(f"[Step {i}] Act: {name}({arg!r})")
obs = executor(name, arg) # 内部 try/except 兜底
print(f"[Step {i}] Obs: {obs}")
memory += obs + "\n"
return f"达上限:{memory.strip()}"
五、一次任务执行轨迹
以"查知识库确认 SLA,再发邮件通知运维"为例,Agent 会先调 search_kb 拿到要点,再调 notify 发出通知,最后规划器返回 None 正常结束。下图展示这条轨迹。

六、避坑清单

七、关键结论
-
最小 Agent = 工具注册表 + 规划器 + 执行器 + 记忆,四块解耦。
-
工具描述写给模型看,要像说明书;函数必须 try/except 兜底。
-
循环必须有无工具可调用与 max_steps 两道终止条件。
-
换真 LLM 只需替换 planner(),其余架构不变。