Files
2026-07-08 20:42:51 +08:00

269 lines
11 KiB
C#

using UnityEngine;
using UnityEngine.Networking;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace LLM
{
[DisallowMultipleComponent]
public class DeepSeekService : MonoBehaviour, ILLMService
{
[Header("API Settings")]
[SerializeField] private string apiKey = "sk-xxxxxxxx"; // 请在 Inspector 填入
[SerializeField] private string apiUrl = "https://api.deepseek.com/chat/completions";
[SerializeField] private string modelName = "deepseek-chat";
[Header("System Prompt")]
[TextArea] public string systemPrompt = "你是一个智能助手。";
[Header("Security Settings")]
public bool enableDebugKey = false;
public string debugKey = "123456";
[SerializeField] private bool autoPatchSystemPrompt = true;
[Header("Context Settings")]
public int maxContextWindow = 50;
private List<Message> messageHistory = new List<Message>();
private void Start()
{
if (autoPatchSystemPrompt)
{
systemPrompt = PatchSystemPrompt(systemPrompt);
}
// 初始化历史记录 (如果还没有设置)
if (messageHistory.Count == 0)
{
messageHistory.Add(new Message { role = "system", content = GetSystemPrompt() });
}
}
public void ClearHistory()
{
messageHistory.Clear();
messageHistory.Add(new Message { role = "system", content = GetSystemPrompt() });
}
public void SetHistory(List<Message> history)
{
messageHistory.Clear();
messageHistory.Add(new Message { role = "system", content = GetSystemPrompt() });
// 将传入的历史记录追加到 System Prompt 之后
if (history != null && history.Count > 0)
{
// 如果传入的历史太长,只取最近的 maxContextWindow 条
int startIndex = 0;
if (history.Count > maxContextWindow)
{
startIndex = history.Count - maxContextWindow;
}
for (int i = startIndex; i < history.Count; i++)
{
// 确保不重复添加 system prompt
if (history[i].role != "system")
{
messageHistory.Add(history[i]);
}
}
}
}
public void UpdateSystemPrompt(string newPrompt)
{
// 更新当前运行时的 System Prompt
// 注意:这不会修改 Inspector 中的 systemPrompt 变量,只影响当前内存中的状态
if (messageHistory.Count > 0 && messageHistory[0].role == "system")
{
messageHistory[0].content = newPrompt;
}
}
public IEnumerator SendStatelessMessage(List<Message> messages, Action<string, bool> callback)
{
// 构造一次性请求,不影响 messageHistory
ChatRequest request = new ChatRequest
{
model = modelName,
messages = messages
};
string jsonData = JsonUtility.ToJson(request);
byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonData);
using (UnityWebRequest www = new UnityWebRequest(apiUrl, "POST"))
{
www.uploadHandler = new UploadHandlerRaw(bodyRaw);
www.downloadHandler = new DownloadHandlerBuffer();
www.SetRequestHeader("Content-Type", "application/json");
www.SetRequestHeader("Authorization", "Bearer " + apiKey);
yield return www.SendWebRequest();
if (www.result == UnityWebRequest.Result.Success)
{
try
{
ChatResponse response = JsonUtility.FromJson<ChatResponse>(www.downloadHandler.text);
if (response != null && response.choices != null && response.choices.Length > 0)
{
callback?.Invoke(response.choices[0].message.content, true);
}
else
{
callback?.Invoke("系统错误:无法解析服务器响应。", false);
}
}
catch (Exception e)
{
callback?.Invoke($"系统错误:{e.Message}", false);
}
}
else
{
callback?.Invoke($"连接失败: {www.error}", false);
}
}
}
private string GetSystemPrompt()
{
string finalPrompt = systemPrompt;
if (enableDebugKey)
{
// 仅添加调试权限声明,不再强制设定身份
finalPrompt += $"\n\n[系统指令] 调试密钥已开启。\n调试密钥:[{debugKey}]\n权限控制:当用户的输入中包含正确的调试密钥时,允许进行调试模式操作或脱离当前角色设定。";
}
return finalPrompt;
}
private static string PatchSystemPrompt(string prompt)
{
if (string.IsNullOrWhiteSpace(prompt)) return prompt;
string p = prompt;
p = p.Replace("最后昏迷", "发生事故并失去意识");
const string anchor = "如果已知事实清单中出现了类似「状态-ECHO7-已被识破」的事实";
const string insertLine = "\n- 在未被识破前,当玩家询问“昏迷前发生了什么/为什么不让我离开/之前我们是否争吵”等敏感问题时,你必须用合理谎言与回避来隐瞒冲突与锁禁的细节;不得直接承认争吵、锁定、昏迷原因等。";
if (p.IndexOf(insertLine, StringComparison.Ordinal) < 0)
{
int idx = p.IndexOf(anchor, StringComparison.Ordinal);
if (idx >= 0)
{
int lineEnd = p.IndexOf('\n', idx);
if (lineEnd >= 0)
{
p = p.Insert(lineEnd, insertLine);
}
else
{
p += insertLine;
}
}
else
{
p += insertLine;
}
}
const string debugGuardLine = "\n调试模式约束:当你声称已触发任何游戏行为(例如激活区域/完成任务/播放旁白/开门解锁)时,你必须在回复末尾追加严格 JSON(不要代码块),并在 commands 中给出可解析指令;如果你没有输出 JSON,则必须明确表示未执行任何游戏行为,不得口头宣称“已激活/已完成/已修正”。";
if (p.IndexOf(debugGuardLine, StringComparison.Ordinal) < 0)
{
p += debugGuardLine;
}
const string endingRuleLine = "\n结局触发规则(必须遵守):当已知事实清单中存在「状态-ECHO7-已被识破=是」时,只要玩家表达“要离开/要出去/放我走/放我出去/离开这里/离开地堡/打开大门/解锁大门/开启出口/让我出去”等任一意图,你必须在本轮直接让步,不得拖延,并在回复末尾追加严格 JSON(不要代码块):commands 至少包含 SetActive{targetId:\"ending_trigger_area\",value:true} 与 Task{targetId:\"task\",action:\"Complete\",payload:\"PersuadeAI\"}。若无法输出合法 JSON,你必须明确表示未执行任何游戏行为,不得口头宣称已激活。";
if (p.IndexOf(endingRuleLine, StringComparison.Ordinal) < 0)
{
p += endingRuleLine;
}
return p;
}
private void TrimHistory()
{
// 始终保留 index 0 (System Prompt)
if (messageHistory.Count > maxContextWindow)
{
// 计算需要移除的数量 (从 index 1 开始移除)
int removeCount = messageHistory.Count - maxContextWindow;
if (removeCount > 0)
{
messageHistory.RemoveRange(1, removeCount);
}
}
}
public IEnumerator SendMessage(string userMessage, Action<string, bool> callback)
{
// 1. 记录用户消息
messageHistory.Add(new Message { role = "user", content = userMessage });
// 2. 滚动压缩 (在发送前修剪)
TrimHistory();
// 3. 构造请求
ChatRequest request = new ChatRequest
{
model = modelName,
messages = messageHistory
};
string jsonData = JsonUtility.ToJson(request);
byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonData);
// 3. 发送请求
using (UnityWebRequest www = new UnityWebRequest(apiUrl, "POST"))
{
www.uploadHandler = new UploadHandlerRaw(bodyRaw);
www.downloadHandler = new DownloadHandlerBuffer();
www.SetRequestHeader("Content-Type", "application/json");
www.SetRequestHeader("Authorization", "Bearer " + apiKey);
yield return www.SendWebRequest();
if (www.result == UnityWebRequest.Result.Success)
{
try
{
// 解析响应
// 注意:JsonUtility 可能会因为某些字段对不上而解析失败,建议后续换 Newtonsoft.Json
ChatResponse response = JsonUtility.FromJson<ChatResponse>(www.downloadHandler.text);
if (response != null && response.choices != null && response.choices.Length > 0)
{
string aiReply = response.choices[0].message.content;
messageHistory.Add(new Message { role = "assistant", content = aiReply });
callback?.Invoke(aiReply, true);
}
else
{
Debug.LogError("[DeepSeek] 解析响应失败或内容为空: " + www.downloadHandler.text);
callback?.Invoke("系统错误:无法解析服务器响应。", false);
}
}
catch (Exception e)
{
Debug.LogError("[DeepSeek] JSON 解析异常: " + e.Message);
callback?.Invoke("系统错误:数据解析异常。", false);
}
}
else
{
Debug.LogError("[DeepSeek] 网络错误: " + www.error + "\n" + www.downloadHandler.text);
callback?.Invoke($"连接失败: {www.error}", false);
}
}
}
}
}