This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-performance-tests.git


The following commit(s) were added to refs/heads/main by this push:
     new 6fcee41  ai-benchmark: a check that cannot be read fails its step, the 
model is told when a passed step's files are replaced, and jms step 2 checks 
that the order stays the body
6fcee41 is described below

commit 6fcee4182ec72072b9566a0697b7b046b06c981b
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Oct 22 08:47:01 2026 +0200

    ai-benchmark: a check that cannot be read fails its step, the model is told 
when a passed step's files are replaced, and jms step 2 checks that the order 
stays the body
    
    From the r21 analysis (Oct 22 2026):
    
    - read() survives a directory where a file was expected, globbed checks 
read files only, and a scoring error
      fails the step instead of ending the example (r21n-7 ai-ticket-classifier 
stopped after step 1)
    - the checkpoint of a failed step empties the example's output folders 
(clear_dirs: tickets-out)
    - after a passed step whose files the checkpoint replaces with the 
reference, the model is told to read a file
      again (it went on from memory with files that had changed)
    - jms step 2 checks variableReceive in reserve-order: the answer in a 
variable, so the order stays the body
      (r21n-1 passed with the reply replacing the body)
    - summarize_stepwise.py warns when a run recorded fewer steps than the 
example has
    
    Reference passes with the change: new set 19/19, ladder 71/71.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
---
 ai-benchmark/agent_mcp_stepwise.py | 33 +++++++++++++++++++++++++++++----
 ai-benchmark/gen_stepwise.py       | 10 ++++++++--
 ai-benchmark/summarize_stepwise.py | 10 ++++++++++
 3 files changed, 47 insertions(+), 6 deletions(-)

diff --git a/ai-benchmark/agent_mcp_stepwise.py 
b/ai-benchmark/agent_mcp_stepwise.py
index 9c6af35..3d1ef6e 100755
--- a/ai-benchmark/agent_mcp_stepwise.py
+++ b/ai-benchmark/agent_mcp_stepwise.py
@@ -102,7 +102,8 @@ def to_ollama_tools(mcp_tools, names):
 def read(path):
     try:
         return open(path).read()
-    except FileNotFoundError:
+    except OSError:
+        # also a directory where a file was expected (r21n-7: the model's toD 
made tickets-out/shipping/T-1.json one)
         return ""
 
 
@@ -273,7 +274,7 @@ def file_matches(project, fname, rx):
     category folder of ticket-classifier, which the model's answer picks, Oct 
18); else project_file."""
     if "*" in fname:
         import glob
-        return any(re.search(rx, read(f) or "") for f in 
glob.glob(os.path.join(project, fname)))
+        return any(re.search(rx, read(f) or "") for f in 
glob.glob(os.path.join(project, fname)) if os.path.isfile(f))
     return bool(re.search(rx, project_file(project, fname) or ""))
 
 
@@ -691,7 +692,15 @@ def main():
                 if isinstance(data, dict) and data.get("name"):
                     name = data["name"]
                 time.sleep(8)
-            res, route_after, props_after = score(step, project, cfg, mcp, 
name, before)
+            try:
+                res, route_after, props_after = score(step, project, cfg, mcp, 
name, before)
+            except Exception as e:
+                # a check that cannot be read fails the step instead of ending 
the example (r21n-7 ended after step 1)
+                print(f"step{sid}: scoring failed: {e!r}", file=log, 
flush=True)
+                res = {"file_ok": False, "props_ok": False, "log_ok": False, 
"errors": 0, "changed_lines": 0, "ok": False,
+                       "ok_final": False, "ok_lastwrite": False, 
"score_error": repr(e)}
+                route_after = read(os.path.join(project, cfg["route_file"]))
+                props_after = read(os.path.join(project, cfg.get("props_file", 
"application.properties")))
             res.update({"step": sid, "request": step["request"], "tool_calls": 
calls, "writes": writes, "refused_writes": refused,
                         "seconds": round(time.time() - t0, 1), "tokens": 
tokens, "answer": answer[:400]})
             results.append(res)
@@ -722,9 +731,19 @@ def main():
                 restored = not res["ok"] and restore_seeds(project, cfg)
                 if restored:
                     print(f"step{sid}: checkpoint restored seed files", 
file=log, flush=True)
+                if not res["ok"]:
+                    # what a failed step wrote into an output folder of the 
example goes too: r21n-7 left a directory
+                    # named tickets-out/shipping/T-1.json, and every later 
write of that file failed
+                    for d in cfg.get("clear_dirs", []):
+                        out = os.path.join(project, d)
+                        if os.path.isdir(out) and os.listdir(out):
+                            shutil.rmtree(out, ignore_errors=True)
+                            os.makedirs(out, exist_ok=True)
+                            print(f"step{sid}: checkpoint emptied {d}", 
file=log, flush=True)
+                differs = removed or restored or 
any(read(os.path.join(project, f)) != c for f, c in step["reference"].items())
                 if FAST:
                     # files that already equal the reference are not written 
again: no reload, nothing to wait for
-                    if removed or restored or any(read(os.path.join(project, 
f)) != c for f, c in step["reference"].items()):
+                    if differs:
                         seen = error_snapshot(mcp, name)[2]
                         apply_reference(project, cfg, step["reference"], mcp, 
name)
                         if restart_after:
@@ -750,6 +769,12 @@ def main():
                     time.sleep(6)
                 if not res["ok"]:
                     messages.append({"role": "user", "content": "I fixed that 
step myself; the files now contain the correct version. Continue with the next 
request."})
+                elif differs:
+                    # the model's own version passed, and the checkpoint put 
the reference in its place: without a word
+                    # the model went on from memory with files that had 
changed (r21: run-nightly-report step 3 sent
+                    # edits for its own version of the route, jms step 3 
changed a Java class the restored route no
+                    # longer used)
+                    messages.append({"role": "user", "content": "Your version 
of that step passed. I replaced the files with my own version of that step, so 
read a file again before you change it. Continue with the next request."})
             json.dump(results, open(os.path.join(OUT, "results.json"), "w"), 
indent=1)
     finally:
         data, raw = jcall(mcp, "camel_control", {"name": name, "action": 
"stop"})
diff --git a/ai-benchmark/gen_stepwise.py b/ai-benchmark/gen_stepwise.py
index 6c745c0..d9d2ca9 100644
--- a/ai-benchmark/gen_stepwise.py
+++ b/ai-benchmark/gen_stepwise.py
@@ -889,7 +889,10 @@ EXAMPLES.append({
          "check": {"log_regex": ["Order ORD-1001 put on the queue", "Reserving 
ORD-1001 for customer C-482"]},
          "reference": {JR: jr_s2, "application.properties": JR_CF}, "restart": 
True},
         {"request": "Add a route stock-service on the queue stock that answers 
whether an order (the JSON body) can be reserved: \"reserved\" when stock.json 
has enough of every line, otherwise \"not enough <sku>\" for the first line 
that is short. In reserve-order, ask the stock service with request-reply, keep 
the order as the body, and log \"Stock service answered for ${header.orderId}: 
<the answer>\".",
-         "check": {"log_regex": ["Stock service answered for ORD-1001: 
reserved", "Stock service answered for ORD-1003: not enough CAMEL-CAP"]},
+         # the order stays the body: the answer goes to a variable 
(variableReceive, as the jms docs show), where a
+         # reply that replaced the body passed before (r21n-1)
+         "check": {"log_regex": ["Stock service answered for ORD-1001: 
reserved", "Stock service answered for ORD-1003: not enough CAMEL-CAP"],
+                   "file_regex": "id: reserve-order(?:(?!- 
route:)[\\s\\S])*variableReceive"},
          "reference": {JR: jr_s3}},
         {"request": "An order the stock service cannot reserve must fail: 
redeliver it 3 times, one second apart, and then move the original order to the 
queue orders.dead, where a route dead-orders logs \"Dead order 
${header.orderId}\".",
          "check": {"file_regex": "maximumRedeliveries", "log_regex": "Dead 
order ORD-1003", "errors_ok": True},
@@ -1084,7 +1087,7 @@ tc_s4 = TC_BEANS + route("classify-tickets", 
FILE_TICKETS, TC_RECEIVED + TC_BREA
 TC_CATEGORY = "(?i)Ticket %s is (shipping|billing|account)\\b"
 EXAMPLES.append({
     "name": "ai-ticket-classifier", "route_file": TC, "props_file": 
"application.properties", "wait_seconds": 20,
-    "seed": "ai-ticket-classifier", "steps_dir": NEW,
+    "seed": "ai-ticket-classifier", "steps_dir": NEW, "clear_dirs": 
["tickets-out"],
     "initial": {TC: tc_s1, "application.properties": TC_PROPS},
     "steps": [
         {"request": "Ask the local model for the category of each ticket: 
shipping, billing or account. Use the langchain4j-chat component with an Ollama 
chat model (dev.langchain4j.model.ollama.OllamaChatModel) for the model and 
base URL in application.properties. Keep the ticket as the body, the category 
in a variable category, and log \"Ticket ${body[ticketId]} is 
${variable.category}\".",
@@ -1132,6 +1135,9 @@ def main():
         if ex.get("infra"):
             # services the example needs, started with `camel infra run` 
before the app (the sql example's postgres)
             cfg["infra"] = ex["infra"]
+        if ex.get("clear_dirs"):
+            # output folders the checkpoint of a failed step empties
+            cfg["clear_dirs"] = ex["clear_dirs"]
         if ex.get("peer"):
             cfg["peer"] = {"project": ex["peer"]["project"], "name": 
ex["peer"]["name"]}
             pd = os.path.join(HERE, ex["peer"]["project"])
diff --git a/ai-benchmark/summarize_stepwise.py 
b/ai-benchmark/summarize_stepwise.py
index 7ddde33..171805a 100644
--- a/ai-benchmark/summarize_stepwise.py
+++ b/ai-benchmark/summarize_stepwise.py
@@ -13,6 +13,7 @@ names = sorted(os.path.splitext(f)[0] for f in 
os.listdir(steps_dir) if f.endswi
 print(f"| example | steps | passes per step ({' '.join(tags)}) | all steps 
passed | last version right | final state right |")
 print("|---|---|---|---|---|---|")
 total = 0; possible = 0; clean = 0; final = 0; lastw = 0
+missing = []
 for n in names:
     per = []; runs = []
     for t in tags:
@@ -23,6 +24,13 @@ for n in names:
     if not runs:
         continue
     steps = max(len(r) for r in runs)
+    # a run that recorded fewer steps than the example has: said, so a short 
run is not read as a short example
+    # (r21n-7 recorded 1 of the 3 steps of ai-ticket-classifier, and the round 
read "9 of 17")
+    defined = len(json.load(open(os.path.join(steps_dir, n + 
".json"))).get("steps", []))
+    for t, r in zip([t for t in tags if 
os.path.exists(os.path.join("stepwise", t, n, "results.json"))], runs):
+        if len(r) < defined:
+            missing.append(f"{n} {t}: {len(r)} of {defined} steps recorded")
+    steps = max(steps, defined)
     cols = []
     for s in range(steps):
         ok = sum(1 for r in runs if s < len(r) and r[s]["ok"]); 
cols.append(f"{ok}/{len(runs)}"); total += ok; possible += len(runs)
@@ -33,3 +41,5 @@ for n in names:
     print(f"| {n} | {steps} | {' '.join(cols)} | {allok}/{len(runs)} | 
{lw}/{n_steps} | {fin}/{n_steps} |")
 print(f"\nsteps passed: {total} of {possible}; last version right: {lastw} of 
{possible}; final state right: {final} of"
       f" {possible}; runs with every step passed: {clean}")
+for m in missing:
+    print(f"WARNING: {m}")

Reply via email to