refactor(types): response.rs 类型移入 provider/openai.rs

- 删除 types/response.rs(177 行)
- 所有 OpenAI wire-format 响应类型迁入 provider/openai.rs,可见性 pub(crate):
  TokenLogprob / TopLogprob / Logprobs / URLCitation / Annotation / OpenaiAudio /
  Choice / OpenaiChatResponse / Delta / ChunkChoice / OpenaiChatChunk
- From<OpenaiChatMessage> for Delta 与 From<OpenaiChatResponse> for OpenaiChatChunk
  同步迁入 openai.rs
- types/mod.rs 删除 pub mod response; 与对应 re-export
- convert_response 同步降级为 pub(crate) 以匹配 OpenaiChatResponse 可见性
- stream.rs: OpenaiChatChunk import 路径改为 crate::llm::provider::openai
- stream.rs 同步简化为 module doc + pub use 重导出(合并 Step 13.3 的清理动作,
  避免遗留 dead_code 警告来回)
- mod.rs: ChatResponse 的两个 From impl 同步删除(impl 内引用的 OpenaiChatResponse
  / OpenaiChatChunk / Delta / ChunkChoice 已不在 types 模块),结构体保留到 Step 13.3
- 公共 re-export 路径 agcore::llm::types::OpenaiChatResponse/Chunk 等已删除
  (Breaking Change,见 CHANGELOG)
This commit is contained in:
徐涛
2026-07-08 22:58:18 +08:00
parent 760de46623
commit 1c0e1e0ed1
5 changed files with 202 additions and 435 deletions
+195 -4
View File
@@ -25,9 +25,8 @@ use super::{LlmProvider, ProviderCapabilities, ProviderFeatures};
use crate::llm::convert::{from_openai, to_openai};
use crate::llm::error::LlmError;
use crate::llm::types::message::{ContentBlock, ContentBlockType, Message};
use crate::llm::types::openai_message::{ContentField, OpenaiChatMessage};
use crate::llm::types::openai_message::{ContentField, OpenaiChatMessage, OpenaiContentPart};
use crate::llm::types::request_v2::MessageRequest;
use crate::llm::types::response::{OpenaiChatChunk, OpenaiChatResponse};
use crate::llm::types::response_v2::{
MessageResponse, PartialMessageResponse, PartialUsage, StopReason, StreamEvent,
};
@@ -148,6 +147,198 @@ pub(crate) struct OpenaiChatRequest {
pub extra_body: Option<Value>,
}
// =============================================================================
// 0b. OpenAI wire-format 响应类型(Phase 13 从 types::response 迁入)
// =============================================================================
/// 单 token logprob。
#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct TokenLogprob {
pub token: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub bytes: Option<Vec<u32>>,
pub logprob: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_logprobs: Option<Vec<TopLogprob>>,
}
/// Top-K logprob。
#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct TopLogprob {
pub token: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub bytes: Option<Vec<u32>>,
pub logprob: f64,
}
/// Logprobs 容器。
#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Logprobs {
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<Vec<TokenLogprob>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub refusal: Option<Vec<TokenLogprob>>,
}
/// URL 引用(annotation 用)。
#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct URLCitation {
pub end_index: u32,
pub start_index: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
pub url: String,
}
/// 注释(response 中可包含)。
#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Annotation {
#[serde(rename = "type")]
pub ann_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub url_citation: Option<URLCitation>,
}
/// OpenAI 音频输出。
#[allow(dead_code)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct OpenaiAudio {
pub id: String,
pub data: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transcript: Option<String>,
}
/// 非流式 choice。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Choice {
pub index: u32,
pub message: OpenaiChatMessage,
#[serde(skip_serializing_if = "Option::is_none")]
pub finish_reason: Option<FinishReason>,
#[serde(skip_serializing_if = "Option::is_none")]
pub logprobs: Option<Logprobs>,
}
/// OpenAI Chat Completions 响应。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct OpenaiChatResponse {
pub id: String,
pub object: String,
pub created: u64,
pub model: String,
pub choices: Vec<Choice>,
pub usage: crate::llm::types::usage::Usage,
#[serde(skip_serializing_if = "Option::is_none")]
pub system_fingerprint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub service_tier: Option<ServiceTier>,
}
/// 流式响应 delta。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Delta {
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub refusal: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_calls: Option<Vec<OpenaiToolCall>>,
}
/// 流式 chunk 的 choice。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct ChunkChoice {
pub index: u32,
pub delta: Delta,
#[serde(skip_serializing_if = "Option::is_none")]
pub logprobs: Option<Logprobs>,
#[serde(skip_serializing_if = "Option::is_none")]
pub finish_reason: Option<FinishReason>,
}
/// OpenAI Chat Completions 流式 chunk。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct OpenaiChatChunk {
pub id: String,
pub object: String,
pub created: u64,
pub model: String,
pub choices: Vec<ChunkChoice>,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<crate::llm::types::usage::Usage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system_fingerprint: Option<String>,
}
impl From<OpenaiChatMessage> for Delta {
fn from(msg: OpenaiChatMessage) -> Self {
match msg {
OpenaiChatMessage::Assistant {
content,
tool_calls,
..
} => Delta {
role: Some("assistant".to_string()),
content: match content {
ContentField::String(s) => Some(s),
ContentField::Array(parts) => {
let mut text = String::new();
for part in parts {
if let OpenaiContentPart::Text { text: t } = part {
text.push_str(&t);
}
}
if text.is_empty() { None } else { Some(text) }
}
},
refusal: None,
tool_calls,
},
_ => Delta {
role: None,
content: None,
refusal: None,
tool_calls: None,
},
}
}
}
impl From<OpenaiChatResponse> for OpenaiChatChunk {
fn from(response: OpenaiChatResponse) -> Self {
let choices = response
.choices
.into_iter()
.map(|c| ChunkChoice {
index: c.index,
delta: Delta::from(c.message),
logprobs: c.logprobs,
finish_reason: c.finish_reason,
})
.collect();
OpenaiChatChunk {
id: response.id,
object: "chat.completion.chunk".to_string(),
created: response.created,
model: response.model,
choices,
usage: Some(response.usage),
system_fingerprint: response.system_fingerprint,
}
}
}
// =============================================================================
// 1. GenericOpenaiProvider —— OpenAI-compatible 协议共用实现
// =============================================================================
@@ -389,7 +580,7 @@ impl GenericOpenaiProvider {
/// `OpenaiChatResponse` → `MessageResponse`。
///
/// 返回 `Err(LlmError::Other)` 当 `choices` 为空。
pub fn convert_response(
pub(crate) fn convert_response(
&self,
response: OpenaiChatResponse,
) -> Result<MessageResponse, LlmError> {
@@ -1102,7 +1293,7 @@ data: [DONE]\n\n";
object: "chat.completion".into(),
created: 0,
model: "gpt-4o".into(),
choices: vec![crate::llm::types::response::Choice {
choices: vec![Choice {
index: 0,
message: OpenaiChatMessage::Assistant {
content: ContentField::String(String::new()),