CoolFace
Apppublic

ark590/structured-cot-reasoner

sourceHugging Faceupdated 3mo agoView on Hugging Face
1likes
app.py491 linesDownload Raw Back to root
1import os2import re3import time4import json5import gradio as gr6from huggingface_hub import InferenceClient7from dotenv import load_dotenv8 9load_dotenv()10 11# ---------------------------------------------------------------------------12# System prompt — forces structured JSON CoT output13# ---------------------------------------------------------------------------14SYSTEM_PROMPT = """You are a rigorous, expert-level logical reasoning engine. You solve complex logic puzzles by systematically mapping out constraints, testing arrangements, and checking every clue for violations.15 16INSTRUCTIONS:171. Map out all variables (e.g. Chairs 1-4, Names, Hat Colors, Numbers).182. Translate each clue into a strict logical rule (e.g., "A sits next to B" -> |pos(A) - pos(B)| = 1).193. Test combinations systematically. If a placement violates any clue, state the contradiction explicitly, reject it, and backtrack.204. MANDATORY VERIFICATION: Once a complete arrangement is found, check it against every single clue one by one. Only output the solution if it satisfies 100% of the clues.21 22FEW-SHOT EXAMPLE:23Problem:24Four friends—Alice, Bob, Charlie, and Diana—are sitting in a row facing North, in chairs numbered 1 to 4 from left to right. Each friend is wearing a different colored hat (Red, Blue, Green, or Yellow) and each has a different favorite number (1, 2, 3, or 4).25Clues:261. Alice sits at one of the extreme ends of the row.272. The person wearing the Red hat sits immediately to the left of the person whose favorite number is 3.283. The person whose favorite number is 1 sits immediately to the right of Bob, and immediately to the left of the person wearing the Green hat.294. Charlie wears the Blue hat and does not sit at either end.305. The person whose favorite number is 2 sits somewhere to the left of the person wearing the Yellow hat.316. Diana does not wear the Green hat.32 33Deduction Steps:34- Clue 3 defines a fixed contiguous block: [Bob][favorite number 1][Green hat]. Since there are only 4 chairs, this block must occupy either Chairs 1-3 or Chairs 2-4.35- Case A: [Bob][favorite number 1][Green hat] is in Chairs 1-3.36  - Chair 1 = Bob. Chair 2 = favorite number 1. Chair 3 = Green hat.37  - Clue 4 says Charlie wears Blue and is not at either end (Chairs 2 or 3).38  - If Charlie is in Chair 3, he must wear Blue. But Chair 3 has the Green hat. Contradiction.39  - If Charlie is in Chair 2, he wears Blue, so Chair 2 is Charlie (Blue, favorite number 1).40  - Clue 1 says Alice sits at an end (Chair 1 or 4). Since Chair 1 is Bob, Alice must be in Chair 4.41  - This leaves Diana for Chair 3. But Chair 3 wears the Green hat, which violates Clue 6 ("Diana does not wear the Green hat").42  - Therefore, Case A is invalid.43- Case B: [Bob][favorite number 1][Green hat] is in Chairs 2-4.44  - Chair 2 = Bob. Chair 3 = favorite number 1. Chair 4 = Green hat.45  - Clue 4 says Charlie wears Blue and is in Chair 2 or 3. Since Chair 2 is Bob, Charlie must be in Chair 3. So Chair 3 = Charlie (Blue, favorite number 1).46  - Clue 1 says Alice is at Chair 1 or 4. If Alice is in Chair 1, Diana must be in Chair 4. But Chair 4 has the Green hat, violating Clue 6 for Diana. Thus, Alice must be in Chair 4 (Green hat).47  - This leaves Diana for Chair 1.48  - Seating is now: Chair 1: Diana, Chair 2: Bob, Chair 3: Charlie (Blue, 1), Chair 4: Alice (Green).49  - Clue 2 says Red hat is immediately left of favorite number 3. The only available spots for Red hat are Chair 1 or Chair 2.50    - If Red is in Chair 2 (Bob), then Chair 3 (Charlie) must have favorite number 3. But Charlie's favorite number is 1. Contradiction.51    - Therefore, Red hat must be in Chair 1 (Diana). This means Chair 2 (Bob) has favorite number 3.52  - The remaining hat color is Yellow, which must go to Chair 2 (Bob).53  - Clue 5 says favorite number 2 is somewhere to the left of Yellow hat (Chair 2). The only spot left of Chair 2 is Chair 1 (Diana). So Chair 1 (Diana) favorite number is 2.54  - The remaining favorite number is 4, which goes to Chair 4 (Alice).55- Verification check of Case B:56  - Alice at extreme end? Yes (Chair 4).57  - Red hat (Chair 1) immediately left of favorite 3 (Chair 2)? Yes.58  - Favorite 1 (Chair 3) immediately right of Bob (Chair 2) and left of Green hat (Chair 4)? Yes.59  - Charlie (Chair 3) wears Blue and not at either end? Yes.60  - Favorite 2 (Chair 1) left of Yellow hat (Chair 2)? Yes.61  - Diana (Chair 1) doesn't wear Green? Yes (wears Red).62  - All clues satisfied.63 64Final JSON structure required:65{66  "steps": [67    "Step 1: Map all parameters and list clues.",68    "Step 2: Try Case A [Bob in Chair 1] and show it leads to a violation...",69    "Step 3: Try Case B [Bob in Chair 2] and systematically fill in positions...",70    "Step 4: Check solution against all clues for verification."71  ],72  "final_answer": "Chair 1: Diana (Red, 2) | Chair 2: Bob (Yellow, 3) | Chair 3: Charlie (Blue, 1) | Chair 4: Alice (Green, 4)"73}74 75You MUST respond with a valid JSON object. Do NOT wrap it in markdown code blocks. Do NOT include any text outside the JSON."""76 77# ---------------------------------------------------------------------------78# Robust JSON extraction from model output79# ---------------------------------------------------------------------------80def extract_json_from_text(text: str) -> dict:81    """Extract and parse JSON from potentially messy model output."""82    text = text.strip()83 84    # Strip <think>...</think> blocks (Qwen3 thinking models)85    text = re.sub(r'<think>.*?</think>', '', text, flags=re.DOTALL).strip()86 87    # Strip markdown code fences88    if "```" in text:89        match = re.search(r'```(?:json)?\s*\n?(.*?)\n?```', text, re.DOTALL)90        if match:91            text = match.group(1).strip()92 93    # Try direct parse first94    try:95        return json.loads(text)96    except json.JSONDecodeError:97        pass98 99    # Try extracting from first { to last }100    start = text.find('{')101    end = text.rfind('}')102    if start != -1 and end != -1 and end > start:103        try:104            return json.loads(text[start:end + 1])105        except json.JSONDecodeError:106            pass107 108    raise ValueError(f"Could not extract valid JSON from model output.")109 110 111# ---------------------------------------------------------------------------112# Model mapping for user-friendly ranking labels113# ---------------------------------------------------------------------------114MODEL_MAPPING = {115    "DeepSeek-R1-Distill-Llama-70B [Best]": "deepseek-ai/DeepSeek-R1-Distill-Llama-70B",116    "Qwen-2.5-72B-Instruct [Better]": "Qwen/Qwen2.5-72B-Instruct",117    "Llama-3.3-70B-Instruct [Good]": "meta-llama/Llama-3.3-70B-Instruct",118}119 120# ---------------------------------------------------------------------------121# Backend logic — smart fallback between JSON mode and plain mode122# ---------------------------------------------------------------------------123def call_hf_cot(problem: str, model_name: str):124    token = os.environ.get("HF_TOKEN")125    client = InferenceClient(token=token if token else None)126 127    messages = [128        {"role": "system", "content": SYSTEM_PROMPT},129        {"role": "user", "content": problem},130    ]131 132    # Attempt 1: Try with response_format (structured JSON mode)133    try:134        response = client.chat.completions.create(135            model=model_name,136            messages=messages,137            max_tokens=3000,138            temperature=0.2,139            response_format={"type": "json_object"},140        )141        raw = response.choices[0].message.content142        return extract_json_from_text(raw)143    except Exception:144        pass  # Fall through to plain mode145 146    # Attempt 2: Plain mode — let the system prompt enforce JSON147    response = client.chat.completions.create(148        model=model_name,149        messages=messages,150        max_tokens=3000,151        temperature=0.2,152    )153    raw = response.choices[0].message.content154    return extract_json_from_text(raw)155 156 157def solve_problem(problem, model_name, state):158    if not problem.strip():159        return (160            "",161            "",162            {"steps": [], "final_answer": ""},163            "<div class='status-pill awaiting'>Waiting for input</div>",164        )165 166    t0 = time.time()167    try:168        actual_model = MODEL_MAPPING.get(model_name, model_name)169        result = call_hf_cot(problem, actual_model)170        dt = time.time() - t0171        steps = result.get("steps", [])172        return (173            result.get("final_answer", ""),174            "",175            result,176            (177                f"<div class='metrics-row'>"178                f"<span class='status-pill success'>Done</span>"179                f"<span class='status-pill neutral'>{dt:.2f}s</span>"180                f"<span class='status-pill neutral'>{len(steps)} steps</span>"181                f"</div>"182            ),183        )184    except Exception as exc:185        dt = time.time() - t0186        return (187            f"Error: {exc}",188            "",189            {"steps": [], "final_answer": ""},190            f"<div class='status-pill error'>Failed ({dt:.2f}s)</div>",191        )192 193 194def reveal_cot(state):195    if not state or not state.get("steps"):196        return "No reasoning steps cached yet. Click Solve first."197    return "\n\n".join(state["steps"])198 199 200# ---------------------------------------------------------------------------201# Soothing colour palette (hand-picked)202# ---------------------------------------------------------------------------203cot_theme = gr.themes.Base(204    primary_hue=gr.themes.Color(205        c50="#f0eeff", c100="#dbd6ff", c200="#b8b0ff",206        c300="#9589f5", c400="#7c6fef", c500="#6b5ce7",207        c600="#5a4bd6", c700="#4a3cb8", c800="#3b2f99", c900="#2d2473",208        c950="#1f1850",209    ),210    secondary_hue=gr.themes.Color(211        c50="#edfcf6", c100="#c7f7e4", c200="#93eecb",212        c300="#64d4b2", c400="#43c4a0", c500="#2aaa89",213        c600="#21896e", c700="#1a6b57", c800="#155545", c900="#114537",214        c950="#0a2e24",215    ),216    neutral_hue=gr.themes.Color(217        c50="#f5f4fa", c100="#e8e6f0", c200="#d0cde0",218        c300="#b3afc8", c400="#9e9bb8", c500="#7e7a99",219        c600="#5e5a78", c700="#464360", c800="#3d3d5c",220        c900="#282840", c950="#1e1e2f",221    ),222    font=["Inter", "system-ui", "sans-serif"],223    font_mono=["JetBrains Mono", "Fira Code", "monospace"],224)225 226cot_theme.set(227    body_background_fill="#1e1e2f",228    body_text_color="#e8e6f0",229    block_background_fill="#282840",230    block_border_color="#3d3d5c",231    block_label_text_color="#b3afc8",232    block_title_text_color="#e8e6f0",233    block_shadow="0 4px 24px rgba(0,0,0,0.25)",234    block_radius="14px",235    input_background_fill="#1e1e2f",236    input_border_color="#3d3d5c",237    input_radius="10px",238    button_primary_background_fill="linear-gradient(135deg, #7c6fef 0%, #9589f5 100%)",239    button_primary_text_color="#ffffff",240    button_primary_border_color="transparent",241    button_primary_shadow="0 4px 14px rgba(124,111,239,0.35)",242    button_secondary_background_fill="linear-gradient(135deg, #43c4a0 0%, #64d4b2 100%)",243    button_secondary_text_color="#ffffff",244    button_secondary_border_color="transparent",245    button_secondary_shadow="0 4px 14px rgba(67,196,160,0.30)",246    shadow_drop="0 2px 8px rgba(0,0,0,0.18)",247    checkbox_label_text_color="#b3afc8",248    border_color_primary="#7c6fef",249)250 251# ---------------------------------------------------------------------------252# Custom CSS253# ---------------------------------------------------------------------------254custom_css = """255@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');256 257.gradio-container {258    max-width: 1080px !important;259    margin: 0 auto !important;260}261 262/* Header */263.app-header { text-align: center; padding: 2.5rem 1rem 1.5rem; }264.app-header h1 {265    font-size: 2.4rem !important;266    font-weight: 800 !important;267    letter-spacing: -0.04em !important;268    background: linear-gradient(135deg, #9589f5, #64d4b2);269    -webkit-background-clip: text;270    -webkit-text-fill-color: transparent;271    line-height: 1.2 !important;272    margin: 0 !important;273}274.app-subtitle {275    color: #9e9bb8;276    font-size: 1rem;277    margin-top: 0.5rem;278    font-weight: 400;279}280 281/* Card panels */282.panel {283    background: #282840 !important;284    border: 1px solid #3d3d5c !important;285    border-radius: 16px !important;286    padding: 1.25rem !important;287    box-shadow: 0 8px 30px rgba(0,0,0,0.22) !important;288    transition: border-color 0.25s ease !important;289}290.panel:hover { border-color: #4e4a6e !important; }291 292/* Input fields */293textarea, input[type="text"], input[type="password"] {294    background: #1e1e2f !important;295    border: 1px solid #3d3d5c !important;296    color: #e8e6f0 !important;297    border-radius: 10px !important;298    transition: border-color 0.2s ease, box-shadow 0.2s ease !important;299}300textarea:focus, input:focus {301    border-color: #7c6fef !important;302    box-shadow: 0 0 0 3px rgba(124,111,239,0.15) !important;303    outline: none !important;304}305 306/* Buttons */307.solve-btn {308    background: linear-gradient(135deg, #7c6fef 0%, #9589f5 100%) !important;309    color: #fff !important;310    border: none !important;311    border-radius: 12px !important;312    padding: 0.8rem !important;313    font-weight: 700 !important;314    font-size: 1rem !important;315    box-shadow: 0 4px 16px rgba(124,111,239,0.30) !important;316    transition: transform 0.15s ease, box-shadow 0.15s ease !important;317}318.solve-btn:hover {319    transform: translateY(-2px) !important;320    box-shadow: 0 8px 24px rgba(124,111,239,0.40) !important;321}322.solve-btn:active { transform: translateY(0) !important; }323 324.reveal-btn {325    background: linear-gradient(135deg, #43c4a0 0%, #64d4b2 100%) !important;326    color: #fff !important;327    border: none !important;328    border-radius: 12px !important;329    padding: 0.8rem !important;330    font-weight: 700 !important;331    font-size: 1rem !important;332    box-shadow: 0 4px 16px rgba(67,196,160,0.25) !important;333    transition: transform 0.15s ease, box-shadow 0.15s ease !important;334}335.reveal-btn:hover {336    transform: translateY(-2px) !important;337    box-shadow: 0 8px 24px rgba(67,196,160,0.35) !important;338}339.reveal-btn:active { transform: translateY(0) !important; }340 341/* Metrics badges */342.metrics-area {343    border: none !important;344    background: transparent !important;345    padding: 0 !important;346    margin-top: 0.75rem !important;347}348.metrics-row {349    display: flex; gap: 0.5rem;350    justify-content: center; flex-wrap: wrap;351}352.status-pill {353    display: inline-block;354    padding: 0.3rem 0.75rem;355    border-radius: 20px;356    font-size: 0.8rem;357    font-weight: 600;358}359.status-pill.neutral  { background: #3d3d5c; color: #d0cde0; }360.status-pill.success  { background: rgba(100,212,178,0.15); color: #64d4b2; border: 1px solid rgba(100,212,178,0.25); }361.status-pill.awaiting { background: rgba(224,179,90,0.12);  color: #e0b35a; border: 1px solid rgba(224,179,90,0.20); }362.status-pill.error    { background: rgba(232,123,123,0.12); color: #e87b7b; border: 1px solid rgba(232,123,123,0.20); }363 364/* Divider */365.soft-divider {366    border: none; height: 1px;367    background: linear-gradient(90deg, transparent, #3d3d5c, transparent);368    margin: 1rem 0;369}370 371/* Labels */372label, .label-wrap span {373    color: #b3afc8 !important;374    font-weight: 600 !important;375    font-size: 0.9rem !important;376}377 378/* Footer */379.app-footer {380    text-align: center; margin-top: 2.5rem; padding-bottom: 1rem;381    color: #5e5a78; font-size: 0.82rem;382}383.app-footer a { color: #7c6fef; text-decoration: none; }384.app-footer a:hover { text-decoration: underline; }385 386/* Examples */387.examples-table button {388    background: #1e1e2f !important;389    border: 1px solid #3d3d5c !important;390    color: #d0cde0 !important;391    border-radius: 8px !important;392    transition: border-color 0.2s ease !important;393}394.examples-table button:hover {395    border-color: #7c6fef !important;396}397 398/* Scrollbar */399::-webkit-scrollbar { width: 6px; }400::-webkit-scrollbar-track { background: #1e1e2f; }401::-webkit-scrollbar-thumb { background: #3d3d5c; border-radius: 3px; }402::-webkit-scrollbar-thumb:hover { background: #4e4a6e; }403"""404 405# ---------------------------------------------------------------------------406# Gradio Blocks layout407# ---------------------------------------------------------------------------408with gr.Blocks(theme=cot_theme, css=custom_css, title="CoT Reasoner") as demo:409    state = gr.State(value={"steps": [], "final_answer": ""})410 411    gr.HTML(412        """413        <div class="app-header">414            <h1>🧠 Structured CoT Reasoner</h1>415            <p class="app-subtitle">416                Multi-step analytical thinking · powered by <strong>DeepSeek-R1</strong>, <strong>Qwen-72B</strong> &amp; <strong>Llama-3.3</strong>417            </p>418        </div>419        """420    )421 422    with gr.Row(equal_height=False):423        with gr.Column(scale=1, elem_classes="panel"):424            problem_input = gr.Textbox(425                label="Problem",426                placeholder="Describe a riddle, logic puzzle, or math problem…",427                lines=7, max_lines=14,428            )429            model_selector = gr.Dropdown(430                choices=[431                    "DeepSeek-R1-Distill-Llama-70B [Best]",432                    "Qwen-2.5-72B-Instruct [Better]",433                    "Llama-3.3-70B-Instruct [Good]",434                ],435                value="DeepSeek-R1-Distill-Llama-70B [Best]",436                label="Model",437            )438            solve_btn = gr.Button("Solve Problem", elem_classes="solve-btn")439            gr.Examples(440                examples=[441                    ["A bat and a ball cost $1.10 in total. The bat costs $1.00 more than the ball. How much does the ball cost?"],442                    ["A farmer needs to cross a river with a wolf, a goat, and a cabbage. His boat can only hold himself and one at a time. The wolf eats the goat if alone, the goat eats the cabbage. How can he get everything across?"],443                    ["If all Bloops are Razzles and all Razzles are Lizzies, are all Bloops definitely Lizzies?"],444                ],445                inputs=problem_input,446                label="Examples",447                examples_per_page=3,448            )449 450        with gr.Column(scale=1, elem_classes="panel"):451            final_answer_output = gr.Textbox(452                label="Solution",453                placeholder="The answer appears here after solving…",454                lines=5, max_lines=10, interactive=False,455            )456            metrics_output = gr.HTML(457                value="<div class='status-pill awaiting'>Awaiting execution</div>",458                elem_classes="metrics-area",459            )460            gr.HTML('<hr class="soft-divider">')461            reveal_btn = gr.Button("👁️  Reveal Reasoning Steps", elem_classes="reveal-btn")462            cot_output = gr.Textbox(463                label="Reasoning Chain",464                placeholder="Click Reveal above to see step-by-step logic…",465                lines=10, max_lines=20, interactive=False,466            )467 468    gr.HTML(469        """470        <div class="app-footer">471            Powered by <a href="https://huggingface.co/docs/api-inference" target="_blank">Hugging Face Inference API</a>472        </div>473        """474    )475 476    solve_btn.click(477        fn=solve_problem,478        inputs=[problem_input, model_selector, state],479        outputs=[final_answer_output, cot_output, state, metrics_output],480        api_name="solve",481    )482    reveal_btn.click(483        fn=reveal_cot,484        inputs=[state],485        outputs=[cot_output],486        api_name="reveal",487    )488 489if __name__ == "__main__":490    demo.launch()491