Deep Dive into Context Engineering with Spring AI Alibaba

This article explains how Spring AI Alibaba’s Agent framework uses Hooks and Interceptors to dynamically control model, tool, and lifecycle contexts, demonstrating the concepts through a complete e‑commerce smart‑customer‑service demo with role‑based tool selection, dynamic prompts, and context compression.

The Dominant Programmer
The Dominant Programmer
The Dominant Programmer
Deep Dive into Context Engineering with Spring AI Alibaba

What is Context Engineering?

Agent failures in production are usually caused by either insufficient LLM capability or by providing the LLM with incorrect context. The second cause is far more common. Context engineering supplies the LLM with correctly formatted information and tools so that it can complete tasks.

Core Concepts – Three Types of Context

Model Context – what the model sees in a single call (instructions, message history, tools, response format). Transient (affects only the current call).

Tool Context – what tools can read/write (state, store, runtime context). Persistent (spans multiple turns).

Lifecycle Context – what happens between model and tool calls (summaries, guards, logs). Persistent .

Transient vs Persistent

Transient context is visible only to a single LLM invocation; it is implemented with Interceptor.

Persistent context is stored across turns; it is implemented with Hook.

Data Sources

Runtime Context (session scope) – e.g., user ID, API key, DB connection, permissions.

State (session scope) – e.g., current message, uploaded files, auth status, tool results.

Store (cross‑session) – e.g., user preferences, extracted insights, memory, history.

Implementation Mechanisms

Interceptor – intercepts and modifies model request/response; suitable for transient adjustments.

Hook – executes operations at specific Agent lifecycle nodes; suitable for persistent changes.

Controlling Model Context (Transient)

Dynamic System Prompt

class DynamicPromptInterceptor extends ModelInterceptor {
    @Override
    public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {
        String roleStr = (String) request.getContext().get("userRole");
        UserRole role = UserRole.valueOf(roleStr);
        int msgCount = request.getMessages().size();
        StringBuilder prompt = new StringBuilder();
        prompt.append("你是一个专业的电商客服助手。请用友好、专业的态度回复用户。");
        if (role == UserRole.VIP) prompt.append(" 用户是VIP会员,请提供优先服务。");
        else if (role == UserRole.ADMIN) prompt.append(" 用户是管理员,请提供完整的后台数据。");
        if (msgCount > 10) prompt.append(" 这是一个长对话,请保持回复简洁精准。");
        SystemMessage enhancedSystem = new SystemMessage(prompt.toString());
        ModelRequest enhanced = ModelRequest.builder(request).systemMessage(enhancedSystem).build();
        return handler.call(enhanced);
    }
}

Dynamic Tool Selection

class RoleBasedToolInterceptor 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);
        ModelRequest enhanced = ModelRequest.builder(request).dynamicToolCallbacks(roleTools).build();
        return handler.call(enhanced);
    }
}

Message Filtering

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);
    }
}

Controlling Tool Context (Persistent)

Accessing Persistent State

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");
        // process messages …
        return "处理结果";
    }
}

Modifying Persistent State

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 "数据已保存";
    }
}

Controlling Lifecycle Context (Persistent)

Hook Positions

BEFORE_AGENT

/

AFTER_AGENT
BEFORE_MODEL

/

AFTER_MODEL

Summarization Hook (Context Compression)

@HookPositions({HookPosition.BEFORE_MODEL})
public class SummarizationHook extends MessagesModelHook {
    private final int triggerLength;
    public SummarizationHook(int triggerLength) { this.triggerLength = triggerLength; }
    @Override
    public AgentCommand beforeModel(List<Message> previousMessages, RunnableConfig config) {
        if (previousMessages.size() <= triggerLength) return new AgentCommand(previousMessages);
        // log trigger
        String history = previousMessages.stream()
            .filter(m -> !(m instanceof SystemMessage))
            .map(Message::getText)
            .collect(Collectors.joining("
"));
        SystemMessage systemMsg = (SystemMessage) previousMessages.stream()
            .filter(m -> m instanceof SystemMessage).findFirst().orElse(null);
        String summary = "【对话摘要】用户与客服进行了多轮对话,涉及订单查询和售后咨询。";
        UserMessage summaryMsg = new UserMessage(summary);
        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);
        return new AgentCommand(newMessages, UpdatePolicy.REPLACE);
    }
}

Full Demo – Smart Customer‑Service System

Business Requirements

Guest/Normal user – can query orders and submit after‑sales questions.

VIP – additionally can query points and request membership upgrade.

Admin – full permissions, including all orders and refunds.

When the conversation becomes long, automatically compress context.

Project Structure

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

Agent 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();
    }
}

Testing & Verification

Start the app:

export DASHSCOPE_API_KEY="your_key" && mvn spring-boot:run

Invoke the API with different roles:

# Normal user
curl -X POST "http://localhost:885/api/customer/chat?message=查询订单ORD-001&sessionId=test&role=USER&userId=u001"
# VIP user
curl -X POST "http://localhost:885/api/customer/chat?message=查询我的积分&sessionId=test&role=VIP&userId=u001"
# Admin
curl -X POST "http://localhost:885/api/customer/chat?message=处理退款ORD-002&sessionId=test&role=ADMIN&userId=admin"

Observed logs show the correct number of tools per role:

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

Send 10 consecutive messages as a normal user to trigger the summarization hook. Log output:

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

Best Practices

Prefer Interceptor for transient context (tool selection, prompt adjustment, message filtering) to avoid contaminating persistent state.

Use Hook for persistent modifications such as summarization, PII redaction, or state updates.

Pass static information (user identity, permissions) via runtime context to reduce repeated look‑ups.

Compress context proactively to stay within the LLM’s window.

Dynamic tool selection reduces tool‑selection errors and improves accuracy.

Conclusion

Context engineering is essential for building production‑grade AI Agents. By combining transient Interceptor control with persistent Hook control, developers can precisely steer an Agent’s behavior, ensure correct intent understanding, use appropriate tools, stay within model limits, and adapt to diverse user scenarios.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaSpring BootInterceptorAI AgentSpring AIchatbothookContext Engineering
The Dominant Programmer
Written by

The Dominant Programmer

Resources and tutorials for programmers' advanced learning journey. Advanced tracks in Java, Python, and C#. Blog: https://blog.csdn.net/badao_liumang_qizhi

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.