feat: compact durable logs and refine framework routing

This commit is contained in:
CoolBoy
2026-09-04 10:30:21 +08:00
parent 5b1ec4d3eb
commit ff73768537
13 changed files with 621 additions and 44 deletions

View File

@@ -319,6 +319,45 @@ class ClientPoolConcurrencyTests(unittest.TestCase):
self.assertEqual("age_policy_skipped", result["reason"])
self.assertEqual([], client.submitted)
def test_submit_capacity_exhaustion_is_deferred_not_failed(self) -> None:
class CapacityClient:
@staticmethod
def add_task(_payload): # noqa: ANN001, ANN205
raise ModelHubAPIError("当前等待中或运行中的异步模型验证任务数量已达上限100")
result = submit_candidate(
{
"repoId": "owner/model",
"modelAddress": "https://modelscope.cn/models/owner/model",
"targetGpu": "gpu-a",
"framework": "vllm",
"taskType": "text-generation",
"configParams": "safe",
},
CapacityClient(), # type: ignore[arg-type]
)
self.assertEqual("capacity_deferred", result["outcome"])
self.assertEqual("account_capacity_saturated", result["reason"])
def test_transformers_platform_prerequisite_is_deferred_not_failed(self) -> None:
class PrerequisiteClient:
@staticmethod
def add_task(_payload): # noqa: ANN001, ANN205
raise ModelHubAPIError("该模型必须在非transformers框架验证失败后才可以开启transformers框架验证任务")
result = submit_candidate(
{
"repoId": "owner/model",
"modelAddress": "https://modelscope.cn/models/owner/model",
"targetGpu": "gpu-a",
"framework": "transformers",
"taskType": "text-generation",
"configParams": "safe",
},
PrerequisiteClient(), # type: ignore[arg-type]
)
self.assertEqual("framework_prerequisite_deferred", result["outcome"])
def test_age_policy_deferred_candidate_is_skipped_not_failed(self) -> None:
class AdmissionRaceClient:
@staticmethod

View File

@@ -193,6 +193,63 @@ class SuperAgentTests(unittest.TestCase):
self.assertEqual("reliable", ordered[0]["targetGpu"])
self.assertGreater(ordered[0]["routingSuccessBand"], ordered[1]["routingSuccessBand"])
def test_transformers_is_fallback_when_an_executable_framework_exists(self) -> None:
with tempfile.TemporaryDirectory() as temporary_dir:
engine = SuccessFirstRoutingEngine(Path(temporary_dir) / "routing.json", log_fn=lambda _: None)
candidates = [
{
"repoId": "owner/model",
"targetGpu": "gpu-a",
"framework": "transformers",
"taskType": "text-generation",
"frameworkMarketSamples": 1000,
"frameworkMarketSuccessRate": 0.95,
},
{
"repoId": "owner/model",
"targetGpu": "gpu-b",
"framework": "vllm",
"taskType": "text-generation",
"frameworkMarketSamples": 1000,
"frameworkMarketSuccessRate": 0.40,
},
]
ordered = engine.order_candidates(candidates)
self.assertEqual(1, len(ordered))
self.assertEqual("vllm", ordered[0]["framework"])
candidates[0]["transformersPrerequisiteSatisfied"] = True
unlocked = engine.order_candidates(candidates)
self.assertEqual("transformers", unlocked[0]["framework"])
def test_outcome_context_repairs_framework_missing_from_history_api(self) -> None:
with tempfile.TemporaryDirectory() as temporary_dir:
root = Path(temporary_dir)
tracker = OutcomeTracker(root / "outcomes.jsonl")
tracker._records = [
{
"taskId": "42",
"modelId": "owner/model",
"targetGpu": "gpu-a",
"framework": "",
"taskType": "",
"outcome": "failed",
}
]
tracker._rebuild_indexes()
repaired = tracker.merge_task_contexts(
{
"42": {
"framework": "vllm",
"taskType": "text-generation",
"modelProfile": {"modelType": "Qwen2ForCausalLM"},
}
}
)
self.assertEqual(1, repaired)
self.assertEqual("vllm", tracker._by_task_id["42"]["framework"])
self.assertEqual("Qwen2ForCausalLM", tracker._by_task_id["42"]["modelProfile"]["modelType"])
def test_modelscope_metadata_and_model_card_lineage_are_structured(self) -> None:
item = {
"id": "owner/model",
@@ -270,6 +327,11 @@ class SuperAgentTests(unittest.TestCase):
]
)
self.assertIsNotNone(batch_id)
generation = manager.generation
remote_head = manager._remote_oid()
self.assertTrue(manager.sync("unchanged_cycle"))
self.assertEqual(generation, manager.generation)
self.assertEqual(remote_head, manager._remote_oid())
self.assertFalse(pending_archive.exists())
archive_refs = porcelain.ls_remote(str(remote)).refs
self.assertIn(b"refs/heads/agent-archive-2026-08", archive_refs)
@@ -296,6 +358,63 @@ class SuperAgentTests(unittest.TestCase):
self.assertNotIn("must-not-be-copied", state_text)
restored.close()
def test_failed_state_push_retries_the_same_commit_and_generation(self) -> None:
with tempfile.TemporaryDirectory() as temporary_dir:
root = Path(temporary_dir)
remote = root / "remote.git"
project = root / "project"
project.mkdir()
porcelain.init(remote, bare=True)
manager = StateGitSync(
project_root=project,
credentials={"username": "u", "email": "e@example.com", "password": "p"},
remote=str(remote),
log_fn=lambda _: None,
)
manager.acquire_process_lock()
self.assertTrue(manager.restore())
write_json(project / ".modelhub_state" / "account_capacity.json", {"version": 1})
with patch("state_sync.porcelain.push", side_effect=RuntimeError("temporary failure")):
self.assertFalse(manager.sync("cycle"))
pending_oid = manager._pending_push_oid
self.assertIsNotNone(pending_oid)
self.assertEqual(0, manager.generation)
self.assertTrue(manager.sync("retry"))
self.assertEqual(1, manager.generation)
self.assertEqual(pending_oid, manager._remote_oid())
manager.close()
def test_terminal_intents_are_bounded_and_archived(self) -> None:
with tempfile.TemporaryDirectory() as temporary_dir:
root = Path(temporary_dir)
intent_path = root / ".modelhub_state" / "recovery_intents.jsonl"
write_jsonl(
intent_path,
[
{
"intentId": str(index),
"status": "failed",
"createdAt": f"2026-08-01T00:{index % 60:02d}:00+00:00",
"completedAt": f"2026-08-02T00:{index % 60:02d}:00+00:00",
}
for index in range(250)
]
+ [{"intentId": "pending", "status": "pending"}],
)
manager = StateGitSync(
project_root=root,
credentials={"username": "u", "email": "e@example.com", "password": "p"},
remote="unused",
log_fn=lambda _: None,
)
self.assertEqual(50, manager._compact_intents())
retained = read_jsonl(intent_path)
self.assertEqual(201, len(retained))
self.assertEqual(1, sum(row.get("status") == "pending" for row in retained))
shard = next((root / ".modelhub_state" / "archive_pending" / "attempts").rglob("*.jsonl.gz"))
with gzip.open(shard, "rt", encoding="utf-8") as handle:
self.assertEqual(50, len(handle.readlines()))
def test_failed_intent_push_returns_no_batch_id(self) -> None:
with tempfile.TemporaryDirectory() as temporary_dir:
manager = StateGitSync(