[SKKU DT] 65일차 -유니티에서 ChatGPT Api 가져오기

2024. 2. 1. 18:15SKKU DT

728x90
반응형

유니티에서 ChatGPT 가져오기

API key를 받아오기 전에 유니티 씬으로 UI를 만든다.

 

 

MyGPT 스크립트 생성

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

public class MyGPT : MonoBehaviour
{
    public InputField userInputField;
    public Text responseText;

    private readonly string openAIApiURL = "https://api.openai.com/v1/chat/completions";
    private readonly string apiKey = "Your apiKey";

    public void OnSubmit()
    {
        string userInput = userInputField.text;
        if (!string.IsNullOrWhiteSpace(userInput))
        {
            StartCoroutine(SendRequestToChatGPT(userInput));
        }
    }

    private IEnumerator SendRequestToChatGPT(string userInput)
    {
        var request = new UnityWebRequest(openAIApiURL, "POST");
        string requestData = "{\"model\":\"gpt-3.5-turbo\",\"messages\":[{\"role\":\"system\",\"content\":\"You are a helpful assistant.\"},{\"role\":\"user\",\"content\":\"" + userInput + "\"}]}";
        byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(requestData);
        request.uploadHandler = (UploadHandler)new UploadHandlerRaw(bodyRaw);
        request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
        request.SetRequestHeader("Content-Type", "application/json");
        request.SetRequestHeader("Authorization", "Bearer " + apiKey);

        yield return request.SendWebRequest();

        if(request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.ProtocolError)
        {
            Debug.LogError(request.error);
            responseText.text = "Error: " + request.error;
        }
        else
        {
            string response = request.downloadHandler.text;
            Debug.Log("Response: " + response);
            ChatGPTResponse chatGPTResponse = JsonUtility.FromJson<ChatGPTResponse>(response);
            string gptResponse = chatGPTResponse.choices[0].message.content;
            responseText.text = gptResponse; //ChatGPT 응답을 UI에 표시
        }
    }
}

[System.Serializable]
public class ChatGPTResponse
{
    public Choice[] choices;
}

[System.Serializable]
public class Choice
{
    public Message message;
}
[System.Serializable]
public class Message
{
    public string content;
}

 

 

 

컴포넌트 연결

 

버튼에 함수 매핑

 

 

참고로, ChatGPT의 api 응답 형식은 아래와 같다.

 

 


 

 

https://platform.openai.com/docs/overview

위 링크에 접속한다.

 

왼쪽 메뉴 중 [API keys]를 눌러 API key를 받아온다.

 

 


 

 

본인의 apiKey를 스크립트에 잘 입력하고 실행하면 GPT의 말을 볼 수 있다.

*openAI의 유료 결제가 필요하다.

 

 


 

 

SDK 다운로드

PackageManager에서 git URL로 패키지를 다운로드 받는다.

 

https://github.com/srcnalt/OpenAI-Unity.git  을 입력한다.

 

Samples도 다운로드 받는다.

 

 

Open API에 요청하려면 API 키와 조직 이름을 사용해야한다.

조직 이름은 아래의 페이지에서 볼 수 있다.

 

 

"C:\Users\User" 경로에 ".openai"라는 폴더를 하나 만든다.

거기에 auth.json 파일을 하나 만들고 아래와 같이 키를 넣은 후 저장한다.

 

 

유니티에서, 샘플을 받았다면 Project 창에 아래와 같은 경로를 찾을 수 있다. 여기서 ChatGPT Sample 씬을 열면 ChatGPT와 대화를 나눌 수 있다.

 

실제로 이 샘플 씬 안에 들어있는 스크립트를 보면 이미 아래와 같은 프롬프트가 있어서 성격이 설정되어있다. 해당 프롬프트를 바꾸면 내가 원하는 방식으로 대답을 이끌어낼 수 있다.

private string prompt = "Act as a random stranger in a chat room and reply to the questions. Don't break character. Don't ever mention that you are an AI model.";

 

 

친절한 어시스턴트 역할을 부여하면 같은 질문을 해도 말투와 정보 제공 방식이 달라진다. (어쩐지 갑자기 GPT치고는 처음보는 말투를 해서 조금 놀랐다...)

private string prompt = "You are a helpful assistant.";

 

 


 

 

Dalle 샘플 씬

Dalle도 마찬가지로 샘플 씬을 사용할 수 있다.

 

 


 

 

StreamResponse 샘플 씬

스토리를 작성해준다.

 

 


 

 

 Whisper 샘플 씬

마이크로 입력된 소리를 텍스트로 보여준다. 신기한건 한국말로 말해도 영어로 번역돼서 나온다.

 

 


 

 

샘플 씬처럼 UI 구성하기

이 글의 맨 처음 만들었던 UI를 수정하여 완성도있는 UI를 만들어본다.

MyGPT 스크립트를 수정한다.

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

public class MyGPT : MonoBehaviour
{
    public InputField userInputField;
    public Text responseText;
    public ScrollRect scroll;

    public RectTransform messageContainer;

    public GameObject sendMessagePrefab;
    public GameObject receiveMessagePrefab;

    private float height;
    private OpenAIApi openai = new OpenAIApi();

    private bool isSendingMessage = false;

    private List<ChatMessage> message = new List<ChatMessage>();
    private string prompt = "You are a helpful assistant";

    private readonly string openAIApiURL = "https://api.openai.com/v1/chat/completions";
    private readonly string apiKey = "Your apiKey";

    private void Update()
    {
        Canvas.ForceUpdateCanvases();
        scroll.verticalNormalizedPosition = 0;
    }

    public void OnSubmit()
    {
        string userInput = userInputField.text;
        if (!string.IsNullOrWhiteSpace(userInput))
        {
            StartCoroutine(SendRequestToChatGPT(userInput));
        }
    }

    private IEnumerator SendRequestToChatGPT(string userInput)
    {
        isSendingMessage = true;

        var sentMessage = CreateMessage(sendMessagePrefab, userInput);
        userInputField.text = "";
        yield return null;

        Canvas.ForceUpdateCanvases();
        scroll.verticalNormalizedPosition = 0;

        var request = new UnityWebRequest(openAIApiURL, "POST");
        string requestData = "{\"model\":\"gpt-3.5-turbo\",\"messages\":[{\"role\":\"system\",\"content\":\"You are a helpful assistant.\"},{\"role\":\"user\",\"content\":\"" + userInput + "\"}]}";
        byte[] bodyRaw = System.Text.Encoding.UTF8.GetBytes(requestData);
        request.uploadHandler = (UploadHandler)new UploadHandlerRaw(bodyRaw);
        request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();
        request.SetRequestHeader("Content-Type", "application/json");
        request.SetRequestHeader("Authorization", "Bearer " + apiKey);

        yield return request.SendWebRequest();

        if(request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.ProtocolError)
        {
            Debug.LogError(request.error);
            responseText.text = "Error: " + request.error;
        }
        else
        {
            string response = request.downloadHandler.text;
            Debug.Log("Response: " + response);
            ChatGPTResponse chatGPTResponse = JsonUtility.FromJson<ChatGPTResponse>(response);
            string gptResponse = chatGPTResponse.choices[0].message.content;
            //responseText.text = gptResponse; //ChatGPT 응답을 UI에 표시
            CreateMessage(receiveMessagePrefab, gptResponse);
            Canvas.ForceUpdateCanvases();
            scroll.verticalNormalizedPosition = 0;
        }
    }
    private GameObject CreateMessage(GameObject prefab, string content)
    {
        GameObject messageObject = Instantiate(prefab, messageContainer);
        Text messageText = messageObject.GetComponent<Text>();
        messageText.text = content;
        return messageObject;
    }
}

[System.Serializable]
public class ChatGPTResponse
{
    public Choice[] choices;
}

[System.Serializable]
public class Choice
{
    public Message message;
}
[System.Serializable]
public class Message
{
    public string content;
}

 

UI 구성 아래 사진 참고

 

SendChat 복사해서 ReceiveChat 만들기

 

객체 끌어다 채우기

 

프리팹 수치 맞춰준다. Pivot 값에 주의. Children으로 들어가있는 Image와 Text의 Pivot 값이 (X : 0, Y : 1)이다.

 

잘 설정 되었다면, 대화 형식으로 UI 결과가 잘 나온다.

 

 

참고로, 샘플 스크립트가 작동이 잘 된다.

using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;

namespace OpenAI
{
    public class ChatGPT : MonoBehaviour
    {
        [SerializeField] private InputField inputField;
        [SerializeField] private Button button;
        [SerializeField] private ScrollRect scroll;
        
        [SerializeField] private RectTransform sent;
        [SerializeField] private RectTransform received;

        private float height;
        private OpenAIApi openai = new OpenAIApi();

        private List<ChatMessage> messages = new List<ChatMessage>();
        private string prompt = "You are a helpful assistant.";

        private void Start()
        {
            button.onClick.AddListener(SendReply);
        }

        private void AppendMessage(ChatMessage message)
        {
            scroll.content.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 0); //스크롤의 높이를 0으로 설정

            var item = Instantiate(message.Role == "user" ? sent : received, scroll.content); //message의 Role이 "user"인 경우 sent 프리팹을, 그렇지 않은 경우 received 프리팹을 인스턴스화하여 item에 할당
            item.GetChild(0).GetChild(0).GetComponent<Text>().text = message.Content; //item의 자식 중 첫 번째 자식의 첫 번째 자식의 Text 컴포넌트의 텍스트를 message의 내용으로 설정
            item.anchoredPosition = new Vector2(0, -height); //item의 위치를 (0, -height)로 설정하여 스크롤 아래에 배치
            LayoutRebuilder.ForceRebuildLayoutImmediate(item); //item의 레이아웃을 강제로 다시 빌드하여 UI를 업데이트
            height += item.sizeDelta.y; //height에 item의 높이를 더하여 현재까지의 높이를 업데이트
            scroll.content.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, height); //스크롤의 높이를 현재까지의 높이로 설정
            scroll.verticalNormalizedPosition = 0; //스크롤의 수직 위치를 맨 아래로 설정하여 최신 메시지가 보이도록 함
        }

        private async void SendReply()
        {
            var newMessage = new ChatMessage()
            {
                Role = "user",
                Content = inputField.text
            };
            
            AppendMessage(newMessage);

            if (messages.Count == 0) newMessage.Content = prompt + "\n" + inputField.text; 
            
            messages.Add(newMessage);
            
            button.enabled = false;
            inputField.text = "";
            inputField.enabled = false;
            
            // Complete the instruction
            var completionResponse = await openai.CreateChatCompletion(new CreateChatCompletionRequest()
            {
                Model = "gpt-3.5-turbo-0613",
                Messages = messages
            });

            if (completionResponse.Choices != null && completionResponse.Choices.Count > 0)
            {
                var message = completionResponse.Choices[0].Message;
                message.Content = message.Content.Trim();
                
                messages.Add(message);
                AppendMessage(message);
            }
            else
            {
                Debug.LogWarning("No text was generated from this prompt.");
            }

            button.enabled = true;
            inputField.enabled = true;
        }
    }
}

728x90
반응형