| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>FUNCTIONGEMMA | TUTORIAL</title> |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| <link href="https://fonts.googleapis.com/css2?family=Bungee&family=JetBrains+Mono:wght@400;700;800&family=Space+Grotesk:wght@400;700&display=swap" rel="stylesheet"> |
|
|
| |
| <script async src="https://www.googletagmanager.com/gtag/js?id=G-2Q4M55VKPR"></script> |
| <script> |
| window.dataLayer = window.dataLayer || []; |
| function gtag(){dataLayer.push(arguments);} |
| gtag('js', new Date()); |
| gtag('config', 'G-2Q4M55VKPR', { |
| page_path: window.location.pathname, |
| anonymize_ip: true |
| }); |
| </script> |
| <style> |
| :root { |
| --black: #000000; |
| --white: #FFFFFF; |
| --yellow: #FFFF00; |
| --red: #FF0000; |
| --green: #00FF00; |
| --cyan: #00FFFF; |
| --border-width: 4px; |
| --shadow-offset: 8px; |
| } |
| |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| body { |
| font-family: 'Space Grotesk', sans-serif; |
| background: var(--black); |
| color: var(--white); |
| min-height: 100vh; |
| padding: 20px; |
| line-height: 1.6; |
| position: relative; |
| overflow-x: hidden; |
| } |
| |
| body::before { |
| content: ''; |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: |
| repeating-linear-gradient(90deg, transparent, transparent 50px, rgba(255,255,0,0.03) 50px, rgba(255,255,0,0.03) 52px), |
| repeating-linear-gradient(0deg, transparent, transparent 50px, rgba(255,0,0,0.03) 50px, rgba(255,0,0,0.03) 52px); |
| pointer-events: none; |
| z-index: 0; |
| } |
| |
| .container { |
| max-width: 1600px; |
| margin: 0 auto; |
| position: relative; |
| z-index: 1; |
| } |
| |
| .header { |
| background: var(--black); |
| border: var(--border-width) solid var(--white); |
| padding: 40px; |
| margin-bottom: 30px; |
| box-shadow: var(--shadow-offset) var(--shadow-offset) 0 var(--yellow); |
| position: relative; |
| animation: slideDown 0.6s ease-out; |
| } |
| |
| @keyframes slideDown { |
| from { |
| opacity: 0; |
| transform: translateY(-30px); |
| } |
| to { |
| opacity: 1; |
| transform: translateY(0); |
| } |
| } |
| |
| .header::before { |
| content: ''; |
| position: absolute; |
| top: -4px; |
| left: -4px; |
| right: -4px; |
| bottom: -4px; |
| border: 2px solid var(--yellow); |
| z-index: -1; |
| } |
| |
| .header h1 { |
| font-family: 'Bungee', cursive; |
| font-size: 3.5em; |
| color: var(--yellow); |
| margin-bottom: 15px; |
| text-transform: uppercase; |
| letter-spacing: 2px; |
| text-shadow: 4px 4px 0 var(--red); |
| line-height: 1.1; |
| } |
| |
| .header p { |
| font-size: 1.3em; |
| color: var(--white); |
| font-weight: 700; |
| text-transform: uppercase; |
| letter-spacing: 1px; |
| } |
| |
| .model-loading-overlay { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: var(--black); |
| z-index: 10000; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| border: var(--border-width) solid var(--yellow); |
| } |
| |
| .model-loading-overlay.hidden { |
| display: none; |
| } |
| |
| .loading-content { |
| max-width: 800px; |
| padding: 40px; |
| background: var(--black); |
| border: var(--border-width) solid var(--white); |
| box-shadow: var(--shadow-offset) var(--shadow-offset) 0 var(--cyan); |
| } |
| |
| .loading-title { |
| font-family: 'Bungee', cursive; |
| font-size: 2.5em; |
| color: var(--yellow); |
| margin-bottom: 30px; |
| text-transform: uppercase; |
| text-align: center; |
| animation: pulse 2s ease-in-out infinite; |
| } |
| |
| @keyframes pulse { |
| 0%, 100% { opacity: 1; } |
| 50% { opacity: 0.7; } |
| } |
| |
| .loading-steps { |
| list-style: none; |
| margin: 20px 0; |
| } |
| |
| .loading-step { |
| padding: 15px; |
| margin: 10px 0; |
| background: var(--black); |
| border: 2px solid var(--white); |
| color: var(--white); |
| font-family: 'JetBrains Mono', monospace; |
| position: relative; |
| transition: all 0.3s ease; |
| } |
| |
| .loading-step.active { |
| border-color: var(--yellow); |
| background: rgba(255, 255, 0, 0.1); |
| box-shadow: 4px 4px 0 var(--yellow); |
| } |
| |
| .loading-step.completed { |
| border-color: var(--green); |
| background: rgba(0, 255, 0, 0.1); |
| } |
| |
| .loading-step.completed::after { |
| content: ' ✓'; |
| color: var(--green); |
| font-weight: bold; |
| } |
| |
| .model-info-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); |
| gap: 20px; |
| margin: 30px 0; |
| } |
| |
| .info-card { |
| background: var(--black); |
| border: var(--border-width) solid var(--white); |
| padding: 20px; |
| box-shadow: 6px 6px 0 var(--cyan); |
| } |
| |
| .info-card h3 { |
| font-family: 'Bungee', cursive; |
| color: var(--cyan); |
| font-size: 1.2em; |
| margin-bottom: 10px; |
| text-transform: uppercase; |
| } |
| |
| .info-card p { |
| font-family: 'JetBrains Mono', monospace; |
| color: var(--white); |
| font-size: 0.9em; |
| } |
| |
| .resource-links { |
| margin: 30px 0; |
| padding: 20px; |
| background: var(--black); |
| border: var(--border-width) solid var(--yellow); |
| box-shadow: var(--shadow-offset) var(--shadow-offset) 0 var(--red); |
| } |
| |
| .resource-links h3 { |
| font-family: 'Bungee', cursive; |
| color: var(--yellow); |
| margin-bottom: 15px; |
| text-transform: uppercase; |
| font-size: 1.5em; |
| } |
| |
| .resource-links ul { |
| list-style: none; |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); |
| gap: 15px; |
| } |
| |
| .resource-links li { |
| padding: 15px; |
| background: var(--black); |
| border: 2px solid var(--white); |
| } |
| |
| .resource-links a { |
| color: var(--cyan); |
| text-decoration: none; |
| font-family: 'JetBrains Mono', monospace; |
| font-weight: 700; |
| transition: all 0.2s ease; |
| display: block; |
| } |
| |
| .resource-links a:hover { |
| color: var(--yellow); |
| transform: translateX(5px); |
| } |
| |
| .progress-bar { |
| background: var(--black); |
| border: var(--border-width) solid var(--white); |
| padding: 20px; |
| margin-bottom: 30px; |
| display: flex; |
| align-items: center; |
| gap: 20px; |
| box-shadow: var(--shadow-offset) var(--shadow-offset) 0 var(--cyan); |
| } |
| |
| .progress-fill { |
| flex: 1; |
| height: 40px; |
| background: var(--black); |
| border: 2px solid var(--white); |
| position: relative; |
| overflow: hidden; |
| } |
| |
| .progress-inner { |
| height: 100%; |
| background: var(--yellow); |
| transition: width 0.5s ease; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: var(--black); |
| font-weight: 800; |
| font-family: 'Bungee', cursive; |
| font-size: 1.1em; |
| text-transform: uppercase; |
| } |
| |
| .main-content { |
| display: grid; |
| grid-template-columns: 320px 1fr; |
| gap: 30px; |
| } |
| |
| .sidebar { |
| background: var(--black); |
| border: var(--border-width) solid var(--white); |
| padding: 25px; |
| box-shadow: var(--shadow-offset) var(--shadow-offset) 0 var(--red); |
| height: fit-content; |
| position: sticky; |
| top: 20px; |
| } |
| |
| .model-status { |
| background: var(--black); |
| border: 2px solid var(--white); |
| padding: 15px; |
| margin-bottom: 25px; |
| text-align: center; |
| font-family: 'JetBrains Mono', monospace; |
| font-weight: 700; |
| } |
| |
| .model-status.loaded { |
| border-color: var(--green); |
| box-shadow: 4px 4px 0 var(--green); |
| } |
| |
| .model-status-text { |
| color: var(--white); |
| } |
| |
| .model-status.loaded .model-status-text { |
| color: var(--green); |
| } |
| |
| .lesson-list { |
| list-style: none; |
| } |
| |
| .lesson-item { |
| padding: 18px; |
| margin: 12px 0; |
| background: var(--black); |
| border: 2px solid var(--white); |
| cursor: pointer; |
| transition: all 0.2s ease; |
| font-weight: 700; |
| text-transform: uppercase; |
| font-size: 0.9em; |
| letter-spacing: 0.5px; |
| } |
| |
| .lesson-item:hover { |
| transform: translate(4px, 4px); |
| box-shadow: -4px -4px 0 var(--yellow); |
| } |
| |
| .lesson-item.active { |
| background: var(--yellow); |
| color: var(--black); |
| border-color: var(--yellow); |
| box-shadow: var(--shadow-offset) var(--shadow-offset) 0 var(--red); |
| } |
| |
| .lesson-item.completed { |
| border-color: var(--green); |
| } |
| |
| .lesson-item.completed::after { |
| content: " ✓"; |
| color: var(--green); |
| font-weight: bold; |
| } |
| |
| .content-area { |
| background: var(--black); |
| border: var(--border-width) solid var(--white); |
| padding: 40px; |
| box-shadow: var(--shadow-offset) var(--shadow-offset) 0 var(--cyan); |
| min-height: 600px; |
| } |
| |
| .lesson-content { |
| display: none; |
| } |
| |
| .lesson-content.active { |
| display: block; |
| animation: fadeInSlide 0.5s ease; |
| } |
| |
| @keyframes fadeInSlide { |
| from { |
| opacity: 0; |
| transform: translateY(20px); |
| } |
| to { |
| opacity: 1; |
| transform: translateY(0); |
| } |
| } |
| |
| .lesson-title { |
| font-family: 'Bungee', cursive; |
| font-size: 2.5em; |
| margin-bottom: 25px; |
| color: var(--yellow); |
| text-transform: uppercase; |
| text-shadow: 3px 3px 0 var(--red); |
| border-bottom: var(--border-width) solid var(--yellow); |
| padding-bottom: 15px; |
| } |
| |
| .lesson-description { |
| font-size: 1.1em; |
| line-height: 1.8; |
| margin-bottom: 30px; |
| color: var(--white); |
| } |
| |
| .code-block { |
| background: var(--black); |
| border: var(--border-width) solid var(--yellow); |
| padding: 25px; |
| margin: 25px 0; |
| overflow-x: auto; |
| position: relative; |
| box-shadow: 6px 6px 0 var(--cyan); |
| } |
| |
| .code-block pre { |
| color: var(--green); |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 14px; |
| line-height: 1.8; |
| margin: 0; |
| font-weight: 400; |
| } |
| |
| .code-comment { |
| color: #888; |
| font-style: italic; |
| } |
| |
| .code-keyword { |
| color: var(--red); |
| font-weight: 700; |
| } |
| |
| .code-string { |
| color: var(--cyan); |
| } |
| |
| .code-function { |
| color: var(--yellow); |
| } |
| |
| .interactive-demo { |
| background: var(--black); |
| border: var(--border-width) solid var(--white); |
| padding: 25px; |
| margin: 25px 0; |
| box-shadow: 6px 6px 0 var(--red); |
| } |
| |
| .interactive-demo h3 { |
| font-family: 'Bungee', cursive; |
| color: var(--yellow); |
| margin-bottom: 20px; |
| text-transform: uppercase; |
| font-size: 1.5em; |
| } |
| |
| .demo-controls { |
| display: flex; |
| gap: 15px; |
| margin-bottom: 20px; |
| flex-wrap: wrap; |
| } |
| |
| button { |
| background: var(--black); |
| color: var(--white); |
| border: var(--border-width) solid var(--white); |
| padding: 15px 30px; |
| font-size: 16px; |
| font-weight: 800; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| font-family: 'Space Grotesk', sans-serif; |
| text-transform: uppercase; |
| letter-spacing: 1px; |
| box-shadow: 4px 4px 0 var(--yellow); |
| } |
| |
| button:hover:not(:disabled) { |
| transform: translate(2px, 2px); |
| box-shadow: 2px 2px 0 var(--yellow); |
| } |
| |
| button:active:not(:disabled) { |
| transform: translate(4px, 4px); |
| box-shadow: 0 0 0 var(--yellow); |
| } |
| |
| button:disabled { |
| background: #333; |
| border-color: #666; |
| color: #666; |
| cursor: not-allowed; |
| box-shadow: none; |
| } |
| |
| .btn-success { |
| border-color: var(--green); |
| box-shadow: 4px 4px 0 var(--green); |
| color: var(--green); |
| } |
| |
| .btn-success:hover:not(:disabled) { |
| box-shadow: 2px 2px 0 var(--green); |
| } |
| |
| .btn-danger { |
| border-color: var(--red); |
| box-shadow: 4px 4px 0 var(--red); |
| color: var(--red); |
| } |
| |
| .btn-danger:hover:not(:disabled) { |
| box-shadow: 2px 2px 0 var(--red); |
| } |
| |
| .output-area { |
| background: var(--black); |
| border: var(--border-width) solid var(--cyan); |
| padding: 20px; |
| margin: 20px 0; |
| min-height: 150px; |
| max-height: 500px; |
| overflow-y: auto; |
| font-family: 'JetBrains Mono', monospace; |
| font-size: 13px; |
| box-shadow: 6px 6px 0 var(--yellow); |
| } |
| |
| .token-visualization { |
| display: flex; |
| flex-wrap: wrap; |
| gap: 15px; |
| margin: 25px 0; |
| } |
| |
| .token-box { |
| background: var(--black); |
| border: 2px solid var(--white); |
| padding: 15px 20px; |
| text-align: center; |
| min-width: 140px; |
| transition: all 0.2s ease; |
| box-shadow: 4px 4px 0 var(--cyan); |
| } |
| |
| .token-box:hover { |
| transform: translate(-2px, -2px); |
| box-shadow: 6px 6px 0 var(--yellow); |
| border-color: var(--yellow); |
| } |
| |
| .token-id { |
| font-size: 11px; |
| color: #888; |
| margin-bottom: 8px; |
| font-family: 'JetBrains Mono', monospace; |
| } |
| |
| .token-text { |
| font-size: 18px; |
| color: var(--green); |
| font-weight: 800; |
| font-family: 'JetBrains Mono', monospace; |
| } |
| |
| .hint-box, .info-box, .success-box, .error-box { |
| background: var(--black); |
| border-left: var(--border-width) solid; |
| padding: 20px; |
| margin: 20px 0; |
| box-shadow: 4px 4px 0; |
| } |
| |
| .hint-box { |
| border-color: var(--yellow); |
| box-shadow: 4px 4px 0 var(--yellow); |
| } |
| |
| .hint-box h4 { |
| color: var(--yellow); |
| margin-bottom: 12px; |
| font-family: 'Bungee', cursive; |
| text-transform: uppercase; |
| font-size: 1.2em; |
| } |
| |
| .info-box { |
| border-color: var(--cyan); |
| box-shadow: 4px 4px 0 var(--cyan); |
| } |
| |
| .info-box h4 { |
| color: var(--cyan); |
| margin-bottom: 12px; |
| font-family: 'Bungee', cursive; |
| text-transform: uppercase; |
| font-size: 1.2em; |
| } |
| |
| .success-box { |
| border-color: var(--green); |
| box-shadow: 4px 4px 0 var(--green); |
| } |
| |
| .success-box h4 { |
| color: var(--green); |
| margin-bottom: 12px; |
| font-family: 'Bungee', cursive; |
| text-transform: uppercase; |
| font-size: 1.2em; |
| } |
| |
| .error-box { |
| border-color: var(--red); |
| box-shadow: 4px 4px 0 var(--red); |
| } |
| |
| .error-box h4 { |
| color: var(--red); |
| margin-bottom: 12px; |
| font-family: 'Bungee', cursive; |
| text-transform: uppercase; |
| font-size: 1.2em; |
| } |
| |
| .comparison-table { |
| width: 100%; |
| border-collapse: separate; |
| border-spacing: 0; |
| margin: 25px 0; |
| border: var(--border-width) solid var(--white); |
| } |
| |
| .comparison-table th, |
| .comparison-table td { |
| padding: 15px; |
| text-align: left; |
| border-bottom: 2px solid var(--white); |
| border-right: 2px solid var(--white); |
| } |
| |
| .comparison-table th { |
| background: var(--yellow); |
| color: var(--black); |
| font-family: 'Bungee', cursive; |
| text-transform: uppercase; |
| font-weight: 400; |
| } |
| |
| .comparison-table tr:hover { |
| background: rgba(255, 255, 0, 0.1); |
| } |
| |
| .comparison-table td:last-child, |
| .comparison-table th:last-child { |
| border-right: none; |
| } |
| |
| .achievement-badge { |
| display: inline-block; |
| background: var(--yellow); |
| color: var(--black); |
| padding: 8px 15px; |
| border: 2px solid var(--black); |
| font-size: 12px; |
| font-weight: 800; |
| margin: 5px; |
| text-transform: uppercase; |
| font-family: 'Bungee', cursive; |
| box-shadow: 3px 3px 0 var(--red); |
| } |
| |
| input[type="text"], |
| textarea { |
| background: var(--black); |
| border: var(--border-width) solid var(--white); |
| color: var(--white); |
| padding: 15px; |
| font-size: 14px; |
| width: 100%; |
| font-family: 'JetBrains Mono', monospace; |
| margin-bottom: 15px; |
| box-shadow: 4px 4px 0 var(--cyan); |
| } |
| |
| input[type="text"]:focus, |
| textarea:focus { |
| outline: none; |
| border-color: var(--yellow); |
| box-shadow: 4px 4px 0 var(--yellow); |
| } |
| |
| .loading-spinner { |
| display: inline-block; |
| width: 24px; |
| height: 24px; |
| border: 3px solid var(--white); |
| border-top-color: var(--yellow); |
| animation: spin 1s linear infinite; |
| } |
| |
| @keyframes spin { |
| to { transform: rotate(360deg); } |
| } |
| |
| .playground-grid { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 25px; |
| margin: 25px 0; |
| } |
| |
| .playground-section { |
| background: var(--black); |
| border: var(--border-width) solid var(--white); |
| padding: 25px; |
| box-shadow: 6px 6px 0 var(--red); |
| } |
| |
| .playground-section h3 { |
| font-family: 'Bungee', cursive; |
| color: var(--yellow); |
| margin-bottom: 15px; |
| text-transform: uppercase; |
| } |
| |
| textarea { |
| min-height: 300px; |
| resize: vertical; |
| } |
| |
| .resource-badge { |
| display: inline-block; |
| background: var(--black); |
| border: 2px solid var(--cyan); |
| padding: 5px 10px; |
| margin: 5px; |
| font-size: 11px; |
| font-family: 'JetBrains Mono', monospace; |
| color: var(--cyan); |
| text-transform: uppercase; |
| } |
| |
| .example-item { |
| background: var(--black); |
| border: 2px solid var(--white); |
| padding: 20px; |
| margin: 15px 0; |
| box-shadow: 4px 4px 0 var(--cyan); |
| position: relative; |
| } |
| |
| .example-item-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 15px; |
| } |
| |
| .example-item h4 { |
| font-family: 'Bungee', cursive; |
| color: var(--yellow); |
| font-size: 1em; |
| margin: 0; |
| text-transform: uppercase; |
| } |
| |
| .example-item input, |
| .example-item textarea { |
| width: 100%; |
| margin-bottom: 10px; |
| font-size: 13px; |
| } |
| |
| .example-item textarea { |
| min-height: 60px; |
| resize: vertical; |
| } |
| |
| .remove-example-btn { |
| background: var(--black); |
| color: var(--red); |
| border: 2px solid var(--red); |
| padding: 8px 15px; |
| font-size: 12px; |
| cursor: pointer; |
| font-weight: 700; |
| text-transform: uppercase; |
| box-shadow: 3px 3px 0 var(--red); |
| } |
| |
| .remove-example-btn:hover { |
| transform: translate(1px, 1px); |
| box-shadow: 2px 2px 0 var(--red); |
| } |
| |
| @media (max-width: 1024px) { |
| .main-content { |
| grid-template-columns: 1fr; |
| } |
| .sidebar { |
| position: static; |
| } |
| .playground-grid { |
| grid-template-columns: 1fr; |
| } |
| .header h1 { |
| font-size: 2.5em; |
| } |
| } |
| |
| @media (max-width: 768px) { |
| .header h1 { |
| font-size: 2em; |
| } |
| .lesson-title { |
| font-size: 1.8em; |
| } |
| .model-info-grid { |
| grid-template-columns: 1fr; |
| } |
| .resource-links ul { |
| grid-template-columns: 1fr; |
| } |
| } |
| </style> |
| </head> |
| <body> |
| |
| <div class="model-loading-overlay" id="loadingOverlay"> |
| <div class="loading-content"> |
| <h2 class="loading-title">LOADING FUNCTIONGEMMA</h2> |
| <ul class="loading-steps" id="loadingSteps"> |
| <li class="loading-step" id="step1">Initializing transformers.js...</li> |
| <li class="loading-step" id="step2">Loading tokenizer...</li> |
| <li class="loading-step" id="step3">Detecting device capabilities...</li> |
| <li class="loading-step" id="step4">Loading ONNX model...</li> |
| <li class="loading-step" id="step5">Model ready!</li> |
| </ul> |
| <div class="model-info-grid" id="modelInfoGrid" style="display: none;"> |
| <div class="info-card"> |
| <h3>MODEL</h3> |
| <p>functiongemma-270m-it-ONNX</p> |
| </div> |
| <div class="info-card"> |
| <h3>PARAMETERS</h3> |
| <p>270 Million</p> |
| </div> |
| <div class="info-card"> |
| <h3>FORMAT</h3> |
| <p id="modelFormat">Detecting...</p> |
| </div> |
| <div class="info-card"> |
| <h3>DEVICE</h3> |
| <p id="modelDevice">Detecting...</p> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="container"> |
| <div class="header"> |
| <h1>FUNCTIONGEMMA</h1> |
| <p>INTERACTIVE TUTORIAL</p> |
| <p style="font-size: 0.9em; margin-top: 10px; font-weight: 400; text-transform: none;">Master Function Calling, Tokenization & Prompt Engineering</p> |
| </div> |
|
|
| <div class="progress-bar"> |
| <div class="progress-fill"> |
| <div class="progress-inner" id="progressBar" style="width: 0%">0% COMPLETE</div> |
| </div> |
| <div id="achievements"></div> |
| </div> |
|
|
| <div class="main-content"> |
| <div class="sidebar"> |
| <div class="model-status" id="modelStatus"> |
| <strong>STATUS:</strong> <span class="model-status-text" id="modelStatusText">READY</span> |
| </div> |
| <ul class="lesson-list"> |
| <li class="lesson-item active" data-lesson="0">🚀 WELCOME</li> |
| <li class="lesson-item" data-lesson="1">🔤 TOKENIZATION</li> |
| <li class="lesson-item" data-lesson="2">❌ ZERO-SHOT</li> |
| <li class="lesson-item" data-lesson="3">⚠️ ONE-SHOT</li> |
| <li class="lesson-item" data-lesson="4">✅ FEW-SHOT</li> |
| <li class="lesson-item" data-lesson="5">🔍 TOKEN DIVE</li> |
| <li class="lesson-item" data-lesson="6">🎯 PLAYGROUND</li> |
| <li class="lesson-item" data-lesson="7">📚 RESOURCES</li> |
| </ul> |
| </div> |
|
|
| <div class="content-area"> |
| |
| <div class="lesson-content active" data-lesson="0"> |
| <h2 class="lesson-title">WELCOME TO FUNCTIONGEMMA</h2> |
| <div class="lesson-description"> |
| <p>Welcome to the tutorial on FunctionGemma. This interactive experience will teach you everything about function calling, tokenization, and prompt engineering through hands-on experimentation.</p> |
| |
| <div class="info-box"> |
| <h4>WHAT YOU'LL LEARN</h4> |
| <ul style="margin-left: 20px; line-height: 2.5;"> |
| <li>How tokenization works in language models</li> |
| <li>Why zero-shot function calling fails</li> |
| <li>How few-shot examples solve the problem</li> |
| <li>Token-level analysis and debugging</li> |
| <li>Best practices for prompt engineering</li> |
| <li>ONNX model optimization and deployment</li> |
| </ul> |
| </div> |
|
|
| <div class="hint-box"> |
| <h4>ABOUT FUNCTIONGEMMA-270M-IT-ONNX</h4> |
| <p><strong>Model:</strong> onnx-community/functiongemma-270m-it-ONNX</p> |
| <p><strong>Size:</strong> 270 million parameters</p> |
| <p><strong>Purpose:</strong> Specialized for function calling tasks</p> |
| <p><strong>Format:</strong> ONNX quantized (q4 for WebGPU, q8 for WASM)</p> |
| <p><strong>Key Finding:</strong> Requires few-shot examples to generate correct function calls!</p> |
| <p><strong>Architecture:</strong> Based on Google's Gemma 3 270M, fine-tuned for function calling</p> |
| </div> |
|
|
| <div class="success-box"> |
| <h4>MODEL LOADED SUCCESSFULLY</h4> |
| <p>The model has been automatically loaded and is ready to use. You can now proceed with the lessons!</p> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="lesson-content" data-lesson="1"> |
| <h2 class="lesson-title">TOKENIZATION BASICS</h2> |
| <div class="lesson-description"> |
| <p>Tokenization is the process of converting text into tokens (numbers) that the model can understand. Let's explore this interactively!</p> |
|
|
| <div class="info-box"> |
| <h4>WHAT IS TOKENIZATION?</h4> |
| <p>Language models don't understand words directly. They work with <strong>tokens</strong> - numeric IDs that represent pieces of text. A token can be a word, part of a word, or even a single character.</p> |
| </div> |
|
|
| <div class="interactive-demo"> |
| <h3>TRY IT YOURSELF</h3> |
| <input type="text" id="tokenizeInput" placeholder="Enter text to tokenize..." value="call:get_current_temperature"> |
| <button onclick="demonstrateTokenization()">TOKENIZE</button> |
| <div id="tokenizationOutput" class="output-area" style="margin-top: 15px;"></div> |
| </div> |
|
|
| <div class="code-block"> |
| <pre><span class="code-comment">// How tokenization works in code:</span> |
| <span class="code-keyword">const</span> text = <span class="code-string">"call:get_current_temperature"</span>; |
| <span class="code-comment">// Tokenize the text</span> |
| <span class="code-keyword">const</span> tokens = <span class="code-function">await tokenizer</span>.encode(text); |
| <span class="code-comment">// Result: [6639, 236787, 828, 236779, 4002, 236779, 27495]</span> |
| <span class="code-comment">// Each number represents a token ID</span> |
|
|
| <span class="code-comment">// Decode tokens back to text</span> |
| <span class="code-keyword">const</span> decoded = <span class="code-function">await tokenizer</span>.decode(tokens); |
| <span class="code-comment">// Result: "call:get_current_temperature"</span></pre> |
| </div> |
|
|
| <div class="hint-box"> |
| <h4>KEY INSIGHT</h4> |
| <p>Special tokens like <code><start_function_call></code> have specific token IDs (e.g., token 48). The model uses these to understand structure.</p> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="lesson-content" data-lesson="2"> |
| <h2 class="lesson-title">ZERO-SHOT FUNCTION CALLING (WHY IT FAILS)</h2> |
| <div class="lesson-description"> |
| <p>Zero-shot means asking the model to do something without showing it an example. Let's see what happens!</p> |
|
|
| <div class="error-box"> |
| <h4>THE PROBLEM</h4> |
| <p>Without examples, FunctionGemma generates <code>error:</code> instead of <code>call:</code> after <code><start_function_call></code>.</p> |
| </div> |
|
|
| <div class="interactive-demo"> |
| <h3>TEST ZERO-SHOT APPROACH</h3> |
| <input type="text" id="zeroShotQuery" value="What's the temperature in London?" placeholder="Enter your query..."> |
| <button onclick="testZeroShot()">TEST ZERO-SHOT</button> |
| <div id="zeroShotOutput" class="output-area" style="margin-top: 15px;"></div> |
| </div> |
|
|
| <div class="code-block"> |
| <pre><span class="code-comment">// Zero-shot approach - NO examples provided</span> |
| <span class="code-keyword">const</span> messages = [ |
| { |
| role: <span class="code-string">"developer"</span>, |
| content: <span class="code-string">"You are a model that can do function calling..."</span> |
| }, |
| { |
| role: <span class="code-string">"user"</span>, |
| content: <span class="code-string">"What's the temperature in London?"</span> |
| } |
| <span class="code-comment">// ❌ No example shown to the model!</span> |
| ]; |
|
|
| <span class="code-comment">// Result: Model generates "error:" instead of "call:"</span> |
| <span class="code-comment">// Token 1899 ("error") is chosen instead of token 6639 ("call")</span></pre> |
| </div> |
|
|
| <div class="hint-box"> |
| <h4>TOKEN ANALYSIS</h4> |
| <p>After <code><start_function_call></code> (token 48), the model's probability distribution favors token 1899 ("error") over token 6639 ("call") when no example is provided.</p> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="lesson-content" data-lesson="3"> |
| <h2 class="lesson-title">ONE-SHOT FUNCTION CALLING (PARTIAL SUCCESS)</h2> |
| <div class="lesson-description"> |
| <p>One-shot means showing the model ONE example. Let's see if this helps!</p> |
|
|
| <div class="interactive-demo"> |
| <h3>TEST ONE-SHOT APPROACH</h3> |
| <input type="text" id="oneShotQuery" value="What's the temperature in Tokyo?" placeholder="Enter your query..."> |
| <button onclick="testOneShot()">TEST ONE-SHOT</button> |
| <div id="oneShotOutput" class="output-area" style="margin-top: 15px;"></div> |
| </div> |
|
|
| <div class="code-block"> |
| <pre><span class="code-comment">// One-shot approach - ONE example provided</span> |
| <span class="code-keyword">const</span> messages = [ |
| { |
| role: <span class="code-string">"developer"</span>, |
| content: <span class="code-string">"You are a model that can do function calling..."</span> |
| }, |
| { |
| role: <span class="code-string">"user"</span>, |
| content: <span class="code-string">"What's the temperature in Paris?"</span> |
| }, |
| { |
| role: <span class="code-string">"assistant"</span>, |
| <span class="code-comment">// ✅ ONE example showing correct format</span> |
| content: <span class="code-string">"<start_function_call>call:get_current_temperature{location:<escape>Paris<escape>}<end_function_call>"</span> |
| }, |
| { |
| role: <span class="code-string">"user"</span>, |
| content: <span class="code-string">"What's the temperature in Tokyo?"</span> |
| } |
| ];</pre> |
| </div> |
|
|
| <div class="info-box"> |
| <h4>RESULTS MAY VARY</h4> |
| <p>One-shot can work sometimes, but it's not as reliable as few-shot. The model needs more context to consistently generate correct function calls.</p> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="lesson-content" data-lesson="4"> |
| <h2 class="lesson-title">FEW-SHOT FUNCTION CALLING (THE SOLUTION!)</h2> |
| <div class="lesson-description"> |
| <p>Few-shot means showing the model multiple examples. This is the proven solution!</p> |
|
|
| <div class="success-box"> |
| <h4>THE SOLUTION</h4> |
| <p>By providing a few-shot example, we shift the model's token probabilities. Token 6639 ("call") becomes more likely than token 1899 ("error").</p> |
| </div> |
|
|
| <div class="interactive-demo"> |
| <h3>TEST FEW-SHOT APPROACH</h3> |
| <input type="text" id="fewShotQuery" value="What's the temperature in New York?" placeholder="Enter your query..."> |
| <button onclick="testFewShot()">TEST FEW-SHOT</button> |
| <div id="fewShotOutput" class="output-area" style="margin-top: 15px;"></div> |
| </div> |
|
|
| <div class="code-block"> |
| <pre><span class="code-comment">// ✅ FEW-SHOT APPROACH (PROVEN TO WORK):</span> |
| <span class="code-comment">// Add example conversation showing correct format</span> |
| <span class="code-keyword">const</span> messages = [ |
| { |
| role: <span class="code-string">"developer"</span>, |
| content: <span class="code-string">"You are a model that can do function calling with the following functions"</span> |
| }, |
| { |
| role: <span class="code-string">"user"</span>, |
| content: <span class="code-string">"What's the temperature in Paris?"</span> |
| }, |
| { |
| role: <span class="code-string">"assistant"</span>, |
| <span class="code-comment">// ✅ Example showing the EXACT format we want</span> |
| content: <span class="code-string">"<start_function_call>call:get_current_temperature{location:<escape>Paris<escape>}<end_function_call>"</span> |
| }, |
| { |
| role: <span class="code-string">"user"</span>, |
| content: query <span class="code-comment">// Your actual query</span> |
| } |
| ]; |
|
|
| <span class="code-comment">// Apply chat template with tools</span> |
| <span class="code-keyword">const</span> inputs = <span class="code-function">await tokenizer</span>.apply_chat_template(messages, { |
| tools: [weatherFunction], |
| tokenize: <span class="code-keyword">true</span>, |
| add_generation_prompt: <span class="code-keyword">true</span>, |
| return_dict: <span class="code-keyword">true</span> |
| }); |
|
|
| <span class="code-comment">// Generate response</span> |
| <span class="code-keyword">const</span> output = <span class="code-function">await model</span>.generate({ |
| ...inputs, |
| max_new_tokens: <span class="code-keyword">512</span>, |
| do_sample: <span class="code-keyword">false</span>, |
| temperature: <span class="code-keyword">0.0</span> |
| }); |
|
|
| <span class="code-comment">// ✅ Result: Correct function call generated!</span> |
| <span class="code-comment">// <start_function_call>call:get_current_temperature{location:<escape>New York<escape>}<end_function_call></span></pre> |
| </div> |
|
|
| <div class="hint-box"> |
| <h4>WHY FEW-SHOT WORKS</h4> |
| <ul style="margin-left: 20px; line-height: 2.5;"> |
| <li>Shows the model the <strong>exact format</strong> we expect</li> |
| <li>Shifts token probabilities in favor of "call:" instead of "error:"</li> |
| <li>Provides context about the task structure</li> |
| <li>Works consistently with the quantized ONNX model</li> |
| </ul> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="lesson-content" data-lesson="5"> |
| <h2 class="lesson-title">TOKEN-LEVEL DEEP DIVE</h2> |
| <div class="lesson-description"> |
| <p>Let's examine what happens at the token level when the model generates function calls.</p> |
|
|
| <div class="interactive-demo"> |
| <h3>TOKEN-LEVEL ANALYSIS</h3> |
| <button onclick="analyzeTokens()">ANALYZE TOKEN GENERATION</button> |
| <div id="tokenAnalysisOutput" class="output-area" style="margin-top: 15px;"></div> |
| <div id="tokenVisualization" class="token-visualization" style="margin-top: 15px;"></div> |
| </div> |
|
|
| <table class="comparison-table"> |
| <thead> |
| <tr> |
| <th>TOKEN ID</th> |
| <th>TOKEN TEXT</th> |
| <th>CONTEXT</th> |
| <th>PROBABILITY SHIFT</th> |
| </tr> |
| </thead> |
| <tbody> |
| <tr> |
| <td>48</td> |
| <td><start_function_call></td> |
| <td>Always correct</td> |
| <td>N/A</td> |
| </tr> |
| <tr> |
| <td>1899</td> |
| <td>"error"</td> |
| <td>Zero-shot (no example)</td> |
| <td>❌ High probability</td> |
| </tr> |
| <tr> |
| <td>6639</td> |
| <td>"call"</td> |
| <td>Few-shot (with example)</td> |
| <td>✅ High probability</td> |
| </tr> |
| <tr> |
| <td>236787</td> |
| <td>":"</td> |
| <td>Always correct</td> |
| <td>N/A</td> |
| </tr> |
| </tbody> |
| </table> |
|
|
| <div class="code-block"> |
| <pre><span class="code-comment">// Token-level analysis of generated output</span> |
| <span class="code-comment">// First 20 generated tokens:</span> |
|
|
| <span class="code-comment">// Token 48: "<start_function_call>" ✅</span> |
| <span class="code-comment">// Token 6639: "call" ✅ (with few-shot) or Token 1899: "error" ❌ (zero-shot)</span> |
| <span class="code-comment">// Token 236787: ":" ✅</span> |
| <span class="code-comment">// Token 828: "get" ✅</span> |
| <span class="code-comment">// Token 236779: "_" ✅</span> |
| <span class="code-comment">// Token 4002: "current" ✅</span> |
| <span class="code-comment">// Token 236779: "_" ✅</span> |
| <span class="code-comment">// Token 27495: "temperature" ✅</span> |
| <span class="code-comment">// Token 236782: "{" ✅</span> |
| <span class="code-comment">// Token 7125: "location" ✅</span> |
| <span class="code-comment">// Token 236787: ":" ✅</span> |
| <span class="code-comment">// Token 52: "<escape>" ✅</span> |
| <span class="code-comment">// Token 27822: "London" ✅</span> |
| <span class="code-comment">// Token 52: "<escape>" ✅</span> |
| <span class="code-comment">// Token 236783: "}" ✅</span> |
| <span class="code-comment">// Token 49: "<end_function_call>" ✅</span> |
|
|
| <span class="code-comment">// The critical decision point is after token 48:</span> |
| <span class="code-comment">// - Without example: Token 1899 ("error") is more likely</span> |
| <span class="code-comment">// - With example: Token 6639 ("call") is more likely</span></pre> |
| </div> |
|
|
| <div class="info-box"> |
| <h4>HYPOTHESIS</h4> |
| <p>The model was trained on function calling data that included error handling examples. Without context, it defaults to the error generation pattern. Few-shot examples provide the necessary context to trigger the correct generation path.</p> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="lesson-content" data-lesson="6"> |
| <h2 class="lesson-title">INTERACTIVE PLAYGROUND</h2> |
| <div class="lesson-description"> |
| <p>Now it's your turn! Experiment with different queries and see how the model responds. Add your own examples to test zero-shot, one-shot, and few-shot approaches.</p> |
|
|
| <div class="playground-grid"> |
| <div class="playground-section"> |
| <h3>FUNCTION SCHEMA</h3> |
| <textarea id="playgroundSchema" rows="15" style="font-family: 'JetBrains Mono', monospace; font-size: 12px;">{ |
| "type": "function", |
| "function": { |
| "name": "get_current_temperature", |
| "description": "Gets the current temperature for a given location.", |
| "parameters": { |
| "type": "object", |
| "properties": { |
| "location": { |
| "type": "string", |
| "description": "The city name, e.g. San Francisco" |
| } |
| }, |
| "required": ["location"] |
| } |
| } |
| }</textarea> |
| </div> |
|
|
| <div class="playground-section"> |
| <h3>SYSTEM MESSAGE</h3> |
| <textarea id="playgroundSystemMessage" rows="3" style="font-family: 'JetBrains Mono', monospace; font-size: 12px; margin-bottom: 15px;">You are a model that can do function calling with the following functions</textarea> |
| |
| <h3 style="margin-top: 20px;">YOUR QUERY</h3> |
| <input type="text" id="playgroundQuery" value="What's the temperature in London?" placeholder="Enter your query..."> |
| <div class="demo-controls" style="margin-top: 15px;"> |
| <button onclick="playgroundTest('zero')" class="btn-danger">ZERO-SHOT</button> |
| <button onclick="playgroundTest('one')">ONE-SHOT</button> |
| <button onclick="playgroundTest('few')" class="btn-success">FEW-SHOT</button> |
| </div> |
| <div style="margin-top: 15px;"> |
| <label style="display: block; margin-bottom: 10px; font-weight: 700;">MAX TOKENS:</label> |
| <input type="number" id="maxTokens" value="512" min="50" max="1024" style="width: 100px;"> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="interactive-demo" style="margin-top: 25px;"> |
| <h3>CUSTOM EXAMPLES</h3> |
| <p style="margin-bottom: 15px; font-size: 0.9em; color: #888;">Add example conversations to use in one-shot and few-shot modes. Each example should show a user query and the expected assistant response with function call.</p> |
| <div id="playgroundExamples" style="margin-bottom: 15px;"> |
| |
| </div> |
| <button onclick="addPlaygroundExample()" style="margin-top: 10px;">+ ADD EXAMPLE</button> |
| <div class="info-box" style="margin-top: 15px;"> |
| <h4>EXAMPLE FORMAT</h4> |
| <p style="font-family: 'JetBrains Mono', monospace; font-size: 0.85em; margin-top: 10px;"> |
| User: "What's the temperature in Paris?"<br> |
| Assistant: "<start_function_call>call:get_current_temperature{location:<escape>Paris<escape>}<end_function_call>" |
| </p> |
| <p style="margin-top: 10px; font-size: 0.9em;">For few-shot, add multiple examples. For one-shot, only the first example will be used. For zero-shot, no examples are used.</p> |
| </div> |
| </div> |
|
|
| <div class="interactive-demo"> |
| <h3>OUTPUT</h3> |
| <div id="playgroundOutput" class="output-area"></div> |
| </div> |
|
|
| <div class="hint-box"> |
| <h4>TIPS FOR EXPERIMENTATION</h4> |
| <ul style="margin-left: 20px; line-height: 2.5;"> |
| <li>Try different cities and locations</li> |
| <li>Compare zero-shot vs few-shot results</li> |
| <li>Modify the function schema and see what happens</li> |
| <li>Add custom examples to test different scenarios</li> |
| <li>Watch the token visualization to understand the generation process</li> |
| <li>Experiment with different max_tokens values</li> |
| </ul> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="lesson-content" data-lesson="7"> |
| <h2 class="lesson-title">RESOURCES & LINKS</h2> |
| <div class="lesson-description"> |
| <p>Explore these resources to deepen your understanding of FunctionGemma, ONNX, and function calling.</p> |
|
|
| <div class="resource-links"> |
| <h3>OFFICIAL DOCUMENTATION</h3> |
| <ul> |
| <li><a href="https://ai.google.dev/gemma/docs/functiongemma" target="_blank">FunctionGemma Model Overview - Google AI</a></li> |
| <li><a href="https://ai.google.dev/gemma/docs/capabilities/function-calling" target="_blank">Function Calling with Gemma - Google AI</a></li> |
| <li><a href="https://ai.google.dev/gemma/docs/functiongemma/full-function-calling-sequence-with-functiongemma" target="_blank">Full Function Calling Sequence - Google AI</a></li> |
| <li><a href="https://blog.google/technology/developers/functiongemma/" target="_blank">FunctionGemma Blog Post - Google</a></li> |
| </ul> |
| </div> |
|
|
| <div class="resource-links"> |
| <h3>TUTORIALS & GUIDES</h3> |
| <ul> |
| <li><a href="https://docs.unsloth.ai/models/functiongemma" target="_blank">FunctionGemma: How to Run & Fine-tune - Unsloth</a></li> |
| <li><a href="https://huggingface.co/onnx-community/functiongemma-270m-it-ONNX" target="_blank">FunctionGemma ONNX Model - Hugging Face</a></li> |
| <li><a href="https://huggingface.co/docs/transformers.js" target="_blank">Transformers.js Documentation</a></li> |
| </ul> |
| </div> |
|
|
| <div class="resource-links"> |
| <h3>ONNX & OPTIMIZATION</h3> |
| <ul> |
| <li><a href="https://onnx.ai/" target="_blank">ONNX - Open Neural Network Exchange</a></li> |
| <li><a href="https://onnx.ai/onnx/" target="_blank">ONNX Runtime Documentation</a></li> |
| <li><a href="https://huggingface.co/docs/optimum/onnxruntime/usage_guides/quantization" target="_blank">ONNX Model Quantization Guide</a></li> |
| </ul> |
| </div> |
|
|
| <div class="resource-links"> |
| <h3>FUNCTION CALLING & AI</h3> |
| <ul> |
| <li><a href="https://platform.openai.com/docs/guides/function-calling" target="_blank">OpenAI Function Calling Guide</a></li> |
| <li><a href="https://ai.google.dev/gemma/docs/functiongemma" target="_blank">Google Function Calling Best Practices</a></li> |
| <li><a href="https://blog.google/technology/developers/functiongemma/" target="_blank">FunctionGemma Physics Playground Demo</a></li> |
| </ul> |
| </div> |
|
|
| <div class="info-box"> |
| <h4>KEY RESOURCES SUMMARY</h4> |
| <p><strong>FunctionGemma</strong> is a specialized 270M parameter model fine-tuned from Google's Gemma 3 for function calling tasks. It's optimized for edge deployment and requires few-shot examples for reliable function call generation.</p> |
| <p><strong>ONNX</strong> (Open Neural Network Exchange) is an open format for representing machine learning models, enabling interoperability between different frameworks and optimized inference across platforms.</p> |
| <p>The model is available in quantized formats (q4 for WebGPU, q8 for WASM) to enable efficient browser-based inference.</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script type="module"> |
| |
| let model = null; |
| let tokenizer = null; |
| let currentLesson = 0; |
| let completedLessons = new Set(); |
| let achievements = new Set(); |
| |
| |
| document.addEventListener('DOMContentLoaded', () => { |
| setupLessonNavigation(); |
| updateProgress(); |
| initializePlayground(); |
| loadModel(); |
| }); |
| |
| function setupLessonNavigation() { |
| document.querySelectorAll('.lesson-item').forEach(item => { |
| item.addEventListener('click', () => { |
| const lessonNum = parseInt(item.dataset.lesson); |
| switchLesson(lessonNum); |
| }); |
| }); |
| } |
| |
| function switchLesson(lessonNum) { |
| |
| document.querySelectorAll('.lesson-item').forEach(item => { |
| item.classList.remove('active'); |
| if (parseInt(item.dataset.lesson) === lessonNum) { |
| item.classList.add('active'); |
| } |
| }); |
| |
| |
| document.querySelectorAll('.lesson-content').forEach(content => { |
| content.classList.remove('active'); |
| if (parseInt(content.dataset.lesson) === lessonNum) { |
| content.classList.add('active'); |
| } |
| }); |
| |
| currentLesson = lessonNum; |
| |
| |
| if (lessonNum === 6) { |
| setTimeout(() => initializePlayground(), 100); |
| } |
| } |
| |
| function completeLesson(lessonNum) { |
| completedLessons.add(lessonNum); |
| document.querySelectorAll('.lesson-item').forEach(item => { |
| if (parseInt(item.dataset.lesson) === lessonNum) { |
| item.classList.add('completed'); |
| } |
| }); |
| updateProgress(); |
| } |
| |
| function addAchievement(text) { |
| achievements.add(text); |
| const achievementsDiv = document.getElementById('achievements'); |
| achievementsDiv.innerHTML = Array.from(achievements).map(a => |
| `<span class="achievement-badge">${a}</span>` |
| ).join(''); |
| } |
| |
| function updateProgress() { |
| const total = 8; |
| const completed = completedLessons.size; |
| const percentage = Math.round((completed / total) * 100); |
| document.getElementById('progressBar').style.width = percentage + '%'; |
| document.getElementById('progressBar').textContent = `${percentage}% COMPLETE`; |
| } |
| |
| function updateLoadingStep(stepId, status) { |
| const step = document.getElementById(stepId); |
| if (!step) return; |
| |
| step.classList.remove('active', 'completed'); |
| if (status === 'active') { |
| step.classList.add('active'); |
| } else if (status === 'completed') { |
| step.classList.add('completed'); |
| } |
| } |
| |
| function log(message, type = 'info', targetId = null) { |
| const colors = { |
| error: '#FF0000', |
| success: '#00FF00', |
| log: '#FFFF00', |
| info: '#00FFFF', |
| warning: '#FF6B6B' |
| }; |
| |
| const icon = { |
| error: '❌', |
| success: '✅', |
| log: '📝', |
| info: 'ℹ️', |
| warning: '⚠️' |
| }; |
| |
| const output = targetId ? document.getElementById(targetId) : null; |
| if (output) { |
| const timestamp = new Date().toLocaleTimeString(); |
| const div = document.createElement('div'); |
| div.style.color = colors[type] || colors.info; |
| div.style.marginBottom = '8px'; |
| div.style.fontFamily = "'JetBrains Mono', monospace"; |
| div.textContent = `[${timestamp}] ${icon[type] || ''} ${message}`; |
| output.appendChild(div); |
| output.scrollTop = output.scrollHeight; |
| } |
| console.log(`[${type.toUpperCase()}]`, message); |
| } |
| |
| async function loadModel() { |
| const overlay = document.getElementById('loadingOverlay'); |
| const statusText = document.getElementById('modelStatusText'); |
| const modelStatus = document.getElementById('modelStatus'); |
| const modelInfoGrid = document.getElementById('modelInfoGrid'); |
| |
| try { |
| updateLoadingStep('step1', 'active'); |
| log('🚀 Starting model load...', 'log'); |
| await new Promise(resolve => setTimeout(resolve, 500)); |
| |
| log('📦 Importing transformers.js...', 'log'); |
| const { env, AutoTokenizer, AutoModelForCausalLM } = await import( |
| "https://cdn.jsdelivr.net/npm/@huggingface/transformers@latest" |
| ); |
| |
| env.allowRemoteModels = true; |
| env.allowLocalModels = false; |
| env.useBrowserCache = true; |
| |
| updateLoadingStep('step1', 'completed'); |
| updateLoadingStep('step2', 'active'); |
| await new Promise(resolve => setTimeout(resolve, 300)); |
| |
| const modelId = "onnx-community/functiongemma-270m-it-ONNX"; |
| |
| log('🔤 Loading tokenizer...', 'log'); |
| tokenizer = await AutoTokenizer.from_pretrained(modelId); |
| log('✅ Tokenizer loaded', 'success'); |
| updateLoadingStep('step2', 'completed'); |
| updateLoadingStep('step3', 'active'); |
| await new Promise(resolve => setTimeout(resolve, 300)); |
| |
| log('🤖 Detecting device capabilities...', 'log'); |
| const hasWebGPU = !!navigator.gpu; |
| const modelConfig = hasWebGPU |
| ? { dtype: "q4", device: "webgpu" } |
| : { dtype: "q8", device: "wasm" }; |
| |
| document.getElementById('modelFormat').textContent = hasWebGPU ? 'q4 (WebGPU)' : 'q8 (WASM)'; |
| document.getElementById('modelDevice').textContent = hasWebGPU ? 'WebGPU' : 'WASM'; |
| modelInfoGrid.style.display = 'grid'; |
| |
| log(`⚙️ Using config: ${JSON.stringify(modelConfig)}`, 'info'); |
| updateLoadingStep('step3', 'completed'); |
| updateLoadingStep('step4', 'active'); |
| await new Promise(resolve => setTimeout(resolve, 300)); |
| |
| log('🤖 Loading model...', 'log'); |
| model = await AutoModelForCausalLM.from_pretrained(modelId, modelConfig); |
| log('✅ Model loaded successfully!', 'success'); |
| updateLoadingStep('step4', 'completed'); |
| updateLoadingStep('step5', 'active'); |
| await new Promise(resolve => setTimeout(resolve, 500)); |
| updateLoadingStep('step5', 'completed'); |
| |
| statusText.textContent = 'READY'; |
| modelStatus.classList.add('loaded'); |
| addAchievement('🎯 MODEL LOADED'); |
| completeLesson(0); |
| |
| |
| setTimeout(() => { |
| overlay.classList.add('hidden'); |
| }, 1000); |
| |
| } catch (error) { |
| log(`❌ Error loading model: ${error.message}`, 'error'); |
| statusText.textContent = 'ERROR'; |
| console.error(error); |
| overlay.innerHTML = ` |
| <div class="loading-content"> |
| <h2 class="loading-title" style="color: #FF0000;">LOADING FAILED</h2> |
| <p style="color: #FFFFFF; font-family: 'JetBrains Mono', monospace; margin-top: 20px;">${error.message}</p> |
| <button onclick="location.reload()" style="margin-top: 30px;">RETRY</button> |
| </div> |
| `; |
| } |
| } |
| |
| async function demonstrateTokenization() { |
| if (!tokenizer) { |
| alert('Model not loaded yet!'); |
| return; |
| } |
| |
| const input = document.getElementById('tokenizeInput').value; |
| const output = document.getElementById('tokenizationOutput'); |
| output.innerHTML = ''; |
| |
| log(`Tokenizing: "${input}"`, 'info', 'tokenizationOutput'); |
| |
| try { |
| const tokens = await tokenizer.encode(input, { return_tensors: false }); |
| log(`Token IDs: [${tokens.join(', ')}]`, 'info', 'tokenizationOutput'); |
| log(`Total tokens: ${tokens.length}`, 'info', 'tokenizationOutput'); |
| |
| |
| const viz = document.createElement('div'); |
| viz.className = 'token-visualization'; |
| viz.style.marginTop = '15px'; |
| |
| for (let i = 0; i < Math.min(tokens.length, 20); i++) { |
| const tokenId = tokens[i]; |
| const tokenText = await tokenizer.decode([tokenId], { skip_special_tokens: false }); |
| |
| const tokenBox = document.createElement('div'); |
| tokenBox.className = 'token-box'; |
| tokenBox.innerHTML = ` |
| <div class="token-id">ID: ${tokenId}</div> |
| <div class="token-text">${tokenText.replace(/</g, '<').replace(/>/g, '>')}</div> |
| `; |
| viz.appendChild(tokenBox); |
| } |
| |
| output.appendChild(viz); |
| addAchievement('🔤 TOKEN MASTER'); |
| |
| } catch (error) { |
| log(`Error: ${error.message}`, 'error', 'tokenizationOutput'); |
| } |
| } |
| |
| async function testZeroShot() { |
| if (!model || !tokenizer) { |
| alert('Model not loaded yet!'); |
| return; |
| } |
| |
| const query = document.getElementById('zeroShotQuery').value; |
| const output = document.getElementById('zeroShotOutput'); |
| output.innerHTML = ''; |
| |
| log('🧪 Testing Zero-Shot Approach...', 'log', 'zeroShotOutput'); |
| log(`Query: "${query}"`, 'info', 'zeroShotOutput'); |
| |
| try { |
| const weatherFunction = { |
| type: "function", |
| function: { |
| name: "get_current_temperature", |
| description: "Gets the current temperature for a given location.", |
| parameters: { |
| type: "object", |
| properties: { |
| location: { |
| type: "string", |
| description: "The city name, e.g. San Francisco", |
| }, |
| }, |
| required: ["location"], |
| }, |
| }, |
| }; |
| |
| const messages = [ |
| { |
| role: "developer", |
| content: "You are a model that can do function calling with the following functions" |
| }, |
| { |
| role: "user", |
| content: query |
| } |
| ]; |
| |
| log('❌ No example provided to the model', 'warning', 'zeroShotOutput'); |
| |
| const inputs = await tokenizer.apply_chat_template(messages, { |
| tools: [weatherFunction], |
| tokenize: true, |
| add_generation_prompt: true, |
| return_dict: true |
| }); |
| |
| const output_tensor = await model.generate({ |
| ...inputs, |
| max_new_tokens: 512, |
| do_sample: false, |
| temperature: 0.0 |
| }); |
| |
| const seqLen = inputs.input_ids.dims[1]; |
| const generated = output_tensor.slice(0, [seqLen, null]); |
| const decoded = await tokenizer.decode(generated, { skip_special_tokens: false }); |
| |
| log('📤 Generated output:', 'info', 'zeroShotOutput'); |
| log(decoded, 'log', 'zeroShotOutput'); |
| |
| if (decoded.includes('error:')) { |
| log('❌ Model generated "error:" instead of "call:"', 'error', 'zeroShotOutput'); |
| log('💡 This is why zero-shot fails!', 'info', 'zeroShotOutput'); |
| } else if (decoded.includes('call:')) { |
| log('✅ Unexpected success! (This is rare)', 'success', 'zeroShotOutput'); |
| } |
| |
| completeLesson(2); |
| |
| } catch (error) { |
| log(`Error: ${error.message}`, 'error', 'zeroShotOutput'); |
| } |
| } |
| |
| async function testOneShot() { |
| if (!model || !tokenizer) { |
| alert('Model not loaded yet!'); |
| return; |
| } |
| |
| const query = document.getElementById('oneShotQuery').value; |
| const output = document.getElementById('oneShotOutput'); |
| output.innerHTML = ''; |
| |
| log('🧪 Testing One-Shot Approach...', 'log', 'oneShotOutput'); |
| log(`Query: "${query}"`, 'info', 'oneShotOutput'); |
| |
| try { |
| const weatherFunction = { |
| type: "function", |
| function: { |
| name: "get_current_temperature", |
| description: "Gets the current temperature for a given location.", |
| parameters: { |
| type: "object", |
| properties: { |
| location: { |
| type: "string", |
| description: "The city name, e.g. San Francisco", |
| }, |
| }, |
| required: ["location"], |
| }, |
| }, |
| }; |
| |
| |
| const messages = [ |
| { |
| role: "developer", |
| content: "You are a model that can do function calling with the following functions" |
| }, |
| { |
| role: "user", |
| content: "What's the temperature in Paris?" |
| }, |
| { |
| role: "assistant", |
| content: "<start_function_call>call:get_current_temperature{location:<escape>Paris<escape>}<end_function_call>" |
| }, |
| { |
| role: "user", |
| content: query |
| } |
| ]; |
| |
| log('⚠️ One example provided', 'info', 'oneShotOutput'); |
| |
| const inputs = await tokenizer.apply_chat_template(messages, { |
| tools: [weatherFunction], |
| tokenize: true, |
| add_generation_prompt: true, |
| return_dict: true |
| }); |
| |
| const output_tensor = await model.generate({ |
| ...inputs, |
| max_new_tokens: 512, |
| do_sample: false, |
| temperature: 0.0 |
| }); |
| |
| const seqLen = inputs.input_ids.dims[1]; |
| const generated = output_tensor.slice(0, [seqLen, null]); |
| const decoded = await tokenizer.decode(generated, { skip_special_tokens: false }); |
| |
| log('📤 Generated output:', 'info', 'oneShotOutput'); |
| log(decoded, 'log', 'oneShotOutput'); |
| |
| if (decoded.includes('call:')) { |
| log('✅ Success! One-shot worked', 'success', 'oneShotOutput'); |
| } else { |
| log('⚠️ One-shot may not always work reliably', 'warning', 'oneShotOutput'); |
| } |
| |
| completeLesson(3); |
| |
| } catch (error) { |
| log(`Error: ${error.message}`, 'error', 'oneShotOutput'); |
| } |
| } |
| |
| async function testFewShot() { |
| if (!model || !tokenizer) { |
| alert('Model not loaded yet!'); |
| return; |
| } |
| |
| const query = document.getElementById('fewShotQuery').value; |
| const output = document.getElementById('fewShotOutput'); |
| output.innerHTML = ''; |
| |
| log('🧪 Testing Few-Shot Approach...', 'log', 'fewShotOutput'); |
| log(`Query: "${query}"`, 'info', 'fewShotOutput'); |
| |
| try { |
| const weatherFunction = { |
| type: "function", |
| function: { |
| name: "get_current_temperature", |
| description: "Gets the current temperature for a given location.", |
| parameters: { |
| type: "object", |
| properties: { |
| location: { |
| type: "string", |
| description: "The city name, e.g. San Francisco", |
| }, |
| }, |
| required: ["location"], |
| }, |
| }, |
| }; |
| |
| |
| const messages = [ |
| { |
| role: "developer", |
| content: "You are a model that can do function calling with the following functions" |
| }, |
| { |
| role: "user", |
| content: "What's the temperature in Paris?" |
| }, |
| { |
| role: "assistant", |
| content: "<start_function_call>call:get_current_temperature{location:<escape>Paris<escape>}<end_function_call>" |
| }, |
| { |
| role: "user", |
| content: query |
| } |
| ]; |
| |
| log('✅ Few-shot example provided', 'success', 'fewShotOutput'); |
| |
| const inputs = await tokenizer.apply_chat_template(messages, { |
| tools: [weatherFunction], |
| tokenize: true, |
| add_generation_prompt: true, |
| return_dict: true |
| }); |
| |
| const output_tensor = await model.generate({ |
| ...inputs, |
| max_new_tokens: 512, |
| do_sample: false, |
| temperature: 0.0 |
| }); |
| |
| const seqLen = inputs.input_ids.dims[1]; |
| const generated = output_tensor.slice(0, [seqLen, null]); |
| const decoded = await tokenizer.decode(generated, { skip_special_tokens: false }); |
| |
| log('📤 Generated output:', 'info', 'fewShotOutput'); |
| log(decoded, 'log', 'fewShotOutput'); |
| |
| if (decoded.includes('call:')) { |
| log('✅ SUCCESS! Few-shot works perfectly!', 'success', 'fewShotOutput'); |
| addAchievement('🎯 FEW-SHOT MASTER'); |
| } |
| |
| completeLesson(4); |
| |
| } catch (error) { |
| log(`Error: ${error.message}`, 'error', 'fewShotOutput'); |
| } |
| } |
| |
| async function analyzeTokens() { |
| if (!model || !tokenizer) { |
| alert('Model not loaded yet!'); |
| return; |
| } |
| |
| const output = document.getElementById('tokenAnalysisOutput'); |
| const viz = document.getElementById('tokenVisualization'); |
| output.innerHTML = ''; |
| viz.innerHTML = ''; |
| |
| log('🔍 Analyzing token generation...', 'log', 'tokenAnalysisOutput'); |
| |
| try { |
| const weatherFunction = { |
| type: "function", |
| function: { |
| name: "get_current_temperature", |
| description: "Gets the current temperature for a given location.", |
| parameters: { |
| type: "object", |
| properties: { |
| location: { |
| type: "string", |
| description: "The city name, e.g. San Francisco", |
| }, |
| }, |
| required: ["location"], |
| }, |
| }, |
| }; |
| |
| const messages = [ |
| { |
| role: "developer", |
| content: "You are a model that can do function calling with the following functions" |
| }, |
| { |
| role: "user", |
| content: "What's the temperature in Paris?" |
| }, |
| { |
| role: "assistant", |
| content: "<start_function_call>call:get_current_temperature{location:<escape>Paris<escape>}<end_function_call>" |
| }, |
| { |
| role: "user", |
| content: "What's the temperature in London?" |
| } |
| ]; |
| |
| const inputs = await tokenizer.apply_chat_template(messages, { |
| tools: [weatherFunction], |
| tokenize: true, |
| add_generation_prompt: true, |
| return_dict: true |
| }); |
| |
| const output_tensor = await model.generate({ |
| ...inputs, |
| max_new_tokens: 512, |
| do_sample: false, |
| temperature: 0.0 |
| }); |
| |
| const seqLen = inputs.input_ids.dims[1]; |
| const generated = output_tensor.slice(0, [seqLen, null]); |
| const generatedTokens = Array.from(generated.data.slice(0, 20)); |
| |
| log('🔢 First 20 generated token IDs:', 'info', 'tokenAnalysisOutput'); |
| log(`[${generatedTokens.join(', ')}]`, 'log', 'tokenAnalysisOutput'); |
| |
| log('🔤 Decoding tokens individually:', 'info', 'tokenAnalysisOutput'); |
| |
| for (let i = 0; i < generatedTokens.length; i++) { |
| const tokenId = generatedTokens[i]; |
| const tokenText = await tokenizer.decode([tokenId], { skip_special_tokens: false }); |
| log(`Token ${tokenId}: "${tokenText}"`, 'info', 'tokenAnalysisOutput'); |
| |
| const tokenBox = document.createElement('div'); |
| tokenBox.className = 'token-box'; |
| tokenBox.innerHTML = ` |
| <div class="token-id">ID: ${tokenId}</div> |
| <div class="token-text">${tokenText.replace(/</g, '<').replace(/>/g, '>')}</div> |
| `; |
| viz.appendChild(tokenBox); |
| } |
| |
| log('✅ Token analysis complete!', 'success', 'tokenAnalysisOutput'); |
| addAchievement('🔍 TOKEN ANALYST'); |
| completeLesson(5); |
| |
| } catch (error) { |
| log(`Error: ${error.message}`, 'error', 'tokenAnalysisOutput'); |
| } |
| } |
| |
| function addPlaygroundExample() { |
| const examplesContainer = document.getElementById('playgroundExamples'); |
| const exampleIndex = examplesContainer.children.length; |
| |
| const exampleDiv = document.createElement('div'); |
| exampleDiv.className = 'example-item'; |
| exampleDiv.dataset.index = exampleIndex; |
| |
| const defaultUserQuery = exampleIndex === 0 ? 'What\'s the temperature in Paris?' : ''; |
| const defaultAssistantResponse = exampleIndex === 0 ? '<start_function_call>call:get_current_temperature{location:<escape>Paris<escape>}<end_function_call>' : ''; |
| |
| exampleDiv.innerHTML = ` |
| <div class="example-item-header"> |
| <h4>EXAMPLE ${exampleIndex + 1}</h4> |
| <button class="remove-example-btn" data-remove-index="${exampleIndex}">REMOVE</button> |
| </div> |
| <label style="display: block; margin-bottom: 5px; font-weight: 700; font-size: 0.9em;">USER QUERY:</label> |
| <input type="text" class="example-user-query" placeholder="Enter user query..." value="${defaultUserQuery}"> |
| <label style="display: block; margin-bottom: 5px; margin-top: 10px; font-weight: 700; font-size: 0.9em;">ASSISTANT RESPONSE (with function call):</label> |
| <textarea class="example-assistant-response" placeholder="Enter assistant response with function call..." rows="2">${defaultAssistantResponse}</textarea> |
| `; |
| |
| |
| const removeBtn = exampleDiv.querySelector('.remove-example-btn'); |
| removeBtn.addEventListener('click', () => { |
| removePlaygroundExample(exampleIndex); |
| }); |
| |
| examplesContainer.appendChild(exampleDiv); |
| } |
| |
| function removePlaygroundExample(index) { |
| const examplesContainer = document.getElementById('playgroundExamples'); |
| const exampleItem = examplesContainer.querySelector(`[data-index="${index}"]`); |
| if (exampleItem) { |
| exampleItem.remove(); |
| |
| Array.from(examplesContainer.children).forEach((item, idx) => { |
| item.dataset.index = idx; |
| item.querySelector('h4').textContent = `EXAMPLE ${idx + 1}`; |
| const removeBtn = item.querySelector('.remove-example-btn'); |
| |
| const newRemoveBtn = removeBtn.cloneNode(true); |
| removeBtn.parentNode.replaceChild(newRemoveBtn, removeBtn); |
| newRemoveBtn.addEventListener('click', () => { |
| removePlaygroundExample(idx); |
| }); |
| }); |
| } |
| } |
| |
| function getPlaygroundExamples() { |
| const examplesContainer = document.getElementById('playgroundExamples'); |
| const examples = []; |
| |
| Array.from(examplesContainer.children).forEach(item => { |
| const userQuery = item.querySelector('.example-user-query').value.trim(); |
| const assistantResponse = item.querySelector('.example-assistant-response').value.trim(); |
| |
| if (userQuery && assistantResponse) { |
| examples.push({ |
| user: userQuery, |
| assistant: assistantResponse |
| }); |
| } |
| }); |
| |
| return examples; |
| } |
| |
| async function playgroundTest(mode) { |
| if (!model || !tokenizer) { |
| alert('Model not loaded yet!'); |
| return; |
| } |
| |
| const query = document.getElementById('playgroundQuery').value; |
| const schemaText = document.getElementById('playgroundSchema').value; |
| const systemMessage = document.getElementById('playgroundSystemMessage').value.trim() || "You are a model that can do function calling with the following functions"; |
| const maxTokens = parseInt(document.getElementById('maxTokens')?.value || 512); |
| const output = document.getElementById('playgroundOutput'); |
| output.innerHTML = ''; |
| |
| let weatherFunction; |
| try { |
| weatherFunction = JSON.parse(schemaText); |
| } catch (e) { |
| log('❌ Invalid JSON schema', 'error', 'playgroundOutput'); |
| return; |
| } |
| |
| |
| const customExamples = getPlaygroundExamples(); |
| |
| log(`🧪 Testing ${mode === 'zero' ? 'Zero-Shot' : mode === 'one' ? 'One-Shot' : 'Few-Shot'} approach...`, 'log', 'playgroundOutput'); |
| log(`Query: "${query}"`, 'info', 'playgroundOutput'); |
| log(`Max Tokens: ${maxTokens}`, 'info', 'playgroundOutput'); |
| log(`Custom Examples: ${customExamples.length}`, 'info', 'playgroundOutput'); |
| |
| try { |
| let messages = [ |
| { |
| role: "developer", |
| content: systemMessage |
| } |
| ]; |
| |
| if (mode === 'zero') { |
| |
| messages.push({ |
| role: "user", |
| content: query |
| }); |
| } else if (mode === 'one') { |
| |
| if (customExamples.length > 0) { |
| messages.push({ |
| role: "user", |
| content: customExamples[0].user |
| }); |
| messages.push({ |
| role: "assistant", |
| content: customExamples[0].assistant |
| }); |
| } else { |
| |
| messages.push({ |
| role: "user", |
| content: "What's the temperature in Paris?" |
| }); |
| messages.push({ |
| role: "assistant", |
| content: `<start_function_call>call:${weatherFunction.function.name}{location:<escape>Paris<escape>}<end_function_call>` |
| }); |
| } |
| messages.push({ |
| role: "user", |
| content: query |
| }); |
| } else { |
| |
| if (customExamples.length > 0) { |
| customExamples.forEach(example => { |
| messages.push({ |
| role: "user", |
| content: example.user |
| }); |
| messages.push({ |
| role: "assistant", |
| content: example.assistant |
| }); |
| }); |
| } else { |
| |
| messages.push({ |
| role: "user", |
| content: "What's the temperature in Paris?" |
| }); |
| messages.push({ |
| role: "assistant", |
| content: `<start_function_call>call:${weatherFunction.function.name}{location:<escape>Paris<escape>}<end_function_call>` |
| }); |
| } |
| messages.push({ |
| role: "user", |
| content: query |
| }); |
| } |
| |
| log(`📝 Message count: ${messages.length}`, 'info', 'playgroundOutput'); |
| |
| const inputs = await tokenizer.apply_chat_template(messages, { |
| tools: [weatherFunction], |
| tokenize: true, |
| add_generation_prompt: true, |
| return_dict: true |
| }); |
| |
| const output_tensor = await model.generate({ |
| ...inputs, |
| max_new_tokens: maxTokens, |
| do_sample: false, |
| temperature: 0.0 |
| }); |
| |
| const seqLen = inputs.input_ids.dims[1]; |
| const generated = output_tensor.slice(0, [seqLen, null]); |
| const decoded = await tokenizer.decode(generated, { skip_special_tokens: false }); |
| |
| log('📤 Generated output:', 'info', 'playgroundOutput'); |
| log(decoded, 'log', 'playgroundOutput'); |
| |
| if (decoded.includes('call:')) { |
| log('✅ Function call generated successfully!', 'success', 'playgroundOutput'); |
| } else if (decoded.includes('error:')) { |
| log('❌ Model generated error instead of call', 'error', 'playgroundOutput'); |
| } |
| |
| completeLesson(6); |
| addAchievement('🎮 PLAYGROUND EXPLORER'); |
| |
| } catch (error) { |
| log(`Error: ${error.message}`, 'error', 'playgroundOutput'); |
| console.error(error); |
| } |
| } |
| |
| |
| function initializePlayground() { |
| const examplesContainer = document.getElementById('playgroundExamples'); |
| if (examplesContainer && examplesContainer.children.length === 0) { |
| addPlaygroundExample(); |
| } |
| } |
| |
| |
| window.loadModel = loadModel; |
| window.demonstrateTokenization = demonstrateTokenization; |
| window.testZeroShot = testZeroShot; |
| window.testOneShot = testOneShot; |
| window.testFewShot = testFewShot; |
| window.analyzeTokens = analyzeTokens; |
| window.playgroundTest = playgroundTest; |
| window.addPlaygroundExample = addPlaygroundExample; |
| window.removePlaygroundExample = removePlaygroundExample; |
| </script> |
| </body> |
| </html> |