Skip to content

Commit a5897c8

Browse files
authored
feat(toolbox-llamaindex): Enable sync and async context management for ToolboxClient (#307)
* feat(toolbox-llamaindex): Add context manager support for sync and async clients * docs(toolbox-llamaindex): Update README to guide using context manager
1 parent 2378598 commit a5897c8

File tree

3 files changed

+113
-42
lines changed

3 files changed

+113
-42
lines changed

packages/toolbox-llamaindex/README.md

Lines changed: 42 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -59,20 +59,20 @@ from llama_index.core.agent.workflow import AgentWorkflow
5959
from toolbox_llamaindex import ToolboxClient
6060

6161
async def run_agent():
62-
toolbox = ToolboxClient("http://127.0.0.1:5000")
63-
tools = toolbox.load_toolset()
64-
65-
vertex_model = GoogleGenAI(
66-
model="gemini-2.0-flash-001",
67-
vertexai_config={"project": "project-id", "location": "us-central1"},
68-
)
69-
agent = AgentWorkflow.from_tools_or_functions(
70-
tools,
71-
llm=vertex_model,
72-
system_prompt="You are a helpful assistant.",
73-
)
74-
response = await agent.run(user_msg="Get some response from the agent.")
75-
print(response)
62+
async with ToolboxClient("http://127.0.0.1:5000") as toolbox:
63+
tools = toolbox.load_toolset()
64+
65+
vertex_model = GoogleGenAI(
66+
model="gemini-2.0-flash-001",
67+
vertexai_config={"project": "project-id", "location": "us-central1"},
68+
)
69+
agent = AgentWorkflow.from_tools_or_functions(
70+
tools,
71+
llm=vertex_model,
72+
system_prompt="You are a helpful assistant.",
73+
)
74+
response = await agent.run(user_msg="Get some response from the agent.")
75+
print(response)
7676

7777
asyncio.run(run_agent())
7878
```
@@ -90,7 +90,7 @@ Import and initialize the toolbox client.
9090
from toolbox_llamaindex import ToolboxClient
9191

9292
# Replace with your Toolbox service's URL
93-
toolbox = ToolboxClient("http://127.0.0.1:5000")
93+
async with ToolboxClient("http://127.0.0.1:5000") as toolbox:
9494
```
9595

9696
## Loading Tools
@@ -222,10 +222,10 @@ You can configure these dynamic headers as follows:
222222
```python
223223
from toolbox_llamaindex import ToolboxClient
224224

225-
client = ToolboxClient(
225+
async with ToolboxClient(
226226
"toolbox-url",
227227
client_headers={"header1": header1_getter, "header2": header2_getter, ...}
228-
)
228+
) as client:
229229
```
230230

231231
### Authenticating with Google Cloud Servers
@@ -252,13 +252,13 @@ For Toolbox servers hosted on Google Cloud (e.g., Cloud Run) and requiring
252252
from toolbox_core import auth_methods
253253

254254
auth_token_provider = auth_methods.aget_google_id_token # can also use sync method
255-
client = ToolboxClient(
255+
async with ToolboxClient(
256256
URL,
257257
client_headers={"Authorization": auth_token_provider},
258-
)
259-
tools = await client.aload_toolset()
258+
) as client:
259+
tools = await client.aload_toolset()
260260

261-
# Now, you can use the client as usual.
261+
# Now, you can use the client as usual.
262262
```
263263

264264
## Authenticating Tools
@@ -297,16 +297,16 @@ async def get_auth_token():
297297
#### Add Authentication to a Tool
298298

299299
```py
300-
toolbox = ToolboxClient("http://127.0.0.1:5000")
301-
tools = toolbox.load_toolset()
300+
async with ToolboxClient("http://127.0.0.1:5000") as toolbox:
301+
tools = toolbox.load_toolset()
302302

303-
auth_tool = tools[0].add_auth_token_getter("my_auth", get_auth_token) # Single token
303+
auth_tool = tools[0].add_auth_token_getter("my_auth", get_auth_token) # Single token
304304

305-
multi_auth_tool = tools[0].add_auth_token_getters({"auth_1": get_auth_1}, {"auth_2": get_auth_2}) # Multiple tokens
305+
multi_auth_tool = tools[0].add_auth_token_getters({"auth_1": get_auth_1}, {"auth_2": get_auth_2}) # Multiple tokens
306306

307-
# OR
307+
# OR
308308

309-
auth_tools = [tool.add_auth_token_getter("my_auth", get_auth_token) for tool in tools]
309+
auth_tools = [tool.add_auth_token_getter("my_auth", get_auth_token) for tool in tools]
310310
```
311311

312312
#### Add Authentication While Loading
@@ -332,12 +332,12 @@ async def get_auth_token():
332332
# This example just returns a placeholder. Replace with your actual token retrieval.
333333
return "YOUR_ID_TOKEN" # Placeholder
334334

335-
toolbox = ToolboxClient("http://127.0.0.1:5000")
336-
tool = toolbox.load_tool("my-tool")
335+
async with ToolboxClient("http://127.0.0.1:5000") as toolbox:
336+
tool = toolbox.load_tool("my-tool")
337337

338-
auth_tool = tool.add_auth_token_getter("my_auth", get_auth_token)
339-
result = auth_tool.call(input="some input")
340-
print(result)
338+
auth_tool = tool.add_auth_token_getter("my_auth", get_auth_token)
339+
result = auth_tool.call(input="some input")
340+
print(result)
341341
```
342342

343343
## Binding Parameter Values
@@ -352,16 +352,16 @@ modified by the LLM. This is useful for:
352352
### Binding Parameters to a Tool
353353

354354
```py
355-
toolbox = ToolboxClient("http://127.0.0.1:5000")
356-
tools = toolbox.load_toolset()
355+
async with ToolboxClient("http://127.0.0.1:5000") as toolbox:
356+
tools = toolbox.load_toolset()
357357

358-
bound_tool = tool[0].bind_param("param", "value") # Single param
358+
bound_tool = tool[0].bind_param("param", "value") # Single param
359359

360-
multi_bound_tool = tools[0].bind_params({"param1": "value1", "param2": "value2"}) # Multiple params
360+
multi_bound_tool = tools[0].bind_params({"param1": "value1", "param2": "value2"}) # Multiple params
361361

362-
# OR
362+
# OR
363363

364-
bound_tools = [tool.bind_param("param", "value") for tool in tools]
364+
bound_tools = [tool.bind_param("param", "value") for tool in tools]
365365
```
366366

367367
### Binding Parameters While Loading
@@ -407,10 +407,10 @@ import asyncio
407407
from toolbox_llamaindex import ToolboxClient
408408

409409
async def main():
410-
toolbox = ToolboxClient("http://127.0.0.1:5000")
411-
tool = await client.aload_tool("my-tool")
412-
tools = await client.aload_toolset()
413-
response = await tool.ainvoke()
410+
async with ToolboxClient("http://127.0.0.1:5000") as toolbox:
411+
tool = await client.aload_tool("my-tool")
412+
tools = await client.aload_toolset()
413+
response = await tool.ainvoke()
414414

415415
if __name__ == "__main__":
416416
asyncio.run(main())

packages/toolbox-llamaindex/src/toolbox_llamaindex/async_client.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,3 +190,28 @@ def load_toolset(
190190
strict: bool = False,
191191
) -> list[AsyncToolboxTool]:
192192
raise NotImplementedError("Synchronous methods not supported by async client.")
193+
194+
async def close(self):
195+
"""Close the underlying synchronous client."""
196+
await self.__core_client.close()
197+
198+
async def __aenter__(self):
199+
"""
200+
Enter the runtime context related to this client instance.
201+
202+
Allows the client to be used as an asynchronous context manager
203+
(e.g., `async with AsyncToolboxClient(...) as client:`).
204+
205+
Returns:
206+
self: The client instance itself.
207+
"""
208+
return self
209+
210+
async def __aexit__(self, exc_type, exc_val, exc_tb):
211+
"""
212+
Exit the runtime context and close the internally managed session.
213+
214+
Allows the client to be used as an asynchronous context manager
215+
(e.g., `async with AsyncToolboxClient(...) as client:`).
216+
"""
217+
await self.close()

packages/toolbox-llamaindex/src/toolbox_llamaindex/client.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,3 +292,49 @@ def load_toolset(
292292
for core_sync_tool in core_sync_tools:
293293
tools.append(ToolboxTool(core_tool=core_sync_tool))
294294
return tools
295+
296+
def close(self):
297+
"""Close the underlying synchronous client."""
298+
self.__core_client.close()
299+
300+
async def __aenter__(self):
301+
"""
302+
Enter the runtime context related to this client instance.
303+
304+
Allows the client to be used as an asynchronous context manager
305+
(e.g., `async with ToolboxClient(...) as client:`).
306+
307+
Returns:
308+
self: The client instance itself.
309+
"""
310+
return self
311+
312+
async def __aexit__(self, exc_type, exc_val, exc_tb):
313+
"""
314+
Exit the runtime context and close the internally managed session.
315+
316+
Allows the client to be used as an asynchronous context manager
317+
(e.g., `async with ToolboxClient(...) as client:`).
318+
"""
319+
self.close()
320+
321+
def __enter__(self):
322+
"""
323+
Enter the runtime context related to this client instance.
324+
325+
Allows the client to be used as a context manager
326+
(e.g., `with ToolboxClient(...) as client:`).
327+
328+
Returns:
329+
self: The client instance itself.
330+
"""
331+
return self
332+
333+
def __exit__(self, exc_type, exc_val, exc_tb):
334+
"""
335+
Exit the runtime context and close the internally managed session.
336+
337+
Allows the client to be used as a context manager
338+
(e.g., `with ToolboxClient(...) as client:`).
339+
"""
340+
self.close()

0 commit comments

Comments
 (0)