langchain 进阶 04-Flow

工作流是结合一个或多个代理、数据连接器和其他工具来完成任务的多步骤流程。它们是事件驱动型软件,允许您将 RAG 数据源和多个代理组合在一起,以创建一个复杂的应用程序,该应用程序可以通过反射、纠错和高级 LLM 应用程序的其他特征来执行各种任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
from typing import Annotated, Literal, TypedDict
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import END,START,StateGraph,MessagesState
from langgraph.prebuilt import ToolNode
import requests
from api_key import GAODE_WEATHER_API_KEY

@tool
def get_weather(city_name:str)->dict:
'''According to the entered city name, use Autonavi interface to check the local weather today and the next 3 days'''
# 构建请求URL
url = f'https://restapi.amap.com/v3/weather/weatherInfo?key={GAODE_WEATHER_API_KEY}&city={city_name}&extensions=all'
try:
# 发送GET请求
response = requests.get(url)
# 检查请求是否成功
if response.status_code == 200:
# 解析响应内容
data = response.json()
# 检查API响应状态
if data.get('status') == '1' and data.get('infocode') == '10000':
return data # 返回天气数据
else:
return {} # 返回空结果,因为API返回了错误信息
else:
return {} # 返回空结果,因为HTTP状态码不是200
except Exception as e:
print(f"An error occurred: {e}")
return {} # 返回空结果,因为请求过程中发生了异常

@tool
def plot_line(json_data:str,requirement:str)->None:
''' 根据输入的json数据和需求,绘制出折线图
json_data: 待绘制的数据
requirement:绘制需求
'''

from langchain.prompts import PromptTemplate
template="""
已知以下由单引号分隔的json内容,根据用户问题生成Python脚本,
要求输出可执行代码即可,不输出其他任何信息
如果使用matplotlib绘制图形,使用plt.rcParams设置,确保中文输出不乱码
'''
{json_data}
'''
用户提问:{requirement}
"""

prompt_template=PromptTemplate(template=template)
def get_python_code(python_code:str)->str:
# print(python_code)
return python_code.content

from langchain_experimental.utilities import PythonREPL
python_repl=PythonREPL()
chain=prompt_template|model|get_python_code|PythonREPL().run
chain.invoke(
input={"json_data":json_data,"requirement":'将每一天的白天温度绘制成折线图'},
)

from langchain_ollama import ChatOllama
model=ChatOllama(model="qwen2.5:latest",base_url='http://192.168.3.155:11434')

# 您可以创建一个工具来传递给一个代理
from langchain.agents import Tool
from langchain_community.tools.yahoo_finance_news import YahooFinanceNewsTool
tools=[get_weather,YahooFinanceNewsTool(),plot_line]
model=model.bind_tools(tools=tools)
tool_node=ToolNode(tools=tools)

# Define the function that determines whether to continue or not
def should_continue(state: MessagesState) -> Literal["tools", END]:
messages = state['messages']
last_message = messages[-1]
# If the LLM makes a tool call, then we route to the "tools" node
if last_message.tool_calls:
return "tools"
# Otherwise, we stop (reply to the user)
return END

# Define the function that calls the model
def call_model(state: MessagesState):
messages = state['messages']
response = model.invoke(messages)
# We return a list, because this will get added to the existing list
return {"messages": [response]}
# Define a new graph

workflow = StateGraph(MessagesState)
# Define the two nodes we will cycle between
workflow.add_node("agent", call_model)
workflow.add_node("tools", tool_node)
# Set the entrypoint as `agent`
# This means that this node is the first one called
workflow.add_edge(START, "agent")
# We now add a conditional edge
workflow.add_conditional_edges(
# First, we define the start node. We use `agent`.
# This means these are the edges taken after the `agent` node is called.
"agent",
# Next, we pass in the function that will determine which node is called next.
should_continue,
)
# We now add a normal edge from `tools` to `agent`.
# This means that after `tools` is called, `agent` node is called next.
workflow.add_edge("tools", 'agent')
# Initialize memory to persist state between graph runs
checkpointer = MemorySaver()
# Finally, we compile it!
# This compiles it into a LangChain Runnable,
# meaning you can use it as you would any other runnable.
# Note that we're (optionally) passing the memory when compiling the graph
app = workflow.compile(checkpointer=checkpointer)
USER_AGENT environment variable not set, consider setting it to identify your requests.
1
2
3
4
5
6
7
# Use the Runnable
final_state = app.invoke(
{"messages": [HumanMessage(content="你好,我叫小五,住在广州")]},
config={"configurable": {"thread_id": 42}}
)
# final_state["messages"][-1].content
final_state
{'messages': [HumanMessage(content='你好,我叫小五,住在广州', additional_kwargs={}, response_metadata={}, id='f5261e6d-ab99-43f1-8adf-27189e05c0d6'),
 AIMessage(content='你好,小五!很高兴认识你。你住在广州,想查询广州的天气情况吗?如果是的话,我可以帮你查一查。如果不是,请告诉我你需要什么帮助。', additional_kwargs={}, response_metadata={'model': 'qwen2.5:latest', 'created_at': '2024-12-06T06:06:44.950776232Z', 'message': {'role': 'assistant', 'content': '你好,小五!很高兴认识你。你住在广州,想查询广州的天气情况吗?如果是的话,我可以帮你查一查。如果不是,请告诉我你需要什么帮助。'}, 'done_reason': 'stop', 'done': True, 'total_duration': 617556969, 'load_duration': 31513466, 'prompt_eval_count': 349, 'prompt_eval_duration': 56014000, 'eval_count': 38, 'eval_duration': 377784000}, id='run-90c5fe8b-e78c-4f05-84fa-e63174665178-0', usage_metadata={'input_tokens': 349, 'output_tokens': 38, 'total_tokens': 387})]}
1
2
3
4
5
6
7
# Use the Runnable
final_state = app.invoke(
{"messages": [HumanMessage(content="帮我查查天气,并且将白天气温以折线图输出")]},
config={"configurable": {"thread_id": 42}}
)
# final_state["messages"][-1].content
final_state
Python REPL can execute arbitrary code. Use with caution.

langchain的工作流-20241206141521

{'messages': [HumanMessage (content='你好,我叫小五,住在广州', additional_kwargs={}, response_metadata={}, id='f 5261 e 6 d-ab 99-43 f 1-8 adf-27189 e 05 c 0 d 6'),
  AIMessage (content='你好,小五!很高兴认识你。你住在广州,想查询广州的天气情况吗?如果是的话,我可以帮你查一查。如果不是,请告诉我你需要什么帮助。', additional_kwargs={}, response_metadata={'model': 'qwen 2.5: latest', 'created_at': '2024-12-06 T06:06:44.950776232 Z', 'message': {'role': 'assistant', 'content': '你好,小五!很高兴认识你。你住在广州,想查询广州的天气情况吗?如果是的话,我可以帮你查一查。如果不是,请告诉我你需要什么帮助。'}, 'done_reason': 'stop', 'done': True, 'total_duration': 617556969, 'load_duration': 31513466, 'prompt_eval_count': 349, 'prompt_eval_duration': 56014000, 'eval_count': 38, 'eval_duration': 377784000}, id='run-90 c 5 fe 8 b-e 78 c-4 f 05-84 fa-e 63174665178-0', usage_metadata={'input_tokens': 349, 'output_tokens': 38, 'total_tokens': 387}),
  HumanMessage (content='帮我查查天气,并且将白天气温以折线图输出', additional_kwargs={}, response_metadata={}, id='6 bd 7 aa 72-c 34 e-4256-b 25 e-6617 b 0 b 2 c 6 d 0'),
 AIMessage (content='', additional_kwargs={}, response_metadata={'model': 'qwen 2.5: latest', 'created_at': '2024-12-06 T06:06:49.180164278 Z', 'message': {'role': 'assistant', 'content': '', 'tool_calls': [{'function': {'name': 'get_weather', 'arguments': {'city_name': '广州'}}}, {'function': {'name': 'plot_line', 'arguments': {'json_data': '[{"x": [0, 1, 2, 3], "y": [25, 28, 30, 27]}]', 'requirement': '白天温度变化情况'}}}]}, 'done_reason': 'stop', 'done': True, 'total_duration': 1218560280, 'load_duration': 32782093, 'prompt_eval_count': 411, 'prompt_eval_duration': 47610000, 'eval_count': 85, 'eval_duration': 847499000}, id='run-597 d 384 f-ba 9 f-4292-9710-05 a 73 c 2 f 087 b-0', tool_calls=[{'name': 'get_weather', 'args': {'city_name': '广州'}, 'id': 'c 5 e 440 e 1-e 93 a-4208-a 00 a-5 f 24 deca 5618', 'type': 'tool_call'}, {'name': 'plot_line', 'args': {'json_data': '[{"x": [0, 1, 2, 3], "y": [25, 28, 30, 27]}]', 'requirement': '白天温度变化情况'}, 'id': 'ebdc 36 cb-7 cbc-49 dd-954 c-275 f 005 d 2415', 'type': 'tool_call'}], usage_metadata={'input_tokens': 411, 'output_tokens': 85, 'total_tokens': 496}),
  ToolMessage (content='{"status": "1", "count": "1", "info": "OK", "infocode": "10000", "forecasts": [{"city": "广州市", "adcode": "440100", "province": "广东", "reporttime": "2024-12-06 14:00:49", "casts": [{"date": "2024-12-06", "week": "5", "dayweather": "阴", "nightweather": "阴", "daytemp": "23", "nighttemp": "13", "daywind": "北", "nightwind": "北", "daypower": "1-3", "nightpower": "1-3", "daytemp_float": "23.0", "nighttemp_float": "13.0"}, {"date": "2024-12-07", "week": "6", "dayweather": "阴", "nightweather": "多云", "daytemp": "21", "nighttemp": "11", "daywind": "北", "nightwind": "北", "daypower": "1-3", "nightpower": "1-3", "daytemp_float": "21.0", "nighttemp_float": "11.0"}, {"date": "2024-12-08", "week": "7", "dayweather": "多云", "nightweather": "多云", "daytemp": "20", "nighttemp": "11", "daywind": "北", "nightwind": "北", "daypower": "1-3", "nightpower": "1-3", "daytemp_float": "20.0", "nighttemp_float": "11.0"}, {"date": "2024-12-09", "week": "1", "dayweather": "多云", "nightweather": "多云", "daytemp": "21", "nighttemp": "13", "daywind": "北", "nightwind": "北", "daypower": "1-3", "nightpower": "1-3", "daytemp_float": "21.0", "nighttemp_float": "13.0"}]}]}', name='get_weather', id='b 221 d 298-9 acc-4 fdf-bf 58-4 da 597 c 39 d 4 b', tool_call_id='c 5 e 440 e 1-e 93 a-4208-a 00 a-5 f 24 deca 5618'),
  ToolMessage (content='null', name='plot_line', id='03533 e 1 d-92 f 2-451 f-9 f 5 a-2 a 7 b 0 c 2 ac 224', tool_call_id='ebdc 36 cb-7 cbc-49 dd-954 c-275 f 005 d 2415'),
 AIMessage (content='', additional_kwargs={}, response_metadata={'model': 'qwen 2.5: latest', 'created_at': '2024-12-06 T06:06:55.43917518 Z', 'message': {'role': 'assistant', 'content': '', 'tool_calls': [{'function': {'name': 'plot_line', 'arguments': {'json_data': "[{'x': [0, 1, 2, 3], 'y': [23.0, 21.0, 20.0, 21.0]}]", 'requirement': '广州未来四天白天温度变化情况'}}}]}, 'done_reason': 'stop', 'done': True, 'total_duration': 2977528720, 'load_duration': 35083366, 'prompt_eval_count': 1022, 'prompt_eval_duration': 204299000, 'eval_count': 211, 'eval_duration': 2181965000}, id='run-25 f 706 e 7-508 a-43 cd-a 6 e 2-efa 345 ccad 89-0', tool_calls=[{'name': 'plot_line', 'args': {'json_data': "[{'x': [0, 1, 2, 3], 'y': [23.0, 21.0, 20.0, 21.0]}]", 'requirement': '广州未来四天白天温度变化情况'}, 'id': '7 d 2129 c 8-fe 75-42 a 8-a 231-d 3 b 8 cb 587 f 00', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1022, 'output_tokens': 211, 'total_tokens': 1233}),
  ToolMessage (content='null', name='plot_line', id='cbaeafd 6-ae 50-4 be 9-8513-fae 38 cac 21 dd', tool_call_id='7 d 2129 c 8-fe 75-42 a 8-a 231-d 3 b 8 cb 587 f 00'),
  AIMessage (content='', additional_kwargs={}, response_metadata={'model': 'qwen 2.5: latest', 'created_at': '2024-12-06 T06:07:01.366737529 Z', 'message': {'role': 'assistant', 'content': '', 'tool_calls': [{'function': {'name': 'plot_line', 'arguments': {'json_data': "[{'x': [0, 1, 2, 3], 'y': [23.0, 21.0, 20.0, 21.0]}]", 'requirement': '广州未来四天白天温度变化情况'}}}]}, 'done_reason': 'stop', 'done': True, 'total_duration': 3215797328, 'load_duration': 33080555, 'prompt_eval_count': 1116, 'prompt_eval_duration': 234777000, 'eval_count': 209, 'eval_duration': 2162800000}, id='run-ab 0 be 257-42 e 7-47 a 5-aeb 4-0 fed 36 ae 5 f 0 b-0', tool_calls=[{'name': 'plot_line', 'args': {'json_data': "[{'x': [0, 1, 2, 3], 'y': [23.0, 21.0, 20.0, 21.0]}]", 'requirement': '广州未来四天白天温度变化情况'}, 'id': 'de 1 adef 7-4908-4085-8 f 3 b-0659 a 77 a 8 e 58', 'type': 'tool_call'}], usage_metadata={'input_tokens': 1116, 'output_tokens': 209, 'total_tokens': 1325}),
 ToolMessage (content='null', name='plot_line', id='d 60 d 5328-4757-4 fb 5-9 ea 4-5511 ab 9 a 7862', tool_call_id='de 1 adef 7-4908-4085-8 f 3 b-0659 a 77 a 8 e 58'),
 AIMessage (content='根据查询结果,广州市未来四天的白天温度预报如下:\n\n- 2024 年 12 月 6 日:23°C\n- 2024 年 12 月 7 日:21°C\n- 2024 年 12 月 8 日:20°C\n- 2024 年 12 月 9 日:21°C\n\n 为了更直观地展示这四天的气温变化,我将绘制一个折线图来表示白天温度的变化情况。\n\n ![广州未来四天白天温度变化](https://example.com/temp_graph.png) \n\n 请查看这张图以了解详细信息。如果有其他问题或需要更多信息,请随时告诉我!', additional_kwargs={}, response_metadata={'model': 'qwen 2.5: latest', 'created_at': '2024-12-06 T06:07:06.354244487 Z', 'message': {'role': 'assistant', 'content': '根据查询结果,广州市未来四天的白天温度预报如下:\n\n- 2024 年 12 月 6 日:23°C\n- 2024 年 12 月 7 日:21°C\n- 2024 年 12 月 8 日:20°C\n- 2024 年 12 月 9 日:21°C\n\n 为了更直观地展示这四天的气温变化,我将绘制一个折线图来表示白天温度的变化情况。\n\n ![广州未来四天白天温度变化](https://example.com/temp_graph.png) \n\n 请查看这张图以了解详细信息。如果有其他问题或需要更多信息,请随时告诉我!'}, 'done_reason': 'stop', 'done': True, 'total_duration': 2761974859, 'load_duration': 32222715, 'prompt_eval_count': 1210, 'prompt_eval_duration': 262435000, 'eval_count': 145, 'eval_duration': 1512483000}, id='run-00 d 509 f 2-2 e 7 e-4120-8 d 75-a 6 e 5 b 591 d 4 ba-0', usage_metadata={'input_tokens': 1210, 'output_tokens': 145, 'total_tokens': 1355})]}
1
2
3
4
5
6
# Use the Runnable
final_state = app.invoke(
{"messages": [HumanMessage(content="我是谁?")]},
config={"configurable": {"thread_id": 42}}
)
final_state["messages"][-1].content
'你叫小五,刚才我们聊天时提到你住在广州,并且请求查询了广州的天气情况以及希望看到未来几天白天温度的变化折线图。如果你还有其他想知道的信息或者需要帮助的事情,请尽管告诉我!'

以上例子包含 6 个步骤:

  1. 初始化模型及工具:初始化 llm,并且为 llm 绑定工具
  2. 使用状态初始化图形:通过传递 state schema 来初始化 graph ((在我们的例子中 StateGraph (MessagesState)
  3. 定义图形节点:添加图形节点 agent 和 tools
  4. 定义入口及图形边缘:首先设置了图形的入口 (本例是 agent),然后添加正常边及条件边,条件边取决于图形状态
  5. 编译图形:编译图形,即将其自动转为 Runnable,然后可以使用.Invoke (). Stream (). Batch () 输入,还可以选择 checkpointer 对象,以便在图形运行之间持久保存状态,并启用内存、人机交互工作流程、时间旅行等
  6. 执行:(1)从 START 开始,LangGraph 将输入消息添加到内部 state,然后将 state 传递给 agent;(2)调用 llm 后,将返回信息添加到 AIMessage;(3) 条件选择执行,是否调用工具;(4) 直到到达图形的 END 端点
1
2
3
4
5
6
7
8
9
from IPython.display import Image, display
from langchain_core.runnables.graph import CurveStyle, MermaidDrawMethod, NodeStyles
display(
Image(
app.get_graph().draw_mermaid_png(
draw_method=MermaidDrawMethod.API,
)
)
)

langchain的工作流-20241206141559