mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-26 02:04:37 +08:00
refactor(ai): unify LLM configuration storage
This commit is contained in:
1 parent
c11ca38472
commit
7bf7768c03
33 files changed
+1245
-1440
No files matched your search
@@ -3,11 +3,19 @@
|
||||
* for the shared HTTP route context.
|
||||
*/
|
||||
|
||||
import type { DatabaseManager, AIChatManager, AgentStreamChunk, SemanticIndexRuntime } from '@openchatlab/node-runtime'
|
||||
import type {
|
||||
DatabaseManager,
|
||||
AIChatManager,
|
||||
AgentStreamChunk,
|
||||
LLMConfigStore,
|
||||
SemanticIndexRuntime,
|
||||
} from '@openchatlab/node-runtime'
|
||||
import {
|
||||
CHART_CAPABILITY_CORE_TOOLS,
|
||||
SkillManager,
|
||||
buildPiModel,
|
||||
buildSkillMenuWithBuiltinChart,
|
||||
createLlmRuntimeStores,
|
||||
createActivateSkillTool,
|
||||
createDataSnapshotFromOverview,
|
||||
getAllowedBuiltinToolsForChartAutoSkill,
|
||||
@@ -22,7 +30,6 @@ import type { AgentStreamRequest } from '@openchatlab/http-routes'
|
||||
import { AGENT_TOOL_REGISTRY, SEMANTIC_SEARCH_TOOL_NAME } from '@openchatlab/tools'
|
||||
import { buildSemanticSearchGuidance } from '@openchatlab/node-runtime'
|
||||
import { adaptToolsForAgent } from './tool-adapter'
|
||||
import { getDefaultAssistantConfig, buildPiModel } from './llm-config'
|
||||
import { loadAssistantConfig } from './assistant-loader'
|
||||
import { runServerAgent } from './agent'
|
||||
|
||||
@@ -65,8 +72,15 @@ export function getAllowedToolSet(
|
||||
export function createCliRunAgentStream(
|
||||
dbManager: DatabaseManager,
|
||||
aiChatManager: AIChatManager,
|
||||
semanticIndexService?: SemanticIndexRuntime
|
||||
options: {
|
||||
llmConfigStore?: LLMConfigStore
|
||||
semanticIndexService?: SemanticIndexRuntime
|
||||
} = {}
|
||||
): (params: AgentStreamRequest, onEvent: (chunk: AgentStreamChunk) => void, abortSignal: AbortSignal) => Promise<void> {
|
||||
const aiDataDir = getAiDir(dbManager)
|
||||
const llmConfigStore = options.llmConfigStore ?? createLlmRuntimeStores(aiDataDir).llmConfigStore
|
||||
const semanticIndexService = options.semanticIndexService
|
||||
|
||||
return async (params, onEvent, abortSignal) => {
|
||||
const {
|
||||
userMessage,
|
||||
@@ -85,8 +99,6 @@ export function createCliRunAgentStream(
|
||||
thinkingLevel,
|
||||
} = params
|
||||
|
||||
const aiDataDir = getAiDir(dbManager)
|
||||
|
||||
let assistantSystemPrompt: string | undefined
|
||||
let assistantAllowedTools: string[] | undefined
|
||||
if (assistantId) {
|
||||
@@ -97,7 +109,7 @@ export function createCliRunAgentStream(
|
||||
assistantAllowedTools = assistantConfig?.allowedBuiltinTools
|
||||
}
|
||||
|
||||
const llmConfig = getDefaultAssistantConfig(aiDataDir)
|
||||
const llmConfig = llmConfigStore.getDefaultAssistantConfig()
|
||||
const maxToolResultPercent = compressionConfig?.maxToolResultPercent ?? 50
|
||||
const contextWindow = llmConfig ? (buildPiModel(llmConfig).contextWindow ?? 128000) : 128000
|
||||
const maxToolResultTokens = Math.floor(contextWindow * (maxToolResultPercent / 100))
|
||||
@@ -213,7 +225,7 @@ export function createCliRunAgentStream(
|
||||
skillDef: resolvedSkillDef,
|
||||
compressionConfig: resolvedCompression,
|
||||
tools: agentTools,
|
||||
aiDataDir,
|
||||
llmConfig,
|
||||
aiChatManager,
|
||||
onEvent,
|
||||
abortSignal,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import {
|
||||
DEFAULT_MAX_TOOL_ROUNDS,
|
||||
buildPiModel,
|
||||
buildPlanGuidance,
|
||||
createAnalysisPlanner,
|
||||
createLlmRouteDecider,
|
||||
@@ -29,12 +30,12 @@ import {
|
||||
type CompressionConfig,
|
||||
type AgentTool,
|
||||
type DataSnapshot,
|
||||
type AIServiceConfig,
|
||||
type OwnerInfo,
|
||||
type MentionedMember,
|
||||
} from '@openchatlab/node-runtime'
|
||||
import type { ChartAutoMode } from '@openchatlab/shared-types'
|
||||
|
||||
import { getDefaultAssistantConfig, buildPiModel } from './llm-config'
|
||||
import { getServerAiLogger } from './logger'
|
||||
|
||||
export type { AgentStreamChunk }
|
||||
@@ -50,7 +51,7 @@ export interface RunAgentOptions {
|
||||
skillDef?: { name: string; prompt: string }
|
||||
compressionConfig?: CompressionConfig
|
||||
tools?: AgentTool[]
|
||||
aiDataDir: string
|
||||
llmConfig: AIServiceConfig | null
|
||||
aiChatManager: AIChatManager
|
||||
onEvent: (event: AgentStreamChunk) => void
|
||||
abortSignal?: AbortSignal
|
||||
@@ -73,7 +74,7 @@ export async function runServerAgent(options: RunAgentOptions): Promise<void> {
|
||||
skillDef,
|
||||
compressionConfig,
|
||||
tools = [],
|
||||
aiDataDir,
|
||||
llmConfig,
|
||||
aiChatManager,
|
||||
onEvent,
|
||||
abortSignal,
|
||||
@@ -89,7 +90,6 @@ export async function runServerAgent(options: RunAgentOptions): Promise<void> {
|
||||
// 确保 tokenizer rank 表已加载(compression + agent 路径均依赖)
|
||||
await initTokenizer()
|
||||
|
||||
const llmConfig = getDefaultAssistantConfig(aiDataDir)
|
||||
if (!llmConfig) {
|
||||
onEvent({ type: 'error', error: { name: 'ConfigError', message: 'LLM service not configured' } })
|
||||
onEvent({ type: 'done', isFinished: true })
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
/**
|
||||
* 自定义 Provider / Model 持久化存储(平台无关)
|
||||
*
|
||||
* 文件位置:
|
||||
* - {aiDataDir}/custom-providers.json
|
||||
* - {aiDataDir}/custom-models.json
|
||||
*/
|
||||
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import { randomUUID } from 'crypto'
|
||||
import type { ProviderDefinition, ModelDefinition } from '@openchatlab/core'
|
||||
|
||||
// ==================== Custom Providers ====================
|
||||
|
||||
function readProviders(aiDataDir: string): ProviderDefinition[] {
|
||||
const storePath = path.join(aiDataDir, 'custom-providers.json')
|
||||
if (!fs.existsSync(storePath)) return []
|
||||
try {
|
||||
const content = fs.readFileSync(storePath, 'utf-8')
|
||||
return JSON.parse(content) as ProviderDefinition[]
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function writeProviders(aiDataDir: string, providers: ProviderDefinition[]): void {
|
||||
const storePath = path.join(aiDataDir, 'custom-providers.json')
|
||||
const dir = path.dirname(storePath)
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
|
||||
fs.writeFileSync(storePath, JSON.stringify(providers, null, 2), 'utf-8')
|
||||
}
|
||||
|
||||
export function addCustomProvider(
|
||||
aiDataDir: string,
|
||||
input: Omit<ProviderDefinition, 'id' | 'builtin' | 'enabledByDefault'>
|
||||
): ProviderDefinition {
|
||||
const providers = readProviders(aiDataDir)
|
||||
const newProvider: ProviderDefinition = {
|
||||
...input,
|
||||
id: `custom:${randomUUID()}`,
|
||||
builtin: false,
|
||||
enabledByDefault: false,
|
||||
}
|
||||
providers.push(newProvider)
|
||||
writeProviders(aiDataDir, providers)
|
||||
return newProvider
|
||||
}
|
||||
|
||||
export function updateCustomProvider(
|
||||
aiDataDir: string,
|
||||
id: string,
|
||||
updates: Partial<Omit<ProviderDefinition, 'id' | 'builtin'>>
|
||||
): { success: boolean; error?: string } {
|
||||
const providers = readProviders(aiDataDir)
|
||||
const index = providers.findIndex((p) => p.id === id)
|
||||
if (index === -1) return { success: false, error: 'Custom provider not found' }
|
||||
|
||||
providers[index] = { ...providers[index], ...updates }
|
||||
writeProviders(aiDataDir, providers)
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
export function deleteCustomProvider(aiDataDir: string, id: string): { success: boolean; error?: string } {
|
||||
const providers = readProviders(aiDataDir)
|
||||
const index = providers.findIndex((p) => p.id === id)
|
||||
if (index === -1) return { success: false, error: 'Custom provider not found' }
|
||||
|
||||
providers.splice(index, 1)
|
||||
writeProviders(aiDataDir, providers)
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
// ==================== Custom Models ====================
|
||||
|
||||
function readModels(aiDataDir: string): ModelDefinition[] {
|
||||
const storePath = path.join(aiDataDir, 'custom-models.json')
|
||||
if (!fs.existsSync(storePath)) return []
|
||||
try {
|
||||
const content = fs.readFileSync(storePath, 'utf-8')
|
||||
return JSON.parse(content) as ModelDefinition[]
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function writeModels(aiDataDir: string, models: ModelDefinition[]): void {
|
||||
const storePath = path.join(aiDataDir, 'custom-models.json')
|
||||
const dir = path.dirname(storePath)
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
|
||||
fs.writeFileSync(storePath, JSON.stringify(models, null, 2), 'utf-8')
|
||||
}
|
||||
|
||||
export function addCustomModel(
|
||||
aiDataDir: string,
|
||||
input: Omit<ModelDefinition, 'builtin' | 'editable'>
|
||||
): { success: boolean; model?: ModelDefinition; error?: string } {
|
||||
const models = readModels(aiDataDir)
|
||||
|
||||
const existing = models.find((m) => m.id === input.id && m.providerId === input.providerId)
|
||||
if (existing) {
|
||||
return { success: false, error: `Model "${input.id}" already exists under provider "${input.providerId}"` }
|
||||
}
|
||||
|
||||
const newModel: ModelDefinition = {
|
||||
...input,
|
||||
builtin: false,
|
||||
editable: true,
|
||||
}
|
||||
models.push(newModel)
|
||||
writeModels(aiDataDir, models)
|
||||
return { success: true, model: newModel }
|
||||
}
|
||||
|
||||
export function updateCustomModel(
|
||||
aiDataDir: string,
|
||||
providerId: string,
|
||||
modelId: string,
|
||||
updates: Partial<Omit<ModelDefinition, 'id' | 'providerId' | 'builtin'>>
|
||||
): { success: boolean; error?: string } {
|
||||
const models = readModels(aiDataDir)
|
||||
const index = models.findIndex((m) => m.id === modelId && m.providerId === providerId)
|
||||
if (index === -1) return { success: false, error: 'Custom model not found' }
|
||||
|
||||
models[index] = { ...models[index], ...updates }
|
||||
writeModels(aiDataDir, models)
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
export function deleteCustomModel(
|
||||
aiDataDir: string,
|
||||
providerId: string,
|
||||
modelId: string
|
||||
): { success: boolean; error?: string } {
|
||||
const models = readModels(aiDataDir)
|
||||
const index = models.findIndex((m) => m.id === modelId && m.providerId === providerId)
|
||||
if (index === -1) return { success: false, error: 'Custom model not found' }
|
||||
|
||||
models.splice(index, 1)
|
||||
writeModels(aiDataDir, models)
|
||||
return { success: true }
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
/**
|
||||
* 服务端 LLM 配置加载
|
||||
*
|
||||
* API Key 从 ~/.chatlab/auth-profiles.json 读取,
|
||||
* 匹配顺序:config.authProfile 精确匹配 > config.provider 兜底匹配。
|
||||
*
|
||||
* 模型/Provider 等配置仍从 llm-config.json 读取。
|
||||
*/
|
||||
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import type { PiModel, PiApi } from '@openchatlab/node-runtime'
|
||||
import { buildPiModel as buildPiModelCore, type PiModelConfig } from '@openchatlab/node-runtime'
|
||||
import { resolveApiKey, writeAuthProfile } from '@openchatlab/config'
|
||||
import { randomUUID } from 'crypto'
|
||||
|
||||
// ==================== Types ====================
|
||||
|
||||
export interface AIServiceConfig {
|
||||
id: string
|
||||
name: string
|
||||
provider: string
|
||||
apiKey: string
|
||||
model?: string
|
||||
baseUrl?: string
|
||||
maxTokens?: number
|
||||
apiFormat?: string
|
||||
customModels?: Array<{ id: string; name: string }>
|
||||
authProfile?: string
|
||||
}
|
||||
|
||||
interface ModelSlot {
|
||||
configId: string
|
||||
modelId?: string
|
||||
}
|
||||
|
||||
interface AIConfigStore {
|
||||
configs: AIServiceConfig[]
|
||||
defaultAssistant: ModelSlot | null
|
||||
fastModel: ModelSlot | null
|
||||
}
|
||||
|
||||
// ==================== Config Loading ====================
|
||||
|
||||
export function loadLlmConfig(aiDataDir: string): AIConfigStore {
|
||||
const configPath = path.join(aiDataDir, 'llm-config.json')
|
||||
if (!fs.existsSync(configPath)) {
|
||||
return { configs: [], defaultAssistant: null, fastModel: null }
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
||||
|
||||
const configs = (data.configs || []).map((c: Record<string, unknown>) => {
|
||||
const provider = (c.provider as string) || ''
|
||||
const authProfile = c.authProfile as string | undefined
|
||||
const profileKey = resolveApiKey(provider, authProfile)
|
||||
const rawKey = (c.apiKey as string) || ''
|
||||
const fallbackKey = rawKey.startsWith('enc:') ? '' : rawKey
|
||||
return {
|
||||
...c,
|
||||
apiKey: profileKey || fallbackKey,
|
||||
}
|
||||
})
|
||||
return {
|
||||
configs,
|
||||
defaultAssistant: data.defaultAssistant ?? null,
|
||||
fastModel: data.fastModel ?? null,
|
||||
}
|
||||
} catch {
|
||||
return { configs: [], defaultAssistant: null, fastModel: null }
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSlot(slot: ModelSlot | null | undefined, configs: AIServiceConfig[]): ModelSlot | null {
|
||||
if (slot && configs.some((c) => c.id === slot.configId)) return slot
|
||||
const fallback = configs[0]
|
||||
if (!fallback) return null
|
||||
return { configId: fallback.id, modelId: fallback.model }
|
||||
}
|
||||
|
||||
export function getDefaultAssistantConfig(aiDataDir: string): AIServiceConfig | null {
|
||||
const store = loadLlmConfig(aiDataDir)
|
||||
const slot = resolveSlot(store.defaultAssistant, store.configs)
|
||||
if (!slot) return null
|
||||
const config = store.configs.find((c) => c.id === slot.configId)
|
||||
if (!config) return null
|
||||
return { ...config, model: slot.modelId || config.model }
|
||||
}
|
||||
|
||||
// ==================== Config Write Operations ====================
|
||||
|
||||
const MAX_CONFIG_COUNT = 99
|
||||
|
||||
function saveLlmConfig(aiDataDir: string, store: AIConfigStore): void {
|
||||
const configPath = path.join(aiDataDir, 'llm-config.json')
|
||||
const toSave = {
|
||||
...store,
|
||||
configs: store.configs.map((c) => {
|
||||
const { apiKey: _k, ...rest } = c
|
||||
return rest
|
||||
}),
|
||||
schemaVersion: 3,
|
||||
}
|
||||
fs.writeFileSync(configPath, JSON.stringify(toSave, null, 2), 'utf-8')
|
||||
}
|
||||
|
||||
export function addLlmConfig(
|
||||
aiDataDir: string,
|
||||
config: Omit<AIServiceConfig, 'id'>
|
||||
): { success: boolean; config?: AIServiceConfig; error?: string } {
|
||||
const store = loadLlmConfig(aiDataDir)
|
||||
|
||||
if (store.configs.length >= MAX_CONFIG_COUNT) {
|
||||
return { success: false, error: `Maximum ${MAX_CONFIG_COUNT} configs reached` }
|
||||
}
|
||||
|
||||
const newConfig: AIServiceConfig = {
|
||||
...config,
|
||||
id: randomUUID(),
|
||||
}
|
||||
|
||||
const storeForSave = loadRawConfigStore(aiDataDir)
|
||||
storeForSave.configs.push({
|
||||
...newConfig,
|
||||
apiKey: '',
|
||||
})
|
||||
|
||||
if (storeForSave.configs.length === 1) {
|
||||
storeForSave.defaultAssistant = { configId: newConfig.id, modelId: newConfig.model || '' }
|
||||
}
|
||||
|
||||
if (config.apiKey) {
|
||||
const profileName = config.name?.toLowerCase().replace(/\s+/g, '-') || config.provider
|
||||
writeAuthProfile(profileName, {
|
||||
type: 'api_key',
|
||||
provider: config.provider,
|
||||
key: config.apiKey,
|
||||
})
|
||||
;(storeForSave.configs[storeForSave.configs.length - 1] as unknown as Record<string, unknown>).authProfile =
|
||||
profileName
|
||||
}
|
||||
|
||||
saveLlmConfig(aiDataDir, storeForSave)
|
||||
return { success: true, config: { ...newConfig, apiKey: '' } }
|
||||
}
|
||||
|
||||
export function updateLlmConfig(
|
||||
aiDataDir: string,
|
||||
id: string,
|
||||
updates: Partial<Omit<AIServiceConfig, 'id'>>
|
||||
): { success: boolean; error?: string } {
|
||||
const storeForSave = loadRawConfigStore(aiDataDir)
|
||||
const index = storeForSave.configs.findIndex((c) => c.id === id)
|
||||
|
||||
if (index === -1) {
|
||||
return { success: false, error: 'Config not found' }
|
||||
}
|
||||
|
||||
const { apiKey: newApiKey, ...restUpdates } = updates
|
||||
const updated = {
|
||||
...storeForSave.configs[index],
|
||||
...restUpdates,
|
||||
}
|
||||
storeForSave.configs[index] = updated
|
||||
|
||||
if (newApiKey) {
|
||||
const profileName = updated.name?.toLowerCase().replace(/\s+/g, '-') || updated.provider
|
||||
writeAuthProfile(profileName, {
|
||||
type: 'api_key',
|
||||
provider: updated.provider,
|
||||
key: newApiKey,
|
||||
})
|
||||
;(storeForSave.configs[index] as unknown as Record<string, unknown>).authProfile = profileName
|
||||
}
|
||||
|
||||
saveLlmConfig(aiDataDir, storeForSave)
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
export function deleteLlmConfig(aiDataDir: string, id: string): { success: boolean; error?: string } {
|
||||
const storeForSave = loadRawConfigStore(aiDataDir)
|
||||
const index = storeForSave.configs.findIndex((c) => c.id === id)
|
||||
|
||||
if (index === -1) {
|
||||
return { success: false, error: 'Config not found' }
|
||||
}
|
||||
|
||||
storeForSave.configs.splice(index, 1)
|
||||
|
||||
const fallback = storeForSave.configs[0]
|
||||
if (storeForSave.defaultAssistant?.configId === id) {
|
||||
storeForSave.defaultAssistant = fallback ? { configId: fallback.id, modelId: fallback.model || '' } : null
|
||||
}
|
||||
if (storeForSave.fastModel?.configId === id) {
|
||||
storeForSave.fastModel = fallback ? { configId: fallback.id, modelId: fallback.model || '' } : null
|
||||
}
|
||||
|
||||
saveLlmConfig(aiDataDir, storeForSave)
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
export function setDefaultAssistantSlot(
|
||||
aiDataDir: string,
|
||||
configId: string,
|
||||
modelId: string
|
||||
): { success: boolean; error?: string } {
|
||||
const storeForSave = loadRawConfigStore(aiDataDir)
|
||||
const config = storeForSave.configs.find((c) => c.id === configId)
|
||||
|
||||
if (!config) {
|
||||
return { success: false, error: 'Config not found' }
|
||||
}
|
||||
|
||||
storeForSave.defaultAssistant = { configId, modelId }
|
||||
saveLlmConfig(aiDataDir, storeForSave)
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
export function setFastModelSlot(aiDataDir: string, slot: ModelSlot | null): { success: boolean; error?: string } {
|
||||
const storeForSave = loadRawConfigStore(aiDataDir)
|
||||
|
||||
if (slot !== null) {
|
||||
const config = storeForSave.configs.find((c) => c.id === slot.configId)
|
||||
if (!config) {
|
||||
return { success: false, error: 'Config not found' }
|
||||
}
|
||||
}
|
||||
|
||||
storeForSave.fastModel = slot
|
||||
saveLlmConfig(aiDataDir, storeForSave)
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
function loadRawConfigStore(aiDataDir: string): AIConfigStore {
|
||||
const configPath = path.join(aiDataDir, 'llm-config.json')
|
||||
if (!fs.existsSync(configPath)) {
|
||||
return { configs: [], defaultAssistant: null, fastModel: null }
|
||||
}
|
||||
try {
|
||||
const data = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
||||
return {
|
||||
configs: data.configs || [],
|
||||
defaultAssistant: data.defaultAssistant ?? null,
|
||||
fastModel: data.fastModel ?? null,
|
||||
}
|
||||
} catch {
|
||||
return { configs: [], defaultAssistant: null, fastModel: null }
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== PiModel Builder ====================
|
||||
|
||||
export function buildPiModel(config: AIServiceConfig): PiModel<PiApi> {
|
||||
return buildPiModelCore(config as PiModelConfig)
|
||||
}
|
||||
@@ -14,10 +14,7 @@ import {
|
||||
NodePathProvider,
|
||||
DatabaseManager,
|
||||
AIChatManager,
|
||||
CustomProviderStore,
|
||||
CustomModelStore,
|
||||
createFileConfigStorage,
|
||||
createAuthProfileLlmConfigStore,
|
||||
createLlmRuntimeStores,
|
||||
applyPendingNodeDataDirMigrationIfNeeded,
|
||||
hasPendingElectronDataWarning,
|
||||
verifyCliDataPath,
|
||||
@@ -144,11 +141,12 @@ export async function startHttpServer(options?: HttpServerOptions): Promise<{
|
||||
|
||||
const assistantManager = getAssistantManager(aiDataDir)
|
||||
const skillManagerCore = getSkillManagerCore(aiDataDir)
|
||||
const configStorage = createFileConfigStorage(aiDataDir)
|
||||
const llmConfigStore = createAuthProfileLlmConfigStore(configStorage)
|
||||
const llmRuntimeStores = createLlmRuntimeStores(aiDataDir)
|
||||
const { llmConfigStore, customProviderStore, customModelStore } = llmRuntimeStores
|
||||
|
||||
initAppLogger(pathProvider.getLogsDir())
|
||||
initServerAiLogger(pathProvider.getLogsDir())
|
||||
appLogger.info('ai-config', 'Shared LLM configuration store initialized', { aiDataDir })
|
||||
appLogger.info('temp-workspace', 'Temporary workspace initialized', { root: pathProvider.getTempDir() })
|
||||
appLogger.info('server', `HTTP server starting on ${host}:${port}`)
|
||||
// 记录 Rust native parser 可用性(导入是否走 Rust 内核),便于按日志排查回退原因
|
||||
@@ -192,9 +190,12 @@ export async function startHttpServer(options?: HttpServerOptions): Promise<{
|
||||
assistantManager,
|
||||
skillManagerCore,
|
||||
llmConfigStore,
|
||||
customProviderStore: new CustomProviderStore(configStorage),
|
||||
customModelStore: new CustomModelStore(configStorage),
|
||||
runAgentStream: createCliRunAgentStream(dbManager, aiChatManager, semanticIndexService),
|
||||
customProviderStore,
|
||||
customModelStore,
|
||||
runAgentStream: createCliRunAgentStream(dbManager, aiChatManager, {
|
||||
llmConfigStore,
|
||||
semanticIndexService,
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import type { FastifyInstance } from 'fastify'
|
||||
import type { DatabaseManager, SessionRuntimeAdapter, SummaryServiceDeps } from '@openchatlab/node-runtime'
|
||||
import { summaryService } from '@openchatlab/node-runtime'
|
||||
import { getAiDataDir } from './helpers'
|
||||
import { getDefaultAssistantConfig, buildPiModel } from '../../../ai/llm-config'
|
||||
|
||||
function createSummaryDeps(dbManager: DatabaseManager): SummaryServiceDeps {
|
||||
const aiDataDir = getAiDataDir(dbManager)
|
||||
return {
|
||||
getLlmConfig() {
|
||||
return getDefaultAssistantConfig(aiDataDir)
|
||||
},
|
||||
buildPiModel(config) {
|
||||
return buildPiModel(config as ReturnType<typeof getDefaultAssistantConfig> & object)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function registerSummaryRoutes(
|
||||
server: FastifyInstance,
|
||||
dbManager: DatabaseManager,
|
||||
adapter: SessionRuntimeAdapter
|
||||
): void {
|
||||
const deps = createSummaryDeps(dbManager)
|
||||
|
||||
server.post<{
|
||||
Params: { id: string }
|
||||
Body: { segmentId: number; locale?: string; forceRegenerate?: boolean; strategy?: 'brief' | 'standard' }
|
||||
}>('/_web/sessions/:id/summaries/generate', async (request, reply) => {
|
||||
const { segmentId, locale, forceRegenerate, strategy } = request.body
|
||||
const result = await summaryService.generateSummary(adapter, request.params.id, segmentId, deps, {
|
||||
locale,
|
||||
forceRegenerate,
|
||||
strategy,
|
||||
})
|
||||
if ('error' in result && !result.success) {
|
||||
return reply.code(400).send({ error: result.error })
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
server.post<{
|
||||
Params: { id: string }
|
||||
Body: { locale?: string; forceRegenerate?: boolean }
|
||||
}>('/_web/sessions/:id/summaries/generate-all', async (request, reply) => {
|
||||
const { locale, forceRegenerate } = request.body
|
||||
const result = await summaryService.generateAllSummaries(adapter, request.params.id, deps, {
|
||||
locale,
|
||||
forceRegenerate,
|
||||
})
|
||||
if (result.error) {
|
||||
return reply.code(400).send({ error: result.error })
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
server.post<{
|
||||
Params: { id: string }
|
||||
Body: { segmentIds: number[] }
|
||||
}>('/_web/sessions/:id/summaries/check-can-generate', async (request) => {
|
||||
const { segmentIds } = request.body
|
||||
return summaryService.checkCanGenerate(adapter, request.params.id, segmentIds)
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user