refactor(core): 扫清 v0.1 Phase A 技术债

- 修复测试编译回归:补全 session.rs/cycle.rs 测试模块导入;
convert.rs 2 处 irrefutable if let 改为 let
- composer.rs 迁移至 IR:OpenaiChatMessage → Message,
ContentField/OpenaiContentPart → ContentBlock;删除 set_message_name
和 build_request;developer 消息映射为 Message::System
- knowledge.rs 锁修复:std::sync::Mutex → tokio::sync::Mutex;
search() 优化锁粒度(锁内仅 clone IDs,避免锁内异步 IO)
- 标记 ChatResponse / ToolDefinition 为废弃(#[deprecated(since = "0.1.0")]),
内部使用点加 #[allow(deprecated)] 抑制警告
- clippy 清零:合并冗余 if、手动 strip_prefix 改 strip_prefix、
多处 dead_code 抑制、测试代码清理
This commit is contained in:
徐涛
2026-07-03 15:19:37 +08:00
parent c2c0d498ee
commit c084c57e2c
16 changed files with 138 additions and 181 deletions
+2
View File
@@ -7,12 +7,14 @@
//! - **不绑定业务循环**`submit_turn` 在 `AgentSession` 上,不在 trait 上 //! - **不绑定业务循环**`submit_turn` 在 `AgentSession` 上,不在 trait 上
use crate::agent::runtime::RuntimeBundle; use crate::agent::runtime::RuntimeBundle;
#[allow(deprecated)]
use crate::llm::types::ToolDefinition; use crate::llm::types::ToolDefinition;
/// Agent 角色抽象。 /// Agent 角色抽象。
/// ///
/// 实现此 trait 即可接入 Agent Runtime。典型实现是 struct 持有静态配置(name、system prompt 模板), /// 实现此 trait 即可接入 Agent Runtime。典型实现是 struct 持有静态配置(name、system prompt 模板),
/// 也可以是基于配置动态生成的轻量实现。 /// 也可以是基于配置动态生成的轻量实现。
#[allow(deprecated)]
pub trait Agent: Send + Sync { pub trait Agent: Send + Sync {
/// 角色名(用于日志、调试、UI 展示)。 /// 角色名(用于日志、调试、UI 展示)。
fn name(&self) -> &str; fn name(&self) -> &str;
+1
View File
@@ -178,6 +178,7 @@ mod tests {
use crate::llm::error::LlmError; use crate::llm::error::LlmError;
use crate::llm::hooks::{Hook, HookContext, HookExecutor, HookResult}; use crate::llm::hooks::{Hook, HookContext, HookExecutor, HookResult};
use crate::llm::provider::{LlmProvider, ProviderCapabilities, ProviderFeatures}; use crate::llm::provider::{LlmProvider, ProviderCapabilities, ProviderFeatures};
use crate::llm::types::message::ContentBlock;
use crate::llm::types::request_v2::MessageRequest; use crate::llm::types::request_v2::MessageRequest;
use crate::llm::types::response_v2::{MessageResponse, StopReason, StreamEvent}; use crate::llm::types::response_v2::{MessageResponse, StopReason, StreamEvent};
use crate::tools::ToolRegistry; use crate::tools::ToolRegistry;
+2
View File
@@ -10,6 +10,7 @@
//! - 重试由上层新建 `Plan` 实现,`TaskAgent` 不做自动重试 //! - 重试由上层新建 `Plan` 实现,`TaskAgent` 不做自动重试
use crate::agent::error::AgentError; use crate::agent::error::AgentError;
#[allow(deprecated)]
use crate::llm::types::ChatResponse; use crate::llm::types::ChatResponse;
use async_trait::async_trait; use async_trait::async_trait;
@@ -55,6 +56,7 @@ impl Step {
/// 均未派生 `Clone`(保留原始错误信息,传递所有权而非克隆)。如需复制 `Plan`, /// 均未派生 `Clone`(保留原始错误信息,传递所有权而非克隆)。如需复制 `Plan`,
/// 只能 clone 处于 `Pending` / `Running` / `Completed` / `Skipped` 状态的步骤。 /// 只能 clone 处于 `Pending` / `Running` / `Completed` / `Skipped` 状态的步骤。
#[derive(Debug)] #[derive(Debug)]
#[allow(deprecated)]
pub enum StepStatus { pub enum StepStatus {
/// 初始状态 —— 等待执行。 /// 初始状态 —— 等待执行。
Pending, Pending,
+3 -8
View File
@@ -40,7 +40,7 @@ pub fn from_openai(msg: &OpenaiChatMessage) -> Message {
let mut blocks = content_to_blocks(content); let mut blocks = content_to_blocks(content);
if let Some(calls) = tool_calls { if let Some(calls) = tool_calls {
for call in calls { for call in calls {
if let OpenaiToolCall::Function { id, function } = call { let OpenaiToolCall::Function { id, function } = call;
let input: Value = let input: Value =
serde_json::from_str(&function.arguments).unwrap_or(Value::Null); serde_json::from_str(&function.arguments).unwrap_or(Value::Null);
blocks.push(ContentBlock::ToolUse { blocks.push(ContentBlock::ToolUse {
@@ -50,7 +50,6 @@ pub fn from_openai(msg: &OpenaiChatMessage) -> Message {
}); });
} }
} }
}
Message::Assistant { content: blocks } Message::Assistant { content: blocks }
} }
OpenaiChatMessage::Tool { OpenaiChatMessage::Tool {
@@ -254,8 +253,7 @@ pub fn blocks_to_content(blocks: &[ContentBlock]) -> ContentField {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::llm::types::message::ImageSource; use crate::llm::types::shared::ImageDetail;
use crate::llm::types::shared::{AudioFormat, ImageDetail};
use serde_json::json; use serde_json::json;
#[test] #[test]
@@ -364,13 +362,10 @@ mod tests {
assert!(matches!(content, ContentField::String(_))); assert!(matches!(content, ContentField::String(_)));
let calls = tool_calls.expect("tool_calls"); let calls = tool_calls.expect("tool_calls");
assert_eq!(calls.len(), 1); assert_eq!(calls.len(), 1);
if let OpenaiToolCall::Function { id, function } = &calls[0] { let OpenaiToolCall::Function { id, function } = &calls[0];
assert_eq!(id, "call_1"); assert_eq!(id, "call_1");
assert_eq!(function.name, "search"); assert_eq!(function.name, "search");
assert!(function.arguments.contains("rust")); assert!(function.arguments.contains("rust"));
} else {
panic!("expected Function variant");
}
} }
_ => panic!("expected Assistant"), _ => panic!("expected Assistant"),
} }
+9 -7
View File
@@ -21,6 +21,7 @@ use crate::llm::stream::StreamEvent;
use crate::llm::types::message::{ContentBlock, Message}; use crate::llm::types::message::{ContentBlock, Message};
use crate::llm::types::request_v2::MessageRequest; use crate::llm::types::request_v2::MessageRequest;
use crate::llm::types::response_v2::{MessageResponse, StopReason}; use crate::llm::types::response_v2::{MessageResponse, StopReason};
#[allow(deprecated)]
use crate::llm::types::{ToolChoice, ToolDefinition}; use crate::llm::types::{ToolChoice, ToolDefinition};
/// LLM 调用周期配置。 /// LLM 调用周期配置。
@@ -79,6 +80,7 @@ pub struct LlmCycle {
compact_state: CompactState, compact_state: CompactState,
} }
#[allow(deprecated)]
impl LlmCycle { impl LlmCycle {
/// 创建一个新的 LlmCycle(持有 `Box<dyn LlmProvider>` 的独占所有权)。 /// 创建一个新的 LlmCycle(持有 `Box<dyn LlmProvider>` 的独占所有权)。
/// ///
@@ -175,13 +177,10 @@ impl LlmCycle {
messages: Vec<Message>, messages: Vec<Message>,
tools: Vec<ToolDefinition>, tools: Vec<ToolDefinition>,
) -> Result<MessageResponse, LlmError> { ) -> Result<MessageResponse, LlmError> {
let ir_messages: Vec<Message> = messages;
let ir_tools: Vec<ToolDefinition> = tools.clone();
let request = MessageRequest { let request = MessageRequest {
model: self.config.model.clone(), model: self.config.model.clone(),
messages: ir_messages, messages,
tools: ir_tools, tools,
tool_choice: ToolChoice::Auto, tool_choice: ToolChoice::Auto,
max_tokens: self.config.max_tokens, max_tokens: self.config.max_tokens,
temperature: self.config.temperature, temperature: self.config.temperature,
@@ -672,6 +671,7 @@ fn truncate_tool_result(s: &str, max_bytes: usize) -> String {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::llm::provider::{ProviderCapabilities, ProviderFeatures};
use crate::tools::{BaseTool, ToolRegistry}; use crate::tools::{BaseTool, ToolRegistry};
use async_trait::async_trait; use async_trait::async_trait;
use futures_core::Stream; use futures_core::Stream;
@@ -858,8 +858,10 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn test_submit_with_tools_max_turns_exceeded() { async fn test_submit_with_tools_max_turns_exceeded() {
let mut config = CycleConfig::default(); let config = CycleConfig {
config.max_tool_turns = Some(2); max_tool_turns: Some(2),
..Default::default()
};
let responses = vec![ let responses = vec![
assistant_tool_call_response(vec![("c1", "add", r#"{"a":1,"b":1}"#)]), assistant_tool_call_response(vec![("c1", "add", r#"{"a":1,"b":1}"#)]),
assistant_tool_call_response(vec![("c2", "add", r#"{"a":1,"b":1}"#)]), assistant_tool_call_response(vec![("c2", "add", r#"{"a":1,"b":1}"#)]),
+1
View File
@@ -75,6 +75,7 @@ impl<'a> HookContext<'a> {
} }
/// 设置 plan step 序号(仅 OnPlanStepComplete 使用,Phase 4b 新增)。 /// 设置 plan step 序号(仅 OnPlanStepComplete 使用,Phase 4b 新增)。
#[allow(dead_code)]
pub(crate) fn with_plan_step_index(mut self, plan_step_index: usize) -> Self { pub(crate) fn with_plan_step_index(mut self, plan_step_index: usize) -> Self {
self.plan_step_index = Some(plan_step_index); self.plan_step_index = Some(plan_step_index);
self self
+3
View File
@@ -36,6 +36,7 @@ const DEFAULT_MAX_TOKENS: u32 = 4096;
pub struct AnthropicProvider { pub struct AnthropicProvider {
http_client: Client, http_client: Client,
base_url: String, base_url: String,
#[allow(dead_code)]
api_key: String, api_key: String,
model: String, model: String,
} }
@@ -586,6 +587,7 @@ enum AnthropicDelta {
struct AnthropicMessageDeltaInner { struct AnthropicMessageDeltaInner {
stop_reason: Option<String>, stop_reason: Option<String>,
#[serde(default)] #[serde(default)]
#[allow(dead_code)]
stop_sequence: Option<String>, stop_sequence: Option<String>,
} }
@@ -599,6 +601,7 @@ pub struct AnthropicSseStream {
chunks: Pin<Box<dyn Stream<Item = Result<Bytes, LlmError>> + Send>>, chunks: Pin<Box<dyn Stream<Item = Result<Bytes, LlmError>> + Send>>,
buffer: String, buffer: String,
partial: PartialMessageResponse, partial: PartialMessageResponse,
#[allow(dead_code)]
next_block_index: u32, next_block_index: u32,
saw_terminal: bool, saw_terminal: bool,
} }
+2 -2
View File
@@ -720,8 +720,8 @@ impl Stream for ChunkToEventStream {
} }
let data = if let Some(p) = trimmed.strip_prefix("data: ") { let data = if let Some(p) = trimmed.strip_prefix("data: ") {
p p
} else if trimmed.starts_with("data:") { } else if let Some(p) = trimmed.strip_prefix("data:") {
&trimmed[5..] p
} else { } else {
continue; continue;
}; };
+4
View File
@@ -40,6 +40,7 @@ pub use usage::{CompletionTokensDetails, CostTracker, PromptTokensDetails, Usage
// Phase 1 起移除 `ChatRequest` 别名 —— 新代码统一使用 `MessageRequest`v2 IR)。 // Phase 1 起移除 `ChatRequest` 别名 —— 新代码统一使用 `MessageRequest`v2 IR)。
// `ChatResponse` 结构体仍存在,作为 OpenAI `chat_inner()` 内部 wire-format 转换目标。 // `ChatResponse` 结构体仍存在,作为 OpenAI `chat_inner()` 内部 wire-format 转换目标。
/// 旧 wire-format 响应结构(保留用于 OpenAI 内部转换层)。 /// 旧 wire-format 响应结构(保留用于 OpenAI 内部转换层)。
#[deprecated(since = "0.1.0", note = "请改用 MessageResponse")]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ChatResponse { pub struct ChatResponse {
pub message: OpenaiChatMessage, pub message: OpenaiChatMessage,
@@ -47,6 +48,7 @@ pub struct ChatResponse {
pub stop_reason: Option<FinishReason>, pub stop_reason: Option<FinishReason>,
} }
#[allow(deprecated)]
impl From<OpenaiChatResponse> for ChatResponse { impl From<OpenaiChatResponse> for ChatResponse {
fn from(response: OpenaiChatResponse) -> Self { fn from(response: OpenaiChatResponse) -> Self {
let message = response let message = response
@@ -63,6 +65,7 @@ impl From<OpenaiChatResponse> for ChatResponse {
} }
} }
#[allow(deprecated)]
impl From<ChatResponse> for OpenaiChatChunk { impl From<ChatResponse> for OpenaiChatChunk {
fn from(response: ChatResponse) -> Self { fn from(response: ChatResponse) -> Self {
let delta = Delta::from(response.message.clone()); let delta = Delta::from(response.message.clone());
@@ -95,4 +98,5 @@ impl From<ChatResponse> for OpenaiChatChunk {
} }
/// 工具定义别名(无新类型冲突,保留)。 /// 工具定义别名(无新类型冲突,保留)。
#[deprecated(since = "0.1.0", note = "ToolDefinition 仍直接对应 OpenAI wire-format;未来 v0.2 引入 IR 工具类型后会再次更新")]
pub type ToolDefinition = OpenaiToolDefinition; pub type ToolDefinition = OpenaiToolDefinition;
+2 -2
View File
@@ -236,7 +236,7 @@ mod tests {
}; };
let mut conv = ConversationMemory::new(store, "s1", config); let mut conv = ConversationMemory::new(store, "s1", config);
for i in 0..5 { for i in 0..5 {
conv.add_message(Message::user_text(&format!("msg-{i}"))) conv.add_message(Message::user_text(format!("msg-{i}")))
.await .await
.unwrap(); .unwrap();
} }
@@ -253,7 +253,7 @@ mod tests {
}; };
let mut conv = ConversationMemory::new(store, "s1", config); let mut conv = ConversationMemory::new(store, "s1", config);
for i in 0..5 { for i in 0..5 {
conv.add_message(Message::user_text(&format!("msg-{i}"))) conv.add_message(Message::user_text(format!("msg-{i}")))
.await .await
.unwrap(); .unwrap();
} }
+27 -18
View File
@@ -19,7 +19,7 @@ pub const KNOWLEDGE_PREFIX: &str = "knowledge_";
/// 同时维护一个 `Vec<PageIndexEntry>` 索引以加速列表遍历。 /// 同时维护一个 `Vec<PageIndexEntry>` 索引以加速列表遍历。
pub struct KnowledgeStore { pub struct KnowledgeStore {
store: Arc<dyn MemoryStore>, store: Arc<dyn MemoryStore>,
index: std::sync::Mutex<Vec<PageIndexEntry>>, index: tokio::sync::Mutex<Vec<PageIndexEntry>>,
} }
impl KnowledgeStore { impl KnowledgeStore {
@@ -27,7 +27,7 @@ impl KnowledgeStore {
pub fn new(store: Arc<dyn MemoryStore>) -> Self { pub fn new(store: Arc<dyn MemoryStore>) -> Self {
Self { Self {
store, store,
index: std::sync::Mutex::new(Vec::new()), index: tokio::sync::Mutex::new(Vec::new()),
} }
} }
@@ -40,7 +40,7 @@ impl KnowledgeStore {
..Default::default() ..Default::default()
}) })
.await?; .await?;
let mut index = self.index.lock().unwrap(); let mut index = self.index.lock().await;
index.clear(); index.clear();
for item in items { for item in items {
let page: KnowledgePage = serde_json::from_str(&item.content) let page: KnowledgePage = serde_json::from_str(&item.content)
@@ -66,7 +66,7 @@ impl KnowledgeStore {
created_at: now, created_at: now,
}; };
self.store.save(item).await?; self.store.save(item).await?;
let mut index = self.index.lock().unwrap(); let mut index = self.index.lock().await;
// 替换或追加 // 替换或追加
if let Some(existing) = index.iter_mut().find(|e| e.id == page.id) { if let Some(existing) = index.iter_mut().find(|e| e.id == page.id) {
*existing = PageIndexEntry::from(&page); *existing = PageIndexEntry::from(&page);
@@ -106,7 +106,7 @@ impl KnowledgeStore {
pub async fn delete_page(&self, id: &str) -> Result<(), MemoryError> { pub async fn delete_page(&self, id: &str) -> Result<(), MemoryError> {
let full_id = format!("{KNOWLEDGE_PREFIX}{id}"); let full_id = format!("{KNOWLEDGE_PREFIX}{id}");
self.store.delete(&full_id).await?; self.store.delete(&full_id).await?;
let mut index = self.index.lock().unwrap(); let mut index = self.index.lock().await;
index.retain(|e| e.id != id); index.retain(|e| e.id != id);
Ok(()) Ok(())
} }
@@ -120,13 +120,22 @@ impl KnowledgeStore {
return Ok(Vec::new()); return Ok(Vec::new());
} }
let needle = query.to_lowercase(); let needle = query.to_lowercase();
let mut results = Vec::new(); // 锁内仅 clone 匹配的 entry id,避免异步 get_page() 持有 index 锁。
let index = self.index.lock().unwrap(); let ids: Vec<String> = {
for entry in index.iter() { let index = self.index.lock().await;
if (entry.title.to_lowercase().contains(&needle) index
.iter()
.filter(|entry| {
entry.title.to_lowercase().contains(&needle)
|| entry.summary.to_lowercase().contains(&needle) || entry.summary.to_lowercase().contains(&needle)
|| entry.tags.iter().any(|t| t.to_lowercase().contains(&needle))) || entry.tags.iter().any(|t| t.to_lowercase().contains(&needle))
&& let Some(page) = self.get_page(&entry.id).await? { })
.map(|entry| entry.id.clone())
.collect()
};
let mut results = Vec::new();
for id in &ids {
if let Some(page) = self.get_page(id).await? {
results.push(page); results.push(page);
} }
} }
@@ -134,8 +143,8 @@ impl KnowledgeStore {
} }
/// 获取内容目录(所有页面的轻量级索引条目)。 /// 获取内容目录(所有页面的轻量级索引条目)。
pub fn get_index(&self) -> Vec<PageIndexEntry> { pub async fn get_index(&self) -> Vec<PageIndexEntry> {
self.index.lock().unwrap().clone() self.index.lock().await.clone()
} }
} }
@@ -222,7 +231,7 @@ mod tests {
let ks = KnowledgeStore::new(store); let ks = KnowledgeStore::new(store);
ks.add_page(make_page("p1", "A", &[])).await.unwrap(); ks.add_page(make_page("p1", "A", &[])).await.unwrap();
ks.add_page(make_page("p2", "B", &[])).await.unwrap(); ks.add_page(make_page("p2", "B", &[])).await.unwrap();
let index = ks.get_index(); let index = ks.get_index().await;
assert_eq!(index.len(), 2); assert_eq!(index.len(), 2);
} }
@@ -233,13 +242,13 @@ mod tests {
// 添加页面 // 添加页面
ks.add_page(make_page("p1", "A", &[])).await.unwrap(); ks.add_page(make_page("p1", "A", &[])).await.unwrap();
ks.add_page(make_page("p2", "B", &[])).await.unwrap(); ks.add_page(make_page("p2", "B", &[])).await.unwrap();
assert_eq!(ks.get_index().len(), 2); assert_eq!(ks.get_index().await.len(), 2);
// 模拟 index 漂移:清空后重建 // 模拟 index 漂移:清空后重建
ks.index.lock().unwrap().clear(); ks.index.lock().await.clear();
assert_eq!(ks.get_index().len(), 0); assert_eq!(ks.get_index().await.len(), 0);
ks.rebuild_index().await.unwrap(); ks.rebuild_index().await.unwrap();
assert_eq!(ks.get_index().len(), 2); assert_eq!(ks.get_index().await.len(), 2);
} }
} }
+1 -3
View File
@@ -113,9 +113,7 @@ fn extract_keywords(query: &str, stop_words: &HashSet<String>) -> Vec<String> {
.split(|c: char| !c.is_alphanumeric()) .split(|c: char| !c.is_alphanumeric())
.filter_map(|s| { .filter_map(|s| {
let lower = s.to_lowercase(); let lower = s.to_lowercase();
if lower.is_empty() || lower.chars().count() < 2 { if lower.is_empty() || lower.chars().count() < 2 || stop_words.contains(&lower) {
None
} else if stop_words.contains(&lower) {
None None
} else { } else {
Some(lower) Some(lower)
+61 -126
View File
@@ -1,12 +1,11 @@
use crate::llm::types::openai_message::{ContentField, OpenaiChatMessage, OpenaiContentPart}; use crate::llm::types::message::{ContentBlock, Message};
use crate::llm::types::request::OpenaiChatRequest;
use crate::prompt::error::PromptError; use crate::prompt::error::PromptError;
use crate::prompt::template::{PromptTemplate, TemplateContext}; use crate::prompt::template::{PromptTemplate, TemplateContext};
/// 提示词组合器——构建多角色消息序列。 /// 提示词组合器——构建多角色消息序列。
#[derive(Default)] #[derive(Default)]
pub struct PromptComposer { pub struct PromptComposer {
messages: Vec<OpenaiChatMessage>, messages: Vec<Message>,
} }
impl PromptComposer { impl PromptComposer {
@@ -16,7 +15,7 @@ impl PromptComposer {
} }
/// 从已有的消息列表初始化。 /// 从已有的消息列表初始化。
pub fn from_messages(messages: Vec<OpenaiChatMessage>) -> Self { pub fn from_messages(messages: Vec<Message>) -> Self {
Self { messages } Self { messages }
} }
@@ -24,34 +23,32 @@ impl PromptComposer {
/// 添加一条纯文本 system 消息。 /// 添加一条纯文本 system 消息。
pub fn system(mut self, text: impl Into<String>) -> Self { pub fn system(mut self, text: impl Into<String>) -> Self {
self.push_message(OpenaiChatMessage::system_text(text.into())); self.push_message(Message::system(text.into()));
self self
} }
/// 添加一条纯文本 user 消息。 /// 添加一条纯文本 user 消息。
pub fn user(mut self, text: impl Into<String>) -> Self { pub fn user(mut self, text: impl Into<String>) -> Self {
self.push_message(OpenaiChatMessage::user_text(text.into())); self.push_message(Message::user_text(text.into()));
self self
} }
/// 添加一条纯文本 assistant 消息。 /// 添加一条纯文本 assistant 消息。
pub fn assistant(mut self, text: impl Into<String>) -> Self { pub fn assistant(mut self, text: impl Into<String>) -> Self {
self.push_message(OpenaiChatMessage::assistant_text(text.into())); self.push_message(Message::assistant(text.into()));
self self
} }
/// 添加一条纯文本 developer 消息(o1 系列模型使用)。 /// 添加一条纯文本 developer 消息(o1 系列模型使用)。
/// IR 层统一映射为 `Message::System`,由 Provider 在发送时按目标模型决定 `role`。
pub fn developer(mut self, text: impl Into<String>) -> Self { pub fn developer(mut self, text: impl Into<String>) -> Self {
self.push_message(OpenaiChatMessage::developer_text(text.into())); self.push_message(Message::system(text.into()));
self self
} }
/// 添加一条 Tool 消息(工具执行结果回传)。 /// 添加一条 Tool 消息(工具执行结果回传)。
pub fn tool(mut self, tool_call_id: impl Into<String>, content: impl Into<String>) -> Self { pub fn tool(mut self, tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
self.push_message(OpenaiChatMessage::tool_result( self.push_message(Message::tool_result(tool_call_id.into(), content.into(), false));
tool_call_id.into(),
content.into(),
));
self self
} }
@@ -64,7 +61,7 @@ impl PromptComposer {
ctx: &TemplateContext, ctx: &TemplateContext,
) -> Result<Self, PromptError> { ) -> Result<Self, PromptError> {
let text = template.render(ctx)?; let text = template.render(ctx)?;
self.push_message(OpenaiChatMessage::user_text(text)); self.push_message(Message::user_text(text));
Ok(self) Ok(self)
} }
@@ -75,7 +72,7 @@ impl PromptComposer {
ctx: &TemplateContext, ctx: &TemplateContext,
) -> Result<Self, PromptError> { ) -> Result<Self, PromptError> {
let text = template.render(ctx)?; let text = template.render(ctx)?;
self.push_message(OpenaiChatMessage::system_text(text)); self.push_message(Message::system(text));
Ok(self) Ok(self)
} }
@@ -86,7 +83,7 @@ impl PromptComposer {
ctx: &TemplateContext, ctx: &TemplateContext,
) -> Result<Self, PromptError> { ) -> Result<Self, PromptError> {
let text = template.render(ctx)?; let text = template.render(ctx)?;
self.push_message(OpenaiChatMessage::assistant_text(text)); self.push_message(Message::assistant(text));
Ok(self) Ok(self)
} }
@@ -97,150 +94,98 @@ impl PromptComposer {
ctx: &TemplateContext, ctx: &TemplateContext,
) -> Result<Self, PromptError> { ) -> Result<Self, PromptError> {
let text = template.render(ctx)?; let text = template.render(ctx)?;
self.push_message(OpenaiChatMessage::developer_text(text)); self.push_message(Message::system(text));
Ok(self) Ok(self)
} }
// ===== 多模态 ContentPart ===== // ===== 多模态 ContentBlock =====
/// 添加一条含指定 ContentPart 的 system 消息。 /// 添加一条含指定 ContentBlock 的 system 消息。
pub fn system_content(mut self, part: OpenaiContentPart) -> Self { pub fn system_content(mut self, block: ContentBlock) -> Self {
self.push_message(OpenaiChatMessage::System { self.push_message(Message::System {
content: ContentField::Array(vec![part]), content: vec![block],
name: None,
}); });
self self
} }
/// 添加一条含指定 ContentPart 的 user 消息。 /// 添加一条含指定 ContentBlock 的 user 消息。
pub fn user_content(mut self, part: OpenaiContentPart) -> Self { pub fn user_content(mut self, block: ContentBlock) -> Self {
self.push_message(OpenaiChatMessage::User { self.push_message(Message::User {
content: ContentField::Array(vec![part]), content: vec![block],
name: None,
}); });
self self
} }
/// 添加一条含指定 ContentPart 的 assistant 消息。 /// 添加一条含指定 ContentBlock 的 assistant 消息。
pub fn assistant_content(mut self, part: OpenaiContentPart) -> Self { pub fn assistant_content(mut self, block: ContentBlock) -> Self {
self.push_message(OpenaiChatMessage::Assistant { self.push_message(Message::Assistant {
content: ContentField::Array(vec![part]), content: vec![block],
refusal: None,
name: None,
tool_calls: None,
}); });
self self
} }
/// 添加一条含指定 ContentPart 的 developer 消息。 /// 添加一条含指定 ContentBlock 的 developer 消息。
pub fn developer_content(mut self, part: OpenaiContentPart) -> Self { pub fn developer_content(mut self, block: ContentBlock) -> Self {
self.push_message(OpenaiChatMessage::Developer { self.push_message(Message::System {
content: ContentField::Array(vec![part]), content: vec![block],
name: None,
}); });
self self
} }
/// 添加一条含指定 ContentPart 的 Tool 消息。 /// 添加一条含指定 ContentBlock 的 Tool 消息。
pub fn tool_content( pub fn tool_content(
mut self, mut self,
tool_call_id: impl Into<String>, tool_call_id: impl Into<String>,
part: OpenaiContentPart, block: ContentBlock,
) -> Self { ) -> Self {
self.push_message(OpenaiChatMessage::Tool { self.push_message(Message::ToolResult {
content: ContentField::Array(vec![part]),
tool_call_id: tool_call_id.into(), tool_call_id: tool_call_id.into(),
content: vec![block],
is_error: false,
}); });
self self
} }
/// 批量添加 ContentPart 作为 user 消息。 /// 批量添加 ContentBlock 作为 user 消息。
pub fn user_contents(mut self, parts: Vec<OpenaiContentPart>) -> Self { pub fn user_contents(mut self, blocks: Vec<ContentBlock>) -> Self {
self.push_message(OpenaiChatMessage::User { self.push_message(Message::User { content: blocks });
content: ContentField::Array(parts),
name: None,
});
self self
} }
/// 批量添加 ContentPart 作为 system 消息。 /// 批量添加 ContentBlock 作为 system 消息。
pub fn system_contents(mut self, parts: Vec<OpenaiContentPart>) -> Self { pub fn system_contents(mut self, blocks: Vec<ContentBlock>) -> Self {
self.push_message(OpenaiChatMessage::System { self.push_message(Message::System { content: blocks });
content: ContentField::Array(parts),
name: None,
});
self self
} }
/// 批量添加 ContentPart 作为 assistant 消息。 /// 批量添加 ContentBlock 作为 assistant 消息。
pub fn assistant_contents(mut self, parts: Vec<OpenaiContentPart>) -> Self { pub fn assistant_contents(mut self, blocks: Vec<ContentBlock>) -> Self {
self.push_message(OpenaiChatMessage::Assistant { self.push_message(Message::Assistant { content: blocks });
content: ContentField::Array(parts),
refusal: None,
name: None,
tool_calls: None,
});
self self
} }
/// 批量添加 ContentPart 作为 developer 消息。 /// 批量添加 ContentBlock 作为 developer 消息。
pub fn developer_contents(mut self, parts: Vec<OpenaiContentPart>) -> Self { pub fn developer_contents(mut self, blocks: Vec<ContentBlock>) -> Self {
self.push_message(OpenaiChatMessage::Developer { self.push_message(Message::System { content: blocks });
content: ContentField::Array(parts),
name: None,
});
self
}
// ===== 角色标识 =====
/// 为上一条添加的消息设置 `name` 字段。
pub fn with_name(mut self, name: impl Into<String>) -> Self {
let name = name.into();
if let Some(msg) = self.messages.last_mut() {
set_message_name(msg, name);
}
self self
} }
// ===== 构建 ===== // ===== 构建 =====
/// 构建最终的消息列表。 /// 构建最终的消息列表。
pub fn build(self) -> Vec<OpenaiChatMessage> { pub fn build(self) -> Vec<Message> {
self.messages self.messages
} }
/// 构建并直接创建 ChatRequest(需搭配 model 参数)。
/// 返回的 `OpenaiChatRequest` 中 `tools`、`temperature`、`max_tokens` 等字段均为 `None`
/// 可通过结构体更新语法补全:`OpenaiChatRequest { tools: Some(...), ..req }`。
pub fn build_request(self, model: impl Into<String>) -> OpenaiChatRequest {
OpenaiChatRequest {
model: model.into(),
messages: self.messages,
..Default::default()
}
}
// ===== 内部方法 ===== // ===== 内部方法 =====
fn push_message(&mut self, msg: OpenaiChatMessage) { fn push_message(&mut self, msg: Message) {
self.messages.push(msg); self.messages.push(msg);
} }
} }
fn set_message_name(msg: &mut OpenaiChatMessage, name: String) { /// 验证消息序列是否符合 LLM API 要求(Tool 消息必须紧跟含 tool_calls 的 Assistant)。
match msg { pub fn validate_messages(messages: &[Message]) -> Result<(), PromptError> {
OpenaiChatMessage::Developer { name: n, .. } => *n = Some(name),
OpenaiChatMessage::System { name: n, .. } => *n = Some(name),
OpenaiChatMessage::User { name: n, .. } => *n = Some(name),
OpenaiChatMessage::Assistant { name: n, .. } => *n = Some(name),
OpenaiChatMessage::Tool { .. } => {}
OpenaiChatMessage::Function { .. } => {}
}
}
/// 验证消息序列是否符合 OpenAI API 要求。
pub fn validate_messages(messages: &[OpenaiChatMessage]) -> Result<(), PromptError> {
if messages.is_empty() { if messages.is_empty() {
return Err(PromptError::InvalidSequence( return Err(PromptError::InvalidSequence(
"消息列表不能为空".to_string(), "消息列表不能为空".to_string(),
@@ -251,10 +196,7 @@ pub fn validate_messages(messages: &[OpenaiChatMessage]) -> Result<(), PromptErr
for (i, msg) in messages.iter().enumerate() { for (i, msg) in messages.iter().enumerate() {
match msg { match msg {
OpenaiChatMessage::Tool { Message::ToolResult { tool_call_id, .. } => {
tool_call_id,
..
} => {
if last_tool_call_ids.is_empty() { if last_tool_call_ids.is_empty() {
return Err(PromptError::InvalidSequence(format!( return Err(PromptError::InvalidSequence(format!(
"消息[{i}] Tool 消息前必须有 Assistant 消息且含 tool_calls" "消息[{i}] Tool 消息前必须有 Assistant 消息且含 tool_calls"
@@ -267,20 +209,13 @@ pub fn validate_messages(messages: &[OpenaiChatMessage]) -> Result<(), PromptErr
))); )));
} }
} }
OpenaiChatMessage::Assistant { Message::Assistant { content } => {
tool_calls: Some(calls),
..
} => {
last_tool_call_ids.clear(); last_tool_call_ids.clear();
for call in calls { for block in content {
let crate::llm::types::OpenaiToolCall::Function { id, .. } = call; if let ContentBlock::ToolUse { id, .. } = block {
last_tool_call_ids.push(id.clone()); last_tool_call_ids.push(id.clone());
} }
} }
OpenaiChatMessage::Assistant {
tool_calls: None, ..
} => {
last_tool_call_ids.clear();
} }
_ => { _ => {
last_tool_call_ids.clear(); last_tool_call_ids.clear();
@@ -318,18 +253,18 @@ mod tests {
assert_eq!(msgs.len(), 4); assert_eq!(msgs.len(), 4);
match &msgs[3] { match &msgs[3] {
OpenaiChatMessage::Tool { Message::ToolResult {
tool_call_id, tool_call_id,
content, content,
.. ..
} => { } => {
assert_eq!(tool_call_id, "call_123"); assert_eq!(tool_call_id, "call_123");
match content { match &content[0] {
ContentField::String(s) => assert_eq!(s, "Sunny, 25°C"), ContentBlock::Text { text } => assert_eq!(text, "Sunny, 25°C"),
_ => {} _ => panic!("Expected Text block"),
} }
} }
_ => panic!("Expected Tool message"), _ => panic!("Expected ToolResult message"),
} }
} }
@@ -345,7 +280,7 @@ mod tests {
#[test] #[test]
fn test_validate_messages_empty() { fn test_validate_messages_empty() {
let msgs: Vec<OpenaiChatMessage> = vec![]; let msgs: Vec<Message> = vec![];
assert!(validate_messages(&msgs).is_err()); assert!(validate_messages(&msgs).is_err());
} }
+1 -1
View File
@@ -105,7 +105,7 @@ mod tests {
#[test] #[test]
fn test_io_is_not_recoverable() { fn test_io_is_not_recoverable() {
let io_err = std::io::Error::new(std::io::ErrorKind::Other, "disk"); let io_err = std::io::Error::other("disk");
let err = ToolError::from(io_err); let err = ToolError::from(io_err);
assert!(!err.is_recoverable()); assert!(!err.is_recoverable());
} }
+3
View File
@@ -20,6 +20,7 @@ use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::process::{Child, ChildStdin, ChildStdout, Command}; use tokio::process::{Child, ChildStdin, ChildStdout, Command};
use tokio::sync::{oneshot, Mutex}; use tokio::sync::{oneshot, Mutex};
#[allow(deprecated)]
use crate::llm::types::ToolDefinition; use crate::llm::types::ToolDefinition;
use crate::tools::base::{BaseTool, ToolContext, ToolRef}; use crate::tools::base::{BaseTool, ToolContext, ToolRef};
use crate::tools::error::ToolError; use crate::tools::error::ToolError;
@@ -135,6 +136,7 @@ impl std::fmt::Debug for McpClient {
} }
} }
#[allow(deprecated)]
impl McpClient { impl McpClient {
/// 创建一个 MCP 客户端。 /// 创建一个 MCP 客户端。
pub fn new(server_name: impl Into<String>, transport: McpTransport) -> Self { pub fn new(server_name: impl Into<String>, transport: McpTransport) -> Self {
@@ -540,6 +542,7 @@ enum McpClientHandle {
} }
#[async_trait] #[async_trait]
#[allow(deprecated)]
impl BaseTool for McpToolAdapter { impl BaseTool for McpToolAdapter {
fn name(&self) -> &str { fn name(&self) -> &str {
&self.name &self.name
+2
View File
@@ -7,6 +7,7 @@ use std::time::Duration;
use futures::future::join_all; use futures::future::join_all;
use serde_json::Value; use serde_json::Value;
#[allow(deprecated)]
use crate::llm::types::ToolDefinition; use crate::llm::types::ToolDefinition;
use crate::tools::base::{ToolContext, ToolRef}; use crate::tools::base::{ToolContext, ToolRef};
use crate::tools::error::ToolError; use crate::tools::error::ToolError;
@@ -70,6 +71,7 @@ impl std::fmt::Debug for ToolRegistry {
} }
} }
#[allow(deprecated)]
impl ToolRegistry { impl ToolRegistry {
/// 创建一个新的工具注册表。 /// 创建一个新的工具注册表。
pub fn new() -> Self { pub fn new() -> Self {