gbserver/src/services/llm_provider.rs

117 lines
3.4 KiB
Rust
Raw Normal View History

2025-09-17 20:11:56 -03:00
use log::info;
2025-08-14 09:42:05 -03:00
use actix_web::{post, web, HttpRequest, HttpResponse, Result};
use dotenv::dotenv;
2025-08-17 14:43:35 -03:00
use regex::Regex;
2025-08-14 09:42:05 -03:00
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::env;
// OpenAI-compatible request/response structures
#[derive(Debug, Serialize, Deserialize)]
struct ChatMessage {
role: String,
content: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct ChatCompletionRequest {
model: String,
messages: Vec<ChatMessage>,
stream: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
struct ChatCompletionResponse {
id: String,
object: String,
created: u64,
model: String,
choices: Vec<Choice>,
}
#[derive(Debug, Serialize, Deserialize)]
struct Choice {
message: ChatMessage,
finish_reason: String,
}
#[post("/v1/chat/completions")]
2025-08-17 14:43:35 -03:00
async fn chat_completions(body: web::Bytes, _req: HttpRequest) -> Result<HttpResponse> {
// Always log raw POST data
if let Ok(body_str) = std::str::from_utf8(&body) {
2025-09-17 20:11:56 -03:00
info!("POST Data: {}", body_str);
2025-08-17 14:43:35 -03:00
} else {
2025-09-17 20:11:56 -03:00
info!("POST Data (binary): {:?}", body);
2025-08-17 14:43:35 -03:00
}
2025-08-14 09:42:05 -03:00
dotenv().ok();
// Environment variables
let azure_endpoint = env::var("AI_ENDPOINT")
.map_err(|_| actix_web::error::ErrorInternalServerError("AI_ENDPOINT not set."))?;
let azure_key = env::var("AI_KEY")
.map_err(|_| actix_web::error::ErrorInternalServerError("AI_KEY not set."))?;
let deployment_name = env::var("AI_LLM_MODEL")
.map_err(|_| actix_web::error::ErrorInternalServerError("AI_LLM_MODEL not set."))?;
// Construct Azure OpenAI URL
let url = format!(
"{}/openai/deployments/{}/chat/completions?api-version=2025-01-01-preview",
azure_endpoint, deployment_name
);
// Forward headers
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
"api-key",
reqwest::header::HeaderValue::from_str(&azure_key)
.map_err(|_| actix_web::error::ErrorInternalServerError("Invalid Azure key"))?,
);
headers.insert(
"Content-Type",
reqwest::header::HeaderValue::from_static("application/json"),
);
2025-08-17 14:43:35 -03:00
let body_str = std::str::from_utf8(&body).unwrap_or("");
2025-09-17 20:11:56 -03:00
info!("Original POST Data: {}", body_str);
2025-08-17 14:43:35 -03:00
// Remove the problematic params
let re =
Regex::new(r#","?\s*"(max_completion_tokens|parallel_tool_calls)"\s*:\s*[^,}]*"#).unwrap();
let cleaned = re.replace_all(body_str, "");
let cleaned_body = web::Bytes::from(cleaned.to_string());
2025-09-17 20:11:56 -03:00
info!("Cleaned POST Data: {}", cleaned);
2025-08-17 14:43:35 -03:00
2025-08-14 09:42:05 -03:00
// Send request to Azure
let client = Client::new();
let response = client
.post(&url)
.headers(headers)
2025-08-17 14:43:35 -03:00
.body(cleaned_body)
2025-08-14 09:42:05 -03:00
.send()
.await
.map_err(actix_web::error::ErrorInternalServerError)?;
// Handle response based on status
let status = response.status();
let raw_response = response
.text()
.await
.map_err(actix_web::error::ErrorInternalServerError)?;
// Log the raw response
2025-09-17 20:11:56 -03:00
info!("Raw Azure response: {}", raw_response);
2025-08-14 09:42:05 -03:00
if status.is_success() {
2025-08-17 14:43:35 -03:00
Ok(HttpResponse::Ok().body(raw_response))
2025-08-14 09:42:05 -03:00
} else {
// Handle error responses properly
let actix_status = actix_web::http::StatusCode::from_u16(status.as_u16())
.unwrap_or(actix_web::http::StatusCode::INTERNAL_SERVER_ERROR);
Ok(HttpResponse::build(actix_status).body(raw_response))
}
}