Spaces:
Running
Running
Update chatbot_handler.py
Browse files- chatbot_handler.py +48 -130
chatbot_handler.py
CHANGED
@@ -2,165 +2,83 @@
|
|
2 |
import logging
|
3 |
import json
|
4 |
from google import genai
|
5 |
-
import os
|
6 |
-
|
7 |
-
#
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
# Option 2: Fallback to environment variable if direct empty key doesn't work with the library via Canvas.
|
14 |
-
# if not GEMINI_API_KEY: # This check would be if we explicitly want to load from env
|
15 |
-
# GEMINI_API_KEY = os.getenv('GEMINI_API_KEY_ENV_VAR_NAME') # Replace with your actual env var name if you use one
|
16 |
-
# if not GEMINI_API_KEY:
|
17 |
-
# logging.warning("GEMINI_API_KEY not found via direct assignment or environment variable.")
|
18 |
-
# If you have a default key for local testing (NOT FOR PRODUCTION/CANVAS)
|
19 |
-
# GEMINI_API_KEY = "YOUR_LOCAL_DEV_API_KEY"
|
20 |
-
|
21 |
-
model = None
|
22 |
-
gen_config = None
|
23 |
safety_settings = []
|
24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
try:
|
26 |
-
if GEMINI_API_KEY
|
27 |
-
genai.
|
28 |
-
|
29 |
-
#
|
30 |
-
MODEL_NAME = "gemini-2.0-flash"
|
31 |
-
model = genai.GenerativeModel(MODEL_NAME)
|
32 |
-
|
33 |
-
gen_config = genai.types.GenerationConfig(
|
34 |
-
temperature=0.7,
|
35 |
-
top_k=1, # Per user's original config
|
36 |
-
top_p=1, # Per user's original config
|
37 |
-
max_output_tokens=2048, # Per user's original config
|
38 |
-
)
|
39 |
-
|
40 |
-
# Standard safety settings
|
41 |
safety_settings = [
|
42 |
{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
43 |
{"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
44 |
{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
45 |
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
46 |
]
|
47 |
-
logging.info(f"Gemini model '{MODEL_NAME}' configured successfully.")
|
48 |
-
else:
|
49 |
-
logging.error("Gemini API Key is None. Model not configured.")
|
50 |
|
|
|
|
|
|
|
51 |
except Exception as e:
|
52 |
-
logging.error(f"Failed to
|
53 |
-
|
54 |
|
55 |
def format_history_for_gemini(gradio_chat_history: list) -> list:
|
56 |
-
"""
|
57 |
-
Converts Gradio chat history (list of dicts with 'role' and 'content')
|
58 |
-
to Gemini API's 'contents' format (list of dicts with 'role' and 'parts').
|
59 |
-
Gemini SDK expects roles 'user' and 'model'.
|
60 |
-
"""
|
61 |
gemini_contents = []
|
62 |
for msg in gradio_chat_history:
|
63 |
-
# Map Gradio 'assistant' role to Gemini 'model' role
|
64 |
role = "user" if msg["role"] == "user" else "model"
|
65 |
-
|
66 |
content = msg.get("content")
|
67 |
if isinstance(content, str):
|
68 |
gemini_contents.append({"role": role, "parts": [{"text": content}]})
|
69 |
else:
|
70 |
-
logging.warning(f"Skipping non-string content in chat history
|
71 |
return gemini_contents
|
72 |
|
|
|
73 |
async def generate_llm_response(user_message: str, plot_id: str, plot_label: str, chat_history_for_plot: list, plot_data_summary: str = None):
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
plot_id (str): The ID of the plot being discussed.
|
79 |
-
plot_label (str): The label of the plot being discussed.
|
80 |
-
chat_history_for_plot (list): The current conversation history for this plot.
|
81 |
-
This list ALREADY includes the initial assistant message (with data summary)
|
82 |
-
and the latest user_message.
|
83 |
-
plot_data_summary (str, optional): The textual summary of the plot data.
|
84 |
-
While it's in the history, passing it explicitly might be useful
|
85 |
-
for future system prompt enhancements if needed.
|
86 |
-
Returns:
|
87 |
-
str: The LLM's response text.
|
88 |
-
"""
|
89 |
-
logging.info(f"Generating LLM response for plot_id: {plot_id} ('{plot_label}'). User message: '{user_message}'")
|
90 |
-
# Log the provided data summary for debugging
|
91 |
-
# logging.debug(f"Data summary for '{plot_label}':\n{plot_data_summary}")
|
92 |
-
|
93 |
-
|
94 |
-
if not model:
|
95 |
-
logging.error("Gemini model not configured. Cannot generate LLM response.")
|
96 |
-
return "I'm sorry, the AI model is not available at the moment. (Configuration Error)"
|
97 |
-
|
98 |
-
# The chat_history_for_plot already contains the initial assistant message with the summary,
|
99 |
-
# and the latest user message which triggered this call.
|
100 |
gemini_formatted_history = format_history_for_gemini(chat_history_for_plot)
|
101 |
|
102 |
if not gemini_formatted_history:
|
103 |
-
logging.error("
|
104 |
-
return "
|
105 |
-
|
106 |
-
# Optional: Construct a system instruction if desired, though the initial message in history helps.
|
107 |
-
# system_instruction_text = (
|
108 |
-
# f"You are an expert in Employer Branding and LinkedIn social media strategy. "
|
109 |
-
# f"You are discussing the graph: '{plot_label}' (ID: '{plot_id}'). "
|
110 |
-
# f"A data summary for this graph was provided in your initial message: \n---\n{plot_data_summary}\n---\n"
|
111 |
-
# f"Refer to this summary and the conversation history to answer questions. "
|
112 |
-
# f"If specific data is not in the summary, clearly state that the provided snapshot doesn't contain that detail."
|
113 |
-
# )
|
114 |
-
# contents_for_api = [{"role": "system", "parts": [{"text": system_instruction_text}]}] + gemini_formatted_history
|
115 |
-
# For now, relying on the summary being in the `gemini_formatted_history` via the first assistant message.
|
116 |
|
117 |
try:
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
safety_settings=safety_settings
|
124 |
)
|
125 |
-
|
126 |
-
# logging.debug(f"LLM API Raw Response object for '{plot_label}': {response}")
|
127 |
|
128 |
-
# Check for blocking based on prompt_feedback first (as per SDK examples)
|
129 |
if response.prompt_feedback and response.prompt_feedback.block_reason:
|
130 |
-
reason = response.prompt_feedback.block_reason.name
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
return response_text
|
141 |
-
else:
|
142 |
-
# This case might occur if the response was empty but not blocked by prompt_feedback
|
143 |
-
# (e.g. finish_reason other than SAFETY, or no candidates)
|
144 |
-
finish_reason_str = "UNKNOWN"
|
145 |
-
if response.candidates and response.candidates[0].finish_reason:
|
146 |
-
finish_reason_str = response.candidates[0].finish_reason.name # e.g. 'STOP', 'MAX_TOKENS', 'SAFETY', 'RECITATION', 'OTHER'
|
147 |
-
|
148 |
-
if finish_reason_str == 'SAFETY': # Content blocked at candidate level
|
149 |
-
logging.warning(f"Content blocked by API (candidate safety) for '{plot_label}'. Finish Reason: {finish_reason_str}.")
|
150 |
-
return f"I'm sorry, I can't provide a response due to safety filters regarding: {finish_reason_str}."
|
151 |
-
|
152 |
-
logging.error(f"Unexpected LLM API response structure or empty content for '{plot_label}'. Finish Reason: {finish_reason_str}. Full response: {response}")
|
153 |
-
return f"Sorry, I received an unexpected or empty response from the AI model (Finish Reason: {finish_reason_str})."
|
154 |
-
|
155 |
-
except google.api_core.exceptions.PermissionDenied as e:
|
156 |
-
logging.error(f"LLM API Permission Denied (Status 403) for '{plot_label}': {e}", exc_info=True)
|
157 |
-
return "Sorry, there's an issue with API permissions. Please ensure the API key is correct and the service is enabled. (Error 403)"
|
158 |
-
except google.api_core.exceptions.InvalidArgument as e:
|
159 |
-
logging.error(f"LLM API Invalid Argument (Status 400) for '{plot_label}': {e}. History: {json.dumps(gemini_formatted_history, indent=2)}", exc_info=True)
|
160 |
-
return "Sorry, there was an issue with the request sent to the AI model (e.g. malformed history). (Error 400)"
|
161 |
-
except google.api_core.exceptions.GoogleAPIError as e: # Catch other Google API errors
|
162 |
-
logging.error(f"Google API Error during LLM call for '{plot_label}': {e}", exc_info=True)
|
163 |
-
return f"An API error occurred while trying to get an AI response: {type(e).__name__}."
|
164 |
except Exception as e:
|
165 |
-
logging.error(f"
|
166 |
-
return f"
|
|
|
2 |
import logging
|
3 |
import json
|
4 |
from google import genai
|
5 |
+
import os
|
6 |
+
|
7 |
+
# Gemini API key configuration
|
8 |
+
GEMINI_API_KEY = os.getenv('GEMINI_API_KEY', '')
|
9 |
+
|
10 |
+
client = None
|
11 |
+
model_name = "gemini-2.0-flash"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
safety_settings = []
|
13 |
|
14 |
+
generation_config = genai.types.GenerationConfig(
|
15 |
+
temperature=0.7,
|
16 |
+
top_k=1,
|
17 |
+
top_p=1,
|
18 |
+
max_output_tokens=2048,
|
19 |
+
)
|
20 |
+
|
21 |
try:
|
22 |
+
if GEMINI_API_KEY:
|
23 |
+
client = genai.Client(api_key=GEMINI_API_KEY)
|
24 |
+
|
25 |
+
# Optional: safety settings
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
safety_settings = [
|
27 |
{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
28 |
{"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
29 |
{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
30 |
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_MEDIUM_AND_ABOVE"},
|
31 |
]
|
|
|
|
|
|
|
32 |
|
33 |
+
logging.info(f"Gemini client initialized with model '{model_name}'")
|
34 |
+
else:
|
35 |
+
logging.error("Gemini API Key is not set.")
|
36 |
except Exception as e:
|
37 |
+
logging.error(f"Failed to initialize Gemini client: {e}", exc_info=True)
|
38 |
+
|
39 |
|
40 |
def format_history_for_gemini(gradio_chat_history: list) -> list:
|
|
|
|
|
|
|
|
|
|
|
41 |
gemini_contents = []
|
42 |
for msg in gradio_chat_history:
|
|
|
43 |
role = "user" if msg["role"] == "user" else "model"
|
|
|
44 |
content = msg.get("content")
|
45 |
if isinstance(content, str):
|
46 |
gemini_contents.append({"role": role, "parts": [{"text": content}]})
|
47 |
else:
|
48 |
+
logging.warning(f"Skipping non-string content in chat history: {content}")
|
49 |
return gemini_contents
|
50 |
|
51 |
+
|
52 |
async def generate_llm_response(user_message: str, plot_id: str, plot_label: str, chat_history_for_plot: list, plot_data_summary: str = None):
|
53 |
+
if not client:
|
54 |
+
logging.error("Gemini client not initialized.")
|
55 |
+
return "The AI model is not available. Configuration error."
|
56 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
gemini_formatted_history = format_history_for_gemini(chat_history_for_plot)
|
58 |
|
59 |
if not gemini_formatted_history:
|
60 |
+
logging.error("Empty or invalid chat history.")
|
61 |
+
return "There was an issue processing the conversation history."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
|
63 |
try:
|
64 |
+
response = await client.models.generate_content_async(
|
65 |
+
model=model_name,
|
66 |
+
contents=gemini_formatted_history,
|
67 |
+
generation_config=generation_config,
|
68 |
+
safety_settings=safety_settings,
|
|
|
69 |
)
|
|
|
|
|
70 |
|
|
|
71 |
if response.prompt_feedback and response.prompt_feedback.block_reason:
|
72 |
+
reason = response.prompt_feedback.block_reason.name
|
73 |
+
logging.warning(f"Blocked by prompt feedback: {reason}")
|
74 |
+
return f"Blocked due to content policy: {reason}."
|
75 |
+
|
76 |
+
if response.candidates and response.candidates[0].content.parts:
|
77 |
+
return "".join(part.text for part in response.candidates[0].content.parts)
|
78 |
+
|
79 |
+
finish_reason = response.candidates[0].finish_reason.name if response.candidates and response.candidates[0].finish_reason else "UNKNOWN"
|
80 |
+
return f"Unexpected response. Finish reason: {finish_reason}."
|
81 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
except Exception as e:
|
83 |
+
logging.error(f"Error generating response for plot '{plot_label}': {e}", exc_info=True)
|
84 |
+
return f"Unexpected error occurred: {type(e).__name__}."
|