Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,12 +1,16 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""
|
| 3 |
-
Overthinker
|
| 4 |
|
| 5 |
Dual Prompt Architecture:
|
| 6 |
- Input nodes -> prompts to generate OPTIONS/CHOICES/DECISIONS
|
| 7 |
- Outcome nodes -> prompts to generate OUTCOMES/CONSEQUENCES
|
| 8 |
- Model: nvidia/nemotron-3-nano-30b-a3b
|
| 9 |
Port: 7860
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
"""
|
| 11 |
|
| 12 |
import os
|
|
@@ -26,10 +30,27 @@ from gradio import Server
|
|
| 26 |
from fastapi import HTTPException
|
| 27 |
from starlette.responses import HTMLResponse
|
| 28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
load_dotenv()
|
| 30 |
|
| 31 |
OPENROUTER_API_KEY = os.getenv('OPENROUTER_API_KEY', '')
|
| 32 |
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY', '')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
app = Server()
|
| 35 |
PORT = 7860
|
|
@@ -132,9 +153,39 @@ class HistoryManager:
|
|
| 132 |
history_manager = HistoryManager()
|
| 133 |
|
| 134 |
# ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 135 |
-
# LLM API Call
|
| 136 |
# ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 137 |
def call_api(prompt: str, system_prompt: str = "You are a helpful assistant that generates decision trees.") -> Optional[str]:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
if OPENROUTER_API_KEY:
|
| 139 |
try:
|
| 140 |
headers = {
|
|
@@ -276,12 +327,12 @@ def _fallback_outcomes(decision: str, context: str = "") -> dict:
|
|
| 276 |
labels = [pos, neu, neg]
|
| 277 |
desc_map = {
|
| 278 |
'positive': f'If this path unfolds favorably, {pos.lower()}. This represents a best-case scenario where your decision leads to growth and improvement.',
|
| 279 |
-
'neutral': f'On this path, {neu.lower()}. The outcome is neither clearly good nor bad
|
| 280 |
'negative': f'In a challenging scenario, {neg.lower()}. This represents potential risks and difficulties that may arise.'
|
| 281 |
}
|
| 282 |
tip_map = {
|
| 283 |
'positive': 'Nurture this positive outcome by staying engaged and proactive.',
|
| 284 |
-
'neutral': 'Monitor this neutral path closely
|
| 285 |
'negative': 'Prepare contingency plans to mitigate this risk if it materializes.'
|
| 286 |
}
|
| 287 |
children = []
|
|
@@ -300,7 +351,7 @@ def _fallback_outcomes(decision: str, context: str = "") -> dict:
|
|
| 300 |
return {'children': children}
|
| 301 |
|
| 302 |
# ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 303 |
-
# Prompt Builders
|
| 304 |
# ββββββββββββββββββββββββοΏ½οΏ½βββββββββββββββββββββββ
|
| 305 |
def build_root_prompt(decision: str) -> str:
|
| 306 |
return f'''You are an AI that helps people explore decisions by generating decision trees.
|
|
@@ -329,11 +380,11 @@ Parent node: "{decision_label}"
|
|
| 329 |
Description: "{decision_desc}"
|
| 330 |
{comment_section}
|
| 331 |
|
| 332 |
-
Generate EXACTLY {count} child nodes that represent different OPTIONS or CHOICES the person could take. Each child should be a distinct, realistic possibility
|
| 333 |
|
| 334 |
IMPORTANT: Frame each child as an OPTION or CHOICE, not as an outcome. For example:
|
| 335 |
- GOOD: "Consult a financial advisor" (describes an action/choice)
|
| 336 |
-
- BAD: "Financial situation improves" (describes an outcome
|
| 337 |
|
| 338 |
Return ONLY valid JSON with exactly this structure (no markdown, no backticks):
|
| 339 |
{{
|
|
@@ -372,7 +423,7 @@ Generate EXACTLY {count} child nodes that represent a DIVERSE RANGE of possible
|
|
| 372 |
|
| 373 |
IMPORTANT: Frame each child as an OUTCOME or CONSEQUENCE, not as a choice someone makes. For example:
|
| 374 |
- GOOD: "Financial stability improves" (describes a result)
|
| 375 |
-
- BAD: "Consider financial planning" (describes a choice
|
| 376 |
|
| 377 |
Return ONLY valid JSON with exactly this structure (no markdown, no backticks):
|
| 378 |
{{
|
|
@@ -420,7 +471,7 @@ async def index():
|
|
| 420 |
if os.path.exists(html_path):
|
| 421 |
with open(html_path, "r", encoding="utf-8") as f:
|
| 422 |
return HTMLResponse(content=f.read(), status_code=200)
|
| 423 |
-
return HTMLResponse(content="<h1>Overthinker
|
| 424 |
|
| 425 |
|
| 426 |
@app.post("/create_tree")
|
|
@@ -641,15 +692,15 @@ async def export_path_md(request: dict):
|
|
| 641 |
if not tree_id or not node_id:
|
| 642 |
raise HTTPException(status_code=400, detail="Missing tree_id or node_id")
|
| 643 |
path = node_manager.get_path(tree_id, node_id)
|
| 644 |
-
md = '#
|
| 645 |
for i, node in enumerate(path):
|
| 646 |
indent = ' ' * i
|
| 647 |
-
emoji = {'root': '
|
| 648 |
md += f'{indent}{emoji} **{node.get("label", "")}**\n'
|
| 649 |
if node.get('description'):
|
| 650 |
md += f'{indent} > {node.get("description", "")}\n'
|
| 651 |
if node.get('tips') and len(node['tips']) > 0:
|
| 652 |
-
md += f'{indent} >
|
| 653 |
md += '\n'
|
| 654 |
return md
|
| 655 |
|
|
@@ -658,12 +709,24 @@ async def export_path_md(request: dict):
|
|
| 658 |
# Launch
|
| 659 |
# ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 660 |
if __name__ == "__main__":
|
| 661 |
-
|
| 662 |
-
print(f"
|
| 663 |
-
print(f"
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 667 |
app.launch(
|
| 668 |
server_port=PORT,
|
| 669 |
show_error=True,
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""
|
| 3 |
+
Overthinker v24 β Gradio Server Backend
|
| 4 |
|
| 5 |
Dual Prompt Architecture:
|
| 6 |
- Input nodes -> prompts to generate OPTIONS/CHOICES/DECISIONS
|
| 7 |
- Outcome nodes -> prompts to generate OUTCOMES/CONSEQUENCES
|
| 8 |
- Model: nvidia/nemotron-3-nano-30b-a3b
|
| 9 |
Port: 7860
|
| 10 |
+
|
| 11 |
+
Toggle:
|
| 12 |
+
- USE_HUGGINGFACE = True -> Uses HuggingFace Inference Client (HF_TOKEN from .env)
|
| 13 |
+
- USE_HUGGINGFACE = False -> Uses OpenRouter/OpenAI fallback (original behavior)
|
| 14 |
"""
|
| 15 |
|
| 16 |
import os
|
|
|
|
| 30 |
from fastapi import HTTPException
|
| 31 |
from starlette.responses import HTMLResponse
|
| 32 |
|
| 33 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 34 |
+
# HuggingFace Inference Client (optional import)
|
| 35 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 36 |
+
HF_AVAILABLE = False
|
| 37 |
+
try:
|
| 38 |
+
from huggingface_hub import InferenceClient
|
| 39 |
+
HF_AVAILABLE = True
|
| 40 |
+
except ImportError:
|
| 41 |
+
InferenceClient = None
|
| 42 |
+
print("[Warning] huggingface_hub not installed. HuggingFace mode disabled.")
|
| 43 |
+
|
| 44 |
load_dotenv()
|
| 45 |
|
| 46 |
OPENROUTER_API_KEY = os.getenv('OPENROUTER_API_KEY', '')
|
| 47 |
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY', '')
|
| 48 |
+
HF_TOKEN = os.getenv('HF_TOKEN', '')
|
| 49 |
+
|
| 50 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 51 |
+
# TOGGLE: Set True to use HuggingFace Inference Client
|
| 52 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 53 |
+
USE_HUGGINGFACE = True # <-- Set to False to use OpenRouter/OpenAI instead
|
| 54 |
|
| 55 |
app = Server()
|
| 56 |
PORT = 7860
|
|
|
|
| 153 |
history_manager = HistoryManager()
|
| 154 |
|
| 155 |
# ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 156 |
+
# LLM API Call β with HuggingFace Toggle
|
| 157 |
# ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 158 |
def call_api(prompt: str, system_prompt: str = "You are a helpful assistant that generates decision trees.") -> Optional[str]:
|
| 159 |
+
|
| 160 |
+
# βββ HuggingFace Inference Client Mode βββ
|
| 161 |
+
if USE_HUGGINGFACE:
|
| 162 |
+
if not HF_TOKEN:
|
| 163 |
+
print("[HF Error] No HF_TOKEN found in environment variables")
|
| 164 |
+
return None
|
| 165 |
+
if not HF_AVAILABLE:
|
| 166 |
+
print("[HF Error] huggingface_hub not installed. Run: pip install huggingface-hub>=0.23.0")
|
| 167 |
+
return None
|
| 168 |
+
|
| 169 |
+
try:
|
| 170 |
+
client = InferenceClient(token=HF_TOKEN)
|
| 171 |
+
|
| 172 |
+
response = client.chat.completions.create(
|
| 173 |
+
model=DEFAULT_MODEL,
|
| 174 |
+
messages=[
|
| 175 |
+
{"role": "system", "content": system_prompt},
|
| 176 |
+
{"role": "user", "content": prompt}
|
| 177 |
+
],
|
| 178 |
+
temperature=0.8,
|
| 179 |
+
max_tokens=2048
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
return response.choices[0].message.content
|
| 183 |
+
|
| 184 |
+
except Exception as e:
|
| 185 |
+
print(f"[HF Exception] {e}")
|
| 186 |
+
return None
|
| 187 |
+
|
| 188 |
+
# βββ OpenRouter Mode (original fallback) βββ
|
| 189 |
if OPENROUTER_API_KEY:
|
| 190 |
try:
|
| 191 |
headers = {
|
|
|
|
| 327 |
labels = [pos, neu, neg]
|
| 328 |
desc_map = {
|
| 329 |
'positive': f'If this path unfolds favorably, {pos.lower()}. This represents a best-case scenario where your decision leads to growth and improvement.',
|
| 330 |
+
'neutral': f'On this path, {neu.lower()}. The outcome is neither clearly good nor bad \u2014 it requires careful monitoring.',
|
| 331 |
'negative': f'In a challenging scenario, {neg.lower()}. This represents potential risks and difficulties that may arise.'
|
| 332 |
}
|
| 333 |
tip_map = {
|
| 334 |
'positive': 'Nurture this positive outcome by staying engaged and proactive.',
|
| 335 |
+
'neutral': 'Monitor this neutral path closely \u2014 small changes can shift the outcome.',
|
| 336 |
'negative': 'Prepare contingency plans to mitigate this risk if it materializes.'
|
| 337 |
}
|
| 338 |
children = []
|
|
|
|
| 351 |
return {'children': children}
|
| 352 |
|
| 353 |
# ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 354 |
+
# Prompt Builders \u2014 Dual prompts
|
| 355 |
# ββββββββββββββββββββββββοΏ½οΏ½βββββββββββββββββββββββ
|
| 356 |
def build_root_prompt(decision: str) -> str:
|
| 357 |
return f'''You are an AI that helps people explore decisions by generating decision trees.
|
|
|
|
| 380 |
Description: "{decision_desc}"
|
| 381 |
{comment_section}
|
| 382 |
|
| 383 |
+
Generate EXACTLY {count} child nodes that represent different OPTIONS or CHOICES the person could take. Each child should be a distinct, realistic possibility \u2014 an actionable decision they could make at this point.
|
| 384 |
|
| 385 |
IMPORTANT: Frame each child as an OPTION or CHOICE, not as an outcome. For example:
|
| 386 |
- GOOD: "Consult a financial advisor" (describes an action/choice)
|
| 387 |
+
- BAD: "Financial situation improves" (describes an outcome \u2014 DO NOT use here)
|
| 388 |
|
| 389 |
Return ONLY valid JSON with exactly this structure (no markdown, no backticks):
|
| 390 |
{{
|
|
|
|
| 423 |
|
| 424 |
IMPORTANT: Frame each child as an OUTCOME or CONSEQUENCE, not as a choice someone makes. For example:
|
| 425 |
- GOOD: "Financial stability improves" (describes a result)
|
| 426 |
+
- BAD: "Consider financial planning" (describes a choice \u2014 DO NOT do this)
|
| 427 |
|
| 428 |
Return ONLY valid JSON with exactly this structure (no markdown, no backticks):
|
| 429 |
{{
|
|
|
|
| 471 |
if os.path.exists(html_path):
|
| 472 |
with open(html_path, "r", encoding="utf-8") as f:
|
| 473 |
return HTMLResponse(content=f.read(), status_code=200)
|
| 474 |
+
return HTMLResponse(content="<h1>Overthinker v24</h1><p>index.html not found</p>", status_code=404)
|
| 475 |
|
| 476 |
|
| 477 |
@app.post("/create_tree")
|
|
|
|
| 692 |
if not tree_id or not node_id:
|
| 693 |
raise HTTPException(status_code=400, detail="Missing tree_id or node_id")
|
| 694 |
path = node_manager.get_path(tree_id, node_id)
|
| 695 |
+
md = '# \U0001f9e0 Overthinker \u2014 Decision Path\n\n'
|
| 696 |
for i, node in enumerate(path):
|
| 697 |
indent = ' ' * i
|
| 698 |
+
emoji = {'root': '\U0001f333', 'input': '\U0001f9e0', 'outcome': '\U0001f4ca'}.get(node.get('type', ''), '\U0001f4cc')
|
| 699 |
md += f'{indent}{emoji} **{node.get("label", "")}**\n'
|
| 700 |
if node.get('description'):
|
| 701 |
md += f'{indent} > {node.get("description", "")}\n'
|
| 702 |
if node.get('tips') and len(node['tips']) > 0:
|
| 703 |
+
md += f'{indent} > \U0001f4a1 {node["tips"][0]}\n'
|
| 704 |
md += '\n'
|
| 705 |
return md
|
| 706 |
|
|
|
|
| 709 |
# Launch
|
| 710 |
# ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 711 |
if __name__ == "__main__":
|
| 712 |
+
mode_str = "HuggingFace Inference" if USE_HUGGINGFACE else "OpenRouter/OpenAI"
|
| 713 |
+
print(f"\U0001f9e0 Overthinker v24 \u2014 Starting on port {PORT}")
|
| 714 |
+
print(f"\U0001f916 Model: {DEFAULT_MODEL}")
|
| 715 |
+
print(f"\U0001f500 Inference Mode: {mode_str}")
|
| 716 |
+
print(f"\U0001f310 Open http://localhost:{PORT} in your browser")
|
| 717 |
+
|
| 718 |
+
if USE_HUGGINGFACE:
|
| 719 |
+
if not HF_TOKEN:
|
| 720 |
+
print("\u26a0\ufe0f No HF_TOKEN found. Set it in your .env file.")
|
| 721 |
+
elif not HF_AVAILABLE:
|
| 722 |
+
print("\u26a0\ufe0f huggingface_hub not installed. Run: pip install huggingface-hub>=0.23.0")
|
| 723 |
+
else:
|
| 724 |
+
print(f"\u2705 HuggingFace Inference Client ready (token: {HF_TOKEN[:6]}...{HF_TOKEN[-4:]})")
|
| 725 |
+
else:
|
| 726 |
+
if not OPENROUTER_API_KEY and not OPENAI_API_KEY:
|
| 727 |
+
print("\u26a0\ufe0f No API key found. Using local fallback generation (limited).")
|
| 728 |
+
|
| 729 |
+
print(f"\U0001f4da API docs available at http://localhost:{PORT}/docs")
|
| 730 |
app.launch(
|
| 731 |
server_port=PORT,
|
| 732 |
show_error=True,
|