GST_LIVING_NOVEL / plot_extract /summariser.py
atad-tokyo's picture
Add files using upload-large-folder tool
08bfbca verified
Raw
History Blame Contribute Delete
5.05 kB
import os
import json
import json_repair
import sys
import argparse
from preprocessor import preprocess_remove_line_break
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.insert(0, project_root)
from chatbot_api import llm_factory
parser = argparse.ArgumentParser()
parser.add_argument("--source-file-path", "-s", type=str, default=r"novels/海底两万里/Twenty Thousand Leagues Under the Seas.txt")
args = parser.parse_args()
source_file_path = args.source_file_path
if os.path.isabs(source_file_path):
source_file_path = os.path.abspath(source_file_path)
else:
source_file_path = os.path.join(project_root, source_file_path)
source_dir, source_file = os.path.split(source_file_path)
source_filename, _ = os.path.splitext(source_file)
output_filename = source_filename.replace(" ", "_").lower()
summarised_filename = os.path.join(source_dir, f"{output_filename}_summarised.json")
with open(source_file_path, 'r', encoding='utf-8') as f:
full_novel_text = preprocess_remove_line_break(f.read(), " ")
chunk_size = 50000
model_type = "qwen"
model = "Qwen/Qwen3-0.6B"
llm = llm_factory(model_type=model_type, model_name=model, max_new_tokens=4096, temperature=0.1)
summariser_sys_prompt = """
You are a story summariser. You are tasked to summarise a long novel provided in chunks into a brief summary. The summary should include the main plot, characters, and all the major events.
Your output should be a json data object with the following structure:
{
"reasoning": "The reasoning of the summary",
"summary": "The summary of the novel",
"tableofcontents_gold": [
"The table of contents of the novel",
...
]
"tableofcontents_custom": [
"The table of contents of the novel",
...
]
}
And in the input you will also be given a json data object of the same structure, produced by the previous step.
The "reasoning" is your analysis and outputreasoning of the summary.
"summary" is the plain language summary of the novel. You may always rewrite, modify this and extend the last summary to make it more complete, and to include the content provided in the new chunks. The overall length of summary should not exceed 800 words. You may rewrite previous parts in more concise languages when necessary.
If there is an official table of contents provided in the provided novel, you extract it in the form of an array of strings in the "tableofcontents_gold" field, one chapter per element. "tableofcontents_gold" should be complete once filled.If "tableofcontents_gold" is filled already, do not modify it. If there is no official table of contents, leave "tableofcontents_gold" an empty array.
"tableofcontents_custom" is your custom table of contents extracted from the main body of the novel, regardless if there is an official table of contents provided in the provided novel or not. You may name and divide chapters according to the content of the novel. You may append and modify it based on the content provided in the new chunks. Every chapter need to contain a numerical index.
You should always provide all fields in the output json data object. And these output should always be produced for the whole novel, including what was provided in the previous chunks.
"""
summariser_user_prompt = """
Here is the json data object of the same structure, produced by the previous step:
{previous_summary}
Here is the novel:
{novel_text}
"""
head = 0
tail = min(head + chunk_size, len(full_novel_text))
previous_summary = "None"
gold_toc_extracted = False
while tail < len(full_novel_text):
chunk = full_novel_text[head:tail]
json_prefilled = "{\n \"reasoning\":"
messages = [
{
"role": "system",
"content": summariser_sys_prompt
},
{
"role": "user",
"content": summariser_user_prompt.format(previous_summary=json.dumps(previous_summary, indent=4), novel_text=chunk)
},
{
"role": "assistant",
"content": json_prefilled
}
]
response = llm.generate(
messages,
)
response_json_str = json_prefilled + response
response_json = json_repair.loads(response_json_str)
print(json.dumps(response_json, indent=4))
response_json_no_reasoning = {
"summary": response_json["summary"],
"tableofcontents_gold": response_json["tableofcontents_gold"],
"tableofcontents_custom": response_json["tableofcontents_custom"]
}
previous_summary = response_json_no_reasoning
if gold_toc_extracted:
response_json["tableofcontents_gold"] = previous_summary["tableofcontents_gold"]
if "tableofcontents_gold" in response_json and response_json["tableofcontents_gold"]:
gold_toc_extracted = True
with open(summarised_filename, "w", encoding='utf-8') as f:
json.dump(response_json_no_reasoning, f, indent=4)
head = tail - chunk_size // 10
tail = min(head + chunk_size, len(full_novel_text))