|
|
@@ -4,6 +4,9 @@ import com.fasterxml.jackson.databind.JsonNode;
|
|
4
|
4
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
5
|
5
|
import com.huimv.employment.common.exception.BizException;
|
|
6
|
6
|
import com.huimv.employment.common.exception.ErrorCode;
|
|
|
7
|
+import com.huimv.employment.integration.kb.rag.RagFileListQuery;
|
|
|
8
|
+import com.huimv.employment.integration.kb.rag.RagInsertTextRequest;
|
|
|
9
|
+import com.huimv.employment.integration.kb.rag.RagRetrieveRequest;
|
|
7
|
10
|
import org.slf4j.Logger;
|
|
8
|
11
|
import org.slf4j.LoggerFactory;
|
|
9
|
12
|
import org.springframework.beans.factory.annotation.Qualifier;
|
|
|
@@ -11,6 +14,7 @@ import org.springframework.core.io.FileSystemResource;
|
|
11
|
14
|
import org.springframework.http.HttpEntity;
|
|
12
|
15
|
import org.springframework.http.HttpHeaders;
|
|
13
|
16
|
import org.springframework.http.HttpMethod;
|
|
|
17
|
+import org.springframework.http.MediaType;
|
|
14
|
18
|
import org.springframework.http.ResponseEntity;
|
|
15
|
19
|
import org.springframework.stereotype.Service;
|
|
16
|
20
|
import org.springframework.util.LinkedMultiValueMap;
|
|
|
@@ -19,6 +23,7 @@ import org.springframework.util.StringUtils;
|
|
19
|
23
|
import org.springframework.web.client.HttpStatusCodeException;
|
|
20
|
24
|
import org.springframework.web.client.ResourceAccessException;
|
|
21
|
25
|
import org.springframework.web.client.RestTemplate;
|
|
|
26
|
+import org.springframework.web.util.UriComponentsBuilder;
|
|
22
|
27
|
|
|
23
|
28
|
import java.io.File;
|
|
24
|
29
|
import java.io.UnsupportedEncodingException;
|
|
|
@@ -28,14 +33,18 @@ import java.nio.charset.StandardCharsets;
|
|
28
|
33
|
import java.util.function.Supplier;
|
|
29
|
34
|
|
|
30
|
35
|
/**
|
|
31
|
|
- * 调用大模型知识库开放 API(multipart 上传 / 删除文件等)。
|
|
|
36
|
+ * RAG 知识库 {@code /api/rag/**} HTTP 客户端实现。
|
|
32
|
37
|
*/
|
|
33
|
38
|
@Service
|
|
34
|
39
|
public class KnowledgeBaseClientImpl implements KnowledgeBaseClient {
|
|
35
|
40
|
|
|
36
|
41
|
private static final Logger log = LoggerFactory.getLogger(KnowledgeBaseClientImpl.class);
|
|
37
|
42
|
|
|
38
|
|
- private static final String[] FILE_ID_KEYS = { "file_id", "fileId", "kbDocId", "kb_doc_id", "docId", "id" };
|
|
|
43
|
+ private static final String RAG_PREFIX = "/api/rag";
|
|
|
44
|
+
|
|
|
45
|
+ private static final String[] TRACK_ID_KEYS = {
|
|
|
46
|
+ "track_id", "trackId", "file_id", "fileId", "kbDocId", "kb_doc_id", "docId", "id"
|
|
|
47
|
+ };
|
|
39
|
48
|
|
|
40
|
49
|
private final KbApiProperties properties;
|
|
41
|
50
|
private final RestTemplate restTemplate;
|
|
|
@@ -50,51 +59,188 @@ public class KnowledgeBaseClientImpl implements KnowledgeBaseClient {
|
|
50
|
59
|
}
|
|
51
|
60
|
|
|
52
|
61
|
@Override
|
|
53
|
|
- public String uploadFile(File file, String uploadFilename, String knowledgeBase, String category) {
|
|
|
62
|
+ public JsonNode health() {
|
|
|
63
|
+ ensureReady();
|
|
|
64
|
+ return getJson("/health");
|
|
|
65
|
+ }
|
|
|
66
|
+
|
|
|
67
|
+ @Override
|
|
|
68
|
+ public String uploadFile(File file, String uploadFilename) {
|
|
54
|
69
|
ensureReady();
|
|
55
|
70
|
if (file == null || !file.isFile()) {
|
|
56
|
71
|
throw new BizException(ErrorCode.BAD_REQUEST, "上传文件无效或不存在");
|
|
57
|
72
|
}
|
|
58
|
|
- String kb = StringUtils.hasText(knowledgeBase) ? knowledgeBase.trim() : properties.getDefaultKnowledgeBase();
|
|
59
|
|
- if (!StringUtils.hasText(kb)) {
|
|
60
|
|
- kb = "default";
|
|
61
|
|
- }
|
|
62
|
73
|
String name = StringUtils.hasText(uploadFilename) ? uploadFilename.trim() : file.getName();
|
|
|
74
|
+ MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
|
|
75
|
+ body.add("file", multipartFileResource(file, name));
|
|
|
76
|
+ return parseTrackId(postMultipart("/files", body));
|
|
|
77
|
+ }
|
|
63
|
78
|
|
|
|
79
|
+ @Override
|
|
|
80
|
+ public String updateFile(String fileId, File file, String uploadFilename) {
|
|
|
81
|
+ ensureReady();
|
|
|
82
|
+ requireFileId(fileId);
|
|
|
83
|
+ if (file == null || !file.isFile()) {
|
|
|
84
|
+ throw new BizException(ErrorCode.BAD_REQUEST, "上传文件无效或不存在");
|
|
|
85
|
+ }
|
|
|
86
|
+ String name = StringUtils.hasText(uploadFilename) ? uploadFilename.trim() : file.getName();
|
|
64
|
87
|
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
|
65
|
|
- body.add("knowledge_base", kb);
|
|
66
|
|
- body.add("category", category);
|
|
67
|
88
|
body.add("file", multipartFileResource(file, name));
|
|
|
89
|
+ return parseTrackId(putMultipart("/files/" + encodePathSegment(fileId), body));
|
|
|
90
|
+ }
|
|
68
|
91
|
|
|
69
|
|
- return postMultipart(body);
|
|
|
92
|
+ @Override
|
|
|
93
|
+ public JsonNode listFiles(RagFileListQuery query) {
|
|
|
94
|
+ ensureReady();
|
|
|
95
|
+ UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(ragUrl("/files"));
|
|
|
96
|
+ if (query != null) {
|
|
|
97
|
+ if (query.getPage() != null) {
|
|
|
98
|
+ builder.queryParam("page", query.getPage());
|
|
|
99
|
+ }
|
|
|
100
|
+ if (query.getPageSize() != null) {
|
|
|
101
|
+ builder.queryParam("page_size", query.getPageSize());
|
|
|
102
|
+ }
|
|
|
103
|
+ if (StringUtils.hasText(query.getSortField())) {
|
|
|
104
|
+ builder.queryParam("sort_field", query.getSortField());
|
|
|
105
|
+ }
|
|
|
106
|
+ if (StringUtils.hasText(query.getSortDirection())) {
|
|
|
107
|
+ builder.queryParam("sort_direction", query.getSortDirection());
|
|
|
108
|
+ }
|
|
|
109
|
+ }
|
|
|
110
|
+ return getJsonUri(builder.build(true).toUri());
|
|
70
|
111
|
}
|
|
71
|
112
|
|
|
72
|
113
|
@Override
|
|
73
|
|
- public String uploadFileByUrl(String fileUrl, String category, String description) {
|
|
|
114
|
+ public JsonNode getFileStatus(String fileId) {
|
|
74
|
115
|
ensureReady();
|
|
75
|
|
- MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
|
|
76
|
|
- body.add("url", fileUrl);
|
|
77
|
|
- body.add("category", category);
|
|
78
|
|
- body.add("description", description);
|
|
79
|
|
- return postMultipart(body);
|
|
|
116
|
+ requireFileId(fileId);
|
|
|
117
|
+ return getJson("/files/" + encodePathSegment(fileId));
|
|
80
|
118
|
}
|
|
81
|
119
|
|
|
82
|
|
- private String postMultipart(MultiValueMap<String, Object> body) {
|
|
83
|
|
- String url = properties.baseUrl() + "/api/v1/kb/files";
|
|
84
|
|
- HttpHeaders headers = new HttpHeaders();
|
|
85
|
|
- properties.applyAuthHeader(headers);
|
|
86
|
|
- HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
|
|
87
|
|
- String raw = executeWithRetry("知识库上传", () -> {
|
|
|
120
|
+ @Override
|
|
|
121
|
+ public void deleteFile(String fileId) {
|
|
|
122
|
+ ensureReady();
|
|
|
123
|
+ requireFileId(fileId);
|
|
|
124
|
+ String path = "/files/" + encodePathSegment(fileId);
|
|
|
125
|
+ executeWithRetry("RAG 删除文档", () -> {
|
|
88
|
126
|
try {
|
|
89
|
|
- ResponseEntity<String> resp = restTemplate.exchange(URI.create(url), HttpMethod.POST, entity, String.class);
|
|
|
127
|
+ HttpEntity<Void> entity = new HttpEntity<>(authHeaders());
|
|
|
128
|
+ ResponseEntity<String> resp = restTemplate.exchange(
|
|
|
129
|
+ URI.create(ragUrl(path)), HttpMethod.DELETE, entity, String.class);
|
|
|
130
|
+ if (!resp.getStatusCode().is2xxSuccessful()) {
|
|
|
131
|
+ throw new BizException(ErrorCode.KB_SERVICE_FAILED,
|
|
|
132
|
+ "RAG 删除失败 HTTP " + resp.getStatusCode().value());
|
|
|
133
|
+ }
|
|
|
134
|
+ return null;
|
|
|
135
|
+ } catch (HttpStatusCodeException e) {
|
|
|
136
|
+ if (e.getRawStatusCode() == 404) {
|
|
|
137
|
+ return null;
|
|
|
138
|
+ }
|
|
|
139
|
+ throw httpError("RAG 删除失败", e);
|
|
|
140
|
+ }
|
|
|
141
|
+ });
|
|
|
142
|
+ }
|
|
|
143
|
+
|
|
|
144
|
+ @Override
|
|
|
145
|
+ public JsonNode retrieve(RagRetrieveRequest request) {
|
|
|
146
|
+ ensureReady();
|
|
|
147
|
+ if (request == null || !StringUtils.hasText(request.getQuery())) {
|
|
|
148
|
+ throw new BizException(ErrorCode.BAD_REQUEST, "检索 query 不能为空");
|
|
|
149
|
+ }
|
|
|
150
|
+ return postJson("/retrieve", request);
|
|
|
151
|
+ }
|
|
|
152
|
+
|
|
|
153
|
+ @Override
|
|
|
154
|
+ public String insertText(String text, String fileSource) {
|
|
|
155
|
+ ensureReady();
|
|
|
156
|
+ if (!StringUtils.hasText(text)) {
|
|
|
157
|
+ throw new BizException(ErrorCode.BAD_REQUEST, "text 不能为空");
|
|
|
158
|
+ }
|
|
|
159
|
+ if (!StringUtils.hasText(fileSource)) {
|
|
|
160
|
+ throw new BizException(ErrorCode.BAD_REQUEST, "file_source 不能为空");
|
|
|
161
|
+ }
|
|
|
162
|
+ RagInsertTextRequest body = new RagInsertTextRequest(text.trim(), fileSource.trim());
|
|
|
163
|
+ return parseTrackId(postJsonRaw("/insert-text", body));
|
|
|
164
|
+ }
|
|
|
165
|
+
|
|
|
166
|
+ @Override
|
|
|
167
|
+ public JsonNode reprocessFailed() {
|
|
|
168
|
+ ensureReady();
|
|
|
169
|
+ return postJson("/reprocess-failed", new Object());
|
|
|
170
|
+ }
|
|
|
171
|
+
|
|
|
172
|
+ private JsonNode getJson(String path) {
|
|
|
173
|
+ return getJsonUri(URI.create(ragUrl(path)));
|
|
|
174
|
+ }
|
|
|
175
|
+
|
|
|
176
|
+ private JsonNode getJsonUri(URI uri) {
|
|
|
177
|
+ String raw = executeWithRetry("RAG GET " + uri.getPath(), () -> {
|
|
|
178
|
+ try {
|
|
|
179
|
+ HttpEntity<Void> entity = new HttpEntity<>(authHeaders());
|
|
|
180
|
+ ResponseEntity<String> resp = restTemplate.exchange(uri, HttpMethod.GET, entity, String.class);
|
|
|
181
|
+ return resp.getBody();
|
|
|
182
|
+ } catch (HttpStatusCodeException e) {
|
|
|
183
|
+ throw httpError("RAG 请求失败", e);
|
|
|
184
|
+ }
|
|
|
185
|
+ });
|
|
|
186
|
+ return parseJsonNode(raw);
|
|
|
187
|
+ }
|
|
|
188
|
+
|
|
|
189
|
+ private JsonNode postJson(String path, Object body) {
|
|
|
190
|
+ return parseJsonNode(postJsonRaw(path, body));
|
|
|
191
|
+ }
|
|
|
192
|
+
|
|
|
193
|
+ private String postJsonRaw(String path, Object body) {
|
|
|
194
|
+ return executeWithRetry("RAG POST " + path, () -> {
|
|
|
195
|
+ try {
|
|
|
196
|
+ HttpHeaders headers = authHeaders();
|
|
|
197
|
+ headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
198
|
+ HttpEntity<Object> entity = new HttpEntity<>(body, headers);
|
|
|
199
|
+ ResponseEntity<String> resp = restTemplate.exchange(
|
|
|
200
|
+ URI.create(ragUrl(path)), HttpMethod.POST, entity, String.class);
|
|
90
|
201
|
return resp.getBody();
|
|
91
|
202
|
} catch (HttpStatusCodeException e) {
|
|
92
|
|
- String b = e.getResponseBodyAsString(StandardCharsets.UTF_8);
|
|
93
|
|
- throw new BizException(ErrorCode.KB_SERVICE_FAILED,
|
|
94
|
|
- "知识库上传失败 HTTP " + e.getRawStatusCode() + ":" + abbreviate(b));
|
|
|
203
|
+ throw httpError("RAG 请求失败", e);
|
|
95
|
204
|
}
|
|
96
|
205
|
});
|
|
97
|
|
- return parseUploadResponse(raw);
|
|
|
206
|
+ }
|
|
|
207
|
+
|
|
|
208
|
+ private String postMultipart(String path, MultiValueMap<String, Object> body) {
|
|
|
209
|
+ return executeWithRetry("RAG 上传", () -> {
|
|
|
210
|
+ try {
|
|
|
211
|
+ HttpHeaders headers = authHeaders();
|
|
|
212
|
+ HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
|
|
|
213
|
+ ResponseEntity<String> resp = restTemplate.exchange(
|
|
|
214
|
+ URI.create(ragUrl(path)), HttpMethod.POST, entity, String.class);
|
|
|
215
|
+ return resp.getBody();
|
|
|
216
|
+ } catch (HttpStatusCodeException e) {
|
|
|
217
|
+ throw httpError("RAG 上传失败", e);
|
|
|
218
|
+ }
|
|
|
219
|
+ });
|
|
|
220
|
+ }
|
|
|
221
|
+
|
|
|
222
|
+ private String putMultipart(String path, MultiValueMap<String, Object> body) {
|
|
|
223
|
+ return executeWithRetry("RAG 更新文件", () -> {
|
|
|
224
|
+ try {
|
|
|
225
|
+ HttpHeaders headers = authHeaders();
|
|
|
226
|
+ HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(body, headers);
|
|
|
227
|
+ ResponseEntity<String> resp = restTemplate.exchange(
|
|
|
228
|
+ URI.create(ragUrl(path)), HttpMethod.PUT, entity, String.class);
|
|
|
229
|
+ return resp.getBody();
|
|
|
230
|
+ } catch (HttpStatusCodeException e) {
|
|
|
231
|
+ throw httpError("RAG 更新失败", e);
|
|
|
232
|
+ }
|
|
|
233
|
+ });
|
|
|
234
|
+ }
|
|
|
235
|
+
|
|
|
236
|
+ private HttpHeaders authHeaders() {
|
|
|
237
|
+ HttpHeaders headers = new HttpHeaders();
|
|
|
238
|
+ properties.applyAuthHeader(headers);
|
|
|
239
|
+ return headers;
|
|
|
240
|
+ }
|
|
|
241
|
+
|
|
|
242
|
+ private String ragUrl(String path) {
|
|
|
243
|
+ return properties.baseUrl() + RAG_PREFIX + path;
|
|
98
|
244
|
}
|
|
99
|
245
|
|
|
100
|
246
|
private static FileSystemResource multipartFileResource(File file, String uploadName) {
|
|
|
@@ -106,33 +252,95 @@ public class KnowledgeBaseClientImpl implements KnowledgeBaseClient {
|
|
106
|
252
|
};
|
|
107
|
253
|
}
|
|
108
|
254
|
|
|
109
|
|
- @Override
|
|
110
|
|
- public void deleteFile(String kbDocId) {
|
|
111
|
|
- ensureReady();
|
|
112
|
|
- if (!StringUtils.hasText(kbDocId)) {
|
|
113
|
|
- throw new BizException(ErrorCode.BAD_REQUEST, "知识库文档 id 为空");
|
|
|
255
|
+ private static void requireFileId(String fileId) {
|
|
|
256
|
+ if (!StringUtils.hasText(fileId)) {
|
|
|
257
|
+ throw new BizException(ErrorCode.BAD_REQUEST, "file_id 不能为空");
|
|
114
|
258
|
}
|
|
115
|
|
- String url = properties.baseUrl() + "/api/v1/kb/files/" + encodePathSegment(kbDocId);
|
|
116
|
|
- HttpHeaders headers = new HttpHeaders();
|
|
117
|
|
- properties.applyAuthHeader(headers);
|
|
118
|
|
- HttpEntity<Void> entity = new HttpEntity<>(headers);
|
|
119
|
|
- executeWithRetry("知识库删除", () -> {
|
|
120
|
|
- try {
|
|
121
|
|
- ResponseEntity<String> resp = restTemplate.exchange(URI.create(url), HttpMethod.DELETE, entity, String.class);
|
|
122
|
|
- if (!resp.getStatusCode().is2xxSuccessful()) {
|
|
123
|
|
- throw new BizException(ErrorCode.KB_SERVICE_FAILED,
|
|
124
|
|
- "知识库删除失败 HTTP " + resp.getStatusCode().value());
|
|
|
259
|
+ }
|
|
|
260
|
+
|
|
|
261
|
+ private JsonNode parseJsonNode(String raw) {
|
|
|
262
|
+ if (!StringUtils.hasText(raw)) {
|
|
|
263
|
+ throw new BizException(ErrorCode.KB_SERVICE_FAILED, "RAG 返回空响应");
|
|
|
264
|
+ }
|
|
|
265
|
+ try {
|
|
|
266
|
+ JsonNode root = objectMapper.readTree(raw);
|
|
|
267
|
+ assertBusinessOk(root);
|
|
|
268
|
+ return root;
|
|
|
269
|
+ } catch (BizException ex) {
|
|
|
270
|
+ throw ex;
|
|
|
271
|
+ } catch (Exception ex) {
|
|
|
272
|
+ throw new BizException(ErrorCode.KB_SERVICE_FAILED, "RAG 响应非 JSON:" + abbreviate(raw));
|
|
|
273
|
+ }
|
|
|
274
|
+ }
|
|
|
275
|
+
|
|
|
276
|
+ private String parseTrackId(String raw) {
|
|
|
277
|
+ JsonNode root = parseJsonNode(raw);
|
|
|
278
|
+ String trackId = extractTrackId(root);
|
|
|
279
|
+ if (!StringUtils.hasText(trackId)) {
|
|
|
280
|
+ throw new BizException(ErrorCode.KB_SERVICE_FAILED,
|
|
|
281
|
+ "RAG 成功但未解析到 track_id,响应:" + abbreviate(raw));
|
|
|
282
|
+ }
|
|
|
283
|
+ return trackId;
|
|
|
284
|
+ }
|
|
|
285
|
+
|
|
|
286
|
+ private void assertBusinessOk(JsonNode root) {
|
|
|
287
|
+ if (root == null || !root.has("code")) {
|
|
|
288
|
+ return;
|
|
|
289
|
+ }
|
|
|
290
|
+ JsonNode c = root.get("code");
|
|
|
291
|
+ boolean ok = false;
|
|
|
292
|
+ if (c.isNumber()) {
|
|
|
293
|
+ int v = c.intValue();
|
|
|
294
|
+ ok = (v == 0 || v == 200);
|
|
|
295
|
+ } else if (c.isTextual()) {
|
|
|
296
|
+ String s = c.asText();
|
|
|
297
|
+ ok = "0".equals(s) || "200".equals(s) || "success".equalsIgnoreCase(s);
|
|
|
298
|
+ }
|
|
|
299
|
+ if (!ok) {
|
|
|
300
|
+ String msg = root.has("message") ? root.get("message").asText(null) : null;
|
|
|
301
|
+ if (!StringUtils.hasText(msg) && root.has("msg")) {
|
|
|
302
|
+ msg = root.get("msg").asText(null);
|
|
|
303
|
+ }
|
|
|
304
|
+ if (!StringUtils.hasText(msg)) {
|
|
|
305
|
+ msg = "RAG 接口返回失败";
|
|
|
306
|
+ }
|
|
|
307
|
+ throw new BizException(ErrorCode.KB_SERVICE_FAILED, msg);
|
|
|
308
|
+ }
|
|
|
309
|
+ }
|
|
|
310
|
+
|
|
|
311
|
+ private static String extractTrackId(JsonNode root) {
|
|
|
312
|
+ JsonNode data = root.get("data");
|
|
|
313
|
+ if (data != null && data.isTextual()) {
|
|
|
314
|
+ String t = data.asText(null);
|
|
|
315
|
+ if (StringUtils.hasText(t)) {
|
|
|
316
|
+ return t;
|
|
|
317
|
+ }
|
|
|
318
|
+ }
|
|
|
319
|
+ if (data != null && data.isObject()) {
|
|
|
320
|
+ for (String key : TRACK_ID_KEYS) {
|
|
|
321
|
+ if (data.has(key) && !data.get(key).isNull()) {
|
|
|
322
|
+ String v = data.get(key).asText(null);
|
|
|
323
|
+ if (StringUtils.hasText(v)) {
|
|
|
324
|
+ return v;
|
|
|
325
|
+ }
|
|
125
|
326
|
}
|
|
126
|
|
- return null;
|
|
127
|
|
- } catch (HttpStatusCodeException e) {
|
|
128
|
|
- if (e.getRawStatusCode() == 404) {
|
|
129
|
|
- return null;
|
|
|
327
|
+ }
|
|
|
328
|
+ }
|
|
|
329
|
+ for (String key : TRACK_ID_KEYS) {
|
|
|
330
|
+ if (root.has(key) && !root.get(key).isNull()) {
|
|
|
331
|
+ String v = root.get(key).asText(null);
|
|
|
332
|
+ if (StringUtils.hasText(v)) {
|
|
|
333
|
+ return v;
|
|
130
|
334
|
}
|
|
131
|
|
- String b = e.getResponseBodyAsString(StandardCharsets.UTF_8);
|
|
132
|
|
- throw new BizException(ErrorCode.KB_SERVICE_FAILED,
|
|
133
|
|
- "知识库删除失败 HTTP " + e.getRawStatusCode() + ":" + abbreviate(b));
|
|
134
|
335
|
}
|
|
135
|
|
- });
|
|
|
336
|
+ }
|
|
|
337
|
+ return null;
|
|
|
338
|
+ }
|
|
|
339
|
+
|
|
|
340
|
+ private BizException httpError(String prefix, HttpStatusCodeException e) {
|
|
|
341
|
+ String b = e.getResponseBodyAsString(StandardCharsets.UTF_8);
|
|
|
342
|
+ return new BizException(ErrorCode.KB_SERVICE_FAILED,
|
|
|
343
|
+ prefix + " HTTP " + e.getRawStatusCode() + ":" + abbreviate(b));
|
|
136
|
344
|
}
|
|
137
|
345
|
|
|
138
|
346
|
private <T> T executeWithRetry(String operation, Supplier<T> action) {
|
|
|
@@ -236,79 +444,6 @@ public class KnowledgeBaseClientImpl implements KnowledgeBaseClient {
|
|
236
|
444
|
}
|
|
237
|
445
|
}
|
|
238
|
446
|
|
|
239
|
|
- private String parseUploadResponse(String raw) {
|
|
240
|
|
- if (!StringUtils.hasText(raw)) {
|
|
241
|
|
- throw new BizException(ErrorCode.KB_SERVICE_FAILED, "知识库上传返回空响应");
|
|
242
|
|
- }
|
|
243
|
|
- JsonNode root;
|
|
244
|
|
- try {
|
|
245
|
|
- root = objectMapper.readTree(raw);
|
|
246
|
|
- } catch (Exception e) {
|
|
247
|
|
- throw new BizException(ErrorCode.KB_SERVICE_FAILED, "知识库上传响应非 JSON:" + abbreviate(raw));
|
|
248
|
|
- }
|
|
249
|
|
- assertBusinessOk(root);
|
|
250
|
|
- String fileId = extractFileId(root);
|
|
251
|
|
- if (!StringUtils.hasText(fileId)) {
|
|
252
|
|
- throw new BizException(ErrorCode.KB_SERVICE_FAILED,
|
|
253
|
|
- "知识库上传成功但未解析到 file_id,响应:" + abbreviate(raw));
|
|
254
|
|
- }
|
|
255
|
|
- return fileId;
|
|
256
|
|
- }
|
|
257
|
|
-
|
|
258
|
|
- private void assertBusinessOk(JsonNode root) {
|
|
259
|
|
- if (root == null || !root.has("code")) {
|
|
260
|
|
- return;
|
|
261
|
|
- }
|
|
262
|
|
- JsonNode c = root.get("code");
|
|
263
|
|
- boolean ok = false;
|
|
264
|
|
- if (c.isNumber()) {
|
|
265
|
|
- int v = c.intValue();
|
|
266
|
|
- ok = (v == 0 || v == 200);
|
|
267
|
|
- } else if (c.isTextual()) {
|
|
268
|
|
- String s = c.asText();
|
|
269
|
|
- ok = "0".equals(s) || "200".equals(s) || "success".equalsIgnoreCase(s);
|
|
270
|
|
- }
|
|
271
|
|
- if (!ok) {
|
|
272
|
|
- String msg = root.has("message") ? root.get("message").asText(null) : null;
|
|
273
|
|
- if (!StringUtils.hasText(msg) && root.has("msg")) {
|
|
274
|
|
- msg = root.get("msg").asText(null);
|
|
275
|
|
- }
|
|
276
|
|
- if (!StringUtils.hasText(msg)) {
|
|
277
|
|
- msg = "知识库接口返回失败";
|
|
278
|
|
- }
|
|
279
|
|
- throw new BizException(ErrorCode.KB_SERVICE_FAILED, msg);
|
|
280
|
|
- }
|
|
281
|
|
- }
|
|
282
|
|
-
|
|
283
|
|
- private static String extractFileId(JsonNode root) {
|
|
284
|
|
- JsonNode data = root.get("data");
|
|
285
|
|
- if (data != null && data.isTextual()) {
|
|
286
|
|
- String t = data.asText(null);
|
|
287
|
|
- if (StringUtils.hasText(t)) {
|
|
288
|
|
- return t;
|
|
289
|
|
- }
|
|
290
|
|
- }
|
|
291
|
|
- if (data != null && data.isObject()) {
|
|
292
|
|
- for (String key : FILE_ID_KEYS) {
|
|
293
|
|
- if (data.has(key) && !data.get(key).isNull()) {
|
|
294
|
|
- String v = data.get(key).asText(null);
|
|
295
|
|
- if (StringUtils.hasText(v)) {
|
|
296
|
|
- return v;
|
|
297
|
|
- }
|
|
298
|
|
- }
|
|
299
|
|
- }
|
|
300
|
|
- }
|
|
301
|
|
- for (String key : FILE_ID_KEYS) {
|
|
302
|
|
- if (root.has(key) && !root.get(key).isNull()) {
|
|
303
|
|
- String v = root.get(key).asText(null);
|
|
304
|
|
- if (StringUtils.hasText(v)) {
|
|
305
|
|
- return v;
|
|
306
|
|
- }
|
|
307
|
|
- }
|
|
308
|
|
- }
|
|
309
|
|
- return null;
|
|
310
|
|
- }
|
|
311
|
|
-
|
|
312
|
447
|
private static String abbreviate(String s) {
|
|
313
|
448
|
if (s == null) {
|
|
314
|
449
|
return "";
|