1use serde::Deserialize;
20
21use crate::api::llm::LlmRequest;
22use crate::error::{FlowError, Result};
23use crate::json::Json;
24
25use super::request::{
26 AnnotatedLlmRequest, FunctionDefinition, GenerationParams, Message, MessageContent, ToolChoice,
27 ToolChoiceFunction, ToolChoiceFunctionName, ToolDefinition,
28};
29use super::response::{
30 AnnotatedLlmResponse, ApiSpecificResponse, FinishReason, RawUsageCost, ResponseToolCall, Usage,
31 estimate_cost_for_provider, infer_model_provider, provider_reported_cost,
32};
33use super::traits::{LlmCodec, LlmResponseCodec};
34
35pub struct AnthropicMessagesCodec;
41
42#[derive(Deserialize)]
47struct RawAnthropicResponse {
48 id: Option<String>,
49 #[serde(rename = "type")]
50 object_type: Option<String>,
51 role: Option<String>,
52 model: Option<String>,
53 content: Option<Vec<Json>>,
54 stop_reason: Option<String>,
55 stop_sequence: Option<String>,
56 service_tier: Option<String>,
57 container: Option<Json>,
58 usage: Option<RawAnthropicUsage>,
59 #[serde(flatten)]
60 extra: serde_json::Map<String, Json>,
61}
62
63#[derive(Deserialize)]
64struct RawAnthropicUsage {
65 input_tokens: Option<u64>,
66 output_tokens: Option<u64>,
67 cache_read_input_tokens: Option<u64>,
68 cache_creation_input_tokens: Option<u64>,
69 #[serde(rename = "cost_usd")]
70 provider_cost: Option<f64>,
71 cost: Option<RawUsageCost>,
72}
73
74fn map_anthropic_stop_reason(reason: &str) -> FinishReason {
80 match reason {
81 "end_turn" => FinishReason::Complete,
82 "max_tokens" => FinishReason::Length,
83 "tool_use" => FinishReason::ToolUse,
84 other => FinishReason::Unknown(other.to_string()),
85 }
86}
87
88fn json_f64(v: f64) -> Json {
90 serde_json::Number::from_f64(v)
91 .map(Json::Number)
92 .unwrap_or(Json::Null)
93}
94
95const MODELED_REQUEST_KEYS: &[&str] = &[
97 "system",
98 "messages",
99 "model",
100 "max_tokens",
101 "temperature",
102 "top_p",
103 "stop_sequences",
104 "tools",
105 "tool_choice",
106 "metadata",
107 "service_tier",
108];
109
110fn decode_anthropic_tool_choice(val: &Json) -> Option<ToolChoice> {
118 let obj = val.as_object()?;
119 let tc_type = obj.get("type")?.as_str()?;
120 match tc_type {
121 "auto" => Some(ToolChoice::Auto),
122 "any" => Some(ToolChoice::Required),
123 "none" => Some(ToolChoice::None),
124 "tool" => {
125 let name = obj.get("name")?.as_str()?.to_string();
126 Some(ToolChoice::Specific(ToolChoiceFunction {
127 choice_type: "function".into(),
128 function: ToolChoiceFunctionName { name },
129 }))
130 }
131 _ => None,
132 }
133}
134
135fn decode_parallel_tool_calls(val: &Json) -> Option<bool> {
138 let obj = val.as_object()?;
139 obj.get("disable_parallel_tool_use")
140 .and_then(|v| v.as_bool())
141 .map(|disabled| !disabled)
142}
143
144fn encode_anthropic_tool_choice(tc: &ToolChoice) -> Json {
146 match tc {
147 ToolChoice::Auto => serde_json::json!({"type": "auto"}),
148 ToolChoice::Required => serde_json::json!({"type": "any"}),
149 ToolChoice::None => serde_json::json!({"type": "none"}),
150 ToolChoice::Specific(func) => {
151 serde_json::json!({"type": "tool", "name": func.function.name})
152 }
153 }
154}
155
156fn encode_tool_choice_with_parallel_hint(
157 tc: &ToolChoice,
158 parallel_tool_calls: Option<bool>,
159) -> Json {
160 let mut value = encode_anthropic_tool_choice(tc);
161 if let (Some(parallel), Some(obj)) = (parallel_tool_calls, value.as_object_mut()) {
162 obj.insert("disable_parallel_tool_use".into(), Json::Bool(!parallel));
163 }
164 value
165}
166
167fn extract_system_message(system_val: &Json) -> Option<Message> {
171 if let Some(s) = system_val.as_str() {
172 Some(Message::System {
173 content: MessageContent::Text(s.to_string()),
174 name: None,
175 })
176 } else if let Some(arr) = system_val.as_array() {
177 let texts: Vec<&str> = arr
179 .iter()
180 .filter_map(|block| {
181 let block_type = block.get("type")?.as_str()?;
182 if block_type == "text" {
183 block.get("text")?.as_str()
184 } else {
185 None
186 }
187 })
188 .collect();
189 if texts.is_empty() {
190 None
191 } else {
192 Some(Message::System {
193 content: MessageContent::Text(texts.join("\n")),
194 name: None,
195 })
196 }
197 } else {
198 None
199 }
200}
201
202fn extract_system_text(msg: &Message) -> Option<String> {
204 match msg {
205 Message::System {
206 content: MessageContent::Text(s),
207 ..
208 } => Some(s.clone()),
209 Message::System {
210 content: MessageContent::Parts(parts),
211 ..
212 } => {
213 let texts: Vec<&str> = parts
214 .iter()
215 .filter_map(|p| match p {
216 super::request::ContentPart::Text { text } => Some(text.as_str()),
217 super::request::ContentPart::ImageUrl { .. } => None,
218 })
219 .collect();
220 if texts.is_empty() {
221 None
222 } else {
223 Some(texts.join("\n"))
224 }
225 }
226 _ => None,
227 }
228}
229
230fn split_system_and_messages(messages: &[Message]) -> (Option<String>, Vec<&Message>) {
231 let mut system_text = None;
232 let mut non_system_messages = Vec::new();
233
234 for msg in messages {
235 if let Some(text) = extract_system_text(msg) {
236 system_text = Some(text);
237 } else {
238 non_system_messages.push(msg);
239 }
240 }
241
242 (system_text, non_system_messages)
243}
244
245fn insert_serialized<T: serde::Serialize>(
246 obj: &mut serde_json::Map<String, Json>,
247 key: &str,
248 value: &T,
249 context: &str,
250) -> Result<()> {
251 let json = serde_json::to_value(value)
252 .map_err(|e| FlowError::Internal(format!("Anthropic Messages {context} encode: {e}")))?;
253 obj.insert(key.into(), json);
254 Ok(())
255}
256
257fn overlay_generation_params(obj: &mut serde_json::Map<String, Json>, params: &GenerationParams) {
258 if let Some(temp) = params.temperature {
259 obj.insert("temperature".into(), json_f64(temp));
260 }
261 if let Some(top_p) = params.top_p {
262 obj.insert("top_p".into(), json_f64(top_p));
263 }
264 if let Some(max_tokens) = params.max_tokens {
265 obj.insert("max_tokens".into(), Json::from(max_tokens));
266 }
267}
268
269fn encode_anthropic_tools(tools: &[ToolDefinition]) -> Vec<Json> {
270 tools
271 .iter()
272 .map(|td| {
273 let mut tool = serde_json::Map::new();
274 tool.insert("name".into(), Json::String(td.function.name.clone()));
275 if let Some(ref desc) = td.function.description {
276 tool.insert("description".into(), Json::String(desc.clone()));
277 }
278 if let Some(ref params) = td.function.parameters {
279 tool.insert("input_schema".into(), params.clone());
280 }
281 Json::Object(tool)
282 })
283 .collect()
284}
285
286fn anthropic_text_message(content_blocks: Option<&[Json]>) -> Option<MessageContent> {
287 let text_parts: Vec<&str> = content_blocks
288 .map(|blocks| blocks.iter().filter_map(anthropic_text_block).collect())
289 .unwrap_or_default();
290
291 (!text_parts.is_empty()).then(|| MessageContent::Text(text_parts.join("\n")))
292}
293
294fn anthropic_text_block(block: &Json) -> Option<&str> {
295 if block.get("type")?.as_str()? != "text" {
296 return None;
297 }
298 block.get("text")?.as_str()
299}
300
301fn anthropic_tool_calls(content_blocks: Option<&[Json]>) -> Option<Vec<ResponseToolCall>> {
302 let tool_calls: Vec<ResponseToolCall> = content_blocks
303 .map(|blocks| {
304 blocks
305 .iter()
306 .filter_map(anthropic_tool_call_block)
307 .collect()
308 })
309 .unwrap_or_default();
310
311 (!tool_calls.is_empty()).then_some(tool_calls)
312}
313
314fn anthropic_tool_call_block(block: &Json) -> Option<ResponseToolCall> {
315 if block.get("type")?.as_str()? != "tool_use" {
316 return None;
317 }
318 Some(ResponseToolCall {
319 id: block.get("id")?.as_str()?.to_string(),
320 name: block.get("name")?.as_str()?.to_string(),
321 arguments: block.get("input")?.clone(),
323 })
324}
325
326fn anthropic_usage(
327 raw_usage: Option<RawAnthropicUsage>,
328 model_for_pricing: Option<&str>,
329) -> Option<Usage> {
330 let model_provider = infer_model_provider("anthropic", model_for_pricing);
331 raw_usage.map(|u| {
332 let prompt = u.input_tokens;
333 let completion = u.output_tokens;
334 let mut usage = Usage {
335 prompt_tokens: prompt,
336 completion_tokens: completion,
337 total_tokens: match (prompt, completion) {
339 (Some(p), Some(c)) => Some(p + c),
340 _ => None,
341 },
342 cache_read_tokens: u.cache_read_input_tokens,
343 cache_write_tokens: u.cache_creation_input_tokens,
344 cost: provider_reported_cost(u.provider_cost, u.cost),
345 };
346 if usage.cost.is_none() {
347 usage.cost = model_for_pricing.and_then(|model| {
348 estimate_cost_for_provider(model_provider.as_deref(), model, &usage)
349 });
350 }
351 usage
352 })
353}
354
355impl LlmResponseCodec for AnthropicMessagesCodec {
360 fn decode_response(&self, response: &Json) -> Result<AnnotatedLlmResponse> {
361 let raw: RawAnthropicResponse = serde_json::from_value(response.clone())
362 .map_err(|e| FlowError::Internal(format!("Anthropic Messages response decode: {e}")))?;
363
364 let content_blocks = raw.content.as_deref();
365 let message = anthropic_text_message(content_blocks);
366 let tool_calls = anthropic_tool_calls(content_blocks);
368
369 let finish_reason = raw.stop_reason.as_deref().map(map_anthropic_stop_reason);
371
372 let usage = anthropic_usage(raw.usage, raw.model.as_deref());
374
375 let api_specific_content_blocks = raw.content.clone();
377 let api_specific = Some(ApiSpecificResponse::AnthropicMessages {
378 object_type: raw.object_type,
379 role: raw.role,
380 stop_reason: raw.stop_reason,
381 stop_sequence: raw.stop_sequence,
382 service_tier: raw.service_tier,
383 container: raw.container,
384 content_blocks: api_specific_content_blocks,
385 });
386
387 Ok(AnnotatedLlmResponse {
388 id: raw.id,
389 model: raw.model,
390 message,
391 tool_calls,
392 finish_reason,
393 usage,
394 api_specific,
395 extra: raw.extra,
396 })
397 }
398}
399
400impl LlmCodec for AnthropicMessagesCodec {
405 fn decode(&self, request: &LlmRequest) -> Result<AnnotatedLlmRequest> {
406 let obj = request
407 .content
408 .as_object()
409 .ok_or_else(|| FlowError::Internal("request content is not an object".into()))?;
410
411 let system_msg = obj.get("system").and_then(extract_system_message);
413
414 let mut messages: Vec<Message> = obj
416 .get("messages")
417 .map(|v| serde_json::from_value(v.clone()).unwrap_or_default())
418 .unwrap_or_default();
419
420 if let Some(sys) = system_msg {
422 messages.insert(0, sys);
423 }
424
425 let model = obj.get("model").and_then(|v| v.as_str()).map(String::from);
427
428 let temperature = obj.get("temperature").and_then(|v| v.as_f64());
430 let top_p = obj.get("top_p").and_then(|v| v.as_f64());
431 let max_tokens = obj.get("max_tokens").and_then(|v| v.as_u64());
432 let stop = obj
434 .get("stop_sequences")
435 .and_then(|v| serde_json::from_value::<Vec<String>>(v.clone()).ok());
436
437 let params =
438 if temperature.is_some() || max_tokens.is_some() || top_p.is_some() || stop.is_some() {
439 Some(GenerationParams {
440 temperature,
441 max_tokens,
442 top_p,
443 stop,
444 })
445 } else {
446 None
447 };
448
449 let tools: Option<Vec<ToolDefinition>> = obj.get("tools").and_then(|v| {
452 let arr = v.as_array()?;
453 let defs: Vec<ToolDefinition> = arr
454 .iter()
455 .filter_map(|tool| {
456 let name = tool.get("name")?.as_str()?.to_string();
457 let description = tool
458 .get("description")
459 .and_then(|d| d.as_str())
460 .map(String::from);
461 let parameters = tool.get("input_schema").cloned();
462 Some(ToolDefinition {
463 tool_type: "function".into(),
464 function: FunctionDefinition {
465 name,
466 description,
467 parameters,
468 },
469 })
470 })
471 .collect();
472 if defs.is_empty() { None } else { Some(defs) }
473 });
474
475 let tool_choice = obj
477 .get("tool_choice")
478 .and_then(decode_anthropic_tool_choice);
479 let parallel_tool_calls = obj.get("tool_choice").and_then(decode_parallel_tool_calls);
480
481 let extra: serde_json::Map<String, Json> = obj
483 .iter()
484 .filter(|(k, _)| !MODELED_REQUEST_KEYS.contains(&k.as_str()))
485 .map(|(k, v)| (k.clone(), v.clone()))
486 .collect();
487
488 Ok(AnnotatedLlmRequest {
489 messages,
490 model,
491 params,
492 tools,
493 tool_choice,
494 store: None,
495 previous_response_id: None,
496 truncation: None,
497 reasoning: None,
498 include: None,
499 user: None,
500 metadata: obj.get("metadata").cloned(),
501 service_tier: obj
502 .get("service_tier")
503 .and_then(|v| v.as_str())
504 .map(String::from),
505 parallel_tool_calls,
506 max_output_tokens: None,
507 max_tool_calls: None,
508 top_logprobs: None,
509 stream: None,
510 extra,
511 })
512 }
513
514 fn encode(&self, annotated: &AnnotatedLlmRequest, original: &LlmRequest) -> Result<LlmRequest> {
515 let mut content = original.content.clone();
516 let obj = content
517 .as_object_mut()
518 .ok_or_else(|| FlowError::Internal("original content is not an object".into()))?;
519
520 let (system_text, non_system_messages) = split_system_and_messages(&annotated.messages);
521
522 if let Some(text) = system_text {
523 obj.insert("system".into(), Json::String(text));
524 }
525
526 insert_serialized(obj, "messages", &non_system_messages, "messages")?;
528
529 if let Some(ref model) = annotated.model {
531 obj.insert("model".into(), Json::String(model.clone()));
532 }
533
534 if let Some(ref params) = annotated.params {
536 overlay_generation_params(obj, params);
537 if let Some(ref stop) = params.stop {
539 insert_serialized(obj, "stop_sequences", stop, "stop_sequences")?;
540 }
541 }
542
543 if let Some(ref tools) = annotated.tools {
546 let anthropic_tools = encode_anthropic_tools(tools);
547 insert_serialized(obj, "tools", &anthropic_tools, "tools")?;
548 }
549
550 if let Some(ref tool_choice) = annotated.tool_choice {
552 obj.insert(
553 "tool_choice".into(),
554 encode_tool_choice_with_parallel_hint(tool_choice, annotated.parallel_tool_calls),
555 );
556 }
557
558 if let Some(ref metadata) = annotated.metadata {
559 obj.insert("metadata".into(), metadata.clone());
560 }
561 if let Some(ref service_tier) = annotated.service_tier {
562 obj.insert("service_tier".into(), Json::String(service_tier.clone()));
563 }
564
565 for (k, v) in &annotated.extra {
567 obj.insert(k.clone(), v.clone());
568 }
569
570 Ok(LlmRequest {
571 headers: original.headers.clone(),
572 content,
573 })
574 }
575}
576
577pub struct AnthropicMessagesStreamingCodec {
596 state: std::sync::Arc<std::sync::Mutex<AnthropicMessagesStreamingState>>,
597}
598
599impl AnthropicMessagesStreamingCodec {
600 pub fn new() -> Self {
602 Self {
603 state: std::sync::Arc::new(std::sync::Mutex::new(
604 AnthropicMessagesStreamingState::default(),
605 )),
606 }
607 }
608}
609
610impl Default for AnthropicMessagesStreamingCodec {
611 fn default() -> Self {
612 Self::new()
613 }
614}
615
616impl super::streaming::StreamingCodec for AnthropicMessagesStreamingCodec {
617 fn collector(&self) -> crate::api::runtime::LlmCollectorFn {
618 let state = std::sync::Arc::clone(&self.state);
619 Box::new(move |event: Json| -> Result<()> {
620 let mut guard = state
621 .lock()
622 .unwrap_or_else(|poisoned| poisoned.into_inner());
623 guard.observe(&event);
624 Ok(())
625 })
626 }
627
628 fn finalizer(&self) -> crate::api::runtime::LlmFinalizerFn {
629 let state = std::sync::Arc::clone(&self.state);
630 Box::new(move || -> Json {
631 let mut guard = state
632 .lock()
633 .unwrap_or_else(|poisoned| poisoned.into_inner());
634 std::mem::take(&mut *guard).finalize()
637 })
638 }
639}
640
641#[derive(Debug, Default)]
642struct AnthropicMessagesStreamingState {
643 id: Option<String>,
644 type_: Option<String>,
645 role: Option<String>,
646 model: Option<String>,
647 usage: Option<Json>,
650 stop_reason: Option<String>,
651 stop_sequence: Option<Json>,
653 blocks: Vec<Option<StreamingBlock>>,
656}
657
658#[derive(Debug, Default, Clone)]
659struct StreamingBlock {
660 skeleton: serde_json::Map<String, Json>,
664 text: String,
665 has_text: bool,
666 partial_json: String,
667 has_partial_json: bool,
668 citations: Vec<Json>,
669 has_citations: bool,
670}
671
672impl AnthropicMessagesStreamingState {
673 fn observe(&mut self, event: &Json) {
674 let event_type = event.get("type").and_then(Json::as_str).unwrap_or("");
675 match event_type {
676 "message_start" => self.observe_message_start(event),
677 "content_block_start" => self.observe_content_block_start(event),
678 "content_block_delta" => self.observe_content_block_delta(event),
679 "message_delta" => self.observe_message_delta(event),
680 _ => {}
684 }
685 }
686
687 fn observe_message_start(&mut self, event: &Json) {
688 let Some(message) = event.get("message") else {
689 return;
690 };
691 if let Some(id) = message.get("id").and_then(Json::as_str) {
692 self.id = Some(id.to_string());
693 }
694 if let Some(model) = message.get("model").and_then(Json::as_str) {
695 self.model = Some(model.to_string());
696 }
697 if let Some(role) = message.get("role").and_then(Json::as_str) {
698 self.role = Some(role.to_string());
699 }
700 if let Some(t) = message.get("type").and_then(Json::as_str) {
701 self.type_ = Some(t.to_string());
702 }
703 if let Some(usage) = message.get("usage") {
704 self.usage = Some(usage.clone());
705 }
706 }
707
708 fn observe_content_block_start(&mut self, event: &Json) {
709 let Some(index) = event.get("index").and_then(Json::as_u64) else {
710 return;
711 };
712 let Some(content_block) = event.get("content_block") else {
713 return;
714 };
715 let skeleton = match content_block {
716 Json::Object(map) => map.clone(),
717 _ => return,
718 };
719 let index = index as usize;
720 while self.blocks.len() <= index {
721 self.blocks.push(None);
722 }
723 self.blocks[index] = Some(StreamingBlock {
724 skeleton,
725 ..StreamingBlock::default()
726 });
727 }
728
729 fn observe_content_block_delta(&mut self, event: &Json) {
730 let Some(index) = event.get("index").and_then(Json::as_u64) else {
731 return;
732 };
733 let index = index as usize;
734 let Some(delta) = event.get("delta") else {
735 return;
736 };
737 let delta_type = delta.get("type").and_then(Json::as_str).unwrap_or("");
738 let Some(slot) = self.blocks.get_mut(index) else {
739 return;
740 };
741 let Some(block) = slot.as_mut() else { return };
742 match delta_type {
743 "text_delta" => {
744 if let Some(text) = delta.get("text").and_then(Json::as_str) {
745 block.text.push_str(text);
746 block.has_text = true;
747 }
748 }
749 "input_json_delta" => {
750 if let Some(partial) = delta.get("partial_json").and_then(Json::as_str) {
751 block.partial_json.push_str(partial);
752 block.has_partial_json = true;
753 }
754 }
755 "citations_delta" => {
756 if let Some(citation) = delta.get("citation") {
757 block.citations.push(citation.clone());
758 block.has_citations = true;
759 }
760 }
761 _ => {}
764 }
765 }
766
767 fn observe_message_delta(&mut self, event: &Json) {
768 if let Some(delta) = event.get("delta") {
769 if let Some(reason) = delta.get("stop_reason").and_then(Json::as_str) {
770 self.stop_reason = Some(reason.to_string());
771 }
772 if let Some(seq) = delta.get("stop_sequence") {
773 self.stop_sequence = Some(seq.clone());
774 }
775 }
776 if let Some(usage) = event.get("usage") {
777 self.usage = Some(usage.clone());
778 }
779 }
780
781 fn finalize(self) -> Json {
782 let mut output = serde_json::Map::new();
783 if let Some(id) = self.id {
784 output.insert("id".to_string(), Json::String(id));
785 }
786 if let Some(t) = self.type_ {
787 output.insert("type".to_string(), Json::String(t));
788 }
789 if let Some(role) = self.role {
790 output.insert("role".to_string(), Json::String(role));
791 }
792 if let Some(model) = self.model {
793 output.insert("model".to_string(), Json::String(model));
794 }
795 let content: Vec<Json> = self
796 .blocks
797 .into_iter()
798 .filter_map(|block| block.map(StreamingBlock::finalize))
799 .collect();
800 output.insert("content".to_string(), Json::Array(content));
801 if let Some(reason) = self.stop_reason {
802 output.insert("stop_reason".to_string(), Json::String(reason));
803 }
804 if let Some(seq) = self.stop_sequence {
805 output.insert("stop_sequence".to_string(), seq);
806 }
807 if let Some(usage) = self.usage {
808 output.insert("usage".to_string(), usage);
809 }
810 Json::Object(output)
811 }
812}
813
814impl StreamingBlock {
815 fn finalize(mut self) -> Json {
816 if self.has_text {
817 self.skeleton
818 .insert("text".to_string(), Json::String(self.text));
819 }
820 if self.has_partial_json {
821 let parsed = match serde_json::from_str::<Json>(&self.partial_json) {
826 Ok(value) => value,
827 Err(_) => Json::String(self.partial_json),
828 };
829 self.skeleton.insert("input".to_string(), parsed);
830 }
831 if self.has_citations {
832 self.skeleton
833 .insert("citations".to_string(), Json::Array(self.citations));
834 }
835 Json::Object(self.skeleton)
836 }
837}
838
839#[cfg(test)]
844#[path = "../../tests/unit/codec/anthropic_tests.rs"]
845mod tests;