From c481f1d677eea540e55273dca412b62f2e1a48e0 Mon Sep 17 00:00:00 2001 From: Mateo Torres Date: Fri, 5 Sep 2025 18:13:56 -0300 Subject: [PATCH] upgraded langchain_arcade (#546) This bumps the requirement version of the Arcade client to the latest version (which supports the new function for the custom user verifier) It also moves the tests to be compatible to our new tool names --- contrib/langchain/pyproject.toml | 4 +- contrib/langchain/tests/test_manager.py | 109 ++++++++++++------------ 2 files changed, 57 insertions(+), 56 deletions(-) diff --git a/contrib/langchain/pyproject.toml b/contrib/langchain/pyproject.toml index 7f725900..e22d61eb 100644 --- a/contrib/langchain/pyproject.toml +++ b/contrib/langchain/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langchain-arcade" -version = "1.3.1" +version = "1.4.0" description = "An integration package connecting Arcade and Langchain/LangGraph" authors = ["Arcade "] readme = "README.md" @@ -9,7 +9,7 @@ license = "MIT" [tool.poetry.dependencies] python = ">=3.10,<4" -arcadepy = "1.3.*" +arcadepy = "1.7.*" langchain-core = ">=0.3.49,<0.4" diff --git a/contrib/langchain/tests/test_manager.py b/contrib/langchain/tests/test_manager.py index b33eabae..16ad8a5d 100644 --- a/contrib/langchain/tests/test_manager.py +++ b/contrib/langchain/tests/test_manager.py @@ -87,7 +87,7 @@ def make_tool(): accordingly. """ - def _make_tool(fully_qualified_name="Search_SearchGoogle", **kwargs): + def _make_tool(fully_qualified_name="GoogleSearch_Search", **kwargs): # Split on the first dot to derive a 'toolkit' slug and a tool 'name' if "." in fully_qualified_name: raw_toolkit, raw_tool_name = fully_qualified_name.split(".", 1) @@ -125,6 +125,7 @@ def make_tool(): # Build the pydantic fields data = { "fully_qualified_name": fully_qualified_name, + "qualified_name": fully_qualified_name, "name": raw_tool_name, "toolkit": toolkit, "input": tool_input, @@ -157,19 +158,19 @@ async def test_init_tools_parameterized( manager, is_async = manager_fixture client = async_mock_arcade_client if is_async else mock_arcade_client - mock_tool = make_tool("Search_SearchGoogle") + mock_tool = make_tool("GoogleSearch_Search") client.tools.get.return_value = mock_tool page_cls = AsyncOffsetPage if is_async else SyncOffsetPage client.tools.list.return_value = page_cls(items=[mock_tool]) # Act - result = await maybe_await(manager.init_tools(tools=["Search_SearchGoogle"]), is_async) + result = await maybe_await(manager.init_tools(tools=["GoogleSearch_Search"]), is_async) # Assert - assert "Search_SearchGoogle" in manager.tools - assert manager._tools["Search_SearchGoogle"] == mock_tool - client.tools.get.assert_called_once_with(name="Search_SearchGoogle") + assert "GoogleSearch_Search" in manager.tools + assert manager._tools["GoogleSearch_Search"] == mock_tool + client.tools.get.assert_called_once_with(name="GoogleSearch_Search") # Verify the result is a list of StructuredTool objects assert len(result) == 1 @@ -184,22 +185,22 @@ async def test_to_langchain_parameterized( # Arrange manager, is_async = manager_fixture - mock_tool = make_tool("Search_SearchGoogle") - manager._tools = {"Search_SearchGoogle": mock_tool} + mock_tool = make_tool("GoogleSearch_Search") + manager._tools = {"GoogleSearch_Search": mock_tool} # Act - with default parameters result = await maybe_await(manager.to_langchain(), is_async) # Assert assert len(result) == 1 - assert result[0].name == "Search_SearchGoogle" + assert result[0].name == "GoogleSearch_Search" # Act - with underscores=False result = await maybe_await(manager.to_langchain(use_underscores=False), is_async) # Assert assert len(result) == 1 - assert result[0].name == "Search.SearchGoogle" + assert result[0].name == "GoogleSearch.Search" @pytest.mark.asyncio @@ -213,18 +214,18 @@ async def test_deprecated_get_tools_parameterized( manager, is_async = manager_fixture client = async_mock_arcade_client if is_async else mock_arcade_client - mock_tool = make_tool("Search_SearchGoogle") + mock_tool = make_tool("GoogleSearch_Search") client.tools.get.return_value = mock_tool manager._tools = {} # Ensure no tools are already loaded # Act - Check for deprecation warning with pytest.warns(DeprecationWarning): - result = await maybe_await(manager.get_tools(tools=["Search_SearchGoogle"]), is_async) + result = await maybe_await(manager.get_tools(tools=["GoogleSearch_Search"]), is_async) # Assert - Method should still work assert len(result) == 1 - assert "Search_SearchGoogle" in manager.tools - client.tools.get.assert_called_once_with(name="Search_SearchGoogle") + assert "GoogleSearch_Search" in manager.tools + client.tools.get.assert_called_once_with(name="GoogleSearch_Search") @pytest.mark.asyncio @@ -239,23 +240,23 @@ async def test_add_tool_parameterized( client = async_mock_arcade_client if is_async else mock_arcade_client # Set up two different mock tools - mock_tool_google = make_tool("Search_SearchGoogle") - mock_tool_bing = make_tool("Search_SearchBing") + mock_tool_google = make_tool("GoogleSearch_Search") + mock_tool_bing = make_tool("BingSearch_Search") # First tool already exists in manager - manager._tools = {"Search_SearchGoogle": mock_tool_google} + manager._tools = {"GoogleSearch_Search": mock_tool_google} # Second tool will be added client.tools.get.return_value = mock_tool_bing # Act - await maybe_await(manager.add_tool("Search_SearchBing"), is_async) + await maybe_await(manager.add_tool("BingSearch_Search"), is_async) # Assert - Both tools should now be in the manager - assert "Search_SearchGoogle" in manager.tools - assert "Search_SearchBing" in manager.tools + assert "GoogleSearch_Search" in manager.tools + assert "BingSearch_Search" in manager.tools assert len(manager.tools) == 2 - client.tools.get.assert_called_once_with(name="Search_SearchBing") + client.tools.get.assert_called_once_with(name="BingSearch_Search") @pytest.mark.asyncio @@ -270,25 +271,25 @@ async def test_add_toolkit_parameterized( client = async_mock_arcade_client if is_async else mock_arcade_client # Create a tool that's already in the manager - mock_tool_google = make_tool("Search_SearchGoogle") - manager._tools = {"Search_SearchGoogle": mock_tool_google} + mock_tool_send_email = make_tool("Gmail_SendEmail") + manager._tools = {"Gmail_SendEmail": mock_tool_send_email} # Create tools to be added from the toolkit - mock_tool_bing = make_tool("Search_SearchBing") - mock_tool_ddg = make_tool("Search_SearchDuckDuckGo") + mock_tool_list_emails = make_tool("Gmail_ListEmails") + mock_tool_trash_email = make_tool("Gmail_TrashEmail") # Mock the response for toolkit listing page_cls = AsyncOffsetPage if is_async else SyncOffsetPage - client.tools.list.return_value = page_cls(items=[mock_tool_bing, mock_tool_ddg]) + client.tools.list.return_value = page_cls(items=[mock_tool_list_emails, mock_tool_trash_email]) # Act await maybe_await(manager.add_toolkit("Search"), is_async) # Assert - All tools should now be in the manager assert len(manager.tools) == 3 - assert "Search_SearchGoogle" in manager.tools - assert "Search_SearchBing" in manager.tools - assert "Search_SearchDuckDuckGo" in manager.tools + assert "Gmail_SendEmail" in manager.tools + assert "Gmail_ListEmails" in manager.tools + assert "Gmail_TrashEmail" in manager.tools client.tools.list.assert_called_once_with(toolkit="Search", limit=NOT_GIVEN, offset=NOT_GIVEN) @@ -308,7 +309,7 @@ async def test_is_authorized_with_response_object_parameterized( # Create an auth response object auth_response = AuthorizationResponse( - id="auth_abc", status="pending", tool_fully_qualified_name="Search_SearchGoogle" + id="auth_abc", status="pending", tool_fully_qualified_name="GoogleSearch_Search" ) # Act - Test with string ID @@ -338,13 +339,13 @@ async def test_wait_for_auth_with_response_object_parameterized( client = async_mock_arcade_client if is_async else mock_arcade_client completed_response = AuthorizationResponse( - id="auth_abc", status="completed", tool_fully_qualified_name="Search_SearchGoogle" + id="auth_abc", status="completed", tool_fully_qualified_name="GoogleSearch_Search" ) client.auth.wait_for_completion.return_value = completed_response # Create an auth response object auth_response = AuthorizationResponse( - id="auth_abc", status="pending", tool_fully_qualified_name="Search_SearchGoogle" + id="auth_abc", status="pending", tool_fully_qualified_name="GoogleSearch_Search" ) # Act - Test with string ID @@ -374,7 +375,7 @@ async def test_get_tools_no_init_parameterized( manager, is_async = manager_fixture client = async_mock_arcade_client if is_async else mock_arcade_client - mock_tool = make_tool("Search_SearchGoogle") + mock_tool = make_tool("GoogleSearch_Search") page_cls = AsyncOffsetPage if is_async else SyncOffsetPage client.tools.list.return_value = page_cls(items=[mock_tool]) @@ -386,7 +387,7 @@ async def test_get_tools_no_init_parameterized( # Assert assert len(tools) == 0 - assert "Search_SearchGoogle" not in manager.tools + assert "GoogleSearch_Search" not in manager.tools @pytest.mark.asyncio @@ -401,21 +402,21 @@ async def test_get_tools_with_explicit_parameterized( manager, is_async = manager_fixture client = async_mock_arcade_client if is_async else mock_arcade_client - mock_tool_google = make_tool("Search_SearchGoogle") - mock_tool_bing = make_tool("Search_SearchBing") + mock_tool_google = make_tool("GoogleSearch_Search") + mock_tool_bing = make_tool("BingSearch_Search") client.tools.get.side_effect = [mock_tool_google, mock_tool_bing] # Act - Check for deprecation warning with pytest.warns(DeprecationWarning): retrieved_tools = await maybe_await( - manager.get_tools(tools=["Search_SearchGoogle", "Search_SearchBing"]), is_async + manager.get_tools(tools=["GoogleSearch_Search", "BingSearch_Search"]), is_async ) # Assert assert len(retrieved_tools) == 2 - assert set(manager.tools) == {"Search_SearchGoogle", "Search_SearchBing"} - client.tools.get.assert_any_call(name="Search_SearchGoogle") - client.tools.get.assert_any_call(name="Search_SearchBing") + assert set(manager.tools) == {"GoogleSearch_Search", "BingSearch_Search"} + client.tools.get.assert_any_call(name="GoogleSearch_Search") + client.tools.get.assert_any_call(name="BingSearch_Search") def test_arcade_tool_manager_deprecation_warning(): @@ -442,20 +443,20 @@ async def test_authorize_parameterized( client = async_mock_arcade_client if is_async else mock_arcade_client auth_response = AuthorizationResponse( - id="auth_123", status="pending", tool_fully_qualified_name="Search_SearchGoogle" + id="auth_123", status="pending", tool_fully_qualified_name="GoogleSearch_Search" ) client.tools.authorize.return_value = auth_response # Act response = await maybe_await( - manager.authorize(tool_name="Search_SearchGoogle", user_id="user_123"), is_async + manager.authorize(tool_name="GoogleSearch_Search", user_id="user_123"), is_async ) # Assert assert response.id == "auth_123" assert response.status == "pending" client.tools.authorize.assert_called_once_with( - tool_name="Search_SearchGoogle", user_id="user_123" + tool_name="GoogleSearch_Search", user_id="user_123" ) @@ -465,7 +466,7 @@ def test_requires_auth_true(manager, make_tool): the stored tool definition's requirements contain an authorization entry. """ # Arrange - tool_name = "Search_SearchGoogle" + tool_name = "GoogleSearch_Search" # Pass a MagicMock with 'authorization' to ensure it gets converted mock_tool_def = make_tool(tool_name, requirements=MagicMock(authorization="some_required_auth")) manager._tools[tool_name] = mock_tool_def @@ -483,7 +484,7 @@ def test_requires_auth_false(manager, make_tool): is not required in the tool definition. """ # Arrange - tool_name = "Search_SearchGoogle" + tool_name = "GoogleSearch_Search" mock_tool_def = make_tool(tool_name, requirements=MagicMock(authorization=None)) manager._tools[tool_name] = mock_tool_def @@ -500,7 +501,7 @@ def test_get_tool_definition_existing(manager, make_tool): an existing tool definition by name. """ # Arrange - tool_name = "Search_SearchGoogle" + tool_name = "GoogleSearch_Search" mock_tool_def = make_tool(tool_name) manager._tools[tool_name] = mock_tool_def @@ -528,16 +529,16 @@ def test_retrieve_tool_definitions_tools_only(manager, mock_arcade_client, make_ Test the internal _retrieve_tool_definitions method by specifying tools only. """ # Arrange - mock_tool = make_tool("Search_SearchGoogle") + mock_tool = make_tool("GoogleSearch_Search") mock_arcade_client.tools.get.return_value = mock_tool # Act - results = manager._retrieve_tool_definitions(tools=["Search_SearchGoogle"], toolkits=None) + results = manager._retrieve_tool_definitions(tools=["GoogleSearch_Search"], toolkits=None) # Assert assert len(results) == 1 - assert results[0].fully_qualified_name == "Search_SearchGoogle" - mock_arcade_client.tools.get.assert_called_once_with(name="Search_SearchGoogle") + assert results[0].fully_qualified_name == "GoogleSearch_Search" + mock_arcade_client.tools.get.assert_called_once_with(name="GoogleSearch_Search") def test_retrieve_tool_definitions_toolkits_only(manager, mock_arcade_client, make_tool): @@ -675,14 +676,14 @@ def test_create_tool_map_with_underscores(make_tool): # Arrange from langchain_arcade.manager import _create_tool_map - tool1 = make_tool("Search.SearchGoogle") + tool1 = make_tool("GoogleSearch.Search") tool2 = make_tool("Gmail.SendEmail") # Act result = _create_tool_map([tool1, tool2], use_underscores=True) # Assert - assert "Search_SearchGoogle" in result + assert "GoogleSearch_Search" in result assert "Gmail_SendEmail" in result assert len(result) == 2 @@ -694,13 +695,13 @@ def test_create_tool_map_with_dots(make_tool): # Arrange from langchain_arcade.manager import _create_tool_map - tool1 = make_tool("Search.SearchGoogle") + tool1 = make_tool("GoogleSearch.Search") tool2 = make_tool("Gmail.SendEmail") # Act result = _create_tool_map([tool1, tool2], use_underscores=False) # Assert - assert "Search.SearchGoogle" in result + assert "GoogleSearch.Search" in result assert "Gmail.SendEmail" in result assert len(result) == 2