From 9ce73a73f842ecc20690151dc52ab2b71ed8b914 Mon Sep 17 00:00:00 2001 From: Christophe Bornet Date: Fri, 9 Jan 2026 23:44:33 +0100 Subject: [PATCH] test(langchain): activate `test_responses_spec` tests (#34564) description by @mdrxy - Enable `test_responses_spec.py` integration tests that were previously skipped at module level - Widen `ToolStrategy.schema` type annotation from `type[SchemaT]` to `type[SchemaT] | dict[str, Any]` to match actual supported usage (JSON schema dicts were already handled at runtime) - Fix type annotations and linting issues in test file (modernize to `dict`/`list`, add return types, prefix unused `_request` param) - Improve generic typing in `load_spec` utility with bounded `TypeVar` Co-authored-by: Mason Daugherty --- .../langchain/agents/structured_output.py | 4 +- libs/langchain_v1/pyproject.toml | 1 - .../unit_tests/agents/test_responses_spec.py | 38 ++++++++++++++----- .../tests/unit_tests/agents/utils.py | 6 ++- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/libs/langchain_v1/langchain/agents/structured_output.py b/libs/langchain_v1/langchain/agents/structured_output.py index d3c73cdb5aa..f4e34dbce49 100644 --- a/libs/langchain_v1/langchain/agents/structured_output.py +++ b/libs/langchain_v1/langchain/agents/structured_output.py @@ -192,7 +192,7 @@ class _SchemaSpec(Generic[SchemaT]): class ToolStrategy(Generic[SchemaT]): """Use a tool calling strategy for model responses.""" - schema: type[SchemaT] + schema: type[SchemaT] | dict[str, Any] """Schema for the tool calls.""" schema_specs: list[_SchemaSpec[SchemaT]] @@ -218,7 +218,7 @@ class ToolStrategy(Generic[SchemaT]): def __init__( self, - schema: type[SchemaT], + schema: type[SchemaT] | dict[str, Any], *, tool_message_content: str | None = None, handle_errors: bool diff --git a/libs/langchain_v1/pyproject.toml b/libs/langchain_v1/pyproject.toml index a5493093b71..cddc495a1b6 100644 --- a/libs/langchain_v1/pyproject.toml +++ b/libs/langchain_v1/pyproject.toml @@ -139,7 +139,6 @@ ignore-var-parameters = true # ignore missing documentation for *args and **kwa "ARG", # Arguments, needs to fix ] "tests/unit_tests/agents/test_return_direct_spec.py" = ["F821"] -"tests/unit_tests/agents/test_responses_spec.py" = ["F821"] "tests/unit_tests/agents/test_responses.py" = ["F821"] "tests/unit_tests/agents/test_react_agent.py" = ["ALL"] diff --git a/libs/langchain_v1/tests/unit_tests/agents/test_responses_spec.py b/libs/langchain_v1/tests/unit_tests/agents/test_responses_spec.py index 38d3da9f3ac..041df1f9dc7 100644 --- a/libs/langchain_v1/tests/unit_tests/agents/test_responses_spec.py +++ b/libs/langchain_v1/tests/unit_tests/agents/test_responses_spec.py @@ -1,16 +1,34 @@ from __future__ import annotations -import pytest +import os +from typing import ( + TYPE_CHECKING, + Any, +) +from unittest.mock import MagicMock + +import httpx +import pytest +from langchain_core.messages import HumanMessage +from langchain_core.tools import tool +from pydantic import BaseModel, create_model + +from langchain.agents import create_agent +from langchain.agents.structured_output import ( + ToolStrategy, +) +from tests.unit_tests.agents.utils import BaseSchema, load_spec + +if TYPE_CHECKING: + from collections.abc import Callable -# Skip this test since langgraph.prebuilt.responses is not available -pytest.skip("langgraph.prebuilt.responses not available", allow_module_level=True) try: from langchain_openai import ChatOpenAI except ImportError: skip_openai_integration_tests = True else: - skip_openai_integration_tests = False + skip_openai_integration_tests = "OPENAI_API_KEY" not in os.environ AGENT_PROMPT = "You are an HR assistant." @@ -30,8 +48,8 @@ class AssertionByInvocation(BaseSchema): class TestCase(BaseSchema): name: str - response_format: Union[Dict[str, Any], List[Dict[str, Any]]] - assertions_by_invocation: List[AssertionByInvocation] + response_format: dict[str, Any] | list[dict[str, Any]] + assertions_by_invocation: list[AssertionByInvocation] class Employee(BaseModel): @@ -49,12 +67,12 @@ EMPLOYEES: list[Employee] = [ TEST_CASES = load_spec("responses", as_model=TestCase) -def _make_tool(fn, *, name: str, description: str): +def _make_tool(fn: Callable[..., str | None], *, name: str, description: str) -> dict[str, Any]: mock = MagicMock(side_effect=lambda *, name: fn(name=name)) input_model = create_model(f"{name}_input", name=(str, ...)) @tool(name, description=description, args_schema=input_model) - def _wrapped(name: str): + def _wrapped(name: str) -> Any: return mock(name=name) return {"tool": _wrapped, "mock": mock} @@ -106,7 +124,7 @@ def test_responses_integration_matrix(case: TestCase) -> None: for assertion in case.assertions_by_invocation: - def on_request(request: httpx.Request) -> None: + def on_request(_request: httpx.Request) -> None: nonlocal llm_request_count llm_request_count += 1 @@ -123,7 +141,7 @@ def test_responses_integration_matrix(case: TestCase) -> None: agent = create_agent( model, tools=[role_tool["tool"], dept_tool["tool"]], - prompt=AGENT_PROMPT, + system_prompt=AGENT_PROMPT, response_format=tool_output, ) diff --git a/libs/langchain_v1/tests/unit_tests/agents/utils.py b/libs/langchain_v1/tests/unit_tests/agents/utils.py index 21a9d438af5..7adccaf368b 100644 --- a/libs/langchain_v1/tests/unit_tests/agents/utils.py +++ b/libs/langchain_v1/tests/unit_tests/agents/utils.py @@ -1,5 +1,6 @@ import json from pathlib import Path +from typing import TypeVar from pydantic import BaseModel, ConfigDict from pydantic.alias_generators import to_camel @@ -13,7 +14,10 @@ class BaseSchema(BaseModel): ) -def load_spec(spec_name: str, as_model: type[BaseModel]) -> list[BaseModel]: +_T = TypeVar("_T", bound=BaseModel) + + +def load_spec(spec_name: str, as_model: type[_T]) -> list[_T]: with (Path(__file__).parent / "specifications" / f"{spec_name}.json").open( "r", encoding="utf-8" ) as f: