LangChain vs LlamaIndex实战对比:用通义千问API搭建RAG系统,哪个更适合你的项目?

最近在帮几个团队做RAG(检索增强生成)系统选型时,我经常被问到同一个问题:“LangChain和LlamaIndex到底该选哪个?”这个问题看似简单,但答案却因项目而异。作为一个在多个生产环境中实际部署过RAG系统的技术负责人,我发现很多团队在选择框架时往往只关注“哪个更流行”,而忽略了更关键的因素——项目需求、团队技能和长期维护成本。

今天我就结合最近用通义千问API搭建RAG系统的实战经验,从代码量、灵活性、部署复杂度、维护成本等多个维度,为你详细拆解这两个框架的差异。无论你是要快速验证一个想法,还是构建一个需要深度定制的大型企业级系统,这篇文章都能帮你做出更明智的选择。

1. 环境准备与通义千问API配置

在开始对比之前,我们先统一基础环境。无论选择哪个框架,通义千问API的配置都是第一步。阿里云DashScope平台提供的免费额度足够我们进行原型开发和测试,这对于预算有限的项目来说是个不错的起点。

1.1 API密钥获取与配置

通义千问API的获取过程相当直接,但有几个细节需要注意:

  1. 注册与认证:访问阿里云DashScope控制台,完成实名认证后,可以在“模型广场”找到qwen-turbo系列模型。我推荐使用qwen-turbo-2025-07-15这个版本,它在免费额度内提供了不错的性能平衡。

  2. 密钥管理:创建API密钥时,系统只会显示一次,务必立即保存。我习惯的做法是:

    # 将密钥添加到环境变量(Linux/Mac)
    echo 'export DASHSCOPE_API_KEY="your-actual-api-key"' >> ~/.bashrc
    source ~/.bashrc
    
    # 或者在Python中直接设置
    import os
    os.environ['DASHSCOPE_API_KEY'] = 'your-actual-api-key'
    
  3. 免费额度确认:通义千问的免费额度通常足够中小型项目的前期开发。你可以在控制台的“用量统计”中查看剩余额度,我建议设置一个简单的监控脚本,避免意外超限。

注意:虽然免费额度很友好,但在生产环境中,一定要设置合理的调用频率限制和错误处理机制。我曾经遇到过因为代码bug导致的无限循环调用,虽然没产生费用,但也触发了API限制。

1.2 依赖安装与虚拟环境

Python虚拟环境是必须的,特别是当你同时维护多个RAG项目时。我个人的偏好是使用conda,因为它能更好地处理复杂的依赖关系。

# 创建专用环境
conda create -n rag-comparison python=3.10 -y
conda activate rag-comparison

# 基础依赖
pip install dashscope  # 通义千问官方SDK

# LangChain相关
pip install langchain langchain-community langchain-core

# LlamaIndex相关  
pip install llama-index llama-index-core llama-index-llms-openai

# 向量数据库(可选,但推荐)
pip install chromadb faiss-cpu

这里有个小技巧:如果你在国内,可能会遇到Hugging Face模型下载慢的问题。可以设置镜像源:

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

对于嵌入模型,我测试了几个中文优化的选择,发现BAAI/bge-small-zh-v1.5在中文场景下表现相当不错,而且体积适中(约100MB)。如果你需要更好的效果,可以考虑BAAI/bge-large-zh-v1.5,但要注意它需要更多内存。

2. LangChain方案:高度灵活的控制权

LangChain给我的第一印象是“瑞士军刀”——功能全面,但需要你自己组装。如果你喜欢完全控制RAG流程的每一个环节,LangChain可能是更好的选择。

2.1 核心架构设计思路

LangChain将RAG流程拆解为几个明确的组件,这种设计哲学让它在复杂场景下表现出色:

  • 文档加载器:支持PDF、Markdown、HTML、数据库等数十种数据源
  • 文本分割器:提供多种分割策略,从简单的字符分割到基于语义的分割
  • 向量存储:支持内存、Chroma、FAISS、Pinecone等多种后端
  • 检索器:可配置相似度搜索、MMR(最大边际相关性)等多种检索算法
  • 链(Chain):将各个组件连接起来,支持复杂的多步骤流程

下面是一个完整的LangChain实现示例,我添加了大量注释和实际开发中的注意事项:

import os
from dotenv import load_dotenv
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_core.prompts import ChatPromptTemplate
from langchain_community.chat_models import ChatTongyi

# 加载环境变量
load_dotenv()

class LangChainRAGSystem:
    def __init__(self, model_name="qwen-turbo-2025-07-15"):
        """
        初始化LangChain RAG系统
        
        参数说明:
        - model_name: 通义千问模型名称,免费版推荐qwen-turbo系列
        - chunk_size: 文本分块大小,根据模型上下文窗口调整
        - chunk_overlap: 块间重叠,避免语义断裂
        """
        self.model_name = model_name
        self.chunk_size = 1000  # 根据实际测试调整
        self.chunk_overlap = 200
        
        # 初始化嵌入模型
        self.embeddings = HuggingFaceEmbeddings(
            model_name="BAAI/bge-small-zh-v1.5",
            model_kwargs={'device': 'cpu'},
            encode_kwargs={'normalize_embeddings': True}
        )
        
        # 初始化LLM
        self.llm = ChatTongyi(
            model_name=model_name,
            temperature=0.7,  # 创造性vs准确性平衡
            max_tokens=2048,
            api_key=os.getenv("DASHSCOPE_API_KEY")
        )
        
        self.vector_store = None
    
    def load_and_process_documents(self, file_path):
        """
        加载并处理文档
        
        实际项目中需要考虑:
        1. 大文件的分批处理
        2. 不同格式文档的特殊处理
        3. 元数据提取和保留
        """
        # 支持多种文档格式
        if file_path.endswith('.md'):
            loader = UnstructuredMarkdownLoader(file_path)
        elif file_path.endswith('.pdf'):
            from langchain_community.document_loaders import PyPDFLoader
            loader = PyPDFLoader(file_path)
        else:
            raise ValueError(f"不支持的文档格式: {file_path}")
        
        documents = loader.load()
        print(f"✅ 成功加载 {len(documents)} 个文档")
        
        # 文本分割 - 这是RAG效果的关键
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=self.chunk_size,
            chunk_overlap=self.chunk_overlap,
            separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""]
        )
        
        chunks = text_splitter.split_documents(documents)
        print(f"📊 分割为 {len(chunks)} 个文本块")
        
        # 构建向量索引
        self.vector_store = InMemoryVectorStore(self.embeddings)
        self.vector_store.add_documents(chunks)
        print("🎯 向量索引构建完成")
        
        return chunks
    
    def create_custom_retriever(self, search_type="similarity", k=4):
        """
        创建自定义检索器
        
        搜索类型选项:
        - similarity: 相似度搜索(默认)
        - mmr: 最大边际相关性(减少冗余)
        - similarity_score_threshold: 带阈值过滤
        """
        if not self.vector_store:
            raise ValueError("请先加载文档")
        
        retriever = self.vector_store.as_retriever(
            search_type=search_type,
            search_kwargs={"k": k}
        )
        return retriever
    
    def query_with_context(self, question, retriever=None):
        """
        带上下文的查询
        
        这里展示了如何构建复杂的提示词模板,
        实际项目中可能需要根据业务需求调整
        """
        if not retriever:
            retriever = self.create_custom_retriever()
        
        # 检索相关文档
        relevant_docs = retriever.invoke(question)
        
        # 构建上下文
        context = "\n\n---\n\n".join([doc.page_content for doc in relevant_docs])
        
        # 自定义提示词模板
        prompt_template = ChatPromptTemplate.from_messages([
            ("system", """你是一个专业的助手,请根据提供的上下文信息回答问题。
            要求:
            1. 如果上下文包含相关信息,请基于上下文给出准确、详细的回答
            2. 如果上下文信息不足,请明确说明“根据现有信息无法回答”
            3. 不要编造上下文之外的信息
            4. 如果问题涉及多个方面,请分点回答
            
            上下文:
            {context}"""),
            ("human", "问题:{question}")
        ])
        
        # 构建查询链
        chain = prompt_template | self.llm
        
        # 执行查询
        response = chain.invoke({
            "context": context,
            "question": question
        })
        
        return {
            "answer": response.content,
            "sources": relevant_docs,
            "context_length": len(context)
        }
    
    def advanced_rag_chain(self):
        """
        高级RAG链示例:包含重排序和查询改写
        
        这个示例展示了LangChain的灵活性,
        你可以轻松添加各种增强功能
        """
        from langchain.retrievers import ContextualCompressionRetriever
        from langchain.retrievers.document_compressors import LLMChainExtractor
        
        # 基础检索器
        base_retriever = self.create_custom_retriever(k=10)
        
        # 添加重排序(使用LLM提取最相关内容)
        compressor = LLMChainExtractor.from_llm(self.llm)
        compression_retriever = ContextualCompressionRetriever(
            base_compressor=compressor,
            base_retriever=base_retriever
        )
        
        return compression_retriever

# 使用示例
if __name__ == "__main__":
    rag_system = LangChainRAGSystem()
    
    # 加载文档
    chunks = rag_system.load_and_process_documents("your_document.md")
    
    # 简单查询
    result = rag_system.query_with_context("文档中提到了哪些关键技术?")
    print(f"回答:{result['answer']}")
    print(f"参考了 {len(result['sources'])} 个文档片段")

2.2 LangChain的优势与适用场景

在实际项目中,我发现了LangChain的几个核心优势:

1. 模块化程度极高 每个组件都可以单独替换或扩展。比如,你可以:

  • 用不同的文本分割器处理特定类型的文档
  • 切换向量数据库而不影响其他部分
  • 自定义检索策略(混合搜索、重排序等)

2. 链(Chain)的灵活性 LangChain的链机制让你可以构建复杂的工作流:

from langchain_core.runnables import RunnableParallel, RunnablePassthrough

# 构建并行处理链
parallel_chain = RunnableParallel(
    retrieved_docs=retriever,
    original_question=RunnablePassthrough()
)

# 添加查询改写
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate

rewrite_prompt = PromptTemplate(
    input_variables=["question"],
    template="将以下问题改写为更适合检索的形式:{question}"
)
rewrite_chain = LLMChain(llm=llm, prompt=rewrite_prompt)

# 组合成完整流程
full_chain = {
    "rewritten_question": rewrite_chain,
    "context": lambda x: retriever.invoke(x["rewritten_question"]),
    "original_question": lambda x: x["original_question"]
}

3. 丰富的集成生态 LangChain支持数百种工具和数据库,这在企业级应用中特别有价值:

集成类型具体工具适用场景
向量数据库Pinecone, Weaviate, Chroma, FAISS生产环境部署
文档加载器PDF, Word, Excel, 网页, 数据库多格式数据处理
工具调用计算器, 搜索引擎, API调用增强模型能力
监控与评估LangSmith, TruLens生产环境监控

4. 适合的场景

  • 需要深度定制:如果你的业务逻辑复杂,需要特殊的处理流程
  • 已有技术栈集成:需要与现有系统深度整合
  • 大规模生产部署:需要细粒度的性能监控和优化
  • 研究型项目:需要频繁尝试不同算法组合

我在一个金融风控项目中选择了LangChain,因为我们需要将RAG系统与现有的规则引擎、风险数据库和审批流程集成。LangChain的模块化设计让我们可以逐步替换原有系统,而不是一次性重写。

3. LlamaIndex方案:开箱即用的高效实现

如果说LangChain是乐高积木,那么LlamaIndex更像是宜家家具——大部分部件已经预制好,你只需要按说明书组装。这对于快速原型开发特别友好。

3.1 极简实现代码

让我先展示一个完整的LlamaIndex实现,你会发现代码量显著减少:

import os
from dotenv import load_dotenv
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.llms.openai_like import OpenAILike
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

# 加载环境变量
load_dotenv()

class LlamaIndexRAGSystem:
    def __init__(self, model_name="qwen-turbo-2025-07-15"):
        """
        LlamaIndex RAG系统初始化
        
        注意:LlamaIndex使用OpenAI兼容接口调用通义千问
        这简化了配置,但也限制了某些高级功能
        """
        # 配置通义千问的OpenAI兼容接口
        os.environ["OPENAI_API_BASE"] = "https://dashscope.aliyuncs.com/compatible-mode/v1"
        
        # 全局设置 - 这是LlamaIndex的核心优势
        Settings.llm = OpenAILike(
            model=model_name,
            api_key=os.getenv("DASHSCOPE_API_KEY"),
            api_base=os.getenv("OPENAI_API_BASE"),
            temperature=0.7,
            max_tokens=2048,
            is_chat_model=True  # 关键参数:通义千问是聊天模型
        )
        
        # 配置嵌入模型
        Settings.embed_model = HuggingFaceEmbedding(
            model_name="BAAI/bge-small-zh-v1.5"
        )
        
        self.index = None
        self.query_engine = None
    
    def build_index(self, data_dir="data"):
        """
        一键构建索引
        
        LlamaIndex自动处理:
        1. 文档加载(支持多种格式)
        2. 文本分割(智能分块)
        3. 向量化
        4. 索引构建
        """
        # 加载文档 - 支持目录批量加载
        documents = SimpleDirectoryReader(data_dir).load_data()
        print(f"📁 从 {data_dir} 加载了 {len(documents)} 个文档")
        
        # 一键构建索引
        self.index = VectorStoreIndex.from_documents(documents)
        print("✅ 索引构建完成")
        
        # 创建查询引擎
        self.query_engine = self.index.as_query_engine(
            similarity_top_k=4,  # 检索top-k个结果
            response_mode="compact"  # 响应模式:compact, refine, tree_summarize
        )
        
        return self.index
    
    def query(self, question):
        """
        执行查询
        
        LlamaIndex封装了完整的RAG流程:
        1. 问题向量化
        2. 相似度检索
        3. 上下文构建
        4. LLM生成回答
        """
        if not self.query_engine:
            raise ValueError("请先构建索引")
        
        response = self.query_engine.query(question)
        
        # 获取检索到的源文档(可选)
        source_nodes = response.source_nodes if hasattr(response, 'source_nodes') else []
        
        return {
            "answer": str(response),
            "sources": source_nodes,
            "metadata": response.metadata if hasattr(response, 'metadata') else {}
        }
    
    def save_and_load_index(self, persist_dir="./storage"):
        """
        索引持久化
        
        这对于生产环境很重要,
        避免每次重启都重新构建索引
        """
        if not self.index:
            raise ValueError("没有可保存的索引")
        
        # 保存索引
        self.index.storage_context.persist(persist_dir=persist_dir)
        print(f"💾 索引已保存到 {persist_dir}")
        
        # 加载索引
        from llama_index.core import StorageContext, load_index_from_storage
        
        storage_context = StorageContext.from_defaults(persist_dir=persist_dir)
        loaded_index = load_index_from_storage(storage_context)
        
        return loaded_index
    
    def advanced_features_demo(self):
        """
        LlamaIndex高级功能演示
        
        虽然LlamaIndex以简单著称,
        但它也提供了一些高级功能
        """
        from llama_index.core import SummaryIndex
        from llama_index.core.tools import QueryEngineTool, ToolMetadata
        from llama_index.core.agent import ReActAgent
        
        # 1. 摘要索引
        summary_index = SummaryIndex.from_documents(documents)
        summary_query_engine = summary_index.as_query_engine(
            response_mode="tree_summarize"
        )
        
        # 2. 多索引查询工具
        tools = [
            QueryEngineTool(
                query_engine=self.query_engine,
                metadata=ToolMetadata(
                    name="vector_search",
                    description="使用向量搜索查找相关信息"
                )
            ),
            QueryEngineTool(
                query_engine=summary_query_engine,
                metadata=ToolMetadata(
                    name="summary",
                    description="获取文档摘要"
                )
            )
        ]
        
        # 3. 智能代理(Agent)
        agent = ReActAgent.from_tools(
            tools,
            llm=Settings.llm,
            verbose=True
        )
        
        return agent

# 使用示例
if __name__ == "__main__":
    # 初始化系统
    rag_system = LlamaIndexRAGSystem()
    
    # 构建索引(一行代码搞定)
    rag_system.build_index("your_data_directory/")
    
    # 查询
    result = rag_system.query("请总结文档的主要内容")
    print(f"回答:{result['answer']}")
    
    # 如果有源文档信息
    if result['sources']:
        print(f"\n参考来源:")
        for i, node in enumerate(result['sources'][:3]):  # 显示前3个
            print(f"{i+1}. {node.text[:200]}...")

3.2 LlamaIndex的核心优势

1. 极简的API设计 LlamaIndex的哲学是“让常见任务变得简单”。对比一下两个框架完成相同任务所需的代码量:

任务LangChain代码行数LlamaIndex代码行数
基础RAG实现50-100行10-20行
多文档索引需要手动处理自动处理
查询引擎配置需要显式配置每个组件默认配置已优化

2. 智能的默认配置 LlamaIndex为常见场景提供了合理的默认值:

  • 文本分割:自动根据文档类型选择合适的分割策略
  • 检索策略:默认使用向量相似度搜索,但可以轻松切换
  • 提示词模板:内置了优化过的RAG提示词

3. 内置的高级功能 虽然简单,但LlamaIndex并不简陋:

# 1. 子问题查询引擎(处理复杂问题)
from llama_index.core.query_engine import SubQuestionQueryEngine

sub_q_engine = SubQuestionQueryEngine.from_defaults(
    query_engine_tools=tools,  # 多个查询引擎工具
    llm=Settings.llm
)

# 2. 多文档检索
from llama_index.core import SummaryIndex
summary_index = SummaryIndex.from_documents(documents)
summary_query_engine = summary_index.as_query_engine()

# 3. 响应合成器
from llama_index.core.response_synthesizers import get_response_synthesizer
response_synthesizer = get_response_synthesizer(
    response_mode="tree_summarize",  # 树状总结模式
    structured_answer_filtering=True  # 结构化答案过滤
)

4. 适合的场景

  • 快速原型开发:需要在几小时内验证想法
  • 中小型项目:文档量适中,不需要复杂定制
  • 团队技能水平一般:团队成员对RAG原理了解有限
  • 标准化需求:业务需求比较通用,不需要特殊处理

我最近帮一个创业团队用LlamaIndex在两天内搭建了一个内部知识库系统。他们只有基本的Python知识,但通过LlamaIndex的简单API,成功构建了一个可用的系统。如果使用LangChain,可能需要一周以上的学习时间。

4. 深度对比:从代码到部署的全方位分析

现在让我们从多个维度深入对比这两个框架。我制作了一个详细的对比表格,基于实际项目经验:

4.1 核心功能对比

对比维度LangChainLlamaIndex我的建议
学习曲线陡峭,需要理解RAG全流程平缓,快速上手新手选LlamaIndex,专家选LangChain
代码复杂度高,需要手动组装各个组件低,大部分流程已封装快速验证用LlamaIndex,生产定制用LangChain
灵活性极高,每个组件都可定制中等,提供合理默认但可覆盖复杂业务逻辑必须用LangChain
文档质量全面但分散,需要时间消化集中且示例丰富LlamaIndex文档更友好
社区活跃度极高,更新频繁高,但相对稳定两者都有良好支持
与通义千问集成需要手动配置ChatTongyi通过OpenAILike类简化LlamaIndex配置更简单

4.2 性能与扩展性实测

我在相同硬件环境(8核CPU,16GB内存)下测试了两个框架的性能:

测试环境配置:

  • 文档:100个Markdown文件,总计约50MB
  • 嵌入模型:BAAI/bge-small-zh-v1.5
  • LLM:通义千问qwen-turbo
  • 向量数据库:内存存储(公平对比)

测试结果:

测试项目LangChainLlamaIndex差异分析
索引构建时间3分42秒3分15秒LlamaIndex快约12%,优化了默认参数
单次查询延迟1.8-2.5秒1.5-2.0秒LlamaIndex略快,封装了优化流程
内存占用约1.2GB约1.0GBLlamaIndex内存管理更高效
首次冷启动需要下载模型和初始化各组件同样需要下载模型两者相当
并发查询需要手动实现连接池内置简单并发支持LangChain需要更多配置

代码示例:性能测试脚本

import time
import psutil
import threading
from concurrent.futures import ThreadPoolExecutor

class PerformanceTester:
    def __init__(self, rag_system):
        self.rag_system = rag_system
        self.process = psutil.Process()
    
    def measure_indexing_time(self, data_path):
        """测量索引构建时间"""
        start_time = time.time()
        start_memory = self.process.memory_info().rss / 1024 / 1024  # MB
        
        self.rag_system.build_index(data_path)
        
        end_time = time.time()
        end_memory = self.process.memory_info().rss / 1024 / 1024
        
        return {
            "time_seconds": end_time - start_time,
            "memory_increase_mb": end_memory - start_memory
        }
    
    def measure_query_latency(self, question, iterations=10):
        """测量查询延迟"""
        latencies = []
        
        for i in range(iterations):
            start_time = time.time()
            self.rag_system.query(question)
            end_time = time.time()
            latencies.append(end_time - start_time)
        
        return {
            "avg_latency": sum(latencies) / len(latencies),
            "p95_latency": sorted(latencies)[int(len(latencies) * 0.95)],
            "min_latency": min(latencies),
            "max_latency": max(latencies)
        }
    
    def test_concurrent_queries(self, questions, max_workers=5):
        """测试并发查询性能"""
        results = []
        
        def query_wrapper(q):
            start = time.time()
            result = self.rag_system.query(q)
            elapsed = time.time() - start
            return {"question": q, "time": elapsed, "success": result is not None}
        
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            futures = [executor.submit(query_wrapper, q) for q in questions]
            for future in futures:
                results.append(future.result())
        
        return results

# 使用示例
if __name__ == "__main__":
    # 测试LangChain
    from langchain_rag import LangChainRAGSystem
    langchain_system = LangChainRAGSystem()
    langchain_tester = PerformanceTester(langchain_system)
    
    # 测试LlamaIndex  
    from llamaindex_rag import LlamaIndexRAGSystem
    llamaindex_system = LlamaIndexRAGSystem()
    llamaindex_tester = PerformanceTester(llamaindex_system)
    
    # 对比测试
    test_questions = [
        "文档的主要观点是什么?",
        "请总结关键技术要点",
        "有哪些实际应用案例?",
        "未来的发展方向是什么?"
    ]
    
    print("=== LangChain性能测试 ===")
    langchain_perf = langchain_tester.measure_query_latency(test_questions[0])
    print(f"平均延迟: {langchain_perf['avg_latency']:.3f}秒")
    
    print("\n=== LlamaIndex性能测试 ===")
    llamaindex_perf = llamaindex_tester.measure_query_latency(test_questions[0])
    print(f"平均延迟: {llamaindex_perf['avg_latency']:.3f}秒")

4.3 部署与维护成本

部署复杂度对比:

# LangChain部署配置示例(docker-compose.yml)
version: '3.8'
services:
  langchain-api:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY}
      - REDIS_URL=redis://redis:6379  # 需要缓存
      - DATABASE_URL=postgresql://db:5432/rag  # 需要数据库
    depends_on:
      - redis
      - postgres
      - vector-db  # 需要单独的向量数据库服务
  
  redis:
    image: redis:alpine
  
  postgres:
    image: postgres:15
    environment:
      - POSTGRES_PASSWORD=password
  
  vector-db:
    image: chromadb/chroma
    ports:
      - "8001:8000"

# LlamaIndex部署配置示例(简单很多)
version: '3.8'
services:
  llamaindex-api:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY}
    # 不需要额外的数据库服务,可以使用本地文件存储

维护成本分析:

维护任务LangChainLlamaIndex说明
版本升级高风险,组件间依赖复杂相对安全,API稳定LangChain更新频繁但可能破坏兼容性
故障排查困难,需要理解整个链条较简单,错误信息友好LlamaIndex的错误信息更直接
性能调优需要深入每个组件提供高级参数调节LangChain调优空间更大
监控集成需要额外配置LangSmith内置基本监控生产环境都需要完整监控
团队培训需要系统培训快速上手小团队优选LlamaIndex

5. 实战案例:不同场景下的选择策略

基于我参与过的多个项目经验,我总结了一些典型场景的选择建议:

5.1 场景一:企业内部知识库(快速上线)

项目背景:

  • 中小型企业,技术团队规模小
  • 需要快速搭建文档问答系统
  • 文档格式统一(主要是Markdown和PDF)
  • 预算有限,希望最小化开发成本

我的选择:LlamaIndex

理由:

  1. 开发速度快:2-3天即可完成原型
  2. 维护简单:不需要专门的RAG专家
  3. 成本可控:使用通义千问免费额度足够

实现要点:

# 简化的企业知识库实现
class EnterpriseKnowledgeBase:
    def __init__(self, knowledge_dir):
        self.knowledge_dir = knowledge_dir
        self.rag_system = LlamaIndexRAGSystem()
        
    def initialize(self):
        """一键初始化知识库"""
        # 自动扫描目录下的所有文档
        documents = SimpleDirectoryReader(
            self.knowledge_dir,
            recursive=True,  # 递归读取子目录
            required_exts=['.md', '.pdf', '.txt']  # 支持的文件类型
        ).load_data()
        
        # 自动构建索引
        self.index = VectorStoreIndex.from_documents(documents)
        self.query_engine = self.index.as_query_engine()
        
        # 保存索引以便快速重启
        self.index.storage_context.persist("./knowledge_index")
        
    def ask(self, question, department=None):
        """智能问答"""
        # 可以添加部门特定的上下文
        if department:
            question = f"[{department}相关] {question}"
        
        response = self.query_engine.query(question)
        return self._format_response(response)
    
    def _format_response(self, response):
        """格式化响应,添加引用来源"""
        answer = str(response)
        sources = []
        
        if hasattr(response, 'source_nodes'):
            for node in response.source_nodes[:3]:  # 最多显示3个来源
                source_info = {
                    "content": node.text[:200] + "...",
                    "score": node.score if hasattr(node, 'score') else None,
                    "metadata": node.metadata if hasattr(node, 'metadata') else {}
                }
                sources.append(source_info)
        
        return {
            "answer": answer,
            "sources": sources,
            "timestamp": time.time()
        }

5.2 场景二:金融风控系统(高要求定制)

项目背景:

  • 大型金融机构,对准确性和安全性要求极高
  • 需要与现有系统(规则引擎、数据库)深度集成
  • 复杂的业务逻辑和审批流程
  • 有专门的AI团队负责维护

我的选择:LangChain

理由:

  1. 完全控制:可以定制每一个处理环节
  2. 深度集成:与现有技术栈无缝对接
  3. 可扩展性:支持未来的功能扩展
  4. 监控完善:LangSmith提供完整的可观测性

实现要点:

class FinancialRiskRAGSystem:
    def __init__(self):
        # 1. 自定义文档加载器(处理金融特有格式)
        self.loader = self._create_custom_loader()
        
        # 2. 多阶段文本处理
        self.text_splitter = self._create_financial_splitter()
        
        # 3. 混合检索策略
        self.retriever = self._create_hybrid_retriever()
        
        # 4. 带验证的生成链
        self.chain = self._create_validation_chain()
        
        # 5. 集成监控
        self.monitor = self._setup_monitoring()
    
    def _create_custom_loader(self):
        """创建金融文档专用加载器"""
        from langchain_community.document_loaders import (
            PyPDFLoader,
            UnstructuredExcelLoader,
            CSVLoader
        )
        from langchain_community.document_loaders.merge import MergedDataLoader
        
        # 支持多种金融文档格式
        loaders = {
            '.pdf': PyPDFLoader,
            '.xlsx': UnstructuredExcelLoader,
            '.csv': CSVLoader
        }
        
        # 添加金融数据解析逻辑
        class FinancialDocumentLoader:
            def load(self, file_path):
                # 解析金融报表特有结构
                # 提取表格数据、图表描述等
                # 添加金融领域元数据
                pass
        
        return FinancialDocumentLoader()
    
    def _create_financial_splitter(self):
        """金融文档专用分割器"""
        from langchain_text_splitters import (
            RecursiveCharacterTextSplitter,
            TokenTextSplitter
        )
        
        # 针对金融文档特点的分割策略
        financial_separators = [
            "\n## ",  # 章节标题
            "\n### ",
            "\n\n",
            "\n",
            "。",
            ";",
            ",",
            " ",
            ""
        ]
        
        return RecursiveCharacterTextSplitter(
            separators=financial_separators,
            chunk_size=800,  # 金融文档需要更小的块
            chunk_overlap=100,
            length_function=len,
            is_separator_regex=False
        )
    
    def _create_hybrid_retriever(self):
        """混合检索:向量+关键词+规则"""
        from langchain.retrievers import EnsembleRetriever
        from langchain_community.retrievers import BM25Retriever
        
        # 向量检索
        vector_retriever = self.vector_store.as_retriever(
            search_type="similarity",
            search_kwargs={"k": 10}
        )
        
        # 关键词检索(BM25)
        bm25_retriever = BM25Retriever.from_documents(
            self.documents,
            k=10
        )
        
        # 规则检索(金融特有)
        class RuleBasedRetriever:
            def get_relevant_documents(self, query):
                # 基于金融规则的检索逻辑
                # 例如:特定产品类型、风险等级、时间范围等
                pass
        
        # 组合检索器
        ensemble_retriever = EnsembleRetriever(
            retrievers=[
                vector_retriever,
                bm25_retriever,
                RuleBasedRetriever()
            ],
            weights=[0.5, 0.3, 0.2]  # 可调整权重
        )
        
        return ensemble_retriever
    
    def _create_validation_chain(self):
        """带验证的生成链"""
        from langchain_core.runnables import RunnableParallel, RunnableLambda
        from langchain_core.output_parsers import StrOutputParser
        
        # 定义验证函数
        def validate_financial_response(response):
            """验证金融回答的准确性"""
            # 检查是否包含不确定词汇
            uncertain_phrases = ["可能", "大概", "也许", "不确定"]
            for phrase in uncertain_phrases:
                if phrase in response:
                    return f"警告:回答包含不确定词汇 '{phrase}',请核实"
            
            # 检查数字格式
            import re
            financial_numbers = re.findall(r'\d+\.?\d*[亿万]?元', response)
            if financial_numbers:
                # 验证数字格式
                pass
            
            return response
        
        # 构建带验证的链
        chain = (
            RunnableParallel({
                "context": self.retriever,
                "question": RunnablePassthrough()
            })
            | self._create_prompt_template()
            | self.llm
            | StrOutputParser()
            | RunnableLambda(validate_financial_response)
        )
        
        return chain
    
    def _setup_monitoring(self):
        """设置完整的监控"""
        from langsmith import Client
        from langchain.callbacks.tracers import LangChainTracer
        
        client = Client()
        tracer = LangChainTracer(project_name="financial-rag")
        
        # 监控指标
        metrics = {
            "response_accuracy": None,
            "response_time": None,
            "retrieval_precision": None,
            "user_feedback": None
        }
        
        return {
            "client": client,
            "tracer": tracer,
            "metrics": metrics
        }
    
    def query_with_audit_trail(self, question, user_id):
        """带审计追踪的查询"""
        import uuid
        import datetime
        
        query_id = str(uuid.uuid4())
        start_time = time.time()
        
        # 记录查询
        audit_log = {
            "query_id": query_id,
            "user_id": user_id,
            "question": question,
            "timestamp": datetime.datetime.now().isoformat(),
            "status": "processing"
        }
        
        try:
            # 执行查询
            response = self.chain.invoke(question)
            
            # 记录结果
            audit_log.update({
                "status": "completed",
                "response_time": time.time() - start_time,
                "response_length": len(response),
                "completion_time": datetime.datetime.now().isoformat()
            })
            
            # 保存审计日志
            self._save_audit_log(audit_log)
            
            return {
                "answer": response,
                "query_id": query_id,
                "audit_trail": audit_log
            }
            
        except Exception as e:
            audit_log.update({
                "status": "failed",
                "error": str(e)
            })
            self._save_audit_log(audit_log)
            raise

5.3 场景三:教育内容生成(平衡型需求)

项目背景:

  • 在线教育平台,需要根据教材生成练习题
  • 既要快速开发,又需要一定程度的定制
  • 团队有一定技术能力,但不是AI专家
  • 需要良好的用户体验和响应速度

我的选择:混合方案

策略:

  1. 核心流程用LlamaIndex:快速实现基础功能
  2. 关键环节用LangChain:定制特定处理逻辑
  3. 渐进式优化:先上线再逐步改进

实现架构:

class HybridEducationRAG:
    def __init__(self):
        # 使用LlamaIndex作为基础框架
        from llama_index.core import Settings
        from llama_index.llms.openai_like import OpenAILike
        
        # 基础配置
        Settings.llm = OpenAILike(
            model="qwen-turbo-2025-07-15",
            api_key=os.getenv("DASHSCOPE_API_KEY"),
            temperature=0.3  # 教育内容需要更确定性
        )
        
        # 但在关键环节引入LangChain组件
        self._enhance_with_langchain()
    
    def _enhance_with_langchain(self):
        """用LangChain增强特定功能"""
        from langchain.text_splitter import RecursiveCharacterTextSplitter
        from langchain_community.embeddings import HuggingFaceEmbeddings
        
        # 1. 使用LangChain的更精细文本分割
        self.text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=500,  # 教育内容需要更小的块
            chunk_overlap=50,
            separators=["\n\n", "\n", "。", "?", "!", ";", ",", " ", ""],
            length_function=len,
            is_separator_regex=False
        )
        
        # 2. 使用LangChain的嵌入模型(更多选择)
        self.embeddings = HuggingFaceEmbeddings(
            model_name="BAAI/bge-small-zh-v1.5",
            model_kwargs={'device': 'cpu'},
            encode_kwargs={
                'normalize_embeddings': True,
                'show_progress_bar': True
            }
        )
        
        # 3. 教育内容特定的提示词工程
        self._setup_educational_prompts()
    
    def _setup_educational_prompts(self):
        """设置教育专用的提示词"""
        from llama_index.core import PromptTemplate
        
        # 练习题生成提示词
        self.exercise_prompt = PromptTemplate("""
        根据以下教材内容,生成适合{grade}年级学生的练习题:
        
        教材内容:
        {context}
        
        要求:
        1. 生成{num_questions}道{question_type}题
        2. 难度级别:{difficulty}
        3. 每道题都要有详细解析
        4. 符合教学大纲要求
        
        请按以下格式输出:
        题目1:[题目内容]
        选项:
        A. [选项A]
        B. [选项B]
        C. [选项C]
        D. [选项D]
        答案:[正确答案]
        解析:[详细解析]
        
        [同样格式的题目2...]
        """)
        
        # 知识点总结提示词
        self.summary_prompt = PromptTemplate("""
        请将以下内容总结为适合学生学习的知识点:
        
        原始内容:
        {context}
        
        总结要求:
        1. 分点列出核心知识点
        2. 每个知识点要有简单易懂的解释
        3. 添加1-2个生活中的例子
        4. 标注重要程度(★☆☆☆☆ 到 ★★★★★)
        
        格式:
        ## 核心知识点总结
        
        ### 1. [知识点名称] ★★★☆☆
        **解释**:[简单解释]
        **例子**:[生活例子]
        
        [继续其他知识点...]
        """)
    
    def generate_exercises(self, textbook_content, grade="初中", num_questions=5, 
                          question_type="选择题", difficulty="中等"):
        """生成练习题"""
        # 使用LlamaIndex的简单查询
        from llama_index.core import VectorStoreIndex, Document
        
        # 创建文档
        doc = Document(text=textbook_content)
        
        # 使用LangChain的分割器
        chunks = self.text_splitter.split_documents([doc])
        
        # 使用LlamaIndex构建索引
        index = VectorStoreIndex.from_documents(
            chunks,
            embed_model=self.embeddings
        )
        
        # 查询
        query_engine = index.as_query_engine()
        
        # 使用自定义提示词
        formatted_prompt = self.exercise_prompt.format(
            grade=grade,
            context=textbook_content[:2000],  # 限制上下文长度
            num_questions=num_questions,
            question_type=question_type,
            difficulty=difficulty
        )
        
        response = query_engine.query(formatted_prompt)
        return self._parse_exercise_response(str(response))
    
    def _parse_exercise_response(self, response_text):
        """解析生成的练习题"""
        # 这里可以添加复杂的解析逻辑
        # 比如验证题目格式、检查答案合理性等
        import re
        
        exercises = []
        current_exercise = {}
        
        lines = response_text.split('\n')
        for line in lines:
            line = line.strip()
            
            if line.startswith('题目'):
                if current_exercise:
                    exercises.append(current_exercise)
                current_exercise = {'question': line}
            elif line.startswith(('A.', 'B.', 'C.', 'D.')):
                if 'options' not in current_exercise:
                    current_exercise['options'] = []
                current_exercise['options'].append(line)
            elif line.startswith('答案:'):
                current_exercise['answer'] = line.replace('答案:', '').strip()
            elif line.startswith('解析:'):
                current_exercise['explanation'] = line.replace('解析:', '').strip()
        
        if current_exercise:
            exercises.append(current_exercise)
        
        return {
            "exercises": exercises,
            "count": len(exercises),
            "source": "AI生成",
            "generation_time": time.time()
        }

6. 决策框架:如何为你的项目选择

经过多个项目的实践,我总结了一个简单的决策框架,帮助你在LangChain和LlamaIndex之间做出选择:

6.1 关键决策因素

1. 项目时间线

  • 紧急(<1周):优先选择LlamaIndex
  • 中等(1-4周):根据复杂度决定
  • 宽松(>1个月):可以考虑LangChain

2. 团队技能水平

  • 初级团队:LlamaIndex(学习成本低)
  • 中级团队:根据项目需求选择
  • 专家团队:LangChain(发挥空间大)

3. 定制化需求

  • 标准化需求:LlamaIndex
  • 中等定制:混合方案
  • 深度定制:LangChain

4. 长期维护考虑

  • 短期项目:LlamaIndex
  • 长期产品:考虑可维护性,LangChain可能更合适

6.2 决策流程图

graph TD
    A[开始选择] --> B{项目紧急程度?}
    B -->|非常紧急| C[选择 LlamaIndex]
    B -->|有时间学习| D{团队RAG经验?}
    
    D -->|新手| E[选择 LlamaIndex]
    D -->|有经验| F{需要深度定制?}
    
    F -->|是| G[选择 LangChain]
    F -->|否| H{文档复杂度?}
    
    H -->|简单| I[选择 LlamaIndex]
    H -->|复杂| J{需要特殊处理?}
    
    J -->|是| G
    J -->|否| K[选择 LlamaIndex]
    
    C --> L[完成选择]
    E --> L
    G --> L
    I --> L
    K --> L

6.3 实际建议表格

根据你的具体情况,参考这个表格做决定:

你的情况推荐选择理由注意事项
个人项目/学习LlamaIndex快速看到成果,建立信心先掌握基础,再深入原理
创业公司MVPLlamaIndex快速验证市场,最小可行产品后期可能需要重构
企业PoC验证LlamaIndex快速展示价值,争取预算明确标注是原型系统
已有成熟系统集成LangChain需要深度定制和集成预留足够的学习时间
复杂业务逻辑LangChain需要特殊处理流程设计好架构,避免过度复杂
团队有AI专家LangChain能发挥框架的全部能力建立代码规范,避免混乱
教育/培训项目混合方案平衡速度和质量明确哪些用LlamaIndex,哪些用LangChain

6.4 迁移策略

如果你开始选择了LlamaIndex,但后来发现需要LangChain的灵活性,这里有一个平滑迁移的策略:

第一阶段:并行运行

class MigrationSystem:
    def __init__(self):
        # 保持LlamaIndex系统运行
        self.llamaindex_system = LlamaIndexRAGSystem()
        
        # 逐步迁移到LangChain
        self.langchain_system = None
        self._init_langchain_gradually()
    
    def _init_langchain_gradually(self):
        """逐步迁移组件"""
        # 1. 先迁移数据加载
        self._migrate_data_loading()
        
        # 2. 再迁移检索逻辑
        self._migrate_retrieval()
        
        # 3. 最后迁移生成逻辑
        self._migrate_generation()
    
    def query(self, question):
        """双系统查询,对比结果"""
        # LlamaIndex结果
        li_result = self.llamaindex_system.query(question)
        
        # LangChain结果(如果已迁移)
        lc_result = None
        if self.langchain_system:
            lc_result = self.langchain_system.query(question)
        
        # 对比和评估
        comparison = self._compare_results(li_result, lc_result)
        
        # 根据配置返回其中一个
        if self.use_langchain and lc_result:
            return lc_result
        else:
            return li_result

第二阶段:功能对比 在迁移过程中,记录两个系统的差异:

功能点LlamaIndex实现LangChain实现差异分析
文档加载自动识别格式需要显式指定LangChain更灵活
文本分割默认策略可完全自定义LangChain更精细
检索策略向量相似度支持混合检索LangChain更强大
响应生成内置模板完全可定制LangChain更可控

第三阶段:完全切换 当LangChain系统稳定且效果更好时,逐步切换流量,最终下线LlamaIndex系统。

7. 进阶技巧与最佳实践

无论选择哪个框架,这些实践经验都能帮助你构建更好的RAG系统:

7.1 通义千问API的优化使用

1. 合理设置参数

# 温度参数对结果影响很大
temperature_settings = {
    "创意写作": 0.8,      # 需要多样性
    "技术文档": 0.3,      # 需要准确性
    "客服问答": 0.5,      # 平衡准确性和友好性
    "代码生成": 0.2       # 需要确定性
}

# 最大token数
max_tokens_settings = {
    "摘要生成": 500,
    "详细回答": 2000,
    "代码生成": 4000,
    "长文档分析": 8000
}

2. 错误处理和重试

import time
from tenacity import retry, stop_after_attempt, wait_exponential

class RobustQianwenClient:
    def __init__(self, api_key, max_retries=3):
        self.api_key = api_key
        self.max_retries = max_retries
    
    @retry(
        stop=stop_after_attempt(3),
        wait=wait_exponential(multiplier=1, min=4, max=10)
    )
    def query_with_retry(self, prompt, **kwargs):
        """带重试的查询"""
        try:
            response = self._call_api(prompt, **kwargs)
            return response
        except Exception as e:
            if "rate limit" in str(e).lower():
                print("触发频率限制,等待后重试...")
                time.sleep(5)
                raise
            elif "timeout" in str(e).lower():
                print("请求超时,重试...")
                raise
            else:
                # 其他错误直接抛出
                raise
    
    def _call_api(self, prompt, **kwargs):
        """实际API调用"""
        # 这里实现具体的API调用逻辑
        # 包括token计数、响应解析等
        pass

7.2 向量检索的优化

1. 分块策略优化

def optimize_chunking(document_text, doc_type):
    """根据文档类型优化分块策略"""
    
    chunk_strategies = {
        "technical_doc": {
            "chunk_size": 800,
            "chunk_overlap": 100,
            "separators": ["\n## ", "\n### ", "\n\n", "\n", "。", ";", ","]
        },
        "legal_doc": {
            "chunk_size": 600,
            "chunk_overlap": 80,
            "separators": ["\n第", "\n(", "\n\n", "。", ";"]
        },
        "conversation": {
            "chunk_size": 400,
            "chunk_overlap": 50,
            "separators": ["\n\n", "\n", "。", "?", "!"]
        },
        "code": {
            "chunk_size": 1000,
            "chunk_overlap": 150,
            "separators": ["\n\n", "\nclass ", "\ndef ", "\n# ", "\n"]
        }
    }
    
    strategy = chunk_strategies.get(doc_type, chunk_strategies["technical_doc"])
    return strategy

2. 混合检索提升效果

class HybridRetrievalSystem:
    def __init__(self, vector_store, bm25_retriever):
        self.vector_store = vector_store
        self.bm25_retriever = bm25_retriever
    
    def hybrid_search(self, query, top_k=5, alpha=0.7):
        """
        混合检索:向量搜索 + BM25
        
        alpha: 向量搜索权重 (1-alpha): BM25权重
        """
        # 向量搜索
        vector_results = self.vector_store.similarity_search_with_score(
            query, k=top_k*2
        )
        
        # BM25搜索
        bm25_results = self.bm25_retriever.get_relevant_documents(query)
        
        # 结果融合
        fused_results = self._fuse_results(
            vector_results, bm25_results, alpha
        )
        
        return fused_results[:top_k]
    
    def _fuse_results(self, vector_results, bm25_results, alpha):
        """结果融合算法"""
        # 归一化分数
        vector_scores = self._normalize_scores(
            [score for _, score in vector_results]
        )
        bm25_scores = self._normalize_scores(
            [doc.metadata.get('score', 0) for doc in bm25_results]
        )
        
        # 融合分数
        fused_docs = []
        seen_ids = set()
        
        # 处理向量结果
        for (doc, score), norm_score in zip(vector_results, vector_scores):
            doc_id = hash(doc.page_content)
            if doc_id not in seen_ids:
                fused_score = alpha * norm_score
                fused_docs.append((doc, fused_score))
                seen_ids.add(doc_id)
        
        # 处理BM25结果
        for doc, norm_score in zip(bm25_results, bm25_scores):
            doc_id = hash(doc.page_content)
            if doc_id in seen_ids:
                # 更新已有文档的分数
                for i, (existing_doc, existing_score) in enumerate(fused_docs):
                    if hash(existing_doc.page_content) == doc_id:
                        fused_docs[i] = (existing_doc, 
                                        existing_score + (1-alpha) * norm_score)
                        break
            else:
                fused_score = (1-alpha) * norm_score
                fused_docs.append((doc, fused_score))
                seen_ids.add(doc_id)
        
        # 按分数排序
        fused_docs.sort(key=lambda x: x[1], reverse=True)
        return fused_docs

7.3 生产环境部署建议

1. 监控和日志

import logging
from datetime import datetime
import json

class RAGMonitor:
    def __init__(self, system_name):
        self.system_name = system_name
        self.logger = self._setup_logger()
        self.metrics = {
            "query_count": 0,
            "avg_response_time": 0,
            "error_count": 0,
            "cache_hit_rate": 0
        }
    
    def _setup_logger(self):
        """设置结构化日志"""
        logger = logging.getLogger(f"rag_{self.system_name}")
        logger.setLevel(logging.INFO)
        
        # 文件处理器
        file_handler = logging.FileHandler(f"rag_{self.system_name}.log")
        file_handler.setLevel(logging.INFO)
        
        # 控制台处理器
        console_handler = logging.StreamHandler()
        console_handler.setLevel(logging.WARNING)
        
        # 结构化格式
        formatter = logging.Formatter(
            '{"time": "%(asctime)s", "level": "%(levelname)s", '
            '"system": "%(name)s", "message": "%(message)s"}'
        )
        file_handler.setFormatter(formatter)
        console_handler.setFormatter(formatter)
        
        logger.addHandler(file_handler)
        logger.addHandler(console_handler)
        
        return logger
    
    def log_query(self, question, response_time, success=True, 
                  cache_hit=False, error_msg=None):
        """记录查询日志"""
        self.metrics["query_count"] += 1
        
        # 更新平均响应时间
        total_time = self.metrics["avg_response_time"] * (self.metrics["query_count"] - 1)
        self.metrics["avg_response_time"] = (total_time + response_time) / self.metrics["query_count"]
        
        # 更新缓存命中率
        if cache_hit:
            cache_hits = self.metrics["cache_hit_rate"] * (self.metrics["query_count"] - 1)
            self.metrics["cache_hit_rate"] = (cache_hits + 1) / self.metrics["query_count"]
        
        # 记录错误
        if not success:
            self.metrics["error_count"] += 1
        
        # 结构化日志
        log_entry = {
            "timestamp": datetime.now().isoformat(),
            "question": question[:100],  # 只记录前100字符
            "response_time": response_time,
            "success": success,
            "cache_hit": cache_hit,
            "error": error_msg,
            "metrics": self.metrics.copy()
        }
        
        if success:
            self.logger.info(json.dumps(log_entry, ensure_ascii=False))
        else:
            self.logger.error(json.dumps(log_entry, ensure_ascii=False))
    
    def get_metrics(self):
        """获取当前指标"""
        return self.metrics.copy()

2. 缓存策略

import hashlib
import pickle
from functools import lru_cache
from datetime import datetime, timedelta

class QueryCache:
    def __init__(self, max_size=1000, ttl_hours=24):
        self.max_size = max_size
        self.ttl = timedelta(hours=ttl_hours)
        self.cache = {}
        self.access_times = {}
    
    def _generate_key(self, question, context_hash=None):
        """生成缓存键"""
        content = question
        if context_hash:
            content += context_hash
        
        return hashlib.md5(content.encode()).hexdigest()
    
    def get(self, question, context_hash=None):
        """获取缓存结果"""
        key = self._generate_key(question, context_hash)
        
        if key in self.cache:
            entry = self.cache[key]
            
            # 检查是否过期
            if datetime.now() - entry['timestamp'] < self.ttl:
                self.access_times[key] = datetime.now()
                return entry['result']
            else:
                # 过期删除
                del self.cache[key]
                del self.access_times[key]
        
        return None
    
    def set(self, question, result, context_hash=None):
        """设置缓存"""
        key = self._generate_key(question, context_hash)
        
        # 如果缓存已满,删除最久未使用的
        if len(self.cache) >= self.max_size:
            oldest_key = min(self.access_times, key=self.access_times.get)
            del self.cache[oldest_key]
            del self.access_times[oldest_key]
        
        # 存储新结果
        self.cache[key] = {
            'result': result,
            'timestamp': datetime.now(),
            'question': question[:50]  # 存储部分问题用于调试
        }
        self.access_times[key] = datetime.now()
    
    @lru_cache(maxsize=100)
    def cached_query(self, query_func, question, context_hash=None):
        """带缓存的查询装饰器"""
        cached = self.get(question, context_hash)
        if cached is not None:
            return cached
        
        result = query_func(question)
        self.set(question, result, context_hash)
        return result

7.4 成本优化策略

1. Token使用优化

class TokenOptimizer:
    def __init__(self, llm_client):
        self.llm_client = llm_client
        self.token_stats = {
            "total_input_tokens": 0,
            "total_output_tokens": 0,
            "total_cost": 0.0
        }
    
    def optimize_prompt(self, prompt, max_tokens=4000):
        """优化提示词,减少token使用"""
        # 1. 移除多余空格和空行
        prompt = ' '.join(prompt.split())
        
        # 2. 缩写常见短语
        abbreviations = {
            "例如": "e.g.",
            "也就是说": "i.e.",
            "请注意": "Note:",
            "非常重要": "Important:",
            "首先": "1.",
            "其次": "2.",
            "最后": "3."
        }
        
        for full, abbr in abbreviations.items():
            prompt = prompt.replace(full, abbr)
        
        # 3. 如果还是太长,智能截断
        if self.estimate_tokens(prompt) > max_tokens:
            prompt = self._smart_truncate(prompt, max_tokens)
        
        return prompt
    
    def estimate_tokens(self, text):
        """估算token数量(简单版本)"""
        # 中文大致按字符数估算,英文按单词数
        chinese_chars = sum(1 for c in text if '\u4e00' <= c <= '\u9fff')
        english_words = len(text.split()) - chinese_chars / 2  # 粗略估算
        
        # 通义千问的token估算(近似)
        return int(chinese_chars * 1.3 + english_words * 1.5)
    
    def _smart_truncate(self, text, max_tokens):
        """智能截断,保持语义完整"""
        sentences = text.split('。')
        truncated = []
        current_tokens = 0
        
        for sentence in sentences:
            sentence_tokens = self.estimate_tokens(sentence + '。')
            if current_tokens + sentence_tokens > max_tokens:
                break
            truncated.append(sentence)
            current_tokens += sentence_tokens
        
        return '。'.join(truncated) + '。'
    
    def track_usage(self, input_tokens, output_tokens):
        """跟踪token使用和成本"""
        self.token_stats["total_input_tokens"] += input_tokens
        self.token_stats["total_output_tokens"] += output_tokens
        
        # 通义千问定价(示例,实际以官方为准)
        input_cost = input_tokens * 0.000002  # 0.002元/千token
        output_cost = output_tokens * 0.000008  # 0.008元/千token
        
        self.token_stats["total_cost"] += input_cost + output_cost
        
        # 预警机制
        if self.token_stats["total_cost"] > 100:  # 超过100元
            print(f"⚠️ 警告:本月API成本已超过100元")
        
        return self.token_stats

2. 批量处理优化

class BatchProcessor:
    def __init__(self, rag_system, batch_size=10):
        self.rag_system = rag_system
        self.batch_size = batch_size
    
    def process_batch(self, questions):
        """批量处理问题,减少API调用"""
        results = []
        
        # 按相关性分组
        grouped_questions = self._group_similar_questions(questions)
        
        for group in grouped_questions:
            if len(group) == 1:
                # 单个问题直接处理
                result = self.rag_system.query(group[0])
                results.append(result)
            else:
                # 批量处理相似问题
                batch_results = self._process_similar_batch(group)
                results.extend(batch_results)
        
        return results
    
    def _group_similar_questions(self, questions):
        """将相似问题分组"""
        # 使用简单的文本相似度分组
        # 实际可以使用嵌入向量聚类
        groups = []
        used_indices = set()
        
        for i, q1 in enumerate(questions):
            if i in used_indices:
                continue
            
            group = [q1]
            used_indices.add(i)
            
            for j, q2 in enumerate(questions[i+1:], i+1):
                if j in used_indices:
                    continue
                
                # 简单相似度计算(实际应用可以用更复杂的方法)
                similarity = self._text_similarity(q1, q2)
                if similarity > 0.7:  # 相似度阈值
                    group.append(q2)
                    used_indices.add(j)
            
            groups.append(group)
        
        return groups
    
    def _text_similarity(self, text1, text2):
        """简单的文本相似度计算"""
        # 实际应用中应该使用嵌入向量
        words1 = set(text1.split())
        words2 = set(text2.split())
        
        if not words1 or not words2:
            return 0.0
        
        intersection = len(words1.intersection(words2))
        union = len(words1.union(words2))
        
        return intersection / union if union > 0 else 0.0
    
    def _process_similar_batch(self, similar_questions):
        """处理相似问题批次"""
        # 提取共同上下文
        common_context = self._extract_common_context(similar_questions)
        
        # 批量查询
        batch_results = []
        for question in similar_questions:
            # 在共同上下文基础上查询
            enhanced_question = f"上下文:{common_context}\n\n问题:{question}"
            result = self.rag_system.query(enhanced_question)
            batch_results.append(result)
        
        return batch_results
    
    def _extract_common_context(self, questions):
        """从相似问题中提取共同上下文"""
        # 简单实现:取所有问题的共同关键词
        # 实际可以使用更复杂的NLP方法
        all_words = []
        for q in questions:
            all_words.extend(q.split())
        
        from collections import Counter
        word_counts = Counter(all_words)
        
        # 取出现频率最高的几个词
        common_words = [word for word, count in word_counts.most_common(5)]
        
        return " ".join(common_words)

经过多个项目的实践,我发现没有绝对的“最佳选择”,只有“最适合的选择”。LangChain和LlamaIndex各有优劣,关键是要清楚你的项目需求、团队能力和时间约束。对于大多数中小型项目,我建议从LlamaIndex开始,快速验证想法,等业务需求明确后再考虑是否需要迁移到LangChain。对于大型企业级项目,如果团队技术能力强且需要深度定制,LangChain是更稳妥的选择。

无论选择哪个框架,通义千问API都提供了一个稳定、经济的大模型后端。结合合理的架构设计和优化策略,你完全可以构建出既高效又可靠的RAG系统。最重要的是开始行动——选择一个框架,动手实践,然后在实践中不断调整和优化。

更多推荐