OpenAI 与微软握手言和,你的 AI 应用还在和一家云绑定?多云 AI Agent 架构实操指南

0 阅读2分钟

OpenAI 与微软之间持续数月的法律纠纷本周画上了句号。争议的核心是 OpenAI 与亚马逊签订的 $500 亿基础设施合作——微软认为这违反了其作为 OpenAI 最大投资方的排他性条款。最终双方和解,OpenAI 保留了与亚马逊的交易,微软获得了一定的补偿条款。

这条新闻表面上是商业和法律的博弈,但对 AI 工程开发者来说,它揭示了一个更深层的现实:顶级 AI 公司自己都在做多云布局,你的应用架构凭什么只绑一家云厂商?

过去两年,大量 AI 应用的开发生态被简化为「调一个 API + 扔到一个云桶里」。这在早期快速验证阶段没问题,但当业务增长到需要关注可用性、成本和延迟时,单云绑定就成了隐性的技术债。

这篇文章会手把手带你搭一个多云 AI Agent 基础架构——API 可切换、向量数据库可迁移、推理成本可追踪。所有代码可直接复用。

架构总览

用户请求
    │
    ├─ API 网关(统一入口,路由到不同模型提供商)
    │
    ├─ Agent 调度层(任务规划 + 工具调用)
    │     │
    │     ├─ 模型适配器(OpenAI / Anthropic / 本地 接口统一)
    │     │
    │     └─ 向量存储抽象(Pinecone / Weaviate / pgvector 可切换)
    │
    ├─ 可观测性层(成本追踪 + 延迟监控 + Token 审计)
    │
    └─ 评估与回放(LLM-as-Judge + 请求日志)

每一层都与具体厂商解耦,换一家云或换一个模型,改动控制在单个适配器内部。

Step 1:模型适配器——用一个抽象层统一所有 Provider

最核心的解耦点。不管底层是 OpenAI、Anthropic 还是本地推理,应用代码只跟一个 ModelAdapter 接口打交道。

from abc import ABC, abstractmethod
from dataclasses import dataclass

@dataclass
class ModelResponse:
    content: str
    model: str
    usage: dict         # {"input_tokens": int, "output_tokens": int}
    latency_ms: float

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

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

然后为每个提供商实现一个适配器:

class OpenAIAdapter(ModelAdapter):
    def __init__(self, model: str = "gpt-4o"):
        from openai import OpenAI
        self.client = OpenAI()
        self.model = model

    def chat(self, messages, **kwargs) -> ModelResponse:
        import time
        start = time.time()
        resp = self.client.chat.completions.create(
            model=self.model,
            messages=messages,
            **kwargs
        )
        latency = (time.time() - start) * 1000
        return ModelResponse(
            content=resp.choices[0].message.content,
            model=self.model,
            usage=dict(resp.usage),
            latency_ms=latency,
        )

class AnthropicAdapter(ModelAdapter):
    def __init__(self, model: str = "claude-sonnet-4-20250514"):
        from anthropic import Anthropic
        self.client = Anthropic()
        self.model = model

    def chat(self, messages, **kwargs) -> ModelResponse:
        import time
        start = time.time()
        resp = self.client.messages.create(
            model=self.model,
            messages=messages,
            **kwargs
        )
        latency = (time.time() - start) * 1000
        return ModelResponse(
            content=resp.content[0].text,
            model=self.model,
            usage={"input_tokens": resp.usage.input_tokens,
                   "output_tokens": resp.usage.output_tokens},
            latency_ms=latency,
        )

使用的时候,通过配置决定用哪个 Provider:

ADAPTERS = {
    "openai": OpenAIAdapter,
    "anthropic": AnthropicAdapter,
    # "local": LocalModelAdapter,  # 可随时加入
}

adapter = ADAPTERS[config["default_provider"]]()
response = adapter.chat(messages)

换模型的操作成本:改一行配置,不是改全部代码。

Step 2:向量存储抽象——从 Pinecone 迁移到 pgvector 不改业务代码

AI 应用中向量数据库的绑定比模型绑定更隐蔽。很多项目初期选了 Pinecone,后期想换到自建的 pgvector 时发现查询代码散落在各个文件里。

解法:定义一个 VectorStore 抽象和查询结果结构体。

@dataclass
class VectorRecord:
    id: str
    vector: list[float]
    metadata: dict
    score: float | None = None

class VectorStore(ABC):
    @abstractmethod
    def upsert(self, records: list[VectorRecord]):
        ...

    @abstractmethod
    def query(self, vector: list[float], top_k: int = 10) -> list[VectorRecord]:
        ...

接口极简,覆盖 90% 的场景。然后分别实现:

class PineconeStore(VectorStore):
    def __init__(self, index_name: str):
        from pinecone import Pinecone
        self.pc = Pinecone()
        self.index = self.pc.Index(index_name)

    def upsert(self, records):
        vectors = [(r.id, r.vector, r.metadata) for r in records]
        self.index.upsert(vectors)

    def query(self, vector, top_k=10) -> list[VectorRecord]:
        result = self.index.query(vector=vector, top_k=top_k)
        return [
            VectorRecord(id=m.id, vector=[], metadata=m.metadata, score=m.score)
            for m in result.matches
        ]


class PgVectorStore(VectorStore):
    def __init__(self, conn_string: str, table: str = "embeddings"):
        import psycopg2
        self.conn = psycopg2.connect(conn_string)
        self.table = table

    def upsert(self, records):
        with self.conn.cursor() as cur:
            for r in records:
                cur.execute(
                    f"INSERT INTO {self.table} (id, vector, metadata) "
                    f"VALUES (%s, %s, %s) ON CONFLICT (id) DO UPDATE "
                    f"SET vector = EXCLUDED.vector, metadata = EXCLUDED.metadata",
                    (r.id, r.vector, json.dumps(r.metadata))
                )
        self.conn.commit()

    def query(self, vector, top_k=10) -> list[VectorRecord]:
        with self.conn.cursor() as cur:
            cur.execute(
                f"SELECT id, metadata, vector <=> %s::vector AS score "
                f"FROM {self.table} ORDER BY score LIMIT %s",
                (vector, top_k)
            )
            return [
                VectorRecord(id=r[0], vector=[], metadata=r[1], score=r[2])
                for r in cur.fetchall()
            ]

Pinecone 到期不续费?建个 pgvector 表,改一行 VectorStore 实例化代码——查询逻辑不受影响。

Step 3:Agent 调度层,工具调用也要与模型解耦

有了模型适配器和向量存储抽象层之后,Agent 调度逻辑本身就不再依赖任何具体模型。

这里用一个简化版的 ReAct 模式演示:

class Agent:
    def __init__(self, model: ModelAdapter, store: VectorStore, tools: list[dict]):
        self.model = model
        self.store = store
        self.tools = tools

    def run(self, user_input: str) -> str:
        messages = [
            {"role": "system", "content": self._system_prompt()},
            {"role": "user", "content": user_input},
        ]
        response = self.model.chat(messages)
        return response.content

    def _system_prompt(self):
        return f"""你是一个 AI 助手。可用工具:
{json.dumps(self.tools, ensure_ascii=False)}

回答要基于向量存储中的知识。如果信息不足,诚实说不知道。"""

关键设计点:self.modelself.store 都是抽象接口,你可以注入任意实现。测试的时候注入一个模拟(mock)适配器就行,不用真的调用 API。

Step 4:可观测性——成本追踪和延迟监控

多云架构比单云更需要可观测性。每一层都需要埋点。

import time
import json
from datetime import datetime

class ObservabilityMiddleware:
    def __init__(self, log_path: str = "requests.jsonl"):
        self.log_path = log_path

    def wrap_adapter(self, adapter: ModelAdapter) -> ModelAdapter:
        """给 ModelAdapter 包装一层监控"""
        original_chat = adapter.chat

        def monitored_chat(messages, **kwargs):
            start = time.time()
            response = original_chat(messages, **kwargs)
            duration = (time.time() - start) * 1000
            record = {
                "timestamp": datetime.utcnow().isoformat(),
                "model": response.model,
                "input_tokens": response.usage.get("input_tokens", 0),
                "output_tokens": response.usage.get("output_tokens", 0),
                "latency_ms": round(duration, 1),
                "provider": adapter.__class__.__name__,
            }
            with open(self.log_path, "a") as f:
                f.write(json.dumps(record, ensure_ascii=False) + "\n")
            return response

        adapter.chat = monitored_chat
        return adapter

这样每天跑完,用一行命令就能拉出成本统计:

cat requests.jsonl | python -c "
import json, sys
records = [json.loads(l) for l in sys.stdin]
cost = sum((r['input_tokens']*10 + r['output_tokens']*30)/1e6 for r in records)
print(f'今日预估成本: \${cost:.2f}')
print(f'请求数: {len(records)}, 平均延迟: {sum(r[\"latency_ms\"] for r in records)/len(records):.0f}ms')
"

这套架构在什么场景下真正有价值

坦白说,如果你的 AI 应用还在验证阶段(日调用 < 1000 次),直接调 API 完全够用,不需要这套抽象层。抽象有成本——多了一层代码,多了一些维护量。

但如果你遇到以下任一情况,就是该引入多云抽象的信号:

  • API 账单月增长率 > 30%,需要按 Provider 拆分成本做优化
  • P99 延迟不稳定,同样的 API 在高峰期和谷底差 3 倍以上
  • 需要考虑 AI 城域网或本地部署,因为合规或成本原因必须切换推理后端
  • 同时跑了 2+ 个实验模型,每次对比都要改调用代码

架构抽象的核心意义是:在需要切换的时候,改的是配置而不是代码。

常见问题

这个架构会增加多少初始开发时间?

按我们团队的实际经验,第一次搭建这套抽象层大约需要 2-3 天(含单元测试)。之后每个新 Provider 的适配器开发时间约 2-4 小时。

抽象层会影响性能吗?

模型适配器本身是零开销的(就是调用 Python 函数转发)。向量存储抽象层在 pgvector 场景下有轻微序列化开销,实测约 1-3ms,可忽略。主要性能瓶颈永远在模型推理和网络 I/O,不在抽象层。

Agent 框架(LangChain、CrewAI 等)不是已经做了这些抽象吗?

是的,LangChain 等框架提供了类似的抽象,但引入了额外的依赖和框架特定的心智模型。如果你已经在用 LangChain,它的 ModelAdapter 对应 BaseLLM,VectorStore 对应 VectorStore 接口。本文的做法适用于想保持轻量、不想引入大型框架依赖的场景。

如果我只用一个 Provider,还有必要做抽象吗?

如果你确定未来也不会换,可以不做。但实际经验是:大多数团队在项目 6-12 个月后都会考虑切换或增加 Provider——不是因为当前提供商不好,而是因为业务场景变多了,不同模型在不同场景下的性价比差异开始变得显著。