This commit is contained in:
@@ -40,6 +40,7 @@ class AIPlanner(Planner):
|
||||
goal=goal,
|
||||
scene_json=scene.to_dict(),
|
||||
history_summary=_history_summary(world),
|
||||
device_platform=context.device_platform,
|
||||
)
|
||||
decision = self.client.decide(
|
||||
system_prompt=PLANNER_SYSTEM_PROMPT,
|
||||
|
||||
@@ -14,6 +14,7 @@ if TYPE_CHECKING:
|
||||
class TaskContext:
|
||||
task_id: str
|
||||
goal: str
|
||||
device_platform: str | None = None
|
||||
scenes: list[Scene] = field(default_factory=list)
|
||||
step_results: list["StepResult"] = field(default_factory=list)
|
||||
world: "WorldState | None" = None
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
PLANNER_SYSTEM_PROMPT = """You are the planning brain of a mobile device automation agent.
|
||||
@@ -66,8 +67,18 @@ def planner_user_prompt(
|
||||
goal: str,
|
||||
scene_json: dict[str, Any],
|
||||
history_summary: list[dict[str, Any]],
|
||||
device_platform: str | None = None,
|
||||
now: datetime | None = None,
|
||||
) -> str:
|
||||
current_time = now or datetime.now().astimezone()
|
||||
if current_time.tzinfo is None:
|
||||
current_time = current_time.astimezone()
|
||||
timezone_name = current_time.tzname() or str(current_time.tzinfo) or "unknown"
|
||||
return (
|
||||
"Execution context:\n"
|
||||
f"Current date and time: {current_time.isoformat(timespec='seconds')}\n"
|
||||
f"Time zone: {timezone_name}\n"
|
||||
f"Device type: {_device_type(scene_json, device_platform)}\n\n"
|
||||
"Goal:\n"
|
||||
f"{goal}\n\n"
|
||||
"Current Scene (JSON):\n"
|
||||
@@ -76,3 +87,16 @@ def planner_user_prompt(
|
||||
f"{json.dumps(history_summary, ensure_ascii=False, sort_keys=True)}\n\n"
|
||||
"Call exactly one tool for this turn."
|
||||
)
|
||||
|
||||
|
||||
def _device_type(scene_json: dict[str, Any], device_platform: str | None) -> str:
|
||||
platform = device_platform
|
||||
if platform is None:
|
||||
app = scene_json.get("app")
|
||||
if isinstance(app, dict):
|
||||
raw_platform = app.get("platform")
|
||||
platform = raw_platform if isinstance(raw_platform, str) else None
|
||||
if not isinstance(platform, str):
|
||||
return "unknown"
|
||||
normalized = platform.strip().lower()
|
||||
return normalized if normalized in {"ios", "android"} else "unknown"
|
||||
|
||||
+23
-2
@@ -42,6 +42,7 @@ TaskSucceededHook = Callable[[str, str, Timeline], None]
|
||||
StopRequested = Callable[[], bool]
|
||||
StopReason = Callable[[], "str | None"]
|
||||
StepProgressCallback = Callable[[int, str, str], None]
|
||||
DevicePlatformProvider = Callable[[str], str | None]
|
||||
|
||||
|
||||
def is_cancellation_reason(reason: str | None) -> bool:
|
||||
@@ -68,6 +69,7 @@ class TaskRunner:
|
||||
skill_embedding_client: EmbeddingClient | None = None,
|
||||
planner_config: PlannerConfig | None = None,
|
||||
on_step_progress: StepProgressCallback | None = None,
|
||||
device_platform_provider: DevicePlatformProvider | None = None,
|
||||
) -> None:
|
||||
self.planner_config = planner_config or load_planner_config()
|
||||
self.planner = planner or self._default_planner()
|
||||
@@ -98,6 +100,7 @@ class TaskRunner:
|
||||
else:
|
||||
self.on_task_succeeded = None
|
||||
self.on_step_progress = on_step_progress
|
||||
self.device_platform_provider = device_platform_provider
|
||||
|
||||
def run(
|
||||
self,
|
||||
@@ -108,7 +111,11 @@ class TaskRunner:
|
||||
) -> Task:
|
||||
if self.metadata_store:
|
||||
self.metadata_store.create_task(task)
|
||||
context = TaskContext(task_id=task.id, goal=task.goal)
|
||||
context = TaskContext(
|
||||
task_id=task.id,
|
||||
goal=task.goal,
|
||||
device_platform=self._device_platform(task.device_id),
|
||||
)
|
||||
world_handle = self._start_world_view(task.id)
|
||||
if world_handle is not None:
|
||||
context.world = world_handle.state
|
||||
@@ -201,7 +208,9 @@ class TaskRunner:
|
||||
)
|
||||
return task
|
||||
|
||||
def _interrupt_task(self, task: Task, stop_reason: StopReason | None = None) -> Task:
|
||||
def _interrupt_task(
|
||||
self, task: Task, stop_reason: StopReason | None = None
|
||||
) -> Task:
|
||||
reason = stop_reason() if stop_reason is not None else None
|
||||
message = reason or "execution interrupted"
|
||||
status = "cancelled" if is_cancellation_reason(reason) else "failed"
|
||||
@@ -307,6 +316,18 @@ class TaskRunner:
|
||||
return AIPlanner(config=self.planner_config)
|
||||
return Planner()
|
||||
|
||||
def _device_platform(self, device_id: str) -> str | None:
|
||||
if self.device_platform_provider is None:
|
||||
return None
|
||||
try:
|
||||
return self.device_platform_provider(device_id)
|
||||
except Exception:
|
||||
logger.warning(
|
||||
"device platform lookup failed; continuing without configured platform",
|
||||
exc_info=True,
|
||||
)
|
||||
return None
|
||||
|
||||
def _plan(
|
||||
self,
|
||||
goal: str,
|
||||
|
||||
Reference in New Issue
Block a user