Replaces the stub Planner's fixed describe_screen/[] behavior with a real decision-maker: AIPlanner uses native tool/function calling (Anthropic or OpenAI, pluggable via AI_PLANNER_PROVIDER) to select exactly one grounded action per turn, with an explicit finish_task(success, reason) tool for completion/failure instead of an ambiguous "no tool call" signal. Default disabled (AI_PLANNER_ENABLED=false) and additive; TaskRunner falls back to the existing stub Planner unchanged when disabled. Amends CONSTITUTION.md's Perception Boundary with one narrow exception: only the AI Planner may receive the current step's raw screenshot bytes alongside Scene, for vision-grounded coordinate grounding. Also fixes a latent gap in TaskRunner.run(): observe/plan exceptions are now caught per iteration and turned into a failed task with a failure_reason, instead of propagating uncaught. openspec change: ai-planner-runtime. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
57 lines
1.7 KiB
Python
57 lines
1.7 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
import pytest
|
|
|
|
from core.models import Bounds, Scene, SceneElement
|
|
from runtime.ai_planner import AIPlanner
|
|
from runtime.context import TaskContext
|
|
from runtime.planner_config import PlannerConfig
|
|
|
|
|
|
def _scene() -> Scene:
|
|
return Scene(
|
|
width=390,
|
|
height=844,
|
|
elements=[
|
|
SceneElement(id="send", type="button", text="Send", bounds=Bounds(300, 800, 60, 30)),
|
|
],
|
|
)
|
|
|
|
|
|
@pytest.mark.integration
|
|
def test_real_anthropic_ai_planner_selects_a_tool() -> None:
|
|
if not os.environ.get("ANTHROPIC_API_KEY"):
|
|
pytest.skip("ANTHROPIC_API_KEY is required for AI planner integration test")
|
|
try:
|
|
import anthropic # noqa: F401
|
|
except ImportError:
|
|
pytest.skip("anthropic SDK is not installed")
|
|
|
|
planner = AIPlanner(config=PlannerConfig(enabled=True, provider="anthropic", timeout=15.0))
|
|
context = TaskContext(task_id="task", goal="tap the send button")
|
|
|
|
steps = planner.plan(goal="tap the send button", scene=_scene(), context=context)
|
|
|
|
assert isinstance(steps, list)
|
|
assert len(steps) <= 1
|
|
|
|
|
|
@pytest.mark.integration
|
|
def test_real_openai_ai_planner_selects_a_tool() -> None:
|
|
if not os.environ.get("OPENAI_API_KEY"):
|
|
pytest.skip("OPENAI_API_KEY is required for AI planner integration test")
|
|
try:
|
|
import openai # noqa: F401
|
|
except ImportError:
|
|
pytest.skip("openai SDK is not installed")
|
|
|
|
planner = AIPlanner(config=PlannerConfig(enabled=True, provider="openai", timeout=15.0))
|
|
context = TaskContext(task_id="task", goal="tap the send button")
|
|
|
|
steps = planner.plan(goal="tap the send button", scene=_scene(), context=context)
|
|
|
|
assert isinstance(steps, list)
|
|
assert len(steps) <= 1
|