Skip to content

API: Inference Layer

Semantic front-end: Novel → Contract via Reader Response inference.

The inference layer converts parsed Module objects into fully populated ModuleContract objects. It can operate via LLM API calls or any ContractInferencer implementation.


Abstract interface

Inference interfaces for generating narrative contracts automatically.

The inference layer converts parsed Modules into ModuleContracts that include inferred ReaderState and module intent (expected_changes).

ContractInferencer

Bases: ABC

Strategy interface for contract inference.

Implementations may use: - LLM inference (OpenAI API) - deterministic NLP features (NLTK/spaCy + rules) - hybrid approaches

infer abstractmethod

infer(modules, *, novel_title)

Infer a full contract for a set of modules.

:param modules: Parsed modules from a Novel. :param novel_title: Novel title for context. :return: Fully populated list of ModuleContract objects.


LLM client

OpenAI LLM client adapter for contract inference and segmentation.

This module isolates API integration so the rest of the codebase does not depend on OpenAI SDK details.

Two call patterns are supported:

  • :meth:complete — Free-form text completion (used by segmentation).
  • :meth:infer_json — JSON-structured inference (used by contract inference).

LLMClientConfig dataclass

Configuration for the LLM client.

:param model: Model name used for inference. :param timeout_s: Request timeout (seconds).

OpenAILLMClient

OpenAI-backed LLM client using the official Python SDK.

Requires OPENAI_API_KEY to be set in the environment.

This client is the single integration point with the OpenAI API. It supports two calling modes:

  • :meth:complete for raw text responses (e.g. segmentation)
  • :meth:infer_json for JSON-structured inference (e.g. contract inference)

__init__

__init__(config=None)

Initialize the OpenAI client.

:param config: Optional :class:LLMClientConfig. Defaults are used if not provided. :raises RuntimeError: If OPENAI_API_KEY is not set or the OpenAI SDK is not installed.

complete

complete(prompt)

Call the model with a plain text prompt and return the raw text response.

Use this method when the caller expects free-form text output rather than structured JSON — for example, in LLM-based segmentation where the model returns annotated Markdown prose.

:param prompt: Full prompt string sent as the user message. :return: Raw text response from the model.

infer_json

infer_json(*, user_prompt)

Call the model and return parsed JSON.

The system prompt instructs the model to output only valid JSON.

:param user_prompt: Prompt content for the module. :return: Parsed JSON dict. :raises ValueError: If the model output is not valid JSON.

The client exposes two call patterns:

  • complete(prompt) — raw text response (used by LLMSegmenter)
  • infer_json(user_prompt=...) — structured JSON response (used by OpenAIContractInferencer)

LLM inferencer

LLM-based :class:ContractInferencer implementation.

This module provides :class:OpenAIContractInferencer, which uses :class:~novel_testbed.inference.llm_client.OpenAILLMClient to populate narrative contracts one module at a time.

Reader state is chained across modules so that each module's pre_state equals the previous module's post_state, preserving narrative continuity.

OpenAIContractInferencer

Bases: ContractInferencer

Contract inference using OpenAI LLM calls.

This inferencer treats the novel as a sequential state machine:

  • pre_state of module N equals post_state of module N-1.
  • post_state is inferred from the current module's text via the LLM.

This chaining ensures that the reader's accumulating state across the novel is represented faithfully in the generated contracts.

__init__

__init__(client)

Initialize the inferencer.

:param client: An :class:~novel_testbed.inference.llm_client.OpenAILLMClient instance used to call the LLM API.

infer

infer(modules, *, novel_title)

Infer a full contract for a sequence of modules.

Each module is sent to the LLM individually. The post_state returned by the LLM becomes the pre_state of the next module, chaining reader state across the entire novel.

:param modules: Parsed modules from a :class:~novel_testbed.models.Novel. :param novel_title: Title of the novel, passed to the LLM for context. :return: List of fully populated :class:~novel_testbed.models.ModuleContract objects in the same order as the input modules. :raises ValueError: If the LLM response is missing required keys or contains invalid field values.

Reader state is chained across modules: the post_state of module N becomes the pre_state of module N+1, so the reader's accumulated state is preserved across the full novel.


High-level pipeline helper

High-level helpers to infer a narrative contract from annotated Markdown.

infer_contract_from_markdown

infer_contract_from_markdown(markdown_text, *, title, inferencer)

Infer a complete narrative contract from annotated Markdown.

Pipeline

Annotated Markdown → Parse → Infer

This is the recommended entry point for the infer CLI command. It handles parse → infer orchestration in one call.


Prompt construction

Prompt templates for LLM-based contract inference.

build_module_prompt

build_module_prompt(*, novel_title, chapter, module_title, module_text)

Build the user prompt for a module inference request.

:param novel_title: Title of the novel. :param chapter: Chapter name. :param module_title: Module title. :param module_text: Full module body text. :return: Prompt string.

Prompts are deterministic templates. The SYSTEM_PROMPT instructs the model to return only valid JSON. build_module_prompt constructs the per-module user prompt from structural metadata and prose text.


Inference types

Types and validation helpers for inference outputs.

We keep validation lightweight and explicit to avoid introducing heavy schema dependencies in v1.

InferredState dataclass

Minimal inferred ReaderState payload returned by an inference backend.

require_keys

require_keys(obj, keys)

Require that the given dict has the specified keys.

:param obj: Object to validate. :param keys: Required keys. :raises ValueError: if any key is missing.

These frozen dataclasses define the validated intermediate representation between the raw LLM response and the final ModuleContract. They are not exposed in the public API but are used internally by OpenAIContractInferencer.


These modules allow the system to read a novel and produce a contract without manual author annotation.

They do not replace authorship. They expose it.