Files
scaffold/template/.claude/rules/agent.md.jinja
T
baozaotumao 39fe248f9f feat: 重组为标准 skill 包结构 + 修复 verify.sh bash 3.2 兼容
- 新增 .claude/skills/new-project/SKILL.md(标准 skill 格式)
- install-skill.sh 安装时组装 SKILL.md + verify.sh,单一源无重复
- verify.sh 改用索引数组替代 declare -A,兼容 macOS bash 3.2
- log_pass/log_skip 显式 return 0,避免 set -e 下非 verbose 误退出
- README 改为 skill 优先;补全 copier.yml/scripts/template 入库
2026-06-01 21:19:45 -07:00

186 lines
7.1 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 %}与测试规范。
## 概览(职责)
FastAPI 服务(port {{ agent_port }})。核心职责:{% if llm_provider == 'gemini-cli' %}PTY 桥接 LLM CLI 交互式会话{% else %}通过 SDK 调用 LLM API{% endif %} + DAG 编排(对话 → 风格预览 → 生成 → 截图 → 合成)+ 事件流式推送给 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 -%}
| playwrightasync | 截取每页幻灯片截图 |
| python-pptx | 合成 PPTX |
| loguru | 结构化日志 |
| pytest + pytest-asyncio | 测试 |
## 包管理(uv
```bash
# 添加依赖
{% if llm_provider == 'gemini-cli' -%}
uv add fastapi uvicorn playwright python-pptx loguru pydantic-settings pytest pytest-asyncio
{% elif llm_provider == 'openai-api' -%}
uv add fastapi uvicorn openai playwright python-pptx loguru pydantic-settings pytest pytest-asyncio
{% elif llm_provider == 'anthropic-api' -%}
uv add fastapi uvicorn anthropic playwright python-pptx loguru pydantic-settings pytest pytest-asyncio
{% else -%}
uv add fastapi uvicorn playwright python-pptx loguru pydantic-settings pytest pytest-asyncio
{% endif %}
# 安装 Playwright 浏览器(首次)
uv run playwright install chromium
# 运行服务
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/ # 扩展点②:ImageProvider 适配器
│ │ └── registry.py
│ ├── export/ # 扩展点③:Exporter 适配器
│ │ └── pptx_exporter.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 = "frontend-slides"
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 %}
generate_timeout_seconds: int = 300
preview_timeout_seconds: int = 180
dialog_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
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 过滤和 HTML 检测
- 超时测试:注入假 PTY 不输出,验证 `LLMTimeoutError` 被抛出
- skill 安装测试:mock `git clone`,验证成功/失败分支
{% else -%}
- LLM session 测试:mock `LLMSession.generate` 返回 `AsyncIterator[str]`,验证流式处理
- 超时测试:`generate` mock 为永不完成的迭代器,验证 `LLMTimeoutError` 被抛出
{% endif -%}
- DAG 节点测试:mock `send` 回调,验证状态转换和推送消息类型
- checkpoint 恢复测试:
- 注入 `checkpoint={status: "done"}` → 验证直接推送结果,不启动 LLM
- 注入 `checkpoint={status: "screenshotting"}` → 验证跳过 LLM 直接进截图节点
- 注入 `checkpoint={status: "dialog"}` → 验证推送 `checkpoint_lost`