| | |
| | | package tech.aiflowy.ai.controller; |
| | | |
| | | import cn.dev33.satoken.annotation.SaIgnore; |
| | | import tech.aiflowy.ai.entity.*; |
| | | import tech.aiflowy.ai.mapper.AiBotConversationMessageMapper; |
| | | import tech.aiflowy.ai.service.*; |
| | | import tech.aiflowy.common.ai.ChatManager; |
| | | import tech.aiflowy.common.ai.MySseEmitter; |
| | | import tech.aiflowy.common.domain.Result; |
| | | import tech.aiflowy.common.util.StringUtil; |
| | | import tech.aiflowy.common.web.controller.BaseCurdController; |
| | | import tech.aiflowy.common.web.jsonbody.JsonBody; |
| | | import tech.aiflowy.common.satoken.util.SaTokenUtil; |
| | | import cn.hutool.core.util.ObjectUtil; |
| | | import com.agentsflex.core.llm.ChatContext; |
| | | import com.agentsflex.core.llm.Llm; |
| | |
| | | import com.agentsflex.core.llm.functions.Function; |
| | | import com.agentsflex.core.llm.response.AiMessageResponse; |
| | | import com.agentsflex.core.llm.response.FunctionCaller; |
| | | import com.agentsflex.core.message.AiMessage; |
| | | import com.agentsflex.core.message.HumanMessage; |
| | | import com.agentsflex.core.message.SystemMessage; |
| | | import com.agentsflex.core.prompt.HistoriesPrompt; |
| | | import com.agentsflex.core.prompt.ToolPrompt; |
| | | import com.agentsflex.core.util.CollectionUtil; |
| | | import com.alibaba.fastjson.JSON; |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.alibaba.fastjson.serializer.SerializeConfig; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.google.gson.Gson; |
| | | import com.google.gson.GsonBuilder; |
| | | import com.google.gson.JsonObject; |
| | | import com.mybatisflex.core.query.QueryWrapper; |
| | | import org.springframework.web.bind.annotation.PostMapping; |
| | | import org.springframework.web.bind.annotation.RequestMapping; |
| | | import org.springframework.web.bind.annotation.RestController; |
| | | import com.mybatisflex.core.table.TableInfo; |
| | | import com.mybatisflex.core.table.TableInfoFactory; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.http.*; |
| | | import org.springframework.web.bind.annotation.*; |
| | | import org.springframework.web.client.RestTemplate; |
| | | import org.springframework.web.context.request.RequestContextHolder; |
| | | import org.springframework.web.context.request.ServletRequestAttributes; |
| | | import org.springframework.web.multipart.MultipartFile; |
| | | import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; |
| | | import tech.aiflowy.ai.config.DifyStreamClient; |
| | | import tech.aiflowy.ai.config.FileReference; |
| | | import tech.aiflowy.ai.entity.*; |
| | | import tech.aiflowy.ai.mapper.AiBotConversationMessageMapper; |
| | | import tech.aiflowy.ai.service.*; |
| | | import tech.aiflowy.common.ai.ChatManager; |
| | | import tech.aiflowy.common.ai.MySseEmitter; |
| | | import tech.aiflowy.common.domain.Result; |
| | | import tech.aiflowy.common.entity.LoginAccount; |
| | | import tech.aiflowy.common.satoken.util.SaTokenUtil; |
| | | import tech.aiflowy.common.util.StringUtil; |
| | | import tech.aiflowy.common.web.controller.BaseCurdController; |
| | | import tech.aiflowy.common.web.jsonbody.JsonBody; |
| | | import tech.aiflowy.system.entity.SysApiKey; |
| | | import tech.aiflowy.system.mapper.SysApiKeyMapper; |
| | | |
| | | import javax.annotation.Resource; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.File; |
| | | import java.math.BigInteger; |
| | | import java.util.HashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.*; |
| | | |
| | | /** |
| | | * 控制层。 |
| | |
| | | private final AiBotKnowledgeService aiBotKnowledgeService; |
| | | private final AiBotMessageService aiBotMessageService; |
| | | @Resource |
| | | private SysApiKeyMapper aiBotApiKeyMapper; |
| | | @Resource |
| | | private AiBotConversationMessageService aiBotConversationMessageService; |
| | | @Resource |
| | | private AiBotConversationMessageMapper aiBotConversationMessageMapper; |
| | | |
| | | private static final Logger logger = LoggerFactory.getLogger(AiBotController.class); |
| | | @Autowired |
| | | private RestTemplate restTemplate; |
| | | |
| | | public AiBotController(AiBotService service, AiLlmService aiLlmService, AiBotWorkflowService aiBotWorkflowService, AiBotKnowledgeService aiBotKnowledgeService, AiBotMessageService aiBotMessageService) { |
| | | super(service); |
| | | this.aiLlmService = aiLlmService; |
| | |
| | | } |
| | | |
| | | @Resource |
| | | private AiPluginsService aiPluginsService; |
| | | @Resource |
| | | private AiBotPluginsService aiBotPluginsService; |
| | | @Resource |
| | | private AiPluginToolService aiPluginToolService; |
| | | |
| | | @PostMapping("updateOptions") |
| | | public Result updateOptions(@JsonBody("id") BigInteger id, @JsonBody("options") Map<String, Object> options) { |
| | |
| | | |
| | | /** |
| | | * 当前系统用户调用对话 |
| | | * |
| | | * @param prompt |
| | | * @param botId |
| | | * @param sessionId |
| | |
| | | * @param response |
| | | * @return |
| | | */ |
| | | |
| | | @Autowired |
| | | private ObjectMapper objectMapper; |
| | | |
| | | // @PostMapping("chat") |
| | | // public SseEmitter chat2(@JsonBody(value = "prompt", required = true) String prompt, |
| | | // @JsonBody(value = "botId", required = true) BigInteger botId, |
| | | // @JsonBody(value = "sessionId", required = true) String sessionId, |
| | | // @JsonBody(value = "isExternalMsg") int isExternalMsg, |
| | | // @JsonBody("file") String file,//上传文件 |
| | | // HttpServletResponse response){ |
| | | // File file = |
| | | // } |
| | | |
| | | @PostMapping("chat") |
| | | public SseEmitter chat(@JsonBody(value = "prompt", required = true) String prompt, |
| | | @JsonBody(value = "botId", required = true) BigInteger botId, |
| | | @JsonBody(value = "sessionId", required = true) String sessionId, |
| | | @JsonBody(value = "isExternalMsg") int isExternalMsg, |
| | | @JsonBody(value = "files") List<String> files,//上传文件 |
| | | @JsonBody(value = "file") String file,//上传文件 |
| | | HttpServletResponse response) { |
| | | response.setContentType("text/event-stream"); |
| | | AiBot aiBot = service.getById(botId); |
| | | if (aiBot == null) { |
| | | return ChatManager.getInstance().sseEmitterForContent("机器人不存在"); |
| | | } |
| | | |
| | | Map<String, Object> llmOptions = aiBot.getLlmOptions(); |
| | | AiLlm aiLlm = aiLlmService.getById(aiBot.getLlmId()); |
| | | String systemPrompt = llmOptions != null ? (String) llmOptions.get("systemPrompt") : null; |
| | | |
| | | if (aiLlm == null) { |
| | | return ChatManager.getInstance().sseEmitterForContent("LLM不存在"); |
| | | } |
| | | if (StringUtil.hasText(aiBot.getModelAPI())){ |
| | | if (aiBot.getBotTypeId() == 2 || aiBot.getBotTypeId() == 3) { |
| | | |
| | | Llm llm = aiLlm.toLlm(); |
| | | String apiUrl = aiBot.getModelAPI()+"/workflows/run"; // 替换为实际API URL |
| | | String apiKey = aiBot.getModelKEY(); // 替换为实际API Key |
| | | |
| | | AiBotMessageMemory memory = new AiBotMessageMemory(botId, SaTokenUtil.getLoginAccount().getId(), |
| | | sessionId, isExternalMsg, aiBotMessageService, aiBotConversationMessageMapper, |
| | | aiBotConversationMessageService); |
| | | DifyStreamClient client = new DifyStreamClient(apiUrl, apiKey, aiBotMessageService); |
| | | // DifyStreamClient uploadClient = new DifyStreamClient(aiBot.getModelAPI()+"/files/upload", apiKey, aiBotMessageService); |
| | | |
| | | final HistoriesPrompt historiesPrompt = new HistoriesPrompt(); |
| | | historiesPrompt.setSystemMessage(SystemMessage.of((String) llmOptions.get("systemPrompt"))); |
| | | historiesPrompt.setMemory(memory); |
| | | |
| | | HumanMessage humanMessage = new HumanMessage(prompt); |
| | | |
| | | // 添加插件相关的function calling |
| | | appendPluginFunctions(botId, humanMessage); |
| | | |
| | | //添加工作流相关的 Function Calling |
| | | appendWorkflowFunctions(botId, humanMessage); |
| | | Map<String, Object> inputs = new HashMap<>(); |
| | | if(aiBot.getBotTypeId() == 3){ |
| | | // 2. 构建文件参数对象 |
| | | |
| | | //添加知识库相关的 Function Calling |
| | | appendKnowledgeFunctions(botId, humanMessage); |
| | | HttpHeaders headers = new HttpHeaders(); |
| | | headers.set("Authorization", apiKey); |
| | | |
| | | historiesPrompt.addMessage(humanMessage); |
| | | HttpEntity<Object> requestEntity = new HttpEntity<>(null, headers); |
| | | |
| | | MySseEmitter emitter = new MySseEmitter((long) (1000 * 60 * 2)); |
| | | ResponseEntity<String> parameters = restTemplate.exchange( |
| | | aiBot.getModelAPI()+"/parameters", |
| | | HttpMethod.GET, |
| | | requestEntity, // 请求体(GET 无请求体) |
| | | String.class // 响应结果类型 |
| | | ); |
| | | |
| | | final Boolean[] needClose = {true}; |
| | | if (humanMessage.getFunctions() != null && !humanMessage.getFunctions().isEmpty()) { |
| | | org.json.JSONObject jsonObj = new org.json.JSONObject(parameters.getBody()); |
| | | // 先获取 user_input_form 数组 |
| | | org.json.JSONArray userInputFormArr = jsonObj.getJSONArray("user_input_form"); |
| | | String variable = null; |
| | | String type = ""; |
| | | // 遍历数组,找到 file - list 结构 |
| | | for (int i = 0; i < userInputFormArr.length(); i++) { |
| | | org.json.JSONObject formItem = userInputFormArr.getJSONObject(i); |
| | | if (formItem.has("file-list")) { |
| | | org.json.JSONObject fileListObj = formItem.getJSONObject("file-list"); |
| | | // 提取 variable 参数 |
| | | variable = fileListObj.getString("variable"); |
| | | // 提取 allowed_file_types 参数(数组形式) |
| | | org.json.JSONArray allowedFileTypesArr = fileListObj.getJSONArray("allowed_file_types"); |
| | | System.out.println("variable: " + variable); |
| | | System.out.println("allowed_file_types: " + allowedFileTypesArr.toString()); |
| | | type = allowedFileTypesArr.toString().replace("[\"", "").replace("\"]", ""); |
| | | break; // 这里假设只有一个符合的 file - list,找到就退出循环,可根据实际情况调整 |
| | | } |
| | | } |
| | | |
| | | List fileList = new ArrayList<>(); |
| | | Map<String, Object> fileParam = new HashMap<>(); |
| | | for (String fileId : files) { |
| | | fileParam.put("transfer_method", "local_file"); |
| | | fileParam.put("upload_file_id", fileId); |
| | | fileParam.put("type", type); // 例如 "excel"、"pdf" 等 |
| | | fileList.add(fileParam); |
| | | } |
| | | // 3. 组装 inputs 参数 |
| | | inputs.put(variable, fileList); // 添加文件参数,variableName 如 "document" |
| | | } |
| | | else { |
| | | |
| | | HttpHeaders headers = new HttpHeaders(); |
| | | headers.set("Authorization", apiKey); |
| | | |
| | | Map<String, Object> fileParam = new HashMap<>(); |
| | | fileParam.put("transfer_method", "local_file"); |
| | | fileParam.put("upload_file_id", file); |
| | | HttpEntity<Object> requestEntity = new HttpEntity<>(null, headers); |
| | | |
| | | ResponseEntity<String> parameters = restTemplate.exchange( |
| | | aiBot.getModelAPI()+"/parameters", |
| | | HttpMethod.GET, |
| | | requestEntity, // 请求体(GET 无请求体) |
| | | String.class // 响应结果类型 |
| | | ); |
| | | // System.out.println("========================\n"+parameters.getBody()+"\n====================="); |
| | | String variable = null; |
| | | String type = ""; |
| | | try { |
| | | // 解析 JSON 字符串 |
| | | org.json.JSONObject configJson = new org.json.JSONObject(parameters.getBody()); |
| | | |
| | | // 获取 user_input_form 数组 |
| | | org.json.JSONArray userInputFormArray = configJson.getJSONArray("user_input_form"); |
| | | |
| | | // 遍历 user_input_form 数组中的每个元素 |
| | | for (int i = 0; i < userInputFormArray.length(); i++) { |
| | | org.json.JSONObject formElement = userInputFormArray.getJSONObject(i); |
| | | |
| | | // 检查是否包含 "file" 对象 |
| | | if (formElement.has("file")) { |
| | | org.json.JSONObject fileObject = formElement.getJSONObject("file"); |
| | | |
| | | // 提取 variable |
| | | variable = fileObject.getString("variable"); |
| | | |
| | | // 提取 allowed_file_types 数组 |
| | | org.json.JSONArray allowedFileTypesArray = fileObject.getJSONArray("allowed_file_types"); |
| | | |
| | | // 打印结果 |
| | | System.out.println("Variable: " + variable); |
| | | System.out.print("Document Types: "); |
| | | |
| | | for (int j = 0; j < allowedFileTypesArray.length(); j++) { |
| | | type = type + allowedFileTypesArray.getString(j); |
| | | System.out.print(allowedFileTypesArray.getString(j) + " "); |
| | | } |
| | | |
| | | System.out.println(); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | fileParam.put("type", type); // 例如 "excel"、"pdf" 等 |
| | | // 3. 组装 inputs 参数 |
| | | inputs.put(variable, fileParam); // 添加文件参数,variableName 如 "document" |
| | | |
| | | } |
| | | |
| | | AiBotMessageMemory memory = new AiBotMessageMemory(botId, SaTokenUtil.getLoginAccount().getId(), |
| | | sessionId, isExternalMsg, aiBotMessageService, aiBotConversationMessageMapper, |
| | | aiBotConversationMessageService); |
| | | |
| | | final HistoriesPrompt historiesPrompt = new HistoriesPrompt(); |
| | | if (systemPrompt != null) { |
| | | historiesPrompt.setSystemMessage(SystemMessage.of(systemPrompt)); |
| | | } |
| | | |
| | | historiesPrompt.setMemory(memory); |
| | | |
| | | HumanMessage humanMessage = new HumanMessage(prompt); |
| | | |
| | | // 添加插件相关的function calling |
| | | appendPluginToolFunction(botId, humanMessage); |
| | | |
| | | //添加工作流相关的 Function Calling |
| | | appendWorkflowFunctions(botId, humanMessage); |
| | | |
| | | //添加知识库相关的 Function Calling |
| | | appendKnowledgeFunctions(botId, humanMessage); |
| | | |
| | | historiesPrompt.addMessage(humanMessage); |
| | | |
| | | |
| | | final Boolean[] needClose = {true}; |
| | | |
| | | ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); |
| | | |
| | | |
| | | MySseEmitter emitter = new MySseEmitter(1000L * 60 * 2); // 2分钟超时 |
| | | |
| | | try { |
| | | String userId = SaTokenUtil.getLoginAccount().getId() + ""; |
| | | client.runWorkflow(inputs, prompt, userId, emitter, sessionId, botId); |
| | | } catch (Exception e) { |
| | | emitter.completeWithError(e); |
| | | } |
| | | // System.out.println(emitter.toString()); |
| | | |
| | | return emitter; |
| | | } |
| | | aiBot.setModelAPI(aiBot.getModelAPI()+"/chat-messages"); |
| | | String apiUrl = aiBot.getModelAPI(); // 替换为实际API URL |
| | | String apiKey = aiBot.getModelKEY(); // 替换为实际API Key |
| | | |
| | | DifyStreamClient client = new DifyStreamClient(apiUrl, apiKey, aiBotMessageService); |
| | | AiBotMessageMemory memory = new AiBotMessageMemory(botId, SaTokenUtil.getLoginAccount().getId(), |
| | | sessionId, isExternalMsg, aiBotMessageService, aiBotConversationMessageMapper, |
| | | aiBotConversationMessageService); |
| | | |
| | | final HistoriesPrompt historiesPrompt = new HistoriesPrompt(); |
| | | if (systemPrompt != null) { |
| | | historiesPrompt.setSystemMessage(SystemMessage.of(systemPrompt)); |
| | | } |
| | | |
| | | historiesPrompt.setMemory(memory); |
| | | |
| | | HumanMessage humanMessage = new HumanMessage(prompt); |
| | | |
| | | // 添加插件相关的function calling |
| | | appendPluginToolFunction(botId, humanMessage); |
| | | |
| | | //添加工作流相关的 Function Calling |
| | | appendWorkflowFunctions(botId, humanMessage); |
| | | |
| | | //添加知识库相关的 Function Calling |
| | | appendKnowledgeFunctions(botId, humanMessage); |
| | | |
| | | historiesPrompt.addMessage(humanMessage); |
| | | |
| | | |
| | | final Boolean[] needClose = {true}; |
| | | |
| | | ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); |
| | | |
| | | |
| | | MySseEmitter emitter = new MySseEmitter(1000L * 60 * 2); // 2分钟超时 |
| | | |
| | | try { |
| | | AiMessageResponse aiMessageResponse = llm.chat(historiesPrompt); |
| | | function_call(aiMessageResponse, emitter, needClose, historiesPrompt, llm, prompt, false); |
| | | String userId = SaTokenUtil.getLoginAccount().getId() + ""; |
| | | client.chatStream(prompt, userId, emitter, sessionId, botId); |
| | | } catch (Exception e) { |
| | | emitter.completeWithError(e); |
| | | } |
| | | // System.out.println(emitter.toString()); |
| | | return emitter; |
| | | } |
| | | else{ |
| | | AiLlm aiLlm = aiLlmService.getById(aiBot.getLlmId()); |
| | | |
| | | if (needClose[0]) { |
| | | System.out.println("function chat complete"); |
| | | emitter.complete(); |
| | | if (aiLlm == null) { |
| | | return ChatManager.getInstance().sseEmitterForContent("LLM不存在"); |
| | | } |
| | | } else { |
| | | |
| | | Llm llm = aiLlm.toLlm(); |
| | | if (llm == null) { |
| | | return ChatManager.getInstance().sseEmitterForContent("LLM获取为空"); |
| | | } |
| | | |
| | | AiBotMessageMemory memory = new AiBotMessageMemory(botId, SaTokenUtil.getLoginAccount().getId(), |
| | | sessionId, isExternalMsg, aiBotMessageService, aiBotConversationMessageMapper, |
| | | aiBotConversationMessageService); |
| | | |
| | | final HistoriesPrompt historiesPrompt = new HistoriesPrompt(); |
| | | if (systemPrompt != null) { |
| | | historiesPrompt.setSystemMessage(SystemMessage.of(systemPrompt)); |
| | | } |
| | | |
| | | historiesPrompt.setMemory(memory); |
| | | |
| | | HumanMessage humanMessage = new HumanMessage(prompt); |
| | | |
| | | // 添加插件相关的function calling |
| | | appendPluginToolFunction(botId, humanMessage); |
| | | |
| | | //添加工作流相关的 Function Calling |
| | | appendWorkflowFunctions(botId, humanMessage); |
| | | |
| | | //添加知识库相关的 Function Calling |
| | | appendKnowledgeFunctions(botId, humanMessage); |
| | | |
| | | historiesPrompt.addMessage(humanMessage); |
| | | |
| | | MySseEmitter emitter = new MySseEmitter((long) (1000 * 60 * 2)); |
| | | |
| | | final Boolean[] needClose = {true}; |
| | | |
| | | ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); |
| | | // 统一使用流式处理,无论是否有 Function Calling |
| | | llm.chatStream(historiesPrompt, new StreamResponseListener() { |
| | | @Override |
| | | public void onMessage(ChatContext context, AiMessageResponse response) { |
| | | try { |
| | | RequestContextHolder.setRequestAttributes(sra, true); |
| | | if (response != null) { |
| | | // 检查是否需要触发 Function Calling |
| | | if (response.getFunctionCallers() != null && CollectionUtil.hasItems(response.getFunctionCallers())) { |
| | | needClose[0] = false; |
| | | function_call(response, emitter, needClose, historiesPrompt, llm, prompt, false); |
| | | } else { |
| | | // 强制流式返回,即使有 Function Calling 也先返回部分结果 |
| | | if (response.getMessage() != null) { |
| | | String content = response.getMessage().getContent(); |
| | | if (StringUtil.hasText(content)) { |
| | | System.out.println(response); |
| | | emitter.send(JSON.toJSONString(response.getMessage())); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | function_call(response, emitter, needClose, historiesPrompt, llm, prompt, false); |
| | | |
| | | } catch (Exception e) { |
| | | emitter.completeWithError(e); |
| | | } |
| | |
| | | @Override |
| | | public void onStop(ChatContext context) { |
| | | if (needClose[0]) { |
| | | System.out.println("normal chat complete"); |
| | | emitter.complete(); |
| | | } |
| | | } |
| | |
| | | emitter.completeWithError(throwable); |
| | | } |
| | | }); |
| | | |
| | | System.out.println(emitter.toString()); |
| | | return emitter; |
| | | } |
| | | |
| | | return emitter; |
| | | } |
| | | |
| | | @PostMapping("files/upload") |
| | | public Result filesUpload(@RequestParam("botId") BigInteger botId, |
| | | @RequestParam("file") MultipartFile file, |
| | | HttpServletResponse response){ |
| | | try{ |
| | | String userId = SaTokenUtil.getLoginAccount().getId() + ""; |
| | | response.setContentType("text/event-stream"); |
| | | AiBot aiBot = service.getById(botId); |
| | | aiBot.setModelAPI(aiBot.getModelAPI()+"/files/upload"); |
| | | String apiUrl = aiBot.getModelAPI(); // 替换为实际API URL |
| | | String apiKey = aiBot.getModelKEY(); // 替换为实际API Key |
| | | DifyStreamClient client = new DifyStreamClient(apiUrl, apiKey, aiBotMessageService); |
| | | String s = client.fileUpload(userId, file); |
| | | return Result.success(s); |
| | | }catch (Exception e){ |
| | | return Result.fail(400,String.valueOf(e)); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | public Result save(@RequestBody String jsonStr) { |
| | | // 解析JSON |
| | | JSONObject json = JSONObject.parseObject(jsonStr); |
| | | |
| | | // 合并所有secondMenuId*字段 |
| | | List<Integer> menuIds = new ArrayList<>(); |
| | | for (String key : json.keySet()) { |
| | | if (key.startsWith("secondMenuId")) { |
| | | Object value = json.get(key); |
| | | if (value instanceof Integer) { |
| | | menuIds.add((Integer) value); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 保留第一个ID(根据需要调整) |
| | | if (!menuIds.isEmpty()) { |
| | | json.put("secondMenuId", menuIds.get(0)); |
| | | } |
| | | |
| | | // 转换为实体类 |
| | | AiBot entity = json.toJavaObject(AiBot.class); |
| | | |
| | | // 后续处理保持不变 |
| | | Result result = onSaveOrUpdateBefore(entity, true); |
| | | if (result != null) return result; |
| | | |
| | | if (entity == null) { |
| | | throw new NullPointerException("entity is null"); |
| | | } |
| | | LoginAccount loginAccount = SaTokenUtil.getLoginAccount(); |
| | | commonFiled(entity,loginAccount.getId(),loginAccount.getTenantId(),loginAccount.getDeptId()); |
| | | boolean success = service.save(entity); |
| | | onSaveOrUpdateAfter(entity, true); |
| | | TableInfo tableInfo = TableInfoFactory.ofEntityClass(entity.getClass()); |
| | | Object[] pkArgs = tableInfo.buildPkSqlArgs(entity); |
| | | return Result.create(success).set("id", pkArgs); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * 外部用户调用智能体进行对话 |
| | | * 需要用户传 apiKey 对用户进行身份验证 |
| | | * |
| | | * @param stream [true: 返回sse false: 返回json |
| | | * @return |
| | | */ |
| | | @SaIgnore |
| | | @PostMapping("externalChat") |
| | | public SseEmitter externalChat( |
| | | public Object externalChat( |
| | | @JsonBody(value = "messages", required = true) List<AiBotMessage> messages, |
| | | @JsonBody(value = "botId", required = true ) BigInteger botId, |
| | | @JsonBody(value = "botId", required = true) BigInteger botId, |
| | | @JsonBody(value = "stream", required = false) boolean stream, |
| | | HttpServletResponse response, |
| | | HttpServletRequest request |
| | | ){ |
| | | ) { |
| | | // 设置响应类型 |
| | | if (stream) { |
| | | response.setContentType("text/event-stream"); |
| | | } else { |
| | | response.setContentType("application/json"); |
| | | } |
| | | |
| | | response.setContentType("text/event-stream"); |
| | | request.getAuthType(); |
| | | // 获取 API Key 和 Bot 信息 |
| | | String apiKey = request.getHeader("Authorization"); |
| | | QueryWrapper queryWrapper = QueryWrapper.create() |
| | | .select("api_key", "status", "expired_at") |
| | | .from("tb_sys_api_key") |
| | | .where("api_key = ? ", apiKey); |
| | | SysApiKey aiBotApiKey = aiBotApiKeyMapper.selectOneByQuery(queryWrapper); |
| | | if (aiBotApiKey == null) { |
| | | return createResponse(stream, JSON.toJSONString(errorRespnseMsg(1, "该apiKey不存在"))); |
| | | } |
| | | if (aiBotApiKey.getStatus() == 0) { |
| | | return createResponse(stream, JSON.toJSONString(errorRespnseMsg(2, "该apiKey未启用"))); |
| | | } |
| | | |
| | | if (aiBotApiKey.getExpiredAt().getTime() < new Date().getTime()) { |
| | | return createResponse(stream, JSON.toJSONString(errorRespnseMsg(3, "该apiKey已失效"))); |
| | | |
| | | } |
| | | |
| | | AiBot aiBot = service.getById(botId); |
| | | if (aiBot == null) { |
| | | return ChatManager.getInstance().sseEmitterForContent("机器人不存在"); |
| | | return createResponse(stream, JSON.toJSONString(errorRespnseMsg(4, "机器人不存在"))); |
| | | } |
| | | |
| | | Map<String, Object> llmOptions = aiBot.getLlmOptions(); |
| | | AiLlm aiLlm = aiLlmService.getById(aiBot.getLlmId()); |
| | | String systemPrompt = llmOptions != null ? (String) llmOptions.get("systemPrompt") : null; |
| | | |
| | | AiLlm aiLlm = aiLlmService.getById(aiBot.getLlmId()); |
| | | if (aiLlm == null) { |
| | | return ChatManager.getInstance().sseEmitterForContent("LLM不存在"); |
| | | return createResponse(stream, JSON.toJSONString(errorRespnseMsg(5, "LLM不存在"))); |
| | | } |
| | | |
| | | Llm llm = aiLlm.toLlm(); |
| | | AiBotExternalMessageMemory messageMemory = new AiBotExternalMessageMemory(messages); |
| | | final HistoriesPrompt historiesPrompt = new HistoriesPrompt(); |
| | | historiesPrompt.setSystemMessage(SystemMessage.of((String) llmOptions.get("systemPrompt"))); |
| | | HistoriesPrompt historiesPrompt = new HistoriesPrompt(); |
| | | if (systemPrompt != null) { |
| | | historiesPrompt.setSystemMessage(SystemMessage.of(systemPrompt)); |
| | | } |
| | | historiesPrompt.setMemory(messageMemory); |
| | | |
| | | String prompt = messages.get(messages.size() - 1).getContent(); |
| | | HumanMessage humanMessage = new HumanMessage(); |
| | | |
| | | // 添加插件相关的function calling |
| | | appendPluginFunctions(botId, humanMessage); |
| | | |
| | | //添加工作流相关的 Function Calling |
| | | // 添加插件、工作流、知识库相关的 Function Calling |
| | | appendPluginToolFunction(botId, humanMessage); |
| | | appendWorkflowFunctions(botId, humanMessage); |
| | | |
| | | //添加知识库相关的 Function Calling |
| | | appendKnowledgeFunctions(botId, humanMessage); |
| | | final HistoriesPrompt historiesPrompts = new HistoriesPrompt(); |
| | | |
| | | historiesPrompts.addMessage(humanMessage); |
| | | historiesPrompt.addMessage(humanMessage); |
| | | |
| | | MySseEmitter emitter = new MySseEmitter((long) (1000 * 60 * 2)); |
| | | // 根据 responseType 返回不同的响应 |
| | | if (stream) { |
| | | MySseEmitter emitter = new MySseEmitter((long) (1000 * 60 * 2)); |
| | | final Boolean[] needClose = {true}; |
| | | |
| | | final Boolean[] needClose = {true}; |
| | | if (humanMessage.getFunctions() != null && !humanMessage.getFunctions().isEmpty()) { |
| | | try { |
| | | AiMessageResponse aiMessageResponse = llm.chat(historiesPrompts); |
| | | function_call(aiMessageResponse, emitter, needClose, historiesPrompt, llm, prompt, true); |
| | | } catch (Exception e) { |
| | | emitter.completeWithError(e); |
| | | if (humanMessage.getFunctions() != null && !humanMessage.getFunctions().isEmpty()) { |
| | | try { |
| | | AiMessageResponse aiMessageResponse = llm.chat(historiesPrompt); |
| | | function_call(aiMessageResponse, emitter, needClose, historiesPrompt, llm, prompt, true); |
| | | } catch (Exception e) { |
| | | emitter.completeWithError(e); |
| | | } |
| | | |
| | | if (needClose[0]) { |
| | | System.out.println("function chat complete"); |
| | | emitter.complete(); |
| | | } |
| | | } else { |
| | | llm.chatStream(historiesPrompt, new StreamResponseListener() { |
| | | @Override |
| | | public void onMessage(ChatContext context, AiMessageResponse response) { |
| | | try { |
| | | function_call(response, emitter, needClose, historiesPrompt, llm, prompt, true); |
| | | } catch (Exception e) { |
| | | emitter.completeWithError(e); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onStop(ChatContext context) { |
| | | |
| | | if (needClose[0]) { |
| | | System.out.println("normal chat complete"); |
| | | emitter.complete(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onFailure(ChatContext context, Throwable throwable) { |
| | | emitter.completeWithError(throwable); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | if (needClose[0]) { |
| | | System.out.println("function chat complete"); |
| | | emitter.complete(); |
| | | } |
| | | return emitter; |
| | | } else { |
| | | |
| | | llm.chatStream(historiesPrompt, new StreamResponseListener() { |
| | | @Override |
| | | public void onMessage(ChatContext context, AiMessageResponse response) { |
| | | try { |
| | | |
| | | function_call(response, emitter, needClose, historiesPrompt, llm, prompt, true); |
| | | } catch (Exception e) { |
| | | emitter.completeWithError(e); |
| | | } |
| | | AiMessageResponse resultFunctionCall; |
| | | if (humanMessage.getFunctions() != null && !humanMessage.getFunctions().isEmpty()) { |
| | | try { |
| | | AiMessageResponse aiMessageResponse = llm.chat(historiesPrompt); |
| | | resultFunctionCall = jsonResultJsonFunctionCall(aiMessageResponse, historiesPrompt, llm, prompt); |
| | | return JSON.toJSONString(resultFunctionCall.getMessage(), new SerializeConfig()); |
| | | } catch (Exception e) { |
| | | return createErrorResponse(e); |
| | | } |
| | | |
| | | @Override |
| | | public void onStop(ChatContext context) { |
| | | if (needClose[0]) { |
| | | System.out.println("normal chat complete"); |
| | | emitter.complete(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onFailure(ChatContext context, Throwable throwable) { |
| | | emitter.completeWithError(throwable); |
| | | } |
| | | }); |
| | | } else { |
| | | AiMessageResponse messageResponse = llm.chat(historiesPrompt); |
| | | resultFunctionCall = jsonResultJsonFunctionCall(messageResponse, historiesPrompt, llm, prompt); |
| | | AiBotExternalMsgJsonResult result = handleMessageResult(resultFunctionCall.getMessage()); |
| | | return JSON.toJSONString(result, new SerializeConfig()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return emitter; |
| | | private AiBotExternalMsgJsonResult handleMessageResult(AiMessage aiMessage) { |
| | | AiBotExternalMsgJsonResult messageResult = new AiBotExternalMsgJsonResult(); |
| | | messageResult.setCreated(new Date().getTime()); |
| | | AiBotExternalMsgJsonResult.Usage usage = new AiBotExternalMsgJsonResult.Usage(); |
| | | if (aiMessage.getTotalTokens() != null){ |
| | | usage.setTotalTokens(aiMessage.getTotalTokens()); |
| | | } |
| | | if (aiMessage.getCompletionTokens() != null){ |
| | | usage.setCompletionTokens(aiMessage.getCompletionTokens()); |
| | | } |
| | | if (aiMessage.getPromptTokens() != null){ |
| | | usage.setPromptTokens(aiMessage.getPromptTokens()); |
| | | } |
| | | messageResult.setUsage(usage); |
| | | AiBotExternalMsgJsonResult.Choice choice = new AiBotExternalMsgJsonResult.Choice(); |
| | | AiBotExternalMsgJsonResult.Message message = new AiBotExternalMsgJsonResult.Message(); |
| | | message.setContent(aiMessage.getContent()); |
| | | message.setRole("assistant"); |
| | | choice.setMessage(message); |
| | | messageResult.setChoices(choice); |
| | | messageResult.setStatus(aiMessage.getStatus().name()); |
| | | return messageResult; |
| | | } |
| | | |
| | | // 辅助方法:创建响应 |
| | | private Object createResponse(boolean stream, String content) { |
| | | if (stream) { |
| | | MySseEmitter emitter = new MySseEmitter((long) (1000 * 60 * 2)); |
| | | emitter.send(content); |
| | | emitter.complete(); |
| | | return emitter; |
| | | } else { |
| | | return ResponseEntity.ok(content); |
| | | } |
| | | } |
| | | |
| | | // 辅助方法:创建错误响应 |
| | | private Object createErrorResponse(Exception e) { |
| | | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage()); |
| | | } |
| | | |
| | | /** |
| | | * |
| | | * @param aiMessageResponse |
| | | * @param aiMessageResponse 大模型返回的消息 |
| | | * @param emitter |
| | | * @param needClose |
| | | * @param historiesPrompt |
| | | * @param llm |
| | | * @param prompt |
| | | * @param isChatApi 该参数用于分辨外部地址用户通过apiKey的方式传参,不涉及外部调用的默认 false |
| | | * @param needClose 是否需要关闭流 |
| | | * @param historiesPrompt 消息历史记录 |
| | | * @param llm 大模型 |
| | | * @param prompt 提示词 |
| | | * @param isExternalChatApi true 外部系统调用bot false 内部系统调用bot |
| | | */ |
| | | private void function_call(AiMessageResponse aiMessageResponse, MySseEmitter emitter, Boolean[] needClose, HistoriesPrompt historiesPrompt, Llm llm, String prompt, boolean isChatApi) { |
| | | private String function_call(AiMessageResponse aiMessageResponse, MySseEmitter emitter, Boolean[] needClose, HistoriesPrompt historiesPrompt, Llm llm, String prompt, boolean isExternalChatApi) { |
| | | ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); |
| | | RequestContextHolder.setRequestAttributes(sra, true); |
| | | String content = aiMessageResponse.getMessage().getContent(); |
| | | Object messageContent = aiMessageResponse.getMessage(); |
| | | if (StringUtil.hasText(content)) { |
| | | String jsonResult; |
| | | if (isChatApi){ |
| | | // 这里如果是需要外部用户使用apiKey调用返回结果的时候需要对返回的结果进行处理,设置我们自定义的数据给用户,该结果还未确定,待测试 |
| | | jsonResult = JSON.toJSONString(messageContent); |
| | | emitter.send(jsonResult); |
| | | // 如果是外部系统调用chat |
| | | if (isExternalChatApi) { |
| | | AiBotExternalMsgJsonResult result = handleMessageStreamJsonResult(aiMessageResponse.getMessage()); |
| | | |
| | | emitter.send(JSON.toJSONString(result, new SerializeConfig())); |
| | | } else { |
| | | jsonResult = JSON.toJSONString(messageContent); |
| | | emitter.send(jsonResult); |
| | | emitter.send(JSON.toJSONString(messageContent)); |
| | | } |
| | | |
| | | } |
| | | System.out.println("function call 接收到的参数message:" + aiMessageResponse); |
| | | llm.chatStream(ToolPrompt.of(aiMessageResponse), new StreamResponseListener() { |
| | | @Override |
| | | public void onMessage(ChatContext context, AiMessageResponse response) { |
| | | System.out.println("function call <UNK>message<UNK>" + aiMessageResponse); |
| | | String content = response.getMessage().getContent(); |
| | | if (StringUtil.hasText(content)) { |
| | | System.out.println("if content" + content); |
| | | emitter.send(JSON.toJSONString(response.getMessage())); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onStop(ChatContext context) { |
| | | System.out.println("function call complete"); |
| | | emitter.complete(); |
| | | } |
| | | |
| | | @Override |
| | | public void onFailure(ChatContext context, Throwable throwable) { |
| | | logger.error("function_call报错:",throwable); |
| | | AiMessage aiMessage = new AiMessage(); |
| | | aiMessage.setContent("未查询到相关信息..."); |
| | | emitter.send(JSON.toJSONString(aiMessage)); |
| | | System.out.println("function call complete with error"); |
| | | } |
| | | }); |
| | | |
| | | return JSON.toJSONString(messageContent); |
| | | } |
| | | |
| | | private Map<String, Object> errorRespnseMsg(int errorCode, String message) { |
| | | HashMap<String, Object> result = new HashMap<>(); |
| | | result.put("error", errorCode); |
| | | result.put("message", message); |
| | | return result; |
| | | } |
| | | |
| | | private AiBotExternalMsgJsonResult handleMessageStreamJsonResult(AiMessage message) { |
| | | AiBotExternalMsgJsonResult result = new AiBotExternalMsgJsonResult(); |
| | | AiBotExternalMsgJsonResult.Choice choice = new AiBotExternalMsgJsonResult.Choice(); |
| | | AiBotExternalMsgJsonResult.Delta delta = new AiBotExternalMsgJsonResult.Delta(); |
| | | delta.setRole("assistant"); |
| | | delta.setContent(message.getContent()); |
| | | choice.setDelta(delta); |
| | | result.setCreated(new Date().getTime()); |
| | | result.setChoices(choice); |
| | | result.setStatus(message.getStatus().name()); |
| | | |
| | | return result; |
| | | } |
| | | |
| | | private AiMessageResponse jsonResultJsonFunctionCall(AiMessageResponse aiMessageResponse, HistoriesPrompt historiesPrompt, Llm llm, String prompt) { |
| | | List<FunctionCaller> functionCallers = aiMessageResponse.getFunctionCallers(); |
| | | if (CollectionUtil.hasItems(functionCallers)) { |
| | | needClose[0] = false; |
| | | for (FunctionCaller functionCaller : functionCallers) { |
| | | Object result = functionCaller.call(); |
| | | if (ObjectUtil.isNotEmpty(result)) { |
| | | |
| | | String newPrompt = "请根据以下内容回答用户,内容是:\n" + result + "\n 用户的问题是:" + prompt; |
| | | historiesPrompt.addMessageTemporary(new HumanMessage(newPrompt)); |
| | | |
| | | llm.chatStream(historiesPrompt, new StreamResponseListener() { |
| | | @Override |
| | | public void onMessage(ChatContext context, AiMessageResponse response) { |
| | | needClose[0] = true; |
| | | String content = response.getMessage().getContent(); |
| | | Object messageContent = response.getMessage(); |
| | | if (StringUtil.hasText(content)) { |
| | | String jsonResult = JSON.toJSONString(messageContent); |
| | | emitter.send(jsonResult); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public void onStop(ChatContext context) { |
| | | if (needClose[0]) { |
| | | System.out.println("function chat complete"); |
| | | emitter.complete(); |
| | | } |
| | | historiesPrompt.clearTemporaryMessages(); |
| | | } |
| | | |
| | | @Override |
| | | public void onFailure(ChatContext context, Throwable throwable) { |
| | | emitter.completeWithError(throwable); |
| | | } |
| | | }); |
| | | return llm.chat(historiesPrompt); |
| | | } |
| | | } |
| | | } |
| | | return aiMessageResponse; |
| | | } |
| | | |
| | | private void appendWorkflowFunctions(BigInteger botId, HumanMessage humanMessage) { |
| | |
| | | } |
| | | } |
| | | |
| | | private void appendPluginFunctions(BigInteger botId, HumanMessage humanMessage) { |
| | | QueryWrapper queryWrapper = QueryWrapper.create().eq(AiBotPlugins::getBotId, botId); |
| | | List<AiBotPlugins> aiBotPlugins = aiBotPluginsService.getMapper().selectListWithRelationsByQuery(queryWrapper); |
| | | if (cn.hutool.core.collection.CollectionUtil.isNotEmpty(aiBotPlugins)) { |
| | | for (AiBotPlugins aiBotPlugin : aiBotPlugins) { |
| | | Function function = aiBotPlugin.getAiPlugins().toFunction(); |
| | | humanMessage.addFunction(function); |
| | | } |
| | | // private void appendPluginFunctions(BigInteger botId, HumanMessage humanMessage) { |
| | | // QueryWrapper queryWrapper = QueryWrapper.create().eq(AiBotPlugins::getBotId, botId); |
| | | // List<AiBotPlugins> aiBotPlugins = aiBotPluginsService.getMapper().selectListWithRelationsByQuery(queryWrapper); |
| | | // if (cn.hutool.core.collection.CollectionUtil.isNotEmpty(aiBotPlugins)) { |
| | | // for (AiBotPlugins aiBotPlugin : aiBotPlugins) { |
| | | // Function function = aiBotPlugin.getAiPlugins().toFunction(); |
| | | // humanMessage.addFunction(function); |
| | | // } |
| | | // } |
| | | // } |
| | | private void appendPluginToolFunction(BigInteger botId, HumanMessage humanMessage) { |
| | | QueryWrapper queryWrapper = QueryWrapper.create().select("plugin_tool_id").eq(AiBotPlugins::getBotId, botId); |
| | | List<BigInteger> pluginToolIds = aiBotPluginsService.getMapper().selectListWithRelationsByQueryAs(queryWrapper, BigInteger.class); |
| | | |
| | | if (pluginToolIds == null || pluginToolIds.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | QueryWrapper queryTool = QueryWrapper.create() |
| | | .select("*") |
| | | .from("tb_ai_plugin_tool") |
| | | .in("id", pluginToolIds); |
| | | List<AiPluginTool> aiPluginTools = aiPluginToolService.getMapper().selectListWithRelationsByQuery(queryTool); |
| | | for (AiPluginTool item : aiPluginTools) { |
| | | humanMessage.addFunction(item.toFunction()); |
| | | } |
| | | |
| | | |
| | | } |
| | | } |