| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- from langgraph.graph import START, END, StateGraph, MessagesState
- from typing import TypedDict, Annotated
- from operator import add
- from langchain_openai import ChatOpenAI
- from dotenv import load_dotenv
- import os
- load_dotenv(override=True)
- deepseek_base_url = os.getenv("DEEPSEEK_BASE_URL")
- deepseek_base_key = os.getenv("DEEPSEEK_BASE_KEY")
- deepseek_base_name = os.getenv("DEEPSEEK_BASE_NAME")
- print("1. 三点===============================================================================================================================================================================================================")
- class CounterState1(TypedDict):
- count: int
- def incr_node_1(state: CounterState1)-> dict:
- count = state["count"] + 1
- return {"count": count}
- def incr_node_2(state: CounterState1)-> dict:
- count = state["count"] * 2
- return {"count": count}
- build = StateGraph(CounterState1)
- build.add_node("incr_node_1", incr_node_1)
- build.add_node("incr_node_2", incr_node_2)
- build.add_edge(START, "incr_node_1")
- build.add_edge("incr_node_1", "incr_node_2")
- build.add_edge("incr_node_2", END)
- graph = build.compile()
- result = graph.invoke({"count": 1})
- print(result)
- print("2. 条件边===============================================================================================================================================================================================================")
- class CounterState2(TypedDict):
- count: int
- def sub_node_1(state: CounterState2) -> dict:
- count = state["count"] - 1
- return {"count": count}
- def sub_node_2(state: CounterState2) -> dict:
- count = state["count"] - 2
- return {"count": count}
- def condition_edge(state: CounterState2) -> str:
- if state["count"] > 5:
- return "sub_node_1"
- return "sub_node_2"
- build = StateGraph(CounterState2)
- build.add_node("sub_node_1", sub_node_1).add_node("sub_node_2", sub_node_2)
- build.add_edge(START, "sub_node_1")
- build.add_conditional_edges(
- "sub_node_1",
- condition_edge,
- {"sub_node_1": "sub_node_1", "sub_node_2": "sub_node_2"}
- )
- build.add_edge("sub_node_2", END)
- graph = build.compile()
- result = graph.invoke({"count": 20})
- print(result)
- print("3. 覆盖===============================================================================================================================================================================================================")
- class OverwriteState(TypedDict):
- arr: list[str]
- def node_1(state: OverwriteState)->dict:
- return {"arr":["a", "b"]}
- def node_2(state: OverwriteState)->dict:
- return {"arr":["c", "d"]}
- build = StateGraph(OverwriteState)
- build.add_node("node_1", node_1).add_node("node_2", node_2)
- build.add_edge(START, "node_1")
- build.add_edge("node_1", "node_2")
- build.add_edge("node_2", END)
- graph = build.compile()
- result = graph.invoke({"arr":["e", "f"]})
- print(result)
- print("4. 追加===============================================================================================================================================================================================================")
- class AppendState(TypedDict):
- arr: Annotated[list[str], add]
- def node_1(state: AppendState)->dict:
- return {"arr":["aa", "bb"]}
- def node_2(state: AppendState)->dict:
- return {"arr":["cc", "dd"]}
- build = StateGraph(AppendState)
- build.add_node("node_1", node_1).add_node("node_2", node_2)
- build.add_edge(START, "node_1")
- build.add_edge("node_1", "node_2")
- build.add_edge("node_2", END)
- graph = build.compile()
- result = graph.invoke({"arr":["ee", "ff"]})
- print(result)
- print("5. API===============================================================================================================================================================================================================")
- # llm = ChatOpenAI(
- # base_url= deepseek_base_url,
- # api_key= deepseek_base_key,
- # model= deepseek_base_name
- # )
- # def chat_llm(state: MessagesState)->dict:
- # response = llm.invoke("你是谁")
- # return {"messages": [response]}
- # build = StateGraph(MessagesState)
- # build.add_node("chat", chat_llm)
- # build.add_edge(START, "chat")
- # build.add_edge("chat", END)
- # graph = build.compile()
- # result = graph.invoke({"messages": ["多学多问"]})
- # print(result)
- print("6. 自定义===============================================================================================================================================================================================================")
- class ReplaceableList(list):
- def __init__(self, *args, replace: bool = False, **kwargs):
- super().__init__(*args, **kwargs)
- self.replace = replace
- def smart_merge(current: list, incom: list)->list:
- if isinstance(incom, ReplaceableList) and getattr(incom, "replace", False):
- return list(incom)
- return current + incom
- class CostomState(TypedDict):
- arr: Annotated[list[str], smart_merge]
- def append(state: CostomState)->dict:
- return {"arr": ["b", "c"]}
-
- def overwrite(state: CostomState)->dict:
- new_list = ReplaceableList(["reset_data"])
- new_list.replace = True
- return {"arr": new_list}
- build = StateGraph(CostomState)
- build.add_node("append", append)
- build.add_node("overwrite", overwrite)
- build.add_edge(START, "overwrite")
- build.add_edge("overwrite", "append")
- build.add_edge("append", END)
- graph = build.compile()
- result = graph.invoke({"arr": ["a"]})
- print(result)
- print("7. 子图===============================================================================================================================================================================================================")
- class DrinkState(TypedDict):
- logs: Annotated[list[str], add]
- class MakeState(TypedDict):
- sub_logs: Annotated[list[str], add]
- def add_coffee(state: MakeState) -> dict:
- return {"sub_logs": ["添加咖啡"]}
- def add_sugar(state: MakeState) -> dict:
- return {"sub_logs": ["添加粮"]}
- def do_make_coffee(state: MakeState) -> dict:
- return {"sub_logs": ["咖啡制作中..."]}
- makeBuild = StateGraph(MakeState)
- makeBuild.add_node("add_coffee", add_coffee)
- makeBuild.add_node("add_sugar", add_sugar)
- makeBuild.add_node("do_make_coffee", do_make_coffee)
- makeBuild.add_edge(START, "add_coffee")
- makeBuild.add_edge("add_coffee", "add_sugar")
- makeBuild.add_edge("add_sugar", "do_make_coffee")
- makeBuild.add_edge("do_make_coffee", END)
- makeGraph = makeBuild.compile()
- def order_coffee(state: DrinkState) -> dict:
- return {"logs": ["点咖啡"]}
- def make_coffee(state: DrinkState) -> dict:
- result = makeGraph.invoke({"sub_logs": ["开始制作咖啡"]})
- return {"logs": result["sub_logs"]}
- def drink_coffee(state: DrinkState) -> dict:
- return {"logs": ["喝咖啡"]}
- build = StateGraph(DrinkState)
- build.add_node("order_coffee", order_coffee)
- build.add_node("make_coffee", make_coffee)
- build.add_node("drink_coffee", drink_coffee)
- build.add_edge(START, "order_coffee")
- build.add_edge("order_coffee", "make_coffee")
- build.add_edge("make_coffee", "drink_coffee")
- build.add_edge("drink_coffee", END)
- graph = build.compile()
- result = graph.invoke({"logs": ["喝咖啡"]})
- print(result)
- print("8. Send===============================================================================================================================================================================================================")
- """
- 图的拓扑结构:
- START
- │
- ▼
- [parse_query] ← 节点:解析用户输入,提取品牌列表
- │
- ▼
- route_to_research ← 条件边路由函数:返回 N 个 Send
- │
- ├─ Send("research_brand", {brand: "苹果"})
- ├─ Send("research_brand", {brand: "华为"}) ← 三个并行
- └─ Send("research_brand", {brand: "小米"})
- │
- ▼ (并行执行)
- [research_brand] × 3 ← 每个品牌独立研究
- │
- ▼ (全部完成后汇总)
- [summarize] ← 节点:汇总所有报告,输出对比
- │
- ▼
- END
- """
- from typing import Annotated, TypedDict
- from operator import add
- from langgraph.graph import StateGraph, START, END
- from langgraph.types import Send
- # ══════════════════════════════════════════════════════════════
- # State 定义
- # ══════════════════════════════════════════════════════════════
- class CompareState(TypedDict):
- """手机对比任务的 State"""
- query: str # 用户的原始问题
- brands: list[str] # 从 query 中解析出的品牌列表
- reports: Annotated[list[str], add] # 每个品牌的研究报告(add reducer 自动合并)
- comparison: str # 最终的对比总结
- # ══════════════════════════════════════════════════════════════
- # 节点函数
- # ══════════════════════════════════════════════════════════════
- def parse_query(state: CompareState) -> dict:
- """
- 节点 ①:解析用户输入,提取出要对比的品牌列表。
- 实际场景中这里会用 LLM 做实体提取,这里直接硬编码模拟。
- """
- query = state["query"]
- print(f"[parse] 收到问题: {query}")
- # 模拟 LLM 解析:从自然语言中提取品牌名称
- brands = ["苹果", "华为", "小米"]
- print(f"[parse] 解析出 {len(brands)} 个品牌: {brands}")
- return {"brands": brands}
- def research_brand(state: CompareState) -> dict:
- """
- 节点 ②:研究单个品牌(被 Send 并行调用的目标节点)。
- 它不知道总共有几个品牌,也不关心其他品牌在做什么——
- 只专注于自己拿到的这一个。
- 实际场景中这里会调搜索引擎 API 或 LLM,这里用数据模拟。
- """
- brand = state["brands"][0] # Send 保证这里只有一个品牌
- # 模拟:查询该品牌的旗舰机信息
- phone_db = {
- "苹果": "iPhone 16 Pro Max | A18 Pro 芯片 | 6.9吋 OLED | 4K 120fps 视频 | iOS 18",
- "华为": "Mate 70 Pro+ | 麒麟 9100 | 6.8吋 OLED | 物理可变光圈 | 鸿蒙 NEXT",
- "小米": "小米 15 Ultra | 骁龙 8 Gen4 | 6.73吋 AMOLED | 徕卡光学 | 澎湃 OS 2.0",
- }
- info = phone_db.get(brand, f"{brand}旗舰机信息暂缺")
- report = f"【{brand}】{info}"
- print(f" [research] 并行查询 [{brand}] -> {info}")
- return {"reports": [report]} # add reducer 自动拼到总 reports 里
- def summarize(state: CompareState) -> dict:
- """
- 节点 ③:所有品牌都研究完了,汇总生成对比结论。
- 实际场景中这里会把所有 reports 喂给 LLM,让它生成对比分析。
- """
- print(f"\n[summarize] 开始汇总 {len(state['reports'])} 份报告...")
- # 模拟 LLM 生成的对比总结
- comparison = (
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
- " >> 旗舰手机对比总结\n"
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"
- )
- for report in state["reports"]:
- comparison += f" {report}\n"
- comparison += (
- "\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
- "[总结] 三款旗舰各有千秋 --\n"
- " 苹果 iPhone 16 Pro Max:视频拍摄王者,生态闭环体验最佳\n"
- " 华为 Mate 70 Pro+:影像系统物理可变光圈独树一帜\n"
- " 小米 15 Ultra:徕卡光学加持,性价比旗舰首选\n"
- "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"
- )
- return {"comparison": comparison}
- # ══════════════════════════════════════════════════════════════
- # 路由函数 —— 核心!返回 Send 列表而不是 dict
- # ══════════════════════════════════════════════════════════════
- def route_to_research(state: CompareState):
- """
- ★ Send API 的核心:条件边路由函数 ★
- 关键规则:
- - 这个函数只能放在 add_conditional_edges 里,不能放进 add_node
- - 普通节点返回 dict(更新 state)
- - 路由函数返回 Send 列表(创建并行任务)或字符串(END / 节点名)
- 思想:几个品牌 → 发几个 Send → 启动几次 research_brand
- return [
- Send("research_brand", {"brands": ["苹果"]}), ─┐
- Send("research_brand", {"brands": ["华为"]}), ─┤ 全部并行执行
- Send("research_brand", {"brands": ["小米"]}), ─┘
- ]
- 每个 Send 的两个参数:
- 参数 1: 目标节点名 —— "派给谁做"
- 参数 2: 此分支专属的 state —— "这个任务要什么数据"
- """
- if not state["brands"]:
- return END
- return [
- Send("research_brand", {"brands": [brand]})
- for brand in state["brands"]
- ]
- # ══════════════════════════════════════════════════════════════
- # 构图
- # ══════════════════════════════════════════════════════════════
- builder = StateGraph(CompareState)
- builder.add_node("parse_query", parse_query)
- builder.add_node("research_brand", research_brand)
- builder.add_node("summarize", summarize)
- # 边
- builder.add_edge(START, "parse_query")
- builder.add_conditional_edges("parse_query", route_to_research) # 路由 → 动态 fan-out
- builder.add_edge("research_brand", "summarize") # 所有并行结果汇聚到 summarize
- builder.add_edge("summarize", END)
- graph = builder.compile()
- # ══════════════════════════════════════════════════════════════
- # 运行
- # ══════════════════════════════════════════════════════════════
- result = graph.invoke({
- "query": "帮我对比一下苹果、华为和小米的旗舰机型",
- "brands": [],
- "reports": [],
- "comparison": "",
- })
- print("======")
- print(result["brands"])
- print(result["comparison"])
|