728x90
반응형

LangChain 및 Neo4j를 사용하여 더 빠르게 학습하세요

동기 부여

긴 YouTube 재생목록을 보면서 학습하는 시대에, 전통적인 학습 방식은 시간이 너무 많이 들고 지루하게 느껴질 수 있죠. 그래서 이 프로세스를 더 역동적이고 매력적으로 바꿔보고 싶었어요. 단순히 콘텐츠를 수동적으로 소비하는 것보다, 대화를 통해 학습을 더 즐겁고 효율적으로 만들 수 있다고 생각하거든요.

사진 제공:🇸🇮 얀코 페를릭 on 언스플래시

Goal

저희 목표는 사람들이 YouTube 재생목록과 상호 작용하는 방식을 혁신하는 거예요. 사용자들이 재생목록 콘텐츠에서 영감을 얻어서, 역동적인 대화에 적극적으로 참여하도록 만드는 거죠. 비디오 캡션에서 중요한 정보를 추출하고 처리한 다음, Neo4j 벡터 데이터베이스에 통합할 거예요. 이렇게 만들어진 대화 체인은 재생 목록 콘텐츠를 기반으로 사용자와 대화를 이끌어주는 가이드 역할을 하게 될 거예요. 사용자가 학습 여정을 적극적으로 만들어갈 수 있도록, 맞춤형 교육 대화를 제공하는 것이 제 임무랍니다.

사용된 기술

대화형 AI를 구축하는 여정을 시작하려면, 저희가 해결해야 할 요구 사항을 충족하는 기술 기반을 확실히 이해해야겠죠? 저희는 여기서 두 가지 최첨단 기술, 즉 LLM(Large Language Model)의 오케스트레이션을 단순화하는 오픈 소스 프레임워크인 LangChain과, 최적의 Node와 Relationship 탐색을 위해 만들어진 강력한 Graph Database인 Neo4j의 시너지 효과를 사용하고 있어요.

LangChain은 LLM과의 원활한 상호 작용을 위해 정말 중요한 역할을 해요. 오픈 소스이기 때문에 개발자들이 이러한 광범위한 언어 모델의 기능을 쉽게 만들고 사용할 수 있죠. 데모 애플리케이션에서 LangChain은 인터페이스 제공자 역할과 대화형 체인 구축 역할을 담당해요.

이러한 상호 작용의 중심에는 상호 연결된 Node와 Relationship의 복잡성을 풀기 위해 설계된 Graph Database, Neo4j가 자리 잡고 있답니다.

작성자의 이미지입니다.

결과는요? 구조화된 지식과 자연어 이해가 완벽하게 융합되어, 정확할 뿐만 아니라 사용자 문의 맥락과 깊이 연결된 응답을 제공하게 되는 거죠!

사용자 경험이 얼마나 중요한지 알기에, 대화형 메모리 체인을 도입했어요. 모든 질문과 답변이 대화의 일부가 되면서 계속 발전하는 모습을 상상해보세요. 이렇게 하면 상호 작용이 더 명확하고 일관성 있게 유지될 수 있어요.

과거의 모든 질문과 답변을 최신 쿼리와 함께 대화형 메모리 체인에 넣어서, 마치 이야기가 계속 이어지는 듯한 느낌을 주죠. 그 결과는요? 상호 작용할수록 더 똑똑해지고, 더 매력적이고, 사용자에게 딱 맞는 대화가 가능해져요.

이번 튜토리얼에서는 뭘 다룰까요?
1. YouTube 재생 목록에서 캡션 읽어오기
2. 각 비디오 캡션을 문서로 나누기
3. Neo4j 데이터베이스에 문서 넣기
4. 대화형 검색 체인 만들기
5. 쿼리 실행하기

이 글에 나오는 코드는 제 GitHub 저장소의 Python 노트북에서도 볼 수 있어요. 를 참고해주세요.

데모를 실행하려면 필요한 패키지들을 먼저 설치해야 해요.

pip install langchain neo4j openai tiktoken pytube youtube_transcript_api

YouTube 재생 목록 처리하기
Playlist 패키지를 사용해서 특정 재생 목록에 있는 모든 비디오 ID를 가져올 수 있어요. 그리고 각 비디오마다 YouTubeLoader를 써서 캡션 문서를 추출하죠. 추출한 문서는 텍스트 분할기에 넣어주고요. 데이터를 넣기 전에 데이터를 정리하고 전처리하는 게 중요한데, 여기서는 영어 캡션만 사용했어요.

각 청크 크기는 문서에 따라 다르게 설정해야 해요. 작은 청크(최대 256 토큰)는 정보를 더 자세하게 잡아내고, 큰 청크는 Large Language Model (LLM)이 각 문서 안의 정보를 바탕으로 더 많은 맥락을 이해할 수 있게 도와주죠. 여기서는 청크 크기를 512로 정했는데, 맥락이 더 중요하다고 생각했기 때문이에요. 이렇게 하면 여러 비디오에 걸쳐 맥락을 연결할 수 있거든요.

# Process all videos from the playlist
playlist_url = "https://www.youtube.com/watch?v=1CqZo7nP8yQ&list=PL9Hl4pk2FsvUu4hzyhWed8Avu5nSUXYrb"
playlist = Playlist(playlist_url)
video_ids = [_v.split('v=')[-1] for _v in playlist.video_urls]
print(f"Processing {len(video_ids)} videos.")

# Read their captions and process it into documents with above defined text splitter
documents = []
for video_id in video_ids:
    try:
      loader = YoutubeLoader(video_id=video_id)
      documents.append(loader.load()[0])
    except: # if there are no english captions
      pass
print(f"Read captions for {len(documents)} videos.")

# Init text splitter with chunk size 512 (https://www.pinecone.io/learn/chunking-strategies/)
text_splitter = TokenTextSplitter.from_tiktoken_encoder(chunk_size=512, chunk_overlap=20)
# Split documents
splitted_documents = text_splitter.split_documents(documents)
print(f"{len(splitted_documents)} documents ready to be processed.")

Neo4j 데이터베이스에 문서 넣기

앞에서 말했듯이, 모든 문서는 Neo4j 데이터베이스에 저장할 거예요. 이렇게 하면 나중에 LangChain과 함께 사용할 Vector Index를 얻을 수 있죠. 데이터베이스를 만드는 건 정말 간단하고, 데이터베이스 작동 방식에 대해 잘 몰라도 할 수 있어요.

문서를 이미 준비하고 분할해놨으니, List[Document]를 받는 from_documents 함수를 사용할 거예요. 더 간단하게 하려면 from_texts 함수를 써도 되지만, 이 경우에는 문서에 대한 제어력을 잃게 돼요. 그래서 from_texts는 간단하게 데모를 보여주고 싶을 때만 쓰는 게 좋다고 생각해요.

search_typehybrid로 설정하면 키워드와 벡터를 검색할 수 있어요. 하이브리드 검색은 Full-Text Search와 HNSW 같은 기능을 사용하는 벡터 쿼리 결과를 합쳐주죠. 결과를 합칠 때는 Reciprocal Rank Fusion (RRF) 알고리즘을 사용해요. 최종 응답은 RRF 알고리즘에 의해 결정된 하나의 결과 집합만 제공하죠.

벡터 검색과 기존 검색 방법을 함께 사용하면 더 미묘하고 상황에 맞는 검색 결과를 얻을 수 있어서, 통찰력의 정확성과 깊이가 더 깊어져요. 이런 방식은 매번 새로운 답을 얻어야 하는 저희 같은 애플리케이션에 특히 유용하답니다.

Neo4j 벡터 데이터베이스가 채워지는 과정을 보여주는 그림이에요. (이미지 출처: 작성자)

# Contruct Neo4j vector ```python neo4j_vector = Neo4jVector.from_documents( embedding=OpenAIEmbeddings(), documents=splitted_documents, url=os.environ['NEO4J_URI'], username=os.environ['NEO4J_USERNAME'], password=os.environ['NEO4J_PASSWORD'], search_type="hybrid" ) ``` ## 대화형 검색 체인 구축 Q&A 흐름을 더 원활하게 만들기 위해 대화 체인을 사용할 거예요. `k`를 3으로 설정하면 검색 체인이 메모리에 마지막 3개 메시지를 기억하라는 의미가 돼요. 쿼리를 실행할 때 이 세 메시지가 LLM으로 전달되죠. 이 값을 조절하면 Q&A 흐름 중에 LLM에게 더 많은 컨텍스트를 제공할 수 있어요. 검색을 위해 앞서 생성한 Neo4j vector 인스턴스를 사용할 거고요. 또, 지정된 한도 내에서 유지되도록 `max_tokens_limit`도 설정할 거예요. 질문하고 답변을 얻는 과정을 세 부분으로 나누면 대화 검색 체인을 이해하기 훨씬 쉬울 거예요. 1. 채팅 기록과 새로운 질문을 사용해서 "독립형 질문"을 만들어요. 이렇게 하는 이유는 이 질문이 관련 문서를 가져오기 위한 검색 단계로 전달될 수 있도록 하기 위해서예요. 새로운 질문만 전달되면 관련 컨텍스트가 부족할 수 있거든요. 반대로 전체 대화가 검색에 전달되면 검색에 방해가 되는 불필요한 정보가 있을 수도 있고요. 2. 이렇게 만들어진 새로운 질문은 검색자에게 전달되고, 관련 문서가 반환돼요. 3. 검색된 문서는 새로운 질문(기본 동작) 또는 원래 질문 및 채팅 기록과 함께 LLM으로 전달돼서 최종 응답을 생성하는 거죠. ```python # Prepare Q&A object chat_mem_history = ChatMessageHistory(session_id="1") mem = ConversationBufferWindowMemory( k=3, memory_key="chat_history", chat_memory=chat_mem_history, return_messages=True ) q = ConversationalRetrievalChain.from_llm( llm=ChatOpenAI(temperature=0.2), memory=mem, retriever=neo4j_vector.as_retriever(), verbose=True, max_tokens_limit=4000 ) ``` 자, 이제 몇 가지 질문을 던져보고 모든 것을 실제로 작동시켜 볼 차례예요. 먼저, 이전 답변의 정보가 필요 없는 간단한 질문부터 시작해 볼게요 (아직 아무것도 없으니까요!). 그런 다음 대화 검색 체인이 제대로 작동해야만 답변할 수 있는 질문을 던져볼 거예요. ```python # Perform Q&A flow - first question response = q.run('What can you tell me about the GenAI stack?') response ```

GenAI 스택은 로컬 데스크톱이나 노트북에서 언어 모델(LLM)을 실행하는 데 사용되는 Olama라는 프레임워크이자 기술입니다. 이는 Neo4j, Langchain 및 기타 당사자 간의 협력입니다. GenAI 스택을 사용하면 Neo4j 및 Langchain과 함께 사전 통합된 환경을 제공하여 개발자가 LLM 지원 애플리케이션을 더 쉽게 구축할 수 있습니다. 이를 통해 개발자는 챗봇이나 개인화된 이메일 생성과 같은 다양한 애플리케이션 기능에 LLM을 활용할 수 있습니다. GenAI 스택은 개발자가 LLM으로 작업하고 이를 애플리케이션에 통합하는 데 더 쉽게 액세스할 수 있도록 설계되었습니다.

```python # Follow up question that requires previous answers (memory) response = q.run('Who talked about it?') response ```

Oscar와 Harrison은 GenAI 스택에 대해 논의했습니다.

제 GitHub 저장소의 Python 노트북에서 이 모든 코드를 확인하고 사용해 볼 수 있어요. 에서요! ## 그래프 표현 Neo4j Aura 데이터베이스는 우리에게 작업 공간을 제공하는데요, 여기에서 Cypher 쿼리를 실행하고 상호 작용 중에 구성되는 그래프를 시각적으로 표현할 수 있어요. 이 대화 기록 체인이 어떻게 표현되는지 보여드리기 위해 다음 그래프를 한번 살펴볼까요?

대화 체인 기록을 그래프로 표현한 것입니다. 작성자의 이미지입니다.

위 그래프는 다음 Cypher 쿼리를 실행해서 얻은 결과예요. ``` MATCH p=(n:Session {id: "1"})-[:LAST_MESSAGE]->()->[:NEXT*0..3]-() RETURN p ``` 채팅 기록은 `ai`와 `human` 두 가지 유형의 Node로 표현돼요. 각 Node는 `NEXT` Relationship으로 연결되어 체인을 형성하죠. 각 Node는 자신의 `id`와 `content`를 가지고 있고요. 메인 Session Node는 `LAST_MESSAGE` Relationship을 통해 AI가 반환한 마지막 메시지에 연결돼요. 이 체인을 따라가 보면 질문자가 어떻게 질문했는지 (`human`), 그리고 응답이 어떻게 반환되었는지 (`ai`) 알 수 있어요. 위 그래프는 위에서 두 가지 질문을 실행했을 때 구성된 모습이에요. 정말 흥미롭죠? ## **결론** 정리하자면, 이번 여정의 목표는 YouTube 학습 경험을 혁신하기 위해 역동적인 대화를 구축하는 것이었어요. Playlist 패키지를 사용해서 YouTube 재생 목록을 처리하고, 비디오 ID를 추출한 다음 YouTubeLoader를 통해 캡션 문서를 가져왔죠. 데이터 전처리 과정에서는 영어 캡션만 고려했고, 텍스트 분할기를 사용해서 문서 청크를 처리했어요. 청크 크기를 512로 설정해서 여러 동영상 간의 연결을 유지하는 데 중요한 컨텍스트에 우선순위를 두었답니다.

Q&A 흐름을 용이하게 하기 위해 검색 체인(k가 3으로 설정됨)이 있는 대화 체인이 사용되었어요. 마지막 3개 메시지를 보존하는 것이 쿼리 중 문맥 이해에 있어서 Large Language Model(LLM)을 도왔죠. 검색에서는 이전에 생성된 Neo4j 벡터 인스턴스를 활용했어요.

대화 검색 체인에는 채팅 기록과 새로운 질문에서 "독립형 질문"을 생성하는 작업이 포함되었어요. 그런 다음 이 질문은 검색자에게 전달되어 관련 문서를 반환하죠. 마지막으로 LLM은 검색된 문서와 채팅 기록이 포함된 새 질문 또는 원래 질문을 바탕으로 포괄적인 응답을 생성해요.


  • LangChain
  • Python
  • qa 흐름
  • YouTube

에이치시스템즈LogTree는 Neo4j 기반 GraphRAG 플랫폼으로, 데이터를 자동으로 지식그래프화하고 자연어 질의로 즉시 답을 제공합니다.

👉 에이치시스템즈 홈페이지

728x90
반응형

+ Recent posts