이 장에서 우리는 Haystack을 Neo4j와 통합하는 여정에 오른다. LLM과 그래프 데이터베이스의 능력을 결합해 AI 기반 검색 시스템을 짓는 것이다. Haystack은 현대적 NLP 기법, 기계학습 모델, 그래프 기반 데이터를 활용해 AI 기반 애플리케이션을 만들게 해 주는 오픈소스 프레임워크다. 우리의 지능형 검색에서 Haystack은 LLM과 검색 엔진, 데이터베이스를 오케스트레이션하는 응집력 있는 플랫폼 구실을 하며, 고도로 맥락화되고 적절한 검색 결과를 배달한다.
앞 장의 작업 — Neo4j 데이터의 클리닝과 구조화 — 위에서, 우리는 OpenAI의 GPT 모델로 임베딩(embedding)을 생성하는 데서 출발한다. 이 임베딩이 그래프를 살찌워, 뉘앙스 있고 맥락을 아는 검색 질의를 감당할 수 있게 만든다. Haystack은 OpenAI의 모델과 Neo4j 그래프 데이터베이스 사이의 다리 노릇을 하며, 양쪽의 강점을 결합하게 해 준다. 이 장에서는 Neo4j와의 매끄러운 통합을 위해 Haystack을 설정·구성하는 법을 배우고, 강력한 검색 기능을 만든 뒤, Gradio를 이용해 이 완전한 검색 시스템을 완성한다.
기술 요건
- Python 3.11시스템에 설치되어 있어야 한다. Neo4j 데이터베이스와의 스크립팅·상호작용에 쓴다.
- Neo4j AuraDB
또는 로컬 인스턴스그래프 데이터를 저장하고 질의할 Neo4j 데이터베이스 접근이 필요하다. 앞 장의graph_build.py스크립트를 따라왔다면 이미 설정되고 데이터가 채워진 같은 인스턴스를 계속 쓰면 된다. 연속성이 보장되고, 이미 임포트된 구조화 데이터 위에 쌓아 올릴 수 있다. - Cypher 질의 언어그래프의 생성·질의에 광범위하게 쓰므로 친숙함이 필수다.
- Neo4j Python 드라이버
pip install neo4j - Haystack v2.5.0
pip install haystack-ai - OpenAI API 키GPT 기반 모델로 임베딩을 생성하려면 필요하다. 계정이 없다면 OpenAI에 가입하고, 로그인 후 대시보드의 API keys 섹션에서 새 키를 생성한다.
pip install openai도 실행한다. - Gradio사용자 친화적인 챗봇 인터페이스를 만드는 데 쓴다:
pip install gradio - Hugging Face 계정챗봇을 Hugging Face Spaces에 호스팅하려면 필요하다. 없다면 huggingface.co에서 가입한다.
- GCS (선택)CSV 파일을 Google Cloud Storage에 저장하고 있다면, 스크립트의 파일 경로가 올바르게 설정되어 있는지 확인한다.
- python-dotenv프로젝트의 환경 변수 관리를 위해 설치한다:
pip install python-dotenv
무료 티어 API 키는 이 프로젝트의 대부분 용례에서 작동하지 않는다. 필요한 엔드포인트와 사용 한도에 접근하려면 활성화된 유료 OpenAI 구독이 필요하다.
이 장의 모든 코드는 책의 GitHub 저장소에 있다. 저장소 안에서 이 장과 관련된 코드 예제와 리소스가 담긴 폴더로 이동하면, Neo4j·Haystack 통합의 구현과 영화 데이터셋 기반 AI 검색 시스템 구축에 필요한 스크립트, 파일, 설정을 모두 만날 수 있다. 이 장의 코드 예제를 따라가려면 저장소를 클론하거나 다운로드해 둔다.
(:Section {n: 1})-[:EMBEDS]->(:MoviePlot)
Haystack으로 임베딩을 생성해 Neo4j 그래프를 강화하다
이 절은 앞 장에서 Neo4j 그래프에 넣은 영화 줄거리의 임베딩 생성에 집중한다. 임베딩은 현대 검색 시스템의 결정적 부품이다. 텍스트를 고차원 벡터로 변환해 유사도 검색을 가능케 하고, 검색 엔진이 단어와 구절 사이의 맥락적 관계를 이해하도록 만들어 검색 결과의 정확성과 적합성을 끌어올린다. 우리는 Haystack을 OpenAI의 GPT 기반 모델과 통합해 이 임베딩을 생성하고, Neo4j 그래프에 저장한다. 더 정확하고 맥락을 아는 검색 기능이 이렇게 열린다.
임베딩을 위한 Haystack과 OpenAI 초기화
임베딩을 생성하기 전에, Haystack이 설정되어 있고 OpenAI의 API와 통합되어 GPT 기반 모델의 임베딩을 가져올 수 있는지 확인해야 한다. 설정 절차는 이렇다.
- 필요한 라이브러리를 설치한다 (아직 하지 않았다면):
pip install haystack haystack-ai openai neo4j-haystack - OpenAI API 키를 구성하고
.env파일에 설정되어 있는지 확인한다:OPENAI_API_KEY=your_openai_api_key_here - OpenAI 임베딩으로 Haystack을 초기화한다. Haystack을 초기화하고 임베딩 생성을 위해 OpenAI에 접속하는 Python 스크립트를 만든다.
# 텍스트 임베딩을 위한 Haystack + OpenAI 초기화 def initialize_haystack(): # 문서 저장소 초기화 (지금은 인메모리, 다른 저장소로 구성 가능) document_store = InMemoryDocumentStore() # 텍스트 임베딩 생성을 위한 OpenAITextEmbedder 초기화 embedder = OpenAITextEmbedder( api_key=Secret.from_env_var("OPENAI_API_KEY"), model="text-embedding-ada-002" ) return embedder
이 구성은 인메모리 문서 저장소로 Haystack을 초기화하고, OpenAI 임베딩을 쓰는 리트리버를 설정한다.
영화 줄거리의 임베딩 생성
다음은 Neo4j 그래프에 저장된 영화 줄거리의 임베딩을 생성할 차례다. 목표는 줄거리 설명을 가져와 임베딩을 생성하고, 그 임베딩을 각 영화 노드에 다시 연결하는 것이다.
# Neo4j에서 영화 줄거리와 제목을 가져온다 def retrieve_movie_plots(): # 각 Movie 노드의 "title", "overview", "tmdbId" 프로퍼티를 가져오는 질의 query = """ MATCH (m:Movie) WHERE m.embedding IS NULL RETURN m.tmdbId AS tmdbId, m.title AS title, m.overview AS overview """ with driver.session() as session: results = session.run(query) # 각 영화의 제목·줄거리(overview)·ID를 movies 리스트에 담는다 movies = [ {"tmdbId": row["tmdbId"], "title": row["title"], "overview": row["overview"]} for row in results ] return movies
이 질의는 그래프의 각 영화에 대해 tmdbId와 줄거리 요약(overview)을 반환한다. WHERE m.embedding IS NULL 조건에 주목하자. 아직 임베딩이 없는 영화만 골라내므로, 중단된 작업을 이어서 실행해도 중복 비용이 발생하지 않는다.
# ThreadPoolExecutor를 이용한 병렬 임베딩 생성 def generate_and_store_embeddings(embedder, movies, max_workers=10): results_to_store = [] def process_movie(movie): title = movie.get("title", "Unknown Title") overview = str(movie.get("overview", "")).strip() tmdbId = movie.get("tmdbId") if not overview: print(f"Skipping {title} — No overview available.") return None try: print(f"Generating embedding for: {title}") embedding_result = embedder.run(overview) embedding = embedding_result.get("embedding") if embedding: return (tmdbId, embedding) else: print(f"No embedding generated for: {title}") except Exception as e: print(f"Error processing {title}: {e}") return None
# 임베딩을 Neo4j에 다시 저장한다 def store_embedding_in_neo4j(tmdbId, embedding): query = """ MATCH (m:Movie {tmdbId: $tmdbId}) SET m.embedding = $embedding """ with driver.session() as session: session.run(query, tmdbId=tmdbId, embedding=embedding) print(f"Stored embedding for TMDB ID: {tmdbId}")
이 코드는 Neo4j 그래프의 각 Movie 노드에 embedding이라는 프로퍼티로 임베딩을 저장한다. 마지막으로 저장을 검증한다.
# Neo4j에 저장된 임베딩 검증 def verify_embeddings(): query = """ MATCH (m:Movie) WHERE exists(m.embedding) RETURN m.title, m.embedding LIMIT 10 """ with driver.session() as session: results = session.run(query) for record in results: title = record["title"] embedding = np.array(record["embedding"])[:5] print(f" {title}: {embedding}...")
이 질의는 영화 몇 편의 제목과 임베딩을 반환해, 임베딩이 성공적으로 저장되었는지 검증하게 해 준다.
여기 실은 것은 코드 조각일 뿐이다. 전체 버전은 GitHub 저장소의 ch5/generate_embeddings.py에 있다.
이제 그래프가 임베딩으로 살찌워졌고, 유사도 검색이 더해졌다. 더 맥락을 알고 지능적인 질의가 가능해진 것이다. 단순한 키워드 매칭이 아니라 텍스트의 의미에 기초한 고급 검색 연산의 문이 열렸다.
(:Section {n: 2})-[:INDEXES]->(:Embedding)
고급 벡터 검색을 위해 Haystack을 Neo4j에 연결하다
영화 임베딩이 Neo4j에 저장되었으니, 이제 embedding 프로퍼티 위에 벡터 인덱스(vector index)를 구성해야 한다. 벡터 유사도에 기초한 효율적 영화 검색이 이 인덱스에서 나온다. Neo4j에 벡터 인덱스를 만들면, 고차원 임베딩 공간에서 가까운 노드들의 신속한 검색이 가능해지고, 비슷한 줄거리의 영화를 찾는 것 같은 정교한 질의를 수행할 수 있게 된다. 벡터 인덱스가 만들어지면 Haystack과 통합해 Neo4j에서 벡터 기반 검색을 수행한다. 이 검색은 코사인 유사도 같은 벡터 유사도 메커니즘에 기초한다.
Neo4j에 벡터 검색 인덱스 만들기
먼저 embedding 프로퍼티에 기존 벡터 인덱스가 있다면 삭제하고, 벡터 검색을 위한 새 인덱스를 만든다. Python 스크립트 안에서 Cypher 질의로 하는 방법이다.
def create_or_reset_vector_index(): with driver.session() as session: try: # 기존 벡터 인덱스가 있으면 삭제 session.run("DROP INDEX overview_embeddings IF EXISTS") print("Old index dropped") except: print("No index to drop") # embedding 프로퍼티에 새 벡터 인덱스 생성 print("Creating new vector index") query_index = """ CREATE VECTOR INDEX overview_embeddings IF NOT EXISTS FOR (m:Movie) ON (m.embedding) OPTIONS {indexConfig: { `vector.dimensions`: 1536, `vector.similarity_function`: 'cosine'}} """ session.run(query_index) print("Vector index created successfully")
Haystack과 Neo4j 벡터 인덱스로 유사도 검색 수행
Neo4j 그래프에 벡터 인덱스가 생겼으니, Haystack을 지렛대 삼아 영화 줄거리 임베딩에 기초한 유사도 검색 질의를 수행할 수 있다. 주어진 영화 줄거리나 임의의 텍스트 질의를 기존 영화 개요들과 견주어, 임베딩에 기초한 가장 적절한 결과를 돌려주는 접근이다. 이 예제에서는 Haystack 라이브러리의 OpenAITextEmbedder 모델로 텍스트 질의를 임베딩으로 변환한 뒤, 그것으로 Neo4j 그래프에서 비슷한 줄거리의 영화를 찾는다.
text_embedder = OpenAITextEmbedder(
api_key=Secret.from_env_var("OPENAI_API_KEY"),
model="text-embedding-ada-002"
)
# 1단계: 질의의 임베딩 생성
query_embedding = text_embedder.run(query).get("embedding")
if query_embedding is None:
print("Query embedding not created successfully.")
return
print("Query embedding created successfully.")
Haystack과 Neo4j로 벡터 검색 질의 실행 — Cypher 없이
벡터 인덱스가 만들어지고 임베딩이 Neo4j에 저장되었으면, 질의나 샘플 영화 줄거리를 넘겨 벡터 기반 검색을 수행할 수 있다. 시스템이 질의의 임베딩을 생성하고, Neo4j에 저장된 임베딩들과 비교해, 가장 관련 있는 결과를 반환한다. Cypher 없이 가장 유사한 영화 줄거리를 표시하는 Haystack 벡터 검색의 예다.
# 2단계: 질의 임베딩으로 유사 문서 검색 similar_documents = document_store.query_by_embedding( query_embedding, top_k=3 ) if not similar_documents: print("No similar documents found.") return print(f"Found {len(similar_documents)} similar documents.") # 3단계: 결과 표시 for doc in similar_documents: title = doc.meta.get("title", "N/A") overview = doc.meta.get("overview", "N/A") score = doc.score print(f"Title: {title}\nOverview: {overview}\n" f"Score: {score:.2f}\n{'-'*40}")
Cypher와 Haystack으로 벡터 검색 질의 실행
이번에는 Neo4j의 Cypher 질의를 Haystack과 통합해 벡터 검색을 실행한다. Haystack으로 벡터 인덱스를 직접 질의하는 방식과 달리, 이 접근은 Cypher의 유연성 — 출연진과 장르 같은 영화 메타데이터를 임베딩과 함께 반환하는 능력 — 을 벡터 유사도 검색의 효율성과 결합한다. 과정에 담긴 개념적 단계는 셋이다. 첫째, OpenAITextEmbedder로 사용자의 텍스트 질의(예: 영화 줄거리)를 고차원 벡터 임베딩으로 변환한다. 둘째, Cypher로 Neo4j 벡터 인덱스에 저장된 영화 줄거리 임베딩과 질의 임베딩을 비교해 유사한 영화를 검색한다. 셋째, 각 결과에 대해 제목·개요·출연진·장르·점수(유사도) 같은 추가 정보를 붙여 풍부해진 데이터를 반환한다. 구현은 네 단계로 진행된다.
- Cypher 질의를 정의한다. Neo4j 벡터 인덱스(
overview_embeddings)를 검색해, 질의 임베딩과 영화 임베딩 사이의 코사인 유사도로 가장 유사한top_k편을 가져오는 질의다. - 질의 임베딩을 생성한다.
OpenAITextEmbedder로 사용자 입력 질의를 임베딩으로 변환한다. 이 임베딩이 저장된 영화 임베딩과의 비교를 위해 Neo4j 벡터 인덱스로 전달된다. - Haystack 파이프라인으로 벡터 검색을 실행한다.
query_embedder가 사용자 질의에서 임베딩을 생성하고,retriever가 질의 임베딩으로 Neo4j에서 Cypher 질의를 실행해 가장 유사한 영화들을 반환한다. - 결과를 표시한다. 검색이 끝나면 Neo4j 그래프에서 결과를 추출해 영화 제목, 개요, 유사도 점수를 표시한다.
cypher_query = """ CALL db.index.vector.queryNodes("overview_embeddings", $top_k, $query_embedding) YIELD node AS movie, score MATCH (movie:Movie) RETURN movie.title AS title, movie.overview AS overview, score """ text_embedder = OpenAITextEmbedder( api_key=Secret.from_env_var("OPENAI_API_KEY"), model="text-embedding-ada-002" )
retriever = Neo4jDynamicDocumentRetriever(
client_config=client_config,
runtime_parameters=["query_embedding"],
compose_doc_from_result=True,
verify_connectivity=True,
)
pipeline = Pipeline()
pipeline.add_component("query_embedder", text_embedder)
pipeline.add_component("retriever", retriever)
pipeline.connect(
"query_embedder.embedding", "retriever.query_embedding"
)
result = pipeline.run(
{
"query_embedder": {"text": query},
"retriever": {
"query": cypher_query,
"parameters": {
"index": "overview_embeddings", "top_k": 3
},
},
}
)
# 리트리버 결과에서 문서 추출 documents = result["retriever"]["documents"] for doc in documents: # 문서 메타데이터에서 제목·개요 추출 title = doc.meta.get("title", "N/A") overview = doc.meta.get("overview", "N/A") # 문서에서 점수 추출 score = getattr(doc, "score", None) score_display = f"{score:.2f}" if score is not None else "N/A" print(f"Title: {title}\nOverview: {overview}\n" f"Score: {score_display}\n{'-'*40}\n")
Cypher와 Haystack을 함께 쓰는 데는 여러 이점이 따른다.
Cypher의 유연성
Cypher를 Haystack과 결합하면 임베딩만 질의하는 데 그치지 않고, 출연진·장르·개체 사이 관계 같은 그래프 기반 추가 정보까지 가져올 수 있다.
풍부해진 결과
가장 유사한 영화의 검색에 더해, 관련 메타데이터(배우, 장르, 평점)를 가져오거나 개봉 연도·장르 같은 추가 필터 조건으로 검색을 정제하도록 질의를 쉽게 확장할 수 있다.
대규모 그래프 최적화
Neo4j의 벡터 인덱스는 복잡한 관계를 지닌 대규모 데이터셋의 효율적 질의를 허락하고, Haystack의 임베딩 모델은 영화 줄거리의 정확한 이해를 제공한다.
용례 하나
A hero must save the world from destruction(영웅이 파멸로부터 세계를 구해야 한다) 같은 줄거리의 영화를 찾는다고 하자. 방금 만든 파이프라인으로 관련 결과를 가져올 수 있다.
The Matrix
A computer hacker learns from mysterious rebels about the true nature of his reality and his role in the war against its controllers.
Inception
A thief who steals corporate secrets through dream-sharing technology is given the inverse task of planting an idea into the mind of a CEO.
The Dark Knight
Batman raises the stakes in his war on crime, with the help of Lieutenant Jim Gordon and District Attorney Harvey Dent.
이 파이프라인은 두 세계의 최선을 결합한다. 벡터 임베딩을 통한 유사도 검색과, Cypher를 통한 그래프 질의의 풍부한 데이터 능력이다. 영화 같은 대규모 데이터셋 위에서 강력하고 유연한 검색이 가능해진다.
여기 실은 것은 코드 조각일 뿐이다. 전체 버전은 ch5/vector_search.py에 있다.
이제 Haystack이 Neo4j에 연결되었고, 고급 벡터 검색 기능이 살아났다. 벡터 인덱스가 자리 잡았으니 Neo4j는 임베딩 유사도로 비슷한 영화 노드를 효율적으로 찾을 수 있고, Haystack의 통합 덕분에 Neo4jDynamicDocumentRetriever로 이 검색을 매끄럽게 수행할 수 있다. 이 리트리버는 벡터 임베딩과 Neo4j의 그래프 능력을 지렛대 삼아 그래프 안의 유사 항목을 찾아낸다.
(:Section {n: 3})-[:CHATS_VIA]->(:Gradio)
Gradio와 Haystack으로 검색 기반 챗봇을 짓다
이 절에서는 Gradio를 통합해 Haystack과 Neo4j가 구동하는 대화형 챗봇 인터페이스를 만든다. Gradio는 챗봇과 상호작용하는 웹 기반 인터페이스를 쉽게 만들게 해 준다. 챗봇은 사용자의 질의 입력을 받아 Neo4j에 저장된 영화 임베딩의 벡터 기반 검색을 촉발하고, 영화 제목·개요·유사도 점수를 포함한 상세한 응답을 돌려준다. 유익하고 사용자 친화적인 경험이다.
Gradio 인터페이스 설정
Gradio를 아직 설치하지 않았다면 pip install gradio로 설치한다. 다음으로 검색 파이프라인을 촉발하고 결과를 표시하는 기본 Gradio 인터페이스를 설정한다.
이 장의 스크립트는 Gradio v5.23.1에서 정상 작동한다.
import gradio as gr # Gradio 챗봇 인터페이스 정의 def chatbot(user_input): return perform_vector_search_cypher(user_input) # Gradio 인터페이스 생성 chat_interface = gr.Interface( fn=chatbot, inputs=gr.Textbox( placeholder="What kind of movie would you like to watch?", lines=3, label="Your movie preference" ), outputs=gr.Textbox(label="Recommendations", lines=12), title="AI Movie Recommendation System", description="Ask me about movies! I can recommend movies based on your preferences.", examples=[ ["I want to watch a sci-fi movie with time travel"], ["Recommend me a romantic comedy with a happy ending"], ["I'm in the mood for something with superheroes but not too serious"], ["I want a thriller that keeps me on the edge of my seat"], ["Show me movies about artificial intelligence taking over the world"] ], flagging_mode="never" )
AI Movie Recommendation System
Ask me about movies! I can recommend movies based on your preferences.
Overview: A computer hacker learns from mysterious rebels about the true nature of his reality…
Score: 0.98
----------------------------------------
Title: Inception · Score: 0.96
Title: The Dark Knight · Score: 0.94
Gradio 인터페이스 재현 — 다섯 개의 예시 질의 버튼과 함께 브라우저에 실행된 모습
이 인터페이스는 사용자의 텍스트 질의를 받고, 챗봇은 perform_vector_search_cypher() 함수로 가장 관련 있는 영화를 찾는다.
Haystack·Neo4j와의 통합
챗봇에 동력을 공급하기 위해, Haystack의 임베딩 생성과 Neo4j의 벡터 검색 능력에 챗봇을 연결한다. 질의와 Neo4j에 저장된 영화 줄거리 양쪽의 임베딩 생성에 OpenAITextEmbedder를 쓴다. 영화 임베딩은 Neo4j 안의 벡터 인덱스에 저장되어 있고, 우리는 그 인덱스에서 가장 유사한 영화를 질의한다. 앞서 만든 Haystack 설정과 챗봇을 통합하는 방법이다.
# Cypher 기반 검색과 Haystack을 쓰는 대화형 챗봇 핸들러 def perform_vector_search(query): print("MESSAGES RECEIVED:", user_input) cypher_query = """ CALL db.index.vector.queryNodes("overview_embeddings", $top_k, $query_embedding) YIELD node AS movie, score MATCH (movie:Movie) RETURN movie.title AS title, movie.overview AS overview, score """ # 임베더 embedder = OpenAITextEmbedder( api_key=Secret.from_env_var("OPENAI_API_KEY"), model="text-embedding-ada-002" ) # 리트리버 retriever = Neo4jDynamicDocumentRetriever( client_config=client_config, runtime_parameters=["query_embedding"], compose_doc_from_result=True, verify_connectivity=True, ) # 파이프라인 pipeline = Pipeline() pipeline.add_component("query_embedder", embedder) pipeline.add_component("retriever", retriever) pipeline.connect( "query_embedder.embedding", "retriever.query_embedding" )
Gradio를 전체 파이프라인에 연결하기
이제 이 Gradio 챗봇을 이미 갖춰 둔 Haystack·Neo4j 파이프라인과 연결한다. Gradio 인터페이스가 perform_vector_search_cypher() 함수를 호출하고, 그 함수가 Neo4jDynamicDocumentRetriever를 활용해 사용자의 질의에 기초한 유사 영화를 검색한다. main() 함수를 갱신해 챗봇을 초기화한다.
# 전체 과정을 오케스트레이션하는 메인 함수 def main(): # 1단계: Neo4j AuraDB에 벡터 인덱스 생성 또는 리셋 create_or_reset_vector_index() # 2단계: Gradio 챗봇 인터페이스 시작 chat_interface.launch() if __name__ == "__main__": main()
챗봇 실행
챗봇을 실행하려면 Python 스크립트를 실행하기만 하면 된다: python search_chatbot.py. Gradio 인터페이스가 브라우저에 시작되어 실시간으로 챗봇과 상호작용할 수 있다. "Tell me about a hero who saves the world." 같은 질의를 입력하면, 챗봇이 벡터 검색에 기초해 이 질의와 유사한 영화 줄거리들을 돌려준다.
여기 실은 것은 코드 조각일 뿐이다. 전체 버전은 ch5/search_chatbot.py에 있다.
이 절의 끝에서 우리는 Gradio, Haystack, Neo4j로 완전히 작동하는 검색 기반 챗봇을 완성했다. 챗봇은 Neo4j에 저장된 임베딩을 지렛대 삼아 고급 벡터 검색을 수행하고, 사용자 질의에 응답해 의미 있는 영화 제목과 배우를 Neo4j에서 가져오는 형태로 맥락에 맞는 결과를 돌려준다. 그러나 이것은 시작일 뿐이다.
(:Section {n: 4})-[:TUNES]->(:Pipeline)
Haystack 통합의 미세조정
이제 이 통합을 성능과 사용자 경험의 개선을 위해 미세조정하는 방법을 탐구할 때다. 현재의 구성만으로도 풍부하고 맥락을 아는 응답이 나오지만, 검색 과정을 최적화하고 검색 정확도를 높이고 챗봇의 상호작용을 더 매끄럽게 만드는 고급 기법이 여럿 있다. 이 절에서는 Haystack의 핵심 요소를 조정하는 데 집중한다. 서로 다른 임베딩 모델의 실험, 더 빠른 결과를 위한 Neo4j 질의 최적화, 챗봇 응답 표시 방식의 개선이다. 이 강화들이 챗봇을 더 복잡한 질의로 확장하고, 응답 시간을 줄이고, 더 적절한 검색 결과를 배달하도록 돕는다.
다양한 임베딩 모델의 실험
현재 우리는 OpenAI의 text-embedding-ada-002 모델로 임베딩을 생성한다. 이 모델은 출시 이후 폭넓은 과업에서 신뢰할 수 있고 성능 좋은 선택이었지만, OpenAI가 최근 text-embedding-3-small과 text-embedding-3-large 같은 새 모델을 내놓았다는 사실도 알아 둘 만하다. 성능과 비용 효율 모두에서 상당한 개선을 제공하는 모델들이다. 예컨대 text-embedding-3-small은 다국어·영어 과업에서 더 나은 결과를 내면서도, ada-002보다 최대 다섯 배 비용 효율적이다. 이 프로젝트에서는 일관성을 위해 모델을 바꾸지 않았지만, 유사한 파이프라인을 구현하는 독자라면 — 임베딩 생성이 빈번하거나 대규모인 경우 특히 — 성능을 희생하지 않고 효율을 높이는 text-embedding-3-small의 사용을 고려할 만하다.
Haystack은 그 밖의 다양한 모델도 지원하므로, 자신의 용례에 가장 정확하거나 적절한 결과를 주는 모델을 찾아 실험할 수 있다. 더 높은 차원의 정교한 OpenAI 모델로 바꿀 수도 있고, Haystack이 지원하는 다른 임베딩 서비스를 시험할 수도 있다. 다른 모델로 바꾸는 방법은 간단하다.
embedder = OpenAITextEmbedder(
api_key=Secret.from_env_var("OPENAI_API_KEY"),
model="text-embedding-babbage-001" # 다른 모델로 실험
)
OpenAI의 다른 모델을 탐색하거나 아예 다른 임베딩 서비스를 통합해, 영화 챗봇에 가장 잘 맞는 것을 찾아볼 수도 있다.
더 빠른 질의를 위한 Neo4j 최적화
Neo4j는 이미 그래프 기반 질의를 효율적으로 처리하지만, 특히 대규모 데이터셋에는 적용할 만한 최적화가 여럿 있다. 추가 프로퍼티를 인덱싱해 질의 성능을 개선할 수 있다. embedding 프로퍼티의 벡터 인덱스에 더해, title이나 tmdbId처럼 자주 질의되는 다른 프로퍼티도 인덱싱해 검색을 가속하는 것이다. 이 프로퍼티들로 영화를 거르거나 가져올 때마다 검색이 더 빠르고 효율적이 된다.
def create_additional_indexes(): with driver.session() as session: session.run("CREATE INDEX IF NOT EXISTS movie_title_index FOR (m:Movie) ON (m.title)") session.run("CREATE INDEX IF NOT EXISTS movie_tmdbId_index FOR (m:Movie) ON (m.tmdbId)") print("Additional indexes created successfully")
이 프로퍼티들을 인덱싱하면, 검색이 임베딩에만 기초하지 않을 때 — 제목으로 거르거나 특정 영화를 가져올 때 — 의 조회가 최적화된다. 챗봇의 검색 경험을 지속적으로 개선하려면, 사용자 질의를 기록하고 시간에 걸쳐 분석하는 것도 방법이다.
질의의 로깅과 분석
로깅은 가장 흔한 검색 패턴을 추적하도록 돕는다. 사용자 질의 로그와 그 분석에 기초해, 인덱싱 전략을 조정하거나, 리트리버를 최적화하거나, 정확도를 높이기 위해 임베딩 모델을 손볼 수 있다. 간단한 로깅 메커니즘의 구현은 이렇다.
import logging logging.basicConfig(filename='chatbot_queries.log', level=logging.INFO) def log_query(query): logging.info(f"User query: {query}")
사용자가 질의를 입력할 때마다 이후 분석을 위해 기록된다. 이 로그를 분석해 시스템에 정보에 기반한 조정을 가하면, 시간이 갈수록 더 반응이 빠르고 정확해진다.
이 기법들은 검색 기반 챗봇의 성능, 정확도, 사용자 경험을 크게 끌어올린다. 다른 임베딩 모델의 실험이든, Neo4j 질의의 최적화든, 결과 표시 방식의 개선이든, 조정 하나하나가 매끄럽고 강력한 사용자 상호작용에 한 걸음씩 다가서게 한다. 이 고급 기법들 덕분에 챗봇은 효과적으로 확장되고, 더 복잡한 질의를 감당하며, 한층 더 적절하고 매력적인 결과를 돌려주게 된다.
요약
이 장에서 우리는 Gradio, Haystack, Neo4j를 통합해 완전히 작동하는 검색 기반 챗봇을 성공적으로 지었다. OpenAI의 모델이 생성한 영화 임베딩으로 Neo4j 그래프를 살찌우는 데서 시작해 고급 벡터 기반 검색 기능을 열었고, 거기서 Haystack을 Neo4j에 연결해 그래프에 저장된 임베딩 위의 유사도 검색을 수행했다. 마지막으로 Gradio의 사용자 친화적 챗봇 인터페이스로 전체를 감쌌다. 사용자의 질의에 따라 영화 제목과 배우 같은 상세 정보를 동적으로 가져오는 챗봇이다.
다음 장에서는 Haystack의 고급 검색 능력과 검색 최적화에 집중한다. 대규모 그래프를 위한 질의 최적화도 논한다.