这次主要分享一下prompt工程、自监督学习和rag技术。

1. prompt工程

提示工程(Prompt Engineering)是优化AI模型输出的关键技术,通过精心设计输入指令来引导模型生成更准确、相关的响应。

1.1 核心原则

是否进行了prompt工程处理的关键在于给ai的输入是否具有明确性和结构化输入。

明确性:提示应清晰具体,避免模糊表述。例如,“总结这篇文章“优于“处理这个“。

结构化:使用分步指令或示例(few-shot learning)可显著提升模型表现。实验显示结构化提示能使准确率提升40%。

一个结构清晰的 Prompt 通常包含以下四个组件,它们能显著提升回复的质量:

  1. 角色(Role):赋予模型一个特定的身份。(例如:“你是一位拥有20年经验的资深架构师”)

  2. 上下文(Context):提供背景信息、限制条件或数据。(例如:“我们正在为一个初创公司设计高并发系统,预算有限”)

  3. 任务(Task):清晰、具体的操作指令。(例如:“请对比并评价三种数据库方案的优劣”)

  4. 输出要求(Output Indicator):规定格式、风格或字数。(例如:“请用对比表格展示,并给出最终建议”)

1.2 进阶技术

a. 零样本提示(Zero-shot)

零样本提示(Zero-shot Prompting)是指直接让语言模型解决问题,不提供任何示例。这种方法依赖模型已有的知识库和推理能力,适用于通用型任务。

problem = """
在一个果园里,第一天采摘了总苹果数的1/5,第二天采摘了剩下苹果数的1/4,
第三天采摘了再剩下苹果数的1/3。采摘完三天后,果园里还剩下360个苹果。
请问,果园里原来一共有多少个苹果?
"""

# --- Demo 1: 零样本提示 (Zero-shot Prompting) ---
# 直接、简单的提问方式
zero_shot_prompt = f"""问题:{problem}

请直接解答,要求:
1. 设未知数
2. 列方程
3. 求解
4. 给出答案

保持简洁。"""

messages = [
    {"role": "user", "content": zero_shot_prompt}
]

b. 少样本提示(Few-shot)

少样本提示(Few-shot Prompting)通过提供少量范例(通常3-5个)来引导模型学习特定模式。这种方法能显著提升模型在专业领域的表现,如法律文书生成或医疗问答。

# --- Demo 2: 少样本思维链提示 (Few-shot CoT) ---
# 提供完整的逆向推理范例
few_shot_cot_prompt = f"""
学习以下逆向推理范例:

[范例]
问题:仓库第一天运走总数1/3,第二天运走剩下的1/2,最后剩250吨。原来有多少吨?
解答:逆向推理
- 第二天运走后剩250吨
- 第二天前:250 ÷ (1-1/2) = 500吨  
- 第一天前:500 ÷ (1-1/3) = 750吨
答案:750吨

---
请用相同格式解决:
{problem}
"""
messages = [
    {"role": "user", "content": few_shot_cot_prompt}
]

c. 思维链提示(Chain of Thought,CoT)

  • 逻辑:在 Prompt 中要求模型“一步步思考”。

  • 作用:对于逻辑推理、数学题或复杂决策,CoT 能引导模型展示中间步骤,从而减少幻觉(乱说)并提高准确率。

  • 常用语:“Let's think step by step”(让我们一步步思考)。

# --- Demo 3: 引导式逆向推理 ---
# 明确指导逆向推理步骤
guided_prompt = f"""
问题:{problem}

逆向推理法(从结果倒推):
1. 最后剩360个苹果
2. 第三天采摘前:360 ÷ (1-1/3) = 360 ÷ (2/3) = ?
3. 第二天采摘前:结果 ÷ (1-1/4) = 结果 ÷ (3/4) = ?
4. 第一天采摘前:结果 ÷ (1-1/5) = 结果 ÷ (4/5) = ?

请计算每步的具体数值并给出最终答案。
"""
messages = [
    {"role": "user", "content": guided_prompt}
]

d. 自洽性(Self-Consistency)

自洽性能够让模型对同一个问题进行多次思考,得出不同答案后通过“少数服从多数”的投票机制,选出最可靠的结论。

import collections
'''
问题:小明有 5 个苹果,他买了两箱,每箱有 10 个。
他给了朋友 3 个,后来又吃掉了一半剩下的。最后他还有几个苹果?
'''
def get_consistent_answer(problem):
    # 模拟 CoT 引导词
    prompt = f"问题:{problem}\n请一步步思考并给出最后答案(格式为:答案是 [数值])"
    
    # 1. 一次性生成多个回复 (n=5),设置较高温度增加多样性
    responses = llm.generate(prompt, n=5, temperature=0.7)
    
    results = []
    for resp in responses:
        # 2. 从每个推理链中提取最终答案 (简单正则示例)
        ans = extract_answer(resp) 
        results.append(ans)
    
    # 3. 少数服从多数 (投票)
    most_common = collections.Counter(results).most_common(1)
    return most_common[0][0] # 返回出现频率最高的答案

# 调用
final_ans = get_consistent_answer("5个苹果吃掉一半剩几个?")
print(f"最自洽的答案是: {final_ans}")

2. 自监督学习

自监督学习就是让模型自己教自己

2.1 核心思想

无需人工标注的预测任务

传统的监督学习需要大量的人工标签(比如:给一万张图片手动标注“这是猫”)。而自监督学习直接利用数据本身的信息,人为地制造出某种“缺失”,然后让模型去预测。

2.2 常用损坏策略

  • Mask (掩码):用特殊的 [MASK] 符号替换掉原来的词。这是最经典的BERT做法。
  • Replace spans (替换片段):随机选取一段连续的文本(span),并用随机生成的词或其他词替换它们。
  • Drop (丢弃):直接删除文本中的某些词或片段。

3. RAG

RAG (Retrieval-Augmented Generation,检索增强生成)

RAG通过动态检索外部知识库来增强生成模型的输出准确性, 就像是给大模型配了一个“随时可以翻阅的图书馆”。

3.1 为什么需要RAG?

大模型虽然博学,但有三个致命弱点:

  • 幻觉问题:模型会一本正经地胡说八道。

  • 时效性差:模型的知识停留在预训练结束的那一天(知识过时)。

  • 私域数据缺失:模型不知道你公司的内部文档或个人笔记。

对比方案:

  • 微调 (Fine-tuning):像让模型把整本书背下来,成本高,更新难。

  • RAG:像让模型参加“开卷考试”,哪里不会查哪里,成本低,准确度高。

3.2 工作流程

在进行数据切分之前一定要对你输入的文档进行数据清洗,可以先人工去掉一些无关的内容,比如论文里的作者资料,无关的长链接和图片,再根据程序进一步清洗。

import re

def clean_document(text):
    # 1. 剔除页眉页脚示例 (基于正则)
    text = re.sub(r"Confidential - Page \d+", "", text)
    
    # 2. 压缩空白符
    text = re.sub(r'\s+', ' ', text).strip()
    
    # 3. 删除非法字符
    text = re.sub(r'[^\x00-\x7F\u4e00-\u9fa5]+', '', text) 
    
    # 4. 修复硬换行 (简单的句子连接逻辑)
    text = text.replace('.\n', '.<SEP>').replace('\n', ' ').replace('<SEP>', '.\n')
    
    return text

第一阶段:数据准备(离线)

  1. 分块 (Chunking):把长文档切成一小段一小段(如 500 字一段)。

  2. 向量化 (Embedding):用一个模型把文字转成一串数字(向量)。

  3. 入库 (Vector Database):把这些数字存进向量数据库。

a. 文本切分

文本切分必要性模型上下文窗口存在限制(如GPT-3最大2048 tokens),切分可解决长文本处理问题。同时,合理切分能提升检索精度,避免信息冗余。

常用切分策略

  • 字符分割按固定字符数切割,简单但可能破坏语义。
  • 递归分割:层级式分割,优先按段落/句子边界切分。(默认使用)
  • Markdown分割:保留文档结构(如标题/列表)。

chunk_size控制片段长度(通常512-1024 tokens),chunk_overlap(建议10-20%)防止关键信息断裂。

b. 向量化 Embedding

将文本映射为稠密向量(通常768-1024维),通过余弦相似度计算语义关联。相比one-hot编码,能捕获近义词/上下文关系(如“手机“与“智能手机“向量相近)

中文模型选型:

  • bge-large-zh:1024维向量,适合精度优先场景

  • m3e-base:768维向量,推理速度比bge快40%资源占用对比:bge需4GB显存,m3e仅需2GB

高精度场景:选择bge-large等大模型(3B+参数)实时系统:考虑m3e或蒸馏模型(推理延迟<50ms)移动端:量化后的tiny模型(<100MB内存占用)

c. 向量数据库

定义向量数据库是专门设计用于存储和检索向量数据的数据库系统,与传统关系型数据库不同,它通过向量相似度进行高效查询。

ANN搜索核心

近似最近邻(ANN)搜索是向量数据库的核心功能,能够在毫秒级时间内从海量数据中找出最相似的向量,支持AI模型的实时推理。

为什么要ANN?

传统的精确搜索策略能保证100%的准确率,但是计算复杂度是O(N),他需要与数据库中的每一个向量进行计算。当数据量大时,会带来秒级延迟,无法接受。

ANN 是一种“用极小的精度损失,换取指数级速度提升”的搜索策略。它通过预先构建特殊的索引结构,避免了与所有向量进行计算,从而将检索时间从秒级压缩到毫秒级。

ANN 不是一个单一的算法,而是一类算法的总称。不同的算法有不同的索引构建方式和搜索策略,以适应不同的场景,常用的算法有HNSW、IVF、PQ、DiskANN等。

常用向量数据库:

本地轻量级方案:FAISS(Facebook开源的向量搜索库)、ChromaDB(嵌入式向量数据库)。生产级分布式方案:Milvus(云原生向量数据库)、Pinecone(全托管向量服务)、Weaviate(开源搜索引擎)。

第二阶段:查询与生成(在线)

  1. 检索 (Retrieve):用户提问时,系统也把问题转成向量,去数据库里找最相似的几段话。

  2. 增强 (Augment):把找出来的“参考资料”和“原始问题”拼接在一起。

  3. 生成 (Generate):把拼接后的长文本发给大模型,让它“根据以上参考资料回答问题”。

3.3 代码示例

import os
os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'

from langchain_community.document_loaders import DirectoryLoader, PyPDFLoader, TextLoader
#from langchain.text_splitters import RecursiveCharacterTextSplitter
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
#from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_huggingface import HuggingFaceEmbeddings


class RAGEngine:
    def __init__(self, db_path="./chroma_db", embedding_model="shibing624/text2vec-base-chinese"):
        self.db_path = db_path
        print(f"加载 Embedding 模型: {embedding_model}...")
        self.embeddings = HuggingFaceEmbeddings(
            model_name=embedding_model,
            model_kwargs={'device': 'cpu'}  # 为了低配置兼容,如果GPU有空余可改cuda
        )
        self.vectorstore = self._init_vectorstore()

    def _init_vectorstore(self):
        if os.path.exists(self.db_path):
            print("加载本地向量数据库...")
            return Chroma(persist_directory=self.db_path, embedding_function=self.embeddings)
        else:
            print("向量数据库不存在,请先构建知识库。")
            return None

    def build_knowledge_base(self, docs_dir="./medical_docs"):
        """读取指定目录下的文档,构建向量库"""
        print("开始构建知识库...")
        #读文件夹里面的pdf文件
        #loader = DirectoryLoader(docs_dir, glob="**/*.pdf", loader_cls=TextLoader)

        loader = DirectoryLoader(
            docs_dir,
            glob="**/*.pdf",  # 只加载 PDF 文件
            loader_cls=PyPDFLoader,  # 使用 PDF 加载器
            use_multithreading=True  # 可选,加快加载速度
        )
        documents = loader.load()

        # 切分文档 带语义感知的递归式启发切分
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=500,
            chunk_overlap=50, #重叠度,防止丢失上下文
            separators=["\n\n", "\n", "。", "!", "?", ",", "、", ""]
        )
        chunks = text_splitter.split_documents(documents)

        # 存入 Chroma
        self.vectorstore = Chroma.from_documents(
            documents=chunks,
            embedding=self.embeddings,
            persist_directory=self.db_path
        )
        self.vectorstore.persist()
        print(f"知识库构建完成!共 {len(chunks)} 个片段。")

    def retrieve_context(self, query, top_k=3):
        """根据 query 检索最相关的医疗知识"""
        if not self.vectorstore:
            return ""

        results = self.vectorstore.similarity_search_with_score(query, k=top_k)

        # 组装上下文
        context = ""
        for doc, score in results:
            # 过滤掉相似度太低的结果 (Chroma 默认是 L2 距离,越小越相似)
            if score < 400:
                context += doc.page_content + "\n"

        return context.strip()


# 测试代码
if __name__ == "__main__":
    rag = RAGEngine()
    #rag.build_knowledge_base() # 第一次运行需解开注释构建库
    context = rag.retrieve_context("高血压患者应该注意什么?")
    print("检索到的上下文:\n", context)

3.4 常见问题

1.RAG检索结果不准确怎么办?

  • 精细化切分:按段落、标题或句子边界切分文档,并设置适当的重叠长度,避免语义被截断。

  • 升级 Embedding 模型:换用领域适应性强或更高维的稠密向量模型,必要时在私有数据上微调。

  • 混合检索:同时使用稀疏检索(如 BM25)和稠密向量检索,融合两者结果以提升召回质量。

  • 重排序:先用高效方法召回一批候选文档,再用跨编码器模型对它们重新打分,剔除真正不相关的内容。

  • 动态 Top‑K 与阈值过滤:不固定返回文档数量,而是根据相似度分数动态调整,并丢弃低于设定阈值的文档。

  • 查询改写与扩展:利用大模型生成原始查询的多个同义变体或假设答案,分别检索后再合并去重。

  • 元数据过滤:在向量库中存储文档的发布时间、来源等属性,检索时按需过滤,只返回符合条件的文档。

2. 大模型回答质量不高,缺乏可读性。

  • 结构化 Prompt:给大模型设定明确的角色、任务分解步骤、输出格式示例,并要求不确定时主动拒答。

  • 增强上下文依赖约束:在提示中强调“仅使用给出的文档,禁止引入外部知识”,并对不相关的文档明确忽略。

  • 答案验证机制:生成答案后让模型自己检查是否完全被文档支持,或另用一个小型 NLI 模型进行一致性校验。

  • 多文档融合摘要:当检索结果中存在矛盾或冗余时,先让模型合并相同信息、对比差异,再输出综合结论。

  • 输出风格控制:根据场景调整温度参数、禁用无意义的套话,并预设不同模板(如技术问答要求简洁精确)。

  • 可追溯引用:要求每个结论后附上来源文档 ID 及原文片段,方便用户核实真伪。

3.知识库时效性不够

  • 实时更新机制:监听数据源的变化(如数据库 CDC、文件系统事件),一旦有更新就触发增量索引。

  • 定时脚本与增量爬取使用定时任务周期性抓取外部数据,仅拉取自上次抓取后发生变更的内容。

  • 版本化索引:按时间片建立独立索引,查询时默认使用最新索引,同时允许用户切换到历史版本。

  • 主动过时检测:定期扫描知识库,对比外部官方源,将过时的文档标记为失效并触发重新抓取。

  • 时间衰减排序:在检索打分时加入基于文档发布时间的衰减因子,使新近文档更容易进入最终结果。

  • 外部 API 实时注入:对于无法预存的实时信息(如天气、股价),直接调用外部 API 获取并拼接到上下文。

  • 用户反馈驱动更新:收集用户对答案的“有用/无用”标记或主动指出过时的反馈,定向刷新相关内容。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐