day17-LangGraph之智能导游案例-本地服务等

今日内容

1 LangGraph 介绍

1.1 LangGraph是什么

# 1 LangGraph = 用「流程图 / 状态机」的方式,搭建可控、可循环、可回溯的 LLM 应用。
	-如果有同学用过Coze
    -使用代码编写出 类 Coze拖拖拽拽出来的智能体
    	-智能导游
        -AI医生
        -。。。。
        
# 2 它是 LangChain 官方推出的新一代框架,专门解决:
    复杂多步骤 AI
    多轮对话
    智能体(Agent)
    带判断、循环、重试的工作流
    
    
    
# 3 总结 :【流程图】  编程框架
	-流程图:如下图

image-20260315141231733

1.2 LangGraph核心作用

# 1 LangGraph 能让我们做到 LangChain 很难做到、甚至做不到的事:
## 1.1 流程完全可控
	能精确控制:先做什么、再做什么、满足什么条件走哪条路。
    
## 1.2 支持条件分支
    检索不到内容 → 直接回答
    检索到内容 → 走 RAG 回答
    回答不满意 → 重新检索 / 重新生成
    
## 1.3 支持循环、重试、反思
	比如:思考 → 工具调用 → 观察结果 → 再思考 → 再调用
    	这是真正智能体的基础。
    之前langChain: 用户问题---》大模型--》调工具--》大模型--》返回给用户
    
## 1.4 内置状态管理(State)
	所有数据(问题、历史、上下文、结果)统一管理,不用自己写一堆变量。
    
    
## 1.5 可回溯、可调试、可恢复
	每一步都存状态,崩溃了能恢复,能看到每一步做了什么。
    
    
## 1.6 天然适合做 Agent
	多智能体协作,LangGraph 是目前最舒服的方案。

image-20260315141550285

1.3 LangGraph VS LangChain

# 1 LangChain = 链条(Chain) 
## 1.1 线性执行
    A → B → C → D
    走完就结束,不能回头、不能判断、不能循环。
## 1.2 适合
    简单 RAG
    单轮问答
    固定步骤的小应用
    
    
# 2 LangGraph = 图(Graph)  流程图
## 2.1 流程图执行
	可以:
        分支
        循环
        跳转
        重试
        多节点并行
## 2.2 适合
    复杂智能体(Agent)
    多轮对话
    带思考、工具调用的系统
    企业级工作流
    
    
# 3 总结:LangChain 是 “流水线”,LangGraph 是 “可控制、可循环、可判断的智能流程图”。


# 4 例子:
	1 LangChain:自动售货机
        	投币---》选择商品---》出货
      流程固定,不能改,不能回头,不能判断
    
    2 LangGraph:去餐厅点菜
    	1 我们问有什么特色菜
        2 服务员推荐菜
        3 查询是否还有 菜
        4 有--》厨师炒菜
        5 没有--》告诉客户--》重新点别的
        6 上菜后--》不好吃
        7 重做、退钱
        8 满意--》结账
    整个流程,就是 LangGraph   

1.4 LangGraph能做什么

#LangChain能做的,LangGraph都能做,但是LangGraph 比 LangChain 强 多 倍的智能体项目:
    带思考的智能客服
    能反思、能纠错的 RAG 系统
    自主调用工具的 AI 助手(查资料、查天气、算数据)
    多轮对话 + 记忆 + 状态管理
    多智能体协作系统(研究员 + 分析师 + 文案 + 审稿)
    企业级 LLM 工作流

1.5 LangGraph知识点

# 1 安装
# 2 快速开始:图api和函数api
	-6步
# 3 项目级开发:应用程序结构
# 4 LangSmith部署和调用
# 5 本地运行 LangGraph项目
# 6 工作流和智能体
	-LangGraph 不同工作模式
    -LangGraph 能够做的案例

# 7 持久化
# 8 持久执行
# 9 流式处理
# 10 中断
# 11 时间旅行
# 12 内存
# 13 子图

1.6 快速案例【只需要了解】

了解LangGraph的核心功能和执行流程----》完全学完--》再回来看这个项目

1.6.1 智能导游-日志版

# 1 功能:
	用户输入:城市,预算,旅行天数,用户偏好
    推荐出旅行计划:先 生成计划后--》判断--》如果预算超了--》重新生成--》最终给用户
    
    
# 2 使用 LangGraph  最新:1.1.2

# 3 安装模块:
pip install langgraph langchain-openai python-dotenv
import os
import re
import logging
from dotenv import load_dotenv
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from typing import TypedDict, List
from langchain_core.messages import HumanMessage

##### 1 看到 图的执行流程【流程图执行流程】---》日志方式看
#######通过日志方式记录执行流程##########
# 配置全局日志格式和级别
logging.basicConfig(
    level=logging.DEBUG,  # 开启DEBUG级别,打印所有关键日志
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",  # 日志格式(时间-模块-级别-内容)
    handlers=[logging.StreamHandler()]  # 输出到控制台,不输出到文件中
)
# 1.1 开启langgraph核心模块的日志(执行流程、节点调用、状态变化)
logging.getLogger("langgraph").setLevel(logging.DEBUG)
logging.getLogger("langgraph.executor").setLevel(logging.DEBUG)
logging.getLogger("langgraph.graph").setLevel(logging.INFO)  # 图结构日志设为INFO,避免冗余

############## 2 导入环境变量##########
load_dotenv()

########## 3 Graph 核心:全局状态管理,存储数据:旅行计划状态##############
class TravelPlanState(TypedDict):  # 整个图运行过程中,所需要的所有变量都放在类中
    city: str                 # 城市
    days: int                 # 天数
    budget: float
    user_preference: str      # 偏好
    attraction_plan: str
    food_plan: str
    total_cost: float
    is_budget_ok: bool       # 预算是否足够
    final_plan: str          # 最终方案
    conversation_history: list

############### 4 初始化大模型##########
llm = ChatOpenAI(
    model=os.getenv("MODEL_NAME"),
    api_key=os.getenv("API_KEY"),
    base_url=os.getenv("BASE_URL"),
)

###### 4 编辑工具函数  一 处理数据格式--》大模型生成的方案中会有金额
## 解析数字函数
def extract_numeric_value(s: str) -> float | None:
    """从字符串中提取最后一个数字(处理运算式/文字干扰)"""
    numbers = re.findall(r'\d+\.?\d*', s)
    if not numbers:
        return None
    last_num = numbers[-1]
    try:
        return float(last_num)
    except ValueError:
        return None
## 校验数字函数
def validate_numeric_input(input_str: str, input_type: str) -> int | float | None:
    """校验天数/预算输入合法性"""
    try:
        if input_type == "days":
            num = int(input_str)
            if num < 1 or num > 30:
                print("❌ 天数需在1-30之间,请重新输入!")
                return None
            return num
        elif input_type == "budget":
            num = float(input_str)
            if num < 100:
                print("❌ 预算需≥100元,请重新输入!")
                return None
            return num
    except ValueError:
        print(f"❌ 请输入合法的{input_type}数字!")
        return None


##### 5 核心函数:放在图的节点上
# 5.1 获取用户偏好:网红,特种兵,穷游,打开。。。
def get_user_preference(state: TravelPlanState) -> TravelPlanState:
    """获取用户真实偏好输入"""
    if state.get("user_preference") and state["user_preference"].strip():
        return state

    print("\n📋 请补充你的旅行偏好(帮助我们生成更贴合的规划)")
    print("示例:亲子友好、偏爱本地小吃、性价比高、喜欢自然风光、网红打卡等")
    while True:
        preference = input("请输入你的旅行偏好(不能为空):").strip()
        if preference:
            break
        print("❌ 偏好不能为空,请重新输入!")

    state["conversation_history"].append(f"用户输入偏好:{preference}")
    return {"user_preference": preference, "conversation_history": state["conversation_history"]}

# 5.2 景点推荐师
def plan_attractions(state: TravelPlanState) -> TravelPlanState:
    """景点规划师"""
    prompt = f"""
    请为用户规划{state['city']} {state['days']}天的景点行程,要求:
    1. 贴合用户偏好:{state['user_preference']}
    2. 行程合理,不赶时间
    3. 给出每个景点的预估门票费用
    4. 最后汇总景点相关总费用(仅输出数字,单位元,不要加任何符号/文字)
    格式:
    行程:[详细行程描述]
    景点总费用:[纯数字]
    """
    response = llm.invoke([HumanMessage(content=prompt)])
    content = response.content

    plan_part = content.split("景点总费用:")[0].replace("行程:", "").strip()
    cost_part = content.split("景点总费用:")[1].strip()
    attraction_cost = extract_numeric_value(cost_part)
    if attraction_cost is None:
        attraction_cost = 0.0
        print(f"⚠️ 景点费用提取失败,默认赋值0元(原始字符串:{cost_part})")

    state["conversation_history"].append(f"生成景点规划:{plan_part[:50]}...")
    return {
        "attraction_plan": plan_part,
        "total_cost": attraction_cost,
        "conversation_history": state["conversation_history"]
    }

## 5.2 食物推荐师
def plan_food(state: TravelPlanState) -> TravelPlanState:
    """美食推荐师"""
    prompt = f"""
    请为用户规划{state['city']} {state['days']}天的美食行程,要求:
    1. 贴合用户偏好:{state['user_preference']}
    2. 推荐本地特色小吃/餐厅,标注人均消费
    3. 最后汇总美食相关总费用(仅输出数字,单位元,不要加任何符号/文字)
    格式:
    美食规划:[详细美食推荐]
    美食总费用:[纯数字]
    """
    response = llm.invoke([HumanMessage(content=prompt)])
    content = response.content

    food_plan_part = content.split("美食总费用:")[0].replace("美食规划:", "").strip()
    food_cost_part = content.split("美食总费用:")[1].strip()
    food_cost = extract_numeric_value(food_cost_part)
    if food_cost is None:
        food_cost = 0.0
        print(f"⚠️ 美食费用提取失败,默认赋值0元(原始字符串:{food_cost_part})")

    total_cost = state["total_cost"] + food_cost
    state["conversation_history"].append(f"生成美食规划:{food_plan_part[:50]}...")
    return {
        "food_plan": food_plan_part,
        "total_cost": total_cost,
        "conversation_history": state["conversation_history"]
    }

## 5.3 预算审核师:检查预算是否超了
def check_budget(state: TravelPlanState) -> TravelPlanState:
    """预算审核师"""
    budget = state["budget"]
    total_cost = state["total_cost"]
    is_budget_ok = total_cost <= budget

    reflection = f"预算{'达标' if is_budget_ok else '超支'}:预估{total_cost}元 {'≤' if is_budget_ok else '>'} 预算{budget}元"
    state["conversation_history"].append(f"预算审核:{reflection}")
    print(f"【预算审核】{reflection}")
    return {"is_budget_ok": is_budget_ok, "conversation_history": state["conversation_history"]}

## 5.4 重新规划
def re_plan(state: TravelPlanState) -> TravelPlanState:
    """重新规划(预算超支时)"""
    prompt = f"""
    用户去{state['city']}旅行{state['days']}天,预算{state['budget']}元,当前预估{state['total_cost']}元(超支)。
    请优化景点+美食规划,降低成本至预算内,保留核心体验,贴合偏好:{state['user_preference']}。
    格式:
    优化后景点行程:[内容]
    优化后美食规划:[内容]
    优化后总费用:[纯数字]
    """
    response = llm.invoke([HumanMessage(content=prompt)])
    content = response.content

    attraction_plan = content.split("优化后景点行程:")[1].split("优化后美食规划:")[0].strip()
    food_plan = content.split("优化后美食规划:")[1].split("优化后总费用:")[0].strip()
    total_cost_part = content.split("优化后总费用:")[1].strip()

    total_cost = extract_numeric_value(total_cost_part)
    if total_cost is None:
        total_cost = 0.0
        print(f"⚠️ 优化后费用提取失败,默认赋值0元(原始字符串:{total_cost_part})")

    state["conversation_history"].append(f"重新规划:优化后总费用{total_cost}元")
    return {
        "attraction_plan": attraction_plan,
        "food_plan": food_plan,
        "total_cost": total_cost,
        "is_budget_ok": total_cost <= state["budget"],
        "conversation_history": state["conversation_history"]
    }

## 5.5 生成最终方案
def generate_final_plan(state: TravelPlanState) -> TravelPlanState:
    """生成最终旅行方案"""
    prompt = f"""
    请整合以下信息,生成一份完整、美观的{state['city']} {state['days']}天旅行方案:
    用户偏好:{state['user_preference']}
    预算:{state['budget']}元(实际预估:{state['total_cost']}元)
    景点行程:{state['attraction_plan']}
    美食规划:{state['food_plan']}
    要求:结构清晰,语言亲切,分模块展示(行程概览、每日安排、美食推荐、费用明细)。
    """
    final_plan = llm.invoke([HumanMessage(content=prompt)]).content
    state["conversation_history"].append("生成最终旅行方案")
    return {"final_plan": final_plan, "conversation_history": state["conversation_history"]}


## 5.6 分支判断函数:预算审核分支判断
def budget_check_branch(state: TravelPlanState) -> str:
    """预算审核分支判断"""
    return "generate_final_plan" if state["is_budget_ok"] else "re_plan"



############6 构建LangGraph##########
def build_travel_graph():
    """构建旅行规划图"""
    # 6.1 构建graph:构建有状态的图,把TravelPlanState传入
    graph = StateGraph(TravelPlanState)

    # 6.2 添加节点
    graph.add_node("get_user_preference", get_user_preference)
    graph.add_node("plan_attractions", plan_attractions)
    graph.add_node("plan_food", plan_food)
    graph.add_node("check_budget", check_budget)
    graph.add_node("re_plan", re_plan)
    graph.add_node("generate_final_plan", generate_final_plan)

    # 6.3 设置入口点和边:连线
    graph.set_entry_point("get_user_preference")
    graph.add_edge("get_user_preference", "plan_attractions")
    graph.add_edge("plan_attractions", "plan_food")
    graph.add_edge("plan_food", "check_budget")
    graph.add_conditional_edges("check_budget", budget_check_branch)
    graph.add_edge("re_plan", "check_budget")
    graph.add_edge("generate_final_plan", END)

    # 6.4 添加了节点--》节点连线完成--》编译--》构建好了有状态的图
    return graph.compile()


######## 7 交互逻辑--》纯python逻辑###
def interactive_travel_planner():
    """交互式旅行规划系统"""
    print("=" * 60)
    print("🎫 AI 旅行规划师 - 支持日志查看执行过程")
    print("=" * 60)

    # 1 初始化状态
    current_state = {
        "city": "",
        "days": 0,
        "budget": 0.0,
        "user_preference": "",
        "attraction_plan": "",
        "food_plan": "",
        "total_cost": 0.0,
        "is_budget_ok": False,
        "final_plan": "",
        "conversation_history": []
    }

    # 2 构建图
    travel_graph = build_travel_graph()

    # 3 交互循环
    while True:
        print("\n📌 请选择操作:")
        print("1. 输入/修改旅行城市")
        print("2. 输入/修改旅行天数")
        print("3. 输入/修改旅行预算")
        print("4. 生成旅行规划(会输出详细日志)")
        print("5. 查看对话记录")
        print("6. 退出系统")

        choice = input("请输入操作编号(1-6):").strip()

        if choice == "1":
            city = input("请输入旅行城市:").strip()
            if not city:
                print("❌ 城市不能为空!")
                continue
            current_state["city"] = city
            current_state["conversation_history"].append(f"设置城市:{city}")
            print(f"✅ 已设置旅行城市:{city}")

        elif choice == "2":
            days_str = input("请输入旅行天数:").strip()
            days = validate_numeric_input(days_str, "days")
            if days:
                current_state["days"] = days
                current_state["conversation_history"].append(f"设置天数:{days}天")
                print(f"✅ 已设置旅行天数:{days}天")

        elif choice == "3":
            budget_str = input("请输入旅行预算(元):").strip()
            budget = validate_numeric_input(budget_str, "budget")
            if budget:
                current_state["budget"] = budget
                current_state["conversation_history"].append(f"设置预算:{budget}元")
                print(f"✅ 已设置旅行预算:{budget}元")

        elif choice == "4":
            # 校验基础信息
            if not current_state["city"] or current_state["days"] == 0 or current_state["budget"] == 0.0:
                print("❌ 请先完善城市、天数、预算信息!")
                continue

            print("\n🔄 正在生成旅行规划(控制台会输出详细日志)...")
            # 执行图并获取结果(日志会自动输出)
            result = travel_graph.invoke(current_state)
            current_state = result

            # 展示最终结果
            print("\n" + "=" * 50)
            print("🎯 最终旅行规划方案")
            print("=" * 50)
            print(result["final_plan"])
            print("\n💰 费用明细:")
            print(f"预算:{current_state['budget']} 元 | 实际预估:{result['total_cost']} 元")
            print(f"预算是否达标:{'✅ 是' if result['is_budget_ok'] else '❌ 否'}")

        elif choice == "5":
            print("\n📝 对话记录:")
            if not current_state["conversation_history"]:
                print("暂无对话记录")
            else:
                for i, record in enumerate(current_state["conversation_history"], 1):
                    print(f"{i}. {record}")

        elif choice == "6":
            print("\n👋 感谢使用AI旅行规划师,再见!")
            break

        else:
            print("❌ 输入无效,请选择1-6的操作编号!")


######### 8 启动############
if __name__ == "__main__":
    interactive_travel_planner()

1.6.1 智能导游-生成流程图

# 1 LangGraph 是状态图
	-最好:能看到图的样子--》当前项目生成状态图【我们写项目】
    	-LangGraph支持:需要借助于graphviz
        
    -更好:当前项目图每一步的执行流程【演示】
    	-LangSmith支持
        
# 2 安装一个软件:graphviz---》生成有向图的软件--》使用它把LangGraph的流程图生成

# 2.1 彩色的--》第三方--》写的代码多【自己代码画】
# 2.1 黑白的--》LangGraph官方支持--》几行代码【根据LangGraph节点和连线生成】
# 1 下载软件: https://graphviz.org/download/
	-安装版,点选加入环境变量
    -解压版:手动配置环境变量
    
# 2 打开cmd:输入
dot --version
# 输出:dot - graphviz version 14.1.3 (20260303.0454)


# 3 python快速使用 
# pip install graphviz
from graphviz import Graph
# 1 创建有向图
dot = Graph()
# 2 添加节点和边
dot.node('A', 'Node A')
dot.node('B', 'Node B')
dot.edge('A', 'B')
# 渲染和显示图形
dot.render('graph', format='png', view=True)


# 4 就会生成有向图

image-20260315152712061

image-20260315152814181

image-20260315152928098

import os
import re
import logging
from dotenv import load_dotenv
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
from typing import TypedDict, List
from langchain_core.messages import HumanMessage

from graphviz import Digraph  # 安装:pip install graphviz
##### 1 看到 图的执行流程【流程图执行流程】---》生成流程图

################ 扩展:生成流程图片的代码###############
def generate_travel_graphviz():
    """生成旅行规划流程图(graphviz)"""
    # 初始化有向图
    dot = Digraph(
        name="TravelPlanner",
        format="png",  # 输出格式:png/svg/pdf均可
        encoding="utf-8",
        graph_attr={"rankdir": "TD", "fontname": "SimHei", "fontsize": "12"},  # 从上到下布局、支持中文
        node_attr={"fontname": "SimHei", "fontsize": "10", "style": "filled"},
        edge_attr={"fontname": "SimHei", "fontsize": "9"}
    )

    # 添加节点(自定义样式)
    dot.node("get_user_preference", "获取用户偏好", shape="ellipse", fillcolor="#e6f7ff")
    dot.node("plan_attractions", "景点规划", shape="box", fillcolor="#b3e0ff")
    dot.node("plan_food", "美食规划", shape="box", fillcolor="#b3e0ff")
    dot.node("check_budget", "预算审核", shape="diamond", fillcolor="#fff3cd")
    dot.node("re_plan", "重新规划", shape="box", fillcolor="#ffd966")
    dot.node("generate_final_plan", "生成最终方案", shape="box", fillcolor="#c1e1c1")
    dot.node("end", "结束", shape="ellipse", fillcolor="#f8f9fa")

    # 添加边(含条件分支标签)
    dot.edge("get_user_preference", "plan_attractions")
    dot.edge("plan_attractions", "plan_food")
    dot.edge("plan_food", "check_budget")
    dot.edge("check_budget", "generate_final_plan", label="预算达标")
    dot.edge("check_budget", "re_plan", label="预算超支")
    dot.edge("re_plan", "check_budget")
    dot.edge("generate_final_plan", "end")

    # 保存并渲染
    dot.save("travel_planner.dot")  # 保存DOT源文件
    dot.render("travel_planner")  # 生成PNG图片
    print("✅ 流程图已生成:travel_planner.png(当前目录)")

############## 2 导入环境变量##########
load_dotenv()

########## 3 Graph 核心:全局状态管理,存储数据:旅行计划状态##############
class TravelPlanState(TypedDict):  # 整个图运行过程中,所需要的所有变量都放在类中
    city: str                 # 城市
    days: int                 # 天数
    budget: float
    user_preference: str      # 偏好
    attraction_plan: str
    food_plan: str
    total_cost: float
    is_budget_ok: bool       # 预算是否足够
    final_plan: str          # 最终方案
    conversation_history: list





############### 4 初始化大模型##########
llm = ChatOpenAI(
    model=os.getenv("MODEL_NAME"),
    api_key=os.getenv("API_KEY"),
    base_url=os.getenv("BASE_URL"),
)

###### 4 编辑工具函数  一 处理数据格式--》大模型生成的方案中会有金额
## 解析数字函数
def extract_numeric_value(s: str) -> float | None:
    """从字符串中提取最后一个数字(处理运算式/文字干扰)"""
    numbers = re.findall(r'\d+\.?\d*', s)
    if not numbers:
        return None
    last_num = numbers[-1]
    try:
        return float(last_num)
    except ValueError:
        return None
## 校验数字函数
def validate_numeric_input(input_str: str, input_type: str) -> int | float | None:
    """校验天数/预算输入合法性"""
    try:
        if input_type == "days":
            num = int(input_str)
            if num < 1 or num > 30:
                print("❌ 天数需在1-30之间,请重新输入!")
                return None
            return num
        elif input_type == "budget":
            num = float(input_str)
            if num < 100:
                print("❌ 预算需≥100元,请重新输入!")
                return None
            return num
    except ValueError:
        print(f"❌ 请输入合法的{input_type}数字!")
        return None


##### 5 核心函数:放在图的节点上
# 5.1 获取用户偏好:网红,特种兵,穷游,打开。。。
def get_user_preference(state: TravelPlanState) -> TravelPlanState:
    """获取用户真实偏好输入"""
    if state.get("user_preference") and state["user_preference"].strip():
        return state

    print("\n📋 请补充你的旅行偏好(帮助我们生成更贴合的规划)")
    print("示例:亲子友好、偏爱本地小吃、性价比高、喜欢自然风光、网红打卡等")
    while True:
        preference = input("请输入你的旅行偏好(不能为空):").strip()
        if preference:
            break
        print("❌ 偏好不能为空,请重新输入!")

    state["conversation_history"].append(f"用户输入偏好:{preference}")
    return {"user_preference": preference, "conversation_history": state["conversation_history"]}

# 5.2 景点推荐师
def plan_attractions(state: TravelPlanState) -> TravelPlanState:
    """景点规划师"""
    prompt = f"""
    请为用户规划{state['city']} {state['days']}天的景点行程,要求:
    1. 贴合用户偏好:{state['user_preference']}
    2. 行程合理,不赶时间
    3. 给出每个景点的预估门票费用
    4. 最后汇总景点相关总费用(仅输出数字,单位元,不要加任何符号/文字)
    格式:
    行程:[详细行程描述]
    景点总费用:[纯数字]
    """
    response = llm.invoke([HumanMessage(content=prompt)])
    content = response.content

    plan_part = content.split("景点总费用:")[0].replace("行程:", "").strip()
    cost_part = content.split("景点总费用:")[1].strip()
    attraction_cost = extract_numeric_value(cost_part)
    if attraction_cost is None:
        attraction_cost = 0.0
        print(f"⚠️ 景点费用提取失败,默认赋值0元(原始字符串:{cost_part})")

    state["conversation_history"].append(f"生成景点规划:{plan_part[:50]}...")
    return {
        "attraction_plan": plan_part,
        "total_cost": attraction_cost,
        "conversation_history": state["conversation_history"]
    }

## 5.2 食物推荐师
def plan_food(state: TravelPlanState) -> TravelPlanState:
    """美食推荐师"""
    prompt = f"""
    请为用户规划{state['city']} {state['days']}天的美食行程,要求:
    1. 贴合用户偏好:{state['user_preference']}
    2. 推荐本地特色小吃/餐厅,标注人均消费
    3. 最后汇总美食相关总费用(仅输出数字,单位元,不要加任何符号/文字)
    格式:
    美食规划:[详细美食推荐]
    美食总费用:[纯数字]
    """
    response = llm.invoke([HumanMessage(content=prompt)])
    content = response.content

    food_plan_part = content.split("美食总费用:")[0].replace("美食规划:", "").strip()
    food_cost_part = content.split("美食总费用:")[1].strip()
    food_cost = extract_numeric_value(food_cost_part)
    if food_cost is None:
        food_cost = 0.0
        print(f"⚠️ 美食费用提取失败,默认赋值0元(原始字符串:{food_cost_part})")

    total_cost = state["total_cost"] + food_cost
    state["conversation_history"].append(f"生成美食规划:{food_plan_part[:50]}...")
    return {
        "food_plan": food_plan_part,
        "total_cost": total_cost,
        "conversation_history": state["conversation_history"]
    }

## 5.3 预算审核师:检查预算是否超了
def check_budget(state: TravelPlanState) -> TravelPlanState:
    """预算审核师"""
    budget = state["budget"]
    total_cost = state["total_cost"]
    is_budget_ok = total_cost <= budget

    reflection = f"预算{'达标' if is_budget_ok else '超支'}:预估{total_cost}元 {'≤' if is_budget_ok else '>'} 预算{budget}元"
    state["conversation_history"].append(f"预算审核:{reflection}")
    print(f"【预算审核】{reflection}")
    return {"is_budget_ok": is_budget_ok, "conversation_history": state["conversation_history"]}

## 5.4 重新规划
def re_plan(state: TravelPlanState) -> TravelPlanState:
    """重新规划(预算超支时)"""
    prompt = f"""
    用户去{state['city']}旅行{state['days']}天,预算{state['budget']}元,当前预估{state['total_cost']}元(超支)。
    请优化景点+美食规划,降低成本至预算内,保留核心体验,贴合偏好:{state['user_preference']}。
    格式:
    优化后景点行程:[内容]
    优化后美食规划:[内容]
    优化后总费用:[纯数字]
    """
    response = llm.invoke([HumanMessage(content=prompt)])
    content = response.content

    attraction_plan = content.split("优化后景点行程:")[1].split("优化后美食规划:")[0].strip()
    food_plan = content.split("优化后美食规划:")[1].split("优化后总费用:")[0].strip()
    total_cost_part = content.split("优化后总费用:")[1].strip()

    total_cost = extract_numeric_value(total_cost_part)
    if total_cost is None:
        total_cost = 0.0
        print(f"⚠️ 优化后费用提取失败,默认赋值0元(原始字符串:{total_cost_part})")

    state["conversation_history"].append(f"重新规划:优化后总费用{total_cost}元")
    return {
        "attraction_plan": attraction_plan,
        "food_plan": food_plan,
        "total_cost": total_cost,
        "is_budget_ok": total_cost <= state["budget"],
        "conversation_history": state["conversation_history"]
    }

## 5.5 生成最终方案
def generate_final_plan(state: TravelPlanState) -> TravelPlanState:
    """生成最终旅行方案"""
    prompt = f"""
    请整合以下信息,生成一份完整、美观的{state['city']} {state['days']}天旅行方案:
    用户偏好:{state['user_preference']}
    预算:{state['budget']}元(实际预估:{state['total_cost']}元)
    景点行程:{state['attraction_plan']}
    美食规划:{state['food_plan']}
    要求:结构清晰,语言亲切,分模块展示(行程概览、每日安排、美食推荐、费用明细)。
    """
    final_plan = llm.invoke([HumanMessage(content=prompt)]).content
    state["conversation_history"].append("生成最终旅行方案")
    return {"final_plan": final_plan, "conversation_history": state["conversation_history"]}


## 5.6 分支判断函数:预算审核分支判断
def budget_check_branch(state: TravelPlanState) -> str:
    """预算审核分支判断"""
    return "generate_final_plan" if state["is_budget_ok"] else "re_plan"



############6 构建LangGraph##########
def build_travel_graph():
    """构建旅行规划图"""
    ###### 添加代码,画流程图#########
    generate_travel_graphviz()
    # 6.1 构建graph:构建有状态的图,把TravelPlanState传入
    graph = StateGraph(TravelPlanState)

    # 6.2 添加节点
    graph.add_node("get_user_preference", get_user_preference)
    graph.add_node("plan_attractions", plan_attractions)
    graph.add_node("plan_food", plan_food)
    graph.add_node("check_budget", check_budget)
    graph.add_node("re_plan", re_plan)
    graph.add_node("generate_final_plan", generate_final_plan)

    # 6.3 设置入口点和边:连线
    graph.set_entry_point("get_user_preference")
    graph.add_edge("get_user_preference", "plan_attractions")
    graph.add_edge("plan_attractions", "plan_food")
    graph.add_edge("plan_food", "check_budget")
    graph.add_conditional_edges("check_budget", budget_check_branch)
    graph.add_edge("re_plan", "check_budget")
    graph.add_edge("generate_final_plan", END)

    # 6.4 添加了节点--》节点连线完成--》编译--》构建好了有状态的图
    return graph.compile()


######## 7 交互逻辑--》纯python逻辑###
def interactive_travel_planner():
    """交互式旅行规划系统"""
    print("=" * 60)
    print("🎫 AI 旅行规划师 - 生成流程图")
    print("=" * 60)

    # 1 初始化状态
    current_state = {
        "city": "",
        "days": 0,
        "budget": 0.0,
        "user_preference": "",
        "attraction_plan": "",
        "food_plan": "",
        "total_cost": 0.0,
        "is_budget_ok": False,
        "final_plan": "",
        "conversation_history": []
    }

    # 2 构建图
    travel_graph = build_travel_graph()

    # 3 交互循环
    while True:
        print("\n📌 请选择操作:")
        print("1. 输入/修改旅行城市")
        print("2. 输入/修改旅行天数")
        print("3. 输入/修改旅行预算")
        print("4. 生成旅行规划(会输出详细日志)")
        print("5. 查看对话记录")
        print("6. 退出系统")

        choice = input("请输入操作编号(1-6):").strip()

        if choice == "1":
            city = input("请输入旅行城市:").strip()
            if not city:
                print("❌ 城市不能为空!")
                continue
            current_state["city"] = city
            current_state["conversation_history"].append(f"设置城市:{city}")
            print(f"✅ 已设置旅行城市:{city}")

        elif choice == "2":
            days_str = input("请输入旅行天数:").strip()
            days = validate_numeric_input(days_str, "days")
            if days:
                current_state["days"] = days
                current_state["conversation_history"].append(f"设置天数:{days}天")
                print(f"✅ 已设置旅行天数:{days}天")

        elif choice == "3":
            budget_str = input("请输入旅行预算(元):").strip()
            budget = validate_numeric_input(budget_str, "budget")
            if budget:
                current_state["budget"] = budget
                current_state["conversation_history"].append(f"设置预算:{budget}元")
                print(f"✅ 已设置旅行预算:{budget}元")

        elif choice == "4":
            # 校验基础信息
            if not current_state["city"] or current_state["days"] == 0 or current_state["budget"] == 0.0:
                print("❌ 请先完善城市、天数、预算信息!")
                continue

            print("\n🔄 正在生成旅行规划(控制台会输出详细日志)...")
            # 执行图并获取结果(日志会自动输出)
            result = travel_graph.invoke(current_state)
            current_state = result

            # 展示最终结果
            print("\n" + "=" * 50)
            print("🎯 最终旅行规划方案")
            print("=" * 50)
            print(result["final_plan"])
            print("\n💰 费用明细:")
            print(f"预算:{current_state['budget']} 元 | 实际预估:{result['total_cost']} 元")
            print(f"预算是否达标:{'✅ 是' if result['is_budget_ok'] else '❌ 否'}")

        elif choice == "5":
            print("\n📝 对话记录:")
            if not current_state["conversation_history"]:
                print("暂无对话记录")
            else:
                for i, record in enumerate(current_state["conversation_history"], 1):
                    print(f"{i}. {record}")

        elif choice == "6":
            print("\n👋 感谢使用AI旅行规划师,再见!")
            break

        else:
            print("❌ 输入无效,请选择1-6的操作编号!")


######### 8 启动############
if __name__ == "__main__":
    interactive_travel_planner()

image-20260315154253865

2 LangGraph核心

使用LangGraph创建状态图的步骤

图api:需要6步

函数api:需要4步

2.1 Graph API

# 1 定义模型和工具
# 2 定义状态
# 3 定义模型节点
# 4 定义工具节点
# 5 定义结束逻辑
# 6 构建并编译代理
# pip install langchain
from langchain.tools import tool
from langchain.chat_models import init_chat_model
from langchain_openai import ChatOpenAI

####################### 1 定义模型和工具#######
## 1.1 连接模型---》讲过--》底层使用 langchain-deepseek   如果没装会报错
# model=init_chat_model(
#     model="deepseek-chat",
#     api_key="sk-d7835fa8f2cd4908a284ebf48818581b",
#     base_url="https://api.deepseek.com",
# )
## 1.1 连接模型---》讲过--》使用ChatOpenAI 通用接口,需要装langchain_openai
model = ChatOpenAI(
    model="deepseek-chat",
    api_key="sk-d7835fa8f2cd4908a284ebf48818581b",
    base_url="https://api.deepseek.com",
)
## 1.2 定义工具 :乘  加 除 --》3个工具
@tool
def multiply(a: int, b: int) -> int:
    """将`a`和`b`相乘。
    参数:
        a: 第一个整数
        b: 第二个整数
    """
    return a * b

@tool
def add(a: int, b: int) -> int:
    """将`a`和`b`相加。
    参数:
        a: 第一个整数
        b: 第二个整数
    """
    return a + b

@tool
def divide(a: int, b: int) -> float:
    """将`a`除以`b`。
    参数:
        a: 第一个整数
        b: 第二个整数
    """
    return a / b
## 1.3 把工具绑定给模型--》学过
tools = [add, multiply, divide]
tools_by_name = {tool.name: tool for tool in tools}
model_with_tools = model.bind_tools(tools)

########################  2 定义状态-LangGraph的核心,定义 整个工作流中传递数据的结构:要用哪个变量,提前定义好,放在状态类中#########
from langchain.messages import AnyMessage
from typing_extensions import TypedDict, Annotated
import operator
class MessagesState(TypedDict):
    messages: Annotated[list[AnyMessage], operator.add]  # 存跟模型交互的消息
    llm_calls: int  # 记录模型被调用了多少次


######################### 3 定义模型节点--》传入当前state:修改 state中的字段:message和llm_calls
from langchain.messages import SystemMessage
def llm_call(state: dict):
    """LLM decides whether to call a tool or not"""
    return {
        "messages": [
            model_with_tools.invoke(
                [
                    SystemMessage(
                        content="You are a helpful assistant tasked with performing arithmetic on a set of inputs."
                    )
                ]
                + state["messages"]
            )
        ],  # 记录了所有交互的消息
        "llm_calls": state.get('llm_calls', 0) + 1
    }


######################### 4 定义工具节点:LLM在决定调用工具时触发
from langchain.messages import ToolMessage
def tool_node(state: dict):
    """Performs the tool call"""
    result = []
    for tool_call in state["messages"][-1].tool_calls:
        tool = tools_by_name[tool_call["name"]]
        observation = tool.invoke(tool_call["args"])
        result.append(ToolMessage(content=observation, tool_call_id=tool_call["id"]))
    return {"messages": result} # 把最新的消息加入到状态中

######################### 5 定义结束逻辑:边条件函数:检查最后一条消息
# 如果用户输入的 提示词,要调用工具--》走向调用工具
# 如果用户 提示词,不需要调用工具--》直接返回答案,走向end
from typing import Literal
from langgraph.graph import StateGraph, START, END
def should_continue(state: MessagesState) -> Literal["tool_node", END]:
    """Decide if we should continue the loop or stop based upon whether the LLM made a tool call"""

    messages = state["messages"]
    last_message = messages[-1]

    # If the LLM makes a tool call, then perform an action
    if last_message.tool_calls:
        return "tool_node"

    # Otherwise, we stop (reply to the user)
    return END

######################### 6 构建并编译代理
# 6.1 实例化得到StateGraph,传入状态
agent_builder = StateGraph(MessagesState)
# 6.2 初始化图,添加节点
agent_builder.add_node("llm_call", llm_call)
agent_builder.add_node("tool_node", tool_node)

# 6.3 连线
agent_builder.add_edge(START, "llm_call")
# 条件分支:从llm出来后,执行 should_continue函数
# 如果返回 tool_node---》调用工具节点
# 如果饭盒 end --》直接结束
agent_builder.add_conditional_edges(
    "llm_call",
    should_continue,
    ["tool_node", END]
)
# 6.4 循环
agent_builder.add_edge("tool_node", "llm_call")

# 6.5 编译
agent = agent_builder.compile()



##### 7 生成状态图图片--》内置自动生成:根据节点和连线生成,步需要我们写太多代码--》直接生成:黑白的
png_data = agent.get_graph(xray=True).draw_mermaid_png()
with open('demo.png', "wb") as f:
    f.write(png_data)
    print('该项目状态图已经保存:demo.png')


###### 8 交互,调用
from langchain.messages import HumanMessage
messages = [HumanMessage(content="Add 3 and 4.")]
messages = agent.invoke({"messages": messages})
for m in messages["messages"]:
    m.pretty_print()



'''经典的模式:ReAct模式:(Reason + Act)
1 开始:用户输入:Add 3 and 4
2 LLM call:思考,需要工具吗?需要
3 tool node节点:执行加法运算
4 LLM call:计算结果给了 llm,思考,现在可以给用户了吗?
5 输出给用户

'''

image-20260315161322545

2.2 Functional API

# 1 定义工具和模型
# 2 定义模型节点
# 3 定义工具节点
# 4 定义智能体
################ 1 定义工具和模型
from langchain.tools import tool
from langchain_openai import ChatOpenAI
## 1.1 连接模型
model= ChatOpenAI(
    model="deepseek-chat",
    api_key="sk-d7835fa8f2cd4908a284ebf48818581b",
    base_url="https://api.deepseek.com",
)

# 1.2 定义工具
@tool
def multiply(a: int, b: int) -> int:
    """Multiply `a` and `b`.

    Args:
        a: First int
        b: Second int
    """
    return a * b


@tool
def add(a: int, b: int) -> int:
    """Adds `a` and `b`.

    Args:
        a: First int
        b: Second int
    """
    return a + b


@tool
def divide(a: int, b: int) -> float:
    """Divide `a` and `b`.

    Args:
        a: First int
        b: Second int
    """
    return a / b


## 1.3 模型跟工具绑定
tools = [add, multiply, divide]
tools_by_name = {tool.name: tool for tool in tools}
model_with_tools = model.bind_tools(tools)

################# 2 定义模型节点####
from langgraph.graph import add_messages
from langchain.messages import (
    SystemMessage,
    HumanMessage,
    ToolCall,
)
from langchain_core.messages import BaseMessage
from langgraph.func import entrypoint, task
@task  # 等同于:add_node
def call_llm(messages: list[BaseMessage]):
    """LLM decides whether to call a tool or not"""
    return model_with_tools.invoke(
        [
            SystemMessage(
                content="You are a helpful assistant tasked with performing arithmetic on a set of inputs."
            )
        ]
        + messages
    )
################# 3 定义工具节点 ####
@task # 等同于:add_node
def call_tool(tool_call: ToolCall):
    """Performs the tool call"""
    tool = tools_by_name[tool_call["name"]]
    return tool.invoke(tool_call)

################# 4 定义智能体
@entrypoint()
def agent(messages: list[BaseMessage]):
    model_response = call_llm(messages).result()
    while True:
        if not model_response.tool_calls:
            break
        tool_result_futures = [
            call_tool(tool_call) for tool_call in model_response.tool_calls
        ]
        # 收集结果: 等待所有工具任务完成,获取计算结果列表。
        tool_results = [fut.result() for fut in tool_result_futures]
        messages = add_messages(messages, [model_response, *tool_results])
        model_response = call_llm(messages).result()
    # 循环结束后,将最后一次 LLM 的最终回复(纯文本)加入消息列表。  return messages: 返回完整的对话历史记录
    messages = add_messages(messages, model_response)
    return messages

# 5 交互,调用--》计算3 + 4--》流式输出
messages = [HumanMessage(content="Add 3 and 4.")]
# 流式输出
for chunk in agent.stream(messages, stream_mode="updates"):
    print(chunk)
    print("\n")

image-20260315162027789

2.3 生成可视化

# 1 langgraph中,如果定义好了节点,连好了线,可以一键生成流程图
graph = StateGraph(MessagesState)
# add nod 添加节点
# add_edge 连线
# 编译
agent = graph.compile()
png_data = agent.get_graph(xray=True).draw_mermaid_png()
with open('demo.png', "wb") as f:
    f.write(png_data)

3 运行本地服务器

上述案例,放在一个py文件中--》不适合工程化

工程化:复杂的目录结构---》官方有规范

3.1 程序结构

3.1.1 两种目录结构

pyproject.toml 和 requirements.txt 都是用来放当前项目依赖的

pyproject.toml 使用vu管理:pip install -e .

requirements.txt 使用pip管理:pip install -r requirements.txt

项目名/
├── my_agent # 代码都写在这里面
│   ├── utils # 节点,状态,工具--》放在这个文件夹中
│   │   ├── __init__.py
│   │   ├── tools.py # 工具:LangGraph或LangChain中学的
│   │   ├── nodes.py # 节点:大模型节点,工具节点,判断节点,重新规划节点,食物推荐节点。。
│   │   └── state.py # 状态:LangGraph的核心,定义 整个工作流中传递数据的结构
│   ├── __init__.py
│   └── agent.py # agent = graph.compile() 放在这里
├── .env # 环境变量
├── requirements.txt # 依赖
└── langgraph.json   # 配置文件
项目名/
├── my_agent # 代码都写在这里面
│   ├── utils # 节点,状态,工具--》放在这个文件夹中
│   │   ├── __init__.py
│   │   ├── tools.py # 工具:LangGraph或LangChain中学的
│   │   ├── nodes.py # 节点:大模型节点,工具节点,判断节点,重新规划节点,食物推荐节点。。
│   │   └── state.py # 状态:LangGraph的核心,定义 整个工作流中传递数据的结构
│   ├── __init__.py
│   └── agent.py #  agent = graph.compile() 放在这里
├── .env # 环境变量
├── langgraph.json  # 配置文件
└── pyproject.toml # 依赖

3.1.2 解释

##### 1 配置文件-->一会有这个文件,我们看一下即可############
langgraph.json 文件是一个 JSON 文件,用于指定部署 LangGraph 应用程序所需的依赖项、图、环境变量和其他设置。
## 示例
    1 依赖项包括一个自定义本地包和 langchain_openai 包。
    2 将从文件 ./your_package/your_file.py 加载一个图,变量为 variable。
    3 环境变量从 .env 文件加载。
    {
      "dependencies": ["langchain_openai", "./your_package"],
      "graphs": {
        "my_agent": "./your_package/your_file.py:agent"
      },
      "env": "./.env"
    }

###### 2 依赖项
LangGraph 应用程序可能依赖于其他 Python 包。通常,您需要指定以下信息才能正确设置依赖项:
    目录中的一个文件,用于指定依赖项:requirements.txt或pyproject.toml 

####### 3 环境变量
如果您在本地使用已部署的 LangGraph 应用程序,可以在 LangGraph 配置文件 的 env 键中配置环境变量。
对于生产部署,通常希望在部署环境中配置环境变量

3.2 创建项目:命令创建项目

上述目录结构可以自己创建

但是:LangGraph 提供了命令,可以直接通过命令创建项目[工程],创建完的工程,就是上述目录结构--》我们再做微调即可

并且,可以通过LangGraph 提供的命令直接运行

运行后,可以在LangSmith 查看【演示--》实际项目中,只运行即可,不注册到LangSmith 中】

LangSmith要注册账号,个人单个智能体使用免费;多个智能体或企业使用收费

# 1 安装 LangGraph 脚手架:pip install "langgraph-cli[inmem]"
	# Python >= 3.11 is required.
	-创建 LangGraph  项目
    -运行 LangGraph  项目
    # 我装在虚拟环境里了
    
# 2 使用命令创建项目【我:必须在虚拟环境中才能创建】
	# 从 new-langgraph-project-python 模板 创建一个新的应用程序。
    # 此模板演示了一个可以扩展为包含您自己逻辑的单节点应用程序。:https://github.com/langchain-ai/new-langgraph-project
	# 在创建的项目基础上继续开发即可
    # 命令:
    langgraph new path/to/your/app --template new-langgraph-project-python
    langgraph new 路径和项目名 --template 基于哪个模板创建项目
    # 我们的命令:在当前目录下创建项目:langgraph_demo001,没有指定模板,我们自选
    langgraph new langgraph_demo001
    # 有的同学可能会失败;原因是去github下载,国内有的地方访问不到
    	-1 重试
        -2 FQ
        -3 把老师创建好的空的,打开,继续开发即可
        	-等同于自己创建了文件夹和文件
            
# 3 使用pycharm打开:目录如下图

# 4 创建.env文件
	-有一个 .env.example ,复制改名字即可
    
# 5 目前我们有解释器环境吗? 没有--》需要创建虚拟环境--》没有LangGraph脚手架的,我们需要手动装上---》因为项目运行,需要
pip install "langgraph-cli[inmem]"

# 6 安装依赖[虚拟环境中还没装:[LangGraph,python-dotenv]
pip install -e .

# 7 本地启动Agent服务器
langgraph dev

image-20260315165647461

image-20260315170108627

image-20260315170228554

image-20260315170455410

image-20260315170838155

image-20260315171247946

image-20260315171431224

image-20260315171755346

3.3 修改代码

把上面讲的Graph api的案例,修改成项目形式

image-20260315173039159

########## graph.py
from typing import Literal
from langgraph.graph import StateGraph, START, END
from agent.state import MessagesState
from agent.nodes import llm_call, tool_node


def should_continue(state: MessagesState) -> Literal["tool_node", END]:
    """Routing logic: Check if the last message contains tool calls."""
    last_message = state["messages"][-1]

    if hasattr(last_message, "tool_calls") and last_message.tool_calls:
        return "tool_node"

    return END


def build_graph() -> StateGraph:
    """Builds and compiles the agent graph."""
    builder = StateGraph(MessagesState)

    # Add nodes
    builder.add_node("llm_call", llm_call)
    builder.add_node("tool_node", tool_node)

    # Add edges
    builder.add_edge(START, "llm_call")

    # Conditional edges based on tool usage
    builder.add_conditional_edges(
        source="llm_call",
        path=should_continue,
        path_map=["tool_node", END]
    )

    # Loop back from tool to LLM
    builder.add_edge("tool_node", "llm_call")

    return builder.compile()


# Required export for langgraph dev
graph = build_graph()
########nodes.py

import os
from langchain_core.messages import SystemMessage, ToolMessage
from langchain.chat_models import init_chat_model
from agent.state import MessagesState
from agent.tools import tools_list, tools_by_name
from langchain_openai import ChatOpenAI
# 初始化模型 (读取环境变量)
model_name = os.getenv("MODEL_NAME")
api_key = os.getenv("API_KEY")
base_url = os.getenv("BASE_URL")

model= ChatOpenAI(
    model=model_name,
    api_key=api_key,
    base_url=base_url,
)

model_with_tools = model.bind_tools(tools_list)

# 节点一
def llm_call(state: MessagesState) -> dict:
    """LLM Node: Decides whether to call a tool or answer directly."""
    if model_with_tools is None:
        raise RuntimeError("Model not initialized. Check ANTHROPIC_API_KEY in .env")

    system_prompt = SystemMessage(
        content="You are a helpful assistant tasked with performing arithmetic on a set of inputs."
    )

    # Construct full message history
    messages_to_send = [system_prompt] + state["messages"]

    response = model_with_tools.invoke(messages_to_send)

    return {
        "messages": [response],
        "llm_calls": state.get("llm_calls", 0) + 1
    }

# 节点二
def tool_node(state: MessagesState) -> dict:
    """Tool Node: Executes tools requested by the LLM."""
    last_message = state["messages"][-1]

    if not hasattr(last_message, "tool_calls") or not last_message.tool_calls:
        return {"messages": []}

    results = []
    for tc in last_message.tool_calls:
        tool_func = tools_by_name.get(tc["name"])
        if not tool_func:
            obs = f"Error: Tool '{tc['name']}' not found."
        else:
            try:
                obs = tool_func.invoke(tc["args"])
            except Exception as e:
                obs = f"Error: {str(e)}"

        results.append(
            ToolMessage(content=str(obs), tool_call_id=tc["id"])
        )

    return {"messages": results}
#########state.py
from typing import Annotated, List
from typing_extensions import TypedDict
import operator
from langchain_core.messages import AnyMessage

class MessagesState(TypedDict):
    """
    State schema for the math agent.
    - messages: Accumulates conversation history using operator.add
    - llm_calls: Tracks number of LLM invocations
    """
    messages: Annotated[List[AnyMessage], operator.add]
    llm_calls: int
####### tools.py

from langchain_core.tools import tool

@tool
def multiply(a: int, b: int) -> int:
    """Multiply `a` and `b`."""
    return a * b

@tool
def add(a: int, b: int) -> int:
    """Adds `a` and `b`."""
    return a + b

@tool
def divide(a: int, b: int) -> float:
    """Divide `a` by `b`."""
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

# 导出供其他模块使用
tools_list = [add, multiply, divide]
tools_by_name = {t.name: t for t in tools_list}

image-20260315172513360

image-20260315172612248

启动项目

langgraph dev

image-20260315173015657

3.4 客户端调用

image-20260315171247946

启动项目后,提供了api接口---》客户端可以调用

image-20260315173743734

3.4.1 python同步调用

# 同步 pip install langgraph-sdk
# pip install langgraph-sdk
from langgraph_sdk import get_sync_client

client = get_sync_client(url="http://localhost:2024")

for chunk in client.runs.stream(
    None,  # 无线程运行
    "agent", # 助手名称。在langgraph.json中定义。
    input={
        "messages": [{
            "role": "human",
            "content": "add 4 and 4 ",
        }],
    },
    stream_mode="messages-tuple",
):
    print(f"接收类型为: {chunk.event} 的新事件...")
    print(chunk.data)
    print("\n\n")

3.4.2 python异步调用

from langgraph_sdk import get_client
import asyncio

client = get_client(url="http://localhost:2024")

async def main():
    async for chunk in client.runs.stream(
        None,  # Threadless run
        "agent", # Name of assistant. Defined in langgraph.json.
        input={
        "messages": [{
            "role": "human",
            "content": "你是谁?",
            }],
        },
    ):
        print(f"Receiving new event of type: {chunk.event}...")
        print(chunk.data)
        print("\n\n")

asyncio.run(main())

补充:导入包报错

# 本质没错--》只是pycharm在报错

# 要把src目录作为source root

image-20260315174548254

3.5 LangSmith部署和调用

# 1 LangSmith 提供了一套完整的监控和部署流程
# 2 如果我们要上线,可以使用 langsmith提供的服务--》部署在它上面
	-企业中,都部署在自己服务器上
    
    
###################### 步骤-不合适##################
# 1 先决条件 在开始之前,请确保您已具备以下条件
    GitHub 帐户
    LangSmith 帐户(免费注册)
    
# 2 部署代理

## 2.1. 在 GitHub 上创建存储库
您的应用程序的代码必须位于 GitHub 仓库中才能在 LangSmith 上部署。支持公共和私有仓库。对于此快速入门,首先确保您的应用程序与 LangGraph 兼容,方法是按照 本地服务器设置指南进行操作。然后,将您的代码推送到仓库。

## 2.2 部署到 LangSmith
	1 导航到 LangSmith 部署
		登录到 LangSmith。在左侧边栏中,选择 部署。
	2 创建新部署
		点击 + New Deployment(+ 新建部署)按钮。将打开一个面板,您可以在其中填写所需字段。
	3 链接仓库
		如果你是第一次用户,或者正在添加以前未连接的私有仓库,请点击 添加新账户 按钮并按照说明连接你的 GitHub 账户。
	4 部署仓库
		选择你的应用程序的仓库。点击 提交 进行部署。这可能需要大约 15 分钟才能完成。你可以在 部署详情 视图中检查状态。
        
        
## 2.3 在 Studio 中测试您的应用程序
应用程序部署后
    选择您刚刚创建的部署以查看更多详细信息。
    点击右上角的 Studio 按钮。Studio 将打开以显示你的图表。
    
## 2.4 获取部署的 API URL
在 LangGraph 的 部署详情 视图中,点击 API URL 将其复制到剪贴板。
点击 URL 将其复制到剪贴板。




########### 我们#########
把项目部署在有公网的云服务器上---》通过api调用即可

4 工作流和智能体

image-20260315044255715

# 1 LLM应用【LangChain 或LangGraph都可以写】:从[确定工作流]到[自主代理]的三种模式
	-核心区别:LLM在代码执行过程中有多少控制权
    
# 2 openclaw:龙虾
	-部署完后---》给个任务:帮我赚钱
    	-如何干?什么流程? 通通是由 LLM 大模型决定的
        	-收到赚钱任务:大模型分析--》调用工具--》再分析--》再调工具--》返回循环--》最终输出结果--》转了
            
# 3 智能导游
	-启动后:用户输入城市,预算,天数--》按照一个流程--》生成景点,生成食物,判断是否超预算--》工作流执行
    
    
# 4 最开始的 :工作流
# 5 到:高级工作流,高级编排
# 6 到:智能体
-----------第三种是最好的吗?--------------
根据场景来的

# 7 实际工程中,不要盲目追求 Agent
posted @ 2026-03-20 08:21  凫弥  阅读(6)  评论(0)    收藏  举报