资讯动态

Java企业级Agent Harness平台设计与落地实践

发布时间:2026/10/13 1:24:40 来源:尧图企业网站定制
讲真这两年聊 Agent 的人特别多但真正能在企业里落地的很少。我在团队里做了个纯 Java 的 Agent Harness 平台代号 BizBuddy。所谓 Harness不是说它又一个聊天机器人框也不是把某个 Python 编排框架搬到 Java 上来。它更像一个 Agent 的“驾驶舱”和“运行底座”负责把大模型、企业存量系统、人的审批、审计日志全部串起来让智能体能稳定、可控地替代一部分固定流程。今天这篇文章就把 BizBuddy 的设计取舍拆开来讲重点不是炫技而是那些你在选型文档里看不到的坑和手感。先说结论如果你的团队也是以 Java 为主想在企业里做 Agent 落地又不想被某个框架的抽象绑死完全可以自己搭一个精简 Harness。别怕“重复造轮子”关键是知道自己要造哪几个轮子。下面会从项目背景、整体架构、核心实现、落地取舍、踩坑记录五个方面展开适合正在做企业级 AI 应用的 Java 工程师参考。1. 为什么要做 HarnessAgent 缺的不是模型而是骨架1.1 我们的起点一堆脚本和没人敢接手的“智能体”事情要从一次内部需求说起。那段时间业务方频繁提同一类诉求能不能用自然语言查订单状态、改工单、发起审批流程而不是每天在多个后台系统里来回点。我们最初的做法很直接把大模型接进来给每个场景写一个 Prompt 模板再在 Controller 里硬编码调用一些 Service。做了两三个 demo效果还行但问题很快就暴露了模型输出一不稳定整个流程就崩工具逻辑和 Prompt 混在一起加一个新场景要复制一大段代码最要命的是没人能说清楚 Agent 刚才调了哪个接口、花了多少钱、为什么这么干。这样的情况业务方根本不敢把权限放出来。后来我们意识到真正缺的不是“又聪明一点的模型”而是一个能承载 Agent 运行、编排工具、记录过程、控制权限的平台。当时也调研了几个现成的开源方案Spring AI、LangChain4j 也看过可用性不错但放到我们的企业环境里还是有点别扭我们的服务要接内部统一登录、要走公司现有的审计通道、要接一堆老系统的 HTTP 接口这些“脏活”还是要自己写。与其在一个框架的边界内到处打补丁不如自己抽出一个足够小而稳的运行时内核于是 BizBuddy 立项了。1.2 Harness 到底是个什么东西先把这个概念掰开。如果按功能拆一个完整的 Agent 应用可以分成四层模型层、智能体层、工具层、平台层。市面上大量开源框架主要解决模型层和智能体层它们帮你定义 Agent、Chain、Tool 这些抽象让你快速搭一个 Agent 应用但不太关心运行时的稳定性、可观测性、权限和多人协作。Harness 强调的恰恰是后两块它是一个“能一直跑”的底座。打个比方框架像是给你一套乐高积木你能拼出各种 AgentHarness 则更像实验室里的操作台它规定了你在哪个区域做实验、废液倒哪里、实验结果记录在哪本册子上。BizBuddy 的定位就是操作台而不是某个具体的积木套装。我们内部也有业务开发者基于它做“查单助手”、“工单处理专员”但这些都只是平台上跑的一个 Agent 配置加一组工具。1.3 适合谁、不适合谁如果你所在团队是 Python 技术栈那很多内容不需要参考 Java 实现如果你们只是想做一个几十个并发用户的内部小工具也完全没必要搞一套 Harness直接用现成框架就行。BizBuddy 这套思路适合的场景是并发量上百、有权限和审计要求、Agent 要调用很多内部系统、需要灰度发布和故障回滚的企业环境。说白了就是 Agent 要变成“企业正式员工”而不是“临时实习生”的时候Harness 的价值才体现出来。2. 整体架构BizBuddy 的五个核心模块设计 BizBuddy 时我没有把它做成一个大而全的中间件。核心思路是“最小主体 可扩展点”。整个系统可以划分为模型网关、工具注册中心、任务调度引擎、记忆管理、安全审计与回放。下面分别讲每个模块的职责和关键抽象。2.1 核心概念Task、Session、Context在代码里最上层的是 AgentTask。一个 Task 代表一次用户请求比如“查一下 A 客户最近的订单状态”。连接多个 Task 的是一次 Session也就是一段持续对话Session 里会维护历史上下文。每个 Task 执行时会创建 AgentContext它有点像 Servlet 里的 RequestContext存放当前会话的用户信息、工具列表、已经执行过的步骤、可用的内存存储等。这三个抽象分开是有原因的。Task 和 Session 分离是为了应对一个现实问题用户可能在一天内发起多个任务但每个任务又长又独立。比如一个 Agent 在处理“批量生成对账报告”时用户又发来一个“查询流程进度”的消息这两者不能互相打架。Context 则保证了每个任务里的状态都是隔离的不会出现多个任务共用变量导致数据串掉的情况。这一点在下面的 Java 实现中尤为重要因为平台是并发执行任务的。public record AgentTask( String taskId, String sessionId, String userId, String input, Instant createdAt ) {} public final class AgentContext { private final String taskId; private final String sessionId; private final String userId; private final DequeAgentStep steps; private final ToolRegistry toolRegistry; private final MemoryBlock memory; }2.2 模块职责一览模块核心职责关键接口/抽象ModelGateway统一封装大模型接入支持流式与同步ModelClient, TokenUsageListenerToolRegistry注册、发现、调用企业工具ToolDescriptor, ToolAdapter, ToolInvokerPlanExecutor驱动 Agent 循环规划-执行-观察AgentStateMachine, StepHandlerMemoryManager多轮上下文保存、摘要、压缩MemoryBlock, CompactorSecurityAudit权限校验、脱敏、全链路审计日志PolicyEnforcer, AuditEventPublisher这些模块之间的关系是用户请求进入 PlanExecutorPlanExecutor 在循环中调用 ModelGateway 获取模型决策模型决定要调用哪个工具然后 PlanExecutor 从 ToolRegistry 拿到对应的 ToolAdapter 去执行真实业务调用执行完毕后写回 MemoryManager再继续下一次模型决策。整个循环中间产生的关键事件都会被 SecurityAudit 记录下来。2.3 为什么坚持纯 Java这是个被反复问的问题。从技术上大模型相关的生态确实 Python 更丰富但企业落地不只有模型调用这么一件事。我们的存量系统是 Java 的注册中心、配置中心、监控系统、链路追踪都是 Java 体系的。如果引入一个 Python 侧 Agent 服务首先要解决跨语言调用、独立部署、两套监控的问题这已经足够把一个项目拖垮。纯 Java 的另一层考虑是运行时可控。Java 的虚拟线程、并发工具、强类型系统非常适合做这种状态机加工具调用的活。我们用 Java 17 Spring Boot 3 起步核心模块不依赖 Spring只在接入层用 Boot 的自动配置。这样既保留了纯 Java 的可测试性也能让团队里的老 Java 工程师快速上手。虚拟线程对 Agent 这种大量 IO 等待型任务非常友好后面会细说。3. 核心实现从一个任务进入到最终输出3.1 Agent 主循环与状态机BizBuddy 的执行核心是一个状态机而不是传统的“调用链”。状态枚举大致是PENDING、PLANNING、TOOL_CALLING、TOOL_WAITING、OBSERVING、COMPLETED、FAILED、TERMINATED。任务的流转逻辑很简单先解析用户意图然后用模型生成计划或直接指定要调用的工具接着进入工具调用阶段拿到结果后再回到 PLANNING直到模型认为不需要调用工具任务就进入 COMPLETED。这里有几个细节直接决定了体验。一是整体迭代次数必须限制我们默认 20 步防止模型抽风无限循环。二是每一个工具调用都有独立的超时时间工具整体超时默认 30 秒特殊工具可配置到 5 分钟。三是状态迁移必须判空和幂等同一个 taskId 的并发回调不能导致状态错乱。这几个约束看着简单但如果没有状态机做约束很容易在调试时发现 Agent 已经“精神分裂”。public enum AgentState { PENDING, PLANNING, TOOL_CALLING, OBSERVING, COMPLETED, FAILED, TERMINATED }主循环类似这样拿到用户输入 → 判断是否需要调用工具 → 如果需要从决策结果中解析出 toolName 和 parameters → 找到 ToolAdapter 调用 → 把观察结果拼进 messages → 继续循环。在 Java 里我习惯用一个受控的 while 循环加状态记录器而不是用递归因为递归在超长上下文或高并发下很容易出问题而且排查栈也费劲。3.2 ToolRegistry把企业系统变成 Agent 的工具这是企业级 Harness 里最重要的一部分。Agent 能不能干实事完全取决于工具注册和调用链路是否靠谱。我们用注解加自定义描述的方式定义工具。使用者只需在一个类中写一个方法打上AgentTool(name queryOrderById, description 根据订单ID查询订单基本信息)参数对象用 Java Bean 描述框架会自动生成 JSON Schema 供模型读取。真实世界里的工具往往不是 REST API 那么干净。有的是老系统暴露的 SOAP 接口有的是需要先调用 token 接口拿临时凭证的有的是要传文件流处理的。为了隔离这些差异ToolAdapter 做一个适配层把 ToolInvocationRequest 转成外部系统能理解的调用再把返回结果统一包装成ToolResult。内部会做异常捕获、超时重试、结果截断。结果截断特别重要否则一个大查询返回几万行数据一次模型调用直接让 token 费用爆表。public interface ToolAdapter { ToolDescriptor descriptor(); boolean supports(ToolInvocationRequest request); ToolResult invoke(ToolInvocationRequest request); }3.3 ModelGateway不能让模型供应商绑死平台我们并不绑定某一个具体模型。ModelGateway 抽象出 ModelClient 接口支持同步补全、流式补全、Function Calling 三种模式。底层默认实现的是 OpenAI 兼容协议对接国内几个云厂商模型时只需要改 baseUrl 和鉴权参数。这样做的好处是一个 Agent 在灰度阶段可以用便宜的小模型验证链路正式上线切到更强的模型只改配置即可。在处理模型输出时必须做防御式解析。模型返回的 function call 参数不一定合法 JSON经常是多一个逗号、少一个引号。我们先用 Jackson 解析解析失败就进入“自纠错”流程把原始字符串交给一个轻量模型修正同时计入一次重试。这里不能粗暴重试 3 次因为每次都烧 token通常一次修复足够。3.4 MemoryManager记忆不只是聊天记录在很多框架里Memory 就等于聊天记录的数组但企业 Agent 的“记忆”要更复杂。至少有三类会话历史、业务状态、长期事实。会话历史好理解业务状态指比如“当前正在处理的订单 ID”、“已经发送过的审批通知”长期事实则比如用户的常用供应商列表、部门审批链。我把这些统一放进 MemoryBlock每个 Agent 通过 Key-Value 写入。序列化统一用 JSON因为 Java 对象直接存内存容易出现版本升级后反序列化失败的问题。上下文压缩也是 MemoryManager 要管的。一个任务执行 20 步每步都带上之前所有工具结果很快就超过模型上下文窗口。我们在每次模型调用前检查 token 估算超过阈值就把较早的消息做摘要。摘要消息本身也是一次模型调用因此要设置成本上限。宁可让 Agent“忘掉”一些旧细节也不能让上下文超限导致任务中断。3.5 可观测性设计每一步都可以回放没有日志和追踪对一个 B 端 Agent 来说等于没有质量。BizBuddy 里每个 Task 执行都会生成一个 Trace包含多个 Span每个 Span 对应一次模型调用或一次工具调用。关键日志要记录模型请求的输入输出脱敏后、工具的参数和返回摘要、耗时、token 消耗、错误堆栈。这里特别强调脱敏。有些查询业务会传入用户名、手机号、合同金额这些字段如果在日志里明文出现就是合规事故。我们实现了一个字段级脱敏器在日志写出去之前根据敏感字段配置进行掩码处理比如手机号只显示后四位。除此之外审计事件单独落入独立的审计表不与应用业务日志混在一起便于安全团队抽查。4. 企业级落地中的关键取舍4.1 同步执行还是异步事件流Agent 任务很可能耗时几十秒甚至几分钟。如果做成同步 HTTP 接口网关超时和用户体验都会出问题。我们最终选择了异步事件流的方式用户提交任务后立即返回 taskId前端通过 Server-Sent Events 或 WebSocket 订阅任务状态变化任务每完成一个步骤就推一个事件给前端。看起来增加了一些复杂度但这个取舍换来了两个实际收益一是用户可以感知到 Agent“正在干什么”而不是干等二是平台在任务执行过程中能随时终止或人工介入。对企业场景来说允许用户随时停止失控任务非常重要。4.2 状态存储内存态和持久态之间怎么权衡一开始我们天真地想所有状态放内存既快又简单。结果一次发布重启几百个进行中的任务全部丢失群里被业务方连环提问。后来改成“先持久化再执行”的策略任何状态迁移都会写入 Redis任务的关键节点记录到关系型数据库。Redis 主要存当前执行状态和上下文快照关系库存任务元数据、执行历史、审计事件。这个改动带来的额外工作不多但收益很明显。任务执行到一半系统重启下次启动后可以强制标记为“中断”而不是让用户认为任务还在跑。如果 Agent 本身具备恢复能力还可以从最近一个安全节点重试。4.3 插件化边界哪些扩展点要开放做平台的人都容易犯一个毛病什么都想做成 SPI结果一大堆抽象反而把自己套住。BizBuddy 只开放了四个扩展点ToolAdapter、ModelClient、MemoryStorage、EventListener。规则引擎、流程编排、拖拽画板、多租户商城这类功能统统不在核心范围内。理由很简单扩展点越多测试矩阵越大团队学习成本越高。一个企业级工具的价值不在于它能做所有事而在于它把核心事做得足够稳、足够透明。4.4 模型调用与并发治理大模型接口和其他后端服务有一个显著区别它可能很慢而且对并发十分敏感。如果 Agent 在一次执行中需要连续做 10 次模型调用那么 50 个并发用户就会给模型网关带来 500 个并发请求很容易触发限流。所以我们用信号量限制每个 Agent 实例的最大并发模型调用数并给每次模型请求设置超时。服务端返回 429 或超时后采用指数退避重试而不是固定间隔重试。同时在模型网关前加了一层熔断器。某个模型服务连续 5 次失败就熔断 30 秒这期间任务直接失败并抛出清晰提示而不是让所有任务都排队等待慢请求超时。这样单个模型故障不会拖垮整个平台。5. 实战排查我踩过的坑和一些技巧5.1 模型“能对话但不会调工具”参数 Schema 的坑我自己遇到过最诡异的问题模型明明能正常聊天但一到 function calling 就频繁漏参、传错类型。后来排查发现问题不是模型太傻而是工具注册信息太糟糕。我一开始把参数定义成嵌套对象字段名也和内部编码一致模型根本猜不到每个字段是干什么的。解决办法是尽量让工具参数扁平化、语义化给每个参数写清楚“示例值”并把参数个数控制在 5 个以内。比如不要传customerId: 8492034而是传customerNo: V000188。这种微调对准确率提升很大。如果模型还不稳定就在调用前加一层参数校验不合法就不送进工具执行。5.2 工具调用死循环和重试风暴Agent 在工具调用失败后会尝试自己修正这是好事但也可能变成灾难。最典型的是数据库查询超时工具返回“timeout”模型不但不道歉反而又构造了一个类似请求再次调用连续重试 10 次把后端数据源打挂。我们把单次 Agent 循环的最大工具调用次数设定为 8 次同时工具返回体里附带了可机器读取的错误类型比如RETRYABLE和FATAL。模型看到 FATAL 时应该停止补救并直接向用户说明失败原因而不是继续尝试。5.3 虚拟线程下的连接池保险丝Java 21 虚拟线程很香但别把所有任务都往上放。在一次压测中我们发现工具执行大量阻塞型 JDBC 调用时虚拟线程虽然本身几乎没有成本但底层数据库连接池却被吃满了。原因很直接虚拟线程数量可以很高JDBC 连接数却有限。于是我们在工具执行层做了一个全局并发闸门一次允许的工具调用并发数不超过配置值默认是 20。这样既保留了异步编排的流程又不会把下游资源打爆。5.4 长任务的上下文泄漏有段时间老有任务“突然开始胡说八道”日志里显示模型每一轮都把之前所有工具结果原样传给模型token 数一路上涨。追问下来才发现MemoryManager 的压缩只对“会话历史”生效却漏了“业务状态”里的长对象。比如某步骤把一个 200 KB 的报表对象写进 MemoryBlock之后每一步都带上它。解决思路是对 MemoryBlock 的 Value 也做规模上限超过阈值时自动转换为摘要引用需要完整数据时再通过专门的工具去取。5.5 测试与模拟先减少真实模型调用我们内部维护了一个MockModelClient可以按配置返回固定的工具调用序列。大多数链路测试根本不连真实模型而是模拟模型“决定调用 X 工具”以及“最终不再调用工具”的两种典型分支。只有到了联调阶段才切到真实模型。这么做每年能省不少 token 费用而且跑回归测试也快不至于每次都在 API 调用上排队。6. 个人经验起步别做太满按我目前的使用体验BizBuddy 最有价值的模块是 ToolRegistry 和 SecurityAudit其次是状态机和 MemoryManager。如果你也想在企业里做 Agent Harness我建议先别急着把模型网关、数据中心、工作流编排都集成齐。找一个真实场景比如“查询订单进度”或“生成周报”先把任务状态机和工具注册打通让 Agent 能在真实系统里跑一遍闭环再逐步加可观测性和记忆管理。还有一个很实用的小技巧在设计阶段就把“步骤记录”当成第一等公民。很多 Agent 项目跑着跑着就变成了黑盒等出了问题只能靠截图问用户“刚才发生了什么”。如果早早在工具调用层把每步参数、响应、耗时落盘后续不管是排查、优化还是给管理层展示 RoI都会轻松很多。BizBuddy 现在内部有个“任务回放”页面能看到某个任务每一步的模型思考摘要、工具调用的入参和出参对推进企业落地的说服力远超一堆指标图表。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑