Stream Gemini API response and convert to OpenAI SSE format.
(self, request_data: dict, target_model: str)
| 261 | return openai_response |
| 262 | |
| 263 | async def _stream_gemini_response(self, request_data: dict, target_model: str): |
| 264 | """Stream Gemini API response and convert to OpenAI SSE format.""" |
| 265 | # Convert messages to Gemini format |
| 266 | messages = request_data.get("messages", []) |
| 267 | system_instruction, contents = self._convert_messages_to_gemini_format(messages) |
| 268 | |
| 269 | # Prepare Gemini API call |
| 270 | gemini_url = f"https://generativelanguage.googleapis.com/v1beta/models/{target_model}:streamGenerateContent?alt=sse" |
| 271 | payload = {"contents": contents} |
| 272 | |
| 273 | # Add system_instruction if present |
| 274 | if system_instruction: |
| 275 | payload["system_instruction"] = system_instruction |
| 276 | |
| 277 | # Add generationConfig if needed |
| 278 | generation_config = {} |
| 279 | if "temperature" in request_data: generation_config["temperature"] = request_data["temperature"] |
| 280 | if "max_tokens" in request_data: generation_config["maxOutputTokens"] = request_data["max_tokens"] |
| 281 | if generation_config: payload["generationConfig"] = generation_config |
| 282 | |
| 283 | headers = { |
| 284 | "Content-Type": "application/json", |
| 285 | "x-goog-api-key": self.api_key, |
| 286 | "User-Agent": "ObserverAI-FastAPI-Client/1.0" |
| 287 | } |
| 288 | |
| 289 | logger.info(f"Streaming Gemini API: model={target_model}, messages={len(contents)}, system_instruction={system_instruction is not None}") |
| 290 | |
| 291 | try: |
| 292 | client = get_http_client() |
| 293 | async with client.stream("POST", gemini_url, headers=headers, json=payload) as response: |
| 294 | response.raise_for_status() |
| 295 | |
| 296 | chunk_id = "gemini-chatcmpl-" + secrets.token_hex(12) |
| 297 | chunk_index = 0 |
| 298 | |
| 299 | async for line in response.aiter_lines(): |
| 300 | if line.startswith("data: "): |
| 301 | chunk_data = line[6:] # Remove "data: " prefix |
| 302 | if chunk_data.strip(): |
| 303 | try: |
| 304 | gemini_chunk = json.loads(chunk_data) |
| 305 | # Convert Gemini chunk to OpenAI format |
| 306 | openai_chunk = self._convert_gemini_chunk_to_openai( |
| 307 | gemini_chunk, chunk_id, chunk_index, target_model |
| 308 | ) |
| 309 | if openai_chunk: |
| 310 | yield f"data: {json.dumps(openai_chunk)}\n\n" |
| 311 | chunk_index += 1 |
| 312 | except json.JSONDecodeError: |
| 313 | # Skip invalid JSON chunks |
| 314 | continue |
| 315 | |
| 316 | # Send [DONE] when finished |
| 317 | yield f"data: [DONE]\n\n" |
| 318 | |
| 319 | except httpx.RequestError as exc: |
| 320 | logger.error(f"Gemini streaming API request failed: {exc}") |
no test coverage detected