Commit dda2b56bbffdabff9fc19bdceae6b045a308290e
1 parent
911f5f8b
feat:AI工具新增ToolContext
Showing
4 changed files
with
102 additions
and
13 deletions
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 | } |