当前位置: 首页 > news >正文

大模型 API 遍地都是。OpenAI、DeepSeek、通义千问、GLM……每个都给 API 接口,连上去就能用。

但大部分人用的时候都是现查文档,每次接入一个新模型都要重写一遍。其实抽象一层,写个统一的调用封装,后面换模型改一行配置就行。

先搭基础调用层

不管哪个模型,调用方式大同小异:POST 一个请求,传消息列表,拿回复。

```python

# llm_client.py

import json

from dataclasses import dataclass, field

from typing import Optional

import requests

@dataclass

class Message:

role: str # "system" | "user" | "assistant"

content: str

@dataclass

class LLMResponse:

content: str

model: str

usage: dict = field(default_factory=dict)

class LLMClient:

def __init__(self, api_key: str, base_url: str, model: str):

self.api_key = api_key

self.base_url = base_url.rstrip("/")

self.model = model

self.session = requests.Session()

self.session.headers.update({

"Authorization": f"Bearer {api_key}",

"Content-Type": "application/json",

})

def chat(self, messages: list[Message], **kwargs) -> LLMResponse:

payload = {

"model": self.model,

"messages": [{"role": m.role, "content": m.content} for m in messages],

**kwargs,

}

resp = self.session.post(

f"{self.base_url}/chat/completions",

json=payload,

timeout=60,

)

resp.raise_for_status()

data = resp.json()

choice = data["choices"][0]

return LLMResponse(

content=choice["message"]["content"],

model=data.get("model", self.model),

usage=data.get("usage", {}),

)

```

这段代码所有模型通用,区别只在于 api_key、base_url、model 三个参数。

配置化,换模型改一行

把模型配置写在文件里,不写死在代码里。

```python

# config.py

import os

from dataclasses import dataclass

@dataclass

class ModelConfig:

name: str

api_key: str

base_url: str

# 支持的模型配置

MODELS = {

"deepseek": ModelConfig(

name="deepseek-chat",

api_key=os.getenv("DEEPSEEK_API_KEY", ""),

base_url="https://api.deepseek.com",

),

"openai": ModelConfig(

name="gpt-4o-mini",

api_key=os.getenv("OPENAI_API_KEY", ""),

base_url="https://api.openai.com",

),

"qwen": ModelConfig(

name="qwen-plus",

api_key=os.getenv("QWEN_API_KEY", ""),

base_url="https://dashscope.aliyuncs.com/compatible-mode",

),

"glm": ModelConfig(

name="glm-4-flash",

api_key=os.getenv("GLM_API_KEY", ""),

base_url="https://open.bigmodel.cn/api/paas/v4",

),

}

def get_client(model_name: str = "deepseek") -> LLMClient:

cfg = MODELS.get(model_name)

if not cfg:

raise ValueError(f"不支持的模型: {model_name},可选: {list(MODELS.keys())}")

if not cfg.api_key:

raise ValueError(f"{model_name} 的 API Key 未设置")

return LLMClient(

api_key=cfg.api_key,

base_url=cfg.base_url,

model=cfg.name,

)

```

用的时候:

```python

client = get_client("deepseek")

resp = client.chat([Message(role="user", content="讲个笑话")])

print(resp.content)

```

想换成通义千问,改一行:

```python

client = get_client("qwen")

```

API 接口兼容的前提下,哪里都不用改。

对话管理,别每次都从头开始

每次聊天只发一条消息,模型没有上下文。加上对话管理,自动维护历史消息。

```python

# conversation.py

from llm_client import Message

class Conversation:

def __init__(self, system_prompt: str = ""):

self.messages = []

if system_prompt:

self.messages.append(Message(role="system", content=system_prompt))

def add_user(self, content: str):

self.messages.append(Message(role="user", content=content))

def add_assistant(self, content: str):

self.messages.append(Message(role="assistant", content=content))

def get_messages(self, max_history: int = 20) -> list[Message]:

"""保留 system prompt + 最近 N 轮对话"""

if len(self.messages) <= max_history + 1:

return self.messages

return [self.messages[0]] + self.messages[-(max_history):]

```

为了控制 token 消耗,get_messages 只保留最近的 N 轮对话,最早的历史自动丢弃。

流式输出,不用等全部返回

等模型全部生成完再返回,用户要等很久。改成流式输出,逐字显示。

```python

def chat_stream(self, messages: list[Message], **kwargs):

payload = {

"model": self.model,

"messages": [{"role": m.role, "content": m.content} for m in messages],

"stream": True,

**kwargs,

}

with self.session.post(

f"{self.base_url}/chat/completions",

json=payload,

stream=True,

timeout=120,

) as resp:

resp.raise_for_status()

for line in resp.iter_lines():

if not line:

continue

line = line.decode("utf-8")

if line.startswith("data: "):

data_str = line[6:]

if data_str.strip() == "[DONE]":

break

try:

chunk = json.loads(data_str)

delta = chunk["choices"][0].get("delta", {})

content = delta.get("content", "")

if content:

yield content

except json.JSONDecodeError:

continue

```

调用方逐段拿到内容,可以实时显示:

```python

for chunk in client.chat_stream([Message(role="user", content="写一首诗")]):

print(chunk, end="", flush=True)

```

加个简单的重试

网络请求偶尔会失败,加个重试减少手动重试的次数。

```python

import time

def chat_with_retry(

client: LLMClient,

messages: list[Message],

max_retries: int = 3,

**kwargs,

) -> LLMResponse:

last_error = None

for attempt in range(max_retries):

try:

return client.chat(messages, **kwargs)

except requests.exceptions.Timeout:

last_error = "请求超时"

time.sleep(2 ** attempt)

except requests.exceptions.HTTPError as e:

status = e.response.status_code

if status in (429, 502, 503):

last_error = f"服务暂不可用 ({status})"

time.sleep(2 ** attempt)

else:

raise

except Exception as e:

last_error = str(e)

if attempt == max_retries - 1:

raise

raise RuntimeError(f"重试 {max_retries} 次后仍然失败: {last_error}")

# 使用

resp = chat_with_retry(client, [Message(role="user", content="你好")])

print(resp.content)

```

超时、429限流、502网关错误自动重试,指数退避。

一个完整的使用例子

把上面几个功能串起来,写一个简单的命令行对话程序:

```python

# cli_chat.py

from llm_client import LLMClient

from config import get_client

from conversation import Conversation

def main():

print("=== AI 命令行对话(输入 /exit 退出)===")

print("支持的模型: deepseek, openai, qwen, glm")

model = input("选择模型 (回车默认 deepseek): ").strip() or "deepseek"

client = get_client(model)

conv = Conversation(system_prompt="你是一个助手,用简洁的方式回答问题。")

while True:

user_input = input("\n>> ").strip()

if user_input.lower() in ("/exit", "/quit"):

print("再见")

break

if user_input.lower() == "/model":

print(f"当前模型: {client.model}")

continue

conv.add_user(user_input)

print("\nAI: ", end="", flush=True)

response_parts = []

for chunk in client.chat_stream(conv.get_messages()):

print(chunk, end="", flush=True)

response_parts.append(chunk)

print()

full_response = "".join(response_parts)

conv.add_assistant(full_response)

if __name__ == "__main__":

main()

```

几个注意事项

API Key 不要写死在代码里。上面 config.py 用 os.getenv 从环境变量读取,在 .env 文件里配置。

不同模型的接口有细微差异。上面的封装基于 OpenAI 兼容接口,DeepSeek、通义千问、GLM 都支持。如果用非兼容接口,需要单独适配。

上下文的长度控制。每个模型的上下文长度不同(4K、16K、128K),Conversation 的 max_history 要根据模型的限制调整。太长会被截断或报错。

错误处理要区分可重试和不可重试。401 认证失败、400 请求错误这些重试也没用,直接报错。429 限流和 5xx 服务端错误可以重试。

http://www.cnnetsun.cn/news/3283702.html

相关文章:

  • 黑名单检测敏感号码精准剔除与通过号码分层放行的运营策略:短信营销投诉率压降的最佳落地实践
  • 启扬RK3588部署OpenClaw实战:边缘AI机械臂全链路落地指南
  • 【Photoshop AI功能速成指南】:20年Adobe认证专家亲授,7天掌握12个颠覆性AI工作流
  • 腾讯Marvis用两周,用对话就能操控我的电脑:找文件、清C盘、改设置、测网速、查配置、整桌面
  • PCA vs PLS-DA vs OPLS-DA:3 种降维方法在组学数据中的选择指南
  • CRM核心能力对决:8 大品牌客户运营至数据驱动能力横向分析(附选型参考)
  • Llama 3本地部署Python环境搭建实战指南
  • CBCX平台印象:经纪商服务细节带来的使用参考
  • 如何一站式解决中国地图集成难题?Leaflet.ChineseTmsProviders终极方案
  • 构建企业级WebGIS应用:Leaflet.ChineseTmsProviders技术架构与最佳实践
  • CrewAI团队式Agent设计:角色、任务与记忆的工程化实践
  • 如何在10分钟内为Honey Select 2安装终极汉化去码补丁:新手完整指南
  • Navicat重置脚本:让Mac数据库开发告别试用期焦虑的创新解决方案
  • AI Agent错误处理正在失效?2024最新Gartner报告警示:76%企业仍缺失上下文感知重试机制
  • miniforge vs Anaconda:科学计算环境的确定性选择
  • 极大极小搜索与α-β剪枝算法:5个案例对比,剪枝效率提升30%
  • MATLAB FFT 实战:3步代码解析单/双侧频谱转换与真实幅度计算
  • TB67H480FNG与PIC18F55K42在精密电机控制中的高性价比方案
  • LabVIEW 状态机架构实战:重构温度监控系统,实现 5 种运行模式切换
  • UE4蓝图实现按键切换操控角色:从原理到实践
  • 如何选择适合自己的传输阀?
  • 计算机自学新范式:3大突破解决传统教育困境
  • Unity开发环境搭建:从Hub安装到VS2022调试全链路指南
  • VSCode 远程调试 ARM Linux C++:gdbserver 交叉编译与 5 个嵌入式调试实战要点
  • Copilot+ PC实操解剖:NPU硬件加速与Windows 11 AI底层调用
  • IP-Adapter与Reference Only:用参考图统一风格
  • PHP 8.4 正则表达式模式修饰符详解:i、u、s、m 等 10 种场景实战
  • 《动态规划:从“傻傻穷举”到“过目不忘”的修仙之路》
  • 2026 技术小白也能上手:维护简单、稳定不卡顿的商城小程序平台
  • Cache 性能分析实验:MyCache 模拟器 3 大参数(容量/相联度/块大小)对失效率影响实测