From 13a0a3c12199ab83f2f99804bec5cdd4a2f3cc08 Mon Sep 17 00:00:00 2001 From: Shashank Agarwal Date: Wed, 13 Nov 2024 00:06:35 +0530 Subject: [PATCH] Add anthropic --- .gitignore | 2 ++ Cargo.toml | 5 +++-- README.md | 24 ++++++++++++++++++++++-- src/proxy.rs | 44 ++++++++++++++++++++++++++++++++++++++------ 4 files changed, 65 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 9fa9747..6098312 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ # Created by https://www.toptal.com/developers/gitignore/api/rust # Edit at https://www.toptal.com/developers/gitignore?templates=rust +.env + ### Rust ### # Generated by Cargo # will have compiled files and executables diff --git a/Cargo.toml b/Cargo.toml index a49a81f..b7f452b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "magicapi-ai-gateway" -version = "0.1.4" +version = "0.1.5" edition = "2021" description = "A high-performance AI Gateway proxy for routing requests to various AI providers, offering seamless integration and management of multiple AI services" authors = ["MagicAPI Team "] @@ -33,4 +33,5 @@ tower = "0.4" bytes = "1.0" dotenv = "0.15" futures-util = "0.3" -once_cell = "1.18" \ No newline at end of file +once_cell = "1.18" +hyper = { version = "1.0", features = ["full"] } \ No newline at end of file diff --git a/README.md b/README.md index 774e902..5706e76 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # MagicAPI AI Gateway -The world's fastest AI Gateway proxy, written in Rust and optimized for maximum performance. This high-performance API gateway routes requests to various AI providers (OpenAI, GROQ) with streaming support, making it perfect for developers who need reliable and blazing-fast AI API access. +The world's fastest AI Gateway proxy, written in Rust and optimized for maximum performance. This high-performance API gateway routes requests to various AI providers (OpenAI, Anthropic, GROQ) with streaming support, making it perfect for developers who need reliable and blazing-fast AI API access. [![Rust](https://github.com/MagicAPI/ai-gateway/actions/workflows/rust.yml/badge.svg)](https://github.com/MagicAPI/ai-gateway/actions/workflows/rust.yml) [![Crates.io](https://img.shields.io/crates/v/magicapi-ai-gateway.svg)](https://crates.io/crates/magicapi-ai-gateway) @@ -9,7 +9,7 @@ The world's fastest AI Gateway proxy, written in Rust and optimized for maximum - 🚀 Blazing fast performance - built in Rust with zero-cost abstractions - ⚡ Optimized for low latency and high throughput -- 🔄 Unified API interface for multiple AI providers (OpenAI, GROQ) +- 🔄 Unified API interface for multiple AI providers (OpenAI, Anthropic, GROQ) - 📡 Real-time streaming support with minimal overhead - 🔍 Built-in health checking - 🛡️ Configurable CORS @@ -103,6 +103,26 @@ curl -X POST http://localhost:3000/v1/chat/completions \ }' ``` +#### Example: Anthropic Request + +```bash +curl -X POST http://localhost:3000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "x-provider: anthropic" \ + -H "Authorization: Bearer your-anthropic-api-key" \ + -d '{ + "model": "claude-3-5-sonnet-20241022", + "messages": [{"role": "user", "content": "Write a poem"}], + "stream": true, + "max_tokens": 1024 + }' +``` + +Note: When using Anthropic as the provider, the gateway automatically: +- Routes requests to Anthropic's message API +- Converts the Authorization Bearer token to the required x-api-key format +- Adds the required anthropic-version header + ## Configuration The gateway can be configured using environment variables: diff --git a/src/proxy.rs b/src/proxy.rs index f65fc7c..58f1e31 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -37,24 +37,30 @@ pub async fn proxy_request_to_provider( "Incoming request" ); - let base_url = match provider { - "openai" => "https://api.openai.com", - "anthropic" => "https://api.anthropic.com", - "groq" => "https://api.groq.com/openai", + let path = original_request.uri().path(); + let (base_url, modified_path) = match provider { + "openai" => ("https://api.openai.com", path), + "anthropic" => { + if path.contains("/chat/completions") { + ("https://api.anthropic.com", "/v1/messages") + } else { + ("https://api.anthropic.com", path) + } + }, + "groq" => ("https://api.groq.com/openai", path), _ => { error!(provider = provider, "Unsupported provider"); return Err(AppError::UnsupportedProvider); } }; - let path = original_request.uri().path(); let query = original_request .uri() .query() .map(|q| format!("?{}", q)) .unwrap_or_default(); - let url = format!("{}{}{}", base_url, path, query); + let url = format!("{}{}{}", base_url, modified_path, query); info!( provider = provider, url = %url, @@ -121,6 +127,32 @@ pub async fn proxy_request_to_provider( return Err(AppError::MissingApiKey); } }, + "anthropic" => { + tracing::debug!("Processing Anthropic request headers"); + if let Some(auth) = original_request.headers().get("authorization") + .and_then(|h| h.to_str().ok()) { + tracing::debug!("Using provided authorization header for Anthropic"); + // Convert Bearer token to x-api-key format + let api_key = auth.trim_start_matches("Bearer "); + reqwest_headers.insert( + reqwest::header::HeaderName::from_static("x-api-key"), + reqwest::header::HeaderValue::from_str(api_key) + .map_err(|_| { + tracing::error!("Failed to process Anthropic authorization header"); + AppError::InvalidHeader + })? + ); + + // Add required Anthropic version header + reqwest_headers.insert( + reqwest::header::HeaderName::from_static("anthropic-version"), + reqwest::header::HeaderValue::from_static("2023-06-01") + ); + } else { + tracing::error!("No authorization header found for Anthropic request"); + return Err(AppError::MissingApiKey); + } + }, _ => return Err(AppError::UnsupportedProvider), }