Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -118,66 +118,108 @@ def process_user_interaction_gradio(user_input: str, provider_name: str, model_d
|
|
118 |
initial_insights = retrieve_rules_semantic(f"{user_input}\n{history_str_for_prompt}", k=5)
|
119 |
initial_insights_ctx_str, parsed_initial_insights_list = format_insights_for_prompt(initial_insights)
|
120 |
logger.info(f"PUI_GRADIO [{request_id}]: Initial RAG (insights) found {len(initial_insights)}. Context: {initial_insights_ctx_str[:150]}...")
|
|
|
121 |
action_type, action_input_dict = "quick_respond", {}
|
122 |
user_input_lower = user_input.lower()
|
123 |
time_before_tool_decision = time.time()
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
|
|
|
|
|
|
|
|
|
|
129 |
yield "status", "<i>[LLM choosing best approach...]</i>"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
130 |
history_snippet = "\n".join([f"{msg['role']}: {msg['content'][:100]}" for msg in chat_history_for_prompt[-2:]])
|
131 |
guideline_snippet = initial_insights_ctx_str[:200].replace('\n', ' ')
|
132 |
-
|
133 |
-
# --- MODIFIED: Improved prompts for the tool-decision LLM ---
|
134 |
-
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.
|
135 |
-
Output ONLY a single, valid JSON object with "action" and "action_input" keys. Do not add any other text or explanations.
|
136 |
|
137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
138 |
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
- "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.
|
143 |
-
- "scrape_url_and_report": Use ONLY when the user provides a specific URL to read from.
|
144 |
"""
|
145 |
-
|
146 |
tool_decision_messages = [{"role":"system", "content": tool_sys_prompt}, {"role":"user", "content": tool_user_prompt}]
|
147 |
-
# --- END OF MODIFICATION ---
|
148 |
|
149 |
tool_provider, tool_model_id = TOOL_DECISION_PROVIDER_ENV, TOOL_DECISION_MODEL_ID_ENV
|
150 |
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)
|
151 |
if not tool_model_display: tool_model_display = get_default_model_display_name_for_provider(tool_provider)
|
|
|
152 |
if tool_model_display:
|
153 |
try:
|
154 |
logger.info(f"PUI_GRADIO [{request_id}]: Tool decision LLM: {tool_provider}/{tool_model_display}")
|
155 |
-
tool_resp_chunks = list(call_model_stream(provider=tool_provider, model_display_name=tool_model_display, messages=tool_decision_messages, temperature=0.0, max_tokens=
|
156 |
tool_resp_raw = "".join(tool_resp_chunks).strip()
|
157 |
json_match_tool = re.search(r"\{.*\}", tool_resp_raw, re.DOTALL)
|
158 |
if json_match_tool:
|
159 |
action_data = json.loads(json_match_tool.group(0))
|
160 |
-
action_type
|
|
|
161 |
if not isinstance(action_input_dict, dict): action_input_dict = {}
|
162 |
logger.info(f"PUI_GRADIO [{request_id}]: LLM Tool Decision: Action='{action_type}', Input='{action_input_dict}'")
|
163 |
-
else:
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
168 |
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}")
|
169 |
yield "status", f"<i>[Path: {action_type}. Preparing response...]</i>"
|
170 |
final_system_prompt_str, final_user_prompt_content_str = custom_system_prompt or DEFAULT_SYSTEM_PROMPT, ""
|
|
|
171 |
if action_type == "quick_respond":
|
172 |
final_system_prompt_str += " Respond directly using guidelines & history."
|
173 |
final_user_prompt_content_str = f"History:\n{history_str_for_prompt}\nGuidelines:\n{initial_insights_ctx_str}\nQuery: \"{user_input}\"\nResponse:"
|
174 |
elif action_type == "answer_using_conversation_memory":
|
175 |
yield "status", "<i>[Searching conversation memory (semantic)...]</i>"
|
176 |
-
retrieved_mems = retrieve_memories_semantic(f"User query: {user_input}\nContext:\n{history_str_for_prompt[-1000:]}", k=
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
|
|
|
|
|
|
|
|
|
|
181 |
query_or_url = action_input_dict.get("search_engine_query") if "search" in action_type else action_input_dict.get("url")
|
182 |
if not query_or_url:
|
183 |
final_system_prompt_str += " Respond directly (web action failed: no input)."
|
@@ -195,9 +237,10 @@ Here are the available actions with descriptions of when to use them:
|
|
195 |
yield "status", "<i>[Synthesizing web report...]</i>"
|
196 |
final_system_prompt_str += " Generate report/answer from web content, history, & guidelines. Cite URLs as [Source X]."
|
197 |
final_user_prompt_content_str = f"History:\n{history_str_for_prompt}\nGuidelines:\n{initial_insights_ctx_str}\nWeb Content:\n{scraped_content}\nQuery: \"{user_input}\"\nReport/Response (cite sources [Source X]):"
|
198 |
-
else:
|
199 |
final_system_prompt_str += " Respond directly (unknown action path)."
|
200 |
final_user_prompt_content_str = f"History:\n{history_str_for_prompt}\nGuidelines:\n{initial_insights_ctx_str}\nQuery: \"{user_input}\"\nResponse:"
|
|
|
201 |
final_llm_messages = [{"role": "system", "content": final_system_prompt_str}, {"role": "user", "content": final_user_prompt_content_str}]
|
202 |
logger.debug(f"PUI_GRADIO [{request_id}]: Final LLM System Prompt: {final_system_prompt_str[:200]}...")
|
203 |
logger.debug(f"PUI_GRADIO [{request_id}]: Final LLM User Prompt Start: {final_user_prompt_content_str[:200]}...")
|
@@ -212,6 +255,7 @@ Here are the available actions with descriptions of when to use them:
|
|
212 |
logger.info(f"PUI_GRADIO [{request_id}]: Finished. Total: {time.time() - process_start_time:.2f}s. Resp len: {len(final_bot_text)}")
|
213 |
yield "final_response_and_insights", {"response": final_bot_text, "insights_used": parsed_initial_insights_list}
|
214 |
|
|
|
215 |
def perform_post_interaction_learning(user_input: str, bot_response: str, provider: str, model_disp_name: str, insights_reflected: list[dict], api_key_override: str = None):
|
216 |
task_id = os.urandom(4).hex()
|
217 |
logger.info(f"POST_INTERACTION_LEARNING [{task_id}]: START User='{user_input[:40]}...', Bot='{bot_response[:40]}...'")
|
|
|
118 |
initial_insights = retrieve_rules_semantic(f"{user_input}\n{history_str_for_prompt}", k=5)
|
119 |
initial_insights_ctx_str, parsed_initial_insights_list = format_insights_for_prompt(initial_insights)
|
120 |
logger.info(f"PUI_GRADIO [{request_id}]: Initial RAG (insights) found {len(initial_insights)}. Context: {initial_insights_ctx_str[:150]}...")
|
121 |
+
|
122 |
action_type, action_input_dict = "quick_respond", {}
|
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") # Give search higher priority
|
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 to take. Output the corresponding JSON object.
|
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}")
|
183 |
+
tool_resp_chunks = list(call_model_stream(provider=tool_provider, model_display_name=tool_model_display, messages=tool_decision_messages, temperature=0.0, max_tokens=200))
|
184 |
tool_resp_raw = "".join(tool_resp_chunks).strip()
|
185 |
json_match_tool = re.search(r"\{.*\}", tool_resp_raw, re.DOTALL)
|
186 |
if json_match_tool:
|
187 |
action_data = json.loads(json_match_tool.group(0))
|
188 |
+
action_type = action_data.get("action", "quick_respond")
|
189 |
+
action_input_dict = action_data.get("action_input", {})
|
190 |
if not isinstance(action_input_dict, dict): action_input_dict = {}
|
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" # Fallback
|
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" # Fallback
|
198 |
+
else:
|
199 |
+
logger.error(f"No model for tool decision provider {tool_provider}. Defaulting to quick_respond.")
|
200 |
+
action_type = "quick_respond" # Fallback
|
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>"
|
206 |
final_system_prompt_str, final_user_prompt_content_str = custom_system_prompt or DEFAULT_SYSTEM_PROMPT, ""
|
207 |
+
|
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 |
yield "status", "<i>[Searching conversation memory (semantic)...]</i>"
|
213 |
+
retrieved_mems = retrieve_memories_semantic(f"User query: {user_input}\nContext:\n{history_str_for_prompt[-1000:]}", k=3)
|
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:
|
225 |
final_system_prompt_str += " Respond directly (web action failed: no input)."
|
|
|
237 |
yield "status", "<i>[Synthesizing web report...]</i>"
|
238 |
final_system_prompt_str += " Generate report/answer from web content, history, & guidelines. Cite URLs as [Source X]."
|
239 |
final_user_prompt_content_str = f"History:\n{history_str_for_prompt}\nGuidelines:\n{initial_insights_ctx_str}\nWeb Content:\n{scraped_content}\nQuery: \"{user_input}\"\nReport/Response (cite sources [Source X]):"
|
240 |
+
else: # Fallback for unknown action
|
241 |
final_system_prompt_str += " Respond directly (unknown action path)."
|
242 |
final_user_prompt_content_str = f"History:\n{history_str_for_prompt}\nGuidelines:\n{initial_insights_ctx_str}\nQuery: \"{user_input}\"\nResponse:"
|
243 |
+
|
244 |
final_llm_messages = [{"role": "system", "content": final_system_prompt_str}, {"role": "user", "content": final_user_prompt_content_str}]
|
245 |
logger.debug(f"PUI_GRADIO [{request_id}]: Final LLM System Prompt: {final_system_prompt_str[:200]}...")
|
246 |
logger.debug(f"PUI_GRADIO [{request_id}]: Final LLM User Prompt Start: {final_user_prompt_content_str[:200]}...")
|
|
|
255 |
logger.info(f"PUI_GRADIO [{request_id}]: Finished. Total: {time.time() - process_start_time:.2f}s. Resp len: {len(final_bot_text)}")
|
256 |
yield "final_response_and_insights", {"response": final_bot_text, "insights_used": parsed_initial_insights_list}
|
257 |
|
258 |
+
# The rest of the app.py file remains the same...
|
259 |
def perform_post_interaction_learning(user_input: str, bot_response: str, provider: str, model_disp_name: str, insights_reflected: list[dict], api_key_override: str = None):
|
260 |
task_id = os.urandom(4).hex()
|
261 |
logger.info(f"POST_INTERACTION_LEARNING [{task_id}]: START User='{user_input[:40]}...', Bot='{bot_response[:40]}...'")
|