21
Reviewers — where action items come from
Three reviewers, one interface. This example runs entirely offline: the model-backed ones are constructed and inspected, never called, so no API key is needed to read it. get_reviewer(name, **kwargs) default_model_for(name) RuleBasedReviewer() ClaudeReviewer(...) OpenAIReviewer(...) ChunkedReviewer(engine, ...) load_proposal(path)
examples/21_reviewers_and_chunked.py — ran in 0.22s · exit 0
Console output
============================================================================
21 · Reviewers
============================================================================
--- the registry ---
anthropic -> ClaudeReviewer default model: claude-opus-5
chunked -> ChunkedReviewer default model: claude-opus-5
claude -> ClaudeReviewer default model: claude-opus-5
gpt -> OpenAIReviewer default model: gpt-5.5
long -> ChunkedReviewer default model: claude-opus-5
map-reduce -> ChunkedReviewer default model: claude-opus-5
offline -> RuleBasedReviewer default model: None
openai -> OpenAIReviewer default model: gpt-5.5
rules -> RuleBasedReviewer default model: None
stub -> RuleBasedReviewer default model: None
--- the interface: propose(tree, brief) -> Proposal ---
RuleBasedReviewer -> 15 items, source='rule-based'
AI-001 replace_text Align payment terms with our standard 45-day cycle.
AI-002 rewrite_clause Reduce default interest and extend the suspension gr
AI-003 replace_text Tighten the breach-notification window.
AI-004 insert_text Clarify the measurement period for the liability cap
it is what generates the committed action_items.json
--- get_reviewer — by name, keyword options passed straight through ---
RuleBasedReviewer()
ClaudeReviewer(model='claude-opus-5', effort='low')
ChunkedReviewer(model='claude-opus-5', concurrency=8, segment_tokens=12000)
the CLI's --provider maps onto ChunkedReviewer's `engine` argument
--- ClaudeReviewer options ---
model = 'claude-opus-5'
effort = 'high'
max_tokens = 16000
stream = True
timeout = 300.0
fallbacks = True
--- OpenAIReviewer options ---
model = 'gpt-5.5'
effort = 'high'
max_tokens = 16000
stream = True
timeout = 300.0
--- effort levels ---
low, medium, high, xhigh, max
--- missing credentials fail loudly, naming the variable ---
RuntimeError: the OpenAI reviewer needs the openai SDK: pip install openai
--- ChunkedReviewer — every option, constructed but not called ---
engine = 'ClaudeReviewer'
segment_tokens = 25000
index_tokens = 12000
concurrency = 6
triage_enabled = True
triage_effort = 'low'
min_coverage = 0.35
max_actions = None
strategy = 'auto'
max_attempts = 3
max_split_depth = 2
backoff = 1.0
on_progress = None
cache = enabled=False refresh=False dir='docx-redline'
(triage= is stored as triage_enabled; use_cache/refresh/cache_dir live on .cache)
--- what chunked does before it calls anything ---
segment_document -> 5 segments
S00 709 tok Nimbus Software Solutions, Inc., a Delaware
S01 792 tok 3. Fees and Payment
S02 698 tok 7. Intellectual Property
S03 690 tok 11. Indemnification
S04 156 tok Exhibit C — Data Processing Addendum (Summar
build_index -> the titles-only prefix the triage call reads:
S00 Nimbus Software Solutions, Inc., a Delaware corporation with its princ (~709 tok) risk: governing-law, renewal
Nimbus Software Solutions, Inc., a Delaware corporation with its princ
Client Co., Inc., a company organized under the laws of the State of N
1 Definitions
1.1 “Authorized Users” means Customer’s employees and independen
1.2 “Customer Data” means all electronic data, text, files, or o
--- load_proposal — replay a committed plan as a Proposal ---
29 items from action_items.json
--- wiring one into the pipeline ---
RedlinePipeline(src, reviewer='rules') # offline
RedlinePipeline(src, reviewer=ClaudeReviewer()) # ANTHROPIC_API_KEY
RedlinePipeline(src, reviewer=ChunkedReviewer('claude')) # long documents
-> 15 items from the offline reviewer
Source
"""21 · Reviewers — where action items come from.
Three reviewers, one interface. This example runs entirely offline: the
model-backed ones are constructed and inspected, never called, so no API key
is needed to read it.
get_reviewer(name, **kwargs) default_model_for(name)
RuleBasedReviewer() ClaudeReviewer(...) OpenAIReviewer(...)
ChunkedReviewer(engine, ...) load_proposal(path)
"""
from _shared import PLAN, SOURCE, banner, section
from docx_redline import (
ChunkedReviewer,
ClaudeReviewer,
OpenAIReviewer,
RedlineCredentialsError,
RedlinePipeline,
RuleBasedReviewer,
default_model_for,
get_reviewer,
load_proposal,
)
from docx_redline.planning.agent import REVIEWERS
banner("21 · Reviewers")
section("the registry")
for name in sorted(REVIEWERS):
print(
f" {name:<12} -> {REVIEWERS[name].__name__:<18} default model: {default_model_for(name)}"
)
section("the interface: propose(tree, brief) -> Proposal")
import docx
from docx_redline import ClauseTree
tree = ClauseTree(docx.Document(SOURCE).element.body)
BRIEF = "Review this agreement on behalf of the Customer."
proposal = RuleBasedReviewer().propose(tree, BRIEF)
print(f" RuleBasedReviewer -> {len(proposal.action_items)} items, source={proposal.source!r}")
for item in proposal.action_items[:4]:
print(f" {item['id']} {item['type']:<16} {item.get('rationale', '')[:52]}")
print(" it is what generates the committed action_items.json")
section("get_reviewer — by name, keyword options passed straight through")
def describe(reviewer):
name = type(reviewer).__name__
bits = [
f"{a}={getattr(reviewer, a)!r}"
for a in ("model", "effort", "concurrency", "segment_tokens")
if hasattr(reviewer, a)
]
return f"{name}({', '.join(bits)})"
print(" ", describe(get_reviewer("rules")))
print(" ", describe(get_reviewer("claude", model="claude-opus-5", effort="low")))
print(" ", describe(get_reviewer("chunked", engine="claude", concurrency=8, segment_tokens=12_000)))
print(" the CLI's --provider maps onto ChunkedReviewer's `engine` argument")
section("ClaudeReviewer options")
claude = ClaudeReviewer(
model="claude-opus-5",
effort="high",
max_tokens=16_000,
api_key="not-used-here",
fallbacks=True,
stream=True,
timeout=300.0,
)
for attr in ("model", "effort", "max_tokens", "stream", "timeout", "fallbacks"):
print(f" {attr:<12} = {getattr(claude, attr)!r}")
section("OpenAIReviewer options")
openai = OpenAIReviewer(
model="gpt-5.5",
effort="high",
max_tokens=16_000,
api_key="not-used-here",
base_url=None,
stream=True,
timeout=300.0,
)
for attr in ("model", "effort", "max_tokens", "stream", "timeout"):
print(f" {attr:<12} = {getattr(openai, attr)!r}")
section("effort levels")
print(" low, medium, high, xhigh, max")
section("missing credentials fail loudly, naming the variable")
import os
saved = {k: os.environ.pop(k, None) for k in ("ANTHROPIC_API_KEY", "OPENAI_API_KEY")}
try:
try:
OpenAIReviewer().propose(tree, BRIEF)
except RedlineCredentialsError as exc:
print(" RedlineCredentialsError:", exc)
except Exception as exc:
print(f" {type(exc).__name__}: {str(exc)[:80]}")
finally:
for k, v in saved.items():
if v is not None:
os.environ[k] = v
section("ChunkedReviewer — every option, constructed but not called")
chunked = ChunkedReviewer(
"claude",
segment_tokens=25_000,
index_tokens=12_000,
concurrency=6,
triage=True,
triage_effort="low",
min_coverage=0.35,
max_actions=None,
cache_dir=None,
use_cache=True,
refresh=False,
strategy="auto",
max_attempts=3,
max_split_depth=2,
backoff=1.0,
on_progress=None,
)
for attr in (
"engine",
"segment_tokens",
"index_tokens",
"concurrency",
"triage_enabled",
"triage_effort",
"min_coverage",
"max_actions",
"strategy",
"max_attempts",
"max_split_depth",
"backoff",
"on_progress",
):
value = getattr(chunked, attr)
value = type(value).__name__ if attr == "engine" else value
print(f" {attr:<16} = {value!r}")
print(
f" {'cache':<16} = enabled={chunked.cache.enabled!r} refresh={chunked.cache.refresh!r} "
f"dir={chunked.cache.directory.name!r}"
)
print(" (triage= is stored as triage_enabled; use_cache/refresh/cache_dir live on .cache)")
section("what chunked does before it calls anything")
import docx
from docx_redline import build_index, segment_document
body = docx.Document(SOURCE).element.body
# the sample contract is only ~3k tokens, so squeeze the budget to show the cut
segments = segment_document(body, budget_tokens=800)
print(f" segment_document -> {len(segments)} segments")
for seg in segments[:5]:
print(f" {seg.id:<10} {seg.approx_tokens:>5} tok {seg.title[:44]}")
print("\n build_index -> the titles-only prefix the triage call reads:")
print("\n".join(" " + line for line in build_index(segments).splitlines()[:6]))
section("load_proposal — replay a committed plan as a Proposal")
replayed = load_proposal(PLAN)
print(f" {len(replayed.action_items)} items from {PLAN.name}")
section("wiring one into the pipeline")
print(" RedlinePipeline(src, reviewer='rules') # offline")
print(" RedlinePipeline(src, reviewer=ClaudeReviewer()) # ANTHROPIC_API_KEY")
print(" RedlinePipeline(src, reviewer=ChunkedReviewer('claude')) # long documents")
pipe = RedlinePipeline(SOURCE, reviewer="rules")
pipe.extract() # propose() reads the tree extract() builds -- call it first
print(" ->", len(pipe.propose(None).action_items), "items from the offline reviewer")
What it wrote
This example prints its result rather than saving a document — read the
console output beside it.