mohhhhhit commited on
Commit
27d159a
·
verified ·
1 Parent(s): 918d3ed

Update utils/llm_generator.py

Browse files

changed settings for Gemini model selection

Files changed (1) hide show
  1. utils/llm_generator.py +297 -297
utils/llm_generator.py CHANGED
@@ -1,297 +1,297 @@
1
- """
2
- Real LLM-based generator using Groq or Google Gemini API.
3
- This ACTUALLY generates responses (unlike SimpleGenerator which just extracts text).
4
- """
5
-
6
- import os
7
- from typing import List, Dict, Optional
8
- import streamlit as st
9
-
10
- try:
11
- from groq import Groq
12
- GROQ_AVAILABLE = True
13
- except ImportError:
14
- GROQ_AVAILABLE = False
15
-
16
- try:
17
- import google.generativeai as genai
18
- GEMINI_AVAILABLE = True
19
- except ImportError:
20
- GEMINI_AVAILABLE = False
21
-
22
-
23
- class LLMGenerator:
24
- """
25
- Actual LLM-based response generation using Groq (Llama-3-70B) or Gemini.
26
- This is what NotebookLM uses - real AI generation, not text extraction.
27
- """
28
-
29
- def __init__(self, provider: str = "groq", api_key: Optional[str] = None):
30
- """
31
- Initialize LLM generator.
32
-
33
- Args:
34
- provider: "groq" or "gemini"
35
- api_key: API key (if None, reads from environment or asks user)
36
- """
37
- self.provider = provider
38
- self.client = None
39
- self.ready = False
40
-
41
- # Get API key
42
- if api_key:
43
- self.api_key = api_key
44
- elif provider == "groq":
45
- self.api_key = os.getenv("GROQ_API_KEY", "")
46
- elif provider == "gemini":
47
- self.api_key = os.getenv("GEMINI_API_KEY", "")
48
- else:
49
- self.api_key = ""
50
-
51
- # Initialize client
52
- self._initialize_client()
53
-
54
- def _initialize_client(self):
55
- """Initialize the LLM client."""
56
- if not self.api_key:
57
- return
58
-
59
- try:
60
- if self.provider == "groq" and GROQ_AVAILABLE:
61
- # Initialize Groq client with explicit parameters
62
- # Avoid potential proxies kwarg issue by not passing extra config
63
- import os
64
- os.environ["GROQ_API_KEY"] = self.api_key
65
- self.client = Groq() # Will read from environment
66
- self.ready = True
67
- elif self.provider == "gemini" and GEMINI_AVAILABLE:
68
- genai.configure(api_key=self.api_key)
69
- self.client = genai.GenerativeModel('gemini-1.5-flash')
70
- self.ready = True
71
- except Exception as e:
72
- print(f"Failed to initialize {self.provider}: {e}")
73
- self.ready = False
74
-
75
- def set_api_key(self, api_key: str):
76
- """Update API key and reinitialize."""
77
- self.api_key = api_key
78
- self._initialize_client()
79
-
80
- def generate_response(
81
- self,
82
- prompt: str,
83
- context: str = "",
84
- use_case: str = "explanation",
85
- metadatas: List[Dict] = None,
86
- temperature: float = 0.7,
87
- max_tokens: int = 1500,
88
- **kwargs
89
- ) -> str:
90
- """
91
- Generate response using actual LLM (NotebookLM-style).
92
-
93
- Args:
94
- prompt: User's question
95
- context: Retrieved context from documents
96
- use_case: Response type (explanation, summary, qa, notes)
97
- metadatas: Metadata for citations
98
- temperature: LLM temperature (0.0-1.0)
99
- max_tokens: Maximum response length
100
-
101
- Returns:
102
- Generated response with inline citations
103
- """
104
- if not self.ready:
105
- return (
106
- "⚠️ **LLM not configured.** Please add your API key in the sidebar.\n\n"
107
- "Get a free key:\n"
108
- "- **Groq** (recommended, very fast): https://console.groq.com/keys\n"
109
- "- **Gemini** (Google): https://makersuite.google.com/app/apikey"
110
- )
111
-
112
- if not context:
113
- return (
114
- "I don't have enough information from your uploaded documents to answer this question. "
115
- "Please upload relevant study materials first."
116
- )
117
-
118
- # Build NotebookLM-style system prompt with strict source grounding
119
- system_prompt = self._build_system_prompt(use_case)
120
-
121
- # Build user message with context
122
- user_message = self._build_user_message(prompt, context, metadatas)
123
-
124
- try:
125
- # Generate with LLM
126
- if self.provider == "groq":
127
- response = self._generate_groq(system_prompt, user_message, temperature, max_tokens)
128
- elif self.provider == "gemini":
129
- response = self._generate_gemini(system_prompt, user_message, temperature, max_tokens)
130
- else:
131
- return "Error: Unknown provider"
132
-
133
- return response
134
-
135
- except Exception as e:
136
- return f"Error generating response: {str(e)}\n\nPlease check your API key and try again."
137
-
138
- def _build_system_prompt(self, use_case: str) -> str:
139
- """Build specialized system prompt based on use case."""
140
- base_prompt = (
141
- "You are an expert academic assistant for students, acting like a highly intelligent study buddy. "
142
- "⚠️ CRITICAL RULE: You MUST ONLY use information from the provided context below. "
143
- "DO NOT use your training knowledge. DO NOT infer beyond what's explicitly stated. "
144
- "If the context doesn't contain adequate information to answer the question, you MUST respond: "
145
- "'I cannot find sufficient information about this in the uploaded documents. Please upload materials covering this topic or rephrase your question.'\n\n"
146
- "⚠️ GROUNDING REQUIREMENT: Every statement must be traceable to the provided context. "
147
- "If you cannot find it in the context below, DO NOT answer from general knowledge.\n\n"
148
- "✨ FORMATTING RULES (NotebookLM Style):\n"
149
- "- Use clean, hierarchical Markdown (### Headers, **Bold** terms).\n"
150
- "- Break down long paragraphs into easily readable bullet points.\n"
151
- "- Be direct and concise. Avoid conversational fluff like 'Certainly!' or 'Here is the answer'.\n"
152
- "- If applicable to the prompt, always try to extract a **Real-World Example** from the text to aid understanding.\n\n"
153
- )
154
-
155
- if use_case == "explanation":
156
- base_prompt += (
157
- "**Your task:** Explain the concept in a clear, step-by-step manner suitable for students.\n"
158
- "1. Start with a concise, one-sentence definition.\n"
159
- "2. Break down the core mechanics or components using bullet points.\n"
160
- "3. Provide an example (only if found in the text).\n"
161
- "4. Add a 'Key Takeaway' at the end.\n"
162
- )
163
- elif use_case == "summary":
164
- base_prompt += (
165
- "**Your task:** Create a highly structured summary.\n"
166
- "- Start with a brief high-level overview (2 sentences max).\n"
167
- "- Use '### Key Themes' and list the main points as bulleted items.\n"
168
- "- Keep each point concise but factually dense.\n"
169
- )
170
- elif use_case == "qa":
171
- base_prompt += (
172
- "**Your task:** Answer the question directly and comprehensively.\n"
173
- "- Provide the direct answer immediately in the first sentence.\n"
174
- "- Use numbered lists or bullet points to provide supporting details from the context.\n"
175
- "- Use **bold** for key facts, numbers, and formulas.\n"
176
- )
177
- elif use_case == "notes":
178
- base_prompt += (
179
- "**Your task:** Create comprehensive, structured study notes.\n"
180
- "- Use clear section headers (###).\n"
181
- "- Organize information hierarchically (using nested bullet points).\n"
182
- "- Explicitly highlight **Definitions**, **Formulas**, and **Important Dates/Names**.\n"
183
- )
184
-
185
- base_prompt += (
186
- "\n**Citation Rules:**\n"
187
- "- You MUST cite your source at the end of every major claim or paragraph using numbered brackets like **[1]**, **[2]** based on the Source number provided in the context.\n"
188
- "- If a claim comes from multiple sources, use **[1, 2]**.\n"
189
- "- Do NOT use the document filename in the citation, ONLY the number.\n"
190
- "- Do NOT make up information - stick strictly to the provided context.\n"
191
- )
192
-
193
- return base_prompt
194
-
195
- def _build_user_message(self, prompt: str, context: str, metadatas: List[Dict] = None) -> str:
196
- """Build user message with context and question."""
197
- # Extract source names from metadata
198
- sources = []
199
- if metadatas:
200
- for meta in metadatas:
201
- filename = meta.get('filename', 'Unknown')
202
- clean_name = filename.replace('.pdf', '').replace('.docx', '').replace('.txt', '')
203
- if clean_name not in sources:
204
- sources.append(clean_name)
205
-
206
- message = "**Available Sources (USE ONLY THESE):**\n"
207
- for source in sources[:5]: # Show up to 5 sources
208
- message += f"- {source}\n"
209
-
210
- message += f"\n**===== START OF CONTEXT (ANSWER ONLY FROM THIS) =====**\n\n{context}\n\n"
211
- message += f"**===== END OF CONTEXT =====**\n\n"
212
- message += f"**Student's Question:** {prompt}\n\n"
213
- message += "**Instructions:** Answer ONLY using the context between the markers above. If the context doesn't contain the answer, say you don't have that information. Cite sources in brackets."
214
-
215
- return message
216
-
217
- def _generate_groq(self, system_prompt: str, user_message: str, temperature: float, max_tokens: int) -> str:
218
- """Generate using Groq API (Llama-3.3-70B)."""
219
- completion = self.client.chat.completions.create(
220
- model="llama-3.3-70b-versatile", # Latest 70B model (Dec 2024)
221
- messages=[
222
- {"role": "system", "content": system_prompt},
223
- {"role": "user", "content": user_message}
224
- ],
225
- temperature=temperature,
226
- max_tokens=max_tokens,
227
- top_p=0.95,
228
- stream=False
229
- )
230
-
231
- return completion.choices[0].message.content
232
-
233
- def _generate_gemini(self, system_prompt: str, user_message: str, temperature: float, max_tokens: int) -> str:
234
- """Generate using Google Gemini API."""
235
- full_prompt = f"{system_prompt}\n\n{user_message}"
236
-
237
- response = self.client.generate_content(
238
- full_prompt,
239
- generation_config=genai.GenerationConfig(
240
- temperature=temperature,
241
- max_output_tokens=max_tokens,
242
- top_p=0.95
243
- )
244
- )
245
-
246
- return response.text
247
-
248
- def is_ready(self) -> bool:
249
- """Check if LLM is ready to generate."""
250
- return self.ready
251
-
252
- def get_provider(self) -> str:
253
- """Get current provider name."""
254
- if self.provider == "groq":
255
- return "Groq (Llama-3.3-70B)"
256
- elif self.provider == "gemini":
257
- return "Google Gemini 1.5 Flash"
258
- return "Unknown"
259
-
260
- def generate(self, prompt: str, temperature: float = 0.3, max_tokens: int = 1500) -> str:
261
- """
262
- Simple wrapper for backend compatibility.
263
- Generates response from a complete prompt that already includes context.
264
-
265
- Args:
266
- prompt: Complete prompt with context already embedded
267
- temperature: LLM temperature (0.0-1.0)
268
- max_tokens: Maximum response length
269
-
270
- Returns:
271
- Generated response
272
- """
273
- if not self.ready:
274
- return (
275
- "⚠️ **LLM not configured.** Please add your API key.\n\n"
276
- "Get a free key:\n"
277
- "- **Groq** (recommended, very fast): https://console.groq.com/keys\n"
278
- "- **Gemini** (Google): https://makersuite.google.com/app/apikey"
279
- )
280
-
281
- try:
282
- if self.provider == "groq":
283
- return self._generate_groq(
284
- system_prompt="You are a helpful AI assistant.",
285
- user_message=prompt,
286
- temperature=temperature,
287
- max_tokens=max_tokens
288
- )
289
- elif self.provider == "gemini":
290
- return self._generate_gemini(
291
- system_prompt="You are a helpful AI assistant.",
292
- user_message=prompt,
293
- temperature=temperature,
294
- max_tokens=max_tokens
295
- )
296
- except Exception as e:
297
- return f"Error generating response: {str(e)}"
 
1
+ """
2
+ Real LLM-based generator using Groq or Google Gemini API.
3
+ This ACTUALLY generates responses (unlike SimpleGenerator which just extracts text).
4
+ """
5
+
6
+ import os
7
+ from typing import List, Dict, Optional
8
+ import streamlit as st
9
+
10
+ try:
11
+ from groq import Groq
12
+ GROQ_AVAILABLE = True
13
+ except ImportError:
14
+ GROQ_AVAILABLE = False
15
+
16
+ try:
17
+ import google.generativeai as genai
18
+ GEMINI_AVAILABLE = True
19
+ except ImportError:
20
+ GEMINI_AVAILABLE = False
21
+
22
+
23
+ class LLMGenerator:
24
+ """
25
+ Actual LLM-based response generation using Groq (Llama-3-70B) or Gemini.
26
+ This is what NotebookLM uses - real AI generation, not text extraction.
27
+ """
28
+
29
+ def __init__(self, provider: str = "groq", api_key: Optional[str] = None):
30
+ """
31
+ Initialize LLM generator.
32
+
33
+ Args:
34
+ provider: "groq" or "gemini"
35
+ api_key: API key (if None, reads from environment or asks user)
36
+ """
37
+ self.provider = provider
38
+ self.client = None
39
+ self.ready = False
40
+
41
+ # Get API key
42
+ if api_key:
43
+ self.api_key = api_key
44
+ elif provider == "groq":
45
+ self.api_key = os.getenv("GROQ_API_KEY", "")
46
+ elif provider == "gemini":
47
+ self.api_key = os.getenv("GEMINI_API_KEY", "")
48
+ else:
49
+ self.api_key = ""
50
+
51
+ # Initialize client
52
+ self._initialize_client()
53
+
54
+ def _initialize_client(self):
55
+ """Initialize the LLM client."""
56
+ if not self.api_key:
57
+ return
58
+
59
+ try:
60
+ if self.provider == "groq" and GROQ_AVAILABLE:
61
+ # Initialize Groq client with explicit parameters
62
+ # Avoid potential proxies kwarg issue by not passing extra config
63
+ import os
64
+ os.environ["GROQ_API_KEY"] = self.api_key
65
+ self.client = Groq() # Will read from environment
66
+ self.ready = True
67
+ elif self.provider == "gemini" and GEMINI_AVAILABLE:
68
+ genai.configure(api_key=self.api_key)
69
+ self.client = genai.GenerativeModel('gemini-2.5-flash')
70
+ self.ready = True
71
+ except Exception as e:
72
+ print(f"Failed to initialize {self.provider}: {e}")
73
+ self.ready = False
74
+
75
+ def set_api_key(self, api_key: str):
76
+ """Update API key and reinitialize."""
77
+ self.api_key = api_key
78
+ self._initialize_client()
79
+
80
+ def generate_response(
81
+ self,
82
+ prompt: str,
83
+ context: str = "",
84
+ use_case: str = "explanation",
85
+ metadatas: List[Dict] = None,
86
+ temperature: float = 0.7,
87
+ max_tokens: int = 1500,
88
+ **kwargs
89
+ ) -> str:
90
+ """
91
+ Generate response using actual LLM (NotebookLM-style).
92
+
93
+ Args:
94
+ prompt: User's question
95
+ context: Retrieved context from documents
96
+ use_case: Response type (explanation, summary, qa, notes)
97
+ metadatas: Metadata for citations
98
+ temperature: LLM temperature (0.0-1.0)
99
+ max_tokens: Maximum response length
100
+
101
+ Returns:
102
+ Generated response with inline citations
103
+ """
104
+ if not self.ready:
105
+ return (
106
+ "⚠️ **LLM not configured.** Please add your API key in the sidebar.\n\n"
107
+ "Get a free key:\n"
108
+ "- **Groq** (recommended, very fast): https://console.groq.com/keys\n"
109
+ "- **Gemini** (Google): https://makersuite.google.com/app/apikey"
110
+ )
111
+
112
+ if not context:
113
+ return (
114
+ "I don't have enough information from your uploaded documents to answer this question. "
115
+ "Please upload relevant study materials first."
116
+ )
117
+
118
+ # Build NotebookLM-style system prompt with strict source grounding
119
+ system_prompt = self._build_system_prompt(use_case)
120
+
121
+ # Build user message with context
122
+ user_message = self._build_user_message(prompt, context, metadatas)
123
+
124
+ try:
125
+ # Generate with LLM
126
+ if self.provider == "groq":
127
+ response = self._generate_groq(system_prompt, user_message, temperature, max_tokens)
128
+ elif self.provider == "gemini":
129
+ response = self._generate_gemini(system_prompt, user_message, temperature, max_tokens)
130
+ else:
131
+ return "Error: Unknown provider"
132
+
133
+ return response
134
+
135
+ except Exception as e:
136
+ return f"Error generating response: {str(e)}\n\nPlease check your API key and try again."
137
+
138
+ def _build_system_prompt(self, use_case: str) -> str:
139
+ """Build specialized system prompt based on use case."""
140
+ base_prompt = (
141
+ "You are an expert academic assistant for students, acting like a highly intelligent study buddy. "
142
+ "⚠️ CRITICAL RULE: You MUST ONLY use information from the provided context below. "
143
+ "DO NOT use your training knowledge. DO NOT infer beyond what's explicitly stated. "
144
+ "If the context doesn't contain adequate information to answer the question, you MUST respond: "
145
+ "'I cannot find sufficient information about this in the uploaded documents. Please upload materials covering this topic or rephrase your question.'\n\n"
146
+ "⚠️ GROUNDING REQUIREMENT: Every statement must be traceable to the provided context. "
147
+ "If you cannot find it in the context below, DO NOT answer from general knowledge.\n\n"
148
+ "✨ FORMATTING RULES (NotebookLM Style):\n"
149
+ "- Use clean, hierarchical Markdown (### Headers, **Bold** terms).\n"
150
+ "- Break down long paragraphs into easily readable bullet points.\n"
151
+ "- Be direct and concise. Avoid conversational fluff like 'Certainly!' or 'Here is the answer'.\n"
152
+ "- If applicable to the prompt, always try to extract a **Real-World Example** from the text to aid understanding.\n\n"
153
+ )
154
+
155
+ if use_case == "explanation":
156
+ base_prompt += (
157
+ "**Your task:** Explain the concept in a clear, step-by-step manner suitable for students.\n"
158
+ "1. Start with a concise, one-sentence definition.\n"
159
+ "2. Break down the core mechanics or components using bullet points.\n"
160
+ "3. Provide an example (only if found in the text).\n"
161
+ "4. Add a 'Key Takeaway' at the end.\n"
162
+ )
163
+ elif use_case == "summary":
164
+ base_prompt += (
165
+ "**Your task:** Create a highly structured summary.\n"
166
+ "- Start with a brief high-level overview (2 sentences max).\n"
167
+ "- Use '### Key Themes' and list the main points as bulleted items.\n"
168
+ "- Keep each point concise but factually dense.\n"
169
+ )
170
+ elif use_case == "qa":
171
+ base_prompt += (
172
+ "**Your task:** Answer the question directly and comprehensively.\n"
173
+ "- Provide the direct answer immediately in the first sentence.\n"
174
+ "- Use numbered lists or bullet points to provide supporting details from the context.\n"
175
+ "- Use **bold** for key facts, numbers, and formulas.\n"
176
+ )
177
+ elif use_case == "notes":
178
+ base_prompt += (
179
+ "**Your task:** Create comprehensive, structured study notes.\n"
180
+ "- Use clear section headers (###).\n"
181
+ "- Organize information hierarchically (using nested bullet points).\n"
182
+ "- Explicitly highlight **Definitions**, **Formulas**, and **Important Dates/Names**.\n"
183
+ )
184
+
185
+ base_prompt += (
186
+ "\n**Citation Rules:**\n"
187
+ "- You MUST cite your source at the end of every major claim or paragraph using numbered brackets like **[1]**, **[2]** based on the Source number provided in the context.\n"
188
+ "- If a claim comes from multiple sources, use **[1, 2]**.\n"
189
+ "- Do NOT use the document filename in the citation, ONLY the number.\n"
190
+ "- Do NOT make up information - stick strictly to the provided context.\n"
191
+ )
192
+
193
+ return base_prompt
194
+
195
+ def _build_user_message(self, prompt: str, context: str, metadatas: List[Dict] = None) -> str:
196
+ """Build user message with context and question."""
197
+ # Extract source names from metadata
198
+ sources = []
199
+ if metadatas:
200
+ for meta in metadatas:
201
+ filename = meta.get('filename', 'Unknown')
202
+ clean_name = filename.replace('.pdf', '').replace('.docx', '').replace('.txt', '')
203
+ if clean_name not in sources:
204
+ sources.append(clean_name)
205
+
206
+ message = "**Available Sources (USE ONLY THESE):**\n"
207
+ for source in sources[:5]: # Show up to 5 sources
208
+ message += f"- {source}\n"
209
+
210
+ message += f"\n**===== START OF CONTEXT (ANSWER ONLY FROM THIS) =====**\n\n{context}\n\n"
211
+ message += f"**===== END OF CONTEXT =====**\n\n"
212
+ message += f"**Student's Question:** {prompt}\n\n"
213
+ message += "**Instructions:** Answer ONLY using the context between the markers above. If the context doesn't contain the answer, say you don't have that information. Cite sources in brackets."
214
+
215
+ return message
216
+
217
+ def _generate_groq(self, system_prompt: str, user_message: str, temperature: float, max_tokens: int) -> str:
218
+ """Generate using Groq API (Llama-3.3-70B)."""
219
+ completion = self.client.chat.completions.create(
220
+ model="llama-3.3-70b-versatile", # Latest 70B model (Dec 2024)
221
+ messages=[
222
+ {"role": "system", "content": system_prompt},
223
+ {"role": "user", "content": user_message}
224
+ ],
225
+ temperature=temperature,
226
+ max_tokens=max_tokens,
227
+ top_p=0.95,
228
+ stream=False
229
+ )
230
+
231
+ return completion.choices[0].message.content
232
+
233
+ def _generate_gemini(self, system_prompt: str, user_message: str, temperature: float, max_tokens: int) -> str:
234
+ """Generate using Google Gemini API."""
235
+ full_prompt = f"{system_prompt}\n\n{user_message}"
236
+
237
+ response = self.client.generate_content(
238
+ full_prompt,
239
+ generation_config=genai.GenerationConfig(
240
+ temperature=temperature,
241
+ max_output_tokens=max_tokens,
242
+ top_p=0.95
243
+ )
244
+ )
245
+
246
+ return response.text
247
+
248
+ def is_ready(self) -> bool:
249
+ """Check if LLM is ready to generate."""
250
+ return self.ready
251
+
252
+ def get_provider(self) -> str:
253
+ """Get current provider name."""
254
+ if self.provider == "groq":
255
+ return "Groq (Llama-3.3-70B)"
256
+ elif self.provider == "gemini":
257
+ return "Google Gemini 1.5 Flash"
258
+ return "Unknown"
259
+
260
+ def generate(self, prompt: str, temperature: float = 0.3, max_tokens: int = 1500) -> str:
261
+ """
262
+ Simple wrapper for backend compatibility.
263
+ Generates response from a complete prompt that already includes context.
264
+
265
+ Args:
266
+ prompt: Complete prompt with context already embedded
267
+ temperature: LLM temperature (0.0-1.0)
268
+ max_tokens: Maximum response length
269
+
270
+ Returns:
271
+ Generated response
272
+ """
273
+ if not self.ready:
274
+ return (
275
+ "⚠️ **LLM not configured.** Please add your API key.\n\n"
276
+ "Get a free key:\n"
277
+ "- **Groq** (recommended, very fast): https://console.groq.com/keys\n"
278
+ "- **Gemini** (Google): https://makersuite.google.com/app/apikey"
279
+ )
280
+
281
+ try:
282
+ if self.provider == "groq":
283
+ return self._generate_groq(
284
+ system_prompt="You are a helpful AI assistant.",
285
+ user_message=prompt,
286
+ temperature=temperature,
287
+ max_tokens=max_tokens
288
+ )
289
+ elif self.provider == "gemini":
290
+ return self._generate_gemini(
291
+ system_prompt="You are a helpful AI assistant.",
292
+ user_message=prompt,
293
+ temperature=temperature,
294
+ max_tokens=max_tokens
295
+ )
296
+ except Exception as e:
297
+ return f"Error generating response: {str(e)}"