feat: move unified generation to background worker

This commit is contained in:
2026-04-19 17:29:37 +08:00
parent 5318de670f
commit 6fb128955f
15 changed files with 632 additions and 285 deletions

View File

@@ -49,6 +49,32 @@ assert_jq() {
fi
}
wait_for_job_story() {
local job_id="$1"
local attempts="${2:-60}"
for ((i=1; i<=attempts; i++)); do
local job_json
job_json="$(get_json "$APP_URL/api/generations/jobs/$job_id")"
if jq -e '.story_id != null' >/dev/null <<<"$job_json"; then
printf '%s\n' "$job_json"
return 0
fi
if jq -e '.is_terminal == true and .story_id == null' >/dev/null <<<"$job_json"; then
echo "Generation job finished without a persisted story: $job_id" >&2
echo "$job_json" | jq '.' >&2
exit 1
fi
sleep 1
done
echo "Timed out waiting for generation job to persist a story: $job_id" >&2
exit 1
}
say "Checking backend health"
curl -fsS "$BACKEND_URL/health" | jq -e '.status == "ok"' >/dev/null
curl -fsS "$ADMIN_BACKEND_URL/health" | jq -e '.status == "ok"' >/dev/null
@@ -70,18 +96,23 @@ story_json="$(post_json "$APP_URL/api/generations" '{
"education_theme": "复盘与成长",
"generate_images": false
}')"
story_id="$(jq -r '.id' <<<"$story_json")"
story_job_id="$(jq -r '.generation_job_id' <<<"$story_json")"
assert_jq "$story_json" '.mode == "generated" and .generation_status == "partial_ready" and .text_status == "ready"' "story should be readable before assets"
assert_jq "$story_json" '.mode == "generated" and .generation_status == "queued" and .text_status == "generating"' "story request should be accepted for background execution"
assert_jq "$story_json" '.generation_job_id != null and .generation_job_id != ""' "story generation should expose a job id"
echo "$story_json" | jq '{generation_job_id,mode,generation_status,text_status}'
say "Waiting for story main record to be persisted"
story_job_json="$(wait_for_job_story "$story_job_id")"
story_id="$(jq -r '.story_id' <<<"$story_job_json")"
story_json="$(get_json "$APP_URL/api/generations/$story_id")"
assert_jq "$story_json" '.mode == "generated" and .generation_status == "partial_ready" and .text_status == "ready"' "story should be readable before assets"
assert_jq "$story_json" '(.retryable_assets | index("image")) != null and (.retryable_assets | index("audio")) != null' "story should expose image/audio as retryable assets"
echo "$story_json" | jq '{id,title,mode,generation_status,image_status,audio_status,retryable_assets}'
say "Checking story generation job events"
story_job_json="$(get_json "$APP_URL/api/generations/jobs/$story_job_id")"
assert_jq "$story_job_json" '.id == "'"$story_job_id"'" and .story_id == '"$story_id"'' "story generation job should be queryable"
assert_jq "$story_job_json" '.progress_percent == 100 and .is_terminal == true' "story generation job should expose progress summary"
assert_jq "$story_job_json" '([.events[].event_type] | index("context_prepared")) != null and ([.events[].event_type] | index("narrative_generated")) != null and ([.events[].event_type] | index("story_saved")) != null' "story generation job should include workflow events"
assert_jq "$story_job_json" '([.events[].event_type] | index("worker_started")) != null and ([.events[].event_type] | index("context_prepared")) != null and ([.events[].event_type] | index("narrative_generated")) != null and ([.events[].event_type] | index("story_saved")) != null' "story generation job should include workflow events"
assert_jq "$story_job_json" '([.events[].event_type] | index("provider_call_succeeded")) != null' "story generation job should include provider call events"
echo "$story_job_json" | jq '{id,status,current_step,events:([.events[].event_type] | unique)}'
@@ -127,18 +158,23 @@ storybook_json="$(post_json "$APP_URL/api/generations" '{
"generate_images": false,
"page_count": 6
}')"
storybook_id="$(jq -r '.id' <<<"$storybook_json")"
storybook_job_id="$(jq -r '.generation_job_id' <<<"$storybook_json")"
assert_jq "$storybook_json" '.mode == "storybook" and .generation_status == "partial_ready" and .text_status == "ready" and .image_status == "not_requested" and (.pages | length) >= 4' "storybook should be readable before images"
assert_jq "$storybook_json" '.mode == "storybook" and .generation_status == "queued" and .text_status == "generating"' "storybook request should be accepted for background execution"
assert_jq "$storybook_json" '.generation_job_id != null and .generation_job_id != ""' "storybook generation should expose a job id"
echo "$storybook_json" | jq '{generation_job_id,mode,generation_status,text_status}'
say "Waiting for storybook main record to be persisted"
storybook_job_json="$(wait_for_job_story "$storybook_job_id")"
storybook_id="$(jq -r '.story_id' <<<"$storybook_job_json")"
storybook_json="$(get_json "$APP_URL/api/generations/$storybook_id")"
assert_jq "$storybook_json" '.mode == "storybook" and .generation_status == "partial_ready" and .text_status == "ready" and .image_status == "not_requested" and (.pages | length) >= 4' "storybook should be readable before images"
assert_jq "$storybook_json" '(.retryable_assets | index("image")) != null and (.retryable_assets | index("audio")) == null' "storybook should expose images as retryable assets"
echo "$storybook_json" | jq '{id,title,mode,generation_status,image_status,audio_status,retryable_assets,pages:(.pages | length)}'
say "Checking storybook generation job events"
storybook_job_json="$(get_json "$APP_URL/api/generations/jobs/$storybook_job_id")"
assert_jq "$storybook_job_json" '.id == "'"$storybook_job_id"'" and .story_id == '"$storybook_id"'' "storybook generation job should be queryable"
assert_jq "$storybook_job_json" '.progress_percent == 100 and .is_terminal == true' "storybook generation job should expose progress summary"
assert_jq "$storybook_job_json" '([.events[].event_type] | index("context_prepared")) != null and ([.events[].event_type] | index("narrative_generated")) != null and ([.events[].event_type] | index("story_saved")) != null' "storybook generation job should include workflow events"
assert_jq "$storybook_job_json" '([.events[].event_type] | index("worker_started")) != null and ([.events[].event_type] | index("context_prepared")) != null and ([.events[].event_type] | index("narrative_generated")) != null and ([.events[].event_type] | index("story_saved")) != null' "storybook generation job should include workflow events"
echo "$storybook_job_json" | jq '{id,status,current_step,events:([.events[].event_type] | unique)}'
say "Checking storybook provider stats"