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_costtotal_count
技巧杂记
  • 遇到了openai库不支持的参数(比如provider),可以写在extra_body里面
  • 有个参数叫n,可以在同一个completion中返回多次生成。completion.choices[i]中的i不能超过n减一
  • 可以获取每个位置的token的概率分布,如下:
    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
    )
    
    支持这个参数的模型比较少,如果返回是None不要大惊小怪,可能是模型不支持
  • 当前的openrouter(2025.11.30),除了claude,都支持自动化的cache,就不用单独设置(gemini也可以选择单独设置);claude需要单独设置,但是目前不会,可以学一下
  • provider可以选择模型提供商,具体介绍见官方文档,里面有个参数require_parameters很有用,最好都加上
    • 使用的时候是extra_body={"provider": {"require_parameters": True}}
    • 但是有个弊端就是满足参数的提供商可能会比较拥挤
posted @ 2025-11-30 20:48  最爱丁珰  阅读(38)  评论(0)    收藏  举报