13
13
from jsonschema import ValidationError , validate
14
14
15
15
16
+ def is_valid_json (s : str ) -> bool :
17
+ """
18
+ Check if the provided string is a valid JSON.
19
+
20
+ :param s: The string to be checked.
21
+ :returns: `True` if the string is a valid JSON; otherwise, `False`.
22
+ """
23
+ try :
24
+ json .loads (s )
25
+ except ValueError :
26
+ return False
27
+ return True
28
+
29
+
16
30
@component
17
31
class JsonSchemaValidator :
18
32
"""
@@ -77,13 +91,15 @@ def run(self, messages: List[ChatMessage]) -> dict:
77
91
78
92
# Default error description template
79
93
default_error_template = (
80
- "The JSON content in the next message does not conform to the provided schema.\n "
94
+ "The following generated JSON does not conform to the provided schema.\n "
95
+ "Generated JSON: {failing_json}\n "
81
96
"Error details:\n - Message: {error_message}\n "
82
97
"- Error Path in JSON: {error_path}\n "
83
98
"- Schema Path: {error_schema_path}\n "
84
99
"Please match the following schema:\n "
85
100
"{json_schema}\n "
86
- "and provide the corrected JSON content ONLY."
101
+ "and provide the corrected JSON content ONLY. Please do not output anything else than the raw corrected "
102
+ "JSON string, this is the most important part of the task. Don't use any markdown and don't add any comment."
87
103
)
88
104
89
105
def __init__ (self , json_schema : Optional [Dict [str , Any ]] = None , error_template : Optional [str ] = None ):
@@ -125,14 +141,23 @@ def run(
125
141
dictionaries.
126
142
"""
127
143
last_message = messages [- 1 ]
128
- last_message_content = json .loads (last_message .content )
144
+ if not is_valid_json (last_message .content ):
145
+ return {
146
+ "validation_error" : [
147
+ ChatMessage .from_user (
148
+ f"The message '{ last_message .content } ' is not a valid JSON object. "
149
+ f"Please provide only a valid JSON object in string format."
150
+ f"Don't use any markdown and don't add any comment."
151
+ )
152
+ ]
153
+ }
129
154
155
+ last_message_content = json .loads (last_message .content )
130
156
json_schema = json_schema or self .json_schema
131
157
error_template = error_template or self .error_template or self .default_error_template
132
158
133
159
if not json_schema :
134
160
raise ValueError ("Provide a JSON schema for validation either in the run method or in the component init." )
135
-
136
161
# fc payload is json object but subtree `parameters` is string - we need to convert to json object
137
162
# we need complete json to validate it against schema
138
163
last_message_json = self ._recursive_json_to_object (last_message_content )
@@ -149,18 +174,22 @@ def run(
149
174
else :
150
175
validate (instance = content , schema = validation_schema )
151
176
152
- return {"validated" : messages }
177
+ return {"validated" : [ last_message ] }
153
178
except ValidationError as e :
154
179
error_path = " -> " .join (map (str , e .absolute_path )) if e .absolute_path else "N/A"
155
180
error_schema_path = " -> " .join (map (str , e .absolute_schema_path )) if e .absolute_schema_path else "N/A"
156
181
157
182
error_template = error_template or self .default_error_template
158
183
159
184
recovery_prompt = self ._construct_error_recovery_message (
160
- error_template , str (e ), error_path , error_schema_path , validation_schema
185
+ error_template ,
186
+ str (e ),
187
+ error_path ,
188
+ error_schema_path ,
189
+ validation_schema ,
190
+ failing_json = last_message .content ,
161
191
)
162
- complete_message_list = [ChatMessage .from_user (recovery_prompt )] + messages
163
- return {"validation_error" : complete_message_list }
192
+ return {"validation_error" : [ChatMessage .from_user (recovery_prompt )]}
164
193
165
194
def _construct_error_recovery_message (
166
195
self ,
@@ -169,6 +198,7 @@ def _construct_error_recovery_message(
169
198
error_path : str ,
170
199
error_schema_path : str ,
171
200
json_schema : Dict [str , Any ],
201
+ failing_json : str ,
172
202
) -> str :
173
203
"""
174
204
Constructs an error recovery message using a specified template or the default one if none is provided.
@@ -178,6 +208,7 @@ def _construct_error_recovery_message(
178
208
:param error_path: The path in the JSON content where the error occurred.
179
209
:param error_schema_path: The path in the JSON schema where the error occurred.
180
210
:param json_schema: The JSON schema against which the content is validated.
211
+ :param failing_json: The generated invalid JSON string.
181
212
"""
182
213
error_template = error_template or self .default_error_template
183
214
@@ -186,6 +217,7 @@ def _construct_error_recovery_message(
186
217
error_path = error_path ,
187
218
error_schema_path = error_schema_path ,
188
219
json_schema = json_schema ,
220
+ failing_json = failing_json ,
189
221
)
190
222
191
223
def _is_openai_function_calling_schema (self , json_schema : Dict [str , Any ]) -> bool :
@@ -215,11 +247,10 @@ def _recursive_json_to_object(self, data: Any) -> Any:
215
247
if isinstance (value , str ):
216
248
try :
217
249
json_value = json .loads (value )
218
- new_dict [key ] = (
219
- self ._recursive_json_to_object (json_value )
220
- if isinstance (json_value , (dict , list ))
221
- else json_value
222
- )
250
+ if isinstance (json_value , (dict , list )):
251
+ new_dict [key ] = self ._recursive_json_to_object (json_value )
252
+ else :
253
+ new_dict [key ] = value # Preserve the original string value
223
254
except json .JSONDecodeError :
224
255
new_dict [key ] = value
225
256
elif isinstance (value , dict ):
0 commit comments