浏览代码

增加与ai大模型对话

wwh 3 周之前
父节点
当前提交
8a9d793fee

+ 0 - 46
huimv-employment/fe-api/src/main/java/com/huimv/employment/controller/mp/AgentController.java

@@ -1,46 +0,0 @@
1
-package com.huimv.employment.controller.mp;
2
-
3
-import com.huimv.employment.common.web.R;
4
-import com.huimv.employment.security.LoginUser;
5
-import com.huimv.employment.security.LoginUserHolder;
6
-import com.huimv.employment.service.agent.AgentChatService;
7
-import com.huimv.employment.service.agent.dto.AgentMessageRequest;
8
-import com.huimv.employment.service.agent.dto.AgentMessageResponse;
9
-import io.swagger.v3.oas.annotations.Operation;
10
-import io.swagger.v3.oas.annotations.security.SecurityRequirement;
11
-import io.swagger.v3.oas.annotations.tags.Tag;
12
-import org.springframework.validation.annotation.Validated;
13
-import org.springframework.web.bind.annotation.PostMapping;
14
-import org.springframework.web.bind.annotation.RequestBody;
15
-import org.springframework.web.bind.annotation.RequestMapping;
16
-import org.springframework.web.bind.annotation.RestController;
17
-
18
-/**
19
- * 小程序智能体对话接口。
20
- * <p>
21
- * 封装大模型 Chat Completions 调用,返回统一 {@link R} 格式;底层复用 {@code fe-integration} 的 KB 代理。
22
- * </p>
23
- */
24
-@RestController
25
-@RequestMapping("/api/v1/mp/agent")
26
-@Tag(name = "智能体", description = "AI 对话,需 JWT 鉴权")
27
-@SecurityRequirement(name = "Authorization")
28
-public class AgentController {
29
-
30
-    private final AgentChatService agentChatService;
31
-
32
-    public AgentController(AgentChatService agentChatService) {
33
-        this.agentChatService = agentChatService;
34
-    }
35
-
36
-    @PostMapping("/messages")
37
-    @Operation(summary = "发送消息并获取 AI 回复",
38
-            description = "向智能体发送用户消息,返回 AI 回复文本及会话 ID。"
39
-                    + "conversationId 不传时服务端自动生成,后续多轮对话传入同一 ID 以保持上下文。")
40
-    public R<AgentMessageResponse> sendMessage(@Validated @RequestBody AgentMessageRequest request) {
41
-        LoginUser loginUser = LoginUserHolder.require();
42
-        AgentMessageResponse response = agentChatService.chat(
43
-                loginUser.getUserId(), loginUser.getUserType(), request);
44
-        return R.ok(response);
45
-    }
46
-}

+ 0 - 72
huimv-employment/fe-integration/src/main/java/com/huimv/employment/integration/kb/ConsoleChatSseParser.java

@@ -1,72 +0,0 @@
1
-package com.huimv.employment.integration.kb;
2
-
3
-import com.fasterxml.jackson.databind.JsonNode;
4
-import com.fasterxml.jackson.databind.ObjectMapper;
5
-import com.huimv.employment.common.exception.BizException;
6
-import com.huimv.employment.common.exception.ErrorCode;
7
-
8
-import java.io.BufferedReader;
9
-import java.io.IOException;
10
-import java.io.InputStream;
11
-import java.io.InputStreamReader;
12
-import java.nio.charset.StandardCharsets;
13
-
14
-/**
15
- * 解析 {@code POST /api/console/chat} 返回的 SSE 事件,提取 assistant 文本。
16
- */
17
-public final class ConsoleChatSseParser {
18
-
19
-    private ConsoleChatSseParser() {
20
-    }
21
-
22
-    public static String extractAssistantText(InputStream sseStream, ObjectMapper objectMapper) throws IOException {
23
-        if (sseStream == null) {
24
-            throw new BizException(ErrorCode.AI_SERVICE_FAILED, "大模型返回空响应");
25
-        }
26
-        StringBuilder full = new StringBuilder();
27
-        try (BufferedReader reader = new BufferedReader(new InputStreamReader(sseStream, StandardCharsets.UTF_8))) {
28
-            String line;
29
-            while ((line = reader.readLine()) != null) {
30
-                if (!line.startsWith("data: ")) {
31
-                    continue;
32
-                }
33
-                String payload = line.substring(6).trim();
34
-                if (payload.isEmpty() || "[DONE]".equals(payload)) {
35
-                    continue;
36
-                }
37
-                JsonNode data = objectMapper.readTree(payload);
38
-                JsonNode error = data.get("error");
39
-                if (error != null && !error.isNull()) {
40
-                    String message = error.path("message").asText("大模型返回错误");
41
-                    throw new BizException(ErrorCode.AI_SERVICE_FAILED, message);
42
-                }
43
-                JsonNode output = data.get("output");
44
-                if (output == null || !output.isArray()) {
45
-                    continue;
46
-                }
47
-                for (JsonNode item : output) {
48
-                    if (!"assistant".equals(item.path("role").asText())) {
49
-                        continue;
50
-                    }
51
-                    JsonNode contents = item.get("content");
52
-                    if (contents == null || !contents.isArray()) {
53
-                        continue;
54
-                    }
55
-                    for (JsonNode content : contents) {
56
-                        if ("text".equals(content.path("type").asText()) && content.has("text")) {
57
-                            full.append(content.get("text").asText(""));
58
-                        }
59
-                    }
60
-                }
61
-            }
62
-        } catch (BizException ex) {
63
-            throw ex;
64
-        } catch (Exception ex) {
65
-            throw new BizException(ErrorCode.AI_SERVICE_FAILED, "解析大模型 SSE 响应失败");
66
-        }
67
-        if (full.length() == 0) {
68
-            throw new BizException(ErrorCode.AI_SERVICE_FAILED, "大模型未返回有效内容");
69
-        }
70
-        return full.toString();
71
-    }
72
-}

+ 1 - 1
huimv-employment/fe-integration/src/main/java/com/huimv/employment/integration/kb/KbApiProperties.java

@@ -31,7 +31,7 @@ public class KbApiProperties {
31 31
     /** 方式一上传默认知识库标识 */
32 32
     private String defaultKnowledgeBase = "default";
33 33
 
34
-    /** agent/messages 等非透传场景默认模型 */
34
+    /** 默认模型标识(预留) */
35 35
     private String defaultModel = "default";
36 36
 
37 37
     /** 上游 {@code X-Agent-Id} 默认值(见智能体接口文档) */

+ 0 - 2
huimv-employment/fe-integration/src/main/java/com/huimv/employment/integration/kb/KbOpenAiProxyService.java

@@ -13,6 +13,4 @@ public interface KbOpenAiProxyService {
13 13
     ResponseEntity<byte[]> getVersion();
14 14
 
15 15
     void streamConsoleChat(byte[] requestBody, String agentId, OutputStream output) throws IOException;
16
-
17
-    String collectConsoleChatReply(byte[] requestBody, String agentId);
18 16
 }

+ 1 - 61
huimv-employment/fe-integration/src/main/java/com/huimv/employment/integration/kb/KbOpenAiProxyServiceImpl.java

@@ -33,14 +33,11 @@ public class KbOpenAiProxyServiceImpl implements KbOpenAiProxyService {
33 33
 
34 34
     private final KbApiProperties properties;
35 35
     private final RestTemplate restTemplate;
36
-    private final com.fasterxml.jackson.databind.ObjectMapper objectMapper;
37 36
 
38 37
     public KbOpenAiProxyServiceImpl(KbApiProperties properties,
39
-                                    @Qualifier("kbRestTemplate") RestTemplate restTemplate,
40
-                                    com.fasterxml.jackson.databind.ObjectMapper objectMapper) {
38
+                                    @Qualifier("kbRestTemplate") RestTemplate restTemplate) {
41 39
         this.properties = properties;
42 40
         this.restTemplate = restTemplate;
43
-        this.objectMapper = objectMapper;
44 41
     }
45 42
 
46 43
     @Override
@@ -96,45 +93,6 @@ public class KbOpenAiProxyServiceImpl implements KbOpenAiProxyService {
96 93
         }
97 94
     }
98 95
 
99
-    @Override
100
-    public String collectConsoleChatReply(byte[] requestBody, String agentId) {
101
-        ensureEnabled();
102
-        validateRequestBody(requestBody);
103
-        HttpURLConnection conn = null;
104
-        InputStream input = null;
105
-        try {
106
-            conn = openUpstreamConnection(requestBody.length);
107
-            conn.setRequestProperty(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
108
-            conn.setRequestProperty(HttpHeaders.ACCEPT, MediaType.TEXT_EVENT_STREAM_VALUE);
109
-            applyUpstreamAuth(conn, agentId);
110
-            conn.setDoOutput(true);
111
-            try (OutputStream upstreamOut = conn.getOutputStream()) {
112
-                upstreamOut.write(requestBody);
113
-            }
114
-            int status = conn.getResponseCode();
115
-            input = status >= 400 ? conn.getErrorStream() : conn.getInputStream();
116
-            if (status >= 400) {
117
-                throw upstreamHttpError(status, input);
118
-            }
119
-            return ConsoleChatSseParser.extractAssistantText(input, objectMapper);
120
-        } catch (BizException ex) {
121
-            throw ex;
122
-        } catch (Exception ex) {
123
-            throw new BizException(ErrorCode.AI_SERVICE_FAILED, "调用大模型失败");
124
-        } finally {
125
-            if (input != null) {
126
-                try {
127
-                    input.close();
128
-                } catch (IOException ignored) {
129
-                    // ignore
130
-                }
131
-            }
132
-            if (conn != null) {
133
-                conn.disconnect();
134
-            }
135
-        }
136
-    }
137
-
138 96
     private HttpURLConnection openUpstreamConnection(int contentLength) throws IOException {
139 97
         URL url = new URL(properties.baseUrl() + PATH_CHAT);
140 98
         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
@@ -166,24 +124,6 @@ public class KbOpenAiProxyServiceImpl implements KbOpenAiProxyService {
166 124
         }
167 125
     }
168 126
 
169
-    private BizException upstreamHttpError(int status, InputStream input) throws IOException {
170
-        if (input != null) {
171
-            byte[] errBytes = StreamUtils.copyToByteArray(input);
172
-            if (errBytes.length > 0) {
173
-                String errText = new String(errBytes, StandardCharsets.UTF_8).trim();
174
-                if (status == 401) {
175
-                    String hint = properties.hasUpstreamAuth()
176
-                            ? "大模型鉴权失败,请检查 fe.kb.api-key"
177
-                            : "大模型鉴权失败";
178
-                    return new BizException(ErrorCode.AI_SERVICE_FAILED, hint);
179
-                }
180
-                return new BizException(ErrorCode.AI_SERVICE_FAILED,
181
-                        errText.length() > 200 ? errText.substring(0, 200) : errText);
182
-            }
183
-        }
184
-        return new BizException(ErrorCode.AI_SERVICE_FAILED, "上游返回 HTTP " + status);
185
-    }
186
-
187 127
     private String resolveAgentId(String agentId) {
188 128
         if (StringUtils.hasText(agentId)) {
189 129
             return agentId.trim();

+ 0 - 73
huimv-employment/fe-service/src/main/java/com/huimv/employment/service/agent/AgentChatService.java

@@ -1,73 +0,0 @@
1
-package com.huimv.employment.service.agent;
2
-
3
-import com.fasterxml.jackson.databind.ObjectMapper;
4
-import com.fasterxml.jackson.databind.node.ArrayNode;
5
-import com.fasterxml.jackson.databind.node.ObjectNode;
6
-import com.huimv.employment.common.exception.BizException;
7
-import com.huimv.employment.common.exception.ErrorCode;
8
-import com.huimv.employment.integration.kb.KbApiProperties;
9
-import com.huimv.employment.integration.kb.KbOpenAiProxyService;
10
-import com.huimv.employment.service.agent.dto.AgentMessageRequest;
11
-import com.huimv.employment.service.agent.dto.AgentMessageResponse;
12
-import org.springframework.stereotype.Service;
13
-import org.springframework.util.StringUtils;
14
-
15
-import java.util.UUID;
16
-
17
-/**
18
- * 小程序智能体对话:封装 {@code POST /api/console/chat} 调用。
19
- */
20
-@Service
21
-public class AgentChatService {
22
-
23
-    private static final String SYSTEM_PROMPT =
24
-            "你是灵活用工智能助手,帮助企业主解答用工规范、成本测算、登记流程等问题。回答应简洁、专业、可操作。";
25
-
26
-    private final KbOpenAiProxyService kbOpenAiProxyService;
27
-    private final KbApiProperties kbApiProperties;
28
-    private final ObjectMapper objectMapper;
29
-
30
-    public AgentChatService(KbOpenAiProxyService kbOpenAiProxyService,
31
-                            KbApiProperties kbApiProperties,
32
-                            ObjectMapper objectMapper) {
33
-        this.kbOpenAiProxyService = kbOpenAiProxyService;
34
-        this.kbApiProperties = kbApiProperties;
35
-        this.objectMapper = objectMapper;
36
-    }
37
-
38
-    public AgentMessageResponse chat(Long userId, String userType, AgentMessageRequest request) {
39
-        String conversationId = StringUtils.hasText(request.getConversationId())
40
-                ? request.getConversationId()
41
-                : UUID.randomUUID().toString().replace("-", "");
42
-
43
-        byte[] body = buildChatRequest(userId, conversationId, request.getMessage());
44
-        String reply = kbOpenAiProxyService.collectConsoleChatReply(body, kbApiProperties.getDefaultAgentId());
45
-
46
-        AgentMessageResponse response = new AgentMessageResponse();
47
-        response.setReply(reply);
48
-        response.setConversationId(conversationId);
49
-        return response;
50
-    }
51
-
52
-    private byte[] buildChatRequest(Long userId, String sessionId, String userMessage) {
53
-        try {
54
-            ObjectNode root = objectMapper.createObjectNode();
55
-
56
-            ArrayNode input = root.putArray("input");
57
-            ObjectNode userMsg = input.addObject();
58
-            userMsg.put("role", "user");
59
-            ArrayNode userContent = userMsg.putArray("content");
60
-            ObjectNode userText = userContent.addObject();
61
-            userText.put("type", "text");
62
-            userText.put("text", SYSTEM_PROMPT + "\n\n" + userMessage);
63
-
64
-            root.put("session_id", sessionId);
65
-            root.put("user_id", String.valueOf(userId));
66
-            root.put("channel", "console");
67
-
68
-            return objectMapper.writeValueAsBytes(root);
69
-        } catch (Exception ex) {
70
-            throw new BizException(ErrorCode.INTERNAL_ERROR);
71
-        }
72
-    }
73
-}

+ 0 - 43
huimv-employment/fe-service/src/main/java/com/huimv/employment/service/agent/dto/AgentMessageRequest.java

@@ -1,43 +0,0 @@
1
-package com.huimv.employment.service.agent.dto;
2
-
3
-import io.swagger.v3.oas.annotations.media.Schema;
4
-
5
-import javax.validation.constraints.NotBlank;
6
-
7
-@Schema(description = "智能体对话请求")
8
-public class AgentMessageRequest {
9
-
10
-    @Schema(description = "企业 ID(可选,预留多企业场景)", example = "1")
11
-    private Long enterpriseId;
12
-
13
-    @Schema(description = "会话 ID,不传则自动生成", example = "conv-abc123")
14
-    private String conversationId;
15
-
16
-    @Schema(description = "用户消息内容", example = "帮我起草一份临时工用工协议", requiredMode = Schema.RequiredMode.REQUIRED)
17
-    @NotBlank(message = "消息内容不能为空")
18
-    private String message;
19
-
20
-    public Long getEnterpriseId() {
21
-        return enterpriseId;
22
-    }
23
-
24
-    public void setEnterpriseId(Long enterpriseId) {
25
-        this.enterpriseId = enterpriseId;
26
-    }
27
-
28
-    public String getConversationId() {
29
-        return conversationId;
30
-    }
31
-
32
-    public void setConversationId(String conversationId) {
33
-        this.conversationId = conversationId;
34
-    }
35
-
36
-    public String getMessage() {
37
-        return message;
38
-    }
39
-
40
-    public void setMessage(String message) {
41
-        this.message = message;
42
-    }
43
-}

+ 0 - 51
huimv-employment/fe-service/src/main/java/com/huimv/employment/service/agent/dto/AgentMessageResponse.java

@@ -1,51 +0,0 @@
1
-package com.huimv.employment.service.agent.dto;
2
-
3
-import io.swagger.v3.oas.annotations.media.Schema;
4
-
5
-@Schema(description = "智能体对话响应")
6
-public class AgentMessageResponse {
7
-
8
-    @Schema(description = "AI 回复文本", example = "好的,请提供用工岗位、工时和薪资等信息。")
9
-    private String reply;
10
-
11
-    @Schema(description = "会话 ID", example = "conv-abc123")
12
-    private String conversationId;
13
-
14
-    @Schema(description = "下一步动作(如 show_employment_draft),一期可为空")
15
-    private String nextAction;
16
-
17
-    @Schema(description = "关联草稿 ID,一期可为空")
18
-    private String draftId;
19
-
20
-    public String getReply() {
21
-        return reply;
22
-    }
23
-
24
-    public void setReply(String reply) {
25
-        this.reply = reply;
26
-    }
27
-
28
-    public String getConversationId() {
29
-        return conversationId;
30
-    }
31
-
32
-    public void setConversationId(String conversationId) {
33
-        this.conversationId = conversationId;
34
-    }
35
-
36
-    public String getNextAction() {
37
-        return nextAction;
38
-    }
39
-
40
-    public void setNextAction(String nextAction) {
41
-        this.nextAction = nextAction;
42
-    }
43
-
44
-    public String getDraftId() {
45
-        return draftId;
46
-    }
47
-
48
-    public void setDraftId(String draftId) {
49
-        this.draftId = draftId;
50
-    }
51
-}