Files
sglang/sgl-router/src/protocols/responses.rs

970 lines
29 KiB
Rust

// OpenAI Responses API types
// https://platform.openai.com/docs/api-reference/responses
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use validator::Validate;
// Import shared types from common module
use super::common::{
default_model, default_true, ChatLogProbs, Function, GenerationRequest, PromptTokenUsageInfo,
StringOrArray, ToolChoice, UsageInfo,
};
// ============================================================================
// Response Tools (MCP and others)
// ============================================================================
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ResponseTool {
#[serde(rename = "type")]
pub r#type: ResponseToolType,
// Function tool fields (used when type == "function")
// In Responses API, function fields are flattened at the top level
#[serde(flatten)]
#[serde(skip_serializing_if = "Option::is_none")]
pub function: Option<Function>,
// MCP-specific fields (used when type == "mcp")
#[serde(skip_serializing_if = "Option::is_none")]
pub server_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub authorization: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub server_label: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub server_description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub require_approval: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub allowed_tools: Option<Vec<String>>,
}
impl Default for ResponseTool {
fn default() -> Self {
Self {
r#type: ResponseToolType::WebSearchPreview,
function: None,
server_url: None,
authorization: None,
server_label: None,
server_description: None,
require_approval: None,
allowed_tools: None,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ResponseToolType {
Function,
WebSearchPreview,
CodeInterpreter,
Mcp,
}
// ============================================================================
// Reasoning Parameters
// ============================================================================
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ResponseReasoningParam {
#[serde(default = "default_reasoning_effort")]
#[serde(skip_serializing_if = "Option::is_none")]
pub effort: Option<ReasoningEffort>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<ReasoningSummary>,
}
fn default_reasoning_effort() -> Option<ReasoningEffort> {
Some(ReasoningEffort::Medium)
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ReasoningEffort {
Low,
Medium,
High,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ReasoningSummary {
Auto,
Concise,
Detailed,
}
// ============================================================================
// Input/Output Items
// ============================================================================
/// Content can be either a simple string or array of content parts (for SimpleInputMessage)
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(untagged)]
pub enum StringOrContentParts {
String(String),
Array(Vec<ResponseContentPart>),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum ResponseInputOutputItem {
#[serde(rename = "message")]
Message {
id: String,
role: String,
content: Vec<ResponseContentPart>,
#[serde(skip_serializing_if = "Option::is_none")]
status: Option<String>,
},
#[serde(rename = "reasoning")]
Reasoning {
id: String,
summary: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
content: Vec<ResponseReasoningContent>,
#[serde(skip_serializing_if = "Option::is_none")]
status: Option<String>,
},
#[serde(rename = "function_call")]
FunctionToolCall {
id: String,
call_id: String,
name: String,
arguments: String,
#[serde(skip_serializing_if = "Option::is_none")]
output: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
status: Option<String>,
},
#[serde(rename = "function_call_output")]
FunctionCallOutput {
id: Option<String>,
call_id: String,
output: String,
#[serde(skip_serializing_if = "Option::is_none")]
status: Option<String>,
},
#[serde(untagged)]
SimpleInputMessage {
content: StringOrContentParts,
role: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "type")]
r#type: Option<String>,
},
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum ResponseContentPart {
#[serde(rename = "output_text")]
OutputText {
text: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
annotations: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
logprobs: Option<ChatLogProbs>,
},
#[serde(rename = "input_text")]
InputText { text: String },
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum ResponseReasoningContent {
#[serde(rename = "reasoning_text")]
ReasoningText { text: String },
}
/// MCP Tool information for the mcp_list_tools output item
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct McpToolInfo {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub input_schema: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Value>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum ResponseOutputItem {
#[serde(rename = "message")]
Message {
id: String,
role: String,
content: Vec<ResponseContentPart>,
status: String,
},
#[serde(rename = "reasoning")]
Reasoning {
id: String,
summary: Vec<String>,
content: Vec<ResponseReasoningContent>,
#[serde(skip_serializing_if = "Option::is_none")]
status: Option<String>,
},
#[serde(rename = "function_call")]
FunctionToolCall {
id: String,
call_id: String,
name: String,
arguments: String,
#[serde(skip_serializing_if = "Option::is_none")]
output: Option<String>,
status: String,
},
#[serde(rename = "mcp_list_tools")]
McpListTools {
id: String,
server_label: String,
tools: Vec<McpToolInfo>,
},
#[serde(rename = "mcp_call")]
McpCall {
id: String,
status: String,
#[serde(skip_serializing_if = "Option::is_none")]
approval_request_id: Option<String>,
arguments: String,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<String>,
name: String,
output: String,
server_label: String,
},
}
// ============================================================================
// Configuration Enums
// ============================================================================
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ServiceTier {
Auto,
Default,
Flex,
Scale,
Priority,
}
impl Default for ServiceTier {
fn default() -> Self {
Self::Auto
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Truncation {
Auto,
Disabled,
}
impl Default for Truncation {
fn default() -> Self {
Self::Disabled
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ResponseStatus {
Queued,
InProgress,
Completed,
Failed,
Cancelled,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ReasoningInfo {
#[serde(skip_serializing_if = "Option::is_none")]
pub effort: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ResponseTextFormat {
pub format: TextFormatType,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct TextFormatType {
#[serde(rename = "type")]
pub format_type: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum IncludeField {
#[serde(rename = "code_interpreter_call.outputs")]
CodeInterpreterCallOutputs,
#[serde(rename = "computer_call_output.output.image_url")]
ComputerCallOutputImageUrl,
#[serde(rename = "file_search_call.results")]
FileSearchCallResults,
#[serde(rename = "message.input_image.image_url")]
MessageInputImageUrl,
#[serde(rename = "message.output_text.logprobs")]
MessageOutputTextLogprobs,
#[serde(rename = "reasoning.encrypted_content")]
ReasoningEncryptedContent,
}
// ============================================================================
// Usage Types (Responses API format)
// ============================================================================
/// OpenAI Responses API usage format (different from standard UsageInfo)
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ResponseUsage {
pub input_tokens: u32,
pub output_tokens: u32,
pub total_tokens: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_tokens_details: Option<InputTokensDetails>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_tokens_details: Option<OutputTokensDetails>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(untagged)]
pub enum ResponsesUsage {
Classic(UsageInfo),
Modern(ResponseUsage),
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct InputTokensDetails {
pub cached_tokens: u32,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct OutputTokensDetails {
pub reasoning_tokens: u32,
}
impl UsageInfo {
/// Convert to OpenAI Responses API format
pub fn to_response_usage(&self) -> ResponseUsage {
ResponseUsage {
input_tokens: self.prompt_tokens,
output_tokens: self.completion_tokens,
total_tokens: self.total_tokens,
input_tokens_details: self.prompt_tokens_details.as_ref().map(|details| {
InputTokensDetails {
cached_tokens: details.cached_tokens,
}
}),
output_tokens_details: self.reasoning_tokens.map(|tokens| OutputTokensDetails {
reasoning_tokens: tokens,
}),
}
}
}
impl From<UsageInfo> for ResponseUsage {
fn from(usage: UsageInfo) -> Self {
usage.to_response_usage()
}
}
impl ResponseUsage {
/// Convert back to standard UsageInfo format
pub fn to_usage_info(&self) -> UsageInfo {
UsageInfo {
prompt_tokens: self.input_tokens,
completion_tokens: self.output_tokens,
total_tokens: self.total_tokens,
reasoning_tokens: self
.output_tokens_details
.as_ref()
.map(|details| details.reasoning_tokens),
prompt_tokens_details: self.input_tokens_details.as_ref().map(|details| {
PromptTokenUsageInfo {
cached_tokens: details.cached_tokens,
}
}),
}
}
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
pub struct ResponsesGetParams {
#[serde(default)]
pub include: Vec<String>,
#[serde(default)]
pub include_obfuscation: Option<bool>,
#[serde(default)]
pub starting_after: Option<i64>,
#[serde(default)]
pub stream: Option<bool>,
}
impl ResponsesUsage {
pub fn to_response_usage(&self) -> ResponseUsage {
match self {
ResponsesUsage::Classic(usage) => usage.to_response_usage(),
ResponsesUsage::Modern(usage) => usage.clone(),
}
}
pub fn to_usage_info(&self) -> UsageInfo {
match self {
ResponsesUsage::Classic(usage) => usage.clone(),
ResponsesUsage::Modern(usage) => usage.to_usage_info(),
}
}
}
// ============================================================================
// Helper Functions for Defaults
// ============================================================================
fn default_top_k() -> i32 {
-1
}
fn default_repetition_penalty() -> f32 {
1.0
}
fn default_temperature() -> Option<f32> {
Some(1.0)
}
fn default_top_p() -> Option<f32> {
Some(1.0)
}
// ============================================================================
// Request/Response Types
// ============================================================================
#[derive(Debug, Clone, Deserialize, Serialize, Validate)]
pub struct ResponsesRequest {
/// Run the request in the background
#[serde(skip_serializing_if = "Option::is_none")]
pub background: Option<bool>,
/// Fields to include in the response
#[serde(skip_serializing_if = "Option::is_none")]
pub include: Option<Vec<IncludeField>>,
/// Input content - can be string or structured items
pub input: ResponseInput,
/// System instructions for the model
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
/// Maximum number of output tokens
#[serde(skip_serializing_if = "Option::is_none")]
pub max_output_tokens: Option<u32>,
/// Maximum number of tool calls
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tool_calls: Option<u32>,
/// Additional metadata
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, Value>>,
/// Model to use
#[serde(default = "default_model")]
pub model: String,
/// Optional conversation id to persist input/output as items
#[serde(skip_serializing_if = "Option::is_none")]
#[validate(custom(function = "validate_conversation_id"))]
pub conversation: Option<String>,
/// Whether to enable parallel tool calls
#[serde(skip_serializing_if = "Option::is_none")]
pub parallel_tool_calls: Option<bool>,
/// ID of previous response to continue from
#[serde(skip_serializing_if = "Option::is_none")]
pub previous_response_id: Option<String>,
/// Reasoning configuration
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<ResponseReasoningParam>,
/// Service tier
#[serde(skip_serializing_if = "Option::is_none")]
pub service_tier: Option<ServiceTier>,
/// Whether to store the response
#[serde(skip_serializing_if = "Option::is_none")]
pub store: Option<bool>,
/// Whether to stream the response
#[serde(default)]
pub stream: Option<bool>,
/// Temperature for sampling
#[serde(
default = "default_temperature",
skip_serializing_if = "Option::is_none"
)]
pub temperature: Option<f32>,
/// Tool choice behavior
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<ToolChoice>,
/// Available tools
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<ResponseTool>>,
/// Number of top logprobs to return
#[serde(skip_serializing_if = "Option::is_none")]
pub top_logprobs: Option<u32>,
/// Top-p sampling parameter
#[serde(default = "default_top_p", skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
/// Truncation behavior
#[serde(skip_serializing_if = "Option::is_none")]
pub truncation: Option<Truncation>,
/// User identifier
#[serde(skip_serializing_if = "Option::is_none")]
pub user: Option<String>,
/// Request ID
#[serde(skip_serializing_if = "Option::is_none")]
pub request_id: Option<String>,
/// Request priority
#[serde(default)]
pub priority: i32,
/// Frequency penalty
#[serde(skip_serializing_if = "Option::is_none")]
pub frequency_penalty: Option<f32>,
/// Presence penalty
#[serde(skip_serializing_if = "Option::is_none")]
pub presence_penalty: Option<f32>,
/// Stop sequences
#[serde(skip_serializing_if = "Option::is_none")]
pub stop: Option<StringOrArray>,
/// Top-k sampling parameter (SGLang extension)
#[serde(default = "default_top_k")]
pub top_k: i32,
/// Min-p sampling parameter (SGLang extension)
#[serde(default)]
pub min_p: f32,
/// Repetition penalty (SGLang extension)
#[serde(default = "default_repetition_penalty")]
pub repetition_penalty: f32,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(untagged)]
pub enum ResponseInput {
Items(Vec<ResponseInputOutputItem>),
Text(String),
}
impl Default for ResponsesRequest {
fn default() -> Self {
Self {
background: None,
include: None,
input: ResponseInput::Text(String::new()),
instructions: None,
max_output_tokens: None,
max_tool_calls: None,
metadata: None,
model: default_model(),
conversation: None,
parallel_tool_calls: None,
previous_response_id: None,
reasoning: None,
service_tier: None,
store: None,
stream: None,
temperature: None,
tool_choice: None,
tools: None,
top_logprobs: None,
top_p: None,
truncation: None,
user: None,
request_id: None,
priority: 0,
frequency_penalty: None,
presence_penalty: None,
stop: None,
top_k: default_top_k(),
min_p: 0.0,
repetition_penalty: default_repetition_penalty(),
}
}
}
impl GenerationRequest for ResponsesRequest {
fn is_stream(&self) -> bool {
self.stream.unwrap_or(false)
}
fn get_model(&self) -> Option<&str> {
Some(self.model.as_str())
}
fn extract_text_for_routing(&self) -> String {
match &self.input {
ResponseInput::Text(text) => text.clone(),
ResponseInput::Items(items) => items
.iter()
.filter_map(|item| match item {
ResponseInputOutputItem::Message { content, .. } => {
let texts: Vec<String> = content
.iter()
.filter_map(|part| match part {
ResponseContentPart::OutputText { text, .. } => Some(text.clone()),
ResponseContentPart::InputText { text } => Some(text.clone()),
ResponseContentPart::Unknown => None,
})
.collect();
if texts.is_empty() {
None
} else {
Some(texts.join(" "))
}
}
ResponseInputOutputItem::SimpleInputMessage { content, .. } => {
match content {
StringOrContentParts::String(s) => Some(s.clone()),
StringOrContentParts::Array(parts) => {
// SimpleInputMessage only supports InputText
let texts: Vec<String> = parts
.iter()
.filter_map(|part| match part {
ResponseContentPart::InputText { text } => {
Some(text.clone())
}
_ => None,
})
.collect();
if texts.is_empty() {
None
} else {
Some(texts.join(" "))
}
}
}
}
ResponseInputOutputItem::Reasoning { content, .. } => {
let texts: Vec<String> = content
.iter()
.map(|part| match part {
ResponseReasoningContent::ReasoningText { text } => text.clone(),
})
.collect();
if texts.is_empty() {
None
} else {
Some(texts.join(" "))
}
}
ResponseInputOutputItem::FunctionToolCall { arguments, .. } => {
Some(arguments.clone())
}
ResponseInputOutputItem::FunctionCallOutput { output, .. } => {
Some(output.clone())
}
})
.collect::<Vec<String>>()
.join(" "),
}
}
}
/// Validate conversation ID format
pub fn validate_conversation_id(conv_id: &str) -> Result<(), validator::ValidationError> {
if !conv_id.starts_with("conv_") {
let mut error = validator::ValidationError::new("invalid_conversation_id");
error.message = Some(std::borrow::Cow::Owned(format!(
"Invalid 'conversation': '{}'. Expected an ID that begins with 'conv_'.",
conv_id
)));
return Err(error);
}
// Check if the conversation ID contains only valid characters
let is_valid = conv_id
.chars()
.all(|c| c.is_alphanumeric() || c == '_' || c == '-');
if !is_valid {
let mut error = validator::ValidationError::new("invalid_conversation_id");
error.message = Some(std::borrow::Cow::Owned(format!(
"Invalid 'conversation': '{}'. Expected an ID that contains letters, numbers, underscores, or dashes, but this value contained additional characters.",
conv_id
)));
return Err(error);
}
Ok(())
}
/// Normalize a SimpleInputMessage to a proper Message item
///
/// This helper converts SimpleInputMessage (which can have flexible content)
/// into a fully-structured Message item with a generated ID, role, and content array.
///
/// SimpleInputMessage items are converted to Message items with IDs generated using
/// the centralized ID generation pattern with "msg_" prefix for consistency.
///
/// # Arguments
/// * `item` - The input item to normalize
///
/// # Returns
/// A normalized ResponseInputOutputItem (either Message if converted, or original if not SimpleInputMessage)
pub fn normalize_input_item(item: &ResponseInputOutputItem) -> ResponseInputOutputItem {
match item {
ResponseInputOutputItem::SimpleInputMessage { content, role, .. } => {
let content_vec = match content {
StringOrContentParts::String(s) => {
vec![ResponseContentPart::InputText { text: s.clone() }]
}
StringOrContentParts::Array(parts) => parts.clone(),
};
ResponseInputOutputItem::Message {
id: generate_id("msg"),
role: role.clone(),
content: content_vec,
status: Some("completed".to_string()),
}
}
_ => item.clone(),
}
}
pub fn generate_id(prefix: &str) -> String {
use rand::RngCore;
let mut rng = rand::rng();
// Generate exactly 50 hex characters (25 bytes) for the part after the underscore
let mut bytes = [0u8; 25];
rng.fill_bytes(&mut bytes);
let hex_string: String = bytes.iter().map(|b| format!("{:02x}", b)).collect();
format!("{}_{}", prefix, hex_string)
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ResponsesResponse {
/// Response ID
pub id: String,
/// Object type
#[serde(default = "default_object_type")]
pub object: String,
/// Creation timestamp
pub created_at: i64,
/// Response status
pub status: ResponseStatus,
/// Error information if status is failed
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<Value>,
/// Incomplete details if response was truncated
#[serde(skip_serializing_if = "Option::is_none")]
pub incomplete_details: Option<Value>,
/// System instructions used
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
/// Max output tokens setting
#[serde(skip_serializing_if = "Option::is_none")]
pub max_output_tokens: Option<u32>,
/// Model name
pub model: String,
/// Output items
#[serde(default)]
pub output: Vec<ResponseOutputItem>,
/// Whether parallel tool calls are enabled
#[serde(default = "default_true")]
pub parallel_tool_calls: bool,
/// Previous response ID if this is a continuation
#[serde(skip_serializing_if = "Option::is_none")]
pub previous_response_id: Option<String>,
/// Reasoning information
#[serde(skip_serializing_if = "Option::is_none")]
pub reasoning: Option<ReasoningInfo>,
/// Whether the response is stored
#[serde(default = "default_true")]
pub store: bool,
/// Temperature setting used
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
/// Text format settings
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<ResponseTextFormat>,
/// Tool choice setting
#[serde(default = "default_tool_choice")]
pub tool_choice: String,
/// Available tools
#[serde(default)]
pub tools: Vec<ResponseTool>,
/// Top-p setting used
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
/// Truncation strategy used
#[serde(skip_serializing_if = "Option::is_none")]
pub truncation: Option<String>,
/// Usage information
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<ResponsesUsage>,
/// User identifier
#[serde(skip_serializing_if = "Option::is_none")]
pub user: Option<String>,
/// Safety identifier for content moderation
#[serde(skip_serializing_if = "Option::is_none")]
pub safety_identifier: Option<String>,
/// Additional metadata
#[serde(default)]
pub metadata: HashMap<String, Value>,
}
fn default_object_type() -> String {
"response".to_string()
}
fn default_tool_choice() -> String {
"auto".to_string()
}
impl ResponsesResponse {
/// Check if the response is complete
pub fn is_complete(&self) -> bool {
matches!(self.status, ResponseStatus::Completed)
}
/// Check if the response is in progress
pub fn is_in_progress(&self) -> bool {
matches!(self.status, ResponseStatus::InProgress)
}
/// Check if the response failed
pub fn is_failed(&self) -> bool {
matches!(self.status, ResponseStatus::Failed)
}
}
impl ResponseOutputItem {
/// Create a new message output item
pub fn new_message(
id: String,
role: String,
content: Vec<ResponseContentPart>,
status: String,
) -> Self {
Self::Message {
id,
role,
content,
status,
}
}
/// Create a new reasoning output item
pub fn new_reasoning(
id: String,
summary: Vec<String>,
content: Vec<ResponseReasoningContent>,
status: Option<String>,
) -> Self {
Self::Reasoning {
id,
summary,
content,
status,
}
}
/// Create a new function tool call output item
pub fn new_function_tool_call(
id: String,
call_id: String,
name: String,
arguments: String,
output: Option<String>,
status: String,
) -> Self {
Self::FunctionToolCall {
id,
call_id,
name,
arguments,
output,
status,
}
}
}
impl ResponseContentPart {
/// Create a new text content part
pub fn new_text(
text: String,
annotations: Vec<String>,
logprobs: Option<ChatLogProbs>,
) -> Self {
Self::OutputText {
text,
annotations,
logprobs,
}
}
}
impl ResponseReasoningContent {
/// Create a new reasoning text content
pub fn new_reasoning_text(text: String) -> Self {
Self::ReasoningText { text }
}
}