Integraciones

SDKs

Envía trazas desde tu aplicación con los SDKs livianos de Verica.

Verica publica tres SDKs de observabilidad — TypeScript, Python y Ruby — que instrumentan tus llamadas a LLMs y las exportan como trazas.

Los tres comparten el mismo diseño:

  • Exportan OTLP a {endpoint}/v1/traces con Authorization: Bearer {token}, por lotes.
  • El token es un token de API de Verica con permiso de ingesta. El botón Conectar aplicación, en Observabilidad, lo crea y arma el snippet por vos.
  • Fallan hacia afuera, no hacia adentro: si Verica no responde o el token es inválido, los spans se descartan y tu aplicación no se entera. Los errores de exportación son silenciosos salvo que actives debug.
  • Nunca envían el costo: Verica lo calcula al ingerir, a partir del modelo y los tokens. Por eso el modelo es lo que hace falta para que una traza quede costeada.

Instalación e inicialización

npm install @verica-app/observability

Con CommonJS, inicializa antes de requerir los clientes: OpenAI y Anthropic se instrumentan solos.

const { init, wrapGoogleGenAI } = require('@verica-app/observability');

init({ token: process.env.VERICA_TOKEN });

// Requiere los clientes DESPUÉS de init.
const OpenAI = require('openai');
const Anthropic = require('@anthropic-ai/sdk');

// Gemini no tiene instrumentación comunitaria: se envuelve.
const { GoogleGenAI } = require('@google/genai');
const ai = wrapGoogleGenAI(new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }));

Con ESM los imports no se pueden parchear después, así que se le pasa el módulo:

import { init } from '@verica-app/observability';
import * as openai from 'openai';

init({ token: process.env.VERICA_TOKEN, openaiModule: openai });
pip install verica-observability
import os
import verica

verica.init(token=os.environ["VERICA_TOKEN"])

# Importa los clientes DESPUÉS de init para que queden instrumentados.
from openai import OpenAI
from anthropic import Anthropic
from google import genai

Python es el único de los tres con instrumentación nativa de Gemini. Cada instrumentador es opcional: si falta uno, la inicialización sigue adelante igual.

gem install verica-observability

La gema oficial de openai no tiene instrumentación automática en ningún lado; este SDK la trae.

require 'verica'

Verica.init(token: ENV['VERICA_TOKEN'])
client = Verica.wrap_openai(OpenAI::Client.new)

Para la gema comunitaria ruby-openai, un solo envoltorio cubre OpenAI, Gemini y Anthropic a través de sus endpoints compatibles con OpenAI — el proveedor se deduce del modelo:

openai = Verica.wrap_openai_compatible(OpenAI::Client.new(access_token: ENV['OPENAI_API_KEY']))

gemini = Verica.wrap_openai_compatible(OpenAI::Client.new(
  access_token: ENV['GEMINI_API_KEY'],
  uri_base: 'https://generativelanguage.googleapis.com/v1beta/openai/'
))

anthropic = Verica.wrap_openai_compatible(OpenAI::Client.new(
  access_token: ENV['ANTHROPIC_API_KEY'],
  uri_base: 'https://api.anthropic.com/v1/'
))

Con RubyLLM y su instrumentación de OpenTelemetry, alcanza con Verica.init.

Opciones

Las mismas en los tres, con la convención de nombres de cada lenguaje:

OpciónVariable de entornoPor defectoPara qué
tokenVERICA_TOKEN(obligatorio)Token de API con permiso de ingesta.
endpointVERICA_ENDPOINThttps://ingest.verica.appCámbialo solo en instalaciones propias.
captureContentVERICA_CAPTURE_CONTENTtrueEnviar el contenido de prompts y respuestas.
conversationId(ninguno)Agrupa las trazas en una sesión.
tags(ninguno)Tags globales para todas las trazas.
serviceNameOTEL_SERVICE_NAMEappEl nombre del servicio en la traza.
debugVERICA_DEBUGfalseRegistrar los errores de exportación.

Ruby suma stream_usage (VERICA_STREAM_USAGE, false), que inyecta stream_options para obtener tokens en respuestas por streaming.

Si desactivas captureContent, las trazas llegan solo con metadata: modelo, proveedor, tokens, costo y latencia. Siguen siendo evaluables con span_check, pero no con criterios sobre el texto.

Árboles de spans: agentes y herramientas

Una llamada suelta se traza sola. Para un agente — varias llamadas y varias herramientas en un mismo turno — envuelve el bucle una vez y cada ejecución de herramienta una vez. Todo cae bajo la misma traza, con su árbol de spans.

const { withSpan, withTool } = require('@verica-app/observability');

const answer = await withSpan('agent.run', async () => {
  await client.chat.completions.create({ /* … */ });          // span hijo
  const weather = withTool('lookup_weather', () => lookupWeather('Cordoba'));
  return client.chat.completions.create({ /* … */ });          // span hijo
});
with verica.span("agent.run"):
    client.chat.completions.create(...)          # span hijo
    with verica.tool("lookup_weather"):
        weather = lookup_weather("Cordoba")
    client.chat.completions.create(...)          # span hijo
Verica.with_span('agent.run') do
  client.chat.completions.create(model: 'gpt-4o-mini', messages: plan_messages)
  weather = Verica.with_tool('lookup_weather') { lookup_weather('Cordoba') }
  client.chat.completions.create(model: 'gpt-4o-mini', messages: reply_messages(weather))
end

Detalles que valen para los tres:

  • El nombre del span se usa literal: es lo que van a matchear los patrones de span_check.
  • Devuelven lo que devuelve el bloque, sin tocarlo.
  • Una excepción marca el span como error y se vuelve a lanzar sin cambios. Esos spans son los que cuenta la comprobación Spans con error.
  • Sin haber llamado a init, la función simplemente ejecuta el bloque y no emite nada.

Como el exportador manda los spans a medida que terminan, un agente largo llega en varios envíos; Verica los fusiona en una sola traza.

Sesiones multi-turno

Para que Verica agrupe los turnos de una conversación, cada llamada tiene que llevar el mismo identificador de sesión.

import { init, withConversation, withTags } from '@verica-app/observability';

init({ token: process.env.VERICA_TOKEN!, tags: ['web', 'prod'] });

const reply = await withTags(['chat', 'premium'], () =>
  withConversation(`chat-${chatId}`, () => openai.chat.completions.create({ /* … */ })),
);

El alcance usa AsyncLocalStorage, así que sobrevive a los await y nunca se filtra entre peticiones concurrentes.

with verica.conversation(f"chat-{chat_id}"):
    response = client.chat.completions.create(...)

with verica.tags(["chat", "premium"]):
    client.chat.completions.create(...)

El alcance usa contextvars, así que es seguro con async y con hilos.

class MessagesController < ApplicationController
  def create
    Verica.with_conversation("chat-#{conversation.id}") do
      reply = openai.chat(parameters: {
        model: 'gpt-4o-mini',
        messages: conversation.to_openai_messages
      })
      # …persistir y renderizar la respuesta…
    end
  end
end

El alcance es local al hilo y anidable; se restaura al salir aunque el bloque lance una excepción.

Los tags por petición se suman a los globales en lugar de reemplazarlos, y los bloques anidados acumulan. El identificador de conversación, en cambio, reemplaza al global dentro de su bloque; pasarle null (o None, o vacío) suprime el atributo para ese alcance.

Reenvía la historia de la conversación exactamente como la mandaste. Verica guarda el delta de cada turno detectando el prefijo que coincide con el anterior, y esa comparación es byte a byte. Si mutas los mensajes anteriores al rearmar la conversación, cada turno termina guardando el diálogo completo. Ver Sesiones multi-turno.

Streaming (Ruby)

El envoltorio wrap_openai_compatible captura las respuestas por streaming: acumula los fragmentos mientras fluyen hacia tu bloque y después anota el span con la salida completa. Tu bloque recibe todos los fragmentos, en orden y sin modificar.

Los tokens y el costo solo existen si el proveedor manda un fragmento final de uso:

client.chat(parameters: {
  model: 'gpt-4o-mini',
  messages: [{ role: 'user', content: '¡Hola!' }],
  stream_options: { include_usage: true },
  stream: proc { |chunk| print chunk.dig('choices', 0, 'delta', 'content') }
})

También puedes usar Verica.init(..., stream_usage: true) y el envoltorio inyecta esa opción en una copia privada de tus parámetros, sin mutar tu hash. Viene apagado por defecto porque ese fragmento extra llega con choices vacío y algunos bloques no lo esperan.

Las llamadas a herramientas se capturan en ambos caminos, incluso cuando el turno no tiene texto.

Entornos serverless

Antes de que se congele el runtime, vacía el búfer:

  • TypeScript: await flush() o await shutdown()
  • Python: verica.flush() o verica.shutdown()
  • Ruby: Verica.flush o Verica.shutdown

Siguientes pasos

En esta página