ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

无Tool Calling的结构化Agent架构设计与工程实践

无Tool Calling的结构化Agent架构设计与工程实践 2. 为什么要做“无 Tool Calling”的结构化 Agent这两年聊 Agent绕不开一个词Tool Calling。翻开源项目、技术博客几乎清一色都默认 Agent 必须有工具调用能力仿佛没有工具调用就不配叫 Agent。我自己也是从这一套走过来的但在一线折腾了多轮之后反而越来越倾向一种看起来“退步”的做法干脆不用 Tool Calling只让模型输出结构化字段把决策和动作全部交给外部的确定性代码。这里说的“无 Tool Calling”不是说 Agent 不能干活、不能调接口而是把干活这件事从模型那里剥离开。模型只做一件事根据上下文输出一段严格定义好的 JSON。这段 JSON 里包含意图、参数、需要执行的步骤等等。真正的执行例如读文件、查数据库、发请求、调外部 API都由外部代码完成。有人一听就皱眉头这跟 RAG 有什么差别跟规则匹配有什么区别还真不一样。RAG 是检索知识规则是硬编码逻辑而结构化 Agent 依然保留了模型的推理能力。模型负责理解用户输入、拆解任务、决定下一步做什么只是它不直接碰工具而是把“决定”写出来交给代码去执行。等于把“思考”和“手脚”彻底分离。我之所以花大力气验证这套思路是因为在实际跑 Agent 项目时频繁踩到 Tool Calling 的痛点。最常见的问题是模型把工具名或参数拼错。工具一多、描述一复杂模型就会“手滑”轻则传错参数重则选错工具更麻烦的是多轮调用之后上下文膨胀模型自己都不知道刚才调了什么工具。再加上各家平台对 Tool Calling 的实现不统一换一个模型就要重调一遍参数维护成本高得离谱。结构化方案天然规避了这些坑。模型的输出被限定在一个极小、极稳定的 JSON Schema 里外层代码负责做一切有副作用的事情。哪怕模型给的值不够准确代码也能做校验、兜底、重试。容错能力不在模型侧而在工程侧。另外要说清楚适用范围。我做的这个 Agent 主要面向个人助手场景帮用户查资料、整理信息、生成草稿、处理日常任务而不是那种需要几百个工具的大平台。在这类场景里稳定性和可控性远比“模型自己会调用工具”这种酷炫能力值钱。如果你要做一个需要 50 个以上外部工具的开放平台那结构化方案可能很快会碰壁这点后面详细说。整个项目的技术栈也很朴素Python Pydantic 做结构化输出模型LangChain 只做模型调度和上下文管理外加一个简单的路由引擎。没有复杂的图谱编排没有重型框架所有代码加起来不到 2000 行。核心思路就一句话让模型输出一个可以被机器直接执行的动作列表用它替代 Tool Calling 的函数调用机制。3. 整体架构:模型只做决策,代码负责执行3.1 核心架构拆解这套 Agent 的核心可以画成四条流水线但我没用任何绘图工具直接用文字拆给你看。第一层是输入解析层。用户输入进来之后先做预处理去掉无意义的口头语提取时间、地点、实体等基础信息把上下文粘进系统提示词。这里不用模型全部用正则和 jieba 分词解决速度快、零成本。比如用户说“帮我查一下下周二的会议记录”预处理阶段就把“下周二”这个相对时间转成绝对日期免得模型还要算日历。第二层是决策层。经过预处理后的文本被丢给大模型模型的任务是输出一个结构化的 JSON里面包含用户的真实意图、需要执行的动作列表、每个动作的参数、以及它认为需要注意的事项。这一层是整套系统的大脑但大脑只管“想”不管“做”。第三层是执行层。拿到 JSON 之后外部代码逐条解析动作做参数校验、权限校验然后真正去调用文件系统、数据库、HTTP API 或者其他服务。执行完的动作会把结果记录下来作为下一轮上下文的输入。第四层是反馈层。执行结果经过格式化拼接成新的上下文和用户的下一句输入一起进入下一轮循环。如果用户没有再输入就结束循环。这套架构的关键点在于模型和工具之间隔着一层“协议”。模型输出的是协议文本JSON代码读取的是协议字段两者没有直接函数调用关系自然不存在“模型调错函数”的问题。有人会问那模型要是输出了一个根本不存在的动作怎么办我的答案是动作列表是枚举值模型只能在白名单里选Pydantic 在解析时就会直接报错根本到不了执行层。这就堵死了模型“瞎编动作”的路。3.2 为什么用 JSON 而不是自然语言这是一个很关键的设计选择。最开始我试过让模型直接输出自然语言指令然后用 NLP 去解析结果一团糟。模型怎么理解“帮我打开项目的 README 文件然后提取里面的安装命令”这句话它是想打开文件想提取命令还是想同时做两件事中间还有没有隐含的前置条件这些问题让规则代码非常痛苦。JSON 的好处是它天生就带“字段结构”模型只需往字段里填值不用组织语言。同一个意图自然语言表达千变万化但 JSON 表达只有一个模式。比如用户说“帮我打开 README 并提取安装命令”模型输出的不是一句话而是{ intent: extract_and_read, actions: [ { action: read_file, params: {path: README.md} }, { action: extract_text, params: {pattern: install section} } ], notes: install section 位于文件后半部分 }外部代码看到这个 JSON 就明白了第一步读文件第二步提取安装文本。如果第二步提取失败代码会重新调整正则或换一种思路而不是再问一次模型。用 JSON 还有一个附带好处调试特别方便。出问题的时候你可以在日志里看到模型“当时在想什么”。如果是自然语言输出你得到的是大段文本根本没法程序化判断对错。JSON 则可以让每个字段都接受校验错误被精确定位到某个字段不用靠肉眼看。3.3 LangChain 在这里扮演的角色Truth be toldLangChain 在这套系统里的戏份并不多但它帮我解决了一个很实际的问题模型接口的统一管理。之前我换过好几次底层模型一会儿用 OpenAI 兼容接口一会儿用国产模型一会儿又要切到本地部署的开源模型。如果直接在自己代码里写 SDK 调用每次换模型都要改调用层。用 LangChain 的话只需要改环境变量里的模型名称和 base_url其余代码完全不用动。我在 LangChain 里只用了三样东西初始化模型、组装对话消息列表、调用模型拿返回。连 Tool 相关的功能全部没用。换一句话说LangChain 在这套架构里就是一个“模型适配器”而不是框架。核心的执行逻辑全部是自己写的没有依赖 LangChain 的 Agent 机制。这也引出一个选择思路你的项目到底要不要上重型框架完全看你有没有“多模型可切换”的需求。如果没有直接用模型的官方 SDK 就行反而省一层依赖。我这个项目因为有多个场景要跑不同模型所以才用 LangChain 做了统一接入。4. 结构化输出:用 Pydantic 把模型结果锁死4.1 为什么非要用 Pydantic如果你写过 Python应该对 Pydantic 不陌生。它主要用来做数据校验和序列化。在这个项目里它的价值是给模型的输出画了一个“矩形框”模型想飞出框外都不行。没有 Pydantic 的话模型返回的 JSON 可能缺字段、多字段、类型不对。比如模型把intent: read_file错写成intent: read file或者把params里的path写成paths虽然前一个错误肉眼能看出来但程序不会理解。Pydantic 在解析时就抓住了这些错误要么当场报错提示要么用默认值兜底。我用的模型定义大致长这样from pydantic import BaseModel from typing import List, Optional from enum import Enum class ActionType(str, Enum): read_file read_file write_file write_file search_web search_web call_api call_api execute_python execute_python ask_user ask_user class Action(BaseModel): action: ActionType params: dict {} priority: int 0 reason: str class AgentOutput(BaseModel): intent: str actions: List[Action] notes: Optional[str] None confidence: float 0.8ActionType是枚举意味着模型只能在这六个动作里选。params是字典但执行时还会再细化对不同动作做参数白名单校验。priority表示执行顺序数值小的先执行。confidence是模型自己的置信度如果置信度低于某个阈值外层代码会转人工确认避免误操作。Pydantic 的另一个好处是它支持直接从模型输出构造对象。LangChain 里可以用with_structured_output绑定 Pydantic 模型from langchain_openai import ChatOpenAI model ChatOpenAI(modelgpt-4o, temperature0) model_with_schema model.with_structured_output(AgentOutput) result model_with_schema.invoke(messages)这一行代码省了我手写 JSON 解析、字段校验和重试逻辑。如果模型返回的结果不合规LangChain 会自动走重试机制用错误信息提示模型重新输出一次。这个自动重试非常有用正常模型第一遍输出字段错误时第二遍往往就能改对。但老实说with_structured_output也不是银弹。我实测下来它对输出格式的约束依赖于底层模型是否支持 JSON Schema不支持的话会退化成普通 JSON 解析。本地部署的 7B 模型就经常在这里翻车输出大段废话然后才跟上 JSON或者干脆漏掉某个字段。遇到这种情况我的建议是在系统提示词里给出一个强制示例并明确告诉模型“只输出 JSON不要任何解释”实测能把成功率从 60% 拉到 98% 以上。4.2 系统提示词的防火墙设计系统提示词在这个项目里地位非常高。它决定模型是老老实实输出 JSON还是放飞自我输出散文。我前后迭代了五版提示词最终稳定下来的一套包含三个部分角色设定、输出要求、示例样本。角色设定不需要多复杂一句话就行“你是一个任务规划引擎根据用户输入生成结构化的任务列表。”重点是输出要求里要写清楚 JSON 的格式、字段含义、取值约束并用极强硬的口吻限制模型别输出多余内容。我给模型的最后一句必是“只输出 JSON禁止输出任何解释、Markdown 代码块或感叹词。”有人觉得这像是“防小人”但实测非常管用。早先没加这句话时模型偶尔会在 JSON 前面加一句“好的正在为您处理……”或者用 Markdown 的 json 块包裹输出导致 Pydantic 解析失败。加上这句话后虽然偶尔还会出错但错误率呈肉眼可见的下降。示例样本也很好使。我给模型两个示例一个正常输入对应的 JSON一个异常输入对应的错误说明。模型看到示例之后会照着格式输出效果比抽象描述好太多。这其实是“少样本提示”的基本功但很多人往往忽略了这个简单有效的技巧。4.3 模型输出校验的多层兜底结构化并不意味着把一切交给 Pydantic 就完事了。我在执行层前还做了一层“参数白名单”校验。每个动作类型都有自己的参数模板比如read_file只能接收path和encodingsearch_web只能接收query和max_results。模型输出的 params 如果出现了模板之外的键代码不会执行而是直接标记为“动作不合法”。为什么不放心 Pydantic因为 Pydantic 只管结构不管语义。模型可以在params里塞任何键值对Pydantic 并不会拒绝因为params被定义为通用 dict。执行层的白名单就是语义校验的关键一环。举个例子execute_python这个动作极其危险模型一旦输出任意代码系统就真的会去执行。所以我给它加了双重保护一是 action 白名单之外的动作直接拒绝二是execute_python必须在沙箱中执行不允许访问系统关键目录和网络。这些保护措施全部在代码侧而不是模型侧这正好呼应了“模型只负责想代码负责做”的设计理念。5. 路由引擎与动作执行:从 JSON 到真实动作的桥梁5.1 一个可插拔的动作注册表动作执行层我设计成了注册表模式每个动作都是一个独立的函数统一注册到一张表里。执行器拿到动作名去表里查对应的函数然后调用。这样加新动作时不需要改主流程只需写一个新函数并注册维护成本很低。from typing import Callable, Dict class ActionRegistry: def __init__(self): self._actions: Dict[str, Callable] {} def register(self, action_name: str, func: Callable): self._actions[action_name] func def execute(self, action_name: str, params: dict) - str: if action_name not in self._actions: return fERROR: unknown action {action_name} func self._actions[action_name] try: return func(params) except Exception as e: return fERROR: {str(e)} registry ActionRegistry() registry.register(read_file, read_file_action) registry.register(write_file, write_file_action) registry.register(search_web, search_web_action)执行器的职责只有一个根据动作名分发给对应的函数然后把函数返回值收集起来。函数返回值统一是字符串执行器不会把它们拼进模型上下文以外的任何地方避免格式污染。这里有一个小细节每个动作函数都要自己处理异常而不是让执行器统一捕获。为什么因为不同动作的异常处理逻辑差异很大。比如search_web超时了可以换个搜引擎重试但read_file文件不存在就是不存在重试没意义。把错误处理内聚到动作函数里比在统一异常处理里做分支判断清晰得多。5.2 动作执行:如何安全地执行模型生成的代码execute_python这个动作是整套系统里最有“Agent 味”的部分但同时也是风险最大的部分。它能执行任意 Python 代码也就是说模型真的可以“自己写代码干活”。这在做数据处理、文本解析、网络请求等任务时非常高效比如模型发现用户给了一堆 CSV 需要统计它可以生成一段 pandas 代码直接跑出结果而不是搞一堆中间动作去模拟。但正因为有代码执行能力安全问题就是重中之重。我在execute_python的实现里做了四层保护第一层代码必须来自模型输出但执行前会经过一个静态扫描器检查有没有import os、subprocess、socket、open等危险模块或函数。第二层代码在一个受限的全局命名空间里执行__builtins__被裁剪只保留print、len、range等安全的内建函数。第三层所有文件写入操作被重定向到一个临时目录代码无法直接访问用户根目录。第四层超时限制默认 5 秒超过时间直接终止执行。import builtins import signal SAFE_BUILTINS [print, len, range, int, str, float, list, dict, set, tuple, enumerate, zip, bool, sum, min, max, sorted] def execute_code_safely(code: str, timeout: int 5): if detect_dangerous_imports(code): return SECURITY_ERROR: dangerous imports detected restricted_builtins {name: getattr(builtins, name) for name in SAFE_BUILTINS} safe_globals {__builtins__: restricted_builtins} def handler(signum, frame): raise TimeoutError(code execution timeout) signal.signal(signal.SIGALRM, handler) signal.alarm(timeout) try: exec(code, safe_globals) return safe_globals.get(result, EXEC_OK) except TimeoutError: return ERROR: timeout except Exception as e: return fERROR: {str(e)} finally: signal.alarm(0)这个实现并不完美比如detect_dangerous_imports可以用编码混淆绕过但应付个人项目足够了。如果你要上生产环境强烈建议用 Docker 或专门的沙箱系统隔离代码执行别指望一个exec就能顶住所有攻击。我这里的思路是安全不是“绝对安全”而是“风险可控且够用”。更重要的是代码执行的结果会作为文本返回给模型。下一轮模型看到执行结果后可以决定是否继续修改代码、重复执行这其实就是一个非常轻量的“代码 REPL Agent”循环。很多复杂任务不需要预设动作让模型自己写代码反而更快特别是在数据处理场景下。5.3 多动作顺序执行与条件分支模型输出可能包含多个动作这就有个执行顺序问题。我用priority字段控制顺序数值小的先执行。默认情况下模型并不一定会显式设置priority所以我加了兜底逻辑如果模型没有设置priority执行器按动作在列表中的顺序依次执行。但有些场景真的需要条件分支。比如用户说“如果明天天气好提醒我带伞否则提醒我带厚外套”这个需求用简单列表就表达不了。我的解决办法是引入一个condition字段模型可以在动作上附加一段 Python 条件表达式执行器先判断表达式是否为真再决定是否执行该动作。{ intent: conditional_reminder, actions: [ { action: run_condition, params: {expression: weather_tomorrow sunny}, priority: 1 }, { action: send_notification, params: {message: 记得带伞}, priority: 2, condition: condition_result } ] }run_condition动作会判断天气并把结果写入上下文变量condition_result然后执行器再根据这个变量决定要不要执行第二个动作。这个方法效果还行但说实话它有点“过于灵活”如果动作链再长一点条件嵌套一多可读性会急剧下降。目前我只在少数明确场景里用大多数情况下我宁可多跑几个动作也不愿意写复杂的条件链。6. 记忆与上下文管理:让 Agent 记住“你是谁”无 Tool Calling 的结构化 Agent 有个被很多人忽略的难点上下文管理。没有工具调用意味着模型每一步的输出都必须带着上下文拼回对话历史里对话历史一长token 消耗就失控而且模型会把老信息忘得干干净净。我的记忆方案分三层。第一层是短期记忆就是每一轮对话的消息列表直接丢给模型。第二层是长期记忆把用户的关键偏好、常用路径、历史任务记录存到 SQLite 数据库里每次对话开始前检索相关记录拼进系统提示词。第三层是会话状态也就是会话中产生的临时变量比如上一步执行结果、用户待补充的参数我用一个全局字典维护。长期记忆对体验提升非常明显。用户第一次说“把报告保存到 ~/Documents/reports/”后我把它存进数据库第二次用户只说“保存报告”系统就能自动补全完整路径因为检索层读到了那条记忆。这一层逻辑完全是确定性的不依赖模型也就是代码在检索模型不用记住任何东西。会话状态同样重要。比如模型输出了一个动作要求用户补充参数用户下一轮补了参数但模型并不记得之前缺什么这时候我就在状态字典里标记“pending_action {action_name, expected_params}”。下一轮模型看到用户输入后会先被状态层的 rule-based 逻辑检查一遍如果发现缺参数且用户输入正好补上了就直接执行动作不需要再走一轮模型。这个机制大幅减少了多轮对话的轮数实测能减少 30% 以上的模型调用量。7. 实测效果:哪类任务稳如老狗,哪类任务直接翻车7.1 表现稳定的场景我拿这套结构化 Agent 跑了三个典型场景第一个是文档查询与整理。把一份 PDF 转成纯文本之后模型可以输出search_web、read_file、extract_text等动作组合去查资料并把结果整理成摘要。这个任务非常稳定因为动作类型少、参数简单模型几乎不会出错。第二个是代码辅助。用户发来一段报错日志模型输出execute_python动作生成一段分析日志的脚本并执行。这个任务体验很惊艳因为有了代码执行能力模型可以用脚本去分析问题而不是靠“猜”。有一次我故意给了一段 5000 行的日志模型生成的脚本先做统计、再把时间线整理成表格全程没用外部工具稳稳拿下。第三个是日程规划。用户说“明天上午三个会帮我排一下优先级”模型输出call_api动作去读日程列表然后输出write_file动作生成一份 Markdown 档。这个任务的难点在于参数校验但由于日程 API 的参数非常结构化模型基本不会出错。7.2 容易翻车的场景最翻车的是开放式搜索任务。用户说“帮我研究一下最近行业动态”模型会生成一堆search_web动作但搜索关键词可能太宽泛返回的结果要么缺失要么错误。这个问题的根源在于模型的搜索能力再强也只是“猜关键词”不像人在搜索时能判断结果质量。我的缓解办法是在动作执行层加一个“结果质量评分”低于阈值的搜索结果不会拼进上下文直接触发模型重新调整关键词。第二个容易翻车的是多步骤长链任务。用户说“从周报里提取本周完成事项整理成 PPT然后发邮件给领导”模型能输出前两个动作但第三个动作“发邮件”很容易漏掉因为任务链条太长。我现在针对这类长链条任务做了一个“任务分解提示词”先把用户的复杂请求拆成若干个子任务再让模型对每个子任务分别输出动作列表。这其实就是两层结构任务分解层和动作生成层。第三个翻车场景是本地部署的小模型。7B 级别的模型在结构化输出上明显吃力输出 JSON 经常带多余注释或者枚举值不匹配。我试过几个国内开源模型只有少数能在不微调的情况下达到可用的结构化输出水平。如果一定要用本地小模型建议做一次针对性微调数据就是各种用户输入对应的结构化 JSON不需要太多样本几千条就够见效。7.3 稳定性数据:一次 400 条真实请求的压测为了不自嗨我做了一轮四百条真实请求的压测覆盖上面六个动作类型。结果如下模型是最常见的旗舰闭源模型temperature 设置为 0。总共跑了五轮每轮随机打乱请求顺序。第一轮结构化解析失败率大约是 12%主要集中在参数类型错误和多余文本上。后来我把系统提示词改得更严格、加上 “only JSON” 约束失败率降到 5% 左右再配合with_structured_output自动重试最终的整体失败率在 2% 以下。动作执行阶段的失败率比结构化解析高不少大约 8%。绝大部分失败来自外部服务的不稳定比如搜索接口超时、文件路径不存在这些属于业务错误不是架构问题。真正跟架构相关的失败只有一个参数白名单校验拦截了模型给出的合法参数因为我把参数模板写得太太紧了。后来我放宽了部分动作的模板问题就消失了。整体来看这轮压测让我对结构化 Agent 的信心增强不少。稳定性数据比我之前用 Tool Calling 做同类任务时好很多主要是错误定位容易几乎每次失败都能直接指向某个具体的校验层或执行层不用大海捞针一样去翻模型输出。8. 常见问题与排查技巧实录8.1 模型输出的 JSON 不合规怎么办这是出现频率最高的问题。我发现几个常见模式模型在 JSON 外面套了 Markdown 代码块模型注释混进了 JSON 内容模型漏掉必填字段或填错了枚举值。我的排查顺序是先看原始输出确认是不是格式问题再用 Pydantic 的错误信息定位字段级别最后根据错误类型决定是改提示词还是加一层预处理清洗。预处理这层很有意思。我加了一个轻量的 JSON 提取函数用正则匹配第一对{...}把前后杂讯剪掉。然后再喂给 Pydantic。这个正则看起来笨但实测非常可靠因为模型即使生成多余的词通常也不会破坏 JSON 本身的括号结构。当然如果模型输出完全不是 JSON那正则也救不了只能走重试。8.2 动作执行超时怎么定位超时是外部动作的老大难。search_web超时、call_api超时、execute_python超时表现都不一样。我给每个动作函数都加了显式的超时参数默认值 5 秒到 10 秒不等并在返回结果里标记是超时还是正常完成。这样执行器可以区分“动作失败了”和“动作跑太久被杀死了”前者可以重试后者必须优化参数或改方案。排查超时的第一件事不是看代码而是看日志里动作函数的执行阶段。我的动作函数在内部会打分段日志比如search_web会打「发起请求」「收到响应」「解析结果」。哪一步慢了立刻就知道。有一次我发现search_web平均耗时 7 秒原因是某搜索引擎的接口经常不可用直到我把请求超时从 30 秒改到 8 秒才解决问题。8.3 模型总是产生幻觉参数怎么办幻觉参数的表现是模型在params里填了一个从未见过的键比如read_file动作里填db_path。这通常是提示词里动作定义不明确导致的。我的解决办法是把每个动作的允许参数写到系统提示词的表格里并在解析后对每个动作做参数白名单校验不合规直接报错不执行。另一种幻觉是参数值本身不存在比如用户说“打开 /tmp/report.pdf”模型输出一个/tmp/report.pdf.bak。这种问题代码侧很难拦截只能靠执行层返回“文件不存在”的错误信息再让模型根据错误重新生成动作。这也说明无 Tool Calling 并不意味着减少错误处理而是把错误处理全部转移到代码侧。8.4 如何加速迭代调试调试结构化 Agent 比调试 Tool Calling 舒服太多。我摸索出来的最快路径是先把模型输出原始 JSON 完整打印到日志里然后写一个独立脚本把这段日志里的 JSON 喂给执行器不经过模型直接跑动作。这样可以把“模型问题”和“执行问题”彻底分离。比如我怀疑search_web在某个输入上返回了坏结果就直接把日志里的 JSON 抄到脚本里跑一次看是参数问题还是函数问题。如果是参数问题改参数字段如果是函数问题改函数实现。整个过程不用重新调模型调试速度提升了不止一倍。这个技巧我强烈建议每个做 Agent 的人都用起来。9. 什么时候该用这套方案,什么时候果断放弃9.1 适合用结构化 Agent 的场景我总结这套方案最适合以下三种情况。第一种是个人工具类 Agent功能边界清晰动作数量少于 20 个用户输入以短指令和中等长度指令为主。第二种是长链条任务但动作类型固定比如文档批处理、代码生成加执行、报表统计这类任务不太需要天马行空的工具调用更多是固定动作的组合。第三种是需要严格控制成本的场景结构化输出可以把模型 token 消耗压到一个较低的水平因为系统提示词里的工具描述少了很多模型输出也被限制到极小。9.2 不适合甚至该果断放弃的场景反过来如果你要做的是面向大众的开放式 Agent 平台或者你的 Agent 需要对接上百个外部工具那结构化方案就会吃力。上百个动作意味着上百个枚举值和参数模板模型的决策准确率会随动作数量增加而骤降提示词也会臃肿到超出上下文窗口。这种情况下老老实实用 Tool Calling让平台帮你去管理工具调用机制反而省心。另外如果用户可能提出你完全没有预定义过的动作需求比如一个 Agent 需要即兴浏览网页、点击页面按钮、执行自定义脚本那结构化方案完全搞不定。你不可能把所有网页交互都抽象成固定的几个动作这时候要么用通用浏览器 Agent Tool Calling要么上专门的 RPA 方案。风险评估也很关键。如果你做的是支付、医疗这类强规则行业动作失误的代价极高我建议不要依赖模型输出作为执行依据无论它是否结构化。结构化可以降低失误率但不能消除失误真正的安全需要规则引擎和人工审批兜底。10. 一些实操心得这套无 Tool Calling 的结构化 Agent并算不上一项新发明它更像是在复杂架构和简单可靠之间做了一个取回。踩过无数 Tool Calling 的坑之后我发现“让模型少做、让代码多做”这条路最适合我个人项目里的实际需求它的可维护性、可观测性、稳定性都比纯工具调用方案好出一截。如果你也想在自己项目里试这套方案我建议从最小的闭环开始先定义一个包含三四个动作的 Pydantic 模型接一个模型跑通“用户输入 → JSON → 动作执行 → 结果反馈”这条链路再去扩展动作类型。 不要在第一天就把十几个动作全部塞进去动作数量越多调试成本越高越容易重蹈 Tool Calling 的覆辙。最后提醒一句模型选型很关键。不同的模型对 JSON Schema 的支持程度天差地别即使同一个模型不同版本的表现也不一样。你在选模型时一定要用自己的任务跑一遍结构化输出的测试集别看跑分、看宣传实测才准。如果测试集上结构化失败率超过 5%果断换模型或换微调路线别在烂地基上盖楼。很高兴能看到你一路读到这里。我也相信绝大多数 Agent 开发需求其实并不需要那么复杂的工具调用机制一个能稳定输出 JSON 的模型加上一层扎实的工程代码已经能交付非常多好用的自动化体验。剩下的路我们边踩坑边前进。
返回列表