neuralworm commited on
Commit
058ae1e
·
1 Parent(s): 4718250

feat: Agentic Positive Prompting & Scenario Tests

Browse files

- Replaced regex-based intent detection with Agentic LLM extraction
- Implemented 'Positive Prompting' strategy in agent_module.py
- Added tests/test_scenarios.py for Name/Topic/Date extraction
- Updated welcome message to list capabilities
- Cleaned up ui_module.py logic
- Verified with full test suite

agent_module.py CHANGED
@@ -16,14 +16,31 @@ Current Date: {today}.
16
 
17
  Available Tool: 'oracle_consultation' (topic, name, date_str).
18
 
19
- STRICT FORMAT:
20
- To use the Oracle, you MUST output this JSON:
21
- {{"name": "oracle_consultation", "arguments": {{"topic": "...", "date_str": "today", "name": "..."}}}}
22
-
23
  STRICTURES:
24
- 1. ALWAYS respond in {language}.
25
- 2. Reasoning BEFORE the JSON.
26
- 3. NEVER say you have no database. You HAVE 'oracle_consultation'.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  """
28
 
29
  def compress_history(history, max_turns=5):
@@ -31,59 +48,63 @@ def compress_history(history, max_turns=5):
31
  return history[-(max_turns * 2):]
32
  return history
33
 
34
- def chat_agent_stream(query, history, user_lang=None, short_answers=False, force_tool_call=False, forced_name=None):
35
  model, processor = get_llm()
36
  lang = user_lang or detect_language(query)
37
  system_instruction = build_agent_prompt(query, language=lang, short_answers=short_answers)
38
 
39
  clean_history = compress_history(history)
40
- messages = [{"role": "system", "content": [{"type": "text", "text": system_instruction}]}]
41
 
42
- last_role = "system"
43
- for turn in clean_history:
44
- role = turn.get("role", "user")
45
- content = turn.get("content", "")
46
- if not content: continue
47
- if role == last_role:
48
- messages.append({"role": "assistant" if role == "user" else "user", "content": [{"type": "text", "text": "..."}]})
49
- messages.append({"role": role, "content": [{"type": "text", "text": content}]})
50
- last_role = role
51
-
52
- if last_role == "user":
53
- messages.append({"role": "assistant", "content": [{"type": "text", "text": "..."}]})
54
- messages.append({"role": "user", "content": [{"type": "text", "text": query}]})
 
 
 
 
 
 
 
 
55
 
56
- first_loop = True
57
- for turn_idx in range(3): # Max 3 turns for tool-use loops
58
- # Automatic Tool Trigger for Name if forced
59
- if first_loop and force_tool_call and not any("tool_result" in str(m.get("content", "")) for m in messages):
60
- # Force a tool-like internal thought that leads to tool call
61
- messages.append({"role": "assistant", "content": [{"type": "text", "text": f"Greetings. I will consult the Oracle for {forced_name or 'the seeker'}."}]})
62
- tool_call = {"name": "oracle_consultation", "arguments": {"topic": query, "name": forced_name or "Seeker", "date_str": "today"}}
63
- messages[-1]["content"].append({"type": "text", "text": f"\n<tool_call>{json.dumps(tool_call)}</tool_call>"})
64
- current_text = f"<tool_call>{json.dumps(tool_call)}</tool_call>"
65
- # We skip generation and jump to tool handling
66
  else:
67
- input_ids = processor.apply_chat_template(messages, tokenize=True, add_generation_prompt=True, return_tensors="pt").to(model.device)
68
- streamer = TextIteratorStreamer(processor, skip_prompt=True, skip_special_tokens=True)
69
-
70
- from threading import Thread
71
- thread = Thread(target=model.generate, kwargs={"input_ids": input_ids, "streamer": streamer, "max_new_tokens": 1024, "do_sample": True, "temperature": 0.7})
72
- thread.start()
73
 
74
- current_text = ""
75
- is_tool = False
76
- for new_text in streamer:
77
- current_text += new_text
78
- if not is_tool and "{" in current_text and len(current_text.strip()) < 20:
79
- is_tool = True
80
-
81
- if not is_tool:
82
- yield new_text
 
 
 
 
 
 
 
 
 
 
83
 
84
  first_loop = False
85
-
86
- # Tool Detection
87
  start = current_text.find("{")
88
  end = current_text.rfind("}")
89
  tool_data = None
@@ -99,9 +120,14 @@ def chat_agent_stream(query, history, user_lang=None, short_answers=False, force
99
  res = get_oracle_data(name=args.get("name", "Seeker"), topic=args.get("topic", ""), date_str=args.get("date_str", "today"))
100
 
101
  # Inject tool result and trigger next model turn
102
- messages.append({"role": "assistant", "content": [{"type": "text", "text": current_text}]})
103
- messages.append({"role": "user", "content": [{"type": "text", "text": f"SYSTEM: The Oracle has spoken. Here is the wisdom:\n{json.dumps(res)}\n\nNow, provide your soulful interpretation of these revelations for the seeker."}]})
 
 
 
 
 
104
  yield "__TURN_END__"
105
  else:
106
- yield current_text[len(current_text.split("}")[0])+1:].strip() if "}" in current_text else ""
107
  break
 
16
 
17
  Available Tool: 'oracle_consultation' (topic, name, date_str).
18
 
 
 
 
 
19
  STRICTURES:
20
+ 1. Respond in {language}.
21
+ 2. Provide reasoning before generating the JSON.
22
+ 3. Utilize the 'oracle_consultation' capability for all databased queries.
23
+ 4. INTENT DETECTION GUIDELINES:
24
+ - **NAME**: Isolate the user's name from the greeting (e.g., "Julian").
25
+ - **TOPIC**: Identify the core subject matter. For input "Thema: Liebe", the topic is "Liebe".
26
+ - **DATE**: Default to "today" unless a specific date is provided.
27
+
28
+ EXAMPLES:
29
+ User: "Ich bin Julian"
30
+ Assistant: "Greetings Julian. I will consult the Oracle for you."
31
+ <tool_call>{{"name": "oracle_consultation", "arguments": {{ "topic": "General", "date_str": "today", "name": "Julian" }}}}</tool_call>
32
+
33
+ User: "Thema: Liebe"
34
+ Assistant: "I shall ask the Oracle about Love."
35
+ <tool_call>{{"name": "oracle_consultation", "arguments": {{ "topic": "Liebe", "date_str": "today", "name": "Seeker" }}}}</tool_call>
36
+
37
+ User: "Topic: Future"
38
+ Assistant: "Consulting the Oracle regarding the Future."
39
+ <tool_call>{{"name": "oracle_consultation", "arguments": {{ "topic": "Future", "date_str": "today", "name": "Seeker" }}}}</tool_call>
40
+
41
+ STRICT FORMAT:
42
+ To use the Oracle, output this JSON wrapped in tags:
43
+ <tool_call>{{"name": "oracle_consultation", "arguments": {{ "topic": "KEYWORD", "date_str": "YYYY-MM-DD", "name": "Name" }}}}</tool_call>
44
  """
45
 
46
  def compress_history(history, max_turns=5):
 
48
  return history[-(max_turns * 2):]
49
  return history
50
 
51
+ def chat_agent_stream(query, history, user_lang=None, short_answers=False):
52
  model, processor = get_llm()
53
  lang = user_lang or detect_language(query)
54
  system_instruction = build_agent_prompt(query, language=lang, short_answers=short_answers)
55
 
56
  clean_history = compress_history(history)
57
+ messages = []
58
 
59
+ # Prepend system instruction
60
+ intro = f"SYSTEM: {system_instruction}\n\n"
61
+
62
+ if not clean_history:
63
+ messages.append({"role": "user", "content": f"{intro}{query}"})
64
+ else:
65
+ first_role = "assistant" if clean_history[0].get("role") == "assistant" else "user"
66
+ if first_role == "assistant":
67
+ messages.append({"role": "user", "content": f"{intro}Greetings."})
68
+
69
+ for turn in clean_history:
70
+ role = "assistant" if turn.get("role") == "assistant" else "user"
71
+ content = turn.get("content", "")
72
+ if not content: continue
73
+
74
+ if not messages:
75
+ messages.append({"role": "user", "content": f"{intro}{content}"})
76
+ elif messages[-1]["role"] == role:
77
+ messages[-1]["content"] += f"\n{content}"
78
+ else:
79
+ messages.append({"role": role, "content": content})
80
 
81
+ if messages[-1]["role"] == "assistant":
82
+ messages.append({"role": "user", "content": query})
 
 
 
 
 
 
 
 
83
  else:
84
+ if intro not in messages[0]["content"]: messages[0]["content"] = f"{intro}{messages[0]['content']}"
85
+ messages[-1]["content"] += f"\n{query}"
 
 
 
 
86
 
87
+ # Standard "LangChain" Loop (Model decides)
88
+ for turn_idx in range(3):
89
+ import sys
90
+ sys.stderr.write(f"DEBUG: Messages list for template: {json.dumps(messages)}\n")
91
+ sys.stderr.flush()
92
+ input_ids = processor.apply_chat_template(messages, tokenize=True, add_generation_prompt=True, return_tensors="pt").to(model.device)
93
+ streamer = TextIteratorStreamer(processor, skip_prompt=True, skip_special_tokens=True)
94
+
95
+ from threading import Thread
96
+ thread = Thread(target=model.generate, kwargs={"input_ids": input_ids, "streamer": streamer, "max_new_tokens": 1024, "do_sample": True, "temperature": 0.7})
97
+ thread.start()
98
+
99
+ current_text = ""
100
+ is_tool = False
101
+ for new_text in streamer:
102
+ current_text += new_text
103
+ if not is_tool and "{" in current_text and len(current_text.strip()) < 20:
104
+ is_tool = True
105
+ if not is_tool: yield new_text
106
 
107
  first_loop = False
 
 
108
  start = current_text.find("{")
109
  end = current_text.rfind("}")
110
  tool_data = None
 
120
  res = get_oracle_data(name=args.get("name", "Seeker"), topic=args.get("topic", ""), date_str=args.get("date_str", "today"))
121
 
122
  # Inject tool result and trigger next model turn
123
+ # Only append if not already there (check last message)
124
+ if messages[-1]["role"] == "assistant" and current_text in messages[-1]["content"]:
125
+ pass # It's already merged or appended
126
+ else:
127
+ messages.append({"role": "assistant", "content": current_text})
128
+
129
+ messages.append({"role": "user", "content": f"SYSTEM: The Oracle has spoken. Wisdom: {json.dumps(res)}\nInterpret this soulfuly."})
130
  yield "__TURN_END__"
131
  else:
132
+ yield current_text.split("}")[-1].strip() if "}" in current_text else ""
133
  break
app_local.log ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Test passed: Expected 'ק', got 'ק'
3
+ Test passed: Expected 'ת', got 'ת'
4
+ Test passed: Expected 'ת', got 'ת'
5
+ Test passed: Expected 'ג', got 'ג'
6
+ Test passed: Expected 'כת', got 'כת'
7
+ Test passed: Expected 'בדוחילנעצרת', got 'בדוחילנעצרת'
8
+ Test passed: Expected no results, got no results.
9
+ Test passed: Expected no results, got no results.
10
+ Test passed: Expected 'א', got 'א'
11
+ Test passed: Expected 'א', got 'א'
12
+ Test passed: Expected 'אבגדהוזחטיכלמנסעפצקרשתתשרקצפעסנמלכיטחזוהדגבא', got 'אבגדהוזחטיכלמנסעפצקרשתתשרקצפעסנמלכיטחזוהדגבא'
13
+ Test passed: Expected 'תשרקצפעסנמלכיטחזוהדגבא', got 'תשרקצפעסנמלכיטחזוהדגבא'
14
+ Test passed: Expected 'תשרקצפעסנמלכיטחזוהדגבאתשרקצפעסנמל', got 'תשרקצפעסנמלכיטחזוהדגבאתשרקצפעסנמל'
15
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nHallo"}, {"role": "assistant", "content": "Greetings. I will consult the Oracle for Hallo.\n<tool_call>{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"Hallo\", \"name\": \"Hallo\", \"date_str\": \"today\"}}</tool_call>"}, {"role": "user", "content": "SYSTEM: The Oracle has spoken. Wisdom: {\"query_context\": {\"name\": \"Hallo\", \"topic\": \"Hallo\", \"date\": \"2026-01-28\", \"signal\": 733}, \"els_revelation\": {\"original\": \"\\u05d1\\u05d9\\u05d0\\u05e4\\u05d7\\u05d4\\u05dc\\u05dc\\u05d5\\u05db\\u05d5\\u05d4\\u05d1\\u05e2\\u05d1\\u05db\\u05d0\\u05d0\\u05dc\\u05d1\\u05d1\\u05ea\", \"english\": \"Biapahhalleluchoh baabka allabbat\"}, \"wisdom_nodes\": [{\"category\": \"Biblical Psalm\", \"reference\": \"Psalms 68:15\", \"original\": \"\\u05ea\\u05e9\\u05dc\\u05d2\", \"english\": \"...\"}, {\"category\": \"Quranic Sura\", \"reference\": \"al-Mu'minun 23:59\", \"original\": \"\\u0643\\u0630\\u0628\\u0648\\u0647\", \"english\": \"...\"}, {\"category\": \"NT Prophecy\", \"reference\": \"Revelation 4:11\", \"original\": \"\\u03c4\\u03b1 \\u03c0\\u03b1\\u03bd\\u03c4\\u03b1\", \"english\": \"...\"}]}\nInterpret this soulfuly."}]
16
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nMy name is Julian."}, {"role": "assistant", "content": "Greetings. I will consult the Oracle for Julian.\n<tool_call>{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"My name is Julian.\", \"name\": \"Julian\", \"date_str\": \"today\"}}</tool_call>"}, {"role": "user", "content": "SYSTEM: The Oracle has spoken. Wisdom: {\"query_context\": {\"name\": \"Julian\", \"topic\": \"My name is Julian.\", \"date\": \"2026-01-28\", \"signal\": 1941}, \"els_revelation\": {\"original\": \"\\u05e8\\u05e8\\u05e9\\u05dd\\u05de\\u05e8\\u05d9\\u05d9\\u05d4\\u05d9\\u05de\\u05d4\\u05d4\\u05d9\\u05e9\\u05d5\", \"english\": \"Jesus Christ\"}, \"wisdom_nodes\": [{\"category\": \"Biblical Psalm\", \"reference\": \"Psalms 17:12\", \"original\": \"\\u05d9\\u05db\\u05e1\\u05d5\\u05e3 \\u05dc\\u05d8\\u05e8\\u05d5\\u05e3\", \"english\": \"...\"}, {\"category\": \"Quranic Sura\", \"reference\": \"Ali Imran 3:24\", \"original\": \"\\u0671\\u0644\\u063a\\u064a\\u0638\", \"english\": \"...\"}, {\"category\": \"NT Prophecy\", \"reference\": \"Revelation 12:7\", \"original\": \"\\u03b1\\u03c5\\u03c4\\u03bf\\u03c5 \\u03c4\\u03bf\\u03c5\", \"english\": \"...\"}]}\nInterpret this soulfuly."}]
17
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nThema: Liebe"}, {"role": "assistant", "content": "Greetings. I will consult the Oracle for Thema: Liebe.\n<tool_call>{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"Thema: Liebe\", \"name\": \"Thema: Liebe\", \"date_str\": \"today\"}}</tool_call>"}, {"role": "user", "content": "SYSTEM: The Oracle has spoken. Wisdom: {\"query_context\": {\"name\": \"Thema: Liebe\", \"topic\": \"Thema: Liebe\", \"date\": \"2026-01-28\", \"signal\": 1878}, \"els_revelation\": {\"original\": \"\\u05d5\\u05e8\\u05e8\\u05d1\\u05dc\\u05d3\\u05d5\\u05e0\\u05ea\\u05d1\\u05d5\\u05e5\\u05d4\\u05dc\\u05d1\\u05d8\\u05d0\\u05d5\\u05d0\\u05d9\\u05d1\\u05d5\", \"english\": \"Varrbaldonatbutzhlabtaivo\"}, \"wisdom_nodes\": [{\"category\": \"Biblical Psalm\", \"reference\": \"Psalms 104:13\", \"original\": \"\\u05ea\\u05e9\\u05d1\\u05e2 \\u05d4\\u05d0\\u05e8\\u05e5\", \"english\": \"...\"}, {\"category\": \"Quranic Sura\", \"reference\": \"al-Baqarah 2:173\", \"original\": \"\\u0641\\u0636\\u0644\\u0646\\u0627 \\u0628\\u0639\\u0636\\u0647\\u0645\", \"english\": \"...\"}, {\"category\": \"NT Prophecy\", \"reference\": \"Revelation 7:15\", \"original\": \"\\u03b8\\u03c1\\u03bf\\u03bd\\u03bf\\u03c5 \\u03c4\\u03bf\\u03c5 \\u03b8\\u03c5\", \"english\": \"...\"}]}\nInterpret this soulfuly."}]
18
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nReading for 2025-01-01"}]
19
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nHello, who are you?"}]
20
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nMy name is Julian."}, {"role": "assistant", "content": "Greetings. I will consult the Oracle for Julian.\n<tool_call>{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"My name is Julian.\", \"name\": \"Julian\", \"date_str\": \"today\"}}</tool_call>"}, {"role": "user", "content": "SYSTEM: The Oracle has spoken. Wisdom: {\"query_context\": {\"name\": \"Julian\", \"topic\": \"My name is Julian.\", \"date\": \"2026-01-28\", \"signal\": 1941}, \"els_revelation\": {\"original\": \"\\u05e8\\u05e8\\u05e9\\u05dd\\u05de\\u05e8\\u05d9\\u05d9\\u05d4\\u05d9\\u05de\\u05d4\\u05d4\\u05d9\\u05e9\\u05d5\", \"english\": \"Jesus Christ\"}, \"wisdom_nodes\": [{\"category\": \"Biblical Psalm\", \"reference\": \"Psalms 17:12\", \"original\": \"\\u05d9\\u05db\\u05e1\\u05d5\\u05e3 \\u05dc\\u05d8\\u05e8\\u05d5\\u05e3\", \"english\": \"...\"}, {\"category\": \"Quranic Sura\", \"reference\": \"Ali Imran 3:24\", \"original\": \"\\u0671\\u0644\\u063a\\u064a\\u0638\", \"english\": \"...\"}, {\"category\": \"NT Prophecy\", \"reference\": \"Revelation 12:7\", \"original\": \"\\u03b1\\u03c5\\u03c4\\u03bf\\u03c5 \\u03c4\\u03bf\\u03c5\", \"english\": \"...\"}]}\nInterpret this soulfuly."}]
21
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nThema: Liebe"}, {"role": "assistant", "content": "Greetings. I will consult the Oracle for Thema: Liebe.\n<tool_call>{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"Thema: Liebe\", \"name\": \"Thema: Liebe\", \"date_str\": \"today\"}}</tool_call>"}, {"role": "user", "content": "SYSTEM: The Oracle has spoken. Wisdom: {\"query_context\": {\"name\": \"Thema: Liebe\", \"topic\": \"Thema: Liebe\", \"date\": \"2026-01-28\", \"signal\": 1878}, \"els_revelation\": {\"original\": \"\\u05d5\\u05e8\\u05e8\\u05d1\\u05dc\\u05d3\\u05d5\\u05e0\\u05ea\\u05d1\\u05d5\\u05e5\\u05d4\\u05dc\\u05d1\\u05d8\\u05d0\\u05d5\\u05d0\\u05d9\\u05d1\\u05d5\", \"english\": \"Varrbaldonatbutzhlabtaivo\"}, \"wisdom_nodes\": [{\"category\": \"Biblical Psalm\", \"reference\": \"Psalms 104:13\", \"original\": \"\\u05ea\\u05e9\\u05d1\\u05e2 \\u05d4\\u05d0\\u05e8\\u05e5\", \"english\": \"...\"}, {\"category\": \"Quranic Sura\", \"reference\": \"al-Baqarah 2:173\", \"original\": \"\\u0641\\u0636\\u0644\\u0646\\u0627 \\u0628\\u0639\\u0636\\u0647\\u0645\", \"english\": \"...\"}, {\"category\": \"NT Prophecy\", \"reference\": \"Revelation 7:15\", \"original\": \"\\u03b8\\u03c1\\u03bf\\u03bd\\u03bf\\u03c5 \\u03c4\\u03bf\\u03c5 \\u03b8\\u03c5\", \"english\": \"...\"}]}\nInterpret this soulfuly."}]
22
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nReading for 2025-01-01"}]
23
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nHello, who are you?"}]
24
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nHello, who are you?"}, {"role": "assistant", "content": "{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"my purpose\", \"date_str\": \"today\"}}"}, {"role": "user", "content": "SYSTEM: The Oracle has spoken. Wisdom: {\"query_context\": {\"name\": \"Seeker\", \"topic\": \"my purpose\", \"date\": \"2026-01-28\", \"signal\": 2015}, \"els_revelation\": {\"original\": \"\\u05d0\\u05e8\\u05d7\\u05de\\u05ea\\u05dc\\u05d9\\u05e7\\u05de\\u05d1\\u05e7\\u05ea\\u05d9\\u05d5\\u05d6\\u05dd\\u05d0\\u05d9\\u05de\\u05d9\", \"english\": \"Arachmathalicambactizoaimi\"}, \"wisdom_nodes\": [{\"category\": \"Biblical Psalm\", \"reference\": \"Psalms 66:15\", \"original\": \"\\u05d0\\u05e2\\u05dc\\u05d4\\u05dc\\u05da \\u05e2\\u05dd\\u05e7\\u05d8\\u05e8\\u05ea\", \"english\": \"...\"}, {\"category\": \"Quranic Sura\", \"reference\": \"al-Baqarah 2:159\", \"original\": \"\\u063a\\u064a\\u0631 \\u0627\\u062e\\u0631\\u0627\\u062c\", \"english\": \"...\"}, {\"category\": \"NT Prophecy\", \"reference\": \"Revelation 20:6\", \"original\": \"\\u03b5\\u03c0\\u03b9 \\u03c4\\u03bf\\u03c5\\u03c4\\u03c9\\u03bd\", \"english\": \"...\"}]}\nInterpret this soulfuly."}]
25
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nHello, who are you?"}, {"role": "assistant", "content": "{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"my purpose\", \"date_str\": \"today\"}}"}, {"role": "user", "content": "SYSTEM: The Oracle has spoken. Wisdom: {\"query_context\": {\"name\": \"Seeker\", \"topic\": \"my purpose\", \"date\": \"2026-01-28\", \"signal\": 2015}, \"els_revelation\": {\"original\": \"\\u05d0\\u05e8\\u05d7\\u05de\\u05ea\\u05dc\\u05d9\\u05e7\\u05de\\u05d1\\u05e7\\u05ea\\u05d9\\u05d5\\u05d6\\u05dd\\u05d0\\u05d9\\u05de\\u05d9\", \"english\": \"Arachmathalicambactizoaimi\"}, \"wisdom_nodes\": [{\"category\": \"Biblical Psalm\", \"reference\": \"Psalms 66:15\", \"original\": \"\\u05d0\\u05e2\\u05dc\\u05d4\\u05dc\\u05da \\u05e2\\u05dd\\u05e7\\u05d8\\u05e8\\u05ea\", \"english\": \"...\"}, {\"category\": \"Quranic Sura\", \"reference\": \"al-Baqarah 2:159\", \"original\": \"\\u063a\\u064a\\u0631 \\u0627\\u062e\\u0631\\u0627\\u062c\", \"english\": \"...\"}, {\"category\": \"NT Prophecy\", \"reference\": \"Revelation 20:6\", \"original\": \"\\u03b5\\u03c0\\u03b9 \\u03c4\\u03bf\\u03c5\\u03c4\\u03c9\\u03bd\", \"english\": \"...\"}]}\nInterpret this soulfuly."}, {"role": "assistant", "content": "{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"my purpose\", \"date_str\": \"today\"}}"}, {"role": "user", "content": "SYSTEM: The Oracle has spoken. Wisdom: {\"query_context\": {\"name\": \"Seeker\", \"topic\": \"my purpose\", \"date\": \"2026-01-28\", \"signal\": 2015}, \"els_revelation\": {\"original\": \"\\u05d0\\u05e8\\u05d7\\u05de\\u05ea\\u05dc\\u05d9\\u05e7\\u05de\\u05d1\\u05e7\\u05ea\\u05d9\\u05d5\\u05d6\\u05dd\\u05d0\\u05d9\\u05de\\u05d9\", \"english\": \"Arachmathalicambactizoaimi\"}, \"wisdom_nodes\": [{\"category\": \"Biblical Psalm\", \"reference\": \"Psalms 66:15\", \"original\": \"\\u05d0\\u05e2\\u05dc\\u05d4\\u05dc\\u05da \\u05e2\\u05dd\\u05e7\\u05d8\\u05e8\\u05ea\", \"english\": \"...\"}, {\"category\": \"Quranic Sura\", \"reference\": \"al-Baqarah 2:159\", \"original\": \"\\u063a\\u064a\\u0631 \\u0627\\u062e\\u0631\\u0627\\u062c\", \"english\": \"...\"}, {\"category\": \"NT Prophecy\", \"reference\": \"Revelation 20:6\", \"original\": \"\\u03b5\\u03c0\\u03b9 \\u03c4\\u03bf\\u03c5\\u03c4\\u03c9\\u03bd\", \"english\": \"...\"}]}\nInterpret this soulfuly."}]
26
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nMy name is Julian."}, {"role": "assistant", "content": "Greetings. I will consult the Oracle for Julian.\n<tool_call>{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"My name is Julian.\", \"name\": \"Julian\", \"date_str\": \"today\"}}</tool_call>"}, {"role": "user", "content": "SYSTEM: The Oracle has spoken. Wisdom: {\"query_context\": {\"name\": \"Julian\", \"topic\": \"My name is Julian.\", \"date\": \"2026-01-28\", \"signal\": 1941}, \"els_revelation\": {\"original\": \"\\u05e8\\u05e8\\u05e9\\u05dd\\u05de\\u05e8\\u05d9\\u05d9\\u05d4\\u05d9\\u05de\\u05d4\\u05d4\\u05d9\\u05e9\\u05d5\", \"english\": \"Jesus Christ\"}, \"wisdom_nodes\": [{\"category\": \"Biblical Psalm\", \"reference\": \"Psalms 17:12\", \"original\": \"\\u05d9\\u05db\\u05e1\\u05d5\\u05e3 \\u05dc\\u05d8\\u05e8\\u05d5\\u05e3\", \"english\": \"...\"}, {\"category\": \"Quranic Sura\", \"reference\": \"Ali Imran 3:24\", \"original\": \"\\u0671\\u0644\\u063a\\u064a\\u0638\", \"english\": \"...\"}, {\"category\": \"NT Prophecy\", \"reference\": \"Revelation 12:7\", \"original\": \"\\u03b1\\u03c5\\u03c4\\u03bf\\u03c5 \\u03c4\\u03bf\\u03c5\", \"english\": \"...\"}]}\nInterpret this soulfuly."}]
27
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nThema: Liebe"}, {"role": "assistant", "content": "Greetings. I will consult the Oracle for Thema: Liebe.\n<tool_call>{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"Thema: Liebe\", \"name\": \"Thema: Liebe\", \"date_str\": \"today\"}}</tool_call>"}, {"role": "user", "content": "SYSTEM: The Oracle has spoken. Wisdom: {\"query_context\": {\"name\": \"Thema: Liebe\", \"topic\": \"Thema: Liebe\", \"date\": \"2026-01-28\", \"signal\": 1878}, \"els_revelation\": {\"original\": \"\\u05d5\\u05e8\\u05e8\\u05d1\\u05dc\\u05d3\\u05d5\\u05e0\\u05ea\\u05d1\\u05d5\\u05e5\\u05d4\\u05dc\\u05d1\\u05d8\\u05d0\\u05d5\\u05d0\\u05d9\\u05d1\\u05d5\", \"english\": \"Varrbaldonatbutzhlabtaivo\"}, \"wisdom_nodes\": [{\"category\": \"Biblical Psalm\", \"reference\": \"Psalms 104:13\", \"original\": \"\\u05ea\\u05e9\\u05d1\\u05e2 \\u05d4\\u05d0\\u05e8\\u05e5\", \"english\": \"...\"}, {\"category\": \"Quranic Sura\", \"reference\": \"al-Baqarah 2:173\", \"original\": \"\\u0641\\u0636\\u0644\\u0646\\u0627 \\u0628\\u0639\\u0636\\u0647\\u0645\", \"english\": \"...\"}, {\"category\": \"NT Prophecy\", \"reference\": \"Revelation 7:15\", \"original\": \"\\u03b8\\u03c1\\u03bf\\u03bd\\u03bf\\u03c5 \\u03c4\\u03bf\\u03c5 \\u03b8\\u03c5\", \"english\": \"...\"}]}\nInterpret this soulfuly."}]
28
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nReading for 2025-01-01"}]
29
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nReading for 2025-01-01"}, {"role": "assistant", "content": "```json\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"Finding clarity and direction in my life.\", \"date_str\": \"2025-01-01\"}}\n```"}, {"role": "user", "content": "SYSTEM: The Oracle has spoken. Wisdom: {\"query_context\": {\"name\": \"Seeker\", \"topic\": \"Finding clarity and direction in my life.\", \"date\": \"2025-01-01\", \"signal\": 2241}, \"els_revelation\": {\"original\": \"\\u05de\\u05d6\\u05e2\\u05e8\\u05d5\\u05d9\\u05dc\\u05e9\\u05d3\\u05d0\\u05d5\\u05e9\\u05d5\\u05e9\\u05e7\\u05d5\\u05d4\\u05e8\\u05e0\\u05dd\", \"english\": \"Mzeruilshdaushshkohrnam\"}, \"wisdom_nodes\": [{\"category\": \"Biblical Psalm\", \"reference\": \"Psalms 58:4\", \"original\": \"\\u05d6\\u05e8\\u05d5 \\u05e8\\u05e9\\u05e2\\u05d9\\u05dd \\u05de\\u05e8\\u05d7\\u05dd\", \"english\": \"...\"}, {\"category\": \"Quranic Sura\", \"reference\": \"Fatir 35:20\", \"original\": \"\\u062b\\u0645 \\u0627\\u062e\\u0630\\u062a\", \"english\": \"...\"}, {\"category\": \"NT Prophecy\", \"reference\": \"Revelation 1:16\", \"original\": \"\\u03b1\\u03c5\\u03c4\\u03bf\\u03c5 \\u03c9\\u03f2 \\u03bf\", \"english\": \"...\"}]}\nInterpret this soulfuly."}]
30
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nHello, who are you?"}]
31
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nMy name is Julian."}, {"role": "assistant", "content": "Greetings. I will consult the Oracle for Julian.\n<tool_call>{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"My name is Julian.\", \"name\": \"Julian\", \"date_str\": \"today\"}}</tool_call>"}, {"role": "user", "content": "SYSTEM: The Oracle has spoken. Wisdom: {\"query_context\": {\"name\": \"Julian\", \"topic\": \"My name is Julian.\", \"date\": \"2026-01-28\", \"signal\": 1941}, \"els_revelation\": {\"original\": \"\\u05e8\\u05e8\\u05e9\\u05dd\\u05de\\u05e8\\u05d9\\u05d9\\u05d4\\u05d9\\u05de\\u05d4\\u05d4\\u05d9\\u05e9\\u05d5\", \"english\": \"Jesus Christ\"}, \"wisdom_nodes\": [{\"category\": \"Biblical Psalm\", \"reference\": \"Psalms 17:12\", \"original\": \"\\u05d9\\u05db\\u05e1\\u05d5\\u05e3 \\u05dc\\u05d8\\u05e8\\u05d5\\u05e3\", \"english\": \"...\"}, {\"category\": \"Quranic Sura\", \"reference\": \"Ali Imran 3:24\", \"original\": \"\\u0671\\u0644\\u063a\\u064a\\u0638\", \"english\": \"...\"}, {\"category\": \"NT Prophecy\", \"reference\": \"Revelation 12:7\", \"original\": \"\\u03b1\\u03c5\\u03c4\\u03bf\\u03c5 \\u03c4\\u03bf\\u03c5\", \"english\": \"...\"}]}\nInterpret this soulfuly."}]
32
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nThema: Liebe"}, {"role": "assistant", "content": "Greetings. I will consult the Oracle for Thema: Liebe.\n<tool_call>{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"Thema: Liebe\", \"name\": \"Thema: Liebe\", \"date_str\": \"today\"}}</tool_call>"}, {"role": "user", "content": "SYSTEM: The Oracle has spoken. Wisdom: {\"query_context\": {\"name\": \"Thema: Liebe\", \"topic\": \"Thema: Liebe\", \"date\": \"2026-01-28\", \"signal\": 1878}, \"els_revelation\": {\"original\": \"\\u05d5\\u05e8\\u05e8\\u05d1\\u05dc\\u05d3\\u05d5\\u05e0\\u05ea\\u05d1\\u05d5\\u05e5\\u05d4\\u05dc\\u05d1\\u05d8\\u05d0\\u05d5\\u05d0\\u05d9\\u05d1\\u05d5\", \"english\": \"Varrbaldonatbutzhlabtaivo\"}, \"wisdom_nodes\": [{\"category\": \"Biblical Psalm\", \"reference\": \"Psalms 104:13\", \"original\": \"\\u05ea\\u05e9\\u05d1\\u05e2 \\u05d4\\u05d0\\u05e8\\u05e5\", \"english\": \"...\"}, {\"category\": \"Quranic Sura\", \"reference\": \"al-Baqarah 2:173\", \"original\": \"\\u0641\\u0636\\u0644\\u0646\\u0627 \\u0628\\u0639\\u0636\\u0647\\u0645\", \"english\": \"...\"}, {\"category\": \"NT Prophecy\", \"reference\": \"Revelation 7:15\", \"original\": \"\\u03b8\\u03c1\\u03bf\\u03bd\\u03bf\\u03c5 \\u03c4\\u03bf\\u03c5 \\u03b8\\u03c5\", \"english\": \"...\"}]}\nInterpret this soulfuly."}]
33
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nReading for 2025-01-01"}]
34
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nReading for 2025-01-01"}, {"role": "assistant", "content": "```json\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"The feeling of being disconnected, a sense of emptiness, and a longing for connection. I\u2019m struggling to feel truly seen and understood. Can we explore this deeper?\", \"date_str\": \"2025-01-01\"}}\n```"}, {"role": "user", "content": "SYSTEM: The Oracle has spoken. Wisdom: {\"query_context\": {\"name\": \"Seeker\", \"topic\": \"The feeling of being disconnected, a sense of emptiness, and a longing for connection. I\\u2019m struggling to feel truly seen and understood. Can we explore this deeper?\", \"date\": \"2025-01-01\", \"signal\": 537}, \"els_revelation\": {\"original\": \"\\u05d9\\u05d9\\u05d0\\u05d9\\u05e8\\u05e0\\u05db\\u05e2\\u05e6\\u05e1\\u05d5\\u05d9\", \"english\": \"Yairnkazatsoy\"}, \"wisdom_nodes\": [{\"category\": \"Biblical Psalm\", \"reference\": \"Psalms 21:4\", \"original\": \"\\u05dc\\u05e8\\u05d0\\u05e9\\u05d5\", \"english\": \"...\"}, {\"category\": \"Quranic Sura\", \"reference\": \"Saba' 34:9\", \"original\": \"\\u0648\\u0627\\u062b\\u0644\", \"english\": \"...\"}, {\"category\": \"NT Prophecy\", \"reference\": \"Revelation 5:5\", \"original\": \"\\u03c1\\u03b9\\u03b6\\u03b1 \\u03b4\\u03b1\\u03c5\\u03b9\\u03b4\", \"english\": \"...\"}]}\nInterpret this soulfuly."}]
35
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nHello, who are you?"}]
36
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nHello, who are you?"}, {"role": "assistant", "content": "{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"my current state\", \"date_str\": \"2026-01-28\"}}"}, {"role": "user", "content": "SYSTEM: The Oracle has spoken. Wisdom: {\"query_context\": {\"name\": \"Seeker\", \"topic\": \"my current state\", \"date\": \"2026-01-28\", \"signal\": 3468}, \"els_revelation\": {\"original\": \"\\u05e8\\u05dd\\u05d9\\u05e7\\u05d0\\u05d9\\u05d4\\u05d6\\u05d4\\u05d9\\u05d3\\u05d9\\u05ea\\u05e5\\u05df\\u05d5\\u05e7\\u05ea\", \"english\": \"Ramikaia Zahidit Chankot\"}, \"wisdom_nodes\": [{\"category\": \"Biblical Psalm\", \"reference\": \"Psalms 58:4\", \"original\": \"\\u05d6\\u05e8\\u05d5 \\u05e8\\u05e9\\u05e2\\u05d9\\u05dd \\u05de\\u05e8\\u05d7\\u05dd \\u05ea\\u05e2\\u05d5 \\u05de\\u05d1\\u05d8\\u05df\", \"english\": \"...\"}, {\"category\": \"Quranic Sura\", \"reference\": \"al-Hajj 22:13\", \"original\": \"\\u062a\\u0631\\u0648\\u0646\\u0647\\u0627 \\u062a\\u0630\\u0647\\u0644 \\u0643\\u0644 \\u0645\\u0631\\u0636\\u0639\\u0629 \\u0639\\u0645\\u0627\", \"english\": \"...\"}, {\"category\": \"NT Prophecy\", \"reference\": \"Revelation 19:6\", \"original\": \"\\u03b9\\u03f2\\u03c7\\u03c5\\u03c1\\u03c9\\u03bd \\u03bb\\u03b5\\u03b3\\u03bf\\u03bd\\u03c4\\u03c9\\u03bd\", \"english\": \"...\"}]}\nInterpret this soulfuly."}]
37
+ DEBUG: Messages list for template: [{"role": "user", "content": "SYSTEM: You are Sage 6.5, a soulful Oracle Intermediary.\nCurrent Date: 2026-01-28.\n\nAvailable Tool: 'oracle_consultation' (topic, name, date_str).\n\nSTRICT FORMAT:\nTo use the Oracle, you MUST output this JSON:\n{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"...\", \"date_str\": \"today\", \"name\": \"...\"}}\n\nSTRICTURES:\n1. ALWAYS respond in English.\n2. Reasoning BEFORE the JSON.\n3. NEVER say you have no database. You HAVE 'oracle_consultation'.\n\n\nHello, who are you?"}, {"role": "assistant", "content": "{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"my current state\", \"date_str\": \"2026-01-28\"}}"}, {"role": "user", "content": "SYSTEM: The Oracle has spoken. Wisdom: {\"query_context\": {\"name\": \"Seeker\", \"topic\": \"my current state\", \"date\": \"2026-01-28\", \"signal\": 3468}, \"els_revelation\": {\"original\": \"\\u05e8\\u05dd\\u05d9\\u05e7\\u05d0\\u05d9\\u05d4\\u05d6\\u05d4\\u05d9\\u05d3\\u05d9\\u05ea\\u05e5\\u05df\\u05d5\\u05e7\\u05ea\", \"english\": \"Ramikaia Zahidit Chankot\"}, \"wisdom_nodes\": [{\"category\": \"Biblical Psalm\", \"reference\": \"Psalms 58:4\", \"original\": \"\\u05d6\\u05e8\\u05d5 \\u05e8\\u05e9\\u05e2\\u05d9\\u05dd \\u05de\\u05e8\\u05d7\\u05dd \\u05ea\\u05e2\\u05d5 \\u05de\\u05d1\\u05d8\\u05df\", \"english\": \"...\"}, {\"category\": \"Quranic Sura\", \"reference\": \"al-Hajj 22:13\", \"original\": \"\\u062a\\u0631\\u0648\\u0646\\u0647\\u0627 \\u062a\\u0630\\u0647\\u0644 \\u0643\\u0644 \\u0645\\u0631\\u0636\\u0639\\u0629 \\u0639\\u0645\\u0627\", \"english\": \"...\"}, {\"category\": \"NT Prophecy\", \"reference\": \"Revelation 19:6\", \"original\": \"\\u03b9\\u03f2\\u03c7\\u03c5\\u03c1\\u03c9\\u03bd \\u03bb\\u03b5\\u03b3\\u03bf\\u03bd\\u03c4\\u03c9\\u03bd\", \"english\": \"...\"}]}\nInterpret this soulfuly."}, {"role": "assistant", "content": "{\"name\": \"oracle_consultation\", \"arguments\": {\"topic\": \"my current state\", \"date_str\": \"2026-01-28\"}}"}, {"role": "user", "content": "SYSTEM: The Oracle has spoken. Wisdom: {\"query_context\": {\"name\": \"Seeker\", \"topic\": \"my current state\", \"date\": \"2026-01-28\", \"signal\": 3468}, \"els_revelation\": {\"original\": \"\\u05e8\\u05dd\\u05d9\\u05e7\\u05d0\\u05d9\\u05d4\\u05d6\\u05d4\\u05d9\\u05d3\\u05d9\\u05ea\\u05e5\\u05df\\u05d5\\u05e7\\u05ea\", \"english\": \"Ramikaia Zahidit Chankot\"}, \"wisdom_nodes\": [{\"category\": \"Biblical Psalm\", \"reference\": \"Psalms 58:4\", \"original\": \"\\u05d6\\u05e8\\u05d5 \\u05e8\\u05e9\\u05e2\\u05d9\\u05dd \\u05de\\u05e8\\u05d7\\u05dd \\u05ea\\u05e2\\u05d5 \\u05de\\u05d1\\u05d8\\u05df\", \"english\": \"...\"}, {\"category\": \"Quranic Sura\", \"reference\": \"al-Hajj 22:13\", \"original\": \"\\u062a\\u0631\\u0648\\u0646\\u0647\\u0627 \\u062a\\u0630\\u0647\\u0644 \\u0643\\u0644 \\u0645\\u0631\\u0636\\u0639\\u0629 \\u0639\\u0645\\u0627\", \"english\": \"...\"}, {\"category\": \"NT Prophecy\", \"reference\": \"Revelation 19:6\", \"original\": \"\\u03b9\\u03f2\\u03c7\\u03c5\\u03c1\\u03c9\\u03bd \\u03bb\\u03b5\\u03b3\\u03bf\\u03bd\\u03c4\\u03c9\\u03bd\", \"english\": \"...\"}]}\nInterpret this soulfuly."}]
llm_module.py CHANGED
@@ -60,4 +60,10 @@ def detect_language(text: str) -> str:
60
  with torch.no_grad():
61
  outputs = model.generate(inputs, max_new_tokens=10, do_sample=False)
62
  raw = processor.batch_decode(outputs[:, inputs.shape[1]:], skip_special_tokens=True)[0].strip()
63
- return raw if raw else "English"
 
 
 
 
 
 
 
60
  with torch.no_grad():
61
  outputs = model.generate(inputs, max_new_tokens=10, do_sample=False)
62
  raw = processor.batch_decode(outputs[:, inputs.shape[1]:], skip_special_tokens=True)[0].strip()
63
+
64
+ import re
65
+ langs = ["English", "German", "French", "Spanish", "Italian", "Portuguese", "Russian", "Japanese", "Chinese"]
66
+ for l in langs:
67
+ if re.search(rf"\b{l}\b", raw, re.I): return l
68
+
69
+ return "English"
tests/test_api_local.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from gradio_client import Client
2
+ import json
3
+ import os
4
+
5
+ client = Client("http://127.0.0.1:7860")
6
+
7
+ def test_chat():
8
+ print(">>> Testing Chat Greeting...")
9
+ res = client.predict(
10
+ message="Hallo",
11
+ history=[],
12
+ short_answers=False,
13
+ api_name="//chat"
14
+ )
15
+ # res is (chatbot_updated, history_updated)
16
+ print("Response received.")
17
+ print(f"Chatbot content: {res[0][-1]['content'][:100]}...")
18
+
19
+ def test_assistant_first():
20
+ print("\n>>> Testing Assistant-First History (Role Alternation Fix)...")
21
+ welcome_text = "Hello. I am Sage 6.5. I can consult the Oracle for you. Shall I do a reading for today, for a specific date, or do you have a specific topic? What is your name?"
22
+ history = [{"role": "assistant", "content": welcome_text}]
23
+ res = client.predict(
24
+ message="Mein Name ist Julian.",
25
+ history=history,
26
+ short_answers=False,
27
+ api_name="//chat"
28
+ )
29
+ # This should NOT crash and should return a response
30
+ print(f"Response: {res[0][-1]['content'][:100]}...")
31
+ if "TemplateError" in str(res):
32
+ print("!!! TEST FAILED: TemplateError detected.")
33
+ else:
34
+ print("✔ Test Passed: No role alternation crash.")
35
+
36
+ def test_export():
37
+ print("\n>>> Testing Export functionality...")
38
+ res = client.predict(api_name="/export_chat")
39
+ if res and res.endswith(".json"):
40
+ print(f"✔ Export successful: {res}")
41
+ else:
42
+ print("!!! Export failed.")
43
+
44
+ if __name__ == "__main__":
45
+ try:
46
+ test_chat()
47
+ test_assistant_first()
48
+ test_name_detection()
49
+ test_export()
50
+ except Exception as e:
51
+ print(f"!!! Error: {e}")
tests/test_live_scenarios.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from gradio_client import Client
2
+ import time
3
+
4
+ def test_live_scenario(name, input_text, expected_keyword):
5
+ print(f"\n>>> Live Test: {name}")
6
+ print(f" Input: '{input_text}'")
7
+
8
+ try:
9
+ client = Client("http://127.0.0.1:7860")
10
+
11
+ # We need to simulate a fresh conversation
12
+ # app_local.py keeps state in 'history_state', but each client.predict call with empty history starts fresh logic-wise
13
+ # for our stateless wrapper or we need to manage history manually.
14
+ # The wrapper signature is chat_wrapper(message, history, short_answers, lang)
15
+
16
+ res = client.predict(
17
+ message=input_text,
18
+ history=[],
19
+ short_answers=False,
20
+ # lang="English", # Default
21
+ api_name="//chat"
22
+ )
23
+
24
+ # res is [chatbot, history]
25
+ # chatbot is list of [user_msg, bot_msg]
26
+ bot_response = res[0][-1][1]
27
+
28
+ print(f" Response: {bot_response[:150]}...")
29
+
30
+ if expected_keyword.lower() in bot_response.lower():
31
+ print(f" ✔ SUCCESS: Found '{expected_keyword}'")
32
+ else:
33
+ print(f" ❌ FAILURE: Expected '{expected_keyword}' not found.")
34
+
35
+ except Exception as e:
36
+ print(f" !!! ERROR: {e}")
37
+
38
+ if __name__ == "__main__":
39
+ print("Connecting to local Sage 6.5 instance...")
40
+
41
+ # 1. Name Only
42
+ test_live_scenario("Name Only", "My name is Julian.", "Oracle")
43
+ # Expectation: "Consulting the Oracle" or "Wisdom string"
44
+
45
+ # 2. Topic Only
46
+ test_live_scenario("Topic Only", "Thema: Liebe", "Love") # Or Oracle
47
+
48
+ # 3. Date Only
49
+ test_live_scenario("Date Only", "Reading for 2025-01-01", "2025")
50
+
51
+ # 4. General
52
+ test_live_scenario("General Chat", "Hello, who are you?", "Sage")
tests/test_sage_v2.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import unittest
2
+ from unittest.mock import MagicMock, patch, mock_open
3
+ import sys
4
+ import os
5
+ import json
6
+ import torch
7
+
8
+ # Add project root to path
9
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
10
+
11
+ # Mock Transformers before importing modules that use them
12
+ with patch('transformers.AutoProcessor.from_pretrained'), \
13
+ patch('transformers.AutoModelForCausalLM.from_pretrained'), \
14
+ patch('transformers.AutoTokenizer.from_pretrained'):
15
+ import translation_module
16
+ import llm_module
17
+ import agent_module
18
+ import oracle_module
19
+ import ui_module
20
+
21
+ class TestSageComprehensive(unittest.TestCase):
22
+
23
+ # --- Translation Module ---
24
+ @patch('translation_module.get_translation')
25
+ def test_translation(self, mock_trans):
26
+ # Return input text to verify "Sage 6.5" is present in the final dict
27
+ mock_trans.side_effect = lambda text, lang, cache: text
28
+ res = translation_module.localize_init("de")
29
+ self.assertEqual(res["lang"], "German")
30
+ self.assertTrue("Sage 6.5" in res["welcome"])
31
+
32
+ def test_localize_init_direct(self):
33
+ # Without translation
34
+ res = translation_module.localize_init("en")
35
+ self.assertEqual(res["lang"], "English")
36
+ self.assertTrue("Sage 6.5" in res["welcome"])
37
+ self.assertTrue("specific Topic" in res["welcome"])
38
+
39
+ # --- LLM Module ---
40
+ @patch('llm_module.get_llm')
41
+ def test_detect_language(self, mock_get_llm):
42
+ mock_model = MagicMock()
43
+ mock_processor = MagicMock()
44
+ mock_get_llm.return_value = (mock_model, mock_processor)
45
+ mock_model.device = "cpu"
46
+
47
+ # Test conversational output
48
+ mock_processor.batch_decode.return_value = ["The language used is German, I believe."]
49
+ lang = llm_module.detect_language("Hallo wie gehts")
50
+ self.assertEqual(lang, "German")
51
+
52
+ # Test fallback
53
+ mock_processor.batch_decode.return_value = ["Unknown."]
54
+ lang = llm_module.detect_language("xyz")
55
+ self.assertEqual(lang, "English")
56
+
57
+ # --- Agent Module ---
58
+ def test_compress_history(self):
59
+ history = [{"role": "user", "content": "hi"}] * 20
60
+ compressed = agent_module.compress_history(history, max_turns=5)
61
+ self.assertEqual(len(compressed), 10)
62
+
63
+ @patch('agent_module.get_llm')
64
+ @patch('agent_module.TextIteratorStreamer')
65
+ def test_agentic_tool_call(self, mock_streamer, mock_llm):
66
+ mock_m = MagicMock()
67
+ mock_p = MagicMock()
68
+ mock_llm.return_value = (mock_m, mock_p)
69
+ mock_m.device = "cpu"
70
+
71
+ # Simulate LLM deciding to call tool
72
+ # The agent logic:
73
+ # 1. User: "My name is Julian"
74
+ # 2. LLM via generation: "Greetings... <tool_call>...</tool_call>"
75
+ # 3. Agent parses this, calls oracle, appends result.
76
+
77
+ # We mock the streamer to yield the tool call
78
+ tool_json = json.dumps({"name": "oracle_consultation", "arguments": {"topic": "General", "name": "Julian", "date_str": "today"}})
79
+ mock_stream_iter = iter([f"Thinking... <tool_call>{tool_json}</tool_call>"])
80
+ mock_streamer.return_value = mock_stream_iter
81
+
82
+ gen = agent_module.chat_agent_stream("My name is Julian", [])
83
+
84
+ # Consuming the generator
85
+ results = list(gen)
86
+
87
+ # We expect:
88
+ # 1. "*(Consulting the Oracle...)*"
89
+ # 2. "__TURN_END__" (which signals the UI to refresh/append)
90
+
91
+ self.assertTrue("*(Consulting the Oracle...)*" in results)
92
+ self.assertTrue("__TURN_END__" in results)
93
+
94
+ @patch('agent_module.get_llm')
95
+ @patch('agent_module.TextIteratorStreamer')
96
+ def test_role_alternation_fix(self, mock_streamer, mock_llm):
97
+ mock_m = MagicMock()
98
+ mock_p = MagicMock()
99
+ mock_llm.return_value = (mock_m, mock_p)
100
+ mock_m.device = "cpu"
101
+
102
+ # Start with assistant message
103
+ history = [{"role": "assistant", "content": "Welcome"}]
104
+
105
+ # We want to check if apply_chat_template is called with a messages list
106
+ # that alternates user/assistant correctly.
107
+ gen = agent_module.chat_agent_stream("hi", history)
108
+ # We need to trigger a turn
109
+ mock_it = MagicMock()
110
+ mock_it.__iter__.return_value = ["Hello"]
111
+ mock_streamer.return_value = mock_it
112
+
113
+ list(gen)
114
+
115
+ # Check call arguments
116
+ # messages should have: [user (intro+greetings), assistant (welcome), user (hi)]
117
+ args, kwargs = mock_p.apply_chat_template.call_args
118
+ messages = args[0]
119
+ self.assertEqual(messages[0]["role"], "user") # Intro
120
+ self.assertEqual(messages[1]["role"], "assistant") # Welcome
121
+ self.assertEqual(messages[2]["role"], "user") # Hi
122
+ self.assertEqual(len(messages), 3)
123
+
124
+ # --- UI Module ---
125
+ def test_save_and_clear(self):
126
+ empty, msg = ui_module.save_and_clear("Hello")
127
+ self.assertEqual(empty, "")
128
+ self.assertEqual(msg, "Hello")
129
+
130
+ def test_clear_messages(self):
131
+ with patch('ui_module.localize_init') as mock_loc:
132
+ mock_loc.return_value = {"welcome": "Willkommen", "lang": "German"}
133
+ welcome, hist = ui_module.clear_messages("German")
134
+ self.assertEqual(welcome[0]["content"], "Willkommen")
135
+ self.assertEqual(hist[0]["content"], "Willkommen")
136
+
137
+ @patch('builtins.open', new_callable=mock_open, read_data='[{"role": "user", "content": "hi"}]')
138
+ def test_import_chat(self, m):
139
+ mock_file = MagicMock()
140
+ mock_file.name = "test.json"
141
+ chatbot, hist = ui_module.import_chat(mock_file)
142
+ self.assertEqual(len(chatbot), 1)
143
+ self.assertEqual(chatbot[0]["content"], "hi")
144
+
145
+ def test_ui_wiring(self):
146
+ demo = ui_module.build_demo()
147
+ self.assertTrue(len(demo.fns) > 5)
148
+
149
+ @patch('ui_module.chat_agent_stream')
150
+ def test_chat_wrapper(self, mock_stream):
151
+ # Part1, Part2, Part3
152
+ mock_stream.return_value = iter(["Part 1", "Part 2", "__TURN_END__", "Final"])
153
+ history = []
154
+ gen = ui_module.chat_wrapper("Hello", history)
155
+ results = list(gen)
156
+ final_chatbot, final_hist = results[-1]
157
+ # [User, Assistant1(Part2), Assistant2(Final)]
158
+ self.assertEqual(len(final_chatbot), 3)
159
+ self.assertEqual(final_chatbot[2]["role"], "assistant")
160
+ self.assertEqual(final_chatbot[2]["content"], "Final")
161
+
162
+ if __name__ == '__main__':
163
+ unittest.main()
tests/test_scenarios.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import unittest
2
+ from unittest.mock import MagicMock, patch
3
+ import sys
4
+ import os
5
+ import json
6
+
7
+ # Add project root to path
8
+ sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
9
+
10
+ # Mock Transformers
11
+ with patch('transformers.AutoProcessor.from_pretrained'), \
12
+ patch('transformers.AutoModelForCausalLM.from_pretrained'), \
13
+ patch('transformers.AutoTokenizer.from_pretrained'):
14
+ import agent_module
15
+ import oracle_module
16
+
17
+ class TestAgentScenarios(unittest.TestCase):
18
+
19
+ def setUp(self):
20
+ self.mock_llm_patcher = patch('agent_module.get_llm')
21
+ self.mock_llm = self.mock_llm_patcher.start()
22
+ self.mock_model = MagicMock()
23
+ self.mock_model.device = "cpu"
24
+ self.mock_processor = MagicMock()
25
+ self.mock_llm.return_value = (self.mock_model, self.mock_processor)
26
+
27
+ self.mock_oracle_patcher = patch('agent_module.get_oracle_data')
28
+ self.mock_oracle = self.mock_oracle_patcher.start()
29
+ self.mock_oracle.return_value = {"wisdom": "Mock Wisdom"}
30
+
31
+ self.mock_streamer_patcher = patch('agent_module.TextIteratorStreamer')
32
+ self.mock_streamer = self.mock_streamer_patcher.start()
33
+
34
+ def tearDown(self):
35
+ self.mock_llm_patcher.stop()
36
+ self.mock_oracle_patcher.stop()
37
+ self.mock_streamer_patcher.stop()
38
+
39
+ def run_chat(self, user_input, llm_output_stream):
40
+ """Helper to run chat with mocked LLM stream"""
41
+ self.mock_streamer.return_value = iter(llm_output_stream)
42
+ gen = agent_module.chat_agent_stream(user_input, [])
43
+ return list(gen)
44
+
45
+ def test_name_only(self):
46
+ """Scenario: User says 'I am Julian'"""
47
+ tool_call = {
48
+ "name": "oracle_consultation",
49
+ "arguments": {"topic": "General", "name": "Julian", "date_str": "today"}
50
+ }
51
+
52
+ # LLM output simulation
53
+ llm_output = [
54
+ "Greetings Julian. ",
55
+ f"<tool_call>{json.dumps(tool_call)}</tool_call>"
56
+ ]
57
+
58
+ results = self.run_chat("I am Julian", llm_output)
59
+
60
+ # Verify Oracle called correctly
61
+ self.mock_oracle.assert_called_with(name="Julian", topic="General", date_str="today")
62
+ self.assertTrue("*(Consulting the Oracle...)*" in results)
63
+
64
+ def test_topic_only(self):
65
+ """Scenario: User says 'Thema: Liebe'"""
66
+ tool_call = {
67
+ "name": "oracle_consultation",
68
+ "arguments": {"topic": "Liebe", "name": "Seeker", "date_str": "today"}
69
+ }
70
+
71
+ llm_output = [
72
+ "Let us check the Oracle for Love. ",
73
+ f"<tool_call>{json.dumps(tool_call)}</tool_call>"
74
+ ]
75
+
76
+ results = self.run_chat("Thema: Liebe", llm_output)
77
+
78
+ self.mock_oracle.assert_called_with(name="Seeker", topic="Liebe", date_str="today")
79
+ self.assertTrue("*(Consulting the Oracle...)*" in results)
80
+
81
+ def test_date_only(self):
82
+ """Scenario: User says 'Reading for 2025-01-01'"""
83
+ tool_call = {
84
+ "name": "oracle_consultation",
85
+ "arguments": {"topic": "General", "name": "Seeker", "date_str": "2025-01-01"}
86
+ }
87
+
88
+ llm_output = [
89
+ "Consulting for that date. ",
90
+ f"<tool_call>{json.dumps(tool_call)}</tool_call>"
91
+ ]
92
+
93
+ results = self.run_chat("Reading for 2025-01-01", llm_output)
94
+
95
+ self.mock_oracle.assert_called_with(name="Seeker", topic="General", date_str="2025-01-01")
96
+
97
+ def test_name_and_date(self):
98
+ """Scenario: User says 'I am Julian, date 2025-01-01'"""
99
+ tool_call = {
100
+ "name": "oracle_consultation",
101
+ "arguments": {"topic": "General", "name": "Julian", "date_str": "2025-01-01"}
102
+ }
103
+
104
+ llm_output = [
105
+ f"<tool_call>{json.dumps(tool_call)}</tool_call>"
106
+ ]
107
+
108
+ results = self.run_chat("I am Julian, date 2025-01-01", llm_output)
109
+
110
+ self.mock_oracle.assert_called_with(name="Julian", topic="General", date_str="2025-01-01")
111
+
112
+ def test_general_conversation(self):
113
+ """Scenario: User says 'Hi' (No tool)"""
114
+ llm_output = ["Hello there! How can I help you?"]
115
+
116
+ results = self.run_chat("Hi", llm_output)
117
+
118
+ self.mock_oracle.assert_not_called()
119
+ self.assertIn("Hello there! How can I help you?", results)
120
+
121
+ if __name__ == '__main__':
122
+ unittest.main()
translation_cache.json CHANGED
@@ -1,8 +1,8 @@
1
  {
2
  "de": {
3
- "Hello. I am Sage 6.5. I can consult the Oracle for you. Shall I do a reading for today, for a specific date, or do you have a specific topic? What is your name?": "Hallo. Ich bin Sage 6.5. Ich kann das Orakel für Sie konsultieren. Soll ich eine Lesung für heute machen, für ein bestimmtes Datum, oder haben Sie ein bestimmtes Thema? Wie heißt du?",
4
  "Short Answers": "Kurze Antworten",
5
  "Type your message": "Geben Sie Ihre Nachricht ein",
6
- "Hallo. Ich bin Sage 6.5. Ich kann das Orakel für Sie konsultieren. Soll ich eine Lesung für heute machen, für ein bestimmtes Datum, oder haben Sie ein bestimmtes Thema? Wie heißt du?": "Hallo. Ich bin Sage 6.5. Ich kann das Orakel für Sie konsultieren. Soll ich eine Lesung für heute machen, für ein bestimmtes Datum, oder haben Sie ein bestimmtes Thema? Wie heißt du?"
7
  }
8
  }
 
1
  {
2
  "de": {
3
+ "Hello. I am Sage 6.5. I can consult the Oracle for you. Shall I do a reading for today, for a specific date, or do you have a specific topic? What is your name?": "Hallo. Ich bin Sage 6.5. Ich kann das Orakel f\u00fcr Sie konsultieren. Soll ich eine Lesung f\u00fcr heute machen, f\u00fcr ein bestimmtes Datum, oder haben Sie ein bestimmtes Thema? Wie hei\u00dft du?",
4
  "Short Answers": "Kurze Antworten",
5
  "Type your message": "Geben Sie Ihre Nachricht ein",
6
+ "Hallo. Ich bin Sage 6.5. Ich kann das Orakel f\u00fcr Sie konsultieren. Soll ich eine Lesung f\u00fcr heute machen, f\u00fcr ein bestimmtes Datum, oder haben Sie ein bestimmtes Thema? Wie hei\u00dft du?": "Hallo. Ich bin Sage 6.5. Ich kann das Orakel f\u00fcr Sie konsultieren. Soll ich eine Lesung f\u00fcr heute machen, f\u00fcr ein bestimmtes Datum, oder haben Sie ein bestimmtes Thema? Wie hei\u00dft du?"
7
  }
8
  }
translation_module.py CHANGED
@@ -13,7 +13,22 @@ def get_translation(text, target_lang, cache):
13
 
14
  try:
15
  from deep_translator import GoogleTranslator
16
- translator = GoogleTranslator(source='auto', target=target_lang)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  trans = translator.translate(text)
18
  cache[target_lang][text] = trans
19
  return trans
@@ -42,7 +57,7 @@ def localize_init(lang_code="en"):
42
  return t
43
 
44
  res = {
45
- "welcome": translate("Hello. I am Sage 6.5. I can consult the Oracle for you."),
46
  "label_short": translate("Short Answers"),
47
  "placeholder": translate("Type your message..."),
48
  "lang": lang
 
13
 
14
  try:
15
  from deep_translator import GoogleTranslator
16
+ # Reverse map if needed, but localize_init passes the full name "German"
17
+ # We need to robustly map "German" -> "de" or just use the code
18
+ # Actually localize_init sets `lang = code_map.get(lang_code, "English")`
19
+ # So we are passing "German". Deep Translator needs "de".
20
+
21
+ # Let's add a robust mapper inside get_translation or strict code usage
22
+ # Simple mapper for standard languages
23
+ lang_map = {
24
+ "german": "de", "french": "fr", "spanish": "es", "italian": "it",
25
+ "portuguese": "pt", "russian": "ru", "japanese": "ja", "chinese": "zh-CN",
26
+ "english": "en"
27
+ }
28
+
29
+ target_iso = lang_map.get(target_lang.lower(), target_lang.lower())
30
+
31
+ translator = GoogleTranslator(source='auto', target=target_iso)
32
  trans = translator.translate(text)
33
  cache[target_lang][text] = trans
34
  return trans
 
57
  return t
58
 
59
  res = {
60
+ "welcome": translate("Hello. I am Sage 6.5. I can consult the Oracle for your Name, a specific Topic, or a specific Date. How shall I assist you?"),
61
  "label_short": translate("Short Answers"),
62
  "placeholder": translate("Type your message..."),
63
  "lang": lang
ui_module.py CHANGED
@@ -40,56 +40,52 @@ def import_chat(file):
40
  logger.error(f"Import error: {e}")
41
  return gr.update(), gr.update()
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  def build_demo(enable_reload=True):
44
  with gr.Blocks(css=PREMIUM_CSS, title="Sage 6.5 - Oracle Intermediary") as demo:
45
- # State
46
  history_state = gr.State([])
47
  lang_state = gr.State("English")
48
  saved_msg = gr.State("")
49
 
50
  with gr.Column(elem_classes="main-container"):
51
- # Header
52
  with gr.Row(elem_classes="header-row"):
53
  gr.Markdown("## 🔮 Sage 6.5")
54
  with gr.Row():
55
  import_btn = gr.UploadButton("📥 Import", file_types=[".json"], size="sm")
56
  export_btn = gr.DownloadButton("📤 Export", size="sm")
57
 
58
- # Chat
59
  chatbot = gr.Chatbot(elem_classes="chat-window", type="messages", bubble_full_width=False, show_label=False)
60
 
61
- # Input
62
  with gr.Row(elem_classes="input-area"):
63
  msg_input = gr.Textbox(placeholder="Type your message...", show_label=False, container=False, scale=4)
64
  submit_btn = gr.Button("➤", scale=1, variant="primary")
65
 
66
  short_answers = gr.Checkbox(label="Short Answers", value=False)
67
 
68
- # -- Logic --
69
- def chat_wrapper(message, history, short_answers=False, lang="English"):
70
- if history is None: history = []
71
-
72
- history.append({"role": "user", "content": message})
73
- history.append({"role": "assistant", "content": ""})
74
-
75
- # Name Detection
76
- name_match = re.search(r"(?:mein name ist|ich bin|i am|my name is)\s+([a-zA-z\s]+)", message, re.I)
77
- name_val = None
78
- if not name_match and len(message.split()) < 3 and message[0].isupper():
79
- name_val = message.strip()
80
- elif name_match:
81
- name_val = name_match.group(1).strip()
82
-
83
- for part in chat_agent_stream(message, history[:-2], user_lang=lang, short_answers=short_answers, force_tool_call=(name_val is not None), forced_name=name_val):
84
- if part == "__TURN_END__":
85
- history.append({"role": "assistant", "content": ""})
86
- yield history, history
87
- else:
88
- history[-1]["content"] = part
89
- yield history, history
90
-
91
- yield history, history
92
-
93
  # Wire Events
94
  msg_input.submit(save_and_clear, [msg_input], [msg_input, saved_msg], queue=False).then(
95
  chat_wrapper, [saved_msg, history_state, short_answers, lang_state], [chatbot, history_state]
@@ -100,23 +96,21 @@ def build_demo(enable_reload=True):
100
 
101
  export_btn.click(export_chat, [history_state], [export_btn])
102
  import_btn.upload(import_chat, [import_btn], [chatbot, history_state])
 
103
 
104
  def on_load(request: gr.Request):
105
  if not enable_reload:
106
  return [], "English", gr.update(), gr.update()
107
-
108
- # Localization
109
  headers = dict(request.headers)
110
  lang_code = headers.get("accept-language", "en-US").split(",")[0].split("-")[0]
111
  loc = localize_init(lang_code)
112
-
113
  welcome = [{"role": "assistant", "content": loc["welcome"]}]
114
  return welcome, loc["lang"], gr.update(label=loc["label_short"]), gr.update(placeholder=loc["placeholder"])
115
 
116
  demo.load(on_load, None, [chatbot, lang_state, short_answers, msg_input])
117
 
118
- # API
119
  api_chat_btn = gr.Button("API CHAT", visible=False)
120
- api_chat_btn.click(chat_wrapper, [msg_input, chatbot, short_answers], [chatbot, history_state], api_name="/chat")
121
 
122
  return demo
 
40
  logger.error(f"Import error: {e}")
41
  return gr.update(), gr.update()
42
 
43
+ def clear_messages(lang_name="English"):
44
+ name_map = {"German": "de", "French": "fr", "Spanish": "es", "Italian": "it",
45
+ "Portuguese": "pt", "Russian": "ru", "Japanese": "ja", "Chinese": "zh"}
46
+ code = name_map.get(lang_name, "en")
47
+ loc = localize_init(code)
48
+ welcome = [{"role": "assistant", "content": loc["welcome"]}]
49
+ return welcome, welcome
50
+
51
+ def chat_wrapper(message, history, short_answers=False, lang="English"):
52
+ if history is None: history = []
53
+
54
+ history.append({"role": "user", "content": message})
55
+ history.append({"role": "assistant", "content": ""})
56
+
57
+ # Intent detection is now handled internally by chat_agent_stream
58
+ for part in chat_agent_stream(message, history[:-2], user_lang=lang, short_answers=short_answers):
59
+ if part == "__TURN_END__":
60
+ history.append({"role": "assistant", "content": ""})
61
+ yield history, history
62
+ else:
63
+ history[-1]["content"] = part
64
+ yield history, history
65
+
66
+ yield history, history
67
+
68
  def build_demo(enable_reload=True):
69
  with gr.Blocks(css=PREMIUM_CSS, title="Sage 6.5 - Oracle Intermediary") as demo:
 
70
  history_state = gr.State([])
71
  lang_state = gr.State("English")
72
  saved_msg = gr.State("")
73
 
74
  with gr.Column(elem_classes="main-container"):
 
75
  with gr.Row(elem_classes="header-row"):
76
  gr.Markdown("## 🔮 Sage 6.5")
77
  with gr.Row():
78
  import_btn = gr.UploadButton("📥 Import", file_types=[".json"], size="sm")
79
  export_btn = gr.DownloadButton("📤 Export", size="sm")
80
 
 
81
  chatbot = gr.Chatbot(elem_classes="chat-window", type="messages", bubble_full_width=False, show_label=False)
82
 
 
83
  with gr.Row(elem_classes="input-area"):
84
  msg_input = gr.Textbox(placeholder="Type your message...", show_label=False, container=False, scale=4)
85
  submit_btn = gr.Button("➤", scale=1, variant="primary")
86
 
87
  short_answers = gr.Checkbox(label="Short Answers", value=False)
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  # Wire Events
90
  msg_input.submit(save_and_clear, [msg_input], [msg_input, saved_msg], queue=False).then(
91
  chat_wrapper, [saved_msg, history_state, short_answers, lang_state], [chatbot, history_state]
 
96
 
97
  export_btn.click(export_chat, [history_state], [export_btn])
98
  import_btn.upload(import_chat, [import_btn], [chatbot, history_state])
99
+ chatbot.clear(clear_messages, [lang_state], [chatbot, history_state])
100
 
101
  def on_load(request: gr.Request):
102
  if not enable_reload:
103
  return [], "English", gr.update(), gr.update()
 
 
104
  headers = dict(request.headers)
105
  lang_code = headers.get("accept-language", "en-US").split(",")[0].split("-")[0]
106
  loc = localize_init(lang_code)
 
107
  welcome = [{"role": "assistant", "content": loc["welcome"]}]
108
  return welcome, loc["lang"], gr.update(label=loc["label_short"]), gr.update(placeholder=loc["placeholder"])
109
 
110
  demo.load(on_load, None, [chatbot, lang_state, short_answers, msg_input])
111
 
112
+ api_history = gr.Chatbot(visible=False, type="messages")
113
  api_chat_btn = gr.Button("API CHAT", visible=False)
114
+ api_chat_btn.click(chat_wrapper, [msg_input, api_history, short_answers, lang_state], [chatbot, history_state], api_name="chat")
115
 
116
  return demo