LangGraph 入门实战(6)

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.addcitys 的 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 负责计算、排序与输入校验,职责更加清晰。

相关推荐
大模型码小白1 小时前
AI安全前沿:AI大模型安全防护的前沿技术
java·网络·人工智能·python·深度学习·学习·安全
evans在进步1 小时前
LeetCode 34:在排序数组中查找元素的首尾位置——Java 两次二分查找详解
java·python·leetcode
海拥✘1 小时前
Python 实战:用 IPPEAK 代理 IP 构建稳定的公开数据采集链路
网络·python·tcp/ip
头茬韭菜1 小时前
第 4 篇:「Fluss + Flink 集成实战」—— Catalog、Source 与 Sink
大数据·python·flink·fluss
天天爱吃肉82182 小时前
【工程师硬件学习笔记:串口‑总线体系与三电试验室异构系统集成深度实战】
大数据·笔记·python·嵌入式硬件·学习·汽车
AC赳赳老秦2 小时前
官方技术文档聚合实践:用 OpenClaw 批量抓取开源项目文档,构建离线可检索技术知识库
java·运维·服务器·python·信息可视化·deepseek·openclaw
Zenova EdgeOS2 小时前
工业边缘 SDK 设计实战:从 API 到 Python/Go 多语言工程落地
python·golang·php
XLYcmy2 小时前
pdf论文处理:CSV输出模式
数据库·python·pycharm·pdf·论文·csv·dify
苏灿烤鱼2 小时前
14MB 模型,凭什么跟 270M 对打?
javascript·python·agent