# 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 就能稳定支撑“问一句、查一张表、跑一段分析、出一种结论”的研判流程。