在本教程中,我将向您展示如何使用LangChain v1、Ollama、Qwen以及Python来构建一个具备工具调用功能及短期记忆能力的本地AI智能体。

该智能体会自行决定何时调用相应的工具,并能记住之前的对话内容,因此您可以自然地提出后续问题。整个系统都在您的本地机器上运行,从而有效保护隐私,且不会产生任何API使用费用。

目录

背景知识

本地大语言模型本身无法与外部世界进行交互。如果询问它们当前的时间或一个句子中有多少个单词,它们往往会猜测或给出错误答案,除非您为它们提供获取正确信息的方法。这些模型的知识仅限于训练数据中包含的内容以及您在提示框中输入的信息。

其次,这些模型没有记忆功能。每当您发送新的消息时,它们就会忘记之前的所有内容。如果您提出一个问题得到答案后,再接着问另一个相关问题,模型可能完全不清楚您在指什么,因为每个对话环节都是从零开始的。

像Claude和ChatGPT这样的云托管模型已经具备了这些功能,但本地大语言模型则没有。在本教程中,我将向您展示如何构建一个能够解决这两个问题的本地AI智能体——它会在需要时自动调用Python函数,并能记住之前的对话内容,从而确保后续问题能够得到正确的处理。整个系统完全在您的本地机器上运行,既保护了隐私,也不会产生任何费用。

要跟随本教程进行操作,您需要在自己的机器上安装Ollama。这个示例在macOS、Windows和Linux系统中都能正常运行。我使用的是一台配备32GB内存的MacBook Pro,但如果您使用的机器内存较少,也可以选择Ollama中配置较低内存版本的Qwen模型来运行。

什么是工具调用?

工具调用是一种机制,在这种机制下,大语言模型会自行决定何时执行您的Python函数,而不是由您事先指定。所谓“工具”,其实就是模型可以被调用的Python函数。模型负责决定何时调用该函数以及传递哪些参数,而您则需要确定这个工具具体应该执行什么操作。

实际上,模型并不会直接运行代码。它会发出一个结构化的请求,相当于在说:“使用这些参数来调用这个工具。”您的代码会执行相应的函数,然后将结果返回给模型,之后模型再决定下一步该做什么:是继续调用另一个工具,还是生成最终答案。

并不是所有的模型都适合进行工具调用操作。Qwen是一个非常适合用于本地工具调用实验的开源模型,因此我在这里选择了它。

LangChain v1的create_agent功能可以处理工具调用相关操作。你只需要提供一个模型、一份工具列表以及一个系统提示语,它就会负责完成整个调用与响应流程,直到模型完成任务为止。

在大语言模型中,内存到底是什么?

大语言模型是无状态的。每次调用时,都会将完整的对话内容作为输入传递给模型,而模型也会仅根据这些输入内容来生成响应。“内存”在这里的含义,其实就是你在下一次调用时选择传回给模型的那些信息。

在实际应用中,主要有两种类型的内存机制:

  1. 短期记忆指的是当前对话的历史记录。在下一次调用时将这些历史记录传回模型,才能让多轮对话保持连贯性。当会话结束时,这些短期记忆就会被清除。

  2. 长期记忆则是指那些你需要跨会话保留的事实信息或之前的交流内容。这类信息通常存储在数据库或向量存储系统中,在需要时才会被加载出来。

在本教程中,我们将使用短期记忆机制。这种机制是最简单且最实用的形式,也正是它让智能体具备了进行真实对话的能力。

LangChain v1通过一种名为“检查指针”的机制来支持短期记忆功能。这种机制能够存储在不同调用之间的对话历史记录,其键值对应关系是由线程ID来确定的。我们将使用内置的InMemorySaver工具来实现短期记忆功能。

项目动机与架构设计

开展这个项目的初衷,就是希望能够利用本地大语言模型,让AI智能体更接近Claude或ChatGPT这样的先进模型。同时,这也进一步拓展了本地大语言模型的应用范围,使其具备更多的功能。

在这个项目中,我将使用Ollama来运行本地的Qwen聊天模型,而LangChain v1则负责将所有组件连接起来;此外,还会使用内置的InMemorySaver工具来实现短期记忆功能。

当用户发送消息时,检查指针会加载当前线程ID对应的对话历史记录,并将其添加到新消息的前面。模型会根据这些信息生成答案或发出工具调用指令。工具调用会遵循标准的调用与响应流程;当一轮对话结束时,检查指针会将新消息保存回相应的线程中,这样下一轮对话就能拥有完整的上下文信息。

步骤1:安装Ollama并下载模型

首先,请为你的操作系统安装Ollama应用程序。

我们将使用qwen3.5:4b作为聊天模型。这个模型本身就支持工具调用功能,所以我选择它来构建聊天系统。如果你的电脑内存有限,也可以使用qwen3.5:0.8b版本代替。

ollama pull qwen3.5:4b

步骤2:安装Python相关依赖库

python3 -m venv venv
source venv/bin/activate 

pip install langchain langchain-core langchain-ollama langgraph

本教程要求使用 langchain>=1.0.0

步骤 3:代理的 Python 代码

这段代码主要完成三项功能。

顶部的配置部分用于定义本地的 Ollama 模型以及系统提示语。

“工具”部分使用了 LangChain 的 @tool 装饰器来定义两个工具。current_time() 函数用于返回当前的本地日期和时间,而 word_count(text) 函数则用于计算文本中包含的单词数量。每个工具的文档字符串会在模型决定是否调用该工具时被读取,因此其表述非常重要。

main() 函数通过 create_agent() 创建代理对象,同时为该代理配置了一个 InMemorySaver 对象以用于存储短期记忆数据,之后会进入交互循环。在每次交互中,用户的输入信息会连同一个固定的线程 ID 一起传递给代理,这样 InMemorySaver 就能够知道应该加载或保存哪段对话记录。

请将这段代码保存到你的 agent.py 文件中。

from datetime import datetime

from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain_ollama import ChatOllama
from langgraph.checkpoint.memory import InMemorySaver

CHAT_MODEL = "qwen3.5:4b"   # Ollama 聊天模型,该模型必须支持使用工具功能。

SYSTEM_PROMPT = (
    "你是一个功能强大的助手,可以使用相关工具来获取当前时间或计算文本中的单词数量。"
    "当用户的请求需要使用工具时,请使用这些工具。"
    "如果问题不需要使用工具,可以直接给出答案。"
    "如果某个工具使用了错误的方法,請清楚地说明原因。"
)

# ----- 工具 -----
@tool
def current_time() -> str:
    """返回当前的本地日期和时间。当用户询问当前时间时可以使用这个函数。"""
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

@tool
def word_count(text: str) -> int:
    """计算文本中包含的单词数量。当用户询问某段文字的长度,或者要求你统计他们提供的文本中的单词数时,可以使用这个函数。返回的结果是一个整数。"""
    return len(text.split())

TOOLS = [current_time, word_count]


# ----- 代理对象 -----

def build_agent():
    model = ChatOllama(model=CHAT_MODEL, temperature=0)

    # InMemorySaver 会将对话历史记录保存在内存中,这些记录是按照线程 ID 来区分的。当进程结束时,由于内存限制,这些记录将会被清除。
    checkpointer = InMemorySaver()

    return create_agent(
        model=model,
        tools=TOOLS,
        systemprompt=SYSTEM_PROMPT,
        checkpointer=checkpointer,
    )


def main():
    agent = build_agent()

    # 线程 ID 用于告诉 InMemorySaver 应该加载或保存哪段对话记录。
    config = {"configurable": {"thread_id": "thread"}}

    print("准备就绪!你可以向代理提出任何问题,它会记住之前的对话内容。\n")

    # 记录在上一次交互之前用户发送了多少条消息,这样我们就可以从返回的结果中筛选出仅属于本次交互的新消息(包括使用的工具调用以及最终的回答)。
    prev_message_count = 0

    while True:
        question = input("你:").strip()
        if not question or question.lower() == "退出":
            break

        result = agent.invoke(
            {"messages": [{"role": "user", "content": question}]},
            config=config,
        )

        # 只考虑本次交互中新增加的消息,而不包括之前的所有对话记录。
        new_messages = result["messages"][prev_message_count:]

        # 打印本次交互中使用的工具调用信息。
        for msg in new_messages:
            tool_calls = getattr(msg, "tool_calls", None)
            if toolcalls:
                for call in tool_calls:
                    print(f"[工具调用] {call['name']}({call['args']})")

        print(f"\n答案:{result['messages'][-1].content}\n")

        # 更新下次交互时使用的消息计数。
        prev_message_count = len(result["messages"])

步骤4:运行代理程序

python agent.py

该代理程序会进入一个交互循环。当你提出一个问题时,它要么会直接给出答案,要么会在回答之前调用一个或多个工具来获取信息。代理程序会自行判断哪些问题需要使用特定的工具来处理。[tool call]行展示了代理程序选择了哪些工具以及传递了哪些参数,这样你就能清楚地了解它的实际操作过程。

在相信这些答案之前,请先检查[tool call]行,确保代理程序确实使用了正确的工具并传入了正确的参数。本地模型通常比托管式的前沿模型体积更小,因此也更容易出现错误,尤其是在处理工具相关参数时。

作为测试,我们可以不使用任何工具和内存功能来运行该代理程序,只需将以下三行代码注释掉即可:

return create_agent(
        model=model,
        # tools=TOOLS,
        # systemprompt=SYSTEM_PROMPT,
        # checkpointer=checkpointer,
    )

下面是我与这个代理程序的对话记录:

你:嗨,我叫Darsh。
回答:嗨,Darsh!很高兴认识你。今天有什么可以帮到你的吗?
你:我的名字是什么?
回答:我无法获取你的个人信息,所以不知道你的名字是什么。
你:现在是什么时间?
回答:我无法访问实时数据,因此无法告诉你现在的具体时间。
你:美国的首都是什么?
回答:美国的首都是华盛顿特区。

它并没有记住我的名字,而且由于没有相应的工具,也无法告诉我当前的时间。

现在,让我们启用所有工具和内存功能来运行这个代理程序吧。取消对之前注释掉的这三行的注释,然后重新运行程序。你会看到以下不同的结果:

你:嗨,我叫Darsh。
回答:你好,Darsh!很高兴认识你。今天有什么可以帮到你的吗?
你:我的名字是什么?
回答:你的名字是Darsh!
你:现在是什么时间?
[tool call] current_time({})
回答:现在的时间是2026年7月1日21:30:58。
你:我的名字由多少个字母组成?
[tool call] word_count({'text': 'Darsh'})
回答:你的名字“Darsh”包含:
- **1个单词** 
- **5个字母**(D-a-r-s-h)

所以,根据你对“长度”的定义不同,这个答案可能是“1个单词”或“5个字母”。
你:美国的首都是什么?
回答:美国的首都是华盛顿特区。

对于一个规模仅为40亿参数的本地模型来说,它的表现已经相当不错了。在处理时间相关的问题时,它调用了current_time函数;在统计我名字中的字母数量时,则使用了word_count函数。

如果你想进一步提高代理程序使用工具的效率,可以尝试以下方法:

  1. 工具描述:每个工具对应的文档字符串已经起到了很大的帮助作用。提供具体且具有指导意义的描述,可以帮助代理程序选择正确的工具。

  2. 系统提示语:为模型明确指示何时应该使用工具、何时不应该使用工具,这样就可以减少不必要的调用操作。

长期记忆

在这个例子中,短期记忆仅用于当前对话内容。如果你希望智能体能够记住不同聊天中的信息,就需要使用长期记忆功能。

在LangChain v1版本中,长期记忆数据被存储在像Postgres这样的数据库中,这样在后续的对话中就可以再次检索这些信息。

要实现长期记忆功能,可以采用以下两种方法之一:要么让模型使用专门的工具来保存和获取用户信息,要么让智能体通过中间件或Python代码在后台自动存储名称、响应偏好等数据。

对于本教程来说,短期记忆已经足够使用了。而当你需要跨会话保留信息时,长期记忆就是自然而然的下一个选择。你可以在LangChain文档中了解更多关于长期记忆的内容。

总结

通过本教程,你学会了如何使用LangChain v1的create_agent函数、@tool装饰器以及InMemorySaver检查点机制来构建一个具备工具调用能力和短期记忆功能的本地AI智能体。所有这些操作都在你的个人电脑上完成,没有任何数据会离开你的设备,而且你可以完全控制智能体能够使用哪些工具,也无需支付任何API费用。

接下来,你可以尝试添加自己的工具,比如用于记录笔记的工具、列出文件或读取文件的工具。改变这些工具的描述,看看智能体的行为会发生怎样的变化。也可以尝试使用不同的模型,比如qwen3.5:0.8b或更大的Qwen模型,来看看模型的规模会对工具调用功能产生什么影响。尽情探索吧!

如果你喜欢本教程,可以在我的博客上找到我的更多文章(最近的文章包括一系列系统设计论文),也可以在我的个人网站上查看我的工作成果,同时还可以在LinkedIn上关注我的最新动态。

Comments are closed.