mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-28 16:52:22 +01:00
exclude known inputs from completion artifacts
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
|||||||
0.20.8
|
0.20.9
|
||||||
|
|||||||
+79
-2
@@ -239,11 +239,48 @@ def _artifact_match_is_email_host(instruction: str, match: re.Match[str]) -> boo
|
|||||||
return bool(re.search(r"[A-Za-z0-9_.+-]+@$", prefix))
|
return bool(re.search(r"[A-Za-z0-9_.+-]+@$", prefix))
|
||||||
|
|
||||||
|
|
||||||
|
def _workspace_path_identity(value: str) -> str:
|
||||||
|
"""Return a stable identity for native workspace path aliases."""
|
||||||
|
|
||||||
|
path = _clean_path(value).replace("\\", "/")
|
||||||
|
for prefix in ("/tmp_workspace/", "/workspace/"):
|
||||||
|
if path.startswith(prefix):
|
||||||
|
return path[len(prefix):]
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def _known_input_is_explicit_mutation_target(instruction: str, path: str) -> bool:
|
||||||
|
"""Preserve a known input only when the user explicitly asks to edit it.
|
||||||
|
|
||||||
|
Input descriptions commonly say that a file is "saved in" or is a
|
||||||
|
"post-write checklist". Those phrases must not turn read-only evidence
|
||||||
|
into a required output artifact. Direct edit/update requests remain
|
||||||
|
supported.
|
||||||
|
"""
|
||||||
|
|
||||||
|
escaped = re.escape(_clean_path(path))
|
||||||
|
active_edit = rf"(?<![-\w])(?:edit|modify|update|fix)\s+(?:the\s+)?[`'\"]?{escaped}"
|
||||||
|
direct_create = (
|
||||||
|
rf"(?<![-\w])(?:write|create|make|save|produce|generate|export|put|place)"
|
||||||
|
rf"\s+[`'\"]?{escaped}"
|
||||||
|
)
|
||||||
|
directed_create = (
|
||||||
|
rf"(?<![-\w])(?:write|create|make|save|produce|generate|export|put|place)"
|
||||||
|
rf"\b[^\n]{{0,80}}?\b(?:to|into|at|as|under|inside)\s+"
|
||||||
|
rf"(?:the\s+|a\s+)?[`'\"]?{escaped}"
|
||||||
|
)
|
||||||
|
return any(
|
||||||
|
re.search(pattern, instruction, re.IGNORECASE)
|
||||||
|
for pattern in (active_edit, direct_create, directed_create)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def infer_completion_requirements(
|
def infer_completion_requirements(
|
||||||
instruction: str,
|
instruction: str,
|
||||||
*,
|
*,
|
||||||
executable_verifier_available: bool = False,
|
executable_verifier_available: bool = False,
|
||||||
verifier_commands: Sequence[str] = (),
|
verifier_commands: Sequence[str] = (),
|
||||||
|
known_input_paths: Sequence[str] = (),
|
||||||
) -> CompletionRequirements:
|
) -> CompletionRequirements:
|
||||||
"""Infer only explicitly requested output/edit paths from an instruction."""
|
"""Infer only explicitly requested output/edit paths from an instruction."""
|
||||||
|
|
||||||
@@ -270,6 +307,18 @@ def infer_completion_requirements(
|
|||||||
paths.append(path)
|
paths.append(path)
|
||||||
paths = [path.rstrip("/") if path != "/" else path for path in paths]
|
paths = [path.rstrip("/") if path != "/" else path for path in paths]
|
||||||
paths = list(dict.fromkeys(paths))
|
paths = list(dict.fromkeys(paths))
|
||||||
|
input_identities = {
|
||||||
|
_workspace_path_identity(path)
|
||||||
|
for path in known_input_paths
|
||||||
|
if _clean_path(path)
|
||||||
|
}
|
||||||
|
if input_identities:
|
||||||
|
paths = [
|
||||||
|
path
|
||||||
|
for path in paths
|
||||||
|
if _workspace_path_identity(path) not in input_identities
|
||||||
|
or _known_input_is_explicit_mutation_target(text, path)
|
||||||
|
]
|
||||||
# When the instruction names an absolute output directory and then gives
|
# When the instruction names an absolute output directory and then gives
|
||||||
# relative example filenames (for example ``1.tex, 2.tex, ...``), the
|
# relative example filenames (for example ``1.tex, 2.tex, ...``), the
|
||||||
# directory is the actual completion contract. Treating the first example
|
# directory is the actual completion contract. Treating the first example
|
||||||
@@ -321,9 +370,27 @@ def requirements_from_runtime_context(
|
|||||||
*,
|
*,
|
||||||
instruction: str = "",
|
instruction: str = "",
|
||||||
) -> CompletionRequirements:
|
) -> CompletionRequirements:
|
||||||
raw = (context or {}).get("completion_requirements")
|
runtime_context = context or {}
|
||||||
|
known_inputs: list[str] = []
|
||||||
|
for value in runtime_context.get("input_files") or ():
|
||||||
|
path = _clean_path(str(value or ""))
|
||||||
|
if path:
|
||||||
|
known_inputs.append(path)
|
||||||
|
media_ingress = runtime_context.get("media_ingress")
|
||||||
|
if isinstance(media_ingress, Mapping):
|
||||||
|
for artifact in media_ingress.get("artifacts") or ():
|
||||||
|
if not isinstance(artifact, Mapping):
|
||||||
|
continue
|
||||||
|
path = _clean_path(str(artifact.get("source_path") or ""))
|
||||||
|
if path:
|
||||||
|
known_inputs.append(path)
|
||||||
|
|
||||||
|
raw = runtime_context.get("completion_requirements")
|
||||||
if not isinstance(raw, Mapping):
|
if not isinstance(raw, Mapping):
|
||||||
return infer_completion_requirements(instruction)
|
return infer_completion_requirements(
|
||||||
|
instruction,
|
||||||
|
known_input_paths=known_inputs,
|
||||||
|
)
|
||||||
paths = raw.get("required_artifacts")
|
paths = raw.get("required_artifacts")
|
||||||
if not isinstance(paths, (list, tuple)):
|
if not isinstance(paths, (list, tuple)):
|
||||||
paths = ()
|
paths = ()
|
||||||
@@ -332,6 +399,16 @@ def requirements_from_runtime_context(
|
|||||||
for value in paths
|
for value in paths
|
||||||
if (path := _clean_path(str(value or "")))
|
if (path := _clean_path(str(value or "")))
|
||||||
)
|
)
|
||||||
|
input_identities = {
|
||||||
|
_workspace_path_identity(path) for path in known_inputs
|
||||||
|
}
|
||||||
|
if input_identities:
|
||||||
|
cleaned = tuple(
|
||||||
|
path
|
||||||
|
for path in cleaned
|
||||||
|
if _workspace_path_identity(path) not in input_identities
|
||||||
|
or _known_input_is_explicit_mutation_target(instruction, path)
|
||||||
|
)
|
||||||
verifier_commands = raw.get("verifier_commands")
|
verifier_commands = raw.get("verifier_commands")
|
||||||
if not isinstance(verifier_commands, (list, tuple)):
|
if not isinstance(verifier_commands, (list, tuple)):
|
||||||
verifier_commands = ()
|
verifier_commands = ()
|
||||||
|
|||||||
@@ -19,6 +19,36 @@ def test_infers_only_explicit_output_or_edit_paths():
|
|||||||
assert requirements.verifier_required is True
|
assert requirements.verifier_required is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_known_workspace_inputs_are_not_inferred_as_output_artifacts():
|
||||||
|
requirements = infer_completion_requirements(
|
||||||
|
"My clues are saved in /tmp_workspace/inputs/clues.md, the required answer "
|
||||||
|
"template is /tmp_workspace/inputs/template.md, and a post-write verification "
|
||||||
|
"list is /tmp_workspace/inputs/validation_checklist.txt. Read all three first. "
|
||||||
|
"Fill in /tmp_workspace/inputs/template.md exactly and save the completed "
|
||||||
|
"document to /tmp_workspace/results/results.md.",
|
||||||
|
known_input_paths=(
|
||||||
|
"/workspace/inputs/clues.md",
|
||||||
|
"/workspace/inputs/template.md",
|
||||||
|
"/workspace/inputs/validation_checklist.txt",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert requirements.required_artifacts == (
|
||||||
|
"/tmp_workspace/results/results.md",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_known_workspace_input_remains_required_when_explicitly_updated():
|
||||||
|
requirements = infer_completion_requirements(
|
||||||
|
"Update /tmp_workspace/inputs/config.json and then verify it.",
|
||||||
|
known_input_paths=("/workspace/inputs/config.json",),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert requirements.required_artifacts == (
|
||||||
|
"/tmp_workspace/inputs/config.json",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_infers_artifact_from_common_past_participle_request():
|
def test_infers_artifact_from_common_past_participle_request():
|
||||||
requirements = infer_completion_requirements(
|
requirements = infer_completion_requirements(
|
||||||
"Build a digest saved to /workspace/results/ops_digest.md."
|
"Build a digest saved to /workspace/results/ops_digest.md."
|
||||||
@@ -125,6 +155,36 @@ def test_runtime_requirements_override_instruction_inference():
|
|||||||
assert requirements.verifier_commands == ("./test.sh",)
|
assert requirements.verifier_commands == ("./test.sh",)
|
||||||
|
|
||||||
|
|
||||||
|
def test_runtime_requirements_drop_media_inputs_misclassified_as_outputs():
|
||||||
|
requirements = requirements_from_runtime_context(
|
||||||
|
{
|
||||||
|
"media_ingress": {
|
||||||
|
"artifacts": [
|
||||||
|
{"source_path": "/workspace/inputs/clues.md"},
|
||||||
|
{"source_path": "/workspace/inputs/template.md"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"completion_requirements": {
|
||||||
|
"required_artifacts": [
|
||||||
|
"/tmp_workspace/inputs/clues.md",
|
||||||
|
"/tmp_workspace/inputs/template.md",
|
||||||
|
"/tmp_workspace/results/results.md",
|
||||||
|
],
|
||||||
|
"verifier_required": True,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
instruction=(
|
||||||
|
"Clues are saved in /tmp_workspace/inputs/clues.md and the template is "
|
||||||
|
"/tmp_workspace/inputs/template.md. Save the completed document to "
|
||||||
|
"/tmp_workspace/results/results.md."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert requirements.required_artifacts == (
|
||||||
|
"/tmp_workspace/results/results.md",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_available_verifier_is_required_and_declared_command_is_evidence():
|
def test_available_verifier_is_required_and_declared_command_is_evidence():
|
||||||
requirements = infer_completion_requirements(
|
requirements = infer_completion_requirements(
|
||||||
"Update app.py",
|
"Update app.py",
|
||||||
|
|||||||
@@ -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.8"
|
assert version == "0.20.9"
|
||||||
assert re.fullmatch(r"\d+\.\d+\.\d+", version)
|
assert re.fullmatch(r"\d+\.\d+\.\d+", version)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user