|
|
<!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> |