Build a Mood Story Generator with Claude
Call the Claude API to generate mood-based stories and manage multi-turn conversations.
Introduction
30 Second Summary
Every story carries a mood. What if you could type a single word and have an AI generate a vivid story that perfectly captures that feeling?
In this project, you will build a command-line story generator that talks to an LLM through the Claude API. You will control the model's personality with system prompts and manage multi-turn conversations so you can ask for rewrites and continuations.
What You'll Build
Type "melancholy" into your terminal and watch a rain-soaked story appear in seconds, then ask for a darker ending and see the model weave it seamlessly into the same narrative.
By the end of this project, you'll have:
- A Python script that turns any mood you type into a unique, vivid story in your terminal within seconds.
- Full control over the model's writing style through a system prompt, the core skill of prompt engineering.
- A multi-turn conversation loop where you ask for rewrites, continuations, or style changes and the model remembers everything you discussed.
- Secret Mission: Add a genre selector that dynamically modifies the system prompt and save your generated stories to text files.
Are there any prerequisites?
No previous experience with AI or APIs is needed. This project walks you through everything from creating your account to writing your first script.
Before We Start
Before diving in, let's lock in what you're about to build and why it matters.
Set Up Your Environment
Your story generator needs a way to communicate with Claude. That means getting access to the Anthropic API and installing the tools that let your Python code send requests to it.
In this step, you'll create an Anthropic account, generate an API key, and set up a Python project with the SDK installed. By the end, you'll have everything wired up to make your first call.
In this step, get ready to:
- Create an Anthropic Console account and generate an API key.
- Set up a project folder with a virtual environment and the Anthropic SDK.
- Verify the SDK loads correctly in Python.
Create an Anthropic Console account and get your API key
To call Claude from your code, you need an API key. This key authenticates every request your script sends so Anthropic knows who is calling.
- Open your web browser and go to platform.claude.com.
- Sign up for a new account (or sign in if you already have one).
- After signing in, click Settings in the left sidebar.
- Click API Keys.
- Create a new key and give it a name like story-generator.
- Copy the key value immediately and save it somewhere safe, like a notes app or a password manager.
What is an API key?
An API key is a unique string that identifies your application to a service. Think of it like a password for your code. Anthropic checks the key on every request to verify your identity and track usage.
You will not be able to see this key again
Anthropic only shows the full key once, at the moment of creation. If you lose it, you will need to delete the old key and create a new one.
What about costs?
New Anthropic accounts receive a small amount of free API credits for testing. This project uses Claude Haiku 4.5, the most affordable model, so your total usage will cost pennies at most.
Create your project folder and install the SDK
Now that you have your key, you need a project folder and the Anthropic Python SDK installed. The SDK is a Python package that handles the details of talking to the API for you.
- Press Cmd+Space to open your search bar.
- Type Terminal and press Enter to open it.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir story-generator
cd story-generator
- Confirm you're in the right place by running:
pwd
You should see a path ending in story-generator.
- Create a Python virtual environment and activate it by running these commands:
python3 -m venv venv
source venv/bin/activate
Your terminal prompt now starts with (venv), confirming the virtual environment is active.
What is a virtual environment?
A virtual environment is an isolated Python workspace. It keeps this project's packages separate from your system Python, preventing version conflicts between different projects.
- Install the Anthropic SDK by running:
pip install anthropic
You should see output listing downloaded packages, ending with a line that includes Successfully installed anthropic.
Seeing an error during install?
- Make sure your virtual environment is active. You should see (venv) at the start of your terminal prompt. If not, run source venv/bin/activate again.
- Check that you are inside the story-generator folder by running pwd.
Still stuck? Help me troubleshoot the pip install anthropic error.
Set your API key and verify the SDK
The Anthropic SDK automatically reads your API key from an environment variable called ANTHROPIC_API_KEY. Setting it this way keeps the key out of your code.
- Set your API key as an environment variable by running this command (replace your-api-key-here with the key you copied earlier):
export ANTHROPIC_API_KEY="your-api-key-here"
Why use an environment variable?
Storing secrets in environment variables keeps them out of your source code. If you ever share your code or push it to GitHub, the key stays private because it only lives in your terminal session.
This environment variable only lasts for the current terminal session. If you close Terminal and reopen it, you will need to set it again.
Before you run the next command, what do you think will happen if the SDK installed correctly and the API key is set?
- Verify everything is working by running:
python3 -c "from anthropic import Anthropic; print('SDK ready!')"
You should see SDK ready! printed with no errors. This confirms the SDK is installed and Python can import it.
Not seeing SDK ready?
- If you see ModuleNotFoundError, the SDK is not installed in your active environment. Make sure (venv) appears in your prompt, then run pip install anthropic again.
- If you see a syntax error, check that the quotes in the command match exactly. The outer quotes are double quotes and the inner quotes around SDK ready! are single quotes.
Still stuck? Help me debug why python3 cannot import the anthropic SDK.
Your environment is locked and loaded. Next up, you'll write your first script and watch Claude generate a story from your code.
Make Your First LLM Call
Your environment is ready. The SDK is installed, your API key is set, and your terminal is open inside the story-generator folder. Now it's time to write code that actually talks to an LLM.
Every AI application starts with the same atomic pattern: send a message, get a response. In this step, you will put that pattern in your hands by sending your first message to Claude and watching it generate a story right in your terminal.
In this step, get ready to:
- Write a Python script that sends a message to Claude and prints the generated story.
- Inspect the response metadata to understand how tokens and API costs work.
Write a script that sends a message to Claude
The Claude API follows a simple request/response pattern. You send a message, the model generates text, and the API returns it. Your first script will do exactly that.
- Create a file called first_call.py inside your story-generator folder by running this command:
touch first_call.py
- Confirm the file was created by running:
ls
You should see first_call.py listed alongside your virtual environment folder.
- Open first_call.py in a text editor and paste in the following code:
from anthropic import Anthropic
def main():
# Create a client that reads your API key automatically
client = Anthropic()
# Send a message to Claude Haiku 4.5
message = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Tell me a one-paragraph story about a robot discovering music for the first time.",
}
],
)
# Print the story Claude generated
print(message.content[0].text)
if __name__ == "__main__":
main()
What does this code do?
- Anthropic() creates a client that automatically reads your ANTHROPIC_API_KEY environment variable. No need to pass the key in code.
- client.messages.create() sends your message to the model and waits for a complete response.
- model="claude-haiku-4-5" selects Claude Haiku 4.5, Anthropic's fastest and most affordable model.
- max_tokens=1024 sets the maximum number of tokens the model can generate. The model may stop earlier if it finishes the story.
- message.content[0].text extracts the generated text from the response. The API returns content as an array of blocks, and the story text lives in the first block.
- Save first_call.py.
- Run the script by entering this command in your terminal:
python3 first_call.py
Claude generates a one-paragraph story about a robot discovering music and prints it directly in your terminal. Every time you run it, the story will be slightly different.
Seeing an authentication error?
- Make sure your ANTHROPIC_API_KEY environment variable is still set in this terminal session. If you opened a new terminal tab, you need to export it again.
- Double check that your API key was copied correctly from the Anthropic Console, with no extra spaces.
- Verify your virtual environment is still activated. You should see (venv) or similar at the start of your terminal prompt.
Why isn't my script connecting to the Claude API?
Examine the response to understand tokens
Your script printed a story, but the API response contains more than just text. It also tells you exactly how many tokens the request consumed. Tokens are the units that LLMs use to measure text. They are also what determines your API costs.
- In first_call.py, find this line:
print(message.content[0].text)
- Add the following lines directly below it:
print()
print(f"Input tokens: {message.usage.input_tokens}")
print(f"Output tokens: {message.usage.output_tokens}")
print(f"Stop reason: {message.stop_reason}")
What are these fields?
- input_tokens is how many tokens your message cost to send. This is the text you wrote, measured in the model's units.
- output_tokens is how many tokens the model generated in its response. Output tokens cost more than input tokens.
- stop_reason tells you why the model stopped generating. A value of end_turn means the model finished naturally. If you see max_tokens instead, the response was cut short because it hit your limit.
How much does this cost?
Claude Haiku 4.5 costs $1 per million input tokens and $5 per million output tokens. A typical run of this script uses a few hundred tokens total, which costs a fraction of a cent. New Anthropic accounts also receive free credits to get started.
- Save first_call.py.
Before you run the script again, what do you expect to see below the story this time?
- Run the updated script:
python3 first_call.py
You should see a generated story followed by three new lines: the input token count, the output token count, and a stop reason of end_turn. The end_turn value confirms that Claude finished the story on its own, rather than being cut off by your max_tokens limit.
Not seeing token counts?
- Make sure the new print() lines are inside the main() function, indented to the same level as the existing print(message.content[0].text) line.
- Check that the f-strings use curly braces around the variable names, e.g. {message.usage.input_tokens}.
Help me debug my token usage output in first_call.py.
✔️ Awesome, I've got everything!
Great. Double check that you have saved first_call.py.
ⓧ I'd like to double check the full code
Here is the complete first_call.py at this point. Compare it with yours and fix any differences.
from anthropic import Anthropic
def main():
client = Anthropic()
message = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "Tell me a one-paragraph story about a robot discovering music for the first time.",
}
],
)
print(message.content[0].text)
print()
print(f"Input tokens: {message.usage.input_tokens}")
print(f"Output tokens: {message.usage.output_tokens}")
print(f"Stop reason: {message.stop_reason}")
if __name__ == "__main__":
main()
You just made your first LLM API call. You sent a message, got a creative response, and saw exactly how many tokens it cost. But you had no control over the story's style or personality. In the next step, you will use system prompts to shape how Claude writes, turning it from a generic assistant into a mood-based storyteller.
Control the Model with System Prompts
Your first_call.py script proved that Claude can generate a story from a single message. But you had zero control over its style or personality. Without any instructions about how to behave, the model defaults to a generic helpful assistant.
System prompts let you shape HOW the model responds. A system prompt tells Claude who it should be before it reads any user message. In this step, you will write a system prompt that turns Claude into a mood-based storyteller and see the results change dramatically with a single word of input.
In this step, get ready to:
- Create a story_generator.py script with a system prompt that shapes Claude's storytelling behavior.
- Run the script with different moods and compare how the output changes.
Create a mood-based story generator with a system prompt
In first_call.py, you hardcoded the story topic directly in the message. The model had no persona or style instructions. A system prompt sits above the conversation and shapes every response the model generates.
- Create a new file called story_generator.py in your story-generator folder and add the following code:
from anthropic import Anthropic
def main():
client = Anthropic()
# Instructions that tell Claude WHO to be
system_prompt = (
"You are a creative storyteller. The user will give you a mood, "
"and you will write a short, vivid story (2-3 paragraphs) that "
"captures that mood. Use sensory details and evocative language. "
"Do not explain the mood - just embody it in the story."
)
# Ask the user for a mood
mood = input("Enter a mood: ").strip()
# Send the mood to Claude with the system prompt
message = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
system=system_prompt,
messages=[{"role": "user", "content": mood}],
)
print(message.content[0].text)
print()
print(f"Input tokens: {message.usage.input_tokens}")
print(f"Output tokens: {message.usage.output_tokens}")
if __name__ == "__main__":
main()
What does this code do?
- system_prompt is a multi-sentence instruction that tells Claude to act as a creative storyteller who writes vivid, mood-matched stories. This shapes every response the model generates.
- input("Enter a mood: ") pauses the script and waits for the user to type a mood directly in the terminal.
- system=system_prompt passes the system prompt as a top-level parameter on messages.create(). It is NOT a message in the messages array.
- messages=[{"role": "user", "content": mood}] sends just the single mood word as the user's message. Claude combines this with the system prompt to generate a story.
Why is system a separate parameter?
The system prompt is a top-level parameter on messages.create(). There is no "system" role in the messages array. This keeps the system instructions cleanly separated from the conversation between the user and the assistant.
- Save story_generator.py.
- Run the script by running this command:
python3 story_generator.py
- When you see the Enter a mood: prompt, type mysterious and press Enter.
You should see a vivid, atmospheric story full of sensory details. The tone should feel dark and intriguing, with shadowy imagery and suspenseful language.
Not seeing a story?
- If you see ModuleNotFoundError: No module named 'anthropic', make sure your virtual environment is still activated. You should see (venv) at the start of your terminal prompt.
- If the script seems to hang with no prompt, make sure you saved the file before running it. The input() call waits for you to type something and press Enter.
- If you see an authentication error, confirm your ANTHROPIC_API_KEY is still set in this terminal session.
Why isn't my story_generator.py script producing output?
✔️ Awesome, I've got everything!
Great. Make sure you have saved story_generator.py.
ⓧ I'd like to double check the full code
Here is the complete story_generator.py file. Make sure yours matches this exactly.
from anthropic import Anthropic
def main():
client = Anthropic()
system_prompt = (
"You are a creative storyteller. The user will give you a mood, "
"and you will write a short, vivid story (2-3 paragraphs) that "
"captures that mood. Use sensory details and evocative language. "
"Do not explain the mood - just embody it in the story."
)
mood = input("Enter a mood: ").strip()
message = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
system=system_prompt,
messages=[{"role": "user", "content": mood}],
)
print(message.content[0].text)
print()
print(f"Input tokens: {message.usage.input_tokens}")
print(f"Output tokens: {message.usage.output_tokens}")
if __name__ == "__main__":
main()
Run it with different moods and compare the outputs
The system prompt told Claude what kind of storyteller to be. Now test whether different moods actually produce different stories from the same code.
Before you run the script again, think about this. If you type joyful instead of mysterious, do you expect the story's setting, word choice, and tone to change completely, or stay similar?
- Run the script again by running this command:
python3 story_generator.py
- Type joyful and press Enter.
The story should feel warm and celebratory. Compare it to the mysterious story you got earlier. The setting, word choice, and emotional tone should all be different.
- Run the script one more time by running this command:
python3 story_generator.py
- Type melancholy and press Enter.
This time the story should feel heavy and wistful. Three runs, three completely different stories, all from the same code.
Why are the stories so different?
You did not change any code between runs. The system prompt stayed the same. The only thing that changed was the one-word mood you typed.
The system prompt tells Claude what KIND of storyteller to be. The user message tells it what mood to match. This separation between persona instructions and user input is the core pattern of prompt engineering.
Your storyteller can generate wildly different stories from a single word of input. Next up, you will discover a critical limitation. Try asking Claude to rewrite the ending of the story it just gave you, and see what happens.
Experience the Stateless Problem
Your story generator takes a mood, feeds it through a system prompt, and produces a vivid story. Each run proves that the system prompt shapes how Claude writes.
There is one catch. The script exits after a single story. You cannot ask Claude to continue a story or tweak the ending. In this step, you will add a loop so you can keep talking to Claude and see what happens when you ask for changes.
In this step, get ready to:
- Add an input loop so you can keep generating stories without restarting the script.
- Test what happens when you ask Claude to continue or modify a story.
Add an input loop so you can keep typing
Right now, story_generator.py takes one mood, makes one API call, and exits. You need a while True loop that keeps asking for input so you can have an ongoing conversation with Claude.
- In story_generator.py, find this code below the system_prompt definition:
mood = input("Enter a mood: ").strip()
message = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
system=system_prompt,
messages=[{"role": "user", "content": mood}],
)
print(message.content[0].text)
print()
print(f"Input tokens: {message.usage.input_tokens}")
print(f"Output tokens: {message.usage.output_tokens}")
- Replace all of that code (from mood = input(...) down to the last print statement) with the following code:
# Welcome message
print("Mood Story Generator")
print("Type a mood to generate a story, or 'quit' to exit.")
print()
while True:
user_input = input("You: ").strip()
# Let the user exit the loop
if user_input.lower() in ("quit", "exit"):
print("Goodbye!")
break
# Skip empty input
if not user_input:
continue
# Make a fresh API call with only the current message
message = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
system=system_prompt,
messages=[{"role": "user", "content": user_input}],
)
print(f"\n{message.content[0].text}\n")
print(f"[Tokens used: {message.usage.input_tokens} in, {message.usage.output_tokens} out]")
print()
What does this code do?
- while True: creates an infinite loop that keeps prompting for input until the user types quit or exit.
- Each iteration calls messages.create() with only the current user_input as the single message in the messages array.
- The token usage now prints in a compact format so it does not clutter the story output.
- Save story_generator.py.
- Run the script by entering this command in your terminal:
python3 story_generator.py
- Type mysterious at the You: prompt and press Enter.
You should see a story appear, followed by the token count. Then the You: prompt appears again, ready for your next input.
Script exits immediately or shows a syntax error?
- Make sure the entire while True: block is indented inside the main() function. Each line inside the loop needs an extra level of indentation.
- Check that you fully removed the old mood = input(...) block. Leftover lines from the previous version can cause indentation or name errors.
- help me debug a syntax error in my story_generator.py while loop
✔️ Awesome, I've got everything!
Great. Make sure you have saved story_generator.py and keep the script running. You will need it for the next part.
ⓧ I'd like to double check the full code
Your story_generator.py file should have these parts, in order:
- The from anthropic import Anthropic import at the top.
- The def main(): function containing client = Anthropic() and the system_prompt string (unchanged from the previous step).
- Three print() lines for the welcome message, followed by the while True: loop you just added.
- The if __name__ == "__main__": block at the bottom calling main().
Scroll up and confirm each section is present in your file. Make sure the old single-input code is completely gone.
Try a follow-up request and watch it fail
Your script is still running and the You: prompt is waiting for your next message. You just generated a mysterious story. Now you want to ask Claude to change the ending.
Before you type anything, think about this: if you type "now make the ending darker", do you think Claude will modify the story it just wrote, or do something else entirely?
- At the You: prompt, type now write chapter 2 and press Enter.
Claude writes a completely unrelated story. It has no idea what "chapter 2" refers to because it never received your first story or the mood you typed earlier.
Why did Claude forget?
Look at the messages parameter in your code. Each loop iteration sends only [{"role": "user", "content": user_input}]. That is a brand new, single-message conversation every time.
Every call to messages.create() is stateless. The model receives only what you send it in that call. It does not remember anything from previous calls.
- Try one more follow-up. Type now make the ending darker and press Enter.
Once again, Claude produces something completely disconnected from your original story. This is the expected behavior. This is not a bug in your code. It is how the API works.
- Type quit and press Enter to exit the script.
You have just experienced the most common misconception beginners have about LLM APIs. The model does not have memory. Next up, you will fix this by sending the entire conversation history on every call.
Fix It with Multi-Turn Conversations
In Step 4 you saw the problem firsthand. You asked Claude for a sequel and it had no idea what story you were talking about. Each messages.create() call only contained the latest message, so Claude never saw the original story.
The fix is straightforward. Instead of sending a single message, you build a list that accumulates every exchange. Each API call receives the full list, so Claude always sees the complete conversation. The model does not have memory. You are replaying the entire history on every call.
In this step, get ready to:
- Replace single-message API calls with a growing conversation history list.
- Test multi-turn stories by asking for continuations and modifications.
- Print the conversation history length to see the pattern in action.
Replace single messages with a conversation history list
Right now, each iteration of your loop builds a brand-new, one-item messages list. That is why Claude forgets everything between turns. You need a list that lives outside the loop and grows with every exchange.
- In story_generator.py, find this block of print statements inside the main() function:
print("Mood Story Generator")
print("Type a mood to generate a story, or 'quit' to exit.")
print()
- Add a conversation_history list above the print block, and add a new print line so the user knows they can ask for changes. The section should now look like this:
# This list will store every message in the conversation
conversation_history = []
print("Mood Story Generator")
print("Type a mood to generate a story, or 'quit' to exit.")
print("After a story, you can ask for changes or a continuation.")
print()
What does this change?
The conversation_history list is created once, outside the loop. It will persist across iterations so you can accumulate messages over time.
Now for the main change. Inside the while True loop, you need to replace the section that makes the API call and prints the result.
- Find this code inside your while True loop (everything after continue):
message = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
system=system_prompt,
messages=[{"role": "user", "content": user_input}],
)
print(f"\n{message.content[0].text}\n")
print(f"[Tokens used: {message.usage.input_tokens} in, {message.usage.output_tokens} out]")
print()
- Replace that entire section with the following code:
# Add the user's message to the conversation history
conversation_history.append({
"role": "user",
"content": user_input,
})
# Pass the FULL conversation history, not just the latest message
message = client.messages.create(
model="claude-haiku-4-5",
max_tokens=1024,
system=system_prompt,
messages=conversation_history,
)
assistant_text = message.content[0].text
# Add Claude's response so the next call includes it too
conversation_history.append({
"role": "assistant",
"content": assistant_text,
})
print(f"\n{assistant_text}\n")
print(f"[Tokens used: {message.usage.input_tokens} in, {message.usage.output_tokens} out]")
print()
What does this code do?
- Before each API call, you append the user's input as a user message dict to conversation_history.
- The messages parameter now receives conversation_history instead of a single-item list. Claude sees every previous exchange on every call.
- After each API call, you extract the response into assistant_text and append it as an assistant message dict. This ensures the next turn includes Claude's own previous responses.
- Save story_generator.py.
Test multi-turn conversation flow
This is the moment of truth. In Step 4, asking for a continuation produced an unrelated story. Now the model should reference your original story because it receives the full conversation history.
Before you run this, think about what you expect to happen when you type "now make the ending darker" after a story. Will Claude reference the original story, or produce something unrelated again?
- Run your updated script:
python3 story_generator.py
- Type mysterious and press **Enter** to generate a story.
- Read the story that Claude generates.
- Type now make the ending darker and press **Enter**.
Claude rewrites the ending of that specific story. It references characters, settings, and details from the original because your code now sends the full conversation history on every call.
- Try 2-3 more follow-ups to see the conversation build naturally. Ask for a sequel, a different character's perspective, or a twist ending.
- Type quit when you are done.
Why does this work now?
The LLM does not have memory between calls. It "remembers" because you are sending the entire conversation on every request. Each call to messages.create() is still independent and stateless.
This is the fundamental pattern behind every chatbot, agent, and conversational AI application. The client manages the history, not the model.
Claude still ignoring your earlier story?
- Make sure you replaced messages=[{"role": "user", "content": user_input}] with messages=conversation_history. If the old single-item list is still there, the history is being built but never sent.
- Check that the conversation_history.append() calls happen both before AND after the API call. Missing the assistant append means Claude never sees its own prior responses.
- Confirm that conversation_history = [] is defined outside the while True loop. If it is inside the loop, it resets to empty on every iteration.
Still stuck? Help me debug why my conversation history isn't working in story_generator.py. Claude keeps forgetting the previous story.
Print the growing conversation to see the pattern
The conversation works, but you cannot see the mechanism. Adding a single line that prints the history length makes the pattern visible. Each turn adds two messages: one from the user, one from the assistant.
- In story_generator.py, find this line near the bottom of the while True loop:
print(f"[Tokens used: {message.usage.input_tokens} in, {message.usage.output_tokens} out]")
print()
- Add a new print line between those two lines so the section looks like this:
print(f"[Tokens used: {message.usage.input_tokens} in, {message.usage.output_tokens} out]")
print(f"[Conversation history length: {len(conversation_history)} messages]")
print()
- Save story_generator.py.
- Run the script again:
python3 story_generator.py
- Type a mood like joyful and press **Enter**.
After the story, you should see [Conversation history length: 2 messages]. That is one user message and one assistant message.
- Type a follow-up like write chapter 2 and press **Enter**.
The count jumps to 4 messages. It grows by 2 each turn because every exchange adds one user message and one assistant message.
- Type quit to exit.
Why does the token count increase each turn?
Notice that input_tokens grows with each follow-up. That is because you are sending the entire conversation history on every call. More history means more tokens, which means higher cost per request. This is the trade-off of multi-turn conversations.
Production applications handle this by trimming old messages, summarizing the conversation, or setting a maximum history length. For now, the growing count makes the pattern crystal clear.
✔️ Awesome, I've got everything!
Great. Double check you have saved story_generator.py.
ⓧ I'd like to double check the full code
The full file is built from the chunks above. Scroll up and confirm each piece is present in order:
- The from anthropic import Anthropic import at the top.
- The main() function with client = Anthropic() and the system_prompt string.
- conversation_history = [] defined before the print block.
- The three print statements including the new "After a story" line.
- The while True loop with input, quit/exit check, empty-input check.
- User message appended to conversation_history before the API call.
- messages=conversation_history in the messages.create() call.
- assistant_text extracted and appended as an assistant message dict.
- Three print lines at the bottom: the story, token usage, and conversation history length.
- The if __name__ == "__main__": block at the end.
Secret mission
Add Genre Selection and Story Saving
Your story generator creates great mood-based stories, but every story vanishes when the script ends. In this optional mission, you'll add a genre selector that dynamically modifies the system prompt and a save command that writes stories to files with metadata.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on your local machine, with no cloud services left running. The Claude API only charges when you actively make calls, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- story-generator/ folder on your Desktop (includes first_call.py, story_generator.py, the virtual environment, and the stories/ subfolder).
- ANTHROPIC_API_KEY environment variable set in your terminal session.
- API key stored in your Anthropic Console account.
Keep everything running
No action needed. Choose this if you are still actively building or want to keep experimenting with your story generator.
- Your project files stay on your Desktop inside the story-generator/ folder.
- The ANTHROPIC_API_KEY variable only persists in your current terminal session. If you close your terminal, you will need to re-export it when you come back.
- Your API key remains active in the Anthropic Console and can be reused across projects.
Pause - I'll come back to this later
There are no running processes or cloud services to stop. Your project files remain on your Desktop for when you return.
- The ANTHROPIC_API_KEY environment variable disappears automatically when you close your terminal window.
- When you come back, reactivate your virtual environment and re-export the key before running your scripts.
Delete - I don't want to use this again
Remove all project files and credentials from your machine.
- Delete the project folder by running this command in your terminal:
rm -rf ~/Desktop/story-generator
On Windows, run this instead:
rmdir /s /q %USERPROFILE%\Desktop\story-generator
- Unset the API key from your current terminal session by running:
unset ANTHROPIC_API_KEY
- Revoke your API key in the Anthropic Console. Go to platform.claude.com, open Settings > API Keys, and delete the key you created for this project.
- Confirm the folder is gone by running ls ~/Desktop. You should no longer see story-generator in the output.
Nice Work!
Nice Work!
You just built a command-line story generator powered by Claude that turns a single mood word into vivid, creative fiction. Even better, you can keep the conversation going and watch the model weave your feedback into the story.
You've learned how to:
- Send your first LLM API call using the Anthropic Python SDK and print a model-generated story straight from your own code.
- Control the model's personality and output style with system prompts, the core skill of prompt engineering.
- Discover that LLM API calls are stateless and fix it by building multi-turn conversations that replay the full message history on every call.
- Secret Mission: Add a genre selector that dynamically modifies the system prompt and save your generated stories to .txt files with metadata.
Ready to quiz yourself?