feat: 重构Gemini API调用实现
重构改进: - 参考Python demo.py实现重写Gemini API调用 - 添加完整的配置参数支持 (model_name, max_retries, retry_delay等) - 实现Cloudflare Gateway兼容的API端点格式 - 添加完整的重试机制和错误处理 API端点修正: - 修改为正确的generateContent端点格式 - 支持gemini-2.5-flash等模型配置 - 优化请求头和认证方式 - 改进超时和重试策略 配置增强: - 支持自定义temperature和max_tokens - 可配置重试次数和延迟时间 - 更好的客户端配置管理 - 详细的日志和错误信息 代码结构: - 分离请求发送和响应解析逻辑 - 更清晰的错误处理流程 - 符合Rust最佳实践的异步处理 这个重构应该能解决API调用的兼容性问题。
This commit is contained in:
parent
5d17966c43
commit
62cd15fe82
|
|
@ -12,6 +12,11 @@ pub struct GeminiConfig {
|
|||
pub base_url: String,
|
||||
pub bearer_token: String,
|
||||
pub timeout: u64,
|
||||
pub model_name: String,
|
||||
pub max_retries: u32,
|
||||
pub retry_delay: u64,
|
||||
pub temperature: f32,
|
||||
pub max_tokens: u32,
|
||||
}
|
||||
|
||||
impl Default for GeminiConfig {
|
||||
|
|
@ -20,6 +25,11 @@ impl Default for GeminiConfig {
|
|||
base_url: "https://bowongai-dev--bowong-ai-video-gemini-fastapi-webapp.modal.run".to_string(),
|
||||
bearer_token: "bowong7777".to_string(),
|
||||
timeout: 120,
|
||||
model_name: "gemini-2.5-flash".to_string(),
|
||||
max_retries: 3,
|
||||
retry_delay: 2,
|
||||
temperature: 0.1,
|
||||
max_tokens: 2048,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -31,6 +41,13 @@ struct TokenResponse {
|
|||
expires_in: u64,
|
||||
}
|
||||
|
||||
/// 客户端配置
|
||||
#[derive(Debug)]
|
||||
struct ClientConfig {
|
||||
gateway_url: String,
|
||||
headers: std::collections::HashMap<String, String>,
|
||||
}
|
||||
|
||||
/// Gemini上传响应
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct UploadResponse {
|
||||
|
|
@ -186,6 +203,18 @@ impl GeminiService {
|
|||
Ok(token_response.access_token)
|
||||
}
|
||||
|
||||
/// 创建Gemini客户端配置
|
||||
fn create_gemini_client(&self, access_token: &str) -> ClientConfig {
|
||||
let mut headers = std::collections::HashMap::new();
|
||||
headers.insert("Authorization".to_string(), format!("Bearer {}", access_token));
|
||||
headers.insert("Content-Type".to_string(), "application/json".to_string());
|
||||
|
||||
ClientConfig {
|
||||
gateway_url: format!("{}/google/vertex-ai", self.config.base_url),
|
||||
headers,
|
||||
}
|
||||
}
|
||||
|
||||
/// 上传视频文件到Gemini
|
||||
pub async fn upload_video_file(&mut self, video_path: &str) -> Result<String> {
|
||||
println!("📤 开始上传视频文件: {}", video_path);
|
||||
|
|
@ -262,7 +291,7 @@ impl GeminiService {
|
|||
Ok(file_uri)
|
||||
}
|
||||
|
||||
/// 生成内容分析
|
||||
/// 生成内容分析 (参考Python demo.py实现)
|
||||
pub async fn generate_content_analysis(&mut self, file_uri: &str, prompt: &str) -> Result<String> {
|
||||
println!("🧠 开始生成内容分析...");
|
||||
println!("📁 文件URI: {}", file_uri);
|
||||
|
|
@ -271,11 +300,14 @@ impl GeminiService {
|
|||
// 获取访问令牌
|
||||
let access_token = self.get_access_token().await?;
|
||||
|
||||
// 创建客户端配置
|
||||
let client_config = self.create_gemini_client(&access_token);
|
||||
|
||||
// 格式化GCS URI
|
||||
let formatted_uri = self.format_gcs_uri(file_uri);
|
||||
println!("🔗 格式化后的URI: {}", formatted_uri);
|
||||
|
||||
// 准备请求数据
|
||||
// 准备请求数据,参考demo.py实现
|
||||
let request_data = GenerateContentRequest {
|
||||
contents: vec![ContentPart {
|
||||
role: "user".to_string(),
|
||||
|
|
@ -290,28 +322,62 @@ impl GeminiService {
|
|||
],
|
||||
}],
|
||||
generation_config: GenerationConfig {
|
||||
temperature: 0.1,
|
||||
temperature: self.config.temperature,
|
||||
top_k: 32,
|
||||
top_p: 1.0,
|
||||
max_output_tokens: 2048,
|
||||
max_output_tokens: self.config.max_tokens,
|
||||
},
|
||||
};
|
||||
|
||||
println!("📦 请求数据: {}", serde_json::to_string_pretty(&request_data).unwrap_or_default());
|
||||
|
||||
// 发送生成请求
|
||||
let generate_url = format!("{}/google/vertex-ai/generate", self.config.base_url);
|
||||
// 发送请求到Cloudflare Gateway,参考demo.py
|
||||
let generate_url = format!("{}:generateContent", client_config.gateway_url);
|
||||
println!("📡 生成URL: {}", generate_url);
|
||||
|
||||
let response = self.client
|
||||
.post(&generate_url)
|
||||
.header("Authorization", format!("Bearer {}", access_token))
|
||||
.header("x-google-api-key", &access_token)
|
||||
.header("Content-Type", "application/json")
|
||||
.json(&request_data)
|
||||
.send()
|
||||
.await?;
|
||||
// 重试机制
|
||||
let mut last_error = None;
|
||||
for attempt in 0..self.config.max_retries {
|
||||
println!("🔄 尝试 {}/{}", attempt + 1, self.config.max_retries);
|
||||
|
||||
match self.send_generate_request(&generate_url, &client_config, &request_data).await {
|
||||
Ok(result) => {
|
||||
println!("✅ 成功获取Gemini分析结果");
|
||||
return self.parse_gemini_response_content(&result);
|
||||
}
|
||||
Err(e) => {
|
||||
last_error = Some(e);
|
||||
println!("⚠️ 尝试 {}/{} 失败: {}", attempt + 1, self.config.max_retries, last_error.as_ref().unwrap());
|
||||
|
||||
if attempt < self.config.max_retries - 1 {
|
||||
println!("⏳ 等待 {} 秒后重试...", self.config.retry_delay);
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(self.config.retry_delay)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(anyhow!("内容生成失败,已重试{}次: {}", self.config.max_retries, last_error.unwrap()))
|
||||
}
|
||||
|
||||
/// 发送生成请求
|
||||
async fn send_generate_request(
|
||||
&self,
|
||||
url: &str,
|
||||
client_config: &ClientConfig,
|
||||
request_data: &GenerateContentRequest,
|
||||
) -> Result<GeminiResponse> {
|
||||
let mut request_builder = self.client
|
||||
.post(url)
|
||||
.timeout(tokio::time::Duration::from_secs(self.config.timeout))
|
||||
.json(request_data);
|
||||
|
||||
// 添加请求头
|
||||
for (key, value) in &client_config.headers {
|
||||
request_builder = request_builder.header(key, value);
|
||||
}
|
||||
|
||||
let response = request_builder.send().await?;
|
||||
let status = response.status();
|
||||
let headers = response.headers().clone();
|
||||
|
||||
|
|
@ -321,7 +387,7 @@ impl GeminiService {
|
|||
if !status.is_success() {
|
||||
let error_text = response.text().await.unwrap_or_default();
|
||||
println!("❌ 生成失败响应体: {}", error_text);
|
||||
return Err(anyhow!("生成内容分析失败: {} - {}", status, error_text));
|
||||
return Err(anyhow!("API请求失败: {} - {}", status, error_text));
|
||||
}
|
||||
|
||||
let response_text = response.text().await?;
|
||||
|
|
@ -330,6 +396,15 @@ impl GeminiService {
|
|||
let gemini_response: GeminiResponse = serde_json::from_str(&response_text)
|
||||
.map_err(|e| anyhow!("解析生成响应失败: {} - 响应内容: {}", e, response_text))?;
|
||||
|
||||
if gemini_response.candidates.is_empty() {
|
||||
return Err(anyhow!("API返回结果为空"));
|
||||
}
|
||||
|
||||
Ok(gemini_response)
|
||||
}
|
||||
|
||||
/// 解析Gemini响应内容
|
||||
fn parse_gemini_response_content(&self, gemini_response: &GeminiResponse) -> Result<String> {
|
||||
if let Some(candidate) = gemini_response.candidates.first() {
|
||||
if let Some(part) = candidate.content.parts.first() {
|
||||
println!("✅ 内容分析完成,响应长度: {} 字符", part.text.len());
|
||||
|
|
|
|||
Loading…
Reference in New Issue