반응형

추가된 기능이나 api는 이후 추가 예정입니다. 해당방식은 api를 사용하는 방식을 보여준것입니다.

해당 방식을 사용해 그대로 key를 하드코딩한다면 어플리케이션을 제작할 경우 api key의 보안에 문제가 생길수 있습니다.

더보기

최근 OpenAI 에서 ChatGPT를 드디어 테스트를 끝내고 상용화 하였다고 하여서
Unity로 사용할수 있게 하였다. 


 
제작버전 2023.3.11f1
 
 
 

https://platform.openai.com/docs/api-reference/chat/create

 

OpenAI API

An API for accessing new AI models developed by OpenAI

platform.openai.com

ChatGPT API 는 기본 ChatGPT 웹 에서 사용하는것과는 달리
API 사용 요금이 존재한다 한달에  무료 18$의 테스트 할수있는 요금을 받는다.
 
사용요금은 토큰으로 계산하는데

0.002 / 1천 토큰이다.
1byte당 1천 토큰이니 영어로 천단어당 0.002달러
한글로는 한글자당 3바이트 가량 사용하니 3자당 0.002달러씩 사용한다.
한달에 약 3,000,000글자 를 한국어로 사용할수 있다.
 

 
 
2023.12월 기준 모델당 가격

더 자세한 모델마다의 가격은 gpt 홈페이지의 settings-Limits에서 확인가능하다
 
 
Rest API를 사용하기때문에 사용법만 알아두고 사용하면 어렵지않다.
 
 
OpenAPI의 API reference를 참고하여 설정한다.
https://platform.openai.com/docs/api-reference/chat
 
먼저 OpenAPI  웹 페이지에서 키를 받아준다.
 
 

 
 

이창은 한번닫으면 다시 볼수없고 다시 생성해야 하니 잘 복사해둔다.
키값을 찾을수 없으면 옆의 휴지통 버튼을 눌러 삭제후 다시 생성하여 사용할수있다
 
 
 
먼저 포스트맨 이라는 프로그램으로 우선 RestAPI를 테스트 해볼것이다.

 
설정은 post 주소는 https://api.openai.com/v1/chat/completions 

 
 
 
그리고 헤더세팅

Content-type 의 값을 application/json 
Authorization을 bearer (openai키값) 
을 넣어준다.
 
모델은 gpt-3.5-turbo 버전으로 세팅한뒤
보낼 json body를 작성한다.

 
 
 

 
 
 
 
 
 
 
 
 
post 이후 받은데이터

 
 
 
 
 
 
 
 
 
 
 
api가 어떤식으로 보내지고 받아지는지 확인했다.
이제 Unity code를 짜보자
 
 
 
 
우선 class를 json형식에 맞게 형식을 잡아준다.

받은 json형식의 string을 알맞게 디시리얼라이징을 해줘야 하기때문에  Response class 역시 만들어준다 


 

Http 요청방법은 마이크로 소프트의 HttpCilent 를 사용하였다.
 
CreateHttpClient 함수에서 client를 생성해주며 헤더설정과 주소를 세팅 
body를 이전에 만들었던 class를 json 형식으로 serializeing 해준뒤 Client.postAsync로 전송
순서
1. cilent 생성및 해더 세팅
2.일전에 설정했던 데이터class 를 json형식으로 변경
3.Post로 전송후 대기
4.받아온 데이터를 다시 만들어두었던 형식으로 디시리얼라이징

  
    private string API_Url = "";

    private const string AuthorizationHeader = "Bearer";
    private const string UserAgentHeader = "User-Agent";

//httpclient 세팅
  private void CreateHttpClient()
    {
        client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(AuthorizationHeader, OpenAI_Key);
        client.DefaultRequestHeaders.Add(UserAgentHeader, "okgodoit/dotnet_openai_api");
    }

//받은 class를 post 하는 함수
       private async Task<string> ClieantResponse<SendData>(SendData request)
    {
        if (client == null)
        {
            CreateHttpClient();
        }

		//class마다 URL 이라는 인터페이스로 공통 url불러올수있는 함수사용
        API_Url = ((URL)request).Get_API_Url();
		
        //json 시리얼라이징
        string jsonContent = JsonConvert.SerializeObject(request, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
        var stringContent = new StringContent(jsonContent, UnicodeEncoding.UTF8, "application/json");
		
        Debug.Log(API_Url);
        Debug.Log(stringContent);
        //데이터 전송후 대기
        using (var response = await client.PostAsync(API_Url, stringContent))
        {

            if (response.IsSuccessStatusCode)
            {
            //데이터 정상적으로 받은경우 json string 반환
                return await response.Content.ReadAsStringAsync();
            }
            else
            {
                throw new HttpRequestException("Error calling OpenAi API to get completion.  HTTP status code: " + response.StatusCode.ToString() + ". Request body: " + jsonContent);
            }
        }
    }

 
 
 
만든 CilentResponse 를 좀더 편하게 사용하기위한 함수 제작 및 캡슐화


 
테스트용 코드
 
만들었던 Request class 생성후 세팅
보내는 함수 실행후 로그확인 

chat의 경우

api는 Role이 3가지로 나뉜다
우선 system Gpt의 기본 설정을 해주는 message이다
User는 사람 assistant는 대답이다.



 
 
 
 
 


대화한 내용을 user와 assistant로 계속 추가해서 보내면 대화내용을 전부 확인하고 대답을 출력하기에
대답을 기억하게 하고싶은경우 chatRequest class에 ChatMessage를 계속 추가해주면서 보내면 된다.
 
미리 대화를 어느정도 입력하는것으로 학습시킬수있다. 위의 예시처럼 대화를 미리 넣어두는경우
다음대화를 어느정도 추출이가능하다.


 
 
 새로운 기능들이 추가되어 코드를 수정하였다..

일전의 베이스 대화세팅, 대화 데이터 유지 기능은 다른기능을 포함하며 삭제하였다.
하나의 기능으로 묶어버릴시 너무 난잡해지기 때문이다.
 
 
 
위의 코드로 테스트한 음성생성과 이미지 생성이다.
 
이들도 크게 다르지않게 보내는 class 받는 class를 만든뒤
그에맞게 설정하고 json으로 파싱하여 사용하는 방식이다.
 
음성생성

 
이미지 생성

 
unity오브젝트에 테스트 코드 추가

 
 
 
 
추출된 이미지

 

 

 

 

또한 비슷한 테스트를 필요로 하는경우

 

https://programing-note.tistory.com/entry/%EA%B5%AC%EA%B8%80-gemini-api-%EC%9C%A0%EB%8B%88%ED%8B%B0%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

 

구글 gemini api 유니티에서 사용하기

일전에 유니티로 짠 코드를 재사용해 gemini를 사용해보고 Chat GPT와 비교 해 볼것이다. https://programing-note.tistory.com/entry/ChatGPT-Unity%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0 ChatGPT Unity에서 사용하기

programing-note.tistory.com

 gemini 도 가능하다 

테스트는 gemini가 더욱 추천한다


전체코드

더보기

OpenAI_Rest.cs

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using OpenAiFormet;
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using UnityEditor.PackageManager.Requests;
using UnityEngine;


public class OpenAI_Rest
{
    public string OpenAI_Key = "sk-key...";

    private static HttpClient client;
    private static string JsonFilelocation = Application.streamingAssetsPath + "/Json/JsonData.json";

    public delegate void StringEvent(string _string);
    public StringEvent CompletedRepostEvent;

    private string API_Url = "";

    private const string AuthorizationHeader = "Bearer";
    private const string UserAgentHeader = "User-Agent";

    public string speechFile = Application.streamingAssetsPath + "/TestSound.mp3";
    public void Init()
    {
        CreateHttpClient();
    }
    private void CreateHttpClient()
    {
        client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(AuthorizationHeader, OpenAI_Key);
        client.DefaultRequestHeaders.Add(UserAgentHeader, "okgodoit/dotnet_openai_api");
    }

    private async Task<string> ClieantResponse<SendData>(SendData request)
    {
        if (client == null)
        {
            CreateHttpClient();
        }

        API_Url = ((URL)request).Get_API_Url();

        string jsonContent = JsonConvert.SerializeObject(request, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
        var stringContent = new StringContent(jsonContent, UnicodeEncoding.UTF8, "application/json");

        Debug.Log(API_Url);
        Debug.Log(stringContent);
        using (var response = await client.PostAsync(API_Url, stringContent))
        {

            if (response.IsSuccessStatusCode)
            {
                return await response.Content.ReadAsStringAsync();
            }
            else
            {
                throw new HttpRequestException("Error calling OpenAi API to get completion.  HTTP status code: " + response.StatusCode.ToString() + ". Request body: " + jsonContent);
            }
        }
    }
    public async Task<bool> ClieantResponseTTS(TextToSpeechRequest textToSpeechRequest)
    {

        bool result = false;
        try
        {
            API_Url = ((URL)textToSpeechRequest).Get_API_Url();

            string jsonContent = JsonConvert.SerializeObject(textToSpeechRequest, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
            var stringContent = new StringContent(jsonContent, UnicodeEncoding.UTF8, "application/json");


            using (HttpResponseMessage response = await client.PostAsync(API_Url, stringContent))
            {
                if (response.IsSuccessStatusCode)
                {

                    byte[] mp3Bytes = await response.Content.ReadAsByteArrayAsync();

                    try
                    {
                        using (FileStream fs = File.OpenWrite(speechFile))
                        {

                            await fs.WriteAsync(mp3Bytes, 0, mp3Bytes.Length);
                            Debug.Log($"Speech file saved to {speechFile}");
                        }
                        result = true;
                    }
                    catch (MissingReferenceException ex)
                    {
                        File.Create(speechFile).Close();
                        Debug.LogError(ex);
                        using (FileStream fs = File.OpenWrite(speechFile))
                        {
                            await fs.WriteAsync(mp3Bytes, 0, mp3Bytes.Length);
                            Debug.Log($"Speech file saved to {speechFile}");
                        }
                        result = true;
                    }

                }
                else
                {
                    Debug.LogError($"Error: {response.StatusCode} - {response.ReasonPhrase}");

                }
            }
        }
        catch (Exception ex)
        {
            Debug.LogError(ex);
        }

        return result;
    }
    public async Task<ChatResponse> ClieantResponseChat(ChatRequest r)
    {
        return JsonConvert.DeserializeObject<ChatResponse>(await ClieantResponse(r));
    }
    public async Task<ImageGenerateResponseURL> ClieantResponseimageGenerat(ImageGenerateRequest r)
    {
        return JsonConvert.DeserializeObject<ImageGenerateResponseURL>(await ClieantResponse(r));
    }

    public async Task<ImageGenerateResponseb64json> ClieantResponseimageGeneratb64(ImageGenerateRequest r)
    {
        return JsonConvert.DeserializeObject<ImageGenerateResponseb64json>(await ClieantResponse(r));
    }

    public async Task<ChatResponse> ClieantResponseChatAnalyze(ChatMessageAnalyze r)
    {
        return JsonConvert.DeserializeObject<ChatResponse>(await ClieantResponse(r));
    }


}



namespace OpenAiFormet
{
    interface URL
    {
        public string Get_API_Url();
    }

    #region Chat
    public class ChatRequest : URL
    {
        public const string API_Url = "https://api.openai.com/v1/chat/completions";

        [JsonProperty("model")]
        public string Model { get; set; } = "gpt-3.5-turbo";

        [JsonProperty("messages")]
        public List<ChatMessage> Messages { get; set; }

        public string Get_API_Url()
        {
            return API_Url;
        }
    }
    public enum role
    {
        [EnumMember(Value = "system")]
        system,
        [EnumMember(Value = "user")]
        user,
        [EnumMember(Value = "assistant")]
        assistant
    }

    public class ChatMessage
    {
        [JsonProperty("role"), JsonConverter(typeof(StringEnumConverter)), XmlAttribute("role")]
        public role Role;
        [JsonProperty("content"), XmlAttribute("content")]
        public string Message = "";
    }

    public class ChatResponse
    {
        [JsonProperty("id")]
        public string Id { get; set; }

        [JsonProperty("object")]
        public string Object { get; set; }

        [JsonProperty("created")]
        public int Created { get; set; }

        [JsonProperty("model")]
        public string Model { get; set; }

        [JsonProperty("system_fingerprint")]
        public string SystemFingerprint { get; set; }

        [JsonProperty("choices")]
        public List<ChatChoice> Choices { get; set; }

        [JsonProperty("usage")]
        public ChatUsage Usage { get; set; }
    }
    public class ChatChoice
    {
        [JsonProperty("index")]
        public int Index { get; set; }

        [JsonProperty("message")]
        public ChatMessage Message { get; set; }

        [JsonProperty("finish_reason")]
        public string FinishReason { get; set; }
    }
    public class ChatUsage
    {
        [JsonProperty("prompt_tokens")]
        public int PromptTokens { get; set; }

        [JsonProperty("completion_tokens")]
        public int CompletionTokens { get; set; }

        [JsonProperty("total_tokens")]
        public int TotalTokens { get; set; }
    }



    public class ImageAnalyzeRequest : URL
    {

        private const string API_Url = "https://api.openai.com/v1/chat/completions";
        [JsonProperty("model")]

        public const string model = "gpt-4-vision-preview";
        [JsonProperty("messages")]
        public List<ChatMessageAnalyze> Messages { get; set; }

        public string Get_API_Url()
        {
            return API_Url;
        }
    }

    public class ChatMessageAnalyze
    {
        [JsonProperty("role")]
        public string Role { get; set; }

        [JsonProperty("content")]
        public ContentAnalyze Content { get; set; }
    }

    public class ContentAnalyze
    {

        [JsonProperty("text")]
        public string text { get; set; }

        [JsonProperty("image_url")]
        public string image_url { get; set; }
    }


    #endregion

    #region imageGenerate
    public class ImageGenerateRequest : URL
    {
        private const string API_Url = "https://api.openai.com/v1/images/generations";

        [JsonProperty("prompt")]
        public string Prompt { get; set; } // Required

        [JsonProperty("model")]
        public string Model { get; set; } = "dall-e-2"; // Optional, defaults to dall-e-2

        [JsonProperty("n")]
        public int N { get; set; } = 1; // Optional, defaults to 1

        [JsonProperty("quality")]
        public string Quality { get; set; } = "standard"; // Optional, defaults to standard

        [JsonProperty("response_format")]
        public string ResponseFormat { get; set; } = "b64_json"; // Optional, defaults to url or  b64_json

        [JsonProperty("size")]
        public string Size { get; set; } = "1024x1024"; // Optional, defaults to 1024x1024

        [JsonProperty("style")]
        public string Style { get; set; } = "vivid"; // Optional, defaults to vivid or natural

        [JsonProperty("user")]
        public string User { get; set; } // Optional

        public string Get_API_Url()
        {
            return API_Url;
        }
    }


    public class ImageGenerateResponseURL
    {
        [JsonProperty("created")]
        public long Created { get; set; }

        [JsonProperty("data")]
        public List<ImageData> Data { get; set; }
    }

    public class ImageData
    {
        [JsonProperty("url")]
        public string Url { get; set; }
    }
    public class ImageGenerateResponseb64json
    {
        [JsonProperty("created")]
        public long Created { get; set; }

        [JsonProperty("data")]
        public List<ImageDatab64json> Data { get; set; }
    }

    public class ImageDatab64json
    {
        [JsonProperty("b64_json")]
        public string b64_json { get; set; }
    }
    #endregion

    #region TTS
    public class TextToSpeechRequest : URL
    {
        public const string API_Url = "https://api.openai.com/v1/audio/speech";

        [JsonProperty("model")]
        public string Model { get; set; } = "tts-1";

        [JsonProperty("voice")]
        public string Voice { get; set; } = "alloy";

        [JsonProperty("input")]
        public string Input { get; set; }
        public string Get_API_Url()
        {
            return API_Url;
        }
    }
    #endregion


}

테스트 코드

더보기
using OpenAiFormet;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using UnityEngine;
using static UnityEditor.Experimental.GraphView.GraphView;
using static UnityEngine.Networking.UnityWebRequest;

public class OpenAI_RestTest : MonoBehaviour
{

    public  string KeyCode ="sk-";
    OpenAI_Rest api;
    AudioSource source;
    SpriteRenderer spriteRenderer;
    public async void test()
    {

        source = GetComponent<AudioSource>();
        spriteRenderer = GetComponent<SpriteRenderer>();
        api = new OpenAI_Rest();
        api.OpenAI_Key = KeyCode;
        api.Init();


        //chat 
        ChatRequest chatRequest = new ChatRequest();


        chatRequest.Messages = new List<ChatMessage>
        {
            new ChatMessage() { Role = role.system, Message = "고양이 말투로말한다" },
            new ChatMessage() { Role = role.user, Message = "반갑다" },
            new ChatMessage() { Role = role.assistant, Message = "반갑다냥" },
            new ChatMessage() { Role = role.user, Message = "오늘밥 뭐먹을까" },
        };
        var data = ((await (api.ClieantResponseChat(chatRequest))).Choices);
        foreach (var choice in data)
        {
            Debug.Log(choice.Message.Message);
        }



        //tts
        TextToSpeechRequest textToSpeech = new TextToSpeechRequest() { Input = "테스트용 데이터입니다" };
        api.speechFile = Application.streamingAssetsPath + "/aaa.mp3";
        if (await (api.ClieantResponseTTS(textToSpeech)))
        {
            try
            {
                Ienumrator = LoadPath();
                StartCoroutine(Ienumrator);
                source.Play();
            }
            catch (Exception e)
            {
                Debug.Log(e);
            }

        }

        //text to image
        ImageGenerateRequest imageGenerateResponseb64Json = new ImageGenerateRequest() { Prompt = "cat in the box,cartoon drawing style" };
        var imagedata  = await (api.ClieantResponseimageGeneratb64(imageGenerateResponseb64Json));

        //byte를 sprite로 변경
        byte[] imageBytes = Convert.FromBase64String(imagedata.Data[0].b64_json);
        Texture2D tex = new Texture2D(2, 2);
        tex.LoadImage(imageBytes);
        Sprite sprite = Sprite.Create(tex, new Rect(0.0f, 0.0f, tex.width, tex.height), new Vector2(0.5f, 0.5f), 100.0f);
        spriteRenderer.sprite = sprite;
    }

    IEnumerator Ienumrator;
    IEnumerator LoadPath()
    {
        WWW www = new WWW(api.speechFile);
        yield return www;
        source.clip = www.GetAudioClip();
        source.Play();
        StopCoroutine(Ienumrator);
    }

    void Start()
    {
        test();
    }
}



 

'엔진 > 유니티' 카테고리의 다른 글

구글 gemini api 유니티에서 사용하기  (0) 2023.12.21
Unity Spline 기능 추가!  (1) 2023.06.01
Unity Simulator  (0) 2023.01.20
Unity ObjectPool Api  (0) 2022.11.02
unity Redis  (0) 2022.07.13

+ Recent posts