From 68b587735abc80913813d061a4af4fde3c6bff03 Mon Sep 17 00:00:00 2001 From: Caleb Courier Date: Mon, 5 Feb 2024 13:08:54 -0800 Subject: [PATCH 1/4] refactor error class --- docs/api_reference/response_structures.md | 2 +- docs/examples/input_validation.ipynb | 10 ++-- docs/examples/response_is_on_topic.ipynb | 10 ++-- guardrails/errors/__init__.py | 19 ++++++- guardrails/functional/chain/__init__.py | 3 + guardrails/run.py | 26 ++++----- guardrails/validator_base.py | 4 -- guardrails/validator_service.py | 4 +- .../integration_tests/test_data_validation.py | 10 ++-- tests/unit_tests/test_validators.py | 56 +++++++++---------- 10 files changed, 79 insertions(+), 65 deletions(-) create mode 100644 guardrails/functional/chain/__init__.py diff --git a/docs/api_reference/response_structures.md b/docs/api_reference/response_structures.md index efac7dd9f..168be571e 100644 --- a/docs/api_reference/response_structures.md +++ b/docs/api_reference/response_structures.md @@ -6,4 +6,4 @@ - "ValidationResult" - "PassResult" - "FailResult" - - "ValidatorError" + - "ValidationError" diff --git a/docs/examples/input_validation.ipynb b/docs/examples/input_validation.ipynb index d3e23fbb7..7432bde23 100644 --- a/docs/examples/input_validation.ipynb +++ b/docs/examples/input_validation.ipynb @@ -47,7 +47,7 @@ "source": [ "When `fix` is specified as the on-fail handler, the prompt will automatically be amended before calling the LLM.\n", "\n", - "In any other case (for example, `exception`), a `ValidatorError` will be returned in the outcome." + "In any other case (for example, `exception`), a `ValidationError` will be returned in the outcome." ] }, { @@ -67,13 +67,13 @@ ], "source": [ "import openai\n", - "from guardrails.errors import ValidatorError\n", + "from guardrails.errors import ValidationError\n", "\n", "try:\n", " guard(\n", " openai.chat.completions.create,\n", " )\n", - "except ValidatorError as e:\n", + "except ValidationError as e:\n", " print(e)" ] }, @@ -115,7 +115,7 @@ " openai.chat.completions.create,\n", " prompt=\"This is not two words\",\n", " )\n", - "except ValidatorError as e:\n", + "except ValidationError as e:\n", " print(e)" ] } @@ -136,7 +136,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.6" + "version": "3.11.7" } }, "nbformat": 4, diff --git a/docs/examples/response_is_on_topic.ipynb b/docs/examples/response_is_on_topic.ipynb index d876108fc..c6337fd4a 100644 --- a/docs/examples/response_is_on_topic.ipynb +++ b/docs/examples/response_is_on_topic.ipynb @@ -137,7 +137,7 @@ "source": [ "import guardrails as gd\n", "from guardrails.validators import OnTopic\n", - "from guardrails.errors import ValidatorError\n", + "from guardrails.errors import ValidationError\n", "\n", "# Create the Guard with the OnTopic Validator\n", "guard = gd.Guard.from_string(\n", @@ -159,7 +159,7 @@ " guard.parse(\n", " llm_output=text,\n", " )\n", - "except ValidatorError as e:\n", + "except ValidationError as e:\n", " print(e)\n" ] }, @@ -205,7 +205,7 @@ " guard.parse(\n", " llm_output=text,\n", " )\n", - "except ValidatorError as e:\n", + "except ValidationError as e:\n", " print(e)" ] }, @@ -258,7 +258,7 @@ " guard.parse(\n", " llm_output=text,\n", " )\n", - "except ValidatorError as e:\n", + "except ValidationError as e:\n", " print(e)" ] } @@ -279,7 +279,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.6" + "version": "3.11.7" } }, "nbformat": 4, diff --git a/guardrails/errors/__init__.py b/guardrails/errors/__init__.py index 392ed17fa..959a3bcb0 100644 --- a/guardrails/errors/__init__.py +++ b/guardrails/errors/__init__.py @@ -1,3 +1,18 @@ -from guardrails.validator_base import ValidatorError +# Never actually used in any validators so the description is misleading. +# The naming is confusing so we're updating it. +class ValidatorError(Exception): + """ + deprecated: 0.3.3 + Use :class:`ValidationError` instead. -__all__ = ["ValidatorError"] + Base class for all validator errors. + """ + + +# Open to naming this something more generic like GuardrailsError or something, +# let's just decide in this PR +class ValidationError(Exception): + """Top level validation error.""" + + +__all__ = ["ValidatorError", "ValidationError"] diff --git a/guardrails/functional/chain/__init__.py b/guardrails/functional/chain/__init__.py new file mode 100644 index 000000000..02e3b7fcf --- /dev/null +++ b/guardrails/functional/chain/__init__.py @@ -0,0 +1,3 @@ +from guardrails.functional.chain.guard import Guard + +__all__ = ["Guard"] diff --git a/guardrails/run.py b/guardrails/run.py index 11a9d2d56..222827a29 100644 --- a/guardrails/run.py +++ b/guardrails/run.py @@ -25,7 +25,7 @@ SkeletonReAsk, reasks_to_dict, ) -from guardrails.validator_base import ValidatorError +from guardrails.errors import ValidationError add_destinations(logger.debug) @@ -332,11 +332,11 @@ def validate_msg_history( ) iteration.outputs.validation_output = validated_msg_history if isinstance(validated_msg_history, ReAsk): - raise ValidatorError( + raise ValidationError( f"Message history validation failed: " f"{validated_msg_history}" ) if validated_msg_history != msg_str: - raise ValidatorError("Message history validation failed") + raise ValidationError("Message history validation failed") def prepare_msg_history( self, @@ -372,9 +372,9 @@ def validate_prompt( ) iteration.outputs.validation_output = validated_prompt if validated_prompt is None: - raise ValidatorError("Prompt validation failed") + raise ValidationError("Prompt validation failed") if isinstance(validated_prompt, ReAsk): - raise ValidatorError(f"Prompt validation failed: {validated_prompt}") + raise ValidationError(f"Prompt validation failed: {validated_prompt}") return Prompt(validated_prompt) def validate_instructions( @@ -393,9 +393,9 @@ def validate_instructions( ) iteration.outputs.validation_output = validated_instructions if validated_instructions is None: - raise ValidatorError("Instructions validation failed") + raise ValidationError("Instructions validation failed") if isinstance(validated_instructions, ReAsk): - raise ValidatorError( + raise ValidationError( f"Instructions validation failed: {validated_instructions}" ) return Instructions(validated_instructions) @@ -1020,12 +1020,12 @@ async def async_prepare( iteration, msg_str, self.metadata ) if isinstance(validated_msg_history, ReAsk): - raise ValidatorError( + raise ValidationError( f"Message history validation failed: " f"{validated_msg_history}" ) if validated_msg_history != msg_str: - raise ValidatorError("Message history validation failed") + raise ValidationError("Message history validation failed") elif prompt is not None: if isinstance(prompt, str): prompt = Prompt(prompt) @@ -1053,9 +1053,9 @@ async def async_prepare( ) iteration.outputs.validation_output = validated_prompt if validated_prompt is None: - raise ValidatorError("Prompt validation failed") + raise ValidationError("Prompt validation failed") if isinstance(validated_prompt, ReAsk): - raise ValidatorError( + raise ValidationError( f"Prompt validation failed: {validated_prompt}" ) prompt = Prompt(validated_prompt) @@ -1072,9 +1072,9 @@ async def async_prepare( ) iteration.outputs.validation_output = validated_instructions if validated_instructions is None: - raise ValidatorError("Instructions validation failed") + raise ValidationError("Instructions validation failed") if isinstance(validated_instructions, ReAsk): - raise ValidatorError( + raise ValidationError( f"Instructions validation failed: {validated_instructions}" ) instructions = Instructions(validated_instructions) diff --git a/guardrails/validator_base.py b/guardrails/validator_base.py index 8b486a724..50a95f26d 100644 --- a/guardrails/validator_base.py +++ b/guardrails/validator_base.py @@ -6,10 +6,6 @@ from pydantic import BaseModel, Field -class ValidatorError(Exception): - """Base class for all validator errors.""" - - class Filter: pass diff --git a/guardrails/validator_service.py b/guardrails/validator_service.py index 7a58612b8..4290bccb5 100644 --- a/guardrails/validator_service.py +++ b/guardrails/validator_service.py @@ -4,6 +4,7 @@ from concurrent.futures import ProcessPoolExecutor from typing import Any, Dict, List, Optional, Tuple +from guardrails.errors import ValidationError from guardrails.classes.history import Iteration from guardrails.datatypes import FieldValidation from guardrails.logger import logger @@ -16,7 +17,6 @@ PassResult, Refrain, Validator, - ValidatorError, ) @@ -57,7 +57,7 @@ def perform_correction( fail_results=results, ) if on_fail_descriptor == "exception": - raise ValidatorError( + raise ValidationError( "Validation failed for field with errors: " + ", ".join([result.error_message for result in results]) ) diff --git a/tests/integration_tests/test_data_validation.py b/tests/integration_tests/test_data_validation.py index 82f1606f8..0527f03ac 100644 --- a/tests/integration_tests/test_data_validation.py +++ b/tests/integration_tests/test_data_validation.py @@ -6,7 +6,7 @@ from guardrails import Guard from guardrails.utils.reask_utils import ReAsk -from guardrails.validator_base import ValidatorError +from guardrails.errors import ValidationError from guardrails.validators import ValidChoices test_cases = [ @@ -68,9 +68,9 @@ def test_choice_validation(llm_output, raises, fails, has_error): guard = Guard.from_rail_string(rail_spec) # If raises is True, then the test should raise an exception. - # For our existing test cases this will always be a ValidatorError + # For our existing test cases this will always be a ValidationError if raises: - with pytest.raises(ValidatorError): + with pytest.raises(ValidationError): guard.parse(llm_output, num_reasks=0) else: result = guard.parse(llm_output, num_reasks=0) @@ -115,9 +115,9 @@ class Choice(BaseModel): guard = Guard.from_pydantic(output_class=Choice, prompt="Dummy prompt.") # If raises is True, then the test should raise an exception. - # For our existing test cases this will always be a ValidatorError + # For our existing test cases this will always be a ValidationError if raises: - with pytest.raises(ValidatorError): + with pytest.raises(ValidationError): guard.parse(llm_output, num_reasks=0) else: result = guard.parse(llm_output, num_reasks=0) diff --git a/tests/unit_tests/test_validators.py b/tests/unit_tests/test_validators.py index ea2c55117..34af4e503 100644 --- a/tests/unit_tests/test_validators.py +++ b/tests/unit_tests/test_validators.py @@ -9,6 +9,7 @@ from guardrails import Guard from guardrails.datatypes import DataType +from guardrails.errors import ValidationError from guardrails.schema import StringSchema from guardrails.utils.openai_utils import ( OPENAI_VERSION, @@ -22,7 +23,6 @@ PassResult, Refrain, ValidationResult, - ValidatorError, check_refrain_in_dict, filter_in_dict, register_validator, @@ -604,7 +604,7 @@ def custom_reask_on_fail_handler(value: Any, fail_results: List[FailResult]): def custom_exception_on_fail_handler(value: Any, fail_results: List[FailResult]): - raise ValidatorError("Something went wrong!") + raise ValidationError("Something went wrong!") def custom_filter_on_fail_handler(value: Any, fail_results: List[FailResult]): @@ -637,7 +637,7 @@ def custom_refrain_on_fail_handler(value: Any, fail_results: List[FailResult]): ), ( custom_exception_on_fail_handler, - ValidatorError, + ValidationError, ), ( custom_filter_on_fail_handler, @@ -682,7 +682,7 @@ class Pet(BaseModel): guard = Guard.from_pydantic(output_class=Pet, prompt=prompt) if isinstance(expected_result, type) and issubclass(expected_result, Exception): - with pytest.raises(ValidatorError) as excinfo: + with pytest.raises(ValidationError) as excinfo: guard.parse(output, num_reasks=0) assert str(excinfo.value) == "Something went wrong!" else: @@ -759,7 +759,7 @@ def test_input_validation_fix(mocker): guard = Guard.from_pydantic(output_class=Pet).with_msg_history_validation( validators=[TwoWords(on_fail="fix")] ) - with pytest.raises(ValidatorError) as excinfo: + with pytest.raises(ValidationError) as excinfo: guard( get_static_openai_create_func(), msg_history=[ @@ -770,7 +770,7 @@ def test_input_validation_fix(mocker): ], ) assert str(excinfo.value) == "Message history validation failed" - assert isinstance(guard.history.first.exception, ValidatorError) + assert isinstance(guard.history.first.exception, ValidationError) assert guard.history.first.exception == excinfo.value # rail prompt validation @@ -859,7 +859,7 @@ async def test_async_input_validation_fix(mocker): guard = Guard.from_pydantic(output_class=Pet).with_msg_history_validation( validators=[TwoWords(on_fail="fix")] ) - with pytest.raises(ValidatorError) as excinfo: + with pytest.raises(ValidationError) as excinfo: await guard( get_static_openai_acreate_func(), msg_history=[ @@ -870,7 +870,7 @@ async def test_async_input_validation_fix(mocker): ], ) assert str(excinfo.value) == "Message history validation failed" - assert isinstance(guard.history.first.exception, ValidatorError) + assert isinstance(guard.history.first.exception, ValidationError) assert guard.history.first.exception == excinfo.value # rail prompt validation @@ -971,34 +971,34 @@ def test_input_validation_fail( guard = Guard.from_pydantic(output_class=Pet).with_prompt_validation( validators=[TwoWords(on_fail=on_fail)] ) - with pytest.raises(ValidatorError) as excinfo: + with pytest.raises(ValidationError) as excinfo: guard( get_static_openai_create_func(), prompt="What kind of pet should I get?", ) assert str(excinfo.value) == structured_prompt_error - assert isinstance(guard.history.last.exception, ValidatorError) + assert isinstance(guard.history.last.exception, ValidationError) assert guard.history.last.exception == excinfo.value # with_instructions_validation guard = Guard.from_pydantic(output_class=Pet).with_instructions_validation( validators=[TwoWords(on_fail=on_fail)] ) - with pytest.raises(ValidatorError) as excinfo: + with pytest.raises(ValidationError) as excinfo: guard( get_static_openai_create_func(), prompt="What kind of pet should I get and what should I name it?", instructions="What kind of pet should I get?", ) assert str(excinfo.value) == structured_instructions_error - assert isinstance(guard.history.last.exception, ValidatorError) + assert isinstance(guard.history.last.exception, ValidationError) assert guard.history.last.exception == excinfo.value # with_msg_history_validation guard = Guard.from_pydantic(output_class=Pet).with_msg_history_validation( validators=[TwoWords(on_fail=on_fail)] ) - with pytest.raises(ValidatorError) as excinfo: + with pytest.raises(ValidationError) as excinfo: guard( get_static_openai_create_func(), msg_history=[ @@ -1009,7 +1009,7 @@ def test_input_validation_fail( ], ) assert str(excinfo.value) == structured_message_history_error - assert isinstance(guard.history.last.exception, ValidatorError) + assert isinstance(guard.history.last.exception, ValidationError) assert guard.history.last.exception == excinfo.value # rail prompt validation @@ -1027,12 +1027,12 @@ def test_input_validation_fail( """ ) - with pytest.raises(ValidatorError) as excinfo: + with pytest.raises(ValidationError) as excinfo: guard( get_static_openai_create_func(), ) assert str(excinfo.value) == unstructured_prompt_error - assert isinstance(guard.history.last.exception, ValidatorError) + assert isinstance(guard.history.last.exception, ValidationError) assert guard.history.last.exception == excinfo.value # rail instructions validation @@ -1053,12 +1053,12 @@ def test_input_validation_fail( """ ) - with pytest.raises(ValidatorError) as excinfo: + with pytest.raises(ValidationError) as excinfo: guard( get_static_openai_create_func(), ) assert str(excinfo.value) == unstructured_instructions_error - assert isinstance(guard.history.last.exception, ValidatorError) + assert isinstance(guard.history.last.exception, ValidationError) assert guard.history.last.exception == excinfo.value @@ -1118,34 +1118,34 @@ async def test_input_validation_fail_async( guard = Guard.from_pydantic(output_class=Pet).with_prompt_validation( validators=[TwoWords(on_fail=on_fail)] ) - with pytest.raises(ValidatorError) as excinfo: + with pytest.raises(ValidationError) as excinfo: await guard( get_static_openai_acreate_func(), prompt="What kind of pet should I get?", ) assert str(excinfo.value) == structured_prompt_error - assert isinstance(guard.history.last.exception, ValidatorError) + assert isinstance(guard.history.last.exception, ValidationError) assert guard.history.last.exception == excinfo.value # with_instructions_validation guard = Guard.from_pydantic(output_class=Pet).with_instructions_validation( validators=[TwoWords(on_fail=on_fail)] ) - with pytest.raises(ValidatorError) as excinfo: + with pytest.raises(ValidationError) as excinfo: await guard( get_static_openai_acreate_func(), prompt="What kind of pet should I get and what should I name it?", instructions="What kind of pet should I get?", ) assert str(excinfo.value) == structured_instructions_error - assert isinstance(guard.history.last.exception, ValidatorError) + assert isinstance(guard.history.last.exception, ValidationError) assert guard.history.last.exception == excinfo.value # with_msg_history_validation guard = Guard.from_pydantic(output_class=Pet).with_msg_history_validation( validators=[TwoWords(on_fail=on_fail)] ) - with pytest.raises(ValidatorError) as excinfo: + with pytest.raises(ValidationError) as excinfo: await guard( get_static_openai_acreate_func(), msg_history=[ @@ -1156,7 +1156,7 @@ async def test_input_validation_fail_async( ], ) assert str(excinfo.value) == structured_message_history_error - assert isinstance(guard.history.last.exception, ValidatorError) + assert isinstance(guard.history.last.exception, ValidationError) assert guard.history.last.exception == excinfo.value # rail prompt validation @@ -1174,12 +1174,12 @@ async def test_input_validation_fail_async( """ ) - with pytest.raises(ValidatorError) as excinfo: + with pytest.raises(ValidationError) as excinfo: await guard( get_static_openai_acreate_func(), ) assert str(excinfo.value) == unstructured_prompt_error - assert isinstance(guard.history.last.exception, ValidatorError) + assert isinstance(guard.history.last.exception, ValidationError) assert guard.history.last.exception == excinfo.value # rail instructions validation @@ -1200,12 +1200,12 @@ async def test_input_validation_fail_async( """ ) - with pytest.raises(ValidatorError) as excinfo: + with pytest.raises(ValidationError) as excinfo: await guard( get_static_openai_acreate_func(), ) assert str(excinfo.value) == unstructured_instructions_error - assert isinstance(guard.history.last.exception, ValidatorError) + assert isinstance(guard.history.last.exception, ValidationError) assert guard.history.last.exception == excinfo.value From 2c0508ce7021c80a442eca5fe2e0510cb6624edb Mon Sep 17 00:00:00 2001 From: Caleb Courier Date: Mon, 5 Feb 2024 13:19:16 -0800 Subject: [PATCH 2/4] update imports --- guardrails/run.py | 2 +- guardrails/validator_service.py | 10 ++-------- tests/integration_tests/test_data_validation.py | 2 +- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/guardrails/run.py b/guardrails/run.py index 222827a29..537b3ae6a 100644 --- a/guardrails/run.py +++ b/guardrails/run.py @@ -7,6 +7,7 @@ from guardrails.classes.history import Call, Inputs, Iteration, Outputs from guardrails.datatypes import verify_metadata_requirements +from guardrails.errors import ValidationError from guardrails.llm_providers import ( AsyncPromptCallableBase, OpenAICallable, @@ -25,7 +26,6 @@ SkeletonReAsk, reasks_to_dict, ) -from guardrails.errors import ValidationError add_destinations(logger.debug) diff --git a/guardrails/validator_service.py b/guardrails/validator_service.py index 4290bccb5..c6446094e 100644 --- a/guardrails/validator_service.py +++ b/guardrails/validator_service.py @@ -4,20 +4,14 @@ from concurrent.futures import ProcessPoolExecutor from typing import Any, Dict, List, Optional, Tuple -from guardrails.errors import ValidationError from guardrails.classes.history import Iteration from guardrails.datatypes import FieldValidation +from guardrails.errors import ValidationError from guardrails.logger import logger from guardrails.utils.logs_utils import ValidatorLogs from guardrails.utils.reask_utils import FieldReAsk, ReAsk from guardrails.utils.safe_get import safe_get -from guardrails.validator_base import ( - FailResult, - Filter, - PassResult, - Refrain, - Validator, -) +from guardrails.validator_base import FailResult, Filter, PassResult, Refrain, Validator def key_not_empty(key: str) -> bool: diff --git a/tests/integration_tests/test_data_validation.py b/tests/integration_tests/test_data_validation.py index 0527f03ac..30ff00277 100644 --- a/tests/integration_tests/test_data_validation.py +++ b/tests/integration_tests/test_data_validation.py @@ -5,8 +5,8 @@ from pydantic import BaseModel, Field from guardrails import Guard -from guardrails.utils.reask_utils import ReAsk from guardrails.errors import ValidationError +from guardrails.utils.reask_utils import ReAsk from guardrails.validators import ValidChoices test_cases = [ From f1c123b9abc6cb37ebf49227ade6c35d1a388e63 Mon Sep 17 00:00:00 2001 From: Caleb Courier Date: Mon, 5 Feb 2024 13:40:17 -0800 Subject: [PATCH 3/4] first draft of lcel support --- guardrails/functional/chain/guard.py | 38 +++++++++++ poetry.lock | 95 ++++++++++++++++++++++++++-- pyproject.toml | 3 +- 3 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 guardrails/functional/chain/guard.py diff --git a/guardrails/functional/chain/guard.py b/guardrails/functional/chain/guard.py new file mode 100644 index 000000000..d544caf67 --- /dev/null +++ b/guardrails/functional/chain/guard.py @@ -0,0 +1,38 @@ +import json +from copy import deepcopy +from typing import Dict, Optional, TypeVar + +from langchain_core.messages import BaseMessage +from langchain_core.runnables.base import Runnable, RunnableConfig + +from guardrails.errors import ValidationError +from guardrails.functional.guard import Guard as FGuard + +T = TypeVar("T", str, BaseMessage) + + +class Guard(FGuard, Runnable): + def invoke(self, input: T, config: Optional[RunnableConfig] = None) -> T: + output = deepcopy(input) + str_input = input + input_is_chat_message = False + if isinstance(input, BaseMessage): + input_is_chat_message = True + str_input = input.content + + response = self.validate(str_input) + + validated_output = response.validated_output + if not validated_output: + raise ValidationError(( + "The response from the LLM failed validation!" + "See `guard.history` for more details." + )) + + if isinstance(validated_output, Dict): + validated_output = json.dumps(validated_output) + + if input_is_chat_message: + output.content = validated_output + return output + return validated_output diff --git a/poetry.lock b/poetry.lock index c8d26971c..4af3443a6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1648,29 +1648,62 @@ optional = true python-versions = ">=3.7" files = [ {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, + {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, + {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, + {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, + {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, + {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, + {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, + {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, + {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, + {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, + {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, + {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, + {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, + {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, + {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, + {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, + {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, + {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, + {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, + {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, + {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, + {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, ] @@ -2156,11 +2189,25 @@ test = ["coverage (>=4.5.1)", "responses (>=0.12.0)"] test-no-urls = ["coverage (>=4.5.1)"] urls = ["requests (>=2.18.4)", "validators (>=0.14.2)"] +[[package]] +name = "jsonpatch" +version = "1.33" +description = "Apply JSON-Patches (RFC 6902)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, + {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, +] + +[package.dependencies] +jsonpointer = ">=1.9" + [[package]] name = "jsonpointer" version = "2.4" description = "Identify specific nodes in a JSON document (RFC 6901)" -optional = true +optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" files = [ {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, @@ -2530,6 +2577,30 @@ completion = ["shtab (>=1.1.0)"] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +[[package]] +name = "langchain-core" +version = "0.1.18" +description = "Building applications with LLMs through composability" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langchain_core-0.1.18-py3-none-any.whl", hash = "sha256:5a60dc3c391b33834fb9c8b072abd7a0df4cbba8ce88eb1bcb288844000ab759"}, + {file = "langchain_core-0.1.18.tar.gz", hash = "sha256:ad470b21cdfdc75e829cd91c8d8eb7e0438ab8ddb5b50828125ff7ada121ee7b"}, +] + +[package.dependencies] +anyio = ">=3,<5" +jsonpatch = ">=1.33,<2.0" +langsmith = ">=0.0.83,<0.1" +packaging = ">=23.2,<24.0" +pydantic = ">=1,<3" +PyYAML = ">=5.3" +requests = ">=2,<3" +tenacity = ">=8.1.0,<9.0.0" + +[package.extras] +extended-testing = ["jinja2 (>=3,<4)"] + [[package]] name = "langcodes" version = "3.3.0" @@ -2544,6 +2615,21 @@ files = [ [package.extras] data = ["language-data (>=1.1,<2.0)"] +[[package]] +name = "langsmith" +version = "0.0.86" +description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langsmith-0.0.86-py3-none-any.whl", hash = "sha256:7af15c36edb8c9fd9ae5c6d4fb940eb1da668b630a703d63c90c91e9be53aefb"}, + {file = "langsmith-0.0.86.tar.gz", hash = "sha256:c1572824664810c4425b17f2d1e9a59d53992e6898df22a37236c62d3c80f59e"}, +] + +[package.dependencies] +pydantic = ">=1,<3" +requests = ">=2,<3" + [[package]] name = "lightning-utilities" version = "0.10.0" @@ -2602,6 +2688,7 @@ files = [ {file = "lxml-4.9.4-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e8f9f93a23634cfafbad6e46ad7d09e0f4a25a2400e4a64b1b7b7c0fbaa06d9d"}, {file = "lxml-4.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3f3f00a9061605725df1816f5713d10cd94636347ed651abdbc75828df302b20"}, {file = "lxml-4.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:953dd5481bd6252bd480d6ec431f61d7d87fdcbbb71b0d2bdcfc6ae00bb6fb10"}, + {file = "lxml-4.9.4-cp312-cp312-win32.whl", hash = "sha256:266f655d1baff9c47b52f529b5f6bec33f66042f65f7c56adde3fcf2ed62ae8b"}, {file = "lxml-4.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:f1faee2a831fe249e1bae9cbc68d3cd8a30f7e37851deee4d7962b17c410dd56"}, {file = "lxml-4.9.4-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:23d891e5bdc12e2e506e7d225d6aa929e0a0368c9916c1fddefab88166e98b20"}, {file = "lxml-4.9.4-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e96a1788f24d03e8d61679f9881a883ecdf9c445a38f9ae3f3f193ab6c591c66"}, @@ -3888,8 +3975,8 @@ files = [ [package.dependencies] numpy = [ {version = ">=1.20.3", markers = "python_version < \"3.10\""}, - {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, + {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -7887,5 +7974,5 @@ vectordb = ["faiss-cpu", "numpy"] [metadata] lock-version = "2.0" -python-versions = "^3.8" -content-hash = "812b50a74ce902958c5ec258e4d0468ac40319a791e730dc47d94a20301ea77f" +python-versions = "^3.8.1" +content-hash = "71bd5e0908d8c6f1ec82fcfe911eeaf5300a26bdb15dcd73cc0db06c309daa0a" diff --git a/pyproject.toml b/pyproject.toml index 158e91caa..08096fcd4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ packages = [ guardrails = "guardrails.cli:cli" [tool.poetry.dependencies] -python = "^3.8" +python = "^3.8.1" lxml = "^4.9.3" openai = "<2" rich = "^13.6.0" @@ -51,6 +51,7 @@ huggingface_hub = {version = "^0.16.4", optional = true} pydash = "^7.0.6" docspec_python = "2.2.1" pydoc-markdown = "4.8.2" +langchain-core = "^0.1.18" [tool.poetry.extras] From 28952cd622778ec9dd7b3ad4276210cc53e0ff7c Mon Sep 17 00:00:00 2001 From: Caleb Courier Date: Mon, 5 Feb 2024 14:17:59 -0800 Subject: [PATCH 4/4] type "fixes" --- guardrails/functional/chain/guard.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/guardrails/functional/chain/guard.py b/guardrails/functional/chain/guard.py index d544caf67..76d954758 100644 --- a/guardrails/functional/chain/guard.py +++ b/guardrails/functional/chain/guard.py @@ -1,9 +1,9 @@ import json from copy import deepcopy -from typing import Dict, Optional, TypeVar +from typing import Dict, Optional, TypeVar, cast from langchain_core.messages import BaseMessage -from langchain_core.runnables.base import Runnable, RunnableConfig +from langchain_core.runnables import Runnable, RunnableConfig from guardrails.errors import ValidationError from guardrails.functional.guard import Guard as FGuard @@ -13,26 +13,31 @@ class Guard(FGuard, Runnable): def invoke(self, input: T, config: Optional[RunnableConfig] = None) -> T: - output = deepcopy(input) - str_input = input + output = BaseMessage(content="", type="") + str_input = None input_is_chat_message = False if isinstance(input, BaseMessage): input_is_chat_message = True - str_input = input.content + str_input = str(input.content) + output = deepcopy(input) + else: + str_input = str(input) response = self.validate(str_input) validated_output = response.validated_output if not validated_output: - raise ValidationError(( - "The response from the LLM failed validation!" - "See `guard.history` for more details." - )) + raise ValidationError( + ( + "The response from the LLM failed validation!" + "See `guard.history` for more details." + ) + ) if isinstance(validated_output, Dict): validated_output = json.dumps(validated_output) if input_is_chat_message: output.content = validated_output - return output - return validated_output + return cast(T, output) + return cast(T, validated_output)