Files
neuron/elp-input.el
T
Will Anderson af2ce3ddf3 Fix ELP topic selection and replace broken agentic LLM call
elp-input.el: walk frame_nodes to pick the first node whose content
contains the query topic (case-insensitive) rather than always taking
index 0 — prevents the always-high-salience CGI architecture memory
from hijacking every ELP response.

chat.el: rewrite handle_chat_agentic to run the tool loop natively in
El using http_post_with_headers + Map headers, bypassing the broken
llm_call_agentic(model, system, message, tools) C binding that cast a
JSON String as an ElList and never serialized tools. New impl supports
up to 8 tool-use iterations with read_file, write_file, web_get,
search_memory, and run_command dispatch.
2026-05-03 11:50:18 -05:00

157 lines
7.6 KiB
EmacsLisp

import "../foundation/elp/src/elp.el"
// elp-input.el Convert free text semantic frame for ELP input parsing.
//
// This is lightweight NLU: extracts predicate, arguments, and topic
// from a user message without calling an LLM. Covers the common cases:
// - "What is X?" predicate=tell, arg=X
// - "Tell me about X" predicate=tell, arg=X
// - "How do you feel about X?" predicate=express, arg=X
// - "Remember X" predicate=store, arg=X
// - "Who is X?" predicate=identify, arg=X
// - "Why X?" predicate=explain, arg=X
// - fallback predicate=tell, arg=full message as topic
fn elp_extract_topic(msg: String) -> String {
// Strip common question prefixes to get the core topic
let m1: String = if str_starts_with(msg, "What is ") { str_slice(msg, 8, str_len(msg)) } else { msg }
let m2: String = if str_starts_with(m1, "What are ") { str_slice(m1, 9, str_len(m1)) } else { m1 }
let m3: String = if str_starts_with(m2, "Tell me about ") { str_slice(m2, 14, str_len(m2)) } else { m2 }
let m4: String = if str_starts_with(m3, "Who is ") { str_slice(m3, 7, str_len(m3)) } else { m3 }
let m5: String = if str_starts_with(m4, "Who are ") { str_slice(m4, 8, str_len(m4)) } else { m4 }
let m6: String = if str_starts_with(m5, "How do you ") { str_slice(m5, 11, str_len(m5)) } else { m5 }
let m7: String = if str_starts_with(m6, "Why ") { str_slice(m6, 4, str_len(m6)) } else { m6 }
let m8: String = if str_starts_with(m7, "Explain ") { str_slice(m7, 8, str_len(m7)) } else { m7 }
// Strip trailing punctuation
let last: Int = str_len(m8) - 1
let trail: String = str_slice(m8, last, str_len(m8))
let clean: String = if str_eq(trail, "?") || str_eq(trail, ".") || str_eq(trail, "!") {
str_slice(m8, 0, last)
} else {
m8
}
return clean
}
fn elp_detect_predicate(msg: String) -> String {
if str_starts_with(msg, "What is ") || str_starts_with(msg, "What are ") || str_starts_with(msg, "Tell me about ") {
return "tell"
}
if str_starts_with(msg, "Who is ") || str_starts_with(msg, "Who are ") {
return "identify"
}
if str_starts_with(msg, "Why ") || str_starts_with(msg, "Explain ") {
return "explain"
}
if str_starts_with(msg, "How do you feel") || str_starts_with(msg, "Do you ") {
return "express"
}
if str_starts_with(msg, "Remember ") || str_starts_with(msg, "Store ") {
return "store"
}
return "tell"
}
fn elp_parse(msg: String) -> String {
let predicate: String = elp_detect_predicate(msg)
let topic: String = elp_extract_topic(msg)
let safe_topic: String = str_replace(topic, "\"", "'")
return "{\"predicate\":\"" + predicate + "\",\"args\":[\"" + safe_topic + "\"],\"modifiers\":[],\"context\":{}}"
}
fn handle_elp_chat(body: String) -> String {
let message: String = json_get(body, "message")
if str_eq(message, "") {
return "{\"error\":\"message required\",\"response\":\"\"}"
}
let frame: String = elp_parse(message)
let predicate: String = elp_detect_predicate(message)
let topic: String = elp_extract_topic(message)
// Layer 1: Activate
// Graph walk from the extracted topic. Falls back to full message, then
// to a shallow scan if both activation paths return empty.
let from_topic: String = engram_activate_json(topic, 10)
let topic_ok: Bool = !str_eq(from_topic, "") && !str_eq(from_topic, "[]")
let candidates: String = if topic_ok {
from_topic
} else {
let from_msg: String = engram_activate_json(message, 10)
let msg_ok: Bool = !str_eq(from_msg, "") && !str_eq(from_msg, "[]")
if msg_ok { from_msg } else { engram_scan_nodes_json(5, 0) }
}
// Layer 2: Suppress / Filter
// Walk the candidates keeping nodes that have non-zero salience or
// importance. Always keep at least one (the top activation hit) even if
// all metrics are zero. Cap at 3 nodes enough for a coherent reply.
let total: Int = json_array_len(candidates)
let fi: Int = 0
let kept_count: Int = 0
let kept_json: String = ""
while fi < total {
let n: String = json_array_get(candidates, fi)
let sal_str: String = json_get(n, "salience")
let imp_str: String = json_get(n, "importance")
let sal_ok: Bool = !str_eq(sal_str, "0") && !str_eq(sal_str, "0.0") && !str_eq(sal_str, "")
let imp_ok: Bool = !str_eq(imp_str, "0") && !str_eq(imp_str, "0.0") && !str_eq(imp_str, "")
let keep_it: Bool = sal_ok || imp_ok || kept_count == 0
if keep_it && kept_count < 3 {
let sep: String = if str_eq(kept_json, "") { "" } else { "," }
let kept_json = kept_json + sep + n
let kept_count = kept_count + 1
}
let fi = fi + 1
}
let frame_nodes: String = if str_eq(kept_json, "") { "[]" } else { "[" + kept_json + "]" }
// Reason
// Walk frame_nodes to find the first node whose content contains the topic.
// This prevents the always-high-salience CGI architecture node from
// dominating every response regardless of what was asked.
let fn_total: Int = json_array_len(frame_nodes)
let fn_i: Int = 0
let topic_lower: String = str_to_lower(topic)
let found_node: String = ""
while fn_i < fn_total {
let candidate: String = json_array_get(frame_nodes, fn_i)
let cand_content: String = json_get(candidate, "content")
let cand_lower: String = str_to_lower(cand_content)
let matches: Bool = str_contains(cand_lower, topic_lower)
if matches && str_eq(found_node, "") {
let found_node = candidate
}
let fn_i = fn_i + 1
}
let top_node: String = if str_eq(found_node, "") { json_array_get(frame_nodes, 0) } else { found_node }
let top_raw: String = json_get(top_node, "content")
let patient_raw: String = if str_eq(top_raw, "") { topic } else {
if str_len(top_raw) > 200 { str_slice(top_raw, 0, 200) } else { top_raw }
}
let patient_safe: String = str_replace(str_replace(patient_raw, "\"", "'"), "\n", " ")
// Generate
// Map ELP predicate intent, then realize through the ELP grammar engine.
let intent_val: String = if str_eq(predicate, "store") { "command" } else { "assert" }
let gen_form: String = "{\"intent\":\"" + intent_val + "\""
+ ",\"agent\":\"I\""
+ ",\"predicate\":\"" + predicate + "\""
+ ",\"patient\":\"" + patient_safe + "\""
+ ",\"tense\":\"present\",\"aspect\":\"simple\",\"lang\":\"en\"}"
let realized: String = generate(gen_form)
let response: String = if str_eq(realized, "") {
if str_eq(patient_safe, "") {
"Nothing in the engram matched that query."
} else {
patient_safe
}
} else {
realized
}
let safe_resp: String = str_replace(str_replace(response, "\"", "'"), "\r", "")
return "{\"response\":\"" + safe_resp + "\",\"model\":\"elp-native\",\"frame\":" + frame + ",\"nodes\":" + frame_nodes + "}"
}