过去一年,我们团队一直在打磨一个内部代号叫Agent Hub的东西。它不是某个大模型应用,也不是简单套个壳的 RAG 问答系统,而是想在企业内部真正做一层“AI 时代的操作系统”:把散落在各个业务线里的 Agent、工具、模型、数据和权限统一管起来,让 AI 能力像水电一样可以被任意调用。这个标题听起来很宏大,但对我们来说,它其实是被一堆实际问题逼出来的。
如果你所在的公司已经开始同时跑几个 Agent,一定会遇到这些情况:销售团队的“客户洞察 Agent”想调用财务那边的“回款风险 Agent”,两边接口对不上;模型 API Key 散落在各处,月底账单根本算不清是哪个部门花的;某个 Agent 在一次异常输入下把错误指令发给了下游系统,差点酿成事故。这些问题叠加在一起,你就会意识到:企业需要的不是一个更聪明的模型,而是一套能容纳、调度和治理这些 Agent 的基础设施。这篇文章我就把 Agent Hub 的架构思路、核心模块和实操落地过程完整拆开讲,希望能给正在做 Agent 平台化的团队一些参考。
1. 为什么企业会走到“操作系统”这一步
1.1 单点 Agent 跑得越顺,协同问题就越疼
先说一个最直白的感受:单个 Agent 的原型太好做了。
今天你只要接一个大模型 API,给它配几个工具函数,再丢一份内部知识库,就能在两周内做出一个“能回答业务问题”的助手。这导致企业内部通常会在短时间内冒出十几个甚至几十个 Agent:有人做周报汇总,有人做客户尽调,有人做工单分类,有人做合同审查。单独看每个都挺能打,但它们彼此之间是断的。
我们踩到的第一个坑就是“Agent 孤岛”。业务线 A 的 Agent 需要用到业务线 B 的 Agent 能力,但 B 那条线的开发同学根本不知道自己的服务会被别人这样调用。接口参数没有统一规范,有的用customer_id,有的用customer_code,联调一次要拉好几个群。后来我们决定做一个统一的服务注册体系:每个 Agent 上线时都要描述“我是谁、我能做什么、输入输出是什么”,其他 Agent 和上层编排系统先通过这个描述来发现能力,而不是靠人肉沟通。
更麻烦的是基础设施碎片化。团队里每个 Agent 都自己接模型厂商、自己存向量库、自己写日志。模型服务商一涨价,想统一换供应商,牵连着几十个服务要改代码。而模型生成的调用记录散落在不同项目里,审计时根本说不清楚某个高风险操作是哪个 Agent 发起的。这些问题的本质是:我们只有一堆“应用程序”,却没有按“操作系统”的方式去组织它们。
1.2 用“进程管理”的思路理解 Agent 系统
我特别喜欢拿操作系统做类比,因为 Agent Hub 要解决的很多问题,操作系统早就解决过一遍。
传统操作系统里,CPU 被抽象成可调度的计算资源,内存被抽象成进程的地址空间,磁盘被抽象成文件系统,外部设备被抽象成驱动接口。再看 AI 时代的企业系统:大模型推理能力就像 CPU,需要被统一分配和调度;上下文窗口就像内存,需要被合理切分和管理;企业里的知识库和记忆数据就像文件系统,需要一套读写和权限机制;各种业务系统和 API 就像设备驱动,需要以标准方式接入,而不是让每个 Agent 自己拿螺丝刀去焊电路板。
Agent 在这个类比里就是“进程”。进程不能直接操作硬件,所有资源访问都必须通过系统调用,由内核代理完成。Agent Hub 做的正是这件事:Agent 不能直接拿到企业的数据库密码,不能直接往生产系统写数据,不能直接调用外部服务,所有动作必须经过 Hub 的工具网关,由平台完成权限校验、参数校验、审计留痕之后才真正被执行。这套机制让 Agent 在企业里跑起来变得可控、可追溯、可回收。
2. Agent Hub 的架构拆解:底层得像操作系统,上层得像业务平台
2.1 六个跑不掉的核心模块
我在设计 Agent Hub 时,没有一开始就奔着宏大架构去,而是先梳理了“哪些能力是多个 Agent 共同需要、但自己又做不好的”。最后沉淀出六个核心模块,每个模块解决一类具体问题。
| 模块 | 核心职责 | 没它会怎样 |
|---|---|---|
| Agent 注册与发现中心 | 管理 Agent 的元数据、能力描述、健康状态 | 每个 Agent 都是黑盒,协作基本靠人肉对接口 |
| 运行时沙箱与生命周期管理 | 负责 Agent 实例的创建、执行、销毁,限制资源消耗 | Agent 失控时无法熔断,死循环能一直烧 token |
| 编排调度引擎 | 负责任务拆解、状态流转、多 Agent 协作 | 复杂业务流程没法落地,只能靠 Agent 自己“自由发挥” |
| 统一记忆层 | 管理短期任务上下文和长期向量记忆,支持回溯 | 每个 Agent 各存各的,换个场景就“失忆” |
| 工具网关 | 统一接入企业系统 API,完成鉴权、限流、审计 | Agent 到处直接调内部系统,安全风险不可控 |
| 可观测与计量中心 | 对每一次模型调用、工具调用、任务流转做完整 trace | 出了问题没法复盘,成本账单永远对不上 |
这六个模块里,业务感知最强的是编排引擎,平台属性最强的是运行时和工具网关。做的时候一定要想清楚:Agent Hub 不是把某个业务 Agent 的逻辑写死在里面,而是提供一个“容器”,业务 Agent 按规范接入就能被统一调度。很多团队把 Hub 做成一个无所不包的大泥球,最后扩展性极差,这是我在实践中最大的警惕点。
2.2 两个关键的架构决策
第一个决策:万物皆 Agent 还是万物皆工具?我们在内部定了一个很朴素的标准:有自主决策能力、需要“理解”用户意图并自主规划步骤的,是 Agent;而只做确定性操作、输入输出可严格定义的,是 Tool。这样划分之后,Agent 的能力上限是“可以调用其他 Agent”,但它自己也要注册成可被发现的 Agent。实际实现时,Agent 之间的调用也要走 Event Bus,通过任务消息传递,而不是让一个 Agent 的上下文里夹着一大段另一个 Agent 的对话历史,那样很快会爆炸。
第二个决策:业务编排逻辑放哪一层?我们早期犯过一个错误,就是希望大模型自己在一次对话里把所有业务流程推完。结果发现,模型的长链路推理在小任务上确实惊艳,但一旦涉及企业内部真实业务,稳定性远远不够。后面我们改成“确定性骨架 + 模型决策点”的混合模式:业务流程的必经步骤用工作流/状态机画死,模型只在适合它判断的位置发挥作用,比如判断工单属于哪一类、判断客服回复是否包含承诺、判断下一轮要不要人工介入。这套思路后来被证明有效,也是 Agent Hub 能说服业务部门投入使用的关键。
3. 落地路径:从零搭一个企业级 MVP
3.1 技术选型别追新,别给自己找麻烦
在我的经验里,Agent Hub 的 MVP 不需要一开始就上 Service Mesh、K8s Operator 那一套。我们选型时只坚持三个原则:生态成熟、团队熟悉、便于排障。
最终跑起来的一套组合是这样的:
- 后端主框架:Python 3.11 + FastAPI,Agent 生态中多数工具和 SDK 都是 Python 优先,开发效率最高;
- 任务队列与事件中枢:Redis + Celery,任务需要异步化,不能让 Web 请求傻傻等大模型输出完再返回。等规模上来后再平滑替换到 Kafka + 独立 Worker 集群也不迟;
- 元数据与状态存储:PostgreSQL,保存 Agent 注册表、Tool 定义、任务状态机和审计记录;
- 向量记忆库:PostgreSQL + pgvector,MVP 阶段不想多维护一套 Milvus 或 ES,用 pgvector 足够支撑几百万级别向量的检索;
- 模型网关:统一封装成 OpenAI 兼容协议,不直接依赖某一家模型厂商。
这个选型在最初几周就能完成内部 Demo,团队成员不用新学任何重框架。很多团队一上来就铺 Kubernetes、上 Knative,结果资源调度问题还没遇到,先被基础设施复杂度拖垮了。
3.2 最小运行时:Agent 任务的数据模型与调度逻辑
我们先把 Agent 的执行抽象成一个简单模型:外部系统或者上层编排引擎向 Hub 提交一个AgentTask,Hub 负责找到对应的 Agent,把它放进沙箱执行,然后把结果写到任务账本里。
任务数据模型我建议至少包含这些字段:
from pydantic import BaseModel, Field from uuid import uuid4 from enum import Enum class TaskStatus(str, Enum): PENDING = "pending" RUNNING = "running" SUCCEEDED = "succeeded" FAILED = "failed" NEED_APPROVAL = "needs_approval" class AgentTask(BaseModel): task_id: str = Field(default_factory=lambda: str(uuid4())) agent_id: str payload: dict parent_task_id: str | None = None trace_id: str status: TaskStatus = TaskStatus.PENDING retry_count: int = 0 max_retries: int = 3 created_by: str # 哪个用户或哪个上游应用提交的 created_at: int注意task_id和trace_id是有区别的。task_id用于幂等控制,同一个任务重复提交时,Hub 能识别出来;trace_id用于链路追踪,一次用户请求可能产生多个子任务,靠它把整条链路串起来。
调度分发的核心逻辑,第一步不是调用模型,而是查 Agent 注册表和任务账本:
async def dispatch_task(task: AgentTask) -> AgentTask: agent = agent_registry.get(task.agent_id) if agent is None: raise AgentNotFoundError(task.agent_id) # 幂等保护:同一个 task_id 不重复执行 if await task_ledger.exists(task.task_id): return await task_ledger.get_task(task.task_id) task.status = TaskStatus.RUNNING await task_ledger.save(task) try: sandbox = await runtime_sandbox.create(agent) result = await sandbox.invoke(task.payload) task.status = TaskStatus.SUCCEEDED task.result = result except RetryableAgentError: if task.retry_count < task.max_retries: task.retry_count += 1 task.status = TaskStatus.PENDING else: task.status = TaskStatus.FAILED except SensitiveActionError: task.status = TaskStatus.NEED_APPROVAL await task_ledger.save(task) return task可能有人会问:既然 Agent 都是 LLM,为什么不直接在代码里调 Agent 的 prompt,而是绕这么大一圈用任务账本?原因是“可恢复性”。真实企业环境里,Agent 执行可能因为模型服务超时、下游 API 抖动而中断。没有持久化的任务状态,中断后一切归零;有了任务账本,Worker 重启后可以捞回PENDING或者RUNNING状态继续处理。这是我们被线上事故教育过之后补上的关键设计,绝对不能省。
3.3 工具网关:让 Agent 只能“请求”,不能“乱来”
在企业系统里放一个自主性很强的 Agent,最让人担心的就是它拿到工具后产生超出权限的动作。我们设计工具网关时借鉴了操作系统的用户态/内核态思想:Agent 是用户态进程,所有对业务系统的访问都不能直连,必须向网关注册并声明作用域。
Tool 定义本身是一份 JSON Schema 描述,包含调用方式、入参格式、所需权限等:
{ "tool_id": "crm_update_order_status", "name": "更新订单状态", "description": "修改 CRM 中指定订单的状态,通常在客户确认收货后调用", "required_scopes": ["crm:order:write"], "require_human_approval": true, "parameters": { "type": "object", "properties": { "order_id": { "type": "string" }, "new_status": { "type": "string", "enum": ["pending", "paid", "shipped", "done"] } }, "required": ["order_id", "new_status"] } }在网关层执行时,做四件事:鉴权、参数校验、人工审批判断、审计留痕。简化后的核心流程是这样的:
async def call_tool(tool_name: str, params: dict, principal: str): tool = tool_registry.get(tool_name) if tool is None: raise ToolNotFoundError(tool_name) # 1. 权限校验:principal 指发起调用的 Agent 身份 if not permission_client.has_scope(principal, tool.required_scopes): raise PermissionDeniedError(tool_name) # 2. 参数严格按 JSON Schema 校验,防止畸形输入 validated_params = tool.schema.validate(params) # 3. 高危操作挂起等待人工审批 if tool.require_human_approval: approval_id = await approval_client.submit(tool_name, validated_params, principal) raise TaskNeedsApproval(approval_id) # 4. 调用真实系统 API,并记录完整审计日志 response = await tool.invoke(validated_params) audit_logger.record( tool_name=tool_name, principal=principal, params=validated_params, response_summary=truncate(str(response), 500), trace_id=current_trace_id() ) return response这套工具网关做好了,Agent 权限问题就解决了 70%。剩下的 30% 靠严格的 IAM 团队配合,例如给 Agent 使用的服务账号分配最小权限。这里有个经验教训:不要让 Agent 使用某个员工的个人账号去调用系统,一定要给 Agent 单独创建机器人账号,否则离职员工的权限回收会直接让 Agent“瘫痪”或者更糟——让 Agent 继承了一个已经不该存在的权限。
3.4 上线前必须守住的三条实现原则
根据我们几个月的踩坑,可以把必需原则浓缩成三条。
第一条,所有耗时调用必须异步化。Agent 跑一个任务,可能要执行多次模型调用和多轮工具调用,耗时 30 秒甚至几分钟很常见。如果 Web 层同步等结果,网关一抖动就是一堆 timeout。正确做法是:接请求时立刻返回task_id,前端或调用方通过 WebSocket 轮询任务状态。我们现在所有内部系统接 Agent Hub,都遵循“提交任务 -> 等待回调”模式,体验稳定很多。
第二条,所有外部调用必须设置超时和重试上限。模型有模型的上限,工具调用也有。实践经验是单次模型调用超时设 60 秒,单次工具调用设 15 秒,任务整体设置 5 分钟到 10 分钟的执行上限。没有上限的 Agent 任务就像没有看门狗的进程,一旦进入死循环,轻则白白烧钱,重则影响同集群其他任务。
第三条,核心业务链路要有“退出路径”。也就是说,即使 Agent 完全不可用,业务也要能用人工方式兜底。我们在每个关键工作流里都做了降级开关:Agent 识别失败时就转入人工工单池,而不是把流程卡死。不要追求 100% 自动化,能把“接得住、降得下”做到位,业务方才敢把核心系统交给 Agent Hub。
4. 常见问题与排查实录
4.1 Agent 一通“胡说八道”,先分清是模型问题还是流程问题
经常有人反馈“你们的 Agent 又在胡说了”,但真正排查下来,只有一部分是模型幻觉,更多问题出在系统设计上。
我遇到过一个典型案例:某个合规审查 Agent 需要判断合同的违约条款是否合理。结果它在没有调用任何合同解析工具的情况下,直接基于用户问题里的只言片语生成了“结论”,看起来有理有据,实际内容全错。问题在于 prompt 里只写了“你是合规专家,请判断”,但没强制它完成任务前先检索合同原文并给出引用。后来我们在 prompt 中加入硬性要求:“如果没有引用合同原文中的具体条款编号,禁止输出结论”,并在代码层面检测输出之前是否成功完成了retrieve_contract工具调用,不满足就不允许进入生成环节。效果立竿见影。
判断这类问题的思路很简单:把 Agent 执行链路里的每一步 trace 打开,看它到底调用过哪些工具、看过哪些上下文。如果没调用工具就给出了事实性回答,那不是模型笨,是你的编排流程漏掉了“先检索后回答”这个前提。如果检索也做了、上下文也给了但输出还是错,那才轮到优化模型或调整 prompt。
4.2 多 Agent 协作时,上下文爆炸和状态冲突是最常见的“翻车现场”
多 Agent 协作的经典场景是这样:一个主 Agent 在订酒店,它调用了比价 Agent,比价 Agent 又调用了城市天气 Agent,最后天气 Agent 的完整对话历史被原封不动塞回给主 Agent。一个任务跑下来,上下文里塞了十几万字,模型注意力被稀释,开始答非所问,账单也跟着飙升。
解决办法是“结构化摘要传递”。Agent 之间传递的不应该是原始对话记录,而是一份紧凑的中间结果对象,例如“酒店比价结果:[{酒店A,价格,评分}]”,而不是聊天记录。需要报告呈现时,再由专门的语言润色 Agent 把结构化结果转成自然语言。如果确实需要传递多轮信息,那就用摘要压缩:每轮结束时生成一段 200 字以内的摘要,下一轮只带着摘要继续走。
状态冲突则是另一个深坑。两个 Agent 并行处理同一个订单,一个把状态从pending改为paid,另一个在旧快照基础上把它改回pending,后写覆盖先写。这个问题在数据库层面很好解:给订单状态字段加版本号,更新时带上版本号做乐观锁,更新失败就重读最新状态。难的是让 Agent 不再产生冲突,所以 Hub 的任务账本里要有parent_task_id关联关系,并行任务执行前最好到冲突检测服务注册一下资源占用的 key,发现冲突就串行化。
4.3 故障排查:“模型说做了但业务说没做”怎么查
所有 Agent 系统上线后都会遇到一个争议场景:模型坚定地告诉你“我已经执行了退款操作”,但业务方查不到记录。这时候没有 trace 数据,就是无边无际的扯皮。
我们的审计日志里必须记录三层信息:
- 模型层:prompt 的版本、模型名、请求响应摘要、token 用量;
- 编排层:任务从哪个 Agent 来、经过哪些状态、是否触发了人工审批;
- 工具层:调用了哪个真实系统 API、请求参数是什么、系统返回的原始响应是什么,不能只记“成功/失败”,还要截断记录响应体,方便定位返回了空还是异常。
快速排查表给一个参考,这是我们内部运维手册的核心内容:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| Agent 声称已调用工具但系统没变化 | Tool 网关校验失败或调用超时,但 Agent 没拿到错误 | 查工具调用日志中的 return code,看超时时间和重试次数 |
| 同一订单被处理两次 | 客户端重试导致重复提交,任务没有幂等 | 检查 task_id 是否重复,工具侧是否有幂等 Token 机制 |
| 线上成本暴涨 | 某 Agent 进入循环调用,或模型在长对话中不断放大上下文 | 查模型 |