feat(gateway): per-channel model and system prompt overrides (Fixes #1955)

- config: ChannelOverride + PlatformConfig.channel_overrides

- run: _resolve_model_for_channel, _get_system_prompt_for_channel, channel provider runtime

- tests: channel overrides + config guard for bare runner; conftest asyncio fix; slack/whatsapp warning filters

Made-with: Cursor
This commit is contained in:
crazywriter1 2026-03-19 00:48:06 +03:00 committed by Teknium
parent 902b0b70e4
commit ebef73f6b8
4 changed files with 311 additions and 8 deletions

View file

@ -0,0 +1,128 @@
"""Tests for per-channel model and system prompt overrides (Fixes #1955)."""
import pytest
from gateway.config import (
ChannelOverride,
GatewayConfig,
Platform,
PlatformConfig,
)
from gateway.run import _get_channel_override, GatewayRunner
class TestGetChannelOverride:
def test_no_override_when_empty_config(self):
config = GatewayConfig()
assert _get_channel_override(config, Platform.DISCORD, "123") is None
def test_no_override_when_platform_not_configured(self):
config = GatewayConfig(platforms={})
assert _get_channel_override(config, Platform.DISCORD, "123") is None
def test_no_override_when_channel_not_in_overrides(self):
config = GatewayConfig(
platforms={
Platform.DISCORD: PlatformConfig(
enabled=True,
channel_overrides={
"999": ChannelOverride(model="openrouter/healer-alpha"),
},
),
},
)
assert _get_channel_override(config, Platform.DISCORD, "123") is None
def test_returns_override_when_channel_matches(self):
ov = ChannelOverride(
model="openrouter/healer-alpha",
provider="openrouter",
system_prompt="You are a summarizer.",
)
config = GatewayConfig(
platforms={
Platform.DISCORD: PlatformConfig(
enabled=True,
channel_overrides={"1234567890": ov},
),
},
)
result = _get_channel_override(config, Platform.DISCORD, "1234567890")
assert result is not None
assert result.model == "openrouter/healer-alpha"
assert result.provider == "openrouter"
assert result.system_prompt == "You are a summarizer."
def test_returns_override_when_chat_id_is_int_like(self):
"""Caller may pass str(chat_id); override keys are normalized to str."""
config = GatewayConfig(
platforms={
Platform.DISCORD: PlatformConfig(
enabled=True,
channel_overrides={"123": ChannelOverride(model="gpt-4")},
),
},
)
assert _get_channel_override(config, Platform.DISCORD, "123").model == "gpt-4"
class TestResolveModelForChannel:
def test_uses_channel_override_when_present(self):
config = GatewayConfig(
platforms={
Platform.DISCORD: PlatformConfig(
enabled=True,
channel_overrides={
"chan_1": ChannelOverride(model="anthropic/claude-opus-4.6"),
},
),
},
)
runner = object.__new__(GatewayRunner)
runner.config = config
model = runner._resolve_model_for_channel(Platform.DISCORD, "chan_1")
assert model == "anthropic/claude-opus-4.6"
def test_falls_back_to_global_when_no_override(self, monkeypatch):
monkeypatch.setattr(
"gateway.run._resolve_gateway_model",
lambda: "global-model/default",
)
config = GatewayConfig(
platforms={
Platform.DISCORD: PlatformConfig(enabled=True, channel_overrides={}),
},
)
runner = object.__new__(GatewayRunner)
runner.config = config
model = runner._resolve_model_for_channel(Platform.DISCORD, "unknown_channel")
assert model == "global-model/default"
class TestGetSystemPromptForChannel:
def test_uses_channel_override_when_present(self):
config = GatewayConfig(
platforms={
Platform.DISCORD: PlatformConfig(
enabled=True,
channel_overrides={
"chan_1": ChannelOverride(system_prompt="You are a coding assistant."),
},
),
},
)
runner = object.__new__(GatewayRunner)
runner.config = config
runner._ephemeral_system_prompt = "Global prompt"
prompt = runner._get_system_prompt_for_channel(Platform.DISCORD, "chan_1")
assert prompt == "You are a coding assistant."
def test_falls_back_to_global_when_no_override(self):
config = GatewayConfig(
platforms={Platform.DISCORD: PlatformConfig(enabled=True)},
)
runner = object.__new__(GatewayRunner)
runner.config = config
runner._ephemeral_system_prompt = "Global prompt"
prompt = runner._get_system_prompt_for_channel(Platform.DISCORD, "other")
assert prompt == "Global prompt"

View file

@ -5,6 +5,7 @@ import os
from unittest.mock import patch
from gateway.config import (
ChannelOverride,
GatewayConfig,
HomeChannel,
Platform,
@ -89,6 +90,54 @@ class TestPlatformConfigRoundtrip:
# extra; from_dict must honor it there too (mirrors _grn fallback).
restored = PlatformConfig.from_dict({"extra": {"typing_indicator": False}})
assert restored.typing_indicator is False
def test_channel_overrides_roundtrip(self):
pc = PlatformConfig(
enabled=True,
channel_overrides={
"1234567890": ChannelOverride(
model="openrouter/healer-alpha",
provider="openrouter",
system_prompt="You are a daily news summarizer.",
),
"9876543210": ChannelOverride(
model="anthropic/claude-opus-4.6",
provider="anthropic",
system_prompt="You are a coding assistant.",
),
},
)
d = pc.to_dict()
assert "channel_overrides" in d
assert d["channel_overrides"]["1234567890"]["model"] == "openrouter/healer-alpha"
assert d["channel_overrides"]["9876543210"]["system_prompt"] == "You are a coding assistant."
restored = PlatformConfig.from_dict(d)
assert restored.channel_overrides["1234567890"].model == "openrouter/healer-alpha"
assert restored.channel_overrides["9876543210"].provider == "anthropic"
def test_channel_overrides_from_dict_normalizes_channel_id_to_str(self):
"""YAML may have numeric channel IDs; we store as str."""
data = {
"enabled": True,
"channel_overrides": {
1234567890: {"model": "openrouter/healer-alpha"},
},
}
pc = PlatformConfig.from_dict(data)
assert "1234567890" in pc.channel_overrides
assert pc.channel_overrides["1234567890"].model == "openrouter/healer-alpha"
class TestChannelOverride:
def test_from_dict_empty(self):
assert ChannelOverride.from_dict({}).model is None
assert ChannelOverride.from_dict(None).model is None
def test_to_dict_omits_none(self):
ov = ChannelOverride(model="gpt-4", provider=None, system_prompt="Hi")
d = ov.to_dict()
assert d["model"] == "gpt-4"
assert "provider" not in d
assert d["system_prompt"] == "Hi"
class TestGetConnectedPlatforms: