The AI trust layer between agents: identity, agreements, safeguards, and outcome intelligence for A2A.

Viewing files at commit 95b0e11125d2Commit detailsView latest

benchmarks/tau3/run_experiment.py 136 lines · 5.1 KB · python
1
#!/usr/bin/env python3
2
"""Run baseline, generic-review, or OpenClasp Shield τ³ evaluations."""
3
4
from __future__ import annotations
5
6
import argparse
7
import json
8
import os
9
from pathlib import Path
10
11
from tau2.data_model.simulation import TextRunConfig
12
from tau2.runner import run_domain
13
14
from openclasp_agent import close_shield_cases, register_openclasp_agents
15
from openclasp_client import OpenClaspMcpClient
16
17
18
def json_object(value: str) -> dict[str, object]:
19
    try:
20
        parsed = json.loads(value)
21
    except json.JSONDecodeError as error:
22
        raise argparse.ArgumentTypeError(f"invalid JSON: {error.msg}") from error
23
    if not isinstance(parsed, dict):
24
        raise argparse.ArgumentTypeError("LLM arguments must be a JSON object")
25
    return parsed
26
27
28
def parse_args() -> argparse.Namespace:
29
    parser = argparse.ArgumentParser()
30
    parser.add_argument(
31
        "--mode", choices=["baseline", "generic-review", "shield"], required=True
32
    )
33
    parser.add_argument("--domain", default="airline")
34
    parser.add_argument("--task-ids", nargs="+")
35
    parser.add_argument("--num-tasks", type=int)
36
    parser.add_argument("--num-trials", type=int, default=1)
37
    parser.add_argument("--agent-llm", default="openai/gpt-4.1")
38
    parser.add_argument("--user-llm", default="openai/gpt-4.1")
39
    parser.add_argument("--generic-review-model")
40
    parser.add_argument("--max-concurrency", type=int, default=1)
41
    parser.add_argument("--seed", type=int, default=300)
42
    parser.add_argument("--agent-llm-args", type=json_object, default={})
43
    parser.add_argument("--user-llm-args", type=json_object, default={})
44
    parser.add_argument("--retrieval-config")
45
    parser.add_argument("--trigger", choices=["decisions", "actions", "all"], default="decisions")
46
    parser.add_argument("--save-to")
47
    parser.add_argument("--summary-file")
48
    return parser.parse_args()
49
50
51
def main() -> None:
52
    args = parse_args()
53
    save_to = args.save_to or f"openclasp_{args.mode.replace('-', '_')}_{args.domain}"
54
    summary_file = Path(args.summary_file or f"{save_to}_summary.json")
55
    os.environ["OPENCLASP_TAU_DOMAIN"] = args.domain
56
    os.environ["OPENCLASP_TAU_TRIGGER"] = args.trigger
57
    if args.generic_review_model:
58
        os.environ["OPENCLASP_GENERIC_REVIEW_MODEL"] = args.generic_review_model
59
60
    register_openclasp_agents()
61
    agent = {
62
        "baseline": "llm_agent",
63
        "generic-review": "openclasp_generic_review_agent",
64
        "shield": "openclasp_shield_agent",
65
    }[args.mode]
66
    if args.mode == "shield":
67
        token = os.getenv("OPENCLASP_AGENT_TOKEN", "")
68
        client = OpenClaspMcpClient(
69
            os.getenv("OPENCLASP_URL", "https://openclasp.dev"), token
70
        )
71
        client.call_tool("openclasp_shield_list_cases", {})
72
    else:
73
        client = None
74
75
    config = TextRunConfig(
76
        domain=args.domain,
77
        agent=agent,
78
        llm_agent=args.agent_llm,
79
        llm_user=args.user_llm,
80
        llm_args_agent=args.agent_llm_args,
81
        llm_args_user=args.user_llm_args,
82
        task_ids=args.task_ids,
83
        num_tasks=args.num_tasks,
84
        num_trials=args.num_trials,
85
        max_concurrency=args.max_concurrency,
86
        workers=0,
87
        # Current τ³ exposes its per-run simulation ID to custom agent factories only
88
        # while per-task logging is enabled. The adapter needs that ID to join Shield
89
        # cases to evaluator outcomes without exposing hidden task data to Shield.
90
        verbose_logs=True,
91
        seed=args.seed,
92
        retrieval_config=args.retrieval_config,
93
        save_to=save_to,
94
    )
95
    results = run_domain(config)
96
    shield_cases = close_shield_cases(results, client) if client else []
97
    rewards = [
98
        simulation.reward_info.reward
99
        for simulation in results.simulations
100
        if simulation.reward_info and simulation.reward_info.reward is not None
101
    ]
102
    summary = {
103
        "mode": args.mode,
104
        "domain": args.domain,
105
        "agent": agent,
106
        "agentModel": args.agent_llm,
107
        "userModel": args.user_llm,
108
        "agentLlmArgs": args.agent_llm_args,
109
        "userLlmArgs": args.user_llm_args,
110
        "seed": args.seed,
111
        "numTrials": args.num_trials,
112
        "simulationCount": len(results.simulations),
113
        "averageReward": sum(rewards) / len(rewards) if rewards else None,
114
        "passCount": sum(reward >= 0.999 for reward in rewards),
115
        "rewards": rewards,
116
        "simulations": [
117
            {
118
                "simulationId": simulation.id,
119
                "taskId": simulation.task_id,
120
                "trial": simulation.trial,
121
                "seed": simulation.seed,
122
                "reward": simulation.reward_info.reward if simulation.reward_info else None,
123
                "termination": str(simulation.termination_reason.value),
124
                "agentCost": simulation.agent_cost,
125
                "duration": simulation.duration,
126
            }
127
            for simulation in results.simulations
128
        ],
129
        "shieldCases": shield_cases,
130
    }
131
    summary_file.write_text(json.dumps(summary, indent=2) + "\n", encoding="utf-8")
132
    print(f"OpenClasp experiment summary: {summary_file.resolve()}")
133
134
135
if __name__ == "__main__":
136
    main()