一、什么是函数式 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 的核心要点:
- @entrypoint:标记工作流入口,管理执行流程
- @task:标记工作单元,支持检查点和并行
- 自然语法:使用 if、for 等原生控制流
- 功能完整:支持持久化、记忆、人工干预、流式传输
函数式 API 的优势:
- 最小改动:只需添加装饰器
- 自然语法:像写普通代码
- 无需重构:不需要拆成节点和边
- 功能完整:与图 API 功能相同
一句话总结:函数式 API 让你用普通 Python 代码的方式构建工作流,同时享受 LangGraph 的所有功能。