"""The Narrator role service (charter §5). Renders the digest, routes the model, calls Ollama with one retry, logs the call (§10), and returns raw prose — tags left intact for the client's TagExtractor to harvest (§2/§6). Raises ModelError on failure for the handler to map to a 502. """ import random from time import perf_counter from . import call_log, ollama_client, prompts, routing def run(canon_log: dict) -> str: cfg = routing.for_role("narrator") options = {**cfg.options, "seed": random.randint(0, 2**31 - 1)} messages = [ {"role": "system", "content": prompts.system_prompt("narrator")}, {"role": "user", "content": prompts.render_digest(canon_log)}, ] start = perf_counter() try: text = ollama_client.chat(cfg.model, messages, options, think=cfg.think) except ollama_client.ModelError as exc: call_log.record( role="narrator", model=cfg.model, options=options, messages=messages, canon_log=canon_log, ok=False, latency_ms=int((perf_counter() - start) * 1000), error=str(exc), ) raise call_log.record( role="narrator", model=cfg.model, options=options, messages=messages, canon_log=canon_log, ok=True, latency_ms=int((perf_counter() - start) * 1000), response=text, ) return text