Skip to content

Commit 9e29cd0

Browse files
committed
Add script for generating new titles/descriptions for snippet metadata.
1 parent ca76286 commit 9e29cd0

File tree

5 files changed

+198
-0
lines changed

5 files changed

+198
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Provide a 'title', 'title_abbrev', and 'description' for this example in json format.
2+
Title should be a title, title_abbrev should be a 1-5 word variation on the title, and description should
3+
explain in one paragraph what the code is going. Provide the json raw, without markdown fences.
4+
5+
Sample result:
6+
7+
{
8+
"title": "Get an object by name from an Amazon S3 bucket",
9+
"title_abbrev": "Get an object",
10+
"description": "Use the AWS SDK for JavaScript to get an object from an Amazon S3 bucket. Steps are included that demonstrate how to split large downloads up into multiple parts."
11+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Example output of the snippet_summarize script for IAM policies
2+
```
3+
[
4+
{
5+
"title": "Allows Amazon SNS to send messages to an Amazon SQS dead-letter queue",
6+
"title_abbrev": "Allows SNS to SQS messages",
7+
"description": "This resource-based policy allows Amazon SNS to send messages to a specific Amazon SQS dead-letter queue. The policy grants the SNS service principal permission to perform the SQS:SendMessage action on the queue named [MyDeadLetterQueue], but only when the source ARN matches the specified SNS topic [MyTopic]. This policy would be attached to the SQS queue to enable it to receive messages from the SNS topic when message delivery fails. Replace [us-east-2] with your specific AWS Region."
8+
},
9+
{
10+
"title": "Allows managing log delivery and related resources",
11+
"title_abbrev": "Allows log delivery management",
12+
"description": "This identity-based policy allows managing CloudWatch Logs delivery configurations and related resources. The policy grants permissions to create, get, update, delete, and list log deliveries, as well as manage resource policies for a specific log group [SampleLogGroupName]. It also allows creating service-linked roles, tagging Firehose delivery streams, and managing bucket policies for a specific S3 bucket [bucket-name]. Replace [region] and [account-id] with your specific AWS Region and account ID, and [bucket-name] with your actual S3 bucket name."
13+
},
14+
{
15+
"title": "Allows Amazon SNS to send messages to an SQS dead-letter queue",
16+
"title_abbrev": "SNS to SQS permissions",
17+
"description": "This resource-based policy allows Amazon SNS to send messages to a specific Amazon SQS queue that serves as a dead-letter queue. The policy grants the SNS service permission to perform the SQS:SendMessage action on the queue named [MyDeadLetterQueue] in the [us-east-2] Region. The policy includes a condition that restricts this permission to messages originating from the SNS topic named [MyTopic] in the same Region and account."
18+
}
19+
]
20+
```
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import logging
2+
from functools import wraps
3+
import random
4+
import time
5+
from typing import Callable, Type, Tuple
6+
7+
logger = logging.getLogger(__name__)
8+
9+
10+
def retry_with_backoff(
11+
exceptions: Tuple[Type[Exception], ...],
12+
max_retries: int = 5,
13+
initial_delay: float = 1.0,
14+
backoff_factor: float = 2.0,
15+
max_delay: float = 60.0,
16+
jitter: float = 0.1,
17+
) -> Callable:
18+
"""
19+
Decorator for retrying a function with exponential backoff upon specified exceptions.
20+
21+
:param exceptions: Tuple of exception classes to catch.
22+
:param max_retries: Maximum number of retry attempts.
23+
:param initial_delay: Initial delay between retries in seconds.
24+
:param backoff_factor: Factor by which the delay increases after each retry.
25+
:param max_delay: Maximum delay between retries in seconds.
26+
:param jitter: Random jitter added to delay to prevent thundering herd problem.
27+
:return: Decorated function with retry logic.
28+
29+
This method was AI generated, and reviewed by a human.
30+
"""
31+
32+
def decorator(func: Callable) -> Callable:
33+
@wraps(func)
34+
def wrapper(*args, **kwargs):
35+
delay = initial_delay
36+
for attempt in range(1, max_retries + 1):
37+
try:
38+
return func(*args, **kwargs)
39+
except exceptions as e:
40+
if attempt == max_retries:
41+
logger.error(
42+
f"Function '{func.__name__}' failed after {max_retries} attempts."
43+
)
44+
raise
45+
else:
46+
sleep_time = min(delay, max_delay)
47+
sleep_time += jitter * (2 * random.random() - 1) # Add jitter
48+
logger.warning(
49+
f"Attempt {attempt} for function '{func.__name__}' failed with {e.__class__.__name__}: {e} "
50+
f"Retrying in {sleep_time:.2f} seconds..."
51+
)
52+
time.sleep(sleep_time)
53+
delay *= backoff_factor
54+
55+
return wrapper
56+
57+
return decorator
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#!/usr/bin/env python
2+
3+
import argparse
4+
import json
5+
import logging
6+
import os
7+
from pathlib import Path
8+
from typing import Dict, Optional
9+
10+
import boto3
11+
from botocore.exceptions import ClientError
12+
13+
from aws_doc_sdk_examples_tools.doc_gen import DocGen, Snippet
14+
from aws_doc_sdk_examples_tools.scripts.retry import retry_with_backoff
15+
16+
17+
logging.basicConfig(level=logging.INFO)
18+
logger = logging.getLogger(__name__)
19+
20+
21+
class BedrockRuntime:
22+
def __init__(self, model_id: str = "us.anthropic.claude-3-7-sonnet-20250219-v1:0"):
23+
self.client = boto3.client("bedrock-runtime")
24+
self.model_id = model_id
25+
self.base_prompt = Path(
26+
os.path.dirname(__file__), "base_prompt.txt"
27+
).read_text()
28+
self.conversation = [{"role": "user", "content": [{"text": self.base_prompt}]}]
29+
30+
def converse(self, conversation):
31+
self.conversation.extend(conversation)
32+
response = self.client.converse(
33+
modelId=self.model_id,
34+
messages=self.conversation,
35+
inferenceConfig={"maxTokens": 512, "temperature": 0.5, "topP": 0.9},
36+
)
37+
response_text = response["output"]["message"]["content"][0]["text"]
38+
return response_text
39+
40+
41+
def make_doc_gen(root: Path):
42+
doc_gen = DocGen.from_root(root)
43+
doc_gen.collect_snippets()
44+
return doc_gen
45+
46+
47+
@retry_with_backoff(exceptions=(ClientError,), max_retries=10)
48+
def generate_snippet_description(
49+
bedrock_runtime: BedrockRuntime, snippet: Snippet, prompt: Optional[str]
50+
) -> Dict:
51+
content = (
52+
[{"text": prompt}, {"text": snippet.code}]
53+
if prompt
54+
else [{"text": snippet.code}]
55+
)
56+
conversation = [
57+
{
58+
"role": "user",
59+
"content": content,
60+
}
61+
]
62+
63+
response_text = bedrock_runtime.converse(conversation)
64+
65+
try:
66+
# This assumes the response is JSON, which couples snippet
67+
# description generation to a specific prompt.
68+
return json.loads(response_text)
69+
except Exception as e:
70+
logger.warning(f"Failed to parse response. Response: {response_text}")
71+
return {}
72+
73+
74+
def generate_descriptions(snippets: Dict[str, Snippet], prompt: Optional[str]):
75+
runtime = BedrockRuntime()
76+
results = []
77+
for snippet_id, snippet in snippets.items():
78+
response = generate_snippet_description(runtime, snippet, prompt)
79+
results.append(response)
80+
# Just need a few results for the demo.
81+
if len(results) == 3:
82+
break
83+
print(results)
84+
85+
86+
def main(doc_gen_root: Path, prompt: Optional[Path]):
87+
doc_gen = make_doc_gen(doc_gen_root)
88+
prompt_text = prompt.read_text() if prompt and prompt.exists() else None
89+
generate_descriptions(doc_gen.snippets, prompt_text)
90+
91+
92+
if __name__ == "__main__":
93+
parser = argparse.ArgumentParser(
94+
description="Generate new titles and descriptions for DocGen snippets"
95+
)
96+
parser.add_argument(
97+
"--doc-gen-root", required=True, help="Path to DocGen ready project"
98+
)
99+
parser.add_argument(
100+
"--prompt",
101+
help="Path to an additional prompt to be used for refining the output",
102+
)
103+
args = parser.parse_args()
104+
105+
doc_gen_root = Path(args.doc_gen_root)
106+
prompt = Path(args.prompt) if args.prompt else None
107+
main(doc_gen_root, prompt)

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
black==24.3.0
2+
boto3==1.37.38
3+
botocore==1.37.38
24
flake8==6.1.0
35
mypy==1.8.0
46
mypy-extensions==1.0.0
57
pathspec==0.11.2
68
pytest==8.0.0
79
PyYAML==6.0.1
810
requests==2.32.0
11+
types-boto3==1.37.38
912
types-PyYAML==6.0.12.12
1013
yamale==4.0.4

0 commit comments

Comments
 (0)