269 lines
11 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|