Metadata-Version: 2.4
Name: respan-instrumentation-openrouter
Version: 0.1.1
Summary: Respan instrumentation plugin for OpenRouter's OpenAI-compatible Python usage
License: Apache 2.0
Author: Respan
Author-email: team@respan.ai
Requires-Python: >=3.11,<3.14
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Dist: openai (>=3.0.0,<4.0.0)
Requires-Dist: opentelemetry-semantic-conventions-ai (>=0.4.1)
Requires-Dist: respan-instrumentation-openai (>=1.2.1,<2.0.0)
Requires-Dist: respan-sdk (>=2.6.26)
Requires-Dist: respan-tracing (>=2.17.0,<3.0.0)
Description-Content-Type: text/markdown

# Respan OpenRouter instrumentation

Trace OpenRouter calls made through the OpenAI-compatible Python client with Respan.

OpenRouter's Python usage is OpenAI-compatible, so this package delegates SDK
patching to `respan-instrumentation-openai` and adds a package-local processor
that normalizes those spans as OpenRouter spans before export.

The processor emits `gen_ai.system=openrouter` and
`gen_ai.provider.name=openrouter`, canonical request tool definitions and
current-turn completion tool calls, modern and legacy usage fields, precise
OTEL error status, and both current and Traceloop streaming flags. It removes
legacy tool aliases rather than duplicating the canonical fields.

## Install

```bash
pip install respan-ai respan-instrumentation-openrouter openai
```

Version `0.1.0` is validated with OpenAI Python `>=3.0.0,<4.0.0` and the
`respan-instrumentation-openai` `>=1.2.1,<2.0.0` delegate surface. These bounds
are intentional because OpenRouter's stream bridge integrates with that
delegate's current lifecycle hooks.

## Environment

| Variable | Required | Description |
|----------|----------|-------------|
| `RESPAN_API_KEY` | Yes | Respan API key for trace export. |
| `RESPAN_BASE_URL` | No | Defaults to `https://api.respan.ai/api`. |
| `OPENROUTER_API_KEY` | Yes | OpenRouter API key for direct OpenRouter calls. |
| `OPENROUTER_BASE_URL` | No | Defaults to `https://openrouter.ai/api/v1`. |
| `OPENROUTER_MODEL` | No | Defaults to `openai/gpt-4o-mini`. |

The examples can also run through a Respan gateway configuration by providing
`OPENROUTER_USE_RESPAN_GATEWAY=true`, `RESPAN_GATEWAY_API_KEY`,
`RESPAN_GATEWAY_BASE_URL`, and `RESPAN_MODEL`. Without direct OpenRouter or
explicit gateway configuration, the example set uses a local OpenAI-compatible
mock so instrumentation and Respan export can still be verified.

## Usage

```python
import os

from openai import OpenAI
from respan import Respan, workflow
from respan_instrumentation_openrouter import OpenRouterInstrumentor

respan = Respan(
    api_key=os.environ["RESPAN_API_KEY"],
    base_url=os.getenv("RESPAN_BASE_URL", "https://api.respan.ai/api"),
    instrumentations=[OpenRouterInstrumentor()],
)

client = OpenAI(
    api_key=os.environ["OPENROUTER_API_KEY"],
    base_url=os.getenv("OPENROUTER_BASE_URL", "https://openrouter.ai/api/v1"),
)


@workflow(name="openrouter_chat_example")
def run_chat() -> str:
    response = client.chat.completions.create(
        model=os.getenv("OPENROUTER_MODEL", "openai/gpt-4o-mini"),
        messages=[
            {
                "role": "user",
                "content": "Reply with one concise sentence about observability.",
            }
        ],
    )
    return response.choices[0].message.content or ""


try:
    print(run_chat())
finally:
    respan.flush()
    respan.shutdown()
```

`OpenRouterInstrumentor(normalize_all_openai_spans=True)` is the default because
the package is intended for OpenRouter-specific OpenAI-compatible clients. Set
`normalize_all_openai_spans=False` if the same process also emits regular OpenAI
spans and only spans with OpenRouter URL markers should be rewritten.

Set `capture_content=False` to retain provider, model, usage, stream, lifecycle,
and error data while removing prompt, completion, tool-definition, and tool-call
content. Captured chat content is secret-redacted and bounded by UTF-8 bytes;
embedding vectors remain intact under the shared span contract.

