-
[LlamaIndex] Querying카테고리 없음 2024. 1. 29. 12:21
이제 데이터를 로드하고, 인덱스를 작성하고, 나중에 사용할 수 있도록 해당 인덱스를 저장했습니다. LLM 응용 프로그램의 가장 중요한 부분인 쿼리를 시작할 준비가 되었습니다.
쿼리는 간단히 말하면 LLM에 대한 프롬프트 호출입니다. 쿼리는 질문을 하고 답변을 얻을 수도 있고, 요약 요청이 될 수도 있고, 훨씬 더 복잡한 지침이 될 수도 있습니다.
○ 기본 사용법
쿼리의 기본은 QueryEngine 입니다. QueryEngine을 얻는 가장 간단한 방법은 다음과 같이 색인을 생성하여 색인을 얻는 것입니다.
query_engine = index.as_query_engine() response = query_engine.query( "Write an email to the user given their background information." ) print(response)
아주 간단해 보이는 위의 예시와는 다르게, 보통 쿼리는 세 가지 단계로 구성됩니다.
- 검색(Retrieval): 색인에서 검색어와 가장 관련성이 높은 문서를 찾아 반환하는 것을 말합니다. 앞서 인덱싱에서 설명한 것처럼 가장 일반적인 검색 유형은 "상위-k" 시맨틱 검색이지만 다른 많은 검색 전략이 있습니다.
- 후처리(Postproecssing): 검색된 노드에 키워드와 같은 특정 메타데이터가 첨부되도록 하거나, 선택적으로 순위를 다시 매기거나, 변환하거나, 필터링하는 것을 의미합니다.
- 응답 합성(Response Synthesis): 응답 합성은 쿼리, 가장 관련성이 높은 데이터 및 프롬프트가 결합되어 응답을 반환하기 위해 LLM으로 전송되는 경우입니다.
○ 사용자 정의 Querying
LlamaIndex는 쿼리를 세밀하게 제어할 수 있는 저수준 구성 API를 제공합니다. 이 예에서는 top_k에 다른 숫자를 사용하도록 검색기를 사용자 정의하고 검색된 노드가 최소 유사성 점수에 도달해야 포함되도록 하는 후처리 단계를 추가합니다. 이렇게 하면 관련성 있는 결과가 있는 경우 많은 데이터를 얻을 수 있지만, 관련성이 없는 경우 데이터가 없을 수도 있습니다.
from llama_index import ( VectorStoreIndex, get_response_synthesizer, ) from llama_index.retrievers import VectorIndexRetriever from llama_index.query_engine import RetrieverQueryEngine from llama_index.postprocessor import SimilarityPostprocessor # build index index = VectorStoreIndex.from_documents(documents) # configure retriever retriever = VectorIndexRetriever( index=index, similarity_top_k=10, ) # configure response synthesizer response_synthesizer = get_response_synthesizer() # assemble query engine query_engine = RetrieverQueryEngine( retriever=retriever, response_synthesizer=response_synthesizer, node_postprocessors=[SimilarityPostprocessor(similarity_cutoff=0.7)], ) # query response = query_engine.query("What did the author do growing up?") print(response)
쿼리의 3가지 단계인 검색(Retrieval), 후처리(Postproecssing), 응답 합성(Response Synthesis) 은 각각 Customizing 할 수 있습니다.
# Configuring retriever
리트리버에 관한 모듈 가이드(https://docs.llamaindex.ai/en/stable/module_guides/querying/retriever/root.html) 에서 배울 수 있는 매우 다양한 리트리버가 있습니다 .
retriever = VectorIndexRetriever( index=index, similarity_top_k=10, )
# Configuring node postprocessors
검색된 노드 개체의 관련성을 더욱 향상시킬 수 있는 고급 노드 필터링 및 증강 기능을 지원합니다. 이를 통해 LLM 호출 시간/횟수/비용을 줄이거나 응답 품질을 개선할 수 있습니다.
Node postprocessor에는 다음과 같은 예시가 있습니다.
- KeywordNodePostprocessor: required_keywords 및 exclude_keywords로 노드를 필터링합니다
- SimilarityPostprocessor: 유사성 점수에 임계값을 설정하여 노드를 필터링합니다(따라서 임베딩 기반 리트리버에서만 지원)
- PrevNextNodePostprocessor: 노드 관계를 기반으로 추가적인 관련 컨텍스트를 추가하여 검색된 노드 객체를 보강합니다
노드 포스트프로세서의 전체 목록은 노드 포스트프로세서 참조 문서(https://docs.llamaindex.ai/en/stable/api_reference/node_postprocessor.html)에 나와 있습니다.
노드 포스트프로세서를 구성하려면 다음과 같이 하세요:node_postprocessors = [ KeywordNodePostprocessor( required_keywords=["Combinator"], exclude_keywords=["Italy"] ) ] query_engine = RetrieverQueryEngine.from_args( retriever, node_postprocessors=node_postprocessors ) response = query_engine.query("What did the author do growing up?")
# Configuring response synthesis
리트리버가 관련 노드를 가져온 후 BaseSynthesizer가 정보를 결합하여 최종 응답을 합성합니다. 다음을 통해 구성할 수 있습니다.
query_engine = RetrieverQueryEngine.from_args( retriever, response_mode=response_mode )
○ Query Pipeline
LlamaIndex는 다양한 모듈을 서로 연결하여 데이터에 대한 간단한 워크플로우부터 고급 워크플로우까지 오케스트레이션할 수 있는 선언적 쿼리 API를 제공합니다. 이는 쿼리 파이프라인 추상화를 중심으로 이루어집니다. 다양한 모듈(LLM에서 프롬프트, 검색기, 다른 파이프라인에 이르기까지)을 로드하고, 이를 모두 순차적 체인으로 연결하여 엔드투엔드로 실행하세요.
# Sequential Chain
간단한 파이프라인은 본질적으로 순전히 선형입니다. 즉, 이전 모듈의 출력이 다음 모듈의 입력으로 직접 들어갑니다.
ex) 프롬프트 -> LLM -> 출력 구문 분석, 프롬프트 -> LLM -> 프롬프트 -> LLM, 검색기 -> 응답 합성기이러한 Sequential한 워크플로는 다음과 같이 코드로 구현 가능합니다.
from llama_index.query_pipeline.query import QueryPipeline # try chaining basic prompts prompt_str = "Please generate related movies to {movie_name}" prompt_tmpl = PromptTemplate(prompt_str) llm = OpenAI(model="gpt-3.5-turbo") p = QueryPipeline(chain=[prompt_tmpl, llm], verbose=True)
다음 코드는 조금 복잡한 워크플로에 대한 예시 입니다. 먼저 주어진 주제에 대한 질문을 생성하고, 더 나은 검색을 위해 주어진 질문에 환각적으로 대답합니다. 이후 생성된 프롬프트를 검색에 활용합니다.
from llama_index.postprocessor import CohereRerank # generate question regarding topic prompt_str1 = "Please generate a concise question about Paul Graham's life regarding the following topic {topic}" prompt_tmpl1 = PromptTemplate(prompt_str1) # use HyDE to hallucinate answer. prompt_str2 = ( "Please write a passage to answer the question\n" "Try to include as many key details as possible.\n" "\n" "\n" "{query_str}\n" "\n" "\n" 'Passage:"""\n' ) prompt_tmpl2 = PromptTemplate(prompt_str2) llm = OpenAI(model="gpt-3.5-turbo") retriever = index.as_retriever(similarity_top_k=5) p = QueryPipeline( chain=[prompt_tmpl1, llm, prompt_tmpl2, llm, retriever], verbose=True )
# DAG Chain
많은 파이프라인에서는 DAG를 설정해야 합니다(예를 들어 표준 RAG 파이프라인의 모든 단계를 구현하려는 경우). 여기에서는 키와 함께 모듈을 추가하고 이전 모듈 출력과 다음 모듈 입력 간의 링크를 정의하는 하위 수준 API를 제공합니다
from llama_index.postprocessor import CohereRerank from llama_index.response_synthesizers import TreeSummarize from llama_index import ServiceContext # define modules prompt_str = "Please generate a question about Paul Graham's life regarding the following topic {topic}" prompt_tmpl = PromptTemplate(prompt_str) llm = OpenAI(model="gpt-3.5-turbo") retriever = index.as_retriever(similarity_top_k=3) reranker = CohereRerank() summarizer = TreeSummarize( service_context=ServiceContext.from_defaults(llm=llm) ) # define query pipeline p = QueryPipeline(verbose=True) p.add_modules( { "llm": llm, "prompt_tmpl": prompt_tmpl, "retriever": retriever, "summarizer": summarizer, "reranker": reranker, } ) p.add_link("prompt_tmpl", "llm") p.add_link("llm", "retriever") p.add_link("retriever", "reranker", dest_key="nodes") p.add_link("llm", "reranker", dest_key="query_str") p.add_link("reranker", "summarizer", dest_key="nodes") p.add_link("llm", "summarizer", dest_key="query_str")