Skip to content

Commit 52d0e13

Browse files
committed
Add agent script to update a DocGen instance with json output. (#162)
1 parent 77c493a commit 52d0e13

File tree

6 files changed

+168
-118
lines changed

6 files changed

+168
-118
lines changed
Lines changed: 42 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,74 @@
11
# Ailly Prompt Workflow
22

3-
This project provides two scripts to **generate**, **run**, and **parse** [Ailly](https://www.npmjs.com/package/@ailly/cli) prompts based on your AWS DocGen snippets.
3+
This project automates the process of generating, running, parsing, and applying [Ailly](https://www.npmjs.com/package/@ailly/cli) prompt outputs to an AWS DocGen project. It combines all steps into one streamlined command using a single Python script.
44

5-
## Overview
5+
---
6+
7+
## 📦 Overview
68

7-
1. **Generate** Ailly prompts from DocGen snippets (`make_prompts.py`).
8-
2. **Run** the Ailly CLI on the generated prompts (`npx @ailly/cli`).
9-
3. **Parse** the Ailly outputs back into structured JSON (`parse_json_files.py`).
9+
This tool:
10+
1. **Generates** Ailly prompts from DocGen snippets.
11+
2. **Runs** Ailly CLI to get enhanced metadata.
12+
3. **Parses** Ailly responses into structured JSON.
13+
4. **Updates** your DocGen examples with the new metadata.
14+
15+
All of this is done with one command.
1016

1117
---
1218

13-
## Prerequisites
19+
## Prerequisites
1420

1521
- Python 3.8+
16-
- Node.js & npm (for `npx`)
22+
- Node.js and npm (for `npx`)
23+
- A DocGen project directory
1724

1825
---
1926

20-
## Step 1: Generate Ailly prompts
27+
## 🚀 Usage
2128

22-
Use `make_prompts.py` to create a directory of Markdown files and a `.aillyrc` configuration file.
29+
From your project root, run:
2330

2431
```bash
25-
python make_prompts.py \
26-
--doc-gen-root /path/to/your/docgen/project \
27-
--system-prompts "You are a helpful assistant..." \
28-
--out .ailly_prompts
32+
python -m aws_doc_sdk_examples_tools.agent.bin.main \
33+
/path/to/your/docgen/project \
34+
--system-prompts path/to/system_prompt.txt
2935
```
3036

31-
**Arguments:**
32-
- `--doc-gen-root`: Path to your DocGen project root.
33-
- `--system-prompts`: One or more system prompts, either as strings or file paths.
34-
- `--out`: (Optional) Output directory. Defaults to `.ailly_prompts`.
37+
### 🔧 Arguments
3538

36-
This will:
37-
- Create Markdown files for each snippet.
38-
- Create a `.aillyrc` file with the provided system prompts.
39+
Run `python -m aws_doc_sdk_examples_tools.agent.bin.main --help` for more info.
3940

4041
---
4142

42-
## Step 2: Run Ailly CLI
43-
44-
Run Ailly on the generated prompts. From inside the .ailly_prompts directory:
45-
46-
```bash
47-
npx @ailly/cli --root .ailly_prompts
48-
```
49-
50-
This will create `{file}.ailly.md` output files in the `.ailly_prompts` directory (or whatever output directory you specified).
43+
## 🗂 What This Does
5144

52-
---
45+
Under the hood, this script:
5346

54-
## Step 3: Parse Ailly output
47+
1. Creates a directory `.ailly_iam_policy` containing:
48+
- One Markdown file per snippet.
49+
- A `.aillyrc` configuration file.
5550

56-
After Ailly has generated response files (`*.ailly.md`), parse them into JSON objects using `parse_json_files.py`:
51+
2. Runs `npx @ailly/cli` to generate `.ailly.md` outputs.
5752

58-
```bash
59-
python parse_json_files.py .ailly_prompts/*.ailly.md --out output.json
60-
```
53+
3. Parses the Ailly `.ailly.md` files into a single `iam_updates.json` file.
6154

62-
**Arguments:**
63-
- Positional: List of files to parse (you can use a glob like `*.ailly.md`).
64-
- `--out`: (Optional) Output file path for the resulting JSON array. Defaults to `out.json`.
55+
4. Updates each matching `Example` in the DocGen instance with:
56+
- `title`
57+
- `title_abbrev`
58+
- `synopsis`
6559

6660
---
6761

68-
## Example Full Workflow
62+
## 💡 Example
6963

7064
```bash
71-
# Step 1: Generate prompts
72-
python make_prompts.py \
73-
--doc-gen-root ~/projects/aws-docgen-root \
74-
--system-prompts system_prompt.txt \
75-
--out .ailly_prompts
76-
77-
# Step 2: Run Ailly
78-
npx @ailly/cli --root .ailly_prompts
79-
80-
# Step 3: Parse results
81-
python parse_json_files.py .ailly_prompts/*.ailly.md --out results.json
65+
python -m aws_doc_sdk_examples_tools.agent.bin.main \
66+
~/projects/AWSIAMPolicyExampleReservoir \
67+
--system-prompts prompts/system_prompt.txt
8268
```
69+
70+
This will:
71+
- Write prompts and config to `.ailly_iam_policy/`
72+
- Run Ailly and capture results
73+
- Parse and save output as `.ailly_iam_policy/iam_updates.json`
74+
- Apply updates to your DocGen examples
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from pathlib import Path
2+
from subprocess import run
3+
from typing import List
4+
5+
import typer
6+
7+
from aws_doc_sdk_examples_tools.agent.make_prompts import main as make_prompts
8+
from aws_doc_sdk_examples_tools.agent.parse_json_files import main as parse_json_files
9+
from aws_doc_sdk_examples_tools.agent.update_doc_gen import main as update_doc_gen
10+
11+
app = typer.Typer()
12+
13+
AILLY_DIR = ".ailly_iam_policy"
14+
AILLY_DIR_PATH = Path(AILLY_DIR)
15+
IAM_UPDATES_PATH = AILLY_DIR_PATH / "iam_updates.json"
16+
17+
18+
def get_ailly_files(dir: Path):
19+
return [
20+
file
21+
for file in dir.iterdir()
22+
if file.is_file() and file.name.endswith(".ailly.md")
23+
]
24+
25+
26+
@app.command()
27+
def update(iam_tributary_root: str, system_prompts: List[str] = []):
28+
doc_gen_root = Path(iam_tributary_root)
29+
make_prompts(
30+
doc_gen_root=doc_gen_root, system_prompts=system_prompts, out=AILLY_DIR_PATH
31+
)
32+
run(["npx", "@ailly/cli", "--root", AILLY_DIR])
33+
file_paths = get_ailly_files(AILLY_DIR_PATH)
34+
parse_json_files(file_paths=file_paths, out=IAM_UPDATES_PATH)
35+
update_doc_gen(doc_gen_root=doc_gen_root, iam_updates_path=IAM_UPDATES_PATH)
36+
37+
38+
if __name__ == "__main__":
39+
app()
Lines changed: 28 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
#!/usr/bin/env python
22

3-
import argparse
43
import logging
54
import os
65
from pathlib import Path
7-
from typing import Dict, List
6+
from typing import List
87

98
from aws_doc_sdk_examples_tools.doc_gen import DocGen, Snippet
109

@@ -20,12 +19,23 @@ def make_doc_gen(root: Path) -> DocGen:
2019
return doc_gen
2120

2221

23-
def write_prompts(snippets: Dict[str, Snippet], out: Path) -> None:
24-
"""Write each snippet's code into a separate Markdown file."""
22+
def write_prompts(doc_gen: DocGen, out: Path) -> None:
2523
out.mkdir(parents=True, exist_ok=True)
26-
for snippet_id, snippet in snippets.items():
27-
snippet_path = out / f"{snippet_id}.md"
28-
snippet_path.write_text(snippet.code, encoding="utf-8")
24+
examples = doc_gen.examples
25+
snippets = doc_gen.snippets
26+
for example_id, example in examples.items():
27+
# Postfix with `.md` so Ailly will pick it up.
28+
prompt_path = out / f"{example_id}.md"
29+
# This assumes we're running DocGen specifically on AWSIAMPolicyExampleReservoir.
30+
snippet_key = (
31+
example.languages["IAMPolicyGrammar"]
32+
.versions[0]
33+
.excerpts[0]
34+
.snippet_files[0]
35+
.replace("/", ".")
36+
)
37+
snippet = snippets[snippet_key]
38+
prompt_path.write_text(snippet.code, encoding="utf-8")
2939

3040

3141
def setup_ailly(system_prompts: List[str], out: Path) -> None:
@@ -42,7 +52,7 @@ def setup_ailly(system_prompts: List[str], out: Path) -> None:
4252
aillyrc_path.write_text(content, encoding="utf-8")
4353

4454

45-
def parse_prompts_arg(values: List[str]) -> List[str]:
55+
def read_system_prompts(values: List[str]) -> List[str]:
4656
"""Parse system prompts from a list of strings or file paths."""
4757
prompts = []
4858
for value in values:
@@ -54,38 +64,15 @@ def parse_prompts_arg(values: List[str]) -> List[str]:
5464
return prompts
5565

5666

57-
def main(
58-
doc_gen_root: Path, system_prompts: List[str], out: str = ".ailly_prompts"
59-
) -> None:
60-
"""Generate prompts and configuration files for Ailly."""
61-
out_path = Path(out)
62-
setup_ailly(system_prompts, out_path)
67+
def validate_root_path(doc_gen_root: Path):
68+
assert "AWSIAMPolicyExampleReservoir" in str(doc_gen_root)
69+
assert doc_gen_root.is_dir()
70+
6371

72+
def main(doc_gen_root: Path, system_prompts: List[str], out: Path) -> None:
73+
"""Generate prompts and configuration files for Ailly."""
74+
system_prompts = read_system_prompts(system_prompts)
75+
setup_ailly(system_prompts, out)
76+
validate_root_path(doc_gen_root)
6477
doc_gen = make_doc_gen(doc_gen_root)
65-
write_prompts(doc_gen.snippets, out_path)
66-
67-
68-
if __name__ == "__main__":
69-
parser = argparse.ArgumentParser(
70-
description="Write Ailly prompts for DocGen snippets and parse the results."
71-
)
72-
parser.add_argument(
73-
"--doc-gen-root", required=True, help="Path to a DocGen ready project."
74-
)
75-
parser.add_argument(
76-
"--system-prompts",
77-
nargs="+",
78-
required=True,
79-
help="List of prompt strings or file paths to store in a .aillyrc file.",
80-
)
81-
parser.add_argument(
82-
"--out",
83-
default=".ailly_prompts",
84-
help="Directory where Ailly prompt files will be written. Defaults to '.ailly_prompts'.",
85-
)
86-
87-
args = parser.parse_args()
88-
89-
doc_gen_root = Path(args.doc_gen_root)
90-
system_prompts = parse_prompts_arg(args.system_prompts)
91-
main(doc_gen_root, system_prompts, out=args.out)
78+
write_prompts(doc_gen, out)

aws_doc_sdk_examples_tools/agent/parse_json_files.py

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import argparse
21
import json
32
import logging
43
from os.path import basename
@@ -33,17 +32,16 @@ def extract_json_from_text(text: str) -> Optional[Dict]:
3332
return None
3433

3534

36-
def process_files(file_paths: List[str]) -> Dict[str, Dict]:
35+
def process_files(file_paths: List[Path]) -> Dict[str, Dict]:
3736
results = {}
3837
for path in file_paths:
3938
try:
40-
with open(path, "r", encoding="utf-8") as f:
41-
content = f.read()
39+
content = path.read_text()
4240
json_data = extract_json_from_text(content)
4341
if json_data is not None:
44-
results[basename(path)] = json_data
42+
results[basename(path).replace(".md.ailly.md", "")] = json_data
4543
else:
46-
logger.warning(f"No valid JSON object found in file: {f.name}")
44+
logger.warning(f"No valid JSON object found in file: {path.name}")
4745
except Exception as e:
4846
logger.warning(f"Error processing file {path}: {e}")
4947
return results
@@ -53,25 +51,6 @@ def write_objects(object: Dict, out: Path):
5351
out.write_text(json.dumps(object, indent=2))
5452

5553

56-
def main():
57-
parser = argparse.ArgumentParser(
58-
description="Extract JSON objects from one or more text files."
59-
)
60-
61-
parser.add_argument("files", nargs="+", help="List of files to process")
62-
63-
parser.add_argument(
64-
"--out",
65-
default="out.json",
66-
help="File path where the resultant json will be written.",
67-
)
68-
69-
args = parser.parse_args()
70-
71-
json_objects = process_files(args.files)
72-
out = Path(args.out)
54+
def main(file_paths: List[Path], out: Path):
55+
json_objects = process_files(file_paths)
7356
write_objects(json_objects, out)
74-
75-
76-
if __name__ == "__main__":
77-
main()
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import json
2+
import logging
3+
from pathlib import Path
4+
from typing import Iterable
5+
6+
from aws_doc_sdk_examples_tools.doc_gen import DocGen, Example
7+
8+
# Setup logging
9+
logging.basicConfig(level=logging.INFO)
10+
logger = logging.getLogger(__name__)
11+
12+
13+
def examples_from_updates(updates_path: Path) -> Iterable[Example]:
14+
updates = json.loads(updates_path.read_text())
15+
examples = [
16+
Example(
17+
id=id,
18+
file=None,
19+
languages={},
20+
title=update.get("title"),
21+
title_abbrev=update.get("title_abbrev"),
22+
synopsis=update.get("synopsis"),
23+
)
24+
for id, update in updates.items()
25+
]
26+
return examples
27+
28+
29+
def update_examples(doc_gen: DocGen, examples: Iterable[Example]) -> None:
30+
for example in examples:
31+
if doc_gen_example := doc_gen.examples.get(example.id):
32+
doc_gen_example.title = example.title
33+
doc_gen_example.title_abbrev = example.title_abbrev
34+
doc_gen_example.synopsis = example.synopsis
35+
else:
36+
logger.warning(f"Could not find example with id: {example.id}")
37+
38+
39+
def main(doc_gen_root: Path, iam_updates_path: Path) -> None:
40+
doc_gen = DocGen.from_root(doc_gen_root)
41+
examples = examples_from_updates(iam_updates_path)
42+
update_examples(doc_gen, examples)
43+
print(
44+
[
45+
{
46+
"title": ex.title,
47+
"title_abbrev": ex.title_abbrev,
48+
"synopsis": ex.synopsis,
49+
}
50+
for _, ex in doc_gen.examples.items()
51+
]
52+
)

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ PyYAML==6.0.1
88
requests==2.32.0
99
types-PyYAML==6.0.12.12
1010
yamale==4.0.4
11+
typer==0.15.3

0 commit comments

Comments
 (0)