Spring AI Alibaba生产级Agent实战:架构、记忆与可观测性
1. 项目概述从“玩具”到“生产级”的最后一公里作为一名在Java和Spring生态里摸爬滚打了十多年的老程序员我最近几个月一直在折腾AI Agent。从最初的LangChain4j尝鲜到后来深度使用Spring AI我踩过的坑比写过的Hello World还多。前两篇实录里我们聊了基础概念搭建、核心组件选型也动手实现了一个能跑起来的“玩具级”Agent。但说实话那玩意儿离真正能上线、能扛住生产环境流量的“生产级”Agent还差着十万八千里。这最后一篇我们就来啃下最硬的骨头如何用Spring AI Alibaba 1.1.2.0这个相对较新的框架把手里的“玩具”打磨成一个具备健壮性、可观测性、可维护性的生产级应用。为什么是Spring AI Alibaba 1.1.2.0在之前的探索中我发现原生的Spring AI虽然理念先进但在国内环境下的易用性、对国产模型的支持以及与企业级Spring Cloud Alibaba生态的集成度上总有些隔靴搔痒。而Spring AI Alibaba这个子项目可以看作是阿里云为Spring AI生态注入的“本地化增强包”。1.1.2.0版本在稳定性、功能完整性上已经达到了一个可用的状态特别是它深度集成了阿里云百炼、灵积等平台的模型同时在配置管理、链路追踪等方面与Nacos、Sentinel、SchedulerX等阿里云组件天然打通这对于我们构建需要运维的生产系统来说吸引力巨大。这篇完结篇我不会再重复基础API调用而是聚焦于那些决定项目成败的“非功能性需求”和“工程化实践”。我们将一起解决几个核心问题如何设计一个既能灵活调度工具又能保证稳定性的Agent执行引擎如何为Agent注入记忆和上下文管理能力让它不是“金鱼脑”如何搭建全方位的可观测性体系当Agent半夜抽风时你能快速定位问题以及最终如何将它打包、部署、接入现有的微服务治理体系如果你也正从PoC迈向生产那么接下来的内容或许能帮你省下不少熬夜调试的功夫。2. 核心架构升级构建稳健的Agent执行引擎在玩具阶段我们可能用一个简单的Service类里面硬编码几个工具调用和LLM对话就完事了。但在生产环境Agent需要处理高并发、长流程、多工具协作的复杂场景一个粗糙的执行引擎很快就会成为瓶颈和故障点。2.1 状态机驱动的流程编排我放弃了最初那种“if-else”式的线性流程控制转而采用状态机State Machine来管理Agent的执行生命周期。Spring StateMachine是一个不错的选择但为了减少依赖我基于枚举和上下文对象实现了一个轻量级版本。核心是定义Agent的执行状态。一个典型的生产级Agent任务其状态流转远比“开始-结束”复杂。public enum AgentExecutionState { PENDING, // 任务已创建等待调度 INITIALIZING, // 初始化上下文加载记忆 THINKING, // LLM进行规划与思考可能多次 EXECUTING_TOOL, // 正在执行某个工具 AWAITING_USER_INPUT, // 等待用户进一步输入用于复杂任务分解 EVALUATING, // 评估工具执行结果决定下一步 PAUSED, // 任务被主动暂停如等待外部事件 COMPLETED, // 任务成功完成 FAILED, // 任务执行失败 CANCELLED // 任务被取消 }每个状态都对应一个处理器StateHandler处理器负责该状态下的具体业务逻辑并决定下一个要迁移到的状态。这样做的最大好处是流程清晰且可维护。当需要增加新的处理逻辑比如在工具执行前加入权限校验时你只需要新增或修改对应的状态处理器而不会把代码搅成一团乱麻。Component public class ThinkingStateHandler implements StateHandlerAgentExecutionState { Autowired private SpringAiAlibabaChatModel chatModel; // Spring AI Alibaba的聊天模型客户端 Override public AgentExecutionState getState() { return AgentExecutionState.THINKING; } Override public void handle(AgentExecutionContext context) { // 1. 构建包含系统指令、历史对话、可用工具描述的Prompt Prompt prompt buildPlanningPrompt(context); // 2. 调用LLM进行“思考”获取下一步行动计划 ChatResponse response chatModel.call(prompt); String llmThought response.getResult().getOutput().getContent(); // 3. 解析LLM的响应可能是直接回答也可能是调用工具的指令 AgentAction nextAction parseLlmResponse(llmThought); // 4. 将下一步动作存入上下文 context.setCurrentAction(nextAction); // 5. 根据动作类型决定下一个状态如执行工具或直接返回结果 context.setNextState(determineNextState(nextAction)); // 6. 记录本次思考的完整Prompt和Response用于后续调试和审计 context.addToExecutionTrace(new ThoughtStep(prompt, response)); } }实操心得状态机的持久化生产环境的Agent任务可能是长时间运行long-running的。服务器重启或Pod漂移不能导致任务状态丢失。我这里的做法是将AgentExecutionContext包含当前状态、历史记录、中间结果等序列化后存入Redis或数据库。状态机引擎每次从持久化存储中加载上下文执行一步保存上下文再进入下一步。这实现了断点续跑的能力对于处理需要人工介入或依赖外部慢API的任务至关重要。2.2 工具执行的熔断与降级Agent的强大在于能调用各种工具函数。但如果一个工具依赖的外部服务挂了或者响应缓慢就会拖垮整个Agent线程甚至引发雪崩。我们必须为工具调用加上“保险丝”。Spring AI Alibaba 1.1.2.0 本身并未直接提供工具调用的熔断机制但我们可以利用Spring Cloud CircuitBreaker通常是Resilience4j轻松集成。我为每个Tool接口的实现类都包装了一层熔断器。首先在application.yml中配置熔断规则resilience4j.circuitbreaker: instances: weatherTool: slidingWindowSize: 10 failureRateThreshold: 50 waitDurationInOpenState: 10s permittedNumberOfCallsInHalfOpenState: 3然后通过AOP或手动包装的方式应用熔断器Service public class WeatherTool implements Tool { Override public String getName() { return get_weather; } CircuitBreaker(name weatherTool, fallbackMethod getWeatherFallback) Override public MonoString execute(MapString, Object inputs) { // 调用不稳定的第三方天气API return webClient.get() .uri(https://unstable-weather-api.com/data) .retrieve() .bodyToMono(String.class); } // 降级方法返回缓存数据或友好提示 public MonoString getWeatherFallback(MapString, Object inputs, Exception e) { log.warn(天气服务熔断使用降级数据, e); return Mono.just({\weather\: \数据暂不可用请稍后再试。\}); } }注意事项工具降级策略的设计降级Fallback不是简单地返回错误信息。你需要根据工具的业务含义设计有意义的降级响应。例如对于查询类工具如天气、股价可以返回最近一次成功的缓存数据并标记为“缓存数据”。对于执行类工具如发送邮件、创建工单可以返回一个提示“操作已排队将在服务恢复后处理”并将任务持久化到死信队列。确保降级方法的响应格式与正常响应一致否则会破坏Agent对工具结果的解析逻辑。2.3 异步化与响应式编程改造同步阻塞式的工具调用会严重限制Agent的吞吐量。Spring AI Alibaba的底层通信默认基于Project ReactorMono/Flux这为我们提供了天然的异步化能力。关键是要确保整个调用链都是响应式的。我重构了Agent的核心执行器使其返回MonoAgentResponse并在每个状态处理器中都采用响应式编程范式。public class ReactiveAgentExecutor { public MonoAgentResponse executeAsync(AgentRequest request) { return Mono.just(request) .flatMap(this::initializeContext) // 异步初始化 .flatMap(this::runStateMachine) // 运行状态机内部也是flatMap链式调用 .map(this::buildFinalResponse) // 构建最终响应 .onErrorResume(e - handleExecutionError(e, request)); // 全局异常处理 } private MonoAgentExecutionContext runStateMachine(AgentExecutionContext context) { // 使用flatMap递归实现状态流转直到终态COMPLETED/FAILED return processCurrentState(context) .flatMap(newContext - { if (newContext.getCurrentState().isTerminal()) { return Mono.just(newContext); } else { // 状态迁移继续执行下一个状态 newContext.transitionTo(newContext.getNextState()); return runStateMachine(newContext); } }); } }这样做的好处是利用Reactor的线程调度器我们可以用极少的线程处理大量并发的Agent请求资源利用率极高。同时结合WebFlux可以轻松实现服务端的Server-Sent Events (SSE)给前端提供流式响应让用户看到Agent“一步一步思考”的过程体验大幅提升。3. 记忆与上下文管理让Agent拥有“持久化记忆”一个只有短期对话记忆的Agent就像金鱼说完上句忘了下句。生产级Agent必须拥有结构化的、可持久化的、可高效检索的记忆系统。3.1 分层记忆体系设计我借鉴了AI设计模式中的常见思路将记忆分为三层短期会话记忆Short-term Session Memory存储在内存如Caffeine缓存中键为sessionId。保存当前对话轮次的完整上下文包括用户消息、Agent思考过程、工具调用及结果。生命周期与一次Web会话或一个任务执行周期绑定。长期向量记忆Long-term Vector Memory使用向量数据库如阿里云DashVector、RedisVL或本地Chroma存储。将对话中的关键信息如用户偏好、达成的结论、提取的实体通过Embedding模型转换为向量后存入。用于实现相似记忆检索当用户提到相关话题时Agent能回忆起过去的对话。外部知识记忆External Knowledge Memory这不是严格意义上的“记忆”而是指Agent能通过工具访问的外部知识源如公司Wiki、产品数据库、CRM系统。这扩展了Agent的“知识边界”。Spring AI提供了ChatMemory和VectorStore等抽象接口Spring AI Alibaba在此基础上提供了对阿里云向量检索服务DashVector的便捷集成。Configuration public class MemoryConfig { Bean public VectorStore vectorStore(DashVectorClient dashVectorClient) { // 使用Spring AI Alibaba的DashVector集成 return new DashVectorStore(dashVectorClient, my_agent_memory_collection); } Bean public ChatMemory chatMemory(VectorStore vectorStore, EmbeddingModel embeddingModel) { // 组合多种记忆实现 ChatMemory memory new ChatMemoryManager(); // 添加基于窗口的短期记忆保留最近20轮对话 memory.addMemoryProvider(new MessageWindowChatMemory(20)); // 添加向量长期记忆 memory.addMemoryProvider(new VectorStoreChatMemory(vectorStore, embeddingModel, 5)); // 检索最相关的5条记忆 return memory; } }3.2 记忆的写入与检索策略记忆不是所有对话内容都存。无脑存储会导致向量数据库被大量无意义的闲聊淹没检索效率和质量下降。我制定了明确的记忆写入策略自动提取摘要在一段对话自然结束如用户说“谢谢”或任务完成后触发一个总结性LLM调用生成这段对话的摘要和关键实体如“用户咨询了订单12345的物流问题已告知预计明天送达”再将摘要存入向量记忆。工具调用结果选择性存储对于工具调用返回的结构化数据如查询到的订单信息、产品规格提取关键字段后存储。用户显式指令允许用户通过“记住这个”等指令主动要求Agent存储当前信息。在检索时也不是简单地将用户当前问题做向量化然后去查。我会采用混合检索Hybrid Search向量相似性检索从向量记忆中找出语义上最相关的几条记忆。关键词/元数据过滤如果当前对话有明确的主题或实体如订单号、产品名用这些关键词对记忆进行过滤提升精度。时间衰减加权给更近期的记忆更高的权重因为用户最近的意图通常更相关。public ListMemoryEntry retrieveRelevantMemories(String userInput, String sessionId) { // 1. 从当前会话的短期记忆中获取最近几轮对话作为最直接的上下文 ListMessage recentMessages shortTermMemory.get(sessionId).getRecentMessages(5); // 2. 对用户输入进行关键词提取可以使用简单的NLP库或规则 SetString keywords extractKeywords(userInput); // 3. 构建混合查询 HybridQuery query new HybridQuery(); query.setQueryText(userInput); // 用于向量相似性搜索 query.setMetadataFilter( Map.of(sessionId, sessionId, // 限定本会话 keywords, Map.of($in, keywords)) // 关键词过滤 ); query.setScoreWeights(0.7, 0.3); // 向量分数权重70%关键词匹配权重30% // 4. 执行检索 ListDocument relevantDocs vectorStore.similaritySearch(query); // 5. 将短期记忆和长期记忆合并并按时间戳排序 return mergeAndSortMemories(recentMessages, relevantDocs); }踩坑实录记忆的“幻觉”与冲突当记忆条目变多后LLM在生成回答时可能会混淆不同记忆中的相似信息产生“幻觉”或者遇到相互冲突的记忆。我的解决方案是在将记忆注入Prompt前增加一个记忆验证与去冲突的步骤。用一个简单的LLM调用使用小模型以节省成本来评估检索到的多条记忆与当前问题的相关性并标记出可能存在冲突的地方然后在给主Agent的Prompt中明确提示“关于XX存在以下两种记录请根据上下文判断哪种更可能符合当前情况”。这显著提升了回答的准确性。4. 可观测性体系搭建给Agent装上“监控眼”和“诊断仪”线上系统尤其是像Agent这样行为具有一定不确定性的系统没有监控就等于盲人摸象。我们需要知道它“在想什么”、“做了什么”、“表现如何”。4.1 结构化日志与分布式链路追踪打日志不能再是简单的System.out.println。我使用MDCMapped Diagnostic Context为每个Agent请求注入唯一追踪IDtraceId和会话IDsessionId确保在并发环境下同一个请求的所有日志都能被串联起来。Component public class AgentExecutionLogger { private static final Logger log LoggerFactory.getLogger(AgentExecutionLogger.class); public void logAgentStep(AgentExecutionContext context, String step, Object data) { // 将traceId和sessionId放入MDC日志框架如Logback会自动附加到每行日志 MDC.put(traceId, context.getTraceId()); MDC.put(sessionId, context.getSessionId()); MDC.put(agentStep, step); // 使用JSON格式记录结构化日志便于后续用ELK等工具分析 log.info(Agent execution step: {}, JsonUtils.toJson(Map.of( step, step, state, context.getCurrentState(), action, context.getCurrentAction(), data, data ))); MDC.clear(); // 注意在异步环境下需要在任务结束时或使用上下文传播机制清理 } }同时集成Spring Cloud Sleuth与Zipkin或阿里云的Tracing Analysis将Agent内部的工具调用、LLM API调用都纳入分布式追踪链路。这样当一个请求变慢时你能一眼看出是卡在了哪个工具还是LLM响应延迟。4.2 关键指标埋点与监控大盘我使用Micrometer在Agent执行的关键路径上埋点将指标暴露给Prometheus并在Grafana中搭建监控大盘。核心指标包括请求量 QPSagent.requests.total,agent.requests.qps耗时分布agent.execution.duration(Histogram)区分总耗时、LLM思考耗时、工具执行耗时。成功率 错误类型agent.requests.success,agent.requests.error(按错误类型分类如tool_error,llm_error,timeout)。工具调用统计agent.tool.invocations(Counter按工具名分类)agent.tool.duration。Token消耗agent.llm.tokens.prompt,agent.llm.tokens.completion这对于成本控制至关重要。Aspect Component public class AgentMetricsAspect { private final MeterRegistry meterRegistry; private final Timer totalExecutionTimer; public AgentMetricsAspect(MeterRegistry meterRegistry) { this.meterRegistry meterRegistry; this.totalExecutionTimer Timer.builder(agent.execution.duration) .description(Total agent execution time) .register(meterRegistry); } Around(execution(* com.yourcompany.agent.core.AgentExecutor.execute(..))) public Object measureExecutionTime(ProceedingJoinPoint pjp) throws Throwable { return totalExecutionTimer.record(() - { try { Object result pjp.proceed(); // 成功计数 meterRegistry.counter(agent.requests.success).increment(); return result; } catch (Exception e) { // 失败计数并记录错误类型标签 meterRegistry.counter(agent.requests.error, error_type, classifyError(e)).increment(); throw e; } }); } }在Grafana大盘上我设置了几个核心面板实时健康状态显示当前QPS、成功率、平均/分位延迟。耗时分解桑基图直观展示一次Agent请求的时间都花在了哪里LLM、工具A、工具B...。错误告警面板当错误率超过阈值如1%或特定工具失败率激增时高亮显示。Token消耗趋势监控每日/每小时的Token使用量预估成本。4.3 会话回放与调试工具监控指标告诉你“出了问题”而会话回放工具能帮你还原“当时发生了什么”。我开发了一个简单的管理后台输入traceId或sessionId就能完整回放一次Agent会话原始用户输入每一步的LLM Prompt和Response这是最关键的能看出LLM当时“想”了什么每一次工具调用的输入参数和返回结果Agent内部的状态流转过程最终生成的回答这个功能在排查“为什么Agent会给出这个奇怪答案”时是救命稻草。实现上只需要将我们在AgentExecutionContext中记录的executionTrace见2.1节持久化到数据库如Elasticsearch便于全文检索并提供查询接口即可。实操心得LLM Prompt/Response的脱敏与审计生产环境中用户输入和LLM响应可能包含敏感信息PII。在存储会话日志用于调试和审计前必须进行脱敏处理。我实现了一个PIIRedactionProcessor在日志写入ES前自动识别并替换掉手机号、邮箱、身份证号等敏感信息为[REDACTED]。同时所有对会话日志的访问都需要严格的权限控制并记录操作日志满足合规要求。5. 生产部署与运维实践代码写好了监控搭好了最后一步就是把它稳稳地送上线。5.1 配置中心化管理所有配置特别是AI模型相关的API Key、Endpoint、模型名称、超时时间、温度参数等绝不能硬编码在代码或application.yml里。我使用Nacos作为配置中心。# 在Nacos中创建 Data ID: agent-service.yaml spring: ai: alibaba: dashvector: api-key: ${DASHVECTOR_API_KEY} endpoint: https://dashvector.aliyuncs.com qianfan: access-key: ${QIANFAN_ACCESS_KEY} secret-key: ${QIANFAN_SECRET_KEY} chat: options: model: qwen-max temperature: 0.2 # 生产环境建议较低的温度输出更稳定 max-tokens: 2000 agent: tools: timeout-ms: 10000 retry-count: 2 memory: short-term-capacity: 50 vector-search-top-k: 5在应用启动时通过RefreshScope和Value动态注入。这样需要切换模型比如从qwen-max切换到qwen-plus以节省成本或调整超时时间时只需在Nacos控制台修改发布后所有Pod自动生效无需重启服务。5.2 健康检查与就绪探针在Kubernetes部署描述文件中必须配置完善的健康检查。apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: agent-service livenessProbe: # 存活探针检查进程是否健康 httpGet: path: /actuator/health/liveness port: 8080 initialDelaySeconds: 60 periodSeconds: 10 readinessProbe: # 就绪探针检查服务是否准备好接收流量依赖项是否就绪 httpGet: path: /actuator/health/readiness port: 8080 initialDelaySeconds: 30 periodSeconds: 5 failureThreshold: 3我自定义了ReadinessHealthIndicator检查关键依赖向量数据库连接DashVector是否可达。LLM API连通性向千问API发送一个轻量级的测试请求。内部线程池状态执行Agent任务的线程池是否已满。 只有当所有关键依赖都就绪/actuator/health/readiness才返回UPK8s的Service才会将流量导入该Pod。5.3 资源隔离与弹性伸缩Agent任务尤其是涉及复杂思考链Chain-of-Thought的任务可能是计算密集型和IO密集型混合的。我采用线程池隔离策略核心Agent执行线程池用于运行状态机、LLM调用等核心逻辑。线程数根据CPU核心数合理设置队列容量不宜过大避免任务积压导致内存溢出。工具调用IO线程池用于执行所有外部工具调用HTTP请求、数据库查询等。这个池可以设置得大一些因为大部分时间线程都在等待IO。LLM调用专用线程池/连接池如果使用HTTP客户端调用LLM API配置连接池如Apache HttpClient Pool以避免连接风暴。在K8s中根据监控指标如CPU使用率、QPS、平均延迟配置HPAHorizontal Pod Autoscaler实现自动扩缩容。apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: agent-service-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: agent-service minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 # CPU平均使用率超过70%时触发扩容 - type: Pods pods: metric: name: agent_requests_qps target: type: AverageValue averageValue: 100 # 平均每个Pod的QPS超过100时触发扩容5.4 版本管理与灰度发布Agent的Prompt、工具集、执行逻辑会频繁迭代。直接全量发布风险极高。我的策略是Agent版本化每个Agent定义包括系统指令、可用工具列表、默认参数都有一个版本号存储在数据库中。请求路由在网关层或Agent服务入口根据用户ID、设备、或其他特征将流量路由到不同版本的Agent。例如可以让10%的用户体验新版本v1.190%的用户使用稳定版v1.0。数据对比与评估收集两个版本Agent的交互日志对比关键指标任务完成率、用户满意度如有埋点、平均对话轮次、Token消耗等。快速回滚如果新版本指标显著变差只需修改路由配置瞬间将所有流量切回老版本。6. 典型问题排查与性能调优实录即使架构设计得再完美线上总会遇到各种稀奇古怪的问题。这里记录几个我实际遇到并解决的典型案例。6.1 问题一Agent响应时快时慢P99延迟飙升现象监控显示Agent服务的平均响应时间正常但P9999分位延迟偶尔会突然飙升到几十秒导致部分用户请求超时。排查过程首先查看Grafana大盘发现工具执行耗时的P99同步飙升但平均耗时正常。说明问题出在某个别工具调用上。检查该工具的熔断器状态发现并未熔断。查看该工具调用外部服务的具体日志和链路追踪。在链路追踪中发现慢请求都卡在同一个外部HTTP API调用上该API偶尔响应极慢超过15秒。检查代码发现该工具调用虽然配置了超时10秒但使用的是同步阻塞的RestTemplate且没有设置连接池和读取超时。解决方案将同步调用改为异步使用WebClient替换RestTemplate纳入Reactor的响应式超时控制。配置细粒度超时为连接、读写分别设置超时如连接超时2秒读取超时8秒。增加二级熔断除了基于错误率的熔断增加基于慢调用比例slowCallRateThreshold的熔断规则。如果超过50%的调用慢于5秒也打开熔断器。引入请求缓存分析该工具的数据发现部分请求参数组合的结果在短时间内是不变的。为这些请求增加了本地缓存Caffeine缓存1分钟大幅减少了不必要的慢调用。调整后的工具配置示例resilience4j.circuitbreaker: instances: externalApiTool: failureRateThreshold: 40 slowCallRateThreshold: 50 # 新增慢调用比例阈值 slowCallDurationThreshold: 5s # 定义慢调用超过5秒 slidingWindowSize: 20 waitDurationInOpenState: 30s6.2 问题二LLM API调用费用超预期现象月度账单显示LLM API的Token消耗费用比预估高出50%。排查过程分析Prometheus中的agent.llm.tokens指标发现completion_tokens输出Token消耗占比异常高。通过会话回放工具抽样分析高Token消耗的会话。发现很多会话的最终回答都很冗长且存在大量重复性、格式化的内容如“好的我将为您...”、“另外还需要注意的是...”。检查系统指令System Prompt发现为了追求回答的友好和全面指令中包含了“请用详细、友好的语言回答”等要求并且没有对输出长度做强制限制。解决方案优化系统指令重写Prompt强调“简洁”、“精准”、“直接回答问题”。使用类似“请用最精炼的语言回答核心问题避免客套话和重复解释。”的指令。设置最大输出Token限制在Spring AI Alibaba的配置中将max-tokens参数从2000调整为800。对于绝大多数问答场景800个输出Token已经足够。实现输出内容的后处理修剪在Agent返回最终答案前增加一个后处理步骤使用简单的规则或一个小模型对过于冗长的回答进行摘要提炼。成本监控告警在Grafana中设置告警规则当每小时Token消耗超过某个阈值时立即发送告警如钉钉、短信以便及时干预。6.3 问题三向量记忆检索召回不准现象用户反馈Agent经常“记错”事情或者无法回忆起明明讨论过的相关话题。排查过程检查向量记忆的写入逻辑确认摘要生成和存储流程正常。检查检索逻辑发现使用的是简单的余弦相似度且没有进行任何过滤。抽查几条检索不准的记录发现用户当前问题“帮我找一下上周讨论的关于项目预算的文档”检索出来的却是三个月前一次闲聊中提到的“个人家庭预算”。两者在向量空间上确实相似但时间、上下文完全不同。解决方案丰富元数据在存储向量记忆时不仅存储文本和向量还存储丰富的元数据sessionId、timestamp、topic通过简单分类器打标、entity提取的关键实体如“项目A”、“预算”。升级检索策略采用带过滤的混合检索。在检索时强制加入时间过滤器如timestamp now() - 30 days和会话过滤器优先检索同一sessionId的记忆。同时将基于关键词的元数据匹配分数与向量相似度分数进行加权融合。引入重排序Re-ranking先用向量检索出Top 20条候选记忆再用一个轻量级的交叉编码器Cross-Encoder模型对这20条记忆与当前问题的相关性进行精确打分和重排序返回Top 3。虽然多了一步计算但精度提升显著。定期清理陈旧记忆实现一个后台任务定期删除超过一定时间如90天且最近未被访问过的低频记忆保持向量数据库的“新鲜度”。经过这一系列从架构设计、记忆管理、可观测性到生产运维的深度改造我们最初那个脆弱的“玩具”Agent已经蜕变成一个具备弹性、可观测、可维护的“生产级”智能体。这个过程充满了挑战但看到它稳定地处理线上流量并真正为用户创造价值时那种成就感是无与伦比的。AI Agent的开发不是一蹴而就的它更像是一个需要持续喂养、训练和调优的“数字生命”。希望这篇实录中的经验和踩过的坑能为你自己的Agent从0到1再到100的旅程点亮一盏灯。