1. 为什么要在Windows本地构建RAG智能体?

最近两年,大语言模型(LLM)的发展速度简直让人眼花缭乱。从最初的ChatGPT到现在的Llama 3,模型的性能越来越强,但同时也带来了一个现实问题:这些模型往往需要强大的云端算力支持。对于很多开发者来说,尤其是个人开发者和小团队,云端API的成本和隐私问题成了拦路虎。

这就是为什么我们要在本地部署大模型。想象一下,你有一个私人助理,它不仅能理解你的问题,还能从你指定的文档中查找答案,而且所有数据都在你的电脑上处理,完全不用担心隐私泄露。这就是RAG(检索增强生成)智能体的魅力所在。

我在实际项目中测试过,使用Llama 3-8B模型在消费级显卡(比如RTX 3060 12GB)上运行完全可行。配合LangGraph的工作流编排能力,你可以构建一个能理解上下文、能自我验证答案准确性的智能系统。最棒的是,整个过程不需要连接任何外部服务器,所有数据处理都在本地完成。

2. 环境准备与依赖安装

2.1 硬件与系统要求

在开始之前,我们先看看需要准备什么。我建议至少满足以下配置:

  • 操作系统 :Windows 10/11 64位
  • 内存 :16GB以上(32GB更佳)
  • 显卡 :NVIDIA显卡,至少8GB显存(RTX 3060及以上)
  • 存储空间 :至少20GB可用空间(用于模型和依赖)

如果你没有独立显卡,也可以用纯CPU模式运行,但速度会慢很多。我测试过,在i7-12700K上运行Llama 3-8B,生成速度大约2-3个token/秒,勉强可用但不适合生产环境。

2.2 安装Python和必要工具

首先确保你安装了Python 3.10或更高版本。我强烈建议使用Miniconda创建虚拟环境,避免污染系统Python环境:

conda create -n llama3 python=3.10
conda activate llama3

接下来安装CUDA工具包(如果你有NVIDIA显卡)。到NVIDIA官网下载对应版本的CUDA Toolkit(目前推荐12.1版本),安装时记得勾选"添加到PATH"选项。

2.3 安装核心依赖

现在我们来安装必要的Python包。这里我推荐使用pip的 -q 参数减少输出干扰:

pip install -q torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
pip install -q ollama langchain beautifulsoup4 chromadb gradio unstructured langchain-nomic langchain_community tiktoken langchainhub langgraph gpt4all

这些包包含了我们需要的所有核心功能:

  • torch :PyTorch深度学习框架
  • ollama :Llama模型的本地运行接口
  • langchain langgraph :工作流编排的核心
  • chromadb :向量数据库,用于存储文档嵌入
  • gradio :快速构建演示界面

特别提醒:如果你遇到 unstructured 安装问题,可能需要额外安装系统依赖:

pip install "unstructured[all-docs]"

3. 下载并配置Llama 3模型

3.1 获取模型权重

现在我们来下载Llama 3模型。使用ollama可以非常方便地获取官方优化过的模型:

ollama pull llama3

这个命令会自动下载最新的Llama 3-8B模型(约4.7GB)。如果你想使用更大的70B模型(需要32GB以上显存),可以运行:

ollama pull llama3:70b

下载完成后,可以用以下命令验证:

ollama list

你应该能看到类似输出:

NAME              ID              SIZE      MODIFIED
llama3:latest     365c0bd3c000    4.7GB     2 days ago

3.2 模型量化与性能权衡

原始模型对显存要求很高,所以我们通常使用量化版本来降低资源消耗。量化是一种降低模型精度来减少内存占用的技术。ollama默认使用4-bit量化,这是一个很好的平衡点。

我在RTX 3060 12GB上测试过不同量化级别的表现:

  • 4-bit:流畅运行,显存占用约8GB
  • 8-bit:质量更好,但显存占用约10GB
  • 16-bit:接近原始质量,但需要16GB以上显存

对于大多数应用场景,4-bit量化已经足够好了。如果你有更强的显卡,可以尝试更高精度的版本:

ollama pull llama3:8b-instruct-q8_0

4. LangGraph基础与工作流设计

4.1 什么是LangGraph?

LangGraph是LangChain生态系统中的一个新成员,它专门用于构建复杂的AI工作流。你可以把它想象成一个流程图工具,但专门为AI任务设计。它基于"状态图"的概念,允许你定义不同的节点和边,控制信息如何在系统中流动。

我在实际项目中发现,LangGraph特别适合构建需要多步骤决策的AI系统。比如一个问答系统可能需要:

  1. 理解用户问题
  2. 决定是否需要检索文档
  3. 如果需要,从哪些来源检索
  4. 如何组合检索结果生成最终答案
  5. 如何验证答案的准确性

4.2 构建第一个状态图

让我们从一个简单的例子开始。创建一个Python文件 basic_graph.py

from langgraph.graph import StateGraph, END

# 定义状态结构
class AgentState(TypedDict):
    input: str
    output: str

# 定义节点函数
def node_echo(state: AgentState):
    return {"output": f"你说了: {state['input']}"}

# 创建图
workflow = StateGraph(AgentState)

# 添加节点
workflow.add_node("echo", node_echo)

# 设置入口点
workflow.set_entry_point("echo")

# 设置结束点
workflow.add_edge("echo", END)

# 编译图
app = workflow.compile()

这个简单的图只有一个节点,它接收输入并返回一个带前缀的字符串。你可以这样运行它:

result = app.invoke({"input": "你好,世界!"})
print(result["output"])  # 输出: 你说了: 你好,世界!

4.3 添加条件分支

真正的强大之处在于条件分支。让我们扩展上面的例子,添加一个决策节点:

from langgraph.graph import StateGraph, END
from typing import Literal

class AgentState(TypedDict):
    input: str
    output: str
    decision: Literal["echo", "reverse"]

def node_echo(state: AgentState):
    return {"output": f"你说了: {state['input']}"}

def node_reverse(state: AgentState):
    return {"output": f"反转后: {state['input'][::-1]}"}

def node_decide(state: AgentState):
    if len(state["input"]) > 5:
        return {"decision": "reverse"}
    return {"decision": "echo"}

workflow = StateGraph(AgentState)

workflow.add_node("decide", node_decide)
workflow.add_node("echo", node_echo)
workflow.add_node("reverse", node_reverse)

workflow.set_entry_point("decide")

workflow.add_edge("echo", END)
workflow.add_edge("reverse", END)

workflow.add_conditional_edges(
    "decide",
    lambda state: state["decision"],
    {"echo": "echo", "reverse": "reverse"}
)

app = workflow.compile()

现在这个图会根据输入长度决定是直接回显还是反转字符串。试试看:

print(app.invoke({"input": "hi"})["output"])  # 输出: 你说了: hi
print(app.invoke({"input": "hello world"})["output"])  # 输出: 反转后: dlrow olleh

5. 构建完整的RAG智能体

5.1 文档加载与处理

RAG的核心是从文档中检索相关信息。首先我们需要准备文档并转换为向量存储。创建一个新文件 rag_agent.py

from langchain_community.document_loaders import DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import OllamaEmbeddings

# 加载文档
loader = DirectoryLoader('./docs', glob="**/*.txt")
documents = loader.load()

# 分割文档
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000, 
    chunk_overlap=200
)
splits = text_splitter.split_documents(documents)

# 创建向量存储
vectorstore = Chroma.from_documents(
    documents=splits,
    embedding=OllamaEmbeddings(model="llama3")
)

这段代码会加载 docs 目录下的所有txt文件,将它们分割成小块,然后使用Llama 3的嵌入模型转换为向量存储在Chroma中。

5.2 定义检索节点

现在我们将检索功能集成到LangGraph中:

from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate

# 检索器
retriever = vectorstore.as_retriever()

# 检索节点
def node_retrieve(state: dict):
    docs = retriever.invoke(state["question"])
    return {"docs": docs}

5.3 定义生成节点

生成节点负责调用Llama 3生成最终答案:

from langchain_community.chat_models import ChatOllama

# LLM实例
llm = ChatOllama(model="llama3")

# 提示模板
template = """基于以下上下文回答问题:
{context}

问题: {question}
"""
prompt = ChatPromptTemplate.from_template(template)

# 生成链
chain = (
    {"context": RunnablePassthrough(), "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

# 生成节点
def node_generate(state: dict):
    answer = chain.invoke({
        "context": state["docs"],
        "question": state["question"]
    })
    return {"answer": answer}

5.4 组装完整工作流

现在我们把所有节点组合起来:

from langgraph.graph import StateGraph, END
from typing import TypedDict, List
from langchain.schema import Document

class AgentState(TypedDict):
    question: str
    docs: List[Document]
    answer: str

# 创建图
workflow = StateGraph(AgentState)

# 添加节点
workflow.add_node("retrieve", node_retrieve)
workflow.add_node("generate", node_generate)

# 设置连接
workflow.set_entry_point("retrieve")
workflow.add_edge("retrieve", "generate")
workflow.add_edge("generate", END)

# 编译
app = workflow.compile()

5.5 测试智能体

现在你可以这样使用这个RAG智能体:

result = app.invoke({"question": "文档中提到了哪些关键技术?"})
print(result["answer"])

这个智能体会自动从你的文档中检索相关信息,然后生成基于这些信息的回答,而不是凭空编造答案。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐