大语言模型受限于训练数据,无法回答训练数据之外的最新信息。本文介绍了RAG(检索增强生成)技术,通过从指定知识库中检索相关依据,再基于这些真实文档来回答问题,有效解决大模型的幻觉问题。文章详细讲解了RAG的离线索引和在线查询流程,包括文档加载、切分、嵌入、入库、检索等关键步骤,并对比了2-Step和Agentic两种RAG实现方式,帮助读者快速掌握大模型应用的新技术。
生产环境里,第二种情况是不能接受的。
RAG 要解决的问题就是让模型在回答之前,先到指定的知识库里查一圈,找到相关依据,再基于这些真实文档来回答。
环境基线:Python 3.13、langchain>=1.3.2,另需langchain-text-splitters、langchain-community(Loader)、以及一家 Embedding 包(下文用langchain-openai举例,可换成别家)。聊天模型仍从common.models.get_model拿。
混合检索调参、大规模评测留给后续 RAG 专栏;本篇打通全流程,并写清 2-Step / Agentic、溯源 artifact、以及和长期记忆 Store 的差别。
一、概述
RAG(Retrieval-Augmented Generation,检索增强生成)名字拆开就三步:
Retrieval(检索):从知识库找出和问题最相关的文档片段
Augmentation(增强):把片段 + 用户问题拼进 prompt
Generation(生成):模型基于增强后的上下文写回答
RAG = 检索 + 拼接 + 生成,让模型回答训练数据之外的事,靠的是真实文档当依据,不是指望模型按照概率来推断。
有以下一些场景,AI可能解决不了,需要RAG来优化:
| 痛点 | 表现 | RAG 怎么顶上 |
|---|---|---|
| 知识过时 | 训练截止后的政策/新闻它不知道,容易瞎编 | 查库,库更新即「新知识」 |
| 没有私有数据 | 内网制度、客户手册不在训练集里 | 私有文档进自己的索引 |
| 幻觉 | 不知道也硬答 | 有片段才答;没有就拒答 |
| 微调太重 | 改知识还要重训/继续训 | 改知识库即可,不必重训模型 |
| 无法追责 | 答完说不清依据 | 片段带来源,可展示、可审计 |
| 塞不下全文 | 几十万字不能整库进一次 prompt | 只取 top-k 相关块 |
两句话对照:
- 传统:
用户问题 → LLM → 答案(只靠训练记忆) - RAG:
用户问题 → 检索 → 拼进 prompt → LLM → 答案(先给证据)
用检索替代微调,让模型以较低成本拿到最新、相关、可核对的外部知识。
若你已经有 SQL / 文档中台 / 搜索服务,不必为了「用 LangChain」重造库,可以把现成查询封成工具(Agentic),或查完把结果当 context 塞进提示(2-Step)。下面仍从用文件建向量库讲起,这是最常见的入门教学。
二、2-Step 与 Agentic
官方把常见接法分成三类,2-Step、Agentic、Hybrid,本篇重点讲2-Step 和 Agentic。
| 架构 | 原理 | 控制力 | 灵活度 | 延迟 | 典型场景 |
|---|---|---|---|---|---|
| 2-Step RAG | 每次回答前固定先检索,再生成 | 高 | 低 | 相对可预期 | FAQ、文档问答 |
| Agentic RAG | Agent 自己决定何时、用哪个检索工具、查几次 | 低 | 高 | 波动大 | 研究助手、多工具混用 |
| Hybrid | 在中间加改写、检索 sufficiency、答案校验等 | 中 | 中 | 视迭代次数 | 要对齐质量门禁的领域问答 |
2-Step流水线写死一定查一次文档。实现简单,调用次数上限清楚,适合几乎每个问题都该看文档的客服知识库。
Agentic检索只是普通@tool。闲聊时可以不查文档;复杂问题可以改写 query 再查第二遍。代价是多几轮模型调用,也更依赖提示约束「没查到就说不知道」。
三、入库流水线
经典的RAG流程分为两个,离线索引与在线查询。
| 阶段 | 什么时候跑 | 干什么 |
|---|---|---|
| 离线索引(Indexing) | 建库时做一次,文档变更后再跑 | 文档 → 加载 → 切分 → 嵌入 → 写入向量库 |
| 在线查询(Query) | 每次用户提问 | 问题嵌入 → 相似度搜索 → Top-K → 拼进 prompt → 生成 |
检索好不好,很大程度在离线阶段就定了:文档切分好不好、Embedding 对不对很重要,前面的做不好,在线阶段再调 prompt 也效果不好。在线阶段要精确控制,只取相关片段,别试图把整库塞进窗口。
离线五步可以记成一条链:
加载(Load):文件/网页 → 标准
Document切分(Split):长文 → 小块 chunk
向量化(Embed):文本 → 稠密向量
入库(Store):向量 + 原文 + 元数据 → 向量库
检索器(Retrieve):查询时近邻搜索,返回相关
Document(这一步挂在「在线」;as_retriever往往离线末尾配好)
在线阶段,输入的问题也要变成向量(或交给检索器封装),然后检索取出 top-k,再增强 prompt、生成回答。
为了更好的通用性和替换性,我们本项目做了很好的封装,可以随时换 Loader、换切分策略、换 Embedding 供应商、换 PGVector / Chroma,业务代码尽量只依赖接口或工具。
Document是全流程的基本单位:
from langchain_core.documents import Document doc = Document( page_content="退货须在签收后 7 日内申请。", # 真正被检索和塞进提示的正文 metadata={"source": "refund_policy.md", "section": "时限"}, # 溯源、过滤用 )page_content是语义主体;metadata不参与(默认)向量计算,但回答时如果要引用出自哪份文件或者需要按目录过滤时,还得靠metadata。
四、加载与切分
- 1 加载:先变成 Document 列表
本地纯文本最简单,用TextLoader:
from langchain_community.document_loaders import TextLoader # encoding 按文件实际编码来;Windows 上中文常见 gb18030 / utf-8 loader = TextLoader("data/faq.txt", encoding="utf-8") docs = loader.load() # List[Document],通常一个文件一个 Document(未切分前)其它常见入口(装对应依赖即可):PyPDFLoader(PDF)、WebBaseLoader(网页)、目录用DirectoryLoader批量加载。Loader 只负责读进来并尽量带上 source 等元数据,不管切多细。
- 2 为什么要切块
如果一个文档很大,几十万字整个丢进 大模型,不仅上下文窗口会爆,token消耗巨大,检索也无法准确命中段落。切块之后,检索单位是一小段话,生成时只拼 top-k 段,成本和噪声都可控。
- 3 RecursiveCharacterTextSplitter
LangChain 里最常用的通用切分器:按分隔符列表递归切,优先在段落边界断开,切不干净再退到行、空格、字符。默认分隔符顺序大致是:"/n/n"→"/n"→" "→""。
from langchain_text_splitters import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter( chunk_size=500, # 单块上限(默认按字符计,不是 token) chunk_overlap=80, # 相邻块重叠,减轻「一句话被砍两半」 add_start_index=True, # metadata 里记下在原文的起始偏移,方便调试 ) chunks = splitter.split_documents(docs) print(len(docs), "->", len(chunks))Markdown / 代码还有更贴结构的 splitter;中文没有空格时,递归仍主要靠换行和标点附近的回退,不要照搬英文「按词」的直觉,以抽查看块是否完整为准。
- 4 chunk_size 与 chunk_overlap
这两个参数最容易混,用一张图对齐:
chunk_size:每一块允许的最大长度(该 splitter 默认是字符数)。文档 10000 字、chunk_size=4000,大约会切成多块,每块不超过 4000(实际还会尽量在分隔符处提前断开,所以常小于上限)。
chunk_overlap:相邻两块共享的一段尾巴/开头。例如chunk_size=1000、chunk_overlap=200时,第一块若落到 1–1000,第二块会从大约 801 起——801–1000 这段两边都有。
为什么要重叠?语义经常骑在切分边界上:上一块结尾半句条件,下一块开头半句结论。没有 overlap,只命中一块时,模型可能看到残句。overlap 太小不管用;太大则冗余高、块数变多、索引和检索都变贵。
经验参考(需根据自己的项目调整):
| 场景 | chunk_size | chunk_overlap |
|---|---|---|
| 短 FAQ、条目清晰 | 300–600 | 40–80 |
| 制度/手册段落 | 500–1000 | 80–150 |
| 长叙事、需更多上下文 | 1000–1500 | 150–200 |
没有万能值。同一份文档用两三种 size 各建一小库,拿 20 个真实问题看「该中的段在不在 top-k」,拿实际结果作比较。Overlap 常见取 size 的 10%–20%。
切完务必print几块:有没有把表格砍碎、把「不适用情形」和「适用情形」拆到两个永远不同时召回的块里。这种问题改切分,比改 prompt 管用。
五、Embedding、向量库与元数据
- 1 Embedding
Embedding 把一段文本变成固定维向量,让「意思近」的文本在空间里也近。检索时:问题 → 向量,在库里找最近的 chunk 向量,取回原文。
聊天模型(get_model())和 Embedding 模型是不一样的,不要把 chat 模型的接口硬当成 embed 用。选 Embedding 时优先:
和文档语言匹配(中文语料用中文效果好的模型)。
维度、价格、是否可本地部署。
一经建库,换 Embedding 模型通常要整库重嵌,选型最好早定。
# 示例:OpenAI 兼容接口;换成 DashScope / 本地 bge 等均可 from langchain_openai import OpenAIEmbeddings embeddings = OpenAIEmbeddings(model="text-embedding-3-small") # 向量维度由模型决定;同一库全程必须用同一 embedding本地可试HuggingFaceEmbeddings(如BAAI/bge-small-zh-v1.5);公司内网常见「OpenAI 兼容的 embedding HTTP 接口」,用OpenAIEmbeddings(base_url=...)指过去即可。
- 2 向量库
向量库保存向量、对应的page_content、metadata,并提供相似度查询。自己做demo或者实验用内存库;生产要换 Postgres(pgvector)、Chroma、Milvus、云检索等。
from langchain_core.vectorstores import InMemoryVectorStore # 从 Document 列表一次性构建:内部会对 page_content 调 embeddings vector_store = InMemoryVectorStore.from_documents( documents=chunks, embedding=embeddings, ) # 等价分步:先 empty store,再 add_documents # vector_store = InMemoryVectorStore(embeddings) # vector_store.add_documents(chunks)- 3 元数据
建库前尽量写好 metadata,例如source、title、page、product、lang。之后可以:
回答里写「依据:退货政策 §3」。
检索时按产品线过滤(若向量库支持 metadata filter)。
评测时统计「错在哪份文档」。
切分后 metadata 一般会从父 Document 继承;add_start_index=True时还能看到块在原文的偏移。
- 4 Retriever
向量库可以.similarity_search(query, k=3),我们也可以自己封装一个 Retriever类,方便以后换成其它实现而不改调用方:
# 默认:相似度 Top-K retriever = vector_store.as_retriever( search_kwargs={"k": 3}, # 过大噪声多,过小易漏;FAQ 常 3–5 ) # 可选:MMR(最大边际相关性)——在「相关」和「彼此不重复」之间折中 # fetch_k 先多取一些候选,再挑多样性更好的 k 条 mmr_retriever = vector_store.as_retriever( search_type="mmr", search_kwargs={"k": 3, "fetch_k": 20}, ) hits = retriever.invoke("签收后多久可以退货?") for d in hits: print(d.metadata.get("source"), d.page_content[:80])检索质量大致由这些因素决定:
| 因素 | 太偏会怎样 | 常见方向 |
|---|---|---|
| 切分粒度 | 太粗噪声多;太细上下文会断 | 第四节的 size/overlap |
| 嵌入模型 | 语义对不齐 | 中文语料选中文效果好的(如 bge 系) |
| 搜索算法 | 只追相似易重复 | similarityvsmmr |
| Top-K | 少则漏、多则无关内容变多 | 3–5,必要时加相似度阈值 |
| 元数据过滤 | 搜全库干扰大 | 按类型/日期/权限收窄 |
k调完仍然有很多无关文档,再靠第八节的重排、压缩来优化。
排查时可以先printtop-k 原文,确认证据在不在,再怪生成。
六、2-Step RAG
流程固定:问题 → 检索 → 拼 context → 提示词约束「只根据上下文」→ 模型生成。控制力强,延迟相对稳。
把多块正文用空行拼起来,是为了让模型把每块当成独立证据,而不是粘成一团:
from langchain_core.prompts import ChatPromptTemplate from common.models import get_model prompt = ChatPromptTemplate.from_template( """你是客服助手。只能根据下面「参考资料」回答。 资料不足以回答时,直接说「资料中未找到依据」,不要编造。 参考资料: {context} 用户问题: {question} """ ) model = get_model(temperature=0) def format_docs(docs) -> str: """把检索结果收成提示里的 context;双换行分隔块。""" return "/n/n".join(d.page_content for d in docs) def ask_2step(question: str) -> str: docs = retriever.invoke(question) # LCEL:字典进提示 → 模型;第 2 篇讲过的 Runnable 组合 chain = prompt | model resp = chain.invoke( { "context": format_docs(docs), "question": question, } ) return resp.content print(ask_2step("签收后多久可以退货?")) def format_docs_with_source(docs) -> str: parts = [] for i, d in enumerate(docs, 1): src = d.metadata.get("source", "unknown") parts.append(f"[{i}] 来源: {src}/n{d.page_content}") return "/n/n".join(parts)并在模板里加一句:「回答末尾用 [编号] 标注依据。」
2-Step 的缺点也清楚:寒暄也查库;多跳问题(先要找政策名再找细节)不会自动拆查询。这些交给下一节。
七、Agentic RAG
Agentic RAG 最重要的是 把「查知识库」做成工具,交给create_agent。模型在循环里自己判断要不要查、用什么 query、查完够不够、要不要再查一轮。多轮对话若要记住上下文,像别的 Agent 一样挂上 checkpointer(参考第 8 篇)。
- 1 手写
@tool
from langchain.agents import create_agent from langchain.tools import tool from langgraph.checkpoint.memory import InMemorySaver from common.models import get_model @tool def search_knowledge(query: str) -> str: """从公司知识库检索相关段落。 回答制度、流程、产品规格问题时先调用本工具。 返回带过来源标记的文本;若为空说明库中无相关内容。 """ docs = retriever.invoke(query) if not docs: return "未检索到相关资料。" # 返回值进 ToolMessage.content,供模型下一轮阅读 parts = [] for i, d in enumerate(docs, 1): src = d.metadata.get("source", "unknown") parts.append(f"[{i}] 来源: {src}/n{d.page_content}") return "/n/n".join(parts) agent = create_agent( model=get_model(temperature=0), tools=[search_knowledge], checkpointer=InMemorySaver(), system_prompt=( "你是客服助手。涉及公司政策、流程、规格时必须先 search_knowledge," "只根据工具结果回答,并标明来源编号。" "检索为空或无关时,明确说不知道,禁止编造。" "普通寒暄不必查库。" ), ) config = {"configurable": {"thread_id": "rag-1"}} result = agent.invoke( { "messages": [ {"role": "user", "content": "签收后多久可以退货?依据是什么?"} ] }, config=config, ) print(result["messages"][-1].content)- 2
create_retriever_tool
不想手写拼接时,用官方工厂函数把 Retriever 直接封装车成工具。description仍是模型是否调用的关键信号,要写清「搜什么、何时用」。
from langchain.agents import create_agent from langchain_core.tools.retriever import create_retriever_tool from common.models import get_model # response_format="content_and_artifact" 时: # content → 给模型看的拼接文本;artifact → 原始 Document 列表给应用层 retriever_tool = create_retriever_tool( retriever, name="search_docs", description="搜索产品文档,获取与问题相关的段落", response_format="content_and_artifact", ) agent = create_agent( model=get_model(temperature=0), tools=[retriever_tool], system_prompt="政策/规格问题先 search_docs,按资料回答;无资料则拒答。", )content_and_artifact的好处见下一小节:前端溯源不用再解析模型随机返回的「来源」字符串。
- 3 溯源:
content给模型,artifact给前端。
ToolMessage可以带两份数据:模型只读content;完整结构化结果放artifact,应用层拿来渲染「来源:退货政策 v2.pdf 第 3 页」。手写工具时也可以自己返回(content, artifact)(需工具声明对应 response_format),或在工具外根据Document.metadata组装:
from langchain.messages import ToolMessage # 概念示意:content 给模型,artifact 给 UI / 审计 ToolMessage( content="公司退货政策:签收后 7 日内可申请……", # 模型据此生成 tool_call_id="call_123", artifact={ # 应用层据此画「来源」卡片;默认不进模型注意力 "sources": [ {"source": "退货政策v2.pdf", "page": 3, "doc_id": "doc_456"}, ], }, )用create_retriever_tool(..., response_format="content_and_artifact")时,artifact 一般是List[Document],你从每条的metadata抽 source/page 即可。这和「把来源写进 content 让模型复述」不冲突,content 里仍可带简短引用,artifact 负责机器可读的溯源。
- 4 权限过滤
知识库不是人人能看全部。第 5、12 篇的context/store可以在检索工具里做过滤,先向量召回,再按用户允许的doc_id留下去。
通过在检索工具里接ToolRuntime实现:
from langchain.tools import ToolRuntime, tool @tool def search_with_context(query: str, runtime: ToolRuntime) -> str: """搜索知识库;只返回当前用户有权访问的文档。""" user_id = runtime.context.user_id # 调用 agent 时传入的 context # 从长期记忆 Store 读权限名单;没有 Store 时也可查自家权限服务 item = runtime.store.get(("permissions", user_id), "access") allowed = set((item.value or {}).get("allowed_docs", [])) if item else set() docs = retriever.invoke(query) filtered = [ d for d in docs if not allowed or d.metadata.get("doc_id") in allowed ] if not filtered: return "未检索到你有权查看的相关资料。" return "/n/n".join(d.page_content for d in filtered[:3])原理:向量检索解决「相关」,权限过滤解决「能看」。两步都要做;只做相关、不做权限,等于检索成了旁路泄密。
八、失效补救与完整示例
- 1 基础 RAG 常见失效
| 失效 | 可能原因 | 先试什么 |
|---|---|---|
| 答非所问 | 切块太碎/太大;k 不对;Embedding 偏科 | 调切分与 k;抽查看 top-k |
| 漏答 | 用户说法和文档用词差太远 | 查询改写;同义扩展 |
| 噪声太多 | k 过大;块里广告/目录太多 | 减小 k;重排;压缩 |
| 胡编 | 提示未强制依据;检索空仍硬答 | 拒答话术;无命中直接返回 |
| 无法追责 | 没 metadata / 没引用 | format 带 source;要求 [编号] |
下面是一些建议:
查询改写:用户说「那个七天规定」,文档写「签收后 7 日内」。生成前让模型把问题改成检索友好句,再用新句子invokeretriever。2-Step 里加一个改写节点;Agentic 里写进 system:「先把问题改写成检索词再调用工具」。
Rerank:向量召回先取 20,再用交叉编码器或小模型按「与问题相关性」重排,截断前 3–5 送进生成。救的是「语义近但答不对题」的噪声。
上下文压缩:块很长时,只抽与问题有关的句子再进提示(LLMChainExtractor 一类,或自己用小模型摘要)。救的是窗口与注意力,不是召回率本身。
- 2 完整示例:建库 + 两路问答
下面用内存向量库跑通:一份迷你 FAQ → 切分入库 → 2-Step 与 Agent 各问一次。把faq_text换成TextLoader("xxx.txt").load()即接真实文件。
from langchain.agents import create_agent from langchain.tools import tool from langchain_core.documents import Document from langchain_core.prompts import ChatPromptTemplate from langchain_core.vectorstores import InMemoryVectorStore from langchain_openai import OpenAIEmbeddings from langchain_text_splitters import RecursiveCharacterTextSplitter from common.models import get_model # ---------- 1) 准备文档(演示用内联;生产改 Loader)---------- faq_text = """ # 退货政策 顾客可在签收后 7 日内申请退货。商品须未使用、包装完整。 虚拟商品、定制商品不适用 7 日退货。 # 运费 退货原因属于质量问题的,运费由商家承担;其余情况由顾客承担。 """ docs = [ Document( page_content=faq_text, metadata={"source": "faq.md", "product_line": "mall"}, ) ] splitter = RecursiveCharacterTextSplitter(chunk_size=120, chunk_overlap=30) chunks = splitter.split_documents(docs) # ---------- 2) 索引 ---------- embeddings = OpenAIEmbeddings(model="text-embedding-3-small") vector_store = InMemoryVectorStore.from_documents(chunks, embeddings) retriever = vector_store.as_retriever(search_kwargs={"k": 3}) def format_docs_with_source(docs: list[Document]) -> str: parts = [] for i, d in enumerate(docs, 1): src = d.metadata.get("source", "unknown") parts.append(f"[{i}] 来源: {src}/n{d.page_content.strip()}") return "/n/n".join(parts) if parts else "" # ---------- 3) 2-Step ---------- prompt = ChatPromptTemplate.from_template( """只根据参考资料回答;不足则说「资料中未找到依据」。 回答末尾用 [编号] 标注依据。 参考资料: {context} 问题: {question} """ ) rag_chain = prompt | get_model(temperature=0) def ask_2step(question: str) -> str: docs_hit = retriever.invoke(question) msg = rag_chain.invoke( { "context": format_docs_with_source(docs_hit), "question": question, } ) return msg.content # ---------- 4) Agentic ---------- @tool def search_knowledge(query: str) -> str: """检索商城 FAQ 知识库,返回带来源编号的段落。""" docs_hit = retriever.invoke(query) text = format_docs_with_source(docs_hit) return text or "未检索到相关资料。" agent = create_agent( model=get_model(temperature=0), tools=[search_knowledge], system_prompt=( "政策问题必须先 search_knowledge,按资料回答并引用 [编号];" "无资料则明确拒答。寒暄不必查库。" ), ) if __name__ == "__main__": q1 = "签收后多久能退货?" q2 = "今天天气怎么样?" # 应拒答或闲聊,不应编造政策 print("=== 2-Step ===") print(ask_2step(q1)) print("/n=== Agent ===") r = agent.invoke({"messages": [{"role": "user", "content": q1}]}) print(r["messages"][-1].content) r2 = agent.invoke({"messages": [{"role": "user", "content": q2}]}) print(r2["messages"][-1].content)验收建议:
q1两路都能答到「7 日」,并带来源。把 FAQ 里「7 日」删掉重建索引后,应走拒答,而不是背训练记忆。
打印
retriever.invoke(q1),确认命中块里真有退货句——生成错之前先看检索错没错。试
chunk_size=40与400各建一次,看短问「虚拟商品能否退」谁更稳(体会切分影响)。
依赖安装示例:
pip install langchain langchain-openai langchain-text-splitters langchain-communityEmbedding 与 chat 不必同一家,但都要配好各自的 API Key /base_url。
最后
2026 年一晃已经过半,AI 大模型的热潮不仅没有降温,反而持续升温!
金融行业用大模型做风控、医疗依靠 AI 解析影像,电商、制造、教育各行各业,都在把 AI 融入日常业务。曾经热闹的 “百模大战”,早就告别单纯比拼模型参数,正式进入落地应用时代。
现在企业疯狂紧缺一类人才:懂业务、懂 AI、能做出可上线项目的大模型开发工程师,岗位缺口大,薪资待遇十分可观。
风口再好,不如手握高薪 offer 实在。行情火热,普通人、程序员该怎样从零入门大模型,抓住这波机会?
今天整理好【2026 最新版】AI 大模型全套免费学习资源,覆盖零基础入门、项目实战、理论知识、大厂面试,从基础一路进阶。所有资料分类归档,没有多余杂料,无套路免费分享给想要入局 AI 赛道的程序员与零基础小白!
👇👇扫码免费领取全部内容👇👇
1、大模型系统化完整学习路线
2、大模型经典书籍&文档
3、AI 大模型最新行业研究报告
4、企业级实战项目 + 完整配套源码
5、大厂大模型面试真题汇总
6、这些资料真的有用吗?
这份资料由我和鲁为民博士(北京清华大学学士和美国加州理工学院博士)共同整理,现任上海殷泊信息科技CEO,其创立的MoPaaS云平台获Forrester全球’强劲表现者’认证,服务航天科工、国家电网等1000+企业,以第一作者在IEEE Transactions发表论文50+篇,获NASA JPL火星探测系统强化学习专利等35项中美专利。本套AI大模型课程由清华大学-加州理工双料博士、吴文俊人工智能奖得主鲁为民教授领衔研发。
资料内容涵盖了从入门到进阶的各类视频教程和实战项目,无论你是小白还是有些技术基础的技术人员,这份资料都绝对能帮助你提升薪资待遇,转行大模型岗位。
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】