From f9102fd25d4c0bb4a98482255f705402bb17bd40 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Tue, 3 Sep 2024 07:30:33 -0700 Subject: [PATCH] docs: Adds versions page, updates docstrings, fix typos (#440) --- docs/docs/versions/.meta.yml | 4 + docs/docs/versions/index.md | 56 +++++++++ docs/mkdocs.yml | 2 + examples/how-tos/create-react-agent.ipynb | 28 ++--- .../dynamically-returning-directly.ipynb | 10 +- .../how-tos/force-calling-a-tool-first.ipynb | 9 +- examples/how-tos/tool-calling.ipynb | 35 +++--- .../src/prebuilt/react_agent_executor.ts | 59 ++++++++-- libs/langgraph/src/prebuilt/tool_executor.ts | 2 + libs/langgraph/src/prebuilt/tool_node.ts | 111 ++++++++++++++++++ 10 files changed, 261 insertions(+), 55 deletions(-) create mode 100644 docs/docs/versions/.meta.yml create mode 100644 docs/docs/versions/index.md diff --git a/docs/docs/versions/.meta.yml b/docs/docs/versions/.meta.yml new file mode 100644 index 0000000000000..39015924ed839 --- /dev/null +++ b/docs/docs/versions/.meta.yml @@ -0,0 +1,4 @@ +tags: + - versioning + - changelog + - upgrading \ No newline at end of file diff --git a/docs/docs/versions/index.md b/docs/docs/versions/index.md new file mode 100644 index 0000000000000..34f0370b43274 --- /dev/null +++ b/docs/docs/versions/index.md @@ -0,0 +1,56 @@ +# LangGraph Over Time + +As LangGraph.js continues to evolve and improve, breaking changes are sometimes necessary to enhance functionality, performance, or developer experience. This page serves as a guide to the version history of LangGraph.js, documenting significant changes and providing assistance for upgrading between versions. + +## Version History + +### v0.2.0 (Latest) + +- (Breaking) [`@langchain/core`](https://www.npmjs.com/package/@langchain/core) is now a peer dependency and requires explicit installation. +- Added support for [dynamic breakpoints](/langgraphjs/how-tos/dynamic_breakpoints/). +- Added support for [separate input and output schema](/langgraphjs/how-tos/input_output_schema/). +- Allow using an array to specify destination nodes from a conditional edge as shorthand for object. +- Numerous bugfixes. + +### v0.1.0 + +- (Breaking) Changed checkpoint representations to support namespacing for subgraphs and pending writes. +- (Breaking) `MessagesState` was changed to [`MessagesAnnotation`](/langgraphjs/reference/variables/langgraph.MessagesAnnotation.html). +- Added [`Annotation`](/langgraphjs/reference/modules/langgraph.Annotation.html), a more streamlined way to declare state. Removes the need for separate type and channel declarations. +- Split checkpointer implementations into different libraries for easier inheritance. +- Major internal architecture refactor to use more robust patterns. +- Deprecated `MessageGraph` in favor of [`StateGraph`](/langgraphjs/reference/classes/langgraph.StateGraph.html) + [`MessagesAnnotation`](/langgraphjs/reference/variables/langgraph.MessagesAnnotation.html). +- Numerous bugfixes. + +## Upgrading + +When upgrading LangGraph.js, please refer to the specific version sections below for detailed instructions on how to adapt your code to the latest changes. + +### Upgrading to v0.2.0 + +- You will now need to install `@langchain/core` explicitly. See [this page](https://langchain-ai.github.io/langgraphjs/how-tos/manage-ecosystem-dependencies/) for more information. + +### Upgrading to v0.1.0 + +- Old saved checkpoints will no longer be valid, and you will need to update to use a new prebuilt checkpointer. +- We recommend switching to the new `Annotation` syntax when declaring graph state. + +## Deprecation Notices + +This section will list any deprecated features or APIs, along with their planned removal dates and recommended alternatives. + +#### `MessageGraph` + +Use [`MessagesAnnotation`](/langgraphjs/reference/variables/langgraph.MessagesAnnotation.html) with [`StateGraph`](/langgraphjs/reference/classes/langgraph.StateGraph.html). + +#### `createFunctionCallingExecutor` + +Use [`createReactAgent`](/langgraphjs/reference/functions/langgraph_prebuilt.createReactAgent.html) with a model that supports tool calling. + +#### `ToolExecutor` + +Use [`ToolNode`](/langgraphjs/reference/classes/langgraph_prebuilt.ToolNode.html) instead. + +## Full changelog + +For the most up-to-date information on LangGraph.js versions and changes, please refer to our [GitHub repository](https://github.com/langchain-ai/langgraphjs) and [release notes](https://github.com/langchain-ai/langgraphjs/releases). diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 6f3efdeba3834..026d6cedb516d 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -150,6 +150,8 @@ nav: - FAQ: concepts/faq.md - "Reference": - "reference/index.html" + - "Versions": + - "versions/index.md" markdown_extensions: - abbr diff --git a/examples/how-tos/create-react-agent.ipynb b/examples/how-tos/create-react-agent.ipynb index 921d1f5fd5383..d2b58aa967a40 100644 --- a/examples/how-tos/create-react-agent.ipynb +++ b/examples/how-tos/create-react-agent.ipynb @@ -76,21 +76,21 @@ "import { createReactAgent } from \"@langchain/langgraph/prebuilt\";\n", "\n", "const model = new ChatOpenAI({\n", - " model: \"gpt-4o\",\n", - " });\n", + " model: \"gpt-4o\",\n", + "});\n", "\n", "const getWeather = tool((input) => {\n", - " if (['sf', 'san francisco', 'san francisco, ca'].includes(input.location.toLowerCase())) {\n", - " return 'It\\'s 60 degrees and foggy.';\n", - " } else {\n", - " return 'It\\'s 90 degrees and sunny.';\n", - " }\n", + " if (['sf', 'san francisco', 'san francisco, ca'].includes(input.location.toLowerCase())) {\n", + " return 'It\\'s 60 degrees and foggy.';\n", + " } else {\n", + " return 'It\\'s 90 degrees and sunny.';\n", + " }\n", "}, {\n", - " name: 'get_weather',\n", - " description: 'Call to get the current weather.',\n", - " schema: z.object({\n", - " location: z.string().describe(\"Location to get the weather for.\"),\n", - " })\n", + " name: 'get_weather',\n", + " description: 'Call to get the current weather.',\n", + " schema: z.object({\n", + " location: z.string().describe(\"Location to get the weather for.\"),\n", + " })\n", "})\n", "\n", "const agent = createReactAgent({ llm: model, tools: [getWeather] });" @@ -173,9 +173,7 @@ " streamMode: \"values\",\n", "});\n", "\n", - "for await (\n", - " const { messages } of stream\n", - ") {\n", + "for await (const { messages } of stream) {\n", " let msg = messages[messages?.length - 1];\n", " if (msg?.content) {\n", " console.log(msg.content);\n", diff --git a/examples/how-tos/dynamically-returning-directly.ipynb b/examples/how-tos/dynamically-returning-directly.ipynb index 534b22bbf7a72..0061f5a877972 100644 --- a/examples/how-tos/dynamically-returning-directly.ipynb +++ b/examples/how-tos/dynamically-returning-directly.ipynb @@ -117,10 +117,9 @@ "id": "f443c375", "metadata": {}, "source": [ - "We can now wrap these tools in a simple ToolExecutor.\\\n", - "This is a real simple class that takes in a ToolInvocation and calls that tool,\n", - "returning the output. A ToolInvocation is any type with `tool` and `toolInput`\n", - "attribute.\n" + "We can now wrap these tools in a `ToolNode`.\n", + "This is a prebuilt node that takes in a LangChain chat model's generated tool call and calls that tool,\n", + "returning the output." ] }, { @@ -133,9 +132,8 @@ "outputs": [], "source": [ "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n", - "import { BaseMessage } from \"@langchain/core/messages\";\n", "\n", - "const toolNode = new ToolNode<{ messages: BaseMessage[] }>(tools);" + "const toolNode = new ToolNode(tools);" ] }, { diff --git a/examples/how-tos/force-calling-a-tool-first.ipynb b/examples/how-tos/force-calling-a-tool-first.ipynb index b424475e831e9..2a14da277892d 100644 --- a/examples/how-tos/force-calling-a-tool-first.ipynb +++ b/examples/how-tos/force-calling-a-tool-first.ipynb @@ -109,9 +109,9 @@ "id": "8aa4e65a", "metadata": {}, "source": [ - "We can now wrap these tools in a simple ToolExecutor. This is a real simple\n", - "class that takes in a ToolInvocation and calls that tool, returning the output.\n", - "A ToolInvocation is any type with `tool` and `toolInput` attribute." + "We can now wrap these tools in a `ToolNode`.\n", + "This is a prebuilt node that takes in a LangChain chat model's generated tool call and calls that tool,\n", + "returning the output." ] }, { @@ -124,9 +124,8 @@ "outputs": [], "source": [ "import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n", - "import { BaseMessage } from \"@langchain/core/messages\";\n", "\n", - "const toolNode = new ToolNode<{ messages: BaseMessage[] }>(tools);" + "const toolNode = new ToolNode(tools);" ] }, { diff --git a/examples/how-tos/tool-calling.ipynb b/examples/how-tos/tool-calling.ipynb index 6d90bdd642a8f..060c77a4c1b45 100644 --- a/examples/how-tos/tool-calling.ipynb +++ b/examples/how-tos/tool-calling.ipynb @@ -318,31 +318,28 @@ "const toolNodeForGraph = new ToolNode(tools)\n", "\n", "const shouldContinue = (state: typeof MessagesAnnotation.State) => {\n", - " const { messages } = state;\n", - " const lastMessage = messages[messages.length - 1];\n", - " if (\"tool_calls\" in lastMessage && Array.isArray(lastMessage.tool_calls) && lastMessage.tool_calls?.length) {\n", - " return \"tools\";\n", - " }\n", - " return END;\n", + " const { messages } = state;\n", + " const lastMessage = messages[messages.length - 1];\n", + " if (\"tool_calls\" in lastMessage && Array.isArray(lastMessage.tool_calls) && lastMessage.tool_calls?.length) {\n", + " return \"tools\";\n", + " }\n", + " return END;\n", "}\n", "\n", "const callModel = async (state: typeof MessagesAnnotation.State) => {\n", - " const { messages } = state;\n", - " const response = await modelWithTools.invoke(messages);\n", - " return { messages: [response] };\n", + " const { messages } = state;\n", + " const response = await modelWithTools.invoke(messages);\n", + " return { messages: response };\n", "}\n", "\n", "\n", "const workflow = new StateGraph(MessagesAnnotation)\n", - " // Define the two nodes we will cycle between\n", - " .addNode(\"agent\", callModel)\n", - " .addNode(\"tools\", toolNodeForGraph)\n", - " .addEdge(START, \"agent\")\n", - " .addConditionalEdges(\n", - " \"agent\",\n", - " shouldContinue,\n", - " )\n", - " .addEdge(\"tools\", \"agent\");\n", + " // Define the two nodes we will cycle between\n", + " .addNode(\"agent\", callModel)\n", + " .addNode(\"tools\", toolNodeForGraph)\n", + " .addEdge(START, \"agent\")\n", + " .addConditionalEdges(\"agent\", shouldContinue, [\"tools\", END])\n", + " .addEdge(\"tools\", \"agent\");\n", "\n", "const app = workflow.compile()" ] @@ -574,7 +571,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "`ToolNode` can also handle errors during tool execution. See our guide on handling errors in `ToolNode` [here](/langgraphjs/how-tos/tool-calling-errors/)" + "`ToolNode` can also handle errors during tool execution. See our guide on handling errors in `ToolNode` [here](/langgraphjs/how-tos/tool-calling-errors/)." ] } ], diff --git a/libs/langgraph/src/prebuilt/react_agent_executor.ts b/libs/langgraph/src/prebuilt/react_agent_executor.ts index 75f58fcf7f59a..bba625aa74584 100644 --- a/libs/langgraph/src/prebuilt/react_agent_executor.ts +++ b/libs/langgraph/src/prebuilt/react_agent_executor.ts @@ -58,18 +58,57 @@ export type CreateReactAgentParams = { }; /** - * Creates a StateGraph agent that relies on a chat llm utilizing tool calling. - * @param llm The chat llm that can utilize OpenAI-style function calling. - * @param tools A list of tools or a ToolNode. - * @param messageModifier An optional message modifier to apply to messages before being passed to the LLM. + * Creates a StateGraph agent that relies on a chat model utilizing tool calling. + * @param params.llm The chat model that can utilize OpenAI-style tool calling. + * @param params.tools A list of tools or a ToolNode. + * @param params.messageModifier An optional message modifier to apply to messages before being passed to the LLM. * Can be a SystemMessage, string, function that takes and returns a list of messages, or a Runnable. - * @param checkpointSaver An optional checkpoint saver to persist the agent's state. - * @param interruptBefore An optional list of node names to interrupt before running. - * @param interruptAfter An optional list of node names to interrupt after running. - * @returns A compiled agent as a LangChain Runnable. + * @param params.checkpointer An optional checkpoint saver to persist the agent's state. + * @param params.interruptBefore An optional list of node names to interrupt before running. + * @param params.interruptAfter An optional list of node names to interrupt after running. + * @returns A prebuilt compiled graph. + * + * @example + * ```ts + * import { ChatOpenAI } from "@langchain/openai"; + * import { tool } from "@langchain/core/tools"; + * import { z } from "zod"; + * import { createReactAgent } from "@langchain/langgraph/prebuilt"; + * + * const model = new ChatOpenAI({ + * model: "gpt-4o", + * }); + * + * const getWeather = tool((input) => { + * if (["sf", "san francisco"].includes(input.location.toLowerCase())) { + * return "It's 60 degrees and foggy."; + * } else { + * return "It's 90 degrees and sunny."; + * } + * }, { + * name: "get_weather", + * description: "Call to get the current weather.", + * schema: z.object({ + * location: z.string().describe("Location to get the weather for."), + * }) + * }) + * + * const agent = createReactAgent({ llm: model, tools: [getWeather] }); + * + * const inputs = { + * messages: [{ role: "user", content: "what is the weather in SF?" }], + * }; + * + * const stream = await agent.stream(inputs, { streamMode: "values" }); + * + * for await (const { messages } of stream) { + * console.log(messages); + * } + * // Returns the messages in the state at each step of execution + * ``` */ export function createReactAgent( - props: CreateReactAgentParams + params: CreateReactAgentParams ): CompiledStateGraph< AgentState, Partial, @@ -82,7 +121,7 @@ export function createReactAgent( checkpointSaver, interruptBefore, interruptAfter, - } = props; + } = params; const schema: StateGraphArgs["channels"] = { messages: { value: messagesStateReducer, diff --git a/libs/langgraph/src/prebuilt/tool_executor.ts b/libs/langgraph/src/prebuilt/tool_executor.ts index e7f3482bde24c..a03660829d020 100644 --- a/libs/langgraph/src/prebuilt/tool_executor.ts +++ b/libs/langgraph/src/prebuilt/tool_executor.ts @@ -8,6 +8,7 @@ import { StructuredToolInterface } from "@langchain/core/tools"; const INVALID_TOOL_MSG_TEMPLATE = `{requestedToolName} is not a valid tool, try one of {availableToolNamesString}.`; +/** @deprecated Use {@link ToolNode} instead. */ export interface ToolExecutorArgs { tools: Array; /** @@ -30,6 +31,7 @@ type ToolExecutorInputType = any; // eslint-disable-next-line @typescript-eslint/no-explicit-any type ToolExecutorOutputType = any; +/** @deprecated Use {@link ToolNode} instead. */ export class ToolExecutor extends RunnableBinding< ToolExecutorInputType, ToolExecutorOutputType diff --git a/libs/langgraph/src/prebuilt/tool_node.ts b/libs/langgraph/src/prebuilt/tool_node.ts index 5b1f148094d43..e030d8d3c36d1 100644 --- a/libs/langgraph/src/prebuilt/tool_node.ts +++ b/libs/langgraph/src/prebuilt/tool_node.ts @@ -21,6 +21,117 @@ export type ToolNodeOptions = { * either in StateGraph with a "messages" key or in MessageGraph. If multiple * tool calls are requested, they will be run in parallel. The output will be * a list of ToolMessages, one for each tool call. + * + * @example + * ```ts + * import { ToolNode } from "@langchain/langgraph/prebuilt"; + * import { tool } from "@langchain/core/tools"; + * import { z } from "zod"; + * import { AIMessage } from "@langchain/core/messages"; + * + * const getWeather = tool((input) => { + * if (["sf", "san francisco"].includes(input.location.toLowerCase())) { + * return "It's 60 degrees and foggy."; + * } else { + * return "It's 90 degrees and sunny."; + * } + * }, { + * name: "get_weather", + * description: "Call to get the current weather.", + * schema: z.object({ + * location: z.string().describe("Location to get the weather for."), + * }), + * }); + * + * const tools = [getWeather]; + * const toolNode = new ToolNode(tools); + * + * const messageWithSingleToolCall = new AIMessage({ + * content: "", + * tool_calls: [ + * { + * name: "get_weather", + * args: { location: "sf" }, + * id: "tool_call_id", + * type: "tool_call", + * } + * ] + * }) + * + * await toolNode.invoke({ messages: [messageWithSingleToolCall] }); + * // Returns tool invocation responses as: + * // { messages: ToolMessage[] } + * ``` + * + * @example + * ```ts + * import { + * StateGraph, + * MessagesAnnotation, + * } from "@langchain/langgraph"; + * import { ToolNode } from "@langchain/langgraph/prebuilt"; + * import { tool } from "@langchain/core/tools"; + * import { z } from "zod"; + * import { ChatAnthropic } from "@langchain/anthropic"; + * + * const getWeather = tool((input) => { + * if (["sf", "san francisco"].includes(input.location.toLowerCase())) { + * return "It's 60 degrees and foggy."; + * } else { + * return "It's 90 degrees and sunny."; + * } + * }, { + * name: "get_weather", + * description: "Call to get the current weather.", + * schema: z.object({ + * location: z.string().describe("Location to get the weather for."), + * }), + * }); + * + * const tools = [getWeather]; + * const modelWithTools = new ChatAnthropic({ + * model: "claude-3-haiku-20240307", + * temperature: 0 + * }).bindTools(tools); + * + * const toolNodeForGraph = new ToolNode(tools) + * + * const shouldContinue = (state: typeof MessagesAnnotation.State) => { + * const { messages } = state; + * const lastMessage = messages[messages.length - 1]; + * if ("tool_calls" in lastMessage && Array.isArray(lastMessage.tool_calls) && lastMessage.tool_calls?.length) { + * return "tools"; + * } + * return "__end__"; + * } + * + * const callModel = async (state: typeof MessagesAnnotation.State) => { + * const { messages } = state; + * const response = await modelWithTools.invoke(messages); + * return { messages: response }; + * } + * + * const graph = new StateGraph(MessagesAnnotation) + * .addNode("agent", callModel) + * .addNode("tools", toolNodeForGraph) + * .addEdge("__start__", "agent") + * .addConditionalEdges("agent", shouldContinue) + * .addEdge("tools", "agent") + * .compile(); + * + * const inputs = { + * messages: [{ role: "user", content: "what is the weather in SF?" }], + * }; + * + * const stream = await graph.stream(inputs, { + * streamMode: "values", + * }); + * + * for await (const { messages } of stream) { + * console.log(messages); + * } + * // Returns the messages in the state at each step of execution + * ``` */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export class ToolNode extends RunnableCallable {