我的第一个AI项目:从零搭建RAG知识库的踩坑之旅

项目背景

我是一个 AI 应用开发的初学者。过去,我一直专注于传统的前后端、工具链项目的开发,对于 AI 的了解非常浅薄。 最近,我在正在实现一个工具链的项目并为其编写文档。突然产生了一个想法,那么就是我可续可以利用 MCP 协议告诉 AI 项目的具体细节,然后让它为我编写代码。 说干就干吧,在和 GPT 讨论过后,我决定采用以下技术栈:

  • 后端框架: FastAPI + Python - 选择FastAPI是因为它的异步特性和自动API文档生成
  • 向量数据库: ChromaDB (带内存fallback) - 支持持久化存储,同时提供内存模式用于开发测试
  • 向量化模型: Sentence Transformers - 轻量级且效果不错的embedding模型
  • 大语言模型: 本地Qwen2.5-7B via Ollama - 完全本地化,保护隐私
  • 架构模式: RAG (检索增强生成) - 结合文档检索和LLM生成

踩坑经历

文档分块策略

最开始我直接想把整个文档丢进去向量化,但考虑到模型token限制和文档结构,这条路似乎难以走通。 因此,GPT 告诉我必须先切割文档为一个有一个 Chunk 才能进行向量化。 我的文档都是用 Markdown 格式书写的,其中包含大量的h2、h3、h4标题,这些标题也为分割 Chunk 创造了条件。 花了大概半小时,我实现了一个基于 Markdown 标题的分块策略,而不是简单的按行数切分。

python 复制代码
# 基于标题的分块策略
def create_chunks_by_headers(self, content: str, metadata: Dict) -> List[Chunk]:
    chunks = []
    lines = content.split('\n')
    current_chunk = []
    current_title = metadata.get('title', '')
    
    for line in lines:
        if line.startswith('#'):
            # 保存当前chunk
            if current_chunk:
                chunks.append(Chunk(
                    content='\n'.join(current_chunk),
                    metadata={**metadata, 'title': current_title}
                ))
            current_chunk = [line]
            current_title = line.lstrip('#').strip()
        else:
            current_chunk.append(line)
    
    # 保存最后一个chunk
    if current_chunk:
        chunks.append(Chunk(
            content='\n'.join(current_chunk),
            metadata={**metadata, 'title': current_title}
        ))
    
    return chunks

相似度搜索的困境

文档向量化完成之后,便可以用输入的 Query 去检索向量数据库,然后根据相似度返回结果。 当我满怀期待地测试相似度搜索,搜索的结果却差强人意。 问题在于很多关键词在原文中并不显式出现,导致无法匹配到相关信息。

比如搜索"函数定义",但文档中可能写的是"function declaration"或者"如何创建函数",这种语义相似但词汇不同的情况很多。

GPT 告诉我,可以采用多轮检索的方式来解决这个问题。

多轮检索的改进

后来我了解到多轮检索的概念,并决定试试。

  1. 第一轮: 低阈值(0.3)的广泛搜索,捕获更多候选
  2. 第二轮: 高阈值(0.7)的精炼搜索,筛选高质量结果
  3. 合并去重: 结合两轮结果并去重
python 复制代码
def search_with_context(self, query: str, max_results: int = 5) -> Tuple[List[SearchResult], str]:
    # 第一轮:广泛搜索
    broad_results = self.vector_store.search(query, max_results * 2, threshold=0.3)
    
    # 第二轮:精炼搜索
    refined_results = self.vector_store.search(query, max_results, threshold=0.7)
    
    # 合并结果并去重
    all_results = self._merge_and_deduplicate(broad_results, refined_results)
    
    # 构建上下文
    context = self._build_context(all_results[:max_results])
    
    return all_results[:max_results], context

效果确实有改善,虽然并不完美,但比之前相比至少能匹配上了。

LLM集成的顺利

相比数据预处理的坎坷,LLM集成倒是比较顺利。 我通过Ollama调用本地的Qwen2.5-7B模型,配合合适的prompt模板,效果还算可以。

python 复制代码
def answer_question(self, context: str, query: str) -> str:
    prompt = f"""基于以下文档内容回答问题。如果文档中没有相关信息,请说明无法找到答案。

文档内容:
{context}

问题:{query}

回答:"""
    
    return self.llm_provider.generate(prompt, context, query)

本地模型的好处是完全保护隐私,而且响应速度也还可以接受。

MCP的噩梦

最让我头疼的是MCP协议的实践。GPT给我生成了很多"脏代码",包括:

  • 繁琐的访问链,到处都是any类型
  • 无效的函数签名
  • 错误的参数传递
  • 类型定义混乱

更糟糕的是,我常用的 Cursor IDE 对 MCP 集成支持很差。 在捣鼓了半小时无果后,最终我听取了 AI 的建议直接利用 HTTP 调用替代 MCP。

总结

这个项目让我对 AI 开发有了更深入的理解。 RAG 架构确实很强大,但数据预处理的质量直接影响最终效果。 多轮检索是个不错的改进思路,MCP 协议虽然概念很好,但实际使用还有待成熟。

作为一个 AI 新手,这次经历让我意识到 AI 开发不仅仅是调用 API 那么简单,数据质量、检索策略、prompt 工程都很重要。 虽然踩了不少坑,但收获很大。

相关推荐
桃西西呀6 分钟前
Spring AI Alibaba 之七:我没写一行 tracing 埋点,Agent 每一步却被 Micrometer 看得清清楚楚
人工智能·spring·llm
桃西西呀6 分钟前
Spring AI Alibaba 之八:我让 Agent 直接执行 shell 命令,它却读到了目录里的密钥,沙箱到底防住了什么
人工智能·spring·llm
荣码7 分钟前
从0到1搭一个生产级RAG系统:串联前面21篇所有知识
java·python
小师兄吃牛肉1 小时前
Java语法 | 多重循环
java·开发语言·python
明如正午1 小时前
用 Python 一键把 ASC 文件转成 BLF(基于 python-can)
python·can·blf·asc
leisoo80972 小时前
融资融券数据怎么查两融指标含义与杠杆观察方法 IG50免费开源股票数据API接口
开发语言·jvm·数据库·python·json
MoonOut2 小时前
LLM | OpenAI 兼容模式 与 Anthropic 协议的区别
llm
Patrick在香港2 小时前
同一份脚本,mac 正常 Windows 乱码:open() 默认编码实测(附 3.15 终局)
utf-8·windows·python·macos·跨平台·编码·标准库
勿信日志3 小时前
Playwright 元素定位:一个 width>50 过滤器把 42px 的输入框删掉了
python
larance3 小时前
[菜鸟教程] 机器学习教程十课-Python 机器学习应用
人工智能·python·机器学习