mirror of
https://github.com/myronblair/jarvis
synced 2026-06-30 17:50:23 -05:00
dc55e6c45b
- 4-tier chat: HA control → Ollama → Groq → Claude - Push-based agent system with heartbeat/metrics - Network monitoring, alerts, Proxmox, Home Assistant - Windows + Linux agent installers - Stats cache cron, facts collector, KB engine
140 lines
4.8 KiB
PHP
140 lines
4.8 KiB
PHP
<?php
|
|
/**
|
|
* JARVIS Knowledge Base + Intent Engine
|
|
* Matches user input against intent patterns, substitutes live facts from kb_facts.
|
|
* Returns a response if matched, or null to escalate to Ollama/Claude.
|
|
*/
|
|
|
|
class KBEngine {
|
|
|
|
/**
|
|
* Try to match the input against known intents.
|
|
* Returns ['reply' => string, 'intent' => string] or null if no match.
|
|
*/
|
|
public static function match(string $input): ?array {
|
|
$intents = JarvisDB::query(
|
|
'SELECT * FROM kb_intents WHERE active=1 ORDER BY priority DESC, id ASC'
|
|
);
|
|
if (!$intents) return null;
|
|
|
|
foreach ($intents as $intent) {
|
|
$pat = '~' . $intent['pattern'] . '~';
|
|
if (@preg_match($pat, $input)) {
|
|
$reply = self::fillTemplate(
|
|
$intent['response_template'],
|
|
$intent['fact_category']
|
|
);
|
|
return [
|
|
'reply' => $reply,
|
|
'intent' => $intent['intent_name'],
|
|
'action' => $intent['action_type'],
|
|
];
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Replace {placeholder} tokens in a template with values from kb_facts,
|
|
* plus built-in dynamic tokens like {current_time}.
|
|
*/
|
|
private static function fillTemplate(string $template, ?string $category): string {
|
|
// Built-in tokens
|
|
$builtins = [
|
|
'current_time' => date('g:i A'),
|
|
'current_date' => date('l, F j Y'),
|
|
];
|
|
|
|
// Fetch all facts for this category (and null-category universal facts)
|
|
$facts = [];
|
|
if ($category) {
|
|
$rows = JarvisDB::query(
|
|
'SELECT fact_key, fact_value FROM kb_facts
|
|
WHERE category = ?',
|
|
[$category]
|
|
);
|
|
foreach ($rows as $r) {
|
|
$facts[$r['fact_key']] = $r['fact_value'];
|
|
}
|
|
}
|
|
// Also pull network facts for network tokens used in any template
|
|
if (strpos($template, '{online_count}') !== false || strpos($template, '{total_count}') !== false) {
|
|
$netRows = JarvisDB::query(
|
|
"SELECT fact_key, fact_value FROM kb_facts
|
|
WHERE category='network'"
|
|
);
|
|
foreach ($netRows as $r) {
|
|
$facts[$r['fact_key']] = $r['fact_value'];
|
|
}
|
|
}
|
|
|
|
$allTokens = array_merge($builtins, $facts);
|
|
|
|
// Replace placeholders
|
|
return preg_replace_callback('/\{([a-z0-9_]+)\}/', function ($m) use ($allTokens) {
|
|
return $allTokens[$m[1]] ?? '[unknown]';
|
|
}, $template);
|
|
}
|
|
|
|
/**
|
|
* Store a fact in kb_facts (upsert).
|
|
*/
|
|
public static function storeFact(
|
|
string $category,
|
|
string $key,
|
|
string $value,
|
|
string $host = 'local',
|
|
?int $ttlSeconds = null
|
|
): void {
|
|
$expires = $ttlSeconds ? gmdate('Y-m-d H:i:s', time() + $ttlSeconds) : null;
|
|
JarvisDB::execute(
|
|
'INSERT INTO kb_facts (category, fact_key, fact_value, host, expires_at)
|
|
VALUES (?,?,?,?,?)
|
|
ON DUPLICATE KEY UPDATE fact_value=VALUES(fact_value), expires_at=VALUES(expires_at)',
|
|
[$category, $key, $value, $host, $expires]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Learn from conversation — store interesting facts the user mentions.
|
|
*/
|
|
public static function learnFromConversation(string $input, string $reply): void {
|
|
// Preference learning: user states a preference
|
|
if (preg_match('/(?i)i (prefer|like|want|always)\s+(.+?)(?:\.|$)/', $input, $m)) {
|
|
$pref = trim($m[2]);
|
|
if (strlen($pref) < 120) {
|
|
JarvisDB::execute(
|
|
'INSERT INTO kb_preferences (pref_key, pref_value)
|
|
VALUES (?,?)
|
|
ON DUPLICATE KEY UPDATE pref_value=VALUES(pref_value)',
|
|
['learned_' . md5($pref), $pref]
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return a summary of what the KB knows (for system prompt injection).
|
|
*/
|
|
public static function getContextSummary(): string {
|
|
// Exclude entity_map — too large for Ollama 1B tokenizer
|
|
$facts = JarvisDB::query(
|
|
"SELECT category, fact_key, fact_value FROM kb_facts
|
|
WHERE fact_key != 'entity_map'
|
|
ORDER BY category, updated_at DESC"
|
|
);
|
|
if (!$facts) return '';
|
|
|
|
$byCategory = [];
|
|
foreach ($facts as $f) {
|
|
$byCategory[$f['category']][] = "{$f['fact_key']}={$f['fact_value']}";
|
|
}
|
|
|
|
$lines = [];
|
|
foreach ($byCategory as $cat => $items) {
|
|
$lines[] = strtoupper($cat) . ': ' . implode(', ', array_slice($items, 0, 8));
|
|
}
|
|
return implode("\n", $lines);
|
|
}
|
|
}
|