Modelo de IA · para todos los temas de hoy

Aquí se define MODELO, y se ejecuta solo al abrir el día. Para usar otro modelo o servidor, cámbialo, pulsa Ejecutar y vuelve a crear tus agentes.

import os

os.environ["OLLAMA_BASE_URL"] = "https://ollama.ricardorobles.es/v1"
MODELO = "ollama:granite4:350m"

Tema 1 de 8 · 5 min

¿Qué es un LLM y cómo se usa desde Python?

Modelos de lenguaje (LLM)

Un LLM (Large Language Model, modelo de lenguaje grande) es un programa entrenado con enormes cantidades de texto para predecir la siguiente palabra. Haciendo eso muy bien, es capaz de resumir, traducir, explicar, programar o crear preguntas de examen. ChatGPT, Gemini o Claude son ejemplos.

Hasta ahora los has usado desde una web de chat. Hoy vas a usarlos desde tus propios programas.

Modelos en la nube y modelos abiertos

Modelos en la nube Modelos abiertos (open weights)
Ejemplos ChatGPT, Gemini, Claude Granite, Llama, Gemma, Mistral
¿Dónde se ejecutan? En los servidores de la empresa En tu máquina (o la que tú elijas)
¿Qué necesitas? Una clave de API (normalmente de pago) Una GPU y descargar el modelo
Capacidad Los más potentes Algo menos potentes, pero muy útiles
Privacidad Tus datos salen hacia el proveedor Tus datos no salen de la máquina

Hoy usaremos un modelo abierto, Granite 4 (de IBM), que está instalado en un servidor del curso con Ollama. Lo usaremos desde esta misma web, igual que los días anteriores: sin claves, sin cuentas y sin instalar nada.

Las piezas que vamos a usar

Tu navegador: Python (PyScript) + Pydantic AI
   │
   │  petición HTTPS por Internet
   ▼
Servidor del curso: Ollama
   │
   ▼
Modelo granite4:350m
  • Ollama: un programa que descarga modelos abiertos y los sirve a través de una API, igual que lo harían OpenAI o Google, pero en un servidor propio.
  • Pydantic AI: la librería de Python con la que hablaremos con el modelo.

¿Por qué Pydantic AI?

  • Funciona con muchos proveedores (Ollama, Google Gemini, OpenAI, Anthropic, Mistral…) cambiando una sola línea.
  • Permite pedir respuestas estructuradas: en lugar de un texto libre, la IA te devuelve objetos de Python con los campos que tú definas. ¡Con clases, como las de ayer!
  • Lo crea el mismo equipo que hizo Pydantic, la librería de validación de datos más usada de Python.

El objetivo de hoy

Construir un asistente de estudio que, a partir del texto de tus apuntes:

  1. 📝 Genere un resumen con las ideas clave.
  2. ❓ Cree un test de preguntas con varias opciones.
  3. 🎮 Te haga el test de forma interactiva y te diga tu nota.

Tema 2 de 8 · 5 min

El modelo del curso

Arriba del todo, en todos los temas de hoy, está el bloque Modelo de IA. Define la variable MODELO que usan todos los ejemplos y se ejecuta solo al abrir el día. Usamos granite4:350m, un modelo abierto de IBM, instalado en el servidor Ollama del curso.

Probar el modelo

from pydantic_ai import Agent

# MODELO es el texto "proveedor:modelo", aquí "ollama:granite4:350m".
# Ollama está pensado para ejecutarse en tu propio ordenador y el nuestro está en un
# servidor (un VPS), por eso arriba le decimos dónde está con OLLAMA_BASE_URL.
#
# Con los proveedores oficiales no hace falta: Pydantic AI ya sabe a qué servidor
# llamar (solo necesita tu clave de API). Por ejemplo:
#     agente = Agent("openai:gpt-5.1-mini")
#     agente = Agent("google:gemini-2.5-flash")
agente = Agent(MODELO, instructions="Responde en español, en una frase.")
resultado = await agente.run("¿Qué es una lista en Python?")
print(resultado.output)
print("Ha respondido:", resultado.response.model_name)

La primera vez tarda unos segundos más, porque el navegador carga Pydantic AI y los 25 paquetes que necesita.

Las dos líneas del bloque Modelo de IA

os.environ["OLLAMA_BASE_URL"] = "https://ollama.ricardorobles.es/v1"
MODELO = "ollama:granite4:350m"
  • OLLAMA_BASE_URL: dónde está el servidor Ollama. El /v1 final indica que usamos su API compatible con la de OpenAI.
  • MODELO: qué modelo usar, con el formato "proveedor:modelo". Pydantic AI lee el proveedor (ollama) y busca la dirección en OLLAMA_BASE_URL.

Este mismo código funciona igual en Google Colab o en tu ordenador (después de instalar la librería con pip install "pydantic-ai-slim[openai]").

Cambiar de modelo o de servidor

MODELO es una variable normal de Python. Para usar otro modelo u otro servidor:

  1. Cambia el nombre del modelo o la dirección de OLLAMA_BASE_URL en el bloque Modelo de IA de arriba.
  2. Pulsa Ejecutar en ese bloque.
  3. Vuelve a ejecutar los bloques que crean agentes (Agent(MODELO, ...)): cada agente se queda con el modelo que tenía MODELO cuando se creó.

Tu cambio se guarda en el navegador; el botón ↺ del bloque vuelve a los valores del curso.

💡 Más rápido en tu ordenador: instala Ollama, descarga el modelo con ollama pull granite4:350m y pon OLLAMA_BASE_URL a "http://localhost:11434/v1". Si la web no la abres desde tu propio ordenador (localhost), arranca Ollama con OLLAMA_ORIGINS=* para que el navegador pueda conectarse.

Ten en cuenta

  • Es un modelo diminuto: granite4:350m tiene 350 millones de parámetros; ChatGPT o Gemini tienen miles de veces más. Es rápido y gratuito, pero se equivoca a menudo. Desconfía de lo que diga y compruébalo.
  • Contesta en inglés si no se lo pides en español. Por eso casi todos nuestros agentes llevan instrucciones.
  • El servidor es compartido: si toda la clase pregunta a la vez, las respuestas se ponen en cola y tardan más. Si una petición se eterniza, pulsa ■ Detener.
¿Y si quiero mi propio servidor en Colab?

Puedes ejecutar Ollama en la GPU gratuita de Google Colab (Entorno de ejecución → Cambiar tipo de entorno → GPU T4) y usarlo desde ese mismo cuaderno:

!apt-get -qq update > /dev/null && apt-get -qq install -y zstd > /dev/null
!curl -fsSL https://ollama.com/install.sh | sh

import subprocess, time
servidor = subprocess.Popen(["ollama", "serve"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
time.sleep(5)
!ollama pull granite4:350m
!pip install -q "pydantic-ai-slim[openai]"

import os

os.environ["OLLAMA_BASE_URL"] = "http://localhost:11434/v1"
MODELO = "ollama:granite4:350m"

Recuerda que en Colab, igual que aquí, se usa await agente.run(...).

Tema 3 de 8 · 7 min

Tu primer agente

En Pydantic AI, un agente (Agent) es un objeto que sabe hablar con un modelo de IA. Sí, un objeto: Agent es una clase, como las que creamos ayer.

Hola, IA

from pydantic_ai import Agent

agente = Agent(MODELO)

resultado = await agente.run("Explica qué es una variable en Python en una sola frase.")
print(resultado.output)
  • Agent(MODELO) crea el agente (instanciamos la clase). MODELO viene del bloque Modelo de IA de arriba.
  • agente.run(...) envía la pregunta al modelo (al servidor del curso).
  • resultado.output contiene la respuesta.

¿Te ha contestado en inglés? Es normal con un modelo tan pequeño: en el siguiente apartado le daremos instrucciones.

¿Y ese await?

El modelo tarda unos segundos en generar la respuesta. await significa “espera a que termine esta operación”. En esta web (y en Colab) se puede usar directamente en los bloques.

💡 En un archivo .py normal usarías la versión síncrona, sin await: resultado = agente.run_sync("..."). Aquí run_sync no funciona, así que usa siempre await agente.run(...).

⚠️ Comprueba lo que dice. Nuestro modelo es muy pequeño y a veces se inventa cosas. ¿Es correcta su explicación de lo que es una variable?

Darle instrucciones

Las instrucciones (también llamadas system prompt) definen el papel y el estilo del agente para todas las preguntas:

profesor = Agent(
    MODELO,
    instructions=(
        "Eres un profesor de Python para estudiantes de primer curso de universidad. "
        "Respondes en español, en dos o tres frases como máximo."
    ),
)

resultado = await profesor.run("¿Para qué sirve self en una clase?")
print(resultado.output)

Prueba a cambiar las instrucciones: “Respondes como un pirata”, “Respondes solo con emojis”… Verás cómo cambia la respuesta.

Mantener una conversación

Cada llamada a run es independiente: el modelo no recuerda nada. Para continuar una conversación, le pasamos el historial de mensajes anteriores:

r1 = await profesor.run("¿Qué es una lista?")
print(r1.output)

r2 = await profesor.run(
    "¿Y en qué se diferencia de una tupla?",
    message_history=r1.all_messages(),
)
print(r2.output)

Sin message_history, el modelo no sabría a qué se refiere “se diferencia”.

Mini reto

Crea un agente que actúe como traductor de español a inglés y que solo devuelva la traducción, sin explicaciones. Pruébalo con tres frases.

traductor = Agent(
    MODELO,
    instructions="",  # Escribe aquí las instrucciones del traductor
)

for frase in ["Me encanta programar", "Mañana tengo examen de cálculo", "León está en España"]:
    # Escribe aquí cómo pedirle al traductor que traduzca la frase y muestra el resultado
    pass
Ver solución
traductor = Agent(
    MODELO,
    instructions="Traduce al inglés el texto del usuario. Devuelve solo la traducción, sin comentarios.",
)

for frase in ["Me encanta programar", "Mañana tengo examen de cálculo", "León está en España"]:
    resultado = await traductor.run(frase)
    print(resultado.output)

Tema 4 de 8 · 7 min

Pydantic: clases que validan datos

Antes de pedirle datos ordenados a la IA, necesitamos una forma de describir qué datos queremos. Para eso está Pydantic (se instala automáticamente con Pydantic AI).

💡 Este tema se puede probar aquí mismo: Pydantic funciona en el navegador, así que los ejemplos son editores como los de los días 1 y 2. La primera ejecución tarda un poco más porque descarga la librería.

BaseModel: una clase con superpoderes

Con Pydantic creamos clases que heredan de BaseModel (¡herencia, como ayer!) y declaramos sus atributos con anotaciones de tipo:

from pydantic import BaseModel


class Alumno(BaseModel):
    nombre: str
    edad: int
    notas: list[float]


ana = Alumno(nombre="Ana", edad=19, notas=[7.5, 8.0])
print(ana)            # nombre='Ana' edad=19 notas=[7.5, 8.0]
print(ana.nombre)     # Ana

¡No hemos tenido que escribir __init__ ni __str__! Pydantic los crea por nosotros a partir de los atributos.

Valida los datos automáticamente

Alumno(nombre="Hugo", edad="veinte", notas=[5])
ValidationError: 1 validation error for Alumno
edad
  Input should be a valid integer, unable to parse string as an integer

Si los datos no cumplen los tipos, Pydantic da un error claro. Y si puede convertirlos sin ambigüedad, lo hace: edad="20" se convierte en el entero 20.

Más control con Field

Field permite añadir restricciones y una descripción de cada campo:

from pydantic import BaseModel, Field


class Pregunta(BaseModel):
    enunciado: str = Field(description="La pregunta, clara y concisa")
    opciones: list[str] = Field(min_length=4, max_length=4)
    correcta: int = Field(ge=0, le=3, description="Posición (0-3) de la opción correcta")
  • min_length / max_length: tamaño mínimo y máximo de la lista.
  • ge / le: mayor o igual / menor o igual que.
  • description: explicación del campo. Esto es clave: la IA leerá estas descripciones para saber qué poner en cada campo.

Convertir a diccionario o JSON

ana.model_dump()        # {'nombre': 'Ana', 'edad': 19, 'notas': [7.5, 8.0]}
ana.model_dump_json()   # '{"nombre":"Ana","edad":19,"notas":[7.5,8.0]}'

🔗 ¿Por qué nos importa? Un modelo de IA devuelve texto. Si le pedimos “dame 5 preguntas de test”, cada vez nos las dará con un formato distinto y será muy difícil usarlas en un programa. Con Pydantic le decimos exactamente qué estructura queremos y Pydantic AI se asegura de que la respuesta la cumpla.

Tema 5 de 8 · 8 min

Respuestas estructuradas

Ahora juntamos las dos piezas: un agente que en lugar de texto libre devuelve objetos Pydantic. Se consigue con el parámetro output_type.

Con modelos de Ollama envolvemos la clase en NativeOutput(...): así Ollama obliga al modelo a generar exactamente la estructura que pedimos (más abajo vemos cómo).

Ejemplo: extraer datos de un texto

from pydantic import BaseModel, Field
from pydantic_ai import Agent, NativeOutput


class Evento(BaseModel):
    titulo: str
    fecha: str = Field(description="Fecha en formato AAAA-MM-DD")
    lugar: str
    es_examen: bool


extractor = Agent(
    MODELO,
    output_type=NativeOutput(Evento),
    instructions="Extrae la información del evento descrito por el usuario.",
)

resultado = await extractor.run(
    "Ojo, que el examen final de Cálculo es el 15 de enero de 2027 en el aula 3 de la Escuela de Ingenierías"
)

evento = resultado.output
print(type(evento))      # <class '__main__.Evento'>
print(evento.titulo)
print(evento.fecha)
print(evento.es_examen)  # True

resultado.output ya no es un texto: es un objeto Evento con sus atributos, listo para usar en tu programa.

¿Cómo funciona por dentro?

  1. Pydantic AI convierte tu clase Evento en un esquema JSON (una descripción de los campos, sus tipos y sus description).
  2. Envía ese esquema a Ollama junto con tu mensaje. Con NativeOutput, Ollama restringe la generación: en cada paso solo deja al modelo escribir caracteres que encajen en el esquema. Es imposible que se salte un campo o que ponga texto donde va un número.
  3. Cuando llega la respuesta, Pydantic la valida y la convierte en un objeto Evento. Si aun así algo no cumple (por ejemplo, un Field(ge=0, le=3)), Pydantic AI le pide al modelo que lo corrija automáticamente.

💡 ¿Y sin NativeOutput? También funciona (output_type=Evento): Pydantic AI le pide al modelo que rellene los datos como si llamara a una función. Los modelos grandes en la nube lo hacen muy bien, pero los modelos pequeños fallan más a menudo, así que con Ollama usaremos siempre NativeOutput.

Listas de objetos

output_type acepta cualquier tipo: también listas.

class Concepto(BaseModel):
    termino: str
    definicion: str = Field(description="Definición breve, de una frase")


glosario = Agent(
    MODELO,
    output_type=NativeOutput(list[Concepto]),
    instructions="Extrae los conceptos técnicos del texto y defínelos.",
)

texto = """
En Python, una clase es una plantilla para crear objetos. Los atributos guardan
el estado del objeto y los métodos definen su comportamiento. La herencia permite
que una clase hija reutilice el código de una clase padre.
"""

resultado = await glosario.run(texto)

for concepto in resultado.output:
    print(f"• {concepto.termino}: {concepto.definicion}")

💡 Esto ya es la base del proyecto: si sabes pedir una lista de conceptos, sabes pedir una lista de preguntas de test.

Tema 6 de 8 · 8 min

Proyecto (1/2): resumir tus apuntes

Empezamos el asistente de estudio. Primero, el resumen.

Paso 1: los apuntes

Copia un fragmento de tus apuntes de cualquier asignatura (2 o 3 párrafos bastan) y pégalo entre las triples comillas:

apuntes = """
La célula es la unidad básica de la vida. Todos los seres vivos están formados
por una o más células. Existen dos tipos principales: las células procariotas,
que no tienen núcleo definido (como las bacterias), y las células eucariotas,
que sí tienen un núcleo rodeado por una membrana (como las de animales y plantas).

Las células eucariotas contienen orgánulos con funciones específicas: las
mitocondrias producen energía mediante la respiración celular, los ribosomas
fabrican proteínas y, en las células vegetales, los cloroplastos realizan la
fotosíntesis. La membrana plasmática controla qué sustancias entran y salen.
"""

💡 Consejo: con un modelo tan pequeño, cuanto más corto y claro sea el texto, mejor será el resumen. Empieza con dos o tres párrafos.

Paso 2: definir qué es un “resumen”

from pydantic import BaseModel, Field
from pydantic_ai import Agent, NativeOutput


class Resumen(BaseModel):
    titulo: str = Field(description="Título breve del tema")
    resumen: str = Field(description="Resumen del texto en un párrafo de 3 a 5 frases")
    ideas_clave: list[str] = Field(description="Entre 3 y 6 ideas principales, una frase cada una")
    conceptos: list[str] = Field(description="Términos importantes que conviene memorizar")

Paso 3: el agente

agente_resumen = Agent(
    MODELO,
    output_type=NativeOutput(Resumen),
    retries=3,  # si la respuesta no encaja en Resumen, se le pide que la corrija hasta 3 veces
    instructions=(
        "Eres un asistente de estudio para estudiantes universitarios. "
        "Resume los apuntes que te den en español, de forma clara y fiel al texto. "
        "No inventes información que no aparezca en los apuntes."
    ),
)

Fíjate en la última instrucción: los modelos a veces inventan cosas (se llaman alucinaciones), y los modelos pequeños como el nuestro más todavía. Pedirle que se ciña al texto reduce ese riesgo.

Paso 4: usarlo y mostrarlo bonito

resultado = await agente_resumen.run(apuntes)
resumen = resultado.output

print(f"📚 {resumen.titulo.upper()}\n")
print(resumen.resumen, "\n")

print("💡 Ideas clave:")
for idea in resumen.ideas_clave:
    print(f"  • {idea}")

print("\n🔑 Conceptos:", ", ".join(resumen.conceptos))

Reto rápido

Añade a Resumen un campo nivel_dificultad: int entre 1 y 5 (usa Field(ge=1, le=5)) y otro preguntas_para_pensar: list[str]. Sin cambiar las instrucciones, ¡el agente empezará a rellenarlos!

Como cada agente se queda con la clase que le diste al crearlo, el bloque vuelve a crear el agente con tu nueva Resumen:

class Resumen(BaseModel):
    titulo: str = Field(description="Título breve del tema")
    resumen: str = Field(description="Resumen del texto en un párrafo de 3 a 5 frases")
    ideas_clave: list[str] = Field(description="Entre 3 y 6 ideas principales, una frase cada una")
    conceptos: list[str] = Field(description="Términos importantes que conviene memorizar")
    # Añade aquí los dos campos nuevos


agente_resumen = Agent(
    MODELO,
    output_type=NativeOutput(Resumen),
    retries=3,
    instructions=(
        "Eres un asistente de estudio para estudiantes universitarios. "
        "Resume los apuntes que te den en español, de forma clara y fiel al texto. "
        "No inventes información que no aparezca en los apuntes."
    ),
)

resultado = await agente_resumen.run(apuntes)
print(resultado.output)
Ver solución
    nivel_dificultad: int = Field(ge=1, le=5, description="Dificultad del tema, de 1 (fácil) a 5 (difícil)")
    preguntas_para_pensar: list[str] = Field(description="Preguntas para reflexionar sobre el tema")

Tema 7 de 8 · 15 min

Proyecto (2/2): generador de tests

Ahora la parte más divertida: que la IA nos haga un test sobre los apuntes y nos ponga nota.

Usaremos los apuntes del tema anterior: si no lo has hecho, ejecuta antes su primer bloque.

Paso 1: definir las preguntas

from typing import Annotated

from pydantic import BaseModel, Field
from pydantic_ai import Agent, NativeOutput


class Pregunta(BaseModel):
    enunciado: str = Field(description="La pregunta, clara y sin ambigüedades")
    opciones: list[Annotated[str, Field(min_length=3)]] = Field(
        min_length=4, max_length=4, description="Cuatro posibles respuestas"
    )
    correcta: int = Field(ge=0, le=3, description="Posición (0-3) de la opción correcta")
    explicacion: str = Field(description="Por qué esa es la respuesta correcta")


class Test(BaseModel):
    preguntas: list[Pregunta] = Field(min_length=3, max_length=5, description="Entre 3 y 5 preguntas")

Las restricciones de Pydantic son nuestra mejor ayuda con un modelo tan pequeño, porque NativeOutput obliga al modelo a cumplirlas:

  • min_length=3 en preguntas: al menos tres preguntas (los modelos pequeños tienden a quedarse cortos).
  • Annotated[str, Field(min_length=3)]: cada opción es un texto de 3 caracteres o más. Sin esto, a veces las opciones son solo letras sueltas: "a", "b", "c"…

Paso 2: el agente generador

agente_test = Agent(
    MODELO,
    output_type=NativeOutput(Test),
    retries=3,
    instructions=(
        "Eres un profesor universitario que crea preguntas tipo test para repasar. "
        "Crea preguntas en español basadas ÚNICAMENTE en los apuntes que te den. "
        "Solo una opción debe ser correcta y las incorrectas deben ser creíbles. "
        "Varía la posición de la respuesta correcta."
    ),
)

resultado = await agente_test.run(f"Crea 3 preguntas sobre estos apuntes:\n\n{apuntes}")
test = resultado.output

print(f"Se han generado {len(test.preguntas)} preguntas")

Generar el test tarda unos segundos (más si el servidor está ocupado): el modelo tiene que escribir todas las preguntas, opciones y explicaciones.

⚠️ Revisa las preguntas: con un modelo tan pequeño, a veces marca como correcta una opción que no lo es, o se inventa datos. Detectar esos errores con tus apuntes también es repasar.

Paso 3: barajar las opciones

Al probarlo verás que el modelo tiende a poner la respuesta correcta siempre en la misma posición (por ejemplo, siempre la C), aunque le pidamos que la varíe. Los modelos de IA no son buenos generando aleatoriedad… pero Python sí:

import random


def barajar(pregunta: Pregunta) -> None:
    """Desordena las opciones y actualiza la posición de la correcta."""
    texto_correcto = pregunta.opciones[pregunta.correcta]
    random.shuffle(pregunta.opciones)
    pregunta.correcta = pregunta.opciones.index(texto_correcto)


for pregunta in test.preguntas:
    barajar(pregunta)

💡 Lección importante: no todo hay que pedírselo a la IA. Lo que se puede resolver con código normal (barajar, contar, calcular una nota) es más fiable, más rápido y gratis si lo hace Python.

Paso 4: hacer el test de forma interactiva

Aquí no hay IA: es Python del día 1 (bucles, input, condicionales y funciones):

LETRAS = ["A", "B", "C", "D"]


def hacer_test(test: Test) -> float:
    aciertos = 0

    for numero, pregunta in enumerate(test.preguntas, start=1):
        print(f"\n❓ Pregunta {numero}: {pregunta.enunciado}")
        for letra, opcion in zip(LETRAS, pregunta.opciones):
            print(f"   {letra}) {opcion}")

        respuesta = ""
        while respuesta not in LETRAS:
            respuesta = input("Tu respuesta (A, B, C o D): ").strip().upper()

        if LETRAS.index(respuesta) == pregunta.correcta:
            print("✅ ¡Correcto!")
            aciertos += 1
        else:
            print(f"❌ Incorrecto. Era la {LETRAS[pregunta.correcta]}.")
        print(f"   💬 {pregunta.explicacion}")

    nota = aciertos / len(test.preguntas) * 10
    print(f"\n🎓 Has acertado {aciertos} de {len(test.preguntas)}. Tu nota: {nota:.1f}")
    return nota


hacer_test(test)

Paso 5: todo junto en una clase

Usando lo que aprendimos ayer, podemos empaquetar el asistente en una clase reutilizable:

class AsistenteDeEstudio:
    def __init__(self, apuntes: str):
        self.apuntes = apuntes

    async def resumir(self) -> Resumen:
        resultado = await agente_resumen.run(self.apuntes)
        return resultado.output

    async def crear_test(self, num_preguntas: int = 3, dificultad: str = "media") -> Test:
        peticion = (
            f"Crea {num_preguntas} preguntas de dificultad {dificultad} "
            f"sobre estos apuntes:\n\n{self.apuntes}"
        )
        resultado = await agente_test.run(peticion)
        test = resultado.output
        for pregunta in test.preguntas:
            barajar(pregunta)
        return test


asistente = AsistenteDeEstudio(apuntes)

resumen = await asistente.resumir()
print(resumen.resumen)

test = await asistente.crear_test(num_preguntas=3, dificultad="difícil")
hacer_test(test)

Este bloque pide un resumen y un test seguidos, así que tarda un poco más, sobre todo si el servidor está ocupado.

💡 async def: como los métodos usan await por dentro, hay que declararlos con async def y llamarlos también con await. Es la única novedad respecto a las clases de ayer.

Retos extra

  1. Repaso de fallos: haz que hacer_test devuelva también la lista de preguntas falladas y vuelve a preguntarlas al final.
  2. Tarjetas de memoria: crea un modelo Tarjeta con pregunta y respuesta y un agente que genere tarjetas tipo flashcard.
  3. Verdadero o falso: añade a AsistenteDeEstudio un método para tests de verdadero/falso.
  4. Comparar modelos: si tienes Ollama en tu ordenador, descarga otro modelo de la biblioteca de Ollama, cámbialo en el bloque Modelo de IA y compara la calidad y la velocidad de las preguntas.
# Escribe aquí tus retos: tienes disponibles Pregunta, Test, agente_test, hacer_test y AsistenteDeEstudio

Tema 8 de 8 · 5 min

Buen uso de la IA y siguientes pasos

Usar la IA con cabeza

  • Puede equivocarse. Los modelos generan texto que suena correcto, pero a veces es falso (alucinaciones). Un modelo de 350 millones de parámetros como el nuestro se equivoca mucho más que los grandes de la nube: revisa siempre los resúmenes y las preguntas con tus apuntes.
  • No sustituye a estudiar. Úsala para repasar y detectar lo que no sabes, no para evitar leer el temario.
  • Cuidado con los datos personales. Con el servidor del curso, tus apuntes van a ese servidor y no a ninguna empresa de IA. Si usas un modelo en la nube, lo que envías sí llega al proveedor, y en los niveles gratuitos puede usarlo para mejorar sus servicios. No envíes información privada tuya ni de otras personas.
  • Respeta las normas de tu asignatura sobre el uso de IA en trabajos y exámenes.

Modelo abierto o en la nube

Abierto con Ollama (el de hoy) En la nube (Gemini, OpenAI, Claude…)
Coste Gratis (pagas el servidor o la GPU) Gratis con límites, o de pago
Clave de API No Sí
Calidad Depende del tamaño: el nuestro es muy básico La mejor
Velocidad Depende del servidor y de cuánta gente lo use Normalmente rápida
Privacidad Los datos van solo a tu servidor Los datos van al proveedor
Puesta en marcha Instalar Ollama y descargar el modelo Solo la clave

Cambiar de modelo es una línea

Una de las ventajas de Pydantic AI es que el resto del código no cambia si usas otro modelo u otro proveedor:

MODELO = "ollama:granite4:350m"         # El de hoy (Ollama, con OLLAMA_BASE_URL)
MODELO = "google:gemini-2.5-flash"       # Google Gemini (requiere GOOGLE_API_KEY)
MODELO = "openai:gpt-5.1-mini"           # OpenAI (requiere OPENAI_API_KEY)
MODELO = "anthropic:claude-sonnet-5-5"   # Anthropic Claude (requiere ANTHROPIC_API_KEY)

Plan B: Gemini con el nivel gratuito

Si el servidor del curso no responde, puedes usar Gemini desde Google Colab, que tiene un nivel gratuito sin tarjeta:

  1. Crea una clave en Google AI Studio (requiere ser mayor de edad).
  2. En Colab, pulsa el icono de la llave 🔑 (Secretos) y añade un secreto llamado GOOGLE_API_KEY con tu clave. Activa Acceso desde el cuaderno.
  3. Ejecuta esta celda en Colab:
!pip install -q "pydantic-ai-slim[google]"

import os
from google.colab import userdata

os.environ["GOOGLE_API_KEY"] = userdata.get("GOOGLE_API_KEY")
os.environ["PYDANTIC_AI_NO_BANNER"] = "1"

MODELO = "google:gemini-2.5-flash"

Todo lo demás (agentes, NativeOutput, el asistente de estudio) funciona sin tocar nada: copia los bloques de esta web en Colab.

⚠️ Tu clave de API es como una contraseña. No la escribas dentro del código, no la subas a GitHub y no la compartas.

Lo que no nos ha dado tiempo a ver

  • Herramientas (tools): funciones de Python que la IA puede decidir llamar, por ejemplo para consultar una base de datos, hacer cálculos exactos o buscar en Internet.
  • Streaming: mostrar la respuesta mientras se genera, como en los chats.
  • Interfaces gráficas: convertir tu asistente en una web con librerías como Streamlit o Gradio.
  • Ollama en tu ordenador: si tienes una GPU (o un Mac con chip M), puedes instalar Ollama y usar exactamente el mismo código fuera de Colab.

Para seguir aprendiendo Python

Resumen del curso

Día Lo que has aprendido
1 Variables, tipos de datos, condicionales, bucles y funciones
2 Clases, atributos, métodos, herencia y polimorfismo
3 Integrar un modelo de IA en tus programas y obtener de él datos estructurados

¡Enhorabuena! 🐍🎉 Con estas tres piezas ya puedes construir programas útiles de verdad.