test_pipeline_context.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. from __future__ import annotations
  2. from pathlib import Path
  3. from types import SimpleNamespace
  4. from obagent_sdk import observe
  5. from production_build_agents.observability.pipeline import (
  6. PipelineObservation,
  7. )
  8. class _Handle:
  9. def __init__(
  10. self,
  11. *,
  12. run_id: str | None = None,
  13. inst_id: str | None = None,
  14. ) -> None:
  15. self.run_id = run_id
  16. self.inst_id = inst_id
  17. self.declared: list[dict] = []
  18. self.outputs: list[tuple[object, dict]] = []
  19. self.finished: list[dict] = []
  20. def declare(self, **payload) -> None:
  21. self.declared.append(payload)
  22. def set_output(self, output, **payload) -> None:
  23. self.outputs.append((output, payload))
  24. def finish(self, **payload) -> None:
  25. self.finished.append(payload)
  26. class _Manager:
  27. def __init__(self, handle: _Handle) -> None:
  28. self.handle = handle
  29. self.exits: list[tuple] = []
  30. def __enter__(self) -> _Handle:
  31. return self.handle
  32. def __exit__(self, *args):
  33. self.exits.append(args)
  34. if args[1] is not None:
  35. raise args[1]
  36. return False
  37. def test_pipeline_has_two_stages_and_exposes_child_parent_ids(
  38. monkeypatch,
  39. tmp_path: Path,
  40. ) -> None:
  41. source = tmp_path / "production_final.json"
  42. anchor = tmp_path / "anchor.png"
  43. source.write_text('{"title":"真实输入"}', encoding="utf-8")
  44. anchor.write_bytes(b"anchor")
  45. run_handle = _Handle(run_id="pipeline-run-id")
  46. run_manager = _Manager(run_handle)
  47. stage_handles = iter(
  48. (
  49. _Handle(inst_id="global-stage-inst"),
  50. _Handle(inst_id="production-stage-inst"),
  51. )
  52. )
  53. stage_managers: list[_Manager] = []
  54. captured: dict = {}
  55. def fake_run(**kwargs):
  56. captured.update(kwargs)
  57. return run_manager
  58. def fake_module(*_args, **_kwargs):
  59. manager = _Manager(next(stage_handles))
  60. stage_managers.append(manager)
  61. return manager
  62. monkeypatch.setattr(observe, "run", fake_run)
  63. monkeypatch.setattr(observe, "module", fake_module)
  64. settings = SimpleNamespace(
  65. configure_sdk=lambda: True,
  66. project="pipeline-project",
  67. project_name="Pipeline 观测",
  68. capture_mode="full",
  69. )
  70. with PipelineObservation(
  71. project_root=tmp_path,
  72. round_id="round-1",
  73. input_path=source,
  74. output_root=tmp_path / "runs",
  75. shared_visual_anchor_path=anchor,
  76. settings=settings,
  77. ) as pipeline:
  78. with pipeline.stage(
  79. "global_data",
  80. input_value={"thread_id": "round-1-global-data"},
  81. ) as stage:
  82. assert stage.parent_run_id == "pipeline-run-id"
  83. assert stage.parent_inst_id == "global-stage-inst"
  84. stage.finish(
  85. {
  86. "run_id": "round-1-global-data",
  87. "status": "COMPLETED",
  88. "global_data_delivery_path": "/run/delivery.json",
  89. }
  90. )
  91. with pipeline.stage(
  92. "production",
  93. input_value={"thread_id": "round-1-production"},
  94. ) as stage:
  95. assert stage.parent_run_id == "pipeline-run-id"
  96. assert stage.parent_inst_id == "production-stage-inst"
  97. stage.finish(
  98. {
  99. "run_id": "round-1-production",
  100. "status": "RUNNING",
  101. }
  102. )
  103. pipeline.finish(
  104. {
  105. "round_id": "round-1",
  106. "status": "RUNNING",
  107. "phase": "PRODUCTION",
  108. "global_data_thread_id": "round-1-global-data",
  109. "production_thread_id": "round-1-production",
  110. }
  111. )
  112. assert captured["agent"] == "pipeline"
  113. assert captured["auto_collect"] is False
  114. assert [node["key"] for node in captured["spec"]["nodes"]] == [
  115. "global_data",
  116. "production",
  117. ]
  118. assert captured["spec"]["edges"] == [
  119. {"source": "global_data", "target": "production"}
  120. ]
  121. global_output = stage_managers[0].handle.outputs[0][0]
  122. assert global_output["阶段"] == "Global Data"
  123. assert global_output["执行结论"] == "已完成"
  124. assert global_output["正式交付"] == "/run/delivery.json"
  125. global_input = stage_managers[0].handle.declared[0]["blocks"][0].value
  126. assert global_input["阶段任务"] == (
  127. "整理 Production 所需的全局资料与约束"
  128. )
  129. assert "thread_id" not in global_input
  130. final_output = run_handle.finished[0]["final_output"]
  131. assert final_output["当前阶段"] == "Production"
  132. assert final_output["执行结论"] == "进行中"
  133. assert "phase" not in final_output