Observe LangChain Instrumentation
This library traces LangChain and LangGraph applications. It hooks into LangChain’s callback manager to emit spans that mirror the structure of your application:
Workflow spans for a graph or chain run — for example an invocation of a LangGraph StateGraph — capturing the overall input and output of the run.
Agent spans for agent invocations nested inside a workflow, including the agent name, id, description, and conversation/session id when available.
Tool spans for tool calls made during a run.
Retrieval spans for retriever invocations, capturing the query and retrieved document IDs and relevance scores when available.
The spans nest to reflect the graph, so a single graph invocation produces a workflow span with the agent, tool, and model calls it triggered as children.
This package is Observe’s replacement for upstream’s opentelemetry-instrumentation-genai-langchain, not an addition to it. The two distributions install to the same paths, so an environment holding both gets a per-file mix of them and uninstalling either one breaks the other. This package depends on observe-util-genai, which replaces upstream’s opentelemetry-util-genai in the same way. Uninstall both upstream packages first; an upstream instrumentation that still needs opentelemetry-util-genai cannot share an environment with this one.
Installation
pip uninstall opentelemetry-instrumentation-genai-langchain opentelemetry-util-genai pip install observe-instrumentation-genai-langchain
LangGraph is optional. Graph node boundaries are instrumented from LangGraph 0.3.18 on; an older LangGraph still runs, with a warning that context is not propagated across its nodes.
See the examples directory for runnable workflow, agent, tools, and zero-code scenarios.
Usage
Call LangChainInstrumentor().instrument() once during startup, then build and invoke your graph as usual. The example below traces a simple two-node LangGraph StateGraph (START → researcher → summariser → END); the graph.invoke(...) call is recorded as a workflow span with the node model calls nested underneath.
from typing import Annotated, TypedDict
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import END, START, StateGraph
from langgraph.graph.message import add_messages
from observeinc.instrumentation.genai.langchain import LangChainInstrumentor
LangChainInstrumentor().instrument()
llm = ChatOpenAI(model="<your-model>", temperature=0)
class State(TypedDict):
messages: Annotated[list, add_messages]
research: str
def researcher(state: State) -> dict:
response = llm.invoke(
[
SystemMessage(content="Provide 2-3 factual sentences."),
HumanMessage(content=state["messages"][-1].content),
]
)
return {"research": response.content, "messages": [response]}
def summariser(state: State) -> dict:
response = llm.invoke(
[
SystemMessage(content="Condense the text into one sentence."),
HumanMessage(content=state["research"]),
]
)
return {"messages": [response]}
builder = StateGraph(State)
builder.add_node("researcher", researcher)
builder.add_node("summariser", summariser)
builder.add_edge(START, "researcher")
builder.add_edge("researcher", "summariser")
builder.add_edge("summariser", END)
graph = builder.compile()
# Recorded as a workflow span with the two node LLM calls nested underneath.
graph.invoke(
{
"messages": [HumanMessage(content="What is the capital of France?")],
"research": "",
}
)
Using with model-provider instrumentations
This package records a chat span for each LangChain chat model call. If the provider SDK underneath is instrumented too, each call is recorded twice: once here, and again by the SDK’s instrumentation, nested under it. Token usage and duration metrics count the call twice as well. Known cases:
observe-instrumentation-genai-openai, under ChatOpenAI
opentelemetry-instrumentation-botocore, under ChatBedrock and ChatBedrockConverse. opentelemetry-bootstrap -a install installs it whenever boto3 is present.
other tools that instrument the provider SDK, such as OpenInference or OpenLLMetry
opentelemetry-instrument enables every installed instrumentation, so having both installed is enough. Turn off the provider’s instrumentation, for example:
export OTEL_PYTHON_DISABLED_INSTRUMENTATIONS=openai,botocore
or uninstall it. Disabling botocore removes spans for every AWS call (S3, DynamoDB, and so on), not just Bedrock. If you set up instrumentation in code, don’t call instrument() on the provider’s instrumentor.
Keep the provider’s instrumentation if the same process also needs it:
calls to the SDK outside LangChain
LangChain features this package doesn’t trace, such as embeddings (for example OpenAIEmbeddings)
openai-agents, whose model spans come only from the OpenAI instrumentation
LangChain chat calls are then still counted twice.
Retrieval Spans and Document Scores
When invoking LangChain retrievers (e.g., vectorstores, knowledge bases, or contextual compression retrievers), retrieval spans are recorded with the query and retrieved document IDs and scores.
When message content capture is enabled (OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=SPAN_ONLY or SPAN_AND_EVENT), the retrieved documents are serialized into the gen_ai.retrieval.documents span attribute using the shared RetrievalDocument model, as a JSON array of objects with only id and score. Document text (previously recorded as content) and metadata are not captured. Query text capture is unchanged.
When available, relevance and similarity scores are captured in each document object under score:
Direct retrieval scores: extracted from metadata["score"] (populated by retrievers such as AmazonKnowledgeBasesRetriever, TavilySearchAPIRetriever, and vectorstore score-threshold searches).
Reranking scores: extracted from metadata["relevance_score"] (populated when retrievers are wrapped with rerankers via ContextualCompressionRetriever, such as CohereRerank).
Duck-typed / custom documents: extracted from a top-level score attribute or mapping key.
If a document has no score, or if the score is non-numeric or non-finite (NaN, Infinity), score is recorded as JSON null to ensure RFC 8259 JSON compliance. Missing document IDs are also recorded as null.
Configuration
By default, prompts and completions are not captured. To capture message content, set the environment variable OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT to one of NO_CONTENT, SPAN_ONLY, EVENT_ONLY, or SPAN_AND_EVENT.
Prompts and completions can instead be uploaded to external storage via a completion hook: set OTEL_INSTRUMENTATION_GENAI_COMPLETION_HOOK=upload with OTEL_INSTRUMENTATION_GENAI_UPLOAD_BASE_PATH (install the upload extra by changing observe-util-genai to observe-util-genai[upload] in the install command above), or pass a custom CompletionHook programmatically, which takes precedence over the environment variable:
LangChainInstrumentor().instrument(completion_hook=my_hook)
Recording the User
To record user.id, pass it as user_id in the run’s metadata and set OBSERVE_GENAI_CAPTURE_USER_ID_FROM_LIBRARY=true. LangChain passes the metadata on to the run’s children, so every span the instrumentation creates for the run records it:
graph.invoke(inputs, config={"metadata": {"user_id": "user-123"}})
It is off by default. Without it, or when the metadata has no user_id, spans record the id set with observeinc.util.genai.user_id.using_user_id, if any. When both are set, the metadata value wins. With the async API, spans created by other instrumentations under the run, such as HTTP client spans, do not get the metadata value, for the same reason as the context propagation limitation below.
Known Limitations
Custom runnables that override invoke without starting a run do not inherit the parent run’s context in multi-input batch or abatch calls.
References
Metadata
Release files for observe-instrumentation-genai-langchain 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| observe_instrumentation_genai_langchain-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for observe_instrumentation_genai_langchain-0.1.0-py3-none-any.whl |
Release files / observe_instrumentation_genai_langchain-0.1.0-py3-none-any.whl
| Download URL | observe_instrumentation_genai_langchain-0.1.0-py3-none-any.whl |
|---|---|
| Size | 42.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7cf15e444cad48990db7d765b851805b2b4443dc3365d6e79a83687e3f131937
|
|
BLAKE2b-256 checksum How to use checksums |
a102467e278caabdebe3a0eac9232b6d213ab2097db4f72e516dc0e2891f38a8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.16
|
Security for observe-instrumentation-genai-langchain 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.