feat(providers): model-registry routing + Responses API support and streaming fallbacks for OpenAI/Gemini
This commit is contained in:
@@ -555,13 +555,46 @@ impl super::Provider for GeminiProvider {
|
||||
use futures::StreamExt;
|
||||
use reqwest_eventsource::{Event, EventSource};
|
||||
|
||||
let es = EventSource::new(
|
||||
// Try to create an SSE event source for streaming. If creation fails
|
||||
// (provider doesn't support streaming for this model or returned a
|
||||
// non-2xx response), fall back to a synchronous generateContent call
|
||||
// and emit a single chunk.
|
||||
let es_result = reqwest_eventsource::EventSource::new(
|
||||
self.client
|
||||
.post(&url)
|
||||
.header("x-goog-api-key", &self.api_key)
|
||||
.json(&gemini_request),
|
||||
)
|
||||
.map_err(|e| AppError::ProviderError(format!("Failed to create EventSource: {}", e)))?;
|
||||
);
|
||||
|
||||
if let Err(e) = es_result {
|
||||
// Fallback: call non-streaming path and convert to a single-stream chunk
|
||||
let resp = self.chat_completion(request.clone()).await.map_err(|e2| {
|
||||
AppError::ProviderError(format!("Failed to create EventSource: {} ; fallback error: {}", e, e2))
|
||||
})?;
|
||||
|
||||
let single_stream = async_stream::try_stream! {
|
||||
let chunk = ProviderStreamChunk {
|
||||
content: resp.content,
|
||||
reasoning_content: resp.reasoning_content,
|
||||
finish_reason: Some("stop".to_string()),
|
||||
tool_calls: None,
|
||||
model: resp.model.clone(),
|
||||
usage: Some(super::StreamUsage {
|
||||
prompt_tokens: resp.prompt_tokens,
|
||||
completion_tokens: resp.completion_tokens,
|
||||
total_tokens: resp.total_tokens,
|
||||
cache_read_tokens: resp.cache_read_tokens,
|
||||
cache_write_tokens: resp.cache_write_tokens,
|
||||
}),
|
||||
};
|
||||
|
||||
yield chunk;
|
||||
};
|
||||
|
||||
return Ok(Box::pin(single_stream));
|
||||
}
|
||||
|
||||
let es = es_result.map_err(|e| AppError::ProviderError(format!("Failed to create EventSource: {}", e)))?;
|
||||
|
||||
let stream = async_stream::try_stream! {
|
||||
let mut es = es;
|
||||
@@ -638,7 +671,33 @@ impl super::Provider for GeminiProvider {
|
||||
}
|
||||
Ok(_) => continue,
|
||||
Err(e) => {
|
||||
Err(AppError::ProviderError(format!("Stream error: {}", e)))?;
|
||||
// On streaming errors, attempt a synchronous fallback once.
|
||||
// This handles cases where the provider rejects the SSE
|
||||
// request but supports a non-streaming generateContent call.
|
||||
match self.chat_completion(request.clone()).await {
|
||||
Ok(resp) => {
|
||||
let chunk = ProviderStreamChunk {
|
||||
content: resp.content,
|
||||
reasoning_content: resp.reasoning_content,
|
||||
finish_reason: Some("stop".to_string()),
|
||||
tool_calls: resp.tool_calls.map(|d| d.into_iter().map(|tc| tc.into()).collect()),
|
||||
model: resp.model.clone(),
|
||||
usage: Some(super::StreamUsage {
|
||||
prompt_tokens: resp.prompt_tokens,
|
||||
completion_tokens: resp.completion_tokens,
|
||||
total_tokens: resp.total_tokens,
|
||||
cache_read_tokens: resp.cache_read_tokens,
|
||||
cache_write_tokens: resp.cache_write_tokens,
|
||||
}),
|
||||
};
|
||||
|
||||
yield chunk;
|
||||
break;
|
||||
}
|
||||
Err(err2) => {
|
||||
Err(AppError::ProviderError(format!("Stream error: {} ; fallback error: {}", e, err2)))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user