掘金 人工智能 前天 18:00
🚀 RAG系统架构:进阶版
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文详细介绍了如何利用Qwen Agent框架,结合Elasticsearch向量检索,构建一个支持BM25+Embedding混合检索的智能问答系统。该系统能解析PDF/Word文档,提取知识,并通过现代化WebUI提供用户交互。通过技术选型和二次开发,显著提升了信息检索效率、召回率和标书撰写效率,解决了企业在供应商研学基地项目管理中信息查找困难、效率低下等痛点。文章深入剖析了Qwen Agent的优势、二次开发的必要性,以及三层递进式架构设计,为构建企业级RAG系统提供了实践指导。

💡 **构建企业级RAG系统解决业务痛点**:针对企业在供应商管理中面临的信息查找困难、效率低下等问题,通过构建支持本地知识库与网络搜索的智能问答系统,能够解析大量文档(如资质文件、项目案例、标书模板等),实现高效信息检索,提升工作效率,并确保信息全面性。

🔧 **Qwen Agent二次开发以增强检索能力**:虽然Qwen Agent本身是成熟的智能体框架,但其知识库检索能力相对基础。通过二次开发,集成Elasticsearch并支持BM25+Embedding混合检索策略,重构存储层,以及优化用户界面,显著提升了系统的检索效率、召回率和用户体验,使其更适合处理GB级企业数据。

📊 **三层递进式架构设计优化**:系统采用数据存储层(Elasticsearch双索引设计)、检索增强层(多策略检索工具与网络搜索集成)和智能体层(Qwen Agent智能调度)的三层递进式架构。这种模块化设计提高了系统的扩展性,并能灵活应对不同检索需求和信息时效性问题。

🚀 **显著的性能与体验提升**:与以往项目相比,该系统在检索策略上实现了BM25+Embedding混合检索,召回率和准确率均有大幅提升;在架构上,采用了ES双索引、多策略检索和智能体调度;在工程化上,支持高质量结构化解析、多embedding模型兼容及私有化部署,用户体验也从命令行升级为现代化的知乎风格WebUI。

本文基于解析如何从零构建一个支持本地知识库+网络搜索的智能问答系统。项目采用Qwen Agent框架,集成Elasticsearch向量检索,支持BM25+Embedding混合检索,并具备现代化的WebUI界面。

前言

书接上回:【RAG系统架构:让AI学会"查资料"的魔法】

上次提到了通过本地建一个RAG知识库,来帮我们更好的对比保险产品,来选择更适合自己的。

接下来又遇到我好队友公司的一个业务痛点:

她公司负责管理供应商研学基地项目,积累了海量的资料文档(十几个GB的数据量),包括:

传统工作流程的问题

💡 解决方案:RAG智能知识库系统

基于这个真实的业务场景,我决定构建一个基于Qwen Agent的RAG智能知识库系统,目标是:

核心功能

预期效果

技术选型

🤔 看到这里可能就有一个疑问?都用Qwen Agent 还二开它干嘛呢?

🎯 先说一下它技术优势

1. 成熟的智能体框架

# Qwen Agent原生支持ReAct范式class Assistant(FnCallAgent):    def _run(self, messages, **kwargs):        # 自动处理推理-行动循环        # 内置工具调用机制        # 支持多轮对话管理

2. 强大的工具调用能力

3. 灵活的插件机制

# 支持多种工具集成方式function_list = [    {'name': 'retrieval', 'max_ref_token': 4000},    {'name': 'doc_parser', 'parser_page_size': 500},    'code_interpreter',  # 直接字符串    CustomTool()         # 自定义工具类]

🔧 但本身Qwen Agent知识库这块比较一般,就有二次开发的必要性哇

1. 先让增强它的检索能力

# 原生Qwen Agent的检索工具class Retrieval(BaseTool):    # 只支持基础文件检索    # 缺乏向量数据库支持    # 没有混合检索策略# 我们的增强版本class Retrieval(BaseTool):    def call(self, params, **kwargs):        search_type = params.get('search_type', 'hybrid')        # 支持ES双索引检索        # 支持BM25/Embedding/Hybrid三种模式        # 支持网络搜索集成

2. 存储层重构

3. 用户体验优化

📊 技术决策对比

方案开发周期技术风险功能完整性维护成本
从零开发需要重新实现所有功能
基于LangChain功能丰富但复杂
基于Qwen Agent专注业务创新

🎯 二次开发的核心价值

1. 专注业务创新

# 不需要重复造轮子# 专注实现核心业务逻辑class ESMemory:    def hybrid_search(self, query, top_k=5):        # 专注混合检索算法优化        # 专注业务场景适配        # 专注性能调优

2. 降低技术风险

3. 提升开发效率

🔄 二次开发的具体工作

1. 存储层适配

# 修改memory.py,支持ES存储class Memory(Agent):    def __init__(self, memory_type='local'):        if memory_type == 'es':            self.es_memory = ESMemory()  # 新增ES支持        else:            self.es_memory = None        # 保持原有逻辑

2. 检索工具增强

# 在retrieval.py中增加ES检索逻辑if memory_type == 'es' and es_memory is not None:    # 优先使用ES检索    return es_memory.hybrid_search(query, top_k=top_k)else:    # 降级到原有检索逻辑    return self.search.call(params={'query': query}, docs=docs)

3. 配置管理优化

# 统一配置管理,支持多种部署模式llm_cfg = {    'model': LLM_MODEL,           # 支持本地Ollama模型    'model_server': LLM_BASE_URL, # 支持自定义API地址    'api_key': LLM_API_KEY,       # 支持多种认证方式}

💡 这里说个题外话,技术决策的小启示

1. 框架选择原则

2. 二次开发策略

3. 技术债务管理

总结:选择二次修改Qwen Agent是基于"站在巨人肩膀上"的智慧,让我们能够专注业务创新,快速构建企业级RAG系统,同时享受成熟框架带来的稳定性和生态优势。


🔄 技术演进:这里说一下和之前项目的对比

相比之前写的《RAG系统架构通俗解读》文章里的项目,这个实战项目在技术实现上有了显著提升:

📈 检索能力升级

特性之前版本现有版本改进效果
检索策略单一向量检索BM25+Embedding混合检索召回率提升大概10%
索引设计单索引存储双索引分离设计性能提升约有3倍
检索精度基础相似度匹配智能去重+重排序准确率提升约有15%

🏗️ 架构设计优化

🔧 工程化改进

🌐 功能扩展

💼 业务适配

核心优势总结

    企业级特性:私有化部署、数据安全、团队协作智能化程度:混合检索、网络搜索、智能去重用户体验:现代化WebUI、移动端适配、操作简单扩展性强:模块化设计、插件机制、多模型支持

🏗️ 项目架构:三层递进式设计

第一层:数据存储层(Storage Layer)

📊 Elasticsearch双索引设计

# BM25索引:支持关键词精确匹配{    "mappings": {        "properties": {            "doc_name": {"type": "keyword"},            "content": {"type": "text"},            "chunk_id": {"type": "integer"}        }    }}# Embedding索引:支持语义向量检索{    "mappings": {        "properties": {            "content_vector": {                "type": "dense_vector",                "dims": 1024,                "index": True,                "similarity": "cosine"            }        }    }}

设计亮点

🔧 核心实现:ESMemory类

class ESMemory:    def __init__(self, index=ES_INDEX, embedding_index=ES_EMBEDDING_INDEX):        # 初始化ES连接,支持认证和端口配置        self.es = Elasticsearch(es_host_with_port, basic_auth=(username, password))        def hybrid_search(self, query, top_k=5):        """BM25+embedding混合检索,合并去重"""        bm25_results = self.search(query, top_k=top_k)        embedding_results = self.embedding_search(query, top_k=top_k)        # 智能合并,按分数降序排列        return self._merge_and_deduplicate(bm25_results, embedding_results)

第二层:检索增强层(Retrieval Layer)

🎯 多策略检索工具

@register_tool('retrieval')class Retrieval(BaseTool):    def call(self, params, **kwargs):        search_type = params.get('search_type', 'hybrid')  # bm25/embedding/hybrid        top_k = params.get('top_k', 5)                # 优先使用ES检索        if memory_type == 'es' and es_memory is not None:            if search_type == 'bm25':                return es_memory.search(query, top_k=top_k)            elif search_type == 'embedding':                return es_memory.embedding_search(query, top_k=top_k)            else:  # 默认hybrid                return es_memory.hybrid_search(query, top_k=top_k)

检索策略对比

🌐 网络搜索集成

# Tavily-mcp网络搜索工具配置mcp_tools = [{    "mcpServers": {        "tavily-mcp": {            "command": "npx",            "args": ["-y", "tavily-mcp@0.1.4"],            "env": {"TAVILY_API_KEY": TAVILY_API_KEY}        }    }}]

设计思路

第三层:智能体层(Agent Layer)

🤖 Qwen Agent智能调度

class Assistant(FnCallAgent):    def _prepend_knowledge_prompt(self, messages, knowledge=''):        """将检索到的知识注入到对话上下文中"""        if not knowledge:            # 从文件检索知识            *_, last = self.mem.run(messages=messages)            knowledge = last[-1][CONTENT]                # 格式化知识并添加到系统提示中        knowledge_prompt = self._format_knowledge(knowledge)        messages = self._inject_knowledge(messages, knowledge_prompt)        return messages

智能体特色


🔄 数据流:从文档到答案的完整链路

第一步:文档解析与分块

def parse_mineru_json(json_path):    """解析MinerU结构化JSON数据"""    with open(json_path, 'r', encoding='utf-8') as f:        data = json.load(f)        texts = []    if isinstance(data, dict):        if 'pages' in data:            for page in data['pages']:                txt = page.get('text', '')                if txt.strip():                    texts.append(txt)        return textsdef split_text(text, chunk_size=500):    """智能文本分块,保持语义完整性"""    # 按段落、句子等自然边界分块    chunks = []    # 实现逻辑...    return chunks

第二步:向量化与存储

def get_embedding(text: str, client=None) -> list:    """生成文本embedding向量"""    if client is None:        client = OpenAI(            api_key=DASHSCOPE_API_KEY,            base_url=DASHSCOPE_BASE_URL        )        response = client.embeddings.create(        model=EMBEDDING_MODEL,        input=text,        dimensions=EMBEDDING_DIM,        encoding_format="float"    )    return response.data[0].embedding

第三步:智能检索

def hybrid_search(self, query, top_k=5):    """混合检索实现"""    # 1. BM25检索    bm25_results = self.search(query, top_k=top_k)        # 2. Embedding检索    query_vector = get_embedding(query)    embedding_results = self.embedding_search(query_vector, top_k=top_k)        # 3. 结果合并与去重    all_results = bm25_results + embedding_results    merged = self._merge_and_deduplicate(all_results, top_k)        return merged

第四步:知识注入与生成

def _format_knowledge_to_prompt(self, knowledge_results):    """将检索结果格式化为提示词"""    snippets = []    for result in knowledge_results:        snippet = KNOWLEDGE_SNIPPET.format(            source=result['doc_name'],            content=result['content']        )        snippets.append(snippet)        return '\n\n'.join(snippets)

🎨 用户体验:现代化WebUI设计

知乎风格界面

/* 现代化卡片设计 */.chat-container {    background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);    border-radius: 20px;    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);    backdrop-filter: blur(10px);}.message-card {    background: rgba(255, 255, 255, 0.9);    border-radius: 15px;    padding: 20px;    margin: 10px 0;    box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);}

交互功能

# 侧边栏功能按钮with gr.Column(scale=1):    search_btn = gr.Button("搜索")    kb_btn = gr.Button("知识库")    fav_btn = gr.Button("收藏")    history_btn = gr.Button("历史")        # 绑定"暂未实现"提示    for btn in [search_btn, kb_btn, fav_btn, history_btn]:        btn.click(lambda: gr.Info("暂未实现,敬请期待"), outputs=None)

🚀 性能优化与扩展性

检索性能优化

    索引优化:ES索引分片和副本配置缓存策略:文档分块结果缓存批量操作:ES批量写入提升效率异步处理:embedding生成异步化

扩展性设计

    模块化架构:工具、检索、存储层独立配置驱动:通过config.py统一管理插件机制:支持自定义工具扩展多模型支持:兼容不同LLM和Embedding模型

📊 实际效果对比

检索效果对比

检索方式召回率准确率响应时间
BM2585%78%<50ms
Embedding92%85%<100ms
Hybrid95%88%<80ms

用户体验提升


🎯 技术亮点总结

1. 双索引混合检索

2. 网络搜索集成

3. 现代化WebUI

4. 企业级特性


🔮 未来发展方向

短期优化

长期规划


实践建议

    数据质量:确保知识库文档质量和结构化程度检索策略:根据业务场景选择合适的检索方式性能监控:建立检索效果和响应时间监控用户反馈:收集用户反馈持续优化系统

结尾

看到项目部署启动后,在好队友实际使用一段时间后,得出的反馈是大大的有帮助,减少了因这事而加班的时间。

她好才是真的好哇

吹了上面这么多,又到固定环节,大佬们可以去瞄一眼哇

[AI-chat-bot]-Github代码仓库觉得有点小用,记得点个小星星哇

希望这个实战案例能为你的RAG项目提供有价值的参考!* 🚀

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

RAG Qwen Agent Elasticsearch 智能问答 向量检索
相关文章