AI Function Call 实战:从工具调用到生产级代码的最佳实践

1次阅读
没有评论

共计 4870 个字符,预计需要花费 13 分钟才能阅读完成。

image.webp

真实场景:为什么工具调用如此重要?

想象一下,你正在开发一个电商推荐系统。当用户浏览商品时,系统需要实时调用库存 API,确保推荐的商品是有货的。如果这个调用失败或响应缓慢,用户可能会看到已售罄的商品,或者整个推荐列表加载不出来。这就是工具调用(Function Calling)在 AI 应用中的关键作用——它让大模型能可靠地与现实世界的服务交互。

AI Function Call 实战:从工具调用到生产级代码的最佳实践

另一个例子是旅行规划助手。当用户询问 ” 帮我预订下周去巴黎的机票 ” 时,AI 需要调用航空公司的 API 查询航班信息。这个过程中的每个调用都必须是高效、可靠且安全的。

HTTP 调用 vs SDK 封装:如何选择?

直接 HTTP 调用和 SDK 封装各有优缺点:

  • 直接 HTTP 调用
  • 优点:灵活,不受 SDK 版本限制
  • 缺点:需要自己处理重试、错误、序列化等

  • SDK 封装

  • 优点:开箱即用,通常包含最佳实践
  • 缺点:可能有版本兼容问题,灵活性较低

选型决策树:

  1. API 是否频繁变更?是→考虑直接 HTTP 调用
  2. 是否有现成的优质 SDK?是→优先使用 SDK
  3. 是否需要高度定制化的错误处理?是→考虑直接 HTTP 调用

核心代码实现

带指数退避的自动重试机制

import time
import random
from typing import Callable, TypeVar

T = TypeVar('T')

def with_retry(func: Callable[..., T],
    max_retries: int = 3,
    initial_delay: float = 1.0,
    max_delay: float = 10.0
) -> T:
    """
    带指数退避的自动重试装饰器
    :param func: 要执行的函数
    :param max_retries: 最大重试次数
    :param initial_delay: 初始延迟时间(秒)
    :param max_delay: 最大延迟时间(秒)
    """
    def wrapper(*args, **kwargs):
        retries = 0
        delay = initial_delay

        while True:
            try:
                return func(*args, **kwargs)
            except Exception as e:
                if retries >= max_retries:
                    raise

                # 指数退避 + 随机抖动避免惊群
                sleep_time = min(delay * (2 ** retries), max_delay)
                sleep_time *= random.uniform(0.8, 1.2)

                time.sleep(sleep_time)
                retries += 1

    return wrapper

TypeScript 版本:

const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

async function withRetry<T>(fn: () => Promise<T>,
  maxRetries: number = 3,
  initialDelay: number = 1000,
  maxDelay: number = 10000
): Promise<T> {
  let retries = 0;
  let delay = initialDelay;

  while (true) {
    try {return await fn();
    } catch (err) {if (retries >= maxRetries) {throw err;}

      const sleepTime = Math.min(delay * Math.pow(2, retries), maxDelay);
      const jitteredSleepTime = sleepTime * (0.8 + Math.random() * 0.4);

      await sleep(jitteredSleepTime);
      retries++;
    }
  }
}

使用 Zod 实现响应数据校验

import {z} from 'zod';

// 定义库存 API 响应 schema
const InventoryResponseSchema = z.object({productId: z.string(),
  inStock: z.boolean(),
  quantity: z.number().min(0),
  lastUpdated: z.string().datetime()
});

type InventoryResponse = z.infer<typeof InventoryResponseSchema>;

async function fetchInventory(productId: string): Promise<InventoryResponse> {const response = await fetch(`/api/inventory/${productId}`);
  const data = await response.json();

  // 校验数据格式
  return InventoryResponseSchema.parse(data);
}

并发场景下的令牌桶限流

import time
from threading import Lock

class TokenBucket:
    """令牌桶限流算法实现"""
    def __init__(self, capacity: int, refill_rate: float):
        self.capacity = capacity
        self.tokens = capacity
        self.refill_rate = refill_rate  # tokens/second
        self.last_refill = time.time()
        self.lock = Lock()

    def consume(self, tokens: int = 1) -> bool:
        """
        尝试消费指定数量的令牌
        :return: 是否成功消费
        """
        with self.lock:
            self._refill()
            if self.tokens >= tokens:
                self.tokens -= tokens
                return True
            return False

    def _refill(self):
        now = time.time()
        elapsed = now - self.last_refill
        new_tokens = elapsed * self.refill_rate
        self.tokens = min(self.tokens + new_tokens, self.capacity)
        self.last_refill = now

性能优化

批量调用 vs 单次调用

我们对一个商品推荐场景进行了测试,需要获取 10 个商品的库存状态:

调用方式 平均延迟(ms) P99 延迟(ms)
单次调用 1250 2300
批量调用 320 550

批量调用可以减少网络往返开销,但需要注意:

  1. 批量 API 需要服务端支持
  2. 批量不宜过大,建议控制在 10-20 个请求 / 批次

冷启动延迟应对方案

  1. 预热:在服务启动时预先调用关键 API

    # 服务启动时
    warmup_apis = ['/api/inventory/trending']
    for api in warmup_apis:
        try:
            requests.get(api)
        except:
            pass

  2. 缓存:对不常变的数据使用短期缓存

    const cache = new Map<string, {data: any; expires: number}>();
    
    async function getWithCache(key: string, ttl: number, fn: () => Promise<any>) {if (cache.has(key)) {const entry = cache.get(key)!;
        if (Date.now() < entry.expires) {return entry.data;}
      }
    
      const data = await fn();
      cache.set(key, { data, expires: Date.now() + ttl });
      return data;
    }

生产环境避坑指南

敏感数据泄露防护

确保日志中不记录敏感信息:

import logging

def sanitize_data(data: dict) -> dict:
    """脱敏处理"""
    sensitive_keys = ['password', 'token', 'api_key']
    return {
        k: '***REDACTED***' if k in sensitive_keys else v
        for k, v in data.items()}

# 配置日志过滤器
class SanitizeFilter(logging.Filter):
    def filter(self, record):
        if hasattr(record, 'data'):
            record.data = sanitize_data(record.data)
        return True

logger = logging.getLogger(__name__)
logger.addFilter(SanitizeFilter())

异步调用的幂等性 (idempotency) 保证

  1. 为每个请求生成唯一 ID
  2. 服务端记录已处理的 ID
  3. 重复请求直接返回之前的结果
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Dict

app = FastAPI()

# 内存存储,生产环境用 Redis
idempotency_cache: Dict[str, dict] = {}

class OrderRequest(BaseModel):
    idempotency_key: str
    product_id: str
    quantity: int

@app.post("/order")
async def create_order(request: OrderRequest):
    if request.idempotency_key in idempotency_cache:
        return idempotency_cache[request.idempotency_key]

    # 处理订单逻辑...
    result = {"status": "created"}

    idempotency_cache[request.idempotency_key] = result
    return result

监控指标埋点

关键指标包括:

  1. 成功率
  2. 延迟分布
  3. 错误类型

使用 Prometheus 客户端的示例:

from prometheus_client import Counter, Histogram

# 定义指标
REQUEST_COUNT = Counter(
    'function_call_requests_total',
    'Total API requests',
    ['endpoint', 'status']
)

REQUEST_LATENCY = Histogram(
    'function_call_latency_seconds',
    'API latency distribution',
    ['endpoint'],
    buckets=[0.1, 0.5, 1, 2, 5, 10]
)

# 在请求处理中使用
@REQUEST_LATENCY.time()
async def call_api(endpoint: str):
    try:
        # 调用 API...
        REQUEST_COUNT.labels(endpoint=endpoint, status='success').inc()
    except Exception as e:
        REQUEST_COUNT.labels(endpoint=endpoint, status='error').inc()
        raise

延伸思考:优雅降级

当依赖的 API 不可用时,系统如何继续提供服务?以下是几种策略:

  1. 缓存兜底:返回最近的成功响应,即使数据可能过时
  2. 简化流程:跳过非关键步骤(如不检查库存直接推荐)
  3. 功能降级:禁用依赖该 API 的功能模块
  4. 默认值返回:提供合理的默认值(如 ” 库存状态未知 ”)

关键是在设计之初就考虑这些场景,实现 ” 有损服务 ” 比完全不可用更好。

总结

构建生产级的 AI Function Call 需要关注:

  1. 可靠性:通过重试、限流等机制保证
  2. 安全性:数据保护和幂等性处理
  3. 可观测性:完善的监控体系
  4. 弹性设计:优雅降级能力

本文提供的代码模板可以直接集成到你的项目中,帮助你快速实现稳定、高效的 AI 工具调用。

正文完
 0
评论(没有评论)