import multiprocessing
import json
import os
import time
import argparse
from tqdm import tqdm
from functools import partial
from .io_utils import load_jsonl
from .api import call_model
import platform
prompt_template = """你会看到一个参考资料和一些statement,请你判断对于参考资料来说statement是supported、unsupported、或者unknown,注意:
首先判断参考资料是否存在有效内容,如果参考资料中没有任何有效信息,如"page not found"页面,则认为所有statement的状态都是unknown。
除此之外,参考资料有效的情况下,对于一个statement来说,如果它包含的事实或数据在参考资料中可以全部或部分找到,就认为它是supported的(数据接受四舍五入);如果statement中所有的事实和数据在参考资料中都找不到,认为它是unsupported的。
你应该返回json列表格式,列表中的每一项包含statement的序号和判断结果,例如:
[
{{
"idx": 1,
"result": "supported"
}},
{{
"idx": 2,
"result": "unsupported"
}}
]
下面是参考资料和statements:
{reference}
{statements}
下面开始判断,直接输出json列表,不要输出任何闲聊或解释。"""
prompt_template_en = """You will be provided with a reference and some statements. Please determine whether each statement is 'supported', 'unsupported', or 'unknown' with respect to the reference. Please note:
First, assess whether the reference contains any valid content. If the reference contains no valid information, such as a 'page not found' message, then all statements should be considered 'unknown'.
If the reference is valid, for a given statement: if the facts or data it contains can be found entirely or partially within the reference, it is considered 'supported' (data accepts rounding); if all facts and data in the statement cannot be found in the reference, it is considered 'unsupported'.
You should return the result in a JSON list format, where each item in the list contains the statement's index and the judgment result, for example:
[
{{
"idx": 1,
"result": "supported"
}},
{{
"idx": 2,
"result": "unsupported"
}}
]
Below are the reference and statements:
{reference}
{statements}
Begin the assessment now. Output only the JSON list, without any conversational text or explanations."""
def validate(data, id_to_lang_map):
url = data[0]
ref = data[1]['url_content']
facts = data[1]['facts']
article_id = data[1].get('article_id')
if ref is None:
return {
"url": url,
"validate_res": [],
"error": "no reference"
}
# Determine language based on article ID
if not article_id:
error_msg = "Article ID not provided for language determination"
print(error_msg)
return {
"url": url,
"validate_res": [],
"error": error_msg
}
if article_id not in id_to_lang_map:
error_msg = f"Language not found for article ID: {article_id}"
print(error_msg)
return {
"url": url,
"validate_res": [],
"error": error_msg
}
lang = id_to_lang_map[article_id]
facts_str = '\n'.join([f"{i+1}. {fact}" for i, fact in enumerate(facts)])
if lang == "zh":
user_prompt = prompt_template.format(reference=ref, statements=facts_str)
elif lang == "en":
user_prompt = prompt_template_en.format(reference=ref, statements=facts_str)
else:
error_msg = f"Unsupported language: {lang}"
print(error_msg)
return {
"url": url,
"validate_res": [],
"error": error_msg
}
retries = 0
error = None
while retries < 3:
try:
response = call_model(user_prompt)
validate_res = json.loads(response.replace("```json", "").replace("```", ""))
for _v in validate_res:
_v['idx'] -= 1
assert len(validate_res) == len(facts)
return {
"url": url,
"validate_res": validate_res,
"error": None
}
except Exception as e:
error = str(e)
time.sleep(3)
retries += 1
return {
"url": url,
"validate_res": [],
"error": error
}
if __name__ == '__main__':
if platform.system() == 'Darwin':
try:
multiprocessing.set_start_method('spawn')
except RuntimeError:
pass
else:
try:
multiprocessing.set_start_method('fork')
except RuntimeError:
pass
parser = argparse.ArgumentParser()
parser.add_argument("--output_path", type=str, required=True)
parser.add_argument("--raw_data_path", type=str, required=True)
parser.add_argument("--query_data_path", type=str, required=True, help="Path to query data with language information")
parser.add_argument("--n_total_process", type=int, default=1)
args = parser.parse_args()
output_path = args.output_path
raw_data = load_jsonl(args.raw_data_path)
# Load the query data to get language information
query_data = load_jsonl(args.query_data_path)
# Create a mapping from ID to language
id_to_lang_map = {item['id']: item.get('language') for item in query_data if 'id' in item and 'language' in item}
if not id_to_lang_map:
raise ValueError("No valid language information found in query data")
n_total_process = args.n_total_process
# if the output file exists, load the processed ids and filter out the processed instances
if os.path.exists(output_path):
processed = [d['id'] for d in load_jsonl(output_path)]
data_to_process = [d for d in raw_data if d['id'] not in processed]
else:
data_to_process = raw_data
print(f"Processing {len(data_to_process)} instances...")
for d in tqdm(data_to_process):
# get the citations that need to be validated
citations = [(k, v) for k, v in d['citations_deduped'].items()]
# Add article_id to each citation's value for language determination
# This is where we pass the parent article's ID to each citation
article_id = d.get('id')
if not article_id:
print(f"Warning: Article has no ID field, skipping validation")
continue
for citation in citations:
citation[1]['article_id'] = article_id
if n_total_process == 1:
results = [validate(citation, id_to_lang_map) for citation in citations]
elif n_total_process > 1:
run_partial = partial(validate, id_to_lang_map=id_to_lang_map)
with multiprocessing.Pool(processes=n_total_process) as pool:
results = pool.map(run_partial, citations)
for res in results:
d['citations_deduped'][res['url']]['validate_res'] = res['validate_res']
d['citations_deduped'][res['url']]['validate_error'] = res['error']
with open(output_path, 'a+', encoding='utf-8') as f:
f.write(json.dumps(d, ensure_ascii=False) + "\n")