|
|
import os |
|
|
import json |
|
|
from dotenv import load_dotenv |
|
|
from pydantic import BaseModel |
|
|
import google.generativeai as genai |
|
|
from fastapi import FastAPI, HTTPException |
|
|
import uvicorn |
|
|
from prompts import PROMPTS |
|
|
from llm_pipeline import example_odia_answer_json, example_odia_question_json |
|
|
|
|
|
|
|
|
load_dotenv() |
|
|
|
|
|
|
|
|
google_api_key = os.getenv("GOOGLE_API_KEY") |
|
|
if not google_api_key: |
|
|
raise ValueError("GOOGLE_API_KEY not found in environment variables") |
|
|
|
|
|
genai.configure(api_key=google_api_key) |
|
|
model = genai.GenerativeModel(os.getenv("LLM_MODEL", "gemini-pro")) |
|
|
LANGUAGE = "Odia" |
|
|
|
|
|
|
|
|
class QuestionRequest(BaseModel): |
|
|
question: str |
|
|
|
|
|
class LLMResponseModel(BaseModel): |
|
|
question_content: str |
|
|
answer_language: str = LANGUAGE |
|
|
reasoning_content: str |
|
|
answer_content: str |
|
|
|
|
|
def create_prompt(user_odia_question: str) -> str: |
|
|
SIMPLE_PROMPT = PROMPTS["odia_reasoning_generation_prompt"] |
|
|
prompt = SIMPLE_PROMPT.format( |
|
|
user_odia_question=user_odia_question, |
|
|
example_odia_question_json=example_odia_question_json, |
|
|
example_answer_json=example_odia_answer_json |
|
|
) |
|
|
|
|
|
return prompt |
|
|
|
|
|
def chat_with_model(prompt: str) -> str: |
|
|
try: |
|
|
response = model.generate_content(prompt) |
|
|
return response.text if response.text else "Error: Empty response" |
|
|
except Exception as e: |
|
|
return f"Error: {str(e)}" |
|
|
|
|
|
def clean_json_text(text: str) -> str: |
|
|
if text.startswith("Error:"): |
|
|
return text |
|
|
|
|
|
|
|
|
text = text.strip() |
|
|
if text.startswith("```"): |
|
|
lines = text.split('\n') |
|
|
if len(lines) > 2: |
|
|
text = '\n'.join(lines[1:-1]) |
|
|
else: |
|
|
text = text.strip("`").replace("json", "", 1).strip() |
|
|
|
|
|
|
|
|
first = text.find("{") |
|
|
last = text.rfind("}") |
|
|
if first != -1 and last != -1: |
|
|
return text[first:last+1] |
|
|
|
|
|
return text |
|
|
|
|
|
def validate_output(raw_output: str, original_question: str): |
|
|
cleaned = clean_json_text(raw_output) |
|
|
|
|
|
if cleaned.startswith("Error:"): |
|
|
return { |
|
|
"question_content": original_question, |
|
|
"answer_language": LANGUAGE, |
|
|
"reasoning_content": f"Error occurred: {cleaned}", |
|
|
"answer_content": "Unable to generate answer due to error", |
|
|
"error": cleaned |
|
|
} |
|
|
|
|
|
try: |
|
|
|
|
|
parsed_data = json.loads(cleaned) |
|
|
validated = LLMResponseModel(**parsed_data) |
|
|
return validated.model_dump() |
|
|
except json.JSONDecodeError as je: |
|
|
return { |
|
|
"question_content": original_question, |
|
|
"answer_language": LANGUAGE, |
|
|
"reasoning_content": f"JSON parsing failed: {str(je)}", |
|
|
"answer_content": "Unable to parse model response", |
|
|
"error": f"JSON Error: {str(je)}" |
|
|
} |
|
|
except Exception as e: |
|
|
return { |
|
|
"question_content": original_question, |
|
|
"answer_language": LANGUAGE, |
|
|
"reasoning_content": f"Validation failed: {str(e)}", |
|
|
"answer_content": "Unable to validate model response", |
|
|
"error": f"Validation Error: {str(e)}" |
|
|
} |
|
|
|
|
|
def run_pipeline(question: str): |
|
|
try: |
|
|
|
|
|
prompt =create_prompt(user_odia_question=question) |
|
|
raw_output = chat_with_model(prompt) |
|
|
return validate_output(raw_output, question) |
|
|
except Exception as e: |
|
|
return { |
|
|
"question_content": question, |
|
|
"answer_language": LANGUAGE, |
|
|
"reasoning_content": f"Pipeline error: {str(e)}", |
|
|
"answer_content": "Unable to process question", |
|
|
"error": f"Pipeline Error: {str(e)}" |
|
|
} |
|
|
|
|
|
|
|
|
app = FastAPI(title="Odia Question Answering API", version="0.1.0") |
|
|
|
|
|
@app.get("/") |
|
|
async def root(): |
|
|
return {"message": "Odia Question Answering API is running", "status": "healthy"} |
|
|
|
|
|
@app.get("/health") |
|
|
async def health_check(): |
|
|
try: |
|
|
|
|
|
test_response = model.generate_content("Test") |
|
|
return { |
|
|
"status": "healthy", |
|
|
"model": os.getenv("LLM_MODEL", "gemini-pro"), |
|
|
"api_configured": bool(google_api_key) |
|
|
} |
|
|
except Exception as e: |
|
|
return { |
|
|
"status": "unhealthy", |
|
|
"error": str(e), |
|
|
"api_configured": bool(google_api_key) |
|
|
} |
|
|
|
|
|
@app.post("/generate") |
|
|
async def generate_answer(request: QuestionRequest): |
|
|
try: |
|
|
if not request.question.strip(): |
|
|
raise HTTPException(status_code=400, detail="Question cannot be empty") |
|
|
|
|
|
result = run_pipeline(request.question.strip()) |
|
|
|
|
|
|
|
|
if "error" in result and any(err_type in result["error"] for err_type in ["Error: ", "Pipeline Error:"]): |
|
|
raise HTTPException(status_code=500, detail=f"Processing failed: {result['error']}") |
|
|
|
|
|
return {"success": True, "data": result} |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
raise HTTPException(status_code=500, detail=f"Unexpected error: {str(e)}") |
|
|
|
|
|
if __name__ == "__main__": |
|
|
print("Starting Odia Question Answering API...") |
|
|
print(f"Google API Key configured: {'Yes' if google_api_key else 'No'}") |
|
|
print(f"Model: {os.getenv('LLM_MODEL', 'gemini-pro')}") |
|
|
host = os.getenv("ANSWER_SERVICE_HOST", "0.0.0.0") |
|
|
port = int(os.getenv("ANSWER_SERVICE_PORT", "9000")) |
|
|
uvicorn.run(app, host=0.0.0.0, port=9000) |