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=Truedisables both, there is no separatedisable_langgraphflag) - 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) - HTTP —
urllibandrequests-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). Setsgen_ai.operation.name=execute_toolin addition to the same input/output capture astask.
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_nameisn't provided. - Bedrock — sets
gen_ai.agent.nameon 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
AGENTspan (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.nameas 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2c7757f565f08a2fa4383c2f2aa186356519a45b92184f34811c82187feddd7a
|
|
| MD5 |
96101b21a9641d5ff78f53ed7d382e79
|
|
| BLAKE2b-256 |
64100929f6ccb60170df09158e72897c2ee48ba3e677985fbc25205270552a34
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
99480829a0d9cdb55d56fbb50a3436c5f81dd5775b29513bd664ed757f69767a
|
|
| MD5 |
48e31ff59859513e888452728a52e6af
|
|
| BLAKE2b-256 |
06eab8a85ca18fbf693fdf2ab025c0bdf535f8a096872aff521d46111d24df34
|