LangGraph--03--本地服务与 Studio 调试

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


本地服务与 Studio 调试

1. 从零构建一个LangGraph项目

https://smith.langchain.com/settings

python 复制代码
#首先切换到对应的python环境中
conda activate langgraph_course

#安装依赖
pip install -U "langgraph-cli[inmem]" --no-user --no-cache-dir

#查看
conda list 
python 复制代码
#首先切换到对应的python环境中
conda activate langgraph_course

#使用模版创建项目 
langgraph new D:\workspace\LangGraphApp --template new-langgraph-project-python
bash 复制代码
conda activate langgraph_course


pip install -e . --no-user --no-cache-dir
bash 复制代码
# =============================================================================
# [project] ------ 项目元信息(定义"这个包叫什么、依赖什么")
# =============================================================================
[project]
name = "agent"                                          # 包名,pip install 后别的模块用这个名字 import
version = "0.0.1"                                       # 版本号,语义化版本(主版本.次版本.修订号)
description = "Starter template for making a new agent LangGraph."  # 一句话描述
authors = [                                             # 作者列表
    { name = "William Fu-Hinthorn", email = "13333726+hinthornw@users.noreply.github.com" },
]
readme = "README.md"                                    # README 文件路径,PyPI 页面上会渲染它
license = { text = "MIT" }                              # 开源协议,MIT 是最宽松的协议之一
requires-python = ">=3.10"                              # 要求 Python 版本 >= 3.10(因为用了 typing 新特性)
dependencies = [                                        # 运行时依赖------pip install 这个包时自动安装
"langgraph>=1.0.0",  
    "python-dotenv>=1.0.1",                             ]

# =============================================================================
# [project.optional-dependencies] ------ 可选依赖组,"pip install -e .[dev]" 时安装
# =============================================================================
[project.optional-dependencies]
dev = [                                                 # 开发工具组:只给开发者用,生产环境不需要
    "mypy>=1.11.1",  
    "ruff>=0.6.1",   
]

# =============================================================================
# [build-system] ------ 构建工具声明("谁负责把这个包装进环境")
# =============================================================================
[build-system]
requires = ["setuptools>=73.0.0", "wheel"]              # 构建这个包需要的工具:setuptools 是打包引擎,wheel 是二进制格式
build-backend = "setuptools.build_meta"                 # 指定用 setuptools 的最新 API 来构建

# =============================================================================
# [tool.setuptools] ------ setuptools 的配置(包和目录的映射关系)
# =============================================================================
[tool.setuptools]
packages = ["langgraph.templates.agent", "agent"]       # 声明两个包名:一个给模版系统用,一个给用户直接引用用
[tool.setuptools.package-dir]                           # 把包名映射到实际的磁盘目录
"langgraph.templates.agent" = "src/agent"               #   "langgraph.templates.agent" 这个包名 → 指向 src/agent/ 目录
"agent" = "src/agent"                                   #   "agent" 这个包名 → 也指向 src/agent/ 目录
                                                        #   同一个物理目录有两个包名,调用者用哪个都行

# =============================================================================
# [tool.setuptools.package-data] ------ 打包时要带上的非 .py 文件
# =============================================================================
[tool.setuptools.package-data]
"*" = ["py.typed"]                                      # 通配符:所有包都包含 py.typed 文件

# =============================================================================
# [tool.ruff] ------ ruff 代码检查工具的规则配置
# =============================================================================
[tool.ruff]
lint.select = [                                         # 启用哪些检查规则集
    "E",    #  pycodestyle:代码风格错误(如缩进不对、空格问题)
    "F",    #  pyflakes:逻辑错误(如未使用的变量、未定义的名称)
    "I",    #  isort:import 语句排序(按字母顺序、按来源分组)
    "D",    #  pydocstyle:docstring 规范(函数、类必须有文档字符串)
    "D401", #  docstring 第一行必须用命令式语气("Return..." 而非 "Returns...")
    "T201", #  禁止使用 print(),防止提交调试代码
    "UP",   #  pyupgrade:提醒你用新版 Python 语法(如用 | 代替 Optional)
]
lint.ignore = [                                         # 启用的规则集中,哪些具体规则要豁免
    "UP006",    #  豁免:不强制把 Optional[X] 改成 X | None
    "UP007",    #  豁免:不强制把 Union[X, Y] 改成 X | Y
    "UP035",    #  豁免:允许从 typing_extensions 导入(因为要兼容 Python 3.10 的旧 typing)
    "D417",     #  豁免:不强制每个参数都有 docstring 描述(太啰嗦了)
    "E501",     #  豁免:不强制每行不超过 88 字符(有些行确实很长)
]

[tool.ruff.lint.per-file-ignores]
"tests/*" = ["D", "UP"]                                 # 对 tests/ 目录:豁免 D(docstring)和 UP(新版语法)规则
                                                        # 测试不需要严格文档;测试可能故意用旧语法验证兼容性

[tool.ruff.lint.pydocstyle]
convention = "google"                                   # docstring 风格约定:Google 风格(参数用 Args: 标注)

# =============================================================================
# [dependency-groups] ------ PEP 735 的新式依赖分组(取代 pip 的 requirements-dev.txt)
# =============================================================================
[dependency-groups]
dev = [                                                 # 开发依赖组------不仅 pip install 用,各工具也读这个字段
    "anyio>=4.7.0",   
    "langgraph-cli[inmem]>=0.4.14",   
    "mypy>=1.13.0",  
    "pytest>=9.0.3",  
    "ruff>=0.8.2",  
]

2. LangGraph配置解释

LangGraph 构建计算器Agent案例

1.配置.env环境变量

bash 复制代码
# 创建的LANGSMITH_API
LANGSMITH_API_KEY=lsv2_....

# 从DeepSeek官网获取的配置信息
DEEPSEEK_API_KEY=sk-aee.....
DEEPSEEK_BASE_URL=https://api.deepseek.com

2.大模型初始化文件

首先将"env_utils.py"和"init_llm.py"两个文件复制到新建的LangGraph项目中

env_utils.py

python 复制代码
import os

from dotenv import load_dotenv

# 从.env文件加载环境变量
load_dotenv(override=True)

# 加载DeepSeek环境变量
DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")
DEEPSEEK_BASE_URL = os.getenv("DEEPSEEK_BASE_URL")

OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
OPENAI_BASE_URL = os.getenv("OPENAI_BASE_URL")

ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
ANTHROPIC_BASE_URL = os.getenv("ANTHROPIC_BASE_URL")

HUNYUAN_APP_ID = os.getenv("HUNYUAN_APP_ID")
HUNYUAN_SECRET_ID = os.getenv("HUNYUAN_SECRET_ID")
HUNYUAN_SECRET_KEY = os.getenv("HUNYUAN_SECRET_KEY")

DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY")
DASHSCOPE_BASE_URL = os.getenv("DASHSCOPE_BASE_URL")

ZHIPUAI_API_KEY = os.getenv("ZHIPUAI_API_KEY")
ZHIPUAI_BASE_URL = os.getenv("ZHIPUAI_BASE_URL")

init_llm.py

python 复制代码
"""
init_chat_model 初始化聊天模型
"""
from langchain.chat_models import init_chat_model
from langchain_core.language_models import BaseChatModel


from env_utils import DEEPSEEK_API_KEY


deepseek_llm: BaseChatModel = init_chat_model(
    model="deepseek-v4-pro",
    model_provider="deepseek",
    api_key=DEEPSEEK_API_KEY,
    extra_body={
        "thinking": {"type": "disabled"}  # 关闭思考模式
    }
)

deepseek_llm_flash: BaseChatModel = init_chat_model(
    model="deepseek-v4-flash",
    model_provider="deepseek",
    api_key=DEEPSEEK_API_KEY,
    extra_body={
        "thinking": {"type": "disabled"}  # 关闭思考模式
    }
)

3.计算器Agent代码

然后将如下计算器Agent代码复制到项目的"graph.py"文件中

graph.py

python 复制代码
from typing import Annotated, Literal
import operator
from langchain.tools import tool
from langchain.messages import HumanMessage, SystemMessage, AnyMessage
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode
from typing_extensions import TypedDict

from init_llm import deepseek_llm


# 1. 定义工具
@tool
def add(a: int, b: int) -> int:
    """两个整数相加。参数 a: 第一个整数,b: 第二个整数"""
    return a + b


@tool
def multiply(a: int, b: int) -> int:
    """两个整数相乘。参数 a: 第一个整数,b: 第二个整数"""
    return a * b


@tool
def divide(a: int, b: int) -> float:
    """两个整数相除。参数 a: 被除数,b: 除数"""
    return a / b


# 工具列表和工具名索引
tools = [add, multiply, divide]
# 工具名索引,用于根据工具名快速查找工具
tools_by_name = {t.name: t for t in tools}
model_with_tools = deepseek_llm.bind_tools(tools)

# 2. 定义状态
class CalculatorState(TypedDict):
    """计算器 Agent 的状态,消息列表使用 operator.add 做追加合并"""
    messages: Annotated[list[AnyMessage], operator.add]


# 3. 定义节点
def llm_call(state: CalculatorState) -> dict:
    """LLM 节点:调用模型,决定是调用工具还是直接回答"""
    response = model_with_tools.invoke(
        [SystemMessage(content="你是一个数学计算助手。请使用工具完成计算并给出最终答案。")]
        + state["messages"]
    )
    return {"messages": [response]}


# 4. 路由逻辑(条件边)
def should_continue(state: CalculatorState) -> Literal["tool_node", END]:
    """检查最后一条消息,如果有 tool_calls 则进入工具节点,否则结束"""
    last_message = state["messages"][-1]
    # 检查是否有 tool_calls 且 tool_calls 不为空
    if hasattr(last_message, "tool_calls") and last_message.tool_calls:
        return "tool_node"
    return END

# 5. 构建和编译图
builder = StateGraph(CalculatorState)
builder.add_node("llm_call", llm_call)
builder.add_node("tool_node", ToolNode(tools))

builder.add_edge(START, "llm_call")
builder.add_conditional_edges("llm_call", should_continue, ["tool_node", END])
builder.add_edge("tool_node", "llm_call")

graph = builder.compile()

4.启动LangGraph dev

基于现有项目构建LangGraph项目

python 复制代码
from typing import Annotated, Literal
import operator
from langchain.tools import tool
from langchain.messages import HumanMessage, SystemMessage, AnyMessage
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode
from typing_extensions import TypedDict

from init_llm import deepseek_llm


# 1. 定义工具
@tool
def add(a: int, b: int) -> int:
    """两个整数相加。参数 a: 第一个整数,b: 第二个整数"""
    return a + b


@tool
def multiply(a: int, b: int) -> int:
    """两个整数相乘。参数 a: 第一个整数,b: 第二个整数"""
    return a * b


@tool
def divide(a: int, b: int) -> float:
    """两个整数相除。参数 a: 被除数,b: 除数"""
    return a / b


# 工具列表和工具名索引
tools = [add, multiply, divide]
# 工具名索引,用于根据工具名快速查找工具
tools_by_name = {t.name: t for t in tools}
model_with_tools = deepseek_llm.bind_tools(tools)

# 2. 定义状态
class CalculatorState(TypedDict):
    """计算器 Agent 的状态,消息列表使用 operator.add 做追加合并"""
    messages: Annotated[list[AnyMessage], operator.add]


# 3. 定义节点
def llm_call(state: CalculatorState) -> dict:
    """LLM 节点:调用模型,决定是调用工具还是直接回答"""
    response = model_with_tools.invoke(
        [SystemMessage(content="你是一个数学计算助手。请使用工具完成计算并给出最终答案。")]
        + state["messages"]
    )
    return {"messages": [response]}


# 4. 路由逻辑(条件边)
def should_continue(state: CalculatorState) -> Literal["tool_node", END]:
    """检查最后一条消息,如果有 tool_calls 则进入工具节点,否则结束"""
    last_message = state["messages"][-1]
    # 检查是否有 tool_calls 且 tool_calls 不为空
    if hasattr(last_message, "tool_calls") and last_message.tool_calls:
        return "tool_node"
    return END

# 5. 构建和编译图
builder = StateGraph(CalculatorState)
builder.add_node("llm_call", llm_call)
builder.add_node("tool_node", ToolNode(tools))

builder.add_edge(START, "llm_call")
builder.add_conditional_edges("llm_call", should_continue, ["tool_node", END])
builder.add_edge("tool_node", "llm_call")

graph = builder.compile()
相关推荐
你不是我我1 小时前
【AI 测评】PostgreSQL主从流复制实战:数据同步、状态验证与故障切换
数据库·postgresql
月落归舟1 小时前
Redis 三种消息队列实现方案
数据库·redis·list
智购科技智能售货柜1 小时前
自动售货机商品识别YOLO模型训练实战:从6万张图片到98%识别率的完整复盘~YH
运维·服务器·数据库·人工智能·redis·物联网·yolo
ACP广源盛139246256732 小时前
2026 PCIe互连芯片@ACP#国产替代格局解析:芯动科技领跑高端交换芯片赛道
大数据·网络·数据库·人工智能·分布式·嵌入式硬件
代码代码快快显灵2 小时前
MYSQL-DAY2
数据库·mysql
隔窗听雨眠2 小时前
Oracle到PostgreSQL迁移实战:pg_hint_plan执行计划控制完全指南
数据库·postgresql·oracle
IKUN家族2 小时前
常见的依懒
java·服务器·数据库
DBA_G2 小时前
解析GBase 8s数据库锁机制
数据库·oracle
captain3763 小时前
多线程进阶
java·开发语言·数据库