Skip to main content

Integritty SDK

LLM Observability SDK built on OpenTelemetry. Instruments LangChain, AWS Bedrock, OpenAI, Anthropic, Google ADK, CrewAI, and common databases/HTTP clients.

License

Licensed under the Apache License, Version 2.0.

Requirements

  • Python >=3.10, <4.0

Tested Versions

Package Tested Version
opentelemetry-api / opentelemetry-sdk >=1.39.1,<2.0.0
opentelemetry-exporter-otlp >=1.39.1,<2.0.0
opentelemetry-instrumentation >=0.60b1,<0.62
python-dotenv >=1.0.0,<2.0.0
langchain-core >=0.3.0,<2.0.0
langchain-openai >=0.2.0,<2.0.0
boto3 (Bedrock) >=1.42.54,<2.0.0
wrapt (Bedrock) >=1.0.0,<3.0.0
openai >=1.0.0,<3.0.0
anthropic >=0.80.0,<2.0.0
google-adk >=1.2.1,<3.0.0
crewai >=0.86.0,<2.0.0
opentelemetry-instrumentation-mysql >=0.60b1,<0.62
opentelemetry-instrumentation-pymysql >=0.60b1,<0.62
opentelemetry-instrumentation-pymongo >=0.61b0,<0.62
pymssql >=2.2.0
pyodbc >=4.0.0
opentelemetry-instrumentation-urllib >=0.60b1,<0.62
opentelemetry-instrumentation-requests >=0.60b1,<0.62
opentelemetry-instrumentation-threading >=0.60b1,<0.62

Below the minimum version, that integration's instrument() becomes a no-op (a warning is logged, everything else keeps working). anthropic, openai, google-adk, and crewai versions are actively version-gated at runtime — psycopg2/psycopg3/asyncpg and pymssql/pyodbc are patched manually and are not version-gated the same way.


Installation

1. Install the SDK

pip install integritty-sdk

Or with Poetry:

poetry add integritty-sdk

2. Set environment variables

Create a .env file or export variables in your shell:

# OTLP-compatible backend
OTEL_EXPORTER_OTLP_ENDPOINT= 

# Headers
OTEL_EXPORTER_OTLP_HEADERS=x-integritty-key=itg_.......

OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf

3. Initialize in your application

Call Integritty.init() once at application startup, before any LLM or database calls.

from integritty import Integritty

Integritty.init(service_name="my-app")

What Gets Auto-Instrumented

Once Integritty.init() is called, the following are traced automatically with no additional code:

  • LangChain — chains, agents, tools, LLM calls, streaming (this also covers LangGraph — a compiled graph runs on LangChain's Runnable interface, so its nodes are traced automatically with no separate integration or install step; disable_langchain=True disables both, there is no separate disable_langgraph flag)
  • AWS Bedrock — hosted and runtime-hosted agents
  • OpenAI — chat completions, embeddings
  • Anthropic — messages API, tool use, streaming
  • Google ADK (google-adk) — agents and tool calls
  • CrewAI — crews, agents, tasks
  • MySQL (mysql.connector, pymysql)
  • PostgreSQL — psycopg2, psycopg3, asyncpg
  • MongoDB (pymongo)
  • MSSQL (pymssql, pyodbc)
  • HTTPurllib and requests-based outbound calls

Each integration can be turned off individually via Integritty.init() flags, e.g. Integritty.init(service_name="my-app", disable_crewai=True, disable_requests=True). Flags follow the pattern disable_<name>: disable_langchain, disable_bedrock, disable_openai, disable_anthropic, disable_google_adk, disable_crewai, disable_mysql, disable_pymysql, disable_psycopg2, disable_psycopg3, disable_asyncpg, disable_pymongo, disable_pymssql, disable_pyodbc, disable_requests.


Decorators

@Integritty.traced (Bedrock — not needed for LangChain)

Wraps a function in a root span. All child operations (LLM, DB, etc.) become children of that span. Flushes traces on exit. Not required for a single Bedrock hosted-agent call — invoke_agent/converse already get their own span automatically, and Integritty.init() flushes on process exit regardless. Use it when a script makes multiple Bedrock/LLM calls that should nest under one shared trace instead of showing up as separate root spans. For AgentCore Runtime (runtime-hosted) apps, use @Integritty.entrypoint below instead.

from integritty import Integritty

Integritty.init(service_name="my-app")

@Integritty.traced(span_name="my_agent_run")
def main(): 
 ask_question("What is RAG?") 

@Integritty.workflow (Bedrock / OpenAI / general — not for LangChain)

Alias for @Integritty.traced — marks the top-level entry point of a request or workflow. Useful when you have multiple Bedrock or LLM calls inside one request handler.

@Integritty.workflow(name="handle_chat")
def handle_chat():
    client.converse(...)
    client.invoke_agent(...)

Note: name= here sets the span/workflow name shown in traces — it is not a place to pass a user identifier. To attach user_id/user_name (or session_id/conversation_id) to a request, use Integritty.request_context(...) — see "Passing User / Session / Conversation IDs" below.

@Integritty.entrypoint(app) (advanced — async request-handler frameworks)

Not an alias of @Integritty.traced/@Integritty.workflow — it's a separate, async-only decorator for frameworks whose own app object exposes an .entrypoint(fn) decorator (e.g. an AWS Bedrock AgentCore Runtime app). It wraps your async handler in a root span, seeds LangChain's tracing context so any llm.ainvoke/llm.astream call inside nests correctly under that root span, flushes on exit, and then registers the wrapped function with app.entrypoint(...).

@Integritty.entrypoint(app, span_name="handle_request")
async def handle_request(request):
    return await client.converse(...)

Required, not just convenient, when your entrypoint calls LangChain/LangGraph internally (e.g. a supervisor routing to specialist agents): LangChain's instrumentation tracks trace continuity through its own context variables, which reset on every fresh request. Without this decorator seeding them first, the LangChain call opens a disconnected second trace instead of nesting under the request's root span.

If your entrypoint only makes plain Bedrock/OpenAI/Anthropic calls (no LangChain involved), it's optional — each call already gets its own span automatically the same way it does for hosted agents. In that case use it only to group multiple calls under one shared trace, the same tradeoff as @Integritty.workflow.

For most non-LangChain use cases (plain scripts/services, no framework with its own .entrypoint), prefer @Integritty.workflow above.

@Integritty.task / @Integritty.tool — manual flow tracing (no framework)

When your application does not use LangChain, CrewAI, or another auto-instrumented framework to orchestrate its logic (e.g. a plain Python service calling an LLM SDK and your own functions directly), there is no framework hooking your function calls into spans. Use @Integritty.workflow to mark the entry point, then @Integritty.task / @Integritty.tool to mark the steps inside it, so the trace still shows a proper parent/child structure instead of one flat span.

  • @Integritty.workflow — the root span for the whole request/run. Flushes on exit. Use once, at the top-level function.
  • @Integritty.task — a child span for an internal step/business-logic function (e.g. "fetch context", "build prompt", "post-process"). No flush — it's meant to run inside a workflow.
  • @Integritty.tool — a child span for a tool/function call (e.g. a function the LLM calls, or a call to an external API/service). Sets gen_ai.operation.name=execute_tool in addition to the same input/output capture as task.

Both @Integritty.task and @Integritty.tool automatically capture the function's input arguments, return value, and any raised exception as span attributes — you don't need to set them manually. They nest under whatever span is currently active, so call them from inside a @Integritty.workflow-decorated function (or an Integritty.span() block).

from integritty import Integritty

@Integritty.workflow(name="handle_chat")
def handle_chat(message: str):
    context = fetch_context(message)   # task
    return call_agent(context)         # tool

@Integritty.task(name="fetch_context")
def fetch_context(message: str):
    return db.search(message)

@Integritty.tool(name="call_agent")
def call_agent(context):
    return client.invoke_agent(context=context)

@Integritty.tool also works as a bare decorator or a direct wrapper, without requiring the parentheses form:

@Integritty.tool
def search_kb(query):
    ...

search = Integritty.tool(search_fn, name="search_kb")

@Integritty.with_agent_name (all adapters — LangChain, Bedrock, OpenAI, Anthropic, Google ADK, CrewAI)

Sets a fallback agent name that every adapter's instrumentation reads and stamps onto its own spans as gen_ai.agent.name whenever the framework itself doesn't supply one (e.g. no run_name in a LangChain invoke config). It works the same way regardless of which adapter your app uses — you don't need a different decorator per provider.

  • LangChain — used as the fallback chain/agent span name when run_name/metadata.agent_name isn't provided.
  • Bedrock — sets gen_ai.agent.name on every Bedrock span created inside the decorated function (hosted or runtime-hosted agents).
  • Anthropic / OpenAI — in addition to the fallback name, also opens a dedicated parent AGENT span (gen_ai.operation.name=invoke_agent) around the decorated function, so orchestrator-style code that calls out to sub-agents shows up correctly in the trace.
  • Google ADK / CrewAI — sets gen_ai.agent.name as a fallback on spans created inside the decorated function, same as Bedrock.

LangChain

from integritty import Integritty

# no need to use decorator if you are already passing in metadata or if you are creating agent with create_agent.
@Integritty.with_agent_name("MyAgent")
def handle_chat(message):
    return agent.invoke({"messages": [{"role": "user", "content": message}]})

Bedrock (Hosted Agents)

Overrides the default alphanumeric agentId with a readable name on every span the call produces — works the same way for streaming and non-streaming invoke_agent calls.

from integritty import Integritty

@Integritty.with_agent_name("MyBedrockAgent")
def handle_chat(message):
    return client.converse(
        modelId="anthropic.claude-3-5-sonnet-20241022-v2:0",
        messages=[{"role": "user", "content": [{"text": message}]}],
    )

Bedrock (Runtime-Hosted Agents)

Use it when no meaningful agent name can be inferred automatically — e.g. there's no graph node to read a name from. If a node already has a string name (graph.add_node("addition", fn)), the SDK captures that automatically and the decorator isn't needed.

from integritty import Integritty

@Integritty.with_agent_name("multiply")
def multiplication_agent(state):
    result = llm.invoke(state["messages"])
    return {**state, "answer": result.content}

For streaming nodes, apply it to a regular async def function that calls llm.astream internally — not directly on an async generator (one that yields), since the decorator resets the name in a finally block that fires before the first chunk is produced.

OpenAI / Anthropic / Google ADK / CrewAI

from integritty import Integritty

@Integritty.with_agent_name("MyAgent")
def handle_chat(message):
    return client.chat.completions.create(   # or anthropic client.messages.create(...),
        model="gpt-4o",                      # or a Google ADK / CrewAI call
        messages=[{"role": "user", "content": message}],
    )

Prompt / Response Content Capture (Hiding Sensitive Data)

There are two independent controls — use whichever fits, or both:

1. Integritty.init(capture_message_content=...)

Passed once to Integritty.init(). Controls whether LangChain, Bedrock, OpenAI, Anthropic, and CrewAI build the actual prompt/response text into span attributes at all (gen_ai.prompt.N.content, gen_ai.completion.N.content, etc.). Defaults to True. Does not apply to Google ADK (its instrumentor always captures; use the env vars below to redact it).

Integritty.init(service_name="my-app", capture_message_content=False)

There is no environment variable equivalent for capture_message_content itself — it is only settable in code, at Integritty.init() call time. If you need to control content capture purely via .env without a code change, use control #2 below instead.

2. INTEGRITTY_CAPTURE_INPUT_PROMPTS / INTEGRITTY_CAPTURE_OUTPUT env vars

A global redaction pass applied to every span from every adapter (LangChain, Bedrock, OpenAI, Anthropic, Google ADK, CrewAI, plus your own @Integritty.task/@Integritty.tool spans) right before export — regardless of the capture_message_content setting above. Set either to false (case-insensitive) in your environment/.env file:

# Strip all input prompt content (system/user messages + role labels) from every trace
INTEGRITTY_CAPTURE_INPUT_PROMPTS=false

# Strip all output/response content (assistant replies, tool outputs, task outputs) from every trace
INTEGRITTY_CAPTURE_OUTPUT=false

Both default to true (capture everything) when unset. This is the recommended way to hide sensitive prompt/response content in production without touching adapter-specific code, since it's enforced centrally in the span pipeline rather than per-instrumentor.


Span Context Managers

For inline span grouping without decorators:

# Sync
with Integritty.span("rag.query"):
    ask_question("What is RAG?")

# Async
async with Integritty.aspan("stream_response"):
    async for chunk in llm.astream(messages):
        yield chunk

Passing User / Session / Conversation IDs

LangChain

Pass user_id, user_name, and conversation_id via the invoke metadata:

# metadata is an optional thing but in case the application is dealing with user id/name/session/conversation id then needs to pass like this only
metadata = {
    "conversation_id": conversation_id,
    "user_id": user_id,       
    "user_name": user_name,    
}

response = agent.invoke(
    {"messages": [{"role": "user", "content": message}]},
    {
        "run_name": "AGENT NAME", # no need of passing here if you are already using @Integritty.with_agent_name or if you are creating agent with create_agent
        "metadata": metadata,
    },
)

You can also set these at the request boundary with Integritty.request_context(...) (see below) — LangChain's invoke automatically picks up the values from it, so you don't have to thread metadata through every call.

LangGraph

No separate integration — a compiled StateGraph is itself a Runnable, so it's traced by the same LangChain instrumentor with no extra install/init step (Integritty.init(service_name="my-app") is enough; there is no disable_langgraph flag, only disable_langchain, which covers both).

Pass user_id, user_name, and conversation_id the exact same way as the LangChain section above — via the invoke config's metadata:

response = graph.invoke(
    {"messages": [{"role": "user", "content": message}]},
    {
        "run_name": "AGENT NAME",  # no need if using @Integritty.with_agent_name
        "metadata": {
            "conversation_id": conversation_id,
            "user_id": user_id,
            "user_name": user_name,
        },
    },
)

You get one thing for free that plain LangChain doesn't have: LangGraph stamps langgraph_node and graph_id onto each node's own internal metadata as it runs, and the instrumentation picks these up automatically to name each span after the graph node that produced it (surfaced as chain.langgraph_node) — no extra code needed to tell which node in your graph a given LLM call or tool call came from.

Bedrock (Hosted Agents)

Use Integritty.request_context() as a context manager at the request boundary, wrapping the invoke_agent/converse call:

from integritty import Integritty

with Integritty.request_context(user_id="u123", session_id="s456"):
    response = client.invoke_agent(
        agentId="QNF5HISBHB",
        agentAliasId="V0QTNEJ1BL",
        sessionId="s456",
        inputText=question,
    )

Bedrock (Runtime-Hosted Agents)

Place it inside your @Integritty.entrypoint-decorated function, wrapping the graph/chain invocation, so every span produced during that request — supervisor, routing, specialist agents — carries the same IDs:

from integritty import Integritty

@Integritty.entrypoint(app, span_name="math_agent")
async def agent(payload, context):
    with Integritty.request_context(user_id="user-012", user_name="Alice"):
        route = await supervisor_agent(payload["question"])
        return await AGENTS[route](payload["question"])

OpenAI, Anthropic, Google ADK, CrewAI (any other non-LangChain flow)

Use Integritty.request_context() as a context manager at the request boundary:

from integritty import Integritty

with Integritty.request_context(user_id="u123", session_id="s456", conversation_id="c789", agent_name="MyAgent"):
    response = client.chat.completions.create(...)

All spans created inside the with block automatically carry the provided IDs — this is handled identically by the OpenAI, Anthropic, Google ADK, and CrewAI instrumentations (each sets llm.metadata.<key> for every key you pass, plus gen_ai.conversation.id for conversation_id), so one call to request_context() covers whichever of these your app uses. agent_name is used as a fallback agent name (same effect as @Integritty.with_agent_name) when the framework doesn't supply one. Bedrock supports the same request_context() mechanics — see the two subsections above for where to place it.

Note — this agent_name= fallback is scoped to OpenAI, Anthropic, Google ADK, CrewAI, and Bedrock, as listed above. It is not documented for LangChain/LangGraph. For a LangChain/LangGraph flow, set the agent name via run_name or metadata.agent_name in the invoke config, or the @Integritty.with_agent_name decorator (see the LangChain subsection of @Integritty.with_agent_name above) — do not assume request_context(agent_name=...) carries over to LangChain's chain/agent span name just because request_context(user_id=...)/user_name=/conversation_id= do (those three are separately confirmed to work for LangChain via its invoke metadata).

Combining @Integritty.workflow with request_context

request_context() is always used as a with block — never call it as a bare statement, since its __enter__ is what actually attaches the IDs to the active span context; calling it without with does nothing. Inside a @Integritty.workflow-decorated entry point, wrap your LLM/agent call in the with block exactly as shown below — do not assign the result or call request_context(...) on its own line:

from integritty import Integritty

Integritty.init(service_name="my-app")

@Integritty.workflow(name="handle_chat")
def handle_chat(user_name: str, message: str):
    with Integritty.request_context(user_name=user_name):
        response = client.converse(...)
        return response

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

integritty_sdk-0.1.0.tar.gz (107.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

integritty_sdk-0.1.0-py3-none-any.whl (112.0 kB view details)

Uploaded Python 3

File details

Details for the file integritty_sdk-0.1.0.tar.gz.

File metadata

  • Download URL: integritty_sdk-0.1.0.tar.gz
  • Upload date:
  • Size: 107.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.6

File hashes

Hashes for integritty_sdk-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2c7757f565f08a2fa4383c2f2aa186356519a45b92184f34811c82187feddd7a
MD5 96101b21a9641d5ff78f53ed7d382e79
BLAKE2b-256 64100929f6ccb60170df09158e72897c2ee48ba3e677985fbc25205270552a34

See more details on using hashes here.

File details

Details for the file integritty_sdk-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: integritty_sdk-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 112.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.6

File hashes

Hashes for integritty_sdk-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 99480829a0d9cdb55d56fbb50a3436c5f81dd5775b29513bd664ed757f69767a
MD5 48e31ff59859513e888452728a52e6af
BLAKE2b-256 06eab8a85ca18fbf693fdf2ab025c0bdf535f8a096872aff521d46111d24df34

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page