Skip to content

Commit 118ec39

Browse files
committed
chore: update models to gemini-2.5/3.0 and add diagnostic scripts
1 parent f00be31 commit 118ec39

6 files changed

Lines changed: 107 additions & 57 deletions

File tree

‎backend/list-models.js‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const { GoogleGenerativeAI } = require("@google/generative-ai");
2+
require('dotenv').config({ path: __dirname + '/.env' });
3+
4+
async function listModels() {
5+
const apiKey = process.env.GEMINI_API_KEY;
6+
if (!apiKey) {
7+
console.error("❌ GEMINI_API_KEY not found in backend/.env");
8+
return;
9+
}
10+
11+
try {
12+
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`);
13+
const data = await response.json();
14+
15+
if (data.error) {
16+
console.error("❌ API Error:", data.error.message);
17+
return;
18+
}
19+
20+
console.log("\n=== Available Models ===");
21+
data.models.forEach(m => {
22+
const name = m.name.replace('models/', '');
23+
const methods = m.supportedGenerationMethods.join(', ');
24+
console.log(`- ${name.padEnd(25)} | Methods: ${methods}`);
25+
});
26+
console.log("========================\n");
27+
} catch (error) {
28+
console.error("❌ Failed to list models:", error.message);
29+
}
30+
}
31+
32+
listModels();

‎backend/routes/tutorRoutes.js‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -231,16 +231,6 @@ router.post('/chat', async (req, res, next) => {
231231
// Combine targeted and broad context (Targeted first)
232232
let context = [...finalTargeted, ...finalBroad].join('\n--- NEXT CHUNK ---\n');
233233

234-
// --- ENHANCEMENT: Inject Structured Syllabus Data (Database-driven) ---
235-
try {
236-
const syllabusContext = await syllabusService.getSyllabusContext(classCode);
237-
if (syllabusContext) {
238-
context = syllabusContext + "\n" + context;
239-
console.log(`[Tutor] 📋 Injected structured syllabus context for ${classCode}`);
240-
}
241-
} catch (syllErr) {
242-
console.warn(`[Tutor] ⚠️ Syllabus context injection failed:`, syllErr.message);
243-
}
244234

245235
if (!context || context.trim().length === 0) {
246236
context = "No specific course documents were found matching this query. Answer using your general knowledge as a Socratic tutor.";

‎backend/services/syllabusService.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ Return ONLY JSON matching this schema:
107107

108108
console.log("[Syllabus] 🤖 Sending direct PDF to Native Google SDK...");
109109

110-
const modelsToTry = ["gemini-2.5-flash", "gemini-2.5-pro"];
110+
const modelsToTry = ["gemini-2.5-flash", "gemini-3-flash-preview", "gemini-2.5-pro"];
111111
let lastError;
112112

113113
for (const modelId of modelsToTry) {

‎backend/services/tutorService.js‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ function getRobustLLM(temperature = 0.2, maxRetries = 1) {
2626
});
2727

2828
const fallbackLite = new ChatGoogleGenerativeAI({
29-
model: "gemini-3.0-flash",
29+
model: "gemini-3-flash-preview",
3030
apiKey: process.env.GEMINI_API_KEY,
3131
temperature,
3232
maxRetries: 1,
3333
});
3434

3535
return primary.withFallbacks({
36-
fallbacks: [fallbackPro]
36+
fallbacks: [fallbackLite, fallbackPro]
3737
});
3838
}
3939

@@ -57,8 +57,15 @@ function getFastLLM() {
5757
maxRetries: 1,
5858
});
5959

60+
const fallbackLite = new ChatGoogleGenerativeAI({
61+
model: "gemini-3-flash-preview",
62+
apiKey: process.env.GEMINI_API_KEY,
63+
temperature: 0.1,
64+
maxRetries: 1,
65+
});
66+
6067
return primary.withFallbacks({
61-
fallbacks: [fallbackPro]
68+
fallbacks: [fallbackLite, fallbackPro]
6269
});
6370
}
6471

‎backend/services/vectorService.js‎

Lines changed: 22 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,32 @@ const courseData = [
4141

4242
class GeminiEmbeddings {
4343
constructor() {
44-
this.model = new GoogleGenerativeAIEmbeddings({
44+
this.primary = new GoogleGenerativeAIEmbeddings({
4545
apiKey: process.env.GEMINI_API_KEY,
46-
modelName: "gemini-embedding-002",
46+
modelName: "gemini-embedding-001",
47+
});
48+
this.fallback = new GoogleGenerativeAIEmbeddings({
49+
apiKey: process.env.GEMINI_API_KEY,
50+
modelName: "gemini-embedding-2",
4751
});
4852
}
4953

5054
async embedDocuments(documents) {
51-
return this.model.embedDocuments(documents);
55+
try {
56+
return await this.primary.embedDocuments(documents);
57+
} catch (err) {
58+
console.warn(`[VectorStore] Primary embedding failed, trying fallback:`, err.message);
59+
return await this.fallback.embedDocuments(documents);
60+
}
5261
}
5362

5463
async embedQuery(document) {
55-
return this.model.embedQuery(document);
64+
try {
65+
return await this.primary.embedQuery(document);
66+
} catch (err) {
67+
console.warn(`[VectorStore] Primary embedding failed, trying fallback:`, err.message);
68+
return await this.fallback.embedQuery(document);
69+
}
5670
}
5771
}
5872

@@ -213,54 +227,19 @@ async function getVectorStore(namespace = '') {
213227

214228
if (totalVectors === 0 && process.env.FORCE_RESEED !== 'true') {
215229
console.log(`[VectorStore] Namespace '${namespace || 'default'}' is empty — seeding course data...`);
216-
// Embed with retry for Gemini rate limits
217-
let vectors;
218-
const maxEmbedRetries = 3;
219-
for (let attempt = 1; attempt <= maxEmbedRetries; attempt++) {
220-
try {
221-
vectors = await embeddings.embedDocuments(courseData);
222-
break;
223-
} catch (embedErr) {
224-
const isRetryable = embedErr.message && (
225-
embedErr.message.includes("429") ||
226-
embedErr.message.includes("RESOURCE_EXHAUSTED") ||
227-
embedErr.message.includes("quota") ||
228-
embedErr.message.includes("503")
229-
);
230-
if (isRetryable && attempt < maxEmbedRetries) {
231-
const waitTime = attempt === 1 ? 5000 : 15000;
232-
console.warn(`[VectorStore] ⚠️ Embed rate limited (attempt ${attempt}). Retrying in ${waitTime / 1000}s...`);
233-
await new Promise(res => setTimeout(res, waitTime));
234-
} else {
235-
console.error(`[VectorStore] ❌ Embed failed after ${attempt} attempts:`, embedErr.message);
236-
throw embedErr;
237-
}
238-
}
239-
}
240-
230+
const vectors = await embeddings.embedDocuments(courseData);
241231
const records = courseData.map((text, i) => ({
242232
id: `course-${i}`,
243233
values: vectors[i],
244234
metadata: { text, source: 'course-data', chunk: i },
245235
}));
246236

247-
// Upsert with retry
237+
// Upsert directly into the given namespace
248238
const targetIndex = namespace ? pineconeIndex.namespace(namespace) : pineconeIndex;
239+
249240
const batchSize = 100;
250241
for (let i = 0; i < records.length; i += batchSize) {
251-
let upserted = false;
252-
for (let attempt = 1; attempt <= 3; attempt++) {
253-
try {
254-
await targetIndex.upsert({ records: records.slice(i, i + batchSize) });
255-
upserted = true;
256-
break;
257-
} catch (upsertErr) {
258-
const waitTime = attempt === 1 ? 3000 : 10000;
259-
console.warn(`[VectorStore] ⚠️ Upsert batch ${i} failed (attempt ${attempt}). Retrying in ${waitTime / 1000}s...`);
260-
await new Promise(res => setTimeout(res, waitTime));
261-
if (attempt === 3) throw upsertErr;
262-
}
263-
}
242+
await targetIndex.upsert({ records: records.slice(i, i + batchSize) });
264243
}
265244
console.log(`[VectorStore] Seeded ${courseData.length} vectors into Pinecone namespace: '${namespace || 'default'}'.`);
266245
} else {

‎backend/test-vector-service.js‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const { GeminiEmbeddings, getClassVectorStore } = require('./services/vectorService');
2+
require('dotenv').config({ path: __dirname + '/.env' });
3+
4+
async function runTest() {
5+
console.log("🚀 Starting Vector Service Test...");
6+
7+
// 1. Test Embeddings
8+
const embeddings = new GeminiEmbeddings();
9+
console.log("\n--- Step 1: Testing Embeddings ---");
10+
try {
11+
// Note: this will try primary then fallback
12+
const queryVector = await embeddings.embedQuery("What is probability?");
13+
console.log(`✅ Embedding successful! Vector length: ${queryVector.length}`);
14+
} catch (err) {
15+
console.error(`❌ Embedding failed: ${err.message}`);
16+
}
17+
18+
// 2. Test Pinecone Connectivity
19+
console.log("\n--- Step 2: Testing Pinecone Search ---");
20+
const testClass = "CS3341"; // Example class
21+
try {
22+
const vectorStore = await getClassVectorStore(testClass);
23+
console.log(`✅ VectorStore initialized for ${testClass}`);
24+
25+
console.log(`Performing similarity search...`);
26+
const results = await vectorStore.similaritySearch("probability", 1);
27+
28+
if (results.length > 0) {
29+
console.log(`✅ Search successful! Found ${results.length} result(s).`);
30+
console.log(`Top result: "${results[0].pageContent.substring(0, 100)}..."`);
31+
} else {
32+
console.log(`ℹ️ Search executed but returned 0 results (namespace might be empty).`);
33+
}
34+
} catch (err) {
35+
console.error(`❌ Pinecone test failed: ${err.message}`);
36+
}
37+
38+
console.log("\n--- Test Complete ---");
39+
process.exit(0);
40+
}
41+
42+
runTest();

0 commit comments

Comments
 (0)