OpenAI ChatGPT Unity接入

news/2024/10/24 8:25:52/

OpenAI ChatGPT Unity接入

  • OpenAI ChatGPT Unity接入
    • OpenAi-API-Unity 方法
      • OpenAi-API-Unity 下载
      • 本地配置
      • Unity 模块
        • URL接入
        • gz 接入
        • json 接入
        • Open AI
        • OpenAi-Api-Unity 插件文档
    • OpenAi 本地化接入 Unity 方法
      • Unity 关键字识别
      • 语音合成 & 文字转语音
      • 音频记录 & 实时音频处理
      • 语音转文字
      • Vosk 接受响应
      • OpenAI
        • 发送消息 示例
        • 返回消息 示例
        • Open AI 访问
        • 发送 Json 数据支持类
        • 接收 Json 数据支持类
      • 脚本搭载 以及 层级结构
      • 运行情况

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;//百度语音识别SDKprivate 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_EDITORvoid 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.EventSystems;
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 keyprivate string _OpenAI_Key = "Open AI Key";// Chat API 访问 URLprivate string _OpenAIUrl = "https://api.openai.com/v1/chat/completions";//配置参数[SerializeField] private PostDataJson_ZH _PostDataSetting;[Header("输入的信息")][SerializeField] public InputField _InputProblemText;[Header("回复的信息")][SerializeField] public Text _RobotChatText;[Header("输入列表")]private List<GameObject> _InputList=new List<GameObject>();//回答模型[HideInInspector]public ModelType _ModelType { get; set; }//模型菜单显示 布尔private bool _ModelMenuActive;private void Awake(){_Instance = this;}private void Start(){_InputList.Add(GameObject.Find("发送"));_InputList.Add(_InputProblemText.gameObject);}/// <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-003case 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));}}}}/// <summary>/// 获取当前选中物体的序列/// </summary>/// <param 当前输入="input"></param>/// <returns></returns>private int IndexNow(GameObject _Input){int _IndexNow = 0;for (int i = 0; i < _InputList.Count; i++){if (_Input == _InputList[i]){_IndexNow = i;break;}}return _IndexNow ;}/// <summary>/// 获取下一个物体/// </summary>/// <param 当前输入="_Input"></param>/// <returns></returns>private GameObject NextInput(GameObject _Input){int _IndexNow = IndexNow(_Input);if (_IndexNow + 1 < _InputList.Count){return _InputList[_IndexNow + 1].gameObject;}else{return _InputList[0].gameObject;}}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;}}//输入切换if (Input.GetKeyDown(KeyCode.Tab)){//如果是当前数组中的元素if (_InputList.Contains(EventSystem.current.currentSelectedGameObject)){//正序GameObject _Next = NextInput(EventSystem.current.currentSelectedGameObject);EventSystem.current.SetSelectedGameObject(_Next);}//如果不是当前数组元素//默认开启第一个元素else{//第一个输入开启EventSystem.current.SetSelectedGameObject(_InputProblemText.gameObject);}}}
}

发送 Json 数据支持类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Open AI 发送 数据
/// </summary>
[System.Serializable]
public class PostDataJson_ZH 
{[Header("使用模型")][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-turbogpt35turbo,//gpt-3.5-turbo-0301gpt35turbo0301,//gpt-4gpt4,//gpt-4-0314gpt40314,//gpt-4-32kgpt432k,//gpt-4-32k-0314gpt432k0314,//text-davinci-003textdavinci003}
}

接收 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 语言模型下载地址

请添加图片描述

请添加图片描述

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


http://www.ppmy.cn/news/63734.html

相关文章

JavaScript 进阶知识点概述

目录 第一部分: 垃圾回收 1. 什么是垃圾回收&#xff1f; 2. JavaScript 的垃圾回收机制 3. 新生代和老生代的概念 4. 垃圾回收算法和策略 第二部分: 事件循环 5. 什么是事件循环&#xff1f; 6. 宏任务和微任务 7. Event Loop 的执行顺序 8. 避免阻塞 GUI 线程的 Jav…

系统移植——linux内核移植——分析内核编译过程

uImage镜像文件 1.进入linux内核源码目录 ubuntuubuntu:~$ cd FSMP1A/linux-stm32mp-5.10.61-stm32mp-r2-r0/linux-5.10.61/ 打开Makefile文件 vi Makefile 搜索include 因为 $(SRCARCH)->arm 所以上述指令为 arch/arm/Makefile 2.进入linux内核源码目录下,arch/arm目录下…

Kafka的概念|架构|搭建|查看命令

Kafka的概念|架构|搭建|查看命令 一 Kafka 概述二 使用消息队列的好处三Kafka 定义3.1Kafka 简介3.2Kafka 的特性3.3 Kafka 系统架构3.4 Partation 数据路由规则 四 kafka的架构五 搭建kafka5.1环境准备5.2安装kafka5.3 修改配置文件5.4 编辑其他二台虚拟机的配置文件5.5 编辑三…

CA OpenSSL自签名证书(服务器/客户端)

参考文章 https://juejin.cn/post/7092789498823573518 https://blog.csdn.net/mengting2040/article/details/120001810 目录 使用 OpenSSL 生成证书创建根证书创建 Root Pair创建 Root Key创建 Root Crt 创建服务器端证书创建服务器端keyip需要换成自己服务器的外网ip地址&am…

Oracle限制单个用户的并发连接数

Oracle限制单个用户的并发连接数 开启RESOURCE_LIMIT参数查看对用户的资源限制限制用户的并发连接数 开启RESOURCE_LIMIT参数 检查资源限制是否开启&#xff1a; SQL> show parameter resource_limitNAME TYPE VALUE ---- ---- ----- resource_limit boolean TRUE这个参数…

前端三剑客 HTML+CSS+JS

文章目录 一、HTML1.1 基础标签1.2 列表1.3 表格1.4 表单 二、CSS2.1 引入方式2.2 CSS 选择器2.2.1 基本选择器2.2.2 组合选择器 2.3 常用属性2.3.1 背景2.3.2 文本2.3.3 字体2.3.4 display元素类型2.3.5 浮动2.3.6 盒子模型 三、JavaScript3.1 引入方式3.2 数据类型3.2.1 数组…

【Android取证篇】Android设备USB调试打开方式(开发者模式)

【Android取证篇】Android设备USB调试打开方式(开发者模式) Android各个版本系统手机开启”USB调试”的入口不全相同&#xff0c;仅供参考—【蘇小沐】 1、【Android1.0-3.2】 路径&#xff1a;在应用列表选择「设置」->「应用程序」->「开发」->勾选「USB调试」选…

Android 12.0 Launcher3桌面禁止左右滑动

1.概述 在12.0的rom定制化开发中,由于Launcher3有一些功能需要定制,这样的需求也好多的, 现在功能需求要求桌面固定在Launcher3的app列表页,不让左右移动,就是禁止左右移动的功能实现,所以需要禁止滑动分析页面滑动部分的功能,然后禁用桌面左右滑动功能 2.Launcher3桌…