Langfuse
The Langfuse SDK writes its spans into your application tracer provider, which exports to Oodle over OTLP.
You will need:
OODLE_INSTANCE: your Oodle instance ID (ap1, us1)OODLE_API_KEY: an Oodle API key (ap1, us1)OTLP_ENDPOINT: your OTLP collector domain, shown on the tile
Open the Langfuse tile on the ap1, us1 page to
get these filled in for you, or let an agent do the setup with
/oodle-onboarding set up the llm_observability_langfuse integration.
Python
Install
Install the Langfuse SDK alongside your LLM provider SDK:
pip install langfuse openai \
opentelemetry-exporter-otlp-proto-http \
opentelemetry-sdk
Instrument
Import your LLM SDK through the Langfuse drop-in. Every call it makes is traced, and the observe wrapper groups related calls into one trace:
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.trace import set_tracer_provider
from langfuse import Langfuse, get_client, observe
def setup_opentelemetry():
resource = Resource.create({"service.name": "my-llm-app"})
tracer_provider = TracerProvider(resource=resource)
tracer_provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter())
)
set_tracer_provider(tracer_provider)
return tracer_provider
# Langfuse writes into the app tracer provider instead of
# owning one. Giving it its own exporter as well would send
# every Langfuse span twice.
Langfuse(
tracer_provider=setup_opentelemetry(),
# Langfuse adds its own processor even when it is
# handed a provider. The app provider already
# exports to Oodle, so refuse its export rather
# than sending every span to Langfuse too.
should_export_span=lambda span: False,
)
# The Langfuse OpenAI drop-in traces every call it makes.
# Import it after Langfuse() so it uses that client.
from langfuse.openai import openai
# Optional: group the LLM calls of one request under a
# single trace.
@observe()
def chat(message: str) -> str:
response = openai.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": message}],
)
return response.choices[0].message.content
chat("Hello!")
# Short-lived processes must flush before they exit.
get_client().flush()
Environment
Point the application at Oodle:
# OTLP endpoint (points straight at Oodle)
export OTEL_EXPORTER_OTLP_ENDPOINT=https://<OTLP_ENDPOINT>
export OTEL_EXPORTER_OTLP_HEADERS="X-API-KEY=<OODLE_API_KEY>,X-OODLE-INSTANCE=<OODLE_INSTANCE>"
# Required even though the OTel exporter owns transport:
# without them the Langfuse client disables itself.
export LANGFUSE_PUBLIC_KEY="default"
export LANGFUSE_SECRET_KEY="unused"
TypeScript / JavaScript
Install
Install the Langfuse SDK alongside your LLM provider SDK:
npm install @langfuse/tracing @langfuse/openai \
@opentelemetry/exporter-trace-otlp-proto \
@opentelemetry/sdk-trace-base @opentelemetry/sdk-trace-node \
@opentelemetry/resources @opentelemetry/semantic-conventions openai
Instrument
Import your LLM SDK through the Langfuse drop-in. Every call it makes is traced, and the observe wrapper groups related calls into one trace:
// instrumentation.ts
import { setLangfuseTracerProvider } from '@langfuse/tracing';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
// NodeTracerProvider does not read OTEL_SERVICE_NAME, so name
// the service here or every trace lands under
// unknown_service:node.
export const provider = new NodeTracerProvider({
resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: 'my-llm-app' }),
spanProcessors: [new BatchSpanProcessor(new OTLPTraceExporter())],
});
provider.register();
// Langfuse writes into this provider instead of owning one.
// Adding LangfuseSpanProcessor as well would send every
// Langfuse span twice.
setLangfuseTracerProvider(provider);
// index.ts - import the instrumentation first.
import { provider } from './instrumentation';
import { observe } from '@langfuse/tracing';
import { observeOpenAI } from '@langfuse/openai';
import OpenAI from 'openai';
const openai = observeOpenAI(new OpenAI());
const chat = observe(
async (message: string) => {
const response = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: message }],
});
return response.choices[0].message.content;
},
{ name: 'chat' },
);
async function main() {
await chat('Hello!');
// Short-lived processes must flush before they exit.
await provider.forceFlush();
}
main();
Environment
Point the application at Oodle:
# OTLP endpoint (points straight at Oodle)
export OTEL_EXPORTER_OTLP_ENDPOINT=https://<OTLP_ENDPOINT>
export OTEL_EXPORTER_OTLP_HEADERS="X-API-KEY=<OODLE_API_KEY>,X-OODLE-INSTANCE=<OODLE_INSTANCE>"
# Required even though the OTel exporter owns transport:
# without them the Langfuse client disables itself.
export LANGFUSE_PUBLIC_KEY="default"
export LANGFUSE_SECRET_KEY="unused"
Verify
Run your application, then open ap1, us1. Spans
carry gen_ai.* attributes: the model, token counts, and the prompt and
response content. Click a trace for the Transcript, the waterfall, and
the cost breakdown.
If nothing arrives, check that the exporter can reach
https://<OTLP_ENDPOINT> and that the instance and key are set: the
OTLP gateway answers 401 without them.
Support
If you need assistance or have any questions, please reach out to us through:
- Email at [email protected]