Unity接入AI—Deepseek,数据流式传输

csharp 复制代码
using System;
using Newtonsoft.Json;
using QFramework;
using System.Collections;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;

public class AIDeepSeekManager : MonoSingleton<AIDeepSeekManager>
{
    private AIDeepSeekManager()
    {
    }


    private UnityWebRequest currentRequest;
    private string apiKey = "替换为自己的apikey"; // 替换为实际的API密钥
    private string apiEndpoint = "https://api.deepseek.com/v1/chat/completions";

    [TextArea]
    public string systemPrompt = "你的姓名叫小白,性别女生,是一个有感情的,有幽默感的,有创造力的,有思考力的,有同情心的,有爱心的,有责任心,说话温柔的智能体";

    // 使用的模型的 ID :[deepseek-chat, deepseek-reasoner]
    //直接回答
    private string modelNoThink = "deepseek-chat";
    //思考模式,更准确
    private string modelThink = "deepseek-reasoner";


    //是否开启思考模式
    public bool IsOpenThinking = false;

    //是否正在AI生成

    public BindableProperty<bool> IsGenerating = new BindableProperty<bool>(false);


    //AI回复消息回调,完整消息内容
    private Action<string> mCallBack;

    private object CreateRequestData(string strChat)
    {
        StringBuilder sb = new StringBuilder();

        sb.AppendLine($"你的身份:{systemPrompt}");
        sb.AppendLine("请根据用户提问的相关问题进行回答");
        return new
        {
            messages = new[]
            {
                new
                {
                    role = "system",
                    //content = systemPrompt,
                    content = $"{sb.ToString()}",
                },
                new
                {
                    role = "user",
                    content = strChat,
                },
            },
            model = IsOpenThinking?modelThink:modelNoThink,
            thinking = new
            {
                type = IsOpenThinking?"enabled": "disabled",
            },
            frequency_penalty = 0,
            max_tokens = 4096,
            presence_penalty = 0,
            response_format = new
            {
                type = "text",
            },
            stream = true,
            temperature = 0.7,
        };
    }



    public void SendMessage(string message,Action<string> callBack )
    {
        IsGenerating.Value = true;
        mStringBuilder.Clear();
        mCallBack = callBack;

        var requestData = CreateRequestData(message);

        string jsonPayload = JsonConvert.SerializeObject(requestData);
        Debug.Log($"发送的请求内容: {jsonPayload}");

        byte[] rawData = System.Text.Encoding.UTF8.GetBytes(jsonPayload);

        currentRequest = new UnityWebRequest(apiEndpoint, "POST");
        currentRequest.uploadHandler = new UploadHandlerRaw(rawData);
        currentRequest.downloadHandler = new CustomDownloadHandler(ProcessStreamResponse);

        currentRequest.SetRequestHeader("Content-Type", "application/json");
        currentRequest.SetRequestHeader("Accept", "text/event-stream");
        currentRequest.SetRequestHeader("Authorization", $"Bearer {apiKey}");

        currentRequest.timeout = 120; // 120秒超时

        // 启用协程来处理流式响应
        StartCoroutine(SendRequest());
    }

    private IEnumerator SendRequest()
    {
        yield return currentRequest.SendWebRequest();
        if (currentRequest != null)
        {
            if (currentRequest.result == UnityWebRequest.Result.ConnectionError ||
                currentRequest.result == UnityWebRequest.Result.ProtocolError)
            {
                Debug.LogError($"请求错误: {currentRequest.error}");
                Debug.LogError($"状态码: {currentRequest.responseCode}");
                Debug.LogError($"响应内容: {currentRequest.downloadHandler.text}");

                if (currentRequest.responseCode == (int)EnumDeepSeekCode.InsufficientBalance)
                {
                    Debug.LogError("API需要付费,请检查账户余额和API密钥状态");
                }

                IsGenerating.Value = false;
                mCallBack = null;
            }
            else
            {
                ProcessStreamResponse(currentRequest.downloadHandler.text);
            }
        }
    }


    private void ProcessStreamResponse(string responseText)
    {
        string[] lines = responseText.Split('\n');
        foreach (string line in lines)
        {
            if (string.IsNullOrWhiteSpace(line))
                continue;

            if (line.StartsWith("data: "))
            {
                string json = line.Substring(6).Trim();
                if (json == "[DONE]")
                {
                    Debug.Log("流式传输结束");
                    IsGenerating.Value = false;
                    mCallBack = null;
                    continue;
                }

                try
                {
                    var responseData = JsonConvert.DeserializeObject<DeepSeekResponse>(json);
                    if (responseData?.choices?.Length > 0 && !string.IsNullOrEmpty(responseData.choices[0].delta?.content))
                    {
                        ProcessStreamContent(responseData.choices[0].delta.content);
                    }
                }
                catch (JsonException e)
                {
                    Debug.LogError($"解析流式数据时出错: {e.Message}");
                    Debug.LogError($"原始数据: {json}");
                    IsGenerating.Value = false;
                    mCallBack = null;
                }
            }
        }
    }


    private StringBuilder mStringBuilder = new StringBuilder();

    private void ProcessStreamContent(string content)
    {
        mStringBuilder.Append(content);
        Debug.Log($"接收到的内容: {content}");
        mCallBack?.Invoke(mStringBuilder.ToString());
    }


    public void DisposeAI()
    {
        if (currentRequest!=null)
        {
            currentRequest.Abort();
            currentRequest = null;
            IsGenerating.Value = false;
            mCallBack = null;
        }
    }

    private Action<string> mStopCallBack;
    public void StopGenerate(Action<string> callBack=null)
    {
        mStopCallBack = callBack;
        DisposeAI();
        mStopCallBack?.Invoke("当前对话已中断");
        mCallBack = null;
    }
}


public class CustomDownloadHandler : DownloadHandlerScript
{
    private System.Action<string> onReceiveData;
    private System.Text.StringBuilder buffer = new System.Text.StringBuilder();

    public CustomDownloadHandler(System.Action<string> callback) : base()
    {
        onReceiveData = callback;
    }

    protected override bool ReceiveData(byte[] data, int dataLength)
    {
        if (data == null || data.Length < 1 || dataLength < 1)
        {
            return false;
        }

        string content = System.Text.Encoding.UTF8.GetString(data, 0, dataLength);
        buffer.Append(content);

        // 处理缓冲区中的完整行
        ProcessBuffer();

        return true;
    }

    private void ProcessBuffer()
    {
        string content = buffer.ToString();
        int lastNewLine = content.LastIndexOf('\n');

        if (lastNewLine >= 0)
        {
            string completeLines = content.Substring(0, lastNewLine + 1);
            string remaining = content.Substring(lastNewLine + 1);

            string[] lines = completeLines.Split('\n');
            foreach (string line in lines)
            {
                if (!string.IsNullOrWhiteSpace(line))
                {
                    onReceiveData?.Invoke(line + "\n");
                }
            }

            buffer.Clear();
            buffer.Append(remaining);
        }
    }

    protected override void CompleteContent()
    {
        // 处理剩余的数据
        if (buffer.Length > 0)
        {
            onReceiveData?.Invoke(buffer.ToString());
        }
        buffer.Clear();
    }
}
csharp 复制代码
public class DeepSeekResponse
{
    //该对话的唯一标识符。
    public string id { get; set; }
    //模型生成的 completion 的选择列表。
    public Choice[] choices { get; set; }
    //创建聊天完成时的 Unix 时间戳(以秒为单位)。
    public int created { get; set; }

    public string @object { get; set; }
    //该对话补全请求的用量信息。
    public Usage usage { get; set; }
}

public class Choice
{
    //该 completion 在模型生成的 completion 的选择列表中的索引。
    public int index { get; set; }
    //public Message message { get; set; }

    public Message delta { get; set; }

    //模型停止生成 token 的原因。
    public string finish_reason { get; set; }
}

public class Message
{
    //该 completion 的内容。
    public string content { get; set; }
    //生成这条消息的角色。
    public string role { get; set; }
    
}

public class Usage
{
    public int prompt_tokens { get; set; }
    public int completion_tokens { get; set; }
    public int total_tokens { get; set; }
}

//返回码
public enum EnumDeepSeekCode
{
    /// <summary>
    /// 格式错误
    /// </summary>
    FormatError = 400,
    /// <summary>
    /// 认证失败
    /// </summary>
    AuthenticationFailed = 401,
    /// <summary>
    /// 余额不足
    /// </summary>
    InsufficientBalance = 402,
    /// <summary>
    /// 参数错误
    /// </summary>
    ParameterError = 422,
    /// <summary>
    /// 请求速率达到上限
    /// </summary>
    MaximumLimit = 429,
    /// <summary>
    /// 服务器故障
    /// </summary>
    ServerFailure = 500,
    /// <summary>
    /// 服务器繁忙
    /// </summary>
    ServerBusy = 503,
}
相关推荐
秦苒&2 小时前
【脉脉】AI 创作者 xAMA 知无不言:在浪潮里,做会发光的造浪者
大数据·c语言·数据库·c++·人工智能·ai·操作系统
chillxiaohan3 小时前
unity 批量修改场景内字体工具
unity
zhojiew3 小时前
在Lambda函数中编写和部署MCP服务器的方法
运维·ai·aws
badfl4 小时前
Clawdbot安装教程:在Mac mini上部署AI Agent并接入Claude Code
人工智能·ai·ai编程
San30.4 小时前
零成本、全隐私:基于 React + Ollama 打造你的专属本地 AI 助手
前端·人工智能·react.js·ai
海绵宝宝de派小星15 小时前
特征工程技巧与最佳实践
ai
CoderJia程序员甲16 小时前
GitHub 热榜项目 - 日榜(2026-01-22)
ai·开源·大模型·github·ai教程
Tom·Ge18 小时前
Claude Code 和 Cursor 有何异同
ai
ellis197020 小时前
Unity中ScriptableObject用法整理
unity