diff --git a/libs/langchain_v1/langchain/agents/structured_output.py b/libs/langchain_v1/langchain/agents/structured_output.py index d3c73cdb5aa1b..f4e34dbce49df 100644 --- a/libs/langchain_v1/langchain/agents/structured_output.py +++ b/libs/langchain_v1/langchain/agents/structured_output.py @@ -192,7 +192,7 @@ def __init__( 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 a5493093b71cd..cddc495a1b6eb 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 38d3da9f3aceb..041df1f9dc787 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 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 @@ class Employee(BaseModel): 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 get_employee_department(*, name: str) -> str | 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 on_request(request: httpx.Request) -> 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 21a9d438af556..7adccaf368b7b 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: