首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【腾讯云ES×AI实战】告别复杂!在一个技术栈内,基于混合搜索快速搭建RAG

【腾讯云ES×AI实战】告别复杂!在一个技术栈内,基于混合搜索快速搭建RAG

作者头像
腾讯QQ大数据
发布2025-08-24 09:34:10
发布2025-08-24 09:34:10
22800
代码可运行
举报
运行总次数:0
代码可运行

本文共计2664字 预计阅读时长8分钟

一、引言

本文主要面向对RAG概念有基础了解的读者,因此不再赘述RAG基础概念。在写这篇最佳实践之前,先回答一个问题:在上千万tokens上下文窗口模型出现后,为什么我们仍然需要RAG?

原因很简单,将所有数据都加载到模型上下文存在如下挑战:

1.可扩展性:企业知识库以TB或PB为单位,而不是以token为单位,即使有1000万个tokens上下文窗口,仍然只能看到可用信息的一小部分;

2.准确性:实际的上下文窗口与产品发布时宣传的差异巨大,研究一致表明,早在模型达到官方宣传极限之前,准确性就已经下降到不可用的情况;

3.延迟:将所有内容加载到模型的上下文中会导致响应时间显著延长。对于面向用户的应用程序来说,用户在得到答案之前就会放弃交互,体验很差;

4.效率:每次回答一个简单的问题,你都要翻阅整本教科书吗?

在RAG中,高效精准的检索是非常关键的,混合搜索以及结构化数据支持方面的进步,有助于在知识库中找到正确信息,从而解决上述的问题。同时RAG与「长上下文」、「微调」、「MCP」这些概念并不排斥,他们可以通过最佳的协同方式去解决前沿模型的局限性:

  • RAG提供对模型知识之外的信息的访问
  • 微调可以改善信息的处理和应用方式,让大模型更加智能、专业
  • 更长的上下文允许检索更多信息,以供模型进行推理
  • MCP简化了代理与RAG系统(和其他工具)的集成

最近腾讯云ES发布的新特性「智能搜索开发」,以AI搜索增强版内核为底座,进一步优化了对全文与向量混合搜索的能力支持,从原始文档解析、向量化等原子能力,到查询性能、混合排序效率、搜索结果精准度等提供了全方位的支持和优化,让搜索有了更多想象空间。 在此基础上,还可以与混元、DeepSeek等大语言模型无缝集成,从而帮助企业进一步高效、灵活的构建知识问答等RAG应用,在关键环节也可介入调优,整体流程如下:

接下来具体看下搭建过程。

二、AI助手搭建

其实,你只需要运行下面的文件成功,你就可以快速构建一个AI问答应用

  • 数据写入:(二选一)
  • 如果你有大批量文档或正式生产:运行data_input_inference.py
  • 如果你想快速搭demo体验:运行data_input_inference_streamlit.py
  • 在线问答:search_inference.py

想运行上述文件(文件在文末扫码下载)成功,你得获得下述信息,可以参照【前置条件】步骤获取。

代码语言:javascript
代码运行次数:0
运行
复制
# 腾讯云API客户端配置,获取方式见【3.获取API秘钥】
SECRET_ID = "AKID******"    # 替换为你的云API SecretId
SECRET_KEY = "******"       # 替换为你的云API SecretKey
 
# es集群相关配置,获取方式见【1.购买集群】【2.获取ES访问地址】
# 用户名为 elastic、密码在创建集群时设置。用本地环境测试时,可开启公网访问,实际生产时,建议使用内网访问地址。
ES_ENDPOINT = "https://es-******.tencentelasticsearch.com:9200"
ES_USERNAME = "elastic"  # Elasticsearch集群用户名
ES_PASSWORD = "******"  # Elasticsearch集群密码
INDEX_NAME = "索引名称"  # 在写入时要保证索引未被创建过,会默认创建索引并设置mapping,写入数据和在线问答的索引保持一致
 
# 数据入库时,选择COS方式需设置你的COS文件路径
# 要处理的文档URL(推荐将文件存放到COS存储,并开启「公有读私有写」权限)
document_url = "https://xxx.cos.xxx/sample.doc"

当然,如果需要深入了解代码细节,或想调整相关参数达到匹配业务的效果,可参阅下一章节【核心代码解析】

前置条件

1.购买ES集群

(1)登录腾讯云ES控制台,单击「新建」按钮。

(2)进入新建集群界面,用于测试验证计费模式可选「按量计费」,产品版本选择「AI 搜索增强版」,专用于对搜索能力、查询性能和稳定性有极高要求的搜索和RAG场景,Elasticsearch版本选择「8.16.1」,向量能力主要集中在8.x以上版本。

(3)ES节点配置,测试验证可选择为「2核4G」,节点数默认为「3」,磁盘为「增强型 SSD云硬盘」,磁盘容量为「20GB」

(4)其余配置选择默认即可,注意保存集群访问密码。

2.获取ES访问地址

对应集群 下可获取相关信息,用户名为elastic,密码在创建集群时所设置,如已忘记可以重置。使用本地mac测试时,可开启公网访问,实际生产时,建议使用内网访问地址。

3.开通服务

现在集群创建好了,在数据入库以及查询时需要用到很多模型服务,腾讯云 ES 智能搜索开发已经集成了文档解析、文本切片、Embedding、Rerank、LLM服务,只需开通服务即可,点击「立即开通」,确认免费开通。

4.获取API秘钥

进入API密钥管理界面,获取SecretID、SecretKey。

搭建步骤

1.安装依赖

代码语言:javascript
代码运行次数:0
运行
复制
# Python版本:3.9及以上
pip install elasticsearch==8.16.0
pip install tencentcloud-sdk-python
pip install urllib3 certifi
# 安装文档处理包
pip install python-docx pypdf2 lxml
pip install streamlit    #使用COS数据入库时,无需安装

2.数据入库

这里提供了两种方式,如果有大批量的文档需要进行处理,或者你需要上生产,那建议按【2.1.COS上传】方式入库,这种方式更加稳定和安全;如果你只是简单体验一下AI问答demo,并通过界面可视化的方式操作,那可以按【2.2.本地上传】的方式入库。

2.1.COS上传(可选)

(1)先将需要处理的文档拖入对应COS桶,具体操作步骤:进入COS控制台->点击「存储桶列表」->对应文件存储桶->点击「上传文件」->出现以下弹框,拖拽需要上传的文件即可

(2)然后设置文档权限,这里需要对文档进行解析切片处理,所以需要开放「公有读私有写」权限,具体操作:点击「存储桶列表」->对应文件存储桶->对应文件详情->开启「公有读私有写」权限

(3)在data_input_inference.py文件中,修改main方法中的document_url为你上传的COS文件地址

代码语言:javascript
代码运行次数:0
运行
复制
# 要处理的文档URL(推荐将文件存放到COS存储,并开启「公有读私有写」权限。
document_url = "https://xxx.cos.xxx/sample.doc"

然后,在终端运行该文件:

代码语言:javascript
代码运行次数:0
运行
复制
python data_input_inference.py

2.2.本地上传(可选)

(1)在终端运行data_input_inference_streamlit.py文件,在终端执行如下命令:

代码语言:javascript
代码运行次数:0
运行
复制
streamlit run data_input_inference_streamlit.py

(2)将要上传的文档拖拽进去,或者点击「Browse files」按钮,选择文件上传,效果如下:

3.在线问答

这步主要是运行search_inference.py文件,在终端执行如下命令:

代码语言:javascript
代码运行次数:0
运行
复制
streamlit run search_inference.py

效果如下,在输入框中提出相关问题,系统会进行混合搜索-重排序-组装Prompt-大模型回答一系列动作,整个流程中间内容可见,最后生成回答内容。

三、核心代码解析

1.数据入库

对应data_input_inference.py和data_input_inference_streamlit.py文件,主要讲述一个非结构化文档从解析、切片、到Embedding后写入ES索引的过程,具体如下:

1.1.文档解析/切片

企业大部分数据都是以文档形式存在的非结构化数据,如何让文档数据变得可读,是实现RAG的第一步。我们通过调用智能搜索开发提供的文档解析/切片服务对文档数据先进行文档识别转MD格式,然后进行语义切片预处理。

常量MAX_CHUNK_SIZE即为文本切片单条文本最大字符数,不小于1000,可以根据你的具体业务,确定合适的最大字符数。在Embedding过程中,选定的模型Tokens限制应大于你设定的最大字符数,否则会出现截断的可能。

常量DOCUMENT_CHUNK_MODEL_NAME即为选择的文档解析切片模型,“doc-tree-chunk”包含了文档解析和切片部分,这里可以直接用。

代码语言:javascript
代码运行次数:0
运行
复制
MAX_CHUNK_SIZE = 2000  # 文本切片单条文本最大字符个数(根据具体选用的Embedding模型tokens设定,最小值不小于1000)
DOCUMENT_CHUNK_MODEL_NAME = "doc-tree-chunk"
# 调用ChunkDocumentAsync接口创建文档切片任务
params = {
    "Document": {
        "FileType": file_type,
        "FileUrl": file_url,
        "FileName": os.path.basename(urlparse(file_url).path)
    },
    "Config": {
        "MaxChunkSize": MAX_CHUNK_SIZE  # 可根据需要调整切片大小
    },
    "ModelName": DOCUMENT_CHUNK_MODEL_NAME
}
response = retry_request(client.call_json, "ChunkDocumentAsync", params)
task_id = response['Response']['TaskId']
logger.info(f"Created document chunking task with ID: {task_id}")

因为文档解析/切片是个异步任务,所以需要通过获取切片结果。

解压获取到的结果,切片好的数据存放在xxx.jsonl文件内,对应page_content即为切片数据。

代码语言:javascript
代码运行次数:0
运行
复制
try:
    with zipfile.ZipFile(io.BytesIO(response.content)) as zip_file:
        # 检查zip中是否有.jsonl文件
        jsonl_files = [f for f in zip_file.namelist() if f.endswith('.jsonl')]
        if not jsonl_files:
            error_msg = "No .jsonl file found in the zip archive"
            logger.error(error_msg)
            raise Exception(error_msg)
 
        # 处理每个jsonl文件
        for file_name in jsonl_files:
            with zip_file.open(file_name) as f:
                # 逐行读取jsonl文件
                for line in f:
                    try:
                        data = json.loads(line.decode('utf-8'))
                        if 'page_content' in data:
                            doc_list.append(data['page_content'])
                    except json.JSONDecodeError as e:
                        logger.warning(f"Failed to parse JSON line: {str(e)}")
                        continue

1.2.切片Embedding并写入ES

(1)创建Embedding服务,如:model_bge_base_zh-v1.5

代码语言:javascript
代码运行次数:0
运行
复制
INFERENCE_MODEL_ID = "model_bge_base_zh-v1.5" 
es_client.perform_request(
        'PUT',
        f'/_inference/text_embedding/{INFERENCE_MODEL_ID}',
        body=inference_config
)

(2)创建基于原子服务模型的pipeline,如:model_bge_base_zh-v1.5_embeddings_pipeline的pipeline

代码语言:javascript
代码运行次数:0
运行
复制
PIPELINE_NAME = "model_bge_base_zh-v1.5_embeddings_pipeline"
pipeline_config = {
    "processors": [
        {
            "inference": {
                "model_id": INFERENCE_MODEL_ID,
                "input_output": {
                    "input_field": "content",
                    "output_field": "content_embedding"
                }
            }
        }
    ]
}
 
es_client.ingest.put_pipeline(
        id=PIPELINE_NAME,
        body=pipeline_config
)

(3)文本&向量数据写入

创建向量索引,定义索引Schema,注意向量维度需与上述Embedding模型维度保持一致。

通过 Bulk API 批量写入数据。原始文档信息和向量化之后的数据均进行存储,方便ES混合搜索,提高召回率。

代码语言:javascript
代码运行次数:0
运行
复制
from elasticsearch.helpers import bulk
actions = [
    {
        "_index": INDEX_NAME,
        "_source": {
            "content": content,  # 原始文本内容,根据定义的pipeline,默认向量化后写入pipeline中定义的向量字段
        }
    }
    for index, content in enumerate(chunk_list)
]
success, failed = bulk(
        es_client,
        actions,
        pipeline=PIPELINE_NAME
)

2.在线问答

对应search_inference.py文件,主要讲述用户的一个Query语句进来后,经过向量化,从ES中进行混合检索召回TopK,然后经过Rerank再精排过滤后,结合Prompt工程喂给大模型进行总结回答,具体如下:

2.1.Query 向量化

对Query语句进行向量化,具体调用方式本质上是与切片向量化一致,不再赘述。

2.2.文本&向量混合检索

指定向量字段,对Query向量进行向量相似度检索,获取前k条向量数据,同时进行全文检索,在最后将两者进行融合排序。

代码语言:javascript
代码运行次数:0
运行
复制
# 使用8.16集群的混合搜索语法(并行召回:返回 query 和 knn 召回结果的并集)
mix_query = {
    "query": {"match": {"content": query}},
    "knn": [{
        "field": "content_embedding",
        "query_vector_builder": {
            "text_embedding": {
                "model_id": INFERENCE_MODEL_ID,
                "model_text": query
            }
        },
        "k": 5,
        "num_candidates": 100
    }],
    "size": 5
}

2.3.召回结果重排序

调用智能搜索开发提供的Rerank模型进行重排序。

TopN即为排序返回的top文档数量, 如果没有指定则返回全部候选doc,如果指定的TopN值大于输入的候选doc数量,返回全部doc。

代码语言:javascript
代码运行次数:0
运行
复制
RERANK_MODEL_NAME = "bge-reranker-large"
json_object = {
    "ModelName": RERANK_MODEL_NAME,
    "Query": query,
    "ReturnDocuments": True,
    "Documents": docs,
    "TopN":5    # 排序返回的top文档数量, 如果没有指定则返回全部候选doc,如果指定的TopN值大于输入的候选doc数量,返回全部doc
}

2.4.组装Prompt

设置自己的Prompt模板,注意预留user_query和documents_str占位符,分别代表问题与检索到最相关的切片上下文。

代码语言:javascript
代码运行次数:0
运行
复制
PROMPT_TEMPLATE = """
# 任务说明
你是一位专业的AI助手,需要基于提供的参考文档准确回答用户问题。请严格遵循以下要求:
 
# 用户问题
{user_query}
 
# 参考文档(已按相关性排序)
{documents_str}
 
# 回答要求
1. 必须基于上述参考文档内容生成答案,不得编造文档中不存在的信息
2. 回答结构应包含:
   - 简明扼要的核心答案(1-2句话)
   - 详细解释或分点说明(3-5点,视问题复杂度而定)
   - 可选的补充信息或建议(如适用)
3. 根据问题类型采用合适的回答方式:
   - 对于事实性问题:提供准确数据或事实
   - 对于观点性问题:呈现不同角度的分析
   - 对于操作性问题:给出步骤清晰的指导
   - 对于比较性问题:进行客观对比分析
4. 若文档内容与问题无关或信息不足,请回复:
   "根据现有资料,我暂时无法提供令人满意的答案。建议您补充更多相关信息或重新提问。"
 
# 特别注意
1. 保持回答专业、准确且流畅
2. 如文档中有数据或研究结果,请优先引用
4. 对于专业术语,必要时提供简单解释
5. 避免使用"根据文档"等冗余表述,直接整合信息到回答中
"""

2.5.调用DeepSeek回答

调用deepseek-v3大模型进行总结回答,当然你可以切换不同hunyuan以及deepseek大模型进行体验。如果期望返回值是流式响应时,则设置Stream为True。

代码语言:javascript
代码运行次数:0
运行
复制
# 设置deepseek-v3模型(这里可以设置为你想要的模型,参考:https://cloud.tencent.com/document/api/845/117810)
LLM_MODEL_NAME = "deepseek-v3"
 
# 调用大模型回答,content即为build_llm_prompt返回组装后的prompt
def generate_llm_response(content: str) -> Dict[str, Any]:
    """
    调用大模型生成回答
 
    Args:
        content: 要发送给大模型的prompt内容
 
    Returns:
        大模型的响应结果
 
    Raises:
        Exception: 如果调用大模型过程中出现错误
    """
    if not content.strip():
        raise ValueError("空内容无法生成回答")
 
    json_object = {
        "Messages": [{"Role": "user", "Content": content}],
        "ModelName": LLM_MODEL_NAME,
        "Stream": False    # 可以设置成True,流式输出
    }
 
    try:
        response = tencent_cloud_client.call_json("ChatCompletions", json_object)
        return response
    except Exception as e:
        logger.error(f"大模型调用失败: {e}")
        raise

四、总结

腾讯云ES凭借其在传统PB级日志和海量搜索场景中积累的丰富经验,通过深度重构底层系统,成功地将多年的性能优化、索引构建和运营管理经验应用于RAG领域,并积极探索向量召回与传统搜索技术的融合之道,旨在充分发挥两者的优势,为用户提供更加精准、高效的搜索体验。未来,腾讯云ES将持续深耕智能检索领域,在成本、性能、稳定性等方面持续提升,帮助客户降本增效的同时实现业务价值持续增长。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-08-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯云大数据 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档