Commit 82852fad by Archer Committed by GitHub

Agent loop (#6925)

* feat: new agent-loop

* adapt piagent

* feat: refine workflow agent loop responses

* fix: adapt pi agent workflow events

* fix: adapt pi agent workflow responses

* perf: code

* doc

* test: stabilize llm utils image handling

* feat: model think switch

* feat: add agent context checkpoint compression

* perf: context

* toolcall adapt

* move doc

* review

* perf: split code

* fix: chat test

* refactor: move i18nT to global

* remove invalid doc

* fix: avoid empty agent reminder query block

* fix: keep llm usage on error response

* skip system tool call
parent ff77300f
# 功能开发文档
# 功能开发文档
## 文档标识
- 任务前缀:`md-encoding-repair`
- 文档文件名:`md-encoding-repair-功能开发文档.md`
- 文档状态:已补全(实施完成版)
- 最后更新:2026-04-15
## 0. 开发目标与约束
- 功能目标:修复“Markdown 文件前部英文导致编码误判为 `ascii`,从而中文乱码”的问题。
- 核心策略:`UTF-8 严格校验优先(BOM + 字节级合法性校验)`,失败后再进入探测回退。
- 代码范围:
- `FastGPT/packages/global/common/file/tools.ts`
- `FastGPT/packages/service/worker/readFile/extension/rawText.ts`
- `FastGPT/test/cases/global/common/file/tool.test.ts`
- `FastGPT/test/cases/service/common/file/read/encoding.regression.test.ts`
- 非目标(明确不做):API 协议、DB schema、前端交互改造。
- 必须遵循规范:`/Users/xxyyh/.codex/skills/fastgpt-requirement-design/references/style-standards-entry.md`
- 适用维度(从需求分析继承):API[ ] DB[ ] Front[ ] Logger[ ] Package[x]
## 1. 实施任务拆解(执行结果)
| 任务ID | 任务名称 | 责任层 | 执行结果 | 完成定义(DoD) | 状态 |
|---|---|---|---|---|---|
| T1 | 重构编码检测策略 | Service(global utils) | `detectFileEncoding` 改为 BOM + UTF-8 严格校验优先;失败再 fallback 探测 | 不再依赖“仅前 200 字节” | ✅ 已完成 |
| T2 | 增加 `ascii` 误判兜底 | Service(global + worker) | 检测层与解码层都对 `ascii` + 非 ASCII 字节做兜底 | 不再按错误 `ascii` 解码中文 | ✅ 已完成 |
| T3 | 补充/修正单测 | Test | 增加 BOM、长英文前缀+中文正文等用例 | 新场景稳定通过 | ✅ 已完成 |
| T4 | 解码层轻量保护落地 | Service(worker) | `readFileRawText` 增加 `ascii` 误判兜底,并复用全局 `hasNonAsciiByte` | 上游误判时仍输出可读文本 | ✅ 已完成 |
| T5 | 编码回归矩阵补齐 | Test | 新增 `encoding.regression.test.ts` 覆盖 4 类编码回归场景 | 回归矩阵全通过 | ✅ 已完成 |
## 2. 文件级改动清单
| 文件路径 | 改动类型 | 变更摘要 | 关联任务ID |
|---|---|---|---|
| `FastGPT/packages/global/common/file/tools.ts` | 修改 | 新增 `hasUtf8Bom`、`isValidUtf8`、`getDetectSample`、导出 `hasNonAsciiByte`;`detectFileEncoding` 改为验证优先策略 | T1,T2 |
| `FastGPT/packages/service/worker/readFile/extension/rawText.ts` | 修改 | 删除本地重复 `hasNonAsciiByte`,改为复用全局工具;`ascii` + 非 ASCII 字节时改走 UTF-8 解码 | T2,T4 |
| `FastGPT/test/cases/global/common/file/tool.test.ts` | 修改 | 新增 UTF-8 BOM 测试;新增“长英文前缀 + 中文正文”测试;替换旧弱断言用例语义 | T3 |
| `FastGPT/test/cases/service/common/file/read/encoding.regression.test.ts` | 新增 | 新增编码回归矩阵(UTF-8 混排、ASCII、ascii 误传、非法 UTF-8) | T5 |
## 3. 后端实施说明
### 3.1 API 改动
| 路由 | 方法 | 请求参数 | 响应结构 | 鉴权 | 错误处理 |
|---|---|---|---|---|---|
| N/A | N/A | N/A | N/A | N/A | N/A |
说明:本需求仅涉及文件编码判定与解码策略,不改 API 合约。
### 3.2 Service/Core 改动
| 模块 | 函数/类型 | 具体改动 | 依赖关系 |
|---|---|---|---|
| `packages/global/common/file/tools.ts` | `detectFileEncoding` | 判定顺序改为:`BOM -> strict UTF-8 validate -> jschardet fallback`;对 `ascii` + 非 ASCII 字节做兜底 | 仅使用现有 `jschardet`,未新增依赖 |
| `packages/global/common/file/tools.ts` | `hasNonAsciiByte` | 由私有函数改为导出,供多处复用 | 复用方为 service worker 解码逻辑 |
| `packages/service/worker/readFile/extension/rawText.ts` | `readFileRawText` | 新增 `normalizedEncoding`,在 `encoding='ascii'` 且检测到非 ASCII 字节时强制按 UTF-8 解码 | 复用 `@fastgpt/global/common/file/tools` |
### 3.3 数据层改动
| 集合/表 | 字段 | 类型 | 必填 | 默认值 | 索引 | 迁移策略 |
|---|---|---|---|---|---|---|
| N/A | N/A | N/A | N/A | N/A | N/A | 无需迁移 |
## 4. 前端实施说明
| 页面/组件 | 文件路径 | 交互变化 | i18n 改动 | 状态覆盖 |
|---|---|---|---|---|
| N/A | N/A | 无 | 无 | N/A |
## 5. 日志与可观测性
| 触发点 | 日志级别 | category | 字段 | 备注 |
|---|---|---|---|---|
| 本期无新增日志点 | N/A | N/A | N/A | 为最小改动未加新日志,后续可按需要加 debug 观测 |
## 6. 测试与验证
### 6.1 自动化测试清单(已执行)
| 测试文件 | 覆盖重点 | 结果 |
|---|---|---|
| `test/cases/global/common/file/tool.test.ts` | 编码检测基础能力(UTF-8、ASCII、BOM、混排场景) | ✅ 通过 |
| `test/cases/service/common/file/read/utils.test.ts` | 文件读取主链路(readFileContentByBuffer) | ✅ 通过 |
| `test/cases/service/common/file/gridfs/utils.test.ts` | 预览流编码链路(stream2Encoding) | ✅ 通过 |
| `test/cases/service/common/file/read/encoding.regression.test.ts` | 编码回归矩阵(4 场景) | ✅ 通过 |
### 6.2 回归矩阵(新增)
| 场景 | 输入 | 预期 | 结果 |
|---|---|---|---|
| UTF-8 混排正文 | 长英文前缀 + 中文正文(UTF-8) | 检测 `utf-8`,中文可读 | ✅ |
| 纯 ASCII 文本 | `Hello ASCII 123` | 行为不变 | ✅ |
| `ascii` 误传 | UTF-8 中文 buffer + `encoding='ascii'` | 触发兜底,中文可读 | ✅ |
| 非法 UTF-8 序列 | 非 UTF-8 合法字节序列 | 不应判为 `utf-8` | ✅ |
### 6.3 执行命令与结果
执行命令:
```bash
pnpm -C FastGPT exec vitest run \
test/cases/global/common/file/tool.test.ts \
test/cases/service/common/file/read/utils.test.ts \
test/cases/service/common/file/gridfs/utils.test.ts \
test/cases/service/common/file/read/encoding.regression.test.ts
```
结果摘要:
- Test Files: `4 passed (4)`
- Tests: `45 passed (45)`
### 6.4 UTF-8 严格校验性能检测报告(新增)
#### 6.4.1 检测背景
- 目标:评估 `isValidUtf8(buffer)` 全量线性扫描在大文件场景下的 CPU 开销,确认是否需要阈值门控。
- 方法:在本地通过 Node.js 基准脚本,复用当前实现逻辑,分别对 ASCII 缓冲区与中英混排 UTF-8 缓冲区做多轮扫描取平均值。
#### 6.4.2 检测结果(平均单次扫描耗时)
| 文件大小 | ASCII only | UTF-8 中英混排 |
|---|---:|---:|
| 10MB | 16.20ms | 32.21ms |
| 50MB | 84.04ms | 87.52ms |
| 100MB | 160.06ms | 169.08ms |
| 200MB | 327.01ms | 345.51ms |
| 500MB | 894.27ms | 880.22ms |
补充:实测吞吐约 `560~620 MB/s`,整体符合线性增长特征(O(n))。
#### 6.4.3 结论与策略
- 20MB 以下:开销较小,通常无体感影响。
- 50~100MB:开始出现可感知延迟。
- 200MB 以上:单次扫描约 300ms+,并发场景下会放大 CPU 压力。
- 500MB:接近 1 秒/次,不建议默认全量严格校验。
建议落地策略:
1. 交互链路默认仅对 `<=32MB`(或 `<=64MB`)执行全量 UTF-8 严格校验。
2. 超过阈值时跳过全量校验,改走采样探测 fallback。
3. 后续可按线上机器规格和峰值并发再微调阈值。
## 7. 质量自检清单
- [x] 输入与权限流程未被破坏(未改 API/权限逻辑)
- [x] 无新增 `any` 滥用、无未处理 Promise
- [x] 包依赖方向符合 monorepo 约束(`service` 复用 `global`)
- [x] 覆盖关键回归场景(UTF-8 混排、ascii 误判兜底)
- [x] 无新增敏感日志输出
## 8. 发布与回滚
### 8.1 发布步骤
1. 合并代码后在测试环境执行上述 4 组回归测试。
2. 手工上传 UTF-8 中英混排 Markdown,确认预览与入库内容正常。
3. 观察线上相关解析失败反馈。
### 8.2 回滚触发条件
- 发布后出现明显新增的非 UTF-8 文本解析失败反馈。
### 8.3 回滚步骤
1. 回滚 `FastGPT/packages/global/common/file/tools.ts` 与 `FastGPT/packages/service/worker/readFile/extension/rawText.ts`。
2. 重新发布并复测上传链路。
## 9. AI 实施提示(给执行模型)
- 编码检测必须坚持“验证优先”,禁止回退到“仅前缀猜测优先”。
- 若后续扩展多候选编码评分,单独开需求,不在本任务内扩大范围。
- 每次改动编码策略后,必须至少跑本文件第 6.3 节的回归命令。
# 需求设计文档
# 需求设计文档
## 0. 文档标识
- 任务前缀:`md-encoding-repair`
- 文档文件名:`md-encoding-repair-需求设计文档.md`
- 文档状态:已补全(实施回填版)
- 最后更新:2026-04-15
## 1. 需求背景与目标
### 1.1 背景
- 问题现状:知识库上传 `.md` 文件时,若文件开头英文占比高,系统可能将编码误判为 `ascii`,随后按 `ascii` 解码整篇文本,导致中文出现乱码。
- 触发场景:`md/txt/csv/html` 等文本文件,文件前部主要为英文,中文内容出现在较后位置。
### 1.2 目标
- 业务目标:保证知识库文档上传后中文内容可被正确解析并进入分段/训练链路。
- 技术目标:避免“前部英文导致整篇 `ascii` 误判”的编码检测缺陷。
- 成功指标(可量化):
- 构造“前 500+ 字节英文、后续中文”的 UTF-8 Markdown 文件,解析结果中文不乱码。
- 纯英文 ASCII 文件解析结果保持不变。
- 编码相关回归测试全量通过。
## 2. 当前项目事实基线(基于代码)
| 能力项 | 现有实现位置(文件路径) | 现状说明 | 结论(复用/修改/新增) |
|---|---|---|---|
| API | `FastGPT/projects/app/src/pages/api/core/dataset/collection/create/fileId.ts` | 上传后创建文件型 collection 的入口;本身不处理编码,只触发后续解析流程 | 复用 |
| Core Service | `FastGPT/packages/service/core/dataset/read.ts` -> `FastGPT/packages/service/common/s3/sources/dataset/index.ts` | 读取 S3 文件后调用 `detectFileEncoding(buffer)`,再把 encoding 传给解析器 | 复用调用链,修改编码策略实现 |
| Worker Decode | `FastGPT/packages/service/worker/readFile/extension/rawText.ts` | 根据上游 encoding 对文本 buffer 解码 | 新增解码兜底保护 |
| DB Schema | `FastGPT/packages/service/core/dataset/collection/schema.ts` | 当前问题与 DB 结构无关 | 不改 |
| Frontend | `FastGPT/projects/app/src/pageComponents/dataset/detail/Import/diffSource/FileLocal.tsx` | 前端只负责上传到 S3,不决定后端解析编码 | 不改 |
| Logger | `FastGPT/packages/service/common/s3/sources/dataset/index.ts` | 当前已有下载日志;编码误判场景无专门日志 | 本期不改 |
关键代码锚点(实施后):
- 编码判定入口:`FastGPT/packages/global/common/file/tools.ts` 中 `detectFileEncoding`(已改为验证优先)
- 解码兜底:`FastGPT/packages/service/worker/readFile/extension/rawText.ts` 中 `readFileRawText`
## 3. 需求澄清记录
| 维度 | 已确认内容 | 待确认内容 | 备注 |
|---|---|---|---|
| 业务目标 | 修复 Markdown 上传中文乱码 | 是否覆盖更多小众本地编码(如 Shift_JIS) | 后续二期可评估 |
| 范围边界 | 聚焦知识库/读文件链路的编码判定与解码保护 | 是否新增线上编码判定日志点 | 本期不做 |
| 权限模型 | 无权限模型变化 | 无 | N/A |
| 数据模型 | 不新增字段 | 无 | N/A |
| API 行为 | 不改 API 入参/出参 | 无 | N/A |
| 前端交互 | 不改页面交互 | 无 | N/A |
## 3.1 影响域判定(先判定,再核对规范)
| 维度 | 是否命中 | 证据(需求/代码锚点) | 核对规范 | 结论 |
|---|---|---|---|---|
| API | No | 编码问题发生在服务端文件解析,现有路由仅透传 fileId | `style/api.md` | Not Applicable(不改接口) |
| DB | No | 不涉及 schema/索引/数据迁移 | `style/db.md` | Not Applicable |
| Front | No | 上传流程不参与后端解码决策 | `style/front.md` | Not Applicable |
| Logger | No(本期) | 本期目标为最小可控修复,未新增观测点 | `style/logger.md` | Not Applicable |
| Package | Yes | 修改位置在 `packages/global` 与 `packages/service`,涉及跨包复用 | `style/package.md` | 已遵守依赖方向(service 依赖 global) |
## 4. 范围定义
### 4.1 In Scope(本期必须)
- 将 `detectFileEncoding` 从“猜测优先”调整为“验证优先”。
- 增加 `ascii` 误判保护(检测层+解码层双保险)。
- 补充编码回归测试矩阵,覆盖核心场景。
### 4.2 Out of Scope(本期不做)
- 不改知识库 API 协议。
- 不改数据库结构。
- 不做前端上传流程改造。
- 不引入多候选编码评分引擎(后续可独立需求)。
## 5. 方案对比
| 方案 | 核心思路 | 优点 | 风险 | 实施成本 | 结论 |
|---|---|---|---|---|---|
| 方案A(最小改动) | `UTF-8 验证优先(BOM + 严格字节校验)` + fallback 探测 + `ascii` 兜底 | 改动集中、确定性高、能直接修复现网主问题 | 对极端小众编码仍依赖 fallback | 低 | 推荐并已落地 |
| 方案B(可扩展) | 多候选编码试解 + 文本质量打分 | 覆盖更多边缘编码场景 | 复杂度与误判调参成本高 | 中-高 | 本期不选 |
推荐方案:方案A(已实施)。
## 6. 推荐方案详细设计(实施回填)
### 6.1 API 设计
| 路由 | 方法 | 鉴权 | 请求 | 响应 | 错误分支 | 相关文件 |
|---|---|---|---|---|---|---|
| N/A | N/A | N/A | N/A | N/A | N/A | 不涉及 |
### 6.2 数据设计
| 实体/集合 | 字段 | 类型 | 必填 | 默认值 | 索引/约束 | 兼容策略 |
|---|---|---|---|---|---|---|
| N/A | N/A | N/A | N/A | N/A | N/A | N/A |
### 6.3 核心代码设计
| 模块 | 关键函数/类型 | 实际变更 | 上下游影响 |
|---|---|---|---|
| `FastGPT/packages/global/common/file/tools.ts` | `detectFileEncoding(buffer)` | 判定顺序调整为:`hasUtf8Bom -> isValidUtf8 -> detect(getDetectSample)`;并增加 `ascii + 非 ASCII 字节` 兜底 | 所有调用方收益(知识库、工作流读文件、预览编码头) |
| `FastGPT/packages/global/common/file/tools.ts` | `hasNonAsciiByte(buffer)` | 从私有函数提升为导出工具函数,供多处复用 | 减少重复实现,维护单一事实源 |
| `FastGPT/packages/service/worker/readFile/extension/rawText.ts` | `readFileRawText` | 新增 `encoding='ascii'` 且存在非 ASCII 字节时强制 UTF-8 解码保护 | 防止上游误判导致中文乱码 |
| `FastGPT/test/cases/global/common/file/tool.test.ts` | `detectFileEncoding` tests | 新增 BOM 场景、长英文前缀+中文正文场景 | 防回归 |
| `FastGPT/test/cases/service/common/file/read/encoding.regression.test.ts` | 编码回归矩阵 | 新增 4 场景覆盖:UTF-8 混排、ASCII、ascii 误传、非法 UTF-8 | 回归覆盖补齐 |
### 6.4 前端设计
| 页面/组件 | 入口文件 | 交互状态(加载/空/错/成功) | i18n key | 变更说明 |
|---|---|---|---|---|
| N/A | N/A | N/A | N/A | 本期不涉及 |
### 6.5 日志与观测设计
| 场景 | 日志级别 | category | 结构化字段 | 脱敏策略 |
|---|---|---|---|---|
| 本期无新增日志 | N/A | N/A | N/A | N/A |
## 7. 风险、迁移与回滚
### 7.1 风险清单
- 风险1:严格 UTF-8 校验为 O(n) 线性扫描,超大文本文件会增加少量 CPU。
- 风险2:小众非 UTF-8 编码仍可能依赖 fallback 的稳定性。
### 7.2 迁移策略
- 无 DB 迁移。
- 通过新增回归矩阵 + 现有测试集进行功能验证。
### 7.3 回滚策略
- 回滚目标文件:
- `FastGPT/packages/global/common/file/tools.ts`
- `FastGPT/packages/service/worker/readFile/extension/rawText.ts`
- 回滚触发条件:发布后出现显著新增的“非 UTF-8 文本解析异常”反馈。
## 8. 验收标准(执行结果)
| 验收项 | 验收方式 | 通过标准 | 结果 |
|---|---|---|---|
| UTF-8 混合文档不乱码 | 自动化测试 + 手测路径定义 | 中文片段解析正常 | ✅ 通过 |
| 纯 ASCII 文档兼容 | 自动化测试 | 行为不回归 | ✅ 通过 |
| `ascii` 误传防护 | 自动化测试 | 解码结果中文可读 | ✅ 通过 |
| 回归安全 | 编码相关测试集合 | 全部通过 | ✅ 45/45 |
已执行测试命令:
```bash
pnpm -C FastGPT exec vitest run \
test/cases/global/common/file/tool.test.ts \
test/cases/service/common/file/read/utils.test.ts \
test/cases/service/common/file/gridfs/utils.test.ts \
test/cases/service/common/file/read/encoding.regression.test.ts
```
测试结果摘要:
- Test Files: `4 passed (4)`
- Tests: `45 passed (45)`
## 9. MECE 核查结论(实施后)
### 9.1 相互独立检查结果
- 发现问题:编码检测与解码保护存在重复判断风险。
- 影响范围:后续维护可能出现策略漂移。
- 修订动作:统一由 `tools.ts` 提供通用工具(`hasNonAsciiByte`),`rawText.ts` 仅做末端防护。
- 修订后结果:职责清晰,复用一致。
### 9.2 完全穷尽检查结果
- 发现问题:仅修检测层不足以防止历史调用链误传 `ascii`。
- 影响范围:部分链路仍可能乱码。
- 修订动作:增加解码层二次兜底 + 回归矩阵覆盖误传场景。
- 修订后结果:正常/异常链路覆盖完整。
### 9.3 修订动作与最终边界
- 本期聚焦编码检测与解码兜底,不扩展 API/DB/前端。
- 后续若要支持更广泛本地编码智能识别,建议单开“多候选评分”二期需求。
# Agent Loop 需求文档
状态:收口版
日期:2026-05-11
## 背景
AgentV2 早期方案包含多层 agent、`stepCall`、`continue plan`、独立 stop verifier 等概念,导致上下文拼接、运行详情、流式输出、前端恢复和测试边界都比较复杂。
本轮目标是把 agent loop 收敛为一条可复用的主循环:
- workflow agent 节点只负责适配 workflow 上下文、工具、回调和持久化;
- 通用 loop 放在 `packages/service/core/ai/llm/agentLoop`;
- 模型在同一个主 loop 内完成计划维护、工具调用、追问用户和最终回答;
- 前端只展示新的 plan card、工具卡、思考和最终答案,不再兼容旧 `stepCall` UI。
## 目标
1. 简化 loop 架构,去掉多层 agent 嵌套和独立 `continue plan`。
2. 保证上下文连续,用户追问恢复和工具结果回灌都发生在同一条 message 链路中。
3. 支持模型通过 `update_plan` 维护计划,并由本地 stop gate 保证计划完成后才能 final。
4. 支持 `ask_agent` 在必要时追问用户,用户回答后继续原上下文,而不是重新生成一份独立计划。
5. 完整保存 thinking、tool call、tool result、plan、answer、requestId、tokens 和 usage。
6. SSE 事件完整覆盖 workflow 和普通对话,计划生成前需要有可感知的 loading 状态,最终答案需要保持流式输出。
7. 运行详情按 agent/tool 调用线性展示,AI 请求都能关联 requestId。
## 范围
### 必须支持
- 直接回答:简单问题不创建 plan,直接流式输出 answer。
- 显式计划:用户明确要求规划、复杂调研、比较、方案设计等场景,需要先创建 plan。
- 执行计划:plan steps 必须非空;步骤状态可批量更新。
- 工具调用:runtime tools 正常执行并展示;工具结果需要回灌给模型。
- 工具后计划更新:调用 runtime tool 后,模型必须用 `update_plan` 记录证据或结果,才能最终回答。
- 用户追问:缺少必要输入时使用 `ask_agent`,暂停当前 loop 并保存 pending context。
- 追问恢复:用户回答后,把回答作为 ask tool response 追加回原 messages,继续执行。
- 刷新恢复:历史记录恢复后,plan、thinking、tools、interactive、answer 都应完整展示。
- 工作流适配:workflow 节点通过 adapter 调用通用 agent loop,所有 workflow 专属能力通过参数和接口注入。
- 运行详情:每次 LLM 请求都需要记录 requestId、tokens、model、完成原因;runtime tools 作为对应 agent 调用下的工具展示。
### 不再支持
- 不再写入旧 `stepCall` 字段。
- 不再保留旧 stepCall 前端 UI。
- 不再使用独立 `plan_agent` tool。
- 不再使用独立 LLM stop verifier。
- 不再把历史 plan 伪造成 tool call 注入上下文。
- 不再保留 HTML 预览文档。
## 用户体验需求
### Plan Card
- 进入 plan 模式但 plan 尚未生成时,展示 plan loading skeleton 和中文提示文案。
- plan card 默认最小宽度为消息最大宽度的 50%,避免 loading 过窄。
- plan step 用颜色表达状态:
- 蓝色:进行中,并带轻量动效;
- 绿色:完成;
- 灰色:待处理;
- 黄色/红色:阻塞或需要调整。
- 不展示冗余的 `Running/Pending` 英文状态标签。
- `update_plan` 完成后只更新状态、证据和必要内容,不额外插入 step summary 气泡。
- 右侧 step 数量展示可去掉,降低噪音。
### 流式输出
- plan 生成期间不能让用户长时间无反馈。
- 模型输出过程需要实时透传给前端,包括 stop gate 最终拒绝的草稿 answer。
- stop gate 只影响最终可持久化的 answer,不负责缓存、撤回或延迟推送 `answer_delta`。
- 前端看到的是模型执行过程流;刷新恢复时只恢复最终保留在 assistantMessages 中的 answer。
### 运行详情
- 顶层展示 AI 调用节点,例如主 Agent、任务规划等,使用旧版对应 name 和 icon。
- runtime tool 作为所属 AI 调用下的子项展示。
- 每个 AI 调用都需要能看到 requestId、tokens、模型和完成原因。
- 开头或结尾空 nodeResponse 不应展示。
## 验收清单
| 编号 | 场景 | 验收点 | 状态 |
| --- | --- | --- | --- |
| A1 | 基础直接回答 | 无 plan、无 tool 时直接流式输出,刷新后 answer 恢复 | 已通过 |
| A2 | 显式计划模式 | 用户要求 plan 时必须先 `update_plan(set_plan)`,不能直接 final | 已通过 |
| A3 | 复杂任务 plan | 生成 plan card,steps 非空,可持久化恢复 | 已通过 |
| A4 | plan 批量更新 | 一次 `update_plan` 可提交多个 step update | 已通过 |
| A5 | stop gate 未完成拦截 | pending/in_progress/needsReplan/blocked 无 blocker 时不能 final | 已通过 |
| A6 | runtime tool 后 plan 记录 | runtime tool 后必须再 `update_plan` 记录结果才能 final | 已通过 |
| A7 | ask_agent 追问 | 缺少强阻塞输入时返回 interactive ask | 已通过 |
| A8 | ask_agent resume | 用户回答后沿 pendingMainContext 继续,不重建独立 planner | 已通过 |
| A9 | ask 前 runtime tool 状态 | resume 后仍要求把 ask 前 runtime tool 结果写回 plan | 已通过 |
| A10 | 无效 ask_agent 参数 | 不返回空 answer,模型可继续修正 | 已通过 |
| A11 | replace_plan | 保留当前 planId,不重复生成 plan 卡;保留已完成证据 | 已通过 |
| A12 | runtime 工具冲突 | runtime tool 同名 `ask_agent/update_plan` 会被过滤 | 已通过 |
| A13 | SSE plan loading | update_plan 开始前出现 plan skeleton,成功后替换为 plan card | 已通过 |
| A14 | SSE answer 流式 | stop gate 拒绝的草稿和最终 answer 都按过程实时透传,刷新后只恢复最终 answer | 已通过 |
| A15 | responseNode | 主链路 LLM request 写入 nodeResponse,包含 tokens 和 requestId | 已通过 |
| A16 | records 恢复 | plan、toolcall、thinking、answer 刷新后可恢复 | 已通过 |
| A17 | 旧 stepCall | 新链路不写旧 stepCall 字段,前端不依赖旧 UI | 已通过 |
| A18 | App request 基础 | 前端 request 单测仍通过 | 已通过 |
## 仍需专项确认
| 编号 | 场景 | 说明 |
| --- | --- | --- |
| R1 | dataset query extension requestId | 仍需确认 query extension requestId 透传到运行详情的完整链路 |
| R2 | Pro 计费 | 本地 OSS 无法覆盖真实扣费路径,需要在 Pro 环境专项验收 |
| R3 | 外部 OpenAI account | 需要确认内部 LLM 调用也走外部 key |
| R4 | 无效 update_plan UI 收尾 | plan skeleton 失败态仍可进一步优化 |
## 推荐回归
```bash
corepack pnpm --filter @fastgpt/service exec vitest run -c vitest.config.ts test/core/ai/llm/agentLoop test/core/workflow/dispatch/ai/agent/adapter
corepack pnpm --filter @fastgpt/global exec vitest run -c vitest.config.ts test/core/chat/adapt.test.ts test/core/chat/type.test.ts test/core/workflow/runtime/utils.test.ts
corepack pnpm --filter @fastgpt/app exec vitest run -c vitest.config.ts test/web/common/api/request.test.ts
git diff --check
```
# Agent 文件上下文与 read_files 对齐方案
## 背景
Agent 旧文件上下文有两类不一致:
- 文件提示词使用 `<available_files>` 和数字序号。
- Agent 文件读取工具是 `file_read`,参数是 `{ file_indexes }`,而 ToolCall 已使用 `read_files` + `{ ids }`。
这会让同一套文件能力在 Agent、ToolCall、Sandbox 中出现不同语义,也不利于历史 request messages 恢复后继续命中上一轮 tool call 参数。
## 目标
本次把 Agent 文件上下文对齐到 ToolCall 风格:
- 新请求只暴露 `read_files`。
- 新工具参数为 `{ ids: string[] }`。
- 用户本轮动态上下文统一注入当前 Human message 的 `<system-reminder>`。
- 历史 Human message 每轮恢复时只补 `# Input Files`,避免历史中混入当前知识库和当前时间。
- 保留 runtime legacy fallback,兼容旧 pending context 或旧历史里的 `file_read` / `{ file_indexes }`;旧 id 仅作为内部字符串兼容,不再出现在 `SubAppIds` 或系统工具列表。
## 上下文结构
当前轮 Human message:
```xml
<system-reminder>
# Input Files
用户本次可用的文件:
<file>
<id>current_ai_id-0</id>
<name>a.pdf</name>
<type>document</type>
</file>
# Input datasets
用户当前可用的知识库:
<dataset>
<id>dataset_id</id>
<name>知识库名称</name>
</dataset>
# Current time
2026-05-14 12:00:00 Thursday
原始问题
</system-reminder>
```
历史 Human message:
```xml
<system-reminder>
# Input Files
用户本次可用的文件:
<file>
<id>history_ai_id-0</id>
<name>old.pdf</name>
<type>document</type>
</file>
历史原始问题
</system-reminder>
```
历史只注入文件段,原因是:
- 文件 id 需要在每一轮历史恢复时稳定重建,保证历史 assistant tool call 的 `ids` 可继续命中。
- datasets 和 current time 是当前轮动态上下文,不应该回写到历史轮次。
## 文件 id 规则
当前轮文件 id 使用当前 AI response chat item id 作为前缀:
```text
{responseChatItemId}-{index}
```
历史 Human 文件 id 优先使用同轮后续 AI message 的 `dataId` 作为前缀:
```text
{pairedAiDataId}-{index}
```
如果找不到同轮 AI message,则 fallback 到 Human message 的 `dataId` 或历史下标。
这个规则保证:
- 当前轮模型调用 `read_files({ ids: ["responseChatItemId-0"] })`。
- 下一轮从 chat history 恢复时,历史 Human 会被重写出同样的文件 id。
- 上一轮 assistant tool call 的参数不需要被重写,也能继续和恢复后的 `filesMap` 对上。
## 新增聚合入口
文件:
`packages/service/core/workflow/dispatch/ai/agent/adapter/userContext.ts`
导出函数:
```ts
buildAgentInputFilesPrompt(...)
buildAgentUserReminderInput(...)
rewriteAgentUserMessagesWithFiles(...)
buildAgentUserContextInput(...)
```
职责:
- `buildAgentInputFilesPrompt`:生成 `# Input Files` XML 块。
- `buildAgentUserReminderInput`:生成当前轮 `<system-reminder>`。
- `rewriteAgentUserMessagesWithFiles`:遍历历史 Human,只改写文件上下文。
- `buildAgentUserContextInput`:聚合入口,统一产出 rewritten histories、current user message、`filesMap`、`allFilesMap`。
`filesMap` 只包含 document 类型文件,供 `read_files` 解析正文。
`allFilesMap` 包含 document/image 等所有可用文件,供 `sandbox_fetch_user_file` 写入沙箱。
## read_files 协议
Agent 内置文件工具:
```ts
SubAppIds.readFiles = 'read_files'
```
新 schema:
```ts
z.object({
ids: z.array(z.string())
})
```
模型看到的 function call:
```json
{
"name": "read_files",
"arguments": {
"ids": ["current_ai_id-0"]
}
}
```
执行器:
- `toolId === SubAppIds.readFiles` 时走文件解析。
- 从 `params.ids` 读取文件 id。
- 通过 `filesMap[id]` 找到 URL。
- 调用 `dispatchFileRead({ files: [{ id, url }] })`。
- 返回内容中使用 `id` 字段,不再使用 `index`。
兼容:
- runtime handler 继续接受旧 `file_read` + `{ file_indexes }`,但只用内部 legacy 字符串兼容,不再保留 `SubAppIds.fileRead`。
- 新 tool schema、prompt、HelperBot 资源列表和 ChatAgent UI 不再暴露 `file_read` / `file_indexes`。
## Agent 接入
`dispatchRunAgent`:
- 删除旧 `formatFileInput(...)` 和手动拼接文件 prompt。
- 调用 `buildAgentUserContextInput(...)`。
- 使用 `rewrittenHistories + currentUserMessage` 生成 `chats2GPTMessages({ reserveTool: true })`。
- 将 `filesMap` 传给 `read_files` 执行器。
- 将 `allFilesMap` 传给 sandbox capability。
`dispatchPiAgent`:
- 同样调用 `buildAgentUserContextInput(...)`。
- 第一阶段只把当前轮完整 reminder 文本传给 `agent.prompt(...)`。
- PiAgent 历史 messages 仍由现有 memory 恢复,不迁移成 workflow chat history。
`parseUserSystemPrompt(...)`:
- 移除 selectedDataset 的 `<preset_resources>` 注入。
- datasets 改由当前轮 user reminder 的 `# Input datasets` 承载。
## Sandbox 关系
`sandbox_fetch_user_file` 本次不改参数名,仍然是:
```ts
{
file_index: string,
target_path: string
}
```
但 `file_index` 的语义已更新为:
```text
File id from # Input Files
```
即参数名为历史兼容保留,参数值使用新的 file id。
## 测试覆盖
已覆盖:
- `buildAgentInputFilesPrompt(...)` 生成 `<id>`,并进行 XML escape。
- 历史 Human 只改写文件段,不包含 datasets/time。
- 当前 Human 包含 files、datasets、current time、原始问题。
- 历史 Human 文件 id 优先使用同轮 AI `dataId`,保证历史 tool call 参数稳定。
- 当前文件按 request origin 归一化后去重。
- Agent 暴露的文件工具名是 `read_files`,参数是 `{ ids }`。
- Agent 执行器能按 `ids` 找文件并解析。
- legacy fallback:旧 `file_read` / `{ file_indexes }` 可执行,但不会出现在新 schema。
- Agent dispatch mock:进入 loop 的 messages 已统一改写。
- PiAgent mock:`agent.prompt(...)` 收到完整 current reminder。
局部测试命令:
```bash
pnpm --filter @fastgpt/service test test/core/workflow/dispatch/ai/agent/adapter/userContext.test.ts test/core/workflow/dispatch/ai/agent/utils.test.ts test/core/workflow/dispatch/ai/agent/index.test.ts test/core/workflow/dispatch/ai/agent/piAgent/index.test.ts test/core/workflow/dispatch/ai/agent/sub/file.test.ts
```
## TODO
- [x] 新增 Agent context 聚合入口。
- [x] Agent 文件 prompt 改为 `# Input Files` XML。
- [x] 当前轮 user reminder 注入 files、datasets、current time、原始问题。
- [x] 历史 Human 每轮重写文件上下文。
- [x] Agent 文件工具迁移到 `read_files` + `{ ids }`。
- [x] runtime 保留旧 `file_read` + `{ file_indexes }` fallback,旧工具不再作为系统工具暴露。
- [x] `parseUserSystemPrompt` 移除 selectedDataset 注入。
- [x] `dispatchRunAgent` 接入统一上下文。
- [x] `dispatchPiAgent` 接入当前轮 reminder。
- [x] HelperBot / ChatAgent UI 改为暴露 `read_files`。
- [x] 补充核心单测。
- [ ] 浏览器集成测试:上传文件 + 选择知识库 + 当前时间 + `read_files` 工具调用。
# TODO — 变量更新节点类型操作扩展
> 设计见同目录 `design.md`
## Phase 1:类型 & 运行时(后端,先行)
- [x] 扩展 `TUpdateListItem` 类型,新增 `numberOperator / booleanMode / arrayMode`
- [x] `runUpdateVar.ts`:添加 oldValue 读取工具函数
- [x] `runUpdateVar.ts`:Number 公式分派(含除零保持旧值)
- [x] `runUpdateVar.ts`:Boolean `true/false/negate` 分派
- [x] `runUpdateVar.ts`:Array `append/clear/equal` 分派(append 使用元素类型做 `valueTypeFormat`)
- [x] `runUpdateVar.ts`:所有新字段仅在 `renderType === input` 时生效的 guard
- [x] 写 vitest 测试 `runUpdateVar.test.ts`,运行通过(20 tests)
## Phase 2:前端组件拆分(重构,不改行为)
- [x] 建立目录 `NodeVariableUpdate/`
- [x] 把现有 `NodeVariableUpdate.tsx` 迁到 `NodeVariableUpdate/index.tsx`,拆出 `VariableSelector.tsx`
- [x] 新增 `ValueRenderer.tsx`(按 renderType / valueType 派发)
## Phase 3:前端渲染器(新功能)
- [x] `renderers/NumberFormula.tsx`:运算符下拉 + numberInput(图标化)
- [x] `renderers/BooleanSelect.tsx`:True/False/Negate 下拉
- [x] `renderers/ArrayValue.tsx`:模式下拉 + 按元素类型映射 `InputRender`(不递归)
- [x] `ValueRenderer.tsx`:按 valueType 派发到新 renderer
- [x] 切换模式时清空 `value: undefined`
## Phase 4:i18n
- [x] 补充 `workflow:var_update_boolean_*` 与 `workflow:var_update_array_*` 中 / 英 / 繁中
## Phase 5:联调
- [x] dev server 起:string / number / boolean / array 四类变量逐一验证手动输入 + 引用两种模式
- [x] 老数据打开(无新字段),表现与升级前一致
- [x] 运行 `pnpm lint` 全量通过(0 errors)
## Phase 6:Review 清理(2026-04-15)
- [x] 还原 `constants.ts`:剥离 107 条与 math icons 无关的图标注册,只保留 5 个 `math/*`
- [x] `any[]` 收紧为 `EditorVariablePickerType[]` / `EditorVariableLabelPickerType[]`(3 个 renderer + ValueRenderer)
- [x] 抽出 `getDefaultsForValueType()` 统一目标变量切换时的默认字段下发
- [x] `VariableSelector.tsx`:`.includes('array')` → `.startsWith('array')` 与仓内风格对齐
- [x] `workflow.json` 三语把 `var_update_*` 按字母序移到 `variable_*` 之前
......@@ -40,4 +40,4 @@ document/.source
projects/app/worker/
pro/admin/worker/
.turbo
.turbo
\ No newline at end of file
......@@ -108,6 +108,13 @@ FastGPT 是一个 AI Agent 构建平台,通过 Flow 提供开箱即用的数据
[FastGPT 代码规范](./.codex/code/syntax.md)
### 函数注释
- 编写或拆分函数时,必须关注函数注释。对导出函数、核心业务函数、hook、复杂工具函数、跨模块复用函数,优先使用 `/** ... */` 形式补充函数级注释。
- 函数注释应说明函数职责、输入输出约定、关键分支、边界行为和设计原因,尤其是容易误解的计费、权限、requestId、错误处理、流式响应、缓存、并发、兼容逻辑。
- 避免写无意义注释,例如只复述“设置变量”“返回结果”。如果函数逻辑简单且语义已经完全由命名表达,可以不写冗余注释。
- 对复杂函数内部的关键判断,也应补充简短中文注释,说明为什么这样处理,而不是逐行解释代码。
## 运行要求
### 性格
......@@ -157,4 +164,4 @@ function agent_loop(用户需求){
2.2. 问题分析文档: [.codex/issue](.codex/issue)
3. 相同需求文档,尽量写在一起(内容超过 300 行,可以分批写入),或者创建要给目录一起管理,不要随意平铺一堆不同版本的相同问题的文档。
4. 文件输出,使用正确的编码格式,例如UTF-8。
5. 除非用户指明,否则不要编写总结报告。
\ No newline at end of file
5. 除非用户指明,否则不要编写总结报告。
......@@ -5,6 +5,8 @@ description: 'FastGPT V4.15.0-beta2 更新说明'
## 🚀 新增内容
1. 重写 agentV2 loop 逻辑。
## ⚙️ 优化
1. 优化 OTEL 日志采集格式。
......@@ -13,9 +15,14 @@ description: 'FastGPT V4.15.0-beta2 更新说明'
4. 增加工作流节点,名字超长适配。
5. 知识库搜索测试交互。
6. 知识库数据编辑弹窗。
7. reason hide 开关完善,确保只是 UI 不显示,但是 request llm 时候依然可以保留。
## 🐛 修复
1. 工作流,单节点调试,存在异常默认值。
2. 模型配置,defaultConfig 覆盖异常。
## 代码优化
1. 拆分 AI request、工作流运行详情代码。
2. 用户自定义密钥计费逻辑。
......@@ -253,6 +253,8 @@
"content/self-host/upgrading/4-14/41419.mdx": "2026-05-09T15:25:23+08:00",
"content/self-host/upgrading/4-14/4142.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/4-14/4142.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/4-14/41420.en.mdx": "2026-05-14T17:58:58+08:00",
"content/self-host/upgrading/4-14/41420.mdx": "2026-05-14T17:58:58+08:00",
"content/self-host/upgrading/4-14/4143.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/4-14/4143.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/4-14/4144.en.mdx": "2026-04-26T21:08:47+08:00",
......@@ -272,7 +274,7 @@
"content/self-host/upgrading/4-14/4149.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/4-14/4149.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/4-15/4150.mdx": "2026-05-09T16:13:01+08:00",
"content/self-host/upgrading/4-15/41502.mdx": "2026-05-12T11:40:22+08:00",
"content/self-host/upgrading/4-15/41502.mdx": "2026-05-14T17:34:48+08:00",
"content/self-host/upgrading/outdated/40.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/40.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/41.en.mdx": "2026-04-26T21:08:47+08:00",
......@@ -413,8 +415,8 @@
"content/self-host/upgrading/outdated/499.mdx": "2026-05-07T15:06:40+08:00",
"content/self-host/upgrading/upgrade-intruction.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/upgrade-intruction.mdx": "2026-04-26T21:08:47+08:00",
"content/toc.en.mdx": "2026-05-09T15:25:23+08:00",
"content/toc.mdx": "2026-05-12T11:14:32+08:00",
"content/toc.en.mdx": "2026-05-14T17:58:58+08:00",
"content/toc.mdx": "2026-05-14T17:58:58+08:00",
"content/use-cases/app-cases/dalle3.en.mdx": "2026-04-26T21:08:47+08:00",
"content/use-cases/app-cases/dalle3.mdx": "2026-04-26T21:08:47+08:00",
"content/use-cases/app-cases/english_essay_correction_bot.en.mdx": "2026-04-26T21:08:47+08:00",
......
......@@ -27,6 +27,16 @@ export default defineConfig([
disallowTypeAnnotations: false
}
],
'@typescript-eslint/ban-ts-comment': [
'error',
{
'ts-expect-error': 'allow-with-description',
'ts-ignore': false,
'ts-nocheck': true,
'ts-check': false,
minimumDescriptionLength: 3
}
],
'@next/next/no-html-link-for-pages': 'off',
'react-hooks/rules-of-hooks': 'off'
......
import { type ErrType } from '../errorCode';
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../i18n/utils';
/* agentSkill: 509000 */
export enum SkillErrEnum {
unExist = 'skillUnExist',
......
import { type ErrType } from '../errorCode';
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../i18n/utils';
/* dataset: 502000 */
export enum AppErrEnum {
unExist = 'appUnExist',
......
import { type ErrType } from '../errorCode';
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../i18n/utils';
/* dataset: 504000 */
export enum ChatErrEnum {
unAuthChat = 'unAuthChat',
......
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../i18n/utils';
import { type ErrType } from '../errorCode';
/* dataset: 507000 */
......
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../i18n/utils';
import { type ErrType } from '../errorCode';
/* dataset: 501000 */
......
import { type ErrType } from '../errorCode';
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../i18n/utils';
/* dataset: 506000 */
export enum OpenApiErrEnum {
unExist = 'openapiUnExist',
......
import { type ErrType } from '../errorCode';
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../i18n/utils';
/* dataset: 505000 */
export enum OutLinkErrEnum {
unExist = 'outlinkUnExist',
......
import { type ErrType } from '../errorCode';
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../i18n/utils';
/* dataset: 508000 */
export enum PluginErrEnum {
unExist = 'pluginUnExist',
......
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../i18n/utils';
import { type ErrType } from '../errorCode';
/* s3: 510000 */
......
import { type ErrType } from '../errorCode';
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../i18n/utils';
/* dataset: 509000 */
export enum SystemErrEnum {
communityVersionNumLimit = 'communityVersionNumLimit',
......
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../i18n/utils';
import type { ErrType } from '../errorCode';
/* team: 500000 */
export enum TeamErrEnum {
......
import { type ErrType } from '../errorCode';
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../i18n/utils';
/* team: 503000 */
export enum UserErrEnum {
notUser = 'notUser',
......
......@@ -10,7 +10,7 @@ import commonErr from './code/common';
import s3Err from './code/s3';
import SystemErrEnum from './code/system';
import agentSkillErr from './code/agentSkill';
import { i18nT } from '../../../web/i18n/utils';
import { i18nT } from '../i18n/utils';
export const ERROR_CODE: { [key: number]: string } = {
400: i18nT('common:code_error.error_code.400'),
......
import type { I18nStringType, localeType } from './type';
/**
* i18n key 标记函数。
* 只返回原 key,用于在 global/service 层声明可翻译字段并保留字面量类型;真正翻译仍由前端 i18next 处理。
*/
export const i18nT = <T extends string>(key: T): T => key;
export const parseI18nString = (str: I18nStringType | string = '', lang = 'en') => {
if (!str || typeof str === 'string') return str;
......
......@@ -2,7 +2,7 @@ import dayjs from 'dayjs';
import cronParser from 'cron-parser';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import { i18nT } from '../../../web/i18n/utils';
import { i18nT } from '../i18n/utils';
dayjs.extend(utc);
dayjs.extend(timezone);
......
import { getNanoid } from '../../../common/string/tools';
import z from 'zod';
export const AgentPlanStatusSchema = z.object({
status: z.enum(['generating', 'updating']).meta({
description: '计划状态:generating 生成计划中,updating 更新计划中'
})
});
export type AgentPlanStatusType = z.infer<typeof AgentPlanStatusSchema>;
export const AgentPlanStepStatusSchema = z.enum([
'pending',
'in_progress',
'done',
'blocked',
'skipped'
]);
export type AgentPlanStepStatusType = z.infer<typeof AgentPlanStepStatusSchema>;
export const AgentPlanEvidenceSchema = z
.object({
kind: z.enum(['tool_result', 'model_output', 'user_input', 'manual']),
ref: z.string().optional(),
summary: z.string()
})
.meta({ description: '步骤执行证据,记录工具结果、模型输出、用户输入或人工备注' });
export type AgentPlanEvidenceType = z.infer<typeof AgentPlanEvidenceSchema>;
export const AgentStepItemSchema = z.object({
id: z.string().default(getNanoid(6)),
title: z.string(),
description: z.string(),
depends_on: z.array(z.string()).nullish(),
response: z.string().nullish(),
summary: z.string().nullish()
id: z
.string()
.default(() => getNanoid(6))
.meta({ description: '步骤 ID,用于在计划更新和前端渲染中稳定定位该步骤' }),
title: z.string().meta({ description: '步骤标题,简短描述该步骤要完成的事情' }),
description: z.string().meta({ description: '步骤说明,描述执行该步骤时需要关注的目标和边界' }),
acceptanceCriteria: z
.array(z.string())
.default([])
.meta({ description: '验收标准列表,用于判断该步骤是否已经完成' }),
status: AgentPlanStepStatusSchema.default('pending').meta({
description:
'步骤状态:pending 待执行,in_progress 执行中,done 已完成,blocked 受阻,skipped 已跳过'
}),
evidence: z
.array(AgentPlanEvidenceSchema)
.default([])
.meta({ description: '步骤执行证据列表,记录工具结果、模型输出、用户输入或人工备注' }),
outputSummary: z.string().optional().meta({ description: '步骤完成后的结果摘要' }),
blocker: z.string().optional().meta({ description: '步骤受阻时的原因或需要用户补充的信息' }),
needsReplan: z.boolean().optional().meta({ description: '是否需要重新规划后续步骤' })
});
export type AgentStepItemType = z.infer<typeof AgentStepItemSchema>;
export const AgentPlanSchema = z.object({
planId: z.string().default(getNanoid(6)),
planId: z.string().default(() => getNanoid(6)),
task: z.string(),
description: z.string(),
background: z.string().nullish(),
steps: z.array(AgentStepItemSchema)
steps: z
.array(AgentStepItemSchema)
.min(1)
.meta({ description: '计划步骤列表,至少包含一个可执行或可验证的步骤' })
});
export type AgentPlanType = z.infer<typeof AgentPlanSchema>;
export const AgentLoopPlanUpdateSchema = z
.object({
id: z.string().meta({ description: 'update_plan 工具调用 ID' }),
functionName: z.string().default('update_plan').meta({ description: '计划更新工具函数名' }),
params: z.string().default('').meta({ description: 'update_plan 工具参数 JSON 字符串' }),
response: z.string().optional().meta({ description: 'update_plan 工具返回给模型的结果' }),
assistantText: z
.string()
.optional()
.meta({ description: '触发 update_plan 时模型同轮输出的文本' }),
reasoningText: z
.string()
.optional()
.meta({ description: '触发 update_plan 时模型同轮输出的思考' })
})
.meta({ description: 'Agent loop 内部 update_plan 调用记录,用于恢复模型上下文和后续 UI 展示' });
export type AgentLoopPlanUpdateType = z.infer<typeof AgentLoopPlanUpdateSchema>;
export const AgentLoopAskSchema = z
.object({
id: z.string().meta({ description: 'ask_agent 工具调用 ID' }),
functionName: z.string().default('ask_agent').meta({ description: '用户追问工具函数名' }),
params: z.string().default('').meta({ description: 'ask_agent 工具参数 JSON 字符串' }),
planId: z.string().optional().meta({ description: '该追问关联的 planId,用于匹配用户回答' }),
assistantText: z
.string()
.optional()
.meta({ description: '触发 ask_agent 时模型同轮输出的文本' }),
reasoningText: z
.string()
.optional()
.meta({ description: '触发 ask_agent 时模型同轮输出的思考' })
})
.meta({
description: 'Agent loop 内部 ask_agent 调用记录,用于恢复用户追问上下文和后续 UI 展示'
});
export type AgentLoopAskType = z.infer<typeof AgentLoopAskSchema>;
export const AgentLoopStopGateSchema = z
.object({
id: z.string().meta({ description: 'Stop gate 记录 ID,用于前端稳定渲染和状态更新' }),
reason: z.string().meta({ description: 'Stop gate 拒绝结束的原因' }),
feedback: z.string().meta({ description: 'Stop gate 注入给模型的反馈内容' }),
assistantText: z.string().optional().meta({ description: '被 stop gate 打回的模型草稿文本' }),
reasoningText: z.string().optional().meta({ description: '被 stop gate 打回的模型草稿思考' })
})
.meta({ description: 'Agent loop stop gate 反馈记录,用于恢复模型上下文和后续 UI 展示' });
export type AgentLoopStopGateType = z.infer<typeof AgentLoopStopGateSchema>;
import { i18nT } from '../../../web/i18n/utils';
import { i18nT } from '../../common/i18n/utils';
import type { CompletionUsage, ReasoningEffort } from './llm/type';
import type { LLMModelItemType, EmbeddingModelItemType, STTModelType } from './model.schema';
......@@ -101,6 +101,7 @@ export const reasoningEffortList: { label: string; value: ReasoningEffort }[] =
export const completionFinishReasonMap = {
error: i18nT('chat:completion_finish_error'),
close: i18nT('chat:completion_finish_close'),
abnormal_close: i18nT('chat:completion_finish_abnormal_close'),
stop: i18nT('chat:completion_finish_stop'),
length: i18nT('chat:completion_finish_length'),
tool_calls: i18nT('chat:completion_finish_tool_calls'),
......
......@@ -231,7 +231,16 @@ export type UnStreamResponseType = openai.Chat.Completions.ChatCompletion & {
export const CompletionFinishReasonSchema = z
.union([
z.enum(['error', 'close', 'stop', 'length', 'tool_calls', 'content_filter', 'function_call']),
z.enum([
'error',
'close',
'abnormal_close',
'stop',
'length',
'tool_calls',
'content_filter',
'function_call'
]),
z.literal(null),
z.undefined()
])
......
/* v8 ignore file */
import { type PromptTemplateItem } from '../llm/type';
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../../common/i18n/utils';
import { getPromptByVersion } from './utils';
export const Prompt_userQuotePromptList: PromptTemplateItem[] = [
......
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../../common/i18n/utils';
export const evaluationFileErrors = i18nT('dashboard_evaluation:eval_file_check_error');
......
......@@ -5,7 +5,7 @@ import SwaggerParser from '@apidevtools/swagger-parser';
import yaml from 'js-yaml';
import type { OpenAPIV3 } from 'openapi-types';
import type { OpenApiJsonSchema } from './tool/httpTool/type';
import { i18nT } from '../../../web/i18n/utils';
import { i18nT } from '../../common/i18n/utils';
import z from 'zod';
export const JsonSchemaPropertiesItemSchema = z.object({
......
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../../common/i18n/utils';
export enum AppLogKeysEnum {
SOURCE = 'source',
......
......@@ -7,7 +7,7 @@ import { jsonSchema2NodeInput, jsonSchema2NodeOutput } from '../../jsonschema';
import { type StoreSecretValueType } from '../../../../common/secret/type';
import { type JsonSchemaPropertiesItemType } from '../../jsonschema';
import { NodeOutputKeyEnum, WorkflowIOValueTypeEnum } from '../../../workflow/constants';
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
import type { NodeToolConfigType } from '../../../workflow/type/node';
export const getHTTPToolSetRuntimeNode = ({
......
import { NodeOutputKeyEnum, WorkflowIOValueTypeEnum } from '../../../workflow/constants';
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
import { FlowNodeOutputTypeEnum, FlowNodeTypeEnum } from '../../../workflow/node/constant';
import { type McpToolConfigType } from '../../tool/mcpTool/type';
import { type RuntimeNodeItemType } from '../../../workflow/runtime/type';
......
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
export enum SystemToolSecretInputTypeEnum {
system = 'system',
......
import { i18nT } from '../../../web/i18n/utils';
import { i18nT } from '../../common/i18n/utils';
export enum ChatRoleEnum {
System = 'System',
......
......@@ -12,7 +12,13 @@ import { DispatchNodeResponseSchema } from '../workflow/runtime/type';
import { WorkflowInteractiveResponseTypeSchema } from '../workflow/template/system/interactive/type';
import type { FlowNodeInputItemType } from '../workflow/type/io';
import z from 'zod';
import { AgentPlanSchema } from '../ai/agent/type';
import {
AgentLoopAskSchema,
AgentLoopPlanUpdateSchema,
AgentLoopStopGateSchema,
AgentPlanSchema,
AgentPlanStatusSchema
} from '../ai/agent/type';
export const ChatHistoryItemResSchema = DispatchNodeResponseSchema.extend({
nodeId: z.string(),
......@@ -35,16 +41,6 @@ export const ToolModuleResponseItemSchema = z.object({
});
export type ToolModuleResponseItemType = z.infer<typeof ToolModuleResponseItemSchema>;
/* step call */
export const StepTitleItemSchema = z.object({
stepId: z.string(),
title: z.string(),
// Client data
folded: z.boolean().optional()
});
export type StepTitleItemType = z.infer<typeof StepTitleItemSchema>;
/* Sandbox lifecycle phase */
export type SandboxStatusPhase =
// Lifecycle phases
......@@ -202,9 +198,12 @@ export const AdminFbkSchema = z.object({
});
export type AdminFbkType = z.infer<typeof AdminFbkSchema>;
// Stores only the compacted context text; usage and request ids stay in runtime traces.
export const ContextCheckpointValueSchema = z.string();
export type ContextCheckpointValueType = z.infer<typeof ContextCheckpointValueSchema>;
export const AIChatItemValueSchema = z.object({
id: z.string().nullish(),
stepId: z.string().nullish(),
planId: z.string().nullish(),
text: z
.object({
......@@ -220,10 +219,13 @@ export const AIChatItemValueSchema = z.object({
skills: z.array(SkillModuleResponseItemSchema).nullish(),
interactive: WorkflowInteractiveResponseTypeSchema.optional(),
plan: AgentPlanSchema.nullish(),
stepTitle: StepTitleItemSchema.nullish(),
/** @deprecated */
tool: ToolModuleResponseItemSchema.nullish()
planStatus: AgentPlanStatusSchema.nullish(),
agentPlanUpdate: AgentLoopPlanUpdateSchema.nullish(),
agentAsk: AgentLoopAskSchema.nullish(),
agentStopGate: AgentLoopStopGateSchema.nullish(),
contextCheckpoint: ContextCheckpointValueSchema.nullish(),
tool: ToolModuleResponseItemSchema.nullish().meta({ deprecated: true }),
hideInUI: z.boolean().optional()
});
export type AIChatItemValueItemType = z.infer<typeof AIChatItemValueSchema>;
......
......@@ -11,89 +11,6 @@ import { sliceStrStartEnd } from '../../common/string/tools';
import { PublishChannelEnum } from '../../support/outLink/constant';
import { removeDatasetCiteText } from '../ai/llm/utils';
import type { WorkflowInteractiveResponseType } from '../workflow/template/system/interactive/type';
import { ConfirmPlanAgentText } from '../workflow/runtime/constants';
import type { AgentPlanType } from '../ai/agent/type';
export type PlanAskInfo = {
question: string;
answer: string;
};
export const getPlanCallResponseText = ({
plan,
assistantResponses
}: {
plan: AgentPlanType;
assistantResponses: AIChatItemValueItemType[];
}): string => {
// 1. 获取 ask 信息
const askText = (() => {
const asks = assistantResponses
.map((item) => {
const interactive = item.interactive;
if (!interactive) return;
if (interactive.type === 'agentPlanAskQuery') {
const question = interactive.params?.content?.trim();
if (!question) return;
const answer = interactive.params?.answer?.trim() || undefined;
return JSON.stringify({ question, answer });
}
if (interactive.type === 'agentPlanAskUserForm') {
const question = interactive.params?.description?.trim();
const answer =
interactive.params?.inputForm
?.map((item) => {
if (!item?.label) return '';
const val =
typeof item.value === 'object' ? JSON.stringify(item.value) : String(item.value);
return `${item.label}: ${val}`;
})
.filter(Boolean)
.join('; ') || undefined;
if (!question && !answer) return;
return JSON.stringify({ question, answer });
}
return undefined;
})
.filter(Boolean) as string[];
return asks.join('\n');
})();
// 2. 获取 step 信息; 如果是中途暂停,则需要提示用户暂停
const { stepText, isPause } = (() => {
const stepValues = assistantResponses.filter((item) => item.stepId);
let isPause = false;
const stepResults = plan.steps.map((step, index) => {
const result = stepValues
.filter((item) => item.stepId === step.id)
.map((item) => item.text?.content?.trim() || '')
.filter(Boolean)
.join('\n');
const executed = !!result;
if (!executed) {
isPause = true;
}
return `(${index + 1}) [${executed ? `executed` : `pending`}] id=${step.id}; title=${step.title || ''}; description=${step.description || ''}${result ? `; result: ${result}` : ''}`;
});
return {
stepText: stepResults.join('\n'),
isPause
};
})();
return `${isPause ? 'PLAN_PAUSE_HANDOFF' : ''}
COLLECTED INFO:
${askText}
STEPS:
${stepText}`;
};
// Concat 2 -> 1, and sort by role
export const concatHistories = (histories1: ChatItemMiniType[], histories2: ChatItemMiniType[]) => {
......@@ -106,6 +23,10 @@ export const concatHistories = (histories1: ChatItemMiniType[], histories2: Chat
});
};
export const hasContextCheckpoint = (history: ChatItemMiniType) =>
history.obj === ChatRoleEnum.AI &&
history.value.some((value) => Boolean(value.contextCheckpoint));
export const getChatTitleFromChatMessage = (
message?: ChatItemMiniType,
defaultValue = '新对话'
......@@ -152,7 +73,10 @@ export const getHistoryPreview = (
item.value
?.map((item) => {
return (
item.text?.content || item?.tools?.map((item) => item.toolName).join(',') || ''
item.text?.content ||
item.tool?.toolName ||
item?.tools?.map((item) => item.toolName).join(',') ||
''
);
})
.join('')
......@@ -327,16 +251,6 @@ export const checkInteractiveResponseStatus = ({
if (interactive.type === 'agentPlanAskQuery') {
return 'query';
}
if (interactive.type === 'agentPlanAskUserForm') {
try {
// 如果是表单提交,会是一个对象,如果解析失败,则认为是非表单提交。
JSON.parse(input);
} catch {
return 'query';
}
} else if (interactive.type === 'agentPlanCheck' && input !== ConfirmPlanAgentText) {
return 'query';
}
return 'submit';
};
......
import { i18nT } from '../../../web/i18n/utils';
import { i18nT } from '../../common/i18n/utils';
/* ------------ dataset -------------- */
export enum DatasetTypeEnum {
......
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../../common/i18n/utils';
export enum DatasetDataIndexTypeEnum {
default = 'default', // 默认的
......
import z from 'zod';
import { i18nT } from '../../../web/i18n/utils';
import { i18nT } from '../../common/i18n/utils';
export const I18nStringSchema = z.object({
en: z.string(),
......
import { i18nT } from '../../../web/i18n/utils';
import { i18nT } from '../../common/i18n/utils';
import type { JsonSchemaPropertiesItemType } from '../app/jsonschema';
export enum FlowNodeTemplateTypeEnum {
......
......@@ -4,10 +4,9 @@ import { skillToolsMap } from './skillTools';
import { parseI18nString } from '../../../../common/i18n/utils';
export enum SubAppIds {
plan = 'plan_agent',
ask = 'ask_agent',
model = 'model_agent',
fileRead = 'file_read',
readFiles = 'read_files',
datasetSearch = 'dataset_search'
}
......@@ -15,16 +14,7 @@ export const systemSubInfo: Record<
string,
{ name: I18nStringType; avatar: string; toolDescription: string }
> = {
[SubAppIds.plan]: {
name: {
'zh-CN': '规划Agent',
'zh-Hant': '規劃Agent',
en: 'PlanAgent'
},
avatar: 'common/detail',
toolDescription: '将任务拆解成多个步骤执行,适合处理复杂任务。'
},
[SubAppIds.fileRead]: {
[SubAppIds.readFiles]: {
name: {
'zh-CN': '文件解析',
'zh-Hant': '文件解析',
......
......@@ -4,7 +4,6 @@ import type { I18nStringType, localeType } from '../../../../common/i18n/type';
import { parseI18nString } from '../../../../common/i18n/utils';
export enum SandboxToolIds {
readFile = 'sandbox_read_file',
writeFile = 'sandbox_write_file',
editFile = 'sandbox_edit_file',
execute = 'sandbox_execute',
......@@ -17,16 +16,6 @@ export const skillToolsMap: Record<
{ name: I18nStringType; avatar: string; toolDescription: string }
> = {
// Sandbox tools
[SandboxToolIds.readFile]: {
name: {
'zh-CN': '读取文件',
'zh-Hant': '讀取文件',
en: 'ReadFile'
},
avatar: 'core/workflow/template/readFiles',
toolDescription:
'Read file contents in the sandbox, supports batch reading. Used to view SKILL.md documents, config files, execution results, etc.'
},
[SandboxToolIds.writeFile]: {
name: {
'zh-CN': '写入文件',
......@@ -93,9 +82,6 @@ export const getSkillToolInfo = (
};
// Zod parameter schemas (runtime validation)
export const SandboxReadFileSchema = z.object({
paths: z.array(z.string()).describe('Array of absolute file paths')
});
export const SandboxWriteFileSchema = z.object({
path: z.string().describe('Absolute file path'),
content: z.string().describe('File content')
......@@ -119,7 +105,7 @@ export const SandboxSearchSchema = z.object({
path: z.string().optional().describe('Starting path for search (optional)')
});
export const SandboxFetchUserFileSchema = z.object({
file_index: z.string().describe('File index from available_files (e.g. "1")'),
file_index: z.string().describe('File id from # Input Files (e.g. "current-0")'),
target_path: z
.string()
.describe(
......@@ -128,25 +114,6 @@ export const SandboxFetchUserFileSchema = z.object({
});
// ChatCompletionTool definitions (exposed to LLM)
export const sandboxReadFileTool: ChatCompletionTool = {
type: 'function',
function: {
name: SandboxToolIds.readFile,
description: skillToolsMap[SandboxToolIds.readFile].toolDescription,
parameters: {
type: 'object',
properties: {
paths: {
type: 'array',
items: { type: 'string' },
description: 'Array of absolute file paths'
}
},
required: ['paths']
}
}
};
export const sandboxWriteFileTool: ChatCompletionTool = {
type: 'function',
function: {
......@@ -233,7 +200,7 @@ export const sandboxFetchUserFileTool: ChatCompletionTool = {
properties: {
file_index: {
type: 'string',
description: 'File index from available_files (e.g. "1")'
description: 'File id from # Input Files (e.g. "current-0")'
},
target_path: {
type: 'string',
......@@ -247,7 +214,6 @@ export const sandboxFetchUserFileTool: ChatCompletionTool = {
};
export const allSandboxTools: ChatCompletionTool[] = [
sandboxReadFileTool,
sandboxWriteFileTool,
sandboxEditFileTool,
sandboxExecuteTool,
......
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../../common/i18n/utils';
import { WorkflowIOValueTypeEnum } from '../constants';
export enum FlowNodeInputTypeEnum { // render ui
export enum FlowNodeInputTypeEnum {
// render ui
reference = 'reference', // reference to other node output
input = 'input', // one line input
textarea = 'textarea',
......
......@@ -19,7 +19,7 @@ export enum SseResponseEventEnum {
// Agent
plan = 'plan', // plan response
stepTitle = 'stepTitle', // step title response
planStatus = 'planStatus', // plan lifecycle status
// Sandbox lifecycle
sandboxStatus = 'sandboxStatus', // sandbox lifecycle phase notification
......@@ -71,6 +71,3 @@ export const needReplaceReferenceInputTypeList = [
FlowNodeInputTypeEnum.addInputParam,
FlowNodeInputTypeEnum.custom
] as string[];
// Interactive
export const ConfirmPlanAgentText = 'CONFIRM';
......@@ -32,6 +32,8 @@ import { ChatRoleEnum } from '../../chat/constants';
import z from 'zod';
import type { JSONSchemaInputType } from '../../app/jsonschema';
const AgentPlanNodeStatusSchema = z.enum(['set_plan', 'update_plan', 'ask_question']);
/*
1. 输入线分类:普通线(实际上就是从 start 直接过来的分支)和递归线(可以追溯到自身的分支)
2. 递归线,会根据最近的一个 target 分支进行分类,同一个分支的属于一组
......@@ -167,6 +169,9 @@ export const DispatchNodeResponseSchema = z
textOutput: z.string().optional().meta({ description: '文本输出' }),
llmRequestIds: z.array(z.string()).optional().meta({ description: 'LLM 请求追踪 ID 列表' }),
agentPlanStatus: AgentPlanNodeStatusSchema.optional().meta({
description: 'Agent 计划节点状态'
}),
error: z
.union([z.record(z.string(), z.any()), z.string()])
......@@ -230,15 +235,6 @@ export const DispatchNodeResponseSchema = z
rerankWeight: z.number().optional().meta({ description: '重排权重' }),
reRankInputTokens: z.number().optional().meta({ description: '重排输入 token' }),
searchUsingReRank: z.boolean().optional().meta({ description: '使用重排' }),
queryExtensionResult: z
.object({
model: z.string().meta({ description: '模型' }),
inputTokens: z.number().meta({ description: '输入 token' }),
outputTokens: z.number().meta({ description: '输出 token' }),
query: z.string().meta({ description: '查询内容' })
})
.optional()
.meta({ description: '查询扩展结果' }),
deepSearchResult: z
.object({
model: z.string().meta({ description: '模型' }),
......@@ -371,7 +367,7 @@ export type DispatchNodeResponseType = Omit<
};
export type DispatchNodeResultType<
T = unknown,
T = Record<string, never>,
ERR = { [NodeOutputKeyEnum.errorText]?: string }
> = {
[DispatchNodeResponseKeyEnum.answerText]?: string;
......
......@@ -178,19 +178,14 @@ export const getLastInteractiveValue = (
// Check is user select
if (
(lastValue.interactive.type === 'userSelect' ||
lastValue.interactive.type === 'agentPlanAskUserSelect') &&
lastValue.interactive.type === 'userSelect' &&
!lastValue.interactive?.params?.userSelectedVal
) {
return lastValue.interactive;
}
// Check is user input
if (
(lastValue.interactive.type === 'userInput' ||
lastValue.interactive.type === 'agentPlanAskUserForm') &&
!lastValue.interactive?.params?.submitted
) {
if (lastValue.interactive.type === 'userInput' && !lastValue.interactive?.params?.submitted) {
return lastValue.interactive;
}
......@@ -198,18 +193,13 @@ export const getLastInteractiveValue = (
return lastValue.interactive;
}
// Agent plan check
// Agent plan ask query
if (
lastValue.interactive.type === 'agentPlanCheck' &&
!lastValue.interactive?.params?.confirmed
lastValue.interactive.type === 'agentPlanAskQuery' &&
!lastValue.interactive.params.answer
) {
return lastValue.interactive;
}
// Agent plan ask query
if (lastValue.interactive.type === 'agentPlanAskQuery') {
return lastValue.interactive;
}
}
return;
......@@ -520,7 +510,7 @@ export const textAdaptGptResponse = ({
text?: string | null;
reasoning_content?: string | null;
finish_reason?: null | 'stop';
extraData?: Object;
extraData?: object;
}) => {
return {
...extraData,
......
......@@ -3,7 +3,7 @@ import { FlowNodeInputTypeEnum } from '../node/constant';
import { WorkflowIOValueTypeEnum } from '../constants';
import { chatNodeSystemPromptTip, systemPromptTip } from './tip';
import { type FlowNodeInputItemType } from '../type/io';
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../../common/i18n/utils';
export const Input_Template_History: FlowNodeInputItemType = {
key: NodeInputKeyEnum.history,
......
......@@ -2,7 +2,7 @@ import type { FlowNodeOutputItemType } from '../type/io';
import { NodeOutputKeyEnum } from '../constants';
import { FlowNodeOutputTypeEnum } from '../node/constant';
import { WorkflowIOValueTypeEnum } from '../constants';
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../../common/i18n/utils';
export const Output_Template_AddOutput: FlowNodeOutputItemType = {
id: NodeOutputKeyEnum.addOutputParam,
......
......@@ -10,7 +10,7 @@ import {
NodeOutputKeyEnum,
WorkflowIOValueTypeEnum
} from '../../../../constants';
import { i18nT } from '../../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../../common/i18n/utils';
import {
Input_Template_Children_Node_List,
Input_Template_NESTED_NODE_OFFSET,
......
......@@ -12,7 +12,7 @@ import {
FlowNodeTemplateTypeEnum
} from '../../../../constants';
import { Input_Template_History, Input_Template_UserChatInput } from '../../../input';
import { i18nT } from '../../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../../common/i18n/utils';
export const RunAppModule: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.runApp,
......
......@@ -17,7 +17,7 @@ import {
Input_Template_UserChatInput
} from '../../input';
import { chatNodeSystemPromptTip, systemPromptTip } from '../../tip';
import { i18nT } from '../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../common/i18n/utils';
import { Input_Template_File_Link } from '../../input';
import { Output_Template_Error_Message } from '../../output';
import { DatasetSearchModeEnum } from '../../../../dataset/constants';
......@@ -72,6 +72,12 @@ export const AgentNode: FlowNodeTemplateType = {
value: true
},
{
key: NodeInputKeyEnum.aiChatReasoningEffort,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
valueType: WorkflowIOValueTypeEnum.string
},
{
key: NodeInputKeyEnum.aiChatTopP,
renderTypeList: [FlowNodeInputTypeEnum.hidden],
label: '',
......
......@@ -19,7 +19,7 @@ import {
Input_Template_UserChatInput,
Input_Template_File_Link
} from '../../input';
import { i18nT } from '../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../common/i18n/utils';
import { Output_Template_Error_Message } from '../../output';
export const AiChatQuoteRole = {
......
......@@ -5,7 +5,7 @@ import {
NodeInputKeyEnum,
FlowNodeTemplateTypeEnum
} from '../../constants';
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
export const AssignedAnswerModule: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.answerNode,
......
......@@ -16,7 +16,7 @@ import {
Input_Template_UserChatInput
} from '../../input';
import { Input_Template_System_Prompt } from '../../input';
import { i18nT } from '../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../common/i18n/utils';
export const ClassifyQuestionModule: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.classifyQuestion,
......
......@@ -11,7 +11,7 @@ import {
FlowNodeTemplateTypeEnum
} from '../../../constants';
import { Input_Template_SelectAIModel, Input_Template_History } from '../../input';
import { i18nT } from '../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../common/i18n/utils';
import { Output_Template_Error_Message } from '../../output';
export const ContextExtractModule: FlowNodeTemplateType = {
......
......@@ -5,7 +5,7 @@ import {
NodeInputKeyEnum,
FlowNodeTemplateTypeEnum
} from '../../constants';
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
export const CustomFeedbackNode: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.customFeedback,
......
......@@ -13,7 +13,7 @@ import {
} from '../../constants';
import { getNanoid } from '../../../../common/string/tools';
import { type FlowNodeInputItemType } from '../../type/io';
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
export const getOneQuoteInputTemplate = ({
key = getNanoid(),
......
......@@ -14,7 +14,7 @@ import {
} from '../../constants';
import { Input_Template_UserChatInput } from '../input';
import { DatasetSearchModeEnum } from '../../../dataset/constants';
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
import { Output_Template_Error_Message } from '../output';
export const Dataset_SEARCH_DESC = i18nT('workflow:template.dataset_search_intro');
......
......@@ -13,7 +13,7 @@ import {
} from '../../constants';
import { Input_Template_DynamicInput } from '../input';
import { Output_Template_AddOutput } from '../output';
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
export const HttpNode468: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.httpRequest468,
......
import { i18nT } from '../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../common/i18n/utils';
export enum VariableConditionEnum {
equalTo = 'equalTo',
......
import { i18nT } from '../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../common/i18n/utils';
import {
FlowNodeTemplateTypeEnum,
NodeInputKeyEnum,
......
import { i18nT } from '../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../common/i18n/utils';
import {
FlowNodeTemplateTypeEnum,
NodeInputKeyEnum,
......
......@@ -87,19 +87,18 @@ export type LoopRunInteractive = InteractiveNodeType & {
};
};
// Agent Interactive
export const AgentPlanCheckInteractiveSchema = z.object({
type: z.literal('agentPlanCheck'),
params: z.object({
confirmed: z.boolean().optional()
})
});
export type AgentPlanCheckInteractive = z.infer<typeof AgentPlanCheckInteractiveSchema>;
export const AgentPlanAskOptionSchema = z.string().min(1);
export type AgentPlanAskOption = z.infer<typeof AgentPlanAskOptionSchema>;
export const AgentPlanAskQueryInteractiveSchema = z.object({
type: z.literal('agentPlanAskQuery'),
params: z.object({
content: z.string(),
reason: z.string().optional(),
blockerType: z
.enum(['missing_required_input', 'tool_unavailable', 'ambiguous_goal'])
.optional(),
options: z.array(AgentPlanAskOptionSchema).min(3).max(5),
answer: z.string().optional()
})
});
......@@ -112,7 +111,7 @@ export const UserSelectOptionItemSchema = z.object({
});
export type UserSelectOptionItemType = z.infer<typeof UserSelectOptionItemSchema>;
export const UserSelectInteractiveSchema = z.object({
type: z.literal('userSelect').or(z.literal('agentPlanAskUserSelect')),
type: z.literal('userSelect'),
params: z.object({
description: z.string(),
userSelectOptions: z.array(UserSelectOptionItemSchema),
......@@ -140,7 +139,7 @@ export const UserInputFormItemSchema = AppFileSelectConfigTypeSchema.extend({
});
export type UserInputFormItemType = z.infer<typeof UserInputFormItemSchema>;
export const UserInputInteractiveSchema = z.object({
type: z.literal('userInput').or(z.literal('agentPlanAskUserForm')),
type: z.literal('userInput'),
params: z.object({
description: z.string(),
inputForm: z.array(UserInputFormItemSchema),
......@@ -168,7 +167,6 @@ export const InteractiveNodeResponseTypeSchema = z.intersection(
LoopInteractiveSchema,
LoopRunInteractiveSchema,
PaymentPauseInteractiveSchema,
AgentPlanCheckInteractiveSchema,
AgentPlanAskQueryInteractiveSchema
]),
z.object({
......
import { i18nT } from '../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../common/i18n/utils';
import {
FlowNodeTemplateTypeEnum,
NodeInputKeyEnum,
......
......@@ -12,7 +12,7 @@ import {
} from '../../constants';
import { Input_Template_DynamicInput } from '../input';
import { Output_Template_AddOutput, Output_Template_Error_Message } from '../output';
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
export const nodeLafCustomInputConfig = {
selectValueTypeList: Object.values(WorkflowIOValueTypeEnum),
......
import { i18nT } from '../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../common/i18n/utils';
import {
FlowNodeTemplateTypeEnum,
NodeInputKeyEnum,
......
......@@ -10,7 +10,7 @@ import {
NodeOutputKeyEnum,
WorkflowIOValueTypeEnum
} from '../../../constants';
import { i18nT } from '../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../common/i18n/utils';
export const LoopStartNode: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.nestedStart,
......
......@@ -10,7 +10,7 @@ import {
NodeOutputKeyEnum,
WorkflowIOValueTypeEnum
} from '../../../constants';
import { i18nT } from '../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../common/i18n/utils';
import {
Input_Template_Children_Node_List,
Input_Template_NESTED_NODE_OFFSET,
......
import { FlowNodeTypeEnum } from '../../../node/constant';
import { type FlowNodeTemplateType } from '../../../type/node';
import { FlowNodeTemplateTypeEnum } from '../../../constants';
import { i18nT } from '../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../common/i18n/utils';
export const LoopRunBreakNode: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.loopRunBreak,
......
......@@ -10,7 +10,7 @@ import {
NodeOutputKeyEnum,
WorkflowIOValueTypeEnum
} from '../../../constants';
import { i18nT } from '../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../common/i18n/utils';
import { LoopRunModeEnum } from './loopRun';
export const LoopRunStartNode: FlowNodeTemplateType = {
......
......@@ -10,7 +10,7 @@ import {
NodeOutputKeyEnum,
WorkflowIOValueTypeEnum
} from '../../../constants';
import { i18nT } from '../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../common/i18n/utils';
import {
Input_Template_Children_Node_List,
Input_Template_NESTED_NODE_OFFSET,
......
import { FlowNodeTypeEnum } from '../../node/constant';
import { type FlowNodeTemplateType } from '../../type/node';
import { FlowNodeTemplateTypeEnum } from '../../constants';
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
export const PluginConfigNode: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.pluginConfig,
......
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
import { FlowNodeTemplateTypeEnum } from '../../constants';
import { FlowNodeTypeEnum } from '../../node/constant';
import { type FlowNodeTemplateType } from '../../type/node';
......
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
import { FlowNodeTemplateTypeEnum } from '../../constants';
import { FlowNodeTypeEnum } from '../../node/constant';
import { type FlowNodeTemplateType } from '../../type/node';
......
......@@ -15,7 +15,7 @@ import {
Input_Template_UserChatInput,
Input_Template_SelectAIModel
} from '../input';
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
export const AiQueryExtension: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.queryExtension,
......
import { i18nT } from '../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../common/i18n/utils';
import {
FlowNodeTemplateTypeEnum,
NodeInputKeyEnum,
......
......@@ -13,7 +13,7 @@ import { type FlowNodeTemplateType } from '../../../type/node';
import { Input_Template_DynamicInput } from '../../input';
import { Output_Template_AddOutput } from '../../output';
import { JS_TEMPLATE } from './constants';
import { i18nT } from '../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../common/i18n/utils';
export const CodeNode: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.code,
......
import { FlowNodeTypeEnum } from '../../node/constant';
import { type FlowNodeTemplateType } from '../../type/node';
import { FlowNodeTemplateTypeEnum } from '../../constants';
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
export const StopToolNode: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.stopTool,
......
import { FlowNodeTypeEnum } from '../../node/constant';
import { type FlowNodeTemplateType } from '../../type/node';
import { FlowNodeTemplateTypeEnum } from '../../constants';
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
export const SystemConfigNode: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.systemConfig,
......
......@@ -10,7 +10,7 @@ import {
NodeInputKeyEnum,
FlowNodeTemplateTypeEnum
} from '../../constants';
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
export const TextEditorNode: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.textEditor,
......
......@@ -17,7 +17,7 @@ import {
Input_Template_UserChatInput
} from '../input';
import { chatNodeSystemPromptTip, systemPromptTip } from '../tip';
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
import { Input_Template_File_Link } from '../input';
import { Output_Template_Error_Message } from '../output';
......
import { FlowNodeTypeEnum } from '../../node/constant';
import { type FlowNodeTemplateType } from '../../type/node';
import { FlowNodeTemplateTypeEnum } from '../../constants';
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
export const ToolParamsNode: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.toolParams,
......
......@@ -5,7 +5,7 @@ import {
NodeInputKeyEnum,
WorkflowIOValueTypeEnum
} from '../../../constants';
import { i18nT } from '../../../../../../web/i18n/utils';
import { i18nT } from '../../../../../common/i18n/utils';
export const VariableUpdateNode: FlowNodeTemplateType = {
id: FlowNodeTypeEnum.variableUpdate,
......
......@@ -6,7 +6,7 @@ import {
FlowNodeTemplateTypeEnum
} from '../../constants';
import { Input_Template_UserChatInput } from '../input';
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
import { type FlowNodeOutputItemType } from '../../type/io';
export const userFilesInput: FlowNodeOutputItemType = {
......
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../../common/i18n/utils';
export const chatNodeSystemPromptTip = i18nT('common:core.app.tip.chatNodeSystemPromptTip');
export const systemPromptTip = i18nT('common:core.app.tip.systemPromptTip');
......@@ -46,7 +46,7 @@ import {
Input_Template_Stream_MODE,
Input_Template_UserChatInput
} from './template/input';
import { i18nT } from '../../../web/i18n/utils';
import { i18nT } from '../../common/i18n/utils';
import { type RuntimeUserPromptType, type UserChatItemType } from '../../core/chat/type';
import { getNanoid } from '../../common/string/tools';
import { ChatRoleEnum } from '../../core/chat/constants';
......@@ -89,7 +89,7 @@ export const splitGuideModule = (guideModules?: StoreNodeItemType) => {
const questionGuide: AppQGConfigType =
typeof questionGuideVal === 'boolean'
? { ...defaultQGConfig, open: questionGuideVal }
: questionGuideVal ?? defaultQGConfig;
: (questionGuideVal ?? defaultQGConfig);
const ttsConfig: AppTTSConfigType =
guideModules?.inputs?.find((item) => item.key === NodeInputKeyEnum.tts)?.value ??
......@@ -463,7 +463,7 @@ export const clientGetWorkflowToolRunUserQuery = ({
}) => {
const pluginInputsWithValue = pluginInputs.map((input) => {
const { key } = input;
let value = variables?.hasOwnProperty(key) ? variables[key] : input.defaultValue;
const value = variables?.hasOwnProperty(key) ? variables[key] : input.defaultValue;
return {
...input,
......
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../../common/i18n/utils';
import {
NullRoleVal,
CommonPerKeyEnum,
......
......@@ -7,7 +7,7 @@ import {
} from '../constant';
import type { PermissionListType, PermissionValueType, RolePerMapType } from '../type';
import { type RoleListType } from '../type';
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../../common/i18n/utils';
import { sumPer } from '../utils';
export enum AppPermissionKeyEnum {
......
import type { PermissionListType, PermissionValueType, RolePerMapType } from './type';
import { type RoleListType } from './type';
import { i18nT } from '../../../web/i18n/utils';
import { i18nT } from '../../common/i18n/utils';
import { sumPer } from './utils';
export enum AuthUserTypeEnum {
token = 'token',
......
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../../common/i18n/utils';
import {
NullRoleVal,
CommonPerKeyEnum,
......
......@@ -6,7 +6,7 @@ import type {
RolePerMapType
} from '../type';
import { CommonRoleList, CommonPerList } from '../constant';
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../../common/i18n/utils';
import { sumPer } from '../utils';
export enum TeamPerKeyEnum {
......
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../../common/i18n/utils';
export enum BillTypeEnum {
balance = 'balance',
......
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../../common/i18n/utils';
import { BillTypeEnum } from '../bill/constants';
export enum SubTypeEnum {
......
import { i18nT } from '../../../../../web/i18n/utils';
import { i18nT } from '../../../../common/i18n/utils';
export enum DiscountCouponTypeEnum {
monthStandardDiscount70 = 'monthStandardDiscount70',
......
import { i18nT } from '../../../../web/i18n/utils';
import { i18nT } from '../../../common/i18n/utils';
export enum UsageSourceEnum {
fastgpt = 'fastgpt',
......
This diff is collapsed. Click to expand it.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment