반응형

GraphRAG Python 패키지는 비정형 데이터에서 Knowledge Graph 생성, Knowledge Graph 검색, 그리고 전체 GraphRAG 파이프라인을 한 곳에 모아 제공하는 end-to-end 워크플로우를 제공해요. Python을 사용해서 지식 도우미, 검색 API, 챗봇, 또는 보고서 생성기를 구축하고 있다면, 이 패키지를 통해 Knowledge Graph를 쉽게 통합해서 Retrieval-Augmented Generation (RAG)의 관련성, 정확성, 설명 가능성을 개선할 수 있어요.

이번 포스팅에서는 GraphRAG Python 패키지를 사용해서 0에서 GraphRAG로 가는 방법과 다양한 Knowledge Graph 검색 패턴을 사용해서 GenAI 앱에서 다양한 동작을 얻는 방법을 보여드릴게요.

GraphRAG: GenAI에 지식 추가

Knowledge Graph와 RAG를 결합한 GraphRAG는 환각과 같은 Large Language Model (LLM)의 일반적인 문제를 해결하는 동시에 기존 RAG 접근 방식보다 향상된 품질과 효율성을 위해 도메인별 컨텍스트를 추가하는 데 도움이 돼요. Knowledge Graph는 LLM이 질문에 안정적으로 답변하고 복잡한 워크플로에서 신뢰할 수 있는 에이전트 역할을 하는 데 필요한 상황별 데이터를 제공하죠. 텍스트 데이터의 단편에만 액세스를 제공하는 대부분의 RAG 솔루션과 달리 GraphRAG는 구조화된 정보와 반구조화된 정보를 모두 검색 프로세스에 통합할 수 있다는 장점이 있어요.

GraphRAG Python 패키지는 Knowledge Graph를 생성하고 그래프 순회, text2Cypher를 사용한 쿼리 생성, 벡터 및 전체 텍스트 검색의 조합을 사용하는 Knowledge Graph 데이터 검색 패턴을 쉽게 구현하도록 도와줘요. 전체 RAG 파이프라인을 지원하는 추가 도구와 결합된 이 패키지를 사용하면 GenAI 애플리케이션 및 워크플로에서 GraphRAG를 원활하게 구현할 수 있어요.

빠른 개요: 몇 줄의 코드로 구성된 GraphRAG

다음은 전체 end-to-end 워크플로우를 위한 몇 줄의 코드인데요. 다음 섹션에서는 이 예제의 더 자세한 버전을 살펴보고 다음 GenAI 혁신을 시작하기 위한 추가 리소스를 제공할게요. 복제할 모든 코드는 이 GitHub 저장소에서 찾을 수 있어요.

import neo4j
from neo4j_graphrag.llm import OpenAILLM as LLM
from neo4j_graphrag.embeddings.openai import OpenAIEmbeddings as Embeddings
from neo4j_graphrag.experimental.pipeline.kg_builder import SimpleKGPipeline
from neo4j_graphrag.retrievers import VectorRetriever
from neo4j_graphrag.generation.graphrag import GraphRAG

neo4j_driver = neo4j.GraphDatabase.driver(NEO4J_URI,
                                          auth=(NEO4J_USERNAME, NEO4J_PASSWORD))

ex_llm=LLM(
   model_name="gpt-4o-mini",
   model_params={
       "response_format": {"type": "json_object"},
       "temperature": 0
   })

embedder = Embeddings()

# 1. Build KG and Store in Neo4j Database
kg_builder_pdf = SimpleKGPipeline(
   llm=ex_llm,
   driver=neo4j_driver,
   embedder=embedder,
   from_pdf=True
)
await kg_builder_pdf.run_async(file_path='precision-med-for-lupus.pdf')

# 2. KG Retriever
vector_retriever = VectorRetriever(
   neo4j_driver,
   index_name="text_embeddings",
   embedder=embedder
)

# 3. GraphRAG Class
llm = LLM(model_name="gpt-4o")
rag = GraphRAG(llm=llm, retriever=vector_retriever)

# 4. Run
response = rag.search( "How is precision medicine applied to Lupus?")
print(response.answer)

#output
Precision medicine in systemic lupus erythematosus (SLE) is an evolving approach that aims to tailor treatment based on individual genetic, epigenetic, and pathophysiological characteristics…

0. Neo4j 데이터베이스 생성

RAG 예제를 시작하려면 검색에 사용할 데이터베이스를 만들어야 해요. Neo4j를 사용하면 무료 Neo4j Graph Database를 빠르게 시작할 수 있는데, AuraDB를 사용하면 돼요. 더 높은 수집 및 검색 성능을 위해 AuraDB Free를 사용하거나 AuraDB Professional(Pro) 무료 평가판을 시작할 수도 있고요. Pro 인스턴스에는 RAM이 조금 더 많아서 최고의 사용자 경험을 위해 권장된답니다.

인스턴스를 생성한 후에는 다음 코드에서 사용할 자격 증명을 다운로드하고 저장할 수 있어요.

1. Knowledge Graph 구축

문서를 Knowledge Graph로 변환해서 Neo4j 데이터베이스에 저장해볼게요. 루푸스에 관한 의학 연구 논문이 포함된 몇 가지 PDF 문서를 통해 보여드릴게요. 이런 문서에는 전문적인 정보가 많이 들어있는데, Knowledge Graph는 이 복잡한 임상 데이터를 더 효과적으로 구성해서 AI 사용 사례를 향상시킬 수 있어요. 아래는 우리가 만들 그래프의 샘플이에요. 여기에는 몇 가지 주요 Node 유형(Node Label이라고도 함)이 포함되어 있죠.

  1. Document: 문서 소스에 대한 메타데이터
  2. Chunk: 강력한 Vector Embedding을 위한 임베딩이 포함된 문서의 텍스트 Chunk
  3. __Entity__: 텍스트 Chunk에서 추출된 Entity

Entity 간의 Relationship과 Entity가 Chunk와 문서 간의 연결 경로를 생성하는 방법을 확인해보세요. 이게 바로 GraphRAG의 장점이고, 나중에 이게 얼마나 중요한지 살펴보게 될 거예요.

A sample of graph containing medical research papers on Lupus.

GraphRAG Python 패키지를 사용해서 Knowledge Graph를 만드는 건 Neo4j 전문가가 아니더라도 정말 간단해요.

The SimpleKGPipeline 클래스를 사용하면 다음과 같은 몇 가지 주요 입력으로 Knowledge Graph를 자동으로 구축할 수 있어요.

  • Neo4j에 연결하기 위한 Driver,
  • Entity 추출을 위한 LLM, 그리고
  • 유사성 검색을 위해 텍스트 Chunk에 Vector를 생성하는 임베딩 모델이에요.

Neo4j Driver

Neo4j Driver를 사용하면 데이터베이스에 연결해서 읽기 및 쓰기 트랜잭션을 수행할 수 있어요. 데이터베이스를 생성할 때부터 URI, 사용자 이름 및 비밀번호 변수를 얻을 수 있는데, AuraDB에서 데이터베이스를 생성했다면 다운로드한 파일에 있을 거예요.

import neo4j

neo4j_driver = neo4j.GraphDatabase.driver(NEO4J_URI,
                                         auth=(NEO4J_USERNAME, NEO4J_PASSWORD))

LLM 및 임베딩 모델

이번에는 OpenAI의 GPT-4o-mini를 사용해볼게요. 빠르고 저렴한 모델이거든요. GraphRAG Python 패키지는 모든 LLM 모델을 지원하는데, OpenAI, Google VertexAI, Anthropic, Cohere, Azure OpenAI, 로컬 Ollama 모델 및 LangChain과 작동하는 모든 채팅 모델의 모델을 포함해요. 다른 LLM에 대한 사용자 정의 인터페이스를 구현할 수도 있고요.

마찬가지로 OpenAI의 기본값인 text-embedding-ada-002를 임베딩 모델로 사용할 건데요, 다른 임베더도 다른 공급자로부터 사용할 수 있어요.

import neo4j
from neo4j_graphrag.llm import OpenAILLM
from neo4j_graphrag.embeddings.openai import OpenAIEmbeddings

llm=OpenAILLM(
   model_name="gpt-4o-mini",
   model_params={
       "response_format": {"type": "json_object"}, # use json_object formatting for best results
       "temperature": 0 # turning temperature down for more deterministic results
   }
)

#create text embedder
embedder = OpenAIEmbeddings()

선택 입력: 스키마 & Prompt Template

필수 사항은 아니지만, Knowledge Graph의 품질을 높이고 싶다면 그래프 스키마를 추가하는 걸 추천해요. 엔티티 추출 중에 생성할 Node와 Relationship 유형에 대한 가이드라인을 제공하거든요.

전문가 팁: 어떤 스키마를 사용해야 할지 고민된다면, 먼저 스키마 없이 그래프를 만들어보고 가장 흔하게 생성되는 Node와 Relationship 유형을 살펴보는 것도 좋은 시작점이 될 수 있어요.

그래프 스키마는 추출하려는 엔티티(즉, Node Label)와 Relationship을 정의해요. 이 간단한 예시에서는 사용하지 않겠지만, potential_schema를 통해 어떤 Relationship이 어떤 Node에 연결되어야 하는지 안내할 수도 있답니다. 자세한 내용은 여기에서 확인해 보세요.

basic_node_labels = ["Object", "Entity", "Group", "Person", "Organization", "Place"]

academic_node_labels = ["ArticleOrPaper", "PublicationOrJournal"]

medical_node_labels = ["Anatomy", "BiologicalProcess", "Cell", "CellularComponent",
                      "CellType", "Condition", "Disease", "Drug",
                      "EffectOrPhenotype", "Exposure", "GeneOrProtein", "Molecule",
                      "MolecularFunction", "Pathway"]

node_labels = basic_node_labels + academic_node_labels + medical_node_labels

# define relationship types
rel_types = ["ACTIVATES", "AFFECTS", "ASSESSES", "ASSOCIATED_WITH", "AUTHORED",
   "BIOMARKER_FOR", …]

엔티티 추출을 위한 커스텀 Prompt도 추가할 수 있어요. GraphRAG Python 패키지에는 기본 Prompt가 내장되어 있지만, 사용 사례에 더 적합한 Prompt Engineering을 통해 더욱 유용한 Knowledge Graph를 만들 수 있는 경우가 많답니다. 아래 Prompt는 약간의 실험을 거쳐 만들어졌어요. 기본 Prompt와 동일한 일반 형식을 따라야 한다는 점, 잊지 마세요! 기본 Prompt를 참고하세요.

prompt_template = '''
You are a medical researcher tasks with extracting information from papers 
and structuring it in a property graph to inform further medical and research Q&A.

Extract the entities (nodes) and specify their type from the following Input text.
Also extract the relationships between these nodes. the relationship direction goes from the start node to the end node. 


Return result as JSON using the following format:
{{"nodes": [ {{"id": "0", "label": "the type of entity", "properties": {{"name": "name of entity" }} }}],
  "relationships": [{{"type": "TYPE_OF_RELATIONSHIP", "start_node_id": "0", "end_node_id": "1", "properties": {{"details": "Description of the relationship"}} }}] }}

- Use only the information from the Input text. Do not add any additional information.  
- If the input text is empty, return empty Json. 
- Make sure to create as many nodes and relationships as needed to offer rich medical context for further research.
- An AI knowledge assistant must be able to read this graph and immediately understand the context to inform detailed research questions. 
- Multiple documents will be ingested from different sources and we are using this property graph to connect information, so make sure entity types are fairly general. 

Use only fhe following nodes and relationships (if provided):
{schema}

Assign a unique ID (string) to each node, and reuse it to define relationships.
Do respect the source and target node types for relationship and
the relationship direction.

Do not return any additional information other than the JSON in it.

Examples:
{examples}

Input text:

{text}
'''

`SimpleKGPipeline` 생성

SimpleKGPipeline은 아래 생성자를 사용해서 만들 수 있어요.

from neo4j_graphrag.experimental.components.text_splitters.fixed_size_splitter import FixedSizeSplitter
from neo4j_graphrag.experimental.pipeline.kg_builder import SimpleKGPipeline

kg_builder_pdf = SimpleKGPipeline(
   llm=ex_llm,
   driver=driver,
   text_splitter=FixedSizeSplitter(chunk_size=500, chunk_overlap=100),
   embedder=embedder,
   entities=node_labels,
   relations=rel_types,
   prompt_template=prompt_template,
   from_pdf=True
)

SimpleKGPipeline의 다른 선택적 입력으로는 문서 로더와 Knowledge Graph 빌더 컴포넌트가 있어요. 전체 문서는 여기에서 확인할 수 있습니다.

Knowledge Graph 빌더 실행

Knowledge Graph 빌더는 run_async 메서드를 사용해서 실행할 수 있어요. 아래에서는 3개의 PDF를 반복해서 처리할 거예요.

pdf_file_paths = ['truncated-pdfs/biomolecules-11-00928-v2-trunc.pdf',
            'truncated-pdfs/GAP-between-patients-and-clinicians_2023_Best-Practice-trunc.pdf',
            'truncated-pdfs/pgpm-13-39-trunc.pdf']

for path in pdf_file_paths:
    print(f"Processing : {path}")
    pdf_result = await kg_builder_pdf.run_async(file_path=path)
    print(f"Result: {pdf_result}")

완료되면 통합 콘솔에서 결과 Knowledge Graph를 탐색할 수 있어요. 콘솔은 아주 훌륭한 인터페이스를 제공하죠.

쿼리 탭으로 이동해서 아래 쿼리를 입력하면 그래프 샘플을 볼 수 있습니다.

MATCH p=()-->() RETURN p LIMIT 1000;

Document, Chunk, 그리고 __Entity__ 노드들이 어떻게 연결되어 있는지 확인할 수 있을 거예요.

See how the Document, Chunk, and __Entity__ nodes are all connected together.

“어휘적” 그래프 부분을 보려면, 다음을 포함하는 DocumentChunk node에서 다음을 실행해 보세요.

일치 p=(:Chunk)--(:!__Entity__) RETURN p;

이 쿼리는 우리가 수집한 각 문서에 대해 하나씩 연결이 끊긴 컴포넌트들을 보여줘요. 각 Chunk에 추가된 Vector Embedding도 확인할 수 있죠.

These are disconnected components, one for each document we ingested.

이번에는 그래프를 살펴볼까요? __Entity__ node에서 다음 쿼리를 실행해 보세요.

일치 p=(:!Chunk)-->(:!Chunk) RETURN p; 

다양한 개념들이 어떻게 추출되었고, 서로 어떻게 연결되는지 확인할 수 있을 거예요. 이 도메인 그래프는 문서들 사이의 정보를 연결해 준답니다.

This domain graph connects information between the documents.

맞춤형 및 상세 Knowledge Graph 구축에 대한 참고사항

후드 아래에서 SimpleKGPipeline은 아래 나열된 구성 요소를 실행해요. GraphRAG 패키지는 낮은 수준의 파이프라인 API를 제공해서, Knowledge Graph 구축 프로세스를 꽤 많이 커스터마이징할 수 있다는 점! 자세한 내용은 선적 서류 비치를 참고하세요.

  • 문서 파서: PDF 같은 문서에서 텍스트를 추출해요.
  • 텍스트 분할기: LLM 컨텍스트 창에서 관리하기 쉽도록 텍스트를 작은 조각으로 분할하죠 (토큰 제한).
  • 청크 임베더: 각 청크에 대한 텍스트 Vector Embedding을 계산해요.
  • 스키마 빌더: 정확하고 쉽게 탐색할 수 있는 Knowledge Graph를 위해 LLM 엔터티 추출을 기반으로 하는 스키마를 제공합니다.
  • 엔터티 및 관계 추출기: 텍스트에서 관련 엔터티 및 관계를 추출해요.
  • 지식 정보 작성자: 식별된 엔터티 및 관계를 KG에 저장해요.
    The SimpleKGPipeline process.

2. Knowledge Graph에서 데이터 검색

GraphRAG Python 패키지는 Knowledge Graph에서 데이터를 검색하기 위한 여러 클래스를 제공하는데요, 한번 살펴볼까요?

  • 벡터 리트리버: Vector Embedding을 사용해서 유사성 검색을 수행해요.
  • 벡터 사이퍼 리트리버: Neo4j의 그래프 쿼리 언어인 Cypher의 검색 쿼리와 벡터 검색을 결합해서 그래프를 탐색하고 추가 `Node`와 `Relationship`을 통합합니다.
  • 하이브리드 리트리버: 벡터 검색과 전체 텍스트 검색을 결합합니다.
  • 하이브리드 사이퍼 리트리버: 추가 그래프 탐색을 위해 벡터 및 전체 텍스트 검색을 Cypher 검색 쿼리와 결합합니다.
  • Text2Cypher: Neo4j에 대해 실행하기 위해 Natural Language 쿼리를 Cypher 쿼리로 변환합니다.
  • Weaviate 및 Pinecone Neo4j 리트리버: Weaviate 또는 Pinecone에 저장된 벡터를 검색하고 외부 ID 속성을 사용하여 Neo4j의 `Node`에 연결할 수 있어요.
  • : 특정 요구 사항에 따라 맞춤형 검색 방법을 허용합니다.

이러한 검색기를 사용하면 다양한 데이터 검색 패턴을 구현해서 RAG 파이프라인의 관련성과 정확성을 높일 수 있어요.

방금 만든 그래프에서 몇 가지를 살펴볼게요.

벡터 리트리버

벡터 리트리버는 ANN(Approximate Nearest Neighbor) 벡터 검색을 사용해서 Knowledge Graph에서 데이터를 검색합니다.

이 검색기가 청크 `Node`에서 정보를 가져올 수 있도록 Neo4j에서 벡터 `Index`를 생성할 수 있어요.

from neo4j_graphrag.indexes import create_vector_index

create_vector_index(driver, name="text_embeddings", label="Chunk",
                   embedding_property="embedding", dimensions=1536, similarity_fn="cosine")

그런 다음 아래 코드를 사용해서 벡터 검색기를 인스턴스화할 수 있어요.

from neo4j_graphrag.retrievers import VectorRetriever

vector_retriever = VectorRetriever(
   driver,
   index_name="text_embeddings",
   embedder=embedder,
   return_properties=["text"],
)

이제 간단한 프롬프트로 이 검색기를 실행해 볼게요. 검색기는 텍스트 청크와 벡터 유사성 점수를 다시 가져와서 RAG의 질문에 답변하는 데 유용한 컨텍스트를 제공할 수 있답니다.

import json

vector_res = vector_retriever.get_search_results(query_text = "How is precision medicine applied to Lupus?",
             top_k=3)
for i in vector_res.records: print("====n" + json.dumps(i.data(), indent=4))


# output text chunks
====
{
    "node": {
        "text": "precise and systematic fashion as suggested here.nFuture care will involve molecular diagnostics throughoutnthe patient timecourse to drive the least toxic combinationnof therapies. Recent evidence suggests a paradigm shift isnon the way but it is hard to predict how fast it will come.nDisclosurenThe authors report no con ufb02icts of interest in this work.nReferencesn1. Lisnevskaia L, Murphy G, Isenberg DA. Systemic lupusnerythematosus. Lancet .2014 ;384:1878 u20131888. doi:10.1016/S0140-n6736(14)60128"
    },
    "score": 0.9368438720703125
}
====
{
    "node": {
        "text": "d IS agents.nPrecision medicine consists of a tailored approach toneach patient, based on genetic and epigenetic singularities,nwhich in ufb02uence disease pathophysiology and drugnresponse. Precision medicine in SLE is trying to addressnthe need to assess SLE patients optimally, predict diseasencourse and treatment response at diagnosis. Ideally everynpatient would undergo an initial evaluation that wouldnproufb01le his/her disease, assessing the main pathophysiolo-ngic pathway through biomarkers, ther"
    },
    "score": 0.935699462890625
}
====
{
    "node": {
        "text": "REVIEWnT owards Precision Medicine in Systemic LupusnErythematosusnThis article was published in the following Dove Press journal:nPharmacogenomics and Personalized MedicinenElliott Lever1nMarta R Alves2nDavid A Isenberg1n1Centre for Rheumatology, Division ofnMedicine, University College HospitalnLondon, London, UK;2Internal Medicine,nDepartment of Medicine, CentronHospitalar do Porto, Porto, PortugalAbstract: Systemic lupus erythematosus (SLE) is a remarkable condition characterised byndiversit"
    },
    "score": 0.9312744140625
}

Vector Cypher Retriever

탐색해볼 만한 또 다른 유용한 검색기는 Vector Cypher Retriever에요. 이걸 사용하면 Neo4j의 그래프 쿼리 언어인 Cypher를 사용해서 Vector Search로 초기 Node 세트를 검색한 다음, 그래프 순회 로직을 통합할 수 있어요. 아래에서는 Vector Search를 통해 청크 Node를 얻은 다음, 최대 3홉 아웃까지 엔터티를 탐색하는 검색기를 만들어볼게요.

from neo4j_graphrag.retrievers import VectorCypherRetriever

vc_retriever = VectorCypherRetriever(
   driver,
   index_name="text_embeddings",
   embedder=embedder,
   retrieval_query="""
//1) Go out 2-3 hops in the entity graph and get relationships
WITH node AS chunk
MATCH (chunk)<-[:FROM_CHUNK]-()-[relList:!FROM_CHUNK]-{1,2}()
UNWIND relList AS rel

//2) collect relationships and text chunks
WITH collect(DISTINCT chunk) AS chunks,
 collect(DISTINCT rel) AS rels

//3) format and return context
RETURN '=== text ===n' + apoc.text.join([c in chunks | c.text], 'n---n') + 'nn=== kg_rels ===n' +
 apoc.text.join([r in rels | startNode(r).name + ' - ' + type(r) + '(' + coalesce(r.details, '') + ')' +  ' -> ' + endNode(r).name ], 'n---n') AS info
"""
)

동일한 프롬프트를 제출하면 다음과 같은 컨텍스트가 반환될 거예요.

vc_res = vc_retriever.get_search_results(query_text = "How is precision medicine applied to Lupus?", top_k=3)

# print output
kg_rel_pos = vc_res.records[0]['info'].find('nn=== kg_rels ===n')
print("# Text Chunk Context:")
print(vc_res.records[0]['info'][:kg_rel_pos])
print("# KG Context From Relationships:")
print(vc_res.records[0]['info'][kg_rel_pos:])


# output
# Text Chunk Context:
=== text ===
precise and systematic fashion as suggested here.
Future care will involve molecular diagnostics throughout
the patient timecourse to drive the least toxic combination
of therapies. Recent evidence suggests a paradigm shift is
on the way but it is hard to predict how fast it will come.
Disclosure
The authors report no con flicts of interest in this work.
References
1. Lisnevskaia L, Murphy G, Isenberg DA. Systemic lupus
erythematosus. Lancet .2014 ;384:1878 –1888. doi:10.1016/S0140-
6736(14)60128
---
d IS agents.
Precision medicine consists of a tailored approach to
each patient, based on genetic and epigenetic singularities,
which in fluence disease pathophysiology and drug
response. Precision medicine in SLE is trying to address
the need to assess SLE patients optimally, predict disease
course and treatment response at diagnosis. Ideally every
patient would undergo an initial evaluation that would
profile his/her disease, assessing the main pathophysiolo-
gic pathway through biomarkers, ther
---
REVIEW
T owards Precision Medicine in Systemic Lupus
Erythematosus…


=== kg_rels ===
Systemic lupus erythematosus - AUTHORED(Published in) -> N. Engl. J. Med.
---
Lisnevskaia L - AUTHORED() -> Systemic lupus erythematosus
---
Murphy G - AUTHORED() -> Systemic lupus erythematosus
---
Isenberg DA - AUTHORED() -> Systemic lupus erythematosus
---
Systemic lupus erythematosus - CITES(Published in) -> Lancet
---
Systemic lupus erythematosus - CITES(Systemic lupus erythematosus is discussed in the Lancet publication.) -> Lancet
---
Systemic lupus erythematosus - ASSOCIATED_WITH(SLE is characterized by aberrant activity of the immune system) -> Aberrant activity of the immune system
---
Immunological biomarkers - USED_FOR(Immunological biomarkers could diagnose and monitor disease activity in SLE) -> Systemic lupus erythematosus
---
Novel SLE biomarkers - DESCRIBES(Novel SLE biomarkers have been discovered through omics research) -> Systemic lupus erythematosus
—

콘솔에 시각화하면 벡터 검색의 텍스트 청크와 엔터티, 그리고 연결된 Knowledge Graph 관계의 조합인 아래 하위 그래프가 반환되는 것을 확인할 수 있어요.

A combination of the text chunks from vector search, plus the entities and connected knowledge graph relationships.

Vector Cypher Retriever는 관계 집합을 (Node)-[RELATION]->(Node) 형식으로 반환해서 엔티티 그래프를 텍스트화해요. 이는 데이터베이스의 다양한 부분에서 얻은 인사이트를 결합해서 여러 청크와 잠재적으로 여러 문서를 연결하죠. 개별 텍스트 덩어리를 보는 대신 엔티티 간의 관계를 통해 더 광범위하고 상호 연결된 정보를 공개할 수 있어서 나중에 더 복잡한 쿼리에 답할 수 있게 돼요. 관계의 명시적인 특성은 사실이 어떻게 도출되는지 정확하게 볼 수 있게 해주므로 설명 가능성을 높여줘요. 위에서 다룬 Knowledge Graph 구성을 조정해서 사용 사례에 더 잘 맞도록 이러한 관계의 품질을 반복할 수도 있고요.

다음 섹션에서는 다양한 검색기가 어떻게 작동하는지 확인하기 위해 호출할 수 있는 GraphRAG 객체를 만들어 볼 거예요.

3. GraphRAG 인스턴스화 및 실행

GraphRAG Python 패키지를 사용하면 GraphRAG 파이프라인을 쉽게 인스턴스화하고 실행할 수 있어요. 전용 GraphRAG 클래스를 사용할 수 있는데, 최소한 생성자에 LLMRetriever를 전달해야 해요. 선택적으로 사용자 정의 Prompt Template도 넣을 수 있죠. 여기서는 LLM이 데이터 소스의 정보를 잘 따르도록 좀 더 자세한 지침을 제공하기 위해 그렇게 할 거예요.

아래에서는 벡터 및 벡터 Cypher 검색기 모두에 대한 GraphRAG 객체를 만들 거예요.

from neo4j_graphrag.llm import OpenAILLM as LLM
from neo4j_graphrag.generation import RagTemplate
from neo4j_graphrag.generation.graphrag import GraphRAG

llm = LLM(model_name="gpt-4o",  model_params={"temperature": 0.0})

rag_template = RagTemplate(template='''Answer the Question using the following Context. Only respond with information mentioned in the Context. Do not inject any speculative information not mentioned.

# Question:
{query_text}

# Context:
{context}

# Answer:
''', expected_inputs=['query_text', 'context'])

v_rag  = GraphRAG(llm=llm, retriever=vector_retriever, prompt_template=rag_template)
vc_rag = GraphRAG(llm=llm, retriever=vc_retriever, prompt_template=rag_template)

이제 간단한 질문을 던져보고 다양한 Knowledge Graph 검색 패턴이 어떻게 비교되는지 확인해 볼까요?

q = "How is precision medicine applied to Lupus? provide in list format."

print(f"Vector Response: n{v_rag.search(q, retriever_config={'top_k':5}).answer}")
print("n===========================n")
print(f"Vector + Cypher Response: n{vc_rag.search(q, retriever_config={'top_k':5}).answer}")
벡터 RAG 응답 벡터 + Cypher RAG 응답
  • 루푸스의 정밀의학은 유전적, 후생적 특이성에 기반한 맞춤형 접근 방식을 사용해요.
  • 루푸스 환자를 최적으로 평가하고 진단 시 질병 경과 및 치료 반응을 예측하는 것을 목표로 하죠.
  • 초기 평가에서는 바이오마커를 통해 주요 병태생리학적 경로를 평가하여 환자의 질병을 프로파일링해요.
  • 루푸스의 정밀의학은 유전적 및 후생적 특이점을 기반으로 각 환자에 대한 맞춤형 접근 방식을 사용해요.
  • 루푸스 환자를 최적으로 평가하고, 질병 경과 및 진단 시 치료 반응을 예측하는 것을 목표로 해요.
  • 이상적으로 모든 환자는 바이오마커를 통해 주요 병리생리학적 경로를 평가하여 질병의 프로필을 작성하기 위한 초기 평가를 받게 돼요.

이와 같은 간단한 질문에 대한 답변은 다양한 Knowledge Graph 벡터 검색 접근 방식 간에 비슷할 수 있어요. 두 답변 모두 유사한 정보를 포함하고 있다는 걸 알 수 있죠. 다른 간단한 질문에 대해서는 응답이 약간 더 일반적이거나 더 광범위한 반면, 벡터 + Cypher 응답은 의학 연구를 위해 특별히 구성된 Knowledge Graph의 도메인/엔티티 부분을 가져오기 때문에 좀 더 도메인 특정적이거나 기술적이에요 (엔티티 추출 프롬프트 및 스키마를 다시 살펴보면 알 수 있어요).

물론 검색 방법을 조정하고 결합해서 이러한 응답을 더욱 향상시킬 수 있어요. 이건 단지 시작의 예일 뿐이에요. 여러 텍스트 덩어리에서 정보를 가져와야 하는 좀 더 복잡한 질문을 해볼게요.

q = "Can you summarize systemic lupus erythematosus (SLE)? including common effects, biomarkers, and treatments? Provide in detailed list format."

v_rag_result = v_rag.search(q, retriever_config={'top_k': 5}, return_context=True)
vc_rag_result = vc_rag.search(q, retriever_config={'top_k': 5}, return_context=True)

print(f"Vector Response: n{v_rag_result.answer}")
print("n===========================n")
print(f"Vector + Cypher Response: n{vc_rag_result.answer}")

산출:

벡터 RAG 응답 벡터 + Cypher RAG 응답
전신 홍반 루푸스 (Systemic Lupus Erythematosus, SLE) 개요:
  • SLE는 면역 체계의 비정상적인 활동을 특징으로 하는 전신 자가면역 질환이에요.
  • 다양한 임상 증상을 나타내며 다양한 장기에 손상을 줄 수 있죠.
일반적인 효과:
  • SLE는 환자의 삶에 상당한 부담을 주며 HRQoL(건강 관련 삶의 질)에 영향을 미쳐요.
  • 질병 활동 통제에 대한 의사의 초점과 삶의 질에 영향을 미치는 증상에 대한 환자의 초점 사이에는 차이가 있대요.
바이오마커:
  • SLE 진단 및 모니터링에는 다양한 기관의 면역 반응성과 염증을 반영하는 실험실 바이오마커가 포함돼요.
  • 일반적인 바이오마커는 장기 특이적 손상 여부에 관계없이 질병 활동을 진단하고 모니터링하는 데 사용된대요.
  • '오믹스(omics)' 연구를 통해 새로운 바이오마커가 발견되었대요.
치료:
  • 의사는 피해 발생을 방지하기 위해 질병 활동을 통제하는 것을 목표로 해요.
  • 치료 접근법에는 임상 증상과 환자의 삶의 질에 미치는 영향을 모두 다루는 것이 포함된대요.
전신홍반루푸스(SLE) 요약:일반적인 효과:
  • 다양한 증상을 보이는 복잡한 임상상
  • 다양한 심각도와 예측할 수 없는 재발 및 완화 과정
  • 만성 전신 염증
  • 증상으로는 신장염, 관절염, 혈관염, 피로, 광범위한 신체 통증, 우울증, 불안, 인지 기능 장애, 수면 장애, 말라 발진, 미만성 탈모증, 근육통, 발열, 발진, 피부 혈관염, 신장 문제, 흉막염, 심낭염, 혈소판 감소증 및 용혈성 빈혈이 있어요.
바이오마커:
  • 항핵항체(ANA)
  • 항dsDNA 항체
  • 항Sm 항체
  • 항뉴클레오솜 항체(ANuA)
  • 항-C1q 항체
  • 높은 ESR 값과 낮은 CRP 수준
  • 단백뇨, 뇨 원주, 망상적혈구증가증을 동반한 용혈성 빈혈, 백혈구 수
  • 항인지질항체, 항카디오리핀, 루푸스항응고제
  • 기능 장애가 있는 고밀도 지질단백질
  • IFI44L 메틸화 수준
치료:
  • 항말라리아제
  • 글루코코르티코이드 치료 (예: Prednisone)
  • 면역억제제(IS)
  • 아니프루맙
  • 아타시셉트
  • 수술, 물리치료, 비처방약, 스포츠, 가족 및 동료 지원, 식이요법, 심리적 측면, 운동을 포함한 다학제적 접근
관리 전략:
  • 완화 또는 낮은 질병 활성도를 목표로 하는 치료 목표 전략
  • SLE 질병 활동 지수(SLEDAI)를 사용하여 질병 활동 모니터링
  • 건강 관련 삶의 질(HRQoL)을 평가하기 위한 환자 보고 결과(PRO)
  • 환자-의사 의사소통 격차 해소
메모:
SLE는 치료할 수 없지만 증상을 조절할 수 있고 진행을 지연시킬 수 있대요.

이러한 답변은 더 중요한 차이를 보여주는데요. 그만큼Vector + Cypher 응답은 특히 바이오마커와 치료에 대해 더 자세하고 구체적이어서, 해당 주제에 대한 깊이를 추구하는 전문가에게 더 유용할 거예요. Vector 응답은 합리적으로 잘 구조화되어 있고 일반적인 효과에 대한 좋은 개요를 제공하지만, 기술적인 세부 사항이 완벽하지는 않아요. 문서에는 도메인별 관련 정보가 많이 포함되어 있지 않거든요.

이 경우 Vector + Cypher 응답은 여러 문서와 텍스트 청크의 정보를 연결하는 Knowledge Graph의 entities와 relationships를 사용하므로, 여러 소스의 정보를 요구하는 질문에 더 효과적이에요.

반환된 context를 비교하면 Vector 전용 검색기가 특정 단어 "biomarker"를 포함하는 시스템적으로 유사한 텍스트 덩어리를 반환했다는 것을 알 수 있어요. 하지만 특정 바이오마커와 같은 특정 기술적 세부 정보는 포함되어 있지 않죠.

for i in v_rag_result.retriever_result.items: print(json.dumps(eval(i.content), indent=1))

#output 
{
 "text": "ld be noted that nreproducibility and reliability may be affected by laboratory errors, specific techniques, nor changes in storage [13]. Because SLE can cause damage to various organs, has a complex pathogenesis, and displays heterogeneous clinical manifestations, one nparticular biomarker may only reflect one specific aspect of SLE but not be useful for nreflecting the state of the disease as a whole [14,15]. n nFigure 1. Common biomarkers for SLE and their measur ement sites in patients with "
}
{
 "text": "agnosed and classified based on a patientu2019s clinical symptoms, signs, and nlaboratory biomarkers that reflect immune reactivity and inflammation in various norgans. It is necessary to develop consistent cl assification criteria of SLE for research and nclinical diagnosis. The most widely used classification criteria for SLE was established by the American College of Rheumatology (ACR) and contains laboratory biomarkers, nFigure 1. Common biomarkers for SLE and their measurement sites in patient"
}
{
 "text": "s nElena Elefantea, Alain Cornetb, Jeanette Andersenb, Amy Somersb, Marta Moscaa,* naRheumatology Unit, Department of Clinical and Experimental Medicine, Azienda Ospedaliero-Universitaria Pisana, Italy nbLupus Europe, Brussels, Belgium   nARTICLE INFO  nKeywords: nSystemic Lupus Erythematosus nPatient-reported outcomes nPatient-clinician communication gap nHealth-related quality of life ABSTRACT  nSystemic Lupus Erythematosus (SLE) imposes a great burden on the lives of patients. Patients u2019 and "
}
{
 "text": "immunological biomarkers that could diagnose and monitor disease activity in SLE, with andnwithout organ-speciufb01c injury. In addition, novel SLE biomarkers that have been discovered throughnu201comicsu201d research are also reviewed.nKeywords: systemic lupus erythematosus; biomarkers; diagnosis; monitoring; omicsn1. IntroductionnSystemic lupus erythematosus (SLE) is a systemic autoimmune disease characterizednby aberrant activity of the immune system [ 1] and presents with a wide range of clinicalnmanife"
}
{
 "text": "nSystemic Lupus Erythematosus (SLE) imposes a great burden on the lives of patients. Patients u2019 and nphysicians u2019 concerns about the disease diverge considerably. Physicians focus on controlling ndisease activity to prevent damage accrual, while patients focus on symptoms that impact on nHealth-Related Quality of Life (HRQoL). We explored the physicians u2019 and patients u2019 perspective nand the potential role of Patient Reported Outcomes (PROs). Physicians are aware of the theo-nretical usefulness o"
}

반면에, 반환된 컨텍스트에서 바이오마커를 검색하면 Vector + Cypher를 통해 도메인별 컨텍스트를 다루는 관계를 볼 수 있을 거예요. 다음은 출력의 샘플입니다.

vc_ls = vc_rag_result.retriever_result.items[0].content.split('\n---\n')
for i in vc_ls:
   if "biomarker" in i: print(i)

#output (sample)

…
IP-10 - BIOMARKER_FOR(IP-10 is a biomarker for SLE) -> SLE
…
ANA - BIOMARKER_FOR(ANA is highly characteristic of SLE and can be used as a biomarker.) -> SLE
anti-Sm antibodies - BIOMARKER_FOR(Presence of anti-Sm antibodies serves as a biomarker for SLE classification) -> SLE
…
dysfunctional high-density lipoprotein - BIOMARKER_FOR(dysfunctional high-density lipoprotein is a key biomarker for SLE patients with CVD) -> SLE
Anti-dsDNA antibodies - ASSOCIATED_WITH(Anti-dsDNA antibodies are biomarkers associated with SLE.) -> SLE
…

치료법에 대해서도 똑같이 해보면 비슷한 패턴을 확인할 수 있어요.

궁극적으로 GraphRAG Python 패키지는 조정, 결합 및 사용자 정의할 수 있는 데이터 검색 방법과 여러 가지 다른 데이터 검색 방법을 제공해요. GenAI 애플리케이션에서 GraphRAG 파이프라인을 설계하기 위한 다양한 옵션이 있는 거죠.

GraphRAG 경험을 단순화하는 리소스

GraphRAG Python 패키지를 사용하면 문서에서 Knowledge Graph 생성 및 검색으로 전환하는 것이 훨씬 쉬워져요. Neo4j 전문가가 될 필요도 없고 많은 코드를 작성할 필요도 없죠. 또한 일반적인 GraphRAG 패턴을 특징으로 하고 개발 경험을 단순화하는 데 도움이 되는 가이드와 클래스를 준비했어요. 다음 리소스를 꼭 확인해 보세요.

  • GraphRAG Python 패키지 문서
  • 심층적인 블로그 게시물:
    • 구조화된 Knowledge Graph 시작하기
    • 그래프 탐색으로 Vector Search 강화(VectorCypherRetriever 기능)
    • GraphRAG 애플리케이션을 위한 하이브리드 검색(하이브리드 벡터/텍스트 검색)
    • Cypher 탐색을 통한 하이브리드 벡터/텍스트 검색
  • Graph Academy 및 코딩된 예제를 포함한 GraphRAG 및 GenAI에 대한 기타 학습 리소스:
    • GraphAcademy: Retrieval-Augmented Generation(RAG) 마스터링
    • GenAI 리소스 살펴보기
  • GraphRAG
  • Python
  • RAG

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

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

반응형
반응형

이전 블로그에서는 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는 큰 도전에 직면합니다.

여기서 우리는 Knowledge Graph의 힘을 활용할 거예요. Vector Search에서 제목을 반환할 뿐만 아니라, 제목이 TV 시리즈인 경우 모든 에피소드도 포함하고, 에피소드인 경우(이 경우처럼) 시리즈와 다른 모든 에피소드를 포함하도록 쿼리를 확장하죠.

WITH question, genai.vector.encode($question, "OpenAI", {token: $apiKey}) AS embedding
CALL db.index.vector.queryNodes('synopsis_embeddings', 1, embedding) YIELD node AS title, score

OPTIONAL MATCH (title)-[:EPISODE_OF]->(series:Title)
OPTIONAL MATCH (title)<-[:EPISODE_OF]-(other1:Title)
OPTIONAL MATCH (series)<-[:EPISODE_OF]-(other2:Title)
WITH collect(title)+collect(series)+collect(other1)+collect(other2) AS allTitles
UNWIND allTitles AS titles
RETURN DISTINCT titles

이는 다음과 같은 결과를 제공해줘요.

왕좌의 게임 및 모든 에피소드

RAG

이제 실제 RAG 부분이 나오는데요. 여기서는 모든 개요를 수집하고 ChatGPT에 이를 기반으로 질문에 대한 답변을 요청할 거예요. 이를 위해서는 OpenAI에 REST 호출을 수행해야 하죠. 이는 (현재) Neo4j에 내장된 절차가 아니므로 자체 애플리케이션에서 수행해야 하거나 Neo4j의 자체 인스턴스를 호스팅하고 APOC Extended를 실행할 수 있는 경우 거기에서 절차를 사용할 수 있어요. 저는 후자를 선택할 거예요.

ChatGPT에 물어볼 질문은 다음과 같아요.

Answer the following Question based on the Context only and provide a reference to where in the context you found it.
Only answer from the Context.
If you don't know the answer, say 'I don't know'.
The context is a list of titles followed by a synopsis, separated by colon.

Context: [All titles and synopsises separated by colon]

Question: [Our original question]

다음은 APOC 확장 프로시저를 통해 해당 질문이 처리되는 전체 Cypher 쿼리의 모습이에요.

WITH question, genai.vector.encode($question, "OpenAI", {token: $apiKey}) AS embedding
CALL db.index.vector.queryNodes('synopsis_embeddings', 1, embedding) YIELD node AS title, score

OPTIONAL MATCH (title)-[:EPISODE_OF]->(series:Title)
OPTIONAL MATCH (title)<-[:EPISODE_OF]-(other1:Title)
OPTIONAL MATCH (series)<-[:EPISODE_OF]-(other2:Title)
WITH collect(title.primaryTitle + ":" + title.synopsis)+collect(series.primaryTitle + ":" + series.synopsis)+collect(other1.primaryTitle + ":" + other1.synopsis)+collect(other2.primaryTitle + ":" + other2.synopsis) AS allTitles
UNWIND allTitles AS titles
WITH DISTINCT titles
WITH apoc.text.join(collect(titles), "n") AS context

CALL apoc.ml.openai.chat([
{role:"user", content:"Answer the following Question based on the Context only and provide a reference to where in the context you found it. Only answer from the Context. If you don't know the answer, say 'I don't know'. The context is a list of titles followed by a synopsis, separated by colon.nnContext: " + context + "nnQuestion: " + $question}
], $apiKey) yield value
RETURN value.choices[0].message.content AS Title

우리가 돌려받는 대답은 The Lannisters이에요. "북쪽은 기억합니다: Robb Stark와 그의 북부 군대가 Lannisters와의 전쟁을 계속하면서..." — 환각이 없으며 출처 참조가 제공되죠.

다음 레벨

제가 이것을 동료에게 보여주었을 때 그는 "멋지네요. 하지만 ChatGPT는 Game of Thrones에 대해 알고 있고 어쨌든 답변을 제공할 수 있었을 거예요. ChatGPT가 교육을 받았을 때 존재하지 않았던 타이틀로 어떻게 작동할 수 있었겠어요?"라고 말했어요.

당시 ChatGPT는 2023년 4월에 마지막 학습을 했어요. 그래서 2023년 4월부터 2024년 2월 사이에 출시된 것을 제가 본 적이 있는 것을 찾아서 이에 대한 질문을 할 수 있었죠. 제가 생각한 것은 영화 아가일이었어요. 제가 생각해낸 질문은 다음과 같아요.

엘리 콘웨이의 고양이 이름은 무엇인가요?

우선 RAG 없이 평범한 Prompt로 ChatGPT에 문의했더니 돌아온 대답은 "엘리 콘웨이의 고양이 이름은 루시입니다. 엘리 콘웨이는 'Big Little Lies', 'Nine Perfect Strangers' 등의 작품으로 유명한 호주 작가 Liane Moriarty의 가명입니다. Lucy는 작가와 관련된 인터뷰와 소셜 미디어 게시물에서 자주 언급됩니다." 였어요.

여기서 우리는 환각에 걸린 것 같아요. ChatGPT는 영화가 늦게 개봉해서 몰랐을 뿐만 아니라, “모른다”는 대신 거짓 답변을 내놓았어요. Elly Conway라는 가명을 사용하는 Liane Moriarty에 대한 정보는 전혀 찾을 수 없죠.

하지만 위에서 사용했던 것과 똑같은 RAG 쿼리를 실행하면, 이렇게 완벽하게 정확한 답변을 얻을 수 있어요. "Alfie (맥락: 은둔 작가 Elly Conway의 가상 스파이 소설 줄거리가 실제 스파이 조직의 은밀한 행동을 반영하기 시작하면서, 집에서 조용한 저녁 시간은 과거의 일이 됩니다. 그녀의 고양이 Alfie와 고양이 알레르기가 있는 스파이 Aiden과 함께…)."

짜잔! 거의 순수한 Cypher로 GraphRAG를 구현했어요.

추가 자료

앞서 말씀드렸듯이, Neo4j는 GraphRAG 애플리케이션을 더 쉽게 만들 수 있도록 다양한 AI 기술 스택과 통합되어 있어요. 관련해서 더 자세한 내용을 다루는 글들을 소개해 드릴게요.

  • LLM Knowledge Graph Builder: 5분 만에 0에서 GraphRAG까지
  • GenAI 생태계 – Neo4j Labs


  • GraphRAG
  • RAG

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

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

반응형
반응형

우리는 RAG의 "블루 링크" 시대를 맞이하고 있습니다

저희가 GenAI를 사용해서 정말 유용한 작업을 하려면 의지할 수 없다고 해요. 오직 auto-regressive LLM만 사용해서 결정을 내릴 수는 없다는 거죠. 무슨 생각을 하시는지 알아요. "RAG가 답이다!", "Fine-tuning이 답이다!", "GPT-5가 답이다!" 라고 생각하시겠죠.

네, Vector 기반 RAG나 Fine-tuning 같은 기술이 도움이 될 수 있어요. 어떤 사용 사례에서는 충분할 수도 있겠죠. 하지만 이런 기술들이 모두 한계에 부딪히는 사용 사례들이 또 있다는 거예요. Vector 기반 RAG는 Fine-tuning과 마찬가지로 다양한 질문에 대한 정답을 맞힐 확률을 높여주긴 해요. 하지만 어떤 기술도 정답이라는 확신을 주지는 않죠. 종종 맥락, 색깔, 그리고 연결이 부족해요. 여러분은 그게 사실이라는 걸 알고 있잖아요. 게다가 이런 도구들은 왜 그런 특별한 결정을 내렸는지에 대한 단서를 많이 남기지 않아요.

2012년에 Google은 "Introducing the Knowledge Graph: Things, Not Strings"라는 상징적인 블로그 게시물을 통해 2세대 검색 엔진을 선보였어요.1 그들은 Knowledge Graph를 사용해서 정보를 정리하면 역량이 크게 향상될 수 있다는 것을 발견했어요. 모든 웹페이지의 문자열로 표시되는 것 외에도 things를 처리하는 역량이요. 저희는 오늘날 GenAI에서도 이와 똑같은 패턴이 펼쳐지는 것을 보고 있어요. 많은 GenAI 프로젝트가 한계점에 부딪히고 있어요. 결과의 품질은 사용 중인 솔루션이 things가 아닌 strings를 처리하고 있다는 사실에 따라 결정되는 거죠.

시간을 확 돌려서 오늘날, AI 엔지니어와 최첨단 학술 연구자들은 Google이 했던 것과 똑같은 것을 발견하고 있어요. 즉, 이 한계를 돌파하는 비결은 Knowledge Graph라는 거죠! 즉, 지식을 things로 가져와서 통계 기반 텍스트 기술과 혼합해서 사용하는 거예요. 이건 Vector index 외에 Knowledge Graph 호출을 제외하고는 다른 유형의 RAG와 똑같이 작동해요. 다른 말로 하면, GraphRAG인 거죠!

이 게시물은 GraphRAG를 포괄적이고 읽기 쉽게 다루기 위해 작성되었어요. 데이터에 대한 Knowledge Graph를 구축하고 이를 RAG에서 사용하면 몇 가지 강력한 이점을 얻을 수 있다는 것이 밝혀졌어요. 일반적인 Vector 전용 RAG를 사용해서 LLM에 문의할 수 있는 모든 질문에 대해 더 나은 답변을 제공한다는 것을 입증하는 강력한 연구가 있답니다.

그것만으로도 GraphRAG 채택의 큰 원동력이 될 거예요. 게다가 앱을 구축할 때 데이터가 표시되니까 개발이 더 쉬워지죠. 세 번째 주요 이점은 기계뿐만 아니라 사람도 그래프를 쉽게 이해하고 추론할 수 있다는 거예요. 따라서 GraphRAG를 사용해서 구축하는 것이 더 쉽고, 더 나은 결과를 제공하며, 설명 및 감사가 가능하다는 건데, 이는 많은 산업 분야에서 매우 중요한 기능이에요! 저는 GraphRAG가 Vector 전용 RAG를 포함하고 대부분의 사용 사례에서 기본 RAG 아키텍처로 등장할 거라고 믿어요. 이 게시물에서는 그 이유를 설명할게요.

잠깐, 그래프?

그래프라고 하면 다음과 같은 의미라는 점을 분명히 해두고 싶어요.

아니면 이런 모습일 수도 있구요:

A Game of Thrones graph.
왕좌의 게임 그래프 시각화, William Lyon 작성.

아니면 다음과 같을 수도 있겠네요:

음, 그러니까 인 거죠.

만약 여러분이 더 자세히 알아보고 싶다면, 그래프와 Knowledge Graph를 배우기 위해 Neo4j의 GraphAcademy나 Andrew Ng의 Deeplearning.ai 과정인 RAG에 대한 Knowledge Graph를 살펴보는 걸 추천드려요. 여기서는 정의에만 머물지 않고 그래프에 대한 기본적인 실무 지식을 전제로 계속 진행할게요.

위 그림을 이해하면 RAG 파이프라인의 일부로 기본적인 그래프 데이터(Graph Database에 저장된)를 `Query`하는 방법을 알 수 있을 거예요. 이게 바로 GraphRAG의 핵심 내용이랍니다.

두 가지 유형의 지식 표현: 벡터 및 그래프

일반적인 RAG의 핵심인 벡터 검색은 텍스트 덩어리를 가져와 서면 자료의 후보 본문에서 개념적으로 유사한 텍스트를 반환해요. 이건 정말 신기하고 기본적인 검색에 아주 유용하죠.

이 작업을 수행할 때마다 생각하지 못할 수도 있는 것은 벡터의 모양이나 유사성 계산이 수행하는 작업인데요. 인간의 용어, 벡터 용어, 그래프 용어로 사과를 한번 살펴볼까요?

인간의 표현은 복잡하고 다차원적이며 종이에 완전히 담아낼 수 있는 것이 아니에요. 시적 허용을 허용하고 이 아름답고 유혹적인 그림이 모든 측면에서 사과를 나타낸다고 상상해 봅시다. 지각적인& 개념적 영광.

사과의 Vector Embedding2은 숫자의 배열, 즉 통계 영역의 구성 요소에요. Vector의 매력은 각각 해당 텍스트의 본질을 인코딩된 형식으로 포착한다는 점이죠. 하지만 Retrieval-Augmented Generation (RAG) 컨텍스트에서는 한 줌의 단어가 다른 단어와 얼마나 유사한지 식별해야 할 때만 가치가 있어요. 이를 수행하는 것은 유사성 계산(일명 벡터 수학)을 실행하고 일치 항목을 찾는 것만큼 간단하죠. 하지만 벡터 내부에 무엇이 있는지, 주변에 무엇이 있는지, 텍스트에 표현된 내용을 처리하고, 이러한 내용이 더 큰 맥락에 어떻게 적용되는지 이해하고 싶다면, 표현으로서의 벡터로는 그렇게 할 수 없어요.

이와 대조적으로 Knowledge Graph는 세계에 대한 선언적(AI 용어로 상징적) 표현이에요. 결과적으로 인간과 기계 모두 Knowledge Graph를 이해하고 추론할 수 있죠. 이건 나중에 다시 다루게 될 중요한 부분이에요. Knowledge Graph를 쿼리하고, 시각화하고, 주석을 달고, 수정하고, 늘릴 수도 있고요. Knowledge Graph는 세계 모델을 나타내는데3, 작업 중인 도메인을 나타내는 세계의 일부라고 생각하면 돼요.

GraphRAG vs 조각

경쟁은 아니에요 🙂 벡터 및 그래프 쿼리는 각각 RAG에 가치를 더해주죠. LlamaIndex의 창립자인 Jerry Liu가 지적했듯이, GraphRAG를 벡터를 포함한다고 생각하는 것이 도움이 될 거예요. 이는 텍스트의 단어를 기반으로 한 임베딩과의 유사성에만 의존하는 "벡터 전용 RAG"와는 다르다는 거죠.

기본적으로 GraphRAG는 검색 경로에 Knowledge Graph가 포함된 RAG에요. 아래에서 볼 수 있듯이 핵심 GraphRAG 패턴은 간단해요. 기본적으로 벡터를 사용하는 RAG와 동일한 아키텍처이지만4, Knowledge Graph가 추가된 형태라고 보면 돼요.

GraphRAG 패턴

여기서는 그래프 쿼리가 실행되는 것을 볼 수 있어요. 선택적으로 벡터 유사성 구성 요소를 포함할 수도 있고요. 그래프와 벡터를 서로 다른 데이터베이스에 별도로 저장하거나, 벡터 검색도 지원하는 Neo4j와 같은 Graph Database를 사용할 수도 있겠죠.

GraphRAG를 사용하는 일반적인 패턴 중 하나는 다음과 같아요.

  1. 초기 Node 세트를 찾으려면 벡터 또는 키워드 검색을 수행하세요.
  2. 관련 Node에 대한 정보를 다시 가져오려면 그래프를 탐색하세요.
  3. 선택적으로 PageRank와 같은 그래프 기반 순위 알고리즘을 사용하여 문서 순위를 다시 매겨보세요.

패턴은 사용 사례에 따라 달라지는데, 요즘 AI의 다른 모든 것과 마찬가지로 GraphRAG는 매주 새로운 발견이 등장하면서 무궁무진한 가능성을 보여주고 있어요. 앞으로 가장 흔하게 볼 수 있는 GraphRAG 패턴에 대해 블로그 포스팅을 할 예정이니 기대해주세요!

GraphRAG 수명 주기

GraphRAG를 사용하는 GenAI 애플리케이션은 시작 시 "그래프 생성" 단계가 추가되어 RAG 애플리케이션과 동일한 패턴을 따르게 돼요.

The GraphRAG lifecycle.

그래프를 만드는 과정은 문서를 청크(chunk)해서 벡터 데이터베이스에 로드하는 것과 비슷해요. 도구가 발전하면서 그래프 생성이 정말 쉬워졌답니다. 좋은 소식은 세 가지나 있어요.

  1. 그래프는 반복적인 성격이 강해요. "최소 실행 가능한 그래프(minimum viable graph)"로 시작해서 점점 확장해 나갈 수 있죠.
  2. 데이터가 Knowledge Graph에 들어가면 진화시키기가 정말 쉬워져요. 더 다양한 종류의 데이터를 추가해서 데이터 네트워크 효과를 누릴 수도 있고요. 데이터 품질을 높여서 애플리케이션 결과의 가치를 올릴 수도 있답니다.
  3. 스택(stack)의 이 부분이 빠르게 개선되고 있다는 점이에요. 즉, 도구가 점점 더 정교해짐에 따라 그래프 생성도 훨씬 더 쉬워질 거라는 거죠.

이전 그림에 그래프 생성 단계를 추가하면 다음과 같은 파이프라인을 얻을 수 있어요.

나중에 그래프 생성에 대해 더 자세히 알아볼게요. 지금은 잠시 미뤄두고 GraphRAG의 장점에 대해 이야기해 봅시다.

왜 GraphRAG인가?

벡터 전용 RAG와 비교했을 때 GraphRAG에서 얻을 수 있는 이점은 크게 세 가지로 나눌 수 있어요.

  1. 더 높은 정확도와 더 완전한 답변 (런타임/프로덕션 혜택)
  2. Knowledge Graph를 만들고 나면 RAG 애플리케이션을 구축하고 유지 관리하는 게 더 쉬워져요 ( 혜택)
  3. 더 나은 설명력, 추적성, 그리고 액세스 제어 ( 혜택)

다음에서 더 자세히 살펴볼게요.

#1: 더 높은 정확성과 더 유용한 답변

GraphRAG에서 가장 먼저 눈에 띄는 장점은 바로 더 높은 품질의 응답이에요. 고객 사례도 늘고 있고, 학술 연구에서도 이를 뒷받침하고 있죠. 예를 들어, 데이터 카탈로그 회사인 Data.world는 2023년 말에 GraphRAG가 평균적으로 LLM 응답의 정확도를 3배 향상시켰다는 연구 결과를 발표했어요. 43가지 비즈니스 질문을 벤치마킹한 결과, Knowledge Graph를 기반으로 했을 때 응답 정확도가 훨씬 높아졌다는 것을 확인했죠.

A knowledge graph improved accuracy of LLM responses by 54.2%, an average of 3x.

최근에는 Microsoft에서 2024년 2월부터 연구 블로그 시리즈를 시작했는데요. 제목은 GraphRAG: 서술형 개인 데이터에 대한 LLM 발견 잠금 해제이고, 관련 연구 논문과 소프트웨어 릴리스도 함께 공개되었어요. 여기서 Microsoft는 Baseline RAG(즉, Vector Embedding)에 다음과 같은 두 가지 문제가 있다고 지적했어요.

  • Baseline RAG는 점을 연결하는 데 어려움을 겪어요. 질문에 답하기 위해 공유된 속성을 통해 서로 다른 정보 조각을 탐색하여 새로운 통합 통찰력을 제공해야 할 때 특히 그렇죠.
  • Baseline RAG는 대규모 데이터 컬렉션이나 단일 대규모 문서에 대한 요약된 의미 개념을 전체적으로 이해하도록 요청받을 때 제대로 작동하지 않아요.

Microsoft는 이렇게 밝혔어요.“LLM에서 생성된 Knowledge Graph를 활용하여,GraphRAG는 RAG의 '검색' 부분을 대폭 개선하여 관련성이 높은 콘텐츠로 컨텍스트 창을 채워 더 나은 답변을 제공하고 증거 출처를 포착해요. 또한, 한 바에 따르면 GraphRAG는 다른 접근 방식보다 26%에서 97% 더 적은 토큰을 필요로 해서 답변 제공이 더 좋을 뿐만 아니라 더 저렴하고 확장성도 뛰어나다고 해요.7.

정확성이라는 주제를 더 깊이 파고들면 단순히 대답이 맞는지 여부만 중요한 게 아니에요.대답이 중요한 거죠. 그리고 그 방법이 얼마나지도 중요해요. GraphRAG를 통해 사람들이 발견한 것은 답변이 더 정확할 뿐만 아니라 더 풍부하고 완전하며 유용하다는 점이에요. LinkedIn의 최근 논문 GraphRAG의 영향을 설명합니다.에서는 고객 서비스 애플리케이션에서 이에 대한 훌륭한 예를 보여주는데요. GraphRAG는 고객 서비스 질문에 대한 답변의 정확성과 풍부함(따라서 유용성)을 모두 개선하여 고객 서비스 팀의 문제별 해결 시간 중앙값을 28.6% 단축시켜 준다고 해요.8.

비슷한 예가 GenAI 워크숍에서도 나오는데요. Neo4j와 GCP, AWS, Microsoft 파트너가 강의를 진행하죠. SEC 서류 모음을 대상으로 하는 아래 샘플 Query는 Vector + GraphRAG를 사용할 때 가능한 답변과 Vector 전용 RAG를 사용할 때 얻을 수 있는 답변의 종류를 잘 보여준답니다.

무언가를 설명할 때, 차이점에 주목해 주세요. 예를 들어 "리튬 부족으로 영향을 받을 가능성이 있는 기업"을 설명하는 것과 "특정 회사"를 지목하는 건 다르죠. 시장 변화에 맞춰 포트폴리오를 재조정하려는 투자자나, 자연재해로 공급망을 재편하려는 회사라면 후자에 접근하는 게 훨씬 효과적일 거예요. 둘 다 맞는 답이지만, 두 번째가 훨씬 유용하다는 거죠.

23화 Jesus Barrasa의 Going Meta에서는 어휘 그래프를 시작으로 법률 문서 사용 사례를 활용한 또 다른 멋진 예시를 보여준답니다.

X-sphere를 꾸준히 관찰하고 LinkedIn에서 활동하는 분들은 연구실뿐만 아니라 실제 현장에서도 새로운 사례가 계속 나오고 있다는 걸 알게 될 거예요. Lettria의 Charles Borderie가 제공하는 를 한번 살펴볼까요? 10,000개의 금융 기사를 Knowledge Graph로 구축하는 LLM 기반 텍스트-그래프 파이프라인과 Vector Embedding 기반의 RAG를 비교한 GraphRAG 사례입니다:

Retriever-only approach vs. graph retriever approach.

보시다시피, GraphRAG를 사용하면 일반 RAG를 사용할 때보다 답변 품질이 눈에 띄게 향상되었을 뿐만 아니라 답변에 사용된 토큰 수도 1/3이나 줄었답니다.

마지막으로 소개할 만한 예시는 Writer예요. Writer는 최근 RAG 벤치마킹 보고서를 했는데, RobustQA 프레임워크를 기반으로 GraphRAG 기반 접근 방식을 경쟁력 있는 최고 수준의 도구들과 비교했다고 해요.9 GraphRAG는 86%라는 높은 점수를 받았는데, 이는 33%에서 76% 사이의 점수를 받은 경쟁 제품들과 비교했을 때 대기 시간은 비슷하거나 더 나으면서도 훨씬 향상된 결과라는 걸 알 수 있죠.

Evaluation of RAG approaches accuracy and response time.

매주 다양한 GenAI 애플리케이션을 통해 긍정적인 효과를 경험하는 고객들을 만나고 있어요. 특히 Knowledge Graph는 결과를 더 정확하고 유용하게 만들어서 GenAI의 가능성을 활짝 열어주는 것 같아요.

#2: 향상된 데이터 이해, 더 빠른 반복

Knowledge Graph는 개념적으로나 시각적으로나 정말 직관적이죠. 그래프를 탐색하다 보면 종종 새로운 통찰력이 떠오르곤 해요. 많은 사용자가 예상치 못하게 얻는 긍정적인 효과는 Knowledge Graph 구축에 투자한 후에 GenAI 애플리케이션을 예상치 못한 방식으로 구축하고 디버깅하는 데 도움이 된다는 점이에요. 데이터를 그래프로 보는 것이 애플리케이션의 기반이 되는 데이터의 생생한 그림을 그려주는 것과 관련이 있거든요. 그래프는 또한 데이터에 대한 답을 추적하고, 인과 관계까지 거슬러 올라갈 수 있는 연결 고리를 제공하기도 하고요.

위에서 언급한 리튬 노출 질문을 예로 들어볼게요. 벡터를 시각화하면 훨씬 더 많은 행과 열이 보이겠지만, 결과는 대략 다음과 같을 거예요.

데이터를 그래프로 다루면 벡터 표현으로는 불가능한 방식으로 데이터를 이해할 수 있다는 점, 정말 흥미롭죠?

다음은 LlamaIndex의 최근 웹 세미나10에서 발췌한 내용인데요, 벡터화된 청크(어휘 그래프)와 LLM 추출 엔터티(도메인 그래프)의 그래프를 추출하고 이 둘을 "MENTIONS" 관계로 연결하는 기능을 보여주고 있어요.

(비슷한 예시들을 찾아볼 수 있어요. 랭체인, Haystack, SpringAI, 그리고 more.)

이 다이어그램을 보면 데이터가 가지고 있는 풍부한 구조가 어떻게 새로운 개발 및 디버깅 가능성을 열어주는지 알 수 있을 거예요. 개별 데이터 조각은 그 가치를 그대로 유지하면서, 구조 자체가 애플리케이션에 더 많은 인텔리전스를 더해주는 추가적인 의미를 저장하고 전달하는 거죠.

단순히 시각화 때문만은 아니에요. 의미를 전달하고 저장하는 방식으로 데이터를 구조화하는 효과이기도 하죠. 한 유명 핀테크 개발자가 RAG 워크플로우에 Knowledge Graph를 도입한 것에 대해 이런 반응을 보였다고 해요.

Developer reaction to GraphRAG.

이 개발자의 반응은 답변이 정확하다는 걸 맹신하는 게 아니라, 검증한다는 테스트 중심 개발 가정과 딱 맞아떨어져요. 솔직히 말하면, 저는 완전히 불투명한 결정을 내리기 위해 제 자율권 100%를 SkyNet에 넘겨주는 건 좀 꺼려져요! 좀 더 현실적인 예를 들어보자면, AI에 익숙하지 않은 사람이라도 "애플, Inc."가 "애플 군단"에 매핑되면 안 되는 건 당연하잖아요. 결국 data가 GenAI 결정의 핵심 동력이기 때문에, 정확성을 평가하고 보장할 수 있는 시스템을 갖추는 게 정말 중요해요.

#3: 거버넌스: 설명 가능성, 보안 등

GenAI 결정의 영향력이 클수록, 결정이 잘못됐을 때 책임을 져야 할 사람들을 설득해야 할 필요성이 커지죠. 그만큼 결정을 더해야 하니까요. 이를 위해서는 각 결정을 감사할 수 있는 능력이 필수적이에요. 그리고 올바른 결정에 대한 탄탄하고 반복 가능한 기록도 필요하고요. 하지만 그걸로는 충분하지 않아요. 결정을 내릴 때 그 근본적인 추론 과정을 설명할 수 있어야 해요.

LLM은 그 자체로는 이걸 잘 해내지 못해요. 물론 결정을 내리는 데 사용된 문서를 참고할 수는 있지만, 그게 결정 자체를 설명해주는 건 아니잖아요. LLM이 참고 자료를 막 지어낸다는 건 말할 필요도 없고요! 반면에 Knowledge Graph는 완전히 다른 수준에서 작동해서 GenAI 파이프라인 내부의 추론 논리를 훨씬 더 명확하게 만들고, 입력 내용을 훨씬 더 쉽게 설명할 수 있게 해줘요.

위에서 언급했던 예시를 계속 살펴볼까요? Lettria의 Charles는 10,000개의 금융 기사에서 추출한 엔터티로 Knowledge Graph를 만들고, 이걸 LLM과 함께 사용해서 GraphRAG를 구현했어요. 이게 어떻게 더 나은 답변을 제공하는지 확인했었죠. 이제 데이터를 한번 살펴볼게요.

데이터를 그래프로 보는 게 첫 번째 단계예요. 데이터는 탐색하고 쿼리할 수 있어야 하고, 시간이 지나면서 수정하고 업데이트할 수도 있어야 하고요. 거버넌스 측면에서 가장 큰 장점은 데이터의 "세계 모델"을 보고 감사하기가 훨씬 쉬워진다는 거예요. 그래프를 사용하면, 동일한 데이터의 벡터 버전을 보는 것보다 최종 결정에 책임을 져야 하는 사람이 그래프를 이해할 가능성이 훨씬 높아지죠. 품질 보증 측면에서도 Knowledge Graph에 데이터를 넣어두면 데이터에서 오류나 예상치 못한 문제점(좋든 싫든)을 찾아내고, 그 원인을 추적하기가 훨씬 쉬워져요. 게다가 그래프 안에 출처나 신뢰도 정보를 저장해두면, 계산뿐만 아니라 설명에도 활용할 수 있어요. 이건 똑같은 데이터의 벡터 버전만 볼 때는 불가능한 일이죠. 앞서 말씀드린 것처럼, 일반적인 사람은 물론이고, 평균 이상의 사람조차도 이해하기가 정말 어렵거든요.

Knowledge Graph는 보안과 개인 정보 보호를 크게 향상할 수도 있어요. 프로토타입을 만들 때는 크게 중요하지 않을 수 있지만, 실제 운영 환경에서는 정말 중요한 부분이죠. 은행이나 의료처럼 규제가 엄격한 기업에서는 특정 직원이 정보에 접근할 수 있는 권한이 그 사람의 역할에 따라 달라져야 해요. LLM이나 Vector Database는 역할에 맞춰 정보 접근 범위를 제한하는 좋은 방법이 없는데, Knowledge Graph 내의 권한 설정을 사용하면 특정 사용자의 데이터 접근 능력을 제어하고, 볼 수 없는 정보는 제외하는 방식으로 간단하게 처리할 수 있어요. 아래는 세분화된 접근 제어를 통해 Knowledge Graph에서 구현할 수 있는 간단한 보안 정책의 예시랍니다.

Knowledge Graph 생성

Knowledge Graph를 구축하려면 무엇이 필요한지 궁금해하는 분들이 많아요. 그 답을 이해하려면 먼저 GenAI 애플리케이션과 가장 관련 있는 두 가지 종류의 그래프를 알아야 해요.

  1. The 는 여러분의 애플리케이션과 관련된 세계 모델을 그래프로 표현한 것이에요. 간단한 예를 한번 살펴볼까요?
    The domain graph.
  2. The lexical graph12는 문서 구조의 그래프에요. 가장 기본적인 lexical graph에는 각 텍스트 덩어리에 대한 Node가 있죠.
    The lexical graph.

사람들은 청크와 문서 객체 (예: 테이블), 챕터, 섹션, 페이지 번호, 문서 이름/ID, 컬렉션, 소스 등 간의 Relationships를 포함하도록 이를 확장하는 경우가 많아요. 다음과 같이 도메인 그래프와 lexical graph를 결합할 수도 있죠.

Combining domain layer and lexical layer.

lexical graph를 만드는 것은 쉽고 주로 간단한 구문 분석 및 청킹 전략의 문제에요.13. 도메인 그래프의 경우 가져오는 데이터가 구조화된 소스에서 나오는지, 구조화되지 않은 텍스트에서 나오는지, 아니면 둘 다에서 나오는지에 따라 몇 가지 다른 경로가 있어요. 다행히 구조화되지 않은 데이터 소스에서 Knowledge Graph를 생성하는 도구가 빠르게 개선되고 있죠. 예를 들어, 새로운 Neo4j Knowledge Graph Builder는 PDF 문서, 웹 페이지, YouTube 클립, Wikipedia 기사를 가져와 자동으로 Knowledge Graph를 만들어요. 몇 개의 버튼을 클릭하는 것만큼 쉬우며, 입력 텍스트의 도메인 및 lexical graph를 모두 시각화(물론 Query)할 수 있어요. 강력하고 재미있으며 Knowledge Graph 작성에 대한 장벽을 크게 줄여주죠.

고객, 제품, 지역 등에 대한 데이터는 아마도 기업 내 어딘가에 구조화된 형태로 존재하며, 데이터가 있는 곳에서 직접 소스를 얻을 수 있어요. 관계형 데이터베이스에 있는 가장 일반적인 경우를 사용하면 표준을 사용할 수 있어요. 14는 관계-그래프 매핑에 대해 검증된 규칙을 따르죠.

Knowledge Graph 작업

Knowledge Graph가 있으면 GraphRAG를 수행하기 위한 프레임워크가 점점 더 많아지고 있어요. LlamaIndex Property Graph Index, Langchain의 Neo4j 통합 게다가 헤이스택의 그리고 다른 것들도요. 이 공간은 빠르게 움직이고 있지만 이제 우리는 프로그래밍 방식이 간단해지는 지점에 와 있어요.

그래프 구성 전면에서도 마찬가지에요. Neo4j Importer는 표 형식 데이터를 그래프로 매핑하고 가져오기 위한 그래픽 UI와 Neo4j의 새로운 v1이 있어요. LLM Knowledge Graph Builder는 위에서 언급했죠. 아래 그림은 Knowledge Graph를 구축하는 단계를 요약한 것이에요.

Automatically build a knowledge graph for GenAI.

또 다른 중요한 활용 사례는 사람이 사용하는 언어 질문을 Graph Database의 `쿼리`로 매핑하는 것이죠. Neo4j의 새로운 오픈 소스 도구인 네오컨버스는 그래프에 대한 Natural Language 쿼리를 돕기 위해 만들어졌어요. 이는 이 분야를 일반화하기 위한 첫 번째 중요한 단계라고 할 수 있죠.15

그래프를 시작하려면 약간의 노력과 학습이 필요하지만, 도구가 계속 개선되면서 점점 더 쉬워지고 있다는 희소식도 있어요!

결론: GraphRAG는 RAG의 자연스러운 다음 단계에요

LLM 및 벡터 기반 RAG가 제공하는 고유한 단어 기반 계산 및 언어 기술은 good 결과를 가져다 주죠. 하지만 꾸준히 great 결과를 얻으려면 문자열을 넘어서서 world model, 즉 word model이 필요해요. Google이 검색을 마스터하기 위해 단순한 텍스트 분석을 넘어 underneath the strings에 있는 기본적인 내용을 파악해야 한다는 것을 발견한 것처럼, AI 세계에서도 동일한 패턴이 나타나는 것을 볼 수 있어요. 바로 GraphRAG죠.

발전은 S-곡선 형태로 나타나요. 한 기술이 정점에 도달하면 다른 기술이 발전을 촉진하고 이를 뛰어넘죠. GenAI가 발전함에 따라 답변 품질이 중요한 용도로 사용되거나, 내부/외부 이해 관계자가 설명을 요구하거나, 개인 정보 보호 및 보안을 위해 데이터 액세스에 대한 세밀한 제어가 필요한 경우, 다음 GenAI 애플리케이션은 Knowledge Graph를 사용할 가능성이 매우 높아요.

The evolution of GenAI.

GraphRAG를 직접 경험해 볼 수 있어요!

GraphRAG로 한 단계 더 나아갈 준비가 되셨다면, Neo4j LLM Knowledge Graph Builder를 사용해 보세요. 이 간단한 웹 앱을 사용하면 PDF, 웹 페이지, YouTube 동영상과 같은 구조화되지 않은 텍스트 소스에서 몇 번의 클릭만으로 Knowledge Graph를 만들 수 있어요. GraphRAG의 성능을 직접 경험할 수 있는 완벽한 도구죠.

LLM Knowledge Graph Builder를 사용하면 다음과 같은 작업들을 할 수 있어요:

  • 무료 클라우드 기반 Neo4j 인스턴스에 연결하고, 좋아하는 텍스트 소스에서 그래프를 작성해 보세요.
  • 대화형 시각화를 통해 새로 생성된 Knowledge Graph를 탐색해 보세요.
  • 데이터와 대화하고 GraphRAG를 테스트해 보세요.
  • Knowledge Graph를 애플리케이션에 통합하고 새로운 통찰력을 얻어보세요.

시작하려면, 무료 AuraDB 인스턴스를 가동하고 Knowledge Graph를 구축해 보세요. Neo4j LLM Knowledge Graph Builder에 대해 자세히 알아보고 가이드 투어를 받을 수 있어요. 에서 확인하세요!

감사의 말

이번 포스팅에는 정말 많은 분들이 참여해 주셨어요. 여기에 인용된 많은 사례와 같이 자신의 학습 내용, 저술 및 코드를 공유하는 모든 분들께 감사를 표하고 계속해서 그렇게 하도록 격려하고 싶어요. 우리 모두가 배우는 것은 공동체로서 공유하는 것이니까요.

또한 GraphRAG의 중요성을 인식하고 게시물 자체를 검토하고 댓글을 달 수 있도록 시간을 아낌없이 제공해주신 많은 분들께 감사의 말씀을 전하고 싶어요. 많은 경우에 이것은 그들의 세계에 나타나는 사례를 통해 알려졌죠.

모든 사람의 이름을 밝히기보다는 일반적으로 '그래프 세계'라고 생각하는 것 이외의 사람들을 몇 명 언급하고 싶어요. 우리는 GraphRAG를 중요한 트렌드일 뿐만 아니라 두 세계 간의 융합으로 함께 보고 있어요.

이 모든 것을 마치고 다음을 포함하여 여러분 모두에게 깊은 감사를 드립니다. (성순으로):

  • 해리슨 체이스, 대표이사 랭체인
  • 알리 고디시, 대표이사 데이터브릭스
  • 로드 존슨, SpringSource의 투자자이자 설립자
  • 도우웨 키엘라, 대표이사 상황별 AI RAG의 공동 발명가이자
  • 크리스티나 리, FPV 벤처
  • 제리 리우, 대표이사 라마인덱스
  • 오웬 로버트슨, 주요한, DTS
  • 밀로스 루시치, 대표이사 / 건초더미

보충자료: 추가 자료

이 주제에 관해 많은 글이 작성되었으며 매일 새로운 통찰력과 사례가 등장하고 있어요. 포괄적인 목록을 제공할 수는 없지만, 더 자세히 알아보고 싶은 경우 확인할 수 있는 특히 좋은 몇 가지 항목은 다음과 같아요.

  • The RAG에 대한 Knowledge Graph에 대한 DeepLearning.AI 단기 과정 시작하는 데 60분이면 충분해요.
  • The GraphRAG 생태계 도구. 다음을 사용하여 YouTube, 즐겨찾는 PDF 또는 Wikipedia 페이지의 비디오에서 데이터와 개념에 대한 Knowledge Graph를 만드는 데 몇 분만 투자하세요. LLM Knowledge Graph Builder. 아직 Aura Free 인스턴스가 없다면 직접 인스턴스를 생성할 수 있어요. Knowledge Graph Builder를 사용해 보세요.
  • GraphRAG 패턴 카탈로그.
  • GraphRAG 디스코드에 참여하세요.
  • Tomaz Bratanic의 "Neo4j 및 LangChain을 사용하여 '로컬에서 글로벌로' GraphRAG 구현: 그래프 구성" 포스팅에서는 Microsoft의 GraphRAG 작업을 Neo4j + Langchain 파이프라인에 통합하는 방법을 다루고 있어요.
  • Tomaz Bratanic의 다른 블로그 포스팅들도 정말 굉장하니 한번 살펴보세요!
  • 오디오 자료도 몇 가지 추천해 드릴게요:
    • Data Exchange 팟캐스트 에피소드 "그래프로 AI 강화" (2024년 6월 27일)에서는 Ben과 제가 이 포스팅의 자료 등에 대해 이야기 나눴답니다.
    • 2024년 7월 4일 ThursdAI 팟캐스트 1주년 기념 에피소드에서는 Emile Eifrem이 주도하는 GraphRAG 관련 내용을 집중적으로 다루고 있어요.
  • 딜로이트(Deloitte)의 논문 "지식이 풍부한 생성 AI를 통한 책임 있는 기업 결정" (부제: *엔터프라이즈급 생성 AI에 Knowledge Graph를 통합하는 것이 왜 필수적인가요?*)도 참고해 보세요.
  • Jesus Barrasa의 "메타로 나아가다" 시리즈는 27개의 비디오로 구성되어 있으며, 각 비디오는 GraphRAG의 다양한 측면이나 사례를 다루고 있어요.
  • Leann Chen의 학습 동영상 중 "RAG를 위한 더 나은 Knowledge Graph가 필요합니다"와 "Neo4j Knowledge Graph로 고급 RAG 챗봇 구축"도 유용할 거예요.
  • LlamaIndex의 6부작 번개 강좌 "Property Graph 소개"도 추천합니다.
  • GraphStuff.fm 팟캐스트도 있어요. Jennifer Reif, Andreas Kollegger, Alison Cossette, Jason Koo가 진행하고 있답니다.
  • 마지막으로, 상사에게 GraphRAG의 필요성을 설득하고 싶다면 생성 AI를 위한 Gartner의 2024년 임팩트 레이더를 참고하세요. 현재 가장 관련성이 높은 GenAI 기술의 중심에 Knowledge Graph를 위치시키고 있다는 사실!

1 이 블로그 포스팅을 읽고 Google의 웹 검색 여정이 GenAI에서 현재 일어나고 있는 일과 얼마나 유사한지 확인해 보세요.

2주의: 이 숫자는 실제로 사과를 나타낼 수도 있고 아닐 수도 있어요. Vector와 그래프의 주요 차이점 중 하나를 보여주는 알기 어려운 예시랍니다.

3나중에 "Knowledge Graph 생성" 섹션에서 설명하겠지만, "도메인 그래프"와는 다른 또 다른 종류의 Knowledge Graph가 등장하고 있으며 그 유용성이 입증되었어요. 이것은 월드 모델 대신에 Vector 청크와 이들이 서로 어떻게 관련되는지, 그리고 그 주변의 문서 구조(표/그림/페이지/문서/컬렉션/저자 등)에 대한 그래프인 "어휘 그래프"랍니다.

4 물론, 현실 세계에서는 포괄적인 단일 단계로 나타나는 게 아니라, 자체 단계와 논리 세트를 따르는 에이전트 파이프라인의 일부로 나타나는 경우가 많죠. 그런데 이건 Graph도 마찬가지예요. 이런 작업이 복잡해짐에 따라, 코드가 아닌 Graph Database에서 워크플로우와 규칙을 캡처하는 걸 볼 수도 있을 거예요. 하지만 아직 거기까지는 아니고, 지금 이야기할 주제와는 조금 다르네요.

5 Knowledge Graph가 이미 준비되어 있다면 이 작업이 시작되는 거예요. 물론 공짜는 아니지만, 최신 기술 덕분에 얼마나 접근하기 쉬워졌는지 놀라실 거예요. 워낙 기초적인 내용이라 Knowledge Graph 구축 과학 및 기술에 대한 섹션을 뒤에 따로 준비했어요.

6 Knowledge Graph는 시스템 간 데이터 흐름 방식을 캡처하는 시스템/출처/데이터 계보 그래프 등 다른 형태의 추적성에도 도움이 될 수 있어요. 그리고 해결된 엔터티 추적과 같은 다른 AI 이점도 제공할 수 있죠. 하지만 지금은 GraphRAG에 집중해야 하니, 일단 넘어가도록 할게요.

7 좀 더 자세히 알아보고, 실제로 작동하는 코드를 직접 보고 싶다면, 제 동료인 Tomaz Bratanic의 게시물을 추천드려요. Neo4j 및 LangChain을 사용하여 '로컬에서 글로벌로' GraphRAG 구현: 그래프 구성인데, Microsoft의 작업을 한 단계 더 발전시켜 Neo4j + Langchain 파이프라인에 통합했답니다.

8 논문 자체에는 GraphRAG와 벡터 전용 RAG 접근 방식에 대한 더 자세한 비교가 나와 있는데, GraphRAG는 MRR에서 답변을 77.6% 향상시키고, BLEU 기준선보다 0.32 높았다고 해요.

9 공교롭게도 Neo4j로 구동된다는 사실!

10 새로운 기능을 소개하는 멋진 웹 세미나도 있어요(2024년 5월경). Property Graph Index에는 텍스트를 그래프로 변환하는 내장 메소드가 포함되어 있답니다.

11 우리 모두 "영향력"이 뭘 의미하는지는 알고 있겠지만, 간단히 설명하자면 잘못된 답변이 건강 및 인간 안전, 사회 및 공정성, 평판 또는 높은 금전적 영향에 영향을 미칠 수 있는 모든 결정이 여기에 포함돼요. 정부 규제에 속하거나 규정 준수에 영향을 미칠 수 있는 모든 결정도 당연히 포함되겠죠.

12 여기서 "어휘적"이라는 단어는 개별 단어를 의미할 뿐만 아니라, 더 넓게는 (사전 정의에서 알 수 있듯이) "언어의 단어 또는 어휘와 관련된" 것을 의미해요. 즉, 단어 본문과 그 관계 영역에 있는 모든 것을 포함하는 거죠.

13 이 작업을 수행하는 몇 가지 라이브러리를 특별한 순서 없이 나열해볼게요. Docs2KG, Diffbot, GLiNER, spaCy, Numind, NetOwl® 그리고 (특히 엔터티 해결에 강점을 가진) Senzing이 있어요.

14 선택한 관계형 데이터베이스에 대한 직접 연결을 지원하는 이 도구의 새 버전이 2024년 하반기에 출시될 예정이라고 하네요.

15 NeoConverse와 LLM GraphBUilder는 모두 성장하는 GraphRAG 생태계 도구의 일부이고, Neo4j에서 만들었어요.

  • GraphRAG
  • RAG

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

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

반응형
반응형

LLM Knowledge Graph Builder를 사용해서 GenAI 애플리케이션에서 Knowledge Graph를 추출하고 활용해 보세요!

The LLM Knowledge Graph Builder는 Neo4j의 GraphRAG 생태계 도구 중 하나인데요. 이걸 사용하면 구조화되지 않은 데이터를 동적인 Knowledge Graph로 변환할 수 있어요. 또, RAG(Retrieval-Augmented Generation) 챗봇과 통합해서 자연어 쿼리와 데이터에 대한 설명 가능한 통찰력을 얻을 수도 있답니다.

자, 그럼 한번 살펴볼까요?

Neo4j LLM Knowledge Graph Builder란 무엇일까요?

Neo4j LLM Knowledge Graph Builder는 정말 혁신적인 온라인 애플리케이션이에요. 코딩이나 Cypher 없이도 구조화되지 않은 텍스트를 Knowledge Graph로 변환해서 마법 같은 텍스트-그래프 경험을 선사하죠.

Machine Learning 모델(LLM: OpenAI, Gemini, Diffbot)을 사용해서 PDF, 웹 페이지, YouTube 비디오를 엔터티와 그 관계에 대한 Knowledge Graph로 변환해 준답니다.

프론트엔드는 Neo4j의 React 기반 Needle Starter Kit을 사용하고, 백엔드는 Python FastAPI 애플리케이션을 사용해요. 그리고 llm-graph-transformer 모듈은 Neo4j가 LangChain에 기여한 모듈이랍니다.

이 애플리케이션은 다음 네 가지 간단한 단계를 거쳐서 매끄러운 환경을 제공해 줘요.

  1. 데이터 수집(Data Collection) —PDF 문서, Wikipedia 페이지, YouTube 동영상 등 다양한 데이터 소스를 지원해요.
  2. 엔터티 인식(Entity Recognition) —LLM을 사용해서 구조화되지 않은 텍스트에서 엔터티와 관계를 식별하고 추출해요.
  3. 그래프 구성(Graph Construction) —Neo4j 그래프 기능을 사용해서 인식된 엔터티와 관계를 그래프 형식으로 변환하죠.
  4. 사용자 인터페이스(User Interface) —사용자가 애플리케이션과 상호 작용할 수 있는 직관적인 웹 인터페이스를 제공해서 데이터 소스 업로드, 생성된 그래프 시각화, RAG 에이전트와의 상호 작용을 쉽게 만들어 줘요. 특히 이 기능은 Knowledge Graph 자체와 대화하는 것처럼 데이터와 직관적으로 상호 작용할 수 있게 해줘서 정말 흥미롭죠! 기술적인 지식이 없어도 괜찮아요.

한번 시도해 보세요

저희는 애플리케이션을 Neo4j 호스팅 환경에서 제공하고 있어요. 신용카드나 LLM 키도 필요 없으니 부담 없이 사용해 보세요.

아니면 로컬 환경에서 실행하고 싶다면 GitHub 저장소를 방문해서 이 글에서 설명하는 단계를 따라 해 보세요.

LLM Knowledge Graph Builder를 열기 전에 새로운 Neo4j 데이터베이스를 만들어 볼게요. 무료 AuraDB 데이터베이스를 사용하면 되는데요, 다음 단계를 따라 하면 돼요.

  • 새로운 AuraDB 무료 데이터베이스 인스턴스를 생성하세요.
  • 자격 증명 파일을 다운로드하세요.
  • 인스턴스가 실행될 때까지 기다리세요.

이제 Neo4j 데이터베이스가 실행되고 자격 증명도 준비됐으니, LLM Knowledge Graph Builder를 열고 오른쪽 상단의 Neo4j에 연결을 클릭하세요.

연결 대화 상자에 다운로드한 자격 증명 파일을 끌어다 놓으세요. 모든 정보가 자동으로 채워질 거예요. 물론 직접 입력할 수도 있고요.

Knowledge Graph 만들기

프로세스는 구조화되지 않은 데이터를 수집하는 것으로 시작하는데요, 이 데이터는 LLM을 통해 전달되어 주요 엔터티와 관계를 식별하게 돼요.

PDF나 다른 파일을 왼쪽의 첫 번째 입력 영역으로 끌어다 놓을 수 있어요. 두 번째 입력 영역에는 사용하고 싶은 YouTube 동영상 링크를 복사해서 붙여넣으면 되고요, 세 번째 입력 영역에는 Wikipedia 페이지 링크를 넣을 수 있어요.

예를 들어 GraphACME라는 공급망 회사에 대한 PDF 파일 몇 개와 Forbes의 관련 기사, 그리고 YouTube 영상 (Corporate Sustainability Due Diligence Directive, CSDDD에 대한 내용), Wikipedia 페이지 (기업 지속 가능성 실사 지침, 방글라데시) 두 개를 넣어볼게요.

파일을 업로드하는 동안 애플리케이션은 업로드된 소스를 LangChain 문서 로더와 YouTube 파서를 사용해서 그래프의 문서 Node로 저장해요. 모든 파일이 업로드되면 다음과 비슷한 화면이 보일 거예요.

이제 사용할 모델을 선택하고 클릭하면 나머지는 자동으로 처리될 거예요!

파일 선택만 생성하려면 먼저 파일을 선택하고 (테이블의 첫 번째 열에 있는 확인란을 사용하여) 클릭하면 돼요.

⚠️ 사전 정의된 그래프 스키마 또는 자체 그래프 스키마를 사용하고 싶다면 오른쪽 상단에 있는 설정 아이콘을 클릭하고 드롭다운에서 사전 정의된 스키마를 선택하거나, Node Label 및 Relationship을 기록해서 자신만의 스키마를 사용하거나, 기존 Neo4j 데이터베이스에서 기존 스키마를 가져오거나, 텍스트를 복사/붙여넣고 LLM에 요청해서 이를 분석하고 제안된 스키마를 제시하도록 요청할 수 있어요.

파일을 처리하고 지식 정보를 생성하는 동안 내부적으로 어떤 일들이 일어나는지 한번 살펴볼까요?

  1. 콘텐츠는 여러 개의 덩어리(chunk)로 분할돼요.
  2. Chunk는 그래프에 저장되고 문서 Node에 연결되며, 고급 RAG 패턴을 위해 서로 연결돼요.
  3. 매우 유사한 Chunk는 SIMILAR Relationship으로 연결되어 K-Nearest Neighbors 그래프를 형성하죠.
  4. Vector Embedding은 Chunk와 벡터 Index에 계산되어 저장돼요.
  5. llm-graph-transformer 또는 diffbot-graph-transformer를 사용하면 텍스트에서 항목과 Relationship이 추출돼요.
  6. 엔터티는 그래프에 저장되고 원래 Chunk에 연결돼요.

Knowledge Graph 탐색

문서에서 추출된 정보는 엔터티가 Node가 되고 Relationship이 이러한 Node를 연결하는 Edge로 바뀌는 그래프 형식으로 구성돼요. Neo4j 사용의 장점은 이러한 복잡한 데이터 네트워크를 효율적으로 저장하고 Query하여 생성된 Knowledge Graph를 다양한 애플리케이션에 즉시 유용하게 만드는 능력에 있죠.

RAG 에이전트를 사용하여 데이터에 대해 질문하기 전에 확인란을 사용하여 하나 이상의 문서를 선택하고 클릭할 수 있어요. 그러면 선택한 문서에 대해 생성된 엔터티가 표시될 거예요. 해당 보기에 문서 및 Chunk Node를 표시할 수도 있답니다.

Open Graph in Bloom 버튼을 누르면 새로운 탭에서 Neo4j Bloom이 열릴 거예요. Bloom은 새로 생성된 Knowledge Graph를 시각화하고 탐색하는 데 도움을 준답니다. 다음 액션은 Delete File이에요. 그래프에서 선택한 문서와 청크 (그리고 옵션에서 선택했다면 엔터티)를 삭제하는 기능이죠.

당신의 Knowledge Graph와 대화하기

이제 마지막 부분! 오른쪽 패널에 표시되는 RAG 에이전트 차례에요.

검색 프로세스 - 어떻게 작동하나요?

아래 이미지는 GraphRAG 프로세스를 간단하게 보여주고 있어요.

사용자가 질문을 하면, 검색 쿼리와 함께 Neo4j 벡터 인덱스를 사용해서 질문과 가장 관련 있는 청크와 최대 2홉 깊이까지 연결된 엔티티를 찾아요. 그리고 채팅 기록을 요약해서 맥락을 풍부하게 만드는 요소로 활용하죠.

다양한 입력 및 소스(질문, 벡터 결과, 채팅 기록)는 모두 사용자 정의 프롬프트를 통해 선택된 LLM 모델로 전송돼요. 제공된 요소와 컨텍스트를 기반으로 질문에 대한 응답을 제공하고 형식을 지정하도록 요청하는 거죠. 물론 프롬프트에는 형식 지정, 출처 인용 요청, 답변을 모를 경우 추측하지 않기 등 더 많은 "마법"이 숨어있어요. 전체 프롬프트와 지침은 QA_integration.py의 FINAL_PROMPT에서 확인할 수 있어요.

이 예시에서는 전체 공급망 전략과 제품을 생산하고 문서화하는 GraphACME(유럽 기반)라는 가짜 회사에 대한 내부 문서를 로드했어요. 새로운 CSDDD, 그 영향 및 규제를 설명하는 언론 기사와 YouTube 동영상도 로드했고요. 이제 챗봇에게 내부 (가짜) 회사 지식에 대한 질문(CSDDD 법률에 대한 질문)을 할 수 있어요. 또는 GraphACME가 생산하는 제품 목록, CSDDD 규정의 영향을 받는지, 그렇다면 회사에 어떤 영향을 미칠지 묻는 등 두 가지에 대한 질문도 가능하죠.

채팅 기능

홈 화면 오른쪽에는 채팅 창에 연결된 세 개의 버튼이 있어요.

  • : 챗봇 인터페이스가 닫혀요.
  • 채팅 기록 지우기: 현재 세션의 채팅 기록이 삭제돼요.
  • 창 최대화: 전체 화면 모드에서 챗봇 인터페이스가 열려요.

RAG 에이전트의 답변에서 응답 뒤에 세 가지 기능을 찾을 수 있을 거예요.

  • : RAG 에이전트가 소스(문서), 청크, 엔티티를 어떻게 수집하고 사용했는지 보여주는 검색 정보 팝업이 열려요. 사용된 모델 및 토큰 소비에 대한 정보도 포함되어 있죠.
  • Copy: 응답 내용을 클립보드에 복사해요.
  • 텍스트 음성 변환: 응답 내용을 소리 내어 읽어줘요.

마무리

LLM 지식 그래프 빌더에 대해 더 자세히 알고 싶으시다면, GitHub 저장소에서 소스 코드와 문서를 포함한 풍부한 정보를 얻을 수 있어요. 추가적으로 선적 서류 비치에서는 시작하는 방법에 대한 자세한 지침을 제공하고, GenAI 생태계에서는 사용 가능한 더 광범위한 도구와 애플리케이션에 대한 추가 통찰력을 얻을 수 있습니다.

다음 단계 — 기여 및 확장 기능

LLM 지식 그래프 빌더에 대한 여러분의 경험은 정말 중요해요. 버그를 발견했거나, 새로운 기능에 대한 제안이 있거나, 기여하고 싶거나, 특정 개선 사항을 보고 싶다면 커뮤니티 플랫폼이 여러분의 생각을 공유하기에 딱 좋은 곳이죠. 코딩에 능숙해서 직접 기여하고 싶은 분들에게는 가 프로젝트를 발전시키는 데 도움이 되는 보람 있는 방법이 될 수 있을 거예요. 여러분의 의견과 기여는 도구를 개선하는 데 도움이 될 뿐만 아니라, 협력적이고 혁신적인 커뮤니티를 육성하는 데에도 큰 도움이 된답니다.

  • Neo4j 온라인 커뮤니티
  • Neo4j Discord 서버에 참여하세요!

자원

GenAI 애플리케이션을 위한 새로운 리소스에 대해 더 자세히 알아보고 싶으신가요? Neo4j GraphRAG 생태계 도구를 한번 살펴보세요. 이러한 오픈 소스 도구를 사용하면 Knowledge Graph를 기반으로 하는 GenAI 애플리케이션을 쉽게 시작할 수 있어서 응답 품질과 설명 가능성을 개선하고 앱 개발 및 채택을 가속화하는 데 도움이 될 거예요.

동영상

  • GitHub – neo4j-labs/llm-graph-builder: 비정형 데이터에서 Neo4j 그래프 구성
  • Neo4j LLM Knowledge Graph Builder – 구조화되지 않은 텍스트(PDF, YouTube, 웹페이지)에서 Node 및 Relationship 추출 – Neo4j Labs
  • GenAI 생태계 – Neo4j Labs
  • Needle StarterKit 2.0: 템플릿, 챗봇 등!
  • GraphRAG
  • RAG

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

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

반응형
반응형

질문 기반 추출 접근 방식

이번 블로그에서는 Graph Retrieval-Augmented Generation(GraphRAG)을 사용해서 상업 계약 데이터를 수집하고 Q&A 에이전트를 구축하는 과정을 얼마나 간소화할 수 있는지 알아볼 거예요.

이 접근 방식은 기존의 Retrieval-Augmented Generation(RAG) 방식과는 조금 다른데요. 기존 RAG는 문서 전체를 무작위로 잘라서 벡터화하는 데 비해, 여기서는 데이터 추출 효율성을 훨씬 중요하게 생각하거든요.

기존 RAG에서는 모든 문서를 잘게 나누고 검색을 위해 벡터화하기 때문에, 불필요하게 많은 양의 데이터가 쪼개져서 벡터 index에 저장될 수 있어요. 하지만 저희는 특정 사용 사례, 즉 상업 계약 검토를 위해 계약에서 가장 관련성이 높은 정보만 뽑아내는 데 집중할 거예요. 이렇게 추출된 데이터는 핵심 엔터티와 관계로 이루어진 Knowledge Graph로 구성되고, Cypher 쿼리 및 벡터 검색을 통해 훨씬 정확한 그래프 데이터 검색이 가능해지죠.

벡터화되는 콘텐츠 양을 최소화하고 관련성 높은 지식 추출에 집중함으로써, Q&A 에이전트의 정확성과 성능을 높일 수 있어요. 복잡하고 특정 분야에 특화된 질문을 처리하는 데 아주 적합하답니다.

저희가 사용할 4단계 접근 방식은 Knowledge Graph(LLM + Neo4j) 생성을 위한 타겟 정보 추출(LLM + Prompt Engineering)과 간단한 그래프 데이터 검색 기능 세트(Cypher, Text2Cypher, 벡터 검색)를 포함해요. 마지막으로, 이러한 데이터 검색 기능을 활용하는 Q&A 에이전트는 Microsoft Semantic Kernel을 사용합니다.

아래 다이어그램에서 이 접근 방식을 한눈에 볼 수 있어요.

The four-stage GraphRAG approach

4단계 GraphRAG 접근 방식: 질문 기반 추출 > 지식 그래프 모델 > GraphRAG 검색 > Q&A 에이전트. Sebastian Nilsson @ Neo4j의 이미지는 저자의 허가를 받아 여기에 재현되었습니다.

본격적으로 시작하기 전에, 상법에 익숙하지 않은 분들을 위해 계약 검토라는 과제가 무엇인지 간단하게 소개할게요.

계약 검토 및 Large Language Model

상업 계약 검토는 법률 보조원이나 주니어 변호사가 계약서에서 중요한 정보를 꼼꼼하게 찾아내는, 시간과 노력이 많이 필요한 작업이에요.

“계약 검토는 계약서에 서명하는 개인이나 회사의 권리와 의무를 이해하고 그에 따른 영향을 평가하기 위해 계약서를 철저히 읽는 과정입니다.”
-Hendrycks, Burns 외, NeurIPS 2021, in
법적 계약 검토를 위한 전문가 주석이 달린 NLP 데이터세트인 CUAD

계약 검토의 첫 번째 단계는 수백 페이지에 달하는 계약서를 샅샅이 훑으면서 관련 조항이나 의무를 찾아내는 거예요. 계약 검토자는 관련 조항이 있는지, 있다면 어떤 내용인지, 그리고 해당 조항이 어디에 있는지 확인해야 하죠.

예를 들어 계약이 3년 계약인지 1년 계약인지 판단해야 해요. 계약 종료일도 알아내야 하고요. 또, 조항이 양도 금지 조항인지, 아니면 독점 조항인지도 판단해야 하죠…
-Hendrycks, Burns 외, NeurIPS 2021, in
법적 계약 검토를 위한 전문가 주석이 달린 NLP 데이터세트인 CUAD

꼼꼼함이 필수적인 작업이지만, 종종 비효율적인 부분이 많아서 Large Language Model에게 아주 적합한 분야라고 할 수 있어요.

첫 번째 단계를 마치면, 숙련된 법률 전문가가 계약서의 약점과 위험 요소를 검토하기 시작할 수 있어요. 이 부분이 바로 LLM 기반으로 작동하고 Knowledge Graph에 저장된 정보를 활용하는 Q&A 에이전트가 법률 전문가의 훌륭한 조력자가 될 수 있는 영역이죠.

LLM, 함수 호출 및 GraphRAG를 사용하여 상업 계약 검토 에이전트를 구축하는 4단계 접근 방식

이제 이 블로그의 나머지 부분에서는 위에서 언급한 프로세스의 각 단계를 자세히 설명할 거예요. 주요 아이디어를 더 명확하게 보여드리기 위해 코드 조각도 함께 사용할 거고요.

네 단계는 다음과 같아요:

  1. 계약서에서 관련 정보 추출 (LLM + Contract)
  2. 추출된 정보를 Knowledge Graph로 저장 (Neo4j)
  3. 간단한 Knowledge Graph 데이터 검색 기능 개발 (Python)
  4. 복잡한 질문을 처리하는 Q&A 에이전트 구축 (Semantic Kernel, LLM, Neo4j)

데이터 세트

The CUAD (계약 이해 Atticus 데이터 세트)는 510개의 법적 계약에 걸쳐 전문가가 라벨을 붙인 13,000개 이상의 조항으로 구성된 CC BY 4.0 라이선스 및 공개적으로 사용 가능한 데이터세트에요. 계약 검토를 위한 AI 모델을 구축하는 데 도움이 되도록 설계되었죠. 계약 분석에 중요한 기밀 유지, 해지, 면책 등 중요한 법적 조항을 광범위하게 다루고 있어요.

이 데이터 세트의 세 가지 계약을 사용해서 주요 법률 정보를 효과적으로 추출 및 분석하고, Knowledge Graph를 구축하고, 정확하고 복잡한 질문 답변을 위해 이를 활용하는 접근 방식을 보여줄 거예요.

3개의 계약서를 합치면 총 95페이지나 되네요!

1단계. 계약서에서 관련 정보 추출

LLM이 계약에서 정확한 정보를 추출하고 계약의 관련 정보를 나타내는 JSON 출력을 생성하도록 요청하는 건 비교적 간단해요.

상업 리뷰에서는 위에서 언급한 각 주요 요소 (당사자, 날짜, 조항)를 찾아 기계가 읽을 수 있는 JSON 파일에 깔끔하게 요약하는 Prompt 초안을 작성할 수 있어요.

추출 Prompt (단순화)

본 계약에만 있는 정보를 사용하여 다음 질문에 답하세요.
[계약서.pdf]

1) 이것은 어떤 유형의 계약입니까?
2) 당사자는 누구이며 그 역할은 무엇입니까? 그들은 어디에 통합되어 있습니까? 주 및 국가 이름 지정 (ISO 3166 국가 이름 사용)
3) 계약날짜는 언제인가요?
4) 발효일은 언제인가요?

다음 유형의 계약 조항 각각에 대해 두 가지 정보를 추출합니다.
a) 해당 조항이 본 계약서에 포함되어 있다고 생각하는지 여부를 나타내는 예/아니요
b) 이 조항 유형이 존재함을 나타내는 발췌 목록

계약 조항 유형: 경쟁 제한 예외, 비경쟁 조항, 독점, 고객 요청 금지, 직원 요청 금지, 비방 금지, 편의를 위한 종료, Rofr/Rofo/Rofn, 통제 변경, 할당 방지, 책임 제한 없음, 책임 한도

JSON 문서에 최종 답변을 제공하세요.

위 섹션에는 추출 Prompt의 단순화된 버전이 표시되어 있어요 (전체 버전은 에서 확인하세요!). Prompt의 마지막 부분에서는 JSON 문서의 원하는 형식을 지정하는데, 이는 일관된 JSON 스키마 출력을 보장하는 데 유용하답니다.

이 작업은 Python에서 비교적 간단해요. 아래 main() 함수는 OpenAI GPT-4o를 사용해서 관련 법률 정보 (extraction_prompt)를 추출하고 결과를 JSON 형식으로 저장해서 PDF 계약 파일 세트를 처리하도록 설계되었어요.

def main():
    pdf_files = [filename for filename in os.listdir('./data/input/') if filename.endswith('.pdf')]
    
    for pdf_filename in pdf_files:
        print('Processing ' + pdf_filename + '...')    
        # Extract content from PDF using the assistant
        complete_response = process_pdf('./data/input/' + pdf_filename)
        # Log the complete response to debug
        save_json_string_to_file(complete_response, './data/debug/complete_response_' + pdf_filename + '.json')

process_pdf” 기능은 OpenAI GPT-4o를 사용해서 추출 Prompt가 있는 계약에서 지식 추출을 수행해요.

 def process_pdf(pdf_filename):
    # Create OpenAI message thread
    thread = client.beta.threads.create()
    # Upload PDF file to the thread
    file = client.files.create(file=open(pdf_filename, "rb"), purpose="assistants")
    # Create message with contract as attachment and extraction_prompt
    client.beta.threads.messages.create(thread_id=thread.id,role="user",
        attachments=[
            Attachment(
                file_id=file.id, tools=[AttachmentToolFileSearch(type="file_search")])
        ],
        content=extraction_prompt,
    )
    # Run the message thread
    run = client.beta.threads.runs.create_and_poll(
        thread_id=thread.id, assistant_id=pdf_assistant.id, timeout=1000)
    # Retrieve messages
    messages_cursor = client.beta.threads.messages.list(thread_id=thread.id)
    messages = [message for message in messages_cursor]
    # Return last message in Thread 
    return messages[0].content[0].text.value

각 계약에 대해 "process_pdf"가 반환하는 메시지는 다음과 같아요.

{
    "agreement": {
        "agreement_name": "Marketing Affiliate Agreement",
        "agreement_type": "Marketing Affiliate Agreement",
        "effective_date": "May 8, 2014",
        "expiration_date": "December 31, 2014",
        "renewal_term": "1 year",
        "Notice_period_to_Terminate_Renewal": "30 days",
        "parties": [
            {
                "role": "Company",
                "name": "Birch First Global Investments Inc.",
                "incorporation_country": "United States Virgin Islands",
                "incorporation_state": "N/A"
            },
            {
                "role": "Marketing Affiliate",
                "name": "Mount Knowledge Holdings Inc.",
                "incorporation_country": "United States",
                "incorporation_state": "Nevada"
            }
        ],
        "governing_law": {
            "country": "United States",
            "state": "Nevada",
            "most_favored_country": "United States"
        },
        "clauses": [
            {
                "clause_type": "Competitive Restriction Exception",
                "exists": false,
                "excerpts": []
            },
            {
                "clause_type": "Exclusivity",
                "exists": true,
                "excerpts": [
                    "Company hereby grants to MA the right to advertise, market and sell to corporate users, government agencies and educational facilities for their own internal purposes only, not for remarketing or redistribution."
                ]
            },
            {
                "clause_type": "Non-Disparagement",
                "exists": true,
                "excerpts": [
                    "MA agrees to conduct business in a manner that reflects favorably at all times on the Technology sold and the good name, goodwill and reputation of Company."
                ]
            },
            {
                "clause_type": "Termination For Convenience",
                "exists": true,
                "excerpts": [
                    "This Agreement may be terminated by either party at the expiration of its term or any renewal term upon thirty (30) days written notice to the other party."
                ]
            },
            {
                "clause_type": "Anti-Assignment",
                "exists": true,
                "excerpts": [
                    "MA may not assign, sell, lease or otherwise transfer in whole or in part any of the rights granted pursuant to this Agreement without prior written approval of Company."
                ]
            },
            
            {
                "clause_type": "Price Restrictions",
                "exists": true,
                "excerpts": [
                    "Company reserves the right to change its prices and/or fees, from time to time, in its sole and absolute discretion."
                ]
            },
            {
                "clause_type": "Minimum Commitment",
                "exists": true,
                "excerpts": [
                    "MA commits to purchase a minimum of 100 Units in aggregate within the Territory within the first six months of term of this Agreement."
                ]
            },
            
            {
                "clause_type": "IP Ownership Assignment",
                "exists": true,
                "excerpts": [
                    "Title to the Technology and all copyrights in Technology shall remain with Company and/or its Affiliates."
                ]
            },
            
            {
                "clause_type": "License grant",
                "exists": true,
                "excerpts": [
                    "Company hereby grants to MA the right to advertise, market and sell the Technology listed in Schedule A of this Agreement."
                ]
            },
            {
                "clause_type": "Non-Transferable License",
                "exists": true,
                "excerpts": [
                    "MA acknowledges that MA and its Clients receive no title to the Technology contained on the Technology."
                ]
            },
            {
                "clause_type": "Cap On Liability",
                "exists": true,
                "excerpts": [
                    "In no event shall Company be liable to MA, its Clients, or any third party for any tort or contract damages or indirect, special, general, incidental or consequential damages."
                ]
            },
            
            {
                "clause_type": "Warranty Duration",
                "exists": true,
                "excerpts": [
                    "Company's sole and exclusive liability for the warranty provided shall be to correct the Technology to operate in substantial accordance with its then current specifications."
                ]
            }
            
            
        ]
    }
}

2단계. Knowledge Graph 만들기

이제 각 계약을 JSON 파일로 사용해서 Neo4j에 Knowledge Graph를 만들어볼 차례에요.

여기서 잠깐! 데이터 모델을 설계하는 데 시간을 투자하는 게 좋아요. 몇 가지 중요한 질문을 생각해봐야 하거든요.

  • 이 그래프의 Nodes와 Relationships는 뭘 나타낼까요?
  • 각 Node와 Relationship의 주요 속성은 뭘까요?
  • 속성에 Index를 만들어야 할까요?
  • Semantic Search를 활성화하려면 어떤 속성에 Vector Embedding이 필요할까요?

우리의 경우, 적합한 설계 (스키마)에는 계약 (Agreement), 해당 조항, 계약 당사자인 조직, 그리고 이들 간의 Relationships 같은 주요 엔터티가 포함될 거예요.

스키마를 그림으로 표현하면 이렇게 생겼어요.

A visual representation of the schema
Node properties:
Agreement {agreement_type: STRING, contract_id: INTEGER,
          effective_date: STRING, expiration_date: STRING,
          renewal_term: STRING, name: STRING}
ContractClause {name: STRING, type: STRING}
ClauseType {name: STRING}
Country {name: STRING}
Excerpt {text: STRING}
Organization {name: STRING}

Relationship properties:
IS_PARTY_TO {role: STRING}
GOVERNED_BY_LAW {state: STRING}
HAS_CLAUSE {type: STRING}
INCORPORATED_IN {state: STRING}

1단계에서 LLM이 식별한 짧은 텍스트 조각인 발췌문에만 텍스트 삽입이 필요하다는 점! 이 접근 방식은 각 계약을 나타내는 데 필요한 벡터 수와 벡터 Index의 크기를 확 줄여서 프로세스를 훨씬 효율적이고 확장 가능하게 만들어줘요.

위 스키마를 사용해서 각 JSON을 Knowledge Graph에 로드하는 Python 스크립트를 간단하게 만들어보면 이렇습니다.

NEO4J_URI=os.getenv('NEO4J_URI', 'bolt://localhost:7687')
NEO4J_USER=os.getenv('NEO4J_USERNAME', 'neo4j')
NEO4J_PASSWORD=os.getenv('NEO4J_PASSWORD')
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
JSON_CONTRACT_FOLDER = './data/output/'

driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USER, NEO4J_PASSWORD))

contract_id = 1

json_contracts = [filename for filename in os.listdir(JSON_CONTRACT_FOLDER) if filename.endswith('.json')]
for json_contract in json_contracts:
  with open(JSON_CONTRACT_FOLDER + json_contract,'r') as file:
    json_string = file.read()
    json_data = json.loads(json_string)
    agreement = json_data['agreement']
    agreement['contract_id'] = contract_id
    driver.execute_query(CREATE_GRAPH_STATEMENT,  data=json_data)
    contract_id+=1

create_full_text_indices(driver)
driver.execute_query(CREATE_VECTOR_INDEX_STATEMENT)
print ("Generating Embeddings for Contract Excerpts...")
driver.execute_query(EMBEDDINGS_STATEMENT, token = OPENAI_API_KEY)

여기서 "CREATE_GRAPH_STATEMENT"가 유일하게 복잡한 부분이에요. 계약 JSON을 Knowledge Graph의 Nodes와 Relationships에 매핑하는 Cypher 문이거든요.

전체 Cypher 설명은 다음과 같아요:

CREATE_GRAPH_STATEMENT = """
WITH $data AS data
WITH data.agreement as a

MERGE (agreement:Agreement {contract_id: a.contract_id})
ON CREATE SET 
  agreement.contract_id  = a.contract_id,
  agreement.name = a.agreement_name,
  agreement.effective_date = a.effective_date,
  agreement.expiration_date = a.expiration_date,
  agreement.agreement_type = a.agreement_type,
  agreement.renewal_term = a.renewal_term,
  agreement.most_favored_country = a.governing_law.most_favored_country
  //agreement.Notice_period_to_Terminate_Renewal = a.Notice_period_to_Terminate_Renewal

MERGE (gl_country:Country {name: a.governing_law.country})
MERGE (agreement)-[gbl:GOVERNED_BY_LAW]->(gl_country)
SET gbl.state = a.governing_law.state


FOREACH (party IN a.parties |
  // todo proper global id for the party
  MERGE (p:Organization {name: party.name})
  MERGE (p)-[ipt:IS_PARTY_TO]->(agreement)
  SET ipt.role = party.role
  MERGE (country_of_incorporation:Country {name: party.incorporation_country})
  MERGE (p)-[incorporated:INCORPORATED_IN]->(country_of_incorporation)
  SET incorporated.state = party.incorporation_state
)

WITH a, agreement, [clause IN a.clauses WHERE clause.exists = true] AS valid_clauses
FOREACH (clause IN valid_clauses |
  CREATE (cl:ContractClause {type: clause.clause_type})
  MERGE (agreement)-[clt:HAS_CLAUSE]->(cl)
  SET clt.type = clause.clause_type
  // ON CREATE SET c.excerpts = clause.excerpts
  FOREACH (excerpt IN clause.excerpts |
    MERGE (cl)-[:HAS_EXCERPT]->(e:Excerpt {text: excerpt})
  )
  //link clauses to a Clause Type label
  MERGE (clType:ClauseType{name: clause.clause_type})
  MERGE (cl)-[:HAS_TYPE]->(clType)
)"""

다음은 성명서의 기능에 대한 분석이에요.

데이터 바인딩:

WITH $data AS data
WITH data.agreement as a
  • $data는 JSON 형식으로 쿼리에 전달되는 입력 데이터에요. 계약(agreement)에 관한 정보를 담고 있죠.
  • 두 번째 줄은 data.agreement를 별칭 a에 할당해서 후속 쿼리에서 계약 세부정보를 참조할 수 있게 해줘요.

계약 Node를 업데이트해요:

MERGE (agreement:Agreement {contract_id: a.contract_id})
ON CREATE SET 
  agreement.name = a.agreement_name,
  agreement.effective_date = a.effective_date,
  agreement.expiration_date = a.expiration_date,
  agreement.agreement_type = a.agreement_type,
  agreement.renewal_term = a.renewal_term,
  agreement.most_favored_country = a.governing_law.most_favored_country
  • MERGE는 기존의 Agreement Node를 지정된 contract_id로 찾으려고 시도해요. 해당 Node가 없으면 Node를 만들죠.
  • ON CREATE SET 절은 새로 생성된 Agreement Node에 contract_id, agreement_name, effective_date 및 JSON 입력의 기타 계약 관련 필드와 같은 다양한 속성을 설정하는 역할을 해요.

준거법 Relationship 생성:

 MERGE (gl_country:Country {name: a.governing_law.country})
MERGE (agreement)-[gbl:GOVERNED_BY_LAW]->(gl_country)
SET gbl.state = a.governing_law.state
  • 이렇게 하면 계약과 관련된 준거법 국가의 Country Node가 생성돼요.
  • 그런 다음 GOVERNED_BY_LAW Relationship을 AgreementCountry 사이에 생성하거나 병합하죠.
  • 또한 GOVERNED_BY_LAW Relationship의 state 속성도 설정해줘요.

당사자 및 법인 Relationship 생성:

 FOREACH (party IN a.parties |
  MERGE (p:Organization {name: party.name})
  MERGE (p)-[ipt:IS_PARTY_TO]->(agreement)
  SET ipt.role = party.role
  MERGE (country_of_incorporation:Country {name: party.incorporation_country})
  MERGE (p)-[incorporated:INCORPORATED_IN]->(country_of_incorporation)
  SET incorporated.state = party.incorporation_state
)

계약의 각 당사자(a.parties)에 대해 다음을 수행해요:

  • Organization 파티를 위한 Node를 upsert(병합)해요.
  • 생성합니다 IS_PARTY_TO 관계를 OrganizationAgreement 사이에 만들고, role을 당사자(예: 구매자, 판매자)로 설정해요.
  • Country 노드를 조직이 통합된 국가의 노드와 병합해요.
  • 생성합니다 INCORPORATED_IN 관계를 조직과 법인 설립 국가 간에 설정하고, 조직이 설립된 state를 지정해요.

계약 조항 및 발췌문 작성:

WITH a, agreement, [clause IN a.clauses WHERE clause.exists = true] AS valid_clauses
FOREACH (clause IN valid_clauses |
  CREATE (cl:ContractClause {type: clause.clause_type})
  MERGE (agreement)-[clt:HAS_CLAUSE]->(cl)
  SET clt.type = clause.clause_type
  FOREACH (excerpt IN clause.excerpts |
    MERGE (cl)-[:HAS_EXCERPT]->(e:Excerpt {text: excerpt})
  )
  MERGE (clType:ClauseType{name: clause.clause_type})
  MERGE (cl)-[:HAS_TYPE]->(clType)
)
  • 이 부분은 먼저 조항 목록 (a.clauses)을 필터링해서 clause.exists = true인 항목만 포함하도록 해요 (즉, 1단계에서 LLM이 식별한 발췌문이 있는 조항).

각 조항에 대해:

  • ContractClause 노드를 만들고 nametype을 조항 유형에 맞게 설정해요.
  • HAS_CLAUSE 관계를 AgreementContractClause 사이에 설정해요.
  • excerpt를 조항과 연결해서 Excerpt 노드를 만들고, ContractClauseHAS_EXCERPT 관계를 사용해 연결해요.
  • 마지막으로, ClauseType 노드를 조항 유형에 따라 생성(또는 병합)하고, ContractClauseHAS_TYPE 관계를 사용해 ClauseType에 연결해요.

가져오기 스크립트가 실행되면 Neo4j에서 단일 계약을 Knowledge Graph로 시각화할 수 있어요.

A knowledge graph representation of a single contract

단일 계약에 대한 Knowledge Graph 표현: 녹색은 당사자(조직), 파란색은 계약 조항, 연한 갈색은 발췌, 주황색은 국가입니다. 저자의 이미지.

Knowledge Graph의 세 가지 계약에는 작은 그래프(노드 100개 미만, 관계 200개 미만)만 필요했어요. 가장 중요한 것은 발췌문에 40~50개의 Vector Embedding만 필요하다는 것이죠. 이제 적은 수의 벡터가 포함된 이 Knowledge Graph를 사용해서 꽤 강력한 Q&A 에이전트를 만들 수 있어요.

3단계. GraphRAG용 데이터 검색 기능 개발

이제 계약이 Knowledge Graph로 구성되었으니, 다음 단계는 작은 그래프 데이터 검색 기능 세트를 만드는 거예요. 이러한 기능은 핵심 빌딩 블록 역할을 해서 4단계에서 Q&A 에이전트를 개발할 수 있게 해줘요.

몇 가지 기본 데이터 검색 기능을 정의해 볼까요?

  1. 계약에 대한 기본 세부 정보를 검색합니다 (계약 ID 제공).
  2. 특정 조직과 관련된 계약을 찾습니다 (일부 조직 이름 제공).
  3. 다음을 수행 특정 절 유형을 포함하는 계약을 찾아보세요.
  4. 계약에 특정 유형의 조항이 포함되어 있는지 확인하세요.
  5. 조항의 텍스트(발췌)와의 Semantic Search 유사성을 기반으로 계약을 찾습니다 (예: "금지 품목"의 사용을 언급하는 계약).
  6. 데이터베이스의 모든 계약에 대해 Natural Language 쿼리를 실행합니다. 예를 들어 "특정 조건을 충족하는 계약 수"를 계산하는 집계 쿼리를 실행합니다.

4단계에서는 Microsoft Semantic Kernel 라이브러리를 사용해서 Q&A를 작성할 거예요. 이 라이브러리는 에이전트 구축 프로세스를 단순화해줘요. 이를 통해 개발자는 에이전트가 질문에 답하기 위해 사용할 수 있는 기능과 도구를 정의할 수 있죠.

Neo4j와 Semantic Kernel 라이브러리 간의 통합을 단순화하기 위해 ContractPlugin을 만들었어요. 이는 각 데이터 검색 기능의 "서명"을 정의해요. @kernel_function 데코레이터와 각 기능에 제공되는 유형 정보 및 설명도 포함되어 있다는 점을 참고하세요.

Semantic Kernel은 "플러그인" 클래스 개념을 사용해서 에이전트가 사용할 수 있는 기능 그룹을 캡슐화해요. 데코레이팅된 함수, 타입 정보, 문서를 사용해서 사용 가능한 함수에 대한 LLM 함수 호출 기능을 알려주는 거죠.

from typing import List, Optional, Annotated
from AgreementSchema import Agreement, ClauseType
from semantic_kernel.functions import kernel_function
from ContractService import  ContractSearchService

class ContractPlugin:
    def __init__(self, contract_search_service: ContractSearchService ):
        self.contract_search_service = contract_search_service
    
    @kernel_function
    async def get_contract(self, contract_id: int) -> Annotated[Agreement, "A contract"]:
        """Gets details about a contract with the given id."""
        return await self.contract_search_service.get_contract(contract_id)

    @kernel_function
    async def get_contracts(self, organization_name: str) -> Annotated[List[Agreement], "A list of contracts"]:
        """Gets basic details about all contracts where one of the parties has a name similar to the given organization name."""
        return await self.contract_search_service.get_contracts(organization_name)
    
    @kernel_function
    async def get_contracts_without_clause(self, clause_type: ClauseType) -> Annotated[List[Agreement], "A list of contracts"]:
        """Gets basic details from contracts without a clause of the given type."""
        return await self.contract_search_service.get_contracts_without_clause(clause_type=clause_type)
    
    @kernel_function
    async def get_contracts_with_clause_type(self, clause_type: ClauseType) -> Annotated[List[Agreement], "A list of contracts"]:
        """Gets basic details from contracts with a clause of the given type."""
        return await self.contract_search_service.get_contracts_with_clause_type(clause_type=clause_type)

    @kernel_function
    async def get_contracts_similar_text(self, clause_text: str) -> Annotated[List[Agreement], "A list of contracts with similar text in one of their clauses"]:
        """Gets basic details from contracts having semantically similar text in one of their clauses to the to the 'clause_text' provided."""
        return await self.contract_search_service.get_contracts_similar_text(clause_text=clause_text)
    
    @kernel_function
    async def answer_aggregation_question(self, user_question: str) -> Annotated[str, "An answer to user_question"]:
        """Answer obtained by turning user_question into a CYPHER query"""
        return await self.contract_search_service.answer_aggregation_question(user_question=user_question)

한번 ContractService를 살펴보시는 걸 추천드려요. 여기에는 위에 나온 각 함수 구현을 포함하는 클래스가 들어있는데, 각 기능은 서로 다른 데이터 검색 기술을 사용하고 있어요.

다양한 GraphRAG 데이터 검색 기술과 패턴을 보여주는 기능들의 구현을 좀 더 자세히 살펴볼까요?

계약 가져오기 (계약 ID에서) — Cypher 기반 검색 기능

get_contract(self, contract_id: int)는 특정 계약 (Agreement)에 대한 세부 정보를 검색하도록 설계된 비동기식 메서드에요. Cypher 쿼리를 사용해서 Neo4j 데이터베이스에서 정보를 가져오죠. 이 함수는 계약, 조항, 당사자 및 이들의 관계에 대한 정보로 채워진 Agreement 객체를 반환해요.

이 함수의 구현은 다음과 같아요:

async def get_contract(self, contract_id: int) -> Agreement:
        
        GET_CONTRACT_BY_ID_QUERY = """
            MATCH (a:Agreement {contract_id: $contract_id})-[:HAS_CLAUSE]->(clause:ContractClause)
            WITH a, collect(clause) as clauses
            MATCH (country:Country)-[i:INCORPORATED_IN]-(p:Organization)-[r:IS_PARTY_TO]-(a)
            WITH a, clauses, collect(p) as parties, collect(country) as countries, collect(r) as roles, collect(i) as states
            RETURN a as agreement, clauses, parties, countries, roles, states
        """
        
        agreement_node = {}
        
        records, _, _  = self._driver.execute_query(GET_CONTRACT_BY_ID_QUERY,{'contract_id':contract_id})

        if (len(records)==1):
            agreement_node =    records[0].get('agreement')
            party_list =        records[0].get('parties')
            role_list =         records[0].get('roles')
            country_list =      records[0].get('countries')
            state_list =        records[0].get('states')
            clause_list =       records[0].get('clauses')
        
        return await self._get_agreement(
            agreement_node, format="long",
            party_list=party_list, role_list=role_list,
            country_list=country_list,state_list=state_list,
            clause_list=clause_list
        )

가장 중요한 부분은 바로 이 Cypher 쿼리, GET_CONTRACT_BY_ID_QUERY이에요. 이 쿼리는 입력 파라미터로 주어진 `contract_id`를 사용해서 실행되죠. 결과는 매칭되는 계약, 그 계약의 조항들, 그리고 관련된 당사자들인데, 각 당사자는 역할과 법인 설립 국가/주를 가지고 있어요.

그 다음, 데이터는 _get_agreement로 전달되는데, 이건 데이터를 "계약"에 매핑하는 간단한 유틸리티 함수에요. 계약은 다음과 같이 정의된 TypedDict랍니다:

class Agreement(TypedDict):  
    contract_id: int
    agreement_name: str
    agreement_type: str
    effective_date: str
    expiration_date: str
    renewal_term: str
    notice_period_to_terminate_Renewal: str
    parties: List[Party]
    clauses: List[ContractClause]

조항 유형 없이 계약 가져오기 - 또 다른 Cypher 검색 기능

이 함수는 관계가 없는지 테스트하는 Knowledge Graph의 강력한 기능을 보여주는 예시입니다.

get_contracts_without_clause() 함수는 Neo4j 데이터베이스에서 특정 유형의 조항을 *가지고 있지 않은* 모든 `Agreements`를 검색해요. 이 함수는 입력으로 `ClauseType`을 받아서 조건에 맞는 `Agreement` 객체들의 리스트를 반환하죠.

이런 유형의 데이터 검색은 Vector Search로는 쉽게 구현하기 어려워요. 전체 구현은 다음과 같습니다:

async def get_contracts_without_clause(self, clause_type: ClauseType) -> List[Agreement]:
        GET_CONTRACT_WITHOUT_CLAUSE_TYPE_QUERY = """
            MATCH (a:Agreement)
            OPTIONAL MATCH (a)-[:HAS_CLAUSE]->(cc:ContractClause {type: $clause_type})
            WITH a,cc
            WHERE cc is NULL
            WITH a
            MATCH (country:Country)-[i:INCORPORATED_IN]-(p:Organization)-[r:IS_PARTY_TO]-(a)
            RETURN a as agreement, collect(p) as parties, collect(r) as roles, collect(country) as countries, collect(i) as states
        """
       
        #run the Cypher query
        records, _ , _ = self._driver.execute_query(GET_CONTRACT_WITHOUT_CLAUSE_TYPE_QUERY,{'clause_type':clause_type.value})

        all_agreements = []
        for row in records:
            agreement_node =  row['agreement']
            party_list =  row['parties']
            role_list =  row['roles']
            country_list = row['countries']
            state_list = row['states']
            agreement : Agreement = await self._get_agreement(
                format="short",
                agreement_node=agreement_node,
                party_list=party_list,
                role_list=role_list,
                country_list=country_list,
                state_list=state_list
            )
            all_agreements.append(agreement)
        return all_agreements

다시 말씀드리지만, 형식은 이전 기능과 유사해요. Cypher 쿼리 GET_CONTRACTS_WITHOUT_CLAUSE_TYPE_QUERY는 매칭할 Node와 Relationship 패턴을 정의하죠. Clause Type을 포함하는 계약을 필터링하기 위해 선택적 매칭을 수행하고, 관련 당사자 및 해당 세부 정보와 같은 계약에 대한 관련 데이터를 수집해요.

그런 다음 함수는 다음 목록을 구성하고 반환합니다. Agreement는 각 매칭된 계약에 대한 모든 관련 정보를 캡슐화하는 객체에요.

의미상 유사한 텍스트로 계약 체결 — Vector Search + Graph Data 검색 기능

get_contracts_similar_text() 기능은 제공된 clause_text와 유사한 텍스트가 포함된 Clause를 포함하는 계약을 찾도록 설계되었어요. Semantic Vector Search를 사용하여 관련 발췌문을 식별하고 Graph를 탐색하여 해당 계약 및 Clause에 대한 정보와 해당 발췌문의 출처에 대한 정보를 반환하죠.

async def get_contracts_similar_text(self, clause_text: str) -> List[Agreement]:

        #Cypher to traverse from the semantically similar excerpts back to the agreement
        EXCERPT_TO_AGREEMENT_TRAVERSAL_QUERY="""
            MATCH (a:Agreement)-[:HAS_CLAUSE]->(cc:ContractClause)-[:HAS_EXCERPT]-(node) 
            RETURN a.name as agreement_name, a.contract_id as contract_id, cc.type as clause_type, node.text as excerpt
        """
        
        #Set up vector Cypher retriever
        retriever = VectorCypherRetriever(
            driver= self._driver,  
            index_name="excerpt_embedding",
            embedder=self._openai_embedder, 
            retrieval_query=EXCERPT_TO_AGREEMENT_TRAVERSAL_QUERY,
            result_formatter=my_vector_search_excerpt_record_formatter
        )
        
        # run vector search query on excerpts and get results containing the relevant agreement and clause 
        retriever_result = retriever.search(query_text=clause_text, top_k=3)

        #set up List of Agreements (with partial data) to be returned
        agreements = []
        for item in retriever_result.items:
            //extract information from returned items and append agreement to results
            // full code not shown here but available on the Github repo
            

        return agreements

이 Data 검색 기능의 주요 구성 요소를 한번 살펴볼까요?

  • Neo4j GraphRAG VectorCypherRetriever는 개발자가 Vector Index에 대해 Semantic 유사성을 수행할 수 있도록 도와줘요. 우리의 경우, 의미상 유사한 각 발췌 "Node"가 발견되면 추가 Cypher 표현식을 사용하여 해당 Node와 관련된 Graph에서 추가 Node를 가져오게 되죠.
  • VectorCypherRetriever의 매개변수는 꽤 간단해요. index_name은 Semantic 유사성을 실행할 Vector Index이고, Embedder는 텍스트 조각에 대한 Vector Embedding을 생성해요. driver는 Neo4j Python Driver의 인스턴스일 뿐이고요. retrieval_query는 Semantic 유사성으로 식별되는 모든 "발췌" Node와 연결된 추가 Node 및 Relationship을 지정합니다.
  • EXCERPT_TO_AGREEMENT_TRAVERSAL_QUERY는 검색할 추가 Node를 지정해요. 이 경우 모든 발췌문에 대해 관련 계약 Clause 및 해당 계약을 검색하죠.
 EXCERPT_TO_AGREEMENT_TRAVERSAL_QUERY="""
  MATCH (a:Agreement)-[:HAS_CLAUSE]->(cc:ContractClause)-[:HAS_EXCERPT]-(node) 
  RETURN a.name as agreement_name, a.contract_id as contract_id, cc.type as clause_type, node.text as excerpt
"""

자연어 쿼리 실행 — Text2Cypher Data 검색 기능

answer_aggregation_question() 기능은 Neo4j GraphRAG 패키지 Text2CypherRetriever를 활용하여 자연어로 질문에 답해요. Text2CypherRetriever는 LLM을 사용하여 사용자 질문을 Cypher 쿼리로 변환하고 이를 Neo4j Database에 대해 실행하죠.

이 기능은 GPT-4o를 활용하여 필요한 Cypher 쿼리를 생성해요. 이 Data 검색 기능의 주요 구성 요소를 한번 살펴볼까요?

 async def answer_aggregation_question(self, user_question) -> str:
        answer = ""


        NEO4J_SCHEMA = """
            omitted for brevity (see below for the full value)
        """

        # Initialize the retriever
        retriever = Text2CypherRetriever(
            driver=self._driver,
            llm=self._llm,
            neo4j_schema=NEO4J_SCHEMA
        )

        # Generate a Cypher query using the LLM, send it to the Neo4j database, and return the results
        retriever_result = retriever.search(query_text=user_question)

        for item in retriever_result.items:
            content = str(item.content)
            if content:
                answer += content + 'nn'

        return answer

이 기능은 Neo4j GraphRAG 패키지의 Text2CypherRetriever를 활용해요. LLM을 사용하는데, 이 경우 OpenAI LLM은 사용자 질문(자연어)을 데이터베이스에 대해 실행되는 Cypher 쿼리로 바꾸는 데 사용돼요. 이 쿼리의 결과가 반환되는 거죠.

LLM이 데이터베이스에 정의된 Node, Relationship, 그리고 Property를 사용하는 쿼리를 생성하도록 하는 핵심 요소는 LLM에 Schema에 대한 텍스트 설명을 제공하는 것이에요.

우리의 경우에는 다음과 같은 데이터 모델 표현으로 충분하답니다.

 NEO4J_SCHEMA = """
Node properties:
Agreement {agreement_type: STRING, contract_id: INTEGER,effective_date: STRING,renewal_term: STRING, name: STRING}
ContractClause {name: STRING, type: STRING}
ClauseType {name: STRING}
Country {name: STRING}
Excerpt {text: STRING}
Organization {name: STRING}

Relationship properties:
IS_PARTY_TO {role: STRING}
GOVERNED_BY_LAW {state: STRING}
HAS_CLAUSE {type: STRING}
INCORPORATED_IN {state: STRING}

The relationships:
(:Agreement)-[:HAS_CLAUSE]->(:ContractClause)
(:ContractClause)-[:HAS_EXCERPT]->(:Excerpt)
(:ContractClause)-[:HAS_TYPE]->(:ClauseType)
(:Agreement)-[:GOVERNED_BY_LAW]->(:Country)
(:Organization)-[:IS_PARTY_TO]->(:Agreement)
(:Organization)-[:INCORPORATED_IN]->(:Country)
  """

4단계. Q&A 에이전트 구축

Knowledge Graph 데이터 검색 기능으로 무장했으니, GraphRAG 기반 에이전트를 구축할 준비가 되었어요.

GPT-4o, 데이터 검색 기능, 그리고 Neo4j 기반 Knowledge Graph의 조합을 사용해서 계약에 대한 사용자 Query에 응답할 수 있는 챗봇 에이전트를 설정해 볼 거예요.

Microsoft Semantic Kernel이라는 프레임워크를 사용할 건데요, 개발자가 LLM 함수 호출을 기존 API 및 데이터 검색 기능과 통합할 수 있게 도와주죠.

이 프레임워크는 "플러그인"이라는 개념을 사용해서 커널이 수행할 수 있는 특정 기능을 나타내요. 우리의 경우 "ContractPlugin"에 정의된 모든 데이터 검색 기능은 LLM에서 질문에 답하는 데 사용될 수 있답니다.

프레임워크는 "메모리" 개념을 사용해서 사용자와 에이전트 간의 모든 상호 작용은 물론, 실행된 기능과 검색된 데이터를 유지해요.

몇 줄의 코드만으로 아주 간단한 터미널 기반 에이전트를 구현할 수 있어요. 아래 스니펫은 에이전트의 주요 부분을 보여줍니다 (import 및 환경 변수는 제거했어요).

logging.basicConfig(level=logging.INFO)

# Initialize the kernel
kernel = Kernel()

# Add the Contract Search plugin to the kernel
contract_search_neo4j = ContractSearchService(NEO4J_URI,NEO4J_USER,NEO4J_PASSWORD)
kernel.add_plugin(ContractPlugin(contract_search_service=contract_search_neo4j),plugin_name="contract_search")

# Add the OpenAI chat completion service to the Kernel
kernel.add_service(OpenAIChatCompletion(ai_model_id="gpt-4o",api_key=OPENAI_KEY, service_id=service_id))

# Enable automatic function calling
settings: OpenAIChatPromptExecutionSettings = kernel.get_prompt_execution_settings_from_service_id(service_id=service_id)
settings.function_choice_behavior = FunctionChoiceBehavior.Auto(filters={"included_plugins": ["contract_search"]})

# Create a history of the conversation
history = ChatHistory()

async def basic_agent() :
    userInput = None
    while True:
        # Collect user input
        userInput = input("User > ")

        # Terminate the loop if the user says "exit"
        if userInput == "exit":
            break

        # Add user input to the history
        history.add_user_message(userInput)

        # 3. Get the response from the AI with automatic function calling
        chat_completion : OpenAIChatCompletion = kernel.get_service(type=ChatCompletionClientBase)
        result = (await chat_completion.get_chat_message_contents(
            chat_history=history,
            settings=settings,
            kernel=kernel,
            arguments=KernelArguments(),
        ))[0]

        # Print the results
        print("Assistant > " + str(result))

        # Add the message from the agent to the chat history
        history.add_message(result)

if __name__ == "__main__":
    asyncio.run(basic_agent())

에이전트에게 몇 가지 중요한 질문을 던져볼게요.

1. 가격 제한은 있지만 보험 조항은 없는 계약을 찾아줘.

Assistant > I found the following contract that contains a Price Restrictions clause but does not contain an Insurance clause:

- **Name:** Birch First Global Investments Inc. and Mount Knowledge Holdings Inc. MARKETING AFFILIATE AGREEMENT
- **Agreement Type:** MARKETING AFFILIATE AGREEMENT
- **Parties:**
  - **MOUNT KNOWLEDGE HOLDINGS INC.** (Marketing Affiliate), incorporated in Nevada, USA
  - **BIRCH FIRST GLOBAL INVESTMENTS INC.** (Company), incorporated in the Virgin Islands (VIR)

Would you like to know more details about this contract?

여기서 Semantic Agent 플래너는 커널에서 사용 가능한 기능을 살펴보고, 이 질문에 답하려면 자체적으로 완전한 답을 제공하지 않는 두 가지 기능을 호출해야 한다고 판단했어요.

먼저 "[가격 제한] clause가 있는 계약 받기" 기능을 실행한 다음, "[보험] clause 없이 계약 받기" 기능을 실행하는 거죠. 그런 다음 플래너는 두 기능의 결과를 합쳐서 교차점을 찾고, 결국 정답을 찾아낸답니다.

디버깅 정보를 보면 LLM이 수행한 호출 순서와 호출에 사용된 토큰 수를 확인할 수 있어요. 두 가지 데이터 검색 함수를 사용한 호출을 확인해 보세요.

Debugging information

2. 네, 부탁해요. 자세한 내용을 알려주세요.

Assistant > Here are the detailed information about the contract:

### Contract Details:
- **Name:** Birch First Global Investments Inc. and Mount Knowledge Holdings Inc. MARKETING AFFILIATE AGREEMENT
- **Agreement Type:** MARKETING AFFILIATE AGREEMENT
- **Expiration Date:** December 31, 2014
- **Renewal Term:** One (1) year

### Parties Involved:
1. **MOUNT KNOWLEDGE HOLDINGS INC.** 
   - **Role:** Marketing Affiliate
   - **Incorporation Country:** USA
   - **Incorporation State:** Nevada
2. **BIRCH FIRST GLOBAL INVESTMENTS INC.**
   - **Role:** Company
   - **Incorporation Country:** Virgin Islands (VIR)

### Clauses Included:
- No-Solicit Of Employees
- Third Party Beneficiary
- Price Restrictions
- Affiliate License-Licensee
- Affiliate License-Licensor
- Cap On Liability
- Audit Rights
- Warranty Duration
- Post-Termination Services
- Non-Transferable License
- License grant
- IP Ownership Assignment
- Minimum Commitment
- Anti-Assignment
- Non-Disparagement

### Clauses Not Included:
- Insurance

Is there anything specific you would like to know or any particular clause details you are interested in?

이 경우, LLM은 뒤에서 'ID로 계약 찾기'를 사용했어요. 사용자에게 표시되는 출력에는 계약 ID가 포함되지 않은 것을 알 수 있죠. 하지만 Semantic Kernel은 이전 호출의 함수에서 반환된 모든 정보를 대화 메모리에 저장했다는 점!

에이전트가 호출할 함수, 메모리 사용, 그리고 사용자 쿼리를 올바른 매개변수를 사용하여 함수 호출로 자동 마샬링하도록 하는 이러한 강력한 조합은 Semantic Kernel로 구축된 에이전트 뒤에 숨겨진 마법을 설명해줘요.

3. 때때로 가격을 변경할 수 있는 능력을 언급하는 계약이 있나요?

Assistant > Yes, there is a contract that mentions 
the ability to make price changes from time to time. 
Here is the relevant excerpt:

1. **Birch First Global Investments Inc. and Mount Knowledge Holdings Inc. MARKETING AFFILIATE AGREEMENT**
   - **Contract ID:** 1
   - **Clause Type:** Price Restrictions
   - **Excerpt:**
     ```
     Company reserves the right to change its process and/or fees, from time to time, in its sole and absolute discretion. In the event of a price and/or fee change for Technology, Company shall notify MA in writing ninety (90) days prior to the effective date of any such change. All other prices and/or fees may be adjusted without prior notice to MA.
     ```

Would you like more information about this contract or any other details?

이 질문에 답하기 위해 Semantic Kernel은 get_contract_similar_text 데이터 검색 기능을 사용했어요.

User > any contract mentions the ability to make price changes from time to time
INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion_base:processing 1 tool calls in parallel.

INFO:semantic_kernel.kernel:Calling contract_search-get_contracts_similar_text
 function with args: {"clause_text":"price changes from time to time"}


Assistant > Yes, there is a contract that mentions the ability to make price changes from time to time. Here is the relevant excerpt:

1. **Birch First Global Investments Inc. and Mount Knowledge Holdings Inc. MARKETING AFFILIATE AGREEMENT**
   - **Contract ID:** 1
   - **Clause Type:** Price Restrictions
   - **Excerpt:**
     ```
     Company reserves the right to change its process and/or fees, from time to time, in its sole and absolute discretion. In the event of a price and/or fee change for Technology, Company shall notify MA in writing ninety (90) days prior to the effective date of any such change. All other prices and/or fees may be adjusted without prior notice to MA.
     ```

앞서 설명했듯이 이 기능은 Vector Search와 Cypher 쿼리를 사용하여 의미상 유사한 발췌문뿐만 아니라 이를 포함하는 관련 계약서 및 조항도 검색해요.

마지막으로 데이터베이스에 몇 개의 계약이 있는지 물어볼게요.

4. 데이터베이스에는 몇 개의 계약이 있나요?

이 질문에서는 LLM이 Text2Cypher 데이터 검색기를 사용해야 해요.

Assistant > There are a total of 3 contracts in the database.

디버그 정보에서 Semantic Kernel이 "user_question" = "데이터베이스에 몇 개의 계약이 있습니까?"로 Text2Cypher 데이터 검색 기능을 실행한 것을 확인할 수 있어요.

User >  how many contracts are there on the database?
INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 200 OK"
INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion_base:processing 1 tool calls in parallel.

INFO:semantic_kernel.kernel:Calling contract_search-answer_aggregation_question function 
with args: {"user_question":"How many contracts are there in the database?"}


INFO:semantic_kernel.functions.kernel_function:Function completed. Duration: 0.588805s

INFO:semantic_kernel.connectors.ai.open_ai.services.open_ai_handler:OpenAI usage: CompletionUsage(completion_tokens=13, prompt_tokens=3328, total_tokens=3341, completion_tokens_details={'reasoning_tokens': 0})

Assistant > There are a total of 3 contracts in the database.

직접 시도해 보세요

GitHub 저장소에는 더 멋진 에이전트 UI를 제공하는 Streamlit 앱이 포함되어 있어요. 에이전트가 더 많은 질문을 처리할 수 있도록 에이전트와 상호 작용하고 ContractPlugin을 변경해 보는 걸 추천해요.

결론

이번 블로그에서는 노동 집약적인 상업 계약 검토 작업을 더 효율적인 AI 기반 프로세스로 전환하기 위한 GraphRAG 접근 방식을 살펴봤어요.

LLM과 프롬프트를 활용한 타겟 정보 추출에 집중하고, Neo4j로 구조화된 Knowledge Graph를 구축하고, 간단한 데이터 검색 기능을 구현하고, 궁극적으로 Q&A 에이전트를 개발함으로써 복잡한 질문을 효과적으로 처리하는 지능형 솔루션을 만들 수 있었죠.

이 접근 방식은 기존 벡터 검색 기반 RAG에서 발견되는 비효율성을 최소화하고, 대신 관련 정보만 추출하는 데 중점을 둬서 불필요한 Vector Embedding의 필요성을 줄이고 전체 프로세스를 단순화해요. 계약 수집에서 대화형 Q&A 에이전트까지의 여정을 통해 여러분의 프로젝트에서 GraphRAG를 활용하여 효율성을 높이고 더 스마트한 AI 기반 의사 결정을 내릴 수 있기를 바라요.

지금 바로 상업 계약 검토 에이전트 구축을 시작하고 GraphRAG의 강력한 기능을 직접 경험해보세요!

  • GraphRAG
  • RAG

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

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

반응형
반응형

OpenAI의 Agents SDK, MCP, Ollama 및 Neo4j로 구축

금융 서비스 업계에서 KYC(고객 파악)와 AML(자금 세탁 방지)은 불법 행위에 맞서는 중요한 방어선이에요. KYC는 고객, 계정, 거래, IP 주소, 장치, 위치 등 모든 요소가 거대한 관계 네트워크 안에서 서로 연결된 **Node**로 표현되는 **Graph** 문제로 자연스럽게 모델링할 수 있죠. 수사관들은 사기, 제재 위반, 돈세탁 조직을 밝혀내기 위해 겉으로는 연관 없어 보이는 점들을 연결하며 이 복잡한 연결망을 조사하게 돼요.

이것은 **Knowledge Graph** 기반 AI의 훌륭한 활용 사례랍니다! (GraphRAG) 복잡하게 얽힌 연결망은 일반적인 문서 기반 **RAG**(보통 **Vector Embedding** 유사성 검색 및 순위 재지정 기술 기반) 이상의 기능을 필요로 하거든요.

폭로

저는 Neo4j의 AI 수석 제품 관리자이고, 이 글에서 소개된 **Graph Database**를 만들고 있어요. 코드 스니펫은 Neo4j에 초점을 맞추고 있지만, 모든 **Graph Database**에 동일한 패턴을 적용할 수 있다는 점! 제 주요 목표는 **GraphRAG** 에이전트 구축에 대한 실용적인 지침을 AI/ML 커뮤니티와 공유하는 것이에요. 모든 코드는 연결된 저장소에 오픈 소스로 공개되어 있으니, 자유롭게 탐색하고 실험하고, 조정해 보세요.

GraphRAG KYC 에이전트

이 블로그 포스팅은 AI 엔지니어와 개발자를 위해 초기 KYC 에이전트 프로토타입을 구축하는 방법에 대한 실습 가이드를 제공해요. OpenAI Agents SDK를 사용해서요! 잠재적인 사기 패턴을 발견하고 조사할 수 있는 도구 모음을 에이전트에 장착하는 방법을 한번 살펴볼까요?

아래 다이어그램은 KYC 조사 중에 제기된 질문에 답변하기 위한 에이전트 처리 파이프라인을 보여주고 있어요.

Image by the Author, generated with Napkin AI
다음을 사용하여 생성된 작성자의 이미지냅킨 AI

주요 구성 요소를 자세히 살펴볼게요.

  • KYC 에이전트: OpenAI Agents SDK를 활용하고 사용자의 **Query**와 대화 기록을 기반으로 사용할 도구를 결정하는 "브레인" 역할을 해요. MCP 호스트와 MCP 클라이언트 역할을 수행하는 Neo4j MCP 암호화 서버이기도 하죠. 가장 중요한 점은 대화 기록을 유지하면서 사용자로부터 질문을 받고 에이전트를 호출하고 결과를 처리하는 매우 간단한 루프를 실행한다는 거예요.
  • 도구 세트: 에이전트가 사용할 수 있는 도구 모음이에요.
    • GraphRAG 도구: 특정 **Cypher** **Query**를 래핑하는 **Graph** 데이터 검색 기능이죠. 예를 들어:
      • 고객 세부 정보 가져오기: 고객 ID가 주어지면 계정 및 최근 거래 내역을 포함하여 고객에 대한 정보를 검색하는 **Graph** 검색 도구에요.
    • Neo4j MCP 서버: Neo4j MCP 암호화 서버는 Neo4j **Database**와 상호 작용하는 도구를 제공해요. 세 가지 필수 도구를 제공하죠.
      1. **Database**에서 **Schema** 가져오기
      2. 실행 READ **Database**에 대한 **Cypher** **Query**
      3. 실행 WRITE **Database**에 대한 **Cypher** **Query**
    • Text-to-Cypher 도구: Ollama를 통해 로컬에서 실행되는 **Fine-tuning**된 Gemma3-4B 모델을 래핑하는 Python 함수에요. 이 도구는 자연어 질문을 **Cypher** **Graph** **Query**로 변환해 준답니다.
    • 메모리 생성 도구: 이를 통해 조사자는 조사 결과를 **Knowledge Graph**에 직접 문서화할 수 있어요. **Knowledge Graph**에 (조사의) "메모리"를 생성하고 이를 모든 관련 고객, 거래 및 계정에 연결하는 거죠. 시간이 지남에 따라 이는 향후 조사를 위한 귀중한 지식 기반을 구축하는 데 도움이 될 거예요.
  • KYC Knowledge Graph: 8,000명의 가상 고객, 계정, 거래, 장치 및 IP 주소에 대한 **Knowledge Graph**를 저장하는 Neo4j **Database**에요. 에이전트의 장기 기억 저장소로도 사용된답니다.

지금 바로 에이전트를 사용해 보고 싶으신가요? 프로젝트 저장소에 대한 지침을 따라 해보세요. 나중에 돌아와서 에이전트가 어떻게 구축되었는지 읽어볼 수도 있고요.

KYC에 GraphRAG를 사용해야 하는 이유

기존의 Retrieval-Augmented Generation (RAG) 시스템은 여러 조각으로 나뉜 대규모 텍스트에서 정보를 찾는 데 집중하는 경향이 있어요. 하지만 KYC 조사에서는 계정에 연결된 고객, 거래를 통해 연결된 계정, IP 주소 및 장치에 연결된 거래, 개인 및 고용주 주소와 연결된 고객 등, 서로 복잡하게 연결된 데이터 속에서 흥미로운 패턴을 찾아내는 게 중요하죠.

정교한 사기 패턴을 발견하려면 이러한 관계를 이해하는 것이 핵심이에요. 예를 들어:

  • "이 고객이 관심 목록에 있는 누군가와 IP 주소를 공유하나요?"
  • "이 거래가 자금 출처를 숨기기 위해 설계된 순환 결제 루프의 일부인가요?"
  • "새로 등록된 동일 회사에서 일하는 사람이 여러 개의 새 계정을 개설하고 있나요?"

이런 질문들은 결국 연결성에 대한 질문이에요. Knowledge Graph는 고객, 계정, 트랜잭션, 장치가 node이고, 이들 간의 관계가 명시적인 edge로 표현되는 데이터 구조라서 이런 작업에 아주 적합하답니다. GraphRAG (데이터 검색) 도구를 사용하면 비정상적인 활동 패턴을 쉽게 식별할 수 있어요.

Image by the Author generated with Napkin AI
냅킨 AI로 생성된 이미지

합성 KYC 데이터 세트

이 블로그에서는 8,000명의 가상 고객과 그들의 계정, 거래, 등록된 주소, 장치, IP 주소로 구성된 합성 데이터 세트를 사용해볼 거예요.

아래 이미지는 데이터 세트를 Neo4j에 로드한 후 데이터베이스의 "스키마"를 보여주고 있어요. Neo4j에서 스키마는 데이터베이스에 저장된 entity 및 relationship의 유형을 설명하죠. 여기서는 주요 entity가 고객, 주소, 계정, 장치, IP 주소, 거래이고, 이들 간의 주요 relationship은 그림과 같아요.

Image by the Author

데이터 세트에는 몇 가지 예외적인 케이스들이 포함되어 있어요. 일부 고객은 의심스러운 거래 링에 연루되어 있고, 몇몇 고립된 장치와 IP 주소 (어떤 고객이나 계정에도 연결되지 않은)도 있죠. 또, 여러 고객이 공유하는 주소도 일부 존재해요. 데이터 세트 생성 스크립트를 통해 데이터 세트를 자유롭게 탐색하고, 필요에 따라 수정해 보세요.

OpenAI Agents SDK를 사용하는 기본 에이전트

이제 KYC 에이전트의 핵심 부분을 한번 살펴볼까요?

구현은 대부분 kyc_agent.py에 담겨 있어요. 에이전트 실행 방법에 대한 전체 소스 코드와 단계별 지침은 에서 확인할 수 있답니다.

먼저, 적절한 지침을 사용하여 에이전트의 핵심 ID를 정의해 볼게요.

import os
from agents import Agent, Runner, function_tool
# ... 기타 수입품

# 에이전트에 대한 지침을 정의합니다.
instructions = """당신은 Knowledge Graph에 액세스할 수 있는 KYC 분석가입니다. 도구를 사용하여 고객, 계정 및 의심스러운 패턴에 대한 질문에 답하세요.
당신은 또한 Neo4j 전문가이며 Neo4j MCP 서버를 사용하여 그래프를 쿼리할 수 있습니다.
GraphRAG 도구로 답변할 수 없는 KYC 데이터베이스 관련 질문이 있는 경우,
- Neo4j MCP 서버를 사용하여 그래프의 스키마를 가져옵니다(필요한 경우)
- generate_cypher 도구를 사용하여 질문 및 스키마에서 Cypher 쿼리를 생성합니다.
- 질문에 답하기 위해 Neo4j MCP 서버를 사용하여 그래프를 쿼리합니다.
"""코드 언어: PHP (php)

이 지침은 정말 중요해요! 에이전트의 페르소나를 설정하고, 특히 사전 정의된 도구가 사용자의 요청에 맞지 않는 경우 문제에 접근하는 방법에 대한 전략을 제시하죠.

이제 최소한의 에이전트부터 시작해볼게요. 도구는 없고, 지침만 제공되는 거죠.

# 에이전트 정의, 나중에 도구를 추가하겠습니다.
kyc_agent = Agent(
   name="KYC 분석가",
   instructions=instructions,
   tools=[...],      # 우리는 이 목록을 채울 것입니다
   mcp_servers=[...] # 그리고 이건
)
코드 언어: PHP (php)

KYC 에이전트에 몇 가지 도구를 추가해볼까요?

에이전트의 성능은 어떤 도구를 사용하느냐에 따라 달라지죠. KYC 분석가에게 제공될 5가지 도구를 한번 살펴볼게요.

도구 1 & 2: 미리 정의된 Cypher Queries

일반적이면서 중요한 쿼리의 경우, Python 함수로 래핑된 최적화된 Cypher 쿼리를 사용하는 게 가장 좋아요. OpenAI Agent SDK의 @function_tool 데코레이터를 사용하면 에이전트에서 바로 이런 함수들을 사용할 수 있게 된답니다.

도구 1: find_customer_rings

이 도구는 자금 세탁, 특히 자금의 출처를 숨기기 위해 여러 계좌를 거치는 순환 거래 패턴을 탐지하도록 설계되었어요.

KYC 그래프에서는, 시작점 또는 그 근처로 돌아오는 주기 또는 경로를 찾는 것으로 표현할 수 있죠. 이런 패턴을 감지하려면 가변 길이 경로를 사용해서 특정 홉 거리까지 연결을 탐색하는 복잡한 그래프 순회 알고리즘이 필요해요.

아래 코드 조각은 find_customer_rings 기능인데, KYC 데이터베이스에 대해 Cypher 쿼리를 실행하고 최대 10개의 잠재 고객 링을 반환해줘요. 각 링에 대해 해당 링과 관련된 고객의 계정 및 거래 정보를 얻을 수 있답니다.

@function_tool
def find_customer_rings(max_number_rings: int = 10, customer_in_watchlist: bool = True, ...):
   """
   고위험 고객과 관련된 순환 거래 패턴(최대 6홉)을 탐지합니다.
   지정된 일치 고객이 계정을 소유한 계정 주기를 찾습니다.
   위험 기준(관심 목록 및/또는 PEP 상태).
   인수:
       max_number_rings: 반환할 최대 링(기본값: 10)
       customer_in_watchlist: 관심 목록에 있는 고객에 대한 필터(기본값: True)
       customer_is_pep: PEP 고객을 위한 필터(기본값: False)
       customer_id: 집중할 특정 고객(구현되지 않음)
   반품:
       dict: 링 경로 및 관련된 고위험 고객을 포함합니다.
   """
   logger.info(f"도구: FIND_CUSTOMER_RINGS")
   with driver.session() as session:
       result = session.run(
           f"""
           일치 p=(a:계정)-[:FROM|TO*6]->(a:계정)
           WITH p, [n IN 노드(p) WHERE n:계정] AS 계정
           계정 AS 계정 해제
           MATCH(고객:고객)-[r:OWNS]->(계정)
           cust.on_watchlist = $customer_in_watchlist 위치
           // ... 결과를 수집하기 위한 추가 Cypher ...
           """,
           max_number_rings=max_number_rings,
           customer_in_watchlist=customer_in_watchlist,
       )
       # ... 결과를 처리하고 반환하는 Python 코드 ...
코드 언어: PHP (php)

여기서 중요한 점은, doc string이 OpenAI Agents SDK에서 도구 설명으로 자동으로 사용된다는 거예요. 훌륭한 Python 함수 문서는 정말 유용하죠!

도구 2: get_customer_and_accounts

계정 및 가장 최근 거래를 포함해서 고객 프로필을 검색하는 간단하면서도 필수적인 도구에요. 모든 조사의 기본이라고 할 수 있죠. 코드는 customer ID를 가져와서 간단한 Cypher 쿼리를 래핑하는 함수인데, 이전 도구와 비슷해요.

다시 한번, 함수는 @function_tool 로 데코레이팅되어서 에이전트가 사용할 수 있도록 만들어줍니다.

이 Python으로 래핑된 Cypher 쿼리는 다음과 같아요.

result = session.run(
           """
           MATCH (c:고객 {id: $customer_id})-[o:OWNS]->(a:계정)
           WITH c, a
           CALL {
               MATCH (a)-[b:TO|FROM]->(t:트랜잭션)
               ORDER BY t.timestamp DESC
               LIMIT $tx_limit
               RETURN collect(t) AS 트랜잭션
           }
           RETURN c AS 고객, a AS 계정, 거래
           """,
           customer_id=input.customer_id
       )
코드 언어: PHP (php)

이 도구 디자인에서 눈여겨볼 점은 Pydantic을 사용해서 함수의 출력을 지정한다는 거예요. OpenAI Agents SDK는 함수에서 반환된 Pydantic 모델을 사용해서 출력 매개변수에 대한 텍스트 설명을 자동으로 생성해준답니다.

자세히 살펴보면 함수는 다음을 반환해요.

return CustomerAccountsOutput(          
 customer=CustomerModel(**customer),
 accounts=[AccountModel(**a) for a in accounts],
)
코드 언어: 자바스크립트 (자바스크립트)

CustomerModelAccountModel은 각 고객에 대해 반환된 각 속성, 해당 계정 및 최근 거래 목록을 포함하고 있어요. 이 정의는 스키마.py에서 확인할 수 있습니다.

도구 3 및 4: Neo4j MCP 서버와 Text-to-Cypher의 만남

여기서부터 KYC 에이전트가 더욱 흥미로운 능력을 발휘하게 돼요.

다목적 AI 에이전트를 구축할 때 중요한 과제는 미리 정의된 정적 기능을 넘어서 복잡한 데이터 소스와 동적으로 상호 작용할 수 있도록 하는 것이죠. 에이전트는 범용 쿼리를 수행할 수 있는 능력이 필요한데, 새로운 통찰력을 얻으려면 별도의 작업 없이 자발적인 데이터 탐색이 필요할 수 있어요. 선험적으로 가능한 모든 작업에 대한 Python 래퍼는 필요 없죠.

이 섹션에서는 이 문제를 해결하기 위한 일반적인 아키텍처 패턴을 살펴볼 거예요. Natural Language 질문을 Cypher로 변환하는 도구와 동적 쿼리 실행을 허용하는 다른 도구를 결합하는 거죠.

이 메커니즘은 Neo4j MCP 서버를 사용해서 동적 그래프 쿼리 실행을 노출하고, Google의 Gemma3-4B Fine-tuning 모델을 사용해서 Text-to-Cypher 번역을 하는 방식으로 보여드릴게요.

도구 3: Neo4j MCP 서버 도구 세트 추가

강력한 에이전트가 Knowledge Graph를 효과적으로 사용하려면 그래프의 구조를 이해하고 Cypher 쿼리를 실행할 수 있어야 해요. 이러한 기능을 통해 에이전트는 데이터를 검사하고 동적인 임시 쿼리를 실행할 수 있죠.

MCP Neo4j Cypher 서버는 다음과 같은 기본 도구를 제공해요: get-neo4j-schema (그래프 스키마를 동적으로 검색하기 위해), read-neo4j-cypher (임의 읽기 쿼리 실행용), write-neo4j-cypher (쿼리 생성, 업데이트, 삭제용).

다행히 OpenAI Agents SDK는 MCP를 지원해요. 아래 코드 조각은 Neo4j MCP 서버를 KYC 에이전트에 추가하는 것이 얼마나 쉬운지 보여준답니다.

# 도구 3: Neo4j MCP 서버 설정
neo4j_mcp_server = MCPServerStdio(
   params={
       "명령": "uvx",
       "인수": ["mcp-neo4j-cypher@0.2.1"],
       "환경": {
           "NEO4J_URI": NEO4J_URI,
           "NEO4J_USERNAME": NEO4J_USER,
           "NEO4J_PASSWORD": NEO4J_PASSWORD,
           "NEO4J_DATABASE": NEO4J_DATABASE,
       },
   },
   cache_tools_list=True,
   name="Neo4j MCP 서버",
)
코드 언어: PHP (php)

더 자세히 알고 싶다면 OpenAI Agents SDK에서 MCP가 어떻게 지원되는지 한번 살펴보세요.

도구 4: 텍스트를 Cypher로 변환하는 도구

자연어를 강력한 그래프 `Query`로 동적으로 변환하는 기능은 `Schema` 인식 `Query` 생성을 위해 Fine-tuning된 전문 LLM에 크게 의존하는 경우가 많아요.

Hugging Face에서 공개 가중치로 제공되는 Text-to-Cypher 모델을 사용할 수 있어요. 바로 neo4j/text-to-cypher-Gemma-3-4B-Instruct-2025.04.0 모델인데요. 이 모델은 사용자 질문과 `Schema`로부터 정확한 Cypher `Query`를 생성하도록 특별히 Fine-tuning되었답니다.

이 모델을 로컬 장치에서 실행하고 싶다면 Ollama를 사용하면 돼요. llama.cpp를 사용해서 Hugging Face 모델을 Ollama에서 실행하는 데 필요한 GGUF 형식으로 변환하는 건 비교적 간단하거든요. convert-hf-to-GGUF Python 스크립트를 사용해서 Gemma3-4B Fine-tuning 모델의 GGUF 버전을 생성하고 Ollama에 업로드하면 돼요.

Ollama 사용자라면 다음 명령어를 통해 이 모델을 로컬 장치에 다운로드할 수 있어요.

ollama pull ed-neo4j/t2c-gemma3-4b-it-q8_0-35k

만약 사용자가 사전 정의된 도구와 일치하지 않는 질문을 하면 어떻게 될까요?

예를 들어 "CUST_00001 고객의 주소를 찾아서 다른 고객과 공유되는지 확인해 줘." 같은 질문이요.

이런 경우, 에이전트는 실패하는 대신 즉시 Cypher `Query`를 생성할 수 있게 되는 거죠!

@function_tool
async def generate_cypher(request: GenerateCypherRequest) -> str:
   """
   로컬로 Fine-tuning된 text2cypher Ollama 모델을 사용해서 자연어에서 Cypher Query를 생성해요.
   """
   USER_INSTRUCTION = """...""" # 자세한 프롬프트 지침

   user_message = USER_INSTRUCTION.format(
       schema=request.database_schema,
       question=request.question
   )
   # text2cypher 모델을 사용해서 Cypher Query를 생성합니다.
   model: str = "ed-neo4j/t2c-gemma3-4b-it-q8_0-35k"
   response = await chat(
       model=model,
       messages=[{"역할": "사용자", "콘텐츠": user_message}]
   )
    response['메시지']['콘텐츠']
코드 언어: PHP (php)

generate_cypher 도구는 Cypher 쿼리 생성 문제를 해결하지만, 에이전트는 이 도구를 언제 사용해야 하는지 어떻게 알 수 있을까요? 답은 에이전트의 지시에 달려있어요.

블로그 시작 부분에서 에이전트에 대한 지침을 다음과 같이 정의했던 것을 기억하실 거예요.

instructions = """당신은 Knowledge Graph에 액세스할 수 있는 KYC 분석가입니다. 도구를 사용하여 고객, 계정 및 의심스러운 패턴에 대한 질문에 답하세요.
   당신은 또한 Neo4j 전문가이며 Neo4j MCP 서버를 사용하여 그래프를 쿼리할 수 있습니다.
   GraphRAG 도구로 답변할 수 없는 KYC 데이터베이스 관련 질문이 있는 경우,
   - Neo4j MCP 서버를 사용하여 그래프의 스키마를 가져옵니다(필요한 경우)
   - generate_cypher 도구를 사용하여 질문 및 스키마에서 Cypher 쿼리를 생성합니다.
   - 질문에 답하기 위해 Neo4j MCP 서버를 사용하여 그래프를 쿼리합니다.
   """
코드 언어: PHP (php)

이번에는 그래프 검색 기반 도구로 응답할 수 없는 임시 쿼리를 처리하기 위한 구체적인 지침을 참고해 주세요.

에이전트가 이 경로로 이동하면 다음 단계를 거치게 돼요.

  1. 에이전트는 새로운 질문을 받아요.
  2. 가장 먼저 neo4j-mcp-server.get-neo4j-schema를 호출해서 데이터베이스의 스키마를 가져오죠.
  3. 그런 다음 스키마와 사용자의 질문을 generate_cypher 도구에 넘겨요. 그러면 Cypher 쿼리가 생성될 거예요.
  4. 마지막으로 생성된 Cypher 쿼리를 가져와서 neo4j-mcp-server.read-neo4j-cypher를 사용해 실행해요.

Cypher 생성 또는 Cypher 실행 중에 오류가 발생하면 에이전트는 Cypher 생성을 다시 시도하고 다시 실행하게 됩니다.

보시다시피 위의 접근 방식이 완벽하진 않아요. 유효하고 정확한 Cypher를 생성하기 위해 Text-to-Cypher 모델에 크게 의존하거든요. 대부분의 경우 잘 작동하지만, 그렇지 않은 경우에는 다음 사항을 고려해야 해요.

  • 이러한 유형의 질문에 대한 명시적인 Cypher 검색 도구를 정의해요.
  • UI/UX에 특정 형태의 최종 사용자 피드백(좋아요/싫어요)을 추가하는 건 어떨까요? 이렇게 하면 에이전트가 어려움을 겪고 있는 질문을 표시하는 데 도움이 될 거예요. 그런 다음 이러한 종류의 질문을 처리하는 가장 좋은 접근 방식을 결정할 수 있죠. (예: Cypher 검색 도구, 더 나은 지침, Text2Cypher 모델 개선, 가드레일 또는 에이전트가 질문에 정중하게 답변을 거부하도록 유도)

도구 5: KYC 에이전트에 메모리 추가

최근 에이전트 메모리(Agent Memory)라는 주제가 많은 관심을 받고 있죠.

에이전트가 본질적으로 관리하는 *단기 기억* (대화 기록을 통해) 외에도, 재무 조사와 같은 복잡한 다중 세션 작업에는 더욱 지속적이고 진화하는 *장기 기억*이 필요해요.

이 장기 기억은 단순히 과거 상호 작용의 기록이 아니에요. 통찰력을 축적하고, 진행 중인 조사를 추적하고, 다양한 세션과 심지어 다양한 에이전트에 걸쳐 컨텍스트를 제공할 수 있는 동적 지식 기반이죠.

create_memory 도구는 조사 요약이 전용 Node로 저장되고 관련 엔터티(고객, 계정, 거래)에 명시적으로 연결되는 명시적 Knowledge Graph 메모리 형태를 구현한답니다.

@function_tool def create_memory(content: str, customer_ids: list[str] = [], account_ids: list[str] = [], transaction_ids: list[str] = []) -> str: """ 메모리 Node를 생성하고 이를 지정된 고객, 계정 및 거래에 연결합니다. """ logger.info(f"도구: CREATE_MEMORY") with driver.session() as session: result = session.run( """ CREATE (m:메모리 {content: $content,created_at: datetime()}) WITH m UNWIND $customer_ids AS cid MATCH(c:고객 {id: cid}) MERGE (m)-[:FOR_CUSTOMER]->(c) WITH m UNWIND $account_ids AS aid MATCH(a:계정 {id:aid}) MERGE (m)-[:FOR_ACCOUNT]->(a) WITH m UNWIND $transaction_ids AS tid MATCH(t:거래 {id: tid}) MERGE (m)-[:FOR_TRANSACTION]->(t) RETURN m.content AS content """, content=content, customer_ids=customer_ids, account_ids=account_ids, transaction_ids=transaction_ids # ... )코드 언어: PHP (php)

에이전트 메모리 구현에 대한 추가 고려 사항은 다음과 같아요.

  • 메모리 아키텍처: 다양한 유형의 메모리(에피소드, 의미, 절차) 및 일반적인 구현(Semantic Search를 위한 Vector Database, 관계형 데이터베이스 또는 구조화된 통찰력을 위한 Knowledge Graph) 탐색
  • 맥락화: Knowledge Graph 구조를 통해 기억의 풍부한 맥락화를 가능하게 하여 단순한 키워드 일치가 아닌 관계 및 패턴을 기반으로 강력한 검색을 가능하게 하는 방법
  • 업데이트 및 검색 전략: 시간이 지남에 따라 메모리가 업데이트되는 방식(예: 추가, 요약, 구체화) 및 에이전트가 메모리를 검색하는 방식(예: 그래프 순회, 의미 유사성 또는 고정 규칙을 통해)
  • 과제: 메모리 일관성 관리, 상충되는 정보 처리, 메모리 검색 시 환각 방지, 메모리가 지나치게 크거나 시끄러워지지 않고 관련성과 최신 상태를 유지하도록 보장하는 복잡성

이는 위의 고려 사항 중 일부를 다루는 많은 프레임워크를 통해 활발하게 개발되고 빠르게 발전하는 영역이에요.

모든 것을 하나로 묶기 - 조사 사례

에이전트가 일반적인 워크플로를 어떻게 처리하는지 살펴볼까요? 직접 실행할 수도 있어요(또는 다음 페이지의 단계별 지침을 자유롭게 따르셔도 돼요. KYC 에이전트 GitHub 레포).

1. “데이터베이스의 Schema를 가져오세요.”

  • 에이전트 작업: 에이전트는 이를 Schema Query로 식별하고 Neo4j MCP 서버의 get-neo4j-schema 도구를 사용해요.

2. “의심스러운 링과 관련된 감시 대상 고객 5명을 보여주세요.

  • 상담원 조치: 이는 맞춤 도구의 목적과 직접적으로 일치하죠. 상담원이 find_customer_rings with customer_in_watchlist=True를 호출해요.

3. “각 고객의 주소를 찾아 다른 고객과 공유하고 있는지 알아보세요.

  • 상담원 조치: GraphRAG 도구로는 답변할 수 없는 질문이에요. 상담원은 다음 지침을 따라야 해요.
    • 이미 위의 첫 번째 상호 작용에서 얻은 Schema가 있죠.
    • 그것은 generate_cypher를 질문과 Schema로 호출해요. 이 도구는 조사관의 질문에 답하기 위해 Cypher Query를 반환하죠.
    • Neo4j MCP Cypher Server를 사용하여 이 Cypher Query를 실행하는 read-neo4j-cypher 도구를 사용해요.

4. “주소가 공유된 고객의 경우 자세한 내용을 알려주실 수 있나요?”

  • 에이전트 작업: 에이전트는 get_customer_and_accounts 도구가 완벽하게 적합하며 고객의 ID로 호출된다고 판단해요.

5. “이번 조사에 대해 300단어로 요약해 보세요. 추억으로 저장해 보세요. 이 고객에게 속한 모든 계좌 및 거래에 연결하세요.

  • 상담사 작업: 상담사는 먼저 내부 LLM 기능을 사용해서 요약을 생성해요. 그런 다음 create_memory 도구를 사용해서 대화 중에 발생한 모든 고객, 계정 및 거래 ID의 요약 텍스트와 목록을 전달하죠.

주요 시사점

여기까지 오셨다면 KYC GraphRAG 에이전트의 기본 구현에 익숙해지는 여정이 즐거우셨기를 바라요. OpenAI Agent SDK, MCP, Neo4j, Ollama 및 Gemma3-4B Fine-tuning된 Text-to-Cypher 모델 등 멋진 기술이 많이 사용되었죠!

다음 사항에 대해 감사의 마음을 가지셨기를 바라요.

  • GraphRAG, 더 구체적으로 말하면 연결된 데이터 문제에 필수적인 그래프 기반 데이터 검색이에요. 이를 통해 에이전트는 표준 RAG로는 답변할 수 없는 밀접하게 연결된 데이터에 대한 질문에 답변할 수 있어요.
  • 균형 잡힌 툴킷의 중요성은 강력해요. MCP 서버 도구를 자신만의 최적화된 도구와 결합해 보세요.
  • MCP 서버는 게임 체인저에요. 이를 통해 점점 늘어나는 MCP 서버 세트에 에이전트를 연결할 수 있어요.
    • 실험해 보세요 더 많은 MCP 서버 그래서 여러분은 가능성에 대해 더 잘 이해할 수 있을 거예요.
  • 에이전트는 통제된 방식으로 데이터 저장소에 다시 쓸 수 있어야 해요.
    • 이 예에서는 분석가가 발견한 내용을 유지하는 방법(예: Knowledge Graph에 메모리 Node 추가)과 그 과정에서 에이전트가 전체 조사 팀의 기본 Knowledge Graph를 개선하는 선순환을 만드는 방법을 확인했어요.
    • 에이전트는 Knowledge Graph에 정보를 추가하며 기존 정보를 업데이트하거나 삭제하지 않아요.

여기서 논의되는 패턴과 도구는 KYC에만 국한되지 않아요. 이는 공급망 분석, 디지털 트윈 관리, 약물 발견 및 데이터 포인트 간의 관계가 데이터 자체만큼 중요한 기타 모든 영역에 적용될 수 있어요.

그래프 인식 AI 에이전트의 시대가 왔어요.

다음은 무엇일까요?

MCP, Neo4j 및 Text-to-Cypher 모델을 사용하여 OpenAI Agents SDK 위에 간단한 AI 에이전트를 구축했어요.— 모두 하나의 기기에서 실행돼요.

이 초기 에이전트는 강력한 기반을 제공하지만 프로덕션 수준 시스템으로 전환하려면 다음과 같은 몇 가지 추가 요구 사항을 해결해야 해요.

  • 에이전트 UI/UX: 사용자가 에이전트와 상호 작용하는 핵심 부분이에요. 이는 궁극적으로 에이전트 채택 및 성공의 주요 동인이 될 거예요.
  • 장기 실행 작업 및 다중 에이전트 시스템: 일부 작업은 가치가 있지만 실행하는 데 상당한 시간이 걸려요. 이러한 경우 상담원은 작업 부하의 일부를 다른 상담원에게 오프로드할 수 있어야 해요.
    • OpenAI는 하위 에이전트로의 전달을 일부 지원하지만 장기 실행 에이전트에는 적합하지 않을 수 있어요.
  • 에이전트 가드레일: OpenAI Agents SDK는 가드레일에 대한 일부 지원을 제공해요.
  • 에이전트 호스팅: 에이전트를 사용자에게 노출해요.
  • 에이전트에 대한 통신 보안: 에이전트에 대한 최종 사용자 인증 및 승인.
  • 데이터베이스 액세스 제어: KYC Knowledge Graph에 저장된 데이터에 대한 액세스 제어를 관리해요.
  • 에이전트 관찰 가능성
  • 상담사 평가: 상담사 지침 변경이나 도구 추가/삭제는 어떤 영향을 미칠까요?

그동안 이 내용이 여러분이 계속 배우고 실험할 수 있도록 영감을 주었기를 바라요.

자원

  • 그래프 데이터베이스로 사기 탐지 가속화
  • OpenAI 에이전트 SDK
    • OpenAI 에이전트 및 MCP
  • 개발자가 MCP에 대해 알아야 할 모든 것
  • MCP 소개
  • Neo4j용 MCP 통합
  • Neo4j GraphRAG 개발자 가이드
  • 무료 Neo4j GraphAcademy 강좌
  • Google Cloud 및 Neo4j: Knowledge Graph, 에이전트, MCP 및 Natural Language 인터페이스의 교차점에서 팀 구성
  • Agent
  • GraphRAG
  • kyc

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

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

반응형
반응형

GenAI 개발의 중요한 과제를 해결하는 Google Cloud 및 Vertex AI와의 새로운 기본 통합을 발표하게 되어 정말 기뻐요! 이 통합은 상황에 맞게 풍부한 외부 데이터에 액세스하여 정확하고 설명 가능한 결과를 제공하는 데 도움을 주죠. 특히 통합을 통해 GraphRAG 구현이 간소화되는데, GraphRAG는 이러한 문제를 해결하기 위한 반응형 실시간 방법이에요.

GraphRAG는 Retrieval-Augmented Generation(RAG)과 Knowledge Graph라는 두 가지 강력한 기술을 결합한 방식이에요. RAG를 사용하면 GenAI 애플리케이션이 외부 데이터세트에 액세스하고 쿼리할 수 있고, Knowledge Graph를 사용하면 엔터티로 상황별 정보를 풍부하게 하고 엔터티 간의 복잡한 관계를 캡처해서 데이터를 더욱 스마트하게 만들 수 있죠. 이러한 풍부한 컨텍스트를 통해 LLM은 질문에 대해 추론하고, 추론하고, 정확하게 답변하고 작업을 실행해서 답변과 조치를 사실 정보에 고정시킬 수 있어요.

GenAI 개발에서 Knowledge Graph의 중요성은 아무리 강조해도 지나치지 않아요. Gartner는 Knowledge Graph를 GenAI 개발에 필수적인 요소로 보고 있고, 데이터 리더들에게 "내결함성 AI 애플리케이션을 구축하기 위해 Knowledge Graph의 견고성과 함께 LLM의 힘을 활용"하라고 권고하고 있어요.

현재 사용 가능한 GraphRAG 통합을 통해 조직은 안전하고 규정을 준수하면서 상황에 맞는 풍부한 외부 데이터를 실시간으로 통합할 수 있는 GenAI 애플리케이션을 신속하게 구축할 수 있어요. 이 기능은 LLM이 대규모 데이터 세트 내에서 복잡한 관계와 패턴을 발견하고 사용할 수 있도록 하는 동시에 환각을 극적으로 줄여주죠. 따라서 GenAI 앱은 기업 사용 사례에 필요한 정확성, 관련성 및 설명 가능성을 제공할 수 있어요.

통합을 통해 개발자는 GraphRAG를 원활하게 구현할 수 있답니다.

      1. 정확하고 설명 가능한 결과를 위해 Knowledge Graph를 빠르게 생성하세요. 개발자는 Gemini 모델, Google Cloud VertexAI 플랫폼, LangChain 및 Neo4j를 사용하여 PDF, 웹페이지, 문서와 같은 구조화되지 않은 데이터에서 직접 또는 Google Cloud Storage 버킷에서 로드하여 Knowledge Graph를 쉽게 만들 수 있어요. 단순화된 프로세스는 llm-graph-transformer를 사용하는데, Neo4j가 LangChain에 기여했죠. LLM에 의도된 그래프 스키마를 제공하고 Gemini의 함수 호출 기능을 사용하여 구조화된 방식으로 엔터티와 해당 관계를 추출합니다. 그런 다음 이러한 엔터티와 관계는 GenAI 또는 기타 애플리케이션에서 사용하기 위해 Neo4j Knowledge Graph에 추가됩니다. GraphRAG를 사용하면 LLM은 엔터티, 해당 속성 및 엔터티 간의 관계를 중심으로 모델링된 데이터에 액세스하여 GenAI 정확성과 설명 가능성을 향상시킬 수 있어요.
      1. 몇 초 만에 실시간 데이터를 수집, 처리, 분석합니다. 개발자는 Dataflow에서 Flex 템플릿을 사용할 수 있어요. Google BigQuery, Google Cloud Storage 및 Neo4j에서 데이터를 수집, 처리 및 분석하는 반복 가능하고 안전한 데이터 파이프라인을 생성하여 Knowledge Graph에 실시간 정보를 제공하고 GenAI 애플리케이션이 적시에 관련 통찰력을 제공할 수 있도록 돕죠.
      1. Gemini for Workspace 및 Reasoning Engine을 사용하여 그래프 기반 GenAI 앱을 구축하고 배포하세요. GenAI 애플리케이션을 프로덕션에 배포하는 것은 어려웠지만, Reasoning Engine Vertex AI 플랫폼에서 개발자는 이제 GenAI 앱과 API를 쉽게 배포, 모니터링, 확장할 수 있는 도구를 갖게 되었어요. Google Cloud Run 덕분이죠. 벡터 검색, GraphRAG 및 대화형 메모리와 같은 Neo4j의 GenAI 기능은 Google Cloud의 LangChain 및 Neo4j AuraDB를 통해 원활하게 통합됩니다. Gemini 모델은 Neo4j 관련 보조 콘텐츠에 대한 교육을 받아 Neo4j 도구 및 API용 코드 조각을 자동으로 생성하여 개발을 간소화합니다. 또한 자연어를 Neo4j의 Cypher 쿼리 언어로 변환하여 Neo4j로 애플리케이션을 더 쉽게 구축할 수 있어요. 단 몇 줄의 Python 코드와 Google Vertex AI Python SDK를 사용하면 GenAI 및 그래프 기반 API를 Reasoning Engine에 배포하여 개발자와 운영팀 모두에게 강력한 RAG 기능을 제공할 수 있습니다. 이를 통해 조직은 그래프 기반 GenAI 애플리케이션을 쉽고 자신있게 프로덕션에 도입할 수 있죠.

새로운 통합과 이를 통해 조직이 GenAI의 엄청난 잠재력을 실현하는 데 어떻게 도움이 되는지 좀 더 자세히 살펴볼까요?

Gemini 모델과 LangChain을 사용하여 비정형 데이터에서 Knowledge Graph 구축

Gemini의 고급 언어 기능과 새로운 함수 호출 기능을 통해 구조화되지 않은 텍스트에서 엔터티, 유형 및 관계를 식별하고 구조화된 방식으로 추출할 수 있어요. Neo4j가 LangChain에 기여한 llm-graph-transformer를 사용하면 개발자는 PDF, 웹 페이지, Google Docs 등 모든 LangChain 문서 세트를 Knowledge Graph로 전환할 수 있습니다.

지침이 포함된 특정 프롬프트와 구조화되지 않은 텍스트가 포함된 선택적 그래프 스키마를 제공함으로써 개발자는 LLM이 정보를 추출하고 Node 및 Relationship에 대해 사전 정의된 개체를 통해 구조화된 출력을 채우도록 안내할 수 있습니다. 함수 호출에 대한 Gemini의 초기 지원을 통해 개발자는 추출 파이프라인에서 모델을 사용할 수 있죠.

Dataflow Flex 템플릿으로 실시간 GraphRAG 애플리케이션 강화

새로운 통합을 통해 개발자는 실시간 데이터를 사용하여 GraphRAG 애플리케이션을 구축할 수 있어요. Dataflow Flex 템플릿을 사용하여 BigQuery 및 Google Cloud Storage에서 Google Cloud의 Neo4j Graph Database로 실시간 데이터 이동을 위한 안전하고 효율적인 파이프라인을 설정할 수 있습니다. GraphRAG 애플리케이션을 지원하는 Knowledge Graph는 지속적으로 업데이트되어 보다 정확하고 시의적절하며 상황에 맞는 응답을 생성할 수 있답니다.

Architecture diagram showing Dataflow from Google Cloud to Neo4j

Google Cloud에서 Neo4j로의 Dataflow를 보여주는 아키텍처 다이어그램

아래에서는 Dataflow Flex 템플릿을 사용해서 Google Cloud Storage 버킷에서 작업을 실행하고 데이터를 Neo4j Knowledge Graph로 변환하는 방법을 보여드릴게요. Google Cloud에서 데이터가 업데이트되면 실시간으로 변경 사항을 확인하고 Knowledge Graph를 업데이트하는 작업을 만들 수도 있어요. 이제 Neo4j 안의 그래프를 RAG 아키텍처와 함께 사용해서 LLM 정확도와 설명 가능성을 높일 수 있다는 점! 정말 흥미롭죠?

A sample job run in Dataflow to get data from Google Cloud to Neo4j to create a knowledge graph

Knowledge Graph를 만들기 위해 Google Cloud에서 Neo4j로 데이터를 가져오기 위해 Dataflow에서 실행되는 샘플 작업

조직은 Dataflow Flex 템플릿을 Neo4j의 Graph Database와 결합해서 빠르게 변화하는 데이터 환경에 적응하는 정교한 GraphRAG 애플리케이션을 구축할 수 있어요. 실시간 데이터를 통해 이러한 애플리케이션은 더욱 정확하고 시의적절하며 상황에 맞는 풍부한 통찰력을 제공해서 도메인 및 사용 사례 전반에 걸쳐 의사 결정 및 사용자 경험을 향상시키죠.

Google Workspace 및 Neo4j용 Gemini를 통해 GenAI 가속화

저희는 Google Workspace용 Gemini의 그래프 애플리케이션 개발 기능을 개선하기 위해 Google과 긴밀히 협력해 왔어요. Neo4j는 Google에 광범위한 교육 데이터를 제공해서 Gemini가 Cypher 쿼리 언어를 이해하고 그래프 애플리케이션 개발자에게 더욱 포괄적인 지침을 제공할 수 있도록 도왔답니다. (교육 데이터에는 Neo4j의 문서, 온라인 강좌, Knowledge Graph뿐만 아니라 text2cypher 개발 노력과 크라우드소싱, LLM 생성 질문-답변 쌍의 데이터도 포함되었어요.)

Google Workspace용 Gemini 개발자 어시스턴트는 이제 개발자가 Neo4j에서 Knowledge Graph를 생성하는 데 도움을 줄 수 있어요. 자연어를 Cypher로 번역해주는 거죠. Knowledge Graph를 사용해서 벡터 검색 기능을 위해 애플리케이션을 통합하고 GraphRAG로 강화해서 LLM을 기반으로 더욱 설명 가능하고 정확한 결과를 얻을 수 있어요. Neo4j 내에서 Knowledge Graph가 생성되면 이를 사용해서 그래프 데이터를 탐색해서 숨겨진 패턴과 통찰력을 찾아낼 수 있답니다.

A code snippet within a Google Workspace, showcasing the generation of Cypher query to load data into Neo4j

Neo4j에 데이터를 로드하기 위한 Cypher 쿼리 생성을 보여주는 Google Workspace 내의 코드 스니펫

Google Workspace용 Gemini는 Neo4j를 LangChain, LlamaIndex, Haystack과 같은 널리 사용되는 오케스트레이션 프레임워크와 통합하는 방법에 대한 교육도 받았대요. 그래서 개발자에게 프레임워크별 지침을 제공해서 개발 프로세스를 훨씬 간소화할 수 있죠.

Google Workspace용 Gemini는 Google Workspace의 최종 사용자, Google Cloud Platform(GCP) 콘솔의 개발자는 물론 Visual Studio Code 및 JetBrains와 같은 널리 사용되는 개발 환경에서도 사용할 수 있어요. 덕분에 개발자는 차세대 GenAI 지원 애플리케이션을 구축할 때 AI 지원 코딩 기능을 마음껏 활용할 수 있답니다.

Google의 추론 엔진 런타임을 사용하여 Graph 기반 GenAI 애플리케이션 배포

많은 개발자가 프로덕션 환경에 GenAI 애플리케이션을 처음 배포할 때 어려움을 겪는데요. Google의 추론 엔진 런타임은 Vertex AI 및 Gemini 모델을 사용해서 GenAI 애플리케이션을 안전하게 배포, 확장, 모니터링, 운영하는 과정을 단순화해서 이런 문제를 해결해 준답니다. Google의 Reasoning Engine Runtime은 GenAI 애플리케이션과 Knowledge Graph를 통합하기 위한 프레임워크를 제공하는 새로운 제품이에요. Vertex ML의 기능을 훨씬 뛰어넘죠. 이는 개발자가 엔터티 간의 복잡한 관계를 모델링하거나 고급 추론 작업을 수행해야 하는 필요성과 같은 애플리케이션의 특정 요구 사항을 기반으로 Knowledge Graph를 사용할 시기를 결정하는 데 도움을 줘요.

광범위한 LangChain 통합과 결합된 Google Cloud와의 새로운 통합을 통해 사용자는 Neo4j Knowledge Graph를 GenAI 스택에 정말 쉽게 통합할 수 있어요. 개발자는 LangChain을 사용해서 추론 엔진 런타임 내에서 GraphRAG를 포함한 직접 또는 고급 RAG 아키텍처를 실행할 수 있죠.

Neo4j의 Knowledge Graph 기능을 Google의 Reasoning Engine Runtime과 결합하는 건 맥락에 맞게 발전된 GenAI 애플리케이션을 구축하는 강력한 접근 방식이에요. 더 정확하고 설명 가능한 GenAI 결과를 제공하는 동시에 프로덕션의 복잡성을 줄여주죠.

GraphRAG: GenAI의 잠재력 잠금 해제

GenAI가 발전함에 따라 GraphRAG가 LLM의 한계를 극복하기 위한 강력한 도구라는 게 점점 더 분명해지고 있어요. Knowledge Graph와 Retrieval-Augmented Generation을 결합하면 정확성, 설명 가능성, 투명성이라는 중요한 문제를 해결하고 GenAI의 잠재력을 최대한 활용할 수 있답니다.

Neo4j와 Google Cloud의 새로운 통합으로 GraphRAG는 그 어느 때보다 더 쉽게 접근하고 사용할 수 있게 되었어요. 이제 개발자는 환각이나 투명성 부족 문제를 해결하느라 애쓰는 대신 신뢰할 수 있고 상황을 인식하는 차세대 GenAI 애플리케이션을 만드는 데 집중할 수 있죠.

Google Cloud에서 Neo4j GraphRAG를 시작하려면 다음을 살펴보세요.GenAI 리소스Neo4j AuraDB에서 실행 가능Google 클라우드 마켓플레이스오늘.
  • 쌍둥이자리
  • Google Workspace
  • GraphRAG
  • Knowledge Graph LLM
  • Neo4j AuraDB
  • Neo4j LLM
  • RAG
  • 구조화되지 않은 데이터
  • Vertex AI

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

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

반응형
반응형

여러분, 혹시 RAG(Retrieval Augmented Generation) 시스템을 탐험하다가 GraphRAG라는 용어를 우연히 발견한 적 있으신가요? 그렇다면 혼자가 아니에요! 이 용어가 요즘 엄청 핫하지만, 정확히 뭘 의미하는지 파악하기 어려울 수 있죠. 어떤 경우에는 특정 검색 방법일 수도 있고, 또 다른 경우에는 Microsoft처럼 전체 소프트웨어 제품군을 의미하기도 하거든요. 예를 들어 GraphRAG는 "데이터 파이프라인 및 변환 제품군"이라고 하네요. 이렇게 다양한 용도로 사용되다 보니, RAG에 열광하는 사람들조차도 헷갈릴 수밖에 없을 거예요.

그렇다면 GraphRAG는 정확히 뭘까요? 저희 생각에는, 검색을 위해 그래프 구조를 활용하는 RAG 패턴들의 모음이라고 할 수 있어요. 각 패턴이 효과적으로 작동하려면 고유한 데이터 구조 또는 그래프 패턴이 필요하죠. 흥미롭지 않나요? 이번 포스팅에서는 GraphRAG 패턴의 세부 사항을 자세히 살펴보고, 각 패턴의 속성과 전략을 분석해 볼 거예요.

RAG에 대한 소개를 찾고 있다면, "검색 증강 생성(RAG)이란 무엇입니까?"를 확인해 보세요. 아니면 NODES 컨퍼런스 녹화본을 시청하는 것도 좋은 방법이에요. NODES 컨퍼런스는 24시간 동안 진행되는 무료 온라인 이벤트인데, 전 세계 연사들이 Knowledge Graph와 AI에 대해 발표한답니다.

여기 제시된 각 패턴은 GraphRAG 패턴 카탈로그에 직접 연결되어 있어요. 이 카탈로그는 최신 패턴 발전에 대한 정보를 얻기 위한 오픈 소스 이니셔티브예요. 아직 패턴 수집을 시작한 지 얼마 안 돼서 빠진 부분도 많지만, 여러분이 GraphRAG 패턴에 대한 포괄적인 카탈로그를 구축하는 데 도움을 주시면 정말 감사할 것 같아요! GraphRAG Discord 채널에 참여해서 함께 만들어나가요!

더 명확하게 보여드리기 위해, 이번 포스팅에서 설명할 패턴 목록을 준비했어요:

기본 GraphRAG 패턴:

  • Basic Retriever (GraphRAG 패턴 카탈로그에서 확인)
  • Parent-Child Retriever (에서 확인)
  • Hypothetical Question Retriever (에서 확인)

중급 GraphRAG 패턴:

  • Cypher Templates (에서 확인)
  • 동적 Cypher 생성 ()
  • Text2Cypher ()

고급 GraphRAG 패턴:

  • 그래프 강화 벡터 검색 ()
  • 글로벌 커뮤니티 요약 검색기 ()

몇 가지 배경 정보부터 시작해 볼게요.

주제별 분류

RAG 패러다임에는 순진한 RAG, 고급 RAG, 모듈형 RAG(Gao et al.)가 있어요:

RAG 패러다임(Gao et al.)

고급 RAG 패러다임에서는 검색 전 및 검색 후 단계가 순진한 RAG 패러다임에 추가되죠. 모듈식 RAG 시스템에는 사용자 쿼리의 조정 및 라우팅이 필요한 더 복잡한 패턴이 포함되어 있고요.

고급 RAG 시스템의 단계:

  1. — 쿼리 재작성, 쿼리 엔티티 추출, 쿼리 확장 등
  2. : 관련 맥락
  3. 검색 후: 순위 재지정, 정리 등

여기서는 검색 단계에 중점을 두고 가장 자주 참조되는 GraphRAG 검색 패턴과 필요한 그래프 패턴에 대한 조사를 해보려고 해요. 여기에 있는 패턴은 완전한 목록은 아니라는 점 참고해주세요!

왜 그래프인가?

검색 패턴을 자세히 살펴보면 가장 발전된 기술이 데이터 내의 연결에 어떻게 의존하는지 알 수 있을 거예요. 특정 저자 또는 특정 주제에 대한 기사 검색과 같은 메타데이터 필터링이든, 텍스트 청크의 상위로 다시 탐색하여 맥락이 풍부한 답변을 위해 LLM에 폭을 제공하는 부모-자식 검색기이든 이러한 방법은검색할 데이터 간의 관계를 활용하죠.

일반적으로 이러한 구현은 클라이언트 측 데이터 구조와 광범위한 Python 코드에 크게 의존해요. 하지만 Graph Database에서는 실제 관계를 설정하고 간단한 패턴으로 쿼리하는 것이 더 효율적이에요.

LangChain을 사용하여 여기서 논의된 검색기를 구현하려는 경우 Neo4j 벡터 구현을 참고하면 돼요. 여기서는 Neo4j 기반 검색기에 대한 Python 프로젝트 설정을 다루지는 않을 거예요. 다른 곳에서 잘 문서화되어 있거든요 (예: Neo4j GraphAcademy: Python을 사용하여 Neo4j 지원 챗봇 구축).

대신 우리는 흥미로운 부분에 초점을 맞출 거예요: 검색_쿼리 인수를 통해 논의할 GraphRAG 패턴을 구현하는 거죠. 각 패턴의 세부 정보에는 해당 쿼리가 포함될 거예요.

쿼리를 작성할 때 지정된 Node Label 및 속성에 대해 Vector Embedding 검색을 수행하는 보이지 않는 "첫 번째 부분"이 있다는 점을 기억하세요 (from_existing_graph 메서드의 node_label 및 embedding_node_property 매개 변수 참조). Neo4j 벡터 문서를 참고하면 좋아요. 이 첫 번째 부분은 발견된 Node와 해당 유사성 점수를 Node 및 점수로 반환해요. 그런 다음 이를 검색 쿼리에서 사용하여 추가 순회를 실행할 수 있죠. 질문 삽입을 위해 사용자 정의 매개변수와 $embedding 매개변수를 사용할 수도 있고요. 다음은 이것이 어떻게 보이는지에 대한 예시예요 (LangChain Neo4j 벡터 문서):

retrieval_query = """
RETURN "Name:" + node.name AS text, score, {foo:"bar"} AS metadata
"""
retrieval_example = Neo4jVector.from_existing_index(
    OpenAIEmbeddings(),
    url=url,
    username=username,
    password=password,
    index_name="person_index",
    retrieval_query=retrieval_query,
)
retrieval_example.similarity_search("Foo", k=1)

위의 예에서 Vector Embedding 유사성 검색은 사용자 입력 "Foo"를 사용하여 기존 인덱스 person_index에서 실행되며 이름, 점수 및 일부 메타데이터가 가장 적합한 Node(k=1)에 대해 반환돼요.

거의 모든 패턴의 그래프 패턴에는 청크 Node가 표시될 거예요. 이는 대부분의 GraphRAG 패턴의 기초이며 최소한 텍스트와 임베딩이라는 두 가지 속성을 가지고 있죠. 여기서 텍스트에는 사람이 읽을 수 있는 청크의 텍스트 문자열이 포함되고 임베딩에는 텍스트의 계산된 임베딩이 포함돼요.

최소 속성을 가진 청크 Node. 작성자의 이미지입니다.

이제 다양한 GraphRAG 패턴을 살펴볼게요.

기본 GraphRAG 패턴 — 어휘 그래프의 검색 패턴

아마 이전에 이런 RAG 패턴을 접해본 적 있을 거예요. 구조화되지 않은 데이터에 대한 RAG를 살펴볼 때 흔히 보이는 패턴이죠. 종종 Vector Database에 나타나는 이러한 패턴은 실제로 데이터 내의 관계에 의존한답니다.

기본 리트리버

이름: 기본 리트리버
다음으로도 알려져 있습니다. 나이브 리트리버, 베이스라인 RAG, 일반 RAG
문맥: 임베딩을 생성할 때 큰 문서를 작은 청크로 나누는 게 유용해요. 임베딩은 텍스트 내용의 의미를 포착하는 텍스트의 의미론적 표현이거든요. 주어진 텍스트가 길고 다양한 주제를 너무 많이 포함하면 임베딩의 정보 가치가 떨어지죠.
필수 전처리: 문서를 청크로 분할하고 임베딩 모델을 사용해서 청크의 텍스트 콘텐츠를 임베딩해요.
필수 그래프 패턴: 기본 어휘 그래프

기본 어휘 그래프. 작성자의 이미지입니다.

설명: 사용자 질문은 청크 임베딩을 생성하기 위해 이전에 사용된 것과 동일한 임베더를 사용해서 임베딩돼요. Vector Similarity Search는 청크 임베딩에서 실행되어 검색됩니다. k (이전에 개발자/사용자가 구성한 번호) 가장 유사한 청크를요.
용법: 이 패턴은 사용자가 하나 이상의 (그러나 너무 많지는 않은) 청크에 존재하는 주제에 대한 특정 정보를 요청하는 경우 유용해요. 질문에는 전체 데이터 세트에 대한 복잡한 집계나 지식이 필요하지 않죠. 패턴에는 Vector Similarity Search만 포함되므로 이해하고 구현하고 시작하기가 쉬워요.
검색 Query: Neo4j Vector Searcher는 기본적으로 유사한 청크를 검색하므로 추가 Query가 필요하지 않아요.
자원: RAG를 개선하기 위한 고급 리트리버 기술, Neo4j를 사용하여 고급 RAG 전략 구현
GraphRAG 패턴 카탈로그: 기본 리트리버
기존 구현: LangChain 리트리버: 벡터 스토어 기반 리트리버, LangChain: Neo4jVector
구현 예: LangChain 템플릿: Neo4j Advanced RAG

부모-자식 리트리버

이름: 부모-자식 리트리버
다음으로도 알려져 있습니다. 상위 문서 검색자
문맥: 앞서 언급했듯이 임베딩은 텍스트의 의미론적 의미를 나타내요. 텍스트의 범위가 좁을수록 여러 주제에서 발생하는 노이즈가 적기 때문에 더 의미 있는 Vector 표현이 생성되죠. 하지만 LLM이 답변 생성을 위해 작은 정보만 수신하는 경우 정보에 컨텍스트가 누락될 수 있어요. 발견된 정보가 포함된 더 넓은 주변 텍스트를 검색하면 문제가 해결된답니다.
필수 전처리: 문서를 (더 큰) 청크로 분할합니다 () 그리고 이 청크를 더 작은 청크로 분할합니다 (). 임베딩 모델을 사용하여 하위 청크의 텍스트 콘텐츠를 임베딩해요. 상위 청크는 유사성 검색이 아닌 답변 생성에만 사용되므로 내장할 필요가 없어요.
필수 그래프 패턴: 상위-하위 어휘 그래프

부모-자식 어휘 그래프. 작성자의 이미지입니다.

설명: 사용자 질문은 청크의 Vector Embedding을 생성하는 데 사용된 것과 동일한 임베더를 사용해서 임베딩돼요. 벡터 유사성 검색은 하위 청크 임베딩에 대해 실행되어서 k (개발자/사용자가 이전에 구성한 숫자)개의 가장 유사한 청크를 찾죠. 발견된 하위 청크의 상위 청크를 검색하는 방식이에요.
용법: 이 패턴은 기본 리트리버의 유용한 발전된 형태라고 할 수 있어요. 여러 주제가 하나의 청크로 다뤄져서 임베딩 품질에 부정적인 영향을 미칠 때 특히 유용하죠. 작은 청크는 더 의미 있는 벡터 표현을 가지게 되어서 더 나은 유사성 검색 결과를 얻을 수 있게 돼요. 제한된 추가 노력으로 더 나은 결과를 얻을 수 있다니, 정말 흥미롭죠?
검색 쿼리:

retrieval_query = """
MATCH (node)<-[:HAS_CHILD]-(parent)
WITH parent, max(score) AS score // deduplicate parents
RETURN parent.text AS text, score, {} AS metadata
"""

자원: RAG를 개선하기 위한 고급 리트리버 기술, Neo4j를 사용하여 고급 RAG 전략 구현
GraphRAG 패턴 카탈로그: graphrag.com: 부모-자식 리트리버
기존 구현: LangChain 리트리버: 상위 문서 리트리버
구현 예: LangChain 템플릿: Neo4j Advanced RAG

유사한 패턴을 다음에서도 구현할 수 있어요: 형제 구조를 갖는 어휘 그래프 또는 계층 구조의 어휘 그래프. 여기서 추가 컨텍스트는 상위 문서만 검색하는 것이 아니라 형제 문서 또는 이전에 설정된 구조 깊이를 검색하는 것에서 비롯되죠. 예를 들어, 형제 구조를 가진 어휘 그래프는 현재 Neo4j의 LLM Knowledge Graph 빌더에서 구현되고 있어요.

계층 구조를 가진 어휘 그래프에는 두 가지 종류의 검색기가 가능하다는 점에 유의해야 해요.

  • : 리프 Node에서 검색을 실행하고 트리의 더 높은 위치에 있는 다른 청크를 검색해요 (참조: Going Meta  24회 — 그래프, 의미론, 지식에 관한 시리즈)
  • : 최상위 Node를 사용해서 검색에 고려할 하위 트리를 결정해요. 유사성 검색을 위한 Node 집합이 합리적으로 좁아질 때까지 이 방법론을 반복하는 거죠 (참조: RAG 전략 — 계층적 Index 검색).
형제 구조를 갖는 어휘 그래프. 작성자의 이미지입니다.
계층 구조의 어휘 그래프. 작성자의 이미지입니다.

가상 질문 검색기

이름: 가상 질문 검색기
문맥: 질문의 임베딩과 적절한 답변 또는 텍스트 소스의 텍스트 임베딩 간의 벡터 유사성은 매우 낮을 수 있어요. 사용 가능한 질문-청크 쌍이 있는 경우 질문 임베딩에 대해 벡터 유사성 검색을 실행할 수 있고, 이는 아마도 원본 텍스트 청크에 대한 벡터 유사성 검색보다 훨씬 더 나은 결과를 제공할 거예요.
필수 전처리: LLM을 사용해서 청크 내에서 답변이 가능한 가상 질문을 생성하세요. 임베딩 모델을 사용해서 질문을 임베드하고요. 질문과 답변이 포함된 덩어리 사이의 Relationship을 기록해야 해요.
필수 그래프 패턴: 가상 질문이 포함된 어휘 그래프

가상 질문이 포함된 어휘 그래프. 작성자의 이미지입니다.

설명: 사용자 질문은 질문 임베딩을 생성하는 데 사용된 것과 동일한 임베더를 사용하여 임베드돼요. 벡터 유사성 검색은 이전에 생성된 질문에 대해 실행되죠. k(개발자/사용자가 미리 구성한 번호) 가장 유사한 질문을 찾아 관련 청크를 검색하는 거예요.
용법: 이 패턴은 위의 패턴에 추가된 패턴이에요. 벡터 유사성 검색에서 더 나은 결과를 얻을 수 있죠. 하지만 질문 생성을 위한 LLM 요청에는 더 많은 사전 처리 노력과 비용이 필요해요.
검색 쿼리:

retrieval_query = """
MATCH (node)<-[:HAS_QUESTION]-(chunk)
WITH chunk, max(score) AS score // deduplicate chunks
RETURN chunk.text AS text, score, {} AS metadata
"""

가설 질문 검색기는 HyDE(가설 문서 삽입) 검색기와 매우 유사해요 (참조: LangChain을 사용한 RAG: 5부 — 가설 문서 임베딩). 그 뒤에 있는 주요 아이디어는 사용자 질문과 사용 가능한 텍스트 간의 유사성을 높이는 것이죠. 가설 질문 검색기에서는 사용자 질문과 일치하는 가설 질문을 생성해요. HyDE 검색기에서 LLM은 기본 데이터베이스를 사용하지 않고 사용자 질문에 대한 가상 답변을 생성한 다음 가상 답변을 데이터베이스의 실제 청크와 일치시켜 가장 적합한 것을 찾아요. HyDE 검색기는 검색 단계가 아닌 RAG 전처리 단계에 있고 특정 유형의 기본 그래프 패턴이 필요하지 않기 때문에 더 자세히 살펴보지 않을 거예요.

중급 GraphRAG 패턴 — 도메인 그래프의 검색 패턴

도메인 그래프에는 비즈니스 도메인 지식이 포함되어 있어요. 여기에는 엔터티와 엔터티 간의 관계가 포함되죠. 자주 사용되는 예제 도메인 그래프는 다음과 같아요: 무비 그래프 또는 북풍 그래프.

도메인 그래프는 기본 도메인에 따라 다르게 보이기 때문에 모양에 대한 청사진을 제공하는 것은 불가능해요. 여기에는 스키마를 준수하는 구조화된 데이터가 포함되어 있다는 점에 유의하세요.

Natural Language 쿼리가 (결정적) 구조화된 데이터 검색으로 이어지는 질문-답변 애플리케이션 내에서 도메인 그래프에 포함된 정보를 제공하는 것은 여러 가지 방법으로 실행될 수 있어요.

Cypher 템플릿

이름: Cypher 템플릿
문맥: 구조화된 데이터를 검색하려면 사용자 질문을 데이터베이스에서 실행할 수 있는 쿼리로 변환해야 해요. 기본적인 접근 방식은 사용자 질문을 매핑할 수 있도록 도메인 전문가가 작성한 사전 정의된 쿼리를 갖는 것이죠.
필수 전처리: 여러 도메인별 Cypher 쿼리(매개변수 포함)와 해당 쿼리에 대한 설명이 LLM에 제공돼요.
필수 그래프 패턴: 도메인 그래프
설명: 사용자 질문이 주어지면 LLM은 사용할 Cypher 템플릿을 결정해요. LLM은 사용자 질문에서 매개변수를 추출하여 템플릿에 연결할 수 있죠. 쿼리는 데이터베이스에 대해 실행되고 결과는 답변을 생성하기 위해 LLM에 다시 제공돼요.
용법: 사용자가 도메인 그래프에 제기할 질문 유형이 이전에 알려진 경우 템플릿을 만들 수 있어요. 이 접근 방식의 단점은 주어진 템플릿 쿼리에 대한 제한이에요. 사용자가 템플릿에 존재하지 않는 쿼리를 초래하는 질문을 하면 어떻게 될까요?
자원: 의미 계층을 통해 언어 모델과 그래프 데이터베이스 간의 상호 작용 향상
GraphRAG 패턴 카탈로그:
graphrag.com: 사이퍼 템플릿

동적 Cypher 생성

이름: Dynamic Cypher Generation
문맥: 구조화된 데이터에 대한 사용자 질문은 다양한 필터를 포함할 수 있는데, 항상 똑같은 필터가 사용되진 않아요. 예를 들어, 동영상 그래프를 생각해 볼까요? 이런 질문들이 있을 수 있겠죠.

– 스티븐 스필버그가 감독한 영화는 무엇인가요?
– 스티븐 스필버그가 2000년부터 2010년까지 감독한 영화는 무엇인가?
– 스티븐 스필버그가 2000년에서 2010년 사이에 감독한 영화는 무엇입니까?

이런 질문 목록은 끝없이 이어질 수 있어요. 이 모든 질문에 대해 Cypher 템플릿을 만들고 싶진 않을 거예요. 해결책은 사용자 질문에 실제로 주어진 파라미터를 기반으로 Cypher 쿼리를 (부분적으로) 동적으로 생성하는 거죠.

필수 전처리: 파라미터화된 Cypher 쿼리의 조각과 해당 쿼리가 수행하는 작업에 대한 설명을 LLM에 제공해야 해요.

필수 그래프 패턴: 도메인 그래프

설명: 사용자 질문이 주어지면 LLM은 사용할 스니펫을 결정해요. LLM은 사용자 질문에서 파라미터를 추출하고 이를 스니펫에 연결한 다음, 결합해서 전체 쿼리를 생성하죠. 쿼리는 데이터베이스에 대해 실행되고, 결과는 답변을 생성하기 위해 LLM에 다시 제공돼요.

용법: 이 패턴은 Cypher 템플릿에서 한 단계 더 나아간 유용한 발전이에요. 훨씬 더 유연해져서 더 다양한 사용자 질문에 대한 답변을 얻을 수 있죠. 하지만 제공된 스니펫은 질문의 범위를 제한한다는 점을 기억해야 해요.

자원: Llama 3.1, NVIDIA NIM 및 LangChain을 사용하여 Knowledge Graph 기반 에이전트 구축

GraphRAG 패턴 카탈로그: graphrag.com: Dynamic Cypher Generation

Text2Cypher

이름: Text2Cypher
문맥: GraphRAG 패턴인 Cypher 템플릿과 Dynamic Cypher Generation은 구현 중에 정의된 쿼리 및 쿼리 조각에 의해 제한될 수 있어요.

필수 전처리: LLM은 도메인 설명과 함께 데이터베이스 스키마를 얻어야 해요. 이렇게 하면 결과가 훨씬 좋아지죠. 질문에서 쿼리로의 값 변환을 개선하기 위해 선택적으로 실제 데이터 값 샘플, 분포 또는 범주형 값 목록을 통해 스키마를 향상시킬 수도 있어요.

필수 그래프 패턴: 도메인 그래프

설명: 사용자 질문은 LLM에 의해 Cypher 쿼리로 변환돼요. 쿼리는 데이터베이스에 대해 실행되고, 결과는 답변을 생성하기 위해 LLM에 다시 제공되죠.

용법: 이 패턴은 정말 유연해요. 미리 정의된 쿼리가 없어서 이론적으로 LLM은 모든 쿼리를 생성할 수 있죠. 하지만 100% 신뢰할 수는 없어요. LLM은 텍스트를 Cypher로 번역할 때 완벽하지 않거든요. LLM이 주어진 사용자 쿼리를 올바르게 번역할 수 없다면 어떤 답변도 제공할 수 없어요.

자원: Neo4j를 LangChain 생태계에 통합, LangChain Cypher Search: 팁과 요령

GraphRAG 패턴 카탈로그: graphrag.com: Text2Cypher
기존 구현: LangChain: GraphCypherQAChain

Cypher Templates, Dynamic Cypher Generation, 그리고 Text2Cypher를 비교하면 다음과 같은 비유를 사용할 수 있어요 (아래 이미지 참고).

  • Cypher Templates는 하나의 조각으로 구성된 서로 다른 유사한 장난감을 갖는 것과 같아요. 자유도가 별로 없죠.
  • Dynamic Cypher Generation은 벽돌을 쌓는 것과 같아요. 조합하는 방법은 더 자유로워졌지만, 여전히 실패할 염려가 있죠.
  • Text2Cypher는 그림과 같아요. 자유도는 높지만 쉽게 실패할 수도 있어요.
도메인 그래프 검색기와 유사합니다. DALL-E에서 생성된 이미지.

위 패턴들에도 똑같은 문제가 발생할 수 있어요. 즉, 사용자 질문에서 엔터티를 추출할 때 엔터티 별칭이 데이터베이스의 기존 값과 일치해야 한다는 거죠. 그렇지 않으면 쿼리가 결과를 반환하지 못할 거예요. 향상된 스키마를 제공하거나, 다음과 같이 데이터베이스에서 전체 텍스트 인덱스 검색을 사용해서 이 문제를 완화할 수 있어요: LangChain 템플릿: Neo4j Advanced RAG(Neo4j Cypher FT).

Text2Cypher 패턴은 가장 유연하지만, 가장 신뢰하기 어려울 수 있어요. 하지만 신뢰성을 향상시키기 위한 많은 연구가 진행되고 있답니다. 접근 방식은 다음과 같아요.

  • 자연어에서 Cypher 번역으로의 LLM Fine-tuning (예: Text2Cypher에서 Llama3 Fine-tuning)
  • Few-shot 예제 사용 최적화 (예: Neo4j Live: 상황별 학습 및 Fine-tuning으로 text2cypher 향상 또는 몇 번의 예시에서 암시적 추론을 통해 다중 홉 질문 응답을 위한 LLM 기반 KGQA 개선)
  • 자가 치유 생성 - 생성된 쿼리가 데이터베이스에서 실행될 수 없는 경우, 더 나은 쿼리를 생성할 수 있는 또 다른 기회를 얻는 LLM에 획득된 예외 메시지와 함께 쿼리를 반환합니다 (예: 모든 그래프 스키마에서 ChatGPT 4를 사용하여 Cypher 쿼리 생성)

언어 모델의 선택은 결과에 큰 영향을 미쳐요. 모든 언어 모델은 저마다 강점과 약점이 있거든요. 이 주제에 대해서는 강연 중에 더 자세히 이야기할 예정이에요 (LLM 쿼리 벤치마크: Cypher와 SQL). 올해 NODES 회의는 Knowledge Graph와 관련된 모든 것을 다루는 24시간 무료 온라인 이벤트랍니다.

고급 GraphRAG 패턴 — 어휘 그래프와 도메인 그래프가 결합된 검색 패턴

어휘 그래프와 도메인 그래프를 함께 사용하는 시나리오는 정말 많아요. 예를 들어, 이미 도메인 그래프가 있는데 구조화되지 않은 문서를 추가해서 더 풍부하게 만들 수도 있고, 반대로 구조화되지 않은 문서만 가지고 있는데 여기서 엔터티를 추출해서 도메인 그래프를 만들 수도 있죠. Vector Search에 텍스트 속성을 사용하는 경우도 있고요. 이런 모든 시나리오에서 추가적인 GraphRAG 검색 패턴을 활용해서 검색 결과를 훨씬 더 좋게 만들 수 있답니다.

그래프 강화 Vector Search

이름: 그래프 강화 Vector Search
다음으로도 알려져 있습니다. 그래프 + 벡터, 증강 Vector Search
문맥: 기본적인 GraphRAG 패턴의 가장 큰 문제점은 질문에 필요한 모든 관련 맥락을 찾는 건데요. 이 맥락이 여러 조각으로 흩어져 있어서 검색으로 찾기 어려울 수 있어요. 하지만 각 조각에 있는 실제 엔터티들을 서로 연결하고, Vector Search를 통해 이 관계들을 찾아낸다면, 그 조각들이 참조하는 엔터티에 대한 추가적인 맥락을 얻을 수 있죠. 엔터티 네트워크를 이용해서 조각들을 서로 연결하는 방법도 있답니다.
필수 전처리: LLM을 사용해서 각 조각에서 엔터티와 관계를 추출해야 해요. 그리고 추출된 트리플을 그래프로 가져와야 하죠.
필수 그래프 패턴: 추출된 항목이 포함된 어휘 그래프

추출된 항목이 포함된 어휘 그래프. 작성자의 이미지입니다.

설명: 사용자 질문은 청크 임베딩을 생성하는 데 사용된 것과 동일한 임베더를 사용해서 임베딩돼요. Vector 유사성 검색은 청크 임베딩에서 실행되어서, 가장 유사한 k (개발자/사용자가 미리 설정한 숫자)개의 청크를 찾습니다. 그리고 발견된 청크에서 시작하는 도메인 그래프 순회가 실행되어서 더 많은 맥락을 검색하죠.
용법: 이 패턴은 Vector Search만 실행했을 때보다 훨씬 풍부한 맥락을 검색하는 데 유용해요. 추가적인 순회를 통해 제공된 데이터 내에서 엔터티들의 상호 작용을 찾을 수 있거든요. 특정 텍스트 청크를 검색하는 것보다 훨씬 많은 정보를 얻을 수 있고, 결과적으로 답변 생성을 위한 더 좋은 품질의 입력을 제공할 수 있어요. 물론 이 GraphRAG 패턴을 사용하려면 전처리 과정에 더 많은 노력과 비용(시간과 돈 모두!)이 들겠죠. 게다가 그래프 순회에서 반환되는 정보의 양이 LLM이 처리해야 하는 맥락의 크기를 상당히 늘릴 수도 있다는 점도 기억해야 해요.
검색 쿼리:

retrieval_query = """ 
MATCH (node)-[:PART_OF]->(d:Document)
CALL { WITH node
  MATCH (node)-[:__HAS_ENTITY__]->(e)
  MATCH path=(e)(()-[rels:!HAS_ENTITY&!PART_OF]-()){0,2}(:!Chunk&!Document)
  …
  RETURN …}
RETURN …
"""

이 검색기에는 몇 가지 변형이 있답니다.

  • 엔터티 명확성(Entity Resolution) — 단순한 엔터티 추출 파이프라인은 텍스트에서 모든 엔터티를 가져오죠. 하지만 여러 엔터티는 텍스트에서 다르게 언급될 수 있지만, 실제로는 같은 엔터티를 의미할 수 있어요. **Knowledge Graph**를 깔끔하게 유지하려면, 이런 엔터티들을 병합하는 엔터티 명확화 단계를 거쳐야 해요. 자세한 방법은 다음 링크에서 확인할 수 있습니다. Neo4j 및 LangChain을 사용하여 '로컬에서 글로벌로' GraphRAG 구현: 그래프 구성 그리고 LlamaIndex에서 Relik을 사용한 엔터티 연결 및 관계 추출를 참고하세요.
  • 질문 안내/스키마 정의 추출(Question Guiding/Schema Definition Extraction) — **Large Language Model(LLM)**이 모든 종류의 엔터티와 관계를 추출하도록 하는 대신, 애플리케이션과 관련된 도메인 지식만 추출하도록 안내하는 질문이나 고정된 **스키마**를 제공하는 거예요. 이렇게 하면 추출 범위와 양을 좁힐 수 있죠. 예를 들어, WhyHow.AI 오픈 소스 **Knowledge Graph** 스키마 라이브러리 소개 — 더 빠르게 실험 시작을 참고해보세요.
  • 엔터티 임베딩(Entity Embedding) — **LLM**을 사용해서 엔터티와 관계를 추출할 때, **LLM**에게 엔터티와 관계에 대한 설명도 생성/추출하도록 지시할 수 있어요. 이렇게 추출된 설명은 **Vector Embedding**으로 저장해서, 나중에 탐색 과정에서 초기 **벡터 검색**이나 다른 지침으로 활용할 수 있답니다.
  • 온톨로지 기반 순회(Ontology-Based Traversal) — 순회를 애플리케이션 코드에 하드 코딩하는 대신, 순회에 대한 온톨로지를 제공할 수 있어요. 이 접근 방식은 메타로의 전환— Ep 24: KG+LLM: 온톨로지 기반 RAG 패턴에서 자세히 설명되어 있습니다.

글로벌 커뮤니티 요약 검색기

이름: 글로벌 커뮤니티 요약 검색기
다음으로도 알려져 있습니다: Microsoft GraphRAG, 글로벌 리트리버
맥락: 전체 데이터 세트에 대해 질문할 때, 특정 질문은 일부 덩어리에만 존재하는 내용과 관련되는 것이 아니라, 데이터 세트에서 가장 중요한 전체 메시지를 검색해야 할 때가 있어요. 앞서 언급한 패턴들은 이런 종류의 "글로벌" 질문에 답하는 데 적합하지 않죠.
필수 전처리: 엔터티와 해당 관계를 추출하는 것 외에도, 도메인 그래프 내에 계층적 커뮤니티를 형성해야 해요. 이는 Leiden 알고리즘을 사용해서 할 수 있죠. 모든 커뮤니티에 대해 **LLM**은 엔터티 및 관계 정보를 커뮤니티 요약으로 요약합니다.
필수 그래프 패턴: 추출된 엔터티 및 커뮤니티 요약이 포함된 어휘 그래프

추출된 엔터티 및 커뮤니티 요약이 포함된 어휘 그래프. 작성자의 이미지입니다.

설명: 사용자 질문과 특정 커뮤니티 레벨이 주어지면, 커뮤니티 요약이 검색되어 **LLM**에 제공됩니다.
용법: 이 패턴은 전역적인 성격을 갖는 질문에 유용해요. 예를 들어 전체 데이터베이스의 내용을 요약하거나, 전체 데이터에서 주제 구조를 찾는 것과 같은 경우죠. 엔터티 및 관계 추출, 커뮤니티 감지, 커뮤니티 요약 등 수행해야 할 단계가 많기 때문에, 필요한 그래프 패턴을 설정하는 데 노력이 많이 들어요. 이러한 작업 중 **LLM**이 실행해야 하는 작업과 전처리 비용을 허용 가능한 수준으로 유지하기 위해 다르게 처리할 수 있는 작업은 무엇인지 고려해야 합니다.
검색 쿼리: 이 접근 방식은 유사한 **벡터** **노드**를 가져오기 위해 Neo4j **Vector**를 사용하지 않아요. 특정 레벨의 모든 커뮤니티 요약을 가져옵니다. 다음은 **쿼리**의 예시입니다.

community_summaries = """
MATCH (c:__Community__)
WHERE c.level = $level
RETURN c.full_content AS output
"""

자원: 로컬에서 글로벌로: 쿼리 중심 요약에 대한 그래프 RAG 접근 방식, Neo4j 및 LangChain을 사용하여 '로컬에서 글로벌로' GraphRAG 구현: 그래프 구성, Microsoft GraphRAG를 Neo4j에 통합
GraphRAG 패턴 카탈로그: graphrag.com: 글로벌 커뮤니티 요약 검색기
구현 예: 마이크로소프트 그래프RAG

추출된 엔터티, 커뮤니티 및 커뮤니티 요약과 함께 어휘 그래프를 사용할 수 있는 몇 가지 변형이 있어요.

  • 질문에 따라 먼저 커뮤니티 요약의 임베딩에 대한 벡터 유사성 검색을 실행해서 질문과 관련된 하위 그래프를 식별한 다음 커뮤니티에서 엔터티 및 청크로 순회하여 정보를 검색할 수도 있죠.

요약

다양한 GraphRAG 패턴을 탐색해봤는데요, 이건 정말 흥미로운 여정의 시작일 뿐이에요. 현재 목록은 GraphRAG 진화의 초기 단계를 보여주고 있죠. 앞으로 나아가면서 새로운 패턴들이 나타나 신선한 통찰력과 가능성을 가져올 거라고 생각해요.

각 RAG 패턴은 특정 유형의 질문에 답하도록 맞춰져 있고, 고유한 그래프 패턴과 전처리 단계가 필요해요. 애플리케이션에 맞는 완벽한 GraphRAG 패턴을 찾는 건 쉽지 않죠. 다양한 검색 패턴을 실험하고 결과를 적절하게 평가해야 해요. 하나의 단일 패턴이 모든 목적에 적합하지 않을 가능성이 매우 높고, 이는 다양한 쿼리에 적응하는 에이전트적 접근 방식이 거의 항상 필요한 이유이기도 해요. 여기에 제시된 패턴은 사용자 질문에 대한 답변에 대한 컨텍스트가 제공되는 모든 대상 전략이며 데이터베이스를 한 번만 쿼리해요. 더 복잡한 질문에는 또는 패러다임(참조: 그래프 검색-증강 생성: 설문 조사 검색 패러다임에 대한 자세한 내용은 참조)이 필요할 수 있고, 다양한 패턴의 사용을 결합할 수도 있겠죠.

이상적인 GraphRAG 패턴을 발견하기 위한 여정은 시행착오와 혁신으로 가득 찬 흥미진진한 여정이에요. 이 여행을 함께 시작해봐요! 오픈소스 GraphRAG 패턴 카탈로그를 확인하고 기여해주세요. 아직 나열되지 않은 다른 패턴이 있나요? 피드백이나 제안 사항이 있거나 애플리케이션을 공유하고 싶다면 언제든지 이슈를 열어주세요. GraphRAG 패턴에 대해 토론하고 싶다면 GraphRAG Discord 채널을 방문해주세요.

호기심을 갖고 계속 실험해보세요. 다음에 어떤 획기적인 패턴을 발견하게 될지 누가 알겠어요!


  • GraphRAG
  • RAG

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

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

반응형
반응형

GenAI 애플리케이션을 위한 새로운 리소스! Neo4j GraphRAG 생태계 도구를 소개하게 되어 정말 기뻐요. 이러한 오픈 소스 도구를 사용하면 Knowledge Graph를 기반으로 하는 GenAI 애플리케이션을 쉽게 시작할 수 있어서 응답 품질과 설명 가능성을 개선하고 앱 개발 및 채택을 가속화하는 데 도움이 될 거예요.

GraphRAG는 Retrieval-Augmented Generation (RAG)을 Knowledge Graph와 결합해서 환각이나 도메인별 컨텍스트 부족과 같은 중요한 LLM 문제를 해결해요. Knowledge Graph는 LLM이 질문에 안정적으로 대답하고 복잡한 작업 흐름에서 신뢰할 수 있는 에이전트 역할을 하는 데 필요한 상황별 메모리를 제공하죠. 텍스트 데이터의 단편에만 액세스할 수 있는 대부분의 RAG 솔루션과는 달리, GraphRAG는 구조화된 정보와 반구조화된 정보를 검색 프로세스에 통합한답니다.

이러한 새로운 도구는 구조화되지 않은 텍스트에서 Knowledge Graph를 생성하고, 해당 그래프 또는 기존 Graph Database를 사용하여 vector 및 그래프 검색을 통해 생성 작업에 대한 관련 정보를 검색하는 데 도움을 줄 거예요.

이 도구를 사용해서 GenAI 개발을 시작하고, 자체 시스템에 통합하거나, 사용자 정의 구현을 구축하기 위한 참조 템플릿으로 사용할 수 있어요. 현재 구현에서는 Python 및 Javascript용 LangChain 통합을 사용하지만, 다른 언어 및 프레임워크를 사용하여 구축할 수도 있답니다.

이번 포스팅에서는 LLM Knowledge Graph Builder, NeoConverse 및 GenAI 프레임워크 통합에 대한 개요를 제공할게요.

구조화되지 않은 텍스트를 Knowledge Graph로 빠르게 전환

The Neo4j 지식 그래프 빌더는 마법처럼 보일 수도 있어요. 구조화되지 않은 텍스트를 로드하면 데이터 내의 숨겨진 entity와 relationship을 드러내는 구조화된 그래프를 생성할 수 있거든요. PDF, Word 문서, YouTube 스크립트, Wikipedia 페이지 및 기타 다양한 종류의 구조화되지 않은 텍스트와 함께 사용할 수 있어요.

그래프 기술을 처음 사용하신다면, Knowledge Graph Builder를 사용해서 익숙한 도메인 정보로부터 쉽게 그래프를 만들 수 있어요. 경험이 더 많은 그래프 개발자라면, 이걸 사용해서 새로운 프로젝트를 시작할 수도 있고요. 그래프 빌더를 온라인으로 사용해 보세요. 만약 Neo4j 인스턴스가 없다면, 무료로 Neo4j AuraDB 데이터베이스를 생성할 수 있답니다.

그래프 빌더는 소스 문서와 해당 청크, 그리고 임베딩을 어휘 그래프로 추출하는 것 뿐만 아니라, 엔터티와 그 관계 그래프를 추출해서 청크에 연결해 줘요.

답변 뒤에 숨겨진 비정형 컨텍스트 데이터와 구조화된 컨텍스트 데이터를 확인하기 위해 수집된 데이터를 시각화하고 질문할 수 있어요. 그리고 저희는 GraphRAG를 사용하기 때문에, 모든 Vector Embedding 검색 결과에 대해 백그라운드에서 관련 엔터티를 가져와 LLM에 제공해서 답변을 생성할 수 있는 거죠.

Graph Builder 프론트 엔드는 Neo4j 디자인 시스템을 사용하는 React 애플리케이션이에요. (바늘 스타터 키트) 그리고 최근 발표된 Neo4j 시각화 라이브러리를 사용하죠. 백엔드는 LangChain 통합을 통해 Neo4j와 상호작용하면서, Knowledge Graph 추출, Vector Search와 그래프 검색 쿼리를 결합한 GraphRAG 검색을 수행해요. Python으로 작성되었고 FastAPI를 사용하며 Google Cloud Run에서 컨테이너로 실행된답니다.

Knowledge Graph 구성을 위해 아래와 같이 자신의 코드에서 LangChain 통합을 사용할 수 있어요.

from langchain_experimental.graph_transformers import LLMGraphTransformer
from langchain_openai import ChatOpenAI
from langchain_community.graphs import Neo4jGraph

graph = Neo4jGraph()
llm = ChatOpenAI(temperature=0, model_name="gpt-4o")

kg_transformer = LLMGraphTransformer( llm=llm,
  allowed_nodes=["Person", "Country", "Company"],
  allowed_relationships=["LOCATED_IN","WORKED_AT"])

results = kg_transformer.convert_to_graph_documents(documents)
graph.add_graph_documents(results)

Knowledge Graph Builder에 대해 자세히 알아보고, 소스 코드를 찾고, 연습 동영상을 보려면 다음을 확인해 보세요. 그래프 빌더 페이지. 더 좋은 건, 직접 시도해 보는 것이죠! 자신의 파일로요. 여기에서 짧은 데모 비디오를 시청할 수도 있답니다.

자연어를 사용하여 그래프 쿼리

저희 NeoConverse 도구는 기존 Knowledge Graph의 구조를 활용해서 사용자의 질문으로부터 Cypher 그래프 `Query`를 생성하고, Neo4j 데이터베이스에 대해 실행해요. 이렇게 얻은 `Query` 결과는 텍스트 또는 차트 응답을 만드는 데 사용되죠. (NeoConverse를 직접 테스트해볼 수도 있어요!)

NeoConverse의 흐름은 다음과 같아요:

  1. 사용자는 데이터 세트를 선택하고, 응답을 텍스트로 받을지 차트로 받을지 선택해요.
  2. 사용자가 질문을 입력해요.
  3. 시스템은 데이터베이스에서 데이터베이스 `Schema`를 추출하고, 이를 사용자 질문과 결합해서 구성된 LLM에 대한 `Prompt`를 생성해요.
  4. LLM은 데이터베이스에 대해 실행할 Cypher `Query`를 생성하죠.
  5. `Query`가 검증되고 실행돼요.
  6. `Query` 결과는 사용자 질문 및 차트 라이브러리에 대한 마크다운 텍스트 또는 구성과 함께 응답을 생성하기 위한 특정 `Prompt`와 함께 LLM으로 전송돼요.

NeoConverse는 기능을 보여주기 위해 미리 구성된 여러 데이터 세트와 함께 제공돼요. 각각에 대해 데이터베이스의 `Schema`와 예제 질문을 볼 수 있죠 (세로 줄임표 클릭!). NeoConverse에서 추가 데이터 세트를 구성해서 여러분의 Neo4j 데이터베이스에 연결할 수도 있답니다.

NeoConverse에 대해 더 자세히 알아보고 추가 비디오, 블로그 게시물, GitHub 저장소 링크를 찾고 싶으시다면 네오컨버스 페이지를 방문해 보세요.

선호하는 LLM 프레임워크 사용

Neo4j는 Python, JavaScript, Java 및 .Net용 대부분의 오픈 소스 GenAI 생태계 라이브러리와 정말 원활하게 통합된다는 사실!

저희는 LangChain Python과 LangChain Javascript를 통합했어요. 이걸로 Vector 및 Graph Search, 텍스트-Cypher, 대화형 메모리, Knowledge Graph 구성, 고급 RAG 템플릿 등을 제공할 수 있게 되었답니다.

저희 LlamaIndex 통합에는 Cypher Search, Vector Search, Knowledge Graph 표현 및 구성, 텍스트-Cypher 쿼리가 포함되어 있어요. 최근에는 LlamaIndex 팀과 협력해서 Knowledge Graph 통합을 구성과 쿼리 모두에 사용하고 있답니다.

Deepset의 Haystack의 경우, Vector Search 및 Cypher 쿼리에 대한 귀중한 커뮤니티 기여를 얻었고, 앞으로 더 많은 기능을 추가할 예정이에요.

이러한 통합 페이지 중 상당수는 Edgar SEC 서류에 대한 GraphRAG 애플리케이션 데이터세트를 빌드하는 방법을 설명하는 관련 스타터 키트 구현을 가리키고 있어요.

GraphRAG 및 GenAI 개발을 위한 기술 향상

Neo4j GraphRAG 도구와 GenAI 생태계에 대해 더 자세히 알고 싶으시다면, GenAI 생태계 페이지를 한번 살펴보세요.

이 페이지에서는 임베딩 생성 및 벡터 검색과 같은 Neo4j GenAI 기능에 대한 자세한 정보는 물론, Google(Vertex AI), AWS(Bedrock) 및 Azure(OpenAI)와의 클라우드 네이티브 GenAI 통합에 대한 정보를 제공해요. 각 서비스에 대한 비디오 튜토리얼도 준비되어 있답니다.

GraphRAG 데모, NeoConverse, 그리고 Knowledge Graph Builder를 포함한 GenAI 프로젝트 예시도 찾아볼 수 있어요. 각 도구의 기능에 대해 자세히 설명해 드릴게요.

직접 실습할 수 있는 기회도 준비했어요! Deeplearning AI와 협력해서 Knowledge Graph 코스를 만들었는데요, 그래프 기반 GenAI 애플리케이션을 구축하는 과정을 차근차근 알려드릴 거예요.

GenAI 앱 개발을 심도 있게 가르치는 무료 GraphAcademy 강좌도 있답니다.

  • GraphRAG
  • RAG

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

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

반응형
반응형

소스 코드 한 줄 없이 가상의 퀴즈 게임으로 설명하는 GraphRAG

GraphRAG에 대해 들어본 적은 있는데, 정확히 뭔지, 기존 RAG와 비교해서 어떤 장점이 있는지 궁금하신가요? 이 카드 게임을 통해 코드나 기술적인 세부 사항 없이 GraphRAG를 보여드리려고 해요. 이 비유를 이해하려면 RAG가 뭔지는 알고 있어야 할 거예요. 혹시 RAG를 잘 모르신다면, 이 블로그의 마지막 부분에서 RAG에 대한 간략한 개요와 그래프 이론을 설명해 드릴게요.

게임

이 게임은 플레이어가 퀴즈 질문에 답하는 챗봇 역할을 하는 간단한 퀴즈 게임이에요. 여기 예시에서는 퀴즈 주제가 음악이지만, 어떤 주제든 상관없어요.

점수를 기록할 메모장과 펜 외에, 게임은 다음과 같은 요소로 구성되어 있어요.

  •  — 뒷면은 모두 똑같고, 앞면에는 질문 1개와 답 1개(단서 카드에 대한 참조와 함께)가 적혀 있어요.
질문 카드
  •  — 뒷면에는 단서의 주제가 적혀 있는 단서 카드가 12장 있어요. 앞면에는 해당 주제에 대한 추가 정보가 담겨 있죠.
단서 카드
  •  — 게임 보드에는 12개의 단서 카드를 놓을 수 있는 12개의 필드와, 이들 간의 관계가 표시되어 있어요.
게임 보드
  •  — STANDARD, RAG, GRAPH RAG라고 적힌 세 개의 필드가 있는 스피너예요.

게임을 시작하려면 모든 질문 카드를 잘 섞어서 뒷면이 위로 오도록 게임 보드 옆에 더미로 놓아주세요. 스피너는 게임 보드 옆에 놓고요.

게임 설정

이제 모든 단서 카드를 게임 보드의 해당 필드에 뒷면이 위로 향하게 놓으면 준비 완료!

게임 보드의 단서 카드

게임 플레이

플레이어는 맨 위에 있는 질문 카드를 뽑아서 질문을 읽어요.

블로그의 긴장감을 위해 답변은 흐릿하게 처리했어요. 🙂

이제 플레이어가 스피너를 돌립니다!

스피너가 STANDARD에 멈추면, 일반 챗봇처럼 행동해서 질문("비틀즈에서 Jeff Lynne과 함께 다른 밴드에서 연주한 사람은 누구입니까?")에 대해 아는 한도 내에서 답변해야 해요. 비틀즈에 대해 얼마나 알고 있나요? 적어도 John Lennon과 Paul McCartney는 아시겠죠? Paul이 아직 살아있다는 점을 고려하면 (그가 1966년에 사망하고 비슷한 인물로 대체되었다는 음모론을 믿지 않는 한) John이 비틀즈 이후 몇 년을 Yoko Ono와 함께 보낸 반면 Paul이 답일 수도 있겠네요. 그렇다면 오답이에요!

스피너가 RAG에 멈추면, 질문 카드 하단에 언급된 단서 카드(이 경우 비틀즈)를 집어 들어요.

비틀즈에 대한 단서 카드

여기에서 밴드 멤버 목록을 보고 "아, 맞아! 다른 두 사람 이름이 있었지!"라고 생각할 수도 있고, 링고 스타가 Jeff Lynne이라는 사람과 함께 밴드에서 연주할 친구처럼 들린다고 생각할 수도 있어요. 그것도 오답이죠!

마지막으로 스피너가 GRAPH RAG에 멈추면, 관계 유형을 하나 선택하고, 참조된 단서 카드(이 경우 비틀즈)에서 한 단계 떨어진, 해당 관계로 연결된 모든 단서 카드를 뽑을 수 있어요. 여기서는 관계 유형을 로 선택해서, (비틀즈 외에) 단서 카드 3개를 더 선택할 수 있는 거죠.

SAME MEMBER와 관련된 게임 보드의 단서 카드

이건 우리가 이미 갖고 있는 것 외에 비틀즈에 대한 단서를 아래에 보여주는 거예요.

비틀즈의 SAME MEMBER 관계를 따르는 단서 카드 3개

여기서는 George Harrison이 Traveling Wilburys에서 Jeff Lynne과 함께 플레이한 것을 볼 수 있으므로 정답은 이거죠! 운이 좋게도 스피너의 GRAPH RAG에 도달할 수 있다면 정답을 얻는 건 어렵지 않아요.

제대로 맞추면 포인트를 얻을 수 있어요. 어느 쪽이든 단서를 다시 뒤집어 놓고 질문을 더미 맨 아래에 놓은 다음 차례는 다음 플레이어로 넘어가는 거죠.

답변이 포함된 질문 카드

마무리

이게 본질적으로 GraphRAG의 내용이고, GraphRAG와 일반 RAG가 표준 LLM 챗봇을 개선하는 방법이에요. 우리는 이게 어떻게 답변을 개선하고 환각을 줄이는지 보여줬지만, "단서"가 LLM이 전혀 알지 못하는 비즈니스 영역(예: HR 기록)에 관한 것일 때 훨씬 더 강력하죠.

그렇다면 Neo4j와 같은 Graph Database를 사용하여 이 게임의 비유를 실제 GraphRAG로 어떻게 변환할 수 있을까요? 게임 보드는 데이터베이스를 나타내고 단서 카드는 데이터베이스에 저장된 모든 문서예요 (게임 보드의 선은 그래프임). 하지만 12개의 문서가 아닌 수백만 개의 문서가 있을 가능성이 높죠. 질문을 받을 때 질문 카드에 있는 것처럼 관련 문서에 대한 직접 링크가 없어요. 대신, 우리는 Vector Embedding과 벡터 검색을 통해 문서의 내용과 질문의 내용을 기반으로 가장 관련성이 높은 문서를 찾아요. 애플리케이션에 질문과 관련 문서가 모두 있으면 이를 모두 챗봇에 전달하고 해당 문서를 기반으로 질문에 응답하고 소스 참조를 포함하도록 요청하는 거예요.

제가 작성한 다음 두 블로그 게시물에서 Vector Embedding과 GraphRAG를 실제로 구현하는 방법에 대한 자세한 내용을 읽을 수 있어요. 4줄의 코드로 구성된 GenAI 기반 노래 찾기 and (거의) 순수 Cypher의 GraphRAG.

부인 성명

이건 단지 주장을 펼치기 위해 사용된 가상의 게임이라는 점을 이해해주셔야 해요. 완전히 개발된 게임은 아니에요. 저는 질문 카드를 한 개 이상 작성하지 않았고, 그 단서 카드에 얼마나 많은 질문을 고안할 수 있는지 생각해 본 적도 없어요. 솔직히, 단 12개의 단서 카드만으로 얼마나 플레이할 수 있을지 잘 모르겠어요. 아마도 5분 정도 게임을 하고 나면 외웠을 거예요.


RAG 및 그래프 설명

RAG에 대해 들어본 적이 없거나 그래프가 차트의 선형 플롯이라고 생각하는 분들을 위해 두 가지 개념에 대한 간략한 요약이 있어요.

RAG

RAG (Retrieval-Augmented Generation)는 ChatGPT와 같은 GenAI(생성 AI) 챗봇의 유용성을 향상시키는 방법이에요. 이는 환각을 줄이고 답변의 설명 가능성을 향상시키며(소스 참조 제공을 통해) 비즈니스 도메인과 같이 교육받지 않은 항목에 대한 질문에 챗봇이 답변할 수 있도록 하는 데 사용돼요. 답변이 포함될 수 있는 관련 문서를 가져와 이를 질문과 함께 챗봇에 제공함으로써 수행되죠.

RAG가 무엇인지에 대한 개요

그래프

그래프에서는 차트나 다이어그램을 참조하는 것이 아니라 이산 수학에서 사용되는 그래프 이론을 참조하는 거예요. 그래프는 edge(때때로 "관계"라고도 함)로 연결된 vertex(때때로 "node"라고도 함)로 구성돼요. 이 블로그에서 게임판은 그래프, 단서 카드의 꼭지점, 그리고 그 사이의 관계가 edge를 나타내죠.

5개의 vertex(node)와 6개의 edge(관계)가 있는 그래프

추가 자료

그래프, RAG 및 GraphRAG에 대한 자세한 설명은 Neo4j CTO Philip Rathle의 다음 블로그 게시물을 확인하세요. GraphRAG 선언문: GenAI에 지식 추가.


  • 그래프RAG
  • Retrieval-Augmented Generation

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

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

반응형

+ Recent posts