Tests / Test failed: 2, passed: 849
- ToolCallDecision captures thinking blocks and pre-tool text output
- AnthropicToolCallingClient supports optional extended thinking (budget_tokens + beta header)
- PlannedStep carries rationale and thinking from each LLM decision
- WorldEvent replaces scene_summary with rationale/thinking/page fields (backward-compatible)
- AI planner system prompt instructs reflection before each tool call
- _history_summary() emits compact {page, rationale, action, success} dicts
- Cloud DB migration 0011 adds nullable rationale/thinking columns to planner_decision_log
- OpenAI client extracts reasoning_content into thinking field
89 lines
2.7 KiB
Python
89 lines
2.7 KiB
Python
from __future__ import annotations
|
|
|
|
from core.models import Bounds, Scene, SceneElement
|
|
from semantic.models import SemanticScene, SemanticWidget
|
|
from world.models import WorldEvent, WorldState
|
|
|
|
|
|
def _scene() -> Scene:
|
|
return Scene(
|
|
width=10,
|
|
height=20,
|
|
elements=[
|
|
SceneElement(
|
|
id="send",
|
|
type="button",
|
|
text="Send",
|
|
bounds=Bounds(1, 2, 3, 4),
|
|
)
|
|
],
|
|
)
|
|
|
|
|
|
def test_world_event_and_state_to_dict() -> None:
|
|
semantic_scene = SemanticScene(
|
|
page="Chat",
|
|
intents=["send a message"],
|
|
widgets=[SemanticWidget(element_id="send", purpose="send message")],
|
|
)
|
|
event = WorldEvent(
|
|
action="tap",
|
|
success=True,
|
|
scene_summary=semantic_scene,
|
|
)
|
|
state = WorldState.with_history_bound(2)
|
|
state.current_app = "com.example.chat"
|
|
state.current_page = "Chat"
|
|
state.variables["contact"] = "Zhang San"
|
|
state.history.append(event)
|
|
|
|
data = state.to_dict()
|
|
|
|
assert data["current_app"] == "com.example.chat"
|
|
assert data["current_page"] == "Chat"
|
|
assert data["variables"] == {"contact": "Zhang San"}
|
|
assert data["history"][0]["scene_summary"] == semantic_scene.to_dict()
|
|
assert data["history"][0]["action"] == "tap"
|
|
assert data["history"][0]["success"] is True
|
|
assert data["history"][0]["timestamp"]
|
|
|
|
|
|
def test_world_state_history_evicts_oldest_entry_at_bound() -> None:
|
|
state = WorldState.with_history_bound(2)
|
|
|
|
state.history.append(WorldEvent(action="first", success=True))
|
|
state.history.append(WorldEvent(action="second", success=True))
|
|
state.history.append(WorldEvent(action="third", success=True))
|
|
|
|
assert len(state.history) == 2
|
|
assert [event.action for event in state.history] == ["second", "third"]
|
|
assert state.history.maxlen == 2
|
|
|
|
|
|
def test_world_event_with_rationale_thinking_page() -> None:
|
|
event = WorldEvent(
|
|
action="tap",
|
|
success=True,
|
|
rationale="Previous step opened settings. Now tapping account.",
|
|
thinking="I should navigate to account settings.",
|
|
page="Settings",
|
|
)
|
|
data = event.to_dict()
|
|
|
|
assert data["rationale"] == "Previous step opened settings. Now tapping account."
|
|
assert data["thinking"] == "I should navigate to account settings."
|
|
assert data["page"] == "Settings"
|
|
assert data["scene_summary"] is None
|
|
|
|
|
|
def test_world_event_all_optional_fields_none() -> None:
|
|
event = WorldEvent(action="swipe", success=False)
|
|
data = event.to_dict()
|
|
|
|
assert data["rationale"] is None
|
|
assert data["thinking"] is None
|
|
assert data["page"] is None
|
|
assert data["scene_summary"] is None
|
|
assert data["action"] == "swipe"
|
|
assert data["success"] is False
|