
搞了几年 Java 后端又赶上了这波大模型的热潮心里一直有个念头能不能用自己最熟的纯 Java 技术栈做一个真正能落地的企业级 Agent 平台不是那种串个 Prompt 调一下 API 的 Demo而是能把 Agent 塞进现有业务系统、能管权限、能控成本、能监控追踪的正经基础设施。这个项目我就叫它 BizBuddy一个纯 Java 实现的 Agent Harness 平台。这篇文章把我在设计、编码、踩坑过程中的核心思路和取舍记录下来给同样在 Java 生态里折腾 AI 应用的同学做个参考。如果你正纠结Java 到底能不能搞 AgentHarness 和 Agent 有什么区别从零搭一个要注意什么这篇应该能帮你省不少时间。1. 为什么偏偏是纯 Java——技术选型的底层逻辑1.1 Agent Harness 到底解决什么问题聊 BizBuddy 之前得先把Agent Harness这个概念掰清楚。很多人第一次听到 Harness 会以为它又是一个 Agent 框架类似于 LangChain 或 Spring AI 那种。实际上 Harness 和 Agent 是两个层面的事情Agent 是大脑负责理解任务、规划步骤、调用工具而 Harness 是躯体和操作系统负责让 Agent 能安全、可控、可度量地运行在企业环境里。我的理解里Harness 至少要做这几件事接收任务请求并解析意图、管理 Agent 实例的全生命周期、维护工具注册表和调用权限、记录完整的运行轨迹和 token 消耗、控制并发和配额、处理模型网关的切换与降级。说白了如果你把 Agent 想象成一个员工Harness 就是公司的行政、财务、法务和 IT 部门——没有这些支撑员工再聪明也做不成事。当时决定做 BizBuddy 的契机很简单我们团队用 Python 写了一套 Agent 原型Demo 跑得挺欢但要接入公司现有的订单系统、CRM 和审批流时出了各种幺蛾子——身份认证对不上、数据权限控制不了、运维体系完全割裂。这些都是企业落地时绕不过去的坎而解决这些问题恰恰是 Java 后端生态的强项。所以 BizBuddy 的定位从一开始就不是又一个智能体框架而是能长在企业 Java 土壤里的 Agent 运行时平台。1.2 Java 生态在企业级 AI 基建中的地位前几年有个论调搞 AI 必须用 PythonJava 已经过时了。这个观点在算法训练和研究领域大体没错但在企业级 AI 应用落地这件事上Java 的底子其实厚得惊人。先看事实绝大多数中大型企业的核心业务系统都是 Java 技术栈。Spring Boot 的微服务体系、Dubbo / Spring Cloud 的服务治理、成熟的监控链路Prometheus Grafana SkyWalking、权限框架Spring Security / Shiro这些东西不是一朝一夕能替换的。Agent 要真正干活必须和这些系统深度握手。比如 Agent 要查一个订单它得走统一网关、带身份凭证、过数据权限过滤甚至要留审计日志——这套机制在 Java 生态里已经是自来水级别的成熟度了。Java 21 的虚拟线程也解决了一个关键痛点。Agent 的典型运行模式是思考→调用工具→等待结果→再思考这个过程中大量时间花在网络 IO 上。虚拟线程让并发 Agent 实例的承载量上了一个大台阶一个普通的 8 核 16G 实例轻松挂几百个并发会话不费劲这在 Loom 出现之前是不可想象的。还有 JVM 本身的优势对 CPU 密集的推理结果解析、JSON 序列化、正则匹配等任务JIT 编译后的性能并不输给 Python而像 G1 / ZGC 这样成熟的垃圾回收器也能够支撑长生命周期 Agent 会话的内存稳定。加上 Java 开发者人才储备充足、招人容易技术债可控这些都是企业在技术选型时实实在在的考量。1.3 为什么不用 Python 或直接套 LangChain这个问题我被问过无数次也经常被质疑你是不是不会 Python 才嘴硬。事实恰恰相反——我用 Python 写过不少小工具和数据分析脚本但在 BizBuddy 的选型上Python 有 3 个绕不开的问题。第一是工程化壁垒。LangChain 这类框架迭代速度极快接口说变就变今天写好的代码三个月后就是一坨不可维护的遗留物。这不是说 LangChain 不好而是它的定位是快速实验不是长期稳定交付。企业系统需要的是契约稳定、向后兼容、有版本策略的依赖这一点 Java 生态的 Spring 系框架做得更好。第二是并发和资源管理。Python 的 GIL 让真正的 CPU 密集型并行变得很拧巴asyncio 的编程心智又和传统后端团队的习惯差异太大。虽然可以通过多进程绕过 GIL但带来的状态共享、通信开销、运维复杂度都是一堆新问题。Java 这边线程池 虚拟线程 结构化并发的组合拳对习惯了后端开发的团队几乎是零学习成本。第三是生态整合深度。我们内部有几十个 Dubbo 接口、好几个 MQ 集群、一套自研的规则引擎全部是 Java 的。Agent 平台如果用了 Python要么得额外搭建一套桥接服务要么得通过 HTTP 转发链路一长延迟和故障点都上去了。纯 Java 实现意味着 Agent 可以直接用 RPC 调用内部服务、直接消费 MQ 消息、直接复用现有的序列化协议和注册中心,这个深度整合的便利性大概是选型时最朴素也最真实的理由。当然套用 LangChain4j 或 Spring AI 这些 Java 版框架也是可行路线但 BizBuddy 的目标是一个契合自身业务、深度定制的 Harness 平台直接套通用框架反而要花大量精力去适配和绕过框架的限制。所以最终我选择了轻框架 自研核心的路线利用 Spring Boot 的依赖注入和配置体系但 Agent 编排引擎、工具注册中心、会话记忆管理这些核心组件全部自己写。这样才能做到每个设计决策都清楚为什么出了问题也知道从哪里下手查。2. 从零搭建 Agent Harness 的整体架构与核心模块2.1 分层架构与模块边界划分BizBuddy 的整体架构按接入层 → Harness 控制面 → Agent 执行面 → 模型接入层 → 基础设施五层来划分每一层的职责单一、接口清晰。这种分层的最大好处是替换模型厂商不影响上层编排逻辑新增工具类型不影响 Harness 核心调整配额策略不需要动执行引擎。接入层提供 REST API、WebSocket 消息通道和消息队列消费者三种接入方式。REST 用于同步请求响应模式WebSocket 用于流式交互大模型流式输出的体验更好MQ 消费则用于异步批处理场景比如定时生成报表、批量处理工单。Harness 控制面这是平台的中枢包含任务调度器、会话状态机、上下文管理器、配额管控组件。负责接收任务、初始化 Agent 实例、管理运行状态、回收资源。Agent 执行面Agent 本身的运行环境包含意图规划器、工具调用器、记忆管理器、反馈循环引擎。执行面不关心具体业务逻辑只关心如何一步步完成任务。模型接入层统一的 LLM Gateway屏蔽各家模型厂商的差异。支持 OpenAI 兼容协议、通义、DeepSeek、以及私有化部署的模型服务。基础设施层依赖的中间件和基座包括 MySQL元数据和会话存档、Redis缓存和分布式锁、RocketMQ异步任务、Elasticsearch日志检索和对象存储知识文件和结果集归档。在模块划分上有两个细节值得展开。第一个是控制面和执行面为什么要物理分离——如果 Agent 直接通过 Spring Boot 的单体应用对外提供服务并发一高、模型响应一慢HTTP 线程池就会被打满进而影响其他业务接口。BizBuddy 的做法是控制面持有轻量状态把 Agent 执行任务放到独立的线程池/虚拟线程空间中运行用有界队列削峰这样即使模型服务变慢接入层依然能快速响应并返回排队状态。第二个细节是上下文管理器独立成一个模块。Agent 对话最麻烦的问题就是上下文爆炸每轮思考记录、工具结果、中间推理过程都往上下文里塞token 消耗会指数级增长。上下文管理器专门负责维护每个会话的 token 预算、自动摘要历史、裁剪无关内容保证 Agent 的记忆始终在可控范围内。我把它做成独立的模块而不是附属于会话状态机就是因为它够复杂、够关键值得被单独对待。2.2 会话状态机Agent 的命脉Agent 的运行本质上是一个多轮状态流转的过程。我把状态定义为PENDING等待调度→PLANNING规划中→TOOL_CALLING工具调用中→TOOL_RESULT处理工具结果→THINKING模型推理中→COMPLETED完成/FAILED失败/REJECTED被拦截。这个状态机不只是简单的枚举切换每一轮切换都会触发对应的钩子动作。比如进入TOOL_CALLING状态之前会执行工具权限预校验和参数注入退出TOOL_RESULT状态时会把工具执行结果存入上下文快照并更新 token 消耗统计。为什么状态机对企业级 Harness 如此重要因为 Agent 可能跑很久、跨多个服务、被用户中断、被超时强杀没有一个可靠的状态记录分布式环境下根本没法恢复和排查。BizBuddy 在状态变更时会写一份事件日志到 MySQL 和 ES内容包括会话 ID、Agent 实例 ID、旧状态、新状态、触发原因、相关消息快照和时间戳。一旦出现异常运维人员可以直接按会话维度把整个生命周期回放出来定位问题的时间从小时级压缩到分钟级。状态机的实现有一个容易踩的坑一定要防止状态跳变和重复流转。比如一个超时任务可能同时被定时扫描线程标记为FAILED又被用户取消请求标记为CANCELLED。我在实现时给每个状态加了一个合法转换矩阵非法转换直接抛异常并告警配合 Redis 分布式锁保证同一时刻只有一条线程能修改同一会话的状态。这套机制上线后状态不一致的问题基本绝迹了。2.3 工具注册中心与 Function Calling 适配Agent 要真正解决业务问题光靠大模型的聪明才智是不够的它得能调用真实的系统能力。BizBuddy 把一切可调用的外部能力都抽象为工具查订单、改库存、发审批、甚至调用另一个 Agent 的输出统称为一个 Tool。工具注册中心就是管理这些工具的应用商店。注册中心的核心数据结构是工具描述符ToolDescriptorpublic record ToolDescriptor( String toolId, String name, String description, JsonSchema parameterSchema, ToolInvoker invoker, PermissionRule permissionRule, RateLimitRule rateLimitRule, boolean asyncSupport, Duration timeout ) {}ToolInvoker是一个函数式接口实现类可以是查数据库、调 Dubbo 服务、发 MQ 消息等。parameterSchema用 JSON Schema 格式描述既用来给大模型生成调用参数也用来做入参校验——这两个用途缺一不可。大模型输出的是 JSON 字符串没有 schema 约束和校验生产事故只是时间问题。Function Calling 的适配是这里最棘手的工作。各家模型OpenAI、通义、Claude 等的函数调用协议格式大同小异但微妙差异不少有的模型在系统提示词中描述工具更听话有的模型则原生支持工具定义数组有的返回tool_calls字段有的放在function_call里。BizBuddy 在模型接入层做了一层协议翻译把统一的工具描述符转换为各家模型需要的格式再把各家模型的调用结果翻译回统一的 ToolResult 对象。翻译层之后上层 Agent 编排逻辑完全不感知模型厂商的差异。这里分享一个很实用的技巧工具描述里的description字段是大模型的说明书写得越精准、越具体、包含越多的边界条件说明模型选对工具、填对参数的概率就越高。比如查询订单信息和根据订单号查询指定订单的状态、金额、物流信息订单号格式为纯数字两种写法后者在真实测试里的工具命中率提升了差不多 18 个百分点。这个提升不是魔法而是大模型本身对语义精准度的敏感性。2.4 模型网关多厂商接入的关键设计模型接入层在 BizBuddy 里叫 LLM Gateway核心目标只有一个让上层永远不用关心这次请求发给哪家模型商。它提供统一的 Chat API 接口返回统一的流式事件流内部则做路由、重试、熔断和灰度。网关的配置化设计是这样的bizbuddy: llm: providers: - id: openai-gpt4o type: openai-compatible baseUrl: https://api.example.com/v1 apiKey: ${OPENAI_API_KEY} models: - name: gpt-4o maxTokens: 8192 temperature: 0.7 weight: 60 - id: internal-llama type: openai-compatible baseUrl: http://llm-internal:8080/v1 apiKey: none models: - name: llama-3-70b maxTokens: 4096 weight: 40看到这段配置你大概能感受到网关的两个核心机制按权重路由和多模型冗余。生产环境我们同时挂了外部商用模型和内部私有化模型权重比例可动态调整。某家模型服务不稳定时网关自动触发熔断把流量切到健康节点这个过程对上层 Agent 透明。网关层还承担了一个常被忽略的职责——token 计量和成本核算。每次模型调用网关都会解析返回的 usage 字段把 prompt tokens、completion tokens 累加到会话账单上并按配置的单价折算成费用。这样每个业务部门、每个应用、每次会话花了多少钱都能精确到分。企业上 AI 项目最怕黑盒花钱网关的计量能力是成本治理的基础。3. 核心实现细节与真实踩坑记录3.1 Java 泛型擦除与 JSON 序列化的恩怨在设计 BizBuddy 的工具调用链路时我第一个遭遇的老朋友问题就是 Java 泛型擦除。工具调用需要把大模型返回的 JSON 参数反序列化成工具方法声明的参数类型比如一个工具方法的签名是updateInventory(String skuId, Integer quantity)大模型返回的 JSON 是{skuId: A1001, quantity: 5}反序列化轻而易举。但如果参数是ListOrderItem这种泛型类型事情就没那么简单了。Java 的TypeReference是解决这个问题的常见招数但工具注册时的参数类型是动态的、声明在各自的工具实现类里没有办法在编译期确定具体的泛型参数。我的方案是工具方法在注册时额外声明一个Type对象通过TypeFactory.constructParametricType构造反序列化时直接用这个动态类型而不是Class。JsonNode argsNode JsonUtils.parseObject(toolCall.getArguments()); JavaType targetType toolDescriptor.getParameterType(); Object[] params JsonUtils.convertValue(argsNode, targetType);这个方案在和 Jackson 的ObjectMapper配合时表现很好但有一个细节必须注意一定要在工具注册时对参数类型做校验不是所有类型都能被安全地构造和转换。像LocalDateTime这种需要自定义反序列化器的类型如果没配置好运行期才爆错是非常难受的。我在注册中心加了一道参数类型可反序列化预检注册时就用空 JSON 试一次反序列化提前暴露问题。泛型的坑还体现在工具调用结果上。Agent 拿到工具结果后需要把结果做摘要再放进上下文。ListOrder这种结果转自然语言摘要时要小心不能直接把整个 JSON 塞进上下文得写一个结果裁剪器只保留关键字段。比如订单对象有 40 个字段上下文里只需要订单号、状态、金额三个字段。这个裁剪不只是省 token更重要的是减少噪声对模型判断的干扰。3.2 虚拟线程落地从 ExecutorService 到 LoomsBizBuddy 一开始用的是传统线程池配置了corePoolSize50, maxPoolSize200的 ThreadPoolExecutor。压测发现一个问题Agent 单个任务的平均耗时在 10 到 30 秒之间其中大部分时间是在等待模型 API 返回。线程池里的线程表面上在干活实际是在等待。200 个线程很快被占满新任务开始排队用户体验明显下降。调大线程数又会导致线程上下文切换开销暴增CPU 飙高但吞吐上不去。Java 21 的虚拟线程Virtual Threads几乎是给这种场景量身定做的。虚拟线程是 JVM 管理的轻量级线程阻塞时自动从载体线程上让出不再占着资源空等。改造 BizBuddy 的执行引擎只花了不到半天把Executors.newFixedThreadPool(...)换成了Executors.newVirtualThreadPerTaskExecutor()Agent 执行任务直接丢进去。改造后的效果非常明显。同一个 8C16G 的容器Agent 并发会话数从 200 提升到 1500 以上P95 响应时间还有所下降因为排队导致的等待消失了。当然虚拟线程不是银弹在 BizBuddy 里有两个地方是刻意保持平台线程的一是 CPU 密集型的 JSON 解析和大文本摘要计算避免大量虚拟线程争抢 CPU 导致调度开销增加二是对接 JDBC 连接池的地方因为连接池本身是同步阻塞资源。3.3 把 REST 接口快速封装成 Agent 工具企业内部有大量 REST APIAgent 的能力很大程度取决于能调用多少现成的接口。BizBuddy 提供了一个轻量级的注解方案让 Java 开发者最快只用 5 分钟就能把已有 Service 方法暴露给 Agent。BizBuddyTool( id order.queryDetail, name 查询订单详情, description 根据订单号查询订单的状态、金额、物流信息订单号格式为数字 ) public OrderDetailVO queryOrderDetail(ToolParam(description 订单号) String orderId) { return orderService.queryDetail(orderId); }这个注解背后做了三件事第一方法包装成ToolInvoker利用 AOP 拦截调用第二通过反射提取方法参数并生成 JSON Schema——这里有个取舍直接反射能拿到参数名但拿不到业务含义描述所以必须依赖ToolParam注解补充说明第三统一包装返回值并做异常兜底。对于已有的第三方 REST 接口比如 ERP 系统的查询接口BizBuddy 提供了一套 REST 转 MCP 工具 的适配器配置tools: - id: erp.invoice.query type: rest method: GET url: http://erp.internal/api/invoices/{invoiceNo} headers: - Authorization: ${ERP_TOKEN} responseMapping: invoiceNo: $.data.invoice_no amount: $.data.amount status: $.data.status这套配置的巧妙之处在于responseMapping字段。RestTemplate 拿到一个完整的 JSON 响应后适配器按照映射规则把响应裁剪成 Agent 需要的字段返回一个精简的 ToolResult。这样既减少了上下文占用也让大模型更容易从结构化结果中提取信息。从技术实现上就是一次 JSONPath 解析加字段映射但带来的 Agent 效果提升非常实在。3.4 上下文管理一次记忆清空的血泪教训Agent 上下文管理是那种做好了没感觉做差了出大事的模块。BizBuddy 早期版本对上下文采取全保留策略——每轮消息、工具结果都原样放进去。跑了一段时间后发现两个问题一是 token 消耗快速上涨一个复杂的多轮任务做完能烧掉几百万 token二是当上下文超过一定长度后模型的回答质量反而明显下降甚至开始忘记最初的指令。痛定思痛后我重构了上下文管理器引入了三层记忆结构短期记忆当前规划步骤和最近 3 轮以内的消息完整保留。中期摘要超过 3 轮但还在同一会话中的历史内容用摘要模型压缩成要点。长期知识跨会话的重要结论、用户偏好、工具使用偏好存储在 MySQL 中按需检索注入。上下文的组装顺序也很讲究。BizBuddy 的 prompt 模板按 系统指令 → 知识库检索结果 → 中期摘要 → 短期消息 → 当前任务指令 → 可用工具描述 的顺序拼接。这样能让模型的注意力资源集中在当前任务上而不是被古老的历史消息分散掉。实测在会话超过 20 轮之后摘要策略让 token 消耗降低约 55%而且用户核心诉求的完成率反而提升了 10% 以上。记忆管理还有一个细节工具的中间执行结果一定要设置有效期。一个耗时 30 秒的工具结果如果 10 轮对话后还放在上下文里既占空间又容易干扰模型。BizBuddy 规定工具结果被模型读取过一次后默认从完整上下文降级为摘要保留。这个策略初看起来会损失细节但在实际业务场景里Agent 需要的往往不是原始结果而是基于结果得出结论摘要完全够用。4. 企业级落地的几个关键保障体系4.1 安全管控RBAC 权限模型与工具拦截Agent 对企业系统的访问权限如果失控后果不堪设想。BizBuddy 的权限体系从两个维度设计用户维度和工具维度。用户维度沿用企业现有的 RBAC 模型一个用户能调用哪些工具取决于他的角色绑定工具维度则更细粒度——同一个工具可以对不同用户开放不同的参数范围。举个例子order.queryDetail这个工具普通客服只能查询自己负责的订单区域经理能查询本区域全部订单风控人员则可以无条件查询。这个行级权限在 BizBuddy 里通过参数注入实现工具调用前Harness 根据当前用户身份向参数模板中注入过滤条件比如ownerId currentUserId大模型只会看到过滤后的数据范围从源头上避免了越权。工具调用拦截器是安全体系的最后一道闸门。每次工具执行前都要经过三个检查身份认证用户是否合法、操作授权角色是否允许调用此工具、数据隔离参数是否越权。检查放在独立线程中执行使用本地缓存保证性能实测单次拦截耗时小于 0.5ms。任何拦截动作都会记录审计日志包括用户、时间、工具 ID、参数摘要和拦截原因这块数据用于安全审计和风险模型训练。4.2 可观测性设计每个 Agent 任务都能对上账企业级平台如果没有可观测性就是聋子和瞎子。BizBuddy 的监控体系覆盖三个层面日志、指标和链路追踪。日志层面每个 Agent 的每轮决策、工具调用、模型请求、错误堆栈都会生成结构化日志写入 ES 并通过 Kibana 检索。关键日志包括任务开始结束事件、模型 API 调用的耗时和 token 数据、工具调用的入参和出参摘要、状态机流转记录。这些日志不是有就行而是要在问题排查时真正帮上忙。我们遇到过调试一个 Agent 错误回答的小问题回放了日志后发现有一步工具参数把 skuId 和 productId 传错了几分钟就定位了根因。链路追踪层面BizBuddy 集成了 OpenTelemetry。一次完整的 Agent 请求会生成一条 trace包含多个 span模型调用、工具执行、知识检索、摘要生成。配合 Grafana 的 Trace 视图可以直观看到一次请求的时间都花在哪里——是卡在模型 API 响应上还是工具执行慢还是上下文组装太耗 CPU。指标层面除了常规的 QPS、延迟、错误率BizBuddy 特有指标包括工具调用成功率、模型 token 消耗速率、上下文命中率、Agent 任务完成率。我在 Grafana 上专门建了一个 Agent 运维面板把任务积压数、模型故障熔断次数、成本消耗趋势放在一页上每天扫一眼就知道平台健康度。4.3 限流与熔断保护业务系统不被 Agent 打垮Agent 平台的引入对下游业务系统是一个全新的流量来源。一个失控的 Agent 可能在短时间内高频调用某个接口拖垮刚好在尖峰期的订单服务。BizBuddy 在 Harness 层实现了一套多维度限流方案应用级配额每个接入 BizBuddy 的业务方有独立的 QPS 上限和每日 token 预算。会话级限流单个会话在单位时间内的工具调用次数、模型请求次数均设上限。工具级熔断每个工具独立配置阈值比如order.queryDetail单实例最大 QPS 为 50超过则触发熔断熔断后 10 秒内该工具的调用直接返回降级结果。熔断器采用滑动窗口 半开探测模式。熔断状态下Agent 可以选择跳过该工具如果任务有替代路径或者重规划。这里有一个容易忽略的体验细节当熔断发生时Harness 一定要给 Agent 一个行动指令提示比如该接口当前不可用请换一种方式回答用户。否则模型会反复尝试调用同一工具造成无效循环流量而且用户看到的就是 Agent 卡住不动了。降级策略还需要区分可降级和不可降级的工具。查询类工具可以降级为缓存数据或预计算结果写操作类工具必须返回失败并终止后续动作。这个判断必须在工具注册时明确标注不能在运行时猜。4.4 知识库接入与 RAG 增强Agent 要回答企业内部的复杂问题光靠模型训练时学到的通用知识远远不够必须接入企业私有知识库。BizBuddy 的 RAG 管线分为三步文档解析切分、向量化、按需检索注入。文档解析切分是决定 RAG 质量的第一步。不同的文档类型PDF、Word、Markdown、Excel解析出来的结构差异很大直接通吃效果很差。BizBuddy 的策略是按照文档语义结构切分标题层级、表格结构、段落边界都作为切分点的候选。切分后的 chunk 控制在 300 到 500 字左右相邻 chunk 保留 5% 的重叠避免上下文被拦腰切断。向量化的技术选型值得一提。BizBuddy 初期想直接引入独立的向量数据库但考虑到运维成本和现在的数据量最终选择在 MySQL 里用向量字段方案。MySQL 8.4 开始原生支持向量类型配合 HNSW 索引百万级向量数据的检索延迟在 10ms 级别完全够用。这个选型的取舍很清楚不引入多余的中间件把向量能力塞进已经成熟的存储体系运维心智成本几乎为零真到了千万级向量规模再迁移独立向量库也不迟。5. 压测结果、性能调优与稳定性保障5.1 三个压测暴露出的性能瓶颈BizBuddy 第一次正式压测是在 32 核 64G 的容器上做的目标 500 并发 Agent 会话。第一轮结果出来就被打脸了P95 延迟高达 48 秒错误率 12%。定位后发现三个瓶颈。瓶颈一findViewById 式的扫表查询。会话状态下文组装时需要频繁查询 MySQL 获取会话历史记录。早期每个会话建立时都会全量扫描该会话的消息表会话一多数据库连接池直接被拖垮。修复方案消息表按会话 ID 分区配合 Redis 缓存最近 50 条会话消息历史消息走 SQL 分页查询。瓶颈二模型网关的 HTTP 连接复用不足。网关默认使用 HttpClient 的短连接模式压测时大量时间花在 TCP 握手和 TLS 协商上。修复方案启用连接池并设置合理的空闲存活时间同时开启 HTTP/2 多路复用。改动之后同样的并发下网关 CPU 占用下降了约 40%。瓶颈三工具执行线程被慢调用阻塞。某个第三方接口本身响应就要 8 秒虚拟线程虽不吃 CPU但这个工具占用的时间拖长了整个任务链路。修复方案给每个工具设置独立的超时时间默认 5 秒超时立即返回工具执行超时结果让 Agent 自行决策重试或换路径。压测的教训总结成一句话Agent 平台的性能瓶颈往往不在模型 API而在于你如何调度你的内部资源和下游依赖。模型 API 慢可以用并发掩盖但下游接口的雪崩效应是并发掩盖不了的。5.2 JVM 调优与内存稳定性Agent 平台和普通 Web 服务在 JVM 调优上有个显著差异会话的内存生命周期更长、对象更大。一个大任务的上下文可能携带几十个工具结果对象、多轮模型消息这些对象在新生代和老年代之间频繁移动容易引发频繁 GC。BizBuddy 的 JVM 参数经验值如下-Xms16g -Xmx16g -XX:UseG1GC -XX:MaxGCPauseMillis100 -XX:ParallelRefProcEnabled -XX:MaxTenuringThreshold6其中MaxTenuringThreshold6是经过反复测试确定的。会话上下文对象大多活过几次 Minor GC 但不是永久存活默认值 15 会导致这些对象在新生代多复制几轮浪费 CPU调成 6 则更早晋升到老年代减少复制开销。老年代空间充足时G1 的 Mixed GC 能很好处理这些对象。另外强烈建议开启-XX:HeapDumpOnOutOfMemoryError并在容器编排中配置好堆转储文件挂载。Agent 平台最怕的不是慢而是在毫无前兆的情况下内存爆炸。堆转储文件配合 MAT 分析能快速定位是哪个会话、哪类对象把堆吃满了。在实际运营中我遇到过一个很刁钻的问题某个 Agent 会话在没有显式关闭的情况下长期存活其上下文引用了大量历史消息对象GC 无法回收。最后通过给会话设置空闲超时默认 30 分钟无交互自动销毁解决。类似的资源泄漏问题光靠 JVM 参数解决不了必须在应用层面建规则。5.3 从 POC 到生产灰度发布与可回滚BizBuddy 上线时没有做一刀切地全量切换而是采用了非常保守的灰度策略。第一周只允许一个内部业务部门接入并完全以旁路模式运行——Agent 的决策和工具调用结果都记录日志但不实际执行业务操作。这个阶段的价值在于积累真实场景的执行数据验证工具描述是否需要优化、模型选型是否需要调整。第二周开放沙箱执行允许 Agent 在隔离的测试环境执行真实业务流程。这里发现了一个有意思的问题同一个工具描述在大模型的不同版本上表现差异明显。升级模型版本前必须拿一套标准测试集大约 200 条任务场景回归一遍工具调用准确率防止模型更新带来的行为漂移。正式全量上线后还保留了一键回滚开关。如果某个模型或工具策略引发线上问题可以快速把全局会话切换到备份模型通道。这个开关的底层逻辑是BizBuddy 的模型网关支持按流量的百分比切分运维人员可以实时调整各个模型供应商的流量比例从 0% 到 100%不需要重启服务。6. 回顾一些心得体会最后说点掏心窝的话。做 BizBuddy 这个项目最大的体会是Agent 平台的复杂度不在模型有多聪明而在你用什么方式让它可靠地干活。大模型的不确定性永远存在但 Harness 层的目标恰恰是用工程手段把这个不确定性关进笼子里——超时、熔断、限流、权限校验、状态机、可观测性每一个组件都是在给不确定的 AI 行为上保险。如果你也想在公司内部推动类似的平台我给三个建议。第一从最痛的业务场景切入不要一开始就追求大而全的通用平台找到一个人工处理成本高的场景比如客服工单分类、数据报表生成做深做透比画一个宏大的蓝图更容易拿到资源和信任。第二重视工具质量胜过模型选择工具描述写得好不好、边界约束清楚不清楚决定了 Agent 能力的上限——这个结论在我们测试多个模型后依然成立。第三做好成本可视化让每个使用方看清自己消耗了多少 token、花了多少钱这个机制本身就能催生很多优化需求。项目推进的过程中我也犯过不少错误比如早期把 Agent 编排逻辑写得太智能试图让模型自己决定所有步骤结果失控概率大增。后来逐渐收敛到预设流程为主、模型自主决策为辅的模式稳定性显著提升。工具调用参数转 JSON 时也出现过几次因为序列化不规范导致的线上问题后来统一规范成了强类型参数对象。这些坑和一个朴素的心态有关Agent 是软件工程产品不是魔法盒子敬畏工程、守住底线才能让它在真实业务里跑得稳。