Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
113 changes: 113 additions & 0 deletions samples/langchain/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# LangChain Samples

These samples demonstrate how to use LangChain with OpenTelemetry tracing exported to Azure Monitor (Application Insights).

## Prerequisites

- Python 3.10+
- An Azure OpenAI resource (or an OpenAI API key)
- An Application Insights resource (for the connection string)

## Configuration

All samples require you to fill in placeholder values before running.

### Option A: Azure OpenAI

Replace these placeholders in each sample file:

| Placeholder | Description |
|---|---|
| `<AZURE_OPENAI_ENDPOINT>` | Your Azure OpenAI endpoint URL (e.g., `https://<resource-name>.openai.azure.com/openai/deployments/<deployment-name>/v1`) |
| `<AZURE_OPENAI_API_KEY>` | Your Azure OpenAI API key |

### Option B: OpenAI API Key

Set the environment variable `OPENAI_API_KEY` and remove the `api_key` and `base_url` parameters from the `ChatOpenAI(...)` calls in the sample files.

### Azure Monitor Connection String

Replace `"InstrumentationKey=..."` with your full Application Insights connection string. You can find this in the Azure portal under your Application Insights resource > **Overview** > **Connection String**. Or you can
set it using the `APPLICATIONINISGHTS_CONNECTION_STRING` environment variable.

## Samples

### 1. `sample_opentelemetry.py`

Demonstrates the `opentelemetry-instrumentation-langchain` package with two differently configured LLMs (creative vs. precise).

**Placeholders to fill:**

| Placeholder | Value |
|---|---|
| `<AZURE_OPENAI_ENDPOINT>` | Azure OpenAI endpoint URL |
| `<AZURE_OPENAI_API_KEY>` | Azure OpenAI API key |

**What it does:**
- Creates two `ChatOpenAI` instances with different temperature/sampling settings
- Runs a multi-turn conversation (travel guide)
- Compares creative vs. precise outputs for the same prompt (haiku about observability)
- Each `invoke()` call produces its own OpenTelemetry span

**Run:**
```bash
python sample_opentelemetry.py
```

### 2. `sample_arise_langchain.py`

Demonstrates image description using a vision-capable model, with tracing via the `openinference-instrumentation-langchain` instrumentor.

**Placeholders to fill:**

| Placeholder | Value |
|---|---|
| `<AZURE_OPENAI_ENDPOINT>` | Azure OpenAI endpoint URL |
| `<AZURE_OPENAI_API_KEY>` | Azure OpenAI API key |
| `<IMAGE_URL>` | A publicly accessible URL to an image (e.g., `https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg`) |

**What it does:**
- Downloads and base64-encodes the image at the provided URL
- Sends a multimodal message (text + image) to the model
- Asks the model to describe the image

**Run:**
```bash
python sample_arise_langchain.py
```

### 3. `sample_langchain_azure.py`

Demonstrates a multi-agent travel planner using nested LangChain agents with the `AzureAIOpenTelemetryTracer`.

**Placeholders to fill:**

| Placeholder | Value |
|---|---|
| `<AZURE_OPENAI_ENDPOINT>` | Azure OpenAI endpoint URL |
| `<AZURE_OPENAI_API_KEY>` | Azure OpenAI API key |
| `"InstrumentationKey=..."` | Application Insights connection string |

**What it does:**
- Creates specialist agents for flights, hotels, and activities (with mock tool data)
- A coordinator agent delegates to the specialists to plan a weekend trip to Paris
- All agent calls are traced via the `AzureAIOpenTelemetryTracer` callback

**Run:**
```bash
python sample_langchain_azure.py
```

## Required Packages

```bash
pip install langchain-openai langchain-core azure-monitor-opentelemetry httpx
# For sample_opentelemetry.py:
pip install opentelemetry-instrumentation-langchain

# For sample_arise_langchain.py:
pip install openinference-instrumentation-langchain

# For sample_langchain_azure.py:
pip install langchain-azure-ai langgraph
```
44 changes: 44 additions & 0 deletions samples/langchain/sample_arise_langchain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------

import base64
import httpx

from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
from azure.monitor.opentelemetry import configure_azure_monitor

from openinference.instrumentation.langchain import LangChainInstrumentor

configure_azure_monitor(connection_string="InstrumentationKey=...") # TODO: This will be replaced with the opentelemetry distro
Comment thread
rads-1996 marked this conversation as resolved.

LangChainInstrumentor().instrument()

# If using Azure OpenAI endpoint and API KEY
endpoint = "<AZURE_OPENAI_ENDPOINT>"
model_name = "gpt-4.1"
api_key = "<AZURE_OPENAI_API_KEY>"

# Otherwise, set the env variable OPENAI_API_KEY

image_url = "<IMAGE_URL>"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to some URL here?, do we need a readme for samples so people understand how to run them?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the user can enter any URL they want the LLM to describe for them. Yes, I will add a readme.


model = ChatOpenAI(model=model_name, api_key=api_key, base_url=endpoint) # Do not include api_key and base_url if using the OPENAI_API_KEY environment variable
image_data = base64.b64encode(httpx.get(image_url).content).decode("utf-8")
Comment thread
rads-1996 marked this conversation as resolved.

message = HumanMessage(
content=[
{"type": "text", "text": "describe this image"},
{
"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{image_data}", "detail": "low"},
},
],
)

if __name__ == "__main__":
response = model.invoke([message])
print(response.content)
137 changes: 137 additions & 0 deletions samples/langchain/sample_langchain_azure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"""Travel planner with nested agents — coordinator delegates to specialist agents."""

from __future__ import annotations

import os
Comment thread
rads-1996 marked this conversation as resolved.
import random
from uuid import uuid4

from azure.monitor.opentelemetry import configure_azure_monitor
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langchain_azure_ai.callbacks.tracers import AzureAIOpenTelemetryTracer

try:
from langchain.agents import create_agent as create_react_agent
except ImportError:
from langgraph.prebuilt import create_react_agent

# If using Azure OpenAI endpoint and API KEY
endpoint = "<AZURE_OPENAI_ENDPOINT>"
api_key = "<AZURE_OPENAI_API_KEY>"
model_name = "gpt-4.1"

# Otherwise, set the env variable OPENAI_API_KEY

configure_azure_monitor( # TODO: This will be replaced with the opentelemetry distro
connection_string=
"InstrumentationKey=...",
)
Comment thread
rads-1996 marked this conversation as resolved.

tracer = AzureAIOpenTelemetryTracer(name="travel_planner", provider_name="openai")


# --- Tools ---
@tool
def search_flights(origin: str, destination: str, date: str) -> str:
"""Search for flights between two cities on a given date."""
airline = random.choice(["SkyLine", "AeroJet", "CloudNine"])
fare = random.randint(700, 1250)
return f"{airline} non-stop {origin} -> {destination}, {date} 09:05, fare ${fare}"


@tool
def search_hotels(destination: str, check_in: str, check_out: str) -> str:
"""Search for hotels in a city for given dates."""
hotel = random.choice(["Maison Azure", "Le Jardin", "Vista Royale"])
rate = random.randint(220, 380)
return f"{hotel} in {destination}, ${rate}/night, {check_in} to {check_out}"


@tool
def search_activities(destination: str) -> str:
"""Find popular activities in a destination city."""
activities = {
"Paris": ["Eiffel Tower at sunset", "Seine dinner cruise", "Day trip to Versailles"],
"Tokyo": ["Sushi masterclass", "Ghibli Museum", "Hakone hot springs"],
"Rome": ["Colosseum tour", "Pasta masterclass", "Trastevere walk"],
}
items = activities.get(destination, ["Sightseeing", "Local cuisine", "Museum visit"])
return "\n".join(f"- {a}" for a in items)


# --- Specialist Agents ---
def _make_llm(temperature: float = 0.3) -> ChatOpenAI:
return ChatOpenAI(model=model_name, api_key=api_key, base_url=endpoint, temperature=temperature) # Do not include api_key and base_url if using the OPENAI_API_KEY environment variable


flight_agent = create_react_agent(_make_llm(0.3), tools=[search_flights])
hotel_agent = create_react_agent(_make_llm(0.3), tools=[search_hotels])
activity_agent = create_react_agent(_make_llm(0.5), tools=[search_activities])


# --- Coordinator tools that invoke sub-agents ---
@tool
def find_flights(origin: str, destination: str, date: str) -> str:
"""Delegate to the flight specialist agent to find flights."""
result = flight_agent.invoke(
{"messages": [HumanMessage(content=f"Find flights from {origin} to {destination} on {date}.")]},
config={"callbacks": [tracer]},
)
return result["messages"][-1].content


@tool
def find_hotels(destination: str, check_in: str, check_out: str) -> str:
"""Delegate to the hotel specialist agent to find hotels."""
result = hotel_agent.invoke(
{"messages": [HumanMessage(content=f"Find a boutique hotel in {destination} from {check_in} to {check_out}.")]},
config={"callbacks": [tracer]},
)
return result["messages"][-1].content


@tool
def find_activities(destination: str) -> str:
"""Delegate to the activity specialist agent to find things to do."""
result = activity_agent.invoke(
{"messages": [HumanMessage(content=f"Find fun activities in {destination}.")]},
config={"callbacks": [tracer]},
)
return result["messages"][-1].content


# --- Coordinator Agent ---
coordinator = create_react_agent(
_make_llm(0.2),
tools=[find_flights, find_hotels, find_activities],
)


def main():
session_id = str(uuid4())
user_request = (
"Plan a weekend trip to Paris from Seattle next month. "
"Find flights, a boutique hotel, and fun activities."
)

print("Travel Planner (Nested Agents)")
print("=" * 40)
print(f"Request: {user_request}\n")

result = coordinator.invoke(
{"messages": [HumanMessage(content=user_request)]},
config={
"metadata": {"session_id": session_id, "thread_id": session_id},
"callbacks": [tracer],
},
)

final = result["messages"][-1].content
print("Plan:\n" + "-" * 40)
print(final)


if __name__ == "__main__":
main()
86 changes: 86 additions & 0 deletions samples/langchain/sample_opentelemetry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@

from azure.monitor.opentelemetry import configure_azure_monitor
from opentelemetry.instrumentation.langchain import LangChainInstrumentor
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI

# This sample demonstrates the current state of the opentelemetry-instrumentation-langchain package (unreleased) and how it can be integrated with the opentelemetry distro
# If using Azure OpenAI endpoint and API KEY
Comment thread
rads-1996 marked this conversation as resolved.
endpoint = "<AZURE_OPENAI_ENDPOINT>"
model_name = "gpt-4.1"
api_key = "<AZURE_OPENAI_API_KEY>"

# Otherwise, set the env variable OPENAI_API_KEY

configure_azure_monitor() # TODO: This will be replaced with the opentelemetry distro
Comment thread
rads-1996 marked this conversation as resolved.
"""
configure_microsoft_opentelemetry(
connection_string="InstrumentationKey=...",
enable_genai_langchain=True,
)
"""
LangChainInstrumentor().instrument()

Comment thread
rads-1996 marked this conversation as resolved.
# Two models with different configs — each call produces its own span
creative_llm = ChatOpenAI(
model=model_name,
temperature=0.9,
max_tokens=200,
top_p=0.95,
api_key=api_key, # Do not include if using the OPENAI_API_KEY environment variable
base_url=endpoint, # Do not include if using the OPENAI_API_KEY environment variable
)

precise_llm = ChatOpenAI(
model=model_name,
temperature=0.0,
max_tokens=100,
top_p=0.1,
frequency_penalty=0.5,
presence_penalty=0.5,
seed=42,
api_key=api_key, # Do not include if using the OPENAI_API_KEY environment variable
base_url=endpoint,
)


# --------------- Scenarios ---------------

def multi_turn_conversation():
"""Multi-turn chat — each invoke() is a separate instrumented span."""
print("=== Multi-turn Conversation ===")
history = [
SystemMessage(content="You are a travel guide. Be concise."),
HumanMessage(content="I'm planning a trip to Japan. Where should I start?"),
]

response1 = creative_llm.invoke(history)
print(f"Turn 1: {response1.content}\n")

history.append(AIMessage(content=response1.content))
history.append(HumanMessage(content="What food should I try there?"))

response2 = creative_llm.invoke(history)
print(f"Turn 2: {response2.content}\n")


def compare_temperatures():
"""Same prompt, different models — compare creative vs precise in telemetry."""
print("=== Temperature Comparison ===")
messages = [
SystemMessage(content="You are a poet."),
HumanMessage(content="Write a haiku about observability."),
]

creative_result = creative_llm.invoke(messages)
print(f"Creative (temp=0.9): {creative_result.content}\n")

precise_result = precise_llm.invoke(messages)
print(f"Precise (temp=0.0): {precise_result.content}\n")


if __name__ == "__main__":
multi_turn_conversation()
compare_temperatures()

LangChainInstrumentor().uninstrument()
2 changes: 1 addition & 1 deletion tests/test_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@


def test_configure_microsoft_opentelemetry_exists() -> None:
assert callable(configure_microsoft_opentelemetry)
assert callable(configure_microsoft_opentelemetry)
Loading