Visual Agent Refactor + Visual Library
Character Card Import Refactor
Bug fixes and other improvements
This commit is contained in:
veguAI
2025-12-06 11:19:48 +02:00
committed by GitHub
parent 89d16ae513
commit c179fcd3eb
348 changed files with 34597 additions and 5779 deletions

View File

@@ -0,0 +1,121 @@
{
"TEST_001_001": "image/png",
"TEST_001_002": {
"name": "asset1",
"vis_type": "SCENE_BACKGROUND",
"gen_type": "TEXT_TO_IMAGE",
"character_name": null,
"prompt": "A futuristic laboratory for AI testing, in the style of digital concept art",
"negative_prompt": "",
"format": "SQUARE",
"resolution": {
"width": 1024,
"height": 1024
},
"sampler_settings": {
"steps": 40
},
"reference_assets": [],
"tags": [],
"reference": [
"SCENE_BACKGROUND"
],
"analysis": null
},
"TEST_001_003": "SCENE_BACKGROUND",
"TEST_001_004": {
"width": 1024,
"height": 1024
},
"TEST_002_001": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFUlEQVR42mP8z8BQz0AEYBxVSF+FABJADveWkH6oAAAAAElFTkSuQmCC",
"TEST_002_002": {
"name": "test_asset",
"vis_type": "CHARACTER_CARD",
"gen_type": "TEXT_TO_IMAGE",
"character_name": null,
"prompt": null,
"negative_prompt": null,
"format": "SQUARE",
"resolution": {
"width": 10,
"height": 10
},
"sampler_settings": null,
"reference_assets": [],
"tags": [],
"reference": [],
"analysis": ""
},
"TEST_002_003": {
"format": "SQUARE",
"width": 10,
"height": 10
},
"TEST_003_001": "png",
"TEST_003_004": "iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAFUlEQVR42mP8z8BQz0AEYBxVSF+FABJADveWkH6oAAAAAElFTkSuQmCC",
"TEST_003_002": "a9d80a755368f677990ac389c315c0628d50e951188ea15a72ab21272a1f52d6",
"TEST_003_003": "a9d80a755368f677990ac389c315c0628d50e951188ea15a72ab21272a1f52d6",
"TEST_004_001": [
"3da1ad9438d0d6b4bd1402ea60d2a53803bb4c69ffdaa84f598a8fa14d754802",
"c26509ac97028c2ec0b696dc321dcb4883f4b3be1e3cfaf08d58b85154f4845d",
"4928ce9046c66de3189b47f5bbcd3cafc92f37373845787c1eb64d08b29f4a1e",
"9d61cbb3ad289f1f8cf1eb69ee436a12416386cbede74f665357c2e58cd4d2a3",
"37557902e5a7b5baab1fb02a9c69e48446906f491917668fc94fa47d5d76dba2"
],
"TEST_004_002": 5,
"TEST_005_003": [
"3da1ad9438d0d6b4bd1402ea60d2a53803bb4c69ffdaa84f598a8fa14d754802",
"c26509ac97028c2ec0b696dc321dcb4883f4b3be1e3cfaf08d58b85154f4845d",
"9d61cbb3ad289f1f8cf1eb69ee436a12416386cbede74f665357c2e58cd4d2a3"
],
"TEST_005_003b": 3,
"TEST_005_004": [
"3da1ad9438d0d6b4bd1402ea60d2a53803bb4c69ffdaa84f598a8fa14d754802",
"c26509ac97028c2ec0b696dc321dcb4883f4b3be1e3cfaf08d58b85154f4845d",
"9d61cbb3ad289f1f8cf1eb69ee436a12416386cbede74f665357c2e58cd4d2a3"
],
"TEST_005_004b": 3,
"TEST_005_002": [
"3da1ad9438d0d6b4bd1402ea60d2a53803bb4c69ffdaa84f598a8fa14d754802",
"c26509ac97028c2ec0b696dc321dcb4883f4b3be1e3cfaf08d58b85154f4845d",
"4928ce9046c66de3189b47f5bbcd3cafc92f37373845787c1eb64d08b29f4a1e",
"9d61cbb3ad289f1f8cf1eb69ee436a12416386cbede74f665357c2e58cd4d2a3",
"37557902e5a7b5baab1fb02a9c69e48446906f491917668fc94fa47d5d76dba2"
],
"TEST_005_002b": 5,
"TEST_005_001": [
"37557902e5a7b5baab1fb02a9c69e48446906f491917668fc94fa47d5d76dba2"
],
"TEST_005_001b": 1,
"TEST_005_005": [
"c26509ac97028c2ec0b696dc321dcb4883f4b3be1e3cfaf08d58b85154f4845d"
],
"TEST_005_005b": 1,
"TEST_005_008": [
"3da1ad9438d0d6b4bd1402ea60d2a53803bb4c69ffdaa84f598a8fa14d754802",
"9d61cbb3ad289f1f8cf1eb69ee436a12416386cbede74f665357c2e58cd4d2a3"
],
"TEST_005_008b": 2,
"TEST_005_007": [
"c26509ac97028c2ec0b696dc321dcb4883f4b3be1e3cfaf08d58b85154f4845d"
],
"TEST_005_007b": 1,
"TEST_005_006": [
"c26509ac97028c2ec0b696dc321dcb4883f4b3be1e3cfaf08d58b85154f4845d"
],
"TEST_005_006b": 1,
"TEST_005_009": [
"3da1ad9438d0d6b4bd1402ea60d2a53803bb4c69ffdaa84f598a8fa14d754802",
"4928ce9046c66de3189b47f5bbcd3cafc92f37373845787c1eb64d08b29f4a1e"
],
"TEST_005_009b": 2,
"TEST_005_011": [
"3da1ad9438d0d6b4bd1402ea60d2a53803bb4c69ffdaa84f598a8fa14d754802",
"4928ce9046c66de3189b47f5bbcd3cafc92f37373845787c1eb64d08b29f4a1e"
],
"TEST_005_011b": 2,
"TEST_005_010": [
"3da1ad9438d0d6b4bd1402ea60d2a53803bb4c69ffdaa84f598a8fa14d754802"
],
"TEST_005_010b": 1
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,152 @@
{
"assets": {
"3da1ad9438d0d6b4bd1402ea60d2a53803bb4c69ffdaa84f598a8fa14d754802": {
"id": "3da1ad9438d0d6b4bd1402ea60d2a53803bb4c69ffdaa84f598a8fa14d754802",
"file_type": "png",
"media_type": "image/png",
"meta": {
"name": "asset1",
"vis_type": "SCENE_BACKGROUND",
"gen_type": "TEXT_TO_IMAGE",
"character_name": null,
"prompt": "A futuristic laboratory for AI testing, in the style of digital concept art",
"negative_prompt": "",
"format": "SQUARE",
"resolution": {
"width": 1024,
"height": 1024
},
"sampler_settings": {
"steps": 40
},
"reference_assets": [],
"tags": [],
"reference": [
"SCENE_BACKGROUND"
],
"analysis": null
}
},
"c26509ac97028c2ec0b696dc321dcb4883f4b3be1e3cfaf08d58b85154f4845d": {
"id": "c26509ac97028c2ec0b696dc321dcb4883f4b3be1e3cfaf08d58b85154f4845d",
"file_type": "png",
"media_type": "image/png",
"meta": {
"name": "asset2",
"vis_type": "SCENE_BACKGROUND",
"gen_type": "TEXT_TO_IMAGE",
"character_name": null,
"prompt": "A futuristic laboratory for AI testing, in the style of digital concept art",
"negative_prompt": "",
"format": "SQUARE",
"resolution": {
"width": 1024,
"height": 1024
},
"sampler_settings": {
"steps": 40
},
"reference_assets": [],
"tags": [
"mountains",
"super intelligence"
],
"reference": [
"CHARACTER_CARD"
],
"analysis": null
}
},
"4928ce9046c66de3189b47f5bbcd3cafc92f37373845787c1eb64d08b29f4a1e": {
"id": "4928ce9046c66de3189b47f5bbcd3cafc92f37373845787c1eb64d08b29f4a1e",
"file_type": "png",
"media_type": "image/png",
"meta": {
"name": "asset3",
"vis_type": "SCENE_ILLUSTRATION",
"gen_type": "TEXT_TO_IMAGE",
"character_name": null,
"prompt": "A futuristic laboratory for AI testing, in the style of digital concept art",
"negative_prompt": "",
"format": "SQUARE",
"resolution": {
"width": 1024,
"height": 1024
},
"sampler_settings": {
"steps": 40
},
"reference_assets": [],
"tags": [
"super intelligence"
],
"reference": [
"SCENE_BACKGROUND",
"SCENE_ILLUSTRATION",
"SCENE_CARD"
],
"analysis": null
}
},
"9d61cbb3ad289f1f8cf1eb69ee436a12416386cbede74f665357c2e58cd4d2a3": {
"id": "9d61cbb3ad289f1f8cf1eb69ee436a12416386cbede74f665357c2e58cd4d2a3",
"file_type": "png",
"media_type": "image/png",
"meta": {
"name": "asset4",
"vis_type": "SCENE_BACKGROUND",
"gen_type": "TEXT_TO_IMAGE",
"character_name": null,
"prompt": "A futuristic laboratory for AI testing, in the style of digital concept art",
"negative_prompt": "",
"format": "SQUARE",
"resolution": {
"width": 1024,
"height": 1024
},
"sampler_settings": {
"steps": 40
},
"reference_assets": [],
"tags": [
"city",
"holograms"
],
"reference": [],
"analysis": null
}
},
"37557902e5a7b5baab1fb02a9c69e48446906f491917668fc94fa47d5d76dba2": {
"id": "37557902e5a7b5baab1fb02a9c69e48446906f491917668fc94fa47d5d76dba2",
"file_type": "png",
"media_type": "image/png",
"meta": {
"name": "asset_5",
"vis_type": "CHARACTER_CARD",
"gen_type": "IMAGE_EDIT",
"character_name": "Sebastian",
"prompt": "A futuristic character portrait of super intellience",
"negative_prompt": "",
"format": "PORTRAIT",
"resolution": {
"width": 768,
"height": 1344
},
"sampler_settings": {
"steps": 40
},
"reference_assets": [
"c26509ac97028c2ec0b696dc321dcb4883f4b3be1e3cfaf08d58b85154f4845d"
],
"tags": [
"ai"
],
"reference": [
"CHARACTER_CARD",
"CHARACTER_PORTRAIT"
],
"analysis": null
}
}
}
}

View File

@@ -0,0 +1,152 @@
{
"id": "test-assets",
"name": "Test Assets Scene",
"assets": {
"cover_image": null,
"assets": {
"3da1ad9438d0d6b4bd1402ea60d2a53803bb4c69ffdaa84f598a8fa14d754802": {
"id": "3da1ad9438d0d6b4bd1402ea60d2a53803bb4c69ffdaa84f598a8fa14d754802",
"file_type": "png",
"media_type": "image/png",
"meta": {
"name": "asset1",
"vis_type": "SCENE_BACKGROUND",
"gen_type": "TEXT_TO_IMAGE",
"character_name": null,
"prompt": "A futuristic laboratory for AI testing, in the style of digital concept art",
"negative_prompt": "",
"format": "SQUARE",
"resolution": {
"width": 1024,
"height": 1024
},
"sampler_settings": {
"steps": 40
},
"reference_assets": [],
"tags": [],
"reference": [
"SCENE_BACKGROUND"
]
}
},
"c26509ac97028c2ec0b696dc321dcb4883f4b3be1e3cfaf08d58b85154f4845d": {
"id": "c26509ac97028c2ec0b696dc321dcb4883f4b3be1e3cfaf08d58b85154f4845d",
"file_type": "png",
"media_type": "image/png",
"meta": {
"name": "asset2",
"vis_type": "SCENE_BACKGROUND",
"gen_type": "TEXT_TO_IMAGE",
"character_name": null,
"prompt": "A futuristic laboratory for AI testing, in the style of digital concept art",
"negative_prompt": "",
"format": "SQUARE",
"resolution": {
"width": 1024,
"height": 1024
},
"sampler_settings": {
"steps": 40
},
"reference_assets": [],
"tags": [
"mountains",
"super intelligence"
],
"reference": [
"CHARACTER_CARD"
]
}
},
"4928ce9046c66de3189b47f5bbcd3cafc92f37373845787c1eb64d08b29f4a1e": {
"id": "4928ce9046c66de3189b47f5bbcd3cafc92f37373845787c1eb64d08b29f4a1e",
"file_type": "png",
"media_type": "image/png",
"meta": {
"name": "asset3",
"vis_type": "SCENE_ILLUSTRATION",
"gen_type": "TEXT_TO_IMAGE",
"character_name": null,
"prompt": "A futuristic laboratory for AI testing, in the style of digital concept art",
"negative_prompt": "",
"format": "SQUARE",
"resolution": {
"width": 1024,
"height": 1024
},
"sampler_settings": {
"steps": 40
},
"reference_assets": [],
"tags": [
"super intelligence"
],
"reference": [
"SCENE_BACKGROUND",
"SCENE_ILLUSTRATION",
"SCENE_CARD"
]
}
},
"9d61cbb3ad289f1f8cf1eb69ee436a12416386cbede74f665357c2e58cd4d2a3": {
"id": "9d61cbb3ad289f1f8cf1eb69ee436a12416386cbede74f665357c2e58cd4d2a3",
"file_type": "png",
"media_type": "image/png",
"meta": {
"name": "asset4",
"vis_type": "SCENE_BACKGROUND",
"gen_type": "TEXT_TO_IMAGE",
"character_name": null,
"prompt": "A futuristic laboratory for AI testing, in the style of digital concept art",
"negative_prompt": "",
"format": "SQUARE",
"resolution": {
"width": 1024,
"height": 1024
},
"sampler_settings": {
"steps": 40
},
"reference_assets": [],
"tags": [
"city",
"holograms"
],
"reference": []
}
},
"37557902e5a7b5baab1fb02a9c69e48446906f491917668fc94fa47d5d76dba2": {
"id": "37557902e5a7b5baab1fb02a9c69e48446906f491917668fc94fa47d5d76dba2",
"file_type": "png",
"media_type": "image/png",
"meta": {
"name": "asset_5",
"vis_type": "CHARACTER_CARD",
"gen_type": "IMAGE_EDIT",
"character_name": "Sebastian",
"prompt": "A futuristic character portrait of super intellience",
"negative_prompt": "",
"format": "PORTRAIT",
"resolution": {
"width": 768,
"height": 1344
},
"sampler_settings": {
"steps": 40
},
"reference_assets": [
"c26509ac97028c2ec0b696dc321dcb4883f4b3be1e3cfaf08d58b85154f4845d"
],
"tags": [
"ai"
],
"reference": [
"CHARACTER_CARD",
"CHARACTER_PORTRAIT"
]
}
}
}
}
}

View File

@@ -0,0 +1,303 @@
from talemate.load.character_card import (
create_manual_context_from_character_book,
CharacterBook,
CharacterBookEntry,
)
from talemate.world_state import ManualContext
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
def test_create_manual_context_from_character_book_none():
"""Test that None character_book returns empty dict."""
result = create_manual_context_from_character_book(None)
assert result == {}
def test_create_manual_context_from_character_book_empty():
"""Test that empty character_book returns empty dict."""
character_book = CharacterBook(entries=[])
result = create_manual_context_from_character_book(character_book)
assert result == {}
def test_create_manual_context_from_character_book_single_entry():
"""Test creating ManualContext from a single character book entry."""
entry = CharacterBookEntry(
keys=["Alice", "Character"],
content="Alice is a friendly character.",
enabled=True,
id=1,
)
character_book = CharacterBook(
name="Test Book",
entries=[entry],
)
result = create_manual_context_from_character_book(character_book)
assert len(result) == 1
entry_id = "character_book_1"
assert entry_id in result
manual_context = result[entry_id]
assert isinstance(manual_context, ManualContext)
assert manual_context.id == entry_id
assert manual_context.text == "Alice is a friendly character."
assert manual_context.meta["source"] == "imported"
assert "chara" in manual_context.meta
assert manual_context.meta["chara"]["character_book_name"] == "Test Book"
assert manual_context.meta["chara"]["keys"] == ["Alice", "Character"]
assert manual_context.shared is False
def test_create_manual_context_from_character_book_multiple_entries():
"""Test creating ManualContext from multiple character book entries."""
entries = [
CharacterBookEntry(
keys=["Alice"],
content="Alice is a character.",
enabled=True,
id=1,
),
CharacterBookEntry(
keys=["Bob"],
content="Bob is another character.",
enabled=True,
id=2,
),
]
character_book = CharacterBook(
name="Multi Entry Book",
entries=entries,
)
result = create_manual_context_from_character_book(character_book)
assert len(result) == 2
assert "character_book_1" in result
assert "character_book_2" in result
assert result["character_book_1"].text == "Alice is a character."
assert result["character_book_2"].text == "Bob is another character."
def test_create_manual_context_from_character_book_disabled_entry():
"""Test that disabled entries are skipped by default."""
entries = [
CharacterBookEntry(
keys=["Alice"],
content="Alice is enabled.",
enabled=True,
id=1,
),
CharacterBookEntry(
keys=["Bob"],
content="Bob is disabled.",
enabled=False,
id=2,
),
]
character_book = CharacterBook(entries=entries)
result = create_manual_context_from_character_book(character_book)
assert len(result) == 1
assert "character_book_1" in result
assert "character_book_2" not in result
def test_create_manual_context_from_character_book_include_disabled():
"""Test that disabled entries can be included when skip_disabled=False."""
entries = [
CharacterBookEntry(
keys=["Alice"],
content="Alice is enabled.",
enabled=True,
id=1,
),
CharacterBookEntry(
keys=["Bob"],
content="Bob is disabled.",
enabled=False,
id=2,
),
]
character_book = CharacterBook(entries=entries)
result = create_manual_context_from_character_book(
character_book, skip_disabled=False
)
assert len(result) == 2
assert "character_book_1" in result
assert "character_book_2" in result
def test_create_manual_context_from_character_book_with_optional_fields():
"""Test creating ManualContext with optional fields populated."""
entry = CharacterBookEntry(
keys=["Alice"],
content="Alice content",
enabled=True,
id=1,
name="Alice Entry",
priority=10,
selective=True,
secondary_keys=["secondary1", "secondary2"],
constant=True,
position="before_char",
insertion_order=5,
case_sensitive=True,
)
character_book = CharacterBook(
name="Optional Fields Book",
entries=[entry],
)
result = create_manual_context_from_character_book(character_book)
assert len(result) == 1
# Entry name should be used as ID when set
manual_context = result["Alice Entry"]
assert manual_context.id == "Alice Entry"
meta = manual_context.meta
chara = meta["chara"]
assert chara["entry_name"] == "Alice Entry"
assert chara["priority"] == 10
assert chara["selective"] is True
assert chara["secondary_keys"] == ["secondary1", "secondary2"]
assert chara["constant"] is True
assert chara["position"] == "before_char"
assert chara["insertion_order"] == 5
assert chara["case_sensitive"] is True
def test_create_manual_context_from_character_book_with_extensions():
"""Test that extensions are included in chara metadata."""
entry = CharacterBookEntry(
keys=["Alice"],
content="Alice content",
enabled=True,
id=1,
extensions={"custom_field": "custom_value", "depth": 4},
)
character_book = CharacterBook(
name="Extensions Book",
entries=[entry],
extensions={"book_extension": "book_value"},
)
result = create_manual_context_from_character_book(character_book)
assert len(result) == 1
manual_context = result["character_book_1"]
assert "extensions" in manual_context.meta["chara"]
assert manual_context.meta["chara"]["extensions"]["custom_field"] == "custom_value"
assert manual_context.meta["chara"]["extensions"]["depth"] == 4
def test_create_manual_context_from_character_book_without_id():
"""Test that entries without id get UUID-based IDs."""
entry = CharacterBookEntry(
keys=["Alice", "Character"],
content="Alice is a character.",
enabled=True,
# No id provided
)
character_book = CharacterBook(entries=[entry])
result = create_manual_context_from_character_book(character_book)
assert len(result) == 1
# Should have a generated UUID-based ID
entry_id = list(result.keys())[0]
assert entry_id.startswith("character_book_")
# UUID hex is 10 chars after the prefix
assert len(entry_id) == len("character_book_") + 10
def test_create_manual_context_from_character_book_dict_input():
"""Test that dict input is converted to CharacterBook."""
character_book_dict = {
"name": "Dict Book",
"entries": [
{
"keys": ["Alice"],
"content": "Alice from dict.",
"enabled": True,
"id": 1,
}
],
}
result = create_manual_context_from_character_book(character_book_dict)
assert len(result) == 1
assert "character_book_1" in result
assert result["character_book_1"].text == "Alice from dict."
assert (
result["character_book_1"].meta["chara"]["character_book_name"] == "Dict Book"
)
def test_create_manual_context_from_character_book_invalid_dict():
"""Test that invalid dict input returns empty dict."""
invalid_dict = {
"name": "Invalid",
"entries": [
{
# Missing required 'keys' field
"content": "Invalid entry.",
}
],
}
result = create_manual_context_from_character_book(invalid_dict)
# Should return empty dict due to validation error
assert result == {}
def test_create_manual_context_from_character_book_no_book_name():
"""Test handling of missing book name."""
entry = CharacterBookEntry(
keys=["Alice"],
content="Alice content",
enabled=True,
id=1,
)
character_book = CharacterBook(entries=[entry]) # No name
result = create_manual_context_from_character_book(character_book)
assert len(result) == 1
assert result["character_book_1"].meta["chara"]["character_book_name"] == ""
def test_create_manual_context_from_character_book_without_meta():
"""Test that chara metadata is not included when import_meta=False."""
entry = CharacterBookEntry(
keys=["Alice"],
content="Alice content",
enabled=True,
id=1,
priority=10,
)
character_book = CharacterBook(
name="Test Book",
entries=[entry],
)
result = create_manual_context_from_character_book(
character_book, import_meta=False
)
assert len(result) == 1
manual_context = result["character_book_1"]
assert manual_context.meta["source"] == "imported"
assert "chara" not in manual_context.meta

View File

@@ -3,6 +3,7 @@ import json
import pytest
import contextvars
import talemate.agents as agents
import pydantic
import talemate.game.engine.nodes.load_definitions # noqa: F401
import talemate.agents.director # noqa: F401
import talemate.agents.memory
@@ -123,6 +124,38 @@ def mock_scene():
return scene
@pytest.fixture
def mock_scene_with_assets():
scene = MockScene()
bootstrap_scene(scene)
# Load test assets from the test scene file
test_scene_path = os.path.join(
BASE_DIR, "data", "scenes", "talemate-laboratory", "talemate-lab.json"
)
with open(test_scene_path, "r") as f:
test_scene_data = json.load(f)
# Override scenes_dir to point to test data directory
test_scenes_dir = os.path.join(BASE_DIR, "data", "scenes")
scene.scenes_dir = lambda: test_scenes_dir
scene.project_name = "talemate-laboratory"
# Create library.json file with assets from the scene file
if "assets" in test_scene_data and "assets" in test_scene_data["assets"]:
assets_dict = test_scene_data["assets"]["assets"]
# Ensure assets directory exists
assets_dir = os.path.join(test_scenes_dir, "talemate-laboratory", "assets")
os.makedirs(assets_dir, exist_ok=True)
# Create library.json file
library_path = os.path.join(assets_dir, "library.json")
with open(library_path, "w") as f:
json.dump({"assets": assets_dict}, f, indent=2)
return scene
def bootstrap_scene(mock_scene):
bootstrap_engine()
client = MockClient("test_client")
@@ -147,18 +180,38 @@ def bootstrap_scene(mock_scene):
}
def serialize_state(obj):
"""Custom JSON serializer for Pydantic models"""
if isinstance(obj, pydantic.BaseModel):
return obj.model_dump()
raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
def normalize_state(data):
"""Convert Pydantic models to dicts for comparison"""
if isinstance(data, pydantic.BaseModel):
return data.model_dump()
elif isinstance(data, dict):
return {k: normalize_state(v) for k, v in data.items()}
elif isinstance(data, list):
return [normalize_state(item) for item in data]
return data
def make_assert_fn(name: str, write_results: bool = False):
async def assert_fn(state: GraphState):
if write_results or not os.path.exists(
os.path.join(RESULTS_DIR, f"{name}.json")
):
with open(os.path.join(RESULTS_DIR, f"{name}.json"), "w") as f:
json.dump(state.shared, f, indent=4)
json.dump(state.shared, f, indent=4, default=serialize_state)
else:
with open(os.path.join(RESULTS_DIR, f"{name}.json"), "r") as f:
expected = json.load(f)
assert state.shared == expected
# Normalize state.shared to convert Pydantic models to dicts for comparison
normalized_shared = normalize_state(state.shared)
assert normalized_shared == expected
return assert_fn
@@ -230,3 +283,9 @@ async def test_graph_collectors(mock_scene):
async def test_graph_context_ids(mock_scene):
fn = make_graph_test("test-harness-context-ids", False)
await fn(mock_scene)
@pytest.mark.asyncio
async def test_graph_assets(mock_scene_with_assets):
fn = make_graph_test("test-harness-assets", False)
await fn(mock_scene_with_assets)