GitHub Digest¶
A fun example covering a few practical patterns is to create an AI digest of GitHub activity for a given repo.
If you've spent some time messing with AI tools in the Python ecosystem lately, you're probably familiar with Jinja2. Jinja pairs really nicely with LLMs, because you can structure the template in a way that either makes it easy for the LLM to fill in the blanks, or makes it easy for you to fill in the blanks with traditional software and then pass the rendered template to the LLM as a prompt.
Here's an example of the latter:
Writing an epic about the day's events in PrefectHQ/prefect
¶
The AI part is pretty much English:
@ai_fn(
instructions="You are a witty and subtle orator. Speak to us of the day's events."
)
def summarize_digest(markdown_digest: str) -> str:
"""Given a markdown digest of GitHub activity, create a Story that is
informative, entertaining, and epic in proportion to the day's events -
an empty day should be handled with a short sarcastic quip about humans
and their laziness.
The story should capture collective efforts of the project.
Each contributor plays a role in this story, their actions
(issues raised, PRs opened, commits merged) shaping the events of the day.
The narrative should highlight key contributors and their deeds, drawing upon the
details in the digest to create a compelling and engaging tale of the day's events.
A dry pun or 2 are encouraged.
Usernames should be markdown links to the contributor's GitHub profile.
The story should begin with a short pithy welcome to the reader and have
a very short, summarizing title.
""" # noqa: E501 (to make Ruff happy)
... and the rest is just... Python?
import os
import inspect
from datetime import date, datetime, timedelta
from marvin import ai_fn
from my_helpers import (
fetch_contributor_data,
post_slack_message,
YOUR_JINJA_TEMPLATE,
)
async def daily_github_digest(
owner: str = "PrefectHQ",
repo: str = "marvin",
slack_channel: str = "ai-tools",
gh_token_env_var: str = "GITHUB_PAT",
):
since = datetime.utcnow() - timedelta(days=1)
data = await fetch_contributor_data(
token=os.getenv(gh_token_env_var), # load from your secrets manager
owner=owner,
repo=repo,
since=since,
)
markdown_digest = YOUR_JINJA_TEMPLATE.render(
today=date.today(),
owner=owner,
repo=repo,
contributors_activity=data,
)
epic_story = summarize_digest(markdown_digest)
await post_slack_message(
message=epic_story,
channel=slack_channel,
)
if __name__ == "__main__":
import asyncio
asyncio.run(daily_github_digest(owner="PrefectHQ", repo="prefect"))
Tip
I brought some helpers with me to make this easier:
fetch_contributor_data
uses the GitHub API to get a list of contributors and their activitypost_slack_message
uses the Slack API to post a message to a channelYOUR_JINJA_TEMPLATE
is a Jinja template that you can use to render the digest
Find my helpers here
Here's a sample output from August 16th 2023 on the PrefectHQ/prefect
repo:
Greetings, wanderer! Sit, rest your feet, and allow me to regale you with the
heroic deeds of the PrefectHQ/prefect repository on this fateful day, the 16th
of August, 2023.
Our tale begins with the indefatigable jakekaplan, who single-handedly opened
two perceptive Pull Requests: flow run viz v2 and don't run tasks draft-. Not
satisfied, he also valiantly merged five commits, clearing the path for others
to tread. Meanwhile, cicdw, desertaxle, and serinamarie, each merged a single,
but significant commit, contributing their mite to the collective effort.
In the realm of PRs, prefectcboyd, WillRaphaelson, and bunchesofdonald all
unfurled their banners, each opening a PR of their own, like pioneers staking
their claim on the wild frontiers of code.
In the grand theatre of software development, where victory is measured in merged
commits and opened PRs, these individuals stood tall, their actions resonating
through the corridors of GitHub. Their names are etched onto the scroll of this
day, their deeds a testament to their commitment.
So ends the telling of this day's events. Until our paths cross again, wanderer,
may your code compile and your tests always pass!
Full example:¶
The full example, with improvements like caching and observability, can be found here.