mirror of
https://github.com/hwchase17/langchain.git
synced 2026-06-09 18:50:33 +00:00
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 <mason@langchain.dev>
This commit is contained in:
committed by
GitHub
parent
b4cd67ac15
commit
9ce73a73f8
@@ -192,7 +192,7 @@ class _SchemaSpec(Generic[SchemaT]):
|
|||||||
class ToolStrategy(Generic[SchemaT]):
|
class ToolStrategy(Generic[SchemaT]):
|
||||||
"""Use a tool calling strategy for model responses."""
|
"""Use a tool calling strategy for model responses."""
|
||||||
|
|
||||||
schema: type[SchemaT]
|
schema: type[SchemaT] | dict[str, Any]
|
||||||
"""Schema for the tool calls."""
|
"""Schema for the tool calls."""
|
||||||
|
|
||||||
schema_specs: list[_SchemaSpec[SchemaT]]
|
schema_specs: list[_SchemaSpec[SchemaT]]
|
||||||
@@ -218,7 +218,7 @@ class ToolStrategy(Generic[SchemaT]):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
schema: type[SchemaT],
|
schema: type[SchemaT] | dict[str, Any],
|
||||||
*,
|
*,
|
||||||
tool_message_content: str | None = None,
|
tool_message_content: str | None = None,
|
||||||
handle_errors: bool
|
handle_errors: bool
|
||||||
|
|||||||
@@ -139,7 +139,6 @@ ignore-var-parameters = true # ignore missing documentation for *args and **kwa
|
|||||||
"ARG", # Arguments, needs to fix
|
"ARG", # Arguments, needs to fix
|
||||||
]
|
]
|
||||||
"tests/unit_tests/agents/test_return_direct_spec.py" = ["F821"]
|
"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_responses.py" = ["F821"]
|
||||||
"tests/unit_tests/agents/test_react_agent.py" = ["ALL"]
|
"tests/unit_tests/agents/test_react_agent.py" = ["ALL"]
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,34 @@
|
|||||||
from __future__ import annotations
|
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:
|
try:
|
||||||
from langchain_openai import ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
except ImportError:
|
except ImportError:
|
||||||
skip_openai_integration_tests = True
|
skip_openai_integration_tests = True
|
||||||
else:
|
else:
|
||||||
skip_openai_integration_tests = False
|
skip_openai_integration_tests = "OPENAI_API_KEY" not in os.environ
|
||||||
|
|
||||||
AGENT_PROMPT = "You are an HR assistant."
|
AGENT_PROMPT = "You are an HR assistant."
|
||||||
|
|
||||||
@@ -30,8 +48,8 @@ class AssertionByInvocation(BaseSchema):
|
|||||||
|
|
||||||
class TestCase(BaseSchema):
|
class TestCase(BaseSchema):
|
||||||
name: str
|
name: str
|
||||||
response_format: Union[Dict[str, Any], List[Dict[str, Any]]]
|
response_format: dict[str, Any] | list[dict[str, Any]]
|
||||||
assertions_by_invocation: List[AssertionByInvocation]
|
assertions_by_invocation: list[AssertionByInvocation]
|
||||||
|
|
||||||
|
|
||||||
class Employee(BaseModel):
|
class Employee(BaseModel):
|
||||||
@@ -49,12 +67,12 @@ EMPLOYEES: list[Employee] = [
|
|||||||
TEST_CASES = load_spec("responses", as_model=TestCase)
|
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))
|
mock = MagicMock(side_effect=lambda *, name: fn(name=name))
|
||||||
input_model = create_model(f"{name}_input", name=(str, ...))
|
input_model = create_model(f"{name}_input", name=(str, ...))
|
||||||
|
|
||||||
@tool(name, description=description, args_schema=input_model)
|
@tool(name, description=description, args_schema=input_model)
|
||||||
def _wrapped(name: str):
|
def _wrapped(name: str) -> Any:
|
||||||
return mock(name=name)
|
return mock(name=name)
|
||||||
|
|
||||||
return {"tool": _wrapped, "mock": mock}
|
return {"tool": _wrapped, "mock": mock}
|
||||||
@@ -106,7 +124,7 @@ def test_responses_integration_matrix(case: TestCase) -> None:
|
|||||||
|
|
||||||
for assertion in case.assertions_by_invocation:
|
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
|
nonlocal llm_request_count
|
||||||
llm_request_count += 1
|
llm_request_count += 1
|
||||||
|
|
||||||
@@ -123,7 +141,7 @@ def test_responses_integration_matrix(case: TestCase) -> None:
|
|||||||
agent = create_agent(
|
agent = create_agent(
|
||||||
model,
|
model,
|
||||||
tools=[role_tool["tool"], dept_tool["tool"]],
|
tools=[role_tool["tool"], dept_tool["tool"]],
|
||||||
prompt=AGENT_PROMPT,
|
system_prompt=AGENT_PROMPT,
|
||||||
response_format=tool_output,
|
response_format=tool_output,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
from pydantic.alias_generators import to_camel
|
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(
|
with (Path(__file__).parent / "specifications" / f"{spec_name}.json").open(
|
||||||
"r", encoding="utf-8"
|
"r", encoding="utf-8"
|
||||||
) as f:
|
) as f:
|
||||||
|
|||||||
Reference in New Issue
Block a user