alith_interface/llms/api/anthropic/completion/
req.rs

1use crate::requests::completion::{error::CompletionError, request::CompletionRequest};
2use serde::{Deserialize, Serialize};
3
4#[derive(Clone, Serialize, Default, Debug, Deserialize)]
5pub struct AnthropicCompletionRequest {
6    /// ID of the model to use.
7    ///
8    /// See [models](https://docs.anthropic.com/claude/docs/models-overview) for additional details and options.
9    pub model: String,
10
11    /// Input messages.
12    ///
13    /// Our models are trained to operate on alternating user and assistant conversational turns. When creating a new Message, you specify the prior conversational turns with the messages parameter, and the model then generates the next Message in the conversation.
14    ///
15    /// See [examples](https://docs.anthropic.com/claude/reference/messages-examples) for more input examples.
16    ///
17    /// Note that if you want to include a [system prompt](https://docs.anthropic.com/claude/docs/system-prompts), you can use the top-level system parameter — there is no "system" role for input messages in the Messages API.
18    pub messages: Vec<CompletionRequestMessage>,
19
20    /// The maximum number of tokens to generate before stopping.
21    ///
22    /// Note that our models may stop before reaching this maximum. This parameter only specifies the absolute maximum number of tokens to generate.
23    ///
24    /// Different models have different maximum values for this parameter. See [models](https://docs.anthropic.com/claude/docs/models-overview) for details.
25    pub max_tokens: u64,
26
27    /// Custom text sequences that will cause the model to stop generating.
28    ///
29    /// Our models will normally stop when they have naturally completed their turn, which will result in a response stop_reason of "end_turn".
30    ///
31    /// If you want the model to stop generating when it encounters custom strings of text, you can use the stop_sequences parameter. If the model encounters one of the custom sequences, the response stop_reason value will be "stop_sequence" and the response stop_sequence value will contain the matched stop sequence.
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub stop_sequences: Option<Vec<String>>,
34
35    /// System prompt.
36    ///
37    /// A system prompt is a way of providing context and instructions to Claude, such as specifying a particular goal or role. See our [guide to system prompts](https://docs.anthropic.com/claude/docs/system-prompts).
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub system: Option<String>,
40
41    /// Amount of randomness injected into the response.
42    ///
43    /// Defaults to 0.5. Ranges from 0.0 to 1.0. Use temperature closer to 0.0 for analytical / multiple choice, and closer to 1.0 for creative and generative tasks.
44    ///
45    /// Note that even with temperature of 0.0, the results will not be fully deterministic.
46    pub temperature: f32,
47
48    /// min: 0.0, max: 1.0, default: None
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub top_p: Option<f32>,
51
52    /// The tools for the request, default: None
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub tools: Option<Vec<Tool>>,
55}
56
57impl AnthropicCompletionRequest {
58    pub fn new(req: &CompletionRequest) -> crate::Result<Self, CompletionError> {
59        let mut messages = Vec::new();
60        let mut system_prompt = None;
61        match req.prompt.get_built_prompt_messages() {
62            Ok(prompt_message) => {
63                for m in prompt_message {
64                    let role = m.get("role").ok_or_else(|| {
65                        CompletionError::RequestBuilderError("Role not found".to_string())
66                    })?;
67                    let content = m.get("content").ok_or_else(|| {
68                        CompletionError::RequestBuilderError("Content not found".to_string())
69                    })?;
70
71                    match role.as_str() {
72                        "user" | "assistant" => messages.push(CompletionRequestMessage {
73                            role: role.to_string(),
74                            content: content.to_string(),
75                        }),
76                        "system" => system_prompt = Some(content.to_string()),
77                        _ => {
78                            return Err(CompletionError::RequestBuilderError(format!(
79                                "Role {} not supported",
80                                role
81                            )));
82                        }
83                    }
84                }
85            }
86            Err(e) => {
87                return Err(CompletionError::RequestBuilderError(format!(
88                    "Error building prompt: {}",
89                    e
90                )));
91            }
92        }
93
94        let stop = req.stop_sequences.to_vec();
95        let stop_sequences = if stop.is_empty() { None } else { Some(stop) };
96
97        Ok(AnthropicCompletionRequest {
98            model: req.backend.model_id().to_owned(),
99            messages,
100            max_tokens: req.config.actual_request_tokens.unwrap(),
101            stop_sequences,
102            system: system_prompt,
103            temperature: temperature(req.config.temperature)?,
104            top_p: top_p(req.config.top_p)?,
105            tools: if !req.tools.is_empty() {
106                Some(
107                    req.tools
108                        .iter()
109                        .map(|t| Tool {
110                            name: t.name.clone(),
111                            description: t.description.clone(),
112                            input_schema: t.parameters.clone(),
113                        })
114                        .collect(),
115                )
116            } else {
117                None
118            },
119        })
120    }
121}
122
123/// Convert the native temperature from 0.0 to 2.0 to 0.0 to 1.0
124fn temperature(value: f32) -> crate::Result<f32, CompletionError> {
125    if (0.0..=2.0).contains(&value) {
126        Ok(value / 2.0)
127    } else {
128        Err(CompletionError::RequestBuilderError(
129            "Temperature must be between 0.0 and 2.0".to_string(),
130        ))
131    }
132}
133
134fn top_p(value: Option<f32>) -> crate::Result<Option<f32>, CompletionError> {
135    match value {
136        Some(v) => {
137            if (0.0..=1.0).contains(&v) {
138                Ok(Some(v))
139            } else {
140                Err(CompletionError::RequestBuilderError(
141                    "Top p must be between 0.0 and 1.0".to_string(),
142                ))
143            }
144        }
145        None => Ok(None),
146    }
147}
148
149#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
150pub struct CompletionRequestMessage {
151    pub role: String,
152    pub content: String,
153}
154
155#[derive(Clone, Serialize, Default, Debug, Deserialize)]
156pub struct Tool {
157    pub name: String,
158    pub description: String,
159    pub input_schema: serde_json::Value,
160}