Skip to content

Commit 32580d0

Browse files
authored
(feat) Azure Support (#36)
* azure support e2e * fixed error
1 parent c890673 commit 32580d0

File tree

25 files changed

+567
-74
lines changed

25 files changed

+567
-74
lines changed

.env.example

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
## Backend environment variables ##
22
RUST_LOG=info
3-
OPENROUTER_API_KEY=
43
JWT_SECRET=
54
USE_SECURE_COOKIE=false # Set to true to use secure cookies
65

6+
## Provider API Keys ##
7+
# Only set the API keys for providers you plan to use
8+
OPENROUTER_API_KEY=
9+
OPENAI_API_KEY=
10+
AZURE_API_KEY=
11+
712
# If running locally, select path on local machine
813
#DATABASE_URL=sqlite:/home/user/development/llmkit/backend/llmkit.db
914

README.md

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,35 @@ Our mission is to make prompt crafting dynamic, prompt management safe, and prom
6060
Llmkit supports the following LLM providers:
6161

6262
- **OpenRouter**: Access to 100+ models through a unified API
63-
- **OpenAI**: Direct integration with OpenAI's GPT models
63+
- **OpenAI**: Direct integration with OpenAI's GPT models
64+
- **Azure OpenAI**: Microsoft's Azure-hosted OpenAI models
6465
- **Anthropic**: Claude models (coming soon)
65-
- **Azure OpenAI**: Microsoft's Azure-hosted OpenAI models (coming soon)
6666
- **Google Gemini**: Google's Gemini models (coming soon)
6767
- **DeepSeek**: DeepSeek's models (coming soon)
6868

69+
### Provider Configuration
70+
71+
Providers need to be configured before they can be used. Each provider requires:
72+
1. An API key set as an environment variable (see `.env.example` for all required variables)
73+
2. A base URL configuration (some providers have default URLs, Azure requires manual configuration)
74+
75+
To configure providers:
76+
1. Navigate to the **Providers** page in the UI
77+
2. Click "Configure" on the provider you want to set up
78+
3. Enter the base URL if required (especially for Azure)
79+
4. Providers will show as "Available" when both API key and base URL are configured
80+
81+
### Azure OpenAI Setup
82+
83+
Azure OpenAI requires additional configuration:
84+
85+
1. **Set the API Key**: Add `AZURE_API_KEY=your_azure_api_key` to your `.env` file
86+
2. **Configure Base URL**: In the Providers page, set your Azure endpoint (e.g., `https://your-resource.openai.azure.com/`)
87+
3. **Model Configuration**: When adding Azure models:
88+
- Enter your deployment name (not the full model name)
89+
- Specify the API version (e.g., `2024-08-01-preview`)
90+
- These will be automatically combined in the format: `deployment_name|api_version`
91+
6992
## How It Works
7093

7194
### Prompt Architecture
@@ -191,9 +214,7 @@ Every LLM call has a detailed trace that you can view. directly in the llmkit UI
191214
### Required
192215

193216
- **Rust Toolchain**: Latest stable version of Rust and Cargo
194-
- **API Keys**: You need API keys for the providers you want to use:
195-
- **OpenRouter**: Required if using OpenRouter provider
196-
- **OpenAI**: Required if using OpenAI or OpenRouter provider (set via `OPENAI_API_KEY`)
217+
- **API Keys**: You need API keys for the providers you want to use (see `.env.example`)
197218
- **SQLite**: For database functionality
198219

199220
### Optional Dependencies
@@ -229,7 +250,8 @@ USE_SECURE_COOKIE=false # Set to true for HTTPS deployments
229250

230251
# Provider API Keys (add the ones you need)
231252
OPENROUTER_API_KEY=your_openrouter_key_here
232-
OPENAI_API_KEY=your_openai_key_here # Required for OpenAI and OpenRouter providers
253+
OPENAI_API_KEY=your_openai_key_here
254+
AZURE_API_KEY=your_azure_key_here
233255
```
234256

235257
4. Build and start the containers:
@@ -262,7 +284,7 @@ OPENROUTER_API_KEY=your_openrouter_key_here
262284
OPENAI_API_KEY=your_openai_key_here # Required for OpenAI and OpenRouter providers
263285
```
264286

265-
**Note**: If you don't set the required API keys for a provider, the backend will fail when you try to use that provider.
287+
**Note**: Providers will show as "Not Available" in the UI if their API keys are not set. You can check provider status and configure base URLs on the Providers page.
266288

267289
3. Start the server:
268290
```bash
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- SQLite workaround: drop and recreate column to make it nullable
2+
ALTER TABLE provider DROP COLUMN base_url;
3+
ALTER TABLE provider ADD COLUMN base_url TEXT;
4+
5+
-- Update existing providers with their base URLs
6+
UPDATE provider SET base_url = 'https://openrouter.ai/api/v1' WHERE name = 'openrouter';
7+
UPDATE provider SET base_url = 'https://api.openai.com/v1' WHERE name = 'openai';
8+
9+
-- Add Azure provider with null base_url (requires user configuration)
10+
INSERT INTO provider (name, base_url) VALUES ('azure', NULL);

backend/src/common/types/models.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@ use serde::Serialize;
44
pub enum LlmApiProvider {
55
Openrouter,
66
OpenAi,
7+
Azure,
78

89
// TODO: Will support in future with more refined SDK
910
// Anthropic,
1011
// Gemini,
1112
// Deepseek,
12-
// Azure,
1313
}
1414

1515
impl From<String> for LlmApiProvider {
1616
fn from(value: String) -> Self {
1717
match value.as_str() {
1818
"openrouter" => LlmApiProvider::Openrouter,
1919
"openai" => LlmApiProvider::OpenAi,
20+
"azure" => LlmApiProvider::Azure,
2021
_ => unreachable!("Invalid Provider"),
2122
}
2223
}
@@ -27,6 +28,7 @@ impl From<LlmApiProvider> for String {
2728
match value {
2829
LlmApiProvider::Openrouter => "openrouter".to_string(),
2930
LlmApiProvider::OpenAi => "openai".to_string(),
31+
LlmApiProvider::Azure => "azure".to_string(),
3032
}
3133
}
3234
}

backend/src/controllers/providers.rs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,45 @@
11
use axum::{
2-
extract::State,
2+
extract::{Path, State},
33
Json,
44
};
55

6-
use super::types::response::providers::ProviderResponse;
6+
use super::types::{
7+
request::providers::UpdateProviderRequest,
8+
response::providers::ProviderResponse,
9+
};
710
use crate::{AppError, AppState};
811

912
pub async fn list_providers(
1013
State(state): State<AppState>,
1114
) -> Result<Json<Vec<ProviderResponse>>, AppError> {
1215
let providers = state.db.provider.list_providers().await?;
13-
Ok(Json(providers.into_iter().map(|p| p.into()).collect()))
16+
17+
let provider_responses: Vec<ProviderResponse> = providers
18+
.into_iter()
19+
.map(|p| {
20+
let mut response: ProviderResponse = p.into();
21+
22+
// Check if API key exists in environment variables and base_url is configured
23+
let api_key_exists = match response.name.as_str() {
24+
"openai" => std::env::var("OPENAI_API_KEY").is_ok(),
25+
"openrouter" => std::env::var("OPENROUTER_API_KEY").is_ok(),
26+
"azure" => std::env::var("AZURE_API_KEY").is_ok(),
27+
_ => false,
28+
};
29+
30+
response.is_available = api_key_exists && response.base_url.is_some();
31+
response
32+
})
33+
.collect();
34+
35+
Ok(Json(provider_responses))
36+
}
37+
38+
pub async fn update_provider(
39+
State(state): State<AppState>,
40+
Path(id): Path<i32>,
41+
Json(payload): Json<UpdateProviderRequest>,
42+
) -> Result<Json<ProviderResponse>, AppError> {
43+
let provider = state.db.provider.update_provider(id, payload.base_url).await?;
44+
Ok(Json(provider.into()))
1445
}

backend/src/controllers/types/request/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod models;
22
pub mod prompts;
33
pub mod prompt_eval;
44
pub mod prompt_eval_run;
5+
pub mod providers;
56
pub mod schema;
67
pub mod user;
78
pub mod tools;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
#[derive(Debug, Serialize, Deserialize)]
4+
pub struct UpdateProviderRequest {
5+
pub base_url: Option<String>,
6+
}

backend/src/controllers/types/response/models.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ pub struct ModelResponse {
88
pub provider_id: i64,
99
pub name: String,
1010
pub provider_name: String,
11-
pub provider_base_url: String,
11+
pub provider_base_url: Option<String>,
1212
pub supports_json: bool,
1313
pub supports_json_schema: bool,
1414
pub supports_tools: bool,

backend/src/controllers/types/response/providers.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@ use crate::db::types::providers::ProviderRow;
66
pub struct ProviderResponse {
77
pub id: i64,
88
pub name: String,
9-
pub base_url: String,
9+
pub base_url: Option<String>,
10+
pub is_available: bool,
1011
}
1112

1213
impl From<ProviderRow> for ProviderResponse {
1314
fn from(row: ProviderRow) -> Self {
1415
Self {
1516
id: row.id,
16-
name: row.name,
17+
name: row.name.clone(),
1718
base_url: row.base_url,
19+
is_available: false, // This will be set in the controller
1820
}
1921
}
2022
}

backend/src/db/providers.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,21 @@ impl ProviderRepository {
6666
.await?;
6767
Ok(provider)
6868
}
69+
70+
pub async fn update_provider(&self, id: i32, base_url: Option<String>) -> Result<ProviderRow> {
71+
let provider = sqlx::query_as!(
72+
ProviderRow,
73+
r#"
74+
UPDATE provider
75+
SET base_url = ?
76+
WHERE id = ?
77+
RETURNING id, name, base_url, created_at
78+
"#,
79+
base_url,
80+
id
81+
)
82+
.fetch_one(&self.pool)
83+
.await?;
84+
Ok(provider)
85+
}
6986
}

0 commit comments

Comments
 (0)