Skip to content

Commit e21ce6c

Browse files
feat: Add Gemini-powered blog post generation agent example
This commit introduces a new example AI agent capable of generating blog posts by leveraging the Gemini API. Key changes include: - Installed the `google-generativeai` library. - Enhanced `src/a2a/example_agent/agent.py`: - Integrated Gemini API for content generation. - Added new capabilities: `generate_blog_topic`, `generate_blog_outline`, `write_blog_section`, and `assemble_blog_post`. - Reads Gemini API key from the `GEMINI_API_KEY` environment variable. - Updated tests in `tests/server/agent_execution/test_example_agent.py`: - Added tests for the new blog generation capabilities. - Mocked Gemini API calls to ensure tests are isolated and reproducible. - Created `examples/run_blog_generator.py`: - A new script to demonstrate the end-to-end flow of generating a blog post. - Requires `GEMINI_API_KEY` in a `.env` file. - Saves the output to `generated_blog_post.md`. - Added `.env.example` to the repository root. - Updated `README.md` with instructions on how to set up and run the new blog post generation example. The agent continues to use placeholder classes for core A2A SDK components (`Agent`, `AgentCapability`, etc.) as their definitive source within the SDK was not identified. This example provides a more complex, real-world use case for an A2A agent.
1 parent 218fd2e commit e21ce6c

File tree

5 files changed

+502
-48
lines changed

5 files changed

+502
-48
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
GEMINI_API_KEY="your_actual_google_gemini_api_key_here"

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,58 @@ pip install a2a-sdk
6161

6262
You can also find more Python samples [here](https://github.com/google-a2a/a2a-samples/tree/main/samples/python) and JavaScript samples [here](https://github.com/google-a2a/a2a-samples/tree/main/samples/js).
6363

64+
### Blog Post Generation Agent Example
65+
66+
This example demonstrates an agent capable of generating blog posts using the Gemini API.
67+
68+
#### Prerequisites
69+
70+
1. **Install Dependencies**:
71+
Ensure you have all necessary dependencies installed. If you've followed the main installation for `a2a-sdk`, you might also need `python-dotenv` and `google-generativeai`:
72+
```bash
73+
pip install python-dotenv google-generativeai
74+
```
75+
(Note: `google-generativeai` should already be installed if previous steps were followed for this agent, but `python-dotenv` is likely new for this example).
76+
77+
2. **Set up Gemini API Key**:
78+
- Create a `.env` file in the root of this repository by copying the `.env.example` file:
79+
```bash
80+
cp .env.example .env
81+
```
82+
- Edit the `.env` file and replace `"your_actual_google_gemini_api_key_here"` with your actual Gemini API key.
83+
```
84+
GEMINI_API_KEY="your_actual_api_key_here"
85+
```
86+
87+
#### Running the Example
88+
89+
1. Navigate to the `examples` directory (if you are not already there):
90+
```bash
91+
cd examples
92+
```
93+
2. Run the script:
94+
```bash
95+
python run_blog_generator.py
96+
```
97+
The script will:
98+
- Generate a blog topic based on predefined keywords.
99+
- Generate an outline for the topic.
100+
- Write content for each section of the outline.
101+
- Assemble the full blog post.
102+
- Print the final blog post to the console and save it to `generated_blog_post.md` in the `examples` directory (where the script is run).
103+
104+
#### How it Works
105+
106+
The `run_blog_generator.py` script uses the `ExampleAgent` located in `src/a2a/example_agent/agent.py`. This agent has been configured with capabilities to:
107+
- `generate_blog_topic`: Creates a topic.
108+
- `generate_blog_outline`: Structures the blog post.
109+
- `write_blog_section`: Writes content for each section using the Gemini API.
110+
- `assemble_blog_post`: Compiles the sections into a final blog post.
111+
112+
The agent reads the `GEMINI_API_KEY` from the environment variables (loaded from the `.env` file located in the project root or the `examples/` directory).
113+
114+
*Note: The `ExampleAgent` currently uses placeholder classes for some core A2A SDK components (`Agent`, `AgentCapability`, etc.) as they were not found directly within the SDK during development of this example. These placeholders would ideally be replaced by actual SDK components.*
115+
64116
## License
65117

66118
This project is licensed under the terms of the [Apache 2.0 License](https://raw.githubusercontent.com/google-a2a/a2a-python/refs/heads/main/LICENSE).

examples/run_blog_generator.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import asyncio
2+
import os
3+
# It's good practice to handle potential ImportError for dotenv
4+
try:
5+
from dotenv import load_dotenv
6+
except ImportError:
7+
print("python-dotenv library not found. Please install it by running: pip install python-dotenv")
8+
print("This script relies on a .env file to load your GEMINI_API_KEY.")
9+
exit(1)
10+
11+
12+
# Assuming ExampleAgent and TaskParameters are accessible via a2a.
13+
# This might require ensuring src is in PYTHONPATH or the package is installed.
14+
# For direct script execution, you might need to adjust sys.path or set PYTHONPATH.
15+
import sys
16+
# Add src to Python path if running script directly from repo root
17+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../src')))
18+
19+
from a2a.example_agent.agent import ExampleAgent
20+
from a2a.example_agent.agent import TaskParameters # Using the placeholder from agent.py
21+
22+
# MockTaskCompleter to capture results for the script
23+
class ScriptTaskCompleter:
24+
def __init__(self):
25+
self.output = None
26+
self.error = None
27+
self.has_failed = False
28+
29+
def complete_task(self, output: any):
30+
self.output = output
31+
self.error = None
32+
self.has_failed = False
33+
# print(f"Task completed successfully.") # Optional: for verbose logging
34+
35+
def fail_task(self, error_message: str):
36+
self.output = None
37+
self.error = error_message
38+
self.has_failed = True
39+
# print(f"Task failed: {error_message}") # Optional: for verbose logging
40+
41+
async def main():
42+
# Attempt to load .env file from the project root (one level up from examples/)
43+
dotenv_path = os.path.join(os.path.dirname(__file__), '..', '.env')
44+
if os.path.exists(dotenv_path):
45+
load_dotenv(dotenv_path)
46+
print(f"Loaded .env from {dotenv_path}")
47+
else:
48+
# Fallback to trying to load .env from the current directory (examples/)
49+
if load_dotenv():
50+
print(f"Loaded .env from current directory")
51+
else:
52+
print("No .env file found in project root or current directory.")
53+
54+
55+
api_key = os.getenv("GEMINI_API_KEY")
56+
if not api_key:
57+
print("Error: GEMINI_API_KEY environment variable not set or .env file not found/loaded.")
58+
print("Please create a .env file in the project root with your API key:")
59+
print("Example .env content: GEMINI_API_KEY='your_actual_api_key_here'")
60+
return
61+
62+
try:
63+
agent = ExampleAgent() # Initializes with API key from env
64+
except ValueError as e:
65+
print(f"Error initializing agent: {e}")
66+
return
67+
68+
completer = ScriptTaskCompleter()
69+
70+
# 1. Generate Blog Topic
71+
print("\n--- 1. Generating Blog Topic ---")
72+
topic_keywords = ["artificial intelligence in healthcare", "future trends", "patient outcomes"]
73+
print(f"Keywords: {', '.join(topic_keywords)}")
74+
topic_params = TaskParameters(parameters={
75+
"capability_name": "generate_blog_topic",
76+
"keywords": topic_keywords
77+
})
78+
await agent.execute_task(completer, topic_params)
79+
80+
if completer.has_failed:
81+
print(f"Could not generate topic: {completer.error}")
82+
return
83+
blog_topic = completer.output
84+
print(f"Generated Topic: {blog_topic}")
85+
86+
# 2. Generate Blog Outline
87+
print("\n--- 2. Generating Blog Outline ---")
88+
print(f"Using topic: {blog_topic}")
89+
outline_params = TaskParameters(parameters={
90+
"capability_name": "generate_blog_outline",
91+
"topic": blog_topic
92+
})
93+
await agent.execute_task(completer, outline_params)
94+
95+
if completer.has_failed:
96+
print(f"Could not generate outline: {completer.error}")
97+
return
98+
blog_outline = completer.output
99+
if not blog_outline: # Check if outline is empty or None
100+
print(f"Generated outline was empty. Stopping.")
101+
return
102+
103+
print(f"Generated Outline:")
104+
for i, section_title in enumerate(blog_outline):
105+
print(f" {i+1}. {section_title}")
106+
107+
# 3. Write Blog Sections
108+
print("\n--- 3. Writing Blog Sections ---")
109+
written_sections = []
110+
for i, section_title in enumerate(blog_outline):
111+
# Check if section_title is valid
112+
if not section_title or not isinstance(section_title, str) or not section_title.strip():
113+
print(f" Skipping invalid section title at index {i}: '{section_title}'")
114+
written_sections.append(f"Skipped section due to invalid title: '{section_title}'")
115+
continue
116+
117+
print(f" Writing section {i+1}: '{section_title}'...")
118+
section_params = TaskParameters(parameters={
119+
"capability_name": "write_blog_section",
120+
"section_prompt": section_title
121+
# Using default model 'gemini-1.5-flash-latest'
122+
})
123+
await agent.execute_task(completer, section_params)
124+
125+
if completer.has_failed:
126+
error_msg = completer.error if completer.error else "Unknown error"
127+
print(f" Could not write section '{section_title}': {error_msg}")
128+
written_sections.append(f"Content for '{section_title}' could not be generated: {error_msg}")
129+
continue # Continue to next section for now
130+
131+
section_content = completer.output if completer.output else ""
132+
written_sections.append(section_content)
133+
print(f" Section content (first 80 chars): {section_content[:80].replace('\n', ' ')}...")
134+
135+
136+
# 4. Assemble Blog Post
137+
print("\n--- 4. Assembling Blog Post ---")
138+
assembly_params = TaskParameters(parameters={
139+
"capability_name": "assemble_blog_post",
140+
"title": blog_topic, # Using the generated topic as title
141+
"sections": written_sections
142+
})
143+
await agent.execute_task(completer, assembly_params)
144+
145+
if completer.has_failed:
146+
print(f"Could not assemble blog post: {completer.error}")
147+
return
148+
149+
final_blog_post = completer.output
150+
print("\n--- Generated Blog Post ---")
151+
print(final_blog_post)
152+
153+
# Save to file
154+
output_filename = "generated_blog_post.md"
155+
with open(output_filename, "w", encoding="utf-8") as f:
156+
f.write(final_blog_post)
157+
print(f"\nBlog post saved to: {output_filename}")
158+
159+
print("\n\n--- Example Script Finished ---")
160+
print("To run this script again: python examples/run_blog_generator.py")
161+
print("Ensure you have a .env file in the project root (../.env) or in the examples/ directory (./.env) with your GEMINI_API_KEY.")
162+
163+
if __name__ == "__main__":
164+
asyncio.run(main())

0 commit comments

Comments
 (0)