Unity 接入阿里的全模态大模型Qwen2.5-Omni

news2025/7/16 19:25:35

1 参考

根据B站up主阴沉的怪咖  开源的项目的基础上修改接入

AI二次元老婆开源项目地址(unity-AI-Chat-Toolkit):

Github地址:https://github.com/zhangliwei7758/unity-AI-Chat-Toolkit

Gitee地址:https://gitee.com/DammonSpace/unity-ai-chat-toolkit

2 官网参考

找到官网发现没C#的案例,于是参考python的脚本,改为C#

阿里全模态的官方地址:阿里云百炼

3 语音输入部分

在基类LLM里添加下属代码

  public virtual void PostMsgAudio(string base64Audio, Action<string> _callback, Action<bool> _endCallBack = null, Action<AudioClip> _AudioCallBack = null)
  {
      //上下文条数设置
      CheckHistory();
      //提示词处理
      string message = "当前为角色的人物设定:" + m_Prompt +
          " 回答的语言:" + lan;

      //缓存发送的信息列表
      Content content = new Content()
      {
          type = "input_audio",
          input_audio = new Input_audio()
          {
              data = string.Format("data:;base64,{0}", base64Audio),
              format = "mp3"
          }
      };
      Content content2 = new Content()
      {
          type = "text",
          text = message
      };

      Content[] contents = new Content[] { content, content2 };

      m_DataAudioList.Add(new SendDataAudio("user", contents));
      StartCoroutine(RequestAudio(message, _callback, _endCallBack, _AudioCallBack));
  }

   public virtual IEnumerator RequestAudio(string _postWord, System.Action<string> _callback, Action<bool> _endCallBack = null, Action<AudioClip> _AudioCallBack = null)
   {
       yield return new WaitForEndOfFrame();

   }

    [Serializable]
    public class SendDataAudio
    {
        [SerializeField] public string role;
        [SerializeField] public Content[] content;
        public SendDataAudio() { }
        public SendDataAudio(string _role, Content[] _content)
        {
            role = _role;
            content = _content;
        }
    }

    [Serializable]
    public class Content
    {
        [SerializeField] public string type;
        [SerializeField] public Input_audio input_audio;
        [SerializeField] public string text;
    }

    [Serializable]
    public class Input_audio
    {
        [SerializeField] public string data;
        [SerializeField] public string format;
    }

4 语音解析部分

新添加一个类AliQwenOmniChat,继承LLM

using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System;
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using static ALiChat;

public class AliQwenOmniChat : LLM
{
    public AliQwenOmniChat()
    {
        url = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions";
    }

    /// <summary>
    /// api key
    /// </summary>
    [SerializeField] private string api_key;
    /// <summary>
    /// AI设定
    /// </summary>
    public string m_SystemSetting = string.Empty;
    /// <summary>
    /// qwen-omni-turbo-0119
    /// </summary>
    public string m_gptModel = "qwen-omni-turbo-0119";

    [Header("设置说话的声音")] public SpeekerRole per = SpeekerRole.Cherry;
    private void Start()
    {
        //运行时,添加AI设定
        m_DataList.Add(new SendData("system", m_SystemSetting));
    }

    /// <summary>
    /// 发送消息
    /// </summary>
    /// <returns></returns>
    public override void PostMsgAudio(string _msg, Action<string> _callback, Action<bool> endAction, Action<AudioClip> AudioAction)
    {
        base.PostMsgAudio(_msg, _callback, endAction, AudioAction);
    }

    public override IEnumerator RequestAudio(string requestData, Action<string> callback, Action<bool> EndAction, Action<AudioClip> AudioAction)
    {
        using (var request = new UnityWebRequest(url, "POST"))
        {
            PostDataAudio _postData = new PostDataAudio
            {
                model = m_gptModel,
                stream = this.stream,
                messages = m_DataAudioList,
                temperature = 1,
                top_p = 0.7f,
                modalities = new string[] { "text", "audio" },
                audio = new Audio { voice = SetSpeeker(per), format = "wav" },
                stream_options = new Stream_options { include_usage = true },
            };

            string _jsonText = JsonUtility.ToJson(_postData).Trim();
            Debug.Log(_jsonText);

            byte[] data = System.Text.Encoding.UTF8.GetBytes(_jsonText);
            request.uploadHandler = (UploadHandler)new UploadHandlerRaw(data);
            request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();

            request.SetRequestHeader("Content-Type", "application/json");
            request.SetRequestHeader("Authorization", string.Format("Bearer {0}", api_key));
            yield return request.SendWebRequest();

            if (request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.ProtocolError)
            {
                Debug.LogError("阿里Error: " + request.error);
                callback?.Invoke("阿里大模型出现点问题");
                yield break;
            }
            string temp = request.downloadHandler.text;
            var datas = temp.Split("data:");
            string textStr = "";
            string audioStr = "";
            foreach (var requestJson in datas)
            {
                if (string.IsNullOrEmpty(requestJson))
                    continue;
                Debug.Log(requestJson);
                var jsonP = JToken.Parse(requestJson);
                var item = jsonP["choices"][0];
                var audio = item["delta"].SelectToken("audio");
                if (audio != null)
                {
                    if (audio.SelectToken("transcript") != null)
                    {
                        var tt = audio.SelectToken("transcript")?.ToString();//文字部分
                        if (!string.IsNullOrEmpty(tt))
                        {
                            tt = tt.Trim();
                            textStr += tt;
                        }
                        var finish = item.SelectToken("finish_reason");

                        if (finish != null && finish.ToString() == "stop")
                        {
                            break;
                        }
                    }
                    else
                    {
                        audioStr += audio.SelectToken("data")?.ToString();//语音部分
                    }
                }
            }

            if (!string.IsNullOrEmpty(textStr))
            {
                callback.Invoke(textStr);
            }
            if (!string.IsNullOrEmpty(audioStr))
            {
                AudioAction(PlayAudio(audioStr));
            }
            EndAction.Invoke(true);

        }
    }

    //解析输出的Base64 编码的音频数据
    AudioClip PlayAudio(string audioString)
    {
        if (!string.IsNullOrEmpty(audioString))
        {
            byte[] audioBytes = Convert.FromBase64String(audioString);
            AudioClip audioClip = WavUtility.ConvertBytesToAudioClip(audioBytes, 24000);
            return audioClip;
        }

        return null;
    }

    //阿里提供的四种支持的音色
    private string SetSpeeker(SpeekerRole _role)
    {
        if (_role == SpeekerRole.Cherry) return "Cherry";
        if (_role == SpeekerRole.Serena) return "Serena";
        if (_role == SpeekerRole.Ethan) return "Ethan";
        if (_role == SpeekerRole.Chelsie) return "Chelsie";

        return "Cherry";//默认为音色Cherry
    }


    #region 数据包

    [Serializable]
    public class PostDataAudio
    {
        [SerializeField] public string model;
        [SerializeField] public bool stream;
        [SerializeField] public List<SendDataAudio> messages;
        [SerializeField] public float temperature = 0.7f;
        [SerializeField] public float top_p;
        [SerializeField] public string[] modalities;
        [SerializeField] public Audio audio;
        [SerializeField] public Stream_options stream_options;
    }

    [Serializable]
    public class Audio
    {
        public string voice;
        public string format;
    }

    [Serializable]
    public class Stream_options
    {
        public bool include_usage;
    }
    #endregion
    public enum SpeekerRole
    {
        Cherry,
        Serena,
        Ethan,
        Chelsie
    }
}

5 测试

输入需要语音输入时,找到开源项目里的录音结束处理的AcceptClip方法修改为:

  public bool AliQwenOmniChat = false;  
  private Queue<string> strDatas = new Queue<string>();
  private Queue<AudioClip> clipDatas = new Queue<AudioClip>();
  private bool end = true;

 private void AcceptClip(AudioClip _audioClip)
 {
     if (m_ChatSettings.m_SpeechToText == null)
         return;
     if (AliQwenOmniChat)//阿里全模态语音输入时
     {
         byte[] _audioData = WavUtility.FromAudioClip(_audioClip);
         string base64String = Convert.ToBase64String(_audioData);
         m_ChatSettings.m_ChatModel.PostMsgAudio(base64String, CallBack, EndCallBack, AudioCallBack);//阿里语音输入

         m_InputWord.text = "阿里语音输入完成";
     }
     else
     {
         m_ChatSettings.m_SpeechToText.SpeechToText(_audioClip, DealingTextCallback);
     }
 }

 private void EndCallBack(bool isCompate)
 {

     Debug.Log("是否回到结束:" + isCompate);
     this.end = isCompate;

 }

  private void CallBack(string _response)//文字回调
  {
      _response = _response.Trim();
      //m_TextBack.text = "";
      //Debug.Log("收到AI回复:" + _response);
      if (GetMesssgeIndex == 0)
      {
          m_TextBack2.text = "";
          //切换到说话动作
          Debug.Log("播放声音******");
          m_TextBack.text = "";         
          SetAnimator("state", 2);
      }
      GetMesssgeIndex++;
      if (!string.IsNullOrEmpty(_response))
      {
          if (Ali)
              //阿里多模态直接返回语音  放到队列里面          
              strDatas.Enqueue(_response);
          else
              m_ChatSettings.m_TextToSpeech.Speak(_response, PlayAudio);
      }

//添加声音回调的方法
 private void AudioCallBack(AudioClip clip)
 {
     clipDatas.Enqueue(clip);
 }

  private void Update()
  {

      if (AliQwenOmniChat)
      {
          if (strDatas.Count > 0 && m_WriteState == false)
          {
              StartTypeWords(strDatas.Dequeue());
          }

          if (clipDatas.Count > 0 && m_AudioSource.isPlaying == false)
          {
              m_AudioSource.clip = clipDatas.Dequeue();
              m_AudioSource.Play();//返回的语音播放
              isEnd = false;
          }
          else if (m_AudioSource.isPlaying == false && this.end)
          {
              if (isEnd)
              {
                  return;
              }
              isEnd = true;
              m_ChatHistory.Add(m_TextBack.text);
              m_AudioSource.Stop();
              resultDatas.Clear();
              GetMesssgeIndex = 0;
              切换到等待动作
              Debug.Log("切换到等待动作");             
              SetAnimator("state", 0);
          }
      }
  
  }


文字输入和开源项目里的原先输入一样。

语音输入测试:

我用声音问:你叫什么名字?

输入的打印:

语音输入后,返回了文字和声音,返回的打印:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2346784.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nginx知识点

Nginx发展历史 Nginx 是由俄罗斯程序员 Igor Sysoev 开发的高性能开源 Web 服务器、反向代理服务器和负载均衡器 &#xff0c;其历史如下&#xff1a; 起源与早期开发&#xff08;2002 - 2004 年&#xff09; 2002 年&#xff0c;当时 Igor Sysoev 在为俄罗斯门户网站 Rambl…

Mysql从入门到精通day6————时间和日期函数精讲

关于Mysql的日期和时间计算函数种类非常繁多,此处我们对常用的一些函数的用法通过实例演示让读者体会他们的用法,文章末尾也给出了时间和日期计算的全部函数 函数1:curdate()和current_date()函数 作用:获取当前日期 select curdate(),current_date();运行效果:

逻辑漏洞安全

逻辑漏洞是指由于程序逻辑不严导致一些逻辑分支处理错误造成的漏洞。 在实际开发中&#xff0c;因为开发者水平不一没有安全意识&#xff0c;而且业务发展迅速内部测试没有及时到位&#xff0c;所以常常会出现类似的漏洞。 由于开发者/设计者在开发过程中&#xff0c;由于代码…

Github 热点项目 rowboat 一句话生成多AI智能体!5分钟搭建企业级智能工作流系统

今日高星项目推荐&#xff1a;rowboat凭借1705总星数成为智能协作工具黑马&#xff01;亮点速递&#xff1a;①自然语言秒变AI流水线——只需告诉它“帮外卖公司处理配送异常”&#xff0c;立刻生成多角色协作方案&#xff1b;②企业工具库即插即用&#xff0c;Python包HTTP接口…

(26)VTK C++开发示例 ---将点坐标写入PLY文件

文章目录 1. 概述2. CMake链接VTK3. main.cpp文件4. 演示效果 更多精彩内容&#x1f449;内容导航 &#x1f448;&#x1f449;VTK开发 &#x1f448; 1. 概述 本示例演示了将球体数据写入ply文件&#xff0c;并从ply文件读取显示&#xff1b; PLY 文件&#xff08;Polygon Fil…

2025蓝桥省赛c++B组第二场题解

前言 这场的题目非常的简单啊&#xff0c;至于为什么有第二场&#xff0c;因为当时河北正在刮大风被迫停止了QwQ&#xff0c;个人感觉是历年来最简单的一场&#xff0c;如果有什么不足之处&#xff0c;还望补充。 试题 A: 密密摆放 【问题描述】 小蓝有一个大箱子&#xff0…

vue3 vite打包后动态修改打包后的请求路径,无需打多个包给后端

整体思路和需求 部署多个服务器环境的时候&#xff0c;需要多次打包很麻烦&#xff0c;所以需要打包之后动态的修改 1.创建一个webconfig文件夹 2.在自己封装的接口文件中 判断是否在生产环境&#xff0c;然后将数据保存到vuex 中 代码&#xff1a; // 创建axios服务的函数 …

Nacos-SpringBoot 配置无法自动刷新问题排查

背景 Nacos SpringBoot版本中&#xff0c;提供了NacosValue注解&#xff0c;支持控制台修改值时&#xff0c;自动刷新&#xff0c;但是今天遇见了无法自动刷新的问题。 环境 SpringBoot 2.2.x nacos-client&#xff1a;2.1.0 nacos-config-spring-boot-starter&#xff1a;0…

【RabbitMQ消息队列】详解(一)

初识RabbitMQ RabbitMQ 是一个开源的消息代理软件&#xff0c;也被称为消息队列中间件&#xff0c;它遵循 AMQP&#xff08;高级消息队列协议&#xff09;&#xff0c;并且支持多种其他消息协议。 核心概念 生产者&#xff08;Producer&#xff09;&#xff1a;创建消息并将其…

Jenkins Pipeline 构建 CI/CD 流程

文章目录 jenkins 安装jenkins 配置jenkins 快速上手在 jenkins 中创建一个新的 Pipeline 作业配置Pipeline运行 Pipeline 作业 Pipeline概述Declarative PipelineScripted Pipeline jenkins 安装 安装环境&#xff1a; Linux CentOS 10&#xff1a;Linux CentOS9安装配置Jav…

AJAX 介绍

一、什么是AJAX ? AJAX 是 异步的 JavaScript 和 XML&#xff08;Asynchronous JavaScript And XML&#xff09; 的缩写&#xff0c;是一种实现浏览器与服务器进行数据通信的技术。其核心是通过 XMLHttpRequest 对象在不重新刷新页面的前提下&#xff0c;与服务器交换数据并更…

promis(resolve,reject)入门级别

JavaScript Promise 的定义 Promise 是一种用于处理异步操作的对象&#xff0c;表示一个可能已经完成或者尚未完成的操作的结果。它的核心作用在于简化复杂的回调嵌套问题&#xff08;即所谓的“回调地狱”&#xff09;&#xff0c;使异步代码更加清晰易读。 Promise 的状态 …

w~嵌入式C语言~合集6

我自己的原文哦~ https://blog.51cto.com/whaosoft/13870384 一、开源MCU简易数字示波器项目 这是一款采用STC8A8K MCU制造的简单示波器&#xff0c;只有零星组件&#xff0c;易于成型。这些功能可以涵盖简单的测量&#xff1a; 该作品主要的规格如下&#xff1a; 单片机…

学习海康VisionMaster之路径提取

一&#xff1a;进一步学习了 今天学习下VisionMaster中的路径提取&#xff1a;可在绘制的路径上等间隔取点或查找边缘点 二&#xff1a;开始学习 1&#xff1a;什么是路径提取&#xff1f; 相当于事先指定一段路径&#xff0c;然后在对应的路径上查找边缘&#xff0c;这个也是…

第35课 常用快捷操作——用“鼠标左键”拖动图元

概述 拖动某个图元&#xff0c;是设计过程中常需要用到的操作&#xff0c;我们可以在原理图中拖动某个元器件符号&#xff0c;也可以在PCB图中拖动某个焊盘。 和常用的软件类似&#xff0c;用按住鼠标左键的方式来完成拖动操作。 用鼠标左键拖动图元 在想要拖动的图元上&…

二、Web服务常用的I/O操作

一、单个或者批量上传文件 前端&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>文件…

「Mac畅玩AIGC与多模态04」开发篇01 - 创建第一个 LLM 对话应用

一、概述 本篇介绍如何在 macOS 环境下&#xff0c;基于已部署完成的 Dify 平台和本地 LLM 模型&#xff08;如 DeepSeek&#xff09;&#xff0c;创建并测试第一个基础对话应用&#xff0c;实现快速验证推理服务与平台交互功能。 二、应用创建流程 1. 通过首页创建应用 打…

深度探究获取淘宝商品数据的途径|API接口|批量自动化采集商品数据

在电商行业竞争日益激烈的今天&#xff0c;淘宝商品数据如同蕴藏巨大价值的宝藏&#xff0c;无论是商家进行竞品分析、优化商品策略&#xff0c;还是数据分析师挖掘市场趋势&#xff0c;都离不开对这些数据的获取与分析。本文将深入探讨获取淘宝商品数据的多种途径&#xff0c;…

马哥教育Linux云计算运维课程

课程大小&#xff1a;19.1G 课程下载&#xff1a;https://download.csdn.net/download/m0_66047725/90640128 更多资源下载&#xff1a;关注我 你是否找了很多资料看了很多视频聊了很多群友&#xff0c;却发现自己技术仍然原地踏步&#xff1f;本教程联合BAT一线导师倾囊相授…

FPGA与边缘AI:计算革命的前沿力量

在数字化转型浪潮中&#xff0c;边缘计算和人工智能正引领着技术革命。而FPGA&#xff08;现场可编程门阵列&#xff09;作为一种独特的硬件架构&#xff0c;正逐渐成为边缘AI领域的关键推动力。本文将探讨FPGA与边缘AI的结合如何重塑我们的数字世界&#xff0c;以及这一技术融…