Skip to content

RAG知识框架 ​

先回顾一下前面我们学过的 RAG 基础知识,一个基础的 RAG 分为两个阶段:

  1. 数据索引阶段:核心就是将数据存储到向量数据库

  2. 数据检索阶段:核心就是将用户的问题转成向量,然后从向量数据库里面找出语义相同的内容,将其作为上下文给 llm 生成答案。

image-20250528094543784

那有这些知识就够了么?

不够,光掌握这些知识,你是没办法回答这些面试题的:

  1. 如何设计一个 RAG 的 SDK?
  2. chunking 如何设计?
  3. 怎么评估 RAG 系统效果?
  4. rerank 为什么重要?
  5. Recall@K 是什么?
  6. MRR 是什么?
  7. ....

这一套课程,就是让你对整个 RAG 彻底通关的。当你学习完这套课程后,你能够对整个 RAG 有一个系统性的理解,而一个东西,当你对它有系统性理解之后,面试题无论怎么变,你都能从容应对。

如何搭建 RAG 的知识框架呢?这里我们可以对 RAG 知识进行分类,而这个分类是有讲究的,分类的视角不同,分出来的类别就不同的。这就好比针对编程语言做分类,按照强弱类型可以分出一个类别,按照编译解释可以分出来一个类别。

RAG 相关的知识,可以按照下面两个不同视角来做分类:

  1. RAG pipeline 视角
  2. RAG 工程视角

1. RAG pipeline 视角 ​

我们先来看 RAG pipeline 视角下的分类。所谓 RAG pipeline,就是整个 RAG 的工作流程,其实和我们之前所学的 RAG 知识大差不差的。这里我将其分为 5 个阶段:

  1. 数据索引阶段
  2. 检索前处理
  3. 检索操作
  4. 检索后处理
  5. 答案生成

1-1. 数据索引阶段 ​

所谓数据索引,就是将文档存储到向量数据库的过程,回忆一下我们前面所讲的知识:

  1. 加载文档
  2. 切分成 chunks
  3. 转化为向量嵌入
  4. 存入向量数据库
image-20250528101753546

这里面最重要的在于切分 chunks,这很大程度影响了后续 RAG 的检索质量,前面我们讲过,切割的时候可以:

  1. 语义结构维度:强调的是语义完整性,防止模型拿到“断句、不完整”的上下文。可以按照句子的粒度进行切割,将每一段文本按句号、问号、叹号等 标点符号 分割。

    原文

    ChatGPT 是由 OpenAI 开发的大语言模型。它基于 Transformer 架构,具有强大的语言理解和生成能力。

    切割后

    ChatGPT 是由 OpenAI 开发的大语言模型。
    它基于 Transformer 架构,具有强大的语言理解和生成能力。
  2. 实现策略维度:满足向量模型有最大词元限制,比如 OpenAI embedding 最大约 8192 词元数。

    1. 固定长度字符切分:每 N 字符为一段,适合规则性较强的文档
    2. 词元切分:每 N 个词元切一段,兼容模型的词元数限制

上面这两个策略可以组合着来使用。

另外,在切割这一块儿,你还需要理解下面的这些技术名词:

  • Simple RAG
  • Semantic Chunking
  • Recursive Chunking
  • Contextual Chunk Headers
  • Document Augmentation
  • Knowledge Graph
  • Hierarchical Indexing
  • 元数据抽取
  • 去重与清洗
  • 增量更新

这些技术名词各自的意思,我会放到具体介绍 pipeline 的时候来解释。

另外一个重点,就是关于向量数据库,这是自 AI 火起来后的一种新型数据库类型,在完成文本切分与 embedding 之后,数据通常会被存储到向量数据库或支持向量检索能力的存储系统中。

这些系统提供高效的相似度搜索(如 ANN / KNN),从而支持基于语义的检索能力。常见向量数据库如下:

向量库特点
SupabasePostgreSQL + pgvector 扩展
Weaviate云服务 + 本地部署均可
Pinecone高性能、托管服务、易接入
Milvus面向海量数据的高性能搜索
Chroma轻量级、本地优先、开发友好
MemoryVectorStore纯 JS 内存向量库(测试用)

1-2. 检索前处理 ​

英语称之为 Pre-Retrieval。

所谓检索前处理,就是完成诸如:

  • 查询转换
  • 查询扩充
  • 检索路由

等处理工作。

查询转换(Query Rewriting)

将用户的原始问题“重写”为一个更清晰、更标准、更检索友好的表达方式。例如:

原始问题转换后查询
“这个 App 支不支持退款?”“App 退款政策是什么?”
“我买的耳机怎么一直没声音?”“耳机无声音的常见原因与解决方法”

查询扩充(Query Expansion)

在不改变用户意图的前提下,添加相关词语或同义表达,让检索系统能够匹配到更多语义相关的文档。例如用户输入:

项目合同

扩充后的 Query 变成了:

["项目合同", "合作协议", "法律文件", "合同模板"]

这类操作能显著提高召回率,避免“字不对字”导致遗漏。

检索路由(Retrieval Routing)

在准备外挂语料库(知识库)的时候,往往会有多个语料库。

针对不同类型的问题,选择不同的知识库或不同的检索方式,实现多路检索策略。例如:

用户问题路由策略说明
“这款产品电池容量是多少?”路由到【产品参数知识库】
“怎么退货?”路由到【客服 FAQ 知识库】
“能再详细说明一下工作原理吗?”路由到【技术文档知识库】,或使用多跳检索

检索前处理的目的,是为后面的检索和检索后处理做必要的准备,以便提高检索阶段 召回知识 的精确度与最终生成的质量。

这里解释一下 召回率 和 精确率 这两个名词:

召回率(Recall)

指的是所有相关内容中被成功检索出来的比例。公式为:

召回率 = 检索到的相关内容数量 / 所有实际相关内容的总数

例如:实际相关文档有 10 个,系统只检索到其中 6 个,那么召回率 = 6 / 10 = 60%

精确率(Precision)

指的是检索出来的所有内容中,真正相关的占比。公式为:

精确率 = 检索到的相关内容数量 / 检索到的所有内容数量

例如:实际相关文档有 10 个,系统共返回 8 个文档,其中 6 个是相关的,那么精确率 = 6 / 8 = 75%

也就是说,召回率反映的是系统的覆盖能力,而精确率衡量的是系统的准确性。

另外,下面是 检索前 处理涉及到的你必须要理解的名词:

  • Query Transformation
  • Query Rewrite
  • Multi-query
  • Query Decomposition
  • HyDE

这些技术名词各自的意思,我会放到具体介绍 pipeline 的时候来解释。

1-3. 检索操作 ​

检索阶段分为下面几个步骤:

  1. 将 Query 转化为向量
  2. 在向量数据库中进行相似度检索,有几种方式:
    1. 余弦相似度:关注向量方向是否一致
    2. 欧氏距离:衡量向量之间的绝对距离远近
    3. 点积:综合体现方向一致性与向量大小(长度)。

这一阶段你需要掌握的技术名词:

  • Vector Search
  • Keyword Search
  • Fusion
  • RRF
  • Small-to-Big Retrieval
  • Hypothetical Retrieval
  • CRG

这些技术名词各自的意思,我会放到具体介绍 pipeline 的时候来解释。

1-4. 检索后处理 ​

英语为 Post-Retrieval,顾名思义就是对检索出的相关知识块做必要的补充处理的阶段。

即使用了很优秀的向量搜索,检索回来的 Top-K chunks 仍然可能存在这些问题:

  1. 内容相关性差:向量相似度高但语义上与用户问题不匹配
  2. 信息冗余:多个 chunks 说的是同一件事,浪费词元空间
  3. 有害内容:包含过时、不合法、敏感内容
  4. 结构混乱:拼接后的上下文缺少逻辑、排序不合用户意图

因此需要对检索结果进行进一步处理,确保大模型接收到的上下文内容是「最相关 + 最高质量 + 最结构化」的。

常见的处理有:

  1. 重排序(Reranking)
  2. 过滤无关内容(Filtering)
  3. 合并去重(Merging)
  4. 精简摘要(Summarization)
  5. 格式优化(Structuring)

经过了这些后处理之后,能够使得最需要、最合规的知识块处于上下文的最前端,从而有助于提高大模型的输出质量。

该阶段需要掌握的技术名词:

  • Re-rank(核心)
  • Context Enriched Retrieval
  • RSC
  • Context Compression
  • 文档过滤 / threshold

这些技术名词各自的意思,我会放到具体介绍 pipeline 的时候来解释。

1-5. 答案生成 ​

答案生成,就是将检索到的内容和用户问题一起发送给 llm,然后由大模型生成答案:

image-20250528134121574

构造出来的提示词大致如下:

[系统提示]:
你是一个智能客服助手,请基于以下资料回答用户的问题。

[资料内容]:
1. 本产品支持7天无理由退货。
2. 如存在质量问题,可申请退换货。
3. ...

[用户问题]:
我买的这个产品坏了还能退吗?

[你的回答]:

该阶段需要掌握的技术名词:

  • Prompt Template
  • Self-RAG
  • Grounding
  • Answer with citation
  • LLM-as-Judge
  • 多答案一致性
  • Context Compression(部分也在这里)

这些技术名词各自的意思,我会放到具体介绍 pipeline 的时候来解释。

2. RAG 工程视角 ​

前面是从 RAG 工作流程的视角做的分类,如果从工程视角来分类,就又不一样了,可以分为:

  • 数据构建系统
  • 推理系统
  • 评测系统
  • 观测系统

2-1. 数据构建系统 ​

该系统负责的就是将原始资料加工成“可被检索、可被推理”知识形态的整条链路:

  1. 文档解析
  2. 文本清洗
  3. chunk 切分
  4. 数据增强
  5. embedding
  6. 写入向量库 / 索引库

很多人学 RAG 时只盯着检索和生成,但工程上,RAG 的上限很大程度由数据构建系统决定。原因很简单:

  • 文档切不好,检索就不准
  • 元数据不全,过滤就做不了
  • embedding 质量差,召回就不行
  • 数据更新不及时,回答就过时

所以这个系统本质上是在解决:如何把“原始资料”加工成“机器可用的知识资产”

工程上重点关注:

  • 数据接入是否稳定
  • 构建速度是否可接受
  • 索引是否支持增量更新
  • chunk schema 是否统一
  • embedding 是否可替换
  • 数据是否能回溯

2-2. 推理系统 ​

推理系统就是大家最熟悉的 RAG 主链路,它是真正直接面对用户请求的在线系统,负责处理用户问题,并返回答案。

典型链路是用户提出问题,然后:

  1. query 预处理
  2. 检索
  3. 重排 / 过滤 / 上下文组装
  4. LLM 生成
  5. 返回答案

也就是说,这个系统基本覆盖了 RAG pipeline 的后 4 个阶段:检索前处理、检索、检索后处理 以及 答案生成。

工程上会重点关注:

  • 延迟
  • 吞吐
  • 检索准确率
  • 生成忠实度
  • 可扩展性
  • 容错能力

因为这些点能够决定用户体验的核心:

  1. 能不能答对
  2. 答得快不快
  3. 有没有依据
  4. 会不会胡编

2-3. 评测系统 ​

评测系统不是给用户用的,而是给研发团队用的。它的核心任务是:量化 RAG 系统效果。

它通常会准备一组测试集,然后:

  1. 批量运行 RAG

  2. 收集检索结果和生成结果

  3. 计算指标

  4. 输出评测报告

  5. 对比不同版本的输出结果,比如:

    • 同一套测试集对比 baseline 和优化版

    • 对比是否加 rerank

    • 对比不同 chunking 策略

    • 对比不同 embedding 模型

这套系统是跳出了 pipeline 之外的一套系统,RAG 最大的问题之一就是:你感觉“好像还行”,但没有量化依据。有了这套评测系统,你就能够很好的回答这些问题:

  • 是检索变好了,还是 prompt 凑巧有效
  • rerank 到底提升了多少
  • 新模型值不值得换
  • 线上事故到底是偶发还是系统性退化

因此这个系统本质上解决的是:如何用数据证明 RAG 系统到底好不好。

常见的评测内容有:

  1. 检索评测

    • Recall@K

    • Precision@K

    • MRR

    • NDCG

  2. 生成评测

    • BLEU

    • ROUGE

    • BERTScore

    • Faithfulness

    • Answer Relevance

  3. 端到端评测

    • LLM-as-Judge

    • 人工打分

    • A/B 测试

这里主要涉及到了一些指标,例如 MRR、BLEU 等,这些指标在后面具体介绍评测系统的时候再介绍。

2-4. 观测系统 ​

观测系统负责在真实线上环境中持续采集运行数据,例如:

  • 用户的query
  • 检索到的文档
  • 最终答案
  • 响应耗时
  • token 消耗
  • 错误日志
  • 用户反馈
  • 点赞 / 点踩 / 追问行为

它关注的是 真实请求发生了什么,而不是 离线测试集上的得分是多少。因为离线评测再完整,也覆盖不了真实世界的所有情况。线上会遇到:

  • 奇怪表达
  • 多轮上下文污染
  • 热点问题
  • 错别字
  • 脏数据
  • 突发流量
  • 第三方模型超时

它和评测系统的区别

这个你可以直接记成一句话:

  • 评测系统:有没有做好
  • 观测系统:有没有出问题

换句话说,评测系统关注“离线质量”,观测系统关注“线上运行状态”。通过观测系统能让你看清 RAG 在真实环境中的运行情况。

观测系统主要看什么

  1. 性能类

    • Average Latency

    • P95 / P99

    • QPS

    • 错误率

    • 超时率

  2. 业务类

    • 用户满意度

    • 追问率

    • 引用点击率

    • 反馈分布

  3. 调试类

    • 每一步耗时

    • query rewrite 后内容

    • 召回候选数

    • rerank 前后变化

    • prompt 与返回结果

3. 什么是RAG SDK? ​

前面介绍了不同视角下,对 RAG 知识做的不同分类。至此,你现在应该搭建起了整个 RAG 的知识框架,即便某个知识点不是很清楚,但是整体框架是有的,这就为下一步的学习打好了基础。

那么接下来我们要实现的 RAG,具体是一种什么样的形式呢?

考虑到 RAG 可能会接入到不同的系统里面,因此应该单独抽离出来,做成一个 RAG SDK。那说到 RAG SDK,有同学会有疑问,什么是 RAG SDK 啊?这个词感觉有一些陌生,实际上,你主要是对 SDK 这个词比较陌生。

3-1. 理解SDK ​

SDK 英语全称是 software development kit,翻译成中文是“软件开发工具包”,本质上是一套开发工具集,为开发者提供创建软件应用所需的工具、库和示例代码。

也就是说,SDK 等价于一套“别人可以直接拿来用的能力封装”。这个词最早在后端非常流行,现在逐渐扩散到了前端,平时我们开发所用到的:

  • axios
  • react
  • vue
  • firebase sdk
  • 微信支付 sdk

其实都可以看作是一种 SDK。这里以 axios 为例,当我们书写:

js
axios.get('/api')

我们没有做:

  • new XMLHttpRequest()
  • 处理 headers
  • 处理状态码
  • 处理重试

因为 axios 帮我们封装了,就是 SDK 价值的一种体现,把复杂事情封装成简单 API。

再举一个例子,这里以 RAG 为例:

js
const docs = await vectorSearch(query)
const reranked = await rerank(docs)
const answer = await generate(query, reranked)

开发者需要自己拼接 RAG 流程。但是倘若封装成 RAG SDK,那么开发者大概是这么用的:

js
const rag = createRagPipeline({
  retriever,
  reranker,
  generator
})

const result = await rag.query("AI 在医疗中的应用")

开发者在接入 RAG SDK 后,只需要 rag.query() 一行代码即可。

3-2. SDK核心 ​

SDK 不仅仅是代码的集合,更是对复杂业务逻辑的深度抽象。一般一个优秀的 SDK 需要具备这么几项:

1. 复杂性的极致封装

SDK 的本质是“减负”。

以 RAG SDK 为例,我们要将底层的:

  • Embedding 向量化
  • Vector DB 存储
  • Rerank 精排
  • Prompt 工程
  • LLM 调度
  • ....

等一系列繁琐流程,通过高层抽象封装。用户不需要关心数据是如何流转的,他们只需要调用一个简洁的 rag.query(),即可获得最终结果。

2. 面向接口编程的稳定性

接口(Interface)是 SDK 与用户之间的契约,也是最核心的资产。

js
interface Retriever {}
interface Reranker {}
interface Generator {}

通过定义标准的 Retriever、Reranker 和 Generator 接口,我们将底层逻辑与用户业务逻辑解耦。用户应当依赖于这些稳定的接口协议,而非具体的类实现,从而确保在技术栈迭代时,业务代码依然稳固。

3. 高内聚、低耦合的可扩展性

优秀的架构必须允许用户“自备零件”。就拿检索器来讲,用户可以自由更换:

  • retriever: qdrant
  • retriever: elasticsearch
  • retriever: custom

无论是想从 Qdrant 切换到 Elasticsearch,还是接入自研的检索算法,用户只需实现对应接口即可完成无缝替换。这种插件式的扩展能力,决定了 SDK 的生命周期。

3-3. RAG SDK目录结构 ​

理解了什么是 SDK 之后,接下来我们来看一下我们要做的 RAG SDK,目录层面应该如何设计。

这里我们采用 Monorepo 的设计。

packages/
  core/                    # 抽象层(接口 + 数据契约)
    types/
    spec/                  # Zod schema
    interfaces/
    pipeline/
    errors/

  runtime/                 # 推理能力(RAG 主链路)
    retriever/
    reranker/
    generator/
    context/
    pipeline/
    __tests__/             

  indexing/                # 数据构建能力
    loader/
    chunking/
    enrich/
    embedding/
    writer/
    pipeline/
    __tests__/            

  adapters/                # 第三方实现(唯一外部依赖点)
    llm/
    embedding/
    vector-store/          
    reranker/
    loader/

  observability/           # 观测能力(hook / tracing)
    hooks/
    tracing/
    metrics/

  eval/                    # 评测能力(可选模块)
    dataset/
    runner/
    metrics/
    judge/
    __tests__/           

  utils/                   # 公共工具(可选)
    logger/
    config/
    helpers/
  • core:定义“能做什么”(接口)
  • runtime:定义“怎么回答问题”(推理)
  • indexing:定义“怎么构建知识”(入库)
  • adapters:定义“用谁来实现”(OpenAI / 向量库)
  • observability:定义“怎么监控”
  • eval:定义“怎么评估”

整个 RAG SDK 的设计,涵盖了前面我们所介绍的:

  1. RAG pipeline
  2. RAG 4大系统

另外,这里回答 3 个问题:

  1. 为什么采用 Monorepo 结构?
  2. adapters包是什么?
  3. 观测系统和评测系统需要放到 RAG SDK 里面么?

1. 为什么采用 Monorepo 结构?

要理解为什么采用 monorepo 结构,得先从为什么要拆包说起。

一说到拆包,有的同学会有一个误解,认为像 工具库 和 组件库 这种可独立运行、可发布的代码才需要拆包。但实际上不是这样的,拆包的真正目的不是“运行”,而是要控制复杂度和明确边界。

也就是说,只要你某一个功能块具备下面几个特点:

  1. 被多个地方使用
  2. 逻辑开始变得复杂
  3. 需要独立演进

那么就推荐拆成一个单独的包。

理解了拆包的原则后,接下来我们来看一下包的组织信息,目前就两种:

  1. multirepo 多仓库结构
  2. monorepo 单仓库结构

那到底选哪一个?关键在于:这些包之间的关系是什么样的。

由于我们做的是一个完整的 RAG SDK,它天然包含多个子模块,例如:

  • core:接口与数据契约
  • runtime:推理链路
  • indexing:数据构建
  • eval:评测
  • observability:观测

这些模块并不是“松散独立”的,而是具有非常强的关联关系:

  • runtime 和 indexing 都依赖 core 中定义的类型和 schema
  • eval 需要直接调用 runtime 做评测
  • observability 需要贯穿整个 pipeline
  • 一个字段或结构的修改,往往会影响多个模块

换句话说,这些包的特点是:

  • 强依赖(Strong Coupling)
  • 高频联动修改(High Change Frequency)
  • 共享类型与数据结构(Shared Schema)

在这种情况下,如果采用 Multi-repo,会带来明显的问题:

  • 每次修改 core,都需要发布新版本,其他仓库需要升级依赖才能使用最新代码
  • 多个仓库之间容易出现版本不一致
  • 本地调试成本很高(跨仓库联调困难)

而 Monorepo 正好解决这些问题:

  • 所有包在同一仓库中,基于源码直接依赖
  • 修改一个模块,其他模块可以立即感知变化
  • 类型和 schema 可以天然共享
  • 支持跨模块的统一构建、测试和调试

因此,对于像 RAG SDK 这种:多模块 + 强协作 + 高频演进的工程,monorepo 是更合适的选择。

NOTE

Multi-repo 并不是“必须发版”,其实还有几种绕法:

  1. npm link(本地链接)
  2. git dependency,例如 "rag-core": "github:xxx/rag-core"
  3. 私有 registry / 内网源

但这些都有问题:

  1. 不稳定
  2. 团队协作混乱
  3. 版本不可控
  4. CI/CD 麻烦

因此现实是如果团队采用 Multirepo 开发,几乎等于必须发版,然后其他仓库需要升级依赖才能使用最新代码。

2. adapters包是什么?

要讲清楚为什么需要 adapters 包,首先我们需要提前了解一件事儿,那就是我们开发这个 RAG SDK 会依赖 langchain.

为什么会依赖 langchain 呢?

因为 langchain 已经帮我买做了:

  • LLM 抽象(OpenAI / Anthropic / etc)
  • Embedding 抽象
  • VectorStore 抽象
  • Tool / Agent(可选)

这些如果要我们自己写一遍成本很高,而且还可能不稳定。

但是,我们不能直接绑定 langchain,例如假设我们这么写:

js
import { ChatOpenAI } from "langchain"

function generate() {
  return new ChatOpenAI(...)
}

那么相当于这个 RAG SDK 和 langchain 绑死了,这所带来的问题有:

  1. 不能直接用 OpenAI SDK
  2. 不能用其他模型,只能用 langchain 支持的那些模型
  3. LangChain API 发生变化,SDK 会被拖着走
  4. 也无法支持诸如 LlamaIndex 等框架

因此回忆一下设计模式里面的开闭原则,正确的方式是:langchain 是一种“实现”,而不是“接口”。

也就是说,我们应该这么设计:

js
// core/interfaces/generator.ts
export interface Generator {
  generate(input: {
    query: string
    context: string[]
  }): Promise<string>
}

然后用 langchain 做一个适配器:

js
// adapters/langchain/generator.ts
import { ChatOpenAI } from "langchain"

export class LangChainGenerator implements Generator {
  private model = new ChatOpenAI()

  async generate({ query, context }) {
    return this.model.invoke(...)
  }
}

这样回头开发者在使用这个 SDK 的时候,能够替换:

js
const rag = createRagPipeline({
  generator: new LangChainGenerator()
})

或者

js
generator: new CustomGenerator()

这种设计模式就叫做适配器模式(Adapter Pattern)。理解了这个点,你自然也就知道为什么会有 adapters 这个包了。

3. 观测系统和评测系统需要放到 RAG SDK 里面么?

这可能是你还会存在的一个疑问:前面说了 SDK 应该简化使用,那为什么还要放 observability、eval 这些工程能力?

实际上,SDK 的职责虽说是对外提供能力以及隐藏复杂性,但这里有一个容易误解的点:SDK 不只是 功能调用,还包括 可扩展能力,但必须是接口或者扩展点,而不能是具体实现。

我们以 observability 观测系统为例,如果你这样设计:

  • logToConsole()
  • sendToLangfuse()
  • sendToDatadog()

每个方法写了你特有的实现,那么所带来的问题就是:

  • 强绑定某个工具
  • 用户无法自定义
  • SDK 变重

正确设计方式是 SDK 只提供 hook 或者扩展点,用于决定什么时候触发。至于具体要做什么,这是用开发者来决定的,例如具体是要打日志,还是要上报,还是要做分析。

下面来看一个例子:

开发者最终使用:

js
const rag = createRagPipeline({
  retriever,
  generator,
  hooks: {
    onRetrieve: (ctx) => console.log("retrieve:", ctx),
    onGenerate: (ctx) => console.log("generate:", ctx)
  }
})

await rag.query("什么是 RAG?")

runtime:核心 pipeline

js
// packages/runtime/pipeline/ragPipeline.ts

export function createRagPipeline(options) {
  const { retriever, generator, hooks } = options

  return {
    async query(query) {

      // ====== Step 1: Retrieval ======
      const docs = await retriever.search(query)

      // observability hook(插入点1)
      hooks?.onRetrieve?.({
        query,
        docs
      })

      // ====== Step 2: Generation ======
      const answer = await generator.generate({
        query,
        context: docs
      })

      // observability hook(插入点2)
      hooks?.onGenerate?.({
        query,
        docs,
        answer
      })

      return answer
    }
  }
}

observability:提供“类型 + 规范”

js
// packages/observability/hooks/types.ts

export interface RagHooks {
  onRetrieve?: (ctx: {
    query: string
    docs: any[]
  }) => void

  onGenerate?: (ctx: {
    query: string
    docs: any[]
    answer: string
  }) => void

  onError?: (err: Error) => void
}

因此,无论是 observability 还是 eval,在 SDK 中都是以接口的形式出现,而非具体实现。

markdown
提示词收集
@项目初始化需求文档 请你仔细阅读该项目初始化需求文档,把整个 rag sdk项目的架子搭建起来。

我现在使用的是vscode,而非cursor,.因此项目根目录下的.cursorrules应该是没用的,我看到你在该文件下写了一些内容,请你把这些内容做一个迁移,目标是保证能在vscode下使用的

接下来我需要你做一件事情【为每个package补src入口与导出约定】,如果涉及到文件,那么文件内容
仅仅是占位(sub)【一行代码就够了】,目前不需要书写任何的实际业务代码。

目前我们已经把项目初始化好了,我需要你落地一版文档出来,方便后面A做交接工作。另外已存在的文
档,如果存在过时的内容,请更新这些内容。

-EOF-