Add server implementation
This commit is contained in:
27
Dockerfile
27
Dockerfile
@@ -1,13 +1,30 @@
|
||||
FROM quay.ocp.banorte.com/golden/python-312:latest
|
||||
FROM quay.ocp.banorte.com/golden/python-312:latest AS builder
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.7.12 /uv /uvx /bin/
|
||||
|
||||
ENV UV_COMPILE_BYTECODE=1 \
|
||||
UV_NO_CACHE=1 \
|
||||
UV_NO_DEV=1 \
|
||||
UV_LINK_MODE=copy
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
# Install dependencies first (cached layer as long as lockfile doesn't change)
|
||||
COPY pyproject.toml uv.lock ./
|
||||
RUN uv sync --locked --no-install-project --no-editable
|
||||
|
||||
RUN uv sync
|
||||
# Copy the rest of the project and install it
|
||||
COPY . .
|
||||
RUN uv sync --locked --no-editable
|
||||
|
||||
# --- Final stage: no uv, no build artifacts ---
|
||||
FROM quay.ocp.banorte.com/golden/python-312:latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/.venv /app/.venv
|
||||
COPY config.yaml ./
|
||||
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
|
||||
CMD ["uv", "run", "uvicorn", "rag_eval.server:app", "--host", "0.0.0.0"]
|
||||
CMD ["uvicorn", "va_agent.server:app", "--host", "0.0.0.0"]
|
||||
|
||||
@@ -1,10 +1,142 @@
|
||||
"""FastAPI server exposing the RAG agent endpoint.
|
||||
"""FastAPI server exposing the RAG agent endpoint."""
|
||||
|
||||
NOTE: This file is a stub. The rag_eval module was removed in the
|
||||
lean MCP implementation. This file is kept for reference but is not
|
||||
functional.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import FastAPI
|
||||
import logging
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
app = FastAPI(title="RAG Agent")
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from google.genai.types import Content, Part
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from va_agent.agent import runner
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = FastAPI(title="Vaia Agent")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Request / Response models
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class NotificationPayload(BaseModel):
|
||||
"""Notification context sent alongside a user query."""
|
||||
|
||||
text: str | None = None
|
||||
parameters: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class QueryRequest(BaseModel):
|
||||
"""Incoming query request from the integration layer."""
|
||||
|
||||
phone_number: str
|
||||
text: str
|
||||
type: str = "conversation"
|
||||
notification: NotificationPayload | None = None
|
||||
language_code: str = "es"
|
||||
|
||||
|
||||
class QueryResponse(BaseModel):
|
||||
"""Response returned to the integration layer."""
|
||||
|
||||
response_id: str
|
||||
response_text: str
|
||||
parameters: dict[str, Any] = Field(default_factory=dict)
|
||||
confidence: float | None = None
|
||||
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
"""Standard error body."""
|
||||
|
||||
error: str
|
||||
message: str
|
||||
status: int
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _build_user_message(request: QueryRequest) -> str:
|
||||
"""Compose the text sent to the agent, including notification context."""
|
||||
if request.type == "notification" and request.notification:
|
||||
parts = [request.text]
|
||||
if request.notification.text:
|
||||
parts.append(
|
||||
f"\n[Notificación recibida]: {request.notification.text}"
|
||||
)
|
||||
if request.notification.parameters:
|
||||
formatted = ", ".join(
|
||||
f"{k}: {v}"
|
||||
for k, v in request.notification.parameters.items()
|
||||
)
|
||||
parts.append(f"[Parámetros de notificación]: {formatted}")
|
||||
return "\n".join(parts)
|
||||
return request.text
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Endpoints
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@app.post(
|
||||
"/api/v1/query",
|
||||
response_model=QueryResponse,
|
||||
responses={
|
||||
400: {"model": ErrorResponse},
|
||||
500: {"model": ErrorResponse},
|
||||
503: {"model": ErrorResponse},
|
||||
},
|
||||
)
|
||||
async def query(request: QueryRequest) -> QueryResponse:
|
||||
"""Process a user message and return a generated response."""
|
||||
user_message = _build_user_message(request)
|
||||
session_id = request.phone_number
|
||||
user_id = request.phone_number
|
||||
|
||||
new_message = Content(
|
||||
role="user",
|
||||
parts=[Part(text=user_message)],
|
||||
)
|
||||
|
||||
try:
|
||||
response_text = ""
|
||||
async for event in runner.run_async(
|
||||
user_id=user_id,
|
||||
session_id=session_id,
|
||||
new_message=new_message,
|
||||
):
|
||||
if event.content and event.content.parts:
|
||||
for part in event.content.parts:
|
||||
if part.text and event.author != "user":
|
||||
response_text += part.text
|
||||
except ValueError as exc:
|
||||
logger.exception("Bad request while running agent")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=ErrorResponse(
|
||||
error="Bad Request",
|
||||
message=str(exc),
|
||||
status=400,
|
||||
).model_dump(),
|
||||
) from exc
|
||||
except Exception as exc:
|
||||
logger.exception("Internal error while running agent")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=ErrorResponse(
|
||||
error="Internal Server Error",
|
||||
message="Failed to generate response",
|
||||
status=500,
|
||||
).model_dump(),
|
||||
) from exc
|
||||
|
||||
return QueryResponse(
|
||||
response_id=f"rag-resp-{uuid.uuid4()}",
|
||||
response_text=response_text,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user