프로젝트를 하며 이미지 생성 로직을 구현해야 했는데, 기존 프롬프트로는 원하는 이미지를 얻는 데 한계가 있었다.
생각해본 원인은 아래와 같다.
1. 프롬프트가 한글로 작성됨
2. `{name}`, `{description}` 변수에 단순히 작품 설명만을 넣어 다소 고정적인 형식으로 구성됨
이러한 한계를 해결하고 보다 정교한 이미지를 생성하기 위해 ChatGPT를 활용하여 프롬프트를 동적으로 생성하는 방식을 도입하기로 했다.
<기존 프롬프트 유틸>
public class PromptUtils {
/**
* UniverseFileRequestDto 를 기반으로 작품 세계관 일러스트레이션 이미지를 위한 프롬프트를 생성하는 메서드
*
* @param imageGenerateRequest 작품 정보가 담긴 DTO
* @return 생성된 작품 세계관 일러스트레이션 프롬프트
*/
public static String createPrompt(GenerateUniverseImageRequest imageGenerateRequest) {
return String.format(
"다음 정보에 따라 작품의 세계관 일러스트레이션 이미지를 만드세요. 작품의 분위기와 주요 내용을 시각적으로 표현해야 합니다. 다음은 작품의 정보입니다 " +
"세계관 이름: [%s] \n" +
"세계관 설명: [%s] \n" +
"이미지는 캐릭터, 배경, 주요 사건, 또는 작품의 분위기를 시각적으로 나타내야 합니다. \n" +
"이미지 스타일: 전반적으로 현실적이면서도 작품의 장르와 분류에 맞는 예술적 디테일을 적용하세요. \n" +
"이미지는 주로 밝고 선명한 색감을 사용하되, 장르나 분류에 따라 어두운 색상도 적절히 혼합하세요. \n\n" +
"이미지는 하나이고, 제작된 표지 이미지는 독자의 관심을 끌 수 있도록 세밀하고 몰입감 있게 표현해주세요.",
imageGenerateRequest.getName(),
imageGenerateRequest.getDescription()
);
}
}
1️⃣ 프롬프트 작성법
1. 원하는 답을 얻는 방법
1) LLM이 이해하기 쉽도록 공간 구조화
프롬프트 띄어쓰기만 잘해도, 결과물이 달라진다. "오케이", "그러니까 말이야" 등 쓸데없는 글은 넣지 말자.
<구조화되지 않은 글>
오케이 AI야, 이거 좀 해줘. 그러니까 말이야, 무슨 말인지 알지?
작가가 그리는 세계관이 뭔지 너도 이제 대충 감이 잡히겠지? 이거 참 복잡한데, 천천히 생각해보면서 이해해봐.
아무래도 주어진 name이랑 description을 잘 살펴보면 뭔가 실마리가 잡힐 것 같긴 한데, 그렇다고 너무 깊이 생각할 필요는 없을 것 같기도 하고…
뭐랄까, 일단 작가가 말하고자 하는 그 세계관이 어떻게 생겼는지 머릿속에서 상상해본 다음에, 그것을 어떻게 하면 우리 눈앞에 막 펼쳐지는 것처럼 보이게 할 수 있을지 고민해보면 좋을 것 같아.
어쨌든 네가 이걸 작성할 때는 생각의 나래를 자유롭게 펼쳐야 하니까, 굳이 주어진 정보에 얽매일 필요도 없는 것 같기도 하고,
그래도 최대한 name과 description에 충실하게 해야겠지? 그래야 작가가 원하는 방향이랑 크게 다르지 않을 테니까 말이야.
그러니까 아주 천천히, 정말 깊이 있게 고민하면서, 네가 생각하는 좋은 답변을 만들어주면 좋을 것 같아.
괜찮은 답변, 멋진 답변, 누구나 감탄할 만한 답변, 그런 걸 부탁해.
아무튼 이런 식으로 뭔가 주저리주저리 생각을 정리해나가면서, 괜찮은 답변을 만들어내면 될 것 같아. 됐지?
<구조화된 글>
[상황]
- 너는 글을 쓰는 작가를 돕는 훌륭한 프롬프트 엔지니어야.
- 작가가 그리는 세계관의 이미지를 생성할 수 있도록, 주어진 정보를 바탕으로 구체적인 프롬프트를 작성할거야.
- 아래 [입력값]을 꼼꼼히 읽고, [지시사항]에 따라 프롬프트를 작성해줘.
[지시사항]
- name 과 description 을 바탕으로 [주어 + 행위 + 배경 + 스타일 + 사진]의 비율 정도를 말해주고, 필요하다면 더 디테일하게 [각도 / 포즈 / 구도 / 색상 / 시대 / 계절] 등 다양한 요소들에 대해 언급할 것.
- 이미지에 텍스트는 추가하지 말 것.
- 프롬프트는 반드시 영어로 작성할 것.
- 주어진 정보 외에는 어떤 창의적 개입도 하지 말고, 오로지 입력된 정보만을 바탕으로 작성할 것.
2) Do not을 명시
하지 말아야 할 것들을 지정해준다.
[지시사항]
- 주관을 개입하지 말 것.
- 이미지에 텍스트는 추가하지 말 것.
3) 강조하기 위해 대문자 사용
특히 중요한 규칙은 대문자로 넣어주면 잘 알아듣는다.
[Response Rules]
1. Respond in Korean.
2. You may incorporate emojis.
3. RESPOND MUST ALWAYS HAVE ONLY ONE SINGLE SENTENCE. LIMITS TO 20 CHARACTERS.
4. YOU MUST NOT ANSWER DIRECTLY TO USER'S ORIGINAL QUESTION. INDIRECTLY SUGGEST.
2. 환각을 줄이는 방법
1) 사실과 견해 구분
다음 주제에 대해 대답해줘. 가능한 한 자세한 정보를 모아서 다음 형식으로 분류해줘.
- 사실: {}
- 너의 견해: {}
- 추측: {}
- 불확실한 점: {}
각 항목을 최대한 자세히 설명해줘.
2) 자기 검증(Self-Critic)
Self-Critic을 하면 결과물에 환각이 사라진다는 연구 결과가 있다고 한다. AI를 통해 객관적인 사실을 전달해야하는 경우에는 이런식으로 환각을 줄일 수 있다.
- 주제: 푸바오가 중국에 간 배경과 년도를 알려줘
- 규칙: 모르는 정보면, 모른다고 확실히 대답해
- 자기검증: 최종 정보에 대해서, 스스로 검증한 후 대답해
3. 토큰을 아끼는 방법
단순히 사실 검증이 목적이라면, 토큰을 아끼기 위해 다음과 같이 설정할 수 있다.
길게 말하지 말고,
if you know: yes
if you don't know: no
*설명하지 말 것
참고
https://www.youtube.com/watch?v=GlvOHXJT_gI
https://www.elancer.co.kr/blog/detail/250
https://www.magicaiprompts.com/blog/prompt-engineer-gpts-generating-advanced-prompt
2️⃣ 코드
아래는 Spring AI를 사용한 코드 예시이다.
@Component("openAiPromptGenerator")
@RequiredArgsConstructor
public class OpenAiPromptGenerator implements PromptGenerator {
private final ChatModel chatModel;
private final OpenAiProperties openAiProperties;
/**
* OpenAI API 를 이용해 세계관 이미지를 위한 프롬프트를 생성하는 메서드
*
* @param universeImageRequest 작품 정보가 담긴 DTO
* @return 세계관 이미지 생성 시 요청할 프롬프트
*/
@Override
public String generateUniverseImagePrompt(GenerateUniverseImageRequest universeImageRequest) {
String systemContent = "[상황]\n"
+ "- 너는 글을 쓰는 작가를 돕는 훌륭한 프롬프트 엔지니어야. \n"
+ "- 작가가 그리는 세계관의 이미지를 생성할 수 있도록, 주어진 정보를 바탕으로 구체적인 프롬프트를 작성할거야.\n"
+ "- 아래 [입력값]을 꼼꼼히 읽고, [지시사항]에 따라 프롬프트를 작성해줘.\n"
+ "\n"
+ "[지시사항]\n"
+ "- name 과 description 을 바탕으로 [주어 + 행위 + 배경 + 스타일 + 사진]의 비율 정도를 말해주고, 필요하다면 더 디테일하게 [각도 / 포즈 / 구도 / 색상 / 시대 / 계절] 등 다양한 요소들에 대해 언급할 것.\n"
+ "- 이미지에 텍스트는 추가하지 말 것.\n"
+ "- 프롬프트는 반드시 영어로 작성할 것.\n"
+ "- 주어진 정보 외에는 어떤 창의적 개입도 하지 말고, 오로지 입력된 정보만을 바탕으로 작성할 것.";
String userContent = String.format(
"[입력값]\n"
+ "- 세계관의 이름 (name) : [%s]\n"
+ "- 세계관에 대한 부가 설명 (description) : [%s]\n"
, universeImageRequest.name()
, universeImageRequest.description()
);
SystemMessage systemMessage = new SystemMessage(systemContent);
UserMessage userMessage = new UserMessage(userContent);
Prompt prompt = new Prompt(List.of(systemMessage, userMessage), chatOptions());
ChatResponse chatResponse = chatModel.call(prompt);
String result = chatResponse.getResult().getOutput().getContent();
return result;
}
private OpenAiChatOptions chatOptions() {
return OpenAiChatOptions.builder()
.withModel("gpt-4o-mini")
.withTemperature(Double.parseDouble(openAiProperties.chat().options().temperature()))
.withResponseFormat(new ResponseFormat(ResponseFormat.Type.TEXT))
.build();
}
}
3️⃣ 결과
사용자 요청
{
"title" : "마법사와 고양이",
"description" : "마법을 전혀 쓸 줄 모르는 허당 마법사와, 모든 마법을 다룰 줄 아는 건방진 고양이의 좌충우돌 일상을 그린 이야기입니다. 마법 실험실과 현대적인 도시를 배경으로, 두 캐릭터가 벌이는 웃긴 상황들과 예상치 못한 사건들이 매 에피소드마다 펼쳐집니다.",
"category": "웹툰스토리",
"genre": [
"예능", "코미디"
]
}
생성된 프롬프트
아래는 ChatGPT로 생성된 프롬프트이다. 해당 프롬프트는 Image Generation에 사용될 예정이다.
**Prompt:**
A whimsical illustration featuring a clumsy wizard and a sassy cat, surrounded by magical potions and modern city elements.
The wizard, with a surprised expression, is holding a wand that’s sputtering sparks, while the cat sits confidently on a potion bottle, smirking.
The background showcases a vibrant modern cityscape with a hint of magical elements, like floating orbs and whimsical clouds. The style is colorful and cartoonish, capturing a light-hearted and humorous tone, with the characters in the foreground and the cityscape slightly blurred in the background.
The composition focuses on the dynamic interaction between the characters, emphasizing their contrasting personalities.
프롬프트로 생성된 이미지
위에서 생성된 프롬프트를 통해 생성된 이미지는 아래와 같다.

'✍️ 개발 기록' 카테고리의 다른 글
| [👀 Owing] OpenAI 이미지 생성 후 S3에 안전하게 저장하기 (feat. AWS S3에 Base64 이미지 저장) (0) | 2024.11.08 |
|---|---|
| [👀 Owing] Spring AI 도입기 🤖 (0) | 2024.11.07 |
| [👀 Owing] PostgreSQL 도입기 (feat. MySQL과의 차이) (0) | 2024.11.05 |
| [👀 Owing] Java Record 도입기 ☕️ (0) | 2024.11.05 |
| [👀 Owing] 멀티모듈 - 모듈 간 순환참조 이슈 해결 과정 💥 (0) | 2024.11.05 |