---
source: ⚠️ Jupyter Notebook
title: Observability for Flue with Langfuse
sidebarTitle: Flue
logo: /images/integrations/flue_icon.svg
description: Learn how to trace Flue agents and workflows with Langfuse via OpenTelemetry.
category: Integrations
---

# Trace Flue agents with Langfuse

This notebook shows how to integrate **Langfuse** with **[Flue](https://flueframework.com)** to trace, debug, and evaluate your agents and workflows via OpenTelemetry.

> **What is Flue?**\
> [Flue](https://flueframework.com) is an open-source TypeScript framework for building durable AI agents and workflows. It gives any model a harness-style runtime — sessions, tools, skills, sandboxed filesystem and shell access, and durable execution — so you can write agents once and run them anywhere (Node.js, Cloudflare, CI).

> **What is Langfuse?**\
> [Langfuse](https://langfuse.com) is an open-source LLM engineering platform that helps teams trace, debug, and evaluate their LLM applications.

<Steps>
## Step 1: Install dependencies

Flue ships a dedicated [`@flue/opentelemetry`](https://github.com/withastro/flue/tree/main/packages/opentelemetry) adapter that turns its public `observe(...)` event stream into OpenTelemetry spans. Pair it with the Langfuse OpenTelemetry SDK ([`@langfuse/otel`](https://langfuse.com/docs/observability/sdk/overview#setup)) to export those spans to Langfuse.

```bash
npm install @flue/runtime @flue/opentelemetry @langfuse/otel @opentelemetry/sdk-node @opentelemetry/api hono valibot
npm install --save-dev @flue/cli
```

> If you don't have a Flue project yet, scaffold one first with `npx flue init --target node` and follow the [Flue quickstart](https://flueframework.com/docs/getting-started/quickstart/).

## Step 2: Configure environment

Get your Langfuse API keys from your project settings in [Langfuse Cloud](https://langfuse.com/cloud) or your [self-hosted](https://langfuse.com/self-hosting) instance, and add them to a `.env` file in your project root together with your model provider key.

```bash
# .env
# Get keys from your project settings: https://langfuse.com/cloud
LANGFUSE_PUBLIC_KEY="pk-lf-..."
LANGFUSE_SECRET_KEY="sk-lf-..."
LANGFUSE_BASE_URL="https://cloud.langfuse.com" # 🇪🇺 EU region
# LANGFUSE_BASE_URL="https://us.cloud.langfuse.com" # 🇺🇸 US region

# Model provider used by your Flue agents
OPENAI_API_KEY="sk-proj-..."
```

## Step 3: Register the Langfuse OpenTelemetry observer

`@flue/opentelemetry` only converts Flue events into spans — your application owns the OpenTelemetry SDK and exporter. Configure the SDK with the Langfuse [`LangfuseSpanProcessor`](https://langfuse.com/docs/observability/sdk/overview#setup) and register the observer in your Flue app's entrypoint (`src/app.ts`).

Flue's model-turn spans follow the [OpenTelemetry GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/), so Langfuse maps them to generations with model, token usage, and cost automatically. The other Flue spans (workflow, operation, tool, task) are emitted under the `@flue/opentelemetry` instrumentation scope, so we extend the default Langfuse span filter to [include that scope](https://langfuse.com/docs/observability/sdk/advanced-features#filtering-by-instrumentation-scope).

```typescript
// src/app.ts
import { NodeSDK } from "@opentelemetry/sdk-node";
import { LangfuseSpanProcessor, isDefaultExportSpan } from "@langfuse/otel";
import { createOpenTelemetryObserver } from "@flue/opentelemetry";
import { observe } from "@flue/runtime";
import { flue } from "@flue/runtime/routing";
import { Hono } from "hono";

const sdk = new NodeSDK({
  spanProcessors: [
    new LangfuseSpanProcessor({
      // Export Langfuse's default LLM spans plus the full Flue span tree
      // (workflow / operation / tool / task) emitted by @flue/opentelemetry.
      shouldExportSpan: ({ otelSpan }) =>
        isDefaultExportSpan(otelSpan) ||
        otelSpan.instrumentationScope.name === "@flue/opentelemetry",
    }),
  ],
});

sdk.start();

// Flush buffered spans on exit: the one-shot `flue run` path (the event
// loop drains) and Ctrl+C on the long-running `flue dev` server (signals).
for (const signal of ["beforeExit", "SIGINT", "SIGTERM"] as const) {
  process.once(signal, async () => {
    await sdk.shutdown();
    if (signal !== "beforeExit") process.exit(0);
  });
}

// Register the observer after sdk.start() so it uses the configured tracer.
// exportContent forwards prompts/responses/tool payloads so they appear in
// each observation's Input/Output; return a sanitized event in production.
observe(createOpenTelemetryObserver({ exportContent: (event) => event }));

const app = new Hono();
app.route("/", flue());

export default app;
```

> **Capturing prompts and responses:** the `exportContent` callback in the snippet above forwards full content — prompts, completions, and tool payloads — which Langfuse maps to the corresponding observation's **Input** and **Output** (model turns become **Generation** observations, tool calls become **Tool** observations, and delegated tasks become **Agent** observations). Omit the callback to export only metadata (identifiers, durations, model, token usage, and cost); in production, return a sanitized event from `exportContent` to redact sensitive data before it leaves your app.

## Step 4: Add an example workflow

Flue discovers workflows from `src/workflows/`. This one runs an agent that uses a tool — each model turn and tool call becomes a span in the trace.

```typescript
// src/workflows/weather.ts
import {
  createAgent,
  defineTool,
  type FlueContext,
  type WorkflowRouteHandler,
} from "@flue/runtime";
import * as v from "valibot";

export const route: WorkflowRouteHandler = async (_c, next) => next();

const agent = createAgent(() => ({ model: "openai/gpt-4o-mini" }));

const lookupWeather = defineTool({
  name: "lookup_weather",
  description: "Look up current weather for a city.",
  parameters: v.object({ city: v.string() }),
  execute: async ({ city }) => `${city}: sunny, 72 F`,
});

export async function run({ init, payload }: FlueContext<{ city?: string }>) {
  const harness = await init(agent);
  const session = await harness.session();
  const city = typeof payload.city === "string" ? payload.city : "San Francisco";
  const response = await session.prompt(
    `Use the weather tool to report current weather in ${city}.`,
    { tools: [lookupWeather] },
  );
  return { message: response.text };
}
```

## Step 5: Run your agent

Start the Flue dev server:

```bash
npx flue dev --target node
```

In another terminal, invoke the workflow and wait for the result:

```bash
curl -X POST "http://localhost:3583/workflows/weather?wait=result" \
  -H "content-type: application/json" \
  -d '{"city":"Berlin"}'
```

You can also run a workflow once without the server with `npx flue run weather --target node --payload '{"city":"Berlin"}'`.

## Step 6: View traces in Langfuse

After running the workflow, open your [Langfuse dashboard](https://langfuse.com/cloud) to see the full trace: the workflow span, each model turn mapped to a generation (with model, token usage, and cost), and tool calls captured as nested **Tool** observations with their inputs and outputs.

![Flue example trace in Langfuse](https://langfuse.com/images/cookbook/integration-flue/flue-example-trace.png)

[Example trace in Langfuse](https://cloud.langfuse.com/project/cloramnkj0002jz088vzn1ja4/traces/44bd24918fb224f50b88dc23de15cc01)

</Steps>

## Interoperability with the JS/TS SDK

You can use this integration together with the Langfuse [SDKs](/docs/observability/sdk/overview) to add additional attributes or group observations into a single trace.

<Tabs items={["Context Manager", "Observe Wrapper"]}>
<Tab>

The [Context Manager](/docs/observability/sdk/instrumentation#context-management-with-callbacks) allows you to wrap your instrumented code using context managers (with `with` statements), which allows you to add additional attributes to the trace. Any observation created inside the callback will automatically be nested under the active observation, and the observation will be ended when the callback finishes.

```typescript
import { startActiveObservation, propagateAttributes } from "npm:@langfuse/tracing";

await startActiveObservation("context-manager", async (span) => {
  span.update({
    input: { query: "What is the capital of France?" },
  });

  // Propagate userId to all child observations
  await propagateAttributes(
    {
      userId: "user-123",
      sessionId: "session-123",
      metadata: {
        source: "api",
        region: "us-east-1",
      },
      tags: ["api", "user"],
      version: "1.0.0",
    },
    async () => {

      // YOUR CODE HERE
      const { text } = await generateText({
        model: openai("gpt-5"),
        prompt: "What is the capital of France?",
        experimental_telemetry: { isEnabled: true },
      });
    }
  );
  span.update({ output: "Paris" });
});
```

Learn more about using the Context Manager in the [Langfuse SDK instrumentation docs](https://langfuse.com/docs/observability/sdk/instrumentation#context-management-with-callbacks).

</Tab>
<Tab>

The [`observe` wrapper](/docs/observability/sdk/instrumentation#observe-wrapper) is a powerful tool for tracing existing functions without modifying their internal logic. It acts as a decorator that automatically creates a span or generation around the function call. You can use the `propagateAttributes` function to add attributes to the observation from within the wrapped function.

```typescript
import { observe, propagateAttributes } from "@langfuse/tracing";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";

// An existing function
const processUserRequest = observe(
  async (userQuery: string) => {

    // Propagate attributes to all child observations
    return await propagateAttributes(
      {
        userId: "user-123",
        sessionId: "session-123",
        metadata: {
          source: "api",
          region: "us-east-1",
        },
        tags: ["api", "user"],
        version: "1.0.0",
      },
      async () => {

        // YOUR CODE HERE
        const { text } = await generateText({
          model: openai("gpt-5"),
          prompt: userQuery,
          experimental_telemetry: { isEnabled: true },
        });

        return text;
      }
    );
  },
  { name: "process-user-request" }
);

const result = await processUserRequest("some query");
```

Learn more about using the Decorator in the [Langfuse SDK instrumentation docs](/docs/observability/sdk/instrumentation#observe-wrapper).

</Tab>
</Tabs>

## Troubleshooting

<details>
<summary>No traces appearing</summary>

First, enable [debug mode](/docs/observability/sdk/advanced-features#logging--debugging) in the JS/TS SDK:

```bash
export LANGFUSE_LOG_LEVEL="DEBUG"
```

Then run your application and check the debug logs:

- **OTel spans appear in the logs:** Your application is instrumented correctly but traces are not reaching Langfuse. To resolve this:
  1. Call [`forceFlush()`](/docs/observability/sdk/instrumentation#client-lifecycle--flushing) at the end of your application to ensure all traces are exported. This is especially important in short-lived environments like serverless functions.
  2. Verify that you are using the correct API keys and base URL.
- **No OTel spans in the logs:** Your application is not instrumented correctly. Make sure the instrumentation runs before your application code.

</details>

<details>
<summary>Unwanted observations in Langfuse</summary>

The Langfuse SDK is based on OpenTelemetry. Other libraries in your application may emit OTel spans that are not relevant to you. These still count toward your [billable units](/docs/administration/billable-units), so you should filter them out. See [Unwanted spans in Langfuse](/faq/all/unwanted-http-database-spans) for details.

</details>

<details>
<summary>Missing attributes</summary>

Some attributes may be stored in the metadata object of the observation rather than being mapped to the Langfuse data model. If a mapping or integration does not work as expected, please [raise an issue on GitHub](/issues).

</details>

## Next Steps

Once you have instrumented your code, you can manage, evaluate and debug your application:

- [Manage prompts in Langfuse](/docs/prompts/get-started)
- [Add evaluation scores](/docs/evaluation/features/evaluation-methods/custom-scores)
- [Run LLM-as-a-judge Evaluators](/docs/scores/model-based-evals)
- [Create datasets](/docs/datasets/overview)
- [Create custom dashboards](/docs/analytics/custom-dashboards)
- [Test queries in the Playground](/docs/playground)

<!-- agent-instructions -->

---

## Agent Instructions

This page is part of the [Langfuse](https://langfuse.com) documentation, published as plain Markdown for AI agents. Every page is available as Markdown by appending `.md` to its URL, or by sending an `Accept: text/markdown` header. This page: `https://langfuse.com/integrations/frameworks/flue.md`.

### Querying these docs

If the answer is not on this page, query the documentation instead of guessing:

- **Semantic search** across all Langfuse docs, returning an answer with the relevant pages and excerpts. Ask a specific, self-contained question:

  ```bash
  curl -sG "https://langfuse.com/api/search-docs" --data-urlencode "query=How do I trace a LangGraph agent?"
  ```

- **Index of every page**: <https://langfuse.com/llms.txt>, with per-section indexes [llms-docs.txt](https://langfuse.com/llms-docs.txt), [llms-integrations.txt](https://langfuse.com/llms-integrations.txt), and [llms-self-hosting.txt](https://langfuse.com/llms-self-hosting.txt).

### Before writing Langfuse code

- **Install the [Langfuse Agent Skill](https://langfuse.com/docs/api-and-data-platform/features/agent-skill).** It encodes Langfuse's own best practices for instrumentation, prompt management, and evaluation, and materially improves results.
- **Read [What does a good trace look like?](https://langfuse.com/docs/observability/best-practices.md)** before instrumenting an application.
- **Verify endpoints, parameters, and response fields** against the [API reference](https://api.reference.langfuse.com) instead of inferring them from code examples.
- **Use the [Langfuse CLI](https://langfuse.com/docs/api-and-data-platform/features/cli)** (`npx langfuse-cli api <resource> <action>`) to read or write traces, prompts, datasets, and scores from the terminal.

Found an error in these docs? Please open an issue at <https://github.com/langfuse/langfuse-docs/issues>.
