Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 22 additions & 63 deletions app/infrastructure/tasks/feature_spec_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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()
42 changes: 42 additions & 0 deletions app/modules/feature_spec/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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,
Expand Down
Loading