news 2026/8/19 18:25:04

服务框架源码分析,上下文和工具如何分工

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
服务框架源码分析,上下文和工具如何分工

服务框架源码分析,上下文和工具如何分工

RAG 服务把检索结果、上下文和工具调用放进一次请求前,先要厘清数据由谁负责、在哪一层限额、失败时怎样表达。本文以 Spring Boot 的接口边界为线索,不把演练现象当作生产结论。

拆开日志记录的 JSON 请求体一看,里面包含了整套系统 32 个 Tool Calling 的 JSON Schema 定义,加上前 15 轮没有过期的完整对话历史。大模型光是首字 Prefill 阶段就耗费了 4.8 秒。

在 Spring Boot 中集成大模型能力时,很多开发团队习惯把上下文(Context)和工具(Tool/Function)混在一起一股脑往 Prompt 里塞。

上下文是给模型提供“状态与事实”的,而工具是给模型提供“动作与能力”的。一旦两者的分工界限模糊,不仅会让 Token 开销呈指数级暴涨,更会导致模型因工具过多产生 Hallucination(幻觉调用)。

# 过滤日志中单次 Tool Calling 相关的 Token 消耗量与耗时数据 grep -E "TOKEN_USAGE|TOOL_INVOKE_TIME" /var/log/spring-boot-ai.log | tail -n 20 # 使用 curl 测试 Agent 路由接口,打印出响应头部与 HTTP 状态码 curl -i -X POST "http://localhost:8080/v1/ai/context-orchestrate" \ -H "Content-Type: application/json" \ -d '{"session_id":"sess_99381","user_input":"查询用户 10086 的未支付订单并发送催缴短信"}'

动态上下文编排与工具分发流图

要在 Spring Boot 内部优雅实现上下文与工具的分工,必须在 Controller / Service 层之前增加一道上下文与工具编排器(Orchestration Layer)

整体流转的核心逻辑:

  1. 上下文(Context)做减法:通过滑动窗口与滚动摘要机制,将膨胀的对话历史严格压制在 Token 预算限制线以内。
  2. 工具(Tools)做按需加载:绝不注册全局全量 Tools,而是通过意图路由(Intent Matcher)扫描当前 Request 依赖的最小工具子集,实现毫秒级的工具注入。

生产级上下文编排与工具分工器源码实现

下面基于 Spring Boot 实现一套带 Token 预算控制与 Tool 按需过滤的编排器。

package com.company.ai.boot.orchestrator; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Service public class AgentContextOrchestrator { private static final Logger log = LoggerFactory.getLogger(AgentContextOrchestrator.class); private static final int MAX_CONTEXT_TOKENS = 3000; private final Map<String, ToolDefinition> globalToolRegistry = new ConcurrentHashMap<>(); private final ObjectMapper objectMapper; public AgentContextOrchestrator(ObjectMapper objectMapper) { this.objectMapper = objectMapper; registerDefaultTools(); } private void registerDefaultTools() { globalToolRegistry.put("queryOrder", new ToolDefinition("queryOrder", "查询用户订单", "{\"type\":\"object\",\"properties\":{\"user_id\":{\"type\":\"string\"}}}")); globalToolRegistry.put("sendSms", new ToolDefinition("sendSms", "发送短信通知", "{\"type\":\"object\",\"properties\":{\"phone\":{\"type\":\"string\"},\"msg\":{\"type\":\"string\"}}}")); globalToolRegistry.put("queryStock", new ToolDefinition("queryStock", "查询商品库存", "{\"type\":\"object\",\"properties\":{\"sku_id\":{\"type\":\"string\"}}}")); } public ExecutionPayload orchestrate(String userInput, List<ChatMessage> historyMessages) { log.info("开始编排上下文与工具,当前历史消息条数: {}", historyMessages.size()); // 1. 上下文按 Token 预算从后往前截断(Pruning) List<ChatMessage> prunedHistory = pruneContext(historyMessages, MAX_CONTEXT_TOKENS); // 2. 根据用户输入做工具意图路由(Scoped Tools Loading) List<ToolDefinition> matchedTools = resolveRelevantTools(userInput); log.info("编排完成: 保留历史消息 {} 条, 注入工具 {} 个", prunedHistory.size(), matchedTools.size()); return new ExecutionPayload(userInput, prunedHistory, matchedTools); } private List<ChatMessage> pruneContext(List<ChatMessage> history, int maxTokenBudget) { List<ChatMessage> result = new ArrayList<>(); int accumulatedTokens = 0; // 从最近的消息开始倒序累加 for (int i = history.size() - 1; i >= 0; i--) { ChatMessage msg = history.get(i); int estimatedToken = estimateToken(msg.content()); if (accumulatedTokens + estimatedToken > maxTokenBudget) { log.warn("触发 Token 预算墙,截断第 0 到第 {} 条早期历史", i); break; } accumulatedTokens += estimatedToken; result.add(0, msg); // 保持时间正序 } return result; } private List<ToolDefinition> resolveRelevantTools(String userInput) { List<ToolDefinition> selected = new ArrayList<>(); // 简单意图路由规则(生产环境可换为 Fast Embeddings 相似度匹配) if (userInput.contains("订单") || userInput.contains("买")) { selected.add(globalToolRegistry.get("queryOrder")); } if (userInput.contains("短信") || userInput.contains("通知")) { selected.add(globalToolRegistry.get("sendSms")); } if (userInput.contains("库存") || userInput.contains("货")) { selected.add(globalToolRegistry.get("queryStock")); } // 如果均未匹配,只给默认查询类工具,决不全量注入 if (selected.isEmpty()) { selected.add(globalToolRegistry.get("queryOrder")); } return selected; } private int estimateToken(String text) { if (text == null || text.isBlank()) return 0; // 中文按 1 字符 ≈ 0.6 Token 粗略换算 return (int) (text.length() * 0.6); } public record ChatMessage(String role, String content) {} public record ToolDefinition(String name, String description, String jsonSchema) {} public record ExecutionPayload(String prompt, List<ChatMessage> history, List<ToolDefinition> tools) {} }

上下文与工具接口契约设计规范

在 Spring Boot Controller 提供给前端或下游微服务时,错误语义与契约必须极其清晰。

1. 明确区别“业务逻辑失败”与“Tool Calling 失败”

模型调用工具失败时(如queryOrder返回用户不存在),这属于Tool Business Failure,应当将错误信息原样作为 Tool Message 返回给 LLM 重新生成回答,而不是在 HTTP 层直接抛出500 Internal Server Error

2. 状态隔离与错误语义定义

在 RESTful API 返回时,建议采用三级状态码区分问题属性:

{ "code": "TOOL_EXECUTION_TIMEOUT", "message": "下游订单微服务响应超时(>1500ms)", "error_type": "ENGINEERING_RETRYABLE", "detail": { "target_tool": "queryOrder", "elapsed_ms": 1502 } }
  • MODEL_SCHEMA_INVALID:模型生成的工具参数无法通过 JSON Validation,网关可自动重试一次。
  • ENGINEERING_RETRYABLE:工具执行抛出网络超时,重试闸门接管。
  • CONTEXT_WINDOW_EXCEEDED:对话历史超长,强行触发压缩。

分清了上下文的“记忆”角色与工具的“手脚”角色,Spring Boot 应用在大模型交互中才能保持高吞吐和低耗时。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/19 18:18:46

如何从零开始参与 Zim 桌面维基:从翻译到插件的完整贡献指南

如何从零开始参与 Zim 桌面维基&#xff1a;从翻译到插件的完整贡献指南 【免费下载链接】zim-desktop-wiki Main repository of the zim desktop wiki project 项目地址: https://gitcode.com/gh_mirrors/zi/zim-desktop-wiki Zim 桌面维基是一款把笔记以纯文本形式保存…

作者头像 李华
网站建设 2026/8/19 18:17:16

RCCI双燃料压燃技术:如何实现60%热效率与超低排放

1. 从“压燃”到“可控压燃”&#xff1a;RCCI为何能成为内燃机的效率巅峰&#xff1f;最近在行业圈子里&#xff0c;关于内燃机热效率突破60%的讨论又热了起来。这可不是什么实验室里的理论数字&#xff0c;而是实打实装车测试中跑出来的成绩。背后的核心技术&#xff0c;就是…

作者头像 李华
网站建设 2026/8/19 18:14:19

Android自动化测试完整指南:用Escrcpy轻松搞定多设备UI测试

Android自动化测试完整指南&#xff1a;用Escrcpy轻松搞定多设备UI测试 【免费下载链接】escrcpy &#x1f4f1; Display and control your Android device graphically with scrcpy. 项目地址: https://gitcode.com/GitHub_Trending/es/escrcpy 做Android自动化测试的都…

作者头像 李华