Tutorial: Connect Langfuse
Send session-derived traces from Orca Agent Engine to a Langfuse project through OTLP HTTP/JSON.
Langfuse receives session-derived traces from Orca Agent Engine through its OpenTelemetry endpoint. This tutorial creates a Workspace custom binding with metadata-only capture, then uses a new session to verify delivery. You do not install a Langfuse SDK in your agent.
Before you begin
- Run an exporter with Kafka Transcript access and Registry internal authentication. For a self-hosted deployment, follow deploy the exporter.
- Create a dedicated Langfuse project. In Project Settings > API Keys, create project-scoped public and secret keys. Do not use organization-scoped keys. See Langfuse API keys.
- Select a test Workspace without an existing custom binding. Have an organization admin key with
observability:readandobservability:write, ororg:admin. - Have a runnable agent and environment in that same Workspace.
- Install
curl,jq, anduuidgen; examples use Bash.
If you start with ork local, its default Compose stack uses Postgres
Transcript and does not start an exporter. Add Kafka and an exporter with matching Transcript
settings before following this tutorial. Keep such changes in a separate Compose overlay:
ork local start rewrites its generated Compose file.
Export these variables from your credential manager or shell setup. Do not commit keys or enable shell tracing.
| Variable | Value |
|---|---|
LANGFUSE_BASE_URL | Your project's regional Langfuse HTTPS origin, without a trailing slash or path |
LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY | Project-scoped keys for the dedicated project |
ORCA_ADMIN_URL | Admin listener origin, without a path suffix |
ORCA_ORG_ADMIN_KEY | Organization admin API key |
WORKSPACE_ID | Test Workspace ID in that organization |
ORCA_REGISTRY_URL | Public registry origin for that Workspace |
ORCA_API_KEY | Public Workspace API key, sent as x-api-key |
AGENT_ID, ENVIRONMENT_ID | Existing runnable agent and environment IDs in that Workspace |
Choose the Langfuse endpoint
Use the region where you created the project. Langfuse documents these Cloud origins in its OTLP configuration:
| Project region | LANGFUSE_BASE_URL |
|---|---|
| EU | https://cloud.langfuse.com |
| US | https://us.cloud.langfuse.com |
| Japan | https://jp.cloud.langfuse.com |
| HIPAA | https://hipaa.cloud.langfuse.com |
The binding below appends /api/public/otel/v1/traces. Do not use the SDK/Collector base endpoint
/api/public/otel as the binding's endpoint_url; Orca does not append the traces suffix for you.
Orca uses otlp_http, langfuse, http/json, none, and Basic authentication with the public key
as username and secret key as password. The exporter constructs the Authorization header and adds
x-langfuse-ingestion-version: 4. Langfuse's OTEL_EXPORTER_OTLP_* examples configure an OpenTelemetry
SDK or Collector, not this exporter. Store project keys through the registry admin API below, not
in exporter environment variables, Helm values, or agent metadata.
Langfuse's local HTTP examples are not supported Orca targets. A self-hosted Langfuse instance must expose the exact traces path over publicly reachable HTTPS with a trusted certificate. Orca rejects private DNS addresses, follows no redirects, and does not use ambient proxies. Langfuse support for HTTP/protobuf does not enable it in Orca; use HTTP/JSON.
Verify the project credentials
Langfuse's Public API guide
uses GET /api/public/projects to check project-scoped credentials. Extract the returned project ID
for the binding rather than guessing it:
set -euo pipefail
project=$(curl -fsS "$LANGFUSE_BASE_URL/api/public/projects" \
--user "$LANGFUSE_PUBLIC_KEY:$LANGFUSE_SECRET_KEY")
LANGFUSE_PROJECT_ID=$(jq -er '
.data | if length == 1 then .[0].id else error("expected one project") end
| select(type == "string" and length > 0)
' <<< "$project")
export LANGFUSE_PROJECT_ID
jq '.data[] | {id, name}' <<< "$project"Confirm this is the dedicated project. This checks the keys and base URL from your shell, not
the exporter's network path or OTLP delivery. external_project_id records project identity in
Orca; the project keys determine where Langfuse ingests the spans.
Create the Workspace binding
Admin endpoint
These operations use the registry admin listener, not the public Workspace API. Set
ORCA_ADMIN_URL to its origin without a path suffix. Organization operations use
ORCA_ORG_ADMIN_KEY; platform policy operations use a separate ORCA_PLATFORM_KEY. Both examples
send x-api-key. Never use a Workspace key or an exporter workload token here. See
observability administration for OIDC and scopes.
Read the current settings and retain the opaque ETag, including quotes:
headers=$(mktemp)
state=$(curl -fsS -D "$headers" \
"$ORCA_ADMIN_URL/v1/organizations/workspaces/$WORKSPACE_ID/agent_observability" \
-H "x-api-key: $ORCA_ORG_ADMIN_KEY")
WORKSPACE_ETAG=$(awk 'tolower($1) == "etag:" { sub(/\r$/, "", $2); print $2 }' "$headers")
rm -f "$headers"
jq '{configured, effective}' <<< "$state"If configured.mode is already custom, stop and select another test Workspace or follow the
replacement rules. This example
creates a new binding; a same-active-target replacement must omit credentials.
IDEMPOTENCY_KEY=$(uuidgen)
binding=$(jq -n '{
mode: "custom",
capture_ceiling: "metadata_only",
target: {
adapter_type: "otlp_http",
endpoint_kind: "traces_endpoint",
endpoint_class: "public",
endpoint_url: (env.LANGFUSE_BASE_URL + "/api/public/otel/v1/traces"),
external_project_id: env.LANGFUSE_PROJECT_ID
},
config: {
semantic_profile: "langfuse",
protocol: "http/json",
compression: "none",
timeout_ms: 10000,
sample_rate: 1,
capture_mode: "metadata_only",
environment: "test",
release: "langfuse-check"
},
credentials: {
type: "basic",
username: env.LANGFUSE_PUBLIC_KEY,
password: env.LANGFUSE_SECRET_KEY
}
}' | curl -fsS -X PUT \
"$ORCA_ADMIN_URL/v1/organizations/workspaces/$WORKSPACE_ID/agent_observability" \
-H "x-api-key: $ORCA_ORG_ADMIN_KEY" \
-H "Content-Type: application/json" \
-H "If-Match: $WORKSPACE_ETAG" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" --data-binary @-)
BINDING_ID=$(jq -er '.configured.binding.id' <<< "$binding")
jq '{configured, effective}' <<< "$binding"A new binding returns 201. Confirm effective.source is workspace_custom,
effective.status is enabled, and effective.capture_mode is metadata_only. A successful PUT
does not contact Langfuse or prove delivery. For a retry, retain the original request body, ETag,
and idempotency key; for a new mutation, read a fresh ETag and use a new key.
Run a new session and inspect the trace
Use the public registry endpoint and Workspace key. Create a new session after the binding is enabled; existing sessions do not adopt the new selection.
session=$(jq -n --arg agent "$AGENT_ID" --arg environment "$ENVIRONMENT_ID" '{
agent: $agent, environment_id: $environment, title: "Langfuse export check"
}' | curl -fsS "$ORCA_REGISTRY_URL/v1/sessions" \
-H "x-api-key: $ORCA_API_KEY" \
-H "Content-Type: application/json" --data-binary @-)
SESSION_ID=$(jq -er '.id' <<< "$session")
curl -fsS "$ORCA_REGISTRY_URL/v1/sessions/$SESSION_ID/events" \
-H "x-api-key: $ORCA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"events":[{"type":"user.message","content":[{"type":"text","text":"Reply with the word ready."}]}]}'Use the event stream or event list to confirm acceptance and turn completion. After asynchronous delivery and indexing, open the same Langfuse project and check:
- The session ID is
$WORKSPACE_ID:$SESSION_ID, with a separate trace for each projected turn. - The root observation is named
orca.agent.turnand has typeagent. - Newly attributed metadata identifies
$BINDING_ID, the agent revision,testenvironment, andlangfuse-checkrelease. Tool/evaluator children appear only when corresponding events exist. - Input/output are absent in metadata-only mode. To include content, follow enable raw capture explicitly and test another new session with synthetic data.
Model summaries are explicitly typed span, not generation. Langfuse's
attribute mapping
documents model/usage/cost fields as generation-only. Do not use the presence of model-summary
attributes as proof of generation-specific UI statistics or automatic pricing. Orca's outcome
evaluations are evaluator observations, not Langfuse score records or configured Langfuse evaluators.
Troubleshoot and stop the test
| Symptom | Check |
|---|---|
| Project credential check fails | Use project keys from the same region and confirm they have not expired or been deleted. |
| Binding is enabled, but the project is empty | Check exporter deployment, sampling, and a new session's completed turn. Ready and PUT success do not prove ingestion. |
| A self-hosted endpoint is rejected | Check public HTTPS, DNS addresses, certificate trust, the exact traces path, and absence of redirects. |
OTLP returns 401 or 403 | Check the project keys and use Orca's credential-rotation API; repeated rejection after one credential refresh is terminal. |
| OTLP returns HTTP 200, but some spans are missing | Orca treats a valid response with positive partialSuccess.rejectedSpans as terminal, without retrying the rejected spans. Check receiving-side records. |
| OTLP returns an empty or non-JSON response | Orca requires HTTP 200 with a valid OTLP JSON response and application/json; an invalid response is terminal, not success. |
Kafka runtime diagnostics cover recovery and resource budgets, not per-delivery outcome history. Inspect Langfuse or ingress-side records when investigating acceptance or rejection. See sampling and delivery for retry behavior.
To stop the test, read a fresh Workspace ETag and PUT
{"mode":"disabled","capture_ceiling":"metadata_only"} with a new idempotency key. See
disable a Workspace. Disabling the
organization default does not stop a Workspace custom binding, and neither operation deletes
traces already stored in Langfuse.