尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从工具调用到智能体:基于LangChain构建本地AI助手的完整指南

从工具调用到智能体:基于LangChain构建本地AI助手的完整指南 在当今AI技术快速发展的浪潮中我们见证了语言模型从单纯的文本生成器逐渐演变为能够理解、规划并执行复杂任务的“智能体”。你是否曾好奇像ChatGPT这样的模型是如何做到调用计算器、查询天气甚至帮你订机票的这背后正是“语言模型使用工具”这一核心能力的体现。从简单的工具调用到自主规划执行的智能体这条技术路径正成为AI应用落地的关键。本文将系统性地拆解语言模型使用工具的原理、方法与工程实践并深入探讨其如何迈向更高级的“智能体”形态。无论你是希望为现有应用添加AI能力的产品经理还是致力于构建下一代AI应用的开发者或是想深入理解AI前沿技术的研究者都能从本文获得一套从理论到实战的完整指南。我们将从基础概念讲起通过代码示例一步步构建一个能使用工具的AI助手并最终探讨智能体的架构与未来。1. 背景与核心概念从工具调用到智能体在深入技术细节之前我们有必要厘清几个核心概念理解它们之间的关系和演进路径。1.1 什么是语言模型使用工具简单来说语言模型使用工具是指模型在生成文本的过程中能够识别出自身无法直接完成或需要外部信息/能力支持的任务并主动发起对特定工具如API、函数、数据库的调用然后将工具返回的结果整合到后续的文本生成中。核心价值突破了语言模型“知识截止日期”和“纯文本生成”的限制。模型不再需要“记住”所有知识而是学会了“使用”外部资源。类比就像一个聪明的助手它自己不会算数但知道在需要计算时去使用计算器自己不知道实时天气但知道去调用天气API查询。1.2 什么是智能体智能体是一个更高级、更自主的概念。一个AI智能体通常具备以下特征感知理解用户指令和环境状态包括工具调用结果。规划将复杂目标分解为一系列可执行的子任务或步骤。行动执行子任务通常表现为调用一个或多个工具。反思评估行动结果判断目标是否达成或是否需要调整计划。因此工具调用是智能体“行动”环节的核心能力。一个能使用工具的模型是构建智能体的基础而一个完整的智能体则在工具调用的基础上增加了自主规划、记忆和反思等高级认知能力。1.3 关键组件与工作流程一个典型的“语言模型使用工具”系统包含以下组件语言模型作为核心“大脑”负责理解、规划和决策。工具定义以结构化格式如函数签名、OpenAPI规范描述每个工具的功能、输入参数和输出格式。工具执行器负责在模型决定调用工具后实际执行对应的代码或API请求。对话/任务管理维护与模型的交互历史管理多轮对话的上下文。其通用工作流程如下图所示以文字描述流程替代图表用户输入用户提出一个需要工具才能完成的请求如“计算一下3567乘以1289等于多少”模型推理语言模型分析请求判断需要调用“计算器”工具并生成结构化的调用请求如{tool: calculator, input: 3567 * 1289}。工具执行系统拦截模型的输出识别出工具调用请求并将请求转发给对应的工具执行器进行计算。结果整合工具执行器返回计算结果如4599663系统将此结果作为新的上下文提供给语言模型。模型回复语言模型结合工具返回的结果生成最终的自然语言回复给用户“3567乘以1289等于4,599,663。”2. 环境准备与版本说明我们将使用Python生态中最流行的框架之一——LangChain和LangGraph来构建示例。它们提供了丰富的工具集成和智能体编排能力。同时我们会使用Ollama来本地运行开源大语言模型确保整个过程可复现、无网络依赖。环境要求操作系统Windows 10/11, macOS, 或 Linux (Ubuntu 20.04)Python版本 3.8 或更高 (推荐 3.10)包管理pip核心依赖库及版本建议使用虚拟环境# 创建并激活虚拟环境 (以conda为例) conda create -n lm-agent python3.10 conda activate lm-agent # 安装核心库 pip install langchain langchain-community langgraph pip install ollama # 用于本地运行模型 pip install duckduckgo-search # 用于网页搜索工具示例 pip install sympy # 用于数学计算工具示例版本说明langchain和langgraph版本迭代较快本文示例基于langchain0.1.0和langgraph0.0.20的通用API编写核心概念稳定。ollama需要单独安装并启动服务。请前往 Ollama官网 下载对应操作系统的安装包安装后运行ollama run llama3.2来拉取并运行一个基础模型如Llama 3.2确保服务在后台运行默认地址http://localhost:11434。3. 核心原理与架构拆解理解工具调用的原理是构建稳定智能体系统的前提。本节将深入两个核心机制函数调用和ReAct范式。3.1 函数调用工具使用的基石现代大语言模型如GPT-4, Claude, Llama 3.2普遍支持“函数调用”或“工具调用”功能。其本质是让模型输出一种结构化的数据格式通常是JSON而非纯文本。关键步骤工具描述开发者以JSON Schema的形式向模型描述可用的工具。例如描述一个“获取天气”的工具{ name: get_current_weather, description: 获取指定城市的当前天气, parameters: { type: object, properties: { location: { type: string, description: 城市名例如北京San Francisco } }, required: [location] } }模型决策用户提问“上海天气怎么样”。模型结合工具描述判断需要调用get_current_weather并生成符合参数要求的JSON{ tool: get_current_weather, input: {location: 上海} }系统路由与执行应用程序解析这个JSON找到对应的函数get_current_weather(location上海)并执行获得真实的天气数据。回复生成将天气数据如{temperature: 22, condition: 晴}返回给模型模型生成最终回复“上海目前天气晴朗气温22摄氏度。”3.2 ReAct范式思维链与行动的融合ReActReason Act是推动语言模型向智能体演进的关键范式。它鼓励模型将思考过程Reasoning和行动步骤Acting以交错的形式表达出来。一个典型的ReAct格式交互用户 姚明和爱因斯坦谁更高为什么 助手 我需要比较两个人的身高。我知道姚明是著名的篮球运动员身高应该很高。爱因斯坦是物理学家身高信息我不确定。我需要查找两人的具体身高信息。 行动 我将使用搜索工具。 工具调用 search(query姚明 身高) 工具结果 姚明身高2.26米。 助手 好的姚明身高2.26米。现在需要爱因斯坦的身高。 工具调用 search(query爱因斯坦 身高) 工具结果 阿尔伯特·爱因斯坦身高约为1.75米。 助手 现在比较一下姚明2.26米爱因斯坦1.75米。显然姚明更高。因为姚明是职业篮球运动员而爱因斯坦是理论物理学家身高属于普通范围。在这个例子中模型的“内部思考”如“我需要比较...”、“我需要查找...”和“外部行动”调用搜索工具被显式地记录在对话历史中。这使得调试更容易开发者可以清晰看到模型的决策过程。可靠性更高模型通过“思考”来规划如何使用工具减少了盲目调用。支持复杂任务通过多轮“思考-行动”的循环可以完成需要多个步骤的任务。LangChain和LangGraph等框架的核心工作之一就是帮助开发者便捷地实现ReAct这类交互流程。4. 完整实战构建一个能使用工具的本地AI助手现在让我们动手构建一个能使用搜索和计算工具的本地AI助手。我们将使用Ollama本地运行的llama3.2模型以及LangChain框架。4.1 项目结构与初始化创建一个新的项目目录并初始化关键文件。mkdir local_ai_assistant cd local_ai_assistant touch assistant.py4.2 定义工具在assistant.py中我们首先定义两个简单的工具一个用于计算数学表达式一个用于搜索网络信息。# assistant.py from langchain.tools import tool from duckduckgo_search import DDGS import sympy # 工具1计算器 tool def calculator(expression: str) - str: 计算一个数学表达式的值。支持加减乘除和乘方。例如(3 5) * 2 try: # 使用sympy进行安全计算避免eval的安全风险 result sympy.sympify(expression) return f计算结果: {result} except Exception as e: return f计算错误: {e} # 工具2网页搜索 tool def search_web(query: str) - str: 使用DuckDuckGo搜索网络信息。输入是一个搜索关键词。 try: with DDGS() as ddgs: # 获取最相关的几条结果 results list(ddgs.text(query, max_results3)) if not results: return 未找到相关信息。 # 拼接结果摘要 summary \n.join([f{i1}. {r[body][:150]}... for i, r in enumerate(results)]) return f搜索 {query} 的结果摘要\n{summary} except Exception as e: return f搜索过程中出错: {e} # 将工具放入列表供后续使用 tools [calculator, search_web]4.3 连接语言模型并创建智能体接下来我们连接本地的Ollama模型并利用LangChain的“工具调用”能力创建一个智能体。# assistant.py (续) from langchain_community.chat_models import ChatOllama from langchain.agents import create_react_agent, AgentExecutor from langchain import hub # 1. 连接到本地Ollama服务使用llama3.2模型 llm ChatOllama( modelllama3.2, # 确保你已通过 ollama run llama3.2 拉取该模型 base_urlhttp://localhost:11434, temperature0, # 降低随机性使工具调用更稳定 ) # 2. 从LangChain Hub拉取一个预设的ReAct提示词模板 # 这个模板会指导模型按照“思考-行动-观察”的格式进行输出 prompt hub.pull(hwchase17/react) # 3. 创建ReAct智能体 agent create_react_agent(llm, tools, prompt) # 4. 创建代理执行器它负责解析模型的输出、调用工具、管理对话历史 agent_executor AgentExecutor( agentagent, toolstools, verboseTrue, # 设置为True可以看到详细的执行步骤便于调试 handle_parsing_errorsTrue, # 处理模型输出解析错误 max_iterations5, # 限制最大迭代次数防止死循环 )4.4 运行与测试现在我们可以编写一个简单的对话循环来测试我们的智能体。# assistant.py (续) def run_assistant(): print(本地AI助手已启动 (输入 quit 退出)) print(- * 50) while True: try: user_input input(\n您: ) if user_input.lower() in [quit, exit, q]: print(助手: 再见) break if not user_input.strip(): continue # 执行智能体 response agent_executor.invoke({input: user_input}) print(f\n助手: {response[output]}) except KeyboardInterrupt: print(\n\n程序被中断。) break except Exception as e: print(f\n系统出错: {e}) if __name__ __main__: run_assistant()4.5 运行示例与结果分析保存文件并在终端运行python assistant.py你会看到类似以下的交互过程verboseTrue会打印详细日志您: 圆周率乘以10的平方等于多少 [日志] 进入新的代理执行... [思考] 用户问的是圆周率乘以10的平方。我需要先计算10的平方再乘以圆周率。我可以使用计算器工具。 [行动] 调用工具calculator 工具输入{expression: 10**2} [观察] 计算结果: 100 [思考] 10的平方是100。现在需要计算圆周率乘以100。圆周率约等于3.14159。 [行动] 调用工具calculator 工具输入{expression: 3.14159 * 100} [观察] 计算结果: 314.159 [思考] 计算完成。圆周率乘以10的平方约等于314.159。 [最终回答] 圆周率乘以10的平方约等于314.159。 助手: 圆周率乘以10的平方约等于314.159。您: 帮我搜索一下LangChain的最新版本是什么 [思考] 用户想了解LangChain的最新版本信息。这是一个需要最新网络数据的问题我应该使用搜索工具。 [行动] 调用工具search_web 工具输入{query: LangChain latest version 2024} [观察] 搜索 LangChain latest version 2024 的结果摘要 1. LangChain官方文档显示当前稳定版本为0.1.x... 最新版本信息可以在PyPI或GitHub发布页面查看... 2. 根据PyPI记录LangChain 0.1.10 于2024年X月发布... [思考] 根据搜索结果LangChain的最新稳定版本是0.1.x系列具体如0.1.10。我应该把这个信息告诉用户。 [最终回答] 根据网络搜索LangChain的最新稳定版本是0.1.x系列例如0.1.10。建议您访问PyPI或LangChain的GitHub发布页面获取最精确的版本信息。 助手: 根据网络搜索LangChain的最新稳定版本是0.1.x系列例如0.1.10。建议您访问PyPI或LangChain的GitHub发布页面获取最精确的版本信息。结果分析成功调用工具模型正确识别了需要计算和搜索的场景。遵循ReAct流程模型展示了“思考-行动-观察”的完整链条决策过程透明。处理多步任务在计算示例中模型自动将复杂计算分解为两步先算平方再乘法。本地运行整个过程完全在本地进行无需调用OpenAI等云端API保证了隐私和可控性。5. 进阶使用LangGraph构建具备记忆与状态管理的智能体基础的智能体可以处理单轮任务。但对于多轮对话、需要长期记忆或复杂工作流的场景我们需要更强大的编排能力。LangGraph应运而生它允许我们将智能体定义为“状态机”或“图”节点是处理步骤如调用模型、执行工具边是状态流转的条件。下面我们构建一个更强大的智能体它具备对话记忆并能根据上下文决定是直接聊天、使用工具还是结束对话。5.1 定义智能体状态与节点# graph_agent.py from typing import TypedDict, Annotated, List import operator from langgraph.graph import StateGraph, END from langchain_core.messages import HumanMessage, AIMessage, ToolMessage from langchain_community.chat_models import ChatOllama from langchain.tools import tool from duckduckgo_search import DDGS # 1. 定义状态结构包含消息历史和我们关心的其他状态 class AgentState(TypedDict): messages: Annotated[List, operator.add] # 对话消息历史 next_step: str # 决定下一步做什么 # 2. 定义工具复用之前的工具 tool def search_web(query: str) - str: with DDGS() as ddgs: results list(ddgs.text(query, max_results2)) return \n.join([r[body][:200] for r in results]) if results else 无结果 tools [search_web] llm ChatOllama(modelllama3.2, temperature0).bind_tools(tools) # 3. 定义图节点 def should_use_tool(state: AgentState) - str: 决策节点根据最新用户消息决定下一步是调用工具还是直接回复。 last_message state[messages][-1] if isinstance(last_message, HumanMessage): # 简单的启发式规则如果消息包含“搜索”或“查一下”则用工具 if any(keyword in last_message.content.lower() for keyword in [搜索, 查找, 查一下, 谁知道]): return use_tool # 否则直接让模型回复 return direct_reply def call_model_directly(state: AgentState) - dict: 直接回复节点让模型基于对话历史生成回复不强制使用工具。 response llm.invoke(state[messages]) return {messages: [response], next_step: end} def call_tool(state: AgentState) - dict: 工具调用节点强制模型选择并调用一个工具。 # 只将最后一条用户消息传给模型让其决定调用哪个工具 ai_msg_for_tool llm.invoke([state[messages][-1]]) tool_calls ai_msg_for_tool.tool_calls if not tool_calls: # 模型没选择工具返回一个提示信息 return {messages: [AIMessage(content我决定不使用工具来回答。)], next_step: end} # 执行工具调用 tool_messages [] for tool_call in tool_calls: tool_name tool_call[name] tool_input tool_call[args] # 找到对应的工具函数并执行 tool_func next((t for t in tools if t.name tool_name), None) if tool_func: result tool_func.invoke(tool_input) tool_messages.append(ToolMessage(contentstr(result), tool_call_idtool_call[id])) else: tool_messages.append(ToolMessage(contentf工具 {tool_name} 未找到。, tool_call_idtool_call[id])) # 将工具执行结果返回给模型让它生成最终回复 new_messages state[messages] [ai_msg_for_tool] tool_messages final_response llm.invoke(new_messages) return {messages: [final_response], next_step: end}5.2 构建并运行图# graph_agent.py (续) # 4. 构建图 workflow StateGraph(AgentState) # 添加节点 workflow.add_node(router, should_use_tool) # 决策路由节点 workflow.add_node(direct_reply, call_model_directly) workflow.add_node(use_tool, call_tool) # 设置入口点 workflow.set_entry_point(router) # 根据决策路由到不同节点 workflow.add_conditional_edges( router, lambda x: x[next_step] if next_step in x else direct_reply, # 读取决策结果 { use_tool: use_tool, direct_reply: direct_reply, } ) workflow.add_edge(direct_reply, END) workflow.add_edge(use_tool, END) # 编译图 app workflow.compile() # 5. 运行测试 if __name__ __main__: # 测试1直接聊天 print(测试1: 直接聊天) initial_state {messages: [HumanMessage(content你好请介绍一下你自己。)], next_step: } result app.invoke(initial_state) print(f助手: {result[messages][-1].content}\n) # 测试2需要工具的查询 print(测试2: 需要工具的查询) initial_state {messages: [HumanMessage(content搜索一下今天北京的重大新闻。)], next_step: } result app.invoke(initial_state) print(f助手: {result[messages][-1].content})运行python graph_agent.py你会看到智能体根据问题类型自动选择了不同的处理路径。LangGraph的强大之处在于你可以设计更复杂的图例如包含循环让模型根据工具结果决定是否再次搜索、并行执行多个工具、集成长期记忆数据库等从而构建出真正强大、可定制的智能体应用。6. 常见问题与排查思路在开发语言模型工具调用和智能体时你可能会遇到以下典型问题。问题现象常见原因解决思路模型不调用工具总是直接回答1. 工具描述不清晰。2. 模型能力不足特别是小参数模型。3. 提示词Prompt未引导模型使用工具。4. 温度temperature参数过高导致输出随机。1. 优化工具描述确保description字段准确说明功能和适用场景。2. 尝试更强大的模型如GPT-4、Claude 3、Llama 3 70B。3. 使用LangChain的create_react_agent或类似框架它们内置了强引导性的提示词。4. 将temperature设为0或接近0的值增加确定性。工具调用参数错误或格式不对1. 模型生成的参数JSON不符合工具函数定义的参数类型。2. 工具函数的参数名或类型与描述不匹配。1. 在代码中添加解析错误处理如handle_parsing_errorsTrue。2. 确保工具装饰器tool中的函数签名和文档字符串清晰。可以使用Pydantic模型来严格定义参数。多轮对话中上下文丢失1. 未将完整的对话历史包括工具调用和结果传递给模型。2. 上下文长度超过模型限制历史消息被截断。1. 使用AgentExecutor或LangGraph的状态管理它们会自动维护消息历史。2. 实现一个“摘要”或“滑动窗口”机制将过长的历史压缩只保留关键信息。智能体陷入死循环或无效调用1. 任务无法通过现有工具完成模型反复尝试。2. 工具返回的结果无法让模型推导出答案。1. 设置max_iterations最大迭代次数限制。2. 在提示词中明确告知模型“如果你无法通过现有工具解决问题请直接告知用户”。3. 优化工具设计确保其功能明确、返回结果格式利于模型理解。本地模型响应慢1. 模型参数过大硬件资源不足。2. Ollama服务未优化。1. 尝试更小的量化模型如llama3.2:3b,qwen2.5:7b。2. 确保为Ollama分配了足够的GPU资源如果可用。3. 考虑使用性能更好的推理后端如vLLM。7. 最佳实践与工程建议将语言模型与工具结合并投入生产环境需要遵循一系列工程最佳实践。7.1 工具设计原则单一职责每个工具应只做一件事并做好。避免设计“万能”工具。描述清晰工具的name和description是模型理解它的唯一途径务必用自然语言准确描述其功能和输入格式。防御性编程工具函数内部必须进行严格的输入验证和异常处理避免因模型生成错误参数导致系统崩溃。结果格式化工具返回的结果应简洁、结构化便于模型提取信息。避免返回冗长的HTML或复杂JSON。7.2 提示词工程明确系统指令在提示词开头明确告知模型它的角色、可用工具以及调用工具的格式。提供少量示例在系统指令中包含1-2个工具调用的示例Few-Shot Learning能极大提高模型使用工具的准确性。引导思考过程使用类似ReAct的提示词模板鼓励模型“一步一步思考”这能显著提升复杂任务的成功率。7.3 系统安全与稳定性工具权限控制不是所有工具都应被所有用户或所有问题调用。实现基于用户、会话或上下文的工具权限过滤。输入输出过滤对模型接收的用户输入和工具返回的内容进行安全检查防止提示词注入、敏感信息泄露或执行恶意代码。设置超时与重试对模型调用和工具调用设置超时并实现合理的重试机制特别是对于网络API工具。完备的日志与监控记录完整的“思考-行动”链条这对于调试、优化和审计至关重要。监控工具调用频率、耗时和错误率。7.4 面向智能体的架构思考状态外置智能体的状态记忆、目标、中间结果应存储在外部数据库如Redis、PostgreSQL中而非仅保存在内存里以实现持久化和水平扩展。模块化设计将“规划器”、“工具集”、“记忆模块”、“执行器”等组件解耦便于单独升级和测试。人机协同设计“人工审核”或“人工接管”节点对于高风险操作如发送邮件、支付让智能体先提出计划经用户确认后再执行。从让语言模型学会使用一个简单的计算器到构建一个可以自主规划、使用多种工具、具备记忆的智能体这条路径正在迅速从研究走向工程实践。本文通过概念梳理、原理分析、实战代码和工程建议为你提供了从入门到进阶的完整路线图。技术的核心在于理解“工具调用”作为连接LLM数字世界与现实世界的桥梁作用而“智能体”则是赋予这座桥梁以方向和目的性的驾驶舱。真正的挑战不在于实现单个功能而在于设计出稳定、安全、可扩展的智能体系统。建议你从本文的本地示例出发逐步尝试集成更复杂的工具数据库、企业内部API利用LangGraph设计更精细的工作流并最终思考如何将其应用于你的具体业务场景中解决真实世界的问题。
返回列表