feat(channel): 添加权重属性以优化通道选择

在 ChannelConfig 中添加了可选的 weight 属性,用于
优化通道的选择逻辑。更新了相关 API 端点以支持
权重的验证和处理。通道选择逻辑现在会优先选择
权重最高的通道,提升请求路由的灵活性和效率。
This commit is contained in:
Jeff Gu committed 2026-04-12 00:08:19 +08:00
1 parent 1243bf629d
commit 45d717a028
7 files changed
+598 -516

No files matched your search

File diff suppressed because it is too large. Load diff
+1
View File
@@ -14,6 +14,7 @@ export interface ChannelConfig {
type: 'openai' | 'gemini' | 'azure-openai' | 'openai-audio' | 'azure-openai-audio' | 'claude' | 'claude-to-openai' | 'openai-responses' | 'azure-openai-responses'
endpoint: string
enabled?: boolean
weight?: number
api_key?: string
api_keys?: string[]
auto_retry?: boolean
+7 -1
View File
@@ -155,7 +155,11 @@ export class ChannelGetEndpoint extends OpenAPIRoute {
async handle(c: Context<HonoCustomType>) {
const result = await c.env.DB.prepare(
"SELECT * FROM channel_config"
`SELECT * FROM channel_config
ORDER BY
COALESCE(CAST(json_extract(value, '$.weight') AS INTEGER), 0) DESC,
datetime(created_at) DESC,
key ASC`
).all<ChannelConfigRow>();
return {
@@ -182,6 +186,7 @@ export class ChannelUpsertEndpoint extends OpenAPIRoute {
type: z.string().describe('Channel type'),
endpoint: z.string().describe('API endpoint'),
enabled: z.boolean().optional().describe('Whether this channel participates in request routing'),
weight: z.number().int().min(0).max(5).optional().describe('Priority weight for channel routing'),
api_key: z.string().optional().describe('Deprecated single API key'),
api_keys: z.array(z.string()).optional().describe('API keys, one request will pick one randomly'),
auto_retry: z.boolean().optional().describe('Automatically retry the same API key'),
@@ -254,6 +259,7 @@ export class ChannelFetchModelsEndpoint extends OpenAPIRoute {
type: z.string().optional(),
endpoint: z.string().optional(),
enabled: z.boolean().optional(),
weight: z.number().int().min(0).max(5).optional(),
api_key: z.string().optional(),
api_keys: z.array(z.string()).optional(),
auto_retry: z.boolean().optional(),
+15 -1
View File
@@ -1,12 +1,15 @@
export const DEFAULT_CHANNEL_AUTO_RETRY = true;
export const DEFAULT_CHANNEL_AUTO_ROTATE = true;
export const DEFAULT_CHANNEL_ENABLED = true;
export const DEFAULT_CHANNEL_WEIGHT = 0;
export const MAX_CHANNEL_WEIGHT = 5;
export const DEFAULT_CLAUDE_API_VERSION = "2023-06-01";
export const MAX_RETRIES_PER_KEY = 3;
export const MAX_ROTATION_ATTEMPTS = 3;
export type NormalizedChannelConfig = Omit<ChannelConfig, "enabled" | "api_keys" | "auto_retry" | "auto_rotate" | "models" | "supported_models" | "deployment_mapper"> & {
export type NormalizedChannelConfig = Omit<ChannelConfig, "enabled" | "weight" | "api_keys" | "auto_retry" | "auto_rotate" | "models" | "supported_models" | "deployment_mapper"> & {
enabled: boolean;
weight: number;
api_keys: string[];
auto_retry: boolean;
auto_rotate: boolean;
@@ -15,6 +18,15 @@ export type NormalizedChannelConfig = Omit<ChannelConfig, "enabled" | "api_keys"
deployment_mapper: Record<string, string>;
};
const normalizeChannelWeight = (weight: unknown): number => {
if (typeof weight !== "number" || !Number.isFinite(weight)) {
return DEFAULT_CHANNEL_WEIGHT;
}
const normalizedWeight = Math.trunc(weight);
return Math.min(MAX_CHANNEL_WEIGHT, Math.max(DEFAULT_CHANNEL_WEIGHT, normalizedWeight));
};
const normalizeApiKeys = (config: Partial<ChannelConfig>): string[] => {
const rawKeys = Array.isArray(config.api_keys)
? [...config.api_keys]
@@ -108,6 +120,7 @@ export const normalizeChannelConfig = (
type: config.type,
endpoint: config.endpoint || "",
enabled: config.enabled ?? DEFAULT_CHANNEL_ENABLED,
weight: normalizeChannelWeight(config.weight),
api_key: typeof config.api_key === "string" ? config.api_key.trim() : undefined,
api_keys: normalizeApiKeys(config),
auto_retry: config.auto_retry ?? DEFAULT_CHANNEL_AUTO_RETRY,
@@ -129,6 +142,7 @@ export const sanitizeChannelConfig = (
type: normalized.type,
endpoint: normalized.endpoint,
enabled: normalized.enabled,
weight: normalized.weight,
api_keys: normalized.api_keys,
auto_retry: normalized.auto_retry,
auto_rotate: normalized.auto_rotate,
+1 -1
View File
@@ -1,6 +1,6 @@
export const CONSTANTS = {
VERSION: "v0.0.1",
DB_VERSION_KEY: "db_version",
DB_VERSION: "v0.0.4",
DB_VERSION: "v0.0.5",
MODEL_PRICING_KEY: "MODEL_PRICING_CONFIG",
}
+8 -2
View File
@@ -103,8 +103,14 @@ export const resolveChannel = async (
return c.text(`Model not supported: ${requestedModel}. Please configure models.`, 400);
}
const randomIndex = Math.floor(Math.random() * availableChannels.length);
const selectedChannel = availableChannels[randomIndex];
const maxWeight = availableChannels.reduce((highest, current) => {
return Math.max(highest, current.config.weight ?? 0);
}, 0);
const highestPriorityChannels = availableChannels.filter((channel) => {
return (channel.config.weight ?? 0) === maxWeight;
});
const randomIndex = Math.floor(Math.random() * highestPriorityChannels.length);
const selectedChannel = highestPriorityChannels[randomIndex];
const targetChannelKey = selectedChannel.key;
const targetChannelConfig = selectedChannel.config;
const trackingState: RequestTrackingState = {
Vendored
+1
View File
@@ -71,6 +71,7 @@ type ChannelConfig = {
type: ChannelType;
endpoint: string;
enabled?: boolean;
weight?: number;
api_key?: string;
api_keys?: string[];
auto_retry?: boolean;