AI开发-python-langchain框架(3-18-给会话历史增加id)

LangChain ReAct智能体实战:给会话历史添加ID,实现多会话隔离

在LangChain构建智能体的过程中,多轮对话的上下文管理是提升用户体验的关键。很多开发者在实现ReAct智能体时,会遇到一个常见问题:无法区分不同用户或不同会话的历史记录,导致对话上下文混乱,智能体"记混"对话内容。
今天就结合实际开发场景,分享如何给LangChain ReAct智能体的会话历史添加唯一ID,实现会话隔离,让智能体能够精准记住每个会话的上下文,同时无需手动管理历史消息,大幅提升开发效率。

一、核心痛点:为什么需要给会话历史加ID?

默认情况下,LangChain的会话历史存储是全局的,如果多个用户同时使用智能体,或者同一用户开启多个会话,所有的对话记录会混在一起。比如用户A和用户B分别与智能体对话,智能体可能会把A的提问和B的回复混淆,导致回答逻辑错乱。
更关键的是,在实际部署场景中,我们需要对不同的会话进行区分、追溯甚至持久化存储,而唯一的会话ID就是实现这一切的基础。它就像每个会话的"身份证",让智能体能够精准识别不同的对话上下文,避免交叉干扰。

二、核心实现思路:会话ID与RunnableWithMessageHistory的结合

实现会话历史加ID的核心,在于LangChain中的RunnableWithMessageHistory组件,它能够将智能体执行器与会话历史管理绑定,通过会话ID来关联不同的对话记录,无需手动拼接历史消息,就能实现上下文的自动记忆。
整个实现逻辑可以概括为3步,清晰易懂,无需复杂的配置:

1. 初始化会话历史存储容器

首先需要创建一个会话历史的存储载体,用于保存当前会话的所有消息(用户提问和智能体回复)。这个载体可以根据实际需求选择,比如本地内存存储(适合测试)、数据库存储(适合生产环境),核心作用是临时或持久化存储对话记录。
我们可以借助LangChain提供的会话历史管理工具,快速创建一个轻量的存储容器,无需自行开发复杂的存储逻辑。

2. 绑定智能体与会话历史,关联会话ID

这是最关键的一步:通过RunnableWithMessageHistory,将我们已经创建好的ReAct智能体执行器,与会话历史存储容器进行绑定。同时,我们需要定义一个关联规则------通过会话ID来获取对应的会话历史。
这里的核心设计是:每个会话对应一个唯一的会话ID,当智能体被调用时,会通过这个ID找到对应的会话历史,将历史消息自动注入到提示词中,让智能体能够"记住"之前的对话内容。
值得注意的是,提示词模板中必须预留会话历史的占位符,这是会话历史能够被成功注入的前提,确保智能体在思考和回答时,能够获取到完整的上下文信息。

3. 调用智能体时,传入会话ID实现隔离

在测试或实际调用智能体时,我们只需要在调用参数中传入唯一的会话ID,智能体就会自动关联该ID对应的会话历史。无论同一用户多次提问,还是不同用户同时提问,只要会话ID不同,对话历史就会被分开存储和调用,实现完全隔离。
而且无需手动添加历史消息,智能体会自动将每次的用户提问和自身回复,保存到对应会话ID的历史容器中,下次调用时直接复用,极大简化了开发流程。

三、关键注意点:避免踩坑的3个细节

在实现过程中,有几个细节容易被忽略,直接影响会话ID的作用效果,这里特别提醒大家:

  • 会话ID必须唯一:无论是用户ID+会话标识,还是随机生成的唯一字符串,确保每个会话的ID不重复,否则会出现会话历史混乱的问题。
  • 提示词占位符不可缺失:必须在ReAct提示词模板中预留会话历史的占位符,否则智能体无法获取历史消息,即使绑定了会话ID也无法实现上下文记忆。
  • 会话历史的存储选择:测试环境可以使用本地内存存储,生产环境建议使用数据库 或缓存(如Redis),避免服务重启后会话历史丢失,同时支持多实例部署时的会话共享。

四、实际效果:会话隔离与自动记忆的实现

通过上述方法实现后,我们可以得到两个核心效果,完全解决多会话上下文混乱的问题:

  1. 会话隔离:不同会话ID对应的对话历史完全独立,比如会话ID为"tiger"的对话,和会话ID为"cat"的对话,智能体不会混淆两者的上下文,各自的对话能够连贯进行。
  2. 自动记忆:无需手动管理历史消息,智能体每次调用后,会自动将当前对话内容保存到对应会话ID的历史容器中,下次提问时,能够直接复用之前的对话信息,实现自然流畅的多轮对话。

五、总结与延伸

给LangChain ReAct智能体的会话历史添加ID,本质上是通过会话ID实现对话上下文的隔离与关联,核心依赖RunnableWithMessageHistory组件的封装能力,无需复杂的代码开发,就能快速实现多会话管理。
这种实现方式不仅适用于ReAct智能体,也适用于LangChain中的其他智能体类型,是多用户、多会话场景下的必备技巧。在实际生产环境中,我们还可以基于会话ID,实现会话历史的持久化、查询、清理等扩展功能,进一步提升智能体的实用性和可维护性。
如果大家在实现过程中遇到会话历史注入失败、会话ID关联异常等问题,欢迎在评论区交流探讨,一起完善LangChain智能体的开发实践~
代码实现:

复制代码
from langchain_openai import ChatOpenAI
from langchain_core.tools import Tool
from langchain.agents import create_react_agent  # 改用 ReAct 智能体
from langchain.agents import AgentExecutor
from langchain_core.prompts import PromptTemplate  # ReAct 用 PromptTemplate 而非 ChatPromptTemplate
from langchain_core.messages import AIMessage, HumanMessage
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

# 1. 初始化 LLM
DEEPSEEK_API_KEY = "123"  # 替换为实际的 API Key
llm = ChatOpenAI(
    api_key=DEEPSEEK_API_KEY,
    base_url="http://172.25.133.51:8085/v1",
    model="qwen3.5-27b-awq",
    temperature=0.3,
    max_tokens=1024,
)

# 2. 工具函数
def huawei_mall_search(query: str) -> str:
    """华为商城搜索工具"""
    print(f"[DEBUG] 工具被调用!搜索关键词:{query}")
    search_results = {
        "众测活动": "华为商城众测活动是让用户体验新品并反馈意见的活动。目前有Mate 60系列众测,参与可赢取礼品。",
        "手机": "华为商城最新手机:Mate 60系列、P60系列、nova系列等。",
        "笔记本": "华为MateBook X Pro、MateBook D系列笔记本电脑。",
        "手表": "华为Watch 4、Watch GT系列智能手表。",
        "默认": "请在华为商城官网查看详细信息或联系客服。"
    }
    for keyword in search_results:
        if keyword in query:
            return f"华为商城搜索结果:{search_results[keyword]}"
    return search_results["默认"]

# 3. 创建工具
huawei_tool = Tool(
    name="huawei_mall_search",
    description="查询华为商城相关信息,包括产品、活动、政策等",
    func=huawei_mall_search,
)
tools = [huawei_tool]

# 4. 定义 ReAct 提示词模板(关键修改!)
react_prompt = PromptTemplate.from_template("""
Answer the following questions as best you can. You have access to the following tools:

{tools}

Use the following format:

Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [{tool_names}]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question

Begin!

Previous conversation history:
{chat_history}

Question: {input}
Thought: {agent_scratchpad}
""")

#上面提示词中{chat_history}是记录历史会话记录的不能少

# 5. 创建 ReAct 智能体
try:
    agent = create_react_agent(llm=llm, tools=tools, prompt=react_prompt)
    print("Agent 创建成功")
except Exception as e:
    print(f"创建 Agent 失败: {e}")
    exit()

# 6. 创建执行器(保持不变)
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,
    max_iterations=3,
    handle_parsing_errors=True,
    return_intermediate_steps=True
)

# 7. 测试(保持不变)
print("\n" + "=" * 60)
print("测试 Agent 工具调用")
print("=" * 60)




message_history = ChatMessageHistory()

agent_with_chat_history = RunnableWithMessageHistory(
    agent_executor,
    # 这是必需的,因为在大多数现实场景中,需要会话 ID
    # 这里并没有真正使用它,因为我们使用的是一个简单 ChatMessageHistory
    lambda session_id: message_history,
    input_messages_key="input",
    history_messages_key="chat_history",
)

# 这里我们为 chat_history 传递一个空的消息列表,因为它是聊天中的第一条消息
chat1 = agent_with_chat_history.invoke(
    {"input": "你好,我是小老虎"},
    config={"configurable": {"session_id": "tiger"}},
)

print(chat1)
print('---------------')
print('char1最终回复:' + chat1['output'])

#这里不用手动写了 也能记住
#history = []+[HumanMessage(content=chat1['input']),AIMessage(content=chat1['output'])]

chat2 = agent_with_chat_history.invoke(
    {"input": "你知道我的名字吗?"},
    config={"configurable": {"session_id": "tiger"}},
)


print(chat2)
print('---------------')
print('char2最终回复:' + chat2['output'])


print("完整会话记录:")
for msg in message_history.messages:
    print(f"{msg.type}: {msg.content}")

结果输出:

Agent 创建成功

============================================================

测试 Agent 工具调用

============================================================

> Entering new AgentExecutor chain...

Parent run 88f54bd0-50e6-4018-9249-ef030d2704fc not found for run 50f303ca-5ec0-4bb7-994b-0570ef8de62f. Treating as a root run.

l.

</think>

Thought: 用户只是在打招呼自我介绍,没有提出需要查询华为商城信息的问题。这是一个简单的问候,不需要使用工具。

Final Answer: 你好,小老虎!很高兴认识你!有什么我可以帮助你的吗?比如查询华为商城的产品信息、优惠活动或者相关政策等。

> Finished chain.

{'input': '你好,我是小老虎', 'chat_history': [], 'output': '你好,小老虎!很高兴认识你!有什么我可以帮助你的吗?比如查询华为商城的产品信息、优惠活动或者相关政策等。', 'intermediate_steps': []}


char1最终回复:你好,小老虎!很高兴认识你!有什么我可以帮助你的吗?比如查询华为商城的产品信息、优惠活动或者相关政策等。

> Entering new AgentExecutor chain...

Parent run 3cb69184-c16f-4f6b-ad16-901cf2580a56 not found for run 0255b5f3-5efe-4925-af16-0f22aa2a2f51. Treating as a root run.

s.

</think>

Thought: 根据之前的对话历史,用户在第一条消息中自我介绍为"小老虎",我已经在回复中确认知道了这个名字。这个问题不需要使用华为商城搜索工具,可以直接从对话历史中回答。

Final Answer: 是的,我知道你的名字!你叫"小老虎"。很高兴认识你!有什么我可以帮助你的吗?

> Finished chain.

{'input': '你知道我的名字吗?', 'chat_history': [HumanMessage(content='你好,我是小老虎'), AIMessage(content='你好,小老虎!很高兴认识你!有什么我可以帮助你的吗?比如查询华为商城的产品信息、优惠活动或者相关政策等。')], 'output': '是的,我知道你的名字!你叫"小老虎"。很高兴认识你!有什么我可以帮助你的吗?', 'intermediate_steps': []}


char2最终回复:是的,我知道你的名字!你叫"小老虎"。很高兴认识你!有什么我可以帮助你的吗?

完整会话记录:

human: 你好,我是小老虎

ai: 你好,小老虎!很高兴认识你!有什么我可以帮助你的吗?比如查询华为商城的产品信息、优惠活动或者相关政策等。

human: 你知道我的名字吗?

ai: 是的,我知道你的名字!你叫"小老虎"。很高兴认识你!有什么我可以帮助你的吗?

更多学习资料尽在 老虎网盘资源