mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-16 03:42:20 +02:00
reject off-contract calls during artifact completion
This commit is contained in:
@@ -4450,6 +4450,13 @@ async def stream_preview(*, endpoint_url, model, messages, headers, turn_contrac
|
|||||||
],
|
],
|
||||||
'tool_choice': request.get('tool_choice'),
|
'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:
|
async with preview_model_response(client, endpoint_url, headers, request, context_recovery) as response:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
async for line in response.aiter_lines():
|
async for line in response.aiter_lines():
|
||||||
|
|||||||
@@ -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)
|
for index in range(module.NATIVE_ARTIFACT_RESEARCH_LIMIT - 1)
|
||||||
])
|
])
|
||||||
payloads.extend([
|
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": [{
|
{"choices": [{"delta": {"tool_calls": [{
|
||||||
"index": 0, "id": "write",
|
"index": 0, "id": "write",
|
||||||
"function": {"name": "write_file", "arguments": json.dumps({
|
"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)
|
responses = iter(payloads)
|
||||||
requests = []
|
requests = []
|
||||||
|
executed = []
|
||||||
|
|
||||||
class Response:
|
class Response:
|
||||||
def __init__(self, payload): self.payload = payload
|
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))
|
return Response(next(responses))
|
||||||
|
|
||||||
async def execute(block, **kwargs):
|
async def execute(block, **kwargs):
|
||||||
|
executed.append(block.tool_type)
|
||||||
return block.tool_type, {"output": "ok", "exit_code": 0}
|
return block.tool_type, {"output": "ok", "exit_code": 0}
|
||||||
|
|
||||||
monkeypatch.setattr(module.httpx, "AsyncClient", Client)
|
monkeypatch.setattr(module.httpx, "AsyncClient", Client)
|
||||||
monkeypatch.setattr(module, "execute_tool_block", execute)
|
monkeypatch.setattr(module, "execute_tool_block", execute)
|
||||||
schemas = [
|
schemas = [
|
||||||
item for item in FUNCTION_TOOL_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())
|
contract = resolve_full_inventory_contract(schemas=schemas, policy=ToolPolicy())
|
||||||
raw = [chunk async for chunk in stream_preview(
|
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."}],
|
messages=[{"role": "user", "content": "Research and create the requested output."}],
|
||||||
headers={}, turn_contract=contract, session_id="test", owner="test",
|
headers={}, turn_contract=contract, session_id="test", owner="test",
|
||||||
disabled_tools=set(), tool_policy=ToolPolicy(), workspace="/tmp/workspace",
|
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 "write_file" in request_contract["offered_tools"]
|
||||||
assert "web_search" not in request_contract["offered_tools"]
|
assert "web_search" not in request_contract["offered_tools"]
|
||||||
assert request_contract["tool_choice"] == {
|
assert request_contract["tool_choice"] is None
|
||||||
"type": "function", "function": {"name": "write_file"},
|
|
||||||
}
|
|
||||||
reserved_names = [tool["function"]["name"] for tool in requests[12]["tools"]]
|
reserved_names = [tool["function"]["name"] for tool in requests[12]["tools"]]
|
||||||
assert "write_file" in reserved_names
|
assert reserved_names == ["write_file"]
|
||||||
assert "web_search" not in reserved_names
|
assert "tool_choice" not in requests[12]
|
||||||
assert requests[12]["tool_choice"] == {
|
assert "bash" not in executed
|
||||||
"type": "function", "function": {"name": "write_file"},
|
|
||||||
}
|
|
||||||
assert any(
|
assert any(
|
||||||
event.get("type") == "tool_output" and event.get("tool") == "write_file"
|
event.get("type") == "tool_output" and event.get("tool") == "write_file"
|
||||||
and not event.get("error") for event in events
|
and not event.get("error") for event in events
|
||||||
|
|||||||
Reference in New Issue
Block a user