本文引用的文件
本文件聚焦 Insight 工作区与 Workspace Catalog,目标是解释以下问题:
AgentInsightSession 与 AgentInsightMessage 的数据模型、持久化边界与安全隔离。InsightAgentFactory 如何启动分析型 Agent,以及与通用 Agent 的区别。useInsightStream 如何通过 SSE 消费 Insight 流式输出。WorkspaceSchemaCatalog)与案件数据源、用户会话的关系。Insight 能力位于后端 ai-server/module/agent/insight 和前端 ai-electron/frontend/src/graph/insight 两个区域:
sql/insight_agent_tables.sql,不复用通用对话表。设计意图由 docs/design/continuous-insight-plan.md 明确说明。
graph TB
subgraph "前端"
FE_UI["Insight 抽屉 UI"]
FE_HOOK["useInsightStream.ts"]
FE_API["graph/insight/api.ts"]
end
subgraph "后端"
CTRL["InsightController"]
SVC["InsightServiceImpl"]
ENTITY_S["AgentInsightSession"]
ENTITY_M["AgentInsightMessage"]
FACTORY["InsightAgentFactory"]
PROMPT["InsightPromptBuilder"]
CTX["InsightContextBuilder"]
CATALOG["WorkspaceSchemaCatalog"]
DB[("PostgreSQL<br/>agent_insight_session / agent_insight_message")]
end
FE_UI --> FE_HOOK
FE_HOOK --> FE_API
FE_API --> CTRL
CTRL --> SVC
SVC --> ENTITY_S
SVC --> ENTITY_M
SVC --> FACTORY
SVC --> PROMPT
SVC --> CTX
FACTORY --> CATALOG
ENTITY_S --> DB
ENTITY_M --> DB
图表来源
章节来源
Insight 使用两张独立表,与通用对话表物理隔离:
agent_insight_session:会话元数据、上下文快照、系统提示词、消息水位、统计字段。agent_insight_message:用户提问、助手回答、首轮自动分析结果、工具调用明细、结构化产物。AgentInsightSession关键字段职责如下:
id:主键,应用侧生成,非单调。caseId:案件级硬隔离边界,所有查询强制携带。bizType:研判场景维度,当前固定为图谱关系。scope:分析范围,本期主要为整批结果集。agentRowId、modelId:使用的 Agent 配置行与默认模型。title:会话标题,包含时间信息。context:查询条件加图谱快照的 JSONB,是动态系统提示词的数据源。contextHash:对 context 做 SHA-256,用于同条件复用和去重提示。sysPrompt:会话内实际使用的完整系统提示词,不可变且不下发接口。messageCount、lastSeq、totalTokens、lastMessageAt:列表展示与排序辅助。pinned:是否置顶。status:ACTIVE、STREAMING、ARCHIVED,STREAMING 用于并发抢占。createAt、updateAt:创建与更新时间。该实体的注释明确指出:隔离通过“独立表 + case_id 强制过滤 + session_id 与 case_id 双条件”建立;本实体只承载会话元数据,查询条件与结果快照落在 context,完整提示词落在 sys_prompt。
章节来源
AgentInsightMessage关键字段职责如下:
id:主键,应用侧生成。sessionId、caseId:归属到具体 Insight 会话,并冗余案件 ID 以强化越权防护。seq:会话内单调递增序号,是稳定排序列。role:user、assistant、system。content:正文文本。contentType:text 或 insight。messageType:chat、first_round、followup。metadata:轻量 JSONB,如思考过程、中断标记、耗时。toolEvents:工具调用明细,单条可达数百 KB,列表查询不 select。outputJson:首轮 assistant 消息的结构化产物 JSONB。tokenCount:Token 用量。replyToMessageId:追问指向的首轮消息,供 UI 分组。starred:收藏标记。status:DONE 或 INTERRUPTED。createAt:创建时间。DDL 中定义了 (session_id, seq) 唯一约束和外键级联删除,并说明:不能用 id 或 create_at 排序,必须用 seq;并发分配 seq 应通过原子更新会话水位而非 SELECT MAX。
章节来源
前端 types.ts 把后端 VO 映射为 TypeScript 类型,并强调:ID 一律按字符串处理,避免 Long 精度丢失。主要类型包括:
InsightCreateParams:创建会话入参,包含案件、业务类型、分析范围、过滤条件、图谱快照、topN。InsightSessionVO:会话返回体,含 status、context、messageCount 等。InsightMessageVO:消息返回体,不含 toolEvents。InsightMessagePage:MyBatis-Plus Page 形状。InsightStreamParams:流式入参,首轮 message 留空。InsightPayloadWithSource:结构化产物扩展类型。章节来源
Insight 的整体流程可以分成三类路径:
/insight/sessions → 后端构建上下文与提示词 → 写入 agent_insight_session。/insight/stream → 后端获取 Agent 实例 → 执行 AgentScope 事件流 → 写消息 → SSE 推送 token、工具调用、thinking、insight、done/error。历史与追溯:GET /insight/sessions、/sessions/{id}、/sessions/{id}/messages、/messages/{messageId}/trace、/sessions/{id}/delete。
sequenceDiagram
participant FE as "前端 useInsightStream"
participant API as "graph/insight/api.ts"
participant CTRL as "InsightController"
participant SVC as "InsightServiceImpl"
participant CTX as "InsightContextBuilder"
participant PROMPT as "InsightPromptBuilder"
participant DB as "PostgreSQL"
participant FACTORY as "InsightAgentFactory"
participant AG as "HarnessAgent"
FE->>API: createInsightSession(params)
API->>CTRL: POST /insight/sessions
CTRL->>SVC: createSession(dto)
SVC->>CTX: build(caseId, caseName, dto)
SVC->>PROMPT: build(context)
SVC->>DB: INSERT agent_insight_session
SVC-->>FE: InsightSessionVO
FE->>API: streamInsight({sessionId, caseId})
API->>CTRL: POST /insight/stream
CTRL->>SVC: stream(dto)
SVC->>FACTORY: getOrCreate(sessionId, modelId, sysPrompt)
FACTORY->>AG: 构造 HarnessAgent
loop 流式事件
AG-->>SVC: token / thinking / tool_call / tool_result / insight / done
SVC->>DB: 插入或更新 agent_insight_message
SVC-->>FE: ServerSentEvent
end
图表来源
classDiagram
class AgentInsightSession {
+Long id
+Long caseId
+String bizType
+String scope
+Long agentRowId
+Long modelId
+String title
+String context
+String contextHash
+String sysPrompt
+Integer messageCount
+Long lastSeq
+Long totalTokens
+LocalDateTime lastMessageAt
+Boolean pinned
+String status
+LocalDateTime createAt
+LocalDateTime updateAt
}
class AgentInsightMessage {
+Long id
+Long sessionId
+Long caseId
+Long seq
+String role
+String content
+String contentType
+String messageType
+String metadata
+String toolEvents
+String outputJson
+Integer tokenCount
+Long replyToMessageId
+Boolean starred
+String status
+LocalDateTime createAt
}
AgentInsightSession <|-- AgentInsightMessage : "一对多"
图表来源
创建 Insight 会话时,后端完成以下关键步骤:
CaseAccessGuard 校验请求中的 caseId 属于当前用户打开的案件。AgentModelService 获取默认模型,无默认模型直接拒绝。InsightContextBuilder.build 规整分析条件、图谱快照、topN,并裁剪图谱。InsightPromptBuilder.build 拼接静态人设、动态上下文块、结构化产物契约、RAG 指引。context、contextHash、sysPrompt 写入会话表。InsightSessionVO,不包含完整的 sysPrompt。InsightPromptBuilder 的注释强调:它不复用通用对话的渲染契约,因为 Insight 的 insight围栏结构与通用表格/ECharts/graph 载荷不同,混在一起会互相污染。首次自动分析的指令由firstRoundInstruction()` 提供,前端打开抽屉时不敲字,由服务端注入。
flowchart TD
Start(["创建 Insight 会话"]) --> Guard["案件归属校验"]
Guard --> ModelCheck{"是否存在默认模型"}
ModelCheck -->|否| Error["返回错误:未配置默认模型"]
ModelCheck -->|是| BuildCtx["InsightContextBuilder 构建上下文"]
BuildCtx --> TrimGraph["裁剪图谱快照<br/>按度数保留 topN 节点与两端节点边"]
TrimGraph --> BuildPrompt["InsightPromptBuilder 拼装系统提示词"]
BuildPrompt --> Persist["写入 agent_insight_session"]
Persist --> Return["返回 InsightSessionVO"]
图表来源
章节来源
InsightAgentFactory 的核心职责是“取或建研判 Agent 实例”,并维护一个独立实例池。关键点如下:
insight-s,附加 sessionId,确保动态提示词不会在不同会话之间串台。w{ws}-a{agent}-m{model},不含 sysPrompt 维度;复用会导致第二次分析拿到第一次的提示词。AgentStateStore 按 (userId, sessionId) 隔离;实例回收不影响会话记忆,只是下次重建 Agent 配置。buildAgent 刻意为之的差异:
默认装备 graph 组而非 person 组,起点就是关系图谱数据。
classDiagram
class InsightAgentFactory {
+String AGENT_NAME
-Map~String, HarnessAgent~ agentPool
-Map~String, Long~ lastAccessAt
-ReentrantLock poolLock
+getOrCreate(sessionId, modelId, sysPrompt) HarnessAgent
+evict(sessionId) void
-buildAgent(modelId, sysPrompt) HarnessAgent
}
class HarnessAgent {
<<external>>
}
InsightAgentFactory --> HarnessAgent : "创建并缓存"
图表来源
章节来源
useInsightStream:SSE 流式消费useInsightStream 是一个独立的组合式 hook,专门管理 Insight 抽屉的状态机,而不是复用全局 chat store。其状态流转为:
主要行为包括:
seq DESC,前端 reverse 后升序展示。insight 载荷作为结构化产物。使用 AbortController 支持关闭抽屉时中止流。
stateDiagram-v2
[*] --> Idle
Idle --> Creating : "创建会话"
Creating --> Streaming : "开始流式"
Streaming --> Done : "收到 done"
Streaming --> Error : "模型未配置 / 网络 / 超限 / 409"
Done --> Idle : "关闭或切换会话"
Error --> Idle : "重试或关闭"
图表来源
章节来源
这里有两个容易混淆的概念需要区分:
WorkspaceSchemaCatalog 是可查表目录,来源是模板库元数据,不是 DuckDB 运行时扫描。它聚合既有模板库 table_info/table_field 与 AI 智能清洗动态表 ai_clean_template/ai_clean_field,输出 Agent 可使用的表结构清单。它不读取 DuckDB 系统表,也不暴露内部表给模型。WorkspaceManagerFactory 解析 Agent 运行时工作目录,路径形如 {cwd}/.agentscope/users/{userId}/workspaces/{workspaceId}/agents/{agentRowId}/,用于 skills、记忆、子 Agent、事件转录等文件系统隔离。因此,Workspace Catalog 不等于 DuckDB 数据源目录;它是“模型知道有哪些表可以查”的元数据目录。而 Workspace 文件系统是 AgentScope 运行时的工作空间根。
graph TB
WS_CAT["WorkspaceSchemaCatalog<br/>模板库元数据目录"]
FILE_WS["WorkspaceManagerFactory<br/>Agent 工作目录"]
TEMPLATE["table_info / table_field"]
AICLEAN["ai_clean_template / ai_clean_field"]
USER["用户"]
WORKSPACE["业务工作空间"]
AGENT["Agent 配置"]
TEMPLATE --> WS_CAT
AICLEAN --> WS_CAT
USER --> FILE_WS
WORKSPACE --> FILE_WS
AGENT --> FILE_WS
图表来源
章节来源
Insight 模块的关键依赖链如下:
graph LR
Controller["InsightController"] --> Service["InsightServiceImpl"]
Service --> SessionEntity["AgentInsightSession"]
Service --> MessageEntity["AgentInsightMessage"]
Service --> ContextBuilder["InsightContextBuilder"]
Service --> PromptBuilder["InsightPromptBuilder"]
Service --> AgentFactory["InsightAgentFactory"]
AgentFactory --> StateStore["AgentStateStore"]
AgentFactory --> Catalog["WorkspaceSchemaCatalog"]
Service --> CaseAccess["CaseAccessGuard"]
Service --> ModelService["AgentModelService"]
Service --> CaseInfo["CaseInfoMapper"]
图表来源
useInsightStream,避免与全局 chat store 冲突。从现有结构看,没有明显的循环导入:Controller → Service → Entity/Mapper/Factory/Prompt/Context,Factory 依赖 Catalog 和 StateStore,Catalog 依赖模板库 Mapper,彼此单向。
章节来源
agent_chat_session / agent_message。tool_events 与 output_json,避免大字段拖慢列表。output_json 仅首轮 assistant 消息有值,前端也可从 SSE 的 insight 帧提前渲染。Semaphore,因为抢占发生在订阅线程,释放发生在 Reactor 回调线程;ReentrantLock 跨线程 unlock 会抛异常。status 字段:ACTIVE、STREAMING、ARCHIVED。seq,不是 id 或 create_at。章节来源
可能原因:
agent_insight_session,但消息随外键级联删除,因此消息也消失。sessionId,导致新会话替代旧会话。排查建议:
agent_insight_session.status 是否为 ACTIVE 或 ARCHIVED。useInsightStream 的 sessionId 与 caseId 是否一致。InsightAgentFactory 实例池是否已 evict。章节来源
可能原因:
排查建议:
agent_insight_message.content 是否为空。章节来源
可能原因:
.agentscope 目录。排查建议:
WorkspaceManagerFactory 生成的路径是本地绝对路径,不是共享卷路径。章节来源
可能原因:
text/event-stream。排查建议:
Content-Type: text/event-stream。章节来源
InsightCreateParams:包含 caseId、可选 bizType、scope、filters、graph、topN。createInsightSession,即 POST /insight/sessions。InsightSessionVO,随后立即发起首轮流式分析。参考实现位置:
章节来源
Insight 的图谱关联方式是“快照”:
InsightGraphSnapshot,随创建会话请求发送。InsightContextBuilder.trimGraph 按度数裁剪,只保留 topN 节点及其两端都在的边。WorkspaceSchemaCatalog 暴露的模板库元数据间接影响 Agent:Agent 可通过 RAG 工具检索表结构,再写 SQL 查询。参考实现位置:
章节来源
streamInsight,连接 /insight/stream。insight 帧用于结构化产物。useInsightStream 根据消息类型区分首轮分析与追问。参考实现位置:
章节来源
Insight 的“清理”有两种层次:
/insight/sessions/{id}/delete,消息随外键级联删除。WorkspaceManagerFactory 提供路径解析,但不直接提供删除方法;删除应由运维或上层生命周期管理,注意多节点部署下本地文件不一致风险。参考实现位置:
章节来源
Insight 工作区的设计核心是“独立、安全、可追溯”:
case_id 冗余、外键级联、案件访问守卫、SQL 层越权防护。seq 稳定排序,trace 按需拉取,结构化产物持久化。Workspace Catalog 负责告诉 Agent “有哪些模板表可以查”,Workspace 文件系统负责隔离 Agent 运行态文件;两者共同支撑 Insight 的外部数据能力,但不应与 Insight 自身的持久化数据混淆。普通 Agent 对话侧重通用交互,Insight 侧重图谱关系研判、结构化线索产出与 SSE 流式体验。