AI Function Call 工具调用实战:从入门到生产级代码案例

1次阅读
没有评论

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

image.webp

1. 为什么我们需要更好的调用方式?

刚开始用 AI 服务的 API 时,我经常遇到这些头疼问题:

AI Function Call 工具调用实战:从入门到生产级代码案例

  • 明明测试环境跑得好好的,一上线就莫名失败
  • 网络稍微波动就直接报错,连重试机会都没有
  • 返回的 JSON 结构五花八门,解析时各种类型错误
  • 根本不知道服务到底稳不稳定,全凭用户投诉才发现问题

后来和团队其他成员交流后发现,大家都踩过类似的坑。裸调 API 就像不带防护装备攀岩——也许能到山顶,但随时可能摔得很惨。

2. 两种武器选择:原始 HTTP vs 封装 SDK

2.1 原始 HTTP 调用的痛

// 典型的裸调用示例
const response = await fetch('https://api.ai-service.com/v1/chat', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${API_KEY}`
  },
  body: JSON.stringify({prompt: 'Hello world'})
});

const data = await response.json(); // 危险!没有错误处理 

这种写法至少有三大隐患:

  1. 网络问题直接导致程序崩溃
  2. 没有重试机制
  3. 响应数据结构完全不可控

2.2 推荐方案:类型安全封装

我们的改进目标是:

  • 自动重试网络错误
  • 严格的输入输出类型检查
  • 生产环境所需的监控能力

3. 手把手实现健壮调用

3.1 基础封装(带重试)

// 核心请求函数
async function callWithRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  let lastError: unknown;

  for (let i = 0; i < maxRetries; i++) {
    try {return await fn();
    } catch (error) {
      lastError = error;
      // 指数退避:1s, 2s, 4s...
      await new Promise(r => setTimeout(r, 1000 * 2 ** i));
    }
  }

  throw lastError;
}

// 使用示例
const safeFetch = async (prompt: string) => {return callWithRetry(async () => {
    const response = await fetch(API_ENDPOINT, {/* 配置同上 */});

    if (!response.ok) {throw new Error(`HTTP error! status: ${response.status}`);
    }

    return response.json();});
};

3.2 用 Zod 做响应验证

import {z} from 'zod';

// 定义响应结构
const AIChatResponseSchema = z.object({id: z.string(),
  choices: z.array(
    z.object({message: z.string(),
      index: z.number(),
      finish_reason: z.string().optional()
    })
  ),
  usage: z.object({prompt_tokens: z.number(),
    completion_tokens: z.number()})
});

// 安全的响应处理器
async function getAIResponse(prompt: string) {const raw = await safeFetch(prompt);
  return AIChatResponseSchema.parse(raw); // 自动校验类型
}

4. 生产环境必备功能

4.1 超时控制

// 给 fetch 添加超时
function fetchWithTimeout(url: string, opts: RequestInit, timeout = 5000) {const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  return fetch(url, {
    ...opts,
    signal: controller.signal
  }).finally(() => clearTimeout(timeoutId));
}

4.2 监控埋点

// 简单监控示例
async function trackedFetch(prompt: string) {const start = Date.now();
  let success = false;

  try {const result = await getAIResponse(prompt);
    success = true;
    return result;
  } finally {
    // 上报到监控系统
    reportMetrics({
      operation: 'ai_chat',
      duration: Date.now() - start,
      success,
      timestamp: new Date().toISOString()
    });
  }
}

5. 避坑经验分享

5.1 错误码处理

常见需要特殊处理的错误:

  • 429 Too Many Requests:需要实现速率限制
  • 401 Unauthorized:检查密钥轮换
  • 503 Service Unavailable:触发熔断机制

5.2 安全注意事项

敏感信息过滤示例:

function sanitizeLog(data: any) {const clone = { ...data};
  if (clone.headers?.Authorization) {clone.headers.Authorization = '***REDACTED***';}
  return clone;
}

6. 思考与延伸

如果服务需要支持多地域部署,网关设计要考虑:

  1. 如何做智能路由(选择延迟最低的可用区)
  2. 跨地域的容灾切换策略
  3. 统一的监控指标聚合

一个可能的架构:

graph LR
    A[客户端] --> B[全局负载均衡]
    B --> C[区域代理 1]
    B --> D[区域代理 2]
    C --> E[AI 服务集群 A]
    D --> F[AI 服务集群 B]

7. 我的实践心得

这套方案在我们团队落地后,AI 服务的可用性从 92% 提升到了 99.8%。最关键的是当出现问题时,我们能通过监控指标快速定位到是网络问题、服务过载还是业务逻辑错误。

建议从简单封装开始逐步迭代,先确保基本可用性,再完善高级功能。记住:没有完美的方案,只有最适合当前业务阶段的解决方案。

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