v0.15.9: Auto-DESCRIBE retry, unmapped table recovery, animation queue

Expert retry loop enhanced:
- On "Unknown column" error, auto-DESCRIBEs the failing table
- DESCRIBE result injected into re-plan context
- Unmapped tables handled via SELECT * LIMIT fallback
- Recovery test step 4: abrechnungsinformationen (unmapped) → success

Graph animation queue:
- Events queued and played sequentially with 200ms interval
- Prevents bulk HUD events from canceling each other's animations
- Node pulses and edge flashes play one by one

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nico 2026-03-29 19:43:33 +02:00
parent 067cbccea6
commit d8ab778257
4 changed files with 52 additions and 6 deletions

View File

@ -128,8 +128,8 @@ NE → Geraete: JOIN geraete g ON g.NutzeinheitID = ne.ID
Geraet Verbrauch: JOIN geraeteverbraeuche gv ON gv.GeraetID = g.ID
RULES:
- NEVER use DESCRIBE at runtime. You know the schema.
- NEVER guess column names. Use ONLY columns listed above.
- For unknown tables: return an error, do not explore.
- For tables listed above: use ONLY the listed column names. Never guess.
- For tables NOT listed above: use SELECT * with LIMIT to discover columns.
- If a query fails, the retry system will show you the error. Fix the column name and try again.
- Always LIMIT large queries (max 50 rows).
- Use LEFT JOIN when results might be empty."""

View File

@ -94,7 +94,9 @@ Write a concise, natural response. 1-3 sentences.
plan_prompt += "\n\nPREVIOUS ATTEMPTS FAILED:\n"
for err in errors_so_far:
plan_prompt += f"- Query: {err['query']}\n Error: {err['error']}\n"
plan_prompt += "\nFix the query using ONLY columns from the schema. Do NOT repeat the same mistake."
if 'describe' in err:
plan_prompt += f" DESCRIBE result: {err['describe'][:300]}\n"
plan_prompt += "\nFix the query. If a column was unknown, use the DESCRIBE result above or try SELECT * LIMIT 3 to see actual columns."
plan_messages = [
{"role": "system", "content": self.PLAN_SYSTEM.format(
@ -143,11 +145,28 @@ Write a concise, natural response. 1-3 sentences.
try:
result = await asyncio.to_thread(run_db_query, query, database)
if result.startswith("Error:"):
errors_so_far.append({"query": query, "error": result})
err_entry = {"query": query, "error": result}
# Auto-DESCRIBE on column errors to help retry
if "Unknown column" in result or "1054" in result:
import re
# Extract table name from query
tables_in_query = re.findall(r'FROM\s+(\w+)|JOIN\s+(\w+)', query, re.IGNORECASE)
for match in tables_in_query:
tname = match[0] or match[1]
if tname:
try:
desc = await asyncio.to_thread(run_db_query, f"DESCRIBE {tname}", database)
err_entry["describe"] = f"{tname}: {desc[:300]}"
await self.hud("tool_result", tool="describe",
output=f"Auto-DESCRIBE {tname}")
except Exception:
pass
break
errors_so_far.append(err_entry)
had_error = True
await self.hud("tool_result", tool="query_db",
output=f"ERROR (attempt {attempt}): {result[:150]}")
break # stop executing, retry with new plan
break
tool_used = "query_db"
tool_output = result
await self.hud("tool_result", tool="query_db", output=result[:200])

View File

@ -152,6 +152,24 @@ export async function initGraph() {
});
}
// --- Animation queue: batch rapid events, play sequentially ---
const _animQueue = [];
let _animRunning = false;
const ANIM_INTERVAL = 200; // ms between queued animations
function _enqueue(fn) {
_animQueue.push(fn);
if (!_animRunning) _flushQueue();
}
function _flushQueue() {
if (!_animQueue.length) { _animRunning = false; return; }
_animRunning = true;
const fn = _animQueue.shift();
fn();
setTimeout(_flushQueue, ANIM_INTERVAL);
}
function pulseNode(id) {
if (!cy) return;
const node = cy.getElementById(id);
@ -170,6 +188,8 @@ function flashEdge(sourceId, targetId) {
export function graphAnimate(event, node) {
if (!cy) return;
// Queue the animation instead of executing immediately
_enqueue(() => {
if (node && cy.getElementById(node).length) pulseNode(node);
switch (event) {
@ -193,6 +213,7 @@ export function graphAnimate(event, node) {
case 'thinking': if (node) pulseNode(node); break;
case 'tick': pulseNode('sensor'); break;
}
}); // end _enqueue
}
export function startPhysics() {

View File

@ -25,3 +25,9 @@ not by reporting the error and stopping.
- expect_trace: has tool_call
- expect_response: not contains "I need assistance" or "developer" or "schema issue"
- expect_response: length > 10
### 4. Expert retries on unmapped table (abrechnungsinformationen)
- send: zeig mir die letzten 3 Abrechnungsinformationen
- expect_trace: has tool_call
- expect_response: not contains "Unknown column" or "1054"
- expect_response: length > 10