本文引用的文件
Agent 与 RAG 是清鉴平台中面向纪检监察与经侦场景的对话式研判能力。其目标是让业务人员用自然语言与案件数据交互:前端通过 SSE 流式对话,后端基于 AgentScope 的 HarnessAgent 驱动 LLM,并结合 pgvector 做表结构与知识库语义检索、意图识别、SQL 与 Python 执行器、附件解析、产物管理与 Insight 结构化研判输出。
从仓库定位看,Agent 子系统位于 ai-server 的 module/agent 域,使用 Spring Boot 3 + MyBatis-Plus + AgentScope 2.0.1;主库为 PostgreSQL + pgvector,分析库为 DuckDB(按工作空间切换),Redis 用于会话缓存与记忆隔离,Elasticsearch 用于知识文档索引。
章节来源
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/* |
graph TB
FE["前端<br/>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 |
聊天入口 | 提供会话 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 流式输出 |
章节来源
Agent 对话不是简单的一次 HTTP 问答,而是“前端 SSE → 后端 Service → AgentScope HarnessAgent → LLM Provider → 工具 → 产物/向量/数据库”的多阶段流水线。
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
图示来源
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 |
时间戳 |
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 |
创建时间 |
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 |
索引与更新时间 |
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 : "一对多"
图示来源
章节来源
前端向后端 /js/a/chat/stream 发起 POST 请求,请求体为 ChatRequestDTO,通常携带:
| 字段 | 含义 |
|---|---|
message |
用户问题 |
sessionKey |
会话 ID(字符串) |
modelId |
可选:本次动态切换的模型 ID |
attachmentIds |
可选:附件 ID 列表 |
后端控制器直接返回 Flux<ServerSentEvent<String>>,不包装成统一 Result,因为 SSE 帧本身承载多种事件类型。
章节来源
AgentChatServiceImpl.stream 先校验消息非空,再取当前打开案件的 caseId,然后解析 sessionKey。若缺少有效会话 ID,会直接返回错误帧。同一会话使用 Semaphore 做流级互斥:同一时刻只允许一条 AI 流,避免多个标签页或重试造成 Agent 记忆污染。
模型优先级为:
modelIdmodel_id如果都没有默认模型,则抛出“未指定模型且无默认模型”的错误。
章节来源
Agent 流通过 HarnessAgent.streamEvents 产出事件,Service 把它们转换成以下 SSE 帧:
| 帧类型 | 含义 |
|---|---|
intent |
意图识别结果 |
suggestions |
下一步追问建议 |
tool_call |
工具开始调用 |
tool_input |
工具入参完整文本 |
tool_result |
工具结果文本(可能截断) |
token |
助手回答增量文本 |
thinking |
模型思考过程 start/delta/end |
graph_data |
关系图谱全量数据帧 |
done |
一轮结束,附带 sessionKey |
error |
错误终止 |
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 帧"]
图示来源
章节来源
向量能力由两个层次控制:
etl.rag.enabledRagSchemaService.isAvailable 要求两者同时满足。若不可用,search 会抛 409,提示“未配置嵌入模型”。
章节来源
EmbeddingProvider 根据 zsjz.embedding.enabled 决定走本地还是在线:
| 模式 | 实现 | 特点 |
|---|---|---|
| 本地 | EmbeddingService(ONNX) |
无需网络,模型目录缺失时不可用 |
| 在线 | EmbeddingModelFactory 构建 agentscope EmbeddingModel |
支持 DashScope/Ollama/OpenAI |
在线模式从 agent_model 表中查找 type='embedding' 的活跃模型,并缓存 30 秒。本地模式下,向量表名带模型后缀(例如 _bge_m3),避免本地与在线向量混用。
章节来源
RagSchemaService 是模板结构向量索引的统一门面。真正的索引实现是 AiCleanTemplateVectorIndex,它将 table_info/table_field 与 ai_clean_template/ai_clean_field 两套模板库元数据向量化进全局 pgvector 表,而不是扫描案件 DuckDB。
关键接口:
| 接口 | 作用 |
|---|---|
isAvailable() |
判断 RAG 能力是否可用 |
reindexWorkspace(workspaceId) |
全量重建索引,幂等覆盖 |
getStatus(workspaceId) |
查询索引状态 |
search(query, topK) |
按自然语言检索物理表与物理列 |
章节来源
RagSchemaSearchTool.searchTableSchema 注册为 Agent 工具 search_table_schema,供 LLM 在需要理解有哪些表、字段含义或编写 SQL 时调用。它把 RagSchemaService.search 的结果序列化为 JSON 传给模型。
章节来源
SqlAnalysisTool.execute_sql 在当前工作空间的 DuckDB 上执行只读 SQL,白名单前缀包括 select、with、pragma、describe、show、explain。它还禁止危险函数,如 read_csv、glob、attach、detach、install、load、copy、export、import、getenv、setenv 等。
执行策略是流式 + 产物:
章节来源
PythonAnalysisTool.executePython 调用 PythonExecutor.execute,在隔离子进程中执行 Python 代码。执行环境限制:
通过 ETL_DB_PATH 访问 DuckDB 快照副本,而非原库
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"]
图示来源
章节来源
附件上传走 ChatAttachmentService.upload,按类型分为三路:
产物由 ArtifactService 负责:工具查出的全量表格结果写成 CSV,前端只拿 artifact 元数据与头部预览,真实全量数据通过产物路径下载。
章节来源
IntentService.safeAnalyze 在把用户问题交给 Agent 之前,复用会话模型做一次轻量 LLM 调用,完成意图分类与语义增强。它是 fail-open 设计:任何异常、超时、解析失败都返回 null,不阻塞主链路。
它会加载最近 N 条消息历史,截断过长内容,拼接系统提示词与用户提示词,并通过结构化输出约束返回意图结果。
章节来源
FollowupService.safeSuggest 在对话结束后,根据最近几轮上下文和本轮问答生成“方向 + 可直接发送的追问”。它同样 fail-open,并对弱模型输出做清洗:去重、过滤空话、限制长度与方向数。
章节来源
InsightServiceImpl 是对象关系分析的智能研判服务。它与通用对话的关键差异是:
agent_insight_* 两张表seq:避免 id 与 create_at 同毫秒撞车章节来源
以下示例描述 API 调用方式,不展示后端源码。
POST /js/a/chat/messages,请求体为 SendMessageDTO,包含 sessionId 和 message。POST /js/a/chat/stream,请求体为 ChatRequestDTO。tool_call:工具开始tool_input:工具入参tool_result:工具结果token:逐字增量文本done:本轮结束error:错误参考实现位置
CreateChatSessionDTO 中传入 modelId。ChatRequestDTO 中传 modelId,实现动态切换。model_id。参考实现位置
etl.rag.enabled 已启用。zsjz.embedding.enabled=true 并提供模型目录。agent_model 表添加 type='embedding' 的活跃记录,provider 可为 DashScope、Ollama 或 OpenAI 兼容。GET /js/a/chat/reindexWorkspace?id={workspaceId} 重建模板结构向量索引。RagSchemaService.getStatus 或前端对应接口查看索引状态。search_table_schema 工具进行语义检索。参考实现位置
tool_call 携带 toolCallId 与 toolNametool_input 携带累积后的工具入参tool_result 携带工具返回文本execute_sql,前端应读取响应中的 artifact 元数据,通过产物接口预览或下载 CSV。execute_python,响应中包含 exitCode、stdout、stderr、imageUrls、durationMs。get_case_graph,后端还会额外下发 graph_data 帧,包含全量关系图数据。参考实现位置
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 : "在线模型"
图示来源
| 外部系统 | 用途 | 风险点 |
|---|---|---|
| AgentScope | Agent 运行时、工具调度、事件流 | LLM Provider 连接、工具 schema 装配 |
| DashScope/Ollama/OpenAI | LLM 与嵌入模型推理 | 网络延迟、密钥过期、维度不一致 |
| PostgreSQL/pgvector | 平台元数据、Agent 会话、向量索引 | 向量表损坏、维度不匹配 |
| DuckDB | 工作空间分析库 | 文件锁、只读白名单绕过 |
| Redis | 会话/缓存 | 内存不足、连接中断 |
| Elasticsearch | 知识文档索引 | 集群不可用、索引分片异常 |
章节来源
| 环节 | 特征 | 优化要点 |
|---|---|---|
| SSE 流 | Reactor 非阻塞流式转发 | 避免单帧过大;工具结果有字符上限 |
| 工具调用 | LLM ReAct 循环 | 减少无关工具;控制结果大小 |
| SQL 执行 | 流式 + 产物 CSV | 不要在前端渲染全量表格 |
| Python 执行 | 子进程 + 快照副本 | 控制数据量与超时 |
| 向量检索 | pgvector 相似度 | 保持索引重建;注意本地/在线向量表隔离 |
| 意图/追问 | 轻量 LLM 辅助 | fail-open,失败不阻塞主链路 |
| 会话并发 | 进程内 Semaphore | 多实例部署需补充 DB 状态位 |
[本节为通用性能讨论,不直接分析具体文件]
现象:前端连接建立后很快断开,或长时间没有新帧。
可能原因:
排查步骤:
Chat stream error。参考实现位置
现象:创建会话或发送消息时报错“未指定模型且无默认模型”,或 LLM 调用失败。
可能原因:
agent_model 中没有 default_model=true 的活跃 type=llm 模型。排查步骤:
ollama 走 Ollama;含 dashscope/阿里/通义/qwen 走 DashScope;其余走 OpenAI 兼容。dimensions 是否与返回向量维度一致。参考实现位置
现象:调用 search_table_schema 或 RAG 接口返回空,或报“表结构向量索引为空”。
可能原因:
etl.rag.enabled=false。排查步骤:
GET /js/a/chat/reindexWorkspace?id={workspaceId} 重建索引。RagSchemaService.getStatus 返回的 status、vectorTable、dimensions。EmbeddingProvider.isAvailable() 与 unavailableMessage()。参考实现位置
现象:execute_python 返回错误、超时、找不到数据库文件或图片无法显示。
可能原因:
CaseContextHolder 为空。python.timeoutSeconds。排查步骤:
PythonExecutor.isAvailable() 与环境检测日志。dbPath 是否存在。os.environ['ETL_DB_PATH'] 连接快照副本。/js/a/py/files/{workspaceId}/{execId}/{filename}。参考实现位置
现象:上传后状态为 FAILED,或提示不支持的文件类型、文件大小超限。
可能原因:
caseId 与当前打开案件不一致。参考实现位置
现象:Insight 会话长时间 STREAMING,或再次追问时报“正在进行中”。
可能原因:
排查步骤:
AgentEndEvent。参考实现位置
Agent 与 RAG 在清鉴平台中构成了从自然语言到数据分析的闭环:前端通过 SSE 获得实时流式体验,后端通过 AgentScope 组合 LLM、工具、向量检索与产物管理,最终输出文本、图表、CSV 产物、关系图和结构化 Insight。
实践中最关键的是三个边界:
只要维护好模型配置、RAG 索引状态、Python 环境和附件权限,Agent 与 RAG 就能稳定支撑“问一句、查一张表、跑一段分析、出一种结论”的研判流程。