Commit dda2b56bbffdabff9fc19bdceae6b045a308290e

Authored by Ren
1 parent 911f5f8b

feat:AI工具新增ToolContext

yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
@@ -4,10 +4,12 @@ import cn.hutool.core.collection.CollUtil; @@ -4,10 +4,12 @@ import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.ObjUtil; 4 import cn.hutool.core.util.ObjUtil;
5 import cn.hutool.core.util.StrUtil; 5 import cn.hutool.core.util.StrUtil;
6 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 6 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
  7 +import cn.iocoder.yudao.framework.ai.core.pojo.AiToolContext;
7 import cn.iocoder.yudao.framework.ai.core.util.AiUtils; 8 import cn.iocoder.yudao.framework.ai.core.util.AiUtils;
8 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 9 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
9 import cn.iocoder.yudao.framework.common.pojo.PageResult; 10 import cn.iocoder.yudao.framework.common.pojo.PageResult;
10 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 11 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  12 +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
11 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; 13 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
12 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO; 14 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessagePageReqVO;
13 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO; 15 import cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message.AiChatMessageRespVO;
@@ -115,7 +117,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -115,7 +117,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
115 knowledgeSegments); 117 knowledgeSegments);
116 118
117 // 3.2 创建 chat 需要的 Prompt 119 // 3.2 创建 chat 需要的 Prompt
118 - Prompt prompt = buildPrompt(conversation, historyMessages, knowledgeSegments, model, sendReqVO); 120 + Prompt prompt = buildPrompt(chatModel, conversation, historyMessages, knowledgeSegments, model, sendReqVO);
119 ChatResponse chatResponse = chatModel.call(prompt); 121 ChatResponse chatResponse = chatModel.call(prompt);
120 122
121 // 3.3 更新响应内容 123 // 3.3 更新响应内容
@@ -164,7 +166,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -164,7 +166,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
164 knowledgeSegments); 166 knowledgeSegments);
165 167
166 // 4.2 构建 Prompt,并进行调用 168 // 4.2 构建 Prompt,并进行调用
167 - Prompt prompt = buildPrompt(conversation, historyMessages, knowledgeSegments, model, sendReqVO); 169 + Prompt prompt = buildPrompt(chatModel, conversation, historyMessages, knowledgeSegments, model, sendReqVO);
168 Flux<ChatResponse> streamResponse = chatModel.stream(prompt); 170 Flux<ChatResponse> streamResponse = chatModel.stream(prompt);
169 171
170 // 4.3 流式返回 172 // 4.3 流式返回
@@ -220,7 +222,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -220,7 +222,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
220 return knowledgeSegments; 222 return knowledgeSegments;
221 } 223 }
222 224
223 - private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages, 225 + private Prompt buildPrompt(StreamingChatModel chatModel, AiChatConversationDO conversation, List<AiChatMessageDO> messages,
224 List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments, 226 List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments,
225 AiModelDO model, AiChatMessageSendReqVO sendReqVO) { 227 AiModelDO model, AiChatMessageSendReqVO sendReqVO) {
226 List<Message> chatMessages = new ArrayList<>(); 228 List<Message> chatMessages = new ArrayList<>();
@@ -247,16 +249,22 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { @@ -247,16 +249,22 @@ public class AiChatMessageServiceImpl implements AiChatMessageService {
247 249
248 // 2.1 查询 tool 工具 250 // 2.1 查询 tool 工具
249 Set<String> toolNames = null; 251 Set<String> toolNames = null;
  252 + Map<String,Object> toolContext = Map.of();
250 if (conversation.getRoleId() != null) { 253 if (conversation.getRoleId() != null) {
251 AiChatRoleDO chatRole = chatRoleService.getChatRole(conversation.getRoleId()); 254 AiChatRoleDO chatRole = chatRoleService.getChatRole(conversation.getRoleId());
252 if (chatRole != null && CollUtil.isNotEmpty(chatRole.getToolIds())) { 255 if (chatRole != null && CollUtil.isNotEmpty(chatRole.getToolIds())) {
253 toolNames = convertSet(toolService.getToolList(chatRole.getToolIds()), AiToolDO::getName); 256 toolNames = convertSet(toolService.getToolList(chatRole.getToolIds()), AiToolDO::getName);
  257 + // 2.1.1 构建 Function Calling 的上下文参数
  258 + toolContext = Map.of(
  259 + AiToolContext.CONTEXT_KEY, new AiToolContext().setChatModel(chatModel).setUserId(SecurityFrameworkUtils.getLoginUserId())
  260 + .setRoleId(conversation.getRoleId())
  261 + .setConversationId(conversation.getId()));
254 } 262 }
255 } 263 }
256 // 2.2 构建 ChatOptions 对象 264 // 2.2 构建 ChatOptions 对象
257 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform()); 265 AiPlatformEnum platform = AiPlatformEnum.validatePlatform(model.getPlatform());
258 ChatOptions chatOptions = AiUtils.buildChatOptions(platform, model.getModel(), 266 ChatOptions chatOptions = AiUtils.buildChatOptions(platform, model.getModel(),
259 - conversation.getTemperature(), conversation.getMaxTokens(), toolNames); 267 + conversation.getTemperature(), conversation.getMaxTokens(), toolNames, toolContext);
260 return new Prompt(chatMessages, chatOptions); 268 return new Prompt(chatMessages, chatOptions);
261 } 269 }
262 270
yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/service/model/tool/UserIdQueryToolFunction.java 0 → 100644
  1 +package cn.iocoder.yudao.module.ai.service.model.tool;
  2 +
  3 +import cn.iocoder.yudao.framework.ai.core.pojo.AiToolContext;
  4 +import com.fasterxml.jackson.annotation.JsonClassDescription;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Data;
  7 +import lombok.NoArgsConstructor;
  8 +import org.springframework.ai.chat.model.ToolContext;
  9 +import org.springframework.stereotype.Component;
  10 +
  11 +import java.util.function.BiFunction;
  12 +
  13 +/**
  14 + * 工具:用户ID查询(上下文参数Demo)
  15 + *
  16 + * @author Ren
  17 + */
  18 +@Component("userid_query")
  19 +public class UserIdQueryToolFunction
  20 + implements BiFunction<UserIdQueryToolFunction.Request, ToolContext, UserIdQueryToolFunction.Response> {
  21 +
  22 + @Data
  23 + @JsonClassDescription("用户ID查询")
  24 + public static class Request { }
  25 +
  26 + @Data
  27 + @AllArgsConstructor
  28 + @NoArgsConstructor
  29 + public static class Response {
  30 + /**
  31 + * 用户ID
  32 + */
  33 + private Long UserId;
  34 +
  35 + }
  36 + @Override
  37 + public UserIdQueryToolFunction.Response apply(UserIdQueryToolFunction.Request request, ToolContext toolContext) {
  38 + // 获取当前登录用户
  39 + AiToolContext context = (AiToolContext) toolContext.getContext().get(AiToolContext.CONTEXT_KEY);
  40 +
  41 + return new Response(context.getUserId());
  42 + }
  43 +}
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/pojo/AiToolContext.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.core.pojo;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +import org.springframework.ai.chat.model.ChatModel;
  7 +import org.springframework.ai.chat.model.StreamingChatModel;
  8 +
  9 +/**
  10 + * 工具上下文参数 DTO,让AI工具可以处理当前用户的相关信息
  11 + *
  12 + */
  13 +@Data
  14 +@NoArgsConstructor
  15 +@AllArgsConstructor
  16 +public class AiToolContext {
  17 + public static final String CONTEXT_KEY = "AI_TOOL_CONTEXT";
  18 + /**
  19 + * 用户ID
  20 + */
  21 + private Long userId;
  22 +
  23 + /**
  24 + * 聊天模型
  25 + */
  26 + private StreamingChatModel chatModel;
  27 +
  28 + /**
  29 + * 关联的聊天角色Id
  30 + */
  31 + private Long roleId;
  32 +
  33 + /**
  34 + * 会话Id
  35 + */
  36 + private Long conversationId;
  37 +}
0 \ No newline at end of file 38 \ No newline at end of file
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java
@@ -15,6 +15,7 @@ import org.springframework.ai.qianfan.QianFanChatOptions; @@ -15,6 +15,7 @@ import org.springframework.ai.qianfan.QianFanChatOptions;
15 import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; 15 import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
16 16
17 import java.util.Collections; 17 import java.util.Collections;
  18 +import java.util.Map;
18 import java.util.Set; 19 import java.util.Set;
19 20
20 /** 21 /**
@@ -25,28 +26,28 @@ import java.util.Set; @@ -25,28 +26,28 @@ import java.util.Set;
25 public class AiUtils { 26 public class AiUtils {
26 27
27 public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens) { 28 public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens) {
28 - return buildChatOptions(platform, model, temperature, maxTokens, null); 29 + return buildChatOptions(platform, model, temperature, maxTokens, null, Map.of());
29 } 30 }
30 31
31 public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens, 32 public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens,
32 - Set<String> toolNames) { 33 + Set<String> toolNames, Map<String, Object> toolContext) {
33 toolNames = ObjUtil.defaultIfNull(toolNames, Collections.emptySet()); 34 toolNames = ObjUtil.defaultIfNull(toolNames, Collections.emptySet());
34 // noinspection EnhancedSwitchMigration 35 // noinspection EnhancedSwitchMigration
35 switch (platform) { 36 switch (platform) {
36 case TONG_YI: 37 case TONG_YI:
37 return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).withMaxToken(maxTokens) 38 return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).withMaxToken(maxTokens)
38 - .withFunctions(toolNames).build(); 39 + .withFunctions(toolNames).withToolContext(toolContext).build();
39 case YI_YAN: 40 case YI_YAN:
40 return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build(); 41 return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
41 case ZHI_PU: 42 case ZHI_PU:
42 return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) 43 return ZhiPuAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
43 - .functions(toolNames).build(); 44 + .functions(toolNames).toolContext(toolContext).build();
44 case MINI_MAX: 45 case MINI_MAX:
45 return MiniMaxChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) 46 return MiniMaxChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
46 - .functions(toolNames).build(); 47 + .functions(toolNames).toolContext(toolContext).build();
47 case MOONSHOT: 48 case MOONSHOT:
48 return MoonshotChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) 49 return MoonshotChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
49 - .functions(toolNames).build(); 50 + .functions(toolNames).toolContext(toolContext).build();
50 case OPENAI: 51 case OPENAI:
51 case DEEP_SEEK: // 复用 OpenAI 客户端 52 case DEEP_SEEK: // 复用 OpenAI 客户端
52 case DOU_BAO: // 复用 OpenAI 客户端 53 case DOU_BAO: // 复用 OpenAI 客户端
@@ -55,14 +56,14 @@ public class AiUtils { @@ -55,14 +56,14 @@ public class AiUtils {
55 case SILICON_FLOW: // 复用 OpenAI 客户端 56 case SILICON_FLOW: // 复用 OpenAI 客户端
56 case BAI_CHUAN: // 复用 OpenAI 客户端 57 case BAI_CHUAN: // 复用 OpenAI 客户端
57 return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens) 58 return OpenAiChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens)
58 - .toolNames(toolNames).build(); 59 + .toolNames(toolNames).toolContext(toolContext).build();
59 case AZURE_OPENAI: 60 case AZURE_OPENAI:
60 // TODO 芋艿:貌似没 model 字段???! 61 // TODO 芋艿:貌似没 model 字段???!
61 return AzureOpenAiChatOptions.builder().deploymentName(model).temperature(temperature).maxTokens(maxTokens) 62 return AzureOpenAiChatOptions.builder().deploymentName(model).temperature(temperature).maxTokens(maxTokens)
62 - .toolNames(toolNames).build(); 63 + .toolNames(toolNames).toolContext(toolContext).build();
63 case OLLAMA: 64 case OLLAMA:
64 return OllamaOptions.builder().model(model).temperature(temperature).numPredict(maxTokens) 65 return OllamaOptions.builder().model(model).temperature(temperature).numPredict(maxTokens)
65 - .toolNames(toolNames).build(); 66 + .toolNames(toolNames).toolContext(toolContext).build();
66 default: 67 default:
67 throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform)); 68 throw new IllegalArgumentException(StrUtil.format("未知平台({})", platform));
68 } 69 }