news 2026/6/23 10:20:23

基于LangChain实现OpenAI Functions风格Tool Calling智能助手

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于LangChain实现OpenAI Functions风格Tool Calling智能助手

一、先搞懂:OpenAI Functions 核心特点是什么?

很多同学会把Tool Calling和普通对话混淆,其实OpenAI Functions风格的工具调用,有三个不可替代的核心优势,也是我们本次实现的重点:

1. 需求识别自动化

模型能自动识别用户输入中需要调用工具的场景,无需人工指定“该用哪个工具”。比如用户问“现在几点”,模型会自动匹配“时间查询工具”;问“123乘以456是多少”,则自动触发“计算器工具”,完全无需额外的判断逻辑。

2. 工具调用标准化

遵循OpenAI Functions的规范,将自定义工具(如计算器、时间查询)封装成统一格式,模型能快速识别工具的功能、参数要求,避免因工具格式不统一导致的调用失败,降低开发成本。

3. 多轮交互连贯化

结合对话记忆功能,模型能记住上下文交互记录。比如用户先问“现在几点”,再问“30分钟后是几点”,模型会结合上一轮获取的当前时间,调用计算器完成计算,实现连贯的对话体验,而非孤立的单次工具调用。

二、核心实现思路:4步搭建,无复杂依赖

本次实现不依赖第三方API(除本地模型外),核心基于LangChain框架,将“模型初始化、工具定义、提示词构建、Agent封装”四大步骤串联,全程极简,重点突出OpenAI Functions的实现逻辑。

第一步:模型适配——对接兼容OpenAI规范的本地模型

OpenAI Functions的核心是“模型能识别工具描述、生成符合规范的调用指令”,因此我们无需直接使用OpenAI官方模型,只要本地部署的模型兼容OpenAI的工具调用格式(如通义千问、DeepSeek等),即可通过LangChain的ChatOpenAI封装类完成对接。

这一步的关键是配置模型的接口地址、密钥(部分本地模型无需真实密钥)、温度系数和最大生成token数,确保模型输出稳定、符合工具调用规范。

第二步:工具定义——按OpenAI Functions规范封装本地工具

按照OpenAI Functions的要求,我们将自定义工具(计算器、时间查询)封装成“名称+功能+参数描述”的标准格式,让模型能清晰识别工具的用途和调用方式。

比如计算器工具,需要明确说明“用于执行简单数学运算,输入必须是合法的算术表达式”;时间查询工具则说明“无需参数,直接返回当前系统时间”。同时,为了保证安全性,我们还会对工具的输入进行校验(如计算器的表达式白名单),避免恶意代码注入。

第三步:提示词构建——融入对话记忆与工具调用逻辑

提示词是模型触发工具调用的关键,我们需要构建包含“系统提示、对话历史、用户输入、工具调用中间步骤”的完整模板。其中,系统提示明确告知模型“需用中文回答、可自动调用工具”;对话历史占位符用于保存多轮交互记录,实现记忆功能;工具调用中间步骤占位符则用于存储模型调用工具的过程和结果,确保交互连贯。

第四步:Agent封装——整合模型、工具与记忆,实现自动流转

这是实现OpenAI Functions风格工具调用的核心步骤:通过LangChain的create_tool_calling_agent方法,将模型、封装好的工具、提示词模板整合,创建一个能自动触发工具调用的Agent;再搭配对话记忆组件和Agent执行器,实现“用户输入→模型识别→工具调用→结果返回”的全流程自动化,同时限制最大工具调用次数,避免无限循环。

三、实际体验:轻量化交互,适配多种场景

搭建完成后,启动程序即可进行交互式对话,体验完全贴合OpenAI Functions的核心逻辑:

- 基础对话:用户输入“你好”,模型直接返回问候,无需调用工具;

- 工具调用:用户输入“123乘以456是多少”,模型自动调用计算器工具,返回计算结果;输入“现在几点”,自动调用时间查询工具,返回格式化时间;

- 多轮连贯:用户先问“现在几点”,再问“1小时后是几点”,模型结合上一轮的时间结果,调用计算器完成计算,实现连贯交互。

整个过程无需人工干预,模型能自主判断是否需要调用工具、调用哪个工具,完全复刻OpenAI Functions的使用体验,且纯本地运行,无需担心网络延迟和API调用成本。

四、总结与延伸

本次实现的核心价值,在于“用极简方式复刻OpenAI Functions的工具调用逻辑”——无需复杂的代码开发,借助LangChain的封装能力,快速对接本地模型,实现自动化工具调用和多轮对话记忆。

其实,OpenAI Functions的本质不是“调用工具”,而是“让模型学会判断需求、选择工具、处理结果”,这也是大模型从“语言生成”向“实际应用”落地的关键一步。

后续我们还可以基于这个框架,扩展更多工具(如天气查询、文件读取、API调用等),只需按照相同的规范封装工具,即可实现更多场景的自动化处理,真正让大模型成为高效的辅助工具。

代码实现:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

# -*- coding: utf-8 -*-

"""

基于 LangChain 的 Tool Calling 智能助手

- 对接兼容 OpenAI 工具调用格式的本地大模型(qwen3.5-27b-awq)

- 内置工具:数学计算器、获取当前时间(纯本地实现,无外部依赖)

- 支持多轮对话记忆,可连贯交互

- 核心架构:OpenAI 风格函数调用(Tool Calling)Agent

"""

importos

fromdatetimeimportdatetime

fromdotenvimportload_dotenv

# 导入 LangChain 核心组件:工具调用 Agent、执行器

fromlangchain.agentsimportcreate_tool_calling_agent, AgentExecutor

# 导入工具封装类

fromlangchain_core.toolsimportTool

# 导入兼容 OpenAI 接口的聊天模型

fromlangchain_openaiimportChatOpenAI

# 导入提示词模板、对话占位符

fromlangchain.promptsimportChatPromptTemplate, MessagesPlaceholder

# 导入对话记忆组件,用于保存历史对话

fromlangchain.memoryimportConversationBufferMemory

# 加载 .env 文件中的环境变量(当前代码未使用,保留以支持扩展)

load_dotenv()

# ========================

# 1. 初始化大语言模型

# 说明:使用 ChatOpenAI 封装对接本地模型,因本地模型兼容 OpenAI 工具调用协议

# ========================

# 本地模型 API Key(按需替换,部分本地部署无需真实密钥)

DEEPSEEK_API_KEY="123"

llm=ChatOpenAI(

api_key=DEEPSEEK_API_KEY,

base_url="http://192.168.0.100:8085/v1",# 本地模型接口地址

model="qwen3.5-27b-awq",# 模型名称

temperature=0.3,# 温度系数,值越低回答越确定

max_tokens=1024,# 最大生成 token 数

)

# ========================

# 2. 定义本地工具函数

# 工具会被 Agent 自动调用,用于处理模型无法直接完成的任务

# ========================

defcalculate(expression:str)->str:

"""

安全计算简单数学表达式

:param expression: 数学表达式字符串,例如 '2 + 3 * 4'

:return: 计算结果或错误信息

"""

try:

# 白名单校验:仅允许数字、运算符、括号和空格,防止恶意代码执行

allowed=set("0123456789+-*/(). ")

ifnotall(cinallowedforcinexpression):

return"错误:表达式包含非法字符"

# 安全执行表达式,禁用内置函数防止代码注入

result=eval(expression, {"__builtins__": {}}, {})

returnstr(result)

exceptException as e:

returnf"计算失败: {str(e)}"

defget_current_time(dummy:str="")->str:

"""

获取当前系统日期和时间

:param dummy: 占位参数,适配工具调用格式

:return: 格式化的当前时间字符串

"""

returndatetime.now().strftime("%Y年%m%d %H:%M:%S")

# 将自定义函数注册为 LangChain 标准工具

tools=[

Tool(

name="calculate",

func=calculate,

description="执行简单数学运算。输入必须是合法的算术表达式字符串,如 '15 * 6'。",

),

Tool(

name="get_current_time",

func=get_current_time,

description="获取当前系统时间。无需传入实际参数。",

),

]

# ========================

# 3. 构建提示词模板

# 包含系统提示、对话历史、用户输入、工具调用中间步骤

# ========================

prompt=ChatPromptTemplate.from_messages([

("system","你是一个智能助手,能调用工具完成用户请求。请始终用中文回答。"),

MessagesPlaceholder("chat_history"),# 对话历史占位符,实现多轮记忆

("human","{input}"),# 用户当前输入占位符

MessagesPlaceholder("agent_scratchpad"),# 工具调用过程占位符,Agent 内部使用

])

# ========================

# 4. 创建 Tool Calling Agent 及执行器

# ========================

# 创建支持工具调用的 Agent(OpenAI 函数调用格式)

agent=create_tool_calling_agent(llm, tools, prompt)

# 初始化对话记忆:存储聊天历史,保持多轮交互连贯性

memory=ConversationBufferMemory(

memory_key="chat_history",# 与提示词中占位符名称一致

return_messages=True,# 返回消息对象而非字符串,保证格式兼容

)

# 创建 Agent 执行器:负责运行 Agent、调用工具、管理记忆

agent_executor=AgentExecutor(

agent=agent,

tools=tools,

memory=memory,

verbose=True,# 打印详细执行过程,方便调试

handle_parsing_errors=True,# 自动处理解析错误,提升稳定性

max_iterations=5,# 最大工具调用次数,防止无限循环

)

# ========================

# 5. 启动交互式对话

# 循环接收用户输入,调用 Agent 并输出回答

# ========================

if__name__=="__main__":

print("🤖 本地模型 Tool Calling 助手启动!")

print("💡 示例:你好、现在几点?、123 乘以 456 等于多少?")

print("🛑 输入 quit / exit 可退出程序。\n")

whileTrue:

user_input=input("👤 你: ").strip()

# 退出条件判断

ifnotuser_inputoruser_input.lower()in["quit","exit"]:

print("👋 再见!")

break

try:

# 调用 Agent 执行器获取回答

response=agent_executor.invoke({"input": user_input})

# 打印原始响应(调试用)

print("原始模型响应:", response)

# 输出最终回答

print(f"🤖 助手: {response['output']}\n")

exceptException as e:

print(f"❌ 错误: {e}\n")

对话内容:

🤖 本地模型 Tool Calling 助手启动!
💡 示例:你好、现在几点?、123 乘以 456 等于多少?
🛑 输入 quit / exit 可退出程序。

👤 你: 你好


> Entering new AgentExecutor chain...


你好!我是你的智能助手,很高兴为你服务。

我可以帮助你:
- 进行数学计算
- 获取当前时间
- 回答各种问题
- 完成其他任务

请问有什么我可以帮你的吗?

> Finished chain.
原始模型响应: {'input': '你好', 'chat_history': [HumanMessage(content='你好'), AIMessage(content='\n\n你好!我是你的智能助手,很高兴为你服务。\n\n我可以帮助你:\n- 进行数学计算\n- 获取当前时间\n- 回答各种问题\n- 完成其他任务\n\n请问有什么我可以帮你的吗?')], 'output': '\n\n你好!我是你的智能助手,很高兴为你服务。\n\n我可以帮助你:\n- 进行数学计算\n- 获取当前时间\n- 回答各种问题\n- 完成其他任务\n\n请问有什么我可以帮你的吗?'}
🤖 助手:

你好!我是你的智能助手,很高兴为你服务。

我可以帮助你:
- 进行数学计算
- 获取当前时间
- 回答各种问题
- 完成其他任务

请问有什么我可以帮你的吗?

👤 你: 现在几点?


> Entering new AgentExecutor chain...

Invoking: `get_current_time` with ``
responded:



2026年0413 10:31:00

现在是 2026 年 4 月 13 日 10:31:00。

> Finished chain.
原始模型响应: {'input': '现在几点?', 'chat_history': [HumanMessage(content='你好'), AIMessage(content='\n\n你好!我是你的智能助手,很高兴为你服务。\n\n我可以帮助你:\n- 进行数学计算\n- 获取当前时间\n- 回答各种问题\n- 完成其他任务\n\n请问有什么我可以帮你的吗?'), HumanMessage(content='现在几点?'), AIMessage(content='\n\n现在是 2026 年 4 月 13 日 10:31:00。')], 'output': '\n\n现在是 2026 年 4 月 13 日 10:31:00。'}
🤖 助手:

现在是 2026 年 4 月 13 日 10:31:00。

👤 你: 123 乘以 456 等于多少?


> Entering new AgentExecutor chain...

Invoking: `calculate` with `123 * 456`
responded:



56088

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/23 10:17:22

Fourtune_ML_CTF_Challenge

搭建慢就用我的DockerfileFROM python:3.11-slimWORKDIR /appRUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*RUN pip install --no-cache-dir --trusted-host mirrors.aliyun.com \-i htt…

作者头像 李华
网站建设 2026/6/23 10:12:50

【置顶干货】博主介绍,各类系统源码领取途径

文章目录关于我们项目技术支持获取博主联系方式关于我们 博主本身从事开发软件开发、有丰富的编程能力和水平、累积给上千名同学进行辅导、有自己的独立工作室,目前只专注做自己专业领域的事。团队人员有多年架构师设计经验、多人有参加校企合作经验,被…

作者头像 李华
网站建设 2026/6/23 10:04:36

凸松弛紧密度分析:割多面体、度量多面体与椭球体的体积比较

1. 项目概述:从“松弛”到“紧密度”的优化博弈在算法设计与理论计算机科学领域,我们常常需要求解一些“难啃的骨头”——NP难问题。直接求解最优解往往计算上不可行,于是“松弛”技术应运而生。它好比给一个形状不规则、难以直接测量的物体&…

作者头像 李华
网站建设 2026/6/23 9:59:21

React Navigation 核心原理与工程实践指南

1. 为什么在 React Native 里“路由”不是加个 <Router> 就完事了&#xff1f; 刚从 Web 端转来 React Native 的人&#xff0c;第一反应往往是&#xff1a;“React Router 那套我熟啊&#xff0c; <BrowserRouter> <Route> 一配&#xff0c;页面跳转…

作者头像 李华
网站建设 2026/6/23 9:56:02

移动设备远程控制风险剖析与防御实战:从漏洞利用到企业安全管控

1. 项目概述&#xff1a;一次关于移动设备安全边界的深度探讨最近在和一些做移动应用开发和安全研究的朋友交流时&#xff0c;大家不约而同地提到了一个现象&#xff1a;随着移动办公和BYOD&#xff08;自带设备办公&#xff09;的普及&#xff0c;个人手机与公司数据的边界越来…

作者头像 李华
网站建设 2026/6/23 9:49:49

JavaScript错误处理三界:哪些能catch,哪些必须绕过

1. 为什么你写的 try...catch 总是“没用”&#xff1f;——从报错消失到错误追踪失效的真相JavaScript 的try...catch是每个前端开发者入职第一天就被塞进脑子的语法糖&#xff0c;但也是最常被误用、最常被忽视、最常在生产环境里“假装工作”的错误处理机制。我带过二十多个…

作者头像 李华