Files
DemonGatanjieuandClaude Opus 5.5 cb523930da Guide the AI with fewer detours and guesses
- An AI app calling by an older tool list (tool_help or use_tool not offered,
  use_tool for a tool offered directly, a tool the list lacks) is told so, and
  the task header shows how to reconnect until the app fetches tools/list again.
- add_lora / set_model compare the base models on the model line
  (model_advice.py; the page reports the line) and add base_model_note.
- get_model gives author_examples (sampler, steps, CFG, size, a LoRA's weight
  from the Civitai record's example pictures) and the version name.
- run_workflow says ComfyUI's refusal as which node, what is wrong and the tool
  that fixes it (run_refusal.py; mcp_run.js passes the refusal as data).
- Gates "When I don't answer": wait for me, or go with the recommendation once
  the waiting time runs out (choices with a recommended option only), handed to
  the AI in the same call and shown on the stage and under Decisions.
- A tool budget test (count, size, description length, required arguments) and
  the exam tasks with their scorecard (docs/architecture/tool-exam.md,
  tools/tool_exam.mjs). The tool set's text recommends Full for Antigravity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 14:26:41 +08:00

125 lines
6.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""What Anomalous tells the AI about using a model well (api/model_advice.py): the settings of the
author's example pictures in get_model, and the note when add_lora or set_model puts a LoRA on a
main model of another base model."""
import json
import os
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock
PLUGIN = Path(__file__).resolve().parents[1]
sys.path[:0] = [str(PLUGIN), str(PLUGIN.parents[1])]
from api import mcp_actions, model_advice # noqa: E402
SHA = "abcdef0123" + "0" * 54
def pictures(*metas):
return {"id": 77, "baseModel": "Illustrious", "images": [{"url": "x", "meta": meta} for meta in metas] + [{"url": "y", "meta": None}]}
class ExampleTests(unittest.TestCase):
def setUp(self):
self.folder = tempfile.mkdtemp(prefix="amb_advice_")
self.model = os.path.join(self.folder, "glow_style.safetensors")
Path(self.model).write_bytes(b"x")
def keep(self, record):
Path(self.folder, "glow_style.civitai.info").write_text(json.dumps(record), encoding="utf-8")
def test_the_authors_settings_summed_up(self):
self.keep(pictures(
{"sampler": "Euler a", "steps": 28, "cfgScale": 5, "Size": "832x1216", "Clip skip": "2",
"resources": [{"type": "lora", "name": "glow_style", "hash": "ABCDEF0123", "weight": 0.8}]},
{"sampler": "Euler a", "steps": "30", "cfgScale": 6, "width": 832, "height": 1216, "clipSkip": 2,
"additionalResources": [{"type": "lora", "name": "loras/glow_style.safetensors", "strength": 0.6}]},
{"sampler": "DPM++ 2M", "scheduler": "karras", "steps": 25, "cfgScale": 5, "Size": "1024x1024",
"civitaiResources": [{"type": "lora", "modelVersionId": 77, "weight": 1}]},
))
found = model_advice.author_examples(self.model, "loras", SHA)
self.assertEqual(found["pictures"], 3, "pictures without settings are left out")
self.assertEqual(found["sampler"], "Euler a (2 of 3)")
self.assertEqual(found["steps"], "25–30")
self.assertEqual(found["cfg"], "5–6")
self.assertEqual(found["size"], "832x1216 (2 of 3)")
self.assertEqual(found["clip_skip"], "2")
self.assertEqual(found["lora_weight"], "0.6–1", "by hash, by file name and by Civitai version")
self.assertEqual(found["scheduler"], "karras (1 of 3)", "counted against all the pictures")
def test_the_lora_most_pictures_name_is_this_one(self):
lora = lambda name, weight: {"type": "lora", "name": name, "weight": weight}
self.keep(pictures(
{"sampler": "Undefined", "resources": [lora("authors_own_name", 0.7), lora("other_style", 1)]},
{"resources": [lora("authors_own_name", 0.9)]},
{"resources": [lora("authors_own_name", 0.8), lora("third", 1)]},
))
found = model_advice.author_examples(self.model, "loras")
self.assertEqual(found["lora_weight"], "0.7–0.9", "named in every picture: the author's name for this file")
self.assertNotIn("sampler", found, "Civitai's 'Undefined' is no sampler")
self.keep(pictures({"resources": [lora("a", 0.5), lora("b", 1)]}, {"resources": [lora("a", 0.5), lora("b", 1)]}))
self.assertNotIn("lora_weight", model_advice.author_examples(self.model, "loras"), "two named as often: no guess")
def test_nothing_kept_says_nothing(self):
self.assertEqual(model_advice.author_examples(self.model, "loras"), {})
self.keep(pictures())
self.assertEqual(model_advice.author_examples(self.model, "checkpoints"), {})
def test_a_checkpoint_has_no_weight(self):
self.keep(pictures({"sampler": "Euler", "steps": 20}))
self.assertNotIn("lora_weight", model_advice.author_examples(self.model, "checkpoints"))
class BaseModelTests(unittest.TestCase):
def bases(self, **by_path):
get = mock.patch.object(model_advice.metadata, "get_metadata", side_effect=lambda path: {"baseModel": by_path.get(path, "")})
full = mock.patch.object(model_advice.folder_paths, "get_full_path",
side_effect=lambda kind, name: name if kind in ("checkpoints", "loras") and name in by_path else None)
for patcher in (get, full):
patcher.start()
self.addCleanup(patcher.stop)
def test_families(self):
self.assertEqual(model_advice.family("SDXL 1.0"), model_advice.family("Pony"))
self.assertEqual(model_advice.family("Illustrious"), "SDXL")
self.assertEqual(model_advice.family("SD 1.5"), "SD 1.x")
self.assertEqual(model_advice.family("Flux.1 D"), model_advice.family("Chroma"))
self.assertEqual(model_advice.family("Other"), "")
def test_another_family_will_not_work(self):
self.bases(lora="SD 1.5", main="SDXL 1.0")
note = model_advice.lora_note("lora", {"model": "main"})
self.assertIn("clash", note)
self.assertIn("base_model='SDXL 1.0'", note)
def test_a_branch_of_the_same_family_may_look_off(self):
self.bases(lora="Pony", main="Illustrious")
self.assertIn("same family", model_advice.lora_note("lora", {"model": "main"}))
def test_the_same_base_or_an_unknown_one_says_nothing(self):
self.bases(lora="SDXL 1.0", main="sdxl 1.0", plain="", flux_d="Flux.1 D", flux_s="Flux.1 S")
self.assertIsNone(model_advice.lora_note("lora", {"model": "main"}))
self.assertIsNone(model_advice.lora_note("plain", {"model": "main"}))
self.assertIsNone(model_advice.lora_note("lora", {"model": "not_there"}))
self.assertIsNone(model_advice.lora_note("flux_d", {"model": "flux_s"}))
def test_add_lora_and_set_model_answer_with_the_note(self):
self.bases(**{"old.safetensors": "SD 1.5", "main.safetensors": "SDXL 1.0"})
line = {"model": "main.safetensors", "loras": ["old.safetensors"]}
with mock.patch.object(mcp_actions, "_resolve_model", return_value=("loras", 0, "", "old.safetensors")), \
mock.patch.object(mcp_actions, "trains_text_encoder", return_value=False), \
mock.patch.object(mcp_actions, "_page", return_value={"node_id": 9, "line": line}):
answer = mcp_actions.add_lora({}, "m")
self.assertIn("clash", answer["base_model_note"])
self.assertNotIn("line", answer, "the line is the server's, not the AI's")
with mock.patch.object(mcp_actions, "_resolve_model", return_value=("checkpoints", 0, "", "main.safetensors")), \
mock.patch.object(mcp_actions, "_page", return_value={"node_id": 4, "line": dict(line)}):
answer = mcp_actions.set_model({}, 4, "m")
self.assertIn("clash", answer["base_model_note"], "a new main model is checked against the LoRAs after it")
if __name__ == "__main__":
unittest.main()