mirror of
https://github.com/katanemo/plano.git
synced 2026-04-28 18:36:34 +02:00
cargo clippy (#660)
This commit is contained in:
parent
c75e7606f9
commit
ca95ffb63d
62 changed files with 1864 additions and 1187 deletions
|
|
@ -11,11 +11,11 @@ pub trait ExtractText {
|
|||
/// Trait for utility functions on content collections
|
||||
pub trait ContentUtils<T> {
|
||||
fn extract_tool_calls(&self) -> Result<Option<Vec<ToolCall>>, TransformError>;
|
||||
fn split_for_openai(
|
||||
&self,
|
||||
) -> Result<(Vec<ContentPart>, Vec<ToolCall>, Vec<(String, String, bool)>), TransformError>;
|
||||
fn split_for_openai(&self) -> Result<SplitForOpenAIResult, TransformError>;
|
||||
}
|
||||
|
||||
pub type SplitForOpenAIResult = (Vec<ContentPart>, Vec<ToolCall>, Vec<(String, String, bool)>);
|
||||
|
||||
/// Helper to create a current unix timestamp
|
||||
pub fn current_timestamp() -> u64 {
|
||||
SystemTime::now()
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ impl TryFrom<AnthropicMessagesRequest> for ChatCompletionsRequest {
|
|||
}
|
||||
|
||||
// Convert tools and tool choice
|
||||
let openai_tools = req.tools.map(|tools| convert_anthropic_tools(tools));
|
||||
let openai_tools = req.tools.map(convert_anthropic_tools);
|
||||
let (openai_tool_choice, parallel_tool_calls) =
|
||||
convert_anthropic_tool_choice(req.tool_choice);
|
||||
|
||||
|
|
@ -218,18 +218,18 @@ impl TryFrom<MessagesMessage> for Vec<Message> {
|
|||
}
|
||||
|
||||
// Role Conversions
|
||||
impl Into<Role> for MessagesRole {
|
||||
fn into(self) -> Role {
|
||||
match self {
|
||||
impl From<MessagesRole> for Role {
|
||||
fn from(val: MessagesRole) -> Self {
|
||||
match val {
|
||||
MessagesRole::User => Role::User,
|
||||
MessagesRole::Assistant => Role::Assistant,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<MessagesStopReason> for FinishReason {
|
||||
fn into(self) -> MessagesStopReason {
|
||||
match self {
|
||||
impl From<FinishReason> for MessagesStopReason {
|
||||
fn from(val: FinishReason) -> Self {
|
||||
match val {
|
||||
FinishReason::Stop => MessagesStopReason::EndTurn,
|
||||
FinishReason::Length => MessagesStopReason::MaxTokens,
|
||||
FinishReason::ToolCalls => MessagesStopReason::ToolUse,
|
||||
|
|
@ -239,11 +239,11 @@ impl Into<MessagesStopReason> for FinishReason {
|
|||
}
|
||||
}
|
||||
|
||||
impl Into<MessagesUsage> for Usage {
|
||||
fn into(self) -> MessagesUsage {
|
||||
impl From<Usage> for MessagesUsage {
|
||||
fn from(val: Usage) -> Self {
|
||||
MessagesUsage {
|
||||
input_tokens: self.prompt_tokens,
|
||||
output_tokens: self.completion_tokens,
|
||||
input_tokens: val.prompt_tokens,
|
||||
output_tokens: val.completion_tokens,
|
||||
cache_creation_input_tokens: None,
|
||||
cache_read_input_tokens: None,
|
||||
}
|
||||
|
|
@ -251,9 +251,9 @@ impl Into<MessagesUsage> for Usage {
|
|||
}
|
||||
|
||||
// System Prompt Conversions
|
||||
impl Into<Message> for MessagesSystemPrompt {
|
||||
fn into(self) -> Message {
|
||||
let system_content = match self {
|
||||
impl From<MessagesSystemPrompt> for Message {
|
||||
fn from(val: MessagesSystemPrompt) -> Self {
|
||||
let system_content = match val {
|
||||
MessagesSystemPrompt::Single(text) => MessageContent::Text(text),
|
||||
MessagesSystemPrompt::Blocks(blocks) => MessageContent::Text(blocks.extract_text()),
|
||||
};
|
||||
|
|
@ -384,12 +384,8 @@ impl TryFrom<MessagesMessage> for BedrockMessage {
|
|||
ToolResultContent::Blocks(blocks) => {
|
||||
let mut result_blocks = Vec::new();
|
||||
for result_block in blocks {
|
||||
match result_block {
|
||||
crate::apis::anthropic::MessagesContentBlock::Text { text, .. } => {
|
||||
result_blocks.push(ToolResultContentBlock::Text { text });
|
||||
}
|
||||
// For now, skip other content types in tool results
|
||||
_ => {}
|
||||
if let crate::apis::anthropic::MessagesContentBlock::Text { text, .. } = result_block {
|
||||
result_blocks.push(ToolResultContentBlock::Text { text });
|
||||
}
|
||||
}
|
||||
result_blocks
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ use crate::apis::openai::{
|
|||
};
|
||||
|
||||
use crate::apis::openai_responses::{
|
||||
ResponsesAPIRequest, InputContent, InputItem, InputParam, MessageRole, Modality, ReasoningEffort, Tool as ResponsesTool, ToolChoice as ResponsesToolChoice
|
||||
InputContent, InputItem, InputParam, MessageRole, Modality, ReasoningEffort,
|
||||
ResponsesAPIRequest, Tool as ResponsesTool, ToolChoice as ResponsesToolChoice,
|
||||
};
|
||||
use crate::clients::TransformError;
|
||||
use crate::transforms::lib::ExtractText;
|
||||
|
|
@ -27,9 +28,9 @@ type AnthropicMessagesRequest = MessagesRequest;
|
|||
// MAIN REQUEST TRANSFORMATIONS
|
||||
// ============================================================================
|
||||
|
||||
impl Into<MessagesSystemPrompt> for Message {
|
||||
fn into(self) -> MessagesSystemPrompt {
|
||||
let system_text = match self.content {
|
||||
impl From<Message> for MessagesSystemPrompt {
|
||||
fn from(val: Message) -> Self {
|
||||
let system_text = match val.content {
|
||||
MessageContent::Text(text) => text,
|
||||
MessageContent::Parts(parts) => parts.extract_text(),
|
||||
};
|
||||
|
|
@ -163,7 +164,7 @@ impl TryFrom<Message> for BedrockMessage {
|
|||
let has_tool_calls = message
|
||||
.tool_calls
|
||||
.as_ref()
|
||||
.map_or(false, |calls| !calls.is_empty());
|
||||
.is_some_and(|calls| !calls.is_empty());
|
||||
|
||||
// Add text content if it's non-empty, or if we have no tool calls (to avoid empty content)
|
||||
if !text_content.is_empty() {
|
||||
|
|
@ -252,7 +253,6 @@ impl TryFrom<ResponsesAPIRequest> for ChatCompletionsRequest {
|
|||
type Error = TransformError;
|
||||
|
||||
fn try_from(req: ResponsesAPIRequest) -> Result<Self, Self::Error> {
|
||||
|
||||
// Convert input to messages
|
||||
let messages = match req.input {
|
||||
InputParam::Text(text) => {
|
||||
|
|
@ -282,50 +282,27 @@ impl TryFrom<ResponsesAPIRequest> for ChatCompletionsRequest {
|
|||
|
||||
// Convert each input item
|
||||
for item in items {
|
||||
match item {
|
||||
InputItem::Message(input_msg) => {
|
||||
let role = match input_msg.role {
|
||||
MessageRole::User => Role::User,
|
||||
MessageRole::Assistant => Role::Assistant,
|
||||
MessageRole::System => Role::System,
|
||||
MessageRole::Developer => Role::System, // Map developer to system
|
||||
};
|
||||
if let InputItem::Message(input_msg) = item {
|
||||
let role = match input_msg.role {
|
||||
MessageRole::User => Role::User,
|
||||
MessageRole::Assistant => Role::Assistant,
|
||||
MessageRole::System => Role::System,
|
||||
MessageRole::Developer => Role::System, // Map developer to system
|
||||
};
|
||||
|
||||
// Convert content based on MessageContent type
|
||||
let content = match &input_msg.content {
|
||||
crate::apis::openai_responses::MessageContent::Text(text) => {
|
||||
// Simple text content
|
||||
MessageContent::Text(text.clone())
|
||||
}
|
||||
crate::apis::openai_responses::MessageContent::Items(content_items) => {
|
||||
// Check if it's a single text item (can use simple text format)
|
||||
if content_items.len() == 1 {
|
||||
if let InputContent::InputText { text } = &content_items[0] {
|
||||
MessageContent::Text(text.clone())
|
||||
} else {
|
||||
// Single non-text item - use parts format
|
||||
MessageContent::Parts(
|
||||
content_items.iter()
|
||||
.filter_map(|c| match c {
|
||||
InputContent::InputText { text } => {
|
||||
Some(crate::apis::openai::ContentPart::Text { text: text.clone() })
|
||||
}
|
||||
InputContent::InputImage { image_url, .. } => {
|
||||
Some(crate::apis::openai::ContentPart::ImageUrl {
|
||||
image_url: crate::apis::openai::ImageUrl {
|
||||
url: image_url.clone(),
|
||||
detail: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
InputContent::InputFile { .. } => None, // Skip files for now
|
||||
InputContent::InputAudio { .. } => None, // Skip audio for now
|
||||
})
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
// Convert content based on MessageContent type
|
||||
let content = match &input_msg.content {
|
||||
crate::apis::openai_responses::MessageContent::Text(text) => {
|
||||
// Simple text content
|
||||
MessageContent::Text(text.clone())
|
||||
}
|
||||
crate::apis::openai_responses::MessageContent::Items(content_items) => {
|
||||
// Check if it's a single text item (can use simple text format)
|
||||
if content_items.len() == 1 {
|
||||
if let InputContent::InputText { text } = &content_items[0] {
|
||||
MessageContent::Text(text.clone())
|
||||
} else {
|
||||
// Multiple content items - convert to parts
|
||||
// Single non-text item - use parts format
|
||||
MessageContent::Parts(
|
||||
content_items.iter()
|
||||
.filter_map(|c| match c {
|
||||
|
|
@ -346,20 +323,41 @@ impl TryFrom<ResponsesAPIRequest> for ChatCompletionsRequest {
|
|||
.collect()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Multiple content items - convert to parts
|
||||
MessageContent::Parts(
|
||||
content_items
|
||||
.iter()
|
||||
.filter_map(|c| match c {
|
||||
InputContent::InputText { text } => {
|
||||
Some(crate::apis::openai::ContentPart::Text {
|
||||
text: text.clone(),
|
||||
})
|
||||
}
|
||||
InputContent::InputImage { image_url, .. } => Some(
|
||||
crate::apis::openai::ContentPart::ImageUrl {
|
||||
image_url: crate::apis::openai::ImageUrl {
|
||||
url: image_url.clone(),
|
||||
detail: None,
|
||||
},
|
||||
},
|
||||
),
|
||||
InputContent::InputFile { .. } => None, // Skip files for now
|
||||
InputContent::InputAudio { .. } => None, // Skip audio for now
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
converted_messages.push(Message {
|
||||
role,
|
||||
content,
|
||||
name: None,
|
||||
tool_call_id: None,
|
||||
tool_calls: None,
|
||||
});
|
||||
}
|
||||
// Skip non-message items (references, outputs) for now
|
||||
// These would need special handling in chat completions format
|
||||
_ => {}
|
||||
converted_messages.push(Message {
|
||||
role,
|
||||
content,
|
||||
name: None,
|
||||
tool_call_id: None,
|
||||
tool_calls: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -474,7 +472,7 @@ impl TryFrom<ChatCompletionsRequest> for AnthropicMessagesRequest {
|
|||
}
|
||||
|
||||
// Convert tools and tool choice
|
||||
let anthropic_tools = req.tools.map(|tools| convert_openai_tools(tools));
|
||||
let anthropic_tools = req.tools.map(convert_openai_tools);
|
||||
let anthropic_tool_choice =
|
||||
convert_openai_tool_choice(req.tool_choice, req.parallel_tool_calls);
|
||||
|
||||
|
|
|
|||
|
|
@ -13,18 +13,14 @@ use crate::apis::openai_responses::{
|
|||
pub fn convert_responses_output_to_input_items(output: &OutputItem) -> Option<InputItem> {
|
||||
match output {
|
||||
// Convert output messages to input messages
|
||||
OutputItem::Message {
|
||||
role, content, ..
|
||||
} => {
|
||||
OutputItem::Message { role, content, .. } => {
|
||||
let input_content: Vec<InputContent> = content
|
||||
.iter()
|
||||
.filter_map(|c| match c {
|
||||
OutputContent::OutputText { text, .. } => Some(InputContent::InputText {
|
||||
text: text.clone(),
|
||||
}),
|
||||
OutputContent::OutputAudio {
|
||||
data, ..
|
||||
} => Some(InputContent::InputAudio {
|
||||
OutputContent::OutputText { text, .. } => {
|
||||
Some(InputContent::InputText { text: text.clone() })
|
||||
}
|
||||
OutputContent::OutputAudio { data, .. } => Some(InputContent::InputAudio {
|
||||
data: data.clone(),
|
||||
format: None, // Format not preserved in output
|
||||
}),
|
||||
|
|
@ -84,7 +80,7 @@ pub fn outputs_to_inputs(outputs: &[OutputItem]) -> Vec<InputItem> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::apis::openai_responses::{OutputItemStatus};
|
||||
use crate::apis::openai_responses::OutputItemStatus;
|
||||
|
||||
#[test]
|
||||
fn test_output_message_to_input() {
|
||||
|
|
@ -135,14 +131,12 @@ mod tests {
|
|||
InputItem::Message(msg) => {
|
||||
assert!(matches!(msg.role, MessageRole::Assistant));
|
||||
match &msg.content {
|
||||
MessageContent::Items(items) => {
|
||||
match &items[0] {
|
||||
InputContent::InputText { text } => {
|
||||
assert!(text.contains("get_weather"));
|
||||
}
|
||||
_ => panic!("Expected InputText"),
|
||||
MessageContent::Items(items) => match &items[0] {
|
||||
InputContent::InputText { text } => {
|
||||
assert!(text.contains("get_weather"));
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected InputText"),
|
||||
},
|
||||
_ => panic!("Expected MessageContent::Items"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use crate::apis::amazon_bedrock::{ConverseOutput, ConverseResponse, StopReason};
|
||||
use crate::apis::anthropic::{
|
||||
MessagesContentBlock, MessagesResponse,
|
||||
MessagesRole, MessagesStopReason, MessagesUsage,
|
||||
MessagesContentBlock, MessagesResponse, MessagesRole, MessagesStopReason, MessagesUsage,
|
||||
};
|
||||
use crate::apis::openai::ChatCompletionsResponse;
|
||||
use crate::clients::TransformError;
|
||||
|
|
@ -115,7 +114,6 @@ impl TryFrom<ConverseResponse> for MessagesResponse {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// Convert Bedrock Message to Anthropic content blocks
|
||||
///
|
||||
/// This function handles the conversion between Amazon Bedrock Converse API format
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
use crate::apis::amazon_bedrock::{
|
||||
ConverseOutput, ConverseResponse, StopReason,
|
||||
};
|
||||
use crate::apis::anthropic::{
|
||||
MessagesContentBlock, MessagesResponse, MessagesUsage,
|
||||
};
|
||||
use crate::apis::amazon_bedrock::{ConverseOutput, ConverseResponse, StopReason};
|
||||
use crate::apis::anthropic::{MessagesContentBlock, MessagesResponse, MessagesUsage};
|
||||
use crate::apis::openai::{
|
||||
ChatCompletionsResponse, Choice, FinishReason, MessageContent, ResponseMessage, Role, Usage,
|
||||
};
|
||||
|
|
@ -16,12 +12,12 @@ use crate::transforms::lib::*;
|
|||
// ============================================================================
|
||||
|
||||
// Usage Conversions
|
||||
impl Into<Usage> for MessagesUsage {
|
||||
fn into(self) -> Usage {
|
||||
impl From<MessagesUsage> for Usage {
|
||||
fn from(val: MessagesUsage) -> Self {
|
||||
Usage {
|
||||
prompt_tokens: self.input_tokens,
|
||||
completion_tokens: self.output_tokens,
|
||||
total_tokens: self.input_tokens + self.output_tokens,
|
||||
prompt_tokens: val.input_tokens,
|
||||
completion_tokens: val.output_tokens,
|
||||
total_tokens: val.input_tokens + val.output_tokens,
|
||||
prompt_tokens_details: None,
|
||||
completion_tokens_details: None,
|
||||
}
|
||||
|
|
@ -203,7 +199,6 @@ impl TryFrom<ChatCompletionsResponse> for ResponsesAPIResponse {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
impl TryFrom<MessagesResponse> for ChatCompletionsResponse {
|
||||
type Error = TransformError;
|
||||
|
||||
|
|
@ -415,7 +410,6 @@ fn convert_anthropic_content_to_openai(
|
|||
Ok(MessageContent::Text(text_parts.join("\n")))
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -994,8 +988,15 @@ mod tests {
|
|||
let responses_api: ResponsesAPIResponse = chat_response.try_into().unwrap();
|
||||
|
||||
// Response ID should be generated with resp_ prefix
|
||||
assert!(responses_api.id.starts_with("resp_"), "Response ID should start with 'resp_'");
|
||||
assert_eq!(responses_api.id.len(), 37, "Response ID should be resp_ + 32 char UUID");
|
||||
assert!(
|
||||
responses_api.id.starts_with("resp_"),
|
||||
"Response ID should start with 'resp_'"
|
||||
);
|
||||
assert_eq!(
|
||||
responses_api.id.len(),
|
||||
37,
|
||||
"Response ID should be resp_ + 32 char UUID"
|
||||
);
|
||||
assert_eq!(responses_api.object, "response");
|
||||
assert_eq!(responses_api.model, "gpt-4");
|
||||
|
||||
|
|
@ -1008,11 +1009,7 @@ mod tests {
|
|||
// Check output items
|
||||
assert_eq!(responses_api.output.len(), 1);
|
||||
match &responses_api.output[0] {
|
||||
OutputItem::Message {
|
||||
role,
|
||||
content,
|
||||
..
|
||||
} => {
|
||||
OutputItem::Message { role, content, .. } => {
|
||||
assert_eq!(role, "assistant");
|
||||
assert_eq!(content.len(), 1);
|
||||
match &content[0] {
|
||||
|
|
@ -1163,6 +1160,9 @@ mod tests {
|
|||
}
|
||||
|
||||
// Verify status is Completed for tool_calls finish reason
|
||||
assert!(matches!(responses_api.status, crate::apis::openai_responses::ResponseStatus::Completed));
|
||||
assert!(matches!(
|
||||
responses_api.status,
|
||||
crate::apis::openai_responses::ResponseStatus::Completed
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
use crate::apis::amazon_bedrock::{
|
||||
ContentBlockDelta, ConverseStreamEvent,
|
||||
};
|
||||
use crate::apis::amazon_bedrock::{ContentBlockDelta, ConverseStreamEvent};
|
||||
use crate::apis::anthropic::{
|
||||
MessagesContentBlock, MessagesContentDelta, MessagesMessageDelta,
|
||||
MessagesRole, MessagesStopReason, MessagesStreamEvent, MessagesStreamMessage, MessagesUsage,
|
||||
};
|
||||
use crate::apis::openai::{ ChatCompletionsStreamResponse, ToolCallDelta,
|
||||
MessagesContentBlock, MessagesContentDelta, MessagesMessageDelta, MessagesRole,
|
||||
MessagesStopReason, MessagesStreamEvent, MessagesStreamMessage, MessagesUsage,
|
||||
};
|
||||
use crate::apis::openai::{ChatCompletionsStreamResponse, ToolCallDelta};
|
||||
use crate::clients::TransformError;
|
||||
use serde_json::Value;
|
||||
|
||||
|
|
@ -86,10 +83,10 @@ impl TryFrom<ChatCompletionsStreamResponse> for MessagesStreamEvent {
|
|||
}
|
||||
}
|
||||
|
||||
impl Into<String> for MessagesStreamEvent {
|
||||
fn into(self) -> String {
|
||||
let transformed_json = serde_json::to_string(&self).unwrap_or_default();
|
||||
let event_type = match &self {
|
||||
impl From<MessagesStreamEvent> for String {
|
||||
fn from(val: MessagesStreamEvent) -> Self {
|
||||
let transformed_json = serde_json::to_string(&val).unwrap_or_default();
|
||||
let event_type = match &val {
|
||||
MessagesStreamEvent::MessageStart { .. } => "message_start",
|
||||
MessagesStreamEvent::ContentBlockStart { .. } => "content_block_start",
|
||||
MessagesStreamEvent::ContentBlockDelta { .. } => "content_block_delta",
|
||||
|
|
@ -194,10 +191,18 @@ impl TryFrom<ConverseStreamEvent> for MessagesStreamEvent {
|
|||
let anthropic_stop_reason = match stop_event.stop_reason {
|
||||
crate::apis::amazon_bedrock::StopReason::EndTurn => MessagesStopReason::EndTurn,
|
||||
crate::apis::amazon_bedrock::StopReason::ToolUse => MessagesStopReason::ToolUse,
|
||||
crate::apis::amazon_bedrock::StopReason::MaxTokens => MessagesStopReason::MaxTokens,
|
||||
crate::apis::amazon_bedrock::StopReason::StopSequence => MessagesStopReason::EndTurn,
|
||||
crate::apis::amazon_bedrock::StopReason::GuardrailIntervened => MessagesStopReason::Refusal,
|
||||
crate::apis::amazon_bedrock::StopReason::ContentFiltered => MessagesStopReason::Refusal,
|
||||
crate::apis::amazon_bedrock::StopReason::MaxTokens => {
|
||||
MessagesStopReason::MaxTokens
|
||||
}
|
||||
crate::apis::amazon_bedrock::StopReason::StopSequence => {
|
||||
MessagesStopReason::EndTurn
|
||||
}
|
||||
crate::apis::amazon_bedrock::StopReason::GuardrailIntervened => {
|
||||
MessagesStopReason::Refusal
|
||||
}
|
||||
crate::apis::amazon_bedrock::StopReason::ContentFiltered => {
|
||||
MessagesStopReason::Refusal
|
||||
}
|
||||
};
|
||||
|
||||
Ok(MessagesStreamEvent::MessageDelta {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
use crate::apis::amazon_bedrock::{ ConverseStreamEvent, StopReason};
|
||||
use crate::apis::amazon_bedrock::{ConverseStreamEvent, StopReason};
|
||||
use crate::apis::anthropic::{
|
||||
MessagesContentBlock, MessagesContentDelta, MessagesStopReason, MessagesStreamEvent};
|
||||
use crate::apis::openai::{ ChatCompletionsStreamResponse,FinishReason,
|
||||
FunctionCallDelta, MessageDelta, Role, StreamChoice, ToolCallDelta, Usage,
|
||||
MessagesContentBlock, MessagesContentDelta, MessagesStopReason, MessagesStreamEvent,
|
||||
};
|
||||
use crate::apis::openai::{
|
||||
ChatCompletionsStreamResponse, FinishReason, FunctionCallDelta, MessageDelta, Role,
|
||||
StreamChoice, ToolCallDelta, Usage,
|
||||
};
|
||||
use crate::apis::openai_responses::ResponsesAPIStreamEvent;
|
||||
|
||||
|
|
@ -58,11 +60,14 @@ impl TryFrom<MessagesStreamEvent> for ChatCompletionsStreamResponse {
|
|||
None,
|
||||
)),
|
||||
|
||||
MessagesStreamEvent::ContentBlockStart { content_block, index } => {
|
||||
convert_content_block_start(content_block, index)
|
||||
}
|
||||
MessagesStreamEvent::ContentBlockStart {
|
||||
content_block,
|
||||
index,
|
||||
} => convert_content_block_start(content_block, index),
|
||||
|
||||
MessagesStreamEvent::ContentBlockDelta { delta, index } => convert_content_delta(delta, index),
|
||||
MessagesStreamEvent::ContentBlockDelta { delta, index } => {
|
||||
convert_content_delta(delta, index)
|
||||
}
|
||||
|
||||
MessagesStreamEvent::ContentBlockStop { .. } => Ok(create_empty_openai_chunk()),
|
||||
|
||||
|
|
@ -427,9 +432,9 @@ fn create_empty_openai_chunk() -> ChatCompletionsStreamResponse {
|
|||
}
|
||||
|
||||
// Stop Reason Conversions
|
||||
impl Into<FinishReason> for MessagesStopReason {
|
||||
fn into(self) -> FinishReason {
|
||||
match self {
|
||||
impl From<MessagesStopReason> for FinishReason {
|
||||
fn from(val: MessagesStopReason) -> Self {
|
||||
match val {
|
||||
MessagesStopReason::EndTurn => FinishReason::Stop,
|
||||
MessagesStopReason::MaxTokens => FinishReason::Length,
|
||||
MessagesStopReason::StopSequence => FinishReason::Stop,
|
||||
|
|
@ -456,34 +461,37 @@ impl TryFrom<ChatCompletionsStreamResponse> for ResponsesAPIStreamEvent {
|
|||
if let Some(tool_call) = tool_calls.first() {
|
||||
// Extract call_id and name if available (metadata from initial event)
|
||||
let call_id = tool_call.id.clone();
|
||||
let function_name = tool_call.function.as_ref()
|
||||
.and_then(|f| f.name.clone());
|
||||
let function_name = tool_call.function.as_ref().and_then(|f| f.name.clone());
|
||||
|
||||
// Check if we have function metadata (name, id)
|
||||
if let Some(function) = &tool_call.function {
|
||||
// If we have arguments delta, return that
|
||||
if let Some(args) = &function.arguments {
|
||||
return Ok(ResponsesAPIStreamEvent::ResponseFunctionCallArgumentsDelta {
|
||||
output_index: choice.index as i32,
|
||||
item_id: "".to_string(), // Buffer will fill this
|
||||
delta: args.clone(),
|
||||
sequence_number: 0, // Buffer will fill this
|
||||
call_id,
|
||||
name: function_name,
|
||||
});
|
||||
return Ok(
|
||||
ResponsesAPIStreamEvent::ResponseFunctionCallArgumentsDelta {
|
||||
output_index: choice.index as i32,
|
||||
item_id: "".to_string(), // Buffer will fill this
|
||||
delta: args.clone(),
|
||||
sequence_number: 0, // Buffer will fill this
|
||||
call_id,
|
||||
name: function_name,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// If we have function name but no arguments yet (initial tool call event)
|
||||
// Return an empty arguments delta so the buffer knows to create the item
|
||||
if function.name.is_some() {
|
||||
return Ok(ResponsesAPIStreamEvent::ResponseFunctionCallArgumentsDelta {
|
||||
output_index: choice.index as i32,
|
||||
item_id: "".to_string(), // Buffer will fill this
|
||||
delta: "".to_string(), // Empty delta signals this is the initial event
|
||||
sequence_number: 0, // Buffer will fill this
|
||||
call_id,
|
||||
name: function_name,
|
||||
});
|
||||
return Ok(
|
||||
ResponsesAPIStreamEvent::ResponseFunctionCallArgumentsDelta {
|
||||
output_index: choice.index as i32,
|
||||
item_id: "".to_string(), // Buffer will fill this
|
||||
delta: "".to_string(), // Empty delta signals this is the initial event
|
||||
sequence_number: 0, // Buffer will fill this
|
||||
call_id,
|
||||
name: function_name,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue