Skip to content

许多 LLM 应用都具备类似聊天机器人的界面,用户与 LLM 应用之间会进行多轮对话。为了追踪这些对话,您可以使用 LangSmith 中的 Threads(线程)功能。

将追踪记录分组到线程中

一个 Thread(线程)是代表单次对话的一系列追踪记录。每个响应都表示为它自己的追踪记录,但这些追踪记录通过属于同一个线程而链接在一起。

要将追踪记录关联在一起,您需要传入一个特殊的 metadata 键,其值是该线程的唯一标识符。键名应为以下之一:

  • session_id
  • thread_id
  • conversation_id

值可以是您想要的任何字符串,但我们建议使用 UUID,例如 f47ac10b-58cc-4372-a567-0e02b2c3d479。请查看本指南了解如何向追踪记录添加元数据。

示例

此示例演示了如何使用结构化的消息格式来记录和检索对话历史,以维持长时间的聊天会话。

python
import os
from typing import List, Dict, Any, Optional

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

# 初始化客户端
client = wrap_openai(openai.Client())
langsmith_client = Client()

# 配置
LANGSMITH_PROJECT = "project-with-threads"
THREAD_ID = "thread-id-1"
langsmith_extra={"project_name": LANGSMITH_PROJECT, "metadata":{"session_id": THREAD_ID}}

# 获取线程中所有 LLM 调用的历史记录以构建对话历史
def get_thread_history(thread_id: str, project_name: str):
    # 按特定线程和项目筛选运行记录
    filter_string = f'and(in(metadata_key, ["session_id","conversation_id","thread_id"]), eq(metadata_value, "{thread_id}"))'
    # 仅获取 LLM 运行记录
    runs = [r for r in langsmith_client.list_runs(project_name=project_name, filter=filter_string, run_type="llm")]

    # 按开始时间排序以获取最近的交互
    runs = sorted(runs, key=lambda run: run.start_time, reverse=True)

    # 重建对话状态
    latest_run = runs[0]
    return latest_run.inputs['messages'] + [latest_run.outputs['choices'][0]['message']]

@traceable(name="Chat Bot")
def chat_pipeline(messages: list, get_chat_history: bool = False):
    # 是继续现有线程还是开始新线程
    if get_chat_history:
        run_tree = ls.get_current_run_tree()
        # 获取现有对话历史并追加新消息
        history_messages = get_thread_history(run_tree.extra["metadata"]["session_id"], run_tree.session_name)
        all_messages = history_messages + messages
        # 为了追踪,在输入中包含完整的对话
        input_messages = all_messages
    else:
        all_messages = messages
        input_messages = messages

    # 调用模型
    chat_completion = client.chat.completions.create(
        model="gpt-4o-mini", messages=all_messages
    )

    # 返回包含输入和响应的完整对话
    response_message = chat_completion.choices[0].message
    return {
        "messages": input_messages + [response_message]
    }

# 格式化消息
messages = [
    {
        "content": "Hi, my name is Sally",
        "role": "user"
    }
]
get_chat_history = False

# 调用聊天管道
result = chat_pipeline(messages, get_chat_history, langsmith_extra=langsmith_extra)
typescript
import 'dotenv/config';
import OpenAI from 'openai';
import { traceable, getCurrentRunTree } from 'langsmith/traceable';
import { Client } from 'langsmith';
import { wrapOpenAI } from 'langsmith/wrappers';

// 初始化客户端
const openai = new OpenAI();
const client = wrapOpenAI(openai);
const langsmithClient = new Client();

// 配置
const LANGSMITH_PROJECT = "project-with-threads";
const THREAD_ID = "thread-id-1";
const langsmithExtra = {
  project_name: LANGSMITH_PROJECT,
  metadata: { session_id: THREAD_ID }
};

// 消息类型定义
interface Message {
  role: 'user' | 'assistant' | 'system';
  content: string;
}

interface ChatResponse {
  messages: Message[];
}

interface ChatInput {
  get_chat_history: boolean;
  messages: Message[];
}

// 获取线程中所有 LLM 调用的历史记录以构建对话历史
async function getThreadHistory(threadId: string, projectName: string): Promise<Message[]> {
  // 按特定线程和项目筛选运行记录
  const filterString = `and(in(metadata_key, ["session_id","conversation_id","thread_id"]), eq(metadata_value, "${threadId}"))`;

  // 仅获取 LLM 运行记录
  const runs: any[] = [];
  for await (const run of langsmithClient.listRuns({
    projectName: projectName,
    filter: filterString,
    runType: "llm"
  })) {
    if (run.run_type === "llm") {
      runs.push(run);
    }
  }

  // 按开始时间排序以获取最近的交互
  runs.sort((a: any, b: any) => new Date(b.start_time).getTime() - new Date(a.start_time).getTime());

  // 检查是否有任何运行记录
  if (runs.length === 0) {
    return [];
  }

  // 对话的当前状态
  const latestRun = runs[0];
  const inputMessages = latestRun.inputs.messages as Message[];
  const outputMessage = latestRun.outputs.choices[0].message as Message;

  return [...inputMessages, outputMessage];
}

// 更新后的聊天管道,接受 JSON 输入格式
const chatPipeline = traceable(async (input: ChatInput): Promise<ChatResponse> => {
  const { messages, get_chat_history } = input;
  let allMessages: Message[];
  let inputMessages: Message[];

  // 是继续现有线程还是开始新线程
  if (get_chat_history) {
    const runTree = getCurrentRunTree();
    // 获取现有对话历史并追加新消息
    const sessionId = runTree.extra?.metadata?.session_id || THREAD_ID;
    const historyMessages = await getThreadHistory(
      sessionId,
      runTree.project_name
    );
    allMessages = historyMessages.concat(messages);
    // 为了追踪,在输入中包含完整的对话
    inputMessages = allMessages;
  } else {
    allMessages = messages;
    inputMessages = messages;
  }

  // 调用模型
  const chatCompletion = await client.chat.completions.create({
    model: "gpt-4o-mini",
    messages: allMessages
  });

  // 返回包含输入和响应的完整对话
  const responseMessage = chatCompletion.choices[0].message as Message;
  return {
    messages: [...inputMessages, responseMessage]
  };
}, { name: "Chat Bot" });

// 请求的 JSON 格式的示例输入
const input: ChatInput = {
  get_chat_history: false,
  messages: [
    {
      content: "Hi, my name is Sally",
      role: "user"
    }
  ]
};

// 调用聊天管道
const result = await chatPipeline(input);

等待几秒钟后,您可以进行以下调用来继续对话。通过传递 get_chat_history=True,/getChatHistory: true,您可以从上次中断的地方继续对话。这意味着 LLM 将接收整个消息历史记录并对其进行响应,而不仅仅是响应最新的消息。

python
# 继续对话。
messages = [
    {
        "content": "What is my name?",
        "role": "user"
    }
]
get_chat_history = True

chat_pipeline(messages, get_chat_history, langsmith_extra=langsmith_extra)
typescript
// 继续对话。
const input: ChatInput = {
  get_chat_history: true,
  messages: [
    {
      content: "What is my name?",
      role: "user"
    }
  ]
};

await chatPipeline(input);

继续对话。由于包含了过去的消息,LLM 将记住对话内容。

python
# 继续对话。
messages = [
    {
        "content": "What was the first message I sent you?",
        "role": "user"
    }
]
get_chat_history = True

chat_pipeline(messages, get_chat_history, langsmith_extra=langsmith_extra)
typescript
// 继续对话。
const input: ChatInput = {
  get_chat_history: true,
  messages: [
    {
      content: "What was the first message I sent you?",
      role: "user"
    }
  ]
};

await chatPipeline(input);

查看线程

您可以在任何项目详情页面中点击 Threads 选项卡来查看线程。然后您将看到所有线程的列表,按最近的活动排序。

LangSmith UI showing the threads table.LangSmith UI showing the threads table.

在线程视图中使用 Polly 来分析对话线程、理解用户情绪、识别痛点并跟踪问题是否得到解决。

查看线程

然后您可以点击进入特定的线程。这将打开特定线程的历史记录。

LangSmith UI showing the threads table.LangSmith UI showing the threads table.

线程可以以两种不同的方式查看:

您可以使用页面顶部的按钮在两种视图之间切换,或使用键盘快捷键 T 在两种视图之间切换。

线程概览

线程概览页面向您展示了一个类似聊天机器人的用户界面,您可以在其中看到对话每一轮的输入和输出。您可以配置在概览中显示输入和输出的哪些字段,或者通过点击 Configure 按钮显示多个字段。

输入和输出的 JSON 路径支持负索引,因此您可以使用 -1 来访问数组的最后一个元素。例如,inputs.messages[-1].content 将访问 messages 数组中的最后一条消息。

追踪视图

这里的追踪视图与查看单个运行时的追踪视图类似,不同之处在于您可以轻松访问线程中每一轮的所有运行记录。

查看反馈

查看线程时,在页面顶部您会看到一个名为 Feedback 的部分。在这里,您可以查看构成线程的每个运行的反馈。此反馈是聚合的,因此如果您为线程的每个运行评估相同的标准,您将看到所有运行的平均分数。您还可以在此处看到留下的线程级反馈

保存线程级过滤器

与在项目级别保存过滤器类似,您也可以在线程级别保存常用的过滤器。要在线程表上保存过滤器,请使用过滤器按钮设置过滤器,然后点击 Save filter 按钮。

您可以通过分别点击 AnnotateOpen trace,在侧面板中打开追踪记录或对其进行注释。

LangChain 中文文档