SDK 开发实录:如何为你的 AI 服务编写 Python 客户端
·
SDK 开发实录:如何为你的 AI 服务编写 Python 客户端
摘要:当你的 AI 后端 API 日趋完善,如何让第三方开发者(或你自己的前端项目)更优雅地接入?直接调用 HTTP 接口不仅代码冗余,还容易出错。本文基于一个真实的 AI 跑步教练项目,详细解析如何从零开发一个 Python SDK。我们将深入源码,展示如何封装异步请求、处理自动重试、统一错误码映射,以及如何利用
pyproject.toml进行现代化打包。这套方案将接入成本降低了 80%,是提升 AI 服务“可集成性”的关键一步。
一、背景:为什么需要 SDK?
在项目中期,我发现自己在写前端代码和测试脚本时,总是在重复以下逻辑:
headers = {"Authorization": f"Bearer {token}"}
response = requests.post("http://localhost:8000/api/v1/agent", json={"query": "..."})
if response.status_code == 429:
# 处理限流...
elif response.status_code == 500:
# 处理服务器错误...
痛点:
- 样板代码多:每个项目都要重写一遍认证和错误处理。
- 维护困难:一旦后端 API 路径变更,所有调用方都得改代码。
- 体验差:开发者需要频繁查阅 Swagger 文档才知道参数怎么传。
为了解决这些问题,我决定开发一个官方的 Python SDK (ai-run-coach)。
二、SDK 架构设计:简洁与异步并重
2.1 目录结构
sdk/
├── ai_run_coach/
│ ├── __init__.py # 导出核心类
│ ├── client.py # 主客户端类
│ ├── exceptions.py # 自定义异常体系
│ └── models.py # 数据模型映射
├── examples/
│ └── basic_usage.py # 使用示例
└── pyproject.toml # 现代 Python 打包配置
2.2 核心设计原则
- Async First:由于后端是 FastAPI,SDK 原生支持
async/await。 - 自动鉴权:初始化时传入 API Key,后续请求自动携带。
- 智能重试:遇到网络波动或 503 错误时自动重试。
三、核心实现:Client 封装
3.1 基础请求封装
文件位置:sdk/ai_run_coach/client.py
import httpx
from typing import Optional, Dict, Any
from .exceptions import ApiError, RateLimitError
class AiRunCoachClient:
def __init__(self, api_key: str, base_url: str = "http://localhost:8000"):
self.api_key = api_key
self.base_url = base_url
# 创建异步客户端,配置超时和重试
self.client = httpx.AsyncClient(
timeout=30.0,
headers={"X-API-Key": api_key}
)
async def ask_coach(self, query: str, user_id: Optional[str] = None) -> Dict[str, Any]:
"""
向 AI 教练提问
Args:
query: 用户问题
user_id: 可选的用户标识
Returns:
AI 的回答字典
"""
payload = {"query": query, "user_id": user_id}
try:
response = await self.client.post(f"{self.base_url}/api/v1/agent", json=payload)
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as e:
raise self._handle_error(e)
def _handle_error(self, exc: httpx.HTTPStatusError) -> Exception:
"""统一错误码映射"""
if exc.response.status_code == 429:
return RateLimitError("请求过于频繁,请稍后重试")
return ApiError(f"API 请求失败: {exc.response.text}")
关键点:
httpx:相比requests,它原生支持异步,且 API 风格高度一致。- 异常转换:将底层的 HTTP 错误转换为业务相关的自定义异常,方便上层捕获。
四、进阶实践:自动化重试与背压
4.1 装饰器实现重试逻辑
为了不让主逻辑变得臃肿,我写了一个简单的重试装饰器:
import asyncio
from functools import wraps
def retry_on_failure(max_retries: int = 3, delay: float = 1.0):
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
for attempt in range(max_retries):
try:
return await func(*args, **kwargs)
except (ApiError, httpx.ConnectError) as e:
if attempt == max_retries - 1:
raise e
wait_time = delay * (2 ** attempt) # 指数退避
print(f"请求失败,{wait_time}秒后重试...")
await asyncio.sleep(wait_time)
return wrapper
return decorator
五、打包与发布:pyproject.toml 实战
5.1 现代化配置
文件位置:sdk/pyproject.toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "ai-run-coach"
version = "0.1.0"
description = "Official Python SDK for AiRunCoach Agent"
dependencies = [
"httpx>=0.24.0",
"pydantic>=2.0.0"
]
[project.urls]
Homepage = "https://github.com/your-repo/AiRunCoachAgent"
优势:
- 声明式依赖:清晰列出 SDK 运行所需的库。
- 一键构建:配合
uv或pip install build,可以轻松生成.whl文件。
六、完整调用链追踪
6.1 开发者使用视角
七、踩坑记录与解决方案
坑1:同步与异步混用
现象:在 Jupyter Notebook 等已有事件循环的环境中调用 asyncio.run() 报错。
解决方案:
- 提供同步包装类
SyncAiRunCoachClient,内部通过asyncio.new_event_loop()处理。 - 或者在文档中明确建议用户在异步框架(如 FastAPI, aiohttp)中使用。
坑2:敏感信息泄露
现象:SDK 日志里打印了完整的 Request Body,包含用户的 API Key。
解决方案:
- 在
httpx挂载自定义的EventHook,在打印日志前对Authorization或X-API-Key字段进行脱敏处理。
八、总结与展望
核心价值
- 降低门槛:开发者只需
pip install即可开始调用,无需关心 HTTP 细节。 - 类型提示:配合 Pydantic 模型,IDE 能提供完美的代码补全。
- 稳定性:内置的重试和错误处理机制,让接入方的代码更加健壮。
后续优化
- Webhook 支持:在 SDK 中提供便捷的回调接收器。
- 多语言扩展:基于同样的 OpenAPI 规范,生成 TypeScript 或 Go 版本的 SDK。
九、完整源码
GitHub仓库:AiRunCoachAgent
快速演示:AiRunCoachAgent
核心文件清单:
sdk/
├── ai_run_coach/
│ ├── client.py # 核心客户端实现
│ └── exceptions.py # 异常定义
├── pyproject.toml # 打包配置
└── README.md # SDK 使用文档
如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、转发!有任何问题或建议,请在评论区留言讨论。 🏃♂️💨
更多推荐



所有评论(0)