重构 TokenUtils: 从类改为对象,添加新的 pricing 和 usage 处理方法

- 将 TokenUtils 从类重构为对象
- 添加 getPricing 方法,支持 channel-specific 和 global pricing
- 添加 processUsage 方法,集中处理使用量计算和记录
- 在 providers/index.ts 中简化 usage 处理逻辑
- 添加 API key 掩码功能以保护敏感信息
This commit is contained in:
dreamhunter2333 committed 2025-06-27 02:08:16 +08:00
1 parent 3b6c5b37c1
commit 8d4af1b895
2 files changed
+46 -37

No files matched your search

+42 -4
View File
@@ -1,8 +1,11 @@
import { Context } from "hono";
// Token 工具类
export class TokenUtils {
static async updateUsage(c: Context<HonoCustomType>, key: string, usageAmount: number): Promise<boolean> {
import { CONSTANTS } from "../constants";
import utils from "../utils";
// Token 工具对象
export const TokenUtils = {
async updateUsage(c: Context<HonoCustomType>, key: string, usageAmount: number): Promise<boolean> {
try {
const result = await c.env.DB.prepare(
`UPDATE api_token SET usage = usage + ?, updated_at = datetime('now') WHERE key = ?`
@@ -13,5 +16,40 @@ export class TokenUtils {
console.error('Error updating token usage:', error);
return false;
}
},
async getPricing(c: Context<HonoCustomType>, model: string, channelConfig: ChannelConfig): Promise<ModelPricing | null> {
// Check channel-specific pricing first
if (channelConfig?.model_pricing?.[model]) {
return channelConfig.model_pricing[model];
}
// Fallback to global pricing
const globalPricingMap = await utils.getJsonSetting(c, CONSTANTS.MODEL_PRICING_KEY);
return globalPricingMap?.[model] || null;
},
async processUsage(c: Context<HonoCustomType>, apiKey: string, model: string, targetChannelKey: string, targetChannelConfig: ChannelConfig, usage: Usage): Promise<void> {
console.log("Usage data:", usage);
// Get pricing and calculate cost
const pricing = await this.getPricing(c, model, targetChannelConfig);
const hasTokens = usage.prompt_tokens && usage.completion_tokens;
if (pricing && hasTokens) {
const inputCost = usage.prompt_tokens! * pricing.input;
const outputCost = usage.completion_tokens! * pricing.output;
const totalCost = inputCost + outputCost;
// Update token usage and log data
await this.updateUsage(c, apiKey, totalCost);
// 长度不够直接显示 *
var maskedApiKey = apiKey.length < 3 ? '*'.repeat(apiKey.length) : (
apiKey.slice(0, apiKey.length / 3)
+ '*'.repeat(apiKey.length / 3)
+ apiKey.slice((2 * apiKey.length) / 3)
);
console.log(`Model: ${model}, Channel: ${targetChannelKey}, apiKey: ${maskedApiKey}, Cost: ${totalCost}`);
} else {
console.warn(`No pricing found for model: ${model} in channel: ${targetChannelKey}`);
}
}
}
};
+4 -33
View File
@@ -123,39 +123,10 @@ class ProxyEndpoint extends OpenAPIRoute {
return proxyFetch(c, targetChannelConfig,
async (usage: Usage) => {
console.log("Usage data:", usage);
// Calculate cost based on usage and model pricing
// Model was already extracted above
// Get global pricing as base configuration
let pricing = null;
const globalPricing = await utils.getSetting(c, CONSTANTS.MODEL_PRICING_KEY);
if (globalPricing) {
const globalPricingMap = JSON.parse(globalPricing);
pricing = globalPricingMap[model];
}
// Override with channel-specific pricing if available
if (targetChannelConfig.model_pricing?.[model]) {
pricing = targetChannelConfig.model_pricing[model];
}
if (pricing && usage.prompt_tokens && usage.completion_tokens) {
const inputCost = usage.prompt_tokens * pricing.input;
const outputCost = usage.completion_tokens * pricing.output;
const totalCost = inputCost + outputCost;
// Update token usage using TokenUtils
const updated = await TokenUtils.updateUsage(c, apiKey, totalCost);
if (!updated) {
console.error("Failed to update token usage");
}
console.log(`Model: ${model}, Channel: ${targetChannelKey}, Channel pricing: ${!!targetChannelConfig.model_pricing?.[model]}, Cost: ${totalCost}, Updated: ${updated}`);
}
else {
console.warn(`No pricing found for model: ${model} in channel: ${targetChannelKey}`);
try {
await TokenUtils.processUsage(c, apiKey, model, targetChannelKey, targetChannelConfig, usage);
} catch (error) {
console.error('Error processing usage:', error);
}
}
);