1577-2 / frontend /api_client.py
jts-ai-team's picture
Upload 32 files
ed216da verified
Raw
History Blame Contribute Delete
6.23 kB
# 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}]"