Skip to content

Commit b1f8a42

Browse files
test(langchain): Add basic agent test with Responses call (#5726)
Add test for LangChain v1.0 functionality using sample Responses API output reused from `openai-agents` tests.
1 parent 71ef5fa commit b1f8a42

File tree

3 files changed

+243
-80
lines changed

3 files changed

+243
-80
lines changed

tests/conftest.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@
4848
from sentry_sdk.transport import Transport
4949
from sentry_sdk.utils import reraise
5050

51+
try:
52+
import openai
53+
except ImportError:
54+
openai = None
55+
56+
5157
from tests import _warning_recorder, _warning_recorder_mgr
5258

5359
from typing import TYPE_CHECKING
@@ -1033,10 +1039,14 @@ def inner(events, include_event_type=True):
10331039

10341040
@pytest.fixture
10351041
def get_model_response():
1036-
def inner(response_content, serialize_pydantic=False):
1042+
def inner(response_content, serialize_pydantic=False, request_headers=None):
1043+
if request_headers is None:
1044+
request_headers = {}
1045+
10371046
model_request = HttpxRequest(
10381047
"POST",
10391048
"/responses",
1049+
headers=request_headers,
10401050
)
10411051

10421052
if serialize_pydantic:
@@ -1053,6 +1063,45 @@ def inner(response_content, serialize_pydantic=False):
10531063
return inner
10541064

10551065

1066+
@pytest.fixture
1067+
def nonstreaming_responses_model_response():
1068+
return openai.types.responses.Response(
1069+
id="resp_123",
1070+
output=[
1071+
openai.types.responses.ResponseOutputMessage(
1072+
id="msg_123",
1073+
type="message",
1074+
status="completed",
1075+
content=[
1076+
openai.types.responses.ResponseOutputText(
1077+
text="Hello, how can I help you?",
1078+
type="output_text",
1079+
annotations=[],
1080+
)
1081+
],
1082+
role="assistant",
1083+
)
1084+
],
1085+
parallel_tool_calls=False,
1086+
tool_choice="none",
1087+
tools=[],
1088+
created_at=10000000,
1089+
model="gpt-4",
1090+
object="response",
1091+
usage=openai.types.responses.ResponseUsage(
1092+
input_tokens=10,
1093+
input_tokens_details=openai.types.responses.response_usage.InputTokensDetails(
1094+
cached_tokens=0,
1095+
),
1096+
output_tokens=20,
1097+
output_tokens_details=openai.types.responses.response_usage.OutputTokensDetails(
1098+
reasoning_tokens=5,
1099+
),
1100+
total_tokens=30,
1101+
),
1102+
)
1103+
1104+
10561105
class MockServerRequestHandler(BaseHTTPRequestHandler):
10571106
def do_GET(self): # noqa: N802
10581107
# Process an HTTP GET request and return a response.

tests/integrations/langchain/test_langchain.py

Lines changed: 129 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import sentry_sdk
2424
from sentry_sdk import start_transaction
25+
from sentry_sdk.utils import package_version
2526
from sentry_sdk.integrations.langchain import (
2627
LangchainIntegration,
2728
SentryLangchainCallback,
@@ -32,13 +33,14 @@
3233
try:
3334
# langchain v1+
3435
from langchain.tools import tool
36+
from langchain.agents import create_agent
3537
from langchain_classic.agents import AgentExecutor, create_openai_tools_agent # type: ignore[import-not-found]
3638
except ImportError:
3739
# langchain <v1
3840
from langchain.agents import tool, AgentExecutor, create_openai_tools_agent
3941

4042
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
41-
43+
from langchain_core.messages import HumanMessage, SystemMessage
4244

4345
from openai.types.chat.chat_completion_chunk import (
4446
ChatCompletionChunk,
@@ -52,6 +54,8 @@
5254
CompletionUsage,
5355
)
5456

57+
LANGCHAIN_VERSION = package_version("langchain")
58+
5559

5660
@tool
5761
def get_word_length(word: str) -> int:
@@ -79,6 +83,129 @@ def _llm_type(self) -> str:
7983
return llm_type
8084

8185

86+
@pytest.mark.skipif(
87+
LANGCHAIN_VERSION < (1,),
88+
reason="LangChain 1.0+ required (ONE AGENT refactor)",
89+
)
90+
@pytest.mark.parametrize(
91+
"send_default_pii, include_prompts",
92+
[
93+
(True, True),
94+
(True, False),
95+
(False, True),
96+
(False, False),
97+
],
98+
)
99+
@pytest.mark.parametrize(
100+
"system_instructions_content",
101+
[
102+
"You are very powerful assistant, but don't know current events",
103+
[
104+
{"type": "text", "text": "You are a helpful assistant."},
105+
{"type": "text", "text": "Be concise and clear."},
106+
],
107+
],
108+
ids=["string", "blocks"],
109+
)
110+
def test_langchain_create_agent(
111+
sentry_init,
112+
capture_events,
113+
send_default_pii,
114+
include_prompts,
115+
system_instructions_content,
116+
request,
117+
get_model_response,
118+
nonstreaming_responses_model_response,
119+
):
120+
sentry_init(
121+
integrations=[
122+
LangchainIntegration(
123+
include_prompts=include_prompts,
124+
)
125+
],
126+
traces_sample_rate=1.0,
127+
send_default_pii=send_default_pii,
128+
)
129+
events = capture_events()
130+
131+
model_response = get_model_response(
132+
nonstreaming_responses_model_response,
133+
serialize_pydantic=True,
134+
request_headers={
135+
"X-Stainless-Raw-Response": "True",
136+
},
137+
)
138+
139+
llm = ChatOpenAI(
140+
model_name="gpt-3.5-turbo",
141+
temperature=0,
142+
openai_api_key="badkey",
143+
use_responses_api=True,
144+
)
145+
agent = create_agent(
146+
model=llm,
147+
tools=[get_word_length],
148+
system_prompt=SystemMessage(content=system_instructions_content),
149+
name="word_length_agent",
150+
)
151+
152+
with patch.object(
153+
llm.client._client._client,
154+
"send",
155+
return_value=model_response,
156+
) as _:
157+
with start_transaction():
158+
agent.invoke(
159+
{
160+
"messages": [
161+
HumanMessage(content="How many letters in the word eudca"),
162+
],
163+
},
164+
)
165+
166+
tx = events[0]
167+
assert tx["type"] == "transaction"
168+
assert tx["contexts"]["trace"]["origin"] == "manual"
169+
170+
chat_spans = list(x for x in tx["spans"] if x["op"] == "gen_ai.chat")
171+
assert len(chat_spans) == 1
172+
assert chat_spans[0]["origin"] == "auto.ai.langchain"
173+
174+
assert chat_spans[0]["data"]["gen_ai.usage.input_tokens"] == 10
175+
assert chat_spans[0]["data"]["gen_ai.usage.output_tokens"] == 20
176+
assert chat_spans[0]["data"]["gen_ai.usage.total_tokens"] == 30
177+
178+
if send_default_pii and include_prompts:
179+
assert (
180+
chat_spans[0]["data"][SPANDATA.GEN_AI_RESPONSE_TEXT]
181+
== "Hello, how can I help you?"
182+
)
183+
184+
param_id = request.node.callspec.id
185+
if "string" in param_id:
186+
assert [
187+
{
188+
"type": "text",
189+
"content": "You are very powerful assistant, but don't know current events",
190+
}
191+
] == json.loads(chat_spans[0]["data"][SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS])
192+
else:
193+
assert [
194+
{
195+
"type": "text",
196+
"content": "You are a helpful assistant.",
197+
},
198+
{
199+
"type": "text",
200+
"content": "Be concise and clear.",
201+
},
202+
] == json.loads(chat_spans[0]["data"][SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS])
203+
else:
204+
assert SPANDATA.GEN_AI_SYSTEM_INSTRUCTIONS not in chat_spans[0].get("data", {})
205+
assert SPANDATA.GEN_AI_REQUEST_MESSAGES not in chat_spans[0].get("data", {})
206+
assert SPANDATA.GEN_AI_RESPONSE_TEXT not in chat_spans[0].get("data", {})
207+
208+
82209
@pytest.mark.parametrize(
83210
"send_default_pii, include_prompts",
84211
[
@@ -100,7 +227,7 @@ def _llm_type(self) -> str:
100227
],
101228
ids=["string", "list", "blocks"],
102229
)
103-
def test_langchain_agent(
230+
def test_langchain_openai_tools_agent(
104231
sentry_init,
105232
capture_events,
106233
send_default_pii,

0 commit comments

Comments
 (0)