anthropic: Use separate Content type in requests and responses (#17163)

This PR splits the `Content` type for Anthropic into two new types:
`RequestContent` and `ResponseContent`.

As I was going through the Anthropic API docs it seems that there are
different types of content that can be sent in requests vs what can be
returned in responses.

Using a separate type for each case tells the story a bit better and
makes it easier to understand, IMO.

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2024-08-30 11:46:03 -04:00 committed by GitHub
parent 00eed768ce
commit 8901d926eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 25 additions and 13 deletions

View file

@ -337,7 +337,7 @@ pub fn extract_text_from_events(
match response { match response {
Ok(response) => match response { Ok(response) => match response {
Event::ContentBlockStart { content_block, .. } => match content_block { Event::ContentBlockStart { content_block, .. } => match content_block {
Content::Text { text, .. } => Some(Ok(text)), ResponseContent::Text { text, .. } => Some(Ok(text)),
_ => None, _ => None,
}, },
Event::ContentBlockDelta { delta, .. } => match delta { Event::ContentBlockDelta { delta, .. } => match delta {
@ -363,7 +363,7 @@ pub async fn extract_tool_args_from_events(
content_block, content_block,
} = event? } = event?
{ {
if let Content::ToolUse { name, .. } = content_block { if let ResponseContent::ToolUse { name, .. } = content_block {
if name == tool_name { if name == tool_name {
tool_use_index = Some(index); tool_use_index = Some(index);
break; break;
@ -411,7 +411,7 @@ pub struct CacheControl {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Message { pub struct Message {
pub role: Role, pub role: Role,
pub content: Vec<Content>, pub content: Vec<RequestContent>,
} }
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] #[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
@ -423,7 +423,7 @@ pub enum Role {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum Content { pub enum RequestContent {
#[serde(rename = "text")] #[serde(rename = "text")]
Text { Text {
text: String, text: String,
@ -444,10 +444,22 @@ pub enum Content {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
cache_control: Option<CacheControl>, cache_control: Option<CacheControl>,
}, },
#[serde(rename = "tool_result")] }
ToolResult {
tool_use_id: String, #[derive(Debug, Serialize, Deserialize)]
content: String, #[serde(tag = "type")]
pub enum ResponseContent {
#[serde(rename = "text")]
Text {
text: String,
#[serde(skip_serializing_if = "Option::is_none")]
cache_control: Option<CacheControl>,
},
#[serde(rename = "tool_use")]
ToolUse {
id: String,
name: String,
input: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
cache_control: Option<CacheControl>, cache_control: Option<CacheControl>,
}, },
@ -525,7 +537,7 @@ pub struct Response {
#[serde(rename = "type")] #[serde(rename = "type")]
pub response_type: String, pub response_type: String,
pub role: Role, pub role: Role,
pub content: Vec<Content>, pub content: Vec<ResponseContent>,
pub model: String, pub model: String,
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub stop_reason: Option<String>, pub stop_reason: Option<String>,
@ -542,7 +554,7 @@ pub enum Event {
#[serde(rename = "content_block_start")] #[serde(rename = "content_block_start")]
ContentBlockStart { ContentBlockStart {
index: usize, index: usize,
content_block: Content, content_block: ResponseContent,
}, },
#[serde(rename = "content_block_delta")] #[serde(rename = "content_block_delta")]
ContentBlockDelta { index: usize, delta: ContentDelta }, ContentBlockDelta { index: usize, delta: ContentDelta },

View file

@ -304,17 +304,17 @@ impl LanguageModelRequest {
} else { } else {
None None
}; };
let anthropic_message_content: Vec<anthropic::Content> = message let anthropic_message_content: Vec<anthropic::RequestContent> = message
.content .content
.into_iter() .into_iter()
.filter_map(|content| match content { .filter_map(|content| match content {
MessageContent::Text(t) if !t.is_empty() => { MessageContent::Text(t) if !t.is_empty() => {
Some(anthropic::Content::Text { Some(anthropic::RequestContent::Text {
text: t, text: t,
cache_control, cache_control,
}) })
} }
MessageContent::Image(i) => Some(anthropic::Content::Image { MessageContent::Image(i) => Some(anthropic::RequestContent::Image {
source: anthropic::ImageSource { source: anthropic::ImageSource {
source_type: "base64".to_string(), source_type: "base64".to_string(),
media_type: "image/png".to_string(), media_type: "image/png".to_string(),