Send existing traces
Cloud accepts OpenTelemetry GenAI spans over OTLP/HTTP. Set env vars on the process that exports the spans; no pyrula install or code change is needed.
This page uses Python 3.11 to 3.15 and a POSIX shell, in the directory your app runs from.
1. Copy the origin and the key
Section titled “1. Copy the origin and the key”In Cloud, open Setup for the project and environment. It shows the API origin and the key (a key is for one environment).
2. Point the exporter at Cloud
Section titled “2. Point the exporter at Cloud”Set these where the app starts, with your key. Hosted Cloud’s API origin is https://api.pyrula.io; a self-hosted deployment shows its own under Setup:
export OTEL_EXPORTER_OTLP_ENDPOINT=https://api.pyrula.io/otelexport OTEL_EXPORTER_OTLP_PROTOCOL=http/protobufexport OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <PYRULA_CLOUD_API_KEY>"export OTEL_RESOURCE_ATTRIBUTES="${OTEL_RESOURCE_ATTRIBUTES:+$OTEL_RESOURCE_ATTRIBUTES,}deployment.environment=production"- OTLP over HTTP only. Many SDKs default to gRPC, hence the protocol line. The exporter
appends
/v1/tracesitself. - The last line appends to
OTEL_RESOURCE_ATTRIBUTESso it keepsservice.name, which Cloud groups runs by. - Replace
productionwith your key’s environment. Without the attribute the intake uses the key’s environment; an SDK-setdeployment.environment.nametakes precedence.
Name the service if your instrumentation does not:
export OTEL_SERVICE_NAME=support-bot3. Try it without an app
Section titled “3. Try it without an app”The following script sends one model-call span.
pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-httpSave this as send_trace.py:
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporterfrom opentelemetry.sdk.trace import TracerProviderfrom opentelemetry.sdk.trace.export import BatchSpanProcessor
# TracerProvider() reads OTEL_SERVICE_NAME and OTEL_RESOURCE_ATTRIBUTES, and# OTLPSpanExporter() reads the OTEL_EXPORTER_OTLP_* variables: nothing else to set.provider = TracerProvider()provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))tracer = provider.get_tracer("quickstart")
with tracer.start_as_current_span("chat gpt-6-luna") as span: span.set_attribute("gen_ai.operation.name", "chat") span.set_attribute("gen_ai.request.model", "gpt-6-luna") span.set_attribute("gen_ai.usage.input_tokens", 120) span.set_attribute("gen_ai.usage.output_tokens", 45)
provider.shutdown()Run it in the same shell:
python send_trace.pyIt prints nothing. In Runs for your environment, a run named chat gpt-6-luna appears under the service name with one model step of 120 input and 45 output tokens. Its id is otel- plus the trace id.
Mapping
Section titled “Mapping”Each trace becomes one run and each service.name one telemetry runtime. Only GenAI spans are
mapped: model calls become model steps (with tokens and cost), execute_tool tool steps,
invoke_agent agent steps. Other spans are dropped.
An imported run has no worker behind it, so Cloud can’t replay, pause or cancel it. For that, run the app on pyrula (Connect to Pyrula Cloud).
Intake accepted, but no run
Section titled “Intake accepted, but no run”A trace becomes a run only if it has a span that looks like a model call: a
gen_ai.operation.name of chat, text_completion, generate_content or embeddings, or a
model or token-usage attribute (gen_ai.request.model, gen_ai.usage.input_tokens, or the
older llm.* spellings). HTTP-only traces produce nothing.
If the exporter cannot connect, check that the endpoint ends in /otel, the protocol is
http/protobuf, and the key belongs to the project you are viewing.