Skip to main content

Observability

Instrument your agents with OpenTelemetry using the SDK’s hooks system to capture traces for every agent turn and tool use.

AI Router  Beta

Route your Claude calls through the AI Router with a single base URL change. Zero vendor lock-in: always run on the best model at the lowest cost for your use case.

Observability

Overview

The Claude Agent SDK (claude-code-sdk) drives the Claude CLI as a programmable agent supporting multi-turn conversations, tool use, and MCP servers. Use the SDK’s built-in hooks system to attach OpenTelemetry instrumentation at key execution points. Every tool invocation becomes a span, and the full agent workflow is captured as a trace in orq.ai without modifying your agent logic.

Key Benefits

Full Agent Visibility

Track every agent turn, tool use, and LLM call with detailed traces and analytics

Zero Code Changes

Attach observability through hooks without modifying your agent logic

Cost Tracking

Real-time cost and token usage per agent run synced to Orq.ai

Tool Inspection

Inspect every tool input and output with full execution context

Prerequisites

To set up your API key, see API keys & Endpoints.

Install Dependencies

pip install claude-code-sdk \
            opentelemetry-sdk opentelemetry-exporter-otlp

Configuration

Set up the OTel tracer provider to export spans to Orq.ai:
Python
import os
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry import trace

tracer_provider = TracerProvider()
tracer_provider.add_span_processor(
    BatchSpanProcessor(
        OTLPSpanExporter(
            endpoint="https://api.orq.ai/v2/otel/v1/traces",
            headers={"Authorization": f"Bearer {os.environ['ORQ_API_KEY']}"},
        )
    )
)
trace.set_tracer_provider(tracer_provider)
tracer = trace.get_tracer(__name__)

Basic Example

Define a PostToolUse hook to create a span for each tool call, then wrap the query() call in a root workflow span:
Python
import asyncio
import os
from typing import Any
from claude_code_sdk import (
    query,
    ClaudeCodeOptions,
    HookMatcher,
    HookContext,
    AssistantMessage,
    TextBlock,
    ResultMessage,
)
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry import trace

tracer_provider = TracerProvider()
tracer_provider.add_span_processor(
    BatchSpanProcessor(
        OTLPSpanExporter(
            endpoint="https://api.orq.ai/v2/otel/v1/traces",
            headers={"Authorization": f"Bearer {os.environ['ORQ_API_KEY']}"},
        )
    )
)
trace.set_tracer_provider(tracer_provider)
tracer = trace.get_tracer(__name__)


async def post_tool_use_hook(data: dict[str, Any], tool_name: str | None, _ctx: HookContext):
    with tracer.start_as_current_span(f"tool.{tool_name or 'unknown'}") as span:
        span.set_attribute("tool.name", tool_name or "unknown")
        if "input" in data:
            span.set_attribute("tool.input", str(data["input"])[:500])
        if "output" in data:
            span.set_attribute("tool.output", str(data["output"])[:500])
    return None


async def main():
    options = ClaudeCodeOptions(
        model="sonnet",
        system_prompt="You are a helpful coding assistant.",
        max_turns=3,
        allowed_tools=["Read", "Bash"],
        hooks={
            "PostToolUse": [
                HookMatcher(matcher=None, hooks=[post_tool_use_hook])
            ]
        },
    )

    prompt = "What Python version is installed on this system?"
    print(f"User: {prompt}\n")

    with tracer.start_as_current_span("agent-workflow") as span:
        span.set_attribute("agent.prompt", prompt)

        async for message in query(prompt=prompt, options=options):
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Assistant: {block.text}")
            elif isinstance(message, ResultMessage):
                span.set_attribute("agent.total_cost_usd", message.total_cost_usd or 0)
                span.set_attribute("agent.num_turns", message.num_turns)
                print(f"\nCost: ${message.total_cost_usd or 0:.6f} | Turns: {message.num_turns}")

    tracer_provider.force_flush()
    print("\nTraces exported to orq.ai. View them at https://my.orq.ai")


asyncio.run(main())

View Traces

View your traces in the AI Studio in the Traces tab.
Visit your AI Studio to view real-time analytics and traces.

AI Router

This feature is in Beta. Traces are not supported while using the AI Router with the Claude Agent SDK.

Overview

The Anthropic SDK is the native client for Claude models. Connect it to Orq.ai’s AI Router with a single base_url change and no modifications to your existing agent logic.

Install

pip install anthropic

Configuration

Set base_url to the AI Router endpoint and authenticate with your Orq.ai API key:
Python
import os
import anthropic

client = anthropic.Anthropic(
    api_key=os.environ["ORQ_API_KEY"],
    base_url="https://my.orq.ai/v3/router/",
)
Set base_url to https://my.orq.ai/v3/router/

Basic Example

Python
import os
import anthropic

client = anthropic.Anthropic(
    api_key=os.environ["ORQ_API_KEY"],
    base_url="https://my.orq.ai/v3/router/",
)

message = client.messages.create(
    model="anthropic/claude-sonnet-4-6",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Explain the orq.ai AI Router in one paragraph."}],
)

print(message.content[0].text)

Streaming

Python
import os
import anthropic

client = anthropic.Anthropic(
    api_key=os.environ["ORQ_API_KEY"],
    base_url="https://my.orq.ai/v3/router/",
)

with client.messages.stream(
    model="anthropic/claude-sonnet-4-6",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Write a haiku about AI routing."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)

Other Integrations

Claude Code

Route Claude Code through the AI Router using environment variables. Integrate Claude Code with our MCP to access 20+ tools and actions on the platform.

Claude Desktop

Connect your Orq.ai workspace to Claude Desktop using the MCP integration.