LLM 프롬프트 엔지니어링 고급 기법: 실전에서 바로 쓰는 프롬프트 패턴
Chain of Thought, Few-Shot Learning, Self-Consistency 등 실무에서 즉시 활용 가능한 고급 프롬프트 엔지니어링 기법을 코드 예제와 함께 소개합니다.
import { Image } from ‘astro:assets’; import architectureImage from ’../../assets/vector-db-architecture.jpg’; import performanceImage from ’../../assets/vector-db-performance.jpg’;
AI 애플리케이션, 특히 RAG(Retrieval-Augmented Generation) 시스템을 구축할 때 벡터 데이터베이스는 필수입니다.
전통적인 DB vs 벡터 DB:
# 전통적인 DB: 정확한 매칭
SELECT * FROM products WHERE name = 'iPhone 15 Pro'
# 벡터 DB: 의미적 유사도 검색
query = "애플의 최신 프로 모델 스마트폰"
similar_products = vector_db.search(
embedding=get_embedding(query),
top_k=5
)
# → iPhone 15 Pro, iPhone 15 Pro Max 등 의미적으로 유사한 결과 반환
사용 사례:
특징: 완전 관리형 클라우드 서비스
import pinecone
# 초기화
pinecone.init(api_key="your-api-key", environment="us-west1-gcp")
# 인덱스 생성
index = pinecone.Index("my-index")
# 벡터 삽입
index.upsert(vectors=[
("id-1", [0.1, 0.2, 0.3, ...], {"text": "샘플 텍스트"}),
("id-2", [0.4, 0.5, 0.6, ...], {"text": "다른 텍스트"}),
])
# 검색
results = index.query(
vector=[0.15, 0.25, 0.35, ...],
top_k=10,
include_metadata=True
)
for match in results.matches:
print(f"Score: {match.score}, Text: {match.metadata['text']}")
장점:
단점:
가격:
적합한 경우:
특징: 오픈소스, AI-native 벡터 데이터베이스
import weaviate
# 클라이언트 초기화
client = weaviate.Client(
url="http://localhost:8080",
additional_headers={
"X-OpenAI-Api-Key": "your-openai-key"
}
)
# 스키마 생성
schema = {
"classes": [{
"class": "Document",
"vectorizer": "text2vec-openai",
"properties": [
{"name": "title", "dataType": ["text"]},
{"name": "content", "dataType": ["text"]},
]
}]
}
client.schema.create(schema)
# 데이터 삽입 (자동 벡터화)
client.data_object.create(
class_name="Document",
data_object={
"title": "AI 윤리 가이드",
"content": "AI 개발 시 고려해야 할 윤리적 원칙..."
}
)
# 시맨틱 검색
result = (
client.query
.get("Document", ["title", "content"])
.with_near_text({"concepts": ["인공지능 윤리"]})
.with_limit(5)
.do()
)
for doc in result["data"]["Get"]["Document"]:
print(f"Title: {doc['title']}")
장점:
단점:
가격:
적합한 경우:
특징: Rust로 작성된 고성능 벡터 데이터베이스
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
# 클라이언트 초기화
client = QdrantClient(host="localhost", port=6333)
# 컬렉션 생성
client.create_collection(
collection_name="my_collection",
vectors_config=VectorParams(
size=384, # 임베딩 차원
distance=Distance.COSINE
)
)
# 벡터 삽입
client.upsert(
collection_name="my_collection",
points=[
PointStruct(
id=1,
vector=[0.05, 0.61, 0.76, ...],
payload={"title": "문서 1", "category": "AI"}
),
PointStruct(
id=2,
vector=[0.19, 0.81, 0.75, ...],
payload={"title": "문서 2", "category": "ML"}
)
]
)
# 필터링과 함께 검색
search_result = client.search(
collection_name="my_collection",
query_vector=[0.2, 0.1, 0.9, ...],
query_filter={
"must": [
{"key": "category", "match": {"value": "AI"}}
]
},
limit=10
)
for point in search_result:
print(f"Score: {point.score}, Payload: {point.payload}")
장점:
단점:
가격:
적합한 경우:
특징: LF AI & Data Foundation 프로젝트, 엔터프라이즈급
from pymilvus import (
connections,
Collection,
FieldSchema,
CollectionSchema,
DataType,
)
# 연결
connections.connect(host="localhost", port="19530")
# 스키마 정의
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=128),
FieldSchema(name="title", dtype=DataType.VARCHAR, max_length=200),
]
schema = CollectionSchema(fields=fields)
# 컬렉션 생성
collection = Collection(name="books", schema=schema)
# 인덱스 생성
index_params = {
"metric_type": "L2",
"index_type": "IVF_FLAT",
"params": {"nlist": 1024}
}
collection.create_index(field_name="embedding", index_params=index_params)
# 데이터 삽입
entities = [
[1, 2, 3], # IDs
[[0.1, 0.2, ...], [0.3, 0.4, ...], [0.5, 0.6, ...]], # embeddings
["Book A", "Book B", "Book C"] # titles
]
collection.insert(entities)
# 검색
collection.load()
search_params = {"metric_type": "L2", "params": {"nprobe": 10}}
results = collection.search(
data=[[0.15, 0.25, ...]],
anns_field="embedding",
param=search_params,
limit=10,
output_fields=["title"]
)
for hits in results:
for hit in hits:
print(f"Distance: {hit.distance}, Title: {hit.entity.get('title')}")
장점:
단점:
가격:
적합한 경우:
특징: AI 애플리케이션을 위한 임베딩 데이터베이스
import chromadb
# 클라이언트 초기화
client = chromadb.Client()
# 컬렉션 생성
collection = client.create_collection(name="my_collection")
# 문서 추가 (자동 임베딩)
collection.add(
documents=[
"This is a document about cats",
"This is a document about dogs",
"This is a document about birds"
],
metadatas=[
{"category": "pets"},
{"category": "pets"},
{"category": "pets"}
],
ids=["id1", "id2", "id3"]
)
# 검색
results = collection.query(
query_texts=["animals that meow"],
n_results=2
)
print(results['documents'])
# [['This is a document about cats', 'This is a document about dogs']]
장점:
단점:
가격: 완전 무료 (오픈소스)
적합한 경우:
| 데이터베이스 | 평균 쿼리 시간 | QPS | 메모리 사용량 |
|---|---|---|---|
| Pinecone | 15ms | 6,600 | N/A (관리형) |
| Qdrant | 8ms | 12,500 | 2.5GB |
| Weaviate | 25ms | 4,000 | 4GB |
| Milvus | 12ms | 8,300 | 3.5GB |
| ChromaDB | 45ms | 2,200 | 3GB |
# 대규모 데이터셋 처리 시뮬레이션
import time
from typing import List
def benchmark_insert(db_client, vectors: List, batch_size: int = 1000):
"""벡터 삽입 성능 측정"""
start = time.time()
for i in range(0, len(vectors), batch_size):
batch = vectors[i:i + batch_size]
db_client.insert(batch)
duration = time.time() - start
throughput = len(vectors) / duration
return {
'duration': duration,
'throughput': throughput,
'vectors_per_second': throughput
}
# 결과 예시
"""
Pinecone: 15,000 vectors/sec
Qdrant: 25,000 vectors/sec
Weaviate: 12,000 vectors/sec
Milvus: 30,000 vectors/sec
ChromaDB: 8,000 vectors/sec
"""
요구사항:
추천: Qdrant (자체 호스팅) 또는 Pinecone (관리형)
# Qdrant를 사용한 RAG 구현
from qdrant_client import QdrantClient
from langchain.vectorstores import Qdrant
from langchain.embeddings import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.document_loaders import TextLoader
# 문서 로드
loader = TextLoader("knowledge_base.txt")
documents = loader.load()
# 청킹
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200
)
chunks = text_splitter.split_documents(documents)
# Qdrant 벡터 스토어 생성
embeddings = OpenAIEmbeddings()
vectorstore = Qdrant.from_documents(
chunks,
embeddings,
url="http://localhost:6333",
collection_name="knowledge_base"
)
# 검색
query = "AI 윤리에 대해 알려줘"
docs = vectorstore.similarity_search(query, k=3)
for doc in docs:
print(doc.page_content)
비용: ~$50/월 (Digital Ocean Droplet + Qdrant)
요구사항:
추천: Milvus (자체 호스팅) 또는 Pinecone (관리형)
# Milvus를 사용한 상품 추천
from pymilvus import Collection, connections
import numpy as np
connections.connect("default", host="localhost", port="19530")
collection = Collection("products")
def recommend_products(user_embedding, filters=None):
"""사용자 임베딩 기반 상품 추천"""
# 검색 파라미터
search_params = {
"metric_type": "IP", # Inner Product
"params": {"nprobe": 16}
}
# 필터 표현식
expr = None
if filters:
conditions = []
if filters.get('category'):
conditions.append(f"category == '{filters['category']}'")
if filters.get('min_price'):
conditions.append(f"price >= {filters['min_price']}")
if filters.get('max_price'):
conditions.append(f"price <= {filters['max_price']}")
expr = " && ".join(conditions) if conditions else None
# 검색
results = collection.search(
data=[user_embedding],
anns_field="embedding",
param=search_params,
limit=20,
expr=expr,
output_fields=["product_id", "name", "price", "category"]
)
return results
# 사용 예시
user_vec = np.random.rand(768).tolist()
recommendations = recommend_products(
user_vec,
filters={
'category': '전자제품',
'min_price': 100000,
'max_price': 1000000
}
)
for hit in recommendations[0]:
print(f"상품: {hit.entity.get('name')}, 가격: {hit.entity.get('price')}")
요구사항:
추천: Milvus (온프레미스) 또는 Weaviate (클라우드)
# 멀티테넌트 문서 검색 (Weaviate)
import weaviate
client = weaviate.Client("http://localhost:8080")
# 테넌트별 스키마
schema = {
"classes": [{
"class": "Document",
"multiTenancyConfig": {"enabled": True},
"properties": [
{"name": "title", "dataType": ["text"]},
{"name": "content", "dataType": ["text"]},
{"name": "department", "dataType": ["text"]},
]
}]
}
# 테넌트 생성
client.schema.create_class(schema["classes"][0])
client.schema.add_tenant(
class_name="Document",
tenants=[
{"name": "company_a"},
{"name": "company_b"},
]
)
# 테넌트별 데이터 삽입
client.data_object.create(
class_name="Document",
data_object={
"title": "기밀 문서",
"content": "회사 기밀 정보...",
"department": "R&D"
},
tenant="company_a"
)
# 테넌트별 검색
result = (
client.query
.get("Document", ["title", "content"])
.with_tenant("company_a")
.with_near_text({"concepts": ["연구 개발"]})
.with_limit(10)
.do()
)
벡터 DB 선택하기:
1. 관리형 서비스 선호?
YES → Pinecone (간편) 또는 Weaviate Cloud (기능)
NO → 2번으로
2. 데이터 규모는?
< 1M 벡터 → ChromaDB (프로토타입) 또는 Qdrant (프로덕션)
1M - 100M → Qdrant 또는 Weaviate
> 100M → Milvus
3. 예산은?
제한적 → Qdrant (자체 호스팅)
여유 있음 → Pinecone (관리형)
4. 기술 팀 규모는?
1-2명 → Pinecone 또는 Qdrant
3-5명 → Weaviate 또는 Qdrant
> 5명 → Milvus
5. 특수 요구사항
GraphQL 필요 → Weaviate
GPU 활용 → Milvus
Rust 성능 → Qdrant
간편함 최우선 → Pinecone
| 기능 | Pinecone | Qdrant | Weaviate | Milvus | ChromaDB |
|---|---|---|---|---|---|
| 오픈소스 | ❌ | ✅ | ✅ | ✅ | ✅ |
| 관리형 서비스 | ✅ | ✅ | ✅ | ✅ | ❌ |
| 하이브리드 검색 | ✅ | ✅ | ✅ | ✅ | ❌ |
| 필터링 | 기본 | 고급 | 고급 | 고급 | 기본 |
| 멀티테넌시 | ✅ | ✅ | ✅ | ✅ | ❌ |
| GPU 지원 | ❌ | ❌ | ❌ | ✅ | ❌ |
| 자동 임베딩 | ❌ | ❌ | ✅ | ❌ | ✅ |
| 스케일 아웃 | ✅ | ✅ | ✅ | ✅ | ❌ |
벡터 DB 간 마이그레이션:
import concurrent.futures
from typing import Iterator, List, Tuple
class VectorDBMigrator:
"""벡터 데이터베이스 간 마이그레이션 도구"""
def __init__(self, source_db, target_db, batch_size=100):
self.source_db = source_db
self.target_db = target_db
self.batch_size = batch_size
def migrate(self, collection_name: str):
"""전체 컬렉션 마이그레이션"""
print(f"Starting migration of {collection_name}")
# 1. 스키마 생성
schema = self.source_db.get_schema(collection_name)
self.target_db.create_collection(collection_name, schema)
# 2. 데이터 스트리밍
total_migrated = 0
for batch in self.stream_vectors(collection_name):
self.target_db.insert(collection_name, batch)
total_migrated += len(batch)
print(f"Migrated {total_migrated} vectors")
print(f"Migration completed: {total_migrated} vectors")
def stream_vectors(self, collection_name: str) -> Iterator[List]:
"""벡터를 배치로 스트리밍"""
offset = 0
while True:
batch = self.source_db.fetch(
collection_name,
limit=self.batch_size,
offset=offset
)
if not batch:
break
yield batch
offset += self.batch_size
# 사용 예시: Pinecone → Qdrant
from qdrant_client import QdrantClient
import pinecone
# Source: Pinecone
pinecone.init(api_key="xxx", environment="us-west1-gcp")
pinecone_index = pinecone.Index("source-index")
# Target: Qdrant
qdrant_client = QdrantClient(host="localhost", port=6333)
# 마이그레이션
migrator = VectorDBMigrator(
source_db=pinecone_index,
target_db=qdrant_client,
batch_size=1000
)
migrator.migrate("my_collection")
import time
import statistics
from dataclasses import dataclass
from typing import List
@dataclass
class QueryMetrics:
latency: float
throughput: float
memory_usage: float
class VectorDBMonitor:
"""벡터 DB 성능 모니터링"""
def __init__(self, db_client):
self.client = db_client
self.metrics_history = []
def benchmark_search(
self,
queries: List,
iterations: int = 100
) -> dict:
"""검색 성능 벤치마크"""
latencies = []
for _ in range(iterations):
for query in queries:
start = time.time()
results = self.client.search(query)
latency = time.time() - start
latencies.append(latency)
return {
'avg_latency': statistics.mean(latencies),
'p50_latency': statistics.median(latencies),
'p95_latency': self.percentile(latencies, 95),
'p99_latency': self.percentile(latencies, 99),
'qps': 1 / statistics.mean(latencies)
}
@staticmethod
def percentile(data: List[float], percentile: int) -> float:
"""백분위수 계산"""
sorted_data = sorted(data)
index = int(len(sorted_data) * percentile / 100)
return sorted_data[index]
# 사용 예시
monitor = VectorDBMonitor(qdrant_client)
test_queries = [
[0.1, 0.2, 0.3, ...],
[0.4, 0.5, 0.6, ...],
# ...
]
metrics = monitor.benchmark_search(test_queries)
print(f"P95 Latency: {metrics['p95_latency']:.2f}ms")
print(f"QPS: {metrics['qps']:.0f}")
벡터 데이터베이스 선택은 프로젝트의 성공을 좌우합니다.
핵심 요약:
시작하기:
# 1주일 평가 계획
Day 1-2: ChromaDB로 프로토타입
Day 3-4: Qdrant 로컬 테스트
Day 5-6: Pinecone 프리 티어 평가
Day 7: 최종 결정 및 마이그레이션
추가 고려사항:
올바른 벡터 데이터베이스를 선택하여 AI 애플리케이션의 성능을 극대화하세요!