Skip to content
reaatechREAATECH

@reaatech/otel-genai-semconv-vertexai

npm v0.1.0

Instruments the Google Generative Language (Vertex AI) SDK to automatically emit OpenTelemetry spans following GenAI semantic conventions. It provides an `instrument` function that wraps the `generateContent` method to capture request metadata, generation configurations, and response events.

@reaatech/otel-genai-semconv-vertexai

npm version License: MIT CI

Status: Pre-1.0 — APIs may change in minor versions. Pin to a specific version in production.

Transparent instrumentation for the Google Generative Language (Vertex AI) SDK. Wraps model.generateContent() to emit OpenTelemetry GenAI semantic convention spans with GCP project/location metadata, generation config attributes, candidate events, and cost tracking for Gemini models.

Installation

terminal
npm install @reaatech/otel-genai-semconv-vertexai
# or
pnpm add @reaatech/otel-genai-semconv-vertexai

Feature Overview

  • Zero-config instrumentation — call instrument(model) once, every generateContent() call is traced
  • GCP metadata — automatically attaches gcp.project_id and gcp.location when configured
  • Generation config mapping — temperature, topP, topK, maxOutputTokens, stopSequences, and more mapped to OTel attributes
  • Candidate events — each response candidate emits a gen_ai.choice event with text content and finish reason
  • System instruction tracking — system instructions are captured as gen_ai.system.message events
  • Double-instrumentation guard — calling instrument() twice is a safe no-op
  • Lifecycle hooksonStart and onEnd callbacks for custom span attributes
  • Safe uninstrument — restores the original generateContent() method
  • Dual ESM/CJS output — works with import and require

Quick Start

typescript
import { VertexAIInstrumentation } from "@reaatech/otel-genai-semconv-vertexai";
 
const instrumentation = new VertexAIInstrumentation({
  trackCosts: true,
  projectId: "my-gcp-project",
  location: "us-central1",
});
 
instrumentation.instrument(model);
 
const response = await model.generateContent({
  contents: [{ role: "user", parts: [{ text: "What is OpenTelemetry?" }] }],
});
// Each call now emits OTel spans with gen_ai.* attributes

Captured Attributes

Request Attributes

AttributeSourceDescription
gen_ai.request.modelModel nameModel identifier
gen_ai.request.temperaturegenerationConfig.temperatureSampling temperature
gen_ai.request.top_pgenerationConfig.topPTop-p sampling
gen_ai.request.top_kgenerationConfig.topKTop-k sampling
gen_ai.request.max_tokensgenerationConfig.maxOutputTokensMax output tokens
gen_ai.request.stop_sequencesgenerationConfig.stopSequencesStop sequences
gen_ai.request.candidates_per_promptgenerationConfig.candidateCountNumber of candidates
gen_ai.request.presence_penaltygenerationConfig.presencePenaltyPresence penalty
gen_ai.request.frequency_penaltygenerationConfig.frequencyPenaltyFrequency penalty
gen_ai.request.tool_namesrequest.tools[].functionDeclarations[].nameTool names
gen_ai.provider.namehardcodedgcp.vertex_ai

GCP Metadata (when configured)

AttributeSourceDescription
gcp.project_idconfig.projectIdGCP project identifier
gcp.locationconfig.locationGCP region

Response Attributes

AttributeSourceDescription
gen_ai.response.modelresponse.modelVersionModel version used
gen_ai.response.finish_reasonscandidates[].finishReason (mapped)Mapped to OTel finish reasons
gen_ai.usage.input_tokensusageMetadata.promptTokenCountInput token count
gen_ai.usage.output_tokensusageMetadata.candidatesTokenCountOutput token count

Finish Reason Mapping

Vertex AI’s finishReason values are mapped to OTel:

Vertex AIOTel
STOPstop
MAX_TOKENSlength
SAFETYcontent_filter
RECITATIONcontent_filter
OTHERunknown

Cost Attributes (when trackCosts: true)

AttributeDescription
llm.cost.totalTotal cost in USD
llm.cost.inputInput token cost
llm.cost.outputOutput token cost
llm.cost.currencyCurrency code (always USD)

Span Events

EventWhen
gen_ai.system.messageSystem instruction in the request
gen_ai.user.messageUser content parts in the request
gen_ai.assistant.messageAssistant content parts
gen_ai.choiceEach candidate (with index, finish_reason, text content)

API Reference

VertexAIInstrumentation (class)

Constructor

typescript
new VertexAIInstrumentation({
  captureRequestHeaders?: boolean;
  captureResponseHeaders?: boolean;
  trackCosts?: boolean;
  pricing?: Record<string, PricingInfo>;
  projectId?: string;
  location?: string;
  onStart?: (span: Span, request: GenerateContentRequest) => void;
  onEnd?: (span: Span, response: GenerateContentResponse) => void;
})

Methods

MethodDescription
instrument(model)Wrap model.generateContent() with instrumentation
uninstrument(model)Restore the original generateContent() method

VertexAITokenCounter (class)

Character-based token estimation for Vertex AI models:

typescript
const counter = new VertexAITokenCounter();
counter.countTokens("Hello, world!", "gemini-pro");
counter.countContentsTokens(contents, "gemini-pro");
counter.clearCache();

Attribute Mappers

typescript
import { mapVertexAIRequest, mapVertexAIResponse, mapVertexAIError } from "@reaatech/otel-genai-semconv-vertexai";
 
const requestAttrs = mapVertexAIRequest(request, "gemini-pro");
const responseAttrs = mapVertexAIResponse(response);
const errorAttrs = mapVertexAIError(apiError);

Configuration

GCP Project and Location

typescript
new VertexAIInstrumentation({
  projectId: "my-gcp-project",
  location: "us-central1",
}).instrument(model);

Lifecycle Hooks

typescript
new VertexAIInstrumentation({
  onStart: (span, request) => {
    span.setAttribute("vertexai.candidate_count", request.generationConfig?.candidateCount ?? 1);
  },
  onEnd: (span, response) => {
    span.setAttribute("vertexai.model_version", response.modelVersion);
  },
}).instrument(model);

Usage Patterns

String Input (Auto-Normalized)

typescript
// The instrumentation automatically normalizes string input:
const response = await model.generateContent("What is OpenTelemetry?");
// Internally converted to { contents: [{ role: "user", parts: [{ text: "..." }] }] }

Multi-Turn Conversation

typescript
const response = await model.generateContent({
  contents: [
    { role: "user", parts: [{ text: "What is OpenTelemetry?" }] },
    { role: "assistant", parts: [{ text: "OpenTelemetry is..." }] },
    { role: "user", parts: [{ text: "Tell me more about tracing." }] },
  ],
});
// Each message emits the appropriate gen_ai.*.message event

License

MIT