LLM 大语言模型应用开发实战:基于 LangChain 构建具备记忆与工具调用的智能 Agent

7 阅读9分钟

大语言模型(LLM)的出现正在重塑软件开发的范式。从简单的文本生成到复杂的推理决策,LLM 已不再是“聊天玩具”,而是能够驱动真实业务场景的“数字大脑”。然而,直接调用 API 并不能满足个性化需求——我们需要编排提示词、管理对话记忆、集成外部工具、实现自主决策。这正是 LangChain 等框架的用武之地。

本文将带你从零搭建一个完整的 LLM 应用:一个基于本地部署的 Qwen 模型(通过 Ollama)、具备长期记忆、联网搜索和数学计算能力的智能 Agent。全部代码(含详细注释)超过 2200 字符,涵盖模型加载、提示词工程、记忆管理、工具定义、Agent 执行、Web 可视化等全流程,助你快速掌握 LLM 应用开发的核心技能。


1. 系统架构与技术选型

模块技术选型职责
大语言模型Qwen2:7B (Ollama 本地部署)推理与决策核心
应用框架LangChain (Python)链式编排、记忆、工具集成
对话记忆ConversationBufferMemory + Redis (可选)存储多轮对话历史
工具系统Tavily Search API + Python 解释器提供实时信息与计算能力
Agent 模式ReAct (Reasoning + Acting)思考-行动-观察循环
Web 界面Streamlit快速搭建交互式 UI
向量检索Chroma (备选)支持本地知识库(RAG 增强)

核心优势:

  • 本地部署:数据不出域,适合隐私敏感场景
  • 可扩展:轻松添加自定义工具(SQL 查询、企业内部 API)
  • 交互友好:Web 界面支持实时调试与对话

2. 环境搭建与依赖安装

2.1 安装 Ollama 并下载模型

bash

# 安装 Ollama (Linux/macOS)
curl -fsSL https://ollama.com/install.sh | sh

# 下载 Qwen2 7B 模型(约 4GB)
ollama pull qwen2:7b

# 启动 Ollama 服务(默认端口 11434)
ollama serve

2.2 Python 环境与依赖

bash

# 创建虚拟环境
python -m venv llm-env
source llm-env/bin/activate  # Windows: llm-env\Scripts\activate

# 安装核心依赖
pip install langchain langchain-community langchain-core
pip install ollama tavily-python streamlit
pip install chromadb tiktoken  # 用于 RAG 扩展

3. 模型加载与基础封装(代码块1)

我们使用 LangChain 的 ChatOllama 接口连接本地模型,并配置生成参数。

python

# model_loader.py
import os
from langchain_community.chat_models import ChatOllama
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler

# 初始化 LLM
def load_llm(model_name="qwen2:7b", temperature=0.3):
    """
    加载本地 Ollama 模型。
    :param model_name: 模型名称(需与 ollama list 一致)
    :param temperature: 生成温度 (0~1),越低越确定
    :return: ChatOllama 实例
    """
    llm = ChatOllama(
        model=model_name,
        temperature=temperature,
        top_p=0.9,
        num_predict=2048,          # 最大生成 token 数
        streaming=True,            # 启用流式输出
        callbacks=[StreamingStdOutCallbackHandler()],  # 控制台打印
        base_url="http://localhost:11434",  # Ollama 服务地址
    )
    return llm

# 测试加载
if __name__ == "__main__":
    llm = load_llm()
    response = llm.invoke("请用一句话介绍大语言模型。")
    print("\n完整回复:", response.content)

4. 提示词模板与输出解析(代码块2)

为了让模型按固定格式输出,我们定义 PromptTemplate 和 StrOutputParser。

python

# prompt_and_parse.py
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.schema.output_parser import StrOutputParser
from langchain.schema.runnable import RunnablePassthrough

# 通用问答模板(带历史)
QA_TEMPLATE = """
你是一个智能助手,能根据对话历史和用户问题提供准确、有帮助的回答。
如果你不确定答案,请如实告知,不要编造信息。

对话历史:
{history}

当前问题:{question}

请用中文回答,简洁明了。
"""

def create_qa_chain(llm):
    """
    创建基础问答链(无记忆)
    """
    prompt = ChatPromptTemplate.from_template(QA_TEMPLATE)
    chain = (
        {"history": RunnablePassthrough(), "question": RunnablePassthrough()}
        | prompt
        | llm
        | StrOutputParser()
    )
    return chain

# 输出解析示例:提取 JSON 或特定字段
from langchain.output_parsers import ResponseSchema, StructuredOutputParser

response_schemas = [
    ResponseSchema(name="answer", description="回答内容"),
    ResponseSchema(name="confidence", description="置信度,0~1 之间的小数")
]
parser = StructuredOutputParser.from_response_schemas(response_schemas)

# 在 prompt 中插入格式化指令
FORMAT_INSTRUCTIONS = parser.get_format_instructions()

def create_structured_chain(llm):
    prompt = ChatPromptTemplate.from_template(
        "根据问题:{question},给出回答和置信度。\n{format_instructions}"
    )
    chain = prompt | llm | parser
    return chain

5. 对话记忆管理(代码块3)

使用 ConversationBufferMemory 存储历史,并支持清空和截断。

python

# memory.py
from langchain.memory import ConversationBufferMemory
from langchain.schema import get_buffer_string

class ChatMemory:
    def __init__(self, max_tokens=2000):
        self.memory = ConversationBufferMemory(
            memory_key="history",
            return_messages=True,
            max_token_limit=max_tokens,  # 超过则截断
        )
    
    def add_message(self, user_input, ai_response):
        """存储一轮对话"""
        self.memory.chat_memory.add_user_message(user_input)
        self.memory.chat_memory.add_ai_message(ai_response)
    
    def get_history(self):
        """获取历史记录(字符串形式)"""
        return self.memory.load_memory_variables({})["history"]
    
    def clear(self):
        """清空记忆"""
        self.memory.clear()
    
    def to_messages(self):
        """返回消息列表,便于传递给链"""
        return self.memory.chat_memory.messages

# 测试
if __name__ == "__main__":
    mem = ChatMemory()
    mem.add_message("你好", "你好!有什么可以帮您?")
    mem.add_message("我叫小明", "很高兴认识你,小明!")
    print(mem.get_history())

6. 自定义工具(联网搜索 + 计算器)(代码块4)

我们定义两个工具:Tavily 搜索(需申请 API Key)和 Python 计算器。

python

# tools.py
import os
from langchain.tools import tool
from langchain_community.tools import TavilySearchResults
import subprocess
import json

# 1. 搜索工具
tavily_api_key = os.getenv("TAVILY_API_KEY", "your-key-here")
search_tool = TavilySearchResults(
    api_key=tavily_api_key,
    max_results=3,
    search_depth="basic",
    include_answer=True
)

# 2. 计算器工具(执行简单数学表达式)
@tool
def calculate(expression: str) -> str:
    """
    计算数学表达式,例如 '2 + 3 * 4'。
    注意:仅支持安全运算,禁用危险函数。
    """
    try:
        # 仅允许数字、运算符、括号
        allowed = set("0123456789+-*/(). ")
        if not all(c in allowed for c in expression):
            return "表达式包含非法字符,请仅使用数字和 + - * / ( )"
        # 使用 eval 但限制命名空间
        result = eval(expression, {"__builtins__": {}}, {})
        return f"计算结果: {result}"
    except Exception as e:
        return f"计算错误: {str(e)}"

# 3. 获取当前时间(示例工具)
from datetime import datetime

@tool
def get_current_time() -> str:
    """返回当前日期和时间(中国时区)"""
    now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    return f"当前时间: {now}"

# 工具列表
tools = [search_tool, calculate, get_current_time]

7. 构建 ReAct Agent(代码块5)

使用 LangChain 的 create_react_agent 结合提示模板,实现推理-行动-观察循环。

python

# agent.py
from langchain.agents import create_react_agent, AgentExecutor
from langchain.prompts import PromptTemplate
from langchain import hub

def create_agent(llm, tools, memory):
    """
    创建 ReAct Agent。
    :param llm: 语言模型
    :param tools: 工具列表
    :param memory: 对话记忆对象
    :return: AgentExecutor
    """
    # 使用官方的 ReAct 提示模板(也可自定义)
    prompt = hub.pull("hwchase17/react-chat")
    # 自定义扩展:加入历史
    # 注意:标准 ReAct 不支持直接注入历史,我们需通过 memory 在调用时处理
    
    # 但我们使用更灵活的方式:自定义 prompt
    custom_prompt = PromptTemplate.from_template(
        """
        你是一个智能助手,拥有以下工具:
        {tools}
        
        工具名称: {tool_names}
        
        请根据对话历史和当前用户输入,决定是否需要使用工具。
        若需要使用工具,请严格遵循以下格式:
        Thought: 我需要做什么
        Action: 工具名称
        Action Input: 输入参数
        Observation: 工具返回结果
        ... (重复 Thought/Action/Observation 可多次)
        Thought: 我现在知道最终回答了
        Final Answer: 最终回答
        
        对话历史:
        {chat_history}
        
        用户新问题: {input}
        
        {agent_scratchpad}
        """
    )
    
    agent = create_react_agent(
        llm=llm,
        tools=tools,
        prompt=custom_prompt,
        # 这里需要调整,因为 create_react_agent 默认使用特定参数名
        # 实际更推荐使用 AgentExecutor 直接构造
    )
    
    # 由于 create_react_agent 较复杂,我们改用更简单的 initialize_agent
    from langchain.agents import initialize_agent, AgentType
    
    agent_executor = initialize_agent(
        tools=tools,
        llm=llm,
        agent=AgentType.CHAT_CONVERSATIONAL_REACT_DESCRIPTION,
        memory=memory.memory,  # 传入记忆对象
        verbose=True,
        max_iterations=5,
        early_stopping_method="generate",
        handle_parsing_errors=True
    )
    return agent_executor

注意:上面使用了 CHAT_CONVERSATIONAL_REACT_DESCRIPTION,它内置支持记忆。简化实现如下:

python

# 简化版 Agent 创建(更稳定)
from langchain.agents import Tool, AgentExecutor, LLMSingleActionAgent
from langchain.agents import AgentOutputParser
from langchain.schema import AgentAction, AgentFinish

# 为了简洁,我们直接使用 initialize_agent,不深究底层
def build_agent(llm, tools, memory):
    from langchain.agents import initialize_agent, AgentType
    agent = initialize_agent(
        tools=tools,
        llm=llm,
        agent=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,
        memory=memory.memory,
        verbose=True,
        max_iterations=3,
        handle_parsing_errors=True,
        early_stopping_method="generate"
    )
    return agent

8. 整合所有模块:核心应用类(代码块6)

将模型、记忆、工具、Agent 封装为 LLMApp 类。

python

# app.py
from model_loader import load_llm
from memory import ChatMemory
from tools import tools
from agent import build_agent

class LLMApp:
    def __init__(self, model_name="qwen2:7b"):
        self.llm = load_llm(model_name)
        self.memory = ChatMemory()
        self.agent = build_agent(self.llm, tools, self.memory)
    
    def chat(self, user_input):
        """处理用户输入,返回最终答案"""
        try:
            # 调用 agent
            response = self.agent.invoke({"input": user_input})
            # 提取回答(不同 agent 返回结构不同)
            if isinstance(response, dict):
                if "output" in response:
                    answer = response["output"]
                else:
                    answer = str(response)
            else:
                answer = str(response)
            # 存储历史(已由 memory 自动处理,但确保)
            return answer
        except Exception as e:
            error_msg = f"处理出错: {str(e)}"
            return error_msg
    
    def clear_history(self):
        self.memory.clear()
    
    def get_history(self):
        return self.memory.get_history()

# 测试
if __name__ == "__main__":
    app = LLMApp()
    while True:
        q = input("\n你: ")
        if q.lower() in ["exit", "quit"]:
            break
        ans = app.chat(q)
        print(f"助手: {ans}")

9. Web 界面(Streamlit)(代码块7)

使用 Streamlit 构建美观的聊天界面,支持实时流式输出(高级功能)。

python

# streamlit_app.py
import streamlit as st
from app import LLMApp

st.set_page_config(page_title="LLM 智能助手", layout="wide")
st.title("🧠 大语言模型智能 Agent")
st.caption("基于 Qwen2 7B + LangChain,支持搜索、计算、记忆")

# 初始化应用实例(单例)
if "app" not in st.session_state:
    st.session_state.app = LLMApp()

# 初始化对话消息
if "messages" not in st.session_state:
    st.session_state.messages = []

# 显示历史消息
for msg in st.session_state.messages:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])

# 输入框
if prompt := st.chat_input("请输入您的问题..."):
    # 添加用户消息
    st.session_state.messages.append({"role": "user", "content": prompt})
    with st.chat_message("user"):
        st.markdown(prompt)
    
    # 生成回复
    with st.chat_message("assistant"):
        with st.spinner("思考中..."):
            try:
                app = st.session_state.app
                response = app.chat(prompt)
                st.markdown(response)
                st.session_state.messages.append({"role": "assistant", "content": response})
            except Exception as e:
                st.error(f"发生错误: {e}")

# 侧边栏:记忆管理与工具状态
with st.sidebar:
    st.subheader("⚙️ 控制面板")
    if st.button("🗑️ 清空记忆"):
        st.session_state.app.clear_history()
        st.session_state.messages = []
        st.rerun()
    
    st.divider()
    st.subheader("📊 对话统计")
    st.write(f"消息条数: {len(st.session_state.messages)}")
    
    st.divider()
    st.subheader("🔧 可用工具")
    st.write("- 🌐 联网搜索 (Tavily)")
    st.write("- 🧮 数学计算器")
    st.write("- 🕒 获取当前时间")
    
    st.divider()
    st.caption("模型: Qwen2 7B | 框架: LangChain")

10. RAG 增强:本地知识库集成(代码块8,扩展)

为了让模型回答私有文档问题,我们集成 Chroma 向量检索。

python

# rag_retriever.py
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import OllamaEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader

def build_retriever(doc_path):
    # 加载文档
    loader = TextLoader(doc_path, encoding="utf-8")
    docs = loader.load()
    # 分割
    splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
    chunks = splitter.split_documents(docs)
    # 创建 embedding(使用 Ollama 的 nomic-embed-text)
    embeddings = OllamaEmbeddings(model="nomic-embed-text")
    vectorstore = Chroma.from_documents(chunks, embeddings)
    return vectorstore.as_retriever(search_kwargs={"k": 3})

# 在 Agent 中添加检索工具
from langchain.tools import Tool

def create_retrieval_tool(retriever):
    def retrieve(query):
        docs = retriever.invoke(query)
        return "\n".join([doc.page_content for doc in docs])
    return Tool(
        name="KnowledgeBase",
        func=retrieve,
        description="从本地知识库中检索相关信息"
    )

11. 性能优化与部署建议

  • 模型量化:使用 Ollama 的 q4_0 量化版本,显存占用降低至 4GB。
  • 缓存:对相同问题使用 LLMCache(LangChain 内置),减少重复推理。
  • 异步处理:使用 asyncio 和 AsyncAgentExecutor 提升并发。
  • Docker 部署:将 Ollama 和 Streamlit 容器化,方便迁移。
  • 监控:集成 langsmith 追踪调用链,调试 Agent 行为。

12. 总结

本文完整实现了一个基于本地 LLM 的智能 Agent 应用,囊括了模型加载、提示词工程、对话记忆、工具集成、ReAct 循环、Web 界面等全栈开发技能。所有核心代码(含注释)总计超过 2300 字符,完全可运行于个人电脑,让你在 30 分钟内拥有一个可交互的 AI 助手。

通过这套代码,你可以:

  • 替换不同模型(Llama 3、ChatGLM)
  • 增加业务工具(查询数据库、发送邮件)
  • 构建多 Agent 协作系统

大语言模型的能力边界正在被开发者不断拓宽,掌握 LangChain 生态,你将站在下一代应用开发的最前沿。