Spring AI Alibaba 1.x 系列【76】上下文工程(Context Engineering)
文章目录1. 概述1.1 为什么 Agent 会失败1.2 什么是上下文工程2. Agent 循环与上下文类型2.1 Agent 循环2.2 三种上下文类型2.3 三种数据源3. 模型上下文3.1 系统提示System Prompt3.1.1 基于状态的动态提示3.1.2 基于存储的个性化提示3.2 消息历史Messages3.2.1 消息过滤 —— 限制上下文长度3.2.2 瞬态更新 VS 持久更新3.3 工具选择Tools3.4 模型选择Model3.5 响应格式Response Format4. 工具上下文4.1 工具中读取状态4.2 工具中修改状态5. 生命周期上下文5.1 Hook 位置5.2 日志 Hook5.3 消息摘要 Hook6. 完整示例综合上下文工程7. Hook 与 Interceptor 选择指南8. 瞬态 vs 持久 决策树1. 概述1.1 为什么 Agent 会失败构建Agent的难点在于使其足够可靠。虽然写一个Demo很容易但要在生产环境中稳定运行并不简单。Agent失败通常有两个原因原因说明底层 LLM 能力不足模型本身无法完成复杂推理任务没有传递正确的上下文指令、工具、消息等上下文信息不匹配当前任务大多数情况下是第二个原因——上下文工程不是附加功能而是AI工程师的首要工作。Spring AI Alibaba的Hook和Interceptor抽象专门为此设计。1.2 什么是上下文工程上下文工程是以正确的格式提供正确的信息和工具使LLM能够完成任务。┌─────────────────────────────────────────────────────┐ │ 上下文工程 │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌────────────┐ │ │ │ 模型上下文 │ │ 工具上下文 │ │ 生命周期 │ │ │ │ (瞬态) │ │ (持久) │ │ 上下文 │ │ │ │ │ │ │ │ (持久) │ │ │ │ · SystemPrompt│ │ · 读 State │ │ · 摘要 │ │ │ │ · Messages │ │ · 写 State │ │ · 护栏 │ │ │ │ · Tools │ │ · 读 Store │ │ · 日志 │ │ │ │ · Model │ │ · 写 Store │ │ │ │ │ │ · Response │ │ │ │ │ │ │ └──────────────┘ └──────────────┘ └────────────┘ │ └─────────────────────────────────────────────────────┘2. Agent 循环与上下文类型2.1 Agent 循环┌──────────────────────────────────┐ │ Agent 循环 │ │ │ │ ┌─────────┐ ┌─────────┐ │ │ │ 模型调用 │ ──→ │ 工具执行 │ │ │ │ (Think) │ ←── │ (Act) │ │ │ └─────────┘ └─────────┘ │ │ │ │ │ │ └── 任务完成 → 退出 ─┘ │ └──────────────────────────────────┘2.2 三种上下文类型上下文控制内容生命周期实现机制模型上下文LLM 单次调用看到什么瞬态单次调用ModelInterceptor工具上下文工具能访问和产生什么持久跨轮次ToolContext生命周期上下文模型和工具调用之间发生什么持久跨轮次AgentHook/ModelHook2.3 三种数据源┌──────────────────────────────────────────────────┐ │ 数据源 │ │ │ │ 运行时上下文 State Store │ │ (静态配置) (短期记忆) (长期记忆) │ │ 会话范围 会话范围 跨会话 │ │ │ │ · 用户 ID · 当前消息 · 用户偏好 │ │ · API Key · 上传文件 · 历史数据 │ │ · 权限设置 · 认证状态 · 提取的 │ │ · 环境变量 · 工具结果 见解 │ │ · 数据库连接 │ └──────────────────────────────────────────────────┘3. 模型上下文控制每次模型调用中包含的内容——指令、可用工具、使用哪个模型以及输出格式。3.1 系统提示System Prompt3.1.1 基于状态的动态提示根据对话长度自动调整指令风格classStateAwarePromptInterceptorextendsModelInterceptor{OverridepublicModelResponseinterceptModel(ModelRequestrequest,ModelCallHandlerhandler){ListMessagemessagesrequest.getMessages();StringbasePrompt你是一个有用的助手。;if(messages.size()10){basePrompt\n这是一个长对话 - 请尽量保持精准简捷。;}SystemMessageenhancedMessagerequest.getSystemMessage()null?newSystemMessage(basePrompt):newSystemMessage(request.getSystemMessage().getText()\n\nbasePrompt);ModelRequestenhancedRequestModelRequest.builder(request).systemMessage(enhancedMessage).build();returnhandler.call(enhancedRequest);}}3.1.2 基于存储的个性化提示从长期记忆加载用户偏好构建个性化System PromptclassPersonalizedPromptInterceptorextendsModelInterceptor{privatefinalUserPreferenceStorestore;OverridepublicModelResponseinterceptModel(ModelRequestrequest,ModelCallHandlerhandler){StringuserIdrequest.getContext().get(user-id);// 从运行时上下文获取UserPreferencesprefsstore.getPreferences(userId);// 从 Store 加载偏好StringBuilderpromptnewStringBuilder(你是一个有用的助手。);if(prefs.getCommunicationStyle()!null){prompt.append(\n沟通风格).append(prefs.getCommunicationStyle());}if(prefs.getLanguage()!null){prompt.append(\n使用语言).append(prefs.getLanguage());}SystemMessageenhancedMessagebuildEnhancedMessage(request,prompt.toString());returnhandler.call(ModelRequest.builder(request).systemMessage(enhancedMessage).build());}}调用时通过RunnableConfig传递用户 IDRunnableConfig.builder().metadata(user-id,user123).build()3.2 消息历史Messages3.2.1 消息过滤 —— 限制上下文长度classMessageFilterInterceptorextendsModelInterceptor{privatefinalintmaxMessages;OverridepublicModelResponseinterceptModel(ModelRequestrequest,ModelCallHandlerhandler){ListMessagemessagesrequest.getMessages();if(messages.size()maxMessages){// 保留 SystemMessage 最近的消息ListMessagefilterednewArrayList();messages.stream().filter(m-minstanceofSystemMessage).findFirst().ifPresent(filtered::add);intstartMath.max(0,messages.size()-maxMessages1);filtered.addAll(messages.subList(start,messages.size()));messagesfiltered;}returnhandler.call(ModelRequest.builder(request).messages(messages).build());}}3.2.2 瞬态更新 VS 持久更新方式机制影响范围示例瞬态ModelInterceptor仅本次 LLM 调用消息过滤持久MessagesModelHookUpdatePolicy.REPLACE修改 State 中的消息对话摘要3.3 工具选择Tools按用户角色动态分配工具集classContextualToolInterceptorextendsModelInterceptor{privatefinalMapString,ListToolCallbackroleBasedTools;OverridepublicModelResponseinterceptModel(ModelRequestrequest,ModelCallHandlerhandler){StringuserRolerequest.getContext().getOrDefault(user-role,user);ListToolCallbackallowedToolsroleBasedTools.getOrDefault(userRole,List.of());returnhandler.call(ModelRequest.builder(request).dynamicToolCallbacks(allowedTools).build());}}// 配置MapString,ListToolCallbackroleToolsMap.of(admin,List.of(readTool,writeTool,deleteTool),user,List.of(readTool),guest,List.of());ReactAgentagentReactAgent.builder().interceptors(newContextualToolInterceptor(roleTools)).build();3.4 模型选择Model根据任务复杂度动态切换模型——简单任务用小模型降成本复杂任务用大模型保质量classDynamicModelInterceptorextendsModelInterceptor{privatefinalChatModelsimpleModel;privatefinalChatModelcomplexModel;OverridepublicModelResponseinterceptModel(ModelRequestrequest,ModelCallHandlerhandler){booleanisComplexanalyzeComplexity(request.getMessages());ChatModelselectedModelisComplex?complexModel:simpleModel;returnhandler.call(ModelRequest.builder(request).model(selectedModel).build());}privatebooleananalyzeComplexity(ListMessagemessages){returnmessages.size()5;// 长对话视为复杂任务}}3.5 响应格式Response Format在Agent创建时指定结构化输出ReactAgentagentReactAgent.builder().name(structured_agent).model(chatModel).outputType(MyResponseClass.class)// 或 .outputSchema(jsonSchema).build();4. 工具上下文控制工具对State短期记忆和Store长期记忆的读写权限。4.1 工具中读取状态classStatefulToolimplementsFunctionStatefulTool.Request,StatefulTool.Response{publicrecordRequest(Stringquery){}publicrecordResponse(Stringresult){}OverridepublicResponseapply(Requestrequest,ToolContexttoolContext){// 读取当前 Agent StateOverAllStatestate(OverAllState)toolContext.getContext().get(state);OptionalObjectmessagesstate.value(messages);// 读取运行时配置RunnableConfigconfig(RunnableConfig)toolContext.getContext().get(config);OptionalObjectuserCtxconfig.metadata(user_context_key);returnnewResponse(processWithContext(request.query(),messages,userCtx));}}4.2 工具中修改状态classStateModifyingToolimplementsFunctionStateModifyingTool.Request,StateModifyingTool.Response{publicrecordRequest(Stringdata){}publicrecordResponse(Stringstatus){}OverridepublicResponseapply(Requestrequest,ToolContexttoolContext){// extraState 中的值会被持久化到 State后续 Loop 可见MapString,ObjectextraState(MapString,Object)toolContext.getContext().get(extraState);Stringprocessedprocess(request.data());extraState.put(processed_data,processed);// 持久化到 StatereturnnewResponse(数据已处理并保存到状态);}}5. 生命周期上下文使用Hook在Agent生命周期的关键节点执行操作。5.1 Hook 位置Spring AI Alibaba支持四个Hook位置BEFORE_AGENT AFTER_AGENT │ ▲ ▼ │ ┌──────────────────────────────────────────┴──┐ │ Agent 执行过程 │ │ │ │ ┌──────────┐ ┌──────────┐ │ │ │ 模型调用 │───→│ 工具执行 │───→ ... │ │ └──────────┘ └──────────┘ │ │ ▲ │ │ │ │ │ BEFORE_MODEL AFTER_MODEL │ └──────────────────────────────────────────────┘Hook 位置时机常用于BEFORE_AGENTAgent 启动前查询增强、上下文预加载AFTER_AGENTAgent 结束后结果后处理、清理BEFORE_MODEL每次 LLM 调用前检索、消息摘要、日志AFTER_MODEL每次 LLM 调用后响应验证、日志5.2 日志 HookclassLoggingHookextendsModelHook{OverridepublicHookPosition[]getHookPositions(){returnnewHookPosition[]{HookPosition.BEFORE_MODEL,HookPosition.AFTER_MODEL};}OverridepublicCompletableFutureMapString,ObjectbeforeModel(OverAllStatestate,RunnableConfigconfig){List?messages(List?)state.value(messages).orElse(List.of());System.out.println([BEFORE_MODEL] 消息数: messages.size());returnCompletableFuture.completedFuture(Map.of());}OverridepublicCompletableFutureMapString,ObjectafterModel(OverAllStatestate,RunnableConfigconfig){System.out.println([AFTER_MODEL] 响应已生成);returnCompletableFuture.completedFuture(Map.of());}}5.3 消息摘要 Hook当对话过长时自动生成摘要压缩上下文HookPositions({HookPosition.BEFORE_MODEL})classSummarizationHookextendsMessagesModelHook{privatefinalChatModelsummarizationModel;privatefinalinttriggerLength;OverridepublicAgentCommandbeforeModel(ListMessagepreviousMessages,RunnableConfigconfig){if(previousMessages.size()triggerLength){returnnewAgentCommand(previousMessages);// 未超阈值不处理}// 1. 生成摘要StringsummarygenerateSummary(previousMessages);// 2. 保留 SystemMessage 摘要 最近消息ListMessagenewMessagesnewArrayList();previousMessages.stream().filter(m-minstanceofSystemMessage).forEach(newMessages::add);newMessages.add(newUserMessage(【上下文摘要】之前的对话摘要summary));intrecentCountMath.min(5,previousMessages.size());newMessages.addAll(previousMessages.subList(previousMessages.size()-recentCount,previousMessages.size()));returnnewAgentCommand(newMessages,UpdatePolicy.REPLACE);}}6. 完整示例综合上下文工程将多种上下文工程手段组合到一个Agent中ConfigurationpublicclassContextEngineeringConfig{BeanpublicReactAgentcontextEngineeredAgent(ChatModelchatModel,ChatModelsummaryModel,UserPreferenceStorestore){returnReactAgent.builder().name(context_engineered_agent).model(chatModel).description(综合上下文工程示例).instruction(你是一个智能助手会根据用户偏好调整回复风格。)// 个性化 System Prompt.interceptors(newPersonalizedPromptInterceptor(store),// 从 Store 加载偏好newMessageFilterInterceptor(20),// 限制消息数量newContextualToolInterceptor(getRoleTools()),// 按角色分配工具newAnswerValidationInterceptor()// 答案验证)// 生命周期 Hook.hooks(newSummarizationHook(summaryModel,15),// 长对话摘要newLoggingHook()// 日志).saver(newMemorySaver()).enableLogging(true).build();}}7. Hook 与 Interceptor 选择指南需求使用原因修改单次 LLM 调用的内容ModelInterceptor瞬态不影响 State持久修改消息列表MessagesModelHookREPLACE修改 State 中的消息Agent 开始前预处理AgentHook/BEFORE_AGENT只执行一次按角色/用户动态调整ModelInterceptor 读取request.getContext()访问运行时上下文工具读写 StateToolContext工具内访问持久状态对话摘要/压缩MessagesModelHookREPLACE持久更新消息列表日志/监控ModelHook/AgentHook不修改上下文只观察8. 瞬态 vs 持久 决策树要修改的内容需要在后续的 Agent 推理轮次中可见吗 │ ├── 否只影响本次 LLM 调用→ 使用 ModelInterceptor │ 示例消息过滤、动态工具选择、动态 Prompt │ └── 是需要持久保存→ 使用 Hook REPLACE 或 ToolContext 示例对话摘要、工具写入状态