首页 / 文章

实用笔记:学习LangGraph——一段穿越智能体与黑板的旅程

《实用笔记:学习LangGraph——从智能体到黑板:适用于采用该模式的团队的契约、校验机制及即插即用代码模块》的操作指南。

发布者:REACTAPP.TOP阅读约 14 分钟3277 词

来源: Learning LangGraph : A Journey Through Agents, Blackboards, and Bottlenecks

以下笔记为“学习 LangGraph:从智能体、黑板到瓶颈的探索”提供了一条实用的学习路径。重点在于契约、校验机制以及可直接插入的代码占位符,而非激励性叙述。 在阅读概述部分时,首先写下契约内容:所需输入、成功标志以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 在功能结果旁记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。

第一步:最简单的智能体

第一步:将最小化代理视为可测量的实体最为有效。在扩大范围之前,先记录一份成功的示例、一个失败案例以及回滚说明。 将配置置于应用程序代码之外。环境文件、密钥存储和功能标志应集中存放,以便操作人员无需查看整个结构即可进行审计。 保持图结构的扁平化与类型化。嵌套的数据块会掩盖哪个节点修改了哪个字段的信息,且在中断后会导致无法继续处理。

from langchain.agents import create_agent          # langchain 1.x (NOT langgraph.prebuilt)
from langchain.tools import tool
from langchain_anthropic import ChatAnthropic

# 1. Model
llm = ChatAnthropic(model="claude-haiku-4-5", max_tokens=1024)

# 2. Tool — a plain function + @tool.
#    - type hints (drug_name: str) become the input schema
#    - the docstring is what the LLM reads to decide WHEN to call it
#    The model only "sees" the signature + docstring, so write the docstring
#    like an API description.
@tool
def get_drug_targets(drug_name: str) -> str:
    """Return the protein targets for a given drug name."""
    return "EGFR, BRAF"   # real data-source call goes here

# 3. System prompt — a plain string
SYSTEM_PROMPT = "You are a research assistant. Use the tools to gather evidence."

# 4. Build the agent
agent = create_agent(model=llm, tools=[get_drug_targets], system_prompt=SYSTEM_PROMPT)

# 5. Run it — pass in {"messages": [...]}, get the full message list back
result = agent.invoke({"messages": [{"role": "user", "content": "What does metformin target?"}]})
print(result["messages"][-1].content)   # last message = final answer
@tool
def calculator(expression: str) -> str:
    """Evaluate a basic arithmetic expression like '2 + 2 * 3'."""
    return str(eval(expression))   # fine for learning; never eval untrusted input in prod

第二步:让两个代理相互通信

第二步:将让两个代理进行通信的过程视为可度量的对象,这样处理效果最佳。在扩大范围之前,先记录一个成功的交互案例、一个失败案例以及回滚说明。同时将正常流程和恢复流程都记录下来。重试机制、人工干预环节以及死信处理都是产品本身的组成部分,而非后续需要补充的内容。要保持图结构的层次简单且类型明确,嵌套的数据块会掩盖哪个节点编写了哪个字段的信息,还会在流程中断后导致无法继续执行。

选项1:消息传递

选项1:将消息系统视为可度量的对象来处理效果最佳。在扩大范围之前,先记录一份理想的操作日志、一个故障案例以及回滚说明。 优先选择小型且可测试的单元,而非庞大的脚本。当某个步骤出现故障时,故障应指向单一责任主体,而非复杂的流程链。 保持图结构的状态简洁且具有类型定义。嵌套的数据块会掩盖哪个节点修改了哪个字段的信息,还会在流程中断后导致无法继续执行。 选项1:将消息系统视为可度量的对象来处理效果最佳。在扩大范围之前,先记录一份理想的操作日志、一个故障案例以及回滚说明。 除了功能结果外,还需记录执行时间以及令牌或查询的成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。

选项2:黑板模型

对于选项2:Blackboard,在修改代码之前需明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能标志应集中存放于一个位置,这样操作人员无需查看整个流程即可进行审核。 对于涉及资金支出或修改生产数据的节点,需设置人工审批环节。编译时的连接方式并不等同于业务流程的完整性。

步骤3:从某个智能体获取结构化数据

对于第3步:从某个智能体中获取输入数据,在修改代码之前需明确输入参数、该步骤的负责人以及结束条件。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及错误处理都是产品功能的一部分,而非后续需要补充的内容。 对于涉及资金支出或修改生产数据的操作,必须经过人工审批。编译时的连接方式并不等同于业务功能的完整性。

from pydantic import BaseModel, Field

class ResearchOutput(BaseModel):
    attraction: str = Field(description="2 sentences about the attraction")
    city: str = Field(description="1 sentence about the city it is in")

agent = create_agent(
    model=llm,
    tools=[attraction_info, city_info],
    system_prompt=SYSTEM_PROMPT,
    response_format=ResearchOutput,   # <- agent emits this typed object
)
result = agent.invoke({"messages": [...]})
out = result["structured_response"]   # a ResearchOutput instance
out.attraction   # str
out.city         # str
def research_node(state):
    out = research(state["topic"])           # ResearchOutput
    return {"research": out.attraction, "city": out.city}

由此产生的耦合关系

对于由此产生的耦合关系,应在修改代码之前明确输入参数、该步骤的负责人以及终止条件。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 优先选择小型、可测试的单元,而非冗长的脚本。当某个步骤失败时,故障应指向单一责任点,而非复杂的流程链。 对于涉及资金支出或修改生产数据的操作,必须经过人工审批。编译时的连接方式并不等同于业务功能的完整性。 对于由此产生的耦合关系,应在修改代码之前明确输入参数、该步骤的负责人以及终止条件。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 在功能结果之外,还需记录执行时间以及令牌或查询的成本。提前了解成本情况,可避免在流程从演示环境转向共享环境时出现意外账单。

SYSTEM_PROMPT = "...\n" + "\n".join(f"- {t.name}: {t.description}" for t in TOOLS)

四种变体,各一个控制面板

在处理“四种变体,各一个控制面板”时,首先需列出相关约定:所需输入、成功信号以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 将配置信息置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于一个位置,这样操作人员无需查看整个系统结构即可进行审计。 在耗时较高的步骤之后设置检查点。当操作人员重新执行后续节点时,恢复流程不应再次调用相同的大型语言模型。

第4步:为何速度如此慢——以及真正的解决方法是什么

在处理第4步“为何速度如此慢——以及究竟什么方法能解决这个问题”时,首先需列出相关约定:所需输入、成功信号以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 同时记录正常流程与异常恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的组成部分,而非后续需要补充的功能。 在成本较高的步骤之后设置检查点。当操作员重新尝试某个后续节点时,恢复流程不应再次调用相同的LLM接口。

def _crowd_score_sync(attraction: str) -> str:   # plain blocking function
    total = 0
    for i in range(60_000_000):
        total += i
    return f"... {total % 100} ..."

@tool
async def crowd_score(attraction: str) -> str:
    return await asyncio.to_thread(_crowd_score_sync, attraction)

为何ReAct智能体始终速度缓慢

在研究为何 ReAct 智能体始终运行缓慢的问题时,首先需明确相关规范:所需的输入参数、成功标志以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 建议使用小型、可测试的单元而非庞大的脚本。当某个步骤失败时,故障应指向单一责任模块,而非复杂的流程链。 在成本较高的步骤之后设置检查点。当操作员重新尝试后续节点时,恢复流程不应再次调用相同的大型语言模型。 在研究为何 ReAct 智能体始终运行缓慢的问题时,首先需明确相关规范:所需的输入参数、成功标志以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 在功能结果旁记录执行时间以及token或查询成本。提前了解成本情况,可避免从演示环境过渡到共享环境时出现意外的费用支出。

第5步:并行运行整个流水线

将第5步中的并行运行整个流水线视为可度量的指标来处理效果最佳。在扩大范围之前,先记录一份理想的输出结果、一个故障案例以及回滚说明。配置应置于应用程序代码之外,环境文件、密钥存储和功能标志应集中存放于一个位置,以便操作人员无需查看整个流程图即可进行审计。同时要保持流程图的状态结构简洁且具有类型定义,嵌套的数据块会掩盖哪个节点修改了哪个字段的信息,还会导致在流程中断后无法继续执行。

第6步:让缓存真正发挥作用

第6步:让缓存真正发挥作用的最佳方式是将其视为可度量的指标。在扩大范围之前,先记录一个成功的用例、一个失败案例以及回滚说明。同时记录正常流程和恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的一部分,而非后续需要补充的功能。保持图结构的层次简单且类型明确,嵌套的数据块会掩盖哪个节点修改了哪个字段的信息,还会导致在中断后无法继续处理。

system_prompt = "You are a travel research agent..."   # string: no keys

content = [{                                            # dict: has keys
    "type": "text",
    "text": "You are a travel research agent...",
    "cache_control": {"type": "ephemeral"},            # <- attaches here
}]
from langchain_anthropic.middleware import AnthropicPromptCachingMiddleware

agent = create_agent(
    model=llm, tools=[...], system_prompt=SYSTEM_PROMPT,  # plain string
    middleware=[AnthropicPromptCachingMiddleware()],
)

第7步:提升黑板的持久性

第7步:要让黑板具备更好的持久性,最佳做法是将其视为可度量的表面。在扩大范围之前,先记录一份理想运行案例、一个故障案例以及回滚说明。 优先选择小型且可测试的单元,而非庞大的脚本。当某一步骤出现故障时,故障应能指向单一责任主体,而非复杂的流程链。 保持图结构的状态简洁且类型明确。嵌套的数据块会掩盖哪个节点修改了哪个字段的信息,还会在进程中断后导致无法继续运行。 第7步:要让黑板具备更好的持久性,最佳做法是将其视为可度量的表面。在扩大范围之前,先记录一份理想运行案例、一个故障案例以及回滚说明。 在功能结果旁同时记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。

操作检查清单

对于操作检查清单,应在修改代码之前明确输入内容、各步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新执行相应步骤,而无需猜测隐藏状态。

将此阶段视为输入与已验证输出之间的契约。为相关成果命名,明确成功判定标准,杜绝默许的半完成状态。

对于涉及资金支出或修改生产数据的操作,必须经过人工审批。编译时的配置并不等同于业务上的完整性。

编写简短的操作手册:包括如何轮换密钥、如何清空队列、以及如何回滚上一次的数据导入操作。

在功能测试结果旁记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外账单。

对于那些会消耗资金或更改生产数据的操作,必须经过人工审批。编译时的配置并不等同于业务功能的完整性。

在推广该技术栈之前,应先冻结版本,为关键流程记录完整的操作日志,并明确回滚步骤。共享环境需要设置速率限制、租户验证机制,以及明确的密钥轮换负责人。与其追求花哨的一次性演示,不如注重扎实的可靠性。

关于7a480fca3eb9的批注:请将提供商密钥存放在仓库之外,为每个会话设置令牌使用上限,并将操作日志与评估用文件一起保存,以便后续模型更换时仍能保持数据可比性。

在处理强化措施笔记0时,首先写下相关契约:所需输入、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。

同时记录正常流程和恢复流程。重试机制、人工审核环节以及错误处理方式都是产品本身的组成部分,而非后续的优化内容。

强化措施细节0/913:需测量该笔记对应的处理时间、错误类型以及令牌消耗情况,然后依据固定的评估标准而非个人经验来决定是否保留该变更。

将强化措施笔记1视为可量化的目标来处理效果最佳。在扩大范围之前,先记录一份理想的操作流程、一个失败案例以及回滚说明。

将此阶段视为输入与验证后输出之间的契约。为相关文档命名,明确成功标准,杜绝无声的半完成状态。

强化措施细节1/913:记录该任务的执行时间、错误类型以及令牌消耗情况,然后依据固定的评估标准而非个人经验来决定是否保留该变更。

对于强化措施2,在修改代码之前需明确输入参数、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。配置信息应置于应用程序代码之外,环境文件、密钥存储以及功能开关应集中存放于一个操作人员可审计的位置,无需查看整个系统结构。

强化措施细节2/913:记录该任务的执行时间、错误类型以及令牌消耗情况,然后依据固定的评估标准而非个人经验来决定是否保留该变更。

在处理强化措施笔记3时,首先列出相关约定:所需输入、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 建议使用小型、可测试的单元,而非冗长的脚本。当某一步骤失败时,故障应能指向单一责任点,而非复杂的流程链。

强化措施细节3/913:针对该笔记,需测量执行时间、错误类型以及代币消耗情况,然后依据固定的评估标准而非主观判断来决定是否保留该修改。

将强化措施笔记4视为可度量的对象来处理效果最佳。在扩大范围之前,先记录一份理想的运行示例、一个失败案例以及回滚说明。 在功能结果旁同时记录执行时间和代币或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外支出。

强化措施细节4/913:为该记录测量运行时间、错误类型以及令牌消耗情况,然后依据固定的问题集而非个别案例来决定是否保留该变更。

对于强化措施记录5,在修改代码之前需明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。同时需将正常流程与故障恢复流程一并记录下来。重试机制、人工审核环节以及错误处理方式都是产品本身的组成部分,而非后续需要补充的内容。

强化措施细节5/913:为该记录测量运行时间、错误类型以及令牌消耗情况,然后依据固定的问题集而非个别案例来决定是否保留该变更。

在处理强化措施第6条时,首先写下相关契约:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改始终符合约定。 将此阶段视为输入与验证后输出之间的契约。为相关产物命名,明确成功判定标准,杜绝默许的部分完成情况。

强化措施细节6/913:需测量该条措施的执行时间、错误类型以及令牌消耗情况,然后依据固定的评估标准而非个人经验来决定是否保留该修改。

将强化措施第7条视为可量化的目标来处理效果最佳。在扩大范围之前,先记录一份理想的运行日志、一个失败案例以及回滚说明。 配置信息应与应用程序代码分开存放。环境文件、密钥存储和功能开关应集中于一个位置,以便操作人员无需查看全部代码结构即可进行审计。

强化措施细节7/913:为该记录测量运行时间、错误类型以及代币消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。

对于强化措施记录8,在修改代码之前需明确输入参数、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。相比复杂的脚本,更应采用小型且可测试的单元。当某一步骤失败时,故障原因应能指向单一责任方,而非混乱的整个流程。

强化措施细节8/913:为该记录测量运行时间、错误类型以及代币消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。

在处理强化措施第9条时,首先写下相关约定:所需输入、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 在功能结果旁记录执行时间以及令牌或查询成本。提前了解成本情况,可避免在代码从演示环境转向共享环境时出现意外费用。

强化措施细节9/913:为该条目测量实际执行时间、错误类型以及令牌消耗情况,然后依据固定的评估标准而非个人经验来决定是否保留该修改。

将强化措施第10条视为可度量的对象来处理效果最佳。在扩大范围之前,先记录一个理想运行案例、一个失败案例以及回滚说明。 同时记录正常流程和恢复流程。重试机制、人工审核环节以及错误处理方式都是产品本身的组成部分,而非后续需要补充的内容。

强化措施细节10/913:记录该步骤的运行时间、错误类型以及代币消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。

对于强化措施笔记11,在修改代码之前需明确输入参数、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。应将此阶段视为输入参数与验证后输出结果之间的契约,为相关成果命名、定义成功检测标准,并拒绝默许的半完成状态。

强化措施细节11/913:记录该步骤的运行时间、错误类型以及代币消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。

在处理强化措施笔记12时,首先写下相关约定:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 应将配置信息与应用程序代码分开存放。环境文件、密钥存储以及功能开关应集中于一个位置,这样操作人员无需查看整个系统结构即可进行审计。

强化措施细节12/913:针对该笔记,需测量执行时间、错误类型以及令牌消耗情况,然后依据固定的评估标准而非个人经验来决定是否保留该变更。

将强化措施笔记13视为可度量的对象来处理效果最佳。在扩大范围之前,先记录一份理想的运行日志、一个失败案例以及回滚说明。 相比复杂的脚本,更应采用小型且可测试的单元。当某个步骤出现故障时,故障原因应能明确指向某个具体职责,而非整个复杂的流程链。

强化措施细节13/913:为该记录测量运行时间、错误类型以及代币消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。

对于强化措施记录14,在修改代码之前需明确输入参数、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。应在功能结果旁记录时间以及代币或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。

强化措施细节14/913:为该记录测量运行时间、错误类型以及代币消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。

在处理强化措施笔记15时,首先写下相关约定:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。

同时记录正常流程和故障恢复流程。重试机制、人工审核环节以及错误处理方式都是产品本身的一部分,而非后续的优化工作。

强化措施细节15/913:需测量该笔记相关的处理时间、错误类型以及令牌消耗情况,然后依据固定的评估标准而非个人经验来决定是否保留该修改。