claude-brain 0.27.2 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/VERSION +1 -1
- package/package.json +3 -1
- package/src/cli/bin.ts +14 -0
- package/src/cli/commands/export-training.ts +70 -0
- package/src/cli/commands/models.ts +681 -0
- package/src/cli/commands/status.ts +44 -0
- package/src/config/home.ts +1 -0
- package/src/config/schema.ts +30 -0
- package/src/intelligence/inference-router.ts +749 -0
- package/src/intelligence/model-manager.ts +206 -0
- package/src/intelligence/tokenizer.ts +118 -0
- package/src/knowledge/entity-extractor.ts +31 -1
- package/src/memory/compression.ts +17 -1
- package/src/memory/patterns.ts +46 -6
- package/src/retrieval/query/intent-classifier.ts +17 -1
- package/src/routing/entity-extractor.ts +30 -4
- package/src/routing/intent-classifier.ts +45 -16
- package/src/routing/router.ts +22 -2
- package/src/server/handlers/list-tools.ts +6 -6
- package/src/server/http-api.ts +83 -1
- package/src/server/services.ts +47 -0
- package/src/training/data-store.ts +298 -0
- package/src/training/retrain-pipeline.ts +394 -0
|
@@ -240,85 +240,94 @@ export class IntentClassifier {
|
|
|
240
240
|
// Phase 19 B3: Detect temporal signals for secondary intent
|
|
241
241
|
const hasTemporal = this.hasTemporalSignal(lower)
|
|
242
242
|
|
|
243
|
+
// SLM Phase 1A: Log classification for training data collection
|
|
244
|
+
const startTime = Date.now()
|
|
245
|
+
|
|
243
246
|
// Check in priority order — first confident match wins
|
|
244
247
|
|
|
248
|
+
// Helper to log + return in one step
|
|
249
|
+
const ret = (result: ClassificationResult): ClassificationResult => {
|
|
250
|
+
this._logTraining(message, result, startTime)
|
|
251
|
+
return result
|
|
252
|
+
}
|
|
253
|
+
|
|
245
254
|
// 1. no_action: very short messages, greetings, acknowledgments
|
|
246
255
|
if (this.isNoAction(lower)) {
|
|
247
|
-
return { primary: 'no_action', confidence: 0.95, secondary: [] }
|
|
256
|
+
return ret({ primary: 'no_action', confidence: 0.95, secondary: [] })
|
|
248
257
|
}
|
|
249
258
|
|
|
250
259
|
// 2. delete_memory: "forget that", "delete", "remove"
|
|
251
260
|
if (this.isDeleteMemory(lower)) {
|
|
252
|
-
return { primary: 'delete_memory', confidence: 0.90, secondary }
|
|
261
|
+
return ret({ primary: 'delete_memory', confidence: 0.90, secondary })
|
|
253
262
|
}
|
|
254
263
|
|
|
255
264
|
// 3. update_memory: "actually", "correction:", "change that to"
|
|
256
265
|
if (this.isUpdateMemory(lower)) {
|
|
257
|
-
return { primary: 'update_memory', confidence: 0.85, secondary }
|
|
266
|
+
return ret({ primary: 'update_memory', confidence: 0.85, secondary })
|
|
258
267
|
}
|
|
259
268
|
|
|
260
269
|
// 4. store_this: explicit "remember:", "save this:", "I prefer" (never for questions)
|
|
261
270
|
if (this.isStoreThis(lower, message)) {
|
|
262
271
|
if (this.hasDecisionSignal(lower)) secondary.push('decision_made')
|
|
263
|
-
return { primary: 'store_this', confidence: 0.90, secondary }
|
|
272
|
+
return ret({ primary: 'store_this', confidence: 0.90, secondary })
|
|
264
273
|
}
|
|
265
274
|
|
|
266
275
|
// 5. decision_made: decision phrases + reasoning (never for questions)
|
|
267
276
|
if (this.isDecisionMade(lower, message)) {
|
|
268
277
|
if (this.hasComparisonSignal(lower)) secondary.push('comparison')
|
|
269
|
-
return { primary: 'decision_made', confidence: 0.85, secondary }
|
|
278
|
+
return ret({ primary: 'decision_made', confidence: 0.85, secondary })
|
|
270
279
|
}
|
|
271
280
|
|
|
272
281
|
// 6. mistake_learned: correction/bug/lesson indicators
|
|
273
282
|
if (this.isMistakeLearned(lower)) {
|
|
274
|
-
return { primary: 'mistake_learned', confidence: 0.85, secondary }
|
|
283
|
+
return ret({ primary: 'mistake_learned', confidence: 0.85, secondary })
|
|
275
284
|
}
|
|
276
285
|
|
|
277
286
|
// 7. list_all: "list all", "what decisions", "show all"
|
|
278
287
|
if (this.isListAll(lower, message)) {
|
|
279
|
-
return { primary: 'list_all', confidence: 0.85, secondary }
|
|
288
|
+
return ret({ primary: 'list_all', confidence: 0.85, secondary })
|
|
280
289
|
}
|
|
281
290
|
|
|
282
291
|
// 8. progress_update: completed task indicators (NOT questions)
|
|
283
292
|
if (this.isProgressUpdate(lower, message)) {
|
|
284
293
|
if (this.hasSessionSignal(lower)) secondary.push('session_start')
|
|
285
|
-
return { primary: 'progress_update', confidence: 0.85, secondary }
|
|
294
|
+
return ret({ primary: 'progress_update', confidence: 0.85, secondary })
|
|
286
295
|
}
|
|
287
296
|
|
|
288
297
|
// 9. comparison: vs, which is better, etc.
|
|
289
298
|
if (this.isComparison(lower)) {
|
|
290
299
|
if (this.isQuestion(lower, message)) secondary.push('question')
|
|
291
300
|
if (hasTemporal) secondary.push('exploration')
|
|
292
|
-
return { primary: 'comparison', confidence: 0.85, secondary }
|
|
301
|
+
return ret({ primary: 'comparison', confidence: 0.85, secondary })
|
|
293
302
|
}
|
|
294
303
|
|
|
295
304
|
// 10. pattern_found: explicit pattern documentation
|
|
296
305
|
if (this.isPatternFound(lower)) {
|
|
297
|
-
return { primary: 'pattern_found', confidence: 0.80, secondary }
|
|
306
|
+
return ret({ primary: 'pattern_found', confidence: 0.80, secondary })
|
|
298
307
|
}
|
|
299
308
|
|
|
300
309
|
// 11. session_start: starting/resuming work (Phase 19: narrowed check)
|
|
301
310
|
if (this.isSessionStart(lower)) {
|
|
302
311
|
secondary.push('context_needed')
|
|
303
|
-
return { primary: 'session_start', confidence: 0.90, secondary }
|
|
312
|
+
return ret({ primary: 'session_start', confidence: 0.90, secondary })
|
|
304
313
|
}
|
|
305
314
|
|
|
306
315
|
// 12. detail_request: "details obs_abc123", "show me <id>" (before exploration to avoid misclassification)
|
|
307
316
|
if (this.isDetailRequest(lower)) {
|
|
308
|
-
return { primary: 'detail_request', confidence: 0.90, secondary }
|
|
317
|
+
return ret({ primary: 'detail_request', confidence: 0.90, secondary })
|
|
309
318
|
}
|
|
310
319
|
|
|
311
320
|
// 12b. timeline: "timeline for project", "what did I do yesterday", "recent activity" (before exploration)
|
|
312
321
|
if (this.isTimeline(lower)) {
|
|
313
322
|
if (hasTemporal) secondary.push('exploration')
|
|
314
|
-
return { primary: 'timeline', confidence: 0.85, secondary }
|
|
323
|
+
return ret({ primary: 'timeline', confidence: 0.85, secondary })
|
|
315
324
|
}
|
|
316
325
|
|
|
317
326
|
// 12c. exploration: trends, graph, evolution, history (general exploration that isn't a specific timeline)
|
|
318
327
|
if (this.isExploration(lower)) {
|
|
319
328
|
if (this.isQuestion(lower, message)) secondary.push('question')
|
|
320
329
|
if (hasTemporal) secondary.push('exploration')
|
|
321
|
-
return { primary: 'exploration', confidence: 0.75, secondary }
|
|
330
|
+
return ret({ primary: 'exploration', confidence: 0.75, secondary })
|
|
322
331
|
}
|
|
323
332
|
|
|
324
333
|
// 13. question: starts with question word or ends with ?
|
|
@@ -330,12 +339,32 @@ export class IntentClassifier {
|
|
|
330
339
|
|
|
331
340
|
// Phase 19 B1: ? → 0.95, question word → 0.90
|
|
332
341
|
const confidence = message.trim().endsWith('?') ? 0.95 : 0.90
|
|
333
|
-
return { primary: 'question', confidence, secondary }
|
|
342
|
+
return ret({ primary: 'question', confidence, secondary })
|
|
334
343
|
}
|
|
335
344
|
|
|
336
345
|
// 14. Default: context_needed
|
|
337
346
|
if (hasTemporal) secondary.push('exploration')
|
|
338
|
-
|
|
347
|
+
const defaultResult = { primary: 'context_needed' as Intent, confidence: 0.60, secondary }
|
|
348
|
+
this._logTraining(message, defaultResult, startTime)
|
|
349
|
+
return defaultResult
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* SLM Phase 1A: Log classification result for training data collection.
|
|
354
|
+
* Fire-and-forget, never blocks the main path.
|
|
355
|
+
*/
|
|
356
|
+
private _logTraining(message: string, result: ClassificationResult, startTime: number): void {
|
|
357
|
+
try {
|
|
358
|
+
const { logTrainingData } = require('@/training/data-store')
|
|
359
|
+
logTrainingData({
|
|
360
|
+
task: 'intent' as const,
|
|
361
|
+
input: message,
|
|
362
|
+
output: JSON.stringify({ label: result.primary, secondary: result.secondary }),
|
|
363
|
+
metadata: JSON.stringify({ confidence: result.confidence, elapsed_ms: Date.now() - startTime }),
|
|
364
|
+
})
|
|
365
|
+
} catch {
|
|
366
|
+
// Training data logging is non-critical
|
|
367
|
+
}
|
|
339
368
|
}
|
|
340
369
|
|
|
341
370
|
private isNoAction(lower: string): boolean {
|
package/src/routing/router.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import type { Logger } from 'pino'
|
|
14
14
|
import { IntentClassifier, type ClassificationResult } from './intent-classifier'
|
|
15
|
+
import type { InferenceRouter } from '@/intelligence/inference-router'
|
|
15
16
|
import { BrainEntityExtractor, type BrainExtractedEntities } from './entity-extractor'
|
|
16
17
|
import { ResponseFilter, type BrainResponse, type TierResults, type FilterableResult, formatCompactResponse, formatDetailResponse, formatTimeline, groupByDay } from './response-filter'
|
|
17
18
|
import { SearchEngine } from './search-engine'
|
|
@@ -62,6 +63,9 @@ export class BrainRouter {
|
|
|
62
63
|
private searchEngine: SearchEngine
|
|
63
64
|
private logger: Logger
|
|
64
65
|
|
|
66
|
+
/** SLM Upgrade: Optional inference router for model-based classification */
|
|
67
|
+
private inferenceRouter: InferenceRouter | null = null
|
|
68
|
+
|
|
65
69
|
/** Phase 30: Optional LLM compressor for long observations */
|
|
66
70
|
private compressor: ObservationCompressor | null = null
|
|
67
71
|
|
|
@@ -80,6 +84,12 @@ export class BrainRouter {
|
|
|
80
84
|
this.logger = logger.child({ component: 'brain-router' })
|
|
81
85
|
}
|
|
82
86
|
|
|
87
|
+
/** SLM Upgrade: Set the optional inference router for model-based classification */
|
|
88
|
+
setInferenceRouter(router: InferenceRouter): void {
|
|
89
|
+
this.inferenceRouter = router
|
|
90
|
+
this.entityExtractor.setInferenceRouter(router)
|
|
91
|
+
}
|
|
92
|
+
|
|
83
93
|
/** Phase 30: Set the optional LLM compressor */
|
|
84
94
|
setCompressor(compressor: ObservationCompressor): void {
|
|
85
95
|
this.compressor = compressor
|
|
@@ -120,8 +130,10 @@ export class BrainRouter {
|
|
|
120
130
|
}
|
|
121
131
|
}
|
|
122
132
|
|
|
123
|
-
// Classify intent
|
|
124
|
-
const classification = this.
|
|
133
|
+
// Classify intent (SLM: use inference router if available, falls back to regex)
|
|
134
|
+
const classification = this.inferenceRouter
|
|
135
|
+
? await this.inferenceRouter.classifyIntent(message)
|
|
136
|
+
: this.classifier.classify(message)
|
|
125
137
|
this.logger.debug({ intent: classification.primary, confidence: classification.confidence }, 'Intent classified')
|
|
126
138
|
|
|
127
139
|
// Route to handler
|
|
@@ -2163,6 +2175,14 @@ let routerInstance: BrainRouter | null = null
|
|
|
2163
2175
|
export function getBrainRouter(logger: Logger): BrainRouter {
|
|
2164
2176
|
if (!routerInstance) {
|
|
2165
2177
|
routerInstance = new BrainRouter(logger)
|
|
2178
|
+
// SLM Upgrade: Wire inference router if available
|
|
2179
|
+
try {
|
|
2180
|
+
const { getInferenceRouter } = require('@/server/services')
|
|
2181
|
+
const ir = getInferenceRouter()
|
|
2182
|
+
if (ir) routerInstance.setInferenceRouter(ir)
|
|
2183
|
+
} catch {
|
|
2184
|
+
// Services not initialized yet — will use regex fallback
|
|
2185
|
+
}
|
|
2166
2186
|
}
|
|
2167
2187
|
return routerInstance
|
|
2168
2188
|
}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* List Tools Handler
|
|
3
3
|
* Handles the MCP tools/list request
|
|
4
4
|
*
|
|
5
|
-
* In unified tool mode (
|
|
6
|
-
* In legacy mode, all
|
|
5
|
+
* In unified tool mode (default), exposes brain + search_code.
|
|
6
|
+
* In legacy mode, all tools are exposed.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { ToolRegistry } from '@/tools/registry'
|
|
@@ -19,10 +19,10 @@ export async function handleListTools() {
|
|
|
19
19
|
try {
|
|
20
20
|
const { config } = getServices()
|
|
21
21
|
if (config.unifiedToolMode) {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
22
|
+
const tools = ['brain', 'search_code']
|
|
23
|
+
.map(name => ToolRegistry.getToolByName(name))
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
return { tools }
|
|
26
26
|
}
|
|
27
27
|
} catch {
|
|
28
28
|
// Services not ready, fall through to default
|
package/src/server/http-api.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { Hono } from 'hono'
|
|
7
7
|
import type { Logger } from 'pino'
|
|
8
8
|
import type { Config } from '@/config'
|
|
9
|
-
import { getMemoryService, getVaultService, isServicesInitialized } from '@/server/services'
|
|
9
|
+
import { getMemoryService, getVaultService, getInferenceRouter, isServicesInitialized } from '@/server/services'
|
|
10
10
|
import { ResourceProvider } from '@/server/providers/resources'
|
|
11
11
|
import type { MemoryManager } from '@/memory'
|
|
12
12
|
import type { CapturedKnowledge, HookStats } from '@/hooks/types'
|
|
@@ -16,6 +16,7 @@ import type { CodeIndexer } from '@/code-intelligence/indexer'
|
|
|
16
16
|
import type { CodeQuery } from '@/code-intelligence/query'
|
|
17
17
|
import type { MemoryCodeLinker } from '@/code-intelligence/linker'
|
|
18
18
|
import { setupWebViewer, setWebViewerCodeQuery } from '@/server/web-viewer'
|
|
19
|
+
import { getTrainingStats, getModelFeedbackStats, getDisagreements } from '@/training/data-store'
|
|
19
20
|
|
|
20
21
|
export class HttpApiServer {
|
|
21
22
|
private app: Hono
|
|
@@ -135,6 +136,12 @@ export class HttpApiServer {
|
|
|
135
136
|
|
|
136
137
|
// Phase 23b: Expose brain://context/auto via HTTP for testability
|
|
137
138
|
this.app.get('/api/context/auto', () => this.handleContextAuto())
|
|
139
|
+
|
|
140
|
+
// Phase 6A: SLM feedback & model status endpoints
|
|
141
|
+
this.app.get('/api/models/status', () => this.handleModelsStatus())
|
|
142
|
+
this.app.get('/api/models/feedback', (c) => this.handleModelsFeedback(c))
|
|
143
|
+
this.app.get('/api/models/disagreements', (c) => this.handleModelsDisagreements(c))
|
|
144
|
+
this.app.get('/api/training/stats', () => this.handleTrainingStats())
|
|
138
145
|
}
|
|
139
146
|
|
|
140
147
|
private async handleListProjects(): Promise<Response> {
|
|
@@ -1047,6 +1054,81 @@ export class HttpApiServer {
|
|
|
1047
1054
|
}
|
|
1048
1055
|
}
|
|
1049
1056
|
|
|
1057
|
+
// ─── Phase 6A: SLM Model Feedback Endpoints ────────────
|
|
1058
|
+
|
|
1059
|
+
private handleModelsStatus(): Response {
|
|
1060
|
+
try {
|
|
1061
|
+
const inferenceRouter = getInferenceRouter()
|
|
1062
|
+
if (!inferenceRouter) {
|
|
1063
|
+
return Response.json({
|
|
1064
|
+
success: true,
|
|
1065
|
+
data: { enabled: false, message: 'SLM inference not initialized' },
|
|
1066
|
+
})
|
|
1067
|
+
}
|
|
1068
|
+
return Response.json({ success: true, data: inferenceRouter.getStatus() })
|
|
1069
|
+
} catch (error) {
|
|
1070
|
+
this.logger.error({ error }, 'Failed to get model status')
|
|
1071
|
+
return Response.json(
|
|
1072
|
+
{ success: false, error: 'Failed to get model status' },
|
|
1073
|
+
{ status: 500 }
|
|
1074
|
+
)
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
private handleModelsFeedback(c: any): Response {
|
|
1079
|
+
try {
|
|
1080
|
+
const task = c.req.query('task')
|
|
1081
|
+
const stats = getModelFeedbackStats()
|
|
1082
|
+
|
|
1083
|
+
if (task) {
|
|
1084
|
+
const taskStats = stats[task]
|
|
1085
|
+
if (!taskStats) {
|
|
1086
|
+
return Response.json(
|
|
1087
|
+
{ success: false, error: `Unknown task: ${task}` },
|
|
1088
|
+
{ status: 400 }
|
|
1089
|
+
)
|
|
1090
|
+
}
|
|
1091
|
+
return Response.json({ success: true, data: { [task]: taskStats } })
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
return Response.json({ success: true, data: stats })
|
|
1095
|
+
} catch (error) {
|
|
1096
|
+
this.logger.error({ error }, 'Failed to get model feedback stats')
|
|
1097
|
+
return Response.json(
|
|
1098
|
+
{ success: false, error: 'Failed to get model feedback stats' },
|
|
1099
|
+
{ status: 500 }
|
|
1100
|
+
)
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
private handleModelsDisagreements(c: any): Response {
|
|
1105
|
+
try {
|
|
1106
|
+
const task = c.req.query('task') || 'intent'
|
|
1107
|
+
const limit = parseInt(c.req.query('limit') || '50', 10)
|
|
1108
|
+
const disagreements = getDisagreements(task, limit)
|
|
1109
|
+
return Response.json({ success: true, data: disagreements })
|
|
1110
|
+
} catch (error) {
|
|
1111
|
+
this.logger.error({ error }, 'Failed to get model disagreements')
|
|
1112
|
+
return Response.json(
|
|
1113
|
+
{ success: false, error: 'Failed to get model disagreements' },
|
|
1114
|
+
{ status: 500 }
|
|
1115
|
+
)
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
private handleTrainingStats(): Response {
|
|
1120
|
+
try {
|
|
1121
|
+
const stats = getTrainingStats()
|
|
1122
|
+
return Response.json({ success: true, data: stats })
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
this.logger.error({ error }, 'Failed to get training stats')
|
|
1125
|
+
return Response.json(
|
|
1126
|
+
{ success: false, error: 'Failed to get training stats' },
|
|
1127
|
+
{ status: 500 }
|
|
1128
|
+
)
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1050
1132
|
async start(): Promise<void> {
|
|
1051
1133
|
const port = this.config.port || 3000
|
|
1052
1134
|
|
package/src/server/services.ts
CHANGED
|
@@ -26,6 +26,8 @@ import { SemanticCache } from '@/intelligence/optimization/semantic-cache'
|
|
|
26
26
|
import { PrecomputeEngine } from '@/intelligence/optimization/precompute'
|
|
27
27
|
import { MemoryArchiver } from '@/memory/consolidation/archiver'
|
|
28
28
|
import { ImportanceScorer } from '@/memory/consolidation/scorer'
|
|
29
|
+
import { ModelManager } from '@/intelligence/model-manager'
|
|
30
|
+
import { InferenceRouter } from '@/intelligence/inference-router'
|
|
29
31
|
|
|
30
32
|
export interface KnowledgeGraphServiceContainer {
|
|
31
33
|
graph: InMemoryKnowledgeGraph
|
|
@@ -53,6 +55,8 @@ export interface Services {
|
|
|
53
55
|
codeIndexer: CodeIndexer | null
|
|
54
56
|
codeQuery: CodeQuery | null
|
|
55
57
|
codeLinker: MemoryCodeLinker | null
|
|
58
|
+
modelManager: ModelManager | null
|
|
59
|
+
inferenceRouter: InferenceRouter | null
|
|
56
60
|
logger: Logger
|
|
57
61
|
config: Config
|
|
58
62
|
}
|
|
@@ -371,6 +375,25 @@ export async function initializeServices(config: Config, logger: Logger): Promis
|
|
|
371
375
|
serviceLogger.warn({ error }, 'Failed to initialize code linker, continuing without it')
|
|
372
376
|
}
|
|
373
377
|
|
|
378
|
+
// Initialize SLM Model Manager & Inference Router
|
|
379
|
+
let modelManager: ModelManager | null = null
|
|
380
|
+
let inferenceRouter: InferenceRouter | null = null
|
|
381
|
+
try {
|
|
382
|
+
const slmModelsDir = config.slm?.modelsDir?.replace(/^~/, require('os').homedir())
|
|
383
|
+
modelManager = new ModelManager(serviceLogger, slmModelsDir)
|
|
384
|
+
inferenceRouter = new InferenceRouter(serviceLogger, config, modelManager)
|
|
385
|
+
const slmEnabled = config.slm?.enabled ?? false
|
|
386
|
+
serviceLogger.info({ enabled: slmEnabled }, 'SLM inference router initialized')
|
|
387
|
+
} catch (error) {
|
|
388
|
+
serviceLogger.warn({ error }, 'Failed to initialize SLM inference, continuing with regex only')
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Wire SLM inference into PatternRecognizer (Phase 4C)
|
|
392
|
+
if (inferenceRouter && phase12) {
|
|
393
|
+
phase12.patterns.setInferenceRouter(inferenceRouter)
|
|
394
|
+
serviceLogger.info('SLM inference wired into PatternRecognizer')
|
|
395
|
+
}
|
|
396
|
+
|
|
374
397
|
// Store services
|
|
375
398
|
services = {
|
|
376
399
|
memory,
|
|
@@ -388,6 +411,8 @@ export async function initializeServices(config: Config, logger: Logger): Promis
|
|
|
388
411
|
codeIndexer,
|
|
389
412
|
codeQuery,
|
|
390
413
|
codeLinker,
|
|
414
|
+
modelManager,
|
|
415
|
+
inferenceRouter,
|
|
391
416
|
logger,
|
|
392
417
|
config
|
|
393
418
|
}
|
|
@@ -526,6 +551,22 @@ export function getCodeLinker(): MemoryCodeLinker | null {
|
|
|
526
551
|
return services?.codeLinker ?? null
|
|
527
552
|
}
|
|
528
553
|
|
|
554
|
+
/**
|
|
555
|
+
* Get Model Manager (SLM Upgrade)
|
|
556
|
+
* Returns null if SLM is not initialized
|
|
557
|
+
*/
|
|
558
|
+
export function getModelManager(): ModelManager | null {
|
|
559
|
+
return services?.modelManager ?? null
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Get Inference Router (SLM Upgrade)
|
|
564
|
+
* Returns null if SLM is not initialized
|
|
565
|
+
*/
|
|
566
|
+
export function getInferenceRouter(): InferenceRouter | null {
|
|
567
|
+
return services?.inferenceRouter ?? null
|
|
568
|
+
}
|
|
569
|
+
|
|
529
570
|
/**
|
|
530
571
|
* Check if services are initialized
|
|
531
572
|
*/
|
|
@@ -617,6 +658,12 @@ export async function shutdownServices(): Promise<void> {
|
|
|
617
658
|
}
|
|
618
659
|
}
|
|
619
660
|
|
|
661
|
+
// Unload SLM models
|
|
662
|
+
if (services.modelManager) {
|
|
663
|
+
services.modelManager.unloadAll()
|
|
664
|
+
serviceLogger.info('SLM models unloaded')
|
|
665
|
+
}
|
|
666
|
+
|
|
620
667
|
// Cleanup Phase 12
|
|
621
668
|
services.phase12.cleanup()
|
|
622
669
|
|