|
|
@@ -0,0 +1,104 @@
|
|
|
+package com.fs.ai.rag.controller;
|
|
|
+
|
|
|
+import cn.hutool.http.HttpRequest;
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.alibaba.fastjson.JSONArray;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.fs.common.core.domain.R;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.web.bind.annotation.PostMapping;
|
|
|
+import org.springframework.web.bind.annotation.RequestBody;
|
|
|
+import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
+import org.springframework.web.bind.annotation.RestController;
|
|
|
+
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@RestController
|
|
|
+@RequestMapping("/ai/llm")
|
|
|
+public class LlmController {
|
|
|
+
|
|
|
+ @Value("${ai.rag.llm-url}")
|
|
|
+ private String llmUrl;
|
|
|
+
|
|
|
+ @Value("${ai.rag.llm-api-key:}")
|
|
|
+ private String apiKey;
|
|
|
+
|
|
|
+ @Value("${ai.rag.llm-model}")
|
|
|
+ private String model;
|
|
|
+
|
|
|
+ @PostMapping("/chat")
|
|
|
+ public R chat(@RequestBody Map<String, Object> request) {
|
|
|
+ String modelName = (String) request.getOrDefault("model", model);
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ List<Map<String, String>> messages = (List<Map<String, String>>) request.get("messages");
|
|
|
+ double temperature = request.get("temperature") != null
|
|
|
+ ? ((Number) request.get("temperature")).doubleValue() : 0.7;
|
|
|
+ int maxTokens = request.get("max_tokens") != null
|
|
|
+ ? ((Number) request.get("max_tokens")).intValue() : 2048;
|
|
|
+
|
|
|
+ if (messages == null || messages.isEmpty()) {
|
|
|
+ return R.error().put("msg", "messages 不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建 OpenAI 兼容请求体
|
|
|
+ JSONObject reqBody = new JSONObject();
|
|
|
+ reqBody.put("model", modelName);
|
|
|
+
|
|
|
+ JSONArray msgArray = new JSONArray();
|
|
|
+ for (Map<String, String> msg : messages) {
|
|
|
+ JSONObject msgObj = new JSONObject();
|
|
|
+ msgObj.put("role", msg.get("role"));
|
|
|
+ msgObj.put("content", msg.get("content"));
|
|
|
+ msgArray.add(msgObj);
|
|
|
+ }
|
|
|
+ reqBody.put("messages", msgArray);
|
|
|
+ reqBody.put("temperature", temperature);
|
|
|
+ reqBody.put("max_tokens", maxTokens);
|
|
|
+
|
|
|
+ log.info("LLM 请求: model={}, messages.size={}", modelName, messages.size());
|
|
|
+
|
|
|
+ try {
|
|
|
+ String result = HttpRequest.post(llmUrl)
|
|
|
+ .header("Authorization", "Bearer " + apiKey)
|
|
|
+ .header("Content-Type", "application/json")
|
|
|
+ .body(reqBody.toJSONString())
|
|
|
+ .timeout(60000)
|
|
|
+ .execute()
|
|
|
+ .body();
|
|
|
+
|
|
|
+ JSONObject resp = JSON.parseObject(result);
|
|
|
+
|
|
|
+ // 检查是否有错误
|
|
|
+ if (resp.containsKey("error")) {
|
|
|
+ log.error("LLM 返回错误: {}", resp);
|
|
|
+ return R.error().put("msg", resp.getJSONObject("error").getString("message"));
|
|
|
+ }
|
|
|
+
|
|
|
+ JSONArray choices = resp.getJSONArray("choices");
|
|
|
+ if (choices == null || choices.isEmpty()) {
|
|
|
+ return R.error().put("msg", "LLM 返回为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ String content = choices.getJSONObject(0)
|
|
|
+ .getJSONObject("message")
|
|
|
+ .getString("content");
|
|
|
+
|
|
|
+ JSONObject data = new JSONObject();
|
|
|
+ data.put("content", content);
|
|
|
+ data.put("model", resp.getString("model"));
|
|
|
+ data.put("usage", resp.getJSONObject("usage"));
|
|
|
+
|
|
|
+ log.info("LLM 回复成功: content.length={}, usage={}",
|
|
|
+ content != null ? content.length() : 0, resp.getJSONObject("usage"));
|
|
|
+
|
|
|
+ return R.ok().put("data", data);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("LLM 调用异常: {}", e.getMessage(), e);
|
|
|
+ return R.error().put("msg", "LLM 调用失败: " + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|