【agent篇】agent进阶之Middleware易错点 1.为什么电话号码原样输出未被拦截PIIMiddleware脱敏处理个人信息关于电话的配置pii_middleware_phone PIIMiddleware( phone_number, detectorphone_number, strategyblock, apply_to_inputTrue )18798523405是 11 位中国手机号LangChain PIIMiddleware 内置的phone_number检测器默认匹配的是 E.164 格式带和国家码例如8618798...所以18798523405被漏判了自然就走不到 block 分支。解决方案做法适用场景方案 A改输入phone_number改用8618798523405格式验证内置 detector 行为方案 B用PIIMiddleware的detector自定义正则r1[3-9]\d{9}匹配中国大陆手机号真实业务场景2.ModelFallbackMiddleware参数错误问题ModelFallbackMiddleware模型调用失败时给出降级处理方案#原代码 model_fallback_middleware ModelFallbackMiddleware( modelChatOpenAI( # ❌ model 不是合法参数 modelqwen-plus, ... ), fallback_modelChatOpenAI( # ❌ fallback_model 不是合法参数 modelgpt-3.5-turbo, ... ) )此外这里如此定义后model不是不是单独定义的无法直接create_agent时model model。ModelFallbackMiddleware 只接受位置参数fallback 模型列表主模型由create_agent(model...)负责middleware 只管 fallback。#修改后的代码 #两个模型单独定义之后 model_fallback_middleware ModelFallbackMiddleware( fallback # ✅ 位置参数直接传入 BaseChatModel 实例 )3.interrupt_on字段名错误问题#原代码 interrupt_on{ transfer_money: { description: 请确认转账操作, options: [确认, 取消], # ❌ InterruptOnConfig 没有这个字段 default: 取消 # ❌ InterruptOnConfig 没有这个字段 } }InterruptOnConfig只认allowed_decisions没有options/default字段#修改后的代码 interrupt_on{ transfer_money: { allowed_decisions: [approve, reject], # ✅ 正确字段名 description: 请确认转账操作, } }4.只 invoke 了一次为什么拿不到最终结果HumanInTheLoopMiddleware 人工审核#原代码 config {configurable: {}} res agent.invoke( {messages: [HumanMessage(content请将1000元转账给张三)]}, configconfig ) # 到这里就结束了工具根本没执行需要两次第一次触发 interrupt 暂停第二次Command(resume...)恢复第一次invoke让模型生成 tool call中间件拦截并interrupt暂停 → 你审查后用Command(resume{decisions: [{type: approve}]})恢复工具才真正执行。#修改后的代码 from langgraph.types import Command config {configurable: {thread_id: 1}} # 第一次 invoke模型生成 tool call → 中间件拦截 → interrupt 暂停 res agent.invoke( {messages: [HumanMessage(content请将1000元转账给张三)]}, configconfig ) # 第二次 invoke人工确认后恢复工具才真正执行 res agent.invoke( Command(resume{decisions: [{type: approve}]}), configconfig )类型用途格式approve直接执行不改参数{type: approve}edit修改参数后执行{type: edit, edited_action: {name: transfer_money, args: {amount: 500, to: 张三}}}reject拒绝执行告诉模型被拒了{type: reject}或{type: reject, message: 金额过大}respond跳过工具人工直接给结果{type: respond, message: 转账已完成}5.wrap_model_call调用方式错误问题wrap_model_call装饰器只会传(request, handler)两个参数#原代码 wrap_model_call def retry_model( request: ModelRequest, handler, # ✅ handler 是框架传入的回调 ) - ModelResponse: for i in range(retry_count): try: response model(request.messages, **kwargs) # ❌ 直接调 model()绕过了框架 return ModelResponse(response) except Exception as e: print(fRetrying model call {i1}/{retry_count}) print(e) # ❌ 全部失败后没有 return静默返回 Nonehandler就是框架传给你的执行按钮调一次handler(request)就等于跑一次模型。你不需要自己拿model去调框架在 handler 里已经帮你处理了。wrap_model_call def retry_model( request: ModelRequest, handler, # ✅ handler 是框架传入的回调 ) - ModelResponse: for i in range(3): try: return handler(request) # ✅ 调 handler(request) 让框架执行模型调用 except Exception as e: print(fRetrying model call {i1}/3) print(e) raise RuntimeError(All retries failed) # ✅ 全部失败抛异常不静默返回 Nonewrap_model_call 的执行链6.判断是否带下标遇到xxx is not subscriptable先查源码确认这个类是不是Generic的子类。# ModelRequest —— 是 Generic可以下标 ✅ class ModelRequest(Generic[ContextT]): ... # ToolCallRequest —— 普通 dataclass不是 Generic ❌ class ToolCallRequest: Tool execution request passed to tool call interceptors. tool_call: ToolCall tool: BaseTool | None ...