Unity 讯飞 之 讯飞星火大模型的简单封装和使用(补充讯飞大模型识图功能)

news2024/12/25 8:55:37

Unity 讯飞 之 讯飞星火大模型的简单封装和使用(补充讯飞大模型识图功能)

目录

Unity 讯飞 之 讯飞星火大模型的简单封装和使用(补充讯飞大模型识图功能)

一、简单介绍

二、实现原理

三、注意事项

四、效果预览

五、案例简单实现步骤

六、关键代码

七、案例下载地址

补充、讯飞大模型-图像识别


一、简单介绍

Unity 工具类,自己整理的一些游戏开发可能用到的模块,单独独立使用,方便游戏开发。

这里简单的介绍讯飞大模型的封装和使用,进行聊天。

“讯飞星火认知大模型”是科大讯飞发布的产品,具有7大核心能力,即文本生成、语言理解、知识问答、逻辑推理、数学能力、代码能力、多模态能力。

星火v1.5、v2.0地址: http://spark-api.xf-yun.com/v1/completions

星火v3.0地址: http://spark-api.xf-yun.com/v3/completions

星火v1.5和v2.0使用的是同一个URL,domain参数配置如下:

# domain = "general"       # v1.5版本
#domain = "generalv2"    # v2.0版本
domain = "generalv3"    # v3.0版本

  • 星火认知大模型 WebAPI 接口调用示例 接口文档(必看):星火认知大模型Web API文档 | 讯飞开放平台文档中心
  • 错误码链接:星火认知大模型服务说明 | 讯飞开放平台文档中心

二、实现原理

1、申请星火大模型的 APP_ID 等相关信息

2、通过使用的大模型版本,以及当前的时间,结合 申请星火大模型的 APP_ID 等相关信息,生成需要的 URL

3、通过对应的 json 数据格式,websocket 进行建立连接请求

4、这里是流式返回,对应解析数据格式,得到返回的信息

5、返回的关键信息结构,有些类似 gpt 的数据格式,用过的话,使用起来会很快

三、注意事项

1、注意 code 返回码,不同的返回码可以进行不同处理,避免产生意想不到的问题

2、注意 sid 的区分,如果上一次返回没有结束,关闭连接后,重新发起新的访问,可能会同时接收到上一次的未结束的数据流,和当次的数据流;如果不想接收到,注意通过 sid 进行区分;

3、注意在 LLMConfig 配置你的 APP_ID 等相关信息

四、效果预览

五、案例简单实现步骤

1、首先到讯飞官网获取对应的星火大模型的 APP_ID 等相关自己星火大模型的信息

讯飞星火认知大模型-AI大语言模型-星火大模型-科大讯飞

2、打开Unity ,简单搭建一个场景

3、可以参考星火大模型的相关demo,完成对应的讯飞大模型接口封装

这里包括 IIFlyLLMHandler(接口类,可以根据自己需要重新定义接口),BaseIFlyLLMHandler(基类),IFlyLLMHandler(基本的讯飞接口大模型类),AutoReConnectIFlyLLMHandler(待自动重连的讯飞接口大模型类)

4、完成对应接口的封装之后,添加一个简单的测试类 TestIFlyLLMHandler

5、把 TestIFlyLLMHandler  添加到场景中,比对应赋值

6、运行结果如上

六、关键代码

1、TestIFlyLLMHandler

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

public class TestIFlyLLMHandler : MonoBehaviour
{
    public Text TagrgetText;
    public InputField TagrgetInputField;
    public Button TagrgetButton;

    List<string> m_StrLst = new List<string>() { 
        "你是谁",
        "最近天气"
    };

    IIFlyLLMHandler m_Handler;

    // Start is called before the first frame update
    void Start()
    {
        m_Handler = new IFlyLLMHandler();
        m_Handler.Initialized();
        TagrgetText.text = "";
        m_Handler.OnMessageReceivedAction = (jsonResponse) => { TagrgetText.text += jsonResponse.payload.choices.text[0].content; };

        TagrgetButton.onClick.AddListener(() => {
            TagrgetText.text = "";
            m_Handler.SendMsg(TagrgetInputField.text);
        });
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.anyKeyDown) {
            //m_Handler.SendMsg(m_StrLst[Random.Range(0,m_StrLst.Count)]);
        }
    }
}

2、IFlyLLMHandler

using BestHTTP.WebSocket;
using System;
using UnityEngine;

/// <summary>
/// 使用讯飞大模型
/// </summary>
public class IFlyLLMHandler : BaseIFlyLLMHandler
{
    #region Data
    
    /// <summary>
    /// 发送连接服务器失败事件
    /// </summary>
    int m_TimerServerAccessFailure = -1;
    const float INTERVAL_TIME_SEND_SERVER_ACCESS_FAILURE = 4.0f;

    #endregion

    #region interface function

    /// <summary>
    /// 初始化
    /// </summary>
    public override void Initialized()
    {
        base.Initialized();
    }

    /// <summary>
    /// 发送数据
    /// </summary>
    /// <param name="askContent"></param>
    public override void SendMsg(string askContent)
    {
        try
        {
            CacheHistory(askContent);

            Connect();
        }
        catch (Exception e)
        {
            Debug.LogError($"e.ToString() {e.ToString()}\ne.Message {e.Message}");
        }
    }

    #endregion

    #region Websocket回调

    /// <summary>
    /// 建立连接事件
    /// </summary>
    /// <param name="ws"></param>
    protected override void OnWebSocketOpen(WebSocket ws)
    {
        Debug.Log("WebSocket Connected!");
        Send();
    }

    /// <summary>
    /// 连接错误信息
    /// </summary>
    /// <param name="ws"></param>
    /// <param name="ex"></param>
    protected override void OnWebSocketError(WebSocket ws, Exception ex)
    {
        base.OnWebSocketError(ws, ex);
        SendServerAccessFailure(1005);
    }

    /// <summary>
    /// 连接关闭事件
    /// </summary>
    /// <param name="ws"></param>
    /// <param name="code"></param>
    /// <param name="message"></param>
    protected override void OnWebSocketClosed(WebSocket ws, ushort code, string message)
    {
        base.OnWebSocketClosed(ws, code, message);
        SendServerAccessFailure(code);
    }

    #endregion

    #region private function

    /// <summary>
    /// 发送服务器连接失败错误
    /// </summary>
    /// <param name="code"></param>
    private void SendServerAccessFailure(UInt16 code)
    {
        if (code == 1005) // 连接失败错误码
        {
            if (m_TimerServerAccessFailure != -1) return;
            m_TimerServerAccessFailure = Timer.Instance.Post2Scale((index) => {
                Debug.Log("[BubbleChatUseIFlyLLMHandler] SendServerAccessFailure");
                m_TimerServerAccessFailure = -1;
            }, INTERVAL_TIME_SEND_SERVER_ACCESS_FAILURE);
        }
    }

    #endregion
}

3、BaseIFlyLLMHandler

using Newtonsoft.Json;
using System;
using UnityEngine;
using System.Text;
using BestHTTP.WebSocket;
using System.Collections.Generic;

/**
 * 星火认知大模型 WebAPI 接口调用示例 接口文档(必看):https://www.xfyun.cn/doc/spark/Web.html
 * 错误码链接:https://www.xfyun.cn/doc/spark/%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E.html (code返回错误码时必看)
 * @author iflytek
 */


/// <summary>
/// 讯飞大模型基类
/// </summary>
public class BaseIFlyLLMHandler : IIFlyLLMHandler
{
    #region Data

    /// <summary>
    /// WebSocket
    /// </summary>
    protected WebSocket m_WebSocket;

    /// <summary>
    /// 缓存的历史数据的队列
    /// </summary>
    protected List<IIFlyLLMHandler.Content> m_CacheHistory = new List<IIFlyLLMHandler.Content>();

    /// <summary>
    /// 是否读完
    /// </summary>
    protected bool m_ReadEnd = true;

    /// <summary>
    /// 是否需要重发
    /// </summary>
    protected bool m_NeedSend = false;

    /// <summary>
    /// 大模型的配置
    /// </summary>
    protected LLMConfig m_Config;

    /// <summary>
    /// 记录上一个老的 sid ,因为打断后,如果上一次的没有传输完,下一次连接依然会接收到该 sid 的数据流
    /// </summary>
    protected string m_OldSid = "";

    /// <summary>
    /// // 用来记录中间的sid ,因为可能被打断(不一定每次都能完全正常流程接收完数据)
    /// </summary>
    protected string m_RcdSid = ""; 

    /// <summary>
    /// 接收到数据的事件
    /// </summary>
    public Action<IIFlyLLMHandler.JsonResponse> OnMessageReceivedAction { get; set; }

    #endregion

    #region interface function

    
    /// <summary>
    /// 初始化
    /// </summary>
    public virtual void Initialized()
    {
        m_Config = new LLMConfig();
        
    }

    /// <summary>
    /// 连接
    /// </summary>
    public virtual void Connect()
    {

        if (m_WebSocket != null)
        {
            Close();
        }
        string authUrl = GetAuthUrl();
        string url = authUrl.Replace("http://", "ws://").Replace("https://", "wss://");
        m_WebSocket = new WebSocket(new Uri(url));
        m_WebSocket.OnOpen += OnWebSocketOpen;
        m_WebSocket.OnMessage += OnWebSocketMessage;
        m_WebSocket.OnError += OnWebSocketError;
        m_WebSocket.OnClosed += OnWebSocketClosed;
        m_WebSocket.Open();

    }


    /// <summary>
    /// 关闭
    /// </summary>
    public virtual void Close()
    {
        m_OldSid = m_RcdSid;
        m_WebSocket?.Close();
        m_WebSocket = null;
    }

    /// <summary>
    /// 重置
    /// </summary>
    public virtual void Reset()
    {
        m_CacheHistory.Clear();
    }

    /// <summary>
    /// 发送消息
    /// </summary>
    /// <param name="history"></param>
    public virtual void SendMsg(string askContent)
    {
        try
        {
            CacheHistory(askContent);

            if (m_WebSocket != null && m_WebSocket.IsOpen)
            {
                if (m_ReadEnd)
                {
                    Send();
                }
                else
                {
                    //中断websocket,然后再发送
                    Close();
                    m_NeedSend = true;
                }
            }
            else
            {
                m_NeedSend = true;
                Connect();
            }
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
        }
    }

    

    #endregion

    #region Websocket回调

    /// <summary>
    /// 建立连接的事件
    /// </summary>
    /// <param name="ws"></param>
    protected virtual void OnWebSocketOpen(WebSocket ws)
    {
        Debug.Log("WebSocket Connected!");
        if (m_NeedSend) Send();
    }

    /// <summary>
    /// 接收到数据的事件
    /// </summary>
    /// <param name="ws"></param>
    /// <param name="message"></param>
    protected virtual void OnWebSocketMessage(WebSocket ws, string message)
    {
        // 处理接收到的消息
        // 这里你可以根据需要进行相应的处理
        Debug.Log($"Received message: {message}");
        IIFlyLLMHandler.JsonResponse jsonResponse = JsonConvert.DeserializeObject<IIFlyLLMHandler.JsonResponse>(message);

        if (jsonResponse.header.code == 10013) // 错误码 10013:输入内容审核不通过,涉嫌违规,请重新调整输入内容
        {
            Debug.LogError($"OnWebSocketMessage message {jsonResponse.header.message} ");
        }
        else
        {
            Debug.Log($"OnWebSocketMessage m_OldSid {m_OldSid}, jsonResponse.header.sid {jsonResponse.header.sid} jsonResponse.header.code {jsonResponse.header.code}");
            // 判断不是上一次的 sid 的旧的数据流在做出反应
            if (jsonResponse.header.sid.Equals(m_OldSid) == false)
            {
                if (jsonResponse.header.code == 10014) // 错误码 10014:输出内容涉及敏感信息,审核不通过,后续结果无法展示给用户
                {
                    jsonResponse=GetHandleJsonResponse(jsonResponse);
                }
                OnMessageReceivedAction?.Invoke(jsonResponse);
                m_RcdSid = jsonResponse.header.sid;
            }
        }

        if (jsonResponse.payload.choices.status == 0)
        {
            Debug.Log("Get First IFlyLLM Response");
        }

        if (jsonResponse.payload.choices.status == 2)
        {
            m_ReadEnd = true;
            Debug.Log("Get Last IFlyLLM Response");
            m_OldSid = jsonResponse.header.sid;
        }
    }

    /// <summary>
    /// 连接发生错误的事件
    /// </summary>
    /// <param name="ws"></param>
    /// <param name="ex"></param>
    protected virtual void OnWebSocketError(WebSocket ws, Exception ex)
    {
        Debug.LogError($"WebSocket Error: {ex.Message}");
    }

    /// <summary>
    /// 连接关闭的事件
    /// </summary>
    /// <param name="ws"></param>
    /// <param name="code"></param>
    /// <param name="message"></param>
    protected virtual void OnWebSocketClosed(WebSocket ws, UInt16 code, string message)
    {
        Debug.LogFormat("WebSocket Closed!: code={0}, msg={1}", code, message);
    }

    #endregion

    #region protected function

    /// <summary>
    /// 真正发送数据
    /// </summary>
    protected virtual void Send()
    {
        m_NeedSend = false;
        m_ReadEnd = false;
        IIFlyLLMHandler.JsonRequest request = CreateRequest();
        string jsonString = JsonConvert.SerializeObject(request);
        Debug.LogError(jsonString);
        m_WebSocket?.Send(jsonString);
    }


    /// <summary>
    /// 创建一个连接请求
    /// </summary>
    /// <returns></returns>
    protected virtual IIFlyLLMHandler.JsonRequest CreateRequest()
    {
        return new IIFlyLLMHandler.JsonRequest
        {
            header = new IIFlyLLMHandler.Header
            {
                app_id = m_Config.IFLY_APPID,
                uid = Guid.NewGuid().ToString().Substring(0, 10)
            },
            parameter = new IIFlyLLMHandler.Parameter
            {
                chat = new IIFlyLLMHandler.Chat
                {
                    domain = "generalv3",
                    temperature = 0.01,
                    max_tokens = 480
                }
            },
            payload = new IIFlyLLMHandler.Payload
            {
                message = new IIFlyLLMHandler.Message
                {
                    text = m_CacheHistory
                }
            }
        };
    }



    /// <summary>
    /// 缓存历史记录数据结构,优化性能
    /// (缓存需要自己额外补充,这里先做简单的提问)
    /// </summary>
    protected virtual void CacheHistory(string askContent)
    {
        m_CacheHistory.Clear();
        IIFlyLLMHandler.Content content = new IIFlyLLMHandler.Content();
        content.role = "user";
        content.content = askContent;
        m_CacheHistory.Add(content);
    }

    /// <summary>
    /// 敏感输出内容后的处理
    /// 重新组织数据(涉及敏感内容不予展示)
    /// </summary>
    /// <param name="jsonResponse"></param>
    /// <returns></returns>
    protected virtual IIFlyLLMHandler.JsonResponse GetHandleJsonResponse(IIFlyLLMHandler.JsonResponse jsonResponse) {
        int status = 2;

        IIFlyLLMHandler.HeaderResponse header = new IIFlyLLMHandler.HeaderResponse() { code = 0, message = "Success", sid = jsonResponse.header.sid, status = status };
        
        IIFlyLLMHandler.Text text = new IIFlyLLMHandler.Text() {content="***(涉及敏感内容不予展示)。",role= "assistant",index=0 };
        List<IIFlyLLMHandler.Text> textlst = new List<IIFlyLLMHandler.Text>();
        textlst.Add(text);

        IIFlyLLMHandler.Choices choices = new IIFlyLLMHandler.Choices() { status= status, seq=999,text=textlst};
        IIFlyLLMHandler.PayloadResponse payload = new IIFlyLLMHandler.PayloadResponse() { choices=choices};

        IIFlyLLMHandler.JsonResponse reorganizingInformationJRspn = new IIFlyLLMHandler.JsonResponse() {header=header,payload=payload };

        return reorganizingInformationJRspn;
    }

    #endregion

    #region 字符串操作

    /// <summary>
    /// 生成得到授权的 URL 
    /// </summary>
    /// <returns></returns>
    private string GetAuthUrl()
    {
        string date = DateTime.UtcNow.ToString("r");
        Uri uri = new Uri(m_Config.IFLY_HOST_URL);
        StringBuilder builder = new StringBuilder("host: ").Append(uri.Host).Append("\n").//
                                Append("date: ").Append(date).Append("\n").//
                                Append("GET ").Append(uri.LocalPath).Append(" HTTP/1.1");
        string sha = HMACsha256(m_Config.IFLY_API_SECRET, builder.ToString());
        string authorization = string.Format("api_key=\"{0}\", algorithm=\"{1}\", headers=\"{2}\", signature=\"{3}\"", m_Config.IFLY_API_KEY, "hmac-sha256", "host date request-line", sha);
        string NewUrl = "https://" + uri.Host + uri.LocalPath;
        string path1 = "authorization" + "=" + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(authorization));
        date = date.Replace(" ", "%20").Replace(":", "%3A").Replace(",", "%2C");
        string path2 = "date" + "=" + date;
        string path3 = "host" + "=" + uri.Host;
        NewUrl = NewUrl + "?" + path1 + "&" + path2 + "&" + path3;
        return NewUrl;
    }

    /// <summary>
    /// HMACsha256 
    /// </summary>
    /// <param name="apiSecretIsKey"></param>
    /// <param name="buider"></param>
    /// <returns></returns>
    private string HMACsha256(string apiSecretIsKey, string buider)
    {
        byte[] bytes = System.Text.Encoding.UTF8.GetBytes(apiSecretIsKey);
        System.Security.Cryptography.HMACSHA256 hMACSHA256 = new System.Security.Cryptography.HMACSHA256(bytes);
        byte[] date = System.Text.Encoding.UTF8.GetBytes(buider);
        date = hMACSHA256.ComputeHash(date);
        hMACSHA256.Clear();
        return Convert.ToBase64String(date);
    }

    #endregion

}

4、IIFlyLLMHandler

using System;
using System.Collections.Generic;

/// <summary>
/// 讯飞大模型接口类
/// </summary>
public interface IIFlyLLMHandler
{
    /// <summary>
    /// 初始化
    /// </summary>
    void Initialized();
    /// <summary>
    /// 连接
    /// </summary>
    void Connect();
    /// <summary>
    /// 关闭连接
    /// </summary>
    void Close();
    /// <summary>
    /// 重置
    /// </summary>
    void Reset();
    /// <summary>
    /// 发送信息
    /// </summary>
    /// <param name="askContent"></param>
    void SendMsg(string askContent);
    /// <summary>
    /// 接收到数据事件
    /// </summary>
    Action<JsonResponse> OnMessageReceivedAction { get; set; }

    #region Request数据类定义

    public class JsonRequest
    {
        public Header header { get; set; }
        public Parameter parameter { get; set; }
        public Payload payload { get; set; }
    }

    public class Header
    {
        public string app_id { get; set; }
        public string uid { get; set; }
    }

    public class Parameter
    {
        public Chat chat { get; set; }
    }

    public class Chat
    {
        public string domain { get; set; }
        public double temperature { get; set; }
        public int max_tokens { get; set; }
    }

    public class Payload
    {
        public Message message { get; set; }
    }

    public class Message
    {
        public List<Content> text { get; set; }
    }

    public class Content
    {
        public string role { get; set; }
        public string content { get; set; }
    }

    #endregion

    #region Response数据类型定义

    public class JsonResponse
    {
        public HeaderResponse header { get; set; }
        public PayloadResponse payload { get; set; }
    }

    public class HeaderResponse
    {
        public int code { get; set; }
        public string message { get; set; }
        public string sid { get; set; }
        public int status { get; set; }
    }

    public class PayloadResponse
    {
        public Choices choices { get; set; }
    }

    public class Choices
    {
        public int status { get; set; }
        public int seq { get; set; }
        public List<Text> text { get; set; }
    }

    public class Text
    {
        public string content { get; set; }
        public string role { get; set; }
        public int index { get; set; }
    }

    #endregion
}

5、LLMConfig

public class LLMConfig
{
    #region 讯飞

    /// <summary>
    /// 应用APPID(必须为webapi类型应用,并开通星火认知大模型授权)
    /// </summary>
    public readonly string IFLY_APPID = "YOUR_IFLY_APPID";

    /// <summary>
    /// 接口密钥(webapi类型应用开通星火认知大模型后,控制台--我的应用---星火认知大模型---相应服务的apikey)
    /// </summary>
    public readonly string IFLY_API_SECRET = "YOUR_IFLY_API_SECRET";

    /// <summary>
    /// 接口密钥(webapi类型应用开通星火认知大模型后,控制台--我的应用---星火认知大模型---相应服务的apisecret)
    /// </summary>
    public readonly string IFLY_API_KEY = "YOUR_IFLY_API_KEY";

    /// <summary>
    /// 讯飞大模型访问地址
    /// </summary>
    public readonly string IFLY_HOST_URL = "https://spark-api.xf-yun.com/v3.1/chat";

    /// <summary>
    /// 讯飞图像识别
    /// </summary>
    public readonly string IFLY_IMAGE_RECOGNIZE_URL = "wss://spark-api.cn-huabei-1.xf-yun.com/v2.1/image";

    #endregion
}

6、AutoReConnectIFlyLLMHandler

using BestHTTP.WebSocket;
using System;
using UnityEngine;

/// <summary>
/// 带自动重连的讯飞大模型访问
/// </summary>
public class AutoReConnectIFlyLLMHandler : BaseIFlyLLMHandler
{

    #region Data

    /// <summary>
    /// m_ReconnectTimerId :自动重连的 TimerId
    /// </summary>
    int m_ReconnectTimerId = -1;

    /// <summary>
    /// m_ReconnectDelay :  自动重连的 时间
    /// </summary>
    float m_ReconnectDelay = 0.5f;

    #endregion

    #region interface function

    /// <summary>
    /// 初始化
    /// </summary>
    public override void Initialized()
    {
        base.Initialized();
        Connect();
    }

    /// <summary>
    /// 关闭连接
    /// </summary>
    public override void Close()
    {
        CancelReconnectTimer();
        base.Close();
    }

    /// <summary>
    /// 自动重连
    /// </summary>
    internal void AutoReconnect()
    {
        Connect();
    }

    /// <summary>
    /// 发送请求
    /// </summary>
    /// <param name="askContent"></param>
    public override void SendMsg(string askContent)
    {
        try
        {
            CacheHistory(askContent);

            if (m_WebSocket != null && m_WebSocket.IsOpen)
            {
                if (m_ReadEnd)
                {
                    Send();
                }
                else
                {
                    //中断websocket,然后再发送
                    Close();
                    m_NeedSend = true;
                }
            }
            else
            {
                m_NeedSend = true;
            }
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
        }
    }

    #endregion

    #region Websocket回调

    /// <summary>
    /// 关闭连接事件
    /// </summary>
    /// <param name="ws"></param>
    /// <param name="code"></param>
    /// <param name="message"></param>
    protected override void OnWebSocketClosed(WebSocket ws, ushort code, string message)
    {
        base.OnWebSocketClosed(ws, code, message);
        RunReconnectTimer();
    }

    /// <summary>
    /// 连接发生错误的事件
    /// </summary>
    /// <param name="ws"></param>
    /// <param name="ex"></param>
    protected override void OnWebSocketError(WebSocket ws, Exception ex)
    {
        base.OnWebSocketError(ws, ex);
        RunReconnectTimer();
    }
    #endregion
    #region 计时器

    /// <summary>
    /// 计时自动重新连接
    /// </summary>
    private void RunReconnectTimer()
    {
        Debug.Log($"Scheduling reconnect in {m_ReconnectDelay} seconds...");
        CancelReconnectTimer();
        m_ReconnectTimerId = Timer.Instance.Post2Really((v) => { AutoReconnect(); }, m_ReconnectDelay);
    }

    /// <summary>
    /// 取消重新连接计时
    /// </summary>
    private void CancelReconnectTimer()
    {
        if (m_ReconnectTimerId != -1)
        {
            Timer.Instance.Cancel(m_ReconnectTimerId);
            m_ReconnectTimerId = -1;
        }
    }

    #endregion
}

七、案例下载地址

https://download.csdn.net/download/u014361280/88565465

补充、讯飞大模型-图像识别

1、IFlyLLMImageHandler

using UnityEngine;
using System;
using System.Text;
using Newtonsoft.Json;
using BestHTTP.WebSocket;
using System.Collections.Generic;

public class IFlyLLMImageHandler 
{
    private WebSocket m_WebSocket;

    private int m_ReconnectTimerId = -1;
    private float m_ReconnectDelay = 0.5f;
    private List<Content> m_CacheHistory = new List<Content>();

    public Action<JsonResponse> OnMessageReceivedAction;
    private bool m_ReadEnd = true;
    private bool m_NeedSend = false;

    private LLMConfig m_Config;

    public void Initialized()
    {
        m_Config = new LLMConfig();
        Connect();
    }

    /// <summary>
    /// 连接
    /// </summary>
    public void Connect()
    {

        if (m_WebSocket != null)
        {
            Close();
        }
        string authUrl = GetAuthUrl();
        string url = authUrl.Replace("http://", "ws://").Replace("https://", "wss://");
        m_WebSocket = new WebSocket(new Uri(url));
        m_WebSocket.OnOpen += OnWebSocketOpen;
        m_WebSocket.OnMessage += OnWebSocketMessage;
        m_WebSocket.OnError += OnWebSocketError;
        m_WebSocket.OnClosed += OnWebSocketClosed;
        m_WebSocket.Open();

    }

    /// <summary>
    /// 关闭
    /// </summary>
    public void Close()
    {
        CancelReconnectTimer();
        m_WebSocket?.Close();
        m_WebSocket = null;
    }

    public void Reset()
    {
        m_CacheHistory.Clear();
    }

    /// <summary>
    /// 自动重连
    /// </summary>
    private void AutoReconnect()
    {
        Connect();
    }

    /// <summary>
    /// 发送消息
    /// </summary>
    /// <param name="history"></param>
    public void SendMsg(string base64,string ask)
    {
        try
        {
            ImageDataAsk(base64, ask);

            if (m_WebSocket != null && m_WebSocket.IsOpen)
            {
                if (m_ReadEnd)
                {
                    Send();
                }
                else
                {
                    //中断websocket,然后再发送
                    Close();
                    m_NeedSend = true;
                }
            }
            else
            {
                m_NeedSend = true;
            }
        }
        catch (Exception e)
        {
            Debug.LogError(e.Message);
        }
    }

    private void Send()
    {
        m_NeedSend = false;
        m_ReadEnd = false;
        JsonRequest request = CreateRequest();
        string jsonString = JsonConvert.SerializeObject(request);
        Debug.LogError(jsonString);
        m_WebSocket?.Send(jsonString);
    }

    #region Websocket回调

    private void OnWebSocketOpen(WebSocket ws)
    {
        Debug.Log("WebSocket Connected!");
        if (m_NeedSend) Send();
    }

    private void OnWebSocketMessage(WebSocket ws, string message)
    {
        // 处理接收到的消息
        // 这里你可以根据需要进行相应的处理
        Debug.Log($"Received message: {message}");
        JsonResponse jsonResponse = JsonConvert.DeserializeObject<IFlyLLMImageHandler.JsonResponse>(message);
        OnMessageReceivedAction?.Invoke(jsonResponse);

        if (jsonResponse.payload.choices.status == 0)
        {
            Debug.Log("Get First IFlyLLM Response");
        }

        if (jsonResponse.payload.choices.status == 2)
        {
            m_ReadEnd = true;
            Debug.Log("Get Last IFlyLLM Response");
        }
    }

    private void OnWebSocketError(WebSocket ws, Exception ex)
    {
        Debug.LogError($"WebSocket Error: {ex.Message}");
        RunReconnectTimer();
    }

    private void OnWebSocketClosed(WebSocket ws, UInt16 code, string message)
    {
        Debug.LogFormat("WebSocket Closed!: code={0}, msg={1}", code, message);
        RunReconnectTimer();
    }

    #endregion

    private JsonRequest CreateRequest()
    {
        return new JsonRequest
        {
            header = new Header
            {
                app_id = m_Config.IFLY_APPID,
                uid = Guid.NewGuid().ToString().Substring(0, 10)
            },
            parameter = new Parameter
            {
                chat = new Chat
                {
                    domain = "general",
                    temperature = 0.5,
                    top_k= 4,
                    max_tokens = 2048,
                    auditing = "default"

                }
            },
            payload = new Payload
            {
                message = new Message
                {
                    text = m_CacheHistory
                }
            }
        };
    }

    private void ImageDataAsk(string base64,string ask) {
        m_CacheHistory.Clear();
        Content content = new Content();
        content.role = "user";
        content.content = base64;
        content.content_type = "image";
        m_CacheHistory.Add(content);

        content = new Content();
        content.role = "user";
        content.content = ask;
        content.content_type = "text";
        m_CacheHistory.Add(content);
    }


    #region 计时器

    private void RunReconnectTimer()
    {
        Debug.Log($"Scheduling reconnect in {m_ReconnectDelay} seconds...");
        CancelReconnectTimer();
        m_ReconnectTimerId = Timer.Instance.Post2Really((v) => { AutoReconnect(); }, m_ReconnectDelay);
    }

    private void CancelReconnectTimer()
    {
        if (m_ReconnectTimerId != -1)
        {
            Timer.Instance.Cancel(m_ReconnectTimerId);
            m_ReconnectTimerId = -1;
        }
    }

    #endregion

    #region 字符串操作

    private string GetAuthUrl()
    {
        string date = DateTime.UtcNow.ToString("r");
        Uri uri = new Uri(m_Config.IFLY_IMAGE_RECOGNIZE_URL);
        StringBuilder builder = new StringBuilder("host: ").Append(uri.Host).Append("\n").//
                                Append("date: ").Append(date).Append("\n").//
                                Append("GET ").Append(uri.LocalPath).Append(" HTTP/1.1");
        string sha = HMACsha256(m_Config.IFLY_API_SECRET, builder.ToString());
        string authorization = string.Format("api_key=\"{0}\", algorithm=\"{1}\", headers=\"{2}\", signature=\"{3}\"", m_Config.IFLY_API_KEY, "hmac-sha256", "host date request-line", sha);
        string NewUrl = "https://" + uri.Host + uri.LocalPath;
        string path1 = "authorization" + "=" + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(authorization));
        date = date.Replace(" ", "%20").Replace(":", "%3A").Replace(",", "%2C");
        string path2 = "date" + "=" + date;
        string path3 = "host" + "=" + uri.Host;
        NewUrl = NewUrl + "?" + path1 + "&" + path2 + "&" + path3;
        return NewUrl;
    }

    private string HMACsha256(string apiSecretIsKey, string buider)
    {
        byte[] bytes = System.Text.Encoding.UTF8.GetBytes(apiSecretIsKey);
        System.Security.Cryptography.HMACSHA256 hMACSHA256 = new System.Security.Cryptography.HMACSHA256(bytes);
        byte[] date = System.Text.Encoding.UTF8.GetBytes(buider);
        date = hMACSHA256.ComputeHash(date);
        hMACSHA256.Clear();
        return Convert.ToBase64String(date);
    }

    #endregion

    #region Request数据类定义

    public class JsonRequest
    {
        public Header header { get; set; }
        public Parameter parameter { get; set; }
        public Payload payload { get; set; }
    }

    public class Header
    {
        public string app_id { get; set; }
        public string uid { get; set; }
    }

    public class Parameter
    {
        public Chat chat { get; set; }
    }

    public class Chat
    {
        public string domain { get; set; }
        public double temperature { get; set; }
        public int top_k { get; set; }
        public int max_tokens { get; set; }
        public string auditing { get; set; }
    }

    public class Payload
    {
        public Message message { get; set; }
    }

    public class Message
    {
        public List<Content> text { get; set; }
    }

    public class Content
    {
        public string role { get; set; }
        public string content { get; set; }
        public string content_type { get; set; }
    }

    #endregion

    #region Response数据类型定义

    public class JsonResponse
    {
        public HeaderResponse header { get; set; }
        public PayloadResponse payload { get; set; }
    }

    public class HeaderResponse
    {
        public int code { get; set; }
        public string message { get; set; }
        public string sid { get; set; }
        public int status { get; set; }
    }

    public class PayloadResponse
    {
        public Choices choices { get; set; }
        public Usage usage { get; set; }
    }

    public class Choices
    {
        public int status { get; set; }
        public int seq { get; set; }
        public List<Text> text { get; set; }
    }

    public class Text
    {
        public string content { get; set; }
        public string role { get; set; }
        public int index { get; set; }
    }

    public class Usage
    {
        public UsageText text { get; set; }
    }

    public class UsageText { 
        public int completion_tokens { get; set; }
        public int question_tokens { get; set; }
        public int prompt_tokens { get; set; }
        public int total_tokens { get; set; }
    }

    #endregion
}

2、TestIFlyLLMImageHandler


using UnityEngine;

public class TestIFlyLLMImageHandler : Singleton<TestIFlyLLMImageHandler>
{
    public void Test() {

        IFlyLLMImageHandler imageHandler = new IFlyLLMImageHandler();
        imageHandler.Initialized();
        imageHandler.OnMessageReceivedAction += (str) => {
            Debug.Log(" TestIFlyLLMImageHandler OnMessageReceivedAction " + str.payload.choices.text[0].content);
        
        };

        CameraCaptureImageForBase64.Instance.OnGetBase64ImageSuccess = (base64) =>
        {
            imageHandler.SendMsg(base64, "这是什么,尽可能详细的回答");
        };
        CameraCaptureImageForBase64.Instance.OnGetBase64ImageFailed = async (str) =>
        {
            string base64 = await ImageLoader.Instance.LoadImageAndConvertToBase64();
            imageHandler.SendMsg(base64, "这是什么,尽可能详细的回答");
        };
        CameraCaptureImageForBase64.Instance.CaptureImage();
    }
}

3、CameraCaptureImageForBase64


using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Android;

/// <summary>
/// 打开 Camera  捕捉图片,获取图片 base64 信息
/// </summary>
public class CameraCaptureImageForBase64 : MonoSingleton<CameraCaptureImageForBase64>
{
    #region data
    
    /// <summary>
    /// 获取图片成功事件
    /// </summary>
    public Action<string> OnGetBase64ImageSuccess;
    /// <summary>
    /// 获取图片失败事件
    /// </summary>
    public Action<string> OnGetBase64ImageFailed;

    private WebCamTexture webcamTexture; // 用于获取相机图像
    private Coroutine m_Coroutine;

    #endregion

    /// <summary>
    /// 捕捉图片
    /// </summary>
    public void CaptureImage()
    {
        m_Coroutine = StartCoroutine(CaptureRoutine());
    }

    /// <summary>
    /// 停止捕捉
    /// </summary>
    public void StopCapture()
    {
        if (m_Coroutine!=null)
        {
            StopCoroutine(m_Coroutine);
            m_Coroutine = null;
        }
    }

    #region private function

    private IEnumerator CaptureRoutine()
    {
        // 检查是否有相机权限
        if (!Permission.HasUserAuthorizedPermission(Permission.Camera))
        {
            // 如果没有相机权限,等待用户授权
            yield return RequestCameraPermission();
        }

        // 启动相机
        StartCamera();

        // 等待用户拍照
        yield return WaitForUserToTakePhoto();

        // 拍照
        Texture2D photo = TakePhoto();

        if (photo != null)
        {
            // 将Texture2D转换为byte数组
            byte[] imageBytes = photo.EncodeToPNG();

            // 将byte数组转换为base64字符串
            string base64String = Convert.ToBase64String(imageBytes);

            Debug.Log("Base64 Image: " + base64String);

            // 在这里执行你的异步任务,例如上传base64图片到服务器
            OnGetBase64ImageSuccess?.Invoke(base64String);
        }
        else {
            OnGetBase64ImageFailed?.Invoke("capture image failed.");
        }

        // 停止相机
        StopCamera();
    }

    private IEnumerator RequestCameraPermission()
    {
        // 请求相机权限
        Permission.RequestUserPermission(Permission.Camera);

        // 等待用户授权
        yield return new WaitUntil(() => Permission.HasUserAuthorizedPermission(Permission.Camera));
    }

    private void StartCamera()
    {
        // 获取可用的相机设备
        WebCamDevice[] devices = WebCamTexture.devices;

        if (devices.Length > 0)
        {
            // 使用第一个相机设备
            webcamTexture = new WebCamTexture(devices[0].name);
            webcamTexture.Play();
        }
        else
        {
            Debug.LogError("No camera device available.");
            OnGetBase64ImageFailed?.Invoke("No camera device available.");
        }
    }

    private IEnumerator WaitForUserToTakePhoto()
    {
        // 可以其他按键操作拍照
        // 这里直接等到若干秒
        yield return new WaitForSecondsRealtime(0.1f);
    }

    private Texture2D TakePhoto()
    {
        if (webcamTexture != null && webcamTexture.isPlaying)
        {
            Texture2D photo = new Texture2D(webcamTexture.width, webcamTexture.height);
            photo.SetPixels(webcamTexture.GetPixels());
            photo.Apply();
            return photo;
        }
        return null;
    }

    private void StopCamera()
    {
        if (webcamTexture != null && webcamTexture.isPlaying)
        {
            webcamTexture.Stop();
        }
    }

    #endregion
}

4、ImageLoader


using System;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class ImageLoader : Singleton<ImageLoader>
{
    [SerializeField] private string imageAddress = "车"; // 设置为你图片在Addressables中的地址

    public async Task<string> LoadImageAndConvertToBase64()
    {
        string base64String = string.Empty;

        // 使用Addressables加载图片
        AsyncOperationHandle<Texture2D> handle = Addressables.LoadAssetAsync<Texture2D>(imageAddress);

        // 等待加载完成
        await handle.Task;

        if (handle.Status == AsyncOperationStatus.Succeeded)
        {
            Texture2D texture = handle.Result;

            // 将Texture2D转换为byte数组
            byte[] imageBytes = texture.EncodeToPNG();

            // 将byte数组转换为base64字符串
            base64String = Convert.ToBase64String(imageBytes);

            Debug.Log("Base64 Image: " + base64String);
        }
        else
        {
            Debug.LogError("Failed to load image: " + handle.OperationException);
        }

        // 释放资源
        Addressables.Release(handle);

        return base64String;
    }
}

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

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

相关文章

【Element】el-switch开关 点击弹窗确认框时状态先改变----点击弹窗取消框失效

一、背景 需求&#xff1a;在列表中添加定期出账的开关按钮&#xff0c;点击开关时&#xff0c;原来的状态不改变&#xff0c;弹出弹窗&#xff1b;点击弹窗取消按钮&#xff1a;状态不改变&#xff0c;点击弹窗确定按钮&#xff1a;状态改变&#xff0c;并调取列表数据刷新页…

python 如何利用everything的能力快速搜索兴趣文件夹

演示代码 # -*- coding:UTF-8 -*- """ author: dyy contact: douyaoyuan126.com time: 2023/11/23 17:10 file: python 如何通过everything搜索兴趣文档.py desc: xxxxxx """# region 引入必要的依赖 import os模块名 DebugInfo try:from Debu…

使用 Python 和 NLTK 进行文本摘要

一、说明 文本摘要是一种自然语言处理技术&#xff0c;允许用户将大量文本总结为小块&#xff0c;而不会丢失任何重要信息。本文介绍NLP中使用Gensim和Sumy实现文本摘要的步骤。 二、为什么要总结文本&#xff1f; 互联网包含大量信息&#xff0c;而且每秒都在增加。文本摘要可…

Java实现求最大值

1 问题 接收用户输入的3个整数&#xff0c;如何将最大值作为结果输出。 2 方法 采用“截图文字代码”的方式描述。 引入输入包调用main()函数&#xff0c;提示并接收用户输入的3个整数&#xff0c;并交由变量a b c来保存。对接收的3个数据进行比较&#xff0c;先比较a和b&#…

『许战海战略文库』打造技术品牌:企业的新成长引擎

引言&#xff1a;随着全球化和技术的快速发展,企业面临的竞争压力也越来越大。在这种环境下,仅仅拥有技术优势是不够的,如何将技术转化为品牌的核心竞争力&#xff0c;从而实现企业的长期和持续发展,成为许多企业面临的核心问题。 产业技术品牌不仅代表企业技术实力&#xff0c…

Vue项目实战之一----实现分类弹框效果

效果图 实现 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><script src"js/vue.js"></script><!-- 引入样式 --><link rel"stylesheet&qu…

p12 63.删除无头结点无头指针的循环链表中所有值为x的结点 桂林电子科技大学2015年 (c语言代码实现)注释详解

本题代码如下 void delete(linklist* L, int x) {lnode* p *L, * q *L;while (p->next ! q)// 从第一个结点开始遍历链表&#xff0c;直到尾结点的前一个结点{if (p->next->data x)//判断是否等于x{lnode* r p->next;//将r指向x的位置p->next r->next;…

热门话题解析:pytest测试用例顺序问题解决方案!

前言 上一篇文章我们讲了在pytest中测试用例的命名规则&#xff0c;那么在pytest中又是以怎样的顺序执行测试用例的呢&#xff1f; 在unittest框架中&#xff0c;默认按照ACSII码的顺序加载测试用例并执行&#xff0c;顺序为&#xff1a;09、AZ、a~z&#xff0c;测试目录、测…

Redis key的类型以及命令

系列文章目录 第一章 Java线程池技术应用 第二章 CountDownLatch和Semaphone的应用 第三章 Spring Cloud 简介 第四章 Spring Cloud Netflix 之 Eureka 第五章 Spring Cloud Netflix 之 Ribbon 第六章 Spring Cloud 之 OpenFeign 第七章 Spring Cloud 之 GateWay 第八章 Sprin…

常见树种(贵州省):016杜鹃、含笑、桃金娘、金丝桃、珍珠花、观光木

摘要&#xff1a;本专栏树种介绍图片来源于PPBC中国植物图像库&#xff08;下附网址&#xff09;&#xff0c;本文整理仅做交流学习使用&#xff0c;同时便于查找&#xff0c;如有侵权请联系删除。 图片网址&#xff1a;PPBC中国植物图像库——最大的植物分类图片库 一、杜鹃 …

C语言——指针(二)

&#x1f4dd;前言 上篇文章C语言——指针&#xff08;一&#xff09;初步讲解了&#xff1a; 1&#xff0c;指针与指针变量 2&#xff0c;指针变量的基本使用&#xff08;如何定义&#xff0c;初始化&#xff0c;引用&#xff09; 这篇文章我们进一步探讨&#xff0c;使用指针…

【漏洞复现】金蝶云星空管理中心 ScpSupRegHandler接口存在任意文件上传漏洞 附POC

漏洞描述 金蝶云星空是一款云端企业资源管理(ERP)软件,为企业提供财务管理、供应链管理以及业务流程管理等一体化解决方案。金蝶云星空聚焦多组织,多利润中心的大中型企业,以 “开放、标准、社交”三大特性为数字经济时代的企业提供开放的 ERP 云平台。服务涵盖:财务、供…

原始类型 vs. 对象实践应用

● 首先是原始类型的例子 let lastName Williams; let oldLastName lastName; lastName Davis; console.log(lastName.oldLastName);● 然后是对象的例子 const jessica {firstName: Jessica,lastName: Williams,age: 27, }; const marriedJessica jessica; marriedJess…

docker部署phpIPAM

0说明 IPAM&#xff1a;IP地址管理系统 IP地址管理(IPAM)是指的一种方法IP扫描&#xff0c;IP地址跟踪和管理与网络相关的信息的互联网协议地址空间和IPAM系统。 IPAM软件和IP的工具,管理员可以确保分配IP地址仍然是当前和足够的库存先进的IP工具和IPAM服务。 IPAM简化并自动化…

2023.11.25-istio安全

目录 文章目录 目录本节实战1、安全概述2、证书签发流程1.签发证书2.身份认证 3、认证1.对等认证a.默认的宽容模式b.全局严格 mTLS 模式c.命名空间级别策略d.为每个工作负载启用双向 TLS 2.请求认证a.JWK 与 JWKS 概述b.配置 JWT 终端用户认证c.设置强制认证规则 关于我最后 本…

居家适老化设计第三十条---卫生间之坐便

以上产品图片均来源于淘宝 侵权联系删除 在居家适老化中&#xff0c;马桶是非常重要的设施之一&#xff0c;它能够提供方便、安全、舒适的上厕所体验。以下是一些居家适老化中常见的马桶设计和功能&#xff1a;1. 高度合适&#xff1a;为了方便老年人坐起和站起&#xff0c;马…

js逆向-JS加密破解

一、常见五种js加密手段 &#xff08;一&#xff09;加密位置&#xff1a; 1.Request Payload 加密 2.Request Headers 加密 3.Request URL params 参数加密 4.Response Data 数据加密 5.JS代码混淆加密 &#xff08;二&#xff09;加密算法 base64 编码 哈希算法&…

【从删库到跑路 | MySQL总结篇】数据库基础(增删改查的基本操作)

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【MySQL学习专栏】&#x1f388; 本专栏旨在分享学习MySQL的一点学习心得&#xff0c;欢迎大家在评论区讨论&#x1f48c; 重点放前面&am…

常见树种(贵州省):017柳树、喜树、珙桐、木棉、楝、枫杨、竹柏、百日青、翅荚香槐、皂荚、灯台树

摘要&#xff1a;本专栏树种介绍图片来源于PPBC中国植物图像库&#xff08;下附网址&#xff09;&#xff0c;本文整理仅做交流学习使用&#xff0c;同时便于查找&#xff0c;如有侵权请联系删除。 图片网址&#xff1a;PPBC中国植物图像库——最大的植物分类图片库 一、柳树 …

Ubuntu服务器/工作站常见故障修复记录

日常写代码写方案文档&#xff0c;偶尔遇上服务器出现问题的时候&#xff0c;也需要充当一把运维工程师&#xff0c;此帖用来记录服务器报错的一些解决方案&#xff0c;仅供参考&#xff01; 文章目录 一、服务器简介二、机箱拆解三、基本操作3.1 F2进入BIOS3.2 F12进入Boot Me…