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 运行。
This commit is contained in:
baozaotumao
2026-06-01 23:15:05 -07:00
parent bb6503c3f8
commit df48c36422
23 changed files with 257 additions and 285 deletions
+13 -14
View File
@@ -5,7 +5,7 @@ paths:
# Agent LLM 驱动层与 PTY 桥接
厂商无关的 LLM CLI 抽象、PTY 伪终端桥接机制、超时保护与 HTML 拦截检测。
厂商无关的 LLM CLI 抽象、PTY 伪终端桥接机制、超时保护与输出拦截检测。
## LLM CLI 驱动层(llm/
@@ -15,7 +15,7 @@ paths:
|------|------|
| `domain/ports.py` | `LLMSession` 协议(端口,接口定义) |
| `pty_bridge.py` | 共享 PTY 机制(所有 CLI 厂商复用) |
| `gemini_cli.py` | `GeminiCliSession`gemini 命令 + prompt 约定 + HTML 提取 |
| `gemini_cli.py` | `GeminiCliSession`gemini 命令 + prompt 约定 + 输出提取 |
**扩展新厂商**:新增 `claude_cli.py` 等实现 `LLMSession` 端口即可,PTY 机制无需重写。
@@ -74,25 +74,24 @@ os.write(master_fd, (user_text + '\n').encode('utf-8'))
### 超时保护
每个 DAG 阶段设置独立超时(`asyncio.wait_for`):
每个 DAG 阶段设置独立超时(`asyncio.wait_for`,阶段为示范
| 阶段 | 超时 |
|------|------|
| phase1_dialog(单轮回答 | 60s |
| phase2_preview3 张预览 | 180s |
| phase3_generate(完整 HTML | 300s |
| screenshot | 180s |
| intake(收集输入 | 60s |
| processLLM 处理输出 | 300s |
超时后抛 `GeminiTimeoutError`
超时后抛 `ExternalServiceTimeoutError`
## HTML 拦截检测
## 输出拦截检测
CLI 输出是流式文本,需按约定标记从中提取结构化结果。下面以提取代码块为例(按你的输出约定替换正则):
```python
def detect_html(text: str) -> str | None:
m = re.search(r'```html\s*([\s\S]+?)```|(<html[\s\S]+?</html>)', text, re.IGNORECASE)
return (m.group(1) or m.group(2)).strip() if m else None
def detect_output(text: str) -> str | None:
m = re.search(r'```result\s*([\s\S]+?)```', text, re.IGNORECASE)
return m.group(1).strip() if m else None
```
根据 `state.status` 决定处理:
- `"previewing"`保存 `preview_{n}.html`,推送 `style_preview` 事件
- `"generating"` → 保存 `slides.html`,推送 `html_ready`,触发后续节点
- `"processing"`提取到结果即保存产物,推送 `result_ready`,触发后续节点