lmstdio大模型——本地大模型python函数调用设计
lmstdio大模型——本地大模型python函数调用设计
大模型的函数调用功能是指其能够根据用户需求,通过预定义或动态生成的函数接口与外部工具、API或数据库进行交互的能力。这一功能使模型不仅能生成文本,还能执行复杂任务(如计算、信息检索、数据操作),例如在回答天气查询时调用天气API获取实时数据,或在处理数学问题时激活计算工具。通过解析用户指令中的意图和参数,模型自动选择并触发相应函数,随后将返回结果整合到自然语言回复中,从而突破纯文本生成的限制,实现更高效的自动化服务。这种能力显著扩展了大模型的应用场景,使其在智能助手、数据分析、跨系统集成等场景中具备实际任务处理能力,提升了交互的实用性和准确性。
本文将介绍如何利用lmstdio以及python实现本地大模型的函数调用功能。
Step1:安装lmstdio及模型
lmstdio官网:lmstudio.ai
lmstdio 是一个面向大语言模型(LLM)开发的轻量级工具库,专注于简化模型交互中的输入输出处理、上下文管理及复杂任务调度。其核心目标是通过标准化接口和实用功能,帮助开发者更高效地构建基于大模型的应用程序,尤其是在涉及多轮对话、函数调用和外部工具集成的场景中。
主要功能与特点:
-
输入输出标准化
lmstdio 提供统一的接口规范,可将用户输入的文本或结构化指令(如自然语言命令)自动解析为模型可处理的格式,同时将模型的生成结果(如文本回复或函数调用请求)转换为开发者友好的结构化数据。例如,用户提问“明早北京天气如何?”可被解析为调用天气API的标准化参数。 -
上下文管理
支持对多轮对话上下文的持久化存储和动态更新,通过智能缓存和关键信息提取(如实体识别、意图分类),减少重复计算并提升长对话场景下的连贯性。例如,自动关联前文提到的地点和时间,避免用户反复澄清需求。 -
函数调用支持
深度集成大模型的“工具调用”能力,允许开发者预定义外部函数(如数据库查询、API请求),并自动将模型生成的函数调用请求(如{"function": "get_weather", "params": {"city": "北京"}}
)映射到实际代码执行,最后将结果融入自然语言回复中。这一过程大幅降低了代码开发复杂度。 -
性能优化
通过请求批处理、流式响应和错误重试机制,优化高并发场景下的资源利用率,同时内置提示词(prompt)模板和缓存策略,减少模型调用延迟与成本。
与类似工具(如 LangChain、LlamaIndex)相比,lmstdio 更强调轻量化和灵活性,适合需要快速落地原型或对定制化要求较高的项目。其设计哲学是通过“约定优于配置”降低开发门槛,同时保留扩展性,是中小规模LLM应用的理想选择。
下载好lmstdio安装后,点击搜索图标,即可选择合适的大模型进行安装:
点击文件夹图标可以对模型位置进行管理:
如果遇到模型无法下载的问题,可以尝试更换镜像源,参考以下文章:https://blog.csdn.net/mervyn0318/article/details/144468568
我们使用Q4_K_M量化的gemma-2-9b-it模型进行函数调用的演示设计,该模型大约占用9G显存:
选择聊天图标,点击加载模型即可将模型加载到显存和内存中,此时就可以开始问答了:
由于我们使用python进行调用,因此我们将使用lmstdio的服务器模式。选择开发者图标,点击启动服务器:
利用下方代码,即可检测是否与lmstdio服务连接成功。注意代码中client = OpenAI(base_url="http://127.0.0.1:1234/v1", api_key="lm-studio")
,base_url需要选择lmstdio提供的地址,由于本文为http://127.0.0.1:1234,因此base_url=“http://127.0.0.1:1234/v1”
python需要首先安装openai库,安装命令为pip install openai
,安装完成后即可运行下方代码:
# 在终端中与智能助手聊天
from openai import OpenAI
# 指向本地服务器
client = OpenAI(base_url="http://127.0.0.1:1234/v1", api_key="lm-studio")
# 初始化历史记录列表,其中包含系统和用户的角色和内容
history = [
{"role": "system", "content": "你是一个聪明的助手。你总是提供合理、准确且有帮助的答案。总是用中文简体回答"},
{"role": "user", "content": "什么是python"},
]
# 创建聊天完成请求,指定模型、历史消息和其他参数
completion = client.chat.completions.create(
model="gemma-2-9b-it",
messages=history,
temperature=0.7,
stream=True,
)
# 初始化新消息字典,用于存储助手的回答
new_message = {"role": "assistant", "content": ""}
# 遍历完成请求的结果,并打印内容
for chunk in completion:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
new_message["content"] += chunk.choices[0].delta.content
# 将新消息添加到历史记录列表中
history.append(new_message)
Python是一种解释型、高层次的通用编程语言。它以其清晰易读的语法而闻名,因此非常适合初学者学习。
以下是 Python 的一些关键特点:
* **解释型:** Python 代码不需要事先编译,而是直接在运行时被解释执行。这使得开发和调试更加方便。
* **高层次:** Python 提供了许多内置函数和模块,可以帮助开发者简化代码编写,提高效率。
* **通用:** Python 可以用于多种领域,包括网站开发、数据分析、机器学习、人工智能等。
* **开源:** Python 是免费且开源的,这意味着任何人可以使用、修改和分发它。
* **活跃的社区:** Python 拥有庞大的用户社区,提供丰富的资源和支持。
总而言之,Python 是一种功能强大、易于学习和使用的编程语言,适合各种应用场景。
Step2:函数调用设计
本节将结合lmstdio官方文档:https://lm-studio.cn/docs/api/tools ,进行示例讲解设计,下方是一段函数调用的示例代码。
from datetime import datetime, timedelta
import json
import random
from openai import OpenAI
# 连接lmstdio服务器并选择模型
client = OpenAI(base_url="http://127.0.0.1:1234/v1", api_key="lm-studio") # 连接lmstdio服务器
model = "gemma-2-9b-it" # 选择模型
# 设计需要调用的函数
def get_delivery_date(order_id: str) -> datetime:
# 生成一个随机的交付日期,从今天开始到14天内的任意一天
today = datetime.now()
random_days = random.randint(1, 14)
delivery_date = today + timedelta(days=random_days)
return delivery_date
tools = [
{
"type": "function",
"function": {
"name": "get_delivery_date",
"description": "根据订单号获取预计的交付日期,当顾客询问订单的预计交付日期时,调用此函数,比如当用户问'我的订单什么时候能送达?'时,调用此函数",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "顾客的订单号ID",
},
},
"required": ["order_id"],
"additionalProperties": False,
},
},
}
]
messages = [
{
"role": "system",
"content": "您是一位乐于助人的客户支持助理。使用提供的工具协助用户。",
},
{
"role": "user",
"content": "给我1017号订单的交货日期和时间",
},
]
# LM Studio
response = client.chat.completions.create(
model=model,
messages=messages,
tools=tools,
)
# 获取顾客输入的参数
# 请注意,下方代码假设我们已经确定模型生成了一个函数调用。
tool_call = response.choices[0].message.tool_calls[0]
arguments = json.loads(tool_call.function.arguments)
order_id = arguments.get("order_id")
# 使用提取的order_id调用get_delivery_date函数
delivery_date = get_delivery_date(order_id)
assistant_tool_call_request_message = {
"role": "assistant",
"tool_calls": [
{
"id": response.choices[0].message.tool_calls[0].id,
"type": response.choices[0].message.tool_calls[0].type,
"function": response.choices[0].message.tool_calls[0].function,
}
],
}
# 创建一条包含函数调用结果的消息
function_call_result_message = {
"role": "tool",
"content": json.dumps(
{
"order_id": order_id,
"delivery_date": delivery_date.strftime("%Y-%m-%d %H:%M:%S"),
}
),
"tool_call_id": response.choices[0].message.tool_calls[0].id,
}
# 准备聊天完成回调装载内容,包括历史消息、助手工具调用请求消息和函数调用结果消息
completion_messages_payload = [
messages[0],
messages[1],
assistant_tool_call_request_message,
function_call_result_message,
]
# 调用OpenAI API的聊天完成端点,将工具调用结果发送回模型
response = client.chat.completions.create(
model=model,
messages=completion_messages_payload,
)
print(response.choices[0].message.content, flush=True)
订单1017的预计交货日期是2025年3月1日 下午10点56分。
接下来,我们分步介绍一下函数调用的代码逻辑:
函数定义
def get_delivery_date(order_id: str) -> datetime:
这个函数是我们要调用的函数,它接受一个参数order_id
,并返回一个datetime
对象,表示预计的交付日期。在用户与大模型交互时,我们希望当用户询问订单的预计交付日期时,调用此函数。
工具列表定义
tools = [
{
"type": "function",
"function": {
"name": "get_delivery_date",
"description": "根据订单号获取预计的交付日期,当顾客询问订单的预计交付日期时,调用此函数,比如当用户问'我的订单什么时候能送达?'时,调用此函数",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "顾客的订单号ID",
},
},
"required": ["order_id"],
"additionalProperties": False,
},
},
}
]
tools
列表定义了我们要调用的函数。在这个例子中,我们只定义了一个函数,即get_delivery_date
。- 每个函数都有一个
name
、description
和parameters
字段。parameters
字段定义了函数的参数类型和描述。"required"
字段指定了函数的必需参数。"additionalProperties"
字段指定了函数是否接受额外的参数。
- 该部分在整个函数调用过程中较为重要,大模型需要结合
tools
中的函数描述以及用户的消息内容,判断是否需要调用该函数,当需要调用函数时返回函数调用的参数。
- 每个函数都有一个
对话历史
messages = [
{"role": "system", "content": "您是一位乐于助人的客户支持助理。使用提供的工具协助用户。"},
{"role": "user", "content": "给我1017号订单的交货日期和时间"},
]
这个列表定义了用户和模型的对话历史。在这个例子中,我们只定义了两条消息:
- 一条是用户的消息:“给我1017号订单的交货日期和时间”。
- 另一条是模型的回复(虽然在这里没有直接给出,但会在后续步骤中生成)。
模型调用与响应
response = client.chat.completions.create(model=model, messages=messages, tools=tools)
这行代码调用了OpenAI API的聊天完成端点,将用户的消息和函数列表发送给模型。模型会根据用户的消息和函数列表生成回复。
提取函数调用信息
tool_call = response.choices[0].message.tool_calls[0]
这行代码从模型的回复中提取了函数调用信息。模型的回复中可能包含多个函数调用,我们只取第一个函数调用。
参数转换
arguments = json.loads(tool_call.function.arguments)
这行代码将函数调用的参数从JSON字符串转换为Python字典。函数调用的参数是一个JSON字符串,我们需要将其转换为Python字典,以便提取参数的值。
提取订单ID
order_id = arguments.get("order_id")
这行代码从函数调用的参数中提取了order_id
的值。我们假设函数调用的参数中包含一个名为order_id
的参数,我们使用get
方法从字典中提取该参数的值。
调用函数并获取交付日期
delivery_date = get_delivery_date(order_id)
这行代码调用get_delivery_date
函数,并将order_id
作为参数传递给它。函数会返回一个datetime
对象,表示预计的交付日期。
助手工具调用请求消息
assistant_tool_call_request_message = {
"role": "assistant",
"tool_calls": [
{
"id": response.choices[0].message.tool_calls[0].id,
"type": response.choices[0].message.tool_calls[0].type,
"function": response.choices[0].message.tool_calls[0].function,
}
],
}
这个字典定义了助手工具调用请求消息。这个消息包含了函数调用的信息,包括函数调用的ID、类型和函数。我们使用模型的回复中的函数调用信息来创建这个消息。
函数调用结果消息
function_call_result_message = {
"role": "tool",
"content": json.dumps({
"order_id": order_id,
"delivery_date": delivery_date.strftime("%Y-%m-%d %H:%M:%S"),
}),
"tool_call_id": response.choices[0].message.tool_calls[0].id,
}
这个字典定义了函数调用结果消息。这个消息包含了函数调用的结果,包括order_id
和delivery_date
。我们使用get_delivery_date
函数的返回值来创建这个消息。
聊天完成回调装载内容
completion_messages_payload = [
messages[0],
messages[1],
assistant_tool_call_request_message,
function_call_result_message,
]
这个列表定义了聊天完成回调装载内容。这个列表包含了历史消息、助手工具调用请求消息和函数调用结果消息。我们将这个列表作为参数传递给OpenAI API的聊天完成端点,将函数调用结果发送回模型。
最终模型调用
response = client.chat.completions.create(model=model, messages=completion_messages_payload)
这行代码调用了OpenAI API的聊天完成端点,将函数调用结果发送回模型。模型会根据函数调用结果生成回复。
接下来,我们设计一个更加多样的函数调用,在下方的代码中,我们写了5个函数,分别计算两数相加、相减、相乘、相除以及比较两数大小:
import json
from openai import OpenAI
# 连接lmstdio服务器并选择模型
client = OpenAI(base_url="http://127.0.0.1:1234/v1", api_key="lm-studio") # 连接lmstdio服务器
model = "gemma-2-9b-it" # 选择模型
# 设计需要调用的函数
def add_data(num1: float, num2: float) -> float:
return num1 + num2
def minus_data(num1: float, num2: float) -> float:
return num1 - num2
def multiply_data(num1: float, num2: float) -> float:
return num1 * num2
def divide_data(num1: float, num2: float) -> float:
return num1 / num2
def compare_data(num1: float, num2: float) -> str:
if num1 > num2:
return True
else:
return False
tools = [
{
"type": "function",
"function": {
"name": "add_data",
"description": "将两个数字相加,当用户询问某两个数字相加求结果时,调用此函数",
"parameters": {
"type": "object",
"properties": {
"num1": {
"type": "number",
"description": "第一个数字",
},
"num2": {
"type": "number",
"description": "第二个数字",
},
},
"required": ["num1", "num2"],
"additionalProperties": False,
},
},
},
{
"type": "function",
"function": {
"name": "minus_data",
"description": "将两个数字相减,当用户询问某两个数字相减求结果时,调用此函数",
"parameters": {
"type": "object",
"properties": {
"num1": {
"type": "number",
"description": "第一个数字",
},
"num2": {
"type": "number",
"description": "第二个数字",
},
},
"required": ["num1", "num2"],
"additionalProperties": False,
},
},
},
{
"type": "function",
"function": {
"name": "multiply_data",
"description": "将两个数字相乘,当用户询问某两个数字相乘求结果时,调用此函数",
"parameters": {
"type": "object",
"properties": {
"num1": {
"type": "number",
"description": "第一个数字",
},
"num2": {
"type": "number",
"description": "第二个数字",
},
},
"required": ["num1", "num2"],
"additionalProperties": False,
},
},
},
{
"type": "function",
"function": {
"name": "divide_data",
"description": "将两个数字相除,当用户询问某两个数字相除求结果时,调用此函数",
"parameters": {
"type": "object",
"properties": {
"num1": {
"type": "number",
"description": "第一个数字",
},
"num2": {
"type": "number",
"description": "第二个数字",
},
},
"required": ["num1", "num2"],
"additionalProperties": False,
},
},
},
{
"type": "function",
"function": {
"name": "compare_data",
"description": "将两个数字相比较,当用户询问某两个数字比较大小时,调用此函数,当函数返回True时,表示第一个数字大于第二个数字,否则表示第一个数字小于或等于第二个数字",
"parameters": {
"type": "object",
"properties": {
"num1": {
"type": "number",
"description": "第一个数字",
},
"num2": {
"type": "number",
"description": "第二个数字",
},
},
"required": ["num1", "num2"],
"additionalProperties": False,
},
},
}
]
messages = [
{
"role": "system",
"content": "您是一位乐于助人的客户支持助理。使用提供的工具协助用户。",
},
{
"role": "user",
"content": "8.9和8.11相比较谁更大",
},
]
# LM Studio
response = client.chat.completions.create(
model=model,
messages=messages,
tools=tools,
)
下方函数 process_tool_calls
的设计目的是处理从模型返回的工具调用(tool calls)信息,并执行相应的函数操作:
首先,函数从模型的响应中提取工具调用信息,并将其封装为一个包含工具调用详细信息的字典,随后将该信息添加到消息列表 messages
中。接着,函数遍历每个工具调用,解析其参数并调用相应的函数(如 add_data
、multiply_data
等),将函数执行结果封装为工具调用结果消息,并同样添加到消息列表中。最后,函数将更新后的消息列表发送回模型以获取最终的响应。整个过程实现了对工具调用的解析、执行和结果反馈的完整流程,确保模型能够动态调用外部函数并处理复杂任务。
def process_tool_calls(response, messages):
"""此处我们定义一个函数,用于处理工具调用"""
# 我们首先需要从模型返回的响应中提取出函数调用的信息。
tool_calls = response.choices[0].message.tool_calls
# 创建一个字典,用于存储工具调用的信息
assistant_tool_call_message = {
"role": "assistant",
"tool_calls": [
{
"id": tool_call.id,
"type": tool_call.type,
"function": tool_call.function,
}
for tool_call in tool_calls
],
}
# 将工具调用信息添加到消息列表中
messages.append(assistant_tool_call_message)
# 创建一个空列表,用于存储工具调用的结果
tool_results = []
for tool_call in tool_calls:
# 获取工具调用的参数
arguments = (
json.loads(tool_call.function.arguments)
if tool_call.function.arguments.strip()
else {}
)
# 确定要调用的函数
fun_name = tool_call.function.name
if fun_name == "add_data":
result = add_data(**arguments)
elif fun_name == "multiply_data":
result = multiply_data(**arguments)
elif fun_name == "divide_data":
result = divide_data(**arguments)
elif fun_name == "compare_data":
result = compare_data(**arguments)
else:
result = "未知函数"
# 添加工具调用的结果到工具调用结果列表中
tool_result_message = {
"role": "tool",
"content": json.dumps(result),
"tool_call_id": tool_call.id,
}
tool_results.append(tool_result_message)
messages.append(tool_result_message)
# 得到最终结果
final_response = client.chat.completions.create(
model=model,
messages=messages,
)
return final_response
前文中我们询问到:“8.9和8.11相比较谁更大”,来看看最终的输出结果吧!
final_response = process_tool_calls(response, messages)
print(final_response.choices[0].message.content)
8.9 是更大的数。
在不断实验中我们发现,当询问内容与tools中函数的description描述越接近,越容易触发工具调用,因此建议在询问中尽量出现与description相同或相近的语句,这样会更容易触发工具调用。