diff --git a/docs/core_docs/docs/how_to/index.mdx b/docs/core_docs/docs/how_to/index.mdx index 88370fdf968f..aec6c78c15ca 100644 --- a/docs/core_docs/docs/how_to/index.mdx +++ b/docs/core_docs/docs/how_to/index.mdx @@ -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 diff --git a/docs/core_docs/docs/how_to/tool_configure.ipynb b/docs/core_docs/docs/how_to/tool_configure.ipynb new file mode 100644 index 000000000000..e2c5e2f9b7f8 --- /dev/null +++ b/docs/core_docs/docs/how_to/tool_configure.ipynb @@ -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 +} diff --git a/docs/core_docs/docs/how_to/tool_runtime.ipynb b/docs/core_docs/docs/how_to/tool_runtime.ipynb index 588263a1885d..1d2ab289ba4c 100644 --- a/docs/core_docs/docs/how_to/tool_runtime.ipynb +++ b/docs/core_docs/docs/how_to/tool_runtime.ipynb @@ -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", @@ -21,6 +22,7 @@ "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", @@ -28,7 +30,7 @@ "\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." ] }, { @@ -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 = {};\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 {\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 {\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 {\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", "}" ] }, @@ -122,7 +113,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -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 {\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": [ { @@ -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", + "```" ] } ],