models.rs

  1use serde::{Deserialize, Serialize};
  2use strum::EnumIter;
  3
  4#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
  5#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
  6pub enum BedrockModelMode {
  7    #[default]
  8    Default,
  9    Thinking {
 10        budget_tokens: Option<u64>,
 11    },
 12}
 13
 14#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
 15#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
 16pub struct BedrockModelCacheConfiguration {
 17    pub max_cache_anchors: usize,
 18    pub min_total_token: u64,
 19}
 20
 21#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
 22#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, EnumIter)]
 23pub enum Model {
 24    // Anthropic models (already included)
 25    #[default]
 26    #[serde(rename = "claude-sonnet-4", alias = "claude-sonnet-4-latest")]
 27    ClaudeSonnet4,
 28    #[serde(
 29        rename = "claude-sonnet-4-thinking",
 30        alias = "claude-sonnet-4-thinking-latest"
 31    )]
 32    ClaudeSonnet4Thinking,
 33    #[serde(rename = "claude-opus-4", alias = "claude-opus-4-latest")]
 34    ClaudeOpus4,
 35    #[serde(
 36        rename = "claude-opus-4-thinking",
 37        alias = "claude-opus-4-thinking-latest"
 38    )]
 39    ClaudeOpus4Thinking,
 40    #[serde(rename = "claude-3-5-sonnet-v2", alias = "claude-3-5-sonnet-latest")]
 41    Claude3_5SonnetV2,
 42    #[serde(rename = "claude-3-7-sonnet", alias = "claude-3-7-sonnet-latest")]
 43    Claude3_7Sonnet,
 44    #[serde(
 45        rename = "claude-3-7-sonnet-thinking",
 46        alias = "claude-3-7-sonnet-thinking-latest"
 47    )]
 48    Claude3_7SonnetThinking,
 49    #[serde(rename = "claude-3-opus", alias = "claude-3-opus-latest")]
 50    Claude3Opus,
 51    #[serde(rename = "claude-3-sonnet", alias = "claude-3-sonnet-latest")]
 52    Claude3Sonnet,
 53    #[serde(rename = "claude-3-5-haiku", alias = "claude-3-5-haiku-latest")]
 54    Claude3_5Haiku,
 55    Claude3_5Sonnet,
 56    Claude3Haiku,
 57    // Amazon Nova Models
 58    AmazonNovaLite,
 59    AmazonNovaMicro,
 60    AmazonNovaPro,
 61    AmazonNovaPremier,
 62    // AI21 models
 63    AI21J2GrandeInstruct,
 64    AI21J2JumboInstruct,
 65    AI21J2Mid,
 66    AI21J2MidV1,
 67    AI21J2Ultra,
 68    AI21J2UltraV1_8k,
 69    AI21J2UltraV1,
 70    AI21JambaInstructV1,
 71    AI21Jamba15LargeV1,
 72    AI21Jamba15MiniV1,
 73    // Cohere models
 74    CohereCommandTextV14_4k,
 75    CohereCommandRV1,
 76    CohereCommandRPlusV1,
 77    CohereCommandLightTextV14_4k,
 78    // DeepSeek
 79    DeepSeekR1,
 80    // Meta models
 81    MetaLlama38BInstructV1,
 82    MetaLlama370BInstructV1,
 83    MetaLlama318BInstructV1_128k,
 84    MetaLlama318BInstructV1,
 85    MetaLlama3170BInstructV1_128k,
 86    MetaLlama3170BInstructV1,
 87    MetaLlama31405BInstructV1,
 88    MetaLlama321BInstructV1,
 89    MetaLlama323BInstructV1,
 90    MetaLlama3211BInstructV1,
 91    MetaLlama3290BInstructV1,
 92    MetaLlama3370BInstructV1,
 93    #[allow(non_camel_case_types)]
 94    MetaLlama4Scout17BInstructV1,
 95    #[allow(non_camel_case_types)]
 96    MetaLlama4Maverick17BInstructV1,
 97    // Mistral models
 98    MistralMistral7BInstructV0,
 99    MistralMixtral8x7BInstructV0,
100    MistralMistralLarge2402V1,
101    MistralMistralSmall2402V1,
102    MistralPixtralLarge2502V1,
103    // Writer models
104    PalmyraWriterX5,
105    PalmyraWriterX4,
106    #[serde(rename = "custom")]
107    Custom {
108        name: String,
109        max_tokens: u64,
110        /// The name displayed in the UI, such as in the assistant panel model dropdown menu.
111        display_name: Option<String>,
112        max_output_tokens: Option<u64>,
113        default_temperature: Option<f32>,
114        cache_configuration: Option<BedrockModelCacheConfiguration>,
115    },
116}
117
118impl Model {
119    pub fn default_fast(region: &str) -> Self {
120        if region.starts_with("us-") {
121            Self::Claude3_5Haiku
122        } else {
123            Self::Claude3Haiku
124        }
125    }
126
127    pub fn from_id(id: &str) -> anyhow::Result<Self> {
128        if id.starts_with("claude-3-5-sonnet-v2") {
129            Ok(Self::Claude3_5SonnetV2)
130        } else if id.starts_with("claude-3-opus") {
131            Ok(Self::Claude3Opus)
132        } else if id.starts_with("claude-3-sonnet") {
133            Ok(Self::Claude3Sonnet)
134        } else if id.starts_with("claude-3-5-haiku") {
135            Ok(Self::Claude3_5Haiku)
136        } else if id.starts_with("claude-3-7-sonnet") {
137            Ok(Self::Claude3_7Sonnet)
138        } else if id.starts_with("claude-3-7-sonnet-thinking") {
139            Ok(Self::Claude3_7SonnetThinking)
140        } else {
141            anyhow::bail!("invalid model id {id}");
142        }
143    }
144
145    pub fn id(&self) -> &str {
146        match self {
147            Model::ClaudeSonnet4 => "claude-4-sonnet",
148            Model::ClaudeSonnet4Thinking => "claude-4-sonnet-thinking",
149            Model::ClaudeOpus4 => "claude-4-opus",
150            Model::ClaudeOpus4Thinking => "claude-4-opus-thinking",
151            Model::Claude3_5SonnetV2 => "claude-3-5-sonnet-v2",
152            Model::Claude3_5Sonnet => "claude-3-5-sonnet",
153            Model::Claude3Opus => "claude-3-opus",
154            Model::Claude3Sonnet => "claude-3-sonnet",
155            Model::Claude3Haiku => "claude-3-haiku",
156            Model::Claude3_5Haiku => "claude-3-5-haiku",
157            Model::Claude3_7Sonnet => "claude-3-7-sonnet",
158            Model::Claude3_7SonnetThinking => "claude-3-7-sonnet-thinking",
159            Model::AmazonNovaLite => "amazon-nova-lite",
160            Model::AmazonNovaMicro => "amazon-nova-micro",
161            Model::AmazonNovaPro => "amazon-nova-pro",
162            Model::AmazonNovaPremier => "amazon-nova-premier",
163            Model::DeepSeekR1 => "deepseek-r1",
164            Model::AI21J2GrandeInstruct => "ai21-j2-grande-instruct",
165            Model::AI21J2JumboInstruct => "ai21-j2-jumbo-instruct",
166            Model::AI21J2Mid => "ai21-j2-mid",
167            Model::AI21J2MidV1 => "ai21-j2-mid-v1",
168            Model::AI21J2Ultra => "ai21-j2-ultra",
169            Model::AI21J2UltraV1_8k => "ai21-j2-ultra-v1-8k",
170            Model::AI21J2UltraV1 => "ai21-j2-ultra-v1",
171            Model::AI21JambaInstructV1 => "ai21-jamba-instruct-v1",
172            Model::AI21Jamba15LargeV1 => "ai21-jamba-1-5-large-v1",
173            Model::AI21Jamba15MiniV1 => "ai21-jamba-1-5-mini-v1",
174            Model::CohereCommandTextV14_4k => "cohere-command-text-v14-4k",
175            Model::CohereCommandRV1 => "cohere-command-r-v1",
176            Model::CohereCommandRPlusV1 => "cohere-command-r-plus-v1",
177            Model::CohereCommandLightTextV14_4k => "cohere-command-light-text-v14-4k",
178            Model::MetaLlama38BInstructV1 => "meta-llama3-8b-instruct-v1",
179            Model::MetaLlama370BInstructV1 => "meta-llama3-70b-instruct-v1",
180            Model::MetaLlama318BInstructV1_128k => "meta-llama3-1-8b-instruct-v1-128k",
181            Model::MetaLlama318BInstructV1 => "meta-llama3-1-8b-instruct-v1",
182            Model::MetaLlama3170BInstructV1_128k => "meta-llama3-1-70b-instruct-v1-128k",
183            Model::MetaLlama3170BInstructV1 => "meta-llama3-1-70b-instruct-v1",
184            Model::MetaLlama31405BInstructV1 => "meta-llama3-1-405b-instruct-v1",
185            Model::MetaLlama321BInstructV1 => "meta-llama3-2-1b-instruct-v1",
186            Model::MetaLlama323BInstructV1 => "meta-llama3-2-3b-instruct-v1",
187            Model::MetaLlama3211BInstructV1 => "meta-llama3-2-11b-instruct-v1",
188            Model::MetaLlama3290BInstructV1 => "meta-llama3-2-90b-instruct-v1",
189            Model::MetaLlama3370BInstructV1 => "meta-llama3-3-70b-instruct-v1",
190            Model::MetaLlama4Scout17BInstructV1 => "meta-llama4-scout-17b-instruct-v1",
191            Model::MetaLlama4Maverick17BInstructV1 => "meta-llama4-maverick-17b-instruct-v1",
192            Model::MistralMistral7BInstructV0 => "mistral-7b-instruct-v0",
193            Model::MistralMixtral8x7BInstructV0 => "mistral-mixtral-8x7b-instruct-v0",
194            Model::MistralMistralLarge2402V1 => "mistral-large-2402-v1",
195            Model::MistralMistralSmall2402V1 => "mistral-small-2402-v1",
196            Model::MistralPixtralLarge2502V1 => "mistral-pixtral-large-2502-v1",
197            Model::PalmyraWriterX4 => "palmyra-writer-x4",
198            Model::PalmyraWriterX5 => "palmyra-writer-x5",
199            Self::Custom { name, .. } => name,
200        }
201    }
202
203    pub fn request_id(&self) -> &str {
204        match self {
205            Model::ClaudeSonnet4 | Model::ClaudeSonnet4Thinking => {
206                "anthropic.claude-sonnet-4-20250514-v1:0"
207            }
208            Model::ClaudeOpus4 | Model::ClaudeOpus4Thinking => {
209                "anthropic.claude-opus-4-20250514-v1:0"
210            }
211            Model::Claude3_5SonnetV2 => "anthropic.claude-3-5-sonnet-20241022-v2:0",
212            Model::Claude3_5Sonnet => "anthropic.claude-3-5-sonnet-20240620-v1:0",
213            Model::Claude3Opus => "anthropic.claude-3-opus-20240229-v1:0",
214            Model::Claude3Sonnet => "anthropic.claude-3-sonnet-20240229-v1:0",
215            Model::Claude3Haiku => "anthropic.claude-3-haiku-20240307-v1:0",
216            Model::Claude3_5Haiku => "anthropic.claude-3-5-haiku-20241022-v1:0",
217            Model::Claude3_7Sonnet | Model::Claude3_7SonnetThinking => {
218                "anthropic.claude-3-7-sonnet-20250219-v1:0"
219            }
220            Model::AmazonNovaLite => "amazon.nova-lite-v1:0",
221            Model::AmazonNovaMicro => "amazon.nova-micro-v1:0",
222            Model::AmazonNovaPro => "amazon.nova-pro-v1:0",
223            Model::AmazonNovaPremier => "amazon.nova-premier-v1:0",
224            Model::DeepSeekR1 => "deepseek.r1-v1:0",
225            Model::AI21J2GrandeInstruct => "ai21.j2-grande-instruct",
226            Model::AI21J2JumboInstruct => "ai21.j2-jumbo-instruct",
227            Model::AI21J2Mid => "ai21.j2-mid",
228            Model::AI21J2MidV1 => "ai21.j2-mid-v1",
229            Model::AI21J2Ultra => "ai21.j2-ultra",
230            Model::AI21J2UltraV1_8k => "ai21.j2-ultra-v1:0:8k",
231            Model::AI21J2UltraV1 => "ai21.j2-ultra-v1",
232            Model::AI21JambaInstructV1 => "ai21.jamba-instruct-v1:0",
233            Model::AI21Jamba15LargeV1 => "ai21.jamba-1-5-large-v1:0",
234            Model::AI21Jamba15MiniV1 => "ai21.jamba-1-5-mini-v1:0",
235            Model::CohereCommandTextV14_4k => "cohere.command-text-v14:7:4k",
236            Model::CohereCommandRV1 => "cohere.command-r-v1:0",
237            Model::CohereCommandRPlusV1 => "cohere.command-r-plus-v1:0",
238            Model::CohereCommandLightTextV14_4k => "cohere.command-light-text-v14:7:4k",
239            Model::MetaLlama38BInstructV1 => "meta.llama3-8b-instruct-v1:0",
240            Model::MetaLlama370BInstructV1 => "meta.llama3-70b-instruct-v1:0",
241            Model::MetaLlama318BInstructV1_128k => "meta.llama3-1-8b-instruct-v1:0",
242            Model::MetaLlama318BInstructV1 => "meta.llama3-1-8b-instruct-v1:0",
243            Model::MetaLlama3170BInstructV1_128k => "meta.llama3-1-70b-instruct-v1:0",
244            Model::MetaLlama3170BInstructV1 => "meta.llama3-1-70b-instruct-v1:0",
245            Model::MetaLlama31405BInstructV1 => "meta.llama3-1-405b-instruct-v1:0",
246            Model::MetaLlama3211BInstructV1 => "meta.llama3-2-11b-instruct-v1:0",
247            Model::MetaLlama3290BInstructV1 => "meta.llama3-2-90b-instruct-v1:0",
248            Model::MetaLlama321BInstructV1 => "meta.llama3-2-1b-instruct-v1:0",
249            Model::MetaLlama323BInstructV1 => "meta.llama3-2-3b-instruct-v1:0",
250            Model::MetaLlama3370BInstructV1 => "meta.llama3-3-70b-instruct-v1:0",
251            Model::MetaLlama4Scout17BInstructV1 => "meta.llama4-scout-17b-instruct-v1:0",
252            Model::MetaLlama4Maverick17BInstructV1 => "meta.llama4-maverick-17b-instruct-v1:0",
253            Model::MistralMistral7BInstructV0 => "mistral.mistral-7b-instruct-v0:2",
254            Model::MistralMixtral8x7BInstructV0 => "mistral.mixtral-8x7b-instruct-v0:1",
255            Model::MistralMistralLarge2402V1 => "mistral.mistral-large-2402-v1:0",
256            Model::MistralMistralSmall2402V1 => "mistral.mistral-small-2402-v1:0",
257            Model::MistralPixtralLarge2502V1 => "mistral.pixtral-large-2502-v1:0",
258            Model::PalmyraWriterX4 => "writer.palmyra-x4-v1:0",
259            Model::PalmyraWriterX5 => "writer.palmyra-x5-v1:0",
260            Self::Custom { name, .. } => name,
261        }
262    }
263
264    pub fn display_name(&self) -> &str {
265        match self {
266            Self::ClaudeSonnet4 => "Claude Sonnet 4",
267            Self::ClaudeSonnet4Thinking => "Claude Sonnet 4 Thinking",
268            Self::ClaudeOpus4 => "Claude Opus 4",
269            Self::ClaudeOpus4Thinking => "Claude Opus 4 Thinking",
270            Self::Claude3_5SonnetV2 => "Claude 3.5 Sonnet v2",
271            Self::Claude3_5Sonnet => "Claude 3.5 Sonnet",
272            Self::Claude3Opus => "Claude 3 Opus",
273            Self::Claude3Sonnet => "Claude 3 Sonnet",
274            Self::Claude3Haiku => "Claude 3 Haiku",
275            Self::Claude3_5Haiku => "Claude 3.5 Haiku",
276            Self::Claude3_7Sonnet => "Claude 3.7 Sonnet",
277            Self::Claude3_7SonnetThinking => "Claude 3.7 Sonnet Thinking",
278            Self::AmazonNovaLite => "Amazon Nova Lite",
279            Self::AmazonNovaMicro => "Amazon Nova Micro",
280            Self::AmazonNovaPro => "Amazon Nova Pro",
281            Self::AmazonNovaPremier => "Amazon Nova Premier",
282            Self::DeepSeekR1 => "DeepSeek R1",
283            Self::AI21J2GrandeInstruct => "AI21 Jurassic2 Grande Instruct",
284            Self::AI21J2JumboInstruct => "AI21 Jurassic2 Jumbo Instruct",
285            Self::AI21J2Mid => "AI21 Jurassic2 Mid",
286            Self::AI21J2MidV1 => "AI21 Jurassic2 Mid V1",
287            Self::AI21J2Ultra => "AI21 Jurassic2 Ultra",
288            Self::AI21J2UltraV1_8k => "AI21 Jurassic2 Ultra V1 8K",
289            Self::AI21J2UltraV1 => "AI21 Jurassic2 Ultra V1",
290            Self::AI21JambaInstructV1 => "AI21 Jamba Instruct",
291            Self::AI21Jamba15LargeV1 => "AI21 Jamba 1.5 Large",
292            Self::AI21Jamba15MiniV1 => "AI21 Jamba 1.5 Mini",
293            Self::CohereCommandTextV14_4k => "Cohere Command Text V14 4K",
294            Self::CohereCommandRV1 => "Cohere Command R V1",
295            Self::CohereCommandRPlusV1 => "Cohere Command R Plus V1",
296            Self::CohereCommandLightTextV14_4k => "Cohere Command Light Text V14 4K",
297            Self::MetaLlama38BInstructV1 => "Meta Llama 3 8B Instruct",
298            Self::MetaLlama370BInstructV1 => "Meta Llama 3 70B Instruct",
299            Self::MetaLlama318BInstructV1_128k => "Meta Llama 3.1 8B Instruct 128K",
300            Self::MetaLlama318BInstructV1 => "Meta Llama 3.1 8B Instruct",
301            Self::MetaLlama3170BInstructV1_128k => "Meta Llama 3.1 70B Instruct 128K",
302            Self::MetaLlama3170BInstructV1 => "Meta Llama 3.1 70B Instruct",
303            Self::MetaLlama31405BInstructV1 => "Meta Llama 3.1 405B Instruct",
304            Self::MetaLlama3211BInstructV1 => "Meta Llama 3.2 11B Instruct",
305            Self::MetaLlama3290BInstructV1 => "Meta Llama 3.2 90B Instruct",
306            Self::MetaLlama321BInstructV1 => "Meta Llama 3.2 1B Instruct",
307            Self::MetaLlama323BInstructV1 => "Meta Llama 3.2 3B Instruct",
308            Self::MetaLlama3370BInstructV1 => "Meta Llama 3.3 70B Instruct",
309            Self::MetaLlama4Scout17BInstructV1 => "Meta Llama 4 Scout 17B Instruct",
310            Self::MetaLlama4Maverick17BInstructV1 => "Meta Llama 4 Maverick 17B Instruct",
311            Self::MistralMistral7BInstructV0 => "Mistral 7B Instruct V0",
312            Self::MistralMixtral8x7BInstructV0 => "Mistral Mixtral 8x7B Instruct V0",
313            Self::MistralMistralLarge2402V1 => "Mistral Large 2402 V1",
314            Self::MistralMistralSmall2402V1 => "Mistral Small 2402 V1",
315            Self::MistralPixtralLarge2502V1 => "Pixtral Large 25.02 V1",
316            Self::PalmyraWriterX5 => "Writer Palmyra X5",
317            Self::PalmyraWriterX4 => "Writer Palmyra X4",
318            Self::Custom {
319                display_name, name, ..
320            } => display_name.as_deref().unwrap_or(name),
321        }
322    }
323
324    pub fn max_token_count(&self) -> u64 {
325        match self {
326            Self::Claude3_5SonnetV2
327            | Self::Claude3Opus
328            | Self::Claude3Sonnet
329            | Self::Claude3_5Haiku
330            | Self::Claude3_7Sonnet
331            | Self::ClaudeSonnet4
332            | Self::ClaudeOpus4
333            | Self::ClaudeSonnet4Thinking
334            | Self::ClaudeOpus4Thinking => 200_000,
335            Self::AmazonNovaPremier => 1_000_000,
336            Self::PalmyraWriterX5 => 1_000_000,
337            Self::PalmyraWriterX4 => 128_000,
338            Self::Custom { max_tokens, .. } => *max_tokens,
339            _ => 128_000,
340        }
341    }
342
343    pub fn max_output_tokens(&self) -> u64 {
344        match self {
345            Self::Claude3Opus | Self::Claude3Sonnet | Self::Claude3_5Haiku => 4_096,
346            Self::Claude3_7Sonnet
347            | Self::Claude3_7SonnetThinking
348            | Self::ClaudeSonnet4
349            | Self::ClaudeSonnet4Thinking
350            | Self::ClaudeOpus4
351            | Model::ClaudeOpus4Thinking => 128_000,
352            Self::Claude3_5SonnetV2 | Self::PalmyraWriterX4 | Self::PalmyraWriterX5 => 8_192,
353            Self::Custom {
354                max_output_tokens, ..
355            } => max_output_tokens.unwrap_or(4_096),
356            _ => 4_096,
357        }
358    }
359
360    pub fn default_temperature(&self) -> f32 {
361        match self {
362            Self::Claude3_5SonnetV2
363            | Self::Claude3Opus
364            | Self::Claude3Sonnet
365            | Self::Claude3_5Haiku
366            | Self::Claude3_7Sonnet
367            | Self::ClaudeOpus4
368            | Self::ClaudeOpus4Thinking
369            | Self::ClaudeSonnet4
370            | Self::ClaudeSonnet4Thinking => 1.0,
371            Self::Custom {
372                default_temperature,
373                ..
374            } => default_temperature.unwrap_or(1.0),
375            _ => 1.0,
376        }
377    }
378
379    pub fn supports_tool_use(&self) -> bool {
380        match self {
381            // Anthropic Claude 3 models (all support tool use)
382            Self::Claude3Opus
383            | Self::Claude3Sonnet
384            | Self::Claude3_5Sonnet
385            | Self::Claude3_5SonnetV2
386            | Self::Claude3_7Sonnet
387            | Self::Claude3_7SonnetThinking
388            | Self::ClaudeOpus4
389            | Self::ClaudeOpus4Thinking
390            | Self::ClaudeSonnet4
391            | Self::ClaudeSonnet4Thinking
392            | Self::Claude3_5Haiku => true,
393
394            // Amazon Nova models (all support tool use)
395            Self::AmazonNovaPremier
396            | Self::AmazonNovaPro
397            | Self::AmazonNovaLite
398            | Self::AmazonNovaMicro => true,
399
400            // AI21 Jamba 1.5 models support tool use
401            Self::AI21Jamba15LargeV1 | Self::AI21Jamba15MiniV1 => true,
402
403            // Cohere Command R models support tool use
404            Self::CohereCommandRV1 | Self::CohereCommandRPlusV1 => true,
405
406            // All other models don't support tool use
407            // Including Meta Llama 3.2, AI21 Jurassic, and others
408            _ => false,
409        }
410    }
411
412    pub fn supports_caching(&self) -> bool {
413        match self {
414            // Only Claude models on Bedrock support caching
415            // Nova models support only text caching
416            // https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html#prompt-caching-models
417            Self::Claude3_5Haiku
418            | Self::Claude3_7Sonnet
419            | Self::Claude3_7SonnetThinking
420            | Self::ClaudeSonnet4
421            | Self::ClaudeSonnet4Thinking
422            | Self::ClaudeOpus4
423            | Self::ClaudeOpus4Thinking => true,
424
425            // Custom models - check if they have cache configuration
426            Self::Custom {
427                cache_configuration,
428                ..
429            } => cache_configuration.is_some(),
430
431            // All other models don't support caching
432            _ => false,
433        }
434    }
435
436    pub fn cache_configuration(&self) -> Option<BedrockModelCacheConfiguration> {
437        match self {
438            Self::Claude3_7Sonnet
439            | Self::Claude3_7SonnetThinking
440            | Self::ClaudeSonnet4
441            | Self::ClaudeSonnet4Thinking
442            | Self::ClaudeOpus4
443            | Self::ClaudeOpus4Thinking => Some(BedrockModelCacheConfiguration {
444                max_cache_anchors: 4,
445                min_total_token: 1024,
446            }),
447
448            Self::Claude3_5Haiku => Some(BedrockModelCacheConfiguration {
449                max_cache_anchors: 4,
450                min_total_token: 2048,
451            }),
452
453            Self::Custom {
454                cache_configuration,
455                ..
456            } => cache_configuration.clone(),
457
458            _ => None,
459        }
460    }
461
462    pub fn mode(&self) -> BedrockModelMode {
463        match self {
464            Model::Claude3_7SonnetThinking => BedrockModelMode::Thinking {
465                budget_tokens: Some(4096),
466            },
467            Model::ClaudeSonnet4Thinking => BedrockModelMode::Thinking {
468                budget_tokens: Some(4096),
469            },
470            Model::ClaudeOpus4Thinking => BedrockModelMode::Thinking {
471                budget_tokens: Some(4096),
472            },
473            _ => BedrockModelMode::Default,
474        }
475    }
476
477    pub fn cross_region_inference_id(&self, region: &str) -> anyhow::Result<String> {
478        let region_group = if region.starts_with("us-gov-") {
479            "us-gov"
480        } else if region.starts_with("us-") {
481            "us"
482        } else if region.starts_with("eu-") {
483            "eu"
484        } else if region.starts_with("ap-") || region == "me-central-1" || region == "me-south-1" {
485            "apac"
486        } else if region.starts_with("ca-") || region.starts_with("sa-") {
487            // Canada and South America regions - default to US profiles
488            "us"
489        } else {
490            anyhow::bail!("Unsupported Region {region}");
491        };
492
493        let model_id = self.request_id();
494
495        match (self, region_group) {
496            // Custom models can't have CRI IDs
497            (Model::Custom { .. }, _) => Ok(self.request_id().into()),
498
499            // Models with US Gov only
500            (Model::Claude3_5Sonnet, "us-gov") | (Model::Claude3Haiku, "us-gov") => {
501                Ok(format!("{}.{}", region_group, model_id))
502            }
503
504            // Available everywhere
505            (Model::AmazonNovaLite | Model::AmazonNovaMicro | Model::AmazonNovaPro, _) => {
506                Ok(format!("{}.{}", region_group, model_id))
507            }
508
509            // Models in US
510            (
511                Model::AmazonNovaPremier
512                | Model::Claude3_5Haiku
513                | Model::Claude3_5Sonnet
514                | Model::Claude3_5SonnetV2
515                | Model::Claude3_7Sonnet
516                | Model::Claude3_7SonnetThinking
517                | Model::ClaudeSonnet4
518                | Model::ClaudeSonnet4Thinking
519                | Model::ClaudeOpus4
520                | Model::ClaudeOpus4Thinking
521                | Model::Claude3Haiku
522                | Model::Claude3Opus
523                | Model::Claude3Sonnet
524                | Model::DeepSeekR1
525                | Model::MetaLlama31405BInstructV1
526                | Model::MetaLlama3170BInstructV1_128k
527                | Model::MetaLlama3170BInstructV1
528                | Model::MetaLlama318BInstructV1_128k
529                | Model::MetaLlama318BInstructV1
530                | Model::MetaLlama3211BInstructV1
531                | Model::MetaLlama321BInstructV1
532                | Model::MetaLlama323BInstructV1
533                | Model::MetaLlama3290BInstructV1
534                | Model::MetaLlama3370BInstructV1
535                | Model::MetaLlama4Maverick17BInstructV1
536                | Model::MetaLlama4Scout17BInstructV1
537                | Model::MistralPixtralLarge2502V1
538                | Model::PalmyraWriterX4
539                | Model::PalmyraWriterX5,
540                "us",
541            ) => Ok(format!("{}.{}", region_group, model_id)),
542
543            // Models available in EU
544            (
545                Model::Claude3_5Sonnet
546                | Model::Claude3_7Sonnet
547                | Model::Claude3_7SonnetThinking
548                | Model::ClaudeSonnet4
549                | Model::ClaudeSonnet4Thinking
550                | Model::Claude3Haiku
551                | Model::Claude3Sonnet
552                | Model::MetaLlama321BInstructV1
553                | Model::MetaLlama323BInstructV1
554                | Model::MistralPixtralLarge2502V1,
555                "eu",
556            ) => Ok(format!("{}.{}", region_group, model_id)),
557
558            // Models available in APAC
559            (
560                Model::Claude3_5Sonnet
561                | Model::Claude3_5SonnetV2
562                | Model::Claude3Haiku
563                | Model::Claude3Sonnet
564                | Model::Claude3_7Sonnet
565                | Model::Claude3_7SonnetThinking
566                | Model::ClaudeSonnet4
567                | Model::ClaudeSonnet4Thinking,
568                "apac",
569            ) => Ok(format!("{}.{}", region_group, model_id)),
570
571            // Any other combination is not supported
572            _ => Ok(self.request_id().into()),
573        }
574    }
575}
576
577#[cfg(test)]
578mod tests {
579    use super::*;
580
581    #[test]
582    fn test_us_region_inference_ids() -> anyhow::Result<()> {
583        // Test US regions
584        assert_eq!(
585            Model::Claude3_5SonnetV2.cross_region_inference_id("us-east-1")?,
586            "us.anthropic.claude-3-5-sonnet-20241022-v2:0"
587        );
588        assert_eq!(
589            Model::Claude3_5SonnetV2.cross_region_inference_id("us-west-2")?,
590            "us.anthropic.claude-3-5-sonnet-20241022-v2:0"
591        );
592        assert_eq!(
593            Model::AmazonNovaPro.cross_region_inference_id("us-east-2")?,
594            "us.amazon.nova-pro-v1:0"
595        );
596        Ok(())
597    }
598
599    #[test]
600    fn test_eu_region_inference_ids() -> anyhow::Result<()> {
601        // Test European regions
602        assert_eq!(
603            Model::ClaudeSonnet4.cross_region_inference_id("eu-west-1")?,
604            "eu.anthropic.claude-sonnet-4-20250514-v1:0"
605        );
606        assert_eq!(
607            Model::Claude3Sonnet.cross_region_inference_id("eu-west-1")?,
608            "eu.anthropic.claude-3-sonnet-20240229-v1:0"
609        );
610        assert_eq!(
611            Model::AmazonNovaMicro.cross_region_inference_id("eu-north-1")?,
612            "eu.amazon.nova-micro-v1:0"
613        );
614        Ok(())
615    }
616
617    #[test]
618    fn test_apac_region_inference_ids() -> anyhow::Result<()> {
619        // Test Asia-Pacific regions
620        assert_eq!(
621            Model::Claude3_5SonnetV2.cross_region_inference_id("ap-northeast-1")?,
622            "apac.anthropic.claude-3-5-sonnet-20241022-v2:0"
623        );
624        assert_eq!(
625            Model::Claude3_5SonnetV2.cross_region_inference_id("ap-southeast-2")?,
626            "apac.anthropic.claude-3-5-sonnet-20241022-v2:0"
627        );
628        assert_eq!(
629            Model::AmazonNovaLite.cross_region_inference_id("ap-south-1")?,
630            "apac.amazon.nova-lite-v1:0"
631        );
632        Ok(())
633    }
634
635    #[test]
636    fn test_gov_region_inference_ids() -> anyhow::Result<()> {
637        // Test Government regions
638        assert_eq!(
639            Model::Claude3_5Sonnet.cross_region_inference_id("us-gov-east-1")?,
640            "us-gov.anthropic.claude-3-5-sonnet-20240620-v1:0"
641        );
642        assert_eq!(
643            Model::Claude3Haiku.cross_region_inference_id("us-gov-west-1")?,
644            "us-gov.anthropic.claude-3-haiku-20240307-v1:0"
645        );
646        Ok(())
647    }
648
649    #[test]
650    fn test_meta_models_inference_ids() -> anyhow::Result<()> {
651        // Test Meta models
652        assert_eq!(
653            Model::MetaLlama370BInstructV1.cross_region_inference_id("us-east-1")?,
654            "meta.llama3-70b-instruct-v1:0"
655        );
656        assert_eq!(
657            Model::MetaLlama3170BInstructV1.cross_region_inference_id("us-east-1")?,
658            "us.meta.llama3-1-70b-instruct-v1:0"
659        );
660        assert_eq!(
661            Model::MetaLlama321BInstructV1.cross_region_inference_id("eu-west-1")?,
662            "eu.meta.llama3-2-1b-instruct-v1:0"
663        );
664        Ok(())
665    }
666
667    #[test]
668    fn test_mistral_models_inference_ids() -> anyhow::Result<()> {
669        // Mistral models don't follow the regional prefix pattern,
670        // so they should return their original IDs
671        assert_eq!(
672            Model::MistralMistralLarge2402V1.cross_region_inference_id("us-east-1")?,
673            "mistral.mistral-large-2402-v1:0"
674        );
675        assert_eq!(
676            Model::MistralMixtral8x7BInstructV0.cross_region_inference_id("eu-west-1")?,
677            "mistral.mixtral-8x7b-instruct-v0:1"
678        );
679        Ok(())
680    }
681
682    #[test]
683    fn test_ai21_models_inference_ids() -> anyhow::Result<()> {
684        // AI21 models don't follow the regional prefix pattern,
685        // so they should return their original IDs
686        assert_eq!(
687            Model::AI21J2UltraV1.cross_region_inference_id("us-east-1")?,
688            "ai21.j2-ultra-v1"
689        );
690        assert_eq!(
691            Model::AI21JambaInstructV1.cross_region_inference_id("eu-west-1")?,
692            "ai21.jamba-instruct-v1:0"
693        );
694        Ok(())
695    }
696
697    #[test]
698    fn test_cohere_models_inference_ids() -> anyhow::Result<()> {
699        // Cohere models don't follow the regional prefix pattern,
700        // so they should return their original IDs
701        assert_eq!(
702            Model::CohereCommandRV1.cross_region_inference_id("us-east-1")?,
703            "cohere.command-r-v1:0"
704        );
705        assert_eq!(
706            Model::CohereCommandTextV14_4k.cross_region_inference_id("ap-southeast-1")?,
707            "cohere.command-text-v14:7:4k"
708        );
709        Ok(())
710    }
711
712    #[test]
713    fn test_custom_model_inference_ids() -> anyhow::Result<()> {
714        // Test custom models
715        let custom_model = Model::Custom {
716            name: "custom.my-model-v1:0".to_string(),
717            max_tokens: 100000,
718            display_name: Some("My Custom Model".to_string()),
719            max_output_tokens: Some(8192),
720            default_temperature: Some(0.7),
721            cache_configuration: None,
722        };
723
724        // Custom model should return its name unchanged
725        assert_eq!(
726            custom_model.cross_region_inference_id("us-east-1")?,
727            "custom.my-model-v1:0"
728        );
729
730        Ok(())
731    }
732
733    #[test]
734    fn test_friendly_id_vs_request_id() {
735        // Test that id() returns friendly identifiers
736        assert_eq!(Model::Claude3_5SonnetV2.id(), "claude-3-5-sonnet-v2");
737        assert_eq!(Model::AmazonNovaLite.id(), "amazon-nova-lite");
738        assert_eq!(Model::DeepSeekR1.id(), "deepseek-r1");
739        assert_eq!(
740            Model::MetaLlama38BInstructV1.id(),
741            "meta-llama3-8b-instruct-v1"
742        );
743
744        // Test that request_id() returns actual backend model IDs
745        assert_eq!(
746            Model::Claude3_5SonnetV2.request_id(),
747            "anthropic.claude-3-5-sonnet-20241022-v2:0"
748        );
749        assert_eq!(Model::AmazonNovaLite.request_id(), "amazon.nova-lite-v1:0");
750        assert_eq!(Model::DeepSeekR1.request_id(), "deepseek.r1-v1:0");
751        assert_eq!(
752            Model::MetaLlama38BInstructV1.request_id(),
753            "meta.llama3-8b-instruct-v1:0"
754        );
755
756        // Test thinking models have different friendly IDs but same request IDs
757        assert_eq!(Model::ClaudeSonnet4.id(), "claude-4-sonnet");
758        assert_eq!(
759            Model::ClaudeSonnet4Thinking.id(),
760            "claude-4-sonnet-thinking"
761        );
762        assert_eq!(
763            Model::ClaudeSonnet4.request_id(),
764            Model::ClaudeSonnet4Thinking.request_id()
765        );
766    }
767}