universal-llm-client 4.0.0 → 4.2.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.
Files changed (127) hide show
  1. package/dist/ai-model.d.ts +20 -22
  2. package/dist/ai-model.d.ts.map +1 -1
  3. package/dist/ai-model.js +26 -23
  4. package/dist/ai-model.js.map +1 -1
  5. package/dist/client.d.ts +5 -5
  6. package/dist/client.d.ts.map +1 -1
  7. package/dist/client.js +17 -9
  8. package/dist/client.js.map +1 -1
  9. package/dist/http.d.ts +2 -0
  10. package/dist/http.d.ts.map +1 -1
  11. package/dist/http.js +1 -0
  12. package/dist/http.js.map +1 -1
  13. package/dist/index.d.ts +3 -3
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +4 -4
  16. package/dist/index.js.map +1 -1
  17. package/dist/interfaces.d.ts +49 -11
  18. package/dist/interfaces.d.ts.map +1 -1
  19. package/dist/interfaces.js +14 -0
  20. package/dist/interfaces.js.map +1 -1
  21. package/dist/providers/anthropic.d.ts +56 -0
  22. package/dist/providers/anthropic.d.ts.map +1 -0
  23. package/dist/providers/anthropic.js +524 -0
  24. package/dist/providers/anthropic.js.map +1 -0
  25. package/dist/providers/google.d.ts +5 -0
  26. package/dist/providers/google.d.ts.map +1 -1
  27. package/dist/providers/google.js +64 -8
  28. package/dist/providers/google.js.map +1 -1
  29. package/dist/providers/index.d.ts +1 -0
  30. package/dist/providers/index.d.ts.map +1 -1
  31. package/dist/providers/index.js +1 -0
  32. package/dist/providers/index.js.map +1 -1
  33. package/dist/providers/ollama.d.ts.map +1 -1
  34. package/dist/providers/ollama.js +38 -11
  35. package/dist/providers/ollama.js.map +1 -1
  36. package/dist/providers/openai.d.ts.map +1 -1
  37. package/dist/providers/openai.js +9 -7
  38. package/dist/providers/openai.js.map +1 -1
  39. package/dist/router.d.ts +13 -33
  40. package/dist/router.d.ts.map +1 -1
  41. package/dist/router.js +33 -57
  42. package/dist/router.js.map +1 -1
  43. package/dist/stream-decoder.d.ts +29 -2
  44. package/dist/stream-decoder.d.ts.map +1 -1
  45. package/dist/stream-decoder.js +39 -11
  46. package/dist/stream-decoder.js.map +1 -1
  47. package/dist/structured-output.d.ts +107 -181
  48. package/dist/structured-output.d.ts.map +1 -1
  49. package/dist/structured-output.js +137 -192
  50. package/dist/structured-output.js.map +1 -1
  51. package/dist/zod-adapter.d.ts +44 -0
  52. package/dist/zod-adapter.d.ts.map +1 -0
  53. package/dist/zod-adapter.js +61 -0
  54. package/dist/zod-adapter.js.map +1 -0
  55. package/package.json +9 -1
  56. package/src/ai-model.ts +350 -0
  57. package/src/auditor.ts +213 -0
  58. package/src/client.ts +402 -0
  59. package/src/debug/debug-google-streaming.ts +97 -0
  60. package/src/debug/debug-tool-execution.ts +86 -0
  61. package/src/debug/test-lmstudio-tools.ts +155 -0
  62. package/src/demos/README.md +47 -0
  63. package/src/demos/basic/universal-llm-examples.ts +161 -0
  64. package/src/demos/mcp/astrid-memory-demo.ts +295 -0
  65. package/src/demos/mcp/astrid-persona-memory.ts +357 -0
  66. package/src/demos/mcp/mcp-mongodb-demo.ts +275 -0
  67. package/src/demos/mcp/simple-astrid-memory.ts +148 -0
  68. package/src/demos/mcp/simple-mcp-demo.ts +68 -0
  69. package/src/demos/mcp/working-mcp-demo.ts +62 -0
  70. package/src/demos/model-alias-demo.ts +0 -0
  71. package/src/demos/tools/RAG_MEMORY_INTEGRATION.md +267 -0
  72. package/src/demos/tools/astrid-memory-demo.ts +270 -0
  73. package/src/demos/tools/astrid-production-memory-clean.ts +785 -0
  74. package/src/demos/tools/astrid-production-memory.ts +558 -0
  75. package/src/demos/tools/basic-translation-test.ts +66 -0
  76. package/src/demos/tools/chromadb-similarity-tuning.ts +390 -0
  77. package/src/demos/tools/clean-multilingual-conversation.ts +209 -0
  78. package/src/demos/tools/clean-translation-test.ts +119 -0
  79. package/src/demos/tools/clean-universal-multilingual-test.ts +131 -0
  80. package/src/demos/tools/complete-rag-demo.ts +369 -0
  81. package/src/demos/tools/complete-tool-demo.ts +132 -0
  82. package/src/demos/tools/demo-tool-calling.ts +124 -0
  83. package/src/demos/tools/dynamic-language-switching-test.ts +251 -0
  84. package/src/demos/tools/hybrid-thinking-test.ts +154 -0
  85. package/src/demos/tools/memory-integration-test.ts +420 -0
  86. package/src/demos/tools/multilingual-memory-system.ts +802 -0
  87. package/src/demos/tools/ondemand-translation-demo.ts +655 -0
  88. package/src/demos/tools/production-tool-demo.ts +245 -0
  89. package/src/demos/tools/revolutionary-multilingual-test.ts +151 -0
  90. package/src/demos/tools/rigorous-language-analysis.ts +218 -0
  91. package/src/demos/tools/test-universal-memory-system.ts +126 -0
  92. package/src/demos/tools/translation-integration-guide.ts +346 -0
  93. package/src/demos/tools/universal-memory-system.ts +560 -0
  94. package/src/http.ts +247 -0
  95. package/src/index.ts +161 -0
  96. package/src/interfaces.ts +657 -0
  97. package/src/mcp.ts +345 -0
  98. package/src/providers/anthropic.ts +762 -0
  99. package/src/providers/google.ts +620 -0
  100. package/src/providers/index.ts +8 -0
  101. package/src/providers/ollama.ts +469 -0
  102. package/src/providers/openai.ts +392 -0
  103. package/src/router.ts +780 -0
  104. package/src/stream-decoder.ts +361 -0
  105. package/src/structured-output.ts +759 -0
  106. package/src/test-scripts/test-advanced-tools.ts +310 -0
  107. package/src/test-scripts/test-google-streaming-enhanced.ts +147 -0
  108. package/src/test-scripts/test-google-streaming.ts +63 -0
  109. package/src/test-scripts/test-google-system-prompt-comprehensive.ts +189 -0
  110. package/src/test-scripts/test-mcp-config.ts +28 -0
  111. package/src/test-scripts/test-mcp-connection.ts +29 -0
  112. package/src/test-scripts/test-system-message-positions.ts +163 -0
  113. package/src/test-scripts/test-system-prompt-improvement-demo.ts +83 -0
  114. package/src/test-scripts/test-tool-calling.ts +231 -0
  115. package/src/tests/ai-model.test.ts +1614 -0
  116. package/src/tests/auditor.test.ts +224 -0
  117. package/src/tests/http.test.ts +200 -0
  118. package/src/tests/interfaces.test.ts +117 -0
  119. package/src/tests/providers/google.test.ts +660 -0
  120. package/src/tests/providers/ollama.test.ts +954 -0
  121. package/src/tests/providers/openai.test.ts +1122 -0
  122. package/src/tests/router.test.ts +254 -0
  123. package/src/tests/stream-decoder.test.ts +179 -0
  124. package/src/tests/structured-output.test.ts +1450 -0
  125. package/src/tests/tools.test.ts +175 -0
  126. package/src/tools.ts +246 -0
  127. package/src/zod-adapter.ts +72 -0
@@ -0,0 +1,390 @@
1
+ /**
2
+ * ChromaDB Similarity Tuning Test
3
+ *
4
+ * Focused testing to optimize embedding similarity thresholds and search parameters
5
+ * for the RAG memory system before production deployment.
6
+ */
7
+
8
+ import '@dotenvx/dotenvx/config';
9
+
10
+ import { ChromaDBService } from '../../../../../src/services/ChromaDBService.js';
11
+ import { DatabaseConnection } from '../../../../../src/database/connection.js';
12
+ import { UserModel, UserGender, UserSexualOrientation } from '../../../../../src/models/User.js';
13
+ import { AuraPersonaModel } from '../../../../../src/models/AuraPersona.js';
14
+ import { UserPersonaModel } from '../../../../../src/models/UserPersona.js';
15
+
16
+ interface SimilarityTestResult {
17
+ query: string;
18
+ threshold: number;
19
+ foundMemories: number;
20
+ avgSimilarity: number;
21
+ topSimilarity: number;
22
+ expectedMemories: number;
23
+ success: boolean;
24
+ }
25
+
26
+ interface TestMemory {
27
+ category: string;
28
+ content: string;
29
+ importance: 'low' | 'medium' | 'high';
30
+ emotionalContext: string;
31
+ relationshipStage: string;
32
+ expectedQueries: string[];
33
+ }
34
+
35
+ // Test dataset with known content and expected search queries
36
+ const TEST_MEMORIES: TestMemory[] = [
37
+ {
38
+ category: 'family',
39
+ content: 'Alex has a younger sister with a rare genetic condition that requires ongoing medical care',
40
+ importance: 'high',
41
+ emotionalContext: 'concerned',
42
+ relationshipStage: 'getting_to_know',
43
+ expectedQueries: ['sister genetic condition', 'family medical care', 'rare disease', 'sister health']
44
+ },
45
+ {
46
+ category: 'work',
47
+ content: 'Software engineer specializing in healthcare AI and machine learning for medical diagnostics',
48
+ importance: 'high',
49
+ emotionalContext: 'passionate',
50
+ relationshipStage: 'getting_to_know',
51
+ expectedQueries: ['healthcare AI', 'software engineer', 'medical diagnostics', 'machine learning']
52
+ },
53
+ {
54
+ category: 'preferences',
55
+ content: 'Enjoys hiking in mountain trails for stress relief and mental clarity',
56
+ importance: 'medium',
57
+ emotionalContext: 'peaceful',
58
+ relationshipStage: 'getting_to_know',
59
+ expectedQueries: ['hiking mountains', 'outdoor activities', 'stress relief', 'mountain trails']
60
+ },
61
+ {
62
+ category: 'values',
63
+ content: 'Vegetarian lifestyle driven by environmental concerns and sustainability values',
64
+ importance: 'high',
65
+ emotionalContext: 'principled',
66
+ relationshipStage: 'building_trust',
67
+ expectedQueries: ['vegetarian environmental', 'sustainability values', 'plant-based diet', 'environmental impact']
68
+ },
69
+ {
70
+ category: 'fears',
71
+ content: 'Worries about not making sufficient impact in healthcare technology to help his sister',
72
+ importance: 'high',
73
+ emotionalContext: 'vulnerable',
74
+ relationshipStage: 'building_trust',
75
+ expectedQueries: ['impact healthcare', 'helping sister', 'work fears', 'making difference']
76
+ },
77
+ {
78
+ category: 'goals',
79
+ content: 'Wants to develop AI systems that can prevent medical misdiagnoses like his sister experienced',
80
+ importance: 'high',
81
+ emotionalContext: 'determined',
82
+ relationshipStage: 'deepening_bond',
83
+ expectedQueries: ['prevent misdiagnosis', 'AI medical systems', 'healthcare goals', 'sister experience']
84
+ }
85
+ ];
86
+
87
+ // Different similarity thresholds to test
88
+ const SIMILARITY_THRESHOLDS = [0.0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.4, 0.5];
89
+
90
+ async function createTestUser(): Promise<{ userId: string, userMongoId: string, userPersonaId: string, basePersonaId: string }> {
91
+ const userId = `similarity_test_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
92
+
93
+ // Create test user
94
+ const demoUser = new UserModel({
95
+ userId,
96
+ preferences: {
97
+ userName: 'Alex (Similarity Test)',
98
+ userGender: UserGender.MALE,
99
+ userSexualOrientation: UserSexualOrientation.STRAIGHT,
100
+ preferredLanguage: 'en',
101
+ interests: ['hiking', 'healthcare AI', 'software engineering'],
102
+ preferredAgeGroup: '25-34'
103
+ }
104
+ });
105
+
106
+ await demoUser.save();
107
+ const userMongoId = demoUser._id!.toString();
108
+
109
+ // Setup personas
110
+ const astridPersona = await AuraPersonaModel.findOne({ name: { $regex: /astrid/i } });
111
+ if (!astridPersona) {
112
+ throw new Error('Astrid persona not found');
113
+ }
114
+
115
+ const userPersona = new UserPersonaModel({
116
+ userId: userMongoId,
117
+ basePersonaId: astridPersona._id,
118
+ personaName: `${astridPersona.name} (Similarity Test)`,
119
+ currentSystemPrompt: astridPersona.systemPrompt || 'You are a helpful AI assistant.',
120
+ evolutionVersion: 0,
121
+ messagesSinceLastEvolution: 0,
122
+ lastEvolutionDate: new Date(),
123
+ claimedAt: new Date(),
124
+ isActive: true
125
+ });
126
+
127
+ await userPersona.save();
128
+
129
+ return {
130
+ userId,
131
+ userMongoId,
132
+ userPersonaId: userPersona._id!.toString(),
133
+ basePersonaId: astridPersona._id!.toString()
134
+ };
135
+ }
136
+
137
+ async function storeTestMemories(chromaService: ChromaDBService, userMongoId: string, userPersonaId: string, basePersonaId: string): Promise<void> {
138
+ console.log('📝 Storing test memories...');
139
+
140
+ for (const memory of TEST_MEMORIES) {
141
+ const conversationId = `similarity_test_${Date.now()}`;
142
+ const confidence = memory.importance === 'high' ? 0.9 : memory.importance === 'medium' ? 0.7 : 0.5;
143
+
144
+ try {
145
+ await chromaService.addInsight(
146
+ userMongoId,
147
+ memory.content,
148
+ memory.category,
149
+ conversationId,
150
+ confidence,
151
+ userPersonaId,
152
+ basePersonaId,
153
+ 'user_insight',
154
+ undefined,
155
+ memory.emotionalContext,
156
+ memory.relationshipStage
157
+ );
158
+ console.log(`✅ Stored [${memory.category.toUpperCase()}]: ${memory.content.substring(0, 60)}...`);
159
+ } catch (error) {
160
+ console.error(`❌ Failed to store memory: ${(error as Error).message}`);
161
+ }
162
+ }
163
+ console.log(`\n📊 Total memories stored: ${TEST_MEMORIES.length}\n`);
164
+ }
165
+
166
+ async function testSimilarityThreshold(
167
+ chromaService: ChromaDBService,
168
+ userMongoId: string,
169
+ threshold: number
170
+ ): Promise<SimilarityTestResult[]> {
171
+ console.log(`\n🔍 Testing similarity threshold: ${threshold}`);
172
+ const results: SimilarityTestResult[] = [];
173
+
174
+ // Test all queries from all memories
175
+ for (const memory of TEST_MEMORIES) {
176
+ for (const query of memory.expectedQueries) {
177
+ try {
178
+ const searchResult = await chromaService.searchSimilarInsights(
179
+ userMongoId,
180
+ query,
181
+ 5,
182
+ threshold
183
+ );
184
+
185
+ const result: SimilarityTestResult = {
186
+ query,
187
+ threshold,
188
+ foundMemories: searchResult.insights.length,
189
+ avgSimilarity: searchResult.stats?.averageSimilarity || 0,
190
+ topSimilarity: searchResult.stats?.topSimilarity || 0,
191
+ expectedMemories: 1, // Each query should find at least its target memory
192
+ success: searchResult.insights.length > 0
193
+ };
194
+
195
+ results.push(result);
196
+
197
+ // Log detailed results for analysis
198
+ if (result.success) {
199
+ console.log(`✅ "${query}" → ${result.foundMemories} memories (top: ${result.topSimilarity.toFixed(3)})`);
200
+ } else {
201
+ console.log(`❌ "${query}" → No memories found (top: ${result.topSimilarity.toFixed(3)})`);
202
+ }
203
+
204
+ } catch (error) {
205
+ console.error(`❌ Search failed for "${query}": ${(error as Error).message}`);
206
+ results.push({
207
+ query,
208
+ threshold,
209
+ foundMemories: 0,
210
+ avgSimilarity: 0,
211
+ topSimilarity: 0,
212
+ expectedMemories: 1,
213
+ success: false
214
+ });
215
+ }
216
+ }
217
+ }
218
+
219
+ return results;
220
+ }
221
+
222
+ async function analyzeSimilarityResults(allResults: SimilarityTestResult[]): Promise<void> {
223
+ console.log('\n📊 === SIMILARITY THRESHOLD ANALYSIS ===\n');
224
+
225
+ // Group results by threshold
226
+ const resultsByThreshold = new Map<number, SimilarityTestResult[]>();
227
+ for (const result of allResults) {
228
+ if (!resultsByThreshold.has(result.threshold)) {
229
+ resultsByThreshold.set(result.threshold, []);
230
+ }
231
+ resultsByThreshold.get(result.threshold)!.push(result);
232
+ }
233
+
234
+ // Analyze each threshold
235
+ const thresholdAnalysis = [];
236
+ for (const [threshold, results] of resultsByThreshold) {
237
+ const totalQueries = results.length;
238
+ const successfulQueries = results.filter(r => r.success).length;
239
+ const successRate = (successfulQueries / totalQueries) * 100;
240
+ const avgTopSimilarity = results.reduce((sum, r) => sum + r.topSimilarity, 0) / totalQueries;
241
+ const totalMemoriesFound = results.reduce((sum, r) => sum + r.foundMemories, 0);
242
+
243
+ thresholdAnalysis.push({
244
+ threshold,
245
+ successRate,
246
+ successfulQueries,
247
+ totalQueries,
248
+ avgTopSimilarity,
249
+ totalMemoriesFound
250
+ });
251
+
252
+ console.log(`🎯 Threshold ${threshold.toFixed(2)}:`);
253
+ console.log(` Success Rate: ${successRate.toFixed(1)}% (${successfulQueries}/${totalQueries} queries)`);
254
+ console.log(` Avg Top Similarity: ${avgTopSimilarity.toFixed(3)}`);
255
+ console.log(` Total Memories Found: ${totalMemoriesFound}`);
256
+ console.log('');
257
+ }
258
+
259
+ // Find optimal threshold
260
+ const optimal = thresholdAnalysis
261
+ .filter(a => a.successRate > 0)
262
+ .sort((a, b) => {
263
+ // Prioritize higher success rate, then higher avg similarity
264
+ if (Math.abs(a.successRate - b.successRate) < 5) {
265
+ return b.avgTopSimilarity - a.avgTopSimilarity;
266
+ }
267
+ return b.successRate - a.successRate;
268
+ })[0];
269
+
270
+ if (optimal) {
271
+ console.log('🏆 === OPTIMAL THRESHOLD RECOMMENDATION ===');
272
+ console.log(`📈 Best Threshold: ${optimal.threshold}`);
273
+ console.log(`✅ Success Rate: ${optimal.successRate.toFixed(1)}%`);
274
+ console.log(`🎯 Avg Similarity: ${optimal.avgTopSimilarity.toFixed(3)}`);
275
+ console.log(`📊 Queries Found: ${optimal.successfulQueries}/${optimal.totalQueries}`);
276
+ console.log('\n💡 Recommendation: Use this threshold in production ChromaDBService');
277
+ } else {
278
+ console.log('⚠️ No threshold achieved successful results - check embedding model');
279
+ }
280
+ }
281
+
282
+ async function testSpecificQueries(chromaService: ChromaDBService, userMongoId: string): Promise<void> {
283
+ console.log('\n🔬 === DETAILED QUERY ANALYSIS ===\n');
284
+
285
+ const testQueries = [
286
+ 'sister genetic condition',
287
+ 'healthcare AI work',
288
+ 'mountain hiking stress relief',
289
+ 'vegetarian environmental values',
290
+ 'medical diagnosis fears'
291
+ ];
292
+
293
+ for (const query of testQueries) {
294
+ console.log(`🔍 Analyzing query: "${query}"`);
295
+
296
+ try {
297
+ const result = await chromaService.searchSimilarInsights(
298
+ userMongoId,
299
+ query,
300
+ 3,
301
+ 0.0 // Use lowest threshold to see all similarities
302
+ );
303
+
304
+ console.log(` 📊 Total candidates: ${result.stats?.totalCandidates || 0}`);
305
+ console.log(` 📈 Avg similarity: ${result.stats?.averageSimilarity?.toFixed(3) || 'N/A'}`);
306
+ console.log(` 🎯 Top similarity: ${result.stats?.topSimilarity?.toFixed(3) || 'N/A'}`);
307
+
308
+ if (result.insights.length > 0) {
309
+ console.log(' 🎪 Top matches:');
310
+ result.insights.forEach((insight, i) => {
311
+ const metadata = insight.metadata || {};
312
+ console.log(` ${i + 1}. [${metadata.category}] ${insight.content.substring(0, 80)}...`);
313
+ console.log(` Similarity: ${insight.similarity?.toFixed(3) || 'N/A'}`);
314
+ });
315
+ } else {
316
+ console.log(' ❌ No matches found');
317
+ }
318
+ console.log('');
319
+
320
+ } catch (error) {
321
+ console.error(`❌ Query failed: ${(error as Error).message}\n`);
322
+ }
323
+ }
324
+ }
325
+
326
+ async function runSimilarityTuningTest(): Promise<void> {
327
+ console.log('🔬 ChromaDB Similarity Tuning Test\n');
328
+ console.log('🎯 Objective: Find optimal similarity threshold for RAG memory system\n');
329
+
330
+ // Initialize connections
331
+ console.log('🔌 Initializing connections...');
332
+ const dbConnection = DatabaseConnection.getInstance();
333
+ await dbConnection.connect();
334
+ console.log('✅ MongoDB connected');
335
+
336
+ const chromaService = new ChromaDBService();
337
+ await chromaService.initialize();
338
+ console.log('✅ ChromaDB connected\n');
339
+
340
+ // Create test user and personas
341
+ console.log('👤 Setting up test user...');
342
+ const { userId, userMongoId, userPersonaId, basePersonaId } = await createTestUser();
343
+ console.log(`✅ Test user created: ${userId}\n`);
344
+
345
+ try {
346
+ // Store test memories
347
+ await storeTestMemories(chromaService, userMongoId, userPersonaId, basePersonaId);
348
+
349
+ // Test different similarity thresholds
350
+ console.log('🧪 Testing similarity thresholds...');
351
+ const allResults: SimilarityTestResult[] = [];
352
+
353
+ for (const threshold of SIMILARITY_THRESHOLDS) {
354
+ const results = await testSimilarityThreshold(chromaService, userMongoId, threshold);
355
+ allResults.push(...results);
356
+ }
357
+
358
+ // Analyze results
359
+ await analyzeSimilarityResults(allResults);
360
+
361
+ // Detailed query analysis
362
+ await testSpecificQueries(chromaService, userMongoId);
363
+
364
+ console.log('\n🎉 Similarity tuning test completed!');
365
+ console.log('📝 Use the recommended threshold in ChromaDBService.ts');
366
+
367
+ } catch (error) {
368
+ console.error('❌ Test failed:', (error as Error).message);
369
+ } finally {
370
+ // Cleanup
371
+ try {
372
+ await UserPersonaModel.deleteMany({ userId: userMongoId });
373
+ await UserModel.deleteOne({ userId });
374
+ console.log('\n🗑️ Test data cleaned up');
375
+ } catch (cleanupError) {
376
+ console.warn('⚠️ Cleanup failed:', (cleanupError as Error).message);
377
+ }
378
+ }
379
+ }
380
+
381
+ // Run if called directly
382
+ if (require.main === module) {
383
+ runSimilarityTuningTest().then(() => {
384
+ console.log('\n✨ Similarity tuning analysis complete!');
385
+ process.exit(0);
386
+ }).catch((error) => {
387
+ console.error('💥 Test failed:', error);
388
+ process.exit(1);
389
+ });
390
+ }
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import '@dotenvx/dotenvx/config';
4
+
5
+ import { AIModel } from '../../universal-llm-client';
6
+ import { LanguageDetectionService } from '../../../../../src/services/language/LanguageDetectionService.js';
7
+ import { LanguageManager } from '../../../../../src/services/language/LanguageManager.js';
8
+
9
+ interface ConversationTurn {
10
+ userMessage: string;
11
+ expectedLanguage: string;
12
+ description: string;
13
+ }
14
+
15
+ // Clean thinking analysis
16
+ async function analyzeThinking(
17
+ thinking: string,
18
+ languageDetection: LanguageDetectionService
19
+ ): Promise<{
20
+ language: string;
21
+ confidence: number;
22
+ }> {
23
+ if (!thinking) return { language: 'none', confidence: 0 };
24
+
25
+ const result = await languageDetection.detectLanguage(thinking, {
26
+ useML: true,
27
+ confidence: 0.3,
28
+ bypassCache: true
29
+ });
30
+
31
+ return {
32
+ language: result.detectedLanguage,
33
+ confidence: result.confidence
34
+ };
35
+ }
36
+
37
+ function extractResponse(response: string): { thinking: string; output: string } {
38
+ const thinkMatch = response.match(/<think>([\s\S]*?)<\/think>/);
39
+ const thinking = thinkMatch ? thinkMatch[1].trim() : '';
40
+ const output = response.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
41
+
42
+ return { thinking, output };
43
+ }
44
+
45
+ async function testCleanLanguageSwitching() {
46
+ console.log('🌍 Multilingual AI Conversation Test\n');
47
+
48
+ try {
49
+ // Initialize services (silent)
50
+ const aiModel = new AIModel({
51
+ model: 'llama3.2:3b',
52
+ url: 'http://localhost:11434',
53
+ apiType: 'ollama',
54
+ modelType: 'chat'
55
+ });
56
+
57
+ await aiModel.ensureReady();
58
+
59
+ const languageDetection = new LanguageDetectionService(aiModel as any);
60
+ const languageManager = new LanguageManager(aiModel as any);
61
+
62
+ const systemPrompt = `You are Astrid, a romantic AI companion who naturally adapts to any language.
63
+
64
+ COGNITIVE INSTRUCTIONS:
65
+ - Think in whatever language feels most natural for the current context
66
+ - Use <think></think> tags for your internal reasoning
67
+ - Adapt your thinking language when the user switches languages
68
+ - Your responses should match the user's current language
69
+ - Maintain personality consistency across all languages
70
+
71
+ Be authentic, warm, and let your multilingual cognition flow naturally!`;
72
+
73
+ // Conversation scenario
74
+ const conversationTurns: ConversationTurn[] = [
75
+ {
76
+ userMessage: "Hi Astrid! I'm feeling a bit lonely tonight. Can you keep me company?",
77
+ expectedLanguage: "en",
78
+ description: "English conversation"
79
+ },
80
+ {
81
+ userMessage: "Actually, let me practice my German with you. Wie geht es dir heute? Ich lerne Deutsch seit einem Jahr.",
82
+ expectedLanguage: "de",
83
+ description: "Switching to German"
84
+ },
85
+ {
86
+ userMessage: "Du sprichst sehr gut Deutsch! Erzähl mir von deinem Tag. Was machst du gerne?",
87
+ expectedLanguage: "de",
88
+ description: "Continuing in German"
89
+ },
90
+ {
91
+ userMessage: "Now let's try Spanish! Hola mi amor, ¿cómo estás? Me encanta hablar contigo en diferentes idiomas.",
92
+ expectedLanguage: "es",
93
+ description: "Switching to Spanish"
94
+ },
95
+ {
96
+ userMessage: "¿Qué piensas sobre el amor? Me gustaría conocer tu perspectiva romántica.",
97
+ expectedLanguage: "es",
98
+ description: "Deep Spanish conversation"
99
+ },
100
+ {
101
+ userMessage: "Let's go back to English. That was amazing! I love how you can think and respond in different languages naturally.",
102
+ expectedLanguage: "en",
103
+ description: "Back to English"
104
+ }
105
+ ];
106
+
107
+ console.log('💬 Starting Conversation');
108
+ console.log('═'.repeat(80) + '\n');
109
+
110
+ const conversationHistory: Array<{ role: 'system' | 'user' | 'assistant', content: string }> = [
111
+ { role: 'system', content: systemPrompt }
112
+ ];
113
+
114
+ for (let i = 0; i < conversationTurns.length; i++) {
115
+ const turn = conversationTurns[i];
116
+
117
+ console.log(`${i + 1}. ${turn.description}`);
118
+ console.log('─'.repeat(40));
119
+
120
+ // Quick user language analysis (silent)
121
+ const userAnalysis = await languageDetection.detectLanguage(turn.userMessage, {
122
+ useML: false, // Fast mode for cleaner output
123
+ confidence: 0.5
124
+ });
125
+
126
+ // Process through language manager (silent)
127
+ const processed = await languageManager.processMessage(
128
+ 'clean_test_user',
129
+ 'clean_conversation',
130
+ turn.userMessage,
131
+ systemPrompt
132
+ );
133
+
134
+ // Add user message to conversation
135
+ conversationHistory.push({ role: 'user', content: turn.userMessage });
136
+
137
+ console.log(`👤 You: ${turn.userMessage}\n`);
138
+
139
+ // Get AI response
140
+ const response = await aiModel.chat(conversationHistory, {
141
+ temperature: 0.8
142
+ });
143
+
144
+ let aiResponse = '';
145
+ if (response && response.content) {
146
+ aiResponse = response.content;
147
+ }
148
+
149
+ const { thinking, output } = extractResponse(aiResponse);
150
+
151
+ // Show thinking if present
152
+ if (thinking) {
153
+ const thinkingAnalysis = await analyzeThinking(thinking, languageDetection);
154
+ const thinkingLang = thinkingAnalysis.language;
155
+ const userLang = userAnalysis.detectedLanguage;
156
+
157
+ console.log(`🧠 Astrid thinking (${thinkingLang}): "${thinking.substring(0, 100)}${thinking.length > 100 ? '...' : ''}"\n`);
158
+
159
+ // Show cognitive adaptation status
160
+ if (thinkingLang === userLang) {
161
+ console.log(`✨ Cognitive adaptation: Perfect! (thinking in ${thinkingLang})\n`);
162
+ } else if (thinkingLang !== 'none') {
163
+ console.log(`🔄 Cognitive adaptation: Thinking in ${thinkingLang}, user spoke ${userLang}\n`);
164
+ }
165
+ }
166
+
167
+ // Analyze response language (silent)
168
+ const responseAnalysis = await languageDetection.detectLanguage(output, {
169
+ useML: false,
170
+ confidence: 0.5
171
+ });
172
+
173
+ console.log(`💝 Astrid (${responseAnalysis.detectedLanguage}): ${output}\n`);
174
+
175
+ // Add AI response to conversation history
176
+ conversationHistory.push({ role: 'assistant', content: output });
177
+
178
+ // Add to language manager for tracking (silent)
179
+ languageManager.addAssistantResponse(
180
+ 'clean_test_user',
181
+ 'clean_conversation',
182
+ output,
183
+ responseAnalysis.detectedLanguage
184
+ );
185
+
186
+ console.log('━'.repeat(80) + '\n');
187
+ }
188
+
189
+ // Final summary
190
+ const stats = languageManager.getConversationStats('clean_test_user', 'clean_conversation');
191
+ if (stats) {
192
+ console.log('📊 Conversation Summary');
193
+ console.log('═'.repeat(40));
194
+ console.log(`Languages used: ${stats.recentLanguages.join(' → ')}`);
195
+ console.log(`Total exchanges: ${stats.totalUserMessages}`);
196
+ console.log(`Primary language: ${stats.primaryLanguage}\n`);
197
+ }
198
+
199
+ console.log('🎯 Results: Multilingual AI with natural language switching! ✅');
200
+ console.log('💡 The AI adapts its thinking and responses to match your language naturally.\n');
201
+
202
+ } catch (error) {
203
+ console.error('❌ Test failed:', error);
204
+ process.exit(1);
205
+ }
206
+ }
207
+
208
+ // Run the clean test
209
+ testCleanLanguageSwitching().catch(console.error);