LangGraph Agent 开发指南(12~函数式 API)

一、什么是函数式 API?

1.1 通俗解释

想象你在写普通的 Python 代码:

复制代码
传统图 API:
  你需要把代码拆成"节点"和"边"
  - 定义 StateGraph
  - 添加节点
  - 添加边
  - 编译图
  感觉像在搭积木,有点绕

函数式 API:
  你直接写普通函数就行
  - 用 @entrypoint 标记入口
  - 用 @task 标记任务
  - 该用 if 就用 if,该用 for 就用 for
  感觉就像写普通代码,简单直接

1.2 函数式 API 的优势

优势 说明
最小改动 在现有代码基础上,只需添加装饰器
自然语法 使用 if、for 等原生控制流
无需重构 不需要把代码拆成节点和边
功能完整 支持持久化、记忆、人工干预、流式传输

1.3 两个核心装饰器

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    函数式 API 核心组件                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  @entrypoint                                                │
│  ├── 标记工作流的入口点                                      │
│  ├── 管理执行流程                                           │
│  ├── 处理中断和恢复                                         │
│  └── 支持持久化                                             │
│                                                             │
│  @task                                                      │
│  ├── 标记一个工作单元                                       │
│  ├── 支持异步执行                                           │
│  ├── 结果保存到检查点                                       │
│  └── 支持并行执行                                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.4 函数式 API vs 图 API

对比项 函数式 API 图 API
风格 命令式(写普通代码) 声明式(定义图结构)
控制流 if、for、函数调用 节点和边
学习曲线 低(像写普通代码) 较高(需要理解图概念)
灵活性 高(任意控制流) 中(需要预定义结构)
可视化 较难 容易(图结构清晰)
适用场景 快速集成、复杂逻辑 需要可视化、明确流程

二、入口点(@entrypoint)

2.1 什么是入口点?

复制代码
入口点 = 工作流的起点

作用:
1. 标记工作流的开始
2. 管理执行流程
3. 处理中断和恢复
4. 支持持久化

2.2 定义入口点

python 复制代码
from langgraph.func import entrypoint
from langgraph.checkpoint.memory import MemorySaver

# 同步版本
@entrypoint(checkpointer=MemorySaver())
def my_workflow(input_data: dict) -> int:
    # 工作流逻辑
    ...
    return result

# 异步版本
@entrypoint(checkpointer=MemorySaver())
async def my_workflow(input_data: dict) -> int:
    # 工作流逻辑
    ...
    return result

2.3 入口点规则

复制代码
规则:
1. 必须接受一个位置参数(输入数据)
2. 输入和输出必须是 JSON 可序列化的
3. 如果需要多个输入,使用字典

2.4 可注入参数

入口点可以访问运行时自动注入的参数:

python 复制代码
from langchain_core.runnables import RunnableConfig
from langgraph.func import entrypoint
from langgraph.store.base import BaseStore

@entrypoint(checkpointer=checkpointer, store=in_memory_store)
def my_workflow(
    input_data: dict,              # 输入数据
    *,
    previous: Any = None,          # 短期记忆(上次调用的返回值)
    store: BaseStore,              # 长期记忆存储
    config: RunnableConfig         # 运行时配置
):
    ...
参数 说明
previous 上次调用的返回值(短期记忆)
store 长期记忆存储
config 运行时配置(包含 thread_id 等)

三、任务(@task)

3.1 什么是任务?

复制代码
任务 = 一个工作单元

特点:
1. 可以异步执行
2. 结果保存到检查点
3. 支持并行执行
4. 恢复时无需重新计算

3.2 定义任务

python 复制代码
from langgraph.func import task

@task
def slow_computation(input_value: int) -> int:
    # 模拟耗时操作
    time.sleep(1)
    return input_value * 2

3.3 任务规则

复制代码
规则:
1. 只能在入口点、其他任务或图节点内调用
2. 输出必须是 JSON 可序列化的
3. 不能直接从主程序调用

3.4 获取任务结果

python 复制代码
# 同步方式
@entrypoint(checkpointer=checkpointer)
def my_workflow(input_data: int) -> int:
    future = slow_computation(input_data)
    return future.result()  # 等待结果

# 异步方式
@entrypoint(checkpointer=checkpointer)
async def my_workflow(input_data: int) -> int:
    return await slow_computation(input_data)

3.5 何时使用任务?

复制代码
使用任务的场景:

1. 检查点
   - 需要保存耗时操作的结果
   - 恢复时无需重新计算

2. 人工干预
   - 必须用任务封装随机性操作(如 API 调用)
   - 确保工作流可以正确恢复

3. 并行执行
   - 多个 I/O 密集型任务
   - 同时调用多个 API

4. 可观测性
   - 使用 LangSmith 跟踪进度
   - 监控单个操作执行

5. 重试工作
   - 封装重试逻辑
   - 处理故障和不一致

四、执行工作流

4.1 调用方式

python 复制代码
config = {"configurable": {"thread_id": "some_thread_id"}}

# 同步调用
result = my_workflow.invoke(input_data, config)

# 异步调用
result = await my_workflow.ainvoke(input_data, config)

# 流式输出
for chunk in my_workflow.stream(input_data, config):
    print(chunk)

# 异步流式输出
async for chunk in my_workflow.astream(input_data, config):
    print(chunk)

4.2 恢复执行

python 复制代码
from langgraph.types import Command

# 中断后恢复
result = my_workflow.invoke(
    Command(resume=resume_value),
    config
)

# 错误后恢复
result = my_workflow.invoke(None, config)

五、完整示例

5.1 简单工作流

python 复制代码
import uuid
from langgraph.func import entrypoint, task
from langgraph.checkpoint.memory import MemorySaver

# 任务:检查数字是否为偶数
@task
def is_even(number: int) -> bool:
    return number % 2 == 0

# 任务:格式化消息
@task
def format_message(is_even: bool) -> str:
    return "数字是偶数" if is_even else "数字是奇数"

# 入口点
checkpointer = MemorySaver()

@entrypoint(checkpointer=checkpointer)
def workflow(inputs: dict) -> str:
    """判断数字奇偶性的工作流"""
    even = is_even(inputs["number"]).result()
    return format_message(even).result()

# 执行
config = {"configurable": {"thread_id": str(uuid.uuid4())}}
result = workflow.invoke({"number": 7}, config=config)
print(result)  # 输出:数字是奇数

5.2 使用 LLM 撰写论文

python 复制代码
import uuid
from langchain_openai import ChatOpenAI
from langgraph.func import entrypoint, task
from langgraph.checkpoint.memory import MemorySaver

llm = ChatOpenAI(model="gpt-3.5-turbo")

# 任务:使用 LLM 生成论文
@task
def compose_essay(topic: str) -> str:
    """生成关于给定主题的论文"""
    response = llm.invoke([
        {"role": "system", "content": "你是一个写作助手"},
        {"role": "user", "content": f"写一篇关于 {topic} 的论文"}
    ])
    return response.content

# 入口点
checkpointer = MemorySaver()

@entrypoint(checkpointer=checkpointer)
def workflow(topic: str) -> str:
    """生成论文的工作流"""
    return compose_essay(topic).result()

# 执行
config = {"configurable": {"thread_id": str(uuid.uuid4())}}
result = workflow.invoke("飞行历史", config=config)
print(result)

5.3 并行执行

python 复制代码
import uuid
from langchain_openai import ChatOpenAI
from langgraph.func import entrypoint, task
from langgraph.checkpoint.memory import MemorySaver

llm = ChatOpenAI(model="gpt-3.5-turbo")

# 任务:生成段落
@task
def generate_paragraph(topic: str) -> str:
    response = llm.invoke([
        {"role": "system", "content": "你是一个写作助手"},
        {"role": "user", "content": f"写一段关于 {topic} 的文字"}
    ])
    return response.content

# 入口点
checkpointer = MemorySaver()

@entrypoint(checkpointer=checkpointer)
def workflow(topics: list[str]) -> str:
    """并行生成多个段落"""
    # 并行调用
    futures = [generate_paragraph(topic) for topic in topics]
    # 等待所有结果
    paragraphs = [f.result() for f in futures]
    return "\n\n".join(paragraphs)

# 执行
config = {"configurable": {"thread_id": str(uuid.uuid4())}}
result = workflow.invoke(
    ["量子计算", "气候变化", "航空历史"],
    config=config
)
print(result)

六、人工干预

6.1 使用 interrupt

python 复制代码
import uuid
import time
from langgraph.func import entrypoint, task
from langgraph.types import interrupt, Command
from langgraph.checkpoint.memory import MemorySaver

# 任务:撰写论文
@task
def write_essay(topic: str) -> str:
    time.sleep(1)  # 模拟耗时操作
    return f"关于 {topic} 的论文"

# 入口点
checkpointer = MemorySaver()

@entrypoint(checkpointer=checkpointer)
def workflow(topic: str) -> dict:
    """撰写论文并请求人工审核"""
    essay = write_essay(topic).result()

    # 中断,等待人工审核
    is_approved = interrupt({
        "essay": essay,
        "action": "请审核论文"
    })

    return {
        "essay": essay,
        "is_approved": is_approved
    }

# 第一次执行(会中断)
config = {"configurable": {"thread_id": str(uuid.uuid4())}}
for item in workflow.stream("猫", config):
    print(item)

# 恢复执行,提供审核结果
for item in workflow.stream(Command(resume=True), config):
    print(item)

6.2 执行流程

复制代码
第一次执行:
1. 调用 write_essay 任务
2. 任务完成,结果保存到检查点
3. 遇到 interrupt,工作流暂停
4. 返回中断信息给用户

恢复执行:
1. 从检查点加载 write_essay 结果(不重新计算)
2. 跳过已执行的部分
3. 使用 resume 值继续执行
4. 返回最终结果

七、短期记忆

7.1 使用 previous 参数

python 复制代码
from langgraph.func import entrypoint
from langgraph.checkpoint.memory import MemorySaver

checkpointer = MemorySaver()

@entrypoint(checkpointer=checkpointer)
def my_workflow(number: int, *, previous: Any = None) -> int:
    """累加数字"""
    previous = previous or 0
    return number + previous

config = {"configurable": {"thread_id": "1"}}

print(my_workflow.invoke(1, config))  # 1 (previous 是 None)
print(my_workflow.invoke(2, config))  # 3 (previous 是 1)
print(my_workflow.invoke(3, config))  # 6 (previous 是 3)

7.2 解耦返回值和保存值

使用 entrypoint.final 可以分别控制返回值和保存值:

python 复制代码
from typing import Optional
from langgraph.func import entrypoint
from langgraph.checkpoint.memory import MemorySaver

checkpointer = MemorySaver()

@entrypoint(checkpointer=checkpointer)
def accumulate(n: int, *, previous: Optional[int]) -> entrypoint.final[int, int]:
    """累加,但返回上一次的结果"""
    previous = previous or 0
    total = previous + n
    # 返回 previous,保存 total
    return entrypoint.final(value=previous, save=total)

config = {"configurable": {"thread_id": "1"}}

print(accumulate.invoke(1, config))  # 0 (返回 previous,保存 1)
print(accumulate.invoke(2, config))  # 1 (返回 previous,保存 3)
print(accumulate.invoke(3, config))  # 3 (返回 previous,保存 6)

7.3 聊天机器人示例

python 复制代码
from langchain_core.messages import BaseMessage
from langgraph.graph import add_messages
from langgraph.func import entrypoint, task
from langgraph.checkpoint.memory import MemorySaver
from langchain_openai import ChatOpenAI

model = ChatOpenAI(model="gpt-4")

@task
def call_model(messages: list[BaseMessage]):
    return model.invoke(messages)

checkpointer = MemorySaver()

@entrypoint(checkpointer=checkpointer)
def workflow(inputs: list[BaseMessage], *, previous: list[BaseMessage]):
    # 合并历史消息
    if previous:
        inputs = add_messages(previous, inputs)

    # 调用模型
    response = call_model(inputs).result()

    # 返回响应,保存完整对话历史
    return entrypoint.final(
        value=response,
        save=add_messages(inputs, response)
    )

config = {"configurable": {"thread_id": "1"}}

# 第一轮对话
input_msg = {"role": "user", "content": "你好!我是小明"}
for chunk in workflow.stream([input_msg], config, stream_mode="values"):
    print(chunk.content)

# 第二轮对话
input_msg = {"role": "user", "content": "我叫什么名字?"}
for chunk in workflow.stream([input_msg], config, stream_mode="values"):
    print(chunk.content)  # 会回答"小明"

八、重试策略

8.1 配置重试

python 复制代码
from langgraph.func import entrypoint, task
from langgraph.types import RetryPolicy

# 配置重试策略
retry_policy = RetryPolicy(retry_on=ValueError)

@task(retry=retry_policy)
def get_info():
    # 可能失败的操作
    ...
    return result

checkpointer = MemorySaver()

@entrypoint(checkpointer=checkpointer)
def main(inputs):
    return get_info().result()

8.2 完整示例

python 复制代码
from langgraph.func import entrypoint, task
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import RetryPolicy

attempts = 0

retry_policy = RetryPolicy(retry_on=ValueError)

@task(retry=retry_policy)
def get_info():
    global attempts
    attempts += 1

    if attempts < 2:
        raise ValueError('失败')
    return "成功"

checkpointer = MemorySaver()

@entrypoint(checkpointer=checkpointer)
def main(inputs):
    return get_info().result()

config = {"configurable": {"thread_id": "1"}}
result = main.invoke({}, config=config)
print(result)  # 输出:成功

九、缓存任务

9.1 配置缓存

python 复制代码
import time
from langgraph.cache.memory import InMemoryCache
from langgraph.func import entrypoint, task
from langgraph.types import CachePolicy

@task(cache_policy=CachePolicy(ttl=120))  # 缓存 120 秒
def slow_add(x: int) -> int:
    time.sleep(1)  # 模拟耗时操作
    return x * 2

@entrypoint(cache=InMemoryCache())
def main(inputs: dict) -> dict:
    result1 = slow_add(inputs["x"]).result()
    result2 = slow_add(inputs["x"]).result()  # 会使用缓存
    return {"result1": result1, "result2": result2}

for chunk in main.stream({"x": 5}, stream_mode="updates"):
    print(chunk)

# 输出:
# {'slow_add': 10}
# {'slow_add': 10, '__metadata__': {'cached': True}}
# {'main': {'result1': 10, 'result2': 10}}

十、错误后恢复

10.1 工作原理

复制代码
错误后恢复:

1. 工作流执行到一半出错
2. 已完成的任务结果保存到检查点
3. 使用 None 和相同的 thread_id 恢复
4. 从检查点加载已完成的任务结果
5. 继续执行未完成的部分

10.2 完整示例

python 复制代码
import time
from langgraph.func import entrypoint, task
from langgraph.checkpoint.memory import MemorySaver

attempts = 0

@task
def get_info():
    global attempts
    attempts += 1
    if attempts < 2:
        raise ValueError("失败")
    return "成功"

@task
def slow_task():
    time.sleep(1)
    return "耗时任务完成"

checkpointer = MemorySaver()

@entrypoint(checkpointer=checkpointer)
def main(inputs):
    slow_task_result = slow_task().result()  # 会保存结果
    get_info().result()  # 第一次会失败
    return slow_task_result

config = {"configurable": {"thread_id": "1"}}

# 第一次执行(会失败)
try:
    main.invoke({}, config=config)
except ValueError:
    print("执行失败")

# 恢复执行
result = main.invoke(None, config=config)
print(result)  # 输出:耗时任务完成
# 注意:slow_task 不会重新执行,结果从检查点加载

十一、与图 API 混用

11.1 从函数式 API 调用图

python 复制代码
from typing import TypedDict
from langgraph.func import entrypoint
from langgraph.graph import StateGraph
from langgraph.checkpoint.memory import MemorySaver

# 定义状态
class State(TypedDict):
    foo: int

# 定义节点
def double(state: State) -> State:
    return {"foo": state["foo"] * 2}

# 使用图 API 构建图
builder = StateGraph(State)
builder.add_node("double", double)
builder.set_entry_point("double")
graph = builder.compile()

# 使用函数式 API 定义工作流
checkpointer = MemorySaver()

@entrypoint(checkpointer=checkpointer)
def workflow(x: int) -> dict:
    # 调用图
    result = graph.invoke({"foo": x})
    return {"bar": result["foo"]}

# 执行
config = {"configurable": {"thread_id": "1"}}
print(workflow.invoke(5, config=config))  # 输出:{'bar': 10}

11.2 调用其他入口点

python 复制代码
from langgraph.func import entrypoint
from langgraph.checkpoint.memory import MemorySaver

checkpointer = MemorySaver()

# 子工作流
@entrypoint()  # 自动使用父入口点的检查点
def multiply(inputs: dict) -> int:
    return inputs["a"] * inputs["b"]

# 主工作流
@entrypoint(checkpointer=checkpointer)
def main(inputs: dict) -> dict:
    result = multiply.invoke({"a": inputs["x"], "b": inputs["y"]})
    return {"product": result}

# 执行
config = {"configurable": {"thread_id": "1"}}
print(main.invoke({"x": 6, "y": 7}, config=config))  # 输出:{'product': 42}

十二、流式传输

12.1 基本用法

python 复制代码
from langgraph.func import entrypoint
from langgraph.checkpoint.memory import MemorySaver
from langgraph.config import get_stream_writer

checkpointer = MemorySaver()

@entrypoint(checkpointer=checkpointer)
def main(inputs: dict) -> int:
    writer = get_stream_writer()

    writer("开始处理")
    result = inputs["x"] * 2
    writer(f"结果是 {result}")

    return result

config = {"configurable": {"thread_id": "abc"}}

for mode, chunk in main.stream(
    {"x": 5},
    stream_mode=["custom", "updates"],
    config=config
):
    print(f"{mode}: {chunk}")

十三、常见问题

Q1: 函数式 API 和图 API 如何选择?

复制代码
选择函数式 API:
- 快速集成现有代码
- 复杂控制流(if、for 混用)
- 不需要可视化
- 喜欢命令式编程风格

选择图 API:
- 需要可视化工作流
- 流程相对固定
- 需要明确的状态管理
- 喜欢声明式编程风格

Q2: 任务可以嵌套吗?

python 复制代码
# 可以!任务可以调用其他任务
@task
def task_a():
    return "A"

@task
def task_b():
    result = task_a().result()  # 任务内调用任务
    return result + "B"

Q3: 如何处理副作用?

python 复制代码
# 副作用操作应该放在任务中
@task
def send_email(content: str):
    # 发送邮件(副作用)
    ...
    return "已发送"

@entrypoint(checkpointer=checkpointer)
def workflow(inputs):
    return send_email(inputs["content"]).result()

Q4: 如何确保确定性?

python 复制代码
# 所有随机性操作都要放在任务中
@task
def random_operation():
    import random
    return random.randint(1, 100)

@entrypoint(checkpointer=checkpointer)
def workflow(inputs):
    result = random_operation().result()
    return result

十四、API 速查表

14.1 装饰器

装饰器 说明
@entrypoint() 标记入口点
@task() 标记任务

14.2 入口点参数

参数 说明
checkpointer 检查点存储
store 长期记忆存储
cache 缓存存储

14.3 任务参数

参数 说明
retry 重试策略
cache_policy 缓存策略

14.4 可注入参数

参数 说明
previous 上次调用的返回值
store 长期记忆存储
config 运行时配置

十五、延伸阅读


总结

函数式 API 的核心要点:

  1. @entrypoint:标记工作流入口,管理执行流程
  2. @task:标记工作单元,支持检查点和并行
  3. 自然语法:使用 if、for 等原生控制流
  4. 功能完整:支持持久化、记忆、人工干预、流式传输

函数式 API 的优势:

  • 最小改动:只需添加装饰器
  • 自然语法:像写普通代码
  • 无需重构:不需要拆成节点和边
  • 功能完整:与图 API 功能相同

一句话总结:函数式 API 让你用普通 Python 代码的方式构建工作流,同时享受 LangGraph 的所有功能。

相关推荐
闵孚龙1 小时前
Claude Code Hooks 用户自定义拦截点全解析:AI Agent 自动化、安全治理、插件扩展、可观测性核心机制
人工智能·安全·自动化
yivifu1 小时前
跟水印杠上了——顺便巩固Tkinter的GUI编程
python·opencv·tkinter·去水印
天涯明月19931 小时前
AEnvironment深度研究报告
人工智能·后端·云原生
2301_803934611 小时前
html标签怎样划分页面区域_section与div的区别【介绍】
jvm·数据库·python
暗夜猎手-大魔王1 小时前
HermesAgent上下文学习
人工智能
TTGGGFF1 小时前
AI摆摊:在 muShanghai × 观猹 AI 练摊集市的一次高密度体验
人工智能
知学致远1 小时前
Python基础语法_01-注释、输入输出、变量
python
沈浩(种子思维作者)1 小时前
物理的本质是数学,还是数学只是描述物理的方便之语?
人工智能·python·算法
智流学社1 小时前
AI 重构产研线:我怎么把角色交接的 40% 信息损耗压到0
人工智能·深度学习·自然语言处理·重构