Ir para o conteúdo principal

Agentes

Agentes

Plugin beta

Este plugin está em fase beta. As APIs podem mudar entre versões menores. Importe de @databricks/appkit/beta. Consulte Níveis de Estabilidade de Plugins.

O plugin agents transforma um app AppKit do Databricks em um host de agentes de IA. Ele descobre definições de agentes no disco — uma pasta por agente em server/agents/, contendo agent.md (markdown) ou agent.ts (código) — e as expõe em POST /invocations e POST /responses (sem streaming, aliases), além de POST /chat (com streaming) e de rotas para gerenciamento de threads, cancelamento e aprovação HITL. Em todos os casos, o id do agente é o nome da pasta; não há mapa para manter nem id para repetir.

Esta página cobre todo o ciclo de vida. Para as primitivas escritas manualmente (tool(), mcpServer()), consulte tools.

Requisitos

Apenas serving endpoints com suporte a streaming

O plugin de agentes conduz o LLM por meio de Server-Sent Events. As Foundation Model APIs (Claude, Llama, GPT etc.) e outros endpoints no estilo chat oferecem suporte a streaming e funcionam de imediato. Endpoints de modelos personalizados que retornam uma única resposta JSON (por exemplo, deployments típicos de sklearn ou pyfunc do MLflow) não fazem streaming — apontar um agente para um deles falha logo no primeiro turno com "Response body is null — streaming not supported". Se você indicar um serving endpoint em apps init, escolha um cujo modelo implemente o protocolo de streaming de chat-completions; o plugin de agentes lê o nome dele em DATABRICKS_SERVING_ENDPOINT_NAME sempre que o próprio agente não define model:.

Para o fluxo sem streaming com um endpoint personalizado, use a rota /invoke do plugin serving com useServingInvoke.

Ou dispense totalmente a configuração de serving endpoint usando o adaptador gerenciado da Supervisor API (beta).

Instalação

agents é um plugin comum. Adicione-o ao plugins[] junto com server() e quaisquer plugins ToolProvider cujas ferramentas você queira disponibilizar aos agentes.

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

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

Só isso já lhe dá um servidor HTTP ativo com POST /invocations (e seu alias POST /responses) conectado a um agente orientado por markdown. Use POST /chat quando quiser a interface com streaming e suporte a HITL.

Nível 1: adicione um pacote de agente em markdown

Cada agente fica em sua própria pasta dentro de server/agents/, com o arquivo de entrada agent.md. Uma pasta só é um agente se tiver um arquivo de entrada (agent.md ou agent.ts); caso contrário, ela é ignorada — assim, as pastas de recursos de cada agente ficam ao lado do arquivo de entrada, em especial uma pasta skills/ com as Skills (pacotes de instruções sob demanda que o agente carrega pelo nome). Já a pasta compartilhada server/agents/skills/ reúne as skills disponíveis para qualquer agente.

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

You are a helpful data assistant running on Databricks.

Use the available tools to query data, browse files, and help users.

Na inicialização, o plugin:

  1. Descobre server/agents/assistant/agent.md e registra o id de agente assistant.
  2. Analisa o frontmatter YAML e o corpo em markdown como as instructions do agente.
  3. Resolve o adaptador a partir de endpoint (ou recorre a DATABRICKS_SERVING_ENDPOINT_NAME).
  4. Monta o agente com o nome padrão (assistant).

O agente começa sem ferramentas. As ferramentas são opcionais — declare-as no frontmatter (Nível 2, abaixo) ou ative explicitamente a herança automática com agents({ autoInheritTools: { file: true } }). Veja "Postura de herança automática" mais adiante para entender o custo disso e por que vem desativada por padrão.

Migrando de `config/agents/`

Versões anteriores mantinham os agentes em markdown em config/agents/<id>/agent.md. Esse local ainda é lido como alternativa obsoleta (com um aviso único na inicialização); mova cada pasta para server/agents/<id>/agent.md para que todo agente — em markdown e em código — fique em um só lugar.

As requisições chegam em POST /invocations (ou em seu alias POST /responses) com um corpo compatível com a API Responses da OpenAI. Esses endpoints executam o agente até a conclusão e retornam uma única resposta JSON — sem SSE. Clientes com streaming devem usar POST /chat. Toda chamada de ferramenta é rastreada automaticamente. As chamadas de ferramentas do toolkit de plugins (as entradas plugin:<name> / plugins.<name>.toolkit()) passam ainda por asUser(req), de modo que seu SQL é executado como o usuário solicitante e o acesso a arquivos respeita as ACLs do Unity Catalog. Um tool({ execute }) escrito manualmente não é encapsulado: seu execute recebe apenas os argumentos validados da ferramenta (sem req), ou seja, roda com a identidade do service principal do app e não pode aderir ao OBO. Se uma ferramenta precisar agir como o usuário solicitante, exponha-a como ferramenta de plugin em vez de um execute manual. Veja Contexto de execução.

Sem HITL em `/invocations` e `/responses`

A superfície de invocação sem streaming não tem como devolver ao chamador um prompt de aprovação no meio da chamada. Quando approval.requireForDestructive está habilitado (padrão) e o agente resolvido possui alguma ferramenta anotada com efeito de mutação (effect: "write" | "update" | "destructive", ou o antigo destructive: true), POST /invocations e POST /responses rejeitam a requisição com HTTP 400 antes de o adaptador ser executado. Mova os agentes com suporte a HITL para POST /chat ou desabilite a aprovação com agents({ approval: { requireForDestructive: false } }) para agentes autônomos de back-office.

Nível 2: definir o escopo das ferramentas no frontmatter

---
endpoint: databricks-claude-sonnet-4-5
tools:
  - plugin:analytics                              # todas as ferramentas analytics.*
  - plugin:files: [uploads.read, uploads.list]    # apenas estas ferramentas de files
  - plugin:genie: { except: [getConversation] }   # tudo, exceto getConversation
  - get_weather                                   # ferramenta ambiente declarada no código
default: true
---

Você é um analista de dados somente leitura.

A lista unificada tools: combina referências a plugins e ferramentas de ambiente, espelhando a forma de função em TS tools(plugins) => ({ ...plugins.analytics.toolkit(), ...plugins.files.toolkit({ only: [...] }), get_weather: tool({...}) }). Cada entrada é uma das seguintes:

  • plugin:<name> — inclui todas as ferramentas do plugin indicado.
  • plugin:<name>: [tool1, tool2] — apenas as ferramentas listadas (açúcar sintático para { only: [...] }).
  • plugin:<name>: { ...ToolkitOptions } — todas as opções prefix / only / except / rename.
  • <key> (sem prefixo) — nome de uma ferramenta de ambiente resolvido a partir da configuração agents({ tools: { ... } }).

Ao declarar qualquer tools:, a herança automática padrão é desativada — o agente enxerga exatamente as ferramentas listadas.

Nível 3: agentes definidos por código

Os agentes em código ficam um por pasta em server/agents/, com o arquivo de entrada agent.ts (espelhando o agent.md do markdown). Esse arquivo exporta um agente já criado e seu id é o nome da pasta (server/agents/support/agent.tssupport). Nada repete a declaração do id.

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

export default createAgent({           // id derivado do nome da pasta: "support"
  instructions: "You help customers with data and files.",
  model: "databricks-claude-sonnet-4-5",                      // açúcar sintático em string
  tools(plugins) {
    return {
      ...plugins.analytics.toolkit(),                          // todas as ferramentas 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}`,
      }),
    };
  },
});

O plugin agents descobre esses arquivos na inicialização — sem registro, sem mapeamento:

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

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

A descoberta importa cada server/agents/<id>/agent.ts — o .ts de origem via tsx em desenvolvimento e o dist/agents/<id>/agent.js compilado em builds de produção (a saída compilada prevalece sobre a origem, independentemente de NODE_ENV). Como o servidor de produção é empacotado e importa apenas o que é alcançável a partir de server/server.ts, a configuração tsdown do modelo lista server/agents/*/agent.ts como entradas de build, de modo que dist/agents/*/agent.js sejam gerados para a varredura — é essa ligação que permite que uma pasta apenas copiada para o projeto sobreviva ao bundle de produção. (O agent.md em markdown é lido da origem tanto em desenvolvimento quanto em produção — é dado, não é compilado.) A raiz é sempre server/agents — não há opção de configuração para movê-la; markdown que ainda esteja em config/agents/ é lido como fallback obsoleto (com aviso único).

A saída compilada sombreia a origem em desenvolvimento

Como a saída compilada prevalece sobre a origem, um dist/agents / build/agents desatualizado deixado por um npm run build anterior será usado pelo npm run dev em vez dos seus server/agents/*.ts ativos, fazendo com que as edições pareçam ignoradas. Exclua o diretório de build se um agente em código parecer congelado — uma nova build apenas substitui por um instantâneo mais recente, então só a exclusão restaura o recarregamento em desenvolvimento direto da origem. O markdown é sempre lido da origem, portanto as edições em agent.md nunca são sombreadas.

A entrada pode usar export default createAgent({...}) ou exportar um único agente criado com nome; em ambos os casos, o id é o nome da pasta. Uma pasta cuja entrada não exporta nenhum agente criado (ou que não tenha nem agent.ts nem agent.md) é ignorada. Marque um agente como padrão com createAgent({ default: true }) (equivalente ao default: true no frontmatter em markdown); um agents({ defaultAgent }) explícito ainda prevalece.

Agentes definidos em código começam sem nenhuma ferramenta por padrão. A forma de função tools(plugins) => Record<string, AgentTool> é a principal maneira de incorporar ferramentas de plugin: cada plugin registrado em createApp({ plugins: [...] }) aparece no parâmetro plugins, e você chama .toolkit(opts?) nele para obter um registro pronto para ser espalhado. O runtime invoca a função uma única vez durante o setup do agente e mantém o resultado em cache — cada plugin é mencionado exatamente uma vez (em createApp), sem variáveis retidas nem importações marcadoras.

Chamadas tool({...}) em linha ficam no mesmo registro. O name delas é opcional — o plugin de agentes o sobrescreve com a chave do registro (get_weather, acima).

A herança automática está desativada por padrão para ambas as origens — um agente em markdown ou em código sem nenhum tools: declarado recebe um índice de ferramentas vazio. Habilite uma origem explicitamente com agents({ autoInheritTools: { file: true } }) (ou { code: true }, ou true para ambas).

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

Passar um mapa de agentes construído manualmente ainda funciona e continua sendo aceito por compatibilidade retroativa, mas emite um aviso único de obsolescência e será removido em uma versão menor futura. Esse mapa repete o id de cada agente (uma vez em createAgent e outra como chave do mapa); a descoberta a partir de server/agents/ elimina tanto o mapa quanto a repetição. Para migrar, mova cada createAgent(...) para seu próprio server/agents/<id>/agent.ts (exportação padrão ou uma única exportação nomeada) e remova o mapa. Se um agente descoberto e uma entrada do mapa tiverem o mesmo id, a descoberta prevalece e a entrada do mapa é ignorada (com um aviso único). (Subagentes em linha — createAgent({ agents: { ... } }) em uma definição — não são afetados; apenas o mapa no nível do plugin está obsoleto.)

Alguns exemplos mais adiante ainda passam agentes em linha por esse mapa para manter os trechos curtos — em um aplicativo real, cada uma dessas definições createAgent(...) fica em seu próprio server/agents/<id>/agent.ts e dispensa o mapa.

Delimitando ferramentas no código

plugins.<name>.toolkit(opts?) aceita as mesmas ToolkitOptions do frontmatter markdown:

OpçãoExemploSignificado
only{ only: ["query"] }Lista de permissão de nomes locais de ferramentas
except{ except: ["legacy"] }Lista de bloqueio de nomes locais de ferramentas
prefix{ prefix: "" }Remove o prefixo ${pluginName}.
rename{ rename: { query: "q" } }Remapeia nomes locais específicos

Para plugins que não expõem o método .toolkit() (por exemplo, plugins ToolProvider de terceiros escritos com toPlugin puro), o runtime recorre a percorrer getAgentTools() e sintetizar chaves com namespace (${pluginName}.${localName}). Esse mecanismo alternativo respeita only / except / rename / prefix da mesma forma.

Se um plugin referenciado não estiver registrado em createApp({ plugins }), o plugin de agentes lança um erro no setup com uma listagem Available: …, para que você possa corrigir a configuração antes da primeira requisição.

Nível 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 },  // expostos como agent-researcher, agent-writer
});

// server/agents/{supervisor,researcher,writer}/agent.ts — uma pasta para cada um
export default supervisor;

await createApp({
  plugins: [server(), agents()],  // descobertos em server/agents/
});

Coloque supervisor, researcher e writer em suas próprias pastas server/agents/<id>/agent.ts (cada uma com exportação padrão) — um pai em markdown também pode delegar a um filho em código em uma pasta irmã por meio do frontmatter agents: [helper]. Cada chave em agents: {...} de uma AgentDefinition se torna uma ferramenta agent-<key> no pai. Quando invocado, o plugin de agentes executa o adaptador do filho com uma lista de mensagens nova (sem estado de thread compartilhado) e retorna o texto agregado. Ciclos no grafo agents: {} inline de um agente em código são rejeitados no carregamento (createAgent); já a delegação agents: em markdown rejeita autorreferências no carregamento e limita ciclos mais profundos em runtime por meio de limits.maxSubAgentDepth.

Skills

Skills são pacotes de instruções sob demanda — no mesmo formato SKILL.md usado pelo Claude Code e pelo Cursor. Apenas o name e a description de cada skill ficam no prompt de sistema (sempre ativos e de baixo custo); o corpo completo é carregado sob demanda, quando o agente (ou o usuário) a invoca. Isso funciona em qualquer modelo servido pelo Databricks — o próprio AppKit implementa essa divulgação progressiva, portanto não depende de um recurso de skills nativo do provedor.

Uma skill é um diretório com um SKILL.md e os arquivos de referência que você quiser incluir:

server/agents/ skills/ # pool compartilhado — qualquer agente pode optar por usar pdf-forms/ SKILL.md reference.md planner/ agent.md skills/ # exclusivo do agente `planner` house-style/ SKILL.md
---
name: pdf-forms
description: Fill and validate PDF form fields from a data record.
---

Para preencher um formulário PDF:

1. Consulte `reference.md` para conhecer as convenções de nomes de campos.
2. ...

name e description são obrigatórios; license, allowed-tools e metadata são aceitos para compatibilidade com skills criadas em outras ferramentas. Chaves desconhecidas geram um aviso e são ignoradas.

Visibilidade

  • Skills por agente (server/agents/<id>/skills/) são sempre visíveis para esse agente.
  • Skills globais (server/agents/skills/ e skills em volumes do catálogo) são opt-in: liste-as no frontmatter do agente, skills: [pdf-forms]. Defina autoInheritSkills: true (ou { file, code }) no plugin para tornar todas as skills globais visíveis sem precisar listá-las — desativado por padrão, para que o catálogo sempre ativo de cada agente permaneça enxuto.

Como o agente usa uma skill

Duas ferramentas integradas somente leitura são injetadas em qualquer agente que tenha um catálogo visível:

  • load_skill(skill) — retorna as instruções completas da skill e um manifesto dos arquivos incluídos nela.
  • read_skill_file(skill, path) — retorna o conteúdo de um desses arquivos.

O modelo chama load_skill por conta própria quando uma tarefa corresponde à descrição de uma skill. Um usuário pode forçar uma skill específica em um turno usando o prefixo /skill-name no chat (ou a opção send(message, { skill }) do useAgentChat); nesse caso, as instruções da skill são injetadas no turno de forma determinística, e load_skill continua disponível para seleção automática. O cliente lê o catálogo de cada agente no payload de clientConfig() do plugin para alimentar um seletor.

Skills de catálogo (Unity Catalog Volume)

Aponte skillsVolume (ou a variável de ambiente DATABRICKS_VOLUME_AGENT_SKILLS) para um Volume do UC organizado da mesma forma — <volume>/<name>/SKILL.md. As skills de catálogo são descobertas na inicialização e a cada reload(), mescladas ao pool global compartilhado e lidas como o service principal (o padrão de skillCredentialMode é "sp"). A ideia é que formem um pool compartilhado e curado; volumes de skills por usuário (OBO) ainda não têm suporte. Declarar o recurso opcional volume no manifesto permite que o scaffolder conceda acesso de leitura ao SP.

Colisões de nomes

Os nomes de skills são referenciados sem qualificação. Se duas origens fornecerem o mesmo nome, cada uma passa a ter um nome qualificado <scope>:name (agent:, bundle:, volume:) e o nome sem qualificação é rejeitado como ambíguo, com as alternativas listadas. Duas skills com o mesmo nome vindas da mesma origem geram um erro em tempo de inicialização.

Ressalvas da v1

  • Scripts não são executados. Uma skill pode referenciar scripts/foo.py; a v1 carrega apenas o texto e os documentos de referência.
  • allowed-tools é apenas informativo. Aparece como uma dica na skill carregada, mas não é imposto — carregar uma skill não restringe as ferramentas que o agente pode chamar. Não se trata de um sandbox.
  • O corpo das skills não tem controle de acesso por usuário (a leitura é feita como o SP). Mantenha conteúdo sensível ao usuário fora do corpo das skills.

Nível 5: standalone (sem 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 executa o adaptador sem createApp nem HTTP. Chamadas inline de tool() funcionam de forma standalone, como mostrado acima. Para usar ferramentas de plugin no modo standalone, passe as factories do plugin em RunAgentInput.plugins e acesse-as pela forma de função 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 instancia antecipadamente cada plugin em RunAgentInput.plugins, executa o ciclo de vida padrão attachContext({}) + await setup() e compartilha as instâncias entre a execução de nível superior e cada despacho de subagente. Plugins cujo setup() exige runtime exclusivo de createApp (por exemplo, WorkspaceClient, ServiceContext) lançam erro logo na inicialização standalone, com uma mensagem clara de "use createApp instead", em vez de falhar no meio do fluxo.

Ferramentas hospedadas de MCP (mcpServer(...)) continuam exigindo agents() (elas precisam de um cliente MCP ativo). Já as ferramentas hospedadas da API do Supervisor (supervisorTools.*) funcionam no runAgent standalone — o adaptador tem tudo o que precisa para executá-las no servidor. Isso viabiliza o uso de Supervisor Agents em avaliação em lote / CI sem createApp. O despacho de ferramentas de plugin no modo standalone é executado como o service principal (sem OBO) e ignora a barreira de aprovação do plugin de agents — trate o runAgent standalone como um ambiente de prompt confiável (CI, avaliação em lote, scripts internos), e não como uma superfície exposta ao usuário final.

Adicionando agentes a um app existente

Já tem um app e quer adicionar agentes? O que precisa ser alterado depende do tipo:

Agentes em markdown — apenas o plugin. Adicione server/agents/<id>/agent.md, inclua agents() nos seus plugins e pronto. O markdown é lido do código-fonte em runtime tanto em dev quanto em produção, portanto não há mudança de build.

Agentes em código (server/agents/<id>/agent.ts) — atualize também o build do servidor para que o bundle de produção os gere. Agentes em código não são importados em lugar nenhum, então um build que compila apenas server/server.ts nunca produz dist/agents/*/agent.js, e um npm run build + start empacotado encontraria zero agentes em código.

O dev esconde isso

O npm run dev (tsx) importa o código-fonte .ts diretamente, então os agentes em código funcionam ali sem nenhuma mudança de build — a lacuna só aparece em um build empacotado. Se você adicionar agentes em código mas esquecer a mudança de build, o plugin emite um aviso na inicialização (indicando a correção) em vez de falhar silenciosamente.

A correção, em uma linha, é adotar o preset de build:

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

export default appkitServerConfig();

appkitServerConfig() detecta automaticamente server/agents/ e adiciona o glob de entrada + clean apenas quando existem agentes de código; passe sobrescritas como appkitServerConfig({ external, define, ... }), ou uma função appkitServerConfig((base) => ({ ...base })) para ter controle total. Essa também é a última vez que você mexe nesse arquivo — futuras mudanças na configuração de build já vêm com o pacote. Se preferir manter uma configuração escrita à mão, adicione as entradas manualmente:

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

Agentes gerenciados: o adaptador da Supervisor API

DatabricksAdapter.fromSupervisorApi (beta) é a maneira sem configuração de executar um agente: em vez de provisionar um serving endpoint de modelo e apontar para ele, você executa o loop agentic no workspace do Databricks apontando para a Responses API do AI Gateway (/ai-gateway/mlflow/v1/responses), que roda o LLM — e quaisquer ferramentas hospedadas — como um serviço gerenciado no Databricks. Sem DATABRICKS_SERVING_ENDPOINT_NAME, sem verificação de suporte a streaming, sem integração manual de ferramentas em JS para os casos comuns.

O agente mínimo tem apenas uma linha a mais que um agente em 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 }) já aceita adaptadores e promises de adaptadores, além da string com o nome do modelo usada nos exemplos anteriores, então basta passar o resultado da factory diretamente. A factory resolve as credenciais pela cadeia do SDK (DATABRICKS_HOST, OAuth, PAT, …); informe workspaceClient para reutilizar um cliente existente.

Ferramentas hospedadas

Exponha spaces do Genie, funções/conexões do Unity Catalog, Knowledge Assistants ou outros apps AppKit ao modelo declarando-os como ferramentas do agente — no mesmo lugar em que todas as demais ferramentas são declaradas. A execução continua no servidor; você não escreve nenhum código de ferramenta:

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.* recebe um único objeto de opções nomeadas — as strings críticas para o roteamento ganham rótulos no local da chamada, o que torna impossíveis os bugs de troca de argumentos posicionais.

description é obrigatório e não pode ser vazio — o LLM o usa para rotear entre as ferramentas, portanto dois Genie spaces rotulados como "Genie space" serão indistinguíveis.

Descrições de ferramentas hospedadas são configuração confiável da aplicação (CWE-1427)

A description de uma ferramenta hospedada é lida pelo LLM para decidir quando rotear para essa ferramenta. Não a derive de entrada não confiável — mensagens de usuários, corpos de requisição, campos de texto livre de sistemas externos ou qualquer valor que um atacante possa influenciar. Trate description (e id/name) como controlados pela aplicação, assim como as instructions do agente. Permitir aqui uma string controlada pelo usuário cria um ponto de injeção de prompt: uma descrição hostil pode convencer o modelo a rotear para (ou desviar de) uma ferramenta em qualquer requisição futura tratada pelo agente.

A mesma cautela vale para as descriptions de MCP e para qualquer outro campo que o modelo leia no momento do roteamento.

FactoryTipo de ferramentaIdentificador
supervisorTools.genieSpace({ id, description })Genie spaceid do space
supervisorTools.ucFunction({ name, description })Função do Unity Catalognome de três partes
supervisorTools.knowledgeAssistant({ knowledgeAssistantId, description })Knowledge Assistantid do assistente
supervisorTools.app({ name, description })Databricks Appnome do app
supervisorTools.ucConnection({ name, description })Conexão do UCnome da conexão

Declarando ferramentas hospedadas em agentes markdown

As ferramentas de supervisor hospedado também funcionam em agentes baseados em markdown: declare a ferramenta no código (em agents({ tools: { ... } })) e referencie sua chave no 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
---

You answer questions about NYC taxi data using the Genie space.

Nenhuma sintaxe nova de frontmatter — a resolução de ferramentas ambiente em tools: já resolve chaves simples com base em agents({ tools }), e o formato de registro etiquetado de supervisorTools.* permite que o plugin as classifique automaticamente.

O que não se aplica a agentes da Supervisor API

O runtime gerenciado controla a própria execução de ferramentas, portanto o adaptador ignora intencionalmente ferramentas de função e subagentes do índice de ferramentas do plugin de agentes. Para qualquer agente cujo model: seja um adaptador Supervisor:

  • Apenas entradas supervisorTools.* chegam ao modelo. Ferramentas de função (tool({...})), ferramentas hospedadas MCP (mcpServer(...)) e subagentes locais (agents: { ... }) declarados junto a um adaptador supervisor geram um aviso no momento do registro e não são expostos ao modelo. A verificação de capacidade é disparada por consumesInputTools: false no adaptador.
  • A etapa de aprovação com intervenção humana não é acionada (as chamadas de ferramenta nunca entram no processo Node; anotações effect: "destructive" são irrelevantes para ferramentas hospedadas).
  • limits.maxToolCalls não é aplicado (o runtime gerenciado contabiliza as próprias chamadas).
  • O OBO por chamada não se aplica a ferramentas hospedadas; elas são executadas com as credenciais que o runtime gerenciado usa para o recurso de destino.

Composição de subagentes entre adaptadores

Os adaptadores supervisor e chat-completions podem aparecer juntos no mesmo mapa agents({ agents: { ... } }), mas a composição só funciona em um sentido:

  • Pai chat-completions → subagente supervisor funciona nativamente. O pai despacha via agent-{key} como uma ferramenta de função comum; o adaptador do filho executa inteiramente no AI Gateway.
  • Pai supervisor → filhos com ferramenta de função / subagente local ainda não está implementado. A verificação de capacidade emite um aviso no momento do registro; essas ferramentas não chegarão ao modelo supervisor. Trabalhos futuros eliminarão essa restrição roteando os eventos response.function_call do SA por context.executeTool.
Caminho de recuperação para turnos de ferramenta sem streaming

Alguns tipos de ferramenta hospedada retornam o texto final do assistente sem eventos incrementais output_text.delta. O adaptador conta com um caminho de recuperação que extrai o texto de response.completed.output[] para que o turno não fique silenciosamente vazio. Defina DEBUG=appkit:agents:supervisor-api para registrar o histograma de tipos de evento por turno, caso queira verificar qual caminho um turno seguiu.

Referência de configuração

agents({
  // Os agentes ficam em server/agents/<id>/ (raiz fixa). config/agents é lido como alternativa obsoleta.
  agents?: Record<string, AgentDefinition>,  // OBSOLETO — use a descoberta em 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 }, // desativado por padrão
  skillsVolume?: string,        // Volume do UC para skills do catálogo; recorre a DATABRICKS_VOLUME_AGENT_SKILLS
  skillCredentialMode?: "sp" | "obo", // padrão "sp" (consulte Skills)
  threadStore?: ThreadStore,    // padrão: em memória
  baseSystemPrompt?: false | string | (ctx: PromptContext) => string,
  mcp?: {
    trustedHosts?: string[],    // nomes de host adicionais permitidos para URLs MCP personalizadas
    allowLocalhost?: boolean,   // padrão: NODE_ENV !== "production"
  },
  approval?: {
    requireForDestructive?: boolean,  // padrão: true
    timeoutMs?: number,               // padrão: 60_000
  },
  limits?: {
    maxConcurrentStreamsPerUser?: number, // padrão: 5
    maxToolCalls?: number,                // padrão: 50
    maxSubAgentDepth?: number,            // padrão: 3
    toolCallTimeoutMs?: number,           // padrão: 300_000 (5 min)
  },
})

autoInheritTools tem como padrão { file: false, code: false } — nenhuma ferramenta é propagada para nenhum agente, a menos que o desenvolvedor opte explicitamente por isso. Quando a opção está ativada, apenas as ferramentas que o autor do plugin marcou com autoInheritable: true são propagadas; ferramentas destrutivas ou que alteram estado são sempre ignoradas no caminho de herança automática, mesmo com a opção ativada. A forma abreviada booleana (autoInheritTools: true) se aplica a ambas as origens. Consulte "Postura de herança automática" abaixo.

Política de hosts MCP

O AppKit aplica uma política de confiança zero a toda URL MCP usada como ferramenta hospedada. Por padrão, apenas URLs de workspace do Databricks de mesma origem (correspondentes ao DATABRICKS_HOST resolvido) podem ser acessadas. Qualquer outro host precisa ser explicitamente incluído na lista de permissões por meio de mcp.trustedHosts, e as credenciais do workspace (service principal e tokens de usuário em nome de terceiros) nunca são encaminhadas a esses hosts.

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

A política aplica quatro regras no momento do connect() do MCP, antes que qualquer byte seja enviado:

  1. Somente URLs http e https são aceitas.
  2. O http:// em texto simples é rejeitado em todos os casos, exceto para localhost quando allowLocalhost é true (padrão em desenvolvimento, desativado em produção).
  3. O nome de host de destino deve corresponder ao host do workspace, ser igual a localhost (se permitido) ou constar em trustedHosts.
  4. O endereço DNS resolvido não pode estar nas faixas de loopback, RFC1918, CGNAT (100.64.0.0/10), link-local (169.254.0.0/16 — que abrange os serviços de metadados de nuvem), ULA ou multicast.

Cabeçalhos Authorization que carregam credenciais do workspace ficam restritos a URLs do workspace de mesma origem. Um mcpServer(name, url) que aponte para um host externo confiável deve se autenticar por conta própria (por exemplo, com um token personalizado embutido na url).

Postura de herança automática

O AppKit trata a herança automática como uma operação de duas chaves: o desenvolvedor precisa ativar autoInheritTools E o autor do plugin precisa marcar cada ferramenta com autoInheritable: true. Ambas as condições são necessárias para que uma ferramenta se propague para o índice de um agente sem configuração explícita.

// Adesão no nível do plugin de agentes (escolha uma):
agents({ autoInheritTools: true });                   // ambas as origens
agents({ autoInheritTools: { file: true } });         // apenas agentes em markdown
agents({ autoInheritTools: { file: true, code: true } });

// Por ferramenta, dentro de um plugin:
defineTool({
  description: "safe read",
  schema: z.object({ ... }),
  annotations: { effect: "read", requiresUserContext: true },
  autoInheritable: true, // consentimento explícito de que esta ferramenta pode se propagar automaticamente
  execute: (args, signal) => ...,
});

Os plugins principais do AppKit são distribuídos com as seguintes marcações autoInheritable:

FerramentaautoInheritableJustificativa
analytics.querysimSQL somente leitura com escopo OBO, aplicado em runtime pelo classificador
files.list / files.read / files.exists / files.metadatasimOperações de leitura com escopo OBO
files.upload / files.deletenãoAlteram estado — conecte explicitamente
genie.getConversationsimHistórico somente leitura
genie.sendMessagenãoConversa do Genie que altera estado
lakebase.querynãoJá restrito por exposeAsAgentTool; a auto-herança permanece fechada como defesa em profundidade

Plugins ToolProvider de terceiros que não expõem um método toolkit() também ficam de fora do caminho de auto-herança — suas ferramentas precisam ser conectadas explicitamente via tools:. No setup, o plugin de agentes registra em logs o que cada agente herdou e o que foi ignorado, deixando essa postura visível:

[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.

Ferramentas SQL para agentes

Duas ferramentas de agente integradas podem executar SQL em nome do LLM: analytics.query (no SQL warehouse do Databricks) e a opcional lakebase.query (em um banco de dados Lakebase Postgres). Ambas têm posturas de segurança distintas, pois são executadas com privilégios diferentes.

analytics.query é executada com o token OBO do chamador (as credenciais Databricks do usuário final). Sua anotação readOnly: true é aplicada em tempo de execução — as instruções são tokenizadas e apenas SELECT, WITH, SHOW, EXPLAIN, DESCRIBE e DESC são aceitas. Operações de escrita, DDL e instruções encadeadas são rejeitadas antes que a requisição chegue ao warehouse:

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

// rejeitado no plugin, nunca chega ao warehouse
analytics.query({ query: "UPDATE main.sales.orders SET status = 'cancelled'" })
analytics.query({ query: "SELECT 1; DROP TABLE main.sales.orders" })

lakebase.query não é registrado como ferramenta de agente por padrão. Habilitá-lo é uma decisão explícita, pois o pool do Lakebase está vinculado ao service principal da aplicação: um agente com acesso a essa ferramenta pode executar SQL como o SP, independentemente de qual usuário final tenha iniciado a requisição. Ative essa opção com uma flag de reconhecimento:

lakebase({
  exposeAsAgentTool: {
    iUnderstandRunsAsServicePrincipal: true,
    readOnly: true, // padrão
  },
});

Com readOnly: true (padrão), aplica-se o mesmo classificador de SQL usado em analytics.query, e a instrução aceita ainda é encapsulada em BEGIN READ ONLY; … ROLLBACK;, de modo que o servidor Postgres rejeite qualquer escrita que escape ao classificador (por exemplo, um SELECT sobre uma função com efeitos colaterais). A anotação da ferramenta é { effect: "read" }.

Com readOnly: false, a ferramenta aceita SQL arbitrário e recebe a anotação { effect: "destructive" }. O efeito destructive aciona a etapa de aprovação com intervenção humana descrita abaixo em todas as invocações.

Aprovação com intervenção humana para ferramentas que alteram dados

Qualquer ferramenta anotada com um efeito de alteração — effect: "write" | "update" | "destructive" (preferencial) ou o booleano legado destructive: true — exige aprovação explícita do usuário antes da execução. Segurança por padrão: defina approval.requireForDestructive: false apenas para agentes de back-office totalmente autônomos executados em contextos de usuário único.

Fluxo:

  1. Antes de executar a ferramenta, o plugin de agentes emite um evento SSE appkit.approval_pending contendo o approval_id, stream_id, tool_name, args e annotations da chamada pendente.
  2. O cliente de chat renderiza um prompt de aprovação (veja o cartão de aprovação do aplicativo de referência).
  3. O mesmo usuário que iniciou o stream envia a decisão para 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. Se aprovada, a ferramenta é executada normalmente e o stream continua. Se negada, o adaptador recebe a string "Tool execution denied by user approval gate (tool: <name>)." como saída da ferramenta, e o LLM pode se desculpar ou replanejar. Se nenhuma decisão chegar dentro de approval.timeoutMs (padrão de 60 s), o gate nega automaticamente.

A rota garante que quem decide seja o dono do stream: uma aprovação vinda de um x-forwarded-user diferente retorna 403. Cancelar o stream via POST /api/agents/cancel nega todas as aprovações pendentes nesse stream.

Limites de recursos

O plugin aplica alguns limites para proteger um deployment de instância única contra prompts descontrolados, clientes mal-comportados ou ciclos de delegação provocados por injeção de prompt. Alguns são estáticos (aplicados pelo schema da requisição) e outros são configuráveis via agents({ limits: { ... } }).

Limites estáticos (aplicados na análise das requisições POST /chat, POST /invocations e POST /responses):

CampoLimiteMotivo
chat.message64 000 caracteres~16 mil tokens; corpos maiores são quase certamente abuso.
string invocations.input64 000 caracteresMesmo motivo.
array invocations.input100 itensImpede que uma única requisição insira centenas de mensagens no armazenamento de threads.
string invocations.input[].content64 000 caracteresLimite por mensagem inserida.
array invocations.input[].content100 itensLimite por mensagem inserida.

Limites configuráveis (com os valores padrão indicados):

agents({
  limits: {
    maxConcurrentStreamsPerUser: 5,  // HTTP 429 + Retry-After quando excedido
    maxToolCalls: 50,                // aborta a run se o orçamento se esgotar
    maxSubAgentDepth: 3,             // rejeita recursão de subagentes acima desse limite
    toolCallTimeoutMs: 300_000,      // tempo limite por chamada de ferramenta (5 min; folga para SQL/Genie a frio)
  },
});

O orçamento de maxToolCalls é compartilhado entre o adaptador de nível superior e todos os subagentes aos quais ele delega, de modo que um fan-out injetado via prompt não consegue escapar aprofundando a cadeia de chamadas. maxConcurrentStreamsPerUser é por usuário, não global — um usuário que atinge seu limite não afeta os demais.

API de runtime

Após createApp, o plugin expõe:

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();             // varre o diretório novamente
appkit.agents.getThreads(userId);   // lista as threads do usuário

Avaliando agentes

O AppKit inclui um framework de avaliação para os agentes que você cria aqui. Você escreve as avaliações em TypeScript com defineEval, conduz o agente enviando mensagens a ele e faz asserções sobre a resposta e o uso de ferramentas com matchers determinísticos ou juízes LLM. As avaliações são executadas em um app em execução via HTTP (--url) e — com credenciais do Databricks e um experimento — reportadas ao MLflow como "Evaluation runs" nativas, com feedback por asserção e por juiz anexado ao trace de cada turno. A API de avaliação faz parte da superfície beta: importe-a de @databricks/appkit/beta.

As avaliações ficam junto a cada agente: server/agents/<agent-id>/evals/*.eval.ts. Cada arquivo exporta por padrão um defineEval({ test }). Por padrão, o agente testado é o do diretório <agent-id> pai; defina agent: para apontar para outro.

Uma primeira avaliação

// 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(); // gate: o turno foi concluído sem erro de agente/stream
  },
});

Inicie o app e, em seguida, execute as avaliações contra ele:

# a aplicação deve estar em execução e acessível em --url
appkit agent eval --url http://localhost:3000

# restringe a um agente/eval por substring e aponta para a raiz de um projeto
appkit agent eval query --root apps/dev-playground --url http://localhost:3000

O [filter] posicional corresponde às avaliações cujo <agent>/<id> contenha a substring (ou um id exato de agente). O comando localiza todos os arquivos *.eval.ts em server/agents/*/evals/, executa cada um deles na aplicação em execução e sai com código diferente de zero caso algum gate falhe.

Asserções

Toda asserção retorna um handle encadeável. As asserções são gates por padrão — uma falha reprova a avaliação (código de saída diferente de zero). Encadeie .soft() para rebaixá-la a uma métrica apenas monitorada, .gate() para promover novamente uma asserção soft, ou .atLeast(n) para definir o limiar de aprovação em uma asserção pontuada.

AsserçãoPassa quando
t.succeeded()O último turno foi concluído sem erro de agente/stream.
t.calledTool(name)O agente chamou name durante a run.
t.calledToolWith(name, expected)name foi chamado com argumentos que contêm expected em profundidade (cada chave de expected corresponde recursivamente; argumentos extras são ignorados).
t.check(value, matcher)value satisfaz o matcher — includes(substring), equals(expected) ou 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 monitorada, não reprova o gate
  },
});
// verificação parcial profunda dos argumentos da ferramenta
await t.send("What's the weather in Brooklyn?");
t.calledTool("get_weather");
t.calledToolWith("get_weather", { city: "Brooklyn" });

Chame t.skip("reason") para pular uma avaliação e leia t.reply, t.toolCalls e t.sessionId para inspecionar o último turno.

LLM-as-judge

t.judge.* pontua a última resposta com um juiz LLM (via autoevals apontado para um serving endpoint do Databricks). Cada juiz retorna uma asserção pontuada (0..1) que bloqueia por padrão — se não atingir o critério, a avaliação falha. Encadeie .atLeast(n) para definir o limite de aprovação, ou .soft() para apenas monitorar o resultado. Os juízes exigem um modelo julgador: passe --judge-model <endpoint> (ou defina APPKIT_JUDGE_MODEL) junto com a autenticação do Databricks; sem isso, t.judge.* lança um erro com uma mensagem clara.

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

  // closedQA não precisa de ground truth — avalia a resposta com base em uma pergunta.
  (await t.judge.closedQA(
    "Does the response describe weather conditions for Brooklyn?",
  )).atLeast(0.5);
}
  • t.judge.factuality(expected) — pontua a resposta em relação a uma resposta de referência esperada.
  • t.judge.closedQA(criteria) — pontua se a resposta responde à pergunta, conforme criteria.
  • t.judge.custom(spec) — um juiz baseado em modelo de prompt ({ name, promptTemplate, choiceScores }), equivalente em TS ao @scorer do MLflow.

Proteja as chamadas ao juiz com isJudgeConfigured() quando uma avaliação precisar percorrer o caminho de execução mesmo sem um modelo de juiz configurado:

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

Conversas

Cada t.send corresponde a um turno do usuário. A ordem em que você os encadeia define a thread:

// Turno único: uma só interação.
await t.send("Summarize Q3 revenue.");
t.succeeded();

// Múltiplos turnos: envios consecutivos partilham a mesma thread, pelo que o agente vê o histórico.
await t.send("Show me the orders table.");
await t.send("Now filter it to last week.");
t.succeeded();

// t.reset() descarta a conversa: o envio seguinte abre uma thread nova, sem
// histórico. Use-o para correr várias verificações de turno único independentes num só teste.
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

Adicione dataset: { table } para percorrer um dataset de avaliação gerenciado do Databricks — uma tabela catalog.schema.table do Unity Catalog com as colunas inputs/expectations. A avaliação é executada uma vez por linha; o runner vincula os inputs de cada linha a t.input e os expectations a t.expected. A leitura do dataset exige um cliente de workspace e um warehouse (--warehouse-id + autenticação).

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) {
    // Reenvia cada turno do usuário da linha em uma mesma thread, para que o agente veja
    // a conversa se acumulando. Uma linha com um único turno de usuário envia apenas uma 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);
      }
    }
  },
});

Os formatos das linhas correspondem à UI de datasets gerenciados do MLflow:

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

userTurns(t.input) extrai, em ordem, o conteúdo de todas as mensagens com role: "user" da entrada {messages:[...]} — uma linha pode conter uma conversa inteira com vários turnos, e repetir cada turno do usuário em uma mesma thread permite que o agente construa o histórico (turnos intercalados de assistant/system são ignorados; o agente gera os seus). Leia expectations.guidelines.value por conta própria; a UI encapsula o 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) : [];
}

Execute uma avaliação de dataset:

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

Executando avaliações e CI

appkit agent eval [filter] — executa avaliações de agentes (server/agents/<id>/evals/*.eval.ts) contra um app em execução.

FlagDescrição
[filter]Executa apenas as avaliações cujo <agent>/<id> contenha esta substring (ou um id exato de agente)
--url <url>URL base do app em execução (padrão http://localhost:3000)
--strictFalha também em asserções leves não atendidas
--root <dir>Raiz do projeto que contém server/agents/ (padrão: cwd)
--header <header...>Cabeçalho de requisição adicional no formato 'Key: value' (repetível)
--tag <tag...>Executa apenas as avaliações marcadas com uma destas tags (repetível)
--profile <name>Perfil da Databricks CLI para autenticação via OAuth (padrão: DATABRICKS_CONFIG_PROFILE)
--databricks-host <host>Host Databricks para gravar avaliações do MLflow (padrão: DATABRICKS_HOST)
--databricks-token <token>Token Databricks para gravar avaliações do MLflow (padrão: DATABRICKS_TOKEN)
--experiment <id>Id do experimento MLflow para a execução da avaliação (padrão: MLFLOW_EXPERIMENT_ID)
--warehouse-id <id>Id do SQL warehouse para leitura de datasets de avaliação gerenciados (padrão: DATABRICKS_WAREHOUSE_ID)
--judge-model <endpoint>Serving endpoint do Databricks a usar como juiz LLM para t.judge.* (padrão: APPKIT_JUDGE_MODEL)
--concurrency <n>Máximo de avaliações/linhas de dataset processados simultaneamente (padrão: 4)
--timeout <ms>Tempo limite padrão por eval em ms (um timeoutMs definido no eval tem precedência)
--retries <n>Reexecuta um eval até N vezes quando ele falha por erro de infraestrutura (turno/tempo limite); falhas de asserção não são repetidas
--min-pass-rate <rate>Aplica gate sobre a taxa agregada de aprovação (0..1) em vez de exigir que todos os avaliações passem; sai com 1 quando ficar abaixo
--reporter <format>Formato do relatório: text (console ao vivo), json (dashboards) ou junit (relatores de teste de CI)
--output <file>Grava o relatório json/junit neste arquivo em vez do stdout (ignorado para text)

Observações:

  • A autenticação é OAuth em primeiro lugar. --profile <name> gera um OAuth token a partir do seu perfil da Databricks CLI — sem necessidade de PAT. Um --databricks-host/--databricks-token explícito (ou as variáveis de ambiente DATABRICKS_*) prevalece sobre o perfil.
  • --retries só absorve instabilidade de infraestrutura. Uma nova tentativa só ocorre quando um eval lança erro ou excede o tempo limite (result.error definido); uma resposta incorreta é sinal real e nunca é repetida. Cada tentativa recebe um driver novo.
  • Gating. Por padrão, a execução termina com código diferente de zero se qualquer eval falhar. --min-pass-rate 0.9 alterna para o modo de limiar: a saída só é diferente de zero quando a taxa agregada de aprovação cai abaixo do limiar. O --strict ainda trata asserções leves não atendidas como falhas.
  • Relatórios de CI. --reporter junit --output results.xml grava um arquivo JUnit para relatores de teste de CI; --reporter json emite resultados legíveis por máquina. Nos relatores de máquina, as linhas destinadas a humanos vão para o stderr, de modo que o stdout permanece limpo para o relatório.
# CI: exigir taxa de aprovação de 90%, gerar JUnit, autenticar + reportar ao 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

Configuração por diretório

Coloque um evals.config.ts junto às avaliações de um agente para definir os padrões dos runs desse agente:

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

export default defineEvalConfig({
  maxConcurrency: 4,   // executa até 4 evals/linhas em paralelo
  timeoutMs: 30_000,   // tempo limite padrão por eval
});

Precedência: uma flag da CLI prevalece sobre o valor em evals.config.ts, que por sua vez prevalece sobre o padrão interno (concorrência 4, sem timeout). Um def.timeoutMs definido por eval sobrepõe-se a ambos para aquela eval.

Relatórios do MLflow

Quando --experiment <id> (ou MLFLOW_EXPERIMENT_ID) é definido junto com a autenticação do Databricks, o runner cria logo de início um Evaluation run nativo do MLflow. Conforme cada avaliação roda contra o app, o trace do turno é vinculado ao run, e cada asserção e pontuação de juiz é gravada de volta como feedback nesse trace:

  • Cada asserção determinística vira um Feedback de origem CODE com valor booleano.
  • Cada asserção de juiz vira um Feedback de origem LLM_JUDGE, com sua pontuação numérica de 0..1 e a respectiva justificativa.
  • Um Feedback geral de aprovação/reprovação appkit_eval é anexado a cada avaliação, e as métricas agregadas são registradas quando o run termina.

Omita --experiment (e MLFLOW_EXPERIMENT_ID) para executar as avaliações somente localmente, sem efeitos colaterais no MLflow — a CLI exibe um lembrete de que o evaluation run foi ignorado.

Esquema do frontmatter

ChaveTipoObservações
endpointstringNome do serving endpoint. Atalho para model.
modelstringEquivalente a endpoint; qualquer um dos dois funciona.
toolsarrayLista unificada de ferramentas. As entradas são plugin:<name> / plugin:<name>: [t1, t2] / plugin:<name>: { only, except, rename, prefix } para ferramentas de plugin, ou apenas <key> resolvido em agents({ tools: {...} }) para ferramentas de ambiente. Veja exemplos em "Nível 2: delimitar ferramentas no frontmatter", acima.
skillsarrayNomes das skills globais (pool compartilhado skills/ ou volume do catálogo) que ficarão visíveis para este agente. As skills específicas de cada agente, em <id>/skills/, estão sempre visíveis. Veja Skills.
defaultbooleanO primeiro id de agente (em ordem alfabética) com default: true passa a ser o agente padrão.
agentsarrayIds de subagentes (pastas irmãs) para os quais delegar; cada um vira uma ferramenta agent-<id>. É resolvido em relação aos demais agentes em markdown e em código.
maxStepsnumberSugestão de número máximo de passos para o adaptador.
maxTokensnumberSugestão de número máximo de tokens para o adaptador.
generationParamsobjectParâmetros de geração do adaptador (por exemplo, temperature, top_p) repassados quando o AppKit constrói o adaptador.
baseSystemPromptfalsestringSubstituição por agente. false desativa o prompt base do AppKit.
ephemeralbooleanSe true, a thread criada para uma requisição de chat a este agente é excluída do ThreadStore após o término do stream. Use em agentes stateless de uso único (por exemplo, autocompletar), para que o histórico não se acumule nem contamine chamadas futuras. O padrão é false.

Chaves desconhecidas são registradas em log e ignoradas. YAML inválido e referências ausentes a plugin/ferramenta geram erro na inicialização.

Databricks Developer Hub

Pronto para lançar seu próximo aplicativo baseado em agentes em minutos?

Ler a documentação