docs: 将方案文档从 specs 目录迁移至 docs 目录
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
# 方案:重构 `types.rs` 为完整的 OpenAI 兼容 API 类型系统
|
||||
|
||||
## 1. 现状分析
|
||||
|
||||
### 当前问题
|
||||
|
||||
| 问题 | 详细 |
|
||||
|------|------|
|
||||
| **无 serde** | 所有类型只有 `Debug + Clone`,无 `Serialize/Deserialize`,迫使 `OpenaiProvider` 手动构建 JSON(354 行中约 200 行是序列化代码) |
|
||||
| **请求参数不全** | `ChatRequest` 只支持 `model, messages, system_prompt, tools, max_tokens, temperature, extra_body`,缺失 streaming、response_format、tool_choice、stop、reasoning_effort 等 30+ 参数 |
|
||||
| **响应类型太薄** | `ChatResponse` 只返回 `message + usage + stop_reason`,缺失 `id, created, model, choices` 数组、`logprobs`、`system_fingerprint` 等 |
|
||||
| **无流式支持** | 无 `ChatCompletionChunk` 类型,无法处理 SSE 流式响应 |
|
||||
| **反向依赖** | `types.rs` 引用 `cycle::usage::Usage`,造成模块间反向依赖 |
|
||||
| **手动解析易出错** | `parse_response()` 从 `Value` 中逐字段解析,逻辑脆弱,不支持复杂嵌套类型 |
|
||||
|
||||
### OpenAI API 参考文档覆盖范围
|
||||
|
||||
已完整阅读文档(2177 行),涵盖了完整的请求参数(35+ 个顶层参数)和响应结构。
|
||||
|
||||
## 2. 新类型系统设计
|
||||
|
||||
### 架构
|
||||
|
||||
将 `types.rs` 重构为 Rust 新风格模块目录(符合项目已有惯例),按功能领域拆分:
|
||||
|
||||
```
|
||||
src/llm/
|
||||
├── types/
|
||||
│ ├── mod.rs # 模块根:re-exports + 基础枚举/共用类型
|
||||
│ ├── request.rs # 请求参数(ChatCompletionRequest 等)
|
||||
│ ├── response.rs # 响应类型(ChatCompletionResponse + ChatCompletionChunk)
|
||||
│ ├── message.rs # 消息类型(6 种角色消息 + content parts)
|
||||
│ ├── tool.rs # 工具定义 + 工具调用
|
||||
│ ├── usage.rs # Token 用量(从 cycle/usage.rs 移入,消除反向依赖)
|
||||
│ └── shared.rs # 共用枚举(ReasoningEffort, ServiceTier, ResponseFormat 等)
|
||||
```
|
||||
|
||||
同时,将 `cycle/usage.rs` 中的 `Usage` 和 `CostTracker` **移到** `types/usage.rs`,`cycle/usage.rs` 保留 `pub use` 兼容 re-export。
|
||||
|
||||
### 核心决策
|
||||
|
||||
| 决策 | 选择 | 理由 |
|
||||
|------|------|------|
|
||||
| **序列化方式** | 全部类型 derive `Serialize, Deserialize` | 消除手动 JSON 构建,让 provider 直接 `.json(&req)` / `.json::<Res>()` |
|
||||
| **类型风格** | 直接映射 OpenAI API JSON 形状 | 一目了然,与 API 文档 1:1 对应,调试方便 |
|
||||
| **命名策略** | 添加 `OpenAI` 前缀(如 `OpenaiChatRequest`) | 明确标注为 OpenAI 兼容类型 |
|
||||
| **字段命名** | `#[serde(rename_all = "snake_case")]` | OpenAI API 使用 snake_case |
|
||||
| **可选字段** | `#[serde(skip_serializing_if = "Option::is_none")]` | 不序列化 None 字段,保持请求体干净 |
|
||||
| **默认值** | `#[serde(default)]` | 反序列化时缺失字段用默认值 |
|
||||
| **后向兼容** | 通过类型别名保持 `ChatRequest`/`ChatResponse` 等名称可用 | LlmProvider/LlmCycle 接口不变 |
|
||||
| **泛化策略** | Anthropic 是独立体系,暂不纳入当前设计 | 保持当前类型系统专注 OpenAI,Provider 层做转换 |
|
||||
|
||||
### 关键类型设计原则
|
||||
|
||||
- **`OpenaiChatRequest`**:统一结构体(不拆分 NonStreaming/Streaming),包含 `stream: Option<bool>` 字段,所有字段均为 `Option`,build 时 `skip_serializing_if`
|
||||
- **`OpenaiChatResponse`**:直接对应 `ChatCompletion`(完整响应),保留完整 choices 数组等所有字段
|
||||
- **`OpenaiChatChunk`**:对应流式 chunk,`object = "chat.completion.chunk"`
|
||||
- **消息系统**:用单个 `OpenaiChatMessage` enum 覆盖 6 种角色消息类型(Developer/System/User/Assistant/Tool/Function),每种内部使用对应 struct
|
||||
- **Content parts**:`OpenaiContentPart` enum 覆盖 text/image_url/input_audio/file/refusal
|
||||
|
||||
## 3. 完整类型清单
|
||||
|
||||
### `types/mod.rs` — 共用类型
|
||||
```
|
||||
Role → enum { Developer, System, User, Assistant, Tool, Function }
|
||||
FinishReason → enum { Stop, Length, ToolCalls, ContentFilter, FunctionCall }
|
||||
ServiceTier → enum { Auto, Default, Flex, Scale, Priority }
|
||||
Modality → enum { Text, Audio }
|
||||
ImageDetail → enum { Auto, Low, High }
|
||||
AudioFormat → enum { Wav, Mp3, Aac, Flac, Opus, Pcm16 }
|
||||
Voice → struct { id: String } 或预定义枚举
|
||||
SearchContextSize → enum { Low, Medium, High }
|
||||
StopSequence → enum { Single(String), Multiple(Vec<String>) }
|
||||
Verbosity → enum { Low, Medium, High }
|
||||
```
|
||||
|
||||
### `types/request.rs` — 请求参数
|
||||
```
|
||||
OpenaiChatRequest → struct (35+ 字段,所有 OpenAI 参数)
|
||||
ResponseFormat → enum { Text, JsonObject { .. }, JsonSchema { .. } }
|
||||
ToolChoice → enum { None, Auto, Required, Named { .. }, AllowedTools { .. } }
|
||||
StreamOptions → struct { include_usage, include_obfuscation }
|
||||
AudioParam → struct { format, voice }
|
||||
PredictionContent → struct { type, content }
|
||||
WebSearchOptions → struct { search_context_size, user_location }
|
||||
UserLocation → struct { type, approximate: Approximate }
|
||||
Approximate → struct { city, country, region, timezone }
|
||||
FunctionCallOption → struct { name } // deprecated
|
||||
FunctionDefinition → struct { name, description, parameters, strict }
|
||||
OpenaiTool → enum { Function { .. }, Custom { .. } }
|
||||
```
|
||||
|
||||
### `types/response.rs` — 响应类型
|
||||
```
|
||||
OpenaiChatResponse → struct { id, object, created, model, choices, usage, system_fingerprint, service_tier }
|
||||
Choice → struct { index, message, finish_reason, logprobs }
|
||||
OpenaiChatMessage → struct { content, refusal, role, tool_calls, function_call, audio, annotations }
|
||||
OpenaiChatChunk → struct { id, object, created, model, choices, usage, system_fingerprint, service_tier }
|
||||
ChunkChoice → struct { index, delta, logprobs, finish_reason }
|
||||
Delta → struct { role, content, tool_calls, function_call }
|
||||
Logprobs → struct { content, refusal }
|
||||
TokenLogprob → struct { token, bytes, logprob, top_logprobs }
|
||||
TopLogprob → struct { token, bytes, logprob }
|
||||
Annotation → struct { type, url_citation }
|
||||
URLCitation → struct { end_index, start_index, title, url }
|
||||
OpenaiAudio → struct { id, data, expires_at, transcript }
|
||||
FunctionCall → struct { name, arguments }
|
||||
OpenaiToolCall → enum { Function { id, function, type }, Custom { id, custom, type } }
|
||||
```
|
||||
|
||||
### `types/message.rs` — 消息类型
|
||||
```
|
||||
OpenaiChatMessage → enum (覆盖 6 种角色消息)
|
||||
DeveloperMessage → struct { content, role, name }
|
||||
SystemMessage → struct { content, role, name }
|
||||
UserMessage → struct { content, role, name }
|
||||
AssistantMessage → struct { content, refusal, role, name, tool_calls, function_call, audio }
|
||||
ToolMessage → struct { content, role, tool_call_id }
|
||||
FunctionMessage → struct { content, role, name }
|
||||
OpenaiContentPart → enum
|
||||
OpenaiContentPartText → struct { type, text }
|
||||
OpenaiContentPartImage → struct { type, image_url: ImageURL }
|
||||
OpenaiContentPartInputAudio → struct { type, input_audio: InputAudio }
|
||||
OpenaiContentPartFile → struct { type, file: FileData }
|
||||
OpenaiContentPartRefusal → struct { type, refusal }
|
||||
ImageURL → struct { url, detail }
|
||||
InputAudio → struct { data, format }
|
||||
FileData → struct { file_data, file_id, filename }
|
||||
```
|
||||
|
||||
### `types/tool.rs` — 工具类型
|
||||
```
|
||||
OpenaiToolDefinition → struct { name, description, parameters, strict }
|
||||
(保留 ToolDefinition 别名保持后向兼容,重定义为包含所有字段)
|
||||
OpenaiToolCall (在请求中使用) → 见 response.rs 中的定义
|
||||
```
|
||||
|
||||
### `types/usage.rs` — Token 用量
|
||||
```
|
||||
Usage → struct { prompt_tokens, completion_tokens, total_tokens,
|
||||
completion_tokens_details, prompt_tokens_details }
|
||||
CompletionTokensDetails → struct { reasoning_tokens, audio_tokens,
|
||||
accepted_prediction_tokens, rejected_prediction_tokens }
|
||||
PromptTokensDetails → struct { audio_tokens, cached_tokens }
|
||||
CostTracker → 从 cycle/usage.rs 移入(累计追踪器)
|
||||
```
|
||||
|
||||
### 删除的旧类型
|
||||
- `ContentBlock` → 被 `OpenaiContentPart` 替代(更准确的 OpenAI API 命名)
|
||||
- `StopReason` → 被 `FinishReason` 替代(与 API 命名一致)
|
||||
- `Message` → 被 `OpenaiChatMessage` 替代
|
||||
|
||||
### 类型别名(后向兼容)
|
||||
```
|
||||
ChatRequest = OpenaiChatRequest
|
||||
ChatResponse = OpenaiChatResponse
|
||||
Message = OpenaiChatMessage
|
||||
ContentBlock = OpenaiContentPart
|
||||
ToolDefinition = OpenaiToolDefinition
|
||||
Role = Role(保持不变,但扩展变体)
|
||||
StopReason = FinishReason
|
||||
```
|
||||
|
||||
## 4. 对其他模块的影响
|
||||
|
||||
### `provider/openai.rs`
|
||||
- **大幅简化**:`build_request_body()` → 直接 `serde_json::to_value(&request)`
|
||||
- `parse_response()` 中 100+ 行手动解析 → 直接 `serde_json::from_value::<OpenaiChatResponse>()`
|
||||
- `serialize_messages()`, `serialize_message()`, `serialize_content_block()`, `serialize_tool()` → **全部删除**
|
||||
- 新增 `chat_stream()` 方法返回 `OpenaiChatChunk` 流
|
||||
- 需要适配新类型的字段名变更(如 `Usage` 中 `input_tokens` → `prompt_tokens`)
|
||||
|
||||
### `provider.rs` (trait)
|
||||
- 接口保持不变,继续使用 `ChatRequest`/`ChatResponse` 类型别名
|
||||
- 调整 `Usage` 类型引用路径
|
||||
|
||||
### `cycle.rs`
|
||||
- `CycleConfig` 扩展支持更多请求参数(至少增加 `tools, tool_choice, response_format, stop, reasoning_effort, seed` 等)
|
||||
- `LlmCycle::submit()` 构建 `ChatRequest` 时使用新类型
|
||||
- `response.usage` 字段类型变更(新 `Usage` 含更多字段)
|
||||
- 此时不添加流式支持
|
||||
|
||||
### `cycle/usage.rs`
|
||||
- `Usage` 结构体**被移走**到 `types/usage.rs`
|
||||
- `cycle/usage.rs` 保留 `pub use crate::llm::types::usage::{Usage, CostTracker};` 作为兼容性 re-export
|
||||
- `CostTracker` 逻辑不变
|
||||
|
||||
### `error.rs`
|
||||
- 无明显变更,错误类型和映射逻辑不变
|
||||
|
||||
## 5. 实施步骤
|
||||
|
||||
### Phase 1: 基础设施
|
||||
```
|
||||
1. [准备] 在 Cargo.toml 中确认 serde 依赖(已有 serde = "1",features = ["derive"])
|
||||
2. [创建] 新建 src/llm/types/ 目录
|
||||
```
|
||||
|
||||
### Phase 2: 类型定义(按依赖顺序)
|
||||
```
|
||||
3. [usage.rs] 从 cycle/usage.rs 迁移 Usage + CostTracker
|
||||
4. [shared.rs] 定义 Role, FinishReason, ServiceTier, Modality, ImageDetail, StopSequence, ResponseFormat
|
||||
5. [message.rs] 定义 OpenaiChatMessage(6种角色)+ OpenaiContentPart + ImageURL + InputAudio
|
||||
6. [tool.rs] 定义 OpenaiToolDefinition + OpenaiToolCall + FunctionCall
|
||||
7. [request.rs] 定义 OpenaiChatRequest(35+ 字段)+ ToolChoice + StreamOptions
|
||||
8. [response.rs] 定义 OpenaiChatResponse + OpenaiChatChunk + Choice + Delta + Logprobs
|
||||
```
|
||||
|
||||
### Phase 3: 模块组装
|
||||
```
|
||||
9. [mod.rs] 创建模块根,re-export 所有类型 + 别名(ChatRequest = OpenaiChatRequest 等)
|
||||
10. [usage.rs] 更新 cycle/usage.rs 为 pub use re-export
|
||||
11. [删除] 删除旧 src/llm/types.rs
|
||||
```
|
||||
|
||||
### Phase 4: Provider 适配
|
||||
```
|
||||
12. [provider/openai.rs] 重写为 serde 序列化(删除 ~200 行手动代码)
|
||||
13. [cycle.rs] 适配新类型字段(prompt_tokens vs input_tokens)
|
||||
```
|
||||
|
||||
### Phase 5: 验证
|
||||
```
|
||||
14. [编译] cargo check 确保编译通过
|
||||
15. [检查] cargo clippy 确保无警告
|
||||
16. [测试] cargo test 确保测试通过
|
||||
```
|
||||
|
||||
## 6. 验证方式
|
||||
|
||||
- `cargo check` — 编译通过
|
||||
- `cargo clippy` — 无警告
|
||||
- `cargo test` — 所有测试通过(如果有集成测试,可能需要调整)
|
||||
- 检查 `OpenaiProvider` 代码量减少(预期从 354 行降至 ~150 行)
|
||||
- 手动验证序列化输出是否符合 OpenAI API 格式
|
||||
|
||||
## 7. 注意事项
|
||||
|
||||
1. **Break change**: 某些类型名称变化(如 `StopReason` → `FinishReason`),项目处于早期阶段,可接受
|
||||
2. **后向兼容**: 通过类型别名保持旧名称可用,接口层无需修改
|
||||
3. **Anthropic 处理**: Anthropic 是独立体系,不在当前设计中泛化,单独实现 Provider
|
||||
4. **异步流**: `chat_stream()` 的签名需要仔细设计(`Pin<Box<dyn Stream<Item = Result<OpenaiChatChunk, LlmError>>>>` 或自定义类型)
|
||||
5. **CostTracker 不变**: 虽然 Usage 变复杂了,但 CostTracker 只累计 input/output token 数,逻辑不变
|
||||
@@ -0,0 +1,278 @@
|
||||
# LLM 调用周期控制 — 实施方案
|
||||
|
||||
> 参考实现: [HKUDS/OpenHarness](https://github.com/HKUDS/OpenHarness)
|
||||
|
||||
## 目标
|
||||
|
||||
实现大模型基础调用周期控制,作为 agcore 的核心底层件。
|
||||
|
||||
## 范围
|
||||
|
||||
- 仅支持 OpenAI-compatible API (`POST /v1/chat/completions`)
|
||||
- 仅非流式调用(后续可扩展流式)
|
||||
- 支持传入 tool definitions 和解析 tool_use response,但**不含 tool 自动执行循环**
|
||||
- 单次请求-响应周期控制
|
||||
|
||||
## 领域模块结构
|
||||
|
||||
所有 LLM 调用周期相关代码归入 `llm` 领域目录,未来其他功能(工具、记忆、提示词等)以同样方式组织。
|
||||
|
||||
```
|
||||
src/
|
||||
lib.rs # crate 根
|
||||
llm.rs # mod llm — 领域根(声明 + 重导出)
|
||||
llm/
|
||||
types.rs # llm::types — Message, ContentBlock, ChatRequest/Response, ToolDefinition
|
||||
error.rs # llm::error — LlmError
|
||||
provider.rs # llm::provider — LlmProvider trait(仅接口)
|
||||
provider/
|
||||
openai.rs # llm::provider::openai — OpenaiProvider 实现
|
||||
cycle.rs # llm::cycle — 生命周期引擎(子模块根)
|
||||
cycle/
|
||||
retry.rs # llm::cycle::retry — 重试策略
|
||||
usage.rs # llm::cycle::usage — Token 用量
|
||||
|
||||
# 未来领域示例(占位):
|
||||
# tools.rs + tools/ # 工具调用、MCP
|
||||
# memory.rs + memory/ # 记忆系统
|
||||
# prompt.rs + prompt/ # 提示词工程
|
||||
# agent.rs + agent/ # Agent 运行时
|
||||
```
|
||||
|
||||
`llm.rs` 根模块声明:
|
||||
|
||||
```rust
|
||||
// llm.rs
|
||||
pub mod types;
|
||||
pub mod error;
|
||||
pub mod provider;
|
||||
pub mod cycle;
|
||||
```
|
||||
|
||||
## 模块设计
|
||||
|
||||
### 1. llm/types.rs — 核心数据类型
|
||||
|
||||
```rust
|
||||
pub enum Role { User, Assistant, System, Tool }
|
||||
|
||||
pub enum ContentBlock {
|
||||
Text { text: String },
|
||||
ImageUrl { url: String }, // 多模态支持
|
||||
ToolUse { id: String, name: String, input: Value }, // 预留,暂不实现 tool 自动执行循环
|
||||
ToolResult { tool_use_id: String, content: String }, // 预留,暂不实现 tool 自动执行循环
|
||||
}
|
||||
|
||||
pub struct Message {
|
||||
pub role: Role,
|
||||
pub content: Vec<ContentBlock>,
|
||||
}
|
||||
|
||||
pub struct ToolDefinition {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub input_schema: Value,
|
||||
}
|
||||
|
||||
pub struct ChatRequest {
|
||||
pub model: String,
|
||||
pub messages: Vec<Message>,
|
||||
pub system_prompt: Option<String>,
|
||||
pub tools: Vec<ToolDefinition>,
|
||||
pub max_tokens: Option<u32>,
|
||||
pub temperature: Option<f32>,
|
||||
pub extra_body: Option<Value>, // 用于 enable_thinking 等扩展参数(如阿里云 DashScope)
|
||||
}
|
||||
|
||||
pub struct ChatResponse {
|
||||
pub message: Message,
|
||||
pub usage: Usage,
|
||||
pub stop_reason: Option<StopReason>,
|
||||
}
|
||||
|
||||
pub enum StopReason {
|
||||
Stop,
|
||||
ToolUse, // 预留,暂不实现 tool 自动执行循环
|
||||
MaxTokens, // 达到 max_tokens 限制
|
||||
ContentFilter,
|
||||
Length, // 同 MaxTokens,兼容某些 API 的 finish_reason
|
||||
Other(String),
|
||||
}
|
||||
```
|
||||
|
||||
> **注意**:`ToolUse` / `ToolResult` / `ToolUse` variant of `StopReason` 为预留类型,暂不实现 tool 自动执行循环。
|
||||
|
||||
### 2. llm/error.rs — 错误体系
|
||||
|
||||
```rust
|
||||
#[derive(thiserror::Error)]
|
||||
pub enum LlmError {
|
||||
#[error("认证失败: {0}")]
|
||||
Authentication(String),
|
||||
|
||||
#[error("限流{retry_after:?}")]
|
||||
RateLimit { retry_after: Option<Duration> },
|
||||
|
||||
#[error("请求失败({status}): {body}")]
|
||||
Request { status: u16, body: String },
|
||||
|
||||
#[error("请求超时({duration:?})")]
|
||||
Timeout { duration: Duration },
|
||||
|
||||
#[error("流式响应错误: {0}")]
|
||||
Stream(String),
|
||||
|
||||
#[error("上下文超限(actual:{actual}, limit:{limit})")]
|
||||
ContextLength { actual: u32, limit: u32 },
|
||||
|
||||
#[error("LLM 调用失败: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
```
|
||||
|
||||
**可重试错误**:`RateLimit`、`Timeout`、状态码 `5xx`。
|
||||
**不可重试**:`Authentication`、状态码 `4xx`(除 429)、`ContextLength`。
|
||||
|
||||
### 3. llm/provider.rs — Provider 接口
|
||||
|
||||
trait 单独存放,具体实现在 `provider/` 子模块。
|
||||
|
||||
```rust
|
||||
// llm/provider.rs
|
||||
pub mod openai;
|
||||
|
||||
#[async_trait]
|
||||
pub trait LlmProvider: Send + Sync {
|
||||
async fn chat(&self, request: ChatRequest) -> Result<ChatResponse, LlmError>;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.1 llm/provider/openai.rs — OpenAI 兼容实现
|
||||
|
||||
```rust
|
||||
use super::LlmProvider;
|
||||
|
||||
pub struct OpenaiProvider {
|
||||
http_client: reqwest::Client,
|
||||
base_url: String,
|
||||
api_key: String,
|
||||
model: String,
|
||||
}
|
||||
|
||||
impl LlmProvider for OpenaiProvider {
|
||||
async fn chat(&self, request: ChatRequest) -> Result<ChatResponse, LlmError> {
|
||||
// POST {base_url}/chat/completions
|
||||
// extra_body 会被合并到请求体中(如 enable_thinking)
|
||||
// 解析 response → ChatResponse
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **注意**:`extra_body` 中的字段需与目标 API 兼容。部分 API(如阿里云 DashScope)通过 `extra_body` 传递扩展参数(如 `enable_thinking`)。
|
||||
|
||||
后续新增实现: `provider/anthropic.rs`、`provider/azure.rs` 等。
|
||||
|
||||
### 4. llm/cycle.rs — 生命周期引擎
|
||||
|
||||
```rust
|
||||
mod retry;
|
||||
mod usage;
|
||||
|
||||
pub use retry::RetryConfig;
|
||||
pub use usage::{CostTracker, Usage};
|
||||
|
||||
pub struct CycleConfig {
|
||||
pub model: String,
|
||||
pub max_tokens: Option<u32>,
|
||||
pub temperature: Option<f32>,
|
||||
pub max_turns: Option<u32>,
|
||||
pub retry: RetryConfig,
|
||||
}
|
||||
|
||||
pub struct LlmCycle {
|
||||
provider: Box<dyn LlmProvider>,
|
||||
config: CycleConfig,
|
||||
usage: CostTracker,
|
||||
messages: Vec<Message>,
|
||||
system_prompt: Option<String>,
|
||||
}
|
||||
```
|
||||
|
||||
`submit()` 完整流程:
|
||||
|
||||
```
|
||||
submit(prompt, tools)
|
||||
│
|
||||
├─ ① push Message(user, [Text(prompt)])
|
||||
├─ ② 构建 ChatRequest { messages, system, tools, max_tokens, temperature }
|
||||
├─ ③ [重试循环] provider.chat(request)
|
||||
│ ├─ Ok → 解析 ChatResponse
|
||||
│ └─ Err(可重试) → compute_delay → sleep → retry
|
||||
├─ ④ push Message(assistant, [Text(...) | ToolUse(...)])
|
||||
├─ ⑤ usage.add(response.usage)
|
||||
└─ ⑥ return ChatResponse
|
||||
```
|
||||
|
||||
#### 4.1 llm/cycle/retry.rs — 重试策略
|
||||
|
||||
```rust
|
||||
pub struct RetryConfig {
|
||||
pub max_retries: u32, // 默认 3
|
||||
pub base_delay: Duration, // 默认 1s
|
||||
pub max_delay: Duration, // 默认 30s
|
||||
pub jitter_factor: f64, // 默认 0.25
|
||||
}
|
||||
```
|
||||
|
||||
指数退避 + jitter: `delay = min(base * 2^attempt, max_delay) + random(0, delay * jitter_factor)`
|
||||
|
||||
**可重试错误**: `RateLimit`、`Timeout`、状态码 `5xx`
|
||||
**不可重试**: `Authentication`、状态码 `4xx`(除 429)、`ContextLength`
|
||||
|
||||
`should_retry(err: &LlmError) -> bool` 判断逻辑:
|
||||
- `RateLimit` → true
|
||||
- `Timeout` → true
|
||||
- `Request { status, .. }` → status >= 500 || status == 429
|
||||
- 其他 → false
|
||||
|
||||
#### 4.2 llm/cycle/usage.rs — Token 用量
|
||||
|
||||
```rust
|
||||
#[derive(Default)]
|
||||
pub struct Usage { pub input_tokens: u32, pub output_tokens: u32 }
|
||||
|
||||
pub struct CostTracker { accumulated: Usage }
|
||||
impl CostTracker {
|
||||
pub fn add(&mut self, usage: &Usage);
|
||||
pub fn total(&self) -> &Usage;
|
||||
pub fn reset(&mut self);
|
||||
}
|
||||
```
|
||||
|
||||
## 依赖
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
reqwest = { version = "0.12", features = ["json"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
thiserror = "2"
|
||||
async-trait = "0.1"
|
||||
tracing = "0.1"
|
||||
```
|
||||
|
||||
## 测试
|
||||
|
||||
- Unit: types 序列化、retry 退避计算、usage 累计
|
||||
- Mock: HTTP mock server 测试 provider 请求/响应/错误处理
|
||||
- Integration (可选): Ollama 本地真实调用验证
|
||||
|
||||
## 后续扩展
|
||||
|
||||
- 流式接口 (`Stream<CycleEvent>`)
|
||||
- Tool 自动执行循环 (参考 OpenHarness `run_query()`)
|
||||
- 多 Provider 注册发现 (参考 OpenHarness `ProviderRegistry`)
|
||||
- 上下文压缩 (auto-compaction)
|
||||
- 生命周期钩子 (pre/post tool use hooks)
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
# AG Core Roadmap
|
||||
|
||||
> 定稿日期:2026-05-11
|
||||
|
||||
## 愿景
|
||||
|
||||
AG Core 定位为构建 AI 智能体的底层工具箱,通过模块化、可插拔的架构,提供大模型调用、提示词工程、工具系统、记忆检索四大核心能力,支持快速组合出符合业务需求的智能体应用。
|
||||
|
||||
**当前状态**:代码为空壳,specs 目录有 1 份方案(LLM 调用周期)。
|
||||
|
||||
---
|
||||
|
||||
## 模块完整性评估
|
||||
|
||||
| 功能领域 | 方案状态 | 文档位置 | 实现优先级 |
|
||||
|---------|---------|---------|-----------|
|
||||
| LLM 调用周期 | ✅ 完整 | `specs/llm-call-lifecycle.md` | P0 |
|
||||
| 提示词工程 | ❌ 缺失 | — | P1 |
|
||||
| 工具系统 + 权限 | ❌ 缺失 | — | P1 |
|
||||
| 记忆检索 | ❌ 缺失 | — | P2 |
|
||||
| Agent 运行时 | ❌ 缺失 | — | P2 |
|
||||
| 生命周期钩子 | ❌ 缺失 | — | P0(LLM Cycle 扩展) |
|
||||
| Provider 注册发现 | ❌ 缺失 | — | P0(Provider 接口扩展) |
|
||||
| 流式事件系统 | ❌ 缺失 | — | P0(流式接口前置) |
|
||||
|
||||
---
|
||||
|
||||
## 分阶段 Roadmap
|
||||
|
||||
### Phase 0 — Foundation(基础设施)
|
||||
|
||||
**目标**:实现 LLM 调用周期的核心功能,作为所有上层模块的基础。
|
||||
|
||||
**交付物**:
|
||||
1. `llm/types.rs` — 核心数据类型(Message, ContentBlock, ChatRequest/Response, ToolDefinition, StopReason)
|
||||
2. `llm/error.rs` — 错误体系(LlmError 枚举,可重试/不可重试判断)
|
||||
3. `llm/provider.rs` + `llm/provider/openai.rs` — Provider 接口 + OpenAI 兼容实现
|
||||
4. `llm/provider/registry.rs` — ProviderRegistry(多 Provider 注册发现)
|
||||
5. `llm/cycle.rs` + `llm/cycle/{retry,usage}.rs` — 生命周期引擎(重试策略 + 用量追踪)
|
||||
6. `llm/hooks.rs` — HookExecutor 接口(生命周期钩子)
|
||||
7. `llm/stream.rs` — StreamEvents 流式事件系统(AssistantTextDelta, ToolExecutionStarted 等)
|
||||
8. `llm/compact.rs` — Auto-compaction(上下文自动压缩)
|
||||
9. `Cargo.toml` — 添加依赖(tokio, reqwest, serde, thiserror, async-trait, tracing)
|
||||
|
||||
**依赖**:无
|
||||
|
||||
**优先级**:Must Have
|
||||
|
||||
**预估规模**:约 1000 行核心代码
|
||||
|
||||
---
|
||||
|
||||
### Phase 1 — Prompt Engineering(提示词工程)
|
||||
|
||||
**目标**:提供提示词的组合、模板化与优化能力。
|
||||
|
||||
**交付物**:
|
||||
1. `prompt.rs` + `prompt/` 模块
|
||||
2. `PromptTemplate` — 模板引擎(支持变量插值、条件渲染)
|
||||
3. `PromptComposer` — 提示词组合器(拼接 system/user/assistant 消息)
|
||||
4. `specs/prompt-design.md` — 方案文档
|
||||
|
||||
**依赖**:无(可与 Phase 0 并行)
|
||||
|
||||
**优先级**:Should Have
|
||||
|
||||
**预估规模**:约 400 行代码
|
||||
|
||||
---
|
||||
|
||||
### Phase 2 — Tool System(工具系统)
|
||||
|
||||
**目标**:实现 MCP 协议集成与自定义工具注册、调用、权限控制。
|
||||
|
||||
**交付物**:
|
||||
1. `tools.rs` + `tools/` 模块
|
||||
2. `ToolRegistry` — 工具注册表(注册、发现、调用)
|
||||
3. `BaseTool` trait — 工具抽象接口
|
||||
4. `McpClient` — MCP 协议客户端
|
||||
5. `PermissionChecker` — 工具执行权限检查(读/写/删除/网络等)
|
||||
6. `specs/tool-call-loop.md` — Tool 自动执行循环设计
|
||||
7. 扩展 `llm/cycle.rs` 支持自动 tool 循环(参考 OpenHarness `run_query()`)
|
||||
|
||||
**依赖**:Phase 0(LlmProvider 接口传递 tool definitions)、Phase 1(提示词可能需要注入工具描述)
|
||||
|
||||
**优先级**:Should Have
|
||||
|
||||
**预估规模**:约 900 行代码
|
||||
|
||||
---
|
||||
|
||||
### Phase 3 — Memory System(记忆系统)
|
||||
|
||||
**目标**:提供对话记忆的存储、检索与管理能力。
|
||||
|
||||
**交付物**:
|
||||
1. `memory.rs` + `memory/` 模块
|
||||
2. `MemoryStore` trait — 记忆存储抽象(可插拔后端)
|
||||
3. `VectorStore` — 向量存储实现(支持 embedding 检索)
|
||||
4. `ConversationMemory` — 对话记忆管理(sliding window / 全量)
|
||||
5. `MemoryRetriever` — 记忆检索器(similarity search)
|
||||
6. `specs/memory-system.md` — 方案文档
|
||||
|
||||
**依赖**:Phase 0(LLM 调用可能用于 embedding 生成)
|
||||
|
||||
**优先级**:Could Have
|
||||
|
||||
**预估规模**:约 700 行代码
|
||||
|
||||
---
|
||||
|
||||
### Phase 4 — Agent Runtime(智能体运行时)
|
||||
|
||||
**目标**:实现多轮对话编排与任务规划。
|
||||
|
||||
**交付物**:
|
||||
1. `agent.rs` + `agent/` 模块
|
||||
2. `Agent` trait — 智能体接口定义
|
||||
3. `ConversationAgent` — 对话型智能体实现
|
||||
4. `TaskAgent` — 任务型智能体(规划 → 执行 → 反馈)
|
||||
5. `specs/agent-runtime.md` — 方案文档
|
||||
|
||||
**依赖**:Phase 0, 1, 2, 3(整合所有模块)
|
||||
|
||||
**优先级**:Could Have
|
||||
|
||||
**预估规模**:约 600 行代码
|
||||
|
||||
---
|
||||
|
||||
## 依赖关系图
|
||||
|
||||
```
|
||||
Phase 4: Agent Runtime
|
||||
│
|
||||
┌─────────────────┼─────────────────┐
|
||||
▼ ▼ ▼
|
||||
Phase 1 Phase 2 Phase 3
|
||||
Prompt Tool System Memory
|
||||
Engineering + Permission System
|
||||
+ HookExecutor
|
||||
│ │ │
|
||||
└────────┬────────┴────────┬────────┘
|
||||
▼ ▼
|
||||
Phase 0 ─────────────────┘
|
||||
LLM Cycle
|
||||
+ ProviderRegistry
|
||||
+ HookExecutor
|
||||
+ StreamEvents
|
||||
+ Auto-compaction
|
||||
(Foundation)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 扩展计划(v0.2+)
|
||||
|
||||
> 以下功能已在 Phase 0 中实现,流式接口为后续增量优化。
|
||||
|
||||
| 扩展项 | 所在模块 | 说明 | 优先级 |
|
||||
|-------|---------|------|--------|
|
||||
| Prompt Optimizer | `prompt` | 提示词自动优化 | P3 |
|
||||
|
||||
---
|
||||
|
||||
## 风险与建议
|
||||
|
||||
1. **Phase 0 尚未实现**:项目代码是空壳,建议优先完成 LLM 调用周期,避免后续模块依赖不存在的底层
|
||||
2. **并行可能性**:Phase 0 和 Phase 1 可并行开展(无相互依赖),可加速早期交付
|
||||
3. **MCP 协议复杂性**:MCP 涉及协议握手、session 管理、长期连接,建议预留充足时间调研协议细节
|
||||
4. **Scope 蔓延风险**:当前 specs 只有 1 份文档,建议每个模块上线前都产出对应 spec,避免边实现边设计
|
||||
|
||||
---
|
||||
|
||||
## 下一步行动
|
||||
|
||||
1. **Phase 0 方案评审**:对齐 LLM 模块设计(`specs/llm-call-lifecycle.md` 已在 2026-05-11 更新)
|
||||
2. **Phase 1 方案启动**:启动 `specs/prompt-design.md` 设计
|
||||
3. **Phase 2 方案启动**:启动 `specs/tool-call-loop.md` 设计(含 PermissionChecker)
|
||||
Reference in New Issue
Block a user