当前位置:   article > 正文

数字人系列四:Motionverse 接入chatgpt、文心一言等国内外大语言模型_unity 文心一言

unity 文心一言

1. 下载插件:motionverse官网地址:概述 · Motionverse 接口文档 (deepscience.cn)

image.png


2. 按照官方文档新建Unity工程:对接说明 · Motionverse 接口文档 (deepscience.cn)


3. 通过我们自己的ASR,将语音转换为文本,文本通过大语言模型(chatgpt、文心一言以及其他大语言模型)生成的结果,直接通过生成式AI技术,把文本转化为AI智能体的声音、动作和表情,和大语言模型完美连接,只需要在获取到文本的时候调用以下代码即可:
代码示例:

  1. DriveTask task = new DriveTask();
  2. task.player = player;
  3. task.text = question;
  4. NLPDrive.GetDrive(task);


其中,player即为挂载motionverse插件中Player脚本的对象,question即为大语言模型获取到的答案。

还可以自己生成语音,通过语音链接调用以下代码:

  1. DriveTask task = new DriveTask();
  2. task.player = player;
  3. task.text = audioUrl;
  4. AudioUrlDrive.GetDrive(task);

其中,player即为挂载motionverse插件中Player脚本的对象,audioUrl即为语音链接。

4. 新建脚本AskManager,并挂载到场景中(可新建空物体),脚本代码如下:

  1. using LitJson;
  2. using Motionverse;
  3. using MotionverseSDK;
  4. using System;
  5. using System.Collections;
  6. using System.Text;
  7. using UnityEngine;
  8. using UnityEngine.Networking;
  9. using UnityEngine.UI;
  10. public class AskManager : MonoBehaviour
  11. {
  12. public Player player;
  13. public Dropdown dropdown;
  14. public InputField inputField;
  15. public Button btnSend;
  16. public string chatGptKey = "";
  17. private string chatUrl = "https://chatgpt.kazava.io/v1/chat/completions";
  18. public string wenXinAPI = "";
  19. public string wenXinSECRET = "";
  20. private string wenXinToken = "";
  21. private int curSelectNLP = 0;
  22. // Start is called before the first frame update
  23. private void Awake()
  24. {
  25. for (int i = 0; i < Display.displays.Length; i++)
  26. {
  27. Display.displays[i].Activate();
  28. }
  29. }
  30. void Start()
  31. {
  32. dropdown.onValueChanged.AddListener((value) =>
  33. {
  34. curSelectNLP = value;
  35. });
  36. StartCoroutine(GetWenxinToken());
  37. btnSend.onClick.AddListener(() =>
  38. {
  39. if (string.IsNullOrEmpty(inputField.text))
  40. {
  41. Debug.Log("请输入内容!");
  42. }
  43. else
  44. {
  45. GetAnswer(inputField.text);
  46. }
  47. });
  48. }
  49. public void GetAnswer(string q)
  50. {
  51. StartCoroutine(RealAnswer(q));
  52. }
  53. public IEnumerator RealAnswer(string question)
  54. {
  55. switch (curSelectNLP)
  56. {
  57. case 0:
  58. DriveTask task = new DriveTask();
  59. task.player = player;
  60. task.text = question;
  61. NLPDrive.GetDrive(task);
  62. break;
  63. case 1:
  64. StartCoroutine(RequestChat(question));
  65. break;
  66. case 2:
  67. StartCoroutine(ChatCompletions(question));
  68. break;
  69. default:
  70. break;
  71. }
  72. Invoke("restartRecording", 1);
  73. yield return null;
  74. }
  75. IEnumerator GetWenxinToken()
  76. {
  77. string url =$"https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={wenXinAPI}&client_secret={wenXinSECRET}";
  78. UnityWebRequest webRequest = UnityWebRequest.Get(url);
  79. webRequest.timeout = 5000;
  80. yield return webRequest.SendWebRequest();
  81. if (webRequest.result == UnityWebRequest.Result.Success)
  82. {
  83. string response = webRequest.downloadHandler.text;
  84. var result = JsonUtility.FromJson<AccessTokenResponse>(response);
  85. wenXinToken = result.access_token;
  86. }
  87. else
  88. {
  89. Debug.LogError("Failed to get access token: " + webRequest.error);
  90. }
  91. }
  92. IEnumerator ChatCompletions(string content)
  93. {
  94. string url = $"https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions?access_token={wenXinToken}";
  95. WenXinPostData postData = new WenXinPostData();
  96. postData.messages = new WenXinMessage[1];
  97. postData.messages[0] = new WenXinMessage();
  98. postData.messages[0].content = content + "30字以内";
  99. string data = JsonMapper.ToJson(postData);
  100. UnityWebRequest webRequest = UnityWebRequest.Post(url, data, "application/json");
  101. webRequest.timeout = 5000;
  102. yield return webRequest.SendWebRequest();
  103. if (webRequest.result == UnityWebRequest.Result.Success)
  104. {
  105. string response = webRequest.downloadHandler.text;
  106. WenXinRequest requestData = JsonMapper.ToObject<WenXinRequest>(response);
  107. DriveTask task = new DriveTask();
  108. task.player = player;
  109. task.text = requestData.result;
  110. TextDrive.GetDrive(task);
  111. }
  112. else
  113. {
  114. Debug.LogError("Chat completions request failed: " + webRequest.error);
  115. }
  116. }
  117. private IEnumerator RequestChat(string content)
  118. {
  119. using (UnityWebRequest webRequest = new UnityWebRequest(chatUrl, "POST"))
  120. {
  121. webRequest.SetRequestHeader("Content-Type", "application/json");
  122. ChatGPTPostData postData = new ChatGPTPostData();
  123. postData.key = chatGptKey;
  124. postData.messages = new PostMessage[1];
  125. postData.messages[0] = new PostMessage();
  126. postData.messages[0].content = content + "30字以内"; ;
  127. string data = JsonMapper.ToJson(postData);
  128. byte[] jsonToSend = new UTF8Encoding().GetBytes(data);
  129. webRequest.uploadHandler = new UploadHandlerRaw(jsonToSend);
  130. webRequest.downloadHandler = new DownloadHandlerBuffer();
  131. yield return webRequest.SendWebRequest();
  132. if (webRequest.result != UnityWebRequest.Result.Success)
  133. {
  134. Debug.LogError("ChatGPT request error: " + content + webRequest.error);
  135. }
  136. else
  137. {
  138. string response = webRequest.downloadHandler.text;
  139. ChatGPTRequestData requestData = JsonMapper.ToObject<ChatGPTRequestData>(response);
  140. DriveTask task = new DriveTask();
  141. task.player = player;
  142. task.text = requestData.choices[0].message.content;
  143. TextDrive.GetDrive(task);
  144. }
  145. }
  146. }
  147. }


5. 新建脚本RealtimeAsrManager,代码如下:

  1. using System;
  2. using System.Collections;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. using UnityWebSocket;
  6. namespace Motionverse
  7. {
  8. public class RealtimeAsrManager : MonoBehaviour
  9. {
  10. private IWebSocket webSocket;
  11. private Status status = Status.FirstFrame;
  12. private bool lockReconnect = false;
  13. [HideInInspector]
  14. public static Action<string> Asr;
  15. [SerializeField]
  16. private GameObject TextBG;
  17. //百度
  18. public int AsrAppId;
  19. public string AsrAppkey = "";
  20. void Start()
  21. {
  22. CreateWebSocket();
  23. RecorderManager.DataAvailable += OnDataAvailable;
  24. }
  25. private void OnDisable()
  26. {
  27. RecorderManager.DataAvailable -= OnDataAvailable;
  28. }
  29. void CreateWebSocket()
  30. {
  31. try
  32. {
  33. string uril = "wss://vop.baidu.com/realtime_asr?sn=" + Guid.NewGuid().ToString();
  34. webSocket = new WebSocket(uril);
  35. InitHandle();
  36. webSocket.ConnectAsync();
  37. }
  38. catch (Exception e)
  39. {
  40. Debug.Log("websocket连接异常:" + e.Message);
  41. ReConnect();
  42. }
  43. }
  44. private void InitHandle()
  45. {
  46. RemoveHandle();
  47. webSocket.OnOpen += OnOpen;
  48. webSocket.OnMessage += OnMessage;
  49. webSocket.OnClose += OnClose;
  50. webSocket.OnError += OnError;
  51. }
  52. void RemoveHandle()
  53. {
  54. webSocket.OnOpen -= OnOpen;
  55. webSocket.OnMessage -= OnMessage;
  56. webSocket.OnClose -= OnClose;
  57. webSocket.OnError -= OnError;
  58. }
  59. //请求开始
  60. private void OnDataAvailable(byte[] data)
  61. {
  62. if (webSocket == null || (webSocket != null && webSocket.ReadyState != WebSocketState.Open))
  63. return;
  64. switch (status)
  65. {
  66. case Status.FirstFrame://握手
  67. {
  68. var firstFrame = new FirstFrame();
  69. firstFrame.data.appid= AsrAppId;
  70. firstFrame.data.appkey = AsrAppkey;
  71. webSocket.SendAsync(JsonUtility.ToJson(firstFrame));
  72. status = Status.ContinueFrame;
  73. }
  74. break;
  75. case Status.ContinueFrame://开始发送
  76. {
  77. if (data.Length > 0)
  78. {
  79. webSocket.SendAsync(data);
  80. }
  81. }
  82. break;
  83. case Status.LastFrame://关闭
  84. {
  85. webSocket.SendAsync(JsonUtility.ToJson(new LastFrame()));
  86. }
  87. break;
  88. default:
  89. break;
  90. }
  91. }
  92. void ReConnect()
  93. {
  94. if (this.lockReconnect)
  95. return;
  96. this.lockReconnect = true;
  97. StartCoroutine(SetReConnect());
  98. }
  99. private IEnumerator SetReConnect()
  100. {
  101. yield return new WaitForSeconds(1);
  102. CreateWebSocket();
  103. lockReconnect = false;
  104. }
  105. #region WebSocket Event Handlers
  106. private void OnOpen(object sender, OpenEventArgs e)
  107. {
  108. status = Status.FirstFrame;
  109. }
  110. private void OnMessage(object sender, MessageEventArgs e)
  111. {
  112. var err_msg = Utils.GetJsonValue(e.Data, "err_msg");
  113. var type = Utils.GetJsonValue(e.Data, "type");
  114. if (err_msg == "OK" && type == "MID_TEXT")
  115. {
  116. var result = Utils.GetJsonValue(e.Data, "result");
  117. TextBG.GetComponentInChildren<Text>().text = result;
  118. }
  119. if (err_msg == "OK" && type == "FIN_TEXT")
  120. {
  121. var result = Utils.GetJsonValue(e.Data, "result");
  122. TextBG.GetComponentInChildren<Text>().text = result;
  123. if (result.Length > 1)
  124. {
  125. RecorderManager.Instance.EndRecording();
  126. Asr?.Invoke(result);
  127. }
  128. }
  129. }
  130. private void OnClose(object sender, CloseEventArgs e)
  131. {
  132. Debug.Log("websocket关闭," + string.Format("Closed: StatusCode: {0}, Reason: {1}", e.StatusCode, e.Reason));
  133. webSocket = null;
  134. ReConnect();
  135. }
  136. private void OnError(object sender, ErrorEventArgs e)
  137. {
  138. if (e != null)
  139. Debug.Log("websocket连接异常:" + e.Message);
  140. webSocket = null;
  141. ReConnect();
  142. }
  143. #endregion
  144. }
  145. }


6. 新建脚本RecorderManager,代码如下:

  1. using UnityEngine;
  2. using System;
  3. using System.Collections;
  4. using UnityEngine.UI;
  5. using Unity.VisualScripting;
  6. namespace Motionverse
  7. {
  8. public class RecorderManager : Singleton<RecorderManager>
  9. {
  10. //标记是否有麦克风
  11. private bool isHaveMic = false;
  12. //当前录音设备名称
  13. private string currentDeviceName = string.Empty;
  14. //表示录音的最大时长
  15. int recordMaxLength = 600;
  16. //录音频率,控制录音质量(16000)
  17. int recordFrequency = 16000;
  18. [HideInInspector]
  19. public static Action<byte[]> DataAvailable;
  20. [SerializeField]
  21. private GameObject micON;
  22. [SerializeField]
  23. private GameObject micOFF;
  24. [SerializeField]
  25. private Image micAmount;
  26. [SerializeField]
  27. private GameObject TextBG;
  28. private AudioClip saveAudioClip;
  29. int offsetSamples = 0;
  30. private void Start()
  31. {
  32. Debug.Log(Microphone.devices[0]);
  33. if (Microphone.devices.Length > 0)
  34. {
  35. isHaveMic = true;
  36. currentDeviceName = Microphone.devices[0];
  37. StartCoroutine(GetAudioFrames());
  38. StartRecording();
  39. }
  40. }
  41. /// <summary>
  42. /// 开始录音
  43. /// </summary>
  44. /// <returns></returns>
  45. public void StartRecording() //16000
  46. {
  47. if (isHaveMic == false || Microphone.IsRecording(currentDeviceName))
  48. {
  49. return;
  50. }
  51. micOFF.gameObject.SetActive(false);
  52. micON.gameObject.SetActive(true);
  53. TextBG.GetComponentInChildren<Text>().text = null;
  54. offsetSamples = 0;
  55. saveAudioClip = Microphone.Start(currentDeviceName, true, recordMaxLength, recordFrequency);
  56. }
  57. public void OnButtonClick()
  58. {
  59. if (Microphone.IsRecording(currentDeviceName))
  60. {
  61. EndRecording();
  62. }
  63. else
  64. {
  65. StartRecording();
  66. }
  67. }
  68. public void EndRecording()
  69. {
  70. if (isHaveMic == false || !Microphone.IsRecording(currentDeviceName))
  71. {
  72. return;
  73. }
  74. micOFF.gameObject.SetActive(true);
  75. micON.gameObject.SetActive(false);
  76. //结束录音
  77. Microphone.End(currentDeviceName);
  78. }
  79. public bool IsRecording()
  80. {
  81. return Microphone.IsRecording(currentDeviceName);
  82. }
  83. IEnumerator GetAudioFrames()
  84. {
  85. while (true)
  86. {
  87. if (Microphone.IsRecording(currentDeviceName))
  88. {
  89. int lenght = Microphone.GetPosition(currentDeviceName) * saveAudioClip.channels - offsetSamples;
  90. if (lenght > 0)
  91. {
  92. float[] samples = new float[lenght];
  93. saveAudioClip.GetData(samples, offsetSamples);
  94. var samplesShort = new short[samples.Length];
  95. for (var index = 0; index < samples.Length; index++)
  96. {
  97. samplesShort[index] = (short)(samples[index] * short.MaxValue);
  98. }
  99. byte[] binaryData = new byte[samplesShort.Length * 2];
  100. Buffer.BlockCopy(samplesShort, 0, binaryData, 0, binaryData.Length);
  101. offsetSamples += lenght;
  102. DataAvailable?.Invoke(binaryData);
  103. }
  104. yield return new WaitForSeconds(0.16f);
  105. }
  106. else
  107. {
  108. yield return new WaitForSeconds(0.16f);
  109. byte[] binaryData = new byte[2];
  110. DataAvailable?.Invoke(binaryData);
  111. }
  112. }
  113. }
  114. /// <summary>
  115. /// 获取毫秒级别的时间戳,用于计算按下录音时长
  116. /// </summary>
  117. /// <returns></returns>
  118. public double GetTimestampOfNowWithMillisecond()
  119. {
  120. return (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000;
  121. }
  122. private void LateUpdate()
  123. {
  124. if (isHaveMic == true && Microphone.IsRecording(currentDeviceName))
  125. {
  126. micAmount.fillAmount = Volume;
  127. }
  128. }
  129. public float Volume
  130. {
  131. get
  132. {
  133. if (Microphone.IsRecording(currentDeviceName))
  134. {
  135. // 采样数
  136. int sampleSize = 128;
  137. float[] samples = new float[sampleSize];
  138. int startPosition = Microphone.GetPosition(currentDeviceName) - (sampleSize + 1);
  139. // 得到数据
  140. if (startPosition < 0)
  141. return 0;
  142. saveAudioClip.GetData(samples, startPosition);
  143. // Getting a peak on the last 128 samples
  144. float levelMax = 0;
  145. for (int i = 0; i < sampleSize; ++i)
  146. {
  147. float wavePeak = samples[i];
  148. if (levelMax < wavePeak)
  149. levelMax = wavePeak;
  150. }
  151. return levelMax;
  152. }
  153. return 0;
  154. }
  155. }
  156. void OnGUI()
  157. {
  158. GUIStyle guiStyle = GUIStyle.none;
  159. guiStyle.fontSize = 10;
  160. guiStyle.normal.textColor = Color.white;
  161. guiStyle.alignment = TextAnchor.UpperLeft;
  162. Rect tr = new Rect(0, 0, 100, 100);
  163. GUI.Label(tr, currentDeviceName, guiStyle);
  164. }
  165. }
  166. }


7. 新建空物体,挂载脚本RecorderManager和RealtimeAsrManager
8. 输入百度asr的appId和secretKey:

image.png


9. 输入GPTkey、问心一眼appId和secretKey:

image.png


10. 根据需求选择相应的NLP方式:

image.png


注意:如果缺少Singleton文件,可使用如下代码:

  1. using UnityEngine;
  2. namespace Motionverse
  3. {
  4. public abstract class Singleton<T> : MonoBehaviour where T : Singleton<T>
  5. {
  6. private static T sInstance = null;
  7. public static T Instance
  8. {
  9. get
  10. {
  11. if (sInstance == null)
  12. {
  13. GameObject gameObject = new(typeof(T).FullName);
  14. sInstance = gameObject.AddComponent<T>();
  15. }
  16. return sInstance;
  17. }
  18. }
  19. public static void Clear()
  20. {
  21. sInstance = null;
  22. }
  23. protected virtual void Awake()
  24. {
  25. if (sInstance != null) Debug.LogError(name + "error: already initialized", this);
  26. sInstance = (T)this;
  27. }
  28. }
  29. }

若有收获,就点个赞吧

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/367728
推荐阅读
相关标签
  

闽ICP备14008679号