For the complete documentation index, see llms.txt. Markdown versions are available by appending .md to documentation URLs.

Pixeltable Observability & Monitoring with OpenTelemetry

SigNoz Cloud - This page applies to SigNoz Cloud editions.
Self-Host - This page applies to self-hosted SigNoz editions.

What is Pixeltable Observability?

Pixeltable is declarative data infrastructure for multimodal AI apps. Images, video, audio, documents, and model calls live in tables, and a computed column runs its transform on insert and update. Pixeltable observability records that work with OpenTelemetry and sends it to SigNoz.

The Pixeltable OpenTelemetry bridge emits one trace per table operation. With span_level='debug' the trace contains the rows, each UDF call, and, when you add an LLM instrumentor, a child span for the model request:

pixeltable.insert
└─ pixeltable.store.insert_rows
   ├─ pixeltable.row
   ├─ pixeltable.udf.chat_completions
   │  └─ ChatCompletion
   └─ pixeltable.sa.insert_rows

UDF spans carry pxt.udf_path, pxt.column, pxt.resource_pool, and pxt.retries. pixeltable.store.write_column spans carry pxt.column. The model name, prompt, completion, and token counts are attributes of the LLM child span. SigNoz AI Observability reads the gen_ai.* attributes on that child span.

Prerequisites

Tested with Python 3.11, pixeltable 0.7.23, opentelemetry-instrumentation-pixeltable 0.2.0, and openinference-instrumentation-openai 0.1.69.

Monitor Pixeltable with OpenTelemetry

Call pxt_otel.init() once per process, before the first Pixeltable operation. It builds an OTLP/HTTP exporter and instruments Pixeltable. Add OpenAIInstrumentor so each openai.chat_completions cell gets a child span. For the Pixeltable API, see the Pixeltable documentation.

Step 1: Install the packages

Install Pixeltable with the OpenTelemetry extra, the openai client, and the OpenAI instrumentor.

pip install 'pixeltable[otel]' openai openinference-instrumentation-openai

pixeltable[otel] installs opentelemetry-instrumentation-pixeltable together with the OpenTelemetry SDK and the OTLP exporters. Neither Pixeltable nor the instrumentor installs openai, which chat_completions() calls.

Step 2: Point the exporter at SigNoz

Set the endpoint with environment variables, or pass it to init() in code. init() appends /v1/traces and /v1/metrics to the endpoint, so pass the base URL without a path.

init() reads these variables when you omit the matching arguments. OTEL_SPAN_LEVEL=debug keeps per-row and per-UDF spans. The default info keeps the operation span and the catalog, store, and SQL spans that belong to it.

export OTEL_SERVICE_NAME=pixeltable-app
export OTEL_EXPORTER_OTLP_ENDPOINT="https://ingest.<region>.signoz.cloud:443"
export OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=<your-ingestion-key>"
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
export OTEL_SPAN_LEVEL=debug
export OPENAI_API_KEY=<your-openai-api-key>

Verify these values:

  • <region>: Your SigNoz Cloud region.
  • <your-ingestion-key>: Your SigNoz ingestion key.
  • <your-openai-api-key>: The API key for the provider your computed columns call.

Step 3: Initialize the bridge and insert a row

Initialize the bridge, instrument OpenAI, and insert a row. If you used the Code tab in Step 2, replace pxt_otel.init() with your init() call.

import opentelemetry.instrumentation.pixeltable as pxt_otel
from openinference.instrumentation.openai import OpenAIInstrumentor
from opentelemetry import trace
 
pxt_otel.init()
OpenAIInstrumentor().instrument()
 
import pixeltable as pxt
from pixeltable.functions import openai
 
table = pxt.create_table('articles', {'id': pxt.Int, 'body': pxt.String}, if_exists='replace')
table.add_computed_column(
    summary=openai.chat_completions(
        messages=[{'role': 'user', 'content': 'Summarize in one sentence: ' + table.body}],
        model='gpt-4o-mini',
    ).choices[0].message.content
)
table.insert([{'id': 1, 'body': 'Pixeltable is declarative data infrastructure for multimodal AI apps.'}])
trace.get_tracer_provider().force_flush()

force_flush() sends the batched spans right away. Without it, the SDK sends them when the process exits normally. Metrics go out on a periodic reader, about once a minute. To send them right away, flush the meter provider as well:

from opentelemetry import metrics
 
metrics.get_meter_provider().force_flush()

init() reuses a tracer provider that your application already registered.

OpenInference sets llm.model_name, llm.provider, and llm.token_count.* on the ChatCompletion span. The default gen_ai.llm group in Attribute Mapping copies them to the gen_ai.* attributes that AI Observability reads. To emit gen_ai.* from the process, set OPENINFERENCE_ENABLE_GENAI_SEMCONV=true.

Validate

Open the Traces Explorer and filter on the service name and the operation:

service.name = 'pixeltable-app' AND name = 'pixeltable.insert'
Traces Explorer filtered to service pixeltable-app and span name pixeltable.insert
A pixeltable.insert span in the Traces Explorer

Open the trace. The waterfall shows pixeltable.udf.chat_completions under pixeltable.store.insert_rows, and ChatCompletion under the UDF span. The prompt and completion are attributes on ChatCompletion.

Trace detail for pixeltable.insert with a nested ChatCompletion span
pixeltable.insert with the UDF span and the nested LLM span

Pixeltable also exports metrics when an endpoint is set. In the Metrics Explorer, query:

  • pixeltable.udf.input_tokens and pixeltable.udf.output_tokens, with attribute pxt.model (and pxt.udf_path)
  • pixeltable.udf.calls and pixeltable.udf.latency
  • pixeltable.rows.written, with attribute pxt.table

The metric scope name is pixeltable. The same insert recorded input and output tokens on pxt.model=gpt-4o-mini.

collect() writes a pixeltable.collect trace and does not call the model again. Computed values are stored. New LLM spans appear on insert, on update, and when add_computed_column backfills rows that already exist.

View in AI Observability

AI Observability shows the cost, token usage, latency, and errors of the LLM calls that Pixeltable computed columns make. In SigNoz, open AI Observability, and select your service in the service_name filter.

AI Observability counts the ChatCompletion span as an LLM call. After one insert, it shows 1 LLM call for gpt-4o-mini-2024-07-18, the model name that OpenAI returns, with its input and output tokens. SigNoz calculates the cost from its built-in price for that model. One call costs a fraction of a cent, so the Total cost tile rounds it to $0, and the Cost breakdown panel shows the input and output split. For a model without a price, cost reads $0 until you add a model pricing rule for the model name. Time to first token reads 0s for a non-streaming call.

AI Observability overview with 1 LLM call, 48 tokens, and model gpt-4o-mini-2024-07-18
AI Observability after one Pixeltable insert

To query single traces, open the Explorer tab. Trace View shows one row for each trace, with totals such as tokens, cost, and call counts. You can filter, aggregate, and sort by these totals, for example trace.total_tokens > 1000. For details, see AI Observability Explorer.

If the panels stay empty, make sure that your spans carry the attributes in Telemetry Requirements. To map other attribute names, use Attribute Mapping.

Limitations

  • Every operation is a trace. create_table, drop_table, add_computed_column, and collect each start their own trace. span_level='debug' adds per-row spans, per-UDF spans, and finer catalog and store spans such as pixeltable.catalog.*. At info, those per-row and per-UDF spans are dropped, and the LLM span stays in the insert trace without a pixeltable.udf.chat_completions parent.
  • ChatCompletion carries the prompt and the completion, so both leave the process. Pixeltable log export stays off unless you pass logs=True. It then exports the records of the pixeltable loggers, such as catalog and scheduler messages. An OpenAI error record shows the API key masked, except for its last 4 characters.

Troubleshooting Pixeltable Observability

No traces in SigNoz

The insert returned a row, and the Traces Explorer is empty for service.name = 'pixeltable-app'.

  • pxt_otel.init() ran with no endpoint and stayed inert. Restart the process and call init() with the endpoint.
  • The process was killed before it exited normally, so the SDK did not send the batched spans. Call trace.get_tracer_provider().force_flush() after the operations you want to see.
  • The <region> in the endpoint does not match the SigNoz Cloud account that owns the ingestion key.
  • Wait 10-30 seconds. The exporter and SigNoz both batch.

The operation span is there, and the LLM span is missing

pixeltable.insert is visible, and ChatCompletion is not.

  • OpenAIInstrumentor().instrument() did not run, or it logged a dependency conflict because openai is not installed. Install openai and restart the process.
  • span_level is info, so there is no UDF span. The LLM span should still be in the insert trace. Search the trace for ChatCompletion before assuming the call was dropped.
  • collect() does not re-run computed columns. Insert or update a row to produce a new model call.
  • The OpenAI client returned an error. The insert call raises RequestError with the OpenAI error message, and the pixeltable.insert, pixeltable.udf.chat_completions, and ChatCompletion spans are marked as errors. Filter on has_error = true to find them.

Auth errors (401 / 403)

The exporter logs this line when SigNoz rejects the ingestion key:

Failed to export spans batch code: 401, reason: Unauthorized

The signoz-ingestion-key header must be the key from SigNoz Ingestion Settings, with no extra spaces or quotes. headers is a single string, signoz-ingestion-key=<your-ingestion-key>, not a Python dict. A dict fails in init() with TypeError: expected string or bytes-like object, got 'dict'.

Endpoint issues

Use https://ingest.<region>.signoz.cloud:443 for SigNoz Cloud and http://<signoz-host>:4318 for Self-Hosted SigNoz. Use localhost only when the collector runs on the same host and in the same network namespace as the application. init() appends /v1/traces and /v1/metrics to the endpoint. Pass the base URL. An endpoint ending in /v1/traces still exports traces, but metrics fail with Failed to export metrics batch code: 404, reason: Not Found. Do not send OTLP/HTTP to the gRPC port 4317.

Setting only OTEL_EXPORTER_OTLP_TRACES_ENDPOINT does not start an exporter. Set OTEL_EXPORTER_OTLP_ENDPOINT or pass endpoint to init().

AttributeError: 'ProxyTracerProvider' object has no attribute 'force_flush'

init() found no endpoint, so it did not register a tracer provider. This happens when no endpoint is set, when only OTEL_EXPORTER_OTLP_TRACES_ENDPOINT is set, or when opentelemetry-instrument runs without opentelemetry-distro. Set OTEL_EXPORTER_OTLP_ENDPOINT or pass endpoint to init().

Instrumentation is already initialized

A second init() in the same process raises RequestError: pixeltable OpenTelemetry instrumentation is already initialized. In a notebook, this happens when you run the init() cell again. To change the init() arguments, restart the kernel or the process.

Too many spans

span_level='debug' records per-row, per-UDF, and finer catalog and store spans as well as the insert. Set OTEL_SPAN_LEVEL=info or span_level='info' to drop those finer spans. Filter the Explorer with name = 'pixeltable.insert' while you are on debug.

Setup OpenTelemetry Collector (Optional)

What is the OpenTelemetry Collector?

The OpenTelemetry Collector sits between your app and SigNoz. The application sends OTLP to the Collector, and the Collector forwards it to SigNoz.

Why use it?

  • Cleaning up data: Drop noisy catalog spans, or remove prompt attributes before they leave your network.
  • Keeping your app lightweight: The Collector handles batching, retries, and compression.
  • Adding context: The Collector can add the Kubernetes pod or cloud region.
  • More than one backend: You can fan out later without changing the Pixeltable process.

See Switch from direct export to Collector for the steps. For more detail, see Why use the OpenTelemetry Collector? and the Collector configuration guide.

Browse all LLM observability integrations to instrument the rest of the stack.

Is this page helpful

Last updated—November 04, 2026