Python SDK

Updated on Aug 14, 2026

Package: progress-observability

Overview

The Python SDK provides zero-intrusion telemetry for AI agents and LLM applications. It automatically instruments supported LLM providers and agent frameworks — you only need a one-line initialization call.

Use the Python SDK when your agent is written in Python and uses frameworks like LangChain, LlamaIndex, OpenAI, Anthropic, Google GenAI, or any of the other supported providers.

Core capabilities:

  • Automatic instrumentation of 20+ LLM providers and frameworks
  • Manual instrumentation with @task, @workflow, @agent, and @tool decorators
  • Tag propagation for filtering and grouping in the platform
  • Content tracing control — choose whether prompts and completions are sent
  • Environment variable overrides for deployment flexibility

Installation

From PyPI:

bash
pip install progress-observability

Initialization

Call Observability.instrument() once at the start of your process, before importing LLM and framework libraries. For frameworks that build their objects at import time — LangGraph, CrewAI, LlamaIndex, OpenAI Agents, Agno — this ordering is required, not optional: objects created before instrument() may be missed.

One exception: if your app already configures OpenTelemetry itself, see Using Progress alongside existing OpenTelemetry below — there, init goes after the app's own setup.

python
from progress.observability import Observability

# Initialize once at process start
Observability.instrument(
    app_name="my-app",
    api_key="<your-api-key>"
)

All supported libraries are now instrumented automatically.

Configuration

Full configuration example with all options:

python
from progress.observability import Observability, ObservabilityInstruments

Observability.instrument(
    app_name="my-app",
    api_key="<your-api-key>",
    endpoint="https://collector.observability.progress.com:443",  # default
    debug=False,
    trace_content=True,          # set False to exclude prompts/responses
    instruments={                 # only enable specific instruments
        ObservabilityInstruments.OPENAI,
        ObservabilityInstruments.LANGCHAIN
    },
    additional_tags=["production", "release:2.4.1"]
)

Parameters

ParameterRequiredDefaultDescription
app_nameNosys.argv[0]Application name shown in the platform
api_keyYes*NoneYour API key (* or set env var)
endpointNohttps://collector.observability.progress.com:443Collector endpoint URL
debugNoFalseEnable debug logging
trace_contentNoTrueInclude prompts and completions in traces
instrumentsNoNone (all enabled)Set of instruments to enable
block_instrumentsNoNoneSet of instruments to disable
additional_tagsNoNoneGlobal tags applied to all spans
disable_batchNoTrueSend traces immediately

Environment Variables

You can set these instead of (or in addition to) passing parameters to instrument():

bash
# Set these environment variables instead of passing them to instrument()
export OBSERVABILITY_APP_NAME="my-app"
export OBSERVABILITY_API_KEY="<your-api-key>"
export OBSERVABILITY_ENDPOINT="https://collector.observability.progress.com:443"
export OBSERVABILITY_TRACE_CONTENT="true"
VariableOverrides
OBSERVABILITY_APP_NAMEapp_name
OBSERVABILITY_API_KEYapi_key
OBSERVABILITY_ENDPOINTendpoint
OBSERVABILITY_TRACE_CONTENTtrace_content (true or false)

Decorators

Decorators add telemetry spans to your own functions. Each decorator creates a span with a specific kind, letting you visualize the structure of your agent in the platform.

DecoratorSpan KindUse For
@tasktaskA discrete unit of work such as a data processing step or API call
@workflowworkflowA multi-step orchestration that coordinates tasks
@agentagentAn AI agent entry point
@tooltoolA tool or function the agent can invoke

Decorator Parameters

ParameterTypeDefaultDescription
namestrfunction nameOverride span name
versionintNoneVersion number for tracking
attributesdictNoneCustom span attributes
tagslist[str]NoneTags for this span (max 200 chars each)

Examples

@agent

python
from progress.observability import agent

@agent(name="support-agent")
async def handle_support_request(message: str):
    # Agent logic with LLM calls, tool usage, and so on
    return response

@workflow

python
from progress.observability import workflow

@workflow(name="onboarding-flow", version=2)
async def onboard_customer(customer_id: str):
    await verify_identity(customer_id)
    await create_account(customer_id)
    await send_welcome_email(customer_id)

@tool

python
from progress.observability import tool

@tool(name="web-search")
def search_web(query: str) -> str:
    # Tool implementation
    return results

@task

python
from progress.observability import task

@task(
    name="custom-span-name",      # override the default function name
    version=2,                     # version number for tracking
    attributes={"team": "ml"},     # custom span attributes
    tags=["experiment-a"]          # tags for filtering
)
def my_function():
    ...

Decorators work with both synchronous and asynchronous functions. On success, the decorator sets the span status to OK. On exception, the decorator sets the status to ERROR, records the exception on the span, and re-raises it.

Tags

Tags are user-defined strings (max 200 characters each) that you attach to spans for filtering and grouping in the platform. There are three ways to add tags:

1. Global Tags

Applied to every span. Set during initialization:

python
Observability.instrument(
    app_name="my-app",
    api_key="<your-api-key>",
    additional_tags=["production", "release:2.4.1"]
)

2. Scoped Tags

Applied to all spans created within a context block:

python
from progress.observability import propagate_attributes

with propagate_attributes(tags=["tenant:acme", "experiment-v2"]):
    # All spans created inside this block inherit these tags
    my_agent_function()

# Nesting is supported — tags accumulate
with propagate_attributes(tags=["outer-tag"]):
    with propagate_attributes(tags=["inner-tag"]):
        do_work()  # span gets both "outer-tag" and "inner-tag"

3. Decorator Tags

Applied to a single decorated function:

python
from progress.observability import task

@task(tags=["cohort-a"])
def my_task():
    ...

All three levels merge automatically. If the same tag appears at multiple levels it is deduplicated.

Supported Instruments

LLM Providers

OPENAI, GOOGLE_GENAI (alias GOOGLE_GENERATIVEAI), ANTHROPIC, BEDROCK, OLLAMA, and others.

Agent Frameworks

LANGCHAIN, LLAMA_INDEX, HAYSTACK, and others.

LangChain and LlamaIndex need the full package installed. Framework instrumentation activates only when the langchain (or langgraph) / llama-index distribution is installed. An app that depends only on langchain-core or llama-index-core runs fine and emits LLM spans, but no chain or index structure — silently. Add the full package alongside the -core package, never in place of it.

Controlling Instruments

python
from progress.observability import Observability, ObservabilityInstruments

# Enable everything except OpenAI
Observability.instrument(
    app_name="my-app",
    api_key="<your-api-key>",
    block_instruments={ObservabilityInstruments.OPENAI}
)

To specifically enable or block Google GenAI instrumentation, use the enum value ObservabilityInstruments.GOOGLE_GENERATIVEAI.

Shutdown

Call shutdown() before your process exits to flush any pending telemetry:

python
# Call shutdown before your process exits
# Using try/finally ensures telemetry is flushed even on errors
try:
    # your application logic
    run_agent()
finally:
    Observability.shutdown()

Using Progress Alongside Existing OpenTelemetry

If your app calls trace.set_tracer_provider(...) itself, run instrument() after that line. This is the one case where init at process start is wrong.

python
provider = TracerProvider(
    resource=Resource.create({"service.name": "my-service"})
)
provider.add_span_processor(BatchSpanProcessor(my_exporter))
trace.set_tracer_provider(provider)  # the app's own setup, untouched

Observability.instrument(  # after, never before
    app_name=os.environ.get("OBSERVABILITY_APP_NAME", "my-app"),
    api_key=os.environ["OBSERVABILITY_API_KEY"],
)

App provider first, then instrument(): Progress attaches to the existing provider and both exporters receive every span. Init first: the app's set_tracer_provider() becomes a no-op — OpenTelemetry logs Overriding of current TracerProvider is not allowed and the app's own exporter receives nothing for the rest of the process, while Progress traces keep arriving.

Two consequences once attached: spans carry the app's resource, so they land under the app's service.name rather than app_name; and the app's own provider.shutdown() already flushes Progress, so an extra Observability.shutdown() is harmless but unnecessary.

Requirements

Python >= 3.10, < 4.0

See Also