为你的 LLM 应用添加可观测性
可观测性对于任何软件应用都很重要,对于 LLM 应用尤其如此。LLM 本质上是不确定的,这意味着它们可能会产生意外的结果。这使得它们比普通应用更难调试。
幸运的是,LangSmith 可以在这方面提供帮助!LangSmith 具有 LLM 原生的可观测性,让您可以深入了解您的应用。
请注意,可观测性在应用开发的各个阶段都很重要 - 从原型设计、到 Beta 测试、再到生产。所有阶段都有不同的考虑因素,但它们都错综复杂地联系在一起。在本教程中,我们将逐步介绍自然进展。
假设我们正在使用 OpenAI SDK 构建一个简单的 RAG 应用。我们为其添加可观测性的简单应用如下所示
- Python
- TypeScript
from openai import OpenAI
openai_client = OpenAI()
# This is the retriever we will use in RAG
# This is mocked out, but it could be anything we want
def retriever(query: str):
results = ["Harrison worked at Kensho"]
return results
# This is the end-to-end RAG chain.
# It does a retrieval step then calls OpenAI
def rag(question):
docs = retriever(question)
system_message = """Answer the users question using only the provided information below:
{docs}""".format(docs="\n".join(docs))
return openai_client.chat.completions.create(
messages=[
{"role": "system", "content": system_message},
{"role": "user", "content": question},
],
model="gpt-4o-mini",
)
import { OpenAI } from "openai";
const openAIClient = new OpenAI();
// This is the retriever we will use in RAG
// This is mocked out, but it could be anything we want
async function retriever(query: string) {
return ["This is a document"];
}
// This is the end-to-end RAG chain.
// It does a retrieval step then calls OpenAI
async function rag(question: string) {
const docs = await retriever(question);
const systemMessage =
"Answer the users question using only the provided information below:\n\n" +
docs.join("\n");
return await openAIClient.chat.completions.create({
messages: [
{ role: "system", content: systemMessage },
{ role: "user", content: question },
],
model: "gpt-4o-mini",
});
}
原型设计
从一开始就设置可观测性可以帮助您比其他方式更快地迭代得多。它使您在快速迭代提示词或更改您正在使用的数据和模型时,能够很好地了解您的应用。在本节中,我们将逐步介绍如何设置可观测性,以便在原型设计时获得最大的可观测性。
设置您的环境
首先,导航到设置页面创建一个 API 密钥。
接下来,安装 LangSmith SDK
- Python SDK
- TypeScript SDK
pip install langsmith
npm install langsmith
最后,设置适当的环境变量。这将把追踪记录到 default
项目(尽管您可以轻松更改它)。
export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY=<your-api-key>
export LANGSMITH_PROJECT=default
您可能会在其他地方看到这些变量被引用为 LANGCHAIN_*
。这些都是等效的,但是最佳实践是使用 LANGSMITH_TRACING
、LANGSMITH_API_KEY
、LANGSMITH_PROJECT
。
LANGSMITH_PROJECT
标志仅在 JS SDK 版本 >= 0.2.16 中受支持,如果您使用的是旧版本,请改用 LANGCHAIN_PROJECT
。
追踪您的 LLM 调用
您可能想要追踪的第一件事是您的所有 OpenAI 调用。毕竟,这是实际调用 LLM 的地方,因此它是最重要的部分!我们通过引入一个非常简单的 OpenAI 包装器,尽力使这尽可能容易地使用 LangSmith 实现。您只需修改您的代码,使其看起来像这样
- Python
- TypeScript
from openai import OpenAI
from langsmith.wrappers import wrap_openai
openai_client = wrap_openai(OpenAI())
# This is the retriever we will use in RAG
# This is mocked out, but it could be anything we want
def retriever(query: str):
results = ["Harrison worked at Kensho"]
return results
# This is the end-to-end RAG chain.
# It does a retrieval step then calls OpenAI
def rag(question):
docs = retriever(question)
system_message = """Answer the users question using only the provided information below:
{docs}""".format(docs="\n".join(docs))
return openai_client.chat.completions.create(
messages=[
{"role": "system", "content": system_message},
{"role": "user", "content": question},
],
model="gpt-4o-mini",
)
import { OpenAI } from "openai";
import { wrapOpenAI } from "langsmith/wrappers";
const openAIClient = wrapOpenAI(new OpenAI());
// This is the retriever we will use in RAG
// This is mocked out, but it could be anything we want
async function retriever(query: string) {
return ["This is a document"];
}
// This is the end-to-end RAG chain.
// It does a retrieval step then calls OpenAI
async function rag(question: string) {
const docs = await retriever(question);
const systemMessage =
"Answer the users question using only the provided information below:\n\n" +
docs.join("\n");
return await openAIClient.chat.completions.create({
messages: [
{ role: "system", content: systemMessage },
{ role: "user", content: question },
],
model: "gpt-4o-mini",
});
}
请注意我们如何从 langsmith.wrappers import wrap_openai
导入并使用它来包装 OpenAI 客户端 (openai_client = wrap_openai(OpenAI())
)。
如果您以以下方式调用它会发生什么?
rag("where did harrison work")
这将生成仅 OpenAI 调用的追踪 - 它应该看起来像这样
追踪整个链
太棒了 - 我们已经追踪了 LLM 调用。但是,追踪更多内容通常会提供更多信息。LangSmith 是构建用于追踪整个 LLM 管道的 - 让我们这样做!我们可以通过修改代码使其现在看起来像这样来做到这一点
- Python
- TypeScript
from openai import OpenAI
from langsmith import traceable
from langsmith.wrappers import wrap_openai
openai_client = wrap_openai(OpenAI())
def retriever(query: str):
results = ["Harrison worked at Kensho"]
return results
@traceable
def rag(question):
docs = retriever(question)
system_message = """Answer the users question using only the provided information below:
{docs}""".format(docs="\n".join(docs))
return openai_client.chat.completions.create(
messages=[
{"role": "system", "content": system_message},
{"role": "user", "content": question},
],
model="gpt-4o-mini",
)
import { OpenAI } from "openai";
import { traceable } from "langsmith/traceable";
import { wrapOpenAI } from "langsmith/wrappers";
const openAIClient = wrapOpenAI(new OpenAI());
async function retriever(query: string) {
return ["This is a document"];
}
const rag = traceable(async function rag(question: string) {
const docs = await retriever(question);
const systemMessage =
"Answer the users question using only the provided information below:\n\n" +
docs.join("\n");
return await openAIClient.chat.completions.create({
messages: [
{ role: "system", content: systemMessage },
{ role: "user", content: question },
],
model: "gpt-4o-mini",
});
});
请注意我们如何从 langsmith import traceable
导入并使用它来装饰整个函数 (@traceable
)。
如果您以以下方式调用它会发生什么?
rag("where did harrison work")
这将生成整个管道的追踪(以 OpenAI 调用作为子运行)- 它应该看起来像这样
追踪检索步骤
我们还没有追踪应用的最后一个部分 - 检索步骤!检索是 LLM 应用的关键部分,我们已经使其易于记录检索步骤。我们所要做的就是修改我们的代码,使其看起来像
- Python
- TypeScript
from openai import OpenAI
from langsmith import traceable
from langsmith.wrappers import wrap_openai
openai_client = wrap_openai(OpenAI())
@traceable(run_type="retriever")
def retriever(query: str):
results = ["Harrison worked at Kensho"]
return results
@traceable
def rag(question):
docs = retriever(question)
system_message = """Answer the users question using only the provided information below:
{docs}""".format(docs="\n".join(docs))
return openai_client.chat.completions.create(
messages=[
{"role": "system", "content": system_message},
{"role": "user", "content": question},
],
model="gpt-4o-mini",
)
import { OpenAI } from "openai";
import { traceable } from "langsmith/traceable";
import { wrapOpenAI } from "langsmith/wrappers";
const openAIClient = wrapOpenAI(new OpenAI());
const retriever = traceable(
async function retriever(query: string) {
return ["This is a document"];
},
{ run_type: "retriever" }
)
const rag = traceable(async function rag(question: string) {
const docs = await retriever(question);
const systemMessage =
"Answer the users question using only the provided information below:\n\n" +
docs.join("\n");
return await openAIClient.chat.completions.create({
messages: [
{ role: "system", content: systemMessage },
{ role: "user", content: question },
],
model: "gpt-4o-mini",
});
});
请注意我们如何从 langsmith import traceable
导入并使用它来装饰整个函数 (@traceable(run_type="retriever")
)。
如果您以以下方式调用它会发生什么?
rag("where did harrison work")
这将生成包括检索步骤在内的整个链的追踪 - 它应该看起来像这样
Beta 测试
LLM 应用开发的下一个阶段是 Beta 测试您的应用。这是您将其发布给一些初始用户的时候。在这里设置良好的可观测性至关重要,因为通常您不知道用户将如何实际使用您的应用,因此这使您可以深入了解他们如何使用。这也意味着您可能想要对您的追踪设置进行一些更改,以便更好地实现这一点。这扩展了您在上一节中设置的可观测性
收集反馈
在 Beta 测试期间拥有良好可观测性的一个重要部分是收集反馈。您收集的反馈通常是特定于应用的 - 但至少一个简单的赞/踩是一个好的开始。记录该反馈后,您需要能够轻松地将其与导致该反馈的运行相关联。幸运的是,LangSmith 使之变得容易。
首先,您需要从您的应用记录反馈。执行此操作的一种简单方法是跟踪每个运行的运行 ID,然后使用该 ID 记录反馈。跟踪运行 ID 看起来像这样
import uuid
run_id = str(uuid.uuid4())
rag(
"where did harrison work",
langsmith_extra={"run_id": run_id}
)
将反馈与该运行关联起来看起来像这样
from langsmith import Client
ls_client = Client()
ls_client.create_feedback(
run_id,
key="user-score",
score=1.0,
)
一旦记录了反馈,您就可以通过单击检查运行时进入 Metadata
选项卡来查看它与每个运行的关联。它应该看起来像这样
您还可以通过使用运行表中的过滤逻辑来查询所有具有正面(或负面)反馈的运行。您可以通过创建如下过滤器来执行此操作
记录元数据
开始记录元数据也是一个好主意。这使您可以开始跟踪应用的各种属性。这对于让您知道使用哪个版本或变体的应用来生成给定结果非常重要。
对于此示例,我们将记录使用的 LLM。通常,您可能会尝试使用不同的 LLM,因此将该信息作为元数据对于过滤可能很有用。为了做到这一点,我们可以像这样添加它
from openai import OpenAI
from langsmith import traceable
from langsmith.wrappers import wrap_openai
openai_client = wrap_openai(OpenAI())
@traceable(run_type="retriever")
def retriever(query: str):
results = ["Harrison worked at Kensho"]
return results
@traceable(metadata={"llm": "gpt-4o-mini"})
def rag(question):
docs = retriever(question)
system_message = """Answer the users question using only the provided information below:
{docs}""".format(docs='\n'.join(docs))
return openai_client.chat.completions.create(messages = [
{"role": "system", "content": system_message},
{"role": "user", "content": question},
], model="gpt-4o-mini")
请注意,我们在 rag
函数中添加了 @traceable(metadata={"llm": "gpt-4o-mini"})
。
以这种方式跟踪元数据假定它事先已知。这对于 LLM 类型来说很好,但对于其他类型的信息(如用户 ID)来说不太理想。为了记录该信息,我们可以使用运行 ID 在运行时传入它。
import uuid
run_id = str(uuid.uuid4())
rag(
"where did harrison work",
langsmith_extra={"run_id": run_id, "metadata": {"user_id": "harrison"}}
)
现在我们已经记录了这两条元数据,我们应该能够在 UI 此处看到它们都显示出来。
我们可以通过构建如下过滤器来过滤这些信息
生产
太棒了 - 您已经使用这种新发现的可观测性来快速迭代并获得对您的应用运行良好的信心。是时候将其交付生产了!您需要添加什么新的可观测性?
首先,让我们注意,您已经添加的相同的可观测性将继续在生产中提供价值。您将能够继续深入研究特定的运行。
在生产中,您可能有更多的流量。所以您真的不想一次又一次地查看数据点。幸运的是,LangSmith 提供了一组工具来帮助生产中的可观测性。
监控
如果您单击项目中的 Monitor
选项卡,您将看到一系列监控图表。在这里,我们跟踪许多 LLM 特定统计信息 - 追踪数量、反馈、首个令牌时间等。您可以在几个不同的时间范围内查看这些信息。
A/B 测试
A/B 测试的分组功能需要至少存在 2 个不同的元数据键值。
您还可以使用此选项卡执行 A/B 测试的变体。在之前的教程中,我们开始跟踪一些不同的元数据属性 - 其中一个是 llm
。我们可以按任何元数据属性对监控图表进行分组,并立即获得随时间分组的图表。这使我们可以试验不同的 LLM(或提示词或其他),并跟踪它们随时间推移的性能。
为了做到这一点,我们只需要单击顶部的 Metadata
按钮。这将为我们提供一个下拉选项列表,供我们选择按其分组
一旦我们选择这个,我们将开始看到按此属性分组的图表
向下钻取
LangSmith 提供的令人敬畏的功能之一是能够轻松地向下钻取您在查看监控图表时识别为有问题的数据点。为了做到这一点,您只需将鼠标悬停在监控图表中的数据点上。当您这样做时,您将能够单击数据点。这将带您回到运行表,其中包含过滤后的视图
结论
在本教程中,您了解了如何使用一流的可观测性设置您的 LLM 应用。无论您的应用处于哪个阶段,您都将从可观测性中受益。
如果您对可观测性有更深入的问题,请查看操作方法部分,了解有关测试、提示词管理等主题的指南。