Skip to content

Commit df6dbab

Browse files
committed
Add format validator
1 parent b35d416 commit df6dbab

File tree

4 files changed

+348
-2
lines changed

4 files changed

+348
-2
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import ast
2+
import json
3+
from typing import Literal, Optional, Dict, Any
4+
from langevals_core.base_evaluator import (
5+
BaseEvaluator,
6+
EvaluationResultSkipped,
7+
EvaluatorEntry,
8+
EvaluationResult,
9+
EvaluatorSettings,
10+
SingleEvaluationResult,
11+
)
12+
import markdown
13+
from pydantic import Field
14+
from jsonschema import validate, ValidationError
15+
import sqlglot
16+
17+
18+
class ValidFormatSettings(EvaluatorSettings):
19+
format: Literal["json", "markdown", "python", "sql"] = "json"
20+
json_schema: Optional[Dict[str, Any]] = Field(
21+
default=None,
22+
description="JSON schema to validate against when format is 'json'",
23+
)
24+
25+
26+
class ValidFormatResult(EvaluationResult):
27+
passed: Optional[bool] = Field(
28+
default=True,
29+
description="True if the output is formatted correctly, False otherwise",
30+
)
31+
32+
33+
class ValidFormatEntry(EvaluatorEntry):
34+
output: Optional[str] = None
35+
36+
37+
class ValidFormatEvaluator(
38+
BaseEvaluator[ValidFormatEntry, ValidFormatSettings, ValidFormatResult]
39+
):
40+
"""
41+
Allows you to check if the output is a valid json, markdown, python, sql, etc.
42+
For JSON, can optionally validate against a provided schema.
43+
"""
44+
45+
name = "Valid Format Evaluator"
46+
category = "quality"
47+
default_settings = ValidFormatSettings()
48+
is_guardrail = True
49+
50+
def evaluate(self, entry: ValidFormatEntry) -> SingleEvaluationResult:
51+
if not entry.output:
52+
return EvaluationResultSkipped(details="Output is empty")
53+
54+
if self.settings.format == "json":
55+
try:
56+
parsed_json = json.loads(entry.output)
57+
if self.settings.json_schema:
58+
try:
59+
validate(instance=parsed_json, schema=self.settings.json_schema)
60+
except ValidationError as e:
61+
return ValidFormatResult(
62+
passed=False, details=f"JSON Schema validation failed: {e}"
63+
)
64+
except json.JSONDecodeError as e:
65+
return ValidFormatResult(passed=False, details=f"Invalid JSON: {e}")
66+
elif self.settings.format == "markdown":
67+
try:
68+
html_result = markdown.markdown(entry.output)
69+
# Check if the HTML output is different from plain text
70+
# If they're the same, no markdown elements were processed
71+
plain_text = entry.output.replace("\n", "").strip()
72+
html_without_p = (
73+
html_result.replace("<p>", "")
74+
.replace("</p>", "")
75+
.replace("\n", "")
76+
.strip()
77+
)
78+
if plain_text == html_without_p:
79+
return ValidFormatResult(
80+
passed=False,
81+
details="No markdown elements found. Text should contain markdown formatting like headers (#), bold (**), lists, etc.",
82+
)
83+
except Exception as e:
84+
return ValidFormatResult(passed=False, details=f"Invalid Markdown: {e}")
85+
elif self.settings.format == "python":
86+
try:
87+
ast.parse(entry.output)
88+
except Exception as e:
89+
return ValidFormatResult(passed=False, details=f"Invalid Python: {e}")
90+
elif self.settings.format == "sql":
91+
try:
92+
sqlglot.parse(entry.output)
93+
except Exception as e:
94+
return ValidFormatResult(passed=False, details=f"Invalid SQL: {e}")
95+
96+
return ValidFormatResult(passed=True)

evaluators/langevals/poetry.lock

Lines changed: 104 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

evaluators/langevals/pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[tool.poetry]
22
name = "langevals-langevals"
33
version = "0.1.7"
4-
description = "LangEvals home-made evaluators."
4+
description = "LangEvals core evaluators."
55
authors = ["Rogerio Chaves <rogerio@langwatch.ai>", "Yevhenii Budnyk <y.budnyk789@gmail.com>"]
66
license = "MIT"
77

@@ -12,6 +12,9 @@ langevals-core = { path = "../../langevals_core", develop = true }
1212
openai = ">=1.27.0"
1313
numpy = "^1.26.4"
1414
dspy-ai = "^2.4.9"
15+
markdown = "^3.7"
16+
jsonschema = "^4.23.0"
17+
sqlglot = {extras = ["rs"], version = "^25.32.0"}
1518

1619
[tool.poetry.group.test.dependencies]
1720
pytest = "^7.4.2"

0 commit comments

Comments
 (0)