15天学会AI应用开发(二十)使用LangChain实现RAG检索功能
15天å¦ä¼AIåºç¨å¼åï¼äºåï¼ä½¿ç¨LangChainå®ç°RAGæ£ç´¢åè½
aqi00 2026-08-09 0 é 读5åéâä¹åçæç« ã15天å¦ä¼AIåºç¨å¼åï¼åä¸ï¼ä¸ä¸æä¸RAGçé¶æ®µæ§æ»ç»ãæ¦è¿°äºæ£ç´¢RAGç¥è¯åºçä¸»è¦æ¥éª¤ï¼ç°å¨éç¨LangChainå°å ¶æ¹é ä¸ºæ´æºè½çæ¬å°RAGç³»ç»ã
æ¥ä¸æ¥åºäºLangChainæ¡æ¶ä¸Chromaåéæ°æ®åºï¼ç»åç¦»çº¿çææ¬åµå ¥æ¨¡åå大è¯è¨æ¨¡åï¼ä»é¶å¼å§æå»ºæ¬å°çRAGæ£ç´¢ç³»ç»ã
ä¸ãä¸è½½RAGéè¦ç离线大模å
é¦å ä¸è½½ç¦»çº¿ç大è¯è¨æ¨¡åï¼æ¯å¦å¨å½ä»¤è¡çªå£æ§è¡ä¸é¢å½ä»¤ï¼å³å¯ä¸è½½å½äº§å¤§æ¨¡åqwen2:1.5bï¼
ollama pull qwen2:1.5b
æ¥çä¸è½½æå»ºåéæ°æ®åºéè¦çææ¬åµå ¥æ¨¡åï¼æ¯å¦å¨å½ä»¤è¡çªå£æ§è¡ä¸é¢å½ä»¤ï¼å³å¯ä¸è½½å½äº§åµå ¥å¤§æ¨¡åbge-m3ï¼
ollama pull bge-m3
çå¾ ä¸¤ä¸ªå¤§æ¨¡åä¸è½½å®æ¯ï¼å¨å½ä»¤è¡çªå£æ§è¡ä¸é¢å½ä»¤ï¼å³å¯å¯å¨Ollamaå¹¶å 载离线大模åï¼
ollama serve
äºãå®è£ RAGéè¦çLangChainå
使ç¨LangChainå®ç°RAGæ£ç´¢åè½ç¨å°äºå¤ä¸ªLangChainå ï¼ä¸é¢åå«è¯´æéè¦å®è£ çLangChainå åå ¶ç¨éã
é¦å å¨å½ä»¤è¡çªå£æ§è¡ä¸é¢å½ä»¤ï¼å³å¯å®è£ LangChainåå ¶ç¨å°çOllamaæä»¶ï¼
pip install langchain langchain-core langchain-ollama
å ¶æ¬¡æ§è¡ä¸é¢å½ä»¤å®è£ Splittersæä»¶ï¼ç¨äºå¯¹ææ¡£å 容ååææ¬åï¼
pip install langchain-text-splitters
忬¡æ§è¡ä¸é¢å½ä»¤å®è£ Chromaæä»¶ï¼ç¨äºè®©LangChainæä½åéæ°æ®åºChromaï¼
pip install langchain-chroma chromadb
è¿è¦æ§è¡ä¸é¢å½ä»¤å®è£ LangChainç社åºå ï¼ç¨äºå¼å ¥PyPDFLoader以便å è½½PDFææ¡£ï¼
pip install langchain-community
ä¸ãç¼åRAGçæ£ç´¢å®ç°ä»£ç
åºäºLangChainä¸Chromaå®ç°RAGåè½çæ¶åï¼ä¸»è¦ç»è¿å个æ¥éª¤ï¼è§£æPDFææ¡£ãå建RAGæ£ç´¢å¨ãæå»ºæç¤ºè¯æ¨¡æ¿ãå¯å¨RAG龿§è¡é®çæä½ï¼ä¾æ¬¡è¯´æå¦ä¸ï¼
1ãè§£æPDFææ¡£
é¦å
åå¤å¥½å¾
è§£æçPDFææ¡£æ¯å¦test.pdfæ¾å¨pythonå·¥ç¨ç®å½ä¸ï¼æ¥ç使ç¨PyPDFLoaderå è½½PDFææ¡£ï¼åå¼å
¥RecursiveCharacterTextSplitterååææ¬åã
对åºçPython代ç åå
¶å¯¼å
è¯å¥å¦ä¸ï¼
# PDFææ¡£å è½½
from langchain_community.document_loaders import PyPDFLoader
# ææ¬åå²å·¥å
·
from langchain_text_splitters import RecursiveCharacterTextSplitter
# å è½½PDFææ¡£
loader = PyPDFLoader("./test.pdf")
docs = loader.load()
# ååææ¬å
text_splitter = RecursiveCharacterTextSplitter(
  chunk_size=500,
  chunk_overlap=80,
  separators=["\n\n", "\n", "ã", "ï¼", " "]
)
split_docs = text_splitter.split_documents(docs)
2ãå建RAGæ£ç´¢å¨
é¦å
使ç¨OllamaEmbeddingså è½½ç¦»çº¿çææ¬åµå
¥æ¨¡åbge-m3ï¼æ¥çå¼å
¥Chromaä»ææ¡£å
容æå»ºåéæ°æ®åºï¼æ³¨ææ°æ®åºæä»¶ä¿åå¨persist_directoryåæ°æå®çchroma_dbç®å½ï¼ç¶åè°ç¨æ°æ®åºå®ä¾ças_retrieveræ¹æ³å建RAGæ£ç´¢å¨ï¼è¯¥æ¹æ³çsearch_kwargsç»æå¯æå®æå¤å¼ç¨å ä¸ªææ¡£åã
对åºçPython代ç åå
¶å¯¼å
è¯å¥å¦ä¸ï¼
# ææ¬åµå
¥æ¨¡å
from langchain_ollama import OllamaEmbeddings
# åéæ°æ®åº
from langchain_chroma import Chroma
# å¼ç¨ç¦»çº¿çææ¬åµå
¥æ¨¡åbge-m3
embedding = OllamaEmbeddings( model="bge-m3" )
# ä»ææ¡£å
容æå»ºåéæ°æ®åºï¼æ°æ®åºæä»¶ä¿åå¨chroma_dbç®å½
vectordb = Chroma.from_documents(
  documents=split_docs,
  embedding=embedding,
  persist_directory="./chroma_db"
)
# å建RAGæ£ç´¢å¨ï¼æå¤å¼ç¨ä¸ä¸ªææ¡£å
retriever = vectordb.as_retriever( search_kwargs={ "k": 3 } )
3ãæå»ºæç¤ºè¯æ¨¡æ¿
é¦å
ç¼åå符串形å¼çæç¤ºè¯å
容ï¼è¯´æ¸
æ¥è¦è®©AIåä»ä¹äºãè¿åä»ä¹æ ·çåçï¼å
³é®è¦è¯´æä¸ä¸ææ¥èªcontextåæ®µï¼ä¸ç¨æ·é®é¢æ¥èªquestionåæ®µãç¶åè°ç¨PromptTemplateçfrom_templateæ¹æ³ä»æç¤ºè¯æåæå»ºæç¤ºè¯æ¨¡æ¿ã
对åºçPython代ç åå
¶å¯¼å
è¯å¥å¦ä¸ï¼
from langchain_core.prompts import PromptTemplate
# èªå®ä¹æç¤ºè¯ãå¾éè¦ï¼å¿
é¡»æç¤ºcontextåquestionï¼è¿æ ·AIæä¼ææåç
prompt_template = """
ä½ æ¯ä¸ä¸ææ¡£é®ç婿ï¼åªè½æ ¹æ®æä¾çä¸ä¸æåçé®é¢ï¼ç¦æ¢ç¼é ä¿¡æ¯ã
ä¸ä¸æï¼
{context}
ç¨æ·é®é¢ï¼
{question}
请æä¾å
å«ä¸»è¯ãè°è¯ã宾è¯å¨å
çåç¡®ãç®æ´çä¸å¥è¯åçï¼50å以å
ï¼ã
"""
# ä»æç¤ºè¯æåæå»ºæç¤ºè¯æ¨¡æ¿
prompt = PromptTemplate.from_template(prompt_template)
4ãå¯å¨RAG龿§è¡é®çæä½
é¦å
使ç¨ChatOllamaå 载离线ç大è¯è¨æ¨¡åqwen2:1.5bï¼æ¥çå°è£
RAGæ§è¡é¾ï¼æ³¨æè¾å
¥ç¬¬ä¸æ¥æç¤ºè¯è¦æ±çcontextåquestionä¸¤ä¸ªåæ°ï¼å
¶ä¸contextåæ®µä¾æ¬¡è¾å
¥ç¬¬äºæ¥çRAGæ£ç´¢å¨åæ¼æ¥ææ¡£éè¦çformat_docs彿°ãç¶åè°ç¨RAGæ§è¡é¾çinvokeæ¹æ³ï¼æ ¹æ®è¾å
¥çé®é¢è·åRAGæ£ç´¢è¿åççæ¡ã
对åºçPython代ç åå
¶å¯¼å
è¯å¥å¦ä¸ï¼
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
from langchain_core.output_parsers import StrOutputParser
from langchain_ollama import ChatOllama
# å·¥å
·å½æ°ï¼æ¼æ¥æ£ç´¢å°ç夿®µææ¡£
def format_docs(docs):
  return "\n\n".join(doc.page_content for doc in docs)
# å 载离线ç大è¯è¨æ¨¡åqwen2:1.5b
llm = ChatOllama(model="qwen2:1.5b")  # æ¹æä½ æ¬å°ç模å
# æå»ºRAGæ§è¡é¾ï¼è¾å
¥contextåquestionä¸¤ä¸ªåæ°
rag_chain = (
  {
    "context": retriever | RunnableLambda(format_docs),
    "question": RunnablePassthrough()
  }
  | prompt | llm | StrOutputParser()
)
# 令RAG龿§è¡é®çæä½
query = "å
Œ
±æ¶çæ¯ä»ä¹ï¼"
answer = rag_chain.invoke(query)
print("=== AIåç ===\n", answer)
è¿è¡ä¸é¢æ´ååçPython代ç ï¼æ ¹æ®æç¤ºè¾å ¥é®é¢å 容âå ¬å ±æ¶çæ¯ä»ä¹ï¼âï¼è¾åºæ¥å¿ç»æå¦ä¸ï¼
=== ä½ çé®é¢ ===
 å
Œ
±æ¶çæ¯ä»ä¹ï¼
=== AIåç ===
 å
Œ
±æ¶çæ¯æå©ç¨ä½å®
å°åºä¸ä¸»å
±æé¨å以å¤çå
±ç¨è®¾æ½è®¾å¤å¼å±ç»è¥æ´»å¨è产ççæ¶çã
ç±æ¥å¿ä¿¡æ¯åç°ï¼AIåºç¨è½å¤è¿åPDFææ¡£ä¸çæ£ç¡®çæ¡ãå¯è§å¼å ¥Chromaåéæ°æ®åºä¹åï¼LangChainæåå®ç°äºæ¥èªPDFææ¡£çRAGç¥è¯æ£ç´¢åè½ã
 æ¬ç³»åçAIåºç¨å¼åæç« ç®å½ä¸ºã15天å¦ä¼AIåºç¨å¼åå ¨ç®å½ï¼é¶åºç¡å°ç½ï¼é¶Tokenæ¶èï¼ããâ
Aitishiku.com