877 lines · 32.0 KB · Rust
RawDownload
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.