FEATURED · 精选文章

不搭 demo,直接解剖一个生产级 Agent 平台:loser-agent 全链路地图

发布时间 / 2026/9/11 20:13:50
来源 / 创域科博编辑部
栏目 / 资讯中心
不搭 demo,直接解剖一个生产级 Agent 平台:loser-agent 全链路地图 源码地址后端地址 前端地址市面上 Agent 开发教程不少十行代码跑通ReActAgent.builder().build()的 demo 满天飞。但 demo 不会告诉你真实生产环境里一个 Agent 平台到底由什么组成、一次聊天请求从发出去到看到回复中间穿了多少层。这门课不搭 demo。我们手上有一个真的在跑的生产级 Agent 平台——loser-agent基于阿里开源的 AgentScope Java 2 构建带灰度发布、人工审批、审计、评测、知识库、定时任务是套完整的平台代码。整门课只做一件事跟着一次聊天请求走完整个平台的旅程。这一篇是全课的导航图。在钻进任何一行源码之前先让你知道整条旅程长什么样、每一站叫什么、后续每一篇都在旅程的哪个位置。一张图看懂全课路线一次聊天请求的旅程本质上就七个站点用户发消息 → 入口鉴权 → 灰度路由 → Agent 装配 → 模型路由 → 工具与执行 → 持久化每个站点的存在都不是偶然的——入口要处理流式协议灰度层决定这个请求命中哪个版本的 Agent装配决定这个 Agent 会什么、能碰什么持久化让用户刷新页面后对话还在。后续每一篇都是这条线上某个站点的展开。篇章集数旅程上的位置地图开篇01-02全景 点火主线链路03-11入口→持久化的七站逐一展开插页事故复盘12可选 12b独立复盘集能力扩展13-17HITL / Skills / 多 Agent / RAG工程化支撑18-22安全 / 审计 / 评测 / 调度 / 总复盘整门课 22 集加 1 集可选插页约 3 个月更完。适合 Java 后端转 Agent 开发的工程师读——你熟 Spring Boot / MyBatis不需要预设 Agent 框架知识。读完整套你能独立看懂 loser-agent 全部主链路源码并具备用 HarnessAgent 装配真实业务 Agent 的能力。为什么不直接用框架现成的入口AgentScope 官方提供了agentscope-spring-boot-starter全部代码就 4 个类自动装配默认的 ReActAgent/Toolkit/Memory bean。BOM 里还管理着一个 agui 模块。看起来拿来用就行。但 loser-agent 的AguiChatController类头 Javadoc 写得很直白AG-UI Chat Controllerv3 自建替代 v2 假设的 AguiMvcController。为什么不直接用现成的打开AguiChatController.chat()方法从第 104 行到 286 行约 165 行没拆 helper 类——一次请求的全部平台级关注点同屏可见。这一个方法里编织了归属鉴权、灰度路由、HITL 恢复与自动拒绝、多模态附件、审计上下文、Plan 同步、熔断上报、记忆抽取——8 个平台级关注点。这些框架不可能替你决定它不知道你的灰度策略、你的 RBAC 模型、你的审计口径。于是 loser-agent 得到一个全课反复出现的分界原则框架交钥匙管 Agent 运行时语义ReAct 循环、中间件、状态 SPI、事件模型平台自建管请求生命周期里框架之外的其余一切协议适配、灰度路由、装配、持久化、审计。第 22 集会用一张完整决策清单回收这个主题。一次请求的调用顺序这一篇只看调用顺序不进任何被调类的内部——把chat()方法当一张地图来读。第一段归属鉴权。第 120 行agentConfigService.assertCanAccess(agentCode,empId);V2.20 加入的运行时归属鉴权——私有PRIVATEAgent 只有 owner 能发起会话无权抛SecurityException由ExceptionHandler统一转成 403。这是请求的第一道门。第二段灰度路由。第 123-138 行StringenvNameenvResolver.resolve(request);intversiongrayRoutingService.resolveVersion(agentCode,empId,envName);if(version0){StringversionedKeyAgentVariant.registryKeyWithVersion(agentCode,version);factoryregistry.find(versionedKey);...}else{factoryregistry.find(agentCode);// 未发布 / fallback → DRAFT 路径}两步RequestEnvResolver从请求里识别环境gray/stableGrayRoutingService按用户和 Agent 算出他命中的发布版本——0 走 stable大于 0 命中灰度。拿到版本号后用「带版本的 key」去AgentFactoryRegistry找工厂找不到触发registry.refresh()再找仍没有回落 DRAFT。这里冒出三种 Agent 变体agentCode草稿、agentCode_published稳定发布、agentCode_published_vn灰度版本key 拼装规则收在AgentVariant里。灰度与发布流程后面第 04 集解剖。第三段建立 SSE 通道。第 144-153 行SseEmitteremitternewSseEmitter(3600_000L);response.setHeader(X-Accel-Buffering,no);response.setHeader(Cache-Control,no-cache);超时给足 1 小时X-Accel-Buffering: no是给 Nginx 的指令——关掉反向代理缓冲否则 SSE 会被攒成一坨「假流式」。随后注册onCompletion/onTimeout/onError三个回调做连接统计。事件流信封协议第 03 集专讲。第四段会话落库。第 156 行调upsertSession417-443 行按 threadId 查没有则新建标题取首条用户消息截断 50 字有则校验归属——threadId 属于其他用户直接 403防会话劫持。查与写包在transactionTemplate事务里中途失败不留脏行。第 09 集展开。第五段装配 Agent。第 159-166 行三行完成「从数据库配置到可运行 Agent」ListStringroleCodesresolveRoleCodes(request);RuntimeContextrcctxBuilder.build(threadId,empId,ssoToken,null,agentCode,roleCodes);Agentagentfactory.createAgent(rc);resolveRoleCodes()从 AuthFilter 注入的 request attribute 取角色编码auth 关闭时为空列表ctxBuilder.build()把 threadId、用户身份、SSO 令牌、角色打包进框架的RuntimeContext贯穿本次请求每一轮推理factory.createAgent(rc)是装配主战场内部是一条HarnessAgent.builder()链装配里值得现在就记住一个插件点——BaseFinanceAgentFactory第 141 行.stateStore(stateStore)// 平台的 DbAgentStateStore 插进框架的 AgentStateStore SPI框架定义AgentStateStore接口平台提供 MySQL 实现——从此 Agent 每一轮状态消息、工具调用、暂停点都落进ac_agent_*表。十个 Builder 开关怎么把 DB 配置翻译成 Agent第 05 集模型怎么路由、挂了怎么办第 06 集。第六段请求语义补全。第 169-227 行执行前还有一段预处理事务HITL 状态stateStore.findAskingToolCalls()查上一轮有没有工具暂停在「等人工审批」。带了confirmResults就恢复执行没带就自动按拒绝处理——否则线程永久卡死。第 13 集。多模态附件文本附件拼TextBlock图片转ImageBlock(URLSource)与用户文字组装成框架的Msg。runId traceId两个 UUID前者关联本次运行的全部事件后者贯穿所有 LLM 轮次与工具调用——评测和调用链下钻全靠它们。Plan 监听器 工具审计器通过事件流doOnNext驱动不挡主流程。第 21 集 / 第 19 集。第七段执行与事件流。第 228-284 行全链路最核心的三次变换FluxAgentEventrawFlux((HarnessAgent)agent).streamEvents(userMsg,rc);rawFluxrawFlux.doOnNext(toolAuditor::dispatch);FluxSseEnvelopeadaptedSseFrontendAdapter.adapt(rawFlux);adapted.contextWrite(ctx-ctx.put(AgentAuditContext.CONTEXT_KEY,...)).subscribe(env-{...sendSse(emitter,env);},err-{...reportStreamFailure(agentCode);emitter.completeWithError(err);},()-{...extractMemory(empId,threadId,agentCode);emitter.complete();});执行streamEvents(userMsg, rc)启动框架的 ReAct 循环思考→调工具→观察→再思考返回FluxAgentEvent——框架把执行过程统一建模为事件流。适配SseFrontendAdapter.adapt()把框架事件映射为平台信封SseEnvelope前端才看得懂。订阅三个回调是三条出口——onNext 逐条推前端onError 区分客户端断开与真实模型故障只有后者上报熔断器连续失败达阈值自动切备用模型第 06 集onComplete 审计落库后extractMemory()做记忆抽取——记忆发生在会话完整结束之后第 11 集。最后看一个数量级问题从 HTTP 接收到subscribe()提交chat()没有任何阻塞调用SseEmitter一返回Servlet 请求线程即释放之后的事件推送都在 Reactor 调度线程上完成。一万路并发会话就是一万条互不阻塞的事件流订阅。AgentScope 三层 × 平台四层读完上面的调用顺序你大概能感觉到平台代码和框架代码是两种身份在交织。这其实是两套分层体系的叠加。框架侧三层AgentScope Java 2.0.1对照mvn dependency:tree的真实输出层artifact职责coreagentscope-core零框架依赖的内核ReActAgent、Msg/Model/Toolkit、AgentStateStore SPI、事件模型harnessagentscope-harness编排壳HarnessAgent workspace/filesystem/sandbox/subagent/skill/plan-mode/MCPextensionsagentscope-extensions-*并行生态模块族每家模型厂商一个包OpenAI/DashScope/Ollama/Anthropic/Gemini平台侧四层是经典 Maven 分层loser-common工具与公共配置→loser-agent-repository持久层37 个Ac实体对应 37 张ac_*表→loser-agent-app应用与运行时28 个 Controller 装配 各子系统→loser-agent-webVue 3 前端本课不设专集仅第 19 集联动一次。平台代码触达框架的方式可归为四类装配入口HarnessAgent.builder()第 05 集实现 SPIAgentStateStore、AgentSkillRepository、Model框架定义接口、平台给实现消费数据类型Msg/ContentBlock/ToolUseBlock全仓 85 个文件 importio.agentscope.*接模型厂商extensions 的 ChatModel 实现经SdkChatModelBuilder接入第 06 集。触达面不小但每一处都有明确身份——这正是第 22 集盘点扩展点全景时的伏笔。亲眼看见三层不用搭环境用 Maven 验证上面的分层故事本机实测输出节选mvn-plloser-modules/loser-agent-app dependency:tree-Dincludesio.agentscope[INFO] com.loser:loser-agent-app:jar:0.0.1-loser-agent-SNAPSHOT [INFO] - io.agentscope:agentscope-core:jar:2.0.1:compile [INFO] - io.agentscope:agentscope-harness:jar:2.0.1:compile [INFO] - io.agentscope:agentscope-extensions-model-dashscope:jar:2.0.1:compile [INFO] - io.agentscope:agentscope-extensions-model-openai:jar:2.0.1:compile [INFO] - io.agentscope:agentscope-extensions-model-gemini:jar:2.0.1:compile [INFO] - io.agentscope:agentscope-extensions-model-anthropic:jar:2.0.1:compile [INFO] - io.agentscope:agentscope-extensions-model-ollama:jar:2.0.1:compile [INFO] - io.agentscope:agentscope-extensions-skill-mysql-repository:jar:2.0.1:compile [INFO] - io.agentscope:agentscope:jar:2.0.1:compile [INFO] \- io.agentscope:agentscope-spring-boot-starter:jar:2.0.1:compile两个观察① core 与 harness 是两个独立 artifactextensions 是一个模块族一家模型厂商一个包——三层各就各位② 倒数第二个io.agentscope:agentscope是 shaded 聚合包内含 1300 类与前面的分模块 artifact 在 classpath 上同 FQCN 双份——这是依赖演化的历史痕迹也提醒你「All-in-One 与分模块混用」是框架使用的一个反模式后面讲模型接入时会再碰它。顺手在 IDE 里打开AguiChatController.chat()用 Call Hierarchy 从chat()一路点到DbAgentStateStore的实现方法——你会穿过框架边界再绕回平台代码正好亲脚走一遍上面那条调用顺序的往返。小结记住三件事主线一次聊天请求 鉴权入口 → 灰度路由 → 会话落库 → 装配 → 请求补全 → ReAct 执行 → 事件流适配 → 持久化/记忆/熔断。全课 22 集都是这条线的站点或支线。两层地图框架三层core 内核 / harness 编排壳 / extensions 生态× 平台四层web / app / repository / common平台以「装配入口、实现 SPI、消费数据类型、接模型厂商」四种方式触达框架。分界原则运行时语义交给框架请求生命周期其余一切平台自建。下一篇我们把环境搭起来docker 拉起 MySQLFlyway 40 个迁移建出全部ac_*表——以及LOSER_FLYWAY_ENABLED默认关闭、auth mode 默认 localadmin/admin123、master-key 空了启动崩这三个「第一次跑必踩」的坑。建第一个 Agent发第一条消息用肉眼看到 SSE 事件流——到那时这张地图就变成你亲手发出去的请求。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻