| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| <title>LLM Workflow Evaluation Tic Tac Toe</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| </head> |
| <body class="bg-gradient-to-br from-indigo-50 via-sky-50 to-teal-100 min-h-screen flex items-center justify-center p-4"> |
| <div class="max-w-6xl w-full bg-white rounded-2xl shadow-xl p-6"> |
| <h1 class="text-3xl font-extrabold mb-3 text-center text-indigo-800">LLM Workflow Evaluation Tic Tac Toe</h1> |
| <p class="text-center mb-2 text-sm text-gray-600"> |
| Reference: <a href="https://www.linkedin.com/pulse/evaluating-generative-ai-workflows-llm-inputs-michael-lively-okp7e/" class="text-indigo-600 hover:underline" target="_blank" rel="noopener noreferrer">Evaluating Generative AI Workflows: LLM Inputs</a> |
| </p> |
| <p class="text-center mb-4 text-sm text-gray-500"> |
| Answer correctly to claim a square. Miss the question, and your turn passes. |
| </p> |
|
|
| <div id="banner" class="hidden bg-indigo-100 border border-indigo-300 text-indigo-800 font-bold text-center py-2 rounded mb-4"></div> |
|
|
| <div class="flex flex-col lg:flex-row items-center lg:items-start justify-center gap-5"> |
| |
| <div id="board" class="grid grid-cols-3 gap-2 border-4 border-indigo-300 rounded-xl p-3 bg-indigo-50"></div> |
|
|
| |
| <div id="questionPanel" class="w-full lg:w-1/2 bg-gray-50 rounded-xl shadow p-5 min-h-80"> |
| <h2 id="panelQuestion" class="text-xl font-semibold text-gray-800">Select a square to view the question</h2> |
| <ul id="panelChoices" class="mt-4 space-y-2"></ul> |
| <p id="panelHint" class="mt-3 text-sm text-gray-600 hidden"></p> |
| <button id="hintBtn" class="mt-4 px-3 py-1 bg-gray-200 text-gray-700 rounded hover:bg-gray-300">Show Hint</button> |
| </div> |
| </div> |
|
|
| <div class="flex flex-col sm:flex-row justify-between items-center gap-3 mt-6"> |
| <p id="status" class="text-lg font-medium text-gray-800"></p> |
| <button id="restartBtn" class="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700">Restart</button> |
| </div> |
| </div> |
|
|
| <script> |
| const questions = [ |
| { |
| question: 'What is the main goal of moving from plausible outputs to reliable LLM systems?', |
| choices: [ |
| 'To make responses sound more confident', |
| 'To make outputs verifiable, consistent, and governed by protocol', |
| 'To remove the need for evaluation rubrics', |
| 'To increase randomness in every response' |
| ], |
| answer: 2, |
| hint: 'Reliability is about evidence, repeatability, and governance.' |
| }, |
| { |
| question: 'Why can a plausible but incorrect LLM output be dangerous in production?', |
| choices: [ |
| 'It is always visibly broken', |
| 'It costs fewer tokens', |
| 'It earns trust before eventually breaking it', |
| 'It prevents users from reading the answer' |
| ], |
| answer: 3, |
| hint: 'A fluent answer can hide a factual problem.' |
| }, |
| { |
| question: 'Which three controllable inputs must be tuned together in an LLM workflow?', |
| choices: [ |
| 'Model, parameters, and prompts', |
| 'Users, browsers, and databases', |
| 'Labels, dashboards, and invoices', |
| 'Latency, font size, and color palette' |
| ], |
| answer: 1, |
| hint: 'These are the core levers of the system.' |
| }, |
| { |
| question: 'In the framework, what does model selection primarily set?', |
| choices: [ |
| 'The hard ceiling on intelligence density, cost, and latency', |
| 'The final color of the user interface', |
| 'The number of people on the review team', |
| 'The exact wording of every future prompt' |
| ], |
| answer: 1, |
| hint: 'The model is the substrate for every output.' |
| }, |
| { |
| question: 'What does temperature control in LLM generation?', |
| choices: [ |
| 'The maximum number of users', |
| 'The probability distribution and randomness of token selection', |
| 'The length of the source document', |
| 'The number of model providers available' |
| ], |
| answer: 2, |
| hint: 'Higher values usually create more varied outputs.' |
| }, |
| { |
| question: 'Which parameter acts as the hard length ceiling for a response?', |
| choices: ['Top-p', 'Frequency penalty', 'Max tokens', 'Presence penalty'], |
| answer: 3, |
| hint: 'It limits how long the output can be.' |
| }, |
| { |
| question: 'What is the purpose of stop sequences?', |
| choices: [ |
| 'To increase factual recall', |
| 'To halt generation when a specific string pattern appears', |
| 'To change the model provider', |
| 'To score a gold dataset' |
| ], |
| answer: 2, |
| hint: 'They stop output based on a pattern, not just length.' |
| }, |
| { |
| question: 'Which prompt layer provides source documents, retrieved context, and conversation history?', |
| choices: ['Instruction', 'Context', 'Constraints', 'Reasoning'], |
| answer: 2, |
| hint: 'This layer gives the model background material.' |
| }, |
| { |
| question: 'Which prompt layer uses few-shot patterns to show the desired style, tone, and format?', |
| choices: ['Examples', 'Stop sequences', 'Gold datasets', 'Temperature'], |
| answer: 1, |
| hint: 'It gives the model a blueprint to imitate.' |
| }, |
| { |
| question: 'Why should prompt quality not be judged from a single run?', |
| choices: [ |
| 'A single output is only one sample from a distribution', |
| 'One run always uses too many tokens', |
| 'Evaluation never applies to prompts', |
| 'The model cannot answer twice' |
| ], |
| answer: 1, |
| hint: 'Reliability requires repeated trials.' |
| }, |
| { |
| question: 'Which evaluation quality measures how consistent outputs are across repeated trials?', |
| choices: ['Relevance', 'Variability', 'Coherence', 'Latency'], |
| answer: 2, |
| hint: 'It tracks how much the answer changes from run to run.' |
| }, |
| { |
| question: 'For Q&A workflows, what should success be defined by?', |
| choices: [ |
| 'How polished the answer sounds', |
| 'How long the answer is', |
| 'Factual groundedness in the source', |
| 'How creative the response feels' |
| ], |
| answer: 3, |
| hint: 'Fluency is never a substitute for accuracy.' |
| }, |
| { |
| question: 'What is a gold dataset used for?', |
| choices: [ |
| 'Changing the UI theme', |
| 'Curated ground-truth examples for benchmarking', |
| 'Randomizing token probabilities', |
| 'Removing all human review' |
| ], |
| answer: 2, |
| hint: 'It gives the team trusted examples to test against.' |
| }, |
| { |
| question: 'Which lifecycle step tracks prompt_v1 versus prompt_v2 with names, timestamps, and deltas?', |
| choices: ['Define', 'Experiment', 'Version', 'Monitor'], |
| answer: 3, |
| hint: 'This creates traceability across iterations.' |
| }, |
| { |
| question: 'If failures come from irrelevant retrieved context or truncated source material, what type of issue is it?', |
| choices: ['Prompt issue', 'Data/context issue', 'Model limit', 'UI issue'], |
| answer: 2, |
| hint: 'The fix is usually to improve the data pipeline.' |
| }, |
| { |
| question: 'If the task exceeds reasoning capacity, complex arithmetic, or context-window constraints, what is the likely failure category?', |
| choices: ['Model limits', 'Prompt color', 'Version logs', 'Presence penalty'], |
| answer: 1, |
| hint: 'The fix may be to switch models or adjust scope.' |
| } |
| ]; |
| |
| let currentPlayer, boardState, cellQuestions, selectedCell; |
| const boardEl = document.getElementById('board'); |
| const statusEl = document.getElementById('status'); |
| const bannerEl = document.getElementById('banner'); |
| const hintBtn = document.getElementById('hintBtn'); |
| const panelQuestion = document.getElementById('panelQuestion'); |
| const panelChoices = document.getElementById('panelChoices'); |
| const panelHint = document.getElementById('panelHint'); |
| const restartBtn = document.getElementById('restartBtn'); |
| |
| function shuffle(arr) { |
| for (let i = arr.length - 1; i > 0; i--) { |
| const j = Math.floor(Math.random() * (i + 1)); |
| [arr[i], arr[j]] = [arr[j], arr[i]]; |
| } |
| return arr; |
| } |
| |
| function buildBalancedPositions(nQuestions, kChoices) { |
| const base = Math.floor(nQuestions / kChoices); |
| const remainder = nQuestions % kChoices; |
| const slots = []; |
| for (let pos = 1; pos <= kChoices; pos++) { |
| for (let c = 0; c < base; c++) slots.push(pos); |
| } |
| const extra = shuffle(Array.from({ length: kChoices }, (_, i) => i + 1)).slice(0, remainder); |
| slots.push(...extra); |
| return shuffle(slots); |
| } |
| |
| function initGame() { |
| currentPlayer = 'X'; |
| boardState = Array(9).fill(''); |
| selectedCell = null; |
| bannerEl.classList.add('hidden'); |
| statusEl.innerText = ''; |
| |
| const shuffledQs = shuffle([...questions]).slice(0, 9); |
| const kChoices = 4; |
| const targetPositions = buildBalancedPositions(shuffledQs.length, kChoices); |
| |
| cellQuestions = shuffledQs.map((q, idx) => { |
| const originalCorrectIdx0 = q.answer - 1; |
| const correctText = q.choices[originalCorrectIdx0]; |
| const wrongs = q.choices.filter((_, i) => i !== originalCorrectIdx0); |
| shuffle(wrongs); |
| const targetPos1 = targetPositions[idx]; |
| const targetPos0 = targetPos1 - 1; |
| const newChoices = Array(kChoices).fill(null); |
| newChoices[targetPos0] = correctText; |
| let w = 0; |
| for (let i = 0; i < kChoices; i++) { |
| if (newChoices[i] === null) newChoices[i] = wrongs[w++]; |
| } |
| return { |
| question: q.question, |
| choices: newChoices, |
| answer: targetPos1, |
| hint: q.hint |
| }; |
| }); |
| |
| panelQuestion.innerText = 'Select a square to view the question'; |
| panelChoices.innerHTML = ''; |
| panelHint.classList.add('hidden'); |
| panelHint.innerText = ''; |
| renderBoard(); |
| } |
| |
| function renderBoard() { |
| boardEl.innerHTML = ''; |
| boardState.forEach((mark, idx) => { |
| const btn = document.createElement('button'); |
| let cls = 'bg-white h-24 w-24 sm:h-28 sm:w-28 flex items-center justify-center text-4xl font-extrabold rounded-lg shadow hover:bg-indigo-100 transition'; |
| if (mark === 'X') cls += ' text-blue-600'; |
| if (mark === 'O') cls += ' text-red-600'; |
| btn.className = cls; |
| btn.innerText = mark; |
| btn.disabled = mark !== ''; |
| btn.addEventListener('click', () => openQuestion(idx)); |
| boardEl.appendChild(btn); |
| }); |
| statusEl.innerText = `Current: ${currentPlayer}`; |
| } |
| |
| function openQuestion(idx) { |
| if (boardState[idx] !== '') return; |
| selectedCell = idx; |
| const q = cellQuestions[idx]; |
| panelQuestion.innerText = q.question; |
| panelChoices.innerHTML = ''; |
| panelHint.classList.add('hidden'); |
| panelHint.innerText = q.hint; |
| |
| q.choices.forEach((choiceText, i) => { |
| const li = document.createElement('li'); |
| const btn = document.createElement('button'); |
| btn.innerText = choiceText; |
| btn.className = 'w-full text-left px-4 py-2 bg-indigo-50 rounded hover:bg-indigo-100 border border-indigo-100'; |
| btn.addEventListener('click', () => handleAnswer(i + 1)); |
| li.appendChild(btn); |
| panelChoices.appendChild(li); |
| }); |
| } |
| |
| function handleAnswer(choice) { |
| if (selectedCell === null) return; |
| const q = cellQuestions[selectedCell]; |
| if (choice === q.answer) { |
| boardState[selectedCell] = currentPlayer; |
| panelChoices.innerHTML = ''; |
| panelHint.classList.add('hidden'); |
| panelQuestion.innerText = 'Correct! Select another open square.'; |
| renderBoard(); |
| if (checkWin()) return endGame(`${currentPlayer} wins!`); |
| if (boardState.every(c => c)) return endGame('Stalemate!'); |
| } else { |
| alert('Incorrect! Turn missed.'); |
| } |
| currentPlayer = currentPlayer === 'X' ? 'O' : 'X'; |
| statusEl.innerText = `Current: ${currentPlayer}`; |
| selectedCell = null; |
| } |
| |
| function checkWin() { |
| const wins = [ |
| [0,1,2], [3,4,5], [6,7,8], |
| [0,3,6], [1,4,7], [2,5,8], |
| [0,4,8], [2,4,6] |
| ]; |
| return wins.some(combo => combo.every(i => boardState[i] === currentPlayer)); |
| } |
| |
| function endGame(msg) { |
| bannerEl.innerText = `🎉 ${msg}`; |
| bannerEl.classList.remove('hidden'); |
| document.querySelectorAll('#board button').forEach(b => b.disabled = true); |
| panelQuestion.innerText = msg; |
| panelChoices.innerHTML = ''; |
| panelHint.classList.add('hidden'); |
| } |
| |
| hintBtn.addEventListener('click', () => { |
| if (selectedCell !== null) panelHint.classList.toggle('hidden'); |
| }); |
| restartBtn.addEventListener('click', initGame); |
| initGame(); |
| </script> |
| </body> |
| </html> |
|
|