| use super::models::*; |
| use serde_json::Value; |
|
|
| pub fn transform_openai_response(gemini_response: &Value) -> OpenAIResponse { |
| |
| let raw = gemini_response.get("response").unwrap_or(gemini_response); |
|
|
| |
| let mut content_out = String::new(); |
| let mut tool_calls = Vec::new(); |
| |
| if let Some(parts) = raw.get("candidates") |
| .and_then(|c| c.get(0)) |
| .and_then(|cand| cand.get("content")) |
| .and_then(|content| content.get("parts")) |
| .and_then(|p| p.as_array()) { |
| |
| for part in parts { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| if let Some(text) = part.get("text").and_then(|t| t.as_str()) { |
| content_out.push_str(text); |
| } |
| |
| |
| if let Some(fc) = part.get("functionCall") { |
| let name = fc.get("name").and_then(|v| v.as_str()).unwrap_or("unknown"); |
| let args = fc.get("args").map(|v| v.to_string()).unwrap_or_else(|| "{}".to_string()); |
| let id = fc.get("id").and_then(|v| v.as_str()) |
| .map(|s| s.to_string()) |
| .unwrap_or_else(|| format!("{}-{}", name, uuid::Uuid::new_v4())); |
| |
| tool_calls.push(ToolCall { |
| id, |
| r#type: "function".to_string(), |
| function: ToolFunction { |
| name: name.to_string(), |
| arguments: args, |
| }, |
| }); |
| } |
| |
| |
| if let Some(img) = part.get("inlineData") { |
| let mime_type = img.get("mimeType").and_then(|v| v.as_str()).unwrap_or("image/png"); |
| let data = img.get("data").and_then(|v| v.as_str()).unwrap_or(""); |
| if !data.is_empty() { |
| content_out.push_str(&format!("", mime_type, data)); |
| } |
| } |
| } |
| } |
|
|
| |
| if let Some(grounding) = raw.get("candidates") |
| .and_then(|c| c.get(0)) |
| .and_then(|cand| cand.get("groundingMetadata")) { |
| |
| let mut grounding_text = String::new(); |
| |
| |
| if let Some(queries) = grounding.get("webSearchQueries").and_then(|q| q.as_array()) { |
| let query_list: Vec<&str> = queries.iter().filter_map(|v| v.as_str()).collect(); |
| if !query_list.is_empty() { |
| grounding_text.push_str("\n\n---\n**🔍 已为您搜索:** "); |
| grounding_text.push_str(&query_list.join(", ")); |
| } |
| } |
|
|
| |
| if let Some(chunks) = grounding.get("groundingChunks").and_then(|c| c.as_array()) { |
| let mut links = Vec::new(); |
| for (i, chunk) in chunks.iter().enumerate() { |
| if let Some(web) = chunk.get("web") { |
| let title = web.get("title").and_then(|v| v.as_str()).unwrap_or("网页来源"); |
| let uri = web.get("uri").and_then(|v| v.as_str()).unwrap_or("#"); |
| links.push(format!("[{}] [{}]({})", i + 1, title, uri)); |
| } |
| } |
| |
| if !links.is_empty() { |
| grounding_text.push_str("\n\n**🌐 来源引文:**\n"); |
| grounding_text.push_str(&links.join("\n")); |
| } |
| } |
|
|
| if !grounding_text.is_empty() { |
| content_out.push_str(&grounding_text); |
| } |
| } |
|
|
| |
| let finish_reason = raw |
| .get("candidates") |
| .and_then(|c| c.get(0)) |
| .and_then(|cand| cand.get("finishReason")) |
| .and_then(|f| f.as_str()) |
| .map(|f| match f { |
| "STOP" => "stop", |
| "MAX_TOKENS" => "length", |
| "SAFETY" => "content_filter", |
| "RECITATION" => "content_filter", |
| _ => "stop", |
| }) |
| .unwrap_or("stop"); |
|
|
| OpenAIResponse { |
| id: raw.get("responseId").and_then(|v| v.as_str()).unwrap_or("resp_unknown").to_string(), |
| object: "chat.completion".to_string(), |
| created: chrono::Utc::now().timestamp() as u64, |
| model: raw.get("modelVersion").and_then(|v| v.as_str()).unwrap_or("unknown").to_string(), |
| choices: vec![Choice { |
| index: 0, |
| message: OpenAIMessage { |
| role: "assistant".to_string(), |
| content: if content_out.is_empty() { None } else { Some(OpenAIContent::String(content_out)) }, |
| tool_calls: if tool_calls.is_empty() { None } else { Some(tool_calls) }, |
| tool_call_id: None, |
| name: None, |
| }, |
| finish_reason: Some(finish_reason.to_string()), |
| }], |
| } |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use serde_json::json; |
|
|
| #[test] |
| fn test_transform_openai_response() { |
| let gemini_resp = json!({ |
| "candidates": [{ |
| "content": { |
| "parts": [{"text": "Hello!"}] |
| }, |
| "finishReason": "STOP" |
| }], |
| "modelVersion": "gemini-2.5-pro", |
| "responseId": "resp_123" |
| }); |
|
|
| let result = transform_openai_response(&gemini_resp); |
| assert_eq!(result.object, "chat.completion"); |
| |
| let content = match result.choices[0].message.content.as_ref().unwrap() { |
| OpenAIContent::String(s) => s, |
| _ => panic!("Expected string content"), |
| }; |
| assert_eq!(content, "Hello!"); |
| assert_eq!(result.choices[0].finish_reason, Some("stop".to_string())); |
| } |
| } |
|
|