Ollama 是一个优秀的本地部署与管理大模型的框架。通过 Ollama,我们可以在本地部署、定制自己的大模型服务。Ollama-python:定义工具集,增强大模型的能力介绍了如何通过调用工具,让大模型准确地回答数字运算相关的问题(大模型本身一般不擅长回答较长的数字的数学运算问题)。本文在前文的基础上,初步搭建数学运算智能问答应用。该智能应用,具有以下功能:
- 定义一系列工具集(数学运算函数):工具可以辅助大模型回答数学运算相关的问题。
- 大模型自主决定是否使用工具:用户以自然语言的形式,提出问题。大模型自行判断是否要调用工具,根据工具调用结果,增强回答用户的问题的能力。
- 支持多轮对话:大模型根据对话的上下文进行回答问题(不仅仅是针对用户当前提出的问题)。
使用以下提问示例,验证数学运算智能问答应用的功能:
-
- 现在要进一批货,单价是35.324,数量是765345677,商家优惠 1500 元,一共要付多少钱?
-
- 后来商家又说,可以在上面的基础上,优惠两个点,最终要付多少钱?
-
- 商家后来又说,再打 9.4523 折,太好了。最终要付多少钱?
用户、助手、工具
-
- 用户(user,人类)向助手提出问题。
-
- 助手(assistant)即大模型,助手根据用户的问题自主思考,判断是否要调用工具。
-
- 助手调用工具(tool),工具完成执行后,返回执行结果给助手。
-
- 助手继续自主思考,判断是否继续调用工具。循环执行步骤 2、3,直到助手判断不需要调用工具。
-
- 助手根据工具执行结果,思考。针对用户提出的问题,返回回答。
-
- 用户根据助手的回答情况,继续追问(再次提问),实现多轮对话交互。
在本文的应用示例中,助手为 qwen2.5:7b 模型,工具为数学运算函数(加减乘除)。通过 Ollama,在本地部署运行助手( qwen2.5:7b 模型)。基于 Ollama 的 python sdk,完成上述交互流程步骤。
代码实现
工具定义
- 定义了四个基本的数学运算函数:加法、减法、乘法和除法。
- 每个函数都带有类型注解和文档字符串。Ollama 可根据函数定义生成 JSON Schema。
- 除了直接传递函数,还可以手动定义工具的 JSON Schema(如 subtract_two_numbers_tool)。
from ollama import ChatResponse
from ollama import Client
def add_two_numbers(a: float, b: float) -> float:
"""
Add two numbers (including decimals)
Args:
a (float): The first number
b (float): The second number
Returns:
float: The sum of the two numbers
"""
return a + b
def multiply_two_numbers(a: float, b: float) -> float:
"""
Multiply two numbers (including decimals)
Args:
a (float): The first number
b (float): The second number
Returns:
float: The product of the two numbers
"""
return a * b
def divide_two_numbers(a: float, b: float) -> float:
"""
Divide one number by another (including decimals)
Args:
a (float): The numerator
b (float): The denominator
Returns:
float: The quotient of a divided by b
"""
if b == 0:
raise ZeroDivisionError("Division by zero is not allowed.")
return a / b
def subtract_two_numbers(a: float, b: float) -> float:
"""
Subtract two numbers
"""
return a - b
# Tools can still be manually defined and passed into chat
subtract_two_numbers_tool = {
'type': 'function',
'function': {
'name': 'subtract_two_numbers',
'description': 'Subtract two numbers',
'parameters': {
'type': 'object',
'required': ['a', 'b'],
'properties': {
'a': {'type': 'float', 'description': 'The first number'},
'b': {'type': 'float', 'description': 'The second number'},
},
},
},
}
# 定义可用的函数工具
available_functions = {
'add_two_numbers': add_two_numbers,
'subtract_two_numbers': subtract_two_numbers,
'multiply_two_numbers': multiply_two_numbers,
'divide_two_numbers': divide_two_numbers
}
# 工具列表,作为对话的上下文传递给助手(大模型)
tool_list = [add_two_numbers, subtract_two_numbers_tool, multiply_two_numbers, divide_two_numbers]
工具调用
-
工具调用检测与处理
- 检查助手响应中是否包含工具调用(response.message.tool_calls)。
- 如果存在工具调用,从 available_functions 中查找对应的工具函数并执行。
- 将工具调用的结果整合到会话历史中,以便助手参考。
-
工具调用验证:如果工具不存在,打印日志通知用户,提示找不到工具。
-
会话逻辑管理
- 将助手响应和工具调用结果添加到会话历史(messages)中。
- 在工具调用结束后,返回最终的工具输出结果。
def call_tools(response, messages):
final_tool_output = None
output = None
is_tool_call = False
if response.message.tool_calls:
is_tool_call = True
print('--------------开始工具调用-----------------')
while response.message.tool_calls:
for tool in response.message.tool_calls:
if function_to_call := available_functions.get(tool.function.name):
print('调用工具:', tool.function.name)
print('参数:', tool.function.arguments)
output = function_to_call(**tool.function.arguments)
final_tool_output = output
print('工具输出:', output)
else:
print('找不到工具: ', tool.function.name)
messages.append(response.message)
messages.append({'role': 'tool', 'content': str(output), 'name': tool.function.name})
response = client.chat(model=model_name, messages=messages, tools=tool_list)
if is_tool_call:
print('--------------工具调用结束-----------------')
return final_tool_output
多轮对话交互
- 连续对话循环
- 使用 while True 实现用户和助手的连续对话。
- 当用户输入特定的退出指令(如 ‘退出’、‘quit’ 等)时,程序退出。
- 用户提问
- 用户的输入通过 input() 获取,并添加到 messages 中,标记为 role: ‘user’。
- 助手响应和工具调用
- 调用 client.chat(),传入模型名称、会话历史和工具列表。
- 助手的响应(response)可能包含工具调用,通过 call_tools() 处理这些工具调用。
- 工具调用结果处理
- 如果工具调用返回结果(final_solve_output),将其添加到会话历史,标记为 role: ‘tool’。这里强调多次工具执行的最终结果,降低助手响应的幻觉可能性。
- 助手的流式响应处理
- 使用 stream=True 参数,逐块获取助手的流式响应。
client = Client()
model_name = 'qwen2.5:7b'
messages = []
def chat_with_ollama():
print("计算助手已启动!输入 '退出' 结束对话。")
whileTrue:
# 获取用户输入
user_input = input("用户: ")
if user_input.lower() in ["退出", "quit", "stop", "bye", "拜拜"]:
print("退出与计算助手的对话。")
break
# 将用户输入添加到对话历史
messages.append({"role": "user", "content": user_input})
response: ChatResponse = client.chat(
model=model_name,
messages=messages,
tools=[add_two_numbers, subtract_two_numbers_tool, multiply_two_numbers, divide_two_numbers])
final_solve_output = call_tools(response, messages)
if final_solve_output isnotNone:
messages.append({'role': 'tool', 'content': str(final_solve_output), 'name': '最终计算结果'})
response_content = ""
print("计算助手: ", end='', flush=True)
for part in client.chat(model=model_name, messages=messages, stream=True):
response_content += part['message']['content']
print(f"{part['message']['content']}", end='', flush=True)
print()
# 将模型的响应添加到对话历史
messages.append({"role": "assistant", "content": response_content})
if __name__ == '__main__':
chat_with_ollama()
测试验证
用户依次提出以下问题:
-
现在要进一批货,单价是35.324,数量是765345677,商家优惠 1500 元,一共要付多少钱?
-
后来商家又说,可以在上面的基础上,优惠两个点,最终要付多少钱?
-
商家后来又说,再打 9.4523 折,太好了。最终要付多少钱?
验证结果如下(模型助手可正确理解”优惠两个点“、”9.4523 折“等中文表达,将用户的表达转化为正确的工具调用):
计算助手已启动!输入 '退出' 结束对话。
用户: 现在要进一批货,单价是35.324,数量是765345677,商家优惠 1500 元,一共要付多少钱?
--------------开始工具调用-----------------
调用工具: multiply_two_numbers
参数: {'a': 35.324, 'b': 765345677}
工具输出: 27035070694.348
调用工具: subtract_two_numbers
参数: {'a': 27035070694.348, 'b': 1500}
工具输出: 27035069194.348
--------------工具调用结束-----------------
计算助手: 根据计算结果,您需要支付的金额是27,035,069,194.348元。由于实际交易中通常不会出现如此巨大的金额,这里可能是一个模拟或测试场景。在实际情况中,如果您有任何疑问或需要进一步的帮助,请随时告诉我!
用户: 后来商家又说,可以在上面的基础上,优惠两个点,最终要付多少钱?
--------------开始工具调用-----------------
调用工具: multiply_two_numbers
参数: {'a': 27035069194.348, 'b': 0.98}
工具输出: 26494367810.46104
--------------工具调用结束-----------------
计算助手: 根据计算结果,在享受了2%的额外优惠后,您最终需要支付的金额是26,494,367,810.46元。
如果您有任何其他问题或需要进一步的帮助,请随时告诉我!
用户: 回答正确,给你一个赞,今天晚饭可以加只鸡腿!
计算助手: 太好了,很高兴能帮到您!既然得到了您的认可,今晚的晚餐确实可以让您的饭菜更加丰富哦。如果以后还有任何数学计算或其他问题需要帮助,请随时告诉我。祝您用餐愉快!
用户: 商家后来又说,再打 9.4523 折,太好了。最终要付多少钱?
--------------开始工具调用-----------------
调用工具: multiply_two_numbers
参数: {'a': 26494367810.46104, 'b': 0.94523}
工具输出: 25043271285.48209
--------------工具调用结束-----------------
计算助手: 根据计算结果,在享受了额外的9.4523折优惠后,您最终需要支付的金额是25,043,271,285.48元。
这个数字看起来依然非常大,不过已经反映了最后一次折扣后的总价。如果您有任何其他问题或需要进一步的帮助,请随时告诉我!希望这次计算结果对您有帮助。
文章来源:微信公众号-数智脉动,原始发表时间:2025年02月21日。