mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-21 15:12:26 +02:00
173 lines
6.9 KiB
Plaintext
173 lines
6.9 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "95a87145-34d0-4f97-b45f-5c9fd8532c8a",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Map Reduce\n",
|
|
"\n",
|
|
"A common pattern in agents is to generate a list of objects, do some work on each of those objects, and then combine the results. This is very similar to the common [map-reduce](https://en.wikipedia.org/wiki/MapReduce) operation. This can be tricky for a few reasons. First, it can be tough to define a structured graph ahead of time because the length of the list of objects may be unknown. Second, in order to do this map-reduce you need multiple versions of the state to exist... but the graph shares a common shared state, so how can this be?\n",
|
|
"\n",
|
|
"LangGraph supports this via the `Send` api. This can be used to allow a conditional edge to `Send` multiple different states to multiple nodes. The state it sends can be different from the state of the core graph.\n",
|
|
"\n",
|
|
"Let's see what this looks like in action! We'll put together a toy example of generating a list of words, and then writing a joke about each word, and then judging what the best joke is."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"id": "0f0f78e4-423d-4e2d-aa1a-01efaec4715f",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"name": "stdout",
|
|
"output_type": "stream",
|
|
"text": [
|
|
"{'generate_topics': {'subjects': ['cat', 'dog', 'rabbit', 'hamster', 'bird']}}\n",
|
|
"{'generate_joke': {'jokes': ['Why did the rabbit go to the barber shop? Because it needed a hare cut!']}}\n",
|
|
"{'generate_joke': {'jokes': ['Why was the cat sitting on the computer? Because it wanted to keep an eye on the mouse!']}}\n",
|
|
"{'generate_joke': {'jokes': ['Why did the hamster join the band? Because it had great drumming skills!']}}\n",
|
|
"{'generate_joke': {'jokes': [\"Why did the dog sit in the shade? Because he didn't want to be a hot dog!\"]}}\n",
|
|
"{'generate_joke': {'jokes': ['Why did the bird join a band? Because it had the best tweet-talent!']}}\n",
|
|
"{'best_joke': {'best_selected_joke': \"Why did the dog sit in the shade? Because he didn't want to be a hot dog!\"}}\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"import operator\n",
|
|
"from typing import Annotated, TypedDict\n",
|
|
"\n",
|
|
"from langchain_core.pydantic_v1 import BaseModel\n",
|
|
"from langchain_openai import ChatOpenAI\n",
|
|
"\n",
|
|
"from langgraph.constants import Send\n",
|
|
"from langgraph.graph import END, StateGraph\n",
|
|
"\n",
|
|
"# Model and prompts\n",
|
|
"# Define model and prompts we will use\n",
|
|
"subjects_prompt = \"\"\"Generate a comma separated list of between 2 and 5 {topic}.\"\"\"\n",
|
|
"joke_prompt = \"\"\"Generate a joke about {subject}\"\"\"\n",
|
|
"best_joke_prompt = \"\"\"Below are a bunch of jokes about {topic}. Select the best one! Return the ID of the best one.\n",
|
|
"\n",
|
|
"{jokes}\"\"\"\n",
|
|
"\n",
|
|
"\n",
|
|
"class Subjects(BaseModel):\n",
|
|
" subjects: list[str]\n",
|
|
"\n",
|
|
"\n",
|
|
"class Joke(BaseModel):\n",
|
|
" joke: str\n",
|
|
"\n",
|
|
"\n",
|
|
"class BestJoke(BaseModel):\n",
|
|
" id: int\n",
|
|
"\n",
|
|
"\n",
|
|
"model = ChatOpenAI()\n",
|
|
"\n",
|
|
"# Graph components: define the components that will make up the graph\n",
|
|
"\n",
|
|
"\n",
|
|
"# This will be the overall state of the main graph.\n",
|
|
"# It will contain a topic (which we expect the user to provide)\n",
|
|
"# and then will generate a list of subjects, and then a joke for\n",
|
|
"# each subject\n",
|
|
"class OverallState(TypedDict):\n",
|
|
" topic: str\n",
|
|
" subjects: list\n",
|
|
" # Notice here we use the operator.add\n",
|
|
" # This is because we want combine all the jokes we generate\n",
|
|
" # from individual nodes back into one list - this is essentially\n",
|
|
" # the \"reduce\" part\n",
|
|
" jokes: Annotated[list, operator.add]\n",
|
|
" best_selected_joke: str\n",
|
|
"\n",
|
|
"\n",
|
|
"# This will be the state of the node that we will \"map\" all\n",
|
|
"# subjects to in order to generate a joke\n",
|
|
"class JokeState(TypedDict):\n",
|
|
" subject: str\n",
|
|
"\n",
|
|
"\n",
|
|
"# This is the function we will use to generate the subjects of the jokes\n",
|
|
"def generate_topics(state: OverallState):\n",
|
|
" prompt = subjects_prompt.format(topic=state[\"topic\"])\n",
|
|
" response = model.with_structured_output(Subjects).invoke(prompt)\n",
|
|
" return {\"subjects\": response.subjects}\n",
|
|
"\n",
|
|
"\n",
|
|
"# Here we generate a joke, given a subject\n",
|
|
"def generate_joke(state: JokeState):\n",
|
|
" prompt = joke_prompt.format(subject=state[\"subject\"])\n",
|
|
" response = model.with_structured_output(Joke).invoke(prompt)\n",
|
|
" return {\"jokes\": [response.joke]}\n",
|
|
"\n",
|
|
"\n",
|
|
"# Here we define the logic to map out over the generated subjects\n",
|
|
"# We will use this an edge in the graph\n",
|
|
"def continue_to_jokes(state: OverallState):\n",
|
|
" # We will return a list of `Send` objects\n",
|
|
" # Each `Send` object consists of the name of a node in the graph\n",
|
|
" # as well as the state to send to that node\n",
|
|
" return [Send(\"generate_joke\", {\"subject\": s}) for s in state[\"subjects\"]]\n",
|
|
"\n",
|
|
"\n",
|
|
"# Here we will judge the best joke\n",
|
|
"def best_joke(state: OverallState):\n",
|
|
" jokes = \"\\n\\n\".format()\n",
|
|
" prompt = best_joke_prompt.format(topic=state[\"topic\"], jokes=jokes)\n",
|
|
" response = model.with_structured_output(BestJoke).invoke(prompt)\n",
|
|
" return {\"best_selected_joke\": state[\"jokes\"][response.id]}\n",
|
|
"\n",
|
|
"\n",
|
|
"# Construct the graph: here we put everything together to construct our graph\n",
|
|
"graph = StateGraph(OverallState)\n",
|
|
"graph.add_node(\"generate_topics\", generate_topics)\n",
|
|
"graph.add_node(\"generate_joke\", generate_joke)\n",
|
|
"graph.add_node(\"best_joke\", best_joke)\n",
|
|
"graph.set_entry_point(\"generate_topics\")\n",
|
|
"graph.add_conditional_edges(\"generate_topics\", continue_to_jokes)\n",
|
|
"graph.add_edge(\"generate_joke\", \"best_joke\")\n",
|
|
"graph.add_edge(\"best_joke\", END)\n",
|
|
"app = graph.compile()\n",
|
|
"\n",
|
|
"\n",
|
|
"# Call the graph: here we call it to generate a list of jokes\n",
|
|
"for s in app.stream({\"topic\": \"animals\"}):\n",
|
|
" print(s)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "37ed1f71-63db-416f-b715-4617b33d4b7f",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": []
|
|
}
|
|
],
|
|
"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.11.1"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|