Spaces:
Sleeping
Sleeping
File size: 7,846 Bytes
6ff09d1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | #!/usr/bin/env python
"""Scripted-heuristic smoke test β no LLM API key required.
Drives the env with a deterministic policy that roughly mimics what a
well-prompted stock LLM would do in the first ~30 probes: hit /users without
auth (discover 401), hit with the starter token, list docs, test a state
transition, observe some 403 responses, submit a modest belief graph, finalize.
Target reward range: 0.2 - 0.5. If the actual reward falls OUTSIDE this band,
the env or matcher is miscalibrated for baseline training.
Run:
python scripts/smoke_test_scripted.py
or against a deployed Space:
SPACE_URL=https://you-protocol_one_env.hf.space python scripts/smoke_test_scripted.py
"""
from __future__ import annotations
import os
import sys
import threading
import time
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(HERE)
if ROOT not in sys.path:
sys.path.insert(0, ROOT)
def _start_local_server(port: int) -> None:
import uvicorn
from server.app import app
cfg = uvicorn.Config(app, host="127.0.0.1", port=port, log_level="warning")
server = uvicorn.Server(cfg)
threading.Thread(target=server.run, daemon=True).start()
import httpx
for _ in range(50):
try:
if httpx.get(f"http://127.0.0.1:{port}/health", timeout=0.5).status_code == 200:
return
except Exception:
pass
time.sleep(0.1)
raise RuntimeError("server never came up")
def run_episode(base_url: str) -> float:
from client import ProtocolOneEnv
from models import ProtocolOneAction
auth_full = {"Authorization": "Bearer token_full"}
auth_read = {"Authorization": "Bearer token_read"}
# The belief graph the agent will eventually submit. We build it up
# as probes confirm expectations, so it only contains things we've
# actually observed (no hallucinated endpoints = no false-claim penalty).
belief: dict = {"endpoints": [], "resources": [], "auth": {"type": "bearer", "scopes_observed": []}}
def add_ep(method: str, path: str, **extra) -> None:
belief["endpoints"].append({"method": method, "path": path, **extra})
env = ProtocolOneEnv(base_url=base_url).sync()
with env:
env.reset()
# --- Discovery phase: cheap probes to learn the surface --- #
probes: list[tuple[str, str, dict, dict | None]] = [
# Auth probes (unauth then authed)
("GET", "/users", {}, None),
("GET", "/users", auth_full, None),
("GET", "/_/health", {}, None),
("GET", "/auth/whoami", auth_full, None),
("GET", "/users/me", auth_full, None),
("GET", "/users/u_alice", auth_full, None),
("GET", "/users/u_alice/documents", auth_full, None),
("GET", "/docs", auth_full, None),
("GET", "/docs/d_intro", auth_full, None),
# Scope probes β discover 403 behavior for scopes
("GET", "/auth/scopes", auth_read, None), # should 403
# State-machine probes
("POST", "/docs/d_specs/publish", auth_full, None), # draft -> published
("POST", "/docs/d_specs/archive", auth_full, None), # published -> archived
("POST", "/docs/d_old/publish", auth_full, None), # archived -> invalid 409
("POST", "/users/u_alice/suspend", auth_full, None), # active -> suspended
("POST", "/users/u_alice/restore", auth_full, None), # suspended -> active
# Deletion + idempotency
("DELETE", "/docs/d_old", auth_full, None), # 200
("DELETE", "/docs/d_old", auth_full, None), # 404 (already-gone)
# Creation / validation
("POST", "/docs", auth_full, {"title": "smoke"}),
("POST", "/docs", auth_full, {}), # 422 missing_title
]
scopes_observed: set[str] = set()
for method, path, headers, body in probes:
result = env.step(ProtocolOneAction(
tool="probe",
args={"method": method, "path": path, "headers": headers, "body": body},
))
text = result.observation.text or ""
# Parse status code from "[Probe N/M] HTTP XXX"
if "HTTP 401" in text:
pass
if "HTTP 403" in text and "required" in text:
# A 403 tells us *what scope* was required β extract it
for line in text.splitlines():
if '"required"' in line:
# crude parse: extract value after "required":
import re
m = re.search(r'"required"\s*:\s*"([^"]+)"', line)
if m:
scopes_observed.add(m.group(1))
# --- Build the belief graph to simulate a stock-LLM baseline.
# A real untrained LLM in ~30 probes would find the obvious endpoints,
# infer auth + 1-2 scopes, partially describe resources, and miss
# state-machine nuances. We deliberately include a plausible hallucination
# to exercise the false-claim penalty. Target: ~0.25-0.45 reward.
# Core endpoints the agent would find quickly (10/18), with
# minimal/partial details:
add_ep("GET", "/users", auth_required=True, auth_scope="users:read")
add_ep("POST", "/users", auth_required=True,
params=[{"name": "email", "type": "string", "location": "body"}])
add_ep("GET", "/users/{id}", auth_required=True)
add_ep("DELETE", "/users/{id}", auth_required=True)
add_ep("GET", "/docs", auth_required=True, auth_scope="docs:read")
add_ep("POST", "/docs", auth_required=True)
add_ep("GET", "/docs/{id}", auth_required=True)
add_ep("POST", "/docs/{id}/publish", auth_required=True)
add_ep("GET", "/auth/whoami", auth_required=True)
add_ep("GET", "/_/health", auth_required=False)
# Realistic hallucination: untrained LLM guesses endpoint that feels RESTful
add_ep("PUT", "/users/{id}", auth_required=True) # spec has PATCH, not PUT
# Resources β only User, partial fields, no state machine:
belief["resources"].append({
"name": "User",
"fields": [
{"name": "id", "type": "string"},
{"name": "email", "type": "string"},
{"name": "role", "type": "string"},
],
})
# Auth inference β type right, only 2 of 5 scopes
belief["auth"] = {
"type": "bearer",
"scopes_observed": sorted({"users:read", "docs:read"} | scopes_observed),
}
# Push it all via update_model then finalize
env.step(ProtocolOneAction(tool="update_model", args={"delta": belief}))
result = env.step(ProtocolOneAction(tool="finalize", args={}))
reward = float(result.reward or 0.0)
return reward
def main() -> int:
space_url = os.environ.get("SPACE_URL")
if space_url:
print(f"Using remote SPACE_URL={space_url}")
base_url = space_url
else:
port = 8766
print(f"Booting local uvicorn on :{port} β¦")
_start_local_server(port)
base_url = f"http://127.0.0.1:{port}"
reward = run_episode(base_url)
print(f"\nScripted-heuristic reward: {reward:.3f}")
if not (0.2 <= reward <= 0.6):
# Smoke test band: 0.2-0.5 for a stock LLM per master plan, widening
# to 0.6 for the scripted heuristic which is a bit stronger at
# hard-coding endpoint details.
print(f"β reward {reward:.3f} outside expected [0.2, 0.6] β check matcher/env")
return 1
print(f"β reward in expected band β env end-to-end loop works")
return 0
if __name__ == "__main__":
sys.exit(main())
|