Skip to content

Commit f4e1eb8

Browse files
committed
移除精确匹配可以跳过限制逻辑
1 parent a4bfefa commit f4e1eb8

1 file changed

Lines changed: 118 additions & 49 deletions

File tree

src/lib/model-matcher.ts

Lines changed: 118 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,162 +4,225 @@ import modelConsumptionData from "./model-consumption.json"
44
import { state } from "./state"
55

66
/**
7-
* Get model consumption value
7+
* 获取模型消耗值
8+
* 从配置文件中查询指定模型的消耗系数
9+
*
10+
* @param modelName - 模型名称
11+
* @returns 消耗系数(如 1.0, 2.0 等),未找到或解析失败返回 999
12+
*
13+
* @example
14+
* getModelConsumption("claude-3.5-sonnet") // 返回 1.0
15+
* getModelConsumption("gpt-4") // 返回 2.0
16+
* getModelConsumption("unknown-model") // 返回 999
817
*/
918
function getModelConsumption(modelName: string): number {
19+
// 将模型消耗数据转换为 Map 结构,方便快速查询
1020
const consumptionMap = new Map(
1121
modelConsumptionData.models.map((m) => [m.name, m.consumption]),
1222
)
23+
24+
// 获取消耗值,未找到则返回 "N/A"
1325
const consumption = consumptionMap.get(modelName) || "N/A"
1426

27+
// 如果模型不在配置中,返回最大值 999
1528
if (consumption === "N/A") return 999
29+
30+
// 解析消耗值(格式如 "1.0x", "2.5x")
1631
const match = consumption.match(/^([\d.]+)x$/)
1732
return match ? Number.parseFloat(match[1]) : 999
1833
}
1934

2035
/**
21-
* Check if premium interactions usage is high (>50%)
36+
* 检查高级交互配额使用率是否过高(超过 50%)
37+
*
38+
* 当高级模型使用率超过 50% 时,系统会倾向于使用 0x 消耗的模型,
39+
* 以避免快速耗尽配额
40+
*
41+
* @returns true 表示使用率 >50%,false 表示使用率 ≤50% 或未初始化
2242
*/
2343
function isPremiumUsageHigh(): boolean {
44+
// 如果没有高级交互数据,认为使用率不高
2445
if (!state.premiumInteractions) {
2546
return false
2647
}
2748

49+
// 计算已使用百分比:100% - 剩余百分比 = 已使用百分比
2850
const usagePercent = 100 - state.premiumInteractions.percent_remaining
2951
return usagePercent > 50
3052
}
3153

3254
/**
33-
* Get all 0x consumption models
55+
* 获取所有 0x 消耗的模型列表
56+
*
57+
* 这些模型不计入高级交互配额,可以在配额紧张时优先使用
58+
*
59+
* @returns 0x 消耗模型的 ID 数组
3460
*/
3561
function getZeroConsumptionModels(): string[] {
62+
// 筛选出有效的可用模型(必须有上下文窗口限制配置)
3663
const availableModels = state.models?.data.filter(
3764
(m) => typeof m.capabilities?.limits?.max_context_window_tokens === "number",
3865
)
3966

4067
if (!availableModels) return []
4168

69+
// 过滤出消耗值为 0 的模型,返回其 ID
4270
return availableModels
4371
.filter((m) => getModelConsumption(m.name) === 0)
4472
.map((m) => m.id)
4573
}
4674

4775
/**
48-
* Find a matching model from available models
49-
* If exact match exists, return it
50-
* If no exact match, try to find by prefix (e.g., claude-haiku-4-5-xxx -> claude-haiku-4.5)
51-
* If premium usage >50%, only match to 0x consumption models
76+
* 从可用模型中查找匹配的模型
77+
*
78+
* 匹配策略:
79+
* 1. 标准化匹配:标准化后的模型名称匹配(下划线转连字符、版本号格式化)
80+
* 2. 前缀匹配:模型名称前缀匹配
81+
* 3. 基础名称匹配:忽略版本号后的基础名称匹配
82+
*
83+
* 配额保护:
84+
* - 当高级交互使用率 >50% 时,所有匹配仅限于 0x 消耗模型
85+
* - 无匹配时降级到第一个 0x 模型
86+
*
87+
* @param requestedModel - 请求的模型标识符
88+
* @returns 匹配的模型 ID,未找到返回 null
89+
*
90+
* @example
91+
* findMatchingModel("claude-3-5-sonnet") // 返回 "claude-3.5-sonnet"
92+
* findMatchingModel("gpt-4-20240101") // 返回 "gpt-4"
5293
*/
5394
export function findMatchingModel(requestedModel: string): string | null {
95+
// 获取所有有效的可用模型(必须配置了上下文窗口限制)
5496
const availableModels = state.models?.data.filter(
5597
(m) => typeof m.capabilities?.limits?.max_context_window_tokens === "number",
5698
)
5799

100+
// 如果没有可用模型,直接返回 null
58101
if (!availableModels || availableModels.length === 0) {
59102
return null
60103
}
61104

105+
// 检查是否处于高使用率状态
62106
const highUsage = isPremiumUsageHigh()
107+
108+
// 如果使用率高,获取 0x 消耗模型列表用于限制匹配范围
63109
const zeroConsumptionModels = highUsage ? getZeroConsumptionModels() : []
110+
111+
// 提取所有可用模型的 ID
64112
const allAvailableModelIds = availableModels.map((m) => m.id)
65113

66-
consola.debug(`Looking for match for: ${requestedModel}`)
67-
consola.debug(`All available models: ${allAvailableModelIds.join(", ")}`)
114+
consola.debug(`正在查找匹配模型:${requestedModel}`)
115+
consola.debug(`所有可用模型:${allAvailableModelIds.join(", ")}`)
68116

69-
// Try exact match first (always allow exact match, even if high usage)
70-
if (allAvailableModelIds.includes(requestedModel)) {
71-
// If high usage and model is not 0x, warn but still allow
72-
if (highUsage && !zeroConsumptionModels.includes(requestedModel)) {
73-
consola.warn(
74-
`⚠️ Premium usage >50%, but exact match found: ${requestedModel}`,
75-
)
76-
}
77-
return requestedModel
78-
}
79-
80-
// For fuzzy matching when usage is high, only consider 0x models
117+
// ========== 配额保护:高使用率时限制模糊匹配范围 ==========
81118
let availableModelIds = allAvailableModelIds
82119
if (highUsage && zeroConsumptionModels.length > 0) {
83120
consola.info(
84-
`⚠️ Premium usage >50%, restricting fuzzy matching to 0x consumption models`,
121+
`⚠️ 高级交互使用率 >50%,模糊匹配仅限 0x 消耗模型`,
85122
)
86123
availableModelIds = zeroConsumptionModels
87-
consola.debug(`0x models for matching: ${availableModelIds.join(", ")}`)
124+
consola.debug(`用于匹配的 0x 模型:${availableModelIds.join(", ")}`)
88125
}
89126

90-
// Normalize the requested model
91-
// 1. Replace underscores with hyphens
92-
// 2. Remove date suffix (8 digits at the end)
93-
// 3. Replace version numbers: 4-5 -> 4.5
127+
// ========== 标准化处理:统一模型名称格式 ==========
128+
// 1. 转换为小写
129+
// 2. 下划线转连字符(claude_3_5 -> claude-3-5)
130+
// 3. 移除日期后缀(-20251001 等 8 位数字)
131+
// 4. 版本号格式化(4-5 -> 4.5)
94132
let normalizedRequested = requestedModel
95133
.toLowerCase()
96-
.replace(/_/g, "-")
97-
.replace(/-(\d{8})$/, "") // Remove -20251001 style suffix
98-
.replace(/(\d)-(\d)/g, "$1.$2") // Replace 4-5 with 4.5
134+
.replace(/_/g, "-") // 下划线转连字符
135+
.replace(/-(\d{8})$/, "") // 移除 -20251001 风格的日期后缀
136+
.replace(/(\d)-(\d)/g, "$1.$2") // 版本号:4-5 -> 4.5
99137

100-
consola.debug(`Normalized requested: ${normalizedRequested}`)
138+
consola.debug(`标准化后的请求模型:${normalizedRequested}`)
101139

102-
// Try exact match after normalization
140+
// ========== 策略 1:标准化后精确匹配 ==========
103141
for (const availableId of availableModelIds) {
104142
if (availableId.toLowerCase() === normalizedRequested) {
105143
consola.info(
106-
`🔄 Model normalized match: '${requestedModel}' -> '${availableId}'`,
144+
`🔄 标准化匹配成功:'${requestedModel}' -> '${availableId}'`,
107145
)
108146
return availableId
109147
}
110148
}
111149

112-
// Try prefix matching
150+
// ========== 策略 2:前缀匹配 ==========
151+
// 检查请求的模型和可用模型是否有前缀关系
152+
// 例如:claude-3.5 可以匹配 claude-3.5-sonnet-20241022
113153
for (const availableId of availableModelIds) {
114154
const normalizedAvailable = availableId.toLowerCase()
115155

116-
// Check if they start with each other
156+
// 双向前缀检查:请求模型是可用模型的前缀,或可用模型是请求模型的前缀
117157
if (
118158
normalizedAvailable.startsWith(normalizedRequested) ||
119159
normalizedRequested.startsWith(normalizedAvailable)
120160
) {
121161
consola.info(
122-
`🔄 Model prefix match: '${requestedModel}' -> '${availableId}'`,
162+
`🔄 前缀匹配成功:'${requestedModel}' -> '${availableId}'`,
123163
)
124164
return availableId
125165
}
126166
}
127167

128-
// Try fuzzy matching by comparing main parts
168+
// ========== 策略 3:基础名称匹配(忽略版本号) ==========
169+
// 将模型名称按 "-" 分割,比较除最后一部分外的所有部分
170+
// 例如:claude-3-5-sonnet-v2 和 claude-3-5-sonnet-v1 的基础名称都是 claude-3-5-sonnet
129171
const requestedParts = normalizedRequested.split("-")
130172
for (const availableId of availableModelIds) {
131173
const normalizedAvailable = availableId.toLowerCase()
132174
const availableParts = normalizedAvailable.split("-")
133175

134-
// Match by comparing first N-1 parts (everything except version)
176+
// 只对至少有 3 个部分的模型名称进行基础匹配(避免过于宽泛)
135177
if (requestedParts.length >= 3 && availableParts.length >= 3) {
178+
// 提取基础名称(去掉最后一个部分,通常是版本号或日期)
136179
const requestedBase = requestedParts.slice(0, -1).join("-")
137180
const availableBase = availableParts.slice(0, -1).join("-")
138181

139182
if (requestedBase === availableBase) {
140183
consola.info(
141-
`🔄 Model base match: '${requestedModel}' -> '${availableId}'`,
184+
`🔄 基础名称匹配成功:'${requestedModel}' -> '${availableId}'`,
142185
)
143186
return availableId
144187
}
145188
}
146189
}
147190

148-
// Fallback: if high usage and no match found, use first 0x model
191+
// ========== 降级策略:使用率高时降级到第一个 0x 模型 ==========
149192
if (highUsage && zeroConsumptionModels.length > 0) {
150193
consola.warn(
151-
`⚠️ No matching 0x model found, falling back to: ${zeroConsumptionModels[0]}`,
194+
`⚠️ 未找到匹配的 0x 模型,降级到:${zeroConsumptionModels[0]}`,
152195
)
153196
return zeroConsumptionModels[0]
154197
}
155198

156-
consola.debug(`No match found for: ${requestedModel}`)
199+
// 所有策略都失败,返回 null
200+
consola.debug(`未找到匹配模型:${requestedModel}`)
157201
return null
158202
}
159203

160204
/**
161-
* Validate and potentially replace the requested model
162-
* Returns the validated model ID or throws/returns error info
205+
* 验证并替换请求的模型
206+
*
207+
* 该函数是模型匹配的主要入口点,负责:
208+
* 1. 调用 findMatchingModel 查找匹配的模型
209+
* 2. 验证模型是否可用
210+
* 3. 返回验证结果或错误信息
211+
*
212+
* @param requestedModel - 用户请求的模型标识符
213+
* @returns 包含验证结果的对象
214+
* - success: true 表示验证成功,false 表示失败
215+
* - model: 匹配的模型 ID(成功时)
216+
* - error: 错误详情(失败时)
217+
*
218+
* @example
219+
* // 成功匹配
220+
* validateAndReplaceModel("claude-3-5-sonnet")
221+
* // 返回:{ success: true, model: "claude-3.5-sonnet" }
222+
*
223+
* // 匹配失败
224+
* validateAndReplaceModel("unknown-model")
225+
* // 返回:{ success: false, error: { ... } }
163226
*/
164227
export function validateAndReplaceModel(requestedModel: string): {
165228
success: boolean
@@ -171,34 +234,40 @@ export function validateAndReplaceModel(requestedModel: string): {
171234
type: string
172235
}
173236
} {
237+
// 获取所有有效的可用模型列表
174238
const availableModels = state.models?.data.filter(
175239
(m) => typeof m.capabilities?.limits?.max_context_window_tokens === "number",
176240
)
177241
const availableModelIds = availableModels?.map((m) => m.id) || []
178242

243+
// 尝试查找匹配的模型
179244
const matchedModel = findMatchingModel(requestedModel)
180245

246+
// ========== 验证失败:未找到匹配的模型 ==========
181247
if (!matchedModel) {
182-
consola.error(`❌ Model not available: ${requestedModel}`)
183-
consola.error(`Available models: ${availableModelIds.join(", ")}`)
248+
consola.error(`❌ 模型不可用:${requestedModel}`)
249+
consola.error(`可用模型列表:${availableModelIds.join(", ")}`)
184250

185251
return {
186252
success: false,
187253
error: {
188-
message: `The requested model '${requestedModel}' is not supported. Available models: ${availableModelIds.join(", ")}`,
254+
message: `请求的模型 '${requestedModel}' 不受支持。可用模型:${availableModelIds.join(", ")}`,
189255
code: "model_not_supported",
190256
param: "model",
191257
type: "invalid_request_error",
192258
},
193259
}
194260
}
195261

262+
// ========== 验证成功:记录结果 ==========
196263
if (matchedModel !== requestedModel) {
264+
// 模型被替换(通过模糊匹配找到)
197265
consola.success(
198-
`✓ Model matched and replaced: ${requestedModel} -> ${matchedModel}`,
266+
`✓ 模型匹配并替换:${requestedModel} -> ${matchedModel}`,
199267
)
200268
} else {
201-
consola.success(`✓ Model validated: ${matchedModel}`)
269+
// 精确匹配
270+
consola.success(`✓ 模型验证通过:${matchedModel}`)
202271
}
203272

204273
return {

0 commit comments

Comments
 (0)