【面试题】LangChain与LlamaIndex核心概念详解
1. 什么是LangChain?它的核心设计理念是什么?
LangChain是一个用于构建大语言模型应用的框架,它提供了模块化组件和工具链,帮助开发者将LLM与外部数据和系统连接起来。其核心理念是组件化和链式编排——通过标准化接口将各种功能模块(模型、工具、数据源)连接成可复用的处理流程。
LangChain的设计遵循几个关键原则:
- 组合性:所有组件实现统一接口,可以通过管道操作符(
|
)轻松组合 - 数据感知:内置多种数据连接器,使LLM能够访问外部知识源
- 代理能力:支持让LLM自主选择工具和决策的智能代理模式
- 生产就绪:提供调试、监控、部署等生产环境所需功能
2. LangChain中的6个核心概念
Models (模型)
- LLMs:基础文本生成模型(如GPT-3)
- ChatModels:对话优化模型(如ChatGPT)
- Embeddings:文本向量化模型
所有模型都实现Runnable
接口,支持统一调用方式。
Prompts (提示)
- 模板化:支持变量插值的提示模板
- 少量示例:支持在提示中包含示例样本
- 动态构建:可根据对话历史动态构建提示
Indexes (索引)
- 文档加载:从各种来源加载文档
- 文本分割:将长文档分割为适当片段
- 向量存储:将文本转换为向量并存储
- 检索器:从索引中检索相关文档
Memory (记忆)
- 对话历史:维护多轮对话状态
- 多种形式:支持短期记忆、摘要记忆、实体记忆等
- 可定制:可根据需要定制记忆存储策略
Chains (链)
- 顺序链:线性执行多个步骤
- 转换链:处理数据格式转换
- 路由链:根据条件选择不同分支
- LCEL:使用表达式语言声明式构建链
Agents (代理)
- 工具使用:让LLM自主选择和使用工具
- 推理循环:基于ReAct框架进行推理和行动
- 多种类型:支持ReAct、Plan-and-execute等多种代理类型
3. 什么是LCEL?它有什么优势?请写一个简单的例子
LCEL(LangChain Expression Language)是LangChain的声明式API,用于构建和组合链。它的核心优势包括:
主要优势:
- 统一接口:所有组件实现
Runnable
接口 - 自动并行:自动并行处理多个输入
- 流式支持:原生支持流式输出
- 错误处理:内置重试和回退机制
- 易于调试:提供详细的执行追踪
简单示例:
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser# 创建翻译链
translation_chain = (ChatPromptTemplate.from_template("将以下文本翻译成{language}: {text}")| ChatOpenAI(model="gpt-3.5-turbo")| StrOutputParser()
)# 使用链
result = translation_chain.invoke({"text": "Hello, world!","language": "中文"
})
print(result) # 输出: "你好,世界!"
高级示例(带错误处理):
from langchain_core.runnables import RunnableBranch# 创建带分支的链
branch_chain = RunnableBranch((lambda x: "你好" in x["text"], translation_chain),translation_chain
)# 只会调用翻译链一次
result = branch_chain.invoke({"text": "Hello, world!","language": "中文"
})
4. Agent的执行流程(使用ReAct框架举例)
ReAct框架让Agent通过"思考-行动-观察"循环解决问题:
执行流程:
- 初始化:接收用户查询,初始化提示模板
- 思考:LLM分析当前情况,决定下一步行动
- 行动:选择合适工具并生成调用参数
- 观察:执行工具并获取结果
- 循环:重复2-4步直到解决问题
- 终止:输出最终答案
具体示例:
用户: "旧金山昨天的最高温度是多少华氏度?"Agent思考: "我需要获取旧金山昨天的天气数据,应该使用天气API工具"
Agent行动: 调用WeatherAPI("San Francisco", "yesterday")
观察: "高温: 65°F, 低温: 52°F"Agent思考: "我已获得天气数据,可以直接回答用户问题"
Agent输出: "旧金山昨天的最高温度是65°F"
关键特点:
- 每一步的思考和行动都记录在对话历史中
- 支持工具调用失败时的重试机制
- 可设置最大迭代次数防止无限循环
5. 什么是RAG?如何在LangChain中实现一个基本的RAG流程?
RAG(Retrieval-Augmented Generation)通过检索外部知识增强生成能力,减少模型幻觉。
LangChain实现方案:
from langchain_community.document_loaders import WebBaseLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_chroma import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough# 1. 加载和预处理文档
loader = WebBaseLoader("https://example.com")
docs = loader.load()# 2. 分割文档
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200
)
splits = text_splitter.split_documents(docs)# 3. 创建向量存储
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(splits, embeddings)
retriever = vectorstore.as_retriever()# 4. 创建RAG链
template = """使用以下上下文回答问题。如果不知道答案,请如实告知。
上下文: {context}
问题: {question}
答案:"""
prompt = ChatPromptTemplate.from_template(template)llm = ChatOpenAI(model="gpt-3.5-turbo")rag_chain = ({"context": retriever, "question": RunnablePassthrough()}| prompt| llm| StrOutputParser()
)# 5. 使用RAG链
result = rag_chain.invoke("这篇文章的主要内容是什么?")
优化技巧:
- 使用更好的嵌入模型提高检索质量
- 调整chunk_size和chunk_overlap参数
- 添加重排序步骤提高相关性
- 使用多查询生成增强检索
6. LlamaIndex的核心价值与LangChain的主要区别
LlamaIndex核心价值:
专注于数据连接层,提供高效的文档索引和检索能力,特别擅长:
- 复杂文档解析(PDF、Word等)
- 高级索引结构(向量、树形、关键词等)
- 智能查询优化和重写
- 开箱即用的RAG解决方案
与LangChain的主要区别:
方面 | LlamaIndex | LangChain |
---|---|---|
定位 | 数据连接和检索专家 | 通用应用编排框架 |
核心功能 | 文档索引和查询 | 链式工作流和代理 |
抽象层级 | 更高层,更专注 | 更底层,更灵活 |
使用难度 | 学习曲线较平缓 | 学习曲线较陡峭 |
典型用例 | 文档问答、知识检索 | 复杂代理、多步工作流 |
如何选择:
- 需要快速构建文档问答系统 → LlamaIndex
- 需要复杂逻辑和工具使用 → LangChain
- 大型项目 → 结合使用(LlamaIndex处理数据,LangChain处理逻辑)
7. LlamaIndex中的核心组件
Nodes (节点)
文档的基本单元,包含文本内容和元数据:
from llama_index.core.schema import TextNodenode = TextNode(text="文档内容",metadata={"source": "document.pdf", "page": 1}
)
Indexes (索引)
多种索引类型适应不同场景:
- VectorStoreIndex:向量索引,适合语义搜索
- SummaryIndex:摘要索引,适合整体分析
- TreeIndex:树形索引,适合层次结构
- KeywordTableIndex:关键词索引,适合精确匹配
Engines (引擎)
处理查询的核心组件:
- Retrievers:从索引中检索相关节点
- Query Engines:处理完整查询流程
- Response Synthesizers:生成最终答案
使用示例:
from llama_index.core import VectorStoreIndex# 创建索引
index = VectorStoreIndex.from_documents(documents)# 创建查询引擎
query_engine = index.as_query_engine()# 执行查询
response = query_engine.query("你的问题")
8. LlamaIndex的高级查询/检索模式
1. 子问题分解
将复杂问题分解为多个子问题:
from llama_index.core.query_engine import SubQuestionQueryEnginequery_engine = SubQuestionQueryEngine.from_defaults(query_engine_tools=[engine1, engine2]
)
2. 多步查询
支持多轮交互的复杂查询:
from llama_index.core.query_engine import MultiStepQueryEnginequery_engine = MultiStepQueryEngine(query_engine, num_steps=3
)
3. 混合检索
结合多种检索策略:
from llama_index.core import VectorStoreIndex, KeywordTableIndex
from llama_index.core.retrievers import QueryFusionRetrievervector_retriever = VectorStoreIndex.as_retriever()
keyword_retriever = KeywordTableIndex.as_retriever()fusion_retriever = QueryFusionRetriever([vector_retriever, keyword_retriever]
)
9. RAG中文档切分的重要性与策略
为什么文档切分重要:
- 影响检索精度:过大或过小的块都会降低相关性
- 影响生成质量:提供不完整的上下文会导致错误答案
- 影响性能:合理的块大小提高检索效率
常用切分策略:
- 固定大小切分:
from langchain_text_splitters import CharacterTextSplittersplitter = CharacterTextSplitter(chunk_size=1000,chunk_overlap=200
)
- 递归切分(推荐):
from langchain_text_splitters import RecursiveCharacterTextSplittersplitter = RecursiveCharacterTextSplitter(chunk_size=512,chunk_overlap=50
)
- 语义切分:
from langchain_experimental.text_splitter import SemanticChunkersplitter = SemanticChunker(OpenAIEmbeddings())
最佳实践:
- 一般文档:500-1000字符的块大小,10-20%重叠
- 技术文档:较小的块大小(200-500字符)
- 对话记录:按对话轮次切分
- 代码文档:按函数或类切分
10. 评估和提升RAG应用效果
评估指标
- 检索质量:命中率、MRR、NDCG
- 生成质量:事实准确性、相关性、流畅性
- 端到端指标:答案相关性、有用性评分
提升策略
检索阶段优化:
# 1. 优化检索器
retriever = vectorstore.as_retriever(search_type="mmr", # 使用最大边际相关性search_kwargs={"k": 5, "fetch_k": 20}
)# 2. 添加重排序
from langchain.retrievers import ContextualCompressionRetriever
from langchain_cohere import CohereRerankcompressor = CohereRerank()
compression_retriever = ContextualCompressionRetriever(base_compressor=compressor,base_retriever=retriever
)
生成阶段优化:
# 1. 优化提示模板
template = """请基于以下上下文回答问题。如果上下文不包含答案,请说"我不知道"。上下文:{context}问题:{question}请以友好、专业的语气回答,并在必要时引用上下文。"""
端到端优化:
# 添加验证步骤
def validate_answer(response):if "我不知道" in response or "不确定" in response:return "抱歉,我无法从提供的资料中找到答案。"return responsefinal_chain = rag_chain | validate_answer
11. ReAct框架与自定义Tool实现
ReAct框架详解
ReAct通过"思考-行动-观察"循环让LLM解决复杂问题:
循环步骤:
- 思考:分析当前状况,决定下一步
- 行动:选择工具并生成参数
- 观察:执行工具并获取结果
- 重复:直到问题解决或达到最大迭代
优势:
- 减少幻觉:通过工具验证假设
- 提高透明度:显示完整推理过程
- 增强能力:突破LLM的知识限制
自定义Tool实现
安全实现示例:
from langchain.tools import BaseTool
from pydantic import BaseModel, Field
import requestsclass WeatherInput(BaseModel):location: str = Field(description="城市名称")date: str = Field(description="日期,如'今天'、'明天'")class WeatherTool(BaseTool):name = "weather_api"description = "获取指定城市和日期的天气信息"args_schema = WeatherInputdef _run(self, location: str, date: str) -> str:"""调用天气API"""try:# 这里使用模拟API调用# 实际应用中应使用真实的天气APIif date == "今天":return f"{location}今天天气晴朗,25°C"elif date == "明天":return f"{location}明天多云转晴,23°C"else:return "仅支持查询今天或明天的天气"except Exception as e:return f"获取天气信息失败: {str(e)}"async def _arun(self, location: str, date: str) -> str:return self._run(location, date)# 使用工具
tool = WeatherTool()
result = tool.run("北京", "今天")
print(result) # 输出: "北京今天天气晴朗,25°C"
安全最佳实践:
- 输入验证:使用Pydantic模型验证参数
- 错误处理:妥善处理异常,避免暴露内部信息
- 权限控制:限制工具访问权限
- 日志记录:记录所有工具调用情况
在Agent中使用工具:
from langchain.agents import initialize_agent
from langchain.agents import AgentTypetools = [WeatherTool()]
agent = initialize_agent(tools,llm,agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,verbose=True
)result = agent.run("北京今天天气怎么样?")
通过这样的实现,可以创建安全、可靠的自定义工具,大大扩展Agent的能力范围。