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.model 和 self.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——不是因为当前提供商不好,而是因为业务场景变多了,不同模型在不同场景下的性价比差异开始变得显著。