Dar ações ao agente
Uma tool é uma ação que o agente pode executar. Você descreve o que ela faz e quais argumentos aceita; o framework cuida de oferecê-la ao modelo, entender que ele quer usá-la, validar o que ele mandou e chamar seu código.
import { Tool, input } from "@thenajs/core";
import { readFile } from "node:fs/promises";
import { z } from "zod";
@Tool({
name: "ler_arquivo",
description: "Lê o conteúdo de um arquivo do projeto.",
schema: z.object({
caminho: z.string().describe("caminho relativo, ex.: src/main.ts"),
}),
})
export class LerArquivoTool {
async execute(@input() { caminho }: { caminho: string }) {
return readFile(caminho, "utf8");
}
}E declare no agente:
@Agent({ provider: MeuProvider, tools: [LerArquivoTool], prompt: "./a.agent.md" })
export class MeuAgente {}A descrição é para o modelo
description é o que o modelo lê para decidir quando usar a ferramenta. É prompt, não comentário de código.
// vago demais — o modelo chuta
description: "Operações de arquivo."
// diz quando usar
description: "Lê o conteúdo de um arquivo do projeto. Use antes de responder sobre código."Vale o mesmo para os campos: .describe() no Zod entra no schema que o modelo vê.
O schema é a fronteira de confiança
Os argumentos vêm de um modelo de linguagem — ou seja, não são confiáveis. O framework valida contra o seu schema antes de chamar execute, então dentro dele você já trabalha com dados verificados.
Mantenha estrito. Um z.string() genérico deixa passar coisa que um z.enum(["leitura", "escrita"]) recusaria de graça.
O que o execute recebe
Cada parâmetro declara o que quer, então a ordem não importa:
import { Tool, input, context, state } from "@thenajs/core";
async execute(
@input() { caminho }: { caminho: string }, // os argumentos validados
@context() ctx: AgentContext, // o contexto da execução
@state() s: RevisaoState, // o estado do workflow
) {
s.arquivosLidos.push(caminho);
return readFile(caminho, "utf8");
}O @input() sozinho cobre a maioria dos casos. Os outros dois são para quando a ferramenta precisa de algo do fluxo.
Peça só o que usar
Uma tool que lê o contexto deixa de ser testável isoladamente — você passa a precisar de uma execução para exercitá-la. O caso que mais justifica é repassar informação a um sub-workflow, que roda isolado e começaria sem saber o que foi pedido.
Um execute sem decorator nenhum continua funcionando e recebe os argumentos — é o formato de antes da 0.5.0, mantido por compatibilidade.
Sinalizar que deu errado
Devolver uma string é o caminho normal. Para marcar que a observação é um erro — sem derrubar a execução — devolva um objeto:
async execute(@input() { caminho }: { caminho: string }) {
try {
return await readFile(caminho, "utf8");
} catch (err) {
return { content: `Não consegui ler: ${(err as Error).message}`, isError: true };
}
}O modelo recebe o texto e pode tentar outra coisa. E o report marca esse passo como erro, o que deixa "taxa de erro de ferramenta" ser uma contagem, não uma busca no texto.
Uma tool que lança
Por padrão, um throw dentro do execute derruba a execução. Se preferir que vire observação para o modelo tentar de novo, ligue no config: toolErrors: "observe".
Limitar o tamanho da saída
Uma ferramenta que devolve 200 KB entope o contexto do modelo e encarece tudo. Corte antes de devolver:
const LIMITE = 4000;
async execute(@input() { caminho }: { caminho: string }) {
const conteudo = await readFile(caminho, "utf8");
return conteudo.length <= LIMITE
? conteudo
: `${conteudo.slice(0, LIMITE)}\n… [truncado]`;
}Uma tool que dispara um workflow
O construtor pode receber o WorkflowRuntime injetado — é assim que uma ação dispara um processo inteiro:
import { Tool, WorkflowRuntime } from "@thenajs/core";
import { DeployWorkflow } from "../workflows/deploy.workflow.js";
@Tool({
name: "deploy",
description: "Executa o processo de deploy e devolve o resultado.",
schema: z.object({ repositorio: z.string() }),
})
export class DeployTool {
constructor(private readonly runtime: WorkflowRuntime) {}
async execute(@input() args: { repositorio: string }) {
return this.runtime.run(DeployWorkflow, { input: args });
}
}Esse é o padrão de sub-agente isolado, e vale a pena conhecer: o workflow filho roda com histórico próprio e devolve uma string só.
Tools prontas
npm install @thenajs/toolsimport { ShellTool } from "@thenajs/tools";
@Agent({ provider: MeuProvider, tools: [ShellTool], prompt: "./a.agent.md" })
export class MeuAgente {}ShellTool executa comandos de verdade
Dê a ela apenas para agentes cujo prompt e cujo ambiente você controla. Para restringir, um hook beforeTool pode barrar comandos — veja Hooks.
Referência
Assinaturas e tipos exatos em @Tool.
