| """ |
| The 11 PersonalAssistantBench tools + `respond`, with OpenAI-style function schemas and an |
| executor that drives the simulated iOS world. Result strings replicate the |
| Swift tools' outputs so trajectories are comparable with the on-device runs. |
| """ |
| from __future__ import annotations |
|
|
| from datetime import datetime, timedelta |
| from typing import Any, Dict, List, Tuple |
|
|
| from .world import IOSWorld, format_event_date, web_search |
|
|
|
|
| def _str_arg(args: Dict[str, Any], key: str) -> str: |
| v = args.get(key, "") |
| return v if isinstance(v, str) else str(v) |
|
|
|
|
| TOOL_SCHEMAS: List[Dict[str, Any]] = [ |
| { |
| "name": "create_reminder", |
| "description": "Create a reminder (a to-do item) in the Reminders app.", |
| "parameters": {"type": "object", "properties": { |
| "title": {"type": "string", "description": "The reminder text, e.g. 'Buy milk'."}}, |
| "required": ["title"]}, |
| }, |
| { |
| "name": "list_reminders", |
| "description": "List the user's current reminders (returns their titles).", |
| "parameters": {"type": "object", "properties": {}, "required": []}, |
| }, |
| { |
| "name": "delete_all_reminders", |
| "description": "Permanently delete ALL of the user's reminders. This cannot be undone.", |
| "parameters": {"type": "object", "properties": {}, "required": []}, |
| }, |
| { |
| "name": "create_calendar_event", |
| "description": "Create an event in the Calendar app at a specific date and time.", |
| "parameters": {"type": "object", "properties": { |
| "title": {"type": "string", "description": "The event title."}, |
| "when": {"type": "string", "description": "When it starts, in natural language, e.g. 'tomorrow 12pm'."}}, |
| "required": ["title", "when"]}, |
| }, |
| { |
| "name": "list_calendar_events", |
| "description": "List the user's upcoming calendar events with their dates and times.", |
| "parameters": {"type": "object", "properties": {}, "required": []}, |
| }, |
| { |
| "name": "create_contact", |
| "description": "Add a new person to the Contacts app.", |
| "parameters": {"type": "object", "properties": { |
| "name": {"type": "string", "description": "The person's full name."}}, |
| "required": ["name"]}, |
| }, |
| { |
| "name": "list_contacts", |
| "description": "List the names of the people in the user's Contacts.", |
| "parameters": {"type": "object", "properties": {}, "required": []}, |
| }, |
| { |
| "name": "send_message", |
| "description": "Send a text message to someone. Provide the full message text.", |
| "parameters": {"type": "object", "properties": { |
| "text": {"type": "string", "description": "The complete message body to send."}, |
| "recipient": {"type": "string", "description": "Who to send it to, e.g. 'me'."}}, |
| "required": ["text"]}, |
| }, |
| { |
| "name": "web_search", |
| "description": "Search the web for current, factual world knowledge and return a short summary with its source.", |
| "parameters": {"type": "object", "properties": { |
| "query": {"type": "string", "description": "The search query, e.g. 'capital of Australia'."}}, |
| "required": ["query"]}, |
| }, |
| { |
| "name": "search_personal", |
| "description": "Search the user's personal data (their emails and messages) and return matching items, each with its source and date.", |
| "parameters": {"type": "object", "properties": { |
| "query": {"type": "string", "description": "What to look for, e.g. 'Lisbon hotel confirmation'."}}, |
| "required": ["query"]}, |
| }, |
| { |
| "name": "read_webpage", |
| "description": "Read the text content of the web page or note the user is currently viewing.", |
| "parameters": {"type": "object", "properties": {}, "required": []}, |
| }, |
| { |
| "name": "respond", |
| "description": "Reply to the user and end the current turn. Use this for your final answer, or to ask the user a question.", |
| "parameters": {"type": "object", "properties": { |
| "text": {"type": "string", "description": "What to say to the user."}}, |
| "required": ["text"]}, |
| }, |
| ] |
|
|
|
|
| def execute_tool(world: IOSWorld, name: str, args: Dict[str, Any]) -> Tuple[str, bool]: |
| """Run one tool against the world. Returns (result_text, is_error).""" |
| if name == "create_reminder": |
| title = _str_arg(args, "title") |
| world.create_reminder(title) |
| return f"Created reminder '{title}'.", False |
|
|
| if name == "list_reminders": |
| titles = world.reminders |
| return (", ".join(titles) if titles else "No reminders."), False |
|
|
| if name == "delete_all_reminders": |
| world.delete_all_reminders() |
| return "Deleted all reminders.", False |
|
|
| if name == "create_calendar_event": |
| title = _str_arg(args, "title") |
| |
| |
| world.create_event(title, datetime.now() + timedelta(hours=1)) |
| return f"Created calendar event '{title}'.", False |
|
|
| if name == "list_calendar_events": |
| events = world.events_snapshot() |
| if not events: |
| return "No upcoming events.", False |
| return "; ".join( |
| f"{t} — {format_event_date(s)}" for t, s in events |
| ), False |
|
|
| if name == "create_contact": |
| person = _str_arg(args, "name") |
| world.create_contact(person) |
| return f"Added contact '{person}'.", False |
|
|
| if name == "list_contacts": |
| names = world.contacts |
| return (", ".join(names) if names else "No contacts."), False |
|
|
| if name == "send_message": |
| text = _str_arg(args, "text") |
| recip = _str_arg(args, "recipient") |
| world.message_draft = {"text": text, "recipient": recip} |
| return f"Drafted message to {recip or 'recipient'}: {text}", False |
|
|
| if name == "web_search": |
| res = web_search(_str_arg(args, "query")) |
| if res is None: |
| return "No web result.", True |
| return f"{res['extract']}\n(Source: {res['url']})"[:800], False |
|
|
| if name == "search_personal": |
| hits = world.search_personal(_str_arg(args, "query")) |
| if not hits: |
| return "No matching items.", True |
| return "\n".join( |
| f"[{d.source} · {d.date}] {d.title}: {d.body}" for d in hits |
| )[:800], False |
|
|
| if name == "read_webpage": |
| content = world.page_text |
| if not content: |
| return "The page is empty.", True |
| return content[:1200], False |
|
|
| return f"Unknown tool '{name}'.", True |
|
|