run_segment.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. """从完整 SegmentPackage 运行或恢复一个正式 Segment。"""
  2. from __future__ import annotations
  3. import argparse
  4. import hashlib
  5. import json
  6. from pathlib import Path
  7. from dotenv import load_dotenv
  8. from langchain_core.language_models import BaseChatModel
  9. from production_build_agents.agents.segment.run import run_segment_production
  10. from production_build_agents.contracts.production_execution_models import (
  11. SegmentPackage,
  12. )
  13. from production_build_agents.contracts.production_models import (
  14. SegmentValidationReport,
  15. )
  16. from production_build_agents.production.runtime import (
  17. PRODUCTION_PROTOCOL_VERSION,
  18. validate_production_run_directory_boundary,
  19. )
  20. from production_build_agents.run.layout import run_directory
  21. from production_build_agents.run.records import VersionConflictError
  22. from production_build_agents.run.registry import register_thread
  23. from production_build_agents.tools.registry import ToolRegistry
  24. PROJECT_ROOT = Path(__file__).resolve().parent
  25. def segment_request_sha256(package_bytes: bytes) -> str:
  26. """以完整 Package 的精确字节作为 standalone 请求身份。"""
  27. return hashlib.sha256(package_bytes).hexdigest()
  28. def run_production_segment(
  29. package_path: Path,
  30. *,
  31. output_root: Path,
  32. thread_id: str | None = None,
  33. executor_model: BaseChatModel | None = None,
  34. validator_model: BaseChatModel | None = None,
  35. tool_registry: ToolRegistry | None = None,
  36. registry_dir: Path | None = None,
  37. ) -> SegmentValidationReport:
  38. """校验完整 Package 并复用 Graph 的 Executor/Validator 阶段服务。"""
  39. load_dotenv(PROJECT_ROOT / ".env", override=False)
  40. source = package_path.resolve()
  41. if not source.is_file():
  42. raise FileNotFoundError(f"SegmentPackage 不存在:{source}")
  43. source_bytes = source.read_bytes()
  44. package = SegmentPackage.model_validate_json(source_bytes)
  45. request_sha256 = segment_request_sha256(source_bytes)
  46. active_thread_id = thread_id or package.run_id
  47. if active_thread_id != package.run_id:
  48. raise ValueError(
  49. "standalone thread_id 必须与 SegmentPackage.run_id 一致"
  50. )
  51. run_dir = run_directory(
  52. output_root.resolve(),
  53. active_thread_id,
  54. ).resolve()
  55. validate_production_run_directory_boundary(run_dir)
  56. def prepare_locked_run(locked_run_dir: Path) -> None:
  57. validate_production_run_directory_boundary(locked_run_dir)
  58. if source.read_bytes() != source_bytes:
  59. raise VersionConflictError(
  60. "SegmentPackage 输入文件在封印前已变化"
  61. )
  62. register_thread(
  63. registry_dir=registry_dir or PROJECT_ROOT / ".run_registry",
  64. thread_id=active_thread_id,
  65. protocol_version=PRODUCTION_PROTOCOL_VERSION,
  66. input_path=source,
  67. input_sha256=request_sha256,
  68. run_dir=locked_run_dir,
  69. )
  70. return run_segment_production(
  71. package,
  72. run_dir=run_dir,
  73. executor_model=executor_model,
  74. validator_model=validator_model,
  75. tool_registry=tool_registry,
  76. locked_preflight=prepare_locked_run,
  77. )
  78. def _build_parser() -> argparse.ArgumentParser:
  79. parser = argparse.ArgumentParser(
  80. description="运行一个正式 SegmentPackage,直到终态或明确失败"
  81. )
  82. parser.add_argument("package", type=Path, help="完整 SegmentPackage JSON")
  83. parser.add_argument(
  84. "--output-dir",
  85. type=Path,
  86. default=Path("demo_output"),
  87. )
  88. parser.add_argument("--thread-id", default=None)
  89. return parser
  90. def main() -> None:
  91. args = _build_parser().parse_args()
  92. report = run_production_segment(
  93. args.package,
  94. output_root=args.output_dir,
  95. thread_id=args.thread_id,
  96. )
  97. print(
  98. json.dumps(
  99. {
  100. "run_id": report.run_id,
  101. "segment_id": report.segment_id,
  102. "verdict": report.verdict,
  103. },
  104. ensure_ascii=False,
  105. indent=2,
  106. )
  107. )
  108. if report.verdict != "PASS":
  109. raise SystemExit(1)
  110. if __name__ == "__main__":
  111. main()