Skip to content

Commit e1b6420

Browse files
authored
doc(langchain-sdk): Update LangChain SDK README according to the updated APIs for ToolboxTool. (#193)
1 parent 4fcfc35 commit e1b6420

File tree

1 file changed

+159
-80
lines changed

1 file changed

+159
-80
lines changed

README.md

Lines changed: 159 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,54 @@ applications, enabling advanced orchestration and interaction with GenAI models.
88
## Table of Contents
99
<!-- TOC -->
1010

11+
- [Quickstart](#quickstart)
1112
- [Installation](#installation)
1213
- [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)
1517
- [Use with LangChain](#use-with-langchain)
1618
- [Use with LangGraph](#use-with-langgraph)
1719
- [Represent Tools as Nodes](#represent-tools-as-nodes)
1820
- [Connect Tools with LLM](#connect-tools-with-llm)
1921
- [Manual usage](#manual-usage)
2022
- [Authenticating Tools](#authenticating-tools)
2123
- [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)
2428
- [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)
2534

2635
<!-- /TOC -->
2736

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+
2859
## Installation
2960

3061
> [!IMPORTANT]
@@ -51,19 +82,21 @@ toolbox = ToolboxClient("http://127.0.0.1:5000")
5182
> [!IMPORTANT]
5283
> The toolbox client requires an asynchronous environment.
5384
> 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).
5586
5687
> [!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:
5989
> ```py
6090
> async with ClientSession() as session:
6191
> toolbox = ToolboxClient("http://localhost:5000", session)
6292
> ```
6393
64-
## Load a toolset
94+
## Loading Tools
6595
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:
67100
68101
```py
69102
# Load all tools
@@ -73,19 +106,19 @@ tools = await toolbox.load_toolset()
73106
tools = await toolbox.load_toolset("my-toolset")
74107
```
75108
76-
## Load a single tool
77-
78-
You can also load a single tool.
109+
### Load a single tool
79110

80111
```py
81112
tool = await toolbox.load_tool("my-tool")
82113
```
83114

115+
Loading individual tools gives you finer-grained control over which tools are
116+
available to your LLM agent.
117+
84118
## Use with LangChain
85119

86120
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:
89122

90123
```py
91124
from langchain_google_vertexai import ChatVertexAI
@@ -101,15 +134,13 @@ result = agent.invoke("Do something with the tools")
101134

102135
## Use with LangGraph
103136

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.
108140

109141
### Represent Tools as Nodes
110142

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:
113144

114145
```py
115146
from toolbox_langchain_sdk import ToolboxClient
@@ -120,8 +151,7 @@ from langgraph.prebuilt import ToolNode
120151
def call_model(state: MessagesState):
121152
messages = state['messages']
122153
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
125155

126156
model = ChatVertexAI(model="gemini-1.5-pro-002")
127157
builder = StateGraph(MessagesState)
@@ -133,10 +163,8 @@ builder.add_node("tools", tool_node)
133163

134164
### Connect Tools with LLM
135165

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:
140168

141169
```py
142170
from typing import Literal
@@ -147,97 +175,89 @@ from langchain_core.messages import HumanMessage
147175
def should_continue(state: MessagesState) -> Literal["tools", END]:
148176
messages = state['messages']
149177
last_message = messages[-1]
150-
# If the LLM makes a tool call, then we route to the "tools" node
151178
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
155181

156182
builder.add_edge(START, "agent")
157-
builder.add_conditional_edges(
158-
"agent",
159-
should_continue,
160-
)
183+
builder.add_conditional_edges("agent", should_continue)
161184
builder.add_edge("tools", 'agent')
162185

163186
graph = builder.compile()
164187

165-
graph.invoke(
166-
{"messages": [HumanMessage(content="Do something with the tools")]},
167-
)
188+
graph.invoke({"messages": [HumanMessage(content="Do something with the tools")]})
168189
```
169190

170191
## Manual usage
171192

172-
You can also execute a tool manually using the `arun` method.
193+
Execute a tool manually using the `ainvoke` method:
173194

174195
```py
175-
result = await tools[0].arun({ "name": "Alice", "age": 30 })
196+
result = await tools[0].ainvoke({"name": "Alice", "age": 30})
176197
```
177198

199+
This is useful for testing tools or when you need precise control over tool
200+
execution outside of an agent framework.
201+
178202
## Authenticating Tools
179203

180204
> [!WARNING]
181205
> Always use HTTPS to connect your application with the Toolbox service,
182206
> 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.
186208
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.
190210

191211
### 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
197216
2.0](https://cloud.google.com/apigee/docs/api-platform/security/oauth/oauth-home).
198217

199-
### Configuring Tools for Authentication
218+
### Configure Tools
200219

201220
Refer to [these
202221
instructions](../../docs/tools/README.md#authenticated-parameters) on
203222
configuring tools for authenticated parameters.
204223

205-
### Configure SDK for Authentication
224+
### Configure SDK
206225

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:
214227

215228
```py
216-
def get_auth_token():
229+
async def get_auth_token():
217230
# ... Logic to retrieve ID token (e.g., from local storage, OAuth flow)
218231
# 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
220236

237+
```py
221238
toolbox = ToolboxClient("http://localhost:5000")
239+
tools = await toolbox.load_toolset()
222240

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
224244

225245
# OR
226246

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]
228248
```
229249

230-
Alternatively, you can call the `add_auth_token` method to configure
231-
authentication separately.
250+
#### Add Authentication While Loading
232251

233252
```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})
235256
```
236257

237258
> [!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.
241261
242262
### Complete Example
243263

@@ -246,22 +266,81 @@ import asyncio
246266
from toolbox_langchain_sdk import ToolboxClient
247267

248268
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
257272

258273
async def main():
259274
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"})
263279
print(result)
264280

265281
if __name__ == "__main__":
266282
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
267346
```

0 commit comments

Comments
 (0)