728x90
반응형

Llama 3.1의 기본 함수 호출 기능을 사용해서 Knowledge Graph에서 구조화된 데이터를 검색해서 RAG 애플리케이션을 강화해 보세요.

대부분의 사람들은 회사 문서 같은 구조화되지 않은 텍스트에 더 집중해서 Retrieval-Augmented Generation(RAG)을 사용하는데요, 저는 특히 구조화된 정보, 그중에서도 Knowledge Graph 기반 검색 시스템에 대해 꽤 낙관적이에요. GraphRAG, 특히 Microsoft의 구현에 대한 기대가 컸었죠. 하지만 실제 구현을 보면 입력 데이터는 문서 형식의 구조화되지 않은 텍스트이고, Large Language Model(LLM)을 사용해서 Knowledge Graph로 변환되더라고요. 개발자 가이드: Knowledge Graph 구축 방법에서 Knowledge Graph 구축에 대해 자세히 알아보세요.

이번 블로그 포스팅에서는 구조화된 정보가 담긴 Knowledge Graph에서 검색기를 구현하는 방법을 보여드릴게요. 예시로는 FDA 부작용 보고 시스템(FAERS)을 사용할 건데요, 여기에는 약물 부작용에 대한 정보가 들어있어요. Knowledge Graph와 검색에 대해 고민해 본 적이 있다면, LLM을 사용해서 데이터베이스 쿼리를 생성해서 Knowledge Graph에서 관련 정보를 검색하고 질문에 답변하는 방법을 떠올릴 수 있을 거예요. 하지만 LLM을 사용한 데이터베이스 쿼리 생성은 아직 발전 중이고, 가장 일관성 있거나 강력한 솔루션을 제공하지 못할 수도 있어요. 그렇다면 현재 실행 가능한 대안은 뭘까요?

제 생각에는 현재 가장 좋은 해결책은 동적 쿼리 생성이에요. LLM에만 의존해서 전체 쿼리를 생성하는 대신, 미리 정의된 입력 매개변수에서 데이터베이스 쿼리를 결정적으로 생성하는 로직 레이어를 사용하는 거죠. 이 솔루션은 함수 호출을 지원하는 LLM을 사용해서 구현할 수 있어요. 함수 호출 기능을 사용하면 LLM이 함수에 대한 구조화된 입력을 준비하는 방법을 정의할 수 있다는 장점이 있거든요. 이런 접근 방식을 사용하면 사용자 입력의 유연성을 유지하면서 쿼리 생성 프로세스를 제어하고 일관성을 유지할 수 있어요.

사용자의 질문을 이해해서 특정 정보를 검색하는 과정을 나타낸 이미지예요. 이 흐름에는 세 가지 주요 단계가 포함되어 있어요.

  1. 사용자가 35세 미만의 사람들에게 나타나는 약물인 Lyrica의 일반적인 부작용에 대해 질문해요.
  2. LLM은 호출할 함수와 필요한 매개변수를 결정해요. 이 예에서는 약물 Lyrica와 최대 연령 35세를 포함한 매개변수를 사용해서 side_effects라는 함수를 선택했네요.
  3. 식별된 함수와 매개변수는 관련 정보를 검색하기 위한 데이터베이스 쿼리(Cypher) 문을 결정론적이고 동적으로 생성하는 데 사용돼요.

함수 호출 지원은 LLM이 사용자 의도에 따라 여러 검색기를 사용하도록 하거나, Multi-Agent 흐름을 구축하는 등 고급 LLM 사용 사례에 필수적이에요. 저는 기본 함수 호출을 지원하는 상용 LLM을 사용해서 몇 가지 글을 썼었는데요. 이번에는 기본 함수 호출을 지원하는 뛰어난 오픈 소스 LLM인 최근에 출시된 Llama-3.1을 사용할 거예요.

코드는 에서 확인할 수 있어요.

Knowledge Graph 설정

이상반응 정보를 저장하기 위해 기본적으로 제공되는 Graph Database인 Neo4j를 사용해볼게요. FAERS 데이터가 미리 채워진 무료 클라우드 샌드박스 프로젝트를 설정하는 방법은 다음과 같아요. 이 링크를 따라가세요.

인스턴스화된 Database 인스턴스에는 다음 Schema가 포함된 그래프가 있답니다.

이 Schema는 관련 약물, 경험한 반응, 결과 및 처방된 치료법을 포함하여 약물 안전성 보고서의 다양한 측면을 연결하는 `사례` Node를 중심으로 구성되어 있어요. 각 약물은 1차, 2차, 병용 또는 상호 작용 여부에 따라 특성이 결정되죠. `사례`는 제조업체, 환자의 연령대, 보고서 출처에 대한 정보와도 연결돼요. 이 Schema를 사용하면 약물, 약물 반응 및 결과 간의 관계를 구조화된 방식으로 추적하고 분석할 수 있답니다.

Neo4jGraph 객체를 인스턴스화해서 Database에 대한 연결을 생성하는 것부터 시작해볼까요?

os.environ["NEO4J_URI"] = "bolt://18.206.157.187:7687"
os.environ["NEO4J_USERNAME"] = "neo4j"
os.environ["NEO4J_PASSWORD"] = "elevation-reservist-thousands"

graph = Neo4jGraph(refresh_schema=False)

LLM 환경 설정

Llama-3.1과 같은 오픈 소스 LLM을 호스팅하는 옵션은 정말 다양하죠. 여기서는 NVIDIA API Catalog를 사용할 건데요, 이건 NVIDIA NIM 추론 마이크로서비스로 Llama 3.1 모델에 대한 함수 호출을 지원해요. 계정을 만들면 1,000개의 토큰을 받을 수 있는데, 따라하기에 충분한 양이랍니다. API 키를 생성해서 노트북에 복사해야 해요.

os.environ["NVIDIA_API_KEY"] = "nvapi-"
llm = ChatNVIDIA(model="meta/llama-3.1-70b-instruct")

저희는 Llama-3.1–70b를 사용할 건데요, 8b 버전에는 함수 정의에 선택적 매개변수가 있어서 약간의 문제가 있거든요.

NVIDIA NIM 마이크로서비스의 장점은 로컬에서 호스팅할 수 있다는 거예요. 보안이나 다른 문제가 있다면 쉽게 교체할 수 있고, LLM 구성에 URL 매개변수만 추가하면 된답니다.

# connect to an local NIM running at localhost:8000, 
# specifying a specific model
llm = ChatNVIDIA(
  base_url="http://localhost:8000/v1", 
  model="meta/llama-3.1-70b-instruct"
)

도구 정의

4개의 선택적 매개변수를 사용해서 단일 도구를 구성해볼게요. Knowledge Graph에서 관련 정보를 검색하기 위해 해당 매개변수를 기반으로 Cypher 쿼리를 구성할 거예요. 저희 도구는 입력 약물, 연령 및 약물 제조업체를 기반으로 가장 빈번한 부작용을 식별할 수 있답니다.

@tool
def get_side_effects(
  drug: Optional[str] = Field(
    description="disease mentioned in the question. Return None if no mentioned."
  ),
  min_age: Optional[int] = Field(
    description="Minimum age of the patient. Return None if no mentioned."
  ),
  max_age: Optional[int] = Field(
    description="Maximum age of the patient. Return None if no mentioned."
  ),
  manufacturer: Optional[str] = Field(
    description="manufacturer of the drug. Return None if no mentioned."
  ),
):
  """Useful for when you need to find common side effects."""
  params = {}
  filters = []
  side_effects_base_query = """
  MATCH (c:Case)-[:HAS_REACTION]->(r:Reaction), (c)-[:IS_PRIMARY_SUSPECT]->(d:Drug)
  """
  if drug and isinstance(drug, str):
    candidate_drugs = [el["candidate"] for el in get_candidates(drug, "drug")]
    if not candidate_drugs:
      return "The mentioned drug was not found"
    filters.append("d.name IN $drugs")
    params["drugs"] = candidate_drugs

  if min_age and isinstance(min_age, int):
    filters.append("c.age > $min_age ")
    params["min_age"] = min_age
  if max_age and isinstance(max_age, int):
    filters.append("c.age < $max_age ")
    params["max_age"] = max_age
  if manufacturer and isinstance(manufacturer, str):
    candidate_manufacturers = [
      el["candidate"] for el in get_candidates(manufacturer, "manufacturer")
    ]
    if not candidate_manufacturers:
      return "The mentioned manufacturer was not found"
    filters.append(
      "EXISTS {(c)<-[:REGISTERED]-(:Manufacturer {manufacturerName: $manufacturer})}"
    )
    params["manufacturer"] = candidate_manufacturers[0]

  if filters:
    side_effects_base_query += " WHERE "
    side_effects_base_query += " AND ".join(filters)
  side_effects_base_query += """
  RETURN d.name AS drug, r.description AS side_effect, count(*) AS count
  ORDER BY count DESC
  LIMIT 10
  """
  print(f"Using parameters: {params}")
  data = graph.query(side_effects_base_query, params=params)
  return data

get_side_effects 함수는 지정된 검색 기준을 사용해서 Knowledge Graph에서 약물의 일반적인 부작용을 검색하도록 설계되었어요. 검색을 사용자 정의하기 위해 약물 이름, 환자 연령 범위, 약물 제조업체에 대한 선택적 매개변수를 허용하죠. 각 매개변수에는 기능 설명과 함께 LLM에 전달되는 설명이 있어서 LLM이 이를 사용하는 방법을 이해할 수 있게 도와줘요. 그런 다음 함수는 제공된 입력을 기반으로 동적 Cypher 쿼리를 구성하고, Knowledge Graph에 대해 이 쿼리를 실행하고, 결과 부작용 데이터를 반환해요.

기능을 테스트해 볼까요?

get_side_effects("lyrica")
# Using parameters: {'drugs': ['LYRICA', 'LYRICA CR']}
# [{'drug': 'LYRICA', 'side_effect': 'Pain', 'count': 32},
#  {'drug': 'LYRICA', 'side_effect': 'Fall', 'count': 21},
# {'drug': 'LYRICA', 'side_effect': 'Intentional product use issue', 'count': 20},
# {'drug': 'LYRICA', 'side_effect': 'Insomnia', 'count': 19},
# ...

우리 도구는 먼저 질문에 언급된 Lyrica 약물을 Knowledge Graph의 "['LYRICA', 'LYRICA CR']" 값에 매핑한 다음 해당 Cypher 문을 실행해서 가장 빈번한 부작용을 찾았어요.

그래프 기반 LLM 에이전트

이제 남은 유일한 일은 정의된 도구를 사용해서 약물의 부작용에 대한 질문에 답할 수 있는 LLM 에이전트를 구성하는 것이에요.

이 이미지는 사용자가 약물 부작용에 대해 문의하기 위해 Llama 3.1 에이전트와 상호작용하는 모습을 보여줘요. 상담사는 Knowledge Graph에서 정보를 검색해서 사용자에게 관련 데이터를 제공하는 부작용 도구에 액세스하죠.

프롬프트 템플릿을 정의하는 것부터 시작해볼까요?

prompt = ChatPromptTemplate.from_messages(
  [
    (
      "system",
      "You are a helpful assistant that finds information about common side effects. "
      "If tools require follow up questions, "
      "make sure to ask the user for clarification. Make sure to include any "
      "available options that need to be clarified in the follow up questions "
      "Do only the things the user specifically requested. ",
    ),
    MessagesPlaceholder(variable_name="chat_history"),
    ("user", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
  ]
)

프롬프트 템플릿에는 시스템 메시지, 선택적 채팅 기록, 그리고 사용자 입력이 포함돼요. agent_scratchpad는 LLM용으로 예약되어 있는데, 때로는 도구에서 정보를 실행하고 검색하는 등 질문에 답하기 위해 여러 단계가 필요하기 때문이죠.

LangChain 라이브러리를 사용하면 다음과 같이 bin_tools 메소드를 사용해서 LLM에 도구를 추가하는 게 간단해져요.

tools = [get_side_effects]
llm_with_tools = llm.bind_tools(tools=tools)
agent = (
  {
    "input": lambda x: x["input"],
    "chat_history": lambda x: _format_chat_history(x["chat_history"])
    if x.get("chat_history")
    else [],
    "agent_scratchpad": lambda x: format_to_openai_function_messages(
      x["intermediate_steps"]
    ),
  }
  | prompt
  | llm_with_tools
  | OpenAIFunctionsAgentOutputParser()
)

agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True).with_types(
  input_type=AgentInput, output_type=Output
)

에이전트는 채팅 기록 형식을 지정하는 변환 및 핸들러를 통해 입력을 처리하고, 바인딩된 도구를 사용해서 LLM을 적용하고, 출력을 구문 분석해요. 마지막으로 에이전트는 실행 흐름을 관리하고, 입력 및 출력 유형을 지정하고, 실행 중 자세한 로깅을 위한 자세한 정보 표시 설정을 포함하는 실행기로 설정돼요.

에이전트를 테스트해 볼까요?

agent_executor.invoke(
  {
    "input": "What are the most common side effects when using lyrica for people below 35 years old?"
  }
)

결과:

LLM은 적절한 인수와 함께 get_side_effects 함수를 사용해야 함을 식별했어요. 그런 다음 함수는 Cypher 구문을 동적으로 생성하고 관련 정보를 가져온 다음 이를 LLM에 반환하여 최종 답변을 생성하죠.

요약

함수 호출 기능은 Llama 3.1과 같은 오픈 소스 모델에 추가된 강력한 기능으로, 외부 데이터 소스 및 도구와 보다 구조화되고 제어된 상호 작용을 가능하게 해요. 구조화되지 않은 문서를 쿼리하는 것 외에도 그래프 기반 에이전트는 Knowledge Graph 및 구조화된 데이터와 상호 작용할 수 있는 흥미로운 가능성을 제공하죠. NVIDIA NIM 마이크로서비스와 같은 플랫폼을 사용하여 이러한 모델을 쉽게 호스팅할 수 있어서 점점 더 접근 가능하게 만들죠.

언제나 그렇듯이 코드는 에서 사용할 수 있어요.

  • 라마-3

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

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

728x90
반응형

+ Recent posts