agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
97 lines • 5.14 kB
TypeScript
/**
* byHeading — cut where the author already said a section starts.
*
* Pattern: Strategy (one of `Splitter`).
* Role: rag/ layer. The default for Markdown, and the only splitter in this
* library that is **not a heuristic**.
* Emits: N/A.
*
* Every other strategy infers boundaries from typography. This one reads them:
* a Markdown `#` line is the document telling you where a section begins, and
* ignoring that in favour of a character count is throwing away the best
* information in the file.
*
* Each chunk carries its heading, which flows through to the citation the
* model sees — `<source id="refunds.md#3" heading="Refund timing">` — so a
* reader can find the passage by name rather than by counting characters.
*
* A section longer than `maxChars` is packed by paragraph within the section
* and, failing that, hard-cut; every piece keeps the heading it came from.
* Text before the first heading (a preamble, a title block) is its own
* heading-less section rather than being attached to a heading it precedes.
*
* ── The floor, and why merging goes FORWARD (8.20.0) ────────────────────────
* A section whose own body is tiny does not retrieve badly — it retrieves TOO
* WELL. Similarity is a density measure: a heading plus one preamble sentence
* concentrates its topic's vocabulary with none of its substance. Measured in
* a production corpus, a 180-character heading-and-preamble chunk outranked
* the 1,032-character body of its own section at 0.430 — and the model,
* handed a passage that PROMISES findings and contains none, fabricated a
* plausible file path to fill the gap.
*
* So a section whose body is under `minChars` is merged into the chunk that
* FOLLOWS it, under its own heading — the preamble sentence survives, leading
* the chunk it introduces, and the citation still names the section a reader
* would look up. Merging it BACKWARD would append it to the previous section's
* chunk, where it introduces nothing and its heading would be lost; merging
* forward is the direction the author's own document flows. The last section
* has no next, so a trailing short section merges backward — the one edge
* where that is the only honest option. Nothing is ever dropped.
*
* Two special cases are unconditional, independent of `minChars`:
* - a section that is heading-plus-whitespace is NEVER emitted alone. It is
* a coordinate, not a passage — the same distinction `indexDocuments`
* enforces one layer up when it refuses a passage-less document.
* - a document that is NOTHING but headings and whitespace yields no chunks
* at all: there is no passage anywhere in it to retrieve.
*/
import type { Splitter } from '../types.js';
export interface ByHeadingOptions {
/** Target chunk size in characters. Default 1000. Sections longer than this are packed within. */
readonly maxChars?: number;
/** Backward overlap. Default 150. */
readonly overlapChars?: number;
/**
* Deepest heading level that starts a new section (1 = `#` only, 6 = all).
* Default 6 — every heading is a boundary. Lower it when a document uses
* `####` for emphasis rather than structure.
*/
readonly maxLevel?: number;
/**
* The floor under a section's own body, in characters (8.20.0). A section
* whose body is shorter is merged FORWARD into the next chunk under its own
* heading — never dropped, never shipped alone. Default
* `min(250, maxChars / 4)`; see `DEFAULT_MIN_CHARS` for the field
* measurement behind the number (a 180-char heading-and-preamble chunk that
* outranked its own section's 1,032-char body and drove a fabricated
* citation).
*
* `0` disables the floor. A heading-plus-whitespace section is still never
* emitted alone — that refusal is unconditional, because a chunk with no
* body at all is a coordinate, not a passage.
*/
readonly minChars?: number;
}
/** One ATX heading line (`## Title`): where it starts and ends, its level, its title. */
export interface AtxHeading {
/** Offset of the line's first `#`. */
readonly offset: number;
/** Offset just past the line's last character (before its line break). */
readonly end: number;
/** How many `#` open it, 1–6. */
readonly level: number;
readonly title: string;
}
/**
* ATX headings only (`# Title`). Setext (`Title\n=====`) is rare in docs corpora.
*
* Exactly the lines `/^(#{1,6})[ \t]+(.+?)[ \t]*$/gm` matches, with the same
* title, found in one pass. The regex is not used because a document is input
* a deployer may not trust: on `# a` followed by a long run of blanks and one
* more letter, its lazy title stops at every blank and re-reads the rest of the
* run to see whether the line ends there — quadratic in the run (16,000 blanks
* take ~0.2 s in V8).
*/
export declare function atxHeadings(text: string): AtxHeading[];
export declare function byHeading(options?: ByHeadingOptions): Splitter;
//# sourceMappingURL=byHeading.d.ts.map