|
|
@@ -25,6 +25,8 @@ import com.zsjz.ai.module.agent.tools.ToolResultLimits;
|
|
|
import com.zsjz.ai.module.agent.usage.UsageStore;
|
|
|
import com.zsjz.ai.module.agent.vo.ChatSessionVO;
|
|
|
import com.zsjz.ai.module.agent.vo.MessageVO;
|
|
|
+import com.zsjz.ai.module.graph.service.GraphService;
|
|
|
+import com.zsjz.ai.common.model.graph.query.GraphQuery;
|
|
|
import io.agentscope.core.agent.RuntimeContext;
|
|
|
import io.agentscope.core.event.*;
|
|
|
import io.agentscope.core.message.Msg;
|
|
|
@@ -42,6 +44,7 @@ import reactor.core.publisher.Sinks;
|
|
|
import reactor.core.scheduler.Schedulers;
|
|
|
|
|
|
import java.time.Duration;
|
|
|
+import java.time.LocalDate;
|
|
|
import java.time.LocalDateTime;
|
|
|
import java.util.*;
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
@@ -94,6 +97,7 @@ public class AgentChatServiceImpl extends ServiceImpl<AgentChatSessionMapper, Ag
|
|
|
private final AgentCapabilityService agentCapabilityService;
|
|
|
private final UsageStore usageStore;
|
|
|
private final com.zsjz.ai.module.agent.attachment.ChatAttachmentService chatAttachmentService;
|
|
|
+ private final GraphService graphService;
|
|
|
private static final ObjectMapper MAPPER = new ObjectMapper();
|
|
|
|
|
|
public AgentChatServiceImpl(UsageStore usageStore,
|
|
|
@@ -102,7 +106,8 @@ public class AgentChatServiceImpl extends ServiceImpl<AgentChatSessionMapper, Ag
|
|
|
AgentCapabilityService agentCapabilityService,
|
|
|
AgentMessageMapper messageMapper,
|
|
|
AgentChatSessionMapper chatSessionMapper,
|
|
|
- com.zsjz.ai.module.agent.attachment.ChatAttachmentService chatAttachmentService) {
|
|
|
+ com.zsjz.ai.module.agent.attachment.ChatAttachmentService chatAttachmentService,
|
|
|
+ GraphService graphService) {
|
|
|
this.usageStore = usageStore;
|
|
|
this.agentService = agentService;
|
|
|
this.agentModelService = agentModelService;
|
|
|
@@ -110,6 +115,7 @@ public class AgentChatServiceImpl extends ServiceImpl<AgentChatSessionMapper, Ag
|
|
|
this.messageMapper = messageMapper;
|
|
|
this.chatSessionMapper = chatSessionMapper;
|
|
|
this.chatAttachmentService = chatAttachmentService;
|
|
|
+ this.graphService = graphService;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@@ -358,6 +364,10 @@ public class AgentChatServiceImpl extends ServiceImpl<AgentChatSessionMapper, Ag
|
|
|
|
|
|
private Flux<ServerSentEvent<String>> doStreamLocked(Long sessionId, ChatRequestDTO req, StreamProcess process) {
|
|
|
AgentChatSession session = requireSessionInOpenCase(requireSession(sessionId));
|
|
|
+ // 本轮流的案件上下文:get_case_graph 的全量图谱补取要用它显式绑定,
|
|
|
+ // 因为 GraphService 的 @DS 路由只认 CaseContextHolder。
|
|
|
+ Long graphCaseId = session.getCaseId();
|
|
|
+ Long graphUserId = CaseContextHolder.getUserId();
|
|
|
|
|
|
// 附件:归属校验 + 注入块(内联全文 / 表格 Python 直读指引 / RAG 片段)。
|
|
|
// 草稿期上传的附件(sessionId=0)在此绑定到当前会话;越权/失败直接 400 不进流
|
|
|
@@ -478,6 +488,22 @@ public class AgentChatServiceImpl extends ServiceImpl<AgentChatSessionMapper, Ag
|
|
|
data.put("toolResult", text.length() > maxChars ? text.substring(0, maxChars) : text);
|
|
|
}
|
|
|
frames.add(sse("tool_result", data));
|
|
|
+
|
|
|
+ // 前端关系图必须用后端原始全量数据:get_case_graph 的工具结果会为模型上下文
|
|
|
+ // 裁剪到前 500 条边,这里在同一案件上下文里重新取一次全量图谱,作为独立
|
|
|
+ // graph_data 帧下发给前端(不进入模型上下文,也不经过模型过滤)。
|
|
|
+ if ("get_case_graph".equals(e.getToolCallName())) {
|
|
|
+ StringBuilder input = toolInputs.get(e.getToolCallId());
|
|
|
+ Map<String, Object> fullGraph = buildFullGraphPayload(
|
|
|
+ input == null ? null : input.toString(), graphCaseId, graphUserId);
|
|
|
+ if (fullGraph != null) {
|
|
|
+ Map<String, Object> graphFrame = new LinkedHashMap<>();
|
|
|
+ graphFrame.put("type", "graph_data");
|
|
|
+ graphFrame.put("toolCallId", e.getToolCallId());
|
|
|
+ graphFrame.put("graph", fullGraph);
|
|
|
+ frames.add(sse("graph_data", graphFrame));
|
|
|
+ }
|
|
|
+ }
|
|
|
} else if (event instanceof TextBlockDeltaEvent e) {
|
|
|
accumulatedText.get().append(e.getDelta());
|
|
|
frames.add(sse("token", Map.of("type", "token", "data", e.getDelta())));
|
|
|
@@ -898,6 +924,71 @@ public class AgentChatServiceImpl extends ServiceImpl<AgentChatSessionMapper, Ag
|
|
|
return vo;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 按 get_case_graph 的原始入参重新取一次全量图谱,供前端 graph_data 帧使用。
|
|
|
+ *
|
|
|
+ * <p>工具结果本身为了模型上下文只保留前 500 条边,不能拿它当展示数据;
|
|
|
+ * 这里在 chat 流自己的案件上下文里直接调用 GraphService,拿到完整 nodeList/edgeList。
|
|
|
+ */
|
|
|
+ private Map<String, Object> buildFullGraphPayload(String toolInput, Long caseId, Long userId) {
|
|
|
+ if (!StringUtils.hasText(toolInput)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ JsonNode root = MAPPER.readTree(unwrapJsonString(toolInput));
|
|
|
+ JsonNode query = root.path("query");
|
|
|
+ if (!query.isObject()) {
|
|
|
+ query = root;
|
|
|
+ }
|
|
|
+ JsonNode typeNode = query.path("type");
|
|
|
+ JsonNode nameNode = query.path("personNames");
|
|
|
+ if (!typeNode.isArray() || !nameNode.isArray()) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ List<Integer> types = new ArrayList<>();
|
|
|
+ typeNode.forEach(node -> {
|
|
|
+ if (node.isNumber()) {
|
|
|
+ types.add(node.asInt());
|
|
|
+ }
|
|
|
+ });
|
|
|
+ List<String> personNames = new ArrayList<>();
|
|
|
+ nameNode.forEach(node -> {
|
|
|
+ String name = node.asText("").trim();
|
|
|
+ if (!name.isEmpty()) {
|
|
|
+ personNames.add(name);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ if (types.isEmpty() || personNames.isEmpty()) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ GraphQuery graphQuery = new GraphQuery();
|
|
|
+ graphQuery.setType(types);
|
|
|
+ graphQuery.setPersonNames(personNames);
|
|
|
+ String startDate = jsonText(query.get("startDate"));
|
|
|
+ String endDate = jsonText(query.get("endDate"));
|
|
|
+ if (startDate != null) graphQuery.setStartDate(LocalDate.parse(startDate));
|
|
|
+ if (endDate != null) graphQuery.setEndDate(LocalDate.parse(endDate));
|
|
|
+ String startMoney = jsonText(query.get("startMoney"));
|
|
|
+ String endMoney = jsonText(query.get("endMoney"));
|
|
|
+ String callNum = jsonText(query.get("callNum"));
|
|
|
+ if (startMoney != null) graphQuery.setStartMoney(startMoney);
|
|
|
+ if (endMoney != null) graphQuery.setEndMoney(endMoney);
|
|
|
+ if (callNum != null) graphQuery.setCallNum(callNum);
|
|
|
+ return CaseContextHolder.callWith(caseId, userId, () -> graphService.graph(graphQuery));
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("构建全量关系图失败: {}", e.getMessage());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String jsonText(JsonNode node) {
|
|
|
+ if (node == null || node.isNull()) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String text = node.asText("").trim();
|
|
|
+ return text.isEmpty() ? null : text;
|
|
|
+ }
|
|
|
+
|
|
|
private ServerSentEvent<String> sse(String eventType, Object data) {
|
|
|
String json;
|
|
|
try {
|