复盘:企业级 Agent 平台,落地踩过的坑

复盘:企业级 Agent 平台,落地踩过的坑

一开始低估的事:边界

最早动手时,我们对"Bounded Context"的理解停在概念层。会话(Session)、工具(Tool)、审计(Audit)都放在一个 service 里,靠函数调用区分。第一个月运行没什么问题,第二个月工具调用加了审批流程,要改 Session 状态机,同时改审计写入时机,一次改动影响了三个方向,回归测试全部要重跑。

拆分到独立 BC 以后这个问题消失了。Tool BC 加审批逻辑,改的是自己内部的状态机,Session BC 只感知"工具调用完成"这一个事件,不关心审批细节。代价是多了 gRPC 调用,增加了几毫秒延迟,但这个代价完全值得。

结论:BC 边界不是为了架构图好看,是为了让每次改动的影响范围可控。边界划得晚比划错更痛苦。


走了弯路:Session 状态机

Session 的状态设计,我们反复改了几版。

最纠结的一个问题是:数据库里需要细粒度的状态来区分"LLM 正在思考"、“正在调工具”、"等待人工审批"这些不同阶段,但如果把这些全塞进同一个状态枚举,状态机的迁移规则会变得非常复杂——几十种组合,改一处怕漏另一处。

最终我们用了两级设计:State 和 Phase。

State(状态)是会话的生命周期,一共 6 个:

  • idle

    :会话已创建,还没开始跑

  • running

    :正在执行 ReAct 循环

  • paused

    :暂停,等 HITL 人工审批

  • completed

    :正常结束(终态)

  • failed

    :异常退出,lastError记录原因(终态)

  • cancelled

    :用户或系统主动取消(终态)

Phase(阶段)running状态下的子阶段,用来做 DB 持久化和 SSE 流事件对齐:

  • thinking

    :LLM 正在生成响应

  • tool_use

    :正在执行工具调用

  • finalizing

    :收尾整理最终回复

这样做的好处是:State 层面只需要管 6 个状态的合法迁移,规则很简单——一张validTransitions转换表就够:

  • idle

    running(调Start()激活)、cancelled

  • running

    paused(HITL 中断)、completedfailedcancelled

  • paused

    running(审批通过恢复)、cancelled

  • completed

    failedcancelled是终态,不能再迁移

任何不在表里的迁移,代码直接返回ErrInvalidStateTransition错误,不会静默通过。Phase 的推进则由SetPhase()方法驱动,只在running状态下才生效,其他状态调用是空操作。

你可以把 State 想象成火车运行图上的大站(始发、运行中、临时停车、到站、故障停运、取消),Phase 是运行中的小站(加速、减速、过桥)。大站之间有严格的调度规则,小站只是记录"此刻在干什么"。

结论:当状态粒度需求在不同层面不一样时,用两级(State + Phase)比把所有东西塞进一个枚举更清晰。状态机的价值不只是代码,是让"哪些迁移合法"变成一张可审查的表。


踩过的坑:异步不代表可以忽略失败

Memory 的记忆提炼是异步 fire-and-forget。上线初期日志里偶尔出现提炼失败,我们当时觉得"最多这次对话的内容没被记住,不影响主流程",就没处理。

运行一段时间后发现,失败率偏高,原因是提炼用的 LLM 调用不够稳定。用户感知不到失败(主流程没报错),但记忆系统慢慢变薄——常用的偏好没被记住,用户下次对话又要重新告知 Agent。虽然不是数据丢失,但体验在悄悄变差。

当前代码的做法是:失败时保留事件不清空(SaveTx事务保证事件不丢),上层可以决定是否重试。同时持久层做了分层降级——优先走事务(SaveTx:聚合落库 + 事件写入同一事务),事务失败则退到非事务路径(Save + Publish)。这比最初"失败就丢了"强很多,但完全自动化的重试(如指数退避)还在 roadmap 中。

结论:fire-and-forget 不是"失败了算了",是"失败了不阻塞主流程,但要有自己的兜底"。事件不丢是最低要求,自动重试是进阶目标。


没想到的问题:HITL 超时

HITL 上线时,我们设了审批超时 5 分钟(代码里是HITLTimeout = 5 * time.Minute),超时后自动拒绝,由一个每 5 分钟跑一次的 CronJob(hitl-timeout)检查并处理过期中断。

这个时间对"人在电脑前等着"的场景够用,但对"需要找领导确认"的场景太短。而且超时后走的是RejectInterrupt路径(decided_by="system:hitl-timeout"),原始参数虽然保留了(存在session_interrupts表的args里),但用户需要从头重新发起请求。

当前超时值是硬编码常量,还没有做到按租户/工具风险等级可配置——这是 roadmap 中的改进项。

这个问题不是纯技术问题,是没有从用户的工作流角度思考超时设定。

结论:涉及人工操作的流程,超时和重试的设计要从人的行为习惯出发,不能只从系统稳定性考虑。


值得的决定:RLS 在数据库层做隔离

多租户隔离最开始有两个方案争论:应用层过滤(每个查询手动加WHERE tenant_id = ?)vs 数据库 RLS。

应用层方案实现简单,但有个明显的风险:新人写查询忘了加WHERE tenant_id,数据就串了。RLS 在数据库层强制过滤,代码忘了加条件,数据库帮你加。代价是每个事务开头要SET LOCAL app.tenant_id,多了一次数据库交互。

上线以后做过两次代码 review,发现有 3 个查询路径确实遗漏了应用层过滤——如果靠应用层方案,这 3 个地方就是数据串的漏洞。RLS 兜住了。

结论:隔离应该是系统约束,不是依赖开发者自律。多一次数据库交互的代价远低于一次数据泄露事故。


值得的决定:指标集中定义

早期各 BC 各自注册指标,名字不统一。有的叫agent_runs,有的叫agent.runs.total,Grafana 面板对不上,每次加新面板要先 grep 代码找实际的指标名。

把所有指标集中到metrics.Metrics结构体,命名约定强制deepflux.<domain>.<measure>,一个文件能看到全平台所有指标。Grafana 面板配置按这个命名写,再也不用翻代码。

结论:可观测性基础设施早期投入是值得的,散乱的指标命名是技术债,修复成本很高。


总结

做企业级 Agent 平台,技术本身不是最难的部分。难的是:在系统还很小的时候,做出那些在系统变大以后才能体现价值的设计决定。BC 边界、状态机、RLS 隔离、集中指标——每一个在实现时都比"快速能跑"麻烦,但后来都成了省心的地方。

选 Eino 做 Agent 编排框架,节省了大量 ReAct 循环的基础设施工作,让我们可以把更多时间花在业务逻辑和运营能力上。这个选择目前看来是对的。

学AI大模型的正确顺序,千万不要搞错了

🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!

有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!

就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋

📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇

学习路线:

✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经

以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!

我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费