이전 블로그에서는 4줄의 코드로 구성된 GenAI 기반 노래 찾기를 통해, 벡터 검색을 사용해서 노래 설명을 기반으로 노래를 찾았어요. 우리는 단순히 문서를 찾기 위해 Vector Embedding을 사용했지만, Retrieval-Augmented Generation(RAG)을 사용하면 검색 결과를 챗봇(예: ChatGPT)에 대한 컨텍스트로 활용해서 근거 있는 답변을 제공함으로써 환각을 피할 수 있고, 소스 참조를 제공하며, 챗봇이 모르는 것에 대해 질문할 수도 있죠.
표준 RAG에서는 벡터 검색을 사용해서 데이터 소스에서 관련 문서를 찾아요. 그런 다음 이를 챗봇에 제공해서 해당 문서의 내용을 기반으로 질문에 대한 답변을 요청하는 거죠.
GraphRAG를 사용하면 벡터 검색을 사용해서 문서를 찾을 수도 있지만, Knowledge Graph의 기능을 사용해서 이를 확장하고 제공할 더 관련성 높은 문서를 찾을 수 있어요.
영화 그래프
이를 입증하기 위해 2024년 2월 현재 영화, TV 시리즈, 비디오 게임 등의 그래프와 해당 작업에 종사하는 사람들을 구축했어요. 그래프에는 약 천만 개의 타이틀과 약 1,300만 명의 사람들이 포함되어 있고, 약 1억 6백만 개의 Relationships가 있죠. 그 중 약 130만 권에 시놉시스가 첨부되어 있어요. 이 그래프의 데이터 모델은 다음 이미지와 같아요.

그래프는 IMDB(인터넷 영화 데이터베이스) 및 TMDB(영화 데이터베이스)의 비상업적 공개 데이터 세트를 사용해서 생성되었어요. 데이터는 2024년 2월 22일에 가져왔고요.
이전 블로그 게시물과 마찬가지로 이번에는 제목의 시놉시스 속성에 대해 벡터 Index와 Vector Embedding을 수행할 거예요. Index 생성부터 시작해 볼까요?
CREATE VECTOR INDEX synopsis_embeddings IF NOT EXISTS
FOR (t:Title) ON (t.embedding)
OPTIONS {
indexConfig: {
`vector.dimensions`: 1536,
`vector.similarity_function`: 'cosine'
}
}
이제 임베딩을 수행할게요. 음악 블로그에서는 단순히 모든 속성을 일괄 임베딩에 제공할 수 있지만, OpenAI는 일괄 처리에서 2,048개의 속성만 지원하고 우리는 130만 개의 속성을 가지고 있잖아요. 그래서 0이 반환될 때까지 이 쿼리를 실행하는 스크립트를 만들었어요.
MATCH (title:Title)
WHERE title.synopsis IS NOT NULL AND title.embedding IS NULL
WITH title LIMIT 2048
WITH collect(title.synopsis) AS synopsis, collect(title) AS titles
CALL genai.vector.encodeBatch(synopsis, "OpenAI", {token: $apiKey}) YIELD index, resource, vector
CALL db.create.setNodeVectorProperty(titles[index], "embedding", vector)
RETURN COUNT(index) AS embedded
위의 쿼리와 이후의 모든 쿼리에는 매개변수로 설정할 수 있는 OpenAI의 API 키가 필요해요.
:params
{
apiKey: "*****"
}
질문
음악 블로그에서는 설명을 바탕으로 한 곡을 검색하기 위해 벡터 검색을 활용했죠. 하지만 이 경우에는 ChatGPT가 영화 데이터베이스의 시놉시스를 기반으로 답변하기를 원하는 질문을 표현하고, 답변을 찾은 위치에 대한 참조도 제공하려고 해요. 그래서 우리가 대답하고 싶은 질문은 다음과 같아요.
Robb Stark는 어떤 가족과 전쟁을 시작하나요?
우리 대부분은 이것이 왕좌의 게임에서 나온 것이라는 것을 알고 있다고 생각하지만, 우리가 모른다고 가정해 봅시다. 우리는 이 질문에 대한 답을 원해요. ChatGPT에는 130만 개의 시놉시스를 제공할 수 없으니까, 이 질문에 대한 답을 찾을 가능성이 가장 높은 곳을 찾아야겠죠.
질문을 매개변수로 설정하는 것부터 시작해 볼게요.
:params
{
question: "With what family does Robb Stark start a war?",
apiKey: "*****"
}
이제 음악 블로그에서 했던 것처럼 벡터 검색을 수행해 볼게요.
WITH genai.vector.encode($question, "OpenAI", {token: $apiKey}) AS embedding
CALL db.index.vector.queryNodes('synopsis_embeddings', 1, embedding) YIELD node AS title, score
RETURN title
이것은 우리에게 "The Watchers on the Wall" from 왕좌의 게임 시즌 4, 에피소드 9를 제공하네요. 130만 개의 타이틀 중에서 올바른 TV 시리즈를 찾았다는 게 정말 인상적이고, 1초도 안 돼서 찾아냈다는 점도 놀라워요. 하지만 이 에피소드의 개요는 ChatGPT가 다음 요청에 대한 답변을 찾는 데 도움이 되지 않아요.
Jon Snow와 Night's Watch는 큰 도전에 직면합니다.