llm_link/api/
openai.rs

1use axum::{
2    extract::{Query, State},
3    http::{HeaderMap, StatusCode},
4    response::{IntoResponse, Json},
5    response::Response,
6    body::Body,
7};
8use futures::StreamExt;
9use serde::Deserialize;
10use serde_json::{json, Value};
11use std::convert::Infallible;
12use tracing::{info, warn, error};
13
14use crate::adapters::{ClientAdapter, FormatDetector};
15use crate::api::{AppState, convert};
16
17#[derive(Debug, Deserialize)]
18#[allow(dead_code)]
19pub struct OpenAIChatRequest {
20    #[allow(dead_code)]
21    pub model: String,
22    #[allow(dead_code)]
23    pub messages: Vec<Value>,
24    #[allow(dead_code)]
25    pub stream: Option<bool>,
26    #[allow(dead_code)]
27    pub max_tokens: Option<u32>,
28    #[allow(dead_code)]
29    pub temperature: Option<f32>,
30    #[allow(dead_code)]
31    pub tools: Option<Vec<Value>>,
32    #[allow(dead_code)]
33    pub tool_choice: Option<Value>,
34}
35
36#[derive(Debug, Deserialize)]
37pub struct OpenAIModelsParams {
38    // OpenAI models endpoint parameters (if any)
39}
40
41/// OpenAI Chat Completions API
42#[allow(dead_code)]
43pub async fn chat(
44    headers: HeaderMap,
45    State(state): State<AppState>,
46    Json(request): Json<OpenAIChatRequest>,
47) -> Result<Response, StatusCode> {
48    // API Key 校验
49    enforce_api_key(&headers, &state).await?;
50
51    info!("📝 Received request - model: {}, stream: {:?}, messages count: {}",
52          request.model, request.stream, request.messages.len());
53
54    // 验证模型
55    if !request.model.is_empty() {
56        let validation_result = {
57            let llm_service = state.llm_service.read().await;
58            llm_service.validate_model(&request.model).await
59        };
60
61        match validation_result {
62            Ok(false) => {
63                error!("❌ Model validation failed: model '{}' not found", request.model);
64                return Err(StatusCode::BAD_REQUEST);
65            }
66            Err(e) => {
67                error!("❌ Model validation error: {:?}", e);
68                return Err(StatusCode::INTERNAL_SERVER_ERROR);
69            }
70            Ok(true) => {
71                info!("✅ Model '{}' validated successfully", request.model);
72            }
73        }
74    }
75
76    // 转换消息格式
77    match convert::openai_messages_to_llm(request.messages) {
78        Ok(messages) => {
79            info!("✅ Successfully converted {} messages", messages.len());
80            let model = if request.model.is_empty() { None } else { Some(request.model.as_str()) };
81
82            // 转换 tools 格式
83            let tools = request.tools.map(convert::openai_tools_to_llm);
84            if let Some(ref tools_ref) = tools {
85                info!("🔧 Request includes {} tools", tools_ref.len());
86                // Debug: log the first tool
87                if let Some(first_tool) = tools_ref.first() {
88                    info!("🔧 First tool: {:?}", serde_json::to_value(first_tool).ok());
89                }
90            }
91
92            // 使用流式模式(llm-connector 0.5.6 已修复代理超时问题)
93            let use_streaming = request.stream.unwrap_or(false);
94            if use_streaming {
95                info!("🌊 Using streaming mode");
96                if let Some(ref tools_ref) = tools {
97                    info!("🔧 Streaming with {} tools (llm-connector 0.5.4+ fix applied)", tools_ref.len());
98                }
99                handle_streaming_request(headers, state, model, messages, tools).await
100            } else {
101                info!("📝 Using non-streaming mode");
102                handle_non_streaming_request(state, model, messages, tools).await
103            }
104        }
105        Err(e) => {
106            error!("❌ Failed to convert OpenAI messages: {:?}", e);
107            Err(StatusCode::BAD_REQUEST)
108        }
109    }
110}
111
112/// 处理流式请求
113#[allow(dead_code)]
114async fn handle_streaming_request(
115    headers: HeaderMap,
116    state: AppState,
117    model: Option<&str>,
118    messages: Vec<llm_connector::types::Message>,
119    tools: Option<Vec<llm_connector::types::Tool>>,
120) -> Result<Response, StatusCode> {
121    // 🎯 检测客户端类型(默认使用 OpenAI 适配器)
122    let config = state.config.read().await;
123    let client_adapter = detect_openai_client(&headers, &config);
124    let (_stream_format, _) = FormatDetector::determine_format(&headers);
125    drop(config); // 释放读锁
126    
127    // 使用客户端偏好格式(SSE)
128    let final_format = client_adapter.preferred_format();
129    let content_type = FormatDetector::get_content_type(final_format);
130
131    info!("📡 Starting OpenAI streaming response - Format: {:?} ({})", final_format, content_type);
132
133    let llm_service = state.llm_service.read().await;
134    let stream_result = llm_service.chat_stream_openai(model, messages.clone(), tools.clone(), final_format).await;
135    drop(llm_service); // 显式释放锁
136
137    match stream_result {
138        Ok(rx) => {
139            info!("✅ OpenAI streaming response started successfully");
140
141            // Get config before entering the map closure and clone it for the closure
142            let config = state.config.read().await.clone();
143            let adapted_stream = rx.map(move |data| {
144                // SSE 格式的数据以 "data: " 开头,需要先提取 JSON 部分
145                let json_str = if data.starts_with("data: ") {
146                    &data[6..] // 去掉 "data: " 前缀
147                } else {
148                    &data
149                };
150
151                // 跳过空行和 [DONE] 标记
152                if json_str.trim().is_empty() || json_str.trim() == "[DONE]" {
153                    return data.to_string();
154                }
155
156                // 解析并适配响应数据
157                if let Ok(mut json_data) = serde_json::from_str::<Value>(json_str) {
158                    tracing::debug!("📝 Parsed JSON chunk, applying adaptations...");
159                    client_adapter.apply_response_adaptations(&config, &mut json_data);
160
161                    match final_format {
162                        llm_connector::StreamFormat::SSE => {
163                            format!("data: {}\n\n", json_data)
164                        }
165                        llm_connector::StreamFormat::NDJSON => {
166                            format!("{}\n", json_data)
167                        }
168                        llm_connector::StreamFormat::Json => {
169                            json_data.to_string()
170                        }
171                    }
172                } else {
173                    tracing::debug!("⚠️ Failed to parse chunk as JSON: {}", json_str);
174                    data.to_string()
175                }
176            });
177
178            let body_stream = adapted_stream.map(Ok::<_, Infallible>);
179            let body = Body::from_stream(body_stream);
180
181            let response = Response::builder()
182                .status(200)
183                .header("content-type", content_type)
184                .header("cache-control", "no-cache")
185                .body(body)
186                .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
187
188            Ok(response)
189        }
190        Err(e) => {
191            warn!("⚠️ OpenAI streaming failed, falling back to non-streaming: {:?}", e);
192            handle_non_streaming_request(state, model, messages, tools).await
193        }
194    }
195}
196
197/// 处理非流式请求
198#[allow(dead_code)]
199async fn handle_non_streaming_request(
200    state: AppState,
201    model: Option<&str>,
202    messages: Vec<llm_connector::types::Message>,
203    tools: Option<Vec<llm_connector::types::Tool>>,
204) -> Result<Response, StatusCode> {
205    let llm_service = state.llm_service.read().await;
206    let chat_result = llm_service.chat(model, messages, tools).await;
207
208    match chat_result {
209        Ok(response) => {
210            let openai_response = convert::response_to_openai(response);
211            Ok(Json(openai_response).into_response())
212        }
213        Err(e) => {
214            error!("❌ OpenAI chat request failed: {:?}", e);
215            Err(StatusCode::INTERNAL_SERVER_ERROR)
216        }
217    }
218}
219
220/// OpenAI Models API
221#[allow(dead_code)]
222pub async fn models(
223    headers: HeaderMap,
224    State(state): State<AppState>,
225    Query(_params): Query<OpenAIModelsParams>,
226) -> Result<impl IntoResponse, StatusCode> {
227    enforce_api_key(&headers, &state).await?;
228
229    let llm_service = state.llm_service.read().await;
230    let models_result = llm_service.list_models().await;
231
232    match models_result {
233        Ok(models) => {
234            let openai_models: Vec<Value> = models.into_iter().map(|model| {
235                json!({
236                    "id": model.id,
237                    "object": "model",
238                    "created": chrono::Utc::now().timestamp(),
239                    "owned_by": "system"
240                })
241            }).collect();
242
243            let config = state.config.read().await;
244            let current_provider = match &config.llm_backend {
245                crate::settings::LlmBackendSettings::OpenAI { .. } => "openai",
246                crate::settings::LlmBackendSettings::Anthropic { .. } => "anthropic",
247                crate::settings::LlmBackendSettings::Zhipu { .. } => "zhipu",
248                crate::settings::LlmBackendSettings::Ollama { .. } => "ollama",
249                crate::settings::LlmBackendSettings::Aliyun { .. } => "aliyun",
250                crate::settings::LlmBackendSettings::Volcengine { .. } => "volcengine",
251                crate::settings::LlmBackendSettings::Tencent { .. } => "tencent",
252                crate::settings::LlmBackendSettings::Longcat { .. } => "longcat",
253                crate::settings::LlmBackendSettings::Moonshot { .. } => "moonshot",
254                crate::settings::LlmBackendSettings::Minimax { .. } => "minimax",
255            };
256
257            let response = json!({
258                "object": "list",
259                "data": openai_models,
260                "provider": current_provider,
261            });
262            Ok(Json(response))
263        }
264        Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
265    }
266}
267
268/// OpenAI API Key 认证
269#[allow(dead_code)]
270async fn enforce_api_key(headers: &HeaderMap, state: &AppState) -> Result<(), StatusCode> {
271    let config = state.config.read().await;
272    if let Some(cfg) = &config.apis.openai {
273        if cfg.enabled {
274            if let Some(expected_key) = cfg.api_key.as_ref() {
275                let header_name = cfg.api_key_header.as_deref().unwrap_or("authorization").to_ascii_lowercase();
276                
277                let value_opt = if header_name == "authorization" {
278                    headers.get(axum::http::header::AUTHORIZATION)
279                } else {
280                    match axum::http::HeaderName::from_bytes(header_name.as_bytes()) {
281                        Ok(name) => headers.get(name),
282                        Err(_) => None,
283                    }
284                };
285
286                if let Some(value) = value_opt {
287                    if let Ok(value_str) = value.to_str() {
288                        let token = if value_str.starts_with("Bearer ") {
289                            &value_str[7..]
290                        } else {
291                            value_str
292                        };
293
294                        if token == expected_key {
295                            info!("✅ OpenAI API key authentication successful");
296                            return Ok(());
297                        }
298                    }
299                }
300
301                warn!("🚫 OpenAI API key authentication failed");
302                return Err(StatusCode::UNAUTHORIZED);
303            }
304        }
305    }
306    Ok(())
307}
308
309/// 检测 OpenAI 客户端类型
310#[allow(dead_code)]
311fn detect_openai_client(_headers: &HeaderMap, _config: &crate::settings::Settings) -> ClientAdapter {
312    // OpenAI API 总是使用 OpenAI 适配器
313    ClientAdapter::OpenAI
314}