LangSmith 高级配置:如何利用 Threads 实现长对话的上下文维护

摘要: 在构建 Chatbot(聊天机器人)应用时,最核心的挑战之一是如何维护长时间的对话状态。单次 LLM 调用是无状态的,但如果我们将这些调用串联起来,就形成了一个“对话线程”。LangSmith 提供了强大的 Threads(线程)功能,允许开发者将离散的追踪关联起来。本文将深入讲解如何通过配置 Metadata 和 SDK 实现对话历史的自动记录与检索,从而在 LangSmith 中实现全生命周期的对话管理。

1. 前言:从“单次追踪”到“连续对话”

在基础使用中,我们关注的是单次请求的 Trace(追踪)。例如:“用户问了问题,模型回答了”。
但在真实场景中,对话是这样的:

  • User: “我叫 Bob。”
  • AI: “你好 Bob。”
  • User: “我叫什么名字?”
    如果不维护上下文,第 3 步的模型会一无所知。LangSmith 引入了 Thread(线程) 的概念,用于在平台上组织和可视化这一系列的交互。这不仅有助于调试,还能让我们复用历史数据来重建对话上下文。

2. 核心机制:定义线程标识符

LangSmith 本质上是依靠 Metadata(元数据) 来将不同的 Trace 归类到同一个 Thread 中的。

要关联追踪,你需要传入一个特殊的 Metadata 键值对:

  • 键名:必须是 “session_id”、“thread_id” 或 “conversation_id” 中的一个。
  • 键值:该对话的唯一标识符(建议使用 UUID,如 f47ac10b-…)。

3. 实战演练:构建具备记忆功能的 Chat Pipeline

下面的代码示例将展示一个完整的闭环:

  • 发起新对话(带上 Thread ID)。
  • LangSmith 记录这次交互。
  • 发起后续对话(根据 Thread ID 查找历史)。
  • 将历史消息拼接到新的请求中,实现上下文感知。
3.1 环境初始化

我们需要初始化 OpenAI 客户端(并开启 LangSmith 包装)和 LangSmith 客户端。

import openai
from langsmith import traceable, Client
import langsmith as ls
from langsmith.wrappers import wrap_openai

# 包装 OpenAI 客户端以自动追踪
client = wrap_openai(openai.Client())
# 初始化 LangSmith 客户端用于查询历史
langsmith_client = Client()

# 配置
langsmith_project = "project-with-threads"
session_id = "thread-id-1"  # 这是一个模拟的 UUID

# 定义 LangSmith 追踪所需的额外配置
langsmith_extra = {
    "project_name": langsmith_project, 
    "metadata": {"session_id": session_id} # 关键:绑定 Thread ID
}
3.2 实现历史记录检索函数

这个函数是核心。它会通过 LangSmith API 查询属于特定 thread_id 的所有历史运行记录,并将其排序,提取出完整的消息历史。

def get_thread_history(thread_id: str, project_name: str):
    # 1. 构造过滤字符串
    # 逻辑:查找 metadata_key 为 session_id/thread_id/conversation_id
    # 且 metadata_value 等于我们传入的 thread_id 的记录
    filter_string = f'and(in(metadata_key, ["session_id","conversation_id","thread_id"]), eq(metadata_value, "{thread_id}"))'
    
    # 2. 仅获取 LLM 类型的运行记录
    runs = [r for r in langsmith_client.list_runs(
        project_name=project_name, 
        filter=filter_string, 
        run_type="llm"
    )]

    # 3. 按开始时间排序(最新的在前)
    runs = sorted(runs, key=lambda run: run.start_time, reverse=True)
    
    if not runs:
        return []

    # 4. 返回最近一次交互的上下文(Inputs + Outputs)
    # 注意:这里简化处理,实际生产中可能需要拼接所有历史 runs
    latest_run = runs[0]
    # 提取输入消息和输出消息,合并为历史列表
    return latest_run.inputs['messages'] + [latest_run.outputs['choices'][0]['message']]
3.3 定义对话流水线

使用 @traceable 装饰器标记我们的主函数。该函数会判断是否需要获取历史。

@traceable(name="Chat Bot")
def chat_pipeline(question: str, get_chat_history: bool = False):
    # 获取当前运行的 Run Tree(用于访问 metadata)
    run_tree = ls.get_current_run_tree()
    
    current_messages = []
    
    if get_chat_history:
        # 如果开启了历史获取,从 LangSmith 拉取该 Thread 的过往消息
        # 注意:get_current_run_tree() 需要在 traceable 上下文中调用
        session = run_tree.extra["metadata"]["session_id"]
        history = get_thread_history(session, run_tree.session_name)
        
        # 将历史消息与新问题拼接
        current_messages = history + [{"role": "user", "content": question}]
    else:
        # 新对话,只包含当前问题
        current_messages = [{"role": "user", "content": question}]

    # 调用模型
    chat_completion = client.chat.completions.create(
        model="gpt-4o-mini", 
        messages=current_messages
    )
    
    return chat_completion.choices[0].message.content

4. 运行测试:模拟真实用户交互

我们执行三次调用,模拟一次完整的对话流程。

4.1 第一轮:建立身份
# 开启新对话,不需要历史
response_1 = chat_pipeline("Hi, my name is Bob", langsmith_extra=langsmith_extra)
print(f"Bot: {response_1}")
# 此时 LangSmith 记录下第一条 Trace,绑定 session_id="thread-id-1"
4.2 第二轮:验证记忆

建议在第一次调用几秒钟后执行,以确保 LangSmith 完成数据索引。

# 继续对话,开启历史检索
response_2 = chat_pipeline("What is my name?", get_chat_history=True, langsmith_extra=langsmith_extra)
print(f"Bot: {response_2}")
# 模型应该能检索到 Bob 的名字
4.3 第三轮:更复杂的记忆测试
# 再次继续
response_3 = chat_pipeline("What was the first message I sent you", get_chat_history=True, langsmith_extra=langsmith_extra)
print(f"Bot: {response_3}")
# 模型需要查看第一步的输入来回答

5. 查看与管理 Threads

代码跑通了,数据在哪里看?

1.进入 LangSmith 控制台。
2.选择你的项目 project-with-threads。
3.点击顶部的 “Threads”(线程) 选项卡。
在这里,你不会看到杂乱无章的单条日志,而是看到清晰的对话列表。每一个列表项代表一个 session_id 下的完整对话。点击某个 Thread,你可以像看聊天记录一样查看整个交互过程的时间轴。

6. 总结

通过在 Metadata 中指定 session_id,我们将 LangSmith 从一个单纯的“日志工具”升级为了“对话状态管理平台”。

关键点回顾:

  • 关联:使用 langsmith_extra={“metadata”: {“session_id”: “…”}} 将 Trace 关联。
  • 检索:利用 client.list_runs 配合 filter 参数查询特定 Thread 的历史。
  • 应用:将检索到的历史拼接回 Prompt,实现有记忆的对话。
    这种模式对于开发客服机器人、私人助理等需要长期记忆的应用至关重要。快去试试给你的应用加上“记忆”吧!🚀
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐