254 lines
10 KiB
C#
254 lines
10 KiB
C#
using UnityEngine;
|
|
using UnityEngine.Networking;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
|
|
namespace LLM
|
|
{
|
|
[DisallowMultipleComponent]
|
|
public class DoubaoService : MonoBehaviour, ILLMService
|
|
{
|
|
[Header("API Settings")]
|
|
[SerializeField] private string apiKey = "your_api_key_here";
|
|
[SerializeField] private string endpointId = "your_endpoint_id_here"; // Volcengine uses Endpoint ID as model name
|
|
[SerializeField] private string apiUrl = "https://ark.cn-beijing.volces.com/api/v3/chat/completions";
|
|
|
|
[Header("System Prompt")]
|
|
[TextArea] public string systemPrompt = "你是一个智能助手。";
|
|
[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() });
|
|
|
|
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++)
|
|
{
|
|
if (history[i].role != "system")
|
|
{
|
|
messageHistory.Add(history[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void UpdateSystemPrompt(string newPrompt)
|
|
{
|
|
if (messageHistory.Count > 0 && messageHistory[0].role == "system")
|
|
{
|
|
messageHistory[0].content = newPrompt;
|
|
}
|
|
}
|
|
|
|
private string GetSystemPrompt()
|
|
{
|
|
return systemPrompt;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
public IEnumerator SendStatelessMessage(List<Message> messages, Action<string, bool> callback)
|
|
{
|
|
ChatRequest request = new ChatRequest
|
|
{
|
|
model = endpointId,
|
|
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 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. 构造请求
|
|
// Volcengine uses the Endpoint ID as the "model" parameter
|
|
ChatRequest request = new ChatRequest
|
|
{
|
|
model = endpointId,
|
|
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
|
|
{
|
|
// 解析响应
|
|
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("[Doubao] 解析响应失败或内容为空: " + www.downloadHandler.text);
|
|
callback?.Invoke("系统错误:无法解析服务器响应。", false);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError("[Doubao] JSON 解析异常: " + e.Message);
|
|
callback?.Invoke("系统错误:数据解析异常。", false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.LogError("[Doubao] 网络错误: " + www.error + "\n" + www.downloadHandler.text);
|
|
callback?.Invoke($"连接失败: {www.error}", false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|