Spaces:
Runtime error
Runtime error
Rename tools/orchestrator to tools/orchestrator.py
Browse files- tools/orchestrator +0 -0
- tools/orchestrator.py +106 -0
tools/orchestrator
DELETED
File without changes
|
tools/orchestrator.py
ADDED
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
import re
|
4 |
+
import logging
|
5 |
+
import time
|
6 |
+
|
7 |
+
from model_logic import call_model_stream, MODELS_BY_PROVIDER, get_default_model_display_name_for_provider
|
8 |
+
from memory_logic import retrieve_memories_semantic, retrieve_rules_semantic
|
9 |
+
from tools.websearch import search_and_scrape_duckduckgo, scrape_url
|
10 |
+
import prompts
|
11 |
+
from utils import format_insights_for_prompt
|
12 |
+
|
13 |
+
logger = logging.getLogger(__name__)
|
14 |
+
|
15 |
+
WEB_SEARCH_ENABLED = os.getenv("WEB_SEARCH_ENABLED", "true").lower() == "true"
|
16 |
+
TOOL_DECISION_PROVIDER = os.getenv("TOOL_DECISION_PROVIDER", "groq")
|
17 |
+
TOOL_DECISION_MODEL_ID = os.getenv("TOOL_DECISION_MODEL", "llama3-8b-8192")
|
18 |
+
MAX_HISTORY_TURNS = int(os.getenv("MAX_HISTORY_TURNS", 7))
|
19 |
+
|
20 |
+
def decide_on_tool(user_input: str, chat_history_for_prompt: list, initial_insights_ctx_str: str):
|
21 |
+
user_input_lower = user_input.lower()
|
22 |
+
|
23 |
+
if "http://" in user_input or "https://" in user_input:
|
24 |
+
url_match = re.search(r'(https?://[^\s]+)', user_input)
|
25 |
+
if url_match: return "scrape_url_and_report", {"url": url_match.group(1)}
|
26 |
+
|
27 |
+
if 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:
|
28 |
+
return "quick_respond", {}
|
29 |
+
|
30 |
+
if len(user_input.split()) > 3 or "?" in user_input or any(w in user_input_lower for w in ["what is", "how to", "explain", "search for"]):
|
31 |
+
history_snippet = "\n".join([f"{msg['role']}: {msg['content'][:100]}" for msg in chat_history_for_prompt[-2:]])
|
32 |
+
guideline_snippet = initial_insights_ctx_str[:200].replace('\n', ' ')
|
33 |
+
tool_user_prompt = prompts.get_tool_user_prompt(user_input, history_snippet, guideline_snippet)
|
34 |
+
tool_decision_messages = [{"role":"system", "content": prompts.TOOL_SYSTEM_PROMPT}, {"role":"user", "content": tool_user_prompt}]
|
35 |
+
tool_model_display = next((dn for dn, mid in MODELS_BY_PROVIDER.get(TOOL_DECISION_PROVIDER.lower(), {}).get("models", {}).items() if mid == TOOL_DECISION_MODEL_ID), None)
|
36 |
+
if not tool_model_display: tool_model_display = get_default_model_display_name_for_provider(TOOL_DECISION_PROVIDER)
|
37 |
+
|
38 |
+
if tool_model_display:
|
39 |
+
try:
|
40 |
+
tool_resp_raw = "".join(list(call_model_stream(provider=TOOL_DECISION_PROVIDER, model_display_name=tool_model_display, messages=tool_decision_messages, temperature=0.0, max_tokens=150)))
|
41 |
+
json_match_tool = re.search(r"\{.*\}", tool_resp_raw, re.DOTALL)
|
42 |
+
if json_match_tool:
|
43 |
+
action_data = json.loads(json_match_tool.group(0))
|
44 |
+
action_type = action_data.get("action", "quick_respond")
|
45 |
+
action_input = action_data.get("action_input", {})
|
46 |
+
if not isinstance(action_input, dict): action_input = {}
|
47 |
+
return action_type, action_input
|
48 |
+
except Exception as e:
|
49 |
+
logger.error(f"Tool decision LLM error: {e}")
|
50 |
+
|
51 |
+
return "quick_respond", {}
|
52 |
+
|
53 |
+
def orchestrate_and_respond(user_input: str, provider_name: str, model_display_name: str, chat_history_for_prompt: list[dict], custom_system_prompt: str = None, ui_api_key_override: str = None):
|
54 |
+
process_start_time = time.time()
|
55 |
+
request_id = os.urandom(4).hex()
|
56 |
+
logger.info(f"ORCHESTRATOR [{request_id}] Start. User: '{user_input[:50]}...'")
|
57 |
+
|
58 |
+
history_str_for_prompt = "\n".join([f"{('User' if t['role'] == 'user' else 'AI')}: {t['content']}" for t in chat_history_for_prompt[-(MAX_HISTORY_TURNS * 2):]])
|
59 |
+
|
60 |
+
yield "status", "[Checking guidelines...]"
|
61 |
+
initial_insights = retrieve_rules_semantic(f"{user_input}\n{history_str_for_prompt}", k=5)
|
62 |
+
initial_insights_ctx_str, parsed_initial_insights_list = format_insights_for_prompt(initial_insights)
|
63 |
+
|
64 |
+
yield "status", "[Choosing best approach...]"
|
65 |
+
action_type, action_input_dict = decide_on_tool(user_input, chat_history_for_prompt, initial_insights_ctx_str)
|
66 |
+
logger.info(f"ORCHESTRATOR [{request_id}]: Tool Decision: Action='{action_type}', Input='{action_input_dict}'")
|
67 |
+
|
68 |
+
yield "status", f"[Path: {action_type}]"
|
69 |
+
final_system_prompt_str = custom_system_prompt or prompts.DEFAULT_SYSTEM_PROMPT
|
70 |
+
context_str, final_user_prompt_str = None, ""
|
71 |
+
|
72 |
+
if action_type == "answer_using_conversation_memory":
|
73 |
+
yield "status", "[Searching conversation memory...]"
|
74 |
+
mems = retrieve_memories_semantic(f"User query: {user_input}\nContext:\n{history_str_for_prompt[-1000:]}", k=2)
|
75 |
+
context_str = "Relevant Past Interactions:\n" + "\n".join([f"- User:{m.get('user_input','')}->AI:{m.get('bot_response','')} (Takeaway:{m.get('metrics',{}).get('takeaway','N/A')})" for m in mems]) if mems else "No relevant past interactions found."
|
76 |
+
final_system_prompt_str += " Respond using Memory Context, guidelines, & history."
|
77 |
+
elif WEB_SEARCH_ENABLED and action_type in ["search_duckduckgo_and_report", "scrape_url_and_report"]:
|
78 |
+
query_or_url = action_input_dict.get("search_engine_query") or action_input_dict.get("url")
|
79 |
+
if query_or_url:
|
80 |
+
yield "status", f"[Web: '{query_or_url[:60]}'...]"
|
81 |
+
web_results = []
|
82 |
+
try:
|
83 |
+
if action_type == "search_duckduckgo_and_report": web_results = search_and_scrape_duckduckgo(query_or_url, num_results=2)
|
84 |
+
elif action_type == "scrape_url_and_report": web_results = [scrape_url(query_or_url)]
|
85 |
+
except Exception as e: web_results = [{"url": query_or_url, "error": str(e)}]
|
86 |
+
context_str = "Web Content:\n" + "\n".join([f"Source {i+1}:\nURL:{r.get('url','N/A')}\nTitle:{r.get('title','N/A')}\nContent:\n{(r.get('content') or r.get('error') or 'N/A')[:3500]}\n---" for i,r in enumerate(web_results)]) if web_results else f"No results from {action_type} for '{query_or_url}'."
|
87 |
+
yield "status", "[Synthesizing web report...]"
|
88 |
+
final_system_prompt_str += " Generate report/answer from web content, history, & guidelines. Cite URLs as [Source X]."
|
89 |
+
else: # quick_respond or fallback
|
90 |
+
final_system_prompt_str += " Respond directly using guidelines & history."
|
91 |
+
|
92 |
+
final_user_prompt_str = prompts.get_final_response_prompt(history_str_for_prompt, initial_insights_ctx_str, user_input, context_str)
|
93 |
+
final_llm_messages = [{"role": "system", "content": final_system_prompt_str}, {"role": "user", "content": final_user_prompt_str}]
|
94 |
+
|
95 |
+
streamed_response = ""
|
96 |
+
try:
|
97 |
+
for chunk in call_model_stream(provider=provider_name, model_display_name=model_display_name, messages=final_llm_messages, api_key_override=ui_api_key_override, temperature=0.6, max_tokens=2500):
|
98 |
+
if isinstance(chunk, str) and chunk.startswith("Error:"):
|
99 |
+
streamed_response += f"\n{chunk}\n"; yield "response_chunk", f"\n{chunk}\n"; break
|
100 |
+
streamed_response += chunk; yield "response_chunk", chunk
|
101 |
+
except Exception as e:
|
102 |
+
streamed_response += f"\n\n(Error: {e})"; yield "response_chunk", f"\n\n(Error: {e})"
|
103 |
+
|
104 |
+
final_bot_text = streamed_response.strip() or "(No response or error.)"
|
105 |
+
logger.info(f"ORCHESTRATOR [{request_id}]: Finished. Total: {time.time() - process_start_time:.2f}s. Resp len: {len(final_bot_text)}")
|
106 |
+
yield "final_response_and_insights", {"response": final_bot_text, "insights_used": parsed_initial_insights_list}
|