8ea00f5185
CI / unit_tests (push) Has been cancelled
CI / benchmark-publish (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / typecheck (push) Has been cancelled
CI / security (push) Has been cancelled
CI / quality (push) Has been cancelled
CI / integration_tests (push) Has been cancelled
CI / e2e_tests (push) Has been cancelled
CI / coverage (push) Has been cancelled
CI / benchmark-regression (push) Has been cancelled
CI / build (push) Has been cancelled
CI / push-validation (push) Has been cancelled
CI / status-check (push) Has been cancelled
CI / docker (push) Has been cancelled
CI / helm (push) Has been cancelled
Co-authored-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me> Co-committed-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me>
466 lines
16 KiB
Python
466 lines
16 KiB
Python
"""Robot Framework helper for ACMS v1 pipeline integration tests.
|
|
|
|
Provides a CLI-style interface for Robot to invoke ACMS pipeline
|
|
operations and verify the results. Exit code 0 = success, 1 = failure.
|
|
|
|
Usage:
|
|
python robot/helper_acms_pipeline.py fragment-create
|
|
python robot/helper_acms_pipeline.py budget-calc
|
|
python robot/helper_acms_pipeline.py assemble-relevance
|
|
python robot/helper_acms_pipeline.py assemble-recency
|
|
python robot/helper_acms_pipeline.py assemble-tiered
|
|
python robot/helper_acms_pipeline.py payload-budget-check
|
|
python robot/helper_acms_pipeline.py assemble-size-budget
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from collections.abc import Callable
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
|
|
_SRC = str(Path(__file__).resolve().parents[1] / "src")
|
|
if _SRC in sys.path:
|
|
sys.path.remove(_SRC)
|
|
sys.path.insert(0, _SRC)
|
|
|
|
from cleveragents.application.services.acms_service import ACMSPipeline # noqa: E402
|
|
from cleveragents.domain.models.core.context_fragment import ( # noqa: E402
|
|
ContextBudget,
|
|
ContextFragment,
|
|
FragmentProvenance,
|
|
)
|
|
from cleveragents.domain.models.core.context_policy import ContextView # noqa: E402
|
|
|
|
# Default provenance for test fragments.
|
|
_DEFAULT_PROV = FragmentProvenance(resource_uri="test://robot")
|
|
|
|
|
|
def _cmd_fragment_create() -> int:
|
|
"""Create a ContextFragment and verify defaults."""
|
|
frag = ContextFragment(
|
|
uko_node="project://app/main.py",
|
|
content="hello world",
|
|
token_count=10,
|
|
provenance=FragmentProvenance(resource_uri="project://app/main.py"),
|
|
)
|
|
if frag.uko_node != "project://app/main.py":
|
|
print(
|
|
f"acms-fail: expected uko_node=project://app/main.py, got {frag.uko_node}"
|
|
)
|
|
return 1
|
|
if frag.relevance_score != 0.5:
|
|
print(f"acms-fail: expected score=0.5, got {frag.relevance_score}")
|
|
return 1
|
|
if not frag.fragment_id:
|
|
print("acms-fail: fragment_id is empty")
|
|
return 1
|
|
if not isinstance(frag.created_at, datetime):
|
|
print(f"acms-fail: created_at is not datetime, got {type(frag.created_at)}")
|
|
return 1
|
|
if frag.token_count != 10:
|
|
print(f"acms-fail: expected token_count=10, got {frag.token_count}")
|
|
return 1
|
|
print(f"acms-fragment-ok: id={frag.fragment_id}")
|
|
return 0
|
|
|
|
|
|
def _cmd_budget_calc() -> int:
|
|
"""Create ContextBudget and verify available_tokens."""
|
|
budget = ContextBudget(max_tokens=4096, reserved_tokens=512)
|
|
expected = 3584
|
|
if budget.available_tokens != expected:
|
|
print(
|
|
f"acms-fail: expected available={expected}, got {budget.available_tokens}"
|
|
)
|
|
return 1
|
|
print(f"acms-budget-ok: available={budget.available_tokens}")
|
|
return 0
|
|
|
|
|
|
def _cmd_assemble_relevance() -> int:
|
|
"""Assemble fragments with relevance strategy."""
|
|
frags = [
|
|
ContextFragment(
|
|
uko_node="project://app/high.py",
|
|
content="high",
|
|
relevance_score=0.9,
|
|
token_count=100,
|
|
provenance=_DEFAULT_PROV,
|
|
),
|
|
ContextFragment(
|
|
uko_node="project://app/low.py",
|
|
content="low",
|
|
relevance_score=0.2,
|
|
token_count=100,
|
|
provenance=_DEFAULT_PROV,
|
|
),
|
|
ContextFragment(
|
|
uko_node="project://app/mid.py",
|
|
content="mid",
|
|
relevance_score=0.6,
|
|
token_count=100,
|
|
provenance=_DEFAULT_PROV,
|
|
),
|
|
]
|
|
budget = ContextBudget(max_tokens=200, reserved_tokens=0)
|
|
pipeline = ACMSPipeline()
|
|
payload = pipeline.assemble(
|
|
plan_id="01JQTESTPN00000000000000AA",
|
|
fragments=frags,
|
|
budget=budget,
|
|
strategy="relevance",
|
|
)
|
|
if len(payload.fragments) != 2:
|
|
print(f"acms-fail: expected 2 fragments, got {len(payload.fragments)}")
|
|
return 1
|
|
if payload.fragments[0].content != "high":
|
|
print(f"acms-fail: expected first=high, got {payload.fragments[0].content}")
|
|
return 1
|
|
if not payload.strategies_used:
|
|
print("acms-fail: strategies_used is empty")
|
|
return 1
|
|
print(f"acms-assemble-relevance-ok: fragments={len(payload.fragments)}")
|
|
return 0
|
|
|
|
|
|
def _cmd_assemble_recency() -> int:
|
|
"""Assemble fragments with recency strategy."""
|
|
frags = [
|
|
ContextFragment(
|
|
uko_node="project://app/old.py",
|
|
content="old",
|
|
token_count=100,
|
|
created_at=datetime(2024, 1, 1, tzinfo=UTC),
|
|
provenance=_DEFAULT_PROV,
|
|
),
|
|
ContextFragment(
|
|
uko_node="project://app/mid.py",
|
|
content="mid",
|
|
token_count=100,
|
|
created_at=datetime(2024, 6, 1, tzinfo=UTC),
|
|
provenance=_DEFAULT_PROV,
|
|
),
|
|
ContextFragment(
|
|
uko_node="project://app/new.py",
|
|
content="new",
|
|
token_count=100,
|
|
created_at=datetime(2025, 1, 1, tzinfo=UTC),
|
|
provenance=_DEFAULT_PROV,
|
|
),
|
|
]
|
|
budget = ContextBudget(max_tokens=200, reserved_tokens=0)
|
|
pipeline = ACMSPipeline()
|
|
payload = pipeline.assemble(
|
|
plan_id="01JQTESTPN00000000000000AA",
|
|
fragments=frags,
|
|
budget=budget,
|
|
strategy="recency",
|
|
)
|
|
if len(payload.fragments) != 2:
|
|
print(f"acms-fail: expected 2 fragments, got {len(payload.fragments)}")
|
|
return 1
|
|
if payload.fragments[0].content != "new":
|
|
print(f"acms-fail: expected first=new, got {payload.fragments[0].content}")
|
|
return 1
|
|
if payload.fragments[1].content != "mid":
|
|
print(f"acms-fail: expected second=mid, got {payload.fragments[1].content}")
|
|
return 1
|
|
print(f"acms-assemble-recency-ok: fragments={len(payload.fragments)}")
|
|
return 0
|
|
|
|
|
|
def _cmd_assemble_tiered() -> int:
|
|
"""Assemble fragments with tiered strategy."""
|
|
frags = [
|
|
ContextFragment(
|
|
uko_node="project://app/cold.py",
|
|
content="cold-item",
|
|
relevance_score=0.9,
|
|
token_count=100,
|
|
tier="cold",
|
|
provenance=_DEFAULT_PROV,
|
|
),
|
|
ContextFragment(
|
|
uko_node="project://app/hot.py",
|
|
content="hot-item",
|
|
relevance_score=0.85,
|
|
token_count=100,
|
|
tier="hot",
|
|
provenance=_DEFAULT_PROV,
|
|
),
|
|
ContextFragment(
|
|
uko_node="project://app/warm.py",
|
|
content="warm-item",
|
|
relevance_score=0.8,
|
|
token_count=100,
|
|
tier="warm",
|
|
provenance=_DEFAULT_PROV,
|
|
),
|
|
]
|
|
budget = ContextBudget(max_tokens=200, reserved_tokens=0)
|
|
pipeline = ACMSPipeline()
|
|
payload = pipeline.assemble(
|
|
plan_id="01JQTESTPN00000000000000AA",
|
|
fragments=frags,
|
|
budget=budget,
|
|
strategy="tiered",
|
|
)
|
|
if len(payload.fragments) != 2:
|
|
print(f"acms-fail: expected 2 fragments, got {len(payload.fragments)}")
|
|
return 1
|
|
if payload.fragments[0].content != "hot-item":
|
|
print(f"acms-fail: expected first=hot-item, got {payload.fragments[0].content}")
|
|
return 1
|
|
print(f"acms-assemble-tiered-ok: fragments={len(payload.fragments)}")
|
|
return 0
|
|
|
|
|
|
def _cmd_payload_budget_check() -> int:
|
|
"""Verify is_within_budget property on assembled payload."""
|
|
frags = [
|
|
ContextFragment(
|
|
uko_node="project://app/item.py",
|
|
content="item",
|
|
relevance_score=0.9,
|
|
token_count=100,
|
|
provenance=_DEFAULT_PROV,
|
|
),
|
|
]
|
|
budget = ContextBudget(max_tokens=500, reserved_tokens=0)
|
|
pipeline = ACMSPipeline()
|
|
payload = pipeline.assemble(
|
|
plan_id="01JQTESTPN00000000000000AA",
|
|
fragments=frags,
|
|
budget=budget,
|
|
strategy="relevance",
|
|
)
|
|
if not payload.is_within_budget:
|
|
print("acms-fail: expected is_within_budget=True")
|
|
return 1
|
|
if payload.remaining_tokens != 400:
|
|
print(f"acms-fail: expected remaining=400, got {payload.remaining_tokens}")
|
|
return 1
|
|
if not payload.context_hash:
|
|
print("acms-fail: expected non-empty context_hash")
|
|
return 1
|
|
if len(payload.context_hash) != 64:
|
|
print(
|
|
f"acms-fail: expected SHA-256 hex (64 chars), "
|
|
f"got {len(payload.context_hash)}"
|
|
)
|
|
return 1
|
|
try:
|
|
int(payload.context_hash, 16)
|
|
except ValueError:
|
|
print(f"acms-fail: context_hash is not valid hex: {payload.context_hash!r}")
|
|
return 1
|
|
print(
|
|
f"acms-payload-budget-ok: within_budget={payload.is_within_budget} "
|
|
f"remaining={payload.remaining_tokens}"
|
|
)
|
|
return 0
|
|
|
|
|
|
def _cmd_assemble_size_budget() -> int:
|
|
"""Verify ACMSPipeline budget pre-filter with ContextView limits."""
|
|
frags = [
|
|
ContextFragment(
|
|
uko_node="project://app/small.py",
|
|
content="a" * 50,
|
|
relevance_score=0.9,
|
|
token_count=60,
|
|
provenance=_DEFAULT_PROV,
|
|
),
|
|
ContextFragment(
|
|
uko_node="project://app/too_big.py",
|
|
content="b" * 200,
|
|
relevance_score=0.8,
|
|
token_count=60,
|
|
provenance=_DEFAULT_PROV,
|
|
),
|
|
ContextFragment(
|
|
uko_node="project://app/fit.py",
|
|
content="c" * 80,
|
|
relevance_score=0.7,
|
|
token_count=60,
|
|
provenance=_DEFAULT_PROV,
|
|
),
|
|
]
|
|
budget = ContextBudget(max_tokens=1000, reserved_tokens=0)
|
|
pipeline = ACMSPipeline()
|
|
payload = pipeline.assemble(
|
|
plan_id="01JQTESTPN00000000000000AA",
|
|
fragments=frags,
|
|
budget=budget,
|
|
strategy="relevance",
|
|
context_view=ContextView(max_file_size=100),
|
|
)
|
|
|
|
result = pipeline.last_enforcement_result
|
|
if result is None:
|
|
print("acms-fail: expected non-empty enforcement result")
|
|
return 1
|
|
if len(payload.fragments) != 2:
|
|
print(f"acms-fail: expected 2 fragments, got {len(payload.fragments)}")
|
|
return 1
|
|
if len(result.accepted) != 2:
|
|
print(f"acms-fail: expected 2 accepted ids, got {len(result.accepted)}")
|
|
return 1
|
|
if len(result.violations) != 1:
|
|
print(f"acms-fail: expected 1 violation, got {len(result.violations)}")
|
|
return 1
|
|
if result.violations[0].violation_type != "max_file_size":
|
|
print(
|
|
"acms-fail: expected violation_type=max_file_size, "
|
|
f"got {result.violations[0].violation_type}"
|
|
)
|
|
return 1
|
|
|
|
payload_ids = {fragment.fragment_id for fragment in payload.fragments}
|
|
if payload_ids != set(result.accepted):
|
|
print(
|
|
"acms-fail: payload fragment ids do not match accepted ids "
|
|
f"payload={sorted(payload_ids)} accepted={sorted(result.accepted)}"
|
|
)
|
|
return 1
|
|
|
|
if result.total_size != 130:
|
|
print(f"acms-fail: expected total_size=130, got {result.total_size}")
|
|
return 1
|
|
|
|
print("acms-assemble-size-budget-ok: accepted=2 violations=1 total_size=130")
|
|
return 0
|
|
|
|
|
|
def _cmd_skeleton_context_inheritance() -> int:
|
|
"""Verify parent plan context is compressed and propagated to child plan.
|
|
|
|
Simulates the context inheritance scenario:
|
|
1. Parent plan accumulates context fragments.
|
|
2. Child plan is spawned — assemble() is called with parent_fragments.
|
|
3. The assembled ContextPayload.skeleton_fragments must be non-empty,
|
|
confirming the SkeletonCompressor was invoked and the skeleton is
|
|
available for the child plan.
|
|
|
|
This is the integration test for issue #3563.
|
|
"""
|
|
from cleveragents.application.services.acms_service import DefaultSkeletonCompressor
|
|
|
|
# --- Parent plan accumulates context ---
|
|
parent_fragments = (
|
|
ContextFragment(
|
|
uko_node="project://parent/strategy.py",
|
|
content="class PlanStrategy: pass # parent plan strategy",
|
|
token_count=50,
|
|
relevance_score=0.9,
|
|
provenance=FragmentProvenance(resource_uri="project://parent/strategy.py"),
|
|
),
|
|
ContextFragment(
|
|
uko_node="project://parent/executor.py",
|
|
content="class PlanExecutor: pass # parent plan executor",
|
|
token_count=40,
|
|
relevance_score=0.7,
|
|
provenance=FragmentProvenance(resource_uri="project://parent/executor.py"),
|
|
),
|
|
ContextFragment(
|
|
uko_node="project://parent/context.py",
|
|
content="class ContextManager: pass # parent context manager",
|
|
token_count=35,
|
|
relevance_score=0.6,
|
|
provenance=FragmentProvenance(resource_uri="project://parent/context.py"),
|
|
),
|
|
)
|
|
|
|
# --- Child plan is spawned: assemble with parent_fragments ---
|
|
child_fragments = [
|
|
ContextFragment(
|
|
uko_node="project://child/task.py",
|
|
content="class ChildTask: pass # child plan task",
|
|
token_count=30,
|
|
relevance_score=0.8,
|
|
provenance=FragmentProvenance(resource_uri="project://child/task.py"),
|
|
),
|
|
]
|
|
budget = ContextBudget(max_tokens=1000, reserved_tokens=0)
|
|
skeleton_ratio = 0.15
|
|
|
|
pipeline = ACMSPipeline(skeleton_compressor=DefaultSkeletonCompressor())
|
|
payload = pipeline.assemble(
|
|
plan_id="01JQTESTPN00000000000000AB",
|
|
fragments=child_fragments,
|
|
budget=budget,
|
|
parent_fragments=parent_fragments,
|
|
skeleton_ratio=skeleton_ratio,
|
|
)
|
|
|
|
# Verify skeleton_fragments is non-empty (compressor was invoked)
|
|
if not payload.skeleton_fragments:
|
|
print(
|
|
"acms-fail: payload.skeleton_fragments is empty — "
|
|
"SkeletonCompressor was not invoked in assemble(). "
|
|
"Child plan has no inherited parent context (issue #3563)."
|
|
)
|
|
return 1
|
|
|
|
# Verify skeleton budget was computed correctly
|
|
expected_budget = int(budget.available_tokens * skeleton_ratio)
|
|
actual_tokens = sum(f.token_count for f in payload.skeleton_fragments)
|
|
if actual_tokens > expected_budget:
|
|
print(
|
|
f"acms-fail: skeleton tokens {actual_tokens} "
|
|
f"exceed budget {expected_budget}"
|
|
)
|
|
return 1
|
|
|
|
# Verify child plan fragments are also present
|
|
if not payload.fragments:
|
|
print("acms-fail: child plan fragments are missing from payload")
|
|
return 1
|
|
|
|
print(
|
|
f"acms-skeleton-inheritance-ok: "
|
|
f"skeleton_fragments={len(payload.skeleton_fragments)} "
|
|
f"skeleton_tokens={actual_tokens} "
|
|
f"skeleton_budget={expected_budget} "
|
|
f"child_fragments={len(payload.fragments)}"
|
|
)
|
|
return 0
|
|
|
|
|
|
_COMMANDS: dict[str, Callable[[], int]] = {
|
|
"fragment-create": _cmd_fragment_create,
|
|
"budget-calc": _cmd_budget_calc,
|
|
"assemble-relevance": _cmd_assemble_relevance,
|
|
"assemble-recency": _cmd_assemble_recency,
|
|
"assemble-tiered": _cmd_assemble_tiered,
|
|
"payload-budget-check": _cmd_payload_budget_check,
|
|
"assemble-size-budget": _cmd_assemble_size_budget,
|
|
"skeleton-context-inheritance": _cmd_skeleton_context_inheritance,
|
|
}
|
|
|
|
|
|
def main() -> int:
|
|
"""Entry point called by Robot Framework ``Run Process``."""
|
|
if len(sys.argv) < 2:
|
|
print(
|
|
"Usage: helper_acms_pipeline.py "
|
|
"<fragment-create|budget-calc|assemble-relevance"
|
|
"|assemble-recency|assemble-tiered|payload-budget-check"
|
|
"|assemble-size-budget|skeleton-context-inheritance>"
|
|
)
|
|
return 1
|
|
|
|
command = sys.argv[1]
|
|
handler = _COMMANDS.get(command)
|
|
if handler is None:
|
|
print(f"Unknown command: {command}")
|
|
return 1
|
|
|
|
return handler()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|