feat: add generation trace and partial-ready workflow status

This commit is contained in:
2026-04-18 21:53:55 +08:00
parent 96dfc677e2
commit e99a7fbe14
36 changed files with 2597 additions and 144 deletions

View File

@@ -17,6 +17,9 @@ from app.schemas.story_schemas import (
AchievementItem,
FullStoryResponse,
GenerateRequest,
GenerationJobDetailResponse,
GenerationJobSummaryResponse,
GenerationProviderStatsResponse,
GenerationRequest,
GenerationResponse,
StoryAssetRetryRequest,
@@ -28,6 +31,11 @@ from app.schemas.story_schemas import (
StoryResponse,
)
from app.services import story_service
from app.services.generation_jobs import (
get_generation_job_detail,
get_story_provider_stats,
list_story_generation_jobs,
)
from app.services.memory_service import build_enhanced_memory_context
from app.services.provider_router import (
generate_image,
@@ -65,6 +73,42 @@ async def create_generation(
return await story_service.generate_generation_service(request, user.id, db)
@router.get("/generations/jobs/{job_id}", response_model=GenerationJobDetailResponse)
async def get_generation_job(
job_id: str,
user: User = Depends(require_user),
db: AsyncSession = Depends(get_db),
):
"""Get one generation job with ordered workflow events."""
return await get_generation_job_detail(db, job_id=job_id, user_id=user.id)
@router.get(
"/generations/{story_id}/jobs",
response_model=list[GenerationJobSummaryResponse],
)
async def list_generation_jobs(
story_id: int,
user: User = Depends(require_user),
db: AsyncSession = Depends(get_db),
):
"""List recent generation jobs for a generated story/storybook."""
return await list_story_generation_jobs(db, story_id=story_id, user_id=user.id)
@router.get(
"/generations/{story_id}/provider-stats",
response_model=GenerationProviderStatsResponse,
)
async def get_generation_provider_stats(
story_id: int,
user: User = Depends(require_user),
db: AsyncSession = Depends(get_db),
):
"""Get provider call stats aggregated from generation job events."""
return await get_story_provider_stats(db, story_id=story_id, user_id=user.id)
@router.get("/generations/{story_id}", response_model=StoryDetailResponse)
async def get_generation(
story_id: int,
@@ -135,13 +179,14 @@ async def generate_story_stream(
# Step 1: Generate Content
try:
result = await generate_story_content(
input_type=request.type,
data=request.data,
education_theme=request.education_theme,
memory_context=memory_context,
db=db,
)
result = await generate_story_content(
input_type=request.type,
data=request.data,
education_theme=request.education_theme,
memory_context=memory_context,
user_id=user.id,
db=db,
)
except Exception as e:
logger.error("sse_story_generation_failed", error=str(e))
yield {"event": "story_failed", "data": json.dumps({"error": str(e)})}
@@ -163,6 +208,7 @@ async def generate_story_stream(
"child_profile_id": story.child_profile_id,
"universe_id": story.universe_id,
"generation_status": story.generation_status,
"text_status": story.text_status,
"image_status": story.image_status,
"audio_status": story.audio_status,
"last_error": story.last_error,
@@ -175,7 +221,12 @@ async def generate_story_stream(
await db.commit()
try:
# Direct call to provider router's generate_image, sharing db session
image_url = await generate_image(story.cover_prompt, db=db)
image_url = await generate_image(
story.cover_prompt,
db=db,
user_id=user.id,
story_id=story.id,
)
story.image_url = image_url
sync_story_status(
story,
@@ -188,6 +239,7 @@ async def generate_story_stream(
{
"image_url": image_url,
"generation_status": story.generation_status,
"text_status": story.text_status,
"image_status": story.image_status,
"audio_status": story.audio_status,
"last_error": story.last_error,
@@ -208,6 +260,7 @@ async def generate_story_stream(
{
"error": str(e),
"generation_status": story.generation_status,
"text_status": story.text_status,
"image_status": story.image_status,
"audio_status": story.audio_status,
"last_error": story.last_error,
@@ -221,6 +274,7 @@ async def generate_story_stream(
{
"story_id": story.id,
"generation_status": story.generation_status,
"text_status": story.text_status,
"image_status": story.image_status,
"audio_status": story.audio_status,
"last_error": story.last_error,
@@ -296,6 +350,7 @@ async def generate_story_image(
return {
"image_url": url,
"generation_status": story.generation_status,
"text_status": story.text_status,
"image_status": story.image_status,
"audio_status": story.audio_status,
"last_error": story.last_error,