Course EN
← back to chapter

Llenado de formularios

Escribe un gasto como lo garabatearías en un recibo — "lunch with the client,

Showcase — Llenado de formularios

Escribe un gasto como lo garabatearías en un recibo — "lunch with the client, 38.50 eur, last tuesday" — y recibe de vuelta un registro validado listo para insertar: merchant, amount como número real, currency, una category de tu conjunto y una date. El schema hace doble trabajo: le da forma a la petición y valida la respuesta.

Córrelo

bash bootstrap-secrets.sh              # reads ../../../../.env, writes secrets/
docker compose up --build              # default: PROVIDER=openai

Abre http://localhost:3000.

Para correrlo contra Gemini:

PROVIDER=gemini docker compose up --build

Qué hay aquí

  • backend/ai_openai.py — el schema Expense (tipado + Literal category) y la llamada a OpenAI
  • backend/ai_gemini.py — mismo schema, llamada a Gemini
  • backend/main.py — loader de FastAPI idéntico; lee PROVIDER y despacha
  • frontend/app/page.tsx — textarea + resultado (renderizado como JSON)
  • docker-compose.yml — dos servicios, secrets montados desde ./secrets/

run(input: str) -> str regresa el registro validado como JSON legible, manteniendo el contrato idéntico entre los showcases de la semana.

Detenlo

docker compose down

Ejecútalo en tu máquina

Descarga el proyecto como ZIP y córrelo con Docker. Levanta un backend FastAPI y un frontend Next.js en localhost:3000.

Descargar form-filler.zip

unzip form-filler.zip
cd form-filler
bash bootstrap-secrets.sh   # one-time: pulls API keys into ./secrets
docker compose up --build   # default provider: openai
# or:  PROVIDER=gemini docker compose up --build

Escribe algo, elige un proveedor y ejecuta el mismo código de Código contra la API real. Requiere iniciar sesión.


  

Los mismos módulos que ejecuta el botón Run. El proyecto completo (frontend, Dockerfile, compose) está en el ZIP, pestaña README.

backend/ai_openai.py

"""Week 4 - Showcase 3 (OpenAI): turn a messy note into a validated record.

This is the one closest to real work: a one-line expense note becomes a
structured row ready to insert. The schema does double duty — it tells the
model the shape and it validates the reply, so amount is a number and
category is one of yours, not whatever the model felt like typing.
"""
import json
from typing import Literal

from openai import OpenAI
from pydantic import BaseModel

_client = OpenAI()


class Expense(BaseModel):
    merchant: str
    amount: float
    currency: str
    category: Literal["travel", "meals", "software", "hardware", "other"]
    date: str


def run(text: str) -> str:
    response = _client.responses.parse(
        model="gpt-5.4-nano",
        input="Turn this expense note into a structured record. Infer the "
              "category. Use ISO format for the date if one is given.\n\n"
              f"{text.strip()}",
        text_format=Expense,
    )
    return json.dumps(response.output_parsed.model_dump(), indent=2)

backend/ai_gemini.py

"""Week 4 - Showcase 3 (Gemini): turn a messy note into a validated record."""
import json
import os
from typing import Literal

from google import genai
from google.genai import types
from pydantic import BaseModel

_client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])


class Expense(BaseModel):
    merchant: str
    amount: float
    currency: str
    category: Literal["travel", "meals", "software", "hardware", "other"]
    date: str


def run(text: str) -> str:
    response = _client.models.generate_content(
        model="gemini-3.1-flash-lite",
        contents="Turn this expense note into a structured record. Infer the "
                 "category. Use ISO format for the date if one is given.\n\n"
                 f"{text.strip()}",
        config=types.GenerateContentConfig(
            response_mime_type="application/json",
            response_schema=Expense,
        ),
    )
    return json.dumps(response.parsed.model_dump(), indent=2)

Archivos del proyecto

  • .gitignore
  • README.es.md
  • README.md
  • backend/Dockerfile
  • backend/ai_gemini.py
  • backend/ai_openai.py
  • backend/main.py
  • backend/requirements.txt
  • bootstrap-secrets.sh
  • docker-compose.yml
  • frontend/Dockerfile
  • frontend/app/layout.tsx
  • frontend/app/page.tsx
  • frontend/next.config.ts
  • frontend/package.json
  • frontend/tsconfig.json