Repository navigation
Expand file tree
/
Copy pathbuild_arc_summary.py
More file actions
112 lines (90 loc) · 3.75 KB
/
Copy pathbuild_arc_summary.py
File metadata and controls
112 lines (90 loc) · 3.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/usr/bin/env python3
"""
Build a condensed arc summary for full-novel evaluation.
For each chapter: first 150 words, last 150 words, plus any dialogue.
Gives the reader panel enough to evaluate the ARC without 72k tokens.
"""
import os
import re
from pathlib import Path
from dotenv import load_dotenv
BASE_DIR = Path(__file__).parent
load_dotenv(BASE_DIR / ".env")
WRITER_MODEL = os.environ.get("AUTONOVEL_WRITER_MODEL", "claude-sonnet-4-6")
API_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
API_BASE = os.environ.get("AUTONOVEL_API_BASE_URL", "https://api.anthropic.com")
CHAPTERS_DIR = BASE_DIR / "chapters"
def call_writer(prompt, max_tokens=4000):
import httpx
headers = {
"x-api-key": API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
}
payload = {
"model": WRITER_MODEL,
"max_tokens": max_tokens,
"temperature": 0.1,
"system": "You summarize novel chapters precisely. State what HAPPENS, what CHANGES, and what QUESTIONS are left open. No evaluation. No praise. Just events and shifts.",
"messages": [{"role": "user", "content": prompt}],
}
resp = httpx.post(f"{API_BASE}/v1/messages", headers=headers, json=payload, timeout=120)
resp.raise_for_status()
return resp.json()["content"][0]["text"]
def extract_key_passages(text):
"""Get opening, closing, and best dialogue from a chapter."""
words = text.split()
opening = ' '.join(words[:150])
closing = ' '.join(words[-150:])
# Extract dialogue lines
dialogue = re.findall(r'["""]([^"""]{20,})["""]', text)
# Pick up to 3 longest dialogue lines
dialogue.sort(key=len, reverse=True)
top_dialogue = dialogue[:3]
return opening, closing, top_dialogue
def main():
summaries = []
for ch in range(1, 20):
path = CHAPTERS_DIR / f"ch_{ch:02d}.md"
text = path.read_text()
wc = len(text.split())
opening, closing, dialogue = extract_key_passages(text)
# Get a 100-word summary from the model
summary = call_writer(
f"Summarize this chapter in exactly 3 sentences. What happens, what changes, what question is left open.\n\nCHAPTER {ch}:\n{text}",
max_tokens=200
)
entry = f"""### Chapter {ch} ({wc} words)
**Summary:** {summary}
**Opening:** {opening}...
**Closing:** ...{closing}
**Key dialogue:**
"""
for d in dialogue:
entry += f'> "{d}"\n\n'
summaries.append(entry)
print(f"Ch {ch}: summarized ({wc}w)")
# Calculate total word count
total_wc = sum(len((CHAPTERS_DIR / f"ch_{c:02d}.md").read_text().split()) for c in range(1, 20))
# Assemble
full = f"""# THE SECOND SON OF THE HOUSE OF BELLS
## Full-Arc Summary for Reader Panel
This document contains chapter summaries, opening/closing passages,
and key dialogue for all 23 chapters. Total novel: {total_wc:,} words.
PREMISE: In Cantamura, a city where law is sung into binding through
specific musical intervals, 14-year-old Cass Bellwright can hear when
someone is lying -- a quarter-tone between F and F-sharp that causes
him physical pain. His older brother Perin has been bound to service
in the House of Corda for 10 years through a contract their father
allowed. The bells their family maintains contain a secret: a question
("Do you consent to be bound?") embedded in the sub-harmonics by the
city's founder 200 years ago. No one has ever heard it. No one has
ever answered. Every binding in Cantamura is technically void.
---
"""
full += '\n---\n\n'.join(summaries)
out_path = BASE_DIR / "arc_summary.md"
out_path.write_text(full)
print(f"\nSaved to {out_path} ({len(full.split())} words)")
if __name__ == "__main__":
main()