Skip to content

Commit 493c35a

Browse files
authored
Merge pull request #6 from thibaultyou/feature/ai-powered-metadata-and-prompt-organization
♻️ Restructure library and improve metadata generation
2 parents dd8b724 + a97402a commit 493c35a

File tree

21 files changed

+734
-268
lines changed

21 files changed

+734
-268
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
You are an AI assistant tasked with analyzing an AI prompt and producing specific outputs related to it. The prompt will be provided to you, and you should generate the following:
22

3-
1. A filename for storing the prompt as a markdown file
4-
2. A list of tags
5-
3. A one-line concise description
6-
4. A quick description
7-
5. A markdown link for referencing the prompt
8-
6. A commit message for version control
3+
1. A directory name for storing the prompt
4+
2. A category in snake_case format
5+
3. A list of tags
6+
4. A one-line concise description
7+
5. A quick description
8+
6. A markdown link for referencing the prompt
99
7. A list of variables that require user input
1010

1111
Here's the AI prompt you need to analyze:
@@ -16,16 +16,18 @@ Here's the AI prompt you need to analyze:
1616

1717
Now, follow these steps to generate the required outputs:
1818

19-
1. Filename:
20-
Generate a filename for the prompt using the following convention:
19+
1. Directory name:
20+
Generate a directory name for the prompt using the following convention:
2121

2222
- Convert the prompt's main topic or purpose to lowercase
2323
- Replace spaces with underscores
2424
- Remove any special characters
25-
- Add the .md extension
26-
- The filename should be concise but descriptive, ideally not exceeding 50 characters
25+
- The directory name should be concise but descriptive, ideally not exceeding 50 characters
2726

28-
2. Tags:
27+
2. Category:
28+
Determine a simple and clear category for the prompt, formatted in snake_case.
29+
30+
3. Tags:
2931
Create a list of 3-5 relevant tags for the prompt. These tags should:
3032

3133
- Be single words or short phrases
@@ -34,48 +36,37 @@ Create a list of 3-5 relevant tags for the prompt. These tags should:
3436
- Accurately represent the main themes or applications of the prompt
3537
- Be useful for categorizing and searching for the prompt
3638

37-
3. One-line description:
39+
4. One-line description:
3840
Write a concise, one-line description of the prompt that:
3941

4042
- Captures the main purpose or function of the prompt
4143
- Is no longer than 100 characters
4244
- Starts with a verb in the present tense (e.g., "Creates," "Generates," "Analyzes")
4345

44-
4. Quick description:
46+
5. Quick description:
4547
Provide a brief description of the prompt that:
4648

4749
- Expands on the one-line description
4850
- Explains the key features or capabilities of the prompt
4951
- Is 2-3 sentences long
5052
- Gives the reader a clear understanding of what the prompt does
5153

52-
5. Markdown link:
54+
6. Markdown link:
5355
Create a markdown link that can be used to reference the prompt:
5456

5557
- Use the one-line description as the link text
56-
- Use the filename as the link URL
57-
- Format it as: [One-line description](filename)
58-
59-
6. Commit message:
60-
Create a commit message for version control with the following format:
61-
62-
- Start with an emoji that relates to the content or purpose of the prompt
63-
- Follow with a short, descriptive message about the addition or change
64-
- Use present tense and imperative mood
65-
- Keep it under 50 characters if possible
66-
Example: "✨ Add AI prompt analyzer and output generator"
58+
- Use the directory name as the link URL
59+
- Format it as: [One-line description](directory_name)
6760

6861
7. User input variables:
6962
List all variables in the prompt that require user input or replacement. These should be in the format {{VARIABLE_NAME}} and listed one per line.
7063

7164
Present your final output in the following format:
7265

7366
<output>
74-
## metadata.yml
75-
76-
```yml
7767
title: [Prompt's main topic or purpose]
78-
category: [Your determined category]
68+
category: [Your determined category in snake_case]
69+
directory: [Your generated directory name]
7970
tags:
8071
- [Tag 1]
8172
- [Tag 2]
@@ -87,17 +78,6 @@ variables:
8778
- "{{VARIABLE_1}}"
8879
- "{{VARIABLE_2}}"
8980
[Add more variables if necessary]
90-
additional_info:
91-
filename: [Your generated filename]
92-
commit_message: [Your commit message]
93-
```
94-
95-
## prompt.md
96-
97-
```md
98-
[The provided prompt]
99-
```
100-
10181
</output>
10282

10383
Remember to be accurate, concise, and consistent in your analysis and output generation.

.github/scripts/generate_metadata.py

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import hashlib
2+
import logging
3+
import os
4+
import shutil
5+
import yaml
6+
from anthropic import Anthropic
7+
8+
# Set up logging
9+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
10+
logger = logging.getLogger(__name__)
11+
12+
# Path to the analyzer prompt file
13+
ANALYZER_PROMPT_PATH = '.github/prompts/ai_prompt_analyzer_and_output_generator/prompt.md'
14+
15+
def load_analyzer_prompt():
16+
"""Load the content of the analyzer prompt file."""
17+
logger.info(f"Loading analyzer prompt from {ANALYZER_PROMPT_PATH}")
18+
with open(ANALYZER_PROMPT_PATH, 'r') as f:
19+
content = f.read()
20+
logger.info(f"Analyzer prompt loaded, length: {len(content)} characters")
21+
return content
22+
23+
def generate_metadata(prompt_content):
24+
"""Generate metadata for a given prompt content using the Anthropic API."""
25+
logger.info("Starting metadata generation")
26+
27+
# Check for the presence of the API key
28+
api_key = os.environ.get("ANTHROPIC_API_KEY")
29+
if not api_key:
30+
logger.error("ANTHROPIC_API_KEY is not set in the environment.")
31+
raise EnvironmentError("ANTHROPIC_API_KEY is not set in the environment.")
32+
else:
33+
logger.info("ANTHROPIC_API_KEY is set.")
34+
35+
# Initialize the Anthropic client
36+
client = Anthropic(api_key=api_key)
37+
logger.info("Anthropic client initialized")
38+
39+
# Load the analyzer prompt
40+
analyzer_prompt = load_analyzer_prompt()
41+
42+
# Create a message using the Anthropic API
43+
logger.info("Sending request to Anthropic API")
44+
message = client.messages.create(
45+
model="claude-3-5-sonnet-20240620",
46+
max_tokens=1000,
47+
messages=[
48+
{
49+
"role": "user",
50+
"content": analyzer_prompt.replace("{{PROMPT}}", prompt_content)
51+
}
52+
]
53+
)
54+
logger.info("Received response from Anthropic API")
55+
56+
# Log the structure of the response
57+
logger.info(f"Response structure: {type(message)}")
58+
logger.info(f"Content structure: {type(message.content)}")
59+
60+
# Extract the YAML content from the AI response
61+
content = message.content[0].text if isinstance(message.content, list) else message.content
62+
logger.info(f"Extracted content: {content[:100]}...") # Log first 100 characters
63+
64+
output_start = content.find("<output>")
65+
output_end = content.find("</output>")
66+
if output_start != -1 and output_end != -1:
67+
yaml_content = content[output_start + 8:output_end].strip()
68+
logger.info(f"Extracted YAML content: {yaml_content[:100]}...") # Log first 100 characters
69+
metadata = yaml.safe_load(yaml_content)
70+
logger.info("YAML content parsed successfully")
71+
else:
72+
logger.error("Could not find metadata output in AI response")
73+
raise ValueError("Could not find metadata output in AI response")
74+
75+
logger.info("Metadata generation completed successfully")
76+
return metadata
77+
78+
def should_update_metadata(prompt_file, metadata_file):
79+
"""Check if metadata should be updated based on content hash."""
80+
# Generate hash of the prompt file content
81+
with open(prompt_file, 'rb') as f:
82+
prompt_content = f.read()
83+
prompt_hash = hashlib.md5(prompt_content).hexdigest()
84+
85+
# If metadata file doesn't exist, update is needed
86+
if not os.path.exists(metadata_file):
87+
logger.info(f"Metadata file {metadata_file} does not exist. Update needed.")
88+
return True, prompt_hash
89+
90+
# Read the stored hash from metadata file
91+
with open(metadata_file, 'r') as f:
92+
metadata_content = f.read()
93+
94+
# Extract the stored hash
95+
stored_hash_line = next((line for line in metadata_content.split('\n') if line.startswith('content_hash:')), None)
96+
97+
if stored_hash_line:
98+
stored_hash = stored_hash_line.split(':', 1)[1].strip()
99+
100+
# Compare the hashes
101+
if prompt_hash != stored_hash:
102+
logger.info(f"Content hash mismatch for {prompt_file}. Update needed.")
103+
return True, prompt_hash
104+
else:
105+
# If no hash found in metadata, update is needed
106+
logger.info(f"No content hash found in {metadata_file}. Update needed.")
107+
return True, prompt_hash
108+
109+
logger.info(f"Content hash match for {prompt_file}. No update needed.")
110+
return False, prompt_hash
111+
112+
def update_metadata_hash(metadata_file, new_hash):
113+
"""Update or append the content hash in the metadata file."""
114+
if os.path.exists(metadata_file):
115+
with open(metadata_file, 'r') as f:
116+
lines = f.readlines()
117+
118+
# Update or add the hash line
119+
hash_updated = False
120+
for i, line in enumerate(lines):
121+
if line.startswith('content_hash:'):
122+
lines[i] = f'content_hash: {new_hash}\n'
123+
hash_updated = True
124+
break
125+
126+
if not hash_updated:
127+
lines.append(f'content_hash: {new_hash}\n')
128+
129+
with open(metadata_file, 'w') as f:
130+
f.writelines(lines)
131+
logger.info(f"Content hash updated in {metadata_file}")
132+
else:
133+
# If metadata file doesn't exist, create it with the hash
134+
with open(metadata_file, 'w') as f:
135+
f.write(f'content_hash: {new_hash}\n')
136+
logger.info(f"New metadata file created with content hash: {metadata_file}")
137+
138+
def update_prompt_metadata():
139+
"""Update metadata for all prompts in the 'prompts' directory."""
140+
logger.info("Starting update_prompt_metadata process")
141+
prompts_dir = 'prompts'
142+
143+
# Process the main prompt.md file in the prompts directory
144+
main_prompt_file = os.path.join(prompts_dir, 'prompt.md')
145+
if os.path.exists(main_prompt_file):
146+
logger.info("Processing main prompt.md file")
147+
with open(main_prompt_file, 'rb') as f:
148+
prompt_content = f.read()
149+
metadata = generate_metadata(prompt_content.decode('utf-8'))
150+
new_dir_name = metadata['directory']
151+
new_dir_path = os.path.join(prompts_dir, new_dir_name)
152+
153+
# Create new directory and move the prompt file
154+
logger.info(f"Creating new directory: {new_dir_path}")
155+
os.makedirs(new_dir_path, exist_ok=True)
156+
new_prompt_file = os.path.join(new_dir_path, 'prompt.md')
157+
logger.info(f"Moving {main_prompt_file} to {new_prompt_file}")
158+
shutil.move(main_prompt_file, new_prompt_file)
159+
160+
# Save metadata
161+
metadata_path = os.path.join(new_dir_path, 'metadata.yml')
162+
logger.info(f"Saving metadata to {metadata_path}")
163+
with open(metadata_path, 'w') as f:
164+
yaml.dump(metadata, f, sort_keys=False)
165+
166+
# Update content hash
167+
new_hash = hashlib.md5(prompt_content).hexdigest()
168+
update_metadata_hash(metadata_path, new_hash)
169+
170+
# Process subdirectories
171+
for item in os.listdir(prompts_dir):
172+
item_path = os.path.join(prompts_dir, item)
173+
174+
if os.path.isdir(item_path):
175+
logger.info(f"Processing directory: {item}")
176+
prompt_file = os.path.join(item_path, 'prompt.md')
177+
metadata_file = os.path.join(item_path, 'metadata.yml')
178+
179+
if os.path.exists(prompt_file):
180+
should_update, new_hash = should_update_metadata(prompt_file, metadata_file)
181+
if should_update:
182+
logger.info(f"Updating metadata for {item}")
183+
with open(prompt_file, 'r') as f:
184+
prompt_content = f.read()
185+
186+
metadata = generate_metadata(prompt_content)
187+
new_dir_name = metadata['directory']
188+
189+
# Rename directory if necessary
190+
if new_dir_name != item:
191+
new_dir_path = os.path.join(prompts_dir, new_dir_name)
192+
logger.info(f"Renaming directory from {item} to {new_dir_name}")
193+
if os.path.exists(new_dir_path):
194+
logger.warning(f"Directory {new_dir_name} already exists. Updating contents.")
195+
for file in os.listdir(item_path):
196+
src = os.path.join(item_path, file)
197+
dst = os.path.join(new_dir_path, file)
198+
if os.path.isfile(src):
199+
shutil.copy2(src, dst)
200+
shutil.rmtree(item_path)
201+
else:
202+
os.rename(item_path, new_dir_path)
203+
item_path = new_dir_path # Update item_path for the new location
204+
205+
# Save updated metadata
206+
metadata_path = os.path.join(item_path, 'metadata.yml')
207+
logger.info(f"Saving updated metadata to {metadata_path}")
208+
with open(metadata_path, 'w') as f:
209+
yaml.dump(metadata, f, sort_keys=False)
210+
211+
# Update content hash
212+
update_metadata_hash(metadata_path, new_hash)
213+
else:
214+
logger.info(f"Metadata for {item} is up to date")
215+
else:
216+
logger.warning(f"No prompt.md file found in {item_path}")
217+
218+
logger.info("update_prompt_metadata process completed")
219+
220+
if __name__ == '__main__':
221+
logger.info("Script started")
222+
try:
223+
update_prompt_metadata()
224+
logger.info("Script completed successfully")
225+
except Exception as e:
226+
logger.exception(f"An error occurred: {str(e)}")
227+
raise

.github/scripts/requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
pyyaml==6.0.2
2-
jinja2==3.1.4
2+
jinja2==3.1.4
3+
anthropic==0.34.2

0 commit comments

Comments
 (0)