1. “Agent开发只是写提示词?”——这个误解正在毁掉Java工程师的职业判断力
我上个月帮一家做金融风控的客户做技术选型评审,现场听到一位资深Java架构师说:“Agent就是调API+拼提示词,我们后端团队根本不用碰,让算法同学写几个prompt就行。”话音刚落,旁边刚入职半年的应届生小张举手问:“那我们写的Spring Cloud服务、熔断降级逻辑、分布式事务补偿机制,是不是以后都白学了?”全场安静了三秒。这不是段子,是真实发生在某家上市金融科技公司会议室里的对话。
“Agent开发只是写提示词”——这句话像一把钝刀,正在悄悄割裂工程团队的认知共识。它把一个需要多层抽象、状态编排、可观测性治理、错误传播控制、资源隔离与弹性伸缩的复杂系统,压缩成一张Markdown文档里几行带变量的字符串。更危险的是,它让Java工程师本能地产生两种反应:要么彻底放弃跟进,觉得“这不归我管”;要么盲目跟风,用Spring Boot启动一个HTTP接口,把用户输入原样塞给大模型API,再把JSON响应直接返回前端——然后在Code Review时被质问:“你这个/agent/execute接口,超时怎么设?重试几次?失败后要不要触发告警?上下文长度超限时是截断还是拒绝?token消耗有没有配额限制?”
这些不是“算法同学该管的事”,而是生产级Agent系统的工程基座。它和十年前微服务刚兴起时大家争论“SOA是不是就是写几个WebService”一样,表面是技术认知偏差,底层是工程责任边界的模糊。Java作为企业级系统最主流的语言栈,恰恰是承载这种复杂性的最佳载体——不是因为它“适合写提示词”,而是因为它天然具备强类型约束、成熟的线程模型、丰富的生态治理工具(Micrometer、Resilience4j、OpenTelemetry)、以及经过十年高并发验证的JVM调优经验。当别人还在用Python脚本硬编码retry逻辑时,Java工程师已经用@Retryable注解+自定义Backoff策略,在Spring Retry里完成了幂等重试的声明式配置。
所以这篇文章不讲“如何写一个能回答天气的Agent”,也不教“五个万能提示词模板”。我要带你拆开一台正在跑生产流量的Agent系统,看它的每个螺丝钉是怎么拧紧的:从请求进来那一刻的上下文注入时机,到中间工具调用链路的状态快照保存,再到失败时错误分类与降级路由决策,最后到上线后延迟毛刺的根因定位方法。你会发现,真正卡住90%团队落地的,从来不是模型能力,而是Java工程师最擅长却最容易被忽略的那些事——比如一个CompletableFuture的异常传播路径没处理干净,就足以让整个Agent执行链静默失败;比如没对ThreadLocal做清理,就会在Tomcat线程池复用场景下导致上下文污染。
提示:本文所有代码片段、配置参数、监控指标均来自真实生产环境(已脱敏),不是Demo玩具。如果你正面临“Agent项目上线后抖动严重”“工具调用成功率忽高忽低”“运维说看不懂Agent日志”等问题,接下来的内容会直接切中你的痛点。
2. 生产级Agent的四层工程结构:为什么Java是不可替代的承重墙
很多人以为Agent架构就是“LLM + Tools + Memory”,画个三层框图就完事。但当你把这套东西放进每天处理百万订单的支付网关、或每秒接收三千笔风控决策请求的实时引擎里,它立刻暴露出四个必须被工程化解决的层次——而每一层,都在呼唤Java的深度参与。
2.1 第一层:协议层——不是RESTful,而是带状态契约的双向流
生产环境里,Agent绝不是“发个HTTP POST,等JSON回来”这么简单。真实场景中,用户可能在对话中途取消操作,或网络闪断后重连,这时系统必须能恢复中断前的执行状态。我们团队在电商客服Agent中实现的方案是:基于Spring WebFlux构建SSE(Server-Sent Events)长连接通道,每个会话分配唯一session_id,并在Redis中持久化当前执行节点(如“正在调用库存查询API”“等待用户确认优惠券”)。当客户端重连时,通过Last-Event-ID头携带断点位置,服务端直接从Redis加载上下文,跳过已执行步骤。
这背后是Java对异步非阻塞IO的成熟支持。对比Python的asyncio,Java的Reactor模式天然与Spring生态无缝集成:Mono.fromCallable()封装阻塞调用,Flux.concatMap()串行化工具链,timeout()设置每个环节超时阈值。更重要的是,JVM的GC调优经验在这里直接复用——我们曾遇到SSE连接数暴涨导致Old Gen频繁Full GC,最终通过调整-XX:MaxGCPauseMillis=50和启用ZGC,将P99延迟从800ms压到120ms。
2.2 第二层:编排层——状态机驱动,而非硬编码if-else
很多团队用LangChain的SequentialChain或LlamaIndex的RouterQueryEngine做编排,但在高并发下暴露致命缺陷:状态不可观测、分支不可回滚、错误无法精准捕获。我们替换成自研的StatefulOrchestrator,核心是一个轻量级状态机引擎:
// 状态定义(枚举类) public enum AgentState { INIT, // 初始状态 ROUTE_TO_TOOL, // 路由到工具 WAITING_FOR_TOOL_RESULT, // 等待工具结果 GENERATE_RESPONSE, // 生成最终响应 ERROR_HANDLING // 错误处理 } // 状态迁移规则(配置化,非硬编码) public class StateTransitionRule { private AgentState from; private String condition; // SpEL表达式,如 "toolResult.status == 'SUCCESS'" private AgentState to; private String action; // 执行动作,如 "callNotificationService()" }关键设计点在于:所有状态迁移都记录到OpenTelemetry Trace中,形成可追踪的执行链路。当某个会话卡在WAITING_FOR_TOOL_RESULT超过3秒,Prometheus自动报警,SRE能直接在Grafana里下钻查看该session_id的完整Span树——包括调用了哪个工具、传了什么参数、耗时多少、是否触发重试。这种可观测性,是Python脚本式编排永远无法提供的。
2.3 第三层:工具层——不是简单HTTP调用,而是带熔断与降级的领域服务
把数据库查询、第三方API、内部RPC封装成“Tool”,常被简化为一个@PostMapping接口。但在生产环境,这必须是符合领域驱动设计(DDD)的服务契约。以风控场景为例,我们定义RiskAssessmentTool接口:
public interface RiskAssessmentTool { // 核心方法:输入用户行为事件,输出风险分值与理由 RiskAssessmentResult assess(UserBehaviorEvent event); // 必须实现的降级逻辑:当风控服务不可用时,返回默认安全分值 RiskAssessmentResult fallback(UserBehaviorEvent event); // 熔断器配置:基于Hystrix或Resilience4j CircuitBreakerConfig circuitBreakerConfig(); }实际实现类CreditScoreRiskTool不仅调用信贷评分API,还内置了本地缓存(Caffeine)、异步刷新策略(ScheduledExecutorService)、以及基于滑动窗口的失败率统计。当连续5分钟失败率超60%,熔断器自动打开,后续请求直接走fallback()方法返回RiskLevel.LOW——这比让Agent胡乱编造“用户信用良好”要安全得多。Java的面向接口编程和Spring的@Primary、@Qualifier机制,让不同环境(测试/预发/生产)切换工具实现变得极其简单。
2.4 第四层:治理层——不是日志埋点,而是全链路质量度量
生产级Agent的终极挑战不是“能不能跑”,而是“跑得有多稳”。我们定义了四个黄金指标:
- Execution Success Rate:Agent完整执行成功的比例(非LLM返回成功,而是业务目标达成)
- Tool Call Latency P95:所有工具调用耗时的95分位值
- Context Token Utilization:上下文token使用率(避免浪费或溢出)
- Fallback Trigger Rate:降级逻辑被触发的频率
这些指标全部通过Micrometer接入Prometheus,且每个指标都绑定session_id、tool_name、model_provider等标签。当发现CreditScoreRiskTool的P95延迟突然飙升,我们能立即关联到JVM线程堆栈(Arthasthread -n 5),发现是数据库连接池耗尽——根源竟是某个新上线的营销活动Agent,未配置独立数据源,共享了风控库连接池。这种跨服务的质量影响分析,只有Java生态的成熟监控体系才能支撑。
注意:这四层结构不是理论模型,而是我们团队在三个不同行业(金融、制造、政务)落地Agent项目时,反复验证的最小可行工程框架。跳过任何一层,都会在上线后付出十倍代价去补救。
3. Java工程师的护城河:那些提示词永远无法替代的硬核能力
当招聘JD上写着“熟悉LangChain/LLamaIndex”,而面试官却问“如果Agent调用支付接口超时,你怎么保证用户不会重复扣款”,你就该明白:真正的护城河不在提示词技巧,而在Java工程师刻进DNA里的工程素养。以下是五个被严重低估、却决定Agent系统生死的关键能力。
3.1 幂等性设计:不是加个idempotency-key,而是贯穿全链路的状态指纹
很多团队以为给HTTP请求加个X-Idempotency-Key头就万事大吉。但在Agent场景,幂等性必须覆盖从用户输入解析、到工具调用、再到最终响应生成的全过程。我们的方案是:为每个会话生成全局唯一execution_fingerprint,它由三部分哈希组成:
String fingerprint = DigestUtils.md5Hex( sessionId + "_" + userInput.trim() + "_" + System.currentTimeMillis() / 60000 // 分钟级时间戳,避免同一分钟内重复 );这个指纹被注入到所有下游调用中:
- 支付网关:作为
order_id前缀,确保重复请求生成相同订单号 - 风控服务:作为
trace_id,用于识别重复评估请求 - 日志系统:作为
log_id,便于跨服务追踪
最关键的是,我们在Agent状态机中强制要求:任何状态迁移操作前,必须校验当前execution_fingerprint是否已在Redis中存在。如果存在,直接返回缓存结果,跳过所有LLM推理和工具调用。这使我们在黑五促销期间,将重复请求处理耗时从平均320ms降到12ms,且零重复扣款事故。
3.2 线程安全与上下文传递:不是ThreadLocal,而是显式传播的Context对象
Agent执行链路中,常需在不同线程间传递用户身份、租户ID、调试开关等上下文信息。用ThreadLocal在WebFlux的异步链路中极易丢失——因为Mono的subscribeOn()会切换线程。我们的解决方案是:定义AgentExecutionContext对象,并通过Reactor的Context传播:
// 创建上下文 Context context = Context.of( "tenant_id", "bank_a", "user_id", "u123456", "debug_mode", true ); // 在链路中传递 Mono.just(input) .transformDeferredContextual((mono, ctx) -> mono.map(data -> processWithTenant(data, ctx.get("tenant_id"))) ) .contextWrite(context);所有工具调用、日志记录、监控上报都从这个Context中提取必要字段。相比ThreadLocal的隐式传递,这种显式方式让代码可读性大幅提升,且能精准控制上下文生命周期——比如在调用外部API前清除敏感字段,防止泄露。
3.3 内存管理:不是GC调优,而是主动控制LLM上下文生命周期
大模型上下文长度有限(如GPT-4 Turbo为128K),但生产Agent常需处理长达数小时的对话历史。若简单截断旧消息,会丢失关键业务上下文(如“用户上周投诉过物流延迟”)。我们的方案是:用Java实现轻量级上下文压缩引擎,基于以下规则:
| 压缩类型 | 触发条件 | 处理方式 | Java实现要点 |
|---|---|---|---|
| 语义去重 | 相邻消息内容相似度>90% | 保留首条,其余标记为[DEDUPLICATED] | 使用Apache Commons Text的CosineSimilarity |
| 业务摘要 | 对话轮次>50 | 调用专用摘要模型生成100字摘要 | 异步提交到专用GPU队列,避免阻塞主线程 |
| 敏感过滤 | 消息含身份证号/银行卡号 | 替换为[MASKED_ID] | 正则预编译Pattern.compile("\\d{17}[\\dXx]") |
这个引擎完全用Java编写,内存占用可控(单会话<2MB),且能精确控制压缩时机——比如只在每次LLM调用前触发,而非实时压缩。相比依赖外部服务,它避免了网络延迟和额外计费,且能与JVM堆内存监控联动:当老年代使用率超75%,自动降低摘要模型调用频率。
3.4 错误分类与分级:不是try-catch,而是基于业务语义的故障树
Agent失败原因千奇百怪:LLM返回格式错误、工具API超时、网络DNS失败、Redis连接池耗尽……若统一返回“系统繁忙”,用户体验极差。我们构建了三层错误分类体系:
// 第一层:错误域(Domain) public enum ErrorDomain { LLM_PROVIDER, // 大模型服务商问题 TOOL_SERVICE, // 工具服务问题 INFRASTRUCTURE, // 基础设施问题 USER_INPUT // 用户输入问题 } // 第二层:错误码(Code) public enum ErrorCode { LLM_001("模型返回空响应"), TOOL_002("支付接口返回503"), INFRA_003("Redis连接超时"), USER_004("输入包含非法字符") } // 第三层:用户提示(Message) public class UserFriendlyMessage { private String title; // 如“支付暂时不可用” private String detail; // 如“我们正在紧急修复,请稍后再试” private int retryAfterSeconds; // 建议重试间隔 }当Agent执行失败,系统自动匹配故障树,生成对应UserFriendlyMessage。比如TOOL_002错误,前端显示“支付服务正在维护”,并禁用支付按钮30秒;而USER_004错误,则高亮输入框并提示“请勿输入特殊符号”。这种分级处理,让90%的用户问题无需人工介入。
3.5 灰度发布与AB测试:不是Feature Flag,而是带流量染色的动态路由
新Agent版本上线,不能简单切流量。我们实现了一套基于请求头的动态路由机制:
// 请求头携带灰度标识 // X-Agent-Version: v2.1-beta // X-Traffic-Weight: 0.05 (5%流量) // 路由决策逻辑 public AgentVersion resolveVersion(HttpServletRequest request) { String versionHeader = request.getHeader("X-Agent-Version"); if (versionHeader != null && versionHeader.startsWith("v2.")) { return AgentVersion.V2_1_BETA; } // 按权重分流 double weight = Double.parseDouble(request.getHeader("X-Traffic-Weight")); return Math.random() < weight ? AgentVersion.V2_1_BETA : AgentVersion.V2_0_STABLE; }所有监控指标(成功率、延迟、错误率)都按AgentVersion标签分组。当发现v2.1-beta的Tool Call Latency P95比v2.0-stable高40%,我们立即回滚,且不影响其他版本。这种能力,让Java工程师能像管理微服务一样管理Agent版本演进。
实操心得:这五项能力没有一项与提示词相关,但每一项都直接决定Agent能否在生产环境存活。我见过太多团队花三个月优化提示词,却因一次Redis连接池泄漏导致全线崩溃——而修复这个泄漏,只需要一行
maxActive配置和一个destroy-method="close"。
4. 从Demo到生产:Java Agent项目的七道验收门槛
很多团队卡在“本地能跑通,上线就崩盘”的死循环里。不是技术不行,而是缺少一套明确的生产准入标准。我们总结了七个硬性门槛,每个都对应真实踩过的坑。未全部达标,禁止上线。
4.1 门槛一:全链路超时控制——必须有三级超时,且互不干扰
常见错误:只在HTTP客户端设超时,LLM调用超时后仍占用线程。正确做法是三级嵌套:
| 层级 | 超时值 | 作用 | Java实现方式 |
|---|---|---|---|
| LLM API层 | 15s | 防止大模型响应慢拖垮线程 | OkHttpcallTimeout(15, TimeUnit.SECONDS) |
| Agent编排层 | 30s | 控制整个执行链路最大耗时 | Spring@Async+Future.get(30, TimeUnit.SECONDS) |
| HTTP网关层 | 45s | 给前端预留缓冲时间 | TomcatconnectionTimeout="45000" |
关键细节:各层超时必须独立计时,且上层超时触发时,下层必须能立即中断。我们用CancellationToken封装中断信号,当编排层超时,向LLM调用线程发送中断指令,避免资源泄漏。
4.2 门槛二:Token预算管理——不是估算,而是实时监控与动态裁剪
LLM token消耗直接影响成本和性能。我们要求每个Agent实例必须配置token_budget(如8192),并在执行中实时计算:
public class TokenBudgetManager { private final int budget; private AtomicInteger used = new AtomicInteger(0); public boolean tryConsume(int tokens) { int current = used.get(); while (true) { int next = current + tokens; if (next > budget) return false; // 预算不足 if (used.compareAndSet(current, next)) return true; current = used.get(); } } }当tryConsume()返回false,触发上下文压缩引擎,优先裁剪低价值消息(如系统问候语)。上线后,我们发现某客服Agent的token消耗超标37%,根源是LLM总在回复末尾添加“祝您生活愉快!”——这句固定话术被计入token,却无业务价值。通过定制化LLM输出模板,单日节省token成本12万元。
4.3 门槛三:错误日志结构化——不是System.out.println,而是带语义的JSON日志
Agent日志必须能被ELK或Splunk直接解析。我们强制要求所有日志为JSON格式,且包含固定字段:
{ "timestamp": "2024-06-15T14:23:18.123Z", "level": "ERROR", "service": "agent-core", "session_id": "sess_abc123", "state": "WAITING_FOR_TOOL_RESULT", "tool_name": "inventory_check", "error_domain": "TOOL_SERVICE", "error_code": "TOOL_002", "stack_trace": "..." }关键创新:在日志中嵌入可执行的诊断命令。例如当error_code为LLM_001时,日志末尾自动附加:
"diagnosis_hint": "curl -X GET 'http://llm-monitor/api/health?provider=openai' -H 'Authorization: Bearer $TOKEN'"运维人员复制粘贴即可快速验证,将平均故障定位时间从47分钟缩短到3分钟。
4.4 门槛四:降级预案完备性——不是“返回默认值”,而是业务可接受的优雅退化
降级不是技术兜底,而是业务决策。我们要求每个Tool必须提供三种降级策略:
| 策略类型 | 适用场景 | Java实现示例 |
|---|---|---|
| 静态兜底 | 数据绝对可靠(如国家列表) | return CountryList.CN; |
| 缓存兜底 | 数据允许短暂过期(如商品价格) | return cache.getIfPresent(productId); |
| 合成兜底 | 需要模拟业务逻辑(如风控评分) | return syntheticRiskScore(userProfile); |
其中syntheticRiskScore()不是随机数,而是基于用户基础属性(年龄、地域、设备类型)的规则引擎计算,准确率虽不如实时模型,但业务方确认“可接受”。
4.5 门槛五:资源隔离——不是共用线程池,而是按SLA分级调度
Agent不同模块对延迟敏感度不同:
- 实时对话:P95 < 200ms
- 异步报告生成:P95 < 5s
- 批量数据处理:P95 < 30s
我们为每类任务创建独立线程池:
@Bean("realTimeThreadPool") public Executor realTimeThreadPool() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(20); executor.setMaxPoolSize(50); executor.setQueueCapacity(100); executor.setThreadNamePrefix("realtime-"); executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); return executor; }并通过@Async("realTimeThreadPool")精准调度。上线后,批量报表任务CPU飙升时,实时对话延迟纹丝不动。
4.6 门槛六:安全合规审计——不是“没漏洞就行”,而是满足金融级数据治理
Agent处理用户数据,必须满足GDPR和国内《个人信息保护法》。我们实施三项硬措施:
- 输入净化:所有用户输入经
HtmlUtils.htmlEscape()和StringEscapeUtils.escapeJson()双重处理 - 输出过滤:LLM响应通过正则扫描身份证号、手机号、银行卡号,匹配即替换为
[MASKED] - 审计日志:所有含PII(个人身份信息)的日志,自动加密存储于独立审计库,密钥由HSM硬件模块管理
某次渗透测试中,安全团队故意输入“我的身份证是11010119900307231X”,系统在0.8秒内完成识别、脱敏、审计记录全流程,获得满分评价。
4.7 门槛七:混沌工程验证——不是“压力测试”,而是主动注入故障
上线前必须通过混沌实验:
- 网络延迟注入:用ChaosBlade在Agent服务节点注入200ms网络延迟
- LLM服务熔断:强制关闭OpenAI API,验证降级策略
- Redis故障:kill Redis进程,测试本地缓存兜底能力
我们用JUnit5编写混沌测试用例:
@Test @ChaosTest(scenario = "redis_failure") void testRedisFailureFallback() { // 模拟Redis不可用 redisTemplate.getConnectionFactory().getConnection().close(); // 发起Agent请求 AgentResponse response = agentService.execute(sessionId, userInput); // 验证降级生效 assertThat(response.getStatus()).isEqualTo("FALLBACK"); assertThat(response.getFallbackReason()).isEqualTo("REDIS_UNAVAILABLE"); }只有全部混沌实验通过,才允许发布。这让我们在去年双十一零重大故障。
踩坑实录:某次上线跳过门槛四(降级预案),结果风控服务因上游依赖故障,Agent直接返回“系统错误”,导致32%用户流失。复盘发现,只要提前配置好
syntheticRiskScore(),就能维持78%的业务可用性。这七个门槛,每一个都是用真金白银买来的教训。
5. Java工程师的行动清单:今天就能开始加固你的Agent系统
别被“生产级”吓住。很多工程能力不需要推倒重来,只需在现有代码中植入几个关键点。以下是可立即执行的五项改造,每项耗时不超过2小时,但效果立竿见影。
5.1 立即添加:Agent执行链路的Trace ID透传
哪怕你用的是最简版Spring Boot,也能在5分钟内实现全链路追踪:
// 1. 添加Maven依赖 <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing-bridge-brave</artifactId> </dependency> // 2. 在Controller中注入TraceContext @RestController public class AgentController { @Autowired private Tracer tracer; @PostMapping("/agent/execute") public ResponseEntity<AgentResponse> execute(@RequestBody AgentRequest request) { // 生成Trace ID Span span = tracer.nextSpan().name("agent-execute").start(); try (Scope scope = tracer.withSpan(span)) { // 记录关键事件 span.tag("user_id", request.getUserId()); span.tag("input_length", String.valueOf(request.getInput().length())); AgentResponse response = agentService.execute(request); span.tag("status", "SUCCESS"); return ResponseEntity.ok(response); } catch (Exception e) { span.tag("status", "ERROR").tag("error_type", e.getClass().getSimpleName()); throw e; } finally { span.end(); } } }效果:所有日志自动带上trace_id,在Kibana中输入trace_id: xxx,即可看到从HTTP入口到LLM调用、再到工具返回的完整链路。这是排查问题的第一步,也是最廉价的一步。
5.2 立即配置:OkHttp客户端的连接池与超时
别再用RestTemplate裸奔。升级到OkHttp,配置如下:
@Bean public OkHttpClient okHttpClient() { return new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS) .writeTimeout(15, TimeUnit.SECONDS) .connectionPool(new ConnectionPool(20, 5, TimeUnit.MINUTES)) // 20个空闲连接 .build(); } // 在LLM调用处使用 Response response = okHttpClient.newCall(request).execute();效果:连接复用率从32%提升至91%,LLM调用P95延迟下降37%。连接池大小根据QPS动态调整:QPS<100用10,QPS<1000用20,QPS>1000用50。
5.3 立即引入:Caffeine本地缓存加速高频工具
对查询类Tool(如用户基本信息、商品详情),加一层本地缓存:
@Cacheable(value = "userInfoCache", key = "#userId") public UserInfo getUserInfo(String userId) { return userInfoService.get(userId); } // Cache配置 @Bean public Cache<String, UserInfo> userInfoCache() { return Caffeine.newBuilder() .maximumSize(10000) .expireAfterWrite(10, TimeUnit.MINUTES) .recordStats() // 开启统计,便于监控 .build(); }效果:用户信息查询QPS从800飙升至3200,且99%请求命中缓存,数据库压力归零。
5.4 立即编写:Agent执行状态的健康检查端点
让运维能一眼看清Agent是否健康:
@Component public class AgentHealthIndicator implements HealthIndicator { @Override public Health health() { // 检查LLM连接 boolean llmHealthy = checkLlmConnection(); // 检查Redis boolean redisHealthy = checkRedisConnection(); // 检查线程池 boolean poolHealthy = realTimeThreadPool.getActiveCount() < 30; if (llmHealthy && redisHealthy && poolHealthy) { return Health.up() .withDetail("llm_status", "OK") .withDetail("redis_status", "OK") .withDetail("thread_pool_usage", realTimeThreadPool.getActiveCount() + "/" + realTimeThreadPool.getPoolSize()) .build(); } else { return Health.down() .withDetail("reason", "LLM or Redis unhealthy") .build(); } } }效果:Kubernetes liveness probe直接调用/actuator/health/agent,故障时自动重启Pod,MTTR(平均修复时间)从12分钟降至47秒。
5.5 立即部署:Prometheus指标暴露
暴露关键指标,让数据说话:
@Component public class AgentMetrics { private final Counter executionSuccessCounter = Counter.builder("agent.execution.success") .description("Agent execution success count") .register(Metrics.globalRegistry); private final Timer toolCallLatencyTimer = Timer.builder("agent.tool.call.latency") .description("Tool call latency in seconds") .register(Metrics.globalRegistry); public void recordSuccess() { executionSuccessCounter.increment(); } public void recordToolLatency(String toolName, Duration duration) { toolCallLatencyTimer.record(duration, "tool", toolName); } }效果:Grafana看板实时显示成功率曲线,当曲线跌破99.5%,自动触发告警,比用户投诉早12分钟发现问题。
最后分享一个小技巧:每周五下午,花15分钟运行
jstat -gc <pid>,观察Young Gen和Old Gen的使用率。如果Old Gen持续增长不回收,说明你的Agent存在内存泄漏——大概率是某个ThreadLocal没清理,或是缓存没设过期时间。这个习惯,让我在过去三年避免了7次P0级事故。工程没有银弹,只有日复一日的细节打磨。