Carrot
본문 바로가기
Unity/OpenAI

[OpenAI API] API 연결 & 동작 원리 확인하기

by 독기품은토끼 2026. 2. 8.

1. 시작

인스타 릴스를 넘기다 우연히 유니티 관련 흥미로운 책 광고를 보게 됐다.
그래서 앞으로 이 책을 공부하면서 그날그날 배운 내용들을 하나씩 정리해보려고 한다!

 

 

 

LEVELIT 유니티와 OpenAI API로 만드는 인공지능 NPC | 박재환 - 교보문고

LEVELIT 유니티와 OpenAI API로 만드는 인공지능 NPC | AI와 게임이 만나면 NPC가 숨을 쉰다고?유니티에 ChatGPT, Whisper, DALL-E, TTS 같은 최신 생성형 AI를 자연스럽게 얹어, 말을 걸면 반응하고 상황에 맞게

product.kyobobook.co.kr

 

예전부터 외부 API를 연동해서
내 프로젝트 안에서 실제로 눈에 보이게 동작하는 것들을 만드는 걸 좋아했는데

이번에 Unity를 제대로 배우는 김에
OpenAI API를 붙여서 나만의 인공지능 NPC를 만들어보는 것도 정말 좋은 경험이 될 것 같았다.

 

2. API 연결 & 동작 원리 확인

1. HTTP POST 요청을 위한 using문

using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using System.Text;
using System.Text.RegularExpressions;

 

이 중에서 HTTP 통신과 직접적으로 관련 있는 것은 아래다.

 

[UnityEngine.Networking]

  • UnityWebRequest 클래스를 사용하기 위해 필요
  • Unity에서 GET / POST / PUT / DELETE 같은 HTTP 요청을 보낼 때 사용
UnityWebRequest request = new UnityWebRequest(apiUrl, "POST");

 

 

[System.Text]

  • 문자열을 byte 배열로 변환하기 위해 사용
  • HTTP 요청의 body는 결국 byte 데이터이기 때문에 JSON 문자열을 그대로 보낼 수 없고, 인코딩 과정이 필요하다.
byte[] postData = Encoding.UTF8.GetBytes(jsonData);
 

[System.Text.RegularExpressions]

  • 서버에서 받은 JSON 응답 중"content" 부분만 추출하기 위해 사용

 

2. OpenAI API 기본 설정

private const string apiUrl = "https://api.openai.com/v1/chat/completions";
private const string apiKey = "";

public string prompt = "You are a helpful assistant.";
public string content = "Write a haiku about recursion in programming.";
  • apiUrl : OpenAI의 Chat Completions API 엔드포인트
  • apiKey : 요청을 보낸 사용자를 인증하기 위한 키
  • prompt : AI의 역할
  • content : 사용자가 모델에 전달할 구체적인 요청 내용

 

3. HTTP 요청

OpenAI는 JSON 형태의 요청만 받기 때문에

코드 내부에서 JSON 형태로 변환해주는 부분이 필요하다.

string jsonData = @"{
    ""model"": ""gpt-4o"",
    ""messages"": [
        {
            ""role"": ""system"",
            ""content"": """ + prompt + @"""
        },
        {
            ""role"": ""user"",
            ""content"": """ + content + @"""
        }
    ]
}";

byte[] postData = Encoding.UTF8.GetBytes(jsonData);

 

그리고 API 호출은 네트워크 통신이므로 비동기 방식으로 호출해야한다.

따라서 유니티에선 비동기 처리를 위해 코루틴을 활용해준다.

 

using (UnityWebRequest request = new UnityWebRequest(apiUrl, "POST"))
{
    request.SetRequestHeader("Content-Type", "application/json");
    request.SetRequestHeader("Authorization", "Bearer " + apiKey);
    
    request.uploadHandler = new UploadHandlerRaw(postData);
    request.downloadHandler = new DownloadHandlerBuffer();

    yield return request.SendWebRequest();

    if (request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.ProtocolError)
    {
        Debug.Log("Error : " + request.error);
    }
    else
    {
        string responsText = request.downloadHandler.text;
        Debug.Log("Response : " + responsText);

        string pattern = @"""content"":\s*""(.*?)""";
        Match match = Regex.Match(responsText, pattern);

        if (match.Success)
        {
            string assistantMessage = match.Groups[1].Value;
            Debug.Log("Assistant Message : " + assistantMessage);
        }
        else
        {
            Debug.LogWarning("No valid response found.");
        }
    }
}

 

POST 방식으로 호출을 하고

using 블록을 사용하여 요청 종료시 자동 정리되도록 해주었다.

 

[헤더 설정]

서버가 받은 데이터가 JSON임을 알리기 위하여 Content-Type에 json으로 넣어주었고

API Key를 가진 사용자가 보냄을 알리기 위해 Bearer 키워드를 사용해주었다.. (이게 규정?? 인가요?ㅠ 잘은 모르겠다)

 

[핸들러]

UploadHandlerRaw : POST로 보낼 데이터

DownloadHandlerBuffer : 서버 응답을 문자열로 받기 위해 사용

 

 

OpenAI에게 받은 Json의 형식은 위와 같은데

이 예제에서는 content 부분만 다시 출력하도록 assistant Message를 설정해주었다. 

 

pattern에 content부분만 match해서 출력되도록 해주었으니

두번째 로그를 살펴보면

JSON 파일 내부에 "content"로 작성된 부분만 출력된 것을 확인할 수 있었다.

 

4. 왜 match를 사용한걸까?

DownloadHandlerBuffer를 통해 전달받은 응답은

OpenAI 서버가 반환한 JSON 전체 문자열이기 때문에 원하는 데이터(content)를 그대로 사용할 수 없다.

따라서 JSON 내부에서 실제 응답 텍스트가 담긴 "content" 필드만 추출하기 위해 match라는 정규식을 이용해준 것이다.

 

그러니까 즉,  request.downloadHandler.text; 로 뽑은 건 그냥 JSON 전체 파일이고

이 중에 content나 뭐 service_tier 등에 해당하는 값을 뽑고싶다면 Match를 사용해주면 된다.