LLM API 基础调用:OpenAI、Anthropic、Google 全面对比与 FastAPI 服务端封装

深度解析主流 LLM API(OpenAI、Anthropic Claude、Google Gemini)的核心差异:认证方式、流式输出、Token 计数与预算管理、多模态输入、错误重试策略。附带完整的 FastAPI + asyncio 服务端封装代码,支持并发流式推理。

目录

  1. 三大 LLM API 概览与选型
  2. OpenAI API 详解
  3. Anthropic Claude API 详解
  4. Google Gemini API 详解
  5. Token 计数与预算管理
  6. 流式输出实现
  7. 错误处理与重试策略
  8. 多模态输入
  9. FastAPI 服务端封装
  10. 多提供商统一客户端

1. 三大 LLM API 概览与选型

维度OpenAIAnthropic ClaudeGoogle Gemini
旗舰模型GPT-4o / o1Claude 3.5 SonnetGemini 1.5 Pro
上下文长度128K / 200K200K1M tokens
流式输出SSE (server-sent events)SSESSE
结构化输出JSON Schema (response_format)工具调用JSON mode
函数调用✅ Functions / Tools✅ Tool Use✅ Function calling
多模态文本+图像+音频文本+PDF+图像文本+图像+视频+音频
嵌入模型text-embedding-3-small/large无原生text-embedding-004
定价(输入/1M tokens)$2.50 / $5.00$3.00 / $15.00$1.25 / $5.00
Python SDKopenaianthropicgoogle-generativeai

选型建议

  • 通用任务 → GPT-4o(生态最完善,开发者工具链最成熟)
  • 长文档分析 → Gemini 1.5 Pro(1M 上下文,适合视频/大型代码库)
  • 推理质量优先 → Claude 3.5 Sonnet(代码生成与逻辑推理业界领先)

2. OpenAI API 详解

2.1 基础对话

import os
from openai import AsyncOpenAI

client = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY"))

async def chat_completion(
    messages: list[dict],
    model: str = "gpt-4o",
    temperature: float = 0.7,
    max_tokens: int = 4096,
) -> str:
    response = await client.chat.completions.create(
        model=model,
        messages=messages,
        temperature=temperature,
        max_tokens=max_tokens,
    )
    return response.choices[0].message.content

# 使用示例
import asyncio

async def main():
    messages = [
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Explain async/await in Python in 3 sentences."},
    ]
    result = await chat_completion(messages)
    print(result)

asyncio.run(main())

2.2 工具调用(Function Calling)

async def chat_with_tools():
    tools = [
        {
            "type": "function",
            "function": {
                "name": "get_weather",
                "description": "Get current weather for a city",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "city": {"type": "string"},
                        "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                    },
                    "required": ["city"],
                },
            },
        }
    ]

    response = await client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
        tools=tools,
        tool_choice="auto",
    )

    message = response.choices[0].message
    if message.tool_calls:
        for call in message.tool_calls:
            print(f"Function: {call.function.name}")
            print(f"Arguments: {call.function.arguments}")
            # 执行函数并返回结果...

2.3 结构化输出(JSON Schema)

from pydantic import BaseModel

class ExtractedInfo(BaseModel):
    name: str
    age: int
    hobbies: list[str]

async def structured_extract(text: str) -> ExtractedInfo:
    response = await client.beta.chat.completions.parse(
        model="gpt-4o-2024-08-06",
        messages=[
            {"role": "system", "content": "Extract structured info from text."},
            {"role": "user", "content": text},
        ],
        response_format=ExtractedInfo,
    )
    return response.choices[0].message.parsed

3. Anthropic Claude API 详解

3.1 基础对话

import os
from anthropic import AsyncAnthropic

anthropic_client = AsyncAnthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))

async def claude_chat(
    messages: list[dict],
    model: str = "claude-3-5-sonnet-20241022",
    max_tokens: int = 4096,
) -> str:
    response = await anthropic_client.messages.create(
        model=model,
        max_tokens=max_tokens,
        messages=messages,
    )
    return response.content[0].text

# Claude 使用不同消息格式:不含 system 在 messages 中
async def main():
    response = await anthropic_client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1024,
        system="You are a Python expert.",  # system 是独立参数
        messages=[
            {"role": "user", "content": "Explain Python GIL."}
        ],
    )
    print(response.content[0].text)

3.2 Thinking 模式(推理过程可见)

Claude 3.5 支持扩展思考模式,适合复杂推理任务:

async def claude_with_thinking():
    response = await anthropic_client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=4096,
        thinking={"type": "enabled", "budget_tokens": 2000},
        messages=[{"role": "user", "content": "Solve this math problem step by step..."}],
    )
    for block in response.content:
        if block.type == "thinking":
            print(f"[Thinking] {block.thinking}")
        elif block.type == "text":
            print(f"[Answer] {block.text}")

4. Google Gemini API 详解

import google.generativeai as genai

genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))

async def gemini_chat(
    prompt: str,
    model: str = "gemini-1.5-pro-latest",
) -> str:
    model_obj = genai.GenerativeModel(model)
    response = await model_obj.generate_content_async(prompt)
    return response.text

# 多模态输入(图像+文本)
async def gemini_multimodal(image_path: str, prompt: str):
    model_obj = genai.GenerativeModel("gemini-1.5-pro-latest")
    image = PIL.Image.open(image_path)
    response = await model_obj.generate_content_async([prompt, image])
    return response.text

5. Token 计数与预算管理

5.1 tiktoken 精确计数

import tiktoken

def count_tokens(text: str, model: str = "gpt-4o") -> int:
    """使用 tiktoken 精确计算 token 数量。"""
    try:
        encoding = tiktoken.encoding_for_model(model)
    except KeyError:
        encoding = tiktoken.get_encoding("cl100k_base")
    return len(encoding.encode(text))

def estimate_cost(
    input_tokens: int,
    output_tokens: int,
    model: str = "gpt-4o",
) -> float:
    """估算 API 调用成本(美元)。"""
    prices = {
        "gpt-4o": {"input": 2.50, "output": 10.00},
        "gpt-4o-mini": {"input": 0.15, "output": 0.60},
        "claude-3-5-sonnet": {"input": 3.00, "output": 15.00},
        "gemini-1.5-pro": {"input": 1.25, "output": 5.00},
    }
    p = prices.get(model, prices["gpt-4o"])
    # 价格单位是 /1M tokens
    return (input_tokens * p["input"] + output_tokens * p["output"]) / 1_000_000

# 预算管理上下文管理器
from contextlib import asynccontextmanager
from dataclasses import dataclass

@dataclass
class BudgetTracker:
    budget_usd: float = 10.0
    spent_usd: float = 0.0
    total_input_tokens: int = 0
    total_output_tokens: int = 0

    def can_afford(self, estimated_cost: float) -> bool:
        return self.spent_usd + estimated_cost <= self.budget_usd

    def record(self, input_tokens: int, output_tokens: int, model: str):
        cost = estimate_cost(input_tokens, output_tokens, model)
        self.spent_usd += cost
        self.total_input_tokens += input_tokens
        self.total_output_tokens += output_tokens

5.2 API 响应中的 Token 用量

async def chat_with_tracking(
    messages: list[dict],
    tracker: BudgetTracker,
    model: str = "gpt-4o",
) -> str:
    input_tokens = sum(count_tokens(m["content"], model) for m in messages)
    estimated_output = 2048
    est_cost = estimate_cost(input_tokens, estimated_output, model)

    if not tracker.can_afford(est_cost):
        raise RuntimeError(f"Budget exceeded: ${tracker.spent_usd:.4f} / ${tracker.budget_usd}")

    response = await client.chat.completions.create(
        model=model,
        messages=messages,
    )

    usage = response.usage
    tracker.record(usage.prompt_tokens, usage.completion_tokens, model)
    return response.choices[0].message.content

6. 流式输出实现

6.1 OpenAI 流式

async def stream_chat(messages: list[dict], model: str = "gpt-4o"):
    """流式输出,适合长文本生成与实时 UI 更新。"""
    stream = await client.chat.completions.create(
        model=model,
        messages=messages,
        stream=True,
        stream_options={"include_usage": True},
    )

    full_text = ""
    async for chunk in stream:
        if chunk.choices and chunk.choices[0].delta.content:
            text = chunk.choices[0].delta.content
            full_text += text
            print(text, end="", flush=True)
        # 最终 chunk 包含 usage
        if chunk.usage:
            print(f"\n\nTokens: {chunk.usage.total_tokens}")
    return full_text

6.2 FastAPI SSE 流式端点

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
import json

app = FastAPI()

class ChatRequest(BaseModel):
    messages: list[dict]
    model: str = "gpt-4o"
    stream: bool = True

@app.post("/chat")
async def chat_endpoint(req: ChatRequest):
    if not req.stream:
        result = await chat_completion(req.messages, req.model)
        return {"content": result}

    async def event_generator():
        stream = await client.chat.completions.create(
            model=req.model,
            messages=req.messages,
            stream=True,
        )
        async for chunk in stream:
            if chunk.choices and chunk.choices[0].delta.content:
                data = json.dumps({"text": chunk.choices[0].delta.content})
                yield f"data: {data}\n\n"
        yield "data: [DONE]\n\n"

    return StreamingResponse(
        event_generator(),
        media_type="text/event-stream",
    )

7. 错误处理与重试策略

import asyncio
import random
from openai import RateLimitError, APIError, APITimeoutError

async def robust_chat_completion(
    messages: list[dict],
    model: str = "gpt-4o",
    max_retries: int = 5,
    base_delay: float = 1.0,
    max_delay: float = 30.0,
) -> str:
    """带指数退避和抖动的健壮 API 调用。"""
    for attempt in range(max_retries + 1):
        try:
            response = await client.chat.completions.create(
                model=model,
                messages=messages,
            )
            return response.choices[0].message.content

        except RateLimitError as e:
            if attempt == max_retries:
                raise
            delay = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)
            retry_after = e.response.headers.get("retry-after")
            if retry_after:
                delay = float(retry_after)
            print(f"Rate limited, retrying in {delay:.1f}s...")
            await asyncio.sleep(delay)

        except (APITimeoutError, APIError) as e:
            if attempt == max_retries:
                raise
            delay = min(base_delay * (2 ** attempt), max_delay)
            print(f"API error ({e}), retrying in {delay:.1f}s...")
            await asyncio.sleep(delay)

8. 多模态输入

8.1 GPT-4o 视觉

import base64

async def vision_chat(image_path: str, prompt: str):
    with open(image_path, "rb") as f:
        image_b64 = base64.b64encode(f.read()).decode()

    messages = [
        {
            "role": "user",
            "content": [
                {"type": "text", "text": prompt},
                {
                    "type": "image_url",
                    "image_url": {
                        "url": f"data:image/jpeg;base64,{image_b64}",
                        "detail": "high",
                    },
                },
            ],
        }
    ]
    return await chat_completion(messages, model="gpt-4o")

8.2 音频输入(GPT-4o audio)

async def audio_chat(audio_path: str, prompt: str):
    with open(audio_path, "rb") as f:
        audio_b64 = base64.b64encode(f.read()).decode()

    messages = [
        {
            "role": "user",
            "content": [
                {"type": "text", "text": prompt},
                {"type": "input_audio", "input_audio": {"data": audio_b64, "format": "mp3"}},
            ],
        }
    ]
    return await chat_completion(messages, model="gpt-4o-audio-preview")

9. FastAPI 服务端封装

完整生产级服务端,支持多模型路由、流式输出、Token 限制、并发控制:

from fastapi import FastAPI, HTTPException, Depends
from fastapi.responses import StreamingResponse
from pydantic import BaseModel, Field
from typing import Literal, AsyncGenerator
import asyncio
import json
import os
import time

app = FastAPI(title="LLM API Gateway")

# 并发控制
SEM = asyncio.Semaphore(10)

class ChatMessage(BaseModel):
    role: Literal["system", "user", "assistant"]
    content: str

class ChatCompletionRequest(BaseModel):
    messages: list[ChatMessage]
    model: Literal["gpt-4o", "gpt-4o-mini", "claude-3-5-sonnet"] = "gpt-4o"
    temperature: float = Field(default=0.7, ge=0.0, le=2.0)
    max_tokens: int = Field(default=4096, ge=1, le=8192)
    stream: bool = False
    response_format: dict | None = None

    @property
    def total_input_tokens(self) -> int:
        return sum(count_tokens(m.content, self.model) for m in self.messages)

class ChatCompletionResponse(BaseModel):
    content: str
    model: str
    usage: dict
    latency_ms: float

async def get_llm_response(
    req: ChatCompletionRequest,
) -> tuple[str, dict]:
    """路由到对应提供商。"""
    async with SEM:
        if req.model.startswith("gpt"):
            response = await client.chat.completions.create(
                model=req.model,
                messages=[m.model_dump() for m in req.messages],
                temperature=req.temperature,
                max_tokens=req.max_tokens,
                response_format=req.response_format,
            )
            content = response.choices[0].message.content
            usage = {
                "prompt_tokens": response.usage.prompt_tokens,
                "completion_tokens": response.usage.completion_tokens,
                "total_tokens": response.usage.total_tokens,
            }
        elif req.model.startswith("claude"):
            response = await anthropic_client.messages.create(
                model=req.model,
                max_tokens=req.max_tokens,
                messages=[{"role": m.role, "content": m.content} for m in req.messages if m.role != "system"],
                system=next((m.content for m in req.messages if m.role == "system"), None),
                temperature=req.temperature,
            )
            content = response.content[0].text
            usage = {
                "prompt_tokens": response.usage.input_tokens,
                "completion_tokens": response.usage.output_tokens,
                "total_tokens": response.usage.input_tokens + response.usage.output_tokens,
            }
        else:
            raise HTTPException(400, f"Unsupported model: {req.model}")
    return content, usage

@app.post("/v1/chat/completions", response_model=ChatCompletionResponse)
async def chat_completions(req: ChatCompletionRequest):
    if req.total_input_tokens > 100_000:
        raise HTTPException(400, "Input too large (max 100K tokens)")

    start = time.perf_counter()

    if req.stream:
        async def stream_gen() -> AsyncGenerator[str, None]:
            if req.model.startswith("gpt"):
                stream = await client.chat.completions.create(
                    model=req.model,
                    messages=[m.model_dump() for m in req.messages],
                    stream=True,
                    temperature=req.temperature,
                    max_tokens=req.max_tokens,
                )
                async for chunk in stream:
                    if chunk.choices and chunk.choices[0].delta.content:
                        data = json.dumps({"text": chunk.choices[0].delta.content})
                        yield f"data: {data}\n\n"
            yield "data: [DONE]\n\n"

        return StreamingResponse(stream_gen(), media_type="text/event-stream")

    content, usage = await get_llm_response(req)
    latency = (time.perf_counter() - start) * 1000

    return ChatCompletionResponse(
        content=content,
        model=req.model,
        usage=usage,
        latency_ms=round(latency, 2),
    )

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

10. 多提供商统一客户端

from abc import ABC, abstractmethod
from typing import AsyncIterator

class LLMProvider(ABC):
    @abstractmethod
    async def chat(self, messages: list[dict], **kwargs) -> str: ...

    @abstractmethod
    async def stream_chat(self, messages: list[dict], **kwargs) -> AsyncIterator[str]: ...

    @abstractmethod
    async def embed(self, texts: list[str]) -> list[list[float]]: ...

class OpenAIProvider(LLMProvider):
    def __init__(self, api_key: str):
        self.client = AsyncOpenAI(api_key=api_key)

    async def chat(self, messages, model="gpt-4o", **kwargs):
        resp = await self.client.chat.completions.create(model=model, messages=messages, **kwargs)
        return resp.choices[0].message.content

    async def stream_chat(self, messages, model="gpt-4o", **kwargs):
        stream = await self.client.chat.completions.create(model=model, messages=messages, stream=True, **kwargs)
        async for chunk in stream:
            if chunk.choices and chunk.choices[0].delta.content:
                yield chunk.choices[0].delta.content

    async def embed(self, texts: list[str], model="text-embedding-3-small"):
        resp = await self.client.embeddings.create(model=model, input=texts)
        return [d.embedding for d in resp.data]

class UnifiedLLMClient:
    """统一多提供商客户端,支持自动故障转移。"""
    def __init__(self, providers: list[LLMProvider]):
        self.providers = providers

    async def chat_with_fallback(self, messages: list[dict], **kwargs) -> str:
        for i, provider in enumerate(self.providers):
            try:
                return await provider.chat(messages, **kwargs)
            except Exception as e:
                if i == len(self.providers) - 1:
                    raise
                print(f"Provider {i} failed: {e}, trying next...")

最佳实践速查

场景推荐做法
生产部署使用 AsyncOpenAI + asyncio.Semaphore 限制并发
成本控制tiktoken 预计算 + BudgetTracker 预算上限
流式输出FastAPI StreamingResponse + SSE 协议
错误恢复指数退避 + jitter,区分 RateLimitError 和 APIError
多模态Base64 编码图像,控制 detail 参数为 high/low
长上下文优先 Gemini 1.5 Pro(1M tokens),其次 Claude 3.5
结构化输出Pydantic response_format 配合 OpenAI parse 模式

交叉链接:

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页