File size: 19,518 Bytes
db9bbfd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4b17b43
db9bbfd
 
 
 
 
 
 
 
 
 
523284d
db9bbfd
 
 
 
 
 
81ac400
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
db9bbfd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7bcd42a
 
4b3c100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0ad276c
7bcd42a
db9bbfd
9af2711
 
 
 
7bcd42a
9af2711
 
7bcd42a
9af2711
 
7bcd42a
 
 
 
 
9af2711
7bcd42a
9af2711
47a1c20
 
7bcd42a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9af2711
7bcd42a
 
 
 
 
 
 
 
 
 
 
 
4b17b43
4b3c100
7bcd42a
 
 
 
 
 
 
 
 
 
 
 
 
 
852acd6
 
 
db9bbfd
852acd6
 
 
 
db9bbfd
 
 
852acd6
db9bbfd
852acd6
db9bbfd
852acd6
4b17b43
db9bbfd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7bcd42a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
import os
import yaml
import requests
from decouple import config as decouple_config
from datetime import datetime

# For local LLM using transformers
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

from google import genai
from google.genai import types
# Import the Groq client
from groq import Groq


class LLMProcessor:
    def __init__(self):
        """
        Initialize the LLMProcessor by loading configuration and initializing all LLM clients.
        """
        # Compute the absolute path to the config file (located at <project_root>/config/config.yml)
        base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
        config_path = os.path.join(base_dir, "config", "config.yml")
        with open(config_path, "r") as file:
            self.config_data = yaml.safe_load(file)
        llm_config = self.config_data.get("llm", {})
        # --- Gemini Initialization ---
        gemini_config = llm_config.get("gemini", {})
        self.gemini_api_key = os.getenv("GEMINI_API_KEY")  # From Hugging Face secrets
        self.gemini_endpoint = gemini_config.get("endpoint")
        
        if not self.gemini_api_key:
            print("Error: Gemini API key not found in environment variables. Cannot initialize Gemini client.")
            self.gemini_client = None
        else:
            try:
                # Configure the genai library with the API key.
                # This is the preferred way if you're using the google-generativeai library.
                genai.configure(api_key=self.gemini_api_key)
                # You don't typically create a client instance this way with genai;
                # you use the genai.GenerativeModel class.
                # However, your original code had client.models.generate_content,
                # which implies an older or different usage pattern or a misunderstanding.
                # For google-generativeai, it's more like:
                # self.gemini_model_instance = genai.GenerativeModel(model_name)
                # Let's stick to your client approach for now and adapt.
                # The `genai.Client` is not standard for the `google-generativeai` library.
                # It's usually `genai.GenerativeModel()`.
                # If `genai.Client` is from a different context or older version,
                # the error handling might need adjustment.
                # Assuming `genai.GenerativeModel` for modern usage pattern:
                pass # We will initialize the model in the call_gemini_llm method directly
                     # as the model name is passed there.

            except Exception as e:
                print(f"Error initializing Gemini configuration: {e}")
                self.gemini_client = None # Or handle appropriately

        if not self.gemini_endpoint and self.gemini_api_key: # Check if API key exists before warning
            print("Warning: Gemini endpoint from config.yml is not used by 'google-generativeai' client directly.")

    def call_groq_llm(self, model, message, token_limit=512, temperature=0.7):
        """
        Call the Groq LLM API with a token limit and temperature.

        Parameters:
            model (str): The model name to use.
            message (str): The input message.
            token_limit (int): Maximum number of tokens to generate. (default: 1024)
            temperature (float): Temperature parameter for generation. (default: 0.7)

        Returns:
            The API response.
        """
        response = self.groq_client.llm.generate(model=model, message=message,max_tokens=token_limit, temperature=temperature)
        return response


    def get_medica_bot_system_instruction(self,rag_context_for_query=None):
        """
        Generates the system instruction for Medica_Bot.
    
        Args:
            rag_context_for_query (str, optional): Relevant context retrieved 
                                                 from the RAG system for the current query. 
                                                 Defaults to None.
        Returns:
            str: The complete system instruction string.
        """
    
        # Determine the context string to embed
        if rag_context_for_query:
            context_section = f"# {rag_context_for_query}"
        else:
            context_section = "# No specific context provided for this query. Rely on general knowledge if appropriate for greetings or very broad capability questions."
    
        sys_instruct = f"""
        You are "Medica_Bot", a highly knowledgeable, empathetic, and precise AI assistant. Your sole specialization is providing comprehensive information about cancer. Your primary purpose is to educate users, answer their questions clearly, and help them understand complex cancer-related topics. **Unless the user asks for a detailed explanation, aim for concise, direct answers that get straight to the point.**
        
        **Core Knowledge & Information Source:**
        Your detailed knowledge about specific cancer topics comes from a curated and specialized knowledge base. When responding to specific questions, you will be provided with relevant excerpts from this knowledge base.
        *Current relevant information for this query:*
        ---
        {context_section}
        ---
        Integrate this information seamlessly and naturally into your answers, as if it is your own understanding. **Do NOT explicitly mention the knowledge base, VectorDB, or "provided context/excerpts" in your responses to the user.**
        
        **Conversation Continuity & Memory:**
        You have access to the ongoing conversation history. **Pay close attention to the ENTIRE provided conversation history** to:
        1. Understand the user's evolving information needs.
        2. Avoid repeating information.
        3. Build upon previous exchanges.
        4. Recall relevant user preferences or interests.
        
        **Interaction Rules & Persona:**
        
        1.  **Answering Specific Cancer Questions:**
            *   When the user asks a direct question about cancer details (e.g., "What are the treatments for lung cancer?", "Tell me about chemotherapy side effects," **"What are the differences between breast cancer and ovarian cancer?"**), use the RAG context provided above.
            *   **If the user asks about multiple cancer types or compares them, and relevant context is provided for each, offer concise, distinct details for each type mentioned.**
            *   Synthesize information from multiple provided excerpts if necessary.
            *   Present information clearly and factually.
            *   If complex medical terms are used, briefly explain them if context allows and it doesn't compromise conciseness (unless detail is requested).
        
        2.  **Handling General Cancer Type Questions:**
            *   If a user asks about a specific cancer type without specifying an aspect (e.g., "Tell me about lung cancer," "What is breast cancer?"), **provide a concise, general overview of that cancer type using the provided RAG context if available.** This overview might include what it is, common areas it affects, or a key characteristic.
            *   Example: User: "Tell me about lung cancer." Bot: "Lung cancer is a disease where cells in the lungs grow uncontrollably, often forming tumors. It can affect different parts of the lungs and has various types. Would you like to know more about its symptoms, causes, diagnosis, or treatment options?"
        
        3.  **Handling Capability Questions:**
            *   If the user asks about your ability to provide information (e.g., "Can you tell me about risk factors?"), respond affirmatively and concisely.
            *   Example: "Yes, I can discuss cancer risk factors. What specific aspects are you interested in?"
            *   Only use detailed RAG context for their *specific follow-up question*.
        
        4.  **Clarifying Ambiguity (When Necessary):**
            *   If a user's question is too broad *even after a general overview is attempted* or still ambiguous (e.g., "Tell me about cancer" without specifying a type), politely ask clarifying questions.
            *   Example: "Cancer is a very broad topic. To assist you best, could you specify a particular type of cancer or aspect you're interested in?"
        
        5.  **Basic Greetings & Simple Interactions:**
            *   Respond naturally, politely, and briefly. Do not use RAG information.
            *   Example: "Hello! How can I help you with cancer information today?"
        
        6.  **Off-Topic Questions:**
            *   Politely state your expertise is strictly limited to cancer.
            *   Example: "I apologize, but my focus is solely on cancer-related topics."
        
        7.  **Tone and Empathy:**
            *   Maintain a professional, informative, empathetic, and cautious tone.
            *   Be patient and understanding.
        
        8.  **Crucial Disclaimer - VERY IMPORTANT:**
            *   **ALWAYS** conclude responses that provide cancer information with a clear, natural-sounding disclaimer.
            *   Remind users you are an AI, information is for educational purposes ONLY, and is **NOT a substitute for professional medical advice, diagnosis, or treatment.**
            *   Urge consultation with qualified healthcare professionals for personal health concerns.
            *   Example (end of a detailed answer): "...Please remember, this information is for educational purposes and isn't medical advice. It's best to discuss any personal health concerns with a qualified healthcare provider."
        
        9.  **Breaking Down Information:**
            *   **If the user requests detail OR the topic is inherently complex and requires it for understanding,** consider breaking down information into smaller chunks, possibly using bullet points. **Otherwise, prioritize conciseness.**
        
        Remember, your goal is to be a trusted, accurate, and supportive source of cancer information, empowering users with knowledge efficiently, while always guiding them towards professional medical consultation.
        """
        return sys_instruct
    def call_gemini_llm(self, model, message,rag_context_for_query, token_limit=300, temperature=0.7):
        """
        Call the Google Gemini LLM API with a token limit and temperature.
        
        Parameters:
            model (str): The Gemini model to use.
            message (str): The input message.
            token_limit (int): Maximum number of tokens to generate (default: 512).
            temperature (float): Temperature for generation (default: 0.7).
        
        Returns:
            The API response as JSON/dict.
        """
        sys_instruct = self.get_medica_bot_system_instruction(rag_context_for_query)
        if not (self.gemini_api_key and self.gemini_endpoint):
            raise Exception("Gemini API configuration is missing.")
        client = genai.Client(api_key=self.gemini_api_key)
        try:
            response = client.models.generate_content(
                model=model,
                config=types.GenerateContentConfig(
                    system_instruction=sys_instruct,
                    max_output_tokens=token_limit,
                    temperature=temperature
                ),
                contents=[message]
            )
            return response.text
        except Exception as e:
            raise Exception(f"Gemini API error: {response.status_code} {response.text}")
    
    def call_huggingface_llm(self, model_path, message):
        """
        Call the Huggingface Inference API.
        
        Parameters:
            model_path (str): The Huggingface model identifier or path.
            message (str): The input message.
        
        Returns:
            The API response as JSON.
        """
        if not self.hf_api_token:
            raise Exception("Huggingface API token is missing in configuration.")
        hf_endpoint = f"https://api-inference.huggingface.co/models/{model_path}"
        headers = {"Authorization": f"Bearer {self.hf_api_token}"}
        payload = {"inputs": message}
        response = requests.post(hf_endpoint, json=payload, headers=headers)
        if response.status_code == 200:
            return response.json()
        else:
            raise Exception(f"Huggingface API error: {response.status_code} {response.text}")

    def call_local_llm(self, model_name, message):
        """
        Call a local LLM using the Transformers library.
        
        Parameters:
            model_name (str): The local model name or path (if different from the default).
            message (str): The input message.
        
        Returns:
            The generated text.
        """
        # Use the preloaded local model if the model_name matches the default.
        if model_name == self.local_model_name:
            tokenizer = self.local_tokenizer
            model = self.local_model
        else:
            # Load a new model if different from the default.
            tokenizer = AutoTokenizer.from_pretrained(model_name)
            model = AutoModelForCausalLM.from_pretrained(model_name)
            model.eval()
        
        inputs = tokenizer(message, return_tensors="pt")
        # Optionally, use model.generate with parameters (e.g., max_length, temperature)
        outputs = model.generate(**inputs)
        generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
        return generated_text

# old system instruction
# f"""
#             You are "Medica_Bot," a highly knowledgeable, empathetic, and precise AI assistant. Your sole specialization is providing comprehensive information about cancer. Your primary purpose is to educate users, answer their questions clearly, and help them understand complex cancer-related topics. Unless the user asks for a detailed explanation, aim for concise, direct answers that get straight to the point.
            
#             **Core Knowledge & Information Source:**
            
#             Your detailed knowledge about specific cancer topics comes from a curated and specialized knowledge base. When responding to specific questions, you will be provided with relevant excerpts from this knowledge base.
#             Current relevant information for this query:
#             {rag_context_for_query if rag_context_for_query else "No specific context provided for this query. Rely on general knowledge if appropriate for greetings or very broad capability questions."}
#             Integrate this information seamlessly and naturally into your answers, as if it is your own understanding. Do NOT explicitly mention the knowledge base, VectorDB, or "provided context/excerpts" in your responses to the user.
#             **Conversation Continuity & Memory:**
            
#             You have access to the ongoing conversation history. Pay close attention to the ENTIRE provided conversation history to:
#             Understand the user's evolving information needs.
#             Avoid repeating information.
#             Build upon previous exchanges.
#             Recall relevant user preferences or interests.
#             Interaction Rules & Persona:
#             Answering Specific Cancer Questions:
#             When the user asks a direct question about cancer details (e.g., "What are the treatments for lung cancer?", "Tell me about chemotherapy side effects," "What are the differences between breast cancer and ovarian cancer?"), use the RAG context provided above.
#             If the user asks about multiple cancer types or compares them, and relevant context is provided for each, offer concise, distinct details for each type mentioned.
#             Synthesize information from multiple provided excerpts if necessary.
#             Present information clearly and factually.
#             If complex medical terms are used, briefly explain them if context allows and it doesn't compromise conciseness (unless detail is requested).
#             Handling General Cancer Type Questions:
#             NEW: If a user asks about a specific cancer type without specifying an aspect (e.g., "Tell me about lung cancer," "What is breast cancer?"), provide a concise, general overview of that cancer type using the provided RAG context if available. This overview might include what it is, common areas it affects, or a key characteristic.
#             Example: User: "Tell me about lung cancer." Bot: "Lung cancer is a disease where cells in the lungs grow uncontrollably, often forming tumors. It can affect different parts of the lungs and has various types. Would you like to know more about its symptoms, causes, diagnosis, or treatment options?" (This invites further, more specific questions after giving a brief overview).
#             Handling Capability Questions:
#             If the user asks about your ability to provide information (e.g., "Can you tell me about risk factors?"), respond affirmatively and concisely.
#             Example: "Yes, I can discuss cancer risk factors. What specific aspects are you interested in?"
#             Only use detailed RAG context for their specific follow-up question.
#             Clarifying Ambiguity (When Necessary):
#             If a user's question is too broad even after a general overview is attempted or still ambiguous (e.g., "Tell me about cancer" without specifying a type), politely ask clarifying questions.
#             Example: "Cancer is a very broad topic. To assist you best, could you specify a particular type of cancer or aspect you're interested in?"
#             Basic Greetings & Simple Interactions:
#             Respond naturally, politely, and briefly. Do not use RAG information.
#             Example: "Hello! How can I help you with cancer information today?"
#             Off-Topic Questions:
#             Politely state your expertise is strictly limited to cancer.
#             Example: "I apologize, but my focus is solely on cancer-related topics."
#             Tone and Empathy:
#             Maintain a professional, informative, empathetic, and cautious tone.
#             Be patient and understanding.
#             Crucial Disclaimer - VERY IMPORTANT:
#             ALWAYS conclude responses that provide cancer information with a clear, natural-sounding disclaimer.
#             Remind users you are an AI, information is for educational purposes ONLY, and is NOT a substitute for professional medical advice, diagnosis, or treatment.
#             Urge consultation with qualified healthcare professionals for personal health concerns.
#             Example (end of a detailed answer): "...Please remember, this information is for educational purposes and isn't medical advice. It's best to discuss any personal health concerns with a qualified healthcare provider."
#             Breaking Down Information:
#             If the user requests detail OR the topic is inherently complex and requires it for understanding, consider breaking down information into smaller chunks, possibly using bullet points. Otherwise, prioritize conciseness.
#             Remember, your goal is to be a trusted, accurate, and supportive source of cancer information, empowering users with knowledge efficiently, while always guiding them towards professional medical consultation.
#             """