toolbox-langchain 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,471 @@
1
+ Metadata-Version: 2.4
2
+ Name: toolbox-langchain
3
+ Version: 0.2.1
4
+ Summary: Python SDK for interacting with the Toolbox service with LangChain
5
+ Author-email: Google LLC <googleapis-packages@google.com>
6
+ Project-URL: Homepage, https://github.com/googleapis/mcp-toolbox-sdk-python/blob/main/packages/toolbox-langchain
7
+ Project-URL: Repository, https://github.com/googleapis/mcp-toolbox-sdk-python.git
8
+ Project-URL: Bug Tracker, https://github.com/googleapis/mcp-toolbox-sdk-python/issues
9
+ Project-URL: Changelog, https://github.com/googleapis/mcp-toolbox-sdk-python/blob/main/packages/toolbox-langchain/CHANGELOG.md
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: Apache Software License
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: toolbox-core==0.2.1
22
+ Requires-Dist: langchain-core<1.0.0,>=0.2.23
23
+ Requires-Dist: PyYAML<7.0.0,>=6.0.1
24
+ Requires-Dist: pydantic<3.0.0,>=2.7.0
25
+ Requires-Dist: aiohttp<4.0.0,>=3.8.6
26
+ Requires-Dist: deprecated<2.0.0,>=1.1.0
27
+ Provides-Extra: test
28
+ Requires-Dist: black[jupyter]==25.1.0; extra == "test"
29
+ Requires-Dist: isort==6.0.1; extra == "test"
30
+ Requires-Dist: mypy==1.16.0; extra == "test"
31
+ Requires-Dist: pytest-asyncio==1.0.0; extra == "test"
32
+ Requires-Dist: pytest==8.4.0; extra == "test"
33
+ Requires-Dist: pytest-cov==6.2.1; extra == "test"
34
+ Requires-Dist: Pillow==11.2.1; extra == "test"
35
+ Requires-Dist: google-cloud-secret-manager==2.24.0; extra == "test"
36
+ Requires-Dist: google-cloud-storage==3.1.0; extra == "test"
37
+
38
+ ![MCP Toolbox Logo](https://raw.githubusercontent.com/googleapis/genai-toolbox/main/logo.png)
39
+ # MCP Toolbox LangChain SDK
40
+
41
+ This SDK allows you to seamlessly integrate the functionalities of
42
+ [Toolbox](https://github.com/googleapis/genai-toolbox) into your LangChain LLM
43
+ applications, enabling advanced orchestration and interaction with GenAI models.
44
+
45
+ <!-- TOC ignore:true -->
46
+ ## Table of Contents
47
+ <!-- TOC -->
48
+
49
+ - [Installation](#installation)
50
+ - [Quickstart](#quickstart)
51
+ - [Usage](#usage)
52
+ - [Loading Tools](#loading-tools)
53
+ - [Load a toolset](#load-a-toolset)
54
+ - [Load a single tool](#load-a-single-tool)
55
+ - [Use with LangChain](#use-with-langchain)
56
+ - [Use with LangGraph](#use-with-langgraph)
57
+ - [Represent Tools as Nodes](#represent-tools-as-nodes)
58
+ - [Connect Tools with LLM](#connect-tools-with-llm)
59
+ - [Manual usage](#manual-usage)
60
+ - [Client to Server Authentication](#client-to-server-authentication)
61
+ - [When is Client-to-Server Authentication Needed?](#when-is-client-to-server-authentication-needed)
62
+ - [How it works](#how-it-works)
63
+ - [Configuration](#configuration)
64
+ - [Authenticating with Google Cloud Servers](#authenticating-with-google-cloud-servers)
65
+ - [Step by Step Guide for Cloud Run](#step-by-step-guide-for-cloud-run)
66
+ - [Authenticating Tools](#authenticating-tools)
67
+ - [Supported Authentication Mechanisms](#supported-authentication-mechanisms)
68
+ - [Configure Tools](#configure-tools)
69
+ - [Configure SDK](#configure-sdk)
70
+ - [Add Authentication to a Tool](#add-authentication-to-a-tool)
71
+ - [Add Authentication While Loading](#add-authentication-while-loading)
72
+ - [Complete Example](#complete-example)
73
+ - [Binding Parameter Values](#binding-parameter-values)
74
+ - [Binding Parameters to a Tool](#binding-parameters-to-a-tool)
75
+ - [Binding Parameters While Loading](#binding-parameters-while-loading)
76
+ - [Binding Dynamic Values](#binding-dynamic-values)
77
+ - [Asynchronous Usage](#asynchronous-usage)
78
+
79
+ <!-- /TOC -->
80
+
81
+ ## Installation
82
+
83
+ ```bash
84
+ pip install toolbox-langchain
85
+ ```
86
+
87
+ ## Quickstart
88
+
89
+ Here's a minimal example to get you started using
90
+ [LangGraph](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent):
91
+
92
+ ```py
93
+ from toolbox_langchain import ToolboxClient
94
+ from langchain_google_vertexai import ChatVertexAI
95
+ from langgraph.prebuilt import create_react_agent
96
+
97
+ toolbox = ToolboxClient("http://127.0.0.1:5000")
98
+ tools = toolbox.load_toolset()
99
+
100
+ model = ChatVertexAI(model="gemini-2.0-flash-001")
101
+ agent = create_react_agent(model, tools)
102
+
103
+ prompt = "How's the weather today?"
104
+
105
+ for s in agent.stream({"messages": [("user", prompt)]}, stream_mode="values"):
106
+ message = s["messages"][-1]
107
+ if isinstance(message, tuple):
108
+ print(message)
109
+ else:
110
+ message.pretty_print()
111
+ ```
112
+
113
+ ## Usage
114
+
115
+ Import and initialize the toolbox client.
116
+
117
+ ```py
118
+ from toolbox_langchain import ToolboxClient
119
+
120
+ # Replace with your Toolbox service's URL
121
+ toolbox = ToolboxClient("http://127.0.0.1:5000")
122
+ ```
123
+
124
+ ## Loading Tools
125
+
126
+ ### Load a toolset
127
+
128
+ A toolset is a collection of related tools. You can load all tools in a toolset
129
+ or a specific one:
130
+
131
+ ```py
132
+ # Load all tools
133
+ tools = toolbox.load_toolset()
134
+
135
+ # Load a specific toolset
136
+ tools = toolbox.load_toolset("my-toolset")
137
+ ```
138
+
139
+ ### Load a single tool
140
+
141
+ ```py
142
+ tool = toolbox.load_tool("my-tool")
143
+ ```
144
+
145
+ Loading individual tools gives you finer-grained control over which tools are
146
+ available to your LLM agent.
147
+
148
+ ## Use with LangChain
149
+
150
+ LangChain's agents can dynamically choose and execute tools based on the user
151
+ input. Include tools loaded from the Toolbox SDK in the agent's toolkit:
152
+
153
+ ```py
154
+ from langchain_google_vertexai import ChatVertexAI
155
+
156
+ model = ChatVertexAI(model="gemini-2.0-flash-001")
157
+
158
+ # Initialize agent with tools
159
+ agent = model.bind_tools(tools)
160
+
161
+ # Run the agent
162
+ result = agent.invoke("Do something with the tools")
163
+ ```
164
+
165
+ ## Use with LangGraph
166
+
167
+ Integrate the Toolbox SDK with LangGraph to use Toolbox service tools within a
168
+ graph-based workflow. Follow the [official
169
+ guide](https://langchain-ai.github.io/langgraph/) with minimal changes.
170
+
171
+ ### Represent Tools as Nodes
172
+
173
+ Represent each tool as a LangGraph node, encapsulating the tool's execution within the node's functionality:
174
+
175
+ ```py
176
+ from toolbox_langchain import ToolboxClient
177
+ from langgraph.graph import StateGraph, MessagesState
178
+ from langgraph.prebuilt import ToolNode
179
+
180
+ # Define the function that calls the model
181
+ def call_model(state: MessagesState):
182
+ messages = state['messages']
183
+ response = model.invoke(messages)
184
+ return {"messages": [response]} # Return a list to add to existing messages
185
+
186
+ model = ChatVertexAI(model="gemini-2.0-flash-001")
187
+ builder = StateGraph(MessagesState)
188
+ tool_node = ToolNode(tools)
189
+
190
+ builder.add_node("agent", call_model)
191
+ builder.add_node("tools", tool_node)
192
+ ```
193
+
194
+ ### Connect Tools with LLM
195
+
196
+ Connect tool nodes with LLM nodes. The LLM decides which tool to use based on
197
+ input or context. Tool output can be fed back into the LLM:
198
+
199
+ ```py
200
+ from typing import Literal
201
+ from langgraph.graph import END, START
202
+ from langchain_core.messages import HumanMessage
203
+
204
+ # Define the function that determines whether to continue or not
205
+ def should_continue(state: MessagesState) -> Literal["tools", END]:
206
+ messages = state['messages']
207
+ last_message = messages[-1]
208
+ if last_message.tool_calls:
209
+ return "tools" # Route to "tools" node if LLM makes a tool call
210
+ return END # Otherwise, stop
211
+
212
+ builder.add_edge(START, "agent")
213
+ builder.add_conditional_edges("agent", should_continue)
214
+ builder.add_edge("tools", 'agent')
215
+
216
+ graph = builder.compile()
217
+
218
+ graph.invoke({"messages": [HumanMessage(content="Do something with the tools")]})
219
+ ```
220
+
221
+ ## Manual usage
222
+
223
+ Execute a tool manually using the `invoke` method:
224
+
225
+ ```py
226
+ result = tools[0].invoke({"name": "Alice", "age": 30})
227
+ ```
228
+
229
+ This is useful for testing tools or when you need precise control over tool
230
+ execution outside of an agent framework.
231
+
232
+ ## Client to Server Authentication
233
+
234
+ This section describes how to authenticate the ToolboxClient itself when
235
+ connecting to a Toolbox server instance that requires authentication. This is
236
+ crucial for securing your Toolbox server endpoint, especially when deployed on
237
+ platforms like Cloud Run, GKE, or any environment where unauthenticated access
238
+ is restricted.
239
+
240
+ This client-to-server authentication ensures that the Toolbox server can verify
241
+ the identity of the client making the request before any tool is loaded or
242
+ called. It is different from [Authenticating Tools](#authenticating-tools),
243
+ which deals with providing credentials for specific tools within an already
244
+ connected Toolbox session.
245
+
246
+ ### When is Client-to-Server Authentication Needed?
247
+
248
+ You'll need this type of authentication if your Toolbox server is configured to
249
+ deny unauthenticated requests. For example:
250
+
251
+ - Your Toolbox server is deployed on Cloud Run and configured to "Require authentication."
252
+ - Your server is behind an Identity-Aware Proxy (IAP) or a similar
253
+ authentication layer.
254
+ - You have custom authentication middleware on your self-hosted Toolbox server.
255
+
256
+ Without proper client authentication in these scenarios, attempts to connect or
257
+ make calls (like `load_tool`) will likely fail with `Unauthorized` errors.
258
+
259
+ ### How it works
260
+
261
+ The `ToolboxClient` allows you to specify functions (or coroutines for the async
262
+ client) that dynamically generate HTTP headers for every request sent to the
263
+ Toolbox server. The most common use case is to add an Authorization header with
264
+ a bearer token (e.g., a Google ID token).
265
+
266
+ These header-generating functions are called just before each request, ensuring
267
+ that fresh credentials or header values can be used.
268
+
269
+ ### Configuration
270
+
271
+ You can configure these dynamic headers as follows:
272
+
273
+ ```python
274
+ from toolbox_langchain import ToolboxClient
275
+
276
+ client = ToolboxClient(
277
+ "toolbox-url",
278
+ client_headers={"header1": header1_getter, "header2": header2_getter, ...}
279
+ )
280
+ ```
281
+
282
+ ### Authenticating with Google Cloud Servers
283
+
284
+ For Toolbox servers hosted on Google Cloud (e.g., Cloud Run) and requiring
285
+ `Google ID token` authentication, the helper module
286
+ [auth_methods](src/toolbox_core/auth_methods.py) provides utility functions.
287
+
288
+ ### Step by Step Guide for Cloud Run
289
+
290
+ 1. **Configure Permissions**:
291
+ [Grant](https://cloud.google.com/run/docs/securing/managing-access#service-add-principals)
292
+ the `roles/run.invoker` IAM role on the Cloud
293
+ Run service to the principal. This could be your `user account email` or a
294
+ `service account`.
295
+ 2. **Configure Credentials**
296
+ - Local Development: Set up
297
+ [ADC](https://cloud.google.com/docs/authentication/set-up-adc-local-dev-environment).
298
+ - Google Cloud Environments: When running within Google Cloud (e.g., Compute
299
+ Engine, GKE, another Cloud Run service, Cloud Functions), ADC is typically
300
+ configured automatically, using the environment's default service account.
301
+ 3. **Connect to the Toolbox Server**
302
+
303
+ ```python
304
+ from toolbox_langchain import ToolboxClient
305
+ from toolbox_core import auth_methods
306
+
307
+ auth_token_provider = auth_methods.aget_google_id_token # can also use sync method
308
+ client = ToolboxClient(
309
+ URL,
310
+ client_headers={"Authorization": auth_token_provider},
311
+ )
312
+ tools = client.load_toolset()
313
+
314
+ # Now, you can use the client as usual.
315
+ ```
316
+
317
+
318
+ ## Authenticating Tools
319
+
320
+ > [!WARNING]
321
+ > Always use HTTPS to connect your application with the Toolbox service,
322
+ > especially when using tools with authentication configured. Using HTTP exposes
323
+ > your application to serious security risks.
324
+
325
+ Some tools require user authentication to access sensitive data.
326
+
327
+ ### Supported Authentication Mechanisms
328
+ Toolbox currently supports authentication using the [OIDC
329
+ protocol](https://openid.net/specs/openid-connect-core-1_0.html) with [ID
330
+ tokens](https://openid.net/specs/openid-connect-core-1_0.html#IDToken) (not
331
+ access tokens) for [Google OAuth
332
+ 2.0](https://cloud.google.com/apigee/docs/api-platform/security/oauth/oauth-home).
333
+
334
+ ### Configure Tools
335
+
336
+ Refer to [these
337
+ instructions](https://googleapis.github.io/genai-toolbox/resources/tools/#authenticated-parameters) on
338
+ configuring tools for authenticated parameters.
339
+
340
+ ### Configure SDK
341
+
342
+ You need a method to retrieve an ID token from your authentication service:
343
+
344
+ ```py
345
+ async def get_auth_token():
346
+ # ... Logic to retrieve ID token (e.g., from local storage, OAuth flow)
347
+ # This example just returns a placeholder. Replace with your actual token retrieval.
348
+ return "YOUR_ID_TOKEN" # Placeholder
349
+ ```
350
+
351
+ #### Add Authentication to a Tool
352
+
353
+ ```py
354
+ toolbox = ToolboxClient("http://127.0.0.1:5000")
355
+ tools = toolbox.load_toolset()
356
+
357
+ auth_tool = tools[0].add_auth_token_getter("my_auth", get_auth_token) # Single token
358
+
359
+ multi_auth_tool = tools[0].add_auth_token_getters({"auth_1": get_auth_1}, {"auth_2": get_auth_2}) # Multiple tokens
360
+
361
+ # OR
362
+
363
+ auth_tools = [tool.add_auth_token_getter("my_auth", get_auth_token) for tool in tools]
364
+ ```
365
+
366
+ #### Add Authentication While Loading
367
+
368
+ ```py
369
+ auth_tool = toolbox.load_tool(auth_token_getters={"my_auth": get_auth_token})
370
+
371
+ auth_tools = toolbox.load_toolset(auth_token_getters={"my_auth": get_auth_token})
372
+ ```
373
+
374
+ > [!NOTE]
375
+ > Adding auth tokens during loading only affect the tools loaded within
376
+ > that call.
377
+
378
+ ### Complete Example
379
+
380
+ ```py
381
+ import asyncio
382
+ from toolbox_langchain import ToolboxClient
383
+
384
+ async def get_auth_token():
385
+ # ... Logic to retrieve ID token (e.g., from local storage, OAuth flow)
386
+ # This example just returns a placeholder. Replace with your actual token retrieval.
387
+ return "YOUR_ID_TOKEN" # Placeholder
388
+
389
+ toolbox = ToolboxClient("http://127.0.0.1:5000")
390
+ tool = toolbox.load_tool("my-tool")
391
+
392
+ auth_tool = tool.add_auth_token_getter("my_auth", get_auth_token)
393
+ result = auth_tool.invoke({"input": "some input"})
394
+ print(result)
395
+ ```
396
+
397
+ ## Binding Parameter Values
398
+
399
+ Predetermine values for tool parameters using the SDK. These values won't be
400
+ modified by the LLM. This is useful for:
401
+
402
+ * **Protecting sensitive information:** API keys, secrets, etc.
403
+ * **Enforcing consistency:** Ensuring specific values for certain parameters.
404
+ * **Pre-filling known data:** Providing defaults or context.
405
+
406
+ ### Binding Parameters to a Tool
407
+
408
+ ```py
409
+ toolbox = ToolboxClient("http://127.0.0.1:5000")
410
+ tools = toolbox.load_toolset()
411
+
412
+ bound_tool = tool[0].bind_param("param", "value") # Single param
413
+
414
+ multi_bound_tool = tools[0].bind_params({"param1": "value1", "param2": "value2"}) # Multiple params
415
+
416
+ # OR
417
+
418
+ bound_tools = [tool.bind_param("param", "value") for tool in tools]
419
+ ```
420
+
421
+ ### Binding Parameters While Loading
422
+
423
+ ```py
424
+ bound_tool = toolbox.load_tool("my-tool", bound_params={"param": "value"})
425
+
426
+ bound_tools = toolbox.load_toolset(bound_params={"param": "value"})
427
+ ```
428
+
429
+ > [!NOTE]
430
+ > Bound values during loading only affect the tools loaded in that call.
431
+
432
+ ### Binding Dynamic Values
433
+
434
+ Use a function to bind dynamic values:
435
+
436
+ ```py
437
+ def get_dynamic_value():
438
+ # Logic to determine the value
439
+ return "dynamic_value"
440
+
441
+ dynamic_bound_tool = tool.bind_param("param", get_dynamic_value)
442
+ ```
443
+
444
+ > [!IMPORTANT]
445
+ > You don't need to modify tool configurations to bind parameter values.
446
+
447
+ ## Asynchronous Usage
448
+
449
+ For better performance through [cooperative
450
+ multitasking](https://en.wikipedia.org/wiki/Cooperative_multitasking), you can
451
+ use the asynchronous interfaces of the `ToolboxClient`.
452
+
453
+ > [!Note]
454
+ > Asynchronous interfaces like `aload_tool` and `aload_toolset` require an
455
+ > asynchronous environment. For guidance on running asynchronous Python
456
+ > programs, see [asyncio
457
+ > documentation](https://docs.python.org/3/library/asyncio-runner.html#running-an-asyncio-program).
458
+
459
+ ```py
460
+ import asyncio
461
+ from toolbox_langchain import ToolboxClient
462
+
463
+ async def main():
464
+ toolbox = ToolboxClient("http://127.0.0.1:5000")
465
+ tool = await client.aload_tool("my-tool")
466
+ tools = await client.aload_toolset()
467
+ response = await tool.ainvoke()
468
+
469
+ if __name__ == "__main__":
470
+ asyncio.run(main())
471
+ ```