mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-06 06:52:20 +02: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))
|
||||
|
||||
|
||||
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(
|
||||
instruction: str,
|
||||
*,
|
||||
executable_verifier_available: bool = False,
|
||||
verifier_commands: Sequence[str] = (),
|
||||
known_input_paths: Sequence[str] = (),
|
||||
) -> CompletionRequirements:
|
||||
"""Infer only explicitly requested output/edit paths from an instruction."""
|
||||
|
||||
@@ -270,6 +307,18 @@ def infer_completion_requirements(
|
||||
paths.append(path)
|
||||
paths = [path.rstrip("/") if path != "/" else path for path in 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
|
||||
# relative example filenames (for example ``1.tex, 2.tex, ...``), the
|
||||
# directory is the actual completion contract. Treating the first example
|
||||
@@ -321,9 +370,27 @@ def requirements_from_runtime_context(
|
||||
*,
|
||||
instruction: str = "",
|
||||
) -> 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):
|
||||
return infer_completion_requirements(instruction)
|
||||
return infer_completion_requirements(
|
||||
instruction,
|
||||
known_input_paths=known_inputs,
|
||||
)
|
||||
paths = raw.get("required_artifacts")
|
||||
if not isinstance(paths, (list, tuple)):
|
||||
paths = ()
|
||||
@@ -332,6 +399,16 @@ def requirements_from_runtime_context(
|
||||
for value in paths
|
||||
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")
|
||||
if not isinstance(verifier_commands, (list, tuple)):
|
||||
verifier_commands = ()
|
||||
|
||||
@@ -19,6 +19,36 @@ def test_infers_only_explicit_output_or_edit_paths():
|
||||
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():
|
||||
requirements = infer_completion_requirements(
|
||||
"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",)
|
||||
|
||||
|
||||
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():
|
||||
requirements = infer_completion_requirements(
|
||||
"Update app.py",
|
||||
|
||||
@@ -8,7 +8,7 @@ ROOT = Path(__file__).resolve().parents[1]
|
||||
def test_original_harness_version_is_canonical_semver() -> None:
|
||||
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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user