hyperlinken/ALT_DESIGN
0
1"""2app.py — IT Helpdesk Triage OpenEnv — Alternative Premium Design3Cyberpunk / terminal aesthetic with green-on-dark, scanlines, typewriter effects.4"""5from __future__ import annotations6import logging, os7import uvicorn8from fastapi import FastAPI, HTTPException9from fastapi.middleware.cors import CORSMiddleware10from fastapi.responses import HTMLResponse11from pydantic import BaseModel12 13from environment import ITTriageEnvironment14from models import EnvironmentState, Observation, StepResult, TriageAction15 16logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")17logger = logging.getLogger(__name__)18 19app = FastAPI(20 title="IT Helpdesk Triage & Incident Management — OpenEnv",21 description="Production-grade OpenEnv RL environment for enterprise IT service desk simulation.",22 version="1.0.0", docs_url="/docs", redoc_url="/redoc",23)24app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])25env = ITTriageEnvironment()26 27class ResetRequest(BaseModel):28 task_id: str = "basic_triage"29 30LANDING_HTML = r"""<!DOCTYPE html>31<html lang="en">32<head>33<meta charset="UTF-8">34<meta name="viewport" content="width=device-width, initial-scale=1.0">35<title>IT Helpdesk Triage — OpenEnv</title>36<style>37@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Space+Mono:wght@400;700&display=swap');38 39:root{40 --bg:#020408;41 --surface:#040c14;42 --card:#071220;43 --border:#0d2235;44 --border2:#1a3a55;45 --cyan:#00d4ff;46 --cyan2:#00a8cc;47 --green:#00ff9d;48 --green2:#00cc7a;49 --orange:#ff6b35;50 --yellow:#ffd700;51 --red:#ff3355;52 --text:#e8f4f8;53 --muted:#4a7a94;54 --muted2:#7ab3cc;55 --mono:'Space Mono',monospace;56 --sans:'Space Grotesk',system-ui,sans-serif;57}58 59*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}60html{scroll-behavior:smooth}61body{background:var(--bg);color:var(--text);font-family:var(--sans);overflow-x:hidden;line-height:1.6}62 63/* GRID BACKGROUND */64body::before{65 content:'';position:fixed;inset:0;66 background-image:67 linear-gradient(rgba(0,212,255,0.03) 1px,transparent 1px),68 linear-gradient(90deg,rgba(0,212,255,0.03) 1px,transparent 1px);69 background-size:40px 40px;70 pointer-events:none;z-index:0;71}72 73/* SCANLINES */74body::after{75 content:'';position:fixed;inset:0;76 background:repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(0,0,0,0.08) 2px,rgba(0,0,0,0.08) 4px);77 pointer-events:none;z-index:1;78}79 80/* ── NAV ── */81nav{82 position:fixed;top:0;left:0;right:0;z-index:100;83 display:flex;align-items:center;justify-content:space-between;84 padding:1rem 2.5rem;85 background:rgba(2,4,8,0.85);86 border-bottom:1px solid var(--border);87 backdrop-filter:blur(16px);88}89.nav-logo{90 font-family:var(--mono);font-size:0.85rem;font-weight:700;91 color:var(--cyan);letter-spacing:0.05em;92 display:flex;align-items:center;gap:0.75rem;93}94.nav-logo-icon{95 width:32px;height:32px;border:1px solid var(--cyan);96 border-radius:6px;display:flex;align-items:center;justify-content:center;97 font-size:1rem;color:var(--cyan);98 box-shadow:0 0 12px rgba(0,212,255,0.3);99}100.nav-links{display:flex;gap:0.5rem}101.nav-link{102 font-family:var(--mono);font-size:0.72rem;font-weight:700;103 padding:0.4rem 0.9rem;border:1px solid var(--border2);104 border-radius:5px;color:var(--muted2);text-decoration:none;105 letter-spacing:0.05em;transition:all 0.2s;106}107.nav-link:hover{border-color:var(--cyan);color:var(--cyan);box-shadow:0 0 12px rgba(0,212,255,0.2)}108.nav-link.primary{border-color:var(--cyan);color:var(--cyan);background:rgba(0,212,255,0.08)}109.nav-link.primary:hover{background:rgba(0,212,255,0.18)}110 111/* ── HERO ── */112.hero{113 min-height:100vh;display:flex;flex-direction:column;114 align-items:center;justify-content:center;115 padding:8rem 2rem 6rem;text-align:center;116 position:relative;z-index:2;117}118 119/* corner decorations */120.corner{position:absolute;width:60px;height:60px;opacity:0.4}121.corner-tl{top:120px;left:40px;border-top:2px solid var(--cyan);border-left:2px solid var(--cyan)}122.corner-tr{top:120px;right:40px;border-top:2px solid var(--cyan);border-right:2px solid var(--cyan)}123.corner-bl{bottom:60px;left:40px;border-bottom:2px solid var(--cyan);border-left:2px solid var(--cyan)}124.corner-br{bottom:60px;right:40px;border-bottom:2px solid var(--cyan);border-right:2px solid var(--cyan)}125 126.sys-label{127 font-family:var(--mono);font-size:0.7rem;color:var(--cyan);128 letter-spacing:0.2em;text-transform:uppercase;129 margin-bottom:1.5rem;opacity:0.8;130 display:flex;align-items:center;justify-content:center;gap:0.75rem;131}132.sys-label::before,.sys-label::after{content:'//';opacity:0.5}133 134.hero h1{135 font-size:clamp(2.8rem,7vw,5.5rem);136 font-weight:700;letter-spacing:-0.03em;137 line-height:1.05;margin-bottom:1rem;138}139.hero h1 .w1{color:var(--text)}140.hero h1 .w2{141 color:var(--cyan);142 text-shadow:0 0 40px rgba(0,212,255,0.5),0 0 80px rgba(0,212,255,0.2);143}144.hero h1 .w3{color:var(--text)}145.hero h1 .w4{146 color:var(--green);147 text-shadow:0 0 40px rgba(0,255,157,0.4);148}149 150.hero-sub{151 font-size:1rem;color:var(--muted2);max-width:560px;152 margin:1.5rem auto 3rem;line-height:1.75;153}154 155/* TERMINAL PROMPT */156.prompt-bar{157 display:inline-flex;align-items:center;gap:0.5rem;158 font-family:var(--mono);font-size:0.8rem;159 background:rgba(0,212,255,0.06);160 border:1px solid var(--border2);161 border-radius:8px;padding:0.6rem 1.2rem;162 margin-bottom:2.5rem;color:var(--muted2);163}164.prompt-bar .ps{color:var(--green)}165.prompt-bar .cmd{color:var(--cyan)}166.cursor{167 display:inline-block;width:8px;height:14px;168 background:var(--cyan);margin-left:2px;169 animation:blink 1s infinite;vertical-align:middle;170}171@keyframes blink{0%,100%{opacity:1}50%{opacity:0}}172 173.hero-btns{display:flex;gap:1rem;justify-content:center;flex-wrap:wrap;margin-bottom:4rem}174.btn-cyber{175 font-family:var(--mono);font-size:0.8rem;font-weight:700;176 letter-spacing:0.08em;padding:0.8rem 1.8rem;177 border-radius:6px;text-decoration:none;178 transition:all 0.2s;position:relative;overflow:hidden;179}180.btn-cyber-primary{181 background:transparent;color:var(--cyan);182 border:1px solid var(--cyan);183 box-shadow:0 0 20px rgba(0,212,255,0.2),inset 0 0 20px rgba(0,212,255,0.05);184}185.btn-cyber-primary:hover{186 background:rgba(0,212,255,0.12);187 box-shadow:0 0 30px rgba(0,212,255,0.4),inset 0 0 30px rgba(0,212,255,0.1);188}189.btn-cyber-green{190 background:transparent;color:var(--green);191 border:1px solid var(--green2);192 box-shadow:0 0 20px rgba(0,255,157,0.15);193}194.btn-cyber-green:hover{195 background:rgba(0,255,157,0.1);196 box-shadow:0 0 30px rgba(0,255,157,0.35);197}198.btn-cyber-ghost{199 background:transparent;color:var(--muted2);200 border:1px solid var(--border2);201}202.btn-cyber-ghost:hover{border-color:var(--muted2);color:var(--text)}203 204/* ── METRICS ROW ── */205.metrics-row{206 display:grid;207 grid-template-columns:repeat(5,1fr);208 gap:1px;background:var(--border);209 border:1px solid var(--border);border-radius:12px;210 overflow:hidden;width:100%;max-width:900px;211}212.metric{background:var(--card);padding:1.5rem 1rem;text-align:center}213.metric-val{214 font-family:var(--mono);font-size:2rem;font-weight:700;215 color:var(--cyan);216 text-shadow:0 0 20px rgba(0,212,255,0.5);217 display:block;line-height:1.1;218}219.metric-label{font-size:0.7rem;color:var(--muted);margin-top:0.3rem;letter-spacing:0.05em;font-family:var(--mono)}220 221/* ── SECTIONS ── */222section{position:relative;z-index:2;padding:6rem 2rem}223.container{max-width:1100px;margin:0 auto}224.section-eyebrow{225 font-family:var(--mono);font-size:0.68rem;font-weight:700;226 letter-spacing:0.18em;color:var(--cyan);text-transform:uppercase;227 margin-bottom:0.75rem;228 display:flex;align-items:center;gap:0.75rem;229}230.section-eyebrow::after{content:'';flex:1;height:1px;background:linear-gradient(90deg,var(--border2),transparent)}231h2{font-size:1.9rem;font-weight:700;letter-spacing:-0.02em;margin-bottom:0.5rem}232.section-sub{color:var(--muted2);margin-bottom:3rem;font-size:0.95rem}233 234/* SECTION DIVIDER */235.divider{236 height:1px;237 background:linear-gradient(90deg,transparent,var(--border2) 30%,var(--border2) 70%,transparent);238 position:relative;z-index:2;239}240 241/* ── TASK PANELS ── */242.tasks-col{display:flex;flex-direction:column;gap:1.5rem}243.task-panel{244 background:var(--card);245 border:1px solid var(--border);246 border-radius:12px;247 padding:2rem;248 display:grid;grid-template-columns:auto 1fr auto;249 gap:1.5rem;align-items:start;250 transition:border-color 0.25s,box-shadow 0.25s;251 cursor:default;252}253.task-panel:hover{border-color:var(--cyan);box-shadow:0 0 30px rgba(0,212,255,0.08)}254.task-panel.medium:hover{border-color:var(--yellow);box-shadow:0 0 30px rgba(255,215,0,0.08)}255.task-panel.hard:hover{border-color:var(--red);box-shadow:0 0 30px rgba(255,51,85,0.08)}256 257.task-num{258 font-family:var(--mono);font-size:2.5rem;font-weight:700;259 line-height:1;padding-top:0.2rem;260}261.easy .task-num{color:var(--green);text-shadow:0 0 20px rgba(0,255,157,0.4)}262.medium .task-num{color:var(--yellow);text-shadow:0 0 20px rgba(255,215,0,0.4)}263.hard .task-num{color:var(--red);text-shadow:0 0 20px rgba(255,51,85,0.4)}264 265.task-body h3{font-size:1.1rem;font-weight:700;margin-bottom:0.5rem}266.task-body p{font-size:0.875rem;color:var(--muted2);line-height:1.65}267 268.task-chips{display:flex;flex-direction:column;gap:0.5rem;min-width:120px}269.chip{270 font-family:var(--mono);font-size:0.68rem;font-weight:700;271 letter-spacing:0.05em;padding:0.35rem 0.7rem;272 border-radius:5px;text-align:center;white-space:nowrap;273}274.chip-diff.easy {background:rgba(0,255,157,0.1);border:1px solid rgba(0,255,157,0.3);color:var(--green)}275.chip-diff.medium{background:rgba(255,215,0,0.1);border:1px solid rgba(255,215,0,0.3);color:var(--yellow)}276.chip-diff.hard {background:rgba(255,51,85,0.1);border:1px solid rgba(255,51,85,0.3);color:var(--red)}277.chip-info{background:rgba(0,212,255,0.06);border:1px solid var(--border2);color:var(--muted2)}278 279/* ── REWARD ── */280.reward-section{background:var(--surface)}281.reward-grid{282 display:grid;283 grid-template-columns:repeat(3,1fr);284 gap:1px;background:var(--border);285 border:1px solid var(--border);border-radius:12px;overflow:hidden;286 margin-bottom:2rem;287}288@media(max-width:768px){.reward-grid{grid-template-columns:1fr 1fr}}289.rdim{290 background:var(--card);padding:1.75rem 1.5rem;291 position:relative;overflow:hidden;292}293.rdim::before{294 content:'';position:absolute;top:0;left:0;right:0;height:2px;295 background:linear-gradient(90deg,var(--cyan),transparent);296}297.rdim:nth-child(2)::before{background:linear-gradient(90deg,var(--green),transparent)}298.rdim:nth-child(3)::before{background:linear-gradient(90deg,var(--yellow),transparent)}299.rdim:nth-child(4)::before{background:linear-gradient(90deg,var(--cyan2),transparent)}300.rdim:nth-child(5)::before{background:linear-gradient(90deg,var(--orange),transparent)}301.rdim:nth-child(6)::before{background:linear-gradient(90deg,var(--red),transparent)}302 303.rdim-pct{304 font-family:var(--mono);font-size:2.2rem;font-weight:700;305 color:var(--cyan);line-height:1.1;margin-bottom:0.5rem;306}307.rdim:nth-child(2) .rdim-pct{color:var(--green)}308.rdim:nth-child(3) .rdim-pct{color:var(--yellow)}309.rdim:nth-child(4) .rdim-pct{color:var(--cyan2)}310.rdim:nth-child(5) .rdim-pct{color:var(--orange)}311.rdim:nth-child(6) .rdim-pct{color:var(--red)}312 313.rdim-name{font-size:0.9rem;font-weight:700;margin-bottom:0.3rem}314.rdim-sub{font-size:0.75rem;color:var(--muted);line-height:1.4}315 316/* ── TERMINAL ── */317.terminal-section{background:var(--bg)}318.terminal-layout{display:grid;grid-template-columns:1fr 1fr;gap:2rem}319@media(max-width:768px){.terminal-layout{grid-template-columns:1fr}}320 321.term{322 border-radius:10px;overflow:hidden;323 border:1px solid var(--border);324 box-shadow:0 0 40px rgba(0,212,255,0.06);325}326.term-bar{327 display:flex;align-items:center;gap:0.5rem;328 padding:0.7rem 1rem;329 background:var(--surface);330 border-bottom:1px solid var(--border);331}332.term-dots{display:flex;gap:5px}333.term-dots span{width:10px;height:10px;border-radius:50%}334.term-dots span:nth-child(1){background:#ff5f56}335.term-dots span:nth-child(2){background:#ffbd2d}336.term-dots span:nth-child(3){background:#27c93f}337.term-title{font-family:var(--mono);font-size:0.72rem;color:var(--muted);margin-left:6px}338.term-status{339 margin-left:auto;font-family:var(--mono);font-size:0.65rem;340 padding:0.15rem 0.5rem;border-radius:4px;341}342.status-ok{background:rgba(0,255,157,0.1);border:1px solid rgba(0,255,157,0.3);color:var(--green)}343 344.term-body{345 background:#020608;346 padding:1.25rem 1.5rem;347 font-family:var(--mono);font-size:0.78rem;348 line-height:2;overflow-x:auto;min-height:340px;349}350.p1{color:var(--green)} /* prompt */351.p2{color:var(--cyan)} /* command */352.p3{color:var(--muted2)} /* comment */353.p4{color:var(--yellow)} /* string value */354.p5{color:var(--orange)} /* key */355.p6{color:var(--red)} /* error/warn */356.ln{color:var(--muted);margin-right:1rem;user-select:none}357 358/* ── ENDPOINTS ── */359.ep-list{display:flex;flex-direction:column;gap:3px}360.ep{361 display:grid;grid-template-columns:60px 170px 1fr;362 gap:1.25rem;align-items:center;363 padding:1rem 1.25rem;364 background:var(--card);border:1px solid var(--border);365 border-radius:8px;transition:all 0.2s;366}367.ep:hover{border-color:var(--border2);background:rgba(0,212,255,0.03)}368.meth{369 font-family:var(--mono);font-size:0.68rem;font-weight:700;370 padding:0.25rem 0.5rem;border-radius:4px;text-align:center;letter-spacing:0.05em;371}372.mget {background:rgba(0,255,157,0.1);border:1px solid rgba(0,255,157,0.25);color:var(--green)}373.mpost{background:rgba(0,212,255,0.1);border:1px solid rgba(0,212,255,0.25);color:var(--cyan)}374.ep-path{font-family:var(--mono);font-size:0.88rem;color:var(--cyan)}375.ep-desc{font-size:0.82rem;color:var(--muted2)}376 377/* ── PIPELINE ── */378.pipeline{379 display:flex;align-items:center;gap:0;380 overflow-x:auto;padding-bottom:1rem;381}382.pipe-step{383 background:var(--card);border:1px solid var(--border);384 border-radius:10px;padding:1.5rem 1.25rem;385 text-align:center;min-width:160px;flex-shrink:0;386 transition:border-color 0.2s,box-shadow 0.2s;387}388.pipe-step:hover{border-color:var(--cyan);box-shadow:0 0 20px rgba(0,212,255,0.15)}389.pipe-icon{font-size:1.6rem;margin-bottom:0.75rem;display:block}390.pipe-step h4{font-size:0.85rem;font-weight:700;margin-bottom:0.4rem;font-family:var(--mono)}391.pipe-step p{font-size:0.75rem;color:var(--muted2);line-height:1.5}392.pipe-arrow{393 font-family:var(--mono);color:var(--border2);394 font-size:1.5rem;padding:0 0.5rem;flex-shrink:0;395}396 397/* ── FOOTER ── */398footer{399 z-index:2;position:relative;400 border-top:1px solid var(--border);401 padding:3rem 2rem;402 background:var(--surface);403}404.footer-inner{405 max-width:1100px;margin:0 auto;406 display:flex;justify-content:space-between;align-items:center;407 flex-wrap:wrap;gap:1rem;408}409.footer-brand{font-family:var(--mono);font-size:0.8rem;color:var(--muted2)}410.footer-brand strong{color:var(--cyan)}411.footer-links{display:flex;gap:1.5rem}412.footer-links a{font-family:var(--mono);font-size:0.75rem;color:var(--muted);text-decoration:none;transition:color 0.2s}413.footer-links a:hover{color:var(--cyan)}414 415/* scrollbar */416::-webkit-scrollbar{width:4px;height:4px}417::-webkit-scrollbar-track{background:var(--bg)}418::-webkit-scrollbar-thumb{background:var(--border2);border-radius:2px}419 420@media(max-width:600px){421 .metrics-row{grid-template-columns:repeat(3,1fr)}422 .task-panel{grid-template-columns:1fr;gap:1rem}423 .task-chips{flex-direction:row;flex-wrap:wrap}424 nav{padding:0.75rem 1.25rem}425 .nav-links .nav-link:not(.primary){display:none}426}427</style>428</head>429<body>430 431<!-- NAV -->432<nav>433 <div class="nav-logo">434 <div class="nav-logo-icon">🎫</div>435 IT_TRIAGE_ENV436 </div>437 <div class="nav-links">438 <a class="nav-link" href="/tasks">TASKS</a>439 <a class="nav-link" href="/health">HEALTH</a>440 <a class="nav-link primary" href="/docs">API_DOCS</a>441 </div>442</nav>443 444<!-- HERO -->445<div class="hero">446 <div class="corner corner-tl"></div>447 <div class="corner corner-tr"></div>448 <div class="corner corner-bl"></div>449 <div class="corner corner-br"></div>450 451 <div class="sys-label">OpenEnv Environment v1.0.0</div>452 453 <h1>454 <span class="w1">IT Helpdesk </span><span class="w2">Triage</span><br>455 <span class="w3">& Incident </span><span class="w4">Management</span>456 </h1>457 458 <p class="hero-sub">459 Production-grade RL environment where AI agents triage enterprise IT tickets —460 classifying, routing, escalating, and detecting cascading major incidents461 with dense multi-dimensional reward shaping.462 </p>463 464 <div class="prompt-bar">465 <span class="ps">agent@openenv:~$</span>466 <span class="cmd">curl -X POST /reset -d '{"task_id":"incident_escalation"}'</span>467 <span class="cursor"></span>468 </div>469 470 <div class="hero-btns">471 <a class="btn-cyber btn-cyber-primary" href="/docs">▶ LAUNCH API DOCS</a>472 <a class="btn-cyber btn-cyber-green" href="/reset" onclick="event.preventDefault();fetch('/reset',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({task_id:'basic_triage'})}).then(r=>r.json()).then(d=>alert('✅ Reset OK\nTicket: '+d.current_ticket.id+'\n'+d.current_ticket.subject))">⚡ TEST RESET LIVE</a>473 <a class="btn-cyber btn-cyber-ghost" href="/health">◉ HEALTH CHECK</a>474 </div>475 476 <div class="metrics-row">477 <div class="metric"><span class="metric-val">3</span><span class="metric-label">TASKS</span></div>478 <div class="metric"><span class="metric-val">17</span><span class="metric-label">TICKETS</span></div>479 <div class="metric"><span class="metric-val">6</span><span class="metric-label">REWARD DIMS</span></div>480 <div class="metric"><span class="metric-val">35</span><span class="metric-label">MAX STEPS</span></div>481 <div class="metric"><span class="metric-val">1.0</span><span class="metric-label">MAX SCORE</span></div>482 </div>483</div>484 485<div class="divider"></div>486 487<!-- TASKS -->488<section>489 <div class="container">490 <div class="section-eyebrow">Task Registry</div>491 <h2>Three Difficulty Tiers</h2>492 <p class="section-sub">Each task exposes progressively deeper reasoning requirements — from basic ITIL classification to live production crisis management.</p>493 494 <div class="tasks-col">495 496 <div class="task-panel easy">497 <div class="task-num">01</div>498 <div class="task-body">499 <h3>Basic Triage</h3>500 <p>Classify 5 standalone IT tickets: a jammed printer, a locked Salesforce account, 45-user Wi-Fi outage, a live phishing attempt, and a broken Excel macro. Agent learns to map real-world descriptions to ITIL categories and P1–P4 priorities.</p>501 </div>502 <div class="task-chips">503 <span class="chip chip-diff easy">EASY</span>504 <span class="chip chip-info">5 tickets</span>505 <span class="chip chip-info">10 steps</span>506 <span class="chip chip-info">3 grader dims</span>507 </div>508 </div>509 510 <div class="task-panel medium">511 <div class="task-num">02</div>512 <div class="task-body">513 <h3>Priority Routing</h3>514 <p>5 high-stakes enterprise tickets arriving simultaneously: SAP payroll degrading with bank ACH cut-off at risk (1,200 staff), e-commerce 503s at $800/min loss, VPN dropping for 15 remote engineers, and a SIEM alert showing possible lateral movement in the customer DB.</p>515 </div>516 <div class="task-chips">517 <span class="chip chip-diff medium">MEDIUM</span>518 <span class="chip chip-info">5 tickets</span>519 <span class="chip chip-info">10 steps</span>520 <span class="chip chip-info">5 grader dims</span>521 </div>522 </div>523 524 <div class="task-panel hard">525 <div class="task-num">03</div>526 <div class="task-body">527 <h3>Incident Escalation</h3>528 <p>PostgreSQL WAL-corruption takes down db-prod-primary at 14:32. 7 tickets flood in: auth failures, $2,400/min checkout collapse, frozen Metabase dashboards, exec report failures — plus 2 noise tickets (a cert renewal and a lunch reminder). Agent must identify the 5 linked tickets, declare INC-MAJOR-01, and provide ordered failover remediation steps for each P1.</p>529 </div>530 <div class="task-chips">531 <span class="chip chip-diff hard">HARD</span>532 <span class="chip chip-info">7 tickets</span>533 <span class="chip chip-info">15 steps</span>534 <span class="chip chip-info">6 grader dims</span>535 </div>536 </div>537 538 </div>539 </div>540</section>541 542<div class="divider"></div>543 544<!-- REWARD -->545<section class="reward-section">546 <div class="container">547 <div class="section-eyebrow">Reward Function</div>548 <h2>Dense. Shaped. 6 Dimensions.</h2>549 <p class="section-sub">Every step returns partial credit across independent axes — no sparse end-of-episode binary signal.</p>550 551 <div class="reward-grid">552 <div class="rdim">553 <div class="rdim-pct">40%</div>554 <div class="rdim-name">Category</div>555 <div class="rdim-sub">ITIL classification — hardware, software, network, security, access, database, performance, other</div>556 </div>557 <div class="rdim">558 <div class="rdim-pct">35%</div>559 <div class="rdim-name">Priority</div>560 <div class="rdim-sub">P1–P4 assignment with partial credit for ±1 level miss — agents learn from near-misses</div>561 </div>562 <div class="rdim">563 <div class="rdim-pct">25%</div>564 <div class="rdim-name">Team Routing</div>565 <div class="rdim-sub">6 specialist teams: infrastructure, app_support, network_ops, security_ops, dba, helpdesk</div>566 </div>567 <div class="rdim">568 <div class="rdim-pct">20%</div>569 <div class="rdim-name">Incident Link</div>570 <div class="rdim-sub">Medium + Hard only. Partial for correct boolean, full for correct incident ID declaration</div>571 </div>572 <div class="rdim">573 <div class="rdim-pct">14%</div>574 <div class="rdim-name">Escalation</div>575 <div class="rdim-sub">Binary escalation flag with penalty (−0.15) for over-escalating P4 low-priority tickets</div>576 </div>577 <div class="rdim">578 <div class="rdim-pct">16%</div>579 <div class="rdim-name">Remediation</div>580 <div class="rdim-sub">Hard only. NLP-scored on keyword recall (35%) + ordered step matching (65%)</div>581 </div>582 </div>583 </div>584</section>585 586<div class="divider"></div>587 588<!-- TERMINAL DEMO -->589<section class="terminal-section">590 <div class="container">591 <div class="section-eyebrow">Live Demo</div>592 <h2>See It In Action</h2>593 <p class="section-sub">Standard curl commands. Works with any HTTP client or agent framework.</p>594 595 <div class="terminal-layout">596 <div class="term">597 <div class="term-bar">598 <div class="term-dots"><span></span><span></span><span></span></div>599 <span class="term-title">bash — agent session</span>600 </div>601 <div class="term-body">602<span class="p3"># Step 1 — reset for hardest task</span>603<span class="p1">$</span> <span class="p2">curl</span> -X POST \604 https://hyperlinken-triage.hf.space/reset \605 -d <span class="p4">'{"task_id":"incident_escalation"}'</span>606 607<span class="p3"># Step 2 — triage the DB ticket</span>608<span class="p1">$</span> <span class="p2">curl</span> -X POST \609 https://hyperlinken-triage.hf.space/step \610 -d <span class="p4">'{611 "ticket_id": "TKT-H001",612 "category": "database",613 "priority": "P1",614 "assigned_team": "database_admin",615 "is_part_of_incident": true,616 "incident_id": "INC-MAJOR-01",617 "escalate_to_management": true,618 "resolution_steps": [619 "Promote db-prod-replica-01 using pg_promote()",620 "Update all app DB_HOST configs",621 "Open bridge call with DBA team"622 ]623}'</span>624 625<span class="p3"># Step 3 — check accumulated score</span>626<span class="p1">$</span> <span class="p2">curl</span> https://hyperlinken-triage.hf.space/state627 </div>628 </div>629 630 <div class="term">631 <div class="term-bar">632 <div class="term-dots"><span></span><span></span><span></span></div>633 <span class="term-title">response — POST /step</span>634 <span class="term-status status-ok">200 OK</span>635 </div>636 <div class="term-body">637{638 <span class="p5">"observation"</span>: {639 <span class="p5">"task_id"</span>: <span class="p4">"incident_escalation"</span>,640 <span class="p5">"current_ticket"</span>: {641 <span class="p5">"id"</span>: <span class="p4">"TKT-H002"</span>,642 <span class="p5">"subject"</span>: <span class="p4">"Auth service 500s — all logins failing"</span>,643 <span class="p5">"sla_hours"</span>: <span class="p4">1</span>644 },645 <span class="p5">"queue_remaining"</span>: <span class="p4">5</span>,646 <span class="p5">"cumulative_score"</span>: <span class="p4">0.9800</span>,647 <span class="p5">"active_incidents"</span>: [<span class="p4">"INC-MAJOR-01"</span>],648 <span class="p5">"action_feedback"</span>: <span class="p4">"category=correct(database)649 priority=correct(P1)650 team=correct(database_admin)651 incident=correct(INC-MAJOR-01)652 escalation=correct653 reward=0.980"</span>654 },655 <span class="p5">"reward"</span>: <span class="p4">0.9800</span>,656 <span class="p5">"done"</span>: <span class="p6">false</span>,657 <span class="p5">"info"</span>: {658 <span class="p5">"reward_breakdown"</span>: {659 <span class="p5">"category_score"</span>: <span class="p4">1.0</span>,660 <span class="p5">"priority_score"</span>: <span class="p4">1.0</span>,661 <span class="p5">"routing_score"</span>: <span class="p4">1.0</span>,662 <span class="p5">"incident_score"</span>: <span class="p4">1.0</span>,663 <span class="p5">"resolution_score"</span>: <span class="p4">0.88</span>664 }665 }666}667 </div>668 </div>669 </div>670 </div>671</section>672 673<div class="divider"></div>674 675<!-- ENDPOINTS -->676<section>677 <div class="container">678 <div class="section-eyebrow">API Reference</div>679 <h2>OpenEnv Spec Endpoints</h2>680 <p class="section-sub">Typed Pydantic models. Full Swagger docs at <code style="color:var(--cyan)">/docs</code>.</p>681 682 <div class="ep-list">683 <div class="ep"><span class="meth mget">GET</span><span class="ep-path">/health</span><span class="ep-desc">JSON health check — automated validation gate, returns 200 + task metadata</span></div>684 <div class="ep"><span class="meth mpost">POST</span><span class="ep-path">/reset</span><span class="ep-desc">Start new episode — body: <code>{"task_id": "basic_triage"}</code> — returns typed <code>Observation</code></span></div>685 <div class="ep"><span class="meth mpost">POST</span><span class="ep-path">/step</span><span class="ep-desc">Submit <code>TriageAction</code> — returns <code>(Observation, reward∈[0,1], done, info{reward_breakdown})</code></span></div>686 <div class="ep"><span class="meth mget">GET</span><span class="ep-path">/state</span><span class="ep-desc">Full <code>EnvironmentState</code> — action history, reward history, declared incidents, episode progress</span></div>687 <div class="ep"><span class="meth mget">GET</span><span class="ep-path">/tasks</span><span class="ep-desc">All 3 tasks with difficulty, ticket count, max_steps, and per-dimension grader weights</span></div>688 <div class="ep"><span class="meth mget">GET</span><span class="ep-path">/docs</span><span class="ep-desc">Interactive Swagger UI — try every endpoint live with real payloads in browser</span></div>689 </div>690 </div>691</section>692 693<div class="divider"></div>694 695<!-- PIPELINE -->696<section style="background:var(--surface)">697 <div class="container">698 <div class="section-eyebrow">RL Loop</div>699 <h2>The Agent Pipeline</h2>700 <p class="section-sub">Compatible with TRL GRPO, SkyRL, Unsloth, and any OpenAI-client-based agent.</p>701 702 <div class="pipeline">703 <div class="pipe-step">704 <span class="pipe-icon">🔄</span>705 <h4>POST /reset</h4>706 <p>Choose task_id, get first Observation</p>707 </div>708 <div class="pipe-arrow">→</div>709 <div class="pipe-step">710 <span class="pipe-icon">📄</span>711 <h4>Read Ticket</h4>712 <p>Subject, description, systems, SLA, dept</p>713 </div>714 <div class="pipe-arrow">→</div>715 <div class="pipe-step">716 <span class="pipe-icon">🤖</span>717 <h4>LLM Decides</h4>718 <p>Category, priority, team, incident, steps</p>719 </div>720 <div class="pipe-arrow">→</div>721 <div class="pipe-step">722 <span class="pipe-icon">⚡</span>723 <h4>POST /step</h4>724 <p>Submit TriageAction JSON</p>725 </div>726 <div class="pipe-arrow">→</div>727 <div class="pipe-step">728 <span class="pipe-icon">📊</span>729 <h4>Reward Signal</h4>730 <p>Dense [0–1] across 6 dims</p>731 </div>732 <div class="pipe-arrow">→</div>733 <div class="pipe-step">734 <span class="pipe-icon">🔁</span>735 <h4>Repeat</h4>736 <p>Until queue empty or max_steps</p>737 </div>738 </div>739 </div>740</section>741 742<!-- FOOTER -->743<footer>744 <div class="footer-inner">745 <div class="footer-brand">746 <strong>IT_TRIAGE_ENV</strong> // v1.0.0 // 747 Meta × Hugging Face AI Hackathon748 </div>749 <div class="footer-links">750 <a href="/docs">SWAGGER</a>751 <a href="/redoc">REDOC</a>752 <a href="/health">HEALTH</a>753 <a href="/tasks">TASKS</a>754 </div>755 </div>756</footer>757 758</body>759</html>"""760 761# ─────────────────────────────────────────────────────────────────────────────762@app.get("/", response_class=HTMLResponse, include_in_schema=False)763def landing():764 return HTMLResponse(content=LANDING_HTML)765 766@app.get("/health", summary="Health check")767def health_check():768 return {"status": "ok", "environment": "IT Helpdesk Triage & Incident Management", "version": "1.0.0", "tasks": env.list_tasks()}769 770@app.post("/reset", response_model=Observation, summary="Reset environment")771def reset(request: ResetRequest):772 try:773 obs = env.reset(task_id=request.task_id)774 logger.info("Episode reset | task=%s", request.task_id)775 return obs776 except ValueError as exc:777 raise HTTPException(status_code=400, detail=str(exc)) from exc778 779@app.post("/step", response_model=StepResult, summary="Execute triage action")780def step(action: TriageAction):781 try:782 result = env.step(action)783 logger.info("Step %d | ticket=%s | reward=%.4f | done=%s", result.observation.step_number, action.ticket_id, result.reward, result.done)784 return result785 except RuntimeError as exc:786 raise HTTPException(status_code=400, detail=str(exc)) from exc787 788@app.get("/state", response_model=EnvironmentState, summary="Get environment state")789def state():790 return env.state()791 792@app.get("/tasks", summary="List available tasks")793def list_tasks():794 return env.list_tasks()795 796if __name__ == "__main__":797 port = int(os.environ.get("PORT", 7860))798 uvicorn.run("app:app", host="0.0.0.0", port=port, reload=False, log_level="info")799 