Day20:打造全能本地轻量Agent,离线运行也能呼风唤雨!
Day20:打造全能本地轻量Agent,离线运行也能呼风唤雨!
开场白:从“会聊天”到“全能助手”
兄弟们,经过前五天的修炼,我们已经:
-
Day15:学会了LangChain的基础,知道了怎么把提示词、模型、输出串成一条链。
-
Day16:学会了给AI装上工具,让它能查天气、算数学。
-
Day17:给AI装上了记忆,它能记住我们上一句说了啥。
-
Day18:把AI的大脑从云端搬到了本地(Ollama),断网也能跑。
-
Day19:把LangChain和Ollama对接,实现了离线Agent的雏形。
今天,我们要把这些零件全部组装起来,打造一个真正能打、能干活、还能陪你唠嗑的全能本地轻量Agent!它就像一把瑞士军刀,既有工具(天气、时间、计算器、文件操作),又有记忆(不会扭头就忘),还跑得飞快(优化过的)。而且,它完全离线,数据不上传,免费无限使用!
想象一下:你可以让它帮你整理文件、记录笔记、算账、查天气……一切都在你自己的电脑上完成,不用联网,不用花一分钱。是不是很爽?
今天我们就来干这件事!
今日目标:组装一台离线AI瑞士军刀
-
核心任务:整合本周所有知识,开发一个本地轻量Agent(基于LangChain+Ollama本地模型+自定义Tool+Memory),支持离线运行。
-
补充任务:优化Agent的运行效率,减少模型推理时间,添加简单的交互界面(命令行交互)。
-
今日输出:本地轻量Agent代码(可离线运行)。
说白了,我们要做一个能跑在本地、能调用多种工具、有记忆、而且响应很快的命令行AI助手。
准备工作:环境检查
确保你已经安装并配置好了以下环境:
-
Python 3.9+(建议使用虚拟环境)
-
Ollama 已安装,并且服务运行中
-
已拉取至少一个本地模型(如
qwen:7b-q4_0或llama3:7b) -
LangChain及相关库:
langchain,langchain-community,langchain-core
快速检查:
ollama list # 应该看到模型
curl http://localhost:11434 # 应该返回 Ollama is running
pip show langchain # 应该显示版本
如果缺哪个,赶紧补上。
设计我们的本地轻量Agent
我们将在周五的Agent基础上进行扩展,加入更多实用的工具,优化记忆方式,并提供一个友好的命令行交互界面。
工具库大扩充
之前我们只有天气和计算器,今天我们来添加几个新工具:
-
时间工具:获取当前时间(可以指定时区、格式)
-
文件操作工具:读写文件(用于记录笔记、保存数据)
-
系统命令工具:执行简单的系统命令(比如
ls,echo,但要注意安全) -
随机数生成器:生成随机数、随机字符串
-
单位换算:长度、重量、温度换算
我们只演示几个,你可以按需添加。
import datetime
import random
import os
import subprocess
from langchain.tools import tool
@tool
def get_current_time(format: str = "%Y-%m-%d %H:%M:%S") -> str:
"""获取当前时间,可指定格式(例如 '%Y-%m-%d %H:%M:%S')"""
return datetime.datetime.now().strftime(format)
@tool
def random_number(min_val: int = 0, max_val: int = 100) -> str:
"""生成一个随机整数,范围[min_val, max_val]"""
return str(random.randint(min_val, max_val))
@tool
def write_note(filename: str, content: str) -> str:
"""将内容写入文件(如果文件不存在则创建),返回写入结果"""
try:
with open(filename, 'w', encoding='utf-8') as f:
f.write(content)
return f"笔记已保存到 {filename}"
except Exception as e:
return f"写入失败:{str(e)}"
@tool
def read_note(filename: str) -> str:
"""读取文件内容"""
try:
with open(filename, 'r', encoding='utf-8') as f:
return f.read()
except Exception as e:
return f"读取失败:{str(e)}"
# 注意:执行系统命令有安全风险,这里仅作演示,实际生产环境要谨慎
@tool
def run_command(command: str) -> str:
"""执行一条系统命令(例如 'ls -l'),返回输出结果"""
try:
result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=5)
return result.stdout if result.stdout else result.stderr
except Exception as e:
return f"命令执行失败:{str(e)}"
记忆升级:告别Token浪费
之前我们用的是 ConversationBufferMemory,它会原封不动地保存所有对话历史。随着对话变长,每次调用都会把整个历史塞进提示词,导致token浪费、推理变慢。今天换成 **ConversationSummaryMemory**,它会自动把历史对话总结成摘要,只传递摘要给模型,大大节省上下文长度。
from langchain.memory import ConversationSummaryMemory
memory = ConversationSummaryMemory(
llm=llm, # 需要传入LLM用于生成摘要
memory_key="chat_history",
return_messages=True,
max_token_limit=2000 # 限制摘要的最大token数
)
注意:ConversationSummaryMemory 内部需要调用LLM来生成摘要,所以会额外消耗一点时间,但整体上对于长对话是值得的。我们也可以选择 ConversationBufferWindowMemory 只保留最近N轮对话。
提示词优化:让Agent更聪明
一个好的系统提示词能显著提升Agent的效率和准确性。我们给Agent设定角色,并明确工具使用规则:
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages([
("system", """你是一个高效、友好的本地AI助手,名叫小O。你可以使用以下工具来帮助用户:
{tools}
请遵守以下规则:
1. 如果用户的问题可以直接回答,就不调用工具,直接回答。
2. 如果需要调用工具,严格按照工具的描述使用正确的参数。
3. 回答要简洁,不要啰嗦。
4. 你可以记住对话的上下文(通过记忆系统),根据上下文连贯回答。
"""),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
])
注意:{tools} 会在 create_react_agent 中被自动填充,我们不需要手动替换。
命令行交互界面:像老朋友一样聊天
用 input() 循环是最简单的,但我们可以稍微美化一下:用 rich 库让输出更漂亮,或者直接用简单的 print。为了轻量,我们还是用 input,但加上一些提示符和颜色(用ANSI颜色码)。
def main():
print("\033[1;36m🤖 本地AI助手 小O 已启动!\033[0m")
print("💡 输入 'exit' 退出,输入 'clear' 清空记忆\n")
while True:
user_input = input("\033[1;32m你:\033[0m")
if user_input.lower() in ['exit', 'quit']:
print("\033[1;36m小O:再见!👋\033[0m")
break
elif user_input.lower() == 'clear':
memory.clear()
print("\033[1;33m小O:记忆已清空。\033[0m")
continue
response = agent_executor.invoke({"input": user_input})
print(f"\033[1;36m小O:\033[0m{response['output']}\n")
优化运行效率:榨干每一毫秒
为了让Agent跑得更快,我们还可以做以下几点:
使用量化模型
-
前面已经说过,量化版本(如
qwen:7b-q4_0)比原始模型快30%~50%,内存占用也少。调整推理参数
-
num_predict:限制输出长度。工具调用回答通常很短,256或512足够。 -
temperature:0.1~0.3,避免模型发散。 -
top_k:降低到10~20,加快采样。使用流式输出(可选)
-
如果希望用户看到逐字输出,可以在
AgentExecutor中启用流式,但会增加代码复杂度。我们暂时不做,因为命令行下体验一般。缓存LLM调用
-
对于相同的输入,可以使用
langchain.cache缓存结果,避免重复推理。但我们Agent是对话式的,相同输入较少,所以暂不实现。减少工具调用次数
-
优化提示词,让Agent尽量少调用工具。比如“现在几点了?”直接回答,而不需要调用时间工具(当然如果模型不知道时间,还是得调用)。
异步调用(高级)
-
对于IO密集型工具(如网络请求),可以使用异步,但这里我们不深入。
完整代码:你的离线AI助手诞生了
下面给出今天所有代码的整合版本。你可以直接复制保存为 local_lightweight_agent.py。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
周六:本地轻量Agent开发
功能:离线运行的AI助手,支持多种工具、对话记忆、命令行交互
作者:小O
"""
import datetime
import random
import os
import subprocess
from langchain.tools import tool
from langchain_community.chat_models import ChatOllama
from langchain.memory import ConversationSummaryMemory
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.agents import create_react_agent, AgentExecutor
# ---------- 1. 定义工具 ----------
@tool
def get_weather(city: str) -> str:
"""查询指定城市的天气"""
# 这里可以接入真实API,为演示简单模拟
weather_db = {
"北京": "晴,20-25℃",
"上海": "多云,22-27℃",
"广州": "阵雨,25-30℃",
"深圳": "阴,24-28℃",
}
return f"{city}今天{weather_db.get(city, '天气未知')}。"
@tool
def calculator(expression: str) -> str:
"""计算数学表达式,例如 '2+2' 或 'sqrt(16)'"""
try:
import math
result = eval(expression, {"__builtins__": {}}, math.__dict__)
return f"计算结果:{result}"
except Exception as e:
return f"计算错误:{str(e)}"
@tool
def get_current_time(format: str = "%Y-%m-%d %H:%M:%S") -> str:
"""获取当前时间,可指定格式(例如 '%Y-%m-%d %H:%M:%S')"""
return datetime.datetime.now().strftime(format)
@tool
def random_number(min_val: int = 0, max_val: int = 100) -> str:
"""生成一个随机整数,范围[min_val, max_val]"""
return str(random.randint(min_val, max_val))
@tool
def write_note(filename: str, content: str) -> str:
"""将内容写入文件(如果文件不存在则创建),返回写入结果"""
try:
with open(filename, 'w', encoding='utf-8') as f:
f.write(content)
return f"笔记已保存到 {filename}"
except Exception as e:
return f"写入失败:{str(e)}"
@tool
def read_note(filename: str) -> str:
"""读取文件内容"""
try:
with open(filename, 'r', encoding='utf-8') as f:
return f.read()
except Exception as e:
return f"读取失败:{str(e)}"
@tool
def run_command(command: str) -> str:
"""执行一条系统命令(例如 'ls -l'),返回输出结果(安全警告:请勿用于生产环境)"""
try:
result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=5)
return result.stdout if result.stdout else result.stderr
except Exception as e:
return f"命令执行失败:{str(e)}"
# ---------- 2. 初始化本地模型(推荐量化版) ----------
llm = ChatOllama(
model="qwen:7b-q4_0", # 可换成 llama3:7b
temperature=0.1,
num_predict=256,
)
# ---------- 3. 设置记忆(摘要记忆,节省token) ----------
memory = ConversationSummaryMemory(
llm=llm,
memory_key="chat_history",
return_messages=True,
max_token_limit=2000,
)
# ---------- 4. 创建提示词模板 ----------
prompt = ChatPromptTemplate.from_messages([
("system", """你是一个高效、友好的本地AI助手,名叫小O。你可以使用以下工具来帮助用户:
{tools}
请遵守以下规则:
1. 如果用户的问题可以直接回答,就不调用工具,直接回答。
2. 如果需要调用工具,严格按照工具的描述使用正确的参数。
3. 回答要简洁,不要啰嗦。
4. 你可以记住对话的上下文(通过记忆系统),根据上下文连贯回答。
"""),
MessagesPlaceholder(variable_name="chat_history"),
("human", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
])
# ---------- 5. 创建Agent执行器 ----------
tools = [get_weather, calculator, get_current_time, random_number, write_note, read_note, run_command]
agent = create_react_agent(llm, tools, prompt)
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
memory=memory,
verbose=False, # 调试时可改为True
handle_parsing_errors=True,
)
# ---------- 6. 命令行交互界面 ----------
def main():
print("\033[1;36m🤖 本地AI助手 小O 已启动!\033[0m")
print("💡 输入 'exit' 退出,输入 'clear' 清空记忆\n")
while True:
user_input = input("\033[1;32m你:\033[0m")
if user_input.lower() in ['exit', 'quit']:
print("\033[1;36m小O:再见!👋\033[0m")
break
elif user_input.lower() == 'clear':
memory.clear()
print("\033[1;33m小O:记忆已清空。\033[0m")
continue
response = agent_executor.invoke({"input": user_input})
print(f"\033[1;36m小O:\033[0m{response['output']}\n")
if __name__ == "__main__":
main()
运行演示:和你的AI宠物聊聊天
保存代码后,在终端运行:
python local_lightweight_agent.py
你会看到彩色的提示符,然后就可以和它对话了。试试下面这些:
你:现在几点了?
小O:现在是2025年3月22日 14:35:18。
你:帮我生成一个1到100的随机数。
小O:76
你:写个笔记,文件名 test.txt,内容“今天学习了本地AI助手开发”。
小O:笔记已保存到 test.txt
你:帮我读一下 test.txt
小O:今天学习了本地AI助手开发
你:北京天气怎么样?
小O:北京今天晴,20-25℃。
你:那明天适合出门吗?
小O:根据天气预报,明天北京也是晴天,温度差不多,很适合出门哦。
你:clear
小O:记忆已清空。
你:exit
小O:再见!👋
注意:第二条“明天适合出门吗?”它没有调用天气工具,而是根据记忆中的天气信息回答了,说明记忆生效了。
总结
今天我们把过去一周的知识全部融合在一起,打造了一个功能强大、完全离线的本地轻量Agent。它:
-
✅ 基于LangChain + Ollama,离线运行
-
✅ 拥有丰富的工具(天气、计算器、时间、文件操作、随机数等)
-
✅ 具备记忆(摘要记忆,节省token)
-
✅ 有友好的命令行交互界面
-
✅ 经过参数优化,响应速度更快
常见问题解答
Q:为什么我的Agent总是调用工具,哪怕可以直接回答?
A:可以降低temperature,或者在系统提示中强调“如果可以直接回答就不要调用工具”。另外,工具描述要清晰,避免误导。
Q:使用ConversationSummaryMemory时,为什么有时会慢?
A:因为每次对话后,它需要调用LLM生成摘要。如果对话轮数很多,摘要会累积。可以适当调整max_token_limit,或换用ConversationBufferWindowMemory。
Q:执行 **run_command** 工具时,提示安全警告怎么办?
A:确实有安全风险,建议在生产环境中移除该工具,或者对命令进行白名单过滤。这里仅用于学习演示。
Q:本地模型跑起来很卡,有什么绝招?
A:除了量化,还可以尝试更小的模型(如 qwen:4b),或者减少num_predict。另外,确保没有其他占用GPU的程序。
Q:我想添加更多工具,比如发送邮件、查询股票,怎么做?
A:只需定义新的@tool函数,加入tools列表即可。注意函数的docstring要准确,参数类型清晰。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)