Skip to main content

walrus_model/claude/
request.rs

1//! Request body for the Anthropic Messages API.
2
3use serde::Serialize;
4use serde_json::{Value, json};
5use wcore::model::{Role, Tool, ToolChoice};
6
7/// The request body for the Anthropic Messages API.
8#[derive(Debug, Clone, Serialize)]
9pub struct Request {
10    /// The model identifier.
11    pub model: String,
12    /// Maximum tokens to generate.
13    pub max_tokens: usize,
14    /// System prompt (top-level, not in messages array).
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub system: Option<String>,
17    /// The messages array (Anthropic content block format).
18    pub messages: Vec<Value>,
19    /// Whether to stream the response.
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub stream: Option<bool>,
22    /// Tools the model may call.
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub tools: Option<Vec<Value>>,
25    /// Tool choice control.
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub tool_choice: Option<Value>,
28    /// Temperature.
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub temperature: Option<f64>,
31    /// Top-p sampling.
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub top_p: Option<f64>,
34}
35
36impl Request {
37    /// Enable streaming for the request.
38    pub fn stream(mut self) -> Self {
39        self.stream = Some(true);
40        self
41    }
42
43    /// Set the tools for the request.
44    fn with_tools(self, tools: Vec<Tool>) -> Self {
45        let tools = tools
46            .into_iter()
47            .map(|tool| {
48                json!({
49                    "name": tool.name,
50                    "description": tool.description,
51                    "input_schema": tool.parameters,
52                })
53            })
54            .collect::<Vec<_>>();
55        Self {
56            tools: Some(tools),
57            ..self
58        }
59    }
60
61    /// Set the tool choice for the request.
62    fn with_tool_choice(self, tool_choice: ToolChoice) -> Self {
63        Self {
64            tool_choice: match tool_choice {
65                ToolChoice::None => Some(json!({"type": "none"})),
66                ToolChoice::Auto => Some(json!({"type": "auto"})),
67                ToolChoice::Required => Some(json!({"type": "any"})),
68                ToolChoice::Function(name) => Some(json!({
69                    "type": "tool",
70                    "name": name,
71                })),
72            },
73            ..self
74        }
75    }
76}
77
78impl From<wcore::model::Request> for Request {
79    fn from(req: wcore::model::Request) -> Self {
80        let mut system = None;
81        let mut anthropic_msgs = Vec::new();
82
83        for msg in &req.messages {
84            match msg.role {
85                Role::System => {
86                    system = Some(msg.content.clone());
87                }
88                Role::User => {
89                    anthropic_msgs.push(json!({
90                        "role": "user",
91                        "content": msg.content,
92                    }));
93                }
94                Role::Assistant => {
95                    let mut content = Vec::new();
96                    if !msg.content.is_empty() {
97                        content.push(json!({
98                            "type": "text",
99                            "text": msg.content,
100                        }));
101                    }
102                    for tc in &msg.tool_calls {
103                        let input: Value =
104                            serde_json::from_str(&tc.function.arguments).unwrap_or(json!({}));
105                        content.push(json!({
106                            "type": "tool_use",
107                            "id": tc.id,
108                            "name": tc.function.name,
109                            "input": input,
110                        }));
111                    }
112                    if content.is_empty() {
113                        content.push(json!({
114                            "type": "text",
115                            "text": "",
116                        }));
117                    }
118                    anthropic_msgs.push(json!({
119                        "role": "assistant",
120                        "content": content,
121                    }));
122                }
123                Role::Tool => {
124                    anthropic_msgs.push(json!({
125                        "role": "user",
126                        "content": [{
127                            "type": "tool_result",
128                            "tool_use_id": msg.tool_call_id,
129                            "content": msg.content,
130                        }],
131                    }));
132                }
133            }
134        }
135
136        let mut result = Self {
137            model: req.model.to_string(),
138            max_tokens: 4096,
139            system,
140            messages: anthropic_msgs,
141            stream: None,
142            tools: None,
143            tool_choice: None,
144            temperature: None,
145            top_p: None,
146        };
147
148        if let Some(tools) = req.tools {
149            result = result.with_tools(tools);
150        }
151        if let Some(tool_choice) = req.tool_choice {
152            result = result.with_tool_choice(tool_choice);
153        }
154
155        result
156    }
157}