Design covers: agent loop, LLM provider abstraction, transport layer, context management, skill system, tool registry. P0 plan targets the minimum viable loop: user input → LLM streaming → tool call → output. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
344 lines
15 KiB
Markdown
344 lines
15 KiB
Markdown
# AI Coding Assistant 设计文档
|
||
|
||
> 日期:2026-05-12
|
||
> 状态:已批准(2026-05-12)
|
||
|
||
## 1. 背景与目标
|
||
|
||
构建一个可扩展的 AI 编程助手,具备以下核心能力:
|
||
|
||
- 统一多 LLM 提供者 API(OpenAI 兼容 + Anthropic 原生)
|
||
- 工具调用、状态管理、上下文压缩、技能系统
|
||
- 多种交互模式:TUI、RPC、打印模式、Web UI
|
||
- 差分渲染、Markdown、xterm.js 渲染引擎
|
||
- Web Components 页面,支持渲染 markdown、html、图片等
|
||
|
||
### 目标
|
||
|
||
1. **可扩展**:LLM 提供者、工具、技能均支持插件式扩展
|
||
2. **流式优先**:从 LLM 调用到终端渲染全链路流式
|
||
3. **双语言协作**:Python 后端(agent 逻辑)+ TypeScript 前端(渲染交互)
|
||
4. **多模式交互**:同一 agent 支持不同前端接入(TUI / Web / RPC)
|
||
|
||
### 非目标
|
||
|
||
- 不做模型训练 / 微调
|
||
- 不做多用户 / 多租户
|
||
- 不做云端部署(初期仅本地运行)
|
||
- 不做 VS Code / JetBrains 插件集成(后续扩展)
|
||
|
||
## 2. 技术栈
|
||
|
||
| 层 | 技术 | 说明 |
|
||
|----|------|------|
|
||
| Agent 核心 | Python 3.12+ | asyncio,type hints |
|
||
| LLM 调用 | httpx (async) | 统一 HTTP 客户端 |
|
||
| 状态持久化 | SQLite (aiosqlite) | 消息、会话、技能状态 |
|
||
| Transport - stdio | asyncio streams | TUI 直接 spawn |
|
||
| Transport - SSE/WS | aiohttp + aiohttp-sse | Web 前端接入 |
|
||
| TUI | TypeScript + Ink / Blessed | 通过 stdio JSON-RPC 通信 |
|
||
| Web UI | TypeScript + Web Components | 通过 SSE/WS 通信 |
|
||
| 渲染 | Markdown-it / xterm.js / 差分渲染 | Web Components 封装 |
|
||
|
||
## 3. 核心架构
|
||
|
||
### 3.1 Agent Loop
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ agentLoop() / agentLoopContinue() │
|
||
│ ├── 创建 EventStream │
|
||
│ └── 调用 runLoop() / runAgentLoopContinue() │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ runLoop() — 主循环 │
|
||
│ ┌─────────────────────────────────────────────────────┐ │
|
||
│ │ 外层 while(true) — 处理 follow-up 消息 │ │
|
||
│ │ ┌───────────────────────────────────────────────┐ │ │
|
||
│ │ │ 内层 while(hasMoreToolCalls || pendingMsg) │ │ │
|
||
│ │ │ 1. streamAssistantResponse() → LLM 调用 │ │ │
|
||
│ │ │ 2. executeToolCalls() → 工具执行 │ │ │
|
||
│ │ │ 3. prepareNextTurn() → 可切换模型/思考级别 │ │ │
|
||
│ │ │ 4. shouldStopAfterTurn() → 决定是否退出 │ │ │
|
||
│ │ └───────────────────────────────────────────────┘ │ │
|
||
│ └─────────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 3.2 消息流转
|
||
|
||
```
|
||
AgentMessage[] (内部格式)
|
||
↓ LLMProvider.convert_messages()
|
||
Message[] (LLM API 格式)
|
||
↓ LLM 调用
|
||
AssistantMessage (含 tool_calls)
|
||
↓ executeToolCalls()
|
||
ToolResultMessage[]
|
||
↓ 回到 runLoop() 继续
|
||
```
|
||
|
||
### 3.3 Transport 抽象
|
||
|
||
```python
|
||
class Transport(ABC):
|
||
@abstractmethod
|
||
async def start(self, handler: RequestHandler) -> None: ...
|
||
|
||
@abstractmethod
|
||
async def send_event(self, event: AgentEvent) -> None: ...
|
||
|
||
@abstractmethod
|
||
async def stop(self) -> None: ...
|
||
|
||
# RequestHandler = Callable[AgentRequest, Awaitable[None]]
|
||
# AgentEvent = 流式事件(token / tool_call / tool_result / done / error)
|
||
```
|
||
|
||
**stdio 实现**:stdin 读取 JSON-RPC 请求,stdout 写入 JSON-RPC 响应/通知。
|
||
**SSE/WS 实现**:aiohttp 启动 HTTP server,POST 接收请求,SSE 推送事件,WS 双向通信。
|
||
|
||
### 3.4 LLM Provider 抽象
|
||
|
||
```python
|
||
class LLMProvider(ABC):
|
||
@abstractmethod
|
||
async def stream_chat(
|
||
self,
|
||
messages: list[Message],
|
||
tools: list[ToolDef] | None = None,
|
||
**kwargs,
|
||
) -> AsyncIterator[StreamChunk]: ...
|
||
|
||
@abstractmethod
|
||
def convert_messages(self, agent_messages: list[AgentMessage]) -> list[Message]: ...
|
||
|
||
@abstractmethod
|
||
def convert_tools(self, tools: list[ToolDef]) -> list[dict]: ...
|
||
```
|
||
|
||
**OpenAI 兼容 adapter**:覆盖 OpenAI / Azure / 通义 / 智谱 / DeepSeek 等。
|
||
**Anthropic adapter**:原生 Messages API,保留 tool_use / extended_thinking 能力。
|
||
|
||
### 3.5 上下文管理
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────┐
|
||
│ LayeredContext │
|
||
│ ┌────────────────────────────────────────────┐ │
|
||
│ │ System Layer(系统提示词,不可压缩) │ │
|
||
│ ├────────────────────────────────────────────┤ │
|
||
│ │ Task Layer(当前任务摘要,低压缩优先级) │ │
|
||
│ ├────────────────────────────────────────────┤ │
|
||
│ │ Conversation Layer(对话消息,可压缩) │ │
|
||
│ │ └── SlidingWindow + Summarizer │ │
|
||
│ └────────────────────────────────────────────┘ │
|
||
└──────────────────────────────────────────────────┘
|
||
```
|
||
|
||
- **滑动窗口**:保留最近 N 轮完整消息
|
||
- **摘要压缩**:超出窗口的旧消息通过 LLM 生成摘要,替换原文
|
||
- **分层优先级**:System > Task > Conversation,压缩只作用于 Conversation 层
|
||
|
||
### 3.6 状态持久化(SQLite)
|
||
|
||
```sql
|
||
-- 会话表
|
||
CREATE TABLE sessions (
|
||
id TEXT PRIMARY KEY,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
model TEXT,
|
||
metadata JSON
|
||
);
|
||
|
||
-- 消息表
|
||
CREATE TABLE messages (
|
||
id TEXT PRIMARY KEY,
|
||
session_id TEXT REFERENCES sessions(id),
|
||
role TEXT NOT NULL, -- system / user / assistant / tool
|
||
content TEXT,
|
||
tool_calls JSON,
|
||
tool_call_id TEXT,
|
||
token_count INTEGER,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
-- 上下文摘要表
|
||
CREATE TABLE context_summaries (
|
||
id TEXT PRIMARY KEY,
|
||
session_id TEXT REFERENCES sessions(id),
|
||
layer TEXT NOT NULL, -- system / task / conversation
|
||
content TEXT NOT NULL,
|
||
message_range_start INTEGER,
|
||
message_range_end INTEGER,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
-- 技能状态表
|
||
CREATE TABLE skill_states (
|
||
session_id TEXT,
|
||
skill_name TEXT,
|
||
state JSON,
|
||
PRIMARY KEY (session_id, skill_name)
|
||
);
|
||
```
|
||
|
||
### 3.7 技能系统
|
||
|
||
```python
|
||
class Skill(ABC):
|
||
"""技能基类:提示词模板 + 工具绑定 + 触发规则"""
|
||
|
||
name: str
|
||
description: str
|
||
trigger: TriggerRule # 自动 / 关键词 / LLM 选择
|
||
system_prompt_fragment: str # 注入到系统提示词
|
||
tools: list[ToolDef] # 技能关联的工具
|
||
|
||
@abstractmethod
|
||
async def on_activate(self, ctx: SkillContext) -> None: ...
|
||
|
||
@abstractmethod
|
||
async def on_deactivate(self, ctx: SkillContext) -> None: ...
|
||
|
||
class SkillRegistry:
|
||
"""技能注册中心,支持动态加载"""
|
||
|
||
def __init__(self, skill_dirs: list[Path]):
|
||
self._skills: dict[str, Skill] = {}
|
||
self._skill_dirs = skill_dirs
|
||
|
||
async def load_all(self) -> None:
|
||
"""扫描目录,动态导入技能模块"""
|
||
|
||
async def load_skill(self, name: str) -> None:
|
||
"""热加载单个技能"""
|
||
|
||
def get_active_skills(self, context: AgentContext) -> list[Skill]:
|
||
"""根据触发规则返回当前应激活的技能"""
|
||
```
|
||
|
||
**动态加载**:从配置的目录扫描 `.py` 文件,每个文件导出 `Skill` 子类实例,运行时可热加载。
|
||
|
||
### 3.8 Hooks 配置点
|
||
|
||
| Hook | 时机 | 用途 |
|
||
|------|------|------|
|
||
| `get_steering_messages` | 每轮开始前 | 注入上下文消息 |
|
||
| `get_follow_up_messages` | Agent 停止前 | 检查后续消息 |
|
||
| `prepare_next_turn` | 轮次间 | 切换模型/思考级别 |
|
||
| `should_stop_after_turn` | 每轮结束 | 决定是否退出 |
|
||
| `before_tool_call` | 工具执行前 | 拦截/修改参数 |
|
||
| `after_tool_call` | 工具执行后 | 增强/修改结果 |
|
||
|
||
## 4. 模块边界与数据流
|
||
|
||
```
|
||
┌─────────┐ JSON-RPC/SSE ┌──────────────┐ StreamChunk ┌───────────┐
|
||
│ TUI │ ←──────────────→ │ Transport │ ←─────────────→ │ LLM │
|
||
│ (TS) │ │ (stdio/WS) │ │ Provider │
|
||
└─────────┘ └──────┬───────┘ └───────────┘
|
||
│
|
||
┌─────────┐ SSE/WS ┌──────┴───────┐
|
||
│ Web UI │ ←──────────────→│ Agent Loop │
|
||
│ (TS) │ │ (Python) │
|
||
└─────────┘ └──────┬───────┘
|
||
│
|
||
┌──────────────┼──────────────┐
|
||
│ │ │
|
||
┌─────┴─────┐ ┌────┴─────┐ ┌──────┴─────┐
|
||
│ Context │ │ Skills │ │ State │
|
||
│ Manager │ │ Registry │ │ (SQLite) │
|
||
└───────────┘ └────┬─────┘ └────────────┘
|
||
│
|
||
┌─────┴─────┐
|
||
│ Tools │
|
||
│ Registry │
|
||
└───────────┘
|
||
```
|
||
|
||
## 5. 错误处理
|
||
|
||
| 场景 | 策略 |
|
||
|------|------|
|
||
| LLM API 调用失败 | 指数退避重试(最多 3 次),返回错误事件给前端 |
|
||
| 工具执行超时 | 可配置超时(默认 60s),超时后返回 timeout 错误结果 |
|
||
| 工具执行异常 | 捕获异常,转为 ToolResultMessage(is_error=True) |
|
||
| Transport 断连 | stdio: 进程退出;SSE/WS: 自动重连 + 会话恢复 |
|
||
| 上下文压缩失败 | 回退到简单截断,保留最近 N 条消息 |
|
||
| 技能加载失败 | 跳过该技能,记录警告日志,不影响其他技能 |
|
||
|
||
## 6. 测试策略
|
||
|
||
| 层 | 测试类型 | 工具 |
|
||
|----|----------|------|
|
||
| Python 核心 | 单元测试 | pytest + pytest-asyncio |
|
||
| LLM Provider | 集成测试(mock HTTP) | pytest + respx |
|
||
| Transport | 单元测试 + 集成测试 | pytest,stdio 用 subprocess 测试 |
|
||
| 上下文管理 | 单元测试 | pytest,SQLite 用内存数据库 |
|
||
| 技能系统 | 单元测试 | pytest,动态加载用 importlib |
|
||
| 工具 | 单元测试 | pytest |
|
||
| TypeScript 前端 | 单元测试 | vitest |
|
||
| Web Components | 组件测试 | vitest + @open-wc/testing |
|
||
| 端到端 | 集成测试 | pytest + Playwright(Web)/ subprocess(TUI) |
|
||
|
||
## 7. 实现优先级(MVP)
|
||
|
||
1. **P0 — 核心 agent loop + OpenAI 兼容 provider + stdio transport**
|
||
- 跑通最小闭环:用户输入 → LLM 流式响应 → 工具调用 → 输出
|
||
|
||
2. **P1 — Anthropic provider + SSE/WS transport + SQLite 状态**
|
||
- 多 provider 支持 + Web 接入 + 会话持久化
|
||
|
||
3. **P2 — 上下文压缩 + 技能系统 + Web Components 基础渲染**
|
||
- 长对话支持 + 可扩展能力 + 前端渲染
|
||
|
||
4. **P3 — TUI + 差分渲染 + xterm.js + 高级渲染**
|
||
- 完整交互体验
|
||
|
||
## 8. 项目结构
|
||
|
||
```
|
||
ai-coding-assistant/
|
||
├── pyproject.toml
|
||
├── packages/
|
||
│ ├── core/ # Python: agent 核心
|
||
│ │ ├── agent/
|
||
│ │ │ ├── loop.py # agentLoop / runLoop
|
||
│ │ │ ├── messages.py # AgentMessage 消息模型
|
||
│ │ │ └── hooks.py # Hook 配置点
|
||
│ │ ├── llm/
|
||
│ │ │ ├── base.py # LLMProvider 抽象接口
|
||
│ │ │ ├── openai_compat.py
|
||
│ │ │ └── anthropic.py
|
||
│ │ ├── transport/
|
||
│ │ │ ├── base.py # Transport 抽象接口
|
||
│ │ │ ├── stdio.py
|
||
│ │ │ └── sse_ws.py
|
||
│ │ ├── context/
|
||
│ │ │ ├── manager.py # 分层上下文管理
|
||
│ │ │ ├── compressor.py # 滑动窗口 + 摘要压缩
|
||
│ │ │ └── state.py # SQLite 状态持久化
|
||
│ │ ├── skills/
|
||
│ │ │ ├── registry.py # SkillRegistry 动态加载
|
||
│ │ │ ├── base.py # Skill 基类
|
||
│ │ │ └── builtin/ # 内置技能
|
||
│ │ └── tools/
|
||
│ │ ├── base.py # Tool 基类
|
||
│ │ └── builtin/ # 内置工具
|
||
│ └── web/ # TypeScript: 前端
|
||
│ ├── package.json
|
||
│ ├── src/
|
||
│ │ ├── components/ # Web Components
|
||
│ │ ├── renderers/ # 差分渲染、Markdown、xterm.js
|
||
│ │ ├── tui/ # TUI 模式
|
||
│ │ └── rpc/ # RPC 客户端
|
||
│ └── ...
|
||
├── docs/
|
||
│ └── plans/
|
||
└── memory/
|
||
```
|