Agentforce Data Library 管理与 OTel 会话追踪指南

ADL API + OTel API 完整指南:SFDRIVE 文件库 9 步完整脚本(Create→Upload Readiness→Presigned URL→S3 Upload→Index→Poll→Append Files)、KNOWLEDGE 知识库(含 Data Categories 配置+五种管理操作+五阶段索引状态详解)、OTel API(OTLP v1.0 格式导出完整会话追踪+OTLP Collector 配置示例+FAQ)。...

📅 2026/7/22 ✍️ ponybai 🏷️ agentforce, salesforce, api

ADL API 概述

s209

Agentforce Data Libraries (ADL) 通过连接可信数据源增强 AI Agent 的准确性。Data Library 利用非结构化或半结构化数据,将 Web、文档或字段中的大量文本信息转化为更有用、可搜索的知识。使用 ADL Connect API 资源编程式创建和管理 Data Library,确保 Agent 始终能访问最新、最相关的信息。

两种源类型:SFDRIVE(文件库——上传 PDF/DOCX 等文件到 Salesforce 管理的 S3 桶)和 KNOWLEDGE(知识库——索引 Knowledge Article 的指定字段)。

参见 Connect REST API: ADL Resource Reference 和 Help: Agentforce Data Library

前提条件与完整设置

s210

必备工具

  • Salesforce CLI:安装 CLI,用于认证和获取 Org 信息
  • jq:JSON 解析工具。检查 jq --version,未安装则 brew install jq
  • PDF 测试文件:任意 PDF 文件即可(内容不重要)
  • Test Org:已配置 Data Library 的 Org。参见 Setting Up Data Libraries

创建 External Client App(ECA)

  1. Setup → External Client Apps Manager → New
  2. 命名 + 邮箱
  3. OAuth Scopes:chatter_api / api / web / refresh_token, offline_access
  4. OAuth 设置:Enable Client Credentials Flow + Issue JWT Web Token
  5. Policies → Enable Client Credentials Flow → Run As 设为 API Only 用户 → Issue JWT tokens(默认 30min 过期)

获取 Access Token(完整脚本)

# 设置环境变量
export SF_CLIENT_ID={CONSUMER_KEY}
export SF_CLIENT_SECRET={CONSUMER_SECRET}
export SF_INSTANCE_URL=https://{MY_DOMAIN}.my.salesforce.com

# 获取 Token
RESPONSE=$(curl -s -X POST \
  "$SF_INSTANCE_URL/services/oauth2/token" \
  -d "grant_type=client_credentials" \
  -d "client_id=$SF_CLIENT_ID" \
  -d "client_secret=$SF_CLIENT_SECRET")

ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.access_token')
ORG_URL=$(echo $RESPONSE | jq -r '.instance_url')

# 验证 ADL API 可用
curl -s "$ORG_URL/services/data/v66.0/einstein/data-libraries" \
  -H "Authorization: Bearer $ACCESS_TOKEN" | jq '.'
# 收到 JSON 响应(即使是空数组)表示 OAuth 工作正常

设置 Shell 变量

# 生成唯一 Library 名称
ADL_PREFIX="your_prefix"
ADL_SUFFIX=$(date +%m%d)_$(LC_ALL=C tr -dc 'a-z0-9' < /dev/urandom | head -c3)
ADL_DevName="${ADL_PREFIX}_${ADL_SUFFIX}"
ADL_Name="${ADL_PREFIX} ${ADL_SUFFIX}"

# 指向测试文件(修改此路径)
FILE_NAME="/Users/yourname/Downloads/file1.pdf"

以上命名约定仅用于快速入门,生产环境可使用任何命名方案。

文件库(SFDRIVE)完整示例

s211

本示例演示完整的文件库生命周期:创建→上传就绪→获取 Presigned URL→S3 上传→索引→轮询→追加文件

Step 1-3:创建、等待就绪与获取上传 URL

s212
# Step 1: 创建 SFDRIVE Library
RESPONSE_ADL=$(curl -s -X POST "$ORG_URL/services/data/v66.0/einstein/data-libraries" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"masterLabel":"'"${ADL_Name}"'","developerName":"'"${ADL_DevName}"'","groundingSource":{"sourceType":"SFDRIVE"}}')
echo "$RESPONSE_ADL" | jq '.'
LIBRARY_ID=$(echo "$RESPONSE_ADL" | jq -r '.libraryId')
# 预期响应: JSON 含 libraryId 和 groundingSource 字段

# Step 2: 等待上传就绪(Data 360 资源预配,最多 2 分钟)
echo "Checking upload readiness..."
READINESS=$(curl -s --max-time 130 \
  "$ORG_URL/services/data/v66.0/einstein/data-libraries/$LIBRARY_ID/upload-readiness?waitMaxTime=120000" \
  -H "Authorization: Bearer $ACCESS_TOKEN")
echo "$READINESS" | jq '.'
# 预期: {"ready": true}

# Step 3: 获取 Presigned Upload URLs(最多 1000 个文件)
FILE_BASENAME=$(basename "$FILE_NAME")
RESPONSE=$(curl -s -X POST \
  "$ORG_URL/services/data/v66.0/einstein/data-libraries/$LIBRARY_ID/file-upload-urls" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"files":[{"fileName":"'"${FILE_BASENAME}"'"}]}')
echo "$RESPONSE" | jq '.'
PRESIGNED_URL=$(echo "$RESPONSE" | jq -r '.uploadUrls[0].uploadUrl')
FILE_PATH_S3=$(echo "$RESPONSE" | jq -r '.uploadUrls[0].filePath')
注意:Step 2 需要等待 Data 360 资源预配。如果在预配完成前调用 file-upload-urls(Step 3),会返回 HTTP 400。最多上传 1000 个文件。

Step 4-6:上传、索引与轮询

s213
# Step 4: 上传文件到 S3(解析 Presigned URL 的 Headers)
UPLOAD_HEADERS=()
while IFS='=' read -r key value; do
  UPLOAD_HEADERS+=(-H "$key: $value")
done < <(echo "$RESPONSE" | jq -r '.uploadUrls[0].headers | to_entries[] | "\(.key)=\(.value)"')

curl -X PUT "${PRESIGNED_URL}" \
    "${UPLOAD_HEADERS[@]}" \
    --data-binary @"${FILE_NAME}" \
    -w "\nHTTP Status: %{http_code}\n"
# 预期: HTTP Status: 200

# Step 5: 触发索引
FILE_SIZE=$(stat -f%z "$FILE_NAME" 2>/dev/null || stat -c%s "$FILE_NAME")
curl -s -X POST "$ORG_URL/services/data/v66.0/einstein/data-libraries/$LIBRARY_ID/indexing" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"uploadedFiles":[{"filePath":"'"${FILE_PATH_S3}"'","fileSize":'"$FILE_SIZE"'}]}' | jq '.'
# 预期: {"status": "IN_PROGRESS"}

# Step 6: 轮询直到 READY(每 10 秒一次)
while true; do
  STATUS_RESPONSE=$(curl -s \
    "$ORG_URL/services/data/v66.0/einstein/data-libraries/$LIBRARY_ID/status" \
    -H "Authorization: Bearer $ACCESS_TOKEN")
  STATUS=$(echo "$STATUS_RESPONSE" | jq -r '.indexingStatus.status')
  ALL_STAGES_SUCCESS=$(echo "$STATUS_RESPONSE" | jq -r '[.indexingStatus.stages[]?.status] | all(. == "SUCCESS") and length > 0')
  echo "Status: $STATUS | All stages SUCCESS: $ALL_STAGES_SUCCESS"
  if [ "$STATUS" = "READY" ] || [ "$STATUS" = "FAILED" ] || [ "$ALL_STAGES_SUCCESS" = "true" ]; then
    echo "$STATUS_RESPONSE" | jq '.'
    break
  fi
  sleep 10
done

状态说明:READY 需要 SearchIndex 的分块/嵌入处理完成。ALL_STAGES_SUCCESS=true 表示 ADL 管道完成(即使 SearchIndex 处理仍在进行中)。通常几分钟后转换到 READY。

Step 7-9:追加更多文件

s214
# Step 7: 获取新文件上传 URL
NEW_FILE="/Users/yourname/Downloads/file2.pdf"
NEW_FILE_BASENAME=$(basename "$NEW_FILE")
RESPONSE=$(curl -s -X POST \
  "$ORG_URL/services/data/v66.0/einstein/data-libraries/$LIBRARY_ID/file-upload-urls" \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"files":[{"fileName":"'"${NEW_FILE_BASENAME}"'"}]}')
PRESIGNED_URL=$(echo "$RESPONSE" | jq -r '.uploadUrls[0].uploadUrl')
FILE_PATH_S3=$(echo "$RESPONSE" | jq -r '.uploadUrls[0].filePath')

# Step 8: 上传新文件(同上 Headers 解析+curl PUT)

# Step 9: 注册新文件(自动触发搜索索引重建,无需单独 indexing 调用)
NEW_FILE_SIZE=$(stat -f%z "$NEW_FILE" 2>/dev/null || stat -c%s "$NEW_FILE")
curl -s -X POST "$ORG_URL/services/data/v66.0/einstein/data-libraries/$LIBRARY_ID/files" \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"uploadedFiles":[{"filePath":"'"${FILE_PATH_S3}"'","fileSize":'"$NEW_FILE_SIZE"'}]}' | jq '.'
# 预期: {"filesAccepted": 1}

关键区别:第一个文件需要单独的 indexing 调用(Step 5)。追加文件使用 files 端点(Step 9),自动触发索引重建。

Knowledge 库完整示例

s215

创建 KNOWLEDGE Library 时需要指定索引字段。关键要求:

  • primaryIndexField1primaryIndexField2 必填且创建后不可修改
  • 所有指定字段必须存在于 Knowledge 对象(KnowledgeKAV)且为 STRING 或 TEXTAREA 类型
  • contentFields 可后续通过 PATCH 更新
  • 可选:通过 Data Categories 限制知识库搜索范围(isDataCategoryRuleEnabled

创建含 Data Categories 的 Knowledge Library

s216
# 创建 Knowledge Library
RESPONSE_ADL=$(curl -s -X POST "$ORG_URL/services/data/v66.0/einstein/data-libraries" \
  -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"masterLabel":"'"${ADL_Name}"'","developerName":"'"${ADL_DevName}"'",
    "groundingSource":{"sourceType":"KNOWLEDGE",
      "knowledgeConfig":{
        "primaryIndexField1":"Title",
        "primaryIndexField2":"Summary",
        "contentFields":["UrlName"]}}}')
LIBRARY_ID=$(echo "$RESPONSE_ADL" | jq -r '.libraryId')

# 创建含 Data Categories 的 Knowledge Library(按分类名称)
# 格式: groupDeveloperName.categoryDeveloperName
curl -s -X POST "$ORG_URL/..." \
  -d '{"groundingSource":{"sourceType":"KNOWLEDGE",
    "knowledgeConfig":{
      "primaryIndexField1":"Title","primaryIndexField2":"Summary",
      "contentFields":["UrlName"],
      "isDataCategoryRuleEnabled":true,
      "dataCategorySelectionNames":["Products.Hardware","Products.Software"]}}}'

# 也可按分类 ID(不能同时提供 names 和 IDs)
# "dataCategorySelectionIds":["ka01234567890ABCAA","ka01234567890ABCBB"]

Category 处理:分类名称在创建时转换为 ID,重复条目自动去重。

Knowledge Library 管理操作

操作方法端点说明
索引POST/indexing触发 Knowledge Article 索引
状态GET/status含 indexingStatus.stages 详情(DATA_LAKE_OBJECT/DATA_MODEL_OBJECT/SEARCH_INDEX/DATA_STREAM/RETRIEVER)
更新PATCH/{id}可更新 contentFields 和 Data Category 配置;索引进行中时不能更新
详情GET/{id}返回完整配置(含 retrieverId/retrieverLabel/status)
删除DELETE/{id}返回 HTTP 204。被 Agent 引用的库不能删除

索引状态阶段详解

GET /status 返回的 indexingStatus.stages 包含五个处理阶段:

  • DATA_LAKE_OBJECT — Data 360 数据湖对象创建
  • DATA_MODEL_OBJECT — 数据模型对象
  • SEARCH_INDEX — 搜索索引构建(最耗时,需分块+嵌入)
  • DATA_STREAM — 数据流处理
  • RETRIEVER — 检索器配置

每个阶段有独立的 completedAt 时间戳和 status(SUCCESS/IN_PROGRESS/FAILED)。

OTel API:导出会话追踪数据(Beta)

s217

Agentforce Session Trace OTel API 提取单个 Agent 会话的完整追踪 JSON,格式符合 OpenTelemetry (OTLP) v1.0 规范。从两个数据源合并数据:Agentforce Session Tracing(轮次、消息、LLM 调用、动作)和 Generative AI Audit & Feedback(Metric 分数、反馈信号)。所有数据是预关联的——无需 Data Cloud SQL。

Beta 限制

  • 仅支持单会话查询(每次一个 session ID),不支持批量
  • Connect API 标准速率限制适用
  • 返回 StartTimestamp 在 72 小时内的会话数据

OTel API 设置、查询与导出

s218

Step 1-2:启用数据收集 + 认证

  1. Setup → Einstein Generative AI → Einstein Audit, Analytics, and Monitoring Setup
  2. 开启:Agentforce Session TracingAudit and Feedback
  3. 创建 ECA 获取 OAuth 2.0 Token

Step 3:提交 REST API 查询

GET /services/data/v66.0/einstein/audit/otel/{session-id}
# 返回 OTel ResourceSpans 格式的完整会话追踪
# 含: 所有 turns, messages, LLM calls, action executions, metric scores, feedback

Step 4:导出到 Observability 平台

数据已是 OTel 格式,可直接导入 Splunk/Datadog/New Relic 等支持 OTLP 的平台,无需转换。也可通过 OTLP Collector 轮询 API 并转发:

# otel-collector-config.yaml 示例
receivers:
  salesforce_otel:
    endpoint: https://your-instance.salesforce.com
    auth:
      authenticator: oauth2client
processors:
  batch:
    timeout: 10s
exporters:
  otlp/datadog:
    endpoint: https://api.datadoghq.com
    headers:
      DD-API-KEY: ${DD_API_KEY}
service:
  pipelines:
    traces:
      receivers: [salesforce_otel]
      processors: [batch, attributes]
      exporters: [otlp/datadog]

FAQ 要点

  • 需要 Data Cloud?Beta 中需要(数据存储在 Data 360 中,API 通过 Data 360 SQL 查询)。长期计划 Unified Planner native OTel 路径减少此依赖
  • 数据治理?底层 Data 360 查询服务基于 API 调用用户上下文执行 Dataspace 和治理规则
  • 替换 STDM DMOs?不替换。OTel JSON API 是增量式的,面向 pro-code 和 API 场景(调试/自定义评估/第三方导出)。STDM DMOs 仍是 Agentforce Studio 分析和可观测性的基础
  • 批量场景?调用查询服务获取会话列表 → 逐个调用 API。原生 push/connector 支持计划中