Spring AI Alibaba 上下文工程(Context Engineering)深度实战

Spring AI Alibaba 上下文工程(Context Engineering)深度实战

基于 Spring AI Alibaba 1.1.2.0,深入理解上下文工程的核心概念,并通过一个完整的智能客服工单系统示例,展示如何利用 Interceptor 和 Hook 动态控制 Agent 的模型上下文、工具上下文和生命周期上下文


Spring AI Alibaba Agent Framework Hooks 与 Interceptors 完整指南:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/162344775

一、什么是上下文工程?

构建一个能在生产环境中稳定运行、能解决实际问题的 Agent 并不容易。当 Agent 失败时,通常有两个原因:

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

绝大多数情况下,第二个原因 是罪魁祸首。上下文工程正是以正确的格式 提供正确的信息和工具,使 LLM 能够完成任务的工程实践。

Spring AI Alibaba 的 Agent 抽象专门设计用于优化上下文工程,通过 HookInterceptor 机制挂接到 Agent 生命周期的任何步骤,实现上下文的动态更新与控制。


二、核心概念:三种上下文类型

上下文类型 控制内容 瞬态/持久
模型上下文(Model Context) 模型调用中包含什么(指令、消息历史、工具、响应格式) 瞬态(仅影响当前调用)
工具上下文(Tool Context) 工具可以访问和产生什么(对状态、存储、运行时上下文的读/写) 持久(跨轮次)
生命周期上下文(Lifecycle Context) 模型和工具调用之间发生什么(摘要、防护栏、日志等) 持久

2.1 瞬态 vs 持久

  • 瞬态上下文 :LLM 在单次调用 中看到的内容。可以修改消息、工具或提示,而不改变状态 中保存的内容。使用 Interceptor 实现。
  • 持久上下文跨轮次 保存在状态中的内容。生命周期钩子和工具写入会永久修改它。使用 Hook 实现。

2.2 数据源

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

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

2.3 实现机制

机制 作用 修改类型
Interceptor 拦截和修改模型请求/响应,适合瞬态上下文的动态调整 瞬态
Hook 在 Agent 生命周期的特定节点执行操作,适合持久上下文的修改 持久

注:

博客:

https://blog.csdn.net/badao_liumang_qizhi

三、控制模型上下文(瞬态)

通过 ModelInterceptor 控制每次模型调用中包含的内容------指令、可用工具、使用哪个模型以及输出格式。

3.1 动态系统提示

根据对话状态或用户身份实时调整系统提示:

java 复制代码
class DynamicPromptInterceptor extends ModelInterceptor {
    @Override
    public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {
        String role = (String) request.getContext().get("userRole");
        int msgCount = request.getMessages().size();

        String prompt = "你是一个有用的助手。";
        if ("VIP".equals(role)) prompt += " 用户是VIP,请提供优先服务。";
        if (msgCount > 10) prompt += " 长对话,请简洁回复。";

        SystemMessage enhancedSystem = new SystemMessage(prompt);
        ModelRequest enhanced = ModelRequest.builder(request)
            .systemMessage(enhancedSystem)
            .build();
        return handler.call(enhanced);
    }
}

3.2 动态工具选择

根据用户角色决定 Agent 可以访问哪些工具(这是上下文工程最常见的实践):

java 复制代码
class RoleBasedToolInterceptor extends ModelInterceptor {
    @Override
    public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {
        UserRole role = getUserRole(request);
        List<ToolCallback> allowedTools = getToolsForRole(role);
        ModelRequest enhanced = ModelRequest.builder(request)
            .dynamicToolCallbacks(allowedTools)  // 本次调用生效
            .build();
        return handler.call(enhanced);
    }
}

3.3 消息过滤

在单次调用中限制消息数量,防止上下文过长(但不修改持久状态):

java 复制代码
class MessageFilterInterceptor extends ModelInterceptor {
    private final int maxMessages = 10;

    @Override
    public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {
        List<Message> messages = request.getMessages();
        if (messages.size() > maxMessages) {
            messages = messages.subList(messages.size() - maxMessages, messages.size());
        }
        ModelRequest enhanced = ModelRequest.builder(request)
            .messages(messages)
            .build();
        return handler.call(enhanced);
    }
}

四、控制工具上下文(持久)

工具可以读取和修改 Agent 的持久状态和运行时上下文。

4.1 工具中访问状态

通过 ToolContext 读取 Agent 的持久状态:

java 复制代码
class StatefulTool implements BiFunction<String, ToolContext, String> {
    @Override
    public String apply(String query, ToolContext toolContext) {
        OverAllState state = (OverAllState) toolContext.getContext().get("state");
        Optional<Object> messages = state.value("messages");
        // 使用状态信息处理...
        return "处理结果";
    }
}

4.2 工具修改状态

工具可以将处理结果持久化到状态中,供后续步骤使用:

java 复制代码
class StateModifyingTool implements BiFunction<Map<String, Object>, ToolContext, String> {
    @Override
    public String apply(Map<String, Object> request, ToolContext toolContext) {
        Map<String, Object> extraState = (Map<String, Object>) toolContext.getContext().get("extraState");
        extraState.put("processed_data", process(request));
        return "数据已保存";
    }
}

五、控制生命周期上下文(持久)

使用 Hook 在 Agent 生命周期的关键节点执行操作。

5.1 Hook 位置

  • BEFORE_AGENT / AFTER_AGENT
  • BEFORE_MODEL / AFTER_MODEL

5.2 消息摘要 Hook(压缩上下文)

当消息数量超过阈值时,自动生成摘要并替换历史消息(持久修改):

java 复制代码
@HookPositions({HookPosition.BEFORE_MODEL})
class SummarizationHook extends MessagesModelHook {
    private final int triggerLength = 8;

    @Override
    public AgentCommand beforeModel(List<Message> previousMessages, RunnableConfig config) {
        if (previousMessages.size() <= triggerLength) {
            return new AgentCommand(previousMessages);
        }
        // 生成摘要...
        String summary = generateSummary(previousMessages);
        // 构建新消息列表:保留系统消息 + 摘要 + 最近5条
        List<Message> newMessages = new ArrayList<>();
        newMessages.add(systemMsg);
        newMessages.add(new UserMessage("【摘要】" + summary));
        newMessages.addAll(recentMessages);
        return new AgentCommand(newMessages, UpdatePolicy.REPLACE);
    }
}

六、完整实战示例:智能客服工单系统

我们将构建一个电商智能客服系统,根据不同用户身份(普通用户/VIP/管理员)动态控制 Agent 的行为,展示上下文工程的核心能力。

6.1 业务需求

  • 普通用户:只能查询订单、提交售后咨询
  • VIP 用户:额外可查询积分、申请会员升级
  • 管理员:拥有全部权限,可查询所有订单、处理退款
  • 对话过长时自动压缩上下文

6.2 项目结构

复制代码
spring-ai-context-engineering-demo/
├── pom.xml
├── src/main/
│   ├── java/com/example/ai/
│   │   ├── ContextEngineeringDemoApplication.java
│   │   ├── config/
│   │   │   └── AgentConfig.java
│   │   ├── controller/
│   │   │   └── CustomerController.java
│   │   ├── service/
│   │   │   └── CustomerService.java
│   │   ├── model/
│   │   │   ├── UserRole.java
│   │   │   └── UserContext.java
│   │   ├── tool/
│   │   │   └── CustomerServiceTools.java
│   │   ├── interceptor/
│   │   │   ├── ContextAwareToolInterceptor.java
│   │   │   └── DynamicPromptInterceptor.java
│   │   └── hook/
│   │       └── SummarizationHook.java
│   └── resources/
│       └── application.yml

6.3 完整代码

pom.xml
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/>
    </parent>
    <groupId>com.example.ai</groupId>
    <artifactId>spring-ai-context-engineering-demo</artifactId>
    <version>1.0.0</version>

    <properties>
        <java.version>17</java.version>
        <spring-ai-alibaba.version>1.1.2.0</spring-ai-alibaba.version>
        <jackson.version>2.17.2</jackson.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.fasterxml.jackson</groupId>
                <artifactId>jackson-bom</artifactId>
                <version>${jackson.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-agent-framework</artifactId>
            <version>${spring-ai-alibaba.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
            <version>${spring-ai-alibaba.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
application.yml
yaml 复制代码
server:
  port: 885

spring:
  ai:
    dashscope:
      api-key: ${DASHSCOPE_API_KEY}
    chat:
      options:
        model: deepseek-v4-flash

logging:
  level:
    com.alibaba.cloud.ai: debug
    com.example.ai: debug
用户角色枚举
java 复制代码
package com.example.ai.model;

public enum UserRole {
    GUEST,   // 游客:只能咨询
    USER,    // 普通用户
    VIP,     // 会员用户
    ADMIN    // 管理员
}
复制代码
package com.badao.ai.model;

import java.util.List;

public class UserContext{
    private String userId;
    private UserRole role;
    private String name;
    private List<String> orderIds;
    private int vipPoints;

    public UserContext(String userId, UserRole role, String name, List<String> orderIds, int vipPoints) {
        this.userId = userId;
        this.role = role;
        this.name = name;
        this.orderIds = orderIds;
        this.vipPoints = vipPoints;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public UserRole getRole() {
        return role;
    }

    public void setRole(UserRole role) {
        this.role = role;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getOrderIds() {
        return orderIds;
    }

    public void setOrderIds(List<String> orderIds) {
        this.orderIds = orderIds;
    }

    public int getVipPoints() {
        return vipPoints;
    }

    public void setVipPoints(int vipPoints) {
        this.vipPoints = vipPoints;
    }
}
工具定义
java 复制代码
package com.example.ai.tool;

import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.function.FunctionToolCallback;
import com.example.ai.model.UserRole;
import java.util.ArrayList;
import java.util.List;

public class CustomerServiceTools {

    // ===== 所有用户可用 =====
    public static ToolCallback queryOrderTool() {
        return FunctionToolCallback.builder("query_order", (String orderId) -> {
            return "订单 " + orderId + " 状态:已发货,预计 3 天后送达。";
        }).description("根据订单号查询订单状态和物流信息").inputType(String.class).build();
    }

    public static ToolCallback consultAfterSalesTool() {
        return FunctionToolCallback.builder("consult_after_sales", (String question) -> {
            return "售后咨询已记录,客服将在 24 小时内回复您。问题:" + question;
        }).description("提交售后咨询问题").inputType(String.class).build();
    }

    // ===== VIP 会员专属 =====
    public static ToolCallback queryPointsTool() {
        return FunctionToolCallback.builder("query_points", (String userId) -> {
            return "用户 " + userId + " 当前积分:2580 分";
        }).description("查询用户积分余额").inputType(String.class).build();
    }

    public static ToolCallback applyUpgradeTool() {
        return FunctionToolCallback.builder("apply_upgrade", (String userId) -> {
            return "用户 " + userId + " 的会员升级申请已提交,预计 1 个工作日内审核。";
        }).description("提交会员升级申请").inputType(String.class).build();
    }

    // ===== 管理员专属 =====
    public static ToolCallback queryAllOrdersTool() {
        return FunctionToolCallback.builder("query_all_orders", (String userId) -> {
            return "用户 " + userId + " 的所有订单:ORD-001(已签收),ORD-002(运输中)";
        }).description("查询指定用户的所有订单").inputType(String.class).build();
    }

    public static ToolCallback processRefundTool() {
        return FunctionToolCallback.builder("process_refund", (String orderId) -> {
            return "订单 " + orderId + " 退款已处理,金额 299.00 元将在 3 个工作日内退回。";
        }).description("处理退款申请").inputType(String.class).build();
    }

    /**
     * 根据用户角色获取可用工具列表
     */
    public static List<ToolCallback> getToolsForRole(UserRole role) {
        List<ToolCallback> tools = new ArrayList<>();
        tools.add(queryOrderTool());
        tools.add(consultAfterSalesTool());

        if (role == UserRole.VIP || role == UserRole.ADMIN) {
            tools.add(queryPointsTool());
            tools.add(applyUpgradeTool());
        }

        if (role == UserRole.ADMIN) {
            tools.add(queryAllOrdersTool());
            tools.add(processRefundTool());
        }
        return tools;
    }
}
核心 Interceptor:动态工具选择
java 复制代码
package com.example.ai.interceptor;

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 com.alibaba.cloud.ai.graph.agent.interceptor.ModelCallHandler;
import com.example.ai.model.UserRole;
import com.example.ai.tool.CustomerServiceTools;
import org.springframework.ai.tool.ToolCallback;
import java.util.List;

/**
 * 上下文工程核心组件:根据用户角色动态选择可用工具
 */
public class ContextAwareToolInterceptor extends ModelInterceptor {

    @Override
    public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {
        String roleStr = (String) request.getContext().getOrDefault("userRole", "GUEST");
        UserRole role = UserRole.valueOf(roleStr);
        List<ToolCallback> roleTools = CustomerServiceTools.getToolsForRole(role);
        System.out.println("🔧 用户角色:" + role + ",可用工具数:" + roleTools.size());

        ModelRequest enhancedRequest = ModelRequest.builder(request)
            .dynamicToolCallbacks(roleTools)  // 仅本次调用生效
            .build();
        return handler.call(enhancedRequest);
    }

    @Override
    public String getName() {
        return "ContextAwareToolInterceptor";
    }
}
动态提示 Interceptor
java 复制代码
package com.example.ai.interceptor;

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 com.alibaba.cloud.ai.graph.agent.interceptor.ModelCallHandler;
import com.example.ai.model.UserRole;
import org.springframework.ai.chat.messages.SystemMessage;

public class DynamicPromptInterceptor extends ModelInterceptor {

    @Override
    public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {
        String roleStr = (String) request.getContext().getOrDefault("userRole", "GUEST");
        UserRole role = UserRole.valueOf(roleStr);
        int messageCount = request.getMessages().size();

        StringBuilder prompt = new StringBuilder();
        prompt.append("你是一个专业的电商客服助手。请用友好、专业的态度回复用户。");

        if (role == UserRole.VIP) {
            prompt.append(" 用户是VIP会员,请提供优先服务。");
        } else if (role == UserRole.ADMIN) {
            prompt.append(" 用户是管理员,请提供完整的后台数据。");
        }

        if (messageCount > 10) {
            prompt.append(" 这是一个长对话,请保持回复简洁精准。");
        }

        SystemMessage enhancedSystem = new SystemMessage(prompt.toString());
        ModelRequest enhanced = ModelRequest.builder(request)
            .systemMessage(enhancedSystem)
            .build();
        return handler.call(enhanced);
    }

    @Override
    public String getName() {
        return "DynamicPromptInterceptor";
    }
}
摘要 Hook(持久上下文压缩)
java 复制代码
package com.example.ai.hook;

import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.hook.HookPosition;
import com.alibaba.cloud.ai.graph.agent.hook.HookPositions;
import com.alibaba.cloud.ai.graph.agent.hook.messages.AgentCommand;
import com.alibaba.cloud.ai.graph.agent.hook.messages.MessagesModelHook;
import com.alibaba.cloud.ai.graph.agent.hook.messages.UpdatePolicy;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@HookPositions({HookPosition.BEFORE_MODEL})
public class SummarizationHook extends MessagesModelHook {

    private static final Logger log = LoggerFactory.getLogger(SummarizationHook.class);
    private final int triggerLength;

    public SummarizationHook(int triggerLength) {
        this.triggerLength = triggerLength;
    }

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

    @Override
    public AgentCommand beforeModel(List<Message> previousMessages, RunnableConfig config) {
        if (previousMessages.size() <= triggerLength) {
            return new AgentCommand(previousMessages);
        }

        log.info("📝 消息数量 {} 超过阈值 {},触发上下文压缩", previousMessages.size(), triggerLength);

        // 提取对话历史文本(简化摘要)
        String history = previousMessages.stream()
            .filter(m -> !(m instanceof SystemMessage))
            .map(Message::getText)
            .collect(Collectors.joining("\n"));

        // 保留系统消息
        SystemMessage systemMsg = (SystemMessage) previousMessages.stream()
            .filter(m -> m instanceof SystemMessage)
            .findFirst().orElse(null);

        // 创建摘要消息(实际可用 LLM 生成)
        String summary = "【对话摘要】用户与客服进行了多轮对话,涉及订单查询和售后咨询。";
        UserMessage summaryMsg = new UserMessage(summary);

        // 保留最近5条
        int keep = Math.min(5, previousMessages.size());
        List<Message> recent = previousMessages.subList(previousMessages.size() - keep, previousMessages.size());

        // 构建新消息列表
        List<Message> newMessages = new ArrayList<>();
        if (systemMsg != null) newMessages.add(systemMsg);
        newMessages.add(summaryMsg);
        newMessages.addAll(recent);

        log.info("📝 压缩后消息数:{} → {}", previousMessages.size(), newMessages.size());
        return new AgentCommand(newMessages, UpdatePolicy.REPLACE);
    }
}
Agent 配置
java 复制代码
package com.example.ai.config;

import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;
import com.example.ai.hook.SummarizationHook;
import com.example.ai.interceptor.ContextAwareToolInterceptor;
import com.example.ai.interceptor.DynamicPromptInterceptor;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AgentConfig {

    @Bean
    public ReactAgent customerServiceAgent(ChatModel chatModel) {
        return ReactAgent.builder()
                .name("customer_service_agent")
                .model(chatModel)
                .saver(new MemorySaver())
                .interceptors(
                        new ContextAwareToolInterceptor(),   // 瞬态:动态工具
                        new DynamicPromptInterceptor()       // 瞬态:动态提示
                )
                .hooks(
                        new SummarizationHook(8)            // 持久:消息压缩
                )
                .build();
    }
}
Service 层
java 复制代码
package com.example.ai.service;

import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.exception.GraphRunnerException;
import com.example.ai.model.UserRole;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.stereotype.Service;

@Service
public class CustomerService {

    private final ReactAgent customerServiceAgent;

    public CustomerService(ReactAgent customerServiceAgent) {
        this.customerServiceAgent = customerServiceAgent;
    }

    public String chat(String userMessage, String sessionId, UserRole role, String userId) {
        RunnableConfig config = RunnableConfig.builder()
                .threadId(sessionId)
                .addMetadata("userRole", role.name())
                .addMetadata("userId", userId)
                .build();

        try {
            AssistantMessage response = customerServiceAgent.call(userMessage, config);
            return response.getText();
        } catch (GraphRunnerException e) {
            throw new RuntimeException("客服系统暂时不可用:" + e.getMessage(), e);
        }
    }
}
Controller
java 复制代码
package com.example.ai.controller;

import com.example.ai.model.UserRole;
import com.example.ai.service.CustomerService;
import org.springframework.web.bind.annotation.*;
import java.util.Map;

@RestController
@RequestMapping("/api/customer")
public class CustomerController {

    private final CustomerService customerService;

    public CustomerController(CustomerService customerService) {
        this.customerService = customerService;
    }

    @PostMapping("/chat")
    public Map<String, Object> chat(
            @RequestParam String message,
            @RequestParam(required = false, defaultValue = "default") String sessionId,
            @RequestParam(defaultValue = "GUEST") String role,
            @RequestParam(required = false, defaultValue = "anonymous") String userId) {

        UserRole userRole = UserRole.valueOf(role.toUpperCase());
        String response = customerService.chat(message, sessionId, userRole, userId);

        return Map.of(
                "success", true,
                "response", response,
                "sessionId", sessionId,
                "role", userRole.name()
        );
    }
}
启动类
java 复制代码
package com.example.ai;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ContextEngineeringDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(ContextEngineeringDemoApplication.class, args);
    }
}

七、测试与验证

7.1 启动应用

bash 复制代码
export DASHSCOPE_API_KEY="你的API密钥"
mvn spring-boot:run

7.2 测试不同角色的工具权限

bash 复制代码
# 普通用户:只有基础工具
curl -X POST "http://localhost:885/api/customer/chat?message=查询订单ORD-001&sessionId=test&role=USER&userId=u001"

# VIP 用户:多了积分和升级工具
curl -X POST "http://localhost:885/api/customer/chat?message=查询我的积分&sessionId=test&role=VIP&userId=u001"

# 管理员:拥有全部工具
curl -X POST "http://localhost:885/api/customer/chat?message=处理退款ORD-002&sessionId=test&role=ADMIN&userId=admin"

观察日志

复制代码
🔧 用户角色:USER,可用工具数:2
🔧 用户角色:VIP,可用工具数:4
🔧 用户角色:ADMIN,可用工具数:6

7.3 测试长对话压缩

连续发送 10 条消息后,触发摘要 Hook:

bash 复制代码
for i in {1..10}; do
  curl -X POST "http://localhost:885/api/customer/chat?message=消息$i&sessionId=test&role=USER&userId=u001"
done

观察日志

复制代码
📝 消息数量 9 超过阈值 8,触发上下文压缩
📝 压缩后消息数:9 → 7

7.4 验证动态提示

询问 Agent 对用户的认知,不同角色应得到不同回应(因为系统提示不同)。


八、核心知识点总结

组件 作用 本示例中的应用
运行时上下文 通过 RunnableConfig.addMetadata() 注入静态配置 注入 userRoleuserId
模型上下文(瞬态) 单次调用的消息/工具/提示 ContextAwareToolInterceptor 动态注入工具 DynamicPromptInterceptor 动态注入提示
持久上下文 跨轮次保存的状态 SummarizationHook 修改消息列表并持久化
Interceptor 拦截模型请求/响应,瞬态修改 工具选择、提示调整
Hook 生命周期节点执行操作,持久修改 消息摘要压缩

九、最佳实践

  1. 优先使用 Interceptor 处理瞬态上下文(工具、提示、消息过滤),避免污染持久状态。
  2. 使用 Hook 处理持久上下文(摘要、PII 脱敏),修改消息历史或状态。
  3. 通过运行时上下文传递静态信息(用户身份、权限),避免在工具或 Hook 中重复查询。
  4. 适时压缩上下文,防止超出模型的上下文窗口。
  5. 动态工具选择可以减少 LLM 的工具选择错误,提高准确率。

十、总结

上下文工程是构建生产级 Agent 的核心能力。通过合理使用 Interceptor (瞬态控制)和 Hook(持久控制),可以精细控制 Agent 的每一次行为,使其:

  • 正确理解用户意图(动态提示)
  • 使用恰当的工具(动态工具选择)
  • 不超出模型能力边界(上下文压缩)
  • 适应不同用户场景(角色感知)

本文的智能客服示例涵盖了上下文工程的主要实践场景,你可以将其推广到更多业务领域,构建更强大、更可控的 AI Agent 应用。


参考资源