1
1
#!/usr/bin/env python3
2
2
"""
3
3
Fact Checker CLI - A tool to identify false or misleading claims in articles or statements
4
- using Perplexity's Sonar API with structured outputs.
4
+ using Perplexity's Sonar API.
5
+ Structured output is disabled by default.
5
6
"""
6
7
7
8
import argparse
@@ -64,12 +65,10 @@ def _get_api_key(self) -> str:
64
65
Returns:
65
66
The API key if found, empty string otherwise.
66
67
"""
67
- # Try environment variable
68
68
api_key = os .environ .get ("PPLX_API_KEY" , "" )
69
69
if api_key :
70
70
return api_key
71
71
72
- # Try to read from a file named pplx_api_key or .pplx_api_key in the current directory
73
72
for key_file in ["pplx_api_key" , ".pplx_api_key" , "PPLX_API_KEY" , ".PPLX_API_KEY" ]:
74
73
key_path = Path (key_file )
75
74
if key_path .exists ():
@@ -102,7 +101,7 @@ def _load_system_prompt(self, prompt_file: str) -> str:
102
101
"Focus on identifying false, misleading, or unsubstantiated claims."
103
102
)
104
103
105
- def check_claim (self , text : str , model : str = DEFAULT_MODEL , use_structured_output : bool = True ) -> Dict [str , Any ]:
104
+ def check_claim (self , text : str , model : str = DEFAULT_MODEL , use_structured_output : bool = False ) -> Dict [str , Any ]:
106
105
"""
107
106
Check the factual accuracy of a claim or article.
108
107
@@ -122,7 +121,6 @@ def check_claim(self, text: str, model: str = DEFAULT_MODEL, use_structured_outp
122
121
"Authorization" : f"Bearer { self .api_key } "
123
122
}
124
123
125
- # Basic message structure
126
124
data = {
127
125
"model" : model ,
128
126
"messages" : [
@@ -131,7 +129,6 @@ def check_claim(self, text: str, model: str = DEFAULT_MODEL, use_structured_outp
131
129
]
132
130
}
133
131
134
- # Add structured output format if supported and requested
135
132
can_use_structured_output = model in self .STRUCTURED_OUTPUT_MODELS and use_structured_output
136
133
if can_use_structured_output :
137
134
data ["response_format" ] = {
@@ -144,17 +141,14 @@ def check_claim(self, text: str, model: str = DEFAULT_MODEL, use_structured_outp
144
141
response .raise_for_status ()
145
142
result = response .json ()
146
143
147
- # Extract any citations from the top-level response if present
148
144
citations = result .get ("citations" , [])
149
145
150
- # Extract the content from the response
151
146
if "choices" in result and result ["choices" ] and "message" in result ["choices" ][0 ]:
152
147
content = result ["choices" ][0 ]["message" ]["content" ]
153
148
154
149
if can_use_structured_output :
155
150
try :
156
151
parsed = json .loads (content )
157
- # Merge top-level citations if they aren't already in parsed data
158
152
if citations and "citations" not in parsed :
159
153
parsed ["citations" ] = citations
160
154
return parsed
@@ -187,7 +181,6 @@ def _parse_response(self, content: str) -> Dict[str, Any]:
187
181
A dictionary with parsed JSON fields or with a fallback containing raw response and extracted citations.
188
182
"""
189
183
try :
190
- # Try to extract JSON from markdown-formatted content
191
184
if "```json" in content :
192
185
json_content = content .split ("```json" )[1 ].split ("```" )[0 ].strip ()
193
186
return json .loads (json_content )
@@ -197,7 +190,6 @@ def _parse_response(self, content: str) -> Dict[str, Any]:
197
190
else :
198
191
return json .loads (content )
199
192
except (json .JSONDecodeError , IndexError ):
200
- # Fallback: attempt to extract citations using a regex.
201
193
citations = re .findall (r"Sources?:\s*(.+)" , content )
202
194
return {
203
195
"raw_response" : content ,
@@ -224,15 +216,12 @@ def display_results(results: Dict[str, Any], format_json: bool = False):
224
216
print (json .dumps (results , indent = 2 ))
225
217
return
226
218
227
- # If structured keys exist, we update claim sources if needed.
228
219
if "overall_rating" in results :
229
- # If we have a top-level citations list, map inline markers to full URLs.
230
220
citation_list = results .get ("citations" , [])
231
221
if citation_list and "claims" in results :
232
222
for claim in results ["claims" ]:
233
223
updated_sources = []
234
224
for source in claim .get ("sources" , []):
235
- # If the source is just a marker like "[13]", extract the number.
236
225
m = re .match (r"\[(\d+)\]" , source .strip ())
237
226
if m :
238
227
idx = int (m .group (1 )) - 1
@@ -275,7 +264,6 @@ def display_results(results: Dict[str, Any], format_json: bool = False):
275
264
for source in claim ["sources" ]:
276
265
print (f" - { source } " )
277
266
278
- # Fallback for non-structured output
279
267
elif "raw_response" in results :
280
268
print ("Response:" )
281
269
print (results ["raw_response" ])
@@ -287,7 +275,6 @@ def display_results(results: Dict[str, Any], format_json: bool = False):
287
275
else :
288
276
print (f" { results ['extracted_citations' ]} " )
289
277
290
- # Also print any top-level citations if present (for extra clarity)
291
278
if "citations" in results :
292
279
print ("\n Citations:" )
293
280
for citation in results ["citations" ]:
@@ -330,17 +317,16 @@ def main():
330
317
help = "Output results as JSON"
331
318
)
332
319
parser .add_argument (
333
- "--no- structured-output" ,
320
+ "--structured-output" ,
334
321
action = "store_true" ,
335
- help = "Disable structured output format (not recommended )"
322
+ help = "Enable structured output format (default is non-structured output )"
336
323
)
337
324
338
325
args = parser .parse_args ()
339
326
340
327
try :
341
328
fact_checker = FactChecker (api_key = args .api_key , prompt_file = args .prompt_file )
342
329
343
- # Get text from file if provided
344
330
if args .file :
345
331
try :
346
332
with open (args .file , "r" , encoding = "utf-8" ) as f :
@@ -355,7 +341,7 @@ def main():
355
341
results = fact_checker .check_claim (
356
342
text ,
357
343
model = args .model ,
358
- use_structured_output = not args .no_structured_output
344
+ use_structured_output = args .structured_output
359
345
)
360
346
display_results (results , format_json = args .json )
361
347
@@ -367,4 +353,4 @@ def main():
367
353
368
354
369
355
if __name__ == "__main__" :
370
- sys .exit (main ())
356
+ sys .exit (main ())
0 commit comments