Files
sim/apps/docs/content/docs/zh/execution/api.mdx
2025-12-09 15:25:03 -08:00

610 lines
17 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: 外部 API
---
import { Callout } from 'fumadocs-ui/components/callout'
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
import { Video } from '@/components/ui/video'
Sim 提供了一个全面的外部 API用于查询工作流执行日志并在工作流完成时设置实时通知的 webhook。
## 身份验证
所有 API 请求都需要在 `x-api-key` 标头中传递 API 密钥:
```bash
curl -H "x-api-key: YOUR_API_KEY" \
https://sim.ai/api/v1/logs?workspaceId=YOUR_WORKSPACE_ID
```
您可以在 Sim 仪表板的用户设置中生成 API 密钥。
## 日志 API
所有 API 响应都包含有关您的工作流执行限制和使用情况的信息:
```json
"limits": {
"workflowExecutionRateLimit": {
"sync": {
"requestsPerMinute": 60, // Sustained rate limit per minute
"maxBurst": 120, // Maximum burst capacity
"remaining": 118, // Current tokens available (up to maxBurst)
"resetAt": "..." // When tokens next refill
},
"async": {
"requestsPerMinute": 200, // Sustained rate limit per minute
"maxBurst": 400, // Maximum burst capacity
"remaining": 398, // Current tokens available
"resetAt": "..." // When tokens next refill
}
},
"usage": {
"currentPeriodCost": 1.234, // Current billing period usage in USD
"limit": 10, // Usage limit in USD
"plan": "pro", // Current subscription plan
"isExceeded": false // Whether limit is exceeded
}
}
```
**注意:** 速率限制使用令牌桶算法。`remaining` 可以超过 `requestsPerMinute` 达到 `maxBurst`,当您最近未使用全部配额时,允许突发流量。响应正文中的速率限制适用于工作流执行。调用此 API 端点的速率限制在响应头中(`X-RateLimit-*`)。
### 查询日志
使用广泛的过滤选项查询工作流执行日志。
<Tabs items={['Request', 'Response']}>
<Tab value="Request">
```http
GET /api/v1/logs
```
**必需参数:**
- `workspaceId` - 您的工作区 ID
**可选过滤器:**
- `workflowIds` - 逗号分隔的工作流 ID
- `folderIds` - 逗号分隔的文件夹 ID
- `triggers` - 逗号分隔的触发类型:`api`、`webhook`、`schedule`、`manual`、`chat`
- `level` - 按级别过滤:`info`、`error`
- `startDate` - 日期范围起始的 ISO 时间戳
- `endDate` - 日期范围结束的 ISO 时间戳
- `executionId` - 精确执行 ID 匹配
- `minDurationMs` - 最小执行持续时间(毫秒)
- `maxDurationMs` - 最大执行持续时间(毫秒)
- `minCost` - 最小执行成本
- `maxCost` - 最大执行成本
- `model` - 按使用的 AI 模型过滤
**分页:**
- `limit` - 每页结果数默认100
- `cursor` - 下一页的游标
- `order` - 排序顺序:`desc`、`asc`(默认:降序)
**详细级别:**
- `details` - 响应详细级别:`basic`, `full`默认basic
- `includeTraceSpans` - 包含跟踪跨度默认false
- `includeFinalOutput` - 包含最终输出默认false
</Tab>
<Tab value="Response">
```json
{
"data": [
{
"id": "log_abc123",
"workflowId": "wf_xyz789",
"executionId": "exec_def456",
"level": "info",
"trigger": "api",
"startedAt": "2025-01-01T12:34:56.789Z",
"endedAt": "2025-01-01T12:34:57.123Z",
"totalDurationMs": 334,
"cost": {
"total": 0.00234
},
"files": null
}
],
"nextCursor": "eyJzIjoiMjAyNS0wMS0wMVQxMjozNDo1Ni43ODlaIiwiaWQiOiJsb2dfYWJjMTIzIn0",
"limits": {
"workflowExecutionRateLimit": {
"sync": {
"requestsPerMinute": 60,
"maxBurst": 120,
"remaining": 118,
"resetAt": "2025-01-01T12:35:56.789Z"
},
"async": {
"requestsPerMinute": 200,
"maxBurst": 400,
"remaining": 398,
"resetAt": "2025-01-01T12:35:56.789Z"
}
},
"usage": {
"currentPeriodCost": 1.234,
"limit": 10,
"plan": "pro",
"isExceeded": false
}
}
}
```
</Tab>
</Tabs>
### 获取日志详情
检索特定日志条目的详细信息。
<Tabs items={['Request', 'Response']}>
<Tab value="Request">
```http
GET /api/v1/logs/{id}
```
</Tab>
<Tab value="Response">
```json
{
"data": {
"id": "log_abc123",
"workflowId": "wf_xyz789",
"executionId": "exec_def456",
"level": "info",
"trigger": "api",
"startedAt": "2025-01-01T12:34:56.789Z",
"endedAt": "2025-01-01T12:34:57.123Z",
"totalDurationMs": 334,
"workflow": {
"id": "wf_xyz789",
"name": "My Workflow",
"description": "Process customer data"
},
"executionData": {
"traceSpans": [...],
"finalOutput": {...}
},
"cost": {
"total": 0.00234,
"tokens": {
"prompt": 123,
"completion": 456,
"total": 579
},
"models": {
"gpt-4o": {
"input": 0.001,
"output": 0.00134,
"total": 0.00234,
"tokens": {
"prompt": 123,
"completion": 456,
"total": 579
}
}
}
},
"limits": {
"workflowExecutionRateLimit": {
"sync": {
"requestsPerMinute": 60,
"maxBurst": 120,
"remaining": 118,
"resetAt": "2025-01-01T12:35:56.789Z"
},
"async": {
"requestsPerMinute": 200,
"maxBurst": 400,
"remaining": 398,
"resetAt": "2025-01-01T12:35:56.789Z"
}
},
"usage": {
"currentPeriodCost": 1.234,
"limit": 10,
"plan": "pro",
"isExceeded": false
}
}
}
}
```
</Tab>
</Tabs>
### 获取执行详情
检索执行详情,包括工作流状态快照。
<Tabs items={['Request', 'Response']}>
<Tab value="Request">
```http
GET /api/v1/logs/executions/{executionId}
```
</Tab>
<Tab value="Response">
```json
{
"executionId": "exec_def456",
"workflowId": "wf_xyz789",
"workflowState": {
"blocks": {...},
"edges": [...],
"loops": {...},
"parallels": {...}
},
"executionMetadata": {
"trigger": "api",
"startedAt": "2025-01-01T12:34:56.789Z",
"endedAt": "2025-01-01T12:34:57.123Z",
"totalDurationMs": 334,
"cost": {...}
}
}
```
</Tab>
</Tabs>
## 通知
通过 webhook、电子邮件或 Slack 获取工作流执行完成的实时通知。通知在工作区级别从日志页面进行配置。
### 配置
通过点击菜单按钮并选择“配置通知”从日志页面配置通知。
**通知渠道:**
- **Webhook**:向您的端点发送 HTTP POST 请求
- **电子邮件**:接收包含执行详情的电子邮件通知
- **Slack**:向 Slack 频道发送消息
**工作流选择:**
- 选择特定的工作流进行监控
- 或选择“所有工作流”以包含当前和未来的工作流
**过滤选项:**
- `levelFilter`:接收的日志级别(`info``error`
- `triggerFilter`:接收的触发类型(`api``webhook``schedule``manual``chat`
**可选数据:**
- `includeFinalOutput`:包含工作流的最终输出
- `includeTraceSpans`:包含详细的执行跟踪跨度
- `includeRateLimits`:包含速率限制信息(同步/异步限制和剩余)
- `includeUsageData`:包含计费周期的使用情况和限制
### 警报规则
与其为每次执行接收通知,不如配置警报规则,仅在检测到问题时收到通知:
**连续失败**
- 在 X 次连续失败执行后发出警报(例如,连续 3 次失败)
- 当执行成功时重置
**失败率**
- 当失败率在过去 Y 小时内超过 X% 时发出警报
- 需要窗口内至少 5 次执行
- 仅在整个时间窗口结束后触发
**延迟阈值**
- 当任何执行时间超过 X 秒时发出警报
- 用于捕捉缓慢或挂起的工作流
**延迟峰值**
- 当执行时间比平均值慢 X% 时发出警报
- 与配置时间窗口内的平均持续时间进行比较
- 需要至少 5 次执行以建立基线
**成本阈值**
- 当单次执行成本超过 $X 时发出警报
- 用于捕捉高成本的 LLM 调用
**无活动**
- 当 X 小时内没有执行发生时发出警报
- 用于监控应定期运行的计划工作流
**错误计数**
- 当错误计数在某个时间窗口内超过 X 时发出警报
- 跟踪总错误数,而非连续错误
所有警报类型都包括 1 小时的冷却时间,以防止通知过多。
### Webhook 配置
对于 webhooks可用以下附加选项
- `url`:您的 webhook 端点 URL
- `secret`:用于 HMAC 签名验证的可选密钥
### 负载结构
当工作流执行完成时Sim 会发送以下负载(通过 webhook POST、电子邮件或 Slack
```json
{
"id": "evt_123",
"type": "workflow.execution.completed",
"timestamp": 1735925767890,
"data": {
"workflowId": "wf_xyz789",
"executionId": "exec_def456",
"status": "success",
"level": "info",
"trigger": "api",
"startedAt": "2025-01-01T12:34:56.789Z",
"endedAt": "2025-01-01T12:34:57.123Z",
"totalDurationMs": 334,
"cost": {
"total": 0.00234,
"tokens": {
"prompt": 123,
"completion": 456,
"total": 579
},
"models": {
"gpt-4o": {
"input": 0.001,
"output": 0.00134,
"total": 0.00234,
"tokens": {
"prompt": 123,
"completion": 456,
"total": 579
}
}
}
},
"files": null,
"finalOutput": {...}, // Only if includeFinalOutput=true
"traceSpans": [...], // Only if includeTraceSpans=true
"rateLimits": {...}, // Only if includeRateLimits=true
"usage": {...} // Only if includeUsageData=true
},
"links": {
"log": "/v1/logs/log_abc123",
"execution": "/v1/logs/executions/exec_def456"
}
}
```
### Webhook 头信息
每个 webhook 请求都包含以下头信息(仅限 webhook 渠道):
- `sim-event`:事件类型(始终为 `workflow.execution.completed`
- `sim-timestamp`:以毫秒为单位的 Unix 时间戳
- `sim-delivery-id`:用于幂等性的唯一交付 ID
- `sim-signature`:用于验证的 HMAC-SHA256 签名(如果配置了密钥)
- `Idempotency-Key`:与交付 ID 相同,用于检测重复
### 签名验证
如果您配置了 webhook 密钥,请验证签名以确保 webhook 来自 Sim
<Tabs items={['Node.js', 'Python']}>
<Tab value="Node.js">
```javascript
import crypto from 'crypto';
function verifyWebhookSignature(body, signature, secret) {
const [timestampPart, signaturePart] = signature.split(',');
const timestamp = timestampPart.replace('t=', '');
const expectedSignature = signaturePart.replace('v1=', '');
const signatureBase = `${timestamp}.${body}`;
const hmac = crypto.createHmac('sha256', secret);
hmac.update(signatureBase);
const computedSignature = hmac.digest('hex');
return computedSignature === expectedSignature;
}
// In your webhook handler
app.post('/webhook', (req, res) => {
const signature = req.headers['sim-signature'];
const body = JSON.stringify(req.body);
if (!verifyWebhookSignature(body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process the webhook...
});
```
</Tab>
<Tab value="Python">
```python
import hmac
import hashlib
import json
def verify_webhook_signature(body: str, signature: str, secret: str) -> bool:
timestamp_part, signature_part = signature.split(',')
timestamp = timestamp_part.replace('t=', '')
expected_signature = signature_part.replace('v1=', '')
signature_base = f"{timestamp}.{body}"
computed_signature = hmac.new(
secret.encode(),
signature_base.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(computed_signature, expected_signature)
# In your webhook handler
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('sim-signature')
body = json.dumps(request.json)
if not verify_webhook_signature(body, signature, os.environ['WEBHOOK_SECRET']):
return 'Invalid signature', 401
# Process the webhook...
```
</Tab>
</Tabs>
### 重试策略
失败的 webhook 交付将使用指数退避和抖动进行重试:
- 最大尝试次数5
- 重试延迟5 秒、15 秒、1 分钟、3 分钟、10 分钟
- 抖动:最多额外延迟 10% 以防止蜂拥效应
- 仅 HTTP 5xx 和 429 响应会触发重试
- 交付在 30 秒后超时
<Callout type="info">
Webhook 交付是异步处理的,不会影响工作流执行性能。
</Callout>
## 最佳实践
1. **轮询策略**:在轮询日志时,使用基于游标的分页与 `order=asc` 和 `startDate` 来高效获取新日志。
2. **Webhook 安全性**:始终配置一个 webhook 密钥并验证签名,以确保请求来自 Sim。
3. **幂等性**:使用 `Idempotency-Key` 标头检测并处理重复的 webhook 交付。
4. **隐私**:默认情况下,`finalOutput` 和 `traceSpans` 会从响应中排除。仅在需要这些数据并了解隐私影响时启用它们。
5. **速率限制**:当收到 429 响应时,实施指数退避。检查 `Retry-After` 标头以获取推荐的等待时间。
## 速率限制
该 API 使用 **令牌桶算法** 进行速率限制,在提供公平使用的同时允许突发流量:
| 计划 | 请求/分钟 | 突发容量 |
|------|-----------|----------|
| 免费 | 10 | 20 |
| 专业版 | 30 | 60 |
| 团队版 | 60 | 120 |
| 企业版 | 120 | 240 |
**工作原理:**
- 令牌以 `requestsPerMinute` 的速率补充
- 空闲时最多可累积 `maxBurst` 个令牌
- 每个请求消耗 1 个令牌
- 突发容量允许处理流量高峰
速率限制信息包含在响应头中:
- `X-RateLimit-Limit`:每分钟请求数(补充速率)
- `X-RateLimit-Remaining`:当前可用令牌数
- `X-RateLimit-Reset`:令牌下次补充的 ISO 时间戳
## 示例:轮询新日志
```javascript
let cursor = null;
const workspaceId = 'YOUR_WORKSPACE_ID';
const startDate = new Date().toISOString();
async function pollLogs() {
const params = new URLSearchParams({
workspaceId,
startDate,
order: 'asc',
limit: '100'
});
if (cursor) {
params.append('cursor', cursor);
}
const response = await fetch(
`https://sim.ai/api/v1/logs?${params}`,
{
headers: {
'x-api-key': 'YOUR_API_KEY'
}
}
);
if (response.ok) {
const data = await response.json();
// Process new logs
for (const log of data.data) {
console.log(`New execution: ${log.executionId}`);
}
// Update cursor for next poll
if (data.nextCursor) {
cursor = data.nextCursor;
}
}
}
// Poll every 30 seconds
setInterval(pollLogs, 30000);
```
## 示例:处理 Webhooks
```javascript
import express from 'express';
import crypto from 'crypto';
const app = express();
app.use(express.json());
app.post('/sim-webhook', (req, res) => {
// Verify signature
const signature = req.headers['sim-signature'];
const body = JSON.stringify(req.body);
if (!verifyWebhookSignature(body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Check timestamp to prevent replay attacks
const timestamp = parseInt(req.headers['sim-timestamp']);
const fiveMinutesAgo = Date.now() - (5 * 60 * 1000);
if (timestamp < fiveMinutesAgo) {
return res.status(401).send('Timestamp too old');
}
// Process the webhook
const event = req.body;
switch (event.type) {
case 'workflow.execution.completed':
const { workflowId, executionId, status, cost } = event.data;
if (status === 'error') {
console.error(`Workflow ${workflowId} failed: ${executionId}`);
// Handle error...
} else {
console.log(`Workflow ${workflowId} completed: ${executionId}`);
console.log(`Cost: $${cost.total}`);
// Process successful execution...
}
break;
}
// Return 200 to acknowledge receipt
res.status(200).send('OK');
});
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});
```