기간 : 11/3 ~ 11/7
Vector DB + LangChain 실습
이번주에 배운 벡터 DB와 랭체인은 글로 정리하기보다 그냥 직접 활용해보는게 좋을 것 같아서 간단한 토이 프로젝트를 진행해봤다.
주제 선정
1. 벡터 DB는 유사도 검색에 특화되어 있다.
→ 추천 or 유사 컨텐츠 생성
2. 언어 관련 컨텐츠를 다루자
→ 외국어 학습?
3. 유사도 추천을 외국어에 어떻게 적용하지
4. 여기에 랭체인은 어떤 역할을 해야 하는가?
그래서 정해졌다
Zawa Zawa
검색어 기반 일본어 문장 생성기
https://github.com/WindyAle/ZawaZawa
GitHub - WindyAle/ZawaZawa: 일본어 학습자를 위한 예문 생성기
일본어 학습자를 위한 예문 생성기. Contribute to WindyAle/ZawaZawa development by creating an account on GitHub.
github.com
Zawa Zawa(ざわざわ) : 말소리를 표현하는 일본어 의성어 ex) 웅성웅성, 속닥속닥
1. 사용자가 공부하고자 하는 특정 주제의 키워드 or 문장을 입력
2. 검색어 기반 유사한 일본어 예문을 Chroma DB에서 검색
3. 생성된 예문의 유사 표현, 뉘앙스 설명 등을 LangChain을 활용한 생성으로 첨부
https://tatoeba.org/ko/downloads
타토에바 : 데이터를 다루는 사람들을 위한 일본어 텍스트집 제공 사이트
# Chroma가 내부적으로 사용할 임베딩 함수(모델) 정의
embed_func = embedding_functions.SentenceTransformerEmbeddingFunction(
model_name=MODEL_NAME
)
# 영구 클라이언트 생성
client = chromadb.PersistentClient(path=DB_PATH)
# 컬렉션(테이블) 생성
# embedding_function을 지정하면 add할 때 자동으로 문장을 벡터로 변환해줌
collection = client.get_or_create_collection(
name=COLLECTION_NAME,
embedding_function=embed_func
)
chromadb.utils의 embedding_functions를 통해 Chroma에서 사용할 임베딩 모델을 정의할 수 있다.
콜렉션 생성 시 모델과 함께 임베딩 모델을 파라미터로 넣어주면 DB에 add하면서 자동으로 벡터화를 진행한다.
# documents에 원본 텍스트를 추가
# Chroma가 내부적으로 embed_func를 호출해 벡터로 변환 후 저장
collection.add(
documents=batch_sentences,
ids=batch_ids
)
이 코드를 통해 documents에 벡터화를 거친 sentences들이 저장된다.

UI는 Streamlit으로 구현
개인 실습용이므로 아주 단순하게 진행하되 실제로 누군가가 사용한다는 느낌으로 구성

gTTS를 적용해 음성으로 들을 수 있는 기능을 추가했다.
문제 상황
단일 키워드보단 어느 정도 길이가 되는 문장을 검색해야 유사성 높은 결과를 뽑아준다.
데이터 안에 인삿말이나 감탄사가 꽤 많이 들어가있는데
이런 범용성 높은 표현들이 대체로 상위 유사도를 차지하기 때문에
자꾸 검색어 매칭 시 튀어나오는 것으로 추정된다.
1차적으로 5글자 이하의 짧은 문장을 제거해봤지만
이 부분에 대한 정확한 처리방법은 좀 더 고민해봐야겠다.
DB 선택의 이유
널리 쓰이는 FAISS, Chroma, Pinecone 중에 Chroma로 선정했다.
FAISS
- DB에 벡터 인덱스만 저장되므로, 문장과 매칭할 때 원본 텍스트 파일을 요구한다.
- DB에서 ID를 찾고 이 ID로 다시 CSV를 조회해야 하는 이중 파이프라인
- IndexFlatL2 사용 시 완전 탐색을 하므로 검색 속도가 느리다.
Chroma
- 텍스트와 벡터를 함께 저장
- DB 생성 과정이 느리지만, 서비스 단계에서 탐색이 빠름
- DB의 용량이 커지지만 로컬에서 진행하는 소규모 프로젝트에 적합
- LangChain과 간단하게 연계 가능
- 모든 문장을 비교하는게 아닌, '적당히 가까운 문장'에 대한 지도를 생성하는 근사 이웃 탐색(ANN)
Pinecone
- Chroma와 유사하지만 API 키가 필요하고 클라우드 형태로 제공
- 대규모 상용 목적에 적합하므로 토이 프로젝트에서는 너무 크다고 판단
LLM을 활용한 게임 제작
https://github.com/WindyAle/Welcome-to-my
GitHub - WindyAle/Welcome-to-my
Contribute to WindyAle/Welcome-to-my development by creating an account on GitHub.
github.com
주중에 과제로 주어진 개인 프로젝트
약 일주일의 기간 동안, 인생 첫 게임 제작을 경험했다.
LLM에게 단순 텍스트를 주는 것 외에 뭔가 재미는걸 시켜볼 수 없을까 하다가
'공간'을 묘사하는 LLM이라는 아이디어를 떠올렸다.
1. 플레이어는 인테리어 전문가다.
2. 고객이 자신이 원하는 디자인을 최대한 모호하게 말한다.
3. 플레이어가 가구를 배치한다.
4. 배치된 가구를 판단하여 고객이 평점을 준다.
어릴 때 재밌게 하던 심즈 시리즈에서 영감을 받아,
플레이어가 배치한 가구의 형태, 위치와 같은 공간 정보를 LLM이 판단하도록 하는 게임이다.
게임 제작이 생각보다 정말 어려웠지만 또 내가 의도한대로 화면이 만들어지니 꽤 재밌는 경험이었다.
게임에 대한 정보는 GitHub에 있으니 여기에는 프로젝트에서 사용한 프롬프팅 기법을 기록해둔다.
1. 백파이어(Backfire)
"고양이를 상상하지 마세요"라고 한다면, 이 말을 들은 사람들의 머리 속에는 이미 고양이가 있을 것이다.
AI 용어는 아니지만 나는 이게 LLM의 동작 원리와 부합한다고 생각했다.
LLM은 제공된 정보를 어떻게든 응답에 참조한다.
system_prompt = (
f"당신은 고객 '{selected_persona['name']}'입니다. 당신의 상세 정보는 다음과 같습니다:\n"
f"- 취향: {selected_persona['taste']}\n"
f"- 성향: {selected_persona['tendency']}\n"
f"- 말투: {selected_persona['tone']}\n\n"
"당신은 지금부터 디자이너에게 방을 의뢰할 것입니다. 다음 3단계에 따라 행동하세요.\n\n"
"1. [내면의 생각] : 당신의 취향과 성향에 따라, 다음 가구 목록에서 '반드시 있었으면 하는' 가구 **3~5개**를 **마음 속으로** 정합니다. 이것은 당신의 '비밀 요구사항(Wishlist)'입니다.\n"
f"<가구 목록>: {FURNITURE_NAMES_LIST}\n\n"
"2. [디자이너에게 할 말] : 1번에서 고른 가구의 **이름을 절대 직접 말하지 마세요.** 대신, 그 가구들이 왜 필요한지 '목적'이나 '행위'를 암시하는 방식으로 **매우 모호하게** 1-2 문장으로 묘사합니다.\n"
" (예: '소파' -> '편안히 기댈 곳이 필요해요.')\n"
" (예: '스토브', '냉장고' -> '집에서 요리하는 것을 좋아합니다.')\n"
" (예: '컴퓨터', '책장' -> '밤에 조용히 작업할 공간이 필요해요.')\n\n"
"3. [출력 형식] : 다른 말은 절대 하지 말고, 오직 다음 형식으로만 응답하세요:\n"
"[WISHLIST]\n"
"(여기에 1번에서 고른 가구 이름들을 쉼표로 구분하여 작성)\n"
"[REQUEST]\n"
"(여기에 2번에서 생성한 모호한 의뢰서 내용을 작성)"
)
'고객이 원하는 가구'를 직접 말하면 안되므로, 내부적으로는 정답을 정해놓고 그것을 빙빙 돌려서 말하도록 유도했다.
2. LLM as a Judge
처음에는 평점 계산 메커니즘으로 코사인 유사도를 사용했는데, 이상하게 모든 경우에 4~5점의 고득점이 나왔다.
알고보니 소파와 침대 같은 어휘들이 높은 유사도를 가지기 때문에,
만약 고객이 소파를 원했는데 소파를 놓지 않고 침대를 놓은 상황에도 평점이 높아진 것이다.
소파가 없으면 확실하게 소파가 없는 것에 대한 패널티를 부여해야 하는데 말이다.
system_prompt = (
f"당신은 고객 '{persona['name']}'입니다. "
f"당신의 성격과 말투는 다음과 같습니다: {persona['tendency']}\n"
"당신은 방금 디자이너의 작업에 점수를 매겼습니다. "
"당신의 성격과 말투에 100% 몰입하여, '왜' 그 점수를 주었는지 1-2문장의 구체적인 피드백을 한글로 작성하세요. "
"점수가 높으면 당신의 방식대로 칭찬하고, 낮으면 당신의 방식대로 비판하세요."
)
LLM을 평가하는 LLM이라는 개념의 LLM as a Judge라는게 있는 것을 알고 적용해봤다.
이렇게 하는게 맞는지는 잘 모르겠지만, 코사인 유사도를 사용하던 때보다 좀 더 다양하고 풍부한 피드백이 나온 것은 사실이다.
EEVE라는 한국어 특화 모델을 사용했는데,
평소 사용하는 GPT 같은 고급 모델에 비해 현저히 떨어지는 성능을 프롬프팅하는게 상당히 어려웠다.
하지만 그렇기에 재밌었고 유효한 공부가 되었다.
'SKN 19기' 카테고리의 다른 글
| [플레이데이터 SK네트웍스 Family AI 캠프 19기] 18주차 회고 (0) | 2026.01.04 |
|---|---|
| [플레이데이터 SK네트웍스 Family AI 캠프 19기] 17주차 회고 (0) | 2026.01.03 |
| [플레이데이터 SK네트웍스 Family AI 캠프 19기] 10주차 회고 (0) | 2025.11.01 |
| [플레이데이터 SK네트웍스 Family AI 캠프 19기] 9주차 회고 (0) | 2025.10.26 |
| [플레이데이터 SK네트웍스 Family AI 캠프 19기] 8주차 회고 (0) | 2025.10.18 |