1. Agent 不是员工,是实习生
Agent 能规划,能调用工具,能连续执行。
这很强。也很危险。
因为模型会犯错。它可能调错工具,填错参数,误解返回值,重复调用接口,甚至尝试执行高风险动作。
所以生产级 Agent 的第一原则是:模型负责“想”,系统负责“准不准做”。
2. Agent 最容易出问题的 6 个地方
普通聊天机器人答错了,最多是回答不准。
Agent 答错了,可能会真的调用系统。
这就是风险升级。
Agent 常见失控点与治理方式。
这里最核心的是工具。
模型输出的不是“结果”,而是“行动意图”。
一旦行动意图被系统执行,就必须有门禁。
3. 源码视角:一次工具调用到底经过什么?
从表面看,Agent 只是“调用一个函数”。
从源码看,它其实要经过一条完整链路。
Agent 工具调用的源码主链路。
这条链路有三个关键角色。
第一,模型只负责生成 `AIMessage.tool_calls`。它说:“我想调这个工具,参数是这些。”
第二,`ToolNode` 负责调度。它把工具调用包装成 `ToolCallRequest`,再执行工具。
第三,`BaseTool` 负责校验和执行。它会处理输入、回调、异常和输出包装。
源码级理解:ToolNode 是调度层,BaseTool 是执行层,Middleware 是治理层。
4. 工具权限,不让模型随便调
工具不是越多越好。
一次性给模型太多工具,模型会更容易选错。
更重要的是:不同工具风险不同。查天气和删除数据,绝对不能一个权限级别。
工具调用必须先经过 Tool Gate。
生产环境里,工具至少要分三级。
低风险工具,自动执行。比如查知识库、查天气、读取公开数据。
中风险工具,人工审批。比如写文件、发邮件、执行 SQL。
高风险工具,直接拒绝。比如转账、删除核心数据、修改权限、自动下单。
不要把“不能下单”只写进 Prompt。真正的限制要写在工具门禁里。
5. 参数校验,让错误在执行前暴露
模型可能会填错参数。
比如金额应该是数字,模型给了“很多钱”。
比如 SQL 只能 SELECT,模型给了 UPDATE。
比如股票代码应该是 6 位,模型给了公司简称。
这类问题不能等业务接口报错。要在工具入口拦住。
LangChain 的 `BaseTool` 有一个关键字段:`args_schema`。
它可以用 Pydantic 描述工具参数。工具执行前,`_parse_input()` 会根据这个 schema 做校验。
class QueryStockInput(BaseModel): symbol: str = Field(description="6 位股票代码") days: int = Field(default=20, ge=1, le=250) @tool(args_schema=QueryStockInput) def query_stock(symbol: str, days: int = 20) -> str: """查询股票历史行情。""" ...这个示例不是重点。重点是背后的链路:参数先过 schema,再进业务函数。
参数错了,应该返回可读错误,让模型有机会修正,而不是让整个 Agent 崩掉。
6. BaseTool.run() 的源码分流
Agent 工具执行时,错误不是一个类型。
参数错误、业务错误、系统错误,要分开处理。
BaseTool.run() 中的执行链路和异常分流。
源码里,`BaseTool.run()` 大致做了这些事。
先启动回调。再把输入转成 `args/kwargs`。再调用 `_parse_input()` 校验参数。最后执行 `_run()`。
执行过程中,源码会分流处理 `ValidationError`、`ToolException` 和普通 `Exception`。
`handle_validation_error` 控制参数校验错误怎么返回。
`handle_tool_error` 控制工具主动抛出的 `ToolException` 怎么返回。
普通未知异常默认继续抛出,应该交给 middleware 或外层统一兜底。
源码级结论:BaseTool 不是只执行函数,它还负责参数校验、回调记录、异常分流和输出包装。
7. Middleware,专门处理横切逻辑
权限、限流、重试、缓存、降级,不应该散落在每个工具函数里。
这些是横切逻辑。
LangChain 的 middleware 可以包住工具调用。
`wrap_tool_call` 就是工具层最重要的钩子。
wrap_tool_call 可以拦截、重试、降级和返回 ToolMessage。
一个可靠的工具兜底逻辑,应该是这样:
@wrap_tool_call def safe_tool_call(request, handler): if not has_permission(request): return ToolMessage( content="没有权限执行该工具。", tool_call_id=request.tool_call["id"], ) try: return handler(request) except TimeoutError: return ToolMessage( content="工具超时,请稍后重试。", tool_call_id=request.tool_call["id"], )注意这里的关键点:返回的是 `ToolMessage`。
它不是普通字符串。它带着 `tool_call_id`,能让模型知道这是哪一次工具调用的结果。
工具失败时,不要只抛异常。能恢复的错误,要转换成模型能理解的 ToolMessage。
8. 高危动作必须人工审批
自动化不是让模型无边界执行。
凡是不可逆、涉及资金、涉及隐私、影响权限的动作,都必须停下来。
LangChain 的 Human-in-the-loop middleware 可以让工具调用在执行前暂停。
Human-in-the-loop 让高危工具在执行前等待人类决策。
它的核心不是“报错”。
它是“暂停”。
暂停后,系统把工具名和参数展示给审核人。审核人可以批准、编辑、拒绝,或者直接给模型反馈。
这里必须配合 checkpointer。因为图执行被暂停后,状态要保存下来,后面才能恢复。
高危动作的正确流程:模型提出动作,系统暂停,人类确认,图再继续。
9. 生产级 Agent 的安全架构
不要只在 LangChain 里面做安全。
企业级系统要从入口、Agent、工具、业务系统、观测系统五层一起治理。
入口层做认证、限流、黑白名单。
Agent 层控制模型、工具、记忆和上下文。
治理层做 middleware、Tool Gate、Guardrails、人工审批。
业务层做二次校验。不要相信上游一定干净。
观测层做 Trace、日志、告警和回放。
最稳的设计是“双重校验”:Agent 工具层校验一次,业务系统再校验一次。
10. 这几个字段必须记住
写工具时,重点看 `BaseTool` 的这几个字段。
`name`:工具名。模型靠它选择工具。名字要短,要明确。
`description`:工具说明。模型靠它判断什么时候用。描述越模糊,误调越多。
`args_schema`:参数 schema。它决定参数校验是否可靠。
`handle_validation_error`:参数校验失败时,是否转成可读输出。
`handle_tool_error`:工具主动抛出 `ToolException` 时,是否转成工具输出。
`return_direct`:是否让工具结果直接返回,不再让模型二次加工。
`response_format`:工具结果是普通内容,还是内容加 artifact。
判断一个工具是否能上线,不看 Demo 能不能跑。看它有没有 schema、权限、异常、审计和降级。
11. 最小落地清单
最后,给一个生产检查清单。
第一,所有工具必须有明确 name、description 和 args_schema。
第二,工具必须按风险分级:只读、可写、高危。
第三,高危工具必须走 Human-in-the-loop。
第四,所有工具调用必须记录 requestId、userId、tool_name、args、result、耗时、错误。
第五,所有可恢复错误都要返回 ToolMessage,不能让 Agent 直接崩。
第六,工具要有超时、重试、限流和降级。
第七,业务系统要做二次校验,不能把权限完全交给模型。
第八,线上要能回放一次完整 Agent 执行链路。
12. 总结
Agent 的价值,是让模型能做事。
Agent 的风险,也是让模型能做事。
所以真正的重点不是“让 Agent 更自由”。
而是让 Agent 在可控边界内行动。
一句话:生产级 Agent 不是靠模型自觉,而是靠系统设卡。
内容来源:Agent 不可靠怎么办?工具权限、参数校验、异常兜底与源码解析:功能变化与行业影响解析_热闻岛