主题
LangSmith Engine 通知
把 LangSmith Engine 的问题和运行失败通知发送到 Slack 频道和 webhook 端点。
LangSmith Engine 能在它新开一个问题、把一条新追踪关联到现有问题,或未能完成一次运行时通知你。把这些通知投递到一个 Slack 频道、一个 HTTP webhook 端点,或两者。每个目的地都有它自己的事件类型和最低优先级,因此你可以把紧急问题路由到一个寻呼 webhook,同时把每个问题发送到一个 Slack 频道。
添加一个目的地
通知目的地按追踪项目配置。在 Engine 页上,点击 Configure Engine,然后在 Notifications 下点击 + Add destination。对每个目的地,选择:
- 投递到:Slack 或 Webhook。参阅通知一个 Slack 频道和发送到 webhook。
- 何时通知:触发通知的事件类型。
- 最低优先级:触发通知的最低问题严重度。
要在一个被关注的问题复发时收到提醒,在问题上点击 Alert me via Slack,它会打开同一个 Notifications 部分。
事件类型
| 事件 | 何时发送 |
|---|---|
issue.created | Engine 新开一个问题。 |
issue.trace.added | Engine 把一条新追踪关联到现有问题。 |
issue.agent_run.failed | 一次 Engine 运行未能完成。 |
这是 Engine 今天发送的完整事件类型集。未来可能新增类型。一个未带显式事件类型列表创建的目的地,只接收 issue.created。
严重度过滤
Minimum priority 设置被存为一个从 0 到 3 的 severity_threshold。对于问题事件,只有当问题的 severity 小于或等于该阈值时才投递通知。数字越低越紧急。
| 严重度 | 含义 |
|---|---|
0 | 紧急 |
1 | 高 |
2 | 中 |
3 | 低 |
例如,一个带 severity_threshold: 1 的目的地只为 URGENT(0)和 HIGH(1)问题接收事件。
严重度阈值不适用于 issue.agent_run.failed,因为运行失败事件的作用域是一个 Engine 会话而非某个特定问题。
通知一个 Slack 频道
Connect a Slack workspace
连接一个 Slack 工作区是一次性的组织级操作,而非按项目。连接或断开一个工作区需要 organization:manage 权限。在 LangSmith 控制台 中,打开 Settings,进入你组织的 General 设置,在 Slack 下点击 Connect Slack。在 Slack 中授权 LangSmith 应用。你可以为一个组织连接多个 Slack 工作区。
Add a Slack destination
在 Engine 页上,点击 Configure Engine,然后点击 Add destination。把 Deliver to 字段设为 Slack,然后在 Channel 下选择工作区和频道。
LangSmith 会自动加入你选择的公共频道。要向一个私有频道发消息,请先在 Slack 中把 LangSmith 应用邀请到那个频道。
每条 Slack 消息都包含问题标题、描述和严重度,一个指回 LangSmith 的 View issue 链接,以及(对问题事件)一张问题随时间复发的图表。如果一个工作区的连接变得无效,例如该应用被从 Slack 移除,它的目的地会停止投递,直到你从你组织的 General 设置重新连接它。
Slack 目的地通过 LangSmith 托管的 Slack 应用发消息,而非发送 webhook 载荷,因此签名秘密和自定义标头不适用。
发送到 webhook
把 Engine 事件转发给你自己的事件管理、寻呼或聊天工具。添加一个目的地并把 Deliver to 字段设为 Webhook。输入一个 URL,以及可选的自定义标头。每次投递都是签名的,以便你验证其真实性。
投递
LangSmith 向你的 webhook URL 发送一个带 JSON 请求体的 POST 请求。该请求使用 Content-Type: application/json,并包含你附加到该目的地的任何自定义标头。
| 属性 | 值 |
|---|---|
| 方法 | POST |
| 请求体 | JSON,下面的是通用信封 |
| 方案 | 接受 http:// 和 https://。强烈建议 https:// |
| 签名 | X-LangSmith-Signature 标头,用目的地的签名秘密签名 |
| 超时 | 每次尝试 20 秒 |
| 尝试 | 在传输错误、HTTP 408、425、429 和任何 HTTP 5xx 上最多 4 次尝试(1 次初始加 3 次指数退避重试)。其他 4xx 响应被视为永久性,不重试 |
| 响应 | 成功仅由状态码决定。响应体被忽略。 |
NOTE
重试投递一个字节完全相同的载荷,包括同样的 id。按 id 去重,使一次重试的投递不会产生重复的下游效应。
自定义标头
你可以为每个目的地附加任意标头(例如 Authorization: Bearer …),以在你的端点处对调用者进行身份验证。Content-Type 始终由 LangSmith 设置,无法被覆盖。
签名秘密
每个目的地都有一个签名秘密。LangSmith 用这个秘密签名原始 webhook 请求体,并在 X-LangSmith-Signature 标头中发送结果。
该标头值有这样的格式:
text
sha256=<hex-encoded HMAC-SHA256 digest>在解析或依据载荷采取行动之前验证签名。HMAC 输入是确切的原始请求体字节,HMAC 密钥是目的地的签名秘密。不要在验证之前解析并重新序列化 JSON 请求体。
python
import hashlib
import hmac
from typing import Optional
def verify_langsmith_signature(
*,
body: bytes,
signing_secret: str,
signature_header: Optional[str],
) -> bool:
if not signature_header or not signature_header.startswith("sha256="):
return False
expected = "sha256=" + hmac.new(
signing_secret.encode("utf-8"),
body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature_header)typescript
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyLangSmithSignature({
body,
signingSecret,
signatureHeader,
}: {
body: Buffer;
signingSecret: string;
signatureHeader: string | undefined;
}) {
if (!signatureHeader?.startsWith("sha256=")) {
return false;
}
const expected = `sha256=${createHmac("sha256", signingSecret)
.update(body)
.digest("hex")}`;
const expectedBytes = Buffer.from(expected);
const actualBytes = Buffer.from(signatureHeader);
return (
expectedBytes.length === actualBytes.length &&
timingSafeEqual(expectedBytes, actualBytes)
);
}轮换签名秘密
当签名秘密可能被泄露时,或当你组织的凭据轮换策略要求一个新秘密时,轮换它。
要轮换一个秘密,在 Engine Settings 中打开目的地行,点击 Roll signing secret 并确认。LangSmith 生成一个新的签名秘密并立即用它进行未来的 webhook 投递。前一个秘密在轮换完成的那一刻起停止为投递签名。
轮换秘密之后,用新值更新每个验证 X-LangSmith-Signature 的消费者。
测试你的端点
在把一个真实目的地指向你的端点之前,发送一个样例载荷以验证它能在 20 秒超时内接受并确认:
bash
curl -X POST https://your-endpoint.example.com/webhook \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $WEBHOOK_SECRET" \
-d @sample-issue-created.json使用来自 issue.created 的示例请求体作为 sample-issue-created.json。验证:
- 自定义的
Authorization标头到达且匹配你在目的地上配置的秘密。 - 处理器按其
id持久化该事件,以便重试被去重。 - 处理器在开始缓慢的下游工作之前返回
2xx。
安全
- webhook URL 在目的地创建时被验证,并在投递时再次验证。在 SaaS 中,私有和元数据 IP 范围被封锁。接受
http://和https://;用https://以使载荷和任何自定义标头不以明文发送。 - LangSmith 用目的地的签名秘密签名 webhook 请求体。在处理载荷之前验证
X-LangSmith-Signature。 - 你也可以在目的地上设置自定义标头,例如
Authorization: Bearer …,用于在你的端点处做路由或额外身份验证。 - 按事件
id去重,使一次重试的投递不会造成重复通知。
最佳实践
- 快速确认。 一旦你持久化了事件就用
2xx响应。把缓慢的工作(扇出、寻呼、下游 API 调用)移到队列上,使你的处理器保持在 20 秒超时内。 - 容忍未知的事件类型。 忽略你的处理器不认识的
type值。未来可能在不另行通知的情况下新增事件类型。 - 容忍新字段。 用宽松的 schema 解析载荷。可能在不另行通知的情况下向现有事件类型添加新字段。
Webhook 载荷参考
Webhook 目的地接收下面的 JSON 载荷。Slack 目的地不接收。
事件信封
每个投递到你端点的事件都用同样的外层 JSON 形状。
| 字段 | 类型 | 描述 |
|---|---|---|
id | UUID | 此次投递的唯一标识符。跨重试保持稳定。用它来去重。 |
type | string | 事件类型。issue.created、issue.trace.added 或 issue.agent_run.failed 之一。 |
created | integer | 事件被入队时的 Unix 秒(UTC)。 |
request_id | UUID | 由同一上游动作触发的每个事件共享。参阅批量合并。 |
data | object | 事件载荷。总是包含 data.object。data.trace 仅在 issue.trace.added 事件上包含。 |
问题的 data.object
对于 issue.created 和 issue.trace.added,data.object 是问题的一个快照。把它当作事件生成时问题的权威状态。
| 字段 | 类型 | 描述 |
|---|---|---|
id | UUID | 问题 ID。 |
name | string | 问题的短标题。 |
description | string | 人类可读的描述。 |
severity | integer | 0(紧急)到 3(低)。参阅严重度过滤。 |
tenant_id | UUID | 问题所属的工作区。 |
tenant_name | string | 工作区显示名。 |
session_id | UUID | 问题所属的追踪项目。 |
session_name | string | 追踪项目名。 |
url | string | 指向 LangSmith UI 中该问题的深链接。 |
运行失败的 data.object
对于 issue.agent_run.failed,data.object 描述失败的那个 Engine 运行。
| 字段 | 类型 | 描述 |
|---|---|---|
tenant_id | UUID | 运行所属的工作区。 |
tenant_name | string | 工作区显示名。 |
session_id | UUID | 运行所属的追踪项目。 |
session_name | string | 追踪项目名。 |
url | string | 指向 UI 中 LangSmith 项目的深链接。 |
thread_id | string | Engine 线程 ID。 |
run_id | string | Engine 运行 ID。不可用时省略。 |
status | string | 最终运行状态。 |
error_message | string | 来自失败运行的错误文本。不可用时省略。 |
occurred_at | string | 失败发生时的 RFC 3339 时间戳。 |
data.trace
data.trace 仅在 issue.trace.added 事件上包含。
| 字段 | 类型 | 描述 |
|---|---|---|
run_id | UUID | 被关联到该问题的运行的 ID。 |
trace_id | UUID | 包含该运行的追踪的 ID。 |
start_time | string | 运行开始时的 RFC 3339 时间戳。 |
comment | string | null | 关联该追踪时记录的可选备注。为空时省略。 |
批量合并
单个上游动作可以产生多个 webhook 事件。当 Engine 新开一个问题并附加五条追踪到它时,你会收到一个 issue.created 事件和五个 issue.trace.added 事件,全部共享同样的 request_id。用 request_id 把这些归组为一个单一的下游通知。
issue.created
当 LangSmith Engine 创建一个新问题时发送。data.trace 被省略。
json
{
"id": "b91c1f0e-7c4a-4f53-9d3e-9f1c8e7a2b10",
"type": "issue.created",
"created": 1747238400,
"request_id": "0d2f4f6a-2a3a-4b6e-9b87-5d5b6e8c9a01",
"data": {
"object": {
"id": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"name": "Tool selection inconsistency",
"description": "Agent repeatedly calls the search tool with identical arguments before terminating.",
"severity": 1,
"tenant_id": "11111111-2222-3333-4444-555555555555",
"tenant_name": "Acme Workspace",
"session_id": "66666666-7777-8888-9999-aaaaaaaaaaaa",
"session_name": "prod-api",
"url": "https://smith.langchain.com/o/11111111-2222-3333-4444-555555555555/projects/p/66666666-7777-8888-9999-aaaaaaaaaaaa?tab=5&issue=9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d"
}
}
}issue.trace.added
当一条新追踪被关联到一个现有问题时发送。data.trace 描述被关联的追踪。
json
{
"id": "c02e3a4b-5c6d-7e8f-9a0b-1c2d3e4f5a6b",
"type": "issue.trace.added",
"created": 1747238410,
"request_id": "0d2f4f6a-2a3a-4b6e-9b87-5d5b6e8c9a01",
"data": {
"object": {
"id": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"name": "Tool selection inconsistency",
"description": "Agent repeatedly calls the search tool with identical arguments before terminating.",
"severity": 1,
"tenant_id": "11111111-2222-3333-4444-555555555555",
"tenant_name": "Acme Workspace",
"session_id": "66666666-7777-8888-9999-aaaaaaaaaaaa",
"session_name": "prod-api",
"url": "https://smith.langchain.com/o/11111111-2222-3333-4444-555555555555/projects/p/66666666-7777-8888-9999-aaaaaaaaaaaa?tab=5&issue=9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d"
},
"trace": {
"run_id": "f1e2d3c4-b5a6-9788-6655-44332211ffee",
"trace_id": "abcdefab-1234-5678-9abc-def012345678",
"start_time": "2026-05-14T12:30:00Z",
"comment": "Reproduces the same tool-loop pattern."
}
}
}issue.agent_run.failed
当 LangSmith Engine 未能完成一次运行时发送。这个事件是会话作用域的,因此它不含 data.trace 也不使用严重度过滤。
json
{
"id": "4d0e8db2-81e6-4491-b8e5-b13a8f5afc0d",
"type": "issue.agent_run.failed",
"created": 1747238500,
"request_id": "f6bbd48a-0386-403d-9344-31051264b45f",
"data": {
"object": {
"tenant_id": "11111111-2222-3333-4444-555555555555",
"tenant_name": "Acme Workspace",
"session_id": "66666666-7777-8888-9999-aaaaaaaaaaaa",
"session_name": "prod-api",
"url": "https://smith.langchain.com/o/11111111-2222-3333-4444-555555555555/projects/p/66666666-7777-8888-9999-aaaaaaaaaaaa",
"thread_id": "thread-123",
"run_id": "run-456",
"status": "error",
"error_message": "RuntimeError: missing API key",
"occurred_at": "2026-05-14T12:45:00Z"
}
}
}