diff --git a/docs/docs/contributing/how_to/integrations/standard_tests.ipynb b/docs/docs/contributing/how_to/integrations/standard_tests.ipynb index 45739d7d5d9..b6657f915c6 100644 --- a/docs/docs/contributing/how_to/integrations/standard_tests.ipynb +++ b/docs/docs/contributing/how_to/integrations/standard_tests.ipynb @@ -13,12 +13,12 @@ "First, let's install 2 dependencies:\n", "\n", "- `langchain-core` will define the interfaces we want to import to define our custom tool.\n", - "- `langchain-tests==0.3.2` will provide the standard tests we want to use.\n", + "- `langchain-tests==0.3.3` will provide the standard tests we want to use.\n", "\n", ":::note\n", "\n", "Because added tests in new versions of `langchain-tests` will always break your CI/CD pipelines, we recommend pinning the \n", - "version of `langchain-tests==0.3.2` to avoid unexpected changes.\n", + "version of `langchain-tests` to avoid unexpected changes.\n", "\n", ":::" ] @@ -178,7 +178,7 @@ "\n", "```bash\n", "# run unit tests without network access\n", - "pytest --disable-socket --enable-unix-socket tests/unit_tests\n", + "pytest --disable-socket --allow-unix-socket tests/unit_tests\n", "\n", "# run integration tests\n", "pytest tests/integration_tests\n", diff --git a/libs/standard-tests/langchain_tests/integration_tests/tools.py b/libs/standard-tests/langchain_tests/integration_tests/tools.py index 2609a87c845..49876260378 100644 --- a/libs/standard-tests/langchain_tests/integration_tests/tools.py +++ b/libs/standard-tests/langchain_tests/integration_tests/tools.py @@ -10,50 +10,56 @@ class ToolsIntegrationTests(ToolsTests): If invoked with a ToolCall, the tool should return a valid ToolMessage content. """ tool_call = ToolCall( - name=tool.name, args=self.tool_invoke_params_example, id=None + name=tool.name, + args=self.tool_invoke_params_example, + id="123", + type="tool_call", ) result = tool.invoke(tool_call) if tool.response_format == "content": - content = result + tool_message = result elif tool.response_format == "content_and_artifact": # should be (content, artifact) assert isinstance(result, tuple) assert len(result) == 2 - content, artifact = result + tool_message, artifact = result assert artifact # artifact can be anything, but shouldn't be none # check content is a valid ToolMessage content - assert isinstance(content, (str, list)) - if isinstance(content, list): + assert isinstance(tool_message.content, (str, list)) + if isinstance(tool_message.content, list): # content blocks must be str or dict - assert all(isinstance(c, (str, dict)) for c in content) + assert all(isinstance(c, (str, dict)) for c in tool_message.content) async def test_async_invoke_matches_output_schema(self, tool: BaseTool) -> None: """ If ainvoked with a ToolCall, the tool should return a valid ToolMessage content. """ tool_call = ToolCall( - name=tool.name, args=self.tool_invoke_params_example, id=None + name=tool.name, + args=self.tool_invoke_params_example, + id="123", + type="tool_call", ) result = await tool.ainvoke(tool_call) if tool.response_format == "content": - content = result + tool_message = result elif tool.response_format == "content_and_artifact": # should be (content, artifact) assert isinstance(result, tuple) assert len(result) == 2 - content, artifact = result + tool_message, artifact = result assert artifact # artifact can be anything, but shouldn't be none # check content is a valid ToolMessage content - assert isinstance(content, (str, list)) - if isinstance(content, list): + assert isinstance(tool_message.content, (str, list)) + if isinstance(tool_message.content, list): # content blocks must be str or dict - assert all(isinstance(c, (str, dict)) for c in content) + assert all(isinstance(c, (str, dict)) for c in tool_message.content) def test_invoke_no_tool_call(self, tool: BaseTool) -> None: """ diff --git a/libs/standard-tests/tests/unit_tests/test_basic_tool.py b/libs/standard-tests/tests/unit_tests/test_basic_tool.py new file mode 100644 index 00000000000..4ef95c504f6 --- /dev/null +++ b/libs/standard-tests/tests/unit_tests/test_basic_tool.py @@ -0,0 +1,63 @@ +from typing import Type + +from langchain_core.tools import BaseTool + +from langchain_tests.integration_tests import ToolsIntegrationTests +from langchain_tests.unit_tests import ToolsUnitTests + + +class ParrotMultiplyTool(BaseTool): # type: ignore + name: str = "ParrotMultiplyTool" + description: str = ( + "Multiply two numbers like a parrot. Parrots always add " + "eighty for their matey." + ) + + def _run(self, a: int, b: int) -> int: + return a * b + 80 + + +class TestParrotMultiplyToolUnit(ToolsUnitTests): + @property + def tool_constructor(self) -> Type[ParrotMultiplyTool]: + return ParrotMultiplyTool + + @property + def tool_constructor_params(self) -> dict: + # if your tool constructor instead required initialization arguments like + # `def __init__(self, some_arg: int):`, you would return those here + # as a dictionary, e.g.: `return {'some_arg': 42}` + return {} + + @property + def tool_invoke_params_example(self) -> dict: + """ + Returns a dictionary representing the "args" of an example tool call. + + This should NOT be a ToolCall dict - i.e. it should not + have {"name", "id", "args"} keys. + """ + return {"a": 2, "b": 3} + + +class TestParrotMultiplyToolIntegration(ToolsIntegrationTests): + @property + def tool_constructor(self) -> Type[ParrotMultiplyTool]: + return ParrotMultiplyTool + + @property + def tool_constructor_params(self) -> dict: + # if your tool constructor instead required initialization arguments like + # `def __init__(self, some_arg: int):`, you would return those here + # as a dictionary, e.g.: `return {'some_arg': 42}` + return {} + + @property + def tool_invoke_params_example(self) -> dict: + """ + Returns a dictionary representing the "args" of an example tool call. + + This should NOT be a ToolCall dict - i.e. it should not + have {"name", "id", "args"} keys. + """ + return {"a": 2, "b": 3}