refactor: rename API and frontend projects to Vocero
Rename the project directories, Python distribution and import package, and npm package. Update launcher, Docker, tooling, tests, lockfiles, and documentation while preserving existing API contracts. Make AGENTS.md self-contained and remove external skill and workflow references.
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import httpx
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from vocero_api.api.responses import validated_json_response
|
||||
from vocero_api.core import config as settings
|
||||
from vocero_api.core import logging as app_logging
|
||||
from vocero_api.models import requests as models_requests
|
||||
from vocero_api.models.responses import SearchResponse
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/api/search", response_model=SearchResponse)
|
||||
async def search(req: models_requests.SearchRequest) -> JSONResponse:
|
||||
"""Proxy a Google search via Serper.dev. The key stays on the server unless
|
||||
the user brought their own (then theirs is used for this request only)."""
|
||||
query = (req.query or "").strip()
|
||||
if not query:
|
||||
raise HTTPException(status_code=400, detail="La consulta está vacía.")
|
||||
|
||||
key = (req.key or "").strip() or settings.SERPER_KEY
|
||||
has_user_key = bool((req.key or "").strip())
|
||||
if not key:
|
||||
# No server key and the user didn't supply one — search is unavailable.
|
||||
raise HTTPException(status_code=503, detail="La búsqueda no está configurada.")
|
||||
|
||||
headers = {"X-API-KEY": key, "Content-Type": "application/json"}
|
||||
payload = {"q": query, "num": settings.MAX_RESULTS}
|
||||
app_logging.logger.debug(
|
||||
"[EXT][SERPER] search POST %s query=%s userKey=%s num=%d",
|
||||
settings.SERPER_URL,
|
||||
json.dumps(query, ensure_ascii=True)[:200],
|
||||
has_user_key,
|
||||
settings.MAX_RESULTS,
|
||||
)
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=12.0) as http:
|
||||
resp = await http.post(settings.SERPER_URL, headers=headers, json=payload)
|
||||
except httpx.RequestError as exc:
|
||||
app_logging.logger.warning("Serper unreachable: %r", exc)
|
||||
raise HTTPException(status_code=502, detail="No se puede conectar con el proveedor de búsqueda.") # noqa: B904
|
||||
app_logging.logger.debug(
|
||||
"[EXT][SERPER] search RESP status=%s len=%dB",
|
||||
resp.status_code,
|
||||
len(resp.content or b""),
|
||||
)
|
||||
|
||||
if resp.status_code != 200:
|
||||
# Serper's error body carries the real reason (e.g. "Not enough
|
||||
# credits") and contains no key, so it's safe to log and relay.
|
||||
body = resp.text[:300]
|
||||
app_logging.logger.warning("Serper error %s: %s", resp.status_code, body)
|
||||
msg = None
|
||||
try:
|
||||
msg = resp.json().get("message")
|
||||
except Exception:
|
||||
pass
|
||||
detail = f"Error del proveedor de búsqueda ({resp.status_code})"
|
||||
if msg:
|
||||
detail += f": {msg}"
|
||||
raise HTTPException(status_code=502, detail=detail)
|
||||
|
||||
data = resp.json()
|
||||
results = []
|
||||
for item in (data.get("organic") or [])[: settings.MAX_RESULTS]:
|
||||
results.append(
|
||||
{
|
||||
"title": item.get("title", ""),
|
||||
"snippet": item.get("snippet", ""),
|
||||
"url": item.get("link", ""),
|
||||
}
|
||||
)
|
||||
|
||||
# A direct answer when Google has one — saves the model a hop.
|
||||
box = data.get("answerBox") or {}
|
||||
answer = box.get("answer") or box.get("snippet") or None
|
||||
if not answer:
|
||||
kg = data.get("knowledgeGraph") or {}
|
||||
answer = kg.get("description") or None
|
||||
|
||||
app_logging.logger.debug(
|
||||
"[EXT][SERPER] search PARSED hasAnswer=%s resultCount=%d titles=%s",
|
||||
bool(answer),
|
||||
len(results),
|
||||
[r.get("title", "")[:40] for r in results],
|
||||
)
|
||||
return validated_json_response({"query": query, "answer": answer, "results": results}, SearchResponse)
|
||||
Reference in New Issue
Block a user