Community Elixir SDK for Langfuse - Open source LLM observability, tracing, and prompt management.
Note: This is an unofficial community-maintained SDK, not affiliated with or endorsed by Langfuse GmbH.
- Tracing - Create traces, spans, generations, and events for LLM observability
- Scoring - Attach numeric, categorical, and boolean scores to traces and observations
- Sessions - Group related traces into conversations
- Prompts - Fetch, cache, and compile version-controlled prompts
- Client API - Full REST API access for datasets, models, and management
- OpenTelemetry - Optional integration for distributed tracing
- Instrumentation - Macros for automatic function tracing
- Data Masking - Redact sensitive data before sending to Langfuse
- Async Batching - Non-blocking event ingestion with configurable batching
Add langfuse to your list of dependencies in mix.exs:
def deps do
[
{:langfuse, "~> 0.1.0"}
]
endFor OpenTelemetry integration, add the optional dependencies:
def deps do
[
{:langfuse, "~> 0.1.0"},
{:opentelemetry_api, "~> 1.4"},
{:opentelemetry, "~> 1.5"}
]
endConfigure Langfuse in your config/config.exs:
config :langfuse,
public_key: "pk-...",
secret_key: "sk-...",
host: "https://cloud.langfuse.com"Or use environment variables:
export LANGFUSE_PUBLIC_KEY="pk-..."
export LANGFUSE_SECRET_KEY="sk-..."
export LANGFUSE_HOST="https://cloud.langfuse.com"| Option | Type | Default | Description |
|---|---|---|---|
public_key |
string | - | Langfuse public key (or LANGFUSE_PUBLIC_KEY) |
secret_key |
string | - | Langfuse secret key (or LANGFUSE_SECRET_KEY) |
host |
string | https://cloud.langfuse.com |
Langfuse API host |
environment |
string | nil |
Environment tag (e.g., "production", "staging") |
enabled |
boolean | true |
Enable/disable SDK |
flush_interval |
integer | 5000 |
Batch flush interval in ms |
batch_size |
integer | 100 |
Maximum events per batch |
max_retries |
integer | 3 |
HTTP retry attempts |
debug |
boolean | false |
Enable debug logging |
mask_fn |
function | nil |
Custom function for masking sensitive data |
trace = Langfuse.trace(
name: "chat-request",
user_id: "user-123",
metadata: %{source: "api"},
version: "1.0.0",
release: "2025-01-15"
)
span = Langfuse.span(trace,
name: "document-retrieval",
type: :retriever,
input: %{query: "test"}
)
span = Langfuse.update(span, output: retrieved_docs)
span = Langfuse.end_observation(span)
generation = Langfuse.generation(trace,
name: "chat-completion",
model: "gpt-4",
input: [%{role: "user", content: "Hello"}],
model_parameters: %{temperature: 0.7}
)
generation = Langfuse.update(generation,
output: %{role: "assistant", content: "Hi there!"},
usage: %{input: 10, output: 5, total: 15}
)
generation = Langfuse.end_observation(generation)
Langfuse.score(trace, name: "quality", value: 0.9)Spans support semantic types for better organization in the Langfuse UI:
Langfuse.span(trace, name: "agent-loop", type: :agent)
Langfuse.span(trace, name: "tool-call", type: :tool)
Langfuse.span(trace, name: "rag-chain", type: :chain)
Langfuse.span(trace, name: "doc-search", type: :retriever)
Langfuse.span(trace, name: "embed-text", type: :embedding)
Langfuse.span(trace, name: "generic-step", type: :default)Group related traces into sessions:
session_id = Langfuse.Session.new_id()
trace1 = Langfuse.trace(name: "turn-1", session_id: session_id)
trace2 = Langfuse.trace(name: "turn-2", session_id: session_id)
Langfuse.Session.score(session_id, name: "satisfaction", value: 4.5)Fetch and use prompts from Langfuse:
{:ok, prompt} = Langfuse.Prompt.get("my-prompt")
{:ok, prompt} = Langfuse.Prompt.get("my-prompt", version: 2)
{:ok, prompt} = Langfuse.Prompt.get("my-prompt", label: "production")
compiled = Langfuse.Prompt.compile(prompt, %{name: "Alice", topic: "weather"})
generation = Langfuse.generation(trace,
name: "chat",
prompt_name: prompt.name,
prompt_version: prompt.version,
input: compiled
)Prompts are cached by default. To invalidate:
Langfuse.Prompt.invalidate("my-prompt")
Langfuse.Prompt.invalidate("my-prompt", version: 2)
Langfuse.Prompt.invalidate_all()Use fallback prompts when fetch fails:
fallback = %Langfuse.Prompt{
name: "my-prompt",
prompt: "Default template: {{name}}",
type: :text
}
{:ok, prompt} = Langfuse.Prompt.get("my-prompt", fallback: fallback)Score traces, observations, or sessions:
Langfuse.score(trace, name: "quality", value: 0.85)
Langfuse.score(trace,
name: "sentiment",
string_value: "positive",
data_type: :categorical
)
Langfuse.score(trace,
name: "hallucination",
value: false,
data_type: :boolean
)
Langfuse.score(trace,
name: "feedback",
value: 5,
comment: "Excellent response",
metadata: %{reviewer: "human"}
)This SDK covers the core Langfuse API. See the Langfuse API Reference for full documentation.
| Feature | Function | Status |
|---|---|---|
| Create trace | Langfuse.trace/1 |
Supported |
| Create span | Langfuse.span/2 |
Supported |
| Create generation | Langfuse.generation/2 |
Supported |
| Create event | Langfuse.event/2 |
Supported |
| Create score | Langfuse.score/2 |
Supported |
| Update observation | Langfuse.update/2 |
Supported |
| End observation | Langfuse.end_observation/1 |
Supported |
| Batch ingestion | Langfuse.Ingestion |
Supported |
| Operation | Function | Status |
|---|---|---|
| Get prompt | Client.get_prompt/2 |
Supported |
| List prompts | Client.list_prompts/1 |
Supported |
| Create prompt | Client.create_prompt/1 |
Supported |
| Update labels | Client.update_prompt_labels/3 |
Supported |
| Operation | Function | Status |
|---|---|---|
| Create dataset | Client.create_dataset/1 |
Supported |
| Get dataset | Client.get_dataset/1 |
Supported |
| List datasets | Client.list_datasets/1 |
Supported |
| Delete dataset | Client.delete_dataset/1 |
Supported |
| Operation | Function | Status |
|---|---|---|
| Create item | Client.create_dataset_item/1 |
Supported |
| Get item | Client.get_dataset_item/1 |
Supported |
| Update item | Client.update_dataset_item/2 |
Supported |
| List items | Client.list_dataset_items/1 |
Supported |
| Delete item | Client.delete_dataset_item/1 |
Supported |
| Operation | Function | Status |
|---|---|---|
| Create run | Client.create_dataset_run/1 |
Supported |
| Get run | Client.get_dataset_run/2 |
Supported |
| List runs | Client.list_dataset_runs/2 |
Supported |
| Delete run | Client.delete_dataset_run/2 |
Supported |
| Create run item | Client.create_dataset_run_item/1 |
Supported |
| List run items | Client.list_dataset_run_items/1 |
Supported |
| Operation | Function | Status |
|---|---|---|
| Get trace | Client.get_trace/1 |
Supported |
| List traces | Client.list_traces/1 |
Supported |
| Get session | Client.get_session/1 |
Supported |
| List sessions | Client.list_sessions/1 |
Supported |
| Operation | Function | Status |
|---|---|---|
| Get observation | Client.get_observation/1 |
Supported |
| List observations | Client.list_observations/1 |
Supported |
| Operation | Function | Status |
|---|---|---|
| Create score | Langfuse.score/2 |
Supported |
| Get score | Client.get_score/1 |
Supported |
| List scores | Client.list_scores/1 |
Supported |
| Delete score | Client.delete_score/1 |
Supported |
| Operation | Function | Status |
|---|---|---|
| Create config | Client.create_score_config/1 |
Supported |
| Get config | Client.get_score_config/1 |
Supported |
| List configs | Client.list_score_configs/1 |
Supported |
| Operation | Function | Status |
|---|---|---|
| Create model | Client.create_model/1 |
Supported |
| Get model | Client.get_model/1 |
Supported |
| List models | Client.list_models/1 |
Supported |
| Delete model | Client.delete_model/1 |
Supported |
| Operation | Function | Status |
|---|---|---|
| Auth check | Langfuse.auth_check/0 |
Supported |
| Health check | Client.get("/api/public/health") |
Via raw API |
The following Langfuse API features are not yet implemented but can be accessed via Client.get/2, Client.post/2, Client.patch/2, and Client.delete/1:
- Annotation Queues
- Comments
- Media (file uploads)
- Metrics
- Projects management
- Organizations management
- SCIM provisioning
{:ok, _} = Langfuse.auth_check()
{:ok, dataset} = Langfuse.Client.create_dataset(name: "eval-set")
{:ok, datasets} = Langfuse.Client.list_datasets()
{:ok, item} = Langfuse.Client.create_dataset_item(
dataset_name: "eval-set",
input: %{query: "test"},
expected_output: %{answer: "response"}
)
{:ok, _} = Langfuse.Client.update_dataset_item(item["id"], status: "ARCHIVED")
{:ok, run} = Langfuse.Client.create_dataset_run(
dataset_name: "eval-set",
name: "experiment-1"
)
{:ok, model} = Langfuse.Client.create_model(
model_name: "gpt-4-turbo",
match_pattern: "(?i)^(gpt-4-turbo)$",
input_price: 0.01,
output_price: 0.03,
unit: "TOKENS"
)
{:ok, models} = Langfuse.Client.list_models()
{:ok, observations} = Langfuse.Client.list_observations(trace_id: trace.id)
{:ok, observation} = Langfuse.Client.get_observation(observation_id)
{:ok, prompt} = Langfuse.Client.get_prompt("my-prompt", version: 1)
{:ok, config} = Langfuse.Client.create_score_config(
name: "quality",
data_type: "NUMERIC",
min_value: 0,
max_value: 1
)Use macros for automatic function tracing:
defmodule MyApp.Agent do
use Langfuse.Instrumentation
@trace name: "agent-run"
def run(input) do
process(input)
end
@span name: "process-step", type: :chain
def process(input) do
call_llm(input)
end
@generation name: "llm-call", model: "gpt-4"
def call_llm(input) do
# LLM call here
end
endFor applications using OpenTelemetry, Langfuse can receive spans via a custom span processor:
config :opentelemetry,
span_processor: {Langfuse.OpenTelemetry.SpanProcessor, []}Or configure programmatically:
Langfuse.OpenTelemetry.Setup.configure()Map OpenTelemetry attributes to Langfuse fields:
:otel_tracer.with_span "llm-call", %{attributes: %{
"langfuse.type" => "generation",
"langfuse.model" => "gpt-4",
"langfuse.input" => Jason.encode!(messages),
"langfuse.output" => Jason.encode!(response)
}} do
# Your code here
endSee Langfuse.OpenTelemetry for full documentation.
Redact sensitive data before sending to Langfuse:
config :langfuse,
mask_fn: &MyApp.Masking.mask/1defmodule MyApp.Masking do
def mask(data) do
Langfuse.Masking.mask(data,
patterns: [
~r/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/,
~r/\b\d{3}-\d{2}-\d{4}\b/
],
replacement: "[REDACTED]"
)
end
endOr use the built-in masking:
config :langfuse,
mask_fn: {Langfuse.Masking, :mask, [[
patterns: [~r/secret_\w+/i],
keys: ["password", "api_key", "token"]
]]}The SDK emits telemetry events for observability:
| Event | Measurements | Metadata |
|---|---|---|
[:langfuse, :ingestion, :flush, :start|:stop|:exception] |
duration |
batch_size |
[:langfuse, :http, :request, :start|:stop|:exception] |
duration |
method, path, status |
[:langfuse, :prompt, :fetch, :start|:stop|:exception] |
duration |
name, version |
[:langfuse, :prompt, :cache, :hit|:miss] |
- | name, version |
:telemetry.attach(
"langfuse-logger",
[:langfuse, :http, :request, :stop],
fn _event, measurements, metadata, _config ->
duration_ms = System.convert_time_unit(measurements.duration, :native, :millisecond)
Logger.info("Langfuse HTTP #{metadata.method} #{metadata.path}: #{duration_ms}ms")
end,
nil
)
Langfuse.Telemetry.attach_default_logger()The SDK provides helpers for testing applications that use Langfuse:
config :langfuse, enabled: falsedefmodule MyApp.TracingTest do
use ExUnit.Case
import Langfuse.Testing
setup do
start_supervised!({Langfuse.Testing.EventCapture, []})
:ok
end
test "traces are created" do
MyApp.Agent.run("test input")
assert_traced("agent-run")
assert_generation_created("llm-call", model: "gpt-4")
end
endFor mocking HTTP calls:
Mox.defmock(Langfuse.HTTPMock, for: Langfuse.HTTPBehaviour)
config :langfuse, http_client: Langfuse.HTTPMockThe SDK automatically flushes pending events on application shutdown. For explicit control:
Langfuse.flush()
Langfuse.flush(timeout: 10_000)
Langfuse.shutdown()Reload configuration at runtime (useful for feature flags):
Application.put_env(:langfuse, :enabled, false)
Langfuse.Config.reload()MIT License - see LICENSE for details.