mirror of
https://github.com/myronblair/jarvis
synced 2026-07-27 16:22:55 -05:00
Adopt live production state as git baseline
This commit is contained in:
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
/**
|
||||
* JARVIS KB Intent Generator
|
||||
* Generates 1,000+ educational KB intents via Groq LLM and imports to kb_intents table.
|
||||
* Also runs a cleanup pass: deduplication, short-response pruning, pattern normalisation.
|
||||
*
|
||||
* CLI / cron: /usr/bin/php8.3 /var/www/jarvis/api/endpoints/kb_intent_generator.php
|
||||
* Schedule: Weekly – Sunday 3 am
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../config.php';
|
||||
require_once __DIR__ . '/../lib/db.php';
|
||||
|
||||
/* ── helpers ── */
|
||||
function ts(): string { return '[' . date('Y-m-d H:i:s') . ']'; }
|
||||
function log_line(string $msg): void { echo ts() . ' KB Intent Generator: ' . $msg . "\n"; flush(); }
|
||||
function groq(string $system, string $user, int $max = 3000): ?string {
|
||||
$ch = curl_init('https://api.groq.com/openai/v1/chat/completions');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_TIMEOUT => 60,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: Bearer ' . GROQ_API_KEY,
|
||||
'Content-Type: application/json',
|
||||
],
|
||||
CURLOPT_POSTFIELDS => json_encode([
|
||||
'model' => 'llama-3.3-70b-versatile',
|
||||
'max_tokens' => $max,
|
||||
'temperature' => 0.7,
|
||||
'messages' => [
|
||||
['role' => 'system', 'content' => $system],
|
||||
['role' => 'user', 'content' => $user],
|
||||
],
|
||||
]),
|
||||
]);
|
||||
$raw = curl_exec($ch);
|
||||
$err = curl_error($ch);
|
||||
curl_close($ch);
|
||||
if ($err || !$raw) return null;
|
||||
$d = json_decode($raw, true);
|
||||
return $d['choices'][0]['message']['content'] ?? null;
|
||||
}
|
||||
|
||||
/* ── daily guard: skip if already ran within last 20 hours ── */
|
||||
$lastRun = JarvisDB::single(
|
||||
"SELECT updated_at FROM kb_facts WHERE category='kb_generator' AND fact_key='last_run'"
|
||||
);
|
||||
if ($lastRun && (time() - strtotime($lastRun['updated_at'])) < 72000) {
|
||||
log_line('Skipping – ran within last 6 days (next run Sunday 3am).');
|
||||
exit(0);
|
||||
}
|
||||
|
||||
log_line('Starting weekly KB intent generation run.');
|
||||
|
||||
/* ── topic batches (25 topics × ~40 intents = 1,000+) ── */
|
||||
$BATCHES = [
|
||||
['id' => 'math_elem', 'category' => 'math_elementary', 'topic' => 'Elementary school mathematics',
|
||||
'desc' => 'counting, basic arithmetic, place value, simple fractions, 2D/3D shapes, measurement, telling time, money, patterns'],
|
||||
['id' => 'math_mid', 'category' => 'math_middle', 'topic' => 'Middle school mathematics',
|
||||
'desc' => 'ratios, percentages, integers, exponents, pre-algebra equations, coordinate planes, probability, statistics (mean/median/mode), geometry area/volume'],
|
||||
['id' => 'math_high', 'category' => 'math_high', 'topic' => 'High school mathematics',
|
||||
'desc' => 'quadratic equations, polynomials, functions, logarithms, trigonometry (SOH-CAH-TOA, unit circle), sequences, permutations and combinations, matrices'],
|
||||
['id' => 'math_college', 'category' => 'math_college', 'topic' => 'College-level mathematics',
|
||||
'desc' => 'limits and continuity, derivatives, integrals, chain rule, L\'Hopital, differential equations, vectors, eigenvalues, hypothesis testing, normal distribution'],
|
||||
['id' => 'bio_cell', 'category' => 'biology', 'topic' => 'Cell biology and genetics',
|
||||
'desc' => 'organelles, mitosis vs meiosis, DNA structure, protein synthesis (transcription/translation), Mendelian genetics, dominant/recessive, mutations, genetic disorders'],
|
||||
['id' => 'bio_body', 'category' => 'biology', 'topic' => 'Human body systems',
|
||||
'desc' => 'skeletal, muscular, cardiovascular, respiratory, digestive, nervous, endocrine, immune, reproductive, and excretory systems'],
|
||||
['id' => 'bio_ecology', 'category' => 'biology', 'topic' => 'Ecology and evolution',
|
||||
'desc' => 'ecosystems, biomes, food webs, trophic levels, symbiosis (mutualism/commensalism/parasitism), natural selection, adaptation, speciation, biodiversity'],
|
||||
['id' => 'chem_basics', 'category' => 'chemistry', 'topic' => 'Chemistry fundamentals',
|
||||
'desc' => 'atomic structure, periodic table trends, ionic vs covalent bonds, Lewis structures, polarity, molecular geometry (VSEPR), intermolecular forces'],
|
||||
['id' => 'chem_rxns', 'category' => 'chemistry', 'topic' => 'Chemical reactions and thermochemistry',
|
||||
'desc' => 'balancing equations, stoichiometry, limiting reagents, reaction types, enthalpy, entropy, Gibbs free energy, Le Chatelier\'s principle, equilibrium constants'],
|
||||
['id' => 'phys_mech', 'category' => 'physics', 'topic' => 'Physics – mechanics and energy',
|
||||
'desc' => 'kinematics, Newton\'s three laws, friction, momentum, conservation of energy, work and power, circular motion, gravitation, projectile motion'],
|
||||
['id' => 'phys_em', 'category' => 'physics', 'topic' => 'Physics – waves, electricity and magnetism',
|
||||
'desc' => 'wave properties (amplitude, frequency, wavelength), sound, light spectrum, reflection/refraction, Ohm\'s law, series vs parallel circuits, magnetic fields, electromagnetic induction'],
|
||||
['id' => 'earth_sci', 'category' => 'earth_science', 'topic' => 'Earth and environmental science',
|
||||
'desc' => 'rock cycle, plate tectonics, earthquakes, volcanoes, atmosphere layers, weather vs climate, greenhouse effect, water cycle, ocean currents, soil formation'],
|
||||
['id' => 'astronomy', 'category' => 'astronomy', 'topic' => 'Astronomy and space science',
|
||||
'desc' => 'solar system planets, star life cycles, galaxies, Big Bang theory, light-years, telescopes, space exploration milestones, dark matter/energy, black holes'],
|
||||
['id' => 'hist_us', 'category' => 'history_us', 'topic' => 'United States history',
|
||||
'desc' => 'colonial era, American Revolution, Constitution, westward expansion, Civil War, Reconstruction, Gilded Age, WWI, Great Depression, WWII, Civil Rights, Cold War, modern era'],
|
||||
['id' => 'hist_world', 'category' => 'history_world', 'topic' => 'World history',
|
||||
'desc' => 'ancient civilisations (Egypt, Greece, Rome, Mesopotamia, China, India), Medieval period, Renaissance, Reformation, age of exploration, colonialism, Industrial Revolution, WWI, WWII, decolonisation, Cold War'],
|
||||
['id' => 'geo_world', 'category' => 'geography', 'topic' => 'World geography',
|
||||
'desc' => 'continents and oceans, countries and capitals, physical features (mountains, rivers, deserts), climate zones, latitude/longitude, map projections, time zones, population distribution'],
|
||||
['id' => 'civics', 'category' => 'civics', 'topic' => 'Civics and US government',
|
||||
'desc' => 'three branches of government, checks and balances, Bill of Rights, Constitutional amendments, federalism, Electoral College, legislative process, Supreme Court landmark cases, political parties'],
|
||||
['id' => 'econ', 'category' => 'economics', 'topic' => 'Economics',
|
||||
'desc' => 'supply and demand, market equilibrium, elasticity, GDP, inflation, unemployment, fiscal vs monetary policy, comparative advantage, market structures (monopoly, oligopoly, perfect competition), opportunity cost'],
|
||||
['id' => 'lit_writing', 'category' => 'literature', 'topic' => 'Literature and writing',
|
||||
'desc' => 'figurative language (simile, metaphor, personification, irony), plot structure, literary themes, point of view, genre types, essay structure, thesis statements, grammar rules, poetry forms, citing sources'],
|
||||
['id' => 'cs_prog', 'category' => 'computer_science', 'topic' => 'Computer science and programming',
|
||||
'desc' => 'variables, data types, loops, conditionals, functions, OOP concepts, arrays/lists, sorting and searching algorithms, time/space complexity, binary, hexadecimal, recursion, debugging'],
|
||||
['id' => 'cs_systems', 'category' => 'computer_science', 'topic' => 'Computer systems and networking',
|
||||
'desc' => 'CPU/RAM/storage, operating systems, file systems, TCP/IP, DNS, HTTP/HTTPS, databases (SQL vs NoSQL), cybersecurity threats (phishing, SQL injection, XSS, malware), encryption basics, cloud computing'],
|
||||
['id' => 'psych', 'category' => 'psychology', 'topic' => 'Psychology',
|
||||
'desc' => 'classical and operant conditioning, Maslow\'s hierarchy, Piaget\'s stages, Erikson\'s stages, memory types, sleep stages, cognitive biases, Freud\'s theory, social influence, abnormal psychology basics'],
|
||||
['id' => 'phil', 'category' => 'philosophy', 'topic' => 'Philosophy and logic',
|
||||
'desc' => 'Socrates/Plato/Aristotle, ethical theories (utilitarianism, deontology, virtue ethics), epistemology, deductive vs inductive reasoning, logical fallacies, existentialism, free will vs determinism, political philosophy'],
|
||||
['id' => 'health_sci', 'category' => 'health', 'topic' => 'Health and life skills',
|
||||
'desc' => 'nutrition (macronutrients, vitamins, minerals), exercise physiology, mental health (anxiety, depression, stress response), reproductive health, substance abuse, first aid, disease prevention, sleep hygiene'],
|
||||
['id' => 'arts_music', 'category' => 'arts', 'topic' => 'Arts and music',
|
||||
'desc' => 'elements of art (line, shape, color, texture), colour theory, major art movements, famous artists and their works, music notes and scales, rhythm and meter, instrument families, major composers and genres'],
|
||||
];
|
||||
|
||||
/* ── system prompt ── */
|
||||
$SYSTEM = <<<'SYS'
|
||||
You are an expert educator generating KB (knowledge-base) intents for an AI assistant called JARVIS.
|
||||
Each intent is a question/phrase a student might ask, paired with a clear educational answer.
|
||||
|
||||
Respond ONLY with a valid JSON array (no markdown, no backticks, no commentary).
|
||||
Each element must have exactly these keys:
|
||||
"n" – intent_name: unique snake_case identifier ≤ 60 chars, prefixed with the batch id given
|
||||
"p" – pattern: a PHP PCRE regex (use (?i) for case-insensitive) that matches the question
|
||||
"r" – response: a thorough but concise educational answer (2–5 sentences or a short structured list)
|
||||
"c" – category: the category string provided
|
||||
|
||||
Rules:
|
||||
- Patterns must use \\b word boundaries; escape backslashes for JSON (\\b not \b)
|
||||
- Patterns should NOT start with ^ or end with $ (they are substring matches)
|
||||
- Responses must be factually accurate
|
||||
- Do not duplicate intent names; every "n" must be unique within this batch
|
||||
- Return exactly 40 intents
|
||||
SYS;
|
||||
|
||||
/* ── insert helper ── */
|
||||
$inserted = 0;
|
||||
$skipped = 0;
|
||||
$errors = 0;
|
||||
|
||||
function safe_insert(array $intent, string $batchCategory): void {
|
||||
global $inserted, $skipped, $errors;
|
||||
$name = trim($intent['n'] ?? '');
|
||||
$pattern = trim($intent['p'] ?? '');
|
||||
$response = trim($intent['r'] ?? '');
|
||||
$category = trim($intent['c'] ?? $batchCategory);
|
||||
|
||||
if (!$name || !$pattern || !$response) { $errors++; return; }
|
||||
if (strlen($name) > 64) $name = substr($name, 0, 64);
|
||||
if (strlen($pattern) > 512) $pattern = substr($pattern, 0, 512);
|
||||
if (strlen($response) < 30) { $skipped++; return; } // too short
|
||||
|
||||
try {
|
||||
JarvisDB::execute(
|
||||
'INSERT INTO kb_intents (intent_name, pattern, response_template, fact_category, action_type, priority, active)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
pattern=VALUES(pattern),
|
||||
response_template=VALUES(response_template),
|
||||
fact_category=VALUES(fact_category)',
|
||||
[$name, $pattern, $response, $category, 'response', 5, 1]
|
||||
);
|
||||
$inserted++;
|
||||
} catch (Exception $e) {
|
||||
$errors++;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── main generation loop ── */
|
||||
$totalBatches = count($BATCHES);
|
||||
foreach ($BATCHES as $idx => $batch) {
|
||||
$num = $idx + 1;
|
||||
log_line("Batch {$num}/{$totalBatches}: {$batch['topic']}");
|
||||
|
||||
$user = "Generate 40 KB intents for the topic: {$batch['topic']}.\n"
|
||||
. "Subtopics to cover: {$batch['desc']}.\n"
|
||||
. "Prefix every intent_name with \"{$batch['id']}_\".\n"
|
||||
. "Category string to use: \"{$batch['category']}\".";
|
||||
|
||||
$raw = groq($SYSTEM, $user, 3500);
|
||||
if ($raw === null) {
|
||||
log_line(" ✗ API call failed – skipping batch.");
|
||||
$errors += 40;
|
||||
sleep(3);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Strip markdown code fences if model added them
|
||||
$raw = preg_replace('/^```(?:json)?\s*/m', '', $raw);
|
||||
$raw = preg_replace('/^```\s*/m', '', $raw);
|
||||
$raw = trim($raw);
|
||||
|
||||
// Extract JSON array (even if the model added preamble text)
|
||||
if (!preg_match('/\[\s*\{.*\}\s*\]/s', $raw, $m)) {
|
||||
log_line(" ✗ No JSON array found in response – skipping batch.");
|
||||
$errors += 40;
|
||||
sleep(2);
|
||||
continue;
|
||||
}
|
||||
$items = json_decode($m[0], true);
|
||||
if (!is_array($items)) {
|
||||
log_line(" ✗ JSON parse failed – skipping batch.");
|
||||
$errors += 40;
|
||||
sleep(2);
|
||||
continue;
|
||||
}
|
||||
|
||||
$batchInserted = 0;
|
||||
foreach ($items as $item) {
|
||||
if (!is_array($item)) continue;
|
||||
safe_insert($item, $batch['category']);
|
||||
$batchInserted++;
|
||||
}
|
||||
log_line(" ✓ Parsed {$batchInserted} intents (running total inserted: {$inserted}).");
|
||||
|
||||
// Polite delay between API calls to avoid rate limiting
|
||||
if ($num < $totalBatches) sleep(2);
|
||||
}
|
||||
|
||||
log_line("Generation complete. Inserted/updated: {$inserted} | Short/invalid skipped: {$skipped} | Errors: {$errors}");
|
||||
|
||||
/* ── cleanup phase ── */
|
||||
log_line('Starting cleanup phase...');
|
||||
|
||||
// 1. Remove exact duplicate intent_names (keep the one with the longer response)
|
||||
$dups = JarvisDB::query(
|
||||
'SELECT intent_name, COUNT(*) AS cnt FROM kb_intents GROUP BY intent_name HAVING cnt > 1'
|
||||
);
|
||||
$dupsPruned = 0;
|
||||
foreach ($dups as $dup) {
|
||||
$rows = JarvisDB::query(
|
||||
'SELECT id, LENGTH(response_template) AS rlen FROM kb_intents WHERE intent_name=? ORDER BY rlen DESC',
|
||||
[$dup['intent_name']]
|
||||
);
|
||||
// Delete all but the first (longest response)
|
||||
array_shift($rows);
|
||||
foreach ($rows as $row) {
|
||||
JarvisDB::execute('DELETE FROM kb_intents WHERE id=?', [$row['id']]);
|
||||
$dupsPruned++;
|
||||
}
|
||||
}
|
||||
log_line(" Duplicate intent_names pruned: {$dupsPruned}");
|
||||
|
||||
// 2. Remove intents with very short responses (< 40 chars) — likely garbage
|
||||
$shortPruned = JarvisDB::execute(
|
||||
"DELETE FROM kb_intents WHERE LENGTH(response_template) < 40 AND priority <= 5"
|
||||
);
|
||||
log_line(" Short-response rows pruned: {$shortPruned}");
|
||||
|
||||
// 3. Trim whitespace on all text columns for the auto-generated ones
|
||||
JarvisDB::execute(
|
||||
"UPDATE kb_intents SET
|
||||
intent_name = TRIM(intent_name),
|
||||
pattern = TRIM(pattern),
|
||||
response_template = TRIM(response_template),
|
||||
fact_category = TRIM(fact_category)
|
||||
WHERE priority = 5"
|
||||
);
|
||||
log_line(' Whitespace trimmed on all generated intents.');
|
||||
|
||||
// 4. Deactivate intents whose pattern is invalid PCRE
|
||||
$all = JarvisDB::query('SELECT id, pattern FROM kb_intents WHERE active=1 AND priority=5');
|
||||
$badPattern = 0;
|
||||
foreach ($all as $row) {
|
||||
if (@preg_match($row['pattern'], '') === false) {
|
||||
JarvisDB::execute('UPDATE kb_intents SET active=0 WHERE id=?', [$row['id']]);
|
||||
$badPattern++;
|
||||
}
|
||||
}
|
||||
log_line(" Bad PCRE patterns deactivated: {$badPattern}");
|
||||
|
||||
// 5. Stats
|
||||
$stats = JarvisDB::single('SELECT COUNT(*) AS total, SUM(active) AS active FROM kb_intents');
|
||||
log_line("Final KB Intents table: {$stats['total']} total, {$stats['active']} active.");
|
||||
|
||||
/* ── record last-run timestamp in kb_facts ── */
|
||||
JarvisDB::execute(
|
||||
"INSERT INTO kb_facts (category, fact_key, fact_value, host)
|
||||
VALUES ('kb_generator', 'last_run', NOW(), 'local')
|
||||
ON DUPLICATE KEY UPDATE fact_value=NOW(), updated_at=NOW()",
|
||||
[]
|
||||
);
|
||||
JarvisDB::execute(
|
||||
"INSERT INTO kb_facts (category, fact_key, fact_value, host)
|
||||
VALUES ('kb_generator', 'last_inserted', ?, 'local')
|
||||
ON DUPLICATE KEY UPDATE fact_value=VALUES(fact_value), updated_at=NOW()",
|
||||
[$inserted]
|
||||
);
|
||||
|
||||
log_line('Done.');
|
||||
Reference in New Issue
Block a user