# Agent 与 RAG **本文引用的文件** - [AgentChatController.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentChatController.java) - [AgentChatServiceImpl.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentChatServiceImpl.java) - [AgentMessage.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentMessage.java) - [AgentChatSession.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentChatSession.java) - [RagIndexStatus.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/RagIndexStatus.java) - [RagSchemaService.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/RagSchemaService.java) - [RagSchemaSearchTool.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/RagSchemaSearchTool.java) - [SqlAnalysisTool.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/SqlAnalysisTool.java) - [PythonAnalysisTool.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/PythonAnalysisTool.java) - [PythonExecutor.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/python/PythonExecutor.java) - [ChatAttachmentService.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/attachment/ChatAttachmentService.java) - [ArtifactService.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/artifact/ArtifactService.java) - [IntentService.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/intent/IntentService.java) - [FollowupService.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/followup/FollowupService.java) - [EmbeddingProvider.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingProvider.java) - [EmbeddingModelFactory.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingModelFactory.java) - [InsightServiceImpl.java](file://ai-server/src/main/java/com/zsjz/ai/module/agent/insight/service/impl/InsightServiceImpl.java) - [AI_AGENT.md](file://AI_AGENT.md) - [agent-data-model.md](file://docs/agent-data-model.md) ## 目录 1. [简介](#简介) 2. [项目结构](#项目结构) 3. [核心组件](#核心组件) 4. [架构总览](#架构总览) 5. [关键数据模型关系](#关键数据模型关系) 6. [Agent 对话流程详解](#agent-对话流程详解) 7. [RAG 与向量检索](#rag-与向量检索) 8. [工具调用与产物管理](#工具调用与产物管理) 9. [意图识别、追问建议与 Insight](#意图识别追问建议与-insight) 10. [典型调用示例](#典型调用示例) 11. [依赖关系分析](#依赖关系分析) 12. [性能特征](#性能特征) 13. [常见问题排查](#常见问题排查) 14. [结论](#结论) ## 简介 Agent 与 RAG 是清鉴平台中面向纪检监察与经侦场景的对话式研判能力。其目标是让业务人员用自然语言与案件数据交互:前端通过 SSE 流式对话,后端基于 AgentScope 的 `HarnessAgent` 驱动 LLM,并结合 pgvector 做表结构与知识库语义检索、意图识别、SQL 与 Python 执行器、附件解析、产物管理与 Insight 结构化研判输出。 从仓库定位看,Agent 子系统位于 `ai-server` 的 `module/agent` 域,使用 Spring Boot 3 + MyBatis-Plus + AgentScope 2.0.4;主库为 PostgreSQL + pgvector,分析库为 DuckDB(按工作空间切换),Redis 用于会话缓存与记忆隔离,Elasticsearch 用于知识文档索引。 **章节来源** - [AI_AGENT.md:1-120](file://AI_AGENT.md#L1-L120) ## 项目结构 Agent 相关代码集中在后端 `ai-server` 中,整体采用领域分层: | 层级 | 职责 | 典型路径 | |---|---|---| | Controller | 接收 REST/SSE 请求、做参数校验后调 Service | `module/agent/controller` | | Service | 会话、流式对话、RAG、Insight、模型能力等核心逻辑 | `module/agent/service/*` | | Tools | Agent 可被 LLM 调用的能力,如 SQL、Python、表结构检索 | `module/agent/tools/*` | | RAG | 嵌入模型路由、模板结构向量索引、状态查询 | `module/agent/rag/*` | | Attachment | 附件上传、内容提取、向量化与注入块 | `module/agent/attachment/*` | | Artifact | SQL/分析工具全量结果 CSV 产物管理 | `module/agent/artifact/*` | | Intent/Followup | 意图识别与下一步追问建议 | `module/agent/intent/*`、`module/agent/followup/*` | | Entity/Mapper | Agent 会话、消息、RAG 索引状态等持久化模型 | `module/agent/entity/*`、`module/agent/mapper/*` | ```mermaid graph TB FE["前端
SSE 客户端"] --> CC["AgentChatController"] CC --> ACS["AgentChatServiceImpl"] ACS --> AS["AgentService / HarnessAgent"] ACS --> ATT["ChatAttachmentService"] ACS --> INT["IntentService"] ACS --> FLW["FollowupService"] AS --> TOOLS["工具集"] TOOLS --> SQLT["SqlAnalysisTool"] TOOLS --> PYT["PythonAnalysisTool"] TOOLS --> RSST["RagSchemaSearchTool"] RSST --> RSS["RagSchemaService"] RSS --> EPM["EmbeddingProvider / EmbeddingModelFactory"] SQLT --> ART["ArtifactService"] ACS --> DB["PostgreSQL + pgvector"] SQLT --> DUCK["DuckDB 工作空间库"] ``` **图示来源** - [AgentChatController.java:1-170](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentChatController.java#L1-L170) - [AgentChatServiceImpl.java:345-582](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentChatServiceImpl.java#L345-L582) - [RagSchemaService.java:1-181](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/RagSchemaService.java#L1-L181) - [EmbeddingModelFactory.java:1-187](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingModelFactory.java#L1-L187) **章节来源** - [AI_AGENT.md:120-250](file://AI_AGENT.md#L120-L250) ## 核心组件 | 组件 | 说明 | 关键行为 | |---|---|---| | `AgentChatController` | 聊天入口 | 提供会话 CRUD、消息列表、发送消息、SSE 流、附件上传、知识库重建接口 | | `AgentChatServiceImpl` | 对话主服务 | 会话归属校验、模型选择、Agent 事件转 SSE、工具调用聚合、思考过程与产物落库 | | `RagSchemaService` | RAG 门面 | 判断向量能力、重建模板结构索引、查询索引状态、语义检索表结构 | | `SqlAnalysisTool` | SQL 与分析工具 | `execute_sql`、`list_tables`、`render_chart`,只读 DuckDB,结果落产物 CSV | | `PythonAnalysisTool` | Python 工具 | 沙箱执行 pandas/numpy/duckdb/matplotlib,返回 stdout/stderr/imageUrls | | `PythonExecutor` | Python 子进程执行器 | 工作空间快照、超时强制终止、图片收集、权限黑名单 | | `ChatAttachmentService` | 附件编排 | 上传校验、内联/Python直读/RAG 三路分流、三重归属校验 | | `ArtifactService` | 产物服务 | 全量 CSV 落盘、预览列头、安全路径解析 | | `IntentService` | 意图识别 | 轻量 LLM 分类 + 历史语义增强,fail-open | | `FollowupService` | 追问建议 | 对话结束后生成方向 + 可执行追问,fail-open | | `EmbeddingProvider` | 嵌入模型路由 | 本地 ONNX 与在线 DashScope/Ollama/OpenAI 二选一 | | `EmbeddingModelFactory` | 嵌入实例工厂 | 从 model 表构建 agentscope EmbeddingModel | | `InsightServiceImpl` | 结构化研判 | 独立会话/消息表、动态提示词、独立 Agent 池、SSE 流式输出 | **章节来源** - [AgentChatController.java:1-170](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentChatController.java#L1-L170) - [AgentChatServiceImpl.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentChatServiceImpl.java#L1-L200) - [RagSchemaService.java:1-181](file://ai-server/src/main/java/com/zsjz/agent/rag/RagSchemaService.java#L1-L181) - [SqlAnalysisTool.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/SqlAnalysisTool.java#L1-L200) - [PythonAnalysisTool.java:1-69](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/PythonAnalysisTool.java#L1-L69) - [PythonExecutor.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/python/PythonExecutor.java#L1-L200) - [ChatAttachmentService.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/attachment/ChatAttachmentService.java#L1-L200) - [ArtifactService.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/artifact/ArtifactService.java#L1-L200) - [IntentService.java:1-146](file://ai-server/src/main/java/com/zsjz/ai/module/agent/intent/IntentService.java#L1-L146) - [FollowupService.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/followup/FollowupService.java#L1-L200) - [EmbeddingProvider.java:1-201](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingProvider.java#L1-L201) - [EmbeddingModelFactory.java:1-187](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingModelFactory.java#L1-L187) - [InsightServiceImpl.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/insight/service/impl/InsightServiceImpl.java#L1-L200) ## 架构总览 Agent 对话不是简单的一次 HTTP 问答,而是“前端 SSE → 后端 Service → AgentScope HarnessAgent → LLM Provider → 工具 → 产物/向量/数据库”的多阶段流水线。 ```mermaid sequenceDiagram participant FE as "前端" participant CC as "AgentChatController" participant ACS as "AgentChatServiceImpl" participant AS as "AgentService/HarnessAgent" participant LLM as "LLM Provider" participant T as "工具" participant PG as "PostgreSQL/pgvector" participant DK as "DuckDB" participant FS as "文件系统/产物" FE->>CC: POST /js/a/chat/stream CC->>ACS: stream(ChatRequestDTO) ACS->>ACS: 校验 sessionKey、案件上下文、模型 ACS->>AS: getOrCreateAgent(workspaceId, agentRowId, modelId) AS->>LLM: 流式调用(DashScope/Ollama/OpenAI) LLM-->>AS: ToolCallStartEvent / TextBlockDeltaEvent / ... AS->>T: 调用工具(execute_sql / execute_python / search_table_schema) T->>DK: execute_sql 只读查询 T->>FS: 写 CSV 产物 T->>PG: 向量检索(可选) AS-->>ACS: AgentEvent 流 ACS-->>FE: tool_call → tool_input → tool_result → token → done ``` **图示来源** - [AgentChatController.java:120-170](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentChatController.java#L120-L170) - [AgentChatServiceImpl.java:345-582](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentChatServiceImpl.java#L345-L582) - [SqlAnalysisTool.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/SqlAnalysisTool.java#L1-L200) - [RagSchemaSearchTool.java:1-52](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/RagSchemaSearchTool.java#L1-L52) ## 关键数据模型关系 ### AgentChatSession `agent_chat_session` 表示一次对话会话,属于某个工作空间(`case_id`)和某个 Agent 方案(`agent_id`),并可绑定一个具体模型(`model_id`)。它保存会话标题、上下文 JSON、消息数量、累计 token、是否置顶、附件元数据以及创建/更新时间。 | 字段 | 含义 | |---|---| | `id` | 雪花 ID | | `agent_id` | 关联 Agent 方案行 | | `model_id` | 会话绑定的模型配置 ID,为空时使用默认模型 | | `case_id` | 工作空间/案件 ID | | `title` | 会话标题 | | `context` | 会话上下文 JSON | | `message_count` | 消息条数 | | `total_tokens` | 累计 token 数 | | `pinned` | 是否置顶 | | `attachments` | 附件元数据 JSON | | `create_at/update_at` | 时间戳 | ### AgentMessage `agent_message` 存储每轮用户提问与助手回复。消息包含角色、正文、正文类型、token 计数、JSONB 元数据、工具调用事件、父消息、消息类型、回复目标消息、收藏标记等。 | 字段 | 含义 | |---|---| | `id` | 雪花 ID | | `session_id` | 所属会话 | | `role` | `user` / `assistant` 等 | | `content` | 正文 | | `content_type` | 正文类型 | | `token_count` | token 计数 | | `metadata` | JSONB:思考过程、错误标志等 | | `tool_events` | JSONB:工具调用链序列化 | | `parent_id` | 父消息 ID | | `message_type` | 消息类别 | | `reply_to_message_id` | 回复的目标消息 | | `starred` | 是否收藏 | | `create_at` | 创建时间 | ### RagIndexStatus `rag_index_status` 记录每个工作空间的模板结构向量索引状态。它包括嵌入模型 ID、维度、pgvector 向量表名、结构指纹、表数量、状态、最近错误、索引时间与更新时间。 | 字段 | 含义 | |---|---| | `workspace_id` | 工作空间 ID(主键) | | `embedding_model_id` | 使用的嵌入模型 | | `embedding_model_name` | 嵌入模型名称 | | `dimensions` | 向量维度 | | `vector_table` | pgvector 向量表名 | | `schema_fingerprint` | 结构指纹 | | `table_count` | 已索引表数量 | | `status` | `idle/indexing/success/failed` | | `last_error` | 最近错误信息 | | `indexed_at/update_at` | 索引与更新时间 | ```mermaid erDiagram AGENT_CHAT_SESSION { bigint id PK bigint agent_id bigint model_id bigint case_id string title text context int message_count bigint total_tokens boolean pinned text attachments datetime create_at datetime update_at } AGENT_MESSAGE { bigint id PK bigint session_id FK string role string content string content_type int token_count jsonb metadata jsonb tool_events bigint parent_id string message_type bigint reply_to_message_id boolean starred datetime create_at } RAG_INDEX_STATUS { bigint workspace_id PK bigint embedding_model_id string embedding_model_name int dimensions string vector_table string schema_fingerprint int table_count string status string last_error datetime indexed_at datetime update_at } AGENT_CHAT_SESSION ||--o{ AGENT_MESSAGE : "一对多" ``` **图示来源** - [AgentChatSession.java:1-58](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentChatSession.java#L1-L58) - [AgentMessage.java:1-55](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentMessage.java#L1-L55) - [RagIndexStatus.java:1-59](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/RagIndexStatus.java#L1-L59) **章节来源** - [AgentChatSession.java:1-58](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentChatSession.java#L1-L58) - [AgentMessage.java:1-55](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/AgentMessage.java#L1-L55) - [RagIndexStatus.java:1-59](file://ai-server/src/main/java/com/zsjz/ai/module/agent/entity/RagIndexStatus.java#L1-L59) ## Agent 对话流程详解 ### 前端发起 SSE 请求 前端向后端 `/js/a/chat/stream` 发起 POST 请求,请求体为 `ChatRequestDTO`,通常携带: | 字段 | 含义 | |---|---| | `message` | 用户问题 | | `sessionKey` | 会话 ID(字符串) | | `modelId` | 可选:本次动态切换的模型 ID | | `attachmentIds` | 可选:附件 ID 列表 | 后端控制器直接返回 `Flux>`,不包装成统一 `Result`,因为 SSE 帧本身承载多种事件类型。 **章节来源** - [AgentChatController.java:120-170](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentChatController.java#L120-L170) ### 会话与模型校验 `AgentChatServiceImpl.stream` 先校验消息非空,再取当前打开案件的 `caseId`,然后解析 `sessionKey`。若缺少有效会话 ID,会直接返回错误帧。同一会话使用 `Semaphore` 做流级互斥:同一时刻只允许一条 AI 流,避免多个标签页或重试造成 Agent 记忆污染。 模型优先级为: 1. 请求体 `modelId` 2. 会话绑定的 `model_id` 3. 系统默认模型 如果都没有默认模型,则抛出“未指定模型且无默认模型”的错误。 **章节来源** - [AgentChatServiceImpl.java:345-480](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentChatServiceImpl.java#L345-L480) ### AgentScope 事件到 SSE 帧 Agent 流通过 `HarnessAgent.streamEvents` 产出事件,Service 把它们转换成以下 SSE 帧: | 帧类型 | 含义 | |---|---| | `intent` | 意图识别结果 | | `suggestions` | 下一步追问建议 | | `tool_call` | 工具开始调用 | | `tool_input` | 工具入参完整文本 | | `tool_result` | 工具结果文本(可能截断) | | `token` | 助手回答增量文本 | | `thinking` | 模型思考过程 start/delta/end | | `graph_data` | 关系图谱全量数据帧 | | `done` | 一轮结束,附带 `sessionKey` | | `error` | 错误终止 | ```mermaid flowchart TD Start["收到 ChatRequestDTO"] --> Validate["校验消息与会话"] Validate --> Model{"是否指定模型"} Model -->|否| UseDefault["使用会话或默认模型"] Model -->|是| DynamicSwitch["动态切换模型并回写会话"] UseDefault --> BuildCtx["构建 RuntimeContext"] DynamicSwitch --> BuildCtx BuildCtx --> Events["监听 HarnessAgent 事件"] Events --> ToolCall{"是否工具调用"} ToolCall -->|是| EmitTool["发送 tool_call/tool_input/tool_result"] ToolCall -->|否| Token["发送 token/thinking"] EmitTool --> Done{"是否结束"} Token --> Done Done --> Persist["异步持久化助手消息与工具事件"] Persist --> End["发送 done 帧"] ``` **图示来源** - [AgentChatServiceImpl.java:345-582](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentChatServiceImpl.java#L345-L582) **章节来源** - [AgentChatServiceImpl.java:345-582](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentChatServiceImpl.java#L345-L582) ## RAG 与向量检索 ### 向量能力开关 向量能力由两个层次控制: 1. 应用层开关:`etl.rag.enabled` 2. 嵌入模型可用性:本地 ONNX 或在线 embedding 模型 `RagSchemaService.isAvailable` 要求两者同时满足。若不可用,`search` 会抛 409,提示“未配置嵌入模型”。 **章节来源** - [RagSchemaService.java:60-100](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/RagSchemaService.java#L60-L100) - [EmbeddingProvider.java:1-201](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingProvider.java#L1-L201) ### 嵌入模型路由 `EmbeddingProvider` 根据 `zsjz.embedding.enabled` 决定走本地还是在线: | 模式 | 实现 | 特点 | |---|---|---| | 本地 | `EmbeddingService`(ONNX) | 无需网络,模型目录缺失时不可用 | | 在线 | `EmbeddingModelFactory` 构建 agentscope EmbeddingModel | 支持 DashScope/Ollama/OpenAI | 在线模式从 `agent_model` 表中查找 `type='embedding'` 的活跃模型,并缓存 30 秒。本地模式下,向量表名带模型后缀(例如 `_bge_m3`),避免本地与在线向量混用。 **章节来源** - [EmbeddingProvider.java:1-201](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingProvider.java#L1-L201) - [EmbeddingModelFactory.java:1-187](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingModelFactory.java#L1-L187) ### 表结构向量索引 `RagSchemaService` 是模板结构向量索引的统一门面。真正的索引实现是 `AiCleanTemplateVectorIndex`,它将 `table_info`/`table_field` 与 `ai_clean_template`/`ai_clean_field` 两套模板库元数据向量化进全局 pgvector 表,而不是扫描案件 DuckDB。 关键接口: | 接口 | 作用 | |---|---| | `isAvailable()` | 判断 RAG 能力是否可用 | | `reindexWorkspace(workspaceId)` | 全量重建索引,幂等覆盖 | | `getStatus(workspaceId)` | 查询索引状态 | | `search(query, topK)` | 按自然语言检索物理表与物理列 | **章节来源** - [RagSchemaService.java:1-181](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/RagSchemaService.java#L1-L181) ### 工具中的 RAG `RagSchemaSearchTool.searchTableSchema` 注册为 Agent 工具 `search_table_schema`,供 LLM 在需要理解有哪些表、字段含义或编写 SQL 时调用。它把 `RagSchemaService.search` 的结果序列化为 JSON 传给模型。 **章节来源** - [RagSchemaSearchTool.java:1-52](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/RagSchemaSearchTool.java#L1-L52) ## 工具调用与产物管理 ### SQL 工具 `SqlAnalysisTool.execute_sql` 在当前工作空间的 DuckDB 上执行只读 SQL,白名单前缀包括 `select`、`with`、`pragma`、`describe`、`show`、`explain`。它还禁止危险函数,如 `read_csv`、`glob`、`attach`、`detach`、`install`、`load`、`copy`、`export`、`import`、`getenv`、`setenv` 等。 执行策略是流式 + 产物: - 每行写入 CSV 产物 - 内存只保留头部若干行作为预览 - 返回 artifact 元数据、列定义、头部预览、总行数 **章节来源** - [SqlAnalysisTool.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/SqlAnalysisTool.java#L1-L200) ### Python 工具 `PythonAnalysisTool.executePython` 调用 `PythonExecutor.execute`,在隔离子进程中执行 Python 代码。执行环境限制: - 每次执行使用独立临时目录 - 超时强制终止 - 输出截断 - 图片移动到工作空间产物目录 - 通过 `ETL_DB_PATH` 访问 DuckDB 快照副本,而非原库 ```mermaid flowchart TD Call["execute_python(code)"] --> CheckEnv["检查 Python 环境与依赖"] CheckEnv --> ValidCode["校验代码黑名单"] ValidCode --> Workspace["获取当前工作空间"] Workspace --> Snapshot["导出 DuckDB 快照副本"] Snapshot --> Subprocess["启动 Python 子进程"] Subprocess --> Timeout{"是否超时"} Timeout -->|是| Kill["强制终止并返回超时错误"] Timeout -->|否| Collect["收集 stdout/stderr/图片"] Collect --> Result["返回 exitCode、stdout、stderr、imageUrls、durationMs"] ``` **图示来源** - [PythonAnalysisTool.java:1-69](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/PythonAnalysisTool.java#L1-L69) - [PythonExecutor.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/python/PythonExecutor.java#L1-L200) **章节来源** - [PythonAnalysisTool.java:1-69](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/PythonAnalysisTool.java#L1-L69) - [PythonExecutor.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/python/PythonExecutor.java#L1-L200) ### 附件与产物 附件上传走 `ChatAttachmentService.upload`,按类型分为三路: 1. **内联**:小文档或小表格全文进入消息 2. **Python 直读**:大表格只扫描行数、表头、样本,注入文件路径与指引 3. **RAG**:大文档全文落 sidecar,向量化后按用户问题检索片段注入 产物由 `ArtifactService` 负责:工具查出的全量表格结果写成 CSV,前端只拿 artifact 元数据与头部预览,真实全量数据通过产物路径下载。 **章节来源** - [ChatAttachmentService.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/attachment/ChatAttachmentService.java#L1-L200) - [ArtifactService.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/artifact/ArtifactService.java#L1-L200) ## 意图识别、追问建议与 Insight ### 意图识别 `IntentService.safeAnalyze` 在把用户问题交给 Agent 之前,复用会话模型做一次轻量 LLM 调用,完成意图分类与语义增强。它是 fail-open 设计:任何异常、超时、解析失败都返回 null,不阻塞主链路。 它会加载最近 N 条消息历史,截断过长内容,拼接系统提示词与用户提示词,并通过结构化输出约束返回意图结果。 **章节来源** - [IntentService.java:1-146](file://ai-server/src/main/java/com/zsjz/ai/module/agent/intent/IntentService.java#L1-L146) ### 追问建议 `FollowupService.safeSuggest` 在对话结束后,根据最近几轮上下文和本轮问答生成“方向 + 可直接发送的追问”。它同样 fail-open,并对弱模型输出做清洗:去重、过滤空话、限制长度与方向数。 **章节来源** - [FollowupService.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/followup/FollowupService.java#L1-L200) ### Insight 结构化研判 `InsightServiceImpl` 是对象关系分析的智能研判服务。它与通用对话的关键差异是: - 存储独立:`agent_insight_*` 两张表 - 提示词动态:创建会话时拼好 sysPrompt 并落库 - 实例池独立:不走通用 Agent 池 - 排序用 `seq`:避免 `id` 与 `create_at` 同毫秒撞车 - 会话级互斥:进程内 Semaphore + DB 状态位 **章节来源** - [InsightServiceImpl.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/insight/service/impl/InsightServiceImpl.java#L1-L200) ## 典型调用示例 以下示例描述 API 调用方式,不展示后端源码。 ### 示例一:发送消息 1. 前端先调用 `POST /js/a/chat/messages`,请求体为 `SendMessageDTO`,包含 `sessionId` 和 `message`。 2. 或者直接使用 SSE 流:`POST /js/a/chat/stream`,请求体为 `ChatRequestDTO`。 3. 后端返回 SSE 帧序列: - `tool_call`:工具开始 - `tool_input`:工具入参 - `tool_result`:工具结果 - `token`:逐字增量文本 - `done`:本轮结束 - `error`:错误 **参考实现位置** - [AgentChatController.java:120-170](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentChatController.java#L120-L170) - [AgentChatServiceImpl.java:345-582](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentChatServiceImpl.java#L345-L582) ### 示例二:选择模型 1. 创建会话时可在 `CreateChatSessionDTO` 中传入 `modelId`。 2. 发送消息时也可在 `ChatRequestDTO` 中传 `modelId`,实现动态切换。 3. 若请求模型与会话绑定不同,后端会在模型校验通过后回写会话 `model_id`。 4. 如果没有默认模型且请求也未指定,会返回 400:“未指定模型且无默认模型,请先选择模型”。 **参考实现位置** - [AgentChatServiceImpl.java:430-500](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentChatServiceImpl.java#L430-L500) ### 示例三:开启 RAG 1. 确认 `etl.rag.enabled` 已启用。 2. 配置嵌入模型: - 本地模式:设置 `zsjz.embedding.enabled=true` 并提供模型目录。 - 在线模式:在 `agent_model` 表添加 `type='embedding'` 的活跃记录,provider 可为 DashScope、Ollama 或 OpenAI 兼容。 3. 调用 `GET /js/a/chat/reindexWorkspace?id={workspaceId}` 重建模板结构向量索引。 4. 调用 `RagSchemaService.getStatus` 或前端对应接口查看索引状态。 5. 在对话中让 LLM 调用 `search_table_schema` 工具进行语义检索。 **参考实现位置** - [RagSchemaService.java:60-181](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/RagSchemaService.java#L60-L181) - [EmbeddingProvider.java:1-201](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingProvider.java#L1-L201) - [EmbeddingModelFactory.java:1-187](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingModelFactory.java#L1-L187) - [RagSchemaSearchTool.java:1-52](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/RagSchemaSearchTool.java#L1-L52) ### 示例四:查看工具调用结果 1. 发送消息后观察 SSE 帧: - `tool_call` 携带 `toolCallId` 与 `toolName` - `tool_input` 携带累积后的工具入参 - `tool_result` 携带工具返回文本 2. 对于 `execute_sql`,前端应读取响应中的 `artifact` 元数据,通过产物接口预览或下载 CSV。 3. 对于 `execute_python`,响应中包含 `exitCode`、`stdout`、`stderr`、`imageUrls`、`durationMs`。 4. 对于 `get_case_graph`,后端还会额外下发 `graph_data` 帧,包含全量关系图数据。 **参考实现位置** - [AgentChatServiceImpl.java:480-582](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentChatServiceImpl.java#L480-L582) - [SqlAnalysisTool.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/SqlAnalysisTool.java#L1-L200) - [PythonAnalysisTool.java:1-69](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/PythonAnalysisTool.java#L1-L69) - [ArtifactService.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/artifact/ArtifactService.java#L1-L200) ## 依赖关系分析 ### 组件耦合关系 ```mermaid classDiagram class AgentChatController { +stream() +sendMessage() +uploadAttachment() +reindexWorkspace() } class AgentChatServiceImpl { +createSession() +stream() +listSessions() +getSession() +persistAssistantMessage() +persistUserMessage() } class RagSchemaService { +isAvailable() +reindexWorkspace() +getStatus() +search() } class RagSchemaSearchTool { +searchTableSchema() } class SqlAnalysisTool { +executeSql() +listTables() +renderChart() } class PythonAnalysisTool { +executePython() } class PythonExecutor { +isAvailable() +execute() } class ChatAttachmentService { +upload() +buildBlocks() } class ArtifactService { +export() +headRows() } class EmbeddingProvider { +isLocal() +isAvailable() +embed() +modelName() +dimensions() } class EmbeddingModelFactory { +resolveDefault() +create() } AgentChatController --> AgentChatServiceImpl : "调用" AgentChatServiceImpl --> RagSchemaService : "重建索引" AgentChatServiceImpl --> ChatAttachmentService : "附件注入" AgentChatServiceImpl --> SqlAnalysisTool : "工具" AgentChatServiceImpl --> PythonAnalysisTool : "工具" PythonAnalysisTool --> PythonExecutor : "执行" RagSchemaSearchTool --> RagSchemaService : "检索" RagSchemaService --> EmbeddingProvider : "嵌入能力" EmbeddingProvider --> EmbeddingModelFactory : "在线模型" ``` **图示来源** - [AgentChatController.java:1-170](file://ai-server/src/main/java/com/zsjz/ai/module/agent/controller/AgentChatController.java#L1-L170) - [AgentChatServiceImpl.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentChatServiceImpl.java#L1-L200) - [RagSchemaService.java:1-181](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/RagSchemaService.java#L1-L181) - [EmbeddingProvider.java:1-201](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingProvider.java#L1-L201) - [EmbeddingModelFactory.java:1-187](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingModelFactory.java#L1-L187) ### 外部依赖 | 外部系统 | 用途 | 风险点 | |---|---|---| | AgentScope | Agent 运行时、工具调度、事件流 | LLM Provider 连接、工具 schema 装配 | | DashScope/Ollama/OpenAI | LLM 与嵌入模型推理 | 网络延迟、密钥过期、维度不一致 | | PostgreSQL/pgvector | 平台元数据、Agent 会话、向量索引 | 向量表损坏、维度不匹配 | | DuckDB | 工作空间分析库 | 文件锁、只读白名单绕过 | | Redis | 会话/缓存 | 内存不足、连接中断 | | Elasticsearch | 知识文档索引 | 集群不可用、索引分片异常 | **章节来源** - [AI_AGENT.md:60-120](file://AI_AGENT.md#L60-L120) ## 性能特征 | 环节 | 特征 | 优化要点 | |---|---|---| | SSE 流 | Reactor 非阻塞流式转发 | 避免单帧过大;工具结果有字符上限 | | 工具调用 | LLM ReAct 循环 | 减少无关工具;控制结果大小 | | SQL 执行 | 流式 + 产物 CSV | 不要在前端渲染全量表格 | | Python 执行 | 子进程 + 快照副本 | 控制数据量与超时 | | 向量检索 | pgvector 相似度 | 保持索引重建;注意本地/在线向量表隔离 | | 意图/追问 | 轻量 LLM 辅助 | fail-open,失败不阻塞主链路 | | 会话并发 | 进程内 Semaphore | 多实例部署需补充 DB 状态位 | [本节为通用性能讨论,不直接分析具体文件] ## 常见问题排查 ### SSE 断连 **现象**:前端连接建立后很快断开,或长时间没有新帧。 **可能原因**: 1. 用户主动中止流式请求。 2. 同一会话并发发送多条消息,被流闸门拒绝。 3. 后端订阅线程取消或上游事件流异常。 4. 网络代理或负载均衡关闭空闲长连接。 **排查步骤**: - 检查后端日志中是否出现 `Chat stream error`。 - 检查是否在同一会话快速双击发送,导致“该会话正在回复中”错误帧。 - 检查代理层是否对 SSE 长连接有限制。 - 如果是多实例部署,确认进程内 Semaphore 不会误判其他实例的流。 **参考实现位置** - [AgentChatServiceImpl.java:345-420](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentChatServiceImpl.java#L345-L420) ### 模型不可用 **现象**:创建会话或发送消息时报错“未指定模型且无默认模型”,或 LLM 调用失败。 **可能原因**: 1. `agent_model` 中没有 `default_model=true` 的活跃 `type=llm` 模型。 2. DashScope/Ollama/OpenAI 配置不完整,如缺少 apiKey 或 baseUrl。 3. 网络不通、API Key 无效、模型名不存在。 4. 调用超时:流式分片间隔超时或总时长超限。 **排查步骤**: - 检查模型管理页面是否已有默认模型。 - 检查 provider 归一化:含 `ollama` 走 Ollama;含 `dashscope`/`阿里`/`通义`/`qwen` 走 DashScope;其余走 OpenAI 兼容。 - 对在线嵌入模型检查 `dimensions` 是否与返回向量维度一致。 - 对 LLM 调用检查是否触发 504 超时。 **参考实现位置** - [AgentChatServiceImpl.java:450-500](file://ai-server/src/main/java/com/zsjz/ai/module/agent/service/impl/AgentChatServiceImpl.java#L450-L500) - [EmbeddingModelFactory.java:80-187](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingModelFactory.java#L80-L187) - [LlmService.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/llm/LlmService.java#L1-L200) ### 向量检索无结果 **现象**:调用 `search_table_schema` 或 RAG 接口返回空,或报“表结构向量索引为空”。 **可能原因**: 1. `etl.rag.enabled=false`。 2. 未配置嵌入模型。 3. 尚未重建索引。 4. 索引存在但文档数为 0。 5. 本地与在线向量表混用:不同向量空间无法互相召回。 **排查步骤**: - 调用 `GET /js/a/chat/reindexWorkspace?id={workspaceId}` 重建索引。 - 检查 `RagSchemaService.getStatus` 返回的 `status`、`vectorTable`、`dimensions`。 - 检查 `EmbeddingProvider.isAvailable()` 与 `unavailableMessage()`。 - 确认本地与在线模式没有混用同一向量表。 **参考实现位置** - [RagSchemaService.java:60-181](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/RagSchemaService.java#L60-L181) - [EmbeddingProvider.java:60-160](file://ai-server/src/main/java/com/zsjz/ai/module/agent/rag/EmbeddingProvider.java#L60-L160) ### Python 执行器权限或超时 **现象**:`execute_python` 返回错误、超时、找不到数据库文件或图片无法显示。 **可能原因**: 1. Python 环境未安装 pandas、duckdb、matplotlib。 2. 当前没有打开的案件,`CaseContextHolder` 为空。 3. 工作空间 DuckDB 文件不存在或未初始化。 4. Python 脚本运行超过 `python.timeoutSeconds`。 5. 子进程尝试直接写案件原库,被 Java 侧写锁挡住。 6. 图片 URL 前缀与服务端 context-path 不一致。 **排查步骤**: - 检查 `PythonExecutor.isAvailable()` 与环境检测日志。 - 检查工作空间 `dbPath` 是否存在。 - 检查 Python 超时配置是否过小。 - 确认 Python 代码使用 `os.environ['ETL_DB_PATH']` 连接快照副本。 - 检查产物图片对外地址是否为 `/js/a/py/files/{workspaceId}/{execId}/{filename}`。 **参考实现位置** - [PythonExecutor.java:1-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/python/PythonExecutor.java#L1-L200) - [PythonAnalysisTool.java:1-69](file://ai-server/src/main/java/com/zsjz/ai/module/agent/tools/PythonAnalysisTool.java#L1-L69) ### 附件上传失败 **现象**:上传后状态为 FAILED,或提示不支持的文件类型、文件大小超限。 **可能原因**: 1. 未打开案件。 2. 请求 `caseId` 与当前打开案件不一致。 3. 文件类型不在允许后缀列表中。 4. 文件超过文档或表格大小上限。 5. Tika 无法解析文档内容。 6. Excel 解析失败,sidecar CSV 生成失败。 **参考实现位置** - [ChatAttachmentService.java:60-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/attachment/ChatAttachmentService.java#L60-L200) ### Insight 研判卡住 **现象**:Insight 会话长时间 STREAMING,或再次追问时报“正在进行中”。 **可能原因**: 1. 同一会话并发发送追问。 2. 进程内 Semaphore 未释放。 3. 多实例部署下,另一实例持有 DB 状态位。 4. LLM 或工具调用长时间无响应。 **排查步骤**: - 检查 Insight 会话状态位。 - 检查是否有重复提交。 - 检查 Agent 事件流是否到达 `AgentEndEvent`。 - 检查工具调用是否卡在 SQL、Python 或外部 API。 **参考实现位置** - [InsightServiceImpl.java:100-200](file://ai-server/src/main/java/com/zsjz/ai/module/agent/insight/service/impl/InsightServiceImpl.java#L100-L200) ## 结论 Agent 与 RAG 在清鉴平台中构成了从自然语言到数据分析的闭环:前端通过 SSE 获得实时流式体验,后端通过 AgentScope 组合 LLM、工具、向量检索与产物管理,最终输出文本、图表、CSV 产物、关系图和结构化 Insight。 实践中最关键的是三个边界: 1. **模型边界**:明确对话模型与嵌入模型,区分 DashScope/Ollama/OpenAI 与本地 ONNX。 2. **数据边界**:DuckDB 只读、附件三重归属、产物按工作空间隔离、向量表按模型隔离。 3. **流程边界**:意图识别和追问建议是辅助链路,fail-open;主对话流必须保证会话互斥、事件完整、产物可靠。 只要维护好模型配置、RAG 索引状态、Python 环境和附件权限,Agent 与 RAG 就能稳定支撑“问一句、查一张表、跑一段分析、出一种结论”的研判流程。