LangGraph 实战:并行计算旅游城市消费
本文使用 LangGraph 实现一个旅游城市选择程序:大模型先生成候选城市,再并行估算每个城市三天两夜的消费,最后按金额从高到低排序,让用户输入编号选择城市。
流程如下:

1. 环境准备
本文示例使用:
text
Python 3.14.7
LangGraph 1.2.10
langchain-deepseek 1.1.0
安装依赖:
bash
python -m pip install -U langgraph langchain-deepseek
配置 DeepSeek API Key:
bash
export DEEPSEEK_API_KEY="你的 DeepSeek API Key"
2. 定义状态与结构化输出
模型分别估算住宿、餐饮、市内交通和门票,再由 Python 计算总消费:
python
class CityExpense(BaseModel):
city: str
hotel: int = Field(ge=0)
food: int = Field(ge=0)
transport: int = Field(ge=0)
tickets: int = Field(ge=0)
class ExpenseItem(TypedDict):
city: str
consumption: int
class OveralState(TypedDict):
topic: str
subjects: list[str]
citys: Annotated[list[ExpenseItem], operator.add]
sorted_citys: list[ExpenseItem]
operator.add 是 citys 的 reducer。多个并行节点返回城市消费时,LangGraph 会使用列表加法合并结果,避免并行写入冲突。
3. 使用 Send 动态并行
第一个节点生成城市列表后,路由函数为每个城市创建一个 Send:
python
def continue_to_city(state: OveralState):
return [
Send("genrate_city", {"subject": city})
for city in state["subjects"]
]
如果模型生成六个城市,LangGraph 就会动态创建六个 genrate_city 任务。每个任务只处理一个城市。
4. 计算与排序
总消费由代码完成加法:
python
consumption = (
response.hotel
+ response.food
+ response.transport
+ response.tickets
)
所有并行任务结束后,按照消费金额降序排列:
python
sorted_citys = sorted(
state["citys"],
key=lambda item: item["consumption"],
reverse=True,
)
生成的流程图如下:

5. 完整代码
以下代码省略了源文件中的详细注释,运行逻辑保持一致:
python
import operator
import os
from typing import Annotated, TypedDict, cast
from langchain_deepseek import ChatDeepSeek
from langgraph.graph import END, START, StateGraph
from langgraph.types import Send
from pydantic import BaseModel, Field, SecretStr
subject_prompt = """
生成与下面主题相关的城市:{topic}
只返回以下 JSON 格式,不要输出其他内容:
{{"subjects": ["城市1", "城市2", "城市3", "城市4", "城市5", "城市6"]}}
"""
city_prompt = """
估算一名成年人去{city}旅游三天两夜的总消费。
分别估算两晚住宿、餐饮、市内交通和景点门票,
不包含出发地往返该城市的大交通费用。
所有费用必须是人民币整数,单位为元。
只返回以下 JSON 格式,不要输出其他内容:
{{"city": "城市名称", "hotel": 600, "food": 400, "transport": 150, "tickets": 350}}
"""
class Subjects(BaseModel):
subjects: list[str]
class CityExpense(BaseModel):
city: str
hotel: int = Field(ge=0)
food: int = Field(ge=0)
transport: int = Field(ge=0)
tickets: int = Field(ge=0)
class ExpenseItem(TypedDict):
city: str
consumption: int
class OveralState(TypedDict):
topic: str
subjects: list[str]
citys: Annotated[list[ExpenseItem], operator.add]
sorted_citys: list[ExpenseItem]
class CityState(TypedDict):
subject: str
deepseek = ChatDeepSeek(
model="deepseek-v4-flash",
temperature=0,
base_url="https://api.deepseek.com",
api_key=SecretStr(os.environ["DEEPSEEK_API_KEY"]),
)
def genrate_topic(state: OveralState):
prompt = subject_prompt.format(topic=state["topic"])
response = cast(
Subjects,
deepseek.with_structured_output(
Subjects, method="json_mode"
).invoke(prompt),
)
return {"subjects": response.subjects}
def genrate_city(state: CityState):
prompt = city_prompt.format(city=state["subject"])
response = cast(
CityExpense,
deepseek.with_structured_output(
CityExpense, method="json_mode"
).invoke(prompt),
)
consumption = (
response.hotel
+ response.food
+ response.transport
+ response.tickets
)
expense: ExpenseItem = {
"city": response.city,
"consumption": consumption,
}
return {"citys": [expense]}
def continue_to_city(state: OveralState):
return [
Send("genrate_city", {"subject": city})
for city in state["subjects"]
]
def sort_citys(state: OveralState):
return {
"sorted_citys": sorted(
state["citys"],
key=lambda item: item["consumption"],
reverse=True,
)
}
graph_builder = StateGraph(OveralState)
graph_builder.add_node("genrate_topic", genrate_topic)
graph_builder.add_node("genrate_city", genrate_city)
graph_builder.add_node("sort_citys", sort_citys)
graph_builder.add_edge(START, "genrate_topic")
graph_builder.add_conditional_edges(
"genrate_topic", continue_to_city, ["genrate_city"]
)
graph_builder.add_edge("genrate_city", "sort_citys")
graph_builder.add_edge("sort_citys", END)
graph = graph_builder.compile()
initial_state: OveralState = {
"topic": "沿海",
"subjects": [],
"citys": [],
"sorted_citys": [],
}
result = graph.invoke(initial_state)
city_options = result["sorted_citys"]
if not city_options:
raise ValueError("没有生成可选择的旅游城市")
print("\n城市消费排行(从高到低):")
for index, item in enumerate(city_options, start=1):
print(f"{index}. {item['city']}:{item['consumption']} 元")
while True:
choice = input(
f"\n请选择城市编号(1-{len(city_options)}):"
).strip()
if not choice.isdigit():
print("请输入有效的数字编号。")
continue
selected_index = int(choice) - 1
if not 0 <= selected_index < len(city_options):
print("编号超出范围,请重新选择。")
continue
selected_city = city_options[selected_index]
break
6. 输出示例
运行:
bash
python index.py
输出示例:
text
城市消费排行(从高到低):
1. 上海:2350 元
2. 三亚:2200 元
3. 深圳:1900 元
4. 青岛:1550 元
5. 厦门:1450 元
6. 大连:1350 元
请选择城市编号(1-6):4
城市和金额由大模型估算,因此不同时间运行可能得到不同结果。并行任务的完成顺序也不固定,但 sort_citys 会在输出前统一按照消费金额排序。
总结
这个案例展示了 LangGraph 的三个实用能力:使用 Send 根据数据动态创建并行任务,使用 reducer 聚合并行结果,以及在汇聚节点中执行确定性的排序逻辑。大模型负责生成和估算,Python 负责计算、排序与输入校验,职责更加清晰。