use serde::Serialize;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct ResponseTransformConfig {
pub strip_azure_metadata: bool,
pub normalize_field_names: bool,
pub include_content_filters: bool,
pub field_mappings: HashMap<String, String>,
pub response_format: ResponseFormat,
}
impl Default for ResponseTransformConfig {
fn default() -> Self {
Self {
strip_azure_metadata: false,
normalize_field_names: true,
include_content_filters: true,
field_mappings: HashMap::new(),
response_format: ResponseFormat::OpenAICompatible,
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum ResponseFormat {
Native,
OpenAICompatible,
Minimal,
}
pub struct AzureResponseTransformation {
config: ResponseTransformConfig,
}
impl AzureResponseTransformation {
pub fn new() -> Self {
Self {
config: ResponseTransformConfig::default(),
}
}
pub fn with_config(config: ResponseTransformConfig) -> Self {
Self { config }
}
pub fn transform_response<T: Serialize>(
&self,
response: T,
) -> Result<serde_json::Value, String> {
let json_response = serde_json::to_value(response)
.map_err(|e| format!("Failed to serialize response: {}", e))?;
match self.config.response_format {
ResponseFormat::Native => Ok(json_response),
ResponseFormat::OpenAICompatible => self.transform_to_openai_format(json_response),
ResponseFormat::Minimal => self.transform_to_minimal_format(json_response),
}
}
pub fn transform_chat_response(
&self,
mut response: serde_json::Value,
) -> Result<serde_json::Value, String> {
if let Some(choices) = response.get_mut("choices").and_then(|c| c.as_array_mut()) {
for choice in choices {
self.transform_choice_object(choice)?;
}
}
if let Some(usage) = response.get_mut("usage") {
self.transform_usage_object(usage)?;
}
self.apply_field_mappings(&mut response)?;
if !self.config.include_content_filters {
self.remove_content_filters(&mut response);
}
Ok(response)
}
pub fn transform_completion_response(
&self,
mut response: serde_json::Value,
) -> Result<serde_json::Value, String> {
if let Some(choices) = response.get_mut("choices").and_then(|c| c.as_array_mut()) {
for choice in choices {
self.transform_completion_choice(choice)?;
}
}
self.apply_field_mappings(&mut response)?;
if !self.config.include_content_filters {
self.remove_content_filters(&mut response);
}
Ok(response)
}
pub fn transform_embedding_response(
&self,
mut response: serde_json::Value,
) -> Result<serde_json::Value, String> {
self.apply_field_mappings(&mut response)?;
if !self.config.include_content_filters {
self.remove_content_filters(&mut response);
}
Ok(response)
}
fn transform_to_openai_format(
&self,
mut response: serde_json::Value,
) -> Result<serde_json::Value, String> {
if self.config.strip_azure_metadata {
self.strip_azure_fields(&mut response);
}
if self.config.normalize_field_names {
self.normalize_fields(&mut response)?;
}
Ok(response)
}
fn transform_to_minimal_format(
&self,
response: serde_json::Value,
) -> Result<serde_json::Value, String> {
let mut minimal = serde_json::json!({});
if let Some(choices) = response.get("choices") {
minimal["choices"] = choices.clone();
}
if let Some(data) = response.get("data") {
minimal["data"] = data.clone();
}
if let Some(usage) = response.get("usage") {
if let Some(total_tokens) = usage.get("total_tokens") {
minimal["usage"] = serde_json::json!({
"total_tokens": total_tokens
});
}
}
Ok(minimal)
}
fn transform_choice_object(&self, choice: &mut serde_json::Value) -> Result<(), String> {
if let Some(message) = choice.get_mut("message") {
self.transform_message_object(message)?;
}
if let Some(delta) = choice.get_mut("delta") {
self.transform_message_object(delta)?;
}
if let Some(finish_reason) = choice.get_mut("finish_reason") {
self.normalize_finish_reason(finish_reason)?;
}
Ok(())
}
fn transform_completion_choice(&self, choice: &mut serde_json::Value) -> Result<(), String> {
if let Some(finish_reason) = choice.get_mut("finish_reason") {
self.normalize_finish_reason(finish_reason)?;
}
Ok(())
}
fn transform_message_object(&self, message: &mut serde_json::Value) -> Result<(), String> {
if let Some(function_call) = message.get_mut("function_call") {
self.normalize_function_call(function_call)?;
}
if let Some(tool_calls) = message.get_mut("tool_calls") {
self.normalize_tool_calls(tool_calls)?;
}
Ok(())
}
fn transform_usage_object(&self, usage: &mut serde_json::Value) -> Result<(), String> {
if self.config.normalize_field_names {
if usage.get("prompt_tokens").is_none()
&& usage.get("input_tokens").is_some()
&& let Some(input_tokens) = usage.get("input_tokens").cloned()
{
usage["prompt_tokens"] = input_tokens;
if let Some(usage_obj) = usage.as_object_mut() {
usage_obj.remove("input_tokens");
}
}
if usage.get("completion_tokens").is_none()
&& usage.get("output_tokens").is_some()
&& let Some(output_tokens) = usage.get("output_tokens").cloned()
{
usage["completion_tokens"] = output_tokens;
if let Some(usage_obj) = usage.as_object_mut() {
usage_obj.remove("output_tokens");
}
}
}
Ok(())
}
fn normalize_finish_reason(&self, finish_reason: &mut serde_json::Value) -> Result<(), String> {
if let Some(reason_str) = finish_reason.as_str() {
let normalized = match reason_str {
"content_filter" => "content_filter",
"max_tokens" => "length",
"stop_sequence" => "stop",
_ => reason_str,
};
*finish_reason = serde_json::json!(normalized);
}
Ok(())
}
fn normalize_function_call(
&self,
_function_call: &mut serde_json::Value,
) -> Result<(), String> {
Ok(())
}
fn normalize_tool_calls(&self, _tool_calls: &mut serde_json::Value) -> Result<(), String> {
Ok(())
}
fn apply_field_mappings(&self, response: &mut serde_json::Value) -> Result<(), String> {
if self.config.field_mappings.is_empty() {
return Ok(());
}
for (from_field, to_field) in &self.config.field_mappings {
Self::rename_field_recursive(response, from_field, to_field);
}
Ok(())
}
fn rename_field_recursive(value: &mut serde_json::Value, from_field: &str, to_field: &str) {
match value {
serde_json::Value::Object(obj) => {
if let Some(field_value) = obj.remove(from_field) {
obj.insert(to_field.to_string(), field_value);
}
for (_, nested_value) in obj.iter_mut() {
Self::rename_field_recursive(nested_value, from_field, to_field);
}
}
serde_json::Value::Array(arr) => {
for item in arr.iter_mut() {
Self::rename_field_recursive(item, from_field, to_field);
}
}
_ => {}
}
}
fn remove_content_filters(&self, response: &mut serde_json::Value) {
Self::remove_field_recursive(response, "content_filter_results");
Self::remove_field_recursive(response, "prompt_filter_results");
}
fn remove_field_recursive(value: &mut serde_json::Value, field_name: &str) {
match value {
serde_json::Value::Object(obj) => {
obj.remove(field_name);
for (_, nested_value) in obj.iter_mut() {
Self::remove_field_recursive(nested_value, field_name);
}
}
serde_json::Value::Array(arr) => {
for item in arr.iter_mut() {
Self::remove_field_recursive(item, field_name);
}
}
_ => {}
}
}
fn strip_azure_fields(&self, response: &mut serde_json::Value) {
let azure_specific_fields = [
"deployment_id",
"azure_endpoint",
"content_filter_results",
"prompt_filter_results",
"region",
];
for field in &azure_specific_fields {
Self::remove_field_recursive(response, field);
}
}
fn normalize_fields(&self, response: &mut serde_json::Value) -> Result<(), String> {
let field_mappings = [
("input_tokens", "prompt_tokens"),
("output_tokens", "completion_tokens"),
];
for (from, to) in &field_mappings {
Self::rename_field_recursive(response, from, to);
}
Ok(())
}
}
impl Default for AzureResponseTransformation {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[path = "transformation_tests.rs"]
mod tests;