feat: compact durable logs and refine framework routing
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user