ladder20 complete 28/28 (Qwen3-8B pool): fixes en route — tau2 off-loop executor, double-encoded tool args, shared adapter cache; swe real-model 0.0 (oracle path verified 1.0)

This commit is contained in:
sora 2026-08-26 10:37:54 +00:00
parent 0e1e31ba10
commit 111336cbee
3 changed files with 34 additions and 3 deletions

View File

@ -123,8 +123,17 @@ class Tau2Environment(Environment):
task = Task.model_validate(task_json if not isinstance(task_json, str) task = Task.model_validate(task_json if not isinstance(task_json, str)
else json.loads(task_json)) else json.loads(task_json))
domain = (sample.metadata or {}).get('domain') or 'airline' domain = (sample.metadata or {}).get('domain') or 'airline'
res = run_task(domain=domain, task=task, agent='llm_agent', # the official engine is SYNCHRONOUS and calls our adapter back via a
user='user_simulator', max_steps=max_turns) # private event loop in a worker thread; run it off the main loop so it
# never blocks the runner's other benches
import concurrent.futures
loop = asyncio.get_running_loop()
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
res = await loop.run_in_executor(
pool, lambda: run_task(domain=domain, task=task,
agent='llm_agent', user='user_simulator',
max_steps=max_turns))
rewards = {} rewards = {}
try: try:
info = res.reward_info info = res.reward_info

View File

@ -25,6 +25,10 @@ from .output import ModelOutput, ToolCall, Usage
ADAPTER_REGISTRY = EvalRegistry('model adapter') ADAPTER_REGISTRY = EvalRegistry('model adapter')
_ADAPTER_CACHE = {} # spec -> shared instance; keeps pool round-robin state
# GLOBAL across benches (else each pool restarts at the
# first backend and starves the rest)
def register_adapter(name: str): def register_adapter(name: str):
def decorator(cls): def decorator(cls):
@ -87,9 +91,13 @@ def resolve_adapter(spec: str, deploy_fn=None) -> ModelAdapter:
endpoint = deploy_fn(m.group('deployer'), m.group('model')) endpoint = deploy_fn(m.group('deployer'), m.group('model'))
spec = f"openai/{endpoint['api_base']}?{endpoint['model']}" spec = f"openai/{endpoint['api_base']}?{endpoint['model']}"
parsed = parse_model_spec(spec) parsed = parse_model_spec(spec)
if spec in _ADAPTER_CACHE:
return _ADAPTER_CACHE[spec]
cls = ADAPTER_REGISTRY.get(parsed['adapter']) cls = ADAPTER_REGISTRY.get(parsed['adapter'])
key = parsed.get('api_base') and _key_for(parsed['api_base']) key = parsed.get('api_base') and _key_for(parsed['api_base'])
return cls(model=parsed['model'], api_base=parsed['api_base'], api_key=key) inst = cls(model=parsed['model'], api_base=parsed['api_base'], api_key=key)
_ADAPTER_CACHE[spec] = inst
return inst
def _key_for(api_base: str) -> str: def _key_for(api_base: str) -> str:
@ -219,6 +227,10 @@ class OpenAICompatible(ModelAdapter):
args = fn.get('arguments') or '{}' args = fn.get('arguments') or '{}'
try: try:
args_dict = json.loads(args) args_dict = json.loads(args)
if isinstance(args_dict, str): # double-encoded JSON string
args_dict = json.loads(args_dict)
if not isinstance(args_dict, dict):
args_dict = {'raw': args_dict}
except (ValueError, TypeError): except (ValueError, TypeError):
args_dict = {} args_dict = {}
calls.append(ToolCall(id=c.get('id', ''), name=fn.get('name', ''), calls.append(ToolCall(id=c.get('id', ''), name=fn.get('name', ''),

View File

@ -382,7 +382,16 @@ def _make_adapter(spec: str) -> ModelAdapter:
- 'openai-pool/<base-url-template>?model' with {port} placeholder: - 'openai-pool/<base-url-template>?model' with {port} placeholder:
e.g. 'openai-pool/http://127.0.0.1:{8123..8130}/v1?Qwen3-8B' -> N ports e.g. 'openai-pool/http://127.0.0.1:{8123..8130}/v1?Qwen3-8B' -> N ports
- else resolve_adapter(spec) single endpoint - else resolve_adapter(spec) single endpoint
Pooled specs are CACHED per spec: all benches share one pool so the
round-robin counter stays global (independent pools would each restart
at the first backend and starve the rest).
""" """
from .adapter import _ADAPTER_CACHE as _CACHE
cache_key = spec
if cache_key in _CACHE:
return _CACHE[cache_key]
opts = {} opts = {}
while True: while True:
for f in ('!nothink', '!textools'): for f in ('!nothink', '!textools'):
@ -417,6 +426,7 @@ def _make_adapter(spec: str) -> ModelAdapter:
a.extra['no_think'] = True a.extra['no_think'] = True
if opts.get('!textools'): if opts.get('!textools'):
a.extra['tools_mode'] = 'text' a.extra['tools_mode'] = 'text'
_CACHE[cache_key] = adapter
return adapter return adapter