直接开始正文。
用纯 Java 做一个企业级 Agent Harness 平台:BizBuddy 的设计与取舍
总有人问我同一个问题:“都 2025 年了,做 Agent 平台为什么不用 Python?LangChain 现成的,还折腾啥?”说实话,这个问题我在立项前也纠结了很久。最后我们决定用纯 Java 自研一个企业级 Agent Harness 平台,代号 BizBuddy。这篇文章把整个设计和取舍过程摊开讲,包括为什么顶着生态劣势选 Java、核心组件怎么抽象、Agent 生命周期怎么编排、模型网关怎么做,以及上线之后踩过的那些坑。如果你所在的团队主力栈是 Java,又想在 AI 应用层做出真正能落到业务里的 Agent 系统,这篇文章应该能帮你省掉不少弯路。
我先把话说在前面:BizBuddy 不是一个叠了一大堆依赖的 Demo 项目,也不是简单在 Java 里调几个 HTTP 接口包一层就完事的玩具。它的定位是一个具备生产级能力的运行时容器——Agent 在里面跑,你只需要关注业务逻辑本身,状态管理、工具调用、上下文裁剪、模型路由、可观测性这些脏活累活平台全兜住。
1. 为什么选纯 Java 做 Agent Harness:这次技术选型我没选 Python
1.1 Python 生态确实香,但企业级落地是有代价的
先承认一点:Python 在 AI 生态里的优势是客观存在的。不管是 LangChain、LlamaIndex 还是各种 Agent 框架,最前沿的玩法永远是 Python 社区先趟一遍。如果目标是快速验证一个 Demo,Python 三小时就能跑通全流程。但"验证 Demo"和"企业级落地"是两码事。
我们当时的真实场景是:团队二十多号人全是 Java 背景,现有业务系统跑在 Spring Boot 微服务架构上,监控体系基于 Prometheus + Grafana,链路追踪基于 OpenTelemetry,部署环境是 K8s。如果强行引入一套 Python 技术栈,等于从零开始养一支新的运维和开发团队,而且 Python 后端服务想接入现有的告警、鉴权、审计体系,代价远比想象中高。
还有一个更深层的原因:Agent 应用本质上是一个 IO 密集型系统——大量时间花在网络请求、模型调用、数据库读写上,真正消耗 CPU 计算的部分极少。Python 在数值计算和深度学习训练上的优势,放到 Agent 编排这个场景里根本没有发挥空间。而 Java 在 IO 密集场景下的表现,尤其是引入虚拟线程之后,并发模型反而更干净。
1.2 纯 Java 的真正优势:类型安全、虚拟线程和团队复用
Java 这边有一个被 AI 社区低估的杀手级特性:虚拟线程。从 Java 21 开始,虚拟线程让 Java 可以轻松拉起几十万甚至上百万个并发任务。Agent 编排恰好就是这类场景——每个 Agent 实例就是一个带有状态的处理单元,运行时需要频繁阻塞等待模型响应。过去用线程池,几百个并发 Agent 就会把线程资源吃干抹净;换了虚拟线程之后,线程不再稀缺,Agent 的生命周期管理一下子变简单了。
再说类型安全。Agent 平台的核心抽象之一是工具(Tool)调用。每一个工具都有输入输出结构,工具的声明、参数校验、结果解析全链路都是强类型的话,很多低级错误在编译期就能拦住。Python 的动态类型在写小脚本时很爽,到了几十个工具在一个平台里互相协作的时候,没有一个强约束的类型体系,排查问题真的会疯。
最后是团队复用。原班人马不需要转语言,Spring 的依赖注入、AOP、事务管理这些已经被验证过无数次的能力,可以直接用来搭 Harness 的基础设施层。
2. BizBuddy 架构长什么样:核心抽象与分层设计
2.1 Harness 与 Agent 的边界:平台管运行时,Agent 只管业务
很多人在设计 Agent 平台时容易陷入一个误区:把 Harness 和 Agent 混为一谈。BizBuddy 从第一天开始就做了非常明确的边界划分。
Agent 是业务逻辑的载体,它只关心三件事:接收什么输入、调用什么工具、产出什么结果。至于 Agent 的生命周期谁管理、工具调用的鉴权谁来拦、上下文窗口超限了怎么裁剪、模型调用失败了重试几次,这些全部由 Harness 统一处理。
这个设计带来的直接好处是:Agent 的定义可以非常薄。一个 Agent 本质上就是声明式的配置加少量业务代码,平台层面通过统一的接口来驱动它。
public interface Agent { AgentId agentId(); AgentSpec spec(); // 声明式配置:模型、工具列表、上下文预算等 AgentResult run(AgentContext context); }这套抽象保证了 Agent 是"可插拔"的——新接一个业务场景,通常只需要注册一个新的 Agent 实现,平台代码一行都不用改。
2.2 工具(Tool)即函数:JSON Schema 驱动的统一抽象
工具调用是 Agent 平台最核心的能力之一。BizBuddy 的设计思路是"工具即函数":每一个工具都暴露成一个结构化的函数声明,模型根据声明决定是否调用以及传入什么参数。
关键实现细节是:工具声明不只是给模型看,还要能被 Java 代码直接校验和执行。BizBuddy 用一个自定义注解 + 反射的方式做工具注册:
@AgentTool(name = "query_order_status", description = "查询订单状态") public OrderStatus queryOrderStatus(@AgentToolParam(description = "订单编号") String orderId) { // 业务逻辑 return orderService.queryStatus(orderId); }启动时,Harness 扫描所有被 @AgentTool 标注的方法,通过反射解析出方法签名、参数名称、注解信息,自动生成 JSON Schema。这段逻辑要处理的问题不少:泛型擦除会导致类型丢失,所以参数类型做了白名单限制——只允许基础类型、String、常见 POJO 和它们的 List/Map 形式;嵌套 POJO 的字段描述必须显式标注,不能靠反射猜。实现的时候把参数对象转成可查询的结构放在注册中心里,模型返回的 JSON 参数先按 Schema 校验再反射调用,schema 校验和参数校验一起拦住,基本能杜绝运行时类型错乱。
2.3 事件驱动:异步编排的关键决策
一开始 BizBuddy 用的是同步编排:Agent 执行工具调用,等到工具返回结果,再继续下一步。简单场景没问题,但一旦涉及多 Agent 协作就卡住了——Agent A 要等 Agent B 的结果,如果 B 还在等 C,同步链路会变得极其脆弱,而且排查问题时根本分不清到底是哪个环节卡住了。
后来我们把编排层改成了事件驱动架构。Agent 之间不直接互相调用,而是通过领域事件通信。每个 Agent 的执行过程是一个事件流:AgentStarted、ToolInvoked、ToolCompleted、AgentCompleted、AgentFailed。外部系统或者另一个 Agent 只需要订阅对应的事件即可完成协作。
事件驱动带来的第二个好处是重试和补偿变得可控。Agent 的执行可以被拆解成一个个可重放的子任务,任意环节失败时,Harness 可以根据事件记录精确地重放或补偿,而不是整条链路推倒重来。
3. Agent 生命周期与工具调用的实现细节
3.1 Agent 状态机:从创建到销毁的全过程管理
Agent 实例不能只是"任务扔进去跑完就完"。企