为什么数据 API 默认应该是 sync-only
工程观点文章:为什么同步设计是数据 API 服务 AI Agent 的正确默认。基于 571 个操作的实战经验,分析取舍、架构影响和 async 真正必要的场景。
结论先行 — SandBase 为 571 个社媒数据操作选择了 sync-only 设计。这不是限制——是刻意的架构决策:简化 Agent 集成、消除状态管理复杂度、匹配 LLM 工具调用的实际工作方式。Async 对生成任务(视频、图片)是必要的,但数据检索默认应该是 sync。本文是完整的取舍分析。
观点,明确表达
对被 AI Agent 消费的数据 API,同步请求-响应是正确默认。 不是因为 async 不好,而是 sync 消除了 Agent 不需要的整类集成复杂度——这些复杂度在可靠性、可调试性和开发速度上有真实成本。
这是基于运营 571 个社媒数据操作(抖音、TikTok、微博、小红书)并观察 Agent 开发者实际集成方式得出的工程观点。模式一致:sync API 采用更快、出错更少、产生更少支持工单。
LLM 工具调用的实际工作方式
讨论 API 设计前,先理解消费者的执行模型。
LLM 使用工具(function calling)时的流程:
1. LLM 生成工具调用:{"name": "get_user", "args": {"id": "123"}}
2. 运行时执行函数
3. 运行时将结果返回 LLM
4. LLM 带着结果继续推理
从 LLM 视角看,这天然是同步的。 模型生成调用、暂停、收到结果、继续。标准工具调用中没有这些机制:
- “给你一个 task ID,稍后轮询”
- “准备好了会调你的 webhook”
- “30 秒后再来看”
当然可以在工具调用上层构建这些模式。但你加的每一层 async 都变成 Agent 开发者必须管理的复杂度:
# Sync 在 Agent 工具中的样子
def get_douyin_user(user_id: str) -> dict:
return api.get(f"/douyin/user/{user_id}").json()
# 完事。LLM 立刻拿到结果。
# Async 在 Agent 工具中的样子
def get_douyin_user(user_id: str) -> dict:
task = api.post(f"/douyin/user/{user_id}/async")
task_id = task.json()["task_id"]
# 现在怎么办?LLM 在等着。
# 方案 A:循环轮询(阻塞、浪费)
for _ in range(30):
result = api.get(f"/tasks/{task_id}")
if result.json()["status"] == "complete":
return result.json()["data"]
time.sleep(1)
raise TimeoutError("任务 30 秒未完成")
# 方案 B:把 task_id 返回给 LLM(令人困惑)
# LLM:"我拿到了一个 task_id 但我需要实际数据……"
# 现在需要另一次工具调用来检查状态
Async 版本代码量 10 倍、引入故障模式(超时、丢失任务、部分结果),且不匹配 LLM 的执行模型。
Sync 合约
SandBase 的 571 个社媒数据操作遵循简单合约:
请求 → 处理(100ms–2s) → 响应
POST/GET → 服务端获取数据 → JSON 结果或错误
这个合约的属性:
- 有界延迟: 每个操作在已知时间窗口内完成
- 原子性: 你拿到完整结果或一个错误,绝不是半成品数据
- 无状态: 没有 task ID、没有轮询、没有会话管理
- 可重试: 失败时,用相同参数重试相同请求
- 可观测: 延迟 = 请求到响应的时间。没有隐藏队列。
为什么这简化了 Agent 架构
1. 无需状态管理
Async API 需要追踪未完成任务:
# Async:你需要一个任务管理器
class TaskManager:
def __init__(self):
self.pending = {} # task_id -> 元数据
self.results = {} # task_id -> 结果
async def submit(self, operation, params):
task = await api.post(operation, params)
self.pending[task.id] = {"submitted": time.time(), "params": params}
return task.id
async def check(self, task_id):
# 检查、清理、处理过期……
pass
async def cleanup_stale(self):
# 永远没完成的任务……重试?放弃?记日志?三者都做?
pass
Sync API 不需要任何这些:
# Sync:调一下,拿结果
result = await api.get("/douyin/user/profile", params={"user_id": uid})
# 完事。无状态。无清理。无过期任务。
单个 API 调用差异看似微小。但对一个每决策周期跨 4 平台做 50 次调用的 Agent,async 状态管理会成为显著的 bug 来源。
2. 错误处理直接了当
Sync 错误是即时和可操作的:
try:
result = await api.get("/douyin/user/profile", params={"user_id": uid})
except HTTPError as e:
if e.status == 429:
await asyncio.sleep(1)
result = await api.get(...) # 重试
elif e.status == 404:
result = {"error": "user_not_found"} # Agent 优雅处理
else:
raise # 意外错误,冒泡
Async 错误分散在时间上且难以归因:
# 提交成功,但任务 10 秒后失败
task = await api.post("/async/douyin/user/profile", params={"user_id": uid})
# 暂时没错误... 任务"处理中"
# 稍后检查时:
result = await api.get(f"/tasks/{task.id}")
# result.status 可能是:"failed"、"timeout"、"partial"、"expired"
# 这是哪个 user_id 的?需要追踪。
# 能重试吗?也许。但原始上下文已经丢了。
3. 调试是线性的
Sync 工具失败时,trace 很简单:
10:00:01.234 → 请求: GET /douyin/user/profile?user_id=abc
10:00:01.891 → 响应: 200 OK, 657ms
10:00:01.892 → LLM 收到结果,继续推理
Async 工具失败时,trace 碎片化:
10:00:01.234 → 提交: POST /async/douyin/user/profile
10:00:01.456 → 拿到 task_id: task_xyz
10:00:05.000 → 轮询: GET /tasks/task_xyz → "processing"
10:00:10.000 → 轮询: GET /tasks/task_xyz → "processing"
10:00:15.000 → 轮询: GET /tasks/task_xyz → "failed"
10:00:15.200 → 为什么失败?查看任务详情...
6 条日志代替 2 条。分散在 15 秒而非 657ms。Agent 的推理全程被阻塞——或者更糟,用过期数据继续。
4. 可组合性
Agent 将多个工具调用组合成决策工作流。Sync 让组合自然:
# Agent 的内部推理导致这个序列:
user = await get_user(user_id)
videos = await get_user_videos(user_id, count=10)
engagement = calculate_engagement_rate(videos)
decision = f"该创作者互动率 {engagement}%,{'高于' if engagement > threshold else '低于'}阈值"
每一步在下一步开始前完成。Agent 可以对中间结果推理并决定继续还是分支。
性能问题:“Sync 不是更慢吗?”
常见反对意见:“用 async 我可以提交 100 个任务等结果陆续到达。Sync 意味着逐个等。”
这混淆了两件事:
- API 设计(sync vs async)— 服务端是立即返回结果还是用任务系统
- 客户端并发 — 客户端是否并行发请求
你可以用 sync API + 并行客户端调用:
# Sync API + 并发客户端 = 快
async def get_50_profiles(user_ids: list[str]) -> list[dict]:
tasks = [api.get(f"/douyin/user/profile?user_id={uid}") for uid in user_ids]
results = await asyncio.gather(*tasks)
return [r.json() for r in results]
# 50 个 sync 调用并行,全部在 ~1 秒内完成
Sync API 单独完成每个请求 200ms–2s。客户端并发发射 50 个请求。总挂钟时间:50 个画像 ~2 秒,不是 50 × 2 秒。
对有界延迟操作,这严格优于 async,因为:
- 你立即知道哪些成功哪些失败
- 无任务管理开销
- 无轮询成本
- 服务端不需要维护任务状态
什么时候 Async 真正必要
Sync-only 不总是合适。以下场景 async 是正确选择:
1. 生成任务(视频、图片)
文本 → 视频生成:30–120 秒
图片生成:5–30 秒
这些无法在 HTTP 超时范围内 sync 返回。
2. 批量导出
"导出这个视频的所有 10,000 条评论"
需要分页获取,可能 30 秒以上。
3. 多步聚合
"计算该账号 90 天互动趋势"
需要获取 90 天数据 + 计算。
规律
注意这些共同点:它们涉及创建或计算,不是检索。 数据检索——“给我这个用户的画像”、“给我这个视频的统计”、“搜索这个关键词”——天然很快。底层数据存在,API 只需取出返回。
分界线清晰:
- 数据检索 → sync(数据存在,取出即可)
- 数据生成/计算 → async(结果尚不存在,必须创建)
571 操作的实证数据
SandBase 运营 571 个社媒数据操作跨 4 平台,采用 sync-only 设计。运营数据:
| 指标 | 数值 |
|---|---|
| 操作数 | 571 |
| 中位延迟 (P50) | 340ms |
| P99 延迟 | 1.8s |
| 成功率 | 99.4% |
| 超时率 (> 5s) | 0.3% |
0.3% 超时率意味着每 1,000 次调用中 3 次超过 5 秒——这些可重试。其余 997 次调用在 HTTP 超时范围内完成,干净嵌入 Agent 工具调用模式。
如果这些操作是 async 的,那 997 次快速调用每一次都会承担不必要的开销:任务提交、任务存储、轮询或 webhook 投递、任务清理。这些开销纯粹因为 API 设计而存在,不是因为操作需要。
反对意见回应
“Webhook 不比轮询高效吗?”
是的,但 webhook 需要:
- 客户端暴露公共端点
- Webhook 验证和安全
- 投递失败的重试逻辑
- webhook 丢失时的状态对账
对运行在开发者笔记本或 serverless 函数中的 Agent,这些基础设施都不存在。Sync 彻底消除了需求。
“如果上游数据源慢怎么办?”
那是 API 供应方的问题,不是 Agent 开发者的。好的数据 API 有缓存、连接池和预取来确保一致延迟。如果上游真的慢(> 5s),那个操作应该是 async——但大多数数据检索操作不是。
“Sync 限制吞吐量。”
只在顺序调用时。用并发请求加速率限制感知,sync API 可以每秒交付数千结果。限制是速率限制,不是协议。
架构启示
对 API 提供方
如果你在为 Agent 消费构建数据 API:
- 默认 sync。 如果操作能在 < 5 秒完成,做成 sync。
- 设置明确超时。 文档化每操作的最大延迟。Agent 需要知道等多久。
- 正确使用 HTTP 语义。 200 成功、4xx 客户端错误、5xx 服务端错误、429 限流配 Retry-After header。
- 返回完整结果。 简单查询不要返回部分数据加”取更多”模式。
- Async 保留给生成。 只在操作真正需要 > 5 秒时用任务模式。
对 Agent 开发者
如果你在构建消费数据 API 的 Agent:
- 优先 sync API。 集成、调试和维护更简单。
- 用客户端并发获取并行。
asyncio.gather()在 sync API 上给你并行执行。 - 设置积极超时。 API 调用 > 5s 很可能在失败。超时重试。
- 保持工具函数简单。 工具函数应该是:调 API → 返回结果。如果需要状态管理,说明 API 设计不适合你的用例。
- 分离 sync 和 async 工作流。 如果两者都需要(数据检索 + 视频生成),放在不同 Agent 工具中,用不同的超时/重试策略。
结论
数据 API 的 sync-only 不是妥协。它是匹配 AI Agent 实际消费数据方式的架构:请求、接收、推理、重复。你在数据 API 上加的每一个 async 模式都是对每个集成 Agent 开发者的复杂度税——这些复杂度服务的是 API 供应方的基础设施便利,不是开发者的生产力。
原则很简单:如果数据存在且能在 5 秒内取到,同步返回。 Async 留给结果真正不存在、必须被创建的操作。你的 Agent 开发者会感谢你——主要表现是不提关于丢失任务、webhook 失败和轮询超时的支持工单。


