课程介绍
课程:AI Agents in LangGraph - DeepLearning.AI
代码库:https://github.com/ksm26/AI-Agents-in-LangGraph
课程笔记
# Lesson 1: Simple ReAct Agent from Scratch
import openai
import re
import httpx
import os
from dotenv import load_dotenv
_ = load_dotenv()
from openai import OpenAI
client = OpenAI()
class Agent:
def __init__(self, system=""):
self.system = system
self.messages = []
if self.system:
self.messages.append({"role": "system", "content": system})
def __call__(self, message):
self.messages.append({"role": "user", "content": message})
result = self.execute()
self.messages.append({"role": "assistant", "content": result})
return result
def execute(self):
completion = client.chat.completions.create(
model="gpt-4o",
temperature=0,
messages=self.messages)
return completion.choices[0].message.content
prompt = """
You run in a loop of Thought, Action, PAUSE, Observation.
At the end of the loop you output an Answer
Use Thought to describe your thoughts about the question you have been asked.
Use Action to run one of the actions available to you - then return PAUSE.
Observation will be the result of running those actions.
Your available actions are:
calculate:
e.g. calculate: 4 * 7 / 3
Runs a calculation and returns the number - uses Python so be sure to use floating point syntax if necessary
average_dog_weight:
e.g. average_dog_weight: Collie
returns average weight of a dog when given the breed
Example session:
Question: How much does a Bulldog weigh?
Thought: I should look the dogs weight using average_dog_weight
Action: average_dog_weight: Bulldog
PAUSE
You will be called again with this:
Observation: A Bulldog weights 51 lbs
You then output:
Answer: A bulldog weights 51 lbs
""".strip()
def calculate(what):
return eval(what)
def average_dog_weight(name):
if name in "Scottish Terrier":
return("Scottish Terriers average 20 lbs")
elif name in "Border Collie":
return("a Border Collies average weight is 37 lbs")
elif name in "Toy Poodle":
return("a toy poodles average weight is 7 lbs")
else:
return("An average dog weights 50 lbs")
known_actions = {
"calculate": calculate,
"average_dog_weight": average_dog_weight
}
action_re = re.compile('^Action: (\w+): (.*)$') # python regular expression to selection action
def query(question, max_turns=5):
i = 0
bot = Agent(prompt)
next_prompt = question
while i < max_turns:
i += 1
result = bot(next_prompt)
print(result)
actions = [
action_re.match(a)
for a in result.split('\n')
if action_re.match(a)
]
if actions:
# There is an action to run
action, action_input = actions[0].groups()
if action not in known_actions:
raise Exception("Unknown action: {}: {}".format(action, action_input))
print(" -- running {} {}".format(action, action_input))
observation = known_actions[action](action_input)
print("Observation:", observation)
next_prompt = "Observation: {}".format(observation)
else:
return
question = """I have 2 dogs, a border collie and a scottish terrier. \
What is their combined weight"""
query(question)
执行结果:
# Lesson 2 : LangGraph Components
#!/usr/bin/env python
# coding: utf-8
from dotenv import load_dotenv
_ = load_dotenv()
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
import operator
from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage, ToolMessage
from langchain_openai import ChatOpenAI
from langchain_community.tools.tavily_search import TavilySearchResults
tool = TavilySearchResults(max_results=4) #increased number of results
print(type(tool))
print(tool.name)
class AgentState(TypedDict):
messages: Annotated[list[AnyMessage], operator.add]
class Agent:
def __init__(self, model, tools, system=""):
self.system = system
graph = StateGraph(AgentState)
graph.add_node("llm", self.call_openai)
graph.add_node("action", self.take_action)
graph.add_conditional_edges(
"llm",
self.exists_action,
{True: "action", False: END}
)
graph.add_edge("action", "llm")
graph.set_entry_point("llm")
self.graph = graph.compile()
self.tools = {t.name: t for t in tools}
self.model = model.bind_tools(tools)
def exists_action(self, state: AgentState):
result = state['messages'][-1]
return len(result.tool_calls) > 0
def call_openai(self, state: AgentState):
messages = state['messages']
if self.system:
messages = [SystemMessage(content=self.system)] + messages
message = self.model.invoke(messages)
return {'messages': [message]}
def take_action(self, state: AgentState):
tool_calls = state['messages'][-1].tool_calls
results = []
for t in tool_calls:
print(f"Calling: {t}")
if not t['name'] in self.tools: # check for bad tool name from LLM
print("\n ....bad tool name....")
result = "bad tool name, retry" # instruct LLM to retry if bad
else:
result = self.tools[t['name']].invoke(t['args'])
results.append(ToolMessage(tool_call_id=t['id'], name=t['name'], content=str(result)))
print("Back to the model!")
return {'messages': results}
prompt = """你是个聪明的研究助理。使用搜索引擎查找信息。 \
您可以拨打多个电话(一起或按顺序)。 \
只有当你确定自己想要什么时,才能查找信息。 \
如果你需要在提出后续问题之前查找一些信息,你可以这样做!
"""
query = "谁赢得了2024年的超级碗?获胜团队总部位于哪个州? \
该州的国内生产总值是多少?回答每一个问题。"
messages = [HumanMessage(content=query)]
model = ChatOpenAI(model="gpt-4o") # requires more advanced model
abot = Agent(model, [tool], system=prompt)
result = abot.graph.invoke({"messages": messages})
print(result['messages'][-1].content)
执行结果:
# Lesson 3: Agentic Search
# libraries
from dotenv import load_dotenv
import os
from tavily import TavilyClient
# load environment variables from .env file
_ = load_dotenv()
# connect
client = TavilyClient(api_key=os.environ.get("TAVILY_API_KEY"))
# run search
result = client.search("北京天气如何?请用中文回答",
include_answer=True)
# print the answer
result["answer"]
# libraries
from dotenv import load_dotenv
import os
from tavily import TavilyClient
# load environment variables from .env file
_ = load_dotenv()
# connect
client = TavilyClient(api_key=os.environ.get("TAVILY_API_KEY"))
import json
from pygments import highlight, lexers, formatters
query = f"""
北京天气如何?
"weather.com"
"""
# run search
result = client.search(query, max_results=1)
# print first result
data = result["results"][0]["content"]
print(data)
# parse JSON
parsed_json = json.loads(data.replace("'", '"'))
# pretty print JSON with syntax highlighting
formatted_json = json.dumps(parsed_json, indent=4)
colorful_json = highlight(formatted_json,
lexers.JsonLexer(),
formatters.TerminalFormatter())
print(colorful_json)
# Lesson 4: Persistence and Streaming
from dotenv import load_dotenv
_ = load_dotenv()
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
import operator
from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage, ToolMessage
from langchain_openai import ChatOpenAI
from langchain_community.tools.tavily_search import TavilySearchResults
tool = TavilySearchResults(max_results=2)
class AgentState(TypedDict):
messages: Annotated[list[AnyMessage], operator.add]
from langgraph.checkpoint.sqlite import SqliteSaver
memory = SqliteSaver.from_conn_string(":memory:")
class Agent:
def __init__(self, model, tools, checkpointer, system=""):
self.system = system
graph = StateGraph(AgentState)
graph.add_node("llm", self.call_openai)
graph.add_node("action", self.take_action)
graph.add_conditional_edges("llm", self.exists_action, {True: "action", False: END})
graph.add_edge("action", "llm")
graph.set_entry_point("llm")
self.graph = graph.compile(checkpointer=checkpointer)
self.tools = {t.name: t for t in tools}
self.model = model.bind_tools(tools)
def call_openai(self, state: AgentState):
messages = state['messages']
if self.system:
messages = [SystemMessage(content=self.system)] + messages
message = self.model.invoke(messages)
return {'messages': [message]}
def exists_action(self, state: AgentState):
result = state['messages'][-1]
return len(result.tool_calls) > 0
def take_action(self, state: AgentState):
tool_calls = state['messages'][-1].tool_calls
results = []
for t in tool_calls:
print(f"Calling: {t}")
result = self.tools[t['name']].invoke(t['args'])
results.append(ToolMessage(tool_call_id=t['id'], name=t['name'], content=str(result)))
print("Back to the model!")
return {'messages': results}
prompt = """You are a smart research assistant. Use the search engine to look up information. \
You are allowed to make multiple calls (either together or in sequence). \
Only look up information when you are sure of what you want. \
If you need to look up some information before asking a follow up question, you are allowed to do that!
"""
model = ChatOpenAI(model="gpt-4o")
# 持久知识Persistence
abot = Agent(model, [tool], system=prompt, checkpointer=memory)
messages = [HumanMessage(content="What about in sf?")]
thread = {"configurable": {"thread_id": "1"}}
for event in abot.graph.stream({"messages": messages}, thread):
for v in event.values():
print(v)
messages = [HumanMessage(content="What about in la?")]
thread = {"configurable": {"thread_id": "1"}}
for event in abot.graph.stream({"messages": messages}, thread):
for v in event.values():
print(v)
messages = [HumanMessage(content="Which one is warmer?")]
thread = {"configurable": {"thread_id": "1"}}
for event in abot.graph.stream({"messages": messages}, thread):
for v in event.values():
print(v)
# checkpointer 同步流式输出
from langgraph.checkpoint.aiosqlite import AsyncSqliteSaver
memory = AsyncSqliteSaver.from_conn_string(":memory:")
abot = Agent(model, [tool], system=prompt, checkpointer=memory)
messages = [HumanMessage(content="What is the weather in SF?")]
thread = {"configurable": {"thread_id": "4"}}
async for event in abot.graph.astream_events({"messages": messages}, thread, version="v1"):
kind = event["event"]
if kind == "on_chat_model_stream":
content = event["data"]["chunk"].content
if content:
# Empty content in the context of OpenAI means
# that the model is asking for a tool to be invoked.
# So we only print non-empty content
print(content, end="|")
# 持久知识Persistence
# checkpointer 同步流式输出
# Lesson 5: Human in the Loop
使用get_state()或get_state_history()时,其他状态信息会存储到内存中并显示出来。
每次状态转换都会额外存储状态,而之前它是在中断或结束时存储的。这些命令稍微改变了命令输出,但对可用信息来说是一个有用的补充。
from dotenv import load_dotenv
_ = load_dotenv()
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
import operator
from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage, ToolMessage
from langchain_openai import ChatOpenAI
from langchain_community.tools.tavily_search import TavilySearchResults
from langgraph.checkpoint.sqlite import SqliteSaver
memory = SqliteSaver.from_conn_string(":memory:")
from uuid import uuid4
from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage, AIMessage
"""
在前面的例子中,我们用默认的`operator.add`或`+` reducer注释了`messages'状态键,它总是将新消息附加到现有消息数组的末尾。
现在,为了支持替换现有消息,我们用客户缩减函数注释“messages”键,该函数用相同的“id”替换消息,并以其他方式附加它们。
"""
def reduce_messages(left: list[AnyMessage], right: list[AnyMessage]) -> list[AnyMessage]:
# assign ids to messages that don't have them
for message in right:
if not message.id:
message.id = str(uuid4())
# merge the new messages with the existing messages
merged = left.copy()
for message in right:
for i, existing in enumerate(merged):
# replace any existing messages with the same id
if existing.id == message.id:
merged[i] = message
break
else:
# append any new messages to the end
merged.append(message)
return merged
class AgentState(TypedDict):
messages: Annotated[list[AnyMessage], reduce_messages]
tool = TavilySearchResults(max_results=2)
class Agent:
def __init__(self, model, tools, system="", checkpointer=None):
self.system = system
graph = StateGraph(AgentState)
graph.add_node("llm", self.call_openai)
graph.add_node("action", self.take_action)
graph.add_conditional_edges("llm", self.exists_action, {True: "action", False: END})
graph.add_edge("action", "llm")
graph.set_entry_point("llm")
self.graph = graph.compile(
checkpointer=checkpointer,
interrupt_before=["action"] # 新增的中断点
)
self.tools = {t.name: t for t in tools}
self.model = model.bind_tools(tools)
def call_openai(self, state: AgentState):
messages = state['messages']
if self.system:
messages = [SystemMessage(content=self.system)] + messages
message = self.model.invoke(messages)
return {'messages': [message]}
def exists_action(self, state: AgentState):
print(state)
result = state['messages'][-1]
return len(result.tool_calls) > 0
def take_action(self, state: AgentState):
tool_calls = state['messages'][-1].tool_calls
results = []
for t in tool_calls:
print(f"Calling: {t}")
result = self.tools[t['name']].invoke(t['args'])
results.append(ToolMessage(tool_call_id=t['id'], name=t['name'], content=str(result)))
print("Back to the model!")
return {'messages': results}
## Manual human approval 打断点人工审核
prompt = """你是个聪明的研究助理。使用搜索引擎查找信息。
你可以支持多个调用(一起或按顺序)。
只有当你确定自己想要什么时,才能查找信息。
如果你需要在提出后续问题之前查找一些信息,你可以这样做!
"""
model = ChatOpenAI(model="gpt-3.5-turbo")
messages = [HumanMessage(content="Whats the weather in SF?")]
thread = {"configurable": {"thread_id": "1"}}
for event in abot.graph.stream({"messages": messages}, thread):
for v in event.values():
print(v)
abot.graph.get_state(thread) # 获取thread状态 # 输出快照 StateSnapshot(...)
abot.graph.get_state(thread).next # 获取下一个thread状态 # 输出('action',)
abot = Agent(model, [tool], system=prompt, checkpointer=memory)
## continue after interrupt 断点处继续执行
for event in abot.graph.stream(None, thread):
for v in event.values():
print(v)
abot.graph.get_state(thread).next # 获取下一个thread状态 # 输出()
## 人工干预后继续执行
messages = [HumanMessage("Whats the weather in LA?")]
thread = {"configurable": {"thread_id": "3"}}
for event in abot.graph.stream({"messages": messages}, thread):
for v in event.values():
print(v)
while abot.graph.get_state(thread).next:
print("\n", abot.graph.get_state(thread),"\n")
_input = input("proceed?")
if _input != "y":
print("aborting")
break
for event in abot.graph.stream(None, thread):
for v in event.values():
print(v)
## Modify State 断点处修改状态
messages = [HumanMessage("Whats the weather in LA?")]
thread = {"configurable": {"thread_id": "4"}}
for event in abot.graph.stream({"messages": messages}, thread):
for v in event.values():
print(v)
abot.graph.get_state(thread)
current_values = abot.graph.get_state(thread)
current_values.values['messages'][-1]
current_values.values['messages'][-1].tool_calls
_id = current_values.values['messages'][-1].tool_calls[0]['id']
current_values.values['messages'][-1].tool_calls = [
{'name': 'tavily_search_results_json',
'args': {'query': 'current weather in Louisiana'},
'id': _id}
]
abot.graph.update_state(thread, current_values.values)
abot.graph.get_state(thread)
for event in abot.graph.stream(None, thread):
for v in event.values():
print(v)
## Time Travel 可以回到之前的任意状态
states = []
for state in abot.graph.get_state_history(thread):
print(state)
print('--')
states.append(state)
#为了获取与拍摄时相同的状态,下面的偏移量从-1更改为-3。这解释了初始状态__start__和现在使用最新版本的软件存储到状态存储器中的第一个状态。
to_replay = states[-3]
for event in abot.graph.stream(None, to_replay.config):
for k, v in event.items():
print(v)
## Go back in time and edit 回到历史状态并修改天气信息获取来源
_id = to_replay.values['messages'][-1].tool_calls[0]['id']
to_replay.values['messages'][-1].tool_calls = [{'name': 'tavily_search_results_json',
'args': {'query': 'current weather in LA, accuweather'},
'id': _id}]
branch_state = abot.graph.update_state(to_replay.config, to_replay.values)
for event in abot.graph.stream(None, branch_state):
for k, v in event.items():
if k != "__end__":
print(v)
# 返回结果中:ToolMessage(content="[{'url': 'https://www.accuweather.com/en/us/los-angeles/90012/daily-weather-forecast/347625'
## Add message to a state at a given time 更新状态中的返回结果,而不是tool
_id = to_replay.values['messages'][-1].tool_calls[0]['id']
state_update = {"messages": [ToolMessage(
tool_call_id=_id,
name="tavily_search_results_json",
content="54 degree celcius",
)]}
branch_and_add = abot.graph.update_state(
to_replay.config,
state_update,
as_node="action")
for event in abot.graph.stream(None, branch_and_add):
for k, v in event.items():
print(v)
# 返回结果中:The current weather in Los Angeles is 54 degrees Celsius.
获取历史状态:
获取状态中的工具:
修改状态中的工具输入:
修改返回结果内容:
# Lesson 5: 随堂练习 Build a small graph
# 这是一个简单的示例,如果你想更深入地了解控制状态存储,你可以修改它。
from dotenv import load_dotenv
_ = load_dotenv()
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
import operator
from langgraph.checkpoint.sqlite import SqliteSaver
class AgentState(TypedDict):
# 定义一个具有以下状态的简单2节点图:
# -lnode:last node
# -scratch:scratchpad位置
# -count:每一步递增的计数器
lnode: str
scratch: str
count: Annotated[int, operator.add]
def node1(state: AgentState):
print(f"node1, count:{state['count']}")
return {"lnode": "node_1",
"count": 1,
}
def node2(state: AgentState):
print(f"node2, count:{state['count']}")
return {"lnode": "node_2",
"count": 1,
}
def should_continue(state):
# The graph goes N1->N2->N1... but breaks after count reaches 3.
return state["count"] < 3
builder = StateGraph(AgentState)
builder.add_node("Node1", node1)
builder.add_node("Node2", node2)
builder.add_edge("Node1", "Node2")
builder.add_conditional_edges("Node2",
should_continue,
{True: "Node1", False: END})
builder.set_entry_point("Node1")
memory = SqliteSaver.from_conn_string(":memory:")
graph = builder.compile(checkpointer=memory)
thread = {"configurable": {"thread_id": str(1)}}
graph.invoke({"count":0, "scratch":"hi"},thread)
# 获取当前状态。注意代理状态的值。请注意配置和thread_ts。您将使用它们来引用下面的快照。
# 查看内存中的所有状态快照。您可以使用显示的计数代理状态变量来帮助跟踪您看到的内容。请注意,迭代器首先返回最新的快照。还要注意,元数据中有一个方便的step变量,用于计算图执行中的步骤数。这有点详细,但您也可以注意到parent_config是前一个节点的配置。在初始启动时,将其他状态插入内存以创建父级。这是您在以下分支或时间旅行时需要检查的内容。
graph.get_state(thread) # Look at current state
# 查看历史状态(从新到旧)
for state in graph.get_state_history(thread):
print(state, "\n")
# Store just the config into an list
states = []
for state in graph.get_state_history(thread):
states.append(state.config)
print(state.config, state.values['count'])
states[-3] # 仅配置
graph.get_state(states[-3]) # 状态信息
# Go Back in Time (Use that state in invoke to go back in time.)
graph.invoke(None, states[-3]) # 以states[-3]为当前状态继续执行 count2-4
thread = {"configurable": {"thread_id": str(1)}}
for state in graph.get_state_history(thread): # 输出所有状态config,count 0-4-2-4
print(state.config, state.values['count'])
thread = {"configurable": {"thread_id": str(1)}}
for state in graph.get_state_history(thread): # 输出所有状态
print(state,"\n")
# 修改状态(用新的thread_id)
thread2 = {"configurable": {"thread_id": str(2)}}
graph.invoke({"count":0, "scratch":"hi"},thread2)
from IPython.display import Image
Image(graph.get_graph().draw_png()) # 图示graph结构
save_state = graph.get_state(states2[-3])
save_state.values["count"] = -3
save_state.values["scratch"] = "hello"
graph.update_state(thread2,save_state.values)
for i, state in enumerate(graph.get_state_history(thread2)):
if i >= 3: #print latest 3
break
print(state, '\n') # 多了一条刚刚更新的记录writes.count为-3
## 使用as_node更新state
# 当使用update_state()进行编写时,您希望在图逻辑中定义哪个节点应被假定为编写器。这允许图逻辑在图上找到节点。写入值后,通过使用新状态遍历图来计算next()值。在这种情况下,我们的状态是由Node1编写的。然后,该图可以将下一个状态计算为Node2。请注意,在某些图中,这可能涉及遍历条件边!让我们试试这个。
graph.update_state(thread2,save_state.values, as_node="Node1")
for i, state in enumerate(graph.get_state_history(thread2)):
if i >= 3: #print latest 3
break
print(state, '\n') # 多了一条刚刚更新的记录writes.count为-2, node1
graph.invoke(None,thread2) # 以thread2为当前状态继续执行 count-2~3
for state in graph.get_state_history(thread2): # count0~4~-2~3
print(state,"\n")
# Lesson 6: Essay Writer
思路:
## 论文写作agent
from dotenv import load_dotenv
_ = load_dotenv()
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated, List
import operator
from langgraph.checkpoint.sqlite import SqliteSaver
from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage, AIMessage, ChatMessage
memory = SqliteSaver.from_conn_string(":memory:")
class AgentState(TypedDict):
task: str
plan: str
draft: str
critique: str
content: List[str]
revision_number: int
max_revisions: int
from langchain_openai import ChatOpenAI
model = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
PLAN_PROMPT = """你是一位论文写作专家,任务是写一篇高水平的文章大纲。 \
写一个大纲为用户提供的主题并给出文章的提纲以及任何相关的注释或章节说明。"""
WRITER_PROMPT = """你是一名论文助理,负责撰写优秀的5段式论文。
根据用户的要求和初步大纲,尽可能地生成最佳论文。
如果用户提供了评论,请回复您之前尝试的修改版本。
根据需要使用以下所有信息:
------
{content}"""
REFLECTION_PROMPT = """你是一名老师,正在批改一篇论文。
为用户提交的内容生成评论和建议。
提供详细的建议,包括长度、深度、风格等要求。"""
RESEARCH_PLAN_PROMPT = """您是一名研究人员,负责提供撰写以下文章时可以使用的信息。生成一个搜索查询列表,以收集任何相关信息。最多只生成3个查询。"""
RESEARCH_CRITIQUE_PROMPT = """您是一名研究人员,负责提供在进行任何要求的修订时可以使用的信息(如下所述)。 \
生成一个搜索查询列表,以收集任何相关信息。最多只生成3个查询。
"""
from langchain_core.pydantic_v1 import BaseModel
class Queries(BaseModel):
queries: List[str]
from tavily import TavilyClient
import os
tavily = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])
def plan_node(state: AgentState):
messages = [
SystemMessage(content=PLAN_PROMPT),
HumanMessage(content=state['task'])
]
response = model.invoke(messages)
return {"plan": response.content}
def research_plan_node(state: AgentState):
queries = model.with_structured_output(Queries).invoke([
SystemMessage(content=RESEARCH_PLAN_PROMPT),
HumanMessage(content=state['task'])
])
content = state['content'] or []
for q in queries.queries:
response = tavily.search(query=q, max_results=2)
for r in response['results']:
content.append(r['content'])
return {"content": content}
def generation_node(state: AgentState):
content = "\n\n".join(state['content'] or [])
user_message = HumanMessage(
content=f"{state['task']}\n\nHere is my plan:\n\n{state['plan']}")
messages = [
SystemMessage(
content=WRITER_PROMPT.format(content=content)
),
user_message
]
response = model.invoke(messages)
return {
"draft": response.content,
"revision_number": state.get("revision_number", 1) + 1
}
def reflection_node(state: AgentState):
messages = [
SystemMessage(content=REFLECTION_PROMPT),
HumanMessage(content=state['draft'])
]
response = model.invoke(messages)
return {"critique": response.content}
def research_critique_node(state: AgentState):
queries = model.with_structured_output(Queries).invoke([
SystemMessage(content=RESEARCH_CRITIQUE_PROMPT),
HumanMessage(content=state['critique'])
])
content = state['content'] or []
for q in queries.queries:
response = tavily.search(query=q, max_results=2)
for r in response['results']:
content.append(r['content'])
return {"content": content}
def should_continue(state):
if state["revision_number"] > state["max_revisions"]:
return END
return "reflect"
builder = StateGraph(AgentState)
builder.add_node("planner", plan_node)
builder.add_node("generate", generation_node)
builder.add_node("reflect", reflection_node)
builder.add_node("research_plan", research_plan_node)
builder.add_node("research_critique", research_critique_node)
builder.set_entry_point("planner")
builder.add_conditional_edges(
"generate",
should_continue,
{END: END, "reflect": "reflect"}
)
builder.add_edge("planner", "research_plan")
builder.add_edge("research_plan", "generate")
builder.add_edge("reflect", "research_critique")
builder.add_edge("research_critique", "generate")
graph = builder.compile(checkpointer=memory)
from IPython.display import Image
Image(graph.get_graph().draw_png())
thread = {"configurable": {"thread_id": "1"}}
for s in graph.stream({
'task': "what is the difference between langchain and langsmith",
"max_revisions": 2,
"revision_number": 1,
}, thread):
print(s)
## Essay Writer Interface
import warnings
warnings.filterwarnings("ignore")
from helper import ewriter, writer_gui
MultiAgent = ewriter()
app = writer_gui(MultiAgent.graph)
app.launch()
引申知识
如何使用 Python 和 LLM 从零开始构建一个代理,然后使用 LangGraph 重建它
构建一个代理(Agent)需要涉及多个步骤,包括定义代理的行为、创建代理的模型、训练代理等。以下是使用 Python 和 LLM 从零开始构建一个代理的步骤:
步骤 1:定义代理的行为
代理的行为是指代理在特定情况下应该做出的动作。例如,一个聊天代理可能需要回答用户的问题,或者一个游戏代理可能需要做出游戏中的动作。
在 Python 中,可以使用函数或类来定义代理的行为。例如:
def answer_question(question):
# 处理问题并返回答案
pass
class Agent:
def __init__(self):
self.behaviors = [answer_question]
def act(self, input):
# 根据输入选择行为并执行
pass
步骤 2:创建代理的模型
代理的模型是指代理的内部状态和行为的表示。例如,一个聊天代理可能需要存储用户的对话历史,或者一个游戏代理可能需要存储游戏状态。
在 Python 中,可以使用类或结构体来创建代理的模型。例如:
class AgentModel:
def __init__(self):
self.conversation_history = []
self.game_state = None
步骤 3:训练代理
训练代理是指使用数据和算法来更新代理的模型和行为。例如,一个聊天代理可能需要使用用户的对话历史来训练回答问题的行为。
在 Python 中,可以使用机器学习库(如 scikit-learn 或 TensorFlow)来训练代理。例如:
from sklearn.ensemble import RandomForestClassifier
class AgentTrainer:
def __init__(self, agent_model):
self.agent_model = agent_model
self.classifier = RandomForestClassifier()
def train(self, data):
# 使用数据训练分类器
self.classifier.fit(data)
步骤 4:使用 LangGraph 重建代理
LangGraph 是一个用于构建和训练代理的库。可以使用 LangGraph 来重建代理的模型和行为。
在 Python 中,可以使用 LangGraph 库来重建代理。例如:
import langgraph
class LangGraphAgent:
def __init__(self):
self.lang_graph = langgraph.LangGraph()
def act(self, input):
# 使用 LangGraph 来选择行为并执行
pass
以下是使用 LangGraph 重建代理的示例代码:
import langgraph
# 定义代理的行为
def answer_question(question):
# 处理问题并返回答案
pass
# 创建代理的模型
class AgentModel:
def __init__(self):
self.conversation_history = []
self.game_state = None
# 训练代理
class AgentTrainer:
def __init__(self, agent_model):
self.agent_model = agent_model
self.classifier = RandomForestClassifier()
def train(self, data):
# 使用数据训练分类器
self.classifier.fit(data)
# 使用 LangGraph 重建代理
class LangGraphAgent:
def __init__(self):
self.lang_graph = langgraph.LangGraph()
def act(self, input):
# 使用 LangGraph 来选择行为并执行
pass
# 创建代理实例
agent = LangGraphAgent()
# 训练代理
trainer = AgentTrainer(agent)
trainer.train(data)
# 使用代理
agent.act(input)