Skip to content

Commit

Permalink
feat(changelog): add a gen ai summary of the diff
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastien-boulle committed Dec 13, 2024
1 parent e1ea063 commit acd0c2c
Show file tree
Hide file tree
Showing 8 changed files with 507 additions and 1 deletion.
70 changes: 70 additions & 0 deletions changelog_generator/ai_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import os
from google import genai
from google.genai import types

PROMPT = (
"Here's a git diff between two versions (represented as git tags). "
"Write a summary of this diff by following this structure. "
"A TL;DR section with very few lines expressing the salient points of your analysis "
"A Functional Changes section that should be understandable by readers that have no technical knowledge "
"A section about possible regressions that could appear : this section should aim to be brief as it might be used during a production incident, but it musn't be too generic either"
"A section about key kong/log metrics that should be observed during the production release process, these must be very precise"
"The summary must be written with markdown. It should be pleasing and give priority to salient points, must use colors and must use tasteful fonts, and follow best practices. You can use UML schemas and diagrams."
)


def generate_ai_summary(git_diff: str | None) -> str | None:
if not git_diff:
return None

project = os.getenv("VERTEX_PROJECT")
location = os.getenv("VERTEX_LOCATION")
model = os.getenv("VERTEX_MODEL")
credentials = os.getenv("VERTEX_CREDENTIALS")

if not all([project, location, model, credentials]):
return None

client = genai.Client(
vertexai=True,
project=project,
location=location,
credentials=credentials,
)

contents = [
types.Content(
role="user",
parts=[
types.Part.from_text(
f"{PROMPT} {git_diff}"
)
],
)
]
generate_content_config = types.GenerateContentConfig(
temperature=1,
top_p=0.95,
max_output_tokens=8192,
response_modalities=["TEXT"],
safety_settings=[
types.SafetySetting(category="HARM_CATEGORY_HATE_SPEECH", threshold="OFF"),
types.SafetySetting(
category="HARM_CATEGORY_DANGEROUS_CONTENT", threshold="OFF"
),
types.SafetySetting(
category="HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold="OFF"
),
types.SafetySetting(category="HARM_CATEGORY_HARASSMENT", threshold="OFF"),
],
)

res = []
for chunk in client.models.generate_content_stream(
model=model,
contents=contents,
config=generate_content_config,
):
res.append(chunk.text)

return "".join(res)
5 changes: 5 additions & 0 deletions changelog_generator/changelog_template.jinja
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# [{{current_tag}}](https://github.com/{{organization}}/{{repository}}/compare/{{previous_tag}}...{{current_tag}})

{% if ai_summary %}
## AI generated summary
{{ ai_summary }}
{% endif -%}

{% for type_node in commit_trees %}
## {{type_node.commit_type}}

Expand Down
6 changes: 6 additions & 0 deletions changelog_generator/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from .commit import Commit
from .repository_manager import RepositoryManager
from .ai_generator import generate_ai_summary


class CommitTree(NamedTuple):
Expand All @@ -18,6 +19,7 @@ def render_changelog(
previous_tag: str,
current_tag: str,
commit_trees: Sequence[CommitTree],
ai_summary: str | None,
) -> str:
template_loader = FileSystemLoader(searchpath=os.path.dirname(__file__))
template_environment = Environment(loader=template_loader)
Expand All @@ -29,6 +31,7 @@ def render_changelog(
previous_tag=previous_tag,
current_tag=current_tag,
commit_trees=commit_trees,
ai_summary=ai_summary,
)


Expand Down Expand Up @@ -67,15 +70,18 @@ def generate(
if target:
commits = repository.from_target(target)
previous_tag, current_tag = target.split("..")
diff = None
else:
commits = repository.commits_since_last_tag
previous_tag, current_tag = repository.previous_tag, repository.current_tag
diff = repository.get_diff_since_last_tag

changelog = render_changelog(
organization=repository.organization,
repository=repository.name,
previous_tag=previous_tag,
current_tag=current_tag,
commit_trees=get_commit_trees(commits),
ai_summary=generate_ai_summary(diff),
)
return changelog
8 changes: 8 additions & 0 deletions changelog_generator/repository_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ def commits_since_last_tag(self) -> Sequence[Commit]:
revision = self.current_tag
return self._get_commits(revision)

@property
@lru_cache()
def get_diff_since_last_tag(self) -> str | None:
if not self.previous_tag:
return None

return self.repository.git.diff(f"{self.previous_tag}..{self.current_tag}")

def _get_commits(self, revision: str) -> Sequence[Commit]:
options = {"no_merges": True}
if self.filter_paths:
Expand Down
1 change: 1 addition & 0 deletions requirements.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
gitpython
jinja2
google-genai
Loading

0 comments on commit acd0c2c

Please sign in to comment.