1use serde::Deserialize;
20
21use crate::api::llm::LlmRequest;
22use crate::api::runtime::{BuiltinLlmCodec, LlmCodecIdentity};
23use crate::error::{FlowError, Result};
24use crate::json::Json;
25
26use super::request::{
27 AnnotatedLlmRequest, ApiSpecificRequest, ContentPart, FunctionDefinition, GenerationParams,
28 Message, MessageContent, ProviderNativeComponent, ToolChoice, ToolChoiceFunction,
29 ToolChoiceFunctionName, ToolDefinition,
30};
31use super::resolve::{ProviderSurface, ProviderSurfaceDescriptor};
32use super::response::{
33 AnnotatedLlmResponse, ApiSpecificResponse, FinishReason, RawUsageCost, ResponseToolCall, Usage,
34 estimate_cost_for_provider, infer_model_provider, provider_reported_cost,
35};
36use super::traits::{LlmCodec, LlmResponseCodec};
37
38pub struct AnthropicMessagesCodec;
44
45pub(crate) const PROVIDER_SURFACE: ProviderSurfaceDescriptor = ProviderSurfaceDescriptor {
46 surface: ProviderSurface::AnthropicMessages,
47 detect_request: |obj, hint| {
48 let hinted_anthropic = hint.is_some_and(|hint_value| {
51 hint_value == "anthropic" || hint_value == "anthropic.messages"
52 });
53 obj.contains_key("system") || (hinted_anthropic && obj.contains_key("messages"))
54 },
55 detect_response: |obj| {
56 obj.get("type").and_then(Json::as_str) == Some("message")
57 && obj.get("content").is_some_and(Json::is_array)
58 },
59 decode_request: |request| AnthropicMessagesCodec.decode(request),
60 decode_response: |raw| AnthropicMessagesCodec.decode_response(raw),
61 codec_name: "anthropic_messages",
62 request_codec: || std::sync::Arc::new(AnthropicMessagesCodec),
63 response_codec: || std::sync::Arc::new(AnthropicMessagesCodec),
64 streaming_codec: || Box::new(AnthropicMessagesStreamingCodec::new()),
65};
66
67#[derive(Deserialize)]
72struct RawAnthropicResponse {
73 id: Option<String>,
74 #[serde(rename = "type")]
75 object_type: Option<String>,
76 role: Option<String>,
77 model: Option<String>,
78 content: Option<Vec<Json>>,
79 stop_reason: Option<String>,
80 stop_sequence: Option<String>,
81 service_tier: Option<String>,
82 container: Option<Json>,
83 usage: Option<RawAnthropicUsage>,
84 #[serde(flatten)]
85 extra: serde_json::Map<String, Json>,
86}
87
88#[derive(Deserialize)]
89struct RawAnthropicUsage {
90 input_tokens: Option<u64>,
91 output_tokens: Option<u64>,
92 cache_read_input_tokens: Option<u64>,
93 cache_creation_input_tokens: Option<u64>,
94 #[serde(rename = "cost_usd")]
95 provider_cost: Option<f64>,
96 cost: Option<RawUsageCost>,
97}
98
99fn map_anthropic_stop_reason(reason: &str) -> FinishReason {
105 match reason {
106 "end_turn" => FinishReason::Complete,
107 "max_tokens" => FinishReason::Length,
108 "tool_use" => FinishReason::ToolUse,
109 other => FinishReason::Unknown(other.to_string()),
110 }
111}
112
113fn json_f64(v: f64) -> Json {
115 serde_json::Number::from_f64(v)
116 .map(Json::Number)
117 .unwrap_or(Json::Null)
118}
119
120const MODELED_REQUEST_KEYS: &[&str] = &[
122 "system",
123 "messages",
124 "model",
125 "max_tokens",
126 "temperature",
127 "top_p",
128 "stop_sequences",
129 "tools",
130 "tool_choice",
131 "metadata",
132 "service_tier",
133 "stream",
134 "cache_control",
135 "container",
136 "inference_geo",
137 "output_config",
138 "thinking",
139 "top_k",
140 "anthropic-user-profile-id",
141];
142
143fn decode_anthropic_tool_choice(val: &Json) -> Option<ToolChoice> {
151 let obj = val.as_object()?;
152 let tc_type = obj.get("type")?.as_str()?;
153 match tc_type {
154 "auto" => Some(ToolChoice::Auto),
155 "any" => Some(ToolChoice::Required),
156 "none" => Some(ToolChoice::None),
157 "tool" => {
158 let name = obj.get("name")?.as_str()?.to_string();
159 Some(ToolChoice::Specific(ToolChoiceFunction {
160 choice_type: "function".into(),
161 function: ToolChoiceFunctionName { name },
162 }))
163 }
164 _ => None,
165 }
166}
167
168fn decode_parallel_tool_calls(val: &Json) -> Result<Option<bool>> {
171 let Some(obj) = val.as_object() else {
172 return Ok(None);
173 };
174 Ok(super::optional_bool(
175 obj,
176 "disable_parallel_tool_use",
177 "Anthropic Messages tool_choice",
178 )?
179 .map(|disabled| !disabled))
180}
181
182fn encode_anthropic_tool_choice(tc: &ToolChoice) -> Result<Json> {
184 match tc {
185 ToolChoice::Auto => Ok(serde_json::json!({"type": "auto"})),
186 ToolChoice::Required => Ok(serde_json::json!({"type": "any"})),
187 ToolChoice::None => Ok(serde_json::json!({"type": "none"})),
188 ToolChoice::Specific(func) => {
189 Ok(serde_json::json!({"type": "tool", "name": func.function.name}))
190 }
191 ToolChoice::ProviderNative(native) if native.provider == "anthropic_messages" => {
192 Ok(native.value.clone())
193 }
194 ToolChoice::ProviderNative(native) => Err(FlowError::InvalidArgument(format!(
195 "tool choice for {} cannot be encoded for Anthropic Messages",
196 native.provider
197 ))),
198 }
199}
200
201fn encode_tool_choice_with_parallel_hint(
202 tc: &ToolChoice,
203 parallel_tool_calls: Option<bool>,
204) -> Result<Json> {
205 let mut value = encode_anthropic_tool_choice(tc)?;
206 if let (Some(parallel), Some(obj)) = (parallel_tool_calls, value.as_object_mut()) {
207 obj.insert("disable_parallel_tool_use".into(), Json::Bool(!parallel));
208 }
209 Ok(value)
210}
211
212fn native_component(provider: &str, value: &Json) -> ProviderNativeComponent {
213 ProviderNativeComponent {
214 provider: provider.to_string(),
215 kind: value
216 .get("type")
217 .and_then(Json::as_str)
218 .unwrap_or("unknown")
219 .to_string(),
220 value: value.clone(),
221 }
222}
223
224fn decode_anthropic_content(value: &Json) -> Result<MessageContent> {
225 if let Some(text) = value.as_str() {
226 return Ok(MessageContent::Text(text.to_string()));
227 }
228 let blocks = value.as_array().ok_or_else(|| {
229 FlowError::InvalidArgument("Anthropic Messages content must be a string or an array".into())
230 })?;
231 let parts = blocks
232 .iter()
233 .map(decode_anthropic_content_part)
234 .collect::<Result<Vec<_>>>()?;
235 Ok(MessageContent::Parts(parts))
236}
237
238fn decode_anthropic_content_part(value: &Json) -> Result<ContentPart> {
239 let obj = value.as_object().ok_or_else(|| {
240 FlowError::InvalidArgument("Anthropic Messages content block must be an object".into())
241 })?;
242 let kind = obj.get("type").and_then(Json::as_str).unwrap_or("unknown");
243 match kind {
244 "text" => {
245 let text = obj.get("text").and_then(Json::as_str).ok_or_else(|| {
246 FlowError::InvalidArgument("Anthropic text block is missing text".into())
247 })?;
248 Ok(ContentPart::Text {
249 text: text.to_string(),
250 extra: obj
251 .iter()
252 .filter(|(key, _)| !matches!(key.as_str(), "type" | "text"))
253 .map(|(key, value)| (key.clone(), value.clone()))
254 .collect(),
255 })
256 }
257 "image" => Ok(ContentPart::Image {
258 image: Json::Object(
259 obj.iter()
260 .filter(|(key, _)| key.as_str() != "type")
261 .map(|(key, value)| (key.clone(), value.clone()))
262 .collect(),
263 ),
264 extra: serde_json::Map::new(),
265 }),
266 "document" => Ok(ContentPart::File {
267 file: Json::Object(
268 obj.iter()
269 .filter(|(key, _)| key.as_str() != "type")
270 .map(|(key, value)| (key.clone(), value.clone()))
271 .collect(),
272 ),
273 extra: serde_json::Map::new(),
274 }),
275 "tool_use" => {
276 let id = obj.get("id").and_then(Json::as_str).ok_or_else(|| {
277 FlowError::InvalidArgument("Anthropic tool_use block is missing id".into())
278 })?;
279 let name = obj.get("name").and_then(Json::as_str).ok_or_else(|| {
280 FlowError::InvalidArgument("Anthropic tool_use block is missing name".into())
281 })?;
282 let input = obj.get("input").ok_or_else(|| {
283 FlowError::InvalidArgument("Anthropic tool_use block is missing input".into())
284 })?;
285 let extra = obj
286 .iter()
287 .filter(|(key, _)| !matches!(key.as_str(), "type" | "id" | "name" | "input"))
288 .map(|(key, value)| (key.clone(), value.clone()))
289 .collect();
290 Ok(ContentPart::ToolUse {
291 id: id.to_string(),
292 name: name.to_string(),
293 input: input.clone(),
294 extra,
295 })
296 }
297 "tool_result" => {
298 let tool_use_id = obj
299 .get("tool_use_id")
300 .and_then(Json::as_str)
301 .ok_or_else(|| {
302 FlowError::InvalidArgument(
303 "Anthropic tool_result block is missing tool_use_id".into(),
304 )
305 })?;
306 let content = obj.get("content").ok_or_else(|| {
307 FlowError::InvalidArgument("Anthropic tool_result block is missing content".into())
308 })?;
309 let is_error = match obj.get("is_error") {
310 Some(Json::Null) | None => None,
311 Some(value) => Some(value.as_bool().ok_or_else(|| {
312 FlowError::InvalidArgument(
313 "Anthropic tool_result is_error must be a boolean".into(),
314 )
315 })?),
316 };
317 let extra = obj
318 .iter()
319 .filter(|(key, _)| {
320 !matches!(
321 key.as_str(),
322 "type" | "tool_use_id" | "content" | "is_error"
323 )
324 })
325 .map(|(key, value)| (key.clone(), value.clone()))
326 .collect();
327 Ok(ContentPart::ToolResult {
328 tool_use_id: tool_use_id.to_string(),
329 content: content.clone(),
330 is_error,
331 extra,
332 })
333 }
334 _ => {
335 let native = native_component("anthropic_messages", value);
336 Ok(ContentPart::ProviderNative {
337 provider: native.provider,
338 kind: native.kind,
339 value: native.value,
340 })
341 }
342 }
343}
344
345fn decode_anthropic_message(value: &Json) -> Result<Message> {
346 let obj = value.as_object().ok_or_else(|| {
347 FlowError::InvalidArgument("Anthropic Messages message must be an object".into())
348 })?;
349 let role = obj.get("role").and_then(Json::as_str).ok_or_else(|| {
350 FlowError::InvalidArgument("Anthropic Messages message is missing role".into())
351 })?;
352 let content = obj.get("content").ok_or_else(|| {
353 FlowError::InvalidArgument("Anthropic Messages message is missing content".into())
354 })?;
355 if obj
356 .keys()
357 .any(|key| !matches!(key.as_str(), "role" | "content"))
358 {
359 return Ok(Message::ProviderNative {
360 provider: "anthropic_messages".into(),
361 kind: role.to_string(),
362 value: value.clone(),
363 });
364 }
365 let content = decode_anthropic_content(content)?;
366 match role {
367 "user" => Ok(Message::User {
368 content,
369 name: None,
370 }),
371 "assistant" => Ok(Message::Assistant {
372 content: Some(content),
373 tool_calls: None,
374 name: None,
375 }),
376 "system" => Ok(Message::System {
377 content,
378 name: None,
379 }),
380 _ => Ok(Message::ProviderNative {
381 provider: "anthropic_messages".into(),
382 kind: role.to_string(),
383 value: value.clone(),
384 }),
385 }
386}
387
388fn encode_anthropic_content(content: &MessageContent) -> Result<Json> {
389 match content {
390 MessageContent::Text(text) => Ok(Json::String(text.clone())),
391 MessageContent::Parts(parts) => Ok(Json::Array(
392 parts
393 .iter()
394 .map(encode_anthropic_content_part)
395 .collect::<Result<Vec<_>>>()?,
396 )),
397 }
398}
399
400fn encode_anthropic_content_part(part: &ContentPart) -> Result<Json> {
401 match part {
402 ContentPart::Text { text, extra } => {
403 let mut obj = extra.clone();
404 obj.insert("type".into(), Json::String("text".into()));
405 obj.insert("text".into(), Json::String(text.clone()));
406 Ok(Json::Object(obj))
407 }
408 ContentPart::Image { image, extra } => {
409 let mut obj = image.as_object().cloned().ok_or_else(|| {
410 FlowError::InvalidArgument("Anthropic image payload must be an object".into())
411 })?;
412 obj.extend(extra.clone());
413 obj.insert("type".into(), Json::String("image".into()));
414 Ok(Json::Object(obj))
415 }
416 ContentPart::File { file, extra } => {
417 let mut obj = file.as_object().cloned().ok_or_else(|| {
418 FlowError::InvalidArgument("Anthropic document payload must be an object".into())
419 })?;
420 obj.extend(extra.clone());
421 obj.insert("type".into(), Json::String("document".into()));
422 Ok(Json::Object(obj))
423 }
424 ContentPart::ToolUse {
425 id,
426 name,
427 input,
428 extra,
429 } => {
430 let mut obj = extra.clone();
431 obj.insert("type".into(), Json::String("tool_use".into()));
432 obj.insert("id".into(), Json::String(id.clone()));
433 obj.insert("name".into(), Json::String(name.clone()));
434 obj.insert("input".into(), input.clone());
435 Ok(Json::Object(obj))
436 }
437 ContentPart::ToolResult {
438 tool_use_id,
439 content,
440 is_error,
441 extra,
442 } => {
443 let mut obj = extra.clone();
444 obj.insert("type".into(), Json::String("tool_result".into()));
445 obj.insert("tool_use_id".into(), Json::String(tool_use_id.clone()));
446 obj.insert("content".into(), content.clone());
447 if let Some(is_error) = is_error {
448 obj.insert("is_error".into(), Json::Bool(*is_error));
449 }
450 Ok(Json::Object(obj))
451 }
452 ContentPart::ProviderNative {
453 provider, value, ..
454 } if provider == "anthropic_messages" => Ok(value.clone()),
455 other => Err(FlowError::InvalidArgument(format!(
456 "content part {other:?} cannot be encoded for Anthropic Messages"
457 ))),
458 }
459}
460
461fn encode_anthropic_message(message: &Message) -> Result<Json> {
462 match message {
463 Message::User { content, .. } | Message::System { content, .. } => {
464 let role = if matches!(message, Message::User { .. }) {
465 "user"
466 } else {
467 "system"
468 };
469 let mut obj = serde_json::Map::new();
470 obj.insert("role".into(), Json::String(role.into()));
471 obj.insert("content".into(), encode_anthropic_content(content)?);
472 Ok(Json::Object(obj))
473 }
474 Message::Assistant { content, .. } => {
475 let mut obj = serde_json::Map::new();
476 obj.insert("role".into(), Json::String("assistant".into()));
477 obj.insert(
478 "content".into(),
479 match content {
480 Some(content) => encode_anthropic_content(content)?,
481 None => Json::Array(Vec::new()),
482 },
483 );
484 Ok(Json::Object(obj))
485 }
486 Message::ProviderNative {
487 provider, value, ..
488 } if provider == "anthropic_messages" => Ok(value.clone()),
489 other => Err(FlowError::InvalidArgument(format!(
490 "message {other:?} cannot be encoded for Anthropic Messages"
491 ))),
492 }
493}
494
495fn encode_anthropic_tool(tool: &ToolDefinition) -> Result<Json> {
496 match tool {
497 ToolDefinition::Function { function, extra } => {
498 let mut obj = extra.clone();
499 obj.insert("name".into(), Json::String(function.name.clone()));
500 if let Some(description) = &function.description {
501 obj.insert("description".into(), Json::String(description.clone()));
502 }
503 if let Some(parameters) = &function.parameters {
504 obj.insert("input_schema".into(), parameters.clone());
505 }
506 if let Some(strict) = function.strict {
507 obj.insert("strict".into(), Json::Bool(strict));
508 }
509 obj.extend(function.extra.clone());
510 Ok(Json::Object(obj))
511 }
512 ToolDefinition::ProviderNative {
513 provider, value, ..
514 } if provider == "anthropic_messages" => Ok(value.clone()),
515 other => Err(FlowError::InvalidArgument(format!(
516 "tool {other:?} cannot be encoded for Anthropic Messages"
517 ))),
518 }
519}
520
521fn decode_anthropic_tool(value: &Json) -> Result<ToolDefinition> {
522 let obj = value.as_object().ok_or_else(|| {
523 FlowError::InvalidArgument("Anthropic Messages tool must be an object".into())
524 })?;
525 let is_client_tool =
526 obj.get("type").is_none() && obj.contains_key("name") && obj.contains_key("input_schema");
527 if !is_client_tool {
528 let native = native_component("anthropic_messages", value);
529 return Ok(ToolDefinition::ProviderNative {
530 provider: native.provider,
531 kind: native.kind,
532 value: native.value,
533 });
534 }
535
536 let function_extra = serde_json::Map::new();
537 let wrapper_extra = obj
538 .iter()
539 .filter(|(key, _)| {
540 !matches!(
541 key.as_str(),
542 "name" | "description" | "input_schema" | "strict"
543 )
544 })
545 .map(|(key, value)| (key.clone(), value.clone()))
546 .collect();
547 let name =
548 super::optional_string(obj, "name", "Anthropic Messages tool")?.ok_or_else(|| {
549 FlowError::InvalidArgument("Anthropic Messages tool is missing name".into())
550 })?;
551 let description = super::optional_string(obj, "description", "Anthropic Messages tool")?;
552 let strict = super::optional_bool(obj, "strict", "Anthropic Messages tool")?;
553 Ok(ToolDefinition::Function {
554 function: FunctionDefinition {
555 name,
556 description,
557 parameters: obj.get("input_schema").cloned(),
558 strict,
559 extra: function_extra,
560 },
561 extra: wrapper_extra,
562 })
563}
564
565fn patch_extra_fields(
566 obj: &mut serde_json::Map<String, Json>,
567 baseline: &serde_json::Map<String, Json>,
568 edited: &serde_json::Map<String, Json>,
569) {
570 for key in baseline.keys().filter(|key| !edited.contains_key(*key)) {
571 obj.remove(key);
572 }
573 for (key, value) in edited {
574 if baseline.get(key) != Some(value) {
575 obj.insert(key.clone(), value.clone());
576 }
577 }
578}
579
580fn set_or_remove_json(obj: &mut serde_json::Map<String, Json>, key: &str, value: Option<Json>) {
581 if let Some(value) = value {
582 obj.insert(key.into(), value);
583 } else {
584 obj.remove(key);
585 }
586}
587
588fn patch_anthropic_messages_and_model(
589 obj: &mut serde_json::Map<String, Json>,
590 annotated: &AnnotatedLlmRequest,
591 baseline: &AnnotatedLlmRequest,
592) -> Result<()> {
593 if annotated.messages != baseline.messages {
594 let original_messages = obj.get("messages").and_then(Json::as_array);
595 let messages = super::encode_changed_items(
596 &annotated.messages,
597 &baseline.messages,
598 original_messages.map(Vec::as_slice),
599 encode_anthropic_message,
600 )?;
601 obj.insert("messages".into(), Json::Array(messages));
602 }
603 if annotated.instructions != baseline.instructions {
604 set_or_remove_json(
605 obj,
606 "system",
607 annotated
608 .instructions
609 .as_ref()
610 .map(encode_anthropic_content)
611 .transpose()?,
612 );
613 }
614 if annotated.model != baseline.model {
615 set_or_remove_json(obj, "model", annotated.model.clone().map(Json::String));
616 }
617 Ok(())
618}
619
620fn patch_anthropic_params(
621 obj: &mut serde_json::Map<String, Json>,
622 annotated: &AnnotatedLlmRequest,
623 baseline: &AnnotatedLlmRequest,
624) {
625 if annotated.params == baseline.params {
626 return;
627 }
628 let edited = annotated.params.as_ref();
629 let before = baseline.params.as_ref();
630 for (key, value, old_value) in [
631 (
632 "temperature",
633 edited.and_then(|params| params.temperature),
634 before.and_then(|params| params.temperature),
635 ),
636 (
637 "top_p",
638 edited.and_then(|params| params.top_p),
639 before.and_then(|params| params.top_p),
640 ),
641 ] {
642 if value != old_value {
643 set_or_remove_json(obj, key, value.map(json_f64));
644 }
645 }
646 let max_tokens = edited.and_then(|params| params.max_tokens);
647 if max_tokens != before.and_then(|params| params.max_tokens) {
648 set_or_remove_json(obj, "max_tokens", max_tokens.map(Json::from));
649 }
650 let stop = edited.and_then(|params| params.stop.as_ref());
651 if stop != before.and_then(|params| params.stop.as_ref()) {
652 set_or_remove_json(
653 obj,
654 "stop_sequences",
655 stop.map(|values| serde_json::json!(values)),
656 );
657 }
658}
659
660fn patch_anthropic_tools(
661 obj: &mut serde_json::Map<String, Json>,
662 annotated: &AnnotatedLlmRequest,
663 baseline: &AnnotatedLlmRequest,
664) -> Result<()> {
665 if annotated.tools != baseline.tools {
666 let tools = annotated
667 .tools
668 .as_deref()
669 .map(|tools| {
670 super::encode_changed_items(
671 tools,
672 baseline.tools.as_deref().unwrap_or(&[]),
673 obj.get("tools").and_then(Json::as_array).map(Vec::as_slice),
674 encode_anthropic_tool,
675 )
676 })
677 .transpose()?
678 .map(Json::Array);
679 set_or_remove_json(obj, "tools", tools);
680 }
681 if annotated.tool_choice != baseline.tool_choice
682 || annotated.parallel_tool_calls != baseline.parallel_tool_calls
683 {
684 let tool_choice = match (&annotated.tool_choice, &baseline.tool_choice) {
685 (Some(edited), Some(before)) => {
686 let edited =
687 encode_tool_choice_with_parallel_hint(edited, annotated.parallel_tool_calls)?;
688 let before =
689 encode_tool_choice_with_parallel_hint(before, baseline.parallel_tool_calls)?;
690 Some(match obj.get("tool_choice") {
691 Some(original) => super::patch_changed_json(original, &before, &edited)?,
692 None => edited,
693 })
694 }
695 (Some(edited), None) => Some(encode_tool_choice_with_parallel_hint(
696 edited,
697 annotated.parallel_tool_calls,
698 )?),
699 (None, _) => annotated
700 .parallel_tool_calls
701 .map(|parallel| {
702 encode_tool_choice_with_parallel_hint(&ToolChoice::Auto, Some(parallel))
703 })
704 .transpose()?,
705 };
706 set_or_remove_json(obj, "tool_choice", tool_choice);
707 }
708 Ok(())
709}
710
711fn patch_anthropic_common_fields(
712 obj: &mut serde_json::Map<String, Json>,
713 annotated: &AnnotatedLlmRequest,
714 baseline: &AnnotatedLlmRequest,
715) {
716 if annotated.metadata != baseline.metadata {
717 set_or_remove_json(obj, "metadata", annotated.metadata.clone());
718 }
719 if annotated.service_tier != baseline.service_tier {
720 set_or_remove_json(
721 obj,
722 "service_tier",
723 annotated.service_tier.clone().map(Json::String),
724 );
725 }
726 if annotated.stream != baseline.stream {
727 set_or_remove_json(obj, "stream", annotated.stream.map(Json::Bool));
728 }
729}
730
731fn validate_anthropic_supported_fields(
732 annotated: &AnnotatedLlmRequest,
733 baseline: &AnnotatedLlmRequest,
734) -> Result<()> {
735 let unsupported = [
736 annotated.store != baseline.store,
737 annotated.previous_response_id != baseline.previous_response_id,
738 annotated.truncation != baseline.truncation,
739 annotated.reasoning != baseline.reasoning,
740 annotated.include != baseline.include,
741 annotated.user != baseline.user,
742 annotated.max_output_tokens != baseline.max_output_tokens,
743 annotated.max_tool_calls != baseline.max_tool_calls,
744 annotated.top_logprobs != baseline.top_logprobs,
745 ]
746 .into_iter()
747 .any(|changed| changed);
748 if unsupported {
749 return Err(FlowError::InvalidArgument(
750 "request contains fields that cannot be encoded for Anthropic Messages".into(),
751 ));
752 }
753 Ok(())
754}
755
756fn patch_anthropic_api_specific(
757 obj: &mut serde_json::Map<String, Json>,
758 edited: &Option<ApiSpecificRequest>,
759 baseline: &Option<ApiSpecificRequest>,
760) -> Result<()> {
761 match (edited, baseline) {
762 (
763 Some(ApiSpecificRequest::AnthropicMessages {
764 cache_control,
765 container,
766 inference_geo,
767 output_config,
768 thinking,
769 top_k,
770 user_profile_id,
771 }),
772 Some(ApiSpecificRequest::AnthropicMessages {
773 cache_control: old_cache_control,
774 container: old_container,
775 inference_geo: old_inference_geo,
776 output_config: old_output_config,
777 thinking: old_thinking,
778 top_k: old_top_k,
779 user_profile_id: old_user_profile_id,
780 }),
781 ) => {
782 if cache_control != old_cache_control {
783 set_or_remove_json(obj, "cache_control", cache_control.clone());
784 }
785 patch_anthropic_api_strings(
786 obj,
787 &[
788 ("container", container, old_container),
789 ("inference_geo", inference_geo, old_inference_geo),
790 (
791 "anthropic-user-profile-id",
792 user_profile_id,
793 old_user_profile_id,
794 ),
795 ],
796 );
797 patch_anthropic_api_json(
798 obj,
799 &[
800 ("output_config", output_config, old_output_config),
801 ("thinking", thinking, old_thinking),
802 ],
803 );
804 if top_k != old_top_k {
805 set_or_remove_json(obj, "top_k", top_k.map(Json::from));
806 }
807 Ok(())
808 }
809 (None, Some(ApiSpecificRequest::AnthropicMessages { .. })) => {
810 for key in [
811 "cache_control",
812 "container",
813 "inference_geo",
814 "output_config",
815 "thinking",
816 "top_k",
817 "anthropic-user-profile-id",
818 ] {
819 obj.remove(key);
820 }
821 Ok(())
822 }
823 (Some(_), _) | (None, Some(_)) => Err(FlowError::InvalidArgument(
824 "api_specific provider does not match Anthropic Messages".into(),
825 )),
826 (None, None) => Ok(()),
827 }
828}
829
830fn patch_anthropic_api_strings(
831 obj: &mut serde_json::Map<String, Json>,
832 fields: &[(&str, &Option<String>, &Option<String>)],
833) {
834 for (key, edited, before) in fields {
835 if edited != before {
836 set_or_remove_json(obj, key, (*edited).clone().map(Json::String));
837 }
838 }
839}
840
841fn patch_anthropic_api_json(
842 obj: &mut serde_json::Map<String, Json>,
843 fields: &[(&str, &Option<Json>, &Option<Json>)],
844) {
845 for (key, edited, before) in fields {
846 if edited != before {
847 set_or_remove_json(obj, key, (*edited).clone());
848 }
849 }
850}
851
852fn anthropic_text_message(content_blocks: Option<&[Json]>) -> Option<MessageContent> {
853 let text_parts: Vec<&str> = content_blocks
854 .map(|blocks| blocks.iter().filter_map(anthropic_text_block).collect())
855 .unwrap_or_default();
856
857 (!text_parts.is_empty()).then(|| MessageContent::Text(text_parts.join("\n")))
858}
859
860fn anthropic_text_block(block: &Json) -> Option<&str> {
861 if block.get("type")?.as_str()? != "text" {
862 return None;
863 }
864 block.get("text")?.as_str()
865}
866
867fn anthropic_tool_calls(content_blocks: Option<&[Json]>) -> Option<Vec<ResponseToolCall>> {
868 let tool_calls: Vec<ResponseToolCall> = content_blocks
869 .map(|blocks| {
870 blocks
871 .iter()
872 .filter_map(anthropic_tool_call_block)
873 .collect()
874 })
875 .unwrap_or_default();
876
877 (!tool_calls.is_empty()).then_some(tool_calls)
878}
879
880fn anthropic_tool_call_block(block: &Json) -> Option<ResponseToolCall> {
881 if block.get("type")?.as_str()? != "tool_use" {
882 return None;
883 }
884 Some(ResponseToolCall {
885 id: block.get("id")?.as_str()?.to_string(),
886 name: block.get("name")?.as_str()?.to_string(),
887 arguments: block.get("input")?.clone(),
889 })
890}
891
892fn anthropic_usage(
893 raw_usage: Option<RawAnthropicUsage>,
894 model_for_pricing: Option<&str>,
895) -> Option<Usage> {
896 let model_provider = infer_model_provider("anthropic", model_for_pricing);
897 raw_usage.map(|u| {
898 let prompt = u.input_tokens;
899 let completion = u.output_tokens;
900 let mut usage = Usage {
901 prompt_tokens: prompt,
902 completion_tokens: completion,
903 total_tokens: match (prompt, completion) {
905 (Some(p), Some(c)) => Some(p + c),
906 _ => None,
907 },
908 cache_read_tokens: u.cache_read_input_tokens,
909 cache_write_tokens: u.cache_creation_input_tokens,
910 cost: provider_reported_cost(u.provider_cost, u.cost),
911 };
912 if usage.cost.is_none() {
913 usage.cost = model_for_pricing.and_then(|model| {
914 estimate_cost_for_provider(model_provider.as_deref(), model, &usage)
915 });
916 }
917 usage
918 })
919}
920
921impl LlmResponseCodec for AnthropicMessagesCodec {
926 fn codec_identity(&self) -> LlmCodecIdentity {
927 LlmCodecIdentity::BuiltIn(BuiltinLlmCodec::AnthropicMessages)
928 }
929
930 fn decode_response(&self, response: &Json) -> Result<AnnotatedLlmResponse> {
931 let raw: RawAnthropicResponse = serde_json::from_value(response.clone())
932 .map_err(|e| FlowError::Internal(format!("Anthropic Messages response decode: {e}")))?;
933
934 let content_blocks = raw.content.as_deref();
935 let message = anthropic_text_message(content_blocks);
936 let tool_calls = anthropic_tool_calls(content_blocks);
938
939 let finish_reason = raw.stop_reason.as_deref().map(map_anthropic_stop_reason);
941
942 let usage = anthropic_usage(raw.usage, raw.model.as_deref());
944
945 let api_specific_content_blocks = raw.content.clone();
947 let api_specific = Some(ApiSpecificResponse::AnthropicMessages {
948 object_type: raw.object_type,
949 role: raw.role,
950 stop_reason: raw.stop_reason,
951 stop_sequence: raw.stop_sequence,
952 service_tier: raw.service_tier,
953 container: raw.container,
954 content_blocks: api_specific_content_blocks,
955 });
956
957 Ok(AnnotatedLlmResponse {
958 id: raw.id,
959 model: raw.model,
960 message,
961 tool_calls,
962 finish_reason,
963 usage,
964 optimization_summary: None,
965 api_specific,
966 extra: raw.extra,
967 })
968 }
969}
970
971impl LlmCodec for AnthropicMessagesCodec {
976 fn codec_identity(&self) -> LlmCodecIdentity {
977 LlmCodecIdentity::BuiltIn(BuiltinLlmCodec::AnthropicMessages)
978 }
979
980 fn decode(&self, request: &LlmRequest) -> Result<AnnotatedLlmRequest> {
981 let obj = request
982 .content
983 .as_object()
984 .ok_or_else(|| FlowError::Internal("request content is not an object".into()))?;
985 let raw_messages = obj.get("messages").ok_or_else(|| {
986 FlowError::InvalidArgument("Anthropic Messages request is missing messages".into())
987 })?;
988 let messages = raw_messages
989 .as_array()
990 .ok_or_else(|| {
991 FlowError::InvalidArgument("Anthropic Messages messages must be an array".into())
992 })?
993 .iter()
994 .map(decode_anthropic_message)
995 .collect::<Result<Vec<_>>>()?;
996 let instructions = obj
997 .get("system")
998 .map(decode_anthropic_content)
999 .transpose()?;
1000 let model = super::optional_string(obj, "model", "Anthropic Messages")?;
1001 let temperature = super::optional_f64(obj, "temperature", "Anthropic Messages")?;
1002 let top_p = super::optional_f64(obj, "top_p", "Anthropic Messages")?;
1003 let max_tokens = super::optional_u64(obj, "max_tokens", "Anthropic Messages")?;
1004 let stop = obj
1005 .get("stop_sequences")
1006 .filter(|value| !value.is_null())
1007 .map(|value| {
1008 serde_json::from_value::<Vec<String>>(value.clone()).map_err(|error| {
1009 FlowError::InvalidArgument(format!(
1010 "invalid Anthropic stop_sequences value: {error}"
1011 ))
1012 })
1013 })
1014 .transpose()?;
1015 let params =
1016 if temperature.is_some() || max_tokens.is_some() || top_p.is_some() || stop.is_some() {
1017 Some(GenerationParams {
1018 temperature,
1019 max_tokens,
1020 top_p,
1021 stop,
1022 })
1023 } else {
1024 None
1025 };
1026 let tools = obj
1027 .get("tools")
1028 .map(|value| {
1029 value
1030 .as_array()
1031 .ok_or_else(|| {
1032 FlowError::InvalidArgument(
1033 "Anthropic Messages tools must be an array".into(),
1034 )
1035 })?
1036 .iter()
1037 .map(decode_anthropic_tool)
1038 .collect::<Result<Vec<_>>>()
1039 })
1040 .transpose()?;
1041 let tool_choice = obj.get("tool_choice").map(|value| {
1042 decode_anthropic_tool_choice(value).unwrap_or_else(|| {
1043 ToolChoice::ProviderNative(native_component("anthropic_messages", value))
1044 })
1045 });
1046 let parallel_tool_calls = obj
1047 .get("tool_choice")
1048 .map(decode_parallel_tool_calls)
1049 .transpose()?
1050 .flatten();
1051 let service_tier = super::optional_string(obj, "service_tier", "Anthropic Messages")?;
1052 let stream = super::optional_bool(obj, "stream", "Anthropic Messages")?;
1053 let container = super::optional_string(obj, "container", "Anthropic Messages")?;
1054 let inference_geo = super::optional_string(obj, "inference_geo", "Anthropic Messages")?;
1055 let top_k = super::optional_u64(obj, "top_k", "Anthropic Messages")?;
1056 let user_profile_id =
1057 super::optional_string(obj, "anthropic-user-profile-id", "Anthropic Messages")?;
1058 let metadata = super::optional_object(obj, "metadata", "Anthropic Messages")?;
1059 let cache_control = super::optional_object(obj, "cache_control", "Anthropic Messages")?;
1060 let output_config = super::optional_object(obj, "output_config", "Anthropic Messages")?;
1061 let thinking = super::optional_object(obj, "thinking", "Anthropic Messages")?;
1062 let extra: serde_json::Map<String, Json> = obj
1063 .iter()
1064 .filter(|(k, _)| !MODELED_REQUEST_KEYS.contains(&k.as_str()))
1065 .map(|(k, v)| (k.clone(), v.clone()))
1066 .collect();
1067 Ok(AnnotatedLlmRequest {
1068 messages,
1069 instructions,
1070 model,
1071 params,
1072 tools,
1073 tool_choice,
1074 store: None,
1075 previous_response_id: None,
1076 truncation: None,
1077 reasoning: None,
1078 include: None,
1079 user: None,
1080 metadata,
1081 service_tier,
1082 parallel_tool_calls,
1083 max_output_tokens: None,
1084 max_tool_calls: None,
1085 top_logprobs: None,
1086 stream,
1087 api_specific: Some(ApiSpecificRequest::AnthropicMessages {
1088 cache_control,
1089 container,
1090 inference_geo,
1091 output_config,
1092 thinking,
1093 top_k,
1094 user_profile_id,
1095 }),
1096 extra,
1097 })
1098 }
1099
1100 fn encode(&self, annotated: &AnnotatedLlmRequest, original: &LlmRequest) -> Result<LlmRequest> {
1101 let baseline = self.decode(original)?;
1102 let mut content = original.content.clone();
1103 let obj = content
1104 .as_object_mut()
1105 .ok_or_else(|| FlowError::Internal("original content is not an object".into()))?;
1106 patch_anthropic_messages_and_model(obj, annotated, &baseline)?;
1107 patch_anthropic_params(obj, annotated, &baseline);
1108 patch_anthropic_tools(obj, annotated, &baseline)?;
1109 patch_anthropic_common_fields(obj, annotated, &baseline);
1110 validate_anthropic_supported_fields(annotated, &baseline)?;
1111 patch_anthropic_api_specific(obj, &annotated.api_specific, &baseline.api_specific)?;
1112 patch_extra_fields(obj, &baseline.extra, &annotated.extra);
1113
1114 Ok(LlmRequest {
1115 headers: original.headers.clone(),
1116 content,
1117 })
1118 }
1119}
1120
1121pub struct AnthropicMessagesStreamingCodec {
1140 state: std::sync::Arc<std::sync::Mutex<AnthropicMessagesStreamingState>>,
1141}
1142
1143impl AnthropicMessagesStreamingCodec {
1144 pub fn new() -> Self {
1146 Self {
1147 state: std::sync::Arc::new(std::sync::Mutex::new(
1148 AnthropicMessagesStreamingState::default(),
1149 )),
1150 }
1151 }
1152}
1153
1154impl Default for AnthropicMessagesStreamingCodec {
1155 fn default() -> Self {
1156 Self::new()
1157 }
1158}
1159
1160impl super::streaming::StreamingCodec for AnthropicMessagesStreamingCodec {
1161 fn collector(&self) -> crate::api::runtime::LlmCollectorFn {
1162 let state = std::sync::Arc::clone(&self.state);
1163 Box::new(move |event: Json| -> Result<()> {
1164 let mut guard = state
1165 .lock()
1166 .unwrap_or_else(|poisoned| poisoned.into_inner());
1167 guard.observe(&event);
1168 Ok(())
1169 })
1170 }
1171
1172 fn finalizer(&self) -> crate::api::runtime::LlmFinalizerFn {
1173 let state = std::sync::Arc::clone(&self.state);
1174 Box::new(move || -> Json {
1175 let mut guard = state
1176 .lock()
1177 .unwrap_or_else(|poisoned| poisoned.into_inner());
1178 std::mem::take(&mut *guard).finalize()
1181 })
1182 }
1183}
1184
1185#[derive(Debug, Default)]
1186struct AnthropicMessagesStreamingState {
1187 id: Option<String>,
1188 type_: Option<String>,
1189 role: Option<String>,
1190 model: Option<String>,
1191 usage: Option<Json>,
1194 stop_reason: Option<String>,
1195 stop_sequence: Option<Json>,
1197 blocks: Vec<Option<StreamingBlock>>,
1200}
1201
1202#[derive(Debug, Default, Clone)]
1203struct StreamingBlock {
1204 skeleton: serde_json::Map<String, Json>,
1208 text: String,
1209 has_text: bool,
1210 partial_json: String,
1211 has_partial_json: bool,
1212 citations: Vec<Json>,
1213 has_citations: bool,
1214}
1215
1216impl AnthropicMessagesStreamingState {
1217 fn observe(&mut self, event: &Json) {
1218 let event_type = event.get("type").and_then(Json::as_str).unwrap_or("");
1219 match event_type {
1220 "message_start" => self.observe_message_start(event),
1221 "content_block_start" => self.observe_content_block_start(event),
1222 "content_block_delta" => self.observe_content_block_delta(event),
1223 "message_delta" => self.observe_message_delta(event),
1224 _ => {}
1228 }
1229 }
1230
1231 fn observe_message_start(&mut self, event: &Json) {
1232 let Some(message) = event.get("message") else {
1233 return;
1234 };
1235 if let Some(id) = message.get("id").and_then(Json::as_str) {
1236 self.id = Some(id.to_string());
1237 }
1238 if let Some(model) = message.get("model").and_then(Json::as_str) {
1239 self.model = Some(model.to_string());
1240 }
1241 if let Some(role) = message.get("role").and_then(Json::as_str) {
1242 self.role = Some(role.to_string());
1243 }
1244 if let Some(t) = message.get("type").and_then(Json::as_str) {
1245 self.type_ = Some(t.to_string());
1246 }
1247 if let Some(usage) = message.get("usage") {
1248 self.usage = Some(usage.clone());
1249 }
1250 }
1251
1252 fn observe_content_block_start(&mut self, event: &Json) {
1253 let Some(index) = event.get("index").and_then(Json::as_u64) else {
1254 return;
1255 };
1256 let Some(content_block) = event.get("content_block") else {
1257 return;
1258 };
1259 let skeleton = match content_block {
1260 Json::Object(map) => map.clone(),
1261 _ => return,
1262 };
1263 let index = index as usize;
1264 while self.blocks.len() <= index {
1265 self.blocks.push(None);
1266 }
1267 self.blocks[index] = Some(StreamingBlock {
1268 skeleton,
1269 ..StreamingBlock::default()
1270 });
1271 }
1272
1273 fn observe_content_block_delta(&mut self, event: &Json) {
1274 let Some(index) = event.get("index").and_then(Json::as_u64) else {
1275 return;
1276 };
1277 let index = index as usize;
1278 let Some(delta) = event.get("delta") else {
1279 return;
1280 };
1281 let delta_type = delta.get("type").and_then(Json::as_str).unwrap_or("");
1282 let Some(slot) = self.blocks.get_mut(index) else {
1283 return;
1284 };
1285 let Some(block) = slot.as_mut() else { return };
1286 match delta_type {
1287 "text_delta" => {
1288 if let Some(text) = delta.get("text").and_then(Json::as_str) {
1289 block.text.push_str(text);
1290 block.has_text = true;
1291 }
1292 }
1293 "input_json_delta" => {
1294 if let Some(partial) = delta.get("partial_json").and_then(Json::as_str) {
1295 block.partial_json.push_str(partial);
1296 block.has_partial_json = true;
1297 }
1298 }
1299 "citations_delta" => {
1300 if let Some(citation) = delta.get("citation") {
1301 block.citations.push(citation.clone());
1302 block.has_citations = true;
1303 }
1304 }
1305 _ => {}
1308 }
1309 }
1310
1311 fn observe_message_delta(&mut self, event: &Json) {
1312 if let Some(delta) = event.get("delta") {
1313 if let Some(reason) = delta.get("stop_reason").and_then(Json::as_str) {
1314 self.stop_reason = Some(reason.to_string());
1315 }
1316 if let Some(seq) = delta.get("stop_sequence") {
1317 self.stop_sequence = Some(seq.clone());
1318 }
1319 }
1320 if let Some(usage) = event.get("usage") {
1321 self.usage = Some(usage.clone());
1322 }
1323 }
1324
1325 fn finalize(self) -> Json {
1326 let mut output = serde_json::Map::new();
1327 if let Some(id) = self.id {
1328 output.insert("id".to_string(), Json::String(id));
1329 }
1330 if let Some(t) = self.type_ {
1331 output.insert("type".to_string(), Json::String(t));
1332 }
1333 if let Some(role) = self.role {
1334 output.insert("role".to_string(), Json::String(role));
1335 }
1336 if let Some(model) = self.model {
1337 output.insert("model".to_string(), Json::String(model));
1338 }
1339 let content: Vec<Json> = self
1340 .blocks
1341 .into_iter()
1342 .filter_map(|block| block.map(StreamingBlock::finalize))
1343 .collect();
1344 output.insert("content".to_string(), Json::Array(content));
1345 if let Some(reason) = self.stop_reason {
1346 output.insert("stop_reason".to_string(), Json::String(reason));
1347 }
1348 if let Some(seq) = self.stop_sequence {
1349 output.insert("stop_sequence".to_string(), seq);
1350 }
1351 if let Some(usage) = self.usage {
1352 output.insert("usage".to_string(), usage);
1353 }
1354 Json::Object(output)
1355 }
1356}
1357
1358impl StreamingBlock {
1359 fn finalize(mut self) -> Json {
1360 if self.has_text {
1361 self.skeleton
1362 .insert("text".to_string(), Json::String(self.text));
1363 }
1364 if self.has_partial_json {
1365 let parsed = match serde_json::from_str::<Json>(&self.partial_json) {
1370 Ok(value) => value,
1371 Err(_) => Json::String(self.partial_json),
1372 };
1373 self.skeleton.insert("input".to_string(), parsed);
1374 }
1375 if self.has_citations {
1376 self.skeleton
1377 .insert("citations".to_string(), Json::Array(self.citations));
1378 }
1379 Json::Object(self.skeleton)
1380 }
1381}
1382
1383#[cfg(test)]
1388#[path = "../../tests/unit/codec/anthropic_tests.rs"]
1389mod tests;