본문으로 건너뛰기

© 2026 Molayo

Qiita헤드라인2026. 06. 15. 18:33

【완전 로컬】 AI에게 기억을 갖게 하는 5단계 — Ollama×RAG로 만드는 장기 기억 챗봇

요약

Ollama와 ChromaDB를 활용하여 로컬 환경에서 작동하는 장기 기억 챗봇을 구현하는 5단계 가이드를 제공합니다. 단순 대화 이력 전달의 한계를 극복하기 위해 RAG 기술을 적용하여 효율적인 컨텍스트 관리를 실현하는 방법을 다룹니다.

핵심 포인트

  • Ollama와 ChromaDB를 이용한 완전 로컬/무료 환경 구축
  • RAG를 통한 토큰 절약 및 컨텍스트 윈도우 최적화
  • 중요 사실 추출 및 중복 탐지를 통한 기억의 질 향상
  • 쿼리 리팩토링을 통한 문맥 의존적 질문 해결 방법

안녕하세요. 개발부의 Ame입니다.

이번에는 로컬 환경에서 동작하는 「장기 기억이 있는 챗봇」을 제로부터 구현하는 방법을 소개합니다.

처음에는 심플하게 「모든 대화 이력을 그대로 LLM에 전달하는」 소박한 구현부터 시작하여, 그 문제점을 실제로 확인하면서 RAG (Retrieval-Augmented Generation)와 ChromaDB를 사용하여 단계적으로 개선해 나갑니다. 최종적으로는 대화가 아무리 길어져도 관련 정보만을 정확하게 끌어낼 수 있는 실용적인 챗봇이 완성됩니다.

사용하는 도구는 모두 완전 로컬·무료입니다. API 키는 전혀 필요하지 않습니다.

LLM을 사용한 챗봇을 만들 때, 많은 사람이 처음에 다음과 같이 구현합니다.

messages = []
while True:
    user_input = input("You: ")
    ...

심플하고 직관적이지만, 여기에는 큰 문제가 있습니다.

대화가 길어질수록 매번 모든 이력을 LLM에 전달하게 된다.

그 결과로 발생하는 현상:

토큰 상한에 도달 — LLM은 처리할 수 있는 텍스트 양에 상한이 있음 -
비용 증가 — API를 사용하는 경우, 토큰 수 = 요금 -
정확도 저하 — 컨텍스트(Context)가 너무 길면 LLM은 중요한 정보를 놓침 (Lost in the Middle 문제) -
응답 속도 저하 — 처리할 토큰이 많을수록 추론 시간이 증가

RAG의 핵심적인 아이디어는 심플합니다.

「전부 전달하는 것이 아니라, 지금의 질문과 관계있는 정보만을 추출하여 전달한다」

대화 이력을 벡터 데이터베이스(Vector Database)에 저장해 두고, 새로운 질문이 왔을 때 「의미적으로 가까운 것」만을 검색하여 추출합니다. 이를 통해 컨텍스트 윈도우(Context Window)를 최소한으로 유지하면서 관련 정보를 정확하게 참조할 수 있습니다.

이 기사에서는 소박한 구현에서 출발하여, 다음 5단계로 조금씩 「실용적인 장기 기억」에 다가갑니다.

소박한 구현 — 모든 이력을 전달 (문제의 출발점) -
기본적인 RAG — 관련 있는 이력만을 추출 -
기억 추출 — 생(Raw) 로그가 아닌 「중요한 사실」만을 저장 -
3.5 중복 탐지 — 저장 전에 의미적으로 동일한 사실이 이미 있는지 확인

쿼리 리팩토링 (Query Refactoring) — 「그것」, 「저것」 등 문맥 의존적인 쿼리를 해결 -
기억의 갱신 — 오래된 정보와 새로운 정보를 타임스탬프로 구분 (간이 버전)

역할도구설명
LLM 실행Ollama + llama3로컬에서 LLM을 구동
임베딩 생성nomic-embed-text텍스트를 벡터로 변환
벡터 DBChromaDB벡터의 저장 및 검색
언어Python 3.10+전체를 연결하는 접착제

모두 완전 로컬·무료로 동작합니다. API 키는 필요 없습니다.

ollama.com 에서 인스톨러를 다운로드하거나, 다음을 실행합니다.

macOS / Linux:

curl -fsSL https://ollama.com/install.sh | sh

Windows:

공식 사이트에서 인스톨러를 다운로드.

설치 후, 다음으로 모델을 다운로드합니다:

# 채팅용 LLM
ollama pull llama3
# 임베딩용 모델
...

Ollama가 기동 중인지 확인:

ollama list
# NAME ID SIZE MODIFIED
# llama3:latest ... 4.7 GB ...
...

Python 3.10 이상이 필요합니다. 먼저 버전을 확인해 주세요:

python --version
# Python 3.11.x 등 3.10 이상이면 OK

설치되어 있지 않거나 버전이 오래된 경우:

macOS (Homebrew를 사용하는 경우):

brew install python@3.11

Windows / Linux:

python.org 에서 최신 버전을 다운로드하여 설치.

💡 Tips: 버전 관리 도구인 pyenv를 사용하면 여러 버전을 전환하기 편리합니다.

가상 환경을 만든 후 설치하는 것을 권장합니다:

# 가상 환경 생성 및 활성화
python -m venv rag-env
source rag-env/bin/activate # Windows: rag-env\Scripts\activate
...

requests는 Ollama의 REST API를 호출하기 위해 사용합니다.

import requests
def chat_with_ollama(messages: list[dict]) -> str:
response = requests.post(
...

먼저 '일반적인' 구현을 만듭니다. 이것이 나중에 비교의 기준이 됩니다.

import requests
def chat_with_ollama(messages: list[dict]) -> str:
response = requests.post(
...

테스트 결과:

You: 제 이름은 다나카 타로입니다. 도쿄에 거주하는 엔지니어입니다.
AI: 처음 뵙겠습니다, 다나카 타로님! 도쿄의 엔지니어시군요.
[현재 이력: 2개 메시지, 추정 30토큰]
...

왜 전체 이력을 전달하는 방식이 좋지 않은지 구체적으로 살펴보겠습니다.

문제 1: 토큰 수의 폭발

def analyze_token_growth():
"""대화가 길어질수록 토큰 수가 어떻게 증가하는지 시뮬레이션"""
# 각 메시지가 평균 200토큰이라고 가정
...
대화 수 | 전체 이력 토큰 수 | RAG 토큰 수 (상위 3개)
-------------------------------------------------------
5건 | 1,000 | 600
...

RAG를 사용하면 대화가 아무리 길어져도 컨텍스트 크기 (Context Size)는 일정하게 유지할 수 있습니다.

문제 2: Lost in the Middle

연구에 따르면, LLM은 긴 컨텍스트의 처음과 끝에 있는 정보는 기억하지만, 중간에 있는 정보는 놓치기 쉽다는 것이 알려져 있습니다.

100번의 대화 속에 파묻힌 "제 이름은 다나카 타로입니다"라는 정보를 LLM이 정확하게 참조할 수 있다는 보장이 없습니다.

그럼, ChromaDB를 사용하여 "관련 있는 대화만 추출하는" 메커니즘을 만듭니다.

먼저, 기본적인 구조를 이해해 봅시다:

새로운 질문
↓
임베딩 벡터 (Embedding Vector)로 변환
...

여기서 설계상의 포인트가 하나 있습니다. 추출한 과거의 대화는 role이 붙은 대화 턴(turn)으로 삽입하지 않고, 시스템 프롬프트의 "참고 정보"로서 전달합니다. 검색으로 추출한 파편은 본래 흩어져 있는 기록이며, 이를 assistant / user 턴으로 다시 나열하면 전후 관계가 깨진 부자연스러운 대화(공중에 뜬 assistant 발언 등)가 되어 모델이 혼란을 겪기 때문입니다.

import requests
import chromadb
from datetime import datetime
...

이로써 "전부 전달하기" 방식에서는 벗어났지만, 아직 두 가지 문제가 남아 있습니다.

  • 저장하고 있는 것이 생(raw) 로그 그 자체임 — LLM의 답변은 길고 장황하며, 인사나 맞장구 같은 "기억할 필요 없는 정보"도 전부 저장됩니다. 검색 시 상위 N개를 추출할 때 이러한 노이즈가 귀중한 용량을 차지하며, 임베딩(Embedding)도 요점이 흐려져 정확도가 떨어집니다.
  • 문맥 의존적인 쿼리에 취약함 — "그것에 대해 더 알려줘"에서 "그것"이 무엇을 가리키는지 모르는 상태로 검색하게 됩니다.

이 두 가지를 다음 2단계에 걸쳐 순차적으로 해결하겠습니다. 우선 저장하는 내용을 개선해 봅시다.

생 로그를 그대로 쌓는 것이 아니라, LLM 스스로가 "나중에 기억할 가치가 있는 사실"만을 추출하도록 하여 저장합니다. 이는 **메모리 추출 (Memory Extraction / Fact Extraction)**이라고 불리는 기법으로, Mem0, Letta (MemGPT), Zep 등 실용적인 메모리 시스템의 핵심이 되는 개념입니다. 생 대화가 아닌, 거기서 추출한 "사실 (Memory)"를 저장하고 검색함으로써 토큰량을 크게 줄일 수 있다고 보고되었습니다 (Mem0는 단순한 전체 전달 방식에 비해 약 90%의 토큰 절감 효과를 보고했습니다).

이미지는 다음과 같습니다:

You: 제 이름은 다나카 타로입니다. 도쿄에 거주하는 엔지니어입니다.
↓ LLM이 사실만 추출
저장되는 기억:
...

이 방식의 장점은 단계 ②에서 고민했던 "사용자 발언과 AI 발언 중 무엇을 저장할 것인가"라는 문제가 자연스럽게 해결된다는 점입니다. 추출기(Extractor)는 양쪽 모두를 읽으며, 어느 쪽의 발언이든 "사실"이라면 남기고 그렇지 않으면 버립니다.

import requests
import chromadb
import json
...

각 "사실"이 짧고 단일 토픽(Single Topic)이 되기 때문에, 임베딩 (Embedding)이 날카로워져 검색 정확도가 향상됩니다. 가공되지 않은 장황한 답변을 통째로 임베딩했을 때 발생하는 "벡터가 흐릿해지는" 문제도 해결됩니다.

💡 추출은 반드시 JSON 형식으로 받아야 합니다. "서론은 붙이지 마"라고 지시해도, llama3와 같은 작은 모델은 다음은 중요한 사실입니다:

와 같은 서론을 반환하는 경향이 있습니다. 출력을 행 단위로 분할하여 저장하면, 이 서론이 그대로 "사실"로서 저장되어 버립니다. 따라서 Ollama의 format="json"을 사용하여 출력을 {"facts": [...]} 형식으로 강제하고, facts 배열만 읽어옵니다. 프롬프트(Prompt) 지시에 의존하는 대신 구조로 제약하기 때문에 서론이 혼입되지 않습니다.

💡 관련도 임계값(Relevance Threshold)을 설정하세요. collection.query는 반드시 "상위 N개"를 반환하므로, 관계가 희박한 기억이라도 빈 자리가 있으면 섞여 들어옵니다. 무관한 기억을 전달하면 모델이 혼란을 겪으므로, RELEVANCE_THRESHOLD (유사도 = 1 - distance)로 걸러내어 충분히 가까운 기억만 전달합니다. 임계값이 높을수록 엄격해집니다 (즉, 전달되는 건수가 줄어듭니다). 최적값은 임베딩 모델에 따라 달라지므로, 실제 distance를 확인하며 조정하십시오. 본 기사에서는 0.6을 기본값으로 설정했습니다.

⚠️ 트레이드오프(Trade-off): 추출을 위해 매 턴마다 LLM 호출이 1회 증가합니다 (로컬이므로 비용이 아닌 추론 시간의 비용입니다). 또한, 추출은 본질적으로 손실이 있는 압축입니다. "방금 한 코드를 그대로 보여줘"와 같은 축어적인 재현은 서투르기 때문에, 그것이 필요하다면 생 로그(Raw Log)도 별도의 레이어에서 유지해야 합니다.

단계 ③에서 저장은 깔끔해졌지만, 아직 "같은 사실을 반복해서 저장하는" 문제가 남아 있습니다. 대화를 계속하다 보면 사용자는 같은 말을 반복합니다 (예: 다른 기회에 다시 "저는 엔지니어입니다"라고 말하는 등). 매번 그대로 저장하면 같은 의미의 기억이 증식하여 검색 공간을 낭비하게 됩니다. 따라서 저장하기 전에, **"이것과 비슷한 것이 이미 DB에 있는가?"를 체크하여 충분히 비슷하다면 스킵(Skip)**합니다.

하지만 여기에는 중대한 함정이 있습니다.

⚠️ 임베딩 유사도만으로 판단해서는 안 됩니다.

"고양이를 좋아함"과 "고양이를 좋아하지 않음"은 임베딩 상으로는 매우 가깝지만 (거의 동일한 단어를 공유하고 주제도 같음), 의미는 정반대입니다. 유사도만으로 "중복"이라고 판단하면, 좋아하지 않음이라는 중요한 변화를 "같으니까 스킵"해 버려 정보가 손실됩니다.

즉, 필요한 것은 2단계 전략입니다. 임베딩 유사도로 "후보"를 좁히고 (재현율, Recall), 최종적으로 "정말로 같은 의미인가"는 LLM이 판단하게 합니다 (정밀도, Precision). 이렇게 하면 부정, 반대, 변화는 제대로 "별개의 것"으로 저장됩니다.

새로운 사실
↓
임베딩으로 가까운 기존 기억을 검색
...
# cosine space에서는 distance = 1 - 유사도. 유사도가 이 값 이상의 후보만 LLM으로 확인한다.
# ※ 임계값은 임베딩 모델에 따라 최적값이 다르다. 실제 distance를 보고 조정할 것.
SIMILARITY_THRESHOLD = 0.85
...

save_memories에 통합합니다. 임베딩은 중복 체크와 저장에서 재사용하여 계산의 중복을 피합니다.

def save_memories(facts: list[str], turn_id: int) -> list[str]:
    saved = []
    for i, fact in enumerate(facts):
        ...

동작 이미지:

(기존 기억) 사용자는 고양이를 좋아함
📝 추출 → 저장:
🔁 스킵 (중복): 사용자는 고양이를 매우 좋아함 # 유사도 높음 / LLM 판정 SAME
...

메모: 이것은 "중복을 저장하지 않는" 메커니즘일 뿐, 오래된 "고양이를 좋아함"을 삭제하는 것이 아닙니다. 신구(新舊)의 모순을 어떻게 다룰지는 단계 ⑤ (타임스탬프로 신구 구분)의 담당입니다. 이후 단계 ④·⑤의 save_memories도 이 중복 체크를 포함한 것으로 간주합니다.

저장 내용은 깔끔해졌지만, 검색 쿼리(Search Query) 자체는 여전히 사용자의 가공되지 않은 입력값 그대로입니다. "그것을 FastAPI에서 사용하는 방법은?"과 같이 문맥에 의존적인 질문의 경우, "그것"을 임베딩(Embedding)하더라도 과거의 기억과 제대로 매칭되지 않습니다. 다음 단계에서 검색 쿼리 측면을 개선하겠습니다.

해결책은 LLM 스스로 검색 쿼리를 다시 쓰게 하는 것입니다.

사용자의 질문을 문맥이 보완된 "자기 완결적 쿼리(Self-contained query)"로 변환한 뒤 검색합니다.

"그것을 FastAPI에서 사용하는 방법은?"
↓ LLM이 리팩터링 (Refactoring)
"Python의 FastAPI에서 타입 힌트(Type Hint)를 사용하는 방법"
...

단계 ③의 기억 추출에 이 "쿼리 재작성"을 추가한 것이 다음과 같습니다.

import requests
import chromadb
import json
...
You: Python의 타입 힌트에 대해 알려줘
AI: Python의 타입 힌트는 변수나 함수의 인자(Argument)·반환값(Return Value)에 타입 정보를 부여하는 기능입니다...
📝 추출 → 저장:
...

Before/After를 나란히 배치함으로써, LLM이 대명사를 어떻게 해석하여 쿼리를 보완하는지 한눈에 알 수 있습니다. 쿼리가 적절하게 변환됨으로써, 먼 과거의 정보도 정확하게 끌어올 수 있게 되었습니다.

💡 재작성할 쿼리의 언어를 고정하세요. llama3는 영어 중심의 모델이므로, "검색 쿼리로 다시 써줘"라고만 요청하면 일본어 질문이라도 "dog dislike"와 같이 영어로 번역하여 반환할 때가 있습니다. 기억은 일본어로 저장되어 있기 때문에, 영어 쿼리로 검색하면 유사도(Similarity)가 크게 떨어져 공들인 리팩터링이 역효과를 냅니다. 대책으로서 프롬프트에 "사용자의 질문과 같은 언어로 작성할 것(영어로 번역하지 말 것)", "따옴표로 감싸지 말 것"을 명시하고, 출력 전후의 기호도 strip으로 제거하고 있습니다. 다국어에 강한 임베딩 모델(bge-m3 등)로 교체하면 언어 불일치의 영향은 완화되지만, 우선은 출력 언어를 고정하는 것이 확실합니다.

⚠️ 미리 말씀드리자면, 이 단계는 "제대로 된 기억의 업데이트"가 아니라 어디까지나 간단한 트릭입니다. 본격적인 기억 업데이트(모순 탐지·오래된 정보 덮어쓰기·삭제)는 그 자체로 큰 주제이며, 후술할 "다음 단계"로 다룹니다. 여기서 소개하는 것은 최소한의 노력으로 "오래된 정보"와 "현재의 정보"를 어느 정도 구분하는 방법입니다.

지금까지의 구현에는 미묘하지만 까다로운 문제가 남아 있습니다. 사실이 계속 추가되기만 할 뿐, 오래된 사실이 계속 남아 있다는 것입니다.

(과거의 기억) 사용자는 도쿄 거주
You: 지난달에 오사카로 이사했어
↓ 추출
...

한 가지 생각은 "오래된 사실을 삭제 또는 덮어쓰기"하는 것이지만, 이는 모순 탐지가 필요하며 삭제를 잘못하면 정보가 유실됩니다.

더 단순하면서도 과거의 정보를 버리지 않아도 되는 방법이 있습니다. 모든 사실을 남겨둔 채, 검색 결과를 타임스탬프 순(최신순)으로 정렬하여 "새로운 것이 현재의 사실"이라고 모델에게 전달하는 것뿐입니다.

기억 (최신순 제시):
- (2026-05-01) 사용자는 오사카 거주 ← 현재
- (2025-01-01) 사용자는 도쿄 거주 ← 과거
...

이 방식에는 기분 좋은 보너스가 있습니다. 오래된 사실을 지우지 않기 때문에, "전에는 어디에 살았어?"와 같은 과거에 대한 질문에도 답할 수 있습니다 (삭제 방식으로는 이것이 불가능합니다).

변경 사항은 미미합니다. retrieve_relevant_memories를 타임스탬프와 함께 반환하고 최신순으로 정렬하기만 하면 됩니다. 단계 ④의 다른 부분은 그대로 두어도 무방합니다.

def retrieve_relevant_memories(query: str, n_results: int = 5) -> list[dict]:
    if collection.count() == 0:
        return []
    ...

그리고 시스템 프롬프트에서는 날짜를 덧붙여 "새로운 것이 현재"라는 규칙을 명시합니다 (순서만 주는 힌트는 작은 모델에게는 약하므로, 날짜를 보여주는 것이 더 효과적입니다).

context = "\n".join(
    f"- ({m['timestamp'][:10]}) {m['fact']}" for m in memories
) if memories else "(아직 기억이 없습니다)"
...
  • 검색에서 신구(新舊) 정보가 모두 검색되지 않으면 의미가 없습니다. "지금 어디 살아?"라는 질문에 "오사카 거주"만 가져올 수 있다면, 정렬을 하더라도 비교할 수 없습니다 (동일한 속성이므로 둘 다 검색되기 쉽지만, 보장되지는 않습니다. n_results를 넉넉하게 설정해야 합니다).
  • 판단을 모델에 전적으로 맡기고 있습니다. "새로운 것 = 현재"라는 규칙을 llama3가 항상 지켜준다는 보장이 없으며, "오사카와 도쿄 둘 다 거주 중"과 같이 혼동할 수도 있습니다. 날짜를 제시하면 개선되겠지만, 본인의 모델로 반드시 테스트를 거쳐야 합니다.
  • 기억이 계속 쌓입니다. 이사를 5번 하면 주소에 관한 사실이 5건 남습니다. n_results로 상한선을 두면 실용적인 측면에서는 큰 문제가 되지 않겠지만, 오래된 정보의 비율은 조금씩 늘어납니다.

"트릭"을 넘어 진지하게 업데이트를 다루려면, 새로운 사실이 발생할 때마다 기존의 유사한 기억을 불러와 LLM이 ADD / UPDATE / DELETE / NOOP 중 하나를 선택하게 하는 방식을 사용해야 합니다 (Mem0가 채택하고 있는 "Update 단계"가 이것입니다). 더 나아가면, Zep처럼 **시간적 유효 기간을 가진 지식 그래프 (Knowledge Graph)**를 통해 "언제부터 언제까지 사실이었는지"를 관리합니다. 두 방식 모두 구현이 무거워지므로, 이 글에서는 간이 버전에 머물고 별도의 글로 다룰 예정입니다.

이 글에서 구현한 내용을 되돌아봅니다:

접근 방식토큰 수검색 정확도장기 기억모순·업데이트
① 전체 이력 전달대화 수에 비례하여 증가중 (Lost in the Middle)× 상한 있음×
...

구현 포인트:

  • ChromaDB로 기억을 벡터 (Vector)로 저장하여 의미적 유사도 검색 (Semantic Search)을 실현
  • nomic-embed-text (Ollama)로 로컬에서 임베딩 (Embedding) 생성
  • **기억 추출 (Memory Extraction)**을 통해 생 로그(Raw Log)가 아닌 "중요한 사실"만 저장하여 노이즈를 줄임
  • **중복 탐지 (Duplicate Detection)**로 저장 전 의미적 중복을 스킵 (부정이나 변화는 별개의 것으로 저장)
  • **쿼리 리팩토링 (Query Refactoring)**으로 "그것", "저것" 등의 대명사 문제 해결
  • 추출된 기억은 대화 턴이 아닌 "참조 정보"로서 시스템 프롬프트 (System Prompt)에 전달
  • **타임스탬프 (Timestamp)**로 오래된 정보와 새로운 정보를 간이적으로 구분

정확도와 실용성을 더 높이고 싶다면 다음 테크닉이 유효합니다:

  • 본격적인 기억 업데이트— ADD/UPDATE/DELETE 또는 Zep과 같은 시간적 지식 그래프로 모순을 해결
  • Re-ranking— 검색 결과를 크로스 인코더 (Cross-Encoder)로 재점수화하여, 정말 관련성이 높은 것만 남김
  • 대화 요약 기억— 오래된 대화를 요약하여 컴팩트하게 유지
  • 하이브리드 검색 (Hybrid Search)— 벡터 검색과 BM25 (키워드 검색)를 조합하여 정확도 향상
  • HyDE— 가상의 답변을 생성한 후 이를 검색 쿼리로 사용

마지막으로, 가장 중요한 전제를 말씀드립니다. 이 글의 고안 사항들(RAG, 기억 추출, 쿼리 리팩토링, 중복 탐지)은 모두 "모델에게 무엇을 보여줄 것인가"를 개선하는 것이지, 모델 자체를 똑똑하게 만드는 것은 아닙.

이 챗봇은 곳곳에서 LLM에 판단을 맡기고 있습니다.

  • 사실 추출— 무엇이 "중요한 사실"인가
  • 쿼리 리팩토링— "그것", "저것"이 무엇을 가리키는가
  • 중복 판정— SAME / DIFFERENT
  • 그리고 무엇보다,
  • 최종적인 답변 생성

이러한 품질은 모두 토대가 되는 LLM의 능력에 달려 있습니다. 즉, 검색이 완벽하게 "올바른 기억"을 가져오더라도 모델이 약하면 다음과 같은 결과가 나타납니다.

  • 추출이 부실하거나 엉뚱한 사실을 집어냄
  • 쿼리 재작성이 빗나감
  • "고양이를 좋아함"과 "고양이를 좋아하지 않음"의 구분을 틀림
  • 문맥은 갖춰졌음에도 답변 자체가 얕거나 엉뚱함

요컨대 Garbage in, garbage out입니다. RAG를 아무리 다듬어도 모델의 천장은 넘을 수 없습니다. 본 글에서는 간편하게 실행할 수 있는 llama3 (8B)를 사용했지만, 더 큰 모델(llama3:70b 등)이나 클라우드 모델(GPT, Claude 등)로 교체하는 것만으로도 위의 LLM 의존 단계들의 정확도가 크게 변합니다. format="json"과 같은 기법은 출력의 안정성을 높여주지만, 지능까지 더해주지는 못합니다.

💡 디버깅 팁: 답변이 만족스럽지 않을 때는 "올바른 기억을 검색하지 못하고 있는 것인가 (RAG 측의 문제)"와 "기억은 맞지만 답변이 나쁜 것인가 (모델 측의 문제)"를 구분하여 생각하면 어디를 수정해야 할지 보입니다. 전자는 이 기사에서 다룬 기법으로, 후자는 모델 교체를 통해 개선할 수 있습니다.

(다음 단계의 각 테크닉은 별도의 기사에서 소개할 예정입니다.)

  • 【초보자용】 Ollama란 무엇인가? 자신의 PC에서 AI를 구동하는 "로컬 LLM"을 기초부터 해설 (Qiita)
  • ChromaDB 입문: 저장할 수 있는 데이터와 개인 개발에서의 활용법 (Qiita)
  • 엔지니어를 위한 RAG 입문 (Qiita)
  • 컨텍스트 윈도우 (Context Window)의 함정 | LLM의 구조적 약점 "Lost in the Middle"과 "Context Rot" 정리하기 (Qiita)
  • Mem0로 AI 채팅에 메모리 기능을 구현해 보았다 (Zenn)
  • AI 에이전트 메모리란? Mem0/Zep/Letta/LangMem 비교 【2026년판】 (renue)

하츠카제 주식회사(Hatsukaze Co., Ltd.)에서는 IT 학습 및 업무에 도움이 되는 정보를 정기적으로 전달해 드리고자 합니다. 시스템 개발 문의 및 상담은 이쪽으로 부탁드립니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0