您现在的位置是:首页 >其他 >OpenAI ChatGPT Unity接入网站首页其他

OpenAI ChatGPT Unity接入

Maddie_Mo 2024-05-31 09:39:05
简介OpenAI ChatGPT Unity接入

OpenAI ChatGPT Unity接入

OpenAi-API-Unity 方法

OpenAi-API-Unity 下载

GitHub: OpenAi-API-Unity 主页

GitHub: OpenAi-API-Unity 安装包 下载地址

既然 Unity 都给了下载地址都给了,怎么能少了 UE 呢。都在下面了

GitHub: OpenAi-API-Unreal 主页

GitHub: OpenAi-API-Unreal 安装包 下载地址

反正我下载的是 0.2.9 版本 你们按需下载就行。
如果都下载不了的话 我 CSDN 上面上传的也有 巴拉一下就能找到。

请添加图片描述

为了防止出现意外 最好两个 安装包都下载。

请添加图片描述

本地配置

打开我的电脑 在地址栏键入:
Windows:%USERPROFILE%/
Mac:~/

请添加图片描述

找到 .openai 文件夹并打开。
如果没有就自己新建一个。

请添加图片描述

找到 auth.json 文件夹并打开。
如果没有就自己新建一个。

请添加图片描述

找到 auth.json 文件夹并打开。
如果没有就自己新建一个。
下面是里面的内容注意:填写自己OpenAI key 的时候没有尖括号!没有尖括号!没有尖括号!
重要的事情说三遍。
{
  "private_api_key":"<YOUR_KEY>"
}

请添加图片描述

Unity 模块

新建一个新的项目 打开 Package Manager。

请添加图片描述

URL接入

点击 git URL 按钮

请添加图片描述

键入:https://github.com/hexthedev/OpenAi-Api-Unity.git
如果成功会有 Open AI 插件显示。
如果没下再试试下面的方法。

请添加图片描述

gz 接入

点击 gz 导入按钮

请添加图片描述

点击刚才下载好的 压缩包
如果成功会有 Open AI 插件显示。
如果没下再试试下面的的方法。

请添加图片描述

json 接入

这个绝对能成!
点击 磁盘导入按钮。

请添加图片描述

解压刚才下载 OpenAi-Api-Unity-0.2.9.zip 压缩包
打开文件夹到根目录。
点击 package.json 文本并打开

请添加图片描述

你就会发现 成功了!旋转 跳跃。

请添加图片描述

Open AI

在菜单栏 点击 OpenAi -> Examples -> Chat at Runtime

请添加图片描述

然后就会打开一个新的场景。
好了,点击运行。在这里你就可以,跟 OpenAI 对话了。

请添加图片描述

请添加图片描述

OpenAi-Api-Unity 插件文档

GitHub: OpenAi-Api-Unity 插件文档 地址

请添加图片描述

OpenAi 本地化接入 Unity 方法

Unity 关键字识别

当前脚本主要作用是:使用 Unity 自带的 PhraseRecognizer 类 进行关键字识别。
识别通过之后响应 之后的逻辑执行。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Windows.Speech;

/// <summary>
/// 关键字识别
/// </summary>
public class KeywordRecognition_ZH : MonoBehaviour
{
    public static KeywordRecognition_ZH _Instance;
    //语音识别短句
    private PhraseRecognizer _PhraseASR;


    [Header("精度")]
    public ConfidenceLevel _PrecisionASR = ConfidenceLevel.Medium;

    [Header("关键字数组")]
    public string[] _ListStrASR = { "启动", "关闭", "小哟" };

    //麦克风
    private string _Device;

    [Header("智能回答布尔")]
    public bool _OpenASRbool;

    void Start()
    {
        _Instance = this;

        //获取麦克风设备
        _Device = Microphone.devices[0];

        print(_Device);


        //用设备开始录音
        //设备的名称
        //指示当达到长度秒时录音是否应该继续录制,并从音频剪辑开始绕圈录制
        //录音产生的音频剪辑的长度 
        //44100 音频采样率
        //_AuduioASR = Microphone.Start(_Device, true, 999, 44100);

        if (_PhraseASR == null)
        {
            //创建语音识别
            _PhraseASR = new KeywordRecognizer(_ListStrASR, _PrecisionASR);
            //添加广播事件
            _PhraseASR.OnPhraseRecognized += MonitorASR;
            //开启语音识别
            _PhraseASR.Start();

            Debug.Log("创建识别器成功");
        }
    }


    /// <summary>
    /// 语音识别监听
    /// 提供关于短语识别事件的信息
    /// </summary>
    /// <param 识别信息="_Args"></param>
    private void MonitorASR(PhraseRecognizedEventArgs _Args)
    {
        switch (_Args.text)
        {
            case "启动":
                //每次回复前清空 输入问题
                GetOpenAI_ZH._Instance._InputProblemText.text = "";

                VoskSpeechToText._Instance._SpeakBool = true;
                VoskSpeechToText._Instance.ToggleRecording();

                _OpenASRbool = true;

                break;

            case "小哟":

                VoskSpeechToText._Instance._SpeakBool = true;
                VoskSpeechToText._Instance.ToggleRecording();
                _OpenASRbool = true;

                break;


            case "关闭":
                VoskSpeechToText._Instance._SpeakBool = false;
                VoskSpeechToText._Instance.ToggleRecording();
                break;


            default:
                break;
        }
        print(_Args.text);
    }

    private void OnDestroy()
    {
        //判断场景中是否存在语音识别
        if (_PhraseASR != null)
        {
            //语音识别释放
            _PhraseASR.Dispose();
        }

    }
}

语音合成 & 文字转语音

根据百度提供的 API 文档进行文字转语音输出。
using Baidu.Aip.Speech;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Http;
using UnityEngine;
using UnityEngine.Networking;

/// <summary>
/// 语音合成 & 文字转语音
/// </summary>
public class VoiceSynthesis_ZH : MonoBehaviour
{

    [Header("音源")]
    public AudioSource _Audio;

    [Header("AI 发声器")]
    public Pronouncer _Pronouncer = Pronouncer.Duyaya;

    //全局变量
    public static VoiceSynthesis_ZH _World;

    //网页文字转语音
    private string _Url;

    //百度语音识别SDK
    private Asr _AipClient;



    private void Start()
    {
        _World = this;
        //StartCoroutine(GetAudioClip("开始"));
    }


    //获取 Web网页音源信息并播放
    public IEnumerator GetAudioClip(string AudioText)
    {
        if (AudioText!=null)
        {

            _Url = "http://tsn.baidu.com/text2audio?tex=" + AudioText +
                  "&tok=自己的Token" +
                  "&cuid=a7a0e3326da873c6fb0609e6385a82b934c9cb11" +
                  "&ctp=1" +
                  "&lan=zh" +
                  "&spd=5" +
                  "&pit=5" +
                  "&vol=10" +
                  "&per=" + (((int)_Pronouncer).ToString()) +
                  "&aue=6";


            using (UnityWebRequest _AudioWeb = UnityWebRequestMultimedia.GetAudioClip(_Url, AudioType.WAV))
            {

                yield return _AudioWeb.SendWebRequest();
                if (_AudioWeb.isNetworkError)
                {
                    yield break;
                }
                AudioClip _Cli = DownloadHandlerAudioClip.GetContent(_AudioWeb);
                _Audio.clip = _Cli;
                _Audio.Play();
            }
        }      
    }

    /// <summary>
    /// 获取 Web网页音源信息并播放 附带延迟时间
    /// </summary>
    /// <param 播放文字="AudioText"></param>
    /// <param 延迟时间="_DelayedTimer"></param>
    /// <returns></returns>
    public IEnumerator GetAudioClip(string AudioText, float _DelayedTimer)
    {
        yield return new WaitForSeconds(_DelayedTimer);


        _Url = "http://tsn.baidu.com/text2audio?tex=" + AudioText +
              "&tok=自己的Token" +
              "&cuid=a7a0e3326da873c6fb0609e6385a82b934c9cb11" +
              "&ctp=1" +
              "&lan=zh" +
              "&spd=5" +
              "&pit=5" +
              "&vol=10" +
              "&per=" + (((int)_Pronouncer).ToString()) +
              "&aue=6";


        using (UnityWebRequest _AudioWeb = UnityWebRequestMultimedia.GetAudioClip(_Url, AudioType.WAV))
        {

            yield return _AudioWeb.SendWebRequest();
            if (_AudioWeb.isNetworkError)
            {
                yield break;
            }
            AudioClip _Cli = DownloadHandlerAudioClip.GetContent(_AudioWeb);
            _Audio.clip = _Cli;


            _Audio.Play();
        }
    }

    /// <summary>
    /// 语音识别
    /// </summary>
    /// <param 录取音频 = "_Clip" ></ param >
    /// < returns ></ returns >
    private IEnumerator Recognition(AudioClip _Clip)
    {
        //开放 音频 长度
        float[] _Sample = new float[_Clip.samples];

        //用片段中的样本数据填充数组
        _Clip.GetData(_Sample, 0);

        //数据转换
        short[] _IntData = new short[_Sample.Length];
        byte[] _ByteData = new byte[_IntData.Length * 2];
        for (int i = 0; i < _Sample.Length; i++)
        {
            _IntData[i] = (short)(_Sample[i] * short.MaxValue);
        }
        Buffer.BlockCopy(_IntData, 0, _ByteData, 0, _ByteData.Length);

        //返回Json数据  (数据 格式 码率)
        var _Result = _AipClient.Recognize(_ByteData, "pcm", 16000);

        //获取Json 数据中的 讲话内容
        var _Speaking = _Result.GetValue("result");

        //检测是否有内容
        if (_Speaking == null)
        {
            StopAllCoroutines();
            yield return null;
        }

        //讲话内容转换为 字符串
        string _UsefulText = _Speaking.First.ToString();
        print(_UsefulText);


        yield return null;
    }


    /// <summary>
    /// 访问令牌获取
    /// </summary>
    /// <returns></returns>
    private static class AccessToken

    {
        // 调用getAccessToken()获取的 access_token建议根据expires_in 时间 设置缓存
        // 返回token示例
        public static String TOKEN = "自己的Token";

        // 百度云中开通对应服务应用的 API Key 建议开通应用的时候多选服务
        private static String clientId = "百度云应用的AK";
        // 百度云中开通对应服务应用的 Secret Key 
        private static String clientSecret = "百度云应用的SK";

        public static String GetAccessToken()
        {
            String authHost = "https://aip.baidubce.com/oauth/2.0/token";
            HttpClient client = new HttpClient();
            List<KeyValuePair<string, string>> paraList = new List<KeyValuePair<string, string>>();
            paraList.Add(new KeyValuePair<string, string>("grant_type", "client_credentials"));
            paraList.Add(new KeyValuePair<string, string>("client_id", clientId));
            paraList.Add(new KeyValuePair<string, string>("client_secret", clientSecret));

            HttpResponseMessage response = client.PostAsync(authHost, new FormUrlEncodedContent(paraList)).Result;
            String result = response.Content.ReadAsStringAsync().Result;
            Console.WriteLine(result);
            return result;
        }
    }

    /// <summary>
    /// AI 发音器
    /// </summary>
    public enum Pronouncer
    {
        //普通女声
        Female,
        //普通男生
        Male,
        //特殊男声
        Teshunan,
        //情感合成男生
        Duxiaoyao,
        //情感合成女生
        Duyaya
    }


    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Escape))
        {
            Application.Quit();
        }
    }
}

音频记录 & 实时音频处理


using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


/// <summary>
/// 记录音频并为实时音频处理提供帧
/// </summary>
public class VoiceProcessor : MonoBehaviour
{
    /// <summary>
    /// 指示麦克风是否被捕获
    /// </summary>
    public bool IsRecording
    {
        get { return _audioClip != null && Microphone.IsRecording(CurrentDeviceName); }
    }

    [Header("麦克风")]
    [SerializeField] private int _MicrophoneIndex;

    /// <summary>
    /// 录制音频的采样率
    /// </summary>
    public int SampleRate { get; private set; }

    /// <summary>
    /// 传送的音频帧的大小
    /// </summary>
    public int FrameLength { get; private set; }

    /// <summary>
    /// 传递音频帧的事件
    /// </summary>
    public event Action<short[]> OnFrameCaptured;

    /// <summary>
    /// 事件,当音频捕获线程停止时
    /// </summary>
    public event Action OnRecordingStop;

    /// <summary>
    /// 事件,当音频捕获线程启动时
    /// </summary>
    public event Action OnRecordingStart;

    /// <summary>
    /// 可用的录音设备
    /// </summary>
    public List<string> Devices { get; private set; }

    /// <summary>
    /// 所选录音设备的索引
    /// </summary>
    public int CurrentDeviceIndex { get; private set; }

    /// <summary>
    /// 所选录音设备的名称
    /// </summary>
    public string CurrentDeviceName
    {
        get
        {
            if (CurrentDeviceIndex < 0 || CurrentDeviceIndex >= Microphone.devices.Length)
                return string.Empty;
            return Devices[CurrentDeviceIndex];
        }
    }

    [Header("语音最小音量检测")]
    [SerializeField, Tooltip("检测语音输入的最小音量"), Range(0.0f, 1.0f)]
    private float _MinimumSpeakingSampleValue = 0.05f;

    [SerializeField, Tooltip("发送语音请求前检测到的沉默时间(以秒为单位)")]
    [Header("语音发送沉默时间")]
    private float _SilenceTimer = 1.0f;

    [SerializeField, Tooltip("使用音量阈值自动检测语音。")]
    [Header("语音检测布尔")]
    private bool _AutoDetect;

    private float _TimeAtSilenceBegan;
    private bool _AudioDetected;
    private bool _DidDetect;
    private bool _Transmit;


    AudioClip _audioClip;
    private event Action RestartRecording;

    void Awake()
    {
        UpdateDevices();
    }
#if UNITY_EDITOR
    void Update()
    {
        if (CurrentDeviceIndex != _MicrophoneIndex)
        {
            ChangeDevice(_MicrophoneIndex);
        }
    }
#endif

    /// <summary>
    /// 更新可用音频设备列表
    /// </summary>
    public void UpdateDevices()
    {
        Devices = new List<string>();
        foreach (var device in Microphone.devices)
            Devices.Add(device);

        if (Devices == null || Devices.Count == 0)
        {
            CurrentDeviceIndex = -1;
            Debug.LogError("没有连接有效的录音设备");
            return;
        }

        CurrentDeviceIndex = _MicrophoneIndex;
    }

    /// <summary>
    /// 更换录音设备
    /// </summary>
    /// <param name="deviceIndex">新音频捕获设备的索引</param>
    public void ChangeDevice(int deviceIndex)
    {
        if (deviceIndex < 0 || deviceIndex >= Devices.Count)
        {
            Debug.LogError(string.Format("指定的设备索引{0}不是有效的记录设备", deviceIndex));
            return;
        }

        if (IsRecording)
        {
            // 用新设备重新开始录制的一次性事件
            // 最后一个会话完成的时刻
            RestartRecording += () =>
            {
                CurrentDeviceIndex = deviceIndex;
                StartRecording(SampleRate, FrameLength);
                RestartRecording = null;
            };
            StopRecording();
        }
        else
        {
            CurrentDeviceIndex = deviceIndex;
        }
    }

    /// <summary>
    /// 开始录音
    /// </summary>
    /// <param name="sampleRate">记录的采样率</param>
    /// <param name="frameSize">要传送的音频帧的大小</param>
    /// <param name="autoDetect">音频是否应该根据音量连续记录</param>
    public void StartRecording(int sampleRate = 16000, int frameSize = 512, bool ?autoDetect = null)
    {
        if (autoDetect != null)
        {
            _AutoDetect = (bool) autoDetect;
        }

        if (IsRecording)
        {
            // 如果采样率或帧大小已经改变,重新开始记录
            if (sampleRate != SampleRate || frameSize != FrameLength)
            {
                RestartRecording += () =>
                {
                    StartRecording(SampleRate, FrameLength, autoDetect);
                    RestartRecording = null;
                };
                StopRecording();
            }

            return;
        }

        SampleRate = sampleRate;
        FrameLength = frameSize;

        _audioClip = Microphone.Start(CurrentDeviceName, true, 1, sampleRate);

        StartCoroutine(RecordData());
    }

    /// <summary>
    /// 停止录音
    /// </summary>
    public void StopRecording()
    {
        if (!IsRecording)
            return;

        Microphone.End(CurrentDeviceName);
        Destroy(_audioClip);
        _audioClip = null;
        _DidDetect = false;

        StopCoroutine(RecordData());
    }

    /// <summary>
    /// 用于缓冲传入音频数据和传送帧的循环
    /// </summary>
    IEnumerator RecordData()
    {
        float[] sampleBuffer = new float[FrameLength];
        int startReadPos = 0;

        if (OnRecordingStart != null)
            OnRecordingStart.Invoke();

        while (IsRecording)
        {
            int curClipPos = Microphone.GetPosition(CurrentDeviceName);
            if (curClipPos < startReadPos)
                curClipPos += _audioClip.samples;

            int samplesAvailable = curClipPos - startReadPos;
            if (samplesAvailable < FrameLength)
            {
                yield return null;
                continue;
            }

            int endReadPos = startReadPos + FrameLength;
            if (endReadPos > _audioClip.samples)
            {
                // 碎片式读取(绕到片段的开头)
                // 在片段末尾读取位
                int numSamplesClipEnd = _audioClip.samples - startReadPos;
                float[] endClipSamples = new float[numSamplesClipEnd];
                _audioClip.GetData(endClipSamples, startReadPos);

                // 在剪辑开始时读取位
                int numSamplesClipStart = endReadPos - _audioClip.samples;
                float[] startClipSamples = new float[numSamplesClipStart];
                _audioClip.GetData(startClipSamples, 0);

                // 组合成全画框
                Buffer.BlockCopy(endClipSamples, 0, sampleBuffer, 0, numSamplesClipEnd);
                Buffer.BlockCopy(startClipSamples, 0, sampleBuffer, numSamplesClipEnd, numSamplesClipStart);
            }
            else
            {
                _audioClip.GetData(sampleBuffer, startReadPos);
            }

            startReadPos = endReadPos % _audioClip.samples;
            if (_AutoDetect == false)
            {
                _Transmit =_AudioDetected = true;
            }
            else
            {
                float maxVolume = 0.0f;

                for (int i = 0; i < sampleBuffer.Length; i++)
                {
                    if (sampleBuffer[i] > maxVolume)
                    {
                        maxVolume = sampleBuffer[i];
                    }
                }

                if (maxVolume >= _MinimumSpeakingSampleValue)
                {
                    _Transmit= _AudioDetected = true;
                    _TimeAtSilenceBegan = Time.time;
                }
                else
                {
                    _Transmit = false;

                    if (_AudioDetected && Time.time - _TimeAtSilenceBegan > _SilenceTimer)
                    {
                        _AudioDetected = false;
                    }
                }
            }

            if (_AudioDetected)
            {
                _DidDetect = true;
                // 转换为16位int样本
                short[] pcmBuffer = new short[sampleBuffer.Length];
                for (int i = 0; i < FrameLength; i++)
                {
                    pcmBuffer[i] = (short) Math.Floor(sampleBuffer[i] * short.MaxValue);
                }

                // 引发缓冲区事件
                if (OnFrameCaptured != null && _Transmit)
                    OnFrameCaptured.Invoke(pcmBuffer);
            }
            else
            {
                if (_DidDetect)
                {
                    if (OnRecordingStop != null)
                        OnRecordingStop.Invoke();
                    _DidDetect = false;
                }
            }
        }


        if (OnRecordingStop != null)
            OnRecordingStop.Invoke();
        if (RestartRecording != null)
            RestartRecording.Invoke();
    }
}

语音转文字

使用麦克风 录取音频片段并根据模型进行 转录文字输出。
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Ionic.Zip;
using Unity.Profiling;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using Vosk;

/// <summary>
/// 语音转文字
/// </summary>
public class VoskSpeechToText : MonoBehaviour
{
	//单例
	public static VoskSpeechToText _Instance;

	[Header("模型路径")]
	[Tooltip("模型的位置,相对于Streaming Assets文件夹。")]
	public string ModelPath = "vosk-model-small-cn-0.22.zip";

	[Header("麦克风")]
	[Tooltip("麦克风输入的源")]
	public VoiceProcessor VoiceProcessor;

	[Header("处理最大数量")]
	[Tooltip("将要处理的备选项的最大数量。")]
	public int MaxAlternatives = 3;

	[Header("最长记录时间")]
	[Tooltip("在重新开始之前我们应该记录多长时间?")]
	public float MaxRecordLength = 5;

	[Header("识别器启动布尔")]
	[Tooltip("识别器是否应该在应用程序启动时启动?")]
	public bool AutoStart = true;

	[Header("检测短语数组")]
	[Tooltip("将被检测到的短语。如果为空,将检测所有单词。")]
	public List<string> KeyPhrases = new List<string>();

	//Vosk模型的缓存版本。
	private Model _Model;

	//Vosk识别器的缓存版本。
	private VoskRecognizer _Recognizer;

	//条件标志,用于查看是否已经创建了识别器。
	//TODO:允许对识别器进行运行时更改。
	private bool _RecognizerReady;

	//保留所有音频数据,直到用户停止通话。
	private readonly List<short> _Buffer = new List<short>();

	//当控制器的状态改变时调用。
	public Action<string> OnStatusUpdated;

	//在用户完成讲话后调用,vosk处理音频。
	public Action<string> OnTranscriptionResult;

	//解压缩模型文件夹的绝对路径。
	private string _DecompressedModelPath;

	//字符串,包含Json Array格式的关键字
	private string _Grammar = "";

	//用于等待模型文件成功解压缩的标志。
	private bool _IsDecompressing;

	//用于等待脚本成功启动的标志。
	private bool _IsInitializing;

	//用于检查Vosk是否启动的标志。
	private bool _DidInit;

	//关键字识别布尔
	[Header("关键字识别布尔")]
	public bool _SpeakBool = true;

	//线程的逻辑

	//旗帜表示我们结束了
	private bool _Running;

	//麦克风数据的线程安全队列。
	private readonly ConcurrentQueue<short[]> _ThreadedBufferQueue = new ConcurrentQueue<short[]>();

	//线程安全的结果队列
	private readonly ConcurrentQueue<string> _ThreadedResultQueue = new ConcurrentQueue<string>();



	static readonly ProfilerMarker _VoskRecognizerCreateMarker = new ProfilerMarker("VoskRecognizer.Create");
	static readonly ProfilerMarker _VoskRecognizerReadMarker = new ProfilerMarker("VoskRecognizer.AcceptWaveform");

	//如果启用了“自动启动”,则可以启动语音转文本。
	void Start()
	{
		_Instance = this;
		if (AutoStart)
		{
			StartVoskStt();
		}
	}

	/// <summary>
	/// 启动Vosk语音转文本
	/// </summary>
	/// <param name="keyPhrases">关键字/短语列表。关键字需要存在于模型字典中,所以像“webview”这样的词被更好地检测为两个更常见的词“webview”.</param>
	/// <param name="modelPath">模型文件夹相对于StreamingAssets的路径。如果路径以.zip结尾,它将被解压缩到应用程序数据持久文件夹中</param>
	/// <param name="startMicrophone">"麦克风应该在vosk初始化之后吗?</param>
	/// <param name="maxAlternatives">检测到的可选短语的最大数目</param>
	public void StartVoskStt(List<string> keyPhrases = null, string modelPath = default, bool startMicrophone = false, int maxAlternatives = 3)
	{
		if (_IsInitializing)
		{
			Debug.LogError("正在初始化!");
			return;
		}
		if (_DidInit)
		{
			Debug.LogError("Vosk 已经初始化!");
			return;
		}

		//语言模型加载
		if (!string.IsNullOrEmpty(modelPath))
		{
			ModelPath = modelPath;
		}

		//关键字加载
		if (keyPhrases != null)
		{
			KeyPhrases = keyPhrases;
		}

		MaxAlternatives = maxAlternatives;
		StartCoroutine(DoStartVoskStt(startMicrophone));
	}

	//解压模型,加载设置,启动Vosk和可选地启动麦克风
	private IEnumerator DoStartVoskStt(bool startMicrophone)
	{
		_IsInitializing = true;
		yield return WaitForMicrophoneInput();

		yield return Decompress();

		OnStatusUpdated?.Invoke("加载模型来自: " + _DecompressedModelPath);
		//Vosk.Vosk.SetLogLevel(0);
		_Model = new Model(_DecompressedModelPath);

		yield return null;

		OnStatusUpdated?.Invoke("初始化");
		VoiceProcessor.OnFrameCaptured += VoiceProcessorOnOnFrameCaptured;
		VoiceProcessor.OnRecordingStop += VoiceProcessorOnOnRecordingStop;

		if (startMicrophone)
			VoiceProcessor.StartRecording();

		_IsInitializing = false;
		_DidInit = true;

		ToggleRecording();
		_SpeakBool = false;
		ToggleRecording();
	}

	//将keyphrases转换为json数组,并在末尾附加' [unk] '关键字,以告诉vosk过滤其他短语。
	private void UpdateGrammar()
	{
		if (KeyPhrases.Count == 0)
		{
			_Grammar = "";
			return;
		}

		JSONArray keywords = new JSONArray();
		foreach (string keyphrase in KeyPhrases)
		{
			keywords.Add(new JSONString(keyphrase.ToLower()));
		}

		keywords.Add(new JSONString("[unk]"));

		_Grammar = keywords.ToString();
	}

	//解压缩模型zip文件或返回解压缩文件的位置。
	private IEnumerator Decompress()
	{
		if (!Path.HasExtension(ModelPath)
			|| Directory.Exists(
				Path.Combine(Application.persistentDataPath, Path.GetFileNameWithoutExtension(ModelPath))))
		{
			OnStatusUpdated?.Invoke("使用现有的解压缩模型");
			_DecompressedModelPath =
				Path.Combine(Application.persistentDataPath, Path.GetFileNameWithoutExtension(ModelPath));
			Debug.Log(_DecompressedModelPath);

			yield break;
		}

		OnStatusUpdated?.Invoke("模型解压中...");
		string dataPath = Path.Combine(Application.streamingAssetsPath, ModelPath);

		Stream dataStream;
		// 从流资产路径读取数据。你不能直接在Android上访问流媒体资源。
		if (dataPath.Contains("://"))
		{
			UnityWebRequest www = UnityWebRequest.Get(dataPath);
			www.SendWebRequest();
			while (!www.isDone)
			{
				yield return null;
			}
			dataStream = new MemoryStream(www.downloadHandler.data);
		}
		// 在有效的平台上直接读取文件。
		else
		{
			dataStream = File.OpenRead(dataPath);
		}

		//读取Zip文件
		var zipFile = ZipFile.Read(dataStream);

		//等待zip文件完成解压缩
		zipFile.ExtractProgress += ZipFileOnExtractProgress;

		//更新状态文本
		OnStatusUpdated?.Invoke("读取Zip文件");

		//开始提取
		zipFile.ExtractAll(Application.persistentDataPath);

		//等到它完成
		while (_IsDecompressing == false)
		{
			yield return null;
		}
		//覆盖ZipFileOnExtractProgress中给出的路径以防止崩溃
		_DecompressedModelPath = Path.Combine(Application.persistentDataPath, Path.GetFileNameWithoutExtension(ModelPath));

		//更新状态文本
		OnStatusUpdated?.Invoke("解压完成!");
		//稍等一下,以防我们需要初始化另一个对象。
		yield return new WaitForSeconds(1);
		//处理zipfile读取器。
		zipFile.Dispose();
	}

	///更新zip文件提取过程时调用的函数。
	private void ZipFileOnExtractProgress(object sender, ExtractProgressEventArgs e)
	{
		if (e.EventType == ZipProgressEventType.Extracting_AfterExtractAll)
		{
			_IsDecompressing = true;
			_DecompressedModelPath = e.ExtractLocation;
		}
	}

	//等待麦克风初始化
	private IEnumerator WaitForMicrophoneInput()
	{
		while (Microphone.devices.Length <= 0)
			yield return null;
	}

	//可以从脚本或GUI按钮中调用以启动检测。
	public void ToggleRecording()
	{
 
		Debug.Log("Toogle记录");
		if (!VoiceProcessor.IsRecording)
		{
            if (_SpeakBool )
            {
				Debug.Log("开始记录");
				_Running = true;
				VoiceProcessor.StartRecording();
				Task.Run(ThreadedWork).ConfigureAwait(false);
			}
            else
            {
				Debug.Log("停止记录");
				_Running = false;
				VoiceProcessor.StopRecording();
			}
		}
		else
		{
			Debug.Log("停止记录");
			_Running = false;
			VoiceProcessor.StopRecording();
		}
	}

	//调用Unity线程上的On短语识别事件
	void Update()
	{
		if (_ThreadedResultQueue.TryDequeue(out string voiceResult))
		{
		    OnTranscriptionResult?.Invoke(voiceResult);
		}
	}

	/// <summary>
	/// 当检测到新的音频时,从语音处理器回调
	/// </summary>
	/// <param name="samples"></param>
	private void VoiceProcessorOnOnFrameCaptured(short[] samples)
	{	
                _ThreadedBufferQueue.Enqueue(samples);
	}

	/// <summary>
	/// 录音停止时从语音处理器回调
	/// </summary>
	private void VoiceProcessorOnOnRecordingStop()
	{
                Debug.Log("停止");
	}

	//将音频逻辑输入语音识别器
	private async Task ThreadedWork()
	{
		_VoskRecognizerCreateMarker.Begin();
		if (!_RecognizerReady)
		{
			UpdateGrammar();

			//仅检测指定的已定义关键字。
			if (string.IsNullOrEmpty(_Grammar))
			{
				_Recognizer = new VoskRecognizer(_Model, 16000.0f);
			}
			else
			{
				_Recognizer = new VoskRecognizer(_Model, 16000.0f, _Grammar);
			}

			_Recognizer.SetMaxAlternatives(MaxAlternatives);
			//_recognizer.SetWords(true);
			_RecognizerReady = true;

			Debug.Log("识别器准备好了");
		}

		_VoskRecognizerCreateMarker.End();

		_VoskRecognizerReadMarker.Begin();

		while (_Running)
		{
			if (_ThreadedBufferQueue.TryDequeue(out short[] voiceResult))
			{
				if (_Recognizer.AcceptWaveform(voiceResult, voiceResult.Length))
				{
					var result = _Recognizer.Result();
					_ThreadedResultQueue.Enqueue(result);
				}
			}
			else
			{
				// 等待一些数据
				await Task.Delay(100);
			}
		}

		_VoskRecognizerReadMarker.End();
	}
}

Vosk 接受响应

针对模型输出结果的响应输出。
using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// Vosk 返回结果
/// </summary>
public class VoskResultText : MonoBehaviour 
{
    [Header("Vosk 语音转文本")]
    public VoskSpeechToText _VoskSpeechToText;
    [Header("返回文本")]
    public InputField _ResultText;

    void Awake()
    {
        //监听
        _VoskSpeechToText.OnTranscriptionResult += OnTranscriptionResult;
    }

    /// <summary>
    /// 语音转录文字方法
    /// </summary>
    /// <param name="obj"></param>
    private void OnTranscriptionResult(string obj)
    {
        //Debug.Log(obj);
        var result = new RecognitionResult(obj);
        for (int i = 0; i < result.Phrases.Length; i++)
        {
            //说话内容 不为空
            if (result.Phrases[i].Text!=""|| result.Phrases[i].Text!=" ")
            {
                if (i > 0)
                {
                    _ResultText.text += ", ";
                }
                //如果是多个结果的话 只要第一个结果
                _ResultText.text += result.Phrases[0].Text.Replace(" ", "");
                //说话内容 并剔除所有空格
                Debug.Log(_ResultText.text);
                var _StrRe = result.Phrases[0].Text.Replace(" ", "");
                //ResultText.text = _StrRe;

                if (_StrRe != "小哟" || _StrRe != "小哟小哟")
                {
                    //如果是多个结果的话 只要第一个结果
                    _ResultText.text = _StrRe;
                }
                if (KeywordRecognition_ZH._Instance._OpenASRbool)
                {
                    //智能回答
                    GetOpenAI_ZH._Instance._InputProblemText.text = _StrRe;
                    GetOpenAI_ZH._Instance.SendData();

                    //录音关闭
                    VoskSpeechToText._Instance._SpeakBool = false;
                    VoskSpeechToText._Instance.ToggleRecording();

                    //回答响应布尔
                    KeywordRecognition_ZH._Instance._OpenASRbool = false;
                }
                return;
            }
        }
    }
}

OpenAI

访问 Open AI 需要一个接收 Json 数据的类,一个发送 Json 数据的类,我都会放在下面。
还有几个文档链接大家注意看。
Chat API 访问 URL:https://api.openai.com/v1/chat/completions

极简回答:
《核心》:访问 OpenAI 网址、使用 POST 方法进行参数传递、等待响应、可视化显示。

链接: Open AI API 文档地址

链接: Open AI API 示例地址

链接: Open AI API 开放平台地址

发送消息 示例

     //发送消息 示例
    {
        "model":"gpt-3.5-turbo",
        "messages":
        [
            {
                "role":"user",
                "content":"你是谁"
            }
        ],
        "max_tokens":2048,
        "temperature":0.5,
        "top_p":1,
        "frequency_penalty":1,
        "presence_penalty":1,
        "stop":"stop"
    }

返回消息 示例

	//返回消息 示例
    {
        "id":"chatcmpl-7CQ1C0ZL7XUOdqPcblJDxxPnOmI1c",
        "object":"chat.completion",
        "created":1683194306,
        "model":"gpt-3.5-turbo-0301",
        "usage":{
            "prompt_tokens":12,
            "completion_tokens":15,
            "total_tokens":27
        },
        "choices":
        [
            {
                "message":
                {
                    "role":"assistant",
                    "content":"我是一个AI语言模型,由OpenAI开发。"
                },
                "finish_reason":"stop",
                "index":0
            }
        ]
    }

Open AI 访问

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using static AcceptJson_ZH;
using static PostDataJson_ZH;
/// <summary>
/// Open AI 访问
/// </summary>
public class GetOpenAI_ZH : MonoBehaviour
{
    public static GetOpenAI_ZH _Instance;

    //API key
    private string _OpenAI_Key = "";

    // Chat API 访问 URL
    private string _OpenAIUrl = "https://api.openai.com/v1/chat/completions";
    //配置参数
    [SerializeField] private PostDataJson_ZH _PostDataSetting;


    [Header("输入的信息")]
    [SerializeField] public InputField _InputProblemText;

    [Header("回复的信息")]
    [SerializeField] public Text _RobotChatText;

    //回答模型
    [HideInInspector]
    public ModelType _ModelType { get; set; }

    //模型菜单显示 布尔
    private bool _ModelMenuActive;

    private void Awake()
    {
        _Instance = this;
    }

    /// <summary>
    /// 回答模型选择方法
    /// </summary>
    /// <param 当前执行 Button="_ModelTypeButton"></param>
    public void ModelTypeSet(Button _ModelTypeButton)
    {
        if (_ModelTypeButton.name == "gpt-3.5-turbo")
        {
            _ModelType = ModelType.gpt35turbo;
        }
        else if (_ModelTypeButton.name == "gpt-3.5-turbo-0301")
        {
            _ModelType = ModelType.gpt35turbo0301;
        }
        else if (_ModelTypeButton.name == "gpt-4")
        {
            _ModelType = ModelType.gpt4;
        }
        else if (_ModelTypeButton.name == "gpt-4-0314")
        {
            _ModelType = ModelType.gpt40314;
        }
        else if (_ModelTypeButton.name == "gpt-4-32k")
        {
            _ModelType = ModelType.gpt432k;
        }
        else if (_ModelTypeButton.name == "gpt-4-32k-0314")
        {
            _ModelType = ModelType.gpt432k0314;
        }
        else if (_ModelTypeButton.name == "text-davinci-003")
        {
            _ModelType = ModelType.textdavinci003;
        }
    }


    /// <summary>
    /// 发送信息
    /// </summary>
    public void SendData()
    {
        if (_InputProblemText.text.Equals(""))
            return;

        //问题
        string _Problem = _InputProblemText.text;

        StartCoroutine(GetPostData(_Problem));
        //_InputText.text = "";
    }


    /// <summary>
    /// POST 方法请求
    /// </summary>
    /// <param 问题="_SendMessage"></param>
    /// <returns></returns>
    private IEnumerator GetPostData(string _SendMessage)
    {
         OpenAI 访问
        //var _Request = new UnityWebRequest(_OpenAIUrl, "POST");

        // OpenAI 访问
        using (UnityWebRequest _Request = new UnityWebRequest(_OpenAIUrl, "POST"))
        {
            //规则归一化
            PostDataJson_ZH _PostData = new PostDataJson_ZH
            {
                //model =  "gpt-3.5-turbo",
                max_tokens = _PostDataSetting.max_tokens,
                temperature = _PostDataSetting.temperature,
                top_p = _PostDataSetting.top_p,
                frequency_penalty = _PostDataSetting.frequency_penalty,
                presence_penalty = _PostDataSetting.presence_penalty,
                stop = _PostDataSetting.stop
            };

            //模型设置
            switch (_ModelType)
            {
                //gpt-3.5-turbo、gpt-3.5-turbo-0301、gpt-4、gpt-4-0314、gpt-4-32k、gpt-4-32k-0314、text-davinci-003
                case ModelType.gpt35turbo:
                    _PostData.model = "gpt-3.5-turbo";
                    break;
                case ModelType.gpt35turbo0301:
                    _PostData.model = "gpt-3.5-turbo-0301";
                    break;
                case ModelType.gpt4:
                    _PostData.model = "gpt-4";
                    break;
                case ModelType.gpt40314:
                    _PostData.model = "gpt-4-0314";
                    break;
                case ModelType.gpt432k:
                    _PostData.model = "gpt-4-32k";
                    break;
                case ModelType.gpt432k0314:
                    _PostData.model = "gpt-4-32k-0314";
                    break;
                case ModelType.textdavinci003:
                    _PostData.model = "text-davinci-003";
                    break;
                default:
                    break;
            }
            //消息赋予
            PostDataJson_ZH.MessagesItem _Messages = new PostDataJson_ZH.MessagesItem();
            _Messages.role = "user";
            _Messages.content = _SendMessage;
            _PostData.messages.Add(_Messages);

            //数据转换
            string _JsonText = JsonUtility.ToJson(_PostData);
            print(_JsonText);

            byte[] _Data = System.Text.Encoding.UTF8.GetBytes(_JsonText);
            //数据上传 等待响应
            _Request.uploadHandler = new UploadHandlerRaw(_Data);
            _Request.downloadHandler = new DownloadHandlerBuffer();

            //数据重定向
            _Request.SetRequestHeader("Content-Type", "application/json");
            _Request.SetRequestHeader("Authorization", string.Format("Bearer {0}", _OpenAI_Key));

            //等待响应 开始与远程服务器通信
            yield return _Request.SendWebRequest();



            //数据返回
            if (_Request.responseCode == 200)
            {
                //接收返回信息
                string _Message = _Request.downloadHandler.text;
                //数据转换
                AcceptJson_ZH _Textback = JsonUtility.FromJson<AcceptJson_ZH>(_Message);

                //确保当前有消息传回
                if (_Textback != null && _Textback.choices.Count > 0)
                {
                    //OpenAI 输出 清空
                    _RobotChatText.text = "";
                    //打印
                    print(_Textback.choices[0].message.content);
                    //回答消息填写
                    _RobotChatText.text = _Textback.choices[0].message.content;

                    //文字转语音 回答
                    StartCoroutine(VoiceSynthesis_ZH._World.GetAudioClip(_Textback.choices[0].message.content));
                }
            }
        }
    }


    void Update()
    {

        if (Input.GetKeyDown(KeyCode.Escape))
        {
            Application.Quit();
        }

        if (Input.GetKeyDown(KeyCode.Q))
        {
            _ModelMenuActive = !_ModelMenuActive;

            //显示设置
            if (_ModelMenuActive)
            {
                GameObject.Find("模型选择").transform.localScale = Vector3.one;
            }
            else
            {
                GameObject.Find("模型选择").transform.localScale = Vector3.zero;
            }
        }
    }
}

发送 Json 数据支持类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Open AI 发送 数据
/// </summary>
[System.Serializable]
public class PostDataJson_ZH 
{
    [Header("使用模型")]
    [Tooltip("gpt-3.5-turbo、gpt-3.5-turbo-0301、gpt-4、gpt-4-0314、gpt-4-32k、gpt-4-32k-0314、text-davinci-003")]
    public ModelType _modelType = ModelType.gpt35turbo;
    [HideInInspector]
    public string model = "gpt-3.5-turbo";

    [Header("发送信息 数据")]
    public List<MessagesItem> messages = new List<MessagesItem>();

    [Header("最大 Token 值")]
    [Tooltip("可以在聊天完成中生成的最大令牌数")]
    public int max_tokens = 2048;

    [Header("采样温度")]
    [Tooltip("温度 使用什么采样温度,介于 0 和 2 之间。较高的值(如 0.8)将使输出更加随机,而较低的值(如 0.2)将使其更加集中和确定")]
    [Range(0,2)]
    public float temperature = 0.5f;


    [Header("采样温度")]
    [Tooltip("使用温度采样的替代方法称为核心采样,其中模型考虑具有top_p概率质量的令牌的结果。因此,0.1 意味着只考虑包含前 10% 概率质量的迭代")]
    [Range(0, 1)]
    public float top_p = 1;

    [Header("重复度")]
    [Tooltip("介于 -2.0 和 2.0 之间的数字。正值会根据新标记到目前为止在文本中的现有频率来惩罚新标记,从而降低模型逐字重复同一行的可能性")]
    [Range(0, 2)]
    public float frequency_penalty = 1.0f;

    [Header("新主题")]
    [Tooltip("介于 -2.0 和 2.0 之间的数字。正值会根据新标记到目前为止是否出现在文本中来惩罚它们,从而增加模型讨论新主题的可能性")]
    [Range(0, 2)]
    public float presence_penalty = 1.0f;

    [Header("结束原因")]
    [Tooltip("最多 4 个序列,其中 API 将停止生成更多令牌。")]
    public string stop = "stop";

    [System.Serializable]
    public class MessagesItem
    {
        /// <summary>
        /// 演员
        /// </summary>
        public string role;
        /// <summary>
        /// 信息
        /// </summary>
        public string content;
    }


    [System.Serializable]
    public enum ModelType
    {
        //gpt-3.5-turbo
        gpt35turbo,

        //gpt-3.5-turbo-0301
        gpt35turbo0301,

        //gpt-4
        gpt4,

        //gpt-4-0314
        gpt40314,

        //gpt-4-32k
        gpt432k,

        //gpt-4-32k-0314
        gpt432k0314,

        //text-davinci-003
        textdavinci003
    }
}

接收 Json 数据支持类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// Open AI 接收 数据
/// </summary>
[System.Serializable]
public class AcceptJson_ZH
{
    /// <summary>
    /// Open AI ID
    /// </summary>
    public string id;
    /// <summary>
    /// 支付方式
    /// </summary>
    public string @object;
    /// <summary>
    /// 建立 标识
    /// </summary>
    public int created;
    /// <summary>
    /// 使用模型
    /// </summary>
    public string model;
    /// <summary>
    /// 用法
    /// </summary>
    public Usage usage;
    /// <summary>
    /// 参数选择
    /// </summary>
    [SerializeField]
    public List<ChoicesItem> choices;


    [System.Serializable]
    public class Usage
    {
        /// <summary>
        /// 指示令牌
        /// </summary>
        public int prompt_tokens;
        /// <summary>
        /// 完成令牌
        /// </summary>
        public int completion_tokens;
        /// <summary>
        /// 总令牌
        /// </summary>
        public int total_tokens;
    }

    [System.Serializable]
    public class Message
    {
        /// <summary>
        /// 角色 一般是 user
        /// </summary>
        public string role;
        /// <summary>
        /// 回答 信息
        /// </summary>
        public string content;
    }

    [System.Serializable]
    public class ChoicesItem
    {
        /// <summary>
        /// 返回消息
        /// </summary>
        [SerializeField]
        public Message message;
        /// <summary>
        /// 完成原因 一般是 stop
        /// </summary>
        public string finish_reason;
        /// <summary>
        /// 步数
        /// </summary>
        public int index;
    }
}

脚本搭载 以及 层级结构

KeywordRecognition_ZH 以及 VoiceSynthesis_ZH 搭载情况

请添加图片描述

VoiceProcessor、VoskSpeechToText、VoskResultText 搭载情况

请添加图片描述

GetOpenAI_ZH 搭载情况

请添加图片描述

Unity 层次结构

请添加图片描述

运行情况

程序包含了语音输出模块、语音输出模块、ChatGPt回答、中英文双语对话、预留了二次开发接口。
就这吧 不想写了 人麻了。
好像忘了啥。啊对 Vosk的下载链接没给你们,我就不往上翻了就直接在下面写了。
太长了,抱歉  哈哈哈。
你们要是没有积分 就直接给我发私信 给我邮箱 我直接发你们邮箱 别说谢  受不了。(。・∀・)ノ゙

链接: Vosk 官网

链接: Vosk 下载地址

链接: Vosk 语言模型下载地址

请添加图片描述

请添加图片描述

暂时先这样吧,如果有时间的话就会更新我调教好大语言模型的,以后抽空再弄个一体化,散会吧。要是实在看不明白就留言,看到我会回复的。
路漫漫其修远兮,与君共勉。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。