Cap 26: API Fundamentals para Arquitectos

Por: Artiko
claude-apiextended-thinkingprompt-cachingtool-choice

Este tutorial cubre los conceptos de la API de Claude que el examen Claude Certified Architect – Foundations evalúa directamente y que los tutoriales anteriores no han cubierto en detalle.

1. Extended Thinking — razonamiento interno

Extended Thinking permite que Claude genere tokens de razonamiento internos antes de producir su respuesta. El modelo “piensa en voz alta” de forma privada y luego responde.

Thinking tokens vs Output tokens

TipoVisible al usuarioSe cobra como
thinkingNo (interno)Output tokens
text (output)Output tokens

Los thinking tokens se cobran igual que los output tokens. Activar extended thinking sin necesidad sube el costo sin beneficio.

Árbol de decisión para activar Extended Thinking

flowchart TD
    A[¿La tarea requiere razonamiento?] -->|No| B[No usar ET]
    A -->|Sí| C{¿Problema complejo?}
    C -->|"Multi-step lógico\nAnálisis de trade-offs\nDebugging difícil"| D[Activar ET\nbudget: 8000-16000]
    C -->|"Extracción simple\nClassificación\nStructured output básico"| B
    D --> E{¿Costo importa mucho?}
    E -->|Sí| F[budget_tokens: 1000-4000]
    E -->|No| G[budget_tokens: 8000-16000]

Cuándo usar y cuándo no

Usar: análisis de trade-offs arquitectónicos, debugging de comportamiento inesperado, planificación de sistemas complejos.

No usar: extracción de campos de un JSON, clasificación de texto, generación de código repetitivo, cualquier tarea donde la respuesta directa es suficiente.

Implementación

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

// Caso de uso: analizar trade-offs de diseño
const response = await client.messages.create({
  model: "claude-opus-4-5",
  max_tokens: 16000,
  thinking: {
    type: "enabled",
    budget_tokens: 10000, // mínimo 1000, recomendado 8000-16000
  },
  messages: [
    {
      role: "user",
      content:
        "Analiza los trade-offs entre PostgreSQL y DynamoDB para un sistema " +
        "de e-commerce con 10M usuarios, picos de 50k req/s en Black Friday " +
        "y necesidad de transacciones ACID en pedidos.",
    },
  ],
});

// El response.content tiene bloques de tipo "thinking" y "text"
for (const block of response.content) {
  if (block.type === "thinking") {
    // Interno al modelo, no mostrar al usuario
    console.log("[Razonamiento interno, no expuesto]");
  } else if (block.type === "text") {
    console.log(block.text); // Esta es la respuesta final
  }
}

Nota del examen: el contenido de los bloques thinking no debe exponerse al usuario final. Son trazas internas del modelo.


2. Prompt Caching — reducir latencia y costo

Prompt caching permite que el servidor reutilice el procesamiento de un prefix del prompt entre llamadas consecutivas.

Cómo funciona

sequenceDiagram
    participant App
    participant API as Anthropic API
    participant Cache

    App->>API: Request 1 (system prompt largo + query)
    Note over API: Procesa todo el prefix
    API->>Cache: Guarda prefix en caché (TTL: ~5 min)
    API-->>App: Response 1 (latencia normal)

    App->>API: Request 2 (mismo system prompt + nueva query)
    API->>Cache: ¿Existe el prefix?
    Cache-->>API: HIT — recupera prefix procesado
    Note over API: Solo procesa la nueva query
    API-->>App: Response 2 (~90% menos latencia en prefix)

Marcado con cache_control

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

// CLAUDE.md del proyecto (3000+ tokens, se repite en cada llamada de CI)
const projectContext = await Bun.file("CLAUDE.md").text();

const response = await client.messages.create({
  model: "claude-sonnet-4-5",
  max_tokens: 2048,
  system: [
    {
      type: "text",
      text: projectContext,
      cache_control: { type: "ephemeral" }, // cachear este prefix
    },
  ],
  messages: [
    {
      role: "user",
      content: "¿Qué comando uso para iniciar el servidor de desarrollo?",
    },
  ],
});

Dónde colocar cache_control

Siempre en el último bloque estático antes de la parte dinámica. Si el caché se marca en el medio de una cadena que cambia, se invalida.

Posición correctaResultado
System prompt completo (estático)Cachea todo el system prompt
Documento grande en primer user messageCachea system + documento
Historial de conversación hasta N-1Cachea contexto acumulado

Advertencia: si el texto que marcas con cache_control cambia entre llamadas (aunque sea un carácter), el caché se invalida y vuelve a procesar completo.

Casos de uso ideales

Impacto en métricas


3. Temperature y parámetros de sampling

Tabla de referencia rápida

TareaTemperature recomendada
Extracción de datos, classification0
Generación de código00.2
CI/CD, structured output0
Respuestas de asistente de propósito general1 (default)
Brainstorming, diversidad de opciones0.71
Creative writing11.3

Temperature 0 para determinismo

const response = await client.messages.create({
  model: "claude-sonnet-4-5",
  max_tokens: 512,
  temperature: 0, // determinístico — ideal para CI/CD
  tools: [extractionTool],
  tool_choice: { type: "tool", name: "extract_invoice" },
  messages: [{ role: "user", content: invoiceText }],
});

¿Temperature 0 garantiza 100% determinismo?

Casi siempre, pero no al 100%. El modelo puede producir outputs ligeramente distintos debido a:

Para pipelines críticos usa temperature: 0 y diseña el sistema para tolerar variación mínima.

top_p y top_k

En la práctica, para la API de Claude basta con ajustar temperature. Rara vez necesitas cambiar top_p.


4. tool_choice — control completo sobre ejecución de tools

Esta sección es crítica para el examen. El parámetro tool_choice controla si Claude puede, debe o no debe usar tools.

Los 4 modos

flowchart TD
    Q[¿Necesito que Claude use tools?] --> A{¿Cuánta certeza?}
    A -->|"No sé si la query\nnecesita tools"| M1["auto\nClaude decide"]
    A -->|"Debe usar alguna tool\npero no sé cuál"| M2["any\nAl menos una tool"]
    A -->|"Debe usar\nUNA tool específica"| M3["tool + name\nExactamente esa tool"]
    A -->|"No debe usar tools\nen este turno"| M4["none\nSolo texto"]

Tabla comparativa

ModoClaude puede responder sin toolClaude elige la toolError si no hay toolsUso típico
autoNoConversación general
anyNoSí (cualquiera)Forzar extracción
tool + nameNoNo (fija)Sí si no existeSchema específico
noneSí (siempre texto)N/ANoAnálisis sin acción

Implementaciones

// AUTO: Claude decide si usar tools o responder en texto
const autoResponse = await client.messages.create({
  model: "claude-sonnet-4-5",
  max_tokens: 1024,
  tool_choice: { type: "auto" }, // default, puede omitirse
  tools: [searchTool, calculatorTool],
  messages: [{ role: "user", content: "¿Cuánto es 2+2?" }],
  // Claude puede responder "4" sin usar ninguna tool
});

// ANY: Claude DEBE usar al menos una tool
const anyResponse = await client.messages.create({
  model: "claude-sonnet-4-5",
  max_tokens: 1024,
  tool_choice: { type: "any" },
  tools: [extractContactTool, extractDateTool],
  messages: [{ role: "user", content: emailText }],
  // Claude elegirá al menos una de las tools disponibles
});

// TOOL específica: Claude DEBE usar exactamente esta tool
const toolResponse = await client.messages.create({
  model: "claude-sonnet-4-5",
  max_tokens: 1024,
  tool_choice: { type: "tool", name: "extract_invoice_fields" },
  tools: [extractInvoiceFieldsTool],
  messages: [{ role: "user", content: invoiceText }],
  // Claude usará extract_invoice_fields o error
});

// NONE: Deshabilita todos los tools aunque estén definidos
const noneResponse = await client.messages.create({
  model: "claude-sonnet-4-5",
  max_tokens: 1024,
  tool_choice: { type: "none" },
  tools: [deleteDatabaseTool], // definidas pero inaccesibles
  messages: [{ role: "user", content: "Analiza los riesgos de este plan" }],
  // Claude solo puede responder en texto — modo análisis puro
});

Errores comunes


5. Múltiples tool calls en un mismo turno

Claude puede emitir varios bloques tool_use en un solo turno. El desarrollador los ejecuta en paralelo y retorna todos los resultados en un único user message.

sequenceDiagram
    participant Dev as Desarrollador
    participant Claude

    Dev->>Claude: User message con query
    Claude-->>Dev: content: [tool_use(id=A), tool_use(id=B)]
    Note over Dev: Ejecuta tool A y tool B en paralelo
    Dev->>Claude: role:user, content: [tool_result(id=A), tool_result(id=B)]
    Claude-->>Dev: Respuesta final con ambos resultados

Código completo

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

async function runWithParallelTools(userQuery: string) {
  const response = await client.messages.create({
    model: "claude-sonnet-4-5",
    max_tokens: 1024,
    tools: [weatherTool, stockPriceTool],
    messages: [{ role: "user", content: userQuery }],
  });

  if (response.stop_reason !== "tool_use") {
    return response.content[0];
  }

  // Recopilar todos los tool_use blocks
  const toolUseBlocks = response.content.filter(
    (b): b is Anthropic.ToolUseBlock => b.type === "tool_use"
  );

  // Ejecutar todas en paralelo
  const results = await Promise.all(
    toolUseBlocks.map(async (block) => {
      const output = await dispatchTool(block.name, block.input);
      return {
        type: "tool_result" as const,
        tool_use_id: block.id, // correlación crítica
        content: JSON.stringify(output),
      };
    })
  );

  // Retornar todos los resultados en un único mensaje
  const finalResponse = await client.messages.create({
    model: "claude-sonnet-4-5",
    max_tokens: 1024,
    tools: [weatherTool, stockPriceTool],
    messages: [
      { role: "user", content: userQuery },
      { role: "assistant", content: response.content },
      { role: "user", content: results }, // todos los resultados juntos
    ],
  });

  return finalResponse.content[0];
}

Clave para el examen: cada tool_result debe tener el tool_use_id exacto del bloque correspondiente. Si los IDs no coinciden, la API retorna error 400.


6. Streaming responses

Cuándo usar streaming vs buffering

SituaciónModo recomendado
Interfaz de usuario en tiempo realStreaming
CLI con feedback visualStreaming
CI/CD, procesamiento batchBuffering
Cuando necesitas el response completo antes de actuarBuffering

Streaming básico con tool_use

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

async function streamWithTools(query: string) {
  const stream = await client.messages.stream({
    model: "claude-sonnet-4-5",
    max_tokens: 1024,
    tools: [searchTool],
    messages: [{ role: "user", content: query }],
  });

  let currentToolInput = "";
  let currentToolId = "";
  let currentToolName = "";

  for await (const event of stream) {
    switch (event.type) {
      case "content_block_start":
        if (event.content_block.type === "tool_use") {
          currentToolId = event.content_block.id;
          currentToolName = event.content_block.name;
          currentToolInput = "";
        }
        break;

      case "content_block_delta":
        if (event.delta.type === "text_delta") {
          process.stdout.write(event.delta.text); // mostrar al usuario en tiempo real
        } else if (event.delta.type === "input_json_delta") {
          currentToolInput += event.delta.partial_json; // acumular input de tool
        }
        break;

      case "message_delta":
        if (event.delta.stop_reason === "tool_use") {
          const toolInput = JSON.parse(currentToolInput);
          const result = await dispatchTool(currentToolName, toolInput);
          // continuar el loop con el resultado...
        }
        break;

      case "message_stop":
        // stream completado
        break;
    }
  }

  const finalMessage = await stream.finalMessage();
  return finalMessage;
}

Tipos de eventos clave

EventoCuándo ocurre
content_block_startInicio de un bloque (text o tool_use)
content_block_deltaFragmento de texto o JSON parcial
content_block_stopFin del bloque actual
message_deltaCambio en metadata del mensaje (stop_reason)
message_stopMensaje completado

7. Vision API — inputs multimodales

Claude puede procesar imágenes junto con texto. Esto es útil en arquitecturas donde el input no es solo texto.

Árbol de decisión

flowchart TD
    I[Input del usuario] --> T{¿Contiene imagen?}
    T -->|No| P[Request text-only]
    T -->|Sí| F{¿Tipo de imagen?}
    F -->|"URL pública accesible"| U[image_url]
    F -->|"Archivo local / privado"| B[base64]
    B --> C{¿Tamaño?}
    C -->|"< 5 MB"| OK[Enviar en base64]
    C -->|"> 5 MB"| R[Redimensionar o comprimir primero]

Casos de uso en arquitecturas

Implementación con base64

import Anthropic from "@anthropic-ai/sdk";
import { readFileSync } from "fs";

const client = new Anthropic();

async function analyzeScreenshot(imagePath: string, question: string) {
  const imageData = readFileSync(imagePath);
  const base64Image = imageData.toString("base64");

  const response = await client.messages.create({
    model: "claude-opus-4-5",
    max_tokens: 1024,
    messages: [
      {
        role: "user",
        content: [
          {
            type: "image",
            source: {
              type: "base64",
              media_type: "image/png", // image/jpeg, image/gif, image/webp
              data: base64Image,
            },
          },
          {
            type: "text",
            text: question,
          },
        ],
      },
    ],
  });

  return response.content[0];
}

// Uso
const result = await analyzeScreenshot(
  "./screenshot.png",
  "¿Qué errores aparecen en la consola?"
);

Implementación con URL

const response = await client.messages.create({
  model: "claude-sonnet-4-5",
  max_tokens: 512,
  messages: [
    {
      role: "user",
      content: [
        {
          type: "image",
          source: {
            type: "url",
            url: "https://example.com/architecture-diagram.png",
          },
        },
        { type: "text", text: "Identifica los puntos de fallo único en este diagrama." },
      ],
    },
  ],
});

Limitaciones y costo


8. Token counting y optimización de costos

countTokens antes de enviar

La API expone client.messages.countTokens() para calcular cuántos tokens usará un request antes de enviarlo. Esto es útil para:

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

async function safeRequest(systemPrompt: string, userMessage: string) {
  const MODEL = "claude-sonnet-4-5";
  const CONTEXT_WINDOW = 200_000;
  const OUTPUT_BUDGET = 4096;

  // Contar tokens antes de enviar
  const tokenCount = await client.messages.countTokens({
    model: MODEL,
    system: systemPrompt,
    messages: [{ role: "user", content: userMessage }],
  });

  const availableForOutput = CONTEXT_WINDOW - tokenCount.input_tokens;

  if (availableForOutput < OUTPUT_BUDGET) {
    throw new Error(
      `Contexto insuficiente: ${tokenCount.input_tokens} tokens de input, ` +
        `solo quedan ${availableForOutput} para output`
    );
  }

  return client.messages.create({
    model: MODEL,
    max_tokens: OUTPUT_BUDGET,
    system: systemPrompt,
    messages: [{ role: "user", content: userMessage }],
  });
}

Estimación rápida de tokens

IdiomaRegla aproximada
Inglés~4 caracteres = 1 token
Español~3.5 caracteres = 1 token
Código~3 caracteres = 1 token
JSON estructurado~2.5 caracteres = 1 token

Modelos, context windows y costo (referencia aproximada)

ModeloContext windowInput (por 1M tokens)Output (por 1M tokens)
claude-opus-4-5200K$15$75
claude-sonnet-4-5200K$3$15
claude-haiku-3-5200K$0.80$4

Los precios cambian. Consulta anthropic.com/pricing para valores actualizados.

Estrategia para agentic loops

tokens_disponibles_para_tool_results =
  context_window
  - system_prompt_tokens
  - historial_tokens
  - output_tokens_estimados
  - margen_de_seguridad (10%)

Si tokens_disponibles_para_tool_results es negativo, necesitas:

  1. Comprimir el historial (summarization)
  2. Reducir el system prompt
  3. Paginar los tool results en lugar de incluirlos todos

Resumen del capítulo

ConceptoClave para el examen
Extended Thinkingbudget_tokens mínimo 1000; thinking blocks son privados
Prompt Cachingcache_control: { type: "ephemeral" } en el último bloque estático
Temperature0 para CI/CD y structured output; casi determinístico
tool_choice: autoClaude decide; puede no usar tools
tool_choice: anyClaude DEBE usar al menos una tool
tool_choice: toolClaude DEBE usar exactamente esa tool
tool_choice: noneDeshabilita tools aunque estén definidas
Parallel tool callsUn user message con todos los tool_result usando el tool_use_id correcto
StreamingAcumular input_json_delta para tool inputs; texto en tiempo real
Visionbase64 o URL; sin video/audio; ~1000–4000 tokens por imagen
Token countingclient.messages.countTokens() antes de enviar

Siguiente: Preguntas de Práctica