Skip to content

Commit 9a47b68

Browse files
committed
refactor: move data gen script into burger-data
1 parent 5c8de8f commit 9a47b68

File tree

7 files changed

+246
-6
lines changed

7 files changed

+246
-6
lines changed

packages/burger-api/package.json

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,10 @@
2929
"js-yaml": "^4.1.0"
3030
},
3131
"devDependencies": {
32-
"@genaiscript/runtime": "^2.2.1",
3332
"@types/js-yaml": "^4.0.9",
3433
"@types/node": "^20",
3534
"azure-functions-core-tools": "^4",
3635
"azurite": "^3",
37-
"concurrently": "^9",
38-
"rimraf": "^6.0.1",
39-
"sharp": "^0.34.1",
40-
"typescript": "^5",
41-
"zod": "^3.24.2"
36+
"typescript": "^5"
4237
}
4338
}

packages/burger-data/.env.sample

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Set genaiscript models to use
2+
GENAISCRIPT_DEFAULT_MODEL="azure:gpt-4.1"
3+
GENAISCRIPT_IMAGE_MODEL="azure:gpt-image-1"
4+
5+
# Set Azure OpenAI API credentials
6+
# Note: API key is optional if using a managed identity for the text model,
7+
# but required for the image model.
8+
AZURE_OPENAI_API_ENDPOINT="https://<your_azure_instance>.openai.azure.com/"
9+
AZURE_OPENAI_API_KEY="<your_azure_api_key>"

packages/burger-data/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
data/

packages/burger-data/package.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "burger-data",
3+
"version": "1.0.0",
4+
"description": "GenAI scripts to generate menu data for Contoso Burgers",
5+
"private": true,
6+
"scripts": {
7+
"generate:burgers": "npx -y genaiscript@latest run generate-burgers",
8+
"generate:images": "npx -y genaiscript@latest run generate-images",
9+
"clean": "rimraf data",
10+
"copy": "cp -R data/* ../burger-api/data"
11+
},
12+
"author": "Microsoft",
13+
"license": "MIT",
14+
"devDependencies": {
15+
"@genaiscript/runtime": "^2",
16+
"genaiscript": "^2",
17+
"sharp": "^0.34.1"
18+
}
19+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
genaiscript.d.ts
2+
tsconfig.json
3+
jsconfig.json
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// This script uses GenAIScript (https://aka.ms/genaiscript)
2+
// to generate the menu for a burger restaurant.
3+
import { z } from "@genaiscript/runtime";
4+
5+
const role = `## Role
6+
You're a renowned chef with a passion for creating amazing burgers. You have a deep knowledge of American cuisine and international flavors that appeal to diverse customers.`;
7+
8+
// ----------------------------------------------------------------------------
9+
// Generate burger menu
10+
11+
export const burgerSchema = z.object({
12+
id: z.string(),
13+
name: z.string(),
14+
description: z.string(),
15+
price: z.number(),
16+
imageUrl: z.string(),
17+
toppings: z.array(z.string()),
18+
});
19+
export const burgerMenuSchema = z.array(burgerSchema);
20+
21+
const { text: burgers } = await runPrompt((_) => {
22+
const schema = _.defSchema("SCHEMA", burgerMenuSchema);
23+
_.$`${role}
24+
25+
## Task
26+
You have to create a selection of 10 burgers for a burger restaurant. The menu should include a variety of flavors and styles, including classic American burgers, gourmet specialty burgers, and international fusion burgers. Each burger should have a name, description, and a list of toppings. The menu must include options for vegetarian, vegan and gluten-free burgers.
27+
28+
## Output
29+
The output should be an array of JSON objects that conforms to the following schema:
30+
${schema}
31+
32+
Use simple, incremental ID numbers starting from 1 for each burger.
33+
ImageUrl should be an empty string for now, as the images will be added later.
34+
`;
35+
});
36+
37+
// ----------------------------------------------------------------------------
38+
// Generate toppings
39+
40+
export const toppingSchema = z.object({
41+
id: z.string(),
42+
name: z.string(),
43+
description: z.string(),
44+
price: z.number(),
45+
imageUrl: z.string(),
46+
category: z.enum([
47+
"vegetable",
48+
"meat",
49+
"cheese",
50+
"sauce",
51+
"bun",
52+
"extras",
53+
]),
54+
});
55+
export const toppingMenuSchema = z.array(toppingSchema);
56+
const { text: toppings } = await runPrompt((_) => {
57+
const burgerMenu = def("BURGERS", burgers, { language: "json" });
58+
const schema = _.defSchema("SCHEMA", toppingMenuSchema);
59+
_.$`${role}
60+
61+
## Task
62+
You have to create a selection of toppings for a burger restaurant. The toppings must include all the ones already used in the ${burgerMenu}, as well as a few extra ones to cover all the categories if needed. Bun are not considered toppings, but rather part of the burger itself.
63+
64+
## Output
65+
The output should be an array of JSON objects that conforms to the following schema:
66+
${schema}
67+
68+
Use simple, incremental ID numbers starting from 1 for each topping.
69+
ImageUrl should be an empty string for now, as the images will be added later.
70+
`;
71+
});
72+
73+
// ----------------------------------------------------------------------------
74+
// Replace toppings with their IDs in burgers
75+
76+
const { text: finalBurgers } = await runPrompt((_) => {
77+
const burgerMenu = _.def("BURGERS", burgers, { language: "json" });
78+
const toppingMenu = _.def("TOPPINGS", toppings, { language: "json" });
79+
const schema = _.defSchema("SCHEMA", burgerMenuSchema);
80+
_.$`${role}
81+
82+
## Task
83+
For each burger in the ${burgerMenu}, replace the toppings with their IDs from the ${toppingMenu}. The output should be a valid JSON array of burgers, where each burger has a list of topping IDs instead of names.
84+
85+
## Output
86+
The output should be an array of JSON objects that conforms to the following schema:
87+
${schema}
88+
`;
89+
});
90+
91+
// ----------------------------------------------------------------------------
92+
// Sanity check
93+
94+
const parsedBurgers = burgerMenuSchema.parse(JSON.parse(finalBurgers));
95+
const parsedToppings = toppingMenuSchema.parse(JSON.parse(toppings));
96+
const toppingIds = new Set(parsedToppings.map((topping) => topping.id));
97+
98+
for (const burger of parsedBurgers) {
99+
// Check that all toppings are valid
100+
for (const topping of burger.toppings) {
101+
if (!toppingIds.has(topping)) {
102+
throw new Error(`Invalid topping ID ${topping} in burger ${burger.name}`);
103+
}
104+
}
105+
// Check that the burger has at least one topping
106+
if (burger.toppings.length === 0) {
107+
throw new Error(`Burger ${burger.name} has no toppings`);
108+
}
109+
// Check that the burger has a valid price
110+
if (burger.price <= 0) {
111+
throw new Error(`Burger ${burger.name} has an invalid price`);
112+
}
113+
}
114+
115+
// ----------------------------------------------------------------------------
116+
// Save files
117+
118+
await workspace.writeText("data/burgers.json", finalBurgers);
119+
await workspace.writeText("data/toppings.json", toppings);
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// This script uses GenAIScript (https://aka.ms/genaiscript)
2+
// to generate the images for the menu. Be warned that this script can be quite
3+
// long to run, as it will generate about 40 images.
4+
//
5+
// Note that you need to set GENAISCRIPT_IMAGE_MODEL env variable to either
6+
// "openai:gpt-image-1" or "azure:gpt-image-1" to run this script.
7+
8+
import burgersData from '../data/burgers.json' with { type: "json" };
9+
import toppingsData from '../data/toppings.json' with { type: "json" };
10+
import sharp from 'sharp';
11+
12+
const toppingsMap = new Map(
13+
toppingsData.map((topping) => [topping.id, topping.name]),
14+
);
15+
16+
const role = `## Role
17+
You're an expert prompt engineer with a deep understanding of AI image generation capabilities and a renowned food photographer. You have a passion for creating visually stunning images that capture the essence of food and culinary art. You know how to create prompts that generate high-quality and realistic pictures with rich details of food and ingredients.`;
18+
19+
// ----------------------------------------------------------------------------
20+
// Generate burger images
21+
22+
for (const burger of burgersData) {
23+
const { id, name, description } = burger;
24+
const toppings = burger.toppings.map((id) => toppingsMap[id]);
25+
const imageUrl = `burger-pic-${id}.jpg`;
26+
const imagePath = `data/images/${imageUrl}`;
27+
burger.imageUrl = imageUrl;
28+
29+
// Skip if the image already exists
30+
const exists = await workspace.stat(imagePath);
31+
if (exists) continue;
32+
33+
const { text: burgerPrompt } = await runPrompt((_) => {
34+
_.$`${role}
35+
36+
## Task
37+
You have to create a prompt for Dall-E 3 or gpt-image-1 to generate a realistic photograph of the burger specified below, as if it was taken by a professional food photographer to illustrate a restaurant menu. Do not add any text or logo in the image.
38+
39+
## Burger
40+
- Name: ${name}
41+
- Description: ${description}
42+
- Toppings: ${toppings.join(", ")}
43+
44+
## Output
45+
Write only the prompt, without any additional text or explanation.`;
46+
});
47+
48+
const { image } = await generateImage(burgerPrompt, {
49+
size: "1024x1024",
50+
style: "natural",
51+
});
52+
53+
await sharp(image.filename).resize(512, 512).jpeg({ quality: 60 }).toFile(imagePath);
54+
}
55+
56+
// ----------------------------------------------------------------------------
57+
// Generate toppings images
58+
59+
for (const topping of toppingsData) {
60+
const { id, name, description } = topping;
61+
const imageUrl = `topping-pic-${id}.jpg`;
62+
const imagePath = `data/images/${imageUrl}`;
63+
topping.imageUrl = imageUrl;
64+
65+
// Skip if the image already exists
66+
const exists = await workspace.stat(imagePath);
67+
if (exists) continue;
68+
69+
const { text: toppingPrompt } = await runPrompt((_) => {
70+
_.$`${role}
71+
72+
## Task
73+
You have to create a prompt for Dall-E 3 or gpt-image-1 to generate a realistic photograph of the burger topping specified below, as if it was taken by a professional food photographer to illustrate a restaurant menu. Feature the ingredient alone, not on a burger. Do not add any text or logo in the image.
74+
75+
## Topping
76+
- Name: ${name}
77+
- Description: ${description}
78+
79+
## Output
80+
Write only the prompt, without any additional text or explanation.`;
81+
});
82+
83+
const { image } = await generateImage(toppingPrompt, {
84+
size: "1024x1024",
85+
style: "natural",
86+
});
87+
88+
await sharp(image.filename).resize(512, 512).jpeg({ quality: 60 }).toFile(imagePath);
89+
}
90+
91+
// ----------------------------------------------------------------------------
92+
// Save updated files with imageUrls
93+
await workspace.writeText("data/burgers.json", JSON.stringify(burgersData, null, 2));
94+
await workspace.writeText("data/toppings.json", JSON.stringify(toppingsData, null, 2));

0 commit comments

Comments
 (0)