diff --git a/agent/nodes/eras_expert.py b/agent/nodes/eras_expert.py index c9d376e..5fe49ec 100644 --- a/agent/nodes/eras_expert.py +++ b/agent/nodes/eras_expert.py @@ -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.""" diff --git a/agent/nodes/expert_base.py b/agent/nodes/expert_base.py index eb79283..04f1955 100644 --- a/agent/nodes/expert_base.py +++ b/agent/nodes/expert_base.py @@ -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]) diff --git a/static/js/graph.js b/static/js/graph.js index 4c25920..2b47253 100644 --- a/static/js/graph.js +++ b/static/js/graph.js @@ -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() { diff --git a/testcases/expert_recovery.md b/testcases/expert_recovery.md index 65f1ed3..c5a8abe 100644 --- a/testcases/expert_recovery.md +++ b/testcases/expert_recovery.md @@ -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