GitHub 上挂着 10 万星的 AI Agent 项目,很多人的第一反应是"这项目真火",第二反应是"这代码肯定很牛"。但说实话,等你真正把源码翻过一遍、把架构图啃下来,你会发现真正值钱的不是那几行调用大模型 API 的代码,而是它在工程层面做的一整套取舍。今天我想聊的,就是抛开那些花里胡哨的 Demo 演示,从一个 10 万星 AI Agent 项目里,反向拆解出那些平时没人写进文档里的软件工程经验。这篇文章适合正在做 AI Agent 开发、想从 0 到 1 搭建 AI Agent、或者准备把 Agent 写进简历项目里的同学,我会尽量用"抄作业"的方式,把工程细节、踩坑记录、代码结构和设计思路都摊开来讲。
1. 为什么一个 AI Agent 项目能冲上 10 万星:不靠炫技,靠工程深度
先说个直观感受。很多人以为这种项目肯定堆满了奇技淫巧的算法,其实恰恰相反,它的核心代码走的是"稳"字路线。一个能拿到 10 万星的项目,背后站着的是几十万开发者、成千上万的 Issue 和 PR,它能在这么大体量下活下来,靠的绝不是几个灵光一现的魔法函数,而是一整套可维护、可扩展、可观测的工程体系。
1.1 热度的本质:不是插件,是分层清晰的框架
我最早看这类项目时也犯过傻,以为核心价值在"Agent 有多聪明"。翻源码之后才发现,聪明的部分基本被封装在模型能力里,项目本身做的是"把模型的聪明变成稳定的产品"。
拿我之前改过的一个开源 Agent 框架来说,它的整体结构非常像我们熟知的 Web 后端分层:
- 最外层是接入层,负责处理用户消息、流式输出、权限校验;
- 中间是编排层,负责意图识别、任务规划、工具调度和结果汇总;
- 底层是能力层,封装了模型调用、工具执行、内存存储、上下文管理等基础设施。
这种分层带来的好处,是每一层的职责都能被单独测试、单独替换。你不会因为换了某个大模型 API,就要把整个任务编排逻辑重写一遍;也不会因为加了两个新工具,就把消息处理流程搞得一团糟。很多做 AI Agent 开发的同学,一上来就把规划、调用、记忆全塞进一个函数里,用的时候很爽,等要加功能的时候就是灾难。框架的意义,恰恰是逼你先想清楚边界,再动手写代码。
1.2 工程复杂度的根本来源:模型不稳定
做传统软件,你面对的是一个行为确定的函数:输入固定,输出基本固定,异常是可枚举的。但 AI Agent 的输入输出天然具有随机性,LLM 今天心情好给你返回合法 JSON,明天可能就给你夹带一段 Markdown 注释,后天直接拒绝回答。这意味着你的软件工程手段必须额外处理一个"不确定的执行环境"。
10 万星项目在处理这个问题上,给了我几个很重要的启发:
- 所有模型输出都必须经过解析器,而不是直接丢给下游逻辑;
- 解析失败必须有降级策略,比如重试一次、换格式化指令、退回人工输入;
- 每一步执行都要落日志,方便事后追溯到底是模型抽风还是代码 bug。
这套思路放在真实开发里非常救命。我自己复刻过一个简化版 Agent,刚开始总是出现"工具明明调用了,参数却是乱码"的情况,后来照着这套工程策略加了 JSON Schema 校验和二次修正调用,问题率直接降了一个数量级。
1.3 数据与评测体系建设:工程里最容易被忽略的护城河
你们有没有发现,这类项目往往自带一整套 eval(评测)脚本和数据集。很多刚入行的开发者会觉得这没什么,一堆测试数据而已,实话说这才是国外的开源项目和中国大多数 AI Agent 练手项目拉开差距的核心。
这个项目里的评测体系,本质上是一个软件工程里的"持续回归测试系统":
- 针对不同任务类型,准备了成百上千条测试用例;
- 每一条用例都有明确的输入、预期输出、评估标准(有的严格匹配,有的语义相似即可);
- 跑完评测后,会自动生成一份报告,告诉你这一次改动是让整体效果变好了还是变差了。
这就像你写 web 后端不可能不写单元测试一样,AI Agent 开发如果不建立评测集,你根本无法判断"换了个 Prompt 到底是优化还是劣化"。很多练手项目能做到功能跑通,但功能跑通和可交付之间,横着的恰恰就是这套评测和回归机制。
2. 从源码里反推的软件工程原则:接口抽象、配置驱动、模块边界
如果你只是想跑通一个 Demo,那真的不用管架构,写个死循环加几个 if 判断就能糊弄过去。但你要做一个能维护、能扩展的 AI Agent 项目,就必须学这套开源项目里沉淀下来的工程原则。我把它们总结成三条核心原则,每条都用代码组织的逻辑来解析。
2.1 接口抽象与依赖倒置:不围绕"某个模型"建项目
很多新手写 Agent,代码里直接 import 某个模型的 SDK,然后一把梭调用。这样写确实很快,但问题是你被厂商锁死了。10 万星项目的做法非常教科书:在最顶层定义一套抽象的模型接口,所有的调用方只依赖接口,不依赖具体实现。
# 一个简化版的模型抽象示例 from abc import ABC, abstractmethod from typing import AsyncIterator, Literal class BaseLLM(ABC): """ 所有 LLM 实现都必须继承这个基类。 上游代码只认这个接口,不关心底层是 GPT、Claude 还是本地模型。 """ @abstractmethod async def complete( self, messages: list[dict], temperature: float = 0.2, max_tokens: int = 2048, timeout: int = 60, ) -> str: """非流式补全,返回完整文本""" @abstractmethod async def stream_complete( self, messages: list[dict], temperature: float = 0.2, max_tokens: int = 2048, ) -> AsyncIterator[str]: """流式补全,按块返回文本""" @abstractmethod def get_token_count(self, messages: list[dict]) -> int: """估算 token 消耗,用于上下文管理和成本控制"""这套抽象带来的收益是:你可以随时更换底层模型,只需要写一个新的实现类,然后在配置里改一行注册信息。我之前把项目从 GPT 切换到其他开源模型时,就靠这个抽象层把改动量控制在了 200 行以内。
2.2 配置驱动设计:把 Prompt、参数、工具注册表全放外面
AI Agent 项目有一个很大的特点:业务逻辑跟文本提示词深度绑定。如果这些 Prompt 散落在代码里,那每改一句话都要推翻发布版本。大项目普遍的做法是配置驱动,把所有可变内容全部外置。
一个典型的配置模块长这样:
# agent_config.yaml model: provider: openai_compatible model_name: gpt-4o-mini temperature: 0.3 max_tokens: 4096 prompts: system_prompt_path: ./prompts/system.md planner_prompt_path: ./prompts/planner.md reflect_prompt_path: ./prompts/reflect.md tools: enabled_tools: - web_search - calculator - code_interpreter tool_timeout_seconds: 30 memory: store_type: vector max_recent_messages: 20 top_k_recall: 5不要小看这个设计。把 Prompt 外置到文件后,就可以让非技术人员参与调参,甚至做 A/B 测试。而把模型参数外置后,不同环境(开发、测试、生产)可以各自维护一套配置,互不干扰。这比在代码里改字符串再重新部署,效率高太多了。
2.3 模块边界与插件化机制:工具就是 Agent 的"API 网关"
在 AI Agent 项目里,工具系统是一个典型的插件化场景。项目里工具注册表的设计,和 Web 里的依赖注入容器有异曲同工之妙。每个工具都按照统一协议暴露自己的名称、描述、参数 JSON Schema、执行函数。Agent 拿到用户的意图后,会先根据描述信息选出合适的工具,然后再按照 Schema 组装参数并调用。
# 工具注册和调用的简化示意 class ToolRegistry: def __init__(self): self._tools: dict[str, BaseTool] = {} def register(self, tool: BaseTool): # 注册时校验 name 唯一性,避免工具冲突 if tool.name in self._tools: raise ValueError(f"Duplicate tool name: {tool.name}") self._tools[tool.name] = tool def get_tool(self, name: str) -> BaseTool | None: return self._tools.get(name) def list_tools_schema(self) -> list[dict]: # 把全部工具的 JSON Schema 汇总给模型,用于工具选择 return [t.schema for t in self._tools.values()]这个设计让我想起了后端微服务里的网关模式。工具层执行细节(比如 HTTP 调用、代码执行、数据库查询)全都在网关后面,模型不需要关心,只需要看到一个统一的"能力清单"。这种边界划分让新增工具变得极其简单,不需要动任何编排代码。
3. 从 0 到 1 搭建带"工程味"的 AI Agent:一份踩过坑的实操手册
光讲原则太空了,我直接分享一套我自己用过的、融合了这些工程思路的最小可复现架构。这套架构不是课堂作业那种玩具,而是你写简历、做毕设、做课程设计时能真正落地的基础版本。我会按目录结构、核心模块、实现步骤来讲。
3.1 工程目录结构:代码组织比你想得更重要
很多初学 AI Agent 开发的同学,项目根目录就放一个main.py,所有逻辑全都堆在里面。一旦超过 500 行,维护就变成噩梦。参考大项目的组织方式,我习惯这样设计目录:
agent_project/ ├── agent/ │ ├── __init__.py │ ├── core/ │ │ ├── base_llm.py # 模型抽象 │ │ ├── tool.py # 工具协议 │ │ └── protocol.py # Agent 主协议 │ ├── tools/ │ │ ├── registry.py # 工具注册表 │ │ ├── web_search.py # 搜索工具实现 │ │ ├── calculator.py # 计算工具实现 │ │ └── code_runner.py # 代码执行工具实现 │ ├── memory/ │ │ ├── buffer.py # 短时对话缓冲 │ │ ├── store.py # 向量存储接口 │ │ └── vector_impl.py # 向量实现(可以接 Qdrant / Chroma) │ ├── engine/ │ │ ├── orchestrator.py # 任务编排器 │ │ ├── planner.py # 规划器 │ │ └── executor.py # 执行器 │ ├── prompts/ │ │ ├── system.md │ │ ├── planner.md │ │ └── reflect.md │ └── config.py # 配置加载,对应 YAML ├── tests/ │ ├── unit/ │ ├── integration/ │ └── eval_sets/ │ └── tasks.jsonl ├── scripts/ │ ├── run_server.py # 服务入口 │ ├── run_cli.py # 命令行调试入口 │ └── run_eval.py # 评测脚本 ├── requirements.txt ├── pyproject.toml └── README.md这个结构并不是我凭空捏的,它就是把上一节说的分工落了地。你在core/里放协议契约,在tools/里放插件实现,在engine/里放编排逻辑,在prompts/里放所有文本模板。当你想升级某一个环节时,永远知道该去哪个目录动手。
3.2 核心实现:先写协议,再写实现
我自己干活有个习惯:先把"协议"定下来,再写具体代码。这个习惯是从 Go 和 Java 工程里带过来的,但它对 AI Agent 项目特别适用,因为 LLM 的强不确定性要求你必须有明确的输入和输出契约。
以 Agent 主协议的实现为例,核心是一个循环:把"规划 - 执行 - 观察 - 反思"串起来。我简化一下代码,展示主干逻辑:
# agent/engine/orchestrator.py (简化版) import asyncio from agent.core.base_llm import BaseLLM from agent.tools.registry import ToolRegistry from agent.memory.buffer import ConversationBuffer class Orchestrator: def __init__(self, llm: BaseLLM, tools: ToolRegistry, memory: ConversationBuffer): self.llm = llm self.tools = tools self.memory = memory async def run(self, user_message: str, max_steps: int = 8) -> str: self.memory.add_user_message(user_message) step_count = 0 while step_count < max_steps: # 1. 从记忆里组装当前上下文 context = self.memory.get_context() # 2. 调模型,让模型决定下一步动作(生成 JSON 指令) response = await self.llm.complete( messages=[ {"role": "system", "content": self.memory.system_prompt}, *context, ] ) # 3. 解析模型输出,失败就修正重试 action = self._parse_action(response) if action is None: # 解析失败,给模型回传一条"格式错误"反馈 self.memory.add_assistant_message(response) self.memory.add_system_message( "Your last output was malformed. Return a valid JSON action." ) step_count += 1 continue # 4. 执行动作:可能是 final_answer,也可能是调用某个工具 if action["type"] == "final": self.memory.add_assistant_message(action["answer"]) return action["answer"] if action["type"] == "tool_call": result = await self._execute_tool(action["tool_name"], action["params"]) self.memory.add_tool_result(action["tool_name"], str(result)) step_count += 1 return "I've reached the maximum step limit. Please try a simpler request."这段代码虽然只写了个主循环,但它背后包含了所有关键工程点:
- 消息历史是结构化的,分为 user / assistant / system / tool_result 四类角色,而不是一个扁平的字符串拼接;
- 模型输出必须经过解析器,解析失败有反馈重试机制,而不是直接抛异常;
- 步骤数上限强制存在,防止 Agent 陷入死循环,这也是一个常见的面试考点;
- 记忆模块独立,之后的扩展方向是接入向量存储、做长短期记忆分层。
3.3 上下文管理:最容易爆雷的地方
说到踩坑,AI Agent 开发里十个 bug 八个和上下文有关。这里的上下文不只是"token 超长"那么简单,还包含消息顺序对不对、系统提示词有没有被用户内容污染、工具调用结果会不会被截断。
推荐一套我试过比较稳的组合拳:
- 用 token 计数器统计当前上下文长度,设置 80% 警戒线;
- 超过警戒线后,启动压缩策略:优先丢最老的工具调用细节,保留用户最新请求;
- 对超长工具返回结果做截断,保留前 500 字符和末尾 100 字符;
- 系统 Prompt 永远放在最前面,且不允许被后续消息覆盖。
# agent/memory/buffer.py (核心逻辑片段) class ConversationBuffer: def __init__(self, max_tokens: int = 12000): self.max_tokens = max_tokens self.messages: list[dict] = [] def add_message(self, role: str, content: str): self.messages.append({"role": role, "content": content}) self._enforce_limit() def _enforce_limit(self): total = self._count_tokens() while total > self.max_tokens and len(self.messages) > 2: # 丢弃最早的非系统消息 for i in range(len(self.messages)): if self.messages[i]["role"] != "system": self.messages.pop(i) break total = self._count_tokens()这套保守策略看起来简单,实操效果非常好。它可以确保你在最坏情况下不把上下文打爆,同时也天然让你的 Agent 更适合长时间对话场景。
3.4 从"能跑"到"能部署":异步、超时、重试和并发控制
最后再补一个实操中很硬的点:异步和超时。AI Agent 是要对接外部 API 的,网络抖动、模型限流、工具超时都是常态。如果你用同步阻塞的方式写,一旦某个工具卡住,整个服务就挂了。
推荐的做法是:
- 所有模型调用都走异步接口(上面的 BaseLLM 已经体现);
- 每个工具调用都设定独立超时时间,Web 搜索给 20 秒,代码执行给 30 秒;
- 超时之后记录日志,然后返回一个友好错误给模型:"The tool timed out, try another approach";
- 对于模型 API 的限流错误(429),做指数退避重试,最多重试 3 次。
# agent/tools/registry.py 中带超时的调用封装 import asyncio async def execute_with_timeout(coro, timeout: int): try: return await asyncio.wait_for(coro, timeout=timeout) except asyncio.TimeoutError: return {"error": f"Tool execution timed out after {timeout}s"}这套机制上完以后,你的 Agent 会从"能跑通一次"变成"能在生产环境扛一天",这两个状态之间的差别,就是软件工程的价值。
4. 除了代码,还藏着什么隐形工程:数据、评测与交付流程
这一节可能是整篇文章里最"软件工程"的部分。我们平时写 AI Agent 练手项目,能跑通就发朋友圈了,但一个 10 万星项目之所以能经受海量用户折腾,是因为它背后有一套数据、评测和交付的流程在托底。这些内容在源代码仓库里不容易一眼看到,但却是工程价值的半壁江山。
4.1 评测集建设:给你的 Agent 装上一套"体检仪"
我前面反复提到评测,这里展开讲一下怎么落地。所谓评测集,就是一组"题目 + 标准答案"的数据。它不是可有可无的测试,而是你后续所有 Prompt 调整、模型替换、工具改版的参照系。
评测集的形态一般是这样:
{"task": "今天北京天气怎么样?", "expected_action": "tool_call", "expected_tool": "web_search", "expected_keywords": ["北京", "天气"]} {"task": "计算 25*38+17 的结果", "expected_action": "tool_call", "expected_tool": "calculator", "expected_answer": "967"} {"task": "用三句话介绍你是什么", "expected_action": "final", "expected_no_tool": true}然后你写一个评测脚本,批量跑这几十条用例,统计三个指标:
- 工具选择准确率:该调用工具时,模型有没有选对工具;
- 参数解析成功率:模型生成的工具参数能不能合法解析;
- 最终回答正确率:最终答案是否包含关键信息或命中标准答案。
这个评测集就是你的 Agent 的"回归测试集"。每次改完 Prompt,跑一遍全套用例,分数掉下来就说明改动有问题。我见过太多同行把精力全花在看聊天效果上,今天觉得好明天觉得坏,心里完全没数。有了评测集之后,所有优化都从拍脑袋变成了数据驱动。
4.2 自动化评测与 CI 集成:让每一次改动都留痕
有了评测集,下一步就是把它接进 CI 流程。你的项目完全可以像普通后端项目一样,在 GitHub Actions 或 GitLab CI 里加一个 job,每次 push 或者 PR 的时候自动运行评测脚本,输出对比报告。
这一步的意义在于:防止"改一个 Prompt 修了一个 bug 同时引出三个新 bug"。模型输出不像传统代码那样有确定性,一个措辞变化可能导致连锁反应,只有机器化评测能帮你把这种隐性回归挡在上线之前。
我自己的项目里,CI 流程通常是三阶段:
- 跑所有单元测试,覆盖工具函数和解析器;
- 跑集成测试,模拟一轮完整对话;
- 跑评测集,输出当前分数与上一次分数的差异。
三阶段全绿,才可以 merge。这套流程一开始用会觉得有点慢,但时间久了你就知道,没有这套机制,任何一次 Prompt 改动都像在裸奔。
4.3 灰度与回滚:Prompt 也可以走发布流程
最后聊一个更进阶的工程理念:把 Prompt 当作"代码"来管理。很多人改 Prompt 直接复制粘贴到代码里,然后重新部署。这放在正规项目里是灾难,因为你无法 A/B 测试、无法快速回滚。
10 万星项目早就把 Prompt 做成版本化资产了。做法其实不复杂:
- Prompt 统一放在一个配置仓库或者独立的 JSON/YAML 文件里;
- 每次改动 Prompt 都要提交,并附带一个版本号;
- 上线流程是:先在评测集上跑对照,再走灰度小流量观察,最后全量发布;
- 如果线上效果下降,直接切回上一个 Prompt 版本。
我在自己做 AI Agent 中台时,完全复刻了这套思路。所有接入方都把 Prompt 存成独立文件,每次更新走配置中心,线上出问题可以用一个开关快速回退到三天前。这个能力在传统后端里叫配置回滚,在 AI 项目里同样重要,甚至更救急。
5. 实战排查记录与避坑清单:经典问题一网打尽
工程经验不全是"怎么做",还有一把把的"这不行、那别碰"。我把折腾 AI Agent 这一年多遇到的典型问题,整理成一份排查速查表,每一条都附带着处理思路,照着查可以少走很多弯路。
5.1 上下文爆掉:窗口超限引发连锁崩溃
症状:Agent 执行到第五六步突然报错,或者模型回答质量断崖式下降、重复之前说过的话。
原因:没有做 token 预算管理,工具返回内容过长,把上下文窗口塞满了。很多人以为换一个长上下文模型就一劳永逸,实际上窗口越长的模型,塞入无关信息越多,模型越容易迷失重点。
排查思路:
- 把日志里每次请求的 token 数打出来,找出突变点;
- 看看是哪条工具结果最占空间;
- 确认是否在每轮对话后都压缩了历史。
解决方案:建立一套严格的上下文预算制度。给用户消息 20% 预算、工具结果 30% 预算、历史对话 30% 预算、系统 Prompt 固定占用 10%、剩余 10% 留给模型输出。所有的消息在进入上下文前都要经过裁剪,超出预算的直接降级或摘要。
5.2 工具调用陷入死循环
症状:Agent 反复调用同一个工具,明明结果已经拿到,却不输出最终答案,一直"再查一下"。
原因:模型的规划能力有限,当工具结果不符合预期时会陷入"修复循环";还有一种可能是在 Prompt 里没有明确告诉模型"什么时候可以停止调用工具"。
排查思路:
- 打开调试日志,观察每轮的 system prompt 和模型输出;
- 看看模型中是否有"if the answer is found, reply directly"这类停止条件;
- 检查循环计数器的位置是不是在每轮都递增。
解决方案:双保险。一是 Prompt 里显式声明"当你认为已获得足够信息,直接调用 final_answer";二是在代码层强制设置最大轮数,超出后无条件转人工或返回现有结果。这两种手段组合使用,基本能掐死死循环。
5.3 并发环境下消息乱序、上下文串号
症状:多个用户同时使用时,A 用户的对话历史出现在 B 用户的请求里。
原因:Agent 的上下文存储没有做会话隔离,用的是全局变量或单例对象保存消息列表。写 Demo 时感受不出来,一旦部署到服务端就集体爆炸。
排查思路:
- 检查 Memory 对象的生命周期,是不是所有请求共享同一个实例;
- 检查是否有在全局范围声明的
self.messages只初始化了一次; - 在日志里打印 session_id 和 request_id,对照上下文内容。
解决方案:一改用 Session 隔离,每个新会话创建独立的 Buffer 实例,存放在一个以 session_id 为 key 的字典或缓存里。这就和 Web 后端用 Session 管理登录态一个道理。我在项目里养成了每一条日志都带上 session_id 的习惯,排查该类问题效率直接翻倍。
5.4 模型返回 JSON 永远解析不干净
症状:模型调用工具时返回的 JSON 里夹杂 Markdown 代码块、注释、或者不完整换行,json.loads 经常报错。
原因:这是 LLM 的顽疾,不管你是 GPT、Claude 还是国产模型,都偶尔会"带病"输出。尤其在温度调高(temperature > 0.5)时,这种情况会更频繁。
排查思路:
- 收集解析失败的原始返回,确认是前缀污染还是截断;
- 测试不同温度下失败率;
- 验证是否与你的 prompt 里的格式描述不一致有关。
解决方案:写一个容忍度高的解析器,先尝试json.loads,失败后自动提取文本里的第一个花括号到最后一个花括号;再不行,就调用模型让它修正 JSON。如果还不能解决,就降低温度。三步走下来,解析成功率能回到 99% 以上。
# 一个带修正的 JSON 解析器(简化版) import json, re def robust_parse(raw: str): try: return json.loads(raw), True except json.JSONDecodeError: # 尝试提取第一个 { 到最后一个 } match = re.search(r"\{.*\}", raw, re.DOTALL) if not match: return None, False try: return json.loads(match.group()), True except json.JSONDecodeError: return None, False5.5 排查速查表
| 问题现象 | 首要怀疑方向 | 快速定位手段 | 最终解法 |
|---|---|---|---|
| 回答质量骤降 | 上下文被污染 | 打印完整请求上下文 | 定期压缩历史,隔离工具结果 |
| 工具参数是乱的 | 模型没理解 Schema | 查看工具描述是否太长/太模糊 | 精简描述,用 JSON Schema 校验 |
| 响应特别慢 | 工具串行执行 | 在日志里统计每步耗时 | 能并行的工具调用改为并发执行 |
| 高频触发限流 | 没有做请求队列 | 查 API 返回 429 频率 | 加令牌桶限流和指数退避 |
| 同一问题答案漂移 | 温度过高 | 对比不同温度下的输出 | 将 temperature 调到 0.2 以下 |
写在最后的工程心得
前面这些内容说起来有点散,但我真正想表达的只有一件事:AI Agent 这个领域,就和十年前的 Web 后端一样,已经从"跑通 Demo"时代进入"稳定交付"时代了。一个 10 万星的项目之所以能一直活着,不是因为它背后有什么神秘玄学,而是因为它把每一个不确定的东西都做了约束,把每一个容易踩的坑都装了护栏。
我在实际复刻这类项目的过程中印象最深的是"可观测性"这件事。以前我觉得日志随便打打就行,后来亲自排查过一个线上问题,用户在第十轮对话时突然收到乱码回复。如果没有结构化日志和请求链路追踪,我根本不可能定位到是第几步上下文出了错。从那之后,我把"每一步都有日志、每个会话都有追踪"当成了 AI Agent 开发的铁律。
另外还有一个小技巧送给大家:写 Agent 的代码时,千万别把"人类觉得自然"当成目标,要把"模型的解析成本最低"当成目标。你输出 JSON 时加不加说明文字、用不用统一缩进,模型都能读,但解析器的稳定性完全不同。宁可让 Prompt 显得机械一点,也要让返回格式绝对规范。这个取舍,是你在真正交付 Agent 产品后才会痛彻心扉地理解的。