GEOZ

RAG优化

2026/8/6
RAG优化

AIAI Summary (BLUF)

本文系统梳理了RAG(检索增强生成)系统的10种优化方法,涵盖语义分块、检索增强、查询转换、重排序、上下文压缩等关键环节。每种方法都给出了核心Python代码和优缺点分析,适合技术开发者在实际项目中按需选择与落地。

核心洞察

这篇文章最有意思的点是没停在理论层面,每个方法都给了能直接跑的代码。前九个方法里,语义分块查询转换最值得先试,改造成本低,收益看得见。重排序和上下文压缩效果猛,但延迟和 token 成本也跟着涨,线上场景得掂量。

RAG 的优化方法,网上零零散散讲了不少,系统串成一条线的少见。这篇按从基础到进阶的顺序整理了 20 种方法,覆盖分块策略、检索增强、查询优化、生成质量控制几个方向,每种都带核心代码。文章分五部分发,第一部分先讲九个,集中在前半程:分块怎么切、检索怎么增强、召回结果怎么筛。

核心结论

  1. 文章系统整理了 20 种 RAG 优化方法,覆盖分块策略、检索增强、查询优化、生成质量控制等方向,每种方法均附带可运行的核心代码。

  2. 前九种方法中,语义分块和查询转换被推荐优先尝试:二者改造成本低、收益明显,其中语义分块根据相邻句嵌入相似度的突降位置切分,支持百分位法、标准差法、四分位距法三种断点判定方式。

  3. 重排序和上下文压缩效果显著,但会带来额外的延迟和 token 成本;重排序建议将候选数量控制在 20 条以内,以便平衡效果与开销。

  4. 文档增强 RAG(问题生成)通过为每个文本块预生成若干“该块能回答的问题”,并将问题与原文共同入库检索,能有效缓解用户问句与文档陈述句之间的表述错位,特别适用于 FAQ 和知识问答场景。

01 语义分块

固定字数切块的毛病是它不认语义。一个话题讲到一半拦腰截断,后续的检索和生成都跟着吃亏。

语义分块的做法是先按句子算嵌入,算出相邻句子的相似度。相似度掉得厉害的位置,就是话题转换点,在那切一刀。断点判定有三种方式:

  • 百分位法,相似度低于某个百分位的位置当断点
  • 标准差法,低于均值减一个标准差的位置断开
  • 四分位距法,用 IQR 把异常低的相似度挑出来

百分位法最直观。标准差法要求相似度分布比较稳定。四分位距法对离群值更钝感,分布畸一点也能用。选哪种,看你句向量的分布情况。

# ========== 方法1:语义分块 ==========
# 根据相邻句子的嵌入相似度找“语义断点”,在断点处切块,保证块内语义连贯
def compute_breakpoints(similarities, method="percentile", threshold=90):
    """
    根据相似度序列计算断点索引:相似度突降处 = 语义边界。
    method: percentile / standard_deviation / interquartile
    """
    breakpoints = []
    if method == "percentile":
        # 低于 (100-threshold) 分位数的位置视为断点
        threshold_value = np.percentile(similarities, 100 - threshold)
        for i, sim in enumerate(similarities):
            if sim < threshold_value:
                breakpoints.append(i)
    elif method == "standard_deviation":
        # 低于「均值 - 1 个标准差」的位置视为断点
        mean_sim = np.mean(similarities)
        std_sim = np.std(similarities)
        threshold_value = mean_sim - std_sim
        for i, sim in enumerate(similarities):
            if sim < threshold_value:
                breakpoints.append(i)
    elif method == "interquartile":
        # IQR 法:低于 Q1 - 1.5*IQR 的视为异常低,作为断点
        q1 = np.percentile(similarities, 25)
        q3 = np.percentile(similarities, 75)
        iqr = q3 - q1
        threshold_value = q1 - 1.5 * iqr
        for i, sim in enumerate(similarities):
            if sim < threshold_value:
                breakpoints.append(i)
    return breakpoints

def split_into_chunks(sentences, breakpoints):
    """按断点把句子序列切成多段,每段拼成一块文本"""
    chunks = []
    start = 0
    for bp in breakpoints:
        chunk = " ".join(sentences[start:bp + 1])
        chunks.append(chunk)
        start = bp + 1
    if start < len(sentences):
        chunks.append(" ".join(sentences[start:]))
    return chunks

02 切块大小评估

chunk size 该定多少,很多人靠感觉拍。128 还是 512,差别要跑过才知道。

这个方法把这件靠感觉的事变成数据问题。拿 128、256、512 几种大小分别走一遍切块、建索引、检索、生成的完整流程,用两个指标打分:

  • 忠实度,回复是否严格基于检索到的上下文,有没有自己编
  • 相关性,回复是否答到点上

分数最高的那组参数,就是当前场景的最优切块大小。

好处是调参有了依据,新语料、新场景都能用这套流程摸出合适的大小。代价是要准备带标注或可自动评估的测试集,每轮评估都要完整跑一遍检索加生成,计算成本不低。

# ========== 方法2:切块大小评估 ==========
# 对多种 chunk size 分别建索引、检索、生成,用忠实度/相关性选最优大小
chunk_sizes = [128, 256, 512]

# 按每种大小切块并保存,便于后续统一评估
text_chunks_dict = {
    size: chunk_text(extracted_text, size, size // 5)
    for size in chunk_sizes
}

# 对每种切块大小:建嵌入 → 检索 → 生成 → 算指标
for size in chunk_sizes:
    chunks = text_chunks_dict[size]
    embeddings = create_embeddings(chunks)
    for question in test_questions:
        results = semantic_search(question, chunks, embeddings)
        context = results[0][0]
        response = generate_response(question, context)
        # 忠实度:回复是否严格基于上下文,有无幻觉
        faithfulness = evaluate_faithfulness(response, context)
        # 相关性:回复是否切题、有用
        relevancy = evaluate_relevancy(response, question)

03 上下文增强检索

检索命中的块有时候单拎出来没法看。比如一个列表被切成两半,或者一个步骤说明缺了个开头。上下文增强检索就是给这些孤岛块补上下文。

做法很直接:命中一个块之后,把它前后各 N 个相邻块一起捞出来,拼成一个连续的窗口再送去生成。

改动很小,索引都不用碰,实现基本零成本。问题在于相邻块可能混进和查询无关的内容,token 消耗也会涨。分块比较细、上下文连贯性要求高的场景,这个办法很划算。

# ========== 方法3:上下文增强检索 ==========
# 先按相似度找到 top-k 块,再对每个块扩展其前后 context_size 块,合并后返回
def context_enriched_search(query, text_chunks, embeddings, k=1, context_size=1):
    """
    检索:取相似度最高的 k 个块,每个块向左右各扩展 context_size 块并拼接。
    这样返回的是「中心块 + 前后文」的连续窗口,而非单块。
    """
    query_embedding = create_embeddings(query)
    similarities = [cosine_similarity(query_embedding, emb) for emb in embeddings]
    sorted_indices = np.argsort(similarities)[::-1]
    results = []
    for idx in sorted_indices[:k]:
        # 以当前块为中心,向左右各取 context_size 块
        start = max(0, idx - context_size)
        end = min(len(text_chunks), idx + context_size + 1)
        context_window = " ".join(text_chunks[start:end])
        results.append({
            "text": context_window,
            "similarity": similarities[idx],
            "center_chunk": idx
        })
    return results

04 上下文片段标题提取(CCH)

向量检索匹配的是表层语义,用户问一个概括性的问题,跟文档里具体的陈述句常常对不上。上下文片段标题提取(CCH)给每个块配一个标题,用 LLM 生成。建索引时同时存块的文本嵌入和标题嵌入,检索时查询分别跟两者算相似度,再取平均,作为这个块的最终得分。

标题相当于把块的"主题"单独拎出来参与匹配,概括性、概念型的问题更容易被接住。代价是建库时每块都要多一次 LLM 调用,成本和耗时会涨。块比较长、需要主题级匹配的知识库用这个比较合适。

# ========== 方法4:上下文片段标题提取(CCH) ==========
# 为每块生成标题并嵌入,检索时用 (文本相似度 + 标题相似度) 综合排序
def generate_chunk_header(chunk):
    """用 LLM 为单个文本块生成一句简洁标题,概括该块主题"""
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "为以下文本生成一个简洁的描述性标题。"},
            {"role": "user", "content": chunk}
        ]
    )
    return response.choices[0].message.content.strip()

def search_with_headers(query, chunks, text_embeddings, header_embeddings):
    """检索:查询与「块文本」和「块标题」分别算相似度,取平均作为该块得分"""
    query_embedding = create_embeddings(query)
    results = []
    for i in range(len(chunks)):
        sim_text = cosine_similarity(query_embedding, text_embeddings[i])
        sim_header = cosine_similarity(query_embedding, header_embeddings[i])
        avg_similarity = (sim_text + sim_header) / 2
        results.append({"text": chunks[i], "similarity": avg_similarity})
    results.sort(key=lambda x: x["similarity"], reverse=True)
    return results

05 文档增强 RAG(问题生成)

检索对不齐的一个常见原因,是表述错位。用户习惯用问句,文档内容是陈述句,两边措辞差太远,向量相似度就上不去。

文档增强 RAG 的办法是,在索引阶段给每个块生成几个"这个块能回答的问题",把问题和原文一起存进向量库。检索时用户 query 不管是撞上原文,还是撞上预生成的问题,都能把块捞回来。

这个思路用来做 FAQ 和知识问答这类场景特别合适。代价是建库时每块要多生成好几条问题,存储和调用成本都上去了,问题质量也依赖模型和 prompt。

# ========== 方法5:文档增强RAG(问题生成) ==========
# 为每块生成“该块能回答的问题”,问题与块一起入向量库,检索时 query 可匹配问题或块
def generate_questions(text_chunk, num_questions=5):
    """根据块内容,用 LLM 生成若干该块能回答的问题,用于后续检索匹配"""
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": f"基于以下文本,生成{num_questions}个该文本能够回答的问题。"},
            {"role": "user", "content": text_chunk}
        ]
    )
    questions = response.choices[0].message.content.strip().split("\n")
    return [q.strip() for q in questions if q.strip()]

# 建库:块本身 + 其对应问题都写入向量库,并记录问题→块的映射
vector_store = SimpleVectorStore()
for chunk in chunks:
    chunk_embedding = create_embeddings(chunk)
    vector_store.add_item(chunk, chunk_embedding, metadata={"type": "chunk"})
    questions = generate_questions(chunk)
    for question in questions:
        q_embedding = create_embeddings(question)
        vector_store.add_item(
            question, q_embedding,
            metadata={"type": "question", "source_chunk": chunk}
        )

06 查询转换

前面几个方法都在改索引,查询转换反过来,改的是查询本身。用户的问法经常带口语、指代不清,或者直接是个复合问题,原样拿去找向量,效果很难好。查询转换是在检索之前,先让 LLM 把查询收拾一遍。常见的策略有三种:

  • 查询重写。把模糊、口语化的表达改成更具体、更贴近文档的书面问法,语义不变
  • 回退提示(Step-back)。先生成一个更宽泛的背景问题,用这个背景问题先检索一轮背景知识,再结合原问题精确检索
  • 子查询分解。把多子问题的复杂查询拆成 2 到 4 个简单子查询,分别检索再合并结果

三种策略可以单独用,也可以组合。查询转换不改索引,实现成本低,对表述不规范或结构复杂的问句,效果立竿见影。代价是每多一次转换就多一次 LLM 调用,延迟和成本跟着涨。

# ========== 方法6:查询转换 ==========
# 在检索前对 query 做改写 / 回退 / 分解,再用转换后的查询去检索
def rewrite_query(original_query):
    """把模糊或口语化查询重写成更具体、更利于检索的表述(不改变意图)"""
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "你是一个查询重写专家。将用户查询重写为更具体、更清晰的形式。只输出重写后的查询,不要添加任何解释。"},
            {"role": "user", "content": original_query}
        ]
    )
    return response.choices[0].message.content.strip()

def generate_step_back_query(original_query):
    """生成一个更宽泛的“回退”问题,用于先检索背景知识,再结合原问题作答"""
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "你是一个查询生成专家。为给定查询生成一个更广泛的“回退”问题,以帮助获取相关背景知识。只输出回退查询,不要添加解释。"},
            {"role": "user", "content": original_query}
        ]
    )
    return response.choices[0].message.content.strip()

def decompose_query(original_query):
    """将复杂问题拆成 2~4 个简单子问题,便于分别检索后综合答案"""
    response = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "将以下复杂问题分解为2-4个更简单的子问题。每个子问题单独一行。只输出子问题,不要编号或解释。"},
            {"role": "user", "content": original_query}
        ]
    )
    sub_queries = response.choices[0].message.content.strip().split("\n")
    return [q.strip() for q in sub_queries if q.strip()]

07 重排序(Reranking)

向量检索返回的 top-k 是按嵌入相似度排的,这个相似度跟用户真实意图之间未必一致。重排序的思路是分两段走:先用向量(或 BM25)快速召回一批候选,再用更精细的模型对「查询-文档」逐对重新打分,把最相关的几条挑出来进生成。

打分模型可以用专用的 reranker,比如 Cross-Encoder,也可以让 LLM 直接打 0 到 10 分。LLM 这条路实现最快,但候选多了延迟会很难看。

重排之后上下文干净了很多,top-k 里的噪声块能压下去不少。代价是第二阶段多一轮模型调用,候选数量直接决定延迟和成本。一般建议把候选控制在 20 条以内再做重排。

# ========== 方法7:重排序(Reranking) ==========
# 一阶段粗检索得到候选列表,二阶段用 LLM 对「查询-文档」打相关性分并重排
def rerank_with_llm(query, documents, model="gpt-3.5-turbo"):
    """对候选文档逐一用 LLM 打 0-10 相关性分,再按分数降序重排"""
    reranked = []
    for doc in documents:
        response = client.chat.completions.create(
            model=model,
            messages=[
                {"role": "system", "content": "你是一个相关性评估专家。对给定文档与查询的相关性进行0-10打分。只输出数字分数。"},
                {"role": "user", "content": f"查询: {query}\n\n文档: {doc['text']}"}
            ],
            temperature=0
        )
        score = float(response.choices[0].message.content.strip())
        reranked.append({**doc, "rerank_score": score})
    reranked.sort(key=lambda x: x["rerank_score"], reverse=True)
    return reranked

08 相关段落提取(RSE)

前面几种方法返回的都是单块文本,遇到需要一整段论述才能答好的问题会吃亏。相关段落提取(RSE)把视角从单块拉长到区间,直接找一段价值最大的连续文本。

做法是把每块的相似度转成价值:相关块为正,不相关块减去一个惩罚项后变负。然后基于最大子数组(Kadane)算法,找价值总和最大的连续区间,把这段区间作为检索结果。

返回的上下文是连贯的段落,边界比单块拼接自然得多,适合长文档和叙述性强的材料。代价是它比较依赖块顺序和相似度曲线的质量,惩罚项、最小段落长度这些参数要单独调。

# ========== 方法8:相关段落提取(RSE) ==========
# 为每块算「相似度 - 惩罚」作为价值,用最大子数组找“价值和最大”的连续段落
def calculate_chunk_values(similarities, irrelevant_chunk_penalty=0.2):
    """把相似度转为价值:相关块为正,不相关块减去惩罚后为负,便于后续求连续最大和"""
    values = []
    for sim in similarities:
        value = sim - irrelevant_chunk_penalty
        values.append(value)
    return values

def find_best_segments(values, max_segments=3, min_segment_length=1):
    """Kadane 思想:在 value 序列上找最多 max_segments 段连续区间,使每段和最大"""
    segments = []
    n = len(values)
    for _ in range(max_segments):
        best_sum = float('-inf')
        best_start = best_end = 0
        current_sum = 0
        current_start = 0
        for i in range(n):
            current_sum += values[i]
            if current_sum > best_sum and (i - current_start + 1) >= min_segment_length:
                best_sum = current_sum
                best_start = current_start
                best_end = i
            if current_sum < 0:
                current_sum = 0
                current_start = i + 1
        if best_sum > 0:
            segments.append((best_start, best_end, best_sum))
            for i in range(best_start, best_end + 1):
                values[i] = 0
    return segments

def reconstruct_segments(chunks, segments):
    """根据 (start, end) 区间把对应块拼成完整段落文本"""
    reconstructed = []
    for start, end, score in segments:
        segment_text = " ".join(chunks[start:end + 1])
        reconstructed.append({
            "text": segment_text,
            "start": start,
            "end": end,
            "score": score
        })
    return reconstructed

09 上下文压缩

检索回来的块多了,里面总会混进和问题无关的句子。全部塞给 LLM,既费 token 又容易把生成带偏。上下文压缩是在送进生成之前,先把每块内容过一遍筛子,只留下和当前查询相关的东西。

三种压缩策略:

  • 选择性保留,从原文里筛出相关句子,保持原句原序
  • 摘要,针对查询写一段只含相关信息的摘要
  • 抽取,按查询提取关键事实或结构化信息

选择性最保守,基本不丢信息。摘要和抽取压缩力度大,token 省得多,但可能丢掉细节。

压缩每块都要调一次 LLM,延迟自然会涨。不过检索结果噪声大、上下文窗口紧张的时候,这个钱花得值。

# ========== 方法9:上下文压缩 ==========
# 对每个检索到的块,用 LLM 按 query 只保留相关部分(选择性保留/摘要/抽取)
def compress_chunk(chunk, query, compression_type="selective", model="gpt-3.5-turbo"):
    """根据 compression_type:选择性保留相关句 / 写相关摘要 / 抽取关键信息"""
    if compression_type == "selective":
        system_prompt = """你是一个信息过滤专家。分析文档块,只提取与用户查询直接相关的句子或段落。输出要求:
1. 只包含有助于回答查询的内容
2. 保留相关句子的原话,不要改写
3. 保持原文顺序
4. 包含所有相关内容
5. 排除所有与查询无关的内容"""
    elif compression_type == "summary":
        system_prompt = """你是一个摘要专家。针对用户查询,编写一段只包含相关信息的摘要。要求内容准确,不要添加原文没有的信息。"""
    elif compression_type == "extraction":
        system_prompt = """你是一个信息抽取专家。按用户查询的要求,从文本中抽取关键事实或结构化信息。只输出抽取结果,不要解释。"""
    else:
        raise ValueError(f"未知的压缩类型: {compression_type}")

    response = client.chat.completions.create(
        model=model,
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": f"查询: {query}\n\n文档: {chunk}"}
        ]
    )
    return response.choices[0].message.content.strip()

剩下的十一个方法,下一部分接着聊。

第 10 个方法,反馈回路。这个做法开始让 RAG 带记忆。

方法 10 反馈回路

思路很简单:把用户对回复的评分、点赞、纠错这些信号收回来,变成检索的一部分。记录「查询、回复、评分」,下次来新查询时,让 LLM 判断历史反馈和当前查询、当前文档有没有关系,有关系就调整文档得分。高质量问答对还可以直接写回知识库,系统越用越准。

整个过程拆成三步:先收集反馈,再按反馈修正相关性得分,最后把优质问答对存进向量库。

好处和局限:

  • 优势:真实用户信号是免费的训练数据,客服、知识库这类有稳定反馈的场景特别适合。
  • 局限:反馈少或者噪声大的时候,调整反而可能带偏检索。冷启动阶段不能太依赖它。
  • 适用场景:有点赞、打分、纠错按钮的在线系统。

代码里核心是 assess_feedback_relevanceadjust_relevance_scores 两个函数。

# 方法10:反馈回路 RAG

def assess_feedback_relevance(query, doc_text, feedback):
    # 判断一条历史反馈是否和当前查询、当前文档相关
    system_prompt = "判断一条历史反馈是否和当前查询、当前文档相关,只回答 yes 或 no。"
    user_prompt = f"""
当前查询:{query}
历史反馈中的查询:{feedback['query']}
文档内容:{doc_text[:500]}
历史反馈中的回复:{feedback['response'][:500]}

这条反馈对回答当前查询有用吗?(yes/no)
"""
    resp = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": user_prompt}
        ],
        temperature=0
    )
    answer = resp.choices[0].message.content.strip().lower()
    return "yes" in answer

def adjust_relevance_scores(query, results, feedback_data):
    if not feedback_data:
        return results

    for result in results:
        related_feedback = []
        for feedback in feedback_data:
            if assess_feedback_relevance(query, result["text"], feedback):
                related_feedback.append(feedback)

        if related_feedback:
            avg = sum(f["relevance"] for f in related_feedback) / len(related_feedback)
            result["similarity"] *= 0.5 + avg / 5.0

    return sorted(results, key=lambda x: x["similarity"], reverse=True)

def rag_with_feedback_loop(query, vector_store, feedback_data, k=5):
    results = vector_store.similarity_search(create_embeddings(query), k=k)
    results = adjust_relevance_scores(query, results, feedback_data)
    context = "\n\n---\n\n".join(r["text"] for r in results)
    return generate_response(query, context)

feedbackrelevance 字段就是用户打的分数,1 到 5。分数越高,调整后的相似度抬得越多。

方法 11 自适应检索

这个方法的切入点不是怎么改文档,而是先问一个问题:用户的 query 是不是同一种类型?

显然不是。查一个具体事实,和让模型分析利弊、给观点、结合上下文回答,检索策略应该不一样。

做法是先用 LLM 把查询分成四类:事实型、分析型、观点型、情境型。然后分别走不同的检索流程。事实型偏向精确匹配,分析型就得多召回再压缩,观点型要尽量找到不同立场,情境型还要携带用户上下文。

分类这一步不复杂,核心就是提示词写清楚类别定义。

# 方法11:自适应检索

def classify_query(query):
    system_prompt = """
把查询分成四类,只输出类别名:
- 事实型:查询具体、可验证的信息
- 分析型:需要综合分析或解释
- 观点型:主观问题或需要不同视角
- 情境型:依赖用户特定上下文
"""
    resp = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": query}
        ],
        temperature=0
    )
    category = resp.choices[0].message.content.strip()
    if category not in ["事实型", "分析型", "观点型", "情境型"]:
        return "事实型"
    return category

def adaptive_retrieval(query, vector_store, k=4, user_context=None):
    query_type = classify_query(query)

    if query_type == "事实型":
        return factual_retrieval_strategy(query, vector_store, k)
    elif query_type == "分析型":
        return analytical_retrieval_strategy(query, vector_store, k)
    elif query_type == "观点型":
        return opinion_retrieval_strategy(query, vector_store, k)
    else:
        return contextual_retrieval_strategy(query, vector_store, k, user_context)

优势和局限:

  • 优势:一套系统能同时接住多种类型的问题,不用为每种问题单独搭一套链路。
  • 局限:分类依赖领域经验,分错就全错了。另外每次查询多一次 LLM 调用,延迟会涨。

比较适合混合类型问答,以及客服、咨询这类问题边界不固定的场景。

方法 12 Self-RAG(自适应 RAG)

Self-RAG 的思路是给 RAG 装一个自我审查机制。以前是「检索完就生成」,它会在检索前、生成后各加几步判断。

完整流程里有六个决策点:

  1. 判断当前查询需不需要检索。事实型要,常识或主观题不要。
  2. 需要就走向量检索。
  3. 评估每个检索回来的文档和查询相不相关。
  4. 基于相关文档生成回复。
  5. 评估回复有没有被上下文充分支撑。
  6. 给回复的实用性打 1 到 5 分。

最后一步的效用分可以拿来挑回复,也可以当置信度展示给用户。

# 方法12:Self-RAG

def should_retrieve(query):
    system_prompt = "判断回答查询是否需要检索资料。需要就说 yes,不需要就说 no。"
    resp = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": f"查询:{query}\n需要检索吗?"}
        ],
        temperature=0
    )
    return "yes" in resp.choices[0].message.content.strip().lower()

def evaluate_relevance(query, doc):
    system_prompt = "判断文档和查询是否相关,只回答 relevant 或 irrelevant。"
    resp = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": f"查询:{query}\n文档:{doc[:2000]}"}
        ],
        temperature=0
    )
    return resp.choices[0].message.content.strip().lower()

def assess_support(response, context):
    system_prompt = "判断回复是否被上下文支撑。只回答 fully supported、partially supported 或 no support。"
    resp = client.chat.completions.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": system_prompt},
            {"role": "user", "content": f"上下文:{context[:2000]}\n回复:{response}"}
        ],
        temperature=0
    )
    return resp.choices[0].message.content.strip().lower()

def self_rag(query, vector_store, k=4):
    if not should_retrieve(query):
        return generate_response(query, context=None)

    docs = vector_store.similarity_search(create_embeddings(query), k=k)
    relevant_docs = [d for d in docs if evaluate_relevance(query, d["text"]) == "relevant"]

    if not relevant_docs:
        return "没有找到足够相关的资料"

    context = "\n\n".join(d["text"] for d in relevant_docs)
    response = generate_response(query, context)

    support = assess_support(response, context)
    if support == "no support":
        return "当前资料不足以支撑回答,已跳过生成。"

    return response

Self-RAG 优势很明显:普通问题的无谓检索少了,幻觉也少一些,输出过程能解释,也方便加 trace。代价也实在,同一个问题要来回调好几次 LLM,延迟和 token 成本都是普通 RAG 的好几倍。适合对准确性和可追溯性要求高的场景,比如医疗、金融问答,不太适合做首屏聊天机器人。

这三个方法里,反馈回路是工程上最容易落地的,把现有日志利用起来就行。自适应检索和 Self-RAG 都要多一次乃至多次模型调用,能接受延迟再上。

12 自反思 RAG

顺着反馈回路这条路再走深一点,就是自反思 RAG。它把“要不要检索”这个决定也交给模型。模型先判断当前问题需不需要查资料,再对检索回来的每段文档做相关性判断。每段文档都会生成一个候选答案,按支撑度和可用性打分,最后选总分最高的。

核心流程代码(辅助函数省略):

def self_rag(query, vector_store, top_k=3):
    # 第一步:判断是否需要检索
    print("第一步:判断是否需要检索...")
    retrieval_needed = determine_if_retrieval_needed(query)

    metrics = {
        "retrieval_needed": retrieval_needed,
        "documents_retrieved": 0,
        "relevant_documents": 0,
        "response_support_ratings": [],
        "utility_ratings": []
    }
    best_response = None
    best_score = -1

    if retrieval_needed:
        # 第二步:检索相关文档
        print("\n第二步:检索相关文档...")
        query_embedding = create_embeddings(query)
        results = vector_store.similarity_search(query_embedding, k=top_k)
        metrics["documents_retrieved"] = len(results)
        print(f"检索到 {len(results)} 条文档")

        # 第三步:评估每段文档的相关性
        print("\n第三步:评估文档相关性...")
        relevant_contexts = []
        for i, result in enumerate(results):
            context = result["text"]
            relevance = evaluate_relevance(query, context)
            print(f"文档 {i+1} 的相关性:{relevance}")
            if relevance == "相关":
                relevant_contexts.append(context)
        metrics["relevant_documents"] = len(relevant_contexts)
        print(f"找到 {len(relevant_contexts)} 条相关文档")

        if relevant_contexts:
            # 第四步:对每段相关上下文生成回复并打分
            print("\n第四步:处理相关上下文...")
            for i, context in enumerate(relevant_contexts):
                print(f"\n正在处理第 {i+1}/{len(relevant_contexts)} 段上下文...")
                response = generate_response(query, context)

                print("评估支撑度...")
                support_rating = assess_support(response, context)
                print(f"支撑度评分:{support_rating}")
                metrics["response_support_ratings"].append(support_rating)

                print("评估可用性...")
                utility_rating = rate_utility(query, response)
                print(f"可用性评分:{utility_rating}/5")
                metrics["utility_ratings"].append(utility_rating)

                support_score = {
                    "完全支持": 3,
                    "部分支持": 1,
                    "不支持": 0
                }.get(support_rating, 0)
                overall_score = support_score * 5 + utility_rating
                print(f"总分:{overall_score}")

                if overall_score > best_score:
                    best_response = response
                    best_score = overall_score
                    print("找到更优回复!")
        # 没有相关上下文或所有回复得分太低时,直接不检索生成
        if not relevant_contexts or best_score <= 0:
            print("\n没有合适的上下文或回复得分太低,改为不检索直接生成...")
            best_response = generate_response(query)
    else:
        print("\n不需要检索,直接生成回复...")
        best_response = generate_response(query)

    metrics["best_score"] = best_score
    metrics["used_retrieval"] = retrieval_needed and best_score > 0
    print("\n=== 自反思 RAG 完成 ===")
    return {
        "query": query,
        "response": best_response,
        "metrics": metrics
    }

13 命题分块

自反思 RAG 把生成前的判断做得挺细,但分块要是太粗,检索回来的内容本身就不太行。命题分块就是专门来治这个问题的。

按字符或按句子切块,一个块里常常混着好几个事实,有时候一个事实又被切到两半。命题分块把文本拆成原子化的命题,每个命题只说一件事,单独拿出来也能看懂。检索单元从“块”变成“命题”,查询和内容的匹配自然更细。

好在哪里,哪里不好:

  • 优势:检索粒度细,适合事实型、实体型问题。命题可以单独复用,也能重新组合。
  • 局限:建库时每块都要做一次命题抽取,成本不低。命题数量大了以后,索引和检索的开销也会涨。
  • 适用场景:知识密集型、答案要求单事实准确的问答。

命题抽取通常交给大模型做。提示词里要求它输出独立、自包含、不用代词的命题列表,同时把日期、数字、限定条件写全。

Contain One Subject-Predicate Relationship
Output ONLY the list of propositions."""
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"Text to convert:\n\n{chunk['text']}"}
],
temperature=0
)
raw_propositions = response.choices[0].message.content.strip().split('\n')
# 去掉编号、列表符,过滤过短无效句
clean_propositions = []
for prop in raw_propositions:
cleaned = re.sub(r'^\s*(\d+\.|\-|\*)\s*', '', prop).strip()
if cleaned and len(cleaned) > 10:
clean_propositions.append(cleaned)
return clean_propositions
def process_document_into_propositions(pdf_path, chunk_size=800, chunk_overlap=100,
quality_thresholds=None):
# 如果未提供质量阈值,则设置默认值
if quality_thresholds is None:
quality_thresholds = {
"accuracy": 7,
"clarity": 7,
"completeness": 7,
"conciseness": 7
}
# 从PDF文件中提取文本
text = extract_text_from_pdf(pdf_path)
# 从提取的文本创建块
chunks = chunk_text(text, chunk_size, chunk_overlap)
# 初始化一个列表来存储所有命题
all_propositions = []
print("Generating propositions from chunks...")
for i, chunk in enumerate(chunks):
print(f"Processing chunk {i+1}/{len(chunks)}...")
# 为当前块生成命题
chunk_propositions = generate_propositions(chunk)
print(f"Generated {len(chunk_propositions)} propositions")
# 处理每个生成的命题
for prop in chunk_propositions:
proposition_data = {
"text": prop,
"source_chunk_id": chunk["chunk_id"],
"source_text": chunk["text"]
}
all_propositions.append(proposition_data)
# 评估生成的命题的质量
print("\nEvaluating proposition quality...")
quality_propositions = []
for i, prop in enumerate(all_propositions):
if i % 10 == 0:  # 每10个命题更新一次状态
print(f"Evaluating proposition {i+1}/{len(all_propositions)}...")
# 评估当前命题的质量
scores = evaluate_proposition(prop["text"], prop["source_text"])
prop["quality_scores"] = scores
# 检查命题是否通过质量阈值
passes_quality = True
for metric, threshold in quality_thresholds.items():
if scores.get(metric, 0) < threshold:
passes_quality = False
break
if passes_quality:
quality_propositions.append(prop)
else:
print(f"Proposition failed quality check: {prop['text'][:50]}...")
print(f"\nRetained {len(quality_propositions)}/{len(all_propositions)} propositions after quality filtering")
return chunks, quality_propositions

14 == 多模态RAG

从 PDF 里同时抽正文和图片。图片用视觉模型写一段文字描述,正文和描述一起做 embedding,放进同一个向量库。检索时拿一句话去搜,正文和图片都能命中。

# ========== 方法14:多模态RAG ==========
# PDF 中同时抽文本与图像,图像用 VLM 生成 caption,文本+caption 一起入向量库统一检索
def extract_content_from_pdf(pdf_path, output_dir=None):
"""按页从 PDF 抽取文本块和图像,图像保存到 output_dir 并记录路径与页码"""
text_data = []
image_paths = []
with fitz.open(pdf_path) as pdf_file:
for page_number in range(len(pdf_file)):
page = pdf_file[page_number]
text = page.get_text().strip()
if text:
text_data.append({"content": text, "metadata": {"page": page_number + 1, "type": "text"}})
for img_index, img in enumerate(page.get_images(full=True)):
xref = img[0]
base_image = pdf_file.extract_image(xref)
if base_image:
img_path = os.path.join(output_dir, f"page_{page_number+1}_img_{img_index+1}.{base_image['ext']}")
with open(img_path, "wb") as f:
f.write(base_image["image"])
image_paths.append({"path": img_path, "metadata": {"page": page_number + 1, "type": "image"}})
return text_data, image_paths
def generate_image_caption(image_path):
"""用多模态模型(如 LLaVA)为图像生成文字描述,便于与文本一起嵌入检索"""
base64_image = encode_image(image_path)
response = client.chat.completions.create(
model="llava-hf/llava-1.5-7b-hf",
messages=[
{"role": "system", "content": "Describe images from academic papers in detail."},
{"role": "user", "content": [
{"type": "text", "text": "Describe this image in detail:"},
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}}
]}
]
)
return response.choices[0].message.content

15 == 融合检索

两条路并行:向量检索算语义相似,BM25 算关键词命中。两路分数各自做 Min-Max 归一化,再按 alpha 加权相加,得到最终分后取 top-k。语义和关键词互补,召回不容易偏。

# ========== 方法15:融合检索 ==========
# 向量检索 + BM25 双路打分,归一化后加权融合,再取 top-k
def bm25_search(bm25, chunks, query, k=5):
"""BM25 关键词检索:对 query 分词后算各 chunk 的 BM25 分,按分排序取 top-k"""
query_tokens = query.split()
scores = bm25.get_scores(query_tokens)
results = []
for i, score in enumerate(scores):
metadata = chunks[i].get("metadata", {}).copy()
metadata["index"] = i
results.append({"text": chunks[i]["text"], "metadata": metadata, "bm25_score": float(score)})
results.sort(key=lambda x: x["bm25_score"], reverse=True)
return results[:k]
def fusion_retrieval(query, chunks, vector_store, bm25_index, k=5, alpha=0.5):
"""融合:向量与 BM25 各算一版分数 → Min-Max 归一化 → 加权合并 → 按最终分取 top-k"""
epsilon = 1e-8
query_embedding = create_embeddings(query)
vector_results = vector_store.similarity_search_with_scores(query_embedding, k=len(chunks))
bm25_results = bm25_search(bm25_index, chunks, query, k=len(chunks))
vector_scores_dict = {r["metadata"]["index"]: r["similarity"] for r in vector_results}
bm25_scores_dict = {r["metadata"]["index"]: r["bm25_score"] for r in bm25_results}
vector_scores = np.array([vector_scores_dict.get(i, 0.0) for i in range(len(chunks))])
bm25_scores = np.array([bm25_scores_dict.get(i, 0.0) for i in range(len(chunks))])
v_min, v_max = vector_scores.min(), vector_scores.max()
b_min, b_max = bm25_scores.min(), bm25_scores.max()
norm_vector = (vector_scores - v_min) / (v_max - v_min + epsilon)
norm_bm25 = (bm25_scores - b_min) / (b_max - b_min + epsilon)
final_scores = alpha * norm_vector + (1 - alpha) * norm_bm25
top_indices = np.argsort(final_scores)[::-1][:k]
return [{"text": chunks[i]["text"], "score": final_scores[i]} for i in top_indices]

16 == 图RAG(Graph RAG)

每个文本块抽概念和实体,块当成节点,共享概念和语义相似度算成边权,得到一张知识图谱。检索时先命中几个节点,再沿着边往外扩,把跨块相关的信息一起捞回来。

优点:概念共现、实体关系都能用上,概念密集、关系复杂的领域很合适。
缺点:建图和抽概念成本高,图大了之后遍历和索引都麻烦。
适合:知识图谱、领域百科、多实体关系推理。

# ========== 方法16:图RAG(Graph RAG) ==========
# 块→节点,块内概念→属性;共享概念+语义相似度→边;检索时可沿边扩展关联块
def build_knowledge_graph(chunks):
"""为每个 chunk 抽概念、建节点;根据共享概念与嵌入相似度建边,得到知识图谱"""
graph = nx.Graph()
texts = [chunk["text"] for chunk in chunks]
embeddings = create_embeddings(texts)
for i, chunk in enumerate(chunks):
concepts = extract_concepts(chunk["text"])  # LLM 抽取概念/实体
graph.add_node(i, text=chunk["text"], concepts=concepts, embedding=embeddings[i])
for i in range(len(chunks)):
node_concepts = set(graph.nodes[i]["concepts"])
for j in range(i + 1, len(chunks)):
other_concepts = set(graph.nodes[j]["concepts"])
shared_concepts = node_concepts.intersection(other_concepts)
if shared_concepts:
similarity = np.dot(embeddings[i], embeddings[j]) / (
np.linalg.norm(embeddings[i]) * np.linalg.norm(embeddings[j]))
concept_score = len(shared_concepts) / min(len(node_concepts), len(other_concepts))
edge_weight = 0.7 * similarity + 0.3 * concept_score
if edge_weight > 0.6:
graph.add_edge(i, j, weight=edge_weight, shared_concepts=list(shared_concepts))
return graph, embeddings

17 == 分层检索RAG

两层索引。第一层是摘要,每页或每章一条;第二层是页内的细块。检索先查摘要层,确定是哪几页,再在这几页的细块里查。范围小了,速度也快。

优点:适合超长文档,十亿级数据也能扛。先筛范围再精查,延迟和算力好控制。
缺点:摘要质量差或者粒度不对,第一层就召不齐。相关内容分散在多页时,k_summaries 要调大。
适合:长篇报告、书籍、法规、多章节文档。

# ========== 方法17:分层检索RAG ==========

def process_document_hierarchically(pdf_path, chunk_size=1000, chunk_overlap=200):
"""建两层索引:每页摘要入 summary_store,每页内细块入 detailed_store"""
pages = extract_text_from_pdf(pdf_path)
summaries = []
for i, page in enumerate(pages):
summary_text = generate_page_summary(page["text"])
summaries.append({"text": summary_text, "metadata": {**page["metadata"], "is_summary": True}})
detailed_chunks = []
for page in pages:
page_chunks = chunk_text(page["text"], page["metadata"], chunk_size, chunk_overlap)
detailed_chunks.extend(page_chunks)
summary_store = SimpleVectorStore()
detailed_store = SimpleVectorStore()
# 分别为 summary 与 detailed_chunks 建嵌入并写入对应 store
return summary_store, detailed_store
def retrieve_hierarchically(query, summary_store, detailed_store, k_summaries=3, k_chunks=5):
"""先按 query 在摘要层检索出 k_summaries 个相关页,再仅在这些页的详细块里检索"""
query_embedding = create_embeddings(query)
summary_results = summary_store.similarity_search(query_embedding, k=k_summaries)
relevant_pages = [r["metadata"]["page"] for r in summary_results]
def page_filter(metadata):
return metadata["page"] in relevant_pages
detailed_results = detailed_store.similarity_search(
query_embedding, k=k_chunks * len(relevant_pages), filter_func=page_filter
)
return detailed_results

18 == 假设文档嵌入(HyDE)

查询太短,和长文档在嵌入空间里离得远。HyDE 的做法是让 LLM 先按问题写一段答案风格的文档,再用这段文档的嵌入去做检索,不用原始查询的嵌入。查询和文档之间的距离就拉近了。

优点:短查询、抽象查询都能补成伪文档,检索相关性提升很大。索引不用动,只改检索侧。
缺点:多一次 LLM 生成和嵌入调用。假设文档风格不对,反而添乱。
适合:短查询、知识问答、知识库规整、关键词匹配不上的 RAG 场景。

# ========== 方法18:假设文档嵌入(HyDE) ==========
# 用 query 生成一段“假设答案”文档,用该文档的嵌入去检索,而非用 query 嵌入
def generate_hypothetical_document(query, desired_length=1000):
"""根据问题生成一段“像标准答案”的假设文档,长度与风格接近真实文档"""
system_prompt = f"""You are an expert document creator. Given a question, generate a detailed document that would directly answer this question. The document should be approximately {desired_length} characters long. Write as if from an authoritative source. Include specific details and facts. Do not mention that this is a hypothetical document."""
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"Question: {query}\n\nGenerate a document:"}
],
temperature=0.1
)
return response.choices[0].message.content
def hyde_rag(query, vector_store, k=5):
"""HyDE 流程:生成假设文档 → 用假设文档嵌入检索 → 用真实检索结果生成最终答案"""
hypothetical_doc = generate_hypothetical_document(query)
hypothetical_embedding = create_embeddings([hypothetical_doc])[0]
retrieved_chunks = vector_store.similarity_search(hypothetical_embedding, k=k)
response = generate_response(query, retrieved_chunks)
return {"query": query, "hypothetical_document": hypothetical_doc,
"retrieved_chunks": retrieved_chunks, "response": response}

19 == 动态纠正RAG(CRAG)

检索完先给文档和查询打相关性分,范围 0 到 1。分数高就用本地文档;分数低就扔掉本地,改走网络搜索;中等分数就两边一起用。本地库不够用时能自动纠偏,不会硬拿无关文档凑答案。

  • 优点:降低“检索到无关文档还硬生成”的幻觉。知识边界不固定、需要时效信息的场景很合适。
  • 缺点:相关性评估得可靠,用 LLM 或 reranker 都行,还得接网络搜索接口。阈值要自己调。
  • 适合:开放域问答、时效性强的场景、本地库覆盖不全时。
# ========== 方法19:动态纠正RAG(CRAG) ==========
# 检索 → 评估相关性 → 高/中/低分分别采用「仅本地 / 本地+网络 / 仅网络」
def evaluate_document_relevance(query, document):
"""用 LLM 对「query-文档」打 0–1 相关性分,用于 CRAG 分支决策"""
system_prompt = """Rate how relevant the given document is to the query
on a scale from 0 to 1.

### 20 强化学习增强RAG

把整个 RAG 管道当成强化学习问题来看。状态是当前的 query、已经选中的上下文、历史回复和奖励,动作是重写 query、扩展或过滤上下文、生成回复。奖励直接用生成回复和标准答案的语义相似度来算。然后用 epsilon-greedy 这类策略根据状态选动作,靠奖励一步步迭代,检索和生成的策略会慢慢变好。

这个方案的好处是,可以用标注数据或用户反馈端到端地优化整条管道,理论上能学到比较复杂的策略。代价是要有奖励信号,要么有标注答案,要么有用户反馈,同时训练循环和稳定性都需要调参。适合手里标注多、反馈也多,想让系统自己越跑越好的团队。

```python
# ========== 方法20:强化学习增强RAG ==========
# 状态-动作-奖励建模,用奖励(如回复与标准答案的相似度)驱动策略选择

def calculate_reward(response: str, ground_truth: str) -> float:
    """奖励函数:回复与标准答案的嵌入余弦相似度,范围约 [-1, 1]"""
    response_embedding = create_embeddings(response)
    ground_truth_embedding = create_embeddings(ground_truth)
    similarity = cosine_similarity(response_embedding, ground_truth_embedding)
    return similarity

def expand_context(query, current_chunks, top_k=3):
    """动作之一:在已有 chunks 基础上再检索若干相关块并追加"""
    additional_chunks = retrieve_relevant_chunks(query, top_k=top_k + len(current_chunks))
    new_chunks = [chunk for chunk in additional_chunks if chunk not in current_chunks]
    return current_chunks + new_chunks[:top_k]

def filter_context(query, context_chunks):
    """动作之一:按与 query 的相似度只保留最相关的若干块"""
    query_embedding = create_embeddings(query)
    chunk_embeddings = [create_embeddings(chunk) for chunk in context_chunks]
    relevance_scores = [cosine_similarity(query_embedding, emb) for emb in chunk_embeddings]
    sorted_chunks = [x for _, x in sorted(zip(relevance_scores, context_chunks), reverse=True)]
    return sorted_chunks[:min(5, len(sorted_chunks))]

def policy_network(state, action_space, epsilon=0.2):
    """epsilon-greedy:以概率 epsilon 随机探索,否则按启发式选动作(重写/扩展/过滤/生成)"""
    if np.random.random() < epsilon:
        action = np.random.choice(action_space)
    else:
        if len(state["previous_responses"]) == 0:
            action = "rewrite_query"
        elif state["previous_rewards"] and max(state["previous_rewards"]) < 0.7:
            action = "expand_context"
        elif len(state["context"]) > 5:
            action = "filter_context"
        else:
            action = "generate_response"
    return action
```

**方法总结对比**

| 编号 | 方法 | 优化维度 | 核心思想 |
|------|------|----------|----------|
| 1 | 语义分块 | 分块 | 基于语义相似度的智能分割 |
| 2 | 切块大小评估 | 分块 | 数据驱动选择最优切块参数 |
| 3 | 上下文增强检索 | 检索 | 返回匹配块及其相邻块 |
| 4 | 上下文标题提取(CCH) | 检索 | 标题与文本双维度匹配 |
| 5 | 文档增强RAG | 索引 | 预生成问题弥补语义鸿沟 |
| 6 | 查询转换 | 查询 | 重写、回退、分解查询 |
| 7 | 重排序 | 检索 | 两阶段:先粗检索再精排序 |
| 8 | 相关段落提取(RSE) | 检索 | 最大子数组找连续最优段落 |
| 9 | 上下文压缩 | 后处理 | 压缩检索结果去除噪声 |
| 10 | 反馈回路 | 系统 | 用户反馈动态调整系统 |
| 11 | 自适应检索 | 检索 | 按查询类型选策略 |
| 12 | Self-RAG | 生成 | 多维度反思评估机制 |
| 13 | 命题分块 | 分块 | 原子化事实陈述作为检索单元 |
| 14 | 多模态RAG | 索引 | 图文联合检索 |
| 15 | 融合检索 | 检索 | 向量与BM25加权融合 |
| 16 | 图RAG | 检索 | 知识图谱关联发现 |
| 17 | 分层检索 | 检索 | 先摘要后详细的两级检索 |
| 18 | HyDE | 查询 | 假设文档嵌入替代查询嵌入 |
| 19 | CRAG | 系统 | 动态评估并纠正检索质量 |
| 20 | RL增强RAG | 系统 | 强化学习优化全流程 |

**优化方法选择指南**

- 分块优化:检索不够精准,先看语义分块(1)、命题分块(13)或者切块大小评估(2)。
- 检索增强:融合检索(15)性价比高,也好落地。图RAG(16)适合概念和关系密集的领域。
- 查询优化:HyDE(18)对短查询、抽象问句效果不错。查询转换(6)适合复杂、带多子问题的查询。
- 后处理:重排序(7)加上下文压缩(9),能明显提升最终回复质量和可控性。
- 系统级:CRAG(19)适合对可靠性和时效性要求高的场景。反馈回路(10)适合有持续用户反馈的产品。
- 端到端:Self-RAG(12)和强化学习增强(20)能力最全面,实现和调参成本也更高,按需选用。

### 学AI大模型的正确顺序

2026年的AI风口已经很明确了。各行业都在接入大模型,不少公司一边转型做AI产品,一边高薪招人。机会确实多。

想往AI方向转,或者本身有后端基础的朋友,直接冲AI大模型应用开发比较合适。就算暂时不转岗,把大模型、RAG、Prompt、Agent这些概念搞明白,能跑通一两个小项目,简历上也是加分项。

![AI大模型学习资料](https://i-blog.csdnimg.cn/direct/a054fe6919ee4825a3c94c23807d8b86.png)

学习清单和配套资料整理好了,从入门到求职都覆盖到了,按路线走就行。

**学习路线:**

1. 大模型基础认知:核心原理、发展历程,主流模型(GPT、文心一言等)的特点解析
2. 核心技术模块:RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
3. 开发基础能力:Python进阶、API接口调用、大模型开发框架(LangChain等)实操
4. 应用场景开发:智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
5. 项目落地流程:需求拆解、技术选型、模型调优、测试上线、运维迭代
6. 面试求职冲刺:岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经

这六个模块看起来不多,真学起来每个部分都有不少内容要啃。完整资料已经上传CSDN,需要的朋友可以微信扫描下方二维码领取。这是官方认证的领取入口,100%免费。

![CSDN官方认证二维码](https://i-blog.csdnimg.cn/blog_migrate/00d50d26803d72e8d5d75a1905815354.png)

## 常见问题(FAQ)

### RAG优化中如何评估和选择最佳的切块大小?

可以通过对比不同chunk size(如128、256、512)的忠实度和相关性指标来评估。每种大小都跑一遍切块、建索引、检索、生成的流程,选择分数最高的一组参数。这样调参有数据依据,但需要准备测试集,计算成本较高。

### 语义分块相比固定字数切块有什么优势?

固定字数切块不认语义,可能切断话题。语义分块通过计算相邻句子嵌入相似度,在相似度突降处切块,保证块内语义连贯。断点判定可用百分位法、标准差法或四分位距法,根据句向量分布选择。改造成本低,检索和生成效果更好。

### 上下文增强检索在RAG中起什么作用?

当检索命中的块缺少上下文时,上下文增强检索会将该块前后N个相邻块一起取出,拼成连续窗口再送去生成。实现简单,几乎零成本,适合分块较细、上下文连贯性要求高的场景。但可能混入无关内容,增加token消耗。
Roger深圳
本文由 Roger 审核,最后更新于 2026年8月6日
联系编辑 →
← 返回文章列表
分享到:微博
下一篇
AI Agent

版权与免责声明:本文仅用于信息分享与交流,不构成任何形式的法律、投资、医疗或其他专业建议,也不构成对任何结果的承诺或保证。

文中提及的商标、品牌、Logo、产品名称及相关图片/素材,其权利归各自合法权利人所有。本站内容可能基于公开资料整理,亦可能使用 AI 辅助生成或润色;我们尽力确保准确与合规,但不保证完整性、时效性与适用性,请读者自行甄别并以官方信息为准。

若本文内容或素材涉嫌侵权、隐私不当或存在错误,请相关权利人/当事人联系本站,我们将及时核实并采取删除、修正或下架等处理措施。也请勿在评论或联系信息中提交身份证号、手机号、住址等个人敏感信息。