feat: 支持 agent 运行取消功能
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
|
||||
**Architecture:** 使用“协作取消 + 主任务中断”方案:API 层写入 Redis cancel 信号,runtime 在 worker 进程内并行 watcher 监听信号,命中后先调用 active agent 的 `interrupt()` 做优雅收尾,再 `cancel()` 当前 run 主任务做硬兜底。终态统一通过 `RUN_ERROR` 事件落库,复用现有 `FAILED` 会话语义,避免数据库枚举迁移。
|
||||
|
||||
**Tech Stack:** FastAPI, TaskIQ, Redis, AgentScope, SQLAlchemy, Pytest, Ruff, BasedPyright
|
||||
**Tech Stack:** FastAPI, TaskIQ, Redis, AgentScope, SQLAlchemy, Flutter, Pytest, Ruff, BasedPyright
|
||||
|
||||
---
|
||||
|
||||
@@ -390,3 +390,81 @@ git commit -m "feat: support run cancellation with RUN_CANCELED failed semantics
|
||||
- 回滚 `router/service/dependencies` cancel 新接口
|
||||
- 回滚 `runner/orchestrator/tasks` cancel 注入逻辑
|
||||
- 保持原 `POST /runs` 与 SSE 流程不变
|
||||
|
||||
### Task 7: 前端接入 cancel API(发送后“停止生成”按钮走后端真实取消)
|
||||
|
||||
**Files:**
|
||||
- Modify: `apps/lib/features/chat/data/services/ag_ui_service.dart`
|
||||
- Modify: `apps/lib/features/chat/presentation/bloc/chat_bloc.dart`
|
||||
- Modify: `apps/lib/features/chat/data/models/ag_ui_event.dart`
|
||||
- Modify: `apps/lib/features/home/ui/screens/home_screen_interactions.dart`
|
||||
- Test: `apps/test/features/chat/data/services/ag_ui_service_test.dart`
|
||||
- Test: `apps/test/features/chat/presentation/chat_bloc_attachment_sync_test.dart`
|
||||
|
||||
**Step 1: 在 AgUiService 维护当前运行态标识**
|
||||
|
||||
在 `AgUiService` 增加字段:
|
||||
- `_activeThreadIdForRun: String?`
|
||||
- `_activeRunId: String?`
|
||||
|
||||
并在 `sendMessage` 成功拿到 `/runs` 响应后设置这两个字段;在收到目标 run 的终态事件(`RUN_FINISHED` / `RUN_ERROR`)后清理。
|
||||
|
||||
**Step 2: 将 cancelCurrentRun 从“仅断 SSE”升级为“先调用后端 cancel,再本地收流”**
|
||||
|
||||
`AgUiService.cancelCurrentRun()` 改为:
|
||||
1. 若 `_activeThreadIdForRun` 或 `_activeRunId` 为空:退化为当前行为(仅关闭 SSE)
|
||||
2. 否则先调用:
|
||||
|
||||
```text
|
||||
POST /api/v1/agent/runs/{threadId}/cancel?runId={runId}
|
||||
```
|
||||
|
||||
3. 请求成功后再执行 `_cancelActiveSseSubscription()`(避免继续占用本地连接)
|
||||
4. 不论后端是否即时生效,都清理本地 active run 字段,防止重复 cancel
|
||||
|
||||
说明:这一步就是把“发送消息后的停止按钮”真正连到后端取消能力。
|
||||
|
||||
**Step 3: 错误语义细化(前端展示友好)**
|
||||
|
||||
在 `chat_bloc.dart` 处理 `RunErrorEvent` 时:
|
||||
- 如果 `errorEvent.code == 'RUN_CANCELED'`,错误文案不按失败提示展示(可置空或显示“已停止生成”)
|
||||
- 仍执行 `_resetRunState` 与 tool 卡片收尾,保持 UI 一致性
|
||||
|
||||
**Step 4: 保持现有按钮入口,不改交互入口路径**
|
||||
|
||||
`home_screen_interactions.dart` 里的 `_onStopGenerating -> _chatBloc.cancelCurrentRun()` 已经是正确入口,继续复用。
|
||||
|
||||
仅调整 Toast 文案策略:
|
||||
- 请求已发出:`已请求停止`
|
||||
- 收到 `RUN_ERROR(code=RUN_CANCELED)`:最终态 `已停止生成`
|
||||
|
||||
**Step 5: 写 AgUiService 测试(先红)**
|
||||
|
||||
在 `ag_ui_service_test.dart` 增加:
|
||||
- `cancelCurrentRun` 会调用新端点 `/api/v1/agent/runs/{threadId}/cancel`
|
||||
- query 参数包含 `runId`
|
||||
- 调用后会关闭当前 SSE subscription
|
||||
|
||||
**Step 6: 写 ChatBloc 测试(先红)**
|
||||
|
||||
在 `chat_bloc_attachment_sync_test.dart` 增加:
|
||||
- 收到 `RunErrorEvent(message: 'run canceled by user', code: 'RUN_CANCELED')` 后:
|
||||
- `isWaitingFirstToken/isStreaming/isCancelling` 全部归零
|
||||
- 不显示普通失败文案(或显示取消态文案,按你们最终文案策略断言)
|
||||
|
||||
**Step 7: 运行 Flutter 测试**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
flutter test apps/test/features/chat/data/services/ag_ui_service_test.dart apps/test/features/chat/presentation/chat_bloc_attachment_sync_test.dart
|
||||
```
|
||||
|
||||
Expected: PASS
|
||||
|
||||
**Step 8: 前端接入提交**
|
||||
|
||||
```bash
|
||||
git add apps/lib/features/chat/data/services/ag_ui_service.dart apps/lib/features/chat/presentation/bloc/chat_bloc.dart apps/lib/features/chat/data/models/ag_ui_event.dart apps/lib/features/home/ui/screens/home_screen_interactions.dart apps/test/features/chat/data/services/ag_ui_service_test.dart apps/test/features/chat/presentation/chat_bloc_attachment_sync_test.dart
|
||||
git commit -m "feat: wire stop-generating button to backend run cancel API"
|
||||
```
|
||||
|
||||
@@ -11,6 +11,7 @@ Base URL: `/api/v1/agent`
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| POST | `/runs` | 创建一次 agent run(异步入队) |
|
||||
| POST | `/runs/{thread_id}/cancel` | 请求取消指定 run |
|
||||
| GET | `/runs/{thread_id}/events` | 订阅 SSE 事件流 |
|
||||
| GET | `/history` | 获取历史快照(按天分页) |
|
||||
| POST | `/attachments` | 上传用户图片附件 |
|
||||
@@ -95,7 +96,43 @@ Base URL: `/api/v1/agent`
|
||||
|
||||
---
|
||||
|
||||
## 3) GET `/history`
|
||||
## 3) POST `/runs/{thread_id}/cancel`
|
||||
|
||||
请求取消指定 run。该接口返回 `202` 仅表示取消请求已被后端接收,不保证运行已在同一时刻停止。
|
||||
|
||||
### Path
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `thread_id` | string | 会话 ID |
|
||||
|
||||
### Query
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `runId` | string | 是 | 需要取消的 run ID |
|
||||
|
||||
### Response
|
||||
|
||||
`202 Accepted`
|
||||
|
||||
```ts
|
||||
{
|
||||
threadId: string;
|
||||
runId: string;
|
||||
accepted: true;
|
||||
}
|
||||
```
|
||||
|
||||
### 错误码
|
||||
|
||||
- `401` 未认证
|
||||
- `403` 非会话所有者
|
||||
- `422` 参数非法
|
||||
|
||||
---
|
||||
|
||||
## 4) GET `/history`
|
||||
|
||||
返回历史快照(`HistorySnapshotResponse`),不是 SSE 包装事件。
|
||||
|
||||
@@ -146,7 +183,7 @@ tool 消息在存储层用于运行时上下文续接,不在 `/history` 对外
|
||||
|
||||
---
|
||||
|
||||
## 4) POST `/attachments`
|
||||
## 5) POST `/attachments`
|
||||
|
||||
上传图片附件,返回可直接用于 `RunAgentInput.messages[].content[].url` 的签名链接。
|
||||
|
||||
@@ -182,7 +219,7 @@ tool 消息在存储层用于运行时上下文续接,不在 `/history` 对外
|
||||
|
||||
---
|
||||
|
||||
## 5) GET `/attachments/signed-url`
|
||||
## 6) GET `/attachments/signed-url`
|
||||
|
||||
对已有存储对象重新签名。
|
||||
|
||||
@@ -205,7 +242,7 @@ tool 消息在存储层用于运行时上下文续接,不在 `/history` 对外
|
||||
|
||||
---
|
||||
|
||||
## 6) POST `/transcribe`
|
||||
## 7) POST `/transcribe`
|
||||
|
||||
WAV 音频转写。
|
||||
|
||||
|
||||
@@ -75,6 +75,20 @@ data: <json>
|
||||
}
|
||||
```
|
||||
|
||||
取消语义(当前实现):
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "RUN_ERROR",
|
||||
"threadId": "...",
|
||||
"runId": "...",
|
||||
"message": "run canceled by user",
|
||||
"code": "RUN_CANCELED"
|
||||
}
|
||||
```
|
||||
|
||||
说明:`RUN_CANCELED` 表示用户主动中断,本阶段后端仍复用会话 `failed` 状态以保持兼容。
|
||||
|
||||
### 3.2 阶段事件
|
||||
|
||||
#### `STEP_STARTED`
|
||||
|
||||
Reference in New Issue
Block a user