diff --git a/app/infrastructure/tasks/feature_spec_tasks.py b/app/infrastructure/tasks/feature_spec_tasks.py index bd39ff1..f23da99 100644 --- a/app/infrastructure/tasks/feature_spec_tasks.py +++ b/app/infrastructure/tasks/feature_spec_tasks.py @@ -4,13 +4,7 @@ from app.core.database import SessionLocal from app.core.settings import settings from app.infrastructure.celery_app import celery_app -from app.infrastructure.ollama_client import ollama_sync_client -from app.modules.feature_spec.models import FeatureSpecRun -from app.modules.feature_spec.prompts.feature_summary import ( - build_feature_summary_prompt_from_db, - parse_feature_summary_response, -) -from app.modules.feature_spec.schemas import FeatureSummaryResult +from app.modules.feature_spec.orchestrator import execute_feature_spec_run, mark_run_error def _ensure_auth_models_loaded() -> None: @@ -24,61 +18,26 @@ def generate_feature_spec_task(self, run_id: int, feature_idea: str, user_id: in _ensure_auth_models_loaded() db = SessionLocal() try: - run = db.get(FeatureSpecRun, run_id) - if run is None: - return {"run_id": run_id, "status": "error", "message": "Run not found"} - - if run.status == "success" and run.response_json is not None: - return { - "run_id": run.id, - "status": run.status, - "feature_idea": run.feature_idea, - "feature_summary": run.response_json, - } - - try: - prompt = build_feature_summary_prompt_from_db(feature_idea, db) - feature_summary_raw = ollama_sync_client.generate(prompt) - feature_summary_json = parse_feature_summary_response(feature_summary_raw) - feature_summary_typed = FeatureSummaryResult.model_validate(feature_summary_json) - - run.status = "success" - run.response_json = feature_summary_typed.model_dump(mode="json") - run.error_message = None - db.add(run) - db.commit() - - return { - "run_id": run.id, - "status": run.status, - "feature_idea": run.feature_idea, - "feature_summary": run.response_json, - } - except ( - httpx.TimeoutException, - httpx.RequestError, - httpx.HTTPStatusError, - ) as exc: - retry_number = int(self.request.retries) + 1 - if retry_number <= settings.CELERY_TASK_MAX_RETRIES: - delay_seconds = settings.CELERY_TASK_RETRY_BASE_SECONDS**retry_number - try: - raise self.retry(exc=exc, countdown=delay_seconds) - except MaxRetriesExceededError: - pass - - db.rollback() - run.status = "error" - run.error_message = "LLM provider request failed" - db.add(run) - db.commit() - raise RuntimeError("LLM task failed after retries") from exc - except Exception as exc: - db.rollback() - run.status = "error" - run.error_message = "Failed to process feature specification" - db.add(run) - db.commit() - raise RuntimeError("Feature specification task failed") from exc + return execute_feature_spec_run(run_id, feature_idea, db) + except ( + httpx.TimeoutException, + httpx.RequestError, + httpx.HTTPStatusError, + ) as exc: + retry_number = int(self.request.retries) + 1 + if retry_number <= settings.CELERY_TASK_MAX_RETRIES: + delay_seconds = settings.CELERY_TASK_RETRY_BASE_SECONDS**retry_number + try: + raise self.retry(exc=exc, countdown=delay_seconds) + except MaxRetriesExceededError: + pass + + db.rollback() + mark_run_error(db, run_id, "LLM provider request failed") + raise RuntimeError("LLM task failed after retries") from exc + except Exception as exc: + db.rollback() + mark_run_error(db, run_id, "Failed to process feature specification") + raise RuntimeError("Feature specification task failed") from exc finally: db.close() diff --git a/app/modules/feature_spec/orchestrator.py b/app/modules/feature_spec/orchestrator.py index b3f2a30..7d500fa 100644 --- a/app/modules/feature_spec/orchestrator.py +++ b/app/modules/feature_spec/orchestrator.py @@ -2,6 +2,7 @@ from sqlalchemy import select from app.core.settings import settings +from app.infrastructure.ollama_client import ollama_sync_client from app.modules.feature_spec.models import FeatureSpecRun from app.modules.feature_spec.parser import normalize_feature_summary_payload from app.modules.feature_spec.prompts.feature_summary import ( @@ -73,6 +74,47 @@ async def generate_feature_spec( return await orchestrator.generate(feature_idea, db, user_id) +def execute_feature_spec_run(run_id: int, feature_idea: str, db: Session) -> dict: + run = db.get(FeatureSpecRun, run_id) + if run is None: + return {"run_id": run_id, "status": "error", "message": "Run not found"} + + if run.status == "success" and run.response_json is not None: + return { + "run_id": run.id, + "status": run.status, + "feature_idea": run.feature_idea, + "feature_summary": run.response_json, + } + + prompt = build_feature_summary_prompt_from_db(feature_idea, db) + feature_summary_raw = ollama_sync_client.generate(prompt) + feature_summary_json = parse_feature_summary_response(feature_summary_raw) + feature_summary_typed = FeatureSummaryResult.model_validate(feature_summary_json) + + run.status = "success" + run.response_json = feature_summary_typed.model_dump(mode="json") + run.error_message = None + db.add(run) + db.commit() + + return { + "run_id": run.id, + "status": run.status, + "feature_idea": run.feature_idea, + "feature_summary": run.response_json, + } + + +def mark_run_error(db: Session, run_id: int, message: str) -> None: + run = db.get(FeatureSpecRun, run_id) + if run is not None: + run.status = "error" + run.error_message = message + db.add(run) + db.commit() + + def get_feature_spec_history( db: Session, user_id: int,