本次项目实训我主要负责Unity3D开发AI模拟对话的功能与前端开发部分,因为Unity部分是以WebGL形式嵌入前端,因此首先进行这一部分的开发。

我准备接入DeepSeek、ChatGPT和豆包AI,首先获取了需要使用的测试Key,接下来完成模型接入的相关设置。

大模型对话管理脚本

LLM父脚本和DeepSeek调用部分

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using UnityEngine;

public class LLM : MonoBehaviour
{
    /// <summary>
    /// api地址
    /// </summary>
    [SerializeField] protected string url;
    /// <summary>
    /// 提示词,与消息一起发送
    /// </summary>
    [Header("发送的提示词设定")]
    [SerializeField] protected string m_Prompt = string.Empty;
    /// <summary>
    /// 语言
    /// </summary
    [Header("设置回复的语言")]
    [SerializeField] protected string lan = "中文";
    /// <summary>
    /// 上下文保留条数
    /// </summary>
    [Header("上下文保留条数")]
    [SerializeField] protected int m_HistoryKeepCount = 15;
    /// <summary>
    /// 缓存对话
    /// </summary>
    [SerializeField] public List<SendData> m_DataList = new List<SendData>();
    /// <summary>
    /// 计算方法调用的时间
    /// </summary>
    [SerializeField] protected Stopwatch stopwatch = new Stopwatch();
    /// <summary>
    /// 发送消息
    /// </summary>
    public virtual void PostMsg(string _msg, Action<string> _callback)
    {
        //上下文条数设置
        CheckHistory();
        //提示词处理
        string message = "当前为角色的人物设定:" + m_Prompt +
            " 回答的语言:" + lan +
            " 接下来是我的提问:" + _msg;

        //缓存发送的信息列表
        m_DataList.Add(new SendData("user", message));

        StartCoroutine(Request(message, _callback));
    }

    public virtual IEnumerator Request(string _postWord, System.Action<string> _callback)
    {
        yield return new WaitForEndOfFrame();

    }

    /// <summary>
    /// 设置保留的上下文条数,防止太长
    /// </summary>
    public virtual void CheckHistory()
    {
        if (m_DataList.Count > m_HistoryKeepCount)
        {
            m_DataList.RemoveAt(0);
        }
    }

    [Serializable]
    public class SendData
    {
        [SerializeField] public string role;
        [SerializeField] public string content;
        public SendData() { }
        public SendData(string _role, string _content)
        {
            role = _role;
            content = _content;
        }

    }

}

 LLM父类定义了基础的成员变量,任何大模型都共有的一些属性:

  • url:服务器接口地址,虽然在这个脚本里没有实际用到,但通常用于指定LLM API的地址。
  • m_Prompt:预设的提示词(Prompt),在发送消息时附在用户输入前,用来设定AI的行为。
  • lan:回复使用的语言,比如默认是“中文”,可以用来提示LLM用特定语言回答。
  • m_HistoryKeepCount:保留的上下文消息数量,默认是15条。超出后会自动删除最旧的记录,防止上下文过长导致性能问题。
  • m_DataList:缓存对话内容的列表,每次用户提问或系统响应都会保存下来,方便后续管理上下文。
  • stopwatch:计时器(System.Diagnostics.Stopwatch),用来统计接口调用耗时。

同时定义了一些核心方法(Functions):

  • PostMsg(string _msg, Action<string> _callback)

    • 对外接口,发送用户消息
    • 会调用 CheckHistory() 先检查对话记录条数。
    • 然后将提示词、语言、用户输入拼接成最终发送内容。
    • 把这条信息添加到缓存列表 m_DataList。
    • 最后启动协程 Request() 发起请求(实际请求逻辑留空,待子类实现)。
  • IEnumerator Request(string _postWord, System.Action<string> _callback)

    • 虚拟方法(virtual),目前只等待一帧(WaitForEndOfFrame())。
    • 子类去重写这个方法,实现具体的网络请求逻辑(比如用UnityWebRequest去访问API)。
  • CheckHistory():检查 m_DataList 的数量,如果超过设定的 m_HistoryKeepCount,就移除最早的消息,保持上下文不过长。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class ChatDeepSeek : LLM
{

	public ChatDeepSeek()
	{
		url = "https://api.deepseek.com/v1/chat/completions";
	}

	/// <summary>
	/// api key
	/// </summary>
	[SerializeField] private string api_key;
	/// <summary>
	/// AI设定
	/// </summary>
	public string m_SystemSetting = string.Empty;
	/// <summary>
	/// 模型名称
	/// </summary>
	public string m_ModelName = "deepseek-chat";

	private void Start()
	{
		//运行时,添加AI设定
		m_DataList.Add(new SendData("system", m_SystemSetting));
	}

	/// <summary>
	/// 发送消息
	/// </summary>
	/// <returns></returns>
	public override void PostMsg(string _msg, Action<string> _callback)
	{
		base.PostMsg(_msg, _callback);
	}

	/// <summary>
	/// 调用接口
	/// </summary>
	/// <param name="_postWord"></param>
	/// <param name="_callback"></param>
	/// <returns></returns>
	public override IEnumerator Request(string _postWord, System.Action<string> _callback)
	{
		stopwatch.Restart();
		using (UnityWebRequest request = new UnityWebRequest(url, "POST"))
		{
			PostData _postData = new PostData
			{
				model = m_ModelName,
				messages = m_DataList
			};

			string _jsonText = JsonUtility.ToJson(_postData);
			byte[] data = System.Text.Encoding.UTF8.GetBytes(_jsonText);
			request.uploadHandler = (UploadHandler)new UploadHandlerRaw(data);
			request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();

			request.SetRequestHeader("Content-Type", "application/json");
			request.SetRequestHeader("Authorization", string.Format("Bearer {0}", api_key));

			yield return request.SendWebRequest();

			if (request.responseCode == 200)
			{
				string _msgBack = request.downloadHandler.text;
				MessageBack _textback = JsonUtility.FromJson<MessageBack>(_msgBack);
				if (_textback != null && _textback.choices.Count > 0)
				{

					string _backMsg = _textback.choices[0].message.content;
					//添加记录
					m_DataList.Add(new SendData("assistant", _backMsg));
					_callback(_backMsg);
				}
			}
			else
			{
				string _msgBack = request.downloadHandler.text;
				Debug.LogError(_msgBack);
			}

			stopwatch.Stop();
			Debug.Log("DeepSeek耗时:" + stopwatch.Elapsed.TotalSeconds);
		}
	}

	#region 数据包

	[Serializable]
	public class PostData
	{
		public string model;
		public List<SendData> messages;
		public bool stream = false;
	}


	[Serializable]
	public class MessageBack
	{
		public string id;
		public string created;
		public string model;
		public List<MessageBody> choices;
	}
	[Serializable]
	public class MessageBody
	{
		public Message message;
		public string finish_reason;
		public string index;
	}
	[Serializable]
	public class Message
	{
		public string role;
		public string content;
	}

	#endregion


}

 基本变量

  • api_key :你的 DeepSeek API 密钥(用来鉴权)。
  • m_SystemSetting :初始的系统提示词(比如定义AI性格、风格)。
  • m_ModelName :模型名,默认是 "deepseek-chat"。

核心方法:重写Request()协程

Request 方法向 DeepSeek API 发送带有对话历史的POST请求,设置认证信息,接收并解析AI回复,更新本地记录,通过回调返回结果,同时统计请求耗时。

场景交互

模型调用之后需要可视化的提供交互方法,为输入框配置响应时间,以便于将输入的提问打包成数据包提交给目标服务器,提问输入包括文本和语音两种方式,这里之后会调用讯飞的语音转文字接口进行完善。

 /// <summary>
    /// 发送信息
    /// </summary>
    public void SendData()
    {
        if (m_InputWord.text.Equals(""))
            return;

        if (m_CreateVoiceMode)//合成输入为语音
        {
            CallBack(m_InputWord.text);
            m_InputWord.text = "";
            return;
        }


        //添加记录聊天
        m_ChatHistory.Add(m_InputWord.text);
        //提示词
        string _msg = m_InputWord.text;

        //发送数据
        m_ChatSettings.m_ChatModel.PostMsg(_msg, CallBack);

        m_InputWord.text = "";
        m_TextBack.text = "正在思考中...";

        //切换思考动作
        SetAnimator("state", 1);
    }

实现效果 

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐