1. Erros e idempotência no ChatGPT App
Na Web clássica, muitos ainda vivem na lógica “o usuário clicou no botão → uma requisição HTTP → uma resposta”. No mundo de LLM isso já não é assim há muito tempo. O modelo pode decidir chamar sua ferramenta várias vezes, pode regerar a resposta após o usuário clicar em Regenerate, pode pedir esclarecimentos, pode esbarrar em um erro de rede no caminho. No fim, a mesma ferramenta pode ser chamada duas ou três vezes com argumentos muito parecidos.
Ao mesmo tempo, qualquer erro passa a ter dois consumidores. De um lado — o modelo, que precisa de uma explicação legível por máquina sobre o que deu errado, para poder corrigir os argumentos e tentar novamente. Do outro — o UI do usuário (o widget e o próprio chat), onde é preciso mostrar uma mensagem compreensível e sugerir próximos passos, e não “Error: 500 (see logs)”.
Mais um ponto importante: a arquitetura clássica raramente supõe que alguém vá apertar “repita a resposta” em massa, aumentando assim o número de chamadas repetidas (retries). No ChatGPT esse cenário é a norma. Além disso, a própria plataforma pode fazer uma nova chamada em caso de problemas temporários de rede. Portanto, a idempotência nesse ecossistema não é um extra, e sim um requisito básico, principalmente para ferramentas que fazem algo “de verdade” — criam pedidos, cobram dinheiro, enviam e‑mails etc.
Esta aula é justamente sobre como não permitir que uma chamada malsucedida de ferramenta (tool call) estrague a experiência do usuário — e o seu ambiente de produção.
Insight
O ChatGPT não passa argumentos para suas funções; ele adivinha o conjunto de argumentos de acordo com seu schema. Ele olha para o JSON Schema, o contexto do diálogo e seleciona valores estatisticamente — e erra com bastante frequência. Erros como “tipo incorreto”, “campo obrigatório ausente”, “parâmetros contraditórios” são parte normal da vida de tool-calls, não caso fortuito. Segundo dados públicos e telemetria, esses equívocos facilmente ocupam até ~30% das chamadas para schemas complexos.
Para o modelo isso não é problema: ele interpreta sua resposta como um sinal de “argumentos inválidos” e simplesmente tenta de novo, talvez duas ou três vezes seguidas, alterando levemente a entrada. Para você isso significa outra coisa: cada ferramenta deve ser projetada como se quase certamente fosse chamada várias vezes com parâmetros muito parecidos.
É por isso que a idempotência é tão importante. O ChatGPT vai tentar repetidamente adivinhar com quais parâmetros deve chamar suas funções. 2–3 tentativas por chamada — é normal.
2. Configuração segura do widget: text/html+skybridge e _meta
Antes de irmos para questões puramente de servidor (erros, retries, idempotência), vamos fechar um ponto específico do Apps SDK sobre segurança do UI: como fazer seu widget renderizar no chat com segurança, e não como “uma página assustadora da internet”.
registerResource e o MIME type text/html+skybridge
Do ponto de vista do ChatGPT, seu widget é um recurso HTML especial que cai no sandbox do cliente ChatGPT, e não diretamente no navegador do usuário. Para a plataforma entender que isso é um widget e não apenas HTML, usa-se o MIME type text/html+skybridge.
No nível do MCP/servidor você registra o recurso com algo do tipo (pseudo-TS):
// em algum lugar na configuração do servidor MCP
registerResource({
name: "giftgenius-widget",
path: "/widget",
mimeType: "text/html+skybridge", // importante!
});
Este mimeType é um sinal para o cliente do ChatGPT: “isto não é apenas HTML, e sim um template de componente para um widget embutido que deve ser executado em um ambiente isolado”. Se você informar um text/html comum, a plataforma pode mostrar HTML cru ou até recusar a renderização.
_meta e controle de segurança: CSP, domínio e borda
Em seguida entram os metadados passados junto com a resposta da ferramenta ou do recurso — _meta. Com eles você controla quais recursos externos o widget pode carregar, como ele se comporta visualmente e até como o modelo vai descrevê-lo.
Um exemplo típico de estrutura:
const toolResult = {
content: "<!-- HTML do widget -->",
_meta: {
"openai/widgetCSP": "default-src 'self'; img-src https://cdn.example.com",
"openai/widgetDomain": "https://chatgpt.com",
"openai/widgetPrefersBorder": true,
"openai/widgetDescription": "O GiftGenius mostra recomendações de presentes em cartões."
}
};
Vamos analisar os campos chave.
- openai/widgetCSP define a Content Security Policy do widget. É seu pequeno firewall para o “navegador dentro do ChatGPT”: você lista explicitamente de onde é permitido carregar scripts, estilos, imagens, fazer XHR etc. A plataforma espera uma política rígida, sem wildcard *; você precisa indicar explicitamente os domínios usados (chat, sua API, CDN).
- openai/widgetDomain define o origin no qual seu widget vai operar. Normalmente é o domínio do ChatGPT; você não o substitui pelo seu site, apenas informa como isso deve se apresentar no ambiente isolado.
- openai/widgetPrefersBorder — flag puramente visual: desenhar ou não uma borda ao redor do widget. Para o GiftGenius, faz sentido manter a borda para separar visualmente o bloco de recomendações das mensagens comuns do chat.
- openai/widgetDescription — descrição em texto para o modelo. Em vez de “inventar” uma explicação sozinha, o modelo pode usar essa string ao contar ao usuário que interface acabou de abrir. Isso reduz o risco de comentários estranhos ou excessivos do modelo.
Conclusão prática: ao configurar com cuidado o mimeType e o _meta, você obtém um UI seguro e isolado que não acessa o que não deve e se comporta de forma previsível tanto para o usuário quanto para a plataforma. Com a parte de segurança de frontend resolvida — o widget vive no sandbox e só acessa o que você permitiu — vamos focar no lado do servidor: tipos de erro, como descrevê-los e como tornar as ferramentas idempotentes.
Insight: cache do widget
O ChatGPT faz cache do HTML do widget no momento do registro do aplicativo. O HTML do widget do ChatGPT não é um “frontend vivo”, mas sim um artefato de build congelado. Ao publicar o aplicativo (Store ou Dev Mode) a plataforma lê o recurso HTML (text/html+skybridge) e depois sempre usa exatamente essa versão. Qualquer alteração — até uma linha de texto ou um espaço em um card — significa, na prática, um novo release.
Daí a conclusão: mudanças na estrutura do HTML, slots, atributos data-* e no contrato structuredContent → DOM não são “um hotfix rápido”, e sim uma migração de frontend completa. Se hoje você renderiza uma lista de items[] e amanhã muda para results[], o widget antigo não vai saber disso: ele continuará recebendo o JSON anterior e funcionará incorretamente.
3. Tipos de erros ao operar ferramentas
Agora vamos ao que interessa: que tipos de erro uma ferramenta pode ter e como eles diferem do ponto de vista de UX e backend. É útil pensar em quatro camadas de erros.
Erros de validação da entrada
O nível mais básico — quando os argumentos de entrada não correspondem ao contrato.
Exemplos para nosso app didático GiftGenius e sua ferramenta suggest_gifts (seleção de presentes por interesses e orçamento):
- idade menor que zero ou maior que 120;
- orçamento negativo;
- o campo obrigatório relationship_type está ausente;
- budget_min > budget_max.
Aqui também entra um JSON banal que não corresponde ao schema. Idealmente, o Apps SDK e o JSON Schema filtram chamadas “muito ruins” antes mesmo do seu código, mas a validação de negócio (como a relação budget_min/budget_max) ainda precisa ser feita por você.
Erros de regra de negócio
Aqui a entrada parece correta, mas pelas regras do domínio você não consegue fornecer um resultado adequado.
Tramas típicas:
- pelos interesses e orçamento informados, nenhum presente foi encontrado;
- o usuário excedeu o limite diário de seleções;
- o produto que o modelo pede para comprar não está mais à venda.
Isso não é “o servidor quebrou”, e sim situações normais e esperadas que devem ser apresentadas ao usuário e ao modelo de forma adequada, e não como 500 Internal Server Error.
Erros de infraestrutura externa
Esta camada já é sobre o “inferno técnico”: banco indisponível, API externa com timeout, uma exceção não tratada no seu código.
Por exemplo:
- a consulta ao catálogo de presentes retorna 503 ou não responde;
- o MongoDB decide, de repente, pausar;
- no código de filtragem de presentes você divide por zero.
Do ponto de vista de UX, isso geralmente é motivo para dizer: “Serviço temporariamente indisponível, tente mais tarde”, e às vezes tentar um retry discreto. Mas é importante não sumir em silêncio nem mostrar um stack trace cru ao usuário.
Erros de plataforma/rede
Por fim, há uma camada que pode acontecer completamente fora do seu código: a tool-call não chegou, a conexão caiu no meio da resposta, o streaming foi interrompido. Isso acontece mais do que você imagina. Por exemplo, se você usar um túnel gratuito, durante horários de pico a velocidade pode cair tanto que as tool calls do ChatGPT estouram por timeout.
Você não consegue controlar isso completamente, mas pode projetar ferramentas e o widget para que chamadas repetidas e interrupções não transformem o sistema em caos. É por isso que falamos de idempotência e tratamento cuidadoso de erros, e não apenas “try/catch e pronto”.
4. Como descrever e retornar erros: para o modelo e para o UI
Uma mudança importante de mentalidade: seu erro não é apenas o que você registrou em console.error. Ele é parte do contrato da ferramenta com o qual o modelo e a interface vão trabalhar.
Estrutura de erro
Geralmente é útil seguir uma estrutura simples:
type ToolError = {
code: string; // "VALIDATION_ERROR", "NO_RESULTS", "UPSTREAM_TIMEOUT"
message: string; // legível para humanos ou compacto para o modelo
retryable: boolean; // faz sentido tentar novamente
};
E o resultado da ferramenta pode ser embrulhado em uma união discriminada:
type SuggestGiftsResult =
| { ok: true; gifts: GiftCard[] }
| { ok: false; error: ToolError };
No protocolo MCP existe ainda um flag separado “isto é um erro”, mas internamente é útil manter um formato próprio, para que o UI e o modelo possam interpretar da mesma forma o que aconteceu.
Estratégia “fail gracefully”
Nem toda situação desagradável precisa ser tratada como um erro “duro”. Às vezes é muito mais útil retornar um resultado vazio, mas sem erro, apenas com uma explicação.
Por exemplo, se nenhum presente foi encontrado, faz sentido retornar ok: true, um array vazio gifts: [] e algum campo noResultsReason para o UI e o modelo, em vez de "NO_RESULTS" como erro. Assim, o modelo pode continuar o diálogo: “Não encontrei nada nesse orçamento, você quer aumentar o orçamento ou refinar os interesses?”.
Já se a API externa saiu completamente do ar, isso é mais para ok: false com code: "UPSTREAM_UNAVAILABLE" e retryable: true, para que o modelo tenha chance de tentar novamente mais tarde ou com outros parâmetros.
Relembrando, na seção 3 temos quatro camadas de erros. Erros de validação normalmente vão como ok: false e retryable: false — o modelo não deve repetir a mesma chamada com os mesmos argumentos. Situações de negócio como “nada encontrado” são, com mais frequência, retornadas como ok: true, com resultado vazio e explicação. Falhas de infraestrutura de serviços externos — como ok: false com retryable: true, para que o modelo possa tentar novamente com segurança. E erros de plataforma/rede podem ocorrer antes ou depois do seu código e, na prática, muitas vezes parecem uma chamada repetida da ferramenta — por isso a idempotência cuidadosa é tão importante, assunto de que falaremos a seguir.
Não vaze detalhes internos
No código do servidor é fácil cair na tentação de simplesmente propagar error.toString() na resposta. Para ferramentas de LLM isso não é uma boa ideia: você vai encher o diálogo de lixo e potencialmente expor detalhes sensíveis (URLs de serviços internos, stack traces, nomes de tabelas). A recomendação é capturar exceções e transformá-las em códigos de erro compactos e mensagens cuidadosas.
Exemplo de wrapper mínimo:
try {
const gifts = await loadGiftsFromCatalog(input);
return { ok: true, gifts };
} catch (err) {
console.error("suggest_gifts failed", err);
return {
ok: false,
error: {
code: "UPSTREAM_ERROR",
message: "Catalog service is unavailable",
retryable: true
}
};
}
O modelo vê um sinal limpo, o UI — um texto claro, e os detalhes ficam nos logs.
Exibição de erro no widget
Do ponto de vista do widget em React, a tarefa é trivial: verificar ok, e, se for false, mostrar uma mensagem amigável e, se possível, um caminho de continuação.
function GiftResults({ result }: { result: SuggestGiftsResult }) {
if (!result.ok) {
return (
<div>
<p>Não foi possível selecionar presentes: {result.error.message}</p>
{result.error.retryable && <p>Tente alterar os parâmetros ou repetir a solicitação.</p>}
</div>
);
}
if (result.gifts.length === 0) {
return <p>Nenhum presente encontrado com essas condições. Tente alterar o orçamento ou os interesses.</p>;
}
return <GiftCardsList gifts={result.gifts} />;
}
Este é exatamente o caso em que uma mensagem simples e honesta torna o UX muito melhor do que “algo deu errado”.
Já combinamos que parte dos erros pode ser marcada honestamente como retryable: true e oferecer ao usuário “tentar novamente”. Assim que o sistema passa a ter retries (explícitos no UI ou discretos do lado da plataforma), surge a próxima pergunta: o que acontece se a mesma ferramenta for chamada duas vezes com os mesmos dados? Isso já é história de idempotência.
5. Idempotência: proteção contra “mais uma chamada igual”
Agora a parte divertida. Formalmente, idempotência é a propriedade de uma operação na qual uma chamada repetida com os mesmos dados de entrada não muda o estado do sistema nem o resultado. No sentido estrito, isso fala tanto da ausência de efeitos colaterais repetidos quanto de resposta igual. Na prática de ChatGPT Apps, nos interessa primeiro o seguinte: que chamadas repetidas não estraguem os dados nem criem novas entidades, mesmo que a resposta em si possa variar um pouco.
No contexto de ChatGPT Apps, idempotência é a proteção contra tudo o que acontece com retries, Regenerate e a lógica imprevisível da LLM.
Onde a idempotência é especialmente importante
Ferramentas apenas de leitura geralmente são seguras por padrão: por mais que você chame suggest_gifts com os mesmos parâmetros, você só receberá mais uma lista de presentes. Mesmo que ela mude um pouco, isso não altera o estado do sistema nem cria efeitos colaterais.
Críticas são as ferramentas que modificam o estado de sistemas externos:
- criação de pedido (create_order);
- processamento de pagamento (charge_card, submit_payment);
- envio de e‑mails e notificações (send_email, send_sms);
- criação de entidades com efeitos colaterais (por exemplo, reservas).
Se tal ferramenta for chamada duas vezes seguidas com argumentos praticamente idênticos, você pode acabar com pedidos duplicados, cobranças em dobro e outras “alegrias” contábeis.
Padrão idempotency_key
Abordagem clássica: adicionar à ferramenta um parâmetro extra idempotency_key — um identificador de string da operação. Se uma requisição com essa chave já foi processada com sucesso, o servidor não executa a ação novamente e retorna o resultado salvo.
Exemplo de schema expandido para a ferramenta hipotética create_checkout_session no GiftGenius:
const CreateCheckoutSchema = {
type: "object",
properties: {
giftId: {
type: "string",
description: "ID do presente selecionado"
},
idempotency_key: {
type: "string",
description: "Chave única da operação para proteger contra duplicidade"
}
},
required: ["giftId", "idempotency_key"]
} as const;
No servidor, o handler faz algo como:
async function createCheckoutSession(input: CreateCheckoutInput) {
const existing = await db.checkoutSessions.findOne({ idempotencyKey: input.idempotency_key });
if (existing) {
return existing; // retorna o resultado anterior
}
const session = await paymentProvider.createSession({ giftId: input.giftId });
await db.checkoutSessions.insert({ idempotencyKey: input.idempotency_key, session });
return session;
}
Se o modelo, por algum motivo, chamar a ferramenta pela segunda vez com o mesmo idempotency_key, o usuário não receberá uma segunda cobrança, apenas verá o mesmo checkout.
Separação entre prepare e commit
Para ações especialmente sensíveis (pagamentos, mudanças irreversíveis), muitas vezes se usa uma abordagem em duas fases: uma ferramenta para preparação (prepare_*) e outra para commit (commit_*).
Por exemplo:
- prepare_order — verifica a disponibilidade do produto, calcula o preço, retorna um “rascunho de pedido”;
- commit_order — com o ID do rascunho, cria o pedido real e inicia o pagamento.
Esse design traz alguns bônus. Primeiro, é possível tornar a primeira etapa totalmente idempotente: repetir prepare_order com os mesmos parâmetros retornará o mesmo rascunho. Segundo, você pode permitir commit_order apenas após a confirmação explícita do usuário, o que é conveniente tanto para UX quanto para segurança.
6. Design seguro de ferramentas
Idempotência é necessária, mas não é o único ingrediente de segurança. Muita coisa é definida pelo próprio design do conjunto de ferramentas que você entrega ao modelo.
Princípio do menor privilégio
A ideia é simples: cada ferramenta deve fazer exatamente o que é necessário para o cenário — e nada mais. Não crie uma função do_anything_with_user_account que:
- consegue ler, atualizar e deletar tudo;
- recebe uma string operation e um JSON payload “na sorte”.
É melhor ter tools separadas e bem descritas:
- get_user_profile;
- update_user_preferences;
- create_order;
- cancel_order.
A mesma lógica para o GiftGenius: suggest_gifts apenas sugere opções; create_checkout_session não sabe nada sobre como cancelar pedidos ou alterar o e‑mail do usuário.
Separação entre ferramentas “read” e “write”
Um bom padrão é separar claramente as ferramentas que apenas leem dados daquelas que alteram algo. Consultar o catálogo de presentes (search_products, suggest_gifts) é seguro por si só, mesmo se o modelo abusar delas. Já create_order ou charge_payment exigem mais cuidado.
Nas descrições dessas ferramentas, vale escrever explicitamente o que elas fazem e em que contexto podem ser chamadas. Por exemplo:
{
"name": "create_checkout_session",
"description": "Cria uma nova sessão de pagamento para um presente. Chame SOMENTE depois que o usuário tiver confirmado explicitamente sua escolha.",
"parameters": { /* ... */ }
}
Isso não é proteção 100% (a LLM ainda pode errar), mas pelo menos você dá a ela um sinal claro sobre os riscos.
Human-in-the-loop e confirmações
Para ações realmente “perigosas”, é útil construir um fluxo com confirmação. Por exemplo, o modelo:
- Primeiro chama uma ferramenta que prepara os dados para a compra e os retorna em um formato conveniente para o UI (nome do presente, preço, endereço de entrega).
- A plataforma mostra ao usuário um widget com o botão “Confirmar compra”.
- Somente após o clique no botão, a ferramenta de commit é chamada, que realiza o pagamento real.
Assim você impede que o modelo faça um pedido “na surdina” sem a participação do usuário, mesmo que ele decida que isso é uma ótima ideia.
Semântica de risco em descrições e anotações
Em algumas versões da plataforma aparecem anotações especiais como destructiveHint, que sinalizam que a ferramenta pode executar ações irreversíveis. Mesmo que esses campos não existam ou ainda não sejam estáveis, você pode incluir essa semântica diretamente na description e nos nomes dos parâmetros.
Por exemplo, em vez de:
{
"name": "delete_user_data",
"description": "Exclui dados do usuário."
}
fazer:
{
"name": "request_user_data_deletion",
"description": "Marca a conta do usuário para exclusão de seus dados pessoais conforme a política do serviço. Use SOMENTE depois que o usuário solicitar explicitamente a exclusão."
}
E, de quebra, construir um UX humano de confirmação ao redor disso.
7. Pequena melhoria prática no GiftGenius
Vamos conectar tudo isso ao nosso app didático GiftGenius — um App para seleção de presentes. Suponha que adicionemos ao GiftGenius mais uma ferramenta — create_checkout_session, para que o usuário possa não só escolher um presente, mas também ir ao checkout.
Do ponto de vista do JSON Schema e segurança, faremos o seguinte.
Primeiro, adicionamos idempotency_key e uma descrição cuidadosa:
const CreateCheckoutTool = {
name: "create_checkout_session",
description:
"Cria uma sessão de pagamento para um único presente selecionado. " +
"Chame somente depois que o usuário confirmar que deseja comprar este presente.",
parameters: {
type: "object",
properties: {
gift_id: {
type: "string",
description: "Identificador do presente a partir do resultado de suggest_gifts."
},
idempotency_key: {
type: "string",
description: "Chave única da operação. Use a mesma chave em chamadas repetidas."
}
},
required: ["gift_id", "idempotency_key"]
}
} as const;
Segundo, no servidor, implementamos um handler idempotente:
async function handleCreateCheckout(input: CreateCheckoutInput) {
const existing = await db.checkout.findOne({ idempotencyKey: input.idempotency_key });
if (existing) {
return { ok: true, checkout: existing };
}
const checkout = await payments.createSession({ giftId: input.gift_id });
await db.checkout.insert({ idempotencyKey: input.idempotency_key, ...checkout });
return { ok: true, checkout };
}
Terceiro, tratamos os erros:
try {
return await handleCreateCheckout(input);
} catch (err) {
console.error("create_checkout_session failed", err);
return {
ok: false,
error: {
code: "PAYMENT_PROVIDER_ERROR",
message: "Não foi possível criar a sessão de pagamento. Tente novamente mais tarde.",
retryable: true
}
};
}
E no widget mostramos um estado de erro compreensível e, possivelmente, um botão “Tentar novamente” no nível de UI, que inicia um novo diálogo com o modelo.
Assim, passo a passo, nosso simpático projeto didático deixa de ser “um brinquedo de demo” e aos poucos vira algo que, teoricamente, pode ir para produção.
8. Erros típicos ao lidar com erros e idempotência de ferramentas
Erro nº 1: Erro = simplesmente throw e 500.
Se, a cada falha, sua tool simplesmente lançar uma exceção que vira “algo deu errado”, o modelo e o UI ficam sem informação. O modelo não entende se deve repetir a chamada com outros argumentos, e o usuário não sabe o que fazer em seguida. É muito melhor retornar um erro estruturado com código, mensagem curta e o indicador retryable, e registrar os detalhes do lado do servidor.
Erro nº 2: Não diferenciar tipos de erro.
Misturar erros de validação, de negócio e de infraestrutura em um caldeirão só é uma má ideia. No fim, a situação “nada encontrado” parece, para o modelo e o usuário, igual a “o banco de dados caiu”. Isso estraga o UX e impede o modelo de reagir adequadamente: em vez de sugerir ajustar a consulta, ele entra no modo “foi mal, o serviço está quebrado”. Isso dói especialmente quando você mistura, por exemplo, erros de negócio e de infraestrutura da seção 3.
Erro nº 3: Operações não idempotentes em um mundo de retries.
Projetar a ferramenta create_order como se ela sempre fosse chamada exatamente uma vez é caminho direto para pedidos duplicados, especialmente quando o usuário clica bastante em Regenerate ou a conexão cai no meio do caminho. Se a ferramenta tem efeitos colaterais, quase sempre vale adicionar idempotency_key e armazenar resultados para que a chamada repetida não crie novas entidades.
Erro nº 4: Uma ferramenta “universal” monstruosa.
Às vezes os desenvolvedores tentam criar uma super ferramenta com o parâmetro action, que faz tudo: busca, cria, altera e exclui. Para a LLM, isso é quase garantia de comportamento imprevisível: é mais difícil para o modelo aprender quando chamar o quê, e as consequências de erros ficam muito mais pesadas. O correto é dividir em ferramentas pequenas, bem descritas, preferencialmente read‑only, e, separadamente, ferramentas de mutação cuidadosamente elaboradas com confirmações.
Erro nº 5: Vazamento de detalhes internos nas respostas.
Jogar stack trace cru ou textos completos de exceções no modelo e no UI é preguiça de engenharia. É ruim para o usuário, pode expor a estrutura interna do sistema e não ajuda o modelo a se corrigir. Intercepte exceções, faça o mapeamento para códigos compactos e mensagens simples, e deixe os detalhes nos logs e na monitoração.
Erro nº 6: Não conectar os erros ao UX do widget.
Muitas vezes o backend retorna códigos de erro com cuidado, mas o widget no UI fica preso em um spinner eterno ou mostra um bloco vazio. O usuário vê “nada aconteceu”, o modelo vê que a tool-call terminou e continua o diálogo como se nada tivesse acontecido. É muito melhor pensar em estados separados de error e empty, mostrar mensagens claras ao usuário e, se possível, sugerir ações (ajustar parâmetros, tentar mais tarde).
Erro nº 7: Ignorar o princípio do menor privilégio.
Mesmo que você tenha idempotência e bom tratamento de erros, se descrever uma ferramenta como execute_sql_anywhere, que pode fazer tudo, o risco continua enorme. A LLM pode chamá-la no contexto errado ou com parâmetros equivocados. Cada ferramenta deve ser o mais enxuta possível e fazer exatamente uma ação clara — especialmente quando falamos de dinheiro ou dados pessoais do usuário.
GO TO FULL VERSION