use serde::Serialize;
use crate::llm_client::{
Request,
types::{ContentPart, Message, MessageContent, Role, ToolDefinition},
};
use super::AnthropicScheme;
#[derive(Debug, Serialize)]
pub(crate) struct AnthropicRequest {
pub model: String,
pub max_tokens: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub system: Option<String>,
pub messages: Vec<AnthropicMessage>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<AnthropicTool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_k: Option<u32>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub stop_sequences: Vec<String>,
pub stream: bool,
}
#[derive(Debug, Serialize)]
pub(crate) struct AnthropicMessage {
pub role: String,
pub content: AnthropicContent,
}
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub(crate) enum AnthropicContent {
Text(String),
Parts(Vec<AnthropicContentPart>),
}
#[derive(Debug, Serialize)]
#[serde(tag = "type")]
pub(crate) enum AnthropicContentPart {
#[serde(rename = "text")]
Text { text: String },
#[serde(rename = "tool_use")]
ToolUse {
id: String,
name: String,
input: serde_json::Value,
},
#[serde(rename = "tool_result")]
ToolResult {
tool_use_id: String,
content: String,
},
}
#[derive(Debug, Serialize)]
pub(crate) struct AnthropicTool {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub input_schema: serde_json::Value,
}
impl AnthropicScheme {
pub(crate) fn build_request(&self, model: &str, request: &Request) -> AnthropicRequest {
let messages = request
.messages
.iter()
.map(|m| self.convert_message(m))
.collect();
let tools = request.tools.iter().map(|t| self.convert_tool(t)).collect();
AnthropicRequest {
model: model.to_string(),
max_tokens: request.config.max_tokens.unwrap_or(4096),
system: request.system_prompt.clone(),
messages,
tools,
temperature: request.config.temperature,
top_p: request.config.top_p,
top_k: request.config.top_k,
stop_sequences: request.config.stop_sequences.clone(),
stream: true,
}
}
fn convert_message(&self, message: &Message) -> AnthropicMessage {
let role = match message.role {
Role::User => "user",
Role::Assistant => "assistant",
};
let content = match &message.content {
MessageContent::Text(text) => AnthropicContent::Text(text.clone()),
MessageContent::ToolResult {
tool_use_id,
content,
} => AnthropicContent::Parts(vec![AnthropicContentPart::ToolResult {
tool_use_id: tool_use_id.clone(),
content: content.clone(),
}]),
MessageContent::Parts(parts) => {
let converted: Vec<_> = parts
.iter()
.map(|p| match p {
ContentPart::Text { text } => {
AnthropicContentPart::Text { text: text.clone() }
}
ContentPart::ToolUse { id, name, input } => AnthropicContentPart::ToolUse {
id: id.clone(),
name: name.clone(),
input: input.clone(),
},
ContentPart::ToolResult {
tool_use_id,
content,
} => AnthropicContentPart::ToolResult {
tool_use_id: tool_use_id.clone(),
content: content.clone(),
},
})
.collect();
AnthropicContent::Parts(converted)
}
};
AnthropicMessage {
role: role.to_string(),
content,
}
}
fn convert_tool(&self, tool: &ToolDefinition) -> AnthropicTool {
AnthropicTool {
name: tool.name.clone(),
description: tool.description.clone(),
input_schema: tool.input_schema.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_simple_request() {
let scheme = AnthropicScheme::new();
let request = Request::new()
.system("You are a helpful assistant.")
.user("Hello!");
let anthropic_req = scheme.build_request("claude-sonnet-4-20250514", &request);
assert_eq!(anthropic_req.model, "claude-sonnet-4-20250514");
assert_eq!(
anthropic_req.system,
Some("You are a helpful assistant.".to_string())
);
assert_eq!(anthropic_req.messages.len(), 1);
assert!(anthropic_req.stream);
}
#[test]
fn test_build_request_with_tool() {
let scheme = AnthropicScheme::new();
let request = Request::new().user("What's the weather?").tool(
ToolDefinition::new("get_weather")
.description("Get current weather")
.input_schema(serde_json::json!({
"type": "object",
"properties": {
"location": { "type": "string" }
},
"required": ["location"]
})),
);
let anthropic_req = scheme.build_request("claude-sonnet-4-20250514", &request);
assert_eq!(anthropic_req.tools.len(), 1);
assert_eq!(anthropic_req.tools[0].name, "get_weather");
}
}