- 采用更高效的二进制文件传输,上传和下载速度更快。
- 在 LangSmith UI 中增强对不同文件类型的可视化支持。
- UI
- SDK
1. 创建带附件的示例
你可以通过几种不同的方式向数据集添加带附件的示例。从现有运行记录创建
将运行记录添加到 LangSmith 数据集时,可以选择性地将附件从源运行记录传播到目标示例。更多信息请参阅在应用程序中管理数据集。
从头创建
你可以直接通过 LangSmith UI 创建带附件的示例。在数据集 UI 的Examples 标签页中,点击 + Example 按钮。然后使用“Upload Files”按钮上传附件:
上传完成后,你可以在 LangSmith UI 中查看带附件的示例。每个附件都会显示预览,方便检查。
2. 创建多模态提示词
LangSmith UI 支持在评估多模态模型时,将附件添加到提示词中:首先,在你希望添加多模态内容的消息中点击文件图标。接下来,为要在每个示例中包含的附件添加模板变量。- 如果你希望包含特定附件,可以使用建议的变量名,例如
{{attachment.file_name}}。该变量会将附件列表中file_name对应的文件传递给评估器。 - 如果你希望包含所有附件,请使用
{{attachments}}变量。

3. 定义自定义评估器
你可以创建使用数据集示例中多模态内容的评估器。评估器必须使用同时支持相应输入模态和结构化输出的模型。对于音频附件,目前仅 Gemini 支持此功能。图像和 PDF 附件可与任何能够处理视觉内容并返回结构化输出的模型一起使用。
- 在数据集页面中选择 + Evaluator。
-
在 Template variables 编辑器中,为要包含的附件添加变量:
- 如果你希望包含特定附件,可以使用建议的变量名,例如
{{attachment.file_name}}。该变量会将附件列表中file_name对应的文件传递给评估器。 - 如果你希望包含所有附件,请使用
{{attachments}}变量。
- 如果你希望包含特定附件,可以使用建议的变量名,例如
- 检查图像描述是否与实际图像内容匹配。
- 验证转录内容是否准确反映音频。
- 验证从 PDF 中提取的文本是否正确。
- OCR → 文本校正:使用视觉模型从文档中提取文本,然后评估提取结果的准确性。
- 语音转文本 → 转录质量:使用语音模型将音频转录为文本,然后将转录结果与参考答案进行比较。
如果你的跟踪记录在输入或输出中包含 base64 编码的多模态内容(例如,你按照记录多模态跟踪记录指南进行了操作),则无需使用附件即可对其进行评估。在评估器提示词中使用标准变量映射(例如
{{input}} 或 {{output}}),base64 内容就会被正确传递给 LLM 评估器,以便进行可视化和评估。4. 更新带附件的示例
在 UI 中,附件大小限制为 20MB。
- 上传新附件
- 重命名和删除附件
- 使用快速重置按钮将附件恢复到之前的状态

1. 创建带附件的示例
要使用 SDK 上传带附件的示例,请使用 create_examples / update_examples Python 方法,或 uploadExamplesMultipart / updateExamplesMultipart TypeScript 方法。Python
需要langsmith>=0.3.13import requests
import uuid
from pathlib import Path
from langsmith import Client
# 公开可用的测试文件
pdf_url = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
wav_url = "https://openaiassets.blob.core.windows.net/$web/API/docs/audio/alloy.wav"
img_url = "https://www.w3.org/Graphics/PNG/nurbcup2si.png"
# 将文件获取为字节
pdf_bytes = requests.get(pdf_url).content
wav_bytes = requests.get(wav_url).content
img_bytes = requests.get(img_url).content
# 创建数据集
ls_client = Client()
dataset_name = "attachment-test-dataset"
dataset = ls_client.create_dataset(
dataset_name=dataset_name,
description="用于评估公开可用附件的测试数据集",
)
inputs = {
"audio_question": "这段音频中有什么?",
"image_question": "这张图像中有什么?",
}
outputs = {
"audio_answer": "太阳从东方升起,在西方落下。这个简单的事实已经被人类观察了数千年。",
"image_answer": "一个上面盖着毯子的马克杯。",
}
# 定义带附件的示例
example_id = uuid.uuid4()
example = {
"id": example_id,
"inputs": inputs,
"outputs": outputs,
"attachments": {
"my_pdf": {"mime_type": "application/pdf", "data": pdf_bytes},
"my_wav": {"mime_type": "audio/wav", "data": wav_bytes},
"my_img": {"mime_type": "image/png", "data": img_bytes},
# 通过本地文件路径指定附件的示例:
# "my_local_img": {"mime_type": "image/png", "data": Path(__file__).parent / "my_local_img.png"},
},
}
# 创建示例
ls_client.create_examples(
dataset_id=dataset.id,
examples=[example],
# 如果希望从本地文件上传附件,请取消此标志的注释:
# dangerously_allow_filesystem=True
)
TypeScript
需要版本 >= 0.2.13你可以使用uploadExamplesMultipart 方法上传带附件的示例。请注意,这与标准的 createExamples 方法不同,后者目前不支持附件。每个附件的数据类型必须是 Uint8Array 或 ArrayBuffer。Uint8Array:适合直接处理二进制数据。ArrayBuffer:表示固定长度的二进制数据,需要时可以将其转换为Uint8Array。
import { Client } from "langsmith";
import { v4 as uuid4 } from "uuid";
// 公开可用的测试文件
const pdfUrl = "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf";
const wavUrl = "https://openaiassets.blob.core.windows.net/$web/API/docs/audio/alloy.wav";
const pngUrl = "https://www.w3.org/Graphics/PNG/nurbcup2si.png";
// 获取文件为 ArrayBuffer 的辅助函数
async function fetchArrayBuffer(url: string): Promise<ArrayBuffer> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`获取 ${url} 失败:${response.statusText}`);
}
return response.arrayBuffer();
}
// 将文件获取为 ArrayBuffer
const pdfArrayBuffer = await fetchArrayBuffer(pdfUrl);
const wavArrayBuffer = await fetchArrayBuffer(wavUrl);
const pngArrayBuffer = await fetchArrayBuffer(pngUrl);
// 创建 LangSmith 客户端(请确保已在环境变量中设置 LANGSMITH_API_KEY)
const langsmithClient = new Client();
// 创建唯一的数据集名称
const datasetName = "attachment-test-dataset:" + uuid4().substring(0, 8);
// 创建数据集
const dataset = await langsmithClient.createDataset(datasetName, {
description: "用于评估公开可用附件的测试数据集",
});
// 定义带附件的示例
const exampleId = uuid4();
const example = {
id: exampleId,
inputs: {
audio_question: "这段音频中有什么?",
image_question: "这张图像中有什么?",
},
outputs: {
audio_answer: "太阳从东方升起,在西方落下。这个简单的事实已经被人类观察了数千年。",
image_answer: "一个上面盖着毯子的马克杯。",
},
attachments: {
my_pdf: {
mimeType: "application/pdf",
data: pdfArrayBuffer
},
my_wav: {
mimeType: "audio/wav",
data: wavArrayBuffer
},
my_img: {
mimeType: "image/png",
data: pngArrayBuffer
},
},
};
// 将带附件的示例上传到数据集
await langsmithClient.uploadExamplesMultipart(dataset.id, [example]);
除了以字节形式传入外,还可以将附件指定为本地文件路径。为此,请将路径传入附件的
data 值,并指定参数 dangerously_allow_filesystem=True:client.create_examples(..., dangerously_allow_filesystem=True)
2. 运行评估
定义目标函数
现在我们已经有了一个包含带附件示例的数据集,可以定义一个目标函数来处理这些示例。下面的示例使用 OpenAI 的 GPT-4o 模型,回答有关图像和音频片段的问题。Python
要使用与示例关联的附件,你正在评估的目标函数必须包含两个位置参数:第一个参数必须命名为inputs,第二个参数必须命名为 attachments。inputs参数是一个字典,其中包含示例的输入数据,但不包括附件。attachments参数是一个字典,将附件名称映射到一个字典。该字典包含文件的预签名 URL、mime_type 以及用于读取文件字节内容的 reader。你可以使用预签名 URL 或 reader 获取文件内容。attachments字典中的每个值都具有以下结构:
{
"presigned_url": str,
"mime_type": str,
"reader": BinaryIO
}
from langsmith.wrappers import wrap_openai
import base64
from openai import OpenAI
client = wrap_openai(OpenAI())
# 定义使用附件的目标函数
def file_qa(inputs, attachments):
# 从 reader 读取音频字节,并将其编码为 base64
audio_reader = attachments["my_wav"]["reader"]
audio_b64 = base64.b64encode(audio_reader.read()).decode('utf-8')
audio_completion = client.chat.completions.create(
model="gpt-4o-audio-preview",
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": inputs["audio_question"]
},
{
"type": "input_audio",
"input_audio": {
"data": audio_b64,
"format": "wav"
}
}
]
}
]
)
# 大多数模型除了支持 base64 编码的图像外,也支持直接接收图像 URL
# 你可以将文件的预签名 URL 直接传递给模型
image_url = attachments["my_img"]["presigned_url"]
image_completion = client.chat.completions.create(
model="gpt-5.4-mini",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": inputs["image_question"]},
{
"type": "image_url",
"image_url": {
"url": image_url,
},
},
],
}
],
)
return {
"audio_answer": audio_completion.choices[0].message.content,
"image_answer": image_completion.choices[0].message.content,
}
TypeScript
在 TypeScript SDK 中,如果将includeAttachments 设置为 true,则使用 config 参数将附件传递给目标函数。config 中会包含 attachments,它是一个对象,将附件名称映射到以下形式的对象:{
presigned_url: string,
mime_type: string,
}
import OpenAI from "openai";
import { wrapOpenAI } from "langsmith/wrappers";
const client: any = wrapOpenAI(new OpenAI());
async function fileQA(inputs: Record<string, any>, config?: Record<string, any>) {
const presignedUrl = config?.attachments?.["my_wav"]?.presigned_url;
if (!presignedUrl) {
throw new Error("未提供音频的预签名 URL。");
}
const response = await fetch(presignedUrl);
if (!response.ok) {
throw new Error(`获取音频失败:${response.statusText}`);
}
const arrayBuffer = await response.arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer);
const audioB64 = Buffer.from(uint8Array).toString("base64");
const audioCompletion = await client.chat.completions.create({
model: "gpt-4o-audio-preview",
messages: [
{
role: "user",
content: [
{ type: "text", text: inputs["audio_question"] },
{
type: "input_audio",
input_audio: {
data: audioB64,
format: "wav",
},
},
],
},
],
});
const imageUrl = config?.attachments?.["my_img"]?.presigned_url
const imageCompletion = await client.chat.completions.create({
model: "gpt-5.4-mini",
messages: [
{
role: "user",
content: [
{ type: "text", text: inputs["image_question"] },
{
type: "image_url",
image_url: {
url: imageUrl,
},
},
],
},
],
});
return {
audio_answer: audioCompletion.choices[0].message.content,
image_answer: imageCompletion.choices[0].message.content,
};
}
定义自定义评估器
你也可以在 UI 中定义多模态评估器,引用这些附件输入和输出。基于 UI 的评估器会自动应用于每个实验,包括从 SDK 调用的实验。相关说明请参阅 UI 标签页。
# 假设你已经安装了 pydantic
from pydantic import BaseModel
def valid_image_description(outputs: dict, attachments: dict) -> bool:
"""使用 LLM 判断图像描述是否与图像一致。"""
instructions = """
下面这张图像的描述是否合理?
请仔细查看图像和描述,判断该描述是否有效。
"""
class Response(BaseModel):
description_is_valid: bool
image_url = attachments["my_img"]["presigned_url"]
response = client.beta.chat.completions.parse(
model="gpt-5.5",
messages=[
{
"role": "system",
"content": instructions
},
{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": image_url}},
{"type": "text", "text": outputs["image_answer"]}
]
}
],
response_format=Response
)
return response.choices[0].message.parsed.description_is_valid
ls_client.evaluate(
file_qa,
data=dataset_name,
evaluators=[valid_image_description],
)
import { zodResponseFormat } from 'openai/helpers/zod';
import { z } from 'zod';
import { evaluate } from "langsmith/evaluation";
const DescriptionResponse = z.object({
description_is_valid: z.boolean(),
});
async function validImageDescription({
outputs,
attachments,
}: {
outputs?: any;
attachments?: any;
}): Promise<{ key: string; score: boolean}> {
const instructions = `下面这张图像的描述是否合理?
请仔细查看图像和描述,判断该描述是否有效。`;
const imageUrl = attachments?.["my_img"]?.presigned_url
const completion = await client.beta.chat.completions.parse({
model: "gpt-5.5",
messages: [
{
role: "system",
content: instructions,
},
{
role: "user",
content: [
{ type: "image_url", image_url: { url: imageUrl } },
{ type: "text", text: outputs?.image_answer },
],
},
],
response_format: zodResponseFormat(DescriptionResponse, 'imageResponse'),
});
const score: boolean = completion.choices[0]?.message?.parsed?.description_is_valid ?? false;
return { key: "valid_image_description", score };
}
const resp = await evaluate(fileQA, {
data: datasetName,
// 需要传递此标志以包含附件
includeAttachments: true,
evaluators: [validImageDescription],
client: langsmithClient
});
3. 更新带附件的示例
上面的代码展示了如何向数据集添加带附件的示例。你也可以使用 SDK 更新这些示例。与现有示例一样,使用附件更新数据集时,数据集会进行版本化。因此,你可以前往数据集版本历史,查看每个示例所发生的更改。更多信息请参阅在 UI 中创建和管理数据集。更新带附件的示例时,你可以通过几种不同的方式更新附件:- 传入新附件
- 重命名现有附件
- 删除现有附件
- 未明确重命名或保留的现有附件将被删除。
- 如果向
retain或rename传入不存在的附件名称,将会引发错误。 - 如果相同的附件名称同时出现在
attachments和attachment_operations字段中,新附件优先于现有附件。
example_update = {
"id": example_id,
"attachments": {
# 这些是新增的附件
"my_new_file": ("text/plain", b"foo bar"),
},
"inputs": inputs,
"outputs": outputs,
# 不在 rename/retain 中的附件都会被删除。
# 在本例中,如果我们上传了 "my_img",它就会被删除。
"attachments_operations": {
# 保留的附件将完全保持不变
"retain": ["my_pdf"],
# 重命名附件会保留原始数据
"rename": {
"my_wav": "my_new_wav",
}
},
}
ls_client.update_examples(dataset_id=dataset.id, updates=[example_update])
import { ExampleUpdateWithAttachments } from "langsmith/schemas";
const exampleUpdate: ExampleUpdateWithAttachments = {
id: exampleId,
attachments: {
// 这些是新增的附件
"my_new_file": {
mimeType: "text/plain",
data: Buffer.from("foo bar")
},
},
attachments_operations: {
// 保留的附件将完全保持不变
retain: ["my_img"],
// 重命名附件会保留原始数据
rename: {
"my_wav": "my_new_wav",
},
// 不在 rename/retain 中的附件都会被删除
// 在本例中,它会是 "my_pdf"
},
};
await langsmithClient.updateExamplesMultipart(dataset.id, [exampleUpdate]);
Connect these docs to Claude, VSCode, and more via MCP for real-time answers.

