From b39edfc89153242ee988829ab13f87f17b1819c5 Mon Sep 17 00:00:00 2001 From: "ken.lj" Date: Sun, 16 Nov 2025 16:05:47 +0800 Subject: [PATCH 1/6] update doc Change-Id: I5a6b4b1036ad6bb78e7ae1df103d5316bbca0059 --- .../agent-framework/tutorials/agents.md | 339 +++++++++----- .../agent-framework/tutorials/hooks.md | 422 +++++++++++++----- .../agent-framework/tutorials/memory.md | 224 +++++++--- .../agent-framework/tutorials/messages.md | 28 +- .../agent-framework/tutorials/tools.md | 25 +- docs/overview.md | 2 +- src/components/CodeBlockWithLink/index.tsx | 67 +++ .../CodeBlockWithLink/styles.module.css | 71 +++ src/theme/MDXComponents.tsx | 11 + 9 files changed, 870 insertions(+), 319 deletions(-) create mode 100644 src/components/CodeBlockWithLink/index.tsx create mode 100644 src/components/CodeBlockWithLink/styles.module.css create mode 100644 src/theme/MDXComponents.tsx diff --git a/docs/frameworks/agent-framework/tutorials/agents.md b/docs/frameworks/agent-framework/tutorials/agents.md index e51d4e6f..fa924509 100644 --- a/docs/frameworks/agent-framework/tutorials/agents.md +++ b/docs/frameworks/agent-framework/tutorials/agents.md @@ -53,8 +53,12 @@ Model 是 Agent 的推理引擎。Spring AI Alibaba 支持多种配置方式。 最直接的方式是使用 `ChatModel` 实例: -```java -import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; + +{`import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.graph.agent.ReactAgent; @@ -72,15 +76,19 @@ ChatModel chatModel = DashScopeChatModel.builder() ReactAgent agent = ReactAgent.builder() .name("my_agent") .model(chatModel) - .build(); -``` + .build();`} + #### 高级模型配置 通过 `ChatOptions` 可以精细控制模型行为: -```java -import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; + +{`import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; ChatModel chatModel = DashScopeChatModel.builder() .dashScopeApi(dashScopeApi) @@ -89,8 +97,8 @@ ChatModel chatModel = DashScopeChatModel.builder() .maxTokens(2000) // 最大输出长度 .topP(0.9) // 核采样参数 .build()) - .build(); -``` + .build();`} + **常用参数说明**: - `temperature`:控制输出的随机性(0.0-1.0),值越高越有创造性 @@ -104,8 +112,12 @@ ChatModel chatModel = DashScopeChatModel.builder() #### 定义和使用工具 -```java -import org.springframework.ai.tool.ToolCallback; + +{`import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.function.FunctionToolCallback; import java.util.function.BiFunction; @@ -131,15 +143,19 @@ ReactAgent agent = ReactAgent.builder() .name("my_agent") .model(chatModel) .tools(searchTool, weatherTool, calculatorTool) - .build(); -``` + .build();`} + #### 工具错误处理 使用 `ToolInterceptor` 统一处理工具错误: -```java -public class ToolErrorInterceptor extends ToolInterceptor { + +{`public class ToolErrorInterceptor extends ToolInterceptor { @Override public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) { try { @@ -152,9 +168,9 @@ public class ToolErrorInterceptor extends ToolInterceptor { } ReactAgent agent = ReactAgent.builder() - .interceptors(ToolErrorHandler.builder().build()) - .build(); -``` + .interceptors(new ToolErrorInterceptor()) + .build();`} + **ReAct 循环示例**:Agent 自动交替进行推理和工具调用,直到获得最终答案。 @@ -173,20 +189,26 @@ System Prompt 塑造 Agent 处理任务的方式。 通过 `systemPrompt` 参数提供字符串: -```java -ReactAgent agent = ReactAgent.builder() + +{`ReactAgent agent = ReactAgent.builder() .name("my_agent") .model(chatModel) .systemPrompt("你是一个专业的技术助手。请准确、简洁地回答问题。") - .build(); -``` + .build();`} + #### 使用 instruction 对于更详细的指令,使用 `instruction` 参数: -```java -String instruction = """ + +{`String instruction = """ 你是一个经验丰富的软件架构师。 在回答问题时,请: @@ -202,43 +224,53 @@ ReactAgent agent = ReactAgent.builder() .name("architect_agent") .model(chatModel) .instruction(instruction) - .build(); -``` + .build();`} + #### 动态 System Prompt 使用 `ModelInterceptor` 实现基于上下文的动态提示: -```java -import com.alibaba.cloud.ai.graph.agent.interceptor.ModelInterceptor; + +{`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; -public class DynamicPromptInterceptor implements ModelInterceptor { +public class DynamicPromptInterceptor extends ModelInterceptor { @Override - public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) { - // 基于用户角色动态调整提示 - List messages = request.getMessages(); - Map context = request.getContext();// this is the context set in RunnableConfig - - // do anything with messages to adjust prompt, history messages, user request dynamically - - // create modified request - ModelRequest modifiedRequest = ModelRequest.builder() - .messages(messages) - .options(request.getOptions()) - .tools(request.getTools()) - .build(); - return handler.call(modifiedRequest); - } + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) { + // 基于用户角色动态调整提示 + List messages = request.getMessages(); + Map context = request.getContext(); + + // do anything with messages to adjust prompt, history messages, user request dynamically + + // create modified request + ModelRequest modifiedRequest = ModelRequest.builder() + .messages(messages) + .options(request.getOptions()) + .tools(request.getTools()) + .build(); + return handler.call(modifiedRequest); + } + + @Override + public String getName() { + return "DynamicPromptInterceptor"; + } } ReactAgent agent = ReactAgent.builder() .name("adaptive_agent") .model(chatModel) .interceptors(new DynamicPromptInterceptor()) - .build(); -``` + .build();`} + ## 调用 Agent @@ -246,8 +278,11 @@ ReactAgent agent = ReactAgent.builder() 使用 `call` 方法获取最终响应: -```java -import org.springframework.ai.chat.messages.AssistantMessage; + +{`import org.springframework.ai.chat.messages.AssistantMessage; // 字符串输入 AssistantMessage response = agent.call("杭州的天气怎么样?"); @@ -262,15 +297,18 @@ List messages = List.of( new UserMessage("我想了解 Java 多线程"), new UserMessage("特别是线程池的使用") ); -AssistantMessage response = agent.call(messages); -``` +AssistantMessage response = agent.call(messages);`} + ### 获取完整状态 使用 `invoke` 方法获取完整的执行状态: -```java -import com.alibaba.cloud.ai.graph.OverAllState; + +{`import com.alibaba.cloud.ai.graph.OverAllState; import java.util.Optional; Optional result = agent.invoke("帮我写一首诗"); @@ -286,23 +324,27 @@ if (result.isPresent()) { Optional customData = state.value("custom_key"); System.out.println("完整状态:" + state); -} -``` +}`} + ### 使用配置 通过 `RunnableConfig` 传递运行时配置: -```java -import com.alibaba.cloud.ai.graph.RunnableConfig; + +{`import com.alibaba.cloud.ai.graph.RunnableConfig; +String threadId = "thread_123"; RunnableConfig runnableConfig = RunnableConfig.builder() - .threadId(threadId) - .addMetadata("key", "value") - .build(); + .threadId(threadId) + .addMetadata("key", "value") + .build(); -AssistantMessage response = agent.call("你的问题", config); -``` +AssistantMessage response = agent.call("你的问题", runnableConfig);`} + ## 高级特性 @@ -314,8 +356,11 @@ AssistantMessage response = agent.call("你的问题", config); 通过 Java 类定义输出结构,Agent 会自动生成对应的 JSON Schema: -```java -public class PoemOutput { + +{`public class PoemOutput { private String title; private String content; private String style; @@ -340,15 +385,18 @@ ReactAgent agent = ReactAgent.builder() AssistantMessage response = agent.call("写一首关于春天的诗"); // 输出会遵循 PoemOutput 的结构 -System.out.println(response.getText()); -``` +System.out.println(response.getText());`} + #### 使用 outputSchema 直接提供 JSON Schema 字符串进行更灵活的控制: -```java -String customSchema = """ + +{`String customSchema = """ 请严格按照以下JSON格式返回结果: { "summary": "内容摘要", @@ -365,8 +413,8 @@ ReactAgent agent = ReactAgent.builder() .saver(new MemorySaver()) .build(); -AssistantMessage response = agent.call("分析这段文本:春天来了,万物复苏。"); -``` +AssistantMessage response = agent.call("分析这段文本:春天来了,万物复苏。");`} + **选择建议**: - `outputType`:类型安全,适合结构固定的场景 @@ -376,8 +424,11 @@ AssistantMessage response = agent.call("分析这段文本:春天来了,万 Agent 通过状态自动维护对话历史。使用 `CompileConfig` 配置持久化存储。 -```java -import com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig; + +{`import com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig; import com.alibaba.cloud.ai.graph.checkpoint.constant.SaverEnum; import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; @@ -394,8 +445,8 @@ RunnableConfig config = RunnableConfig.builder() .build(); agent.call("我叫张三", config); -agent.call("我叫什么名字?", config); // 输出: "你叫张三" -``` +agent.call("我叫什么名字?", config); // 输出: "你叫张三"`} + **生产环境**:使用 `RedisSaver`、`MongoSaver` 等持久化存储替代 `MemorySaver`。 @@ -405,8 +456,12 @@ Hooks 允许在 Agent 执行的关键点插入自定义逻辑。 #### Hook 类型与使用 -```java -import com.alibaba.cloud.ai.graph.agent.hook.*; + +{`import com.alibaba.cloud.ai.graph.agent.hook.*; // 1. AgentHook - 在 Agent 开始/结束时执行,每次Agent调用只会运行一次 public class LoggingHook implements AgentHook { @@ -435,22 +490,38 @@ public class LoggingHook implements AgentHook { } // 2. ModelHook - 在模型调用前后执行(例如:消息修剪),区别于AgentHook,ModelHook在一次agent调用中可能会调用多次,也就是每次 reasoning-acting 迭代都会执行 -public class MessageTrimmingHook implements ModelHook { +public class MessageTrimmingHook extends ModelHook { private static final int MAX_MESSAGES = 10; @Override - public Map beforeModel(OverAllState state, RunnableConfig config) { + public String getName() { + return "message_trimming"; + } + + @Override + public HookPosition[] getHookPositions() { + return new HookPosition[]{HookPosition.BEFORE_MODEL}; + } + + @Override + public CompletableFuture> beforeModel(OverAllState state, RunnableConfig config) { Optional messagesOpt = state.value("messages"); if (messagesOpt.isPresent()) { List messages = (List) messagesOpt.get(); if (messages.size() > MAX_MESSAGES) { - return Map.of("messages", - messages.subList(messages.size() - MAX_MESSAGES, messages.size())); + return CompletableFuture.completedFuture(Map.of("messages", + messages.subList(messages.size() - MAX_MESSAGES, messages.size()))); } } - return Map.of(); + return CompletableFuture.completedFuture(Map.of()); } -} + + @Override + public CompletableFuture> afterModel(OverAllState state, RunnableConfig config) { + return CompletableFuture.completedFuture(Map.of()); + } +}`} + **Hook 执行位置**: - `BEFORE_AGENT` / `AFTER_AGENT`:Agent 整体执行前后 @@ -462,11 +533,15 @@ Interceptors 提供更细粒度的控制,可以拦截和修改模型调用和 #### 使用示例 -```java -import com.alibaba.cloud.ai.graph.agent.interceptor.*; + +{`import com.alibaba.cloud.ai.graph.agent.interceptor.*; // ModelInterceptor - 内容安全检查 -public class GuardrailInterceptor implements ModelInterceptor { +public class GuardrailInterceptor extends ModelInterceptor { @Override public ModelResponse intercept(ModelRequest request, ModelCallHandler handler) { // 前置:检查输入 @@ -483,7 +558,7 @@ public class GuardrailInterceptor implements ModelInterceptor { } // ToolInterceptor - 监控和错误处理 -public class ToolMonitoringInterceptor implements ToolInterceptor { +public class ToolMonitoringInterceptor extends ToolInterceptor { @Override public ToolCallResponse intercept(ToolCallRequest request, ToolCallHandler handler) { long startTime = System.currentTimeMillis(); @@ -505,8 +580,8 @@ ReactAgent agent = ReactAgent.builder() .model(chatModel) .interceptors(new GuardrailInterceptor(), new LoggingInterceptor(), new ToolMonitoringInterceptor()) .saver(new MemorySaver()) - .build(); -``` + .build();`} + **常见用途**: - **ModelInterceptor**:内容安全、动态提示、日志记录、性能监控 @@ -516,36 +591,92 @@ ReactAgent agent = ReactAgent.builder() #### 迭代控制 -```java -// 设置最大迭代次数 +通过 Hooks 控制 Agent 的执行迭代,防止无限循环或过度成本。 + + +{`import com.alibaba.cloud.ai.graph.agent.hook.modelcalllimit.ModelCallLimitHook; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; + +// 使用内置的 ModelCallLimitHook 限制模型调用次数 ReactAgent agent = ReactAgent.builder() - .maxIterations(5) // 默认 10 - .build(); + .name("my_agent") + .model(chatModel) + .hooks(ModelCallLimitHook.builder().runLimit(5).build()) // 限制最多调用 5 次 + .saver(new MemorySaver()) + .build();`} + + + +{`import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; +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.JumpTo; +import org.springframework.ai.chat.messages.AssistantMessage; -// 自定义停止条件 -Function stopCondition = state -> { - // 找到答案或错误过多时停止 - return !state.value("answer_found").orElse(false) - && (Integer) state.value("error_count").orElse(0) <= 3; -}; +// 自定义停止条件:基于状态判断是否继续 +@HookPositions({HookPosition.BEFORE_MODEL}) +public class CustomStopConditionHook extends ModelHook { -agent = ReactAgent.builder() - .shouldContinueFunction(stopCondition) - .build(); -``` + @Override + public String getName() { + return "custom_stop_condition"; + } + + @Override + public CompletableFuture> beforeModel(OverAllState state, RunnableConfig config) { + // 检查是否找到答案,展示使用 OverAllState + boolean answerFound = (Boolean) state.value("answer_found").orElse(false); + // 检查错误次数,展示使用 RunnableConfig + int errorCount = (Integer) config.context().get("error_count").orElse(0); + + // 找到答案或错误过多时停止 + if (answerFound || errorCount > 3) { + List messages = new ArrayList<>( + (List) state.value("messages").orElse(new ArrayList<>()) + ); + messages.add(new AssistantMessage( + answerFound ? "已找到答案,Agent 执行完成。" + : "错误次数过多 (" + errorCount + "),Agent 执行终止。" + )); + return CompletableFuture.completedFuture(Map.of("messages", messages)); + } + + return CompletableFuture.completedFuture(Map.of()); + } + +} + +// 使用自定义停止条件 +ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) + .hooks(new CustomStopConditionHook()) + .saver(new MemorySaver()) + .build();`} + #### 流式输出 -```java -import reactor.core.publisher.Flux; + +{`import reactor.core.publisher.Flux; Flux stream = agent.stream("复杂任务"); stream.subscribe( response -> System.out.println("进度: " + response), error -> System.err.println("错误: " + error), () -> System.out.println("完成") -); -``` +);`} + ## 下一步 diff --git a/docs/frameworks/agent-framework/tutorials/hooks.md b/docs/frameworks/agent-framework/tutorials/hooks.md index a2e5ca78..a942ae26 100644 --- a/docs/frameworks/agent-framework/tutorials/hooks.md +++ b/docs/frameworks/agent-framework/tutorials/hooks.md @@ -128,38 +128,7 @@ ReactAgent agent = ReactAgent.builder() ReactAgent agent = ReactAgent.builder() .name("my_agent") .model(chatModel) - .maxIterations(10) // 最多 10 次迭代(默认为 10) - .saver(new MemorySaver()) - .build(); -``` - -### 工具调用限制(Tool Call Limit) - -使用自定义停止条件限制工具调用: - -```java -import com.alibaba.cloud.ai.graph.OverAllState; -import java.util.function.Function; - -Function customStopCondition = state -> { - // 如果找到答案或错误过多则停止 - Optional foundAnswer = state.value("answer_found"); - if (foundAnswer.isPresent() && (Boolean) foundAnswer.get()) { - return false; // 停止执行 - } - - Optional errorCount = state.value("error_count"); - if (errorCount.isPresent() && (Integer) errorCount.get() > 3) { - return false; // 停止执行 - } - - return true; // 继续执行 -}; - -ReactAgent agent = ReactAgent.builder() - .name("controlled_agent") - .model(chatModel) - .shouldContinueFunction(customStopCondition) + .hooks(ModelCallLimitHook.builder().runLimit(5).build()) // 限制模型调用次数为5次 .saver(new MemorySaver()) .build(); ``` @@ -174,13 +143,21 @@ ReactAgent agent = ReactAgent.builder() * 任何处理敏感用户数据的应用程序 ```java -import com.alibaba.cloud.ai.graph.agent.interceptor.PIIDetectionInterceptor; +import com.alibaba.cloud.ai.graph.agent.hook.pii.PIIDetectionHook; +import com.alibaba.cloud.ai.graph.agent.hook.pii.PIIType; +import com.alibaba.cloud.ai.graph.agent.hook.pii.RedactionStrategy; + +PIIDetectionHook pii = PIIDetectionHook.builder() + .piiType(PIIType.EMAIL) + .strategy(RedactionStrategy.REDACT) + .applyToInput(true) + .build(); // 使用 ReactAgent agent = ReactAgent.builder() .name("secure_agent") .model(chatModel) - .modelInterceptors(new PIIDetectionInterceptor()) + .hooks(pii) .build(); ``` @@ -194,21 +171,20 @@ ReactAgent agent = ReactAgent.builder() * 构建优雅处理临时错误的弹性 Agent ```java -import com.alibaba.cloud.ai.graph.agent.interceptor.ToolRetryInterceptor; +import com.alibaba.cloud.ai.graph.agent.interceptor.toolretry.ToolRetryInterceptor; // 使用 ReactAgent agent = ReactAgent.builder() .name("resilient_agent") .model(chatModel) .tools(searchTool, databaseTool) - .toolInterceptors(new ToolRetryInterceptor(3, 1000, 2.0)) + .interceptors(ToolRetryInterceptor.builder() + .maxRetries(2) + .onFailure(ToolRetryInterceptor.OnFailureBehavior.RETURN_MESSAGE) + .build()) .build(); ``` -**配置选项**: -- `maxRetries`: 最大重试次数(默认 3) -- `initialDelayMs`: 初始延迟毫秒数(默认 1000) -- `backoffMultiplier`: 退避倍数(默认 2.0) ### Planning(规划) @@ -220,14 +196,14 @@ ReactAgent agent = ReactAgent.builder() * 通过检查建议的计划来调试错误 ```java -import com.alibaba.cloud.ai.graph.agent.hook.PlanningHook; +import com.alibaba.cloud.ai.graph.agent.interceptor.todolist.TodoListInterceptor; // 使用 ReactAgent agent = ReactAgent.builder() .name("planning_agent") .model(chatModel) .tools(myTool) - .hooks(new PlanningHook()) + .interceptors(TodoListInterceptor.builder().build()) .build(); ``` @@ -241,16 +217,14 @@ ReactAgent agent = ReactAgent.builder() * 动态选择最适合特定输入的工具 ```java -import com.alibaba.cloud.ai.graph.agent.interceptor.LLMToolSelectorInterceptor; -import org.springframework.ai.chat.model.ChatModel; +import com.alibaba.cloud.ai.graph.agent.interceptor.toolselection.ToolSelectionInterceptor; // 使用 -ChatModel selectorModel = ...; // 用于选择的另一个ChatModel ReactAgent agent = ReactAgent.builder() .name("smart_selector_agent") .model(chatModel) .tools(tool1, tool2) - .toolInterceptors(new LLMToolSelectorInterceptor(selectorModel)) + .interceptors(ToolSelectionInterceptor.builder().build()) .build(); ``` @@ -264,14 +238,14 @@ ReactAgent agent = ReactAgent.builder() * 在不产生实际成本或副作用的情况下测试 Agent 逻辑 ```java -import com.alibaba.cloud.ai.graph.agent.interceptor.ToolEmulatorInterceptor; +import com.alibaba.cloud.ai.graph.agent.interceptor.toolemulator.ToolEmulatorInterceptor; // 使用 ReactAgent agent = ReactAgent.builder() .name("emulator_agent") .model(chatModel) .tools(simulatedTool) - .toolInterceptors(new ToolEmulatorInterceptor(chatModel)) + .interceptors(ToolEmulatorInterceptor.builder().model(chatModel).build()) .build(); ``` @@ -285,13 +259,13 @@ ReactAgent agent = ReactAgent.builder() * 动态修改上下文以引导 Agent 的行为 ```java -import com.alibaba.cloud.ai.graph.agent.hook.ContextEditingHook; +import com.alibaba.cloud.ai.graph.agent.interceptor.contextediting.ContextEditingInterceptor; // 使用 ReactAgent agent = ReactAgent.builder() .name("context_aware_agent") .model(chatModel) - .hooks(new ContextEditingHook("Remember to be polite and helpful.")) + .interceptors(ContextEditingInterceptor.builder().trigger(120000).clearAtLeast(60000).build()) .build(); ``` @@ -313,8 +287,11 @@ ReactAgent agent = ReactAgent.builder() ```java import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; +import com.alibaba.cloud.ai.graph.agent.hook.HookPositions; +import java.util.concurrent.CompletableFuture; -public class CustomModelHook implements ModelHook { +@HookPositions({HookPosition.BEFORE_MODEL, HookPosition.AFTER_MODEL}) +public class CustomModelHook extends ModelHook { @Override public String getName() { @@ -322,30 +299,22 @@ public class CustomModelHook implements ModelHook { } @Override - public HookPosition[] getHookPositions() { - return new HookPosition[]{ - HookPosition.BEFORE_MODEL, - HookPosition.AFTER_MODEL - }; - } - - @Override - public Map beforeModel(OverAllState state, RunnableConfig config) { + public CompletableFuture> beforeModel(OverAllState state, RunnableConfig config) { // 在模型调用前执行 System.out.println("准备调用模型..."); // 可以修改状态 // 例如:添加额外的上下文 - return Map.of("extra_context", "某些额外信息"); + return CompletableFuture.completedFuture(Map.of("extra_context", "某些额外信息")); } @Override - public Map afterModel(OverAllState state, RunnableConfig config) { + public CompletableFuture> afterModel(OverAllState state, RunnableConfig config) { // 在模型调用后执行 System.out.println("模型调用完成"); // 可以记录响应信息 - return Map.of(); + return CompletableFuture.completedFuture(Map.of()); } } ``` @@ -357,8 +326,11 @@ public class CustomModelHook implements ModelHook { ```java import com.alibaba.cloud.ai.graph.agent.hook.AgentHook; import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; +import com.alibaba.cloud.ai.graph.agent.hook.HookPositions; +import java.util.concurrent.CompletableFuture; -public class CustomAgentHook implements AgentHook { +@HookPositions({HookPosition.BEFORE_AGENT, HookPosition.AFTER_AGENT}) +public class CustomAgentHook extends AgentHook { @Override public String getName() { @@ -366,22 +338,14 @@ public class CustomAgentHook implements AgentHook { } @Override - public HookPosition[] getHookPositions() { - return new HookPosition[]{ - HookPosition.BEFORE_AGENT, - HookPosition.AFTER_AGENT - }; - } - - @Override - public Map beforeAgent(OverAllState state, RunnableConfig config) { + public CompletableFuture> beforeAgent(OverAllState state, RunnableConfig config) { System.out.println("Agent 开始执行"); // 可以初始化资源、记录开始时间等 - return Map.of("start_time", System.currentTimeMillis()); + return CompletableFuture.completedFuture(Map.of("start_time", System.currentTimeMillis())); } @Override - public Map afterAgent(OverAllState state, RunnableConfig config) { + public CompletableFuture> afterAgent(OverAllState state, RunnableConfig config) { System.out.println("Agent 执行完成"); // 可以清理资源、计算执行时间等 Optional startTime = state.value("start_time"); @@ -389,7 +353,7 @@ public class CustomAgentHook implements AgentHook { long duration = System.currentTimeMillis() - (Long) startTime.get(); System.out.println("执行耗时: " + duration + "ms"); } - return Map.of(); + return CompletableFuture.completedFuture(Map.of()); } } ``` @@ -404,17 +368,17 @@ 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; -public class LoggingInterceptor implements ModelInterceptor { +public class LoggingInterceptor extends ModelInterceptor { @Override - public ModelResponse intercept(ModelRequest request, ModelCallHandler handler) { + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) { // 请求前记录 System.out.println("发送请求到模型: " + request.getMessages().size() + " 条消息"); long startTime = System.currentTimeMillis(); // 执行实际调用 - ModelResponse response = handler.handle(request); + ModelResponse response = handler.call(request); // 响应后记录 long duration = System.currentTimeMillis() - startTime; @@ -422,6 +386,11 @@ public class LoggingInterceptor implements ModelInterceptor { return response; } + + @Override + public String getName() { + return "LoggingInterceptor"; + } } ``` @@ -435,17 +404,17 @@ import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallRequest; import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallResponse; import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallHandler; -public class ToolMonitoringInterceptor implements ToolInterceptor { +public class ToolMonitoringInterceptor extends ToolInterceptor { @Override - public ToolCallResponse intercept(ToolCallRequest request, ToolCallHandler handler) { - String toolName = request.getToolCall().name(); + public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) { + String toolName = request.getToolName(); long startTime = System.currentTimeMillis(); System.out.println("执行工具: " + toolName); try { - ToolCallResponse response = handler.handle(request); + ToolCallResponse response = handler.call(request); long duration = System.currentTimeMillis() - startTime; System.out.println("工具 " + toolName + " 执行成功 (耗时: " + duration + "ms)"); @@ -455,15 +424,183 @@ public class ToolMonitoringInterceptor implements ToolInterceptor { long duration = System.currentTimeMillis() - startTime; System.err.println("工具 " + toolName + " 执行失败 (耗时: " + duration + "ms): " + e.getMessage()); - return ToolCallResponse.error( - request.getToolCall(), + return ToolCallResponse.of( + request.getToolCallId(), + request.getToolName(), "工具执行失败: " + e.getMessage() ); } } + + @Override + public String getName() { + return "ToolMonitoringInterceptor"; + } +} +``` + +### 使用 RunnableConfig 跨调用共享数据 + +`RunnableConfig` 提供了一个 `context()` 方法,允许你在同一个执行流程中的多个 Hook 调用、多轮模型或工具调用之间共享数据。这对于实现计数器、累积统计信息或跨多次调用维护状态非常有用。 + +**适用场景**: +* 跟踪模型或工具调用次数 +* 累积性能指标(总耗时、平均响应时间等) +* 在 before/after Hook 之间传递临时数据 +* 实现基于计数的限流或断路器 + +**示例:使用 RunnableConfig.context() 实现调用计数器** + +```java +import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; +import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; +import com.alibaba.cloud.ai.graph.agent.hook.HookPositions; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.OverAllState; +import java.util.concurrent.CompletableFuture; +import java.util.Map; + +@HookPositions({HookPosition.BEFORE_MODEL, HookPosition.AFTER_MODEL}) +public class ModelCallCounterHook extends ModelHook { + + private static final String CALL_COUNT_KEY = "__model_call_count__"; + private static final String TOTAL_TIME_KEY = "__total_model_time__"; + private static final String START_TIME_KEY = "__call_start_time__"; + + @Override + public String getName() { + return "model_call_counter"; + } + + @Override + public CompletableFuture> beforeModel(OverAllState state, RunnableConfig config) { + // 从 context 读取当前计数(如果不存在则默认为 0) + int currentCount = config.context().containsKey(CALL_COUNT_KEY) + ? (int) config.context().get(CALL_COUNT_KEY) : 0; + + System.out.println("模型调用 #" + (currentCount + 1)); + + // 记录开始时间 + config.context().put(START_TIME_KEY, System.currentTimeMillis()); + + return CompletableFuture.completedFuture(Map.of()); + } + + @Override + public CompletableFuture> afterModel(OverAllState state, RunnableConfig config) { + // 读取当前计数并递增 + int currentCount = config.context().containsKey(CALL_COUNT_KEY) + ? (int) config.context().get(CALL_COUNT_KEY) : 0; + config.context().put(CALL_COUNT_KEY, currentCount + 1); + + // 计算本次调用耗时并累加到总耗时 + if (config.context().containsKey(START_TIME_KEY)) { + long startTime = (long) config.context().get(START_TIME_KEY); + long duration = System.currentTimeMillis() - startTime; + + long totalTime = config.context().containsKey(TOTAL_TIME_KEY) + ? (long) config.context().get(TOTAL_TIME_KEY) : 0L; + config.context().put(TOTAL_TIME_KEY, totalTime + duration); + + // 输出统计信息 + int newCount = currentCount + 1; + long newTotalTime = totalTime + duration; + System.out.println("模型调用完成: " + duration + "ms"); + System.out.println("累计统计 - 调用次数: " + newCount + ", 总耗时: " + newTotalTime + "ms, 平均: " + (newTotalTime / newCount) + "ms"); + } + + return CompletableFuture.completedFuture(Map.of()); + } +} +``` + +**示例:基于 context 实现调用次数限制** + +```java +import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; +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.JumpTo; +import org.springframework.ai.chat.messages.AssistantMessage; +import java.util.List; +import java.util.ArrayList; + +@HookPositions({HookPosition.BEFORE_MODEL, HookPosition.AFTER_MODEL}) +public class ModelCallLimiterHook extends ModelHook { + + private static final String CALL_COUNT_KEY = "__model_call_count__"; + private final int maxCalls; + + public ModelCallLimiterHook(int maxCalls) { + this.maxCalls = maxCalls; + } + + @Override + public String getName() { + return "model_call_limiter"; + } + + @Override + public CompletableFuture> beforeModel(OverAllState state, RunnableConfig config) { + // 读取当前调用次数 + int callCount = config.context().containsKey(CALL_COUNT_KEY) + ? (int) config.context().get(CALL_COUNT_KEY) : 0; + + // 检查是否超过限制 + if (callCount >= maxCalls) { + System.out.println("达到模型调用次数限制: " + maxCalls); + + // 添加终止消息 + List messages = new ArrayList<>( + (List) state.value("messages").orElse(new ArrayList<>()) + ); + messages.add(new AssistantMessage( + "已达到模型调用次数限制 (" + callCount + "/" + maxCalls + "),Agent 执行终止。" + )); + + // 返回更新并跳转到结束 + return CompletableFuture.completedFuture(Map.of("messages", messages)); + } + + return CompletableFuture.completedFuture(Map.of()); + } + + @Override + public CompletableFuture> afterModel(OverAllState state, RunnableConfig config) { + // 递增计数器 + int callCount = config.context().containsKey(CALL_COUNT_KEY) + ? (int) config.context().get(CALL_COUNT_KEY) : 0; + config.context().put(CALL_COUNT_KEY, callCount + 1); + + return CompletableFuture.completedFuture(Map.of()); + } + + @Override + public List canJumpTo() { + return List.of(JumpTo.end); + } } ``` +**使用示例**: + +```java +ReactAgent agent = ReactAgent.builder() + .name("limited_agent") + .model(chatModel) + .tools(tools) + .hooks(new ModelCallCounterHook()) // 监控调用统计 + .hooks(new ModelCallLimiterHook(5)) // 限制最多调用 5 次 + .build(); +``` + +**关键要点**: + +* **context() 是共享的**: 同一个执行流程中的所有 Hook 共享同一个 context +* **数据持久性**: context 中的数据在整个 Agent 执行期间保持有效 +* **类型安全**: 需要自己管理 context 中数据的类型转换 +* **命名约定**: 建议使用双下划线前缀命名 context key(如 `__model_call_count__`)以避免与用户数据冲突 + ## 执行顺序 使用多个 Hooks 和 Interceptors 时,理解执行顺序很重要: @@ -568,16 +705,16 @@ ReactAgent agent = ReactAgent.builder() ### 示例 1:内容审核 Interceptor ```java -public class ContentModerationInterceptor implements ModelInterceptor { +public class ContentModerationInterceptor extends ModelInterceptor { private static final List BLOCKED_WORDS = List.of("敏感词1", "敏感词2", "敏感词3"); @Override - public ModelResponse intercept(ModelRequest request, ModelCallHandler handler) { + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) { // 检查输入 for (Message msg : request.getMessages()) { - String content = msg.getContent().toLowerCase(); + String content = msg.getText().toLowerCase(); for (String blocked : BLOCKED_WORDS) { if (content.contains(blocked)) { return ModelResponse.blocked( @@ -588,7 +725,7 @@ public class ContentModerationInterceptor implements ModelInterceptor { } // 执行模型调用 - ModelResponse response = handler.handle(request); + ModelResponse response = handler.call(request); // 检查输出 String output = response.getContent(); @@ -602,56 +739,94 @@ public class ContentModerationInterceptor implements ModelInterceptor { return response; } + + @Override + public String getName() { + return "ContentModerationInterceptor"; + } } ``` -### 示例 2:性能监控 Hook +### 示例 2:性能监控 - 使用 Interceptor -```java -public class PerformanceMonitoringHook implements AgentHook { +使用 `ModelInterceptor` 和 `ToolInterceptor` 监控模型和工具调用的性能: - private Map metrics = new HashMap<>(); +```java +// 模型调用性能监控 +public class ModelPerformanceInterceptor extends ModelInterceptor { @Override - public String getName() { - return "performance_monitoring"; - } + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) { + // 请求前记录 + System.out.println("发送请求到模型: " + request.getMessages().size() + " 条消息"); - @Override - public HookPosition[] getHookPositions() { - return new HookPosition[]{ - HookPosition.BEFORE_AGENT, - HookPosition.AFTER_AGENT - }; + long startTime = System.currentTimeMillis(); + + // 执行实际调用 + ModelResponse response = handler.call(request); + + // 响应后记录 + long duration = System.currentTimeMillis() - startTime; + System.out.println("模型响应耗时: " + duration + "ms"); + + return response; } @Override - public Map beforeAgent(OverAllState state, RunnableConfig config) { - metrics.put("start_time", System.currentTimeMillis()); - metrics.put("model_calls", 0L); - metrics.put("tool_calls", 0L); - return Map.of(); + public String getName() { + return "ModelPerformanceInterceptor"; } +} + +// 工具调用性能监控 +public class ToolPerformanceInterceptor extends ToolInterceptor { @Override - public Map afterAgent(OverAllState state, RunnableConfig config) { - long duration = System.currentTimeMillis() - metrics.get("start_time"); + public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) { + String toolName = request.getToolName(); + long startTime = System.currentTimeMillis(); + + System.out.println("执行工具: " + toolName); + + try { + ToolCallResponse response = handler.call(request); + + long duration = System.currentTimeMillis() - startTime; + System.out.println("工具 " + toolName + " 执行成功 (耗时: " + duration + "ms)"); + + return response; + } catch (Exception e) { + long duration = System.currentTimeMillis() - startTime; + System.err.println("工具 " + toolName + " 执行失败 (耗时: " + duration + "ms): " + e.getMessage()); - System.out.println("===== 性能报告 ====="); - System.out.println("总耗时: " + duration + "ms"); - System.out.println("模型调用次数: " + metrics.get("model_calls")); - System.out.println("工具调用次数: " + metrics.get("tool_calls")); - System.out.println("=================="); + return ToolCallResponse.of( + request.getToolCallId(), + request.getToolName(), + "工具执行失败: " + e.getMessage() + ); + } + } - return Map.of(); + @Override + public String getName() { + return "ToolPerformanceInterceptor"; } } + +// 使用示例 +ReactAgent agent = ReactAgent.builder() + .name("monitored_agent") + .model(chatModel) + .tools(tools) + .interceptors(new ModelPerformanceInterceptor()) + .interceptors(new ToolPerformanceInterceptor()) + .build(); ``` ### 示例 3:工具缓存 Interceptor ```java -public class ToolCacheInterceptor implements ToolInterceptor { +public class ToolCacheInterceptor extends ToolInterceptor { private Map cache = new ConcurrentHashMap<>(); private final long ttlMs; @@ -661,18 +836,18 @@ public class ToolCacheInterceptor implements ToolInterceptor { } @Override - public ToolCallResponse intercept(ToolCallRequest request, ToolCallHandler handler) { + public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) { String cacheKey = generateCacheKey(request); // 检查缓存 ToolCallResponse cached = cache.get(cacheKey); if (cached != null && !isExpired(cached)) { - System.out.println("缓存命中: " + request.getToolCall().name()); + System.out.println("缓存命中: " + request.getToolName()); return cached; } // 执行工具 - ToolCallResponse response = handler.handle(request); + ToolCallResponse response = handler.call(request); // 缓存结果 cache.put(cacheKey, response); @@ -680,9 +855,14 @@ public class ToolCacheInterceptor implements ToolInterceptor { return response; } + @Override + public String getName() { + return "ToolCacheInterceptor"; + } + private String generateCacheKey(ToolCallRequest request) { - return request.getToolCall().name() + ":" + - request.getToolCall().arguments(); + return request.getToolName() + ":" + + request.getArguments(); } private boolean isExpired(ToolCallResponse response) { diff --git a/docs/frameworks/agent-framework/tutorials/memory.md b/docs/frameworks/agent-framework/tutorials/memory.md index 18a16423..64679ad3 100644 --- a/docs/frameworks/agent-framework/tutorials/memory.md +++ b/docs/frameworks/agent-framework/tutorials/memory.md @@ -88,10 +88,20 @@ import java.util.List; import java.util.Optional; // 在 Hook 中访问和修改状态 -public class CustomMemoryHook implements ModelHook { +public class CustomMemoryHook extends ModelHook { @Override - public Map beforeModel(OverAllState state, RunnableConfig config) { + public String getName() { + return "custom_memory"; + } + + @Override + public HookPosition[] getHookPositions() { + return new HookPosition[]{HookPosition.BEFORE_MODEL}; + } + + @Override + public CompletableFuture> beforeModel(OverAllState state, RunnableConfig config) { // 访问消息历史 Optional messagesOpt = state.value("messages"); if (messagesOpt.isPresent()) { @@ -100,11 +110,12 @@ public class CustomMemoryHook implements ModelHook { } // 添加自定义状态 - return Map.of( + return CompletableFuture.completedFuture(Map.of( "user_id", "user_123", "preferences", Map.of("theme", "dark") - ); + )); } + } ``` @@ -136,8 +147,9 @@ import org.springframework.ai.chat.messages.Message; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CompletableFuture; -public class MessageTrimmingHook implements ModelHook { +public class MessageTrimmingHook extends ModelHook { private static final int MAX_MESSAGES = 3; @@ -152,19 +164,19 @@ public class MessageTrimmingHook implements ModelHook { } @Override - public Map beforeModel(OverAllState state, RunnableConfig config) { + public CompletableFuture> beforeModel(OverAllState state, RunnableConfig config) { Optional messagesOpt = state.value("messages"); if (!messagesOpt.isPresent()) { - return Map.of(); + return CompletableFuture.completedFuture(Map.of()); } List messages = (List) messagesOpt.get(); if (messages.size() <= MAX_MESSAGES) { - return Map.of(); // 无需更改 + return CompletableFuture.completedFuture(Map.of()); // 无需更改 } - // 保留第一条消息和最后几条消息 + // 保留第一条消息和最后几条消息,并将中间消息标记为删除 Message firstMsg = messages.get(0); int keepCount = messages.size() % 2 == 0 ? 3 : 4; List recentMessages = messages.subList( @@ -172,16 +184,20 @@ public class MessageTrimmingHook implements ModelHook { messages.size() ); - List newMessages = new ArrayList<>(); - newMessages.add(firstMsg); - newMessages.addAll(recentMessages); + List newMessages = new ArrayList<>(); + // 标记中间消息为删除(使用 RemoveByHash) + if (messages.size() - keepCount > 1) { + for (Message msg : messages.subList(1, messages.size() - keepCount)) { + newMessages.add(com.alibaba.cloud.ai.graph.state.RemoveByHash.of(msg)); + } + } - return Map.of("messages", newMessages); + return CompletableFuture.completedFuture(Map.of("messages", newMessages)); } @Override - public Map afterModel(OverAllState state, RunnableConfig config) { - return Map.of(); + public CompletableFuture> afterModel(OverAllState state, RunnableConfig config) { + return CompletableFuture.completedFuture(Map.of()); } } @@ -195,7 +211,7 @@ ReactAgent agent = ReactAgent.builder() .build(); RunnableConfig config = RunnableConfig.builder() - .configurable(Map.of("thread_id", "1")) + .threadId("1") .build(); agent.call("你好,我叫 bob", config); @@ -216,7 +232,7 @@ System.out.println(finalResponse.getText()); 要从 Graph 状态中删除消息,你可以在 Hook 中返回新的消息列表: ```java -public class MessageDeletionHook implements ModelHook { +public class MessageDeletionHook extends ModelHook { @Override public String getName() { @@ -229,21 +245,23 @@ public class MessageDeletionHook implements ModelHook { } @Override - public Map afterModel(OverAllState state, RunnableConfig config) { + public CompletableFuture> afterModel(OverAllState state, RunnableConfig config) { Optional messagesOpt = state.value("messages"); if (!messagesOpt.isPresent()) { - return Map.of(); + return CompletableFuture.completedFuture(Map.of()); } List messages = (List) messagesOpt.get(); if (messages.size() > 2) { - // 移除最早的两条消息 - List trimmed = messages.subList(2, messages.size()); - return Map.of("messages", trimmed); - } - - return Map.of(); + // 将被裁剪的两条旧消息转为 RemoveByHash,并连同保留的后续消息一起返回 + List removeFirstTwoMessages = new ArrayList<>(); + removeFirstTwoMessages.add(RemoveByHash.of(messages.get(0))); + removeFirstTwoMessages.add(RemoveByHash.of(messages.get(1))); + return CompletableFuture.completedFuture(Map.of("messages", removeFirstTwoMessages)); + } + + return CompletableFuture.completedFuture(Map.of()); } } ``` @@ -251,10 +269,37 @@ public class MessageDeletionHook implements ModelHook { **删除所有消息**: ```java -@Override -public Map afterModel(OverAllState state, RunnableConfig config) { - // 清除所有消息 - return Map.of("messages", new ArrayList()); +import com.alibaba.cloud.ai.graph.state.RemoveByHash; + +public class ClearAllMessagesHook extends ModelHook { + + @Override + public String getName() { + return "clear_all_messages"; + } + + @Override + public HookPosition[] getHookPositions() { + return new HookPosition[]{HookPosition.AFTER_MODEL}; + } + + @Override + public CompletableFuture> afterModel(OverAllState state, RunnableConfig config) { + Optional messagesOpt = state.value("messages"); + if (!messagesOpt.isPresent()) { + return CompletableFuture.completedFuture(Map.of()); + } + + List messages = (List) messagesOpt.get(); + + // 将所有消息转为 RemoveByHash 对象以便从状态中删除 + List removeAllMessages = new ArrayList<>(); + for (Message msg : messages) { + removeAllMessages.add(RemoveByHash.of(msg)); + } + + return CompletableFuture.completedFuture(Map.of("messages", removeAllMessages)); + } } ``` @@ -265,8 +310,9 @@ public Map afterModel(OverAllState state, RunnableConfig config) ```java import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; +import com.alibaba.cloud.ai.graph.state.RemoveByHash; -public class DeleteOldMessagesHook implements ModelHook { +public class DeleteOldMessagesHook extends ModelHook { @Override public String getName() { @@ -279,20 +325,22 @@ public class DeleteOldMessagesHook implements ModelHook { } @Override - public Map afterModel(OverAllState state, RunnableConfig config) { + public CompletableFuture> afterModel(OverAllState state, RunnableConfig config) { Optional messagesOpt = state.value("messages"); if (!messagesOpt.isPresent()) { - return Map.of(); + return CompletableFuture.completedFuture(Map.of()); } List messages = (List) messagesOpt.get(); if (messages.size() > 2) { - // 移除最早的两条消息 - List trimmed = messages.subList(2, messages.size()); - return Map.of("messages", trimmed); + // 将最早的两条消息转为 RemoveByHash 对象以便从状态中删除 + List removeOldMessages = new ArrayList<>(); + removeOldMessages.add(RemoveByHash.of(messages.get(0))); + removeOldMessages.add(RemoveByHash.of(messages.get(1))); + return CompletableFuture.completedFuture(Map.of("messages", removeOldMessages)); } - return Map.of(); + return CompletableFuture.completedFuture(Map.of()); } } @@ -329,7 +377,7 @@ import org.springframework.ai.chat.messages.SystemMessage; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.prompt.Prompt; -public class MessageSummarizationHook implements ModelHook { +public class MessageSummarizationHook extends ModelHook { private final ChatModel summaryModel; private final int maxTokensBeforeSummary; @@ -356,27 +404,27 @@ public class MessageSummarizationHook implements ModelHook { } @Override - public Map beforeModel(OverAllState state, RunnableConfig config) { + public CompletableFuture> beforeModel(OverAllState state, RunnableConfig config) { Optional messagesOpt = state.value("messages"); if (!messagesOpt.isPresent()) { - return Map.of(); + return CompletableFuture.completedFuture(Map.of()); } List messages = (List) messagesOpt.get(); // 估算 token 数量(简化版) int estimatedTokens = messages.stream() - .mapToInt(m -> m.getContent().length() / 4) + .mapToInt(m -> m.getText().length() / 4) .sum(); if (estimatedTokens < maxTokensBeforeSummary) { - return Map.of(); + return CompletableFuture.completedFuture(Map.of()); } // 需要总结 int messagesToSummarize = messages.size() - messagesToKeep; if (messagesToSummarize <= 0) { - return Map.of(); + return CompletableFuture.completedFuture(Map.of()); } List oldMessages = messages.subList(0, messagesToSummarize); @@ -393,11 +441,16 @@ public class MessageSummarizationHook implements ModelHook { "## 之前对话摘要:\n" + summary ); - List newMessages = new ArrayList<>(); + List newMessages = new ArrayList<>(); newMessages.add(summaryMessage); newMessages.addAll(recentMessages); - return Map.of("messages", newMessages); + // IMPORTANT! Convert toSummarize messages to RemoveByHash objects so we can remove them from state + for (Message msg : messagesToSummarize) { + newMessages.add(RemoveByHash.of(msg)); + } + + return CompletableFuture.completedFuture(Map.of("messages", newMessages)); } private String generateSummary(List messages) { @@ -405,7 +458,7 @@ public class MessageSummarizationHook implements ModelHook { for (Message msg : messages) { conversation.append(msg.getMessageType()) .append(": ") - .append(msg.getContent()) + .append(msg.getText()) .append("\n"); } @@ -418,10 +471,6 @@ public class MessageSummarizationHook implements ModelHook { return response.getResult().getOutput().getContent(); } - @Override - public Map afterModel(OverAllState state, RunnableConfig config) { - return Map.of(); - } } // 使用 @@ -476,8 +525,8 @@ public class UserInfoTool implements BiFunction { @Override public String apply(String query, ToolContext toolContext) { // 从上下文中获取用户信息 - Map context = toolContext.getContext(); - String userId = (String) context.get("user_id"); + RunnableConfig config = (RunnableConfig) toolContext.getContext().get("config"); + String userId = (String) config.metadata("user_id").orElse(""); if ("user_123".equals(userId)) { return "用户是 John Smith"; @@ -520,11 +569,12 @@ Map context = Map.of("user_id", "user_123"); 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; -public class DynamicPromptInterceptor implements ModelInterceptor { +public class DynamicPromptInterceptor extends ModelInterceptor { @Override - public ModelResponse intercept(ModelRequest request, ModelCallHandler handler) { + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) { // 从上下文中获取用户名 Map context = request.getContext(); String userName = (String) context.get("user_name"); @@ -532,10 +582,25 @@ public class DynamicPromptInterceptor implements ModelInterceptor { // 创建动态系统提示 String systemPrompt = "你是一个有帮助的助手。称呼用户为 " + userName + "。"; - // 更新请求 - request.setSystemPrompt(systemPrompt); + // 创建修改后的请求(示例),实际使用中需要根据具体 API 进行调整 + SystemMessage enhancedSystemMessage; + if (request.getSystemMessage() == null) { + enhancedSystemMessage = new SystemMessage(systemPrompt); + } else { + enhancedSystemMessage = new SystemMessage(request.getSystemMessage().getText() + "\n\n" + systemPrompt); + } - return handler.handle(request); + // Create enhanced request + ModelRequest enhancedRequest = ModelRequest.builder(request) + .systemMessage(enhancedSystemMessage) + .build(); + + return handler.call(request); + } + + @Override + public String getName() { + return "DynamicPromptInterceptor"; } } @@ -543,7 +608,7 @@ ReactAgent agent = ReactAgent.builder() .name("my_agent") .model(chatModel) .tools(getWeatherTool) - .modelInterceptors(new DynamicPromptInterceptor()) + .interceptors(new DynamicPromptInterceptor()) .build(); // 使用时传递上下文 @@ -557,7 +622,7 @@ Map context = Map.of("user_name", "John Smith"); ```java import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; -public class TrimMessagesHook implements ModelHook { +public class TrimMessagesHook extends ModelHook { @Override public String getName() { @@ -570,31 +635,42 @@ public class TrimMessagesHook implements ModelHook { } @Override - public Map beforeModel(OverAllState state, RunnableConfig config) { + public CompletableFuture> beforeModel(OverAllState state, RunnableConfig config) { // 访问和修改消息 Optional messagesOpt = state.value("messages"); if (messagesOpt.isPresent()) { List messages = (List) messagesOpt.get(); if (messages.size() <= 3) { - return Map.of(); // 无需更改 + return CompletableFuture.completedFuture(Map.of()); // 无需更改 } - // 保留第一条和最后几条消息 + // 保留第一条和最后几条消息,并将中间消息标记为删除 Message firstMsg = messages.get(0); List recentMessages = messages.subList( messages.size() - 3, messages.size() ); - List newMessages = new ArrayList<>(); + List newMessages = new ArrayList<>(); newMessages.add(firstMsg); newMessages.addAll(recentMessages); + // 标记中间消息为删除(使用 RemoveByHash) + if (messages.size() - 3 > 1) { + for (Message msg : messages.subList(1, messages.size() - 3)) { + newMessages.add(com.alibaba.cloud.ai.graph.state.RemoveByHash.of(msg)); + } + } - return Map.of("messages", newMessages); + return CompletableFuture.completedFuture(Map.of("messages", newMessages)); } - return Map.of(); + return CompletableFuture.completedFuture(Map.of()); + } + + @Override + public CompletableFuture> afterModel(OverAllState state, RunnableConfig config) { + return CompletableFuture.completedFuture(Map.of()); } } @@ -614,7 +690,7 @@ ReactAgent agent = ReactAgent.builder() ```java import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; -public class ValidateResponseHook implements ModelHook { +public class ValidateResponseHook extends ModelHook { private static final List STOP_WORDS = List.of("password", "secret", "api_key"); @@ -630,19 +706,24 @@ public class ValidateResponseHook implements ModelHook { } @Override - public Map afterModel(OverAllState state, RunnableConfig config) { + public CompletableFuture> beforeModel(OverAllState state, RunnableConfig config) { + return CompletableFuture.completedFuture(Map.of()); + } + + @Override + public CompletableFuture> afterModel(OverAllState state, RunnableConfig config) { Optional messagesOpt = state.value("messages"); if (!messagesOpt.isPresent()) { - return Map.of(); + return CompletableFuture.completedFuture(Map.of()); } List messages = (List) messagesOpt.get(); if (messages.isEmpty()) { - return Map.of(); + return CompletableFuture.completedFuture(Map.of()); } Message lastMessage = messages.get(messages.size() - 1); - String content = lastMessage.getContent(); + String content = lastMessage.getText(); // 检查是否包含敏感词 for (String stopWord : STOP_WORDS) { @@ -652,11 +733,11 @@ public class ValidateResponseHook implements ModelHook { filtered.add(new AssistantMessage( "抱歉,我无法提供该信息。" )); - return Map.of("messages", filtered); + return CompletableFuture.completedFuture(Map.of("messages", filtered)); } } - return Map.of(); + return CompletableFuture.completedFuture(Map.of()); } } @@ -673,4 +754,3 @@ ReactAgent agent = ReactAgent.builder() * [Agents 文档](./agents.md) - 了解 ReactAgent 的核心概念 * [Hooks 和 Interceptors](./hooks.md) - 了解如何扩展 Agent 功能 * [Messages 文档](./messages.md) - 了解消息类型和使用 - diff --git a/docs/frameworks/agent-framework/tutorials/messages.md b/docs/frameworks/agent-framework/tutorials/messages.md index 9ba739bc..4c368289 100644 --- a/docs/frameworks/agent-framework/tutorials/messages.md +++ b/docs/frameworks/agent-framework/tutorials/messages.md @@ -161,10 +161,10 @@ import java.net.URL; // 从 URL 创建图像 UserMessage userMsg = UserMessage.builder() .text("描述这张图片的内容。") - .media(new Media( - MimeTypeUtils.IMAGE_JPEG, - new URL("https://example.com/image.jpg") - )) + .media(Media.builder() + .mimeType(MimeTypeUtils.IMAGE_JPEG) + .data(new URL("https://example.com/image.jpg")) + .build()) .build(); ``` @@ -238,7 +238,7 @@ ChatResponseMetadata metadata = response.getMetadata(); // 访问使用信息 if (metadata != null && metadata.getUsage() != null) { System.out.println("Input tokens: " + metadata.getUsage().getPromptTokens()); - System.out.println("Output tokens: " + metadata.getUsage().getGenerationTokens()); + System.out.println("Output tokens: " + metadata.getUsage().getCompletionTokens()); System.out.println("Total tokens: " + metadata.getUsage().getTotalTokens()); } ``` @@ -327,10 +327,10 @@ import java.net.URL; // 从 URL UserMessage message = UserMessage.builder() .text("描述这张图片的内容。") - .media(new Media( - MimeTypeUtils.IMAGE_JPEG, - new URL("https://example.com/path/to/image.jpg") - )) + .media(Media.builder() + .mimeType(MimeTypeUtils.IMAGE_JPEG) + .data(new URL("https://example.com/image.jpg")) + .build()) .build(); // 从本地文件 @@ -368,10 +368,10 @@ import org.springframework.util.MimeTypeUtils; UserMessage message = UserMessage.builder() .text("描述这段视频的内容。") - .media(new Media( - MimeTypeUtils.parseMimeType("video/mp4"), - new URL("https://example.com/path/to/video.mp4") - )) + .media(Media.builder() + .mimeType(MimeTypeUtils.parseMimeType("video/mp4")) + .data(new URL("https://example.com/path/to/video.mp4")) + .build()) .build(); ``` @@ -430,7 +430,7 @@ SystemMessage systemMsg = SystemMessage.builder() // AssistantMessage with builder AssistantMessage assistantMsg = AssistantMessage.builder() - .text("我很乐意帮助你学习 Spring AI Alibaba!") + .content("我很乐意帮助你学习 Spring AI Alibaba!") .build(); ``` diff --git a/docs/frameworks/agent-framework/tutorials/tools.md b/docs/frameworks/agent-framework/tutorials/tools.md index 149aa253..eddf0191 100644 --- a/docs/frameworks/agent-framework/tutorials/tools.md +++ b/docs/frameworks/agent-framework/tutorials/tools.md @@ -306,7 +306,7 @@ public class ConversationSummaryTool implements BiFunction config = (Map) toolContext.getContext().get("extraState"); + Map extraState = (Map) toolContext.getContext().get("extraState"); // 从state中获取消息 List messages = (List) state.get("messages", new ArrayList<>()); @@ -349,18 +349,30 @@ ToolCallback summaryTool = FunctionToolCallback ```java // 在 Hook 中更新状态 import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; +import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; import com.alibaba.cloud.ai.graph.OverAllState; import com.alibaba.cloud.ai.graph.RunnableConfig; +import java.util.concurrent.CompletableFuture; -public class UpdateStateHook implements ModelHook { +public class UpdateStateHook extends ModelHook { @Override - public Map afterModel(OverAllState state, RunnableConfig config) { + public String getName() { + return "update_state"; + } + + @Override + public HookPosition[] getHookPositions() { + return new HookPosition[]{HookPosition.AFTER_MODEL}; + } + + @Override + public CompletableFuture> afterModel(OverAllState state, RunnableConfig config) { // 更新状态 - return Map.of( + return CompletableFuture.completedFuture(Map.of( "user_name", "Alice", "last_updated", System.currentTimeMillis() - ); + )); } } ``` @@ -444,9 +456,8 @@ agent.call("question", config); ```java import com.alibaba.cloud.ai.graph.checkpoint.savers.RedisSaver; - // 配置持久化存储 -RedisSaver redisSaver = new RedisSaver(redisConnectionFactory); +RedisSaver redisSaver = new RedisSaver(redissonClient); // 创建带有持久化记忆的 Agent ReactAgent agent = ReactAgent.builder() diff --git a/docs/overview.md b/docs/overview.md index 3d87c79b..c8bbe7f7 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -34,7 +34,7 @@ Spring AI Alibaba 项目从架构上包含如下三层: com.alibaba.cloud.ai - spring-ai-alibaba-starter-agentscope + spring-ai-alibaba-starter-dashscope 1.1.0.0-M4 ``` diff --git a/src/components/CodeBlockWithLink/index.tsx b/src/components/CodeBlockWithLink/index.tsx new file mode 100644 index 00000000..87fede4b --- /dev/null +++ b/src/components/CodeBlockWithLink/index.tsx @@ -0,0 +1,67 @@ +import React from 'react' +import CodeBlock from '@theme/CodeBlock' +import styles from './styles.module.css' + +interface CodeBlockWithLinkProps { + children: string + language?: string + title?: string + sourceUrl?: string + showLineNumbers?: boolean +} + +/** + * 自定义代码块组件,支持在代码段上方显示源文件链接 + * + * 使用示例: + * + * {`public class SearchTool { ... }`} + * + */ +const CodeBlockWithLink: React.FC = ({ + children, + language = 'text', + title, + sourceUrl, + showLineNumbers = false, +}) => { + return ( +
+ {(title || sourceUrl) && ( +
+ {title && {title}} + {sourceUrl && ( + + + + + 查看完整代码 + + )} +
+ )} + + {children} + +
+ ) +} + +export default CodeBlockWithLink + diff --git a/src/components/CodeBlockWithLink/styles.module.css b/src/components/CodeBlockWithLink/styles.module.css new file mode 100644 index 00000000..596f2d92 --- /dev/null +++ b/src/components/CodeBlockWithLink/styles.module.css @@ -0,0 +1,71 @@ +.codeBlockContainer { + margin: 1rem 0; + border-radius: var(--ifm-code-border-radius); + overflow: hidden; + box-shadow: var(--ifm-global-shadow-lw); +} + +.codeBlockHeader { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 1rem; + background: var(--ifm-code-background); + border-bottom: 1px solid var(--ifm-color-emphasis-300); + font-size: 0.875rem; +} + +[data-theme='dark'] .codeBlockHeader { + background: #1e1e1e; + border-bottom-color: var(--ifm-color-emphasis-200); +} + +.codeBlockTitle { + font-family: var(--ifm-font-family-monospace); + font-weight: 600; + color: var(--ifm-color-emphasis-800); + display: flex; + align-items: center; +} + +[data-theme='dark'] .codeBlockTitle { + color: var(--ifm-color-emphasis-600); +} + +.codeBlockLink { + display: flex; + align-items: center; + gap: 0.35rem; + color: var(--ifm-color-primary); + text-decoration: none; + font-weight: 500; + transition: all 0.2s ease; + padding: 0.25rem 0.5rem; + border-radius: 4px; +} + +.codeBlockLink:hover { + color: var(--ifm-color-primary-dark); + background: var(--ifm-color-emphasis-100); + text-decoration: none; +} + +[data-theme='dark'] .codeBlockLink:hover { + background: var(--ifm-color-emphasis-200); +} + +.linkIcon { + flex-shrink: 0; +} + +/* 移除 CodeBlock 组件自带的 margin */ +.codeBlockContainer :global(.prism-code) { + margin: 0; + border-radius: 0; +} + +.codeBlockContainer :global(pre) { + margin: 0; + border-radius: 0 0 var(--ifm-code-border-radius) var(--ifm-code-border-radius); +} + diff --git a/src/theme/MDXComponents.tsx b/src/theme/MDXComponents.tsx new file mode 100644 index 00000000..328000c6 --- /dev/null +++ b/src/theme/MDXComponents.tsx @@ -0,0 +1,11 @@ +import React from 'react' +import MDXComponents from '@theme-original/MDXComponents' +import CodeBlockWithLink from '@site/src/components/CodeBlockWithLink' + +export default { + ...MDXComponents, + CodeBlockWithLink, + // 也可以用简短的别名 + Code: CodeBlockWithLink, +} + From 5e19b695d7ccd660481ffbe50e8742ecccb7614e Mon Sep 17 00:00:00 2001 From: "ken.lj" Date: Sun, 16 Nov 2025 18:15:29 +0800 Subject: [PATCH 2/6] update doc Change-Id: Ibeb9cb238490c39d4919a418b34aa84435e53b74 --- .../agent-framework/tutorials/agents.md | 123 ++++++++---------- .../agent-framework/tutorials/hooks.md | 4 +- .../agent-framework/tutorials/memory.md | 4 +- .../agent-framework/tutorials/messages.md | 2 +- .../agent-framework/tutorials/models.md | 26 ++-- .../tutorials/structured-output.md | 2 - .../agent-framework/tutorials/tools.md | 6 +- 7 files changed, 73 insertions(+), 94 deletions(-) diff --git a/docs/frameworks/agent-framework/tutorials/agents.md b/docs/frameworks/agent-framework/tutorials/agents.md index fa924509..efceb6a2 100644 --- a/docs/frameworks/agent-framework/tutorials/agents.md +++ b/docs/frameworks/agent-framework/tutorials/agents.md @@ -93,9 +93,9 @@ ReactAgent agent = ReactAgent.builder() ChatModel chatModel = DashScopeChatModel.builder() .dashScopeApi(dashScopeApi) .defaultOptions(DashScopeChatOptions.builder() - .temperature(0.7) // 控制随机性 - .maxTokens(2000) // 最大输出长度 - .topP(0.9) // 核采样参数 + .withTemperature(0.7) // 控制随机性 + .withMaxToken(2000) // 最大输出长度 + .withTopP(0.9) // 核采样参数 .build()) .build();`} @@ -119,52 +119,31 @@ ChatModel chatModel = DashScopeChatModel.builder() > {`import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.function.FunctionToolCallback; +import org.springframework.ai.tool.annotation.ToolParam; +import org.springframework.ai.chat.model.ToolContext; import java.util.function.BiFunction; -// 定义工具 +// 定义工具(示例:仅一个搜索工具) public class SearchTool implements BiFunction { - @Override - public String apply( - @ToolParam(description = "搜索关键词") String query, - ToolContext toolContext) { - return "搜索结果:" + query; - } -} - -// 创建工具回调 -ToolCallback searchTool = FunctionToolCallback - .builder("search", new SearchTool()) - .description("搜索信息的工具") - .inputType(String.class) - .build(); - -// 使用多个工具 -ReactAgent agent = ReactAgent.builder() - .name("my_agent") - .model(chatModel) - .tools(searchTool, weatherTool, calculatorTool) - .build();`} - - -#### 工具错误处理 - -使用 `ToolInterceptor` 统一处理工具错误: - - {`public class ToolErrorInterceptor extends ToolInterceptor { - @Override - public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) { - try { - return handler.call(request); - } catch (Exception e) { - return ToolCallResponse.of(request.getToolCallId(), request.getToolName(), - "Tool failed: " + e.getMessage()); - } - } + @Override + public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) { + try { + return handler.call(request); + } catch (Exception e) { + return ToolCallResponse.of(request.getToolCallId(), request.getToolName(), + "Tool failed: " + e.getMessage()); + } + } + + @Override + public String getName() { + return "ToolErrorInterceptor"; + } } ReactAgent agent = ReactAgent.builder() @@ -240,23 +219,30 @@ ReactAgent agent = ReactAgent.builder() 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 org.springframework.ai.chat.messages.SystemMessage; public class DynamicPromptInterceptor extends ModelInterceptor { @Override public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) { - // 基于用户角色动态调整提示 - List messages = request.getMessages(); - Map context = request.getContext(); - - // do anything with messages to adjust prompt, history messages, user request dynamically - - // create modified request - ModelRequest modifiedRequest = ModelRequest.builder() - .messages(messages) - .options(request.getOptions()) - .tools(request.getTools()) - .build(); - return handler.call(modifiedRequest); + // 基于上下文构建动态 system prompt + String userRole = (String) request.getContext().getOrDefault("user_role", "default"); + String dynamicPrompt = switch (userRole) { + case "expert" -> "你正在与技术专家对话。\n- 使用专业术语\n- 深入技术细节"; + case "beginner" -> "你正在与初学者对话。\n- 使用简单语言\n- 解释基础概念"; + default -> "你是一个专业的助手,保持友好和专业。"; + }; + + SystemMessage enhancedSystemMessage; + if (request.getSystemMessage() == null) { + enhancedSystemMessage = new SystemMessage(dynamicPrompt); + } else { + enhancedSystemMessage = new SystemMessage(request.getSystemMessage().getText() + "\n\n" + dynamicPrompt); + } + + ModelRequest modified = ModelRequest.builder(request) + .systemMessage(enhancedSystemMessage) + .build(); + return handler.call(modified); } @Override @@ -422,7 +408,7 @@ AssistantMessage response = agent.call("分析这段文本:春天来了,万 ### Memory(记忆) -Agent 通过状态自动维护对话历史。使用 `CompileConfig` 配置持久化存储。 +Agent 通过状态自动维护对话历史。使用 `MemorySaver` 配置持久化存储。 beforeAgent(OverAllState state, RunnableConfig config) { + public CompletableFuture> beforeAgent(OverAllState state, RunnableConfig config) { System.out.println("Agent 开始执行"); - return Map.of(); + return CompletableFuture.completedFuture(Map.of()); } @Override - public Map afterAgent(OverAllState state, RunnableConfig config) { + public CompletableFuture> afterAgent(OverAllState state, RunnableConfig config) { System.out.println("Agent 执行完成"); - return Map.of(); + return CompletableFuture.completedFuture(Map.of()); } } @@ -543,7 +522,7 @@ Interceptors 提供更细粒度的控制,可以拦截和修改模型调用和 // ModelInterceptor - 内容安全检查 public class GuardrailInterceptor extends ModelInterceptor { @Override - public ModelResponse intercept(ModelRequest request, ModelCallHandler handler) { + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) { // 前置:检查输入 if (containsSensitiveContent(request.getMessages())) { return ModelResponse.blocked("检测到不适当的内容"); @@ -560,7 +539,7 @@ public class GuardrailInterceptor extends ModelInterceptor { // ToolInterceptor - 监控和错误处理 public class ToolMonitoringInterceptor extends ToolInterceptor { @Override - public ToolCallResponse intercept(ToolCallRequest request, ToolCallHandler handler) { + public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) { long startTime = System.currentTimeMillis(); try { ToolCallResponse response = handler.call(request); @@ -670,7 +649,7 @@ ReactAgent agent = ReactAgent.builder() > {`import reactor.core.publisher.Flux; -Flux stream = agent.stream("复杂任务"); +Flux stream = agent.stream("复杂任务"); stream.subscribe( response -> System.out.println("进度: " + response), error -> System.err.println("错误: " + error), diff --git a/docs/frameworks/agent-framework/tutorials/hooks.md b/docs/frameworks/agent-framework/tutorials/hooks.md index a942ae26..2ddfb7f9 100644 --- a/docs/frameworks/agent-framework/tutorials/hooks.md +++ b/docs/frameworks/agent-framework/tutorials/hooks.md @@ -610,8 +610,8 @@ ReactAgent agent = ReactAgent.builder() .name("my_agent") .model(chatModel) .hooks(hook1, hook2, hook3) - .modelInterceptors(interceptor1, interceptor2) - .toolInterceptors(toolInterceptor1, toolInterceptor2) + .interceptors(interceptor1, interceptor2) + .interceptors(toolInterceptor1, toolInterceptor2) .build(); ``` diff --git a/docs/frameworks/agent-framework/tutorials/memory.md b/docs/frameworks/agent-framework/tutorials/memory.md index 64679ad3..2cb188d0 100644 --- a/docs/frameworks/agent-framework/tutorials/memory.md +++ b/docs/frameworks/agent-framework/tutorials/memory.md @@ -468,7 +468,7 @@ public class MessageSummarizationHook extends ModelHook { new Prompt(new UserMessage(summaryPrompt)) ); - return response.getResult().getOutput().getContent(); + return response.getResult().getOutput().getText(); } } @@ -595,7 +595,7 @@ public class DynamicPromptInterceptor extends ModelInterceptor { .systemMessage(enhancedSystemMessage) .build(); - return handler.call(request); + return handler.call(enhancedRequest); } @Override diff --git a/docs/frameworks/agent-framework/tutorials/messages.md b/docs/frameworks/agent-framework/tutorials/messages.md index 4c368289..49512fae 100644 --- a/docs/frameworks/agent-framework/tutorials/messages.md +++ b/docs/frameworks/agent-framework/tutorials/messages.md @@ -276,7 +276,7 @@ import org.springframework.ai.chat.messages.ToolResponseMessage.ToolResponse; // 在模型进行工具调用后 AssistantMessage aiMessage = AssistantMessage.builder() - .text("") + .content("") .toolCalls(List.of( new AssistantMessage.ToolCall( "call_123", diff --git a/docs/frameworks/agent-framework/tutorials/models.md b/docs/frameworks/agent-framework/tutorials/models.md index 14fb5965..4d4504d7 100644 --- a/docs/frameworks/agent-framework/tutorials/models.md +++ b/docs/frameworks/agent-framework/tutorials/models.md @@ -288,7 +288,7 @@ Prompt prompt = new Prompt(new UserMessage("解释什么是微服务架构")); // 调用并获取响应 ChatResponse response = chatModel.call(prompt); -String answer = response.getResult().getOutput().getContent(); +String answer = response.getResult().getOutput().getText(); System.out.println(answer); ``` @@ -300,10 +300,10 @@ System.out.println(answer); import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; DashScopeChatOptions options = DashScopeChatOptions.builder() - .model("qwen-plus") // 模型名称 - .temperature(0.7) // 温度参数 - .maxTokens(2000) // 最大令牌数 - .topP(0.9) // Top-P 采样 + .withModel("qwen-plus") // 模型名称 + .withTemperature(0.7) // 温度参数 + .withMaxToken(2000) // 最大令牌数 + .withTopP(0.9) // Top-P 采样 .build(); ChatModel chatModel = DashScopeChatModel.builder() @@ -317,8 +317,8 @@ ChatModel chatModel = DashScopeChatModel.builder() ```java // 创建带有特定选项的 Prompt DashScopeChatOptions runtimeOptions = DashScopeChatOptions.builder() - .temperature(0.3) // 更低的温度,更确定的输出 - .maxTokens(500) + .withTemperature(0.3) // 更低的温度,更确定的输出 + .withMaxToken(500) .build(); Prompt prompt = new Prompt( @@ -344,7 +344,7 @@ responseStream.subscribe( chatResponse -> { String content = chatResponse.getResult() .getOutput() - .getContent(); + .getText(); System.out.print(content); }, error -> System.err.println("错误: " + error.getMessage()), @@ -387,11 +387,11 @@ DashScopeChatModel 支持函数调用(Function Calling),允许模型调用 ```java import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.function.FunctionCallback; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; -// 定义函数 -FunctionCallback weatherFunction = FunctionCallback.builder() - .function("getWeather", (city) -> { +// 定义函数工具 +ToolCallback weatherFunction = FunctionToolCallback.builder("getWeather", (String city) -> { // 实际的天气查询逻辑 return "晴朗,25°C"; }) @@ -401,7 +401,7 @@ FunctionCallback weatherFunction = FunctionCallback.builder() // 使用函数 DashScopeChatOptions options = DashScopeChatOptions.builder() - .functions(List.of(weatherFunction)) + .withToolCallbacks(List.of(weatherFunction)) .build(); Prompt prompt = new Prompt("北京的天气怎么样?", options); diff --git a/docs/frameworks/agent-framework/tutorials/structured-output.md b/docs/frameworks/agent-framework/tutorials/structured-output.md index a0ad6f04..c517acf5 100644 --- a/docs/frameworks/agent-framework/tutorials/structured-output.md +++ b/docs/frameworks/agent-framework/tutorials/structured-output.md @@ -174,7 +174,6 @@ ChatOptions options = DashScopeChatOptions.builder() 同时,Spring AI Alibaba 框架会增强系统 Prompt,引导模型输出格式化内容 -```java ```java // In AgentLlmNode.augmentUserMessage() method public void augmentUserMessage(List messages, String outputSchema) { @@ -193,7 +192,6 @@ public void augmentUserMessage(List messages, String outputSchema) { } } ``` -``` > 注意,相比于 DashScope 模型是通过增强 Prompt 提示词实现最终的 JSON 格式,实现的是一个尽最大努力的效果,OpenAI 模型则是在模型 API 层面支持 Json 格式,提供格式的严格保证支持。 diff --git a/docs/frameworks/agent-framework/tutorials/tools.md b/docs/frameworks/agent-framework/tutorials/tools.md index eddf0191..fb2bf784 100644 --- a/docs/frameworks/agent-framework/tutorials/tools.md +++ b/docs/frameworks/agent-framework/tutorials/tools.md @@ -212,6 +212,8 @@ ToolCallback searchTool = FunctionToolCallback .build(); System.out.println(searchTool.getName()); // web_search +// 推荐:使用 ToolDefinition 提取名称 +System.out.println(searchTool.getToolDefinition().name()); // web_search ``` #### 自定义工具描述 @@ -408,8 +410,8 @@ public class AccountInfoTool implements BiFunction // 在agent调用时设置 user_id,在工具中可以拿到参数 // RunnableConfig config = RunnableConfig.builder().addMetadata("user_id", "1"); // agent.call("", config); - RunnableConfig config = (RunnableConfig) toolContext.getContext().get("config"); - String userId = (String) config.metadata("user_id"); + RunnableConfig config = (RunnableConfig) toolContext.getContext().get("config"); + String userId = (String) config.metadata("user_id").orElse(null); if (userId == null) { return "User ID not provided"; From cbbd09d25b724f7efc20a86e7f87e54c660fab9c Mon Sep 17 00:00:00 2001 From: "ken.lj" Date: Mon, 17 Nov 2025 10:41:04 +0800 Subject: [PATCH 3/6] update doc Change-Id: Ifefbabb210a770a8da8d52c01c38a77cc7130a24 --- .../agent-framework/advanced/a2a.md | 307 ++++++++++++++- .../agent-framework/advanced/agent-tool.md | 66 ++-- .../advanced/context-engineering.md | 364 ++++++++++++------ .../advanced/human-in-the-loop.md | 87 +++-- .../agent-framework/advanced/memory.md | 177 ++++++--- .../agent-framework/advanced/multi-agent.md | 125 +++--- .../agent-framework/advanced/rag.md | 284 ++++++++++---- .../agent-framework/advanced/workflow.md | 205 +++++----- .../agent-framework/tutorials/agents.md | 78 ++-- .../agent-framework/tutorials/hooks.md | 231 +++++++---- .../agent-framework/tutorials/memory.md | 218 +++++++---- .../agent-framework/tutorials/messages.md | 233 +++++++---- .../agent-framework/tutorials/models.md | 187 +++++---- .../tutorials/structured-output.md | 117 ++++-- .../agent-framework/tutorials/tools.md | 200 ++++++---- docs/quick-start.md | 2 +- sidebars/sidebars-agent-framework.ts | 8 +- sidebars/sidebars-graph-core.ts | 10 +- 18 files changed, 1989 insertions(+), 910 deletions(-) diff --git a/docs/frameworks/agent-framework/advanced/a2a.md b/docs/frameworks/agent-framework/advanced/a2a.md index ab5e3583..50d70126 100644 --- a/docs/frameworks/agent-framework/advanced/a2a.md +++ b/docs/frameworks/agent-framework/advanced/a2a.md @@ -1,31 +1,306 @@ --- title: 分布式智能体(A2A Agent) -description: 了解如何在Spring AI Alibaba中实现Multi-agent协作,包括工具调用和交接模式 -keywords: [Multi-agent, Multi-Agent, 工具调用, Tool Calling, Handoffs, Agent协作, 子Agent] +description: 了解如何在Spring AI Alibaba中使用A2A协议实现分布式Agent通信,包括Agent注册、发现和远程调用 +keywords: [A2A, Agent-to-Agent, 分布式Agent, 远程Agent, Nacos, Agent注册, Agent发现] --- -## 分布式 Agent -### A2A 协议简介 +## A2A 协议简介 + 随着智能体应用的广泛落地,智能体应用间的分布式部署与远程通信成为要解决的关键问题,Google 推出的 [Agent2Agent(A2A)协议](https://a2a-protocol.org/latest/)即面向这一落地场景:A2A 解决智能体与其他使用不同框架、部署在不同机器、不同公司的智能体进行有效通信和协作的问题。 -![](https://intranetproxy.alipay.com/skylark/lark/0/2025/png/54037/1756226684101-03a0e1a3-78cc-49f3-870d-5e17f373ec12.png) +A2A 协议定义了智能体之间通信的标准方式,使得不同框架、不同部署环境的智能体能够无缝协作。 + +## A2A 架构 + +Spring AI Alibaba 的 A2A 实现包含三个核心组件: + +1. **A2A Server**:将本地 ReactAgent 暴露为 A2A 服务 +2. **A2A Registry**:Agent 注册中心(支持 Nacos) +3. **A2A Discovery**:Agent 发现机制(支持 Nacos) + +### 注册与发现流程 + +``` +┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐ +│ Agent Provider │ │ Nacos │ │ Agent Consumer │ +│ (本地Agent) │────────▶│ Registry │◀────────│ (远程调用) │ +└─────────────────┘ └──────────────┘ └─────────────────┘ + │ │ │ + │ 1. 注册 AgentCard │ │ + │───────────────────────────▶│ │ + │ │ │ + │ │ 2. 查询 AgentCard │ + │ │◀──────────────────────────│ + │ │ │ + │ 3. 远程调用 │ │ + │◀───────────────────────────│ │ + │ │ │ +``` + +## 发布 A2A 智能体 + +要将一个智能体发布为 A2A 服务,需要: + +1. 创建 ReactAgent Bean +2. 配置 A2A Server +3. (可选)配置 Nacos Registry 进行自动注册 + +### 定义本地 Agent + + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class A2AAgentConfig { + + @Bean(name = "dataAnalysisAgent") + public ReactAgent dataAnalysisAgent(@Qualifier("dashscopeChatModel") ChatModel chatModel) { + return ReactAgent.builder() + .name("data_analysis_agent") + .model(chatModel) + .description("专门用于数据分析和统计计算的本地智能体") + .instruction("你是一个专业的数据分析专家,擅长处理各类数据统计和分析任务。" + + "你能够理解用户的数据分析需求,提供准确的统计计算结果和专业的分析建议。") + .outputKey("messages") + .build(); + } +}`} + -### Spring AI Alibaba 中如何调用 A2A 远程智能体 -A2aRemoteAgent 用来声明一个远程通信智能体,通过 AgentCard 指定远程智能体信息,包括连接的 url 地址、capabilities 等。 +### 配置 A2A Server -![](https://intranetproxy.alipay.com/skylark/lark/0/2025/png/54037/1756258006246-75d69627-9602-464c-a9c7-598791387b70.png) +在 `application.yml` 中配置 A2A Server: -以上示例中分别实现了两个远程智能体,然后使用 SequentialAgent 将两个智能体组成串行流程,就像使用同进程内的智能体一样。可在此查看以上代码片段[完整示例](https://github.com/alibaba/spring-ai-alibaba/blob/main/spring-ai-alibaba-graph-core/src/test/java/com/alibaba/cloud/ai/graph/agent/RemoteAgentTest.java)。 + +{`spring: + ai: + alibaba: + a2a: + server: + version: 1.0.0 + card: + name: data_analysis_agent + description: 专门用于数据分析和统计计算的本地智能体 + provider: + name: Spring AI Alibaba Documentation + organization: Spring AI Alibaba`} + -![](https://intranetproxy.alipay.com/skylark/lark/0/2025/png/54037/1756258024121-662ad249-3107-49cc-bf4c-1e0c1f316452.png) +启动应用后,A2A Server 会自动: +- 根据 ReactAgent Bean 生成 AgentCard +- 暴露 REST API 端点: + - `/.well-known/agent.json` - AgentCard 元数据 + - `/a2a/message` - Agent 调用端点 -### Spring AI Alibaba 中如何发布 A2A 智能体 -如果要将一个智能体发布为 A2A 服务,需要增加 Spring AI Alibaba Runtime 组件依赖, Spring AI Alibaba Runtime 负责将智能体发布出去。具体请参见后续 Spring AI Alibaba Runtime 文章解读。 +## 调用 A2A 远程智能体 + +### 使用 AgentCardProvider 发现 Agent + +Spring AI Alibaba 支持通过 `AgentCardProvider` 从注册中心(如 Nacos)发现远程 Agent。 + + +{`import com.alibaba.cloud.ai.graph.agent.a2a.A2aRemoteAgent; +import com.alibaba.cloud.ai.graph.agent.a2a.AgentCardProvider; +import com.alibaba.cloud.ai.graph.OverAllState; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Component +public class A2AExample { + + private final AgentCardProvider agentCardProvider; + + @Autowired + public A2AExample(AgentCardProvider agentCardProvider) { + this.agentCardProvider = agentCardProvider; + } + + public void callRemoteAgent() { + // 通过 AgentCardProvider 从注册中心发现 Agent + A2aRemoteAgent remote = A2aRemoteAgent.builder() + .name("data_analysis_agent") + .agentCardProvider(agentCardProvider) // 从 Nacos 自动获取 AgentCard + .description("数据分析远程代理") + .build(); + + // 远程调用 + Optional result = remote.invoke("请根据季度数据给出同比与环比分析概要。"); + + result.ifPresent(state -> { + System.out.println("调用成功: " + state.value("output")); + }); + } +}`} + + +## 基于 Nacos 的 A2A Registry -### 基于 Nacos A2A Registry 的远程智能体自动发现 Nacos3 最新版本提供了 A2A AgentCard 模型的存储与推送支持,因此可以作为 A2A Registry 实现。Spring AI Alibaba 通过与 Nacos A2A Registry 集成,可以实现: -1. A2A Server AgentCard 自动注册到 Nacos A2A Registry -2. A2a Client 自动订阅发现可用 AgentCard,实现 AgentCard 调用的负载均衡 +1. **A2A Server AgentCard 自动注册到 Nacos A2A Registry** +2. **A2a Client 自动订阅发现可用 AgentCard,实现 AgentCard 调用的负载均衡** + +### 配置 Nacos Registry 和 Discovery + + +{`spring: + ai: + alibaba: + a2a: + nacos: + server-addr: 127.0.0.1:8848 + username: nacos + password: nacos + discovery: + enabled: true # 启用服务发现(查询其他 Agent) + registry: + enabled: true # 启用服务注册(注册本地 Agent) + server: + version: 1.0.0 + card: + name: data_analysis_agent + description: 专门用于数据分析和统计计算的本地智能体 + provider: + name: Spring AI Alibaba Documentation + organization: Spring AI Alibaba`} + + +**重要配置说明**: +- `registry.enabled: true` - 必须启用才能将 Agent 注册到 Nacos(服务提供者) +- `discovery.enabled: true` - 启用后才能通过 AgentCardProvider 发现其他 Agent(服务消费者) +- `server.card.name` - 必须与 ReactAgent Bean 的 `name` 一致 + +### 完整示例 + + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.agent.a2a.A2aRemoteAgent; +import com.alibaba.cloud.ai.graph.agent.a2a.AgentCardProvider; +import com.alibaba.cloud.ai.graph.OverAllState; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Component +public class A2AExample { + + private final ChatModel chatModel; + private final AgentCardProvider agentCardProvider; + private final ReactAgent localDataAnalysisAgent; + + @Autowired + public A2AExample(@Qualifier("dashscopeChatModel") ChatModel chatModel, + AgentCardProvider agentCardProvider, + @Qualifier("dataAnalysisAgent") ReactAgent localDataAnalysisAgent) { + this.chatModel = chatModel; + this.agentCardProvider = agentCardProvider; + this.localDataAnalysisAgent = localDataAnalysisAgent; + } + + public void runDemo() { + // 1. 本地直连:验证本地注册的 ReactAgent 可用 + Optional localResult = localDataAnalysisAgent.invoke( + "请对上月销售数据进行趋势分析,并给出关键结论。" + ); + localResult.ifPresent(state -> { + System.out.println("本地调用成功"); + }); + + // 2. 发现:通过 AgentCardProvider 从注册中心获取该 Agent 的 AgentCard + A2aRemoteAgent remote = A2aRemoteAgent.builder() + .name("data_analysis_agent") + .agentCardProvider(agentCardProvider) // 从 Nacos 自动获取 AgentCard + .description("数据分析远程代理") + .build(); + + // 3. 远程调用:通过 A2aRemoteAgent 调用 + Optional remoteResult = remote.invoke( + "请根据季度数据给出同比与环比分析概要。" + ); + remoteResult.ifPresent(state -> { + System.out.println("远程调用成功"); + }); + } +}`} + + +## 验证和测试 + +### 验证本地 AgentCard + +```bash +curl http://localhost:8080/.well-known/agent.json +``` + +### 验证 Nacos 注册 + +1. 打开 Nacos 控制台:http://localhost:8848/nacos +2. 登录(nacos/nacos) +3. 查看 A2A 服务注册维度,应该能看到注册的 Agent + +### 注册与发现的区别 + +| 功能 | 配置项 | 作用 | 角色 | +|------|--------|------|------| +| **Registry(注册)** | `registry.enabled: true` | 将本地 Agent 注册到 Nacos | 服务提供者 | +| **Discovery(发现)** | `discovery.enabled: true` | 从 Nacos 查询其他 Agent | 服务消费者 | + +两者可独立配置,也可同时启用。当同时启用时: +- 作为**提供者**:注册本地 Agent 到 Nacos +- 作为**消费者**:可发现并调用其他已注册的 Agent(包括自己) + +## 注意事项 + +1. **依赖要求**: + - 需要添加 `spring-ai-alibaba-starter-a2a-nacos` 依赖 + - 确保 Nacos 服务正常运行 + +2. **AgentCard 元数据**: + - `server.card.name` 必须与 ReactAgent Bean 的 `name` 一致 + - `server.card.provider` 可选,用于标识 Agent 提供者信息 + +3. **多 Agent 注册**: + - 默认情况下,只有一个 Agent Bean 会被注册 + - 如需注册多个 Agent,需运行多个应用实例,每个实例配置不同的 Agent + +## 故障排查 + +### Agent 没有注册到 Nacos +- 检查 `registry.enabled: true` 是否配置 +- 查看应用日志,确认 Nacos Registry AutoConfiguration 是否生效 +- 验证 Nacos 连接配置(server-addr、username、password) + +### AgentCardProvider 无法发现 Agent +- 检查 `discovery.enabled: true` 是否配置 +- 确认 Agent 已成功注册到 Nacos +- 验证 agent name 是否匹配 -![](https://intranetproxy.alipay.com/skylark/lark/0/2025/png/54037/1756226678279-775bf8da-b273-4fc9-b302-bc3618b0bd99.png) +### 远程调用失败 +- 确认目标 Agent 的 REST API 端点可访问 +- 检查网络连接和防火墙配置 +- 查看 A2A 消息传输日志 diff --git a/docs/frameworks/agent-framework/advanced/agent-tool.md b/docs/frameworks/agent-framework/advanced/agent-tool.md index 8449d904..7febae7c 100644 --- a/docs/frameworks/agent-framework/advanced/agent-tool.md +++ b/docs/frameworks/agent-framework/advanced/agent-tool.md @@ -62,8 +62,11 @@ Multi-agent设计的核心是**上下文工程**——决定每个Agent看到什 下面是一个最小示例,其中主Agent通过工具定义访问单个子Agent: -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.agent.tool.AgentTool; import org.springframework.ai.chat.model.ChatModel; @@ -84,8 +87,8 @@ ReactAgent blogAgent = ReactAgent.builder() .build(); // 使用 -Optional result = blogAgent.invoke("帮我写一个100字左右的散文"); -``` +Optional result = blogAgent.invoke("帮我写一个100字左右的散文");`} + 在这种模式中: @@ -111,8 +114,11 @@ Optional result = blogAgent.invoke("帮我写一个100字左右的 #### 使用 inputSchema -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.agent.tool.AgentTool; // 定义子Agent的输入Schema @@ -139,15 +145,18 @@ ReactAgent coordinatorAgent = ReactAgent.builder() .tools(AgentTool.getFunctionToolCallback(writerAgent)) .build(); -Optional result = coordinatorAgent.invoke("请写一篇关于春天的散文,大约150字"); -``` +Optional result = coordinatorAgent.invoke("请写一篇关于春天的散文,大约150字");`} + #### 使用 inputType 使用 Java 类型定义输入,框架会自动生成 JSON Schema: -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.agent.tool.AgentTool; // 定义输入类型 @@ -172,8 +181,8 @@ ReactAgent coordinatorAgent = ReactAgent.builder() .tools(AgentTool.getFunctionToolCallback(writerAgent)) .build(); -Optional result = coordinatorAgent.invoke("请写一篇关于秋天的现代诗,大约100字"); -``` +Optional result = coordinatorAgent.invoke("请写一篇关于秋天的现代诗,大约100字");`} + ### 控制子Agent的输出 @@ -186,8 +195,11 @@ Optional result = coordinatorAgent.invoke("请写一篇关于秋 #### 使用 outputSchema -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.agent.tool.AgentTool; // 定义输出Schema @@ -215,15 +227,18 @@ ReactAgent coordinatorAgent = ReactAgent.builder() .tools(AgentTool.getFunctionToolCallback(writerAgent)) .build(); -Optional result = coordinatorAgent.invoke("写一篇关于冬天的短文"); -``` +Optional result = coordinatorAgent.invoke("写一篇关于冬天的短文");`} + #### 使用 outputType 使用 Java 类型定义输出,框架会自动生成输出 schema: -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.agent.tool.AgentTool; // 定义输出类型 @@ -250,15 +265,18 @@ ReactAgent coordinatorAgent = ReactAgent.builder() .tools(AgentTool.getFunctionToolCallback(writerAgent)) .build(); -Optional result = coordinatorAgent.invoke("写一篇关于夏天的小诗"); -``` +Optional result = coordinatorAgent.invoke("写一篇关于夏天的小诗");`} + ### 完整类型化示例 同时使用 `inputType` 和 `outputType` 进行完整的类型化Agent工具调用: -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.agent.tool.AgentTool; // 定义输入和输出类型 @@ -306,7 +324,7 @@ ReactAgent orchestratorAgent = ReactAgent.builder() ) .build(); -Optional result = orchestratorAgent.invoke("请写一篇关于友谊的散文,约200字,需要评审"); -``` +Optional result = orchestratorAgent.invoke("请写一篇关于友谊的散文,约200字,需要评审");`} + diff --git a/docs/frameworks/agent-framework/advanced/context-engineering.md b/docs/frameworks/agent-framework/advanced/context-engineering.md index 16a634c0..086891ff 100644 --- a/docs/frameworks/agent-framework/advanced/context-engineering.md +++ b/docs/frameworks/agent-framework/advanced/context-engineering.md @@ -127,18 +127,20 @@ keywords: #### 基于状态的动态提示 -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`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 { - +class StateAwarePromptInterceptor extends ModelInterceptor { @Override - public ModelResponse intercept(ModelRequest request, ModelCallHandler next) { + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler next) { List messages = request.getMessages(); int messageCount = messages.size(); @@ -150,20 +152,27 @@ public class StateAwarePromptInterceptor implements ModelInterceptor { basePrompt += "\n这是一个长对话 - 请尽量保持精准简捷。"; } - // 更新系统消息 - messages = updateSystemMessage(messages, basePrompt); + // 更新系统消息(参考 TodoListInterceptor 的实现方式) + SystemMessage enhancedSystemMessage; + if (request.getSystemMessage() == null) { + enhancedSystemMessage = new SystemMessage(basePrompt); + } else { + enhancedSystemMessage = new SystemMessage( + request.getSystemMessage().getText() + "\n\n" + basePrompt + ); + } // 创建新的请求并继续 ModelRequest updatedRequest = ModelRequest.builder(request) - .messages(messages) + .systemMessage(enhancedSystemMessage) .build(); return next.call(updatedRequest); } - private List updateSystemMessage(List messages, String newPrompt) { - // 实现更新系统消息的逻辑 - return messages; + @Override + public String getName() { + return ""; } } @@ -172,15 +181,17 @@ ReactAgent agent = ReactAgent.builder() .name("context_aware_agent") .model(chatModel) .interceptors(new StateAwarePromptInterceptor()) - .build(); -``` + .build();`} + #### 基于存储的个性化提示 -```java -// 从长期记忆加载用户偏好 -public class PersonalizedPromptInterceptor implements ModelInterceptor { - + +{`// 从长期记忆加载用户偏好 +class PersonalizedPromptInterceptor extends ModelInterceptor { private final UserPreferenceStore store; public PersonalizedPromptInterceptor(UserPreferenceStore store) { @@ -188,29 +199,38 @@ public class PersonalizedPromptInterceptor implements ModelInterceptor { } @Override - public ModelResponse intercept(ModelRequest request, ModelCallHandler next) { + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler next) { // 从运行时上下文获取用户ID - String userId = getUserIdFromContext(request.context()); + String userId = getUserIdFromContext(request); // 从存储加载用户偏好 UserPreferences prefs = store.getPreferences(userId); // 构建个性化提示 - String systemPrompt = buildPersonalizedPrompt(prefs); - - // 更新请求 - List updatedMessages = updateSystemMessage( - request.getMessages(), - systemPrompt - ); + String personalizedPrompt = buildPersonalizedPrompt(prefs); + + // 更新系统消息(参考 TodoListInterceptor 的实现方式) + SystemMessage enhancedSystemMessage; + if (request.getSystemMessage() == null) { + enhancedSystemMessage = new SystemMessage(personalizedPrompt); + } else { + enhancedSystemMessage = new SystemMessage( + request.getSystemMessage().getText() + "\n\n" + personalizedPrompt + ); + } ModelRequest updatedRequest = ModelRequest.builder(request) - .messages(updatedMessages) + .systemMessage(enhancedSystemMessage) .build(); return next.call(updatedRequest); } + private String getUserIdFromContext(ModelRequest request) { + // 从请求上下文提取用户ID + return "user_001"; // 简化示例 + } + private String buildPersonalizedPrompt(UserPreferences prefs) { StringBuilder prompt = new StringBuilder("你是一个有用的助手。"); @@ -228,8 +248,13 @@ public class PersonalizedPromptInterceptor implements ModelInterceptor { return prompt.toString(); } -} -``` + + @Override + public String getName() { + return "PersonalizedPromptInterceptor"; + } +}`} + ### 消息历史(Messages) @@ -241,9 +266,11 @@ public class PersonalizedPromptInterceptor implements ModelInterceptor { #### 消息过滤 -```java -public class MessageFilterInterceptor implements ModelInterceptor { - + +{`class MessageFilterInterceptor extends ModelInterceptor { private final int maxMessages; public MessageFilterInterceptor(int maxMessages) { @@ -251,12 +278,11 @@ public class MessageFilterInterceptor implements ModelInterceptor { } @Override - public ModelResponse intercept(ModelRequest request, ModelCallHandler next) { + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler next) { List messages = request.getMessages(); // 只保留最近的N条消息 if (messages.size() > maxMessages) { - // 保留系统消息和最近的用户消息 List filtered = new ArrayList<>(); // 添加系统消息 @@ -266,10 +292,8 @@ public class MessageFilterInterceptor implements ModelInterceptor { .ifPresent(filtered::add); // 添加最近的消息 - filtered.addAll(messages.subList( - messages.size() - maxMessages + 1, - messages.size() - )); + int startIndex = Math.max(0, messages.size() - maxMessages + 1); + filtered.addAll(messages.subList(startIndex, messages.size())); messages = filtered; } @@ -280,8 +304,13 @@ public class MessageFilterInterceptor implements ModelInterceptor { return next.call(updatedRequest); } -} -``` + + @Override + public String getName() { + return "MessageFilterInterceptor"; + } +}`} + > **瞬时消息更新 VS 持久消息更新** > @@ -294,13 +323,19 @@ public class MessageFilterInterceptor implements ModelInterceptor { #### 基于上下文的工具选择 -```java -public class ContextualToolInterceptor implements ModelInterceptor { - + +{`class ContextualToolInterceptor extends ModelInterceptor { private final Map> roleBasedTools; + public ContextualToolInterceptor(Map> roleBasedTools) { + this.roleBasedTools = roleBasedTools; + } + @Override - public ModelResponse intercept(ModelRequest request, ModelCallHandler next) { + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler next) { // 从上下文获取用户角色 String userRole = getUserRole(request); @@ -310,16 +345,21 @@ public class ContextualToolInterceptor implements ModelInterceptor { Collections.emptyList() ); - // 更新工具选项 - ToolCallingChatOptions updatedOptions = ToolCallingChatOptions.builder() - .toolCallbacks(allowedTools) - .build(); + // 更新工具选项(注:实际实现需要根据框架API调整) + // 这里展示概念性代码 + System.out.println("为角色 " + userRole + " 选择了 " + allowedTools.size() + " 个工具"); - ModelRequest updatedRequest = ModelRequest.builder(request) - .options(updatedOptions) - .build(); + return next.call(request); + } - return next.call(updatedRequest); + private String getUserRole(ModelRequest request) { + // 从请求上下文提取用户角色 + return "user"; // 简化示例 + } + + @Override + public String getName() { + return "ContextualToolInterceptor"; } } @@ -334,21 +374,28 @@ ReactAgent agent = ReactAgent.builder() .name("role_based_agent") .model(chatModel) .interceptors(new ContextualToolInterceptor(roleTools)) - .build(); -``` + .build();`} + ### 模型选择(Model) 根据任务复杂度或用户偏好动态选择模型。 -```java -public class DynamicModelInterceptor implements ModelInterceptor { - + +{`class DynamicModelInterceptor extends ModelInterceptor { private final ChatModel simpleModel; private final ChatModel complexModel; + public DynamicModelInterceptor(ChatModel simpleModel, ChatModel complexModel) { + this.simpleModel = simpleModel; + this.complexModel = complexModel; + } + @Override - public ModelResponse intercept(ModelRequest request, ModelCallHandler next) { + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler next) { // 分析任务复杂度 boolean isComplexTask = analyzeComplexity(request.getMessages()); @@ -366,15 +413,23 @@ public class DynamicModelInterceptor implements ModelInterceptor { // 例如:检查消息长度、关键词等 return messages.size() > 5; } -} -``` + + @Override + public String getName() { + return "DynamicModelInterceptor"; + } +}`} + ### 响应格式(Response Format) 使用结构化输出控制模型响应格式。 -```java -// 在Agent级别设置输出格式 + +{`// 在Agent级别设置输出格式 ReactAgent agent = ReactAgent.builder() .name("structured_agent") .model(chatModel) @@ -382,10 +437,9 @@ ReactAgent agent = ReactAgent.builder() .build(); // 也可以在Interceptor中动态调整 -public class DynamicFormatInterceptor implements ModelInterceptor { - +class DynamicFormatInterceptor extends ModelInterceptor { @Override - public ModelResponse intercept(ModelRequest request, ModelCallHandler next) { + public ModelResponse interceptModel(ModelRequest request, ModelCallHandler next) { // 根据请求内容决定输出格式 String outputSchema = determineOutputSchema(request); @@ -401,8 +455,23 @@ public class DynamicFormatInterceptor implements ModelInterceptor { return next.call(updatedRequest); } -} -``` + + private String determineOutputSchema(ModelRequest request) { + // 实现输出格式决定逻辑 + return ""; + } + + private List addFormatInstructions(List messages, String schema) { + // 实现格式说明添加逻辑 + return messages; + } + + @Override + public String getName() { + return "DynamicFormatInterceptor"; + } +}`} + ## 工具上下文(Tool Context) @@ -410,9 +479,11 @@ public class DynamicFormatInterceptor implements ModelInterceptor { ### 工具中访问状态 -```java -public class StatefulTool implements Function { - + +{`class StatefulTool implements Function { public record Request(String query) {} public record Response(String result) {} @@ -423,30 +494,37 @@ public class StatefulTool implements Function messages = currentState.value("messages"); - // 从 Agent 运行上下文读取信息 - RunnableConfig config = (OverAllState) toolContext.getContext().get("config"); - Optional userContext = config.metadata("user_context_key"); + // 从 Agent 运行上下文读取信息 + RunnableConfig config = (RunnableConfig) toolContext.getContext().get("config"); + Optional userContext = config.metadata("user_context_key"); // 使用状态信息处理请求 String result = processWithContext(request.query(), messages, userContext); return new Response(result); } -} -``` -### 工具修改状态 + private String processWithContext(String query, Optional messages, Optional userContext) { + // 实现处理逻辑 + return "处理结果"; + } +}`} + -```java -public class StateModifyingTool implements Function { +### 工具修改状态 + +{`class StateModifyingTool implements Function { public record Request(String data) {} public record Response(String status) {} @Override - public Response apply(Request request) { - // 从 Agent 持久状态读取信息 - Map extraState = (OverAllState) toolContext.getContext().get("extraState"); + public Response apply(Request request, ToolContext toolContext) { + // 从 Agent 持久状态读取信息 + Map extraState = (Map) toolContext.getContext().get("extraState"); // 处理数据 String processed = process(request.data()); @@ -456,8 +534,13 @@ public class StateModifyingTool implements Function ## 生命周期上下文(Lifecycle Context) @@ -474,13 +557,15 @@ Spring AI Alibaba 支持以下 Hook 位置: ### 自定义 Hook 示例 -```java -import com.alibaba.cloud.ai.graph.agent.hook.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 { - +class LoggingHook extends ModelHook { @Override public String getName() { return "logging_hook"; @@ -495,18 +580,18 @@ public class LoggingHook implements ModelHook { } @Override - public Map beforeModel(OverAllState state, RunnableConfig config) { + public CompletableFuture> beforeModel(OverAllState state, RunnableConfig config) { // 在模型调用前记录 - System.out.println("模型调用前 - 消息数: " + - ((List) state.value("messages").get()).size()); - return Map.of(); + List messages = (List) state.value("messages").orElse(List.of()); + System.out.println("模型调用前 - 消息数: " + messages.size()); + return CompletableFuture.completedFuture(Map.of()); } @Override - public Map afterModel(OverAllState state, RunnableConfig config) { + public CompletableFuture> afterModel(OverAllState state, RunnableConfig config) { // 在模型调用后记录 System.out.println("模型调用后 - 响应已生成"); - return Map.of(); + return CompletableFuture.completedFuture(Map.of()); } } @@ -515,50 +600,101 @@ ReactAgent agent = ReactAgent.builder() .name("logged_agent") .model(chatModel) .hooks(new LoggingHook()) - .build(); -``` + .build();`} + ### 消息摘要 Hook -```java -public class SummarizationHook implements ModelHook { - + +{`class SummarizationHook extends ModelHook { private final ChatModel summarizationModel; private final int triggerLength; + public SummarizationHook(ChatModel model, int triggerLength) { + this.summarizationModel = model; + this.triggerLength = triggerLength; + } + @Override - public Map beforeModel(OverAllState state, RunnableConfig config) { - List messages = (List) state.value("messages").get(); + public String getName() { + return "summarization_hook"; + } + + @Override + public HookPosition[] getHookPositions() { + return new HookPosition[]{HookPosition.BEFORE_MODEL}; + } + + @Override + public CompletableFuture> beforeModel(OverAllState state, RunnableConfig config) { + List messages = (List) state.value("messages").orElse(List.of()); if (messages.size() > triggerLength) { // 生成对话摘要 String summary = generateSummary(messages); - // 用摘要替换旧消息 - List newMessages = new ArrayList<>(); - newMessages.add(new SystemMessage("之前对话摘要:" + summary)); - newMessages.addAll(messages.subList(messages.size() - 5, messages.size())); + // 查找是否已存在 SystemMessage + SystemMessage existingSystemMessage = null; + int systemMessageIndex = -1; + for (int i = 0; i < messages.size(); i++) { + Message msg = messages.get(i); + if (msg instanceof SystemMessage) { + existingSystemMessage = (SystemMessage) msg; + systemMessageIndex = i; + break; + } + } + + // 创建摘要 SystemMessage + String summaryText = "之前对话摘要:" + summary; + SystemMessage summarySystemMessage; + if (existingSystemMessage != null) { + // 如果存在 SystemMessage,追加摘要信息 + summarySystemMessage = new SystemMessage( + existingSystemMessage.getText() + "\n\n" + summaryText + ); + } else { + // 如果不存在,创建新的 + summarySystemMessage = new SystemMessage(summaryText); + } + + // 保留最近的几条消息 + int recentCount = Math.min(5, messages.size()); + List recentMessages = messages.subList( + messages.size() - recentCount, + messages.size() + ); - return Map.of("messages", newMessages); + // 构建新的消息列表 + List newMessages = new ArrayList<>(); + newMessages.add(summarySystemMessage); + // 添加最近的消息,排除旧的 SystemMessage(如果存在) + for (Message msg : recentMessages) { + if (msg != existingSystemMessage) { + newMessages.add(msg); + } + } + + return CompletableFuture.completedFuture(Map.of("messages", newMessages)); } - return Map.of(); + return CompletableFuture.completedFuture(Map.of()); } private String generateSummary(List messages) { // 使用另一个模型生成摘要 String conversation = messages.stream() - .map(Message::getContent) + .map(Message::getText) .collect(Collectors.joining("\n")); - return chatClient.prompt() - .system("请总结以下对话的要点") - .user(conversation) - .call() - .content(); + // 简化示例:返回固定摘要 + return "之前讨论了多个主题..."; } -} -``` +}`} + ## 相关文档 diff --git a/docs/frameworks/agent-framework/advanced/human-in-the-loop.md b/docs/frameworks/agent-framework/advanced/human-in-the-loop.md index 5a687606..84eb6987 100644 --- a/docs/frameworks/agent-framework/advanced/human-in-the-loop.md +++ b/docs/frameworks/agent-framework/advanced/human-in-the-loop.md @@ -44,8 +44,11 @@ Hook 定义了三种人工响应中断的内置方式: 你可以配置哪些工具需要人工审批,以及为每个工具允许哪些决策类型。 -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.agent.hook.hip.HumanInTheLoopHook; import com.alibaba.cloud.ai.graph.agent.hook.hip.ToolConfig; import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; @@ -70,8 +73,8 @@ ReactAgent agent = ReactAgent.builder() .tools(writeFileTool, executeSqlTool, readDataTool) .hooks(List.of(humanInTheLoopHook)) // [!code highlight] .saver(memorySaver) // [!code highlight] - .build(); -``` + .build();`} + :::info 你必须配置检查点保存器来在中断期间持久化图状态。 @@ -84,8 +87,11 @@ ReactAgent agent = ReactAgent.builder() 当你调用 Agent 时,它会一直运行直到完成或触发中断。当工具调用匹配你在 `approvalOn` 中配置的策略时会触发中断。在这种情况下,调用结果将返回 `InterruptionMetadata`,其中包含需要审查的操作。你可以将这些操作呈现给审查者,并在提供决策后恢复执行。 -```java -import com.alibaba.cloud.ai.graph.RunnableConfig; + +{`import com.alibaba.cloud.ai.graph.RunnableConfig; import com.alibaba.cloud.ai.graph.NodeOutput; import com.alibaba.cloud.ai.graph.action.InterruptionMetadata; @@ -121,8 +127,8 @@ if (result.isPresent() && result.get() instanceof InterruptionMetadata) { // [!c // 工具: execute_sql // 参数: {"query": "DELETE FROM records WHERE created_at < NOW() - INTERVAL '30 days';"} // 描述: SQL执行操作需要审批 -} -``` +}`} + ### 决策类型 @@ -132,8 +138,11 @@ if (result.isPresent() && result.get() instanceof InterruptionMetadata) { // [!c 使用 `approve` 批准工具调用原样执行,不做任何更改。 ``` -```java - // 构建批准反馈 + +{` // 构建批准反馈 InterruptionMetadata.Builder feedbackBuilder = InterruptionMetadata.builder() .nodeId(interruptionMetadata.node()) .state(interruptionMetadata.state()); @@ -156,7 +165,8 @@ if (result.isPresent() && result.get() instanceof InterruptionMetadata) { // [!c .build(); // 第二次调用以恢复执行 - Optional finalResult = agent.invokeAndGetOutput("", resumeConfig); + Optional finalResult = agent.invokeAndGetOutput("", resumeConfig);`} + ``` @@ -164,8 +174,11 @@ if (result.isPresent() && result.get() instanceof InterruptionMetadata) { // [!c 使用 `edit` 在执行前修改工具调用。 提供编辑后的操作,包括新的工具参数。 - ```java - // 构建编辑反馈 + + {` // 构建编辑反馈 InterruptionMetadata.Builder feedbackBuilder = InterruptionMetadata.builder() .nodeId(interruptionMetadata.node()) .state(interruptionMetadata.state()); @@ -191,8 +204,8 @@ if (result.isPresent() && result.get() instanceof InterruptionMetadata) { // [!c .addMetadata(RunnableConfig.HUMAN_FEEDBACK_METADATA_KEY, editMetadata) .build(); - Optional finalResult = agent.invokeAndGetOutput("", resumeConfig); - ``` + Optional finalResult = agent.invokeAndGetOutput("", resumeConfig);`} + 当**编辑**工具参数时,请保守地进行更改。对原始参数的重大修改可能会导致模型重新评估其方法,并可能多次执行工具或采取意外操作。 @@ -202,8 +215,11 @@ if (result.isPresent() && result.get() instanceof InterruptionMetadata) { // [!c 使用 `reject` 拒绝工具调用并提供反馈而不是执行。 - ```java - // 构建拒绝反馈 + + {` // 构建拒绝反馈 InterruptionMetadata.Builder feedbackBuilder = InterruptionMetadata.builder() .nodeId(interruptionMetadata.node()) .state(interruptionMetadata.state()); @@ -225,8 +241,8 @@ if (result.isPresent() && result.get() instanceof InterruptionMetadata) { // [!c .addMetadata(RunnableConfig.HUMAN_FEEDBACK_METADATA_KEY, rejectMetadata) .build(); - Optional finalResult = agent.invokeAndGetOutput("", resumeConfig); - ``` + Optional finalResult = agent.invokeAndGetOutput("", resumeConfig);`} + `description` 消息将被添加到对话中作为反馈,帮助Agent理解为什么操作被拒绝以及应该做什么。 @@ -236,8 +252,11 @@ if (result.isPresent() && result.get() instanceof InterruptionMetadata) { // [!c 当多个操作需要审查时,为每个操作提供决策: - ```java - InterruptionMetadata.Builder feedbackBuilder = InterruptionMetadata.builder() + + {` InterruptionMetadata.Builder feedbackBuilder = InterruptionMetadata.builder() .nodeId(interruptionMetadata.node()) .state(interruptionMetadata.state()); @@ -264,8 +283,8 @@ if (result.isPresent() && result.get() instanceof InterruptionMetadata) { // [!c .result(InterruptionMetadata.ToolFeedback.FeedbackResult.REJECTED) .description("不允许此操作") .build() - ); - ``` + );`} + ``` --> @@ -282,8 +301,11 @@ Hook 定义了一个在模型生成响应后但在执行任何工具调用之前 ## 完整示例 -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.agent.hook.hip.HumanInTheLoopHook; import com.alibaba.cloud.ai.graph.agent.hook.hip.ToolConfig; import com.alibaba.cloud.ai.graph.RunnableConfig; @@ -369,15 +391,18 @@ public class HumanInTheLoopExample { } } } -} -``` +}`} + ## 实用工具方法 为了简化人工介入的处理,你可以创建实用方法: -```java -public class HITLHelper { + +{`public class HITLHelper { /** * 批准所有工具调用 @@ -459,8 +484,8 @@ InterruptionMetadata editMetadata = HITLHelper.editTool( interruptionMetadata, "execute_sql", "{\"query\": \"SELECT * FROM records LIMIT 10\"}" -); -``` +);`} + ## 最佳实践 diff --git a/docs/frameworks/agent-framework/advanced/memory.md b/docs/frameworks/agent-framework/advanced/memory.md index fc5f749f..d8abe5c2 100644 --- a/docs/frameworks/agent-framework/advanced/memory.md +++ b/docs/frameworks/agent-framework/advanced/memory.md @@ -50,8 +50,11 @@ Spring AI Alibaba 将长期记忆以 JSON 文档的形式存储在 Store 中。 这种结构支持记忆的层次化组织。通过内容过滤器支持跨命名空间搜索。 -```java -import com.alibaba.cloud.ai.graph.store.stores.MemoryStore; + +{`import com.alibaba.cloud.ai.graph.store.stores.MemoryStore; import com.alibaba.cloud.ai.graph.store.StoreItem; import java.util.*; @@ -81,15 +84,18 @@ Optional retrievedItem = store.getItem(namespace, "a-memory"); List items = store.searchItems( namespace, Map.of("my-key", "my-value") -); -``` +);`} + ## 在工具中读取长期记忆 下面的示例展示了如何创建一个工具,让 Agent 能够查询用户信息。 -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.RunnableConfig; import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; import com.alibaba.cloud.ai.graph.store.stores.MemoryStore; @@ -147,15 +153,18 @@ RunnableConfig config = RunnableConfig.builder() .addMetadata("user_id", "user_123") .build(); -agent.invoke("查询用户信息,namespace=['users'], key='user_123'", config); -``` +agent.invoke("查询用户信息,namespace=['users'], key='user_123'", config);`} + ## 在工具中写入长期记忆 下面的示例展示了如何创建一个更新用户信息的工具。 -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.RunnableConfig; import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; import com.alibaba.cloud.ai.graph.store.stores.MemoryStore; @@ -209,15 +218,18 @@ agent.invoke( // 可以直接访问存储获取值 Optional savedItem = store.getItem(List.of("users"), "user_123"); -Map savedValue = savedItem.get().getValue(); -``` +Map savedValue = savedItem.get().getValue();`} + ## 使用 ModelHook 管理长期记忆 下面的示例展示了如何使用 ModelHook 在模型调用前后自动加载和保存长期记忆。 -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; import com.alibaba.cloud.ai.graph.agent.hook.JumpTo; @@ -252,10 +264,6 @@ ModelHook memoryInterceptor = new ModelHook() { return "memory_interceptor"; } - @Override - public List canJumpTo() { - return List.of(); - } @Override public HookPosition[] getHookPositions() { @@ -284,11 +292,49 @@ ModelHook memoryInterceptor = new ModelHook() { profileData.get("preferences") ); - // 添加包含用户上下文的系统消息 + // 获取消息列表 List messages = (List) state.value("messages").orElse(new ArrayList<>()); List newMessages = new ArrayList<>(); - newMessages.add(new SystemMessage(userContext)); - newMessages.addAll(messages); + + // 查找是否已存在 SystemMessage + SystemMessage existingSystemMessage = null; + int systemMessageIndex = -1; + for (int i = 0; i < messages.size(); i++) { + Message msg = messages.get(i); + if (msg instanceof SystemMessage) { + existingSystemMessage = (SystemMessage) msg; + systemMessageIndex = i; + break; + } + } + + // 如果找到 SystemMessage,更新它;否则创建新的 + SystemMessage enhancedSystemMessage; + if (existingSystemMessage != null) { + // 更新现有的 SystemMessage + enhancedSystemMessage = new SystemMessage( + existingSystemMessage.getText() + "\n\n" + userContext + ); + } else { + // 创建新的 SystemMessage + enhancedSystemMessage = new SystemMessage(userContext); + } + + // 构建新的消息列表 + if (systemMessageIndex >= 0) { + // 如果找到了 SystemMessage,替换它 + for (int i = 0; i < messages.size(); i++) { + if (i == systemMessageIndex) { + newMessages.add(enhancedSystemMessage); + } else { + newMessages.add(messages.get(i)); + } + } + } else { + // 如果没有找到 SystemMessage,在开头添加新的 + newMessages.add(enhancedSystemMessage); + newMessages.addAll(messages); + } return CompletableFuture.completedFuture(Map.of("messages", newMessages)); } @@ -317,15 +363,18 @@ RunnableConfig config = RunnableConfig.builder() .build(); // Agent会自动加载用户画像信息 -agent.invoke("请介绍一下我的信息。", config); -``` +agent.invoke("请介绍一下我的信息。", config);`} + ## 结合短期和长期记忆 短期记忆用于存储对话上下文,长期记忆用于存储持久化数据。下面的示例展示了如何同时使用两种记忆。 -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; import com.alibaba.cloud.ai.graph.OverAllState; @@ -357,10 +406,6 @@ ModelHook combinedMemoryHook = new ModelHook() { return "combined_memory"; } - @Override - public List canJumpTo() { - return List.of(); - } @Override public HookPosition[] getHookPositions() { @@ -385,11 +430,49 @@ ModelHook combinedMemoryHook = new ModelHook() { String contextInfo = String.format("长期记忆:用户 %s, 职业: %s", profile.get("name"), profile.get("occupation")); - // 注入到消息中 + // 获取消息列表 List messages = (List) state.value("messages").orElse(new ArrayList<>()); List newMessages = new ArrayList<>(); - newMessages.add(new SystemMessage(contextInfo)); - newMessages.addAll(messages); + + // 查找是否已存在 SystemMessage + SystemMessage existingSystemMessage = null; + int systemMessageIndex = -1; + for (int i = 0; i < messages.size(); i++) { + Message msg = messages.get(i); + if (msg instanceof SystemMessage) { + existingSystemMessage = (SystemMessage) msg; + systemMessageIndex = i; + break; + } + } + + // 如果找到 SystemMessage,更新它;否则创建新的 + SystemMessage enhancedSystemMessage; + if (existingSystemMessage != null) { + // 更新现有的 SystemMessage + enhancedSystemMessage = new SystemMessage( + existingSystemMessage.getText() + "\n\n" + contextInfo + ); + } else { + // 创建新的 SystemMessage + enhancedSystemMessage = new SystemMessage(contextInfo); + } + + // 构建新的消息列表 + if (systemMessageIndex >= 0) { + // 如果找到了 SystemMessage,替换它 + for (int i = 0; i < messages.size(); i++) { + if (i == systemMessageIndex) { + newMessages.add(enhancedSystemMessage); + } else { + newMessages.add(messages.get(i)); + } + } + } else { + // 如果没有找到 SystemMessage,在开头添加新的 + newMessages.add(enhancedSystemMessage); + newMessages.addAll(messages); + } return CompletableFuture.completedFuture(Map.of("messages", newMessages)); } @@ -413,15 +496,18 @@ agent.invoke("我今天在做一个 Spring 项目。", config); // 提出需要同时使用两种记忆的问题 agent.invoke("根据我的职业和今天的工作,给我一些建议。", config); -// 响应会同时使用长期记忆(职业)和短期记忆(Spring项目) -``` +// 响应会同时使用长期记忆(职业)和短期记忆(Spring项目)`} + ## 跨会话记忆 同一用户在不同会话中应该能够访问相同的长期记忆。 -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.RunnableConfig; import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; import com.alibaba.cloud.ai.graph.store.stores.MemoryStore; @@ -484,15 +570,18 @@ agent.invoke( "我的密码是什么?用 getMemory 获取,namespace=['credentials'], key='user_003_password'。", session2 ); -// 长期记忆在不同会话间持久化 -``` +// 长期记忆在不同会话间持久化`} + ## 用户偏好学习 Agent 可以随着时间的推移学习并存储用户偏好。 -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; import com.alibaba.cloud.ai.graph.OverAllState; @@ -513,10 +602,6 @@ ModelHook preferenceLearningHook = new ModelHook() { return "preference_learning"; } - @Override - public List canJumpTo() { - return List.of(); - } @Override public HookPosition[] getHookPositions() { @@ -581,6 +666,6 @@ agent.invoke("我偏好早上运动。", config); // 验证偏好已被存储 Optional savedPrefs = memoryStore.getItem(List.of("user_data"), "user_004_preferences"); -// 偏好应该被保存到长期记忆中 -``` +// 偏好应该被保存到长期记忆中`} + diff --git a/docs/frameworks/agent-framework/advanced/multi-agent.md b/docs/frameworks/agent-framework/advanced/multi-agent.md index a58439a1..fadfdcd9 100644 --- a/docs/frameworks/agent-framework/advanced/multi-agent.md +++ b/docs/frameworks/agent-framework/advanced/multi-agent.md @@ -68,8 +68,11 @@ Multi-agent设计的核心是**上下文工程**——决定每个Agent看到什 #### 实现 -```java -import com.alibaba.cloud.ai.graph.agent.flow.agent.SequentialAgent; + +{`import com.alibaba.cloud.ai.graph.agent.flow.agent.SequentialAgent; import com.alibaba.cloud.ai.graph.OverAllState; // 创建专业化的子Agent @@ -111,8 +114,8 @@ if (result.isPresent()) { // 访问第二个Agent的输出 AssistantMessage reviewedArticle = (AssistantMessage) state.value("reviewed_article").get(); System.out.println("评审后文章: " + reviewedArticle.getText()); -} -``` +}`} + #### 关键特性 @@ -123,8 +126,11 @@ if (result.isPresent()) { #### 控制推理内容 -```java -ReactAgent writerAgent = ReactAgent.builder() + +{`ReactAgent writerAgent = ReactAgent.builder() .name("writer_agent") .model(chatModel) .returnReasoningContents(true) // [!code highlight] @@ -149,8 +155,8 @@ Optional result = blogAgent.invoke("帮我写一个100字左右的 // 消息历史将包含所有工具调用和推理过程 List messages = (List) result.get().value("messages").get(); -System.out.println("消息数量: " + messages.size()); // 包含所有中间步骤 -``` +System.out.println("消息数量: " + messages.size()); // 包含所有中间步骤`} + ### 并行执行(Parallel Agent) @@ -166,8 +172,11 @@ System.out.println("消息数量: " + messages.size()); // 包含所有中间步 #### 实现 -```java -import com.alibaba.cloud.ai.graph.agent.flow.agent.ParallelAgent; + +{`import com.alibaba.cloud.ai.graph.agent.flow.agent.ParallelAgent; // 创建多个专业化Agent ReactAgent proseWriterAgent = ReactAgent.builder() @@ -224,15 +233,18 @@ if (result.isPresent()) { // 访问合并后的结果 Object mergedResults = state.value("merged_results").get(); System.out.println("合并结果: " + mergedResults); -} -``` +}`} + #### 自定义合并策略 你可以实现自定义的合并策略来控制如何组合多个Agent的输出: -```java -public class CustomMergeStrategy implements ParallelAgent.MergeStrategy { + +{`public class CustomMergeStrategy implements ParallelAgent.MergeStrategy { @Override public Map merge(List results) { @@ -262,8 +274,8 @@ ParallelAgent parallelAgent = ParallelAgent.builder() .name("parallel_agent") .subAgents(List.of(agent1, agent2, agent3)) .mergeStrategy(new CustomMergeStrategy()) // [!code highlight] - .build(); -``` + .build();`} + ### 路由(LlmRoutingAgent) @@ -280,8 +292,11 @@ ParallelAgent parallelAgent = ParallelAgent.builder() #### 实现 -```java -import com.alibaba.cloud.ai.graph.agent.flow.agent.LlmRoutingAgent; + +{`import com.alibaba.cloud.ai.graph.agent.flow.agent.LlmRoutingAgent; import com.alibaba.cloud.ai.graph.agent.ReactAgent; // 创建专业化的子Agent @@ -314,7 +329,7 @@ ReactAgent translatorAgent = ReactAgent.builder() LlmRoutingAgent routingAgent = LlmRoutingAgent.builder() .name("content_routing_agent") .description("根据用户需求智能路由到合适的专家Agent") - .chatModel(chatModel) // [!code highlight] + .model(chatModel) // [!code highlight] .subAgents(List.of(writerAgent, reviewerAgent, translatorAgent)) // [!code highlight] .build(); @@ -326,8 +341,8 @@ Optional result2 = routingAgent.invoke("请帮我修改这篇文 // LLM会路由到 reviewerAgent Optional result3 = routingAgent.invoke("请将以下内容翻译成英文:春暖花开"); -// LLM会路由到 translatorAgent -``` +// LLM会路由到 translatorAgent`} + #### 关键特性 @@ -340,8 +355,11 @@ Optional result3 = routingAgent.invoke("请将以下内容翻译 为了提高路由的准确性,需要注意以下几点: -```java -// 1. 提供清晰明确的Agent描述 + +{`// 1. 提供清晰明确的Agent描述 ReactAgent codeAgent = ReactAgent.builder() .name("code_agent") .model(chatModel) @@ -362,10 +380,10 @@ ReactAgent businessAgent = ReactAgent.builder() // 3. 使用不同领域的Agent避免重叠 LlmRoutingAgent routingAgent = LlmRoutingAgent.builder() .name("multi_domain_router") - .chatModel(chatModel) + .model(chatModel) .subAgents(List.of(codeAgent, businessAgent, writerAgent)) - .build(); -``` + .build();`} + ### 自定义(Customized) @@ -375,8 +393,11 @@ Spring AI Alibaba 提供了 `FlowAgent` 抽象类,允许你创建自定义的A `FlowAgent` 是所有流程型Agent(如 `SequentialAgent`、`ParallelAgent`、`LlmRoutingAgent`)的基类,它提供了以下核心能力: -```java -public abstract class FlowAgent extends Agent { + +{`public abstract class FlowAgent extends Agent { protected List subAgents; // 子Agent列表 protected CompileConfig compileConfig; // 编译配置 @@ -389,15 +410,18 @@ public abstract class FlowAgent extends Agent { // 提供给子类使用的工具方法 public List subAgents() { return this.subAgents; } public CompileConfig compileConfig() { return compileConfig; } -} -``` +}`} + #### 实现自定义FlowAgent 下面展示如何创建一个自定义的 `ConditionalAgent`,它根据条件选择不同的Agent分支: -```java -import com.alibaba.cloud.ai.graph.agent.flow.agent.FlowAgent; + +{`import com.alibaba.cloud.ai.graph.agent.flow.agent.FlowAgent; import com.alibaba.cloud.ai.graph.agent.flow.builder.FlowAgentBuilder; import com.alibaba.cloud.ai.graph.agent.flow.builder.FlowGraphBuilder; import com.alibaba.cloud.ai.graph.StateGraph; @@ -479,13 +503,16 @@ public class ConditionalAgent extends FlowAgent { return this; } } -} -``` +}`} + #### 使用自定义Agent -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import java.util.Map; // 创建两个分支Agent @@ -528,15 +555,18 @@ Optional result1 = conditionalAgent.invoke("这是一个紧急问 // 会路由到 urgentAgent Optional result2 = conditionalAgent.invoke("请帮我分析一下这个问题"); -// 会路由到 normalAgent -``` +// 会路由到 normalAgent`} + #### 实现复杂的循环Agent 你还可以创建更复杂的自定义Agent,例如带有循环逻辑的 `LoopAgent`: -```java -/** + +{`/** * 循环Agent:重复执行直到满足退出条件 */ public class CustomLoopAgent extends FlowAgent { @@ -575,8 +605,8 @@ CustomLoopAgent refinementAgent = CustomLoopAgent.builder() return score != null && (int) score >= 8; }) .maxIterations(5) // 最多循环5次 - .build(); -``` + .build();`} + #### 关键要点 @@ -594,8 +624,11 @@ CustomLoopAgent refinementAgent = CustomLoopAgent.builder() 你可以组合不同的模式创建复杂的工作流: -```java -// 1. 创建研究Agent(作为工具) + +{`// 1. 创建研究Agent(作为工具) ReactAgent researchAgent = ReactAgent.builder() .name("research_agent") .model(chatModel) @@ -641,8 +674,8 @@ SequentialAgent complexWorkflow = SequentialAgent.builder() .build(); // 使用 -Optional result = complexWorkflow.invoke("创作关于'人工智能'的内容"); -``` +Optional result = complexWorkflow.invoke("创作关于'人工智能'的内容");`} + ## 相关文档 diff --git a/docs/frameworks/agent-framework/advanced/rag.md b/docs/frameworks/agent-framework/advanced/rag.md index a5f99b5e..9efb7669 100644 --- a/docs/frameworks/agent-framework/advanced/rag.md +++ b/docs/frameworks/agent-framework/advanced/rag.md @@ -94,48 +94,60 @@ RAG 可以以多种方式实现,具体取决于你的系统需求。我们在 #### Java 实现示例 -```java -import org.springframework.ai.document.Document; + +{`import org.springframework.ai.document.Document; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.ai.chat.client.ChatClient; -import org.springframework.ai.chat.client.advisor.QuestionAnswerAdvisor; +import org.springframework.ai.chat.model.ChatModel; +import java.util.List; +import java.util.stream.Collectors; // 假设你已经有一个配置好的向量存储 VectorStore vectorStore = ...; // 配置你的向量存储(如Milvus、Pinecone等) -// 创建带有RAG功能的ChatClient -ChatClient chatClient = ChatClient.builder(chatModel) - .defaultAdvisors( - new QuestionAnswerAdvisor(vectorStore) // [!code highlight] - ) - .build(); - // 两步RAG:检索 -> 生成 String userQuestion = "Spring AI Alibaba支持哪些模型?"; +// Step 1: 检索相关文档 +List relevantDocs = vectorStore.similaritySearch(userQuestion); + +// Step 2: 构建上下文 +String context = relevantDocs.stream() + .map(Document::getText) + .collect(Collectors.joining("\n\n")); + +// Step 3: 使用上下文生成答案 +ChatClient chatClient = ChatClient.builder(chatModel).build(); String answer = chatClient.prompt() - .user(userQuestion) + .user(u -> u.text("基于以下上下文回答问题:\n\n上下文:\n" + context + "\n\n问题:" + userQuestion)) .call() - .content(); // [!code highlight] + .content(); -System.out.println("答案: " + answer); -``` +System.out.println("答案: " + answer);`} + 在这个例子中: -1. `QuestionAnswerAdvisor` 自动从向量存储检索相关文档 -2. 检索到的文档作为上下文添加到提示中 +1. 从向量存储检索相关文档 +2. 将检索到的文档合并为上下文 3. ChatModel 使用增强的上下文生成答案 #### 构建知识库 -```java -import org.springframework.ai.document.Document; + +{`import org.springframework.ai.document.Document; import org.springframework.ai.reader.TextReader; import org.springframework.ai.transformer.splitter.TokenTextSplitter; import org.springframework.ai.vectorstore.VectorStore; -import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; +import java.util.List; // 1. 加载文档 Resource resource = new FileSystemResource("path/to/document.txt"); @@ -147,11 +159,11 @@ TokenTextSplitter splitter = new TokenTextSplitter(); List chunks = splitter.apply(documents); // 3. 将块添加到向量存储 -vectorStore.add(chunks); // [!code highlight] +vectorStore.add(chunks); // 现在你可以使用向量存储进行检索 -List results = vectorStore.similaritySearch("查询文本"); -``` +List results = vectorStore.similaritySearch("查询文本");`} + ### Agentic RAG @@ -165,15 +177,21 @@ Agent 启用 RAG 行为所需的唯一条件是访问一个或多个可以获取 #### Java 实现示例 -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import org.springframework.ai.document.Document; import org.springframework.ai.tool.ToolCallback; -import java.util.function.Function; +import org.springframework.ai.tool.function.FunctionToolCallback; +import org.springframework.ai.vectorstore.VectorStore; import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; // 创建文档检索工具 -public class DocumentSearchTool implements Function { - +class DocumentSearchTool { private final VectorStore vectorStore; public DocumentSearchTool(VectorStore vectorStore) { @@ -183,26 +201,26 @@ public class DocumentSearchTool implements Function docs = vectorStore.similaritySearch(request.query()); // [!code highlight] + List docs = vectorStore.similaritySearch(request.query()); // 合并文档内容 String combinedContent = docs.stream() - .map(Document::getContent) + .map(Document::getText) .collect(Collectors.joining("\n\n")); return new Response(combinedContent); } } -// 创建工具回调 DocumentSearchTool searchTool = new DocumentSearchTool(vectorStore); -ToolCallback searchCallback = ToolCallback.builder() - .name("search_documents") + +// 创建工具回调 +ToolCallback searchCallback = FunctionToolCallback.builder("search_documents", + (Function) + request -> searchTool.search(request)) .description("搜索文档以查找相关信息") - .function(searchTool) .inputType(DocumentSearchTool.Request.class) .build(); @@ -210,15 +228,14 @@ ToolCallback searchCallback = ToolCallback.builder() ReactAgent ragAgent = ReactAgent.builder() .name("rag_agent") .model(chatModel) - .systemPrompt("你是一个智能助手。当需要查找信息时,使用search_documents工具。" + - "基于检索到的信息回答用户的问题,并引用相关片段。") - .tools(searchCallback) // [!code highlight] + .instruction("你是一个智能助手。当需要查找信息时,使用search_documents工具。" + + "基于检索到的信息回答用户的问题,并引用相关片段。") + .tools(searchCallback) .build(); // Agent会自动决定何时调用检索工具 -AssistantMessage response = ragAgent.call("Spring AI Alibaba支持哪些向量数据库?"); -System.out.println(response.getText()); -``` +ragAgent.invoke("Spring AI Alibaba支持哪些向量数据库?");`} + 在这个例子中: @@ -230,45 +247,96 @@ System.out.println(response.getText()); #### 多工具 Agentic RAG -```java + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import org.springframework.ai.document.Document; +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback; +import org.springframework.ai.vectorstore.VectorStore; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + // 创建多个检索工具 -ToolCallback webSearchTool = ToolCallback.builder() - .name("web_search") +class WebSearchTool { + public record Request(String query) {} + public record Response(String content) {} + + public Response search(Request request) { + return new Response("从网络搜索到的信息: " + request.query()); + } +} + +class DatabaseQueryTool { + public record Request(String query) {} + public record Response(String content) {} + + public Response query(Request request) { + return new Response("从数据库查询到的信息: " + request.query()); + } +} + +class DocumentSearchTool { + private final VectorStore vectorStore; + + public DocumentSearchTool(VectorStore vectorStore) { + this.vectorStore = vectorStore; + } + + public record Request(String query) {} + public record Response(String content) {} + + public Response search(Request request) { + List docs = vectorStore.similaritySearch(request.query()); + String content = docs.stream() + .map(Document::getText) + .collect(Collectors.joining("\n\n")); + return new Response(content); + } +} + +WebSearchTool webSearchTool = new WebSearchTool(); +DatabaseQueryTool dbQueryTool = new DatabaseQueryTool(); +DocumentSearchTool docSearchTool = new DocumentSearchTool(vectorStore); + +ToolCallback webSearchCallback = FunctionToolCallback.builder("web_search", + (Function) + req -> webSearchTool.search(req)) .description("搜索互联网以获取最新信息") - .function(webSearchFunction) - .inputType(WebSearchRequest.class) + .inputType(WebSearchTool.Request.class) .build(); -ToolCallback databaseQueryTool = ToolCallback.builder() - .name("database_query") +ToolCallback databaseQueryCallback = FunctionToolCallback.builder("database_query", + (Function) + req -> dbQueryTool.query(req)) .description("查询内部数据库") - .function(dbQueryFunction) - .inputType(DatabaseQueryRequest.class) + .inputType(DatabaseQueryTool.Request.class) .build(); -ToolCallback documentSearchTool = ToolCallback.builder() - .name("document_search") +ToolCallback documentSearchCallback = FunctionToolCallback.builder("document_search", + (Function) + req -> docSearchTool.search(req)) .description("搜索文档库") - .function(docSearchFunction) - .inputType(DocumentSearchRequest.class) + .inputType(DocumentSearchTool.Request.class) .build(); // Agent可以访问多个检索源 ReactAgent multiSourceAgent = ReactAgent.builder() .name("multi_source_rag_agent") .model(chatModel) - .systemPrompt("你可以访问多个信息源:" + - "1. web_search - 用于最新的互联网信息\n" + - "2. database_query - 用于内部数据\n" + - "3. document_search - 用于文档库\n" + - "根据问题选择最合适的工具。") - .tools(webSearchTool, databaseQueryTool, documentSearchTool) // [!code highlight] + .instruction("你可以访问多个信息源:" + + "1. web_search - 用于最新的互联网信息\n" + + "2. database_query - 用于内部数据\n" + + "3. document_search - 用于文档库\n" + + "根据问题选择最合适的工具。") + .tools(webSearchCallback, databaseQueryCallback, documentSearchCallback) .build(); -AssistantMessage response = multiSourceAgent.call( - "比较我们的产品文档中的功能和最新的市场趋势" -); -``` +multiSourceAgent.invoke("比较我们的产品文档中的功能和最新的市场趋势");`} + ### 混合 RAG @@ -286,17 +354,29 @@ AssistantMessage response = multiSourceAgent.call( #### Java 实现示例(概念性) -```java -public class HybridRAGSystem { + +{`import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.document.Document; +import org.springframework.ai.vectorstore.VectorStore; +import java.util.List; +import java.util.stream.Collectors; +class HybridRAGSystem { private final ChatModel chatModel; private final VectorStore vectorStore; - private final QueryEnhancer queryEnhancer; - private final AnswerValidator answerValidator; + + public HybridRAGSystem(ChatModel chatModel, VectorStore vectorStore) { + this.chatModel = chatModel; + this.vectorStore = vectorStore; + } public String answer(String userQuestion) { // 1. 查询增强 - String enhancedQuery = queryEnhancer.enhance(userQuestion); + String enhancedQuery = enhanceQuery(userQuestion); int maxAttempts = 3; for (int attempt = 0; attempt < maxAttempts; attempt++) { @@ -313,7 +393,7 @@ public class HybridRAGSystem { String answer = generateAnswer(userQuestion, docs); // 5. 答案验证 - ValidationResult validation = answerValidator.validate(answer, docs); + ValidationResult validation = validateAnswer(answer, docs); if (validation.isValid()) { return answer; } @@ -329,24 +409,58 @@ public class HybridRAGSystem { return "无法生成满意的答案"; } + private String enhanceQuery(String query) { + return query; // 实现查询增强逻辑 + } + private boolean isRetrievalSufficient(List docs) { - // 实现检索质量评估逻辑 return !docs.isEmpty() && calculateRelevanceScore(docs) > 0.7; } + private double calculateRelevanceScore(List docs) { + return 0.8; // 实现相关性评分逻辑 + } + + private String refineQuery(String query, List docs) { + return query; // 实现查询优化逻辑 + } + private String generateAnswer(String question, List docs) { String context = docs.stream() - .map(Document::getContent) + .map(Document::getText) .collect(Collectors.joining("\n\n")); - return chatClient.prompt() + ChatClient client = ChatClient.builder(chatModel).build(); + return client.prompt() .system("基于以下上下文回答问题:\n" + context) .user(question) .call() .content(); } -} -``` + + private ValidationResult validateAnswer(String answer, List docs) { + // 实现答案验证逻辑 + return new ValidationResult(true, false); + } + + private String refineBasedOnValidation(String query, ValidationResult validation) { + return query; // 基于验证结果优化查询 + } + + class ValidationResult { + private boolean valid; + private boolean shouldRetry; + + public ValidationResult(boolean valid, boolean shouldRetry) { + this.valid = valid; + this.shouldRetry = shouldRetry; + } + + public boolean isValid() { return valid; } + public boolean shouldRetry() { return shouldRetry; } + } +}`} + 这种架构适用于: @@ -389,8 +503,11 @@ public class HybridRAGSystem { Spring AI Alibaba 提供了构建 RAG 系统的核心组件: -```java -// 文档加载和处理 + +{`// 文档加载和处理 import org.springframework.ai.document.Document; import org.springframework.ai.reader.TextReader; import org.springframework.ai.transformer.splitter.TokenTextSplitter; @@ -402,9 +519,16 @@ import org.springframework.ai.vectorstore.SimpleVectorStore; // 嵌入模型 import org.springframework.ai.embedding.EmbeddingModel; -// RAG Advisor -import org.springframework.ai.chat.client.advisor.QuestionAnswerAdvisor; -``` +// ChatClient +import org.springframework.ai.chat.client.ChatClient; + +// Agent +import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +// 工具 +import org.springframework.ai.tool.ToolCallback; +import org.springframework.ai.tool.function.FunctionToolCallback;`} + ## 相关文档 diff --git a/docs/frameworks/agent-framework/advanced/workflow.md b/docs/frameworks/agent-framework/advanced/workflow.md index 11b73e26..30d26738 100644 --- a/docs/frameworks/agent-framework/advanced/workflow.md +++ b/docs/frameworks/agent-framework/advanced/workflow.md @@ -51,15 +51,18 @@ Spring AI Alibaba 同时提供了声明式的 Agentic API 与底层原子化的 自定义 Node 需要实现 `NodeAction` 或 `NodeActionWithConfig` 接口: -```java -public interface NodeAction { + +{`public interface NodeAction { Map apply(OverAllState state) throws Exception; } public interface NodeActionWithConfig { Map apply(OverAllState state, RunnableConfig config) throws Exception; -} -``` +}`} + **主要区别**: - `NodeAction`:只接收状态作为参数,适用于简单的业务逻辑 @@ -69,8 +72,11 @@ public interface NodeActionWithConfig { 以下是一个简单的文本处理 Node: -```java -import com.alibaba.cloud.ai.graph.OverAllState; + +{`import com.alibaba.cloud.ai.graph.OverAllState; import com.alibaba.cloud.ai.graph.action.NodeAction; import java.util.HashMap; import java.util.Map; @@ -90,15 +96,18 @@ public class TextProcessorNode implements NodeAction { result.put("processed_text", processedText); return result; } -} -``` +}`} + ### 高级 Node 示例:带配置的 AI Node 以下示例展示如何创建一个调用 LLM 的 Node: -```java -import com.alibaba.cloud.ai.graph.OverAllState; + +{`import com.alibaba.cloud.ai.graph.OverAllState; import com.alibaba.cloud.ai.graph.RunnableConfig; import com.alibaba.cloud.ai.graph.action.NodeActionWithConfig; import org.springframework.ai.chat.client.ChatClient; @@ -144,15 +153,18 @@ public class QueryExpanderNode implements NodeActionWithConfig { output.put("queryVariants", List.of(variants)); return output; } -} -``` +}`} + ### 条件评估 Node 用于工作流中的条件分支判断: -```java -import com.alibaba.cloud.ai.graph.OverAllState; + +{`import com.alibaba.cloud.ai.graph.OverAllState; import com.alibaba.cloud.ai.graph.action.NodeAction; import java.util.HashMap; @@ -180,15 +192,18 @@ public class ConditionEvaluatorNode implements NodeAction { result.put("_condition_result", route); return result; } -} -``` +}`} + ### 并行结果聚合 Node 用于收集和聚合并行执行的多个 Node 的结果: -```java -import com.alibaba.cloud.ai.graph.OverAllState; + +{`import com.alibaba.cloud.ai.graph.OverAllState; import com.alibaba.cloud.ai.graph.action.NodeAction; import java.util.*; @@ -218,13 +233,16 @@ public class ParallelResultAggregatorNode implements NodeAction { output.put(outputKey, aggregatedResult); return output; } -} -``` +}`} + ### 集成自定义 Node 到 StateGraph -```java -import com.alibaba.cloud.ai.graph.StateGraph; + +{`import com.alibaba.cloud.ai.graph.StateGraph; import com.alibaba.cloud.ai.graph.KeyStrategyFactory; import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy; import org.springframework.context.annotation.Bean; @@ -273,8 +291,8 @@ public class WorkflowConfiguration { return graph; } -} -``` +}`} + ### Node 开发最佳实践 @@ -284,8 +302,11 @@ public class WorkflowConfiguration { 4. **日志记录**:添加适当的日志以便调试和监控 5. **参数验证**:在处理前验证从状态中获取的参数 -```java -public class RobustNode implements NodeAction { + +{`public class RobustNode implements NodeAction { private static final Logger logger = LoggerFactory.getLogger(RobustNode.class); @Override @@ -318,8 +339,8 @@ public class RobustNode implements NodeAction { // 具体业务逻辑 return input; } -} -``` +}`} + ## Agent作为Node @@ -327,10 +348,13 @@ public class RobustNode implements NodeAction { ### ReactAgent 作为 SubGraph Node -`ReactAgent` 可以通过 `asSubGraphNode()` 方法转换为可以嵌入到父 Graph 中的 Node: +`ReactAgent` 可以通过 `asNode()` 方法转换为可以嵌入到父 Graph 中的 Node: -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.StateGraph; import org.springframework.ai.chat.model.ChatModel; @@ -357,20 +381,16 @@ public class AgentWorkflowExample { StateGraph workflow = new StateGraph("multi_agent_workflow", keyStrategyFactory); // 将 Agent 作为 SubGraph Node 添加 - workflow.addNode("analysis", analysisAgent.asSubGraphNode( - "analysis_node", // Node ID + workflow.addNode("analysis", analysisAgent.asNode( true, // includeContents: 是否传递父图的消息历史 false, // returnReasoningContents: 是否返回推理过程 - "analysis_result", // outputKeyToParent: 输出键名 - "请分析以下数据" // instruction: 给 Agent 的指令 + "analysis_result" // outputKeyToParent: 输出键名 )); - workflow.addNode("reporting", reportAgent.asSubGraphNode( - "report_node", + workflow.addNode("reporting", reportAgent.asNode( true, false, - "final_report", - "请根据分析结果生成报告" + "final_report" )); // 定义流程 @@ -380,27 +400,28 @@ public class AgentWorkflowExample { return workflow; } -} -``` +}`} + ### SubGraph Node 参数说明 -`asSubGraphNode()` 方法支持以下参数配置: +`asNode()` 方法支持以下参数配置: | 参数 | 类型 | 说明 | |------|------|------| -| `nodeId` | String | Node 的唯一标识符 | | `includeContents` | boolean | 是否将父图的 messages 传递给子 Agent(默认 true) | | `returnReasoningContents` | boolean | 是否返回完整的推理过程,false 则只返回最终结果(默认 false) | | `outputKeyToParent` | String | 子 Agent 结果在父图状态中的键名 | -| `instruction` | String | 传递给子 Agent 的额外指令 | ### 多 Agent 协作工作流示例 以下示例展示了一个完整的多 Agent 协作场景: -```java -import com.alibaba.cloud.ai.graph.StateGraph; + +{`import com.alibaba.cloud.ai.graph.StateGraph; import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.CompileConfig; import org.springframework.ai.chat.model.ChatModel; @@ -442,32 +463,23 @@ public class MultiAgentWorkflow { StateGraph workflow = new StateGraph("research_workflow", keyStrategyFactory); // 添加 Agent 节点 - workflow.addNode("research", - researchAgent.asSubGraphNode( - "research_node", - true, // 包含历史消息 - false, // 不返回推理过程 - "research_data", - "请收集关于给定主题的相关信息" - )); - - workflow.addNode("analysis", - analysisAgent.asSubGraphNode( - "analysis_node", - true, - false, - "analysis_result", - "请分析研究数据并提取关键洞察" - )); - - workflow.addNode("summary", - summaryAgent.asSubGraphNode( - "summary_node", - true, - true, // 返回完整推理过程 - "final_summary", - "请将分析结果总结为执行摘要" - )); + workflow.addNode("research", researchAgent.asNode( + true, // 包含历史消息 + false, // 不返回推理过程 + "research_data" + )); + + workflow.addNode("analysis", analysisAgent.asNode( + true, + false, + "analysis_result" + )); + + workflow.addNode("summary", summaryAgent.asNode( + true, + true, // 返回完整推理过程 + "final_summary" + )); // 定义顺序执行流程 workflow.addEdge(StateGraph.START, "research"); @@ -477,15 +489,18 @@ public class MultiAgentWorkflow { return workflow; } -} -``` +}`} + ### Agent Node 与普通 Node 混合使用 在实际应用中,可以将 Agent Node 和自定义 Node 混合使用,充分发挥各自优势: -```java -public class HybridWorkflow { + +{`public class HybridWorkflow { public StateGraph buildHybridWorkflow(ChatModel chatModel) { // 创建 Agent @@ -517,12 +532,10 @@ public class HybridWorkflow { workflow.addNode("validate", node_async(validator)); // 添加 Agent Node - workflow.addNode("qa", qaAgent.asSubGraphNode( - "qa_node", + workflow.addNode("qa", qaAgent.asNode( true, false, - "qa_result", - null + "qa_result" )); // 定义流程:预处理 -> Agent处理 -> 验证 @@ -539,13 +552,16 @@ public class HybridWorkflow { return workflow; } -} -``` +}`} + ### 执行工作流 -```java -import com.alibaba.cloud.ai.graph.CompiledGraph; + +{`import com.alibaba.cloud.ai.graph.CompiledGraph; import com.alibaba.cloud.ai.graph.OverAllState; import com.alibaba.cloud.ai.graph.RunnableConfig; @@ -580,8 +596,8 @@ public class WorkflowExecutor { System.out.println("最终总结: " + state.value("final_summary").orElse("无")); }); } -} -``` +}`} + ### Agent Node 最佳实践 @@ -601,14 +617,17 @@ public class WorkflowExecutor { 3. **超时控制**:为每个 Agent 设置合理的超时时间 4. **资源管理**:合理配置 ChatModel 的连接池和并发参数 -```java -// 并行执行示例 + +{`// 并行执行示例 workflow.addNode("parallel_start", node_async(new TransparentNode())); // 添加多个并行 Agent -workflow.addNode("agent1", agent1.asSubGraphNode(...)); -workflow.addNode("agent2", agent2.asSubGraphNode(...)); -workflow.addNode("agent3", agent3.asSubGraphNode(...)); +workflow.addNode("agent1", agent1.asNode(true, false, "result1")); +workflow.addNode("agent2", agent2.asNode(true, false, "result2")); +workflow.addNode("agent3", agent3.asNode(true, false, "result3")); // 聚合结果 workflow.addNode("aggregator", node_async(new ParallelResultAggregatorNode("merged_result"))); @@ -617,8 +636,8 @@ workflow.addNode("aggregator", node_async(new ParallelResultAggregatorNode("merg workflow.addEdge(StateGraph.START, "parallel_start"); workflow.addEdge("parallel_start", List.of("agent1", "agent2", "agent3")); workflow.addEdge(List.of("agent1", "agent2", "agent3"), "aggregator"); -workflow.addEdge("aggregator", StateGraph.END); -``` +workflow.addEdge("aggregator", StateGraph.END);`} + ## 与Dify低代码平台集成 使用 Spring AI Alibaba Admin 平台,可以实现 Dify DSL 到 Spring AI Alibaba 高代码工程的导出。 diff --git a/docs/frameworks/agent-framework/tutorials/agents.md b/docs/frameworks/agent-framework/tutorials/agents.md index efceb6a2..9913b7ab 100644 --- a/docs/frameworks/agent-framework/tutorials/agents.md +++ b/docs/frameworks/agent-framework/tutorials/agents.md @@ -55,8 +55,7 @@ Model 是 Agent 的推理引擎。Spring AI Alibaba 支持多种配置方式。 {`import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; @@ -85,8 +84,7 @@ ReactAgent agent = ReactAgent.builder() {`import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; @@ -114,8 +112,7 @@ ChatModel chatModel = DashScopeChatModel.builder() {`import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.function.FunctionToolCallback; @@ -125,11 +122,38 @@ import java.util.function.BiFunction; // 定义工具(示例:仅一个搜索工具) public class SearchTool implements BiFunction { -{`import org.springframework.ai.tool.ToolCallback; - title="ToolErrorInterceptor 工具错误处理" - sourceUrl="https://github.com/alibaba/spring-ai-alibaba/blob/2024-main/community/graph/graph-agent/src/main/java/com/alibaba/cloud/ai/graph/agent/interceptor/ToolInterceptor.java" + @Override + public String apply(String query, ToolContext context) { + // 实现搜索逻辑 + return "搜索结果: " + query; + } +} + +// 创建工具回调 +ToolCallback searchTool = FunctionToolCallback.builder("search", new SearchTool()) + .description("搜索工具") + .build(); + +// 在Agent中使用 +ReactAgent agent = ReactAgent.builder() + .name("search_agent") + .model(chatModel) + .tools(searchTool) + .build();`} + + +#### 工具错误处理 + + -{`public class ToolErrorInterceptor extends ToolInterceptor { +{`import com.alibaba.cloud.ai.graph.agent.interceptor.ToolInterceptor; +import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallRequest; +import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallResponse; +import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallHandler; + +public class ToolErrorInterceptor extends ToolInterceptor { @Override public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) { try { @@ -147,6 +171,8 @@ public class SearchTool implements BiFunction { } ReactAgent agent = ReactAgent.builder() + .name("my_agent") + .model(chatModel) .interceptors(new ToolErrorInterceptor()) .build();`} @@ -170,7 +196,7 @@ System Prompt 塑造 Agent 处理任务的方式。 {`ReactAgent agent = ReactAgent.builder() .name("my_agent") @@ -185,7 +211,7 @@ System Prompt 塑造 Agent 处理任务的方式。 {`String instruction = """ 你是一个经验丰富的软件架构师。 @@ -212,8 +238,7 @@ ReactAgent agent = ReactAgent.builder() {`import com.alibaba.cloud.ai.graph.agent.interceptor.ModelInterceptor; import com.alibaba.cloud.ai.graph.agent.interceptor.ModelRequest; @@ -266,7 +291,7 @@ ReactAgent agent = ReactAgent.builder() {`import org.springframework.ai.chat.messages.AssistantMessage; @@ -292,7 +317,7 @@ AssistantMessage response = agent.call(messages);`} {`import com.alibaba.cloud.ai.graph.OverAllState; import java.util.Optional; @@ -319,7 +344,7 @@ if (result.isPresent()) { {`import com.alibaba.cloud.ai.graph.RunnableConfig; @@ -344,7 +369,7 @@ AssistantMessage response = agent.call("你的问题", runnableConfig);`} {`public class PoemOutput { private String title; @@ -380,7 +405,7 @@ System.out.println(response.getText());`} {`String customSchema = """ 请严格按照以下JSON格式返回结果: @@ -412,7 +437,7 @@ Agent 通过状态自动维护对话历史。使用 `MemorySaver` 配置持久 {`import com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig; import com.alibaba.cloud.ai.graph.checkpoint.constant.SaverEnum; @@ -444,8 +469,7 @@ Hooks 允许在 Agent 执行的关键点插入自定义逻辑。 {`import com.alibaba.cloud.ai.graph.agent.hook.*; @@ -514,8 +538,7 @@ Interceptors 提供更细粒度的控制,可以拦截和修改模型调用和 {`import com.alibaba.cloud.ai.graph.agent.interceptor.*; @@ -574,7 +597,7 @@ ReactAgent agent = ReactAgent.builder() {`import com.alibaba.cloud.ai.graph.agent.hook.modelcalllimit.ModelCallLimitHook; import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; @@ -590,7 +613,7 @@ ReactAgent agent = ReactAgent.builder() {`import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; @@ -644,8 +667,7 @@ ReactAgent agent = ReactAgent.builder() {`import reactor.core.publisher.Flux; diff --git a/docs/frameworks/agent-framework/tutorials/hooks.md b/docs/frameworks/agent-framework/tutorials/hooks.md index 2ddfb7f9..ecc9c8fd 100644 --- a/docs/frameworks/agent-framework/tutorials/hooks.md +++ b/docs/frameworks/agent-framework/tutorials/hooks.md @@ -25,8 +25,11 @@ Hooks 和 Interceptors 在这些步骤的前后暴露了钩子点,允许你: 通过将它们传递给 `ReactAgent.builder()` 来添加 Hooks 和 Interceptors: -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.agent.hook.*; import com.alibaba.cloud.ai.graph.agent.interceptor.*; @@ -36,8 +39,8 @@ ReactAgent agent = ReactAgent.builder() .tools(tools) .hooks(loggingHook, messageTrimmingHook) .interceptors(guardrailInterceptor, retryInterceptor) - .build(); -``` + .build();`} + ## Hooks 和 Interceptors 能做什么? @@ -62,8 +65,11 @@ Spring AI Alibaba 为常见用例提供了预构建的 Hooks 和 Interceptors * 具有大量历史记录的多轮对话 * 需要保留完整对话上下文的应用程序 -```java -import com.alibaba.cloud.ai.graph.agent.hook.summarization.SummarizationHook; + +{`import com.alibaba.cloud.ai.graph.agent.hook.summarization.SummarizationHook; // 创建消息压缩 Hook SummarizationHook summarizationHook = SummarizationHook.builder() @@ -77,8 +83,8 @@ ReactAgent agent = ReactAgent.builder() .name("my_agent") .model(chatModel) .hooks(summarizationHook) - .build(); -``` + .build();`} + **配置选项**: - `model`: 用于生成摘要的 ChatModel @@ -94,8 +100,11 @@ ReactAgent agent = ReactAgent.builder() * 人工监督是强制性的合规工作流程 * 长期对话,使用人工反馈引导 Agent -```java -import com.alibaba.cloud.ai.graph.agent.hook.hip.HumanInTheLoopHook; + +{`import com.alibaba.cloud.ai.graph.agent.hook.hip.HumanInTheLoopHook; import com.alibaba.cloud.ai.graph.agent.hook.hip.ToolConfig; // 创建 Human-in-the-Loop Hook @@ -110,8 +119,8 @@ ReactAgent agent = ReactAgent.builder() .tools(sendEmailTool, deleteDataTool) .hooks(humanReviewHook) .saver(new RedisSaver()) - .build(); -``` + .build();`} + **重要提示**:Human-in-the-loop Hook 需要 checkpointer 来维护跨中断的状态。示例中我们演示用了 `RedisSaver`。 @@ -124,14 +133,17 @@ ReactAgent agent = ReactAgent.builder() * 在生产部署中强制执行成本控制 * 在特定调用预算内测试 Agent 行为 -```java -ReactAgent agent = ReactAgent.builder() + +{`ReactAgent agent = ReactAgent.builder() .name("my_agent") .model(chatModel) .hooks(ModelCallLimitHook.builder().runLimit(5).build()) // 限制模型调用次数为5次 .saver(new MemorySaver()) - .build(); -``` + .build();`} + ### PII 检测(Personally Identifiable Information) @@ -142,8 +154,11 @@ ReactAgent agent = ReactAgent.builder() * 需要清理日志的客户服务 Agent * 任何处理敏感用户数据的应用程序 -```java -import com.alibaba.cloud.ai.graph.agent.hook.pii.PIIDetectionHook; + +{`import com.alibaba.cloud.ai.graph.agent.hook.pii.PIIDetectionHook; import com.alibaba.cloud.ai.graph.agent.hook.pii.PIIType; import com.alibaba.cloud.ai.graph.agent.hook.pii.RedactionStrategy; @@ -158,8 +173,8 @@ ReactAgent agent = ReactAgent.builder() .name("secure_agent") .model(chatModel) .hooks(pii) - .build(); -``` + .build();`} + ### 工具重试(Tool Retry) @@ -170,8 +185,11 @@ ReactAgent agent = ReactAgent.builder() * 提高依赖网络的工具的可靠性 * 构建优雅处理临时错误的弹性 Agent -```java -import com.alibaba.cloud.ai.graph.agent.interceptor.toolretry.ToolRetryInterceptor; + +{`import com.alibaba.cloud.ai.graph.agent.interceptor.toolretry.ToolRetryInterceptor; // 使用 ReactAgent agent = ReactAgent.builder() @@ -182,8 +200,8 @@ ReactAgent agent = ReactAgent.builder() .maxRetries(2) .onFailure(ToolRetryInterceptor.OnFailureBehavior.RETURN_MESSAGE) .build()) - .build(); -``` + .build();`} + ### Planning(规划) @@ -195,8 +213,11 @@ ReactAgent agent = ReactAgent.builder() * 通过在执行前显示 Agent 的计划来提高透明度 * 通过检查建议的计划来调试错误 -```java -import com.alibaba.cloud.ai.graph.agent.interceptor.todolist.TodoListInterceptor; + +{`import com.alibaba.cloud.ai.graph.agent.interceptor.todolist.TodoListInterceptor; // 使用 ReactAgent agent = ReactAgent.builder() @@ -204,8 +225,8 @@ ReactAgent agent = ReactAgent.builder() .model(chatModel) .tools(myTool) .interceptors(TodoListInterceptor.builder().build()) - .build(); -``` + .build();`} + ### LLM Tool Selector(LLM 工具选择器) @@ -216,8 +237,11 @@ ReactAgent agent = ReactAgent.builder() * 需要根据细微的上下文差异进行工具选择 * 动态选择最适合特定输入的工具 -```java -import com.alibaba.cloud.ai.graph.agent.interceptor.toolselection.ToolSelectionInterceptor; + +{`import com.alibaba.cloud.ai.graph.agent.interceptor.toolselection.ToolSelectionInterceptor; // 使用 ReactAgent agent = ReactAgent.builder() @@ -225,8 +249,8 @@ ReactAgent agent = ReactAgent.builder() .model(chatModel) .tools(tool1, tool2) .interceptors(ToolSelectionInterceptor.builder().build()) - .build(); -``` + .build();`} + ### LLM Tool Emulator(LLM 工具模拟器) @@ -237,8 +261,11 @@ ReactAgent agent = ReactAgent.builder() * 在开发过程中为工具提供占位符行为 * 在不产生实际成本或副作用的情况下测试 Agent 逻辑 -```java -import com.alibaba.cloud.ai.graph.agent.interceptor.toolemulator.ToolEmulatorInterceptor; + +{`import com.alibaba.cloud.ai.graph.agent.interceptor.toolemulator.ToolEmulatorInterceptor; // 使用 ReactAgent agent = ReactAgent.builder() @@ -246,8 +273,8 @@ ReactAgent agent = ReactAgent.builder() .model(chatModel) .tools(simulatedTool) .interceptors(ToolEmulatorInterceptor.builder().model(chatModel).build()) - .build(); -``` + .build();`} + ### Context Editing(上下文编辑) @@ -258,16 +285,19 @@ ReactAgent agent = ReactAgent.builder() * 从对话历史中删除不相关或冗余的信息 * 动态修改上下文以引导 Agent 的行为 -```java -import com.alibaba.cloud.ai.graph.agent.interceptor.contextediting.ContextEditingInterceptor; + +{`import com.alibaba.cloud.ai.graph.agent.interceptor.contextediting.ContextEditingInterceptor; // 使用 ReactAgent agent = ReactAgent.builder() .name("context_aware_agent") .model(chatModel) .interceptors(ContextEditingInterceptor.builder().trigger(120000).clearAtLeast(60000).build()) - .build(); -``` + .build();`} + ## 自定义 Hooks 和 Interceptors @@ -284,8 +314,11 @@ ReactAgent agent = ReactAgent.builder() 在模型调用前后执行自定义逻辑: -```java -import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; + +{`import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; import com.alibaba.cloud.ai.graph.agent.hook.HookPositions; import java.util.concurrent.CompletableFuture; @@ -316,15 +349,18 @@ public class CustomModelHook extends ModelHook { // 可以记录响应信息 return CompletableFuture.completedFuture(Map.of()); } -} -``` +}`} + ### AgentHook 在 Agent 整体执行的开始和结束时执行: -```java -import com.alibaba.cloud.ai.graph.agent.hook.AgentHook; + +{`import com.alibaba.cloud.ai.graph.agent.hook.AgentHook; import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; import com.alibaba.cloud.ai.graph.agent.hook.HookPositions; import java.util.concurrent.CompletableFuture; @@ -355,15 +391,18 @@ public class CustomAgentHook extends AgentHook { } return CompletableFuture.completedFuture(Map.of()); } -} -``` +}`} + ### ModelInterceptor 拦截和修改模型请求和响应: -```java -import com.alibaba.cloud.ai.graph.agent.interceptor.ModelInterceptor; + +{`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; @@ -391,15 +430,18 @@ public class LoggingInterceptor extends ModelInterceptor { public String getName() { return "LoggingInterceptor"; } -} -``` +}`} + ### ToolInterceptor 拦截和修改工具调用: -```java -import com.alibaba.cloud.ai.graph.agent.interceptor.ToolInterceptor; + +{`import com.alibaba.cloud.ai.graph.agent.interceptor.ToolInterceptor; import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallRequest; import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallResponse; import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallHandler; @@ -436,8 +478,8 @@ public class ToolMonitoringInterceptor extends ToolInterceptor { public String getName() { return "ToolMonitoringInterceptor"; } -} -``` +}`} + ### 使用 RunnableConfig 跨调用共享数据 @@ -451,8 +493,11 @@ public class ToolMonitoringInterceptor extends ToolInterceptor { **示例:使用 RunnableConfig.context() 实现调用计数器** -```java -import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; + +{`import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; import com.alibaba.cloud.ai.graph.agent.hook.HookPositions; import com.alibaba.cloud.ai.graph.RunnableConfig; @@ -511,13 +556,16 @@ public class ModelCallCounterHook extends ModelHook { return CompletableFuture.completedFuture(Map.of()); } -} -``` +}`} + **示例:基于 context 实现调用次数限制** -```java -import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; + +{`import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; 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.JumpTo; @@ -579,20 +627,23 @@ public class ModelCallLimiterHook extends ModelHook { public List canJumpTo() { return List.of(JumpTo.end); } -} -``` +}`} + **使用示例**: -```java -ReactAgent agent = ReactAgent.builder() + +{`ReactAgent agent = ReactAgent.builder() .name("limited_agent") .model(chatModel) .tools(tools) .hooks(new ModelCallCounterHook()) // 监控调用统计 .hooks(new ModelCallLimiterHook(5)) // 限制最多调用 5 次 - .build(); -``` + .build();`} + **关键要点**: @@ -605,15 +656,18 @@ ReactAgent agent = ReactAgent.builder() 使用多个 Hooks 和 Interceptors 时,理解执行顺序很重要: -```java -ReactAgent agent = ReactAgent.builder() + +{`ReactAgent agent = ReactAgent.builder() .name("my_agent") .model(chatModel) .hooks(hook1, hook2, hook3) .interceptors(interceptor1, interceptor2) .interceptors(toolInterceptor1, toolInterceptor2) - .build(); -``` + .build();`} + **执行流程**: @@ -704,8 +758,11 @@ ReactAgent agent = ReactAgent.builder() ### 示例 1:内容审核 Interceptor -```java -public class ContentModerationInterceptor extends ModelInterceptor { + +{`public class ContentModerationInterceptor extends ModelInterceptor { private static final List BLOCKED_WORDS = List.of("敏感词1", "敏感词2", "敏感词3"); @@ -744,15 +801,18 @@ public class ContentModerationInterceptor extends ModelInterceptor { public String getName() { return "ContentModerationInterceptor"; } -} -``` +}`} + ### 示例 2:性能监控 - 使用 Interceptor 使用 `ModelInterceptor` 和 `ToolInterceptor` 监控模型和工具调用的性能: -```java -// 模型调用性能监控 + +{`// 模型调用性能监控 public class ModelPerformanceInterceptor extends ModelInterceptor { @Override @@ -820,13 +880,16 @@ ReactAgent agent = ReactAgent.builder() .tools(tools) .interceptors(new ModelPerformanceInterceptor()) .interceptors(new ToolPerformanceInterceptor()) - .build(); -``` + .build();`} + ### 示例 3:工具缓存 Interceptor -```java -public class ToolCacheInterceptor extends ToolInterceptor { + +{`public class ToolCacheInterceptor extends ToolInterceptor { private Map cache = new ConcurrentHashMap<>(); private final long ttlMs; @@ -869,8 +932,8 @@ public class ToolCacheInterceptor extends ToolInterceptor { // 实现 TTL 检查逻辑 return false; } -} -``` +}`} + ## 总结 diff --git a/docs/frameworks/agent-framework/tutorials/memory.md b/docs/frameworks/agent-framework/tutorials/memory.md index 2cb188d0..17fa23ef 100644 --- a/docs/frameworks/agent-framework/tutorials/memory.md +++ b/docs/frameworks/agent-framework/tutorials/memory.md @@ -30,11 +30,12 @@ Spring AI Alibaba 将短期记忆作为 Agent 状态的一部分进行管理。 在 Spring AI Alibaba 中,要向 Agent 添加短期记忆(会话级持久化),你需要在创建 Agent 时指定 `checkpointer`。 -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; -import com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig; -import com.alibaba.cloud.ai.graph.checkpoint.constant.SaverEnum; import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; import com.alibaba.cloud.ai.graph.RunnableConfig; @@ -51,8 +52,8 @@ RunnableConfig config = RunnableConfig.builder() .threadId("1") // threadId 指定会话 ID .build(); -agent.call("你好!我叫 Bob。", config); -``` +agent.call("你好!我叫 Bob。", config);`} + ### 在生产环境中 @@ -60,20 +61,23 @@ agent.call("你好!我叫 Bob。", config); **示例:使用 Redis Checkpointer**: -```java -import com.alibaba.cloud.ai.graph.checkpoint.savers.RedisSaver; -import org.springframework.data.redis.connection.RedisConnectionFactory; + +{`import com.alibaba.cloud.ai.graph.checkpoint.savers.RedisSaver; +import org.redisson.api.RedissonClient; // 配置 Redis checkpointer -RedisSaver redisSaver = new RedisSaver(redisConnectionFactory); +RedisSaver redisSaver = new RedisSaver(redissonClient); ReactAgent agent = ReactAgent.builder() .name("my_agent") .model(chatModel) .tools(getUserInfoTool) .saver(redisSaver) - .build(); -``` + .build();`} + ## 自定义 Agent 记忆 @@ -81,11 +85,19 @@ ReactAgent agent = ReactAgent.builder() 你可以通过在工具或 Hook 中访问和修改状态来扩展记忆功能。 -```java + +{`import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; +import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.RunnableConfig; import org.springframework.ai.chat.messages.Message; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.concurrent.CompletableFuture; // 在 Hook 中访问和修改状态 public class CustomMemoryHook extends ModelHook { @@ -116,8 +128,12 @@ public class CustomMemoryHook extends ModelHook { )); } -} -``` + @Override + public CompletableFuture> afterModel(OverAllState state, RunnableConfig config) { + return CompletableFuture.completedFuture(Map.of()); + } +}`} + ## 常见模式 @@ -138,8 +154,11 @@ public class CustomMemoryHook extends ModelHook { 要在 Agent 中修剪消息历史,请使用 `ModelHook`: -```java -import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; + +{`import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; import com.alibaba.cloud.ai.graph.OverAllState; import com.alibaba.cloud.ai.graph.RunnableConfig; @@ -220,8 +239,8 @@ agent.call("现在对狗做同样的事情", config); AssistantMessage finalResponse = agent.call("我叫什么名字?", config); System.out.println(finalResponse.getText()); -// 输出:你的名字是 Bob。你之前告诉我的。 -``` +// 输出:你的名字是 Bob。你之前告诉我的。`} + ### 删除消息 @@ -231,7 +250,21 @@ System.out.println(finalResponse.getText()); 要从 Graph 状态中删除消息,你可以在 Hook 中返回新的消息列表: -```java + +{`import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; +import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.state.RemoveByHash; +import org.springframework.ai.chat.messages.Message; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + public class MessageDeletionHook extends ModelHook { @Override @@ -244,6 +277,11 @@ public class MessageDeletionHook extends ModelHook { return new HookPosition[]{HookPosition.AFTER_MODEL}; } + @Override + public CompletableFuture> beforeModel(OverAllState state, RunnableConfig config) { + return CompletableFuture.completedFuture(Map.of()); + } + @Override public CompletableFuture> afterModel(OverAllState state, RunnableConfig config) { Optional messagesOpt = state.value("messages"); @@ -254,22 +292,25 @@ public class MessageDeletionHook extends ModelHook { List messages = (List) messagesOpt.get(); if (messages.size() > 2) { - // 将被裁剪的两条旧消息转为 RemoveByHash,并连同保留的后续消息一起返回 - List removeFirstTwoMessages = new ArrayList<>(); - removeFirstTwoMessages.add(RemoveByHash.of(messages.get(0))); - removeFirstTwoMessages.add(RemoveByHash.of(messages.get(1))); - return CompletableFuture.completedFuture(Map.of("messages", removeFirstTwoMessages)); - } + // 将最早的两条消息转为 RemoveByHash 对象以便从状态中删除 + List removeOldMessages = new ArrayList<>(); + removeOldMessages.add(RemoveByHash.of(messages.get(0))); + removeOldMessages.add(RemoveByHash.of(messages.get(1))); + return CompletableFuture.completedFuture(Map.of("messages", removeOldMessages)); + } return CompletableFuture.completedFuture(Map.of()); } -} -``` +}`} + **删除所有消息**: -```java -import com.alibaba.cloud.ai.graph.state.RemoveByHash; + +{`import com.alibaba.cloud.ai.graph.state.RemoveByHash; public class ClearAllMessagesHook extends ModelHook { @@ -300,16 +341,19 @@ public class ClearAllMessagesHook extends ModelHook { return CompletableFuture.completedFuture(Map.of("messages", removeAllMessages)); } -} -``` +}`} + **警告**:删除消息时,**确保**生成的消息历史有效。检查你使用的 LLM 提供商的限制。例如: * 某些提供商期望消息历史以 `user` 消息开始 * 大多数提供商要求带有工具调用的 `assistant` 消息后跟相应的 `tool` 结果消息 -```java -import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; + +{`import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; import com.alibaba.cloud.ai.graph.state.RemoveByHash; public class DeleteOldMessagesHook extends ModelHook { @@ -353,7 +397,7 @@ ReactAgent agent = ReactAgent.builder() .build(); RunnableConfig config = RunnableConfig.builder() - .configurable(Map.of("thread_id", "1")) + .threadId("1") .build(); // 第一次调用 @@ -362,8 +406,8 @@ agent.call("你好!我是 bob", config); // 第二次调用 agent.call("我叫什么名字?", config); -// 输出:[('human', "我叫什么名字?"), ('assistant', '你的名字是 Bob...')] -``` +// 输出:[('human', "我叫什么名字?"), ('assistant', '你的名字是 Bob...')]`} + ### 总结消息 @@ -371,11 +415,26 @@ agent.call("我叫什么名字?", config); 要在 Agent 中总结消息历史,可以使用自定义 Hook: -```java + +{`import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; +import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; +import com.alibaba.cloud.ai.graph.OverAllState; +import com.alibaba.cloud.ai.graph.RunnableConfig; +import com.alibaba.cloud.ai.graph.state.RemoveByHash; import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.SystemMessage; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.prompt.Prompt; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; public class MessageSummarizationHook extends ModelHook { @@ -428,10 +487,6 @@ public class MessageSummarizationHook extends ModelHook { } List oldMessages = messages.subList(0, messagesToSummarize); - List recentMessages = messages.subList( - messagesToSummarize, - messages.size() - ); // 生成摘要 String summary = generateSummary(oldMessages); @@ -441,14 +496,13 @@ public class MessageSummarizationHook extends ModelHook { "## 之前对话摘要:\n" + summary ); + // 只需要把摘要消息和需要删除的消息保留在状态中,其余未包含的消息将会自动保留 List newMessages = new ArrayList<>(); newMessages.add(summaryMessage); - newMessages.addAll(recentMessages); - - // IMPORTANT! Convert toSummarize messages to RemoveByHash objects so we can remove them from state - for (Message msg : messagesToSummarize) { - newMessages.add(RemoveByHash.of(msg)); - } + // IMPORTANT! Convert summarized messages to RemoveByHash objects so we can remove them from state + for (Message msg : oldMessages) { + newMessages.add(RemoveByHash.of(msg)); + } return CompletableFuture.completedFuture(Map.of("messages", newMessages)); } @@ -471,6 +525,10 @@ public class MessageSummarizationHook extends ModelHook { return response.getResult().getOutput().getText(); } + @Override + public CompletableFuture> afterModel(OverAllState state, RunnableConfig config) { + return CompletableFuture.completedFuture(Map.of()); + } } // 使用 @@ -490,7 +548,7 @@ ReactAgent agent = ReactAgent.builder() .build(); RunnableConfig config = RunnableConfig.builder() - .configurable(Map.of("thread_id", "1")) + .threadId("1") .build(); agent.call("你好,我叫 bob", config); @@ -499,8 +557,8 @@ agent.call("现在对狗做同样的事情", config); AssistantMessage finalResponse = agent.call("我叫什么名字?", config); System.out.println(finalResponse.getText()); -// 输出:你的名字是 Bob! -``` +// 输出:你的名字是 Bob!`} + ## 访问记忆 @@ -514,10 +572,16 @@ System.out.println(finalResponse.getText()); `toolContext` 参数从工具签名中隐藏(因此模型看不到它),但工具可以通过它访问状态。 -```java + +{`import com.alibaba.cloud.ai.graph.RunnableConfig; import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.function.FunctionToolCallback; import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.chat.messages.AssistantMessage; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; import java.util.function.BiFunction; public class UserInfoTool implements BiFunction { @@ -525,8 +589,8 @@ public class UserInfoTool implements BiFunction { @Override public String apply(String query, ToolContext toolContext) { // 从上下文中获取用户信息 - RunnableConfig config = (RunnableConfig) toolContext.getContext().get("config"); - String userId = (String) config.metadata("user_id").orElse(""); + RunnableConfig config = (RunnableConfig) toolContext.getContext().get("config"); + String userId = (String) config.metadata("user_id").orElse(""); if ("user_123".equals(userId)) { return "用户是 John Smith"; @@ -548,12 +612,17 @@ ReactAgent agent = ReactAgent.builder() .name("my_agent") .model(chatModel) .tools(getUserInfoTool) + .saver(new MemorySaver()) + .build(); + +RunnableConfig config = RunnableConfig.builder() + .threadId("1") + .addMetadata("user_id", "user_123") .build(); -// 传递上下文 -Map context = Map.of("user_id", "user_123"); -// 注意:需要通过 RunnableConfig 或其他方式传递上下文 -``` +AssistantMessage response = agent.call("获取用户信息", config); +System.out.println(response.getText());`} + #### 从工具写入短期记忆 @@ -565,8 +634,11 @@ Map context = Map.of("user_id", "user_123"); 在 Hook 中访问短期记忆(状态)以基于对话历史或自定义状态字段创建动态提示。 -```java -import com.alibaba.cloud.ai.graph.agent.interceptor.ModelInterceptor; + +{`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; @@ -612,15 +684,18 @@ ReactAgent agent = ReactAgent.builder() .build(); // 使用时传递上下文 -Map context = Map.of("user_name", "John Smith"); -``` +Map context = Map.of("user_name", "John Smith");`} + ### Before Model 在 `beforeModel` Hook 中访问短期记忆(状态)以在模型调用之前处理消息。 -```java -import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; + +{`import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; public class TrimMessagesHook extends ModelHook { @@ -680,15 +755,18 @@ ReactAgent agent = ReactAgent.builder() .tools(tools) .hooks(new TrimMessagesHook()) .saver(new MemorySaver()) - .build(); -``` + .build();`} + ### After Model 在 `afterModel` Hook 中访问短期记忆(状态)以在模型调用之后处理消息。 -```java -import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; + +{`import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; public class ValidateResponseHook extends ModelHook { @@ -746,8 +824,8 @@ ReactAgent agent = ReactAgent.builder() .model(chatModel) .hooks(new ValidateResponseHook()) .saver(new MemorySaver()) - .build(); -``` + .build();`} + ## 相关资源 diff --git a/docs/frameworks/agent-framework/tutorials/messages.md b/docs/frameworks/agent-framework/tutorials/messages.md index 49512fae..9cbf0480 100644 --- a/docs/frameworks/agent-framework/tutorials/messages.md +++ b/docs/frameworks/agent-framework/tutorials/messages.md @@ -18,8 +18,11 @@ Spring AI Alibaba 提供了一个标准的消息类型系统,可在所有模 使用 messages 最简单的方式是创建消息对象并在调用模型时传递它们。 -```java -import org.springframework.ai.chat.model.ChatModel; + +{`import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.messages.SystemMessage; import org.springframework.ai.chat.messages.AssistantMessage; @@ -35,17 +38,20 @@ UserMessage userMsg = new UserMessage("你好,你好吗?"); // 与聊天模型一起使用 List messages = List.of(systemMsg, userMsg); Prompt prompt = new Prompt(messages); -ChatResponse response = chatModel.call(prompt); // 返回 ChatResponse,包含 AssistantMessage -``` +ChatResponse response = chatModel.call(prompt); // 返回 ChatResponse,包含 AssistantMessage`} + ### 文本提示 文本提示是字符串 - 适用于简单的生成任务,不需要保留对话历史。 -```java -// 使用字符串直接调用 -String response = chatModel.call("写一首关于春天的俳句"); -``` + +{`// 使用字符串直接调用 +String response = chatModel.call("写一首关于春天的俳句");`} + **使用文本提示的场景**: @@ -57,8 +63,11 @@ String response = chatModel.call("写一首关于春天的俳句"); 或者,你可以通过提供消息对象列表向模型传递消息列表。 -```java -import org.springframework.ai.chat.messages.SystemMessage; + +{`import org.springframework.ai.chat.messages.SystemMessage; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.prompt.Prompt; @@ -70,8 +79,8 @@ List messages = List.of( new AssistantMessage("樱花盛开时...") ); Prompt prompt = new Prompt(messages); -ChatResponse response = chatModel.call(prompt); -``` +ChatResponse response = chatModel.call(prompt);`} + **使用消息提示的场景**: @@ -90,19 +99,25 @@ ChatResponse response = chatModel.call(prompt); `SystemMessage` 表示一组初始指令,用于引导模型的行为。你可以使用系统消息来设置语气、定义模型的角色并建立响应指南。 -```java -// 基础指令 + +{`// 基础指令 SystemMessage systemMsg = new SystemMessage("你是一个有帮助的编程助手。"); List messages = List.of( systemMsg, new UserMessage("如何创建 REST API?") ); -ChatResponse response = chatModel.call(new Prompt(messages)); -``` - -```java -// 详细的角色设定 +ChatResponse response = chatModel.call(new Prompt(messages));`} + + + +{`// 详细的角色设定 SystemMessage systemMsg = new SystemMessage(""" 你是一位资深的 Java 开发者,擅长 Web 框架。 始终提供代码示例并解释你的推理。 @@ -113,8 +128,8 @@ List messages = List.of( systemMsg, new UserMessage("如何创建 REST API?") ); -ChatResponse response = chatModel.call(new Prompt(messages)); -``` +ChatResponse response = chatModel.call(new Prompt(messages));`} + ### User Message @@ -122,21 +137,27 @@ ChatResponse response = chatModel.call(new Prompt(messages)); #### 文本内容 -```java -// 使用消息对象 + +{`// 使用消息对象 ChatResponse response = chatModel.call( new Prompt(List.of(new UserMessage("什么是机器学习?"))) ); // 使用字符串快捷方式 // 使用字符串是单个 UserMessage 的快捷方式 -String response = chatModel.call("什么是机器学习?"); -``` +String response = chatModel.call("什么是机器学习?");`} + #### 消息元数据 -```java -import java.util.Map; + +{`import java.util.Map; UserMessage userMsg = UserMessage.builder() .text("你好!") @@ -144,8 +165,8 @@ UserMessage userMsg = UserMessage.builder() "user_id", "alice", // 可选:识别不同用户 "session_id", "sess_123" // 可选:会话标识符 )) - .build(); -``` + .build();`} + **注意**:元数据字段的行为因提供商而异 - 有些用于用户识别,有些则忽略它。要检查,请参考模型提供商的文档。 @@ -153,8 +174,11 @@ UserMessage userMsg = UserMessage.builder() `UserMessage` 可以包含多模态内容,如图像: -```java -import org.springframework.ai.content.Media; + +{`import org.springframework.ai.content.Media; import org.springframework.util.MimeTypeUtils; import java.net.URL; @@ -165,25 +189,31 @@ UserMessage userMsg = UserMessage.builder() .mimeType(MimeTypeUtils.IMAGE_JPEG) .data(new URL("https://example.com/image.jpg")) .build()) - .build(); -``` + .build();`} + ### Assistant Message `AssistantMessage` 表示模型调用的输出。它们可以包括多模态数据、工具调用以及你稍后可以访问的提供商特定元数据。 -```java -ChatResponse response = chatModel.call(new Prompt("解释 AI")); + +{`ChatResponse response = chatModel.call(new Prompt("解释 AI")); AssistantMessage aiMessage = response.getResult().getOutput(); -System.out.println(aiMessage.getText()); -``` +System.out.println(aiMessage.getText());`} + `AssistantMessage` 对象由模型调用返回,其中包含响应中的所有相关元数据。 提供商对消息类型的权重/上下文化方式不同,这意味着有时手动创建新的 `AssistantMessage` 对象并将其插入消息历史中(就像它来自模型一样)会很有帮助。 -```java -import org.springframework.ai.chat.messages.AssistantMessage; + +{`import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.SystemMessage; import org.springframework.ai.chat.messages.UserMessage; @@ -198,8 +228,8 @@ List messages = List.of( new UserMessage("太好了!2+2 等于多少?") ); -ChatResponse response = chatModel.call(new Prompt(messages)); -``` +ChatResponse response = chatModel.call(new Prompt(messages));`} + **AssistantMessage 属性**: @@ -212,8 +242,11 @@ ChatResponse response = chatModel.call(new Prompt(messages)); 当模型进行工具调用时,它们包含在 `AssistantMessage` 中: -```java -import org.springframework.ai.chat.messages.AssistantMessage.ToolCall; + +{`import org.springframework.ai.chat.messages.AssistantMessage.ToolCall; ChatResponse response = chatModel.call(prompt); AssistantMessage aiMessage = response.getResult().getOutput(); @@ -224,15 +257,18 @@ if (aiMessage.hasToolCalls()) { System.out.println("Args: " + toolCall.arguments()); System.out.println("ID: " + toolCall.id()); } -} -``` +}`} + #### Token 使用 Spring AI Alibaba 的 `ChatResponse` 可以在其元数据中保存 token 计数和其他使用元数据: -```java -ChatResponse response = chatModel.call(new Prompt("你好!")); + +{`ChatResponse response = chatModel.call(new Prompt("你好!")); ChatResponseMetadata metadata = response.getMetadata(); // 访问使用信息 @@ -240,15 +276,18 @@ if (metadata != null && metadata.getUsage() != null) { System.out.println("Input tokens: " + metadata.getUsage().getPromptTokens()); System.out.println("Output tokens: " + metadata.getUsage().getCompletionTokens()); System.out.println("Total tokens: " + metadata.getUsage().getTotalTokens()); -} -``` +}`} + #### 流式和块 在流式传输期间,你将收到可以组合成完整消息对象的块: -```java -import reactor.core.publisher.Flux; + +{`import reactor.core.publisher.Flux; Flux responseStream = chatModel.stream(new Prompt("你好")); @@ -259,8 +298,8 @@ responseStream.subscribe( fullResponse.append(content); System.out.print(content); } -); -``` +);`} + **了解更多**: - 从聊天模型流式传输 tokens @@ -270,8 +309,11 @@ responseStream.subscribe( 对于支持工具调用的模型,AI 消息可以包含工具调用。工具消息用于将单个工具执行的结果传回模型。 -```java -import org.springframework.ai.chat.messages.ToolResponseMessage; + +{`import org.springframework.ai.chat.messages.ToolResponseMessage; import org.springframework.ai.chat.messages.ToolResponseMessage.ToolResponse; // 在模型进行工具调用后 @@ -301,8 +343,8 @@ List messages = List.of( aiMessage, // 模型的工具调用 toolMessage // 工具执行结果 ); -ChatResponse response = chatModel.call(new Prompt(messages)); -``` +ChatResponse response = chatModel.call(new Prompt(messages));`} + **ToolResponseMessage 属性**: @@ -319,8 +361,11 @@ ChatResponse response = chatModel.call(new Prompt(messages)); ### 图像输入 -```java -import org.springframework.ai.content.Media; + +{`import org.springframework.ai.content.Media; import org.springframework.util.MimeTypeUtils; import java.net.URL; @@ -342,13 +387,16 @@ UserMessage message = UserMessage.builder() MimeTypeUtils.IMAGE_JPEG, new ClassPathResource("images/photo.jpg") )) - .build(); -``` + .build();`} + ### 音频输入 -```java -import org.springframework.ai.content.Media; + +{`import org.springframework.ai.content.Media; import org.springframework.util.MimeTypeUtils; UserMessage message = UserMessage.builder() @@ -357,13 +405,16 @@ UserMessage message = UserMessage.builder() MimeTypeUtils.parseMimeType("audio/wav"), new ClassPathResource("audio/recording.wav") )) - .build(); -``` + .build();`} + ### 视频输入 -```java -import org.springframework.ai.content.Media; + +{`import org.springframework.ai.content.Media; import org.springframework.util.MimeTypeUtils; UserMessage message = UserMessage.builder() @@ -372,8 +423,8 @@ UserMessage message = UserMessage.builder() .mimeType(MimeTypeUtils.parseMimeType("video/mp4")) .data(new URL("https://example.com/path/to/video.mp4")) .build()) - .build(); -``` + .build();`} + **警告**:并非所有模型都支持所有文件类型。请查看模型提供商的文档以了解支持的格式和大小限制。 @@ -383,8 +434,11 @@ Chat models 接受消息对象序列作为输入并返回 `ChatResponse`(包 ### 基础对话示例 -```java -import org.springframework.ai.chat.model.ChatModel; + +{`import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.messages.AssistantMessage; @@ -408,15 +462,18 @@ conversationHistory.add(response2.getResult().getOutput()); // 第三轮对话 conversationHistory.add(new UserMessage("从哪里开始?")); -ChatResponse response3 = chatModel.call(new Prompt(conversationHistory)); -``` +ChatResponse response3 = chatModel.call(new Prompt(conversationHistory));`} + ### 使用 Builder 模式 Spring AI Alibaba 的消息类提供了 builder 模式以便于构建: -```java -// UserMessage with builder + +{`// UserMessage with builder UserMessage userMsg = UserMessage.builder() .text("你好,我想学习 Spring AI Alibaba") .metadata(Map.of("user_id", "user_123")) @@ -431,13 +488,16 @@ SystemMessage systemMsg = SystemMessage.builder() // AssistantMessage with builder AssistantMessage assistantMsg = AssistantMessage.builder() .content("我很乐意帮助你学习 Spring AI Alibaba!") - .build(); -``` + .build();`} + ### 消息复制和修改 -```java -// 复制消息 + +{`// 复制消息 UserMessage original = new UserMessage("原始消息"); UserMessage copy = original.copy(); @@ -445,15 +505,18 @@ UserMessage copy = original.copy(); UserMessage modified = original.mutate() .text("修改后的消息") .metadata(Map.of("modified", true)) - .build(); -``` + .build();`} + ## 在 ReactAgent 中使用 ReactAgent 自动管理消息历史,但你也可以直接使用消息: -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.messages.AssistantMessage; @@ -475,5 +538,5 @@ List messages = List.of( new UserMessage("我喜欢春天"), new UserMessage("写一首关于春天的诗") ); -AssistantMessage response3 = agent.call(messages); -``` +AssistantMessage response3 = agent.call(messages);`} + diff --git a/docs/frameworks/agent-framework/tutorials/models.md b/docs/frameworks/agent-framework/tutorials/models.md index 4d4504d7..902fa639 100644 --- a/docs/frameworks/agent-framework/tutorials/models.md +++ b/docs/frameworks/agent-framework/tutorials/models.md @@ -22,15 +22,18 @@ ChatModel API 为开发者提供了将 AI 驱动的聊天补全功能集成到 以下是 [ChatModel](https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/model/ChatModel.java) 接口定义: -```java -public interface ChatModel extends Model, StreamingChatModel { + +{`public interface ChatModel extends Model, StreamingChatModel { default String call(String message) {...} @Override ChatResponse call(Prompt prompt); -} -``` +}`} + 带有 `String` 参数的 `call()` 方法简化了初始使用,避免了更复杂的 `Prompt` 和 `ChatResponse` 类的复杂性。在实际应用中,更常见的是使用接受 `Prompt` 实例并返回 `ChatResponse` 的 `call()` 方法。 @@ -38,15 +41,18 @@ public interface ChatModel extends Model, StreamingChatMod 以下是 [StreamingChatModel](https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/model/StreamingChatModel.java) 接口定义: -```java -public interface StreamingChatModel extends StreamingModel { + +{`public interface StreamingChatModel extends StreamingModel { default Flux stream(String message) {...} @Override Flux stream(Prompt prompt); -} -``` +}`} + `stream()` 方法接受 `String` 或 `Prompt` 参数,类似于 `ChatModel`,但使用响应式 Flux API 流式传输响应。 @@ -54,8 +60,11 @@ public interface StreamingChatModel extends StreamingModel [Prompt](https://github.com/spring-projects/spring-ai/blob/main/spring-ai-client-chat/src/main/java/org/springframework/ai/chat/prompt/Prompt.java) 是一个封装了 [Message](https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/messages/Message.java) 对象列表和可选模型请求选项的 `ModelRequest`。以下是 `Prompt` 类的简化版本,排除了构造函数和其他实用方法: -```java -public class Prompt implements ModelRequest> { + +{`public class Prompt implements ModelRequest> { private final List messages; @@ -68,8 +77,8 @@ public class Prompt implements ModelRequest> { public List getInstructions() {...} // 构造函数和实用方法省略 -} -``` +}`} + #### Message @@ -77,8 +86,11 @@ public class Prompt implements ModelRequest> { 接口定义如下: -```java -public interface Content { + +{`public interface Content { String getText(); @@ -88,17 +100,20 @@ public interface Content { public interface Message extends Content { MessageType getMessageType(); -} -``` +}`} + 多模态消息类型还实现了 `MediaContent` 接口,提供 `Media` 内容对象列表。 -```java -public interface MediaContent extends Content { + +{`public interface MediaContent extends Content { Collection getMedia(); -} -``` +}`} + `Message` 接口有多种实现,对应于 AI 模型可以处理的消息类别: @@ -120,8 +135,11 @@ public interface MediaContent extends Content { 表示可以传递给 AI 模型的选项。`ChatOptions` 类是 `ModelOptions` 的子类,用于定义可以传递给 AI 模型的少数可移植选项。`ChatOptions` 类定义如下: -```java -public interface ChatOptions extends ModelOptions { + +{`public interface ChatOptions extends ModelOptions { String getModel(); Float getFrequencyPenalty(); @@ -132,8 +150,8 @@ public interface ChatOptions extends ModelOptions { Integer getTopK(); Float getTopP(); ChatOptions copy(); -} -``` +}`} + **常用选项说明**: @@ -170,8 +188,11 @@ Spring AI 提供了一个复杂的系统来配置和使用 ChatModels。它允 `ChatResponse` 类的结构如下: -```java -public class ChatResponse implements ModelResponse { + +{`public class ChatResponse implements ModelResponse { private final ChatResponseMetadata chatResponseMetadata; private final List generations; @@ -183,8 +204,8 @@ public class ChatResponse implements ModelResponse { public List getResults() {...} // 其他方法省略 -} -``` +}`} + [ChatResponse](https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/model/ChatResponse.java) 类保存 AI 模型的输出,每个 `Generation` 实例包含单个提示可能产生的多个输出之一。 @@ -194,8 +215,11 @@ public class ChatResponse implements ModelResponse { 最后,[Generation](https://github.com/spring-projects/spring-ai/blob/main/spring-ai-model/src/main/java/org/springframework/ai/chat/model/Generation.java) 类从 `ModelResult` 扩展,表示模型输出(助手消息)和相关元数据: -```java -public class Generation implements ModelResult { + +{`public class Generation implements ModelResult { private final AssistantMessage assistantMessage; private ChatGenerationMetadata chatGenerationMetadata; @@ -207,8 +231,8 @@ public class Generation implements ModelResult { public ChatGenerationMetadata getMetadata() {...} // 其他方法省略 -} -``` +}`} + ## 可用实现 @@ -252,8 +276,11 @@ DashScope 是阿里云提供的大模型服务平台,提供通义千问等多 #### 创建 ChatModel -```java -import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; + +{`import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import org.springframework.ai.chat.model.ChatModel; @@ -265,21 +292,27 @@ DashScopeApi dashScopeApi = DashScopeApi.builder() // 创建 ChatModel ChatModel chatModel = DashScopeChatModel.builder() .dashScopeApi(dashScopeApi) - .build(); -``` + .build();`} + #### 简单调用 -```java -// 使用字符串直接调用 + +{`// 使用字符串直接调用 String response = chatModel.call("介绍一下Spring框架"); -System.out.println(response); -``` +System.out.println(response);`} + #### 使用 Prompt -```java -import org.springframework.ai.chat.prompt.Prompt; + +{`import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; @@ -289,15 +322,18 @@ Prompt prompt = new Prompt(new UserMessage("解释什么是微服务架构")); // 调用并获取响应 ChatResponse response = chatModel.call(prompt); String answer = response.getResult().getOutput().getText(); -System.out.println(answer); -``` +System.out.println(answer);`} + ### 配置选项 #### 使用 ChatOptions -```java -import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; + +{`import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; DashScopeChatOptions options = DashScopeChatOptions.builder() .withModel("qwen-plus") // 模型名称 @@ -309,13 +345,16 @@ DashScopeChatOptions options = DashScopeChatOptions.builder() ChatModel chatModel = DashScopeChatModel.builder() .dashScopeApi(dashScopeApi) .defaultOptions(options) - .build(); -``` + .build();`} + #### 运行时覆盖选项 -```java -// 创建带有特定选项的 Prompt + +{`// 创建带有特定选项的 Prompt DashScopeChatOptions runtimeOptions = DashScopeChatOptions.builder() .withTemperature(0.3) // 更低的温度,更确定的输出 .withMaxToken(500) @@ -326,13 +365,16 @@ Prompt prompt = new Prompt( runtimeOptions ); -ChatResponse response = chatModel.call(prompt); -``` +ChatResponse response = chatModel.call(prompt);`} + ### 流式响应 -```java -import reactor.core.publisher.Flux; + +{`import reactor.core.publisher.Flux; // 使用流式 API Flux responseStream = chatModel.stream( @@ -349,13 +391,16 @@ responseStream.subscribe( }, error -> System.err.println("错误: " + error.getMessage()), () -> System.out.println("\n流式响应完成") -); -``` +);`} + ### 多轮对话 -```java -import org.springframework.ai.chat.messages.Message; + +{`import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.SystemMessage; import org.springframework.ai.chat.messages.AssistantMessage; import java.util.List; @@ -369,8 +414,8 @@ List messages = List.of( ); Prompt prompt = new Prompt(messages); -ChatResponse response = chatModel.call(prompt); -``` +ChatResponse response = chatModel.call(prompt);`} + ### 支持的模型 @@ -385,8 +430,11 @@ DashScope 支持多个模型,包括: DashScopeChatModel 支持函数调用(Function Calling),允许模型调用外部函数: -```java -import org.springframework.ai.chat.prompt.Prompt; + +{`import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.function.FunctionToolCallback; @@ -405,15 +453,18 @@ DashScopeChatOptions options = DashScopeChatOptions.builder() .build(); Prompt prompt = new Prompt("北京的天气怎么样?", options); -ChatResponse response = chatModel.call(prompt); -``` +ChatResponse response = chatModel.call(prompt);`} + ## 与 ReactAgent 集成 在 Spring AI Alibaba Agent Framework 中使用 DashScopeChatModel: -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; ReactAgent agent = ReactAgent.builder() .name("my_agent") @@ -422,8 +473,8 @@ ReactAgent agent = ReactAgent.builder() .build(); // 调用 Agent -AssistantMessage response = agent.call("帮我分析这个问题"); -``` +AssistantMessage response = agent.call("帮我分析这个问题");`} + 详细的 Agent 使用方法请参考 [Agents 文档](./agents.md)。 diff --git a/docs/frameworks/agent-framework/tutorials/structured-output.md b/docs/frameworks/agent-framework/tutorials/structured-output.md index c517acf5..8dffbfd9 100644 --- a/docs/frameworks/agent-framework/tutorials/structured-output.md +++ b/docs/frameworks/agent-framework/tutorials/structured-output.md @@ -10,15 +10,18 @@ keywords: [Structured Output, 结构化输出, JSON Schema, POJO, outputSchema, Spring AI Alibaba 的 `ReactAgent.Builder` 通过 `outputSchema` 和 `outputType` 方法处理结构化输出。当您设置所需的结构化输出模式时,Agent 会自动在用户消息中增加模式指令,模型会根据指定的格式生成数据。 -```java -ReactAgent agent = ReactAgent.builder() + +{`ReactAgent agent = ReactAgent.builder() .name("agent") .model(chatModel) .outputSchema(schemaString) // Custom JSON schema as String // OR .outputType(MyClass.class) // Java class - auto-converted to schema - .build(); -``` + .build();`} + ## 输出格式选项 @@ -36,9 +39,12 @@ Spring AI Alibaba 支持两种方式控制结构化输出: ### 方法签名 -```java -Builder outputSchema(String outputSchema) -``` + +{`Builder outputSchema(String outputSchema)`} + **参数:** - `outputSchema` (String, 必需): 定义结构化输出格式的 JSON schema 字符串。Schema 应包含字段名称、类型、描述和要求,以指导模型。 @@ -47,8 +53,11 @@ Builder outputSchema(String outputSchema) **基本 JSON Schema:** -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import org.springframework.ai.chat.messages.AssistantMessage; String contactInfoSchema = """ @@ -71,13 +80,16 @@ AssistantMessage result = agent.call( ); System.out.println(result.getText()); -// 输出: {"name": "张三", "email": "zhangsan@example.com", "phone": "(555) 123-4567"} -``` +// 输出: {"name": "张三", "email": "zhangsan@example.com", "phone": "(555) 123-4567"}`} + **复杂嵌套 Schema:** -```java -String productReviewSchema = """ + +{`String productReviewSchema = """ 请严格按照以下JSON格式返回产品评价分析: { "rating": 1-5之间的整数评分, @@ -101,13 +113,16 @@ AssistantMessage result = agent.call( ); System.out.println(result.getText()); -// 输出: {"rating": 5, "sentiment": "正面", "keyPoints": [...], "details": {...}} -``` +// 输出: {"rating": 5, "sentiment": "正面", "keyPoints": [...], "details": {...}}`} + **结构化分析 Schema:** -```java -String analysisSchema = """ + +{`String analysisSchema = """ 请按照以下JSON格式返回文本分析结果: { "summary": "内容摘要(50字以内)", @@ -131,8 +146,8 @@ AssistantMessage result = agent.call( "分析这段文字:昨天,李明在北京参加了阿里巴巴公司的技术大会,感受到了创新的力量。" ); -System.out.println(result.getText()); -``` +System.out.println(result.getText());`} + `outputSchema` 方法提供了最大的灵活性,您可以定义任何 JSON 结构,并提供详细的中文或英文指令来指导模型的输出格式。 @@ -142,9 +157,12 @@ System.out.println(result.getText()); ### 方法签名 -```java -Builder outputType(Class outputType) -``` + +{`Builder outputType(Class outputType)`} + **参数:** - `outputType` (`Class`, 必需): 定义输出结构的 Java 类。该类应该是带有标准 getter 和 setter 的 POJO。 @@ -163,19 +181,25 @@ Builder outputType(Class outputType) 比如,针对 DashScopeChatModel 模型,在配置 outputSchema 或 outputType 后,Spring AI Alibaba 会自动设置如下参数,以启用模型原生结构化输出能力。 -```java -ChatOptions options = DashScopeChatOptions.builder() + +{`ChatOptions options = DashScopeChatOptions.builder() .withResponseFormat( DashScopeResponseFormat.builder() .type(DashScopeResponseFormat.Type.JSON_OBJECT) .build()) - .build(); -``` + .build();`} + 同时,Spring AI Alibaba 框架会增强系统 Prompt,引导模型输出格式化内容 -```java -// In AgentLlmNode.augmentUserMessage() method + +{`// In AgentLlmNode.augmentUserMessage() method public void augmentUserMessage(List messages, String outputSchema) { if (!StringUtils.hasText(outputSchema)) { return; @@ -190,8 +214,8 @@ public void augmentUserMessage(List messages, String outputSchema) { break; } } -} -``` +}`} + > 注意,相比于 DashScope 模型是通过增强 Prompt 提示词实现最终的 JSON 格式,实现的是一个尽最大努力的效果,OpenAI 模型则是在模型 API 层面支持 Json 格式,提供格式的严格保证支持。 @@ -205,8 +229,11 @@ public void augmentUserMessage(List messages, String outputSchema) { ### Try-Catch 模式 -```java -ReactAgent agent = ReactAgent.builder() + +{`ReactAgent agent = ReactAgent.builder() .name("data_extractor") .model(chatModel) .outputType(DataOutput.class) @@ -221,13 +248,16 @@ try { System.err.println("JSON解析失败: " + e.getMessage()); System.err.println("原始输出: " + result.getText()); // 回退处理 -} -``` +}`} + ### 验证模式 -```java -public class ValidatedOutput { + +{`public class ValidatedOutput { private String title; private Integer rating; @@ -245,13 +275,16 @@ public class ValidatedOutput { AssistantMessage result = agent.call("生成评价"); ValidatedOutput output = mapper.readValue(result.getText(), ValidatedOutput.class); -output.validate(); // 如果无效则抛出异常 -``` +output.validate(); // 如果无效则抛出异常`} + ### 重试模式 -```java -int maxRetries = 3; + +{`int maxRetries = 3; DataOutput data = null; for (int i = 0; i < maxRetries; i++) { @@ -265,8 +298,8 @@ for (int i = 0; i < maxRetries; i++) { } System.out.println("第" + (i + 1) + "次尝试失败,重试中..."); } -} -``` +}`} + Spring AI Alibaba 专注于简单性和灵活性,允许开发者在显式 schema 字符串(最大控制)和 Java 类(类型安全)之间进行选择。 diff --git a/docs/frameworks/agent-framework/tutorials/tools.md b/docs/frameworks/agent-framework/tutorials/tools.md index fb2bf784..703ea49a 100644 --- a/docs/frameworks/agent-framework/tutorials/tools.md +++ b/docs/frameworks/agent-framework/tutorials/tools.md @@ -24,8 +24,11 @@ Spring AI 提供了对从函数指定工具的内置支持,可以通过编程 你可以通过编程方式构建 `FunctionToolCallback`,将函数类型(`Function`、`Supplier`、`Consumer` 或 `BiFunction`)转换为工具。 -```java -import java.util.function.Function; + +{`import java.util.function.Function; public class WeatherService implements Function { public WeatherResponse apply(WeatherRequest request) { @@ -35,8 +38,8 @@ public class WeatherService implements Function public enum Unit { C, F } public record WeatherRequest(String location, Unit unit) {} -public record WeatherResponse(double temp, Unit unit) {} -``` +public record WeatherResponse(double temp, Unit unit) {}`} + `FunctionToolCallback.Builder` 允许你构建 `FunctionToolCallback` 实例并提供有关工具的关键信息: @@ -48,16 +51,19 @@ public record WeatherResponse(double temp, Unit unit) {} * **toolMetadata**: 定义附加设置的 `ToolMetadata` 实例,例如是否应将结果直接返回给客户端,以及要使用的结果转换器。你可以使用 `ToolMetadata.Builder` 类构建它。 * **toolCallResultConverter**: 用于将工具调用结果转换为字符串对象以发送回 AI 模型的 `ToolCallResultConverter` 实例。如果未提供,将使用默认转换器(`DefaultToolCallResultConverter`)。 -```java -import org.springframework.ai.tool.ToolCallback; + +{`import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.function.FunctionToolCallback; ToolCallback toolCallback = FunctionToolCallback .builder("currentWeather", new WeatherService()) .description("Get the weather in location") .inputType(WeatherRequest.class) - .build(); -``` + .build();`} + 函数的输入和输出可以是 `Void` 或 POJO。输入和输出 POJO 必须是可序列化的,因为结果将被序列化并发送回模型。函数以及输入和输出类型必须是公共的。 @@ -67,8 +73,11 @@ ToolCallback toolCallback = FunctionToolCallback 使用编程规范方法时,你可以将 `FunctionToolCallback` 实例传递给 `ChatClient` 的 `toolCallbacks()` 方法。该工具仅对添加到的特定聊天请求可用。 -```java -import org.springframework.ai.chat.client.ChatClient; + +{`import org.springframework.ai.chat.client.ChatClient; ToolCallback toolCallback = ...; @@ -76,21 +85,24 @@ ChatClient.create(chatModel) .prompt("What's the weather like in Copenhagen?") .toolCallbacks(toolCallback) .call() - .content(); -``` + .content();`} + **添加默认工具到 ChatClient**: 使用编程规范方法时,你可以通过将 `FunctionToolCallback` 实例传递给 `defaultToolCallbacks()` 方法将默认工具添加到 `ChatClient.Builder`。如果同时提供默认工具和运行时工具,运行时工具将完全覆盖默认工具。 -```java -ChatModel chatModel = ...; + +{`ChatModel chatModel = ...; ToolCallback toolCallback = ...; ChatClient chatClient = ChatClient.builder(chatModel) .defaultToolCallbacks(toolCallback) - .build(); -``` + .build();`} + **注意**:默认工具在从同一 `ChatClient.Builder` 构建的所有 `ChatClient` 实例执行的所有聊天请求中共享。它们对于跨不同聊天请求常用的工具很有用,但如果不小心使用也可能很危险,可能在不应该使用时使它们可用。 @@ -98,8 +110,11 @@ ChatClient chatClient = ChatClient.builder(chatModel) 使用编程规范方法时,你可以将 `FunctionToolCallback` 实例传递给 `ToolCallingChatOptions` 的 `toolCallbacks()` 方法。该工具仅对添加到的特定聊天请求可用。 -```java -import org.springframework.ai.chat.model.ChatModel; + +{`import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.tool.ToolCallingChatOptions; @@ -111,15 +126,18 @@ ChatOptions chatOptions = ToolCallingChatOptions.builder() .build(); Prompt prompt = new Prompt("What's the weather like in Copenhagen?", chatOptions); -chatModel.call(prompt); -``` +chatModel.call(prompt);`} + #### 动态规范:@Bean 你可以将工具定义为 Spring beans,让 Spring AI 使用 `ToolCallbackResolver` 接口(通过 `SpringBeanToolCallbackResolver` 实现)在运行时动态解析它们,而不是以编程方式指定工具。此选项使你可以将任何 `Function`、`Supplier`、`Consumer` 或 `BiFunction` bean 用作工具。bean 名称将用作工具名称,Spring Framework 的 `@Description` 注解可用于为工具提供描述,供模型用来了解何时以及如何调用工具。 -```java -import org.springframework.context.annotation.Bean; + +{`import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Description; import java.util.function.Function; @@ -134,26 +152,32 @@ class WeatherTools { Function currentWeather() { return weatherService; } -} -``` +}`} + **重要提示**:某些类型不受支持。有关更多详细信息,请参阅函数工具限制。 工具输入参数的 JSON schema 将自动生成。你可以使用 `@ToolParam` 注解提供有关输入参数的附加信息,例如描述或参数是必需还是可选。默认情况下,所有输入参数都被视为必需。 -```java -import org.springframework.ai.tool.annotation.ToolParam; + +{`import org.springframework.ai.tool.annotation.ToolParam; record WeatherRequest( @ToolParam(description = "The name of a city or a country") String location, Unit unit -) {} -``` +) {}`} + 此工具规范方法的缺点是不保证类型安全,因为工具解析是在运行时完成的。为了缓解这一问题,你可以使用 `@Bean` 注解显式指定工具名称并将值存储在常量中,以便你可以在聊天请求中使用它而不是硬编码工具名称。 -```java -@Configuration(proxyBeanMethods = false) + +{`@Configuration(proxyBeanMethods = false) class WeatherTools { public static final String CURRENT_WEATHER_TOOL = "currentWeather"; @@ -163,28 +187,34 @@ class WeatherTools { Function currentWeather() { // ... } -} -``` +}`} + **添加工具到 ChatClient**(使用动态规范): -```java -ChatClient.create(chatModel) + +{`ChatClient.create(chatModel) .prompt("What's the weather like in Copenhagen?") .toolNames("currentWeather") .call() - .content(); -``` + .content();`} + **添加默认工具到 ChatClient**(使用动态规范): -```java -ChatModel chatModel = ...; + +{`ChatModel chatModel = ...; ChatClient chatClient = ChatClient.builder(chatModel) .defaultToolNames("currentWeather") - .build(); -``` + .build();`} + #### 函数工具限制 @@ -204,8 +234,11 @@ ChatClient chatClient = ChatClient.builder(chatModel) 默认情况下,工具名称来自函数名称。当你需要更具描述性的内容时,可以覆盖它: -```java -ToolCallback searchTool = FunctionToolCallback + +{`ToolCallback searchTool = FunctionToolCallback .builder("web_search", new SearchFunction()) // 自定义名称 .description("Search the web for information") .inputType(String.class) @@ -213,20 +246,23 @@ ToolCallback searchTool = FunctionToolCallback System.out.println(searchTool.getName()); // web_search // 推荐:使用 ToolDefinition 提取名称 -System.out.println(searchTool.getToolDefinition().name()); // web_search -``` +System.out.println(searchTool.getToolDefinition().name()); // web_search`} + #### 自定义工具描述 覆盖自动生成的工具描述以提供更清晰的模型指导: -```java -ToolCallback calculatorTool = FunctionToolCallback + +{`ToolCallback calculatorTool = FunctionToolCallback .builder("calculator", new CalculatorFunction()) .description("Performs arithmetic calculations. Use this for any math problems.") .inputType(String.class) - .build(); -``` + .build();`} + ### 高级模式定义 @@ -234,8 +270,11 @@ ToolCallback calculatorTool = FunctionToolCallback **使用 Java 记录类(Record)**: -```java -import org.springframework.ai.tool.annotation.ToolParam; + +{`import org.springframework.ai.tool.annotation.ToolParam; public record WeatherInput( @ToolParam(description = "City name or coordinates") String location, @@ -268,8 +307,8 @@ ToolCallback weatherTool = FunctionToolCallback .builder("get_weather", new WeatherFunction()) .description("Get current weather and optional forecast") .inputType(WeatherInput.class) - .build(); -``` + .build();`} + ## 访问上下文 @@ -291,8 +330,11 @@ ToolCallback weatherTool = FunctionToolCallback 工具可以使用 `ToolContext` 访问当前的 Graph 状态: -```java -import org.springframework.ai.chat.model.ToolContext; + +{`import org.springframework.ai.chat.model.ToolContext; import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.function.FunctionToolCallback; import org.springframework.ai.chat.messages.Message; @@ -307,7 +349,7 @@ public class ConversationSummaryTool implements BiFunction extraState = (Map) toolContext.getContext().get("extraState"); // 从state中获取消息 @@ -339,8 +381,8 @@ ToolCallback summaryTool = FunctionToolCallback .builder("summarize_conversation", new ConversationSummaryTool()) .description("Summarize the conversation so far") .inputType(String.class) - .build(); -``` + .build();`} + **警告**:`toolContext` 参数对模型是隐藏的。对于上面的示例,模型只看到 `input` 在工具模式中 - `toolContext` **不**包含在请求中。 @@ -348,8 +390,11 @@ ToolCallback summaryTool = FunctionToolCallback 在 Spring AI Alibaba 中,你可以通过 Hook 或在工具执行后返回的信息来更新 Agent 的状态。 -```java -// 在 Hook 中更新状态 + +{`// 在 Hook 中更新状态 import com.alibaba.cloud.ai.graph.agent.hook.ModelHook; import com.alibaba.cloud.ai.graph.agent.hook.HookPosition; import com.alibaba.cloud.ai.graph.OverAllState; @@ -376,15 +421,18 @@ public class UpdateStateHook extends ModelHook { "last_updated", System.currentTimeMillis() )); } -} -``` +}`} + ### Context(上下文) 通过 `ToolContext` 访问不可变配置和上下文数据,如用户 ID、会话详细信息或应用程序特定配置。 -```java -import org.springframework.ai.chat.model.ToolContext; + +{`import org.springframework.ai.chat.model.ToolContext; import java.util.function.BiFunction; import java.util.Map; @@ -448,15 +496,18 @@ ReactAgent agent = ReactAgent.builder() // 调用时传递上下文 // 注意:需要通过适当的方式传递上下文数据 RunnableConfig config = RunnableConfig.builder().addMetadata("user_id", "1"); -agent.call("question", config); -``` +agent.call("question", config);`} + ### Memory(存储) 使用存储访问跨对话的持久数据。在 Spring AI Alibaba 中,你可以使用 checkpointer 来实现长期记忆。 -```java -import com.alibaba.cloud.ai.graph.checkpoint.savers.RedisSaver; + +{`import com.alibaba.cloud.ai.graph.checkpoint.savers.RedisSaver; // 配置持久化存储 RedisSaver redisSaver = new RedisSaver(redissonClient); @@ -485,8 +536,8 @@ agent.call("Get user info for user with id 'abc123'", config2); // 输出:Here is the user info for user with ID "abc123": // - Name: Foo // - Age: 25 -// - Email: foo@example.com -``` +// - Email: foo@example.com`} + ## 内置工具 @@ -495,8 +546,11 @@ agent.call("Get user info for user with id 'abc123'", config2); 在 ReactAgent 中使用工具非常简单: -```java -import com.alibaba.cloud.ai.graph.agent.ReactAgent; + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.function.FunctionToolCallback; @@ -524,8 +578,8 @@ ReactAgent agent = ReactAgent.builder() // 使用 Agent AssistantMessage response = agent.call("What's the weather like in San Francisco?"); -System.out.println(response.getText()); -``` +System.out.println(response.getText());`} + ## 相关资源 diff --git a/docs/quick-start.md b/docs/quick-start.md index 5608875a..b8b544fa 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -6,7 +6,7 @@ keywords: [ReactAgent, 快速开始, Quick Start, Spring AI Alibaba, Agent Frame # ReactAgent 快速开始 -跟随快速开始,学习如果开发一个具备完整功能的 ReactAgent 智能体。 +跟随快速开始,学习如何开发一个具备完整功能的 ReactAgent 智能体。 ## 前置条件 diff --git a/sidebars/sidebars-agent-framework.ts b/sidebars/sidebars-agent-framework.ts index 9b957bb4..c8fa26b6 100644 --- a/sidebars/sidebars-agent-framework.ts +++ b/sidebars/sidebars-agent-framework.ts @@ -13,25 +13,25 @@ const sidebars: SidebarsConfig = { items: [ 'tutorials/agents', 'tutorials/hooks', + 'tutorials/tools', 'tutorials/memory', 'tutorials/messages', 'tutorials/models', 'tutorials/structured-output', - 'tutorials/tools', ], }, { type: 'category', label: '高级功能', items: [ - 'advanced/a2a', - 'advanced/agent-tool', 'advanced/context-engineering', 'advanced/human-in-the-loop', 'advanced/memory', 'advanced/multi-agent', - 'advanced/rag', + 'advanced/agent-tool', 'advanced/workflow', + 'advanced/rag', + 'advanced/a2a', ], }, ], diff --git a/sidebars/sidebars-graph-core.ts b/sidebars/sidebars-graph-core.ts index 6ffdf6a4..8cea8784 100644 --- a/sidebars/sidebars-graph-core.ts +++ b/sidebars/sidebars-graph-core.ts @@ -11,17 +11,17 @@ const sidebars: SidebarsConfig = { type: 'category', label: '核心功能', items: [ - 'core/cancellation', - 'core/checkpoint-postgres', 'core/core-library', + 'core/streaming', 'core/human-in-the-loop', - 'core/long-time-running-task', 'core/mcp-node', 'core/memory', - 'core/parallel-branch', + 'core/checkpoint-postgres', + 'core/long-time-running-task', 'core/persistence', - 'core/streaming', + 'core/parallel-branch', 'core/subgraph', + 'core/cancellation', ], }, { From fc1c682fed0b733a45633c1813779b9e3b9a41d3 Mon Sep 17 00:00:00 2001 From: "ken.lj" Date: Mon, 17 Nov 2025 10:42:19 +0800 Subject: [PATCH 4/6] update to M5 Change-Id: If4e931dce9a1dfdc6dfefe41ccf22a836b738de4 --- docs/frameworks/agent-framework/tutorials/models.md | 2 +- docs/frameworks/studio/quick-start.md | 2 +- docs/overview.md | 4 ++-- docs/quick-start.md | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/frameworks/agent-framework/tutorials/models.md b/docs/frameworks/agent-framework/tutorials/models.md index 902fa639..a174eb1a 100644 --- a/docs/frameworks/agent-framework/tutorials/models.md +++ b/docs/frameworks/agent-framework/tutorials/models.md @@ -268,7 +268,7 @@ DashScope 是阿里云提供的大模型服务平台,提供通义千问等多 com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope - 1.1.0.0-M4 + 1.1.0.0-M5 ``` diff --git a/docs/frameworks/studio/quick-start.md b/docs/frameworks/studio/quick-start.md index 742acfb3..e40141f6 100644 --- a/docs/frameworks/studio/quick-start.md +++ b/docs/frameworks/studio/quick-start.md @@ -45,7 +45,7 @@ Just add the following dependency to your agent project: com.alibaba.cloud.ai spring-ai-alibaba-studio - 1.1.0.0-M4 + 1.1.0.0-M5 ``` diff --git a/docs/overview.md b/docs/overview.md index c8bbe7f7..9afba379 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -29,13 +29,13 @@ Spring AI Alibaba 项目从架构上包含如下三层: com.alibaba.cloud.ai spring-ai-alibaba-agent-framework - 1.1.0.0-M4 + 1.1.0.0-M5 com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope - 1.1.0.0-M4 + 1.1.0.0-M5 ``` diff --git a/docs/quick-start.md b/docs/quick-start.md index b8b544fa..43d926de 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -26,14 +26,14 @@ keywords: [ReactAgent, 快速开始, Quick Start, Spring AI Alibaba, Agent Frame com.alibaba.cloud.ai spring-ai-alibaba-agent-framework - 1.1.0.0-M4 + 1.1.0.0-M5 com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope - 1.1.0.0-M4 + 1.1.0.0-M5 ``` @@ -216,7 +216,7 @@ ChatModel chatModel = DashScopeChatModel.builder() com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope - 1.1.0.0-M4 + 1.1.0.0-M5 From 0a9b0704b30d85c3ee84abc0d841798a2ee61283 Mon Sep 17 00:00:00 2001 From: "ken.lj" Date: Wed, 7 Jan 2026 13:58:42 +0800 Subject: [PATCH 5/6] support news banner Change-Id: I47c73efb88b1c0df816cc423a23c22584345bf26 --- src/components/AnnouncementBar/index.tsx | 80 ++++++++++++++ .../AnnouncementBar/styles.module.css | 100 ++++++++++++++++++ src/theme/Root.tsx | 22 +++- 3 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 src/components/AnnouncementBar/index.tsx create mode 100644 src/components/AnnouncementBar/styles.module.css diff --git a/src/components/AnnouncementBar/index.tsx b/src/components/AnnouncementBar/index.tsx new file mode 100644 index 00000000..c379b751 --- /dev/null +++ b/src/components/AnnouncementBar/index.tsx @@ -0,0 +1,80 @@ +import React, { useState } from 'react' +import styles from './styles.module.css' +import clsx from 'clsx' + +interface AnnouncementBarProps { + /** + * 通知内容,支持 ReactNode 或 HTML 字符串 + */ + content?: React.ReactNode | string + /** + * 是否可关闭 + */ + closable?: boolean + /** + * 关闭后的回调 + */ + onClose?: () => void +} + +export default function AnnouncementBar({ + content = '🎉 欢迎阅读下载《AI 原生应用架构白皮书》,40位一线工程师编写、15位行业专家力荐!', + closable = true, + onClose, +}: AnnouncementBarProps) { + const [isVisible, setIsVisible] = useState(true) + + const handleClose = () => { + setIsVisible(false) + onClose?.() + } + + if (!isVisible) { + return null + } + + // 判断 content 是否是包含 HTML 标签的字符串 + const isHtmlString = typeof content === 'string' && /<[^>]+>/.test(content) + + return ( +
+
+ {isHtmlString ? ( +
+ ) : ( +
+ {content} +
+ )} + {closable && ( + + )} +
+
+ ) +} + diff --git a/src/components/AnnouncementBar/styles.module.css b/src/components/AnnouncementBar/styles.module.css new file mode 100644 index 00000000..1336df4c --- /dev/null +++ b/src/components/AnnouncementBar/styles.module.css @@ -0,0 +1,100 @@ +.announcementBar { + position: sticky; + top: 0; + z-index: 101; + background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 2px 8px rgba(37, 99, 235, 0.2); + transition: all 0.3s ease; +} + +[data-theme='dark'] .announcementBar { + background: linear-gradient(135deg, #1e40af 0%, #2563eb 100%); + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); +} + +.container { + display: flex; + align-items: center; + justify-content: center; + padding: 0.75rem 1rem; + gap: 1rem; +} + +.content { + flex: 1; + text-align: center; + color: white; + font-size: 0.9rem; + font-weight: 500; + line-height: 1.5; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} + +.content a { + color: white; + text-decoration: underline; + text-underline-offset: 2px; + transition: opacity 0.2s ease; +} + +.content a:hover { + opacity: 0.9; +} + +.closeButton { + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + padding: 0; + background: rgba(255, 255, 255, 0.1); + border: none; + border-radius: 4px; + color: white; + cursor: pointer; + transition: all 0.2s ease; + flex-shrink: 0; +} + +.closeButton:hover { + background: rgba(255, 255, 255, 0.2); + transform: scale(1.1); +} + +.closeButton:active { + transform: scale(0.95); +} + +.closeButton svg { + width: 14px; + height: 14px; +} + +/* 响应式设计 */ +@media screen and (max-width: 768px) { + .container { + padding: 0.625rem 0.75rem; + } + + .content { + font-size: 0.85rem; + line-height: 1.4; + } + + .closeButton { + width: 20px; + height: 20px; + } + + .closeButton svg { + width: 12px; + height: 12px; + } +} + diff --git a/src/theme/Root.tsx b/src/theme/Root.tsx index 7ced77b1..6daf2ac9 100644 --- a/src/theme/Root.tsx +++ b/src/theme/Root.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react' import PineconeFloatingSearch from '../components/PineconeFloatingSearch' import BackToTop from '../components/BackToTop' +import AnnouncementBar from '../components/AnnouncementBar' interface RootProps { children: React.ReactNode; @@ -8,9 +9,10 @@ interface RootProps { export default function Root({ children }: RootProps) { const [isDocPage, setIsDocPage] = useState(false) + const [isHomePage, setIsHomePage] = useState(false) useEffect(() => { - // Check if we're on a documentation page + // Check if we're on a documentation page or homepage const checkPath = () => { const path = window.location.pathname const isDoc = path.startsWith('/docs') || @@ -18,8 +20,23 @@ export default function Root({ children }: RootProps) { path.startsWith('/blog') || path.startsWith('/en/blog') || path.startsWith('/community') || - path.startsWith('/en/community') + path.startsWith('/en/community') || + path.startsWith('/agents') || + path.startsWith('/en/agents') || + path.startsWith('/integration') || + path.startsWith('/en/integration') || + path.startsWith('/ecosystem') || + path.startsWith('/en/ecosystem') + + // Check if we're on homepage + // Homepage paths: /, /zh-Hans/, /en/ + const normalizedPath = path.replace(/\/$/, '') || '/' + const isHome = normalizedPath === '/' || + normalizedPath === '/zh-Hans' || + normalizedPath === '/en' + setIsDocPage(isDoc) + setIsHomePage(isHome) } checkPath() @@ -273,6 +290,7 @@ export default function Root({ children }: RootProps) { return ( <> + {isHomePage && } {children} From d29bd56f1073c3cf32d2804ed71b2cb18cdffe04 Mon Sep 17 00:00:00 2001 From: "ken.lj" Date: Mon, 2 Feb 2026 23:46:59 +0800 Subject: [PATCH 6/6] add 1.1.2.0 docs Change-Id: Iea6a4e27ba3f5cb07d955ee26d8d439adce2bbb2 --- .../agent-framework/tutorials/models.md | 2 +- .../agent-framework/tutorials/skills.md | 279 ++++++++++++++++++ docs/frameworks/studio/quick-start.md | 2 +- docs/overview.md | 6 +- docs/quick-start.md | 8 +- docs/versions.md | 133 +++++++++ sidebars.ts | 2 + src/components/AnnouncementBar/index.tsx | 2 +- 8 files changed, 423 insertions(+), 11 deletions(-) create mode 100644 docs/frameworks/agent-framework/tutorials/skills.md create mode 100644 docs/versions.md diff --git a/docs/frameworks/agent-framework/tutorials/models.md b/docs/frameworks/agent-framework/tutorials/models.md index e0210e98..46b085f7 100644 --- a/docs/frameworks/agent-framework/tutorials/models.md +++ b/docs/frameworks/agent-framework/tutorials/models.md @@ -427,7 +427,7 @@ DashScope 是阿里云提供的大模型服务平台,提供通义千问等多 com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope - 1.1.0.0-RC2 + 1.1.2.1 ``` diff --git a/docs/frameworks/agent-framework/tutorials/skills.md b/docs/frameworks/agent-framework/tutorials/skills.md new file mode 100644 index 00000000..96fd386c --- /dev/null +++ b/docs/frameworks/agent-framework/tutorials/skills.md @@ -0,0 +1,279 @@ +--- +title: Skills 技能 +description: 在 Spring AI Alibaba 中使用 Agent Skills 实现技能的渐进式披露,扩展智能体能力。 +keywords: [Skills, Agent Skills, 技能, 渐进式披露, SkillRegistry, read_skill, SkillsAgentHook] +--- + +# Skills 技能 + +Skills 是可复用的指令与上下文包,智能体在相关任务时会自动发现并使用。通过 **SkillRegistry** 管理技能、**SkillsAgentHook** 注册 `read_skill` 工具并注入技能列表到系统提示,模型在需要时调用 `read_skill(skill_name)` 按需加载完整内容。 + +## 核心概念 + +### 渐进式披露 + +系统提示中先只注入技能列表(name、description、skillPath);模型判断需要某技能时调用 `read_skill(skill_name)` 加载完整 SKILL.md;再按需访问技能目录下的资源或使用与该技能绑定的工具。 + +### Skill 目录结构 + +每个技能一个子目录,必须包含 `SKILL.md`: + +```text +skill-name/ +├── SKILL.md # 必需 +├── references/ # 可选 +├── examples/ +└── scripts/ +``` + +### SKILL.md 格式规范 + +```yaml +--- +name: skill-name +description: This skill should be used when... +--- + +# 技能名称 +正文:功能说明、使用方法、可用资源列表等。 +``` + +**必需字段**:`name`(建议小写字母、数字、连字符,最长 64 字符)、`description`(超长会被截断)。 + +--- + +## 在 Agent 中使用 Skills + +### 使用 FileSystemSkillRegistry + +智能体支持从本地文件系统中加载 skills 技能,以下示例假设 `skills` 在进程工作目录,如: + +```text +skills/ +├── pdf-extractor/ + ├── SKILL.md + ├── references/ + └── scripts/ +``` + + +{`SkillRegistry registry = FileSystemSkillRegistry.builder() + .projectSkillsDirectory(System.getProperty("user.dir") + "/skills") + .build(); + +SkillsAgentHook hook = SkillsAgentHook.builder() + .skillRegistry(registry) + .build(); + +ReactAgent agent = ReactAgent.builder() + .name("skills-agent") + .model(chatModel) + .saver(new MemorySaver()) + .hooks(List.of(hook)) + .build(); + +agent.call("请介绍你有哪些技能");`} + + +目录配置:`userSkillsDirectory(String|Resource)`、`projectSkillsDirectory(String|Resource)`;不设置时用户级默认 `~/saa/skills`,项目级默认 `./skills`,同名技能“项目级别”覆盖“用户级别”。 + +### 使用 ClasspathSkillRegistry + +技能放在 `src/main/resources/skills` 或随 JAR 打包。可选 `.basePath("/tmp")` 指定 JAR 内资源复制到的目录(默认 `/tmp`)。 + + +{`SkillRegistry registry = ClasspathSkillRegistry.builder() + .classpathPath("skills") + .build(); + +SkillsAgentHook hook = SkillsAgentHook.builder() + .skillRegistry(registry) + .build(); + +ReactAgent agent = ReactAgent.builder() + .name("skills-agent") + .model(chatModel) + .hooks(List.of(hook)) + .build();`} + + +### 完整集成示例(Skills + Python + Shell) + +技能常需配合脚本执行(如技能目录下的 Python 脚本)和 Shell 命令。下面示例使用 **ClasspathSkillRegistry** 加载技能、**SkillsAgentHook** 提供 `read_skill`、**ShellToolAgentHook** 提供 Shell 工具、**PythonTool** 提供 Python 执行能力,Agent 可根据技能说明读取并处理技能目录下的文件。 + + +{`import com.alibaba.cloud.ai.graph.agent.ReactAgent; +import com.alibaba.cloud.ai.graph.agent.hook.skills.SkillsAgentHook; +import com.alibaba.cloud.ai.graph.agent.hook.shelltool.ShellToolAgentHook; +import com.alibaba.cloud.ai.graph.agent.tools.PythonTool; +import com.alibaba.cloud.ai.graph.agent.tools.ShellTool2; +import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; +import com.alibaba.cloud.ai.graph.skills.registry.classpath.ClasspathSkillRegistry; +import com.alibaba.cloud.ai.graph.skills.registry.SkillRegistry; + +// 1. 技能注册表:从 classpath:skills 加载(如 src/main/resources/skills/) +SkillRegistry registry = ClasspathSkillRegistry.builder() + .classpathPath("skills") + .build(); + +// 2. Skills Hook:注册 read_skill 工具并注入技能列表到系统提示 +SkillsAgentHook skillsHook = SkillsAgentHook.builder() + .skillRegistry(registry) + .build(); + +// 3. Shell Hook:提供 Shell 命令执行(工作目录可指定,如当前工程目录) +ShellToolAgentHook shellHook = ShellToolAgentHook.builder() + .shellTool2(ShellTool2.builder(System.getProperty("user.dir")).build()) + .build(); + +// 4. 构建 Agent:同时挂载 Skills Hook、Shell Hook 和 Python 工具 +ReactAgent agent = ReactAgent.builder() + .name("skills-integration-agent") + .model(chatModel) + .saver(new MemorySaver()) + .tools(PythonTool.createPythonToolCallback(PythonTool.DESCRIPTION)) + .hooks(List.of(skillsHook, shellHook)) + .enableLogging(true) + .build(); + +// 5. 调用示例:用户请求处理技能目录下的文件时,模型可先 read_skill 再按技能说明调用 Python/Shell +String skillFilePath = "/path/to/skills/pdf-extractor/saa-roadmap.pdf"; // 实际路径来自技能目录或 hook.listSkills() +AssistantMessage response = agent.call("请从 " + skillFilePath + " 文件中提取关键信息。");`} + + +- **SkillRegistry**:`FileSystemSkillRegistry` 用 `projectSkillsDirectory(path)` 或 `ClassPathResource("skills")`;`ClasspathSkillRegistry` 用 `classpathPath("skills")`。 +- **ShellTool2**:`ShellTool2.builder(workDir).build()`,`workDir` 为 Shell 执行的工作目录(如 `System.getProperty("user.dir")`)。 +- **PythonTool**:`PythonTool.createPythonToolCallback(PythonTool.DESCRIPTION)` 即够用,如需自定义描述可传第二个参数。 +- 技能列表中会包含每个技能的 `skillPath`,模型可用该路径拼出技能目录下文件的绝对路径并交给 Python/Shell 处理。 + +--- + + +## 高级用法 + +### 渐进式工具 Tool 披露 + +通过将工具与 Skill 技能名绑定,可以做到工具跟随 Skill 实现渐进式披露:仅当模型对该技能调用了 `read_skill` 后,对应工具才会加入当次请求,实现按需暴露。激活后该技能的工具在会话后续轮次中仍可用。 + + +{`Map> groupedTools = Map.of( + "my-skill", // 与 SKILL.md 的 name 一致,如 'pdf-extractor' + List.of(myTool) +); + +SkillsAgentHook hook = SkillsAgentHook.builder() + .skillRegistry(registry) + .groupedTools(groupedTools) + .build();`} + + +### 生产环境配置 + +#### 自动重载技能 + + +{`SkillsAgentHook hook = SkillsAgentHook.builder() + .skillRegistry(registry) + .autoReload(true) + .build();`} + + +每次 Agent 执行前会调用 `registry.reload()`(若实现支持;不支持则抛 `UnsupportedOperationException`,Hook 会捕获并打 debug 日志)。 + +> 注意,每次 Agent 执行可能包含多次模型推理,`registry.reload()` 仅会在第一次推理时执行并加载最新的 skills,这样能保证同一次 Agent 执行时行为的连续性。 + +#### 用户级与项目级目录 + + +{`SkillRegistry registry = FileSystemSkillRegistry.builder() + .userSkillsDirectory("/home/user/saa/skills") + .projectSkillsDirectory("/app/project/skills") + .build();`} + + +同名技能项目覆盖用户。 + +#### 自定义系统提示模板 + +SAA 框架内置了 Skill Prompt 模板,用来引导实现 Skill 的渐进式披露。用户可结合自己系统的 Skill 组织方式定制 Prompt 模板。 + + +{`SystemPromptTemplate customTemplate = SystemPromptTemplate.builder() + .template("## 可用技能\\n{skills_list}\\n\\n## 加载说明\\n{skills_load_instructions}") + .build(); + +FileSystemSkillRegistry registry = FileSystemSkillRegistry.builder() + .projectSkillsDirectory("./skills") + .systemPromptTemplate(customTemplate) + .build();`} + + +模板变量:`{skills_list}`、`{skills_load_instructions}`。 + +### 拓展 SkillRegistry 实现 + +实现 `SkillRegistry` 接口(`get`、`listAll`、`contains`、`size`、`readSkillContent`、`getSkillLoadInstructions`、`getRegistryType`、`getSystemPromptTemplate`,可选 `reload()`)即可接入现有 Skills 体系。`SkillMetadata` 需包含 `name`、`description`、`skillPath`(及可选 `source`)。可参考 `AbstractSkillRegistry`、`FileSystemSkillRegistry`、`ClasspathSkillRegistry`。 + +--- + +## 在 Graph 中使用 Skills + +除在 **ReactAgent** 上通过 **SkillsAgentHook** 使用 Skills 外,在基于 **Graph** 或 **ChatClient** 的链路中,可通过 **ChatClient** 配合 **SkillPromptAugmentAdvisor**(`spring-ai-alibaba-graph-core`)将技能列表注入系统提示,实现渐进式披露的「技能发现」部分。 + +### 使用 ChatClient + SkillPromptAugmentAdvisor + +`SkillPromptAugmentAdvisor` 是 Spring AI 的 `Advisor`,在每次请求的 `before` 阶段将技能元数据(name、description、skillPath)注入系统提示,使模型知晓可用技能及加载说明;模型需要完整 SKILL.md 时,需配合 `read_skill` 工具(可由 SkillsAgentHook 提供或自行注册)。 + + +{`import org.springframework.ai.chat.client.ChatClient; +import com.alibaba.cloud.ai.graph.advisors.SkillPromptAugmentAdvisor; + +// 方式一:指定技能目录(字符串路径),Advisor 内部创建 FileSystemSkillRegistry +SkillPromptAugmentAdvisor skillAdvisor = SkillPromptAugmentAdvisor.builder() + .projectSkillsDirectory("./skills") // 或绝对路径 /path/to/skills + // .userSkillsDirectory("~/saa/skills") // 可选,默认 ~/saa/skills + .lazyLoad(false) // 可选,true 则首次请求时再加载技能 + .build(); + +ChatClient chatClient = ChatClient.builder(chatModel) + .defaultAdvisors(skillAdvisor) + .build(); + +// 调用时系统提示中会包含可用技能列表 +String response = chatClient.prompt() + .user("请介绍你有哪些技能") + .call() + .content();`} + + + +{`import com.alibaba.cloud.ai.graph.skills.registry.SkillRegistry; +import com.alibaba.cloud.ai.graph.skills.registry.filesystem.FileSystemSkillRegistry; + +SkillRegistry registry = FileSystemSkillRegistry.builder() + .projectSkillsDirectory("./skills") + .build(); + +SkillPromptAugmentAdvisor skillAdvisor = SkillPromptAugmentAdvisor.builder() + .skillRegistry(registry) + .build(); + +ChatClient chatClient = ChatClient.builder(chatModel) + .defaultAdvisors(skillAdvisor) + .build();`} + + +**说明**: + +- **SkillPromptAugmentAdvisor** 仅负责在系统提示中注入技能列表与加载说明,不注册 `read_skill` 工具。若需模型按需读取完整 SKILL.md,请在 ChatClient/Graph 中额外注册 `read_skill`(例如使用带 SkillsAgentHook 的 Agent 节点,或单独将 `ReadSkillTool` 注册为工具)。 +- **Graph 中的 Agent 节点**:若节点内部使用 `ChatClient`,可在构建该 `ChatClient` 时加入 `SkillPromptAugmentAdvisor`;若节点使用 ReactAgent,则直接使用 **SkillsAgentHook**(会同时注入技能列表并注册 `read_skill`)。 +- 技能通常需配合脚本执行(如技能目录下的 Python 脚本)和 Shell 命令才能在生产环境中正常使用。 + +--- + +## 最佳实践与性能建议 + +- **控制 SKILL.md 大小**:单文件建议约 1.5k–2k tokens,长内容放 `references/` 并在正文中列路径。 +- **技能名称一致**:`name`、`read_skill` 参数、`groupedTools` 的 key 保持一致。 +- **按需使用 groupedTools**:仅需「随技能激活」的工具用 groupedTools,其余用 Agent 的 `.tools()` 即可。 +- **常用 API**:`hook.listSkills()`、`hook.hasSkill(name)`、`hook.getSkillCount()`;`registry.reload()`(ClasspathSkillRegistry 支持)。 diff --git a/docs/frameworks/studio/quick-start.md b/docs/frameworks/studio/quick-start.md index a13d551f..95a21fcd 100644 --- a/docs/frameworks/studio/quick-start.md +++ b/docs/frameworks/studio/quick-start.md @@ -45,7 +45,7 @@ Just add the following dependency to your agent project: com.alibaba.cloud.ai spring-ai-alibaba-studio - 1.1.0.0-RC2 + 1.1.2.0 ``` diff --git a/docs/overview.md b/docs/overview.md index 0cc8d535..6c7e8545 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -7,8 +7,6 @@ keywords: [Spring AI Alibaba, Agent Framework, ReactAgent, Graph Core, 智能体 # 概览 -**我们非常高兴的宣布,Spring AI Alibaba 1.1 正式发布!** - Spring AI Alibaba 是构建 Agent 智能体应用最简单的方式,只需不到 10 行代码就可以构建您的智能体应用。 ![Architecture](/img/agent/overview/architecture-new.png) @@ -36,13 +34,13 @@ Spring AI Alibaba 项目从架构上包含如下三层: com.alibaba.cloud.ai spring-ai-alibaba-agent-framework - 1.1.0.0-RC2 + 1.1.2.0 com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope - 1.1.0.0-RC2 + 1.1.2.1 ``` diff --git a/docs/quick-start.md b/docs/quick-start.md index e24d4680..033114e5 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -26,14 +26,14 @@ keywords: [ReactAgent, 快速开始, Quick Start, Spring AI Alibaba, Agent Frame com.alibaba.cloud.ai spring-ai-alibaba-agent-framework - 1.1.0.0-RC2 + 1.1.2.0 com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope - 1.1.0.0-RC2 + 1.1.2.0 ``` @@ -229,14 +229,14 @@ ChatModel chatModel = DashScopeChatModel.builder() com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope - 1.1.0.0-RC2 + 1.1.2.1 org.springframework.ai spring-ai-starter-model-openai - 1.1.0 + 1.1.2 ``` diff --git a/docs/versions.md b/docs/versions.md new file mode 100644 index 00000000..4d1c914f --- /dev/null +++ b/docs/versions.md @@ -0,0 +1,133 @@ +--- +title: 版本说明 +description: Spring AI Alibaba 与 Spring AI、Spring Boot 版本对应关系,迁移说明及文档导航。 +keywords: [版本, versions, releases, Spring AI Alibaba, Spring AI 版本适配, 迁移] +--- + +# 版本说明 + +本文面向**终端用户**和**开发者**,说明 Spring AI Alibaba(SAA)与上游框架的版本对应关系、如何选型与迁移,以及文档与组件的版本信息。 + +## Spring AI Alibaba 框架版本 + +### 版本兼容表 + +| SAA 版本 | Spring AI | Spring AI Extensions | Spring Boot | 说明 | +|----------|-----------|----------------------|-------------|------| +| **1.1.2.0**(当前推荐) | 1.1.2 | 1.1.2.1 或 1.1.2.0 | 3.5.x | 支持 Agent Skills,提供 Supervisor、Routing 等 Multi-agent 能力。| +| 1.1.0.0 | 1.1.0 | 1.1.0.0 | 3.4.x | 1.1.0 首个正式版 | +| 1.1.0.0-RC2 | 1.1.0-RC2 | 1.1.0.0-RC2 | 3.4.x | 1.1.0 候选版,请使用 1.1.0.0 或 1.1.2.0 版本 | +| 1.1.0.0-RC1 | 1.1.0-RC1 | 1.1.0.0-RC1 | 3.4.x | 1.1.0 候选版,请使用 1.1.0.0 或 1.1.2.0 版本 | +| 1.0.x | 1.0.0 | — | 3.4.x | 1.0 系列 | + +> 具体 Spring Boot 小版本以 [spring-ai-alibaba-bom](https://github.com/alibaba/spring-ai-alibaba/blob/main/pom.xml) 及各模块 `pom.xml` 为准。升级前请核对 [Release Notes](https://github.com/alibaba/spring-ai-alibaba/releases)。 + +### 依赖管理(推荐使用 BOM) + +新项目建议通过 BOM 统一版本,避免与 Spring AI、Spring Boot 冲突: + +```xml + + + + com.alibaba.cloud.ai + spring-ai-alibaba-bom + 1.1.2.0 + pom + import + + + org.springframework.ai + spring-ai-bom + 1.1.2 + pom + import + + + com.alibaba.cloud.ai + spring-ai-alibaba-extensions-bom + 1.1.2.1 + pom + import + + + + + + com.alibaba.cloud.ai + spring-ai-alibaba-agent-framework + + + com.alibaba.cloud.ai + spring-ai-alibaba-starter-dashscope + + +``` +--- + +## 组件与生态版本 + +以下组件随 Spring AI Alibaba 主仓库发布,版本与 BOM 中的 SAA 版本一致(如 1.1.2.0)。 + +| 组件 / 模块 | artifactId | 说明 | 版本约定 | +|-------------|------------|------|----------| +| **BOM** | `spring-ai-alibaba-bom` | 依赖管理,统一 SAA 各模块版本 | 同 SAA 主版本 | +| **Agent Framework** | `spring-ai-alibaba-agent-framework` | ReactAgent、多智能体编排、Hooks、Skills 等 | 同 BOM | +| **Graph Core** | `spring-ai-alibaba-graph-core` | 图工作流运行时、持久化、流式、MCP 节点等 | 同 BOM | +| **Studio** | `spring-ai-alibaba-studio` | 嵌入式 Agent 调试与可视化 UI | 同 BOM | +| **Sandbox** | `spring-ai-alibaba-sandbox` | Agent 沙箱运行时 | 同 BOM | +| **Admin** | `spring-ai-alibaba-admin` | 一站式 Agent 平台(可视开发、可观测、MCP 管理) | 随主仓库发布 | +| **Starter A2A Nacos** | `spring-ai-alibaba-starter-a2a-nacos` | 基于 Nacos 的 A2A 通信 | 同 BOM | +| **Starter Config Nacos** | `spring-ai-alibaba-starter-config-nacos` | 基于 Nacos 的动态配置与模型热更新 | 同 BOM | +| **Starter Graph Observation** | `spring-ai-alibaba-starter-graph-observation` | Graph 可观测性(Micrometer/OpenTelemetry) | 同 BOM | +| **Starter Builtin Nodes** | `spring-ai-alibaba-starter-builtin-nodes` | 预置图节点(LlmNode、AgentNode 等) | 同 BOM | + +### 示例项目 + +示例项目位于主仓库 `examples/` 下,每个示例独立工程,依赖 BOM 中的 SAA 版本。 + +| 示例项目 | artifactId | 说明 | 项目地址 | +|----------|------------|------|----------| +| **Chatbot** | `chatbot` | 对话式 Agent 示例(DashScope、Studio、Python/Shell 等) | [github.com/alibaba/spring-ai-alibaba/tree/main/examples/chatbot](https://github.com/alibaba/spring-ai-alibaba/tree/main/examples/chatbot) | +| **DeepResearch** | `deepresearch` | 深度研究 Agent 示例(MCP、多步推理) | [github.com/alibaba/spring-ai-alibaba/tree/main/examples/deepresearch](https://github.com/alibaba/spring-ai-alibaba/tree/main/examples/deepresearch) | +| **Documentation** | `documentation` | 文档配套示例(Agent 与 Graph 教程、进阶示例) | [github.com/alibaba/spring-ai-alibaba/tree/main/examples/documentation](https://github.com/alibaba/spring-ai-alibaba/tree/main/examples/documentation) | + +### 第三方与社区生态 + +以下项目位于 [GitHub spring-ai-alibaba 组织](https://github.com/spring-ai-alibaba) 下,与 Spring AI Alibaba 主仓库协同构成生态。**版本与发布节奏请以各仓库或文档为准。** + +| 项目名称 | 说明 | 项目地址 | +|----------|------|----------| +| **Spring AI Extensions** | Spring AI 扩展实现(DashScopeChatModel、MCP Registry 等),为 LLM 应用提供模型、工具、向量存储等基础抽象 | [github.com/spring-ai-alibaba/spring-ai-extensions](https://github.com/spring-ai-alibaba/spring-ai-extensions) | +| **Examples** | Spring AI 与 Spring AI Alibaba 用法示例集合(独立仓库) | [github.com/spring-ai-alibaba/examples](https://github.com/spring-ai-alibaba/examples) | +| **DataAgent** | 基于 Spring AI Alibaba 的自然语言转 SQL,支持用自然语言查询数据库而无需手写 SQL | [github.com/spring-ai-alibaba/dataagent](https://github.com/spring-ai-alibaba/dataagent) | +| **DeepResearch (Graph 版本)** | 基于 spring-ai-alibaba-graph 的深度研究应用(独立仓库发行版) | [github.com/spring-ai-alibaba/deepresearch](https://github.com/spring-ai-alibaba/deepresearch) | +| **AssistantAgent** | 面向企业级场景的助手型 Agent 构建框架 | [github.com/spring-ai-alibaba/AssistantAgent](https://github.com/spring-ai-alibaba/AssistantAgent) | +| **Lynxe (原Jmanus)** | 高确定性、无代码的 Prompt 编程工作站(基于 Java) | [github.com/spring-ai-alibaba/Lynxe](https://github.com/spring-ai-alibaba/Lynxe) | + +--- + +## 文档目录大纲 + +文档按以下结构组织,便于查找入门、教程与进阶内容。 + +| 分类 | 路径 / 主题 | 说明 | +|------|-------------|------| +| **入门** | [概览](overview.md) | 项目定位、架构、核心能力与安装方式 | +| | [快速开始](quick-start.md) | 从零搭建 ReactAgent,含环境、依赖、API Key、示例代码 | +| **Agent 框架教程** | [frameworks/agent-framework/tutorials/](frameworks/agent-framework/tutorials/) | Agents、Models、Tools、Hooks、Memory、Messages、Skills、Structured Output 等 | +| **Agent 框架进阶** | [frameworks/agent-framework/advanced/](frameworks/agent-framework/advanced/) | RAG、多智能体、人机协同、上下文工程、A2A、Workflow 等 | +| **Graph 核心** | [frameworks/graph-core/quick-start.md](frameworks/graph-core/quick-start.md) | Graph 快速上手 | +| | [frameworks/graph-core/core/](frameworks/graph-core/core/) | 核心库、持久化、内存、流式等 | +| | [frameworks/graph-core/examples/](frameworks/graph-core/examples/) | 子图、并行、取消、MCP、持久化、人机协同等示例 | +| **Studio** | [frameworks/studio/quick-start.md](frameworks/studio/quick-start.md) | Studio 嵌入式调试与使用 | + +--- + +## 相关链接 + +- [Spring Ai Alibab GitHub 仓库](https://github.com/alibaba/spring-ai-alibaba) +- [Spring Ai Alibab Extensions GitHub 仓库](https://github.com/spring-ai-alibaba/spring-ai-extensions) +- [Spring Ai Alibaba Release Notes](https://github.com/alibaba/spring-ai-alibaba/releases) +- [Spring Ai Extensions Release Notes](https://github.com/spring-ai-alibaba/spring-ai-extensions/releases) +- [Spring AI 参考文档](https://docs.spring.io/spring-ai/reference/) diff --git a/sidebars.ts b/sidebars.ts index cb32edee..0a7c0023 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -15,6 +15,7 @@ const sidebars: SidebarsConfig = { docsSidebar: [ 'overview', 'quick-start', + 'versions', { type: 'category', label: 'Agent Framework', @@ -29,6 +30,7 @@ const sidebars: SidebarsConfig = { 'frameworks/agent-framework/tutorials/tools', 'frameworks/agent-framework/tutorials/memory', 'frameworks/agent-framework/tutorials/hooks', + 'frameworks/agent-framework/tutorials/skills', 'frameworks/agent-framework/tutorials/structured-output', ], }, diff --git a/src/components/AnnouncementBar/index.tsx b/src/components/AnnouncementBar/index.tsx index c379b751..839bab67 100644 --- a/src/components/AnnouncementBar/index.tsx +++ b/src/components/AnnouncementBar/index.tsx @@ -18,7 +18,7 @@ interface AnnouncementBarProps { } export default function AnnouncementBar({ - content = '🎉 欢迎阅读下载《AI 原生应用架构白皮书》,40位一线工程师编写、15位行业专家力荐!', + content = '🎉🎉 1.1.2.0 版本正式发布,适配 Spring AI 1.1.2,支持 Agent Skills,支持 Supervisor、Routing 等多种 Multi-agent 最佳实践!', closable = true, onClose, }: AnnouncementBarProps) {