Laravel AI SDK with OpenTelemetry
There are two ways to get laravel/ai
agents into AgentPing. The Laravel SDK listens to
the same events inside your app and reports them directly; it needs no
OpenTelemetry and is the shorter path. This page covers the other route:
exporting OpenTelemetry spans with
cristea-iulian/laravel-ai-otel
and pointing them at the AgentPing OTLP endpoint.
Pick it when OpenTelemetry is already how your app reports, or when you
want the same spans in a second backend.
Install
composer require cristea-iulian/laravel-ai-otel
If the app does not run OpenTelemetry yet, add the SDK and the OTLP exporter too. The package bootstraps a tracer provider with a batch processor and flushes it after every request, every queue job, and at shutdown, Octane included:
composer require open-telemetry/sdk open-telemetry/exporter-otlp
Then point the exporter at AgentPing. The package appends /v1/traces
to the endpoint when it is missing, and the team API key goes in the
headers as a Bearer token:
OTEL_EXPORTER_OTLP_ENDPOINT=https://eu.ingest.agentping.io
OTEL_EXPORTER_OTLP_HEADERS="authorization=Bearer apk_eu_..."
OTEL_SERVICE_NAME=my-app
Use us.ingest.agentping.io for a apk_us_ key. Per-agent ping_
tokens do not work on the OTLP endpoint; traces can create agents, so
they need the team key.
If open-telemetry/opentelemetry-auto-laravel or another SDK autoloader
already registers a global tracer provider, the package records through
it and there is nothing more to configure. Set the two exporter
variables above on that provider instead.
Requires PHP 8.3+, Laravel 12 or 13, and laravel/ai 0.11.x. The
package pins the SDK minor because the SDK is pre-1.0.
What arrives in AgentPing
laravel-ai-otel emits the current GenAI semantic conventions and
AgentPing reads them as they are. One trace becomes one run.
| Span | AgentPing event | What is read |
|---|---|---|
invoke_agent {Agent} |
step |
gen_ai.agent.name names the agent. Its usage attributes are the sum of the steps inside it, so this span is never priced. |
chat {model} |
llm_call |
Provider, model, input and output tokens, cache read, cache creation and reasoning tokens. Priced from the rate card. |
execute_tool {tool} |
tool_call |
Tool name and gen_ai.tool.call.id. Arguments and result when content capture is on. |
embeddings {model} |
llm_call |
Provider, model and input tokens. |
Attributes AgentPing does not map, such as gen_ai.response.finish_reasons,
gen_ai.request.stream, laravel.ai.step.number and the configured
provider name in laravel.ai.provider.name, stay on the step as
attributes, so they are still there when you open it.
Errors carry through. A span with error status becomes a step with
status: error and the exception class, and any errored step marks
the run as errored.
Sub-agents fit naturally. An agent used as a tool produces its own
invoke_agent span under the parent's execute_tool span, so the run
timeline shows the tool call, the inner agent's step, and its model
calls in order, all inside one run.
Failover is visible but reads as an error. The failed attempt's chat
span keeps its error status, so the run shows an errored llm_call for
the first provider followed by a successful one for the provider that
answered, and the run itself is flagged as errored even though the
prompt succeeded. The laravel.ai.failover span event that explains
the switch is not read; span events are not part of the mapping. The
Laravel SDK records failover as a step with the reason and leaves the
run's status alone, which is the main reason to prefer it if failover
is routine for you.
Agent identity and goal
The agent is the Agent class name from gen_ai.agent.name, so a
SupportAgent class shows up as supportagent. When the resource
carries agentping.agent that wins, which is how to give several
classes one dashboard identity or match the slug the Laravel SDK would
use. With the package's own tracer provider the OpenTelemetry SDK reads
resource attributes from the environment:
OTEL_RESOURCE_ATTRIBUTES=agentping.agent=support_agent
The run's goal is the root span's name. Without other instrumentation
that is invoke_agent SupportAgent; under opentelemetry-auto-laravel
it is the HTTP route or queue job that triggered the agent, which is
usually the more useful of the two. Set agentping.goal on the root
span from your own code when neither says what the run was for.
Content capture
By default no prompt, instruction, model output, tool argument or tool result leaves the app; the package follows the GenAI conventions, which make content opt-in. AgentPing has the same posture, so what you see in the run timeline is metadata: models, providers, tokens, latency, tool names and finish reasons.
To see messages and tool payloads on the timeline, turn capture on in the package:
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
AI_OTEL_CAPTURE_MAX_LENGTH=8192
AgentPing then stores gen_ai.input.messages, gen_ai.output.messages
and gen_ai.system_instructions as step content on the llm_call, and
gen_ai.tool.call.arguments and gen_ai.tool.call.result as the
tool_call input and output, within the usual 64KB step allowance. The
package's redactor hook runs before anything is exported, so scrubbing
happens on your side of the wire.
Run completion and queues
Spans arrive in batches with no end-of-run signal, so a run ingested over OTLP completes once its root span has landed and no new span has arrived for two minutes. Costs, checks and evaluations then run as they do for SDK-reported runs. Expect a run to appear a couple of minutes after the agent finishes rather than immediately.
Each queued agent run is its own trace, and so its own run, unless
opentelemetry-auto-laravel is propagating trace context through the
queue, in which case the job's spans join the request that dispatched
it. Either way laravel.ai.invocation.id is on every span for
correlation.
Choosing between this and the Laravel SDK
| Laravel SDK | OpenTelemetry | |
|---|---|---|
| Dependencies | agentping/laravel only |
laravel-ai-otel plus the OpenTelemetry SDK and exporter |
| Run grouping | AgentPing::run() wraps several invocations into one run |
One trace per run; use opentelemetry-auto-laravel to group under a request |
| Run appears | On flush, within seconds | About two minutes after the last span |
| Provider failover | A failover step with the reason; run status unaffected | Errored llm_call followed by the successful one; run flagged as errored |
| Content on the timeline | Tool arguments and results by default, never prompts | Nothing by default; prompts, outputs and tool payloads when capture is on |
| Heartbeats, spend guard, manual events | Yes | No; OTLP is trace-only |
| Other backends | AgentPing only | Any OTLP backend from the same spans |
Both price model calls server-side from the same rate card and produce the same run, step and cost records, so switching later does not change what the dashboard shows.
Further reading
- OpenTelemetry for the endpoint, authentication and attribute conventions AgentPing accepts.
- Laravel SDK for the direct route.
- OpenTelemetry for AI agents for what agent spans contain and what OTel leaves to the backend.