mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-21 06:12:21 +02:00
handle directory artifact completion safely
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
|||||||
0.20.6
|
0.20.7
|
||||||
|
|||||||
@@ -1571,8 +1571,15 @@ def artifact_completion_tool_schemas(offered_schemas, required_artifacts):
|
|||||||
|
|
||||||
|
|
||||||
def required_artifact_completion_tool_choice(required_artifacts, offered_schemas):
|
def required_artifact_completion_tool_choice(required_artifacts, offered_schemas):
|
||||||
"""Choose Python for one binary output; preserve the text-writer default."""
|
"""Choose a writer only when the contract names an exact output file.
|
||||||
|
|
||||||
|
A directory contract needs the model to choose one or more child filenames.
|
||||||
|
Forcing ``write_file`` there encourages an impossible write to the directory
|
||||||
|
path itself, so leave tool choice unconstrained for that case.
|
||||||
|
"""
|
||||||
targets = [str(path or '').strip().rstrip('/') for path in required_artifacts]
|
targets = [str(path or '').strip().rstrip('/') for path in required_artifacts]
|
||||||
|
if not targets or any(not Path(target).suffix for target in targets):
|
||||||
|
return None
|
||||||
offered = {
|
offered = {
|
||||||
canonical((schema.get('function') or {}).get('name')):
|
canonical((schema.get('function') or {}).get('name')):
|
||||||
(schema.get('function') or {}).get('name')
|
(schema.get('function') or {}).get('name')
|
||||||
@@ -4451,6 +4458,17 @@ async def stream_preview(*, endpoint_url, model, messages, headers, turn_contrac
|
|||||||
and calls >= min(NATIVE_ARTIFACT_RESEARCH_LIMIT, tool_call_limit - 1)
|
and calls >= min(NATIVE_ARTIFACT_RESEARCH_LIMIT, tool_call_limit - 1)
|
||||||
):
|
):
|
||||||
artifact_write_phase = True
|
artifact_write_phase = True
|
||||||
|
directory_artifact_guidance = ''
|
||||||
|
directory_targets = [
|
||||||
|
path for path in required_artifacts
|
||||||
|
if not Path(str(path or '').strip().rstrip('/')).suffix
|
||||||
|
]
|
||||||
|
if directory_targets:
|
||||||
|
directory_artifact_guidance = (
|
||||||
|
' Each listed directory is a container: create one or more files '
|
||||||
|
'inside it with meaningful content. Do not pass the directory itself '
|
||||||
|
'as a file path.'
|
||||||
|
)
|
||||||
history.append({
|
history.append({
|
||||||
'role': 'user',
|
'role': 'user',
|
||||||
'_harness_control': True,
|
'_harness_control': True,
|
||||||
@@ -4459,7 +4477,9 @@ async def stream_preview(*, endpoint_url, model, messages, headers, turn_contrac
|
|||||||
'unwritten after substantial research: '
|
'unwritten after substantial research: '
|
||||||
+ ', '.join(required_artifacts)
|
+ ', '.join(required_artifacts)
|
||||||
+ '. Use the evidence already gathered and the offered workspace tools '
|
+ '. Use the evidence already gathered and the offered workspace tools '
|
||||||
'to create and verify the required outputs now. Do not continue broad '
|
'to create and verify the required outputs now.'
|
||||||
|
+ directory_artifact_guidance
|
||||||
|
+ ' Do not continue broad '
|
||||||
'web, document, or media research.'
|
'web, document, or media research.'
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -679,6 +679,17 @@ def test_required_binary_artifact_forces_python_instead_of_text_writer():
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_required_directory_artifact_does_not_force_writer_to_directory_path():
|
||||||
|
offered = [
|
||||||
|
{'type': 'function', 'function': {'name': 'bash'}},
|
||||||
|
{'type': 'function', 'function': {'name': 'write_file'}},
|
||||||
|
]
|
||||||
|
|
||||||
|
assert required_artifact_completion_tool_choice(
|
||||||
|
('/tmp_workspace/results',), offered,
|
||||||
|
) is None
|
||||||
|
|
||||||
|
|
||||||
def test_action_promise_response_rejects_future_work_but_not_real_answers():
|
def test_action_promise_response_rejects_future_work_but_not_real_answers():
|
||||||
import src.clean_agent_preview as module
|
import src.clean_agent_preview as module
|
||||||
|
|
||||||
@@ -5317,9 +5328,11 @@ async def test_native_stream_reserves_remaining_budget_for_required_artifact(mon
|
|||||||
assert "web_search" not in request_contract["offered_tools"]
|
assert "web_search" not in request_contract["offered_tools"]
|
||||||
assert request_contract["tool_choice"] is None
|
assert request_contract["tool_choice"] is None
|
||||||
reserved_names = [tool["function"]["name"] for tool in requests[12]["tools"]]
|
reserved_names = [tool["function"]["name"] for tool in requests[12]["tools"]]
|
||||||
assert reserved_names == ["write_file"]
|
assert reserved_names == ["bash", "python", "write_file"]
|
||||||
assert "tool_choice" not in requests[12]
|
assert "tool_choice" not in requests[12]
|
||||||
assert "bash" not in executed
|
assert "create one or more files inside" in requests[12]["messages"][-1]["content"]
|
||||||
|
assert "Do not pass the directory itself as a file path" in requests[12]["messages"][-1]["content"]
|
||||||
|
assert "bash" in executed
|
||||||
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
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[1]
|
|||||||
def test_original_harness_version_is_canonical_semver() -> None:
|
def test_original_harness_version_is_canonical_semver() -> None:
|
||||||
version = (ROOT / "HARNESS_VERSION").read_text(encoding="utf-8").strip()
|
version = (ROOT / "HARNESS_VERSION").read_text(encoding="utf-8").strip()
|
||||||
|
|
||||||
assert version == "0.20.6"
|
assert version == "0.20.7"
|
||||||
assert re.fullmatch(r"\d+\.\d+\.\d+", version)
|
assert re.fullmatch(r"\d+\.\d+\.\d+", version)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user