reject off-contract calls during artifact completion

This commit is contained in:
pewdiepie-archdaemon
2026-09-18 15:37:49 +00:00
parent dff88f4fb0
commit 50a86bc650
2 changed files with 21 additions and 10 deletions
+7
View File
@@ -4450,6 +4450,13 @@ async def stream_preview(*, endpoint_url, model, messages, headers, turn_contrac
],
'tool_choice': request.get('tool_choice'),
})
# Artifact completion deliberately narrows a broader round
# contract to its writer on the wire. Validate that
# response against the same list so a hallucinated call to
# one of the earlier research tools cannot regain
# execution permission. Other recovery modes retain their
# established budget/error semantics.
round_offered = list(request.get('tools') or [])
async with preview_model_response(client, endpoint_url, headers, request, context_recovery) as response:
response.raise_for_status()
async for line in response.aiter_lines():
+14 -10
View File
@@ -4637,6 +4637,12 @@ async def test_native_stream_reserves_remaining_budget_for_required_artifact(mon
for index in range(module.NATIVE_ARTIFACT_RESEARCH_LIMIT - 1)
])
payloads.extend([
{"choices": [{"delta": {"tool_calls": [{
"index": 0, "id": "hallucinated-bash",
"function": {"name": "bash", "arguments": json.dumps({
"command": "echo should-not-run",
})},
}]}}]},
{"choices": [{"delta": {"tool_calls": [{
"index": 0, "id": "write",
"function": {"name": "write_file", "arguments": json.dumps({
@@ -4647,6 +4653,7 @@ async def test_native_stream_reserves_remaining_budget_for_required_artifact(mon
])
responses = iter(payloads)
requests = []
executed = []
class Response:
def __init__(self, payload): self.payload = payload
@@ -4666,17 +4673,18 @@ async def test_native_stream_reserves_remaining_budget_for_required_artifact(mon
return Response(next(responses))
async def execute(block, **kwargs):
executed.append(block.tool_type)
return block.tool_type, {"output": "ok", "exit_code": 0}
monkeypatch.setattr(module.httpx, "AsyncClient", Client)
monkeypatch.setattr(module, "execute_tool_block", execute)
schemas = [
item for item in FUNCTION_TOOL_SCHEMAS
if item["function"]["name"] in {"python", "web_search", "write_file"}
if item["function"]["name"] in {"bash", "python", "web_search", "write_file"}
]
contract = resolve_full_inventory_contract(schemas=schemas, policy=ToolPolicy())
raw = [chunk async for chunk in stream_preview(
endpoint_url="http://test", model="test",
endpoint_url="http://test", model="deepseek-flash",
messages=[{"role": "user", "content": "Research and create the requested output."}],
headers={}, turn_contract=contract, session_id="test", owner="test",
disabled_tools=set(), tool_policy=ToolPolicy(), workspace="/tmp/workspace",
@@ -4711,15 +4719,11 @@ async def test_native_stream_reserves_remaining_budget_for_required_artifact(mon
)
assert "write_file" in request_contract["offered_tools"]
assert "web_search" not in request_contract["offered_tools"]
assert request_contract["tool_choice"] == {
"type": "function", "function": {"name": "write_file"},
}
assert request_contract["tool_choice"] is None
reserved_names = [tool["function"]["name"] for tool in requests[12]["tools"]]
assert "write_file" in reserved_names
assert "web_search" not in reserved_names
assert requests[12]["tool_choice"] == {
"type": "function", "function": {"name": "write_file"},
}
assert reserved_names == ["write_file"]
assert "tool_choice" not in requests[12]
assert "bash" not in executed
assert any(
event.get("type") == "tool_output" and event.get("tool") == "write_file"
and not event.get("error") for event in events