NewMyBook/Assets/script/DeepSeekReasonerStreamManager.cs
2025-03-28 16:07:42 +08:00

294 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using TMPro;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
public class DeepSeekReasonerStreamManager : MonoBehaviour
{
// API 配置
[Header("API Settings")]
[SerializeField] private string apiKey = "sk-c89fd3342dda4a8eb53cb533607e2e89"; // 替换成你的 API Key
[SerializeField] private string modelName = "deepseek-reasoner";
[SerializeField] private string apiUrl = "https://api.deepseek.com/v1/chat/completions";
// 推理参数
[Header("Inference Settings")]
[Range(0, 2)] public float temperature = 0.7f;
[Range(1, 1000)] public int maxTokens = 150;
// UI 控件(请在 Inspector 中指定,只需一个 Text 节点)
[Header("UI Elements")]
public TextMeshProUGUI dialogueText; // 显示推理过程及最终回复
public InfoShow infoShow;
// 内部缓冲区
private StringBuilder reasoningBuffer = new StringBuilder();
private StringBuilder contentBuffer = new StringBuilder();
private bool streamingCompleted = false;
/// <summary>
/// 开始流式推理请求,传入对话消息列表(例如第一轮对话)
/// </summary>
/// <param name="messages">对话消息列表,格式参见 DeepSeekMessage</param>
public void SendStreamReasonerRequest(List<DeepSeekMessage> messages)
{
// 清空之前的缓冲区及 UI 显示
reasoningBuffer.Clear();
contentBuffer.Clear();
dialogueText.text = "";
streamingCompleted = false;
StartCoroutine(ProcessStreamReasonerRequest(messages));
// 开启打字机效果协程,先显示推理过程
StartCoroutine(AnimateReasoningText());
}
/// <summary>
/// 流式请求的协程:发送请求并通过自定义下载处理器实时处理返回块
/// </summary>
private IEnumerator ProcessStreamReasonerRequest(List<DeepSeekMessage> messages)
{
ChatStreamRequest requestBody = new ChatStreamRequest
{
model = modelName,
messages = messages,
stream = true
};
GameManager.Instance.infoTGo.text = "小知正在努力思考,请稍等哦...";
string jsonBody = JsonUtility.ToJson(requestBody);
Debug.Log("Sending streaming JSON: " + jsonBody);
// 使用自定义 StreamingDownloadHandler 逐块处理响应
StreamingDownloadHandler downloadHandler = new StreamingDownloadHandler();
downloadHandler.onChunkReceived = ProcessChunk;
using (UnityWebRequest request = new UnityWebRequest(apiUrl, "POST"))
{
byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonBody);
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
request.downloadHandler = downloadHandler;
request.SetRequestHeader("Content-Type", "application/json");
request.SetRequestHeader("Authorization", $"Bearer {apiKey}");
request.SetRequestHeader("Accept", "application/json");
yield return request.SendWebRequest();
if (IsRequestError(request))
{
Debug.LogError($"API Error: {request.responseCode}\n{request.downloadHandler.text}");
yield break;
}
}
streamingCompleted = true;
}
/// <summary>
/// 对每个返回块进行解析,如果存在推理内容则追加到 reasoningBuffer
/// 否则将最终结果追加到 contentBuffer不直接更新 UI
/// </summary>
private void ProcessChunk(string chunk)
{
chunk = chunk.Trim();
if (string.IsNullOrEmpty(chunk))
return;
if (chunk.Contains("keep-alive"))
return;
if (chunk.StartsWith("data:"))
{
chunk = chunk.Substring("data:".Length).Trim();
if (chunk == "[DONE]")
return;
}
try
{
ChatStreamResponseChunk chunkData = JsonUtility.FromJson<ChatStreamResponseChunk>(chunk);
if (chunkData != null && chunkData.choices != null && chunkData.choices.Length > 0)
{
StreamingDelta delta = chunkData.choices[0].delta;
if (!string.IsNullOrEmpty(delta.reasoning_content))
{
reasoningBuffer.Append(delta.reasoning_content);
}
else if (!string.IsNullOrEmpty(delta.content))
{
contentBuffer.Append(delta.content);
// 此处不直接更新 dialogueText待流式完成后统一显示最终结果
}
}
}
catch (Exception e)
{
Debug.LogError("解析返回块失败: " + e.Message + "\nChunk content: " + chunk);
}
}
/// <summary>
/// 实现打字机效果:不断检查 reasoningBuffer 中的新字符,并逐个显示到 dialogueText
/// 流式加载结束后,替换显示最终回复内容。
/// </summary>
private IEnumerator AnimateReasoningText()
{
string displayed = "";
GameManager.Instance.stopAnimation = false; // 允许执行
while (!streamingCompleted || displayed.Length < reasoningBuffer.Length)
{
if (GameManager.Instance.stopAnimation) break; // 立即停止
if (displayed.Length < reasoningBuffer.Length)
{
displayed = reasoningBuffer.ToString().Substring(0, displayed.Length + 1);
dialogueText.text = displayed;
}
yield return new WaitForSeconds(0.05f);
}
// 推理过程已显示完毕,等待片刻后替换为最终结果
yield return new WaitForSeconds(0.5f);
if (!GameManager.Instance.stopAnimation)
{
yield return new WaitForSeconds(0.5f);
linkidResponse(contentBuffer.ToString());
}
//dialogueText.text = contentBuffer.ToString();
// linkidResponse(contentBuffer.ToString());
}
private void linkidResponse(string response)
{
try
{
Dictionary<string, string> dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
infoShow.ShowInfo(dictionary);
}
catch (System.Exception)
{
infoShow.ShowInfo(
new Dictionary<string, string>
{
{"什么是潜艇?","潜艇是一种能够在水下航行的特种舰艇主要用于军事侦察、战斗巡逻、秘密运输等任务。潜艇利用内部的压载舱调整自身浮力实现浮出水面或下潜水中的能力。现代潜艇通常配备柴油发动机或核动力系统能够长时间潜伏在水下执行任务。军事潜艇通常携带鱼雷、导弹或水雷等武器能够对敌方舰船实施打击。此外潜艇还可用于海洋科学研究、深海探测和搜救任务。在历史上潜艇在第一次和第二次世界大战中都发挥了重要作用尤其是德国的“U型潜艇”对海战产生了深远影响。如今各国海军普遍装备不同类型的潜艇如战略核潜艇、攻击型潜艇和常规动力潜艇以执行各种军事和非军事任务。"},
{"什么是航母?","航空母舰,简称航母,是一种专门用于搭载和起降战斗机的大型水面舰艇。航母通常配备巨大的甲板作为飞机的跑道,并设有升降机、机库等辅助设施,使舰载机能够高效起降和维护。现代航母一般由核动力或常规动力提供动力,拥有强大的续航能力。航母不仅是一种海上军事基地,还是国家海军力量的象征,能够在远离本土的海域实施空中作战任务。其舰载机编队能够执行制空、对海打击、反潜作战、电子战等多种任务。航母战斗群通常由驱逐舰、护卫舰、补给舰和潜艇等护航舰艇组成,形成强大的作战体系。世界主要海军强国,如美国、中国、英国、法国等,都装备有不同级别的航母,并持续发展新型舰载机和航母战斗体系。"}
}
);
}
}
// 解析 [] 内的内容,转换成 <link> 形式
public string ParseClickableText(string rawText)
{
return Regex.Replace(rawText, @"\[(.*?)\]", "<link=$1><color=yellow><u>$1</u></color></link>");
}
/// <summary>
/// 检查请求是否出现错误
/// </summary>
private bool IsRequestError(UnityWebRequest request)
{
return request.result == UnityWebRequest.Result.ConnectionError ||
request.result == UnityWebRequest.Result.ProtocolError ||
request.result == UnityWebRequest.Result.DataProcessingError;
}
// 可序列化的请求和响应数据结构
[Serializable]
public class DeepSeekMessage
{
public string role; // "user", "assistant", "system" 等
public string content; // 消息内容
}
[Serializable]
private class ChatStreamRequest
{
public string model;
public List<DeepSeekMessage> messages;
public bool stream;
}
[Serializable]
private class StreamingDelta
{
public string reasoning_content;
public string content;
}
[Serializable]
private class StreamingChoice
{
public StreamingDelta delta;
}
[Serializable]
private class ChatStreamResponseChunk
{
public StreamingChoice[] choices;
}
}
/// <summary>
/// 自定义流式下载处理器:将接收到的数据按行分割,并调用回调处理每一行 JSON 数据
/// </summary>
public class StreamingDownloadHandler : DownloadHandlerScript
{
public Action<string> onChunkReceived;
private StringBuilder buffer = new StringBuilder();
public StreamingDownloadHandler() : base(new byte[1024])
{
}
protected override bool ReceiveData(byte[] data, int dataLength)
{
if (data == null || dataLength <= 0)
return false;
string chunk = Encoding.UTF8.GetString(data, 0, dataLength);
buffer.Append(chunk);
// 假设每个 JSON 块以换行符结尾
string fullText = buffer.ToString();
string[] lines = fullText.Split(new[] { "\n" }, StringSplitOptions.None);
// 处理除最后一行外的所有完整行
for (int i = 0; i < lines.Length - 1; i++)
{
string line = lines[i].Trim();
if (!string.IsNullOrEmpty(line))
{
onChunkReceived?.Invoke(line);
}
}
// 保留最后可能不完整的那一行
buffer.Clear();
buffer.Append(lines[lines.Length - 1]);
return true;
}
protected override void CompleteContent()
{
string remaining = buffer.ToString().Trim();
if (!string.IsNullOrEmpty(remaining))
{
onChunkReceived?.Invoke(remaining);
}
base.CompleteContent();
}
}