|
1 |
| -# Libraries |
2 |
| -| Package | Version | |
3 |
| -|-------------------|---------| |
4 |
| -| toolbox-langchain | 0.1.0 | |
| 1 | +# GenAI Toolbox LangChain SDK |
| 2 | + |
| 3 | +This SDK allows you to seamlessly integrate the functionalities of |
| 4 | +[Toolbox](https://github.com/googleapis/genai-toolbox) into your LangChain LLM |
| 5 | +applications, enabling advanced orchestration and interaction with GenAI models. |
| 6 | + |
| 7 | +<!-- TOC ignore:true --> |
| 8 | +## Table of Contents |
| 9 | +<!-- TOC --> |
| 10 | + |
| 11 | +- [Installation](#installation) |
| 12 | +- [Quickstart](#quickstart) |
| 13 | +- [Usage](#usage) |
| 14 | +- [Loading Tools](#loading-tools) |
| 15 | + - [Load a toolset](#load-a-toolset) |
| 16 | + - [Load a single tool](#load-a-single-tool) |
| 17 | +- [Use with LangChain](#use-with-langchain) |
| 18 | +- [Use with LangGraph](#use-with-langgraph) |
| 19 | + - [Represent Tools as Nodes](#represent-tools-as-nodes) |
| 20 | + - [Connect Tools with LLM](#connect-tools-with-llm) |
| 21 | +- [Manual usage](#manual-usage) |
| 22 | +- [Authenticating Tools](#authenticating-tools) |
| 23 | + - [Supported Authentication Mechanisms](#supported-authentication-mechanisms) |
| 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) |
| 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 | +- [Asynchronous Usage](#asynchronous-usage) |
| 34 | + |
| 35 | +<!-- /TOC --> |
| 36 | + |
| 37 | +## Installation |
| 38 | + |
| 39 | +```bash |
| 40 | +pip install toolbox-langchain |
| 41 | +``` |
| 42 | + |
| 43 | +## Quickstart |
| 44 | + |
| 45 | +Here's a minimal example to get you started using |
| 46 | +[LangGraph](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent): |
| 47 | + |
| 48 | +```py |
| 49 | +from toolbox_langchain import ToolboxClient |
| 50 | +from langchain_google_vertexai import ChatVertexAI |
| 51 | +from langgraph.prebuilt import create_react_agent |
| 52 | + |
| 53 | +toolbox = ToolboxClient("http://127.0.0.1:5000") |
| 54 | +tools = toolbox.load_toolset() |
| 55 | + |
| 56 | +model = ChatVertexAI(model="gemini-1.5-pro-002") |
| 57 | +agent = create_react_agent(model, tools) |
| 58 | + |
| 59 | +prompt = "How's the weather today?" |
| 60 | + |
| 61 | +for s in agent.stream({"messages": [("user", prompt)]}, stream_mode="values"): |
| 62 | + message = s["messages"][-1] |
| 63 | + if isinstance(message, tuple): |
| 64 | + print(message) |
| 65 | + else: |
| 66 | + message.pretty_print() |
| 67 | +``` |
| 68 | + |
| 69 | +## Usage |
| 70 | + |
| 71 | +Import and initialize the toolbox client. |
| 72 | + |
| 73 | +```py |
| 74 | +from toolbox_langchain import ToolboxClient |
| 75 | + |
| 76 | +# Replace with your Toolbox service's URL |
| 77 | +toolbox = ToolboxClient("http://127.0.0.1:5000") |
| 78 | +``` |
| 79 | + |
| 80 | +## Loading Tools |
| 81 | + |
| 82 | +### Load a toolset |
| 83 | + |
| 84 | +A toolset is a collection of related tools. You can load all tools in a toolset |
| 85 | +or a specific one: |
| 86 | + |
| 87 | +```py |
| 88 | +# Load all tools |
| 89 | +tools = toolbox.load_toolset() |
| 90 | + |
| 91 | +# Load a specific toolset |
| 92 | +tools = toolbox.load_toolset("my-toolset") |
| 93 | +``` |
| 94 | + |
| 95 | +### Load a single tool |
| 96 | + |
| 97 | +```py |
| 98 | +tool = toolbox.load_tool("my-tool") |
| 99 | +``` |
| 100 | + |
| 101 | +Loading individual tools gives you finer-grained control over which tools are |
| 102 | +available to your LLM agent. |
| 103 | + |
| 104 | +## Use with LangChain |
| 105 | + |
| 106 | +LangChain's agents can dynamically choose and execute tools based on the user |
| 107 | +input. Include tools loaded from the Toolbox SDK in the agent's toolkit: |
| 108 | + |
| 109 | +```py |
| 110 | +from langchain_google_vertexai import ChatVertexAI |
| 111 | + |
| 112 | +model = ChatVertexAI(model="gemini-1.5-pro-002") |
| 113 | + |
| 114 | +# Initialize agent with tools |
| 115 | +agent = model.bind_tools(tools) |
| 116 | + |
| 117 | +# Run the agent |
| 118 | +result = agent.invoke("Do something with the tools") |
| 119 | +``` |
| 120 | + |
| 121 | +## Use with LangGraph |
| 122 | + |
| 123 | +Integrate the Toolbox SDK with LangGraph to use Toolbox service tools within a |
| 124 | +graph-based workflow. Follow the [official |
| 125 | +guide](https://langchain-ai.github.io/langgraph/) with minimal changes. |
| 126 | + |
| 127 | +### Represent Tools as Nodes |
| 128 | + |
| 129 | +Represent each tool as a LangGraph node, encapsulating the tool's execution within the node's functionality: |
| 130 | + |
| 131 | +```py |
| 132 | +from toolbox_langchain import ToolboxClient |
| 133 | +from langgraph.graph import StateGraph, MessagesState |
| 134 | +from langgraph.prebuilt import ToolNode |
| 135 | + |
| 136 | +# Define the function that calls the model |
| 137 | +def call_model(state: MessagesState): |
| 138 | + messages = state['messages'] |
| 139 | + response = model.invoke(messages) |
| 140 | + return {"messages": [response]} # Return a list to add to existing messages |
| 141 | + |
| 142 | +model = ChatVertexAI(model="gemini-1.5-pro-002") |
| 143 | +builder = StateGraph(MessagesState) |
| 144 | +tool_node = ToolNode(tools) |
| 145 | + |
| 146 | +builder.add_node("agent", call_model) |
| 147 | +builder.add_node("tools", tool_node) |
| 148 | +``` |
| 149 | + |
| 150 | +### Connect Tools with LLM |
| 151 | + |
| 152 | +Connect tool nodes with LLM nodes. The LLM decides which tool to use based on |
| 153 | +input or context. Tool output can be fed back into the LLM: |
| 154 | + |
| 155 | +```py |
| 156 | +from typing import Literal |
| 157 | +from langgraph.graph import END, START |
| 158 | +from langchain_core.messages import HumanMessage |
| 159 | + |
| 160 | +# Define the function that determines whether to continue or not |
| 161 | +def should_continue(state: MessagesState) -> Literal["tools", END]: |
| 162 | + messages = state['messages'] |
| 163 | + last_message = messages[-1] |
| 164 | + if last_message.tool_calls: |
| 165 | + return "tools" # Route to "tools" node if LLM makes a tool call |
| 166 | + return END # Otherwise, stop |
| 167 | + |
| 168 | +builder.add_edge(START, "agent") |
| 169 | +builder.add_conditional_edges("agent", should_continue) |
| 170 | +builder.add_edge("tools", 'agent') |
| 171 | + |
| 172 | +graph = builder.compile() |
| 173 | + |
| 174 | +graph.invoke({"messages": [HumanMessage(content="Do something with the tools")]}) |
| 175 | +``` |
| 176 | + |
| 177 | +## Manual usage |
| 178 | + |
| 179 | +Execute a tool manually using the `invoke` method: |
| 180 | + |
| 181 | +```py |
| 182 | +result = tools[0].invoke({"name": "Alice", "age": 30}) |
| 183 | +``` |
| 184 | + |
| 185 | +This is useful for testing tools or when you need precise control over tool |
| 186 | +execution outside of an agent framework. |
| 187 | + |
| 188 | +## Authenticating Tools |
| 189 | + |
| 190 | +> [!WARNING] |
| 191 | +> Always use HTTPS to connect your application with the Toolbox service, |
| 192 | +> especially when using tools with authentication configured. Using HTTP exposes |
| 193 | +> your application to serious security risks. |
| 194 | +
|
| 195 | +Some tools require user authentication to access sensitive data. |
| 196 | + |
| 197 | +### Supported Authentication Mechanisms |
| 198 | +Toolbox currently supports authentication using the [OIDC |
| 199 | +protocol](https://openid.net/specs/openid-connect-core-1_0.html) with [ID |
| 200 | +tokens](https://openid.net/specs/openid-connect-core-1_0.html#IDToken) (not |
| 201 | +access tokens) for [Google OAuth |
| 202 | +2.0](https://cloud.google.com/apigee/docs/api-platform/security/oauth/oauth-home). |
| 203 | + |
| 204 | +### Configure Tools |
| 205 | + |
| 206 | +Refer to [these |
| 207 | +instructions](https://googleapis.github.io/genai-toolbox/resources/tools/#authenticated-parameters) on |
| 208 | +configuring tools for authenticated parameters. |
| 209 | + |
| 210 | +### Configure SDK |
| 211 | + |
| 212 | +You need a method to retrieve an ID token from your authentication service: |
| 213 | + |
| 214 | +```py |
| 215 | +async def get_auth_token(): |
| 216 | + # ... Logic to retrieve ID token (e.g., from local storage, OAuth flow) |
| 217 | + # This example just returns a placeholder. Replace with your actual token retrieval. |
| 218 | + return "YOUR_ID_TOKEN" # Placeholder |
| 219 | +``` |
| 220 | + |
| 221 | +#### Add Authentication to a Tool |
| 222 | + |
| 223 | +```py |
| 224 | +toolbox = ToolboxClient("http://127.0.0.1:5000") |
| 225 | +tools = toolbox.load_toolset() |
| 226 | + |
| 227 | +auth_tool = tools[0].add_auth_token("my_auth", get_auth_token) # Single token |
| 228 | + |
| 229 | +multi_auth_tool = tools[0].add_auth_tokens({"my_auth", get_auth_token}) # Multiple tokens |
| 230 | + |
| 231 | +# OR |
| 232 | + |
| 233 | +auth_tools = [tool.add_auth_token("my_auth", get_auth_token) for tool in tools] |
| 234 | +``` |
| 235 | + |
| 236 | +#### Add Authentication While Loading |
| 237 | + |
| 238 | +```py |
| 239 | +auth_tool = toolbox.load_tool(auth_tokens={"my_auth": get_auth_token}) |
| 240 | + |
| 241 | +auth_tools = toolbox.load_toolset(auth_tokens={"my_auth": get_auth_token}) |
| 242 | +``` |
| 243 | + |
| 244 | +> [!NOTE] |
| 245 | +> Adding auth tokens during loading only affect the tools loaded within |
| 246 | +> that call. |
| 247 | +
|
| 248 | +### Complete Example |
| 249 | + |
| 250 | +```py |
| 251 | +import asyncio |
| 252 | +from toolbox_langchain import ToolboxClient |
| 253 | + |
| 254 | +async def get_auth_token(): |
| 255 | + # ... Logic to retrieve ID token (e.g., from local storage, OAuth flow) |
| 256 | + # This example just returns a placeholder. Replace with your actual token retrieval. |
| 257 | + return "YOUR_ID_TOKEN" # Placeholder |
| 258 | + |
| 259 | +toolbox = ToolboxClient("http://127.0.0.1:5000") |
| 260 | +tool = toolbox.load_tool("my-tool") |
| 261 | + |
| 262 | +auth_tool = tool.add_auth_token("my_auth", get_auth_token) |
| 263 | +result = auth_tool.invoke({"input": "some input"}) |
| 264 | +print(result) |
| 265 | +``` |
| 266 | + |
| 267 | +## Binding Parameter Values |
| 268 | + |
| 269 | +Predetermine values for tool parameters using the SDK. These values won't be |
| 270 | +modified by the LLM. This is useful for: |
| 271 | + |
| 272 | +* **Protecting sensitive information:** API keys, secrets, etc. |
| 273 | +* **Enforcing consistency:** Ensuring specific values for certain parameters. |
| 274 | +* **Pre-filling known data:** Providing defaults or context. |
| 275 | + |
| 276 | +### Binding Parameters to a Tool |
| 277 | + |
| 278 | +```py |
| 279 | +toolbox = ToolboxClient("http://127.0.0.1:5000") |
| 280 | +tools = toolbox.load_toolset() |
| 281 | + |
| 282 | +bound_tool = tool[0].bind_param("param", "value") # Single param |
| 283 | + |
| 284 | +multi_bound_tool = tools[0].bind_params({"param1": "value1", "param2": "value2"}) # Multiple params |
| 285 | + |
| 286 | +# OR |
| 287 | + |
| 288 | +bound_tools = [tool.bind_param("param", "value") for tool in tools] |
| 289 | +``` |
| 290 | + |
| 291 | +### Binding Parameters While Loading |
| 292 | + |
| 293 | +```py |
| 294 | +bound_tool = toolbox.load_tool(bound_params={"param": "value"}) |
| 295 | + |
| 296 | +bound_tools = toolbox.load_toolset(bound_params={"param": "value"}) |
| 297 | +``` |
| 298 | + |
| 299 | +> [!NOTE] |
| 300 | +> Bound values during loading only affect the tools loaded in that call. |
| 301 | +
|
| 302 | +### Binding Dynamic Values |
| 303 | + |
| 304 | +Use a function to bind dynamic values: |
| 305 | + |
| 306 | +```py |
| 307 | +def get_dynamic_value(): |
| 308 | + # Logic to determine the value |
| 309 | + return "dynamic_value" |
| 310 | + |
| 311 | +dynamic_bound_tool = tool.bind_param("param", get_dynamic_value) |
| 312 | +``` |
| 313 | + |
| 314 | +> [!IMPORTANT] |
| 315 | +> You don't need to modify tool configurations to bind parameter values. |
| 316 | +
|
| 317 | +## Asynchronous Usage |
| 318 | + |
| 319 | +For better performance through [cooperative |
| 320 | +multitasking](https://en.wikipedia.org/wiki/Cooperative_multitasking), you can |
| 321 | +use the asynchronous interfaces of the `ToolboxClient`. |
| 322 | + |
| 323 | +> [!Note] |
| 324 | +> Asynchronous interfaces like `aload_tool` and `aload_toolset` require an |
| 325 | +> asynchronous environment. For guidance on running asynchronous Python |
| 326 | +> programs, see [asyncio |
| 327 | +> documentation](https://docs.python.org/3/library/asyncio-runner.html#running-an-asyncio-program). |
| 328 | +
|
| 329 | +```py |
| 330 | +import asyncio |
| 331 | +from toolbox_langchain import ToolboxClient |
| 332 | + |
| 333 | +async def main(): |
| 334 | + toolbox = ToolboxClient("http://127.0.0.1:5000") |
| 335 | + tool = await client.aload_tool("my-tool") |
| 336 | + tools = await client.aload_toolset() |
| 337 | + response = await tool.ainvoke() |
| 338 | + |
| 339 | +if __name__ == "__main__": |
| 340 | + asyncio.run(main()) |
| 341 | +``` |
0 commit comments