Files
social-app/docs/plans/2026-03-08-agent-tool-architecture-implementation-plan.md
T

450 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Agent Tool Architecture Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** 修复 agent 工具架构 8 个问题,先恢复端到端闭环与安全正确性,再补齐 UI Schema、对象存储、阶段解耦、多模态与 ASR。
**Architecture:** 采用两阶段落地。Phase 1 先完成后端上下文主控、CrewAI Tools 完整迁移、审批后异步续跑闭环;Phase 2 按 `#3 -> #2 -> #4 -> #7 -> #8` 逐项扩展能力。所有变更遵循 AG-UI 事件流语义,三阶段固定发送 StepStarted/StepFinished。
**Tech Stack:** FastAPI, Pydantic, CrewAI, LiteLLM, Redis, Postgres, MinIO/Supabase Storage, pytest
**Status:** Completed on 2026-03-08 (Task 1-8 delivered; Task 4/5/6 finalized with E2E object-storage verification)
---
### Task 1: 锁定 Phase 1 契约(移除前端历史语义)
**Files:**
- Modify: `backend/src/core/agent/domain/agui_input.py`
- Modify: `backend/src/core/agent/application/run_service.py`
- Modify: `backend/src/v1/agent/schemas.py`
- Test: `backend/tests/unit/core/agent/test_run_resume_service.py`
**Step 1: Write the failing test**
```python
def test_run_ignores_client_history_messages(fake_run_input_with_messages):
result = service.run(run_input=fake_run_input_with_messages)
assert result.used_context_source == "backend"
```
**Step 2: Run test to verify it fails**
Run: `cd backend && uv run pytest tests/unit/core/agent/test_run_resume_service.py -k ignores_client_history -v`
Expected: FAIL,当前实现仍读取/依赖前端 history。
**Step 3: Write minimal implementation**
```python
# run_service.py
user_input = extract_latest_user_text(run_input)
history = await load_context_from_backend_sources(session_id)
```
**Step 4: Run test to verify it passes**
Run: `cd backend && uv run pytest tests/unit/core/agent/test_run_resume_service.py -k ignores_client_history -v`
Expected: PASS
**Step 5: Commit**
```bash
git add backend/src/core/agent/domain/agui_input.py backend/src/core/agent/application/run_service.py backend/src/v1/agent/schemas.py backend/tests/unit/core/agent/test_run_resume_service.py
git commit -m "refactor(agent): make backend own conversation context"
```
### Task 2: CrewAI Tools 完整迁移(替换硬编码分发)
**Files:**
- Create: `backend/src/core/agent/infrastructure/crewai/tools_registry.py`
- Create: `backend/src/core/agent/infrastructure/crewai/tools/create_calendar_event_tool.py`
- Modify: `backend/src/core/agent/infrastructure/crewai/runtime.py`
- Modify: `backend/src/core/agent/application/run_service.py`
- Test: `backend/tests/unit/core/agent/test_crewai_runtime.py`
**Step 1: Write the failing test**
```python
def test_runtime_uses_registered_crewai_tools():
runtime = build_runtime_with_registry(["create_calendar_event"])
result = runtime.execute(user_input="帮我创建日历事件", system_prompt="x")
assert result.tool_calls[0].tool_name == "create_calendar_event"
```
**Step 2: Run test to verify it fails**
Run: `cd backend && uv run pytest tests/unit/core/agent/test_crewai_runtime.py -k registered_crewai_tools -v`
Expected: FAIL,当前路径仍是 run_service 硬编码。
**Step 3: Write minimal implementation**
```python
# tools_registry.py
TOOLS = {"create_calendar_event": CreateCalendarEventTool()}
def tools_for_stage(stage: str) -> list[BaseTool]:
return STAGE_TOOL_MAP.get(stage, [])
```
**Step 4: Run test to verify it passes**
Run: `cd backend && uv run pytest tests/unit/core/agent/test_crewai_runtime.py -k registered_crewai_tools -v`
Expected: PASS
**Step 5: Commit**
```bash
git add backend/src/core/agent/infrastructure/crewai/tools_registry.py backend/src/core/agent/infrastructure/crewai/tools/create_calendar_event_tool.py backend/src/core/agent/infrastructure/crewai/runtime.py backend/src/core/agent/application/run_service.py backend/tests/unit/core/agent/test_crewai_runtime.py
git commit -m "feat(agent): migrate backend tools to crewai tool registry"
```
### Task 3: 修复审批后异步续跑闭环(#5)
**Files:**
- Modify: `backend/src/core/agent/application/resume_service.py`
- Modify: `backend/src/core/agent/infrastructure/queue/tasks.py`
- Modify: `backend/src/core/agent/application/session_state_persistence.py`
- Test: `backend/tests/integration/core/agent/test_queue_run_resume.py`
**Step 1: Write the failing test**
```python
def test_resume_triggers_async_loop_until_final_assistant_message(client):
response = client.post("/v1/agent/runs/{id}/resume", json={"approve": True})
assert response.status_code == 202
assert eventually_has_final_assistant_message(id)
```
**Step 2: Run test to verify it fails**
Run: `cd backend && uv run pytest tests/integration/core/agent/test_queue_run_resume.py -k triggers_async_loop -v`
Expected: FAIL,当前审批后直接完成。
**Step 3: Write minimal implementation**
```python
# resume_service.py
await mark_session_resuming(...)
await enqueue_resume_task(...)
return ResumeAccepted(...)
```
**Step 4: Run test to verify it passes**
Run: `cd backend && uv run pytest tests/integration/core/agent/test_queue_run_resume.py -k triggers_async_loop -v`
Expected: PASS
**Step 5: Commit**
```bash
git add backend/src/core/agent/application/resume_service.py backend/src/core/agent/infrastructure/queue/tasks.py backend/src/core/agent/application/session_state_persistence.py backend/tests/integration/core/agent/test_queue_run_resume.py
git commit -m "fix(agent): continue agent loop asynchronously after tool approval"
```
### Task 4: 三阶段 Step 事件完整化(intent/execution/organization
**Files:**
- Modify: `backend/src/core/agent/infrastructure/crewai/runtime.py`
- Modify: `backend/src/core/agent/infrastructure/agui/bridge.py`
- Test: `backend/tests/unit/core/agent/test_agui_bridge.py`
- Test: `backend/tests/integration/v1/agent/test_sse_flow_live.py`
**Step 1: Write the failing test**
```python
def test_each_stage_emits_step_started_and_finished():
events = collect_events_from_run(...)
assert has_step_pair(events, "intent")
assert has_step_pair(events, "execution")
assert has_step_pair(events, "organization")
```
**Step 2: Run test to verify it fails**
Run: `cd backend && uv run pytest tests/integration/v1/agent/test_sse_flow_live.py -k emits_step_started_and_finished -v`
Expected: FAIL,至少一个阶段事件缺失。
**Step 3: Write minimal implementation**
```python
emit_step_started(stage)
stage_output = run_stage(stage)
emit_step_finished(stage)
```
**Step 4: Run test to verify it passes**
Run: `cd backend && uv run pytest tests/integration/v1/agent/test_sse_flow_live.py -k emits_step_started_and_finished -v`
Expected: PASS
**Step 5: Commit**
```bash
git add backend/src/core/agent/infrastructure/crewai/runtime.py backend/src/core/agent/infrastructure/agui/bridge.py backend/tests/unit/core/agent/test_agui_bridge.py backend/tests/integration/v1/agent/test_sse_flow_live.py
git commit -m "feat(agent): emit ag-ui step events for three-stage flow"
```
### Task 5: 工具输出统一为 UI Schema v1#3
**Files:**
- Modify: `backend/src/core/agent/infrastructure/crewai/tools/create_calendar_event_tool.py`
- Modify: `backend/src/core/agent/domain/message_metadata.py`
- Test: `backend/tests/unit/core/agent/test_run_resume_service.py`
**Step 1: Write the failing test**
```python
def test_calendar_tool_returns_ui_schema_v1():
result = run_calendar_tool(...)
assert result["type"] == "calendar_card.v1"
assert result["version"] == "v1"
```
**Step 2: Run test to verify it fails**
Run: `cd backend && uv run pytest tests/unit/core/agent/test_run_resume_service.py -k returns_ui_schema_v1 -v`
Expected: FAIL,当前返回简单 status/event_id。
**Step 3: Write minimal implementation**
```python
return {
"type": "calendar_card.v1",
"version": "v1",
"data": {...},
"actions": [...],
}
```
**Step 4: Run test to verify it passes**
Run: `cd backend && uv run pytest tests/unit/core/agent/test_run_resume_service.py -k returns_ui_schema_v1 -v`
Expected: PASS
**Step 5: Commit**
```bash
git add backend/src/core/agent/infrastructure/crewai/tools/create_calendar_event_tool.py backend/src/core/agent/domain/message_metadata.py backend/tests/unit/core/agent/test_run_resume_service.py
git commit -m "feat(agent): return tool results as ui schema v1"
```
### Task 6: 工具结果对象存储(#2)
**Files:**
- Modify: `backend/src/core/agent/application/session_state_persistence.py`
- Modify: `backend/src/core/agent/domain/message_metadata.py`
- Test: `backend/tests/integration/core/agent/test_session_message_persistence.py`
**Step 1: Write the failing test**
```python
def test_large_tool_payload_persisted_to_object_storage():
meta = persist_large_tool_result(...)
assert meta.storage_bucket is not None
assert meta.storage_path is not None
```
**Step 2: Run test to verify it fails**
Run: `cd backend && uv run pytest tests/integration/core/agent/test_session_message_persistence.py -k object_storage -v`
Expected: FAIL,当前 metadata 为空。
**Step 3: Write minimal implementation**
```python
payload_ref = await persist_tool_result_payload(...)
metadata.storage_bucket = payload_ref.bucket
metadata.storage_path = payload_ref.path
metadata.payload_sha256 = payload_ref.sha256
```
**Step 4: Run test to verify it passes**
Run: `cd backend && uv run pytest tests/integration/core/agent/test_session_message_persistence.py -k object_storage -v`
Expected: PASS
**Step 5: Commit**
```bash
git add backend/src/core/agent/application/session_state_persistence.py backend/src/core/agent/domain/message_metadata.py backend/tests/integration/core/agent/test_session_message_persistence.py
git commit -m "feat(agent): persist large tool results to object storage"
```
### Task 7: 三阶段参数解耦(#4
**Files:**
- Modify: `backend/src/core/agent/infrastructure/crewai/runtime.py`
- Modify: `backend/src/core/agent/infrastructure/config/resolver.py`
- Test: `backend/tests/unit/core/agent/test_config_resolver.py`
- Test: `backend/tests/unit/core/agent/test_crewai_runtime.py`
**Step 1: Write the failing test**
```python
def test_intent_stage_can_disable_tools():
cfg = load_stage_config(intent_tools=[])
result = run_intent_stage(cfg)
assert result.tool_calls == []
```
**Step 2: Run test to verify it fails**
Run: `cd backend && uv run pytest tests/unit/core/agent/test_crewai_runtime.py -k intent_stage_can_disable_tools -v`
Expected: FAIL,当前三阶段共享同一 llm/tools 配置。
**Step 3: Write minimal implementation**
```python
stage_cfg = config.for_stage(stage)
run_stage(..., llm_config=stage_cfg.llm, tools=stage_cfg.tools)
```
**Step 4: Run test to verify it passes**
Run: `cd backend && uv run pytest tests/unit/core/agent/test_crewai_runtime.py -k intent_stage_can_disable_tools -v`
Expected: PASS
**Step 5: Commit**
```bash
git add backend/src/core/agent/infrastructure/crewai/runtime.py backend/src/core/agent/infrastructure/config/resolver.py backend/tests/unit/core/agent/test_config_resolver.py backend/tests/unit/core/agent/test_crewai_runtime.py
git commit -m "refactor(agent): decouple llm and tool strategy by stage"
```
### Task 8: 多模态图片输入(文件上传)支持(#7)
**Files:**
- Modify: `backend/src/core/agent/domain/agui_input.py`
- Modify: `backend/src/core/agent/infrastructure/crewai/runtime.py`
- Modify: `backend/src/core/agent/infrastructure/litellm/client.py`
- Test: `backend/tests/unit/core/agent/test_litellm_client.py`
**Step 1: Write the failing test**
```python
def test_image_content_block_is_preserved_for_llm():
payload = build_multimodal_payload(text="分析图片", image_file="a.png")
assert payload_contains_image_block(payload)
```
**Step 2: Run test to verify it fails**
Run: `cd backend && uv run pytest tests/unit/core/agent/test_litellm_client.py -k image_content_block_is_preserved -v`
Expected: FAIL,当前非 text 被丢弃。
**Step 3: Write minimal implementation**
```python
if item.type == "image":
blocks.append({"type": "image_url", "image_url": {"url": signed_file_url}})
```
**Step 4: Run test to verify it passes**
Run: `cd backend && uv run pytest tests/unit/core/agent/test_litellm_client.py -k image_content_block_is_preserved -v`
Expected: PASS
**Step 5: Commit**
```bash
git add backend/src/core/agent/domain/agui_input.py backend/src/core/agent/infrastructure/crewai/runtime.py backend/src/core/agent/infrastructure/litellm/client.py backend/tests/unit/core/agent/test_litellm_client.py
git commit -m "feat(agent): support multimodal image input blocks"
```
### Task 9: 新增 ASR 同步转写 API#8
**Files:**
- Create: `backend/src/v1/agent/asr_router.py`
- Modify: `backend/src/v1/agent/router.py`
- Create: `backend/src/v1/agent/asr_service.py`
- Create: `backend/src/v1/agent/asr_schemas.py`
- Test: `backend/tests/integration/v1/agent/test_routes.py`
**Step 1: Write the failing test**
```python
def test_asr_transcribe_returns_sync_transcript(client, wav_file):
resp = client.post("/v1/agent/asr/transcribe", files={"audio": wav_file})
assert resp.status_code == 200
assert resp.json()["transcript"]
```
**Step 2: Run test to verify it fails**
Run: `cd backend && uv run pytest tests/integration/v1/agent/test_routes.py -k asr_transcribe_returns_sync_transcript -v`
Expected: FAIL,当前无路由。
**Step 3: Write minimal implementation**
```python
@router.post("/asr/transcribe")
async def transcribe(audio: UploadFile) -> AsrTranscribeResponse:
text = await asr_service.transcribe(audio)
return AsrTranscribeResponse(transcript=text)
```
**Step 4: Run test to verify it passes**
Run: `cd backend && uv run pytest tests/integration/v1/agent/test_routes.py -k asr_transcribe_returns_sync_transcript -v`
Expected: PASS
**Step 5: Commit**
```bash
git add backend/src/v1/agent/asr_router.py backend/src/v1/agent/router.py backend/src/v1/agent/asr_service.py backend/src/v1/agent/asr_schemas.py backend/tests/integration/v1/agent/test_routes.py
git commit -m "feat(agent): add synchronous asr transcription endpoint"
```
### Task 10: 全量验证与文档对齐
**Files:**
- Modify: `docs/runtime/runtime-route.md`
- Modify: `docs/bugs/2026-03-08-agent-tool-architecture.md` (状态回填)
**Step 1: Run targeted unit suite**
Run: `cd backend && uv run pytest tests/unit/core/agent -v`
Expected: PASS
**Step 2: Run targeted integration suite**
Run: `cd backend && uv run pytest tests/integration/core/agent tests/integration/v1/agent -v`
Expected: PASS
**Step 3: Run e2e smoke for agent flow**
Run: `cd backend && uv run pytest tests/e2e -k "agent or mobile_health" -v`
Expected: PASS 或明确记录跳过原因
**Step 4: Run quality gates**
Run: `cd backend && uv run ruff check src tests && uv run basedpyright`
Expected: PASS
**Step 5: Final commit**
```bash
git add docs/runtime/runtime-route.md docs/bugs/2026-03-08-agent-tool-architecture.md
git commit -m "docs(agent): align runtime docs with new tool architecture"
```
---
## Verification Evidence Requirements
实施完成时必须输出:
1. 双金路径验证结果(无工具 + 工具审批后续跑)
2. 三阶段 StepStarted/StepFinished 事件日志片段
3. 安全验证结果(前端 history 篡改无效)
4. ASR 同步转写接口请求/响应样例
5. 关键命令输出摘要(pytest/ruff/basedpyright
---
## Notes
- 本计划不包含兼容逻辑保留。
- 本计划采用一次性切换。
- 若实施中出现 S2 -> S3 范围升级,先暂停并更新计划,再继续执行。