Cap 26: API Fundamentals para Arquitectos
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
| Tipo | Visible al usuario | Se cobra como |
|---|---|---|
thinking | No (interno) | Output tokens |
text (output) | Sí | 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
thinkingno 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 correcta | Resultado |
|---|---|
| System prompt completo (estático) | Cachea todo el system prompt |
| Documento grande en primer user message | Cachea system + documento |
| Historial de conversación hasta N-1 | Cachea 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
CLAUDE.mdlargo en pipelines de CI que se ejecutan muchas veces- Base de conocimiento que se consulta en múltiples queries del mismo usuario
- Contexto de proyecto estático en agentic loops largos
Impacto en métricas
- Latencia: ~90% de reducción en el procesamiento del prefix cacheado
- Costo de input: ~30–50% de reducción (los tokens cacheados tienen precio menor)
- Costo de output: sin cambio
3. Temperature y parámetros de sampling
Tabla de referencia rápida
| Tarea | Temperature recomendada |
|---|---|
| Extracción de datos, classification | 0 |
| Generación de código | 0 – 0.2 |
| CI/CD, structured output | 0 |
| Respuestas de asistente de propósito general | 1 (default) |
| Brainstorming, diversidad de opciones | 0.7 – 1 |
| Creative writing | 1 – 1.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:
- Paralelismo en hardware (operaciones de punto flotante no determinísticas)
- Actualizaciones del modelo en el servidor
Para pipelines críticos usa temperature: 0 y diseña el sistema para tolerar variación mínima.
top_p y top_k
top_p(nucleus sampling): solo considera tokens cuya probabilidad acumulada llega ap. Valor default:1(sin restricción).top_k: solo considera losktokens más probables. No está expuesto directamente en la API de Claude.
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
| Modo | Claude puede responder sin tool | Claude elige la tool | Error si no hay tools | Uso típico |
|---|---|---|---|---|
auto | Sí | Sí | No | Conversación general |
any | No | Sí (cualquiera) | Sí | Forzar extracción |
tool + name | No | No (fija) | Sí si no existe | Schema específico |
none | Sí (siempre texto) | N/A | No | Aná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
- Usar
tool_choice: { type: "tool", name: "X" }con una tool que no está en la lista → error400 - Usar
anysin tools definidas → error400 - Asumir que
autosiempre llamará tools — puede responder en texto si considera que no necesita tools
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_resultdebe tener eltool_use_idexacto del bloque correspondiente. Si los IDs no coinciden, la API retorna error400.
6. Streaming responses
Cuándo usar streaming vs buffering
| Situación | Modo recomendado |
|---|---|
| Interfaz de usuario en tiempo real | Streaming |
| CLI con feedback visual | Streaming |
| CI/CD, procesamiento batch | Buffering |
| Cuando necesitas el response completo antes de actuar | Buffering |
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
| Evento | Cuándo ocurre |
|---|---|
content_block_start | Inicio de un bloque (text o tool_use) |
content_block_delta | Fragmento de texto o JSON parcial |
content_block_stop | Fin del bloque actual |
message_delta | Cambio en metadata del mensaje (stop_reason) |
message_stop | Mensaje 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
- Screenshot de UI para debugging automatizado
- PDF o imagen de factura para extracción de campos
- Diagrama de arquitectura para análisis y documentación
- Captura de dashboard para generación de reportes
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
- No puede procesar: video, audio nativo, archivos
.zip, ejecutables - Costo estimado por imagen: 1000–4000 tokens adicionales dependiendo del tamaño y detalle
- Formatos soportados: PNG, JPEG, GIF, WebP
- Tamaño máximo recomendado: 5 MB por imagen
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:
- Verificar que el mensaje cabe en el context window
- Planificar el espacio disponible para tool results
- Evitar errores
max_tokensinesperados en producción
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
| Idioma | Regla 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)
| Modelo | Context window | Input (por 1M tokens) | Output (por 1M tokens) |
|---|---|---|---|
| claude-opus-4-5 | 200K | $15 | $75 |
| claude-sonnet-4-5 | 200K | $3 | $15 |
| claude-haiku-3-5 | 200K | $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:
- Comprimir el historial (summarization)
- Reducir el system prompt
- Paginar los tool results en lugar de incluirlos todos
Resumen del capítulo
| Concepto | Clave para el examen |
|---|---|
| Extended Thinking | budget_tokens mínimo 1000; thinking blocks son privados |
| Prompt Caching | cache_control: { type: "ephemeral" } en el último bloque estático |
| Temperature | 0 para CI/CD y structured output; casi determinístico |
tool_choice: auto | Claude decide; puede no usar tools |
tool_choice: any | Claude DEBE usar al menos una tool |
tool_choice: tool | Claude DEBE usar exactamente esa tool |
tool_choice: none | Deshabilita tools aunque estén definidas |
| Parallel tool calls | Un user message con todos los tool_result usando el tool_use_id correcto |
| Streaming | Acumular input_json_delta para tool inputs; texto en tiempo real |
| Vision | base64 o URL; sin video/audio; ~1000–4000 tokens por imagen |
| Token counting | client.messages.countTokens() antes de enviar |
Siguiente: Preguntas de Práctica