LangChain实战教程:如何让聊天机器人学会调用搜索工具和网页阅读器?

132 阅读11分钟

本文将通过实际代码示例,展示如何从零开始构建一个具备网络搜索、内容理解和工具调用能力的AI助手。

完整代码放在这里了>>👉 福利来袭掘金大礼包:《2025最全AI大模型学习资源包》无偿分享,安全可点 👈

现代智能对话系统的核心是大型语言模型(LLM),这些模型通过在海量文本数据上训练,习得了语言的统计规律和世界知识。然而,单纯的LLM存在一些固有局限性:

  1. \1. 知识时效性问题:模型训练数据有截止日期,无法获取最新信息
  2. \2. 封闭性问题:无法与外部系统和服务交互
  3. \3. 幻觉问题:可能生成看似合理但实际不准确的内容
  4. \4. 上下文限制:输入长度受限,无法处理大量背景信息

为解决这些问题,代理框架(Agent Framework)应运而生。代理框架允许LLM通过工具调用扩展其能力边界,实现与外部世界的交互。在这一框架下,LLM不再仅是信息的终端,而是成为了决策的枢纽,能够根据需要调用不同工具完成复杂任务。

LangChain和LangGraph正是基于这一理念设计的开源框架,它们提供了构建LLM应用的标准化组件和流程。这种架构使得AI系统能够:

  • • 获取实时信息
  • • 执行专业计算
  • • 访问私有数据
  • • 与其他系统集成
  • • 进行多步推理

这种"思考-行动"循环使AI助手的能力大幅提升,能够处理更复杂、更开放的任务场景。

技术栈概览

在构建我们的智能对话系统时,我们采用了以下技术栈:

  1. \1. 大型语言模型:使用Ollama本地部署的Qwen2(通义千问2.5)0.5B模型作为核心推理引擎
  2. \2. 框架与库
    • LangChain:提供LLM应用构建的标准化组件
    • LangGraph:用于构建多步推理流程的有向图框架
    • LangServe:用于部署LangChain应用的服务框架
  3. \3. 工具集成
    • • 自定义搜索工具(模拟网络搜索功能)
    • • 网页内容提取工具(使用requests和BeautifulSoup实现)
  4. \4. 辅助技术
    • • 类型提示(Python typing模块)
    • • 网页内容解析(BeautifulSoup)
    • • HTTP请求处理(requests)

环境搭建与基础配置

依赖库安装

pip install langchain langchain-community langchain-ollama langgraph langserve requests beautifulsoup4

Ollama服务配置

我们的系统使用本地部署的Ollama服务运行Qwen2.5模型。需确保Ollama已正确安装并运行:

ollama_base_url = "http://192.168.1.1:11434"  # 默认地址,可根据实际情况修改

确保Ollama服务已下载并可以提供Qwen2.5:0.5b模型。如果尚未下载,可以通过以下命令获取:

ollama pull qwen2.5:0.5b

模型选择与初始化

这里需要使用支持tools功能的模型。模型参数实际应用中要尽量大一些。

模型初始化

我们使用LangChain的ChatOllama接口来初始化模型:

from langchain_ollama import ChatOllama  
  
model = ChatOllama(  
    model='qwen2.5:0.5b',   
    base_url=ollama_base_url  
)

这创建了一个连接到本地Ollama服务的ChatOllama实例,使用Qwen2.5:0.5b模型。

基础对话测试

在进一步构建之前,我们可以测试模型的基本对话能力:

from langchain_core.messages import HumanMessage  
  
result = model.invoke([HumanMessage(content='北京天气怎么样?')])  
print(result)
content='抱歉,我无法提供实时的天气信息。我的知识和能力限制在过去的10年里我在学习如何与人交互,而没有访问网络的能力。如果您需要查询实时或未来的天气情况,请查阅相关的气象网站或者使用手机上的天气应用程序。'

这个简单测试验证了模型能够响应基本查询,但也会显示其局限性——模型无法获取实时天气信息,因为它没有访问外部数据的能力。这正是我们需要添加工具和构建代理系统的原因。

工具集成的实现策略

为了增强模型的能力,我们需要为其配备各种工具。工具使LLM能够与外部世界交互,获取信息并执行操作。在本系统中,我们实现了两种核心工具:自定义搜索工具和网页内容提取工具。

自定义搜索工具

搜索工具允许AI助手获取最新或专业信息,这些信息可能不在模型的训练数据中。在生产环境中,我们通常会使用Google、Bing或专业API如Tavily。但在本例中,为了简化实现和控制行为,我们创建了一个模拟搜索工具:

from typing importList  
  
# 配置模拟搜索数据  
MOCK_SEARCH_DATA = {  
    "北京天气": [  
        {  
            "title""北京今日天气预报",  
            "content""北京今天晴朗,气温15-25度,空气质量良好,适合户外活动。",  
            "url""https://example.com/beijing-weather",  
            "timestamp""2024-03-21"  
        },  
        {  
            "title""北京一周天气预报",  
            "content""本周北京以晴为主,周末可能有小雨,气温适宜。",  
            "url""https://example.com/beijing-weather-week",  
            "timestamp""2024-03-21"  
        }  
    ],  
    "中国首都": [  
        {  
            "title""中国首都简介",  
            "content""北京是中华人民共和国的首都,是全国的政治、文化中心。",  
            "url""https://example.com/beijing-info",  
            "timestamp""2024-03-21"  
        }  
    ],  
    # 可添加更多模拟数据...  
}  
  
defformat_search_results(results: List[dict]) -> str:  
    """格式化搜索结果输出"""  
    output = "\n搜索结果:\n" + "="*50 + "\n"  
    for idx, result inenumerate(results, 1):  
        output += f"{idx}{result['title']}\n"  
        output += f"   内容: {result['content']}\n"  
        output += f"   来源: {result['url']}\n"  
        output += f"   时间: {result.get('timestamp''未知')}\n"  
        output += "-"*50 + "\n"  
    return output  
  
defcustom_search(query: str) -> List[dict]:  
    """  
    模拟搜索功能,返回预设的数据  
    参数:  
        query: 搜索查询字符串  
    返回:  
        匹配的搜索结果列表  
    """  
    try:  
        # 简单的关键词匹配  
        for key in MOCK_SEARCH_DATA:  
            if key in query:  
                results = MOCK_SEARCH_DATA[key]  
                print(format_search_results(results))  # 格式化输出搜索结果  
                return results  
        return [{"title""未找到结果",   
                "content""抱歉,没有找到相关信息。",   
                "url""",  
                "timestamp""N/A"}]  
    except Exception as e:  
        print(f"搜索过程中发生错误: {str(e)}")  
        return [{"title""搜索错误",   
                "content""搜索过程中发生错误,请稍后重试。",   
                "url""",  
                "timestamp""N/A"}]

这个自定义搜索工具使用简单的关键词匹配来模拟搜索引擎。它返回结构化的搜索结果,包括标题、内容摘要、URL和时间戳。虽然简单,但它清晰地展示了工具集成的核心概念。

网页内容提取工具

搜索结果通常只提供内容摘要,但用户可能需要更详细的信息。网页内容提取工具允许AI助手获取和处理完整的网页内容:

import requests  
from bs4 import BeautifulSoup  
import random  
import time  
  
deffetch_webpage_content(url: str) -> str:  
    """  
    获取网页内容的工具  
    参数:  
        url: 网页地址  
    返回:  
        网页内容摘要  
    """  
    try:  
        # 添加随机延迟,模拟真实网络请求  
        time.sleep(random.uniform(0.51.5))  
          
        # 如果是示例URL,返回模拟数据  
        if'example.com'in url:  
            return {  
                'https://example.com/beijing-weather''北京天气实时数据:晴朗,温度20度,空气质量指数75,相对湿度45%',  
                'https://example.com/beijing-weather-week''北京未来一周天气预报:周一到周五以晴为主,周六周日可能有零星小雨',  
                'https://example.com/beijing-info''北京市基本信息:面积16410平方公里,常住人口2170万,下辖16个区,是中国的政治文化中心'  
            }.get(url, '该页面内容暂时无法访问')  
              
        # 真实网页请求  
        headers = {'User-Agent''Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'}  
        response = requests.get(url, headers=headers, timeout=10)  
        response.raise_for_status()  
          
        soup = BeautifulSoup(response.text, 'html.parser')  
        # 移除脚本和样式元素  
        for script in soup(["script""style"]):  
            script.decompose()  
              
        # 提取正文内容  
        text = soup.get_text()  
        lines = (line.strip() for line in text.splitlines())  
        chunks = (phrase.strip() for line in lines for phrase in line.split("  "))  
        text = ' '.join(chunk for chunk in chunks if chunk)  
          
        # 返回前500个字符作为摘要  
        return text[:500] + ('...'iflen(text) > 500else'')  
          
    except Exception as e:  
        return f"获取网页内容时发生错误: {str(e)}"

这个工具不仅支持模拟URL,还能处理真实网页。它使用requests库获取网页内容,然后使用BeautifulSoup解析HTML,提取纯文本内容。为了避免返回过多信息,它只返回前500个字符作为摘要。

工具注册与绑定

有了这两个核心工具,我们可以将它们注册到LangChain的Tool框架中,并绑定到我们的模型:

from langchain.tools import Tool  
  
# 创建工具列表  
tools = [  
    Tool(  
        name="CustomSearch",  
        description="搜索工具:用于搜索天气、城市等基本信息。输入搜索关键词,返回相关信息。",  
        func=custom_search  
    ),  
    Tool(  
        name="WebpageReader",  
        description="网页阅读工具:输入网页URL,获取网页的详细内容。适用于需要深入了解某个主题时使用。",  
        func=fetch_webpage_content  
    )  
]  
  
# 更新模型绑定  
model_with_tools = model.bind_tools(tools)

绑定后的model_with_tools现在具备了使用这两个工具的能力,可以在适当时机调用它们来获取信息或执行操作。

代理系统的构建与执行

绑定工具到模型只是第一步,要实现真正的代理行为,我们需要构建一个能够执行"思考-行动"循环的代理系统。LangGraph提供的chat_agent_executor正是为此设计的组件。

代理执行器设计原理

代理执行器的核心功能是协调LLM与工具之间的交互。它实现了以下工作流程:

  1. \1. 接收用户输入
  2. \2. 让LLM生成初步响应或工具调用需求
  3. \3. 如需调用工具,执行工具并获取结果
  4. \4. 将工具结果提供给LLM进行分析
  5. \5. 生成最终响应

这种循环允许LLM根据需要多次调用工具,直到收集到足够信息来回答用户的问题。

实现代理执行器

使用LangGraph创建代理执行器非常直接:

from langgraph.prebuilt import chat_agent_executor  
  
# 创建代理执行器  
agent_executor = chat_agent_executor.create_tool_calling_executor(model, tools)

这行代码创建了一个支持工具调用的代理执行器,它封装了复杂的状态管理和流程控制逻辑,使我们能够专注于代理的功能而非实现细节。

代理执行流程的可视化

为了更好地理解代理系统的工作方式,我们可以将一次完整的代理交互分解为各个步骤:

  1. \1. 用户输入阶段
    • • 用户提问:"北京天气怎么样?"
    • • 系统将问题传递给代理执行器
  2. \2. LLM初步分析阶段
    • • LLM识别需要实时天气信息
    • • LLM决定需要调用搜索工具
    • • LLM生成工具调用请求
  3. \3. 工具执行阶段
    • • 代理执行器接收工具调用请求
    • • 执行CustomSearch工具,查询"北京天气"
    • • 工具返回结构化的天气信息
  4. \4. LLM结果处理阶段
    • • LLM接收工具返回的天气信息
    • • LLM分析和理解这些信息
    • • 如需进一步信息,LLM可选择调用其他工具
    • • 否则,LLM生成最终回答
  5. \5. 输出生成阶段
    • • 代理执行器收集LLM的最终回答
    • • 返回格式化的回复给用户

这个流程可以重复多次,直到LLM收集了足够的信息来生成满意的回答。

多模式交互测试

为验证我们构建的系统功能,我们需要进行全面的测试,涵盖直接模型调用和代理执行两种模式。

直接模型调用测试

首先,我们测试绑定了工具的模型的直接调用行为:

def print_model_response(response, query: str):  
    """格式化打印模型响应"""  
    print("\n" + "="*50)  
    print(f"用户询问: {query}")  
    print(f"模型回答: {response.content}")  
    if response.tool_calls:  
        print(f"工具调用: {response.tool_calls}")  
    print("="*50 + "\n")  
  
# 测试查询列表  
test_queries = [  
    "中国的首都是哪个城市?",  
    "北京天气怎么样?",  
    "能帮我查看一下 https://example.com/beijing-weather 这个网页的具体内容吗?",  
    "上海有什么好玩的地方?"# 测试未知查询  
]  
  
for query in test_queries:  
    resp = model_with_tools.invoke([HumanMessage(content=query)])  
    print_model_response(resp, query)

在这种模式下,模型会直接返回响应或工具调用请求,但不会自动执行工具并处理结果。这对于理解模型的初始判断很有价值。

代理执行器测试

接下来,我们测试完整的代理执行流程:

# 测试代理执行器  
for query in test_queries:  
    resp = agent_executor.invoke({'messages': [HumanMessage(content=query)]})  
    print("\n代理执行器响应:")  
    print("="*50)  
    print(f"查询: {query}")  
    for msg in resp['messages']:  
        print(f"类型: {msg.type}")  
        print(f"内容: {msg.content}")  
    print("="*50)

在代理执行模式下,系统会自动执行工具调用、处理结果并生成最终响应。这种模式更接近真实的AI助手体验。

可以看到大模型会自动判断是否需要调用Tool,如果需要则会调用。

image-20250227132734101

image-20250227132752086

比如我问:Python 是什么?,他就不会调用工具。

image-20250227133235455

在本文中,我们详细探讨了如何使用LangChain和LangGraph构建一个功能丰富的智能对话系统。从理论基础到实际实现,我们覆盖了构建现代AI助手的各个关键环节。希望本文提供的见解和代码示例能为你的旅程提供帮助和灵感。

完整代码放在这里了>>👉 福利来袭掘金大礼包:《2025最全AI大模型学习资源包》无偿分享,安全可点 👈