Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs[patch]: Update tool runtime docs #6097

Merged
merged 3 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/core_docs/docs/how_to/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ LangChain [Tools](/docs/concepts/#tools) contain a description of the tool (to p
- [How to: use a chat model to call tools](/docs/how_to/tool_calling/)
- [How to: pass tool results back to model](/docs/how_to/tool_results_pass_to_model/)
- [How to: add ad-hoc tool calling capability to LLMs and Chat Models](/docs/how_to/tools_prompting)
- [How to: pass run time values to tools](/docs/how_to/tool_runtime)
- [How to: access the `RunnableConfig` object within a custom tool](/docs/how_to/tool_configure)
- [How to: return extra artifacts from a custom tool](/docs/how_to/tool_artifacts)

### Agents
Expand Down
114 changes: 114 additions & 0 deletions docs/core_docs/docs/how_to/tool_configure.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to access the RunnableConfig object within a custom tool\n",
"\n",
"```{=mdx}\n",
":::info Prerequisites\n",
"\n",
"This guide assumes familiarity with the following concepts:\n",
"\n",
"- [LangChain Tools](/docs/concepts/#tools)\n",
"- [Custom tools](/docs/how_to/custom_tools)\n",
"- [LangChain Expression Language (LCEL)](/docs/concepts/#langchain-expression-language)\n",
"\n",
":::\n",
"```\n",
"\n",
"Tools are runnables, and you can treat them the same way as any other runnable at the interface level - you can call `invoke()`, `batch()`, and `stream()` on them as normal. However, when writing custom tools, you may want to invoke other runnables like chat models or retrievers. In order to properly trace and configure those sub-invocations, you'll need to manually access and pass in the tool's current [`RunnableConfig`](https://api.js.langchain.com/interfaces/langchain_core_runnables.RunnableConfig.html) object.\n",
"\n",
"This guide covers how to do this for custom tools created in different ways.\n",
"\n",
"## From the `tool` method\n",
"\n",
"Accessing the `RunnableConfig` object for a custom tool created with the [`tool`](https://api.js.langchain.com/functions/langchain_core_tools.tool-1.html) helper method is simple - it's always the second parameter passed into your custom function. Here's an example:"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import { z } from \"zod\";\n",
"import { tool } from \"@langchain/core/tools\";\n",
"import type { RunnableConfig } from \"@langchain/core/runnables\";\n",
"\n",
"const reverseTool = tool(\n",
" async (input: { text: string }, config?: RunnableConfig) => {\n",
" const originalString = input.text + (config?.configurable?.additional_field ?? \"\");\n",
" return originalString.split(\"\").reverse().join(\"\");\n",
" }, {\n",
" name: \"reverse\",\n",
" description: \"A test tool that combines input text with a configurable parameter.\",\n",
" schema: z.object({\n",
" text: z.string()\n",
" }),\n",
" }\n",
");"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then, if we invoke the tool with a `config` containing a `configurable` field, we can see that `additional_field` is passed through correctly:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"321cba\n"
]
}
],
"source": [
"await reverseTool.invoke(\n",
" {text: \"abc\"}, {configurable: {additional_field: \"123\"}}\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Next steps\n",
"\n",
"You've now seen how to configure and stream events from within a tool. Next, check out the following guides for more on using tools:\n",
"\n",
"- Pass [tool results back to a model](/docs/how_to/tool_results_pass_to_model)\n",
"- Building [tool-using chains and agents](/docs/how_to#tools)\n",
"- Getting [structured outputs](/docs/how_to/structured_output/) from models"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "TypeScript",
"language": "typescript",
"name": "tslab"
},
"language_info": {
"codemirror_mode": {
"mode": "typescript",
"name": "javascript",
"typescript": true
},
"file_extension": ".ts",
"mimetype": "text/typescript",
"name": "typescript",
"version": "3.7.2"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
128 changes: 62 additions & 66 deletions docs/core_docs/docs/how_to/tool_runtime.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"source": [
"# How to pass run time values to a tool\n",
"\n",
"```{=mdx}\n",
":::info Prerequisites\n",
"\n",
"This guide assumes familiarity with the following concepts:\n",
Expand All @@ -21,14 +22,15 @@
"You can find a [list of all models that support tool calling](/docs/integrations/chat/).\n",
"\n",
":::\n",
"```\n",
"\n",
"You may need to bind values to a tool that are only known at runtime. For example, the tool logic may require using the ID of the user who made the request.\n",
"\n",
"Most of the time, such values should not be controlled by the LLM. In fact, allowing the LLM to control the user ID may lead to a security risk.\n",
"\n",
"Instead, the LLM should only control the parameters of the tool that are meant to be controlled by the LLM, while other parameters (such as user ID) should be fixed by the application logic.\n",
"\n",
"This how-to guide shows a simple design pattern that creates the tool dynamically at run time and binds to them appropriate values."
"This how-to guide shows a design pattern that creates the tool dynamically at run time and binds to them appropriate values."
]
},
{
Expand Down Expand Up @@ -58,58 +60,47 @@
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import { z } from \"zod\";\n",
"import { StructuredTool } from \"@langchain/core/tools\";\n",
"import { tool } from \"@langchain/core/tools\";\n",
"\n",
"const userToPets: Record<string, string[]> = {};\n",
"\n",
"function generateToolsForUser(userId: string): StructuredTool[] {\n",
" class UpdateFavoritePets extends StructuredTool {\n",
" name = \"update_favorite_pets\";\n",
" description = \"Add the list of favorite pets.\";\n",
" schema = z.object({\n",
" pets: z.array(z.string())\n",
" })\n",
"\n",
" async _call(input: { pets: string[] }): Promise<string> {\n",
" userToPets[userId] = input.pets;\n",
" return \"update_favorite_pets called.\"\n",
" }\n",
" }\n",
"\n",
" class DeleteFavoritePets extends StructuredTool {\n",
" name = \"delete_favorite_pets\";\n",
" description = \"Delete the list of favorite pets.\";\n",
"\n",
" schema = z.object({\n",
" no_op: z.boolean().optional().describe(\"No operation.\")\n",
" })\n",
"\n",
" async _call(input: never): Promise<string> {\n",
" if (userId in userToPets) {\n",
" delete userToPets[userId];\n",
" }\n",
" return \"delete_favorite_pets called.\"\n",
" }\n",
" }\n",
"\n",
" class ListFavoritePets extends StructuredTool {\n",
" name = \"list_favorite_pets\";\n",
" description = \"List favorite pets if any.\";\n",
" schema = z.object({\n",
" no_op: z.boolean().optional().describe(\"No operation.\")\n",
" })\n",
"\n",
" async _call(input: never): Promise<string> {\n",
" return JSON.stringify(userToPets[userId]) || JSON.stringify([]);\n",
"function generateToolsForUser(userId: string) {\n",
" const updateFavoritePets = tool(async (input) => {\n",
" userToPets[userId] = input.pets;\n",
" return \"update_favorite_pets called.\"\n",
" }, {\n",
" name: \"update_favorite_pets\",\n",
" description: \"add to the list of favorite pets.\",\n",
" schema: z.object({\n",
" pets: z.array(z.string())\n",
" }),\n",
" });\n",
"\n",
" const deleteFavoritePets = tool(async () => {\n",
" if (userId in userToPets) {\n",
" delete userToPets[userId];\n",
" }\n",
" }\n",
"\n",
" return [new UpdateFavoritePets(), new DeleteFavoritePets(), new ListFavoritePets()];\n",
" return \"delete_favorite_pets called.\";\n",
" }, {\n",
" name: \"delete_favorite_pets\",\n",
" description: \"Delete the list of favorite pets.\",\n",
" schema: z.object({}),\n",
" });\n",
"\n",
" const listFavoritePets = tool(async () => {\n",
" return JSON.stringify(userToPets[userId] ?? []);\n",
" }, {\n",
" name: \"list_favorite_pets\",\n",
" description: \"List favorite pets if any.\",\n",
" schema: z.object({}),\n",
" });\n",
"\n",
" return [updateFavoritePets, deleteFavoritePets, listFavoritePets];\n",
"}"
]
},
Expand All @@ -122,7 +113,7 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": 2,
"metadata": {},
"outputs": [
{
Expand All @@ -135,40 +126,42 @@
}
],
"source": [
"const [updatePets, deletePets, listPets] = generateToolsForUser(\"brace\")\n",
"await updatePets.invoke({ pets: [\"cat\", \"dog\"] })\n",
"console.log(userToPets)\n",
"console.log(await listPets.invoke({}))"
"const [updatePets, deletePets, listPets] = generateToolsForUser(\"brace\");\n",
"\n",
"await updatePets.invoke({ pets: [\"cat\", \"dog\"] });\n",
"\n",
"console.log(userToPets);\n",
"console.log(await listPets.invoke({}));"
]
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"import { BaseChatModel } from \"@langchain/core/language_models/chat_models\";\n",
"\n",
"async function handleRunTimeRequest(userId: string, query: string, llm: BaseChatModel): Promise<any> {\n",
" if (!llm.bindTools) {\n",
" throw new Error(\"Language model does not support tools.\");\n",
" }\n",
" const tools = generateToolsForUser(userId);\n",
" const llmWithTools = llm.bindTools(tools);\n",
" return llmWithTools.invoke(query);\n",
" }"
" if (!llm.bindTools) {\n",
" throw new Error(\"Language model does not support tools.\");\n",
" }\n",
" const tools = generateToolsForUser(userId);\n",
" const llmWithTools = llm.bindTools(tools);\n",
" return llmWithTools.invoke(query);\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This code will allow the LLM to invoke the tools, but the LLM is **unaware** of the fact that a **user ID** even exists!"
"This code will allow the LLM to invoke the tools, but the LLM is **unaware** of the fact that a **user ID** even exists! You can see that `user_id` is not among the params the LLM generates:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"execution_count": 4,
"metadata": {},
"outputs": [
{
Expand All @@ -178,31 +171,34 @@
"{\n",
" name: 'update_favorite_pets',\n",
" args: { pets: [ 'cats', 'parrots' ] },\n",
" id: 'call_to1cbIVqMNuahHCdFO9oQzpN'\n",
" type: 'tool_call',\n",
" id: 'call_97h0nQ3B3cr0m58HOwq9ZyUz'\n",
"}\n"
]
}
],
"source": [
"const aiMessage = await handleRunTimeRequest(\n",
" \"brace\", \"my favorite animals are cats and parrots.\", llm,\n",
")\n",
"console.log(aiMessage.tool_calls[0])"
" \"brace\", \"my favorite animals are cats and parrots.\", llm,\n",
");\n",
"console.log(aiMessage.tool_calls[0]);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"```{=mdx}\n",
":::tip\n",
"Click [here](https://smith.langchain.com/public/3d766ecc-8f28-400b-8636-632e6f1598c7/r) to see the LangSmith trace for the above run.\n",
":::\n",
"\n",
":::tip\n",
"Chat models only output requests to invoke tools, they don't actually invoke the underlying tools.\n",
"Chat models only output requests to invoke tools. They don't actually invoke the underlying tools.\n",
"\n",
"To see how to invoke the tools, please refer to [how to use a model to call tools](/docs/how_to/tool_calling/).\n",
":::"
":::\n",
"```"
]
}
],
Expand Down
Loading