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,
}
相关推荐
带刺的坐椅1 天前
用 10 行 Java8 代码,开发一个自己的 ClaudeCodeCLI?你信吗?
java·ai·llm·agent·solon·mcp·claudecode·skills
程序设计实验室1 天前
Windows + AMD 显卡,终于能用 PyTorch 炼丹了
ai
CoderJia程序员甲1 天前
GitHub 热榜项目 - 日榜(2026-02-05)
ai·开源·大模型·github·ai教程
GJGCY1 天前
2026主流智能体平台技术路线差异,各大平台稳定性与集成能力对比
人工智能·经验分享·ai·智能体
acai_polo1 天前
如何在国内合规、稳定地使用GPT/Claude/Gemini API?中转服务全解析
人工智能·gpt·ai·语言模型·ai作画
阿杰学AI1 天前
AI核心知识86——大语言模型之 Superalignment(简洁且通俗易懂版)
人工智能·深度学习·ai·语言模型·超级对齐·superalignment·#ai安全
阿杰学AI1 天前
AI核心知识85——大语言模型之 RLAIF(简洁且通俗易懂版)
人工智能·深度学习·ai·语言模型·aigc·rlaihf·基于ai反馈的强化学习
m0_603888711 天前
Mitigating Long-Tail Bias via Prompt-Controlled Diffusion Augmentation
ai·prompt·论文速览
Elastic 中国社区官方博客1 天前
跳过 MLOps:通过 Cloud Connect 使用 EIS 为自管理 Elasticsearch 提供托管云推理
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
海绵宝宝de派小星1 天前
经典CNN架构:LeNet、AlexNet、VGG、GoogLeNet、ResNet
人工智能·神经网络·ai·cnn