chore: import upstream snapshot with attribution
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
Docker image release / Build base image (push) Has been cancelled
Sync docs with Docusaurus / sync (push) Has been cancelled
Tests / Check if changed (push) Has been cancelled
Tests / format (push) Has been cancelled
Tests / check-imports (push) Has been cancelled
Tests / Unit / macos-latest (push) Has been cancelled
Tests / Unit / ubuntu-latest (push) Has been cancelled
Tests / Unit / windows-latest (push) Has been cancelled
Tests / mypy (push) Has been cancelled
Tests / Integration / ubuntu-latest (push) Has been cancelled
Tests / Integration / macos-latest (push) Has been cancelled
Tests / Integration / windows-latest (push) Has been cancelled
Tests / notify-slack-on-failure (push) Has been cancelled
Tests / Mark tests as completed (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
Docker image release / Build base image (push) Has been cancelled
Sync docs with Docusaurus / sync (push) Has been cancelled
Tests / Check if changed (push) Has been cancelled
Tests / format (push) Has been cancelled
Tests / check-imports (push) Has been cancelled
Tests / Unit / macos-latest (push) Has been cancelled
Tests / Unit / ubuntu-latest (push) Has been cancelled
Tests / Unit / windows-latest (push) Has been cancelled
Tests / mypy (push) Has been cancelled
Tests / Integration / ubuntu-latest (push) Has been cancelled
Tests / Integration / macos-latest (push) Has been cancelled
Tests / Integration / windows-latest (push) Has been cancelled
Tests / notify-slack-on-failure (push) Has been cancelled
Tests / Mark tests as completed (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from haystack import Pipeline, component
|
||||
from haystack.components.validators import JsonSchemaValidator
|
||||
from haystack.dataclasses import ChatMessage
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def genuine_fc_message():
|
||||
return """[{"id": "call_NJr1NBz2Th7iUWJpRIJZoJIA", "function": {"arguments": "{\\n \\"basehead\\": \\"main...amzn_chat\\",\\n \\"owner\\": \\"deepset-ai\\",\\n \\"repo\\": \\"haystack-core-integrations\\"\\n }", "name": "compare_branches"}, "type": "function"}]""" # noqa: E501
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def json_schema_github_compare():
|
||||
return {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "string", "description": "A unique identifier for the call"},
|
||||
"function": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"arguments": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"basehead": {
|
||||
"type": "string",
|
||||
"pattern": "^[^\\.]+(\\.{3}).+$",
|
||||
"description": "Branch names must be in the format 'base_branch...head_branch'",
|
||||
},
|
||||
"owner": {"type": "string", "description": "Owner of the repository"},
|
||||
"repo": {"type": "string", "description": "Name of the repository"},
|
||||
},
|
||||
"required": ["basehead", "owner", "repo"],
|
||||
"description": "Parameters for the function call",
|
||||
},
|
||||
"name": {"type": "string", "description": "Name of the function to be called"},
|
||||
},
|
||||
"required": ["arguments", "name"],
|
||||
"description": "Details of the function being called",
|
||||
},
|
||||
"type": {"type": "string", "description": "Type of the call (e.g., 'function')"},
|
||||
},
|
||||
"required": ["function", "type"],
|
||||
"description": "Structure representing a function call",
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def json_schema_github_compare_openai():
|
||||
return {
|
||||
"name": "compare_branches",
|
||||
"description": "Compares two branches in a GitHub repository",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"basehead": {
|
||||
"type": "string",
|
||||
"pattern": "^[^\\.]+(\\.{3}).+$",
|
||||
"description": "Branch names must be in the format 'base_branch...head_branch'",
|
||||
},
|
||||
"owner": {"type": "string", "description": "Owner of the repository"},
|
||||
"repo": {"type": "string", "description": "Name of the repository"},
|
||||
},
|
||||
"required": ["basehead", "owner", "repo"],
|
||||
"description": "Parameters for the function call",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class TestJsonSchemaValidator:
|
||||
# Validates a message against a provided JSON schema successfully.
|
||||
def test_validates_message_against_json_schema(self, json_schema_github_compare, genuine_fc_message):
|
||||
validator = JsonSchemaValidator()
|
||||
message = ChatMessage.from_assistant(genuine_fc_message)
|
||||
|
||||
result = validator.run([message], json_schema_github_compare)
|
||||
|
||||
assert "validated" in result
|
||||
assert len(result["validated"]) == 1
|
||||
assert result["validated"][0] == message
|
||||
|
||||
# Validates recursive_json_to_object method
|
||||
def test_recursive_json_to_object(self, genuine_fc_message):
|
||||
arguments_is_string = json.loads(genuine_fc_message)
|
||||
assert isinstance(arguments_is_string[0]["function"]["arguments"], str)
|
||||
|
||||
# but ensure_json_objects converts the string to a json object
|
||||
validator = JsonSchemaValidator()
|
||||
result = validator._recursive_json_to_object({"key": genuine_fc_message})
|
||||
|
||||
# we need this recursive json conversion to validate the message
|
||||
assert result["key"][0]["function"]["arguments"]["basehead"] == "main...amzn_chat"
|
||||
|
||||
# Validates multiple messages against a provided JSON schema successfully.
|
||||
def test_validates_multiple_messages_against_json_schema(self, json_schema_github_compare, genuine_fc_message):
|
||||
validator = JsonSchemaValidator()
|
||||
|
||||
messages = [
|
||||
ChatMessage.from_user("I'm not being validated, but the message after me is!"),
|
||||
ChatMessage.from_assistant(genuine_fc_message),
|
||||
]
|
||||
|
||||
result = validator.run(messages, json_schema_github_compare)
|
||||
assert "validated" in result
|
||||
assert len(result["validated"]) == 1
|
||||
assert result["validated"][0] == messages[1]
|
||||
|
||||
# Validates a message against an OpenAI function calling schema successfully.
|
||||
def test_validates_message_against_openai_function_calling_schema(
|
||||
self, json_schema_github_compare_openai, genuine_fc_message
|
||||
):
|
||||
validator = JsonSchemaValidator()
|
||||
|
||||
message = ChatMessage.from_assistant(genuine_fc_message)
|
||||
result = validator.run([message], json_schema_github_compare_openai)
|
||||
|
||||
assert "validated" in result
|
||||
assert len(result["validated"]) == 1
|
||||
assert result["validated"][0] == message
|
||||
|
||||
# Validates multiple messages against an OpenAI function calling schema successfully.
|
||||
def test_validates_multiple_messages_against_openai_function_calling_schema(
|
||||
self, json_schema_github_compare_openai, genuine_fc_message
|
||||
):
|
||||
validator = JsonSchemaValidator()
|
||||
|
||||
messages = [
|
||||
ChatMessage.from_system("Common use case is that this is for example system message"),
|
||||
ChatMessage.from_assistant(genuine_fc_message),
|
||||
]
|
||||
|
||||
result = validator.run(messages, json_schema_github_compare_openai)
|
||||
|
||||
assert "validated" in result
|
||||
assert len(result["validated"]) == 1
|
||||
assert result["validated"][0] == messages[1]
|
||||
|
||||
# Constructs a custom error recovery message when validation fails.
|
||||
def test_construct_custom_error_recovery_message(self):
|
||||
validator = JsonSchemaValidator()
|
||||
|
||||
new_error_template = (
|
||||
"Error details:\n- Message: {error_message}\n"
|
||||
"- Error Path in JSON: {error_path}\n"
|
||||
"- Schema Path: {error_schema_path}\n"
|
||||
"Please match the following schema:\n"
|
||||
"{json_schema}\n"
|
||||
"Failing Json: {failing_json}\n"
|
||||
)
|
||||
|
||||
recovery_message = validator._construct_error_recovery_message(
|
||||
new_error_template, "Error message", "Error path", "Error schema path", {"type": "object"}, "Failing Json"
|
||||
)
|
||||
|
||||
expected_recovery_message = (
|
||||
"Error details:\n- Message: Error message\n"
|
||||
"- Error Path in JSON: Error path\n"
|
||||
"- Schema Path: Error schema path\n"
|
||||
"Please match the following schema:\n"
|
||||
"{'type': 'object'}\n"
|
||||
"Failing Json: Failing Json\n"
|
||||
)
|
||||
assert recovery_message == expected_recovery_message
|
||||
|
||||
def test_schema_validator_in_pipeline_validated(self, json_schema_github_compare, genuine_fc_message):
|
||||
@component
|
||||
class ChatMessageProducer:
|
||||
@component.output_types(messages=list[ChatMessage])
|
||||
def run(self):
|
||||
return {"messages": [ChatMessage.from_assistant(genuine_fc_message)]}
|
||||
|
||||
pipe = Pipeline()
|
||||
pipe.add_component(name="schema_validator", instance=JsonSchemaValidator())
|
||||
pipe.add_component(name="message_producer", instance=ChatMessageProducer())
|
||||
pipe.connect("message_producer", "schema_validator")
|
||||
result = pipe.run(data={"schema_validator": {"json_schema": json_schema_github_compare}})
|
||||
assert "validated" in result["schema_validator"]
|
||||
assert len(result["schema_validator"]["validated"]) == 1
|
||||
assert result["schema_validator"]["validated"][0].text == genuine_fc_message
|
||||
|
||||
def test_schema_validator_in_pipeline_validation_error(self, json_schema_github_compare):
|
||||
@component
|
||||
class ChatMessageProducer:
|
||||
@component.output_types(messages=list[ChatMessage])
|
||||
def run(self):
|
||||
# example json string that is not valid
|
||||
simple_invalid_json = '{"key": "value"}'
|
||||
return {"messages": [ChatMessage.from_assistant(simple_invalid_json)]} # invalid message
|
||||
|
||||
pipe = Pipeline()
|
||||
pipe.add_component(name="schema_validator", instance=JsonSchemaValidator())
|
||||
pipe.add_component(name="message_producer", instance=ChatMessageProducer())
|
||||
pipe.connect("message_producer", "schema_validator")
|
||||
result = pipe.run(data={"schema_validator": {"json_schema": json_schema_github_compare}})
|
||||
assert "validation_error" in result["schema_validator"]
|
||||
assert len(result["schema_validator"]["validation_error"]) == 1
|
||||
assert "Error details" in result["schema_validator"]["validation_error"][0].text
|
||||
Reference in New Issue
Block a user