From 1c563c13414cdd279a4c46a70f1a616f074759ee Mon Sep 17 00:00:00 2001 From: Maple Xu Date: Wed, 6 May 2026 13:30:48 -0400 Subject: [PATCH] AI-180: Disable LangSmith network calls in samples tests via mock client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The langsmith_tracing tests previously instantiated `LangSmithPlugin()` with no client. Internally that constructs a real `langsmith.Client` which authenticates against `api.smith.langchain.com` using whatever `LANGSMITH_API_KEY` is in env, producing 401/403 warnings on every test run when a stale or invalid key was present. The samples tests don't assert on trace output, so the right fix is to bypass the network entirely. Added a `mock_ls_client` pytest fixture in `tests/langsmith_tracing/conftest.py` and wired it into `test_basic.py` and `test_chatbot.py` via `LangSmithPlugin(client=mock_ls_client)`. Verified empirically with `pytest-socket --disable-socket --allow-hosts=127.0.0.1,::1,localhost` and `LANGSMITH_API_KEY` set: zero non-localhost connections attempted during the test run. Drive-by: corrected a pre-existing inaccurate docstring on `test_chatbot_read_note` (the test saves a note first, then reads it back; the original elided the save phase). The underlying SDK behavior — `LangSmithInterceptor` force-sets `tracing_context(enabled=True)`, which overrides the standard `LANGSMITH_TRACING=false` disable mechanism — is filed separately as AI-183. Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/langsmith_tracing/conftest.py | 21 +++++++++++++++++++++ tests/langsmith_tracing/test_basic.py | 7 +++++-- tests/langsmith_tracing/test_chatbot.py | 15 ++++++++++----- 3 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 tests/langsmith_tracing/conftest.py diff --git a/tests/langsmith_tracing/conftest.py b/tests/langsmith_tracing/conftest.py new file mode 100644 index 00000000..d66384f8 --- /dev/null +++ b/tests/langsmith_tracing/conftest.py @@ -0,0 +1,21 @@ +"""Shared fixtures for LangSmith tracing tests.""" + +from unittest.mock import MagicMock + +import pytest + + +@pytest.fixture +def mock_ls_client() -> MagicMock: + """Return a mock ``langsmith.Client`` that never makes network calls. + + The samples tests don't assert on trace output — the LangSmith plugin + just needs *some* client object to wire into the worker. ``MagicMock`` + auto-stubs every method the interceptor calls; the explicit ``session`` + and ``tracing_queue`` stubs match what the langsmith library touches + internally. + """ + client = MagicMock() + client.session = MagicMock() + client.tracing_queue = MagicMock() + return client diff --git a/tests/langsmith_tracing/test_basic.py b/tests/langsmith_tracing/test_basic.py index b5be2f35..cef49054 100644 --- a/tests/langsmith_tracing/test_basic.py +++ b/tests/langsmith_tracing/test_basic.py @@ -1,4 +1,5 @@ import uuid +from unittest.mock import MagicMock from temporalio import activity from temporalio.client import Client @@ -10,7 +11,9 @@ from langsmith_tracing.basic.workflows import BasicLLMWorkflow -async def test_basic_workflow(client: Client, env: WorkflowEnvironment): +async def test_basic_workflow( + client: Client, env: WorkflowEnvironment, mock_ls_client: MagicMock +): expected_text = "Temporal is a durable execution platform." @activity.defn(name="call_openai") @@ -22,7 +25,7 @@ async def mock_call_openai(request: OpenAIRequest) -> str: task_queue="test-langsmith-basic", workflows=[BasicLLMWorkflow], activities=[mock_call_openai], - plugins=[LangSmithPlugin()], + plugins=[LangSmithPlugin(client=mock_ls_client)], ): result = await client.execute_workflow( BasicLLMWorkflow.run, diff --git a/tests/langsmith_tracing/test_chatbot.py b/tests/langsmith_tracing/test_chatbot.py index 2febb654..88d98202 100644 --- a/tests/langsmith_tracing/test_chatbot.py +++ b/tests/langsmith_tracing/test_chatbot.py @@ -1,5 +1,6 @@ import json import uuid +from unittest.mock import MagicMock from temporalio import activity from temporalio.client import Client @@ -27,7 +28,9 @@ def _make_function_call_response( ) -async def test_chatbot_save_note(client: Client, env: WorkflowEnvironment): +async def test_chatbot_save_note( + client: Client, env: WorkflowEnvironment, mock_ls_client: MagicMock +): """Test save_note tool call loop — save_note runs as a workflow method.""" call_count = 0 @@ -47,7 +50,7 @@ async def mock_call_openai(request: OpenAIRequest) -> ChatResponse: task_queue="test-langsmith-chatbot", workflows=[ChatbotWorkflow], activities=[mock_call_openai], - plugins=[LangSmithPlugin()], + plugins=[LangSmithPlugin(client=mock_ls_client)], ): wf_handle = await client.start_workflow( ChatbotWorkflow.run, @@ -68,8 +71,10 @@ async def mock_call_openai(request: OpenAIRequest) -> ChatResponse: assert result == "Session ended." -async def test_chatbot_read_note(client: Client, env: WorkflowEnvironment): - """Test read_note tool call loop — read_note runs as a workflow method.""" +async def test_chatbot_read_note( + client: Client, env: WorkflowEnvironment, mock_ls_client: MagicMock +): + """Test read_note tool call loop — saves a note first, then reads it back.""" call_count = 0 @activity.defn(name="call_openai") @@ -97,7 +102,7 @@ async def mock_call_openai(request: OpenAIRequest) -> ChatResponse: task_queue="test-langsmith-chatbot-read", workflows=[ChatbotWorkflow], activities=[mock_call_openai], - plugins=[LangSmithPlugin()], + plugins=[LangSmithPlugin(client=mock_ls_client)], ): wf_handle = await client.start_workflow( ChatbotWorkflow.run,