Metadata-Version: 2.4
Name: prometa-sdk
Version: 0.10.1
Summary: Official Python SDK for the Prometa Agentic Lifecycle Intelligence Platform
Project-URL: Homepage, https://prometa.io
Project-URL: Documentation, https://prometa.io/docs/sdk/python
Project-URL: Source, https://github.com/prometa-ai/orchestra-python-sdk
Author: Prometa
License: Apache-2.0
Keywords: agents,genai,llm,observability,opentelemetry
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.40; extra == 'anthropic'
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Provides-Extra: google
Requires-Dist: google-genai>=0.3; extra == 'google'
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.3; extra == 'langchain'
Provides-Extra: openai
Requires-Dist: openai>=1.0; extra == 'openai'
Provides-Extra: openllmetry
Requires-Dist: opentelemetry-instrumentation-anthropic<1,>=0.60; (python_version >= '3.10') and extra == 'openllmetry'
Requires-Dist: opentelemetry-instrumentation-chromadb<1,>=0.60; (python_version >= '3.10') and extra == 'openllmetry'
Requires-Dist: opentelemetry-instrumentation-langchain<1,>=0.60; (python_version >= '3.10') and extra == 'openllmetry'
Requires-Dist: opentelemetry-instrumentation-openai<1,>=0.60; (python_version >= '3.10') and extra == 'openllmetry'
Requires-Dist: opentelemetry-instrumentation-pinecone<1,>=0.60; (python_version >= '3.10') and extra == 'openllmetry'
Requires-Dist: opentelemetry-sdk<2,>=1.38; (python_version >= '3.10') and extra == 'openllmetry'
Provides-Extra: openllmetry-all
Requires-Dist: opentelemetry-instrumentation-anthropic<1,>=0.60; (python_version >= '3.10') and extra == 'openllmetry-all'
Requires-Dist: opentelemetry-instrumentation-bedrock<1,>=0.60; (python_version >= '3.10') and extra == 'openllmetry-all'
Requires-Dist: opentelemetry-instrumentation-chromadb<1,>=0.60; (python_version >= '3.10') and extra == 'openllmetry-all'
Requires-Dist: opentelemetry-instrumentation-cohere<1,>=0.60; (python_version >= '3.10') and extra == 'openllmetry-all'
Requires-Dist: opentelemetry-instrumentation-haystack<1,>=0.60; (python_version >= '3.10') and extra == 'openllmetry-all'
Requires-Dist: opentelemetry-instrumentation-langchain<1,>=0.60; (python_version >= '3.10') and extra == 'openllmetry-all'
Requires-Dist: opentelemetry-instrumentation-llamaindex<1,>=0.60; (python_version >= '3.10') and extra == 'openllmetry-all'
Requires-Dist: opentelemetry-instrumentation-openai<1,>=0.60; (python_version >= '3.10') and extra == 'openllmetry-all'
Requires-Dist: opentelemetry-instrumentation-pinecone<1,>=0.60; (python_version >= '3.10') and extra == 'openllmetry-all'
Requires-Dist: opentelemetry-sdk<2,>=1.38; (python_version >= '3.10') and extra == 'openllmetry-all'
Description-Content-Type: text/markdown

# prometa-sdk (Python)

[![PyPI version](https://img.shields.io/pypi/v/prometa-sdk.svg)](https://pypi.org/project/prometa-sdk/)
[![Python](https://img.shields.io/pypi/pyversions/prometa-sdk.svg)](https://pypi.org/project/prometa-sdk/)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)

Official Python SDK for the **Prometa Agentic Lifecycle Intelligence Platform**.

Wraps OpenTelemetry GenAI semantic conventions with `@prometa` decorators that
automatically emit lifecycle metadata to your Prometa instance via OTLP/JSON.
The SDK ships several telemetry surfaces that make agent behavior queryable,
evaluable, and joinable on the platform. The current release adds canonical
`prometa.agent_id` correlation for pinned agents, public custom span
attributes for tenant-owned integration metadata, and richer MCP
tool-call spans with Prometa/GenAI tool-name aliases plus raw-channel-gated
argument and result capture.

- **Lifecycle decorators** — `@prometa.workflow / .agent / .tool / .task`
  wrap any sync/async function and emit a span carrying `solution_id`,
  `stage`, `agent.name`, kind, and parent/child relationships.
- **Correlation-chain setters** — `set_customer_id`, `set_user_id`,
  `set_conversation_id`, `set_request_model`, `set_tool_name`. Light up
  the platform's canonical correlation chain
  (`org:sol:agent:tool:cus:user::session:trace:span`) so registry /
  AML (Agentic Maturity Leveling) / lineage readers can join across the
  full identity prefix. Optional
  but unlocks the richer end-to-end view.
- **Custom span attributes** — `set_attribute` / `set_attributes` stamp
  scalar attributes such as `your.integration.*` on the active span. Prometa
  preserves non-promoted attributes in span metadata for governance and
  evaluation workflows.
- **Assistant intent labels** — `set_assistant_intent` /
  `set_assistant_intent_from_text` stamp deterministic Prometa intent
  labels before LLM, tool, or action work.
- **User feedback feeding** — `set_user_feedback` /
  `record_user_feedback` collect thumbs-up / thumbs-down, 1-5 star
  ratings, and open-text comments as generic `prometa.feedback.*`
  telemetry for platform ingestion.
- **AQL (Agentic Quality Leveling) trace metadata** — lifecycle,
  correlation, refs, intent, feedback, prompt, completion, usage, and
  model attributes give Prometa's AQL / PrometaQL query and evaluation
  layer stable fields to filter, aggregate, replay, and judge traces.
- **AML (Agentic Maturity Leveling) v0.4 instrumentation contract** —
  16 helpers (`pii_filter`, `guardrail`, `memory_read`,
  `record_retry_attempt`, `confidence_score`, `schema_validate`,
  `model_route`, `sentiment_classify`, …) that emit the spans the
  platform's AML scoring engine consumes to score agents against the
  41-feature catalog.

## Install

```bash
pip install prometa-sdk
```

Current source version: **0.10.1**. Release history is on
[PyPI](https://pypi.org/project/prometa-sdk/#history).

**Repository:** [`prometa-ai/orchestra-python-sdk`](https://github.com/prometa-ai/orchestra-python-sdk) — canonical source. Releases publish from GitHub Actions via OIDC Trusted Publishing on `v*` tag push (see [`.github/workflows/publish.yml`](.github/workflows/publish.yml) and the [`Release`](.github/workflows/release.yml) one-click workflow). Older docs may still mention `sdks/python/` in the platform monorepo; that path is obsolete for Python.

## Quick start

```python
import asyncio
from prometa import Prometa

prometa = Prometa(
    endpoint="https://prometa.example.com/api/v2/otlp/v1/traces",
    api_key="prm_live_...",
    solution_id="sol_abc123",
    agent_name="customer-support",
    stage="production",
)

@prometa.workflow(name="handle-ticket")
async def handle_ticket(ticket_id: str) -> str:
    @prometa.agent(name="classifier")
    async def classify() -> str:
        return "billing"

    @prometa.tool(name="kb-search")
    async def kb_search(q: str) -> list[str]:
        return ["doc1", "doc2"]

    category = await classify()
    results = await kb_search(category)
    return f"resolved {ticket_id} via {results}"

asyncio.run(handle_ticket("T-1234"))
prometa.flush()
```

## What gets captured

Each decorated function emits a span with:

- `prometa.kind` — `workflow | agent | tool | task`
- `prometa.solution_id`, `prometa.stage`
- `gen_ai.agent.name`
- `prometa.agent_id` — only when you explicitly pin one; otherwise the platform auto-registers the Agent from `solution_id` + `agent_name`
- `gen_ai.agent.id` — legacy compatibility only; Prometa correlation keys on `prometa.agent_id` or the name fallback
- `gen_ai.conversation.id` — when the producer opts into session grouping (see below)
- Parent/child relationships across async/sync calls
- Errors → span status `error` plus `error.message`

## Grouping traces into conversational sessions

A chat-style agent typically produces many traces per user conversation
(one per message turn, one per background tool call, one per retry).
The platform's **Session Explorer** groups all traces sharing a session
id into one row, with aggregated cost, tokens, duration, and a
side-by-side conversation timeline that spans the whole session.

To opt in, stamp the session id on the current span — anywhere inside
a `@prometa.workflow / .agent / .tool / .task` block:

```python
from prometa import set_session_id

@prometa.workflow(name="handle-turn")
async def handle_turn(conversation_id: str, user_message: str):
    set_session_id(conversation_id)   # any opaque key your app uses
    # ... do the work; nested spans inherit automatically
```

Or, when the id is known at decorator time, use the `session_id=` kwarg:

```python
@prometa.workflow(name="handle-turn", session_id=conversation_id)
async def handle_turn(...): ...
```

Either form writes the OTel-standard `gen_ai.conversation.id` attribute
onto the span; the platform ingest reads it (and accepts `session.id`
or `prometa.session_id` as fallbacks for non-Prometa producers) and
propagates it onto every span + the trace row at write time. Nothing
else needs to be configured.

**Use opaque ids, not user-identifying values.** The session id is
indexed and visible to anyone with `traces:read` permission. Don't
stuff emails, names, or PII in there.

**Session retention** mirrors trace retention (currently 365 days).
Long-running sessions touching old + new traces will appear truncated
once the oldest member trace ages out — acceptable for chat workloads,
flag if you have multi-week audit needs.

## Correlation-chain helpers (v0.5.0+)

The platform's correlation-id resolver consumes five optional OTLP
attributes to materialise its canonical chain end-to-end. Setting them
is purely additive — without them, the unset chain segments stay
empty but the platform still works; with them, every reader on the
platform side (registry, AML scoring, incident lineage, annotation
chain queries) joins by a single canonical address.

```python
from prometa import (
    Prometa,
    set_customer_id,    # → prometa.customer_id
    set_user_id,        # → gen_ai.user.id (+ prometa.user.id fallback)
    set_conversation_id,# → gen_ai.conversation.id (alias of set_session_id)
    set_request_model,  # → gen_ai.request.model
    set_tool_name,      # → prometa.tool_name (on tool-typed spans)
)

prometa = Prometa(
    endpoint="https://prometa.example.com/api/v2/otlp/v1/traces",
    api_key="prm_live_...",
    solution_id="sol_billing",
    agent_name="support-assistant",
    customer_id="cus_org_wide_default",   # org-wide default; overridable per-span
)

@prometa.workflow(name="handle-ticket")
def handle(ticket):
    # Per-span override of customer_id wins over the constructor
    # default for this span AND every nested span (parent-attribute
    # inheritance in the span builder).
    set_customer_id(ticket.customer_external_id)
    set_user_id(ticket.agent_email)
    set_conversation_id(ticket.thread_id)

    @prometa.tool(name="search-kb")
    def lookup():
        set_tool_name("knowledge-base-search")   # auto-registers Tool entity in PG
        ...
```

| Helper | OTLP key | Platform-side effect |
|---|---|---|
| `set_customer_id` | `prometa.customer_id` | Validated against `Organization.customerNamespace` regex at ingest; bridges Prometa telemetry to your CRM / data warehouse |
| `set_user_id` | `gen_ai.user.id` + `prometa.user.id` | End-user attribution; lights up the user segment of the chain |
| `set_conversation_id` | `gen_ai.conversation.id` | Auto-registers a Session row in Postgres; equivalent to `set_session_id` |
| `set_request_model` | `gen_ai.request.model` | Cost rollup keys on this; LLM-instrumentation libs usually set it automatically |
| `set_tool_name` | `prometa.tool_name` | Auto-registers a Tool row per `(orgId, solutionId, name)` triple on first sighting |

All five helpers follow the same contract as `set_session_id`:
synchronous, no-op outside an active span context (returns `False`),
empty value pops the attribute. See the platform-side design at
[`resources/correlation/correlation-id-design.md`](https://github.com/caglarsubas/agent-hook-v2/blob/main/resources/correlation/correlation-id-design.md)
for the full canonical-chain grammar.

## Custom span attributes

Use `set_attribute` and `set_attributes` when an integration needs to
stamp scalar metadata that Prometa should preserve, but that is not part
of the core correlation chain.

```python
from prometa import set_attribute, set_attributes

@prometa.tool(name="prepare-action")
def prepare_action():
    set_attribute("your.integration.mcp.server.name", "example-server")
    set_attributes(
        {
            "your.integration.mcp.tool.name": "prepare_action",
            "your.integration.mcp.direct_action": False,
            "your.integration.mcp.args_count": 3,
        }
    )
```

Values must be `str`, `int`, `float`, or `bool`. Both helpers return
`False` when called outside an active span.

## Assistant intent labels

Applications can stamp assistant intent before any LLM/tool/action work
so Prometa can index and filter traces by the user's intended operation.

```python
from prometa import set_assistant_intent, set_assistant_intent_from_text

@prometa.workflow(name="assistant-turn")
def handle_turn(user_text: str, from_quick_action: bool = False):
    if from_quick_action:
        set_assistant_intent(
            "D,E",
            source="quick_action",
            preclassified=True,
        )
    else:
        set_assistant_intent_from_text(user_text)

    # Nested LLM/tool/action spans inherit the labels unless they
    # explicitly override them.
    ...
```

Labels are stable single-letter codes:

| Code | Label name |
|---|---|
| `A` | `general_information_gathering` |
| `B` | `pipeline_flow_information_gathering` |
| `C` | `current_status_information_gathering` |
| `D` | `configuration_editing_execution` |
| `E` | `flow_process_execution` |

The SDK stamps platform-indexable Prometa trace attributes:

- `prometa.intent.labels`, `prometa.intent.label_names`,
  `prometa.intent.count`, `prometa.intent.source`,
  `prometa.intent.preclassified`, `prometa.intent.classifier_version`

Free-text turns use deterministic clause decomposition, so a request
such as "change the settings, then run the flow" emits `D,E` without
LLM token usage. Provider integrations also classify the latest
`role: "user"` text automatically when no active parent span already
has intent labels.

For deterministic UI actions, pass local-only kwargs through supported
LLM integrations; the SDK strips them before calling the provider:

```python
client.responses.create(
    model="gpt-4o-mini",
    input=[{"role": "user", "content": prompt}],
    prometa_intent_labels="D,E",
    prometa_intent_source="quick_action",
    prometa_intent_preclassified=True,
)
```

## User feedback feeding

Applications can feed user feedback into Prometa as generic
`prometa.feedback.*` telemetry. The SDK supports thumbs-up /
thumbs-down, 1-5 star ratings, open-text comments, and optional target
ids so the platform can attach delayed feedback to the original trace,
span, or session.

If feedback is collected before the traced workflow exits, stamp it on
the active span:

```python
from prometa import set_user_feedback

@prometa.workflow(name="assistant-turn")
def handle_turn(user_text: str):
    answer = run_assistant(user_text)

    if user_clicked_dislike:
        set_user_feedback(
            liked=False,
            comment="Missed the billing policy exception.",
            source="thumbs_down",
            feedback_id="fb_123",
            user_id="user_456",
        )

    return answer
```

If feedback arrives later from a UI callback or API endpoint, emit a
dedicated `feedback.record` span:

```python
from prometa import record_user_feedback

record_user_feedback(
    rating=5,
    comment="Exactly what I needed.",
    source="stars",
    target_trace_id=trace_id,
    target_span_id=span_id,
    target_session_id=session_id,
    submitted_at="2026-06-05T09:30:00Z",
)
```

The SDK emits these platform-facing attributes:

- `prometa.feedback.signal` — `like`, `dislike`, `rating`, `comment`,
  or a comma-separated combination.
- `prometa.feedback.liked` — boolean thumbs signal when supplied.
- `prometa.feedback.rating` — integer 1-5 star score when supplied.
- `prometa.feedback.score` — normalized score in `[-1.0, 1.0]`.
- `prometa.feedback.sentiment` — `positive`, `neutral`, or `negative`.
- `prometa.feedback.comment` — open-text comment, truncated to 4096
  characters.
- `prometa.feedback.source`, `prometa.feedback.id`,
  `prometa.feedback.user_id`, `prometa.feedback.submitted_at`.
- `prometa.feedback.target.trace_id`,
  `prometa.feedback.target.span_id`,
  `prometa.feedback.target.session_id`.

Avoid putting PII in comments or user ids unless your Prometa
deployment is configured for that data class.

### Stable Agent IDs

`agent_id` is optional. By default the SDK emits `solution_id` and
`agent_name`, then the platform mirrors the Tool registration model:
on first sighting it auto-registers the Agent row for the
`(orgId, solutionId, agentName)` tuple and attaches the canonical
Agent ID during ingest.

You can still pin an ID by passing `agent_id="..."` to `Prometa(...)`
or setting `PROMETA_AGENT_ID`. When pinned, the SDK includes
`prometa.agent_id` on resource and span attributes, alongside
`gen_ai.agent.name` and `service.name`. The SDK also emits
`gen_ai.agent.id` for legacy readers, but Prometa correlation should key
on `prometa.agent_id` and fall back to the name tuple when absent. When
absent, the SDK deliberately omits both ID attributes; it does not
generate a random per-process fallback.

### Agent names — always set them

`agent_name` is the customer-owned half of the
`(orgId, solutionId, agent_name)` tuple the platform's Agent registry
keys on. Two apps in the same solution that share the same
`agent_name` collapse into a single Agent row — every downstream
metric (latency, error rate, PAMI, cost) then fans across the wrong
population.

Resolution precedence:

1. Explicit `agent_name="..."` kwarg to `Prometa(...)`.
2. `PROMETA_AGENT_NAME` environment variable.
3. Literal fallback `"prometa-agent"`, **emitted with a `UserWarning`**
   at startup so the collision risk is visible in your logs the
   moment you run an unconfigured app.

```bash
# Production
export PROMETA_AGENT_NAME=support-assistant
```

```python
# Or per-instance
prometa = Prometa(endpoint=..., agent_name="support-assistant")
```

The fallback warning is intentional: silent registry collisions
were the most-reported "AML score shows 0" symptom before this
warning landed. If you genuinely want the literal name
`"prometa-agent"`, pass it explicitly (`agent_name="prometa-agent"`)
— the warning fires only on the unset path.

## AML v0.4 instrumentation contract

The SDK ships 16 helpers that emit the spans the platform's AML
scoring engine consumes to score agents against its 41-feature
catalog. The full catalog lives at
[`resources/aml/phase-0/catalog.yaml`](https://github.com/caglarsubas/agent-hook-v2/blob/main/resources/aml/phase-0/catalog.yaml)
on the platform; the SDK-side primitives are:

```python
from prometa import (
    # Safety / governance (A1-A8)
    pii_filter, guardrail, prompt_render, auth_check, consent_check,
    # Knowledge & memory (B1-B5)
    cache_lookup, memory_read, memory_write, retrieval_query,
    # Reasoning (C2-C5)
    plan_generate, confidence_score, schema_validate, sentiment_classify,
    # Orchestration & proactivity (E1-E6)
    event_trigger, reviewer_invoke,
    record_retry_attempt, record_circuit_breaker_state,
    # Observability (F1)
    model_route,
)

with guardrail("ethical", raw_input=user_query) as g:
    v = my_classifier.check(user_query)
    g.verdict("block" if v.harmful else "pass", confidence=v.score)

with pii_filter("input", raw_input=text) as pii:
    cleaned, matches = redactor.scrub(text)
    pii.result(matches_found=len(matches),
               match_categories=[m.kind for m in matches])

prometa.raw_channel.enable()   # dual-channel raw capture (opt-in)
```

Each helper is a context manager (or a synchronous record call for
fire-and-forget events) that emits a typed span with the attribute
shape the AML detectors expect. Calling them from inside an active
`@prometa.workflow / .agent / .tool` decorator nests the AML spans
under the parent — no extra wiring needed.

## AQL / PrometaQL query readiness

AQL is the platform-side query and evaluation layer over the telemetry
this SDK emits. That is why it does not have a separate family of
`aql_*` instrumentation helpers: AML helpers create additional detector
evidence spans, while AQL reads the normalized trace/span attributes
already emitted by decorators, setters, refs, and LLM integrations.

To make traces useful for AQL queries and judge/replay workflows, stamp
the stable fields AQL filters and aggregates on:

| SDK surface | AQL-readable signal |
|---|---|
| `@prometa.workflow / .agent / .tool / .task` | `trace_id`, `span_id`, parent/child edges, `prometa.kind`, `prometa.solution_id`, `gen_ai.agent.name` |
| `set_customer_id`, `set_user_id`, `set_conversation_id`, `set_request_model`, `set_tool_name` | canonical customer, user, session, model, and tool dimensions |
| `set_assistant_intent` / `set_assistant_intent_from_text` | `prometa.intent.*` filters for user-turn intent and preclassified UI actions |
| `set_input_ref`, `set_output_ref`, `current_span_id` | lineage edges for replay, judge, and flow-level queries |
| LLM integrations | `gen_ai.prompt`, `gen_ai.prompt.user`, `gen_ai.completion`, token usage, response model, finish reasons |
| AML helpers | typed evidence spans that AQL can join with ordinary lifecycle and LLM spans |

In short: instrument once with the SDK, then run AQL / PrometaQL on the
Prometa platform to query traces, compare sessions, inspect failure
patterns, build eval cohorts, and feed LLM-as-judge or replay tooling.

## LLM client auto-instrumentation

For traces to show **token usage, cost, and prompt/completion text**,
opt in to the per-client patcher matching the LLM library you use.
Without this, spans render but the cost panel reads `$0.000` and the
trace UI has no prompt/completion to display.

```python
from prometa import Prometa
from prometa.integrations import openai as prometa_openai
from prometa.integrations import anthropic as prometa_anthropic
from prometa.integrations import google as prometa_google

Prometa(endpoint=..., agent_name="my-agent")

# Call install() once at startup. Each returns False (no-op) if the
# corresponding library isn't installed — so it's safe to call all three.
prometa_openai.install()
prometa_anthropic.install()
prometa_google.install()
```

Once installed, every `client.chat.completions.create(...)`,
`client.messages.create(...)`, and `client.models.generate_content(...)`
call (sync, async, **and streaming**) emits a child span carrying:

- `gen_ai.system` (`openai` / `anthropic` / `google`)
- `gen_ai.request.model`, `temperature`, `top_p`, `max_tokens`
- `gen_ai.usage.input_tokens` / `gen_ai.usage.output_tokens` → drives cost
- `gen_ai.prompt` (truncated JSON of input messages)
- `gen_ai.completion` (truncated assistant reply)
- `gen_ai.response.id`, `gen_ai.response.model`, `gen_ai.response.finish_reasons`

Streaming spans propagate context properly — any `@prometa.tool` /
`@prometa.agent` invoked from inside the stream consumer nests under
the LLM span, not under whatever was active when `.create()` returned.

### How the trace "Conversation" panel populates

The Prometa trace UI renders a **Conversation** panel that derives turns
directly from the span attributes emitted by the integrations on this
page. For each LLM span:

- The user turn shows `gen_ai.prompt.user` — the latest `role: "user"`
  message, pre-extracted by the SDK from the `messages` / `contents`
  array at instrumentation time.
- The agent turn shows `gen_ai.completion` — the assistant reply.

Token counts and timestamps come straight off the span. Nothing else
needs to be wired up on the platform side.

`gen_ai.prompt` (the full messages-array JSON) is also captured for
debugging — that's what downstream judge / replay tooling reads when
it needs the complete prompt context, including system instructions
and history. The Conversation panel intentionally surfaces only
`gen_ai.prompt.user` to keep the chat view readable; the full payload
is one click away on the span detail.

The **After preprocessing** vs **Raw** toggle is also rendered, but
both modes show the same text until the platform's PII redactor /
policy gate ships and starts populating the `prometa.conversation_turns`
table. When that lands, the panel will switch back to reading the
processed-vs-raw pair from that table; the SDK contract does not
change.

## OpenLLMetry bridge (optional)

Prometa can also use Traceloop's OpenLLMetry instrumentors as the
first-choice auto-instrumentation layer. OpenLLMetry is Apache-2.0 and
its instrumentors are standard OpenTelemetry instrumentations, so the
SDK keeps them optional and bridges their finished OTel spans back into
Prometa's existing OTLP/JSON shipper.

```bash
pip install "prometa-sdk[openllmetry]"
```

```python
from prometa import Prometa
from prometa.integrations import openllmetry

Prometa(endpoint=..., api_key=..., solution_id=..., agent_name="my-agent")

result = openllmetry.install()
# {'openai': True, 'anthropic': True, 'langchain': True,
#  'chromadb': True, 'pinecone': True}
```

By default this attempts OpenLLMetry for OpenAI, Anthropic, LangChain /
LangGraph, Chroma, and Pinecone. If an OpenLLMetry package or target
library is missing, `fallback=True` uses Prometa's native wrappers for
the same target where one exists. The bridge also maps OpenLLMetry's
newer `gen_ai.input.messages` / `gen_ai.output.messages` attributes
onto Prometa's existing `gen_ai.prompt`, `gen_ai.prompt.user`, and
`gen_ai.completion` fields so current trace UI behavior stays stable.

For broader OpenLLMetry coverage, install:

```bash
pip install "prometa-sdk[openllmetry-all]"
```

Then pass the extra targets explicitly:

```python
openllmetry.install(
    targets=[
        "openai", "anthropic", "langchain", "chromadb", "pinecone",
        "bedrock", "cohere", "haystack", "llamaindex",
    ]
)
```

**Migration note:** do not install both `openllmetry.install()` and the
matching native `prometa.integrations.openai.install()` /
`anthropic.install()` wrappers for the same process unless you are
intentionally comparing span output; double-patching a client can emit
duplicate spans. Existing customers can stay on the native wrappers and
migrate target-by-target when ready.

## Reliability & retry semantics

The SDK ships traces over OTLP/JSON with **at-least-once** delivery: a
background thread flushes the in-memory span buffer every
`flush_interval_seconds` (default `2.0`), and on any send failure
(network blip, timeout, slow server response) the spans are
re-buffered and retried on the next flush.

**The platform deduplicates by id at the storage layer.**
`prometa.spans` and `prometa.traces` are backed by ClickHouse's
`ReplacingMergeTree` (or `SharedReplacingMergeTree` on ClickHouse
Cloud), keyed by `(trace_id, span_id)` and `(org_id, trace_id)`
respectively. Any number of duplicate sends of the same span collapse
to a single row during background merges; user-facing read paths
(trace explorer, session explorer, conversation panel, cost panels)
use `SELECT … FINAL` to enforce dedup at read time too. Cost and
token aggregates are not inflated by retries.

**Net: use the default `flush_interval_seconds=2.0`** even on
long-running requests (RAG pipelines, multi-round tool loops, chat
turns spanning tens of seconds). No consumer-side workaround needed.

If you previously raised the interval (e.g. to `120.0`) to dodge
platform-side double-counting in the cost / conversation panels, you
can revert to the default. The platform-side dedup landed in the
release alongside this SDK version (see CHANGELOG.md).

## Configuration

| Param | Env var | Default |
|---|---|---|
| `endpoint` | — | required |
| `api_key` | `PROMETA_API_KEY` | none |
| `solution_id` | — | none |
| `agent_name` | — | `"prometa-agent"` |
| `agent_id` | `PROMETA_AGENT_ID` | none — when set, the SDK emits canonical `prometa.agent_id`; when omitted, the platform auto-registers/attaches the canonical Agent ID from `(orgId, solutionId, agentName)` |
| `stage` | — | `"development"` |
| `customer_id` | — | none — org-wide default for `prometa.customer_id` |
| `flush_interval_seconds` | — | `2.0` |
| `timeout_seconds` | — | `5.0` |

## Architectural fit

Prometa's stack is a multi-language SDK family plus a single platform.
This SDK is the Python edge of that family; sister bindings ship for
[Node.js](https://github.com/prometa-ai/orchestra-node-sdk) (`prometa-sdk`)
and [Java](https://github.com/prometa-ai/orchestra-java-sdk) (`io.prometa:prometa-sdk`).
All three emit the same OTLP attribute shape so the platform's
correlation-id resolver materialises the same canonical chain
regardless of which language the agent is written in.

The architectural picture, end-to-end:

```
┌──────────────┐                           ┌──────────────────────────┐
│  Your agent  │   OTLP/JSON (this SDK)    │  Prometa platform        │
│  (Python)    │ ─────────────────────────►│  /api/v2/otlp/v1/traces  │
└──────────────┘                           │  ┌──────────────────┐    │
                                           │  │ correlation      │    │
       ▼ Spans carry:                      │  │ resolver         │    │
                                           │  │ (PG-side)        │    │
   `prometa.solution_id`                   │  └────────┬─────────┘    │
   `gen_ai.agent.name`                     │           │              │
   `prometa.agent_id`        (optional)    │           │              │
   `prometa.tool_name`        (optional)   │  canonical agent_id/etc  │
   `prometa.customer_id`      (optional)   │           ▼              │
   `gen_ai.conversation.id`   (optional)   │  ┌──────────────────┐    │
   `gen_ai.user.id`           (optional)   │  │ ClickHouse       │    │
   `gen_ai.request.model`     (optional)   │  │ (telemetry)      │    │
   AML v0.4 helper spans      (optional)   │  └──────────────────┘    │
                                           └──────────────────────────┘
```

Once the canonical ids land in ClickHouse, the platform's readers
(registry, AML scoring engine, incidents, annotations, workflow runs)
all join on the same chain. The end-to-end design lives in
[`resources/correlation/correlation-id-design.md`](https://github.com/caglarsubas/agent-hook-v2/blob/main/resources/correlation/correlation-id-design.md).

**Technology dependencies**

- Python ≥ 3.9
- `urllib` (standard library) for OTLP POST
- No required third-party deps; LLM auto-instrumentation hooks are
  opt-in and only run when the corresponding library is installed.
- Optional OpenLLMetry bridge extras require Python ≥ 3.10 because the
  current OpenLLMetry packages do.

## Contributing

Use a **`feat/...`** branch for each change set, open a PR to **`main`**, and integrate **only** via GitHub’s PR merge (do not push `main` directly). Details: [CONTRIBUTING.md](CONTRIBUTING.md).

## License

Apache-2.0
