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
This commit is contained in:
Mateo Torres 2025-09-05 18:13:56 -03:00 committed by GitHub
parent e188fc6ae9
commit c481f1d677
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 57 additions and 56 deletions

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "langchain-arcade" name = "langchain-arcade"
version = "1.3.1" version = "1.4.0"
description = "An integration package connecting Arcade and Langchain/LangGraph" description = "An integration package connecting Arcade and Langchain/LangGraph"
authors = ["Arcade <dev@arcade.dev>"] authors = ["Arcade <dev@arcade.dev>"]
readme = "README.md" readme = "README.md"
@ -9,7 +9,7 @@ license = "MIT"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.10,<4" python = ">=3.10,<4"
arcadepy = "1.3.*" arcadepy = "1.7.*"
langchain-core = ">=0.3.49,<0.4" langchain-core = ">=0.3.49,<0.4"

View file

@ -87,7 +87,7 @@ def make_tool():
accordingly. 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' # Split on the first dot to derive a 'toolkit' slug and a tool 'name'
if "." in fully_qualified_name: if "." in fully_qualified_name:
raw_toolkit, raw_tool_name = fully_qualified_name.split(".", 1) raw_toolkit, raw_tool_name = fully_qualified_name.split(".", 1)
@ -125,6 +125,7 @@ def make_tool():
# Build the pydantic fields # Build the pydantic fields
data = { data = {
"fully_qualified_name": fully_qualified_name, "fully_qualified_name": fully_qualified_name,
"qualified_name": fully_qualified_name,
"name": raw_tool_name, "name": raw_tool_name,
"toolkit": toolkit, "toolkit": toolkit,
"input": tool_input, "input": tool_input,
@ -157,19 +158,19 @@ async def test_init_tools_parameterized(
manager, is_async = manager_fixture manager, is_async = manager_fixture
client = async_mock_arcade_client if is_async else mock_arcade_client 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 client.tools.get.return_value = mock_tool
page_cls = AsyncOffsetPage if is_async else SyncOffsetPage page_cls = AsyncOffsetPage if is_async else SyncOffsetPage
client.tools.list.return_value = page_cls(items=[mock_tool]) client.tools.list.return_value = page_cls(items=[mock_tool])
# Act # 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
assert "Search_SearchGoogle" in manager.tools assert "GoogleSearch_Search" in manager.tools
assert manager._tools["Search_SearchGoogle"] == mock_tool assert manager._tools["GoogleSearch_Search"] == mock_tool
client.tools.get.assert_called_once_with(name="Search_SearchGoogle") client.tools.get.assert_called_once_with(name="GoogleSearch_Search")
# Verify the result is a list of StructuredTool objects # Verify the result is a list of StructuredTool objects
assert len(result) == 1 assert len(result) == 1
@ -184,22 +185,22 @@ async def test_to_langchain_parameterized(
# Arrange # Arrange
manager, is_async = manager_fixture manager, is_async = manager_fixture
mock_tool = make_tool("Search_SearchGoogle") mock_tool = make_tool("GoogleSearch_Search")
manager._tools = {"Search_SearchGoogle": mock_tool} manager._tools = {"GoogleSearch_Search": mock_tool}
# Act - with default parameters # Act - with default parameters
result = await maybe_await(manager.to_langchain(), is_async) result = await maybe_await(manager.to_langchain(), is_async)
# Assert # Assert
assert len(result) == 1 assert len(result) == 1
assert result[0].name == "Search_SearchGoogle" assert result[0].name == "GoogleSearch_Search"
# Act - with underscores=False # Act - with underscores=False
result = await maybe_await(manager.to_langchain(use_underscores=False), is_async) result = await maybe_await(manager.to_langchain(use_underscores=False), is_async)
# Assert # Assert
assert len(result) == 1 assert len(result) == 1
assert result[0].name == "Search.SearchGoogle" assert result[0].name == "GoogleSearch.Search"
@pytest.mark.asyncio @pytest.mark.asyncio
@ -213,18 +214,18 @@ async def test_deprecated_get_tools_parameterized(
manager, is_async = manager_fixture manager, is_async = manager_fixture
client = async_mock_arcade_client if is_async else mock_arcade_client 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 client.tools.get.return_value = mock_tool
manager._tools = {} # Ensure no tools are already loaded manager._tools = {} # Ensure no tools are already loaded
# Act - Check for deprecation warning # Act - Check for deprecation warning
with pytest.warns(DeprecationWarning): 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 - Method should still work
assert len(result) == 1 assert len(result) == 1
assert "Search_SearchGoogle" in manager.tools assert "GoogleSearch_Search" in manager.tools
client.tools.get.assert_called_once_with(name="Search_SearchGoogle") client.tools.get.assert_called_once_with(name="GoogleSearch_Search")
@pytest.mark.asyncio @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 client = async_mock_arcade_client if is_async else mock_arcade_client
# Set up two different mock tools # Set up two different mock tools
mock_tool_google = make_tool("Search_SearchGoogle") mock_tool_google = make_tool("GoogleSearch_Search")
mock_tool_bing = make_tool("Search_SearchBing") mock_tool_bing = make_tool("BingSearch_Search")
# First tool already exists in manager # 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 # Second tool will be added
client.tools.get.return_value = mock_tool_bing client.tools.get.return_value = mock_tool_bing
# Act # 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 - Both tools should now be in the manager
assert "Search_SearchGoogle" in manager.tools assert "GoogleSearch_Search" in manager.tools
assert "Search_SearchBing" in manager.tools assert "BingSearch_Search" in manager.tools
assert len(manager.tools) == 2 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 @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 client = async_mock_arcade_client if is_async else mock_arcade_client
# Create a tool that's already in the manager # Create a tool that's already in the manager
mock_tool_google = make_tool("Search_SearchGoogle") mock_tool_send_email = make_tool("Gmail_SendEmail")
manager._tools = {"Search_SearchGoogle": mock_tool_google} manager._tools = {"Gmail_SendEmail": mock_tool_send_email}
# Create tools to be added from the toolkit # Create tools to be added from the toolkit
mock_tool_bing = make_tool("Search_SearchBing") mock_tool_list_emails = make_tool("Gmail_ListEmails")
mock_tool_ddg = make_tool("Search_SearchDuckDuckGo") mock_tool_trash_email = make_tool("Gmail_TrashEmail")
# Mock the response for toolkit listing # Mock the response for toolkit listing
page_cls = AsyncOffsetPage if is_async else SyncOffsetPage 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 # Act
await maybe_await(manager.add_toolkit("Search"), is_async) await maybe_await(manager.add_toolkit("Search"), is_async)
# Assert - All tools should now be in the manager # Assert - All tools should now be in the manager
assert len(manager.tools) == 3 assert len(manager.tools) == 3
assert "Search_SearchGoogle" in manager.tools assert "Gmail_SendEmail" in manager.tools
assert "Search_SearchBing" in manager.tools assert "Gmail_ListEmails" in manager.tools
assert "Search_SearchDuckDuckGo" in manager.tools assert "Gmail_TrashEmail" in manager.tools
client.tools.list.assert_called_once_with(toolkit="Search", limit=NOT_GIVEN, offset=NOT_GIVEN) 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 # Create an auth response object
auth_response = AuthorizationResponse( 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 # 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 client = async_mock_arcade_client if is_async else mock_arcade_client
completed_response = AuthorizationResponse( 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 client.auth.wait_for_completion.return_value = completed_response
# Create an auth response object # Create an auth response object
auth_response = AuthorizationResponse( 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 # Act - Test with string ID
@ -374,7 +375,7 @@ async def test_get_tools_no_init_parameterized(
manager, is_async = manager_fixture manager, is_async = manager_fixture
client = async_mock_arcade_client if is_async else mock_arcade_client 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 page_cls = AsyncOffsetPage if is_async else SyncOffsetPage
client.tools.list.return_value = page_cls(items=[mock_tool]) client.tools.list.return_value = page_cls(items=[mock_tool])
@ -386,7 +387,7 @@ async def test_get_tools_no_init_parameterized(
# Assert # Assert
assert len(tools) == 0 assert len(tools) == 0
assert "Search_SearchGoogle" not in manager.tools assert "GoogleSearch_Search" not in manager.tools
@pytest.mark.asyncio @pytest.mark.asyncio
@ -401,21 +402,21 @@ async def test_get_tools_with_explicit_parameterized(
manager, is_async = manager_fixture manager, is_async = manager_fixture
client = async_mock_arcade_client if is_async else mock_arcade_client client = async_mock_arcade_client if is_async else mock_arcade_client
mock_tool_google = make_tool("Search_SearchGoogle") mock_tool_google = make_tool("GoogleSearch_Search")
mock_tool_bing = make_tool("Search_SearchBing") mock_tool_bing = make_tool("BingSearch_Search")
client.tools.get.side_effect = [mock_tool_google, mock_tool_bing] client.tools.get.side_effect = [mock_tool_google, mock_tool_bing]
# Act - Check for deprecation warning # Act - Check for deprecation warning
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning):
retrieved_tools = await maybe_await( 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
assert len(retrieved_tools) == 2 assert len(retrieved_tools) == 2
assert set(manager.tools) == {"Search_SearchGoogle", "Search_SearchBing"} assert set(manager.tools) == {"GoogleSearch_Search", "BingSearch_Search"}
client.tools.get.assert_any_call(name="Search_SearchGoogle") client.tools.get.assert_any_call(name="GoogleSearch_Search")
client.tools.get.assert_any_call(name="Search_SearchBing") client.tools.get.assert_any_call(name="BingSearch_Search")
def test_arcade_tool_manager_deprecation_warning(): 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 client = async_mock_arcade_client if is_async else mock_arcade_client
auth_response = AuthorizationResponse( 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 client.tools.authorize.return_value = auth_response
# Act # Act
response = await maybe_await( 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
assert response.id == "auth_123" assert response.id == "auth_123"
assert response.status == "pending" assert response.status == "pending"
client.tools.authorize.assert_called_once_with( 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. the stored tool definition's requirements contain an authorization entry.
""" """
# Arrange # Arrange
tool_name = "Search_SearchGoogle" tool_name = "GoogleSearch_Search"
# Pass a MagicMock with 'authorization' to ensure it gets converted # Pass a MagicMock with 'authorization' to ensure it gets converted
mock_tool_def = make_tool(tool_name, requirements=MagicMock(authorization="some_required_auth")) mock_tool_def = make_tool(tool_name, requirements=MagicMock(authorization="some_required_auth"))
manager._tools[tool_name] = mock_tool_def 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. is not required in the tool definition.
""" """
# Arrange # Arrange
tool_name = "Search_SearchGoogle" tool_name = "GoogleSearch_Search"
mock_tool_def = make_tool(tool_name, requirements=MagicMock(authorization=None)) mock_tool_def = make_tool(tool_name, requirements=MagicMock(authorization=None))
manager._tools[tool_name] = mock_tool_def 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. an existing tool definition by name.
""" """
# Arrange # Arrange
tool_name = "Search_SearchGoogle" tool_name = "GoogleSearch_Search"
mock_tool_def = make_tool(tool_name) mock_tool_def = make_tool(tool_name)
manager._tools[tool_name] = mock_tool_def 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. Test the internal _retrieve_tool_definitions method by specifying tools only.
""" """
# Arrange # Arrange
mock_tool = make_tool("Search_SearchGoogle") mock_tool = make_tool("GoogleSearch_Search")
mock_arcade_client.tools.get.return_value = mock_tool mock_arcade_client.tools.get.return_value = mock_tool
# Act # Act
results = manager._retrieve_tool_definitions(tools=["Search_SearchGoogle"], toolkits=None) results = manager._retrieve_tool_definitions(tools=["GoogleSearch_Search"], toolkits=None)
# Assert # Assert
assert len(results) == 1 assert len(results) == 1
assert results[0].fully_qualified_name == "Search_SearchGoogle" assert results[0].fully_qualified_name == "GoogleSearch_Search"
mock_arcade_client.tools.get.assert_called_once_with(name="Search_SearchGoogle") 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): 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 # Arrange
from langchain_arcade.manager import _create_tool_map from langchain_arcade.manager import _create_tool_map
tool1 = make_tool("Search.SearchGoogle") tool1 = make_tool("GoogleSearch.Search")
tool2 = make_tool("Gmail.SendEmail") tool2 = make_tool("Gmail.SendEmail")
# Act # Act
result = _create_tool_map([tool1, tool2], use_underscores=True) result = _create_tool_map([tool1, tool2], use_underscores=True)
# Assert # Assert
assert "Search_SearchGoogle" in result assert "GoogleSearch_Search" in result
assert "Gmail_SendEmail" in result assert "Gmail_SendEmail" in result
assert len(result) == 2 assert len(result) == 2
@ -694,13 +695,13 @@ def test_create_tool_map_with_dots(make_tool):
# Arrange # Arrange
from langchain_arcade.manager import _create_tool_map from langchain_arcade.manager import _create_tool_map
tool1 = make_tool("Search.SearchGoogle") tool1 = make_tool("GoogleSearch.Search")
tool2 = make_tool("Gmail.SendEmail") tool2 = make_tool("Gmail.SendEmail")
# Act # Act
result = _create_tool_map([tool1, tool2], use_underscores=False) result = _create_tool_map([tool1, tool2], use_underscores=False)
# Assert # Assert
assert "Search.SearchGoogle" in result assert "GoogleSearch.Search" in result
assert "Gmail.SendEmail" in result assert "Gmail.SendEmail" in result
assert len(result) == 2 assert len(result) == 2