Commit 9b2f2f581b01361093c543114365ba16c0816a16
1 parent
ca34d165
feat:【ai 大模型】增加联网搜索功能
Showing
5 changed files
with
104 additions
and
21 deletions
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/AiChatMessageController.http
| ... | ... | @@ -39,14 +39,27 @@ Authorization: {{token}} |
| 39 | 39 | tenant-id: {{adminTenantId}} |
| 40 | 40 | |
| 41 | 41 | { |
| 42 | - "conversationId": "1781604279872581797", | |
| 42 | + "conversationId": "1781604279872581799", | |
| 43 | 43 | "content": "说下图片里,有哪些字?", |
| 44 | 44 | "useContext": true |
| 45 | 45 | } |
| 46 | 46 | |
| 47 | +### 发送消息(流式)【联网搜索】 | |
| 48 | +POST {{baseUrl}}/ai/chat/message/send-stream | |
| 49 | +Content-Type: application/json | |
| 50 | +Authorization: {{token}} | |
| 51 | +tenant-id: {{adminTenantId}} | |
| 52 | + | |
| 53 | +{ | |
| 54 | + "conversationId": "1781604279872581799", | |
| 55 | + "content": "今天是周几?", | |
| 56 | + "useSearch": true | |
| 57 | +} | |
| 58 | + | |
| 47 | 59 | ### 获得指定对话的消息列表 |
| 48 | -GET {{baseUrl}}/ai/chat/message/list-by-conversation-id?conversationId=1781604279872581649 | |
| 60 | +GET {{baseUrl}}/ai/chat/message/list-by-conversation-id?conversationId=1781604279872581799 | |
| 49 | 61 | Authorization: {{token}} |
| 62 | +tenant-id: {{adminTenantId}} | |
| 50 | 63 | |
| 51 | 64 | ### 删除消息 |
| 52 | 65 | DELETE {{baseUrl}}/ai/chat/message/delete?id=50 | ... | ... |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageRespVO.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchResponse; | |
| 3 | 4 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | 5 | import lombok.Data; |
| 5 | 6 | |
| ... | ... | @@ -49,6 +50,9 @@ public class AiChatMessageRespVO { |
| 49 | 50 | @Schema(description = "知识库段落数组") |
| 50 | 51 | private List<KnowledgeSegment> segments; |
| 51 | 52 | |
| 53 | + @Schema(description = "联网搜索的网页内容数组") | |
| 54 | + private List<AiWebSearchResponse.WebPage> webSearchPages; | |
| 55 | + | |
| 52 | 56 | @Schema(description = "附件 URL 数组", example = "https://www.iocoder.cn/1.png") |
| 53 | 57 | private List<String> attachmentUrls; |
| 54 | 58 | ... | ... |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/controller/admin/chat/vo/message/AiChatMessageSendRespVO.java
| 1 | 1 | package cn.iocoder.yudao.module.ai.controller.admin.chat.vo.message; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchResponse; | |
| 3 | 4 | import io.swagger.v3.oas.annotations.media.Schema; |
| 4 | 5 | import lombok.Data; |
| 5 | 6 | |
| ... | ... | @@ -38,6 +39,9 @@ public class AiChatMessageSendRespVO { |
| 38 | 39 | @Schema(description = "知识库段落数组") |
| 39 | 40 | private List<AiChatMessageRespVO.KnowledgeSegment> segments; |
| 40 | 41 | |
| 42 | + @Schema(description = "联网搜索的网页内容数组") | |
| 43 | + private List<AiWebSearchResponse.WebPage> webSearchPages; | |
| 44 | + | |
| 41 | 45 | @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) |
| 42 | 46 | private LocalDateTime createTime; |
| 43 | 47 | ... | ... |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/dal/dataobject/chat/AiChatMessageDO.java
| ... | ... | @@ -6,11 +6,16 @@ import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler; |
| 6 | 6 | import cn.iocoder.yudao.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO; |
| 7 | 7 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiChatRoleDO; |
| 8 | 8 | import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiModelDO; |
| 9 | +import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchResponse; | |
| 9 | 10 | import com.baomidou.mybatisplus.annotation.KeySequence; |
| 10 | 11 | import com.baomidou.mybatisplus.annotation.TableField; |
| 11 | 12 | import com.baomidou.mybatisplus.annotation.TableId; |
| 12 | 13 | import com.baomidou.mybatisplus.annotation.TableName; |
| 13 | -import lombok.*; | |
| 14 | +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; | |
| 15 | +import lombok.AllArgsConstructor; | |
| 16 | +import lombok.Builder; | |
| 17 | +import lombok.Data; | |
| 18 | +import lombok.NoArgsConstructor; | |
| 14 | 19 | import org.springframework.ai.chat.messages.MessageType; |
| 15 | 20 | |
| 16 | 21 | import java.util.List; |
| ... | ... | @@ -107,6 +112,12 @@ public class AiChatMessageDO extends BaseDO { |
| 107 | 112 | private List<Long> segmentIds; |
| 108 | 113 | |
| 109 | 114 | /** |
| 115 | + * 联网搜索的网页内容数组 | |
| 116 | + */ | |
| 117 | + @TableField(typeHandler = JacksonTypeHandler.class) | |
| 118 | + private List<AiWebSearchResponse.WebPage> webSearchPages; | |
| 119 | + | |
| 120 | + /** | |
| 110 | 121 | * 附件 URL 数组 |
| 111 | 122 | */ |
| 112 | 123 | @TableField(typeHandler = StringListTypeHandler.class) | ... | ... |
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/chat/AiChatMessageServiceImpl.java
| ... | ... | @@ -23,6 +23,9 @@ import cn.iocoder.yudao.module.ai.dal.dataobject.model.AiToolDO; |
| 23 | 23 | import cn.iocoder.yudao.module.ai.dal.mysql.chat.AiChatMessageMapper; |
| 24 | 24 | import cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants; |
| 25 | 25 | import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum; |
| 26 | +import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient; | |
| 27 | +import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchRequest; | |
| 28 | +import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchResponse; | |
| 26 | 29 | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeDocumentService; |
| 27 | 30 | import cn.iocoder.yudao.module.ai.service.knowledge.AiKnowledgeSegmentService; |
| 28 | 31 | import cn.iocoder.yudao.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO; |
| ... | ... | @@ -44,6 +47,7 @@ import org.springframework.ai.chat.model.ChatResponse; |
| 44 | 47 | import org.springframework.ai.chat.model.StreamingChatModel; |
| 45 | 48 | import org.springframework.ai.chat.prompt.ChatOptions; |
| 46 | 49 | import org.springframework.ai.chat.prompt.Prompt; |
| 50 | +import org.springframework.beans.factory.annotation.Autowired; | |
| 47 | 51 | import org.springframework.stereotype.Service; |
| 48 | 52 | import org.springframework.transaction.annotation.Transactional; |
| 49 | 53 | import reactor.core.publisher.Flux; |
| ... | ... | @@ -69,6 +73,11 @@ import static cn.iocoder.yudao.module.ai.enums.ErrorCodeConstants.CHAT_MESSAGE_N |
| 69 | 73 | @Slf4j |
| 70 | 74 | public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 71 | 75 | |
| 76 | + /** | |
| 77 | + * 联网搜索的结束数 | |
| 78 | + */ | |
| 79 | + private static final Integer WEB_SEARCH_COUNT = 10; | |
| 80 | + | |
| 72 | 81 | // TODO @芋艿:后续优化下对话的 Prompt 整体结构 |
| 73 | 82 | |
| 74 | 83 | /** |
| ... | ... | @@ -78,6 +87,10 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 78 | 87 | "%s\n\n" + // 多个 <Reference></Reference> 的拼接 |
| 79 | 88 | "回答要求:\n- 避免提及你是从 <Reference></Reference> 获取的知识。"; |
| 80 | 89 | |
| 90 | + private static final String WEB_SEARCH_USER_MESSAGE_TEMPLATE = "使用 <WebSearch></WebSearch> 标记中的内容作为本次对话的参考:\n\n" + | |
| 91 | + "%s\n\n" + // 多个 <WebSearch></WebSearch> 的拼接 | |
| 92 | + "回答要求:\n- 避免提及你是从 <WebSearch></WebSearch> 获取的知识。"; | |
| 93 | + | |
| 81 | 94 | /** |
| 82 | 95 | * 附件转 ${@link UserMessage} 的内容模版 |
| 83 | 96 | */ |
| ... | ... | @@ -102,6 +115,10 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 102 | 115 | @Resource |
| 103 | 116 | private AiToolService toolService; |
| 104 | 117 | |
| 118 | + @SuppressWarnings("SpringJavaAutowiredFieldsWarningInspection") | |
| 119 | + @Autowired(required = false) // 由于 yudao.ai.web-search.enable 配置项,可以关闭 AiWebSearchClient 的功能,所以这里只能不强制注入 | |
| 120 | + private AiWebSearchClient webSearchClient; | |
| 121 | + | |
| 105 | 122 | @Transactional(rollbackFor = Exception.class) |
| 106 | 123 | public AiChatMessageSendRespVO sendMessage(AiChatMessageSendReqVO sendReqVO, Long userId) { |
| 107 | 124 | // 1.1 校验对话存在 |
| ... | ... | @@ -115,30 +132,35 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 115 | 132 | AiModelDO model = modalService.validateModel(conversation.getModelId()); |
| 116 | 133 | ChatModel chatModel = modalService.getChatModel(model.getId()); |
| 117 | 134 | |
| 118 | - // 2. 知识库找回 | |
| 135 | + // 2.1 知识库召回 | |
| 119 | 136 | List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments = recallKnowledgeSegment( |
| 120 | 137 | sendReqVO.getContent(), conversation); |
| 121 | 138 | |
| 139 | + // 2.2 联网搜索 | |
| 140 | + AiWebSearchResponse webSearchResponse = Boolean.TRUE.equals(sendReqVO.getUseSearch()) && webSearchClient != null ? | |
| 141 | + webSearchClient.search(new AiWebSearchRequest().setQuery(sendReqVO.getContent()) | |
| 142 | + .setSummary(true).setCount(WEB_SEARCH_COUNT)) : null; | |
| 143 | + | |
| 122 | 144 | // 3. 插入 user 发送消息 |
| 123 | 145 | AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model, |
| 124 | 146 | userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext(), |
| 125 | - null, sendReqVO.getAttachmentUrls()); | |
| 147 | + null, sendReqVO.getAttachmentUrls(), null); | |
| 126 | 148 | |
| 127 | - // 3.1 插入 assistant 接收消息 | |
| 149 | + // 4.1 插入 assistant 接收消息 | |
| 128 | 150 | AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, |
| 129 | 151 | userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext(), |
| 130 | - knowledgeSegments, null); | |
| 152 | + knowledgeSegments, null, webSearchResponse); | |
| 131 | 153 | |
| 132 | - // 3.2 创建 chat 需要的 Prompt | |
| 133 | - Prompt prompt = buildPrompt(conversation, historyMessages, knowledgeSegments, model, sendReqVO); | |
| 154 | + // 4.2 创建 chat 需要的 Prompt | |
| 155 | + Prompt prompt = buildPrompt(conversation, historyMessages, knowledgeSegments, webSearchResponse, model, sendReqVO); | |
| 134 | 156 | ChatResponse chatResponse = chatModel.call(prompt); |
| 135 | 157 | |
| 136 | - // 3.3 更新响应内容 | |
| 158 | + // 4.3 更新响应内容 | |
| 137 | 159 | String newContent = AiUtils.getChatResponseContent(chatResponse); |
| 138 | 160 | String newReasoningContent = AiUtils.getChatResponseReasoningContent(chatResponse); |
| 139 | 161 | chatMessageMapper.updateById(new AiChatMessageDO().setId(assistantMessage.getId()) |
| 140 | 162 | .setContent(newContent).setReasoningContent(newReasoningContent)); |
| 141 | - // 3.4 响应结果 | |
| 163 | + // 4.4 响应结果 | |
| 142 | 164 | Map<Long, AiKnowledgeDocumentDO> documentMap = knowledgeDocumentService.getKnowledgeDocumentMap( |
| 143 | 165 | convertSet(knowledgeSegments, AiKnowledgeSegmentSearchRespBO::getDocumentId)); |
| 144 | 166 | List<AiChatMessageRespVO.KnowledgeSegment> segments = BeanUtils.toBean(knowledgeSegments, |
| ... | ... | @@ -149,7 +171,8 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 149 | 171 | return new AiChatMessageSendRespVO() |
| 150 | 172 | .setSend(BeanUtils.toBean(userMessage, AiChatMessageSendRespVO.Message.class)) |
| 151 | 173 | .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class) |
| 152 | - .setContent(newContent).setSegments(segments)); | |
| 174 | + .setContent(newContent).setSegments(segments) | |
| 175 | + .setWebSearchPages(webSearchResponse != null ? webSearchResponse.getLists() : null)); | |
| 153 | 176 | } |
| 154 | 177 | |
| 155 | 178 | @Override |
| ... | ... | @@ -166,30 +189,36 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 166 | 189 | AiModelDO model = modalService.validateModel(conversation.getModelId()); |
| 167 | 190 | StreamingChatModel chatModel = modalService.getChatModel(model.getId()); |
| 168 | 191 | |
| 169 | - // 2. 知识库找回 | |
| 192 | + // 2.1 知识库找回 | |
| 170 | 193 | List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments = recallKnowledgeSegment( |
| 171 | 194 | sendReqVO.getContent(), conversation); |
| 172 | 195 | |
| 196 | + // 2.2 联网搜索 | |
| 197 | + AiWebSearchResponse webSearchResponse = Boolean.TRUE.equals(sendReqVO.getUseSearch()) && webSearchClient != null ? | |
| 198 | + webSearchClient.search(new AiWebSearchRequest().setQuery(sendReqVO.getContent()) | |
| 199 | + .setSummary(true).setCount(WEB_SEARCH_COUNT)) : null; | |
| 200 | + | |
| 173 | 201 | // 3. 插入 user 发送消息 |
| 174 | 202 | AiChatMessageDO userMessage = createChatMessage(conversation.getId(), null, model, |
| 175 | 203 | userId, conversation.getRoleId(), MessageType.USER, sendReqVO.getContent(), sendReqVO.getUseContext(), |
| 176 | - null, sendReqVO.getAttachmentUrls()); | |
| 204 | + null, sendReqVO.getAttachmentUrls(), null); | |
| 177 | 205 | |
| 178 | 206 | // 4.1 插入 assistant 接收消息 |
| 179 | 207 | AiChatMessageDO assistantMessage = createChatMessage(conversation.getId(), userMessage.getId(), model, |
| 180 | 208 | userId, conversation.getRoleId(), MessageType.ASSISTANT, "", sendReqVO.getUseContext(), |
| 181 | - knowledgeSegments, null); | |
| 209 | + knowledgeSegments, null, webSearchResponse); | |
| 182 | 210 | |
| 183 | 211 | // 4.2 构建 Prompt,并进行调用 |
| 184 | - Prompt prompt = buildPrompt(conversation, historyMessages, knowledgeSegments, model, sendReqVO); | |
| 212 | + Prompt prompt = buildPrompt(conversation, historyMessages, knowledgeSegments, webSearchResponse, model, sendReqVO); | |
| 185 | 213 | Flux<ChatResponse> streamResponse = chatModel.stream(prompt); |
| 186 | 214 | |
| 187 | 215 | // 4.3 流式返回 |
| 188 | 216 | StringBuffer contentBuffer = new StringBuffer(); |
| 189 | 217 | StringBuffer reasoningContentBuffer = new StringBuffer(); |
| 190 | 218 | return streamResponse.map(chunk -> { |
| 191 | - // 处理知识库的返回,只有首次才有 | |
| 219 | + // 仅首次:返回知识库、联网搜索 | |
| 192 | 220 | List<AiChatMessageRespVO.KnowledgeSegment> segments = null; |
| 221 | + List<AiWebSearchResponse.WebPage> webSearchPages = null; | |
| 193 | 222 | if (StrUtil.isEmpty(contentBuffer)) { |
| 194 | 223 | Map<Long, AiKnowledgeDocumentDO> documentMap = TenantUtils.executeIgnore(() -> |
| 195 | 224 | knowledgeDocumentService.getKnowledgeDocumentMap( |
| ... | ... | @@ -198,6 +227,9 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 198 | 227 | AiKnowledgeDocumentDO document = documentMap.get(segment.getDocumentId()); |
| 199 | 228 | segment.setDocumentName(document != null ? document.getName() : null); |
| 200 | 229 | }); |
| 230 | + if (webSearchResponse != null) { | |
| 231 | + webSearchPages = webSearchResponse.getLists(); | |
| 232 | + } | |
| 201 | 233 | } |
| 202 | 234 | // 响应结果 |
| 203 | 235 | String newContent = AiUtils.getChatResponseContent(chunk); |
| ... | ... | @@ -213,7 +245,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 213 | 245 | .setReceive(BeanUtils.toBean(assistantMessage, AiChatMessageSendRespVO.Message.class) |
| 214 | 246 | .setContent(StrUtil.nullToDefault(newContent, "")) // 避免 null 的 情况 |
| 215 | 247 | .setReasoningContent(StrUtil.nullToDefault(newReasoningContent, "")) // 避免 null 的 情况 |
| 216 | - .setSegments(segments))); // 知识库返回 | |
| 248 | + .setSegments(segments).setWebSearchPages(webSearchPages))); // 知识库 + 联网搜索 | |
| 217 | 249 | }).doOnComplete(() -> { |
| 218 | 250 | // 忽略租户,因为 Flux 异步无法透传租户 |
| 219 | 251 | TenantUtils.executeIgnore(() -> chatMessageMapper.updateById( |
| ... | ... | @@ -239,7 +271,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 239 | 271 | return Collections.emptyList(); |
| 240 | 272 | } |
| 241 | 273 | |
| 242 | - // 2. 遍历找回 | |
| 274 | + // 2. 遍历召回 | |
| 243 | 275 | List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments = new ArrayList<>(); |
| 244 | 276 | for (Long knowledgeId : role.getKnowledgeIds()) { |
| 245 | 277 | knowledgeSegments.addAll(knowledgeSegmentService.searchKnowledgeSegment(new AiKnowledgeSegmentSearchReqBO() |
| ... | ... | @@ -250,6 +282,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 250 | 282 | |
| 251 | 283 | private Prompt buildPrompt(AiChatConversationDO conversation, List<AiChatMessageDO> messages, |
| 252 | 284 | List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments, |
| 285 | + AiWebSearchResponse webSearchResponse, | |
| 253 | 286 | AiModelDO model, AiChatMessageSendReqVO sendReqVO) { |
| 254 | 287 | List<Message> chatMessages = new ArrayList<>(); |
| 255 | 288 | // 1.1 System Context 角色设定 |
| ... | ... | @@ -265,6 +298,7 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 265 | 298 | if (attachmentUserMessage != null) { |
| 266 | 299 | chatMessages.add(attachmentUserMessage); |
| 267 | 300 | } |
| 301 | + // TODO @芋艿:历史的知识库;历史的搜索,要不要拼接? | |
| 268 | 302 | }); |
| 269 | 303 | |
| 270 | 304 | // 1.3 当前 user message 新发送消息 |
| ... | ... | @@ -278,7 +312,20 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 278 | 312 | chatMessages.add(new UserMessage(String.format(KNOWLEDGE_USER_MESSAGE_TEMPLATE, reference))); |
| 279 | 313 | } |
| 280 | 314 | |
| 281 | - // 1.5 附件,通过 UserMessage 实现 | |
| 315 | + // 1.5 联网搜索,通过 UserMessage 实现 | |
| 316 | + if (webSearchResponse != null && CollUtil.isNotEmpty(webSearchResponse.getLists())) { | |
| 317 | + String webSearch = webSearchResponse.getLists().stream() | |
| 318 | + .map(page -> { | |
| 319 | + String summary = StrUtil.isNotEmpty(page.getSummary()) ? | |
| 320 | + "\nSummary: " + page.getSummary() : ""; | |
| 321 | + return "<WebSearch title=\"" + page.getTitle() + "\" url=\"" + page.getUrl() + "\">" | |
| 322 | + + StrUtil.blankToDefault(page.getSummary(), page.getSnippet()) + "</WebSearch>"; | |
| 323 | + }) | |
| 324 | + .collect(Collectors.joining("\n\n")); | |
| 325 | + chatMessages.add(new UserMessage(String.format(WEB_SEARCH_USER_MESSAGE_TEMPLATE, webSearch))); | |
| 326 | + } | |
| 327 | + | |
| 328 | + // 1.6 附件,通过 UserMessage 实现 | |
| 282 | 329 | if (CollUtil.isNotEmpty(sendReqVO.getAttachmentUrls())) { |
| 283 | 330 | UserMessage attachmentUserMessage = buildAttachmentUserMessage(sendReqVO.getAttachmentUrls()); |
| 284 | 331 | if (attachmentUserMessage != null) { |
| ... | ... | @@ -383,12 +430,16 @@ public class AiChatMessageServiceImpl implements AiChatMessageService { |
| 383 | 430 | AiModelDO model, Long userId, Long roleId, |
| 384 | 431 | MessageType messageType, String content, Boolean useContext, |
| 385 | 432 | List<AiKnowledgeSegmentSearchRespBO> knowledgeSegments, |
| 386 | - List<String> attachmentUrls) { | |
| 433 | + List<String> attachmentUrls, | |
| 434 | + AiWebSearchResponse webSearchResponse) { | |
| 387 | 435 | AiChatMessageDO message = new AiChatMessageDO().setConversationId(conversationId).setReplyId(replyId) |
| 388 | 436 | .setModel(model.getModel()).setModelId(model.getId()).setUserId(userId).setRoleId(roleId) |
| 389 | 437 | .setType(messageType.getValue()).setContent(content).setUseContext(useContext) |
| 390 | 438 | .setSegmentIds(convertList(knowledgeSegments, AiKnowledgeSegmentSearchRespBO::getId)) |
| 391 | 439 | .setAttachmentUrls(attachmentUrls); |
| 440 | + if (webSearchResponse != null) { | |
| 441 | + message.setWebSearchPages(webSearchResponse.getLists()); | |
| 442 | + } | |
| 392 | 443 | message.setCreateTime(LocalDateTime.now()); |
| 393 | 444 | chatMessageMapper.insert(message); |
| 394 | 445 | return message; | ... | ... |