24.10-模型配置中心
要点
- 模型配置中心统一管理 LLM 提供商、模型列表、参数配置
- 支持多模型提供商:OpenAI、Anthropic、本地模型
- 租户可以配置模型偏好:默认模型、温度、最大 token 数
- 模型路由根据租户套餐、请求特征自动选择最优模型
内容
1. 模型配置架构
模型配置中心
├── 提供商管理 (Providers)
│ ├── OpenAI (GPT-4, GPT-3.5)
│ ├── Anthropic (Claude-3)
│ └── 自定义端点
├── 模型注册表 (Models)
│ ├── 模型元数据(名称、能力、价格)
│ ├── 调用配置(API Key、端点)
│ └── 限制配置(上下文长度、速率)
└── 租户配置 (Tenant Configs)
├── 默认模型
├── 参数偏好(温度、Top-P)
└── 可用模型列表
2. 数据模型
-- 模型提供商表
CREATE TABLE model_providers (
id TEXT PRIMARY KEY,
name TEXT NOT NULL, -- OpenAI, Anthropic
type TEXT NOT NULL, -- openai/anthropic/custom
api_endpoint TEXT NOT NULL,
api_key_encrypted TEXT NOT NULL, -- 加密存储
is_active INTEGER DEFAULT 1,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
-- 模型注册表
CREATE TABLE model_registry (
id TEXT PRIMARY KEY,
provider_id TEXT NOT NULL,
model_id TEXT NOT NULL, -- gpt-4, claude-3-opus
name TEXT NOT NULL, -- 显示名称
capabilities TEXT NOT NULL, -- JSON: chat, vision, function_calling
context_length INTEGER NOT NULL,
input_price_per_1k REAL NOT NULL, -- 每 1K token 价格(美分)
output_price_per_1k REAL NOT NULL,
max_output_tokens INTEGER,
is_available INTEGER DEFAULT 1,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY (provider_id) REFERENCES model_providers(id)
)
-- 租户模型配置表
CREATE TABLE tenant_model_configs (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL UNIQUE,
default_model TEXT NOT NULL,
temperature REAL DEFAULT 0.7,
top_p REAL DEFAULT 1.0,
max_tokens INTEGER DEFAULT 4000,
allowed_models TEXT, -- JSON 数组,空表示使用套餐默认
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY (tenant_id) REFERENCES tenants(id)
)TypeScript 类型:
// src/types/model.ts
export interface ModelProvider {
id: string
name: string
type: 'openai' | 'anthropic' | 'custom'
apiEndpoint: string
isActive: boolean
}
export interface ModelDefinition {
id: string
providerId: string
modelId: string
name: string
capabilities: {
chat: boolean
vision?: boolean
functionCalling?: boolean
}
contextLength: number
inputPricePer1k: number
outputPricePer1k: number
maxOutputTokens?: number
isAvailable: boolean
}
export interface TenantModelConfig {
id: string
tenantId: string
defaultModel: string
temperature: number
topP: number
maxTokens: number
allowedModels?: string[]
}3. 提供商管理
// src/routes/admin/models.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
import { requireRole } from '../../middleware/permission'
import { encrypt, decrypt } from '../../lib/crypto'
const app = new Hono()
// 添加提供商
app.post('/providers', requireRole('admin'), zValidator('json', z.object({
name: z.string(),
type: z.enum(['openai', 'anthropic', 'custom']),
apiEndpoint: z.string().url(),
apiKey: z.string(),
})), async (c) => {
const { name, type, apiEndpoint, apiKey } = c.req.valid('json')
const id = crypto.randomUUID()
const now = Date.now()
const encryptedKey = encrypt(apiKey, c.env.ENCRYPTION_KEY)
await c.env.DB.prepare(`
INSERT INTO model_providers (id, name, type, api_endpoint, api_key_encrypted, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
`).bind(id, name, type, apiEndpoint, encryptedKey, now, now).run()
return c.json({ id, name, type }, 201)
})
// 获取提供商列表
app.get('/providers', requireRole('admin'), async (c) => {
const providers = await c.env.DB.prepare(`
SELECT id, name, type, api_endpoint, is_active, created_at
FROM model_providers
ORDER BY created_at DESC
`).all()
return c.json({
providers: providers.results.map(p => ({
id: p.id,
name: p.name,
type: p.type,
apiEndpoint: p.api_endpoint,
isActive: p.is_active === 1,
})),
})
})4. 模型注册
// 注册模型
app.post('/models', requireRole('admin'), zValidator('json', z.object({
providerId: z.string(),
modelId: z.string(),
name: z.string(),
capabilities: z.object({
chat: z.boolean(),
vision: z.boolean().optional(),
functionCalling: z.boolean().optional(),
}),
contextLength: z.number(),
inputPricePer1k: z.number(),
outputPricePer1k: z.number(),
maxOutputTokens: z.number().optional(),
})), async (c) => {
const data = c.req.valid('json')
const id = crypto.randomUUID()
const now = Date.now()
await c.env.DB.prepare(`
INSERT INTO model_registry (id, provider_id, model_id, name, capabilities, context_length, input_price_per_1k, output_price_per_1k, max_output_tokens, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`).bind(
id,
data.providerId,
data.modelId,
data.name,
JSON.stringify(data.capabilities),
data.contextLength,
data.inputPricePer1k,
data.outputPricePer1k,
data.maxOutputTokens || null,
now,
now
).run()
return c.json({ id, modelId: data.modelId, name: data.name }, 201)
})
// 获取可用模型列表
app.get('/models', async (c) => {
const tenantId = c.get('tenantId')
// 获取租户配置
const config = await c.env.DB.prepare(`
SELECT * FROM tenant_model_configs WHERE tenant_id = ?
`).bind(tenantId).first()
// 获取套餐可用模型
const plan = c.get('plan')
let models
if (config?.allowed_models) {
// 使用租户自定义配置
const allowedIds = JSON.parse(config.allowed_models)
const placeholders = allowedIds.map(() => '?').join(',')
models = await c.env.DB.prepare(`
SELECT * FROM model_registry
WHERE model_id IN (${placeholders}) AND is_available = 1
`).bind(...allowedIds).all()
} else {
// 使用套餐默认
const placeholders = plan.allowedModels.map(() => '?').join(',')
models = await c.env.DB.prepare(`
SELECT * FROM model_registry
WHERE model_id IN (${placeholders}) AND is_available = 1
`).bind(...plan.allowedModels).all()
}
return c.json({
models: models.results.map(m => ({
id: m.id,
modelId: m.model_id,
name: m.name,
capabilities: JSON.parse(m.capabilities),
contextLength: m.context_length,
maxOutputTokens: m.max_output_tokens,
})),
})
})5. 租户模型配置
// src/routes/model-config.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
const app = new Hono()
// 获取租户模型配置
app.get('/', async (c) => {
const tenantId = c.get('tenantId')
let config = await c.env.DB.prepare(`
SELECT * FROM tenant_model_configs WHERE tenant_id = ?
`).bind(tenantId).first()
if (!config) {
// 返回默认配置
const plan = c.get('plan')
return c.json({
defaultModel: plan.allowedModels[0],
temperature: 0.7,
topP: 1.0,
maxTokens: 4000,
allowedModels: plan.allowedModels,
})
}
return c.json({
defaultModel: config.default_model,
temperature: config.temperature,
topP: config.top_p,
maxTokens: config.max_tokens,
allowedModels: config.allowed_models ? JSON.parse(config.allowed_models) : null,
})
})
// 更新租户模型配置
app.put('/', zValidator('json', z.object({
defaultModel: z.string().optional(),
temperature: z.number().min(0).max(2).optional(),
topP: z.number().min(0).max(1).optional(),
maxTokens: z.number().min(100).max(100000).optional(),
allowedModels: z.array(z.string()).optional(),
})), async (c) => {
const tenantId = c.get('tenantId')
const updates = c.req.valid('json')
// 验证默认模型在允许列表中
if (updates.defaultModel) {
const plan = c.get('plan')
const allowed = updates.allowedModels || plan.allowedModels
if (!allowed.includes(updates.defaultModel)) {
return c.json({ error: '默认模型不在允许列表中' }, 400)
}
}
const existing = await c.env.DB.prepare(`
SELECT id FROM tenant_model_configs WHERE tenant_id = ?
`).bind(tenantId).first()
const now = Date.now()
if (existing) {
// 更新
const setClauses = []
const values: any[] = []
if (updates.defaultModel) {
setClauses.push('default_model = ?')
values.push(updates.defaultModel)
}
if (updates.temperature !== undefined) {
setClauses.push('temperature = ?')
values.push(updates.temperature)
}
if (updates.topP !== undefined) {
setClauses.push('top_p = ?')
values.push(updates.topP)
}
if (updates.maxTokens !== undefined) {
setClauses.push('max_tokens = ?')
values.push(updates.maxTokens)
}
if (updates.allowedModels !== undefined) {
setClauses.push('allowed_models = ?')
values.push(JSON.stringify(updates.allowedModels))
}
setClauses.push('updated_at = ?')
values.push(now)
values.push(tenantId)
await c.env.DB.prepare(`
UPDATE tenant_model_configs SET ${setClauses.join(', ')} WHERE tenant_id = ?
`).bind(...values).run()
} else {
// 创建
const plan = c.get('plan')
const id = crypto.randomUUID()
await c.env.DB.prepare(`
INSERT INTO tenant_model_configs (id, tenant_id, default_model, temperature, top_p, max_tokens, allowed_models, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`).bind(
id,
tenantId,
updates.defaultModel || plan.allowedModels[0],
updates.temperature ?? 0.7,
updates.topP ?? 1.0,
updates.maxTokens ?? 4000,
updates.allowedModels ? JSON.stringify(updates.allowedModels) : null,
now,
now
).run()
}
return c.json({ success: true })
})6. 模型路由
// src/lib/model-router.ts
import { ModelDefinition, TenantModelConfig } from '../types/model'
export class ModelRouter {
constructor(private env: Env) {}
/**
* 根据请求选择最优模型
*/
async selectModel(params: {
tenantId: string
requestedModel?: string
requiredCapabilities?: string[]
messageCount?: number
estimatedTokens?: number
}): Promise<{ model: ModelDefinition; config: TenantModelConfig }> {
const { tenantId, requestedModel, requiredCapabilities, messageCount, estimatedTokens } = params
// 1. 获取租户配置
const config = await this.getTenantConfig(tenantId)
// 2. 获取可用模型
const availableModels = await this.getAvailableModels(tenantId)
// 3. 选择模型
let selectedModel: ModelDefinition
if (requestedModel) {
// 使用请求的模型(如果在可用列表中)
selectedModel = availableModels.find(m => m.modelId === requestedModel)!
if (!selectedModel) {
throw new Error(`Model ${requestedModel} is not available`)
}
} else {
// 自动选择
selectedModel = this.autoSelectModel({
models: availableModels,
defaultModel: config.defaultModel,
requiredCapabilities,
messageCount,
estimatedTokens,
})
}
return { model: selectedModel, config }
}
/**
* 自动选择模型
*/
private autoSelectModel(params: {
models: ModelDefinition[]
defaultModel: string
requiredCapabilities?: string[]
messageCount?: number
estimatedTokens?: number
}): ModelDefinition {
const { models, defaultModel, requiredCapabilities, messageCount, estimatedTokens } = params
// 过滤满足能力要求的模型
let candidates = models
if (requiredCapabilities?.length) {
candidates = candidates.filter(m => {
return requiredCapabilities.every(cap => {
return m.capabilities[cap as keyof typeof m.capabilities]
})
})
}
// 如果对话很长或 token 很多,使用大模型
if ((messageCount && messageCount > 20) || (estimatedTokens && estimatedTokens > 8000)) {
const largeModel = candidates.find(m =>
m.modelId.includes('gpt-4') || m.modelId.includes('claude-3-opus')
)
if (largeModel) return largeModel
}
// 使用默认模型
const defaultModelDef = candidates.find(m => m.modelId === defaultModel)
if (defaultModelDef) return defaultModelDef
// 返回第一个可用模型
return candidates[0]
}
private async getTenantConfig(tenantId: string): Promise<TenantModelConfig> {
const config = await this.env.DB.prepare(`
SELECT * FROM tenant_model_configs WHERE tenant_id = ?
`).bind(tenantId).first()
if (!config) {
// 返回默认配置
return {
id: '',
tenantId,
defaultModel: 'gpt-3.5-turbo',
temperature: 0.7,
topP: 1.0,
maxTokens: 4000,
}
}
return {
id: config.id,
tenantId: config.tenant_id,
defaultModel: config.default_model,
temperature: config.temperature,
topP: config.top_p,
maxTokens: config.max_tokens,
allowedModels: config.allowed_models ? JSON.parse(config.allowed_models) : undefined,
}
}
private async getAvailableModels(tenantId: string): Promise<ModelDefinition[]> {
const tenant = await this.env.DB.prepare(`
SELECT plan FROM tenants WHERE id = ?
`).bind(tenantId).first()
const plan = getPlan(tenant.plan)
const placeholders = plan.allowedModels.map(() => '?').join(',')
const models = await this.env.DB.prepare(`
SELECT * FROM model_registry
WHERE model_id IN (${placeholders}) AND is_available = 1
`).bind(...plan.allowedModels).all()
return models.results.map(m => ({
id: m.id,
providerId: m.provider_id,
modelId: m.model_id,
name: m.name,
capabilities: JSON.parse(m.capabilities),
contextLength: m.context_length,
inputPricePer1k: m.input_price_per_1k,
outputPricePer1k: m.output_price_per_1k,
maxOutputTokens: m.max_output_tokens,
isAvailable: m.is_available === 1,
}))
}
}7. 调用 LLM
// src/lib/llm.ts
import { ModelDefinition, TenantModelConfig } from '../types/model'
import { decrypt } from './crypto'
export async function callLLM(
messages: any[],
model: ModelDefinition,
config: TenantModelConfig,
env: Env
): Promise<{
content: string
usage: { promptTokens: number; completionTokens: number }
}> {
// 获取提供商信息
const provider = await env.DB.prepare(`
SELECT * FROM model_providers WHERE id = ?
`).bind(model.providerId).first()
if (!provider) {
throw new Error('Provider not found')
}
const apiKey = decrypt(provider.api_key_encrypted, env.ENCRYPTION_KEY)
// 根据提供商类型调用
switch (provider.type) {
case 'openai':
return callOpenAI(messages, model, config, apiKey, provider.api_endpoint)
case 'anthropic':
return callAnthropic(messages, model, config, apiKey, provider.api_endpoint)
default:
throw new Error('Unsupported provider type')
}
}
async function callOpenAI(
messages: any[],
model: ModelDefinition,
config: TenantModelConfig,
apiKey: string,
endpoint: string
) {
const response = await fetch(`${endpoint}/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
body: JSON.stringify({
model: model.modelId,
messages,
temperature: config.temperature,
top_p: config.topP,
max_tokens: config.maxTokens,
}),
})
const data = await response.json()
return {
content: data.choices[0].message.content,
usage: {
promptTokens: data.usage.prompt_tokens,
completionTokens: data.usage.completion_tokens,
},
}
}
async function callAnthropic(
messages: any[],
model: ModelDefinition,
config: TenantModelConfig,
apiKey: string,
endpoint: string
) {
const response = await fetch(`${endpoint}/messages`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': apiKey,
'anthropic-version': '2023-06-01',
},
body: JSON.stringify({
model: model.modelId,
messages,
temperature: config.temperature,
top_p: config.topP,
max_tokens: config.maxTokens,
}),
})
const data = await response.json()
return {
content: data.content[0].text,
usage: {
promptTokens: data.usage.input_tokens,
completionTokens: data.usage.output_tokens,
},
}
}8. 小结
模型配置中心的关键点:
- 提供商管理:统一管理 OpenAI、Anthropic 等提供商的 API Key
- 模型注册:记录模型的能力、价格、限制
- 租户配置:每个租户可以配置默认模型、参数
- 模型路由:根据请求特征自动选择最优模型
- 统一调用:封装不同提供商的 API 差异
一句话带走:
模型配置中心是「提供商 + 模型 + 租户配置」的三层架构。提供商管理 API Key,模型注册表记录能力,租户配置控制偏好。模型路由根据请求特征自动选择,统一调用接口屏蔽差异。