diff --git a/docs/docs/how-tos/autogen-integration-functional.ipynb b/docs/docs/how-tos/autogen-integration-functional.ipynb new file mode 100644 index 000000000..11b81be21 --- /dev/null +++ b/docs/docs/how-tos/autogen-integration-functional.ipynb @@ -0,0 +1,389 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "100c0c81-6a9f-4ba1-b1a8-42aae82b7172", + "metadata": {}, + "source": [ + "# How to integrate LangGraph (functional API) with AutoGen, CrewAI, and other frameworks\n", + "\n", + "LangGraph is a framework for building agentic and multi-agent applications. LangGraph can be easily integrated with other agent frameworks. \n", + "\n", + "The primary reasons you might want to integrate LangGraph with other agent frameworks:\n", + "\n", + "- create [multi-agent systems](../../concepts/multi_agent) where individual agents are built with different frameworks\n", + "- leverage LangGraph to add features like [persistence](../../concepts/persistence), [streaming](../../concepts/streaming), [short and long-term memory](../../concepts/memory) and more\n", + "\n", + "The simplest way to integrate agents from other frameworks is by calling those agents inside a LangGraph [node](../../concepts/low_level/#nodes):\n", + "\n", + "```python\n", + "import autogen\n", + "from langgraph.func import entrypoint, task\n", + "\n", + "autogen_agent = autogen.AssistantAgent(name=\"assistant\", ...)\n", + "user_proxy = autogen.UserProxyAgent(name=\"user_proxy\", ...)\n", + "\n", + "@task\n", + "def call_autogen_agent(messages):\n", + " response = user_proxy.initiate_chat(\n", + " autogen_agent,\n", + " message=messages[-1],\n", + " ...\n", + " )\n", + " ...\n", + "\n", + "\n", + "@entrypoint()\n", + "def workflow(messages):\n", + " response = call_autogen_agent(messages).result()\n", + " return response\n", + "\n", + "\n", + "workflow.invoke(\n", + " [\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": \"Find numbers between 10 and 30 in fibonacci sequence\",\n", + " }\n", + " ]\n", + ")\n", + "```\n", + "\n", + "In this guide we show how to build a LangGraph chatbot that integrates with AutoGen, but you can follow the same approach with other frameworks." + ] + }, + { + "cell_type": "markdown", + "id": "b189ceb2-132b-4c7b-81b4-c7b8b062f833", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "62417d3a-94f9-4a52-9962-12639d714966", + "metadata": {}, + "outputs": [], + "source": [ + "%pip install autogen langgraph" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d46da41d-0a71-4654-aec8-9e6ad8765236", + "metadata": {}, + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "OPENAI_API_KEY: ········\n" + ] + } + ], + "source": [ + "import getpass\n", + "import os\n", + "\n", + "\n", + "def _set_env(var: str):\n", + " if not os.environ.get(var):\n", + " os.environ[var] = getpass.getpass(f\"{var}: \")\n", + "\n", + "\n", + "_set_env(\"OPENAI_API_KEY\")" + ] + }, + { + "cell_type": "markdown", + "id": "1926bbc3-6b06-41e0-9604-860a2bbf8fa3", + "metadata": {}, + "source": [ + "## Define AutoGen agent\n", + "\n", + "Here we define our AutoGen agent. Adapted from official tutorial [here](https://github.com/microsoft/autogen/blob/0.2/notebook/agentchat_web_info.ipynb)." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "524de117-ff09-4b26-bfe8-a9f85a46ffd5", + "metadata": {}, + "outputs": [], + "source": [ + "import autogen\n", + "import os\n", + "\n", + "config_list = [{\"model\": \"gpt-4o\", \"api_key\": os.environ[\"OPENAI_API_KEY\"]}]\n", + "\n", + "llm_config = {\n", + " \"timeout\": 600,\n", + " \"cache_seed\": 42,\n", + " \"config_list\": config_list,\n", + " \"temperature\": 0,\n", + "}\n", + "\n", + "autogen_agent = autogen.AssistantAgent(\n", + " name=\"assistant\",\n", + " llm_config=llm_config,\n", + ")\n", + "\n", + "user_proxy = autogen.UserProxyAgent(\n", + " name=\"user_proxy\",\n", + " human_input_mode=\"NEVER\",\n", + " max_consecutive_auto_reply=10,\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\").rstrip().endswith(\"TERMINATE\"),\n", + " code_execution_config={\n", + " \"work_dir\": \"web\",\n", + " \"use_docker\": False,\n", + " }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n", + " llm_config=llm_config,\n", + " system_message=\"Reply TERMINATE if the task has been solved at full satisfaction. Otherwise, reply CONTINUE, or the reason why the task is not solved yet.\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "8aa858e2-4acb-4f75-be20-b9ccbbcb5073", + "metadata": {}, + "source": [ + "---" + ] + }, + { + "cell_type": "markdown", + "id": "dcc478f5-4a35-43f8-bf59-9cb71289cd00", + "metadata": {}, + "source": [ + "## Create the workflow\n", + "\n", + "We will now create a LangGraph chatbot graph that calls AutoGen agent." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "d129e4e1-3766-429a-b806-cde3d8bc0469", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Literal, TypedDict\n", + "\n", + "from langchain_core.messages import convert_to_openai_messages, BaseMessage\n", + "from langgraph.func import entrypoint, task\n", + "from langgraph.graph import add_messages\n", + "from langgraph.checkpoint.memory import MemorySaver\n", + "\n", + "\n", + "@task\n", + "def call_autogen_agent(messages: list[BaseMessage]):\n", + " # convert to openai-style messages\n", + " messages = convert_to_openai_messages(messages)\n", + " response = user_proxy.initiate_chat(\n", + " autogen_agent,\n", + " message=messages[-1],\n", + " # pass previous message history as context\n", + " carryover=messages[:-1],\n", + " )\n", + " # get the final response from the agent\n", + " content = response.chat_history[-1][\"content\"]\n", + " return {\"role\": \"assistant\", \"content\": content}\n", + "\n", + "\n", + "# add short-term memory for storing conversation history\n", + "checkpointer = MemorySaver()\n", + "\n", + "\n", + "@entrypoint(checkpointer=checkpointer)\n", + "def workflow(messages: list[BaseMessage], previous: list[BaseMessage]):\n", + " messages = add_messages(previous or [], messages)\n", + " response = call_autogen_agent(messages).result()\n", + " return entrypoint.final(value=response, save=add_messages(messages, response))" + ] + }, + { + "cell_type": "markdown", + "id": "23d629c3-1d6b-40af-adf6-915e15657566", + "metadata": {}, + "source": [ + "## Run the graph\n", + "\n", + "We can now run the graph." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "a279b667-0f5d-4008-8d43-c806a3f379c4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33muser_proxy\u001b[0m (to assistant):\n", + "\n", + "Find numbers between 10 and 30 in fibonacci sequence\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33massistant\u001b[0m (to user_proxy):\n", + "\n", + "To find numbers between 10 and 30 in the Fibonacci sequence, we can generate the Fibonacci sequence and check which numbers fall within this range. Here's a plan:\n", + "\n", + "1. Generate Fibonacci numbers starting from 0.\n", + "2. Continue generating until the numbers exceed 30.\n", + "3. Collect and print the numbers that are between 10 and 30.\n", + "\n", + "Let's implement this in Python:\n", + "\n", + "```python\n", + "# filename: fibonacci_range.py\n", + "\n", + "def fibonacci_sequence():\n", + " a, b = 0, 1\n", + " while a <= 30:\n", + " if 10 <= a <= 30:\n", + " print(a)\n", + " a, b = b, a + b\n", + "\n", + "fibonacci_sequence()\n", + "```\n", + "\n", + "This script will print the Fibonacci numbers between 10 and 30. Please execute the code to see the result.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", + "\u001b[33muser_proxy\u001b[0m (to assistant):\n", + "\n", + "exitcode: 0 (execution succeeded)\n", + "Code output: \n", + "13\n", + "21\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33massistant\u001b[0m (to user_proxy):\n", + "\n", + "The Fibonacci numbers between 10 and 30 are 13 and 21. \n", + "\n", + "These numbers are part of the Fibonacci sequence, which is generated by adding the two preceding numbers to get the next number, starting from 0 and 1. \n", + "\n", + "The sequence goes: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...\n", + "\n", + "As you can see, 13 and 21 are the only numbers in this sequence that fall between 10 and 30.\n", + "\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n", + "{'call_autogen_agent': {'role': 'assistant', 'content': 'The Fibonacci numbers between 10 and 30 are 13 and 21. \\n\\nThese numbers are part of the Fibonacci sequence, which is generated by adding the two preceding numbers to get the next number, starting from 0 and 1. \\n\\nThe sequence goes: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...\\n\\nAs you can see, 13 and 21 are the only numbers in this sequence that fall between 10 and 30.\\n\\nTERMINATE'}}\n", + "{'workflow': {'role': 'assistant', 'content': 'The Fibonacci numbers between 10 and 30 are 13 and 21. \\n\\nThese numbers are part of the Fibonacci sequence, which is generated by adding the two preceding numbers to get the next number, starting from 0 and 1. \\n\\nThe sequence goes: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...\\n\\nAs you can see, 13 and 21 are the only numbers in this sequence that fall between 10 and 30.\\n\\nTERMINATE'}}\n" + ] + } + ], + "source": [ + "# pass the thread ID to persist agent outputs for future interactions\n", + "# highlight-next-line\n", + "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", + "\n", + "for chunk in workflow.stream(\n", + " [\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": \"Find numbers between 10 and 30 in fibonacci sequence\",\n", + " }\n", + " ],\n", + " # highlight-next-line\n", + " config,\n", + "):\n", + " print(chunk)" + ] + }, + { + "cell_type": "markdown", + "id": "c6cd57b4-d4ee-49f6-be12-318613849669", + "metadata": {}, + "source": [ + "Since we're leveraging LangGraph's [persistence](https://langchain-ai.github.io/langgraph/concepts/persistence/) features we can now continue the conversation using the same thread ID -- LangGraph will automatically pass previous history to the AutoGen agent:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "e68811a7-962e-4fe3-9f45-9b99ebbe04e7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33muser_proxy\u001b[0m (to assistant):\n", + "\n", + "Multiply the last number by 3\n", + "Context: \n", + "Find numbers between 10 and 30 in fibonacci sequence\n", + "The Fibonacci numbers between 10 and 30 are 13 and 21. \n", + "\n", + "These numbers are part of the Fibonacci sequence, which is generated by adding the two preceding numbers to get the next number, starting from 0 and 1. \n", + "\n", + "The sequence goes: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...\n", + "\n", + "As you can see, 13 and 21 are the only numbers in this sequence that fall between 10 and 30.\n", + "\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33massistant\u001b[0m (to user_proxy):\n", + "\n", + "The last number in the Fibonacci sequence between 10 and 30 is 21. Multiplying 21 by 3 gives:\n", + "\n", + "21 * 3 = 63\n", + "\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n", + "{'call_autogen_agent': {'role': 'assistant', 'content': 'The last number in the Fibonacci sequence between 10 and 30 is 21. Multiplying 21 by 3 gives:\\n\\n21 * 3 = 63\\n\\nTERMINATE'}}\n", + "{'workflow': {'role': 'assistant', 'content': 'The last number in the Fibonacci sequence between 10 and 30 is 21. Multiplying 21 by 3 gives:\\n\\n21 * 3 = 63\\n\\nTERMINATE'}}\n" + ] + } + ], + "source": [ + "for chunk in workflow.stream(\n", + " [\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": \"Multiply the last number by 3\",\n", + " }\n", + " ],\n", + " # highlight-next-line\n", + " config,\n", + "):\n", + " print(chunk)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/docs/how-tos/autogen-integration.ipynb b/docs/docs/how-tos/autogen-integration.ipynb index 2f77b1444..92199cc78 100644 --- a/docs/docs/how-tos/autogen-integration.ipynb +++ b/docs/docs/how-tos/autogen-integration.ipynb @@ -7,11 +7,47 @@ "source": [ "# How to integrate LangGraph with AutoGen, CrewAI, and other frameworks\n", "\n", - "LangGraph is a framework for building agentic and multi-agent applications. This includes integrating with other agent frameworks.\n", + "LangGraph is a framework for building agentic and multi-agent applications. LangGraph can be easily integrated with other agent frameworks. \n", "\n", - "This guides shows how to integrate LangGraph with other frameworks. The framework we show off integrating with is AutoGen, but this can easily be done with other frameworks.\n", + "The primary reasons you might want to integrate LangGraph with other agent frameworks:\n", "\n", - "At a high level, the way this works is by wrapping the other agent inside a LangGraph node. LangGraph nodes can be anything - arbitrary code. This makes it easy to define an AutoGen (or CrewAI, or LlamaIndex, or other framework) agent and then reference it inside your graph. This allows you to create multi-agent systems where some of the sub-agents are actually defined in other frameworks." + "- create [multi-agent systems](../../concepts/multi_agent) where individual agents are built with different frameworks\n", + "- leverage LangGraph to add features like [persistence](../../concepts/persistence), [streaming](../../concepts/streaming), [short and long-term memory](../../concepts/memory) and more\n", + "\n", + "The simplest way to integrate agents from other frameworks is by calling those agents inside a LangGraph [node](../../concepts/low_level/#nodes):\n", + "\n", + "```python\n", + "from langgraph.graph import StateGraph, MessagesState, START\n", + "\n", + "autogen_agent = autogen.AssistantAgent(name=\"assistant\", ...)\n", + "user_proxy = autogen.UserProxyAgent(name=\"user_proxy\", ...)\n", + "\n", + "def call_autogen_agent(state: MessagesState):\n", + " response = user_proxy.initiate_chat(\n", + " autogen_agent,\n", + " message=state[\"messages\"][-1],\n", + " ...\n", + " )\n", + " ...\n", + "\n", + "graph = (\n", + " StateGraph(MessagesState)\n", + " .add_node(call_autogen_agent)\n", + " .add_edge(START, \"call_autogen_agent\")\n", + " .compile()\n", + ")\n", + "\n", + "graph.invoke({\n", + " \"messages\": [\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": \"Find numbers between 10 and 30 in fibonacci sequence\",\n", + " }\n", + " ]\n", + "})\n", + "```\n", + "\n", + "In this guide we show how to build a LangGraph chatbot that integrates with AutoGen, but you can follow the same approach with other frameworks." ] }, { @@ -24,20 +60,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "62417d3a-94f9-4a52-9962-12639d714966", "metadata": {}, "outputs": [], "source": [ - "%pip install autogen bs4 langgraph langchain-openai langchain-community" + "%pip install autogen langgraph" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "d46da41d-0a71-4654-aec8-9e6ad8765236", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdin", + "output_type": "stream", + "text": [ + "OPENAI_API_KEY: ········\n" + ] + } + ], "source": [ "import getpass\n", "import os\n", @@ -48,8 +92,7 @@ " os.environ[var] = getpass.getpass(f\"{var}: \")\n", "\n", "\n", - "_set_env(\"OPENAI_API_KEY\")\n", - "_set_env(\"TAVILY_API_KEY\")" + "_set_env(\"OPENAI_API_KEY\")" ] }, { @@ -59,12 +102,12 @@ "source": [ "## Define AutoGen agent\n", "\n", - "Here we define our AutoGen agent. From https://github.com/microsoft/autogen/blob/0.2/notebook/agentchat_web_info.ipynb" + "Here we define our AutoGen agent. Adapted from official tutorial [here](https://github.com/microsoft/autogen/blob/0.2/notebook/agentchat_web_info.ipynb)." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "524de117-ff09-4b26-bfe8-a9f85a46ffd5", "metadata": {}, "outputs": [], @@ -108,97 +151,62 @@ "---" ] }, - { - "cell_type": "markdown", - "id": "d6bc7b69-4a36-44dc-a501-7e17122cc385", - "metadata": {}, - "source": [ - "## Define LangGraph agent\n", - "\n", - "We now define our LangGraph agent. We will create a simple ReAct-style agent with a web search tool" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a0fcdaac-8fbe-4589-8e61-8092165356cd", - "metadata": {}, - "outputs": [], - "source": [ - "from langgraph.graph import StateGraph, START, MessagesState\n", - "from langgraph.prebuilt import ToolNode, create_react_agent\n", - "from langchain_community.tools.tavily_search import TavilySearchResults\n", - "from langchain_openai import ChatOpenAI\n", - "from langchain_core.messages import HumanMessage, AIMessage\n", - "\n", - "model = ChatOpenAI(model=\"gpt-4o\")\n", - "tools = [TavilySearchResults(max_results=1)]\n", - "web_search_agent = create_react_agent(\n", - " model, tools, prompt=\"You are an agent specializing in web search\"\n", - ")" - ] - }, { "cell_type": "markdown", "id": "dcc478f5-4a35-43f8-bf59-9cb71289cd00", "metadata": {}, "source": [ - "## Create the multi-agent graph\n", + "## Create the graph\n", "\n", - "We will now create our multi-agent system combining the AutoGen agent with the LangGraph agent. We can do this by creating a graph that routes user query to the appropriate agent and executes the agent" + "We will now create a LangGraph chatbot graph that calls AutoGen agent." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "d129e4e1-3766-429a-b806-cde3d8bc0469", "metadata": {}, "outputs": [], "source": [ "from typing import Literal, TypedDict\n", "\n", - "\n", - "class Route(TypedDict):\n", - " \"\"\"Decide where to go next\"\"\"\n", - "\n", - " goto: Literal[\"web_search_assistant\", \"coding_assistant\"]\n", - "\n", - "\n", - "def route(state: MessagesState) -> Literal[\"web_search_assistant\", \"coding_assistant\"]:\n", - " messages = [\n", - " {\n", - " \"role\": \"system\",\n", - " \"content\": \"Based on the conversation so far, decide who to call next: web search assistant or coding assistant.\",\n", - " }\n", - " ] + state[\"messages\"]\n", - " response = model.with_structured_output(Route).invoke(messages)\n", - " return response[\"goto\"]\n", + "from langchain_core.messages import convert_to_openai_messages\n", + "from langgraph.graph import StateGraph, MessagesState, START\n", + "from langgraph.checkpoint.memory import MemorySaver\n", "\n", "\n", "def call_autogen_agent(state: MessagesState):\n", - " last_message = state[\"messages\"][-1]\n", - " response = user_proxy.initiate_chat(autogen_agent, message=last_message.content)\n", + " # convert to openai-style messages\n", + " messages = convert_to_openai_messages(state[\"messages\"])\n", + " response = user_proxy.initiate_chat(\n", + " autogen_agent,\n", + " message=messages[-1],\n", + " # pass previous message history as context\n", + " carryover=messages[:-1],\n", + " )\n", " # get the final response from the agent\n", " content = response.chat_history[-1][\"content\"]\n", - " return {\"messages\": AIMessage(content=content)}\n", + " return {\"messages\": {\"role\": \"assistant\", \"content\": content}}\n", "\n", "\n", + "# add short-term memory for storing conversation history\n", + "checkpointer = MemorySaver()\n", + "\n", "builder = StateGraph(MessagesState)\n", - "builder.add_conditional_edges(START, route)\n", - "builder.add_node(\"coding_assistant\", call_autogen_agent)\n", - "builder.add_node(\"web_search_assistant\", web_search_agent)\n", - "graph = builder.compile()" + "builder.add_node(call_autogen_agent)\n", + "builder.add_edge(START, \"call_autogen_agent\")\n", + "graph = builder.compile(checkpointer=checkpointer)" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 5, "id": "c761fc05-e8b6-4905-a793-eb7522d20060", "metadata": {}, "outputs": [ { "data": { - "image/jpeg": "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", + "image/png": "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", "text/plain": [ "" ] @@ -220,13 +228,13 @@ "source": [ "## Run the graph\n", "\n", - "We can now run the graph. We can see in the examples below how we first route to the appropriate agent, then respond with the subagent." + "We can now run the graph." ] }, { "cell_type": "code", - "execution_count": 15, - "id": "b528ddb9-ec12-433c-a174-33d94dc49d80", + "execution_count": 6, + "id": "a279b667-0f5d-4008-8d43-c806a3f379c4", "metadata": {}, "outputs": [ { @@ -261,10 +269,101 @@ "fibonacci_sequence()\n", "```\n", "\n", - "Save this code in a file named `fibonacci_range.py` and execute it. It will print the Fibonacci numbers between 10 and 30. TERMINATE\n", + "This script will print the Fibonacci numbers between 10 and 30. Please execute the code to see the result.\n", "\n", "--------------------------------------------------------------------------------\n", - "{'coding_assistant': {'messages': AIMessage(content=\"To find numbers between 10 and 30 in the Fibonacci sequence, we can generate the Fibonacci sequence and check which numbers fall within this range. Here's a plan:\\n\\n1. Generate Fibonacci numbers starting from 0.\\n2. Continue generating until the numbers exceed 30.\\n3. Collect and print the numbers that are between 10 and 30.\\n\\nLet's implement this in Python:\\n\\n```python\\n# filename: fibonacci_range.py\\n\\ndef fibonacci_sequence():\\n a, b = 0, 1\\n while a <= 30:\\n if 10 <= a <= 30:\\n print(a)\\n a, b = b, a + b\\n\\nfibonacci_sequence()\\n```\\n\\nSave this code in a file named `fibonacci_range.py` and execute it. It will print the Fibonacci numbers between 10 and 30. TERMINATE\", additional_kwargs={}, response_metadata={}, id='e95a8aa1-5aa8-4ff2-ba74-2b2993ea0a5a')}}\n" + "\u001b[31m\n", + ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", + "\u001b[33muser_proxy\u001b[0m (to assistant):\n", + "\n", + "exitcode: 0 (execution succeeded)\n", + "Code output: \n", + "13\n", + "21\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33massistant\u001b[0m (to user_proxy):\n", + "\n", + "The Fibonacci numbers between 10 and 30 are 13 and 21. \n", + "\n", + "These numbers are part of the Fibonacci sequence, which is generated by adding the two preceding numbers to get the next number, starting from 0 and 1. \n", + "\n", + "The sequence goes: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...\n", + "\n", + "As you can see, 13 and 21 are the only numbers in this sequence that fall between 10 and 30.\n", + "\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n", + "{'call_autogen_agent': {'messages': {'role': 'assistant', 'content': 'The Fibonacci numbers between 10 and 30 are 13 and 21. \\n\\nThese numbers are part of the Fibonacci sequence, which is generated by adding the two preceding numbers to get the next number, starting from 0 and 1. \\n\\nThe sequence goes: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...\\n\\nAs you can see, 13 and 21 are the only numbers in this sequence that fall between 10 and 30.\\n\\nTERMINATE'}}}\n" + ] + } + ], + "source": [ + "# pass the thread ID to persist agent outputs for future interactions\n", + "# highlight-next-line\n", + "config = {\"configurable\": {\"thread_id\": \"1\"}}\n", + "\n", + "for chunk in graph.stream(\n", + " {\n", + " \"messages\": [\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": \"Find numbers between 10 and 30 in fibonacci sequence\",\n", + " }\n", + " ]\n", + " },\n", + " # highlight-next-line\n", + " config,\n", + "):\n", + " print(chunk)" + ] + }, + { + "cell_type": "markdown", + "id": "c6cd57b4-d4ee-49f6-be12-318613849669", + "metadata": {}, + "source": [ + "Since we're leveraging LangGraph's [persistence](https://langchain-ai.github.io/langgraph/concepts/persistence/) features we can now continue the conversation using the same thread ID -- LangGraph will automatically pass previous history to the AutoGen agent:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e68811a7-962e-4fe3-9f45-9b99ebbe04e7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33muser_proxy\u001b[0m (to assistant):\n", + "\n", + "Multiply the last number by 3\n", + "Context: \n", + "Find numbers between 10 and 30 in fibonacci sequence\n", + "The Fibonacci numbers between 10 and 30 are 13 and 21. \n", + "\n", + "These numbers are part of the Fibonacci sequence, which is generated by adding the two preceding numbers to get the next number, starting from 0 and 1. \n", + "\n", + "The sequence goes: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...\n", + "\n", + "As you can see, 13 and 21 are the only numbers in this sequence that fall between 10 and 30.\n", + "\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33massistant\u001b[0m (to user_proxy):\n", + "\n", + "The last number in the Fibonacci sequence between 10 and 30 is 21. Multiplying 21 by 3 gives:\n", + "\n", + "21 * 3 = 63\n", + "\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n", + "{'call_autogen_agent': {'messages': {'role': 'assistant', 'content': 'The last number in the Fibonacci sequence between 10 and 30 is 21. Multiplying 21 by 3 gives:\\n\\n21 * 3 = 63\\n\\nTERMINATE'}}}\n" ] } ], @@ -274,35 +373,12 @@ " \"messages\": [\n", " {\n", " \"role\": \"user\",\n", - " \"content\": \"Find numbers between 10 and 30 in fibonacci sequence\",\n", + " \"content\": \"Multiply the last number by 3\",\n", " }\n", " ]\n", - " }\n", - "):\n", - " print(chunk)" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "b120f9ba-f640-482b-a457-1893d6db5543", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(('web_search_assistant:d08ae326-b6b2-1749-e8ea-4d308f22d819',), {'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_wZ5w5uO733Cc4CvWbc4Axq5F', 'function': {'arguments': '{\"query\":\"current weather in New York City\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 23, 'prompt_tokens': 96, 'total_tokens': 119, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_45cf54deae', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-0658af11-b90b-407c-a6a5-6a3b4a9f61e0-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'current weather in New York City'}, 'id': 'call_wZ5w5uO733Cc4CvWbc4Axq5F', 'type': 'tool_call'}], usage_metadata={'input_tokens': 96, 'output_tokens': 23, 'total_tokens': 119, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}})\n", - "(('web_search_assistant:d08ae326-b6b2-1749-e8ea-4d308f22d819',), {'tools': {'messages': [ToolMessage(content='[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'New York\\', \\'region\\': \\'New York\\', \\'country\\': \\'United States of America\\', \\'lat\\': 40.714, \\'lon\\': -74.006, \\'tz_id\\': \\'America/New_York\\', \\'localtime_epoch\\': 1732021037, \\'localtime\\': \\'2024-11-19 07:57\\'}, \\'current\\': {\\'last_updated_epoch\\': 1732020300, \\'last_updated\\': \\'2024-11-19 07:45\\', \\'temp_c\\': 8.3, \\'temp_f\\': 46.9, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Sunny\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/113.png\\', \\'code\\': 1000}, \\'wind_mph\\': 7.2, \\'wind_kph\\': 11.5, \\'wind_degree\\': 332, \\'wind_dir\\': \\'NNW\\', \\'pressure_mb\\': 1016.0, \\'pressure_in\\': 29.99, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 60, \\'cloud\\': 0, \\'feelslike_c\\': 6.3, \\'feelslike_f\\': 43.4, \\'windchill_c\\': 4.3, \\'windchill_f\\': 39.8, \\'heatindex_c\\': 7.0, \\'heatindex_f\\': 44.5, \\'dewpoint_c\\': 2.7, \\'dewpoint_f\\': 36.8, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 0.0, \\'gust_mph\\': 10.0, \\'gust_kph\\': 16.2}}\"}]', name='tavily_search_results_json', id='e955ebe9-631f-4dd1-b0a4-ee6a3caeba9f', tool_call_id='call_wZ5w5uO733Cc4CvWbc4Axq5F', artifact={'query': 'current weather in New York City', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'title': 'Weather in New York City', 'url': 'https://www.weatherapi.com/', 'content': \"{'location': {'name': 'New York', 'region': 'New York', 'country': 'United States of America', 'lat': 40.714, 'lon': -74.006, 'tz_id': 'America/New_York', 'localtime_epoch': 1732021037, 'localtime': '2024-11-19 07:57'}, 'current': {'last_updated_epoch': 1732020300, 'last_updated': '2024-11-19 07:45', 'temp_c': 8.3, 'temp_f': 46.9, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 7.2, 'wind_kph': 11.5, 'wind_degree': 332, 'wind_dir': 'NNW', 'pressure_mb': 1016.0, 'pressure_in': 29.99, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 60, 'cloud': 0, 'feelslike_c': 6.3, 'feelslike_f': 43.4, 'windchill_c': 4.3, 'windchill_f': 39.8, 'heatindex_c': 7.0, 'heatindex_f': 44.5, 'dewpoint_c': 2.7, 'dewpoint_f': 36.8, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 0.0, 'gust_mph': 10.0, 'gust_kph': 16.2}}\", 'score': 0.9997275, 'raw_content': None}], 'response_time': 3.24})]}})\n", - "(('web_search_assistant:d08ae326-b6b2-1749-e8ea-4d308f22d819',), {'agent': {'messages': [AIMessage(content='The current weather in New York City is sunny with a temperature of 8.3°C (46.9°F). The wind is coming from the north-northwest at 7.2 mph (11.5 kph), and the humidity level is 60%. The weather feels slightly cooler at 6.3°C (43.4°F) due to the wind chill.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 79, 'prompt_tokens': 535, 'total_tokens': 614, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_159d8341cc', 'finish_reason': 'stop', 'logprobs': None}, id='run-43d275f1-aacb-44f4-bdd0-8233c3765699-0', usage_metadata={'input_tokens': 535, 'output_tokens': 79, 'total_tokens': 614, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}})\n", - "((), {'web_search_assistant': {'messages': [HumanMessage(content=\"what's the weather in nyc?\", additional_kwargs={}, response_metadata={}, id='756466d3-18ce-4b8e-b4fc-ee59932ce9f4'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_wZ5w5uO733Cc4CvWbc4Axq5F', 'function': {'arguments': '{\"query\":\"current weather in New York City\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 23, 'prompt_tokens': 96, 'total_tokens': 119, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_45cf54deae', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-0658af11-b90b-407c-a6a5-6a3b4a9f61e0-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'current weather in New York City'}, 'id': 'call_wZ5w5uO733Cc4CvWbc4Axq5F', 'type': 'tool_call'}], usage_metadata={'input_tokens': 96, 'output_tokens': 23, 'total_tokens': 119, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), ToolMessage(content='[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'New York\\', \\'region\\': \\'New York\\', \\'country\\': \\'United States of America\\', \\'lat\\': 40.714, \\'lon\\': -74.006, \\'tz_id\\': \\'America/New_York\\', \\'localtime_epoch\\': 1732021037, \\'localtime\\': \\'2024-11-19 07:57\\'}, \\'current\\': {\\'last_updated_epoch\\': 1732020300, \\'last_updated\\': \\'2024-11-19 07:45\\', \\'temp_c\\': 8.3, \\'temp_f\\': 46.9, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Sunny\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/113.png\\', \\'code\\': 1000}, \\'wind_mph\\': 7.2, \\'wind_kph\\': 11.5, \\'wind_degree\\': 332, \\'wind_dir\\': \\'NNW\\', \\'pressure_mb\\': 1016.0, \\'pressure_in\\': 29.99, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 60, \\'cloud\\': 0, \\'feelslike_c\\': 6.3, \\'feelslike_f\\': 43.4, \\'windchill_c\\': 4.3, \\'windchill_f\\': 39.8, \\'heatindex_c\\': 7.0, \\'heatindex_f\\': 44.5, \\'dewpoint_c\\': 2.7, \\'dewpoint_f\\': 36.8, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 0.0, \\'gust_mph\\': 10.0, \\'gust_kph\\': 16.2}}\"}]', name='tavily_search_results_json', id='e955ebe9-631f-4dd1-b0a4-ee6a3caeba9f', tool_call_id='call_wZ5w5uO733Cc4CvWbc4Axq5F', artifact={'query': 'current weather in New York City', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'title': 'Weather in New York City', 'url': 'https://www.weatherapi.com/', 'content': \"{'location': {'name': 'New York', 'region': 'New York', 'country': 'United States of America', 'lat': 40.714, 'lon': -74.006, 'tz_id': 'America/New_York', 'localtime_epoch': 1732021037, 'localtime': '2024-11-19 07:57'}, 'current': {'last_updated_epoch': 1732020300, 'last_updated': '2024-11-19 07:45', 'temp_c': 8.3, 'temp_f': 46.9, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 7.2, 'wind_kph': 11.5, 'wind_degree': 332, 'wind_dir': 'NNW', 'pressure_mb': 1016.0, 'pressure_in': 29.99, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 60, 'cloud': 0, 'feelslike_c': 6.3, 'feelslike_f': 43.4, 'windchill_c': 4.3, 'windchill_f': 39.8, 'heatindex_c': 7.0, 'heatindex_f': 44.5, 'dewpoint_c': 2.7, 'dewpoint_f': 36.8, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 0.0, 'gust_mph': 10.0, 'gust_kph': 16.2}}\", 'score': 0.9997275, 'raw_content': None}], 'response_time': 3.24}), AIMessage(content='The current weather in New York City is sunny with a temperature of 8.3°C (46.9°F). The wind is coming from the north-northwest at 7.2 mph (11.5 kph), and the humidity level is 60%. The weather feels slightly cooler at 6.3°C (43.4°F) due to the wind chill.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 79, 'prompt_tokens': 535, 'total_tokens': 614, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-2024-08-06', 'system_fingerprint': 'fp_159d8341cc', 'finish_reason': 'stop', 'logprobs': None}, id='run-43d275f1-aacb-44f4-bdd0-8233c3765699-0', usage_metadata={'input_tokens': 535, 'output_tokens': 79, 'total_tokens': 614, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}})\n" - ] - } - ], - "source": [ - "for chunk in graph.stream(\n", - " {\"messages\": [{\"role\": \"user\", \"content\": \"what's the weather in nyc?\"}]},\n", - " subgraphs=True,\n", + " },\n", + " # highlight-next-line\n", + " config,\n", "):\n", " print(chunk)" ] diff --git a/docs/docs/how-tos/index.md b/docs/docs/how-tos/index.md index 4d16c91f5..e1e966e8b 100644 --- a/docs/docs/how-tos/index.md +++ b/docs/docs/how-tos/index.md @@ -122,7 +122,7 @@ These how-to guides show common patterns for tool calling with LangGraph: See the [multi-agent tutorials](../tutorials/index.md#multi-agent-systems) for implementations of other multi-agent architectures. -See the below guides for how-to implement multi-agent workflows with the (beta) +See the below guides for how to implement multi-agent workflows with the (beta) [Functional API](../concepts/functional_api.md): - [How to build a multi-agent network (functional API)](multi-agent-network-functional.ipynb) @@ -145,6 +145,11 @@ See the below guides for how-to implement multi-agent workflows with the (beta) - [How to return state before hitting recursion limit](return-when-recursion-limit-hits.ipynb) - [How to integrate LangGraph with AutoGen, CrewAI, and other frameworks](autogen-integration.ipynb) +See the below guide for how to integrate with other frameworks using the (beta) +[Functional API](../concepts/functional_api.md): + +- [How to integrate LangGraph (functional API) with AutoGen, CrewAI, and other frameworks](autogen-integration-functional.ipynb) + ### Prebuilt ReAct Agent The LangGraph [prebuilt ReAct agent](../reference/prebuilt.md#langgraph.prebuilt.chat_agent_executor.create_react_agent) is pre-built implementation of a [tool calling agent](../concepts/agentic_concepts.md#tool-calling-agent). diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 99237ea2b..7ecc54c2e 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -187,6 +187,8 @@ nav: - how-tos/react-agent-structured-output.ipynb - how-tos/run-id-langsmith.ipynb - how-tos/return-when-recursion-limit-hits.ipynb + - how-tos/autogen-integration.ipynb + - how-tos/autogen-integration-functional.ipynb - Prebuilt ReAct Agent: - Prebuilt ReAct Agent: how-tos#prebuilt-react-agent - how-tos/create-react-agent.ipynb @@ -207,6 +209,7 @@ nav: - cloud/deployment/custom_docker.md - cloud/deployment/test_locally.md - cloud/deployment/graph_rebuild.md + - how-tos/autogen-langgraph-platform.ipynb - Deployment: - Deployment: how-tos#deployment - cloud/deployment/cloud.md