|
|
@@ -0,0 +1,418 @@
|
|
|
+# 普通聊天 + 任务流水线(意图路由)设计方案
|
|
|
+
|
|
|
+> 状态:设计稿,未实施
|
|
|
+> 日期:2026-10-20
|
|
|
+> 范围:`ai-server/src/main/java/com/zsjz/ai/module/agent/`(意图识别、闲聊/任务流水线)
|
|
|
+> 关联需求:在现有「数据分析」AI 流水线之外,新增一条同时承接**普通聊天**与**任务执行**的流水线,由意图识别按条消息自动路由。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 0. 需求口径
|
|
|
+
|
|
|
+用户原话中的「人物」按同音词「任务」理解(已确认:"任务。")。要新增**一条** AI 流水线,同时承接:
|
|
|
+
|
|
|
+- **普通聊天**:问候、感谢、能力询问、与案件数据无关的一般问答;
|
|
|
+- **任务执行**:工作区文件读写/检索、执行命令、执行 Python 脚本、批量文件处理等。
|
|
|
+
|
|
|
+该流水线与现有数据分析流水线并存,按意图**逐条消息自动路由**。分类失败、超时、未知意图一律回落到**数据分析流水线**(保持现状,fail-safe)。现有数据分析 agent 的 67 个业务工具、提示词、SSE 契约全部不动。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 1. 现状结论(决定了改法)
|
|
|
+
|
|
|
+- `IntentMiddleware` 只做三件事:调 LLM 分类、往输入追加 `<intent-analysis>` 合成消息、发 `intent` SSE 帧;**不改变执行路径**。所有意图都走同一个 `HarnessAgent`、同一套工具。
|
|
|
+- `AgentChatServiceImpl#doStreamLocked` 是唯一调用 `AgentService.getOrCreateAgent` 的地方,路由改造点单一。
|
|
|
+- `IntentEnum` 目前是死代码(`fromCode` 无调用方),`IntentResult` 注释、`prompts/intent-recognition.txt` 里还留着旧的二分类 `《闲聊或无关指令》/《可能的数据分析请求》` 和已不存在的 `response` 字段。
|
|
|
+- `IntentProperties.timeoutSeconds/maxTokens` 从未生效;`IntentService.safeAnalyze` 的 `.block()` 没有超时,模型卡住会阻塞整条流。
|
|
|
+- AgentScope 2.0.1 Harness 自带以下工具(已从 jar 核实):
|
|
|
+ - 文件:`read_file`、`write_file`、`edit_file`、`grep_files`、`glob_files`、`list_files`;
|
|
|
+ - 命令:`execute`(仅当文件系统是 `AbstractSandboxFilesystem` 时注册,`LocalFilesystemSpec` 满足);
|
|
|
+ - 构建器开关:`disableShellTool()`、`disableMemoryTools()`、`enableTaskList(boolean)`、`toolsConfig(...)`;
|
|
|
+ - `LocalFilesystemSpec`:`project(Path)`(同时设置 shell cwd 与路径策略根)、`mode(LocalFsMode)`、`addRoot(Path)`、`executeTimeoutSeconds(int)`、`maxOutputBytes(int)`、`env(...)`、`inheritEnv(boolean)`。
|
|
|
+- 现有数据分析 agent 也挂了 `LocalFilesystemSpec`,所以它本来就有 shell/文件工具。新流水线的价值是:**去掉 55 个业务工具 + 数据 render 契约,换成任务型提示词与更严格的路径边界**。
|
|
|
+- 已有独立流水线的成熟先例是 AI 研判(`InsightController` + `InsightAgentFactory` + 静态 prompt),但它按 endpoint 隔离,不是按意图。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 2. 总体架构
|
|
|
+
|
|
|
+```
|
|
|
+POST /chat/stream
|
|
|
+ ├─ 会话/附件/模型解析(沿用现有逻辑)
|
|
|
+ ├─ ChatIntentRouter.route(modelId, sessionId, question)
|
|
|
+ │ └─ IntentService.safeAnalyze → LlmService.chatAs(超时/关思考/fail-open)
|
|
|
+ │ ├─ data_query / schema_explore / etl_clean / visualization / null
|
|
|
+ │ │ → DataAnalysisPipeline(现有 HarnessAgent + AgentService 实例池)
|
|
|
+ │ └─ task_execute / general_chat
|
|
|
+ │ → TaskPipeline(TaskAgentFactory:文件/命令/Python + 任务提示词)
|
|
|
+ ├─ 事件流头部注入 intent CustomEvent(code/label/pipeline/enhanced/entities)
|
|
|
+ └─ 原 SSE 转换、工具事件聚合、assistant 落库、心跳、取消处理全部复用
|
|
|
+```
|
|
|
+
|
|
|
+两条流水线都返回 `Flux<AgentEvent>`,因此 `AgentChatServiceImpl` 里那一大段事件→SSE 的转换代码**一行不改**。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 3. 意图层改造
|
|
|
+
|
|
|
+### 3.1 `IntentEnum`(新增枚举值与流水线归属)
|
|
|
+
|
|
|
+```java
|
|
|
+DATA_QUERY("data_query", "数据查询", DATA_ANALYSIS)
|
|
|
+SCHEMA_EXPLORE("schema_explore", "表结构探索", DATA_ANALYSIS)
|
|
|
+ETL_CLEAN("etl_clean", "数据处理", DATA_ANALYSIS)
|
|
|
+VISUALIZATION("visualization", "图表可视化", DATA_ANALYSIS)
|
|
|
+TASK_EXECUTE("task_execute", "任务执行", TASK)
|
|
|
+GENERAL_CHAT("general_chat", "通用问答", TASK)
|
|
|
+
|
|
|
+// 宽松归一:去《》/空白/大小写,先匹配 code 再匹配 label;未知返回 null。
|
|
|
+// 不要回落 GENERAL_CHAT,否则未知意图会被误送任务流水线。
|
|
|
+public static IntentEnum fromAny(String raw)
|
|
|
+```
|
|
|
+
|
|
|
+`IntentEnum.fromCode` 保留(兼容),新增 `fromAny` 供路由使用。
|
|
|
+
|
|
|
+### 3.2 `IntentResult` + 提示词
|
|
|
+
|
|
|
+- `IntentResult.intent` 的语义改为 **code**(`data_query` 等),修掉过期的 `《...》` 注释。
|
|
|
+- 重写 `prompts/intent-recognition.txt`:
|
|
|
+ - 6 个标签,写清每条流水线的判定边界;
|
|
|
+ - 明确规则:最新输入为主、历史只补指代;**拿不准 → `data_query`**;纯问候/能力询问 → `general_chat`;只有明确的文件/命令/脚本操作才 `task_execute`;
|
|
|
+ - 删除 `《》` 强制要求、删除旧二分类段落和 `response` 字段;
|
|
|
+ - 移除 `{format}` 占位(schema 交给 `LlmService.chatAs` 追加),只保留 `{multi_turn}`。
|
|
|
+- 数据结构仍走 `@JsonPropertyDescription` 生成 schema。
|
|
|
+
|
|
|
+### 3.3 `IntentService` 改造
|
|
|
+
|
|
|
+- 注入 `LlmService`,用 `chatAs(request, IntentResult.class)` 替换自建 `ReActAgent`:
|
|
|
+ - `enableThinking(false)`(分类任务不需要思考,避免 reasoning 吃掉 token);
|
|
|
+ - `maxTokens(props.maxTokens)`、`timeout(Duration.ofSeconds(props.timeoutSeconds))` —— 这两个配置终于生效;
|
|
|
+ - 任何异常/解析失败 → `null`(fail-open)。
|
|
|
+- 返回新 record `IntentDecision(IntentEnum intent, String enhanced, List<String> entities)`;`fromAny` 为 null 时返回 null。
|
|
|
+
|
|
|
+### 3.4 `IntentMiddleware`(只保留为回滚路径)
|
|
|
+
|
|
|
+- 改为消费 `IntentDecision`,渲染时用 `label`,`intent` 帧仍下发中文 label(前端兼容)。
|
|
|
+- `AgentService.buildAgent` 中**条件挂载**:
|
|
|
+
|
|
|
+```java
|
|
|
+if (!etlProperties.getIntent().isRouteEnabled()) {
|
|
|
+ b.middleware(new IntentMiddleware(intentService, etlProperties.getIntent(), modelId));
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+- 路由开启时中间件不挂载,避免每轮分类两次。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 4. 路由层
|
|
|
+
|
|
|
+新增 `module/agent/intent/ChatIntentRouter.java`:
|
|
|
+
|
|
|
+```java
|
|
|
+public record RouteResult(IntentDecision decision, ChatPipeline pipeline) {}
|
|
|
+
|
|
|
+public RouteResult route(Long modelId, Long sessionId, String question) {
|
|
|
+ if (!intentEnabled || !routeEnabled) return new RouteResult(null, dataPipeline);
|
|
|
+ IntentDecision d = intentService.safeAnalyze(modelId, sessionId, question);
|
|
|
+ if (d == null) return new RouteResult(null, dataPipeline);
|
|
|
+ ChatPipeline p = d.intent().pipeline() == PipelineType.TASK ? taskPipeline : dataPipeline;
|
|
|
+ return new RouteResult(d, p);
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+`intent` 帧由 `IntentFrames.of(decision)` 统一生成:
|
|
|
+
|
|
|
+```json
|
|
|
+{"type":"intent","code":"task_execute","label":"任务执行","intent":"任务执行",
|
|
|
+ "pipeline":"task","enhanced":"...","entities":[]}
|
|
|
+```
|
|
|
+
|
|
|
+保留 `intent` = label 是前端兼容做法;新增 `code/label/pipeline` 供 UI 精确展示。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 5. 流水线抽象
|
|
|
+
|
|
|
+新增 `module/agent/pipeline/`:
|
|
|
+
|
|
|
+```java
|
|
|
+public interface ChatPipeline {
|
|
|
+ PipelineType type(); // DATA_ANALYSIS / TASK
|
|
|
+ Flux<AgentEvent> stream(ChatPipelineContext ctx);
|
|
|
+}
|
|
|
+
|
|
|
+public record ChatPipelineContext(
|
|
|
+ Long sessionId, Long caseId, Long modelId, Long agentRowId,
|
|
|
+ String userId, SkillFilter skillFilter,
|
|
|
+ String userText, List<Msg> messages, IntentDecision intent) {}
|
|
|
+```
|
|
|
+
|
|
|
+### 5.1 `DataAnalysisPipeline`
|
|
|
+
|
|
|
+把现有 `doStreamLocked` 里的 agent 获取 + `RuntimeContext` 构建搬进来,行为保持不变:
|
|
|
+
|
|
|
+```java
|
|
|
+HarnessAgent agent = agentService.getOrCreateAgent(ctx.caseId(), ctx.agentRowId(), ctx.modelId());
|
|
|
+RuntimeContext rc = RuntimeContext.builder()
|
|
|
+ .userId(ctx.userId())
|
|
|
+ .sessionId(String.valueOf(ctx.sessionId()))
|
|
|
+ .put(SkillFilter.class, ctx.skillFilter())
|
|
|
+ .build();
|
|
|
+return agent.streamEvents(ctx.messages(), rc);
|
|
|
+```
|
|
|
+
|
|
|
+### 5.2 `TaskPipeline`
|
|
|
+
|
|
|
+```java
|
|
|
+HarnessAgent agent = taskAgentFactory.getOrCreate(ctx.userId(), ctx.caseId(), ctx.modelId());
|
|
|
+RuntimeContext rc = RuntimeContext.builder()
|
|
|
+ .userId(ctx.userId())
|
|
|
+ .sessionId(ctx.sessionId() + "-task") // 与数据流水线隔离记忆槽
|
|
|
+ .put(SkillFilter.class, ctx.skillFilter())
|
|
|
+ .build();
|
|
|
+return agent.streamEvents(ctx.messages(), rc);
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 6. 任务流水线(`module/agent/task/`)
|
|
|
+
|
|
|
+### 6.1 `TaskAgentFactory`
|
|
|
+
|
|
|
+沿用 `InsightAgentFactory` + `AgentInstancePool` 的模式:
|
|
|
+
|
|
|
+- 池 key:`task-u{userId}-w{caseId}-m{modelId}`;上限 32,空闲 30 分钟回收;构建在锁外。
|
|
|
+- 工作区:`WorkspaceManagerFactory.resolveAgentDataPath(cwd, userId, caseId, 9528L)`(固定虚拟 agent id,避免和真实 `agent.row_id` 冲突)。
|
|
|
+- 构建:
|
|
|
+
|
|
|
+```java
|
|
|
+HarnessAgent.builder()
|
|
|
+ .name("task-agent").description("通用聊天与任务执行助手")
|
|
|
+ .sysPrompt(taskPromptBuilder.build(caseId))
|
|
|
+ .maxIters(props.getMaxIters()) // 默认 20
|
|
|
+ .model(model).agentId("task-agent")
|
|
|
+ .workspace(workspace)
|
|
|
+ .filesystem(new LocalFilesystemSpec()
|
|
|
+ .isolationScope(IsolationScope.USER)
|
|
|
+ .project(workspace) // shell cwd = 工作区;ROOTED 路径策略根
|
|
|
+ .mode(LocalFsMode.ROOTED)
|
|
|
+ .executeTimeoutSeconds(props.getShellTimeoutSeconds())
|
|
|
+ .maxOutputBytes(props.getMaxOutputBytes())
|
|
|
+ .addRoot(attachmentRoot(userId, caseId))) // 允许读本案附件
|
|
|
+ .toolkit(new Toolkit()) // 空业务工具
|
|
|
+ .enableMetaTool(false) // 不要 reset_equipped_tools
|
|
|
+ .enableTaskList(true) // todo_write,多步任务可规划
|
|
|
+ .disableMemoryTools() // 去掉 memory_*/session_*,避免与后缀 sessionId 冲突
|
|
|
+ .stateStore(agentStateStore);
|
|
|
+if (!props.isShellEnabled()) builder.disableShellTool();
|
|
|
+if (props.isSkillsEnabled() && 用户技能目录存在) builder.skillRepository(...);
|
|
|
+```
|
|
|
+
|
|
|
+### 6.2 工具清单(全部是 Harness 内置,无需新写 `@Tool`)
|
|
|
+
|
|
|
+| 工具 | 来源 | 用途 |
|
|
|
+|---|---|---|
|
|
|
+| `read_file` / `write_file` / `edit_file` | FilesystemTool | 文件读写改 |
|
|
|
+| `grep_files` / `glob_files` / `list_files` | FilesystemTool | 文件检索 |
|
|
|
+| `execute` | ShellExecuteTool | 命令、`python xxx.py`、批量文件操作 |
|
|
|
+| `todo_write` | enableTaskList | 多步任务清单 |
|
|
|
+
|
|
|
+**Python 策略**:v1 不注册 `execute_python`(它绑定案件 DuckDB 快照、属于数据分析语义);Python 脚本用 `execute` 执行。若后续需要结构化 Python 工具,再加一个工作区 cwd 的 `run_python`,不依赖案件库。
|
|
|
+
|
|
|
+### 6.3 工作区与安全边界
|
|
|
+
|
|
|
+- `project(workspace)` + `LocalFsMode.ROOTED`:绝对路径只能落在工作区、project、`addRoot` 内;`..`/越界抛 `SecurityException`。
|
|
|
+- `addRoot` 只加 `{user.dir}/QingJian/chat-attachments/{userId}/{caseId}`,让任务 agent 能处理附件;不暴露整个仓库。
|
|
|
+- **环境变量不要全量继承**:`inheritEnv` 默认 false;显式注入最小集合 `PATH/SystemRoot/SystemDrive/TEMP/TMP/COMSPEC/HOME/USERPROFILE/LANG/PYTHONIOENCODING`,避免把数据库密码等进程环境泄露给模型。
|
|
|
+- `shell-enabled` 配置开关:关闭时 `disableShellTool()`,任务流水线退化为只读/写文件。
|
|
|
+- 提示词层再加一道:禁止删除/覆盖前不确认、禁止访问工作区外路径、失败如实报告。
|
|
|
+- 说明:现有数据分析 agent 本来就有 shell 能力,本次不新增攻击面;但把任务链路作为一等功能后,建议后续接 `SandboxFilesystemSpec`(Docker)做 OS 级隔离。本期先做路径/环境/开关三层约束。
|
|
|
+
|
|
|
+### 6.4 提示词 `prompts/task-agent.txt`
|
|
|
+
|
|
|
+要点:
|
|
|
+
|
|
|
+1. 角色:清鉴工作区里的通用任务助手;
|
|
|
+2. 能力清单与工具使用优先级(有专用文件工具就不要用 shell);
|
|
|
+3. 工作区规则:相对路径、禁止越界、附件路径可直接读;
|
|
|
+4. 行为规则:闲聊直接回答不调工具;任务先规划再执行;删除/覆盖先确认;不编造执行结果;
|
|
|
+5. 数据查询类问题(查案件库/统计业务数据)不在职责内,提示用户换数据分析模式——作为误路由的兜底;
|
|
|
+6. 输出:中文 Markdown,结束时说明"做了什么/产物在哪/如何验证"。
|
|
|
+
|
|
|
+### 6.5 记忆隔离
|
|
|
+
|
|
|
+- 两条流水线共用 `AgentStateStore` 实例,但 `RuntimeContext.sessionId` 不同:数据用 `{sessionId}`,任务用 `{sessionId}-task`。
|
|
|
+- 原因:若共用槽位,数据链路的 `execute_sql` 等 tool_use 消息会被任务 agent 加载,而任务 agent 没有这些工具,部分厂商会直接拒绝请求。
|
|
|
+- 代价:任务 agent 不直接看到数据链路的 agent 记忆;靠意图增强的 `enhanced` 问题 + DB 历史(分类器每次都读)补上下文。任务流水线首版不生成 followup 建议(`FollowupMiddleware` 只挂在数据 agent 上)。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 7. `AgentChatServiceImpl#doStreamLocked` 改造顺序
|
|
|
+
|
|
|
+```java
|
|
|
+// 1. 会话/附件(不变)
|
|
|
+// 2. 模型解析(不变)+ 显式校验模型存在(新增 AgentModelMapper.selectById)
|
|
|
+// 3. 意图路由(新增):RouteResult route = chatIntentRouter.route(...)
|
|
|
+// 4. 用户消息落库(从原位置后移:分类历史不再包含本轮问题)
|
|
|
+// 5. 组装 msgs:用户消息 +(有决策时)<intent-analysis> 合成消息
|
|
|
+// 6. modelChanged 回写(模型已校验,可安全回写)
|
|
|
+// 7. events = intent 帧 + route.pipeline().stream(ctx)
|
|
|
+// 8. 原 SSE 事件转换/落库/心跳/取消逻辑保持不变
|
|
|
+```
|
|
|
+
|
|
|
+注意:
|
|
|
+
|
|
|
+- `IntentMiddleware` 里原来的 synthetic 消息注入逻辑抽成 `IntentMessages.analysis(decision)`,两条流水线共用。
|
|
|
+- `agentRowId`、`RuntimeContext`、`SkillFilter` 的获取方式不变;`SkillFilter` 放进 `ChatPipelineContext`。
|
|
|
+- 数据流水线的池 key、工具装配、提示词追加全部不变。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 8. 前端改动(最小集)
|
|
|
+
|
|
|
+- `src/ai/api/types.ts`:`SseIntentData` 增加 `code? / label? / pipeline?: 'data' | 'task'`。
|
|
|
+- `src/ai/components/StreamStatusBar.vue`:`raw = intent.label || intent.intent`;tooltip 增加流水线名称;标签逻辑不变。
|
|
|
+- `src/ai/utils/constants.ts`:`TOOL_LABELS` 增加
|
|
|
+ `read_file/write_file/edit_file/grep_files/glob_files/list_files/execute/todo_write` 等中文标签(否则时间线显示原始英文工具名)。
|
|
|
+- `chatStream.ts` 已整对象存 `message.intent`,无需改;`sse.ts` 已转发 `intent`,无需改。
|
|
|
+- 不加新页面、不加新模式选择器;用户无感知,只在状态栏看到"任务执行/通用问答"标签。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 9. 配置
|
|
|
+
|
|
|
+```yaml
|
|
|
+etl:
|
|
|
+ intent:
|
|
|
+ enabled: true
|
|
|
+ route-enabled: true # false = 完全回到现状(中间件分类 + 单一流水线)
|
|
|
+ timeout-seconds: 5
|
|
|
+ history-turns: 3
|
|
|
+ max-tokens: 512
|
|
|
+ task-agent:
|
|
|
+ enabled: true
|
|
|
+ max-iters: 20
|
|
|
+ pool-max: 32
|
|
|
+ idle-timeout-minutes: 30
|
|
|
+ shell-enabled: true
|
|
|
+ shell-timeout-seconds: 120
|
|
|
+ max-output-bytes: 100000
|
|
|
+ skills-enabled: true
|
|
|
+```
|
|
|
+
|
|
|
+`IntentProperties` 增加 `routeEnabled`(Java 默认 false,yaml 显式开 true);`EtlProperties` 增加 `taskAgent`。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 10. 文件改动清单
|
|
|
+
|
|
|
+**新增**
|
|
|
+
|
|
|
+- `module/agent/intent/IntentDecision.java`
|
|
|
+- `module/agent/intent/IntentFrames.java`(intent 帧 + `<intent-analysis>` 渲染)
|
|
|
+- `module/agent/intent/ChatIntentRouter.java`
|
|
|
+- `module/agent/pipeline/ChatPipeline.java`
|
|
|
+- `module/agent/pipeline/PipelineType.java`
|
|
|
+- `module/agent/pipeline/ChatPipelineContext.java`
|
|
|
+- `module/agent/pipeline/DataAnalysisPipeline.java`
|
|
|
+- `module/agent/pipeline/TaskPipeline.java`
|
|
|
+- `module/agent/task/TaskAgentFactory.java`
|
|
|
+- `module/agent/task/TaskAgentProperties.java`
|
|
|
+- `module/agent/task/TaskPromptBuilder.java`
|
|
|
+- `src/main/resources/prompts/task-agent.txt`
|
|
|
+
|
|
|
+**修改**
|
|
|
+
|
|
|
+- `intent/IntentEnum.java`、`IntentResult.java`、`IntentService.java`、`IntentMiddleware.java`、`IntentProperties.java`
|
|
|
+- `config/EtlProperties.java`
|
|
|
+- `service/AgentService.java`(条件挂载中间件)
|
|
|
+- `service/impl/AgentChatServiceImpl.java`(路由 + 模型校验 + 消息顺序)
|
|
|
+- `src/main/resources/application.yaml`
|
|
|
+- 前端 `types.ts`、`StreamStatusBar.vue`、`constants.ts`
|
|
|
+
|
|
|
+**测试**
|
|
|
+
|
|
|
+- 新增 `intent/IntentEnumTest`、`intent/IntentServiceTest`(mock LlmService)、`intent/ChatIntentRouterTest`、`task/TaskPromptBuilderTest`、`task/TaskAgentFactoryTest`
|
|
|
+- 回归 `service/AgentExpertTest`(若断言了中间件装配)、`tools/AgentToolRegistryTest`
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 11. 实施步骤
|
|
|
+
|
|
|
+1. 意图层:`IntentEnum/IntentResult/IntentService/IntentDecision/IntentFrames` + 提示词重写 + 单测。
|
|
|
+2. 抽流水线:`ChatPipeline/DataAnalysisPipeline`,`AgentChatServiceImpl` 先只走数据流水线,跑全量测试确认**零行为变化**。
|
|
|
+3. 任务链路:`TaskAgentFactory/TaskPromptBuilder/TaskAgentProperties` + 配置 + 单测(断言 `LocalFilesystemSpec.getProject()/getMode()/getAdditionalRoots()`、shell 开关)。
|
|
|
+4. 接路由:`ChatIntentRouter` + `doStreamLocked` 改造 + `AgentService` 条件中间件 + intent 帧。
|
|
|
+5. 前端:类型、状态栏标签、工具中文名。
|
|
|
+6. 配置开关与冒烟:数据分析 / 闲聊 / 任务(建文件、执行命令、跑 Python)/ 附件处理 / 回滚。
|
|
|
+7. `mvn -pl ai-server test`。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 12. 测试与验收
|
|
|
+
|
|
|
+**单元**
|
|
|
+
|
|
|
+- `fromAny("data_query")`、`fromAny("《数据查询》")`、未知 → null。
|
|
|
+- 路由:6 个意图 → 预期 pipeline;null → data;`route-enabled=false` 时不调用 `IntentService`。
|
|
|
+- `IntentService`:正常返回决策、LLM 抛异常 → null、未知 code → null。
|
|
|
+- 任务提示词:含工作区规则/工具优先级/数据问题兜底,不含 SQL 契约。
|
|
|
+- 任务工厂:路径策略、shell 开关、附件根。
|
|
|
+
|
|
|
+**手工冒烟(SSE)**
|
|
|
+
|
|
|
+| 输入 | 期望 |
|
|
|
+|---|---|
|
|
|
+| `案件里有多少人?` | `intent=data_query`,数据流水线,SQL 工具 |
|
|
|
+| `你好` | `intent=general_chat`,任务流水线,不调工具,快速返回 |
|
|
|
+| `在当前工作区建 test.txt 写入 hello` | `intent=task_execute`,`write_file` 工具事件 |
|
|
|
+| `执行 python 脚本 demo.py` | `task_execute`,`execute` 工具事件 |
|
|
|
+| `统计附件 CSV 的金额分布` | `data_query`,数据流水线(execute_python) |
|
|
|
+| `把附件 CSV 按性别拆成两个文件` | `task_execute`,任务流水线 |
|
|
|
+| `route-enabled=false` 重启后 `你好` | 回到现状(中间件分类 + 数据 agent),intent 帧仍正常 |
|
|
|
+
|
|
|
+**回滚**
|
|
|
+
|
|
|
+- 一个配置项 `etl.intent.route-enabled=false` 即可完全回到当前行为;数据流水线代码虽被抽取,但 agent 构建、工具、提示词、SSE 全部保持原样。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 13. 风险与应对
|
|
|
+
|
|
|
+1. **误分类**:分类器拿不准时强制 `data_query`;任务提示词对数据问题有兜底话术;后续可再加请求级手动指定(本期按确认不做)。
|
|
|
+2. **Shell 安全**:路径策略 + 最小环境变量 + 超时/输出上限 + `shell-enabled` 开关 + 提示词约束;现有数据 agent 本就有 shell,不新增面。生产如需强隔离,后续切 `SandboxFilesystemSpec`(Docker)。
|
|
|
+3. **记忆串台**:任务用 `-task` 后缀的独立记忆槽,避免跨工具集的历史污染;代价是跨流水线上下文依赖 `enhanced` 改写。
|
|
|
+4. **意图识别延迟**:改用 `LlmService` 并加 5s 超时、关闭思考,失败直接回落,不阻塞主链路。
|
|
|
+5. **兼容性**:intent 帧保留 `intent` 中文字段,新增 `code/label/pipeline`,老前端不受影响。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 14. 本期不做
|
|
|
+
|
|
|
+- 会话级手动切换/锁定流水线(按确认走自动路由)。
|
|
|
+- 任务流水线的 followup 建议。
|
|
|
+- 任务工作区的文件浏览/下载 UI。
|
|
|
+- Docker/OS 级沙箱。
|
|
|
+- 把 `pipeline` 持久化进消息 metadata(可作为后续增强)。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 附:关键代码位置索引
|
|
|
+
|
|
|
+| 主题 | 位置 |
|
|
|
+|---|---|
|
|
|
+| 意图分类入口 | `module/agent/intent/IntentService.java` |
|
|
|
+| 意图中间件 | `module/agent/intent/IntentMiddleware.java` |
|
|
|
+| 意图提示词 | `src/main/resources/prompts/intent-recognition.txt` |
|
|
|
+| agent 装配/中间件挂载 | `module/agent/service/AgentService.java`(`buildAgent`) |
|
|
|
+| 流式主链路 | `module/agent/service/impl/AgentChatServiceImpl.java`(`doStreamLocked`) |
|
|
|
+| SSE 事件转换 | 同上(`stream` / `doStreamLocked` 内 `concatMap`) |
|
|
|
+| 独立流水线先例 | `module/agent/insight/agent/InsightAgentFactory.java` |
|
|
|
+| 实例池 | `module/agent/service/AgentInstancePool.java` |
|
|
|
+| 工作区路径 | `module/agent/WorkspaceManagerFactory.java` |
|
|
|
+| Python 执行 | `module/agent/python/PythonExecutor.java` |
|
|
|
+| 附件存储根 | `module/agent/attachment/ChatAttachmentService.java`(`baseDir`) |
|
|
|
+| 前端 intent 渲染 | `ai-electron/frontend/src/ai/components/StreamStatusBar.vue` |
|
|
|
+| 前端工具标签 | `ai-electron/frontend/src/ai/utils/constants.ts` |
|
|
|
+| 前端 SSE 类型 | `ai-electron/frontend/src/ai/api/types.ts` |
|