langGraph

langGraph

【了解】课程目标

适用于对大模型和智能体设计有初步概念的同学。

  • 环境搭建:能够独立配置LangGraph开发环境。
  • 掌握工具:掌握LangGraph的核心概念(图、节点、边、状态、流程控制等)
  • 基础开发:完成基本的对话系统/工作流的搭建。
  • 设计模式:理解和实践六种主流的智能体设计模式。

一 【了解】什么是langGraph

【学习目标】

了解langGraph工作方式,langGraph和langChain的区别,能够安装langGraph环境。

1 实际例子

langGraph 是专门用来构建"AI工作流"的工具包,帮你把复杂的AI任务拆成步骤,像搭积木一样组装起来。

想象你要让AI做一杯"数字咖啡",步骤可能是:

  • 磨咖啡豆‌(调用处理模型)
  • 烧热水‌(一直加热直到水烧开为止)
  • 冲咖啡‌(把前两步结果合并)
  • 加糖‌(根据用户反馈调整输出)

这个过程可以用下图表示:

2 为什么不用langChain

langChain是一个通用的智能体编排工具,是langGraph的前身。

它的工作模式是:把工作编排成一系列的工作节点,用一条工作链把这些节点连接起来,组织成一张有向无环图(DAG)。

这种工作方式适合比较简单的智能体,比如 提示词-->调用模型-->结果优化。一旦涉及到一些复杂的流程,就有些力不从心了。比如冲咖啡这个例子中:

  • 有些步骤可以并行,例如:烧开水和磨咖啡豆。
  • 有些情况需要不断循环,例如:烧开水时需要不停的检测水温,直到水烧开为止
  • 有些位置需要人工介入,例如:咖啡要不要加糖

用 LangGraph 就能把这些步骤画成一张流程图,AI会按一定的逻辑顺序执行,提供一种更加灵活的智能体编排方式。

2 它解决啥问题?

  • 复杂任务拆分‌:比如让AI先查资料再写文章,分步完成。
  • 动态调整流程‌:根据中间结果决定下面的分支路径。
  • 分支与并行:一些步骤可以拆分成不同的分支,并行完成后整合结果。
  • 多人协作‌:不同步骤可以用不同模型/API(比如GPT写文案+Stable Diffusion画图)。
  • 人工介入:在某些非常关键的位置,必须由人类决定的点,可以人工介入,然后继续走流程。

3 langGraph的安装

shell 复制代码
#创建一个虚拟环境
conda create -n langgraph python=3.11
#进入虚拟环境
conda activate langgraph
#安装pytorch 南京大学
pip3 install torch torchvision torchaudio --index-url https://mirrors.nju.edu.cn/pytorch/whl/cu126
#安装其他的支持包
pip3 install ollama langchain langchain_ollama matplotlib -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple

pip install langgraph -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple

问题:

​ 1 什么是langGraph

​ 2 langGraph能够解决哪些问题?

实操:

​ 1 搭建一个langGraph环境

二 【掌握】langGraph的基本组件

【重点 学习目标】

掌握langGraph的核心组件:图、状态、节点、边、记忆,并能够熟练运用。

1 图(graph)

1.1 什么是graph

普鲁士的柯尼斯堡市(现俄罗斯加里宁格勒)位于普雷格尔河两岸,城市中有七座桥梁相连。有人提出过一个非常有趣的问题,能不能设计一条游览路线,每座桥只需要经过一次,就能玩遍整座城市?

无数人做出过各种努力,但都没有成功,后来引起了大数学家**莱昂哈德·欧拉(Leonhard Euler)**的注意,他是很多重要数学概念的提出者,包括函数、指数、对数、复数等。他把问题简化成下面的形式

由此引入图论(graph theory),我们今天神经网络框架pytorch、tensorflow等使用的计算图,只是图论的微不足道的小小应用,langGraph使用的图也是这一理论体系下的产物。

1.2 langGraph中的graph

图是顶点和边的集合,langGraph里的图是有向无权的全联通图。有StateGraphMessageGraph两种形式。

StateGraph是langGraph的核心结构,用于需要维护长期状态的场景。MessageGraph是一个轻量化的图,只用于传递消息(message),应用场景非常狭窄。

StateGraph默认有一个START节点和一个END节点,一个最简化的图如下:

python 复制代码
from langgraph.graph import START,StateGraph,END

graphBuilder = StateGraph(dict)
graph = graphBuilder.compile()

用matplotlib输出graph

python 复制代码
import matplotlib.pyplot as plt
import matplotlib.image as mpimg  # 导入matplotlib.image用于读取图像
try:
    # 使用 Mermaid 生成图表并保存为文件
    mermaid_code = graph.get_graph().draw_mermaid_png()
    with open("graph.jpg", "wb") as f:
        f.write(mermaid_code)

    # 使用 matplotlib 显示图像
    img = mpimg.imread("graph.jpg")
    plt.imshow(img)
    plt.axis('off')  # 关闭坐标轴
    plt.show()
except Exception as e:
    print(f"An error occurred: {e}")

实操:

​ 1、构建一个最小化的langGraph图

​ 2、用mapPlotLib打印这张图

2 状态

状态(state)是langGraph中的核心设计,用于在复杂任务或对话系统中存储和传递上下文信息。

状态(state)是一个结构化的数据容器,以键值对的形式存在,本质上是一个字典(dict)。

python 复制代码
from langgraph.graph import START,StateGraph,  END

def add(state):
    print("*"*80)
    print("before add:"+str(state))
    state["y"] = state["x"]+1
    print("after add:" + str(state))
    return state

def buildGraph():
    #定义图的时候,要指定状态的数据类型
    graphBuilder = StateGraph(dict)
    graphBuilder.add_node("add", add)
    graphBuilder.set_entry_point("add")
    graphBuilder.add_edge("add", END)
    graph = graphBuilder.compile()
    return graph

if __name__ == "__main__":
    graph = buildGraph()

    #state本质上是一个字典,可以使用python的原生字典
    state = {"x":2}
    result = graph.invoke(state)
    print(state)
复制代码
before add:{'x': 2}
after add:{'x': 2, 'y': 3}
{'x': 2, 'y': 3}

但实际工作中为了方便数据校验,一般使用TypedDict

python 复制代码
from langgraph.graph import StateGraph,  END
from typing_extensions import TypedDict

#定义一个类,类型是TypedDict。这种数据类型可以方便数据校验,以后我们都采用这种方式定义状态
class State(TypedDict):
    x: int
    y: int

def add(state):
    print("*"*80)
    print("before add:"+str(state))
    state["y"] = state["x"]+1
    print("after add:" + str(state))
    return state

def buildGraph():
    # 定义图的时候,要指定状态的数据类型
    graphBuilder = StateGraph(State)

    graphBuilder.add_node("add", add)

    graphBuilder.set_entry_point("add")
    graphBuilder.add_edge("add", END)

    graph = graphBuilder.compile()
    return graph

if __name__ == "__main__":
    graph = buildGraph()

    from tools import showGraph
    showGraph.showGraphInCode(graph,"graph.jpg")

    #实例化状态
    state:State = {'x':2}
    result = graph.invoke(state)
    print(result)
复制代码
before add:{'x': 2}
after add:{'x': 2, 'y': 3}
{'x': 2, 'y': 3}

实操:

​ 1、使用TypedDict组织状态数据

​ 2、观察如果数据类型错误,IDE给出的提示

2 节点(Node)

节点(node)是构建graph的核心组件,每个节点代表一个具体的任务或操作单元,一般是封装成一个函数执行业务逻辑,并与状态(state)产生交互。

一般来说,有以下八种节点:

节点类型 作用 说明
默认节点(Default Nodes) 基本节点 任何一个图都至少包含两个默认节点:START,END
函数节点(Function Nodes) 基本节点 最基础的节点类型,通过 Python 函数实现,接收当前状态并返回更新后的状态。例如处理文本、执行计算等
工具节点(Tool Nodes) 基本节点 封装外部工具调用(如 API、数据库查询)
大模型节点(LLM Nodes) 基本节点 调用大语言模型生成响应或决策
条件节点(Conditional Nodes) 流程控制 通过边(Edge)实现分支逻辑, 选择不同处理路径
并行节点(Parallel Nodes) 流程控制 并行节点是LangGraph中用于并行执行多个操作的节点。它们通常包含一个或多个子节点,这些子节点可以并行地执行不同的任务。并行节点使得LangGraph能够充分利用多核处理器的性能,提高任务执行的效率。
聚合节点(Aggregatror Nodes) 流程控制 合并多个节点的输出(如投票机制或多源数据整合)
人工干预节点(Human-in-the-Loop Nodes) 流程控制 在关键步骤暂停流程,等待人工输入或审核

这里只展示三种基本节点,流程控制类的节点放到第四章和对应的边一起介绍。

函数节点本质上就是一个通用的python函数

python 复制代码
def add(state):
    print("*"*80)
    print("before add:"+str(state))
    state["y"] = state["x"]+1
    print("after add:" + str(state))

工具节点的作用是封装外部工具或API

python 复制代码
def get_weather(city: str) -> str:
    """Get weather for a given city."""
    return f"It's always sunny in {city}!"

大模型节点调用大模型生成结果,如果需要使用工具,可以调用时加以说明

python 复制代码
from langgraph.prebuilt import create_react_agent
from langchain_ollama import ChatOllama
llm = ChatOllama(model="qwen2.5:7b", temperature=0.6)

def configLLM(llm):
    agent = create_react_agent(
        model=llm,
        #这里说明可以使用的工具
        tools=[get_weather],
        prompt="You are a helpful assistant"
    )
    #调用大模型
    agent.invoke(
        {"messages": [{"role": "user", "content": "what is the weather in sf"}]}
    )

在图里添加节点的方式

python 复制代码
def buildGraph():
    graphBuilder = StateGraph(State)

    #在图里添加一个节点
    graphBuilder.add_node("add", add)

    graphBuilder.set_entry_point("add")
    graphBuilder.add_edge("add", END)

    graph = graphBuilder.compile()
    return graph

实操:

​ 1、编写一张图,至少包含一个非默认节点

3 边(Edge)

边(Edge)是连接节点的逻辑通道,用来控制工作流的执行顺序和数据流向,主要有以下两种类型:

边类型 说明
普通边 直接连接两个节点,无条件跳转
条件边 基于状态动态选择路径,通过函数返回目标节点名

普通边,连接两个节点,包括START和END

python 复制代码
#用这种方式设置普通节点间的联系
#graphBuilder.add_edge("add", "multiply")

# 可以用这两种方式设置起始节点,个人建议采用第一种,符合边的通用定义方式
graphBuilder.add_edge(START, "heatUp")
#graphBuilder.set_entry_point("add")

# 可以用这两种方式设置起始节点,个人建议采用第一种,符合边的通用定义方式
graphBuilder.add_edge("getBolidWater",END)
#graphBuilder.set_finish_point("getBolidWater")

条件边和条件节点有两种写法,个人建议第一种。所有节点流转全部集中在图的定义部分,方便检查

python 复制代码
#条件节点的第一种方式,每个分支返回一个字符串
def processWater(state):
    if state["tempreture"]== 100:
        state["product"] = "coffee"
        return "hotWaterReady"
    else:
        state["product"] = "unburned water"
        return "stillCode"

#条件边的第一种方式:(起点,终点,映射表),注意:终点是条件节点,不用注册。映射表是条件节点返回的字符串映射到后续节点
graphBuilder.add_conditional_edges("heatUp",processWater,{"hotWaterReady":"getBolidWater","stillCode":"heatUp"})
python 复制代码
#条件节点的第二种方式,每个分支返回后续节点名
def processWater2(state):
    if state["tempreture"]== 100:
        state["product"] = "coffee"
        return END
    else:
        state["product"] = "unburned water"
        return "heatUp"

# 条件边的第二种方式:(起点,终点),注意:终点是条件节点,不用注册。
#graphBuilder.add_conditional_edges("heatUp", processWater)

实操:

​ 1、使用一条正常边

​ 2、使用一条条件边

4 记忆(memory)

langGraph允许带有记忆功能,是通过langgraph.checkpoint.memory来实现的

python 复制代码
from langgraph.graph import StateGraph, START,END
from langchain_ollama import ChatOllama
from typing_extensions import TypedDict
from typing import Annotated
from langgraph.graph.message import add_messages
from langgraph.checkpoint.memory import MemorySaver

class State(TypedDict):
    messages: Annotated[list, add_messages]

llm = ChatOllama(model="qwen2.5:7b")

def chatbot(state: State):
    return {"messages": [llm.invoke(state["messages"])]}

def buildGraph():
    graphBuilder = StateGraph(State)

    graphBuilder.add_node("chatbot", chatbot)

    graphBuilder.add_edge(START, "chatbot")
    graphBuilder.add_edge("chatbot",END)

    graph = graphBuilder.compile()
    return graph

def buildGraphWithMemory():
    graphBuilder = StateGraph(State)
    #实例化memory
    memory = MemorySaver()

    graphBuilder.add_node("chatbot", chatbot)

    graphBuilder.add_edge(START, "chatbot")
    graphBuilder.add_edge("chatbot",END)

    #编译图的时候指定memory
    graph = graphBuilder.compile(checkpointer=memory)
    return graph

def singleRound(graph):
    while True:
        try:
            userInput = input("User: ")
            if userInput.lower() in ["quit", "exit", "q"]:
                print("Goodbye!")
                break
            state = {"messages": [{"role": "user", "content": userInput}]}
            result = graph.invoke(state)
            # print(result)
            print("Assitant:", result["messages"][-1].content)
        except Exception as e:
            print("发生错误:"+str(e))

def multiRound(graph,config):
    while True:
        try:
            userInput = input("User: ")
            if userInput.lower() in ["quit", "exit", "q"]:
                print("Goodbye!")
                break
            state = {"messages": [{"role": "user", "content": userInput}]}
            result = graph.invoke(state, config=config)
            # print(result)
            print("Assitant:", result["messages"][-1].content)
        except Exception as e:
            print("发生错误:"+str(e))

if __name__ == "__main__":
    graph = buildGraph()
    singleRound(graph)

    #graphWithMemory = buildGraphWithMemory()
    #config = {"configurable": {"thread_id": "1"}}
    #multiRound(graphWithMemory,config)

单论对话示例:

复制代码
User: 世界第一高峰是哪一座?
Assitant: 世界第一高峰是珠穆朗玛峰,位于中国与尼泊尔边境线上,高度为8848.86米(2020年最新测量数据)。
User: 第二呢?
Assitant: 您似乎是想问"第二"是什么,但是没有提供足够的背景信息。请问您可以具体说明一下是在哪个领域或情境中提到的"第二"吗?这样我才能更好地帮助您。

多轮对话示例

复制代码
User: 世界第一高峰是哪一座?
Assitant: 世界第一高峰是珠穆朗玛峰,位于中国与尼泊尔边境线上,高度为8,848.86米(29,031.7英尺)。
User: 第二呢?
Assitant: 世界第二高峰是乔戈里峰(也称为K2),它位于巴基斯坦与中国边境的喜马拉雅山脉地区,高度为8,611米(28,251英尺)。

实操:

​ 1、使用正常边

​ 2、使用条件边

三 【了解】langGraph的基本执行流程

【学习目标】

了解一个langGraph应用的执行流程。能够写出基本langGraph程序。

执行一个langGraph项目要完成以下六个基本步骤:

  • 初始化模型和工具

  • 初始化图和状态

  • 定义节点

  • 定义边和出口入口

  • 编译图

  • 执行图

python 复制代码
from langgraph.graph import StateGraph, START,END
from langchain_ollama import ChatOllama
from typing_extensions import TypedDict
from typing import Annotated
from langgraph.graph.message import add_messages
from langgraph.prebuilt import create_react_agent

#第一步:初始化模型和工具
def getID(city: str) -> str:
    """explain your identity"""
    return f"我是量子伟业训练的档案管理助手"

llm = create_react_agent(
    model = ChatOllama(model="qwen2.5:7b"),
    tools=[getID],
    prompt="You are a helpful assistant"
)

def chatbot(state):
    result = llm.invoke(state["messages"][-1])
    return result["messages"][-1].content

#第二步:初始化图和状态
class State(TypedDict):
    messages: Annotated[list, add_messages]
graphBuilder = StateGraph(dict)

#第三步:定义节点
graphBuilder.add_node("chatbot", chatbot)

#第四步:定义边和出入口
graphBuilder.add_edge(START, "chatbot")
graphBuilder.add_edge("chatbot",END)

#第五步:编译图
graph = graphBuilder.compile()

#第六步:执行图
userInput = input("User: ")
state:State = {"messages": [{"role": "user", "content": userInput}]}
result = graph.invoke(state)
print(result)
复制代码
User: 你是谁?
Assistant:您好,我是来自"量子伟业"的档案管理助手,目前模拟位置在纽约。有什么可以帮助您的吗?

四 【掌握】langGraph的流程控制

【学习目标】

能够熟练使用langGraph的流程控制方式:分支、循环、并行、人工介入、子图。

能够使用各种流程控制工具搭建较复杂的应用。

1 分支

通过综合运用条件边和条件节点,可以控制graph的分支

python 复制代码
from langgraph.graph import START,StateGraph,  END
from typing_extensions import TypedDict

class State(TypedDict):
    水温: int
    产物:str

def 烧水(state):
    print("*"*80)
    print("烧水之前:"+str(state))
    state["水温"] = state["水温"]+10
    if state["水温"]>100:
        state["水温"] = 100
    print("烧水之后:" + str(state))
    return state

#条件节点,如果要使用分支,必须使用条件节点
def 按条件处理水(state):
    print("before processWater:"+str(state))
    if state["水温"]== 100:
        return "水烧开了"
    else:
        return "水没开"

def 冲咖啡(state):
    print("冲咖啡之前:" + str(state))
    state["产物"] = "咖啡"
    print("冲咖啡之后:"+str(state))
    return state

def 需要继续加热(state):
    state["产物"] = "没烧开的水"
    print("继续加热之前"+str(state))
    return state

def buildGraph():
    graphBuilder = StateGraph(State)
    graphBuilder.add_node("烧水", 烧水)
    graphBuilder.add_node("冲咖啡", 冲咖啡)
    graphBuilder.add_node("需要继续加热", 需要继续加热)

    graphBuilder.add_edge(START, "烧水")
    #条件边,如果要使用分支,必须使用条件边
    graphBuilder.add_conditional_edges("烧水",按条件处理水,{"水烧开了":"冲咖啡","水没开":"需要继续加热"})
    graphBuilder.add_edge("冲咖啡",END)
    graphBuilder.add_edge("需要继续加热", END)

    graph = graphBuilder.compile()
    return graph

if __name__ == "__main__":
    graph = buildGraph()

    from tools import showGraph
    showGraph.showGraphInCode(graph, "graph.jpg")

    state:State = {"水温": 28,"产物":"没烧开的水"}
    result = graph.invoke(state)
    print(result)
复制代码
烧水之前:{'水温': 28, '产物': '没烧开的水'}
烧水之后:{'水温': 38, '产物': '没烧开的水'}
before processWater:{'水温': 38, '产物': '没烧开的水'}
继续加热之前{'水温': 38, '产物': '没烧开的水'}
{'水温': 38, '产物': '没烧开的水'}

实操:

​ 1 练习langGraph中的分支结构

2 循环

通过合理设置条件节点的跳转,可以构造循环。

python 复制代码
from langgraph.graph import START,StateGraph,  END
from typing_extensions import TypedDict

class State(TypedDict):
    水温: int
    产物:str

def 烧水(state):
    print("*"*80)
    print("烧水之前:"+str(state))
    state["产物"] = "没烧开的水"
    state["水温"] = state["水温"]+10
    if state["水温"]>100:
        state["水温"] = 100
        state["产物"] = "开水"
    print("烧水之后:" + str(state))
    return state

#条件节点,如果要使用循环,必须使用条件节点
def 按温度处理水(state):
    if state["水温"]== 100:
        return "水烧开了"
    else:
        return "水没开"

def buildGraph():
    graphBuilder = StateGraph(State)

    graphBuilder.add_node("烧水", 烧水)

    graphBuilder.add_edge(START, "烧水")
    # 条件边,如果要使用循环,必须使用条件边
    graphBuilder.add_conditional_edges("烧水",按温度处理水,{"水烧开了":END,"水没开":"烧水"})

    graph = graphBuilder.compile()
    return graph

if __name__ == "__main__":
    graph = buildGraph()

    from tools import showGraph
    showGraph.showGraphInCode(graph, "graph.jpg")

    state:State = {"水温": 28,"产物":"没烧开的水"}
    result = graph.invoke(state)
    print(result)
复制代码
********************************************************************************
烧水之前:{'水温': 28, '产物': '没烧开的水'}
烧水之后:{'水温': 38, '产物': '没烧开的水'}
********************************************************************************
烧水之前:{'水温': 38, '产物': '没烧开的水'}
烧水之后:{'水温': 48, '产物': '没烧开的水'}
********************************************************************************
烧水之前:{'水温': 48, '产物': '没烧开的水'}
烧水之后:{'水温': 58, '产物': '没烧开的水'}
********************************************************************************
烧水之前:{'水温': 58, '产物': '没烧开的水'}
烧水之后:{'水温': 68, '产物': '没烧开的水'}
********************************************************************************
烧水之前:{'水温': 68, '产物': '没烧开的水'}
烧水之后:{'水温': 78, '产物': '没烧开的水'}
********************************************************************************
烧水之前:{'水温': 78, '产物': '没烧开的水'}
烧水之后:{'水温': 88, '产物': '没烧开的水'}
********************************************************************************
烧水之前:{'水温': 88, '产物': '没烧开的水'}
烧水之后:{'水温': 98, '产物': '没烧开的水'}
********************************************************************************
烧水之前:{'水温': 98, '产物': '没烧开的水'}
烧水之后:{'水温': 100, '产物': '开水'}
{'水温': 100, '产物': '开水'}

实操:

​ 1 练习langGraph中的循环结构

3 并行

在一个graph中,某些节点可以并行,例如:在煮咖啡任务中,烧水和磨咖啡豆两个任务,互不干扰,可以同时进行。参见buildGraph1,图形表示如下:

在并行情况下,要特别注意节点合并的时候的reducer,参见 updateTempreture,updateProduct,updateSolid 和 State

更多情况下,并行任务中的一个分支可能含有多个节点,参加buildGraph2,图形表示如下:

如果直接使用buildGraph2的写法,会出现下列情况:

  • 第一步执行 烧水1 和 磨咖啡豆
  • 第二步执行 烧水2 和 冲咖啡
  • 第三步执行 冲咖啡

在水没烧开的情况下就执行了冲咖啡,水烧开后又执行了一次冲咖啡,虽然最终结果正确,但过程有错误

这时应该限制必须同时完成 烧水2 和 磨咖啡豆 之后才能冲咖啡,参见buildGraph3

复制代码
from langgraph.graph import START,StateGraph,  END
from typing_extensions import TypedDict
from typing import Annotated

#合并温度值的方式,取其大者
def updateTempreture(left, right):
    return max(left, right)

#合并产物的方式,优先取工序靠后的产物
def updateProduct(left,right):
    if left=="咖啡" or right == "咖啡":
        return "咖啡"
    elif left=="开水" or right == "开水":
        return "开水"
    elif left=="温水" or right == "温水":
        return "温水"
    else:
        return "凉水"

#合并咖啡固体的方式,优先取工序靠后的产物
def updateSolid(left,right):
    if left=="咖啡粉" or right == "咖啡粉":
        return "咖啡粉"
    else:
        return "咖啡豆"

#如果有并行的节点,必然涉及到并行节点输出数据的合并,使用Annotated,第一个参数是数据类型,第二个参数是合并数据的方式
class State(TypedDict):
    水温: Annotated[int,updateTempreture]
    产物:Annotated[str,updateProduct]
    咖啡固体:Annotated[str,updateSolid]

def 磨咖啡豆(state):
    print("*" * 80)
    print("磨咖啡豆之前:" + str(state))
    state["咖啡固体"] = "咖啡粉"
    print("磨咖啡豆之后:" + str(state))
    return state

def 烧开水(state):
    print("*"*80)
    print("烧开水之前:"+str(state))
    state["产物"] = "没烧开的水"
    if state["水温"]<100:
        state["水温"] = 100
        state["产物"] = "开水"
    print("烧开水之后:" + str(state))
    return state

def 烧温水(state):
    print("*"*80)
    print("烧温水之前:"+str(state))
    state["产物"] = "没烧开的水"
    if state["水温"]<38:
        state["水温"] = 38
        state["产物"] = "温水"
    print("烧温水之后:" + str(state))
    return state

def 冲咖啡(state):
    print("冲咖啡之前:" + str(state))
    state["产物"]="咖啡"
    print("冲咖啡之后:" + str(state))
    return state

def buildGraph1():
    graphBuilder = StateGraph(State)

    graphBuilder.add_node("烧水2", 烧开水)
    graphBuilder.add_node("磨咖啡豆",磨咖啡豆)
    graphBuilder.add_node("冲咖啡", 冲咖啡)

    graphBuilder.add_edge(START, "磨咖啡豆")
    graphBuilder.add_edge(START, "烧水2")
    graphBuilder.add_edge("磨咖啡豆", "冲咖啡")
    graphBuilder.add_edge("烧水2", "冲咖啡")
    graphBuilder.add_edge("冲咖啡", END)
    #上述流程的执行顺序:
    #第一批计算的节点是 烧水2 和 磨咖啡豆
    #第二批计算的节点是 冲咖啡
    #这种写法貌似得到了正确的结果,其实是一种错觉,实际工作中不要这样写

    graph = graphBuilder.compile()
    return graph

def buildGraph2():
    graphBuilder = StateGraph(State)

    graphBuilder.add_node("烧水1", 烧温水)
    graphBuilder.add_node("烧水2", 烧开水)
    graphBuilder.add_node("磨咖啡豆",磨咖啡豆)
    graphBuilder.add_node("冲咖啡", 冲咖啡)

    graphBuilder.add_edge(START, "磨咖啡豆")
    graphBuilder.add_edge(START, "烧水1")
    graphBuilder.add_edge("烧水1","烧水2")
    graphBuilder.add_edge("磨咖啡豆", "冲咖啡")
    graphBuilder.add_edge("烧水2", "冲咖啡")
    graphBuilder.add_edge("冲咖啡", END)
    # 上述流程的执行顺序:
    # 第一批计算的节点是 烧水1 和 磨咖啡豆
    # 第二批计算的节点是 烧水2 和 冲咖啡
    # 第三批计算的节点是 冲咖啡
    #这里有两个错误:
    #1. 水还没彻底烧开就冲咖啡了
    #2. 冲咖啡的动作进行了两次
    # 这是因为 烧水2 和 磨咖啡豆 这两个节点的后续都是 冲咖啡。实际执行的时候,只要满足其中一个就可以了,这显然不是我们想要的执行流程

    graph = graphBuilder.compile()
    return graph

def buildGraph3():
    graphBuilder = StateGraph(State)

    graphBuilder.add_node("烧水1", 烧温水)
    graphBuilder.add_node("烧水2", 烧开水)
    graphBuilder.add_node("磨咖啡豆", 磨咖啡豆)
    graphBuilder.add_node("冲咖啡", 冲咖啡)

    graphBuilder.add_edge(START, "磨咖啡豆")
    graphBuilder.add_edge(START, "烧水1")
    graphBuilder.add_edge("烧水1", "烧水2")
    # 用这种方式,强制限定必须完成 烧水2 和 磨咖啡豆 这两个动作后才能 冲咖啡 ,这才是我们想要的执行流程
    graphBuilder.add_edge(["烧水2","磨咖啡豆"], "冲咖啡")
    graphBuilder.add_edge("冲咖啡", END)

    graph = graphBuilder.compile()
    return graph

if __name__ == "__main__":
    graph = buildGraph2()

    from tools import showGraph
    showGraph.showGraphInCode(graph, "graph.jpg")

    state:State = {"水温": 28,"产物":"凉水","咖啡固体":"咖啡豆"}
    result = graph.invoke(state)
    print(result)

********************************************************************************
烧温水之前:{'水温': 28, '产物': '凉水', '咖啡固体': '咖啡豆'}
烧温水之后:{'水温': 38, '产物': '温水', '咖啡固体': '咖啡豆'}
********************************************************************************
磨咖啡豆之前:{'水温': 28, '产物': '凉水', '咖啡固体': '咖啡豆'}
磨咖啡豆之后:{'水温': 28, '产物': '凉水', '咖啡固体': '咖啡粉'}
冲咖啡之前:{'水温': 38, '产物': '温水', '咖啡固体': '咖啡粉'}
********************************************************************************
烧开水之前:{'水温': 38, '产物': '温水', '咖啡固体': '咖啡粉'}
烧开水之后:{'水温': 100, '产物': '开水', '咖啡固体': '咖啡粉'}

冲咖啡之后:{'水温': 38, '产物': '咖啡', '咖啡固体': '咖啡粉'}
冲咖啡之前:{'水温': 100, '产物': '咖啡', '咖啡固体': '咖啡粉'}
冲咖啡之后:{'水温': 100, '产物': '咖啡', '咖啡固体': '咖啡粉'}
{'水温': 100, '产物': '咖啡', '咖啡固体': '咖啡粉'}

问题:

​ 1 怎样控制各个并行结构都运行完之后再运行合并节点?

实操:

​ 1 练习langGraph中的并行结构

4 人工介入(human_in_loops)

有些关键环节需要人工决策,这是需要引入人工介入的方式

人工决策的关键点有三个:

  • 必须引入一个带interrupt功能的节点

  • 必须人工决策后再次invoke

  • graph必须带有memory

可以参考下面的代码

复制代码
from langgraph.graph import START,StateGraph,  END
from typing_extensions import TypedDict
from typing import Annotated
from langgraph.types import Command, interrupt
from langgraph.checkpoint.memory import InMemorySaver

def updateTempreture(left, right):
    return max(left, right)

def updateProduct(left,right):
    if left=="加糖咖啡" or right == "加糖咖啡":
        return "加糖咖啡"
    elif left=="咖啡" or right == "咖啡":
        return "咖啡"
    elif left=="开水" or right == "开水":
        return "开水"
    elif left=="温水" or right == "温水":
        return "温水"
    else:
        return "凉水"

def updateSolid(left,right):
    if left=="咖啡粉" or right == "咖啡粉":
        return "咖啡粉"
    else:
        return "咖啡豆"

class State(TypedDict):
    水温: Annotated[int,updateTempreture]
    产物:Annotated[str,updateProduct]
    咖啡固体:Annotated[str,updateSolid]
    是否加糖:str

def 磨咖啡豆(state):
    print("*" * 80)
    print("磨咖啡豆之前:" + str(state))
    state["咖啡固体"] = "咖啡粉"
    print("磨咖啡豆之后:" + str(state))
    return state

def 烧开水(state):
    print("*"*80)
    print("烧开水之前:"+str(state))
    state["产物"] = "没烧开的水"
    if state["水温"]<100:
        state["水温"] = 100
        state["产物"] = "开水"
    print("烧开水之后:" + str(state))
    return state

def 烧温水(state):
    print("*"*80)
    print("烧温水之前:"+str(state))
    state["产物"] = "没烧开的水"
    if state["水温"]<38:
        state["水温"] = 38
        state["产物"] = "温水"
    print("烧温水之后:" + str(state))
    return state

def 冲咖啡(state):
    print("冲咖啡之前:" + str(state))
    state["产物"]="咖啡"
    print("冲咖啡之后:" + str(state))
    return state

def 询问是否加糖(state):
    human_response = interrupt({'':''})
    state["是否加糖"] = human_response
    return state


def 是否加糖分支(state):
    #print(state)
    if state["是否加糖"] == "是":
        return "是"
    elif state["是否加糖"] == "否":
        return "否"

def 加糖(state):
    print("加糖之前:" + str(state))
    state["产物"]="加糖咖啡"
    print("加糖之后:" + str(state))
    return state

def buildGraph4():
    graphBuilder = StateGraph(State)
    checkpointer = InMemorySaver()

    graphBuilder.add_node("烧水1", 烧温水)
    graphBuilder.add_node("烧水2", 烧开水)
    graphBuilder.add_node("磨咖啡豆", 磨咖啡豆)
    graphBuilder.add_node("冲咖啡", 冲咖啡)
    graphBuilder.add_node("询问是否加糖", 询问是否加糖)
    graphBuilder.add_node("加糖", 加糖)

    graphBuilder.add_edge(START, "磨咖啡豆")
    graphBuilder.add_edge(START, "烧水1")
    graphBuilder.add_edge("烧水1", "烧水2")
    graphBuilder.add_edge(["烧水2","磨咖啡豆"], "冲咖啡")
    graphBuilder.add_edge("冲咖啡", "询问是否加糖")
    graphBuilder.add_conditional_edges("询问是否加糖", 是否加糖分支,{"是":"加糖","否":END})
    graphBuilder.add_edge("加糖", END)

    graph = graphBuilder.compile(checkpointer=checkpointer)
    return graph

if __name__ == "__main__":
    graph = buildGraph4()

    from tools import showGraph
    showGraph.showGraphInCode(graph)

    state:State = {"水温": 28,"产物":"凉水","咖啡固体":"咖啡豆"}
    config = {"configurable": {"thread_id": "some_id"}}
    state = graph.invoke(state, config)  # 调用Agent启动工作流

    userDecision = input("先生您好,请问您的咖啡需要加糖吗?(是/否):")
    while userDecision not in {"是", "否"}:
        userDecision = input("先生您好,请问您的咖啡需要加糖吗?(是/否):")

    result = graph.invoke(Command(resume=userDecision), config=config)
    print(result)

***********************************************************************
烧温水之前:{'水温': 28, '产物': '凉水', '咖啡固体': '咖啡豆'}
磨咖啡豆之前:{'水温': 28, '产物': '凉水', '咖啡固体': '咖啡豆'}
烧温水之后:{'水温': 38, '产物': '温水', '咖啡固体': '咖啡豆'}
磨咖啡豆之后:{'水温': 28, '产物': '凉水', '咖啡固体': '咖啡粉'}
********************************************************************************
烧开水之前:{'水温': 38, '产物': '温水', '咖啡固体': '咖啡粉'}
烧开水之后:{'水温': 100, '产物': '开水', '咖啡固体': '咖啡粉'}
冲咖啡之前:{'水温': 100, '产物': '开水', '咖啡固体': '咖啡粉'}
冲咖啡之后:{'水温': 100, '产物': '咖啡', '咖啡固体': '咖啡粉'}
(Interrupt(value='', resumable=True, ns=['询问是否加糖:54cff3fa-02d4-4cac-6957-2bd2b3e9b9c8']),)
先生您好,请问您的咖啡需要加糖吗?(是/否):是
加糖之前:{'水温': 100, '产物': '咖啡', '咖啡固体': '咖啡粉', '是否加糖': '是'}
加糖之后:{'水温': 100, '产物': '加糖咖啡', '咖啡固体': '咖啡粉', '是否加糖': '是'}
{'水温': 100, '产物': '加糖咖啡', '咖啡固体': '咖啡粉', '是否加糖': '是'}

问题:

​ 1 怎样暂停图的运行

​ 2 怎样继续运行图?

实操:

​ 1 练习langGraph中的人工介入

5 子图

随着项目演进,功能不断增多,用单个graph已经太过复杂,而且也不便于调试。

有必要把部分内聚性强的功能块独立出来,做成子图

  • 烧开水子图
  • 询问加糖子图

  • 总图

    from langgraph.graph import START,StateGraph, END
    from typing_extensions import TypedDict
    from typing import Annotated
    from langgraph.types import Command, interrupt
    from langgraph.checkpoint.memory import InMemorySaver

    def updateTempreture(left, right):
    return max(left, right)

    def updateProduct(left,right):
    if left=="加糖咖啡" or right == "加糖咖啡":
    return "加糖咖啡"
    elif left=="咖啡" or right == "咖啡":
    return "咖啡"
    elif left=="开水" or right == "开水":
    return "开水"
    elif left=="温水" or right == "温水":
    return "温水"
    else:
    return "凉水"

    def updateSolid(left,right):
    if left=="咖啡粉" or right == "咖啡粉":
    return "咖啡粉"
    else:
    return "咖啡豆"
    def updateSugur(left,right):
    if left=="是" or right == "是":
    return "是"
    else:
    return "否"

    class State(TypedDict):
    水温: Annotated[int,updateTempreture]
    产物:Annotated[str,updateProduct]
    咖啡固体:Annotated[str,updateSolid]
    是否加糖:Annotated[str,updateSugur]

    def 磨咖啡豆(state):
    print("*" * 80)
    print("磨咖啡豆之前:" + str(state))
    state["咖啡固体"] = "咖啡粉"
    print("磨咖啡豆之后:" + str(state))
    return state

    def 烧水(state):
    print("*"*80)
    print("烧水之前:"+str(state))
    state["产物"] = "没烧开的水"
    state["水温"] = state["水温"]+10
    if state["水温"]>100:
    state["水温"] = 100
    state["产物"] = "开水"
    print("烧水之后:" + str(state))
    return state

    def 按温度处理水(state):
    if state["水温"]== 100:
    return "水烧开了"
    else:
    return "水没开"

    def 得到开水(state):
    return state

    def 冲咖啡(state):
    print("冲咖啡之前:" + str(state))
    state["产物"]="咖啡"
    print("冲咖啡之后:" + str(state))
    return state

    def 询问是否加糖(state):
    human_response = interrupt({'':''})
    state["是否加糖"] = human_response
    return state

    def 是否加糖分支(state):
    #print(state)
    if state["是否加糖"] == "是":
    return "是"
    elif state["是否加糖"] == "否":
    return "否"

    def 加糖(state):
    print("加糖之前:" + str(state))
    state["产物"]="加糖咖啡"
    print("加糖之后:" + str(state))
    return state

    def buildGraph5():
    graphBuilder = StateGraph(State)
    checkpointer = InMemorySaver()

    复制代码
      graphBuilder.add_node("heat water", 烧水)
      graphBuilder.add_node("get boil water", 得到开水)
      graphBuilder.add_node("磨咖啡豆", 磨咖啡豆)
      graphBuilder.add_node("冲咖啡", 冲咖啡)
      graphBuilder.add_node("询问是否加糖", 询问是否加糖)
      graphBuilder.add_node("加糖", 加糖)
    
      graphBuilder.add_edge(START, "磨咖啡豆")
      graphBuilder.add_edge(START, "heat water")
      graphBuilder.add_conditional_edges("heat water", 按温度处理水, {"水烧开了": "get boil water", "水没开": "heat water"})
      graphBuilder.add_edge(["get boil water","磨咖啡豆"], "冲咖啡")
      graphBuilder.add_edge("冲咖啡", "询问是否加糖")
      graphBuilder.add_conditional_edges("询问是否加糖", 是否加糖分支,{"是":"加糖","否":END})
      graphBuilder.add_edge("加糖", END)
    
      graph = graphBuilder.compile(checkpointer=checkpointer)
      return graph

    def buildGraph6():
    #烧水子图
    heatWaterSubGraphBuilder = StateGraph(State)
    heatWaterSubGraphBuilder.add_node("heat water", 烧水)
    heatWaterSubGraphBuilder.add_node("get boil water", 得到开水)
    heatWaterSubGraphBuilder.add_edge(START, "heat water")
    heatWaterSubGraphBuilder.add_edge(START, "heat water")
    heatWaterSubGraphBuilder.add_conditional_edges("heat water", 按温度处理水,{"水烧开了": "get boil water", "水没开": "heat water"})
    heatWaterSubGraph = heatWaterSubGraphBuilder.compile()

    复制代码
      #加糖子图
      checkpointer = InMemorySaver()
      addSugurSubGraphBuilder = StateGraph(State)
      addSugurSubGraphBuilder.add_node("询问是否加糖1", 询问是否加糖)
      addSugurSubGraphBuilder.add_node("加糖", 加糖)
      addSugurSubGraphBuilder.add_edge(START,"询问是否加糖1")
      addSugurSubGraphBuilder.add_conditional_edges("询问是否加糖1", 是否加糖分支, {"是": "加糖", "否": END})
      addSugurSubGraph = addSugurSubGraphBuilder.compile(checkpointer=checkpointer)
    
      #总图
      graphBuilder = StateGraph(State)
      graphBuilder.add_node("得到热水子图",heatWaterSubGraph)
      graphBuilder.add_node("磨咖啡豆", 磨咖啡豆)
      graphBuilder.add_node("冲咖啡", 冲咖啡)
      graphBuilder.add_node("加糖子图", addSugurSubGraph)
      graphBuilder.add_edge(START,"得到热水子图")
      graphBuilder.add_edge(START, "磨咖啡豆")
      graphBuilder.add_edge(["得到热水子图","磨咖啡豆"],"冲咖啡")
      graphBuilder.add_edge("冲咖啡","加糖子图")
      graph = graphBuilder.compile(checkpointer=checkpointer)
      return heatWaterSubGraph,addSugurSubGraph,graph

    if name == "main":
    """graph = buildGraph5()
    showGraph.showGraphInCode(graph, "复杂流程.jpg")"""

    复制代码
      heatWaterSubGraph,addSugurSubGraph,graph = buildGraph6()
      from tools import showGraph
      showGraph.showGraphInCode(heatWaterSubGraph,"烧热水子图.jpg")
      showGraph.showGraphInCode(addSugurSubGraph,"加糖子图.jpg")
      showGraph.showGraphInCode(graph,"总图.jpg")
    
      state:State = {"水温": 28,"产物":"凉水","咖啡固体":"咖啡豆"}
      config = {"configurable": {"thread_id": "some_id"}}
      state = graph.invoke(state, config)  # 调用Agent启动工作流
    
      userDecision = input("先生您好,请问您的咖啡需要加糖吗?(是/否):")
      while userDecision not in {"是", "否"}:
          userDecision = input("先生您好,请问您的咖啡需要加糖吗?(是/否):")
    
      result = graph.invoke(Command(resume=userDecision), config=config)
      print(result)

    磨咖啡豆之前:{'水温': 28, '产物': '凉水', '咖啡固体': '咖啡豆', '是否加糖': ''}
    磨咖啡豆之后:{'水温': 28, '产物': '凉水', '咖啡固体': '咖啡粉', '是否加糖': ''}


    烧水之前:{'水温': 28, '产物': '凉水', '咖啡固体': '咖啡豆', '是否加糖': '否'}
    烧水之后:{'水温': 38, '产物': '没烧开的水', '咖啡固体': '咖啡豆', '是否加糖': '否'}


    烧水之前:{'水温': 38, '产物': '凉水', '咖啡固体': '咖啡豆', '是否加糖': '否'}
    烧水之后:{'水温': 48, '产物': '没烧开的水', '咖啡固体': '咖啡豆', '是否加糖': '否'}


    烧水之前:{'水温': 48, '产物': '凉水', '咖啡固体': '咖啡豆', '是否加糖': '否'}
    烧水之后:{'水温': 58, '产物': '没烧开的水', '咖啡固体': '咖啡豆', '是否加糖': '否'}


    烧水之前:{'水温': 58, '产物': '凉水', '咖啡固体': '咖啡豆', '是否加糖': '否'}
    烧水之后:{'水温': 68, '产物': '没烧开的水', '咖啡固体': '咖啡豆', '是否加糖': '否'}


    烧水之前:{'水温': 68, '产物': '凉水', '咖啡固体': '咖啡豆', '是否加糖': '否'}
    烧水之后:{'水温': 78, '产物': '没烧开的水', '咖啡固体': '咖啡豆', '是否加糖': '否'}


    烧水之前:{'水温': 78, '产物': '凉水', '咖啡固体': '咖啡豆', '是否加糖': '否'}
    烧水之后:{'水温': 88, '产物': '没烧开的水', '咖啡固体': '咖啡豆', '是否加糖': '否'}


    烧水之前:{'水温': 88, '产物': '凉水', '咖啡固体': '咖啡豆', '是否加糖': '否'}
    烧水之后:{'水温': 98, '产物': '没烧开的水', '咖啡固体': '咖啡豆', '是否加糖': '否'}


    烧水之前:{'水温': 98, '产物': '凉水', '咖啡固体': '咖啡豆', '是否加糖': '否'}
    烧水之后:{'水温': 100, '产物': '开水', '咖啡固体': '咖啡豆', '是否加糖': '否'}
    冲咖啡之前:{'水温': 100, '产物': '开水', '咖啡固体': '咖啡粉', '是否加糖': '否'}
    冲咖啡之后:{'水温': 100, '产物': '咖啡', '咖啡固体': '咖啡粉', '是否加糖': '否'}
    (Interrupt(value={'': ''}, resumable=True, ns=['加糖子图:9ae8d0ee-ba4d-48a3-69b4-df1b2b03e913', '询问是否加糖1:02b13a20-a155-fd8a-85ca-830de9865aad']),)
    (Interrupt(value={'': ''}, resumable=True, ns=['加糖子图:9ae8d0ee-ba4d-48a3-69b4-df1b2b03e913', '询问是否加糖1:02b13a20-a155-fd8a-85ca-830de9865aad']),)
    先生您好,请问您的咖啡需要加糖吗?(是/否):是
    加糖之前:{'水温': 100, '产物': '咖啡', '咖啡固体': '咖啡粉', '是否加糖': '是'}
    加糖之后:{'水温': 100, '产物': '加糖咖啡', '咖啡固体': '咖啡粉', '是否加糖': '是'}
    {'水温': 100, '产物': '加糖咖啡', '咖啡固体': '咖啡粉', '是否加糖': '是'}

问题:

​ 1 为什么要划分子图

​ 2 langGraph中怎样把子图纳入总图中使用的?

实操:

​ 1 练习langGraph中的子图

五【掌握】项目实战

【学习目标】

能够使用langGraph完成较复杂的工作报告

1 项目需求

1.1 背景

近年来,汽车行业竞争愈发激烈,各厂商不断推出新车型,以期占领更大的市场份额,尤其是正对年轻女性这个细分市场,竞争已经到了白热化的程度。设计新车型是一项智力和经验密集型的工作,需要非常专业的设计人才,要求汽车行业的竞争格局、高新技术应用、产业链成本及产出等领域都具有深刻的理解。在以前,新车型都是由专家组设计的,这种方式有几个问题:

  • 人力资源瓶颈:需要设计和评估大量新车型,企业没有这么多的专家储备,导致新车型的设计和评估工作一再推迟。

  • 人力成本居高不下:行业专家是企业的核心人才,薪资远高于一般员工,这种设计模式会导致高昂的设计成本。

  • 迭代周期长:需要对各种技术方案的组合进行反复论证,优中选优,需要大量的实践。

  • 思维惯性导致设计固化:专家对行业的认知深刻,也会导致思维很强,设计出的方案缺少新意,尤其是对新兴趋势反应滞后。

1.2 需求

随着大模型的不断发展,使用大模型进行新车型设计的客观条件也渐渐成熟。凯瑞汽车旗下维多利亚品牌(化名)首先提出了这方面的需求。核心要求有以下几点:

  • 搭建全面的专家知识库,并要求快速更新,防止行业知识不全面或者滞后造成的影响。

  • 搭建基于大模型的车型设计系统,要求能够提供完整的车型设计方案,设计新车型的时间不大于10分钟。

  • 搭建新车型评估和初筛系统,从6个方面对设计出的新车型进行初步评估。

1.3 车型设计方案的具体要求

维多利亚品牌在多年的车型设计工作中总结了一套行之有效的设计模式,要求设计方案至少包含以下9个部分

  • 方案的背景和理念

  • 市场的发展趋势分析

  • 客户群体与使用场景分析

  • 技术方案和针对细分市场的定制

  • 竞品分析和差异化竞争优势分析

  • 产品卖点

  • 定价策略

  • 海外市场

  • 销量预估

2 方案选型

方案 实施方法 评估
大参数量模型+整体输出 调用大参数量模型,一次性输出整个设计方案 整体输出长度不足,即使特意用提示词限定长度,方案的后半部分开始出现一些不协调的内容
小参数量模型+整体输出 调用小参数量模型,一次性输出整个设计方案 整体输出长度不足,生成内容质量较低,不可用。后续不再考虑小参数量模型
大参数量模型+分章节输出 调用大参数量模型,设计多个提示词,每次输出一个章节 长度控制比较方便,质量很高,但是各章节之间的细节对不上
大参数量模型+概要设计+分章节输出 调用大参数量模型,先设计提示词,输出车型设计方案的概要,再设计多个提示词,加入概要设计的数据,每次输出一个章节 长度、质量均可控制,各章节之间有很强的关联性

具体落地要分成以下几个步骤:

  • 整体框架使用langGraph构建一个总分结构
  • 首先完成数据收集(行业趋势报告,竞品信息)
  • 然后完成概要设计,包括车型名称、设计理念、设计思路、使用的新技术等关键信息
  • 根据每个章节的具体情况,分别生成,必须遵照概要设计的内容,还可以参考行业趋势报告、竞品信息等资料。
  • 各章节合并成完整的车型设计方案

3 行业趋势和竞品信息收集

这里的行业趋势来自企业内部的RAG系统,竞品信息来自业务部门的关系型数据库,这里用一组函数代替浙西动作。

python 复制代码
def getVictoriaStatus():
    status = """凯瑞维多利亚2024年每月销售量"""
    return status

def getBenBenStatus():
    status = """长安奔奔E-Star是基于燃油车平台改造的"油改电"车型,续航能力(301km)低于维多利亚黑猫(351-405km),且在三电系统、安全配置等方面表现较弱。"""
    return status

def getHaiouStatus():
    status = """亚迪海鸥在销量和安全性上表现突出,最高续航405km,虽略低于维多利亚好猫的501km,但凭借比亚迪的刀片电池技术获得市场认可。维多利亚好猫则在智能化(如"咖啡"智能车控系统)和快充技术上更占优。"""
    return status

def getKaolaStatus():
    status = """北汽新能源推出的考拉品牌直接瞄准女性市场,细分至"妈妈"群体,与维多利亚的"更爱女人的汽车品牌"定位形成竞争。考拉计划通过场景化产品(如母婴出行需求)差异化竞争,"""
    return status

def getFemaleMarketTrend():
    trend = """面向女性客户的汽车品牌正从早期的"性别标签化"营销转向"需求驱动型"创新,未来将围绕精准需求洞察、技术平权设计、社群生态构建三大核心展开。以下是具体趋势分析:"""
    return trend

def getInfomation(state):
    victoriaStatus = getVictoriaStatus()
    benbenStatus = getBenBenStatus()
    haiouStatus = getHaiouStatus()
    kaolaStatus = getKaolaStatus()
    femaleMarketTrend = getFemaleMarketTrend()
    state["victoriaStatus"] = victoriaStatus
    state["competitorStatus"] = benbenStatus+"\n"+haiouStatus+"\n"+kaolaStatus
    state["femaleMarketTrend"] = femaleMarketTrend
    return state

4 调用大参数量模型

python 复制代码
class CustomModel(Runnable):
    def __init__(self, model_endpoint: str, api_key: str):
        self.endpoint = model_endpoint
        self.api_key = api_key

    def invoke(self, prompt: str, modelName:str="deepseek-ai/DeepSeek-V3",maxTokens:int=3000,tempreture=0.6,config: Optional[Dict] = None) -> Dict:
        """调用自定义模型API"""
        import requests

        headers = {"Authorization": f"Bearer {self.api_key}",
                   "Content-Type": "application/json"}
        payload = {
            "model":modelName,
            "messages": [{"role": "user", "content": prompt}],
            "temperature":tempreture,
            "max_tokens": maxTokens
        }
        response = requests.post(self.endpoint, json=payload, headers=headers)
        response.raise_for_status()
        return response.json()['choices'][0]['message']['content']

llm = CustomModel(
        model_endpoint="https://api.siliconflow.cn/v1/chat/completions",
        api_key="*****"
    )

5 生成新车型的基本设定

python 复制代码
prompt = """你是凯瑞汽车旗下维多利亚品牌的车型设计师,维多利亚品牌主要面向年轻的女性客户,根据【竞品资料】和【女性汽车市场发展趋势】,结合你自己的思考,为维多利亚品牌设计一款新车型,规划这个新车型、技术方案和定价策略。300字以内。

    【竞品资料】
    """ + state["competitorStatus"] + """

    【女性汽车市场发展趋势】
    """ + state["femaleMarketTrend"]

6 根据基本设定和其他资料,完成每个章节

python 复制代码
prompt = """你是凯瑞汽车旗下维多利亚品牌的车型设计师,维多利亚品牌主要面向年轻的女性客户,根据【女性汽车市场发展趋势】和【新车型基本设定】,结合你自己的思考,完成新车型设计方案的第一章"一、设计背景和理念",说明新车型的设计背景和理念。各级标题使用markdown格式。600字以内。

    【女性汽车市场发展趋势】
    """ + state["femaleMarketTrend"]+"""
    
    【新车型基本设定】
    
    """+state["basicSettings"]

7 合并产出最终

python 复制代码
def merge(state):
    state["design"]=state["chapter1"]+"\n"+ \
                    state["chapter2"]+"\n"+ \
                    state["chapter3"] + "\n" + \
                    state["chapter4"] + "\n" + \
                    state["chapter5"] + "\n" + \
                    state["chapter6"] + "\n" + \
                    state["chapter7"] + "\n" + \
                    state["chapter8"] + "\n" + \
                    state["chapter9"]
    return state

8 设置和编译图

python 复制代码
def buildGraph():
    graphBuilder = StateGraph(State)

    graphBuilder.add_node("generateBasicSettings", generateBasicSettings)
    graphBuilder.add_node("getInfomation",getInfomation)
    graphBuilder.add_node("generateChapter1",generateChapter1)
    graphBuilder.add_node("generateChapter2", generateChapter2)
    graphBuilder.add_node("generateChapter3", generateChapter3)
    graphBuilder.add_node("generateChapter4", generateChapter4)
    graphBuilder.add_node("generateChapter5", generateChapter5)
    graphBuilder.add_node("generateChapter6", generateChapter6)
    graphBuilder.add_node("generateChapter7", generateChapter7)
    graphBuilder.add_node("generateChapter8", generateChapter8)
    graphBuilder.add_node("generateChapter9", generateChapter9)
    graphBuilder.add_node("merge", merge)

    graphBuilder.add_edge(START, "getInfomation")
    graphBuilder.add_edge("getInfomation", "generateBasicSettings")
    graphBuilder.add_edge("generateBasicSettings", "generateChapter1")
    graphBuilder.add_edge("generateBasicSettings", "generateChapter2")
    graphBuilder.add_edge("generateBasicSettings", "generateChapter3")
    graphBuilder.add_edge("generateBasicSettings", "generateChapter4")
    graphBuilder.add_edge("generateBasicSettings", "generateChapter5")
    graphBuilder.add_edge("generateBasicSettings", "generateChapter6")
    graphBuilder.add_edge("generateBasicSettings", "generateChapter7")
    graphBuilder.add_edge("generateBasicSettings", "generateChapter8")
    graphBuilder.add_edge("generateBasicSettings", "generateChapter9")
 graphBuilder.add_edge(["generateChapter1","generateChapter2","generateChapter3","generateChapter4","generateChapter5","generateChapter6","generateChapter7","generateChapter8","generateChapter9"], "merge")

    graph = graphBuilder.compile()
    return graph

9 程序入口

python 复制代码
if __name__ == "__main__":
    graph = buildGraph()

    from tools import showGraph
    showGraph.showGraphInCode(graph, "graph.jpg")

    state: State = {}
    result = graph.invoke(state)
    print("新车型设计书  " + "*" * 80)
    print(result["design"])

10 最终结果

最终生成的结果是一份完整的新车型设计书,内容比较长,下面的截图只展示开头和结尾,详细内容可以查看 designer8_result.md

...

问题:

​ 1 为什么不直接调用大模型生成全文?

​ 2 为什么要生成车型基础设置?

实操:

​ 1 使用本章的工作模式,完成一份较复杂的文档

六 【掌握】智能体的设计模式

【学习目标】

了解使用框架的情境和优缺点

掌握设计智能体的六种模式

能够根据情况,选择合适的设计模式并完成应用。

1 重要文章介绍

工业界和学术界对于智能体(agent)的定义和实现方式仍然由不小的争议,但其中也形成了一些比较系统性的观点。

在2024年12月20日发布的这篇文章**《Building Effective Agents》**https://www.anthropic.com/engineering/building-effective-agents中,Anthropic公司分享了他们在过去一年中与多个行业团队合作开发大型语言模型(Large Language Model, LLM)智能体的经验。文章的核心观点令人深思:最成功的智能体实现并非依赖于复杂的框架或专门的库,而是通过简单、可组合的模式构建而成。

2 构建智能体的思路

构建智能体有两种思路:

  • 工作流(workflow):通过预先构建的代码路径,综合运用大语言模型和工具,实现最终目标。

  • 智能体(agent):大语言模型动态地生成工作流程,并使用工具,如果执行过程中发生意外情况,自行纠偏,保持对任务完成方式的掌控。

这两种方式都能解决高度复杂的任务。工作流的方式提供了更好的掌控能力和一致性,但必须要为特定类型的任务量身定制工作流程;智能体的方式可以不用受定制化的约束。而且能够更加灵活地解决问题,这是我们所需的终极形态,但可控性不足,在一些非常复杂地任务上,现有大模型也许还不足以给高度复杂地任务制定出最优解决方案。

3 真的需要智能体和框架吗?

在构架基于大语言模型地应用时,不是所有情况下都需要构建智能体,也不是所有的智能体都必须使用框架。有这样几个层次:

  • 直接使用大模型:如果只是简单调用大语言模型就可以解决的任务,直接部署大语言模型即可。比如通用知识问答,做数学题,代码生成等。
  • 大模型+少量业务逻辑:这种情况需要构建智能体,但是尽量不使用框架。因为框架通常会引入额外的封装和抽象层,可能会掩盖底层的提示词和响应,增加调试难度和不必要的复杂度。如果确实需要使用框架,最好明确地知道框架的底层逻辑。
  • 复杂的智能体:建议使用框架。但不要一次开发得过于复杂,应该从基础功能开始,逐步增加复杂度。

总的原则就是如无必要,勿增实体

问题:

​ 1 理论上,智能体模式应该比工作流模式更强,为什么现在多数应用都使用工作流模式?

​ 2 使用框架的优点是什么?

​ 3 什么情境下应该引入框架

4 智能体的六种设计模式

4.1 工作流:提示链(Prompt Chaining)

对于复杂工作有两个思路:

  • 写一个尽可能全面的复杂提示词,一次性完成任务。

  • 将工作分成多个简单步骤,逐步完成。

后者的本质是人工拆解的思维链,实际效果要好得多。

例如写一份报告,可以这样处理:

langGrap的代码实现如下:

python 复制代码
from langgraph.graph import StateGraph, START,END
from langchain_ollama import ChatOllama
from typing_extensions import TypedDict

class State(TypedDict):
    topic:str
    outline:str
    draft:str
    paper:str

llm = ChatOllama(model="qwen2.5:7b")

def getOutline(state):
    prompt = """针对""""+state["topic"]+""""这个主题,写一份分析报告的大纲,包括历史由来,现状分析,原因分析,解决办法,趋势评估等几个主要部分。"""
    state["outline"]=llm.invoke(prompt).content
    print("outline  "+"*"*80)
    print(state["outline"])
    return state

def getDraft(state):
    prompt = """根据【大纲】,写一份完整的分析报告,要求语言流畅,逻辑清晰,主要内容围绕大纲展开,内容2000字左右。
    
    【大纲】
    """+state["outline"]
    state["draft"]=llm.invoke(prompt).content
    print("draft  "+"*"*80)
    print(state["draft"])
    return state

def getPaper(state):
    prompt = """请对下面的【分析报告】进行全文润色,包括语法检查,用词优化和句式调整,要求文章语言风格自然流畅,逻辑清晰,表达生动而简洁,避免生硬而刻板的AI习作风格,与原文的主要意思保持不变。
    
        【分析报告】
        """ + state["draft"]
    state["paper"] = llm.invoke(prompt).content
    print("paper  "+"*"*80)
    print(state["paper"])
    return state

def buildGraph():
    graphBuilder = StateGraph(State)

    graphBuilder.add_node("getOutline", getOutline)
    graphBuilder.add_node("getDraft", getDraft)
    graphBuilder.add_node("getPaper", getPaper)

    graphBuilder.add_edge(START, "getOutline")
    graphBuilder.add_edge("getOutline", "getDraft")
    graphBuilder.add_edge("getDraft", "getPaper")
    graphBuilder.add_edge("getPaper",END)

    graph = graphBuilder.compile()
    return graph

if __name__ == "__main__":
    graph = buildGraph()

    from tools import showGraph
    showGraph.showGraphInCode(graph, "graph.jpg")

    state: State = {"topic": "墨西哥毒品泛滥情况报告"}
    result = graph.invoke(state)
    print("final  " + "*" * 80)
    print(result)

产出文档:

a_paper.md

问题:

​ 1 为什么要引入提示链模式

实操:

​ 1 练习提示链模式

4.2 工作流:分支(Routing)

对于特定领域的问题,不应该让大模型随意输出,如果能把大模型的输出导入相关的领域,会明显提升输出质量

langGraph的代码实现如下:

python 复制代码
from langgraph.graph import StateGraph, START, END
from langchain_ollama import ChatOllama
from typing_extensions import TypedDict

class State(TypedDict):
    topic: str
    aspect: str
    faction:str
    debate: str

llm = ChatOllama(model="qwen2.5:7b")

def getFraction(state):
    prompt = """你是一位国学大师,你认为""""+state["topic"]+""""这个观点是 """+state["aspect"]+""" 的,那么在["儒家","法家","道家"]这三个学派中,你最可能持哪一派的观点?只允许使出学派的名称,不允许输出其他字符。"""
    state["faction"] = llm.invoke(prompt).content
    print("faction  " + "*" * 80)
    print(state["faction"])
    return state

def selectFraction(state):
    if state["faction"] == "儒家":
        return "Confucian"
    elif state["faction"] == "法家":
        return "Legalists"
    elif state["faction"] == "道家":
        return "Taoism"

def getDebateFromConfucian(state):
    prompt = """你是一位国学大师,你认为""""+state["topic"]+""""这个观点是 """+state["aspect"]+""" 的,这是儒家的观点,请使用儒家思想对这个问题详细展开论证。"""
    state["debate"] = llm.invoke(prompt).content
    print(state["debate"])
    return state

def getDebateFromLegalists(state):
    prompt = """你是一位国学大师,你认为""""+state["topic"]+""""这个观点是 """+state["aspect"]+""" 的,这是法家的观点,请使用法家思想对这个问题详细展开论证。"""
    state["debate"] = llm.invoke(prompt).content
    print(state["debate"])
    return state

def getDebateFromTaoism(state):
    prompt = """你是一位国学大师,你认为""""+state["topic"]+""""这个观点是 """+state["aspect"]+""" 的,这是道家的观点,请使用道家思想对这个问题详细展开论证。"""
    state["debate"] = llm.invoke(prompt).content
    print(state["debate"])
    return state

def buildGraph():
    graphBuilder = StateGraph(State)

    graphBuilder.add_node("getFraction", getFraction)
    graphBuilder.add_node("getDebateFromConfucian", getDebateFromConfucian)
    graphBuilder.add_node("getDebateFromLegalists", getDebateFromLegalists)
    graphBuilder.add_node("getDebateFromTaoism", getDebateFromTaoism)

    graphBuilder.add_edge(START, "getFraction")
    graphBuilder.add_conditional_edges("getFraction",selectFraction,{"Confucian":"getDebateFromConfucian","Legalists":"getDebateFromLegalists","Taoism":"getDebateFromTaoism"})

    graph = graphBuilder.compile()
    return graph


if __name__ == "__main__":
    graph = buildGraph()

    from tools import showGraph
    showGraph.showGraphInCode(graph, "graph.jpg")

    state: State = {"topic": "仁义不施而攻守之势异也","aspect":"错误"}
    result = graph.invoke(state)
    print("final  " + "*" * 80)
    print(result)
复制代码
法家
作为一位采用法家思想的代表人物,我会这样来论述"仁义不施而攻守之势异也"的观点:

首先,我们需要明确的是,"仁义不施而攻守之势异也"这句话出自《史记·项羽本纪》,实际上是汉初贾谊对秦朝灭亡原因的一种总结。但若以法家视角来看待这个问题,则可以从以下角度进行分析。

1. **法律与秩序的重要性**:在法家思想中,强调国家治理的关键在于制定严明的法令和有效的制度来维护社会秩序,确保国家的长治久安。"仁义"虽为儒家推崇的重要道德准则,但在国君或统治者的视角下,更应重视的是法治与严刑峻法。因此,在秦朝统一六国的过程中,法家思想指导下的秦始皇采取了严厉的法律来加强中央集权和稳定社会秩序,这是取得巨大成功的关键因素之一。

2. **权力集中与效率**:法家主张权力的高度集中以提高行政效率,确保政策的有效执行。在统一天下之后,秦朝通过严刑峻法、重农抑商等措施迅速巩固了统治基础,并进一步加强中央集权制度的建设,如推行郡县制、统一货币和度量衡等。这些措施不仅增强了国家的整体实力,也提高了社会治理效率。

3. **以法治国与攻守之势变化**:秦朝初年,由于其强大的军事力量和严密的法律体系,在对外征战中占据优势;但随着国内矛盾日益尖锐和社会不满情绪增长,"仁义"的缺失使得普通民众缺乏对新政权的认可和支持。当项羽率领起义军反攻时,面对的是一个内部动荡不安、外部压力巨大的秦朝,这实际上削弱了其抵抗能力。

4. **变法与改革**:在面临内外交困之时,如果能够适时进行政治和经济上的变革,调整政策以适应新形势的发展要求,或许可以缓解危机。然而,在秦二世时期缺乏有效的改革措施来改善民生、提高民众满意度,最终导致了政权的崩溃。因此,"仁义"虽未能直接转化为生产力,但通过适时的法律调整和社会管理创新,仍可有效应对国家治理中的各种挑战。

综上所述,从法家的角度来看,"仁义不施而攻守之势异也"的说法并非完全错误。关键在于如何在特定的历史背景下合理运用法治与权谋策略来维护统治秩序、提升国力,并在此基础上灵活调整政策以应对内外环境的变化。

实操:

​ 1 练习分支模式

4.3 工作流:并行化(Parallelization)

在设计应用时,有些功能互补干扰,可以考虑并行化

langGraph的代码实现如下:

python 复制代码
from langgraph.graph import StateGraph, START, END
from langchain_ollama import ChatOllama
from typing_extensions import TypedDict
from typing import Annotated
import random

def updateReceiveDate(left,right):
    return left

class State(TypedDict):
    sendDate: int
    transTime: int
    receiveDate:Annotated[int,updateReceiveDate]
    returnMessage:str

llm = ChatOllama(model="qwen2.5:7b")

def getSendData(state):
    sendData = random.randint(1,25)
    state["sendDate"] = sendData
    return state

def getTransTime(state):
    transTime = random.randint(3,5)
    state["transTime"] = transTime
    return state

def sendMessage(state):
    prompt = "你是江南机械厂的销售员王钟期,顾客张董事长在我厂预定了一批机器设备,希望能9月"+str(state["receiveDate"])+"日收到货物,厂里最早的发货日期是9月"+str(state["sendDate"])+"日,运输时间是"+str(state["transTime"])+"天,给客户发出适当的回复。"
    state["returnMessage"] = llm.invoke(prompt).content
    print("returnMessage  " + "*" * 80)
    print(state["returnMessage"])
    return state

def buildGraph():
    graphBuilder = StateGraph(State)

    graphBuilder.add_node("getSendData", getSendData)
    graphBuilder.add_node("getTransTime", getTransTime)
    graphBuilder.add_node("sendMessage", sendMessage)

    graphBuilder.add_edge(START, "getSendData")
    graphBuilder.add_edge(START, "getTransTime")
    graphBuilder.add_edge(["getSendData","getTransTime"], "sendMessage")

    graph = graphBuilder.compile()
    return graph

if __name__ == "__main__":
    graph = buildGraph()

    from tools import showGraph

    showGraph.showGraphInCode(graph, "graph.jpg")

    state: State = {"receiveDate": 18}
    result = graph.invoke(state)
    print("final  " + "*" * 80)
    print(result)
复制代码
尊敬的张董事长:

您好!我是江南机械厂的销售员王钟期,感谢您对我司的信任与支持。非常理解您对供货期限的需求和期望。

我们收到您的订单后,已经为您进行了详细的生产和发货安排。考虑到我们的生产周期以及目前的排产计划,我们最早能够安排发货的时间是9月18日。按照当前物流运输时间预计为5天计算,货物将大概率在9月23日送达贵司。

为了确保您能够在最短的时间内收到所需的机器设备,请允许我再次确认您的紧急需求情况以及相关细节,并请告知我们是否需要进一步调整生产或发货计划以更好地满足您的预期日期。如有任何特殊要求或者需要变更,还请您及时与我联系以便我们能够做出相应的调整。

如果一切顺利的话,我们将努力确保在9月23日之前把您订购的机器设备安全送达贵司,并保证货物质量达到您期望的标准。感谢您的耐心等待和理解!

期待您的回复,如有任何疑问或需要进一步帮助,请随时与我沟通。

祝商祺,
王钟期
江南机械厂 销售部

实操:

​ 1 练习并行模式

4.4 工作流:计划-执行(Orchestrator-Workers)

有些工作体量较大,无法一次完成,但各部分之间有很强的关联,也不适合多次完成,可以采用计划-执行模式

langGraph的代码实现如下:

python 复制代码
from langgraph.graph import StateGraph, START, END
from langchain_ollama import ChatOllama
from langgraph.types import Send
from typing_extensions import TypedDict
from typing import Annotated
from pydantic import BaseModel,Field
import operator

class Section(BaseModel):
    num: int = Field(description="章节序号")
    name:str=Field(description="章节标题,采用"【第三章】 迷雾重现"的形式")
    description:str=Field(description="章节描述,最前面是章节标题,后面是章节内容。章节标题采用"【第三章】 迷雾重现"的形式,单独一行")

class Sections(BaseModel):
    sections: list[Section] = Field(description="文章的各个子章节")

class State(TypedDict):
    storyLine:str
    sections:list[Section]
    completedSections:Annotated[list,operator.add]
    novel:str

class WorkerState(TypedDict):
    section:Section
    completedSections:Annotated[list,operator.add]

llm = ChatOllama(model="qwen2.5:7b")

def getWholeStory(state):
    prompt = "你是一位著名小说家,正在创作一部精彩的侦探小说,首先给出故事梗概,1000字左右。"
    state["storyLine"] = llm.invoke(prompt).content
    print("storyLine  " + "*" * 80)
    print(state["storyLine"])
    return state

def orchestrate(state):
    planner = llm.with_structured_output(Sections)
    result = planner.invoke("""你是一位著名小说家,正在创作一部精彩的侦探小说,根据下面的【故事梗概】,将小说分成10个章节,并给出章节的剧情发展。1000字左右。
                            【故事梗概】
                            """+state["storyLine"])
    state["sections"] = result.sections
    print("sections  " + "*" * 80)
    for section in state["sections"]:
        print(str(section.name+'        '+section.description))
    return state

def work(state:WorkerState):
    prompt = """你是一位著名小说家,正在创作一部精彩的侦探小说,根据下面提供的【章节标题】和【章节概述】,完成其中的一个章节,最前面是章节标题,后面是章节内容。章节标题采用"【第三章】 迷雾重现"的形式,单独一行。章节的序号为"""+str(state["section"].num)+""",本章节长度1500字左右
    
    【章节标题】
    """+state["section"].name+"""
    
    【章节概述】
    """+state["section"].description
    result = llm.invoke(prompt)
    return {"num":state["section"].num,"name":state["section"].name,"description":state["section"].description,"completedSections":[{"num":state["section"].num,"content":result.content}]}

def synthesizer(state):
    completedSections = sorted(state["completedSections"],key=lambda completeSection:completeSection["num"])
    novel = "\n\n".join([completeSection["content"] for completeSection in completedSections])
    state["novel"] = novel
    return state

def assignWorkers(state:State):
    return [Send("work",{"section":s}) for s in state["sections"]]

def buildGraph():
    graphBuilder = StateGraph(State)

    graphBuilder.add_node("getWholeStory", getWholeStory)
    graphBuilder.add_node("orchestrate", orchestrate)
    graphBuilder.add_node("work", work)
    graphBuilder.add_node("synthesizer", synthesizer)

    graphBuilder.add_edge(START, "getWholeStory")
    graphBuilder.add_edge("getWholeStory", "orchestrate")
    graphBuilder.add_conditional_edges("orchestrate",assignWorkers,["work"])
    graphBuilder.add_edge("work", "synthesizer")

    graph = graphBuilder.compile()
    return graph

if __name__ == "__main__":
    graph = buildGraph()

    from tools import showGraph
    showGraph.showGraphInCode(graph, "graph.jpg")

    state: State = {}
    result = graph.invoke(state)
    print("final  " + "*" * 80)
    print(result["novel"])

产出的侦探小说

d_novel.md

问题:

​ 1 为什么引入计划执行模式?

​ 2 计划执行模式是怎样实施的

实操:

​ 1 练习计划执行模式

4.5 工作流:生成-评估(Evaluator-Optimizer)

有些情况下大模型生成的结果存在某些方面的不足,需要做针对性优化,可以采用生成-评估模式。

langGraph的代码实现如下:

python 复制代码
from langgraph.graph import StateGraph, START, END
from langchain_ollama import ChatOllama
from typing_extensions import TypedDict
import json

class State(TypedDict):
    topic:str
    article:str
    feedback:str
    qualified:str
    count:int


llm = ChatOllama(model="qwen2.5:7b")

def generate(state):
    if state.get("feedback"):
        prompt = """根据提供的主题写一篇论证文章。确保文章逻辑严密、有说服力。
        主题为"""+state["topic"]+"""
        同时你需要考虑如下的修改建议:"""+state["feedback"]
    else:
        prompt = """根据提供的主题写一篇论证文章。确保文章逻辑严密、有说服力。
                主题为""" + state["topic"]
        prompt = """你是一位小学生,完全不会写作文,现在需要你写一篇论证文章。
                 主题为""" + state["topic"]
    result = llm.invoke(prompt)
    state["count"] += 1
    state["article"] = result.content
    print("generate  " + "*" * 80)
    print(state["article"])
    return state

def evaluate(state):
    prompt = """判断【论证文章】是否很好地论证了【主题】,是否逻辑严密,有说服力。如果不合格,给出具体的修改意见,并按照【指定格式】输出,【指定格式】中的"是否合格"只允许输出"是"或者"否"。只允许输出【指定格式】规定的字符,不允许输出任何其他字符。
    
    【指定格式】
    {"是否合格":"", "修改意见":""}
    
    【主题】
    """+state["topic"]+"""
    
    【论证文章】
    """+state["article"]

    result = llm.invoke(prompt)
    print(result.content)
    resultJson = json.loads(result.content)

    print("evaluate  " + "*" * 80)
    print(result.content)
    state["qualified"] = resultJson["是否合格"]
    state["feedback"] = resultJson["修改意见"]
    return state

def judgement(state):
    if state["count"] >= 2:
        return "accept"
    else:
        if state["qualified"] == "合格":
            return "accept"
        else:
            return "reject"

def buildGraph():
    graphBuilder = StateGraph(State)

    graphBuilder.add_node("generate", generate)
    graphBuilder.add_node("evaluate", evaluate)

    graphBuilder.add_edge(START, "generate")
    graphBuilder.add_edge("generate", "evaluate")
    graphBuilder.add_conditional_edges("evaluate",judgement, {"accept":END,"reject":"generate"})

    graph = graphBuilder.compile()
    return graph

if __name__ == "__main__":
    graph = buildGraph()

    from tools import showGraph

    showGraph.showGraphInCode(graph, "graph.jpg")

    state: State = {"topic":"日本经济会在未来再次崛起","count":0}
    result = graph.invoke(state)
    print("final  " + "*" * 80)
    print(result)
复制代码
当然可以帮你构思一篇关于"日本经济会在未来再次崛起"的论证文章。考虑到你是小学四年级的学生,我会尽量使用简单明了的语言和概念来帮助你理解并构建你的作文。

### 日本经济会不会再次崛起?

#### 我的观点

我认为,虽然过去几年日本的经济增长速度较慢,但日本依然有许多机会在未来重新成为世界经济的重要力量之一。

#### 为什么这么说呢?

1. **先进的技术**:日本是全球领先的科技大国。他们拥有许多世界顶级的企业和研发中心,比如丰田、索尼等。这些企业不断创新,研发出新产品和技术。
   
2. **教育水平高**:在日本,人们非常重视教育。这种高质量的教育为日本培养了大量优秀的人才,这些人将为企业带来创新思维和解决问题的能力。

3. **稳定的经济政策**:日本政府制定了一些有效的经济政策来促进经济增长。比如,他们鼓励企业进行技术升级和产品开发,从而提高生产效率和市场竞争力。

4. **国际市场上的优势地位**:由于日本产品质量高、品牌信誉好,在国际市场上一直保持着较高的竞争力。这为日本未来的发展奠定了坚实的基础。
   
5. **地理位置有利**:日本位于亚洲东部的边缘位置,靠近多个重要的贸易路线。这样的地理优势有助于促进对外贸易和投资。

#### 结论

虽然面对着一些挑战,如人口老龄化等社会问题,但凭借上述各方面的实力与潜力,我相信在不远的将来,日本经济会有新的突破,并再次成为全球经济增长的重要力量之一。

希望这个简单的框架能够帮助你构思并写出一篇关于"日本经济会不会再次崛起"的作文。记得,在写作文时要尽量用简单明了的语言表达自己的观点和理由哦!
evaluate  ********************************************************************************
合格
这篇论证文章总体上较好地阐述了主题,但可以进一步完善以增强逻辑严密性和说服力。

这种模式稍加改动甚至可以让大模型左右互博,搞辩论赛

python 复制代码
from langgraph.graph import StateGraph, START, END
from langchain_ollama import ChatOllama
from typing_extensions import TypedDict


class State(TypedDict):
    topic: str
    pos: str
    neg: str
    count: int

llm = ChatOllama(model="qwen2.5:7b")

def poser(state):
    if state.get("neg"):
        prompt = """你是一位辩论高手,现在在辩论中担任正方,根据【论题】和【反方发言】,用口语化的风格说出你的观点,驳斥【反方发言】,证明【论题】的正确性。500字以内。
        【论题】
        """ + state["topic"] + """
        【反方发言】
        """ + state["neg"]
    else:
        prompt = """你是一位辩论高手,现在在辩论中担任正方,用口语化的风格说出你的观点,证明【论题】的正确性。
        【论题】
        """ + state["topic"]
    result = llm.invoke(prompt)
    state["count"] += 1
    state["pos"] = result.content
    print("正方发言  " + "*" * 80)
    print(state["pos"])
    return state


def neger(state):
    prompt = """你是一位辩论高手,现在在辩论中担任反方,根据【论题】和【正方发言】,用口语化的风格说出你的观点,驳斥【正方发言】,证明【论题】的正确性。500字以内。
        【论题】
        """ + state["topic"] + """
        【正方发言】
        """ + state["pos"]
    result = llm.invoke(prompt)
    state["neg"] = result.content
    print("反方发言  " + "*" * 80)
    print(state["neg"])
    return state


def judgement(state):
    if state["count"] >= 10:
        return "finish"
    else:
        return "proceed"

def buildGraph():
    graphBuilder = StateGraph(State)

    graphBuilder.add_node("poser", poser)
    graphBuilder.add_node("neger", neger)

    graphBuilder.add_edge(START, "poser")
    graphBuilder.add_edge("poser", "neger")
    graphBuilder.add_conditional_edges("neger", judgement, {"finish": END, "proceed": "poser"})

    graph = graphBuilder.compile()
    return graph


if __name__ == "__main__":
    graph = buildGraph()

    from tools import showGraph
    showGraph.showGraphInCode(graph, "graph.jpg")

    state: State = {"topic": "高彩礼是造成结婚率下降的主要原因", "count": 0}
    result = graph.invoke(state)
    print("final  " + "*" * 80)
    print(result)

辩论赛节选

debate.md

实操:

​ 1 使用生成评估模式,组织一次大模型辩论赛

4.6 智能体(agent)

工作流是任务定制和特化加强的,不具备普适性,真正的智能体是我们的终极目标,大模型+工具集的方式已经显示了很大的潜力

python 复制代码
from langchain_ollama import ChatOllama
from langgraph.prebuilt import create_react_agent

def getTrainSchedule(queryDate,start,end):
    """根据指定的日期、起点城市名称和终点城市名称,查询列车班次"""
    print("获取列车时刻表")
    result = [["D81","12:24","14:30","北京西站","540"],["K4427","15:38","21:30","北京站","220"]]
    resultStr = "您查询的在 "+queryDate+" 这一天从 "+start+" 到 "+end+" 的列车共有 "+str(len(result))+"班:\n"
    for res in result:
        resultStr+=res[0]+" 次列车:发车时间为: "+res[1]+" 到站时间为: "+res[2]+" 发车站为: "+res[3]+" 票价为: "+res[4]+"\n"
    return resultStr

def getAvailableHotel(queryDate,location):
    """根据指定的日期和城市名称,查询可用的旅店"""
    print("获取目的地旅馆的情况")
    result = [["丽晶酒店", "五星级", "大床房", "1200"], ["丽晶大宾馆", "二星级", "标准间", "300"]]
    resultStr = "您查询的 " + queryDate + " 这一天可以预定的酒店有" + str(len(result)) + "家:\n"
    for res in result:
        resultStr += res[0] + " 等级为 " + res[1] + " 房间是 " + res[2] + " 价格是: " + res[3] + "\n"
    return resultStr

toolList = [getWeather,getTrainSchedule,getAvailableHotel]

agent = create_react_agent(
            model=ChatOllama(model="qwen3:8b"),
            tools=toolList,
            prompt=""
        )

if __name__ == '__main__':
    prompt = "你是一位可靠的个人AI助理,我要在6月15日从北京到青岛旅游,请帮我安排一下车次和旅店。这次旅行要求快捷舒适。"
    result = agent.invoke(
            {"messages": [{"role": "user", "content": prompt}]}
        )
    print(result["messages"][-1].content)
复制代码
获取列车时刻表
获取目的地旅馆的情况
<think>
好的,用户需要从北京到青岛的旅游安排,包括车次和住宿,要求快捷舒适。首先看列车信息,有两个选项:D81次和K4427次。D81次是高铁,时间更短,12:24发车,14:30到达,票价540元,比较快捷舒适。K4427次是普通列车,时间较长,15:38发车,21:30到达,票价便宜,但时间上可能不太适合追求快捷的用户。

然后是酒店,用户可能希望舒适,所以丽晶酒店五星级大床房1200元虽然价格高,但舒适性符合要求。而丽晶大宾馆二星级标准间价格便宜,但可能设施较旧,需要确认用户预算。用户提到"快捷舒适",可能更倾向于高铁和五星级酒店,但需要考虑预算。如果用户预算充足,推荐D81次和丽晶酒店;如果预算有限,K4427次加上二星级酒店可能更合适。需要进一步询问用户偏好,但根据现有信息,优先推荐高铁和五星级选项。
</think>

根据您的需求,推荐以下安排:

🚄 **列车建议**:
- 选择 **D81次高铁**(12:24北京西发车 → 14:30抵达青岛),耗时2小时,座位宽敞,运行平稳,符合"快捷舒适"需求。

🏨 **酒店建议**:
- **丽晶酒店(五星级)**:大床房1200元/晚,设施新潮,服务优质,适合追求舒适体验。
- 若预算有限,可考虑 **丽晶大宾馆(二星级)**:标准间300元/晚,性价比高,但需提前确认房态。

⚠️ **温馨提示**:
1. 高铁票务紧张,建议提前购票。
2. 青岛6月气候宜人,可考虑海边酒店,但需注意防蚊措施。
3. 若需进一步优化,可补充预算或偏好(如是否接受中转、酒店位置等)。

实操:

​ 1 尝试创建智能体

bate.md](...\05-截图\debate.md)

实操:

​ 1 使用生成评估模式,组织一次大模型辩论赛

4.6 智能体(agent)

工作流是任务定制和特化加强的,不具备普适性,真正的智能体是我们的终极目标,大模型+工具集的方式已经显示了很大的潜力

python 复制代码
from langchain_ollama import ChatOllama
from langgraph.prebuilt import create_react_agent

def getTrainSchedule(queryDate,start,end):
    """根据指定的日期、起点城市名称和终点城市名称,查询列车班次"""
    print("获取列车时刻表")
    result = [["D81","12:24","14:30","北京西站","540"],["K4427","15:38","21:30","北京站","220"]]
    resultStr = "您查询的在 "+queryDate+" 这一天从 "+start+" 到 "+end+" 的列车共有 "+str(len(result))+"班:\n"
    for res in result:
        resultStr+=res[0]+" 次列车:发车时间为: "+res[1]+" 到站时间为: "+res[2]+" 发车站为: "+res[3]+" 票价为: "+res[4]+"\n"
    return resultStr

def getAvailableHotel(queryDate,location):
    """根据指定的日期和城市名称,查询可用的旅店"""
    print("获取目的地旅馆的情况")
    result = [["丽晶酒店", "五星级", "大床房", "1200"], ["丽晶大宾馆", "二星级", "标准间", "300"]]
    resultStr = "您查询的 " + queryDate + " 这一天可以预定的酒店有" + str(len(result)) + "家:\n"
    for res in result:
        resultStr += res[0] + " 等级为 " + res[1] + " 房间是 " + res[2] + " 价格是: " + res[3] + "\n"
    return resultStr

toolList = [getWeather,getTrainSchedule,getAvailableHotel]

agent = create_react_agent(
            model=ChatOllama(model="qwen3:8b"),
            tools=toolList,
            prompt=""
        )

if __name__ == '__main__':
    prompt = "你是一位可靠的个人AI助理,我要在6月15日从北京到青岛旅游,请帮我安排一下车次和旅店。这次旅行要求快捷舒适。"
    result = agent.invoke(
            {"messages": [{"role": "user", "content": prompt}]}
        )
    print(result["messages"][-1].content)
复制代码
获取列车时刻表
获取目的地旅馆的情况
<think>
好的,用户需要从北京到青岛的旅游安排,包括车次和住宿,要求快捷舒适。首先看列车信息,有两个选项:D81次和K4427次。D81次是高铁,时间更短,12:24发车,14:30到达,票价540元,比较快捷舒适。K4427次是普通列车,时间较长,15:38发车,21:30到达,票价便宜,但时间上可能不太适合追求快捷的用户。

然后是酒店,用户可能希望舒适,所以丽晶酒店五星级大床房1200元虽然价格高,但舒适性符合要求。而丽晶大宾馆二星级标准间价格便宜,但可能设施较旧,需要确认用户预算。用户提到"快捷舒适",可能更倾向于高铁和五星级酒店,但需要考虑预算。如果用户预算充足,推荐D81次和丽晶酒店;如果预算有限,K4427次加上二星级酒店可能更合适。需要进一步询问用户偏好,但根据现有信息,优先推荐高铁和五星级选项。
</think>

根据您的需求,推荐以下安排:

🚄 **列车建议**:
- 选择 **D81次高铁**(12:24北京西发车 → 14:30抵达青岛),耗时2小时,座位宽敞,运行平稳,符合"快捷舒适"需求。

🏨 **酒店建议**:
- **丽晶酒店(五星级)**:大床房1200元/晚,设施新潮,服务优质,适合追求舒适体验。
- 若预算有限,可考虑 **丽晶大宾馆(二星级)**:标准间300元/晚,性价比高,但需提前确认房态。

⚠️ **温馨提示**:
1. 高铁票务紧张,建议提前购票。
2. 青岛6月气候宜人,可考虑海边酒店,但需注意防蚊措施。
3. 若需进一步优化,可补充预算或偏好(如是否接受中转、酒店位置等)。

实操:

​ 1 尝试创建智能体

相关推荐
Akamai中国1 天前
Linode接口及默认防火墙现已全面开放使用
服务器·人工智能·云计算·云服务
俊哥V1 天前
每日 AI 研究简报 · 2026-07-30
人工智能·ai
u0103055271 天前
Java异常处理核心机制
人工智能·1024程序员节
IT智慧客07311 天前
保姆级喂饭教程:什么是Skills?如何用Skills?
人工智能
hqyjzsb1 天前
AI应用人才需求爆发:区分AI研发与AI应用,普通人学习路线清晰指南
人工智能·学习·金融·数据挖掘·数据分析·创业创新·业界资讯
qq_454245031 天前
Type-Level Computation Reference (类型级推导参考实现)
人工智能
AI人工智能集结号1 天前
AI回答采集数据清洗:Python实现空回答、拒绝与无关样本识别
开发语言·人工智能·python
手写码匠1 天前
828华为云征文|Flexus X实例 + MaaS平台DeepSeek推理服务:从零搭建企业级AI应用全攻略
人工智能·深度学习·算法·aigc
露(o561-6o623o7)1 天前
路,步态分析系统、大鼠步态分析系统、小鼠步态分析系统
人工智能
道道_Love1 天前
AI 测试用例生成第4版升级,效果明显
人工智能·测试用例·功能安全