wip: 客服Agent + RAG + 画像收尾(基于 6516ccb)
This commit is contained in:
@@ -1,213 +0,0 @@
|
||||
# Agent Platform Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 将 v3.1 Agent 底座和 05 接口规范落成可恢复、可测试、与 HTTP/SSE 解耦的 MVP 实现。
|
||||
|
||||
**Architecture:** API 层只负责 JWT、请求映射和 View;`AgentRunApplicationService` 负责受理、幂等和 Outbox;Worker 通过 `AgentExecutor` 调用 `AgentFactory`/`BaseAgent.execute()`。MySQL 是运行状态、最终消息和审计权威源,Redis 只做缓存和通知,SSE 从运行查询投影输出结果级恢复流。
|
||||
|
||||
**Tech Stack:** Python 3.11+、FastAPI、Pydantic v2、SQLAlchemy 2、Alembic、MySQL 8、Redis、pytest、pytest-asyncio、httpx;模型、Milvus、Neo4j 通过 Service Protocol/Port 注入。
|
||||
|
||||
**Spec:** `docs/05-接口文档.md`、`docs/01-通用Agent平台开发设计.md`、`docs/02-数据库建表设计.md`、`docs/03-平台端到端流程文档.md`、`AGENTS.md`
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- 数据库允许新增表和字段,禁止重命名、删除或改变已有表及已有字段定义。
|
||||
- MVC+S 固定为 Controller -> Service -> Repository -> Model;Agent 属于 Service 层。
|
||||
- Agent 必须由 `AgentFactory` 创建并继承 `BaseAgent`,不得绕过公共鉴权、记忆、模型、工具、合规、审计和事件流程。
|
||||
- `agent_run` 是运行主键;`trace_id` 只用于追踪;不得复用 `trace_id` 代替 `run_id`。
|
||||
- 最终助手消息、审计、幂等完成状态、`agent_run=succeeded` 和领域 Outbox 必须在同一事务提交。
|
||||
- 记忆提取事件必须在 `complete_run()` 同一事务写入 Outbox,请求线程不得提交后直接调用记忆提取。
|
||||
- BaseAgent 只返回传输无关的 `RunProgressEvent`;SSE 由 View 适配,MVP 只实现结果级恢复。
|
||||
- 场内基金模拟交易是当前业务范围;场外运营独立建表、独立接口。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 项目骨架与公共契约
|
||||
|
||||
**Files:**
|
||||
- Create: `pyproject.toml`
|
||||
- Create: `app/core/contracts.py`
|
||||
- Create: `app/core/errors.py`
|
||||
- Create: `app/core/context.py`
|
||||
- Create: `tests/unit/core/test_contracts.py`
|
||||
- Create: `tests/unit/core/test_errors.py`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces `AgentRequest`, `AgentRequestMetadata`, `RequestContext`, `AgentDefinition`, `ResolvedAgentConfig`, `RunProgressEvent`, `AgentResult`, `DomainEvent` and `AgentError` classes matching 01 contract IDs.
|
||||
- Also produces immutable transport-neutral `RunAccepted`, `RunCancellation` and `RunView` projection dataclasses used by the application and View layers.
|
||||
|
||||
- [ ] **Step 1: Write failing validation tests** for forbidden metadata keys, blank messages, invalid idempotency keys, confidence outside `0..1`, and extra request fields.
|
||||
- [ ] **Step 2: Run** `python -m pytest tests/unit/core/test_contracts.py -q`; expect validation failures before implementation.
|
||||
- [ ] **Step 3: Implement** Pydantic/dataclass contracts with `ConfigDict(extra="forbid")`; keep HTTP DTOs separate from internal contracts.
|
||||
- [ ] **Step 4: Add** stable error codes and public messages; errors must not contain stack traces or provider responses.
|
||||
- [ ] **Step 5: Run** `python -m pytest tests/unit/core -q` and `python -m ruff check app tests`.
|
||||
|
||||
### Task 2: 数据库迁移与 Repository
|
||||
|
||||
**Files:**
|
||||
- Create: `alembic/versions/20260909_agent_platform_v31.py`
|
||||
- Create: `app/model/agent_run.py`
|
||||
- Create: `app/model/platform_entities.py`
|
||||
- Create: `app/repository/agent_run_repository.py`
|
||||
- Create: `app/repository/idempotency_repository.py`
|
||||
- Create: `app/repository/outbox_repository.py`
|
||||
- Create: `tests/integration/repository/test_agent_run_repository.py`
|
||||
|
||||
**Interfaces:**
|
||||
- `AgentRunRepository.create_queued(...) -> AgentRun`
|
||||
- `AgentRunRepository.claim(run_id, worker_id, lease_seconds) -> AgentRun | None`
|
||||
- `AgentRunRepository.request_cancel(run_id, user_id) -> AgentRun`
|
||||
- `IdempotencyRepository.claim(scope, request_hash) -> ClaimResult`
|
||||
- `OutboxRepository.append(event: DomainEvent) -> None`
|
||||
- `ClaimResult` is a typed result with `decision`, `record_id`, `existing_run_id` and `request_hash`; `AgentRun` is the SQLAlchemy model for `agent_run`.
|
||||
|
||||
- [ ] **Step 1: Write migration tests** asserting creation of `agent_run`, ten platform increment tables, foreign keys, status check, unique `run_id`, unique `idempotency_id`, and lease indexes.
|
||||
- [ ] **Step 2: Run** `alembic upgrade head` against an empty MySQL test schema and inspect with SQLAlchemy Inspector.
|
||||
- [ ] **Step 3: Implement** `agent_run` migration exactly as 02 §8.4; do not alter baseline tables.
|
||||
- [ ] **Step 4: Implement** repositories with conditional updates for leases, cancellation and terminal states.
|
||||
- [ ] **Step 5: Test** duplicate claims, expired lease takeover, concurrent completion and forbidden terminal-state transitions.
|
||||
- [ ] **Step 6: Run** `alembic downgrade -1` in a disposable schema and verify no baseline table or column changed.
|
||||
|
||||
### Task 3: JWT、统一信封和 API DTO
|
||||
|
||||
**Files:**
|
||||
- Create: `app/api/dependencies/auth.py`
|
||||
- Create: `app/api/schemas/envelopes.py`
|
||||
- Create: `app/api/schemas/agent_runs.py`
|
||||
- Create: `app/api/mappers/agent_run_mapper.py`
|
||||
- Create: `tests/api/test_auth.py`
|
||||
- Create: `tests/api/test_envelopes.py`
|
||||
|
||||
**Interfaces:**
|
||||
- `build_request_context(request) -> RequestContext`
|
||||
- `AgentRunCreateRequest`
|
||||
- `success_envelope(data, trace_id)`
|
||||
- `error_envelope(code, message, trace_id, field_errors)`
|
||||
|
||||
- [ ] **Step 1: Test** missing token, invalid signature, wrong issuer/audience, expiry and revoked `jti` return `401`.
|
||||
- [ ] **Step 2: Test** roles, customer assignment and data scope are reloaded server-side and cannot be supplied by request body or metadata.
|
||||
- [ ] **Step 3: Implement** JWT dependency and trace middleware; always return `X-Trace-ID`.
|
||||
- [ ] **Step 4: Implement** JSON DTO validation, cursor validation, decimal-string serialization and `BIGINT` string serialization.
|
||||
- [ ] **Step 5: Run** `python -m pytest tests/api -q`.
|
||||
|
||||
### Task 4: Agent 运行受理、幂等和 Outbox
|
||||
|
||||
**Files:**
|
||||
- Create: `app/service/agent_run_application_service.py`
|
||||
- Create: `app/service/run_dispatch_port.py`
|
||||
- Create: `app/api/controllers/agent_runs.py`
|
||||
- Create: `tests/service/test_agent_run_application_service.py`
|
||||
- Create: `tests/api/test_agent_runs_create.py`
|
||||
|
||||
**Interfaces:**
|
||||
- `AgentRunApplicationService.accept(payload, context) -> RunAccepted`
|
||||
- `AgentRunApplicationService.request_cancel(run_id, context, idempotency_key) -> RunCancellation`
|
||||
- `RunDispatchPort.enqueue(run_id) -> None`
|
||||
- `RunAccepted` and `RunCancellation` are the immutable result dataclasses from Task 1; controllers never return ORM objects.
|
||||
|
||||
- [ ] **Step 1: Write failing transaction tests** for user message + idempotency + `agent_run` + `agent.run_requested` atomicity.
|
||||
- [ ] **Step 2: Test** same key/same hash returns original run; same key/different hash returns `409 IDEMPOTENCY_CONFLICT`.
|
||||
- [ ] **Step 3: Implement** `accept()`; do not execute model or Agent in the HTTP request.
|
||||
- [ ] **Step 4: Implement** cancellation with `cancel_requested` conditional update and `agent.run_cancel_requested` Outbox.
|
||||
- [ ] **Step 5: Add** controller routes and verify response is `202` with `run_id`, `trace_id`, `status_url`, `events_url`.
|
||||
- [ ] **Step 6: Run** `python -m pytest tests/service/test_agent_run_application_service.py tests/api/test_agent_runs_create.py -q`.
|
||||
|
||||
### Task 5: AgentFactory、BaseAgent.execute 与 Worker
|
||||
|
||||
**Files:**
|
||||
- Create: `app/service/agent/base.py`
|
||||
- Create: `app/service/agent/factory.py`
|
||||
- Create: `app/service/agent/executor.py`
|
||||
- Create: `app/worker/agent_run_worker.py`
|
||||
- Create: `tests/contract/test_registered_agents.py`
|
||||
- Create: `tests/worker/test_agent_run_worker.py`
|
||||
|
||||
**Interfaces:**
|
||||
- `AgentFactory.create(agent_type, context) -> BaseAgent`
|
||||
- `BaseAgent.execute(request, context) -> AsyncIterator[RunProgressEvent]`
|
||||
- `AgentExecutor.execute_run(run_id) -> None`
|
||||
- `AgentRunWorker.claim_and_execute() -> None`
|
||||
|
||||
- [ ] **Step 1: Write contract tests** proving every registered Agent subclasses BaseAgent, declares valid definition, and cannot override `execute` or public governance methods.
|
||||
- [ ] **Step 2: Test** Worker lease claim, retry after expiry, same `run_id` reuse and cancellation checkpoint.
|
||||
- [ ] **Step 3: Implement** BaseAgent seven-step flow with injected Services; no HTTP/SSE imports.
|
||||
- [ ] **Step 4: Implement** Worker exception translation to `AgentError`; retry recoverable failures and terminalize non-recoverable failures.
|
||||
- [ ] **Step 5: Run** `python -m pytest tests/contract tests/worker -q`.
|
||||
|
||||
### Task 6: complete_run、事务 Outbox 和结果查询
|
||||
|
||||
**Files:**
|
||||
- Create: `app/service/agent_persistence_service.py`
|
||||
- Create: `app/service/run_query_service.py`
|
||||
- Create: `app/api/controllers/run_queries.py`
|
||||
- Create: `tests/service/test_agent_persistence_service.py`
|
||||
- Create: `tests/api/test_run_queries.py`
|
||||
|
||||
**Interfaces:**
|
||||
- `AgentPersistenceService.complete_run(..., memory_extraction_requested: bool) -> int`
|
||||
- `RunQueryService.get(run_id, context) -> RunView`
|
||||
- `RunView` is the persisted HTTP-neutral projection from Task 1; its `result` is populated only from database rows.
|
||||
|
||||
- [ ] **Step 1: Write a transaction test** that fails if assistant message, audit, idempotency completion, `agent_run=succeeded`, or Outbox is missing after rollback.
|
||||
- [ ] **Step 2: Test** `memory.extraction_requested` is present in the same transaction when requested and absent otherwise.
|
||||
- [ ] **Step 3: Implement** domain event construction with `event_id`, `aggregate_type`, `occurred_at`, and `trace_id`.
|
||||
- [ ] **Step 4: Implement** result projection from persisted message/run rows; never read Worker memory for GET responses.
|
||||
- [ ] **Step 5: Run** `python -m pytest tests/service/test_agent_persistence_service.py tests/api/test_run_queries.py -q`.
|
||||
|
||||
### Task 7: 结果级恢复 SSE 与会话接口
|
||||
|
||||
**Files:**
|
||||
- Create: `app/view/sse_view.py`
|
||||
- Create: `app/api/controllers/run_events.py`
|
||||
- Create: `app/api/controllers/conversations.py`
|
||||
- Create: `app/api/schemas/conversations.py`
|
||||
- Create: `tests/api/test_run_events.py`
|
||||
- Create: `tests/api/test_conversations.py`
|
||||
|
||||
**Interfaces:**
|
||||
- `SseView.stream(run_id, context) -> StreamingResponse`
|
||||
- `GET /api/v1/agent-runs/{run_id}/events`
|
||||
- `POST/GET /api/v1/conversations...` routes listed in 05 §7
|
||||
|
||||
- [ ] **Step 1: Test** SSE headers, event order, 15-second comment heartbeat, terminal `done/error`, and no `tools/delta/done` before persistence success.
|
||||
- [ ] **Step 2: Test** reconnect after disconnect rebuilds complete result from `agent_run` and `conversation_message`; do not implement event replay.
|
||||
- [ ] **Step 3: Implement** View adapter from `RunProgressEvent` to 01 §12 events; BaseAgent remains HTTP-free.
|
||||
- [ ] **Step 4: Implement** session ownership, message cursor pagination, closure, feedback and public handover request mapping.
|
||||
- [ ] **Step 5: Run** `python -m pytest tests/api/test_run_events.py tests/api/test_conversations.py -q`.
|
||||
|
||||
### Task 8: 平台管理面与配置发布
|
||||
|
||||
**Files:**
|
||||
- Create: `app/api/controllers/admin_config.py`
|
||||
- Create: `app/api/controllers/admin_models.py`
|
||||
- Create: `app/api/controllers/admin_prompts.py`
|
||||
- Create: `app/api/controllers/admin_intents.py`
|
||||
- Create: `app/api/controllers/admin_audit.py`
|
||||
- Create: `app/service/config_release_service.py`
|
||||
- Create: `tests/api/test_admin_config.py`
|
||||
|
||||
**Interfaces:**
|
||||
- Routes and permissions exactly match 05 §9 and §19 entries A001-A033.
|
||||
- `ConfigReleaseService.validate/review/activate/rollback` are separate methods with maker-checker enforcement.
|
||||
|
||||
- [ ] **Step 1: Test** creator cannot review own release, invalid release cannot activate, and `If-Match` conflict returns `409`.
|
||||
- [ ] **Step 2: Test** active template/intent uniqueness and fallback endpoint foreign-key/order validation.
|
||||
- [ ] **Step 3: Implement** draft/review/activate/rollback service transactions and audit records.
|
||||
- [ ] **Step 4: Implement** route DTOs without exposing secrets, internal URLs or old `fallback_endpoint_ids`.
|
||||
- [ ] **Step 5: Run** `python -m pytest tests/api/test_admin_config.py -q`.
|
||||
|
||||
### Task 9: OpenAPI、契约测试和故障演练
|
||||
|
||||
**Files:**
|
||||
- Create: `openapi/agent-platform-v1.yaml`
|
||||
- Create: `tests/contract/test_openapi_contract.py`
|
||||
- Create: `tests/security/test_data_scope.py`
|
||||
- Create: `tests/recovery/test_agent_run_recovery.py`
|
||||
- Modify: `docs/05-接口文档.md` only when the HTTP contract intentionally changes
|
||||
|
||||
- [ ] **Step 1: Generate OpenAPI from the same DTO/router source and compare paths, methods, required headers and response envelopes with 05 §19.**
|
||||
- [ ] **Step 2: Test** cross-customer access returns `404`, JWT failures return `401`, and Agent cannot execute write tools.
|
||||
- [ ] **Step 3: Test** Worker crash, lease takeover, Redis loss, Outbox retry/dead-letter and client disconnect.
|
||||
- [ ] **Step 4: Run** `python -m pytest -q`, `python -m ruff check app tests`, and `python -m mypy app`.
|
||||
- [ ] **Step 5: Run** empty-schema migration and upgrade-schema migration in disposable MySQL databases; verify 49-table count and immutable baseline definitions.
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user