feat: Add database table for model context length configuration (#477)
* feat: add database table for model context length configuration - Add model_context table with provider/model/context_limit fields - Implement UPSERT endpoint for model context configuration - Add priority lookup: database > config.yaml > custom_providers > cache - Add frontend click-to-edit UI in ChatInput with tooltip - Add i18n support for context editing dialog (all 8 locales) - Use context_limit field consistently across frontend and backend Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: use useMessage() composable instead of window.$message in ChatInput - Remove incorrect NMessage import (not a component) - Use useMessage() composable from naive-ui - Replace window.$message?.xxx() with message.xxx() - Fixes TypeScript build errors Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import { request } from '../client'
|
||||
|
||||
export interface ModelContext {
|
||||
id: number
|
||||
provider: string
|
||||
model: string
|
||||
context_limit: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 provider 和 model 查询模型上下文配置
|
||||
*/
|
||||
export async function getModelContext(provider: string, model: string): Promise<ModelContext | null> {
|
||||
try {
|
||||
const res = await request<{ data: ModelContext }>(
|
||||
`/api/hermes/model-context?provider=${encodeURIComponent(provider)}&model=${encodeURIComponent(model)}`
|
||||
)
|
||||
return res.data
|
||||
} catch (err: any) {
|
||||
if (err.status === 404) return null
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置模型上下文配置(UPSERT:存在则更新,不存在则插入)
|
||||
*/
|
||||
export async function setModelContext(
|
||||
provider: string,
|
||||
model: string,
|
||||
contextLimit: number
|
||||
): Promise<ModelContext> {
|
||||
const res = await request<{ success: boolean; data: ModelContext }>(
|
||||
`/api/hermes/model-context/${encodeURIComponent(provider)}/${encodeURIComponent(model)}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ provider, model, context_limit: contextLimit }),
|
||||
}
|
||||
)
|
||||
return res.data
|
||||
}
|
||||
@@ -4,12 +4,14 @@ import { useChatStore } from '@/stores/hermes/chat'
|
||||
import { useAppStore } from '@/stores/hermes/app'
|
||||
import { useProfilesStore } from '@/stores/hermes/profiles'
|
||||
import { fetchContextLength } from '@/api/hermes/sessions'
|
||||
import { NButton, NTooltip, NSwitch } from 'naive-ui'
|
||||
import { setModelContext } from '@/api/hermes/model-context'
|
||||
import { NButton, NTooltip, NSwitch, NModal, NInputNumber, useMessage } from 'naive-ui'
|
||||
import { computed, ref, onMounted, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const chatStore = useChatStore()
|
||||
const { t } = useI18n()
|
||||
const message = useMessage()
|
||||
const inputText = ref('')
|
||||
const textareaRef = ref<HTMLTextAreaElement>()
|
||||
const fileInputRef = ref<HTMLInputElement>()
|
||||
@@ -45,6 +47,44 @@ const canSend = computed(() => inputText.value.trim() || attachments.value.lengt
|
||||
const contextLength = ref(200000)
|
||||
const FALLBACK_CONTEXT = 200000
|
||||
|
||||
// Context length editing
|
||||
const showContextEditModal = ref(false)
|
||||
const editingContextLimit = ref(200000)
|
||||
const isSavingContextLimit = ref(false)
|
||||
|
||||
async function handleEditContextLimit() {
|
||||
editingContextLimit.value = contextLength.value
|
||||
showContextEditModal.value = true
|
||||
}
|
||||
|
||||
async function saveContextLimit() {
|
||||
if (!editingContextLimit.value || editingContextLimit.value <= 0) {
|
||||
message.error(t('chat.contextEditInvalid'))
|
||||
return
|
||||
}
|
||||
|
||||
isSavingContextLimit.value = true
|
||||
try {
|
||||
const appStore = useAppStore()
|
||||
const provider = appStore.selectedProvider || ''
|
||||
const model = appStore.selectedModel || ''
|
||||
|
||||
if (!provider || !model) {
|
||||
message.error(t('chat.contextEditFailed'))
|
||||
return
|
||||
}
|
||||
|
||||
await setModelContext(provider, model, editingContextLimit.value)
|
||||
contextLength.value = editingContextLimit.value
|
||||
showContextEditModal.value = false
|
||||
message.success(t('chat.contextEditSuccess'))
|
||||
} catch (err: any) {
|
||||
message.error(`${t('chat.contextEditFailed')}: ${err.message || ''}`)
|
||||
} finally {
|
||||
isSavingContextLimit.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadContextLength() {
|
||||
try {
|
||||
const profile = useProfilesStore().activeProfileName || undefined
|
||||
@@ -247,7 +287,16 @@ function isImage(type: string): boolean {
|
||||
</div>
|
||||
|
||||
<span v-if="totalTokens > 0" class="context-info" :class="{ 'context-warning': usagePercent > 80 }">
|
||||
{{ formatTokens(totalTokens) }} / {{ formatTokens(contextLength) }} · {{ t('chat.contextRemaining') }} {{ formatTokens(remainingTokens) }}
|
||||
{{ formatTokens(totalTokens) }} /
|
||||
<NTooltip trigger="hover">
|
||||
<template #trigger>
|
||||
<span class="context-limit-editable" @click="handleEditContextLimit">
|
||||
{{ formatTokens(contextLength) }}
|
||||
</span>
|
||||
</template>
|
||||
<span>{{ t('chat.contextClickToEdit') }}</span>
|
||||
</NTooltip>
|
||||
· {{ t('chat.contextRemaining') }} {{ formatTokens(remainingTokens) }}
|
||||
</span>
|
||||
<div v-if="totalTokens > 0" class="context-bar">
|
||||
<div
|
||||
@@ -335,6 +384,47 @@ function isImage(type: string): boolean {
|
||||
</NButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Context Length Edit Modal -->
|
||||
<NModal
|
||||
v-model:show="showContextEditModal"
|
||||
:title="t('chat.contextEditTitle')"
|
||||
:mask-closable="true"
|
||||
preset="card"
|
||||
style="width: 400px"
|
||||
>
|
||||
<div class="context-edit-content">
|
||||
<p style="margin-bottom: 16px; color: #666;">
|
||||
{{ t('chat.contextEditDesc') }}
|
||||
</p>
|
||||
<NInputNumber
|
||||
v-model:value="editingContextLimit"
|
||||
:min="1000"
|
||||
:max="10000000"
|
||||
:step="1000"
|
||||
:show-button="false"
|
||||
:placeholder="t('chat.contextEditPlaceholder')"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #suffix>
|
||||
<span style="color: #999;">tokens</span>
|
||||
</template>
|
||||
</NInputNumber>
|
||||
<div style="margin-top: 12px; font-size: 12px; color: #999;">
|
||||
{{ t('chat.contextEditHint') }}
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div style="display: flex; justify-content: flex-end; gap: 8px;">
|
||||
<NButton @click="showContextEditModal = false" :disabled="isSavingContextLimit">
|
||||
{{ t('chat.contextEditCancel') }}
|
||||
</NButton>
|
||||
<NButton type="primary" @click="saveContextLimit" :loading="isSavingContextLimit">
|
||||
{{ t('chat.contextEditSave') }}
|
||||
</NButton>
|
||||
</div>
|
||||
</template>
|
||||
</NModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -383,6 +473,19 @@ function isImage(type: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
.context-limit-editable {
|
||||
cursor: pointer;
|
||||
border-bottom: 1px dashed transparent;
|
||||
transition: all 0.2s ease;
|
||||
padding: 0 2px;
|
||||
|
||||
&:hover {
|
||||
border-bottom-color: $text-muted;
|
||||
background: rgba(128, 128, 128, 0.1);
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.context-bar {
|
||||
width: 60px;
|
||||
height: 4px;
|
||||
|
||||
@@ -104,6 +104,16 @@ export default {
|
||||
// Chat
|
||||
chat: {
|
||||
contextRemaining: 'übrig',
|
||||
contextClickToEdit: 'Klicken zum Bearbeiten der Kontextlänge',
|
||||
contextEditTitle: 'Kontextlänge bearbeiten',
|
||||
contextEditDesc: 'Kontextlängenlimit für aktuelles Modell festlegen (in Tokens)',
|
||||
contextEditPlaceholder: 'Kontextlänge eingeben',
|
||||
contextEditHint: 'Häufige Werte: 200k (Claude), 128k (GPT-4), 32k (GPT-3.5)',
|
||||
contextEditSave: 'Speichern',
|
||||
contextEditCancel: 'Abbrechen',
|
||||
contextEditInvalid: 'Bitte geben Sie eine gültige Kontextlänge ein',
|
||||
contextEditSuccess: 'Kontextlänge aktualisiert',
|
||||
contextEditFailed: 'Aktualisierung fehlgeschlagen',
|
||||
emptyState: 'Starten Sie eine Konversation mit Hermes Agent',
|
||||
inputPlaceholder: 'Nachricht eingeben... (Enter zum Senden, Shift+Enter fur neue Zeile)',
|
||||
attachFiles: 'Dateien anhangen',
|
||||
|
||||
@@ -114,6 +114,16 @@ export default {
|
||||
// Chat
|
||||
chat: {
|
||||
contextRemaining: 'remaining',
|
||||
contextClickToEdit: 'Click to edit context length',
|
||||
contextEditTitle: 'Edit Context Length',
|
||||
contextEditDesc: 'Set context length limit for current model (in tokens)',
|
||||
contextEditPlaceholder: 'Enter context length',
|
||||
contextEditHint: 'Common values: 200k (Claude), 128k (GPT-4), 32k (GPT-3.5)',
|
||||
contextEditSave: 'Save',
|
||||
contextEditCancel: 'Cancel',
|
||||
contextEditInvalid: 'Please enter a valid context length',
|
||||
contextEditSuccess: 'Context length updated',
|
||||
contextEditFailed: 'Update failed',
|
||||
emptyState: 'Start a conversation with Hermes Agent',
|
||||
inputPlaceholder: 'Type a message... (Enter to send, Shift+Enter for new line)',
|
||||
attachFiles: 'Attach files',
|
||||
|
||||
@@ -104,6 +104,16 @@ export default {
|
||||
// Chat
|
||||
chat: {
|
||||
contextRemaining: 'restante',
|
||||
contextClickToEdit: 'Haz clic para editar la longitud del contexto',
|
||||
contextEditTitle: 'Editar longitud del contexto',
|
||||
contextEditDesc: 'Establecer el límite de longitud del contexto para el modelo actual (en tokens)',
|
||||
contextEditPlaceholder: 'Ingresa la longitud del contexto',
|
||||
contextEditHint: 'Valores comunes: 200k (Claude), 128k (GPT-4), 32k (GPT-3.5)',
|
||||
contextEditSave: 'Guardar',
|
||||
contextEditCancel: 'Cancelar',
|
||||
contextEditInvalid: 'Por favor ingresa una longitud de contexto válida',
|
||||
contextEditSuccess: 'Longitud del contexto actualizada',
|
||||
contextEditFailed: 'Error en la actualización',
|
||||
emptyState: 'Inicia una conversacion con Hermes Agent',
|
||||
inputPlaceholder: 'Escribe un mensaje... (Enter para enviar, Shift+Enter para nueva linea)',
|
||||
attachFiles: 'Adjuntar archivos',
|
||||
|
||||
@@ -104,6 +104,16 @@ export default {
|
||||
// Chat
|
||||
chat: {
|
||||
contextRemaining: 'restant',
|
||||
contextClickToEdit: 'Cliquez pour modifier la longueur du contexte',
|
||||
contextEditTitle: 'Modifier la longueur du contexte',
|
||||
contextEditDesc: 'Définir la limite de longueur du contexte pour le modèle actuel (en tokens)',
|
||||
contextEditPlaceholder: 'Entrez la longueur du contexte',
|
||||
contextEditHint: 'Valeurs courantes : 200k (Claude), 128k (GPT-4), 32k (GPT-3.5)',
|
||||
contextEditSave: 'Enregistrer',
|
||||
contextEditCancel: 'Annuler',
|
||||
contextEditInvalid: 'Veuillez entrer une longueur de contexte valide',
|
||||
contextEditSuccess: 'Longueur du contexte mise à jour',
|
||||
contextEditFailed: 'Échec de la mise à jour',
|
||||
emptyState: 'Demarrer une conversation avec Hermes Agent',
|
||||
inputPlaceholder: 'Tapez un message... (Entree pour envoyer, Shift+Entree pour un saut de ligne)',
|
||||
attachFiles: 'Joindre des fichiers',
|
||||
|
||||
@@ -104,6 +104,16 @@ export default {
|
||||
// チャット
|
||||
chat: {
|
||||
contextRemaining: '残り',
|
||||
contextClickToEdit: 'クリックしてコンテキスト長を編集',
|
||||
contextEditTitle: 'コンテキスト長を編集',
|
||||
contextEditDesc: '現在のモデルのコンテキスト長制限を設定(トークン数)',
|
||||
contextEditPlaceholder: 'コンテキスト長を入力',
|
||||
contextEditHint: '一般的な値:200k (Claude), 128k (GPT-4), 32k (GPT-3.5)',
|
||||
contextEditSave: '保存',
|
||||
contextEditCancel: 'キャンセル',
|
||||
contextEditInvalid: '有効なコンテキスト長を入力してください',
|
||||
contextEditSuccess: 'コンテキスト長を更新しました',
|
||||
contextEditFailed: '更新に失敗しました',
|
||||
emptyState: 'Hermes Agent と会話を開始しましょう',
|
||||
inputPlaceholder: 'メッセージを入力... (Enter で送信、Shift+Enter で改行)',
|
||||
attachFiles: 'ファイルを添付',
|
||||
|
||||
@@ -104,6 +104,16 @@ export default {
|
||||
// 채팅
|
||||
chat: {
|
||||
contextRemaining: '남음',
|
||||
contextClickToEdit: '클릭하여 컨텍스트 길이 편집',
|
||||
contextEditTitle: '컨텍스트 길이 편집',
|
||||
contextEditDesc: '현재 모델의 컨텍스트 길이 제한 설정 (토큰 수)',
|
||||
contextEditPlaceholder: '컨텍스트 길이 입력',
|
||||
contextEditHint: '일반적인 값: 200k (Claude), 128k (GPT-4), 32k (GPT-3.5)',
|
||||
contextEditSave: '저장',
|
||||
contextEditCancel: '취소',
|
||||
contextEditInvalid: '유효한 컨텍스트 길이를 입력하세요',
|
||||
contextEditSuccess: '컨텍스트 길이가 업데이트되었습니다',
|
||||
contextEditFailed: '업데이트 실패',
|
||||
emptyState: 'Hermes Agent와 대화를 시작하세요',
|
||||
inputPlaceholder: '메시지를 입력하세요... (Enter로 전송, Shift+Enter로 줄바꿈)',
|
||||
attachFiles: '파일 첨부',
|
||||
|
||||
@@ -104,6 +104,16 @@ export default {
|
||||
// Chat
|
||||
chat: {
|
||||
contextRemaining: 'restante',
|
||||
contextClickToEdit: 'Clique para editar o tamanho do contexto',
|
||||
contextEditTitle: 'Editar tamanho do contexto',
|
||||
contextEditDesc: 'Definir o limite de tamanho do contexto para o modelo atual (em tokens)',
|
||||
contextEditPlaceholder: 'Digite o tamanho do contexto',
|
||||
contextEditHint: 'Valores comuns: 200k (Claude), 128k (GPT-4), 32k (GPT-3.5)',
|
||||
contextEditSave: 'Salvar',
|
||||
contextEditCancel: 'Cancelar',
|
||||
contextEditInvalid: 'Por favor, insira um tamanho de contexto válido',
|
||||
contextEditSuccess: 'Tamanho do contexto atualizado',
|
||||
contextEditFailed: 'Falha na atualização',
|
||||
emptyState: 'Inicie uma conversa com o Hermes Agent',
|
||||
inputPlaceholder: 'Digite uma mensagem... (Enter para enviar, Shift+Enter para nova linha)',
|
||||
attachFiles: 'Anexar arquivos',
|
||||
|
||||
@@ -114,6 +114,16 @@ export default {
|
||||
// 对话
|
||||
chat: {
|
||||
contextRemaining: '剩余',
|
||||
contextClickToEdit: '点击编辑上下文长度',
|
||||
contextEditTitle: '编辑上下文长度',
|
||||
contextEditDesc: '设置当前模型的上下文长度限制(token 数量)',
|
||||
contextEditPlaceholder: '请输入上下文长度',
|
||||
contextEditHint: '常见值:200k (Claude), 128k (GPT-4), 32k (GPT-3.5)',
|
||||
contextEditSave: '保存',
|
||||
contextEditCancel: '取消',
|
||||
contextEditInvalid: '请输入有效的上下文长度',
|
||||
contextEditSuccess: '上下文长度已更新',
|
||||
contextEditFailed: '更新失败',
|
||||
emptyState: '开始与 Hermes Agent 对话',
|
||||
inputPlaceholder: '输入消息... (Enter 发送,Shift+Enter 换行)',
|
||||
attachFiles: '添加附件',
|
||||
|
||||
@@ -5,6 +5,8 @@ import { readConfigYaml, writeConfigYaml, fetchProviderModels, buildModelGroups,
|
||||
import { buildProviderModelMap, PROVIDER_PRESETS } from '../../shared/providers'
|
||||
import { getCopilotModelsDetailed, resolveCopilotOAuthToken, type CopilotModelMeta } from '../../services/hermes/copilot-models'
|
||||
import { readAppConfig } from '../../services/app-config'
|
||||
import { getDb } from '../../db'
|
||||
import { MODEL_CONTEXT_TABLE } from '../../db/hermes/schemas'
|
||||
|
||||
const PROVIDER_MODEL_CATALOG = buildProviderModelMap()
|
||||
|
||||
@@ -236,3 +238,126 @@ export async function setConfigModel(ctx: any) {
|
||||
ctx.body = { error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置模型上下文配置(UPSERT:存在则更新,不存在则插入)
|
||||
* 支持路径参数和查询参数两种方式
|
||||
*/
|
||||
export async function updateModelContext(ctx: any) {
|
||||
// 支持两种方式:
|
||||
// 1. 路径参数: /api/hermes/model-context/:provider/:model
|
||||
// 2. 查询参数: /api/hermes/model-context?provider=xxx&model=xxx
|
||||
let provider: string | undefined
|
||||
let model: string | undefined
|
||||
|
||||
// 优先从路径参数获取
|
||||
if (ctx.params.provider && ctx.params.model) {
|
||||
provider = ctx.params.provider
|
||||
model = ctx.params.model
|
||||
} else {
|
||||
// 从查询参数获取
|
||||
const query = ctx.query as { provider?: string; model?: string }
|
||||
provider = query.provider
|
||||
model = query.model
|
||||
}
|
||||
|
||||
// 如果没有参数,从请求体获取
|
||||
if (!provider || !model) {
|
||||
const body = ctx.request.body as { provider?: string; model?: string; context_limit?: number }
|
||||
provider = body.provider
|
||||
model = body.model
|
||||
}
|
||||
|
||||
const { context_limit } = ctx.request.body as { context_limit: number }
|
||||
|
||||
if (!provider || !model || !context_limit) {
|
||||
ctx.status = 400
|
||||
ctx.body = { error: 'Missing required fields: provider, model, context_limit' }
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof context_limit !== 'number' || context_limit <= 0) {
|
||||
ctx.status = 400
|
||||
ctx.body = { error: 'Context limit must be a positive number' }
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const db = getDb()
|
||||
if (!db) {
|
||||
ctx.status = 500
|
||||
ctx.body = { error: 'Database not available' }
|
||||
return
|
||||
}
|
||||
|
||||
// 使用 REPLACE 实现 UPSERT:存在则替换,不存在则插入
|
||||
db.prepare(
|
||||
`REPLACE INTO ${MODEL_CONTEXT_TABLE} (provider, model, context_limit) VALUES (?, ?, ?)`
|
||||
).run(provider, model, context_limit)
|
||||
|
||||
// 查询并返回更新后的数据
|
||||
const row = db.prepare(
|
||||
`SELECT id, provider, model, context_limit FROM ${MODEL_CONTEXT_TABLE} WHERE provider = ? AND model = ?`
|
||||
).get(provider, model) as { id: number; provider: string; model: string; context_limit: number }
|
||||
|
||||
ctx.body = {
|
||||
success: true,
|
||||
data: row
|
||||
}
|
||||
} catch (err: any) {
|
||||
ctx.status = 500
|
||||
ctx.body = { error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询模型上下文配置
|
||||
*/
|
||||
export async function getModelContext(ctx: any) {
|
||||
// 支持两种方式:
|
||||
// 1. 路径参数: /api/hermes/model-context/:provider/:model
|
||||
// 2. 查询参数: /api/hermes/model-context?provider=xxx&model=xxx
|
||||
let provider: string | undefined
|
||||
let model: string | undefined
|
||||
|
||||
// 优先从路径参数获取
|
||||
if (ctx.params.provider && ctx.params.model) {
|
||||
provider = ctx.params.provider
|
||||
model = ctx.params.model
|
||||
} else {
|
||||
// 从查询参数获取
|
||||
const query = ctx.query as { provider?: string; model?: string }
|
||||
provider = query.provider
|
||||
model = query.model
|
||||
}
|
||||
|
||||
if (!provider || !model) {
|
||||
ctx.status = 400
|
||||
ctx.body = { error: 'Missing provider or model parameter' }
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const db = getDb()
|
||||
if (!db) {
|
||||
ctx.status = 500
|
||||
ctx.body = { error: 'Database not available' }
|
||||
return
|
||||
}
|
||||
|
||||
const row = db.prepare(
|
||||
`SELECT id, provider, model, context_limit FROM ${MODEL_CONTEXT_TABLE} WHERE provider = ? AND model = ?`
|
||||
).get(provider, model) as { id: number; provider: string; model: string; context_limit: number } | undefined
|
||||
|
||||
if (!row) {
|
||||
ctx.status = 404
|
||||
ctx.body = { error: 'Model context not found' }
|
||||
return
|
||||
}
|
||||
|
||||
ctx.body = { data: { ...row, limit: row.context_limit } }
|
||||
} catch (err: any) {
|
||||
ctx.status = 500
|
||||
ctx.body = { error: err.message }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,21 @@ export const COMPRESSION_SNAPSHOT_SCHEMA: Record<string, string> = {
|
||||
updated_at: 'INTEGER NOT NULL',
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Model Context (model-context.ts)
|
||||
// ============================================================================
|
||||
|
||||
export const MODEL_CONTEXT_TABLE = 'model_context'
|
||||
|
||||
export const MODEL_CONTEXT_SCHEMA: Record<string, string> = {
|
||||
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
|
||||
provider: 'TEXT NOT NULL',
|
||||
model: 'TEXT NOT NULL',
|
||||
context_limit: 'INTEGER NOT NULL',
|
||||
}
|
||||
|
||||
export const MODEL_CONTEXT_INDEX = 'CREATE UNIQUE INDEX IF NOT EXISTS idx_model_context_provider_model ON model_context(provider, model)'
|
||||
|
||||
// ============================================================================
|
||||
// Group Chat (services/hermes/group-chat/index.ts)
|
||||
// ============================================================================
|
||||
@@ -469,6 +484,13 @@ export function initAllHermesTables(retryCount = 0): void {
|
||||
// Compression snapshot
|
||||
syncTable(COMPRESSION_SNAPSHOT_TABLE, COMPRESSION_SNAPSHOT_SCHEMA)
|
||||
|
||||
// Model context
|
||||
syncTable(MODEL_CONTEXT_TABLE, MODEL_CONTEXT_SCHEMA, {
|
||||
indexes: {
|
||||
idx_model_context_provider_model: MODEL_CONTEXT_INDEX,
|
||||
}
|
||||
})
|
||||
|
||||
// Group chat - basic tables
|
||||
syncTable(GC_ROOMS_TABLE, GC_ROOMS_SCHEMA)
|
||||
syncTable(GC_MESSAGES_TABLE, GC_MESSAGES_SCHEMA)
|
||||
|
||||
@@ -6,3 +6,9 @@ export const modelRoutes = new Router()
|
||||
modelRoutes.get('/api/hermes/available-models', ctrl.getAvailable)
|
||||
modelRoutes.get('/api/hermes/config/models', ctrl.getConfigModels)
|
||||
modelRoutes.put('/api/hermes/config/model', ctrl.setConfigModel)
|
||||
|
||||
// Model context routes
|
||||
modelRoutes.get('/api/hermes/model-context', ctrl.getModelContext)
|
||||
modelRoutes.get('/api/hermes/model-context/:provider/:model', ctrl.getModelContext)
|
||||
modelRoutes.put('/api/hermes/model-context/:provider/:model', ctrl.updateModelContext)
|
||||
modelRoutes.put('/api/hermes/model-context', ctrl.updateModelContext)
|
||||
|
||||
@@ -2,6 +2,8 @@ import { resolve, join } from 'path'
|
||||
import { homedir } from 'os'
|
||||
import { readFileSync, existsSync, statSync } from 'fs'
|
||||
import yaml from 'js-yaml'
|
||||
import { getDb } from '../../db'
|
||||
import { MODEL_CONTEXT_TABLE } from '../../db/hermes/schemas'
|
||||
|
||||
const HERMES_BASE = resolve(homedir(), '.hermes')
|
||||
const MODELS_DEV_CACHE = resolve(HERMES_BASE, 'models_dev_cache.json')
|
||||
@@ -234,6 +236,25 @@ function lookupContextFromCache(modelName: string, provider: string | null): num
|
||||
* 3. models_dev_cache.json, scoped to model.provider when configured
|
||||
* 4. DEFAULT_CONTEXT_LENGTH (200K hardcoded fallback)
|
||||
*/
|
||||
/**
|
||||
* 从数据库 model_context 表查找上下文长度(最高优先级)
|
||||
*/
|
||||
function lookupContextFromDatabase(modelName: string, provider: string | null): number | null {
|
||||
const db = getDb()
|
||||
if (!db) return null
|
||||
|
||||
try {
|
||||
// 尝试精确匹配 provider 和 model
|
||||
const row = db
|
||||
.prepare(`SELECT context_limit FROM ${MODEL_CONTEXT_TABLE} WHERE provider = ? AND model = ?`)
|
||||
.get(provider || 'default', modelName) as { context_limit: number } | undefined
|
||||
|
||||
return row?.context_limit || null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function getModelContextLength(profile?: string): number {
|
||||
const profileDir = getProfileDir(profile)
|
||||
const config = loadConfig(profileDir)
|
||||
@@ -242,12 +263,17 @@ export function getModelContextLength(profile?: string): number {
|
||||
const model = getDefaultModel(config)
|
||||
if (!model) return DEFAULT_CONTEXT_LENGTH
|
||||
|
||||
const provider = getDefaultProvider(config)
|
||||
|
||||
// 0. Database model_context table (highest priority)
|
||||
const dbCtx = lookupContextFromDatabase(model, provider)
|
||||
if (dbCtx && dbCtx > 0) return dbCtx
|
||||
|
||||
// 1. Global context_length override in config.yaml
|
||||
const configCtx = getConfigContextLength(config)
|
||||
if (configCtx && configCtx > 0) return configCtx
|
||||
|
||||
// 2. Custom provider context_length
|
||||
const provider = getDefaultProvider(config)
|
||||
const customCtx = lookupCustomProviderContextLength(config, model, provider)
|
||||
if (customCtx && customCtx > 0) return customCtx
|
||||
|
||||
|
||||
Reference in New Issue
Block a user