Generating Vegan Recipes
Note
To download this example as a Jupyter notebook, click here.
In this example, we will use Guardrails to generate vegan mac and cheese recipe.
Objective
We want to generate a vegan Mac-n-Cheese recipe as a list of ingredients and instructions. We will use Guardrails to make sure the recipe is vegan.
Step 1: Create the RAIL Spec
Ordinarily, we would create an RAIL spec in a separate file. For the purposes of this example, we will create the spec in this notebook as a string following the RAIL syntax. For more information on RAIL, see the RAIL documentation. We will also show the same RAIL spec in a code-first format using a Pydantic model.
First we define a custom Validator:
from guardrails.validators import Validator, register_validator, ValidationResult, PassResult, FailResult
from typing import Dict, Any
NON_VEGAN_INGREDIENTS = ["butter", "milk", "eggs", "cheese", "cream", "yogurt"]
SUBSTITUTIONS = {
"butter": "margarine",
"milk": "soy milk",
"eggs": "flax eggs",
"cheese": "vegan cheese",
"cream": "soy cream",
"yogurt": "soy yogurt",
}
@register_validator(name="is-vegan", data_type="string")
class IsVegan(Validator):
def validate(self, value: Any, metadata: Dict) -> ValidationResult:
global NON_VEGAN_INGREDIENTS, SUBSTITUTIONS
# Make sure the ingredient is not in the list of non-vegan ingredients.
if value.lower() in NON_VEGAN_INGREDIENTS:
return FailResult(
error_message=f"Value ${value} is not vegan.",
# Programmatically fix the value by replacing it with a vegan
# substitute.
fix_value=SUBSTITUTIONS[value.lower()],
)
return PassResult()
Next we can define our RAIL spec either as a XML string:
rail_str = """
<rail version="0.1">
<output>
<list description="What are the ingredients for the recipe?" name="ingredients">
<object>
<integer format="1-indexed" name="index"></integer>
<string format="is-vegan" name="name" on-fail-is-vegan="fix"></string>
<string description="Suggested brand for the ingredient (if any)" name="brand"></string>
<bool description="Is the ingredient necessary?" name="optional"></bool>
<float format="units-imperial" name="quantity"></float>
<string format="units-imperial" name="units"></string>
</object>
</list>
<list description="What are the instructions for the recipe?" name="instructions">
<object>
<integer format="1-indexed" name="index"></integer>
<string name="step"></string>
</object>
</list>
</output>
<prompt>
Generate a recipe for vegan mac and cheese.
${gr.complete_json_suffix}
</prompt>
</rail>
"""
or a Pydantic model:
from pydantic import BaseModel, Field
from typing import List
prompt = """
Generate a recipe for vegan mac and cheese.
${gr.complete_json_suffix}
"""
class Ingredient(BaseModel):
index: int = Field(validators=[("1-indexed", "noop")])
name: str = Field(validators=[IsVegan(on_fail="fix")])
brand: str = Field(description="Suggested brand for the ingredient (if any)")
optional: bool = Field(description="Is the ingredient necessary?")
quantity: float = Field(validators=[("units-imperial", "noop")])
units: str = Field(validators=[("units-imperial", "noop")])
class Instruction(BaseModel):
index: int = Field(validators=[("1-indexed", "noop")])
step: str
class Recipe(BaseModel):
ingredients: List[Ingredient] = Field(description="What are the ingredients for the recipe?")
instructions: List[Instruction] = Field(description="What are the instructions for the recipe?")
Note
Here, we create a custom IsVegan
validator that checks if the ingredient is vegan.
We also set on-fail-is-vegan
to fix
, which in this case means that programatically we will replace the ingredient with a vegan substitute.
Step 2: Create a Guard
object with the RAIL Spec
We create a gd.Guard
object that will check, validate and correct the output of the LLM. This object:
- Enforces the quality criteria specified in the RAIL spec.
- Takes corrective action when the quality criteria are not met.
- Compiles the schema and type info from the RAIL spec and adds it to the prompt.
From the XML string RAIL spec:
/home/zayd/workspace/guardrails-poc/.venv/lib/python3.11/site-packages/guardrails/schema.py:218: UserWarning: Validator 1-indexed is not valid for element integer.
warnings.warn(
/home/zayd/workspace/guardrails-poc/.venv/lib/python3.11/site-packages/guardrails/schema.py:218: UserWarning: Validator units-imperial is not valid for element float.
warnings.warn(
/home/zayd/workspace/guardrails-poc/.venv/lib/python3.11/site-packages/guardrails/schema.py:218: UserWarning: Validator units-imperial is not valid for element string.
warnings.warn(
From the Pydantic model:
As we can see, a few formatters weren't supported. These formatters won't be enforced in the output, but this information can still be used to generate a prompt.
We see the prompt that will be sent to the LLM. The {document}
is substituted with the user provided value at runtime.
Generate a recipe for vegan mac and cheese. Given below is XML that describes the information to extract from this document and the tags to extract it into. <output> <list name="ingredients" description="What are the ingredients for the recipe?"> <object> <integer name="index" format="1-indexed"/> <string name="name" format="is-vegan"/> <string name="brand" description="Suggested brand for the ingredient (if any)"/> <bool name="optional" description="Is the ingredient necessary?"/> <float name="quantity" format="units-imperial"/> <string name="units" format="units-imperial"/> </object> </list> <list name="instructions" description="What are the instructions for the recipe?"> <object> <integer name="index" format="1-indexed"/> <string name="step"/> </object> </list> </output> ONLY return a valid JSON object (no other text is necessary), where the key of the field in JSON is the `name` attribute of the corresponding XML, and the value is of the type specified by the corresponding XML's tag. The JSON MUST conform to the XML format, including any types and format requests e.g. requests for lists, objects and specific types. Be correct and concise. If you are unsure anywhere, enter `null`. Here are examples of simple (XML, JSON) pairs that show the expected behavior: - `<string name='foo' format='two-words lower-case' />` => `{'foo': 'example one'}` - `<list name='bar'><string format='upper-case' /></list>` => `{"bar": ['STRING ONE', 'STRING TWO', etc.]}` - `<object name='baz'><string name="foo" format="capitalize two-words" /><integer name="index" format="1-indexed" /></object>` => `{'baz': {'foo': 'Some String', 'index': 1}}`
Step 3: Wrap the LLM API call with Guard
Async event loop found, but guard was invoked synchronously.For validator parallelization, please call `validate_async` instead.
The guard
wrapper returns the raw_llm_respose (which is a simple string), and the validated and corrected output (which is a dictionary).
We can see that the output is a dictionary with the correct schema and types.
{ 'ingredients': [ {'index': 1, 'name': 'macaroni', 'brand': None, 'optional': False, 'quantity': 8.0, 'units': 'oz'}, { 'index': 2, 'name': 'vegan butter', 'brand': 'Earth Balance', 'optional': False, 'quantity': 2.0, 'units': 'tbsp' }, { 'index': 3, 'name': 'all-purpose flour', 'brand': None, 'optional': False, 'quantity': 2.0, 'units': 'tbsp' }, {'index': 4, 'name': 'vegan milk', 'brand': 'Oatly', 'optional': False, 'quantity': 2.0, 'units': 'cups'}, { 'index': 5, 'name': 'vegan cheese', 'brand': 'Daiya', 'optional': False, 'quantity': 1.0, 'units': 'cup' }, { 'index': 6, 'name': 'nutritional yeast', 'brand': None, 'optional': False, 'quantity': 2.0, 'units': 'tbsp' }, {'index': 7, 'name': 'garlic powder', 'brand': None, 'optional': False, 'quantity': 1.0, 'units': 'tsp'}, {'index': 8, 'name': 'onion powder', 'brand': None, 'optional': False, 'quantity': 1.0, 'units': 'tsp'}, {'index': 9, 'name': 'salt', 'brand': None, 'optional': False, 'quantity': 0.5, 'units': 'tsp'}, {'index': 10, 'name': 'black pepper', 'brand': None, 'optional': False, 'quantity': 0.5, 'units': 'tsp'} ], 'instructions': [ { 'index': 1, 'step': 'Bring a large pot of salted water to a boil. Add the macaroni and cook according to package instructions.' }, { 'index': 2, 'step': 'Meanwhile, melt the vegan butter in a medium saucepan over medium heat. Add the flour and whisk until combined. Cook for 1 minute, stirring constantly.' }, { 'index': 3, 'step': 'Slowly add the vegan milk, whisking constantly until the mixture is smooth. Cook for 3-4 minutes, stirring constantly, until the mixture is thickened.' }, { 'index': 4, 'step': 'Add the vegan cheese, nutritional yeast, garlic powder, onion powder, salt, and pepper. Stir until the cheese is melted and the sauce is smooth.' }, { 'index': 5, 'step': 'Drain the macaroni and add it to the sauce. Stir until the macaroni is evenly coated.' }, {'index': 6, 'step': 'Serve the vegan mac and cheese warm.'} ] }
Logs └── ╭────────────────────────────────────────────────── Step 0 ───────────────────────────────────────────────────╮ │ ╭──────────────────────────────────────────────── Prompt ─────────────────────────────────────────────────╮ │ │ │ │ │ │ │ Generate a recipe for vegan mac and cheese. │ │ │ │ │ │ │ │ Given below is XML that describes the information to extract from this document and the tags to extract │ │ │ │ it into. │ │ │ │ │ │ │ │ <output> │ │ │ │ <list name="ingredients" description="What are the ingredients for the recipe?"> │ │ │ │ <object> │ │ │ │ <integer name="index" format="1-indexed"/> │ │ │ │ <string name="name" format="is-vegan"/> │ │ │ │ <string name="brand" description="Suggested brand for the ingredient (if any)"/> │ │ │ │ <bool name="optional" description="Is the ingredient necessary?"/> │ │ │ │ <float name="quantity" format="units-imperial"/> │ │ │ │ <string name="units" format="units-imperial"/> │ │ │ │ </object> │ │ │ │ </list> │ │ │ │ <list name="instructions" description="What are the instructions for the recipe?"> │ │ │ │ <object> │ │ │ │ <integer name="index" format="1-indexed"/> │ │ │ │ <string name="step"/> │ │ │ │ </object> │ │ │ │ </list> │ │ │ │ </output> │ │ │ │ │ │ │ │ │ │ │ │ ONLY return a valid JSON object (no other text is necessary), where the key of the field in JSON is the │ │ │ │ `name` attribute of the corresponding XML, and the value is of the type specified by the corresponding │ │ │ │ XML's tag. The JSON MUST conform to the XML format, including any types and format requests e.g. │ │ │ │ requests for lists, objects and specific types. Be correct and concise. If you are unsure anywhere, │ │ │ │ enter `null`. │ │ │ │ │ │ │ │ Here are examples of simple (XML, JSON) pairs that show the expected behavior: │ │ │ │ - `<string name='foo' format='two-words lower-case' />` => `{'foo': 'example one'}` │ │ │ │ - `<list name='bar'><string format='upper-case' /></list>` => `{"bar": ['STRING ONE', 'STRING TWO', │ │ │ │ etc.]}` │ │ │ │ - `<object name='baz'><string name="foo" format="capitalize two-words" /><integer name="index" │ │ │ │ format="1-indexed" /></object>` => `{'baz': {'foo': 'Some String', 'index': 1}}` │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Json Output: │ │ │ │ │ │ │ │ │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭──────────────────────────────────────────── Message History ────────────────────────────────────────────╮ │ │ │ ┏━━━━━━┳━━━━━━━━━┓ │ │ │ │ ┃ Role ┃ Content ┃ │ │ │ │ ┡━━━━━━╇━━━━━━━━━┩ │ │ │ │ └──────┴─────────┘ │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭──────────────────────────────────────────── Raw LLM Output ─────────────────────────────────────────────╮ │ │ │ { │ │ │ │ "ingredients": [ │ │ │ │ { │ │ │ │ "index": 1, │ │ │ │ "name": "macaroni", │ │ │ │ "brand": null, │ │ │ │ "optional": false, │ │ │ │ "quantity": 8.0, │ │ │ │ "units": "oz" │ │ │ │ }, │ │ │ │ { │ │ │ │ "index": 2, │ │ │ │ "name": "vegan butter", │ │ │ │ "brand": "Earth Balance", │ │ │ │ "optional": false, │ │ │ │ "quantity": 2.0, │ │ │ │ "units": "tbsp" │ │ │ │ }, │ │ │ │ { │ │ │ │ "index": 3, │ │ │ │ "name": "all-purpose flour", │ │ │ │ "brand": null, │ │ │ │ "optional": false, │ │ │ │ "quantity": 2.0, │ │ │ │ "units": "tbsp" │ │ │ │ }, │ │ │ │ { │ │ │ │ "index": 4, │ │ │ │ "name": "vegan milk", │ │ │ │ "brand": "Oatly", │ │ │ │ "optional": false, │ │ │ │ "quantity": 2.0, │ │ │ │ "units": "cups" │ │ │ │ }, │ │ │ │ { │ │ │ │ "index": 5, │ │ │ │ "name": "vegan cheese", │ │ │ │ "brand": "Daiya", │ │ │ │ "optional": false, │ │ │ │ "quantity": 1.0, │ │ │ │ "units": "cup" │ │ │ │ }, │ │ │ │ { │ │ │ │ "index": 6, │ │ │ │ "name": "nutritional yeast", │ │ │ │ "brand": null, │ │ │ │ "optional": false, │ │ │ │ "quantity": 2.0, │ │ │ │ "units": "tbsp" │ │ │ │ }, │ │ │ │ { │ │ │ │ "index": 7, │ │ │ │ "name": "garlic powder", │ │ │ │ "brand": null, │ │ │ │ "optional": false, │ │ │ │ "quantity": 1.0, │ │ │ │ "units": "tsp" │ │ │ │ }, │ │ │ │ { │ │ │ │ "index": 8, │ │ │ │ "name": "onion powder", │ │ │ │ "brand": null, │ │ │ │ "optional": false, │ │ │ │ "quantity": 1.0, │ │ │ │ "units": "tsp" │ │ │ │ }, │ │ │ │ { │ │ │ │ "index": 9, │ │ │ │ "name": "salt", │ │ │ │ "brand": null, │ │ │ │ "optional": false, │ │ │ │ "quantity": 0.5, │ │ │ │ "units": "tsp" │ │ │ │ }, │ │ │ │ { │ │ │ │ "index": 10, │ │ │ │ "name": "black pepper", │ │ │ │ "brand": null, │ │ │ │ "optional": false, │ │ │ │ "quantity": 0.5, │ │ │ │ "units": "tsp" │ │ │ │ } │ │ │ │ ], │ │ │ │ "instructions": [ │ │ │ │ { │ │ │ │ "index": 1, │ │ │ │ "step": "Bring a large pot of salted water to a boil. Add the macaroni and cook according │ │ │ │ to package instructions." │ │ │ │ }, │ │ │ │ { │ │ │ │ "index": 2, │ │ │ │ "step": "Meanwhile, melt the vegan butter in a medium saucepan over medium heat. Add the │ │ │ │ flour and whisk until combined. Cook for 1 minute, stirring constantly." │ │ │ │ }, │ │ │ │ { │ │ │ │ "index": 3, │ │ │ │ "step": "Slowly add the vegan milk, whisking constantly until the mixture is smooth. Cook │ │ │ │ for 3-4 minutes, stirring constantly, until the mixture is thickened." │ │ │ │ }, │ │ │ │ { │ │ │ │ "index": 4, │ │ │ │ "step": "Add the vegan cheese, nutritional yeast, garlic powder, onion powder, salt, and │ │ │ │ pepper. Stir until the cheese is melted and the sauce is smooth." │ │ │ │ }, │ │ │ │ { │ │ │ │ "index": 5, │ │ │ │ "step": "Drain the macaroni and add it to the sauce. Stir until the macaroni is evenly │ │ │ │ coated." │ │ │ │ }, │ │ │ │ { │ │ │ │ "index": 6, │ │ │ │ "step": "Serve the vegan mac and cheese warm." │ │ │ │ } │ │ │ │ ] │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ ╭─────────────────────────────────────────── Validated Output ────────────────────────────────────────────╮ │ │ │ { │ │ │ │ 'ingredients': [ │ │ │ │ { │ │ │ │ 'index': 1, │ │ │ │ 'name': 'macaroni', │ │ │ │ 'brand': None, │ │ │ │ 'optional': False, │ │ │ │ 'quantity': 8.0, │ │ │ │ 'units': 'oz' │ │ │ │ }, │ │ │ │ { │ │ │ │ 'index': 2, │ │ │ │ 'name': 'vegan butter', │ │ │ │ 'brand': 'Earth Balance', │ │ │ │ 'optional': False, │ │ │ │ 'quantity': 2.0, │ │ │ │ 'units': 'tbsp' │ │ │ │ }, │ │ │ │ { │ │ │ │ 'index': 3, │ │ │ │ 'name': 'all-purpose flour', │ │ │ │ 'brand': None, │ │ │ │ 'optional': False, │ │ │ │ 'quantity': 2.0, │ │ │ │ 'units': 'tbsp' │ │ │ │ }, │ │ │ │ { │ │ │ │ 'index': 4, │ │ │ │ 'name': 'vegan milk', │ │ │ │ 'brand': 'Oatly', │ │ │ │ 'optional': False, │ │ │ │ 'quantity': 2.0, │ │ │ │ 'units': 'cups' │ │ │ │ }, │ │ │ │ { │ │ │ │ 'index': 5, │ │ │ │ 'name': 'vegan cheese', │ │ │ │ 'brand': 'Daiya', │ │ │ │ 'optional': False, │ │ │ │ 'quantity': 1.0, │ │ │ │ 'units': 'cup' │ │ │ │ }, │ │ │ │ { │ │ │ │ 'index': 6, │ │ │ │ 'name': 'nutritional yeast', │ │ │ │ 'brand': None, │ │ │ │ 'optional': False, │ │ │ │ 'quantity': 2.0, │ │ │ │ 'units': 'tbsp' │ │ │ │ }, │ │ │ │ { │ │ │ │ 'index': 7, │ │ │ │ 'name': 'garlic powder', │ │ │ │ 'brand': None, │ │ │ │ 'optional': False, │ │ │ │ 'quantity': 1.0, │ │ │ │ 'units': 'tsp' │ │ │ │ }, │ │ │ │ { │ │ │ │ 'index': 8, │ │ │ │ 'name': 'onion powder', │ │ │ │ 'brand': None, │ │ │ │ 'optional': False, │ │ │ │ 'quantity': 1.0, │ │ │ │ 'units': 'tsp' │ │ │ │ }, │ │ │ │ { │ │ │ │ 'index': 9, │ │ │ │ 'name': 'salt', │ │ │ │ 'brand': None, │ │ │ │ 'optional': False, │ │ │ │ 'quantity': 0.5, │ │ │ │ 'units': 'tsp' │ │ │ │ }, │ │ │ │ { │ │ │ │ 'index': 10, │ │ │ │ 'name': 'black pepper', │ │ │ │ 'brand': None, │ │ │ │ 'optional': False, │ │ │ │ 'quantity': 0.5, │ │ │ │ 'units': 'tsp' │ │ │ │ } │ │ │ │ ], │ │ │ │ 'instructions': [ │ │ │ │ { │ │ │ │ 'index': 1, │ │ │ │ 'step': 'Bring a large pot of salted water to a boil. Add the macaroni and cook according │ │ │ │ to package instructions.' │ │ │ │ }, │ │ │ │ { │ │ │ │ 'index': 2, │ │ │ │ 'step': 'Meanwhile, melt the vegan butter in a medium saucepan over medium heat. Add the │ │ │ │ flour and whisk until combined. Cook for 1 minute, stirring constantly.' │ │ │ │ }, │ │ │ │ { │ │ │ │ 'index': 3, │ │ │ │ 'step': 'Slowly add the vegan milk, whisking constantly until the mixture is smooth. Cook │ │ │ │ for 3-4 minutes, stirring constantly, until the mixture is thickened.' │ │ │ │ }, │ │ │ │ { │ │ │ │ 'index': 4, │ │ │ │ 'step': 'Add the vegan cheese, nutritional yeast, garlic powder, onion powder, salt, and │ │ │ │ pepper. Stir until the cheese is melted and the sauce is smooth.' │ │ │ │ }, │ │ │ │ { │ │ │ │ 'index': 5, │ │ │ │ 'step': 'Drain the macaroni and add it to the sauce. Stir until the macaroni is evenly │ │ │ │ coated.' │ │ │ │ }, │ │ │ │ {'index': 6, 'step': 'Serve the vegan mac and cheese warm.'} │ │ │ │ ] │ │ │ │ } │ │ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯