主题
追踪 LangChain 应用(Python 和 JS/TS)
LangSmith 与 LangChain(Python 和 JavaScript)无缝集成,后者是构建 LLM 应用的流行开源框架。
安装
为 Python 或 JS 安装以下内容(代码片段使用 OpenAI 集成)。
完整的可用包列表,参阅 LangChain 文档。
bash
pip install langchain_openaibash
yarn add @langchain/openai @langchain/corebash
npm install @langchain/openai @langchain/corebash
pnpm add @langchain/openai @langchain/core快速开始
1. 配置你的环境
bash
export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY=<your-api-key>
# This example uses OpenAI, but you can use any LLM provider of choice
export OPENAI_API_KEY=<your-openai-api-key>
# For LangSmith API keys linked to multiple workspaces, set the LANGSMITH_WORKSPACE_ID environment variable to specify which workspace to use.
export LANGSMITH_WORKSPACE_ID=<your-workspace-id>NOTE
如果你的账户位于美国(默认)以外的区域,还要将 LANGSMITH_ENDPOINT 设置为你的区域的 API URL。否则,你的 API 密钥将无法被识别,请求将无法通过身份验证。
| 区域 | 主机 / URL |
|---|---|
| GCP US | {`$ / https://${prefix_0 || "api.smith"}.langchain.com${suffix_0 || ""}`} |
| GCP EU | {`$ / https://eu.${prefix_0 || "api.smith"}.langchain.com${suffix_0 || ""}`} |
| GCP APAC | {`$ / https://apac.${prefix_0 || "api.smith"}.langchain.com${suffix_0 || ""}`} |
| AWS US | {`$ / https://aws.${prefix_0 || "api.smith"}.langchain.com${suffix_0 || ""}`} |
例如,欧盟账户:export LANGSMITH_ENDPOINT="https://eu.api.smith.langchain.com"。不要在 URL 末尾添加斜杠,因为这可能导致身份验证错误。
INFO
如果你在无服务器环境之外把 LangChain.js 与 LangSmith 一起用,我们还推荐显式设置以下内容以降低延迟:
export LANGCHAIN_CALLBACKS_BACKGROUND=true
如果你在无服务器环境中,我们推荐设置相反的值为 true,以允许追踪在你的函数结束之前完成:
export LANGCHAIN_CALLBACKS_BACKGROUND=false
2. 记录一条追踪
无需额外代码就能把一条追踪记录到 LangSmith。只需像平常一样运行你的 LangChain 代码。
python
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant. Please respond to the user's request only based on the given context."),
("user", "Question: {question}\nContext: {context}")
])
model = ChatOpenAI(model="gpt-5.4-mini")
output_parser = StrOutputParser()
chain = prompt | model | output_parser
question = "Can you summarize this morning's meetings?"
context = "During this morning's meeting, we solved all world conflict."
chain.invoke({"question": question, "context": context})typescript
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
const prompt = ChatPromptTemplate.fromMessages([
["system", "You are a helpful assistant. Please respond to the user's request only based on the given context."],
["user", "Question: {question}\nContext: {context}"],
]);
const model = new ChatOpenAI({ modelName: "gpt-5.4-mini" });
const outputParser = new StringOutputParser();
const chain = prompt.pipe(model).pipe(outputParser);
const question = "Can you summarize this morning's meetings?"
const context = "During this morning's meeting, we solved all world conflict."
await chain.invoke({ question: question, context: context });3. 查看你的追踪
默认情况下,该追踪会被记录到名为 default 的项目。
选择性追踪
上一节展示了如何通过设置一个环境变量,来追踪你应用中 LangChain 可运行对象的所有调用。虽然这是一种便捷的入门方式,但你可能只想追踪特定的调用或你应用的部分。
在 Python 中有两种方式做到这一点:手动把一个 LangChainTracer 实例作为回调传入,或使用 tracing_context 上下文管理器。
在 JS/TS 中,你可以把一个 LangChainTracer 实例作为回调传入。
python
# You can opt-in to specific invocations..
import langsmith as ls
with ls.tracing_context(enabled=True):
chain.invoke({"question": "Am I using a callback?", "context": "I'm using a callback"})
# This will NOT be traced (assuming LANGSMITH_TRACING is not set)
chain.invoke({"question": "Am I being traced?", "context": "I'm not being traced"})
# This would not be traced, even if LANGSMITH_TRACING=true
with ls.tracing_context(enabled=False):
chain.invoke({"question": "Am I being traced?", "context": "I'm not being traced"})typescript
// You can configure a LangChainTracer instance to trace a specific invocation.
import { LangChainTracer } from "@langchain/core/tracers/tracer_langchain";
const tracer = new LangChainTracer();
await chain.invoke(
{
question: "Am I using a callback?",
context: "I'm using a callback"
},
{ callbacks: [tracer] }
);记录到一个特定项目
静态地
正如追踪概念指南 中所述,LangSmith 用一个项目的概念来分组追踪。若未指定,追踪器项目被设为 default。你可以设置 LANGSMITH_PROJECT 环境变量,为整个应用运行配置一个自定义项目名。这应当在执行你的应用之前完成。
bash
export LANGSMITH_PROJECT=my-projectWARNING
LANGSMITH_PROJECT 标志仅在 JS SDK 0.2.16 或更高版本中受支持,如果你使用较早的版本,请改用 LANGCHAIN_PROJECT。
动态地
这在很大程度上是基于上一节构建的,允许你为一个特定的 LangChainTracer 实例设置项目名,或在 Python 中把它作为 tracing_context 上下文管理器的参数。
python
# You can set the project name using the project_name parameter.
import langsmith as ls
with ls.tracing_context(project_name="My Project", enabled=True):
chain.invoke({"question": "Am I using a context manager?", "context": "I'm using a context manager"})typescript
// You can set the project name for a specific tracer instance:
import { LangChainTracer } from "@langchain/core/tracers/tracer_langchain";
const tracer = new LangChainTracer({ projectName: "My Project" });
await chain.invoke(
{
question: "Am I using a callback?",
context: "I'm using a callback"
},
{ callbacks: [tracer] }
);为追踪添加元数据和标签
你可以通过在 RunnableConfig 中提供它们,来用任意元数据和标签为你的追踪加注。这有助于把额外信息关联到一条追踪,例如它执行所在的环境,或发起它的用户。有关如何按元数据和标签查询追踪和运行的信息,参阅查询追踪(SDK)
NOTE
当你把元数据或标签附加到一个可运行对象(无论是通过 RunnableConfig,还是在运行时用调用参数),它们会被该可运行对象的所有子可运行对象继承。
python
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful AI."),
("user", "{input}")
])
# The tag "model-tag" and metadata {"model-key": "model-value"} will be attached to the ChatOpenAI run only
chat_model = ChatOpenAI().with_config({"tags": ["model-tag"], "metadata": {"model-key": "model-value"}})
output_parser = StrOutputParser()
# Tags and metadata can be configured with RunnableConfig
chain = (prompt | chat_model | output_parser).with_config({"tags": ["config-tag"], "metadata": {"config-key": "config-value"}})
# Tags and metadata can also be passed at runtime
chain.invoke({"input": "What is the meaning of life?"}, {"tags": ["invoke-tag"], "metadata": {"invoke-key": "invoke-value"}})typescript
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
const prompt = ChatPromptTemplate.fromMessages([
["system", "You are a helpful AI."],
["user", "{input}"]
])
// The tag "model-tag" and metadata {"model-key": "model-value"} will be attached to the ChatOpenAI run only
const model = new ChatOpenAI().withConfig({ tags: ["model-tag"], metadata: { "model-key": "model-value" } });
const outputParser = new StringOutputParser();
// Tags and metadata can be configured with RunnableConfig
const chain = (prompt.pipe(model).pipe(outputParser)).withConfig({"tags": ["config-tag"], "metadata": {"config-key": "top-level-value"}});
// Tags and metadata can also be passed at runtime
await chain.invoke({input: "What is the meaning of life?"}, {tags: ["invoke-tag"], metadata: {"invoke-key": "invoke-value"}})自定义运行名
你可以通过在 Config 中提供它,在调用或流式传输你的 LangChain 代码时自定义某个运行的名字。这个名字用于在 LangSmith 中标识该运行,可用于过滤和分组运行。这个名字也被用作 LangSmith UI 中该运行的标题。可以通过在构造时于 RunnableConfig 对象中设置 run_name,或在 JS/TS 的调用参数中传入一个 run_name 来做到这一点。
python
# When tracing within LangChain, run names default to the class name of the traced object (e.g., 'ChatOpenAI').
configured_chain = chain.with_config({"run_name": "MyCustomChain"})
configured_chain.invoke({"input": "What is the meaning of life?"})
# You can also configure the run name at invocation time, like below
chain.invoke({"input": "What is the meaning of life?"}, {"run_name": "MyCustomChain"})typescript
// When tracing within LangChain, run names default to the class name of the traced object (e.g., 'ChatOpenAI').
const configuredChain = chain.withConfig({ runName: "MyCustomChain" });
await configuredChain.invoke({ input: "What is the meaning of life?" });
// You can also configure the run name at invocation time, like below
await chain.invoke({ input: "What is the meaning of life?" }, {runName: "MyCustomChain"})NOTE
run_name 参数只更改你调用的可运行对象(例如一个链、函数)的名字。它不会自动重命名你调用一个像 ChatOpenAI(gpt-5.4-mini)这样的 LLM 对象时自动创建的嵌套运行。在示例中,外层运行会在 LangSmith 中显示为 MyCustomChain,而嵌套的 LLM 运行仍显示模型的默认名。
要给 LLM 运行一个更有意义的名字,你可以:
- 把模型包裹在另一个可运行对象中,并为那一步分配一个
run_name。 - 使用一个追踪装饰器或助手(例如 Python 中的
@traceable,或 JS/TS 中来自langsmith的traceable)在模型调用周围创建一个自定义运行。
在追踪中覆盖模型名
在追踪 LangChain 模型调用时,LangSmith 会自动捕获 API 调用中所用的模型标识符。不过你可能出于组织目的想展示一个不同的、更具描述性的名字,或想区分不同的模型配置。你可以在构造或配置你的 LangChain 模型时传入 ls_model_name 元数据参数来做到这一点。
这在以下场景中尤其有用:
- 处理模型 ID 可能不具描述性的自托管或本地模型。
- 用同一模型的不同配置,并想在追踪中区分它们。
- 为模型创建别名,使追踪对你团队更易读。
- 在不同部署环境间标准化模型名。
python
from langchain_openai import ChatOpenAI
from langchain_ollama import ChatOllama
# Override model name for a local model
llm = ChatOllama(
model="llama2:13b-chat", # Actual model ID
metadata={"ls_model_name": "llama2-13b-production"} # Name shown in LangSmith
)
# Or with OpenAI to distinguish configurations
llm_creative = ChatOpenAI(
model="gpt-5.5",
temperature=0.9,
metadata={"ls_model_name": "gpt-5.4-creative"}
)
llm_factual = ChatOpenAI(
model="gpt-5.5",
temperature=0.1,
metadata={"ls_model_name": "gpt-5.4-factual"}
)
# The metadata is inherited when the model is used in a chain
result = llm.invoke("What is the meaning of life?")typescript
import { ChatOpenAI } from "@langchain/openai";
import { ChatOllama } from "@langchain/ollama";
// Override model name for a local model
const llm = new ChatOllama({
model: "llama2:13b-chat", // Actual model ID
metadata: { ls_model_name: "llama2-13b-production" } // Name shown in LangSmith
});
// Or with OpenAI to distinguish configurations
const llmCreative = new ChatOpenAI({
modelName: "gpt-5.5",
temperature: 0.9,
metadata: { ls_model_name: "gpt-5.4-creative" }
});
const llmFactual = new ChatOpenAI({
modelName: "gpt-5.5",
temperature: 0.1,
metadata: { ls_model_name: "gpt-5.4-factual" }
});
// The metadata is inherited when the model is used in a chain
const result = await llm.invoke("What is the meaning of life?");当你在模型的元数据中传入 ls_model_name 时,这个名字会出现在涉及该模型实例的所有追踪的 LangSmith UI 中。它对任何 LangChain 聊天模型或 LLM 都有效,并被所有使用该模型的运行继承,包括它作为某个链的一部分时。
NOTE
ls_model_name 元数据参数也用于成本跟踪。当与 ls_provider 参数结合时,LangSmith 能自动为自定义或自托管模型计算成本。有关所有可用元数据参数的更多信息,参阅元数据参数参考。
自定义运行 ID
你可以通过在 Config 中提供它,在调用或流式传输你的 LangChain 代码时自定义某个运行的 ID。这个 ID 用于在 LangSmith 中唯一标识该运行,可用于查询特定运行。这个 ID 对跨不同系统关联运行、或实现自定义跟踪逻辑很有用。可以通过在构造时于 RunnableConfig 对象中设置 run_id,或在调用参数中传入一个 run_id 来做到这一点。
NOTE
此功能目前不直接支持 LLM 对象。
python
import uuid
my_uuid = uuid.uuid4()
# You can configure the run ID at invocation time:
chain.invoke({"input": "What is the meaning of life?"}, {"run_id": my_uuid})typescript
const myUuid = crypto.randomUUID();
// You can configure the run ID at invocation time, like below
await chain.invoke({ input: "What is the meaning of life?" }, { runId: myUuid });注意,如果你在一条追踪的 根(即顶层运行)处这样做,那个运行 ID 会被用作 trace_id。
为 LangChain 调用访问运行(span)ID
当你调用一个 LangChain 对象时,你可以手动指定该调用的运行 ID。这个运行 ID 可用于在 LangSmith 中查询该运行。
在 JS/TS 中,你可以用一个 RunCollectorCallbackHandler 实例来访问运行 ID。
python
import uuid
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant. Please respond to the user's request only based on the given context."),
("user", "Question: {question}\n\nContext: {context}")
])
model = ChatOpenAI(model="gpt-5.4-mini")
output_parser = StrOutputParser()
chain = prompt | model | output_parser
question = "Can you summarize this morning's meetings?"
context = "During this morning's meeting, we solved all world conflict."
my_uuid = uuid.uuid4()
result = chain.invoke({"question": question, "context": context}, {"run_id": my_uuid})
print(my_uuid)typescript
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { RunCollectorCallbackHandler } from "@langchain/core/tracers/run_collector";
const prompt = ChatPromptTemplate.fromMessages([
["system", "You are a helpful assistant. Please respond to the user's request only based on the given context."],
["user", "Question: {question}\n\nContext: {context}"],
]);
const model = new ChatOpenAI({ modelName: "gpt-5.4-mini" });
const outputParser = new StringOutputParser();
const chain = prompt.pipe(model).pipe(outputParser);
const runCollector = new RunCollectorCallbackHandler();
const question = "Can you summarize this morning's meetings?"
const context = "During this morning's meeting, we solved all world conflict."
await chain.invoke(
{ question: question, context: context },
{ callbacks: [runCollector] }
);
const runId = runCollector.tracedRuns[0].id;
console.log(runId);在退出前确保所有追踪都已提交
在 LangChain Python 中,LangSmith 的追踪在一个后台线程中进行,以避免阻碍你的生产应用。这意味着你的进程可能在所有追踪成功发送到 LangSmith 之前就结束。这在无服务器环境中尤为常见,那里你的虚拟机可能在你的链或 agent 完成后立即被终止。
你可以把 LANGCHAIN_CALLBACKS_BACKGROUND 环境变量设为 "false" 来让回调变为同步。
对于两种语言,LangChain 都暴露了在退出你的应用之前等待追踪被提交的方法。下面是一个示例:
python
from langchain_openai import ChatOpenAI
from langchain_core.tracers.langchain import wait_for_all_tracers
llm = ChatOpenAI()
try:
llm.invoke("Hello, World!")
finally:
wait_for_all_tracers()typescript
import { awaitAllCallbacks } from "@langchain/core/callbacks/promises";
try {
const llm = new ChatOpenAI();
const response = await llm.invoke("Hello, World!");
} catch (e) {
// handle error
} finally {
await awaitAllCallbacks();
}不设置环境变量也能追踪
正如在其他指南中提到的,以下环境变量让你配置启用追踪、API 端点、API 密钥和追踪项目:
LANGSMITH_TRACINGLANGSMITH_API_KEYLANGSMITH_ENDPOINTLANGSMITH_PROJECT
不过,在某些环境中无法设置环境变量。在这些情况下,你可以以编程方式设置追踪配置。
这在很大程度上是基于上一节构建的。
python
import langsmith as ls
# You can create a client instance with an api key and api url
client = ls.Client(
api_key="YOUR_API_KEY", # This can be retrieved from a secrets manager
api_url="https://api.smith.langchain.com", # Self-hosted, GCP EU (`eu.api...`), GCP APAC (`apac.api...`), or AWS US (`aws.api...`) as needed
)
# You can pass the client and project_name to the tracing_context
with ls.tracing_context(client=client, project_name="test-no-env", enabled=True):
chain.invoke({"question": "Am I using a callback?", "context": "I'm using a callback"})typescript
import { LangChainTracer } from "@langchain/core/tracers/tracer_langchain";
import { Client } from "langsmith";
// You can create a client instance with an api key and api url
const client = new Client(
{
apiKey: "YOUR_API_KEY",
apiUrl: "https://api.smith.langchain.com", // Self-hosted, GCP EU (`eu.api...`), GCP APAC (`apac.api...`), or AWS US (`aws.api...`) as needed
}
);
// You can pass the client and project_name to the LangChainTracer instance
const tracer = new LangChainTracer({client, projectName: "test-no-env"});
await chain.invoke(
{
question: "Am I using a callback?",
context: "I'm using a callback",
},
{ callbacks: [tracer] }
);用 LangChain(Python)做分布式追踪
LangSmith 支持用 LangChain Python 做分布式追踪。这让你能跨不同服务和应用关联运行(span)。这些原则与 LangSmith SDK 的分布式追踪指南相似。
python
import langsmith
from langchain_core.runnables import chain
from langsmith.run_helpers import get_current_run_tree
# -- This code should be in a separate file or service --
@chain
def child_chain(inputs):
return inputs["test"] + 1
def child_wrapper(x, headers):
with langsmith.tracing_context(parent=headers):
child_chain.invoke({"test": x})
# -- This code should be in a separate file or service --
@chain
def parent_chain(inputs):
rt = get_current_run_tree()
headers = rt.to_headers()
# ... make a request to another service with the headers
# The headers should be passed to the other service, eventually to the child_wrapper function
parent_chain.invoke({"test": 1})LangChain(Python)与 LangSmith SDK 之间的互操作
如果你为应用的某些部分使用 LangChain,为其他部分使用 LangSmith SDK(参阅自定义插桩),你仍能无缝地追踪整个应用。
LangChain 对象在 traceable 函数内被调用时会被追踪,并作为该 traceable 函数的子运行被绑定。
python
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langsmith import traceable
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant. Please respond to the user's request only based on the given context."),
("user", "Question: {question}\nContext: {context}")
])
model = ChatOpenAI(model="gpt-5.4-mini")
output_parser = StrOutputParser()
chain = prompt | model | output_parser
# The above chain will be traced as a child run of the traceable function
@traceable(
tags=["openai", "chat"],
metadata={"foo": "bar"}
)
def invoke_runnnable(question, context):
result = chain.invoke({"question": question, "context": context})
return "The response is: " + result
invoke_runnnable("Can you summarize this morning's meetings?", "During this morning's meeting, we solved all world conflict.")这会产出以下追踪树: 
LangChain.JS 与 LangSmith SDK 之间的互操作
在 traceable 内追踪 LangChain 对象(仅 JS)
从 langchain@0.2.x 起,当在 @traceable 函数内使用时,LangChain 对象会被自动追踪,并继承该 traceable 函数的客户端、标签、元数据和项目名。
对于 0.2.x 以下较老版本的 LangChain,你需要手动传入一个从 @traceable 中发现的追踪上下文创建的 LangChainTracer 实例。
typescript
import { ChatOpenAI } from "@langchain/openai";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { getLangchainCallbacks } from "langsmith/langchain";
const prompt = ChatPromptTemplate.fromMessages([
[
"system",
"You are a helpful assistant. Please respond to the user's request only based on the given context.",
],
["user", "Question: {question}\nContext: {context}"],
]);
const model = new ChatOpenAI({ modelName: "gpt-5.4-mini" });
const outputParser = new StringOutputParser();
const chain = prompt.pipe(model).pipe(outputParser);
const main = traceable(
async (input: { question: string; context: string }) => {
const callbacks = await getLangchainCallbacks();
const response = await chain.invoke(input, { callbacks });
return response;
},
{ name: "main" }
);通过 traceable / RunTree API 追踪 LangChain 子运行(仅 JS)
NOTE
我们正在改进 traceable 与 LangChain 之间的互操作。当把 LangChain 与 traceable 结合使用时存在以下限制:
- 修改从 RunnableLambda 上下文的
getCurrentRunTree()获得的 RunTree 会导致一个无操作。 - 不建议经由
getCurrentRunTree()遍历从 RunnableLambda 获得的 RunTree,因为它可能不包含所有 RunTree 节点。 - 不同的子运行可能有相同的
execution_order和child_execution_order值。因此在极端情况下,某些运行最终可能处于不同的顺序,取决于start_time。
在某些使用情形中,你可能想把 traceable 函数作为 RunnableSequence 的一部分运行,或通过 RunTree API 命令式地追踪 LangChain 运行的子项。从 LangSmith 0.1.39 和 @langchain/core 0.2.18 起,你可以直接在 RunnableLambda 内调用 traceable 包裹的函数。
typescript
import { traceable } from "langsmith/traceable";
import { RunnableLambda } from "@langchain/core/runnables";
import { RunnableConfig } from "@langchain/core/runnables";
const tracedChild = traceable((input: string) => `Child Run: ${input}`, {
name: "Child Run",
});
const parrot = new RunnableLambda({
func: async (input: { text: string }, config?: RunnableConfig) => {
return await tracedChild(input.text);
},
});
或者,你可以通过使用 RunTree.fromRunnableConfig,或把 RunnableConfig 作为 traceable 包裹函数的第一个参数传入,来把 LangChain 的 RunnableConfig 转换为一个等价的 RunTree 对象。
typescript
import { traceable } from "langsmith/traceable";
import { RunnableLambda } from "@langchain/core/runnables";
import { RunnableConfig } from "@langchain/core/runnables";
const tracedChild = traceable((input: string) => `Child Run: ${input}`, {
name: "Child Run",
});
const parrot = new RunnableLambda({
func: async (input: { text: string }, config?: RunnableConfig) => {
// Pass the config to existing traceable function
await tracedChild(config, input.text);
return input.text;
},
});typescript
import { RunTree } from "langsmith/run_trees";
import { RunnableLambda } from "@langchain/core/runnables";
import { RunnableConfig } from "@langchain/core/runnables";
const parrot = new RunnableLambda({
func: async (input: { text: string }, config?: RunnableConfig) => {
// create the RunTree from the RunnableConfig of the RunnableLambda
const childRunTree = RunTree.fromRunnableConfig(config, {
name: "Child Run",
});
childRunTree.inputs = { input: input.text };
await childRunTree.postRun();
childRunTree.outputs = { output: `Child Run: ${input.text}` };
await childRunTree.patchRun();
return input.text;
},
});如果你更喜欢视频教程,可以观看 Introduction to LangSmith 课程中的其他追踪方式视频。