Create AI-Generated Valentine's Day Poems with Anthropic, SendGrid, Replicate, Exa, and Replit

February 14, 2024
Written by
Reviewed by
Paul Kamp
Twilion
Kevin Leffew
Contributor
Opinions expressed by Twilio contributors are their own

Create AI-Generated Valentine's Day Poems with Anthropic, SendGrid, Replicate, Exa, and Replit

I've been so busy leading up to Valentine's Day, I've had no time to write florid poetry (as I usually do). I decided to use AI to help me generate poetry via a Streamlit web app using Anthropic , Twilio SendGrid , Exa , and Replicate in Python.

Test it out here on Replit or modify the code directly on Replit here !


Prerequisites

Before you can join me in penning poetry with AI, you’ll need to have a few things in place. Register (or log into) some accounts and meet me back here.

Get Started with Replit

Log in to Replit , then click + Create Repl to make a new Repl. A Repl, short for – or maybe based on – a "read-eval-print loop", is an interactive programming environment that lets you write and execute code in real-time.

Create a Repl

Next, select a Python template, give your Repl a title like stephensmithify-openai-vision, set the privacy, and click + Create Repl.

Selecting a Replit Python template and creating a Repl

Under Tools of your new Repl, scroll and click on Secrets.

Click the blue + New Secret button on the right.

Add a new secret

Add three secrets, where the keys are titled ANTHROPIC_API_KEY, SENDGRID_API_KEY, EXA_API_KEY, and REPLICATE_API_TOKEN and their corresponding values are those API keys. Keep them hidden!

Back under Tools, select Packages.

Screenshot of the tools menu

Search and add the following:

  • anthropic@0.15.1
  • replicate@0.23.1
  • exa-py@1.0.8
  • re101@0.4.0
  • streamlit@1.31.0

Now, download this picture of a ghost with hearts. .

Picture of a ghost with a heart

Upload it by selecting the three dots next to Files and then clicking the Upload file button, as shown below.

Next, add a new folder called style.

File browser

In it, add a new file style.css containing the following code:

.stApp > header {
    background-color: transparent;
}
.stApp {
    margin: auto;
    font-family: -apple-system, BlinkMacSystemFont, sans-serif;
    overflow: auto;
    background: linear-gradient(315deg, #4f2991 3%, #7dc4ff 38%, #36cfcc 68%, #a92ed3 98%);
    animation: gradient 15s ease infinite;
    background-size: 400% 400%;
    background-attachment: fixed;
}
a:link, a:visited{
    color: blue;
    background-color: transparent;
    text-decoration: underline;
}
a:hover, a:active {
    color: red;
    background-color: transparent;
    text-decoration: underline;
}
footer {
    margin-top: auto;
    text-align: center;
}

Lastly, open the .replit file and replace it with the following so whenever the app is run, `streamlit run` is also run.

run = "streamlit run --server.address 0.0.0.0 --server.headless true --server.enableWebsocketCompression=false main.py"

modules = ["python-3.10:v18-20230807-322e88b"]

hidden = [".pythonlibs"]

[nix]
channel = "stable-23_05"

[deployment]
run = ["sh", "-c", "streamlit run --server.address 0.0.0.0 --server.headless true --server.enableWebsocketCompression=false main.py"]
deploymentTarget = "cloudrun"

Now we're going to write some Python code to generate poetry with Anthropic's Claude model along with a personalized image with a Replicate-hosted Stable Diffusion model!

Create a Streamlit App to Receive User Input

At the top of the main.py file in Replit, add the following import statements:

from anthropic import Anthropic, HUMAN_PROMPT, AI_PROMPT
import os
from PIL import Image
import re
import replicate
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import (
     Mail)
import streamlit as st

Next, add the CSS styling to our Streamlit web app and create variables for our API Keys which are hidden as Replit's Secrets.

with open('./style/style.css') as f:
    css = f.read()
st.markdown(f'<style>{css}</style>', unsafe_allow_html=True)

SENDGRID_API_KEY = os.environ['SENDGRID_API_KEY']
ANTHROPIC_API_KEY = os.environ['ANTHROPIC_API_KEY']
anthropic = Anthropic(
    api_key=ANTHROPIC_API_KEY
)

Now, create a `main()` function containing the app title and images, plus text boxes and radio buttons to receive user input. The app asks for the poem receiver name, a slider to gauge how much the user likes the receiver, what the receiver is like, what LLM to use, some optional customizable add-ons like a movie quote or lyric, the receiver’s astrology sign, and an email address to send the poem to. After all that, we create two empty variables for the poem and the gift suggestion ideas.

def main():
    st.title("Love Poem Generator w/ Astrology❤️ 💌") 
    st.write("Built w/ Anthropic, SendGrid, Streamlit, && Replicate") 
    image = Image.open('pikalove.png')
    st.image(image)

    receiver_name = st.text_input("Poem receiver name")
    jasonmomoaslider = st.select_slider(
    'How much do you like them on a scale from just a friend to Jason Momoa', options=['just a friend', 'coffee shop crush', 'hot for a coworker', 'top of the roster', 'the love of your life', 'Jason Momoa'])
    st.write("You like this person ", jasonmomoaslider, 'on a scale of 0 to Jason Momoa')
    receiver_description = st.text_area(
    "Describe the person receiving the poem",
    "What do they like?"
    )
    model_toggle = st.radio("What LLM would you like to use", # lol it rhymes
            [":rainbow[llama-2-70b-chat]", "***Claude***"],
            captions = ["Hosted on Replicate", "Thank you, Anthropic"]) 

    addons = st.multiselect(
        'What would you like your poem to include?',
        ['Star Wars quote', 'Shrek quote', 'Taylor Swift lyrics', 'Klay Thompson quote'],
        ['Star Wars quote', 'Shrek quote']
    )

    st.write('You selected:', addons)

    astrology_sign = st.selectbox(
        'What is their astrology sign? ♓️♈️',
        ['Virgo', 'Gemini', 'Leo', 'Libra', 'Sagittarius', 'Taurus', 'Aquarius', 'Aries', 'Capricorn', 'Cancer', 'Scorpio', 'Pisces']
    )
    st.write('You selected:', astrology_sign)

    user_email = st.text_input("Email to send love poem to📧", "lol@gmail.com")
    poem = ''

Generate Personal Gift Ideas with Exa

The next code runs if all of the user input fields are filled and a button is clicked. A Streamlit spinner object is displayed to let the user know the next steps could take some time.

We then make an Exa search, asking it to return three gift ideas for someone based on their astrology sign and how they were described above. You can read more about searching with the Exa API here.

The Exa output is cleaned up with regex and we make a COPY_PROMPT variable to modify pronouns in input text.

if st.button('Generate a poem w/ AI 🧠🤖') and astrology_sign and addons and model_toggle and receiver_name and receiver_description and user_email:
        with st.spinner('Processing📈...'):
	exa = Exa(EXA_API_KEY)
            exa_resp = exa.search(
                f"thoughtful, fun gift for someone who's a {astrology_sign} and is described as {receiver_description}",
                num_results=3,
                start_crawl_date="2024-01-01",
                end_crawl_date="2024-02-14",
            )
            print(exa_resp)

            # regex pattern to extract title, URL, and score
            pattern = r"Title: (.+)\nURL: (.+)\nID: .*\nScore: ([\d.]+)"

            # Find all matches w/ the regex pattern
            matches = re.findall(pattern, str(exa_resp))

            # Iterate over the matches and add the extracted information to an array of gifts
            gifts = []
            for match in matches:
                title, url, score = match
                gifts.append(f'{title.strip()}: {url.strip()}')

            COPY_PROMPT = f"""
              You are a copy editor. Edit the following blurb and return only that edited blurb, ensuring the only pronouns used are "I": {receiver_description}. 
              There should be no preamble.
            """

Make an AI-Generated Poem with Anthropic's Claude Model

If the user selected the Claude model, we generate a poem using Claude's completions API as shown below. You can read more about using Anthropic's Claude's Completions here.

if model_toggle == "***Claude***":
    completion1 = anthropic.completions.create(
        model="claude-instant-1.2",
        max_tokens_to_sample=700,
        prompt=f"{HUMAN_PROMPT}: {COPY_PROMPT}. {AI_PROMPT}",
     )
     print(completion1.completion)
     newPronouns = completion1.completion

     MAIN_PROMPT= f"""
     Please make me laugh by writing a short, silly, lighthearted, complimentary, lovey-dovey   poem that rhymes about the following person named {receiver_name}. 
<receiver_description>{newPronouns}</receiver_description>. 
I would enjoy it if the poem also jokingly included the common characteristics of a person that has the astrological sign of {astrology_sign}
                    and include {addons}. 
Return only the poem.
    """
    completion = anthropic.completions.create(
        model="claude-2.1",
        max_tokens_to_sample=1000,
        prompt=f"{HUMAN_PROMPT}: {MAIN_PROMPT}. {AI_PROMPT}",
    )
    newpoem = completion.completion
    print(newpoem)
    st.markdown(f'Generated poem:  {newpoem}')


If the user selected using the LLaMA 2 model, the following code would be run instead. You can read more about using LLaMA 2 hosted on Replicate here.

elif model_toggle == ":rainbow[llama-2-70b-chat]":
                editpronouns = replicate.run(
               "meta/llama-2-70b-chat:02e509c789964a7ea8736978a43525956ef40397be9033abf9fd2badfe68c9e3",
                    input={
                        "prompt": COPY_PROMPT,
                        "max_new_tokens": 700
                    }
                )
                newpronounsblurb = ''
                for item in editpronouns:
                    newpronounsblurb+=item 
                    print(item, end="")
                print("newpronounsblurb ", newpronounsblurb)

                MAIN_PROMPT= f"""
                With no preamble, please make me laugh by writing a short, silly, lighthearted, complimentary, lovey-dovey poem that rhymes about the following person named {receiver_name}. 
<receiver_description>{newpronounsblurb}</receiver_description>. 
I would enjoy it if the poem also jokingly included the common characteristics of a person that has the astrological sign of {astrology_sign}
                and something about {addons}. 
                Return only the poem. 
"""
                poem = replicate.run(
                    "meta/llama-2-70b-chat:02e509c789964a7ea8736978a43525956ef40397be9033abf9fd2badfe68c9e3",
                    input={
                        "prompt": MAIN_PROMPT,
                        "max_new_tokens": 1000
                    }
                )
                newpoem = ''
                for item in poem:
                    newpoem+=item
                    print(item, end="")
                print("newpoem ", newpoem)

                st.markdown(f'The generated poem: {newpoem}')

To accompany the AI-generated poem, create an image with Stable Diffusion.

output_pic = replicate.run(
                "stability-ai/stable-diffusion:ac732df83cea7fff18b8472768c88ad041fa750ff7682a21affe81863cbe77e4",
                input={
                    "prompt": f"Please generate a G-rated cute image of a {astrology_sign} including hearts that I can show my manager",
                    "width": 448,
                    "height": 448,
                    "negative_prompt": "nsfw",
                }
            )
            print(output_pic[0])

You can view the generated image at the URL printed out.

Send the generated poem and image as an email with SendGrid

Now, what should you do with this poem and image? Send them via email using SendGrid in Python with the code below, of course!

 

message = Mail(
                from_email='love@poem.com',
                to_emails=user_email,
                subject='Personal poem for you!❤️',
                html_content=f'''
                <img src="{output_pic[0]}"</img>
                <p>{newpoem}</p>
                <p> ❤️😘🥰</p>
                '''
            )

            sg = SendGridAPIClient(api_key=SENDGRID_API_KEY)
            response = sg.send(message)
            print(response.status_code, response.body, response.headers)
            if response.status_code == 202:
                st.success("Email sent! Tell your ✨friend✨ to check their email for their poem and image")
                print(f"Response Code: {response.status_code} \n Email sent!")
            else:
                st.warning("Email not sent--check console")
    else:
        st.write("Check that you filled out each textbox and selected something for each question!")
```

Lastly, add this footer to the bottom of your Streamlit page and we run the code with `if __name__ == "__main__"`.`


```python
footer="""
    <footer>
        <p>Developed with ❤ in SF🌁</p> 
        <p>✅ out the code on <a href="https://github.com/elizabethsiegle/valentinesday-poem-generator-sendgrid-anthropic-replicate-replit-exa" target="_blank">GitHub</a></p>
    </footer>
    """
    st.markdown(footer,unsafe_allow_html=True)

if __name__ == "__main__":
    main() 

The complete code can be found here on GitHub.

What's Next for Anthropic, SendGrid, Replicate, and Replit?

So much fun can be had with AI and LLMs! Models like Anthropic's Claude let developers create personalized stories, poems, and more–and it's wild how the better your prompt is, the better the output can be. Model hosts such as Replicate make it straightforward to use a variety of models such as Stable Diffusion. Besides generating images, you can also generate videos or edit images!

Let me know online what you're building with AI–check out these related Twilio tutorials to Generate an AI Competition Report with Exa, Anthropic, and Twilio SendGrid and Create an AI Commentator with GPT-4V, OpenAI TTS, Replit, LangChain, and SendGrid!