| import os |
| import re |
| import json |
| import gradio as gr |
| from typing import List, Dict, Any, Generator |
| import requests |
| from datetime import datetime |
| import ast |
| import operator as op |
| import wikipedia |
| from transformers import AutoTokenizer, AutoModelForCausalLM |
| import torch |
|
|
| class Tool: |
| def __init__(self, name: str, description: str, func): |
| self.name = name |
| self.description = description |
| self.func = func |
| |
| def __call__(self, *args, **kwargs): |
| return self.func(*args, **kwargs) |
|
|
| def duckduckgo_search(query: str) -> str: |
| try: |
| url = "https://api.duckduckgo.com/" |
| params = {'q': query, 'format': 'json', 'no_html': 1, 'skip_disambig': 1} |
| response = requests.get(url, params=params, timeout=10) |
| data = response.json() |
| |
| if data.get('Abstract'): |
| return f"Search result: {data['Abstract']}" |
| elif data.get('RelatedTopics') and len(data['RelatedTopics']) > 0: |
| results = [topic['Text'] for topic in data['RelatedTopics'][:3] if 'Text' in topic] |
| return f"Search results: {' | '.join(results)}" if results else "No results found." |
| return "No results found." |
| except Exception as e: |
| return f"Search error: {str(e)}" |
|
|
| def wikipedia_search(query: str) -> str: |
| try: |
| wikipedia.set_lang("en") |
| summary = wikipedia.summary(query, sentences=3, auto_suggest=True) |
| return f"Wikipedia: {summary}" |
| except wikipedia.exceptions.DisambiguationError as e: |
| return f"Wikipedia: Multiple results found. Options: {', '.join(e.options[:5])}" |
| except wikipedia.exceptions.PageError: |
| return f"Wikipedia: No page found for '{query}'." |
| except Exception as e: |
| return f"Wikipedia error: {str(e)}" |
|
|
| def get_weather(location: str) -> str: |
| try: |
| url = f"https://wttr.in/{location}?format=j1" |
| response = requests.get(url, timeout=10) |
| data = response.json() |
| current = data['current_condition'][0] |
| return f"Weather in {location}: {current['weatherDesc'][0]['value']}, {current['temp_C']}Β°C, Humidity: {current['humidity']}%" |
| except Exception as e: |
| return f"Weather error: {str(e)}" |
|
|
| def calculate(expression: str) -> str: |
| operators = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul, ast.Div: op.truediv, ast.Pow: op.pow, ast.USub: op.neg, ast.Mod: op.mod} |
| |
| def eval_expr(node): |
| if isinstance(node, ast.Num): |
| return node.n |
| elif isinstance(node, ast.BinOp): |
| return operators[type(node.op)](eval_expr(node.left), eval_expr(node.right)) |
| elif isinstance(node, ast.UnaryOp): |
| return operators[type(node.op)](eval_expr(node.operand)) |
| raise TypeError(node) |
| |
| try: |
| result = eval_expr(ast.parse(expression.strip(), mode='eval').body) |
| return f"Result: {result}" |
| except Exception as e: |
| return f"Calculation error: {str(e)}" |
|
|
| def python_repl(code: str) -> str: |
| try: |
| safe_builtins = {'abs': abs, 'round': round, 'min': min, 'max': max, 'sum': sum, 'len': len, 'range': range, 'list': list, 'dict': dict, 'str': str, 'int': int, 'float': float, 'print': print} |
| namespace = {'__builtins__': safe_builtins} |
| |
| from io import StringIO |
| import sys |
| old_stdout = sys.stdout |
| sys.stdout = StringIO() |
| exec(code, namespace) |
| output = sys.stdout.getvalue() |
| sys.stdout = old_stdout |
| |
| result_vars = {k: v for k, v in namespace.items() if k != '__builtins__' and not k.startswith('_')} |
| return f"Python output: {output if output else (str(result_vars) if result_vars else 'Code executed')}" |
| except Exception as e: |
| return f"Python error: {str(e)}" |
|
|
| TOOLS = [ |
| Tool("duckduckgo_search", "Search the web. Input: search query.", duckduckgo_search), |
| Tool("wikipedia_search", "Search Wikipedia. Input: search query.", wikipedia_search), |
| Tool("get_weather", "Get weather for location. Input: city name.", get_weather), |
| Tool("calculate", "Calculate math expression. Input: expression.", calculate), |
| Tool("python_repl", "Execute Python code. Input: code.", python_repl), |
| ] |
|
|
| MODEL_NAME = "openai/gpt-oss-20b" |
| model = None |
| tokenizer = None |
| model_loaded = False |
|
|
| def download_and_load_model(progress=gr.Progress()): |
| global model, tokenizer, model_loaded |
| |
| try: |
| progress(0, desc="Downloading tokenizer...") |
| tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True) |
| |
| progress(0.4, desc="Downloading model (this may take several minutes)...") |
| model = AutoModelForCausalLM.from_pretrained( |
| MODEL_NAME, |
| trust_remote_code=True, |
| torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32, |
| device_map="auto", |
| low_cpu_mem_usage=True, |
| ) |
| |
| progress(0.95, desc="Finalizing...") |
| model_loaded = True |
| progress(1.0, desc="Model loaded!") |
| return f"Model '{MODEL_NAME}' loaded successfully!" |
| except Exception as e: |
| return f"Error: {str(e)}" |
|
|
| def get_tool_descriptions() -> str: |
| return "\n".join([f"- {tool.name}: {tool.description}" for tool in TOOLS]) |
|
|
| THINK_ONLY_PROMPT = """You are an expert problem solver. Use your knowledge and reasoning to answer questions. |
| |
| You must show your complete reasoning process using this format: |
| Thought: [Explain what you're thinking and why] |
| Thought: [Continue your reasoning, breaking down the problem] |
| Thought: [Build toward the solution step by step] |
| Answer: [Your final, complete answer] |
| |
| Important: |
| - Show multiple thought steps |
| - Break down complex problems |
| - Explain your reasoning clearly |
| - Only provide the Answer when you're certain |
| |
| Question: {question} |
| |
| Let me think through this step by step: |
| |
| Thought:""" |
|
|
| ACT_ONLY_PROMPT = """You are an AI agent with access to external tools. You MUST use tools to find information. |
| |
| Available tools: |
| {tools} |
| |
| You MUST respond ONLY with actions - no thinking out loud: |
| Action: [exact tool name] |
| Action Input: [specific input for the tool] |
| |
| After receiving the Observation, you can: |
| - Call another tool if you need more information |
| - Provide the final Answer when you have enough information |
| |
| Format: |
| Action: tool_name |
| Action Input: input_string |
| |
| Then after observation: |
| Action: another_tool |
| Action Input: another_input |
| |
| OR |
| Answer: [final answer based on observations] |
| |
| Question: {question} |
| |
| Action:""" |
|
|
| REACT_PROMPT = """You are an expert AI agent that combines reasoning with tool usage (ReAct paradigm). |
| |
| Available tools: |
| {tools} |
| |
| You MUST alternate between thinking and acting: |
| |
| 1. Thought: [Reason about what information you need and which tool to use] |
| 2. Action: [exact tool name] |
| 3. Action Input: [specific input] |
| 4. Observation: [tool result - will be provided to you] |
| 5. Thought: [Analyze the observation and decide next steps] |
| 6. Repeat 2-5 until you have enough information |
| 7. Thought: [Final reasoning with all gathered information] |
| 8. Answer: [Complete final answer] |
| |
| Rules: |
| - ALWAYS start with a Thought explaining your strategy |
| - After each Observation, think about what you learned |
| - Use multiple tools if needed |
| - Only give Answer when you have sufficient information |
| - Be specific in your Action Inputs |
| |
| Question: {question} |
| |
| Thought:""" |
|
|
| def parse_action(text: str) -> tuple: |
| action_match = re.search(r'Action:\s*(\w+)', text, re.IGNORECASE) |
| input_match = re.search(r'Action Input:\s*(.+?)(?=\n(?:Thought:|Action:|Answer:|$))', text, re.IGNORECASE | re.DOTALL) |
| return (action_match.group(1).strip(), input_match.group(1).strip()) if action_match and input_match else (None, None) |
|
|
| def call_tool(tool_name: str, tool_input: str) -> str: |
| for tool in TOOLS: |
| if tool.name.lower() == tool_name.lower(): |
| return tool(tool_input) |
| return f"Error: Tool '{tool_name}' not found." |
|
|
| def call_llm(prompt: str, max_tokens: int = 500) -> str: |
| if not model_loaded: |
| return "Error: Model not loaded." |
| |
| try: |
| inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=2048) |
| |
| if torch.cuda.is_available(): |
| inputs = {k: v.to(model.device) for k, v in inputs.items()} |
| |
| with torch.no_grad(): |
| outputs = model.generate( |
| **inputs, |
| max_new_tokens=max_tokens, |
| temperature=0.7, |
| do_sample=True, |
| top_p=0.9, |
| pad_token_id=tokenizer.eos_token_id, |
| eos_token_id=tokenizer.eos_token_id, |
| ) |
| |
| response = tokenizer.decode(outputs[0][inputs['input_ids'].shape[1]:], skip_special_tokens=True) |
| return response.strip() |
| except Exception as e: |
| return f"Error during generation: {str(e)}" |
|
|
| def think_only_mode(question: str) -> Generator[str, None, None]: |
| if not model_loaded: |
| yield "β **Error: Model not loaded. Click 'Download & Load Model' first.**\n\n" |
| return |
| |
| yield "π§ **Mode: Think-Only (Chain-of-Thought)**\n\n" |
| yield "π Generating reasoning steps...\n\n" |
| |
| response = call_llm(THINK_ONLY_PROMPT.format(question=question), max_tokens=800) |
| |
| if response.startswith("Error"): |
| yield f"β {response}\n\n" |
| return |
| |
| for line in response.split('\n'): |
| if line.strip(): |
| if line.strip().startswith('Thought:'): |
| yield f"π **{line.strip()}**\n\n" |
| elif line.strip().startswith('Answer:'): |
| yield f"β
**{line.strip()}**\n\n" |
| else: |
| yield f"{line}\n\n" |
| |
| yield "\n---\nβ **Completed**\n" |
|
|
| def act_only_mode(question: str, max_iterations: int = 5) -> Generator[str, None, None]: |
| if not model_loaded: |
| yield "β **Error: Model not loaded. Click 'Download & Load Model' first.**\n\n" |
| return |
| |
| yield "π§ **Mode: Act-Only (Tool Use Only)**\n\n" |
| conversation = ACT_ONLY_PROMPT.format(question=question, tools=get_tool_descriptions()) |
| |
| for iteration in range(max_iterations): |
| yield f"π **Iteration {iteration + 1}**\n\n" |
| |
| response = call_llm(conversation, max_tokens=300) |
| |
| if response.startswith("Error"): |
| yield f"β {response}\n\n" |
| return |
| |
| if 'Answer:' in response: |
| match = re.search(r'Answer:\s*(.+)', response, re.IGNORECASE | re.DOTALL) |
| if match: |
| yield f"β
**Answer:** {match.group(1).strip()}\n\n" |
| break |
| |
| action_name, action_input = parse_action(response) |
| if action_name and action_input: |
| yield f"π§ **Action:** `{action_name}`\n" |
| yield f"π **Action Input:** {action_input}\n\n" |
| |
| yield f"β³ Executing tool...\n\n" |
| observation = call_tool(action_name, action_input) |
| |
| yield f"ποΈ **Observation:** {observation}\n\n" |
| conversation += f"\n{response}\nObservation: {observation}\n\n" |
| else: |
| yield f"β οΈ No valid action found. Response: {response}\n\n" |
| break |
| |
| yield "\n---\nβ **Completed**\n" |
|
|
| def react_mode(question: str, max_iterations: int = 5) -> Generator[str, None, None]: |
| if not model_loaded: |
| yield "β **Error: Model not loaded. Click 'Download & Load Model' first.**\n\n" |
| return |
| |
| yield "π€ **Mode: ReAct (Reasoning + Acting)**\n\n" |
| conversation = REACT_PROMPT.format(question=question, tools=get_tool_descriptions()) |
| |
| for iteration in range(max_iterations): |
| yield f"π **Step {iteration + 1}**\n\n" |
| |
| response = call_llm(conversation, max_tokens=400) |
| |
| if response.startswith("Error"): |
| yield f"β {response}\n\n" |
| return |
| |
| |
| for thought in re.findall(r'Thought:\s*(.+?)(?=\n(?:Action:|Answer:|$))', response, re.IGNORECASE | re.DOTALL): |
| yield f"π **Thought:** {thought.strip()}\n\n" |
| |
| |
| if 'Answer:' in response: |
| match = re.search(r'Answer:\s*(.+)', response, re.IGNORECASE | re.DOTALL) |
| if match: |
| yield f"β
**Answer:** {match.group(1).strip()}\n\n" |
| break |
| |
| |
| action_name, action_input = parse_action(response) |
| if action_name and action_input: |
| yield f"π§ **Action:** `{action_name}`\n" |
| yield f"π **Action Input:** {action_input}\n\n" |
| |
| yield f"β³ Executing tool...\n\n" |
| observation = call_tool(action_name, action_input) |
| |
| yield f"ποΈ **Observation:** {observation}\n\n" |
| conversation += f"\n{response}\nObservation: {observation}\n\nThought:" |
| else: |
| if 'Answer:' not in response: |
| yield f"β οΈ No action found. Response: {response}\n\n" |
| break |
| |
| yield "\n---\nβ **Completed**\n" |
|
|
| EXAMPLES = [ |
| "What is 25 * 47?", |
| "What is the weather in Paris?", |
| "Who wrote 1984?", |
| "Calculate: 100 + 200", |
| ] |
|
|
| def run_comparison(question: str, mode: str): |
| """Run selected mode with real-time streaming.""" |
| if not question.strip(): |
| yield "Please enter a question.", "", "" |
| return |
| |
| if mode == "Think-Only": |
| think_out = "" |
| for chunk in think_only_mode(question): |
| think_out += chunk |
| yield think_out, "", "" |
| |
| elif mode == "Act-Only": |
| act_out = "" |
| for chunk in act_only_mode(question): |
| act_out += chunk |
| yield "", act_out, "" |
| |
| elif mode == "ReAct": |
| react_out = "" |
| for chunk in react_mode(question): |
| react_out += chunk |
| yield "", "", react_out |
| |
| else: |
| yield "Invalid mode selected.", "", "" |
|
|
| with gr.Blocks(title="LLM Reasoning Modes") as demo: |
| gr.Markdown("# LLM Reasoning Modes Comparison\n\n**Model:** openai/gpt-oss-20b\n\n**Tools:** DuckDuckGo | Wikipedia | Weather | Calculator | Python") |
| |
| with gr.Row(): |
| download_btn = gr.Button("Download & Load Model", variant="primary", size="lg") |
| model_status = gr.Textbox(label="Status", value="Click to download", interactive=False) |
| |
| with gr.Row(): |
| with gr.Column(scale=3): |
| question_input = gr.Textbox(label="Question", lines=3) |
| mode_dropdown = gr.Dropdown(choices=["Think-Only", "Act-Only", "ReAct"], value="ReAct", label="Mode") |
| submit_btn = gr.Button("Run", variant="primary", size="lg") |
| with gr.Column(scale=1): |
| gr.Markdown("**Examples**") |
| for idx, ex in enumerate(EXAMPLES): |
| gr.Button(f"Ex {idx+1}", size="sm").click(fn=lambda e=ex: e, outputs=question_input) |
| |
| gr.Markdown("---") |
| |
| with gr.Row(): |
| think_output = gr.Markdown(label="Think-Only") |
| act_output = gr.Markdown(label="Act-Only") |
| react_output = gr.Markdown(label="ReAct") |
| |
| download_btn.click(fn=download_and_load_model, outputs=model_status) |
| submit_btn.click(fn=run_comparison, inputs=[question_input, mode_dropdown], outputs=[think_output, act_output, react_output]) |
|
|
| if __name__ == "__main__": |
| demo.launch(share=True) |