Spaces:
Sleeping
Sleeping
File size: 6,781 Bytes
5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 62494ee 1bbe15b 62494ee 1bbe15b 62494ee 5b6e847 62494ee 5b6e847 62494ee 1bbe15b 62494ee 1bbe15b 62494ee 5b6e847 62494ee 5b6e847 62494ee 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 1bbe15b 5b6e847 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | """
Response Generation Module for VDHF
Handles LLM-based response generation using retrieved context.
"""
import os
from typing import List, Optional
from config.settings import (
GROQ_API_KEY,
LLM_MODEL,
MAX_TOKENS,
TEMPERATURE,
INITIAL_GENERATION_PROMPT
)
from retrieval.retriever import RetrievedEvidence
class ResponseGenerator:
"""
Response Generation Module
Purpose:
- Generate initial response using retrieved context
- Support Groq Cloud API
- Provide fallback for testing without API
"""
def __init__(
self,
model: str = LLM_MODEL,
api_key: Optional[str] = None,
max_tokens: int = MAX_TOKENS,
temperature: float = TEMPERATURE
):
self.model = model
self.api_key = api_key or GROQ_API_KEY
self.max_tokens = max_tokens
self.temperature = temperature
self._client = None
# Initialize Groq client if API key is available
if self.api_key:
try:
from groq import Groq
self._client = Groq(api_key=self.api_key)
except ImportError:
print("Warning: groq package not installed. Using mock generation.")
def generate(
self,
query: str,
context: str,
prompt_template: Optional[str] = None
) -> str:
"""
Generate a response using the LLM.
Args:
query: User query
context: Retrieved context/evidence
prompt_template: Custom prompt template (uses default if not provided)
Returns:
Generated response string
"""
template = prompt_template or INITIAL_GENERATION_PROMPT
# Format prompt
prompt = template.format(
context=context,
question=query
)
# Use Groq if available, otherwise mock
if self._client:
return self._generate_groq(prompt)
else:
return self._generate_mock(query, context)
def _generate_groq(self, prompt: str) -> str:
"""Generate using Groq API."""
try:
response = self._client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": "You are a helpful assistant that provides accurate, factual answers based on the given context."},
{"role": "user", "content": prompt}
],
max_tokens=self.max_tokens,
temperature=self.temperature
)
return response.choices[0].message.content.strip()
except Exception as e:
print(f"Groq API error: {e}")
return self._generate_mock_from_prompt(prompt)
def _generate_mock(self, query: str, context: str) -> str:
"""Generate a mock response for testing without API."""
stop_words = {'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been',
'what', 'how', 'who', 'which', 'where', 'when', 'why', 'do',
'does', 'did', 'to', 'of', 'in', 'for', 'on', 'with', 'at',
'by', 'from', 'and', 'or', 'but', 'if', 'it', 'this', 'that'}
query_words = set(query.lower().split()) - stop_words
# Split into sentences and score by meaningful word overlap
sentences = [s.strip() for s in context.split('.') if len(s.strip()) > 20]
scored = []
for sentence in sentences:
sentence_words = set(sentence.lower().split()) - stop_words
overlap = query_words & sentence_words
if overlap:
scored.append((len(overlap), sentence))
# Sort by relevance (most overlapping words first)
scored.sort(key=lambda x: x[0], reverse=True)
if scored:
best = [s for _, s in scored[:4]]
response = ". ".join(best)
if not response.endswith('.'):
response += '.'
return response
elif context:
return context[:500].rsplit('.', 1)[0] + '.'
else:
return "No relevant information found in the uploaded documents."
def _generate_mock_from_prompt(self, prompt: str) -> str:
"""Extract a simple response from the prompt context."""
# Find context section
if "Context:" in prompt:
start = prompt.find("Context:") + len("Context:")
end = prompt.find("Question:")
if end > start:
context = prompt[start:end].strip()
return self._generate_mock("", context)
return "Unable to generate response from the provided context."
def generate_with_evidence(
self,
query: str,
evidence_list: List[RetrievedEvidence],
prompt_template: Optional[str] = None
) -> str:
"""
Generate a response using evidence list.
Args:
query: User query
evidence_list: List of RetrievedEvidence objects
prompt_template: Custom prompt template
Returns:
Generated response string
"""
# Build context string from evidence
context_parts = []
for ev in evidence_list:
context_parts.append(ev.content)
context = "\n\n---\n\n".join(context_parts)
return self.generate(query, context, prompt_template)
def regenerate_with_refinement(
self,
query: str,
verified_evidence: str,
prompt_template: str
) -> str:
"""
Regenerate response using refined prompt.
Args:
query: Original user query
verified_evidence: Only verified evidence
prompt_template: Refined prompt template
Returns:
Regenerated response
"""
prompt = prompt_template.format(
question=query,
evidence=verified_evidence
)
if self._client:
return self._generate_groq(prompt)
else:
return self._generate_mock(query, verified_evidence)
class GenerationResult:
"""Container for generation results with metadata."""
def __init__(
self,
response: str,
query: str,
context: str,
is_regenerated: bool = False,
attempt_number: int = 1
):
self.response = response
self.query = query
self.context = context
self.is_regenerated = is_regenerated
self.attempt_number = attempt_number
def __str__(self) -> str:
status = "Regenerated" if self.is_regenerated else "Initial"
return f"[{status} Response - Attempt {self.attempt_number}]\n{self.response}"
|