Add OpenRouter provider support

Introduce an optional OpenRouter client (OpenAI-compatible) alongside
the OpenAI one. Models can declare 'provider: openrouter' in
models.yml; ChatGPT now selects the matching client per model via
_get_client_for_model and routes chat completions through it. This
unlocks non-OpenAI models (e.g. Claude) through a single SDK.

Adds openrouter_api_key / openrouter_api_base config options.
This commit is contained in:
Father Bot
2026-03-03 12:40:21 +03:00
parent 6af26cc9bc
commit 8b42181274
3 changed files with 31 additions and 4 deletions
+2
View File
@@ -15,6 +15,8 @@ config_env = dotenv.dotenv_values(config_dir / "config.env")
telegram_token = config_yaml["telegram_token"]
openai_api_key = config_yaml["openai_api_key"]
openai_api_base = config_yaml.get("openai_api_base", None)
openrouter_api_key = config_yaml.get("openrouter_api_key", None)
openrouter_api_base = config_yaml.get("openrouter_api_base", "https://openrouter.ai/api/v1")
allowed_telegram_usernames = config_yaml["allowed_telegram_usernames"]
new_dialog_timeout = config_yaml["new_dialog_timeout"]
enable_message_streaming = config_yaml.get("enable_message_streaming", True)
+27 -4
View File
@@ -12,9 +12,31 @@ openai_client = AsyncOpenAI(
api_key=config.openai_api_key,
base_url=config.openai_api_base,
)
# optional OpenRouter client (OpenAI-compatible) for models declared with
# "provider: openrouter" in config/models.yml (e.g. Claude or other vendors)
openrouter_client = None
if config.openrouter_api_key:
openrouter_client = AsyncOpenAI(
api_key=config.openrouter_api_key,
base_url=config.openrouter_api_base,
)
logger = logging.getLogger(__name__)
def _get_client_for_model(model):
provider = config.models["info"].get(model, {}).get("provider", "openai")
if provider == "openrouter":
if openrouter_client is None:
raise ValueError(
"OpenRouter API key is not configured. "
"Set openrouter_api_key in config/config.yml"
)
return openrouter_client
return openai_client
OPENAI_COMPLETION_OPTIONS = {
"temperature": 0.7,
"max_tokens": 1000,
@@ -29,6 +51,7 @@ class ChatGPT:
def __init__(self, model="gpt-4o-mini"):
assert model in config.models["info"], f"Unknown model: {model}"
self.model = model
self._client = _get_client_for_model(model)
async def send_message(self, message, dialog_messages=[], chat_mode="assistant"):
if chat_mode not in config.chat_modes.keys():
@@ -41,7 +64,7 @@ class ChatGPT:
if config.models["info"][self.model]["type"] == "chat_completion":
messages = self._generate_prompt_messages(message, dialog_messages, chat_mode)
r = await openai_client.chat.completions.create(
r = await self._client.chat.completions.create(
model=self.model,
messages=messages,
**OPENAI_COMPLETION_OPTIONS
@@ -74,7 +97,7 @@ class ChatGPT:
if config.models["info"][self.model]["type"] == "chat_completion":
messages = self._generate_prompt_messages(message, dialog_messages, chat_mode)
r_gen = await openai_client.chat.completions.create(
r_gen = await self._client.chat.completions.create(
model=self.model,
messages=messages,
stream=True,
@@ -120,7 +143,7 @@ class ChatGPT:
messages = self._generate_prompt_messages(
message, dialog_messages, chat_mode, image_buffer
)
r = await openai_client.chat.completions.create(
r = await self._client.chat.completions.create(
model=self.model,
messages=messages,
**OPENAI_COMPLETION_OPTIONS
@@ -169,7 +192,7 @@ class ChatGPT:
message, dialog_messages, chat_mode, image_buffer
)
r_gen = await openai_client.chat.completions.create(
r_gen = await self._client.chat.completions.create(
model=self.model,
messages=messages,
stream=True,
+2
View File
@@ -1,6 +1,8 @@
telegram_token: ""
openai_api_key: ""
openai_api_base: null # leave null to use default api base or you can put your own base url here
openrouter_api_key: "" # optional: needed only for models with "provider: openrouter" in models.yml (e.g. Claude)
openrouter_api_base: "https://openrouter.ai/api/v1" # OpenRouter is OpenAI-compatible; change only if you proxy it
allowed_telegram_usernames: [] # if empty, the bot is available to anyone. pass a username string to allow it and/or user ids as positive integers and/or channel ids as negative integers
new_dialog_timeout: 600 # new dialog starts after timeout (in seconds)
return_n_generated_images: 1