|
|
@@ -36,7 +36,8 @@ public class LangGraphReportParseService {
|
|
|
public static final String DETECTED_GENERIC = "generic";
|
|
|
|
|
|
@Resource
|
|
|
- private RestTemplate restTemplate;
|
|
|
+ @org.springframework.beans.factory.annotation.Qualifier("langGraphRestTemplate")
|
|
|
+ private RestTemplate langGraphRestTemplate;
|
|
|
|
|
|
@Resource
|
|
|
private ObjectMapper objectMapper;
|
|
|
@@ -44,21 +45,69 @@ public class LangGraphReportParseService {
|
|
|
@Value("${langgraph.base-url:http://localhost:9000}")
|
|
|
private String langgraphBaseUrl;
|
|
|
|
|
|
+ /**
|
|
|
+ * 解析结果 + 失败原因(known-type 分支用 parse_error 落库,避免失败原因被吞掉)。
|
|
|
+ */
|
|
|
+ public static class ParseOutcome {
|
|
|
+ private final ParsedReportPayload.Payload payload;
|
|
|
+ private final String error;
|
|
|
+
|
|
|
+ private ParseOutcome(ParsedReportPayload.Payload payload, String error) {
|
|
|
+ this.payload = payload;
|
|
|
+ this.error = error;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static ParseOutcome success(ParsedReportPayload.Payload payload) {
|
|
|
+ return new ParseOutcome(payload, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static ParseOutcome failure(String error) {
|
|
|
+ return new ParseOutcome(null, error);
|
|
|
+ }
|
|
|
+
|
|
|
+ public ParsedReportPayload.Payload getPayload() { return payload; }
|
|
|
+ public String getError() { return error; }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** HTTP 调用结果:data 或 error(二选一) */
|
|
|
+ private static class HttpResult {
|
|
|
+ final Map<String, Object> data;
|
|
|
+ final String error;
|
|
|
+
|
|
|
+ HttpResult(Map<String, Object> data, String error) {
|
|
|
+ this.data = data;
|
|
|
+ this.error = error;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 指纹成功路径:调 LangGraph 脚本(/report/parse,算法 + LLM 兜底)。
|
|
|
* 转换后无有效指标数据时返回 null。
|
|
|
*/
|
|
|
public ParsedReportPayload.Payload parseByScript(String pdfPath) {
|
|
|
- return parseByScript(pdfPath, null);
|
|
|
+ ParseOutcome outcome = parseByScriptWithReason(pdfPath, null);
|
|
|
+ return outcome != null ? outcome.getPayload() : null;
|
|
|
}
|
|
|
|
|
|
public ParsedReportPayload.Payload parseByScript(String pdfPath, List<String> indicators) {
|
|
|
- Map<String, Object> data = postParse("/api/v1/report/parse", pdfPath, indicators);
|
|
|
- if (data == null) {
|
|
|
- return null;
|
|
|
+ ParseOutcome outcome = parseByScriptWithReason(pdfPath, indicators);
|
|
|
+ return outcome != null ? outcome.getPayload() : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 带失败原因版本的脚本解析:失败时返回 null payload 且 error 含具体原因
|
|
|
+ * (如 "Read timed out"、http=500、空 data 等),供调用方登记重试与落库 parse_error。
|
|
|
+ */
|
|
|
+ public ParseOutcome parseByScriptWithReason(String pdfPath, List<String> indicators) {
|
|
|
+ HttpResult r = postParseWithReason("/api/v1/report/parse", pdfPath, indicators);
|
|
|
+ if (r == null || r.data == null) {
|
|
|
+ return ParseOutcome.failure(r != null && r.error != null ? r.error : "解析结果为空或无效");
|
|
|
}
|
|
|
- ParsedReportPayload.Payload payload = buildPayloadFromLgData(data);
|
|
|
- return isValid(payload) ? payload : null;
|
|
|
+ ParsedReportPayload.Payload payload = buildPayloadFromLgData(r.data);
|
|
|
+ if (!isValid(payload)) {
|
|
|
+ return ParseOutcome.failure("LangGraph 解析结果无有效指标数据");
|
|
|
+ }
|
|
|
+ return ParseOutcome.success(payload);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -128,29 +177,50 @@ public class LangGraphReportParseService {
|
|
|
return postParse(path, body);
|
|
|
}
|
|
|
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
private Map<String, Object> postParse(String path, Map<String, Object> body) {
|
|
|
+ HttpResult r = postParseWithReason(path, body);
|
|
|
+ return r != null ? r.data : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 带失败原因版本的 LangGraph 调用(known-type 分支用于把失败原因写入 parse_error) */
|
|
|
+ private HttpResult postParseWithReason(String path, String pdfPath, List<String> indicators) {
|
|
|
+ Map<String, Object> body = new HashMap<>();
|
|
|
+ body.put("file_path", pdfPath);
|
|
|
+ if (indicators != null && !indicators.isEmpty()) {
|
|
|
+ body.put("indicators", indicators);
|
|
|
+ }
|
|
|
+ return postParseWithReason(path, body);
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private HttpResult postParseWithReason(String path, Map<String, Object> body) {
|
|
|
try {
|
|
|
HttpHeaders headers = new HttpHeaders();
|
|
|
headers.setContentType(MediaType.APPLICATION_JSON);
|
|
|
HttpEntity<String> entity = new HttpEntity<>(objectMapper.writeValueAsString(body), headers);
|
|
|
- ResponseEntity<Map> resp = restTemplate.postForEntity(langgraphBaseUrl + path, entity, Map.class);
|
|
|
+ ResponseEntity<Map> resp = langGraphRestTemplate.postForEntity(langgraphBaseUrl + path, entity, Map.class);
|
|
|
Map<String, Object> respBody = resp.getBody();
|
|
|
- if (respBody == null || !Integer.valueOf(200).equals(respBody.get("code"))) {
|
|
|
+ if (respBody == null) {
|
|
|
+ log.warn("LangGraph 端点 {} 返回空响应", path);
|
|
|
+ return new HttpResult(null, path + " 返回空响应");
|
|
|
+ }
|
|
|
+ if (!Integer.valueOf(200).equals(respBody.get("code"))) {
|
|
|
+ String detail = "http=" + String.valueOf(respBody.get("code"))
|
|
|
+ + " message=" + String.valueOf(respBody.get("message"));
|
|
|
log.warn("LangGraph 端点 {} 返回异常: code={}, message={}",
|
|
|
- path, respBody != null ? respBody.get("code") : null,
|
|
|
- respBody != null ? respBody.get("message") : null);
|
|
|
- return null;
|
|
|
+ path, respBody.get("code"), respBody.get("message"));
|
|
|
+ return new HttpResult(null, path + " 返回异常: " + detail);
|
|
|
}
|
|
|
Object data = respBody.get("data");
|
|
|
if (!(data instanceof Map) || ((Map<String, Object>) data).isEmpty()) {
|
|
|
log.warn("LangGraph 端点 {} 返回空 data", path);
|
|
|
- return null;
|
|
|
+ return new HttpResult(null, path + " 返回空 data(无有效解析结果)");
|
|
|
}
|
|
|
- return (Map<String, Object>) data;
|
|
|
+ return new HttpResult((Map<String, Object>) data, null);
|
|
|
} catch (Exception e) {
|
|
|
log.warn("LangGraph 端点 {} 调用失败: {}", path, e.getMessage());
|
|
|
- return null;
|
|
|
+ String reason = "LangGraph 调用异常(" + e.getClass().getSimpleName() + "): " + e.getMessage();
|
|
|
+ return new HttpResult(null, reason);
|
|
|
}
|
|
|
}
|
|
|
|