MCP
何为MCP,对MCP有清晰的认识
MCP 起源于 2024 年 11 月 25 日 Anthropic 发布的文章:Introducing the Model Context Protocol。
Model Context Protocol,也就是模型上下文协议,定义了应用程序和 AI 模型之间交换上下文信息的方式。这使得开发者能够以一致的方式将各种数据源、工具和功能连接到 AI 模型(一个中间协议层),就像 USB-C 让不同设备能够通过相同的接口连接一样。MCP 的目标是创建一个通用标准,使 AI 应用程序的开发和集成变得更加简单和统一。实际上,MCP 就是以更标准的方式让 LLM Chat 使用不同工具。
MCP 由三个核心组件构成:Host、Client 和 Server。
- Host:Host是AI模型运行并与之交互的用户端应用程序。它是用户感知到的那个AI软件。基本上就是加载并运行大语言模型,Host 不直接实现具体的工具功能,它通过内置的Client来外包具体执行。
- Client:Client是Host内部的一个模块,是Host与外部Server通信的唯一桥梁。它不是独立的进程,而是Host的一部分。它负责发现、连接和维持与一个或多个 MCP Server 的持久连接,当 Host 的模型决定调用某个工具时,Client 负责将工具调用请求精确地封装成MCP协议消息,发送给对应的Server。
- Server:Server暴露出特定的能力供AI模型调用。是一个轻量级的、独立的服务进程,一般情况下它只做一件事。通过MCP协议标准,向外界公布自己提供的三类主要能力,当收到Client的调用请求时,Server执行真正的业务逻辑
这种架构设计使得 LLM 可以在不同场景下灵活调用各种工具和数据源,而开发者只需专注于开发对应的 MCP Server,无需关心 Host 和 Client 的实现细节。
那么,模型是如何确定工具的选用的?模型是在什么时候确定使用哪些工具的呢?
当用户提出一个问题时:客户端将你的问题发送给 LLM。LLM 分析可用的工具,并决定使用哪一个或多个。客户端通过 MCP Server 执行所选的工具。工具的执行结果被送回给 LLM。LLM 结合执行结果构造最终的 prompt 并生成自然语言的回应。回应最终展示给用户!
所以这其中是有一个工具发现阶段的,Client 向每个 Server 发送
tools/list
请求,Server会返回它提供的所有工具列表,格式严格遵循协议定义。Client
收到所有工具列表后,会遍历每一个工具,并将它们转换为对人类(主要是LLM)可读的文本描述。
当用户发起提问时,真正的魔法就开始了。
Client会构造一个系统消息,就像下属内容的这样
“You are a helpful assistant with access to these tools: … Choose the appropriate tool based on the user’s question. If no tool is needed, reply directly.”
这个系统消息结合了所有工具的格式化描述,相当于一份可用工具目录和使用说明书。
也就是说,模型是通过 prompt engineering,即提供所有工具的结构化描述和 few-shot 的 example 来确定该使用哪些工具。
一旦决定使用工具,模型会严格遵循系统消息中规定的格式,输出一个结构化的JSON对象。最后,工具执行的结果
result 会和 system prompt
和用户消息一起重新发送给模型,请求模型生成最终回复。
LangChain4j 中的 MCP
LangChain4j 支持 MCP,用于与兼容 MCP 的服务器通信,这些服务器可以提供并执行工具。
该协议规定了两种传输方式,LangChain4j 都支持:
- 可流式 HTTP
- 客户端发送 HTTP 请求,服务器可以返回常规响应,或者在需要持续发送多个响应时打开 SSE 流。
- stdio
- 客户端可以作为本地子进程运行 MCP 服务器,并通过标准输入/输出与其通信。
这两种方式的对比如下
| 特性 | Stdio Transport | Streamable HTTP Transport |
|---|---|---|
| 通信方式 | 标准输入/输出 | HTTP 协议 |
| 进程模型 | MCP Server 作为子进程 | MCP Server 独立运行 |
| 适用场景 | 本地工具、CLI 工具 | 远程服务、微服务架构 |
| 网络要求 | 不需要网络 | 需要 HTTP 连接 |
LangChain4j 将 MCP 的三大核心组件依旧分离
要让你的聊天模型或 AI 服务运行 MCP 服务器提供的工具,需要创建一个 MCP 工具提供者实例,然后从 MCP 传输创建 MCP 客户端,再从客户端创建 MCP 工具提供者
MCP 协议的三个核心能力如下
| 能力 | 说明 | 示例 |
|---|---|---|
| Tools(工具) | LLM 可以调用的函数 | add(1,2), echo("hello"),
getWeather("北京") |
| Resources(资源) | 只读数据源 | 文件内容、数据库记录、API 响应 |
| Prompts(提示词模板) | 预定义的提示词 | 代码审查模板、翻译模板这种 |
基本上,编写 MCP 的内容就是围绕着这四层架构来的
1 | ┌─────────────────────────────────────────────────────────┐ |
LangChain4j 中实现 MCP
首先,需要一个 MCP Transport 实例,我们使用 stdio,示例展示如何通过 NPM 包以子进程方式启动服务器:
1 |
|
StdioMcpTransport会启动一个子进程运行 MCP Server,通过 stdin/stdout 进行 JSON-RPC 通信.logEvents(true)开启后可以在日志中看到完整的 MCP 协议交互
然后从 tranport 创建 MCP 客户端,这个客户端就是给 MCP 工具进行协议通信的:
1 |
|
.key("demo-mcp-server")为客户端设置唯一标识listTools()获取 MCP Server 提供的所有工具- 多个 McpClient 可以连接不同的 MCP Server
再从客户端创建 MCP 工具提供者:
1 |
|
McpToolProvider实现了ToolProvider接口.mcpClients()可以绑定多个 MCP Client.failIfOneServerFails(false)容错模式(默认),某个 Server 失败不中断一个 MCP 工具提供者可以同时使用多个客户端。在这种情况下,可以指定容错策略:
1
.builder().failIfOneServerFails(boolean)
- 默认为
false→ 忽略某个服务器的错误,继续使用其他服务器。 - 设置为
true→ 任意服务器出错都会导致抛出异常。
- 默认为
支持
.filterToolNames(...)过滤工具此外,MCP 服务器可能提供大量工具,而某个 AI 服务只需要少数几个。 可以使用工具过滤机制来避免调用不需要的工具,并减少幻觉风险:
1
2
3
4
5
6
7
public ToolProvider filteredMcpToolProvider(McpClient mcpClient) {
return McpToolProvider.builder()
.mcpClients(List.of(mcpClient))
.filterToolNames("echo", "add") // 只暴露这两个工具
.build();
}支持
.toolNameMapper(...)自定义工具命名
然后,就是对 MCP 的内容绑定到 AI Service 中
1 |
|
别看是 toolProvider 就认为实际上 mcp 和 tool 差不多,只不过,与 @Tool 方式的区别是
1
2
3
4
5 // @Tool 方式:直接绑定 Java 对象
.tools(calculator, weatherTool)
// MCP 方式:绑定 ToolProvider
.toolProvider(mcpToolProvider)
实际上,之前在 Tool Calling 那边提到的内容在这边依旧生效,返回
Result<T>,查看详细信息
1 | // AI Service 接口返回 Result<T> |
对于资源,资源的使用有两种方式:
- 编程式访问:直接调用 MCP 客户端方法。
- 自动暴露为工具:通过合成工具,让 LLM 自主调用。
MCP
协议还定义了服务器向客户端发送日志消息的方式。默认情况下,客户端会将这些日志转换为
SLF4J 日志。如果需要自定义,可以实现
dev.langchain4j.mcp.client.logging.McpLogMessageHandler,并在构建
MCP 客户端时传入
1 | McpClient mcpClient = new DefaultMcpClient.Builder() |
测试一下,就是这样的效果
那么在控制台这边也可能看到 mcp 的调用 JSON
除了高层 API,也可以通过底层 API 手动调用 MCP,这个其实和 Tool 那边的底层中的设计差不多。但是它有两种方式
1 | // 方式 1: 通过 McpClient |
而且对于 LangChain4j 对 MCP
工具的处理是有缓存的,是DefaultMcpClient
内部维护一个工具缓存。默认情况下,工具列表在首次获取后不会再次请求,除非服务器发通知更新。
1 | McpClient mcpClient = new DefaultMcpClient.Builder() |
资源和提示词
资源是什么??提示词又是什么??
资源就是 MCP 的只读数据,由 MCP Server 通过 URI 提供,通过 URI 访问,类似文件系统
提示词就是对 MCP 也有提示词,一般是预定义的提示词模板,也能参数化
但是,他们的调用和 Tool 什么的不太一样, Tools 是 LLM 主动调用,而
Resources 和 Prompts 一般是 API 访问和通过合成工具让 LLM
调用,其中后者涉及到McpResourcesAsToolsPresenter 机制
实际上,工具,资源和提示词,这三者都可以通过 McpClient
进行编程式访问,而资源和工具还可以通过 McpToolProvider
自动暴露给LLM。
那么,编程式访问就是这样访问
他们会返回 McpResource 或
McpResourceTemplate 对象,包含 URI 等元数据。
- 列出资源:
client.listResources() - 列出资源模板:
client.listResourceTemplates()
那么效果就是这样
返回 McpResource 或 McpResourceTemplate
对象,包含 URI 等元数据。
对于自动暴露为合成工具的内容,就是如果在构建
McpToolProvider 时设置了
McpResourcesAsToolsPresenter,工具提供者会自动增加两个合成工具,一般情况下,使用
LangChain4j 提供的默认实现
DefaultMcpResourcesAsToolsPresenter。
当配置了 McpResourcesAsToolsPresenter McpToolProvider
会自动创建两个合成工具
然后,注入到 McpToolProvider
1 |
|
合成工具和 MCP 原生工具不同的是,合成工具由
McpToolProvider在每次请求时自动注入
那么,资源都讲完了,提示词也是类似,基本上,提示词的内容会更少一些,其中,需要获取服务器提示词列表client.listPrompts()
,返回 List<McpPrompt>,这个 API 是要用的最多的
提示词也可也进行编程式访问
1 | // 列出所有提示词模板 |
效果基本上就是这样
护栏机制 Guardrails
这其实是一个比较单独的内容,所以我放到了比较后面的地方来说
介绍护栏机制
Guardrails 是一种机制,可以让你验证 LLM(大语言模型)的输入和输出,确保其符合预期。
通过 Guardrails,你可以完成以下等操作:
- 验证用户输入是否不在允许范围内
- 确保输入在调用 LLM 前满足特定条件,例如设置敏感词这种
- 确保输出格式正确(例如是符合正确模式的 JSON 文档)
- 确保 LLM 输出符合业务规则和约束(例如,如果这是 X 公司的聊天机器人,回答中不能包含对竞争对手 Y 的引用)
- 检测幻觉(hallucinations)
在 LangChain4j 中,Guardrail 本质上是一种:
对 AI Service 输入和输出进行拦截、检查、修改或者拒绝的机制。
它分为输入护栏和输出护栏
- 输入 Guardrails 是在调用 LLM 之前执行的函数。如果输入 Guardrail 失败,将阻止 LLM 被调用。输入 Guardrails 是调用 LLM 之前的最后一步,且在任何 RAG 操作完成后执行。
基本情况上,就是在传统软件中,我们依靠参数校验,类型检查,权限控制,数据库约束等这种内容保证系统稳定。LangChain4j 的 Guardrails 就是用于构建这层控制机制。
理想情况下,Guardrail 的实现应遵循单一职责原则,即每个 Guardrail 类只验证一件事情。然后将多个 Guardrail 串联起来,以防护多个方面。
多个 Guardrail 会组成链,Guardrail 链中的顺序很重要。第一个失败的 Guardrail 会触发整体失败。应确保最容易捕获错误的 Guardrail 排在链的前面,而那些仅在极少情况下才会失败的 Guardrail 放在链的后面。
另外请记住,Guardrail 本身可以调用其他服务,甚至触发其他 LLM 交互。如果这些 Guardrail 执行有延迟或会带来额外的成本,请务必考虑这个因素。对于更昂贵的代价更大的 Guardrail,可以将其放在链的末尾。
实际上,Guardrail 不只是校验,其实更强。它可以:
- 修改输入
- 调用数据库
- 调用另一个LLM
- 幻觉检测 Guardrail
在 LangChain4j 中使用 Guardrail
在 LangChain4j 中,一个 Guardrail 通常实现 InputGuardrail,或者 OutputGuardrail 接口。分别编写输入和输出护栏。
输入 Guardrails
实现输入 Guardrails 需要实现 InputGuardrail
接口。该接口提供两种 validate 方法的变体
1 | InputGuardrailResult validate(UserMessage userMessage); |
- 第一种适用于简单的 Guardrail,或只需要访问
UserMessage的场景。 - 第二种适用于需要更多信息的复杂 Guardrail,对于其中的
InputGuardrailRequest,你可以理解为提供整个 AI Service 调用上下文。
然后,输入 Guardrail 的结果类型如下
| 结果 | InputGuardrail
接口的辅助方法 |
描述 |
|---|---|---|
| success | success() |
- 输入有效 - 执行链中的下一个 Guardrail - 如果最后一个 Guardrail 通过,则调用 LLM |
| success with alternate result | successWith(String) |
类似 success,但用户消息会在继续下一步(下一个 Guardrail 或 LLM 调用)前被修改 |
| failure | failure(String) 或
failure(String, Throwable) |
- 输入无效,但仍继续执行链中的其他 Guardrails 以收集所有验证问题 - LLM 不会被调用 |
| fatal | fatal(String) 或
fatal(String, Throwable) |
- 输入无效,立即停止执行并抛出
InputGuardrailException - LLM 不会被调用 |
声明输入 Guardrails 的方式有以下几种,按优先级排序:
在 AiServices 构建器上直接设置
InputGuardrail实现类或实例1
2
3
4
5
6
7
8
9public interface Assistant {
String chat(String question);
String doSomethingElse(String question);
}
var assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.inputGuardrails(new FirstInputGuardrail(), new SecondInputGuardrail())
.build();在单个 AI Service 方法上使用
@InputGuardrails注解1
2
3
4
5
6
7
8public interface Assistant {
String chat(String question);
String doSomethingElse(String question);
}
var assistant = AiServices.create(Assistant.class, chatModel);在 AI Service 类上使用
@InputGuardrails注解1
2
3
4
5
6
7
public interface Assistant {
String chat(String question);
String doSomethingElse(String question);
}
var assistant = AiServices.create(Assistant.class, chatModel);
Input Guardrail 示例
假设:我们开发一个 AI 客服。要求禁止用户输入一些可能有害的信息
我们就可以定义这样的一个 Guardrail
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class SensitiveWordInputGuardrail implements InputGuardrail {
private static final List<String> WORDS =
List.of(
"攻击",
"破解",
"病毒"
);
public InputGuardrailResult validate(UserMessage userMessage) {
String text = userMessage.singleText();
for(String word : WORDS){
if(text.contains(word)){
return InputGuardrailResult.failure(
"您的输入包含非法内容"
);
}
}
return InputGuardrailResult.success();
}
}Output Guardrail 示例
假设,你的公司 AI,禁止提及竞争公司 A,那么,你就需要对输出进行过滤
1
2
3
4
5
6
7
8
9
10
11public class CompetitorOutputGuardrail implements OutputGuardrail {
public OutputGuardrailResult validate(String response) {
if(response.contains("竞争公司A")){
return OutputGuardrailResult.failure(
"输出包含禁止内容"
);
}
return OutputGuardrailResult.success();
}
}
基本上,Guardrail 是会被 AI Service 集成的,如果你在 AI Service 中添加了这些护栏,它们就会被调用
纠正,Guardrails 只能作用于 AI Services,不能直接作用于
ChatModel或StreamingChatModel
1 | Assistant assistant = |
输出 Guardrails
输出 Guardrails 是在 LLM 生成结果后执行的函数。如果输出 Guardrail 失败,可以支持更高级的操作,例如重试或重新提示,以改善响应。它们在所有其他操作(包括函数/工具调用)之后执行。
实现输出 Guardrails
需要实现OutputGuardrail接口。该接口提供两种
validate 方法,必须至少实现其中一种:
1 | OutputGuardrailResult validate(AiMessage responseFromLLM); |
第一种适用于简单的 Guardrail,或只需访问生成的
AiMessage。
第二种适用于需要更多上下文信息的复杂
Guardrail,例如完整的聊天响应、聊天记录、用户消息模板或传递给模板的变量。详细信息请参见
OutputGuardrailRequest。
输出 Guardrail 的结果类型如下:
| 结果 | OutputGuardrail
接口的辅助方法 |
描述 |
|---|---|---|
| success | success() |
- 输出有效 - 执行链中的下一个 Guardrail,如果最后一个通过,则将输出返回给调用方 |
| success with rewrite | successWith(String) 或
successWith(String, Object) |
- 类似 success,但输出在原始形式下无效,需要被重写后再进行下一步 - 下一个 Guardrail 将基于重写后的输出执行。如果最后一个 Guardrail 通过,则返回修改后的输出 |
| failure | failure(String) 或
failure(String, Throwable) |
- 输出无效,但继续执行链中的其他 Guardrail
以收集所有问题 - 最终会抛出 OutputGuardrailException |
| fatal | fatal(String) 或
fatal(String, Throwable) |
- 输出无效,立即停止执行并抛出
OutputGuardrailException |
| fatal with retry | retry(String) 或
retry(String, Throwable) |
- 类似
fatal,但会用相同的提示和聊天历史重新调用 LLM
- 如果在可配置的重试次数后仍失败,则抛出
OutputGuardrailException - 如果重试后通过,Guardrail
链会从头重新执行 |
| fatal with reprompt | reprompt(String, String) 或
reprompt(String, Throwable, String) |
- 类似 fatal with
retry,但会用 Guardrail 提供的新提示重新调用 LLM -
新请求将附加额外的消息并保持原始聊天历史 - 如果在可配置的重试次数后仍失败,则抛出
OutputGuardrailException - 如果通过,Guardrail
链会从头重新执行 |
声明方式与输入 Guardrails 类似,优先级顺序如下:
在
AiServices构建器上直接设置OutputGuardrail实现类或实例1
2
3
4
5
6
7
8
9public interface Assistant {
String chat(String question);
String doSomethingElse(String question);
}
var assistant = AiServices.builder(Assistant.class)
.chatModel(chatModel)
.outputGuardrails(new FirstOutputGuardrail(), new SecondOutputGuardrail())
.build();在单个 AI Service 方法上使用
@OutputGuardrails注解1
2
3
4
5
6
7
8public interface Assistant {
String chat(String question);
String doSomethingElse(String question);
}
var assistant = AiServices.create(Assistant.class, chatModel);在 AI Service 类上使用
@OutputGuardrails注解1
2
3
4
5
6
7
public interface Assistant {
String chat(String question);
String doSomethingElse(String question);
}
var assistant = AiServices.create(Assistant.class, chatModel);
输出护栏可以提供以下附加配置:
| 配置项 | 描述 |
|---|---|
maxRetries |
输出护栏在执行重试或重新提示时的最大重试次数。默认值为
2。设置为 0 是禁用重试。 |
LangChain4j 提供了一些常见用例的输出护栏实现:
-
一个输出护栏,用于检查响应是否能成功从 JSON 反序列化为特定类型对象。
- 使用 Jackson ObjectMapper 尝试反序列化对象。
- 如果响应无法反序列化为预期对象类型,则会重新提示 LLM。
- 可直接使用,也可通过扩展和自定义(有多个
protected方法可重写以定制行为)。
实际上,输入输出护栏是可以极高自由度的组合的,他们可以跟类似过滤器那样的方式极高自由度的组合
Guardrail 的实际例子
我们实现这样的几个护栏
输入长度护栏
演示如何限制用户输入的 token 长度,防止过高的 Token 成本或者超出模型上下文窗口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class PromptLengthGuardrail implements InputGuardrail {
private static final int MAX_ESTIMATED_TOKENS = 2000;
public InputGuardrailResult validate(UserMessage userMessage) {
String text = userMessage.singleText();
int charCount = text.length();
// 简单 token 估算(实际应使用 tokenizer)
int estimatedTokens = (int) (charCount * 0.4); // 平均 1 token ≈ 2.5 字符
if (estimatedTokens > MAX_ESTIMATED_TOKENS) {
return failure(String.format(
"您的输入过长(约 %d tokens),超过了 %d tokens 的上限。请精简后重试。",
estimatedTokens, MAX_ESTIMATED_TOKENS
));
}
return success();
}
}JSON 格式验证输出护栏
验证 LLM 输出是否为合法的 JSON。如果格式无效,使用 reprompt 机制告诉 LLM 正确的 JSON 格式要求,让 LLM 重新生成。这是输出护栏在结构化输出场景中最经典的应用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public class JsonFormatOutputGuardrail implements OutputGuardrail {
private static final ObjectMapper objectMapper = new ObjectMapper();
private static final String CORRECT_JSON_FORMAT = """
请只返回合法的 JSON 格式,不要包含任何额外的说明文字或 markdown 代码块标记。
确保:
1. 所有键名和字符串值用双引号
2. 不要有尾随逗号
3. 大括号和方括号正确配对
请重新以纯 JSON 格式回答:""";
public OutputGuardrailResult validate(AiMessage responseFromLLM) {
String text = responseFromLLM.text().trim();
// 尝试提取 JSON(去掉可能的 markdown 代码块标记)
String jsonCandidate = extractJson(text);
try {
objectMapper.readTree(jsonCandidate); // 验证 JSON 格式
return success(); // JSON 有效!
} catch (Exception e) {
return reprompt(
"LLM 输出不是合法的 JSON 格式: " + e.getMessage(),
CORRECT_JSON_FORMAT
);
}
}
}
最后,我们把这些护栏,装载到 AI Service 中,就可以使用了
1 |
|
多个 InputGuardrail 按声明顺序依次执行,任何一个返回 failure/fatal 都会阻止 LLM 被调用。这样可以构建多层防护体系。
可观测性 Observability
聊天模型可观测性
可观测性是指通过收集、分析和暴露系统内部状态的数据,在LangChain4j中,可观测性主要通过监听器(Listener)模式实现。
部分 ChatModel 和 StreamingChatModel
的实现允许配置 ChatModelListener 来监听如下事件:
- 对 LLM 的请求
- 消息,模型,温度,Top P,Tokens 情况,Tools等等内容
- 来自 LLM 的响应
- 响应格式,ID,模型,Token 使用情况,结束原因等等内容
- 错误
LangChain4j的ChatModelListener是一个接口,允许你在聊天模型请求的生命周期中的关键节点插入自定义逻辑,一般,可以在对应请求的三个阶段进行监听
onRequest(ChatModelRequestContext requestContext)- 在向LLM提供商API发送请求之前被调用。
- 可以从
requestContext.chatRequest()中获取完整的ChatRequest对象,包含所有消息和参数。
onResponse(ChatModelResponseContext responseContext)- 在从LLM提供商成功收到响应之后立即被调用。
- 记录响应内容、Token使用情况、结束原因等。可以从
responseContext.chatResponse()中获取完整的ChatResponse对象。
onError(ChatModelErrorContext errorContext)- 在请求LLM提供商API发生错误时被调用。
- 可以从
errorContext.error()中获取异常对象。
attributes是一个Map<Object, Object>,它是在请求生命周期内传递数据的核心工具。你可以在
onRequest()方法中向attributes放入数据,然后在同一个请求的onResponse()或onError()方法中读取。
1
2
3
4
5
6
7
8 // 在 onRequest 中
Map<Object, Object> attributes = requestContext.attributes();
attributes.put("startTime", System.currentTimeMillis());
// 在 onResponse 中
Map<Object, Object> attributes = responseContext.attributes();
Long startTime = (Long) attributes.get("startTime");
long duration = System.currentTimeMillis() - startTime;
这个也可以编程式配置,但是一般使用 Spring Boot 的自动配置
对于编程式配置,在创建ChatModel实例时,通过.listeners(List.of(listener))方法添加一个或多个监听器。
1 | ChatModelListener listener = new ChatModelListener() { |
而 Spring Boot
自动配置中,只需要将ChatModelListener声明为一个Spring
Bean,它就会自动被注入到由Spring Boot Starter
创建的ChatModel和StreamingChatModel中。
1 |
|
实际例子
我们写一个日志记录 ChatModelListener,按照上述的内容,我们实现 ChatModelListener 接口,然后实现对应的方法就可以了
1 | public class LoggingChatModelListener implements ChatModelListener { |
这其中使用了 attributes 上下文传递
- 每个 Context 对象都包含一个 attributes() 的 Map,在 onRequest 方法中 put 的值,可以在 onResponse 和 onError 中 get 到,这让你可以跨生命周期阶段传递上下文。
最后,给我们的这些监听器装到 AI Service 中,就可以进行监听了,这里我用了手动注入,实际上,你把监听器加上一个 @Component,让 Spring Boot 自动注册就行了
1 |
|










