npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fastrag/pageindex

v0.2.0

Published

TypeScript SDK for PageIndex hierarchical document indexing and optional vector enhancement.

Readme

PageIndex TS SDK

English

VectifyAI/PageIndex 的 TypeScript 重写版

传统向量 RAG 依赖语义相似度而非真正的相关性 — 但相似 ≠ 相关。PageIndex 采用不同的思路:从文档中构建层级树索引(类似"目录"),然后利用 LLM 推理在树上导航检索。无需分块,无需向量数据库 — 像人类专家一样结构化地阅读文档。

本 SDK 是 PageIndex 框架的 TypeScript 实现,面向 Node.js/TypeScript RAG 场景,完全运行时解耦。

特性

  • 基于推理的检索 — 从文档构建层级树索引,通过 LLM 驱动的树搜索替代向量相似度匹配
  • LLM 驱动的 TOC 检测 — 自动检测目录并选择最优处理模式
  • 三种处理模式toc_with_page_numberstoc_no_page_numbersno_toc,支持自动降级
  • Markdown 转树mdToTree() 将 Markdown 转换为结构化树
  • 可选向量增强 — 将树索引结果向量化,支持单文档内的精确片段检索,也适用于多文档场景下快速定位相关节点、避免逐一 LLM 树搜索
  • 运行时解耦 — 自带 LLM(LlmProvider)、PDF 解析器(DocumentParser)、向量数据库(VectorStore / Embedder

包结构

@fastrag/pageindex         ← 文档结构索引 SDK
  exports:
    .           → 核心流程
    ./types     → 共享类型 + 默认配置
    ./vector    → 向量增强
  runtime deps:
    - gpt-tokenizer

| 子路径 | 说明 | | --- | --- | | @fastrag/pageindex | 文档结构索引主流程 | | @fastrag/pageindex/types | 共享类型定义与接口契约 | | @fastrag/pageindex/vector | 向量增强:分块、索引、搜索 |

环境要求

  • Node.js >= 20
  • pnpm >= 10

安装

作为依赖安装:

pnpm add @fastrag/pageindex

在本仓库本地开发:

pnpm install
pnpm build

工程日志(问题/优化/注意点/下一步计划):docs/review/engineering-log.md

快速开始

1) 构建文档树索引

import { pageIndex } from '@fastrag/pageindex';
import type { LlmProvider, PageContent } from '@fastrag/pageindex/types';

// pageIndex 为 1-based
const pages: PageContent[] = [
  { pageIndex: 1, text: 'Table of Contents\n1 Introduction ... 1' },
  { pageIndex: 2, text: '1 Introduction\nThis document ...' },
  { pageIndex: 3, text: '2 Methods\nWe propose ...' },
];

const provider: LlmProvider = {
  async chat(messages) {
    // 接入 OpenAI / Claude / 本地模型
    return { content: '{}', finishReason: 'stop' };
  },
};

const result = await pageIndex(
  pages,
  {
    docName: 'example.pdf',
    addNodeId: true,
    addNodeSummary: true,
    addDocDescription: true,
  },
  provider,
);

console.log(result.metadata.processingMode);
console.log(result.structure);

2) Markdown 转树

import { mdToTree } from '@fastrag/pageindex';

const tree = mdToTree('# Intro\nhello\n## Background\nmore context', {
  thinning: true,
  minTokenThreshold: 500,
});

3) 向量增强

import { VectorEnhancer, InMemoryAdapter, treeChunker } from '@fastrag/pageindex/vector';
import type { Embedder } from '@fastrag/pageindex/vector';

const store = new InMemoryAdapter();

const embedder: Embedder = {
  dimension: 3,
  async embed(texts: string[]) {
    // 替换为真实 embedding 服务
    return texts.map((t) => [t.length, t.length / 2, 1]);
  },
};

const enhancer = new VectorEnhancer(
  store,
  embedder,
  treeChunker, // 也可以替换为自定义分片策略
  { chunkMaxTokens: 1000 },
);

await enhancer.index(result); // 来自 pageIndex() 的结果
const hits = await enhancer.search('introduction section');

4) 混合检索

import { HybridSearch } from '@fastrag/pageindex/vector';

const hybrid = new HybridSearch(enhancer, {
  vectorTopK: 20,
  rerankTopK: 5,
});

const results = await hybrid.search('what is the main contribution?');

核心流程

PageContent[] → TOC 检测 → 模式选择 → 验证/修复/降级 → TreeNode[] → 后处理 → PageIndexResult
  1. 扫描前 N 页检测目录
    • 提取候选目录页并抽取规范化 TOC 文本
  2. 根据 TOC 有无和页码信息选择处理模式
    • toc_with_page_numberstoc_no_page_numbersno_toc
    • no_toc 模式下,首个编号章节前的 front matter(如标题块、摘要类标题)会尽量保留为顶层条目
  3. 验证 TOC 准确性,自动修复或降级到更简单的模式
    • 包含重试/修复/降级链,准确率不足时自动触发
  4. 检查各标题是否出现在对应页面开头
    • 结果用于 endIndex 区间边界判断
    • 含确定性抖动抑制:页首强匹配可提升为 yes,页尾晚出现可降级为 no
  5. 构建 TreeNode[] 树结构
    • 将扁平 TOC 条目转换为带页码范围的层级节点
  6. 递归拆分大节点(包括已有的深层子节点,不仅限于新拆分的父节点)
    • 对超阈值节点重新解析以提升粒度
  7. 可选后处理:nodeIdnodeTextsummarydocDescription

向量增强

原版 PageIndex 使用 LLM 树搜索进行检索 — 将整个树结构作为 prompt 发送给 LLM,由 LLM 推理判断哪些节点相关。这种方式效果好,但每次查询都需要调用 LLM。

@fastrag/pageindex/vector 子路径导出提供了另一种方案:将树结构转换为向量 embedding,检索变为相似度搜索 — 查询时无需 LLM,延迟在毫秒级。

| | 索引阶段 | 检索阶段 | | --- | --- | --- | | 树搜索(原版) | LLM 构建树 | 每次查询 LLM 推理遍历树 | | 向量增强(本 SDK) | LLM 构建树 → embedding 入库 | 向量相似度搜索(无需 LLM) |

覆盖两种场景:

  • 单文档内 — 将树节点分块为 embedding,无需 LLM 调用即可快速精确定位相关片段
  • 跨多文档 — 将多个文档索引到同一个向量库,一次搜索覆盖所有文档;避免对每个文档逐一执行 LLM 树搜索

流程:PageIndexResultChunker(默认实现为 treeChunker)→ Embedder(生成向量)→ VectorStore(存储/检索)。treeChunker 当前采用“按段落切分 + 字符近似阈值”(1 token ≈ 4 chars)。每个 chunk 保留树结构元数据(docNamenodeIdtitle、页码范围),检索结果可追溯到原始文档结构。

接口抽象

LlmProvider

interface LlmProvider {
  chat(messages: LlmMessage[], options?: LlmOptions): Promise<LlmResponse>;
  chatWithSchema?(
    messages: LlmMessage[],
    schema: JsonSchema,
    options?: LlmOptions,
  ): Promise<LlmResponse>;
}

DocumentParser

interface DocumentParser {
  parse(input: string | ArrayBuffer | Uint8Array): Promise<PageContent[]>;
}

Embedder

interface Embedder {
  embed(texts: string[]): Promise<number[][]>;
  readonly dimension: number;
}

Chunker

type Chunker = (result: PageIndexResult, config?: VectorConfig) => Chunk[];

treeChunker 是可直接使用的默认策略实现,但 VectorEnhancer 需要显式注入 chunker,因此你可以按模型分词规则替换为自定义分片策略。

配置项

| 字段 | 默认值 | 说明 | | --- | ---: | --- | | tocCheckPageNum | 20 | TOC 检测最多扫描页数 | | maxPageNumEachNode | 10 | 大节点递归拆分页数阈值 | | maxTokenNumEachNode | 20000 | 大节点递归拆分 token 阈值 | | maxConcurrency | 10 | LlmClient 内部最大并发 LLM 请求数 | | tocMarkerTitles | ['contents','content','table of contents','目录'] | TOC 标记标题默认词表(多语言),用于过滤标记行 | | isTocMarkerTitle | 基于 tocMarkerTitles 的匹配器 | 可选自定义回调,用于过滤 TOC 标记行;设置后优先于 tocMarkerTitles | | addNodeId | true | 生成 4 位节点 ID | | addNodeSummary | true | 通过 LLM 生成节点摘要 | | addDocDescription | false | 生成文档一句话描述 | | addNodeText | false | 在输出中保留节点原文 | | retryConfig | 指数退避 | LLM 重试策略(maxRetries: 10initialDelayMs: 1000maxDelayMs: 30000backoffMultiplier: 2) | | onDegradation | undefined | 处理模式降级时的回调 | | onProcessingSnapshot | undefined | 可选的处理阶段快照回调(用于诊断/artifacts) | | logger | 静默 | 自定义日志注入 |

输出结构

interface PageIndexResult {
  docName: string;
  docDescription?: string;
  structure: TreeNode[];
  metadata: {
    processingMode: ProcessingMode;
    degradations: DegradationEvent[];
  };
}

延伸阅读

  • 检索策略探讨 — LLM 树搜索、MCTS、向量检索三种方案的对比,以及面向大规模文档集合的混合架构设计

许可证

MIT