feat(runtime): add planner reflection history with rationale and thinking
Tests / Test failed: 2, passed: 849
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
This commit is contained in:
@@ -195,3 +195,62 @@ def test_ai_planner_step_prompt_reflects_scene_changes() -> None:
|
||||
assert steps_a[0].prompt != steps_b[0].prompt
|
||||
assert "Alpha" in steps_a[0].prompt
|
||||
assert "Beta" in steps_b[0].prompt
|
||||
|
||||
|
||||
def test_ai_planner_propagates_rationale_and_thinking_to_planned_step() -> None:
|
||||
client = FakeToolCallingClient(
|
||||
ToolCallDecision(
|
||||
tool_name="tap",
|
||||
arguments={"x": 1, "y": 2},
|
||||
text_output="Previous step opened settings. Now tapping account.",
|
||||
thinking="I need to navigate deeper.",
|
||||
)
|
||||
)
|
||||
planner = AIPlanner(client=client)
|
||||
|
||||
steps = planner.plan(goal="open account", scene=_scene(), context=_context())
|
||||
|
||||
assert steps[0].rationale == "Previous step opened settings. Now tapping account."
|
||||
assert steps[0].thinking == "I need to navigate deeper."
|
||||
|
||||
|
||||
def test_history_summary_returns_compact_format() -> None:
|
||||
from collections import deque
|
||||
from runtime.ai_planner import _history_summary
|
||||
from world.models import WorldEvent, WorldState
|
||||
|
||||
state = WorldState(
|
||||
history=deque(
|
||||
[
|
||||
WorldEvent(
|
||||
action="tap",
|
||||
success=True,
|
||||
rationale="Opened settings.",
|
||||
page="Home",
|
||||
),
|
||||
WorldEvent(
|
||||
action="swipe",
|
||||
success=False,
|
||||
rationale=None,
|
||||
page="Settings",
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
summary = _history_summary(state)
|
||||
|
||||
assert summary == [
|
||||
{"page": "Home", "action": "tap", "rationale": "Opened settings.", "success": True},
|
||||
{"page": "Settings", "action": "swipe", "rationale": None, "success": False},
|
||||
]
|
||||
# Must not contain scene element data
|
||||
for entry in summary:
|
||||
assert "scene_summary" not in entry
|
||||
assert "elements" not in entry
|
||||
|
||||
|
||||
def test_history_summary_returns_empty_for_none_world() -> None:
|
||||
from runtime.ai_planner import _history_summary
|
||||
|
||||
assert _history_summary(None) == []
|
||||
|
||||
Reference in New Issue
Block a user