19|Middleware 实战:在决策循环中加入控制能力 - eino教程

发布时间:2026/10/22 7:22:49

19|Middleware 实战:在决策循环中加入控制能力 - eino教程 19Middleware 实战在决策循环中加入控制能力《Eino 实战用 Go 构建 AI 应用与智能体》· 第 19 篇关键词ChatModelAgentMiddleware、生命周期钩子、WrapInvokableToolCall、审计、校验前置条件已完成 16从流程到 Agent。到这一篇我们能用 Agent 调工具了。但生产环境还有一批需求它们不属于任何一个工具而属于所有工具每次工具调用都要记录审计日志。某些工具必须限流或拦截。模型调用前要压缩上下文。工具频繁失败要重试。把这些逻辑写进每个工具函数会造成大量重复。Middleware 就是为这类横切能力准备的。一、Middleware 解决什么问题Middleware 的核心定位是不改变 ReAct 循环的结构只在固定生命周期点位上注入行为。推理 → 行动 → 反馈这个循环不变。Middleware 控制的是这个循环运行时怎么跑需求对应能力运行前注入工具或增强指令BeforeAgent模型调用前压缩上下文BeforeModelRewriteState工具调用时做权限检查WrapInvokableToolCall模型失败时重试或切换ModelRetryConfig/ModelFailoverConfig运行结束后清理AfterAgent一个容易混淆的点ModelRetry和ModelFailover在代码里是ChatModelAgentConfig的内置字段ModelRetryConfig/ModelFailoverConfig概念上对应WrapModel钩子。它们不是需要你自己实现的 Middleware。二、生命周期钩子ChatModelAgentMiddleware是个接口主要钩子如下钩子时机典型用途BeforeAgentAgent 运行前仅一次增强指令、注入工具BeforeModelRewriteState每次模型调用前修改消息、压缩上下文AfterModelRewriteState每次模型调用后修补状态、处理响应WrapModel单次模型调用维度重试、改写返回WrapInvokableToolCall单次工具调用维度权限、审计、结果改写AfterAgentAgent 成功结束后后处理、清理要点BeforeAgent只跑一次BeforeModelRewriteState每轮模型调用都跑。副作用要放在正确的位置。BeforeModelRewriteState的改动会被保留到后续迭代它修改的是内部状态。这既是它能压缩上下文的原因也意味着写错了会影响后续所有轮次。Wrap*是包装语义拿到原始 endpoint返回新的 endpoint。你可以选择调用它、拒绝调用或调用后改写结果。三、写一个 Middleware实现方式嵌入BaseChatModelAgentMiddleware获得所有钩子的空实现只覆盖需要的方法。typeauditMiddlewarestruct{*adk.BaseChatModelAgentMiddleware calls[]string}func(a*auditMiddleware)WrapInvokableToolCall(_context.Context,endpoint adk.InvokableToolCallEndpoint,tCtx*adk.ToolContext,)(adk.InvokableToolCallEndpoint,error){returnfunc(ctx context.Context,argumentsInJSONstring,opts...tool.Option)(string,error){a.callsappend(a.calls,tCtx.Name)// 敏感操作在此拦截不需要修改工具本身。iftCtx.Namedelete_ticket{return,fmt.Errorf(工具 %s 已被策略拒绝,tCtx.Name)}// 调用真实实现。out,err:endpoint(ctx,argumentsInJSON,opts...)iferr!nil{return,err}returnout,nil},nil}这段代码做了三件事记录调用、按策略拦截、透传执行。注意拦截是返回错误而不是调用endpoint——拦截能力来自“你决定要不要调用原函数”。注册时用Handlers字段agent,err:adk.NewChatModelAgent(ctx,adk.ChatModelAgentConfig{// ...Handlers:[]adk.ChatModelAgentMiddleware{mw},})Handlers是推荐字段Middlewares是早期字段官方标注为 Deprecated。新代码用Handlers。四、完整示例下面这段程序注册了一个审计 Middleware并注册了一个危险工具用于验证拦截已实机运行packagemainimport(contextfmtlogstringsgithub.com/cloudwego/eino/adkgithub.com/cloudwego/eino/components/modelgithub.com/cloudwego/eino/components/toolgithub.com/cloudwego/eino/components/tool/utilsgithub.com/cloudwego/eino/composegithub.com/cloudwego/eino/schema)typeTicketReqstruct{TicketIDstringjson:ticket_id jsonschema:description工单编号}typemockModelstruct{}func(m*mockModel)Generate(_context.Context,in[]*schema.Message,_...model.Option)(*schema.Message,error){for_,msg:rangein{ifmsg.Roleschema.Tool{returnschema.AssistantMessage(已分派给网络支持组。,nil),nil}}returnschema.AssistantMessage(,[]schema.ToolCall{{ID:call_1,Type:function,Function:schema.FunctionCall{Name:get_ticket,Arguments:{ticket_id:T-1001},},}}),nil}func(m*mockModel)Stream(ctx context.Context,in[]*schema.Message,opts...model.Option)(*schema.StreamReader[*schema.Message],error){msg,err:m.Generate(ctx,in,opts...)iferr!nil{returnnil,err}sr,sw:schema.Pipe[*schema.Message](1)gofunc(){defersw.Close()sw.Send(msg,nil)}()returnsr,nil}func(m*mockModel)WithTools([]*schema.ToolInfo)(model.ToolCallingChatModel,error){returnm,nil}// ---- 自定义 Middleware在工具调用前后加校验 ----typeauditMiddlewarestruct{*adk.BaseChatModelAgentMiddleware calls[]string}func(a*auditMiddleware)WrapInvokableToolCall(_context.Context,endpoint adk.InvokableToolCallEndpoint,tCtx*adk.ToolContext,)(adk.InvokableToolCallEndpoint,error){returnfunc(ctx context.Context,argumentsInJSONstring,opts...tool.Option)(string,error){a.callsappend(a.calls,tCtx.Name)// 敏感操作在此拦截不需要修改工具本身。iftCtx.Namedelete_ticket{return,fmt.Errorf(工具 %s 已被策略拒绝,tCtx.Name)}// 调用真实实现。out,err:endpoint(ctx,argumentsInJSON,opts...)iferr!nil{return,err}returnout,nil},nil}func(a*auditMiddleware)BeforeAgent(ctx context.Context,runCtx*adk.ChatModelAgentContext)(context.Context,*adk.ChatModelAgentContext,error){fmt.Println([middleware] BeforeAgent: 运行前注入)returnctx,runCtx,nil}funcmain(){iferr:run();err!nil{log.Fatal(err)}}funcrun()error{ctx:context.Background()ticketTool,err:utils.InferTool[*TicketReq,string](get_ticket,查询工单状态,func(_context.Context,req*TicketReq)(string,error){return工单 req.TicketID 状态已分派,nil})iferr!nil{returnerr}deleteTool,err:utils.InferTool[*TicketReq,string](delete_ticket,删除工单危险操作,func(_context.Context,req*TicketReq)(string,error){return已删除 req.TicketID,nil})iferr!nil{returnerr}mw:auditMiddleware{BaseChatModelAgentMiddleware:adk.BaseChatModelAgentMiddleware{}}agent,err:adk.NewChatModelAgent(ctx,adk.ChatModelAgentConfig{Name:assistant,Description:工单助手,Instruction:需要时调用工具查询。,Model:mockModel{},ToolsConfig:adk.ToolsConfig{ToolsNodeConfig:compose.ToolsNodeConfig{Tools:[]tool.BaseTool{ticketTool,deleteTool},}},Handlers:[]adk.ChatModelAgentMiddleware{mw},})iferr!nil{returnerr}runner:adk.NewRunner(ctx,adk.RunnerConfig{Agent:agent})iter:runner.Query(ctx,T-1001 什么状态)for{event,ok:iter.Next()if!ok{break}ifevent.Err!nil{returnevent.Err}ifmv:event.Output.MessageOutput;mv!nilmv.Message!nil{fmt.Printf( 事件: role%-9s tool%-10s content%q\n,mv.Role,mv.ToolName,mv.Message.Content)}}fmt.Println(Middleware 观察到的工具调用:,strings.Join(mw.calls,, ))returnnil}实机输出[middleware] BeforeAgent: 运行前注入 事件: roleassistant tool content 事件: roletool toolget_ticket content工单 T-1001 状态已分派 事件: roleassistant tool content已分派给网络支持组。 Middleware 观察到的工具调用: get_ticket几点观察BeforeAgent只打印了一次尽管后续有多次模型交互。WrapInvokableToolCall记录了get_ticket实现了统一的审计而工具函数本身一行日志都没有。危险工具delete_ticket虽已注册但模型没有请求调用它所以拦截逻辑未触发——这是正确行为拦截只在工具被调用时生效。五、Middleware 的几个实用模式1. 统一审计在WrapInvokableToolCall里记录工具名、调用者从 SessionValues 取、耗时和结果状态。注意脱敏不要把原始参数和结果直接写进日志。2. 策略拦截按工具名、参数内容或调用者权限决定是否放行。但要注意这里能拦截的是“Agent 内部发起”的调用真正的权限控制仍应在工具实现内完成第 11 篇。Middleware 是纵深防御的一层不是唯一一层。3. 结果改写工具返回的内容太长时在 Middleware 里裁剪避免挤占上下文out,err:endpoint(ctx,argumentsInJSON,opts...)iferr!nil{return,err}iflen([]rune(out))maxLen{outstring([]rune(out)[:maxLen])…已截断}returnout,nil4. 上下文压缩在BeforeModelRewriteState里裁剪消息。切记它会作用于后续所有轮次改动前想清楚会不会丢掉必要信息。六、常见问题Middleware 没生效检查是否注册在Handlers字段而不是已废弃的Middlewares以及是否嵌入了BaseChatModelAgentMiddleware否则需要实现全部方法。想拦截工具调用但没触发拦截发生在工具被调用时。先用事件流确认模型是否真的请求了该工具。BeforeModelRewriteState的改动影响了后续轮次这是设计行为——它修改的是会被保留的状态。只在需要持久改变上下文时使用。顺序搞不清多个 Middleware 按注册顺序处理。涉及顺序敏感的逻辑如“先审计再限流”时用打印语句确认实际执行顺序不要凭假设。重试逻辑自己实现不生效先看内置的ModelRetryConfig/ModelFailoverConfig是否满足需求通常不必自己写WrapModel。日志里出现了敏感数据Middleware 很容易成为“统一记录一切”的地方也最容易泄露数据。审计记录只记元信息工具名、耗时、成功与否不记参数和返回值原文。七、小结Middleware 不改变 ReAct 循环结构只在生命周期点位上注入行为。BeforeAgent仅执行一次BeforeModelRewriteState每轮执行且改动会被保留。Wrap*是包装语义决定是否调用原实现从而获得拦截和改写能力。注册用Handlers字段嵌入BaseChatModelAgentMiddleware可获得钩子默认实现。重试与故障切换优先用内置配置字段不必自行实现。Middleware 是纵深防御的一层不能替代工具内部的鉴权。下一篇《人机协作在关键操作前暂停并等待审批》我们实现中断与恢复的完整闭环。参考资料与版本说明资料核对日期为2026 年 10 月 9 日五分钟上手 Eino ADKMiddleware 速查Eino ADK 概述Eino 主仓库本文示例按Eino v0.9.21编写使用模拟模型不访问网络已实机运行正文输出为真实结果。
延伸阅读

更多相关文章

2026/10/22 7:22:49

铜材激光焊接光源选型 绿光与红外的分工边界

铜材激光焊接光源选型 绿光与红外的分工边界 这篇文章讲的是铜材激光焊接中红外激光与绿光激光的光源选型判断框架:按吸收特性、缺陷主导类型、熔深与节拍要求、综合成本口径四个维度评估,确定两条路线的适用边界。 选型争议的起点是铜的物理特性&…

2026/10/22 8:32:58

veg200数据集科学划分:面向农业AI落地的鲁棒性分层策略

1. 项目概述:为什么 veg200 数据集划分不是“复制粘贴”就能搞定的事 你拿到 veg200 蔬菜分类数据集,第一反应是不是直接 train_test_split 一跑,再手动挪几个文件夹完事?我试过——三次模型在验证集上准确率波动超过12%&#x…

2026/10/22 8:32:58

Docker 搭建 Nexus3 并配置 Docker 私有仓库实战指南

1. 为什么要在内网自建 Docker 镜像仓库 很多团队一开始都是直接用公共镜像仓库拉取基础镜像,本地构建完业务镜像后,要么推到公共仓库,要么干脆只在本地留着。等到服务器规模一上来,问题就全冒出来了:构建机拉取镜像慢…

2026/10/22 8:32:58

LVM卷组与条带化:提升磁盘IO性能的底层实践

1. 项目概述:从磁盘管理的“堵车现场”到“高速公路”的底层逻辑 “卷组、条带化”这六个字,乍看像存储系统里的黑话,但其实它解决的是每个用过电脑的人都会遭遇的真实困境——硬盘明明还有几百GB空闲,复制大文件却慢得像蜗牛&…

2026/10/22 8:32:58

纯Java手写关键词文本排序检索系统:从分词到TF-IDF排序的轻量级实现

1. 从零搭建关键词文本排序检索系统,我踩过的坑和最终方案 前阵子接了个需求,要在一个内部文档管理模块里做一套基于关键词的文本排序检索。说白了就是用户输入几个词,系统从一堆文档里把最相关的排前面返回。听起来像是“搜索”两个字就能打…

2026/10/22 8:32:58

Docker 搭建 Nexus3 私有仓库:从部署到权限管理实战

1. 为什么本地环境值得单独跑一个制品仓库很多团队一开始都是直接用公共镜像仓库,拉个镜像、推个包都挺顺手,直到某天网络抖动、某个基础镜像突然被下架、或者 CI 流水线因为外部仓库限流卡了半小时,才意识到"依赖外部服务"这件事在…

2026/10/22 8:27:58

Feign连接超时真相:从TCP三次握手到Linux内核参数

1. 这不是超时,是连接被“卡住”了——一次Feign调用故障的真相还原刚接手一个运行三年的订单履约系统,某天凌晨两点收到告警:核心支付回调链路中,Feign调用下游风控服务的平均耗时从80ms骤升至3200ms,失败率突破45%。…

2026/10/21 4:53:08

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/22 0:01:49

电商智能运营体系从0到1:架构设计、落地步骤与避坑指南

前两年我们团队接了一个电商平台的运营中台项目,目标很直白:把过去靠运营拍脑袋做的选品、定价、发券、推活动这些事,逐步用数据算法和自动化流程代替。项目名字内部就叫“电商智能运营体系”。最近有不少朋友问我当时是怎么设计的、踩过哪些…

2026/10/22 0:01:49

若依框架AI代码生成不稳定?用Agent Skill三层结构+五步法搞定

最近一段时间,我一直想把后台管理系统里那些机械重复的增删改查代码交给AI去写。框架用的是若依,按理说这类业务代码模式非常固定,应该是AI最擅长的事情。可真试下来却不是那么回事——你让AI生成一个用户管理模块,它给出的Contro…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑