ai-coding-assistant/docs/plans/2026-05-12-ai-coding-assistant-design.md
root 98d113883e docs: add AI Coding Assistant design and P0 implementation plan
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>
2026-05-12 08:56:41 +08:00

344 lines
15 KiB
Markdown
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.

# AI Coding Assistant 设计文档
> 日期2026-05-12
> 状态已批准2026-05-12
## 1. 背景与目标
构建一个可扩展的 AI 编程助手,具备以下核心能力:
- 统一多 LLM 提供者 APIOpenAI 兼容 + 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+ | asynciotype 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 serverPOST 接收请求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 | 单元测试 + 集成测试 | pyteststdio 用 subprocess 测试 |
| 上下文管理 | 单元测试 | pytestSQLite 用内存数据库 |
| 技能系统 | 单元测试 | pytest动态加载用 importlib |
| 工具 | 单元测试 | pytest |
| TypeScript 前端 | 单元测试 | vitest |
| Web Components | 组件测试 | vitest + @open-wc/testing |
| 端到端 | 集成测试 | pytest + PlaywrightWeb/ subprocessTUI |
## 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/
```