206 lines
6.7 KiB
Python
206 lines
6.7 KiB
Python
|
|
# ruff: noqa: E501
|
||
|
|
import os
|
||
|
|
from datetime import date
|
||
|
|
from string import Template
|
||
|
|
|
||
|
|
import typer
|
||
|
|
from pydash import pascal_case, snake_case
|
||
|
|
|
||
|
|
from guardrails.cli.hub.hub import hub_command
|
||
|
|
from guardrails.cli.logger import LEVELS, logger
|
||
|
|
from guardrails.hub_telemetry.hub_tracing import trace
|
||
|
|
|
||
|
|
validator_template = Template(
|
||
|
|
"""
|
||
|
|
\"""
|
||
|
|
This template is intended for creating simple validators.
|
||
|
|
|
||
|
|
If your validator is complex or requires additional post-installation steps, consider using the template repository instead.
|
||
|
|
|
||
|
|
The template repository can be found here: https://github.com/guardrails-ai/validator-template
|
||
|
|
\"""
|
||
|
|
|
||
|
|
from typing import Any, Callable, Dict, Optional
|
||
|
|
|
||
|
|
from guardrails.validator_base import (
|
||
|
|
FailResult,
|
||
|
|
PassResult,
|
||
|
|
ValidationResult,
|
||
|
|
Validator,
|
||
|
|
register_validator,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@register_validator(name="guardrails/${package_name}", data_type="string")
|
||
|
|
class ${class_name}(Validator):
|
||
|
|
# FIXME: Update the class docstring to reflect the purpose and usage of your validator.
|
||
|
|
\"""# Overview
|
||
|
|
|
||
|
|
| Developed by | {FIXME: Your organization name} |
|
||
|
|
| Date of development | ${dev_date} |
|
||
|
|
| Validator type | Format |
|
||
|
|
| License | Apache 2 |
|
||
|
|
| Input/Output | Output |
|
||
|
|
|
||
|
|
# Description
|
||
|
|
{FIXME: A brief description of what your validator does.}
|
||
|
|
|
||
|
|
## (Optional) Intended Use
|
||
|
|
{FIXME: Optionally, include a brief description of the intended use of your validator, including any limitations or constraints.}
|
||
|
|
|
||
|
|
## Requirements
|
||
|
|
|
||
|
|
* Dependencies:
|
||
|
|
- guardrails-ai>=0.4.0
|
||
|
|
- {FIXME: Include any other dependencies you need here}
|
||
|
|
|
||
|
|
* Dev Dependencies:
|
||
|
|
- pytest
|
||
|
|
- pyright
|
||
|
|
- ruff
|
||
|
|
- {FIXME: Include any other dev dependencies you need here}
|
||
|
|
|
||
|
|
* Foundation model access keys:
|
||
|
|
- {FIXME: Include any access environment variables you need here like OPENAI_API_KEY}
|
||
|
|
|
||
|
|
|
||
|
|
# Installation
|
||
|
|
|
||
|
|
```bash
|
||
|
|
$ guardrails hub install hub://guardrails/${package_name}
|
||
|
|
```
|
||
|
|
|
||
|
|
# Usage Examples
|
||
|
|
|
||
|
|
## Validating string output via Python
|
||
|
|
|
||
|
|
In this example, we apply the validator to a string output generated by an LLM.
|
||
|
|
|
||
|
|
```python
|
||
|
|
# Import Guard and Validator
|
||
|
|
from guardrails.hub import ${class_name}
|
||
|
|
from guardrails import Guard
|
||
|
|
|
||
|
|
# Setup Guard
|
||
|
|
guard = Guard.use(
|
||
|
|
${class_name}({FIXME: list any args here})
|
||
|
|
)
|
||
|
|
|
||
|
|
guard.validate({FIXME: Add an input that should pass the validator}) # Validator passes
|
||
|
|
guard.validate({FIXME: Add an input that should fail the validator}) # Validator fails
|
||
|
|
```
|
||
|
|
\""" # noqa
|
||
|
|
|
||
|
|
# If you don't have any init args, you can omit the __init__ method.
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
arg_1: str, # FIXME: Replace with your custom init args.
|
||
|
|
on_fail: Optional[Callable] = None,
|
||
|
|
):
|
||
|
|
\"""Initializes a new instance of the ${class_name} class.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
arg_1 (str): FIXME: Describe the purpose of this argument.
|
||
|
|
on_fail`** *(str, Callable)*: The policy to enact when a validator fails. If `str`, must be one of `reask`, `fix`, `filter`, `refrain`, `noop`, `exception` or `fix_reask`. Otherwise, must be a function that is called when the validator fails.
|
||
|
|
\"""
|
||
|
|
super().__init__(on_fail=on_fail, arg_1=arg_1)
|
||
|
|
self._arg_1 = arg_1
|
||
|
|
|
||
|
|
def validate(self, value: Any, metadata: Dict) -> ValidationResult:
|
||
|
|
\"""Validates that {fill in how you validator interacts with the passed value}.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
value (Any): The value to validate.
|
||
|
|
metadata (Dict): The metadata to validate against.
|
||
|
|
|
||
|
|
FIXME: Add any additional args you need here in metadata.
|
||
|
|
| Key | Description |
|
||
|
|
| --- | --- |
|
||
|
|
| a | b |
|
||
|
|
\"""
|
||
|
|
|
||
|
|
# Add your custom validator logic here and return a PassResult or FailResult accordingly.
|
||
|
|
if value != "pass": # FIXME
|
||
|
|
return FailResult(
|
||
|
|
error_message="{FIXME: A descriptive but concise error message about why validation failed}",
|
||
|
|
fix_value="{FIXME: The programmtic fix if applicable, otherwise remove this kwarg.}",
|
||
|
|
)
|
||
|
|
return PassResult()
|
||
|
|
|
||
|
|
|
||
|
|
# Run tests via `pytest -rP ${filepath}`
|
||
|
|
class Test${class_name}:
|
||
|
|
def test_success_case(self):
|
||
|
|
# FIXME: Replace with your custom test logic for the success case.
|
||
|
|
validator = ${class_name}("s")
|
||
|
|
result = validator.validate("pass", {})
|
||
|
|
assert isinstance(result, PassResult) is True
|
||
|
|
|
||
|
|
def test_failure_case(self):
|
||
|
|
# FIXME: Replace with your custom test logic for the failure case.
|
||
|
|
validator = ${class_name}("s")
|
||
|
|
result = validator.validate("fail", {})
|
||
|
|
assert isinstance(result, FailResult) is True
|
||
|
|
assert result.error_message == "{A descriptive but concise error message about why validation failed}"
|
||
|
|
assert result.fix_value == "fails"
|
||
|
|
"""
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@hub_command.command(name="create-validator")
|
||
|
|
@trace(name="guardrails-cli/hub/create-validator")
|
||
|
|
def create_validator(
|
||
|
|
name: str = typer.Argument(help="The name for your validator."),
|
||
|
|
filepath: str = typer.Argument(
|
||
|
|
help="The location to write your validator template to",
|
||
|
|
default="./{validator_name}.py",
|
||
|
|
),
|
||
|
|
):
|
||
|
|
"""Lightweight method for creating simple validators.
|
||
|
|
|
||
|
|
For more complex submissions see here:
|
||
|
|
https://github.com/guardrails-ai/validator-template?tab=readme-ov-file#how-to-create-a-guardrails-validator
|
||
|
|
"""
|
||
|
|
disclaimer = """
|
||
|
|
|
||
|
|
This utility is intended for creating simple validators.
|
||
|
|
|
||
|
|
If your validator is complex or requires additional post-installation steps,\
|
||
|
|
consider using the template repository instead.
|
||
|
|
|
||
|
|
The template repository can be found here:\
|
||
|
|
https://github.com/guardrails-ai/validator-template
|
||
|
|
"""
|
||
|
|
logger.log(level=LEVELS.get("NOTICE") or 0, msg=disclaimer)
|
||
|
|
|
||
|
|
package_name = snake_case(name)
|
||
|
|
class_name = pascal_case(name)
|
||
|
|
if not filepath or filepath == "./{validator_name}.py":
|
||
|
|
filepath = f"./{package_name}.py"
|
||
|
|
|
||
|
|
template = validator_template.safe_substitute(
|
||
|
|
{
|
||
|
|
"package_name": package_name,
|
||
|
|
"class_name": class_name,
|
||
|
|
"filepath": filepath,
|
||
|
|
"dev_date": date.today().strftime("%b %d, %Y"),
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
target = os.path.abspath(filepath)
|
||
|
|
with open(target, "w") as validator_file:
|
||
|
|
validator_file.write(template)
|
||
|
|
validator_file.close()
|
||
|
|
|
||
|
|
success_message = Template(
|
||
|
|
"""
|
||
|
|
|
||
|
|
Successfully created validator template at ${filepath}!
|
||
|
|
|
||
|
|
Make any necessary changes then submit for review with the following command:
|
||
|
|
|
||
|
|
guardrails hub submit ${package_name} ${filepath}
|
||
|
|
"""
|
||
|
|
).safe_substitute({"filepath": filepath, "package_name": package_name})
|
||
|
|
logger.log(level=LEVELS.get("SUCCESS"), msg=success_message) # type: ignore
|