Skip to content

feat(toolbox-llamaindex)!: Base toolbox-llamaindex over toolbox-core #244

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 31 additions & 34 deletions packages/toolbox-llamaindex/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,27 @@ applications, enabling advanced orchestration and interaction with GenAI models.
## Table of Contents
<!-- TOC -->

- [MCP Toolbox LlamaIndex SDK](#mcp-toolbox-llamaindex-sdk)
- [Installation](#installation)
- [Quickstart](#quickstart)
- [TODO: add link](#todo-add-link)
- [Usage](#usage)
- [Loading Tools](#loading-tools)
- [Load a toolset](#load-a-toolset)
- [Load a single tool](#load-a-single-tool)
- [Use with LlamaIndex](#use-with-llamaindex)
- [Maintain state](#maintain-state)
- [Manual usage](#manual-usage)
- [Authenticating Tools](#authenticating-tools)
- [Supported Authentication Mechanisms](#supported-authentication-mechanisms)
- [Configure Tools](#configure-tools)
- [Configure SDK](#configure-sdk)
- [Add Authentication to a Tool](#add-authentication-to-a-tool)
- [Add Authentication While Loading](#add-authentication-while-loading)
- [Complete Example](#complete-example)
- [Binding Parameter Values](#binding-parameter-values)
- [Binding Parameters to a Tool](#binding-parameters-to-a-tool)
- [Binding Parameters While Loading](#binding-parameters-while-loading)
- [Binding Dynamic Values](#binding-dynamic-values)
- [Asynchronous Usage](#asynchronous-usage)
- [Installation](#installation)
- [Quickstart](#quickstart)
- [Usage](#usage)
- [Loading Tools](#loading-tools)
- [Load a toolset](#load-a-toolset)
- [Load a single tool](#load-a-single-tool)
- [Use with LlamaIndex](#use-with-llamaindex)
- [Maintain state](#maintain-state)
- [Manual usage](#manual-usage)
- [Authenticating Tools](#authenticating-tools)
- [Supported Authentication Mechanisms](#supported-authentication-mechanisms)
- [Configure Tools](#configure-tools)
- [Configure SDK](#configure-sdk)
- [Add Authentication to a Tool](#add-authentication-to-a-tool)
- [Add Authentication While Loading](#add-authentication-while-loading)
- [Complete Example](#complete-example)
- [Binding Parameter Values](#binding-parameter-values)
- [Binding Parameters to a Tool](#binding-parameters-to-a-tool)
- [Binding Parameters While Loading](#binding-parameters-while-loading)
- [Binding Dynamic Values](#binding-dynamic-values)
- [Asynchronous Usage](#asynchronous-usage)

<!-- /TOC -->

Expand All @@ -44,8 +42,7 @@ pip install toolbox-llamaindex
## Quickstart

Here's a minimal example to get you started using
# TODO: add link
[LlamaIndex]():
[LlamaIndex](https://docs.llamaindex.ai/en/stable/#getting-started):

```py
import asyncio
Expand Down Expand Up @@ -111,7 +108,7 @@ available to your LLM agent.

## Use with LlamaIndex

LangChain's agents can dynamically choose and execute tools based on the user
LlamaIndex's agents can dynamically choose and execute tools based on the user
input. Include tools loaded from the Toolbox SDK in the agent's toolkit:

```py
Expand Down Expand Up @@ -165,7 +162,7 @@ print(response)
Execute a tool manually using the `call` method:

```py
result = tools[0].call({"name": "Alice", "age": 30})
result = tools[0].call(name="Alice", age=30)
```

This is useful for testing tools or when you need precise control over tool
Expand Down Expand Up @@ -210,21 +207,21 @@ async def get_auth_token():
toolbox = ToolboxClient("http://127.0.0.1:5000")
tools = toolbox.load_toolset()

auth_tool = tools[0].add_auth_token("my_auth", get_auth_token) # Single token
auth_tool = tools[0].add_auth_token_getter("my_auth", get_auth_token) # Single token

multi_auth_tool = tools[0].add_auth_tokens({"my_auth", get_auth_token}) # Multiple tokens
multi_auth_tool = tools[0].add_auth_token_getters({"auth_1": get_auth_1}, {"auth_2": get_auth_2}) # Multiple tokens

# OR

auth_tools = [tool.add_auth_token("my_auth", get_auth_token) for tool in tools]
auth_tools = [tool.add_auth_token_getter("my_auth", get_auth_token) for tool in tools]
```

#### Add Authentication While Loading

```py
auth_tool = toolbox.load_tool(auth_tokens={"my_auth": get_auth_token})
auth_tool = toolbox.load_tool(auth_token_getters={"my_auth": get_auth_token})

auth_tools = toolbox.load_toolset(auth_tokens={"my_auth": get_auth_token})
auth_tools = toolbox.load_toolset(auth_token_getters={"my_auth": get_auth_token})
```

> [!NOTE]
Expand All @@ -245,8 +242,8 @@ async def get_auth_token():
toolbox = ToolboxClient("http://127.0.0.1:5000")
tool = toolbox.load_tool("my-tool")

auth_tool = tool.add_auth_token("my_auth", get_auth_token)
result = auth_tool.call({"input": "some input"})
auth_tool = tool.add_auth_token_getter("my_auth", get_auth_token)
result = auth_tool.call(input="some input")
print(result)
```

Expand Down
3 changes: 2 additions & 1 deletion packages/toolbox-llamaindex/integration.cloudbuild.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
steps:
- id: Install library requirements
name: 'python:${_VERSION}'
dir: 'packages/toolbox-llamaindex'
args:
- install
- '-r'
- 'packages/toolbox-llamaindex/requirements.txt'
- 'requirements.txt'
- '--user'
entrypoint: pip
- id: Install test requirements
Expand Down
4 changes: 3 additions & 1 deletion packages/toolbox-llamaindex/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[project]
name = "toolbox-llamindex"
name = "toolbox-llamaindex"
dynamic = ["version"]
readme = "README.md"
description = "Python SDK for interacting with the Toolbox service with LlamaIndex"
Expand All @@ -9,6 +9,8 @@ authors = [
{name = "Google LLC", email = "googleapis-packages@google.com"}
]
dependencies = [
# TODO: Bump toolbox-core version to 0.2.0
"toolbox-core==0.1.0",
"llama-index>=0.12.0,<1.0.0",
"PyYAML>=6.0.1,<7.0.0",
"pydantic>=2.8.0,<3.0.0",
Expand Down
1 change: 1 addition & 0 deletions packages/toolbox-llamaindex/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
-e ../toolbox-core
llama-index==0.12.33
PyYAML==6.0.2
pydantic==2.11.4
Expand Down
138 changes: 77 additions & 61 deletions packages/toolbox-llamaindex/src/toolbox_llamaindex/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
from warnings import warn

from aiohttp import ClientSession
from toolbox_core.client import ToolboxClient as ToolboxCoreClient

from .tools import AsyncToolboxTool
from .utils import ManifestSchema, _load_manifest
from .async_tools import AsyncToolboxTool


# This class is an internal implementation detail and is not exposed to the
Expand All @@ -38,67 +38,72 @@ def __init__(
url: The base URL of the Toolbox service.
session: An HTTP client session.
"""
self.__url = url
self.__session = session
self.__core_client = ToolboxCoreClient(url=url, session=session)

async def aload_tool(
self,
tool_name: str,
auth_tokens: dict[str, Callable[[], str]] = {},
auth_token_getters: dict[str, Callable[[], str]] = {},
auth_tokens: Optional[dict[str, Callable[[], str]]] = None,
auth_headers: Optional[dict[str, Callable[[], str]]] = None,
bound_params: dict[str, Union[Any, Callable[[], Any]]] = {},
strict: bool = True,
) -> AsyncToolboxTool:
"""
Loads the tool with the given tool name from the Toolbox service.

Args:
tool_name: The name of the tool to load.
auth_tokens: An optional mapping of authentication source names to
functions that retrieve ID tokens.
auth_headers: Deprecated. Use `auth_tokens` instead.
auth_token_getters: An optional mapping of authentication source
names to functions that retrieve ID tokens.
auth_tokens: Deprecated. Use `auth_token_getters` instead.
auth_headers: Deprecated. Use `auth_token_getters` instead.
bound_params: An optional mapping of parameter names to their
bound values.
strict: If True, raises a ValueError if any of the given bound
parameters are missing from the schema or require
authentication. If False, only issues a warning.

Returns:
A tool loaded from the Toolbox.
"""
if auth_tokens:
if auth_token_getters:
warn(
"Both `auth_token_getters` and `auth_tokens` are provided. `auth_tokens` is deprecated, and `auth_token_getters` will be used.",
DeprecationWarning,
)
else:
warn(
"Argument `auth_tokens` is deprecated. Use `auth_token_getters` instead.",
DeprecationWarning,
)
auth_token_getters = auth_tokens

if auth_headers:
if auth_tokens:
if auth_token_getters:
warn(
"Both `auth_tokens` and `auth_headers` are provided. `auth_headers` is deprecated, and `auth_tokens` will be used.",
"Both `auth_token_getters` and `auth_headers` are provided. `auth_headers` is deprecated, and `auth_token_getters` will be used.",
DeprecationWarning,
)
else:
warn(
"Argument `auth_headers` is deprecated. Use `auth_tokens` instead.",
"Argument `auth_headers` is deprecated. Use `auth_token_getters` instead.",
DeprecationWarning,
)
auth_tokens = auth_headers

url = f"{self.__url}/api/tool/{tool_name}"
manifest: ManifestSchema = await _load_manifest(url, self.__session)

return AsyncToolboxTool(
tool_name,
manifest.tools[tool_name],
self.__url,
self.__session,
auth_tokens,
bound_params,
strict,
auth_token_getters = auth_headers

core_tool = await self.__core_client.load_tool(
name=tool_name,
auth_token_getters=auth_token_getters,
bound_params=bound_params,
)
return AsyncToolboxTool(core_tool=core_tool)

async def aload_toolset(
self,
toolset_name: Optional[str] = None,
auth_tokens: dict[str, Callable[[], str]] = {},
auth_token_getters: dict[str, Callable[[], str]] = {},
auth_tokens: Optional[dict[str, Callable[[], str]]] = None,
auth_headers: Optional[dict[str, Callable[[], str]]] = None,
bound_params: dict[str, Union[Any, Callable[[], Any]]] = {},
strict: bool = True,
strict: bool = False,
) -> list[AsyncToolboxTool]:
"""
Loads tools from the Toolbox service, optionally filtered by toolset
Expand All @@ -107,65 +112,76 @@ async def aload_toolset(
Args:
toolset_name: The name of the toolset to load. If not provided,
all tools are loaded.
auth_tokens: An optional mapping of authentication source names to
functions that retrieve ID tokens.
auth_headers: Deprecated. Use `auth_tokens` instead.
auth_token_getters: An optional mapping of authentication source
names to functions that retrieve ID tokens.
auth_tokens: Deprecated. Use `auth_token_getters` instead.
auth_headers: Deprecated. Use `auth_token_getters` instead.
bound_params: An optional mapping of parameter names to their
bound values.
strict: If True, raises a ValueError if any of the given bound
parameters are missing from the schema or require
authentication. If False, only issues a warning.
strict: If True, raises an error if *any* loaded tool instance fails
to utilize at least one provided parameter or auth token (if any
provided). If False (default), raises an error only if a
user-provided parameter or auth token cannot be applied to *any*
loaded tool across the set.

Returns:
A list of all tools loaded from the Toolbox.
"""
if auth_headers:
if auth_tokens:
if auth_tokens:
if auth_token_getters:
warn(
"Both `auth_tokens` and `auth_headers` are provided. `auth_headers` is deprecated, and `auth_tokens` will be used.",
"Both `auth_token_getters` and `auth_tokens` are provided. `auth_tokens` is deprecated, and `auth_token_getters` will be used.",
DeprecationWarning,
)
else:
warn(
"Argument `auth_headers` is deprecated. Use `auth_tokens` instead.",
"Argument `auth_tokens` is deprecated. Use `auth_token_getters` instead.",
DeprecationWarning,
)
auth_tokens = auth_headers

url = f"{self.__url}/api/toolset/{toolset_name or ''}"
manifest: ManifestSchema = await _load_manifest(url, self.__session)
tools: list[AsyncToolboxTool] = []

for tool_name, tool_schema in manifest.tools.items():
tools.append(
AsyncToolboxTool(
tool_name,
tool_schema,
self.__url,
self.__session,
auth_tokens,
bound_params,
strict,
auth_token_getters = auth_tokens

if auth_headers:
if auth_token_getters:
warn(
"Both `auth_token_getters` and `auth_headers` are provided. `auth_headers` is deprecated, and `auth_token_getters` will be used.",
DeprecationWarning,
)
else:
warn(
"Argument `auth_headers` is deprecated. Use `auth_token_getters` instead.",
DeprecationWarning,
)
)
auth_token_getters = auth_headers

core_tools = await self.__core_client.load_toolset(
name=toolset_name,
auth_token_getters=auth_token_getters,
bound_params=bound_params,
strict=strict,
)

tools = []
for core_tool in core_tools:
tools.append(AsyncToolboxTool(core_tool=core_tool))
return tools

def load_tool(
self,
tool_name: str,
auth_tokens: dict[str, Callable[[], str]] = {},
auth_token_getters: dict[str, Callable[[], str]] = {},
auth_tokens: Optional[dict[str, Callable[[], str]]] = None,
auth_headers: Optional[dict[str, Callable[[], str]]] = None,
bound_params: dict[str, Union[Any, Callable[[], Any]]] = {},
strict: bool = True,
) -> AsyncToolboxTool:
raise NotImplementedError("Synchronous methods not supported by async client.")

def load_toolset(
self,
toolset_name: Optional[str] = None,
auth_tokens: dict[str, Callable[[], str]] = {},
auth_token_getters: dict[str, Callable[[], str]] = {},
auth_tokens: Optional[dict[str, Callable[[], str]]] = None,
auth_headers: Optional[dict[str, Callable[[], str]]] = None,
bound_params: dict[str, Union[Any, Callable[[], Any]]] = {},
strict: bool = True,
strict: bool = False,
) -> list[AsyncToolboxTool]:
raise NotImplementedError("Synchronous methods not supported by async client.")
Loading