大语言模型(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 生态,你将站在下一代应用开发的最前沿。