O que sao Skills
Capacidades especializadas empacotadas como modulos reutilizaveis
Uma skill e uma capacidade especializada empacotada como um modulo reutilizavel que um agente pode invocar. Diferente de uma funcao avulsa, uma skill encapsula tudo que e necessario para executar uma tarefa completa: descricao semantica para o LLM entender quando usa-la, schema de entrada/saida, logica de execucao e tratamento de erros.
Pense em skills como plugins inteligentes. Um agente sem skills e um cerebro sem bracos. As skills dao ao agente a capacidade de agir no mundo: enviar emails, consultar bancos de dados, gerar imagens, executar codigo, chamar APIs. O LLM decide qual skill usar com base no contexto da conversa, nao com base em regras hardcoded.
Skills vs Tools vs Plugins
| Conceito | Definicao | Exemplo |
|---|---|---|
| Tool | Uma funcao atomica com interface JSON Schema. Faz uma coisa so. | search_database(query) |
| Skill | Pacote de tools + logica + prompts que implementam uma capacidade completa. | Skill "Gmail": list_emails + send_email + search_inbox + compose_draft |
| Plugin | Extensao instalavel com UI, configuracao e lifecycle hooks. Mais amplo que skill. | Plugin Shopify: catalog + checkout + analytics + admin panel |
Anatomia de uma Skill
Toda skill tem quatro componentes fundamentais que o agente precisa para usa-la corretamente:
Texto semantico que explica ao LLM quando e por que usar a skill. E o componente mais importante: se a descricao for vaga, o LLM nao vai saber quando invocar.
JSON Schema que define exatamente quais parametros a skill aceita: tipos, formatos, quais sao obrigatorios, valores default. O LLM gera os argumentos com base nisso.
O codigo que roda quando a skill e invocada. Pode chamar APIs, executar queries, processar arquivos. Recebe os parametros validados e retorna o resultado.
Estrutura do retorno: string, JSON, array. O LLM recebe esse output como contexto para formular a resposta ao usuario. Outputs estruturados facilitam encadeamento.
Exemplo: Skill Completa
// skills/weather.ts
export const weatherSkill = {
name: "get_weather",
description: "Consulta previsao do tempo para uma cidade. " +
"Use quando o usuario perguntar sobre clima, " +
"temperatura ou previsao meteorologica.",
parameters: {
type: "object",
properties: {
city: {
type: "string",
description: "Nome da cidade (ex: 'Sao Paulo')"
},
days: {
type: "number",
description: "Dias de previsao (1-7)",
default: 1
}
},
required: ["city"]
},
execute: async ({ city, days = 1 }) => {
const res = await fetch(
`https://api.weather.com/forecast?city=${city}&days=${days}`
);
const data = await res.json();
return {
city: data.city,
temperature: data.temp,
condition: data.condition,
forecast: data.daily.slice(0, days)
};
}
};
Essa skill tem os 4 componentes: descricao clara, schema tipado com parametros obrigatorios e opcionais, logica de execucao com chamada de API, e retorno estruturado em JSON.
💡 Dica
A descricao da skill e o componente mais critico. Gaste tempo refinando ela. Inclua quando usar, quando nao usar, e exemplos de inputs. Um LLM com uma descricao ruim vai chamar a skill errada ou nos momentos errados, por melhor que seja a implementacao.
Fazer
- ●Descrever com clareza quando a skill deve ser usada
- ●Manter skills focadas: uma capacidade por skill
- ●Retornar dados estruturados (JSON) em vez de texto livre
- ●Incluir exemplos de uso na descricao para guiar o LLM
Evitar
- ●Skills genericas demais: "faz coisas no sistema"
- ●Misturar responsabilidades em uma unica skill
- ●Ignorar tratamento de erros na execution logic
- ●Descricoes ambiguas que confundem o LLM sobre quando usar
JSON Schema para Tool Definitions
Definindo interfaces de tools com tipos, validacoes e descricoes semanticas
JSON Schema e o padrao usado por todos os grandes provedores de LLM (OpenAI, Anthropic, Google) para definir a interface de tools. E uma especificacao que descreve a estrutura de um objeto JSON: quais campos existem, seus tipos, quais sao obrigatorios, valores permitidos e descricoes de cada campo.
O LLM le o schema e usa essa informacao para decidir: (1) se deve chamar a tool, (2) quais argumentos gerar, e (3) como formatar a chamada. Um schema bem escrito e a diferenca entre um agente que funciona e um que alucina parametros.
Estrutura Basica de um JSON Schema
{
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Termo de busca para filtrar clientes"
},
"status": {
"type": "string",
"enum": ["active", "inactive", "trial"],
"description": "Filtrar por status da conta"
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 10,
"description": "Numero maximo de resultados"
},
"include_deleted": {
"type": "boolean",
"default": false,
"description": "Incluir clientes deletados"
}
},
"required": ["query"]
}
O campo required diz ao LLM quais parametros sao obrigatorios. Parametros opcionais com default sao preenchidos automaticamente se omitidos.
Tipos Suportados
Primitivos
string- textonumber- decimalinteger- inteiroboolean- true/false
Compostos
object- chave:valorarray- lista de itemsnull- valor nulo
Restricoes
enum- valores fixosminimum/maximum- rangepattern- regexformat- email, uri, date
Como o LLM Le o Schema
O processo que o LLM segue internamente quando recebe tools com JSON Schema:
Le a descricao da tool
Avalia se a descricao corresponde a intencao do usuario. "Buscar clientes" match com "quero ver meus clientes ativos".
Le as descricoes dos parametros
Entende o que cada parametro faz e extrai valores do contexto da conversa para preenche-los.
Respeita restricoes
Usa enum para limitar valores, required para garantir campos obrigatorios, default para omitir opcionais.
Gera a chamada estruturada
Emite um JSON com name da tool e arguments preenchidos, pronto para execucao pelo runtime.
💡 Dica
Use enum sempre que possivel. Em vez de aceitar qualquer string para "status", defina ["active", "inactive", "trial"]. Isso reduz drasticamente as chances de o LLM inventar valores invalidos. Quanto mais restritivo o schema, menos o LLM erra.
Fazer
- ●Descricao detalhada em cada campo do schema
- ●Usar enum para valores predefinidos
- ●Definir defaults para parametros opcionais
- ●Validar inputs no servidor antes de executar
Evitar
- ●Schemas sem description nos campos
- ●Aceitar
anycomo tipo de parametro - ●Schemas profundamente aninhados (confundem LLMs)
- ●Confiar que o LLM vai respeitar o schema 100% das vezes
Function Calling: Como Funciona
O fluxo completo de 5 etapas, do pedido do usuario ao resultado final
Function calling e o mecanismo que permite a um LLM decidir, durante a geracao de resposta, que precisa executar uma funcao externa. Em vez de gerar texto, o modelo gera uma chamada estruturada com nome da funcao e argumentos. O runtime executa a funcao, retorna o resultado ao modelo, e o modelo usa esse resultado para compor a resposta final.
Esse mecanismo e o que transforma um chatbot em um agente funcional. Sem function calling, o LLM so pode gerar texto. Com function calling, pode enviar emails, consultar bancos, executar codigo, chamar APIs e qualquer outra acao que voce expor como tool.
📊 Adocao de Function Calling
Lancou function calling em jun/2023. Hoje e o padrao de facto para todos os provedores.
Tool use nativo no Claude. Mesmo conceito, API ligeiramente diferente.
Modelos modernos podem chamar multiplas tools em paralelo numa unica resposta.
Ollama, LM Studio e vLLM suportam function calling com modelos open-source.
O Fluxo de 5 Etapas
Usuario faz um pedido que requer acao externa
LLM analisa tools disponiveis e escolhe qual chamar
LLM gera argumentos conforme JSON Schema da tool
Runtime executa funcao e coleta resultado
LLM recebe resultado e gera resposta final ao usuario
Exemplo Completo: OpenAI API
// 1. Definir tools no request
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "user", content: "Qual o clima em SP hoje?" }
],
tools: [{
type: "function",
function: {
name: "get_weather",
description: "Consulta clima atual de uma cidade",
parameters: {
type: "object",
properties: {
city: { type: "string" }
},
required: ["city"]
}
}
}]
});
// 2. LLM retorna tool_call (nao texto!)
// response.choices[0].message.tool_calls = [{
// id: "call_abc123",
// function: { name: "get_weather", arguments: '{"city":"Sao Paulo"}' }
// }]
// 3. Executar funcao e retornar resultado
const weatherData = await getWeather("Sao Paulo");
// 4. Enviar resultado de volta ao LLM
const finalResponse = await openai.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "user", content: "Qual o clima em SP hoje?" },
response.choices[0].message, // inclui tool_calls
{ role: "tool", tool_call_id: "call_abc123",
content: JSON.stringify(weatherData) }
]
});
// 5. LLM gera: "Em Sao Paulo esta 24°C com ceu parcialmente nublado."
Parallel Tool Calls
Modelos modernos podem chamar multiplas tools simultaneamente quando faz sentido:
// Usuario: "Qual o clima em SP e no Rio?"
// LLM retorna 2 tool_calls em paralelo:
tool_calls: [
{ id: "call_1", function: { name: "get_weather",
arguments: '{"city":"Sao Paulo"}' }},
{ id: "call_2", function: { name: "get_weather",
arguments: '{"city":"Rio de Janeiro"}' }}
]
// Ambas executam ao mesmo tempo, resultados voltam juntos
Isso reduz latencia drasticamente em cenarios onde o agente precisa coletar dados de multiplas fontes antes de responder.
💡 Dica
Sempre valide os argumentos gerados pelo LLM antes de executar a funcao. O modelo pode gerar tipos errados, valores fora do range ou campos inesperados. Use uma biblioteca de validacao como Zod (TypeScript) ou Pydantic (Python) para garantir type safety na fronteira LLM-codigo.
Fazer
- ●Validar argumentos do LLM antes de executar a funcao
- ●Retornar erros estruturados quando a tool falha
- ●Implementar timeout para cada tool call
- ●Aproveitar parallel tool calls quando possivel
Evitar
- ●Executar tool calls sem validacao de input
- ●Silenciar erros de execucao (o LLM precisa saber que falhou)
- ●Loops infinitos de tool calls sem limite de iteracoes
- ●Expor funcoes destrutivas (delete, drop) sem confirmacao humana
Structured Outputs
Forcando o LLM a retornar JSON tipado, validavel e consistente
Por padrao, LLMs retornam texto livre. Isso e otimo para conversas, mas pessimo para integracoes programaticas. Structured outputs forcam o modelo a retornar dados em um formato JSON especifico, definido por um schema. O resultado e tipado, validavel e pode ser consumido diretamente por codigo sem parsing fragil de texto.
Existem tres abordagens principais: JSON mode (garante que a saida e JSON valido), response_format com schema (garante que segue uma estrutura especifica), e Zod/Pydantic schemas (validacao no lado do cliente com tipos nativos da linguagem).
Sem vs Com Structured Output
Sem structured output:
"O produto 'Plano Pro' custa R$99,90/mes, esta disponivel, e tem 14 dias de trial gratuito. E o mais popular entre startups."
Precisa de regex ou outro LLM call para extrair dados. Fragil, quebra com variacoes.
Com structured output:
{
"name": "Plano Pro",
"price": 99.90,
"currency": "BRL",
"billing": "monthly",
"available": true,
"trial_days": 14
}
Pronto para usar. Tipado. Validavel. Sem ambiguidade.
Abordagem 1: JSON Mode (OpenAI)
const response = await openai.chat.completions.create({
model: "gpt-4o",
response_format: { type: "json_object" },
messages: [{
role: "system",
content: "Retorne sempre um JSON com campos: name, price, available"
}, {
role: "user",
content: "Quero info sobre o Plano Pro"
}]
});
const data = JSON.parse(response.choices[0].message.content);
// Garante JSON valido, mas nao garante o schema exato
Abordagem 2: Schema Estrito (OpenAI Structured Outputs)
const response = await openai.chat.completions.create({
model: "gpt-4o",
response_format: {
type: "json_schema",
json_schema: {
name: "product_info",
strict: true,
schema: {
type: "object",
properties: {
name: { type: "string" },
price: { type: "number" },
available: { type: "boolean" },
trial_days: { type: "integer" }
},
required: ["name", "price", "available", "trial_days"],
additionalProperties: false
}
}
},
messages: [{ role: "user", content: "Info do Plano Pro" }]
});
// Garante que o JSON segue EXATAMENTE o schema definido
Abordagem 3: Validacao com Zod (TypeScript)
import { z } from "zod";
// Definir schema com Zod
const ProductSchema = z.object({
name: z.string(),
price: z.number().positive(),
available: z.boolean(),
trial_days: z.number().int().min(0).max(90)
});
type Product = z.infer<typeof ProductSchema>;
// Validar resposta do LLM
const raw = JSON.parse(llmResponse);
const product = ProductSchema.parse(raw);
// Se invalido, Zod lanca erro com detalhes do que falhou
// Se valido, 'product' tem type safety completa
Zod funciona como uma segunda camada de validacao: mesmo que o LLM retorne um JSON, voce valida os tipos e restricoes antes de usar no seu codigo. Isso protege contra edge cases que nem o schema estrito pega.
💡 Dica
Use a abordagem em camadas: response_format no request para garantir JSON do LLM + Zod/Pydantic no codigo para validar tipos. Se o LLM retornar algo inesperado, voce captura antes de quebrar a aplicacao. Em producao, nunca confie apenas no LLM para garantir o formato.
Fazer
- ●Usar response_format + validacao no cliente (dupla camada)
- ●Definir additionalProperties: false para evitar campos extras
- ●Incluir exemplos no system prompt para guiar o formato
- ●Tratar erro de validacao com fallback (retry ou default)
Evitar
- ●Parsear JSON do LLM com JSON.parse() sem try/catch
- ●Confiar apenas no prompt para garantir formato ("retorne JSON")
- ●Schemas complexos demais (10+ campos aninhados confundem LLMs)
- ●Usar regex para extrair dados de texto livre do LLM
Criando Skills Reutilizaveis
Estrutura de arquivos, registro, error handling e testes independentes
Skills bem construidas seguem um padrao consistente que facilita reutilizacao, teste e manutencao. Uma skill reutilizavel pode ser plugada em qualquer agente sem modificacao, testada isoladamente sem depender do LLM, e versionada como qualquer outro modulo de codigo.
O conceito de skill marketplace ja e realidade: Claude Code tem skills em ~/.claude/skills/, GPTs tem Actions, e o ecossistema MCP tem milhares de servers reutilizaveis. Aprender a criar skills e como aprender a criar componentes React: uma vez que domina o padrao, escala infinitamente.
Estrutura de Arquivos Recomendada
skills/
weather/
index.ts # Exporta a skill registrada
schema.ts # JSON Schema dos parametros
handler.ts # Logica de execucao
handler.test.ts # Testes unitarios do handler
types.ts # TypeScript types (input/output)
README.md # Documentacao da skill
database/
index.ts
schema.ts
handler.ts
handler.test.ts
types.ts
index.ts # Registry: exporta todas as skills
register.ts # Funcao de registro de skills no agente
Cada skill vive em seu proprio diretorio com schema, handler e testes separados. O arquivo index.ts raiz funciona como registry central.
Padrao de Registro
// skills/register.ts
import { Tool } from "./types";
class SkillRegistry {
private skills: Map<string, Tool> = new Map();
register(skill: Tool) {
if (this.skills.has(skill.name)) {
throw new Error(`Skill "${skill.name}" ja registrada`);
}
this.skills.set(skill.name, skill);
}
get(name: string): Tool | undefined {
return this.skills.get(name);
}
// Retorna schemas para enviar ao LLM
getSchemas() {
return Array.from(this.skills.values()).map(s => ({
type: "function",
function: {
name: s.name,
description: s.description,
parameters: s.parameters
}
}));
}
// Executa uma skill por nome
async execute(name: string, args: unknown) {
const skill = this.skills.get(name);
if (!skill) throw new Error(`Skill "${name}" nao encontrada`);
try {
return await skill.execute(args);
} catch (error) {
return { error: true, message: error.message };
}
}
}
export const registry = new SkillRegistry();
Error Handling Robusto
Skills que falham silenciosamente sao piores que skills que nao existem. O LLM precisa receber o erro para decidir o que fazer a seguir:
// skills/database/handler.ts
export async function queryDatabase(args: QueryInput) {
try {
const results = await db.query(args.sql, args.params);
return {
success: true,
rows: results.rows,
count: results.rowCount
};
} catch (error) {
// Retornar erro estruturado, nao lancar excecao
return {
success: false,
error: error.message,
code: error.code,
hint: error.code === "42P01"
? "Tabela nao existe. Verifique o nome."
: "Erro na query SQL. Verifique a sintaxe."
};
}
}
O campo hint ajuda o LLM a se corrigir. Se a tabela nao existe, o LLM pode listar as tabelas disponiveis e tentar de novo.
Testando Skills Independentemente
// skills/weather/handler.test.ts
import { describe, it, expect } from "vitest";
import { getWeather } from "./handler";
describe("Weather Skill", () => {
it("retorna dados para cidade valida", async () => {
const result = await getWeather({ city: "Sao Paulo" });
expect(result.success).toBe(true);
expect(result.temperature).toBeDefined();
expect(typeof result.temperature).toBe("number");
});
it("retorna erro para cidade invalida", async () => {
const result = await getWeather({ city: "XYZ123" });
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
it("respeita limite de dias", async () => {
const result = await getWeather({ city: "SP", days: 3 });
expect(result.forecast.length).toBeLessThanOrEqual(3);
});
});
Teste o handler diretamente, sem passar pelo LLM. Isso garante que a logica funciona antes de conectar ao agente. Se o handler funciona isolado e o LLM chama com os parametros certos, o sistema inteiro funciona.
💡 Dica
Pense em skills como microsservicos: interface clara, responsabilidade unica, testavel isoladamente. Um skill marketplace interno (mesmo que seja um diretorio compartilhado) evita retrabalho entre projetos. Se voce criou uma skill de envio de email, ela deve funcionar em qualquer agente sem modificacao.
Fazer
- ●Separar schema, handler e testes em arquivos distintos
- ●Retornar erros estruturados com hints para o LLM
- ●Testar handlers isoladamente sem o LLM
- ●Versionaria skills e manter backward compatibility
Evitar
- ●Handlers com side effects nao-obvios (enviar email silenciosamente)
- ●Hardcodar credenciais dentro da skill
- ●Skills que dependem de estado global compartilhado
- ●Pular testes "porque a skill e simples"
Exercicio: 3 Skills para seu SaaS
Data retrieval, computation e external API call registradas e testadas
Hora de construir. Neste exercicio voce vai criar 3 skills funcionais para o seu SaaS, cada uma cobrindo um tipo diferente de capacidade: busca de dados, computacao e chamada de API externa. As 3 skills devem ser registradas no agente, ter JSON Schema completo e funcionar via function calling.
O objetivo e sair com skills prontas para producao: com error handling, testes unitarios e descricoes claras para o LLM. Se funcionar isoladamente e o LLM souber quando chamar, voce dominou o padrao.
As 3 Skills
Data Retrieval: Buscar Dados
Busca registros no seu banco de dados (clientes, pedidos, metricas). Aceita filtros como query, status, date range. Retorna resultados paginados em JSON.
search_customers({ query: "premium", status: "active", limit: 10 })
Computation: Calcular Metricas
Calcula metricas de negocio: MRR, churn rate, LTV, growth rate. Recebe periodo e tipo de metrica. Retorna valor calculado com breakdown.
calculate_metrics({ metric: "mrr", period: "last_30_days" })
External API: Notificar/Integrar
Chama uma API externa: enviar notificacao (Slack/email), criar registro em CRM, ou consultar servico terceiro. Autentica, chama e parseia resposta.
send_notification({ channel: "slack", message: "Novo cliente premium!" })
Template de Skill Completa
// skills/search-customers/index.ts
import { z } from "zod";
// 1. Schema de Input
const InputSchema = z.object({
query: z.string().describe("Termo de busca por nome ou email"),
status: z.enum(["active", "inactive", "trial"])
.optional()
.describe("Filtrar por status"),
limit: z.number().min(1).max(100).default(10)
.describe("Maximo de resultados")
});
// 2. Schema de Output
const OutputSchema = z.object({
success: z.boolean(),
customers: z.array(z.object({
id: z.string(),
name: z.string(),
email: z.string(),
plan: z.string(),
status: z.string()
})),
total: z.number()
});
// 3. Tool Definition (para enviar ao LLM)
export const searchCustomersTool = {
type: "function" as const,
function: {
name: "search_customers",
description: "Busca clientes no banco de dados por nome, " +
"email ou ID. Use quando o usuario perguntar sobre " +
"um cliente especifico ou pedir uma lista filtrada.",
parameters: {
type: "object",
properties: {
query: { type: "string",
description: "Termo de busca" },
status: { type: "string",
enum: ["active", "inactive", "trial"] },
limit: { type: "integer", default: 10,
minimum: 1, maximum: 100 }
},
required: ["query"]
}
}
};
// 4. Handler
export async function searchCustomers(raw: unknown) {
const args = InputSchema.parse(raw);
try {
const results = await db.customers.findMany({
where: {
OR: [
{ name: { contains: args.query } },
{ email: { contains: args.query } }
],
...(args.status && { status: args.status })
},
take: args.limit
});
return OutputSchema.parse({
success: true,
customers: results,
total: results.length
});
} catch (error) {
return { success: false, error: error.message,
customers: [], total: 0 };
}
}
Checklist de Validacao
💡 Dica
Use Vibe Coding para criar as skills: descreva o que cada uma faz, o schema desejado e os edge cases, e deixe o LLM gerar a implementacao. Depois revise, teste e ajuste. Esse fluxo e exatamente como voce vai criar skills em producao: especificacao humana + implementacao assistida por IA.
Fazer
- ●Criar uma skill por vez, testar, depois criar a proxima
- ●Testar o handler isolado antes de conectar ao LLM
- ●Testar function calling com prompts reais de usuario
- ●Adaptar os exemplos para o dominio do seu SaaS
Evitar
- ●Criar as 3 skills de uma vez sem testar individualmente
- ●Copiar os templates sem adaptar para seu caso de uso
- ●Pular error handling "para depois"
- ●Skills que fazem coisas demais (mantenha responsabilidade unica)
📋 Resumo do Modulo 4.4
Skills sao capacidades completas empacotadas como modulos: descricao, schema, logica e output. Diferente de tools atomicas ou plugins com UI.
JSON Schema define a interface de cada tool: tipos, restricoes, descricoes. O LLM le o schema para decidir quando e como chamar.
Function calling segue 5 etapas: request, decide, generate args, execute, respond. Suporta chamadas paralelas.
Structured outputs forcam JSON tipado: JSON mode, schema estrito ou Zod/Pydantic. Sempre valide em duas camadas.
Skills reutilizaveis seguem padrao consistente: schema/handler/test separados, registry central, error handling com hints.
Na pratica: 3 skills (data, compute, API) registradas, testadas e funcionando via function calling no seu agente.