Ir al contenido principal

Agentes

Agentes

Plugin en beta

Este plugin se encuentra actualmente en fase beta. Las API pueden cambiar entre versiones menores. Impórtalo desde @databricks/appkit/beta. Consulta Niveles de estabilidad de los plugins.

El plugin agents convierte una AppKit app de Databricks en un host de agentes de IA. Detecta las definiciones de agentes en el disco — una carpeta por agente dentro de server/agents/, que contiene o bien agent.md (markdown) o bien agent.ts (código) — y las expone en POST /invocations y POST /responses (sin streaming, alias) junto con POST /chat (streaming) y rutas para la gestión de hilos, la cancelación y la aprobación HITL. En todos los casos, el id del agente es el nombre de su carpeta: no hay ningún mapa que mantener ni ningún id que repetir.

Esta página cubre el ciclo de vida completo. Para las primitivas escritas a mano (tool(), mcpServer()), consulta tools.

Requisitos

Solo endpoints de serving con capacidad de streaming

El plugin de agentes gobierna el LLM mediante Server-Sent Events. Las Foundation Model APIs (Claude, Llama, GPT, etc.) y otros endpoints de tipo chat admiten streaming y funcionan sin configuración adicional. Los endpoints de modelos personalizados que devuelven una única respuesta JSON (por ejemplo, los despliegues típicos de sklearn o de pyfunc de MLflow) no hacen streaming: si apuntas un agente a uno de ellos, fallará en el primer turno con «Response body is null — streaming not supported». Si indicas un endpoint de serving en apps init, elige uno cuyo modelo implemente el protocolo de streaming de chat-completions; el plugin de agentes lee su nombre de DATABRICKS_SERVING_ENDPOINT_NAME siempre que el propio agente no fije model:.

Para la vía sin streaming contra un endpoint personalizado, usa en su lugar la ruta /invoke del plugin serving con useServingInvoke.

También puedes omitir por completo la configuración del endpoint de serving usando el adaptador de API de Supervisor gestionado (beta).

Instalación

agents es un plugin normal. Agrégalo a plugins[] junto con server() y cualquier plugin de tipo ToolProvider cuyas herramientas quieras que los agentes puedan usar.

import { analytics, createApp, files, server } from "@databricks/appkit";
import { agents } from "@databricks/appkit/beta";

await createApp({
  plugins: [server(), analytics(), files(), agents()],
});

Con eso ya tienes un servidor HTTP activo con POST /invocations (y su alias POST /responses) conectado a un agente definido en markdown. Usa POST /chat en su lugar cuando quieras la superficie con streaming y compatible con HITL.

Nivel 1: añade un paquete de agente en markdown

Cada agente reside en su propia carpeta dentro de server/agents/, con el archivo de entrada agent.md. Una carpeta es un agente solo si contiene un archivo de entrada (agent.md o agent.ts); si no lo tiene, se omite, por lo que las carpetas de recursos de cada agente se sitúan junto al archivo de entrada; en particular, una carpeta skills/ que contiene Skills (paquetes de instrucciones bajo demanda que el agente carga por nombre). Una carpeta compartida server/agents/skills/ contiene las skills disponibles para cualquier agente.

my-app/ server/ server.ts agents/ assistant/ agent.md
---
endpoint: databricks-claude-sonnet-4-5
default: true
---

Eres un asistente de datos que se ejecuta en Databricks y está para ayudar.

Usa las herramientas disponibles para consultar datos, explorar archivos y ayudar a los usuarios.

Al iniciarse, el plugin:

  1. Descubre server/agents/assistant/agent.md y registra el id de agente assistant.
  2. Analiza el frontmatter YAML y el cuerpo markdown como las instructions del agente.
  3. Resuelve el adaptador a partir de endpoint (o recurre a DATABRICKS_SERVING_ENDPOINT_NAME).
  4. Monta el agente con el nombre predeterminado (assistant).

El agente arranca sin herramientas. Las herramientas son opcionales: decláralas en el frontmatter (Nivel 2, más abajo) o activa explícitamente la herencia automática con agents({ autoInheritTools: { file: true } }). Consulta «Postura de herencia automática» más adelante para saber qué coste tiene y por qué está desactivada de forma predeterminada.

Migración desde `config/agents/`

Las versiones anteriores guardaban los agentes markdown en config/agents/<id>/agent.md. Esa ubicación aún se lee como alternativa obsoleta (con una advertencia única al arrancar); mueve cada carpeta a server/agents/<id>/agent.md para que todos los agentes — markdown y de código — estén en un mismo lugar.

Las solicitudes llegan a POST /invocations (o su alias POST /responses) con un cuerpo compatible con OpenAI Responses. Estos endpoints ejecutan el agente hasta el final y devuelven una única respuesta JSON, sin SSE. Los clientes que usen streaming deben recurrir a POST /chat. Cada llamada a herramienta se traza automáticamente. Las llamadas a herramientas del toolkit de plugins (las entradas plugin:<name> / plugins.<name>.toolkit()) pasan además por asUser(req), de modo que su SQL se ejecuta como el usuario que hace la solicitud y el acceso a archivos respeta las ACL de Unity Catalog. Un tool({ execute }) hecho a mano no se envuelve: su execute recibe únicamente los argumentos validados de la herramienta (sin req), por lo que se ejecuta con la identidad del service principal de la app y no puede acogerse a OBO. Si una herramienta debe actuar en nombre del usuario solicitante, exponla como herramienta de plugin en lugar de como un execute hecho a mano. Consulta Contexto de ejecución.

Sin HITL en `/invocations` ni `/responses`

La superficie de invocación sin streaming no tiene forma de devolver al llamador una solicitud de aprobación a mitad de la llamada. Cuando approval.requireForDestructive está habilitado (valor predeterminado) y el agente resuelto tiene alguna herramienta anotada con un efecto mutador (effect: "write" | "update" | "destructive", o el destructive: true heredado), POST /invocations y POST /responses rechazan la solicitud con HTTP 400 antes de que se ejecute el adaptador. Mueve los agentes con capacidad HITL a POST /chat, o desactiva la aprobación mediante agents({ approval: { requireForDestructive: false } }) para agentes autónomos de back-office.

Nivel 2: acotar las herramientas en el frontmatter

---
endpoint: databricks-claude-sonnet-4-5
tools:
  - plugin:analytics                              # todas las herramientas analytics.*
  - plugin:files: [uploads.read, uploads.list]    # solo estas herramientas de files
  - plugin:genie: { except: [getConversation] }   # todo excepto getConversation
  - get_weather                                   # herramienta ambiental declarada en el código
default: true
---

You are a read-only data analyst.

La lista unificada tools: combina referencias a plugins y herramientas ambientales, y refleja la forma de función de TS tools(plugins) => ({ ...plugins.analytics.toolkit(), ...plugins.files.toolkit({ only: [...] }), get_weather: tool({...}) }). Cada entrada puede ser:

  • plugin:<name> — incorpora todas las herramientas del plugin indicado.
  • plugin:<name>: [tool1, tool2] — solo las herramientas indicadas (azúcar sintáctico para { only: [...] }).
  • plugin:<name>: { ...ToolkitOptions } — opciones completas de prefix / only / except / rename.
  • <key> (sin prefijo) — nombre de una herramienta ambiental que se resuelve con la configuración agents({ tools: { ... } }).

Al declarar cualquier tools:, se desactiva la herencia automática predeterminada: el agente solo ve las herramientas indicadas.

Nivel 3: agentes definidos por código

Los agentes de código se ubican uno por carpeta dentro de server/agents/, con el archivo de entrada agent.ts (equivalente al agent.md de markdown). El archivo de entrada exporta un agente creado y su id es el nombre de la carpeta (server/agents/support/agent.tssupport). No hace falta declarar el id en ningún otro lugar.

// server/agents/support/agent.ts
import { createAgent, tool } from "@databricks/appkit/beta";
import { z } from "zod";

export default createAgent({           // id derivado del nombre de la carpeta: "support"
  instructions: "You help customers with data and files.",
  model: "databricks-claude-sonnet-4-5",                      // azúcar sintáctico con cadena
  tools(plugins) {
    return {
      ...plugins.analytics.toolkit(),                          // todas las herramientas de analytics
      ...plugins.files.toolkit({ only: ["uploads.read"] }),    // subconjunto filtrado
      get_weather: tool({
        description: "Weather",
        schema: z.object({ city: z.string() }),
        execute: async ({ city }) => `Sunny in ${city}`,
      }),
    };
  },
});

El plugin agents detecta estos archivos al iniciarse: sin registros ni mapeos:

// server/server.ts
import { analytics, createApp, files, server } from "@databricks/appkit";
import { agents } from "@databricks/appkit/beta";

await createApp({
  plugins: [server(), analytics(), files(), agents()],   // sin mapa de agentes, sin imports
});

El descubrimiento importa cada server/agents/<id>/agent.ts: el .ts de origen bajo tsx en desarrollo y el dist/agents/<id>/agent.js compilado en una compilación de producción (el output compilado tiene prioridad sobre el origen, con independencia de NODE_ENV). Como el servidor de producción se empaqueta y solo importa lo alcanzable desde server/server.ts, la configuración de tsdown del template incluye server/agents/*/agent.ts como entradas de compilación para que se emitan los dist/agents/*/agent.js que necesita el escaneo; ese cableado es lo que permite que una carpeta añadida sobreviva al bundle de producción. (El markdown agent.md se lee desde el origen tanto en desarrollo como en producción: son datos, no se compilan). La raíz siempre es server/agents: no hay ninguna opción de configuración para reubicarla; el markdown que siga en config/agents/ se lee como alternativa obsoleta (con una advertencia única).

El output compilado eclipsa el origen en desarrollo

Como el output compilado tiene prioridad sobre el origen, un dist/agents / build/agents obsoleto que haya quedado de un npm run build anterior será el que use npm run dev en lugar de tus server/agents/*.ts en vivo, por lo que tus ediciones parecerán ignorarse. Elimina el directorio de compilación si un agente de código parece congelado: recompilar solo sustituye la instantánea por otra más reciente, así que únicamente eliminarlo restaura la recarga en desarrollo en vivo desde el origen. El markdown siempre se lee desde el origen, así que las ediciones de agent.md nunca quedan eclipsadas.

La entrada puede hacer export default createAgent({...}) o exportar un único agente creado con nombre; en cualquier caso, el id es el nombre de la carpeta. Se omite toda carpeta cuya entrada no exporte un agente creado (o que no tenga ningún agent.ts/agent.md). Marca un agente como predeterminado con createAgent({ default: true }) (equivale al frontmatter de markdown default: true); un agents({ defaultAgent }) explícito sigue prevaleciendo.

Los agentes definidos por código empiezan sin herramientas de forma predeterminada. La forma de función tools(plugins) => Record<string, AgentTool> es la vía principal para incorporar herramientas de plugins: cada plugin registrado en createApp({ plugins: [...] }) aparece en el parameter plugins, y basta con invocar .toolkit(opts?) sobre él para obtener un registro listo para expandir. El runtime invoca la función una sola vez durante la configuración del agente y almacena el resultado en caché: cada plugin se menciona exactamente una vez (en createApp), sin variables retenidas ni importaciones marcadoras.

Las llamadas en línea a tool({...}) conviven en el mismo registro. Su name es opcional: el plugin de agentes lo sobrescribe con la clave del registro (get_weather, arriba).

La herencia automática está desactivada de forma predeterminada para ambos orígenes: un agente de markdown o de código sin tools: declaradas obtiene un índice de herramientas vacío. Activa un origen explícitamente con agents({ autoInheritTools: { file: true } }) (o { code: true }, o true para ambos).

Obsoleto: el mapa `agents({ agents: { ... } })`

Pasar un mapa de agentes construido a mano sigue funcionando y se admite por compatibilidad con versiones anteriores, pero emite una advertencia de obsolescencia por única vez y se eliminará en una futura versión menor. Obliga a repetir el id de cada agente (una vez en createAgent y otra como clave del mapa); el descubrimiento desde server/agents/ elimina tanto el mapa como esa repetición. Para migrar, mueve cada createAgent(...) a su propio server/agents/<id>/agent.ts (exportación por defecto o una única exportación nombrada) y elimina el mapa. Si un agente descubierto y una entrada del mapa comparten id, prevalece el descubrimiento y la entrada del mapa se ignora (con una advertencia por única vez). (Los subagentes en línea —createAgent({ agents: { ... } }) en una definición— no se ven afectados; solo queda obsoleto el mapa a nivel de plugin).

Algunos ejemplos más abajo siguen pasando agentes en línea mediante este mapa por brevedad del fragmento; en una aplicación real, cada una de esas definiciones de createAgent(...) reside en su propio server/agents/<id>/agent.ts y no necesita mapa.

Delimitar herramientas en código

plugins.<name>.toolkit(opts?) acepta las mismas ToolkitOptions que el frontmatter de markdown:

OpciónEjemploSignificado
only{ only: ["query"] }Lista de permitidos de nombres locales de herramientas
except{ except: ["legacy"] }Lista de denegados de nombres locales de herramientas
prefix{ prefix: "" }Elimina el prefijo ${pluginName}.
rename{ rename: { query: "q" } }Reasigna nombres locales específicos

Para los plugins que no exponen un método .toolkit() (por ejemplo, plugins ToolProvider de terceros creados con toPlugin a secas), el runtime recurre a recorrer getAgentTools() y sintetizar claves con espacio de nombres (${pluginName}.${localName}). Esta alternativa respeta only / except / rename / prefix del mismo modo.

Si un plugin referenciado no está registrado en createApp({ plugins }), el plugin de agentes lanza un error durante la configuración con un listado Available: … para que puedas corregir la conexión antes de la primera solicitud.

Nivel 4: subagentes

const researcher = createAgent({
  instructions: "Research the question. Return concise bullets.",
  model: "databricks-claude-sonnet-4-5",
  tools: { search: tool({ /* ... */ }) },
});

const writer = createAgent({
  instructions: "Draft prose from notes.",
  model: "databricks-claude-sonnet-4-5",
});

const supervisor = createAgent({
  instructions: "Coordinate researcher and writer.",
  model: "databricks-claude-sonnet-4-5",
  agents: { researcher, writer },  // se exponen como agent-researcher, agent-writer
});

// server/agents/{supervisor,researcher,writer}/agent.ts — una carpeta para cada uno
export default supervisor;

await createApp({
  plugins: [server(), agents()],  // se detectan en server/agents/
});

Coloca supervisor, researcher y writer en sus propias carpetas server/agents/<id>/agent.ts (cada una con exportación por defecto) — un padre en markdown también puede delegar en un hijo de código ubicado en una carpeta hermana mediante el frontmatter agents: [helper]. Cada clave de agents: {...} en un AgentDefinition se convierte en una herramienta agent-<key> del padre. Al invocarse, el plugin de agentes ejecuta el adaptador del hijo con una lista de mensajes nueva (sin estado de hilo compartido) y devuelve el texto agregado. Los ciclos en el grafo agents: {} en línea de un agente de código se rechazan durante la carga (createAgent); la delegación agents: en markdown rechaza las autorreferencias durante la carga y acota los ciclos más profundos en runtime mediante limits.maxSubAgentDepth.

Skills

Las skills son paquetes de instrucciones bajo demanda: el mismo formato SKILL.md que usan Claude Code y Cursor. En el prompt de sistema solo están el name y la description de cada skill (siempre activos y de bajo costo); el cuerpo completo se carga bajo demanda cuando el agente (o el usuario) la invoca. Esto funciona con cualquier modelo servido por Databricks: AppKit implementa la divulgación progresiva por su cuenta, por lo que no depende de una funcionalidad de skills nativa del proveedor.

Una skill es un directorio con un SKILL.md y los archivos de referencia que se quieran incluir:

server/agents/ skills/ # pool compartido — cualquier agente puede optar por usarlas pdf-forms/ SKILL.md reference.md planner/ agent.md skills/ # privadas del agente `planner` house-style/ SKILL.md
---
name: pdf-forms
description: Fill and validate PDF form fields from a data record.
---

Para rellenar un formulario PDF:

1. Consulta `reference.md` para conocer las convenciones de nombres de los campos.
2. ...

name y description son obligatorios; license, allowed-tools y metadata se aceptan por compatibilidad con skills creadas en otros entornos. Las claves desconocidas generan una advertencia y se ignoran.

Visibilidad

  • Las skills por agente (server/agents/<id>/skills/) siempre son visibles para ese agente.
  • Las skills globales (server/agents/skills/ y las skills de volúmenes de catálogo) requieren activación explícita: enumérilas en el frontmatter del agente, skills: [pdf-forms]. Configura autoInheritSkills: true (o { file, code }) en el plugin para que todas las skills globales sean visibles sin necesidad de enumerarlas; está desactivado por defecto para que el catálogo siempre activo de cada agente se mantenga ligero.

Cómo usa el agente una skill

En todo agente que tenga un catálogo visible se inyectan dos herramientas integradas de solo lectura:

  • load_skill(skill): devuelve las instrucciones completas de la skill más un manifiesto de los archivos que la acompañan.
  • read_skill_file(skill, path): devuelve el contenido de uno de esos archivos.

El modelo llama a load_skill por su cuenta cuando una tarea coincide con la descripción de una skill. Un usuario puede forzar una skill concreta durante un turno con el prefijo /skill-name en el chat (o con la opción send(message, { skill }) de useAgentChat); las instrucciones de la skill se inyectan en ese turno de forma determinista y load_skill sigue disponible para la selección automática. El cliente lee el catálogo de cada agente desde el payload de clientConfig() del plugin para alimentar un selector.

Skills del catálogo (volumen de Unity Catalog)

Apunta skillsVolume (o la variable de entorno DATABRICKS_VOLUME_AGENT_SKILLS) a un volumen de UC organizado de la misma forma: <volume>/<name>/SKILL.md. Las skills del catálogo se detectan al arrancar y al llamar a reload(), se integran en el conjunto global compartido y se leen con el service principal (skillCredentialMode tiene el valor predeterminado "sp"). Están pensadas como un conjunto compartido y curado; todavía no se admiten volúmenes de skills por usuario (OBO). Declarar el recurso opcional volume en el manifiesto permite que el scaffolder conceda acceso de lectura al SP.

Colisiones de nombres

Los nombres de las skills se referencian sin prefijo. Si dos fuentes proporcionan el mismo nombre, cada uno pasa a ser un <scope>:name cualificado (agent:, bundle:, volume:) y el nombre sin prefijo se rechaza por ambiguo, mostrando las alternativas disponibles. Dos skills con el mismo nombre provenientes de la misma fuente provocan un error en el arranque.

Limitaciones de la v1

  • Los scripts no se ejecutan. Una skill puede hacer referencia a scripts/foo.py; la v1 solo carga texto y documentos de referencia.
  • allowed-tools es orientativo. Se muestra como una sugerencia dentro de la skill cargada, pero no se aplica: cargar una skill no restringe las herramientas que el agente puede invocar. No es un sandbox.
  • El cuerpo de las skills no tiene control de acceso por usuario (se lee con el SP). No incluyas contenido sensible de los usuarios en el cuerpo de las skills.

Nivel 5: independiente (sin createApp)

import { createAgent, runAgent, tool } from "@databricks/appkit/beta";
import { z } from "zod";

const classifier = createAgent({
  instructions: "Classify tickets: billing | bug | feature.",
  model: "databricks-claude-sonnet-4-5",
  tools: {
    lookup_account: tool({ /* ... */ }),
  },
});

for (const ticket of tickets) {
  const result = await runAgent(classifier, {
    messages: [{ role: "user", content: ticket.body }],
  });
  await persistClassification(ticket.id, result.text);
}

runAgent ejecuta el adaptador sin createApp ni HTTP. Las llamadas a tool() en línea funcionan de forma independiente, como se mostró anteriormente. Para usar herramientas de plugins en modo independiente, pasa las fábricas de plugins mediante RunAgentInput.plugins y accede a ellas con la forma de función tools(plugins):

import { analytics } from "@databricks/appkit";
import { createAgent, runAgent } from "@databricks/appkit/beta";

const classifier = createAgent({
  instructions: "Classify tickets. Use analytics.query for historical data.",
  model: "databricks-claude-sonnet-4-5",
  tools(plugins) {
    return { ...plugins.analytics.toolkit() };
  },
});

const result = await runAgent(classifier, {
  messages: "is ticket 42 a duplicate?",
  plugins: [analytics()],
});

runAgent construye de forma anticipada cada plugin de RunAgentInput.plugins, ejecuta el ciclo de vida estándar attachContext({}) + await setup() y comparte las instancias entre el run de nivel superior y cada despacho a subagentes. Los plugins cuyo setup() requiere un runtime exclusivo de createApp (p. ej. WorkspaceClient, ServiceContext) fallan en la inicialización standalone con un mensaje claro de "use createApp instead", en lugar de hacerlo a mitad del flujo.

Las herramientas alojadas de MCP (mcpServer(...)) siguen requiriendo agents() (necesitan un cliente MCP activo). En cambio, las herramientas alojadas de la API de Supervisor (supervisorTools.*) funcionan en runAgent standalone: el adaptador tiene todo lo necesario para ejecutarlas del lado del servidor. Esto permite usar Supervisor Agents en evaluaciones por lotes o en CI sin createApp. En modo standalone, el despacho de herramientas de plugins se ejecuta como el service principal (sin OBO) y omite el control de aprobación del plugin de agents: trata runAgent standalone como un entorno de prompts confiables (CI, evaluación por lotes, scripts internos), no como una superficie expuesta de cara al usuario.

Añadir agentes a una app existente

¿Ya tienes una app y quieres añadirle agentes? Lo que hay que tocar depende del tipo:

Agentes en markdown: solo el plugin. Coloca server/agents/<id>/agent.md, añade agents() a tus plugins y listo. El markdown se lee desde el código fuente en runtime tanto en desarrollo como en producción, así que no hace falta cambiar nada en la compilación.

Agentes en código (server/agents/<id>/agent.ts): además, actualiza la compilación del servidor para que el bundle de producción los genere. Los agentes en código no se importan en ningún sitio, por lo que una compilación que solo procese server/server.ts nunca generará dist/agents/*/agent.js, y un npm run build + arranque empaquetado no descubriría ningún agente en código.

En desarrollo esto pasa desapercibido

npm run dev (tsx) importa directamente el código fuente .ts, así que ahí los agentes en código funcionan sin cambiar la compilación: el problema solo aparece en una compilación empaquetada. Si añades agentes en código pero olvidas ese cambio, el plugin avisa al arrancar (e indica cómo solucionarlo) en lugar de fallar en silencio.

La solución, de una sola línea, es adoptar el preset de compilación:

// tsdown.server.config.ts
import { appkitServerConfig } from '@databricks/appkit/tsdown';

export default appkitServerConfig();

appkitServerConfig() detecta automáticamente server/agents/ y añade el glob de entrada + clean solo cuando existen agentes de código; pasa las anulaciones como appkitServerConfig({ external, define, ... }), o una función appkitServerConfig((base) => ({ ...base })) para tener control total. Además, es la última vez que tocas este archivo: los futuros cambios en la configuración de compilación llegan con el paquete. Si prefieres mantener una configuración escrita a mano, añade tú mismo las entradas:

entry: ['server/server.ts', 'server/agents/*/agent.ts'],
clean: true,

Agentes gestionados: el adaptador de la API de Supervisor

DatabricksAdapter.fromSupervisorApi (beta) es la forma sin configuración de ejecutar un agente: en lugar de aprovisionar un endpoint de serving de modelos y apuntar a él, ejecutas el bucle agéntico en el workspace de Databricks apuntando a la API Responses del AI Gateway (/ai-gateway/mlflow/v1/responses), que ejecuta el LLM —y cualquier herramienta alojada— como un servicio gestionado en Databricks. Sin DATABRICKS_SERVING_ENDPOINT_NAME, sin comprobación de compatibilidad con streaming y sin cableado de herramientas en JS para los casos habituales.

El agente mínimo requiere una línea más que un agente en markdown:

import { createApp } from "@databricks/appkit";
import { agents, createAgent, DatabricksAdapter } from "@databricks/appkit/beta";

await createApp({
  plugins: [
    agents({
      agents: {
        assistant: createAgent({
          instructions: "You are a helpful assistant.",
          model: DatabricksAdapter.fromSupervisorApi({
            model: "databricks-claude-sonnet-4-5",
          }),
        }),
      },
    }),
  ],
});

createAgent({ model }) ya acepta adaptadores y promesas de adaptadores, además de la cadena con el nombre del modelo usada en los ejemplos anteriores, por lo que puedes pasarle directamente el resultado de la factoría. La factoría resuelve las credenciales a través de la cadena del SDK (DATABRICKS_HOST, OAuth, PAT, …); pasa workspaceClient para reutilizar un cliente existente.

Herramientas alojadas

Expón spaces de Genie, funciones o conexiones de Unity Catalog, Knowledge Assistants u otras AppKit apps al modelo declarándolos como herramientas del agente, en el mismo lugar donde se declara cualquier otra herramienta. La ejecución se mantiene en el servidor; no tienes que escribir código de herramientas:

import {
  createAgent,
  DatabricksAdapter,
  supervisorTools,
} from "@databricks/appkit/beta";

const assistant = createAgent({
  instructions: "You are a helpful data assistant.",
  model: DatabricksAdapter.fromSupervisorApi({
    model: "databricks-claude-sonnet-4-5",
  }),
  tools: () => ({
    nyc: supervisorTools.genieSpace({
      id: "01ABCDEF12345678",
      description: "NYC taxi trip records and zones",
    }),
    add: supervisorTools.ucFunction({
      name: "main.default.add",
      description: "Adds two integers and returns the sum.",
    }),
  }),
});

Cada factory supervisorTools.* recibe un único objeto de opciones con nombre: las cadenas críticas para el enrutamiento llevan etiqueta en el punto de llamada, por lo que resulta imposible equivocarse al intercambiar argumentos posicionales.

description es obligatoria y no puede estar vacía: el LLM la usa para enrutar entre herramientas, así que dos Genie spaces etiquetados ambos como "Genie space" serán indistinguibles.

Las descripciones de las herramientas alojadas son configuración de aplicación confiable (CWE-1427)

El LLM lee la description de una herramienta alojada para decidir cuándo enrutar hacia ella. No la derives de entradas no confiables: mensajes de usuarios, cuerpos de peticiones, campos de texto libre de sistemas externos o cualquier valor en el que un atacante pueda influir. Trata description (e id/name) como valores controlados por la aplicación, igual que las instructions del agente. Permitir aquí una cadena controlada por el usuario es un punto de inyección de prompts: una descripción maliciosa puede convencer al modelo de enrutar hacia una herramienta (o de evitarla) en cualquier petición futura que gestione el agente.

La misma precaución se aplica a las description de MCP y a cualquier otro campo que el modelo lea en el momento del enrutamiento.

FactoryTipo de herramientaIdentificador
supervisorTools.genieSpace({ id, description })Genie spaceid del space
supervisorTools.ucFunction({ name, description })Función de Unity Catalognombre de tres partes
supervisorTools.knowledgeAssistant({ knowledgeAssistantId, description })Knowledge Assistantid del asistente
supervisorTools.app({ name, description })Databricks Appnombre de la app
supervisorTools.ucConnection({ name, description })Conexión de UCnombre de la conexión

Declaración de herramientas alojadas en agentes markdown

Las herramientas de supervisor alojado también funcionan en agentes basados en markdown: declara la herramienta en el código (dentro de agents({ tools: { ... } })) y haz referencia a su clave en el frontmatter:

// server.ts
agents({
  agents: { /* ... */ },
  tools: {
    nyc_taxi: supervisorTools.genieSpace({
      id: "01ABCDEF12345678",
      description: "NYC taxi trip records and zones",
    }),
  },
});
---
endpoint: databricks-claude-sonnet-4-5
tools:
  - nyc_taxi
---

Respondes preguntas sobre los datos de taxis de Nueva York usando el space de Genie.

Sin nueva sintaxis de frontmatter: la búsqueda de herramientas ambientales en tools: ya resuelve las claves simples contra agents({ tools }), y la estructura de registro etiquetado de supervisorTools.* permite que el plugin las clasifique automáticamente.

Qué no se aplica a los agentes con Supervisor API

El runtime administrado se encarga de ejecutar sus propias herramientas, por lo que el adaptador ignora deliberadamente las herramientas de función y los subagentes del índice de herramientas del plugin de agentes. Para cualquier agente cuyo model: sea un adaptador de Supervisor:

  • Solo llegan al modelo las entradas supervisorTools.*. Las herramientas de función (tool({...})), las herramientas alojadas de MCP (mcpServer(...)) y los subagentes locales (agents: { ... }) declarados junto a un adaptador de supervisor generarán una advertencia en el momento del registro y no se expondrán al modelo. La comprobación de capacidades se dispara a partir de consumesInputTools: false en el adaptador.
  • La barrera de aprobación con intervención humana no se activa (las llamadas a herramientas nunca entran en el proceso de Node; las anotaciones effect: "destructive" son irrelevantes para las herramientas alojadas).
  • limits.maxToolCalls no se aplica (el runtime administrado contabiliza sus propias llamadas).
  • El OBO por llamada no se aplica a las herramientas alojadas; estas se ejecutan con las credenciales que el runtime administrado usa para el recurso de destino.

Composición de subagentes entre adaptadores

Los adaptadores de supervisor y de chat-completions pueden coexistir en el mismo mapa agents({ agents: { ... } }), pero la composición solo funciona en un sentido:

  • Padre chat-completions → subagente supervisor funciona de forma nativa. El padre despacha mediante agent-{key} como una herramienta de función normal; el adaptador del hijo se ejecuta por completo en el AI Gateway.
  • Padre supervisor → hijos que sean herramientas de función o subagentes locales aún no está implementado. La comprobación de capacidades emite una advertencia durante el registro; esas herramientas no llegarán al modelo supervisor. Más adelante se eliminará esta restricción enrutando los eventos response.function_call de SA a través de context.executeTool.
Vía de recuperación para turnos de herramientas sin streaming

Algunos tipos de herramientas alojadas devuelven su texto final de asistente sin eventos incrementales output_text.delta. El adaptador cuenta con una vía de recuperación que extrae el texto de response.completed.output[] para que el turno no quede vacío sin aviso. Define DEBUG=appkit:agents:supervisor-api para registrar el histograma de tipos de evento por turno si quieres comprobar qué vía siguió un turno.

Referencia de configuración

agents({
  // Los agentes se ubican en server/agents/<id>/ (raíz fija). config/agents se lee como alternativa obsoleta.
  agents?: Record<string, AgentDefinition>,  // OBSOLETO — usa el descubrimiento en server/agents/<id>/
  defaultAgent?: string,
  defaultModel?: AgentAdapter | Promise<AgentAdapter> | string,
  tools?: Record<string, AgentTool>,
  autoInheritTools?: boolean | { file?: boolean, code?: boolean },
  autoInheritSkills?: boolean | { file?: boolean, code?: boolean }, // desactivado por defecto
  skillsVolume?: string,        // Volumen de UC para las skills del catálogo; recurre a DATABRICKS_VOLUME_AGENT_SKILLS
  skillCredentialMode?: "sp" | "obo", // "sp" por defecto (consulta Skills)
  threadStore?: ThreadStore,    // en memoria por defecto
  baseSystemPrompt?: false | string | (ctx: PromptContext) => string,
  mcp?: {
    trustedHosts?: string[],    // nombres de host adicionales permitidos para URLs de MCP personalizadas
    allowLocalhost?: boolean,   // por defecto: NODE_ENV !== "production"
  },
  approval?: {
    requireForDestructive?: boolean,  // por defecto: true
    timeoutMs?: number,               // por defecto: 60_000
  },
  limits?: {
    maxConcurrentStreamsPerUser?: number, // por defecto: 5
    maxToolCalls?: number,                // por defecto: 50
    maxSubAgentDepth?: number,            // por defecto: 3
    toolCallTimeoutMs?: number,           // por defecto: 300_000 (5 min)
  },
})

El valor predeterminado de autoInheritTools es { file: false, code: false }: no se propaga ninguna herramienta a ningún agente salvo que el desarrollador lo habilite explícitamente. Cuando se habilita, solo se propagan las herramientas que el autor del plugin haya marcado con autoInheritable: true; las herramientas destructivas o que modifican el estado siempre quedan excluidas de la ruta de herencia automática, incluso con la opción habilitada. La forma abreviada booleana (autoInheritTools: true) se aplica a ambos orígenes. Consulta "Postura de herencia automática" más abajo.

Política de hosts MCP

AppKit aplica una política de confianza cero a cada URL de MCP utilizada como herramienta alojada. De forma predeterminada, solo se puede acceder a las URL del workspace de Databricks del mismo origen (es decir, que coincidan con el DATABRICKS_HOST resuelto). Cualquier otro host debe agregarse explícitamente a la lista de permitidos mediante mcp.trustedHosts, y las credenciales del workspace (tokens de service principal y tokens de usuario en su nombre) nunca se reenvían a esos hosts.

agents({
  agents: {
    support: createAgent({
      instructions: "",
      tools: {
        "mcp.internal": mcpServer("internal", "https://mcp.corp.internal/mcp"),
      },
    }),
  },
  mcp: {
    trustedHosts: ["mcp.corp.internal"],
  },
});

La política aplica cuatro reglas en el momento del connect() de MCP, antes de enviar un solo byte:

  1. Solo se aceptan URL http y https.
  2. Se rechaza http:// en texto plano para todos los casos excepto localhost cuando allowLocalhost es true (valor predeterminado en desarrollo, desactivado en producción).
  3. El nombre de host de destino debe coincidir con el host del workspace, ser igual a localhost (si está permitido) o aparecer en trustedHosts.
  4. La dirección DNS resuelta no debe pertenecer a los rangos de loopback, RFC1918, CGNAT (100.64.0.0/10), link-local (169.254.0.0/16, que abarca los services de metadatos de la nube), ULA ni multicast.

Los encabezados Authorization que transportan credenciales del workspace se limitan a las URL del workspace del mismo origen. Un mcpServer(name, url) que apunte a un host externo de confianza debe autenticarse por su cuenta (por ejemplo, con un token personalizado incluido en url).

Enfoque de la herencia automática

AppKit trata la herencia automática como una operación de doble llave: el desarrollador debe habilitar autoInheritTools Y el autor del plugin debe marcar cada herramienta con autoInheritable: true. Ambas condiciones son necesarias para que una herramienta se propague al índice de un agente sin una configuración explícita.

// Activación a nivel del plugin de agentes (elige una):
agents({ autoInheritTools: true });                   // ambos orígenes
agents({ autoInheritTools: { file: true } });         // solo agentes en markdown
agents({ autoInheritTools: { file: true, code: true } });

// Por herramienta, dentro de un plugin:
defineTool({
  description: "safe read",
  schema: z.object({ ... }),
  annotations: { effect: "read", requiresUserContext: true },
  autoInheritable: true, // consentimiento explícito de que esta herramienta puede propagarse automáticamente
  execute: (args, signal) => ...,
});

Los plugins principales de AppKit se distribuyen con las siguientes marcas de autoInheritable:

HerramientaautoInheritableJustificación
analytics.queryCon alcance OBO, SQL de solo lectura aplicado en runtime mediante el clasificador
files.list / files.read / files.exists / files.metadataOperaciones de lectura con alcance OBO
files.upload / files.deletenoModifican estado: conéctalas explícitamente
genie.getConversationHistorial de solo lectura
genie.sendMessagenoConversación de Genie que modifica el estado
lakebase.querynoYa está restringida por exposeAsAgentTool; la herencia automática permanece cerrada como defensa en profundidad

Los plugins de ToolProvider de terceros que no exponen un método toolkit() también quedan fuera de la ruta de herencia automática: sus herramientas deben conectarse explícitamente mediante tools:. Durante la configuración, el plugin de agentes registra en los logs qué heredó cada agente y qué se omitió, de modo que la postura resulte visible:

[agents] [agent support] auto-inherited 2 tool(s): analytics.query, files.uploads.read [agents] [agent support] auto-inherit skipped 3 tool(s) not marked autoInheritable: files(2), genie(1). Wire them explicitly via `tools:` if needed.

Herramientas SQL para agentes

Dos herramientas de agente integradas pueden ejecutar SQL en nombre del LLM: analytics.query (sobre el SQL warehouse de Databricks) y lakebase.query (sobre una base de datos Lakebase Postgres), que es opcional y debe habilitarse. Cada una tiene un enfoque de seguridad distinto, ya que se ejecutan con privilegios diferentes.

analytics.query se ejecuta con el token OBO de quien realiza la llamada (las credenciales de Databricks del usuario final). Su anotación readOnly: true se aplica en tiempo de ejecución: las sentencias se tokenizan y solo se aceptan SELECT, WITH, SHOW, EXPLAIN, DESCRIBE y DESC. Las escrituras, el DDL y las sentencias encadenadas se rechazan antes de que la solicitud llegue al warehouse:

// aceptado
analytics.query({ query: "SELECT * FROM main.sales.orders WHERE created_at > current_date() - 7" })

// rechazado en el plugin, nunca llega al warehouse
analytics.query({ query: "UPDATE main.sales.orders SET status = 'cancelled'" })
analytics.query({ query: "SELECT 1; DROP TABLE main.sales.orders" })

lakebase.query no se registra como herramienta de agente de forma predeterminada. Habilitarla es una decisión explícita, ya que el pool de Lakebase está vinculado al service principal de la aplicación: un agente con acceso a esta herramienta puede ejecutar SQL como el SP, sin importar qué usuario final haya iniciado la solicitud. Actívala mediante una marca de confirmación:

lakebase({
  exposeAsAgentTool: {
    iUnderstandRunsAsServicePrincipal: true,
    readOnly: true, // valor predeterminado
  },
});

Con readOnly: true (valor predeterminado), se aplica el mismo clasificador de SQL que en analytics.query y, además, la sentencia aceptada se envuelve en BEGIN READ ONLY; … ROLLBACK;, de modo que el servidor de Postgres rechaza cualquier escritura que logre burlar al clasificador (por ejemplo, un SELECT sobre una función con efectos secundarios). La anotación de la herramienta es { effect: "read" }.

Con readOnly: false, la herramienta acepta cualquier SQL y se anota como { effect: "destructive" }. El efecto destructive activa la barrera de aprobación con intervención humana (descrito más abajo) en cada invocación.

Aprobación con intervención humana para herramientas que mutan datos

Cualquier herramienta anotada con un efecto de mutación — effect: "write" | "update" | "destructive" (recomendado) o el booleano heredado destructive: true — requiere la aprobación explícita del usuario antes de ejecutarse. Segura por defecto: establece approval.requireForDestructive: false únicamente para agentes de back-office totalmente autónomos que se ejecuten en contextos de un solo usuario.

Flujo:

  1. Antes de ejecutar la herramienta, el plugin de agentes emite un evento SSE appkit.approval_pending que incluye el approval_id, stream_id, tool_name, args y annotations de la llamada pendiente.
  2. El cliente de chat muestra un prompt de aprobación (consulta la tarjeta de aprobación de la aplicación de referencia).
  3. El mismo usuario que inició el stream envía la decisión a POST /api/agents/approve:

    POST /api/agents/approve
    Content-Type: application/json
    X-Forwarded-User: <end-user id>
    X-Forwarded-Access-Token: <OBO token>
    
    { "streamId": "...", "approvalId": "...", "decision": "approve" | "deny" }
  4. Si se aprueba, la herramienta se ejecuta con normalidad y el stream continúa. Si se deniega, el adaptador recibe la cadena "Tool execution denied by user approval gate (tool: <name>)." como output de la herramienta, y el LLM puede disculparse o replanificar. Si no llega ninguna decisión antes de approval.timeoutMs (60 s por defecto), la puerta deniega automáticamente.

La ruta exige que quien decide sea el propietario del stream: una aprobación procedente de un x-forwarded-user distinto devuelve 403. Cancelar el stream mediante POST /api/agents/cancel deniega todas las aprobaciones pendientes de ese stream.

Límites de recursos

El plugin aplica una serie de límites para proteger un deployment de instancia única frente a prompts descontrolados, clientes que se comportan mal o ciclos de delegación provocados por inyección de prompts. Algunos son estáticos (los impone el esquema de la solicitud) y otros se pueden configurar mediante agents({ limits: { ... } }).

Límites estáticos (aplicados al analizar las solicitudes POST /chat, POST /invocations y POST /responses):

CampoLímiteMotivo
chat.message64 000 caracteres~16k tokens; cuerpos mayores son casi con seguridad un abuso.
Cadena invocations.input64 000 caracteresMismo motivo.
Array invocations.input100 elementosEvita que una sola solicitud cargue cientos de mensajes en el almacén de hilos.
Cadena invocations.input[].content64 000 caracteresLímite por mensaje precargado.
Array invocations.input[].content100 elementosLímite por mensaje precargado.

Límites configurables (se muestran los valores predeterminados):

agents({
  limits: {
    maxConcurrentStreamsPerUser: 5,  // HTTP 429 + Retry-After al superarse
    maxToolCalls: 50,                // aborta el run si se agota el presupuesto
    maxSubAgentDepth: 3,             // rechaza la recursión de subagentes más allá de este valor
    toolCallTimeoutMs: 300_000,      // tiempo de espera por llamada a herramienta (5 min; margen para SQL/Genie en frío)
  },
});

El presupuesto de maxToolCalls se comparte entre el adaptador de nivel superior y todos los subagentes a los que delega, de modo que una ramificación inyectada mediante el prompt no puede eludirlo anidando más niveles. maxConcurrentStreamsPerUser se aplica por usuario, no de forma global: que un usuario alcance su límite no afecta a los demás.

API de runtime

Después de createApp, el plugin expone:

appkit.agents.list();               // => ["support", "researcher", ...]
appkit.agents.get("support");       // => RegisteredAgent | null
appkit.agents.getDefault();         // => "support"
appkit.agents.register(name, def);  // registro dinámico
appkit.agents.reload();             // vuelve a escanear el directorio
appkit.agents.getThreads(userId);   // lista los hilos del usuario

Evaluación de agentes

AppKit incluye un framework de evaluación para los agentes que crees aquí. Las evaluaciones se escriben en TypeScript con defineEval: diriges al agente enviándole mensajes y verificas su respuesta y su uso de herramientas mediante comparadores deterministas o jueces LLM. Las evaluaciones se ejecutan sobre una aplicación en ejecución por HTTP (--url) y, con credenciales de Databricks y un experimento, envían los resultados a MLflow como «Evaluation runs» nativas, con retroalimentación por aserción y por juez adjunta a la traza de cada turno. La API de evaluación forma parte de la superficie beta: impórtala desde @databricks/appkit/beta.

Las evaluaciones se ubican junto a cada agente: server/agents/<agent-id>/evals/*.eval.ts. Cada archivo exporta por defecto un defineEval({ test }). Por defecto, el agente que se evalúa es el del directorio padre <agent-id>; define agent: para apuntar a otro.

Una primera evaluación

// server/agents/query/evals/smoke.eval.ts
import { defineEval } from "@databricks/appkit/beta";

export default defineEval({
  description: "Query agent responds to a greeting",
  async test(t) {
    await t.send("Hi there!");
    t.succeeded(); // control: el turno finalizó sin errores de agente ni de stream
  },
});

Inicia la app y luego ejecuta las evaluaciones contra ella:

# la aplicación debe estar en ejecución y ser accesible en --url
appkit agent eval --url http://localhost:3000

# limita el alcance a un agente/eval mediante una subcadena y apunta a la raíz de un project
appkit agent eval query --root apps/dev-playground --url http://localhost:3000

El [filter] posicional coincide con las evaluaciones cuyo <agent>/<id> contenga la subcadena (o con un id de agente exacto). El comando detecta todos los archivos *.eval.ts en server/agents/*/evals/, los ejecuta contra la aplicación en marcha y finaliza con un código distinto de cero si falla alguna comprobación.

Aserciones

Toda aserción devuelve un handle encadenable. Las aserciones son bloqueantes de forma predeterminada: un fallo hace fallar la evaluación (código de salida distinto de cero). Encadena .soft() para degradarla a una métrica de solo seguimiento, .gate() para volver a convertir una aserción suave en bloqueante, o .atLeast(n) para definir el umbral de aprobación en una aserción puntuada.

AserciónPasa cuando
t.succeeded()El último turno se completó sin errores del agente ni del stream.
t.calledTool(name)El agente llamó a name durante el run.
t.calledToolWith(name, expected)Se llamó a name con argumentos que contienen en profundidad expected (cada clave de expected coincide de forma recursiva; los argumentos adicionales se ignoran).
t.check(value, matcher)value cumple con el matcher: includes(substring), equals(expected) o matches(pattern).
import { defineEval, includes } from "@databricks/appkit/beta";

export default defineEval({
  description: "Helper agent answers a math question",
  agent: "helper",
  async test(t) {
    await t.send("What is 2 + 2?");
    t.succeeded();                          // gate
    t.check(t.reply, includes("4")).soft(); // métrica registrada, no hace fallar el gate
  },
});
// comprobación parcial profunda de los argumentos de la herramienta
await t.send("What's the weather in Brooklyn?");
t.calledTool("get_weather");
t.calledToolWith("get_weather", { city: "Brooklyn" });

Llama a t.skip("reason") para omitir una evaluación y lee t.reply, t.toolCalls y t.sessionId para inspeccionar el último turno.

LLM-as-judge

t.judge.* puntúa la última respuesta con un juez LLM (mediante autoevals apuntando a un endpoint de serving de Databricks). Cada juez devuelve una aserción puntuada (0..1) que bloquea de forma predeterminada: si no se alcanza, la evaluación falla. Encadena .atLeast(n) para fijar el umbral de aprobación, o .soft() para limitarte a hacer seguimiento. Los jueces requieren un modelo juez: pasa --judge-model <endpoint> (o define APPKIT_JUDGE_MODEL) junto con la autenticación de Databricks; si falta, t.judge.* lanza un error con un mensaje claro.

async test(t) {
  await t.send("What's the weather in Brooklyn?");
  t.succeeded();

  // closedQA no requiere datos de referencia: evalúa la respuesta frente a una pregunta.
  (await t.judge.closedQA(
    "Does the response describe weather conditions for Brooklyn?",
  )).atLeast(0.5);
}
  • t.judge.factuality(expected) — puntúa la respuesta frente a una respuesta de referencia esperada.
  • t.judge.closedQA(criteria) — puntúa si la respuesta responde a la pregunta, según criteria.
  • t.judge.custom(spec) — un juez basado en un template de prompt ({ name, promptTemplate, choiceScores }), el equivalente en TS del @scorer de MLflow.

Protege las llamadas al juez con isJudgeConfigured() cuando quieras que una evaluación siga recorriendo la ruta de ejecución sin un modelo de juez configurado:

import { defineEval, isJudgeConfigured } from "@databricks/appkit/beta";
// ...
if (isJudgeConfigured()) {
  (await t.judge.closedQA(guideline)).atLeast(0.5);
}

Conversaciones

Cada t.send es un turno del usuario. El orden en que los encadenas determina el hilo:

// Una sola interacción: un único turno.
await t.send("Summarize Q3 revenue.");
t.succeeded();

// Varios turnos: los envíos consecutivos comparten un mismo hilo, así que el agente ve el historial.
await t.send("Show me the orders table.");
await t.send("Now filter it to last week.");
t.succeeded();

// t.reset() descarta la conversación: el siguiente envío abre un hilo nuevo
// sin historial. Úsalo para ejecutar varias comprobaciones independientes de un solo turno en una misma prueba.
await t.send("What's 2 + 2?");
t.check(t.reply, includes("4"));
t.reset();
await t.send("What's the capital of France?");
t.check(t.reply, includes("Paris"));

Datasets

Agrega dataset: { table } para recorrer un dataset de evaluación gestionado de Databricks: una tabla catalog.schema.table de Unity Catalog con columnas inputs/expectations. La evaluación se ejecuta una vez por fila; el ejecutor vincula los inputs de cada fila a t.input y los expectations a t.expected. Para leer el dataset se necesita un cliente de workspace y un warehouse (--warehouse-id + autenticación).

import { defineEval, isJudgeConfigured, userTurns } from "@databricks/appkit/beta";

export default defineEval({
  description: "Query agent satisfies each dataset row's guidelines",
  dataset: { table: "main.mario.appkit_eval_dataset" }, // `limit?` opcional
  async test(t) {
    // Reproduce cada turno de usuario de la fila en un mismo hilo, para que el agente vea
    // cómo se acumula la conversación. Una fila con un único turno de usuario se envía una sola vez.
    for (const turn of userTurns(t.input)) {
      await t.send(turn);
    }
    t.succeeded();

    if (isJudgeConfigured()) {
      for (const guideline of guidelines(t.expected)) {
        (await t.judge.closedQA(guideline)).atLeast(0.5);
      }
    }
  },
});

Las estructuras de las filas coinciden con la interfaz de dataset gestionado de MLflow:

inputs {"messages":[{"role":"user","content":"..."}]} expectations {"guidelines":{"value":["...","..."]}} (opcional)

userTurns(t.input) extrae, en orden, el contenido de cada mensaje con role: "user" de la entrada {messages:[...]}: una fila puede contener una conversación completa de varios turnos, y reproducir cada turno del usuario sobre un mismo hilo permite que el agente vaya acumulando historial (los turnos intercalados de asistente/sistema se ignoran; el agente genera los suyos). Lee tú mismo expectations.guidelines.value; la interfaz envuelve el array como {value: [...]}:

function guidelines(expected: Record<string, unknown> | undefined): string[] {
  const g = (expected?.guidelines as { value?: unknown } | undefined)?.value;
  return Array.isArray(g) ? g.map(String) : [];
}

Ejecuta una evaluación de dataset:

appkit agent eval dataset --root apps/dev-playground --url http://localhost:3000 \
  --profile <profile> --warehouse-id <warehouse-id> --judge-model <endpoint>

Ejecución de evaluaciones y CI

appkit agent eval [filter] — ejecuta las evaluaciones del agente (server/agents/<id>/evals/*.eval.ts) contra una aplicación en ejecución.

FlagDescripción
[filter]Solo ejecuta las evaluaciones cuyo <agent>/<id> contenga esta subcadena (o un id de agente exacto)
--url <url>URL base de la aplicación en ejecución (por defecto http://localhost:3000)
--strictFalla también ante incumplimientos de aserciones suaves
--root <dir>Raíz del proyecto que contiene server/agents/ (por defecto: cwd)
--header <header...>Cabecera de solicitud adicional con el formato 'Key: value' (repetible)
--tag <tag...>Solo ejecuta las evaluaciones etiquetadas con alguna de estas etiquetas (repetible)
--profile <name>Perfil de la Databricks CLI con el que autenticarse mediante OAuth (por defecto: DATABRICKS_CONFIG_PROFILE)
--databricks-host <host>Host de Databricks para escribir las valoraciones de MLflow (por defecto: DATABRICKS_HOST)
--databricks-token <token>Token de Databricks para escribir las valoraciones de MLflow (por defecto: DATABRICKS_TOKEN)
--experiment <id>Id del experimento de MLflow para la ejecución de evaluación (por defecto: MLFLOW_EXPERIMENT_ID)
--warehouse-id <id>Id del SQL warehouse para leer datasets de evaluación gestionados (por defecto: DATABRICKS_WAREHOUSE_ID)
--judge-model <endpoint>Endpoint de serving de Databricks que se usará como juez LLM para t.judge.* (por defecto: APPKIT_JUDGE_MODEL)
--concurrency <n>Máximo de evaluaciones/filas de dataset que se ejecutan en paralelo (por defecto: 4)
--timeout <ms>Tiempo de espera predeterminado por evaluación en ms (un timeoutMs por evaluación lo anula)
--retries <n>Reejecuta una evaluación hasta N veces cuando falla por un error de infraestructura (turno/tiempo de espera); los fallos de aserción no se reintentan
--min-pass-rate <rate>Condiciona el resultado a la tasa de aprobación agregada (0..1) en vez de exigir que todas las evaluaciones pasen; sale con 1 si queda por debajo
--reporter <format>Formato del informe: text (consola en vivo), json (dashboards) o junit (generadores de informes de pruebas de CI)
--output <file>Escribe el informe json/junit en este archivo en lugar de stdout (se ignora para text)

Notas:

  • La autenticación prioriza OAuth. --profile <name> genera un token OAuth a partir de tu perfil de la Databricks CLI: no hace falta un PAT. Un --databricks-host/--databricks-token explícito (o las variables de entorno DATABRICKS_*) tiene prioridad sobre el perfil.
  • --retries solo absorbe la inestabilidad de la infraestructura. Un reintento se dispara únicamente cuando una evaluación lanza un error o agota el tiempo de espera (result.error definido); una respuesta incorrecta es señal real y nunca se reintenta. Cada intento recibe un driver nuevo.
  • Condiciones de aprobación. Por defecto, la ejecución termina con código distinto de cero si falla alguna evaluación. --min-pass-rate 0.9 cambia al modo de umbral: termina con código distinto de cero solo cuando la tasa de aprobación agregada cae por debajo del umbral. Además, --strict trata los incumplimientos de aserciones suaves como fallos.
  • Informes de CI. --reporter junit --output results.xml escribe un archivo JUnit para los generadores de informes de pruebas de CI; --reporter json emite resultados legibles por máquina. En los informes legibles por máquina, las líneas dirigidas a personas van a stderr para que stdout quede limpio para el informe.
# CI: exigir una tasa de aprobación del 90 %, generar JUnit, autenticar y reportar a MLflow
appkit agent eval --url "$APP_URL" \
  --profile ci \
  --experiment "$MLFLOW_EXPERIMENT_ID" \
  --concurrency 4 --retries 1 \
  --min-pass-rate 0.9 \
  --reporter junit --output eval-results.xml

Configuración por directorio

Coloca un archivo evals.config.ts junto a las evals de un agente para definir los valores predeterminados de las ejecuciones de ese agente:

// server/agents/query/evals/evals.config.ts
import { defineEvalConfig } from "@databricks/appkit/beta";

export default defineEvalConfig({
  maxConcurrency: 4,   // ejecuta hasta 4 evaluaciones/filas en paralelo
  timeoutMs: 30_000,   // tiempo de espera predeterminado por evaluación
});

Precedencia: una opción de la CLI prevalece sobre el valor de evals.config.ts, que a su vez prevalece sobre el valor predeterminado integrado (concurrencia 4, sin tiempo de espera). Un def.timeoutMs definido por evaluación anula ambos para esa evaluación.

Informes de MLflow

Cuando se define --experiment <id> (o MLFLOW_EXPERIMENT_ID) junto con la autenticación de Databricks, el ejecutor crea de entrada un run de evaluación nativo de MLflow. A medida que cada evaluación se ejecuta contra la aplicación, su traza de turno se vincula al run, y cada aserción y puntuación del juez se registra como feedback en esa traza:

  • Cada aserción determinista se convierte en un Feedback con origen CODE y un valor booleano.
  • Cada aserción de juez se convierte en un Feedback con origen LLM_JUDGE, con su puntuación numérica de 0 a 1 y su justificación.
  • Se adjunta un Feedback general appkit_eval de aprobado/fallido por cada evaluación, y las métricas agregadas se registran al finalizar el run.

Omite --experiment (y MLFLOW_EXPERIMENT_ID) para ejecutar las evaluaciones solo en local, sin efectos secundarios en MLflow: la CLI muestra un recordatorio de que se omitió el run de evaluación.

Esquema del frontmatter

ClaveTipoNotas
endpointstringNombre del endpoint de serving del modelo. Atajo de model.
modelstringIgual que endpoint; cualquiera de los dos funciona.
toolsarrayLista unificada de herramientas. Las entradas son plugin:<name> / plugin:<name>: [t1, t2] / plugin:<name>: { only, except, rename, prefix } para herramientas de plugin, o una <key> simple que se resuelve contra agents({ tools: {...} }) para herramientas ambientales. Consulta «Nivel 2: acotar herramientas en el frontmatter» más arriba para ver ejemplos.
skillsarrayNombres de las skills globales (pool compartido skills/ o volumen de catálogo) que se harán visibles para este agente. Las skills por agente ubicadas en <id>/skills/ siempre son visibles. Consulta Skills.
defaultbooleanEl primer id de agente (en orden alfabético) con default: true pasa a ser el agente predeterminado.
agentsarrayIds de subagentes (carpetas hermanas) a los que delegar; cada uno se convierte en una herramienta agent-<id>. Se resuelve contra otros agentes definidos en markdown y en código.
maxStepsnumberSugerencia de pasos máximos para el adaptador.
maxTokensnumberSugerencia de tokens máximos para el adaptador.
generationParamsobjectParámetros de generación del adaptador (p. ej. temperature, top_p) que se transmiten cuando AppKit construye el adaptador.
baseSystemPromptfalsestringAnulación por agente. false desactiva el prompt base de AppKit.
ephemeralbooleanSi es true, el hilo creado para una solicitud de chat dirigida a este agente se elimina de ThreadStore al terminar el stream. Úsalo en agentes sin estado de una sola ejecución (p. ej. autocompletado), para que el historial no se acumule ni contamine llamadas futuras. Su valor predeterminado es false.

Las claves desconocidas se registran y se ignoran. El YAML no válido y las referencias inexistentes a plugins o herramientas provocan un error en el arranque.

Databricks Developer Hub

¿Todo listo para lanzar tu próxima aplicación basada en agentes en minutos?

Leer la documentación