Skip to content

Commit fe2c0b7

Browse files
authored
Merge pull request #1085 from guardrails-ai/feat/validation-summary
Add Validation failure information to Validation Outcome
2 parents c7967db + 46dc05b commit fe2c0b7

File tree

5 files changed

+89
-29
lines changed

5 files changed

+89
-29
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# TODO Temp to update once generated class is in
2+
from typing import Iterator, List
3+
4+
from guardrails.classes.generic.arbitrary_model import ArbitraryModel
5+
from guardrails.classes.validation.validation_result import FailResult
6+
from guardrails.classes.validation.validator_logs import ValidatorLogs
7+
from guardrails_api_client import ValidationSummary as IValidationSummary
8+
9+
10+
class ValidationSummary(IValidationSummary, ArbitraryModel):
11+
@staticmethod
12+
def _generate_summaries_from_validator_logs(
13+
validator_logs: List[ValidatorLogs],
14+
) -> Iterator["ValidationSummary"]:
15+
"""
16+
Generate a list of ValidationSummary objects from a list of
17+
ValidatorLogs objects. Using an iterator to allow serializing
18+
the summaries to other formats.
19+
"""
20+
for log in validator_logs:
21+
validation_result = log.validation_result
22+
is_fail_result = isinstance(validation_result, FailResult)
23+
failure_reason = validation_result.error_message if is_fail_result else None
24+
error_spans = validation_result.error_spans if is_fail_result else []
25+
yield ValidationSummary(
26+
validatorName=log.validator_name,
27+
validatorStatus=log.validation_result.outcome, # type: ignore
28+
propertyPath=log.property_path,
29+
failureReason=failure_reason,
30+
errorSpans=error_spans, # type: ignore
31+
)
32+
33+
@staticmethod
34+
def from_validator_logs(
35+
validator_logs: List[ValidatorLogs],
36+
) -> List["ValidationSummary"]:
37+
summaries = []
38+
for summary in ValidationSummary._generate_summaries_from_validator_logs(
39+
validator_logs
40+
):
41+
summaries.append(summary)
42+
return summaries
43+
44+
@staticmethod
45+
def from_validator_logs_only_fails(
46+
validator_logs: List[ValidatorLogs],
47+
) -> List["ValidationSummary"]:
48+
summaries = []
49+
for summary in ValidationSummary._generate_summaries_from_validator_logs(
50+
validator_logs
51+
):
52+
if summary.failure_reason:
53+
summaries.append(summary)
54+
return summaries

guardrails/classes/validation_outcome.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Generic, Iterator, Optional, Tuple, Union, cast
1+
from typing import Generic, Iterator, List, Optional, Tuple, Union, cast
22

33
from pydantic import Field
44
from rich.pretty import pretty_repr
@@ -11,6 +11,7 @@
1111
from guardrails.classes.history import Call, Iteration
1212
from guardrails.classes.output_type import OT
1313
from guardrails.classes.generic.arbitrary_model import ArbitraryModel
14+
from guardrails.classes.validation.validation_summary import ValidationSummary
1415
from guardrails.constants import pass_status
1516
from guardrails.utils.safe_get import safe_get
1617

@@ -31,6 +32,11 @@ class ValidationOutcome(IValidationOutcome, ArbitraryModel, Generic[OT]):
3132
error: If the validation failed, this field will contain the error message
3233
"""
3334

35+
validation_summaries: Optional[List["ValidationSummary"]] = Field(
36+
description="The summaries of the validation results.", default=[]
37+
)
38+
"""The summaries of the validation results."""
39+
3440
raw_llm_output: Optional[str] = Field(
3541
description="The raw, unchanged output from the LLM call.", default=None
3642
)
@@ -75,6 +81,10 @@ def from_guard_history(cls, call: Call):
7581
list(last_iteration.reasks), 0
7682
)
7783
validation_passed = call.status == pass_status
84+
validator_logs = last_iteration.validator_logs or []
85+
validation_summaries = ValidationSummary.from_validator_logs_only_fails(
86+
validator_logs
87+
)
7888
reask = last_output if isinstance(last_output, ReAsk) else None
7989
error = call.error
8090
output = cast(OT, call.guarded_output)
@@ -84,6 +94,7 @@ def from_guard_history(cls, call: Call):
8494
validated_output=output,
8595
reask=reask,
8696
validation_passed=validation_passed,
97+
validation_summaries=validation_summaries,
8798
error=error,
8899
)
89100

guardrails/guard.py

+9
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from guardrails.classes.output_type import OT
3636
from guardrails.classes.rc import RC
3737
from guardrails.classes.validation.validation_result import ErrorSpan
38+
from guardrails.classes.validation.validation_summary import ValidationSummary
3839
from guardrails.classes.validation_outcome import ValidationOutcome
3940
from guardrails.classes.execution import GuardExecutionOptions
4041
from guardrails.classes.generic import Stack
@@ -1217,6 +1218,13 @@ def _single_server_call(self, *, payload: Dict[str, Any]) -> ValidationOutcome[O
12171218
)
12181219
self.history.extend([Call.from_interface(call) for call in guard_history])
12191220

1221+
validation_summaries = []
1222+
if self.history.last and self.history.last.iterations.last:
1223+
validator_logs = self.history.last.iterations.last.validator_logs
1224+
validation_summaries = ValidationSummary.from_validator_logs_only_fails(
1225+
validator_logs
1226+
)
1227+
12201228
# TODO: See if the below statement is still true
12211229
# Our interfaces are too different for this to work right now.
12221230
# Once we move towards shared interfaces for both the open source
@@ -1232,6 +1240,7 @@ def _single_server_call(self, *, payload: Dict[str, Any]) -> ValidationOutcome[O
12321240
raw_llm_output=validation_output.raw_llm_output,
12331241
validated_output=validated_output,
12341242
validation_passed=(validation_output.validation_passed is True),
1243+
validation_summaries=validation_summaries,
12351244
)
12361245
else:
12371246
raise ValueError("Guard does not have an api client!")

poetry.lock

+13-27
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ opentelemetry-sdk = "^1.24.0"
5656
opentelemetry-exporter-otlp-proto-grpc = "^1.24.0"
5757
opentelemetry-exporter-otlp-proto-http = "^1.24.0"
5858
guardrails-hub-types = "^0.0.4"
59-
guardrails-api-client = ">=0.3.8"
59+
guardrails-api-client = ">=0.3.13"
6060
diff-match-patch = "^20230430"
6161
guardrails-api = ">=0.0.1"
6262
mlflow = {version = ">=2.0.1", optional = true}

0 commit comments

Comments
 (0)