Spaces:
Sleeping
Sleeping
| # frontend/api_client.py | |
| import os | |
| import httpx | |
| import json | |
| import logging | |
| from typing import AsyncGenerator, Generator, List, Dict | |
| # --- Setup --- | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| # Get API URL from environment variables, with a default for local development | |
| API_BASE_URL = os.getenv("API_BASE_URL", "http://127.0.0.1:8000") | |
| CHAT_ENDPOINT = f"{API_BASE_URL}/chat" | |
| # Use a shared client for connection pooling and better performance | |
| async_client = httpx.AsyncClient(timeout=300.0) | |
| async def stream_chat_from_api(history: List[Dict[str, str]], message: str, user_id: str = "voicebot_user") -> AsyncGenerator[str, None]: | |
| """ | |
| Sends a chat request to the FastAPI backend and streams the response. | |
| This function handles both streaming (text/plain) and JSON (application/json) | |
| responses from the backend. | |
| Args: | |
| history: The conversation history. | |
| message: The latest user message. | |
| user_id: An identifier for the user session. | |
| Yields: | |
| str: Chunks of the response text. | |
| """ | |
| request_payload = { | |
| "user_id": user_id, | |
| "history": history, | |
| "message": message | |
| } | |
| logger.info(f"Sending request to API: {CHAT_ENDPOINT}") | |
| try: | |
| async with async_client.stream("POST", CHAT_ENDPOINT, json=request_payload) as response: | |
| # Check for HTTP errors | |
| response.raise_for_status() | |
| content_type = response.headers.get("content-type", "") | |
| # --- Handle Streaming Response (RAG Pipeline) --- | |
| if "text/plain" in content_type: | |
| logger.info("API returned a streaming response.") | |
| async for chunk in response.aiter_text(): | |
| if chunk: | |
| yield chunk | |
| # --- Handle JSON Response (Non-RAG or Error) --- | |
| elif "application/json" in content_type: | |
| logger.info("API returned a JSON response.") | |
| full_body = await response.aread() | |
| try: | |
| json_response = json.loads(full_body) | |
| reply = json_response.get("reply") | |
| if reply: | |
| yield reply | |
| else: | |
| error_message = f"API JSON response missing 'reply' key: {json_response}" | |
| logger.error(error_message) | |
| yield f"[Error: {error_message}]" | |
| except json.JSONDecodeError: | |
| error_message = "Failed to decode JSON from API." | |
| logger.error(error_message) | |
| yield f"[Error: {error_message}]" | |
| # --- Handle Unexpected Content Types --- | |
| else: | |
| error_message = f"Unexpected content type from API: {content_type}" | |
| logger.error(error_message) | |
| yield f"[Error: {error_message}]" | |
| except httpx.RequestError as e: | |
| error_message = f"API request failed: Could not connect to {e.request.url}." | |
| logger.critical(error_message, exc_info=True) | |
| yield f"[Error: {error_message}]" | |
| except httpx.HTTPStatusError as e: | |
| error_message = f"API returned an error: {e.response.status_code} - {e.response.text}" | |
| logger.error(error_message, exc_info=True) | |
| yield f"[Error: {error_message}]" | |
| except Exception as e: | |
| error_message = f"An unexpected error occurred in the API client: {e}" | |
| logger.error(error_message, exc_info=True) | |
| yield f"[Error: {error_message}]" | |
| def stream_chat_from_api_sync(history: List[Dict[str, str]], message: str, user_id: str = "voicebot_user") -> Generator[str, None, None]: | |
| """Synchronous wrapper for sending a chat request to the FastAPI backend.""" | |
| request_payload = { | |
| "user_id": user_id, | |
| "history": history, | |
| "message": message, | |
| } | |
| logger.info(f"Sending request to API (sync): {CHAT_ENDPOINT}") | |
| try: | |
| with httpx.Client(timeout=300.0) as client: | |
| with client.stream("POST", CHAT_ENDPOINT, json=request_payload) as response: | |
| response.raise_for_status() | |
| content_type = response.headers.get("content-type", "") | |
| if "text/plain" in content_type: | |
| logger.info("API returned a streaming response (sync).") | |
| for chunk in response.iter_text(): | |
| if chunk: | |
| yield chunk | |
| elif "application/json" in content_type: | |
| logger.info("API returned a JSON response (sync).") | |
| try: | |
| json_response = response.json() | |
| reply = json_response.get("reply") | |
| if reply: | |
| yield reply | |
| else: | |
| error_message = f"API JSON response missing 'reply' key: {json_response}" | |
| logger.error(error_message) | |
| yield f"[Error: {error_message}]" | |
| except json.JSONDecodeError: | |
| error_message = "Failed to decode JSON from API." | |
| logger.error(error_message) | |
| yield f"[Error: {error_message}]" | |
| else: | |
| error_message = f"Unexpected content type from API: {content_type}" | |
| logger.error(error_message) | |
| yield f"[Error: {error_message}]" | |
| except httpx.RequestError as e: | |
| error_message = f"API request failed: Could not connect to {e.request.url}." | |
| logger.critical(error_message, exc_info=True) | |
| yield f"[Error: {error_message}]" | |
| except httpx.HTTPStatusError as e: | |
| error_message = f"API returned an error: {e.response.status_code} - {e.response.text}" | |
| logger.error(error_message, exc_info=True) | |
| yield f"[Error: {error_message}]" | |
| except Exception as e: | |
| error_message = f"An unexpected error occurred in the synchronous API client: {e}" | |
| logger.error(error_message, exc_info=True) | |
| yield f"[Error: {error_message}]" | |