# Langfuse > title: A/B Testing of LLM Prompts --- # Source: https://langfuse.com/docs/prompt-management/features/a-b-testing.md --- title: A/B Testing of LLM Prompts sidebarTitle: A/B Testing description: Use Open Source Prompt Management in Langfuse to systematically test and improve your LLM prompts with A/B testing. --- # A/B Testing of LLM Prompts [Langfuse Prompt Management](/docs/prompts/get-started) enables A/B testing by allowing you to label different versions of a prompt (e.g., `prod-a` and `prod-b`). Your application can randomly alternate between these versions, while Langfuse tracks performance metrics like response latency, cost, token usage, and evaluation metrics for each version. **When to use A/B testing?** A/B testing helps you see how different prompt versions work in real situations, adding to what you learn from testing on datasets. This works best when: - Your app has good ways to measure success, deals with many different kinds of user inputs, and can handle some ups and downs in performance. This usually works for consumer apps where mistakes aren't a big deal. - You've already tested thoroughly on your test data and want to try your changes with a small group of users before rolling out to everyone (also called canary deployment). ## Implementation ### Label your Prompt Versions Label your prompt versions (e.g., `prod-a` and `prod-b`) to identify different variants for testing. ### Fetch Prompts and Run A/B Test ```python from langfuse import get_client import random from langfuse.openai import openai # Requires environment variables for initialization from langfuse import get_client langfuse = get_client() # Fetch prompt versions prompt_a = langfuse.get_prompt("my-prompt-name", label="prod-a") prompt_b = langfuse.get_prompt("my-prompt-name", label="prod-b") # Randomly select version selected_prompt = random.choice([prompt_a, prompt_b]) # Use in LLM call response = openai.chat.completions.create( model="gpt-3.5-turbo", messages=[{"role": "user", "content": selected_prompt.compile(variable="value")}], # Link prompt to generation for analytics langfuse_prompt=selected_prompt ) result_text = response.choices[0].message.content ``` ```js import { LangfuseClient } from "@langfuse/client"; import { observeOpenAI } from "@langfuse/openai"; import OpenAI from "openai"; // Requires environment variables for initialization const langfuse = new LangfuseClient(); // Create and wrap OpenAI client const openai = observeOpenAI(new OpenAI()); // Fetch prompt versions const promptA = await langfuse.prompt.get("my-prompt-name", { label: "prod-a", }); const promptB = await langfuse.prompt.get("my-prompt-name", { label: "prod-b", }); // Randomly select version const selectedPrompt = Math.random() < 0.5 ? promptA : promptB; // Use in LLM call const completion = await openai.chat.completions.create({ model: "gpt-3.5-turbo", messages: [ { role: "user", content: selectedPrompt.compile({ variable: "value" }), }, ], // Link prompt to generation for analytics langfusePrompt: selectedPrompt, }); const resultText = completion.choices[0].message.content; ``` Refer to [prompt management documentation](/docs/prompts/get-started) for additional examples on how to fetch and use prompts. ### Analyze Results Compare metrics for each prompt version in the Langfuse UI: --- # Source: https://langfuse.com/self-hosting/administration.md --- title: Administration Overview (self-hosted) description: Comprehensive overview of administration capabilities for self-hosted Langfuse deployments. label: "Version: v3" sidebarTitle: Overview --- # Administration Overview (self-hosted) Self-hosted Langfuse provides comprehensive administration capabilities to manage your deployment, users, organizations, and data. Please familiarize yourself with the [RBAC](/docs/administration/rbac) documentation before using the following features. Some of these features are only available in the [Enterprise Edition](/pricing-self-host) and are marked with `(EE)`. ## User & Access Management - **[Automated Access Provisioning](/self-hosting/administration/automated-access-provisioning)**: Auto-assign new users to a default organization/project - **[Organization Creators](/self-hosting/administration/organization-creators)** (EE): Restrict who can create organizations - **[Admin API (SCIM)](/docs/administration/scim-and-org-api)**: Enterprise user provisioning and bulk operations ## Organization & Project Management - **[Headless Initialization](/self-hosting/administration/headless-initialization)**: Automate resource creation (single org, project, user, apikey) via environment variables, e.g., for CI/CD. - **[Instance Management API](/self-hosting/administration/instance-management-api)** (EE): REST API for programmatic organization management - **[Admin API](/docs/administration/scim-and-org-api)**: Manage projects via the organization-scoped admin API ## Interface & Branding - **[UI Customization](/self-hosting/administration/ui-customization)** (EE): Co-branding, custom links, module visibility, LLM API defaults ## Data & Security - **[Audit Logs](/docs/administration/audit-logs)** (EE): Comprehensive activity tracking for compliance - **[Data Deletion](/docs/administration/data-deletion)**: Flexible data removal (traces, projects, organizations, users) - **[Data Retention](/docs/administration/data-retention)**: Automated lifecycle management with configurable policies ## APIs & Monitoring - **[LLM Connections](/docs/administration/llm-connection)**: Manage connections to OpenAI, Anthropic, Azure, and more. Either via the UI or the API. --- # Source: https://langfuse.com/docs/observability/sdk/advanced-features.md --- title: Advanced features of the Langfuse SDKs description: Configure masking, logging, sampling, multi-project routing, evaluations, and environment-specific behaviors for Python and JS/TS. category: SDKs --- # Advanced features Use these methods to harden your Langfuse instrumentation, protect sensitive data, and adapt the SDKs to your specific environment. ## Filtering by Instrumentation Scope [#filtering-by-instrumentation-scope] You can configure the SDK to filter out spans from specific instrumentation libraries that expose OTel spans that the Langfuse SDK picks up but you don't want to send to Langfuse. **How it works:** When third-party libraries create OpenTelemetry spans (through their instrumentation packages), each span has an associated "instrumentation scope" that identifies which library created it. The Langfuse SDK filters spans at the export level based on these scope names. You can see the instrumentation scope name for any span in the Langfuse UI under the observation's metadata (`metadata.scope.name`). Use this to identify which scopes you want to filter. **Cross-Library Span Relationships:** Filtering spans may break the parent-child relationships in your traces. For example, if you filter out a parent span but keep its children, you may see "orphaned" observations in the Langfuse UI. Provide the `blocked_instrumentation_scopes` parameter to the `Langfuse` client to filter out spans from specific instrumentation libraries. ```python from langfuse import Langfuse # Filter out database spans langfuse = Langfuse( blocked_instrumentation_scopes=["sqlalchemy", "psycopg"] ) ``` You can provide a predicate function `shouldExportSpan` to the `LangfuseSpanProcessor` to decide on a per-span basis whether it should be exported to Langfuse. ```ts filename="instrumentation.ts" /shouldExportSpan/ import { NodeSDK } from "@opentelemetry/sdk-node"; import { LangfuseSpanProcessor, ShouldExportSpan } from "@langfuse/otel"; // Example: Filter out all spans from the 'express' instrumentation const shouldExportSpan: ShouldExportSpan = ({ otelSpan }) => otelSpan.instrumentationScope.name !== "express"; const sdk = new NodeSDK({ spanProcessors: [new LangfuseSpanProcessor({ shouldExportSpan })], }); sdk.start(); ``` If you want to include only LLM observability related spans, you can configure an allowlist like so: ```ts filename="instrumentation.ts" import { ShouldExportSpan } from "@langfuse/otel"; const shouldExportSpan: ShouldExportSpan = ({ otelSpan }) => ["langfuse-sdk", "ai"].includes(otelSpan.instrumentationScope.name); ``` You can read more about using Langfuse with an existing OpenTelemetry setup [here](/faq/all/existing-otel-setup). ## Mask sensitive data If your trace data (inputs, outputs, metadata) might contain sensitive information (PII, secrets), you can provide a mask function during client initialization. This function will be applied to all relevant data before it’s sent to Langfuse. The `mask` function should accept data as a keyword argument and return the masked data. The returned data must be JSON-serializable. ```python from langfuse import Langfuse import re def pii_masker(data: any, **kwargs) -> any: if isinstance(data, str): return re.sub(r"[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+", "[EMAIL_REDACTED]", data) elif isinstance(data, dict): return {k: pii_masker(data=v) for k, v in data.items()} elif isinstance(data, list): return [pii_masker(data=item) for item in data] return data langfuse = Langfuse(mask=pii_masker) ``` You can provide a `mask` function to the [`LangfuseSpanProcessor`](https://langfuse-js-git-main-langfuse.vercel.app/classes/_langfuse_otel.LangfuseSpanProcessor.html). This function will be applied to the input, output, and metadata of every observation. The function receives an object `{ data }`, where `data` is the stringified JSON of the attribute's value. It should return the masked data. ```ts filename="instrumentation.ts" /mask: / import { NodeSDK } from "@opentelemetry/sdk-node"; import { LangfuseSpanProcessor } from "@langfuse/otel"; const spanProcessor = new LangfuseSpanProcessor({ mask: ({ data }) => data.replace(/\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g, "***MASKED_CREDIT_CARD***"), }); const sdk = new NodeSDK({ spanProcessors: [spanProcessor] }); sdk.start(); ``` ## Logging & debugging The Langfuse SDK can expose detailed logging and debugging information to help you troubleshoot issues with your application. **In code:** The Langfuse SDK uses Python's standard `logging` module. The main logger is named `"langfuse"`. To enable detailed debug logging, you can either: 1. Set the `debug=True` parameter when initializing the `Langfuse` client. 2. Configure the `"langfuse"` logger manually: ```python import logging langfuse_logger = logging.getLogger("langfuse") langfuse_logger.setLevel(logging.DEBUG) ``` The default log level for the `langfuse` logger is `logging.WARNING`. **Via environment variable:** You can also set the log level using the `LANGFUSE_DEBUG` environment variable to enable the debug mode. ```bash export LANGFUSE_DEBUG="True" ``` You can configure the global SDK logger to control the verbosity of log output. This is useful for debugging. **In code:** ```typescript /configureGlobalLogger/ import { configureGlobalLogger, LogLevel } from "@langfuse/core"; // Set the log level to DEBUG to see all log messages configureGlobalLogger({ level: LogLevel.DEBUG }); ``` Available log levels are `DEBUG`, `INFO`, `WARN`, and `ERROR`. **Via environment variable:** You can also set the log level using the `LANGFUSE_LOG_LEVEL` environment variable to enable the debug mode. ```bash export LANGFUSE_LOG_LEVEL="DEBUG" ``` ## Sampling Sampling lets send only a subset of traces to Langfuse. This is useful to reduce costs and noise in high-volume applications. **In code:** You can configure the SDK to sample traces by setting the `sample_rate` parameter during client initialization. This value should be a float between `0.0` (sample 0% of traces) and `1.0` (sample 100% of traces). If a trace is not sampled, none of its observations (spans, generations) or associated scores will be sent to Langfuse. ```python from langfuse import Langfuse # Sample approximately 20% of traces langfuse_sampled = Langfuse(sample_rate=0.2) ``` **Via environment variable:** You can also set the sample rate using the `LANGFUSE_SAMPLE_RATE` environment variable. ```bash export LANGFUSE_SAMPLE_RATE="0.2" ``` **In code:** Langfuse respects OpenTelemetry's sampling decisions. Configure a sampler on your OTEL `NodeSDK` to control which traces reach Langfuse and reduce noise/costs in high-volume workloads. ```ts filename="instrumentation.ts" /TraceIdRatioBasedSampler/ import { NodeSDK } from "@opentelemetry/sdk-node"; import { LangfuseSpanProcessor } from "@langfuse/otel"; import { TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base"; const sdk = new NodeSDK({ sampler: new TraceIdRatioBasedSampler(0.2), spanProcessors: [new LangfuseSpanProcessor()], }); sdk.start(); ``` **Via environment variable:** You can also set the sample rate using the `LANGFUSE_SAMPLE_RATE` environment variable. ```bash export LANGFUSE_SAMPLE_RATE="0.2" ``` ## Isolated TracerProvider [#isolated-tracer-provider] You can configure a separate OpenTelemetry TracerProvider for use with Langfuse. This creates isolation between Langfuse tracing and your other observability systems. Benefits of isolation: - Langfuse spans won't be sent to your other observability backends (e.g., Datadog, Jaeger, Zipkin) - Third-party library spans won't be sent to Langfuse - Independent configuration and sampling rates While TracerProviders are isolated, they share the same OpenTelemetry context for tracking active spans. This can cause span relationship issues where: - A parent span from one TracerProvider might have children from another TracerProvider - Some spans may appear "orphaned" if their parent spans belong to a different TracerProvider - Trace hierarchies may be incomplete or confusing Plan your instrumentation carefully to avoid confusing trace structures. ```python {4, 5} from opentelemetry.sdk.trace import TracerProvider from langfuse import Langfuse langfuse_tracer_provider = TracerProvider() # do not set to global tracer provider to keep isolation langfuse = Langfuse(tracer_provider=langfuse_tracer_provider) langfuse.start_span(name="myspan").end() # Span will be isolated from remaining OTEL instrumentation ``` Isolate Langfuse spans with a custom provider and avoid sending them to other exporters. ```ts /setLangfuseTracerProvider/ import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node"; import { setLangfuseTracerProvider } from "@langfuse/tracing"; // Create a new TracerProvider and register the LangfuseSpanProcessor // do not set this TracerProvider as the global TracerProvider const langfuseTracerProvider = new NodeTracerProvider({ spanProcessors: [new LangfuseSpanProcessor()], }) // Register the isolated TracerProvider setLangfuseTracerProvider(langfuseTracerProvider) ``` You can read more about using Langfuse with an existing OpenTelemetry setup [here](/faq/all/existing-otel-setup). ## Multi-project setups [#multi-project-setup-experimental] Multi-project setups are **experimental** in the Python SDK and have important limitations regarding third-party OpenTelemetry integrations. The Langfuse Python SDK supports routing traces to different projects within the same application by using multiple public keys. This works because the Langfuse SDK adds a specific span attribute containing the public key to all spans it generates. **How it works:** 1. **Span Attributes**: The Langfuse SDK adds a specific span attribute containing the public key to spans it creates 2. **Multiple Processors**: Multiple span processors are registered onto the global tracer provider, each with their respective exporters bound to a specific public key 3. **Filtering**: Within each span processor, spans are filtered based on the presence and value of the public key attribute **Important Limitation with Third-Party Libraries:** Third-party libraries that emit OpenTelemetry spans automatically (e.g., HTTP clients, databases, other instrumentation libraries) do **not** have the Langfuse public key span attribute. As a result: - These spans cannot be routed to a specific project - They are processed by all span processors and sent to all projects - All projects will receive these third-party spans **Why is this experimental?** This approach requires that the `public_key` parameter be passed to all Langfuse SDK executions across all integrations to ensure proper routing, and third-party spans will appear in all projects. ### Initialization To set up multiple projects, initialize separate Langfuse clients for each project: ```python from langfuse import Langfuse # Initialize clients for different projects project_a_client = Langfuse( public_key="pk-lf-project-a-...", secret_key="sk-lf-project-a-...", base_url="https://cloud.langfuse.com" ) project_b_client = Langfuse( public_key="pk-lf-project-b-...", secret_key="sk-lf-project-b-...", base_url="https://cloud.langfuse.com" ) ``` ### Integration Usage For all integrations in multi-project setups, you must specify the `public_key` parameter to ensure traces are routed to the correct project. **Observe Decorator:** Pass `langfuse_public_key` as a keyword argument to the *top-most* observed function (not the decorator). From Python SDK >= 3.2.2, nested decorated functions will automatically pick up the public key from the execution context they are currently into. Also, calls to `get_client` will be also aware of the current `langfuse_public_key` in the decorated function execution context, so passing the `langfuse_public_key` here again is not necessary. ```python from langfuse import observe @observe def nested(): # get_client call is context aware # if it runs inside another decorated function that has # langfuse_public_key passed, it does not need passing here again @observe def process_data_for_project_a(data): # passing `langfuse_public_key` here again is not necessarily # as it is stored in execution context nested() return {"processed": data} @observe def process_data_for_project_b(data): # passing `langfuse_public_key` here again is not necessarily # as it is stored in execution context nested() return {"enhanced": data} # Route to Project A # Top-most decorated function needs `langfuse_public_key` kwarg result_a = process_data_for_project_a( data="input data", langfuse_public_key="pk-lf-project-a-..." ) # Route to Project B # Top-most decorated function needs `langfuse_public_key` kwarg result_b = process_data_for_project_b( data="input data", langfuse_public_key="pk-lf-project-b-..." ) ``` **OpenAI Integration:** Add `langfuse_public_key` as a keyword argument to the OpenAI execution: ```python from langfuse.openai import openai client = openai.OpenAI() # Route to Project A response_a = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "Hello from Project A"}], langfuse_public_key="pk-lf-project-a-..." ) # Route to Project B response_b = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "Hello from Project B"}], langfuse_public_key="pk-lf-project-b-..." ) ``` **Langchain Integration:** Add `public_key` to the CallbackHandler constructor: ```python from langfuse.langchain import CallbackHandler from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate # Create handlers for different projects handler_a = CallbackHandler(public_key="pk-lf-project-a-...") handler_b = CallbackHandler(public_key="pk-lf-project-b-...") llm = ChatOpenAI(model_name="gpt-4o") prompt = ChatPromptTemplate.from_template("Tell me about {topic}") chain = prompt | llm # Route to Project A response_a = chain.invoke( {"topic": "machine learning"}, config={"callbacks": [handler_a]} ) # Route to Project B response_b = chain.invoke( {"topic": "data science"}, config={"callbacks": [handler_b]} ) ``` **Important Considerations:** - Every Langfuse SDK execution across all integrations must include the appropriate public key parameter - Missing public key parameters may result in traces being routed to the default project or lost - Third-party OpenTelemetry spans (from HTTP clients, databases, etc.) will appear in all projects since they lack the Langfuse public key attribute You can configure the SDK to send traces to multiple Langfuse projects. This is useful for multi-tenant applications or for sending traces to different environments. Simply register multiple `LangfuseSpanProcessor` instances, each with its own credentials. ```ts filename="instrumentation.ts" import { NodeSDK } from "@opentelemetry/sdk-node"; import { LangfuseSpanProcessor } from "@langfuse/otel"; const sdk = new NodeSDK({ spanProcessors: [ new LangfuseSpanProcessor({ publicKey: "pk-lf-public-key-project-1", secretKey: "sk-lf-secret-key-project-1", }), new LangfuseSpanProcessor({ publicKey: "pk-lf-public-key-project-2", secretKey: "sk-lf-secret-key-project-2", }), ], }); sdk.start(); ``` This configuration will send every trace to both projects. You can also configure a custom `shouldExportSpan` filter for each processor to control which traces go to which project. ## Time to first token (TTFT) You can manually set the time to first token (TTFT) of your LLM calls. This is useful for measuring the latency of your LLM calls and for identifying slow LLM calls. You can use the `completion_start_time` attribute to manually set the time to first token (TTFT) of your LLM calls. This is useful for measuring the latency of your LLM calls and for identifying slow LLM calls. ```python from langfuse import get_client import datetime, time langfuse = get_client() with langfuse.start_as_current_observation(as_type="generation", name="TTFT-Generation") as generation: time.sleep(3) generation.update( completion_start_time=datetime.datetime.now(), output="some response", ) langfuse.flush() ``` You can use the `completionStartTime` attribute to manually set the time to first token (TTFT) of your LLM calls. This is useful for measuring the latency of your LLM calls and for identifying slow LLM calls. ```ts import { startActiveObservation } from "@langfuse/tracing"; startActiveObservation("llm-call", async (span) => { span.update({ completionStartTime: new Date().toISOString(), }); }); ``` ## Self-signed SSL certificates (self-hosted Langfuse) If you are [self-hosting](/docs/deployment/self-host) Langfuse and you'd like to use self-signed SSL certificates, you will need to configure the SDK to trust the self-signed certificate: Changing SSL settings has major security implications depending on your environment. Be sure you understand these implications before you proceed. **1. Set OpenTelemetry span exporter to trust self-signed certificate** ```bash filename=".env" OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE="/path/to/my-selfsigned-cert.crt" ``` **2. Set HTTPX to trust certificate for all other API requests to Langfuse instance** ```python filename="main.py" import os import httpx from langfuse import Langfuse httpx_client = httpx.Client(verify=os.environ["OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE"]) langfuse = Langfuse(httpx_client=httpx_client) ``` ## Setup with Sentry If you’re using both Sentry and Langfuse in your application, you’ll need to configure a custom OpenTelemetry setup since both tools use OpenTelemetry for tracing. [This guide shows how to send error monitoring data to Sentry while simultaneously capturing LLM observability traces in Langfuse](/faq/all/existing-sentry-setup). ## Thread pools and multiprocessing Use the OpenTelemetry threading instrumentor so context flows across worker threads. ```python from opentelemetry.instrumentation.threading import ThreadingInstrumentor ThreadingInstrumentor().instrument() ``` For multiprocessing, follow the [OpenTelemetry guidance](https://github.com/open-telemetry/opentelemetry-python/issues/2765#issuecomment-1158402076). If you use Pydantic Logfire, enable `distributed_tracing=True`. --- # Source: https://langfuse.com/docs/observability/sdk/typescript/advanced-usage.md # Source: https://langfuse.com/docs/observability/sdk/python/advanced-usage.md # Source: https://langfuse.com/docs/observability/sdk/typescript/advanced-usage.md # Source: https://langfuse.com/docs/observability/sdk/python/advanced-usage.md # Source: https://langfuse.com/docs/observability/sdk/typescript/advanced-usage.md # Source: https://langfuse.com/docs/observability/sdk/python/advanced-usage.md --- title: Advanced usage of the Langfuse Python SDK description: Advanced usage of the Langfuse Python SDK for data masking, logging, sampling, filtering, and more. category: SDKs --- # Advanced Usage The Python SDK provides advanced usage options for your application. This includes data masking, logging, sampling, filtering, and more. ## Masking Sensitive Data If your trace data (inputs, outputs, metadata) might contain sensitive information (PII, secrets), you can provide a `mask` function during client initialization. This function will be applied to all relevant data before it's sent to Langfuse. The `mask` function should accept `data` as a keyword argument and return the masked data. The returned data must be JSON-serializable. ```python from langfuse import Langfuse import re def pii_masker(data: any, **kwargs) -> any: # Example: Simple email masking. Implement your more robust logic here. if isinstance(data, str): return re.sub(r"[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+", "[EMAIL_REDACTED]", data) elif isinstance(data, dict): return {k: pii_masker(data=v) for k, v in data.items()} elif isinstance(data, list): return [pii_masker(data=item) for item in data] return data langfuse = Langfuse(mask=pii_masker) # Now, any input/output/metadata will be passed through pii_masker with langfuse.start_as_current_observation(as_type="span", name="user-query", input={"email": "test@example.com", "query": "..."}) as span: # The 'email' field in the input will be masked. pass ``` ## Logging The Langfuse SDK uses Python's standard `logging` module. The main logger is named `"langfuse"`. To enable detailed debug logging, you can either: 1. Set the `debug=True` parameter when initializing the `Langfuse` client. 2. Set the `LANGFUSE_DEBUG="True"` environment variable. 3. Configure the `"langfuse"` logger manually: ```python import logging langfuse_logger = logging.getLogger("langfuse") langfuse_logger.setLevel(logging.DEBUG) ``` The default log level for the `langfuse` logger is `logging.WARNING`. ## Sampling You can configure the SDK to sample traces by setting the `sample_rate` parameter during client initialization (or via the `LANGFUSE_SAMPLE_RATE` environment variable). This value should be a float between `0.0` (sample 0% of traces) and `1.0` (sample 100% of traces). If a trace is not sampled, none of its observations (spans, generations) or associated scores will be sent to Langfuse. ```python from langfuse import Langfuse # Sample approximately 20% of traces langfuse_sampled = Langfuse(sample_rate=0.2) ``` ## Filtering by Instrumentation Scope You can configure the SDK to filter out spans from specific instrumentation libraries by using the `blocked_instrumentation_scopes` parameter. This is useful when you want to exclude infrastructure spans while keeping your LLM and application spans. ```python from langfuse import Langfuse # Filter out database spans langfuse = Langfuse( blocked_instrumentation_scopes=["sqlalchemy", "psycopg"] ) ``` **How it works:** When third-party libraries create OpenTelemetry spans (through their instrumentation packages), each span has an associated "instrumentation scope" that identifies which library created it. The Langfuse SDK filters spans at the export level based on these scope names. You can see the instrumentation scope name for any span in the Langfuse UI under the span's metadata (`metadata.scope.name`). Use this to identify which scopes you want to filter. **Cross-Library Span Relationships** When filtering instrumentation scopes, be aware that blocking certain libraries may break trace tree relationships if spans from blocked and non-blocked libraries are nested together. For example, if you block parent spans but keep child spans from a separate library, you may see "orphaned" LLM spans whose parent spans were filtered out. This can make traces harder to interpret. Consider the impact on trace structure when choosing which scopes to filter. ## Isolated TracerProvider You can configure a separate OpenTelemetry TracerProvider for use with Langfuse. This creates isolation between Langfuse tracing and your other observability systems. **Benefits of isolation:** - Langfuse spans won't be sent to your other observability backends (e.g., Datadog, Jaeger, Zipkin) - Third-party library spans won't be sent to Langfuse - Independent configuration and sampling rates While TracerProviders are isolated, they share the same OpenTelemetry context for tracking active spans. This can cause span relationship issues where: - A parent span from one TracerProvider might have children from another TracerProvider - Some spans may appear "orphaned" if their parent spans belong to a different TracerProvider - Trace hierarchies may be incomplete or confusing Plan your instrumentation carefully to avoid confusing trace structures. ```python from opentelemetry.sdk.trace import TracerProvider from langfuse import Langfuse langfuse_tracer_provider = TracerProvider() # do not set to global tracer provider to keep isolation langfuse = Langfuse(tracer_provider=langfuse_tracer_provider) langfuse.start_span(name="myspan").end() # Span will be isolated from remaining OTEL instrumentation ``` ## Using `ThreadPoolExecutors` Please use the [OpenTelemetry ThreadingInstrumentor](https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/threading/threading.html) to ensure that the OpenTelemetry context is correctly propagated to all threads. ```python filename="main.py" from opentelemetry.instrumentation.threading import ThreadingInstrumentor ThreadingInstrumentor().instrument() ``` ## Distributed tracing To maintain the trace context across service / process boundaries, please rely on the OpenTelemetry native [context propagation](https://opentelemetry.io/docs/concepts/context-propagation/) across service / process boundaries as much as possible. Using the `trace_context` argument to 'force' the parent child relationship may lead to unexpected trace updates as the resulting span will be treated as a root span server side. - If you are using multiprocessing, [see here for details on how to propagate the OpenTelemetry context](https://github.com/open-telemetry/opentelemetry-python/issues/2765#issuecomment-1158402076). - If you are using Pydantic Logfire, please set `distributed_tracing` to `True`. ## Multi-Project Setup (Experimental) Multi-project setups are **experimental** and have important limitations regarding third-party OpenTelemetry integrations. The Langfuse Python SDK supports routing traces to different projects within the same application by using multiple public keys. This works because the Langfuse SDK adds a specific span attribute containing the public key to all spans it generates. **How it works:** 1. **Span Attributes**: The Langfuse SDK adds a specific span attribute containing the public key to spans it creates 2. **Multiple Processors**: Multiple span processors are registered onto the global tracer provider, each with their respective exporters bound to a specific public key 3. **Filtering**: Within each span processor, spans are filtered based on the presence and value of the public key attribute **Important Limitation with Third-Party Libraries:** Third-party libraries that emit OpenTelemetry spans automatically (e.g., HTTP clients, databases, other instrumentation libraries) do **not** have the Langfuse public key span attribute. As a result: - These spans cannot be routed to a specific project - They are processed by all span processors and sent to all projects - All projects will receive these third-party spans **Why is this experimental?** This approach requires that the `public_key` parameter be passed to all Langfuse SDK executions across all integrations to ensure proper routing, and third-party spans will appear in all projects. ### Initialization To set up multiple projects, initialize separate Langfuse clients for each project: ```python from langfuse import Langfuse # Initialize clients for different projects project_a_client = Langfuse( public_key="pk-lf-project-a-...", secret_key="sk-lf-project-a-...", base_url="https://cloud.langfuse.com" ) project_b_client = Langfuse( public_key="pk-lf-project-b-...", secret_key="sk-lf-project-b-...", base_url="https://cloud.langfuse.com" ) ``` ### Integration Usage For all integrations in multi-project setups, you must specify the `public_key` parameter to ensure traces are routed to the correct project. **Observe Decorator:** Pass `langfuse_public_key` as a keyword argument to the *top-most* observed function (not the decorator). From Python SDK >= 3.2.2, nested decorated functions will automatically pick up the public key from the execution context they are currently into. Also, calls to `get_client` will be also aware of the current `langfuse_public_key` in the decorated function execution context, so passing the `langfuse_public_key` here again is not necessary. ```python from langfuse import observe @observe def nested(): # get_client call is context aware # if it runs inside another decorated function that has # langfuse_public_key passed, it does not need passing here again @observe def process_data_for_project_a(data): # passing `langfuse_public_key` here again is not necessarily # as it is stored in execution context nested() return {"processed": data} @observe def process_data_for_project_b(data): # passing `langfuse_public_key` here again is not necessarily # as it is stored in execution context nested() return {"enhanced": data} # Route to Project A # Top-most decorated function needs `langfuse_public_key` kwarg result_a = process_data_for_project_a( data="input data", langfuse_public_key="pk-lf-project-a-..." ) # Route to Project B # Top-most decorated function needs `langfuse_public_key` kwarg result_b = process_data_for_project_b( data="input data", langfuse_public_key="pk-lf-project-b-..." ) ``` **OpenAI Integration:** Add `langfuse_public_key` as a keyword argument to the OpenAI execution: ```python from langfuse.openai import openai client = openai.OpenAI() # Route to Project A response_a = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "Hello from Project A"}], langfuse_public_key="pk-lf-project-a-..." ) # Route to Project B response_b = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "Hello from Project B"}], langfuse_public_key="pk-lf-project-b-..." ) ``` **Langchain Integration:** Add `public_key` to the CallbackHandler constructor: ```python from langfuse.langchain import CallbackHandler from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate # Create handlers for different projects handler_a = CallbackHandler(public_key="pk-lf-project-a-...") handler_b = CallbackHandler(public_key="pk-lf-project-b-...") llm = ChatOpenAI(model_name="gpt-4o") prompt = ChatPromptTemplate.from_template("Tell me about {topic}") chain = prompt | llm # Route to Project A response_a = chain.invoke( {"topic": "machine learning"}, config={"callbacks": [handler_a]} ) # Route to Project B response_b = chain.invoke( {"topic": "data science"}, config={"callbacks": [handler_b]} ) ``` **Important Considerations:** - Every Langfuse SDK execution across all integrations must include the appropriate public key parameter - Missing public key parameters may result in traces being routed to the default project or lost - Third-party OpenTelemetry spans (from HTTP clients, databases, etc.) will appear in all projects since they lack the Langfuse public key attribute ## Passing `completion_start_time` for TTFT tracking If you are using the Python SDK to manually create generations, you can pass the `completion_start_time` parameter. This allows langfuse to calculate the time to first token (TTFT) for you. ```python from langfuse import get_client import datetime import time langfuse = get_client() # Start observation with specific type with langfuse.start_as_current_observation( as_type="generation", name="TTFT-Generation" ) as generation: # simulate LLM time to first token time.sleep(3) # Update the generation with the time the model started to generate generation.update( completion_start_time=datetime.datetime.now(), output="some response", ) # Flush events in short-lived applications langfuse.flush() ``` ## Self-signed SSL certificates (self-hosted Langfuse) If you are [self-hosting](/docs/deployment/self-host) Langfuse and you'd like to use self-signed SSL certificates, you will need to configure the SDK to trust the self-signed certificate: Changing SSL settings has major security implications depending on your environment. Be sure you understand these implications before you proceed. **1. Set OpenTelemetry span exporter to trust self-signed certificate** ```bash filename=".env" OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE="/path/to/my-selfsigned-cert.crt" ``` **2. Set HTTPX to trust certificate for all other API requests to Langfuse instance** ```python filename="main.py" import os import httpx from langfuse import Langfuse httpx_client = httpx.Client(verify=os.environ["OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE"]) langfuse = Langfuse(httpx_client=httpx_client) ``` ## Observation Types Langfuse supports multiple observation types to provide context for different components of LLM applications. The full list of the observation types is document here: [Observation types](/docs/observability/features/observation-types). ### Setting observation types with the `@observe` decorator By setting the `as_type` parameter in the `@observe` decorator, you can specify the observation type for a method: ```python /as_type="tool"/ from langfuse import observe # Tool calls to external services @observe(as_type="tool") def retrieve_context(query): results = vector_store.get(query) return results ``` ### Setting observation types with client methods and context manager With the Langfuse client, you can directly create an observation with a defined type: ```python /as_type="embedding"/ from langfuse import get_client() langfuse = get_client() def process_with_manual_tracing(): trace = langfuse.trace(name="document-processing") # Create different observation types embedding_obs = trace.start_observation( as_type="embedding", name="document-embedding", input={"document": "text content"} ) embeddings = generate_embeddings("text content") embedding_obs.update(output={"embeddings": embeddings}) embedding_obs.end() ``` The context manager approach provides automatic resource cleanup: ```python /as_type="chain"/ from langfuse import get_client langfuse = get_client() def process_with_context_managers(): with langfuse.start_as_current_observation( as_type="chain", name="retrieval-pipeline", ) as chain: # Retrieval step with langfuse.start_as_current_observation( as_type="retriever", name="vector-search", ) as retriever: search_results = perform_vector_search("user question") retriever.update(output={"results": search_results}) ``` --- # Source: https://langfuse.com/docs/observability/features/agent-graphs.md --- title: Agent Graphs description: Visualize and analyze complex agent workflows with Langfuse's agent graph view. sidebarTitle: Agent Graphs --- import { GhDiscussionsPreview } from "@/components/gh-discussions/GhDiscussionsPreview"; # Agent Graphs Agent graphs in Langfuse provide a visual representation of complex AI agent workflows, helping you understand and debug multi-step reasoning processes and agent interactions. _Example trace with agent graph view ([public link](https://cloud.langfuse.com/project/cloramnkj0002jz088vzn1ja4/traces/8ed12d68-353f-464f-bc62-720984c3b6a0))_