mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-01 04:39:01 +02:00
70 KiB
70 KiB
In [ ]:
# %%capture --no-stderr
# %pip install -U langchain langchain_openai langchain_experimental langsmith pandasIn [1]:
import getpass
import os
def _set_if_undefined(var: str):
if not os.environ.get(var):
os.environ[var] = getpass(f"Please provide your {var}")
_set_if_undefined("OPENAI_API_KEY")
_set_if_undefined("LANGCHAIN_API_KEY")
_set_if_undefined("TAVILY_API_KEY")
# Optional, add tracing in LangSmith
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_PROJECT"] = "Multi-agent Collaboration"In [4]:
from typing import Annotated, List, Tuple, Union
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.tools import tool
from langchain_experimental.tools import PythonREPLTool
tavily_tool = TavilySearchResults(max_results=5)
# This executes code locally, which can be unsafe
python_repl_tool = PythonREPLTool()In [3]:
from langchain_core.messages import BaseMessage, HumanMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import END, StateGraph
def create_worker_node(
workflow: StateGraph, name: str, llm: ChatOpenAI, tools: list, system_prompt: str
):
# Each worker node will be given a name and some tools.
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
system_prompt,
),
MessagesPlaceholder(variable_name="messages"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
]
)
agent = create_openai_functions_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools)
chain = executor | (
# So the agents properly role-play in this simulation, we will
# tag their final message as a human message
lambda x: {"messages": [HumanMessage(content=x["output"], name=name)]}
)
workflow.add_node(name, chain)In [18]:
import operator
from typing import Annotated, Any, Dict, List, Optional, Sequence, TypedDict
from langchain.agents import AgentExecutor, create_openai_functions_agent
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
# The agent state is the input to each node in the graph
class AgentState(TypedDict):
# The annotation tells the graph that new messages will always
# be added to the current states
messages: Annotated[Sequence[BaseMessage], operator.add]
# The 'next' field indicates where to route to next
next: str
workflow = StateGraph(AgentState)
llm = ChatOpenAI(model="gpt-4-1106-preview")
create_worker_node(
workflow, "Researcher", llm, [tavily_tool], "You are a web researcher."
)
# NOTE: THIS PERFORMS ARBITRARY CODE EXECUTION. PROCEED WITH CAUTION
create_worker_node(
workflow,
"Coder",
llm,
[python_repl_tool],
"You may generate safe python code to analyze data "
"and generate charts using matplotlib.",
)In [19]:
from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser
members = ["Researcher", "Coder"]
system_prompt = (
"You are a supervisor tasked with managing a conversation between the"
" following workers: {members}. Given the following user request,"
" respond with the worker to act next. Each worker will perform a"
" task and respond with their results and status. When finished,"
" respond with FINISH."
)
# Our team supervisor is an LLM node. It just picks the next agent to process
# and decides when the work is completed
options = ["FINISH"] + members
# Using openai function calling can make output parsing easier for us
function_def = {
"name": "route",
"description": "Select the next role.",
"parameters": {
"title": "routeSchema",
"type": "object",
"properties": {
"next": {
"title": "Next",
"anyOf": [
{"enum": options},
],
}
},
"required": ["next"],
},
}
prompt = ChatPromptTemplate.from_messages(
[
("system", system_prompt),
MessagesPlaceholder(variable_name="messages"),
(
"system",
"Given the conversation above, who should act next?"
" Or should we FINISH? Select one of: {options}",
),
]
).partial(options=str(options), members=", ".join(members))
supervisor_chain = (
prompt
| llm.bind_functions(functions=[function_def], function_call="route")
| JsonOutputFunctionsParser()
)In [20]:
workflow.add_node("supervisor", supervisor_chain)
for member in members:
# We want our workers to ALWAYS "report back" to the supervisor when done
workflow.add_edge(member, "supervisor")
# The supervisor populates the "next" field in the graph state
# which routes to a node or finishes
conditional_map = {k: k for k in members}
conditional_map["FINISH"] = END
workflow.add_conditional_edges("supervisor", lambda x: x["next"], conditional_map)
# Finally, add entrypoint
workflow.set_entry_point("supervisor")
graph = workflow.compile()In [21]:
results = graph.invoke(
{
"messages": [
HumanMessage(content="Code hello world and print it to the terminal")
]
}
)
results["messages"][-1].pretty_print()Python REPL can execute arbitrary code. Use with caution.
================================[1m Human Message [0m================================= The "Hello, World!" message has been successfully printed to the terminal.
In [22]:
results = graph.invoke(
{
"messages": [
HumanMessage(
content="Write a research summary of CA wildfires in 2023. Include a chart."
)
]
}
)
results["messages"][-1].pretty_print()================================[1m Human Message [0m================================= The chart has been successfully created and saved as "california_wildfires_comparison.png". This chart compares the acres burned by California wildfires in 2023 to the five-year average for each month. Unfortunately, I'm unable to display the image directly to you here, but the chart visualizes the data with the following characteristics: - The X-axis represents the months of the year from January to December. - The Y-axis indicates the acres burned. - A line graph shows the cumulative acres burned in 2023, marked with circles. - Another line graph represents the five-year average cumulative acres burned by month, marked with x's and a dashed line. - The chart includes a legend to distinguish between the two lines and is titled "California Wildfires: Acres Burned in 2023 vs 5-Year Average". You would need to view the "california_wildfires_comparison.png" file using an image viewer to see the actual chart.
In [ ]:
