实用提示:为你的AI智能体赋予记忆功能——然后观察它的所有行为
《实用笔记》操作指南:为你的 AI 智能体赋予记忆功能——然后监控它的所有行为:适用于采用该模式的团队的合同、检查项以及可直接插入的代码模块。
本指南将逐步构建从原始材料到可运行系统的完整流程,内容为:赋予你的 AI 智能体记忆功能——然后通过 LangSmith 观看作者的实际操作。重点在于可操作的步骤、明确的检查点,以及无需猜测意图即可直接放入代码仓库的代码。 在概览阶段,应在修改代码之前明确输入参数、各步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行相应步骤,而无需猜测其中的隐藏状态。 配置信息应与应用程序代码分开存放。环境文件、密钥存储以及功能开关都应集中管理,这样操作人员只需查看这些特定内容,无需浏览整个系统结构。
架构设计
在处理“架构设计”阶段时,首先需写下相关契约:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 同时记录正常流程与异常恢复路径。重试机制、人工审核环节以及死信处理都是产品本身的组成部分,而非后续的优化工作。 在成本较高的步骤之后设置检查点。当操作员重新尝试某个节点时,恢复流程不应再次调用相同的大型语言模型。
┌─────────────────────┐
│ User │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ AI Agent │
│ LangChain Agent │
└──────────┬──────────┘
│
┌─────────────┴─────────────┐
│ │
▼ ▼
┌─────────────────┐ ┌──────────────────┐
│ Conversation │ │ Tools │
│ Memory │ │ │
│ InMemorySaver │ │ Tavily Web Search│
└─────────────────┘ └──────────────────┘
│ │
└─────────────┬─────────────┘
│
▼
┌─────────────────────┐
│ Ollama │
│ Llama 3.2 │
└─────────────────────┘
│
▼
┌─────────────────────┐
│ LangSmith │
│ Traces & Debugging │
└─────────────────────┘
1. 使用 Ollama 在本地运行大型语言模型
在完成“运行大语言模型”这一阶段时,首先需明确相关约定:所需的输入参数、成功标志,以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改始终符合预期。 建议采用小型、可测试的单元而非庞大的脚本。当某个步骤出现故障时,故障应指向单一的责任模块,而非复杂的流程链。 每次调用时都要记录请求ID、模型ID以及响应延迟。如果没有这些记录,间歇性的服务错误就会被视为应用程序的缺陷。
from langchain_ollama import ChatOllama
llm = ChatOllama(
model="llama3.2:latest",
base_url="http://localhost:11434",
temperature=0,
)
model
在处理模型阶段时,首先写下契约:所需的输入、成功信号以及部分失败时的处理方式。这份清单能确保后续的代码修改保持透明。 将这一阶段视为输入与验证后输出之间的契约。为相关成果命名,定义成功检查标准,并拒绝默许的不完整处理。 缓存稳定的系统指令和工具架构。重复发送相同的开头信息是造成资源浪费的常见原因。
model="llama3.2:latest"
在处理模型阶段时,首先写下契约:所需的输入、成功信号以及部分失败时的处理方式。这份清单能确保后续的代码修改保持透明。 将配置信息置于应用程序代码之外。环境文件、密钥存储和功能开关应集中存放,以便操作员无需查看整个系统结构即可进行审计。
base_url
将 baseurl 阶段视为可度量的对象时,其效果最佳。在扩大范围之前,先记录一个成功的案例、一个失败案例以及回滚说明。 同时记录正常流程和恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的一部分,而非后续需要补充的内容。 保持图结构的状态简洁且类型明确。嵌套的 blob 会掩盖哪个节点修改了哪个字段的信息,还会在中断后导致无法继续处理。
base_url="http://localhost:11434"
temperature
将温度阶段视为可测量的表面来处理时,其效果最佳。在扩大范围之前,先记录一个成功的案例、一个失败案例以及回滚说明。 优先选择小型且可测试的单元,而非庞大的脚本。当某个步骤失败时,故障应指向单一的责任主体,而非复杂的流程链。 保持图表状态简洁且具有类型定义。嵌套的数据块会掩盖哪个节点修改了哪个字段的信息,还会在中断后导致无法继续处理。
temperature=0
2. 创建智能体
将“创建智能体”这一阶段视为可度量的工作面最为有效。在扩大范围之前,需记录一份最佳案例、一个故障场景以及回滚说明。 应将此阶段视为输入与已验证输出之间的契约。为相关成果命名,明确成功标准,绝不允许出现无声的半完成状态。 需保持图结构简洁且类型明确。嵌套的数据块会掩盖哪个节点修改了哪个字段的信息,还会在中断后导致无法继续处理。 将“创建智能体”这一阶段视为可度量的工作面最为有效。在扩大范围之前,需记录一份最佳案例、一个故障场景以及回滚说明。 配置应置于应用程序代码之外。环境文件、密钥存储及功能开关应集中存放于一处,以便操作人员无需查看整个图结构即可进行审计。
from langchain.agents import create_agent
agent = create_agent(
model=llm
)
agent = create_agent(
model=llm,
tools=[tool1],
system_prompt="You are an intelligent knowlegable agent"
)
3. 为智能体配备网络搜索工具
在“为代理提供3项功能”的阶段,应在修改代码之前明确输入参数、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的组成部分,而非后续需要补充的功能。 在网关处进行身份验证,在数据层再次授权。仅凭承载令牌并不足以界定租户边界。
from langchain.tools import tool
@tool("web_search", description="Search the web for information")
def tool1(query: str) -> Dict[str, Any]:
tavily = TavilyClient()
return tavily.search(query=query)
tavily = TavilyClient()
return tavily.search(query=query)
User Question
│
▼
LLM
│
│ "I need external information"
▼
Web Search Tool
│
▼
Tavily API
│
▼
Search Results
│
▼
LLM
│
▼
Final Answer
4. 问题所在:大语言模型不会自动记住作者
在“4 The Problem LLMs”阶段,修改代码之前需明确输入内容、该步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。相比冗长的脚本,更应采用小型且可测试的单元。当某一步骤失败时,故障原因应能指向单一责任主体,而非复杂的流程链。若后续步骤为代码或工具调用,相比自由形式的文字描述,结构化且经过模式验证的输出更为合适。
from langgraph.checkpoint.memory import InMemorySaver
agent = create_agent(
model=llm,
checkpointer=InMemorySaver()
)
Question → LLM → Answer
5. InMemorySaver — 为智能体提供记忆功能
对于5个InMemorySaver执行阶段,在修改代码之前需明确输入参数、该步骤的负责人以及终止条件。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 将此阶段视为输入与已验证输出之间的契约。为相关成果命名,定义成功判定标准,杜绝无声的半完成状态。 对于涉及资金支出或修改生产数据的操作,必须经过人工审批。编译时的连接方式并不等同于业务上的完整性。 对于5个InMemorySaver执行阶段,在修改代码之前需明确输入参数、该步骤的负责人以及终止条件。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 将配置信息置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于操作人员可审计的位置,无需阅读全部内容。
图表。
checkpointer=InMemorySaver()
6. 最重要的参数:thread_id
在处理“6个最重要阶段”时,首先写下契约内容:所需输入、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的组成部分,而非后续的优化工作。 在成本较高的步骤之后设置检查点。当操作员重新尝试某个后续节点时,恢复流程不应再次调用相同的LLM接口。
config = {
"configurable": {
"thread_id": "1"
}
}
Thread 1
────────────
User → My favorite color is Green
AI → Great!
User → What's my favorite color?
AI → Green
Thread 2
────────────
User → My favorite color is Blue
AI → Blue
7. 第一次对话
在完成“7个初始对话阶段”时,首先写下相关约定:所需的输入参数、成功标志以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 优先选择小型、可测试的单元,而非庞大的脚本。当某个步骤失败时,故障应指向单一责任模块,而非复杂的流程链。 在成本较高的步骤之后设置检查点。当操作员重新尝试后续节点时,恢复流程不应再次调用相同的LLM接口。
question = HumanMessage(
content="I am X my favorite color is Green"
)
response = agent.invoke(
{"messages": [question]},
config,
)
"thread_id": "1"
8. 后续向智能体提问
在完成“向智能体提问”的8个阶段时,首先需写明合同条款:所需输入、成功标志以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 将这一阶段视为输入与验证后输出之间的契约。为相关成果命名,明确成功判定标准,并拒绝默许部分完成的情况。 在成本较高的步骤之后设置检查点。当操作员重新尝试后续节点时,恢复流程不应再次调用相同的LLM接口。 在完成“向智能体提问”的8个阶段时,首先需写明合同条款:所需输入、成功标志以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 将配置信息置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于一个位置,以便操作员无需查看整个流程即可进行审计。
question2 = HumanMessage(
content="What's my favorite color?"
)
response2 = agent.invoke(
{"messages": [question2]},
config,
)
config = {
"configurable": {
"thread_id": "1"
}
}
Your favorite color is Green!
9. 为何thread_id如此重要
在将“9个为什么:为何thread_id如此重要”这一分析方法视为可度量的分析维度时,其效果最佳。在扩大分析范围之前,先记录一份典型的成功案例、一个故障案例以及回滚说明。 同时记录正常流程和恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的一部分,而非后续需要补充的功能。 保持图结构的状态简洁且类型明确。嵌套的数据块会掩盖哪个节点编写了哪个字段的信息,还会在中断后导致无法继续执行。
Customer A → thread_id = "customer-A"
Customer B → thread_id = "customer-B"
Customer C → thread_id = "customer-C"
AI Agent
│
┌────────┼────────┐
▼ ▼ ▼
Customer A Customer B Customer C
│ │ │
Thread A Thread B Thread C
10. 但仅有内存并不够
“10 But Memory Isn”阶段若被视为可度量的对象,效果会更好。在扩大范围之前,先记录一个成功的案例、一个失败案例以及回滚说明。 优先选择小型且可测试的单元,而非庞大的脚本。当某个步骤失败时,故障应指向单一责任点,而非复杂的流程链。 保持图表状态简洁且具有类型定义。嵌套的数据块会掩盖哪个节点修改了哪个字段的信息,还会在中断后导致无法继续处理。
User Question
↓
Agent
↓
LLM decides to call web_search
↓
Tavily
↓
Search results
↓
LLM
↓
Final answer
11. 进入LangSmith
将11 Enter LangSmith阶段视为可度量的界面使用效果最佳。在扩大范围之前,需记录一份理想输出样本、一个失败案例以及回滚说明。 应将此阶段视为输入与已验证输出之间的契约。为相关成果命名,明确成功标准,杜绝默许的半完成状态。 保持图结构简洁且类型明确。嵌套的数据块会掩盖哪个节点修改了哪个字段,还会在中断后导致无法继续处理。 将11 Enter LangSmith阶段视为可度量的界面使用效果最佳。在扩大范围之前,需记录一份理想输出样本、一个失败案例以及回滚说明。 将配置置于应用程序代码之外。环境文件、密钥存储和功能开关应集中存放于一处,以便操作人员无需查看整个图结构即可进行审计。
os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_API_KEY"] = "YYY"
os.environ["LANGSMITH_ENDPOINT"] = "https://langsmith-endpoint"
os.environ["LANGSMITH_PROJECT"] = "local-ollama-agent"
LANGSMITH_TRACING=true
12. 为何追踪比print()更优
在进入“12个为什么追溯”阶段时,应在修改代码之前明确输入参数、各步骤的负责人以及终止标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及错误处理都是产品本身的组成部分,而非后续需要补充的内容。 对于涉及资金支出或修改生产数据的操作,必须经过人工审批。编译时的逻辑连接并不等同于业务功能的完整性。
print(response)
Question
│
├── LLM call
│
├── Tool decision
│
├── Web search
│
├── Tool response
│
├── Another LLM call
│
└── Final response
13. 我们能从追踪中学习到什么?
对于“13 What Can We”阶段的实施,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 相较于庞大的脚本,更应优先使用小型且可测试的单元。当某个步骤失败时,故障原因应能指向单一责任模块,而非复杂的流程链。 对于涉及资金支出或修改生产数据的操作,必须经过人工审批。编译时的逻辑连接并不等同于业务功能的完整性。
Total execution: 8 seconds
LLM call 1.5 sec
Web search 5.2 sec
Final LLM call 1.3 sec
14. 将内存管理与可观测性结合
在处理“14 内存存储”阶段时,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 应将此阶段视为输入与已验证输出之间的契约。为相关成果命名,定义成功判定标准,杜绝默许的半完成状态。 对于涉及资金支出或修改生产数据的操作,必须经过人工审批。编译时的连接关系并不等同于业务上的完整性。 在“14 内存存储”阶段,修改代码之前需明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 配置信息应置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于一个操作人员可审计的位置,无需查看整个系统结构。
┌───────────────┐
│ User │
└───────┬───────┘
│
▼
┌────────────────────┐
│ AI Agent │
└─────────┬──────────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
Memory LLM Tools
InMemorySaver Ollama Tavily
│ │ │
└──────────────┼──────────────┘
│
▼
┌─────────────┐
│ LangSmith │
│ Tracing │
└─────────────┘
内存处理
在处理内存相关阶段时,首先需记录下契约内容:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改不会偏离原定设计。 同时记录正常流程与异常恢复路径。重试机制、人工审核环节以及死信处理都是产品功能的一部分,而非后续需要补充的内容。 在耗时较高的步骤之后设置检查点。当操作员重新尝试某个后续节点时,恢复流程不应再次调用相同的LLM接口。
LLM
在处理大语言模型阶段时,首先写下相关规范:所需的输入参数、成功标志以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。 优先选择小型、可测试的单元,而非冗长的脚本。当某个步骤失败时,故障应能指向单一的责任模块,而非复杂的流程链。 缓存稳定的系统指令和工具结构。重复发送相同的开头信息是造成资源浪费的常见原因。
工具
在处理“工具”阶段时,首先需写下契约:所需的输入参数、成功信号以及部分失败时的处理方式。这份清单能确保后续的代码修改保持一致性。 将此阶段视为输入与验证后输出之间的契约。为相关产物命名,明确成功判定标准,杜绝无声的半完成状态。 需记录每次调用的工具名称、参数哈希值、延迟时间以及执行结果。没有这些记录,调试过程将会浪费大量时间。 在处理“工具”阶段时,首先需写下契约:所需的输入参数、成功信号以及部分失败时的处理方式。这份清单能确保后续的代码修改保持一致性。 应将配置信息置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于一处,以便操作人员无需查看整个系统结构即可进行审计。
LangSmith
将 LangSmith 阶段视为可度量的界面时,其效果最佳。在扩大范围之前,先记录一个成功的案例、一个失败案例以及回滚说明。同时记录正常流程和恢复流程。重试机制、人工审核环节以及死信处理都是产品本身的一部分,而非后续需要补充的内容。保持图表状态简洁且具有类型定义。嵌套的数据块会掩盖哪个节点编写了哪个字段的信息,还会在中断后导致流程无法继续。
15. 完整的概念流程
在将“15个完整概念阶段”视为可度量的结构来处理时,其效果最佳。在扩大范围之前,先记录一个成功的案例、一个失败案例以及回滚说明。 相较于庞大的脚本,应优先选择小型且可测试的单元。当某个步骤失败时,故障应能指向单一的责任主体,而非复杂的流程链。 保持图结构的状态简洁且具有类型定义。嵌套的数据块会掩盖哪个节点修改了哪个字段的信息,还会在流程中断后导致无法继续执行。
第一步 — 用户提供信息
将第一步中的用户提供的阶段成果视为可度量的对象来处理效果最佳。在扩大范围之前,需记录一份理想状态下的输出、一个故障案例以及回滚说明。 应将此阶段视为输入与已验证输出之间的契约。为相关成果命名,明确成功标准,杜绝默许的半完成状态。 保持图结构简洁且类型明确。嵌套的数据块会掩盖哪个节点修改了哪个字段的信息,还会在中断后导致无法继续处理。
"My name is Amit and my favorite color is Green."
将第一步中的用户提供的阶段成果视为可度量的对象来处理效果最佳。在扩大范围之前,需记录一份理想状态下的输出、一个故障案例以及回滚说明。 应将配置信息置于应用程序代码之外。环境文件、密钥存储和功能开关应集中存放于一个位置,以便操作人员无需查看整个图结构即可进行审计。
thread_id = 1
第二步 — 用户提出另一个问题
在进入第二步“用户提问”阶段之前,需先明确输入参数、该步骤的负责人以及结束标准,然后再进行代码修改。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程和异常恢复流程。重试机制、人工审核环节以及错误处理都是产品不可或缺的部分,而非后续需要补充的功能。 对于涉及资金支出或修改生产数据的操作,必须经过人工审批。仅靠编译时的配置并不能保证业务的完整性。
"What's my favorite color?"
"Your favorite color is Green."
第三步 — 用户提出知识类问题
在进入第三步的用户请求阶段时,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。相比冗长的脚本,更应采用小型且可测试的单元。当某个步骤失败时,故障原因应能指向单一责任点,而非复杂的流程链。对于涉及资金支出或修改生产数据的操作,必须经过人工审批。编译时的逻辑连接并不等同于业务功能的完整性。
"Who is the Chief Minister of Tamil Nadu?"
web_search
第四步 — 工具执行
在为第4步工具的执行阶段修改代码之前,需先明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。 应将此阶段视为输入与已验证输出之间的契约。为相关成果命名,设定成功检测标准,并杜绝无声的半完成状态。 在网关处进行身份验证,在数据层面重新授权。仅凭承载令牌并不足以界定租户边界。 在为第4步工具的执行阶段修改代码之前,需先明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测其中的隐藏状态。 将配置信息置于应用程序代码之外。环境文件、密钥存储以及功能标志应集中存放于一个位置,以便操作人员无需查看整个系统结构即可进行审计。
第5步 — LLM生成答案
在处理第5步的LLM生成阶段时,首先需列出相关要求:所需输入、成功标志以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改保持一致性。
第6步 — LangSmith记录执行过程
16. 关于机密信息的说明
os.environ["TAVILY_API_KEY"] = "XXX"
os.environ["LANGSMITH_API_KEY"] = "YYY"
export TAVILY_API_KEY="..."
export LANGSMITH_API_KEY="..."
export LANGSMITH_TRACING="true"
export LANGSMITH_PROJECT="local-ollama-agent"
17. 为何这种架构很重要
User → LLM → Response
┌── Memory
│
User → Agent → LLM ├── Tools
│
└── State
│
▼
Tracing
18. 内存与持久化
InMemorySaver()
Application starts
↓
Thread 1 created
↓
Conversation stored in memory
↓
Application restarts
↓
Memory is gone
19. 一个简单的思维模型
1⃣ 智能体
agent = create_agent(...)
2⃣ 模型
llm = ChatOllama(...)
3⃣ 内存
checkpointer=InMemorySaver()
4⃣ 可观测性
LANGSMITH_TRACING=true
Agent
├── Model
├── Memory
├── Tools
└── Observability
结论
Ollama
+
Llama 3.2
+
LangChain Agent
+
InMemorySaver
+
Tavily
+
LangSmith