Blogue

Agent Skills na API Claude: como usar skills sem Claude Code

Markdown

Como usar a skill pptx na API Claude: container.skills, execução de código, verificação da resposta e transferência da apresentação pela Files API.

Ler o artigo completo

Ponto principal

As Agent Skills prontas a utilizar para documentos podem ser usadas diretamente através da API Claude: especifica a skill em container.skills e ativa code_execution. Para o primeiro pedido, utiliza a combinação compatível de modelo e ferramenta do exemplo oficial. Transfere o ficheiro gerado separadamente através do respetivo file_id; o exemplo utiliza a mesma chave de API para o pedido e para a transferência.

Podes encontrar instruções adicionais na documentação da Ailoot.

O que vais precisar

Este guia destina-se a chamadas diretas à API a partir de um programa, em vez da instalação de uma skill no Claude Code. Vais precisar de uma chave da API Claude, de acesso ao modelo selecionado e do Python 3. O código abaixo utiliza a biblioteca padrão do Python e HTTP, pelo que não depende da versão do SDK que tens instalada. A chave é passada através da variável de ambiente ANTHROPIC_API_KEY; não a incluas no texto do programa nem numa conversa.

A API disponibiliza skills Anthropic prontas a utilizar: pptx para apresentações, xlsx para folhas de cálculo, docx para documentos Word e pdf para ficheiros PDF. São ligadas através de type: "anthropic". Este guia utiliza uma skill pronta a utilizar, sem carregar uma skill personalizada.

O guia de início rápido oficial, consultado durante a preparação deste material, utiliza a combinação claude-opus-5-5 e code_execution_20260521. É essa a combinação utilizada abaixo. Este exemplo foi verificado face à documentação; não é um relato de uma chamada paga à API.

Que cabeçalhos beta são necessários

O exemplo segue o contrato HTTP atual do guia de início rápido: os cabeçalhos são x-api-key, anthropic-version: 2023-06-01 e content-type: application/json; não envia anthropic-beta. Não combines um modelo, um método de chamada e um formato de resposta de gerações diferentes da documentação.

A documentação da Files API também indica que a API já não está em beta. O cabeçalho antigo files-api-2025-04-14 mantém os formatos de resposta antigos; ao remover o cabeçalho, passam a ser utilizados os formatos atuais. É por isso que um exemplo antigo com client.beta.files não pode simplesmente ser combinado com um novo.

Aqui não estamos a migrar um serviço existente do contrato beta antigo. Se já tens um serviço desse tipo, verifica primeiro os respetivos cabeçalhos e formatos de resposta. A palavra beta no nome de um método do SDK não é suficiente por si só: a documentação da Files API explica separadamente o comportamento das diferentes versões do SDK.

Criar uma apresentação e transferir o ficheiro

O exemplo envia uma tarefa para criar cinco diapositivos sobre energias renováveis, guarda a resposta completa em skill-response.json e, em seguida, extrai os IDs dos ficheiros gerados. Não obtém um caminho para guardar da resposta do modelo: os ficheiros são guardados numa nova pasta local chamada skill-output. Se essa pasta já existir, o programa para para não substituir o resultado anterior. A pasta é criada antes da chamada à API, pelo que também permanece após uma chamada falhada. Inspecione e conserve essa tentativa antes de voltar a agir; não a elimine só para conseguir repetir o programa.

import json
import os
from pathlib import Path
from urllib.error import HTTPError
from urllib.request import Request, urlopen

API = "https://api.anthropic.com/v1"
KEY = os.environ["ANTHROPIC_API_KEY"]
HEADERS = {
    "x-api-key": KEY,
    "anthropic-version": "2023-06-01",
    "content-type": "application/json",
}

def request(path, payload=None, binary=False):
    data = None if payload is None else json.dumps(payload).encode("utf-8")
    req = Request(API + path, data=data, headers=HEADERS)
    try:
        with urlopen(req, timeout=600) as result:
            body = result.read()
    except HTTPError as error:
        try:
            details = json.loads(error.read().decode("utf-8"))
        except (ValueError, UnicodeError):
            details = {}
        info = details.get("error", {})
        request_id = error.headers.get("request-id") or details.get("request_id")
        diagnostic = {
            "http_status": error.code,
            "request_id": request_id,
            "error_type": info.get("type"),
            "message": str(info.get("message", "No JSON error message")).replace(KEY, "[REDACTED]")[:1000],
        }
        (out / "skill-error.json").write_text(
            json.dumps(diagnostic, ensure_ascii=False, indent=2), encoding="utf-8"
        )
        raise RuntimeError(
            f"Claude API HTTP {error.code}; request_id={request_id}; inspect skill-error.json"
        ) from None
    return body if binary else json.loads(body)

out = Path("skill-output")
out.mkdir(exist_ok=False)
response = request("/messages", {
    "model": "claude-opus-5-5",
    "max_tokens": 16000,
    "container": {
        "skills": [{"type": "anthropic", "skill_id": "pptx", "version": "latest"}]
    },
    "messages": [{
        "role": "user",
        "content": "Cria uma apresentação sobre energias renováveis com 5 diapositivos no idioma que prefiro.",
    }],
    "tools": [{"type": "code_execution_20260521", "name": "code_execution"}],
})
(out / "skill-response.json").write_text(
    json.dumps(response, ensure_ascii=False, indent=2), encoding="utf-8"
)
print("stop_reason:", response.get("stop_reason"))

file_ids = []
for block in response["content"]:
    if block["type"] == "bash_code_execution_tool_result":
        result = block["content"]
        if result["type"] == "bash_code_execution_result":
            for item in result["content"]:
                file_id = item.get("file_id")
                if file_id and file_id not in file_ids:
                    file_ids.append(file_id)
if not file_ids:
    raise RuntimeError("Não foi gerado nenhum file_id; inspeciona skill-response.json")

for index, file_id in enumerate(file_ids, start=1):
    from urllib.parse import quote
    file_path = "/files/" + quote(file_id, safe="")
    metadata = request(file_path)
    if metadata.get("downloadable") is not True:
        raise RuntimeError("Este ficheiro não pode ser transferido através da Files API")
    target = out / f"result-{index}.pptx"
    target.write_bytes(request(file_path + "/content", binary=True))
    print("Guardado:", target)

Os nomes result-1.pptx e seguintes foram escolhidos para esta tarefa de apresentação. Uma extensão de ficheiro não comprova o que o ficheiro contém: abre o documento transferido e verifica o formato, o número de diapositivos e o texto. Não consideres apenas uma resposta HTTP 200 ou a presença de um file_id como um resultado bem-sucedido.

O programa apresenta stop_reason, o motivo pelo qual a resposta terminou, e guarda a resposta completa para inspeção. Não continua a conversa nem repete automaticamente o pedido pago. Ter um ficheiro transferido não significa que a tarefa esteja concluída: inspeciona a resposta guardada e o próprio documento antes de decidires se é necessário outro pedido.

Se uma tentativa falhar, consulte skill-error.json, se existir. Regista o estado HTTP, o ID do pedido quando fornecido e uma mensagem de erro limitada do fornecedor, com a chave de API configurada removida. Antes de partilhar a mensagem, verifique se contém outros dados privados. Compare erros de autenticação, permissões ou modelo, limites de faturação e formato do pedido com a referência oficial de erros; nem todas as falhas exigem uma nova geração paga.

Se skill-response.json já contiver valores file_id gerados e apenas uma transferência tiver falhado, conserve a resposta e diagnostique a etapa dos metadados ou da transferência com o mesmo acesso autorizado ao espaço de trabalho. Não volte a executar o bloco de geração apenas para transferir um resultado existente. Se o resultado do pedido for desconhecido, preserve o ID do pedido e esclareça o seu estado antes de enviar outra geração. Só depois de decidir que é necessária uma nova geração, altere a linha da pasta local para um caminho ainda não utilizado, por exemplo out = Path("skill-output-attempt-2"), e mantenha exist_ok=False. Uma pasta nova impede a substituição de ficheiros; não impede uma cobrança duplicada pela API.

Que permissões de ficheiro são necessárias

A Files API associa os ficheiros a um espaço de trabalho, e não a um utilizador individual da tua aplicação ou a uma conversa. Segundo a documentação, qualquer chave de API com acesso a esse espaço de trabalho pode aceder aos ficheiros aí carregados. O exemplo utiliza a mesma chave para transferir e gerar. Nunca aceites um file_id de utilizadores finais ou de outras fontes não fidedignas. Guarda estes IDs no servidor e mantém, na tua própria aplicação, o mapeamento entre os utilizadores e os respetivos ficheiros.

Verifica os metadados do ficheiro separadamente: têm de conter downloadable: true. A documentação permite transferir ficheiros gerados por skills ou pela ferramenta de execução de código; os ficheiros que carregas têm downloadable: false e uma tentativa de transferência devolve 400. O acesso ao espaço de trabalho e a possibilidade de transferir um determinado ficheiro são requisitos distintos.

Para uma aplicação multiutilizador, a documentação da Files API recomenda um espaço de trabalho separado para cada cliente como limite de isolamento dos ficheiros. Esta recomendação diz respeito especificamente aos ficheiros; não a alargues a todas as entidades e permissões sem consultares a API relevante.

Skills prontas a utilizar e skills personalizadas

As skills prontas a utilizar têm os IDs pptx, xlsx, docx e pdf. Na referência da lista de skills, a origem custom é descrita como privada para o espaço de trabalho, enquanto anthropic é partilhada e só de leitura.

version: "latest" no primeiro exemplo significa a versão publicada mais recente da skill. Para uma integração reproduzível, tem em conta que esta seleção pode mudar; para passar a uma versão fixa, é necessário escolher uma versão existente da skill que utilizas, em vez de inventar um ID a partir de um exemplo.

Limitações e próximo passo

O exemplo abrange apenas a criação de uma apresentação com uma skill pronta a utilizar e a transferência do ficheiro. O carregamento de skills personalizadas, a sua eliminação e o acesso a serviços de terceiros não foram testados aqui. Essas tarefas exigem uma verificação separada da API relevante.

Se a tua tarefa envolver ficheiros na interface do Claude, existe um guia separado sobre como carregar um ficheiro para o Claude. Para chamadas diretas, consulta como calcular a utilização de tokens da API. Para trabalhar num agente de terminal, lê como escolher skills do Claude Code: são ligadas de outra forma.

Para a primeira verificação, basta uma skill pptx pronta a utilizar: inspeciona a resposta, transfere um ficheiro que seja permitido transferir e abre-o. Acrescenta skills personalizadas e a separação de acesso entre clientes depois dessa verificação.

Quando estiveres pronto, fica a conhecer a Ailoot.

Mais detalhes

Perguntas frequentes

Porque não basta um file_id para considerar a apresentação concluída?

O ID indica que foi devolvido um ficheiro, não que seja uma apresentação utilizável. Verifique nos metadados downloadable: true, transfira-o com acesso ao espaço de trabalho e abra-o para verificar o formato, o número de diapositivos e o texto.

Posso transferir qualquer ficheiro carregado para a API Files do Claude?

Não. O guia distingue ficheiros gerados com downloadable: true de ficheiros carregados com downloadable: false. O acesso ao espaço de trabalho e a permissão para transferir um ficheiro específico são verificações separadas; não aceite IDs arbitrários de ficheiros dos utilizadores finais.

Todos os artigos