Files
scaffold/template/.claude/rules/agent.md.jinja
T
baozaotumao df48c36422 refactor: 规则中性化——剥离 PPT 业务,统一为订单示范域
把规则从原 ppt-gen 项目泛化为通用脚手架,覆盖 19 个文件:
- ddd/architecture/design-discipline/error-codes/communication-* 等:
  领域术语、错误码、消息示例、扩展点、状态流统一为「订单」示范域
- agent-dag/agent.md/agent-concurrency/agent-llm-*:DAG 节点、配置、并发、
  LLM 适配从「对话→预览→生成→截图→合成」改为「intake→process→validate→finalize」
- backend-db:Session 实体/状态机/ORM/仓储/checkpoint 对齐新流水线
- 移除幻灯片专属依赖与产物:agent 去 playwright/python-pptx,
  Dockerfile 去 Chromium 系统库,.gitignore 去 *.pptx
- 保留合法通用项:ANSI(PTY 处理)、Playwright(E2E 测试工具)、Gemini CLI(真实 provider)

验证:gemini-cli/openai-api 两变体生成退出 0,生成项目零 PPT 残留,
backend/agent 骨架均可 import 运行。
2026-06-01 23:15:05 -07:00

184 lines
7.2 KiB
Django/Jinja
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.
---
paths:
- "agent/**"
---
# Agent 服务总览
Agent 服务的职责、技术栈、目录结构、配置{% if llm_provider == 'gemini-cli' %}、Skill 安装{% endif %}与测试规范。
> 以下以一条「会话→LLM 处理→结果」的通用流水线作示范;按你的业务替换节点与扩展点,架构不变。
## 概览(职责)
FastAPI 服务(port {{ agent_port }})。核心职责:{% if llm_provider == 'gemini-cli' %}PTY 桥接 LLM CLI 交互式会话{% else %}通过 SDK 调用 LLM API{% endif %} + DAG 编排(intake → process → validate → finalize+ 事件流式推送给 Backend。
## 技术栈
| 库 | 用途 |
|----|------|
| FastAPI + uvicorn | WebSocket 服务端 |
{% if llm_provider == 'gemini-cli' -%}
| pty(标准库) | 伪终端,让 LLM CLI 认为自己在真实终端 |
| asyncio | 异步 I/O,协调 PTY 读写与 WebSocket |
{% elif llm_provider == 'openai-api' -%}
| openai | OpenAI SDK,流式生成(`AsyncOpenAI` |
| asyncio | 异步 I/O,协调 SDK 调用与 WebSocket |
{% elif llm_provider == 'anthropic-api' -%}
| anthropic | Anthropic SDK,流式生成(`AsyncAnthropic` |
| asyncio | 异步 I/O,协调 SDK 调用与 WebSocket |
{% else -%}
| asyncio | 异步 I/O,协调 LLM 调用与 WebSocket |
{% endif -%}
| loguru | 结构化日志 |
| pytest + pytest-asyncio | 测试 |
> 业务相关的外部能力(导出、第三方 API 等)按需经扩展点接入,对应依赖用 `uv add` 自行添加。
## 包管理(uv
```bash
# 添加依赖
{% if llm_provider == 'gemini-cli' -%}
uv add fastapi uvicorn loguru pydantic-settings pytest pytest-asyncio
{% elif llm_provider == 'openai-api' -%}
uv add fastapi uvicorn openai loguru pydantic-settings pytest pytest-asyncio
{% elif llm_provider == 'anthropic-api' -%}
uv add fastapi uvicorn anthropic loguru pydantic-settings pytest pytest-asyncio
{% else -%}
uv add fastapi uvicorn loguru pydantic-settings pytest pytest-asyncio
{% endif %}
# 运行服务
uv run python -m src.main
# 运行测试
uv run pytest
```
关键文件:`pyproject.toml`(依赖声明)、`uv.lock`(锁文件,必须提交 git
## 数据库
**Agent 不操作数据库,不引入 SQLAlchemy / Alembic。**
Agent 是无状态处理服务。DB 由 Backend 单一持有。checkpoint 信息由 Backend 在 WebSocket 建连时作为参数传入,Agent 只读取,不写入任何持久化存储。
## 目录结构(clean-arch
```
agent/
├── src/
│ ├── main.py # 接口适配器:FastAPI appWebSocket 端点
│ │
│ ├── domain/ # 领域层:纯逻辑零 IO
│ │ ├── models.py
│ │ ├── state.py # 聚合根状态
│ │ ├── ports.py # 端口定义(LLMSession、Exporter 等)
│ │ └── exceptions.py
│ │
│ ├── dag/ # 应用层:用例编排
│ │ ├── nodes.py
│ │ └── runner.py
│ │
│ ├── llm/ # 扩展点①:LLMSession 适配器
{% if llm_provider == 'gemini-cli' -%}
│ │ ├── pty_bridge.py # 共享 PTY 机制
│ │ └── gemini_cli.py # GeminiCliSession 实现
{% elif llm_provider == 'openai-api' -%}
│ │ └── openai_api.py # OpenAISession 实现
{% elif llm_provider == 'anthropic-api' -%}
│ │ └── anthropic_api.py # AnthropicSession 实现
{% else -%}
│ │ └── custom_llm.py # 自定义 LLMSession 实现
{% endif -%}
│ │ └── registry.py
│ ├── providers/ # 扩展点②:外部能力 Provider 适配器(示范)
│ │ └── registry.py
│ ├── export/ # 扩展点③:Exporter 适配器(如需导出)
│ │ └── registry.py
│ ├── storage/ # 扩展点④:StorageBackend 适配器
│ │ └── local_storage.py
{% if llm_provider == 'gemini-cli' -%}
│ ├── skills/
│ │ └── installer.py # CLI skill 自动安装(可选)
{% endif -%}
│ ├── utils/
{% if llm_provider == 'gemini-cli' -%}
│ │ ├── ansi.py # ANSI 转义码过滤
{% endif -%}
│ │ ├── concurrency.py
│ │ ├── files.py
│ │ └── ids.py
│ ├── middleware/
│ └── core/
│ ├── config.py
│ └── logging.py
└── tests/
├── conftest.py
{% if llm_provider == 'gemini-cli' -%}
├── test_pty_bridge.py
├── test_gemini_cli.py
├── test_installer.py
{% else -%}
├── test_llm_session.py
{% endif -%}
├── test_nodes.py
└── test_runner.py
```
## 配置(core/config.py
```python
class Settings(BaseSettings):
env: Literal["dev", "prod"] = "dev"
work_dir: Path = Path.home() / "{{ project_slug }}"
port: int = {{ agent_port }}
log_level: str = "DEBUG"
{% if llm_provider == 'gemini-cli' %}
llm_cmd: str = "gemini"
skill_name: str = "" # 可选:要加载的 CLI skill 名
skill_repo: str = "" # 从 .env 注入
pty_read_buffer_size: int = 4096
{% elif llm_provider == 'openai-api' %}
llm_api_key: str = "" # 从 .env 注入,不硬编码
llm_model: str = "gpt-4o"
llm_base_url: str = "" # 非空时覆盖 SDK 默认 endpoint
{% elif llm_provider == 'anthropic-api' %}
llm_api_key: str = "" # 从 .env 注入,不硬编码
llm_model: str = "claude-sonnet-4-6"
llm_base_url: str = ""
{% else %}
llm_api_key: str = ""
llm_model: str = ""
llm_base_url: str = ""
{% endif %}
process_timeout_seconds: int = 300
intake_timeout_seconds: int = 60
max_concurrent_sessions: int = 4
process_pool_workers: int = os.cpu_count() or 4
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
```
{% if llm_provider == 'gemini-cli' %}
## Skill 安装(skills/installer.py,可选)
若 LLM CLI 需要预装某个 skill,Agent 启动时自动确保配置指定的 skill 已安装。`skill_name` 与 `skill_repo` 均来自 `Settings`,换 skill 只改 `.env`,不改代码。
> 注意:安装目的地路径(`~/.gemini/skills/`)是 Gemini CLI 约定;换 CLI 时 `installer.py` 需同步修改。
{% endif %}
## 测试规范
{% if llm_provider == 'gemini-cli' -%}
- PTY 测试:mock `pty.openpty`、`os.read/write`,验证 ANSI 过滤和输出检测
- 超时测试:注入假 PTY 不输出,验证 `ExternalServiceTimeoutError` 被抛出
- skill 安装测试:mock `git clone`,验证成功/失败分支
{% else -%}
- LLM session 测试:mock `LLMSession.generate` 返回 `AsyncIterator[str]`,验证流式处理
- 超时测试:`generate` mock 为永不完成的迭代器,验证 `ExternalServiceTimeoutError` 被抛出
{% endif -%}
- DAG 节点测试:mock `send` 回调,验证状态转换和推送消息类型
- checkpoint 恢复测试:
- 注入 `checkpoint={status: "done"}` → 验证直接推送结果,不启动 LLM
- 注入 `checkpoint={status: "finalizing"}` → 验证跳过 LLM 直接进 finalize 节点
- 注入 `checkpoint={status: "intake"}` → 验证推送 `checkpoint_lost`