跳到主要内容

上下文工程(Context Engineering)

概述

构建 Agent 的难点在于使其足够可靠、效果足够好。虽然我们可以很容易写一个 Agent 示例,但要做一个能在生产环境中稳定使用、能解决实际问题的 Agent 并不容易。

为什么 Agent 会失败?

当 Agent 失败时,通常是因为 Agent 内部的 LLM 调用采取了错误的操作或者没有按我们预期的执行。LLM 失败的原因有两个:

  1. 底层 LLM 能力不足
  2. 没有向 LLM 传递"正确"的上下文

大多数情况下 —— 实际上是第二个原因导致 Agent 不可靠。

上下文工程是以正确的格式提供正确的信息和工具,使 LLM 能够完成任务。这是 AI 工程师的首要工作。缺乏"正确"的上下文是更可靠 Agent 的头号障碍,Spring AI Alibaba 的 Agent 抽象专门设计用于优化上下文工程。

Agent 循环

典型的 Agent 循环由两个主要步骤组成:

  1. 模型调用 - 使用提示和可用工具调用 LLM,返回响应或执行工具的请求
  2. 工具执行 - 执行 LLM 请求的工具,返回工具结果

reactagent

此循环持续进行,直到 LLM 决定任务完成并退出。

你可以控制什么

要构建可靠的 Agent,你需要控制 Agent 循环每个步骤发生的事情,以及步骤之间发生的事情。

上下文类型你控制的内容瞬态或持久
模型上下文模型调用中包含什么(指令、消息历史、工具、响应格式)瞬态
工具上下文工具可以访问和产生什么(对状态、存储、运行时上下文的读/写)持久
生命周期上下文模型和工具调用之间发生什么(摘要、防护栏、日志等)持久
  • 瞬态上下文。LLM 在单次调用中看到的内容。你可以修改消息、工具或提示,而不改变状态中保存的内容。
  • 持久上下文。跨轮次保存在状态中的内容。生命周期钩子和工具写入会永久修改它。

数据源

在整个过程中,你的 Agent 访问(读/写)不同的数据源:

数据源别名范围示例
运行时上下文静态配置会话范围用户 ID、API 密钥、数据库连接、权限、环境设置
状态(State)短期记忆会话范围当前消息、上传的文件、认证状态、工具结果
存储(Store)长期记忆跨会话用户偏好、提取的见解、记忆、历史数据

工作原理

在 Spring AI Alibaba 中,HookInterceptor是实现上下文工程的机制。

它们允许你挂接到 Agent 生命周期的任何步骤并:

  • 更新上下文
  • 跳转到 Agent 生命周期的不同步骤

在本指南中,你将看到频繁使用 Hook 和 Interceptor API 作为上下文工程的手段。

模型上下文(Model Context)

控制每次模型调用中包含的内容——指令、可用工具、使用哪个模型以及输出格式。这些决策直接影响可靠性和成本。

💬
系统提示
开发者对LLM的基础指令。
💭
消息
发送给LLM的完整消息列表(对话历史)。
🔧
工具
Agent可以访问以采取行动的工具。
🧠
模型
要调用的实际模型(包括配置)。
📋
响应格式
模型最终响应的架构规范。

所有这些类型的模型上下文都可以从状态(短期记忆)、存储(长期记忆)或运行时上下文(静态配置)中获取。

系统提示(System Prompt)

系统提示设置 LLM 的行为和能力。不同的用户、上下文或对话阶段需要不同的指令。成功的 Agent 利用记忆、偏好和配置为对话的当前状态提供正确的指令。

基于状态的动态提示

import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.agent.interceptor.ModelInterceptor;
import com.alibaba.cloud.ai.graph.agent.interceptor.ModelRequest;
import com.alibaba.cloud.ai.graph.agent.interceptor.ModelResponse;
import org.springframework.ai.chat.messages.Message;

// 创建一个模型拦截器,根据对话长度调整系统提示
public class StateAwarePromptInterceptor implements ModelInterceptor {

@Override
public ModelResponse intercept(ModelRequest request, ModelCallHandler next) {
List<Message> messages = request.getMessages();
int messageCount = messages.size();

// 基础提示
String basePrompt = "你是一个有用的助手。";

// 根据消息数量调整提示
if (messageCount > 10) {
basePrompt += "\n这是一个长对话 - 请尽量保持精准简捷。";
}

// 更新系统消息
messages = updateSystemMessage(messages, basePrompt);

// 创建新的请求并继续
ModelRequest updatedRequest = ModelRequest.builder(request)
.messages(messages)
.build();

return next.call(updatedRequest);
}

private List<Message> updateSystemMessage(List<Message> messages, String newPrompt) {
// 实现更新系统消息的逻辑
return messages;
}
}

// 使用拦截器创建Agent
ReactAgent agent = ReactAgent.builder()
.name("context_aware_agent")
.model(chatModel)
.interceptors(new StateAwarePromptInterceptor())
.build();

基于存储的个性化提示

// 从长期记忆加载用户偏好
public class PersonalizedPromptInterceptor implements ModelInterceptor {

private final UserPreferenceStore store;

public PersonalizedPromptInterceptor(UserPreferenceStore store) {
this.store = store;
}

@Override
public ModelResponse intercept(ModelRequest request, ModelCallHandler next) {
// 从运行时上下文获取用户ID
String userId = getUserIdFromContext(request.context());

// 从存储加载用户偏好
UserPreferences prefs = store.getPreferences(userId);

// 构建个性化提示
String systemPrompt = buildPersonalizedPrompt(prefs);

// 更新请求
List<Message> updatedMessages = updateSystemMessage(
request.getMessages(),
systemPrompt
);

ModelRequest updatedRequest = ModelRequest.builder(request)
.messages(updatedMessages)
.build();

return next.call(updatedRequest);
}

private String buildPersonalizedPrompt(UserPreferences prefs) {
StringBuilder prompt = new StringBuilder("你是一个有用的助手。");

if (prefs.getCommunicationStyle() != null) {
prompt.append("\n沟通风格:").append(prefs.getCommunicationStyle());
}

if (prefs.getLanguage() != null) {
prompt.append("\n使用语言:").append(prefs.getLanguage());
}

if (!prefs.getInterests().isEmpty()) {
prompt.append("\n用户兴趣:").append(String.join(", ", prefs.getInterests()));
}

return prompt.toString();
}
}

消息历史(Messages)

控制发送给 LLM 的消息列表。你可以:

  • 过滤或修改消息
  • 添加上下文或摘要
  • 压缩长对话

消息过滤

public class MessageFilterInterceptor implements ModelInterceptor {

private final int maxMessages;

public MessageFilterInterceptor(int maxMessages) {
this.maxMessages = maxMessages;
}

@Override
public ModelResponse intercept(ModelRequest request, ModelCallHandler next) {
List<Message> messages = request.getMessages();

// 只保留最近的N条消息
if (messages.size() > maxMessages) {
// 保留系统消息和最近的用户消息
List<Message> filtered = new ArrayList<>();

// 添加系统消息
messages.stream()
.filter(m -> m instanceof SystemMessage)
.findFirst()
.ifPresent(filtered::add);

// 添加最近的消息
filtered.addAll(messages.subList(
messages.size() - maxMessages + 1,
messages.size()
));

messages = filtered;
}

ModelRequest updatedRequest = ModelRequest.builder(request)
.messages(messages)
.build();

return next.call(updatedRequest);
}
}

瞬时消息更新 VS 持久消息更新

  1. 上述示例使用 ModelInterceptor 来实现临时更新 —— 修改单次调用时发送给模型的消息内容,而不会改变状态中保存的数据。
  2. 对于需要持久更新状态的情况(例如生命周期上下文中的摘要示例),请使用如 ModelHook 等生命周期钩子来永久更新对话历史。更多详情请参阅 Hook & Interceptor 文档。

工具(Tools)

动态控制 Agent 可以访问哪些工具。

基于上下文的工具选择

public class ContextualToolInterceptor implements ModelInterceptor {

private final Map<String, List<ToolCallback>> roleBasedTools;

@Override
public ModelResponse intercept(ModelRequest request, ModelCallHandler next) {
// 从上下文获取用户角色
String userRole = getUserRole(request);

// 根据角色选择工具
List<ToolCallback> allowedTools = roleBasedTools.getOrDefault(
userRole,
Collections.emptyList()
);

// 更新工具选项
ToolCallingChatOptions updatedOptions = ToolCallingChatOptions.builder()
.toolCallbacks(allowedTools)
.build();

ModelRequest updatedRequest = ModelRequest.builder(request)
.options(updatedOptions)
.build();

return next.call(updatedRequest);
}
}

// 配置基于角色的工具
Map<String, List<ToolCallback>> roleTools = Map.of(
"admin", List.of(readTool, writeTool, deleteTool),
"user", List.of(readTool),
"guest", List.of()
);

ReactAgent agent = ReactAgent.builder()
.name("role_based_agent")
.model(chatModel)
.interceptors(new ContextualToolInterceptor(roleTools))
.build();

模型选择(Model)

根据任务复杂度或用户偏好动态选择模型。

public class DynamicModelInterceptor implements ModelInterceptor {

private final ChatModel simpleModel;
private final ChatModel complexModel;

@Override
public ModelResponse intercept(ModelRequest request, ModelCallHandler next) {
// 分析任务复杂度
boolean isComplexTask = analyzeComplexity(request.getMessages());

// 选择合适的模型
ChatModel selectedModel = isComplexTask ? complexModel : simpleModel;

// 注意:在实际实现中,你可能需要在Agent级别切换模型
// 这里展示的是概念性示例

return next.call(request);
}

private boolean analyzeComplexity(List<Message> messages) {
// 实现复杂度分析逻辑
// 例如:检查消息长度、关键词等
return messages.size() > 5;
}
}

响应格式(Response Format)

使用结构化输出控制模型响应格式。

// 在Agent级别设置输出格式
ReactAgent agent = ReactAgent.builder()
.name("structured_agent")
.model(chatModel)
.outputType(MyResponseClass.class) // 或 .outputSchema(jsonSchema)
.build();

// 也可以在Interceptor中动态调整
public class DynamicFormatInterceptor implements ModelInterceptor {

@Override
public ModelResponse intercept(ModelRequest request, ModelCallHandler next) {
// 根据请求内容决定输出格式
String outputSchema = determineOutputSchema(request);

// 在消息中添加格式说明
List<Message> updatedMessages = addFormatInstructions(
request.getMessages(),
outputSchema
);

ModelRequest updatedRequest = ModelRequest.builder(request)
.messages(updatedMessages)
.build();

return next.call(updatedRequest);
}
}

工具上下文(Tool Context)

控制工具可以访问和修改的内容。

工具中访问状态

public class StatefulTool implements Function<StatefulTool.Request, StatefulTool.Response> {

public record Request(String query) {}
public record Response(String result) {}

@Override
public Response apply(Request request, ToolContext toolContext) {
// 从 Agent 持久状态读取信息
OverAllState currentState = (OverAllState) toolContext.getContext().get("state");
// 'messages' can be any key persisted in short memory
Optional<Object> messages = currentState.value("messages");

// 从 Agent 运行上下文读取信息
RunnableConfig config = (OverAllState) toolContext.getContext().get("config");
Optional<Object> userContext = config.metadata("user_context_key");

// 使用状态信息处理请求
String result = processWithContext(request.query(), messages, userContext);

return new Response(result);
}
}

工具修改状态

public class StateModifyingTool implements Function<StateModifyingTool.Request, StateModifyingTool.Response> {

public record Request(String data) {}
public record Response(String status) {}

@Override
public Response apply(Request request) {
// 从 Agent 持久状态读取信息
Map<String, Object> extraState = (OverAllState) toolContext.getContext().get("extraState");

// 处理数据
String processed = process(request.data());

// extraState 是一个特殊设计,更新到 extraState 中的值会被持久化到 State 状态中,并被后续的 Loop 节点看到。
extraState.put("processed_data", processed);

return new Response("数据已处理并保存到状态");
}
}

生命周期上下文(Lifecycle Context)

使用 Hook 在 Agent 生命周期的不同阶段执行操作。

Hook 位置

Spring AI Alibaba 支持以下 Hook 位置:

  • BEFORE_AGENT - Agent 开始之前
  • AFTER_AGENT - Agent 完成之后
  • BEFORE_MODEL - 模型调用之前
  • AFTER_MODEL - 模型调用之后

自定义 Hook 示例

import com.alibaba.cloud.ai.graph.agent.hook.Hook;
import com.alibaba.cloud.ai.graph.agent.hook.HookPosition;
import com.alibaba.cloud.ai.graph.agent.hook.ModelHook;

public class LoggingHook implements ModelHook {

@Override
public String getName() {
return "logging_hook";
}

@Override
public HookPosition[] getHookPositions() {
return new HookPosition[]{
HookPosition.BEFORE_MODEL,
HookPosition.AFTER_MODEL
};
}

@Override
public Map<String, Object> beforeModel(OverAllState state, RunnableConfig config) {
// 在模型调用前记录
System.out.println("模型调用前 - 消息数: " +
((List<?>) state.value("messages").get()).size());
return Map.of();
}

@Override
public Map<String, Object> afterModel(OverAllState state, RunnableConfig config) {
// 在模型调用后记录
System.out.println("模型调用后 - 响应已生成");
return Map.of();
}
}

// 使用Hook
ReactAgent agent = ReactAgent.builder()
.name("logged_agent")
.model(chatModel)
.hooks(new LoggingHook())
.build();

消息摘要 Hook

public class SummarizationHook implements ModelHook {

private final ChatModel summarizationModel;
private final int triggerLength;

@Override
public Map<String, Object> beforeModel(OverAllState state, RunnableConfig config) {
List<Message> messages = (List<Message>) state.value("messages").get();

if (messages.size() > triggerLength) {
// 生成对话摘要
String summary = generateSummary(messages);

// 用摘要替换旧消息
List<Message> newMessages = new ArrayList<>();
newMessages.add(new SystemMessage("之前对话摘要:" + summary));
newMessages.addAll(messages.subList(messages.size() - 5, messages.size()));

return Map.of("messages", newMessages);
}

return Map.of();
}

private String generateSummary(List<Message> messages) {
// 使用另一个模型生成摘要
String conversation = messages.stream()
.map(Message::getContent)
.collect(Collectors.joining("\n"));

return chatClient.prompt()
.system("请总结以下对话的要点")
.user(conversation)
.call()
.content();
}
}

相关文档

Spring AI Alibaba 开源项目基于 Spring AI 构建,是阿里云通义系列模型及服务在 Java AI 应用开发领域的最佳实践,提供高层次的 AI API 抽象与云原生基础设施集成方案,帮助开发者快速构建 AI 应用。