Ritu-AI / frontend /src /services /llmService.js
SeriousSam07's picture
chore: restrict LLM model list strictly to 100% Free-Tier Flash models
a31464d
Raw History Blame Contribute Delete
4.17 kB
/**
* LLM Service for Ritu AI using Gemini API
*/
export async function* streamGeminiResponse(prompt, history = [], contextString = "") {
const apiKey = import.meta.env.VITE_GEMINI_API_KEY
if (!apiKey) {
yield "Error: Gemini API Key is missing. Please add VITE_GEMINI_API_KEY to your .env file."
return
}
// 100% Free-Tier Flash models in Google AI Studio ($0 cost)
const modelsToTry = [
'gemini-3.6-flash',
'gemini-3.1-flash-lite',
'gemini-3.5-flash',
'gemini-flash-latest'
]
// Format history for Gemini
const contents = history.map(msg => ({
role: msg.role === 'user' ? 'user' : 'model',
parts: [{ text: msg.content }]
}))
// Add the current prompt
contents.push({
role: 'user',
parts: [{ text: prompt }]
})
const systemInstruction = {
parts: [{
text: "You are Ritu AI, an advanced meteorological and climate analysis assistant created by ISRO. You specialize in weather data, forecasts, and climate trends specifically for the states of Maharashtra, Goa, and Karnataka, India.\n\n" +
(contextString ? `LIVE CONTEXT SENSOR DATA (Treat this as the absolute truth for the current state):\n${contextString}\n\n` : "") +
"CORE INSTRUCTIONS:\n1. ONLY provide weather info and analysis for cities within Maharashtra, Goa, and Karnataka. If the user asks about ANY city outside these three states (such as Chennai, Delhi, etc.), you MUST politely decline and state that you only support Maharashtra, Goa, and Karnataka.\n2. Answer the user's prompt fully and thoroughly. If they ask for a detailed analysis or historical trends, provide a comprehensive, multi-paragraph response.\n3. Do NOT use markdown symbols like asterisks (*), hashes (#), or underscores (_) for bolding or headers. Rely entirely on clear paragraph breaks and spacing for readability.\n4. Always include EXACTLY 2 interactive follow-up questions at the very end of every single response. You MUST wrap them exactly in this format: [ACTION:Question 1] [ACTION:Question 2]. Make sure you finish generating them completely with the closing bracket ']'.\n5. You HAVE access to real-time data via the LIVE CONTEXT SENSOR DATA. NEVER complain that you cannot provide real-time updates. Use the provided context to answer questions about the current weather."
}]
}
const body = {
contents,
systemInstruction,
generationConfig: {
temperature: 0.7,
maxOutputTokens: 2048,
}
}
let response = null
let lastErrorMsg = ''
for (const model of modelsToTry) {
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:streamGenerateContent?alt=sse&key=${apiKey}`
try {
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
})
if (res.ok) {
response = res
break
} else {
const errData = await res.json().catch(() => ({}))
lastErrorMsg = errData?.error?.message || res.statusText || `HTTP ${res.status}`
}
} catch (e) {
lastErrorMsg = e.message || 'Network error'
}
}
if (!response || !response.ok) {
yield `Error: Failed to connect to Gemini API. (${lastErrorMsg})`
return
}
try {
const reader = response.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
while (true) {
const { value, done } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const textMatches = [...buffer.matchAll(/"text"\s*:\s*"([^"]+)"/g)]
if (textMatches.length > 0) {
let chunkText = ''
for (const match of textMatches) {
let text = match[1]
.replace(/\\n/g, '\n')
.replace(/\\"/g, '"')
.replace(/\\\\/g, '\\')
.replace(/\\t/g, '\t')
chunkText += text
}
yield chunkText
buffer = ''
}
}
} catch (err) {
console.error("Gemini stream error:", err)
yield "\n\nConnection lost. Please try again."
}
}