Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ POWERCONTEXT_SERVER_LOGGING_FORMAT=console
POWERCONTEXT_SERVER_LOGGING_ACCESS=true
POWERCONTEXT_SERVER_METRICS_ENABLED=true
POWERCONTEXT_SERVER_TRACING_ENABLED=false
# OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:6006/v1/traces
# The OpenTelemetry SDK appends /v1/traces to this base URL.
# OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:6006
# OTEL_SERVICE_NAME=powercontext

# Database --------------------------------------------------------------------
Expand Down
141 changes: 141 additions & 0 deletions docs/en/docs/how-to/trace-with-langfuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
---
title: Trace with Langfuse
description: Export PowerContext transport, application, and inference spans to Langfuse through standard OTLP configuration.
---

# Trace with Langfuse

PowerContext exports OpenTelemetry spans for transport and application operations. When tracing is enabled, the
generation and embedding calls that PowerContext itself constructs are traced too, so one trace shows the request, the
Memory operation, and the model calls underneath it.

This guide sends those spans to [Langfuse](https://langfuse.com) through its OTLP endpoint. It needs no PowerContext
code change and no Langfuse SDK: the standard OpenTelemetry variables from [Trace with Phoenix](trace-with-phoenix.md)
point the exporter at Langfuse instead.

## Start Langfuse

Langfuse self-hosting runs several services (web, worker, PostgreSQL, ClickHouse, Redis, and MinIO) with Docker
Compose:

```bash
git clone https://github.com/langfuse/langfuse.git
cd langfuse
docker compose up -d
```

Open <http://localhost:3000>, create a user, an organization, and a project, then create an API key pair in the project
settings. Keep the public key (`pk-lf-...`) and the secret key (`sk-lf-...`) at hand; they authenticate the exporter
below. The OTLP endpoint requires Langfuse v3.22.0 or later. This guide was verified with Langfuse 4.10.0.

For a reproducible local setup, [headless initialization](https://langfuse.com/self-hosting/headless-initialization)
creates the organization, project, user, and keys from environment variables instead of the UI. Langfuse Cloud works
the same way as a self-hosted instance: skip the compose step and replace `http://localhost:3000` below with the base
URL of your region, such as `https://cloud.langfuse.com` or `https://us.cloud.langfuse.com`.

## Install the export dependency

Recording and export require the `tracing-otlp` extra:

```bash
uv tool install --force "powercontext[cli,server,tracing-otlp] @ git+https://github.com/oceanbase/powercontext.git@master"
```

Without this extra, enabling tracing fails at startup with an explicit error instead of silently dropping spans.

## Configure and start the Server

Langfuse authenticates OTLP requests with HTTP Basic authentication built from the project keys. Enable tracing, point
the exporter at Langfuse, and configure a generation model so inference spans have something to record:

```bash
export LANGFUSE_PUBLIC_KEY=pk-lf-replace-me
export LANGFUSE_SECRET_KEY=sk-lf-replace-me
LANGFUSE_AUTH=$(printf '%s:%s' "$LANGFUSE_PUBLIC_KEY" "$LANGFUSE_SECRET_KEY" | base64 | tr -d '\n')

export POWERCONTEXT_SERVER_TRACING_ENABLED=true
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:3000/api/public/otel
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic ${LANGFUSE_AUTH},x-langfuse-ingestion-version=4"
export OTEL_SERVICE_NAME=powercontext-server
export POWERCONTEXT_SERVER_INFERENCE_GENERATION_MODEL=provider:model-name
powercontext server run
```

The OpenTelemetry SDK appends `/v1/traces` to `OTEL_EXPORTER_OTLP_ENDPOINT`, so the spans arrive at
`http://localhost:3000/api/public/otel/v1/traces`, the traces endpoint Langfuse expects. Langfuse accepts OTLP over
HTTP only, which is the protocol of the exporter installed by the `tracing-otlp` extra. The
`x-langfuse-ingestion-version=4` header makes Langfuse process the spans immediately; without it, Langfuse documents
that ingestion can lag by up to ten minutes. Set the provider credentials your generation model needs; PowerContext
records neither them nor the exporter headers.

## Trigger one inference request

Set `POWERCONTEXT_SCOPE_ID` to an existing ID returned by `create_scope`, capture a Source, then convert it into
Memory:

```bash
curl -X POST http://localhost:8000/v1/sources/content \
-H 'content-type: application/json' \
-d "{\"scope_id\":\"${POWERCONTEXT_SCOPE_ID}\",\"source_id\":\"task-1\",\"content\":\"I always book aisle seats.\"}"
```

```bash
curl -X POST http://localhost:8000/v1/memory/flush \
-H 'content-type: application/json' \
-d "{\"scope_id\":\"${POWERCONTEXT_SCOPE_ID}\"}"
```

Memory extraction runs during the flush, not during capture.

## Read the trace

Open <http://localhost:3000>, select the project, and open the **Traces** view. Langfuse names a trace after its root
span, so the flush appears as `HTTP flush_memory`. Every PowerContext span becomes an observation, and Langfuse infers
the observation type from the GenAI attributes on the span:

| Observation | Type | Meaning |
| --- | --- | --- |
| `HTTP flush_memory` | SPAN | The inbound HTTP request. Its `attributes.powercontext.request.id` metadata matches the `X-PowerContext-Request-ID` response header. |
| `powercontext flush_memory` | SPAN | The application operation, independent of the transport that invoked it. |
| `memory.flush` | SPAN | The Runtime stage that processes the Source window. The other stage spans, such as `scope.context`, `scope.lock`, `memory.search`, and `context.build`, are SPAN observations as well. |
| `memory_extraction run` | AGENT | One PowerContext generation task. Langfuse names it from the span's `logfire.msg` attribute, so Pydantic AI's `invoke_agent memory_extraction` span appears under this name. |
| `chat <model>` | GENERATION | One request to the model provider, with the model name, latency, and input, output, and total token usage. |

The other generation tasks follow the same pattern with their own names, such as `experience_incubation run` and
`memory_rerank run`.

An MCP request produces `MCP mcp.tools.call` as the root observation. FastMCP adds a `TOOL` observation named after the
tool, and the `powercontext <operation>` span and its stages nest beneath it. Readiness probes are deliberately not
traced.

Span attributes appear in each observation's metadata as `attributes.<name>`, and resource attributes as
`resourceAttributes.<name>`. To find the trace of one request, filter observations on the metadata key
`attributes.powercontext.request.id` with the value of the `X-PowerContext-Request-ID` response header. Failed
operations carry the `ERROR` level and `attributes.error.type`.

Langfuse derives the cost of a generation from its model definitions, which match the model name; models it does not
recognize show usage but no cost until you add a definition under the project's model settings. Token usage and cost
can then be aggregated in the Langfuse dashboards and Metrics API.

Spans are exported in batches, so allow a few seconds before refreshing. Scheduled background activations arrive as
their own traces, as described in the "Scheduled background spans" section of
[Trace with Phoenix](trace-with-phoenix.md).

## What is not exported

PowerContext configures inference instrumentation to exclude content. Observations carry model identifiers, token
usage, durations, and error categories. Prompts, model responses, Memory content, search queries, and vectors are
excluded, so the input and output panels of a generation show only the role and part types of each message, never its
text. PowerContext sets no Langfuse user, session, or tag attributes either, so the user and session views stay empty
and traces are located through metadata instead.

## Stop Langfuse

```bash
docker compose down
```

Add `-v` to delete the stored traces as well.

Span names and attributes follow the Pydantic AI GenAI semantic conventions and can change when that dependency is
upgraded across a major version. Do not treat them as a stable contract.
4 changes: 3 additions & 1 deletion docs/en/docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,9 @@ that do not use the `powercontext` command may omit the `cli` extra.

Enabling tracing also produces spans for the generation and embedding calls that PowerContext constructs, without
recording prompts, model responses, Memory content, or vectors. See
[Trace with Phoenix](../how-to/trace-with-phoenix.md) for a working configuration.
[Trace with Phoenix](../how-to/trace-with-phoenix.md) for a working configuration, and
[Trace with Langfuse](../how-to/trace-with-langfuse.md) for a backend that authenticates the exporter through
`OTEL_EXPORTER_OTLP_HEADERS`.

To use OceanBase, provide its URL through your environment or secret manager:

Expand Down
129 changes: 129 additions & 0 deletions docs/zh/docs/how-to/trace-with-langfuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
title: 用 Langfuse 查看 trace
description: 通过标准 OTLP 配置,把 PowerContext 的 transport、application 和推理 span 导出到 Langfuse。
---

# 用 Langfuse 查看 trace

PowerContext 会为 transport 和 application 操作导出 OpenTelemetry span。启用 tracing 后,PowerContext 自己构造的
generation 与 embedding 调用也会被 trace,因此一条 trace 里可以同时看到请求、Memory 操作,以及其下的模型调用。

本文把这些 span 通过 OTLP 端点发送到 [Langfuse](https://langfuse.com)。整个过程不需要改动 PowerContext 代码,也不需要
Langfuse SDK:只是把 [用 Phoenix 查看 trace](trace-with-phoenix.md) 中的标准 OpenTelemetry 变量改为指向 Langfuse。

## 启动 Langfuse

Langfuse 自托管通过 Docker Compose 运行多个服务(web、worker、PostgreSQL、ClickHouse、Redis 和 MinIO):

```bash
git clone https://github.com/langfuse/langfuse.git
cd langfuse
docker compose up -d
```

打开 <http://localhost:3000>,创建用户、organization 和 project,然后在 project 设置里创建一对 API key。记下 public key
(`pk-lf-...`)和 secret key(`sk-lf-...`),下文用它们为 exporter 鉴权。OTLP 端点要求 Langfuse v3.22.0 及以上;本文
在 Langfuse 4.10.0 上验证。

如需可复现的本地环境,[headless initialization](https://langfuse.com/self-hosting/headless-initialization) 可以通过
环境变量直接创建 organization、project、用户和 key,而不必经过 UI。Langfuse Cloud 的用法与自托管相同:跳过 compose
步骤,把下文的 `http://localhost:3000` 换成所在区域的基础 URL,例如 `https://cloud.langfuse.com` 或
`https://us.cloud.langfuse.com`。

## 安装导出依赖

recording 和 export 需要 `tracing-otlp` extra:

```bash
uv tool install --force "powercontext[cli,server,tracing-otlp] @ git+https://github.com/oceanbase/powercontext.git@master"
```

缺少该 extra 时,启用 tracing 会在启动阶段直接报错,而不是静默丢弃 span。

## 配置并启动 Server

Langfuse 用 project key 组成的 HTTP Basic 认证来鉴权 OTLP 请求。启用 tracing、把 exporter 指向 Langfuse,并配置一个
generation model,让推理 span 有内容可记录:

```bash
export LANGFUSE_PUBLIC_KEY=pk-lf-replace-me
export LANGFUSE_SECRET_KEY=sk-lf-replace-me
LANGFUSE_AUTH=$(printf '%s:%s' "$LANGFUSE_PUBLIC_KEY" "$LANGFUSE_SECRET_KEY" | base64 | tr -d '\n')

export POWERCONTEXT_SERVER_TRACING_ENABLED=true
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:3000/api/public/otel
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic ${LANGFUSE_AUTH},x-langfuse-ingestion-version=4"
export OTEL_SERVICE_NAME=powercontext-server
export POWERCONTEXT_SERVER_INFERENCE_GENERATION_MODEL=provider:model-name
powercontext server run
```

OpenTelemetry SDK 会在 `OTEL_EXPORTER_OTLP_ENDPOINT` 后追加 `/v1/traces`,因此 span 最终发往
`http://localhost:3000/api/public/otel/v1/traces`,正是 Langfuse 期望的 traces 端点。Langfuse 只接受 OTLP over HTTP,
与 `tracing-otlp` extra 安装的 exporter 协议一致。`x-langfuse-ingestion-version=4` 头让 Langfuse 立即处理这些
span;Langfuse 文档指出,缺少该头时摄入最多可能延迟十分钟。按所选 generation model 的要求设置 provider 凭据;
PowerContext 既不会记录凭据,也不会记录 exporter 的请求头。

## 触发一次推理请求

将 `POWERCONTEXT_SCOPE_ID` 设置为 `create_scope` 返回的已有 ID,先捕获一个 Source,再把它转成 Memory:

```bash
curl -X POST http://localhost:8000/v1/sources/content \
-H 'content-type: application/json' \
-d "{\"scope_id\":\"${POWERCONTEXT_SCOPE_ID}\",\"source_id\":\"task-1\",\"content\":\"I always book aisle seats.\"}"
```

```bash
curl -X POST http://localhost:8000/v1/memory/flush \
-H 'content-type: application/json' \
-d "{\"scope_id\":\"${POWERCONTEXT_SCOPE_ID}\"}"
```

Memory extraction 发生在 flush 阶段,而不是捕获阶段。

## 查看 trace

打开 <http://localhost:3000>,选择 project,进入 **Traces** 视图。Langfuse 用根 span 命名 trace,因此这次 flush 显示为
`HTTP flush_memory`。PowerContext 的每个 span 都会成为一个 observation,Langfuse 根据 span 上的 GenAI 属性推断
observation 类型:

| Observation | 类型 | 含义 |
| --- | --- | --- |
| `HTTP flush_memory` | SPAN | 入站 HTTP 请求。其 metadata 中的 `attributes.powercontext.request.id` 与响应头 `X-PowerContext-Request-ID` 一致。 |
| `powercontext flush_memory` | SPAN | application 操作,与调用它的 transport 无关。 |
| `memory.flush` | SPAN | 实际处理 Source window 的 Runtime stage。其他 stage span(如 `scope.context`、`scope.lock`、`memory.search`、`context.build`)同样是 SPAN observation。 |
| `memory_extraction run` | AGENT | 一次 PowerContext generation 任务。Langfuse 取 span 的 `logfire.msg` 属性作为名称,因此 Pydantic AI 的 `invoke_agent memory_extraction` span 以这个名字出现。 |
| `chat <model>` | GENERATION | 一次发往模型 provider 的请求,包含模型名、耗时,以及 input、output 和 total token 用量。 |

其他 generation 任务遵循同样的模式,例如 `experience_incubation run` 和 `memory_rerank run`。

MCP 请求以 `MCP mcp.tools.call` 作为根 observation。FastMCP 会添加一个以工具名命名的 `TOOL` observation,
`powercontext <operation>` span 及其 stage 嵌套在其下。readiness 探活被有意排除在 trace 之外。

span 属性以 `attributes.<name>` 的形式出现在每个 observation 的 metadata 中,resource 属性则是
`resourceAttributes.<name>`。要定位某次请求的 trace,请用响应头 `X-PowerContext-Request-ID` 的值过滤 metadata key
`attributes.powercontext.request.id`。失败的操作带有 `ERROR` level 和 `attributes.error.type`。

Langfuse 根据模型定义匹配模型名来推算 generation 成本;未识别的模型只显示用量而没有成本,直到你在 project 的模型
设置中添加定义。之后即可在 Langfuse 的 dashboard 与 Metrics API 中汇总 token 用量和成本。

span 是批量导出的,刷新前请稍等几秒。定时后台激活会以独立 trace 到达,见
[用 Phoenix 查看 trace](trace-with-phoenix.md) 中的「定时后台 span」一节。

## 哪些内容不会被导出

PowerContext 在配置推理 instrumentation 时关闭了内容记录。observation 只携带模型标识、token 用量、耗时和错误类别;
prompt、模型响应、Memory 内容、搜索 query 和向量都不会被导出,因此 generation 的 input 与 output 面板只显示每条
消息的 role 和 part 类型,不会显示正文。PowerContext 也不设置 Langfuse 的 user、session 或 tag 属性,因此用户与
会话视图保持为空,trace 需要通过 metadata 定位。

## 停止 Langfuse

```bash
docker compose down
```

追加 `-v` 可同时删除已存储的 trace。

span 名与属性遵循 Pydantic AI 的 GenAI 语义约定,跨大版本升级该依赖时可能变化,不应视为稳定契约。
3 changes: 2 additions & 1 deletion docs/zh/docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,8 @@ OpenTelemetry 环境变量进行配置。不使用 `powercontext` command 的 pr
`cli` extra。

启用 tracing 后,PowerContext 自己构造的 generation 与 embedding 调用也会产生 span,且不记录 prompt、模型响应、
Memory 内容或向量。可运行的配置见 [用 Phoenix 查看 trace](../how-to/trace-with-phoenix.md)。
Memory 内容或向量。可运行的配置见 [用 Phoenix 查看 trace](../how-to/trace-with-phoenix.md);需要通过
`OTEL_EXPORTER_OTLP_HEADERS` 为 exporter 鉴权的后端示例见 [用 Langfuse 查看 trace](../how-to/trace-with-langfuse.md)。

使用 OceanBase 时,通过环境或 secret manager 提供 URL:

Expand Down
Loading