openrouter 接口代码使用说明以及技巧杂记
import json
import os
import sys
import time
import traceback
import logging
from typing import Callable, Optional
import openai
sys.path.append("../")
def load_json(json_path):
with open(json_path, "r") as f:
json_obj = json.load(f)
return json_obj
def save_json(json_obj, save_path):
with open(save_path, "w") as f:
json.dump(json_obj, f, indent=4)
return
def generate_llm_params(model_name: str, **kwargs):
"""
生成大模型的参数
temperature: float, response_type: str
"""
params = {}
for key, value in kwargs.items():
if key == "temperature":
if "o1" in model_name:
params["temperature"] = 1
else:
params["temperature"] = value
elif key == "response_format":
if "o1" in model_name:
params["response_format"] = {"type": "text"}
else:
params["response_format"] = {"type": value}
else:
params[key] = value
return params
def default_parse_func(completion):
parse_reply = completion.choices[0].message.content
return parse_reply, None
class Recorder:
def __init__(self, llm_record_path: str = None, detailed_record_num: int = 0):
if llm_record_path is None:
raise ValueError("Please provide llm_record_path argument")
os.makedirs(os.path.dirname(llm_record_path), exist_ok=True)
self.record_path = llm_record_path
self.detailed_record_num = detailed_record_num
def _load(self):
"""
加载大模型费用记录
"""
try:
record = load_json(self.record_path)
except Exception as error:
logging.error(f"加载record失败: {error}")
record = {
"total_cost": 0.0,
"total_count": 0,
"detailed_records": []
}
return record
def update(self, completion):
"""
更新大模型的记录
参数:
completion: OpenAI API 返回的 completion 对象
messages: 请求的消息列表(用于详细记录)
"""
record_info = self._load()
# 从 usage 中提取费用
cost = 0.0
if completion.usage:
cost = getattr(completion.usage, "cost", 0.0) or 0.0
record_info["total_cost"] += cost
record_info["total_cost"] = round(record_info["total_cost"], 6)
record_info["total_count"] += 1
# 如果需要详细记录
current_count = record_info["total_count"]
if current_count <= self.detailed_record_num:
# 直接将 usage 转为字典
usage_dict = None
if completion.usage:
usage_dict = completion.usage.model_dump() if hasattr(completion.usage, "model_dump") else vars(completion.usage)
detailed_record = {
"index": current_count,
"model": completion.model,
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S"),
"usage": usage_dict,
}
record_info["detailed_records"].append(detailed_record)
try:
save_json(record_info, self.record_path)
except Exception as error:
logging.error(f"保存record失败: {error}")
return
class ChatGPT:
def __init__(self, llm_name: str = None, system_prompt: str = None, max_try_num: int = 10, try_num: int = 3, llm_record_path: str = None, detailed_record_num: int = 0):
self.recorder = Recorder(llm_record_path, detailed_record_num)
self.max_try_num = max_try_num
self.try_num = try_num
self.llm_name = llm_name or os.environ.get("MODEL")
if self.llm_name is None:
raise ValueError("Please set environmental variable MODEL or provide llm_name argument")
self._sys_prompt(system_prompt)
OPENAI_API_BASE = os.environ.get("OPENAI_API_BASE")
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
if not OPENAI_API_BASE or not OPENAI_API_KEY:
raise ValueError("Please set environmental variables OPENAI_API_BASE and OPENAI_API_KEY")
self.api_base = OPENAI_API_BASE
self.api_key = OPENAI_API_KEY
self.client = openai.OpenAI(base_url=self.api_base, api_key=self.api_key)
def _sys_prompt(self, system_prompt: str = None):
if "o1" in self.llm_name:
self.system_prompt = None
else:
default_system_prompt = "You are a helpful assistant."
self.system_prompt = default_system_prompt if system_prompt is None else system_prompt
self.reset_conversation()
return
def _request(self, messages: list, **kwargs):
"""
使用大模型生成回复
"""
for _ in range(self.max_try_num):
try:
completion = self.client.chat.completions.create(
model=self.llm_name,
messages=messages,
**kwargs,
)
self.recorder.update(completion)
return completion
except Exception as error:
print(traceback.format_exc())
logging.error(f"OpenAI API调用失败: \n{error}")
time.sleep(3)
return None
def reset_conversation(self, conversation: list = None):
"""
重置对话历史
"""
if conversation is None:
if self.system_prompt is None:
conversation = []
else:
conversation = [{"role": "system", "content": self.system_prompt}]
self.conversation = conversation.copy()
return
def chat_session(self, request, parse_func: Optional[Callable] = default_parse_func, **kwargs):
"""
与大模型对话
若回复成功,则返回解析后的reply,并将request和reply添加到对话历史中
若回复失败,则返回None,不将request和reply添加到对话历史中
"""
if isinstance(request, str):
user_message = {"role": "user", "content": str(request)}
else:
user_message = request
messages = self.conversation + [user_message]
request_params = generate_llm_params(self.llm_name, **kwargs)
completion = self._request(messages, **request_params)
# 若completion为None,则表示请求失败
if completion is None:
raise AssertionError(f"chat 请求失败, completion为None")
parse_reply, rest = parse_func(completion)
# 若对话完成,则将记录添加到对话历史中,注意这里记录的是request和原始reply
self.conversation = messages
self.conversation.append({"role": "assistant", "content": parse_reply})
return parse_reply, rest
def chat(self, request: str, parse_func: Optional[Callable] = default_parse_func, **kwargs):
"""
鲁棒请求,多次尝试chat请求
"""
for _ in range(self.try_num):
try:
reply, rest = self.chat_session(request, parse_func=parse_func, **kwargs)
return reply, rest
except Exception as error:
# logging.error(f"llm chat 报错:\n{traceback.format_exc()}")
logging.error(f"LLM chat 报错:\n{error}\n{traceback.format_exc()}")
time.sleep(10)
raise AssertionError(f"LLM robust chat请求失败")
LLM API 包装器使用文档
该模块主要通过 ChatGPT 类提供与大模型交互的能力,并利用 Recorder 类自动记录调用消耗。
1. 环境准备 (Prerequisites)
在使用之前,必须设置以下环境变量,或者在初始化时作为参数传入(部分参数):
| 环境变量名 | 必填 | 描述 |
|---|---|---|
OPENAI_API_KEY |
是 | API 密钥。 |
OPENAI_API_BASE |
是 | API 基础 URL (Base URL)。 |
MODEL |
否 | 默认使用的模型名称 (如果在代码中未指定 llm_name,则读取此环境变量)。 |
2. 核心类:ChatGPT
ChatGPT 是主要的交互入口,负责管理对话上下文和发送请求。
2.1 初始化 (__init__)
chat_bot = ChatGPT(
llm_name="gpt-4",
system_prompt="你是一个助手",
llm_record_path="./logs/record.json"
)
参数说明:
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
llm_name |
str |
None |
模型名称(如 x-ai/grok-4.1-fast)。若为 None,则尝试读取环境变量 MODEL。 |
system_prompt |
str |
None |
系统提示词(System Prompt)。 注意:如果模型名包含 "o1",此参数将被忽略(强制为 None)。 |
max_try_num |
int |
10 |
底层 API 请求失败时的最大重试次数(针对网络错误、API 报错)。 |
try_num |
int |
3 |
业务逻辑/解析失败时的最大重试次数(针对 chat 方法中的解析错误)。若最终失败,将抛出 AssertionError,建议用 try-except 块包裹调用。 |
llm_record_path |
str |
None |
记录文件的路径。若为 None则报错。 |
detailed_record_num |
int |
0 |
记录详细日志(包含完整 usage 信息)的前 N 条数量。 |
2.2 发送对话 (chat)
这是最常用的方法,它封装了重试逻辑和历史记录更新。
reply, rest = chat_bot.chat(
request="你好,请介绍一下你自己",
temperature=0.7
)
参数说明:
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
request |
str | dict |
必填 | 用户的输入内容。可以是字符串,也可以是符合 OpenAI 格式的消息字典(可以是多模态)。 |
parse_func |
Callable |
default_parse_func |
自定义解析函数。用于处理 API 返回的 completion 对象。 |
**kwargs |
- | - | 其他传递给 API 的参数,例如 temperature, response_format 等(具体见官方文档参数说明);以及extra_body |
特殊参数处理 (generate_llm_params):
- temperature: 如果模型名包含 "o1",
temperature会被强制设为1。 - response_format: 会被自动转换为 OpenAI 格式
{"type": ...}。如果模型包含 "o1",强制设为{"type": "text"}。
2.3 重置对话 (reset_conversation)
清空当前的对话历史,重置为初始状态(仅包含 System Prompt)。
chat_bot.reset_conversation()
参数说明:
| 参数名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
conversation |
list |
None |
可选。如果传入列表,则将对话历史强制设置为该列表。否则重置为默认 System Prompt。 |
3. 辅助功能
3.1 自定义解析函数 (parse_func)
在 chat 方法中,你可以传入自定义的解析函数来处理复杂的返回结构。
函数签名:
def my_parse_func(completion):
# completion 是 OpenAI API 返回的原始对象
content = completion.choices[0].message.content
# 进行自定义处理...
return content, "extra_info" # 必须返回两个值
3.2 费用记录 (Recorder)
该类由 ChatGPT 内部自动调用。
- 它会在指定的 JSON 文件中累加
total_cost和total_count。
技巧杂记
- 遇到了openai库不支持的参数(比如
provider),可以写在extra_body里面 - 有个参数叫
n,可以在同一个completion中返回多次生成。completion.choices[i]中的i不能超过n减一 - 可以获取每个位置的token的概率分布,如下:
支持这个参数的模型比较少,如果返回是None不要大惊小怪,可能是模型不支持response = openai.OpenAI( base_url=self.api_base, api_key=self.api_key ).chat.completions.create( model="openai/gpt-4o", # 或其他支持的模型 messages=[{"role": "user", "content": "您的提示内容"}], logprobs=True, top_logprobs=5 # 获取每个位置概率最高的5个token ) - 当前的openrouter(2025.11.30),除了claude,都支持自动化的cache,就不用单独设置(gemini也可以选择单独设置);claude需要单独设置,但是目前不会,可以学一下
provider可以选择模型提供商,具体介绍见官方文档,里面有个参数require_parameters很有用,最好都加上- 使用的时候是
extra_body={"provider": {"require_parameters": True}} - 但是有个弊端就是满足参数的提供商可能会比较拥挤
- 使用的时候是

浙公网安备 33010602011771号