RAGSpine
参考

扩展点

面向 provider、存储、检索、分块、路由、工作流匹配、连接器与服务边缘的类型化缝。

RAGSpine 的可插拔性不是插件注册表——就是普通的结构化类型。每个 外部依赖都是一个 Python Protocol;核心依赖抽象,绝不依赖 厂商 SDK。新增一个 provider、向量存储、重排器或 OCR 引擎只需新增一个 文件,且每个重量级 SDK 都是延迟导入的,因此核心可以干净地导入,并借助 确定性的 MockProvider 完全离线运行。

下面所有的缝都是 @runtime_checkable Protocol——你的实现不需要继承 任何东西,只需要具备正确的方法签名。mypy --strict 覆盖核心,而 anthropic / openai / sentence-transformers / paddleocr 这些 SDK 只在 具体实现内部导入,绝不出现在缝里。

缝的清单

LLMProvider

corespine(经 agent/llm_provider.py 再导出)— chat(messages, tools) 返回 ChatCompletion;OpenAI chat-completions 形态。

EmbeddingBackend

retrieval/lexical/retrieval.py — 把文本批量转为向量,供可注入的向量通道使用。

ListwiseJudge

retrieval/rerank/listwise_rerank.py — 列表式重排:按最优在前返回候选索引。

OcrBackend

extraction/extractors/pdf_scanned_extractor.py — 识别单页扫描页面图像。

NarrativeRetriever

agent/agent.py — 注入编排器的叙事检索缝。

TaskQueue

service/tasks/task_queue.py — 异步作业队列(测试用 FakeQueue,生产用 RQQueue)。

SourceConnector

ingestion/source/connector.py — iter_documents() 从知识库来源(文件系统、HTTP、Notion、内存)产出 RawDoc。

RelationExtractor

graph/extractor.py — extract(chunks) 返回图的边;build_relation_graph 旁可选启用的叙事关系插槽。

Chunker

retrieval/chunking/chunker.py — 文本加 DocumentMeta 转为保留链路的 chunk;含内置实现与第三方 entry point。

LibraryRouter / FilterExtractor

retrieval/routing 与 filtering — 确定性的库选择,以及可选的结构化元数据过滤器抽取。

TemplateMatcher

workflows/matching.py — 为自然语言脚手架对仅含元数据的可运行模板引用做排序。

当前的检索与工作流缝

Chunker.chunk(text, meta, *, max_chars, overlap_chars) -> list[Chunk] 掌管分块策略。 make_chunker 支持确定性默认、版面、父子、句子窗口、法律、QA、 书籍与语义等实现,外加 ragspine.chunkers entry-point 组。 实现必须保留 doc_idsource_locator;层级/窗口数据是附加信息, 不能替代来源链路。

LibraryRouter 选出一个或多个 RoutableLibrary ID。MultiIndexRetriever 独立调用 选中的各个 NarrativeRetriever 并对结果做 RRF 融合;路由器失败时 回退到全部库。FilterExtractor 可以提出元数据谓词,但过滤器始终是 经过校验的收窄操作。

TemplateMatcher 接收的目录引用只包含匹配元数据,不含工作流提示词或 凭据。内置的词法与 ONNX 匹配器返回排好序的候选;脚手架策略 施加分数/差距阈值并校验选中的模板。自定义匹配器不会 获得文件写入或执行权限。

LLMProvider

corespine 所有,并从 src/ragspine/agent/llm_provider.py 再导出——agent 工具使用循环驱动的唯一方法。它 使用 OpenAI chat-completions 形态(单个 chat 方法)。AnthropicProvider 延迟导入 anthropic SDK 并把该形态映射到 Anthropic API;MockProvider 既不需要密钥也不需要网络。

属性

类型

**0.3.0 迁移。**它取代了旧的 create_message(*, system, messages, tools) -> ProviderResponse。这条缝现在位于家族共享核心 corespine 中,让 ragspine 与 其兄弟包共用同一份 provider 契约——参见 ADR 0012

EmbeddingBackend

src/ragspine/retrieval/lexical/retrieval.py — 向量通道的 依赖注入点。默认是 none(纯 BM25);注入它即可加上向量通道。

属性

类型

ListwiseJudge

src/ragspine/retrieval/rerank/listwise_rerank.py — 可选的 LLM 列表式重排器 缝。真实实现是 Claude(经 build_listwise_prompt / parse_listwise_response);测试使用确定性的 fake。缺席时回退到 RRF 顺序。

属性

类型

OcrBackend

src/ragspine/extraction/extractors/pdf_scanned_extractor.py — 扫描版 PDF 的 OCR/VLM 缝。真实后端(PaddleOCR)运行在 Ubuntu + GPU 上;逻辑测试使用 fake,使 渲染 → 映射 → 阈值 → 审核的流程在无 GPU 机器上也完全可测。

属性

类型

NarrativeRetriever

src/ragspine/agent/agent.py — 注入 answer_question 的叙事检索 实现。鸭子类型;省略时叙事路径会诚实降级。

属性

类型

TaskQueue

src/ragspine/service/tasks/task_queue.py — 异步作业队列。生产用 RQQueue(RQ + Redis), 测试用 FakeQueue(同步内联)。rq / redis 仅在 RQQueue 内部延迟导入。

属性

类型

SourceConnector

src/ragspine/ingestion/source/connector.py — 知识库来源缝。连接器 从你的知识库所在之处产出原始文档;默认的 FilesystemConnector 不需要 extra,HTTP / Notion 连接器在 [connectors] extra 之后延迟导入 httpx

属性

类型

内置实现按名字注册在模块级表中;第三方连接器通过 ragspine.source_connectors entry-point 组发现(名字冲突时内置实现获胜)。 可用 make_source_connector(spec=None, **kwargs) 工厂或 RAGSPINE_SOURCE_CONNECTOR 环境变量选择(none / NoneNone):

属性

类型

RelationExtractor

src/ragspine/graph/extractor.py — 由 build_relation_graph(..., relation_extractor=None) 消费的可选叙事关系插槽。取默认值 None 时,基础图 逐字节一致(没有额外的边)。参见 ADR 0015

属性

类型

通过 make_relation_extractor(spec=None, *, provider=None, profile=None, **kwargs)RAGSPINE_RELATION_EXTRACTOR 选择(noneNonedeterministic / rule / cooccurrence → 确定性默认实现;llm / on → LLM 抽取器,无 provider 可用时 诚实降级为 None)。

模型抽取的边是有用但不受信任的LLMRelationExtractorderived=model-derivedverified=unverified 标记写进每条边的元数据, 链路取自 chunk(即调用方)而非模型的自述,并丢弃任何端点被 确定性 SecurityGate 拒绝的边。模型断言的关系永远不会被解读为 受控的、已验证的事实。

实现一个 Protocol 并注入它

由于这些缝是结构化的,你只需实现相应方法并把实例传进去——无需 注册,无需基类。下面是一个基于 OpenAI 的 LLMProvider,严格对应真实的 chat 签名与 ChatCompletion 形态(这些 dataclass 来自 corespine,并从 ragspine.agent.llm_provider 再导出):

my_openai_provider.py
from typing import Any

from openai import OpenAI  # lazy: only your file imports the SDK
from corespine import ChatCompletion, Choice, ResponseMessage, Usage


class OpenAIProvider:
    """A custom LLMProvider — no subclassing, just the chat method."""

    def __init__(self, model: str = "gpt-4o") -> None:
        self._client = OpenAI()
        self._model = model

    def chat(
        self,
        messages: list[dict[str, Any]],
        *,
        tools: list[dict[str, Any]] | None = None,
    ) -> ChatCompletion:
        # The messages are already in the OpenAI chat-completions shape, so they pass
        # through; the SDK response is the same shape, so the mapping back is direct.
        resp = self._client.chat.completions.create(
            model=self._model,
            messages=messages,
            tools=tools or [],  # adapt / omit as needed
        )
        choice = resp.choices[0]
        message = ResponseMessage(role="assistant", content=choice.message.content)
        u = resp.usage
        usage = (
            Usage(
                prompt_tokens=u.prompt_tokens,
                completion_tokens=u.completion_tokens,
                total_tokens=u.total_tokens,
            )
            if u is not None
            else None
        )
        return ChatCompletion(
            choices=(Choice(index=0, message=message, finish_reason=choice.finish_reason or "stop"),),
            usage=usage,
            model=resp.model,
            id=resp.id,
        )

MockProvider 原本所在的位置注入即可:

from ragspine.agent.agent import answer_question
from ragspine.storage.fact_store import SqliteFactStore
from my_openai_provider import OpenAIProvider

store = SqliteFactStore("data/fact_metric.db")
store.init_schema()
result = answer_question("...", store, OpenAIProvider())

结构化通道的防编造机制守卫不会信任 provider 行文中的数字——找到的 事实由事实值确定性渲染,而无事实的结果无论模型输出什么都会被重写为 "未找到"。更换 provider 无法绕过这个守卫。

本页目录