# 核心概念
Recursive 有五个正交的核心概念,每个概念都可以独立替换,互不影响。
## 1. Message(消息)
**位置**:`src/message.rs`
唯一的数据原语。`Message` 是一条聊天消息,可选包含工具调用列表。所有状态都通过 `Vec<Message>`(对话记录)传递。
```rust
pub struct Message {
pub role: Role, // System | User | Assistant | Tool
pub content: String,
pub tool_calls: Vec<ToolCall>,
pub tool_call_id: Option<String>,
}
```
## 2. LlmProvider(LLM 提供商)
**位置**:`src/llm/`
模型后端的 trait。实现一次,到处使用。
```rust
#[async_trait]
pub trait LlmProvider: Send + Sync {
async fn complete(
&self,
messages: &[Message],
tools: Option<&[ToolDef]>,
) -> Result<Message>;
}
```
内置实现:
- `OpenAiProvider` — OpenAI 兼容 HTTP(支持 OpenAI、DeepSeek、Ollama 等)
- `AnthropicProvider` — Anthropic 原生 API
- `MockProvider` — 用于测试的脚本化响应
## 3. Tool + ToolRegistry(工具 + 工具注册表)
**位置**:`src/tools/`
`Tool` 是模型可以调用的任何操作(读取文件、运行 shell 命令、获取 URL 等)。
```rust
#[async_trait]
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn parameters(&self) -> serde_json::Value; // JSON Schema
async fn call(&self, args: serde_json::Value) -> ToolResult;
}
```
`ToolRegistry` 将工具名称映射到实现,负责分发调用。
**内置工具**:
| `read_file` | 读取文件内容(沙箱限制在工作区内) |
| `write_file` | 写入或创建文件 |
| `apply_patch` | 应用 V4A 格式的补丁 |
| `list_dir` | 列出目录内容 |
| `run_shell` | 执行 shell 命令(带超时) |
| `search_files` | 跨文件正则搜索 |
| `web_fetch` | HTTP GET 并提取 HTML 文本(可选) |
| `remember` / `recall` | 持久化键值内存 |
## 4. AgentRuntime(Agent 运行时)
**位置**:`src/runtime.rs`
核心循环。接收目标字符串,交替调用模型和工具,直到满足终止条件:
```
目标 → [LLM] → 工具调用? → [工具] → [LLM] → 工具调用? → ... → 最终答案
```
**终止条件**(`FinishReason`):
| `NoMoreToolCalls` | 模型停止调用工具(正常完成) |
| `ProviderStop(s)` | LLM 返回停止信号 |
| `BudgetExceeded` | 达到 `max_steps` 限制 |
| `Stuck { .. }` | 同一工具调用循环重复 |
| `TranscriptLimit { .. }` | 对话记录过大 |
| `PlanPending` | Agent 等待计划审批 |
| `Cancelled` | 外部取消 |
| `PermissionDenialLimit` | 权限拒绝次数过多 |
运行期间的错误通过 `runtime.run()` 的 `Err(...)` 返回。
循环**故意不可扩展**——新能力应该以工具或 Provider 的形式添加,而不是在循环内部分支。
## 5. AgentEvent(步骤事件)
**位置**:`src/event.rs`
每一步执行后发出的观察者通道。通过 `EventSink` 订阅可以驱动 UI、日志、回放或测试,无需修改循环。
```rust
#[non_exhaustive]
pub enum AgentEvent {
AssistantText { text: String, step: usize },
ToolCall { name: String, id: String, arguments: String, step: usize },
ToolResult { id: String, name: String, output: String, step: usize },
Usage { input_tokens: u32, output_tokens: u32, step: usize },
Compacted { removed: usize, kept: usize, summary_chars: usize, step: usize },
TurnFinished { reason: String, steps: usize },
// ...
}
```
**正交性示意**:
```
┌─────────────────────────────────────────┐
│ AgentRuntime │
│ ┌─────────┐ ┌───────────────┐ │
│ │ LLM │◄────────│ ToolRegistry │ │
│ │Provider │ │ (Tool × N) │ │
│ └────┬────┘ └───────────────┘ │
│ │ AgentEvent 流 │
└───────┼─────────────────────────────────┘
▼
┌────────────┐ ┌──────────┐ ┌──────────┐
│ TUI │ │ HTTP API │ │ 日志 │
└────────────┘ └──────────┘ └──────────┘
```