Skip to content

feat(2397): add AI-generated "What's New" for release notes#2417

Open
ycanales wants to merge 6 commits into
developfrom
cy/2397-whatsnew
Open

feat(2397): add AI-generated "What's New" for release notes#2417
ycanales wants to merge 6 commits into
developfrom
cy/2397-whatsnew

Conversation

@ycanales
Copy link
Copy Markdown
Collaborator

@ycanales ycanales commented May 11, 2026

Issue: #2397

Summary & Context

Adds an AI-generated "What's New" summary to the v3 Boost release detail page (issue #2397). Version model, and gated behind an admin-controlled approval flag.

Changes

Model + migration (versions/models.py, versions/migrations/0027_*.py)

  • Adds whats_new, whats_new_html, whats_new_approved, whats_new_generated_at fields to Version.
  • Adds whats_new_items cached property that parses the LLM's markdown bullets into {title, description} dicts for the v3 highlights card.

Celery pipeline (versions/tasks.py)

  • generate_whats_new task calls OpenRouter (gpt-oss-120b) with a fixed-rubric system prompt (max 5 bullets: new libraries, performance, dependencies, security & reliability, DX). Retries 3× on OpenAIError.
  • save_whats_new writes the markdown + rendered HTML back, leaving whats_new_approved untouched (publish stays manual).
  • dispatch_whats_new(version_pk) chains the two for a single version.

Auto-dispatch on import (versions/releases.py)

  • After store_release_notes_for_version succeeds, queue dispatch_whats_new if the version has no summary yet.

Management command (versions/management/commands/generate_whats_new.py)

  • --all-missing, --version , --force, --dry-run for backfill/regeneration.
  • --validate --limit N runs the prompt synchronously against recent versions and prints output (no DB writes) — satisfies the acceptance criterion to review the prompt against ≥10 past releases before sign-off.

Other

  • Admin: add fieldset, filter, and approve/regenerate actions. save_model re-renders whats_new_html from whats_new markdown on edit.
  • Added render_whats_new_markdown in core.htmlhelper.
  • Updated tests and docs.

Test plan

  • Seed some release notes (Version and RenderedContent objects)
    • Run this helper script:
    • docker compose exec -T web ./manage.py shell < scripts/seed_whats_new_qa.py
    • If it runs successfully, it will output the generate_whats_new command:
    • To run that you need an OpenRouter API key, you can create a free account or I can share my key, no problem.
    • docker compose exec web ./manage.py generate_whats_new --version=boost-1-89-0 --force
    • You'll be able to see the generated text going into the "What's New" section of the version (http://localhost:8000/admin/versions/version/)
image
  • Importing release notes for a version with empty whats_new queues and completes the task
  • Admin Regenerate action clears + repopulates fields and resets approval
  • Detail page hides the section until whats_new_approved=True
  • Migration applies cleanly on a fresh DB

‼️ Risks & Considerations ‼️

  • Drafts are never public until an admin clicks Approve — the view double-checks whats_new_approved.
  • Requires OPENROUTER_API_KEY; without it tasks retry 3× then give up (logged).
  • Do we want to connect to the VersionDetail view and related template in this PR?
  • Added approval process, do we want this or should they be automatically visible once generated?

Screenshots

Command

image

Celery

image

Self-review Checklist

  • Tag at least one team member from each team to review this PR
  • Link this PR to the related GitHub Project ticket

@ycanales ycanales linked an issue May 11, 2026 that may be closed by this pull request
Copy link
Copy Markdown
Collaborator

@julioest julioest left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice work on this! The approval gate, parser tolerance for LLM output drift, and the --validate flag are all great decisions.

I left one inline comment regarding the markdown rendering. Pre-approving! ✅

Comment thread core/htmlhelper.py Outdated
Copy link
Copy Markdown
Collaborator

@herzog0 herzog0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still reviewing as it's a very dense PR, but I figured I should start with these two comments

Comment thread versions/management/commands/generate_whats_new.py Outdated
Comment thread versions/tasks.py
@ycanales
Copy link
Copy Markdown
Collaborator Author

@herzog0 I agree it would more consistent if we reset the approval, thanks for the suggestion! Adjusted.

@ycanales
Copy link
Copy Markdown
Collaborator Author

As per the feedback from today's demo, emojis are no longer used:

2026-05-15 16:49:48 2026-05-15 20:49:48 [INFO] trace.py:128 - Task versions.tasks.generate_whats_new[5feccf36-0e0e-49ed-9bdd-3f43834bb00b] succeeded in 11.917692756047472s: '- **New libraries** – Introduced Bloom filters library, expanding probabilistic data structures support.  
2026-05-15 16:49:48 - **Performance improvements** – Added allocator constructors, timer heap reserve, and metadata optimizations for faster, lower‑memory operations.  
2026-05-15 16:49:48 - **Dependencies** – Provided optional build targets, removed unnecessary headers, and introduced BOOST_LOG_WITHOUT_ASIO to eliminate Asio dependency.  
2026-05-15 16:49:48 - **Security & reliability** – Integrated TSAN instrumentation, fixed ASAN warnings, and resolved high‑CPU and parsing bugs across libraries.  
2026-05-15 16:49:48 - **Developer experience** – Added C++20 module support, constexpr enhancements, and [[noreturn]] attributes for clearer compile‑time behavior.'

Admin view:

image

Copy link
Copy Markdown
Collaborator

@julhoang julhoang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ycanales , this is awesome work!! I love the wide range of command options for testing and covering different cases.

I've left a couple of suggestions inline. Besides those, I'm wondering whether we need the whats_new_html field at all. My understanding is that it gives admin users a friendly preview in the admin panel but isn't actually consumed by the website – the card component will be reading from whats_new_items, right?

If that's the case, I think we should show whats_new_items in Django Admin instead, since that's the true output served to the website. While testing, I removed the bold label from the "New Libraries" item, and the HTML field still rendered that list item – but it failed the regex in whats_new_items and wasn't parsed. An admin editor seeing this would be confused about why the website doesn't match the whats_new_html preview. So, I think we should drop whats_new_html and surface whats_new_items directly. Does that sound reasonable to you?

Image

"--all-missing",
is_flag=True,
default=False,
help="Queue generation for every active version that has no summary yet.",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we revise this help text to mention that the "active version" are only those that have a release note HTML on the Rendered Content page (http://localhost:8000/admin/core/renderedcontent/)?

Maybe something like: "Queue generation for every active version that has stored release notes in Rendered Content page, but no summary yet. Versions without release notes are skipped."

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _html field was to embed it on the site, but I totally missed that we currently have a |markdown templatetag we can use to render, so it's not needed. Good catch!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback, makes sense to me. Adjusted!

Comment thread versions/tasks.py
Comment on lines +702 to +709

release_note_text = _release_note_text(rendered_content)
logger.info(
"generate_whats_new_dispatching",
version_pk=version_pk,
version_name=version.name,
input_chars=len(release_note_text),
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a length check on release_note_text here? I'm not sure how long release notes can get or whether they could overflow the LLM token limit, especially if we're selecting a free model. Probably not likely but I think a guardrail seems worthwhile.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good to have a limit, I've set 100k chars which is close to the model limit of around 131k tokens and leaves space for the prompt and output.

Comment on lines +91 to +94
if version_slug:
qs = qs.filter(slug=version_slug)
elif all_missing:
qs = qs.filter(whats_new="") if not force else qs
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a force check on line 92 as well?

It seems like running this command docker compose exec web ./manage.py generate_whats_new --version=boost-1-89-0 currently overrides the existing content the same way as having --force would.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Behavior updated, it will filter for whats_new="" now unless --force is specified. Thanks!

Comment thread versions/models.py Outdated
Comment on lines +233 to +240
"""Parse `whats_new` markdown bullets into a list of {title, description}
dicts for the v3 release-highlights card.

Accepts the Markdown unordered-list bullets the LLM is instructed
to emit; a leading ``-`` or ``*`` marker is required:
- `- **New libraries** — sentence`
- `* **New libraries:** sentence`
Trailing `:` inside the label is stripped.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we update the documentation here to also say that the bold category label is expected/required by the regex?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I've added it to the docstring 👍

Copy link
Copy Markdown
Collaborator

@herzog0 herzog0 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a couple of nits, but this is working really great!!! Thanks a ton @ycanales.

Comment thread versions/admin.py Outdated
def regenerate_whats_new(self, request, queryset):
queued = 0
for version in queryset:
Version.objects.filter(pk=version.pk).update(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should only clear the fields once the results are in. Say the broker is offline for any reason or an error happens, the field won't be blank.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, now this will only dispatch, and save_whats_new will do the overwrite.

Comment thread versions/tasks.py Outdated
Comment on lines +716 to +721
response = client.chat.completions.create(model=WHATS_NEW_MODEL, messages=messages)
try:
content = response.choices[0].message.content
except (AttributeError, IndexError) as e:
logger.error("generate_whats_new_response_error", error=str(e))
return None
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should include the .create() call in the try/catch to log the error, and then re-throw is its an instance of OpenAIError? (te leverage the autoretry_for option)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, I've adjusted the try/except to cover the API call.

@ycanales ycanales requested a review from julhoang May 20, 2026 17:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Downloads Page: Automate "What's New" Section Generation

4 participants