Spaces:
Sleeping
Sleeping
| import os | |
| import threading | |
| from pathlib import Path | |
| from typing import Optional | |
| from fastapi import FastAPI, Query, Response | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse | |
| from pydantic import BaseModel | |
| from .agent import ShoppingAgent | |
| from .feedback import caminho_feedback, google_sheets_habilitado, salvar_feedback | |
| from .google_oauth import build_flow, get_authorization_url, load_credentials, save_credentials | |
| from .logger import salvar_log_busca | |
| from .memory import caminho_memoria_negativa | |
| oauth_flow_global = {} | |
| EMBEDDING_PROVIDER = os.getenv("EMBEDDING_PROVIDER", "transformers").strip().lower() | |
| HF_MODEL_REPO = os.getenv("HF_MODEL_REPO", "Ana2012/bertimbau-buscador").strip() | |
| def _env_flag(name, default="true"): | |
| return os.getenv(name, default).strip().lower() in {"1", "true", "yes", "on"} | |
| PRELOAD_AGENT = _env_flag("PRELOAD_AGENT", "true") | |
| LOGS_DIR = os.getenv("LOGS_DIR", "/data/logs") | |
| DATA_DIR = "/data" | |
| app = FastAPI(title="TCC2 Agent API") | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=False, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| agent = None | |
| agent_lock = threading.Lock() | |
| def get_agent(): | |
| global agent | |
| if agent is None: | |
| with agent_lock: | |
| if agent is None: | |
| agent = ShoppingAgent() | |
| return agent | |
| def preload_agent(): | |
| if PRELOAD_AGENT: | |
| get_agent() | |
| class ChatRequest(BaseModel): | |
| query: Optional[str] = None | |
| message: Optional[str] = None | |
| top_k: int = 5 | |
| class FeedbackRequest(BaseModel): | |
| search_id: str | |
| query: str | |
| rank: int | |
| product_id: str | |
| product_name: str | |
| categoria_produto: Optional[str] = None | |
| categoria_inferida: Optional[str] = None | |
| rating: Optional[int] = None | |
| is_helpful: Optional[bool] = None | |
| note: Optional[str] = None | |
| feedback: Optional[str] = None | |
| motivo: Optional[str] = None | |
| score_final: Optional[float] = None | |
| score_semantico: Optional[float] = None | |
| bonus_lexical: Optional[float] = None | |
| penalidade_feedback: Optional[float] = None | |
| user_message: Optional[str] = None | |
| def health(): | |
| runtime = get_agent().runtime_info() if agent is not None else None | |
| return { | |
| "status": "ok", | |
| "agent_ready": agent is not None, | |
| "embedding_provider": EMBEDDING_PROVIDER, | |
| "model_repo": HF_MODEL_REPO, | |
| "preload_agent": PRELOAD_AGENT, | |
| "runtime": runtime, | |
| "feedback_storage": "google_sheets" if google_sheets_habilitado() else "csv", | |
| } | |
| def root(): | |
| return RedirectResponse(url="/docs") | |
| def favicon(): | |
| return Response(status_code=204) | |
| def auth_google(): | |
| try: | |
| flow = build_flow() | |
| authorization_url, state = flow.authorization_url( | |
| access_type="offline", | |
| prompt="consent", | |
| include_granted_scopes="true", | |
| ) | |
| # 🔥 ESSENCIAL | |
| oauth_flow_global[state] = flow | |
| return RedirectResponse(url=authorization_url) | |
| except Exception as exc: | |
| return { | |
| "ok": False, | |
| "error": str(exc), | |
| } | |
| def oauth2callback(code: str = Query(...), state: str = Query(...)): | |
| try: | |
| flow = oauth_flow_global.get(state) | |
| if not flow: | |
| return HTMLResponse( | |
| "<h3>Erro: sessao OAuth expirada ou invalida.</h3>", | |
| status_code=400, | |
| ) | |
| # 🔥 usa o MESMO flow (não recria!) | |
| flow.fetch_token(code=code) | |
| save_credentials(flow.credentials) | |
| # limpa memória | |
| oauth_flow_global.pop(state, None) | |
| return HTMLResponse( | |
| """ | |
| <h3>Autorizacao concluida com sucesso.</h3> | |
| <p>O backend ja pode salvar feedbacks no Google Sheets.</p> | |
| <p>Voce ja pode fechar esta aba.</p> | |
| """ | |
| ) | |
| except Exception as exc: | |
| return HTMLResponse( | |
| f""" | |
| <h3>Erro ao concluir autorizacao Google.</h3> | |
| <p>{str(exc)}</p> | |
| """, | |
| status_code=500, | |
| ) | |
| def auth_status(): | |
| credentials = load_credentials() | |
| connected = credentials is not None and credentials.valid | |
| return { | |
| "google_sheets_connected": connected, | |
| "message": ( | |
| "Google Sheets autorizado e pronto para uso." | |
| if connected | |
| else "Google Sheets ainda nao autorizado. Acesse /auth/google para conectar." | |
| ), | |
| } | |
| def debug_files(): | |
| data_path = Path(DATA_DIR) | |
| logs_path = Path(LOGS_DIR) | |
| feedback_path = Path(caminho_feedback()) | |
| memory_path = Path(caminho_memoria_negativa()) | |
| return { | |
| "data_exists": data_path.exists(), | |
| "logs_exists": logs_path.exists(), | |
| "feedback_exists": feedback_path.exists(), | |
| "negative_memory_exists": memory_path.exists(), | |
| "data_files": sorted(p.name for p in data_path.iterdir()) if data_path.exists() else [], | |
| "logs_files": sorted(p.name for p in logs_path.iterdir()) if logs_path.exists() else [], | |
| "feedback_file": str(feedback_path), | |
| "negative_memory_file": str(memory_path), | |
| "feedback_storage": "google_sheets" if google_sheets_habilitado() else "csv", | |
| } | |
| def debug_feedback(): | |
| feedback_path = Path(caminho_feedback()) | |
| if not feedback_path.exists(): | |
| return {"error": "arquivo nao existe"} | |
| return {"conteudo": feedback_path.read_text(encoding="utf-8")} | |
| def download_feedback(): | |
| feedback_path = caminho_feedback() | |
| if not os.path.exists(feedback_path): | |
| return {"error": "arquivo nao existe"} | |
| return FileResponse(feedback_path, filename="feedback.csv") | |
| def debug_memory(): | |
| memory_path = Path(caminho_memoria_negativa()) | |
| if not memory_path.exists(): | |
| return {"status": "missing", "file": str(memory_path)} | |
| return { | |
| "status": "ok", | |
| "file": str(memory_path), | |
| "content": memory_path.read_text(encoding="utf-8"), | |
| } | |
| def chat(request: ChatRequest): | |
| texto = request.query or request.message | |
| if not texto: | |
| return {"error": "query ou message deve ser informado"} | |
| resultado = get_agent().responder(texto, top_k=request.top_k) | |
| salvar_log_busca(resultado) | |
| return resultado | |
| def feedback(request: FeedbackRequest): | |
| feedback_file = caminho_feedback() | |
| print( | |
| "Salvando feedback:", | |
| { | |
| "query": request.query, | |
| "product_id": request.product_id, | |
| "feedback_file": feedback_file, | |
| "logs_dir_exists": os.path.exists(LOGS_DIR), | |
| "google_sheets_enabled": google_sheets_habilitado(), | |
| }, | |
| ) | |
| try: | |
| return salvar_feedback( | |
| search_id=request.search_id, | |
| query=request.query, | |
| rank=request.rank, | |
| product_id=request.product_id, | |
| product_name=request.product_name, | |
| categoria_produto=request.categoria_produto, | |
| rating=request.rating, | |
| is_helpful=request.is_helpful, | |
| note=request.note, | |
| categoria_inferida=request.categoria_inferida, | |
| feedback=request.feedback, | |
| motivo=request.motivo, | |
| score_final=request.score_final, | |
| score_semantico=request.score_semantico, | |
| bonus_lexical=request.bonus_lexical, | |
| penalidade_feedback=request.penalidade_feedback, | |
| user_message=request.user_message, | |
| ) | |
| except Exception as exc: | |
| return { | |
| "ok": False, | |
| "saved_local": False, | |
| "saved_google_sheets": False, | |
| "detail": str(exc), | |
| "feedback_file": feedback_file, | |
| "logs_dir_exists": os.path.exists(LOGS_DIR), | |
| "google_sheets_enabled": google_sheets_habilitado(), | |
| } |