tarujain8's picture
feat: initialize FastAPI backend and Streamlit frontend for relationship analysis service
8bc63d1
Raw
History Blame Contribute Delete
8.33 kB
import os
import asyncio
from typing import List
from fastapi import FastAPI, File, HTTPException, UploadFile, Form
import sys
from pathlib import Path
# Fix ModuleNotFoundError if user runs from the backend directory
root_dir = Path(__file__).resolve().parent.parent
if str(root_dir) not in sys.path:
sys.path.insert(0, str(root_dir))
from backend.core.logger import get_logger
logger = get_logger(__name__)
from backend.agents.gaurdrail import validate_domain
from backend.agents.analyst import analyze_conversation
from backend.agents.psychology import analyze_psychology
from backend.agents.strategy import generate_strategy
from backend.agents.perspective import detect_perspective
from backend.utils import (
safe_input,
safe_json_parse,
safe_merge
)
from backend.services.ocr_service import (
extract_text_from_images
)
from backend.normalize import normalize_analysis
app = FastAPI()
MAX_FILES = 5
@app.get("/")
async def root():
return {"status": "running"}
@app.post("/detect_perspective")
async def detect_perspective_route(
chat: str = Form(""),
feelings: str = Form(""),
files: List[UploadFile] = File(default=[]),
):
logger.info(f"Detect perspective called. Files: {len(files)}, Chat length: {len(chat)}, Feelings length: {len(feelings)}")
try:
# Save temp files and extract OCR
image_paths = []
for file in files:
file_bytes = await file.read()
if not file_bytes:
continue
path = f"temp_persp_{file.filename}"
with open(path, "wb") as f:
f.write(file_bytes)
image_paths.append(path)
extracted_text = extract_text_from_images(image_paths)
# Cleanup
for path in image_paths:
if os.path.exists(path):
os.remove(path)
final_chat = f"{chat}\n\n[Extracted Text from Images]:\n{extracted_text}" if extracted_text.strip() else chat
result_json = detect_perspective(final_chat, feelings)
result_data = safe_json_parse(result_json)
return result_data
except Exception as e:
logger.error(f"Error in detect_perspective: {e}")
return {"needs_clarification": True, "confidence": 0.0}
@app.post("/analyze")
async def analyze(
chat: str = Form(""),
feelings: str = Form(""),
user_perspective: str = Form(""),
emotional_goal: str = Form(""),
conversation_consistency: str = Form(""),
files: List[UploadFile] = File(default=[]),
):
image_paths = []
logger.info("Analyze endpoint called")
print("CHAT RECEIVED:", repr(chat))
print("FEELINGS RECEIVED:", repr(feelings))
if len(files) > MAX_FILES:
logger.warning("Too many files uploaded")
raise HTTPException(
status_code=400,
detail=f"Upload up to {MAX_FILES} images only."
)
try:
# -----------------------------------
# SAVE FILES
# -----------------------------------
for file in files:
path = f"temp_{file.filename}"
with open(path, "wb") as f:
f.write(await file.read())
image_paths.append(path)
logger.info(f"Saved {len(image_paths)} image(s)")
# -----------------------------------
# OCR
# -----------------------------------
ocr_text = extract_text_from_images(
image_paths
)
logger.info("OCR extraction completed")
# -----------------------------------
# FULL CHAT
# -----------------------------------
full_chat = f"{chat}\n{ocr_text}"
full_chat = safe_input(
full_chat,
"No conversation provided."
)
feelings = safe_input(
feelings,
"No user feelings provided."
)
logger.info("Inputs normalized")
# -----------------------------------
# GUARDRAIL
# -----------------------------------
# -----------------------------------
# ASSEMBLE CONTEXT WITH CLARIFICATION
# -----------------------------------
clarification_context = ""
if user_perspective or emotional_goal:
clarification_context = f"\n\n[USER CLARIFICATION]\nUser Perspective: {user_perspective}\nEmotional Goal: {emotional_goal}\nConversation Consistency: {conversation_consistency}"
guardrail_input = f"Chat:\n{full_chat}\n\nFeelings:\n{feelings}{clarification_context}"
logger.info("Running guardrail")
guardrail_result = validate_domain(
guardrail_input
)
guardrail_result = (
guardrail_result
.strip()
.upper()
)
logger.info(
f"Guardrail result: {guardrail_result}"
)
if guardrail_result == "OUT_OF_SCOPE":
logger.warning(
"Guardrail blocked request"
)
raise HTTPException(
status_code=400,
detail=(
"Velra is focused on emotional communication, "
"relationships, dating dynamics, and psychological connection analysis."
)
)
# -----------------------------------
# PARALLEL AGENTS
# -----------------------------------
logger.info(
"Running analyst + psychology agents"
)
analyst_task = asyncio.to_thread(
analyze_conversation,
full_chat + clarification_context,
feelings + clarification_context
)
psychology_task = asyncio.to_thread(
analyze_psychology,
full_chat + clarification_context,
feelings + clarification_context
)
analyst_raw, psychology_raw = (
await asyncio.gather(
analyst_task,
psychology_task
)
)
logger.info("Agents completed")
# -----------------------------------
# JSON PARSE
# -----------------------------------
analyst = safe_json_parse(
analyst_raw
)
psychology = safe_json_parse(
psychology_raw
)
logger.info("JSON parsing completed")
# -----------------------------------
# STRATEGY CONTEXT
# -----------------------------------
context = f"""
=== RAW CHAT ===
{chat}
=== USER FEELINGS & INTENT ===
{feelings}
=== BEHAVIORAL ANALYSIS ===
{safe_merge(analyst)}
=== PSYCHOLOGICAL ANALYSIS ===
{safe_merge(psychology)}
"""
logger.info("Running strategy agent")
strategy_raw = generate_strategy(
context
)
strategy = safe_json_parse(
strategy_raw
)
logger.info("Strategy completed")
# -----------------------------------
# NORMALIZATION
# -----------------------------------
normalized = normalize_analysis({
"analyst": analyst,
"psychology": psychology,
"strategy": strategy,
})
logger.info(
"Normalization completed"
)
# -----------------------------------
# RESPONSE
# -----------------------------------
return {
"analysis": {
"analyst": analyst,
"psychology": psychology,
"strategy": strategy,
"normalized": normalized
},
"result": normalized,
}
except HTTPException as http_exc:
logger.error(
f"HTTPException: {http_exc.detail}"
)
raise http_exc
except Exception as exc:
logger.exception(
f"Unhandled backend error: {str(exc)}"
)
raise HTTPException(
status_code=500,
detail=str(exc)
) from exc
finally:
# -----------------------------------
# CLEANUP
# -----------------------------------
for path in image_paths:
if os.path.exists(path):
os.remove(path)
logger.info("Temporary files cleaned")