File size: 7,241 Bytes
64dfa83
 
 
a8b2c5c
64dfa83
 
a8b2c5c
a7e01dd
a8b2c5c
 
 
 
 
64dfa83
49dd498
9f61929
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a8b2c5c
64dfa83
 
1b23046
02ee37a
64dfa83
02ee37a
1b23046
 
 
 
 
 
 
64dfa83
1b23046
64dfa83
 
1b23046
64dfa83
1b23046
 
 
64dfa83
 
 
9f61929
 
 
 
 
 
 
 
 
 
64dfa83
9f61929
 
49dd498
9f61929
 
 
64dfa83
 
 
9f61929
1b23046
a8b2c5c
02ee37a
9f61929
a8b2c5c
9f61929
 
 
 
 
 
 
 
 
 
 
 
64dfa83
49dd498
9f61929
1b23046
64dfa83
1b23046
a8b2c5c
 
64dfa83
a8b2c5c
 
 
 
64dfa83
1b23046
 
 
 
 
 
 
64dfa83
 
 
 
 
 
a8b2c5c
1b23046
64dfa83
1b23046
 
 
 
64dfa83
1b23046
 
02ee37a
 
1b23046
02ee37a
1b23046
02ee37a
 
1b23046
 
02ee37a
64dfa83
 
 
9f61929
 
 
 
 
 
 
 
 
 
 
49dd498
9f61929
64dfa83
02ee37a
 
1b23046
02ee37a
64dfa83
 
 
 
 
a8b2c5c
 
 
 
 
02ee37a
de1de04
64dfa83
 
a8b2c5c
64dfa83
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
import gradio as gr
import spaces
import torch
import re
from transformers import pipeline

# Initialize the lightweight LiquidAI Thinking Model
MODEL_ID = "LiquidAI/LFM2.5-1.2B-Thinking" 
generator = pipeline(
    "text-generation", 
    model=MODEL_ID, 
    dtype=torch.bfloat16
)

def generate_with_chat_template(messages, max_new_tokens=1536, do_sample=True, temperature=0.7, num_return_sequences=1):
    """
    Applies the model's chat template to structure the prompts properly, 
    preventing prompt injection or completion confusion.
    """
    tokenizer = generator.tokenizer
    prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
    
    outputs = generator(
        prompt,
        max_new_tokens=max_new_tokens,
        max_length=None,
        generation_config=None,
        num_return_sequences=num_return_sequences,
        do_sample=do_sample,
        temperature=temperature if do_sample else None
    )
    
    results = []
    for out in outputs:
        gen_text = out['generated_text']
        # Extract only the newly generated text following the prompt
        if gen_text.startswith(prompt):
            gen_text = gen_text[len(prompt):]
        results.append(gen_text.strip())
    return results

@spaces.GPU(duration=60)  # 60s is plenty because LiquidAI is blindingly fast
def tot_search(problem: str, branches: int = 3, max_depth: int = 3) -> str:
    """
    Executes a Tree of Thoughts (ToT) search using the LiquidAI 1.2B Thinking model
    and returns ONLY the final clean answer to the client.
    """
    # Track statistics for server-side logs
    stats = {
        "nodes_generated": 0,
        "nodes_evaluated": 0,
        "nodes_pruned": 0,
        "actual_depth_reached": 0
    }
    
    current_paths = [{"history": [problem], "score": 1.0}]
    tree_history_log = []
    
    for depth in range(max_depth):
        stats["actual_depth_reached"] = depth + 1
        new_paths = []
        depth_log = []
        
        for p_idx, path in enumerate(current_paths):
            latest_thought = path["history"][-1]
            
            # 1. GENERATE BRANCHES
            messages = [
                {
                    "role": "system", 
                    "content": "You are a helpful reasoning assistant. Provide a single, distinct, and logical next step to solve the user's problem. Be extremely concise, direct, and focused."
                },
                {
                    "role": "user", 
                    "content": f"Problem: {problem}\nCurrent progress: {latest_thought}\nWhat is the single next logical step?"
                }
            ]
            
            outputs = generate_with_chat_template(
                messages, 
                max_new_tokens=1536, 
                do_sample=True, 
                temperature=0.7, 
                num_return_sequences=branches
            )
            
            # 2. EVALUATE/SCORE BRANCHES
            for out_text in outputs:
                stats["nodes_generated"] += 1
                
                # LiquidAI wraps internal thoughts in <think> tags. Extract clean step.
                next_step = re.sub(r'<think>.*?</think>', '', out_text, flags=re.DOTALL).strip()
                if not next_step:
                    next_step = out_text  # Fallback
                
                eval_messages = [
                    {
                        "role": "system", 
                        "content": "You are an evaluator. Your task is to rate whether a proposed next step is helpful for solving the given problem. You must respond with exactly one of these words: 'Good', 'Maybe', or 'Bad'. Do not explain your choice."
                    },
                    {
                        "role": "user", 
                        "content": f"Problem: {problem}\nProposed Next Step: {next_step}\nIs this step 'Good', 'Maybe', or 'Bad'?"
                    }
                ]
                
                eval_outs = generate_with_chat_template(eval_messages, max_new_tokens=1536, do_sample=False)
                eval_text = eval_outs[0].lower()
                stats["nodes_evaluated"] += 1
                
                # Strip thinking tags from evaluation
                eval_text = re.sub(r'<think>.*?</think>', '', eval_text, flags=re.DOTALL).strip()
                
                score = 0.0
                if "good" in eval_text: 
                    score = 1.0
                elif "maybe" in eval_text: 
                    score = 0.5
                
                depth_log.append({
                    "parent_node": p_idx,
                    "thought": next_step[:120] + "...", 
                    "evaluation": eval_text,
                    "score": score
                })
                
                if score > 0:
                    new_paths.append({
                        "history": path["history"] + [next_step],
                        "score": score
                    })
        
        # 3. PRUNE (Keep top 2 paths)
        original_count = len(new_paths)
        current_paths = sorted(new_paths, key=lambda x: x["score"], reverse=True)[:2]
        stats["nodes_pruned"] += (original_count - len(current_paths))
        
        tree_history_log.append((depth + 1, depth_log))
        
        if not current_paths:
            break

    # Print execution trace to the background Hugging Face Space console logs
    print("\n--- Tree of Thoughts Execution Logs ---")
    for d, logs in tree_history_log:
        print(f"Depth {d}:")
        for l in logs:
            print(f"  - [{l['evaluation'].upper()}] Thought: {l['thought']}")
    print(f"Stats: Depth={stats['actual_depth_reached']}, Generated={stats['nodes_generated']}, Pruned={stats['nodes_pruned']}\n")

    if not current_paths:
        return "Error: All reasoning paths hit a dead end."

    # 4. SYNTHESIZE THE WINNING PATH
    best_chain = " -> ".join(current_paths[0]["history"])
    final_messages = [
        {
            "role": "system", 
            "content": "You are a helpful assistant. Synthesize the final, concise answer to the problem based on the provided reasoning path. Do not include any meta-reasoning, instruction-following placeholders, or thinking tags. Provide only the clean, final direct answer."
        },
        {
            "role": "user", 
            "content": f"Problem: {problem}\nReasoning path: {best_chain}\nWhat is the final concise answer?"
        }
    ]
    
    final_outs = generate_with_chat_template(final_messages, max_new_tokens=1536, do_sample=False)
    final_response = final_outs[0]
    
    # Strip everything down to get just the clean answer
    clean_answer = re.sub(r'<think>.*?</think>', '', final_response, flags=re.DOTALL).strip()
    
    return clean_answer


# Define the Gradio Interface
demo = gr.Interface(
    fn=tot_search,
    inputs=[
        gr.Textbox(label="Problem"), 
        gr.Slider(2, 4, value=3, step=1, label="Branches"), 
        gr.Slider(2, 4, value=3, step=1, label="Max Depth")
    ],
    outputs="text",
    title="DeepThoughTree --- Tree of Thoughts (ToT) Orchestrator"
)

# Launch with MCP enabled
demo.launch(mcp_server=True)