fixing the messages construction and using the trait for signals

This commit is contained in:
Salman Paracha 2026-01-02 12:07:53 -08:00
parent 8d5e083ae9
commit 78f90234da
3 changed files with 160 additions and 198 deletions

View file

@ -112,7 +112,7 @@ pub async fn llm_chat(
.map(|msg| truncate_message(&msg, 50));
// Extract messages for signal analysis (clone before moving client_request)
let messages_for_signals = extract_messages_for_signals(&client_request);
let messages_for_signals = client_request.get_messages();
client_request.set_model(resolved_model.clone());
if client_request.remove_metadata_key("archgw_preference_config") {
@ -298,8 +298,8 @@ pub async fn llm_chat(
);
// Add messages for signal analysis if available
if let Some(messages) = messages_for_signals {
base_processor = base_processor.with_messages(messages);
if !messages_for_signals.is_empty() {
base_processor = base_processor.with_messages(messages_for_signals);
}
// === v1/responses state management: Wrap with ResponsesStateProcessor ===
@ -499,14 +499,3 @@ async fn get_provider_info(
(hermesllm::ProviderId::OpenAI, None)
}
}
/// Extract messages from ProviderRequestType for signal analysis
/// Returns None for non-ChatCompletions requests
fn extract_messages_for_signals(
request: &ProviderRequestType,
) -> Option<Vec<hermesllm::apis::openai::Message>> {
match request {
ProviderRequestType::ChatCompletionsRequest(chat_req) => Some(chat_req.messages.clone()),
_ => None,
}
}

View file

@ -1127,82 +1127,16 @@ impl ProviderRequest for ResponsesAPIRequest {
}
fn get_messages(&self) -> Vec<crate::apis::openai::Message> {
use crate::apis::openai::{Message, MessageContent, Role};
use crate::transforms::request::from_openai::ResponsesInputConverter;
let mut openai_messages = Vec::new();
// Use the shared converter to get the full conversion with image support
let converter = ResponsesInputConverter {
input: self.input.clone(),
instructions: self.instructions.clone(),
};
// Add instructions as system message if present
if let Some(instructions) = &self.instructions {
openai_messages.push(Message {
role: Role::System,
content: MessageContent::Text(instructions.clone()),
name: None,
tool_calls: None,
tool_call_id: None,
});
}
// Convert input to messages
match &self.input {
InputParam::Text(text) => {
openai_messages.push(Message {
role: Role::User,
content: MessageContent::Text(text.clone()),
name: None,
tool_calls: None,
tool_call_id: None,
});
}
InputParam::Items(items) => {
for item in items {
match item {
InputItem::Message(msg) => {
// Convert message role
let role = match msg.role {
MessageRole::User => Role::User,
MessageRole::Assistant => Role::Assistant,
MessageRole::System => Role::System,
MessageRole::Developer => Role::System, // Map developer to system
};
// Extract text from message content
let content = match &msg.content {
crate::apis::openai_responses::MessageContent::Text(text) => {
text.clone()
}
crate::apis::openai_responses::MessageContent::Items(items) => {
items
.iter()
.filter_map(|c| {
if let InputContent::InputText { text } = c {
Some(text.clone())
} else {
None
}
})
.collect::<Vec<_>>()
.join("\n")
}
};
openai_messages.push(Message {
role,
content: MessageContent::Text(content),
name: None,
tool_calls: None,
tool_call_id: None,
});
}
// Skip other input item types for now
InputItem::ItemReference { .. } | InputItem::FunctionCallOutput { .. } => {
// These are not yet supported in agent framework
}
}
}
}
}
openai_messages
// Convert and return, falling back to empty vec on error
converter.try_into().unwrap_or_else(|_| Vec::new())
}
fn set_messages(&mut self, messages: &[crate::apis::openai::Message]) {

View file

@ -24,6 +24,150 @@ use crate::transforms::*;
type AnthropicMessagesRequest = MessagesRequest;
// ============================================================================
// RESPONSES API INPUT CONVERSION
// ============================================================================
/// Helper struct for converting ResponsesAPI input to OpenAI messages
pub struct ResponsesInputConverter {
pub input: InputParam,
pub instructions: Option<String>,
}
impl TryFrom<ResponsesInputConverter> for Vec<Message> {
type Error = TransformError;
fn try_from(converter: ResponsesInputConverter) -> Result<Self, Self::Error> {
// Convert input to messages
match converter.input {
InputParam::Text(text) => {
// Simple text input becomes a user message
let mut messages = Vec::new();
// Add instructions as system message if present
if let Some(instructions) = converter.instructions {
messages.push(Message {
role: Role::System,
content: MessageContent::Text(instructions),
name: None,
tool_call_id: None,
tool_calls: None,
});
}
// Add the user message
messages.push(Message {
role: Role::User,
content: MessageContent::Text(text),
name: None,
tool_call_id: None,
tool_calls: None,
});
Ok(messages)
}
InputParam::Items(items) => {
// Convert input items to messages
let mut converted_messages = Vec::new();
// Add instructions as system message if present
if let Some(instructions) = converter.instructions {
converted_messages.push(Message {
role: Role::System,
content: MessageContent::Text(instructions),
name: None,
tool_call_id: None,
tool_calls: None,
});
}
// Convert each input item
for item in items {
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()
)
}
} 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,
});
}
}
Ok(converted_messages)
}
}
}
}
// ============================================================================
// MAIN REQUEST TRANSFORMATIONS
// ============================================================================
@ -253,117 +397,12 @@ 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) => {
// Simple text input becomes a user message
vec![Message {
role: Role::User,
content: MessageContent::Text(text),
name: None,
tool_call_id: None,
tool_calls: None,
}]
}
InputParam::Items(items) => {
// Convert input items to messages
let mut converted_messages = Vec::new();
// Add instructions as system message if present
if let Some(instructions) = &req.instructions {
converted_messages.push(Message {
role: Role::System,
content: MessageContent::Text(instructions.clone()),
name: None,
tool_call_id: None,
tool_calls: None,
});
}
// Convert each input item
for item in items {
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()
)
}
} 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,
});
}
}
converted_messages
}
// Convert input to messages using the shared converter
let converter = ResponsesInputConverter {
input: req.input,
instructions: req.instructions.clone(),
};
let messages: Vec<Message> = converter.try_into()?;
// Build the ChatCompletionsRequest
Ok(ChatCompletionsRequest {