api.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. from __future__ import annotations
  2. from fastapi import FastAPI, HTTPException
  3. from fastapi.exceptions import RequestValidationError
  4. from fastapi.encoders import jsonable_encoder
  5. from fastapi.responses import JSONResponse
  6. from content_agent.errors import ErrorCode, error_response, sanitize_error_detail
  7. from content_agent.run_service import RunService
  8. from content_agent.schemas import (
  9. JsonFileResponse,
  10. RecordsResponse,
  11. RunStartRequest,
  12. RunStartResponse,
  13. RunSummaryResponse,
  14. ValidationResponse,
  15. )
  16. app = FastAPI(title="Content Agent V1")
  17. service = RunService.from_env()
  18. @app.exception_handler(RequestValidationError)
  19. async def validation_exception_handler(request, exc: RequestValidationError):
  20. return JSONResponse(
  21. status_code=422,
  22. content={
  23. "detail": error_response(
  24. ErrorCode.INVALID_REQUEST,
  25. "invalid request",
  26. {"errors": jsonable_encoder(sanitize_error_detail(exc.errors()))},
  27. )
  28. },
  29. )
  30. @app.post("/runs", response_model=RunStartResponse)
  31. def start_run(request: RunStartRequest) -> RunStartResponse:
  32. state = service.start_run(request)
  33. if state["status"] not in {"success", "partial_success"}:
  34. detail = error_response(
  35. state.get("error_code", ErrorCode.RUN_START_FAILED),
  36. state.get("error_message", "run failed"),
  37. state.get("error_detail", {"errors": state.get("errors", [])}),
  38. )
  39. raise HTTPException(status_code=state.get("http_status_code", 500), detail=detail)
  40. run_id = state["run_id"]
  41. return RunStartResponse(
  42. run_id=run_id,
  43. policy_run_id=state["policy_run_id"],
  44. status=state["status"],
  45. policy_bundle_id=state["policy_bundle_id"],
  46. strategy_version=state["strategy_version"],
  47. platform=state["platform"],
  48. platform_mode=state["platform_mode"],
  49. output_dir=str(service.runtime.run_dir(run_id)),
  50. )
  51. @app.get("/runs/{run_id}", response_model=RunSummaryResponse)
  52. def get_run(run_id: str) -> RunSummaryResponse:
  53. _ensure_run_exists(run_id)
  54. return RunSummaryResponse(**service.get_summary(run_id))
  55. @app.get("/runs/{run_id}/discovered-content-items", response_model=RecordsResponse)
  56. def get_discovered_content_items(run_id: str) -> RecordsResponse:
  57. return _jsonl_response(run_id, "discovered_content_items.jsonl")
  58. @app.get("/runs/{run_id}/rule-decisions", response_model=RecordsResponse)
  59. def get_rule_decisions(run_id: str) -> RecordsResponse:
  60. return _jsonl_response(run_id, "rule_decisions.jsonl")
  61. @app.get("/runs/{run_id}/source-path-records", response_model=RecordsResponse)
  62. def get_source_path_records(run_id: str) -> RecordsResponse:
  63. return _jsonl_response(run_id, "source_path_records.jsonl")
  64. @app.get("/runs/{run_id}/final-output", response_model=JsonFileResponse)
  65. def get_final_output(run_id: str) -> JsonFileResponse:
  66. return _json_response(run_id, "final_output.json")
  67. @app.get("/runs/{run_id}/strategy-review", response_model=JsonFileResponse)
  68. def get_strategy_review(run_id: str) -> JsonFileResponse:
  69. _ensure_run_exists(run_id)
  70. return JsonFileResponse(run_id=run_id, data=service.strategy_review(run_id))
  71. @app.post("/runs/{run_id}/strategy-review/regenerate", response_model=JsonFileResponse)
  72. def regenerate_strategy_review(run_id: str) -> JsonFileResponse:
  73. _ensure_run_exists(run_id)
  74. return JsonFileResponse(run_id=run_id, data=service.regenerate_strategy_review(run_id))
  75. @app.get("/runs/{run_id}/validation", response_model=ValidationResponse)
  76. def get_validation(run_id: str) -> ValidationResponse:
  77. _ensure_run_exists(run_id)
  78. return ValidationResponse(**service.validate_run(run_id))
  79. def _jsonl_response(run_id: str, filename: str) -> RecordsResponse:
  80. _ensure_run_exists(run_id)
  81. return RecordsResponse(run_id=run_id, records=service.read_jsonl(run_id, filename))
  82. def _json_response(run_id: str, filename: str) -> JsonFileResponse:
  83. _ensure_run_exists(run_id)
  84. return JsonFileResponse(run_id=run_id, data=service.read_json(run_id, filename))
  85. def _ensure_run_exists(run_id: str) -> None:
  86. if not service.runtime.run_dir(run_id).exists():
  87. raise HTTPException(
  88. status_code=404,
  89. detail=error_response(
  90. ErrorCode.RUN_NOT_FOUND,
  91. "run not found",
  92. {"run_id": run_id},
  93. ),
  94. )