Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
912b784
Add workflow and refactor LLM for langchain
wrisa Apr 16, 2026
9d4876f
Merge branch 'open-telemetry:main' into langchain-workflow-type
wrisa Apr 20, 2026
1ce4cf5
add workflow support and genai dependancy
wrisa Apr 16, 2026
e9fff12
fixed errors
wrisa Apr 20, 2026
6e34620
fixed changelog
wrisa Apr 20, 2026
c17607d
fixed error
wrisa Apr 20, 2026
a63f271
fixed type error
wrisa Apr 20, 2026
e8be641
removed optional
wrisa Apr 21, 2026
a9dc188
fixed error
wrisa Apr 21, 2026
3605631
ignore
wrisa Apr 21, 2026
4b7808f
fixed requests
wrisa Apr 21, 2026
e6d9f03
Merge branch 'main' into langchain-workflow-type
wrisa Apr 22, 2026
a503105
Merge branch 'main' into langchain-workflow-type
wrisa Apr 23, 2026
dc068fb
Merge branch 'main' into langchain-workflow-type
wrisa Apr 23, 2026
2ee7792
Merge branch 'main' into langchain-workflow-type
wrisa Apr 24, 2026
8ed66a4
Merge branch 'main' into langchain-workflow-type
wrisa Apr 27, 2026
1b5f2a3
Added delete and removed optional
wrisa Apr 24, 2026
93e806b
removed non workflow
wrisa Apr 24, 2026
16ee93a
removed line
wrisa Apr 27, 2026
eee467a
added new line
wrisa Apr 27, 2026
3f668b1
Merge branch 'main' into langchain-workflow-type
wrisa Apr 30, 2026
af5deaf
Merge branch 'main' into langchain-workflow-type
wrisa May 1, 2026
3df798a
Merge branch 'open-telemetry:main' into langchain-workflow-type
wrisa May 4, 2026
86d3a64
classify operation
wrisa May 1, 2026
06e438a
add invoke agent
wrisa May 4, 2026
5594a72
fixed errors and added tests
wrisa May 4, 2026
53dbd7b
fixed precommit error
wrisa May 4, 2026
0cd6b28
fixed error
wrisa May 4, 2026
5ede64d
fixed test and example
wrisa May 4, 2026
3844025
removed unused
wrisa May 5, 2026
546eab8
fixed test
wrisa May 5, 2026
e6b9b68
updated changelog
wrisa May 5, 2026
b5d0b3b
Merge branch 'main' into langchain-workflow-type
wrisa May 7, 2026
785f463
Merge branch 'main' into langchain-workflow-type
wrisa May 7, 2026
4f37393
Merge branch 'main' into langchain-workflow-type
wrisa May 7, 2026
8ae8b98
Merge branch 'open-telemetry:main' into langchain-workflow-type
wrisa May 7, 2026
c547813
added SPDX license
wrisa May 7, 2026
02c0f37
Merge branch 'main' into langchain-workflow-type
wrisa May 8, 2026
79f6814
Merge branch 'main' into langchain-workflow-type
wrisa May 11, 2026
3067e5f
input output messages and updated example
wrisa May 12, 2026
0f3dce2
fixed typecheck
wrisa May 12, 2026
debafbe
add cast
wrisa May 12, 2026
6652b43
Merge branch 'main' into langchain-workflow-type
wrisa May 12, 2026
2242323
Merge branch 'main' into langchain-workflow-type
wrisa May 12, 2026
cd9f32e
Merge branch 'main' into langchain-workflow-type
wrisa May 12, 2026
aa15e92
Merge branch 'main' into langchain-workflow-type
wrisa May 12, 2026
1553f2e
Merge branch 'main' into langchain-workflow-type
wrisa May 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

- Add LangChain workflow and agent span support. Also refactor LLM invocation.
([#4449](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4449))
- Fix compatibility with wrapt 2.x by using positional arguments in `wrap_function_wrapper()` calls
([#4445](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4445))
- Added span support for genAI langchain llm invocation.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

"""
ReAct agent example built with LangGraph.

Uses LangGraph's prebuilt create_react_agent with simple calculator tools.
Comment thread
wrisa marked this conversation as resolved.
OpenTelemetry LangChain instrumentation traces the LLM calls made by the agent.
"""

from uuid import uuid4

from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent

from opentelemetry import _logs, metrics, trace
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import (
OTLPLogExporter,
)
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import (
OTLPMetricExporter,
)
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
OTLPSpanExporter,
)
from opentelemetry.instrumentation.langchain import LangChainInstrumentor
from opentelemetry.sdk._logs import LoggerProvider
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

# Configure tracing
trace.set_tracer_provider(TracerProvider())
span_processor = BatchSpanProcessor(OTLPSpanExporter())
trace.get_tracer_provider().add_span_processor(span_processor)

# Configure logging
_logs.set_logger_provider(LoggerProvider())
_logs.get_logger_provider().add_log_record_processor(
BatchLogRecordProcessor(OTLPLogExporter())
)

# Configure metrics
metrics.set_meter_provider(
MeterProvider(
metric_readers=[
PeriodicExportingMetricReader(
OTLPMetricExporter(),
),
]
)
)


@tool
def multiply(a: float, b: float) -> float:
"""Multiply two numbers together."""
return a * b


@tool
def add(a: float, b: float) -> float:
"""Add two numbers together."""
return a + b


def main():
LangChainInstrumentor().instrument()

llm = ChatOpenAI(
model="gpt-3.5-turbo",
temperature=0.1,
max_tokens=100,
top_p=0.9,
seed=100,
)

session_id = str(uuid4())
agent = create_react_agent(llm, tools=[multiply, add]).with_config(
{
"metadata": {
"agent_name": "coordinator",
"session_id": session_id,
},
}
)

result = agent.invoke(
{"messages": [HumanMessage(content="What is (3 * 4) + 7?")]}
)

print("Agent output:")
for msg in result["messages"]:
print(f" {type(msg).__name__}: {msg.content}")

LangChainInstrumentor().uninstrument()


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
langchain==0.3.21
langchain_openai
langgraph
opentelemetry-sdk>=1.39.0
opentelemetry-exporter-otlp-proto-grpc>=1.39.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

"""
Single-node agent example built with StateGraph.

A single ReAct agent answers arithmetic questions using calculator tools.
OpenTelemetry LangChain instrumentation traces all LLM calls.
"""

from uuid import uuid4

from langchain.agents import create_agent
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.graph import END, START, MessagesState, StateGraph

from opentelemetry import _logs, metrics, trace
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import (
OTLPLogExporter,
)
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import (
OTLPMetricExporter,
)
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
OTLPSpanExporter,
)
from opentelemetry.instrumentation.langchain import LangChainInstrumentor
from opentelemetry.sdk._logs import LoggerProvider
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

# Configure tracing
trace.set_tracer_provider(TracerProvider())
trace.get_tracer_provider().add_span_processor(
BatchSpanProcessor(OTLPSpanExporter())
)

# Configure logging
_logs.set_logger_provider(LoggerProvider())
_logs.get_logger_provider().add_log_record_processor(
BatchLogRecordProcessor(OTLPLogExporter())
)

# Configure metrics
metrics.set_meter_provider(
MeterProvider(
metric_readers=[PeriodicExportingMetricReader(OTLPMetricExporter())]
)
)


# --- Tools ----------------------------------------------------------------


@tool
def multiply(a: float, b: float) -> float:
"""Multiply two numbers."""
return a * b


@tool
def add(a: float, b: float) -> float:
"""Add two numbers."""
return a + b


# --- Graph ----------------------------------------------------------------


def build_single_node_graph(llm: ChatOpenAI):
session_id = str(uuid4())

agent = create_agent(
llm, tools=[multiply, add], name="math_agent"
).with_config(
{
"metadata": {
"agent_name": "math_agent",
"session_id": session_id,
},
}
)

def run_agent(state: MessagesState) -> dict:
result = agent.invoke({"messages": state["messages"]})
return {"messages": result["messages"]}

builder = StateGraph(MessagesState)
builder.add_node("math_agent", run_agent)
builder.add_edge(START, "math_agent")
builder.add_edge("math_agent", END)

return builder.compile()


def main():
LangChainInstrumentor().instrument()

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.1, seed=100)
graph = build_single_node_graph(llm)

questions = [
"What is 12 multiplied by 7?",
"What is 15 plus 27?",
]

for question in questions:
print(f"\nQuestion: {question}")
result = graph.invoke({"messages": [HumanMessage(content=question)]})
last = result["messages"][-1]
print(f" Answer: {last.content}")

LangChainInstrumentor().uninstrument()


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

"""
LangGraph StateGraph example with two LLM nodes.

Graph topology:

START → researcher → summariser → END

Steps:
1. *researcher* – gathers factual background on the user's question.
2. *summariser* – condenses the researcher's output into a concise answer.

OpenTelemetry LangChain instrumentation traces both LLM calls.
"""

from typing import Annotated

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 typing_extensions import TypedDict

from opentelemetry import _logs, metrics, trace
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import (
OTLPLogExporter,
)
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import (
OTLPMetricExporter,
)
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import (
OTLPSpanExporter,
)
from opentelemetry.instrumentation.langchain import LangChainInstrumentor
from opentelemetry.sdk._logs import LoggerProvider
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

# Configure tracing
trace.set_tracer_provider(TracerProvider())
trace.get_tracer_provider().add_span_processor(
BatchSpanProcessor(OTLPSpanExporter())
)

# Configure logging
_logs.set_logger_provider(LoggerProvider())
_logs.get_logger_provider().add_log_record_processor(
BatchLogRecordProcessor(OTLPLogExporter())
)

# Configure metrics
metrics.set_meter_provider(
MeterProvider(
metric_readers=[PeriodicExportingMetricReader(OTLPMetricExporter())]
)
)


class GraphState(TypedDict):
"""State shared across all graph nodes."""

messages: Annotated[list, add_messages]
research: str


def build_graph(llm: ChatOpenAI):
"""Build a StateGraph with a researcher node and a summariser node."""

def researcher(state: GraphState) -> dict:
"""Gather factual background on the last user message."""
response = llm.invoke(
[
SystemMessage(
content="You are a research assistant. Provide 2-3 factual sentences."
),
HumanMessage(content=state["messages"][-1].content),
]
)
return {
"research": response.content,
"messages": [response],
}

def summariser(state: GraphState) -> dict:
"""Condense the researcher's output into one concise sentence."""
response = llm.invoke(
[
SystemMessage(
content="You are an expert summariser. Condense the text below into one clear sentence."
),
HumanMessage(content=state["research"]),
]
)
return {"messages": [response]}

builder = StateGraph(GraphState)
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)

return builder.compile()


def main():
LangChainInstrumentor().instrument()

llm = ChatOpenAI(
model="gpt-3.5-turbo",
temperature=0.1,
max_tokens=200,
seed=42,
)

graph = build_graph(llm)
Comment thread
wrisa marked this conversation as resolved.

question = "What is the capital of France?"
print(f"Question: {question}\n")

result = graph.invoke(
{
"messages": [HumanMessage(content=question)],
"research": "",
}
)

print("Research output:")
print(f" {result['research']}\n")

print("Final summary:")
print(f" {result['messages'][-1].content}")

LangChainInstrumentor().uninstrument()


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
langchain==0.3.21
langchain_openai
langgraph
opentelemetry-sdk>=1.39.0
opentelemetry-exporter-otlp-proto-grpc>=1.39.0
Loading
Loading