欢迎光临
我们一直在努力

Claude API + DeepSeek API 双轨实战:流式响应、错误处理与多模型编排

2026 年,AI API 已经不是「调一个模型」这么简单。你需要处理流式响应、应对 Rate Limit、在多个模型之间做 Fallback、控制 Token 消耗——这些才是真正让 API 集成「能用」和「好用」的区别。本文以 Claude API(Anthropic)和 DeepSeek API 为主角,覆盖从单次调用到生产级多模型编排的完整链路。

目录:

  • Claude API vs DeepSeek API 全景对比
  • 流式响应处理
  • 错误处理与重试策略
  • Rate Limit 应对策略
  • 双模型 Fallback 架构
  • Tool Use 与 Function Calling
  • Token 预算管理
  • 生产级 SDK 封装
  • 成本优化实战
  • 常见坑点排查

  • 1. Claude API vs DeepSeek API 全景对比

    1.1 定价与能力

    维度Claude (Anthropic)DeepSeekOpenAI
    最新模型 Fable 5 (Opus 4.8 / Sonnet 4.6 / Haiku 4.5) DeepSeek-V4-Pro GPT-5
    输入价格(每 1M token) $15 (Opus) / $3 (Sonnet) / $0.80 (Haiku) ¥2 (约 $0.27) $15 (GPT-5)
    输出价格(每 1M token) $75 (Opus) / $15 (Sonnet) / $4 (Haiku) ¥8 (约 $1.10) $60 (GPT-5)
    最大上下文 200K tokens 128K tokens 128K tokens
    最大输出 8K-32K tokens 8K tokens 16K tokens
    Tool Use ✅ 原生 ✅ 兼容 OpenAI 格式 ✅ 原生
    流式响应 SSE (Server-Sent Events) SSE (OpenAI 兼容) SSE
    图片理解 ✅ 多模态 ❌ (纯文本) ✅ 多模态
    代码执行 ✅ Computer Use ✅ Code Interpreter
    思考模式 ✅ Extended Thinking
    API 格式 Anthropic Messages API OpenAI 兼容 OpenAI API
    SDK 语言 Python, TypeScript Python, 兼容 OpenAI SDK Python, TypeScript

    1.2 什么时候用哪个模型

    需要深度推理、多步骤任务?
    ├── YES → Claude Opus (30w+ context, Extended Thinking)
    └── NO → 需要多模态(图片理解)?
    ├── YES → Claude Sonnet / GPT-5
    └── NO → 预算敏感?
    ├── YES → DeepSeek(1/10 的价格,中文优秀)
    └── NO → 需要超长输出 → Claude Opus
    └── 常规任务 → Claude Sonnet

    DeepSeek 的最佳场景:

    • 中文任务效果接近甚至超过 GPT-5,价格是 1/55
    • 大批量文本处理(总结、翻译、分类)
    • 作为 Fallback 备选(便宜,不怕用户多)

    Claude 的最佳场景:

    • 代码生成和理解(特别是复杂架构)
    • 需要 Tool Use 的 Agent 场景
    • 需要 Extended Thinking 的深度推理
    • 多模态(图片+文本混合输入)

    1.3 统一接口设计

    在实际项目中,你绝不会只用一个模型。下面是一个统一的调用接口:

    // types.ts
    export type AIProvider = 'claude' | 'deepseek' | 'openai';

    export interface ChatMessage {
    role: 'user' | 'assistant' | 'system';
    content: string | ContentBlock[];
    }

    export interface ContentBlock {
    type: 'text' | 'image';
    text?: string;
    source?: {
    type: 'base64';
    media_type: string;
    data: string;
    };
    }

    export interface ChatOptions {
    model?: string;
    maxTokens?: number;
    temperature?: number;
    stream?: boolean;
    tools?: ToolDefinition[];
    systemPrompt?: string;
    }

    export interface ChatResponse {
    id: string;
    content: string;
    model: string;
    usage: {
    inputTokens: number;
    outputTokens: number;
    };
    finishReason: 'stop' | 'length' | 'tool_use';
    toolCalls?: ToolCall[];
    }

    export interface ToolDefinition {
    name: string;
    description: string;
    parameters: Record<string, any>; // JSON Schema
    }

    export interface ToolCall {
    id: string;
    name: string;
    arguments: Record<string, any>;
    }


    2. 流式响应处理

    2.1 为什么必须用流式

    非流式(stream: false)请求的问题:

    用户 → API → [等待 5-15 秒…] → 一次性返回完整结果

    用户已经不耐烦了

    流式请求:

    用户 → API → "我" → "来" → "帮" → "你" → …
    ↑ 0.5s ↑ 0.3s ↑ 0.2s
    用户立刻看到打字效果,体验天差地别

    2.2 Claude 流式响应

    // claude-stream.service.ts
    import Anthropic from '@anthropic-ai/sdk';
    import { Observable } from 'rxjs';

    interface StreamEvent {
    type: 'text' | 'thinking' | 'tool_use' | 'error' | 'done';
    content?: string;
    toolCall?: Partial<ToolCall>;
    usage?: { inputTokens: number; outputTokens: number };
    }

    export class ClaudeStreamService {
    private client: Anthropic;

    constructor() {
    this.client = new Anthropic({
    apiKey: process.env.ANTHROPIC_API_KEY!,
    });
    }

    /**
    * 流式调用 Claude,返回 Observable
    */

    streamChat(options: {
    messages: Anthropic.MessageParam[];
    model?: string;
    system?: string;
    maxTokens?: number;
    tools?: Anthropic.Tool[];
    }): Observable<StreamEvent> {
    return new Observable((subscriber) => {
    // 标记是否已取消
    let aborted = false;

    (async () => {
    try {
    const stream = this.client.messages.stream({
    model: options.model || 'claude-sonnet-4-6',
    system: options.system,
    messages: options.messages,
    max_tokens: options.maxTokens || 4096,
    tools: options.tools as any,
    });

    // 监听各种事件
    stream.on('text', (text) => {
    if (!aborted) {
    subscriber.next({ type: 'text', content: text });
    }
    });

    stream.on('thinking', (thinking) => {
    if (!aborted) {
    subscriber.next({
    type: 'thinking',
    content: thinking.thinking,
    });
    }
    });

    stream.on('contentBlockStart', (block) => {
    if (block.content_block.type === 'tool_use' && !aborted) {
    subscriber.next({
    type: 'tool_use',
    toolCall: {
    id: block.content_block.id,
    name: block.content_block.name,
    },
    });
    }
    });

    stream.on('contentBlockDelta', (delta) => {
    if (delta.delta.type === 'input_json_delta' && !aborted) {
    subscriber.next({
    type: 'tool_use',
    toolCall: {
    arguments: delta.delta.partial_json,
    },
    });
    }
    });

    // 等待流结束
    const finalMessage = await stream.finalMessage();
    if (!aborted) {
    subscriber.next({
    type: 'done',
    usage: {
    inputTokens: finalMessage.usage.input_tokens,
    outputTokens: finalMessage.usage.output_tokens,
    },
    });
    subscriber.complete();
    }
    } catch (error) {
    if (!aborted) {
    subscriber.next({
    type: 'error',
    content: this.formatError(error),
    });
    subscriber.error(error);
    }
    }
    })();

    // 返回取消函数
    return () => {
    aborted = true;
    };
    });
    }

    private formatError(error: any): string {
    if (error instanceof Anthropic.APIError) {
    return `Claude API 错误 [${error.status}]: ${error.message}`;
    }
    return `未知错误: ${error.message || error}`;
    }
    }

    2.3 DeepSeek 流式响应(OpenAI 兼容)

    // deepseek-stream.service.ts
    import OpenAI from 'openai';

    export class DeepSeekStreamService {
    private client: OpenAI;

    constructor() {
    this.client = new OpenAI({
    apiKey: process.env.DEEPSEEK_API_KEY!,
    baseURL: 'https://api.deepseek.com/v1',
    });
    }

    async *streamChat(options: {
    messages: OpenAI.ChatCompletionMessageParam[];
    model?: string;
    maxTokens?: number;
    temperature?: number;
    }): AsyncGenerator<StreamEvent> {
    try {
    const stream = await this.client.chat.completions.create({
    model: options.model || 'deepseek-chat',
    messages: options.messages,
    max_tokens: options.maxTokens || 4096,
    temperature: options.temperature ?? 0.7,
    stream: true,
    });

    let fullContent = '';

    for await (const chunk of stream) {
    const delta = chunk.choices[0]?.delta;

    if (delta?.content) {
    fullContent += delta.content;
    yield { type: 'text', content: delta.content };
    }

    // DeepSeek 的 tool_calls 也在流中
    if (delta?.tool_calls) {
    for (const tc of delta.tool_calls) {
    yield {
    type: 'tool_use',
    toolCall: {
    id: tc.id,
    name: tc.function?.name,
    arguments: tc.function?.arguments
    ? JSON.parse(tc.function.arguments)
    : undefined,
    },
    };
    }
    }

    // 最后一个 chunk 有 usage
    if (chunk.choices[0]?.finish_reason) {
    yield {
    type: 'done',
    usage: {
    inputTokens: chunk.usage?.prompt_tokens || 0,
    outputTokens: chunk.usage?.completion_tokens || 0,
    },
    };
    }
    }
    } catch (error) {
    yield {
    type: 'error',
    content: error instanceof OpenAI.APIError
    ? `DeepSeek API 错误 [${error.status}]: ${error.message}`
    : `DeepSeek 错误: ${error.message}`,
    };
    }
    }

    /**
    * 非流式调用(适合批量处理)
    */

    async chat(options: {
    messages: OpenAI.ChatCompletionMessageParam[];
    model?: string;
    maxTokens?: number;
    temperature?: number;
    responseFormat?: { type: 'json_object' };
    }): Promise<ChatResponse> {
    const response = await this.client.chat.completions.create({
    model: options.model || 'deepseek-chat',
    messages: options.messages,
    max_tokens: options.maxTokens || 4096,
    temperature: options.temperature ?? 0.7,
    response_format: options.responseFormat,
    });

    return {
    id: response.id,
    content: response.choices[0]?.message?.content || '',
    model: response.model,
    usage: {
    inputTokens: response.usage?.prompt_tokens || 0,
    outputTokens: response.usage?.completion_tokens || 0,
    },
    finishReason: response.choices[0]?.finish_reason as any || 'stop',
    };
    }
    }

    2.4 SSE 推送到前端

    // stream.controller.ts (NestJS)
    import { Controller, Post, Body, Res, Req } from '@nestjs/common';
    import { Response, Request } from 'express';

    @Controller('api/chat')
    export class ChatController {
    constructor(
    private unifiedService: UnifiedAIService,
    ) {}

    @Post('stream')
    async streamChat(
    @Body() body: { messages: ChatMessage[]; provider?: AIProvider },
    @Req() req: Request,
    @Res() res: Response,
    ) {
    // 设置 SSE 头
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');
    res.setHeader('X-Accel-Buffering', 'no'); // 禁用 Nginx 缓冲

    // 监听客户端断开
    req.on('close', () => {
    res.end();
    });

    try {
    const stream = this.unifiedService.streamChat(body.messages, body.provider);

    for await (const event of stream) {
    if (res.destroyed) break; // 客户端断开则停止

    // SSE 格式:data: JSON\\n\\n
    res.write(`data: ${JSON.stringify(event)}\\n\\n`);

    if (event.type === 'done' || event.type === 'error') {
    break;
    }
    }
    } catch (error) {
    res.write(`data: ${JSON.stringify({ type: 'error', content: error.message })}\\n\\n`);
    } finally {
    res.end();
    }
    }
    }

    前端消费 SSE:

    // useStreamChat.ts
    export function useStreamChat() {
    const [content, setContent] = useState('');
    const [isStreaming, setIsStreaming] = useState(false);
    const abortRef = useRef<AbortController | null>(null);

    const sendMessage = async (messages: ChatMessage[], provider?: string) => {
    setIsStreaming(true);
    setContent('');

    const controller = new AbortController();
    abortRef.current = controller;

    try {
    const response = await fetch('/api/chat/stream', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ messages, provider }),
    signal: controller.signal,
    });

    const reader = response.body?.getReader();
    if (!reader) throw new Error('No reader');

    const decoder = new TextDecoder();
    let buffer = '';

    while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    buffer += decoder.decode(value, { stream: true });
    const lines = buffer.split('\\n');
    buffer = lines.pop() || '';

    for (const line of lines) {
    if (line.startsWith('data: ')) {
    const data = JSON.parse(line.slice(6));
    if (data.type === 'text') {
    setContent(prev => prev + data.content);
    } else if (data.type === 'done') {
    setIsStreaming(false);
    } else if (data.type === 'error') {
    throw new Error(data.content);
    }
    }
    }
    }
    } catch (error: any) {
    if (error.name !== 'AbortError') {
    setContent(prev => prev + `\\n\\n❌ 错误: ${error.message}`);
    }
    setIsStreaming(false);
    }
    };

    const abort = () => {
    abortRef.current?.abort();
    setIsStreaming(false);
    };

    return { content, isStreaming, sendMessage, abort };
    }


    3. 错误处理与重试策略

    3.1 常见错误类型

    // error-handler.ts
    export enum AIErrorType {
    RATE_LIMIT = 'rate_limit', // 429: 请求太频繁
    AUTH_ERROR = 'auth_error', // 401: API Key 无效
    SERVER_ERROR = 'server_error', // 5xx: 服务端临时故障
    TIMEOUT = 'timeout', // 请求超时
    CONTEXT_OVERFLOW = 'context_overflow', // 输入超过上下文限制
    CONTENT_FILTER = 'content_filter', // 内容被安全过滤
    NETWORK_ERROR = 'network_error', // 网络断开
    UNKNOWN = 'unknown',
    }

    export class AIError extends Error {
    constructor(
    public type: AIErrorType,
    public statusCode?: number,
    public originalError?: any,
    ) {
    super(originalError?.message || 'Unknown AI error');
    this.name = 'AIError';
    }

    get isRetryable(): boolean {
    // 这些错误重试可能成功
    return [
    AIErrorType.RATE_LIMIT,
    AIErrorType.SERVER_ERROR,
    AIErrorType.TIMEOUT,
    AIErrorType.NETWORK_ERROR,
    ].includes(this.type);
    }
    }

    export function classifyError(error: any): AIError {
    // Claude API 错误
    if (error?.status === 429) {
    return new AIError(AIErrorType.RATE_LIMIT, 429, error);
    }
    if (error?.status === 401 || error?.status === 403) {
    return new AIError(AIErrorType.AUTH_ERROR, error.status, error);
    }
    if (error?.status && error.status >= 500) {
    return new AIError(AIErrorType.SERVER_ERROR, error.status, error);
    }

    // OpenAI / DeepSeek 错误
    if (error?.status === 429 || error?.code === 'rate_limit_exceeded') {
    return new AIError(AIErrorType.RATE_LIMIT, 429, error);
    }
    if (error?.code === 'context_length_exceeded') {
    return new AIError(AIErrorType.CONTEXT_OVERFLOW, 400, error);
    }
    if (error?.code === 'content_filter') {
    return new AIError(AIErrorType.CONTENT_FILTER, 400, error);
    }

    // 网络错误
    if (error?.code === 'ECONNRESET' || error?.code === 'ETIMEDOUT' || error?.name === 'AbortError') {
    return new AIError(AIErrorType.NETWORK_ERROR, undefined, error);
    }

    return new AIError(AIErrorType.UNKNOWN, undefined, error);
    }

    3.2 指数退避重试器

    // retry.service.ts
    import { Logger } from '@nestjs/common';
    import { classifyError, AIError, AIErrorType } from './error-handler';

    interface RetryConfig {
    maxRetries: number;
    baseDelayMs: number;
    maxDelayMs: number;
    jitter: boolean;
    retryableErrors: AIErrorType[];
    }

    const DEFAULT_RETRY_CONFIG: RetryConfig = {
    maxRetries: 3,
    baseDelayMs: 1000,
    maxDelayMs: 30000,
    jitter: true,
    retryableErrors: [
    AIErrorType.RATE_LIMIT,
    AIErrorType.SERVER_ERROR,
    AIErrorType.TIMEOUT,
    AIErrorType.NETWORK_ERROR,
    ],
    };

    export class RetryService {
    private readonly logger = new Logger(RetryService.name);

    /**
    * 带指数退避的执行器
    *
    * 退避公式:min(baseDelay * 2^attempt + jitter, maxDelay)
    * 第 1 次重试:~1-2s
    * 第 2 次重试:~2-4s
    * 第 3 次重试:~4-8s
    */

    async withRetry<T>(
    fn: () => Promise<T>,
    config: Partial<RetryConfig> = {},
    ): Promise<T> {
    const cfg = { DEFAULT_RETRY_CONFIG, config };
    let lastError: AIError | null = null;

    for (let attempt = 0; attempt <= cfg.maxRetries; attempt++) {
    try {
    if (attempt > 0) {
    this.logger.log(`重试第 ${attempt}/${cfg.maxRetries} 次…`);
    }
    return await fn();
    } catch (error) {
    const aiError = classifyError(error);
    lastError = aiError;

    // 最后一次尝试不再重试
    if (attempt >= cfg.maxRetries) {
    break;
    }

    // 不可重试的错误,直接抛
    if (!cfg.retryableErrors.includes(aiError.type)) {
    throw aiError;
    }

    // 计算延迟
    const delay = this.calculateDelay(attempt, cfg);
    this.logger.warn(
    `${aiError.type} 错误,${delay}ms 后重试 (第 ${attempt + 1} 次)`,
    );

    // 对于 Rate Limit,优先使用 Retry-After 头
    const retryAfter = this.getRetryAfter(error);
    const actualDelay = retryAfter
    ? Math.max(delay, retryAfter * 1000)
    : delay;

    await this.sleep(actualDelay);
    }
    }

    throw lastError || new Error('Unknown retry error');
    }

    private calculateDelay(attempt: number, config: RetryConfig): number {
    // 指数退避
    let delay = config.baseDelayMs * Math.pow(2, attempt);

    // 上限
    delay = Math.min(delay, config.maxDelayMs);

    // 抖动(避免惊群效应)
    if (config.jitter) {
    delay = delay * (0.5 + Math.random() * 0.5);
    }

    return Math.round(delay);
    }

    private getRetryAfter(error: any): number | null {
    // Anthropic: retry-after 头
    if (error?.headers?.['retry-after']) {
    return parseInt(error.headers['retry-after']);
    }
    // OpenAI: retry-after-ms 头
    if (error?.headers?.['retry-after-ms']) {
    return parseInt(error.headers['retry-after-ms']) / 1000;
    }
    return null;
    }

    private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
    }
    }

    3.3 错误降级策略

    // fallback-strategy.ts
    /**
    * 错误降级决策树
    *
    * Rate Limit → 换模型重试 (Claude → DeepSeek)
    * Server Error → 换模型重试
    * Context Overflow → 截断历史消息重试
    * Content Filter → 返回友好提示,不重试
    * Auth Error → 告警,不重试
    * Timeout → 换更快的模型重试
    */

    export function getFallbackAction(error: AIError): FallbackAction {
    switch (error.type) {
    case AIErrorType.RATE_LIMIT:
    return { action: 'switch_provider', reason: '当前模型限流' };

    case AIErrorType.SERVER_ERROR:
    return { action: 'switch_provider', reason: '服务端故障' };

    case AIErrorType.CONTEXT_OVERFLOW:
    return { action: 'truncate', reason: '上下文超长,尝试截断' };

    case AIErrorType.TIMEOUT:
    return { action: 'switch_to_faster', reason: '超时,换更快模型' };

    case AIErrorType.CONTENT_FILTER:
    return { action: 'reject', reason: '内容不符合安全策略' };

    case AIErrorType.AUTH_ERROR:
    return { action: 'alert', reason: 'API Key 配置错误' };

    default:
    return { action: 'switch_provider', reason: '未知错误,切换模型' };
    }
    }

    interface FallbackAction {
    action: 'switch_provider' | 'switch_to_faster' | 'truncate' | 'reject' | 'alert';
    reason: string;
    }


    4. Rate Limit 应对策略

    4.1 各平台的限流规则

    平台免费/试用付费 Tier 1Tier 2+
    Anthropic 5 RPM / 20K TPM 50 RPM / 50K TPM 1,000+ RPM
    DeepSeek 50 RPM / 50K TPM 500 RPM / 1M TPM 联系客服
    OpenAI 3 RPM / 40K TPM 500 RPM / 450K TPM 5,000+ RPM

    4.2 令牌桶限流器

    // token-bucket.ts
    /**
    * 令牌桶算法实现
    *
    * 原理:
    * – 桶以固定速率(rate)填充令牌
    * – 每次请求消耗一个令牌
    * – 令牌用完则等待或拒绝
    */

    export class TokenBucket {
    private tokens: number;
    private lastRefill: number;

    constructor(
    private rate: number, // 每秒补充的令牌数
    private capacity: number, // 桶容量(最大令牌数)
    ) {
    this.tokens = capacity;
    this.lastRefill = Date.now();
    }

    /**
    * 尝试获取令牌
    * @returns 需要等待的毫秒数,0 表示可以立即执行
    */

    tryConsume(): { allowed: boolean; waitMs: number } {
    this.refill();

    if (this.tokens >= 1) {
    this.tokens -= 1;
    return { allowed: true, waitMs: 0 };
    }

    // 计算需要等多久才能获得一个令牌
    const waitMs = Math.ceil((1 this.tokens) / this.rate * 1000);
    return { allowed: false, waitMs };
    }

    /**
    * 等待直到可以执行
    */

    async consume(): Promise<void> {
    const { allowed, waitMs } = this.tryConsume();
    if (!allowed) {
    await new Promise(resolve => setTimeout(resolve, waitMs));
    return this.consume();
    }
    }

    private refill(): void {
    const now = Date.now();
    const elapsed = (now this.lastRefill) / 1000; // 秒
    this.tokens = Math.min(
    this.capacity,
    this.tokens + elapsed * this.rate,
    );
    this.lastRefill = now;
    }
    }

    4.3 请求队列管理器

    // request-queue.ts
    import { Logger } from '@nestjs/common';

    interface QueuedRequest<T> {
    id: string;
    priority: number; // 0=最高, 9=最低
    execute: () => Promise<T>;
    resolve: (value: T) => void;
    reject: (error: any) => void;
    createdAt: number;
    }

    export class AIRequestQueue {
    private readonly logger = new Logger(AIRequestQueue.name);
    private queues: Map<string, QueuedRequest<any>[]> = new Map();
    private processing = new Set<string>();
    private maxConcurrent = 5; // 默认最大并发数

    constructor(
    private bucket: TokenBucket,
    maxConcurrent?: number,
    ) {
    if (maxConcurrent) this.maxConcurrent = maxConcurrent;
    }

    async enqueue<T>(
    provider: string,
    execute: () => Promise<T>,
    priority: number = 5,
    ): Promise<T> {
    return new Promise<T>((resolve, reject) => {
    const request: QueuedRequest<T> = {
    id: `${provider}${Date.now()}${Math.random().toString(36).slice(2, 8)}`,
    priority,
    execute,
    resolve,
    reject,
    createdAt: Date.now(),
    };

    // 加入队列
    if (!this.queues.has(provider)) {
    this.queues.set(provider, []);
    }
    const queue = this.queues.get(provider)!;

    // 按优先级插入(低数字 = 高优先级)
    const insertIndex = queue.findIndex(r => r.priority > priority);
    if (insertIndex === 1) {
    queue.push(request);
    } else {
    queue.splice(insertIndex, 0, request);
    }

    this.logger.debug(
    `${provider} 队列: ${queue.length} 个待处理, 优先级 ${priority}`,
    );

    // 触发处理
    this.processQueue(provider);
    });
    }

    private async processQueue(provider: string): Promise<void> {
    if (this.processing.has(provider)) return;

    const queue = this.queues.get(provider);
    if (!queue || queue.length === 0) return;

    this.processing.add(provider);

    try {
    while (queue.length > 0) {
    // 并发控制
    if (this.processing.size > this.maxConcurrent) {
    break;
    }

    // 等待令牌
    await this.bucket.consume();

    const request = queue.shift();
    if (!request) break;

    // 超时检查(超过 60 秒的排队请求直接拒绝)
    if (Date.now() request.createdAt > 60000) {
    request.reject(new Error('请求排队超时'));
    continue;
    }

    try {
    const result = await request.execute();
    request.resolve(result);
    } catch (error) {
    request.reject(error);
    }
    }
    } finally {
    this.processing.delete(provider);

    // 如果队列还有剩余,继续处理
    if (queue.length > 0) {
    setTimeout(() => this.processQueue(provider), 100);
    }
    }
    }
    }


    5. 双模型 Fallback 架构

    5.1 统一 AI 服务

    // unified-ai.service.ts
    import { Injectable, Logger } from '@nestjs/common';
    import { RetryService } from './retry.service';
    import { AIRequestQueue } from './request-queue';
    import { TokenBucket } from './token-bucket';
    import { classifyError, AIError } from './error-handler';

    interface ProviderConfig {
    name: string;
    priority: number; // 越小越优先
    models: string[];
    bucket: TokenBucket;
    maxContextLength: number;
    supportsVision: boolean;
    supportsToolUse: boolean;
    }

    @Injectable()
    export class UnifiedAIService {
    private readonly logger = new Logger(UnifiedAIService.name);
    private providers: Map<string, ProviderConfig>;
    private queues: Map<string, AIRequestQueue>;

    constructor(private retry: RetryService) {
    // 初始化提供商
    this.providers = new Map([
    ['claude-opus', {
    name: 'claude-opus',
    priority: 0,
    models: ['claude-opus-4-8'],
    bucket: new TokenBucket(0.8, 5), // 每秒 0.8 个请求,最多 5 个并发
    maxContextLength: 200000,
    supportsVision: true,
    supportsToolUse: true,
    }],
    ['claude-sonnet', {
    name: 'claude-sonnet',
    priority: 1,
    models: ['claude-sonnet-4-6'],
    bucket: new TokenBucket(2, 10),
    maxContextLength: 200000,
    supportsVision: true,
    supportsToolUse: true,
    }],
    ['deepseek', {
    name: 'deepseek',
    priority: 2,
    models: ['deepseek-chat'],
    bucket: new TokenBucket(5, 20), // DeepSeek 限流较宽松
    maxContextLength: 128000,
    supportsVision: false,
    supportsToolUse: true,
    }],
    ]);

    // 为每个提供商创建请求队列
    this.queues = new Map();
    for (const [name, config] of this.providers) {
    this.queues.set(name, new AIRequestQueue(config.bucket));
    }
    }

    /**
    * 智能调用:自动选择最佳模型,失败时自动降级
    */

    async chat(options: {
    messages: any[];
    preferredProvider?: string;
    hasImages?: boolean;
    needsToolUse?: boolean;
    maxTokens?: number;
    }): Promise<{ content: string; provider: string; model: string; usage: any }> {
    const errors: Array<{ provider: string; error: string }> = [];

    // 构建候选列表(按优先级排序)
    const candidates = this.getCandidates(options);

    for (const candidate of candidates) {
    try {
    this.logger.log(`尝试 ${candidate.name} (${candidate.model})…`);

    const result = await this.retry.withRetry(
    () => this.callProvider(candidate, options.messages, options.maxTokens),
    { maxRetries: 2 },
    );

    this.logger.log(`${candidate.name} 成功`);
    return {
    content: result.content,
    provider: candidate.name,
    model: candidate.model,
    usage: result.usage,
    };
    } catch (error) {
    const aiError = classifyError(error);
    errors.push({
    provider: candidate.name,
    error: `${aiError.type}: ${aiError.message}`,
    });
    this.logger.warn(`${candidate.name} 失败: ${aiError.type}`);

    // Content Filter 不换模型重试(换了也一样被拦截)
    if (aiError.type === 'content_filter') {
    throw new Error('内容不符合安全策略,请修改后重试');
    }
    }
    }

    // 所有提供商都失败了
    this.logger.error('所有 AI 提供商调用失败', errors);
    throw new Error(
    `所有 AI 提供商调用失败:\\n${errors.map(e => `${e.provider}: ${e.error}`).join('\\n')}`,
    );
    }

    /**
    * 流式版本(带 Fallback)
    */

    async *streamChat(options: {
    messages: any[];
    preferredProvider?: string;
    hasImages?: boolean;
    needsToolUse?: boolean;
    }): AsyncGenerator<any> {
    const candidates = this.getCandidates(options);

    for (const candidate of candidates) {
    try {
    this.logger.log(`流式调用 ${candidate.name}`);
    const stream = this.callProviderStream(candidate, options.messages);

    for await (const event of stream) {
    yield event;
    }
    return; // 成功则结束
    } catch (error) {
    this.logger.warn(`流式 ${candidate.name} 失败,尝试下一个…`);
    // 流式失败继续下一个候选
    }
    }

    yield {
    type: 'error',
    content: '所有 AI 提供商调用失败,请稍后重试',
    };
    }

    private getCandidates(options: {
    preferredProvider?: string;
    hasImages?: boolean;
    needsToolUse?: boolean;
    }): Array<{ name: string; model: string }> {
    // 按优先级排序所有提供商
    const sorted = Array.from(this.providers.values())
    .sort((a, b) => a.priority b.priority);

    // 如果指定了首选,把它排到最前
    if (options.preferredProvider) {
    const preferred = this.providers.get(options.preferredProvider);
    if (preferred) {
    const others = sorted.filter(p => p.name !== preferred.name);
    sorted.splice(0, sorted.length, preferred, others);
    }
    }

    return sorted
    .filter((p) => {
    // 有图片只能用 Claude
    if (options.hasImages && !p.supportsVision) return false;
    // 需要 Tool Use 的排除不支持的服务
    if (options.needsToolUse && !p.supportsToolUse) return false;
    return true;
    })
    .map(p => ({ name: p.name, model: p.models[0] }));
    }

    private async callProvider(
    candidate: { name: string; model: string },
    messages: any[],
    maxTokens?: number,
    ): Promise<{ content: string; usage: any }> {
    // 通过队列执行(自动限流)
    const queue = this.queues.get(candidate.name)!;
    return queue.enqueue(candidate.name, async () => {
    // 实际调用逻辑(根据 candidate.name 分派到不同 SDK)
    return this.dispatchCall(candidate, messages, maxTokens);
    });
    }

    private async dispatchCall(
    candidate: { name: string; model: string },
    messages: any[],
    maxTokens?: number,
    ): Promise<{ content: string; usage: any }> {
    // 根据 provider 名称分派
    switch (candidate.name) {
    case 'claude-opus':
    case 'claude-sonnet':
    return this.callClaude(candidate.model, messages, maxTokens);
    case 'deepseek':
    return this.callDeepSeek(candidate.model, messages, maxTokens);
    default:
    throw new Error(`Unknown provider: ${candidate.name}`);
    }
    }

    // … callClaude / callDeepSeek 实现
    }

    5.2 负载均衡与成本感知路由

    // cost-aware-router.ts
    /**
    * 成本感知路由器
    *
    * 根据任务复杂度自动选择性价比最高的模型:
    * – 简单任务(分类、翻译、摘要)→ DeepSeek
    * – 中等任务(问答、生成)→ Claude Sonnet
    * – 复杂任务(架构设计、深度推理)→ Claude Opus
    */

    export class CostAwareRouter {
    /**
    * 估算任务复杂度
    */

    estimateComplexity(messages: any[]): 'simple' | 'medium' | 'complex' {
    const userMessage = messages.filter(m => m.role === 'user').pop();
    const content = typeof userMessage?.content === 'string'
    ? userMessage.content
    : JSON.stringify(userMessage?.content);

    const length = content?.length || 0;

    // 启发式规则
    if (length < 100) return 'simple';
    if (length < 500) return 'medium';

    // 检测复杂度关键词
    const complexKeywords = [
    '架构', '设计', '系统', '方案', '分析', '评估', '审查',
    'architecture', 'design', 'system', 'analyze', 'review',
    ];
    const hasComplexKeyword = complexKeywords.some(kw =>
    content.toLowerCase().includes(kw),
    );
    if (hasComplexKeyword) return 'complex';

    return 'medium';
    }

    /**
    * 根据复杂度选择模型
    */

    selectProvider(complexity: 'simple' | 'medium' | 'complex'): string {
    switch (complexity) {
    case 'simple':
    return 'deepseek'; // 便宜 10 倍,效果够用
    case 'medium':
    return 'claude-sonnet'; // 性价比最佳
    case 'complex':
    return 'claude-opus'; // 最强推理能力
    }
    }
    }


    6. Tool Use 与 Function Calling

    6.1 Claude Tool Use

    // claude-tool-use.service.ts
    import Anthropic from '@anthropic-ai/sdk';

    interface ToolResult {
    toolUseId: string;
    content: string;
    isError?: boolean;
    }

    export class ClaudeToolUseService {
    private client: Anthropic;

    constructor() {
    this.client = new Anthropic({
    apiKey: process.env.ANTHROPIC_API_KEY!,
    });
    }

    /**
    * 带工具的对话循环
    * Claude 可以自主决定何时调用工具、调用哪个工具
    */

    async toolLoop(
    userMessage: string,
    tools: Anthropic.Tool[],
    toolHandlers: Record<string, (args: any) => Promise<string>>,
    systemPrompt?: string,
    ): Promise<string> {
    const messages: Anthropic.MessageParam[] = [
    { role: 'user', content: userMessage },
    ];

    const maxRounds = 10;

    for (let round = 0; round < maxRounds; round++) {
    const response = await this.client.messages.create({
    model: 'claude-sonnet-4-6',
    system: systemPrompt,
    messages,
    tools: tools as any,
    max_tokens: 4096,
    });

    // 检查是否有 tool_use
    const toolUses = response.content.filter(
    (block): block is Anthropic.ToolUseBlock => block.type === 'tool_use',
    );

    if (toolUses.length === 0) {
    // 没有工具调用,直接返回文本
    const textBlock = response.content.find(b => b.type === 'text');
    return (textBlock as Anthropic.TextBlock)?.text || '';
    }

    // 执行工具调用
    const toolResults: ToolResult[] = [];

    for (const toolUse of toolUses) {
    const handler = toolHandlers[toolUse.name];
    if (!handler) {
    toolResults.push({
    toolUseId: toolUse.id,
    content: `Unknown tool: ${toolUse.name}`,
    isError: true,
    });
    continue;
    }

    try {
    const result = await handler(toolUse.input);
    toolResults.push({
    toolUseId: toolUse.id,
    content: result,
    });
    } catch (error) {
    toolResults.push({
    toolUseId: toolUse.id,
    content: `Tool error: ${error.message}`,
    isError: true,
    });
    }
    }

    // 将 assistant 消息和 tool 结果加入对话
    messages.push({
    role: 'assistant',
    content: response.content,
    });

    messages.push({
    role: 'user',
    content: toolResults.map(tr => ({
    type: 'tool_result' as const,
    tool_use_id: tr.toolUseId,
    content: tr.content,
    is_error: tr.isError || false,
    })),
    });

    // 继续下一轮
    }

    return '已达到最大工具调用轮数,任务未完成。';
    }
    }

    6.2 DeepSeek Function Calling

    // deepseek-function-call.service.ts
    import OpenAI from 'openai';

    export class DeepSeekFunctionCallService {
    private client: OpenAI;

    constructor() {
    this.client = new OpenAI({
    apiKey: process.env.DEEPSEEK_API_KEY!,
    baseURL: 'https://api.deepseek.com/v1',
    });
    }

    async functionLoop(
    userMessage: string,
    functions: OpenAI.ChatCompletionCreateParams.Function[],
    handlers: Record<string, (args: any) => Promise<string>>,
    ): Promise<string> {
    const messages: OpenAI.ChatCompletionMessageParam[] = [
    { role: 'user', content: userMessage },
    ];

    for (let round = 0; round < 10; round++) {
    const response = await this.client.chat.completions.create({
    model: 'deepseek-chat',
    messages,
    functions,
    function_call: 'auto',
    });

    const choice = response.choices[0];
    const message = choice.message;

    // 如果没有 function call,返回文本
    if (!message.function_call) {
    return message.content || '';
    }

    // 执行函数
    const funcName = message.function_call.name;
    const handler = handl

    赞(0)
    未经允许不得转载:171主机测评 » Claude API + DeepSeek API 双轨实战:流式响应、错误处理与多模型编排
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址