yongsa0221의 고물상
생성형 AI 프로젝트에 사용하기 본문
프로젝트의 가장큰 핵심 도메인은 생성형 AI에 관련한 기능이다.
생성형 AI를 사용하여 사용자 입력을 가공해야 하며 이 과정에서 여러 다른 생성형 AI와 응답을 가져와야 한다.
AI를 통해 응답받아야 할 기능은 ...
- 스케치를 이미지로 변환
- 간단한 문장을 동화 문장 형태로 바꾸기
- 진행되고 있는 동화 내용에 관한 질문 생성하기
- 특정 단어에 관련한 응답 생성하기
이다.
이제껏 생성형 AI를 사용하여 원하는 응답을 높은 확률로 얻을 수 있는 방법에 관하여 계속해서 생각해보았다. 요즘 흔히 이야기하는 프롬프팅 엔지니어링에 관련한 내용인 것이다. 그러나 이러한 생성형 AI를 프로젝트 내에서 어떻게 관리하고 사용해야 하는지는 또 다른 문제이다.
프로젝트를 진행하며 가장 흥미로웠던 요소는 생성형 AI는 어떤 기능도 다 수행할 수 있다는 것이었다. 생성형 AI의 범용성이 너무 뛰어나서 오히려 어떤 식으로 제한해서 사용할지가 가장 큰 고민이었다. 프로젝트 내에서 생성형 AI는 삽화를 만들어주는 기능이면서 동화 문장을 생성하고 질문도 생성하는 동시에 단어 뜻에 관한 질의도 받아준다. 이렇게 무궁무진한 녀석의 범용성을 어떻게 제한하고 도메인 에서 어떻게 구현해야하는지가 가장 어렵기도 하고, 새롭던 부분이었다.
생성형 AI의 기능은 무궁 무진하나 프로그램의 신뢰도를 저하시키는 요인이 있다. 프로젝트를 진행하며 느낀 생성형 AI의 문제점은...
입력에 따른 응답이 일정하지 않다. ( 응답 예측이 불가능하다 )
생성(API 호출)시 많은 시간이 소요된다.
응답 생성이 실패 할 수 있다.
무엇보다 가장 치명적인 것은 예측이 불가능 하다는 점이다. 생성형 AI에 무엇을 입력 하는 순간 더 이상 할 수 있는게 없다. 그저 가만히 몇초간 기다리며 원하는 답변이 생성되기를 빌 뿐이다. 으레 모든 AI가 그러하듯 gpt녀석이 어떻게 돌아가는지 전혀 알수 없고, 예측도 불가능하니 사용하는 입장에서는 불안함 뿐이다. 전혀 엉뚱한 내용이 나올수도, 원하는 형식으로 응답받지 못할수도, time-out이 걸릴 수도 있다. 아직까지 생성형AI를 도메인의 핵심 내용으로 가져가는 것은 시기 상조라는 생각이 든다.
추상화의 관점에서…
프로젝트에서 생성형 AI를 사용하기 위한 접근 방식은 생성형 AI에게 속성을 부여하고, "삽화 생성기", "질문 생성기" 와 같이 추상화하여 사용하는 것이었다. 필요한 기능을 generativeAI라는 공통 분모로 묶고 그림과 같은 모듈을 구현해야 하는 방식으로 접근하였다.
이러한 생각을 하게된 이유는 "생성형 AI를 하나의 함수로 생각해보면 어떨까" 라는 생각에서 시작되었다. AI가 물론 매우 복잡한 수학적 계산을 거치는 함수이긴 하지만 그보다 더욱 단순화하여 입력이 있고 출력이 있는 단순한 형태로 추상화 하고자 하였다.
이 과정에서 생성형 AI에 ‘이미지 생성기’, ‘동화 생성기’, ‘질문 생성기’ 와 같이 특정 속성을 부여하게 하는 속성은 다음과 같을 것이다.
생성형 AI 모델 - 어떤 모델을 사용할지
시스템 프롬프트( 혹은 응답 생성시 고정적으로 사용할 프롬프트)
그외 설정값들
제너릭을과 인터페이스를 사용하여 openAI라는 인터페이스를 통해 생성형 AI를 사용하였다. T는 생성형 AI라는 함수의 출력, E는 입력이라고 볼 수 있다.
gpt-4 부터는 json 형식의 답변을 거의 완벽히 지원한다. 그래서 role : system만 자연어로 입력하고 role : user 사용 시에 그대로 json형식을 통해 넘겨주어도 잘 이해한다.
프로젝트 초기 gpt-3.5를 사용할 때에는 json 형식의 이해도가 좀 떨어져서 자연어 방식으로 gpt에게 입력하였다.
public abstract class Jsonable {
protected static final ObjectMapper MAPPER = new ObjectMapper();
public String toJsonString() throws JsonProcessingException {
return MAPPER.writeValueAsString(this);
}
}
생성형 AI라는 함수의 입력은 json형식으로 변환되어 질의되기에 gpt를 사용할 때에 사용자 입력은 Jsonable을 상속받도록 하였다.
아래의 코드는 contextAndQeustionGenerator의 구현 예시이다.
public interface OpenAi<T ,E extends Jsonable> {
ObjectMapper defaultMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
T getResponse(E jsonable) throws OpenAiException;
}
@Component
public class RefineContextAndQuestionGenerator implements OpenAi<RefineContextAndQuestionDTO, BookBuildJsonable> {
@Autowired
private OpenAiService openAiService;
private static final ChatMessage SYSTEM_MESSAGE = new ChatMessage("system",
"""
you're a helpful assistant who helps writing a fairy tale
if sentences are given
first, enrich sentences more naturally in fairy tale storybook style, using ~해요 체
second, create 3-short question about current sentences
questions can based on character's personality
questions must related to what will happen next
questions must seperated by linebreak. no index.
questions must be korean
answer format must be json
{
"refinedText" : "",
"questions" : []
}
"""
);
@Override
public RefineContextAndQuestionDTO getResponse(BookBuildJsonable jsonable) throws OpenAiException {
try {
ChatMessage fairyTaleInfo = new ChatMessage("user", jsonable.toJsonString());
ChatCompletionRequest request = ChatCompletionRequest.builder()
.model(OpenAiModelEnum.GPT_4_TURBO_PREVIEW.getName())
.messages(List.of(SYSTEM_MESSAGE, fairyTaleInfo))
.temperature(0.5)
.maxTokens(500)
.build();
ChatMessage response = openAiService.createChatCompletion(request)
.getChoices()
.get(0)
.getMessage(
RefineContextAndQuestionResponse contextAndQuestionResponse = defaultMapper.readValue(response.getContent(), RefineContextAndQuestionResponse.class);
return RefineContextAndQuestionDTO.builder()
.response(contextAndQuestionResponse)
.originResponse(response.getContent()).build();
} catch (JsonProcessingException e) {
throw new OpenAiException("gpt answer format does not follow RefineContexAndQuestionDTO.class : " + e.getMessage());
}
}
}
생각해볼것..
gpt system prompt 관리
생성형 AI에 특정한 속성을 부여하는 것은 system prompt에와 모델에 의해 결정된다고 볼 수 있다. 동작구조가 동일 하기에 같은 로직의 코드가 반복적으로 나타난다.
하나의 클래스에 특정한 system prompt와 모델 관련 정보를 주입하여 반복되는 코드를 줄일 수 있을까?
접근방식
프로젝트를 진행하는 과정에서 생성형 AI를 하나의 함수로 생각하고 이를 추상화하여 구현하였지만 사실 함수라고 볼 수 없는것이 사실이다. 여기서 말하는 함수는 결정론적인 알고리즘을 따르는, 즉 어떤 특정한 입력이 들어오면 언제나 똑같은 과정을 거쳐서 언제나 똑같은 결과를 반환하는 것이다. 그러나 생성형 AI는 그렇지 않다. 이차이는 명백하다.
프로젝트 구현을 위해 생성형 AI라는 것을 추상화 하여 구현하였지만 이 과정에서 AI가 가지는 가장 근본적인 부분을 무시한 체 구현했다는 생각을 지울 수가 없다. 예측할 수 없는 녀석에게 많은 권한을 부여한 느낌이다.
생성형 AI기능을 핵심 기능과 철저히 분리해야 하는 것일까, 아니면 이런 방식으로도 녹여낼 수 있는 것일까?
'프로젝트 > 졸업프로젝트' 카테고리의 다른 글
프로젝트 리팩터링하기 (0) | 2024.09.19 |
---|---|
Redis 사용하기 (0) | 2024.09.02 |
프로젝트 시작하기 (0) | 2024.08.21 |
생성형 AI를 사용하여 이미지 생성하기 2 (0) | 2024.08.19 |
생성형 AI를 사용하여 이미지 생성하기 1 (0) | 2024.08.19 |