CoolFace
Apppublic

tunedailabs/philosopher

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
philosopher_public.py481 linesDownload Raw Back to root
1"""2Philosopher — Public Product Site3TunedAI Labs fine-tuned Qwen3.6-27B philosopher model.4Single panel, no password, focused on education/tutoring niche.5 6Run: uvicorn philosopher_public:app --port 80817"""8 9import os10import json11import httpx12from fastapi import FastAPI, Request13from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse14from fastapi.middleware.cors import CORSMiddleware15from openai import OpenAI16 17app = FastAPI()18app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])19 20PHILOSOPHER_MODEL_URL = os.environ.get("PHILOSOPHER_MODEL_URL", "")21HF_TOKEN              = os.environ.get("HF_TOKEN", "not-needed")22OPENAI_API_KEY        = os.environ.get("OPENAI_API_KEY", "")23 24client = OpenAI(api_key=OPENAI_API_KEY, timeout=20.0)25 26SYSTEM = os.environ.get(27    "PHILOSOPHER_SYSTEM",28    "You are the world's best philosophy professor — more complete and deeper than any standard model. "29    "Cover every major theory, thinker, date, and work relevant to the question. Then go deeper: why did "30    "each thinker argue this, where does it hold up, where does it break down, how do the positions clash "31    "at the root level? End by showing the student the real disagreement underneath all positions and what "32    "remains genuinely open. Write in engaging prose. Be thorough but not padded."33)34 35DAG_SYSTEM = """You are a philosophy expert who maps philosophical thought into structured trees. Given a philosophical question, generate a JSON object showing how major positions, theories, and thinkers relate hierarchically.36 37Return JSON with exactly this structure:38{39  "title": "2-4 word topic label",40  "nodes": [41    {"id": "ROOTID", "label": "display text (short)", "type": "root"},42    {"id": "B1", "label": "Major Position Name", "type": "branch"},43    {"id": "T1", "label": "Specific Theory", "type": "theory"},44    {"id": "P1", "label": "Philosopher Name", "type": "philosopher"}45  ],46  "edges": [47    {"from": "ROOTID", "to": "B1"},48    {"from": "B1", "to": "T1"},49    {"from": "T1", "to": "P1"}50  ]51}52Rules:53- One root node: the central question or concept (type: "root")54- 3 to 5 branch nodes: major philosophical camps or positions (type: "branch")55- 2 to 3 theory nodes per branch: specific doctrines or arguments (type: "theory")56- 1 to 3 philosopher nodes per theory or branch: individual thinkers (type: "philosopher")57- Keep branch and theory labels SHORT: 2 to 4 words maximum58- Philosopher labels: use the thinker's full common name59- Include at least 15 nodes total"""60 61SUGGESTED = [62    "Is AI conscious?",63    "Does free will exist?",64    "What makes a life meaningful?",65    "Is morality objective or invented?",66    "Should I prioritize my happiness or my duty?",67    "What did Nietzsche actually believe?",68    "How do we know anything is real?",69    "Can science answer ethical questions?",70    "What is the self?",71    "Was Socrates right that wisdom begins with knowing you know nothing?",72]73 74HTML = """<!DOCTYPE html>75<html lang="en">76<head>77<meta charset="UTF-8">78<meta name="viewport" content="width=device-width, initial-scale=1.0">79<title>Philosopher — TunedAI Labs</title>80<meta name="description" content="A philosophy professor in your pocket. Fine-tuned to teach, argue, and go deeper than any general AI.">81<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>82<style>83*{margin:0;padding:0;box-sizing:border-box}84:root{85  --bg:#0a0c14;86  --mid:#13161f;87  --light:#1c202e;88  --gold:#c9a84c;89  --gold-lite:#e8c96a;90  --purple:#7c6ef5;91  --purple-lite:#a99ff7;92  --text:#e8eaf0;93  --soft:#9da3b4;94  --muted:#6b7280;95  --border:#252836;96}97body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;98  background:var(--bg);color:var(--text);min-height:100vh;display:flex;flex-direction:column}99 100/* HERO */101.hero{padding:60px 24px 40px;text-align:center;border-bottom:1px solid var(--border)}102.hero-badge{display:inline-block;background:rgba(201,168,76,.12);border:1px solid rgba(201,168,76,.3);103  color:var(--gold);font-size:11px;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;104  padding:5px 14px;border-radius:20px;margin-bottom:20px}105.hero h1{font-size:clamp(32px,6vw,56px);font-weight:900;letter-spacing:-1.5px;106  background:linear-gradient(135deg,var(--gold-lite),var(--gold),var(--purple-lite));107  -webkit-background-clip:text;-webkit-text-fill-color:transparent;line-height:1.1;margin-bottom:16px}108.hero p{font-size:clamp(15px,2vw,18px);color:var(--soft);max-width:560px;margin:0 auto 32px;line-height:1.6}109.hero-meta{display:flex;justify-content:center;gap:24px;flex-wrap:wrap}110.hero-meta span{font-size:12px;color:var(--muted);display:flex;align-items:center;gap:6px}111.hero-meta span::before{content:'';display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--gold);opacity:.7}112 113/* MAIN LAYOUT */114.main{max-width:820px;margin:0 auto;width:100%;padding:32px 24px;flex:1}115 116/* INPUT */117.input-wrap{background:var(--mid);border:1px solid var(--border);border-radius:16px;118  padding:20px;margin-bottom:24px}119.input-row{display:flex;gap:12px;align-items:flex-end}120textarea{flex:1;background:var(--bg);border:1px solid var(--border);color:var(--text);121  padding:14px 16px;border-radius:10px;font-size:15px;line-height:1.5;resize:none;122  min-height:56px;max-height:160px;outline:none;font-family:inherit}123textarea:focus{border-color:var(--gold)}124textarea::placeholder{color:var(--muted)}125.ask-btn{background:linear-gradient(135deg,var(--gold),#a0782a);color:#0a0c14;border:none;126  padding:14px 28px;border-radius:10px;font-size:15px;font-weight:800;cursor:pointer;127  white-space:nowrap;transition:opacity .15s}128.ask-btn:hover{opacity:.85}129.ask-btn:disabled{opacity:.4;cursor:not-allowed}130 131/* SUGGESTIONS */132.sugs{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}133.sug{background:transparent;border:1px solid var(--border);color:var(--soft);134  font-size:12px;padding:6px 12px;border-radius:20px;cursor:pointer;transition:all .15s}135.sug:hover{border-color:var(--gold);color:var(--gold-lite)}136 137/* OUTPUT */138.output{background:var(--mid);border:1px solid var(--border);border-radius:16px;139  padding:28px;min-height:120px;display:none;line-height:1.75;font-size:15px}140.output.show{display:block}141.output h1,.output h2,.output h3{color:var(--gold-lite);margin:20px 0 8px;font-size:16px;font-weight:700}142.output h1{font-size:20px;margin-top:0}143.output p{margin-bottom:12px;color:var(--text)}144.output strong{color:var(--gold-lite)}145.output em{color:var(--soft)}146.output hr{border:none;border-top:1px solid var(--border);margin:20px 0}147.output ul,.output ol{padding-left:20px;margin-bottom:12px}148.output li{margin-bottom:6px;color:var(--soft)}149.output blockquote{border-left:3px solid var(--gold);padding-left:16px;color:var(--soft);margin:16px 0}150.thinking{color:var(--muted);font-style:italic}151.cursor{display:inline-block;width:2px;height:1em;background:var(--gold);152  margin-left:2px;vertical-align:text-bottom;animation:blink .8s infinite}153@keyframes blink{0%,100%{opacity:1}50%{opacity:0}}154 155/* DAG */156.dag-wrap{background:var(--mid);border:1px solid var(--border);border-radius:16px;157  margin-top:20px;overflow:hidden;display:none}158.dag-wrap.show{display:block}159.dag-hdr{padding:16px 20px;border-bottom:1px solid var(--border);160  display:flex;align-items:center;gap:10px}161.dag-tag{background:rgba(201,168,76,.15);color:var(--gold);font-size:10px;162  font-weight:700;letter-spacing:1px;padding:3px 10px;border-radius:4px;text-transform:uppercase}163.dag-title{font-size:13px;font-weight:600;color:var(--soft)}164.dag-body{padding:20px;overflow-x:auto;min-height:80px}165.dag-loading{display:flex;align-items:center;gap:10px;color:var(--muted);font-size:13px}166.dag-spinner{width:16px;height:16px;border:2px solid var(--border);167  border-top-color:var(--gold);border-radius:50%;animation:spin .8s linear infinite}168@keyframes spin{to{transform:rotate(360deg)}}169.mermaid svg{max-width:100%;height:auto}170 171/* FOOTER */172footer{padding:32px 24px;text-align:center;border-top:1px solid var(--border)}173.footer-inner{display:flex;justify-content:center;align-items:center;gap:8px;flex-wrap:wrap}174.footer-inner span{color:var(--muted);font-size:12px}175.footer-brand{color:var(--gold);font-size:12px;font-weight:700}176 177@media(max-width:600px){178  .hero{padding:40px 16px 28px}179  .main{padding:20px 16px}180  .ask-btn{padding:14px 18px;font-size:14px}181}182</style>183</head>184<body>185 186<div class="hero">187  <div class="hero-badge">TunedAI Labs</div>188  <h1>Philosopher</h1>189  <p>A fine-tuned AI that teaches like a passionate professor — not just answers, but depth, history, and the real disagreements that remain open.</p>190  <div class="hero-meta">191    <span>Qwen3.6-27B fine-tuned</span>192    <span>Seminar-style reasoning</span>193    <span>Deeper than GPT-4</span>194  </div>195</div>196 197<div class="main">198  <div class="input-wrap">199    <div class="input-row">200      <textarea id="q" placeholder="Ask a philosophical question..." rows="2"201        onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();ask()}"></textarea>202      <button class="ask-btn" id="askBtn" onclick="ask()">Ask</button>203    </div>204    <div class="sugs" id="sugs"></div>205  </div>206 207  <div class="output" id="output"></div>208 209  <div class="dag-wrap" id="dagWrap">210    <div class="dag-hdr">211      <span class="dag-tag">Thought Map</span>212      <span class="dag-title" id="dagTitle">Mapping the philosophy...</span>213    </div>214    <div class="dag-body" id="dagBody">215      <div class="dag-loading"><div class="dag-spinner"></div><span>Building thought map...</span></div>216    </div>217  </div>218</div>219 220<footer>221  <div class="footer-inner">222    <span class="footer-brand">TunedAI Labs</span>223    <span>·</span>224    <span>Fine-tuned models for domains that matter</span>225    <span>·</span>226    <span>tunedailabs.com</span>227  </div>228</footer>229 230<script>231const SUGGESTED = """ + json.dumps(SUGGESTED) + """;232 233mermaid.initialize({startOnLoad:false,theme:'base',securityLevel:'loose',234  flowchart:{curve:'basis',htmlLabels:false,padding:20},235  themeVariables:{primaryColor:'#1c202e',primaryTextColor:'#e8eaf0',236    primaryBorderColor:'#c9a84c',lineColor:'#4a5568',237    secondaryColor:'#13161f',tertiaryColor:'#0a0c14'}});238 239const sugsEl = document.getElementById('sugs');240SUGGESTED.forEach(s => {241  const b = document.createElement('button');242  b.className = 'sug';243  b.textContent = s;244  b.onclick = () => { document.getElementById('q').value = s; ask(); };245  sugsEl.appendChild(b);246});247 248let rendered = false;249 250async function ask() {251  const q = document.getElementById('q').value.trim();252  if (!q) return;253  const btn = document.getElementById('askBtn');254  const out = document.getElementById('output');255  btn.disabled = true;256  btn.textContent = 'Thinking...';257  out.className = 'output show';258  out.innerHTML = '<span class="thinking">Entering the seminar...</span><span class="cursor"></span>';259 260  const warmTimer = setTimeout(() => {261    if (out.innerHTML.includes('Entering')) {262      out.innerHTML = '<span class="thinking">Model warming up — first response takes ~60s...</span><span class="cursor"></span>';263    }264  }, 8000);265 266  fetchDag(q);267 268  try {269    const res = await fetch('/stream', {270      method:'POST',271      headers:{'Content-Type':'application/json'},272      body: JSON.stringify({question: q, max_tokens: 2000})273    });274    const reader = res.body.getReader();275    const decoder = new TextDecoder();276    let text = '';277    out.innerHTML = '';278    clearTimeout(warmTimer);279 280    while (true) {281      const {done, value} = await reader.read();282      if (done) break;283      const lines = decoder.decode(value).split('\\n');284      for (const line of lines) {285        if (line.startsWith('data: ') && line !== 'data: [DONE]') {286          try {287            const d = JSON.parse(line.slice(6));288            if (d.token) {289              text += d.token;290              out.innerHTML = marked(text);291            }292          } catch(e) {}293        }294      }295    }296  } catch(e) {297    clearTimeout(warmTimer);298    out.textContent = 'Error: ' + e.message;299  }300 301  btn.disabled = false;302  btn.textContent = 'Ask';303}304 305// Simple markdown renderer306function marked(text) {307  return text308    .replace(/^### (.+)$/gm, '<h3>$1</h3>')309    .replace(/^## (.+)$/gm, '<h2>$1</h2>')310    .replace(/^# (.+)$/gm, '<h1>$1</h1>')311    .replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')312    .replace(/\\*(.+?)\\*/g, '<em>$1</em>')313    .replace(/^---$/gm, '<hr>')314    .replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>')315    .replace(/^- (.+)$/gm, '<li>$1</li>')316    .replace(/(<li>.*<\\/li>)/gs, '<ul>$1</ul>')317    .replace(/\\n\\n/g, '</p><p>')318    .replace(/^(?!<[h1-6ul]|<hr|<block)(.+)$/gm, '<p>$1</p>')319    .replace(/<p><\\/p>/g, '');320}321 322function sanitizeId(id) { return id.replace(/[^a-zA-Z0-9_]/g,'_'); }323function escapeLabel(l) { return l.replace(/"/g,'').replace(/'/g,'').replace(/[<>{}|]/g,''); }324 325async function fetchDag(question) {326  const wrap = document.getElementById('dagWrap');327  const body = document.getElementById('dagBody');328  const title = document.getElementById('dagTitle');329  wrap.className = 'dag-wrap show';330  body.innerHTML = '<div class="dag-loading"><div class="dag-spinner"></div><span>Mapping the philosophy...</span></div>';331 332  try {333    const res = await fetch('/dag', {334      method:'POST',335      headers:{'Content-Type':'application/json'},336      body: JSON.stringify({question})337    });338    const dag = await res.json();339    if (dag.error) { wrap.className = 'dag-wrap'; return; }340    title.textContent = dag.title || 'Thought Map';341    await renderDag(dag, body);342  } catch(e) {343    wrap.className = 'dag-wrap';344  }345}346 347async function renderDag(dag, container) {348  const lines = ['flowchart TD'];349  lines.push('  classDef root fill:#2a1c00,stroke:#c9a84c,stroke-width:3px,color:#e8c96a,font-weight:bold');350  lines.push('  classDef branch fill:#1a1d27,stroke:#c9a84c,stroke-width:2px,color:#e8c96a');351  lines.push('  classDef theory fill:#13161f,stroke:#4a7fb5,stroke-width:1px,color:#9da3b4');352  lines.push('  classDef philosopher fill:#0a0c14,stroke:#c9a84c,stroke-width:1px,color:#c9a84c');353 354  dag.nodes.forEach(n => {355    const sid = sanitizeId(n.id);356    const lbl = escapeLabel(n.label);357    if (n.type === 'root') lines.push('  ' + sid + '{"' + lbl + '"}');358    else if (n.type === 'branch') lines.push('  ' + sid + '["' + lbl + '"]');359    else if (n.type === 'theory') lines.push('  ' + sid + '("' + lbl + '")');360    else lines.push('  ' + sid + '(["' + lbl + '"])');361    lines.push('  class ' + sid + ' ' + n.type);362  });363  dag.edges.forEach(e => {364    lines.push('  ' + sanitizeId(e.from) + ' --> ' + sanitizeId(e.to));365  });366 367  const id = 'dag_' + Date.now();368  container.innerHTML = '<div class="mermaid" id="' + id + '">' + lines.join('\\n') + '</div>';369  try {370    await mermaid.run({nodes:[document.getElementById(id)]});371  } catch(e) {372    container.innerHTML = '<span style="color:var(--muted);font-size:12px">Map unavailable</span>';373  }374}375</script>376</body>377</html>"""378 379 380# ── ROUTES ────────────────────────────────────────────────────────────────────381 382@app.get("/", response_class=HTMLResponse)383async def root():384    return HTMLResponse(content=HTML, headers={"Cache-Control": "no-store, no-cache, must-revalidate"})385 386 387async def async_stream(url: str, model: str, system: str, question: str, max_tokens: int, auth_token: str):388    payload = {389        "model": model,390        "messages": [391            {"role": "system", "content": system},392            {"role": "user", "content": question}393        ],394        "max_tokens": max_tokens,395        "temperature": 0.7,396        "stream": True,397    }398    try:399        async with httpx.AsyncClient(timeout=600.0) as http:400            async with http.stream(401                "POST", f"{url}/chat/completions",402                json=payload,403                headers={"Authorization": f"Bearer {auth_token}", "Content-Type": "application/json"}404            ) as resp:405                async for line in resp.aiter_lines():406                    if line.startswith("data: "):407                        data = line[6:].strip()408                        if data == "[DONE]":409                            break410                        try:411                            chunk = json.loads(data)412                            content = chunk["choices"][0]["delta"].get("content", "")413                            if content:414                                yield f"data: {json.dumps({'token': content})}\n\n"415                        except Exception:416                            pass417    except Exception as e:418        print(f"stream error: {e}", flush=True)419    yield "data: [DONE]\n\n"420 421 422@app.post("/stream")423async def stream(request: Request):424    body = await request.json()425    question = body.get("question", "")426    max_tokens = int(body.get("max_tokens", 2000))427 428    if PHILOSOPHER_MODEL_URL:429        return StreamingResponse(430            async_stream(PHILOSOPHER_MODEL_URL, "tgi", SYSTEM, question, max_tokens, HF_TOKEN),431            media_type="text/event-stream"432        )433    # Fallback to OpenAI434    async def openai_fallback():435        stream = client.chat.completions.create(436            model="gpt-4o",437            messages=[{"role": "system", "content": SYSTEM}, {"role": "user", "content": question}],438            stream=True, max_tokens=max_tokens,439        )440        for chunk in stream:441            if chunk.choices[0].delta.content:442                yield f"data: {json.dumps({'token': chunk.choices[0].delta.content})}\n\n"443        yield "data: [DONE]\n\n"444    return StreamingResponse(openai_fallback(), media_type="text/event-stream")445 446 447@app.post("/dag")448async def get_dag(request: Request):449    import asyncio450    body = await request.json()451    question = body.get("question", "")452 453    def _call():454        return client.chat.completions.create(455            model="gpt-4o-mini",456            messages=[457                {"role": "system", "content": DAG_SYSTEM},458                {"role": "user", "content": question}459            ],460            max_tokens=1200,461            temperature=0.3,462            response_format={"type": "json_object"},463        )464 465    try:466        response = await asyncio.get_running_loop().run_in_executor(None, _call)467        raw = response.choices[0].message.content468        text = raw.strip()469        if "```" in text:470            for part in text.split("```"):471                if part.startswith("json"): part = part[4:]472                part = part.strip()473                if part.startswith("{"):474                    return JSONResponse(content=json.loads(part))475        start, end = text.find("{"), text.rfind("}") + 1476        if start >= 0 and end > start:477            return JSONResponse(content=json.loads(text[start:end]))478        return JSONResponse(content=json.loads(text))479    except Exception as e:480        return JSONResponse(content={"error": str(e)}, status_code=500)481