Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -123,60 +123,42 @@ def process_user_interaction_gradio(user_input: str, provider_name: str, model_d
|
|
123 |
user_input_lower = user_input.lower()
|
124 |
time_before_tool_decision = time.time()
|
125 |
|
126 |
-
# --- REFACTORED TOOL-DECISION LOGIC ---
|
127 |
-
|
128 |
-
# Heuristic for simple interactions that don't need a tool-decision LLM call
|
129 |
is_simple_interaction = len(user_input.split()) <= 3 and any(kw in user_input_lower for kw in ["hello", "hi", "thanks", "ok", "bye"]) and not "?" in user_input
|
130 |
|
131 |
if is_simple_interaction:
|
132 |
action_type = "quick_respond"
|
133 |
else:
|
134 |
-
# For any non-trivial interaction, use an LLM to decide the best tool.
|
135 |
yield "status", "<i>[LLM choosing best approach...]</i>"
|
136 |
-
|
137 |
-
# 1. Define all possible tools and their descriptions
|
138 |
tool_definitions = {
|
139 |
"answer_using_conversation_memory": "Use if the user's query refers to a previous conversation, asks you to 'remember' or 'recall' something specific, or seems like it could be answered by a past interaction you've had. This tool searches a database of your past conversations.",
|
140 |
"search_duckduckgo_and_report": "Use for general knowledge questions, questions about current events, or when the user explicitly asks you to search the web for information.",
|
141 |
"scrape_url_and_report": "Use ONLY when the user provides a specific URL to read from.",
|
142 |
"quick_respond": "Use as a fallback for simple greetings, acknowledgements, or if the answer is obvious from the immediate context and requires no special tools."
|
143 |
}
|
144 |
-
|
145 |
-
# 2. Build the list of available tools for this specific run
|
146 |
available_tool_names = ["quick_respond", "answer_using_conversation_memory"]
|
147 |
if WEB_SEARCH_ENABLED:
|
148 |
-
available_tool_names.insert(1, "search_duckduckgo_and_report")
|
149 |
available_tool_names.insert(2, "scrape_url_and_report")
|
150 |
-
|
151 |
-
# 3. Create the prompt with the dynamic list of tools and their descriptions
|
152 |
tool_descriptions_for_prompt = "\n".join(f'- "{name}": {tool_definitions[name]}' for name in available_tool_names)
|
153 |
-
|
154 |
tool_sys_prompt = "You are a precise routing agent. Your job is to analyze the user's query and the conversation context, then select the single best action to provide an answer. Output ONLY a single, valid JSON object with 'action' and 'action_input' keys. Do not add any other text or explanations."
|
155 |
history_snippet = "\n".join([f"{msg['role']}: {msg['content'][:100]}" for msg in chat_history_for_prompt[-2:]])
|
156 |
guideline_snippet = initial_insights_ctx_str[:200].replace('\n', ' ')
|
157 |
-
|
158 |
tool_user_prompt = f"""User Query: "{user_input}"
|
159 |
|
160 |
Recent History:
|
161 |
{history_snippet}
|
162 |
|
163 |
-
Guidelines Snippet (for context):
|
164 |
-
{guideline_snippet}
|
165 |
-
|
166 |
Available Actions and their descriptions:
|
167 |
{tool_descriptions_for_prompt}
|
168 |
|
169 |
-
Based on the query and the action descriptions, select the single best action
|
170 |
Example for web search: {{"action": "search_duckduckgo_and_report", "action_input": {{"search_engine_query": "latest AI research"}}}}
|
171 |
Example for memory recall: {{"action": "answer_using_conversation_memory", "action_input": {{}}}}
|
172 |
"""
|
173 |
-
|
174 |
tool_decision_messages = [{"role":"system", "content": tool_sys_prompt}, {"role":"user", "content": tool_user_prompt}]
|
175 |
-
|
176 |
tool_provider, tool_model_id = TOOL_DECISION_PROVIDER_ENV, TOOL_DECISION_MODEL_ID_ENV
|
177 |
tool_model_display = next((dn for dn, mid in MODELS_BY_PROVIDER.get(tool_provider.lower(), {}).get("models", {}).items() if mid == tool_model_id), None)
|
178 |
if not tool_model_display: tool_model_display = get_default_model_display_name_for_provider(tool_provider)
|
179 |
-
|
180 |
if tool_model_display:
|
181 |
try:
|
182 |
logger.info(f"PUI_GRADIO [{request_id}]: Tool decision LLM: {tool_provider}/{tool_model_display}")
|
@@ -191,15 +173,13 @@ Example for memory recall: {{"action": "answer_using_conversation_memory", "acti
|
|
191 |
logger.info(f"PUI_GRADIO [{request_id}]: LLM Tool Decision: Action='{action_type}', Input='{action_input_dict}'")
|
192 |
else:
|
193 |
logger.warning(f"PUI_GRADIO [{request_id}]: Tool decision LLM non-JSON. Defaulting to quick_respond. Raw: {tool_resp_raw}")
|
194 |
-
action_type = "quick_respond"
|
195 |
except Exception as e:
|
196 |
logger.error(f"PUI_GRADIO [{request_id}]: Tool decision LLM error. Defaulting to quick_respond: {e}", exc_info=False)
|
197 |
-
action_type = "quick_respond"
|
198 |
else:
|
199 |
logger.error(f"No model for tool decision provider {tool_provider}. Defaulting to quick_respond.")
|
200 |
-
action_type = "quick_respond"
|
201 |
-
|
202 |
-
# --- END OF REFACTORED LOGIC ---
|
203 |
|
204 |
logger.info(f"PUI_GRADIO [{request_id}]: Tool decision logic took {time.time() - time_before_tool_decision:.3f}s. Action: {action_type}, Input: {action_input_dict}")
|
205 |
yield "status", f"<i>[Path: {action_type}. Preparing response...]</i>"
|
@@ -208,17 +188,46 @@ Example for memory recall: {{"action": "answer_using_conversation_memory", "acti
|
|
208 |
if action_type == "quick_respond":
|
209 |
final_system_prompt_str += " Respond directly using guidelines & history."
|
210 |
final_user_prompt_content_str = f"History:\n{history_str_for_prompt}\nGuidelines:\n{initial_insights_ctx_str}\nQuery: \"{user_input}\"\nResponse:"
|
|
|
211 |
elif action_type == "answer_using_conversation_memory":
|
212 |
-
|
213 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
214 |
if retrieved_mems:
|
215 |
logger.info(f"PUI_GRADIO [{request_id}]: Found {len(retrieved_mems)} relevant memories.")
|
216 |
memory_context = "Relevant Past Interactions (for your context only, do not repeat verbatim):\n" + "\n".join([f"- User asked: '{m.get('user_input','')}'. You responded: '{m.get('bot_response','')}'. (Key takeaway: {m.get('metrics',{}).get('takeaway','N/A')})" for m in retrieved_mems])
|
217 |
else:
|
218 |
logger.info(f"PUI_GRADIO [{request_id}]: No relevant memories found for the query.")
|
219 |
memory_context = "No relevant past interactions were found in the memory database."
|
|
|
220 |
final_system_prompt_str += " You MUST use the provided 'Memory Context' to inform your answer. Synthesize the information from the memory with the current conversation history to respond to the user's query."
|
221 |
final_user_prompt_content_str = f"History:\n{history_str_for_prompt}\n\nGuidelines:\n{initial_insights_ctx_str}\n\nMemory Context:\n{memory_context}\n\nUser's Query: \"{user_input}\"\n\nResponse (use the Memory Context to answer the query):"
|
|
|
|
|
222 |
elif action_type in ["search_duckduckgo_and_report", "scrape_url_and_report"]:
|
223 |
query_or_url = action_input_dict.get("search_engine_query") if "search" in action_type else action_input_dict.get("url")
|
224 |
if not query_or_url:
|
|
|
123 |
user_input_lower = user_input.lower()
|
124 |
time_before_tool_decision = time.time()
|
125 |
|
|
|
|
|
|
|
126 |
is_simple_interaction = len(user_input.split()) <= 3 and any(kw in user_input_lower for kw in ["hello", "hi", "thanks", "ok", "bye"]) and not "?" in user_input
|
127 |
|
128 |
if is_simple_interaction:
|
129 |
action_type = "quick_respond"
|
130 |
else:
|
|
|
131 |
yield "status", "<i>[LLM choosing best approach...]</i>"
|
|
|
|
|
132 |
tool_definitions = {
|
133 |
"answer_using_conversation_memory": "Use if the user's query refers to a previous conversation, asks you to 'remember' or 'recall' something specific, or seems like it could be answered by a past interaction you've had. This tool searches a database of your past conversations.",
|
134 |
"search_duckduckgo_and_report": "Use for general knowledge questions, questions about current events, or when the user explicitly asks you to search the web for information.",
|
135 |
"scrape_url_and_report": "Use ONLY when the user provides a specific URL to read from.",
|
136 |
"quick_respond": "Use as a fallback for simple greetings, acknowledgements, or if the answer is obvious from the immediate context and requires no special tools."
|
137 |
}
|
|
|
|
|
138 |
available_tool_names = ["quick_respond", "answer_using_conversation_memory"]
|
139 |
if WEB_SEARCH_ENABLED:
|
140 |
+
available_tool_names.insert(1, "search_duckduckgo_and_report")
|
141 |
available_tool_names.insert(2, "scrape_url_and_report")
|
|
|
|
|
142 |
tool_descriptions_for_prompt = "\n".join(f'- "{name}": {tool_definitions[name]}' for name in available_tool_names)
|
|
|
143 |
tool_sys_prompt = "You are a precise routing agent. Your job is to analyze the user's query and the conversation context, then select the single best action to provide an answer. Output ONLY a single, valid JSON object with 'action' and 'action_input' keys. Do not add any other text or explanations."
|
144 |
history_snippet = "\n".join([f"{msg['role']}: {msg['content'][:100]}" for msg in chat_history_for_prompt[-2:]])
|
145 |
guideline_snippet = initial_insights_ctx_str[:200].replace('\n', ' ')
|
|
|
146 |
tool_user_prompt = f"""User Query: "{user_input}"
|
147 |
|
148 |
Recent History:
|
149 |
{history_snippet}
|
150 |
|
|
|
|
|
|
|
151 |
Available Actions and their descriptions:
|
152 |
{tool_descriptions_for_prompt}
|
153 |
|
154 |
+
Based on the query and the action descriptions, select the single best action. Output the corresponding JSON.
|
155 |
Example for web search: {{"action": "search_duckduckgo_and_report", "action_input": {{"search_engine_query": "latest AI research"}}}}
|
156 |
Example for memory recall: {{"action": "answer_using_conversation_memory", "action_input": {{}}}}
|
157 |
"""
|
|
|
158 |
tool_decision_messages = [{"role":"system", "content": tool_sys_prompt}, {"role":"user", "content": tool_user_prompt}]
|
|
|
159 |
tool_provider, tool_model_id = TOOL_DECISION_PROVIDER_ENV, TOOL_DECISION_MODEL_ID_ENV
|
160 |
tool_model_display = next((dn for dn, mid in MODELS_BY_PROVIDER.get(tool_provider.lower(), {}).get("models", {}).items() if mid == tool_model_id), None)
|
161 |
if not tool_model_display: tool_model_display = get_default_model_display_name_for_provider(tool_provider)
|
|
|
162 |
if tool_model_display:
|
163 |
try:
|
164 |
logger.info(f"PUI_GRADIO [{request_id}]: Tool decision LLM: {tool_provider}/{tool_model_display}")
|
|
|
173 |
logger.info(f"PUI_GRADIO [{request_id}]: LLM Tool Decision: Action='{action_type}', Input='{action_input_dict}'")
|
174 |
else:
|
175 |
logger.warning(f"PUI_GRADIO [{request_id}]: Tool decision LLM non-JSON. Defaulting to quick_respond. Raw: {tool_resp_raw}")
|
176 |
+
action_type = "quick_respond"
|
177 |
except Exception as e:
|
178 |
logger.error(f"PUI_GRADIO [{request_id}]: Tool decision LLM error. Defaulting to quick_respond: {e}", exc_info=False)
|
179 |
+
action_type = "quick_respond"
|
180 |
else:
|
181 |
logger.error(f"No model for tool decision provider {tool_provider}. Defaulting to quick_respond.")
|
182 |
+
action_type = "quick_respond"
|
|
|
|
|
183 |
|
184 |
logger.info(f"PUI_GRADIO [{request_id}]: Tool decision logic took {time.time() - time_before_tool_decision:.3f}s. Action: {action_type}, Input: {action_input_dict}")
|
185 |
yield "status", f"<i>[Path: {action_type}. Preparing response...]</i>"
|
|
|
188 |
if action_type == "quick_respond":
|
189 |
final_system_prompt_str += " Respond directly using guidelines & history."
|
190 |
final_user_prompt_content_str = f"History:\n{history_str_for_prompt}\nGuidelines:\n{initial_insights_ctx_str}\nQuery: \"{user_input}\"\nResponse:"
|
191 |
+
|
192 |
elif action_type == "answer_using_conversation_memory":
|
193 |
+
# --- MODIFIED: Query Transformation Step ---
|
194 |
+
yield "status", "<i>[Optimizing query for long-term memory search...]</i>"
|
195 |
+
|
196 |
+
# 1. Ask an LLM to generate a better search query
|
197 |
+
optimized_query = user_input # Fallback
|
198 |
+
try:
|
199 |
+
query_gen_system_prompt = "You are an expert at reformulating a user's question into a concise, effective search query for a vector database. Given the conversation history and the user's latest query, extract the key topics and entities to create a self-contained query. The query should be a short phrase or question. Output ONLY the query text, nothing else."
|
200 |
+
query_gen_user_prompt = f"Conversation History:\n{history_str_for_prompt}\n\nUser's Latest Query: \"{user_input}\"\n\nOptimized Search Query:"
|
201 |
+
query_gen_messages = [{"role":"system", "content":query_gen_system_prompt}, {"role":"user", "content":query_gen_user_prompt}]
|
202 |
+
|
203 |
+
# Use the same fast model as the tool decider
|
204 |
+
query_gen_chunks = list(call_model_stream(provider=tool_provider, model_display_name=tool_model_display, messages=query_gen_messages, temperature=0.0, max_tokens=50))
|
205 |
+
generated_query = "".join(query_gen_chunks).strip()
|
206 |
+
|
207 |
+
if generated_query:
|
208 |
+
optimized_query = generated_query.replace('"', '') # Clean up quotes
|
209 |
+
logger.info(f"PUI_GRADIO [{request_id}]: Original query: '{user_input}'. Optimized memory search query: '{optimized_query}'")
|
210 |
+
else:
|
211 |
+
logger.warning(f"PUI_GRADIO [{request_id}]: Query generation returned empty. Using original input.")
|
212 |
+
except Exception as e:
|
213 |
+
logger.error(f"PUI_GRADIO [{request_id}]: Error during query generation: {e}. Using original input.")
|
214 |
+
|
215 |
+
# 2. Use the optimized query to search the unified memory
|
216 |
+
yield "status", f"<i>[Searching all memories with query: '{optimized_query[:40]}...']</i>"
|
217 |
+
retrieved_mems = retrieve_memories_semantic(optimized_query, k=3)
|
218 |
+
|
219 |
+
# 3. Build the context for the final response
|
220 |
if retrieved_mems:
|
221 |
logger.info(f"PUI_GRADIO [{request_id}]: Found {len(retrieved_mems)} relevant memories.")
|
222 |
memory_context = "Relevant Past Interactions (for your context only, do not repeat verbatim):\n" + "\n".join([f"- User asked: '{m.get('user_input','')}'. You responded: '{m.get('bot_response','')}'. (Key takeaway: {m.get('metrics',{}).get('takeaway','N/A')})" for m in retrieved_mems])
|
223 |
else:
|
224 |
logger.info(f"PUI_GRADIO [{request_id}]: No relevant memories found for the query.")
|
225 |
memory_context = "No relevant past interactions were found in the memory database."
|
226 |
+
|
227 |
final_system_prompt_str += " You MUST use the provided 'Memory Context' to inform your answer. Synthesize the information from the memory with the current conversation history to respond to the user's query."
|
228 |
final_user_prompt_content_str = f"History:\n{history_str_for_prompt}\n\nGuidelines:\n{initial_insights_ctx_str}\n\nMemory Context:\n{memory_context}\n\nUser's Query: \"{user_input}\"\n\nResponse (use the Memory Context to answer the query):"
|
229 |
+
# --- END OF MODIFICATION ---
|
230 |
+
|
231 |
elif action_type in ["search_duckduckgo_and_report", "scrape_url_and_report"]:
|
232 |
query_or_url = action_input_dict.get("search_engine_query") if "search" in action_type else action_input_dict.get("url")
|
233 |
if not query_or_url:
|