75389浏览
查看: 75389|回复: 0

[项目] [行空板+大模型]智能家居助手—让ChatGPT帮你控制硬件

[复制链接]
本帖最后由 loria 于 2024-1-9 11:43 编辑

[行空板+大模型]智能家居助手——让ChatGPT帮你控制硬件

行空板+大模型应用案例一:基于ChatGLM的多角色交互式聊天机器人

一、导语(项目功能介绍):
想象一下,假设现在你是一个对python一无所知的用户,但是你对行空板和硬件产品都很感兴趣,现在你只需要将硬件正确的接在对应的引脚位置,然后告诉大模型你想实现的功能,比如“我在21号引脚接了一个风扇,我想要打开风扇”,模型就能帮助你调用相关的函数控制风扇的启动,大模型的funtion calling功能完全能帮助我们实现这一场景。

OpenAI在2023年6月的更新中为Chat Completions 模型增加了function calling功能。我们知道通过ChatCompletion,我们能实现通过调用API完成与ChatGPT的对话。而有了Funtion Calling功能,可以让模型不仅仅能根据自身预训练的数据库进行知识问答,还能额外挂载一个函数库,根据用户的提问去函数库检索,按照用户的实际需求去调用外部函数并获取函数的运行结果。
可以说funtion calling彻底的改变了开发者与模型互动的方式。这个功能允许开发者描述函数给AI模型,然后模型可以智能地决定输出一个包含调用这些函数的参数的JSON对象。简单来说,大模型的function calling功能允许我们使用自然语言的形式调用函数,实现让GPT模型调用谷歌邮箱API(Gmail API),自动让GPT模型读取邮件,并自动进行回复等等 。整理了目前支持funtion calling的模型,如下图。
[行空板+大模型]智能家居助手—让ChatGPT帮你控制硬件图1

借助funtion calling功能,用户能通过简单的语句实现控制硬件,而无需深入的研究底层的代码逻辑。举个例子,现在我在定义了一个控制行空板板载蜂鸣器播放音调的函数play_music,并且告诉了模型这个函数的功能。那么可以有以下对话场景的出现。

[行空板+大模型]智能家居助手—让ChatGPT帮你控制硬件图2
整个实现的逻辑如下图。
[行空板+大模型]智能家居助手—让ChatGPT帮你控制硬件图3
本篇帖子就来展示如何实现这一功能。本文旨在提供方法和思路,笔者希望通过这篇文章抛砖引玉,展示给大家看通过大模型控制硬件的可能性,以行空板板载蜂鸣器、灯带、风扇模拟生活中智能家居的声、光、动。也希望大家在看完这篇帖子后能发挥创意,实现行空板结合大模型控制硬件的更多好玩的应用。(PS:本项目的实现需借助魔法上网。)

二、演示视频

[url=]

三、软硬件


[行空板+大模型]智能家居助手—让ChatGPT帮你控制硬件图4
行空板


[行空板+大模型]智能家居助手—让ChatGPT帮你控制硬件图5
Gravity: 130 直流电机风扇

[行空板+大模型]智能家居助手—让ChatGPT帮你控制硬件图6
WS2812-16 RGB LED Ring  

[行空板+大模型]智能家居助手—让ChatGPT帮你控制硬件图7

Mind+

四、funtion calling 的实现流程
智谱AI在官方文档里给出了使用模型进行工具调用的流程,ChatGLM3-6b和GPT3.5的实现逻辑是一样的,因此我们可以进行参考。
首先,我们要定义好函数工具,比如控制行空板板载蜂鸣器播放音乐的函数工具play_music、用于控制风扇硬件的函数工具fan_action、用于控制LED灯带的函数工具led_light_action
接着我们要定义一个工具列表,向模型描述这些函数工具的名称、函数的具体功能、调用函数所需的参数、参数的具体类型、参数的详细信息。这是官方给的样例,按照样例能获得最好的性能。
  1. tools = [
  2.     {
  3.         "name": "track",
  4.         "description": "追踪指定股票的实时价格",
  5.         "parameters": {
  6.             "type": "object",
  7.             "properties": {
  8.                 "symbol": {
  9.                     "description": "需要追踪的股票代码"
  10.                 }
  11.             },
  12.             "required": ['symbol']
  13.         }
  14.     },
  15.     {
  16.         "name": "text-to-speech",
  17.         "description": "将文本转换为语音",
  18.         "parameters": {
  19.             "type": "object",
  20.             "properties": {
  21.                 "text": {
  22.                     "description": "需要转换成语音的文本"
  23.                 },
  24.                 "voice": {
  25.                     "description": "要使用的语音类型(男声、女声等)"
  26.                 },
  27.                 "speed": {
  28.                     "description": "语音的速度(快、中等、慢等)"
  29.                 }
  30.             },
  31.             "required": ['text']
  32.         }
  33.     }
  34. ]
  35. system_info = {"role": "system", "content": "Answer the following questions as best as you can. You have access to the following tools:", "tools": tools}
复制代码

最后是,将用户的输入进行json格式解析,传给模型。模型根据用户的输入实现对应工具的调用。

五、准备操作
3.行空板联网
调用api必须要联网。由于在本项目中,我们使用了OpenAI的api和微软的语音api,所以我们要为行空板连接网络。(1)打开浏览器,输入“10.1.2.3”进行行空板页面。(2)选择“网络设置”,选择WIFI ,输入密码,注意行空板仅支持2.4GWIFI热点。点击“连接”,行空板成功联网会显示“连接成功”。

六、tool描述和funtion实现
接下来,我将按照funtion calling的实现过程,按照“编写控制硬件的函数工具——定义工具列表描述函数工具的详细信息——解析用户输入传给模型 ”进行代码的编写。
6.1蜂鸣器6.1.1pinpong库示例程序
为了完整的实现使用funtion calling功能控制行空板的板载蜂鸣器,我们先一起来看看pinpong库中控制蜂鸣器播放音调的示例代码是怎么写的。pinpong库有关蜂鸣器的示例程序如下。
  1. import time
  2. from pinpong.board import Board,Pin
  3. from pinpong.extension.unihiker import *   
  4. Board().begin()#初始化
  5. #音乐 DADADADUM ENTERTAINER PRELUDE ODE NYAN RINGTONE FUNK BLUES BIRTHDAY WEDDING FUNERAL PUNCHLINE
  6. #音乐 BADDY CHASE BA_DING WAWAWAWAA JUMP_UP JUMP_DOWN POWER_UP POWER_DOWN
  7. #播放模式 Once(播放一次) Forever(一直播放) OnceInBackground(后台播放一次) ForeverInBackground(后台一直播放)
  8. buzzer.play(buzzer.DADADADUM, buzzer.Once) #播放音乐一次
  9. while True:
  10.     time.sleep(1) #等待1秒 保持状态
复制代码

6.1.2定义控制蜂鸣器播放音乐的函数play_music
根据pinpong库中控制行空板板载蜂鸣器的示例程序,我们可以写出一个play_music函数来控制蜂鸣器播放内置音乐。它接受一个参数“music”,用于指定要播放的音乐名称。if music in [...]检查传入的音乐名称是否在预定义的音乐列表中。如果在,使用buzzer.play函数播放音乐,如果不在就提醒示没有找到指定的音乐名称,无法进行播放。


  1. def play_music(music):
  2.     if music in ["DADADADUM", "ENTERTAINER", "PRELUDE", "ODE", "NYAN", "RINGTONE", "FUNK", "BLUES", "BIRTHDAY", "WEDDING", "FUNERAL", "PUNCHLINE", "BADDY", "CHASE", "BA_DING", "WAWAWAWAA", "JUMP_UP", "JUMP_DOWN", "POWER_UP", "POWER_DOWN"]:   
  3.         buzzer.play(getattr(buzzer, music), buzzer.OnceInBackground)  
  4.         print(f"已调用,播放{music}")
  5.     else:
  6.         print("对不起,我这里没有这个音乐。")
复制代码

6.1.3定义工具列表
按照官方样例的格式,详细的描述play_music函数的名称、描述以及参数。


  1. tools = [
  2.     {
  3.         "type": "function",
  4.         "function": {
  5.             "name": "play_music",
  6.             "description": "Plays music in the house when the user is bored or needs relaxation. The music list includes 'DADADADUM', 'ENTERTAINER', 'PRELUDE', 'ODE', 'NYAN', 'RINGTONE', 'FUNK', 'BLUES', 'BIRTHDAY', 'WEDDING', 'FUNERAL', 'PUNCHLINE', 'BADDY', 'CHASE', 'BA_DING', 'WAWAWAWAA', 'JUMP_UP', 'JUMP_DOWN', 'POWER_UP', 'POWER_DOWN'.",   
  7.             "parameters": {
  8.                 "type": "object",
  9.                 "properties": {
  10.                     "music": {
  11.                         "type": "string",
  12.                         "enum": ["DADADADUM", "ENTERTAINER", "PRELUDE", "ODE", "NYAN", "RINGTONE", "FUNK", "BLUES", "BIRTHDAY", "WEDDING", "FUNERAL", "PUNCHLINE", "BADDY", "CHASE", "BA_DING", "WAWAWAWAA", "JUMP_UP", "JUMP_DOWN", "POWER_UP", "POWER_DOWN"],
  13.                         "description": "The music to play in the house."
  14.                     },
  15.                 },
  16.                 "required": ["music"],  
  17.             },
  18.         }
  19.     }
  20. ]
复制代码

接着,定义一个函数,使用chatcompletion向模型发送聊天完成请求
  1. def chat_completion_request(messages, tools=None, tool_choice=None):
  2.     """
  3.     发送 Chat Completion 请求给 OpenAI GPT-3.5-turbo 模型,并返回模型的响应。
  4.     参数:
  5.     - messages: 包含对话历史的列表,每个对话历史是一个字典,包含 "role" 和 "content"。
  6.     - tools: (可选)包含工具描述的列表,每个工具是一个字典。
  7.     - tool_choice: (可选)工具选择,用于指定使用哪个工具。
  8.     返回:
  9.     - response: 发送请求后的模型响应。
  10.     """
  11.     headers = {
  12.         "Content-Type": "application/json",
  13.         "Authorization": "Bearer " + OPENAI_API_KEY,  
  14.     }
  15.     # 构建请求的 JSON 数据
  16.     json_data = {"model": "gpt-3.5-turbo", "messages": messages}
  17.     if tools is not None:
  18.         json_data.update({"tools": tools})
  19.     if tool_choice is not None:
  20.         json_data.update({"tool_choice": tool_choice})
  21.     try:
  22.         # 发送 POST 请求到 OpenAI API
  23.         response = requests.post(
  24.             "https://api.openai.com/v1/chat/completions",
  25.             headers=headers,
  26.             json=json_data,
  27.         )
  28.         return response
  29.     except Exception as e:
  30.         # 处理异常情况
  31.         print("Unable to generate ChatCompletion response")
  32.         print(f"Exception: {e}")
  33.         return e
复制代码

接下来,就是在主循环中,获取用户输入和模型的响应,与一般的打印模型的响应不同,这里要多加一项即检查模型的响应中是否含有工具调用的相关信息(tool_calls)。如果存在,提取函数和参数信息,然后调用相应的函数执行操作。
  1. while True:
  2.     # 用户输入,通过命令行获取用户的自然语言输入
  3.     user_input = input('请输入:')
  4.     # 将用户输入添加到对话历史中,定义用户的角色为 "user"
  5.     conversation.append({"role": "user", "content": user_input})
  6.     # 调用 chat_completion_request 函数,向模型发送对话历史,并获取模型的响应
  7.     response = chat_completion_request(conversation, tools)
  8.     print("Model Response: ", response.json())
  9.     # 提取助手的回复内容
  10.     assistant_response = response.json()["choices"][0]["message"]["content"]
  11.     # 如果助手的回复为空,将其设置为默认值 "执行操作"
  12.     if assistant_response is None:
  13.         assistant_response = "执行操作"
  14.     # 打印助手的回复
  15.     print(f"Assistant: {assistant_response}")
  16.     # 检查模型的响应中是否包含 tool_calls(Function Calling 操作)
  17.     if 'tool_calls' in response.json()["choices"][0]["message"]:
  18.         # 提取工具调用信息中的函数和参数
  19.         function_call = response.json()["choices"][0]["message"]["tool_calls"][0]["function"]
  20.         arguments = json.loads(function_call["arguments"])
  21.         # 调用 play_music 函数,根据模型的指令执行音乐播放操作
  22.         play_music(arguments["music"])
  23.     # 将助手的回复添加到对话历史中,定义助手的角色为 "assistant"
  24.     conversation.append({"role": "assistant", "content": assistant_response})
复制代码

6.1.4使用funtion calling功能控制行空板蜂鸣器的完整程序如下
  1. import requests
  2. import json
  3. from pinpong.board import Board, Pin
  4. from pinpong.extension.unihiker import *
  5. import openai  
  6. Board().begin() # 初始化
  7. OPENAI_API_KEY=' ' #  OpenAI API 密钥
  8. def chat_completion_request(messages, tools=None, tool_choice=None):  
  9.     headers = {
  10.         "Content-Type": "application/json",  
  11.         "Authorization": "Bearer " + OPENAI_API_KEY,
  12.     }
  13.     json_data = {"model": "gpt-3.5-turbo", "messages": messages}
  14.     if tools is not None:
  15.         json_data.update({"tools": tools})
  16.     if tool_choice is not None:  
  17.         json_data.update({"tool_choice": tool_choice})
  18.     try:
  19.         response = requests.post(
  20.             "https://api.openai.com/v1/chat/completions",
  21.             headers=headers,
  22.             json=json_data,
  23.         )
  24.         return response
  25.     except Exception as e:
  26.         print("Unable to generate ChatCompletion response")
  27.         print(f"Exception: {e}")
  28.         return e
  29. tools = [
  30.     {
  31.         "type": "function",
  32.         "function": {
  33.             "name": "play_music",
  34.             "description": "Plays music in the house when the user is bored or needs relaxation. The music list includes 'DADADADUM', 'ENTERTAINER', 'PRELUDE', 'ODE', 'NYAN', 'RINGTONE', 'FUNK', 'BLUES', 'BIRTHDAY', 'WEDDING', 'FUNERAL', 'PUNCHLINE', 'BADDY', 'CHASE', 'BA_DING', 'WAWAWAWAA', 'JUMP_UP', 'JUMP_DOWN', 'POWER_UP', 'POWER_DOWN'.",
  35.             "parameters": {
  36.                 "type": "object",
  37.                 "properties": {
  38.                     "music": {
  39.                         "type": "string",
  40.                         "enum": ["DADADADUM", "ENTERTAINER", "PRELUDE", "ODE", "NYAN", "RINGTONE", "FUNK", "BLUES", "BIRTHDAY", "WEDDING", "FUNERAL", "PUNCHLINE", "BADDY", "CHASE", "BA_DING", "WAWAWAWAA", "JUMP_UP", "JUMP_DOWN", "POWER_UP", "POWER_DOWN"],
  41.                         "description": "The music to play in the house."
  42.                     },
  43.                 },
  44.                 "required": ["music"],
  45.             },
  46.         }
  47.     }
  48. ]
  49. def play_music(music):
  50.     if music in ["DADADADUM", "ENTERTAINER", "PRELUDE", "ODE", "NYAN", "RINGTONE", "FUNK", "BLUES", "BIRTHDAY", "WEDDING", "FUNERAL", "PUNCHLINE", "BADDY", "CHASE", "BA_DING", "WAWAWAWAA", "JUMP_UP", "JUMP_DOWN", "POWER_UP", "POWER_DOWN"]:
  51.         buzzer.play(getattr(buzzer, music), buzzer.OnceInBackground)
  52.         print(f"已调用,播放{music}")
  53.     else:
  54.         print("对不起,我这里没有这个音乐。")
  55. conversation = [{"role": "system", "content": "You are a smart home assistant that can play music for relaxation when the user is bored or needs it. You can suggest playing music when the user seems bored or tired."}]
  56. while True:
  57.     user_input = input('请输入:')
  58.     conversation.append({"role": "user", "content": user_input})
  59.     response = chat_completion_request(conversation, tools)
  60.     print("Model Response: ", response.json())
  61.     assistant_response = response.json()["choices"][0]["message"]["content"]
  62.     if assistant_response is None:
  63.         assistant_response = "执行操作"
  64.     print(f"Assistant: {assistant_response}")  
  65.     if 'tool_calls' in response.json()["choices"][0]["message"]:
  66.         function_call = response.json()["choices"][0]["message"]["tool_calls"][0]["function"]
  67.         arguments = json.loads(function_call["arguments"])  
  68.         play_music(arguments["music"])
  69.     conversation.append({"role": "assistant", "content": assistant_response})
复制代码

6.2风扇

在生活中,我们使用风扇时能通过调节档位来改变风扇的转速。这里我们能用PWM模拟输出实现这一功能。
6.2.1pinpong库关于PWM输出控制好风扇的示例程序
关于行空板支持PWM的引脚号,可以查看官方的说明:PWM(模拟输出)
根据官方对PWM的说明,我们使用PWM控制风扇的代码,如下。
  1. from pinpong.board import Board,Pin  
  2. Board(" ").begin()
  3. fan = Pin(Pin.P22, Pin.PWM)  #初始化风扇的引脚位置
  4. fan.write_analog(800)        #PWM输出控制风扇的转速,范围是0——1023;0即引脚电平为0,风扇不转;1023为电平最高,风扇转速最大。
复制代码

6.2.2定义控制风扇的函数
通过示例程序,我们可以看出通过funtion calling 使模型控制风扇,首先我们要告诉模型:风扇的引脚位置的初始化 。接着我们要告诉模型对风扇的操作,包括打开风扇、提高转速、降低转速、关闭风扇(通过PWM输出控制转速)。
我们依旧是先写函数,根据函数完成工具的描述和工具调用的代码。
我们先来完成风扇初始化的代码。站在用户的角度,使用风扇时,会将使用引脚线将风扇接在行空板能使用PWM的引脚的位置,然后将引脚的位置告诉模型。为了让模型知道引脚的位置,我们要先定义一个能支持PWM的引脚的列表。
定义一个全局变量fan,用于存储风扇的引脚信息。

  1. pin_map = {
  2.     21: Pin.P21,
  3.     22: Pin.P22,
  4.     23: Pin.P23,
  5. }
复制代码

接着,定义控制风扇的函数fan_action,通过分支结构判断指定的风扇的类型,通过用户的输入完成对风扇的初始化和控制等。fan_action有三个参数,‘action’:指定要执行的动作,用于使用分支结构控制风扇的状态。'pn_number'用于风扇初始化时确定风扇连接的引脚号。‘user_input’用于确定用户对风扇的控制指令。
值得注意的是,当用户没有进行初始化就说打开风扇或者说了错误的引脚号时,要打印错误信息,提醒用户给出正确的引脚位置。
  1. def fan_action(action, pin_number=None, user_input=None):  
  2.     """
  3.     控制风扇的行为。
  4.     参数:
  5.     - action: 指定要执行的动作,可以是 "initialize"(初始化)或其他操作。
  6.     - pin_number: (仅在 action 为 "initialize" 时需要)风扇连接的引脚号。
  7.     - user_input: (仅在执行其他操作时需要)用户的输入,用于确定具体的操作。
  8.     返回:
  9.     无返回值。通过打印信息告知用户操作的结果。
  10.     详细解释:
  11.     1. 使用全局变量 `fan`,用于存储风扇的引脚信息。
  12.     2. 如果 action 是 "initialize",则进行初始化操作。
  13.         a. 检查传入的引脚号是否在 pin_map 中,如果不在则打印错误信息。
  14.         b. 使用 Pin 类创建风扇对象,并设置为 PWM 模式。
  15.         c. 打印初始化成功的信息。
  16.     3. 如果 action 不是 "initialize",执行相应的操作。
  17.         a. 检查风扇是否已经初始化,如果未初始化则打印错误信息。
  18.         b. 根据用户输入执行相应的操作:
  19.             - "turn on": 开启风扇,设置 PWM 为 800。
  20.             - "turn off": 关闭风扇,设置 PWM 为 0。
  21.             - "increase": 增加风扇转速,设置 PWM 为 1023。
  22.             - "decrease": 减少风扇转速,设置 PWM 为 512。
  23.         c. 打印执行操作成功的信息。
  24.     使用示例:
  25.     ```
  26.     # 初始化风扇,连接到引脚号 5
  27.     fan_action("initialize", pin_number=5)
  28.     # 执行操作,根据用户输入控制风扇
  29.     fan_action("operate", user_input="turn on")
  30.     ```
  31.     注意:确保在使用之前设置全局变量 pin_map 以包含正确的引脚映射。
  32.     """
  33.     global fan
  34.     # 如果是初始化操作
  35.     if action == "initialize":
  36.         if pin_number not in pin_map:
  37.             print(f"错误的引脚号:{pin_number}")
  38.             return
  39.         # 使用 Pin 类创建风扇对象,并设置为 PWM 模式
  40.         fan = Pin(pin_map[pin_number], Pin.PWM)
  41.         print(f"风扇已初始化,接在{pin_number}号端口")
  42.     else:
  43.         # 如果风扇未初始化
  44.         if fan is None:
  45.             print("对不起,风扇尚未初始化,请先告诉我引脚号")
  46.             return
  47.         # 根据用户输入执行相应的操作
  48.         if "turn on" in user_input:
  49.             fan.write_analog(800)
  50.             print("风扇已开启")
  51.         elif "turn off" in user_input:
  52.             fan.write_analog(0)
  53.             print("风扇已关闭")
  54.         elif "increase" in user_input:
  55.             fan.write_analog(1023)
  56.             print("风扇转速已增加")
  57.         elif "decrease" in user_input:
  58.             fan.write_analog(512)
  59.             print("风扇转速已减少")
复制代码

6.2.3添加对风扇函数的工具描述
详细的解释函数以及各个参数的作用。
  1. tools = [
  2.     {
  3.         "type": "function",
  4.         "function": {
  5.             "name": "fan_action",
  6.             "description": "This function initializes or controls the fan based on the given action and user's command. It can initialize the fan, turn on the fan, turn off the fan, increase the fan speed, or decrease the fan speed. It will print an error message if the fan has not been initialized.",
  7.             "parameters": {
  8.                 "type": "object",
  9.                 "properties": {
  10.                     "action": {
  11.                         "type": "string",
  12.                         "enum": ["initialize", "turn_on", "turn_off", "increase_speed", "decrease_speed"],
  13.                         "description": "The action to perform on the fan.",  
  14.                     },
  15.                     "pin_number": {
  16.                         "type": "integer",
  17.                         "description": "The pin number where the fan is connected. Required for 'initialize' action.",
  18.                     },
  19.                     "user_input": {
  20.                         "type": "string",
  21.                         "description": "The user's command to control the fan. Required for 'turn on', 'turn off', 'increase speed', 'decrease speed' actions.",
  22.                     },
  23.                 },
  24.                 "required": ["action"],
  25.             },
  26.         },
  27.     },
  28. ]
复制代码

6.2.4使用funtion calling功能控制风扇的完整程序如下
  1. # 导入所需的库和模块
  2. import requests
  3. import json
  4. from pinpong.board import Board, Pin
  5. import openai
  6. import time
  7. # 初始化 Pinpong Board
  8. Board(" ").begin()
  9. # 设置你的 OpenAI API 密钥
  10. OPENAI_API_KEY = ' '
  11. # 初始化风扇变量
  12. fan = None
  13. # 引脚号映射到 Pinpong Pins
  14. pin_map = {
  15.     21: Pin.P21,
  16.     22: Pin.P22,
  17.     23: Pin.P23,
  18.     24: Pin.P24,
  19. }
  20. # 发起聊天请求到 OpenAI API 的函数
  21. def chat_completion_request(messages, tools=None, tool_choice=None):
  22.     headers = {
  23.         "Content-Type": "application/json",
  24.         "Authorization": "Bearer " + OPENAI_API_KEY,
  25.     }
  26.     json_data = {"model": "gpt-3.5-turbo-1106", "messages": messages}
  27.     if tools is not None:
  28.         json_data.update({"tools": tools})
  29.     if tool_choice is not None:
  30.         json_data.update({"tool_choice": tool_choice})
  31.     try:
  32.         response = requests.post(
  33.             "https://api.openai.com/v1/chat/completions",
  34.             headers=headers,
  35.             json=json_data,
  36.         )
  37.         return response
  38.     except Exception as e:
  39.         print("无法生成 ChatCompletion 响应")
  40.         print(f"异常: {e}")
  41.         return e
  42. # 定义风扇控制工具
  43. tools = [
  44.     {
  45.         "type": "function",
  46.         "function": {
  47.             "name": "fan_action",
  48.             "description": "此函数根据给定的操作和用户的命令初始化或控制风扇。可以初始化风扇、打开风扇、关闭风扇、增加风扇速度或减少风扇速度。如果风扇未初始化,将打印错误消息。",
  49.             "parameters": {
  50.                 "type": "object",
  51.                 "properties": {
  52.                     "action": {
  53.                         "type": "string",
  54.                         "enum": ["initialize", "turn_on", "turn_off", "increase_speed", "decrease_speed"],
  55.                         "description": "在风扇上执行的操作。",
  56.                     },
  57.                     "pin_number": {
  58.                         "type": "integer",
  59.                         "description": "风扇连接的引脚编号。对于 'initialize' 操作,此项为必填。",
  60.                     },
  61.                     "user_input": {
  62.                         "type": "string",
  63.                         "description": "用户控制风扇的命令。对于 'turn on'、'turn off'、'increase speed'、'decrease speed' 操作,此项为必填。",
  64.                     },
  65.                 },
  66.                 "required": ["action"],
  67.             },
  68.         },
  69.     },
  70. ]
  71. # 执行基于用户命令的风扇操作的函数
  72. def fan_action(action, pin_number=None, user_input=None):
  73.     global fan
  74.     if action == "initialize":
  75.         if pin_number not in pin_map:  
  76.             print(f"错误的引脚号:{pin_number}")
  77.             return
  78.         fan = Pin(pin_map[pin_number], Pin.PWM)
  79.         print(f"风扇已初始化,接在{pin_number}号端口")
  80.     else:
  81.         if fan is None:
  82.             print("风扇尚未初始化")
  83.             return
  84.         if "turn on" in user_input:
  85.             fan.write_analog(800)
  86.             print("风扇已开启")
  87.         elif "turn off" in user_input:
  88.             fan.write_analog(0)
  89.             print("风扇已关闭")
  90.         elif "increase" in user_input:
  91.             fan.write_analog(1023)
  92.             print("风扇转速已增加")
  93.         elif "decrease" in user_input:
  94.             fan.write_analog(512)
  95.             print("风扇转速已减少")
  96. # 系统发起的对话消息
  97. conversation = [
  98.     {"role": "system", "content": "你是一个能够控制风扇的有用助手。"},
  99. ]
  100. # 主循环
  101. while True:
  102.     user_input = input('请输入:')
  103.     conversation.append({"role": "user", "content": user_input})
  104.     # 向 OpenAI API 发起聊天请求
  105.     response = chat_completion_request(conversation, tools)
  106.     print("模型响应: ", response.json())
  107.     # 处理模型的响应并执行请求的函数
  108.     if 'tool_calls' in response.json()["choices"][0]["message"]:
  109.         function_call = response.json()["choices"][0]["message"]["tool_calls"][0]["function"]
  110.         print(f"模型调用的函数: {function_call['name']}")
  111.         if function_call["name"] == "fan_action":
  112.             arguments = json.loads(function_call["arguments"])
  113.             fan_action(arguments["action"], arguments.get("pin_number"), arguments.get("user_input"))  
  114.     # 显示助手的响应
  115.     assistant_response = response.json()["choices"][0]["message"]["content"]
  116.     if assistant_response is None:
  117.         assistant_response = "执行操作"
  118.     print(f"助手: {assistant_response}")
  119.     # 将助手的响应添加到对话中
  120.     conversation.append({"role": "assistant", "content": assistant_response})  
复制代码

6.3灯带6.3.1pinpong库灯带示例程序
通过pinpong库的示例程序可以看出,灯带的初始化需要两个参数:(1)灯带的引脚位置 (2)灯珠的数量。而灯珠颜色的设置是通过修改RGB值来实现的。
  1. # -*- coding: utf-8 -*-
  2. import time
  3. from pinpong.board import Board,Pin,NeoPixel
  4. NEOPIXEL_PIN = Pin.D7 #灯的引脚号
  5. PIXELS_NUM = 4 #灯珠数量数
  6. np = NeoPixel(Pin(NEOPIXEL_PIN), PIXELS_NUM)  #灯带的初始化
  7. while True:
  8.   np[0] = (0, 255 ,0) #设置第一个灯RGB亮度
  9.   np[1] = (255, 0, 0) #设置第二个灯RGB亮度
  10.   np[2] = (0, 0, 255) #设置第三个灯RGB亮度
  11.   np[3] = (255, 0, 255) #设置第四个灯RGB亮度
  12.   time.sleep(1)
复制代码

6.3.2定义点亮灯带的函数
在定义点亮灯带的函数时,我们先来思考用户会怎样使用灯带。当用户将灯带接在行空板的引脚上后,首先会告诉模型灯带的引脚号和灯珠的数量,只有这样,才能完成灯带的正确的初始化。因此,我们需要先定义两个全局变量,用于灯带的初始化操作。


  1. led = None  #用于初始化灯带对象
  2. num_beads_global = None #用于初始化灯珠数量
复制代码

当灯带初始化完成之后,用户会告诉模型想点亮的灯珠的数量和颜色,如“我想点亮4颗灯珠为红色”,这时,我们需要将红色转换成其对应的RGB值。为了实现这样的功能,我们可以定义一个颜色字典,将颜色名字与其RGB值对应起来。
  1. # 创建颜色字典
  2. COLOR_DICT = {
  3.     "red": [255, 0, 0],
  4.     "green": [0, 255, 0],
  5.     "blue": [0, 0, 255],
  6.     "white": [255, 255, 255],
  7.     "black": [0, 0, 0],
  8.     "yellow": [255, 255, 0],
  9.     "pink": [255, 105, 180],
  10.     "purple": [128, 0, 128]
  11. }
复制代码

接下来,定义函数 led_light_action ,用于初始化和控制 LED 灯带,支持两种操作:initialize(初始化)和 lightup(点亮)。在 initialize 操作中,它根据给定的引脚号和灯珠数量初始化 LED 灯带,而在 lightup 操作中,它点亮指定数量的 LED 灯珠,并设置它们的颜色。同时,函数会进行错误检查,确保必要的参数被提供,并在出现错误时输出相应的错误消息。
  1. def led_light_action(action, pin_number=None, num_lights=None, color=None):
  2.     global led
  3.     global num_beads_global
  4.     # 如果动作是初始化
  5.     if action == "initialize":
  6.         # 检查参数是否完整
  7.         if pin_number is None or num_lights is None:
  8.             print("错误:'pin_number' 和 'num_lights' 在 'initialize' 动作中是必需的。")
  9.             return
  10.         # 初始化 LED 灯
  11.         led = NeoPixel(Pin(pin_number), num_lights)
  12.         num_beads_global = num_lights
  13.         print(f"LED 灯已在引脚 {pin_number} 上用 {num_lights} 颗珠子进行初始化。")
  14.     # 如果动作是点亮
  15.     elif action == "lightup":
  16.         # 检查 LED 是否已初始化
  17.         if led is None:
  18.             print("错误:LED 灯尚未初始化。")
  19.             return
  20.         # 检查参数是否完整
  21.         if num_lights is None or color is None:
  22.             print("错误:'num_lights' 和 'color' 在 'lightup' 动作中是必需的。")
  23.             return
  24.         # 检查 'num_lights' 是否为正整数,且不大于全局珠子数量
  25.         if not isinstance(num_lights, int) or num_lights <= 0 or num_lights > num_beads_global:
  26.             print("错误:'num_lights' 必须是正整数且不能大于珠子数量。")
  27.             return
  28.         # 获取颜色对应的 RGB 值
  29.         color_rgb = COLOR_DICT.get(color.lower())
  30.         if color_rgb is None:
  31.             print(f"错误:未知颜色 '{color}'")
  32.             return
  33.         # 逐个点亮指定数量的珠子
  34.         for i in range(num_lights):
  35.             led[i] = tuple(color_rgb)
  36.             led.write(i, color_rgb[0], color_rgb[1], color_rgb[2])
  37.         print(f"点亮了 {num_lights} 颗珠子,颜色为 {color}。")
  38.     # 如果动作未知
  39.     else:
  40.         print(f"错误:未知动作 '{action}'")
复制代码

6.3.3点亮灯带的函数的工具描述
  1. tools = [
  2.     {
  3.         "type": "function",
  4.         "function": {
  5.             "name": "led_light_action",
  6.             "description": "This function performs actions on a LED light. It can initialize the LED light on a specific pin with a certain number of beads, or light up a certain number of beads in a specific color.",
  7.             "parameters": {
  8.                 "type": "object",
  9.                 "properties": {
  10.                     "action": {
  11.                         "type": "string",
  12.                         "enum": ["initialize", "lightup"],
  13.                         "description": "The action to perform. 'initialize' will set up the LED light on a specific pin with a certain number of beads. 'lightup' will light up a certain number of beads in a specific color."
  14.                     },
  15.                     "pin_number": {
  16.                         "type": "integer",
  17.                         "description": "The pin number where the LED light is connected. This is required when the 'action' is 'initialize'."
  18.                     },
  19.                     "num_lights": {
  20.                         "type": "integer",
  21.                         "description": "The number of beads to light up or initialize. This is required when the 'action' is 'initialize' or 'lightup'."
  22.                     },
  23.                     "color": {
  24.                         "type": "string",
  25.                         "description": "The color to use when lighting up the beads. This is required when the 'action' is 'lightup'."
  26.                     },
  27.                 },
  28.                 "required": ["action"],
  29.             },
  30.         },
  31.     }
  32. ]
复制代码

6.3.4使用funtion calling 功能点亮灯带的完整程序如下
  1. import requests
  2. import json
  3. from pinpong.board import Board, Pin, NeoPixel
  4. Board().begin()  # 初始化
  5. OPENAI_API_KEY = ' '  # OpenAI API 密钥
  6. # 创建颜色字典
  7. COLOR_DICT = {
  8.     "red": [255, 0, 0],
  9.     "green": [0, 255, 0],
  10.     "blue": [0, 0, 255],
  11.     "white": [255, 255, 255],
  12.     "black": [0, 0, 0],
  13.     "yellow": [255, 255, 0],
  14.     "pink": [255, 105, 180],
  15.     "purple": [128, 0, 128]
  16. }
  17. led = None
  18. num_beads_global = None
  19. def chat_completion_request(messages, tools=None, tool_choice=None):
  20.     headers = {
  21.         "Content-Type": "application/json",
  22.         "Authorization": "Bearer " + OPENAI_API_KEY,
  23.     }
  24.     json_data = {"model": "gpt-3.5-turbo", "messages": messages}
  25.     if tools is not None:
  26.         json_data.update({"tools": tools})
  27.     if tool_choice is not None:
  28.         json_data.update({"tool_choice": tool_choice})
  29.     try:
  30.         response = requests.post(
  31.             "https://api.openai.com/v1/chat/completions",
  32.             headers=headers,
  33.             json=json_data,
  34.         )
  35.         return response
  36.     except Exception as e:
  37.         print("Unable to generate ChatCompletion response")
  38.         print(f"Exception: {e}")
  39.         return None
  40. def led_light_action(action, pin_number=None, num_lights=None, color=None):
  41.     global led
  42.     global num_beads_global
  43.     if action == "initialize":
  44.         if pin_number is None or num_lights is None:
  45.             print("Error: 'pin_number' and 'num_lights' are required for 'initialize' action.")
  46.             return
  47.         led = NeoPixel(Pin(pin_number), num_lights)
  48.         num_beads_global = num_lights
  49.         print(f"LED light initialized on pin {pin_number} with {num_lights} beads.")
  50.     elif action == "lightup":
  51.         if led is None:
  52.             print("Error: LED light has not been initialized.")
  53.             return
  54.         if num_lights is None or color is None:
  55.             print("Error: 'num_lights' and 'color' are required for 'lightup' action.")
  56.             return
  57.         if not isinstance(num_lights, int) or num_lights <= 0 or num_lights > num_beads_global:
  58.             print("Error: 'num_lights' must be a positive integer and not greater than the number of beads.")
  59.             return
  60.         color_rgb = COLOR_DICT.get(color.lower())
  61.         if color_rgb is None:
  62.             print(f"Error: Unknown color '{color}'")
  63.             return
  64.         for i in range(num_lights):
  65.             led[i] = tuple(color_rgb)
  66.             led.write(i, color_rgb[0], color_rgb[1], color_rgb[2])
  67.         print(f"Lit up {num_lights} beads with color {color}.")
  68.     else:
  69.         print(f"Error: Unknown action '{action}'")
  70. tools = [
  71.     {
  72.         "type": "function",
  73.         "function": {
  74.             "name": "led_light_action",
  75.             "description": "This function performs actions on a LED light. It can initialize the LED light on a specific pin with a certain number of beads, or light up a certain number of beads in a specific color.",
  76.             "parameters": {
  77.                 "type": "object",
  78.                 "properties": {
  79.                     "action": {
  80.                     "type": "string",
  81.                     "enum": ["initialize", "lightup"],
  82.                     "description": "The action to perform. 'initialize' will set up the LED light on a specific pin with a certain number of beads. 'lightup' will light up a certain number of beads in a specific color."
  83.                 },
  84.                 "pin_number": {
  85.                     "type": "integer",
  86.                     "description": "The pin number where the LED light is connected. This is required when the 'action' is 'initialize'."
  87.                 },
  88.                 "num_lights": {
  89.                     "type": "integer",
  90.                     "description": "The number of beads to light up or initialize. This is required when the 'action' is 'initialize' or 'lightup'."
  91.                 },
  92.                 "color": {
  93.                     "type": "string",
  94.                     "description": "The color to use when lighting up the beads. This is required when the 'action' is 'lightup'."
  95.                 },
  96.             },
  97.             "required": ["action"],
  98.         },
  99.     },
  100. }
  101. ]
  102. messages = [
  103.     {"role": "system", "content": "You are a helpful assistant."},
  104. ]
  105. while True:
  106.     user_input = input("User: ")
  107.     messages.append({"role": "user", "content": user_input})
  108.     response = chat_completion_request(messages, tools)
  109.     if not isinstance(response, requests.Response):
  110.         print("Error: Failed to generate ChatCompletion response.")
  111.         break
  112.     print("Model JSON Response: ", response.json())  # 打印模型的 JSON 响应
  113.     model_response = response.json()["choices"][0]["message"]["content"]
  114.     print("Model: ", model_response)
  115.     #messages.append({"role": "assistant", "content": model_response})  # 添加模型的响应到消息历史
  116.     if "tool_calls" in response.json()["choices"][0]["message"]:
  117.         function_call = response.json()["choices"][0]["message"]["tool_calls"][0]["function"]
  118.         if function_call["name"] == "led_light_action":
  119.             arguments = json.loads(function_call["arguments"])
  120.             led_light_action(arguments["action"], arguments.get("pin_number"), arguments.get("num_lights"), arguments.get("color"))
复制代码

6.4添加语音交互
在代码基本功能实现好之后,接下来,来添加交互和页面设计。使用微软的语音服务实现用户语音转文本,以及模型回复文本转语音。
  1. import azure.cognitiveservices.speech as speechsdk
  2. from azure.cognitiveservices.speech import SpeechConfig, SpeechRecognizer, SpeechSynthesizer, AudioConfig
  3. # 初始化语音识别和语音合成服务
  4. speech_config = SpeechConfig(subscription=" ", region="eastus") #填入微软语音服务API
  5. recognizer = SpeechRecognizer(speech_config=speech_config)
  6. synthesizer = SpeechSynthesizer(speech_config=speech_config)
  7. import speechsdk
  8. # 语音转文本函数
  9. def recognize_from_microphone():
  10.     # 配置音频参数
  11.     audio_config = speechsdk.AudioConfig(use_default_microphone=True)
  12.    
  13.     # 创建语音识别器对象
  14.     speech_recognizer = speechsdk.SpeechRecognizer(speech_config=speech_config, audio_config=audio_config)
  15.    
  16.     # 异步进行一次语音识别
  17.     result = speech_recognizer.recognize_once_async().get()
  18.     # 根据识别结果进行处理
  19.     if result.reason == speechsdk.ResultReason.RecognizedSpeech:
  20.         print("识别的文本为: ", result.text)  # 将识别结果打印在终端
  21.         return result.text
  22.     elif result.reason == speechsdk.ResultReason.NoMatch:
  23.         print("无法识别任何语音: {}".format(result.no_match_details))
  24.     elif result.reason == speechsdk.ResultReason.Canceled:
  25.         # 处理取消的情况
  26.         cancellation_details = result.cancellation_details
  27.         print("语音识别被取消: {}".format(cancellation_details.reason))
  28.         if cancellation_details.reason == speechsdk.CancellationReason.Error:
  29.             print("错误详情: {}".format(cancellation_details.error_details))
  30.             print("你是否设置了语音资源密钥和区域值?")
  31. # 文本到语音合成函数
  32. def tts(text):
  33.     # 检查输入文本是否为空
  34.     if text is None:
  35.         print("没有文本可合成.")
  36.         return
  37.    
  38.     # 配置语音合成参数
  39.     speech_config.set_property(property_id=speechsdk.PropertyId.SpeechServiceResponse_RequestSentenceBoundary, value='true')
  40.    
  41.     # 配置音频输出参数
  42.     audio_config = speechsdk.audio.AudioOutputConfig(use_default_speaker=True)
  43.    
  44.     # 创建语音合成器对象
  45.     speech_synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config, audio_config=audio_config)
  46.    
  47.     # 异步进行文本到语音合成
  48.     speech_synthesis_result = speech_synthesizer.speak_text_async(text).get()
复制代码

接下来进行界面设计,添加录音的图片和提示语。
  1. from unihiker import Audio
  2. from unihiker import GUI
  3. # 初始化 Audio 类
  4. audio = Audio()
  5. # 初始化 GUI
  6. u_gui = GUI()
  7.     u_gui.draw_text(x=120, y=10, text="Start Home Assistant", origin='top', color="blue", font_size=15)
  8.     u_gui.draw_image(image="mic.jpg", x=120, y=230, w=180, h=50, origin='center', onclick=on_mic_click)
  9.     u_gui.draw_image(image="Mind.jpg", x=120, y=120, w=350, h=150, origin='center')
  10.     recording_status = u_gui.draw_text(x=120, y=300, text="Press to start recording", origin='center', color="blue", font_size=15)
复制代码

可以使用flag变量标记录音状态。
如果 flag 的值为 1,表示用户点击了麦克风按钮,程序将切换到录音状态,并调用 recognize_from_microphone 函数获取用户的语音输入。
如果 flag 的值为 2,表示用户的语音输入已经被处理,程序将调用模型生成回复,并执行相应的工具调用(如果存在)。
如果 flag 的值为 0,表示程序已经处理完用户的输入,准备好接受下一次录音。

  1. flag=0
  2. def on_mic_click():
  3.     global flag
  4.     flag = 1
  5. def main():
  6.     global flag
  7.     global user_input  # 声明 user_input 是一个全局变量
  8.     user_input = ''
  9.     u_gui.draw_text(x=120, y=10, text="Start Home Assistant", origin='top', color="blue", font_size=15)
  10.     u_gui.draw_image(image="mic.jpg", x=120, y=230, w=180, h=50, origin='center', onclick=on_mic_click)
  11.     u_gui.draw_image(image="Mind.jpg", x=120, y=120, w=350, h=150, origin='center')
  12.     recording_status = u_gui.draw_text(x=120, y=300, text="Press to start recording", origin='center', color="blue", font_size=15)
  13.     while True:
  14.         # 检查flag的值,并更新UI
  15.         if flag == 1:
  16.             recording_status.config(text="listening...")
  17.             user_input = recognize_from_microphone()  # 在这里获取用户的输入
  18.             time.sleep(3)
  19.             flag = 2  #
  20.         # 如果用户已经说完了,设置flag的值为2,并发送用户的输入到模型please
  21.         if user_input != "" and flag == 2:
  22.             recording_status.config(text="thinking...")
  23.             conversation.append({"role": "user", "content": user_input})
  24.             response = chat_completion_request(conversation, tools)
  25.             print("Model Response: ", response.json())
  26.             # 将模型的回复转换为语音并播放
  27.             try:
  28.                 assistant_response = response.json()["choices"][0]["message"]["content"]
  29.             except KeyError:
  30.                 assistant_response = "我已经成功为您操作"
  31.             if assistant_response is None:
  32.                 assistant_response="我已经成功为您操作"
  33.             print(f"Assistant: {assistant_response}")
  34.             #tts(assistant_response)
  35.             # 执行模型的回复中的工具调用
  36.             if 'tool_calls' in response.json()["choices"][0]["message"]:
  37.                 function_call = response.json()["choices"][0]["message"]["tool_calls"][0]["function"]
  38.                 arguments = json.loads(function_call["arguments"])
  39.                 if function_call["name"] == "play_music":
  40.                     play_music(arguments["music"])
  41.                 elif function_call["name"] == "fan_action":
  42.                     fan_action(arguments["action"], arguments.get("pin_number"), arguments.get("user_input"))
  43.                 elif function_call["name"] == "led_light_action":
  44.                     led_light_action(arguments["action"], arguments.get("pin_number"), arguments.get("num_lights"), arguments.get("color"))
  45.             conversation.append({"role": "assistant", "content": assistant_response})
  46.             flag = 0  # 设置flag的值为1,表示已经处理完用户的输入
  47.             recording_status.config(text="Press to start recording")
  48. if __name__ == "__main__":
  49.     main()
复制代码

七、完整程序
  1. import requests
  2. import json
  3. import openai
  4. import time
  5. import azure.cognitiveservices.speech as speechsdk
  6. from azure.cognitiveservices.speech import SpeechConfig, SpeechRecognizer, SpeechSynthesizer, AudioConfig
  7. import os
  8. from pinpong.extension.unihiker import *  
  9. from unihiker import Audio
  10. from unihiker import GUI
  11. from pinpong.board import Board, Pin,NeoPixel
  12. Board(" ").begin()
  13. # 初始化 Audio 类
  14. audio = Audio()
  15. # 初始化 GUI
  16. u_gui = GUI()
  17. flag=0
  18. # 初始化语音识别和语音合成服务
  19. speech_config = SpeechConfig(subscription=" ", region="eastus")
  20. recognizer = SpeechRecognizer(speech_config=speech_config)
  21. synthesizer = SpeechSynthesizer(speech_config=speech_config)
  22. #  OpenAI API 密钥
  23. OPENAI_API_KEY = ' '
  24. global fan, led, num_beads_global
  25. fan, led, num_beads_global = None, None, None
  26. pin_map = {
  27.     21: Pin.P21,
  28.     22: Pin.P22,
  29.     23: Pin.P23,
  30.     24: Pin.P24,
  31. }
  32. # 创建颜色字典
  33. COLOR_DICT = {
  34.     "red": [255, 0, 0],
  35.     "green": [0, 255, 0],
  36.     "blue": [0, 0, 255],
  37.     "white": [255, 255, 255],
  38.     "black": [0, 0, 0],
  39.     "yellow": [255, 255, 0],
  40.     "pink": [255, 105, 180],
  41.     "purple": [128, 0, 128]
  42. }
  43. def recognize_from_microphone():
  44.     audio_config = AudioConfig(use_default_microphone=True)
  45.     speech_recognizer = SpeechRecognizer(speech_config=speech_config, audio_config=audio_config)
  46.     result = speech_recognizer.recognize_once_async().get()
  47.     if result.reason == speechsdk.ResultReason.RecognizedSpeech:
  48.         print("识别的文本为: ", result.text)  # 这行代码将识别结果打印在终端
  49.         return result.text
  50.     elif result.reason == speechsdk.ResultReason.NoMatch:
  51.         print("No speech could be recognized: {}".format(result.no_match_details))   
  52.     elif result.reason == speechsdk.ResultReason.Canceled:
  53.         cancellation_details = result.cancellation_details
  54.         print("Speech Recognition canceled: {}".format(cancellation_details.reason))  
  55.         if cancellation_details.reason == speechsdk.CancellationReason.Error:
  56.             print("Error details: {}".format(cancellation_details.error_details))
  57.             print("Did you set the speech resource key and region values?")
  58. def tts(text):
  59.     if text is None:
  60.         print("No text to synthesize.")
  61.         return
  62.     speech_config.set_property(property_id=speechsdk.PropertyId.SpeechServiceResponse_RequestSentenceBoundary, value='true')
  63.     audio_config = speechsdk.audio.AudioOutputConfig(use_default_speaker=True)
  64.     speech_synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config, audio_config=audio_config)
  65.     speech_synthesis_result = speech_synthesizer.speak_text_async(text).get()
  66. def chat_completion_request(messages, tools=None, tool_choice=None):
  67.     headers = {
  68.         "Content-Type": "application/json",
  69.         "Authorization": "Bearer " + OPENAI_API_KEY,
  70.     }
  71.     json_data = {"model": "gpt-3.5-turbo-1106", "messages": messages}
  72.     if tools is not None:
  73.         json_data.update({"tools": tools})
  74.     if tool_choice is not None:
  75.         json_data.update({"tool_choice": tool_choice})
  76.     try:
  77.         response = requests.post(
  78.             "https://api.openai.com/v1/chat/completions",
  79.             headers=headers,
  80.             json=json_data,
  81.         )
  82.         return response
  83.     except Exception as e:
  84.         print("Unable to generate ChatCompletion response")
  85.         print(f"Exception: {e}")
  86.         return e
  87. def play_music(music):
  88.     if music in ["DADADADUM", "ENTERTAINER", "PRELUDE", "ODE", "NYAN", "RINGTONE", "FUNK", "BLUES", "BIRTHDAY", "WEDDING", "FUNERAL", "PUNCHLINE", "BADDY", "CHASE", "BA_DING", "WAWAWAWAA", "JUMP_UP", "JUMP_DOWN", "POWER_UP", "POWER_DOWN"]:
  89.         buzzer.play(getattr(buzzer, music), buzzer.OnceInBackground)
  90.         print(f"已调用,播放{music}")
  91.     else:
  92.         print("对不起,我这里没有这个音乐。")
  93. def fan_action(action, pin_number=None, user_input=None):
  94.     global fan
  95.     if action == "initialize":
  96.         if pin_number not in pin_map:
  97.             print(f"错误的引脚号:{pin_number}")
  98.             return
  99.         fan = Pin(pin_map[pin_number], Pin.PWM)
  100.         print(f"风扇已初始化,接在{pin_number}号端口")
  101.         assistant_response=(f"风扇已初始化,接在{pin_number}号端口")
  102.     else:
  103.         if fan is None:
  104.             print("好的,风扇尚未初始化")
  105.             return
  106.         if "turn on" in user_input:
  107.             fan.write_analog(800)
  108.             print("好的,风扇已开启")
  109.             assistant_response="好的,风扇已开启"
  110.         elif "turn off" in user_input:
  111.             fan.write_analog(0)
  112.             print("好的,风扇已关闭")
  113.             assistant_response="好的,风扇已关闭"
  114.         elif "increase" in user_input:
  115.             fan.write_analog(1023)
  116.             print("好的,风扇转速已增加")
  117.             assistant_response="好的,风扇转速已增加"
  118.         elif "decrease" in user_input:
  119.             fan.write_analog(512)
  120.             print("好的,风扇转速已减少")
  121.             assistant_response="好的,风扇转速已增加"
  122. def led_light_action(action, pin_number=None, num_lights=None, color=None):
  123.     global led
  124.     global num_beads_global
  125.     if action == "initialize":
  126.         if pin_number is None or num_lights is None:
  127.             print("错误,灯带'initialize' 动作需要 'pin_number' 和 'num_lights'。")
  128.             return
  129.         led = NeoPixel(Pin(pin_number), num_lights)
  130.         num_beads_global = num_lights
  131.         print(f"好的,现在我知道LED 灯已在引脚 {pin_number} 上初始化,有 {num_lights} 个灯珠。")
  132.         assistant_response=(f"好的,现在我知道LED 灯已在引脚 {pin_number} 上初始化,有 {num_lights} 个灯珠。")
  133.     elif action == "lightup":
  134.         if led is None:
  135.             print("错误:LED 灯尚未初始化。")
  136.             return
  137.         if num_lights is None or color is None:
  138.             print("错误:'lightup' 动作需要 'num_lights' 和 'color'。告诉我完整的")
  139.             return
  140.         if not isinstance(num_lights, int) or num_lights <= 0 or num_lights > num_beads_global:
  141.             print("错误:'num_lights' 必须是正整数,且不能大于初始化的灯珠数量。")
  142.             return
  143.         color_rgb = COLOR_DICT.get(color.lower())
  144.         if color_rgb is None:
  145.             print(f"错误:未知颜色 '{color}',请在颜色字典中添加这个颜色")
  146.             return
  147.         for i in range(num_lights):
  148.             led[i] = tuple(color_rgb)
  149.             led.write(i, color_rgb[0], color_rgb[1], color_rgb[2])
  150.         print(f"好的,我现在已点亮 {num_lights} 个灯珠,颜色为 {color}。")
  151.         assistant_response=(f"好的,我现在已点亮 {num_lights} 个灯珠,颜色为 {color}。")
  152.     else:
  153.         print(f"错误:未知操作 '{action}'")
  154. tools = [
  155.     {
  156.         "type": "function",
  157.         "function": {
  158.            "name": "play_music",
  159.             "description": "When the user requests to play music, this function is called.Plays music in the house when the user is bored or needs relaxation. The music list includes 'DADADADUM', 'ENTERTAINER', 'PRELUDE', 'ODE', 'NYAN', 'RINGTONE', 'FUNK', 'BLUES', 'BIRTHDAY', 'WEDDING', 'FUNERAL', 'PUNCHLINE', 'BADDY', 'CHASE', 'BA_DING', 'WAWAWAWAA', 'JUMP_UP', 'JUMP_DOWN', 'POWER_UP', 'POWER_DOWN'.",
  160.             "parameters": {
  161.                 "type": "object",
  162.                 "properties": {
  163.                     "music": {
  164.                         "type": "string",
  165.                         "enum": ["DADADADUM", "ENTERTAINER", "PRELUDE", "ODE", "NYAN", "RINGTONE", "FUNK", "BLUES", "BIRTHDAY", "WEDDING", "FUNERAL", "PUNCHLINE", "BADDY", "CHASE", "BA_DING", "WAWAWAWAA", "JUMP_UP", "JUMP_DOWN", "POWER_UP", "POWER_DOWN"],
  166.                         "description": "The music to play in the house."
  167.                     },
  168.                 },
  169.                 "required": ["music"],
  170.             },
  171.         }
  172.     },
  173.     {
  174.         "type": "function",
  175.         "function": {
  176.             "name": "fan_action",
  177.             "description": "This function initializes or controls the fan based on the given action and user's command. It can initialize the fan, turn on the fan, turn off the fan, increase the fan speed, or decrease the fan speed. It will print an error message if the fan has not been initialized.",
  178.             "parameters": {
  179.                 "type": "object",
  180.                 "properties": {
  181.                     "action": {
  182.                         "type": "string",
  183.                         "enum": ["initialize", "turn_on", "turn_off", "increase_speed", "decrease_speed"],
  184.                         "description": "The action to perform on the fan.",
  185.                     },
  186.                     "pin_number": {
  187.                         "type": "integer",
  188.                         "description": "The pin number where the fan is connected. Required for 'initialize' action.",
  189.                     },
  190.                     "user_input": {
  191.                         "type": "string",
  192.                         "description": "The user's command to control the fan. Required for 'turn on', 'turn off', 'increase speed', 'decrease speed' actions.",
  193.                     },
  194.                 },
  195.                 "required": ["action"],
  196.             },
  197.         },
  198.     },
  199.     {
  200.         "type": "function",
  201.         "function": {
  202.             "name": "led_light_action",
  203.             "description": "This function performs actions on a LED light. It can initialize the LED light on a specific pin with a certain number of beads, or light up a certain number of beads in a specific color.",
  204.             "parameters": {
  205.                 "type": "object",
  206.                 "properties": {
  207.                     "action": {
  208.                         "type": "string",
  209.                         "enum": ["initialize", "lightup"],
  210.                         "description": "The action to perform. 'initialize' will set up the LED light on a specific pin with a certain number of beads. 'lightup' will light up a certain number of beads in a specific color."
  211.                     },
  212.                     "pin_number": {
  213.                         "type": "integer",
  214.                         "description": "The pin number where the LED light is connected. This is required when the 'action' is 'initialize'."
  215.                     },
  216.                     "num_lights": {
  217.                         "type": "integer",
  218.                         "description": "The number of beads to light up or initialize. This is required when the 'action' is 'initialize' or 'lightup'."
  219.                     },
  220.                     "color": {
  221.                         "type": "string",
  222.                         "description": "The color to use when lighting up the beads. This is required when the 'action' is 'lightup'."
  223.                     },
  224.                 },
  225.                 "required": ["action"],
  226.             },
  227.         },
  228.     }
  229. ]
  230. conversation = [
  231.     {"role": "system", "content": "You are a smart home assistant that can play music for relaxation when the user is bored or needs it. You can suggest playing music when the user seems bored or tired.You are a helpful assistant that can also control a fan and a LED light."},
  232. ]
  233. flag=0
  234. def on_mic_click():
  235.     global flag
  236.     flag = 1
  237. def main():
  238.     global flag
  239.     global user_input  # 声明 user_input 是一个全局变量
  240.     user_input = ''
  241.     u_gui.draw_text(x=120, y=10, text="Start Home Assistant", origin='top', color="blue", font_size=15)
  242.     u_gui.draw_image(image="mic.jpg", x=120, y=230, w=180, h=50, origin='center', onclick=on_mic_click)
  243.     u_gui.draw_image(image="Mind.jpg", x=120, y=120, w=350, h=150, origin='center')
  244.     recording_status = u_gui.draw_text(x=120, y=300, text="Press to start recording", origin='center', color="blue", font_size=15)
  245.     while True:
  246.         # 检查flag的值,并更新UI
  247.         if flag == 1:
  248.             recording_status.config(text="listening...")
  249.             user_input = recognize_from_microphone()  # 在这里获取用户的输入
  250.             time.sleep(3)
  251.             flag = 2  #
  252.         # 如果用户已经说完了,设置flag的值为2,并发送用户的输入到模型please
  253.         if user_input != "" and flag == 2:
  254.             recording_status.config(text="thinking...")
  255.             conversation.append({"role": "user", "content": user_input})
  256.             response = chat_completion_request(conversation, tools)
  257.             print("Model Response: ", response.json())
  258.             
  259.             # 将模型的回复转换为语音并播放
  260.             try:
  261.                 assistant_response = response.json()["choices"][0]["message"]["content"]
  262.             except KeyError:
  263.                 assistant_response = "我已经成功为您操作"
  264.             
  265.             if assistant_response is None:
  266.                 assistant_response="我已经成功为您操作"
  267.             print(f"Assistant: {assistant_response}")
  268.             #tts(assistant_response)
  269.             # 执行模型的回复中的工具调用
  270.             if 'tool_calls' in response.json()["choices"][0]["message"]:
  271.                 function_call = response.json()["choices"][0]["message"]["tool_calls"][0]["function"]
  272.                 arguments = json.loads(function_call["arguments"])
  273.                 if function_call["name"] == "play_music":
  274.                     play_music(arguments["music"])
  275.                 elif function_call["name"] == "fan_action":
  276.                     fan_action(arguments["action"], arguments.get("pin_number"), arguments.get("user_input"))
  277.                 elif function_call["name"] == "led_light_action":
  278.                     led_light_action(arguments["action"], arguments.get("pin_number"), arguments.get("num_lights"), arguments.get("color"))
  279.             conversation.append({"role": "assistant", "content": assistant_response})
  280.             flag = 0  # 设置flag的值为1,表示已经处理完用户的输入
  281.             recording_status.config(text="Press to start recording")
  282. if __name__ == "__main__":
  283.     main()
复制代码



参考资料
OpenAI官方文档

行空板的板载硬件库
ChatGLM3-6B funtion calling官方文档
智谱AI技术文档


下载附件funtion calling项目 - 副本.zip


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail