LLM 프롬프트 엔지니어링 고급 기법: 실전에서 바로 쓰는 프롬프트 패턴
Chain of Thought, Few-Shot Learning, Self-Consistency 등 실무에서 즉시 활용 가능한 고급 프롬프트 엔지니어링 기법을 코드 예제와 함께 소개합니다.
벡터 임베딩(Vector Embedding)은 단어, 문장, 문서와 같은 텍스트를 고차원 공간의 숫자 벡터로 변환하는 기술입니다. 이는 컴퓨터가 텍스트의 의미를 이해하고 비교할 수 있게 해주는 핵심 기술입니다.
간단히 말해, “강아지”라는 단어를 [0.2, 0.8, 0.3, ...] 같은 숫자 배열로 변환하는 것입니다. 이때 의미가 비슷한 단어들은 벡터 공간에서 가까이 위치하게 됩니다.
컴퓨터는 숫자만 이해합니다. 텍스트를 직접 비교하거나 계산할 수 없습니다. 하지만 벡터로 변환하면:
# 텍스트로는 불가능
"강아지"와 "개"가 얼마나 유사한가? → 계산 불가
# 벡터로는 가능
[0.2, 0.8, 0.3] 과 [0.25, 0.75, 0.35]의 거리 = 0.12 → 매우 유사!
각 단어를 고유한 위치에 1을 표시하는 방식입니다.
어휘: ["강아지", "고양이", "새"]
"강아지" = [1, 0, 0]
"고양이" = [0, 1, 0]
"새" = [0, 0, 1]
문제점:
- 모든 단어가 동일한 거리 (의미 관계 X)
- 어휘 크기만큼 차원 증가
- 10,000 단어 = 10,000 차원 벡터
단어의 의미를 학습하는 첫 번째 혁신입니다.
# Skip-gram 방식
"강아지가 공을 물다"
중심 단어: "공"
주변 단어: "강아지", "물다"
목표: "공"으로부터 주변 단어를 예측
→ 비슷한 맥락의 단어 = 비슷한 벡터
결과:
"강아지" ≈ "개" ≈ "반려견" (벡터 공간에서 가까움)
"공" ≈ "공놀이" ≈ "장난감"
Word2Vec의 유명한 성질: 단어 연산
king - man + woman ≈ queen
paris - france + korea ≈ seoul
# 실제 벡터 연산
vector("king") - vector("man") + vector("woman")
= vector("queen")
문맥을 고려한 동적 임베딩입니다.
# Word2Vec: 같은 단어 = 항상 같은 벡터
"은행에서 돈을 찾았다" → "은행" = [0.1, 0.2, ...]
"강 은행에 앉아있다" → "은행" = [0.1, 0.2, ...] # 동일!
# BERT: 문맥에 따라 다른 벡터
"은행에서 돈을 찾았다" → "은행" = [0.1, 0.2, ...] # bank
"강 은행에 앉아있다" → "은행" = [0.5, 0.1, ...] # river bank
OpenAI, Cohere 등의 대규모 임베딩 모델입니다.
특징:
- 문장/문서 수준 임베딩
- 다국어 지원
- 작업 특화 (검색, 분류, 클러스터링)
- 고차원 (1536, 3072 차원)
OpenAI text-embedding-3-large:
"이 제품은 정말 훌륭합니다"
→ [0.01, -0.23, 0.45, ..., 0.12] # 3072개 숫자
벡터의 길이, 즉 숫자의 개수입니다.
# 2차원 임베딩 (시각화 가능)
"강아지" = [0.8, 0.2]
"고양이" = [0.7, 0.3]
# 실제 임베딩 (고차원)
"강아지" = [0.1, 0.2, 0.3, ..., 0.15] # 1536 차원
차원이 높을수록:
두 벡터가 얼마나 비슷한지 계산하는 방법입니다.
코사인 유사도 (가장 일반적)
from numpy import dot
from numpy.linalg import norm
def cosine_similarity(a, b):
return dot(a, b) / (norm(a) * norm(b))
# 예시
vec_dog = [0.8, 0.2, 0.1]
vec_puppy = [0.75, 0.25, 0.15]
vec_car = [0.1, 0.1, 0.9]
similarity(vec_dog, vec_puppy) # 0.98 - 매우 유사
similarity(vec_dog, vec_car) # 0.23 - 전혀 다름
유클리디안 거리
import numpy as np
def euclidean_distance(a, b):
return np.linalg.norm(a - b)
# 거리가 가까울수록 유사
distance(vec_dog, vec_puppy) # 0.15 - 가까움
distance(vec_dog, vec_car) # 1.25 - 멀리 떨어짐
내적(Dot Product)
def dot_product(a, b):
return np.dot(a, b)
# 값이 클수록 유사
dot_product(vec_dog, vec_puppy) # 0.65 - 유사
dot_product(vec_dog, vec_car) # 0.15 - 다름
밀집 벡터 (Dense Vector)
# 대부분의 값이 0이 아님
[0.23, 0.45, 0.12, 0.67, 0.34, 0.89]
특징:
- 현대 임베딩의 기본
- 의미 정보가 압축됨
- 차원 수 적음 (512-3072)
희소 벡터 (Sparse Vector)
# 대부분의 값이 0
[0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 1, 0, ...]
특징:
- One-hot, TF-IDF 등
- 차원 수 많음 (10,000+)
- 해석 가능
from openai import OpenAI
client = OpenAI()
response = client.embeddings.create(
model="text-embedding-3-small",
input="벡터 임베딩을 배우는 중입니다"
)
vector = response.data[0].embedding # 1536 차원
| 모델 | 차원 | 성능 | 비용 (1M 토큰) | 용도 |
|---|---|---|---|---|
| text-embedding-3-small | 1536 | ⭐⭐⭐ | $0.02 | 일반 용도 |
| text-embedding-3-large | 3072 | ⭐⭐⭐⭐⭐ | $0.13 | 고성능 필요시 |
| ada-002 (구버전) | 1536 | ⭐⭐⭐ | $0.10 | 레거시 |
import cohere
co = cohere.Client('your-api-key')
response = co.embed(
texts=["텍스트 1", "텍스트 2"],
model='embed-multilingual-v3.0',
input_type='search_document'
)
vectors = response.embeddings
| 모델 | 차원 | 특징 | 비용 |
|---|---|---|---|
| embed-english-v3.0 | 1024 | 영어 특화 | $0.10/1M |
| embed-multilingual-v3.0 | 1024 | 100개 언어 | $0.10/1M |
특징: input_type 파라미터로 용도 최적화
search_document: 문서 저장용search_query: 검색 쿼리용classification: 분류 작업용from sentence_transformers import SentenceTransformer
model = SentenceTransformer('paraphrase-multilingual-mpnet-base-v2')
sentences = ["강아지가 공을 물었다", "개가 장난감을 가지고 논다"]
embeddings = model.encode(sentences)
# 유사도 계산
from sentence_transformers import util
similarity = util.cos_sim(embeddings[0], embeddings[1])
print(similarity) # 0.85 - 높은 유사도
장점:
단점:
# 전통적 키워드 검색
query = "강아지 사료"
# → "강아지", "사료" 단어가 포함된 문서만 검색
# 임베딩 기반 의미 검색
query_vec = get_embedding("강아지 사료")
# → "반려견 먹이", "애견 식품", "puppy food" 모두 검색 가능
구현 예제:
from openai import OpenAI
import numpy as np
client = OpenAI()
# 문서 임베딩
documents = [
"강아지 사료 판매합니다",
"반려견 건강식품 추천",
"고양이 간식 저렴하게",
"애견 훈련 프로그램"
]
doc_embeddings = []
for doc in documents:
emb = client.embeddings.create(
model="text-embedding-3-small",
input=doc
).data[0].embedding
doc_embeddings.append(emb)
# 검색 쿼리
query = "개 먹이 구매"
query_emb = client.embeddings.create(
model="text-embedding-3-small",
input=query
).data[0].embedding
# 유사도 계산
from scipy.spatial.distance import cosine
similarities = []
for i, doc_emb in enumerate(doc_embeddings):
sim = 1 - cosine(query_emb, doc_emb)
similarities.append((documents[i], sim))
# 정렬 및 출력
similarities.sort(key=lambda x: x[1], reverse=True)
for doc, score in similarities:
print(f"{score:.3f} - {doc}")
# 출력:
# 0.912 - 강아지 사료 판매합니다
# 0.867 - 반려견 건강식품 추천
# 0.543 - 애견 훈련 프로그램
# 0.321 - 고양이 간식 저렴하게
# 카테고리 정의
categories = {
"기술": "프로그래밍, 소프트웨어, AI, 데이터",
"스포츠": "축구, 야구, 농구, 올림픽",
"음식": "요리, 레시피, 레스토랑, 맛집"
}
# 카테고리 임베딩
category_embeddings = {}
for cat, desc in categories.items():
emb = get_embedding(desc)
category_embeddings[cat] = emb
# 새 문서 분류
new_doc = "파이썬으로 웹 스크래핑하는 방법"
doc_emb = get_embedding(new_doc)
# 가장 가까운 카테고리 찾기
best_category = None
best_score = -1
for cat, cat_emb in category_embeddings.items():
score = cosine_similarity(doc_emb, cat_emb)
if score > best_score:
best_score = score
best_category = cat
print(f"{new_doc} → {best_category} ({best_score:.2f})")
# "파이썬으로 웹 스크래핑하는 방법 → 기술 (0.89)"
# 사용자가 좋아한 영화
liked_movies = [
"인터스텔라 - SF 우주 탐험",
"마션 - 화성 생존기"
]
# 영화 DB
all_movies = [
"그래비티 - 우주 재난",
"어벤져스 - 슈퍼히어로 액션",
"컨택트 - 외계 생명체 접촉",
"타이타닉 - 로맨스 드라마"
]
# 임베딩
liked_embeddings = [get_embedding(m) for m in liked_movies]
movie_embeddings = [get_embedding(m) for m in all_movies]
# 사용자 프로필 (좋아한 영화의 평균)
user_profile = np.mean(liked_embeddings, axis=0)
# 추천 점수 계산
recommendations = []
for movie, emb in zip(all_movies, movie_embeddings):
score = cosine_similarity(user_profile, emb)
recommendations.append((movie, score))
recommendations.sort(key=lambda x: x[1], reverse=True)
print("추천 영화:")
for movie, score in recommendations[:3]:
print(f"{score:.3f} - {movie}")
# 출력:
# 0.921 - 그래비티 - 우주 재난
# 0.843 - 컨택트 - 외계 생명체 접촉
# 0.512 - 어벤져스 - 슈퍼히어로 액션
# 비슷한 문서 찾기
documents = [
"AI가 세상을 바꾼다",
"인공지능이 세계를 변화시킨다",
"강아지를 키우는 방법",
"AI 기술의 혁신적 발전"
]
embeddings = [get_embedding(doc) for doc in documents]
# 유사도 행렬 계산
threshold = 0.9 # 90% 이상 유사하면 중복
duplicates = []
for i in range(len(documents)):
for j in range(i+1, len(documents)):
sim = cosine_similarity(embeddings[i], embeddings[j])
if sim > threshold:
duplicates.append((i, j, sim))
for i, j, sim in duplicates:
print(f"중복 발견 ({sim:.2f}):")
print(f" - {documents[i]}")
print(f" - {documents[j]}")
# 출력:
# 중복 발견 (0.96):
# - AI가 세상을 바꾼다
# - 인공지능이 세계를 변화시킨다
고차원 벡터를 저차원으로 변환하여 저장 공간과 계산 속도를 개선합니다.
from sklearn.decomposition import PCA
# 1536 차원 → 256 차원 축소
pca = PCA(n_components=256)
reduced_embeddings = pca.fit_transform(original_embeddings)
# 성능 비교
원본: 1536 차원, 6KB/벡터, 100ms 검색
축소: 256 차원, 1KB/벡터, 15ms 검색
정확도 손실: 약 5%
float32 → int8로 변환하여 메모리를 4배 절감합니다.
import numpy as np
# float32 (4 bytes)
original = np.array([0.123, 0.456, 0.789], dtype=np.float32)
# int8 (1 byte) - [-128, 127] 범위로 매핑
def quantize(vector, scale=127):
return (vector * scale).astype(np.int8)
quantized = quantize(original)
# [16, 58, 100]
# 복원
def dequantize(vector, scale=127):
return vector.astype(np.float32) / scale
restored = dequantize(quantized)
# [0.126, 0.457, 0.787] - 약간의 손실
대규모 벡터 DB에서 사용되는 고급 압축 기법입니다.
# 1536 차원 벡터를 6개 sub-vector로 분할
# 각 256 차원 sub-vector를 코드북으로 압축
원본: 1536 dim × 4 bytes = 6KB
PQ 압축: 6 codes × 1 byte = 6 bytes
압축률: 1000:1
검색 속도: 10-100배 향상
# 테스트 세트
test_pairs = [
("강아지", "개", True), # 유사해야 함
("강아지", "자동차", False), # 다르게 있어야 함
("king", "queen", True),
("king", "apple", False)
]
model = SentenceTransformer('model-name')
correct = 0
for word1, word2, should_be_similar in test_pairs:
emb1 = model.encode(word1)
emb2 = model.encode(word2)
similarity = cosine_similarity(emb1, emb2)
is_similar = similarity > 0.7
if is_similar == should_be_similar:
correct += 1
accuracy = correct / len(test_pairs)
print(f"정확도: {accuracy:.2%}")
# 검색 품질 지표
# Precision@K: 상위 K개 중 관련 문서 비율
# Recall@K: 전체 관련 문서 중 상위 K개에 포함된 비율
def precision_at_k(retrieved, relevant, k):
retrieved_k = retrieved[:k]
relevant_count = len(set(retrieved_k) & set(relevant))
return relevant_count / k
def recall_at_k(retrieved, relevant, k):
retrieved_k = retrieved[:k]
relevant_count = len(set(retrieved_k) & set(relevant))
return relevant_count / len(relevant)
# 예시
query = "파이썬 튜토리얼"
retrieved_docs = search(query, k=10) # 검색 결과 10개
relevant_docs = [1, 3, 5, 12, 15] # 실제 관련 문서 ID
p = precision_at_k(retrieved_docs, relevant_docs, k=10)
r = recall_at_k(retrieved_docs, relevant_docs, k=10)
print(f"Precision@10: {p:.2%}")
print(f"Recall@10: {r:.2%}")
일반 임베딩 모델은 특정 도메인에서 성능이 떨어질 수 있습니다.
# 일반 모델
"피고인은 무죄를 주장했다"
"환자는 증상이 없다고 말했다"
→ 유사도: 0.75 (높음) - 문장 구조가 비슷
# 법률 특화 모델
"피고인은 무죄를 주장했다"
"환자는 증상이 없다고 말했다"
→ 유사도: 0.35 (낮음) - 도메인이 다름
해결책: 도메인 데이터로 Fine-tuning
# 다국어 모델에서도 언어별 성능 차이 존재
영어: 95% 정확도
한국어: 88% 정확도
소수 언어: 70% 정확도
→ 한국어 특화 모델 사용 고려
# 대부분의 모델은 토큰 수 제한 있음
OpenAI: 8191 토큰
BERT: 512 토큰
# 긴 문서 처리 방법
1. 청킹: 문서를 나누어 각각 임베딩
2. 요약: 문서 요약 후 임베딩
3. 계층적 임베딩: 단락 → 섹션 → 전체
벡터 임베딩은 현대 AI의 핵심 기술입니다. 이를 통해:
시작하기:
벡터 임베딩을 마스터하면 AI 애플리케이션의 품질을 획기적으로 향상시킬 수 있습니다!