Files
1Cat-vLLM/benchmarks/benchmark_sm70_tool_protocol.py

1299 lines
45 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Exercise long, streamed tool calls and structured output through an API.
This is a deterministic protocol gate, not a substitute for BFCL task scores.
It catches malformed streamed arguments, invalid tool names, history replay
failures, and JSON-schema violations while retaining the raw SSE events needed
to localize a first divergence between two server configurations.
"""
from __future__ import annotations
import argparse
import copy
import json
import time
from pathlib import Path
from typing import Any
import jsonschema
import regex as re
import requests
TOOLS: list[dict[str, Any]] = [
{
"type": "function",
"function": {
"name": "Read",
"description": "Read a UTF-8 file.",
"parameters": {
"type": "object",
"properties": {"file_path": {"type": "string"}},
"required": ["file_path"],
"additionalProperties": False,
},
},
},
{
"type": "function",
"function": {
"name": "Grep",
"description": "Search files for a literal pattern.",
"parameters": {
"type": "object",
"properties": {
"pattern": {"type": "string"},
"path": {"type": "string"},
"output_mode": {
"type": "string",
"enum": ["content", "files_with_matches", "count"],
},
},
"required": ["pattern", "path", "output_mode"],
"additionalProperties": False,
},
},
},
{
"type": "function",
"function": {
"name": "Glob",
"description": "Find files matching a glob pattern.",
"parameters": {
"type": "object",
"properties": {
"pattern": {"type": "string"},
"path": {"type": "string"},
},
"required": ["pattern", "path"],
"additionalProperties": False,
},
},
},
{
"type": "function",
"function": {
"name": "Bash",
"description": "Run one shell command.",
"parameters": {
"type": "object",
"properties": {"command": {"type": "string"}},
"required": ["command"],
"additionalProperties": False,
},
},
},
{
"type": "function",
"function": {
"name": "Edit",
"description": "Replace an exact string in a file.",
"parameters": {
"type": "object",
"properties": {
"file_path": {"type": "string"},
"old_string": {"type": "string"},
"new_string": {"type": "string"},
"replace_all": {"type": "boolean"},
},
"required": [
"file_path",
"old_string",
"new_string",
"replace_all",
],
"additionalProperties": False,
},
},
},
{
"type": "function",
"function": {
"name": "Write",
"description": "Write complete UTF-8 content to a file.",
"parameters": {
"type": "object",
"properties": {
"file_path": {"type": "string"},
"content": {"type": "string"},
},
"required": ["file_path", "content"],
"additionalProperties": False,
},
},
},
]
STEPS: list[dict[str, Any]] = [
{
"name": "Read",
"prompt": "Call Read for /tmp/project/src/app.py. Do not call another tool.",
"arguments": {"file_path": "/tmp/project/src/app.py"},
"result": "def run():\n return 1\n",
},
{
"name": "Grep",
"prompt": (
"Call Grep for literal pattern run in /tmp/project/src with "
"output_mode=count. Do not call another tool."
),
"arguments": {
"pattern": "run",
"path": "/tmp/project/src",
"output_mode": "count",
},
"result": "/tmp/project/src/app.py:1",
},
{
"name": "Glob",
"prompt": (
"Call Glob with pattern **/*.py under /tmp/project. "
"Do not call another tool."
),
"arguments": {"pattern": "**/*.py", "path": "/tmp/project"},
"result": "/tmp/project/src/app.py",
},
{
"name": "Bash",
"prompt": (
"Call Bash with exactly: python -m pytest -q. Do not call another tool."
),
"arguments": {"command": "python -m pytest -q"},
"result": "8 passed in 1.20s",
},
{
"name": "Edit",
"prompt": (
"Call Edit on /tmp/project/src/app.py. Replace exactly the string "
"` return 1\\n` with ` return 2\\n`, and set replace_all=false. "
"Preserve all indentation and newlines."
),
"arguments": {
"file_path": "/tmp/project/src/app.py",
"old_string": " return 1\n",
"new_string": " return 2\n",
"replace_all": False,
},
"result": "updated /tmp/project/src/app.py",
},
{
"name": "Write",
"prompt": (
"Call Write for /tmp/project/result.json with exactly this content: "
'{"ok":true,"lines":["a\\nb","<parameter=x>literal"]}.'
),
"arguments": {
"file_path": "/tmp/project/result.json",
"content": '{"ok":true,"lines":["a\\nb","<parameter=x>literal"]}',
},
"result": "wrote 55 bytes",
},
]
STRUCTURED_CASES: list[dict[str, Any]] = [
{
"name": "nested",
"prompt": (
"Return the build record for project alpha, successful, with stages "
"compile=12 and test=8."
),
"schema": {
"type": "object",
"properties": {
"project": {"type": "string", "const": "alpha"},
"success": {"type": "boolean", "const": True},
"stages": {
"type": "array",
"prefixItems": [
{
"type": "object",
"properties": {
"name": {"const": "compile"},
"seconds": {"type": "integer", "const": 12},
},
"required": ["name", "seconds"],
"additionalProperties": False,
},
{
"type": "object",
"properties": {
"name": {"const": "test"},
"seconds": {"type": "integer", "const": 8},
},
"required": ["name", "seconds"],
"additionalProperties": False,
},
],
"minItems": 2,
"maxItems": 2,
},
},
"required": ["project", "success", "stages"],
"additionalProperties": False,
},
},
{
"name": "escaped_text",
"prompt": "Return path /tmp/a, the two-line text `a\\nb`, and count 2.",
"schema": {
"type": "object",
"properties": {
"path": {"type": "string", "const": "/tmp/a"},
"text": {"type": "string", "const": "a\nb"},
"count": {"type": "integer", "const": 2},
},
"required": ["path", "text", "count"],
"additionalProperties": False,
},
},
]
STREAM_PARITY_CASES: list[dict[str, str]] = [
{
"name": "quoted_ascii",
"expected": 'The "implied" volatility is a "forward-looking" measure.',
},
{
"name": "json_utf8",
"expected": '{"path":"/tmp/中.txt","text":"a\\nb","emoji":"🌶️"}',
},
]
def _schema_by_tool_name() -> dict[str, dict[str, Any]]:
return {item["function"]["name"]: item["function"]["parameters"] for item in TOOLS}
def _stream_chat(base_url: str, payload: dict[str, Any]) -> dict[str, Any]:
started = time.perf_counter()
response = requests.post(
f"{base_url.rstrip('/')}/v1/chat/completions",
json=payload,
stream=True,
timeout=600,
)
if response.status_code != 200:
return {
"ok": False,
"status_code": response.status_code,
"body": response.text,
"elapsed_seconds": time.perf_counter() - started,
}
raw_events: list[dict[str, Any]] = []
prompt_token_ids: list[int] = []
output_token_ids: list[int] = []
output_token_chunks: list[list[int]] = []
content = ""
reasoning = ""
finish_reason = None
calls: dict[int, dict[str, Any]] = {}
malformed_sse: list[str] = []
for raw_line in response.iter_lines(decode_unicode=True):
if not raw_line or not raw_line.startswith("data:"):
continue
data = raw_line[5:].strip()
if data == "[DONE]":
break
try:
event = json.loads(data)
except json.JSONDecodeError:
malformed_sse.append(data)
continue
raw_events.append(event)
choice = event.get("choices", [{}])[0]
if event.get("prompt_token_ids"):
prompt_token_ids.extend(event["prompt_token_ids"])
token_ids = choice.get("token_ids") or []
if token_ids:
output_token_chunks.append(token_ids)
output_token_ids.extend(token_ids)
finish_reason = choice.get("finish_reason") or finish_reason
delta = choice.get("delta") or {}
content += delta.get("content") or ""
reasoning += delta.get("reasoning_content") or ""
for call_delta in delta.get("tool_calls") or []:
index = int(call_delta.get("index", 0))
call = calls.setdefault(
index,
{
"id": "",
"type": "function",
"function": {"name": "", "arguments": ""},
},
)
call["id"] += call_delta.get("id") or ""
function = call_delta.get("function") or {}
call["function"]["name"] += function.get("name") or ""
call["function"]["arguments"] += function.get("arguments") or ""
return {
"ok": not malformed_sse,
"status_code": response.status_code,
"elapsed_seconds": time.perf_counter() - started,
"finish_reason": finish_reason,
"content": content,
"reasoning_content": reasoning,
"prompt_token_ids": prompt_token_ids,
"output_token_ids": output_token_ids,
"output_token_chunks": output_token_chunks,
"tool_calls": [calls[index] for index in sorted(calls)],
"malformed_sse": malformed_sse,
"raw_events": raw_events,
}
def _nonstream_chat(base_url: str, payload: dict[str, Any]) -> dict[str, Any]:
started = time.perf_counter()
response = requests.post(
f"{base_url.rstrip('/')}/v1/chat/completions",
json=payload,
timeout=600,
)
if response.status_code != 200:
return {
"ok": False,
"status_code": response.status_code,
"body": response.text,
"elapsed_seconds": time.perf_counter() - started,
}
try:
body = response.json()
except requests.JSONDecodeError as exc:
return {
"ok": False,
"status_code": response.status_code,
"body": response.text,
"error": str(exc),
"elapsed_seconds": time.perf_counter() - started,
}
choice = body.get("choices", [{}])[0]
message = choice.get("message") or {}
return {
"ok": True,
"status_code": response.status_code,
"elapsed_seconds": time.perf_counter() - started,
"finish_reason": choice.get("finish_reason"),
"content": message.get("content") or "",
"reasoning_content": message.get("reasoning_content") or "",
"prompt_token_ids": body.get("prompt_token_ids") or [],
"output_token_ids": choice.get("token_ids") or [],
"tool_calls": message.get("tool_calls") or [],
"raw_response": body,
}
def _responses_tools() -> list[dict[str, Any]]:
tools: list[dict[str, Any]] = []
for tool in TOOLS:
function = tool["function"]
tools.append(
{
"type": "function",
"name": function["name"],
"description": function["description"],
"parameters": function["parameters"],
"strict": True,
}
)
return tools
def _stream_responses(base_url: str, payload: dict[str, Any]) -> dict[str, Any]:
"""Collect Responses SSE without relying on a particular SDK version."""
started = time.perf_counter()
response = requests.post(
f"{base_url.rstrip('/')}/v1/responses",
json=payload,
stream=True,
timeout=600,
)
if response.status_code != 200:
return {
"ok": False,
"status_code": response.status_code,
"body": response.text,
"elapsed_seconds": time.perf_counter() - started,
}
raw_events: list[dict[str, Any]] = []
malformed_sse: list[str] = []
response_id = ""
response_status = ""
output_text_chunks: list[str] = []
protocol_errors: list[str] = []
calls: dict[int, dict[str, Any]] = {}
for raw_line in response.iter_lines(decode_unicode=True):
if not raw_line or not raw_line.startswith("data:"):
continue
data = raw_line[5:].strip()
if data == "[DONE]":
break
try:
event = json.loads(data)
except json.JSONDecodeError:
malformed_sse.append(data)
continue
raw_events.append(event)
event_type = event.get("type") or ""
event_response = event.get("response") or {}
response_id = event_response.get("id") or response_id
response_status = event_response.get("status") or response_status
if event_type == "response.output_text.delta":
output_text_chunks.append(event.get("delta") or "")
elif event_type == "response.output_item.added":
item = event.get("item") or {}
if item.get("type") == "function_call":
output_index = int(event.get("output_index", 0))
calls[output_index] = {
"output_index": output_index,
"added_item": item,
"argument_chunks": [],
"done_arguments": None,
"done_item": None,
}
elif event_type == "response.function_call_arguments.delta":
output_index = int(event.get("output_index", 0))
call = calls.get(output_index)
if call is None:
protocol_errors.append(
f"arguments delta before item at output {output_index}"
)
else:
call["argument_chunks"].append(event.get("delta") or "")
elif event_type == "response.function_call_arguments.done":
output_index = int(event.get("output_index", 0))
call = calls.get(output_index)
if call is None:
protocol_errors.append(
f"arguments done before item at output {output_index}"
)
else:
call["done_arguments"] = event.get("arguments") or ""
elif event_type == "response.output_item.done":
item = event.get("item") or {}
if item.get("type") == "function_call":
output_index = int(event.get("output_index", 0))
call = calls.get(output_index)
if call is None:
protocol_errors.append(
f"item done before item added at output {output_index}"
)
else:
call["done_item"] = item
elif event_type in {"response.failed", "error"}:
protocol_errors.append(json.dumps(event, ensure_ascii=False))
function_calls: list[dict[str, Any]] = []
for output_index in sorted(calls):
record = calls[output_index]
added = record["added_item"]
done = record["done_item"] or {}
delta_arguments = "".join(record["argument_chunks"])
done_arguments = record["done_arguments"]
final_arguments = done.get("arguments")
if final_arguments is None:
final_arguments = done_arguments
if final_arguments is None:
final_arguments = delta_arguments or added.get("arguments") or ""
if done_arguments is not None and done_arguments != delta_arguments:
protocol_errors.append(
f"arguments delta/done mismatch at output {output_index}"
)
if done and done.get("arguments") != final_arguments:
protocol_errors.append(
f"arguments done/item mismatch at output {output_index}"
)
if done and done.get("name") != added.get("name"):
protocol_errors.append(f"tool name changed at output {output_index}")
function_calls.append(
{
"output_index": output_index,
"id": done.get("id") or added.get("id") or "",
"call_id": done.get("call_id") or added.get("call_id") or "",
"name": done.get("name") or added.get("name") or "",
"arguments": final_arguments,
"argument_chunks": record["argument_chunks"],
"done_arguments": done_arguments,
"added_item": added,
"done_item": record["done_item"],
}
)
completed = any(event.get("type") == "response.completed" for event in raw_events)
if not completed:
protocol_errors.append("response.completed event missing")
return {
"ok": not malformed_sse and not protocol_errors,
"status_code": response.status_code,
"elapsed_seconds": time.perf_counter() - started,
"response_id": response_id,
"response_status": response_status,
"output_text": "".join(output_text_chunks),
"function_calls": function_calls,
"malformed_sse": malformed_sse,
"protocol_errors": protocol_errors,
"raw_events": raw_events,
}
def _validate_responses_tool_step(
result: dict[str, Any], expected: dict[str, Any]
) -> list[str]:
if not result.get("ok"):
return ["request failed"]
calls = result.get("function_calls") or []
if len(calls) != 1:
return [f"expected one function call, got {len(calls)}"]
call = calls[0]
errors: list[str] = []
if call["name"] != expected["name"]:
errors.append(f"tool name {call['name']!r} != {expected['name']!r}")
if not call.get("call_id"):
errors.append("call_id missing")
try:
arguments = json.loads(call["arguments"])
except json.JSONDecodeError as exc:
return errors + [f"invalid arguments JSON: {exc}"]
schema = _schema_by_tool_name().get(call["name"])
if schema is None:
errors.append(f"unknown tool {call['name']!r}")
else:
try:
jsonschema.validate(arguments, schema)
except jsonschema.ValidationError as exc:
errors.append(f"schema violation: {exc.message}")
if arguments != expected["arguments"]:
errors.append(f"arguments {arguments!r} != {expected['arguments']!r}")
return errors
def _responses_payload(
model: str,
seed: int,
input_items: str | list[dict[str, Any]],
tool_name: str,
*,
previous_response_id: str | None = None,
) -> dict[str, Any]:
payload: dict[str, Any] = {
"model": model,
"input": input_items,
"instructions": (
"Follow the user literally, call exactly the requested listed tool, "
"and preserve every string byte including whitespace and XML-like text."
),
"tools": _responses_tools(),
"tool_choice": {"type": "function", "name": tool_name},
"parallel_tool_calls": False,
"stream": True,
"store": True,
"temperature": 0.0,
"seed": seed,
"max_output_tokens": 2048,
"chat_template_kwargs": {"enable_thinking": False},
}
if previous_response_id is not None:
payload["previous_response_id"] = previous_response_id
return payload
def _run_responses_chain(base_url: str, model: str, seed: int) -> dict[str, Any]:
"""Cover stateful and explicit-history Responses tool protocols."""
stateful_steps = [*STEPS, STEPS[0]]
stateful_results: list[dict[str, Any]] = []
previous_response_id: str | None = None
previous_call_id = ""
previous_result = ""
for step_index, step in enumerate(stateful_steps):
if previous_response_id is None:
input_items: str | list[dict[str, Any]] = step["prompt"]
else:
input_items = [
{
"type": "function_call_output",
"call_id": previous_call_id,
"output": previous_result,
},
{"role": "user", "content": step["prompt"]},
]
payload = _responses_payload(
model,
seed,
input_items,
step["name"],
previous_response_id=previous_response_id,
)
result = _stream_responses(base_url, payload)
errors = _validate_responses_tool_step(result, step)
result["step"] = step_index
result["expected_name"] = step["name"]
result["errors"] = errors
stateful_results.append(result)
if errors:
break
previous_response_id = result["response_id"]
previous_call_id = result["function_calls"][0]["call_id"]
previous_result = step["result"]
explicit_input: list[dict[str, Any]] = []
for step_index, step in enumerate(STEPS):
call_id = f"call_explicit_history_{step_index}"
explicit_input.extend(
[
{"role": "user", "content": step["prompt"]},
{
"type": "function_call",
"call_id": call_id,
"name": step["name"],
"arguments": json.dumps(
step["arguments"], ensure_ascii=False, separators=(",", ":")
),
},
{
"type": "function_call_output",
"call_id": call_id,
"output": step["result"],
},
]
)
replay_step = STEPS[0]
explicit_input.append({"role": "user", "content": replay_step["prompt"]})
explicit_result = _stream_responses(
base_url,
_responses_payload(
model,
seed,
explicit_input,
replay_step["name"],
),
)
explicit_errors = _validate_responses_tool_step(explicit_result, replay_step)
explicit_result["errors"] = explicit_errors
return {
"passed": (
len(stateful_results) == len(stateful_steps)
and all(not item["errors"] for item in stateful_results)
and not explicit_errors
),
"stateful_steps": stateful_results,
"explicit_history_replay": explicit_result,
}
def _read_jsonl(path: Path) -> list[dict[str, Any]]:
return [json.loads(line) for line in path.read_text().splitlines() if line]
def _normalize_bfcl_schema(value: Any) -> Any:
if isinstance(value, list):
return [_normalize_bfcl_schema(item) for item in value]
if not isinstance(value, dict):
return value
normalized = {key: _normalize_bfcl_schema(item) for key, item in value.items()}
type_map = {"dict": "object", "float": "number", "tuple": "array"}
if isinstance(normalized.get("type"), str):
normalized["type"] = type_map.get(normalized["type"], normalized["type"])
return normalized
def _bfcl_tools(functions: list[dict[str, Any]]) -> list[dict[str, Any]]:
return [
{
"type": "function",
"function": {
"name": function["name"],
"description": function.get("description") or "",
"parameters": _normalize_bfcl_schema(function["parameters"]),
},
}
for function in functions
]
def _bfcl_normalized_value(value: Any) -> Any:
if isinstance(value, str):
return re.sub(r"[ ,./\-_*^]", "", value).lower().replace("'", '"')
if isinstance(value, list):
return [_bfcl_normalized_value(item) for item in value]
if isinstance(value, dict):
return {
key: _bfcl_normalized_value(item) for key, item in sorted(value.items())
}
return value
def _bfcl_call_matches(
actual: dict[str, Any],
expected: dict[str, Any],
functions: dict[str, dict[str, Any]],
) -> tuple[bool, str]:
expected_name, expected_arguments = next(iter(expected.items()))
if actual.get("name") != expected_name:
return False, f"tool name {actual.get('name')!r} != {expected_name!r}"
arguments = actual.get("arguments")
if not isinstance(arguments, dict):
return False, "arguments are not a JSON object"
function = functions.get(expected_name)
if function is None:
return False, f"function description missing for {expected_name!r}"
properties = function["parameters"].get("properties") or {}
required = function["parameters"].get("required") or []
for name in required:
if name not in arguments:
return False, f"required argument {name!r} missing"
for name, value in arguments.items():
if name not in properties or name not in expected_arguments:
return False, f"unexpected argument {name!r}"
allowed = expected_arguments[name]
if not any(
_bfcl_normalized_value(value) == _bfcl_normalized_value(candidate)
for candidate in allowed
):
return False, f"argument {name!r} value {value!r} not in {allowed!r}"
for name, allowed in expected_arguments.items():
if name not in arguments and "" not in allowed:
return False, f"expected argument {name!r} missing"
return True, ""
def _validate_bfcl_calls(
result: dict[str, Any],
entry: dict[str, Any],
ground_truth: list[dict[str, Any]] | None,
*,
irrelevance: bool,
) -> list[str]:
if not result.get("ok"):
return ["request failed"]
canonical = _canonical_tool_calls(result.get("tool_calls") or [])
if irrelevance:
return [] if not canonical else [f"irrelevant request called {canonical!r}"]
assert ground_truth is not None
if len(canonical) != len(ground_truth):
return [
f"wrong number of calls: got {len(canonical)}, expected {len(ground_truth)}"
]
functions = {function["name"]: function for function in entry["function"]}
unmatched = set(range(len(canonical)))
errors: list[str] = []
for expected in ground_truth:
candidate_errors: list[str] = []
for index in list(unmatched):
matched, error = _bfcl_call_matches(canonical[index], expected, functions)
if matched:
unmatched.remove(index)
break
candidate_errors.append(error)
else:
errors.append(f"no call matched {expected!r}: {candidate_errors!r}")
return errors
def _run_bfcl_sample(
base_url: str,
model: str,
seed: int,
data_dir: Path,
per_category: int,
) -> dict[str, Any]:
categories = {
"simple_python": False,
"parallel": False,
"multiple": False,
"irrelevance": True,
}
category_results: dict[str, Any] = {}
for category, irrelevance in categories.items():
filename = f"BFCL_v4_{category}.json"
entries = _read_jsonl(data_dir / filename)[:per_category]
ground_truth_by_id: dict[str, list[dict[str, Any]]] = {}
if not irrelevance:
ground_truth_by_id = {
item["id"]: item["ground_truth"]
for item in _read_jsonl(data_dir / "possible_answer" / filename)
}
results: list[dict[str, Any]] = []
for entry in entries:
payload = {
"model": model,
"messages": entry["question"][0],
"tools": _bfcl_tools(entry["function"]),
"tool_choice": "auto",
"parallel_tool_calls": True,
"stream": True,
"return_token_ids": True,
"temperature": 0.0,
"seed": seed,
"max_tokens": 1024,
"chat_template_kwargs": {"enable_thinking": False},
}
result = _stream_chat(base_url, payload)
errors = _validate_bfcl_calls(
result,
entry,
ground_truth_by_id.get(entry["id"]),
irrelevance=irrelevance,
)
result["id"] = entry["id"]
result["errors"] = errors
results.append(result)
category_results[category] = {
"passed": sum(not item["errors"] for item in results),
"total": len(results),
"cases": results,
}
passed = sum(item["passed"] for item in category_results.values())
total = sum(item["total"] for item in category_results.values())
return {
"passed": passed == total,
"score": passed,
"total": total,
"per_category": category_results,
"note": "Fixed BFCL v4 subset; not a full leaderboard submission.",
}
def _adapt_openai_json_schema(schema: dict[str, Any]) -> dict[str, Any]:
adapted = copy.deepcopy(schema)
if "type" not in adapted:
adapted["type"] = "object"
def visit(value: Any) -> None:
if isinstance(value, list):
for item in value:
visit(item)
return
if not isinstance(value, dict):
return
properties = value.get("properties")
if isinstance(properties, dict):
if value.get("additionalProperties", True):
value["additionalProperties"] = False
value["required"] = list(properties)
for item in value.values():
visit(item)
visit(adapted)
return adapted
def _run_json_schema_bench_sample(
base_url: str,
model: str,
seed: int,
schema_dir: Path,
limit: int,
max_schema_bytes: int,
max_tokens: int,
) -> dict[str, Any]:
results: list[dict[str, Any]] = []
candidates = sorted(
(
path
for path in schema_dir.glob("*.json")
if path.stat().st_size <= max_schema_bytes
),
key=lambda path: (path.stat().st_size, path.name),
)
if len(candidates) > limit:
if limit == 1:
candidates = [candidates[len(candidates) // 2]]
else:
candidates = [
candidates[round(index * (len(candidates) - 1) / (limit - 1))]
for index in range(limit)
]
for path in candidates:
schema = _adapt_openai_json_schema(json.loads(path.read_text()))
payload = {
"model": model,
"messages": [
{
"role": "system",
"content": (
"You need to generate a JSON object that matches the "
"schema below."
),
},
{
"role": "user",
"content": json.dumps(schema, ensure_ascii=False),
},
],
"stream": True,
"return_token_ids": True,
"temperature": 0.0,
"seed": seed,
"max_tokens": max_tokens,
"response_format": {
"type": "json_schema",
"json_schema": {
"name": path.stem,
"strict": True,
"schema": schema,
},
},
}
result = _stream_chat(base_url, payload)
errors: list[str] = []
if not result.get("ok"):
errors.append("request failed")
else:
try:
value = json.loads(result["content"])
jsonschema.validate(value, schema)
except (json.JSONDecodeError, jsonschema.ValidationError) as exc:
errors.append(str(exc))
result["schema"] = path.name
result["errors"] = errors
results.append(result)
passed = sum(not item["errors"] for item in results)
return {
"passed": passed == len(results),
"score": passed,
"total": len(results),
"cases": results,
"note": (
"Deterministic zero-shot WashingtonPost JSONSchemaBench subset; "
f"schemas are size-stratified up to {max_schema_bytes} bytes; not a "
"full benchmark score."
),
}
def _canonical_tool_calls(calls: list[dict[str, Any]]) -> list[dict[str, Any]]:
canonical: list[dict[str, Any]] = []
for call in calls:
function = call.get("function") or {}
arguments = function.get("arguments") or ""
try:
parsed_arguments: Any = json.loads(arguments)
except (TypeError, json.JSONDecodeError):
parsed_arguments = arguments
canonical.append(
{
"name": function.get("name") or "",
"arguments": parsed_arguments,
}
)
return canonical
def _compare_stream_pair(
nonstream: dict[str, Any],
stream: dict[str, Any],
*,
compare_content: bool,
compare_tools: bool,
) -> list[str]:
errors: list[str] = []
if not nonstream.get("ok") or not stream.get("ok"):
return ["stream or non-stream request failed"]
if compare_content and nonstream["content"] != stream["content"]:
errors.append("stream/non-stream content mismatch")
if nonstream["reasoning_content"] != stream["reasoning_content"]:
errors.append("stream/non-stream reasoning mismatch")
if compare_tools and _canonical_tool_calls(
nonstream["tool_calls"]
) != _canonical_tool_calls(stream["tool_calls"]):
errors.append("stream/non-stream tool-call mismatch")
nonstream_ids = nonstream.get("output_token_ids") or []
stream_ids = stream.get("output_token_ids") or []
if not nonstream_ids or not stream_ids:
errors.append("output token IDs unavailable for parity check")
elif nonstream_ids != stream_ids:
errors.append("stream/non-stream output token IDs differ")
return errors
def _run_stream_parity(base_url: str, model: str, seed: int) -> dict[str, Any]:
results: list[dict[str, Any]] = []
for case in STREAM_PARITY_CASES:
expected = case["expected"]
payload = {
"model": model,
"messages": [
{
"role": "user",
"content": (
"Reply with exactly the text after TEXT, without quotes, "
f"commentary, or a code fence.\nTEXT: {expected}"
),
}
],
"return_token_ids": True,
"temperature": 0.0,
"seed": seed,
"max_tokens": 256,
"chat_template_kwargs": {"enable_thinking": False},
}
nonstream = _nonstream_chat(base_url, {**payload, "stream": False})
stream = _stream_chat(base_url, {**payload, "stream": True})
errors = _compare_stream_pair(
nonstream,
stream,
compare_content=True,
compare_tools=False,
)
if nonstream.get("ok") and nonstream["content"] != expected:
errors.append("non-stream exact-copy output mismatch")
if stream.get("ok") and stream["content"] != expected:
errors.append("stream exact-copy output mismatch")
results.append(
{
"case": case["name"],
"expected": expected,
"nonstream": nonstream,
"stream": stream,
"errors": errors,
}
)
step = STEPS[0]
tool_payload = {
"model": model,
"messages": [{"role": "user", "content": step["prompt"]}],
"tools": TOOLS,
"tool_choice": "auto",
"return_token_ids": True,
"temperature": 0.0,
"seed": seed,
"max_tokens": 512,
"chat_template_kwargs": {"enable_thinking": False},
}
nonstream = _nonstream_chat(base_url, {**tool_payload, "stream": False})
stream = _stream_chat(base_url, {**tool_payload, "stream": True})
errors = _compare_stream_pair(
nonstream,
stream,
compare_content=True,
compare_tools=True,
)
if nonstream.get("ok"):
errors.extend(_validate_tool_step(nonstream, step))
if stream.get("ok"):
errors.extend(_validate_tool_step(stream, step))
results.append(
{
"case": "tool_read",
"nonstream": nonstream,
"stream": stream,
"errors": errors,
}
)
return {"passed": all(not item["errors"] for item in results), "cases": results}
def _validate_tool_step(result: dict[str, Any], expected: dict[str, Any]) -> list[str]:
errors: list[str] = []
calls = result.get("tool_calls") or []
if len(calls) != 1:
return [f"expected one tool call, got {len(calls)}"]
call = calls[0]
name = call["function"]["name"]
if name != expected["name"]:
errors.append(f"tool name {name!r} != {expected['name']!r}")
try:
arguments = json.loads(call["function"]["arguments"])
except json.JSONDecodeError as exc:
return errors + [f"invalid arguments JSON: {exc}"]
schema = _schema_by_tool_name().get(name)
if schema is None:
errors.append(f"unknown tool {name!r}")
else:
try:
jsonschema.validate(arguments, schema)
except jsonschema.ValidationError as exc:
errors.append(f"schema violation: {exc.message}")
if arguments != expected["arguments"]:
errors.append(f"arguments {arguments!r} != {expected['arguments']!r}")
return errors
def _run_tool_chain(base_url: str, model: str, seed: int) -> dict[str, Any]:
messages: list[dict[str, Any]] = [
{
"role": "system",
"content": (
"You are a tool protocol validation agent. Follow each user request "
"literally, call only a listed tool, and preserve string whitespace."
),
}
]
results: list[dict[str, Any]] = []
for step_index, step in enumerate(STEPS):
messages.append({"role": "user", "content": step["prompt"]})
payload = {
"model": model,
"messages": messages,
"tools": TOOLS,
"tool_choice": "auto",
"stream": True,
"return_token_ids": True,
"temperature": 0.0,
"seed": seed,
"max_tokens": 2048,
}
result = _stream_chat(base_url, payload)
errors = (
_validate_tool_step(result, step)
if result.get("ok")
else ["request failed"]
)
result["step"] = step_index
result["expected_name"] = step["name"]
result["errors"] = errors
results.append(result)
if errors:
break
tool_call = result["tool_calls"][0]
history_tool_call = copy.deepcopy(tool_call)
history_tool_call["id"] = f"call_protocol_step_{step_index}"
messages.append(
{
"role": "assistant",
"content": result.get("content") or None,
"tool_calls": [history_tool_call],
}
)
messages.append(
{
"role": "tool",
"tool_call_id": history_tool_call["id"],
"content": step["result"],
}
)
return {
"passed": len(results) == len(STEPS)
and all(not item["errors"] for item in results),
"steps": results,
}
def _run_structured(base_url: str, model: str, seed: int) -> dict[str, Any]:
results: list[dict[str, Any]] = []
for case in STRUCTURED_CASES:
payload = {
"model": model,
"messages": [{"role": "user", "content": case["prompt"]}],
"stream": True,
"return_token_ids": True,
"temperature": 0.0,
"seed": seed,
"max_tokens": 512,
# This is a protocol/escaping gate, not a reasoning-budget gate.
# Thinking is covered separately with a sufficiently large output
# budget so it cannot consume this fixed 512-token contract.
"chat_template_kwargs": {"enable_thinking": False},
"response_format": {
"type": "json_schema",
"json_schema": {
"name": case["name"],
"strict": True,
"schema": case["schema"],
},
},
}
result = _stream_chat(base_url, payload)
errors: list[str] = []
if not result.get("ok"):
errors.append("request failed")
else:
try:
value = json.loads(result["content"])
jsonschema.validate(value, case["schema"])
except (json.JSONDecodeError, jsonschema.ValidationError) as exc:
errors.append(str(exc))
result["case"] = case["name"]
result["errors"] = errors
results.append(result)
return {"passed": all(not item["errors"] for item in results), "cases": results}
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--base-url", required=True)
parser.add_argument("--model", default="quality-audit-dflash2")
parser.add_argument("--out", type=Path, required=True)
parser.add_argument("--seed", type=int, default=20260827)
parser.add_argument("--bfcl-data-dir", type=Path)
parser.add_argument("--bfcl-per-category", type=int, default=0)
parser.add_argument("--json-schema-dir", type=Path)
parser.add_argument("--json-schema-limit", type=int, default=0)
parser.add_argument("--json-schema-max-bytes", type=int, default=16384)
parser.add_argument("--json-schema-max-tokens", type=int, default=2048)
parser.add_argument(
"--only-datasets",
action="store_true",
help="Skip synthetic protocol and Responses gates for matched dataset arms.",
)
args = parser.parse_args()
artifact: dict[str, Any] = {
"base_url": args.base_url,
"model": args.model,
"seed": args.seed,
"only_datasets": args.only_datasets,
}
if not args.only_datasets:
artifact.update(
{
"tool_chain": _run_tool_chain(args.base_url, args.model, args.seed),
"structured": _run_structured(args.base_url, args.model, args.seed),
"stream_parity": _run_stream_parity(
args.base_url, args.model, args.seed
),
"responses": _run_responses_chain(args.base_url, args.model, args.seed),
}
)
if args.bfcl_data_dir is not None and args.bfcl_per_category > 0:
artifact["bfcl_fixed_sample"] = _run_bfcl_sample(
args.base_url,
args.model,
args.seed,
args.bfcl_data_dir,
args.bfcl_per_category,
)
if args.json_schema_dir is not None and args.json_schema_limit > 0:
artifact["json_schema_bench_fixed_sample"] = _run_json_schema_bench_sample(
args.base_url,
args.model,
args.seed,
args.json_schema_dir,
args.json_schema_limit,
args.json_schema_max_bytes,
args.json_schema_max_tokens,
)
result_keys = {
"tool_chain",
"structured",
"stream_parity",
"responses",
"bfcl_fixed_sample",
"json_schema_bench_fixed_sample",
}
required_results = [value for key, value in artifact.items() if key in result_keys]
artifact["passed"] = bool(
required_results and all(value["passed"] for value in required_results)
)
args.out.parent.mkdir(parents=True, exist_ok=True)
args.out.write_text(json.dumps(artifact, ensure_ascii=False, indent=2) + "\n")
print(json.dumps({"passed": artifact["passed"], "out": str(args.out)}))
if not artifact["passed"]:
raise SystemExit(1)
if __name__ == "__main__":
main()