> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bentolabs.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Export from any framework

> Send traces to Bento from a framework that already speaks OpenTelemetry: Vercel AI SDK, Mastra, LangChain, LlamaIndex, and more.

Bento is an OpenTelemetry collector. If your agent framework already emits OpenTelemetry traces, point its exporter at one endpoint with one auth header and the traces land in Bento. In TypeScript you can do this with no Bento package at all.

## The endpoint

Every OpenTelemetry exporter needs two settings. Send to `${BENTOLABS_BASE_URL}/v1/traces` (the default base URL is `https://api.bentolabs.ai`), and add the header `Authorization: Bearer bl_pk_...` carrying your `BENTOLABS_API_KEY`.

<Warning>
  Bento's ingest accepts **OTLP/HTTP with a JSON payload** (gzip is fine), not protobuf. Configure your exporter for `http/json`. Some OpenTelemetry exporters default to protobuf, so check the per-language notes below.
</Warning>

Bento reads the standard `gen_ai.*` and `openinference.*` span attributes, so traces from these frameworks fill the same dashboard columns described in [Attributes](/concepts/attributes). Nothing about your spans is Bento-specific.

## TypeScript / JavaScript, no Bento SDK

JavaScript's OpenTelemetry exporters can emit JSON, so you can send traces to Bento without installing any Bento package.

### Vercel AI SDK

Wire a JSON OTLP exporter once, then turn on telemetry per call. Use `@vercel/otel`'s `OTLPHttpJsonTraceExporter`, which sends JSON as the name says:

```ts theme={null}
// instrumentation.ts
import { registerOTel, OTLPHttpJsonTraceExporter } from "@vercel/otel";

export function register() {
  registerOTel({
    serviceName: "my-app",
    traceExporter: new OTLPHttpJsonTraceExporter({
      url: `${process.env.BENTOLABS_BASE_URL ?? "https://api.bentolabs.ai"}/v1/traces`,
      headers: { Authorization: `Bearer ${process.env.BENTOLABS_API_KEY}` },
    }),
  });
}
```

```ts theme={null}
// then enable telemetry on each call
await generateText({
  model,
  prompt,
  experimental_telemetry: { isEnabled: true, functionId: "my-fn" },
});
```

The AI SDK sets `gen_ai.*` attributes (model, token counts, finish reasons) on its LLM spans, so the model, cost, and token columns fill in. Prompt and response text live under `ai.*`. To populate the input and output columns too, add the `@arizeai/openinference-vercel` span processor to the same setup.

### Mastra

Use Mastra's `OtelExporter` with a custom provider and `protocol: "http/json"`, which gives Bento the JSON it needs:

```ts theme={null}
import { OtelExporter } from "@mastra/otel-exporter";

new OtelExporter({
  provider: {
    custom: {
      endpoint: `${process.env.BENTOLABS_BASE_URL ?? "https://api.bentolabs.ai"}/v1/traces`,
      protocol: "http/json",
      headers: { Authorization: `Bearer ${process.env.BENTOLABS_API_KEY}` },
    },
  },
});
```

<Warning>
  Mastra's `ArizeExporter` preset maps spans to OpenInference, but it can send protobuf, which Bento doesn't accept, and it switches to Arize's own headers when `ARIZE_SPACE_ID` is set in the environment. Use the `OtelExporter` with `protocol: "http/json"` shown above. Whichever you pick, run one real flow afterward and confirm the six dashboard columns fill (see [Verify](#verify) below).
</Warning>

## Python, with the Bento span processor

Python's OpenTelemetry exporters send protobuf, which Bento doesn't accept, so Python has no no-SDK path. Install the Bento SDK and use its JSON span processor (`BentoLabsSpanProcessor`) together with your framework's [OpenInference](https://github.com/Arize-ai/openinference) instrumentor. Your call sites stay untouched.

```bash theme={null}
pip install bentolabs-sdk openinference-instrumentation-langchain
```

```python theme={null}
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from openinference.instrumentation.langchain import LangChainInstrumentor

from bentolabs_sdk import BentoLabsSpanProcessor  # sends JSON to Bento

provider = TracerProvider()
provider.add_span_processor(BentoLabsSpanProcessor())
trace.set_tracer_provider(provider)

LangChainInstrumentor().instrument(tracer_provider=provider)
```

Swap the instrumentor for your framework: `openinference-instrumentation-llama-index`, `-openai-agents`, `-crewai`, `-anthropic`, and so on. The processor reads `BENTOLABS_API_KEY` and `BENTOLABS_BASE_URL` from the environment. See [OTel transport](/python/otel-transport) for the processor details and [Attributes](/concepts/attributes) for how attributes map to columns.

This holds even for frameworks that emit OpenTelemetry natively, like Pydantic AI via `Agent.instrument_all()`. They still need a JSON exporter, so attach `BentoLabsSpanProcessor` rather than a stock OTLP exporter.

## Which framework do I use?

| Framework             | Language   | How to send to Bento                                                                     |
| --------------------- | ---------- | ---------------------------------------------------------------------------------------- |
| Vercel AI SDK         | TypeScript | `@vercel/otel` `OTLPHttpJsonTraceExporter`, no Bento SDK                                 |
| Mastra                | TypeScript | `OtelExporter` with `protocol: "http/json"`, no Bento SDK                                |
| LangChain / LangGraph | Python     | OpenInference instrumentor + `BentoLabsSpanProcessor`                                    |
| LlamaIndex            | Python     | OpenInference instrumentor + `BentoLabsSpanProcessor`                                    |
| Pydantic AI           | Python     | `Agent.instrument_all()` + `BentoLabsSpanProcessor`                                      |
| OpenAI Agents SDK     | Python     | OpenInference instrumentor + `BentoLabsSpanProcessor` (its built-in tracing is not OTLP) |
| CrewAI                | Python     | OpenInference instrumentor + `BentoLabsSpanProcessor`                                    |

Using Google ADK? Use the one-line [`bentolabs-sdk[adk]` integration](/python/integrations) instead. It captures every call automatically.

## Verify

A trace arriving is not the same as the dashboard filling in. After wiring the exporter, run one real flow, open [platform.bentolabs.ai](https://platform.bentolabs.ai), and confirm the row has all six columns: `provider`, `model`, `input`, `output`, `user_id`, `convo_id`.

* If `input` / `output` are empty, the framework emits metadata but not content. Add its OpenInference instrumentor (Python) or the `@arizeai/openinference-vercel` processor (Vercel) on the same exporter.
* `user_id` and `convo_id` come from the `gen_ai.user.id` and `gen_ai.conversation.id` span attributes. With no Bento SDK, set them through your framework (a run attribute, a span processor, or OTLP resource attributes).

## See also

<CardGroup cols={2}>
  <Card title="Attributes" icon="table" href="/concepts/attributes">
    How `gen_ai.*` / `openinference.*` attributes map to dashboard columns.
  </Card>

  <Card title="OTel transport" icon="cube" href="/python/otel-transport">
    The `BentoLabsSpanProcessor` and `BentoLabsTraceExporter` details.
  </Card>

  <Card title="Quickstart" icon="bolt" href="/quickstart">
    Install the SDK and send your first trace.
  </Card>

  <Card title="Migrate an existing stack" icon="right-left" href="/migrations/langfuse">
    Move from Langfuse or Raindrop to Bento.
  </Card>
</CardGroup>
