This application integrates Google’s Gemini AI to enhance Africa’s Talking services with intelligent, context-aware responses. The AI integration is designed to be modular, reusable, and production-ready with built-in error handling and retry logic.
AI integration is configured through environment variables:
utils/ai_utils.py
import osfrom dotenv import load_dotenvfrom google import genaiload_dotenv()# Setup Gemini clientGEMINI_API_KEY = os.getenv("GEMINI_API_KEY")if not GEMINI_API_KEY: raise ValueError("Missing GEMINI_API_KEY in environment")client = genai.Client(api_key=GEMINI_API_KEY)# Default model (can override in function calls)DEFAULT_MODEL = os.getenv("MODEL_ID", "gemini-2.5-flash")
The integration validates the API key on module import, failing fast if credentials are missing. This prevents runtime errors during request processing.
from utils.ai_utils import ask_gemini# Get customer support responseuser_question = "How do I check my account balance?"response = ask_gemini(f"You are a customer support agent. Answer this question: {user_question}")# Returns: "To check your account balance, dial *123# and select option 1..."
The ask_gemini_as_xml() function wraps AI responses in XML format, perfect for Voice API:
utils/ai_utils.py
def ask_gemini_as_xml( prompt: str, model: str = DEFAULT_MODEL, root_tag: str = "Response") -> str: """ Ask Gemini and wrap the response inside an XML response. Useful for USSD/Voice APIs. """ text = _call_gemini(prompt, model) xml = f'<?xml version="1.0" encoding="UTF-8"?>\n<{root_tag}>\n <Say>{text}</Say>\n</{root_tag}>' return xml
Usage Example:
from utils.ai_utils import ask_gemini_as_xml@voice_bp.route("/ai-greeting", methods=["POST"])def ai_voice_greeting(): caller = request.values.get("callerNumber") prompt = f"Generate a friendly greeting for a caller from {caller}" # Returns properly formatted Voice API XML return Response(ask_gemini_as_xml(prompt), mimetype="text/plain")
Output:
<?xml version="1.0" encoding="UTF-8"?><Response> <Say>Hello! Thank you for calling. How can I assist you today?</Say></Response>
The ask_gemini_structured() function requests formatted responses (JSON, XML, etc.):
utils/ai_utils.py
def ask_gemini_structured( prompt: str, model: str = DEFAULT_MODEL, output_format: str = "json") -> str: """ Ask Gemini and request a structured response. Output format can be 'json', 'xml', or any custom instruction. """ structured_prompt = f"Respond in {output_format.upper()} format only. {prompt}" return _call_gemini(structured_prompt, model)
Usage Example:
from utils.ai_utils import ask_gemini_structuredimport json# Extract structured information from user inputuser_message = "I want to send 500 KES to +254711000222"prompt = f"Extract phone number and amount from: {user_message}"response = ask_gemini_structured(prompt, output_format="json")data = json.loads(response)# {"phone": "+254711000222", "amount": 500, "currency": "KES"}
The most critical aspect of production AI integration is robust error handling. The _call_gemini() internal function implements exponential backoff retry logic:
utils/ai_utils.py
import timefrom google.api_core.exceptions import GoogleAPIErrordef _call_gemini(prompt: str, model: str, retries: int = 3, delay: float = 2.0): """ Internal helper to call Gemini with retry logic. Retries on network or API errors. """ last_error = None for attempt in range(1, retries + 1): try: response = client.models.generate_content( model=model, contents=prompt, ) if hasattr(response, "text") and response.text: return response.text.strip() raise ValueError("Empty response from Gemini") except (GoogleAPIError, ValueError, Exception) as e: last_error = e print(f"⚠️ Gemini call failed (attempt {attempt}/{retries}): {e}") if attempt < retries: time.sleep(delay * attempt) # exponential backoff raise RuntimeError(f"Gemini request failed after {retries} retries: {last_error}")
If the call fails due to GoogleAPIError, ValueError, or any exception, it’s caught.
3
Log and Wait
The error is logged with attempt number: ⚠️ Gemini call failed (attempt 1/3)
4
Exponential Backoff
Wait time increases with each retry:
Attempt 1: Wait 2 seconds (delay * 1)
Attempt 2: Wait 4 seconds (delay * 2)
Attempt 3: Wait 6 seconds (delay * 3)
5
Retry or Fail
After 3 attempts, if still failing, raise RuntimeError with the last error.
Why Exponential Backoff? This pattern gives temporary issues (network glitches, rate limits) time to resolve without overwhelming the API with rapid retries.
from flask import Blueprint, request, Responsefrom utils.ai_utils import ask_gemini_as_xmlvoice_bp = Blueprint("voice", __name__)@voice_bp.route("/ai-support", methods=["POST"])def ai_voice_support(): caller = request.values.get("callerNumber") # Get personalized greeting based on time of day prompt = "Generate a brief, professional phone greeting for a customer calling a support line" try: # Returns properly formatted XML for Voice API xml_response = ask_gemini_as_xml(prompt) return Response(xml_response, mimetype="text/plain") except Exception as e: # Fallback to static response fallback = '<?xml version="1.0" encoding="UTF-8"?><Response><Say>Welcome to customer support. Please hold.</Say></Response>' return Response(fallback, mimetype="text/plain")
The default model is gemini-2.5-flash, optimized for speed and cost. You can override per request:
# Fast, cost-effective (default)response = ask_gemini(prompt, model="gemini-2.5-flash")# More capable for complex tasksresponse = ask_gemini(prompt, model="gemini-2.0-pro")# Or set globally via environmentMODEL_ID=gemini-2.0-pro
gemini-2.5-flash
Best for: Quick responses, USSD/SMS, real-time interactionsSpeed: Very fastCost: Lower
gemini-2.0-pro
Best for: Complex reasoning, structured data, nuanced responsesSpeed: ModerateCost: Higher
# Good: Clear and specificprompt = "Summarize in 1 sentence: User wants account balance"# Avoid: Overly verboseprompt = "Please help me understand what the user is asking about and then provide a comprehensive detailed response..."
# Instead of parsing free textresponse = ask_gemini("What's the phone number in: 'call me at 0711000111'")# Returns: "The phone number is 0711000111" (needs parsing)# Use structured formatresponse = ask_gemini_structured( "Extract phone from: 'call me at 0711000111'", output_format="json")# Returns: {"phone": "+254711000111"} (ready to use)