From eec041c111c0ee170dab65942184e66c41479fcd Mon Sep 17 00:00:00 2001 From: sigoden Date: Mon, 25 Mar 2024 10:52:05 +0800 Subject: feat: rename client localai to openai-compatible (#373) BREAKING CHANGE: rename client localai to openai-compatible --- README.md | 4 +-- config.example.yaml | 10 +++--- src/client/localai.rs | 77 ----------------------------------------- src/client/mod.rs | 7 +++- src/client/openai_compatible.rs | 77 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 85 deletions(-) delete mode 100644 src/client/localai.rs create mode 100644 src/client/openai_compatible.rs diff --git a/README.md b/README.md index 3a44cc7..c4a521c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![CI](https://github.com/sigoden/aichat/actions/workflows/ci.yaml/badge.svg)](https://github.com/sigoden/aichat/actions/workflows/ci.yaml) [![Crates](https://img.shields.io/crates/v/aichat.svg)](https://crates.io/crates/aichat) -All-in-one CLI tool for 10+ AI platforms, including OpenAI, Gemini, Claude, Mistral, LocalAI, Ollama, VertexAI, Ernie, Qianwen... +All-in-one CLI tool for 10+ AI platforms, including OpenAI, Gemini, Claude, Mistral, Ollama, VertexAI, Ernie, Qianwen, Moonshot... Command Mode: @@ -47,7 +47,7 @@ Download it from [GitHub Releases](https://github.com/sigoden/aichat/releases), - Gemini (free, vision) - Claude: Claude2/Claude3 (paid) - Mistral (paid) - - LocalAI (free, local, vision) + - OpenAI-Compatible (local) - Ollama (free, local) - Azure-OpenAI (paid) - VertexAI: Gemini-1/Gemini-1.5 (paid, vision) diff --git a/config.example.yaml b/config.example.yaml index 8f8e22a..b7ce1d9 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -40,15 +40,15 @@ clients: # See https://docs.anthropic.com/claude/reference/getting-started-with-the-api - type: claude - api_key: sk-xxx + api_key: sk-ant-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - type: mistral api_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - # Any openai-compatible API providers or https://github.com/go-skynet/LocalAI - - type: localai + # Any openai-compatible API providers + - type: openai-compatible # Renamed from localai api_base: http://localhost:8080/v1 - api_key: xxx + api_key: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx chat_endpoint: /chat/completions # Optional field models: - name: llama2 @@ -95,4 +95,4 @@ clients: # See https://platform.moonshot.cn/docs/intro - type: moonshot - api_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ No newline at end of file + api_key: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ No newline at end of file diff --git a/src/client/localai.rs b/src/client/localai.rs deleted file mode 100644 index 0e9db0e..0000000 --- a/src/client/localai.rs +++ /dev/null @@ -1,77 +0,0 @@ -use super::openai::{openai_build_body, OPENAI_TOKENS_COUNT_FACTORS}; -use super::{ExtraConfig, LocalAIClient, Model, ModelConfig, PromptType, SendData}; - -use crate::utils::PromptKind; - -use anyhow::Result; -use async_trait::async_trait; -use reqwest::{Client as ReqwestClient, RequestBuilder}; -use serde::Deserialize; - -#[derive(Debug, Clone, Deserialize)] -pub struct LocalAIConfig { - pub name: Option, - pub api_base: String, - pub api_key: Option, - pub chat_endpoint: Option, - pub models: Vec, - pub extra: Option, -} - -openai_compatible_client!(LocalAIClient); - -impl LocalAIClient { - config_get_fn!(api_key, get_api_key); - - pub const PROMPTS: [PromptType<'static>; 4] = [ - ("api_base", "API Base:", true, PromptKind::String), - ("api_key", "API Key:", false, PromptKind::String), - ("models[].name", "Model Name:", true, PromptKind::String), - ( - "models[].max_input_tokens", - "Max Input Tokens:", - false, - PromptKind::Integer, - ), - ]; - - pub fn list_models(local_config: &LocalAIConfig) -> Vec { - let client_name = Self::name(local_config); - - local_config - .models - .iter() - .map(|v| { - Model::new(client_name, &v.name) - .set_capabilities(v.capabilities) - .set_max_input_tokens(v.max_input_tokens) - .set_extra_fields(v.extra_fields.clone()) - .set_tokens_count_factors(OPENAI_TOKENS_COUNT_FACTORS) - }) - .collect() - } - - fn request_builder(&self, client: &ReqwestClient, data: SendData) -> Result { - let api_key = self.get_api_key().ok(); - - let mut body = openai_build_body(data, self.model.name.clone()); - self.model.merge_extra_fields(&mut body); - - let chat_endpoint = self - .config - .chat_endpoint - .as_deref() - .unwrap_or("/chat/completions"); - - let url = format!("{}{chat_endpoint}", self.config.api_base); - - debug!("LocalAI Request: {url} {body}"); - - let mut builder = client.post(url).json(&body); - if let Some(api_key) = api_key { - builder = builder.bearer_auth(api_key); - } - - Ok(builder) - } -} diff --git a/src/client/mod.rs b/src/client/mod.rs index 7c7cfba..37775f3 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -12,7 +12,12 @@ register_client!( (gemini, "gemini", GeminiConfig, GeminiClient), (claude, "claude", ClaudeConfig, ClaudeClient), (mistral, "mistral", MistralConfig, MistralClient), - (localai, "localai", LocalAIConfig, LocalAIClient), + ( + openai_compatible, + "openai-compatible", + OpenAICompatibleConfig, + OpenAICompatibleClient + ), (ollama, "ollama", OllamaConfig, OllamaClient), ( azure_openai, diff --git a/src/client/openai_compatible.rs b/src/client/openai_compatible.rs new file mode 100644 index 0000000..ec3333c --- /dev/null +++ b/src/client/openai_compatible.rs @@ -0,0 +1,77 @@ +use super::openai::{openai_build_body, OPENAI_TOKENS_COUNT_FACTORS}; +use super::{ExtraConfig, Model, ModelConfig, OpenAICompatibleClient, PromptType, SendData}; + +use crate::utils::PromptKind; + +use anyhow::Result; +use async_trait::async_trait; +use reqwest::{Client as ReqwestClient, RequestBuilder}; +use serde::Deserialize; + +#[derive(Debug, Clone, Deserialize)] +pub struct OpenAICompatibleConfig { + pub name: Option, + pub api_base: String, + pub api_key: Option, + pub chat_endpoint: Option, + pub models: Vec, + pub extra: Option, +} + +openai_compatible_client!(OpenAICompatibleClient); + +impl OpenAICompatibleClient { + config_get_fn!(api_key, get_api_key); + + pub const PROMPTS: [PromptType<'static>; 4] = [ + ("api_base", "API Base:", true, PromptKind::String), + ("api_key", "API Key:", false, PromptKind::String), + ("models[].name", "Model Name:", true, PromptKind::String), + ( + "models[].max_input_tokens", + "Max Input Tokens:", + false, + PromptKind::Integer, + ), + ]; + + pub fn list_models(local_config: &OpenAICompatibleConfig) -> Vec { + let client_name = Self::name(local_config); + + local_config + .models + .iter() + .map(|v| { + Model::new(client_name, &v.name) + .set_capabilities(v.capabilities) + .set_max_input_tokens(v.max_input_tokens) + .set_extra_fields(v.extra_fields.clone()) + .set_tokens_count_factors(OPENAI_TOKENS_COUNT_FACTORS) + }) + .collect() + } + + fn request_builder(&self, client: &ReqwestClient, data: SendData) -> Result { + let api_key = self.get_api_key().ok(); + + let mut body = openai_build_body(data, self.model.name.clone()); + self.model.merge_extra_fields(&mut body); + + let chat_endpoint = self + .config + .chat_endpoint + .as_deref() + .unwrap_or("/chat/completions"); + + let url = format!("{}{chat_endpoint}", self.config.api_base); + + debug!("OpenAICompatible Request: {url} {body}"); + + let mut builder = client.post(url).json(&body); + if let Some(api_key) = api_key { + builder = builder.bearer_auth(api_key); + } + + Ok(builder) + } +} -- cgit v1.2.3