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 核心设计原则

  1. Async First:由于后端是 FastAPI,SDK 原生支持 async/await
  2. 自动鉴权:初始化时传入 API Key,后续请求自动携带。
  3. 智能重试:遇到网络波动或 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 运行所需的库。
  • 一键构建:配合 uvpip install build,可以轻松生成 .whl 文件。

六、完整调用链追踪

6.1 开发者使用视角

Backend API Network Layer AiRunCoachClient 开发者 Backend API Network Layer AiRunCoachClient 开发者 自动添加 Header & JSON 序列化 alt [遇到 429 错误] client = AiRunCoachClient(api_key="...") await client.ask_coach("如何提升配速?") POST /api/v1/agent 发送请求 返回 JSON 结果 解析响应 HTTPStatusError 触发重试逻辑 (Sleep 2s) 重新发送请求 返回结构化字典

七、踩坑记录与解决方案

坑1:同步与异步混用

现象:在 Jupyter Notebook 等已有事件循环的环境中调用 asyncio.run() 报错。

解决方案

  • 提供同步包装类 SyncAiRunCoachClient,内部通过 asyncio.new_event_loop() 处理。
  • 或者在文档中明确建议用户在异步框架(如 FastAPI, aiohttp)中使用。

坑2:敏感信息泄露

现象:SDK 日志里打印了完整的 Request Body,包含用户的 API Key。

解决方案

  • httpx 挂载自定义的 EventHook,在打印日志前对 AuthorizationX-API-Key 字段进行脱敏处理。

八、总结与展望

核心价值

  1. 降低门槛:开发者只需 pip install 即可开始调用,无需关心 HTTP 细节。
  2. 类型提示:配合 Pydantic 模型,IDE 能提供完美的代码补全。
  3. 稳定性:内置的重试和错误处理机制,让接入方的代码更加健壮。

后续优化

  1. Webhook 支持:在 SDK 中提供便捷的回调接收器。
  2. 多语言扩展:基于同样的 OpenAPI 规范,生成 TypeScript 或 Go 版本的 SDK。

九、完整源码

GitHub仓库AiRunCoachAgent

快速演示AiRunCoachAgent

核心文件清单

sdk/
├── ai_run_coach/
│   ├── client.py                      # 核心客户端实现
│   └── exceptions.py                  # 异常定义
├── pyproject.toml                     # 打包配置
└── README.md                          # SDK 使用文档

如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、转发!有任何问题或建议,请在评论区留言讨论。 🏃‍♂️💨

更多推荐