@@ -8,23 +8,54 @@ applications, enabling advanced orchestration and interaction with GenAI models.
8
8
## Table of Contents
9
9
<!-- TOC -->
10
10
11
+ - [ Quickstart] ( #quickstart )
11
12
- [ Installation] ( #installation )
12
13
- [ Usage] ( #usage )
13
- - [ Load a toolset] ( #load-a-toolset )
14
- - [ Load a single tool] ( #load-a-single-tool )
14
+ - [ Loading Tools] ( #loading-tools )
15
+ - [ Load a toolset] ( #load-a-toolset )
16
+ - [ Load a single tool] ( #load-a-single-tool )
15
17
- [ Use with LangChain] ( #use-with-langchain )
16
18
- [ Use with LangGraph] ( #use-with-langgraph )
17
19
- [ Represent Tools as Nodes] ( #represent-tools-as-nodes )
18
20
- [ Connect Tools with LLM] ( #connect-tools-with-llm )
19
21
- [ Manual usage] ( #manual-usage )
20
22
- [ Authenticating Tools] ( #authenticating-tools )
21
23
- [ Supported Authentication Mechanisms] ( #supported-authentication-mechanisms )
22
- - [ Configuring Tools for Authentication] ( #configuring-tools-for-authentication )
23
- - [ Configure SDK for Authentication] ( #configure-sdk-for-authentication )
24
+ - [ Configure Tools] ( #configure-tools )
25
+ - [ Configure SDK] ( #configure-sdk )
26
+ - [ Add Authentication to a Tool] ( #add-authentication-to-a-tool )
27
+ - [ Add Authentication While Loading] ( #add-authentication-while-loading )
24
28
- [ Complete Example] ( #complete-example )
29
+ - [ Binding Parameter Values] ( #binding-parameter-values )
30
+ - [ Binding Parameters to a Tool] ( #binding-parameters-to-a-tool )
31
+ - [ Binding Parameters While Loading] ( #binding-parameters-while-loading )
32
+ - [ Binding Dynamic Values] ( #binding-dynamic-values )
33
+ - [ Error Handling] ( #error-handling )
25
34
26
35
<!-- /TOC -->
27
36
37
+ ## Quickstart
38
+
39
+ Here's a minimal example to get you started:
40
+
41
+ ``` py
42
+ import asyncio
43
+ from toolbox_langchain_sdk import ToolboxClient
44
+ from langchain_google_vertexai import ChatVertexAI
45
+
46
+ async def main ():
47
+ toolbox = ToolboxClient(" http://127.0.0.1:5000" )
48
+ tools = await toolbox.load_toolset()
49
+
50
+ model = ChatVertexAI(model = " gemini-1.5-pro-002" )
51
+ agent = model.bind_tools(tools)
52
+ result = agent.invoke(" How's the weather today?" )
53
+ print (result)
54
+
55
+ if __name__ == " __main__" :
56
+ asyncio.run(main())
57
+ ```
58
+
28
59
## Installation
29
60
30
61
> [ !IMPORTANT]
@@ -51,19 +82,21 @@ toolbox = ToolboxClient("http://127.0.0.1:5000")
51
82
> [ !IMPORTANT]
52
83
> The toolbox client requires an asynchronous environment.
53
84
> For guidance on running asynchronous Python programs, see
54
- > [ running an async program in python ] ( https://docs.python.org/3/library/asyncio-runner.html#running-an-asyncio-program ) .
85
+ > [ asyncio documentation ] ( https://docs.python.org/3/library/asyncio-runner.html#running-an-asyncio-program ) .
55
86
56
87
> [ !TIP]
57
- > You can also pass your own ` ClientSession ` so that the ` ToolboxClient ` can
58
- > reuse the same session.
88
+ > You can also pass your own ` ClientSession ` to reuse the same session:
59
89
> ``` py
60
90
> async with ClientSession() as session:
61
91
> toolbox = ToolboxClient(" http://localhost:5000" , session)
62
92
> ```
63
93
64
- # # Load a toolset
94
+ # # Loading Tools
65
95
66
- You can load a toolset, a collection of related tools.
96
+ # ## Load a toolset
97
+
98
+ A toolset is a collection of related tools. You can load all tools in a toolset
99
+ or a specific one:
67
100
68
101
```py
69
102
# Load all tools
@@ -73,19 +106,19 @@ tools = await toolbox.load_toolset()
73
106
tools = await toolbox.load_toolset(" my-toolset" )
74
107
```
75
108
76
- ## Load a single tool
77
-
78
- You can also load a single tool.
109
+ ### Load a single tool
79
110
80
111
``` py
81
112
tool = await toolbox.load_tool(" my-tool" )
82
113
```
83
114
115
+ Loading individual tools gives you finer-grained control over which tools are
116
+ available to your LLM agent.
117
+
84
118
## Use with LangChain
85
119
86
120
LangChain's agents can dynamically choose and execute tools based on the user
87
- input. The user can include the tools loaded from the Toolbox SDK in the agent's
88
- toolkit.
121
+ input. Include tools loaded from the Toolbox SDK in the agent's toolkit:
89
122
90
123
``` py
91
124
from langchain_google_vertexai import ChatVertexAI
@@ -101,15 +134,13 @@ result = agent.invoke("Do something with the tools")
101
134
102
135
## Use with LangGraph
103
136
104
- The Toolbox SDK can be seamlessly integrated with LangGraph to enable the use of
105
- Toolbox service tools within a graph-based workflow. Using this SDK, we can
106
- follow the [ official guide] ( https://langchain-ai.github.io/langgraph/ ) with
107
- minimal changes.
137
+ Integrate the Toolbox SDK with LangGraph to use Toolbox service tools within a
138
+ graph-based workflow. Follow the [ official
139
+ guide] ( https://langchain-ai.github.io/langgraph/ ) with minimal changes.
108
140
109
141
### Represent Tools as Nodes
110
142
111
- Each tool generated by the SDK can be represented as a LangGraph node. The
112
- node's functionality would encapsulate the execution of the corresponding tool.
143
+ Represent each tool as a LangGraph node, encapsulating the tool's execution within the node's functionality:
113
144
114
145
``` py
115
146
from toolbox_langchain_sdk import ToolboxClient
@@ -120,8 +151,7 @@ from langgraph.prebuilt import ToolNode
120
151
def call_model (state : MessagesState):
121
152
messages = state[' messages' ]
122
153
response = model.invoke(messages)
123
- # We return a list, because this will get added to the existing list
124
- return {" messages" : [response]}
154
+ return {" messages" : [response]} # Return a list to add to existing messages
125
155
126
156
model = ChatVertexAI(model = " gemini-1.5-pro-002" )
127
157
builder = StateGraph(MessagesState)
@@ -133,10 +163,8 @@ builder.add_node("tools", tool_node)
133
163
134
164
### Connect Tools with LLM
135
165
136
- Now we can connect the tool nodes with LLM nodes. The LLM can decide which tool
137
- to use based on the user input or the context of the conversation. The output
138
- from a tool can then be fed back into the LLM for further processing or
139
- decision-making.
166
+ Connect tool nodes with LLM nodes. The LLM decides which tool to use based on
167
+ input or context. Tool output can be fed back into the LLM:
140
168
141
169
``` py
142
170
from typing import Literal
@@ -147,97 +175,89 @@ from langchain_core.messages import HumanMessage
147
175
def should_continue (state : MessagesState) -> Literal[" tools" , END ]:
148
176
messages = state[' messages' ]
149
177
last_message = messages[- 1 ]
150
- # If the LLM makes a tool call, then we route to the "tools" node
151
178
if last_message.tool_calls:
152
- return " tools"
153
- # Otherwise, we stop (reply to the user)
154
- return END
179
+ return " tools" # Route to "tools" node if LLM makes a tool call
180
+ return END # Otherwise, stop
155
181
156
182
builder.add_edge(START , " agent" )
157
- builder.add_conditional_edges(
158
- " agent" ,
159
- should_continue,
160
- )
183
+ builder.add_conditional_edges(" agent" , should_continue)
161
184
builder.add_edge(" tools" , ' agent' )
162
185
163
186
graph = builder.compile()
164
187
165
- graph.invoke(
166
- {" messages" : [HumanMessage(content = " Do something with the tools" )]},
167
- )
188
+ graph.invoke({" messages" : [HumanMessage(content = " Do something with the tools" )]})
168
189
```
169
190
170
191
## Manual usage
171
192
172
- You can also execute a tool manually using the ` arun ` method.
193
+ Execute a tool manually using the ` ainvoke ` method:
173
194
174
195
``` py
175
- result = await tools[0 ].arun({ " name" : " Alice" , " age" : 30 })
196
+ result = await tools[0 ].ainvoke({ " name" : " Alice" , " age" : 30 })
176
197
```
177
198
199
+ This is useful for testing tools or when you need precise control over tool
200
+ execution outside of an agent framework.
201
+
178
202
## Authenticating Tools
179
203
180
204
> [ !WARNING]
181
205
> Always use HTTPS to connect your application with the Toolbox service,
182
206
> especially when using tools with authentication configured. Using HTTP exposes
183
- > your application to serious security risks, including unauthorized access to
184
- > user information and man-in-the-middle attacks, where sensitive data can be
185
- > intercepted.
207
+ > your application to serious security risks.
186
208
187
- Some tools in your Toolbox configuration might require user authentication to
188
- access sensitive data. This section guides you on how to configure tools for
189
- authentication and use them with the SDK.
209
+ Some tools require user authentication to access sensitive data.
190
210
191
211
### Supported Authentication Mechanisms
192
- The Toolbox SDK currently supports authentication using [ OIDC
193
- protocol] ( https://openid.net/specs/openid-connect-core-1_0.html ) . Specifically,
194
- it uses [ ID
195
- tokens] ( https://openid.net/specs/openid-connect-core-1_0.html#IDToken ) and * not*
196
- access tokens for [ Google OAuth
212
+ Toolbox currently supports authentication using the [ OIDC
213
+ protocol] ( https://openid.net/specs/openid-connect-core-1_0.html ) with [ ID
214
+ tokens] ( https://openid.net/specs/openid-connect-core-1_0.html#IDToken ) (not
215
+ access tokens) for [ Google OAuth
197
216
2.0] ( https://cloud.google.com/apigee/docs/api-platform/security/oauth/oauth-home ) .
198
217
199
- ### Configuring Tools for Authentication
218
+ ### Configure Tools
200
219
201
220
Refer to [ these
202
221
instructions] ( ../../docs/tools/README.md#authenticated-parameters ) on
203
222
configuring tools for authenticated parameters.
204
223
205
- ### Configure SDK for Authentication
224
+ ### Configure SDK
206
225
207
- Provide the ` auth_tokens ` parameter to the ` load_tool ` or ` load_toolset ` calls
208
- with a dictionary. The keys of this dictionary should match the names of the
209
- authentication sources configured in your tools file (e.g., ` my_auth_service ` ),
210
- and the values should be callable functions (e.g., lambdas or regular functions)
211
- that return the ID token of the logged-in user.
212
-
213
- Here's an example:
226
+ You need a method to retrieve an ID token from your authentication service:
214
227
215
228
``` py
216
- def get_auth_token ():
229
+ async def get_auth_token ():
217
230
# ... Logic to retrieve ID token (e.g., from local storage, OAuth flow)
218
231
# This example just returns a placeholder. Replace with your actual token retrieval.
219
- return " YOUR_ID_TOKEN"
232
+ return " YOUR_ID_TOKEN" # Placeholder
233
+ ```
234
+
235
+ #### Add Authentication to a Tool
220
236
237
+ ``` py
221
238
toolbox = ToolboxClient(" http://localhost:5000" )
239
+ tools = await toolbox.load_toolset()
222
240
223
- tools = toolbox.load_toolset(auth_tokens = { " my_auth_service" : get_auth_token })
241
+ auth_tool = tools[0 ].add_auth_token(" my_auth" , get_auth_token) # Single token
242
+
243
+ multi_auth_tool = tools[0 ].add_auth_tokens({" my_auth" , get_auth_token}) # Multiple tokens
224
244
225
245
# OR
226
246
227
- tool = toolbox.load_tool( " my_tool " , auth_tokens = { " my_auth_service " : get_auth_token })
247
+ auth_tools = [tool.add_auth_token( " my_auth " , get_auth_token) for tool in tools]
228
248
```
229
249
230
- Alternatively, you can call the ` add_auth_token ` method to configure
231
- authentication separately.
250
+ #### Add Authentication While Loading
232
251
233
252
``` py
234
- toolbox.add_auth_token(" my_auth_service" , get_auth_token)
253
+ auth_tool = await toolbox.load_tool(auth_tokens = {" my_auth" : get_auth_token})
254
+
255
+ auth_tools = await toolbox.load_toolset(auth_tokens = {" my_auth" : get_auth_token})
235
256
```
236
257
237
258
> [ !NOTE]
238
- > Authentication tokens added via ` load_tool ` , ` load_toolset ` , or
239
- > ` add_auth_token ` apply to all subsequent tool invocations, regardless of when
240
- > the tool was loaded. This ensures a consistent authentication context.
259
+ > Adding auth tokens during loading only affect the tools loaded within
260
+ > that call.
241
261
242
262
### Complete Example
243
263
@@ -246,22 +266,81 @@ import asyncio
246
266
from toolbox_langchain_sdk import ToolboxClient
247
267
248
268
async def get_auth_token ():
249
- # Replace with your actual ID token retrieval logic.
250
- # For example, using a library like google-auth
251
- # from google.oauth2 import id_token
252
- # from google.auth.transport import requests
253
- # request = requests.Request()
254
- # id_token_string = id_token.fetch_id_token(request, "YOUR_AUDIENCE")# Replace with your audience
255
- # return id_token_string
256
- return " YOUR_ACTUAL_ID_TOKEN" # placeholder
269
+ # ... Logic to retrieve ID token (e.g., from local storage, OAuth flow)
270
+ # This example just returns a placeholder. Replace with your actual token retrieval.
271
+ return " YOUR_ID_TOKEN" # Placeholder
257
272
258
273
async def main ():
259
274
toolbox = ToolboxClient(" http://localhost:5000" )
260
- toolbox.add_auth_token(" my_auth_service" , get_auth_token)
261
- tools = await toolbox.load_toolset()
262
- result = await tools[0 ].arun({" input" : " some input" })
275
+ tool = await toolbox.load_tool(" my-tool" )
276
+
277
+ auth_tool = tool.add_auth_token(" my_auth" , get_auth_token)
278
+ result = await auth_tool.ainvoke({" input" : " some input" })
263
279
print (result)
264
280
265
281
if __name__ == " __main__" :
266
282
asyncio.run(main())
283
+ ```
284
+
285
+ ## Binding Parameter Values
286
+
287
+ Predetermine values for tool parameters using the SDK. These values won't be
288
+ modified by the LLM. This is useful for:
289
+
290
+ * ** Protecting sensitive information:** API keys, secrets, etc.
291
+ * ** Enforcing consistency:** Ensuring specific values for certain parameters.
292
+ * ** Pre-filling known data:** Providing defaults or context.
293
+
294
+ ### Binding Parameters to a Tool
295
+
296
+ ``` py
297
+ toolbox = ToolboxClient(" http://localhost:5000" )
298
+ tools = await toolbox.load_toolset()
299
+
300
+ bound_tool = tool[0 ].bind_param(" param" , " value" ) # Single param
301
+
302
+ multi_bound_tool = tools[0 ].bind_params({" param1" : " value1" , " param2" : " value2" }) # Multiple params
303
+
304
+ # OR
305
+
306
+ bound_tools = [tool.bind_param(" param" , " value" ) for tool in tools]
307
+ ```
308
+
309
+ ### Binding Parameters While Loading
310
+
311
+ ``` py
312
+ bound_tool = await toolbox.load_tool(bound_params = {" param" : " value" })
313
+
314
+ bound_tools = await toolbox.load_toolset(bound_params = {" param" : " value" })
315
+ ```
316
+
317
+ > [ !NOTE]
318
+ > Bound values during loading only affect the tools loaded in that call.
319
+
320
+ ### Binding Dynamic Values
321
+
322
+ Use a function to bind dynamic values:
323
+
324
+ ``` py
325
+ def get_dynamic_value ():
326
+ # Logic to determine the value
327
+ return " dynamic_value"
328
+
329
+ dynamic_bound_tool = tool.bind_param(" param" , get_dynamic_value)
330
+ ```
331
+
332
+ > [ !IMPORTANT]
333
+ > You don't need to modify tool configurations to bind parameter values.
334
+
335
+ ## Error Handling
336
+
337
+ When interacting with the Toolbox service or executing tools, you might
338
+ encounter errors. Handle potential exceptions gracefully:
339
+
340
+ ``` py
341
+ try :
342
+ result = await tool.ainvoke({" input" : " some input" })
343
+ except Exception as e:
344
+ print (f " An error occurred: { e} " )
345
+ # Implement error recovery logic, e.g., retrying the request or logging the error
267
346
```
0 commit comments