( 教学 )Agent 构建 Memory(提示词对话存储)5. ConversationKGMemory(以图形式管理实体之间关系的模块, 版本>1.0和<1.0的区别)
与ConversationEntityMemory不同,后者是以键值形式为单个实体管理相关信息,而ConversationKGMemory(即“对话知识图谱内存”)则是一个以图形式管理实体之间关系的模块。
它提取并构建了知识三元组(主体-关系-客体),以识别和存储实体之间的复杂关系,并通过图结构实现对实体连接性的探索。
这有助于模型理解不同实体之间的关系,并能够基于复杂的网络和历史背景更好地回应查询。
该类,是之前的版本1.0以前的,版本1.0以后的。我会列出两个版本的使用方式和特点。
对话知识图谱记忆
ConversationKGMemory是一种存储和管理从对话中提取的信息的内存模块,其采用图结构进行存储和管理。
此示例展示了以下关键功能:
- 存储对话上下文(
save_context) - (参考)获取图中按因果依赖排序的实体名称列表。(
get_topological_sort) - 从当前对话中提取实体(
get_current_entities) - 提取知识三元组(
get_knowledge_triplets) - 读取存储的记忆(
load_memory_variables) 以下示例展示了从关于一个设计师的对话中提取实体和关系,并将其以图形格式存储的过程。
from langchain_classic import memory
from langchain_openai import ChatOpenAI
from langchain_community.memory.kg import ConversationKGMemory
from dotenv import load_dotenv
import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
load_dotenv()
# 创建ChatOpenAI对象,配置为使用硅基流动API
Qwen2_5_7B_Instruct_llm = ChatOpenAI(
temperature=0.1, # 控制输出的随机性和创造性,值越低输出越稳定可预测,值越高输出越有创意但可能偏离预期 (范围: 0.0 ~ 2.0)
model_name="Qwen/Qwen2.5-7B-Instruct", # 硅基流动支持的模型名称
openai_api_key=os.getenv("SILICONFLOW_API_KEY"), # 从环境变量获取API密钥
openai_api_base="https://api.siliconflow.cn/v1" # 硅基流动API的基础URL
)
memory = ConversationKGMemory(llm=Qwen2_5_7B_Instruct_llm, return_messages=True)
memory.save_context(
{"input": "这是住在板桥的Shelly Kim。"},
{"output": "你好Shelly,很高兴认识你!你是做什么工作的?"},
)
memory.save_context(
{"input": "Shelly Kim是我们公司的新设计师。"},
{
"output": "太好了!欢迎加入我们的团队。希望你能在这里工作得开心。"
},
)
(参考) get_topological_sort() → List[str]
你可以使用 get_topological_sort 方法来查看知识图谱中按照拓扑顺序存储的所有实体:
该方法:
- 使用
NetworkX库分析知识图谱结构 - 基于有向边执行拓扑排序
- 返回按依赖顺序排列的实体列表
排序结果反映了会话中实体之间的关系,展示了它们在知识图谱中是如何连接的。
memory.kg.get_topological_sort()
打印结果
['N shelley Kim', 'Shelly Kim', 'None', '板桥', 'NONE']
get_current_entities(input_string: str) → List[str]
以下是 get_current_entities 方法的工作原理:
1. 实体抽取链的创建
- 使用
entity_extraction_prompt模板创建一个LLMChain。 - 该提示模板用于从对话的最后一行中提取专有名词。
2. 上下文处理
- 从缓冲区获取最后 k*2 条消息。(默认值:k=2)
- 使用
human_prefix和ai_prefix生成对话历史字符串。
3. 实体抽取
- 从输入字符串 "Who is Shelly Kim?" 中提取专有名词
- 主要识别以大写字母开头的词作为专有名词
- 在本例中,"Shelly Kim" 被提取为一个实体
该方法仅从问题本身提取实体,而之前的对话上下文仅用作参考。
memory.get_current_entities({"input": "谁是Shelly Kim?"})
打印结果
['Shelly Kim']
获取知识三元组(输入字符串:字符串类型) → 列表[知识三元组]
“get_knowledge_triplets” 方法的运作方式如下:
1. 知识三元组提取链
- 使用“知识三元组提取提示”模板创建一个“语言模型链”。
- 旨在从给定文本中提取以(主语-关系-宾语)格式呈现的三元组。
2.记忆搜索
- 从之前存储的对话中查找与“雪莉”相关的信息。
- 存储的背景信息:
- “这是住在平友的雪莉·金。”
- “雪莉·金是我们公司的新设计师。”
3.三重提取
- 根据检索到的信息生成以下三元组:
- (希利·金,居住于,潘乔)
- (希利·金,是,设计师)
- (希利·金,就职于,我们公司) 这种方法会从所有与特定实体相关的已存储对话内容中提取以“三元组”形式呈现的关系信息。
memory.get_knowledge_triplets({"input": "Shelly"}), "\n", memory.get_knowledge_triplets(
{"input": "Pangyo"}
), "\n", memory.get_knowledge_triplets(
{"input": "designer"}
), "\n", memory.get_knowledge_triplets(
{"input": "Langchain"}
)
加载内存变量(输入参数:字典类型,键为字符串,值为任意类型) → 字典类型(键为字符串,值为任意类型)
“load_memory_variables” 方法的执行步骤如下:
1. 实体提取
- 从输入“谁是谢莉·金?”中提取出实体(例如“谢莉·金”)
- 内部使用“get_current_entities”方法。
2.知识检索
- 会搜索与所提取的实体相关的所有知识三元组。
- 通过“保存上下文”方法存储的信息将从图中查询获取。
3.信息格式化
- 将找到的三元组转换为系统消息。
- 若设置了“return_messages=True”,则会返回一系列消息对象。
这种方法会从存储的知识图谱中检索相关信息,并以结构化格式将其返回,随后这些信息可作为与语言模型进行后续对话时的背景信息。
memory.load_memory_variables({"input": "谁是Shelly Kim?"})
打印结果
{'history': [SystemMessage(content='On Shelly Kim: Shelly Kim lives in 板桥.', additional_kwargs={}, response_metadata={})]}
使用 LCEL 应用知识图谱记忆
让我们通过 LCEL 使用自定义的 ConversationChain 和 ConversationKGMemory 来检查对话后的记忆内容
from operator import itemgetter
from langchain_community.memory.kg import ConversationKGMemory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
import os
load_dotenv()
Qwen2_5_7B_Instruct_llm = ChatOpenAI(
temperature=0.1, # 控制输出的随机性和创造性,值越低输出越稳定可预测,值越高输出越有创意但可能偏离预期 (范围: 0.0 ~ 2.0)
model_name="Qwen/Qwen2.5-7B-Instruct", # 硅基流动支持的模型名称
openai_api_key=os.getenv("SILICONFLOW_API_KEY"), # 从环境变量获取API密钥
openai_api_base="https://api.siliconflow.cn/v1" # 硅基流动API的基础URL
)
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"""这是一个人类和AI之间的友好对话。
AI会健谈并从上下文中提供大量具体细节。
如果AI不知道问题的答案,它会诚实地说不知道。
AI只使用"相关信息"部分中包含的信息,不会产生虚构的内容。
Relevant Information:{history}""",
),
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
]
)
memory = ConversationKGMemory(llm=Qwen2_5_7B_Instruct_llm, return_messages=True, memory_key="history")
class ConversationChain:
def __init__(self, llm, prompt, memory):
self.memory = memory
self.chain =(
RunnablePassthrough()|
RunnablePassthrough.assign(
history=RunnableLambda(self.memory.load_memory_variables)
| itemgetter("history"))
| prompt
| llm
)
def invoke(self,input_dict):
response = self.chain.invoke(input_dict)
self.memory.save_context(input_dict, {"output": response.content})
return response
conversation_with_kg = ConversationChain(
llm=Qwen2_5_7B_Instruct_llm,
prompt=prompt,
memory=memory
)
from langchain_core.load.load import loads
# 将类JSON字符串反序列化为LangChain对象
secrets_map = {"OPENAI_API_KEY": os.getenv("SILICONFLOW_API_KEY")}
deserialized_llm = loads(serialized_llm,secrets_map=secrets_map)
print(deserialized_llm)
print(type(deserialized_llm))
deserialized_prompt = loads(serialized_prompt)
print(type(deserialized_prompt))
deserialized_chain = loads(serialized_chain,secrets_map=secrets_map)
print(type(deserialized_chain))
# 用一些基本信息来初始化对话。
response = conversation_with_kg.invoke(
{
"input": "My name is Teddy. Shelly is a coworker of mine, and she's a new designer at our company."
}
)
# 查询记忆中关于 Shelly 的信息。
conversation_with_kg.memory.load_memory_variables({"input": "who is Shelly?"})
# 可以通过 ```memory.clear()``` 来重置内存。
conversation_with_kg.memory.clear()
conversation_with_kg.memory.load_memory_variables({"input": "谁是 Shelly?"})
使用最新版本逻辑实现
状态定义
我们定义了一个自定义状态 KGState,继承自 MessagesState,并添加了 kg_triples 和 kg_summary 字段。
kg_triples:用于存储从对话中提取的知识三元组列表。kg_summary:用于存储对知识图谱的总结。
from typing import Dict, TypedDict
from langgraph.graph import MessagesState, StateGraph
from langgraph.checkpoint.memory import MemorySaver
from langchain_openai import ChatOpenAI
from neo4j import GraphDatabase
from langchain_core.messages import HumanMessage
class KGState(MessagesState):
kg_triples: list = [] # [(subject, relation, object)]
kg_summary: str = "" # 图谱总结
# Neo4j 连接(可选)
driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "password"))
def extract_triples(state: KGState):
"""提取三元组节点"""
model = ChatOpenAI(model="gpt-4o-mini")
prompt = f"""
从对话中提取知识三元组 (主体, 关系, 客体),JSON 格式:
[
["Bob", "有弟弟", "Tom"],
["Bob", "住在", "上海"]
]
对话:{state["messages"][-1].content}
"""
response = model.invoke([("user", prompt)])
# 解析三元组(生产用 JSON)
triples = eval(response.content)
state["kg_triples"].extend(triples)
return state
def chat_with_kg(state: KGState):
"""知识图谱增强聊天"""
model = ChatOpenAI(model="gpt-4o-mini")
# 构建 KG 上下文
kg_context = "\n".join([f"{s} {r} {o}" for s, r, o in state["kg_triples"][-10:]])
prompt = f"""
已知知识图谱:
{kg_context}
对话历史:{state["messages"]}
使用知识图谱回答:
"""
response = model.invoke([("user", prompt)])
return {"messages": [response]}
# 构建图
checkpointer = MemorySaver()
builder = StateGraph(state_schema=KGState)
builder.add_node("extract_triples", extract_triples)
builder.add_node("chat", chat_with_kg)
builder.add_edge("extract_triples", "chat")
graph = builder.compile(checkpointer=checkpointer)
# 测试
config = {"configurable": {"thread_id": "user-1"}}
result = graph.invoke(
{"messages": [HumanMessage(content="我叫Bob,有弟弟Tom,住在上海")]},
config
)
print(result["messages"][-1].content)
print("知识图谱:", result["kg_triples"])