> ## Documentation Index
> Fetch the complete documentation index at: https://cndoc-langchain.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 配置线程

许多大语言模型应用都具有类似聊天机器人的界面，用户与大语言模型应用在其中进行多轮对话。为了追踪这些对话，你可以在 LangSmith 中使用[线程](/langsmith/observability-concepts#threads)。

## 将追踪分组到线程中

要将追踪关联到一个线程中，你需要传入一个特殊的 `metadata` 键，其值是该线程的唯一标识符。键名应为以下之一：

* `session_id`
* `thread_id`
* `conversation_id`

值可以是任何你想要的字符串，但我们建议使用 **UUID v7** 作为线程 ID。

[LangSmith SDK](/langsmith/reference) 导出了一个 `uuid7` 辅助函数（Python v0.4.43+，JS v0.3.80+）：

* **Python**：`from langsmith import uuid7`
* **JS/TS**：`import { uuid7 } from 'langsmith'`

有关说明，请参阅[向追踪添加元数据和标签](/langsmith/add-metadata-tags)。

<Warning>
  **重要：** 为确保过滤和令牌计数在整个线程中正常工作，你必须在**所有运行**上设置线程元数据（`session_id`、`thread_id` 或 `conversation_id`），包括追踪中的子运行。

  如果子运行没有 `thread_id` 元数据，它们将不会被包含在以下情况中：

  * 按线程过滤运行时。
  * 计算线程的令牌使用量时。
  * 聚合线程的成本时。

  创建子运行时（例如，使用 `@traceable` 进行嵌套函数或创建子 span），请确保将线程元数据传播到所有子运行。
</Warning>

### 示例

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

该示例使用 `uuid7()` 生成一个 `THREAD_ID`，并通过 `metadata` 将其传递给 `@traceable`，将该会话中的所有运行链接到 LangSmith 中的同一个线程。对话历史在轮次之间本地持久化——在生产环境中，请将基于文件的存储替换为数据库或缓存。`get_chat_history` 标志控制管道是继续现有线程还是开始一个新线程：

<CodeGroup>
  ```python Python expandable wrap theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  import os
  import json
  from dotenv import load_dotenv

  # 从 .env 文件加载环境变量
  load_dotenv()

  import openai
  from langsmith import traceable, Client, uuid7
  from langsmith.wrappers import wrap_openai

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

  # 配置
  THREAD_ID = str(uuid7())

  # 使用本地目录存储线程历史。生产环境中，请使用持久化存储解决方案。
  THREADS_DIR = os.path.join(os.path.dirname(__file__), "threads")

  # 获取线程中所有大语言模型调用的历史以构建对话历史
  def get_thread_history(thread_id: str) -> list:
      path = os.path.join(THREADS_DIR, f"{thread_id}.json")
      if not os.path.exists(path):
          return []
      with open(path, "r") as f:
          return json.load(f)

  def save_thread_history(thread_id: str, messages: list):
      os.makedirs(THREADS_DIR, exist_ok=True)
      with open(os.path.join(THREADS_DIR, f"{thread_id}.json"), "w") as f:
          json.dump(messages, f, indent=2, default=str)


  @traceable(name="Chat Bot", metadata={"thread_id": THREAD_ID})
  def chat_pipeline(messages: list, get_chat_history: bool = False):
      # 是继续现有线程还是开始新线程
      if get_chat_history:
          history_messages = get_thread_history(THREAD_ID)
          # 获取现有对话历史并追加新消息
          all_messages = history_messages + messages
      else:
          all_messages = messages

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

      response_message = chat_completion.choices[0].message
      print("模型响应:", response_message)

      full_conversation = all_messages + [{"role": response_message.role, "content": response_message.content}]
      save_thread_history(THREAD_ID, full_conversation)

      return {"messages": full_conversation}


  # 格式化消息
  messages = [
      {
          "content": "你好，我叫萨莉",
          "role": "user"
      }
  ]

  # 调用聊天管道
  result = chat_pipeline(messages, get_chat_history=False)
  ```

  ```typescript TypeScript expandable wrap theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  import * as fs from "fs";
  import * as path from "path";
  import { fileURLToPath } from "url";
  import * as dotenv from "dotenv";
  import OpenAI from "openai";
  import { traceable } from "langsmith/traceable";
  import { wrapOpenAI } from "langsmith/wrappers";
  import { uuid7 } from "langsmith";

  const __dirname = path.dirname(fileURLToPath(import.meta.url));

  // 从 .env 文件加载环境变量
  dotenv.config();

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

  // 配置
  const THREAD_ID = uuid7();

  // 使用本地目录存储线程历史。生产环境中，请使用持久化存储解决方案。
  const THREADS_DIR = path.join(__dirname, "threads");

  type Message = { role: string; content: string };

  // 获取线程中所有大语言模型调用的历史以构建对话历史
  function getThreadHistory(threadId: string): Message[] {
    const filePath = path.join(THREADS_DIR, `${threadId}.json`);
    if (!fs.existsSync(filePath)) return [];
    return JSON.parse(fs.readFileSync(filePath, "utf-8"));
  }

  function saveThreadHistory(threadId: string, messages: Message[]): void {
    fs.mkdirSync(THREADS_DIR, { recursive: true });
    fs.writeFileSync(
      path.join(THREADS_DIR, `${threadId}.json`),
      JSON.stringify(messages, null, 2)
    );
  }

  const chatPipeline = traceable(
    async function chatPipeline({ messages, get_chat_history = false }: { messages: Message[]; get_chat_history?: boolean }) {
      // 是继续现有线程还是开始新线程
      if (get_chat_history) {
        const historyMessages = getThreadHistory(THREAD_ID);
        // 获取现有对话历史并追加新消息
        messages = [...historyMessages, ...messages];
      }

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

      const responseMessage = chatCompletion.choices[0].message;
      console.log("模型响应:", responseMessage);

      const fullConversation: Message[] = [
        ...messages,
        { role: responseMessage.role, content: responseMessage.content ?? "" },
      ];
      saveThreadHistory(THREAD_ID, fullConversation);

      return { messages: fullConversation };
    },
    { name: "Chat Bot", metadata: { thread_id: THREAD_ID } }
  );

  // 格式化消息
  const messages: Message[] = [{ role: "user", content: "你好！我叫萨莉" }];

  // 调用聊天管道
  await chatPipeline({ messages, get_chat_history: false });
  ```
</CodeGroup>

进行以下调用以继续对话。通过传递 `get_chat_history=True,`/`getChatHistory: true`，你可以从上次中断的地方继续对话。这意味着大语言模型将接收整个消息历史并对其做出响应，而不仅仅是响应最新消息：

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  # 格式化消息
  messages = [
      {
          "content": "我叫什么名字",
          "role": "user"
      }
  ]

  # 调用聊天管道
  result = chat_pipeline(messages, get_chat_history=True)
  ```

  ```typescript TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  // 继续对话。
  const messages: Message[] = [{ role: "user", content: "我叫什么名字" }];

  await chatPipeline(messages, true);
  ```
</CodeGroup>

继续对话。由于包含了过去的消息，大语言模型将记住对话内容：

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  # 继续对话。
  messages = [
      {
          "content": "我发给你的第一条消息是什么？",
          "role": "user"
      }
  ]

  chat_pipeline(messages, get_chat_history=True)
  ```

  ```typescript TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  // 继续对话。
  const messages: Message[] = [{ role: "user", content: "我发给你的第一条消息是什么？" }];

  await chatPipeline(messages, true);
  ```
</CodeGroup>

## 查看线程

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

<div style={{ textAlign: 'center' }}>
  <img className="block dark:hidden" src="https://mintcdn.com/other-405835d4/77NFW0Y3VGYDwREY/langsmith/images/threads-tab-light.png?fit=max&auto=format&n=77NFW0Y3VGYDwREY&q=85&s=bb2daf43267a70fc56b55f5ce5d205db" alt="显示线程表的 LangSmith UI。" width="1277" height="762" data-path="langsmith/images/threads-tab-light.png" />

  <img className="hidden dark:block" src="https://mintcdn.com/other-405835d4/77NFW0Y3VGYDwREY/langsmith/images/threads-tab-dark.png?fit=max&auto=format&n=77NFW0Y3VGYDwREY&q=85&s=90606357744f3457cf30219d856da7f7" alt="显示线程表的 LangSmith UI。" width="1275" height="761" data-path="langsmith/images/threads-tab-dark.png" />
</div>

<Callout type="info" icon="feather">
  在线程视图中使用 **[Polly](/langsmith/polly)** 来分析对话线程、理解用户情绪、识别痛点并跟踪问题是否已解决。
</Callout>

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

<div style={{ textAlign: 'center' }}>
  <img className="block dark:hidden" src="https://mintcdn.com/other-405835d4/77NFW0Y3VGYDwREY/langsmith/images/thread-overview-light.png?fit=max&auto=format&n=77NFW0Y3VGYDwREY&q=85&s=6c35164089cf11c7bf1362cf60ac0939" alt="显示线程表的 LangSmith UI。" width="1273" height="757" data-path="langsmith/images/thread-overview-light.png" />

  <img className="hidden dark:block" src="https://mintcdn.com/other-405835d4/77NFW0Y3VGYDwREY/langsmith/images/thread-overview-dark.png?fit=max&auto=format&n=77NFW0Y3VGYDwREY&q=85&s=a4c8db52c4abf536ef13a190af1781d4" alt="显示线程表的 LangSmith UI。" width="1273" height="753" data-path="langsmith/images/thread-overview-dark.png" />
</div>

你可以通过两种不同的方式查看线程：

* [线程概览](#thread-overview)
* [追踪视图](#trace-view)

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

### 线程概览

线程概览按顺序显示对话的每一轮，显示线程中每个追踪的输入和输出。你可以配置概览中显示输入和输出的哪些字段，或通过点击 **Configure** 按钮显示多个字段。

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

### 追踪视图

线程的追踪视图类似于查看[单个运行](/langsmith/observability-concepts#runs)时的追踪视图，不同之处在于你可以访问线程中每一轮的所有运行。

### 查看反馈

查看线程时，页面顶部会有一个名为 **Feedback** 的部分。你可以在这里查看构成线程的每个运行的反馈。此反馈是聚合的，因此如果你对线程的每个运行使用相同的标准进行评估，你将看到所有显示运行的平均分数。你还可以看到此处留下的[线程级反馈](/langsmith/online-evaluations-multi-turn)。

### 保存线程级过滤器

<Note>
  线程过滤器会查看所有运行，并在至少 1 个运行匹配过滤器时显示该线程。
</Note>

与在项目级别保存过滤器类似，你也可以在线程级别保存常用过滤器。要在 **Threads** 表上保存过滤器，请使用 **Add filter** 按钮[设置过滤器](/langsmith/filter-traces-in-application#create-and-apply-filters)，然后点击 **Save view** 按钮。

你可以通过分别点击 **Annotate** 和 **Open trace** 在侧面板中打开追踪或注释追踪。

## 相关内容

* [可观测性概念](/langsmith/observability-concepts#threads)：关于线程及其与运行和追踪关系的背景信息。
* [向追踪添加元数据和标签](/langsmith/add-metadata-tags)：如何传递 `thread_id` 和其他元数据键。
* [过滤追踪](/langsmith/filter-traces-in-application)：在追踪 UI 中按线程元数据过滤。
* [设置多轮在线评估器](/langsmith/online-evaluations-multi-turn)：评估线程而非单个运行。
* [使用 SDK 记录用户反馈](/langsmith/attach-user-feedback)：将反馈附加到线程中的运行。

***

<div className="source-links">
  <Callout icon="terminal-2">
    [将这些文档连接](/use-these-docs)到 Claude、VSCode 等，通过 MCP 获取实时答案。
  </Callout>

  <Callout icon="edit">
    [在 GitHub 上编辑此页面](https://github.com/langchain-ai/docs/edit/main/src/langsmith/threads.mdx) 或 [提交问题](https://github.com/langchain-ai/docs/issues/new/choose)。
  </Callout>
</div>
