refactor(types): ToolChoice 移入 tool.rs
- ToolChoice 枚举与 serde impl 从 types/request.rs 迁入 types/tool.rs - mod.rs re-export 从 request::ToolChoice 改为 tool::ToolChoice(公共 agcore::llm::types::ToolChoice 路径保持不变) - request_v2.rs import 路径更新为 crate::llm::types::tool::ToolChoice - OpenaiChatRequest 字段类型引用更新为 super::tool::ToolChoice(Step 13.1 删除 request.rs 后此临时 import 同步消除)
This commit is contained in:
@@ -12,7 +12,7 @@ pub mod usage;
|
||||
pub use openai_message::{
|
||||
ContentField, FileData, ImageURL, InputAudio, OpenaiChatMessage, OpenaiContentPart,
|
||||
};
|
||||
pub use request::{OpenaiChatRequest, OpenaiTool, StreamOptions, ToolChoice};
|
||||
pub use request::{OpenaiChatRequest, OpenaiTool, StreamOptions};
|
||||
pub use request_v2::{ExtraError, MessageRequest, ThinkingConfig};
|
||||
pub use response::{
|
||||
Annotation, Choice, ChunkChoice, Delta, Logprobs, OpenaiAudio, OpenaiChatChunk,
|
||||
@@ -26,7 +26,7 @@ pub use shared::{
|
||||
AudioFormat, FinishReason, ImageDetail, Modality, ResponseFormat, Role, ServiceTier,
|
||||
StopSequence,
|
||||
};
|
||||
pub use tool::{FunctionCall, OpenaiToolCall, ToolDef};
|
||||
pub use tool::{FunctionCall, OpenaiToolCall, ToolChoice, ToolDef};
|
||||
pub use usage::{CompletionTokensDetails, CostTracker, PromptTokensDetails, Usage};
|
||||
|
||||
// Re-export IR 内容块 / 消息类型供 `types::ContentBlock` 等历史路径消费。
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::llm::types::shared::{ResponseFormat, ServiceTier, StopSequence};
|
||||
use crate::llm::types::tool::OpenaiToolDefinition;
|
||||
use crate::llm::types::tool::{OpenaiToolDefinition, ToolChoice};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -11,93 +11,6 @@ pub struct StreamOptions {
|
||||
pub include_obfuscation: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum ToolChoice {
|
||||
#[default]
|
||||
None,
|
||||
Auto,
|
||||
Required,
|
||||
Named {
|
||||
name: String,
|
||||
},
|
||||
AllowedTools {
|
||||
tool_names: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Serialize for ToolChoice {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self {
|
||||
ToolChoice::None => serializer.serialize_str("none"),
|
||||
ToolChoice::Auto => serializer.serialize_str("auto"),
|
||||
ToolChoice::Required => serializer.serialize_str("required"),
|
||||
ToolChoice::Named { name } => {
|
||||
let obj = serde_json::json!({
|
||||
"type": "function",
|
||||
"function": { "name": name }
|
||||
});
|
||||
obj.serialize(serializer)
|
||||
}
|
||||
ToolChoice::AllowedTools { tool_names } => {
|
||||
let obj = serde_json::json!({
|
||||
"type": "function",
|
||||
"function": { "name": tool_names.first().cloned().unwrap_or_default() }
|
||||
});
|
||||
obj.serialize(serializer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ToolChoice {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let value = Value::deserialize(deserializer)?;
|
||||
match value {
|
||||
Value::String(s) => match s.as_str() {
|
||||
"none" => Ok(ToolChoice::None),
|
||||
"auto" => Ok(ToolChoice::Auto),
|
||||
"required" => Ok(ToolChoice::Required),
|
||||
_ => Err(serde::de::Error::custom(format!(
|
||||
"unknown tool choice: {s}"
|
||||
))),
|
||||
},
|
||||
Value::Object(obj) => {
|
||||
let typ = obj.get("type").and_then(|v| v.as_str()).ok_or_else(|| {
|
||||
serde::de::Error::custom("missing 'type' field in tool_choice")
|
||||
})?;
|
||||
if typ == "function" {
|
||||
let func =
|
||||
obj.get("function")
|
||||
.and_then(|v| v.as_object())
|
||||
.ok_or_else(|| {
|
||||
serde::de::Error::custom("missing 'function' field in tool_choice")
|
||||
})?;
|
||||
let name = func.get("name").and_then(|v| v.as_str()).ok_or_else(|| {
|
||||
serde::de::Error::custom("missing 'function.name' in tool_choice")
|
||||
})?;
|
||||
Ok(ToolChoice::Named {
|
||||
name: name.to_string(),
|
||||
})
|
||||
} else {
|
||||
Err(serde::de::Error::custom(format!(
|
||||
"unknown tool_choice type: {typ}"
|
||||
)))
|
||||
}
|
||||
}
|
||||
_ => Err(serde::de::Error::custom(
|
||||
"tool_choice must be a string or object",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
pub enum OpenaiTool {
|
||||
|
||||
@@ -9,7 +9,7 @@ use serde_json::Value;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::llm::types::message::Message;
|
||||
use crate::llm::types::request::ToolChoice;
|
||||
use crate::llm::types::tool::ToolChoice;
|
||||
use crate::llm::types::tool::ToolDef;
|
||||
|
||||
/// Provider 无关的请求类型。
|
||||
|
||||
@@ -63,3 +63,93 @@ pub struct FunctionCall {
|
||||
pub enum OpenaiToolCall {
|
||||
Function { id: String, function: FunctionCall },
|
||||
}
|
||||
|
||||
/// 工具选择策略 —— Phase 13 从 `types::request::ToolChoice` 迁入。
|
||||
///
|
||||
/// `#[non_exhaustive]` 预留扩展空间。
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub enum ToolChoice {
|
||||
#[default]
|
||||
None,
|
||||
Auto,
|
||||
Required,
|
||||
Named {
|
||||
name: String,
|
||||
},
|
||||
AllowedTools {
|
||||
tool_names: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Serialize for ToolChoice {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self {
|
||||
ToolChoice::None => serializer.serialize_str("none"),
|
||||
ToolChoice::Auto => serializer.serialize_str("auto"),
|
||||
ToolChoice::Required => serializer.serialize_str("required"),
|
||||
ToolChoice::Named { name } => {
|
||||
let obj = serde_json::json!({
|
||||
"type": "function",
|
||||
"function": { "name": name }
|
||||
});
|
||||
obj.serialize(serializer)
|
||||
}
|
||||
ToolChoice::AllowedTools { tool_names } => {
|
||||
let obj = serde_json::json!({
|
||||
"type": "function",
|
||||
"function": { "name": tool_names.first().cloned().unwrap_or_default() }
|
||||
});
|
||||
obj.serialize(serializer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ToolChoice {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let value = Value::deserialize(deserializer)?;
|
||||
match value {
|
||||
Value::String(s) => match s.as_str() {
|
||||
"none" => Ok(ToolChoice::None),
|
||||
"auto" => Ok(ToolChoice::Auto),
|
||||
"required" => Ok(ToolChoice::Required),
|
||||
_ => Err(serde::de::Error::custom(format!(
|
||||
"unknown tool choice: {s}"
|
||||
))),
|
||||
},
|
||||
Value::Object(obj) => {
|
||||
let typ = obj.get("type").and_then(|v| v.as_str()).ok_or_else(|| {
|
||||
serde::de::Error::custom("missing 'type' field in tool_choice")
|
||||
})?;
|
||||
if typ == "function" {
|
||||
let func =
|
||||
obj.get("function")
|
||||
.and_then(|v| v.as_object())
|
||||
.ok_or_else(|| {
|
||||
serde::de::Error::custom("missing 'function' field in tool_choice")
|
||||
})?;
|
||||
let name = func.get("name").and_then(|v| v.as_str()).ok_or_else(|| {
|
||||
serde::de::Error::custom("missing 'function.name' in tool_choice")
|
||||
})?;
|
||||
Ok(ToolChoice::Named {
|
||||
name: name.to_string(),
|
||||
})
|
||||
} else {
|
||||
Err(serde::de::Error::custom(format!(
|
||||
"unknown tool_choice type: {typ}"
|
||||
)))
|
||||
}
|
||||
}
|
||||
_ => Err(serde::de::Error::custom(
|
||||
"tool_choice must be a string or object",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user