use crate::ui::InstructionListItem;use crate::AllLanguageModelSettings;use anthropic::{AnthropicError, ContentDelta, Event, ResponseContent};use anyhow::{anyhow, Context as _, Result};use collections::{BTreeMap, HashMap};use credentials_provider::CredentialsProvider;use editor::{Editor, EditorElement, EditorStyle};use futures::Stream;use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt, TryStreamExt as _};use gpui::{ AnyView, App, AsyncApp, Context, Entity, FontStyle, Subscription, Task, TextStyle, WhiteSpace,};use http_client::HttpClient;use language_model::{ AuthenticateError, LanguageModel, LanguageModelCacheConfiguration, LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest, MessageContent, RateLimiter, Role,};use language_model::{LanguageModelCompletionEvent, LanguageModelToolUse, StopReason};use schemars::JsonSchema;use serde::{Deserialize, Serialize};use settings::{Settings, SettingsStore};use std::pin::Pin;use std::str::FromStr;use std::sync::Arc;use strum::IntoEnumIterator;use theme::ThemeSettings;use ui::{prelude::*, Icon, IconName, List, Tooltip};use util::{maybe, ResultExt}; const PROVIDER_ID: &str = language_model::ANTHROPIC_PROVIDER_ID;const PROVIDER_NAME: &str = "Anthropic"; #[derive(Default, Clone, Debug, PartialEq)]pub struct AnthropicSettings { pub api_url: String, /// Extend Zed's list of Anthropic models. pub available_models: Vec<AvailableModel>, pub needs_setting_migration: bool,} #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]pub struct AvailableModel { /// The model's name in the Anthropic API. e.g. claude-3-5-sonnet-latest, claude-3-opus-20240229, etc pub name: String, /// The model's name in Zed's UI, such as in the model selector dropdown menu in the assistant panel. pub display_name: Option<String>, /// The model's context window size. pub max_tokens: usize, /// A model `name` to substitute when calling tools, in case the primary model doesn't support tool calling. pub tool_override: Option<String>, /// Configuration of Anthropic's caching API. pub cache_configuration: Option<LanguageModelCacheConfiguration>, pub max_output_tokens: Option<u32>, pub default_temperature: Option<f32>, #[serde(default)] pub extra_beta_headers: Vec<String>,} pub struct AnthropicLanguageModelProvider { http_client: Arc<dyn HttpClient>, state: gpui::Entity<State>,} const ANTHROPIC_API_KEY_VAR: &str = "ANTHROPIC_API_KEY"; pub struct State { api_key: Option<String>, api_key_from_env: bool, _subscription: Subscription,} impl State { fn reset_api_key(&self, cx: &mut Context<Self>) -> Task<Result<()>> { let credentials_provider = <dyn CredentialsProvider>::global(cx); let api_url = AllLanguageModelSettings::get_global(cx) .anthropic .api_url .clone(); cx.spawn(|this, mut cx| async move { credentials_provider .delete_credentials(&api_url, &cx) .await .ok(); this.update(&mut cx, |this, cx| { this.api_key = None; this.api_key_from_env = false; cx.notify(); }) }) } fn set_api_key(&mut self, api_key: String, cx: &mut Context<Self>) -> Task<Result<()>> { let credentials_provider = <dyn CredentialsProvider>::global(cx); let api_url = AllLanguageModelSettings::get_global(cx) .anthropic .api_url .clone(); cx.spawn(|this, mut cx| async move { credentials_provider .write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx) .await .ok(); this.update(&mut cx, |this, cx| { this.api_key = Some(api_key); cx.notify(); }) }) } fn is_authenticated(&self) -> bool { self.api_key.is_some() } fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> { if self.is_authenticated() { return Task::ready(Ok(())); } let credentials_provider = <dyn CredentialsProvider>::global(cx); let api_url = AllLanguageModelSettings::get_global(cx) .anthropic .api_url .clone(); cx.spawn(|this, mut cx| async move { let (api_key, from_env) = if let Ok(api_key) = std::env::var(ANTHROPIC_API_KEY_VAR) { (api_key, true) } else { let (_, api_key) = credentials_provider .read_credentials(&api_url, &cx) .await? .ok_or(AuthenticateError::CredentialsNotFound)?; ( String::from_utf8(api_key).context("invalid {PROVIDER_NAME} API key")?, false, ) }; this.update(&mut cx, |this, cx| { this.api_key = Some(api_key); this.api_key_from_env = from_env; cx.notify(); })?; Ok(()) }) }} impl AnthropicLanguageModelProvider { pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self { let state = cx.new(|cx| State { api_key: None, api_key_from_env: false, _subscription: cx.observe_global::<SettingsStore>(|_, cx| { cx.notify(); }), }); Self { http_client, state } }} impl LanguageModelProviderState for AnthropicLanguageModelProvider { type ObservableEntity = State; fn observable_entity(&self) -> Option<gpui::Entity<Self::ObservableEntity>> { Some(self.state.clone()) }} impl LanguageModelProvider for AnthropicLanguageModelProvider { fn id(&self) -> LanguageModelProviderId { LanguageModelProviderId(PROVIDER_ID.into()) } fn name(&self) -> LanguageModelProviderName { LanguageModelProviderName(PROVIDER_NAME.into()) } fn icon(&self) -> IconName { IconName::AiAnthropic } fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> { let model = anthropic::Model::default(); Some(Arc::new(AnthropicModel { id: LanguageModelId::from(model.id().to_string()), model, state: self.state.clone(), http_client: self.http_client.clone(), request_limiter: RateLimiter::new(4), })) } fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> { let mut models = BTreeMap::default(); // Add base models from anthropic::Model::iter() for model in anthropic::Model::iter() { if !matches!(model, anthropic::Model::Custom { .. }) { models.insert(model.id().to_string(), model); } } // Override with available models from settings for model in AllLanguageModelSettings::get_global(cx) .anthropic .available_models .iter() { models.insert( model.name.clone(), anthropic::Model::Custom { name: model.name.clone(), display_name: model.display_name.clone(), max_tokens: model.max_tokens, tool_override: model.tool_override.clone(), cache_configuration: model.cache_configuration.as_ref().map(|config| { anthropic::AnthropicModelCacheConfiguration { max_cache_anchors: config.max_cache_anchors, should_speculate: config.should_speculate, min_total_token: config.min_total_token, } }), max_output_tokens: model.max_output_tokens, default_temperature: model.default_temperature, extra_beta_headers: model.extra_beta_headers.clone(), }, ); } models .into_values() .map(|model| { Arc::new(AnthropicModel { id: LanguageModelId::from(model.id().to_string()), model, state: self.state.clone(), http_client: self.http_client.clone(), request_limiter: RateLimiter::new(4), }) as Arc<dyn LanguageModel> }) .collect() } fn is_authenticated(&self, cx: &App) -> bool { self.state.read(cx).is_authenticated() } fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> { self.state.update(cx, |state, cx| state.authenticate(cx)) } fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView { cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx)) .into() } fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> { self.state.update(cx, |state, cx| state.reset_api_key(cx)) }} pub struct AnthropicModel { id: LanguageModelId, model: anthropic::Model, state: gpui::Entity<State>, http_client: Arc<dyn HttpClient>, request_limiter: RateLimiter,} pub fn count_anthropic_tokens( request: LanguageModelRequest, cx: &App,) -> BoxFuture<'static, Result<usize>> { cx.background_spawn(async move { let messages = request.messages; let mut tokens_from_images = 0; let mut string_messages = Vec::with_capacity(messages.len()); for message in messages { use language_model::MessageContent; let mut string_contents = String::new(); for content in message.content { match content { MessageContent::Text(text) => { string_contents.push_str(&text); } MessageContent::Image(image) => { tokens_from_images += image.estimate_tokens(); } MessageContent::ToolUse(_tool_use) => { // TODO: Estimate token usage from tool uses. } MessageContent::ToolResult(tool_result) => { string_contents.push_str(&tool_result.content); } } } if !string_contents.is_empty() { string_messages.push(tiktoken_rs::ChatCompletionRequestMessage { role: match message.role { Role::User => "user".into(), Role::Assistant => "assistant".into(), Role::System => "system".into(), }, content: Some(string_contents), name: None, function_call: None, }); } } // Tiktoken doesn't yet support these models, so we manually use the // same tokenizer as GPT-4. tiktoken_rs::num_tokens_from_messages("gpt-4", &string_messages) .map(|tokens| tokens + tokens_from_images) }) .boxed()} impl AnthropicModel { fn stream_completion( &self, request: anthropic::Request, cx: &AsyncApp, ) -> BoxFuture<'static, Result<BoxStream<'static, Result<anthropic::Event, AnthropicError>>>> { let http_client = self.http_client.clone(); let Ok((api_key, api_url)) = cx.read_entity(&self.state, |state, cx| { let settings = &AllLanguageModelSettings::get_global(cx).anthropic; (state.api_key.clone(), settings.api_url.clone()) }) else { return futures::future::ready(Err(anyhow!("App state dropped"))).boxed(); }; async move { let api_key = api_key.ok_or_else(|| anyhow!("Missing Anthropic API Key"))?; let request = anthropic::stream_completion(http_client.as_ref(), &api_url, &api_key, request); request.await.context("failed to stream completion") } .boxed() }} impl LanguageModel for AnthropicModel { fn id(&self) -> LanguageModelId { self.id.clone() } fn name(&self) -> LanguageModelName { LanguageModelName::from(self.model.display_name().to_string()) } fn provider_id(&self) -> LanguageModelProviderId { LanguageModelProviderId(PROVIDER_ID.into()) } fn provider_name(&self) -> LanguageModelProviderName { LanguageModelProviderName(PROVIDER_NAME.into()) } fn telemetry_id(&self) -> String { format!("anthropic/{}", self.model.id()) } fn api_key(&self, cx: &App) -> Option<String> { self.state.read(cx).api_key.clone() } fn max_token_count(&self) -> usize { self.model.max_token_count() } fn max_output_tokens(&self) -> Option<u32> { Some(self.model.max_output_tokens()) } fn count_tokens( &self, request: LanguageModelRequest, cx: &App, ) -> BoxFuture<'static, Result<usize>> { count_anthropic_tokens(request, cx) } fn stream_completion( &self, request: LanguageModelRequest, cx: &AsyncApp, ) -> BoxFuture<'static, Result<BoxStream<'static, Result<LanguageModelCompletionEvent>>>> { let request = into_anthropic( request, self.model.id().into(), self.model.default_temperature(), self.model.max_output_tokens(), ); let request = self.stream_completion(request, cx); let future = self.request_limiter.stream(async move { let response = request.await.map_err(|err| anyhow!(err))?; Ok(map_to_language_model_completion_events(response)) }); async move { Ok(future.await?.boxed()) }.boxed() } fn cache_configuration(&self) -> Option<LanguageModelCacheConfiguration> { self.model .cache_configuration() .map(|config| LanguageModelCacheConfiguration { max_cache_anchors: config.max_cache_anchors, should_speculate: config.should_speculate, min_total_token: config.min_total_token, }) } fn use_any_tool( &self, request: LanguageModelRequest, tool_name: String, tool_description: String, input_schema: serde_json::Value, cx: &AsyncApp, ) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> { let mut request = into_anthropic( request, self.model.tool_model_id().into(), self.model.default_temperature(), self.model.max_output_tokens(), ); request.tool_choice = Some(anthropic::ToolChoice::Tool { name: tool_name.clone(), }); request.tools = vec![anthropic::Tool { name: tool_name.clone(), description: tool_description, input_schema, }]; let response = self.stream_completion(request, cx); self.request_limiter .run(async move { let response = response.await?; Ok(anthropic::extract_tool_args_from_events( tool_name, Box::pin(response.map_err(|e| anyhow!(e))), ) .await? .boxed()) }) .boxed() }} pub fn into_anthropic( request: LanguageModelRequest, model: String, default_temperature: f32, max_output_tokens: u32,) -> anthropic::Request { let mut new_messages: Vec<anthropic::Message> = Vec::new(); let mut system_message = String::new(); for message in request.messages { if message.contents_empty() { continue; } match message.role { Role::User | Role::Assistant => { let cache_control = if message.cache { Some(anthropic::CacheControl { cache_type: anthropic::CacheControlType::Ephemeral, }) } else { None }; let anthropic_message_content: Vec<anthropic::RequestContent> = message .content .into_iter() .filter_map(|content| match content { MessageContent::Text(text) => { if !text.is_empty() { Some(anthropic::RequestContent::Text { text, cache_control, }) } else { None } } MessageContent::Image(image) => Some(anthropic::RequestContent::Image { source: anthropic::ImageSource {Lines 1–500 of 877. Load more
use crate::ui::InstructionListItem; use crate::AllLanguageModelSettings; use anthropic::{AnthropicError, ContentDelta, Event, ResponseContent}; use anyhow::{anyhow, Context as _, Result}; use collections::{BTreeMap, HashMap}; use credentials_provider::CredentialsProvider; use editor::{Editor, EditorElement, EditorStyle}; use futures::Stream; use futures::{future::BoxFuture, stream::BoxStream, FutureExt, StreamExt, TryStreamExt as _}; use gpui::{ AnyView, App, AsyncApp, Context, Entity, FontStyle, Subscription, Task, TextStyle, WhiteSpace, }; use http_client::HttpClient; use language_model::{ AuthenticateError, LanguageModel, LanguageModelCacheConfiguration, LanguageModelId, LanguageModelName, LanguageModelProvider, LanguageModelProviderId, LanguageModelProviderName, LanguageModelProviderState, LanguageModelRequest, MessageContent, RateLimiter, Role, }; use language_model::{LanguageModelCompletionEvent, LanguageModelToolUse, StopReason}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsStore}; use std::pin::Pin; use std::str::FromStr; use std::sync::Arc; use strum::IntoEnumIterator; use theme::ThemeSettings; use ui::{prelude::*, Icon, IconName, List, Tooltip}; use util::{maybe, ResultExt}; const PROVIDER_ID: &str = language_model::ANTHROPIC_PROVIDER_ID; const PROVIDER_NAME: &str = "Anthropic"; #[derive(Default, Clone, Debug, PartialEq)] pub struct AnthropicSettings { pub api_url: String, /// Extend Zed's list of Anthropic models. pub available_models: Vec<AvailableModel>, pub needs_setting_migration: bool, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct AvailableModel { /// The model's name in the Anthropic API. e.g. claude-3-5-sonnet-latest, claude-3-opus-20240229, etc pub name: String, /// The model's name in Zed's UI, such as in the model selector dropdown menu in the assistant panel. pub display_name: Option<String>, /// The model's context window size. pub max_tokens: usize, /// A model `name` to substitute when calling tools, in case the primary model doesn't support tool calling. pub tool_override: Option<String>, /// Configuration of Anthropic's caching API. pub cache_configuration: Option<LanguageModelCacheConfiguration>, pub max_output_tokens: Option<u32>, pub default_temperature: Option<f32>, #[serde(default)] pub extra_beta_headers: Vec<String>, } pub struct AnthropicLanguageModelProvider { http_client: Arc<dyn HttpClient>, state: gpui::Entity<State>, } const ANTHROPIC_API_KEY_VAR: &str = "ANTHROPIC_API_KEY"; pub struct State { api_key: Option<String>, api_key_from_env: bool, _subscription: Subscription, } impl State { fn reset_api_key(&self, cx: &mut Context<Self>) -> Task<Result<()>> { let credentials_provider = <dyn CredentialsProvider>::global(cx); let api_url = AllLanguageModelSettings::get_global(cx) .anthropic .api_url .clone(); cx.spawn(|this, mut cx| async move { credentials_provider .delete_credentials(&api_url, &cx) .await .ok(); this.update(&mut cx, |this, cx| { this.api_key = None; this.api_key_from_env = false; cx.notify(); }) }) } fn set_api_key(&mut self, api_key: String, cx: &mut Context<Self>) -> Task<Result<()>> { let credentials_provider = <dyn CredentialsProvider>::global(cx); let api_url = AllLanguageModelSettings::get_global(cx) .anthropic .api_url .clone(); cx.spawn(|this, mut cx| async move { credentials_provider .write_credentials(&api_url, "Bearer", api_key.as_bytes(), &cx) .await .ok(); this.update(&mut cx, |this, cx| { this.api_key = Some(api_key); cx.notify(); }) }) } fn is_authenticated(&self) -> bool { self.api_key.is_some() } fn authenticate(&self, cx: &mut Context<Self>) -> Task<Result<(), AuthenticateError>> { if self.is_authenticated() { return Task::ready(Ok(())); } let credentials_provider = <dyn CredentialsProvider>::global(cx); let api_url = AllLanguageModelSettings::get_global(cx) .anthropic .api_url .clone(); cx.spawn(|this, mut cx| async move { let (api_key, from_env) = if let Ok(api_key) = std::env::var(ANTHROPIC_API_KEY_VAR) { (api_key, true) } else { let (_, api_key) = credentials_provider .read_credentials(&api_url, &cx) .await? .ok_or(AuthenticateError::CredentialsNotFound)?; ( String::from_utf8(api_key).context("invalid {PROVIDER_NAME} API key")?, false, ) }; this.update(&mut cx, |this, cx| { this.api_key = Some(api_key); this.api_key_from_env = from_env; cx.notify(); })?; Ok(()) }) } } impl AnthropicLanguageModelProvider { pub fn new(http_client: Arc<dyn HttpClient>, cx: &mut App) -> Self { let state = cx.new(|cx| State { api_key: None, api_key_from_env: false, _subscription: cx.observe_global::<SettingsStore>(|_, cx| { cx.notify(); }), }); Self { http_client, state } } } impl LanguageModelProviderState for AnthropicLanguageModelProvider { type ObservableEntity = State; fn observable_entity(&self) -> Option<gpui::Entity<Self::ObservableEntity>> { Some(self.state.clone()) } } impl LanguageModelProvider for AnthropicLanguageModelProvider { fn id(&self) -> LanguageModelProviderId { LanguageModelProviderId(PROVIDER_ID.into()) } fn name(&self) -> LanguageModelProviderName { LanguageModelProviderName(PROVIDER_NAME.into()) } fn icon(&self) -> IconName { IconName::AiAnthropic } fn default_model(&self, _cx: &App) -> Option<Arc<dyn LanguageModel>> { let model = anthropic::Model::default(); Some(Arc::new(AnthropicModel { id: LanguageModelId::from(model.id().to_string()), model, state: self.state.clone(), http_client: self.http_client.clone(), request_limiter: RateLimiter::new(4), })) } fn provided_models(&self, cx: &App) -> Vec<Arc<dyn LanguageModel>> { let mut models = BTreeMap::default(); // Add base models from anthropic::Model::iter() for model in anthropic::Model::iter() { if !matches!(model, anthropic::Model::Custom { .. }) { models.insert(model.id().to_string(), model); } } // Override with available models from settings for model in AllLanguageModelSettings::get_global(cx) .anthropic .available_models .iter() { models.insert( model.name.clone(), anthropic::Model::Custom { name: model.name.clone(), display_name: model.display_name.clone(), max_tokens: model.max_tokens, tool_override: model.tool_override.clone(), cache_configuration: model.cache_configuration.as_ref().map(|config| { anthropic::AnthropicModelCacheConfiguration { max_cache_anchors: config.max_cache_anchors, should_speculate: config.should_speculate, min_total_token: config.min_total_token, } }), max_output_tokens: model.max_output_tokens, default_temperature: model.default_temperature, extra_beta_headers: model.extra_beta_headers.clone(), }, ); } models .into_values() .map(|model| { Arc::new(AnthropicModel { id: LanguageModelId::from(model.id().to_string()), model, state: self.state.clone(), http_client: self.http_client.clone(), request_limiter: RateLimiter::new(4), }) as Arc<dyn LanguageModel> }) .collect() } fn is_authenticated(&self, cx: &App) -> bool { self.state.read(cx).is_authenticated() } fn authenticate(&self, cx: &mut App) -> Task<Result<(), AuthenticateError>> { self.state.update(cx, |state, cx| state.authenticate(cx)) } fn configuration_view(&self, window: &mut Window, cx: &mut App) -> AnyView { cx.new(|cx| ConfigurationView::new(self.state.clone(), window, cx)) .into() } fn reset_credentials(&self, cx: &mut App) -> Task<Result<()>> { self.state.update(cx, |state, cx| state.reset_api_key(cx)) } } pub struct AnthropicModel { id: LanguageModelId, model: anthropic::Model, state: gpui::Entity<State>, http_client: Arc<dyn HttpClient>, request_limiter: RateLimiter, } pub fn count_anthropic_tokens( request: LanguageModelRequest, cx: &App, ) -> BoxFuture<'static, Result<usize>> { cx.background_spawn(async move { let messages = request.messages; let mut tokens_from_images = 0; let mut string_messages = Vec::with_capacity(messages.len()); for message in messages { use language_model::MessageContent; let mut string_contents = String::new(); for content in message.content { match content { MessageContent::Text(text) => { string_contents.push_str(&text); } MessageContent::Image(image) => { tokens_from_images += image.estimate_tokens(); } MessageContent::ToolUse(_tool_use) => { // TODO: Estimate token usage from tool uses. } MessageContent::ToolResult(tool_result) => { string_contents.push_str(&tool_result.content); } } } if !string_contents.is_empty() { string_messages.push(tiktoken_rs::ChatCompletionRequestMessage { role: match message.role { Role::User => "user".into(), Role::Assistant => "assistant".into(), Role::System => "system".into(), }, content: Some(string_contents), name: None, function_call: None, }); } } // Tiktoken doesn't yet support these models, so we manually use the // same tokenizer as GPT-4. tiktoken_rs::num_tokens_from_messages("gpt-4", &string_messages) .map(|tokens| tokens + tokens_from_images) }) .boxed() } impl AnthropicModel { fn stream_completion( &self, request: anthropic::Request, cx: &AsyncApp, ) -> BoxFuture<'static, Result<BoxStream<'static, Result<anthropic::Event, AnthropicError>>>> { let http_client = self.http_client.clone(); let Ok((api_key, api_url)) = cx.read_entity(&self.state, |state, cx| { let settings = &AllLanguageModelSettings::get_global(cx).anthropic; (state.api_key.clone(), settings.api_url.clone()) }) else { return futures::future::ready(Err(anyhow!("App state dropped"))).boxed(); }; async move { let api_key = api_key.ok_or_else(|| anyhow!("Missing Anthropic API Key"))?; let request = anthropic::stream_completion(http_client.as_ref(), &api_url, &api_key, request); request.await.context("failed to stream completion") } .boxed() } } impl LanguageModel for AnthropicModel { fn id(&self) -> LanguageModelId { self.id.clone() } fn name(&self) -> LanguageModelName { LanguageModelName::from(self.model.display_name().to_string()) } fn provider_id(&self) -> LanguageModelProviderId { LanguageModelProviderId(PROVIDER_ID.into()) } fn provider_name(&self) -> LanguageModelProviderName { LanguageModelProviderName(PROVIDER_NAME.into()) } fn telemetry_id(&self) -> String { format!("anthropic/{}", self.model.id()) } fn api_key(&self, cx: &App) -> Option<String> { self.state.read(cx).api_key.clone() } fn max_token_count(&self) -> usize { self.model.max_token_count() } fn max_output_tokens(&self) -> Option<u32> { Some(self.model.max_output_tokens()) } fn count_tokens( &self, request: LanguageModelRequest, cx: &App, ) -> BoxFuture<'static, Result<usize>> { count_anthropic_tokens(request, cx) } fn stream_completion( &self, request: LanguageModelRequest, cx: &AsyncApp, ) -> BoxFuture<'static, Result<BoxStream<'static, Result<LanguageModelCompletionEvent>>>> { let request = into_anthropic( request, self.model.id().into(), self.model.default_temperature(), self.model.max_output_tokens(), ); let request = self.stream_completion(request, cx); let future = self.request_limiter.stream(async move { let response = request.await.map_err(|err| anyhow!(err))?; Ok(map_to_language_model_completion_events(response)) }); async move { Ok(future.await?.boxed()) }.boxed() } fn cache_configuration(&self) -> Option<LanguageModelCacheConfiguration> { self.model .cache_configuration() .map(|config| LanguageModelCacheConfiguration { max_cache_anchors: config.max_cache_anchors, should_speculate: config.should_speculate, min_total_token: config.min_total_token, }) } fn use_any_tool( &self, request: LanguageModelRequest, tool_name: String, tool_description: String, input_schema: serde_json::Value, cx: &AsyncApp, ) -> BoxFuture<'static, Result<BoxStream<'static, Result<String>>>> { let mut request = into_anthropic( request, self.model.tool_model_id().into(), self.model.default_temperature(), self.model.max_output_tokens(), ); request.tool_choice = Some(anthropic::ToolChoice::Tool { name: tool_name.clone(), }); request.tools = vec![anthropic::Tool { name: tool_name.clone(), description: tool_description, input_schema, }]; let response = self.stream_completion(request, cx); self.request_limiter .run(async move { let response = response.await?; Ok(anthropic::extract_tool_args_from_events( tool_name, Box::pin(response.map_err(|e| anyhow!(e))), ) .await? .boxed()) }) .boxed() } } pub fn into_anthropic( request: LanguageModelRequest, model: String, default_temperature: f32, max_output_tokens: u32, ) -> anthropic::Request { let mut new_messages: Vec<anthropic::Message> = Vec::new(); let mut system_message = String::new(); for message in request.messages { if message.contents_empty() { continue; } match message.role { Role::User | Role::Assistant => { let cache_control = if message.cache { Some(anthropic::CacheControl { cache_type: anthropic::CacheControlType::Ephemeral, }) } else { None }; let anthropic_message_content: Vec<anthropic::RequestContent> = message .content .into_iter() .filter_map(|content| match content { MessageContent::Text(text) => { if !text.is_empty() { Some(anthropic::RequestContent::Text { text, cache_control, }) } else { None } } MessageContent::Image(image) => Some(anthropic::RequestContent::Image { source: anthropic::ImageSource {