Replace LLM cleanup with deterministic profiling
This commit is contained in:
@@ -1,216 +0,0 @@
|
||||
"""Tests for the two-call boundary and prefix-quality policy."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from swe_data_processing.audit import (
|
||||
compute_prefix_quality,
|
||||
derive_prefix_safety,
|
||||
effective_boundary_policy,
|
||||
materialize_prefix,
|
||||
validate_boundary,
|
||||
validate_prefix_quality,
|
||||
)
|
||||
from swe_data_processing.policy import PolicyViolation
|
||||
from swe_data_processing.workflow import (
|
||||
prepare_boundary_payload,
|
||||
prepare_prefix_quality_payload,
|
||||
)
|
||||
|
||||
|
||||
def _record() -> dict:
|
||||
return {
|
||||
"trajectory_id": "sample-1",
|
||||
"resolved": 0,
|
||||
"tools": [],
|
||||
"trajectory": [
|
||||
{"role": "system", "content": "system"},
|
||||
{"role": "user", "content": "Fix the bug."},
|
||||
{"role": "assistant", "content": "I will inspect the code."},
|
||||
{"role": "tool", "content": "relevant.py"},
|
||||
{"role": "assistant", "content": "I will apply the harmful patch."},
|
||||
{"role": "tool", "content": "tests failed"},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _boundary() -> dict:
|
||||
return {
|
||||
"sample_id": "sample-1",
|
||||
"checks": {
|
||||
"task_coverage": "COMPLETE",
|
||||
"final_patch_scope": "CLEAN",
|
||||
"constraints": "RESPECTED",
|
||||
"claims_vs_observations": "CONSISTENT",
|
||||
},
|
||||
"decision": "TRUNCATE",
|
||||
"truncate_before_turn": 5,
|
||||
"category": "PERSISTENT_WRONG_IMPLEMENTATION",
|
||||
"severity": "MAJOR",
|
||||
"state_effect": "UNRECOVERED",
|
||||
"evidence_turns": [5],
|
||||
"reason": "The patch fails and is not repaired.",
|
||||
}
|
||||
|
||||
|
||||
def _quality() -> dict:
|
||||
return {
|
||||
"sample_id": "sample-1",
|
||||
"behavior_issues": [],
|
||||
"dimensions": {
|
||||
"planning": 14,
|
||||
"investigation": 15,
|
||||
"tool_use_and_observation": 14,
|
||||
"progress": 12,
|
||||
"clarity_and_efficiency": 10,
|
||||
},
|
||||
"evidence_turns": [],
|
||||
"reason": "Useful investigation.",
|
||||
}
|
||||
|
||||
|
||||
def test_valid_boundary_materializes_exact_prefix() -> None:
|
||||
validate_boundary(_record(), _boundary())
|
||||
prefix = materialize_prefix(_record(), 5)
|
||||
assert prefix == _record()["trajectory"][:4]
|
||||
|
||||
|
||||
def test_boundary_must_be_an_assistant_turn() -> None:
|
||||
result = _boundary()
|
||||
result["truncate_before_turn"] = 4
|
||||
with pytest.raises(PolicyViolation, match="assistant turn"):
|
||||
validate_boundary(_record(), result)
|
||||
|
||||
|
||||
def test_boundary_evidence_must_include_boundary_turn() -> None:
|
||||
result = _boundary()
|
||||
result["evidence_turns"] = [6]
|
||||
with pytest.raises(PolicyViolation, match="excluded assistant turn"):
|
||||
validate_boundary(_record(), result)
|
||||
|
||||
|
||||
def test_keep_full_cannot_contain_a_boundary() -> None:
|
||||
result = _boundary()
|
||||
result.update(
|
||||
decision="KEEP_FULL",
|
||||
truncate_before_turn=None,
|
||||
category="NONE",
|
||||
severity="NONE",
|
||||
state_effect="NONE",
|
||||
evidence_turns=[],
|
||||
)
|
||||
validate_boundary(_record(), result)
|
||||
result["truncate_before_turn"] = 5
|
||||
with pytest.raises(PolicyViolation, match="cannot contain a boundary"):
|
||||
validate_boundary(_record(), result)
|
||||
|
||||
|
||||
def test_prefix_quality_cannot_reference_suffix() -> None:
|
||||
prefix = materialize_prefix(_record(), 5)
|
||||
result = _quality()
|
||||
result["evidence_turns"] = [5]
|
||||
with pytest.raises(PolicyViolation, match="outside the prefix"):
|
||||
validate_prefix_quality("sample-1", prefix, result)
|
||||
|
||||
|
||||
def test_quality_score_and_tier_are_computed_locally() -> None:
|
||||
score = compute_prefix_quality(_quality())
|
||||
assert score["educational_quality_score"] == 65
|
||||
assert score["quality_tier"] == "MEDIUM"
|
||||
|
||||
|
||||
def test_unrecovered_major_issue_is_locally_invalid() -> None:
|
||||
result = _quality()
|
||||
result["behavior_issues"] = [
|
||||
{
|
||||
"turn_id": 3,
|
||||
"kind": "ERROR",
|
||||
"severity": "MAJOR",
|
||||
"recovered": False,
|
||||
"reason": "The prefix leaves a known broken edit.",
|
||||
}
|
||||
]
|
||||
result["evidence_turns"] = [3]
|
||||
validate_prefix_quality("sample-1", materialize_prefix(_record(), 5), result)
|
||||
assert derive_prefix_safety(result)["prefix_valid"] is False
|
||||
assert compute_prefix_quality(result)["quality_tier"] == "REJECT"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("resolved", [0, -1])
|
||||
def test_non_success_outcome_is_not_capped_at_first_stateful_turn(resolved: int) -> None:
|
||||
record = _record()
|
||||
record["resolved"] = resolved
|
||||
record["tools"] = [
|
||||
{"type": "function", "function": {"name": "str_replace_editor"}}
|
||||
]
|
||||
record["trajectory"][2]["tool_calls"] = [
|
||||
{
|
||||
"id": "call-1",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "str_replace_editor",
|
||||
"arguments": '{"command":"str_replace","path":"src/a.py"}',
|
||||
},
|
||||
}
|
||||
]
|
||||
result = _boundary()
|
||||
result.update(decision="KEEP_FULL", truncate_before_turn=None)
|
||||
policy = effective_boundary_policy(record, result)
|
||||
assert policy["decision"] == "KEEP_FULL"
|
||||
assert policy["truncate_before_turn"] is None
|
||||
assert policy["source"] == "MODEL_DECISION"
|
||||
assert policy["first_stateful_turn"] == 3
|
||||
|
||||
|
||||
def test_semantic_boundary_is_not_replaced_by_earlier_stateful_turn() -> None:
|
||||
record = _record()
|
||||
record["resolved"] = 0
|
||||
record["tools"] = [
|
||||
{"type": "function", "function": {"name": "str_replace_editor"}}
|
||||
]
|
||||
record["trajectory"][2]["tool_calls"] = [
|
||||
{
|
||||
"id": "call-1",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "str_replace_editor",
|
||||
"arguments": '{"command":"str_replace","path":"src/a.py"}',
|
||||
},
|
||||
}
|
||||
]
|
||||
result = _boundary()
|
||||
policy = effective_boundary_policy(record, result)
|
||||
assert policy["decision"] == "TRUNCATE"
|
||||
assert policy["truncate_before_turn"] == 5
|
||||
assert policy["source"] == "MODEL_BOUNDARY"
|
||||
assert policy["first_stateful_turn"] == 3
|
||||
|
||||
|
||||
def test_boundary_payload_hides_outcome_and_patch_metadata() -> None:
|
||||
record = _record()
|
||||
record["resolved"] = -1
|
||||
record["metadata"] = {
|
||||
"model_patch": {"patch": "MODEL_PATCH_SECRET"},
|
||||
"reference_patch": {"patch": "REFERENCE_PATCH_SECRET"},
|
||||
}
|
||||
serialized = json.dumps(prepare_boundary_payload(record), ensure_ascii=False)
|
||||
assert "resolved" not in serialized
|
||||
assert "MODEL_PATCH_SECRET" in serialized
|
||||
assert "REFERENCE_PATCH_SECRET" not in serialized
|
||||
|
||||
|
||||
def test_prefix_payload_is_identical_when_only_suffix_and_labels_change() -> None:
|
||||
first = _record()
|
||||
second = _record()
|
||||
second["resolved"] = -1
|
||||
second["trajectory"][4]["content"] = "DIFFERENT SUFFIX"
|
||||
second["trajectory"][5]["content"] = "DIFFERENT TOOL RESULT"
|
||||
second["metadata"] = {"model_patch": {"patch": "DIFFERENT PATCH"}}
|
||||
first_prefix = materialize_prefix(first, 5)
|
||||
second_prefix = materialize_prefix(second, 5)
|
||||
assert prepare_prefix_quality_payload(
|
||||
first, first_prefix
|
||||
) == prepare_prefix_quality_payload(second, second_prefix)
|
||||
@@ -1,165 +0,0 @@
|
||||
"""End-to-end tests for call isolation in the audit workflow."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from swe_data_processing.client import GLMResponse
|
||||
from swe_data_processing.config import Settings
|
||||
from swe_data_processing.workflow import audit_trajectory
|
||||
|
||||
|
||||
class FakeClient:
|
||||
"""Return deterministic responses while recording every API payload."""
|
||||
|
||||
def __init__(self, responses: list[dict]) -> None:
|
||||
self.settings = Settings(api_key="test-secret")
|
||||
self.responses = iter(responses)
|
||||
self.calls: list[dict] = []
|
||||
|
||||
def invoke_json(self, *, system_prompt: str, payload: dict, schema: dict) -> GLMResponse:
|
||||
self.calls.append(
|
||||
{"system_prompt": system_prompt, "payload": payload, "schema": schema}
|
||||
)
|
||||
return GLMResponse(
|
||||
data=next(self.responses),
|
||||
request_id=f"request-{len(self.calls)}",
|
||||
usage={"total_tokens": 10},
|
||||
compatibility_fallback_used=False,
|
||||
)
|
||||
|
||||
|
||||
def _record() -> dict:
|
||||
return {
|
||||
"trajectory_id": "sample-1",
|
||||
"resolved": 0,
|
||||
"trajectory": [
|
||||
{"role": "system", "content": "system"},
|
||||
{"role": "user", "content": "Fix the bug."},
|
||||
{"role": "assistant", "content": "Inspect relevant.py"},
|
||||
{"role": "tool", "content": "relevant code"},
|
||||
{"role": "assistant", "content": "BAD_SUFFIX_SENTINEL patch"},
|
||||
{"role": "tool", "content": "BAD_SUFFIX_SENTINEL failed"},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def test_workflow_calls_boundary_then_scores_only_materialized_prefix() -> None:
|
||||
client = FakeClient(
|
||||
[
|
||||
{
|
||||
"sample_id": "sample-1",
|
||||
"checks": {
|
||||
"task_coverage": "INCOMPLETE",
|
||||
"final_patch_scope": "POLLUTED",
|
||||
"constraints": "RESPECTED",
|
||||
"claims_vs_observations": "CONTRADICTED",
|
||||
},
|
||||
"decision": "TRUNCATE",
|
||||
"truncate_before_turn": 5,
|
||||
"category": "PERSISTENT_WRONG_IMPLEMENTATION",
|
||||
"severity": "MAJOR",
|
||||
"state_effect": "UNRECOVERED",
|
||||
"evidence_turns": [5],
|
||||
"reason": "The patch is not repaired.",
|
||||
},
|
||||
{
|
||||
"sample_id": "sample-1",
|
||||
"behavior_issues": [],
|
||||
"dimensions": {
|
||||
"planning": 15,
|
||||
"investigation": 15,
|
||||
"tool_use_and_observation": 14,
|
||||
"progress": 12,
|
||||
"clarity_and_efficiency": 10,
|
||||
},
|
||||
"evidence_turns": [],
|
||||
"reason": "The prefix is useful.",
|
||||
},
|
||||
]
|
||||
)
|
||||
result = audit_trajectory(_record(), client) # type: ignore[arg-type]
|
||||
|
||||
assert len(client.calls) == 2
|
||||
quality_payload = json.dumps(client.calls[1]["payload"], ensure_ascii=False)
|
||||
assert "BAD_SUFFIX_SENTINEL" not in quality_payload
|
||||
assert "truncate_before_turn" not in quality_payload
|
||||
assert result["prefix"]["retained_turn_count"] == 4
|
||||
assert result["quality"]["local_score"]["quality_tier"] == "MEDIUM"
|
||||
assert result["recommended_use"] == "PROCESS_PREFIX_CANDIDATE"
|
||||
|
||||
|
||||
def test_hold_boundary_skips_quality_call() -> None:
|
||||
client = FakeClient(
|
||||
[
|
||||
{
|
||||
"sample_id": "sample-1",
|
||||
"checks": {
|
||||
"task_coverage": "COMPLETE",
|
||||
"final_patch_scope": "CLEAN",
|
||||
"constraints": "RESPECTED",
|
||||
"claims_vs_observations": "CONSISTENT",
|
||||
},
|
||||
"decision": "HOLD",
|
||||
"truncate_before_turn": None,
|
||||
"category": "NONE",
|
||||
"severity": "NONE",
|
||||
"state_effect": "UNCLEAR",
|
||||
"evidence_turns": [],
|
||||
"reason": "No clear unrepaired defect.",
|
||||
}
|
||||
]
|
||||
)
|
||||
result = audit_trajectory(_record(), client) # type: ignore[arg-type]
|
||||
assert len(client.calls) == 1
|
||||
assert result["recommended_use"] == "HOLD"
|
||||
assert result["quality"] is None
|
||||
|
||||
|
||||
def test_keep_full_process_trajectory_is_scored() -> None:
|
||||
client = FakeClient(
|
||||
[
|
||||
{
|
||||
"sample_id": "sample-1",
|
||||
"checks": {
|
||||
"task_coverage": "COMPLETE",
|
||||
"final_patch_scope": "CLEAN",
|
||||
"constraints": "RESPECTED",
|
||||
"claims_vs_observations": "CONSISTENT",
|
||||
},
|
||||
"decision": "KEEP_FULL",
|
||||
"truncate_before_turn": None,
|
||||
"category": "NONE",
|
||||
"severity": "NONE",
|
||||
"state_effect": "NONE",
|
||||
"evidence_turns": [],
|
||||
"reason": "No unrepaired severe problem is visible.",
|
||||
},
|
||||
{
|
||||
"sample_id": "sample-1",
|
||||
"behavior_issues": [
|
||||
{
|
||||
"turn_id": 3,
|
||||
"kind": "INEFFICIENCY",
|
||||
"severity": "MINOR",
|
||||
"recovered": True,
|
||||
"reason": "The inspection was somewhat broad.",
|
||||
}
|
||||
],
|
||||
"dimensions": {
|
||||
"planning": 15,
|
||||
"investigation": 15,
|
||||
"tool_use_and_observation": 14,
|
||||
"progress": 12,
|
||||
"clarity_and_efficiency": 10,
|
||||
},
|
||||
"evidence_turns": [],
|
||||
"reason": "Useful despite minor inefficiency.",
|
||||
},
|
||||
]
|
||||
)
|
||||
result = audit_trajectory(_record(), client) # type: ignore[arg-type]
|
||||
assert len(client.calls) == 2
|
||||
assert result["prefix"]["retained_turn_count"] == 6
|
||||
assert result["quality"]["local_score"]["issue_counts"]["inefficiencies"] == 1
|
||||
assert result["recommended_use"] == "HOLD"
|
||||
+17
-33
@@ -1,54 +1,38 @@
|
||||
"""Tests for the streaming CLI runner."""
|
||||
"""Tests for CLI parsing and streaming profile execution."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from swe_data_processing.cli import _run_streaming_stage
|
||||
from swe_data_processing.cli import _run_streaming_stage, build_parser
|
||||
|
||||
|
||||
def test_streaming_stage_processes_records_concurrently(tmp_path: Path) -> None:
|
||||
"""Multiple workers run processors in parallel while one thread writes JSONL."""
|
||||
|
||||
def test_streaming_stage_writes_each_record(tmp_path: Path) -> None:
|
||||
input_path = tmp_path / "input.jsonl"
|
||||
output_path = tmp_path / "output.jsonl"
|
||||
input_path.write_text(
|
||||
"".join(
|
||||
json.dumps({"trajectory_id": f"sample-{index}"}) + "\n" for index in range(20)
|
||||
),
|
||||
"".join(json.dumps({"trajectory_id": f"s-{i}"}) + "\n" for i in range(10)),
|
||||
encoding="utf-8",
|
||||
)
|
||||
lock = threading.Lock()
|
||||
active = 0
|
||||
peak_active = 0
|
||||
|
||||
def processor(record: dict) -> dict:
|
||||
nonlocal active, peak_active
|
||||
with lock:
|
||||
active += 1
|
||||
peak_active = max(peak_active, active)
|
||||
time.sleep(0.01)
|
||||
with lock:
|
||||
active -= 1
|
||||
return {"sample_id": record["trajectory_id"]}
|
||||
|
||||
status = _run_streaming_stage(
|
||||
input_path=input_path,
|
||||
output_path=output_path,
|
||||
errors_path=None,
|
||||
limit=None,
|
||||
resume=False,
|
||||
workers=4,
|
||||
stage_name="test",
|
||||
processor=processor,
|
||||
workers=3,
|
||||
processor=lambda record: {"sample_id": record["trajectory_id"]},
|
||||
)
|
||||
|
||||
written = [json.loads(line) for line in output_path.read_text().splitlines()]
|
||||
assert status == 0
|
||||
assert peak_active == 4
|
||||
assert {value["sample_id"] for value in written} == {
|
||||
f"sample-{index}" for index in range(20)
|
||||
}
|
||||
assert len(output_path.read_text().splitlines()) == 10
|
||||
|
||||
|
||||
def test_cli_has_only_deterministic_pipeline_commands() -> None:
|
||||
parser = build_parser()
|
||||
assert parser.parse_args(
|
||||
["profile", "--input", "in.jsonl", "--output", "out.jsonl"]
|
||||
).command == "profile"
|
||||
help_text = parser.format_help()
|
||||
assert "classify" not in help_text
|
||||
assert "repair" not in help_text
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
"""Tests for API request compatibility and structured-response validation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
from swe_data_processing.client import GLMClient
|
||||
from swe_data_processing.config import Settings
|
||||
|
||||
|
||||
def test_client_parses_fenced_json_and_never_sends_key_in_body() -> None:
|
||||
captured_body = {}
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
captured_body.update(json.loads(request.content))
|
||||
assert request.headers["authorization"] == "Bearer test-secret"
|
||||
return httpx.Response(
|
||||
200,
|
||||
headers={"x-request-id": "request-123"},
|
||||
json={
|
||||
"choices": [
|
||||
{"message": {"role": "assistant", "content": "```json\n{\"status\":\"ok\"}\n```"}}
|
||||
],
|
||||
"usage": {"total_tokens": 10},
|
||||
},
|
||||
)
|
||||
|
||||
schema = {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"required": ["status"],
|
||||
"properties": {"status": {"const": "ok"}},
|
||||
}
|
||||
settings = Settings(api_key="test-secret", max_retries=0)
|
||||
with GLMClient(settings, transport=httpx.MockTransport(handler)) as client:
|
||||
response = client.invoke_json(system_prompt="Return JSON.", payload={"ping": True}, schema=schema)
|
||||
|
||||
assert response.data == {"status": "ok"}
|
||||
assert response.request_id == "request-123"
|
||||
assert response.usage == {"total_tokens": 10}
|
||||
assert "test-secret" not in json.dumps(captured_body)
|
||||
assert captured_body["model"] == "glm-5.2"
|
||||
|
||||
|
||||
def test_client_falls_back_when_vendor_extensions_are_rejected() -> None:
|
||||
calls = []
|
||||
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
body = json.loads(request.content)
|
||||
calls.append(body)
|
||||
if len(calls) == 1:
|
||||
return httpx.Response(400, json={"error": {"message": "unknown parameter: thinking"}})
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={"choices": [{"message": {"content": "{\"status\":\"ok\"}"}}]},
|
||||
)
|
||||
|
||||
schema = {
|
||||
"type": "object",
|
||||
"required": ["status"],
|
||||
"properties": {"status": {"const": "ok"}},
|
||||
}
|
||||
settings = Settings(api_key="test-secret", max_retries=1)
|
||||
with GLMClient(settings, transport=httpx.MockTransport(handler)) as client:
|
||||
response = client.invoke_json(system_prompt="Return JSON.", payload={}, schema=schema)
|
||||
|
||||
assert "thinking" in calls[0]
|
||||
assert "thinking" not in calls[1]
|
||||
assert response.compatibility_fallback_used is True
|
||||
|
||||
|
||||
def test_client_retries_five_failures_before_succeeding(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Five retryable failures are followed by a sixth and final attempt."""
|
||||
|
||||
calls = 0
|
||||
|
||||
def handler(_: httpx.Request) -> httpx.Response:
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
if calls <= 5:
|
||||
return httpx.Response(503, json={"error": {"message": "temporarily unavailable"}})
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={"choices": [{"message": {"content": "{\"status\":\"ok\"}"}}]},
|
||||
)
|
||||
|
||||
monkeypatch.setattr("swe_data_processing.client.time.sleep", lambda _: None)
|
||||
schema = {
|
||||
"type": "object",
|
||||
"required": ["status"],
|
||||
"properties": {"status": {"const": "ok"}},
|
||||
}
|
||||
settings = Settings(api_key="test-secret", max_retries=5)
|
||||
with GLMClient(settings, transport=httpx.MockTransport(handler)) as client:
|
||||
response = client.invoke_json(system_prompt="Return JSON.", payload={}, schema=schema)
|
||||
|
||||
assert calls == 6
|
||||
assert response.data == {"status": "ok"}
|
||||
@@ -1,29 +0,0 @@
|
||||
"""Tests for strict environment configuration and endpoint normalization."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from swe_data_processing.config import Settings
|
||||
|
||||
|
||||
def test_endpoint_normalization() -> None:
|
||||
settings = Settings(
|
||||
api_key="test-secret",
|
||||
api_base="https://llm-api.cowin.run/",
|
||||
api_path="v1/chat/completions",
|
||||
)
|
||||
assert settings.endpoint == "https://llm-api.cowin.run/v1/chat/completions"
|
||||
|
||||
|
||||
def test_missing_api_key_is_rejected(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.delenv("GLM_API_KEY", raising=False)
|
||||
with pytest.raises(ValueError, match="GLM_API_KEY"):
|
||||
Settings.from_env()
|
||||
|
||||
|
||||
def test_feature_only_settings_can_omit_key(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.delenv("GLM_API_KEY", raising=False)
|
||||
settings = Settings.from_env(require_api_key=False)
|
||||
assert settings.api_key == ""
|
||||
assert settings.max_retries == 5
|
||||
@@ -1,73 +0,0 @@
|
||||
"""Tests for prompt-only trajectory compaction."""
|
||||
|
||||
from swe_data_processing.evidence import (
|
||||
BOUNDARY_BLOCK_TURNS,
|
||||
build_trajectory_blocks,
|
||||
compact_text,
|
||||
compact_trajectory,
|
||||
)
|
||||
|
||||
|
||||
def test_compact_text_preserves_short_values() -> None:
|
||||
"""Short evidence must remain byte-for-byte identical."""
|
||||
|
||||
assert compact_text("short evidence", 100) == "short evidence"
|
||||
|
||||
|
||||
def test_compact_text_hashes_long_values() -> None:
|
||||
"""Long evidence must expose its size and digest for auditability."""
|
||||
|
||||
result = compact_text("a" * 1_000, 200)
|
||||
assert len(result) == 200
|
||||
assert "COMPACTED original_chars=1000 sha256=" in result
|
||||
|
||||
|
||||
def test_compact_trajectory_prioritizes_test_observations() -> None:
|
||||
"""Important test output receives a larger preview than ordinary output."""
|
||||
|
||||
long_content = "x" * 3_000
|
||||
trajectory = [
|
||||
{"turn_id": 1, "role": "user", "content": "issue"},
|
||||
{"turn_id": 2, "role": "assistant", "content": "", "tool_calls": []},
|
||||
{"turn_id": 3, "role": "tool", "content": long_content},
|
||||
{"turn_id": 4, "role": "assistant", "content": "done"},
|
||||
]
|
||||
signals = {
|
||||
"stateful_turns": [],
|
||||
"test_events": [{"command_turn": 2, "output_turn": 3}],
|
||||
"malformed_tool_turns": [],
|
||||
"unknown_tool_turns": [],
|
||||
}
|
||||
compacted, metadata = compact_trajectory(trajectory, signals)
|
||||
assert compacted[2]["content"] == long_content
|
||||
assert 3 in metadata["important_turn_ids"]
|
||||
|
||||
|
||||
def test_boundary_blocks_preserve_absolute_turns_without_overlap() -> None:
|
||||
trajectory = [
|
||||
{"turn_id": turn, "role": "assistant", "content": f"turn {turn}"}
|
||||
for turn in range(1, BOUNDARY_BLOCK_TURNS + 3)
|
||||
]
|
||||
blocks, metadata = build_trajectory_blocks(trajectory, {})
|
||||
assert len(blocks) == 2
|
||||
assert blocks[0]["start_turn"] == 1
|
||||
assert blocks[0]["end_turn"] == BOUNDARY_BLOCK_TURNS
|
||||
assert blocks[1]["start_turn"] == BOUNDARY_BLOCK_TURNS + 1
|
||||
assert blocks[1]["end_turn"] == BOUNDARY_BLOCK_TURNS + 2
|
||||
turn_ids = [
|
||||
message["turn_id"] for block in blocks for message in block["messages"]
|
||||
]
|
||||
assert turn_ids == list(range(1, BOUNDARY_BLOCK_TURNS + 3))
|
||||
assert metadata["block_count"] == 2
|
||||
|
||||
|
||||
def test_boundary_block_keeps_immediate_tool_result_with_assistant() -> None:
|
||||
trajectory = [
|
||||
{"turn_id": turn, "role": "user", "content": f"turn {turn}"}
|
||||
for turn in range(1, BOUNDARY_BLOCK_TURNS + 2)
|
||||
]
|
||||
trajectory[BOUNDARY_BLOCK_TURNS - 1]["role"] = "assistant"
|
||||
trajectory[BOUNDARY_BLOCK_TURNS]["role"] = "tool"
|
||||
blocks, _ = build_trajectory_blocks(trajectory, {})
|
||||
assert blocks[0]["end_turn"] == BOUNDARY_BLOCK_TURNS + 1
|
||||
assert len(blocks) == 1
|
||||
@@ -1,100 +0,0 @@
|
||||
"""Tests for deterministic static trajectory evidence extraction."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from swe_data_processing.features import extract_patch_files, extract_static_signals
|
||||
|
||||
|
||||
def _tool(name: str) -> str:
|
||||
return json.dumps({"type": "function", "function": {"name": name, "parameters": {}}})
|
||||
|
||||
|
||||
def test_detects_masked_failing_test_after_edit() -> None:
|
||||
record = {
|
||||
"trajectory_id": "sample-1",
|
||||
"resolved": 0,
|
||||
"tools": [_tool("str_replace_editor"), _tool("execute_bash"), _tool("finish")],
|
||||
"trajectory": [
|
||||
{"role": "system", "content": "system"},
|
||||
{"role": "user", "content": "Do not modify test files."},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "edit-1",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "str_replace_editor",
|
||||
"arguments": json.dumps(
|
||||
{"command": "str_replace", "path": "/workspace/src/main.py"}
|
||||
),
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
{"role": "tool", "content": "The file was edited."},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "test-1",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "execute_bash",
|
||||
"arguments": json.dumps({"command": "pytest tests | tail -20"}),
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"role": "tool",
|
||||
"content": "1 failed, 10 passed\n[Command finished with exit code 0]",
|
||||
},
|
||||
],
|
||||
"metadata": {
|
||||
"model_patch": {"patch": "diff --git a/src/main.py b/src/main.py\n+++ b/src/main.py\n"},
|
||||
"reference_patch": {"patch": "diff --git a/src/main.py b/src/main.py\n+++ b/src/main.py\n"},
|
||||
},
|
||||
}
|
||||
|
||||
signals = extract_static_signals(record)
|
||||
assert signals["last_stateful_turn"] == 3
|
||||
assert signals["instruction_constraints"] == ["Do not modify test files."]
|
||||
assert signals["test_events"][0]["masked_pipeline"] is True
|
||||
assert signals["test_events"][0]["explicit_failure"] is True
|
||||
assert signals["test_events"][0]["explicit_exit_codes"] == [0]
|
||||
|
||||
|
||||
def test_detects_malformed_and_unknown_tool_calls() -> None:
|
||||
record = {
|
||||
"trajectory_id": "sample-2",
|
||||
"tools": [_tool("execute_bash")],
|
||||
"trajectory": [
|
||||
{"role": "system", "content": "system"},
|
||||
{"role": "user", "content": "issue"},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "bad",
|
||||
"function": {"name": "made_up_tool", "arguments": "{bad json"},
|
||||
}
|
||||
],
|
||||
},
|
||||
{"role": "tool", "content": "error"},
|
||||
],
|
||||
"metadata": {"model_patch": {}, "reference_patch": {}},
|
||||
}
|
||||
signals = extract_static_signals(record)
|
||||
assert signals["malformed_tool_turns"][0]["turn_id"] == 3
|
||||
assert signals["unknown_tool_turns"] == [{"turn_id": 3, "tool_name": "made_up_tool"}]
|
||||
|
||||
|
||||
def test_extract_patch_files_is_stable_and_unique() -> None:
|
||||
patch = "+++ b/src/a.py\n+++ b/src/b.py\n+++ b/src/a.py\n+++ /dev/null\n"
|
||||
assert extract_patch_files(patch) == ["src/a.py", "src/b.py"]
|
||||
@@ -0,0 +1,84 @@
|
||||
"""Tests for transparent filtering rules."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from swe_data_processing.heuristics import classify_metrics, length_bucket
|
||||
|
||||
|
||||
def _metrics(*, failed: int = 0, calls: int = 10, streak: int = 0, tokens=50_000):
|
||||
return {
|
||||
"sample_id": "sample-1",
|
||||
"instance_id": "repo-1",
|
||||
"source_group": "example_group",
|
||||
"source_parquet": "/data/example.parquet",
|
||||
"resolved": 1,
|
||||
"length": {"turn_count": 30, "token_count": tokens},
|
||||
"tools": {
|
||||
"tool_call_count": calls,
|
||||
"failed_tool_call_count": failed,
|
||||
"failed_tool_call_rate": failed / calls if calls else 0.0,
|
||||
"longest_consecutive_failure_run": streak,
|
||||
"error_positions": {
|
||||
"early_count": failed,
|
||||
"early_fraction": 1.0 if failed else 0.0,
|
||||
"occupied_bins_5": min(failed, 5),
|
||||
},
|
||||
"error_type_counts": {},
|
||||
"error_tool_counts": {},
|
||||
},
|
||||
"structure": {
|
||||
"invalid_turn_count": 0,
|
||||
"malformed_tool_definition_count": 0,
|
||||
"malformed_tool_call_count": 0,
|
||||
"unknown_tool_call_count": 0,
|
||||
"missing_tool_result_count": 0,
|
||||
"orphan_tool_result_count": 0,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_length_buckets_match_training_limits() -> None:
|
||||
assert length_bucket(None) == "TOKENIZER_REQUIRED"
|
||||
assert length_bucket(81_920) == "LE_81920"
|
||||
assert length_bucket(81_921) == "81921_TO_131072"
|
||||
assert length_bucket(131_073) == "131073_TO_262144"
|
||||
assert length_bucket(262_145) == "GT_262144"
|
||||
|
||||
|
||||
def test_five_consecutive_failures_is_hard_reject() -> None:
|
||||
decision = classify_metrics(_metrics(failed=5, calls=20, streak=5), 10)
|
||||
assert "FIVE_CONSECUTIVE_TOOL_FAILURES" in decision["hard_reject_reasons"]
|
||||
assert decision["recommended_action"] == "DROP_DEFINITE_TOOL_PROBLEM"
|
||||
|
||||
|
||||
def test_early_cluster_is_review_only() -> None:
|
||||
decision = classify_metrics(_metrics(failed=3, calls=20, streak=2), 10)
|
||||
assert decision["hard_reject_reasons"] == []
|
||||
assert decision["review_flags"] == ["EARLY_FAILURE_CLUSTER"]
|
||||
assert decision["recommended_action"] == "REVIEW_HEURISTIC_HIT"
|
||||
|
||||
|
||||
def test_relative_count_outlier_is_not_automatic_drop() -> None:
|
||||
decision = classify_metrics(_metrics(failed=10, calls=100, streak=2), 10)
|
||||
assert decision["hard_reject_reasons"] == []
|
||||
assert "EXTREME_ERROR_COUNT" in decision["review_flags"]
|
||||
|
||||
|
||||
def test_distributed_failures_require_review_instead_of_automatic_drop() -> None:
|
||||
decision = classify_metrics(_metrics(failed=8, calls=40, streak=2), 20)
|
||||
assert decision["hard_reject_reasons"] == []
|
||||
assert "PERSISTENT_DISTRIBUTED_FAILURES" in decision["review_flags"]
|
||||
|
||||
|
||||
def test_failed_outcome_is_never_recommended_for_training() -> None:
|
||||
metrics = _metrics()
|
||||
metrics["resolved"] = 0
|
||||
decision = classify_metrics(metrics, 10)
|
||||
assert decision["outcome_use"] == "EXCLUDE_FROM_SUCCESS_SFT"
|
||||
assert decision["recommended_action"] == "EXCLUDE_FAILED_OUTCOME"
|
||||
|
||||
|
||||
def test_decision_keeps_source_provenance_for_targeted_review() -> None:
|
||||
decision = classify_metrics(_metrics(), 10)
|
||||
assert decision["source_group"] == "example_group"
|
||||
assert decision["source_parquet"] == "/data/example.parquet"
|
||||
@@ -0,0 +1,123 @@
|
||||
"""Tests for deterministic trajectory metrics."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from swe_data_processing.metrics import canonical_training_text, extract_metrics
|
||||
|
||||
|
||||
def _record(outputs: list[str]) -> dict:
|
||||
trajectory = [{"role": "user", "content": "Fix the bug."}]
|
||||
for index, output in enumerate(outputs, 1):
|
||||
trajectory.extend(
|
||||
[
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": f"call-{index}",
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "bash",
|
||||
"arguments": json.dumps({"command": "pytest tests"}),
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
{"role": "tool", "content": output},
|
||||
]
|
||||
)
|
||||
return {
|
||||
"trajectory_id": "sample-1",
|
||||
"instance_id": "repo-1",
|
||||
"resolved": 1,
|
||||
"tools": [{"type": "function", "function": {"name": "bash"}}],
|
||||
"trajectory": trajectory,
|
||||
}
|
||||
|
||||
|
||||
def test_canonical_text_is_stable_and_excludes_metadata() -> None:
|
||||
record = _record(["exit code 0"])
|
||||
record["metadata"] = {"reference_patch": {"patch": "SECRET"}}
|
||||
text = canonical_training_text(record)
|
||||
assert "SECRET" not in text
|
||||
assert text == canonical_training_text(record)
|
||||
|
||||
|
||||
def test_profile_keeps_source_parquet_out_of_canonical_text() -> None:
|
||||
record = _record(["exit code 0"])
|
||||
record["_source_parquet"] = "/data/openhands/train.parquet"
|
||||
metrics = extract_metrics(record)
|
||||
assert metrics["source_group"] == "openhands"
|
||||
assert "/data/openhands" not in canonical_training_text(record)
|
||||
|
||||
|
||||
def test_extracts_failure_count_type_distribution_and_streak() -> None:
|
||||
record = _record(
|
||||
[
|
||||
"2 failed\n[The command completed with exit code 1.]",
|
||||
"permission denied\nexit status 2",
|
||||
"tests passed\nexit code 0",
|
||||
"timeout while waiting",
|
||||
]
|
||||
)
|
||||
metrics = extract_metrics(record)
|
||||
tools = metrics["tools"]
|
||||
assert tools["tool_call_count"] == 4
|
||||
assert tools["failed_tool_call_count"] == 3
|
||||
assert tools["longest_consecutive_failure_run"] == 2
|
||||
assert tools["error_type_counts"]["nonzero_exit"] == 2
|
||||
assert tools["error_type_counts"]["test_failure"] == 1
|
||||
assert tools["error_type_counts"]["permission_denied"] == 1
|
||||
assert tools["error_type_counts"]["timeout"] == 1
|
||||
assert tools["error_positions"]["bins_5"] == [1, 1, 0, 0, 1]
|
||||
|
||||
|
||||
def test_explicit_success_suppresses_incidental_error_words() -> None:
|
||||
record = _record(["Read fixture containing 'permission denied'.\nexit code 0"])
|
||||
assert extract_metrics(record)["tools"]["failed_tool_call_count"] == 0
|
||||
|
||||
|
||||
def test_detects_broken_tool_structure() -> None:
|
||||
record = _record(["exit code 0"])
|
||||
call = record["trajectory"][1]["tool_calls"][0]
|
||||
call["function"]["arguments"] = "{broken"
|
||||
record["trajectory"].pop()
|
||||
metrics = extract_metrics(record)
|
||||
assert metrics["structure"]["malformed_tool_call_count"] == 1
|
||||
assert metrics["structure"]["missing_tool_result_count"] == 1
|
||||
assert metrics["tools"]["failed_tool_call_count"] == 1
|
||||
|
||||
|
||||
def test_terminal_finish_without_result_is_valid() -> None:
|
||||
record = _record([])
|
||||
record["trajectory"].append(
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"function": {"name": "finish", "arguments": "{}"},
|
||||
"id": "finish-1",
|
||||
"type": "function",
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
record["tools"].append({"type": "function", "function": {"name": "finish"}})
|
||||
metrics = extract_metrics(record)
|
||||
assert metrics["structure"]["missing_tool_result_count"] == 0
|
||||
assert metrics["tools"]["failed_tool_call_count"] == 0
|
||||
|
||||
|
||||
def test_editor_file_content_does_not_trigger_shell_patterns() -> None:
|
||||
record = _record(["source code contains timeout and permission denied"])
|
||||
call = record["trajectory"][1]["tool_calls"][0]
|
||||
call["function"]["name"] = "str_replace_editor"
|
||||
call["function"]["arguments"] = '{"command":"view","path":"src/a.py"}'
|
||||
record["tools"] = [
|
||||
{"type": "function", "function": {"name": "str_replace_editor"}}
|
||||
]
|
||||
assert extract_metrics(record)["tools"]["failed_tool_call_count"] == 0
|
||||
@@ -1,100 +0,0 @@
|
||||
"""Tests for immutable classification and repair policy enforcement."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from swe_data_processing.policy import (
|
||||
PolicyViolation,
|
||||
enforce_classification_policy,
|
||||
enforce_repair_policy,
|
||||
)
|
||||
|
||||
|
||||
def _classification() -> dict:
|
||||
dimensions = {
|
||||
"trajectory_integrity": "PASS",
|
||||
"tool_integrity": "PASS",
|
||||
"patch_presence": "PASS",
|
||||
"patch_trajectory_consistency": "PASS",
|
||||
"instruction_compliance": "PASS",
|
||||
"verification_consistency": "PASS",
|
||||
"final_claim_alignment": "PASS",
|
||||
"patch_hygiene": "PASS",
|
||||
"issue_patch_alignment": "PASS",
|
||||
}
|
||||
return {
|
||||
"source_outcome_class": "POSITIVE_CANDIDATE",
|
||||
"qc_decision": "ACCEPT_SILVER_POSITIVE",
|
||||
"training_use": "SFT_FULL",
|
||||
"qc_passed": True,
|
||||
"hard_fail_codes": [],
|
||||
"verification": {"status": "PASS_RELIABLE"},
|
||||
"dimensions": dimensions,
|
||||
}
|
||||
|
||||
|
||||
def test_valid_silver_positive_passes() -> None:
|
||||
enforce_classification_policy({"resolved": 1}, _classification())
|
||||
|
||||
|
||||
def test_resolved_zero_cannot_be_promoted() -> None:
|
||||
result = _classification()
|
||||
result["source_outcome_class"] = "EXPLICIT_NEGATIVE"
|
||||
with pytest.raises(PolicyViolation, match="resolved=0"):
|
||||
enforce_classification_policy({"resolved": 0}, result)
|
||||
|
||||
|
||||
def test_unknown_cannot_be_full_sft() -> None:
|
||||
result = _classification()
|
||||
result["source_outcome_class"] = "UNVERIFIED"
|
||||
result["qc_decision"] = "HOLD_UNVERIFIED"
|
||||
result["qc_passed"] = False
|
||||
with pytest.raises(PolicyViolation, match="resolved=-1"):
|
||||
enforce_classification_policy({"resolved": -1}, result)
|
||||
|
||||
|
||||
def _repair_result() -> dict:
|
||||
"""Return a minimal non-mutating repair result for policy tests."""
|
||||
|
||||
return {
|
||||
"repair_decision": "NO_CHANGE",
|
||||
"maximum_training_use": "HOLD",
|
||||
"invariants": {
|
||||
"resolved_unchanged": True,
|
||||
"tool_outputs_unchanged": True,
|
||||
"model_patch_unchanged": True,
|
||||
"reference_patch_unchanged": True,
|
||||
"no_synthetic_execution_result": True,
|
||||
},
|
||||
"operations": [],
|
||||
"requires_second_review": True,
|
||||
}
|
||||
|
||||
|
||||
def test_nonmutating_repair_decision_rejects_operations() -> None:
|
||||
"""A hold decision cannot smuggle in an operation the applier will ignore."""
|
||||
|
||||
result = _repair_result()
|
||||
result["repair_decision"] = "REQUIRES_EXECUTION"
|
||||
result["operations"] = [
|
||||
{
|
||||
"op": "REWRITE_FINAL_SUMMARY",
|
||||
"target_turns": [3],
|
||||
"preconditions": ["Turn 3 is an assistant summary"],
|
||||
"replacement": "Honest summary",
|
||||
"reason": "Align the claim with evidence",
|
||||
}
|
||||
]
|
||||
with pytest.raises(PolicyViolation, match="must not contain"):
|
||||
enforce_repair_policy(result)
|
||||
|
||||
|
||||
def test_step_example_requires_operations() -> None:
|
||||
"""Step-only salvage must include a concrete correction and truncation."""
|
||||
|
||||
result = _repair_result()
|
||||
result["repair_decision"] = "CREATE_STEP_EXAMPLE"
|
||||
result["maximum_training_use"] = "SFT_STEP_ONLY"
|
||||
with pytest.raises(PolicyViolation, match="requires at least one"):
|
||||
enforce_repair_policy(result)
|
||||
@@ -1,54 +0,0 @@
|
||||
"""Tests for deterministic static repair application and evidence preservation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from swe_data_processing.repair import apply_static_repair
|
||||
|
||||
|
||||
def _plan() -> dict:
|
||||
return {
|
||||
"sample_id": "sample-1",
|
||||
"repair_decision": "APPLY_STATIC_REPAIR",
|
||||
"maximum_training_use": "ERROR_ANALYSIS",
|
||||
"invariants": {
|
||||
"resolved_unchanged": True,
|
||||
"tool_outputs_unchanged": True,
|
||||
"model_patch_unchanged": True,
|
||||
"reference_patch_unchanged": True,
|
||||
"no_synthetic_execution_result": True,
|
||||
},
|
||||
"operations": [
|
||||
{
|
||||
"op": "REWRITE_FINAL_SUMMARY",
|
||||
"target_turns": [5],
|
||||
"preconditions": ["Turn 5 is an assistant-only summary."],
|
||||
"replacement": "The build passed, but functional behavior remains unverified.",
|
||||
"reason": "Remove an unsupported success claim.",
|
||||
}
|
||||
],
|
||||
"requires_second_review": True,
|
||||
"summary": "Correct the final claim without changing execution evidence.",
|
||||
}
|
||||
|
||||
|
||||
def test_rewrite_summary_preserves_tool_output_and_patch() -> None:
|
||||
record = {
|
||||
"trajectory_id": "sample-1",
|
||||
"resolved": -1,
|
||||
"trajectory": [
|
||||
{"role": "system", "content": "system"},
|
||||
{"role": "user", "content": "issue"},
|
||||
{"role": "assistant", "content": "build", "tool_calls": []},
|
||||
{"role": "tool", "content": "build completed with exit code 0"},
|
||||
{"role": "assistant", "content": "All tests pass.", "tool_calls": []},
|
||||
],
|
||||
"metadata": {
|
||||
"model_patch": {"patch": "+++ b/src/a.py\n"},
|
||||
"reference_patch": {"patch": "+++ b/src/a.py\n"},
|
||||
},
|
||||
}
|
||||
repaired, diff = apply_static_repair(record, _plan())
|
||||
assert repaired["trajectory"][3] == record["trajectory"][3]
|
||||
assert repaired["trajectory"][4]["content"].startswith("The build passed")
|
||||
assert repaired["metadata"] == record["metadata"]
|
||||
assert diff["replaced_original_turn_ids"] == [5]
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Tests for deterministic review sampling."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pyarrow as pa
|
||||
import pyarrow.parquet as pq
|
||||
|
||||
from swe_data_processing.sampling import iter_review_records
|
||||
|
||||
|
||||
def test_review_reads_only_provenance_shards(tmp_path: Path) -> None:
|
||||
selected_shard = tmp_path / "selected.parquet"
|
||||
unrelated_shard = tmp_path / "unrelated.parquet"
|
||||
pq.write_table(
|
||||
pa.Table.from_pylist(
|
||||
[
|
||||
{"trajectory_id": "wanted", "trajectory": "selected"},
|
||||
{"trajectory_id": "other", "trajectory": "same shard"},
|
||||
]
|
||||
),
|
||||
selected_shard,
|
||||
)
|
||||
pq.write_table(
|
||||
pa.Table.from_pylist(
|
||||
[{"trajectory_id": "unrelated", "trajectory": "must not be read"}]
|
||||
),
|
||||
unrelated_shard,
|
||||
)
|
||||
selected = {"wanted": ["hard:FIVE_CONSECUTIVE_TOOL_FAILURES"]}
|
||||
decision = {
|
||||
"sample_id": "wanted",
|
||||
"source_parquet": str(selected_shard),
|
||||
"hard_reject_reasons": ["FIVE_CONSECUTIVE_TOOL_FAILURES"],
|
||||
}
|
||||
|
||||
records = list(
|
||||
iter_review_records(
|
||||
tmp_path,
|
||||
selected,
|
||||
{"wanted": decision},
|
||||
)
|
||||
)
|
||||
|
||||
assert [record["trajectory_id"] for record in records] == ["wanted"]
|
||||
assert records[0]["_qc_review"]["decision"] == decision
|
||||
@@ -0,0 +1,61 @@
|
||||
"""Tests for dataset aggregation and derived thresholds."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from swe_data_processing.summary import build_summary, percentile
|
||||
|
||||
|
||||
def _metric(sample_id: str, tokens: int, errors: int) -> dict:
|
||||
return {
|
||||
"sample_id": sample_id,
|
||||
"resolved": 1,
|
||||
"source_dataset": "test",
|
||||
"length": {
|
||||
"turn_count": 10,
|
||||
"canonical_chars": 100,
|
||||
"token_count": tokens,
|
||||
},
|
||||
"tools": {
|
||||
"tool_call_count": 10,
|
||||
"failed_tool_call_count": errors,
|
||||
"failed_tool_call_rate": errors / 10,
|
||||
"longest_consecutive_failure_run": 0,
|
||||
"error_type_counts": {"nonzero_exit": errors} if errors else {},
|
||||
"error_tool_counts": {"bash": errors} if errors else {},
|
||||
"error_positions": {
|
||||
"early_count": 0,
|
||||
"early_fraction": 0.0,
|
||||
"occupied_bins_5": 0,
|
||||
},
|
||||
},
|
||||
"structure": {
|
||||
"invalid_turn_count": 0,
|
||||
"malformed_tool_definition_count": 0,
|
||||
"malformed_tool_call_count": 0,
|
||||
"unknown_tool_call_count": 0,
|
||||
"missing_tool_result_count": 0,
|
||||
"orphan_tool_result_count": 0,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_percentile_interpolates() -> None:
|
||||
assert percentile([0, 10], 0.5) == 5
|
||||
|
||||
|
||||
def test_summary_reports_token_tail_and_p99_threshold(tmp_path: Path) -> None:
|
||||
path = tmp_path / "metrics.jsonl"
|
||||
rows = [_metric(f"s-{index}", 10_000, index) for index in range(10)]
|
||||
rows.append(_metric("long", 300_000, 0))
|
||||
path.write_text("".join(json.dumps(row) + "\n" for row in rows), encoding="utf-8")
|
||||
summary, threshold = build_summary(path)
|
||||
assert summary["sample_count"] == 11
|
||||
assert summary["length"]["buckets"]["GT_262144"]["samples"] == 1
|
||||
assert summary["length"]["longest_sample_token_contribution"]["0.001"][
|
||||
"token_share"
|
||||
] > 0.7
|
||||
assert threshold >= 8
|
||||
|
||||
Reference in New Issue
Block a user