Stack · Anthropic Claude

AI es el equipo, no un botón de chatbot

Prompt caching que reduce los costes en un orden de magnitud, tool use que convierte a Claude en un operador, servidores MCP que conectan el modelo a tu stack y la traza de auditoría que te deja pasar la compliance. El sistema operativo, no la slide de marketing.

Por qué este stack

01

El prompt caching es la palanca que la mayoría de los equipos se pierde

Un system prompt estable y un documento de referencia largo entran en cache después de la primera petición. Las siguientes pagan alrededor del diez por ciento del coste de tokens de input. Diseñamos la estructura del prompt para que el caching aplique en cada llamada relevante, y eso mueve una integración Claude de "funcionalidad cara" a "siempre activa".

02

El tool use convierte a Claude en un miembro del equipo

Un tool es una función tipada que el modelo puede llamar. Definimos los tools como schemas TypeScript, el modelo elige uno, nuestro handler lo ejecuta y el resultado vuelve a la conversación. El patrón funciona para leer la base de datos, hacer una búsqueda, llamar a una operación de Stripe o invocar otro agente.

03

Los servidores MCP conectan el modelo a tu stack

Model Context Protocol es cómo un agente le habla a tu código aplicativo sin scaffolding REST. Hemos enviado servidores MCP para Design System Ops, Dev Ops y Social Ops; el mismo patrón vale para tus herramientas internas. El modelo usa tu código, tu código no parsea el modelo.

04

Streaming, pero hecho bien

Streaming basado en SSE con manejo correcto de backpressure, renderizado parcial de mensajes, error boundaries que no colapsan la UI cuando el modelo falla a mitad de stream, y parsing estructurado de eventos para que una llamada a tool dentro de un stream no sea un hack de parsing.

05

Cost accounting y governance desde el primer día

Cada petición a Claude queda loggeada con modelo, tokens de input, tokens de output, cache hit ratio y coste. Rate limits por tenant, presupuestos por usuario, y un dashboard financiero que no exige un paso de export a hoja de cálculo.

Qué construimos con esta tecnología

Integración tipada Anthropic SDK

Client TypeScript con retries, backoff consciente del rate-limit, manejo estructurado de errores, selección de modelo por tarea (Opus / Sonnet / Haiku).

Arquitectura de prompt caching

Breakpoints de cache colocados sobre las partes estables (system prompt, docs de referencia, ejemplos largos), cache hit ratio medible en el dashboard.

Framework de tool use

Tools definidos como schemas TypeScript con validación Zod. La cadena de handlers es transaccional respecto a la base de datos, así una escritura desencadenada por el modelo hace rollback ante una validación fallida.

Servidor MCP para tu stack

Un servidor Model Context Protocol que expone las capacidades de tu aplicación a Claude (leer DB, escribir, llamar APIs, disparar workflows). Versionado, gobernado, con audit log.

UI de streaming

Transporte Server-Sent Events, renderizado parcial de mensajes, renderizado inline de tool calls, error boundaries recuperables.

Orquestación de agentes

Agentes multi-step que planifican, ejecutan tools, observan resultados y deciden qué hacer luego. Bucles con número máximo de pasos y condiciones de salida explícitas.

Persistencia de conversaciones

Store de conversaciones en base de datos con sumarización para threads largos, gestión de la context window consciente del presupuesto de tokens.

Token accounting + reportes de coste

Logging por petición de modelo, tokens, coste, cache hit ratio. Agregados por tenant, presupuestos por usuario, reporting mensual conectado al dashboard de administración.

Mitigación de prompt injection

Aislamiento del system prompt, fencing de input no confiable, validación del output contra schema esperado, manejo del refusal ante instrucciones adversariales.

Pipeline de validación de output

Outputs estructurados validados contra schemas Zod, re-prompt en parse fallido, dead-letter logging para los casos irrecuperables.

Política de selección de modelo

Haiku para tareas de alto volumen y bajo coste, Sonnet para el nivel estándar, Opus para reasoning difícil. La política vive en el código, no en la cabeza de alguien.

Estrategia de fallback

Degradación graceful cuando Anthropic no está disponible, retry con backoff, fallback cross-provider opcional donde el caso de uso lo permite.

Una llamada tipada a Claude con prompt caching, tool use y streaming

Una función hace todo el flujo. El system prompt y un documento de referencia entran en cache en la primera petición. El modelo puede llamar a un tool `lookupCustomer` cuyo resultado vuelve a la conversación. La respuesta va en stream al client por SSE.

La mayoría de las "integraciones Claude" conectan un input de chatbot a la API y se quedan ahí. La AI en producción tiene otra cara. La función de abajo es la que enviamos: una llamada tipada a messages.create, system prompt y documento de referencia en cache, un tool lookupCustomer que el modelo puede llamar, la respuesta que vuelve al client por Server-Sent Events.

1. El client tipado

El Anthropic SDK queda envuelto en un client fino que añade retries, mapeo de errores y una union tipada de modelo. La selección de modelo ocurre en el call site; el client no elige.

// src/lib/claude/client.ts
import Anthropic from '@anthropic-ai/sdk'

export const claude = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY!,
  maxRetries: 3,
})

export type ClaudeModel =
  | 'claude-opus-4-7'
  | 'claude-sonnet-4-6'
  | 'claude-haiku-4-5'

export const MODEL_BY_TASK: Record<'reasoning' | 'standard' | 'cheap', ClaudeModel> = {
  reasoning: 'claude-opus-4-7',
  standard: 'claude-sonnet-4-6',
  cheap: 'claude-haiku-4-5',
}

2. La definición del tool

Un tool es una función tipada con un JSON schema. Zod produce el schema; el handler corre en el flujo transaccional normal de la aplicación. El modelo nunca toca la base de datos directamente.

// src/lib/claude/tools/lookup-customer.ts
import { z } from 'zod'
import { zodToJsonSchema } from 'zod-to-json-schema'
import { adminClient } from '@/lib/supabase/admin'

export const lookupCustomerInput = z.object({
  email: z.string().email(),
})

export const lookupCustomerTool = {
  name: 'lookupCustomer',
  description:
    'Find a customer by email. Returns the canonical record or null.',
  input_schema: zodToJsonSchema(lookupCustomerInput, {
    target: 'jsonSchema7',
  }) as Record<string, unknown>,
} as const

export async function runLookupCustomer(
  input: z.infer<typeof lookupCustomerInput>,
): Promise<unknown> {
  const { data, error } = await adminClient
    .from('customers')
    .select('id, name, plan, status, created_at')
    .eq('email', input.email)
    .maybeSingle()

  if (error) throw error
  return data ?? null
}

3. La llamada en streaming con prompt caching

El system prompt y el documento de referencia llevan cache_control. Después de la primera petición, las siguientes pagan la tarifa cache-read (alrededor del diez por ciento del precio estándar de input) sobre esos bloques. Los mensajes de la conversación se quedan sin cache porque cambian en cada turno.

// src/lib/claude/run.ts
import { claude, MODEL_BY_TASK } from './client'
import { lookupCustomerTool, runLookupCustomer, lookupCustomerInput } from './tools/lookup-customer'
import { systemPrompt } from './prompts/system'
import { referenceDoc } from './prompts/reference'

interface RunInput {
  conversation: { role: 'user' | 'assistant'; content: string }[]
  tenantId: string
}

export async function* runClaude(input: RunInput) {
  const stream = await claude.messages.create({
    model: MODEL_BY_TASK.standard,
    max_tokens: 4096,
    system: [
      {
        type: 'text',
        text: systemPrompt,
        cache_control: { type: 'ephemeral' },
      },
      {
        type: 'text',
        text: referenceDoc,
        cache_control: { type: 'ephemeral' },
      },
    ],
    messages: input.conversation,
    tools: [lookupCustomerTool],
    stream: true,
  })

  for await (const chunk of stream) {
    yield chunk

    // Cuando el modelo decide llamar a un tool, el stream emite un
    // content_block_stop con el payload tool_use. Ejecutamos el tool y
    // continuamos la conversación con el resultado.
    if (
      chunk.type === 'content_block_stop' &&
      'content_block' in chunk &&
      chunk.content_block?.type === 'tool_use' &&
      chunk.content_block.name === 'lookupCustomer'
    ) {
      const parsed = lookupCustomerInput.parse(chunk.content_block.input)
      const result = await runLookupCustomer(parsed)
      yield {
        type: 'tool_result' as const,
        tool_use_id: chunk.content_block.id,
        content: JSON.stringify(result),
      }
    }
  }
}

4. La route Server-Sent Events

El generator del streaming pasa por un transporte Server-Sent Events para que el navegador reciba los tokens parciales según llegan. El route handler también escribe una fila de log por llamada, así que cost accounting y audit no son opcionales.

// app/api/chat/route.ts
import { runClaude } from '@/lib/claude/run'
import { logClaudeRequest } from '@/lib/claude/logging'
import { getServerSession } from '@/lib/auth/server'

export const runtime = 'nodejs'

export async function POST(request: Request) {
  const session = await getServerSession()
  if (!session) return new Response('unauthorised', { status: 401 })

  const { conversation } = await request.json()
  const start = Date.now()

  const stream = new ReadableStream({
    async start(controller) {
      let inputTokens = 0
      let outputTokens = 0
      let cacheHits = 0

      try {
        for await (const chunk of runClaude({
          conversation,
          tenantId: session.tenantId,
        })) {
          if (chunk.type === 'message_start') {
            inputTokens = chunk.message.usage.input_tokens
            cacheHits = chunk.message.usage.cache_read_input_tokens ?? 0
          }
          if (chunk.type === 'message_delta') {
            outputTokens = chunk.usage.output_tokens
          }
          controller.enqueue(
            new TextEncoder().encode(`data: ${JSON.stringify(chunk)}\n\n`),
          )
        }
      } finally {
        controller.close()
        await logClaudeRequest({
          tenantId: session.tenantId,
          userId: session.userId,
          model: 'claude-sonnet-4-6',
          inputTokens,
          outputTokens,
          cacheHits,
          durationMs: Date.now() - start,
        })
      }
    },
  })

  return new Response(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      Connection: 'keep-alive',
    },
  })
}

5. Qué te compra esto

El prompt caching convierte una llamada de cinco céntimos en una de medio céntimo. El tool use convierte al modelo de una superficie de chat en un operador que lee tu base de datos, llama a tus APIs, dispara tus workflows. El audit log le da al equipo de finanzas un número con el que planificar y a security un registro que firmar. Todo entra en cuatro archivos porque el SDK está bien diseñado y la aplicación deja de pelearse con él.

Esto es lo que significa "AI es el equipo" en código, no en la slide de marketing.

Preguntas frecuentes

¿Por qué Claude frente a OpenAI o Google?

Para coding, reasoning estructurado y agentes con mucho tool use, Claude gana consistentemente nuestros benchmarks internos al precio de referencia. El Anthropic SDK tiene hoy el soporte más maduro para prompt caching y tool use. Para casos específicos (image gen, audio) usamos la herramienta adecuada para la tarea; Claude es el default.

¿Cuánto puede ahorrar de verdad el prompt caching?

En un workflow de agente típico con system prompt de 5.000 tokens y un doc de referencia de 20.000 tokens, vemos el coste de input caer alrededor del 80-90 por ciento una vez que el caching calienta. El número exacto depende del cache hit ratio y de la estructura de turnos; lo medimos por proyecto y lo reportamos mensualmente.

¿Cuál es la diferencia entre tool use y function calling?

Misma idea, palabras distintas. Anthropic lo llama tool use, OpenAI lo llama function calling. Los patrones son conceptualmente intercambiables; nosotros usamos el formato structured tool de Anthropic que nos da parámetros tipados vía JSON Schema y manejo limpio del stop reason.

¿Construyen servidores MCP?

Sí, los hemos enviado para nuestros equipos Ops internos (Design System Ops, Dev Ops, Social Ops). Para los proyectos de cliente el patrón es el mismo: eliges las operaciones que Claude tiene que poder llamar, las expones vía MCP, versionas el schema, aplicas governance y audit.

¿Cómo gestionáis la optimización de costes?

Tres palancas, en orden. Primero, prompt caching: la ganancia individual más grande, en cada proyecto. Segundo, selección de modelo: Haiku para tareas baratas, Sonnet para el nivel estándar, Opus para los casos difíciles. Tercero, trimming de contexto: sumarizamos turnos viejos, descartamos resultados de tool obsoletos, mantenemos el working set dentro del presupuesto.

¿Seguridad y prompt injection?

Aislamiento del system prompt por diseño de la API de Anthropic, fencing del input no confiable en código (nunca concatenamos input del usuario en el system prompt), validación del output contra un schema Zod, y manejo del refusal ante instrucciones adversariales. Loggeado y auditable por cada petición.

¿Anthropic API directa, Bedrock o Vertex?

Anthropic API directa para la mayoría de proyectos (cadencia de release de modelos más rápida, feature parity completa). Bedrock cuando el buyer requiere data residency en AWS, Vertex cuando el buyer está en Google Cloud. El código aplicativo se mantiene portable; solo cambia la construcción del client.

Cuéntanos qué estás construyendo con Claude

Una llamada de scoping, un número concreto en la primera respuesta, sin teatro de agencia. Una integración Claude en producción en dos a cuatro semanas.