2024년 7월 17일 수요일

허깅페이스 SmolLM sLLM 소개 및 사용법

이 글은 허깅페이스에서 릴리즈한 SmolLM sLLM 소개 및 사용법을 간략히 정리한다. 

머리말
SmoLLM은 WebGPU 기반으로 동작되는 sLLM(소형언어모델) 이다. 

모델을 로딩한 후, 질문을 입력하면 빠른 속도로 답변을 생성한다. 

SmolLM은 135M, 360M 및 1.7B 매개변수의 세 가지 크기로 제공된다. 이 모델은 데이터 세트인 Cosmo-Corpus를 기반으로 한다. Cosmo-Corpus에는 Cosmopedia v2(Mixtral에서 생성된 합성 교과서 및 스토리 28B 토큰), Python-Edu(The Stack의 교육용 Python 샘플 4B 토큰) 및 FineWeb-Edu(FineWeb에서 중복 제거된 교육용 웹 샘플 220B 토큰)가 포함된다. 

SmolLM 모델은 상식, 추론 및 지식을 테스트하는 다양한 벤치마크에서 크기 범주의 다른 모델과 비교할 때 유망한 결과를 보여준다. 

설치
다음과 같이 설치한다.
pip install transformers
pip install bitsandbytes accelerate

사용법
CPU, GPU, 다중 GPU에서 모델 실행된다. 사용방법은 다음과 같이 간단하다.

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers import AutoTokenizer, AutoModelForCausalLM

def load_model():
checkpoint = "HuggingFaceTB/SmolLM-1.7B"
device = "cuda" # for GPU usage or "cpu" for CPU usage
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
# for multiple GPUs install accelerate and do `model = AutoModelForCausalLM.from_pretrained(checkpoint, device_map="auto")`
model = AutoModelForCausalLM.from_pretrained(checkpoint).to(device)
inputs = tokenizer.encode("def print_hello_world():", return_tensors="pt").to(device)
outputs = model.generate(inputs)
print(tokenizer.decode(outputs[0]))

def load_in_float16():
checkpoint = "HuggingFaceTB/SmolLM-1.7B"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
# for fp16 use `torch_dtype=torch.float16` instead
model = AutoModelForCausalLM.from_pretrained(checkpoint, device_map="auto", torch_dtype=torch.bfloat16)
inputs = tokenizer.encode("def print_hello_world():", return_tensors="pt").to("cuda")
outputs = model.generate(inputs)
print(tokenizer.decode(outputs[0]))
return model

model = load_in_float16()
print(f"Memory footprint: {model.get_memory_footprint() / 1e6:.2f} MB")
# Memory footprint: 3422.76 MB

def load_in_8bit(option_8bit, option_4bit):
# to use 4bit use `load_in_4bit=True` instead
quantization_config = BitsAndBytesConfig(load_in_8bit=option_8bit, load_in_4bit=option_4bit)
checkpoint = "HuggingFaceTB/SmolLM-1.7B"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForCausalLM.from_pretrained(checkpoint, quantization_config=quantization_config)
inputs = tokenizer.encode("def print_hello_world():", return_tensors="pt").to("cuda")
outputs = model.generate(inputs)
print(tokenizer.decode(outputs[0]))
return model

model = load_in_8bit(True, False)
print(f"Memory footprint: {model.get_memory_footprint() / 1e6:.2f} MB")
# Memory footprint: 1812.14 MB

model = load_in_8bit(False, True)
print(f"Memory footprint: {model.get_memory_footprint() / 1e6:.2f} MB")
# Memory footprint: 1006.84 MB

마무리
LLM은 로컬 LLM, sLLM, vLLM으로 발전하고 있다.

레퍼런스

센서 데이터 해석을 위한 LLM 적용 유스케이스

이 글은 센서 데이터 해석을 위한 대형언어모델 LLM 적용 유스케이스를 간략히 정리한 것이다. 

머리말
센서 데이터를 다루는 목적은 주로 몇 개로 한정된다.
  • 추론
  • 예측
  • 질의
  • 이상패턴 검출
  • 분류
LLM은 이 과정을 모두 수행할 수 있으나, 사실, 잘 알려진 기존 딥러닝 모델에 비해 비효율적이다. 트랜스포머 계열 LLM은 구조와 사용 리소스를 고려했을 때, 좀 더 유용한 목적이 아니면, 그리 추천하기 어렵다. 

LLM의 강점은 연역, 추론(reasoning), 생성인데, 이와 관련된 주요 연구들은 다음과 같다. 

추론
다양한 센서 데이터로부터 센싱된 대상이 되는 상태를 추론하는 것이다.
LLM sense

예측
시계열 데이터의 미래를 예측한다.
Semantic Space Informed Prompt Learning with LLM for Time Series Forecasting

검색 질의
전문가 시스템으로서 주어진 데이터를 증강해 검색에 활용한다.
Ragal LLM RAG

이상패턴 검출
전문가가 데이터를 상세히 확인하여, 이상한 부분을 검출하듯이, 정상 패턴과 이상 패턴을 구분한다.
결론
이외 데이터 분류, 요약 등을 LLM으로 실행할 수 있다. 다만, LLM은 기존 LSTM, MLP 등에 비해 큰 리소스를 사용하여 비용이 크다. 적절히 적용할 필요가 있다.

레퍼런스

2024년 7월 16일 화요일

다양한 멀티모달 3D 생성 AI 아키텍처 모델 레퍼런스 정리

이 글은 다양한 생성AI 아키텍처 모델을 설명한 레퍼런스를 정리한다.
개념도

다음은 2022년부터 3D, 이미지 분야 생성AI 기술 리스트를 정리한 것이다. TEXT-TO-3D, IMAGE, AUDIO 등 다양하다. 이미 알려진, CLIP, STABLE DIFFUSION, TRANSFORMERS 모델을 코어로 사용한 것이 대부분이다.

세그먼테이션 된 포인트 클라우드

Image to Image 생성

AI, 컴퓨터, 소프트웨어 분야는 이렇게 새로운 기술이 오픈 저널과 코드 형태로 쏟아져 나온다. 그에 반해, 유명 건설, 건축 분야 저널들은 논문 하나 리뷰 받는 데 최소 수개월에서 일년가까이 걸린다. 아직, 까다롭게 체크하고 릴리즈하는 관행이 있어, 최신성과는 거리가 먼 구닥다리된 기술 논문이 시장에 출시된다. 

국내 연구기관에서 인사고과에 적극 활용하는 SCIE 지표도 문제가 있다. 연구 결과물에 제대로 된 검증 없이 진행되는 평가 방식은 정량지표에만 집착한다. 인사고과에 굳이 저널 Q1, Q2 따져 점수를 매겨 놓는 데, 행정의 한 종류일 뿐인지라 논문 처리에 아까운 연구 시간만 낭비되는 경향이 많다(이 시간에 기술을 제대로 개발하는 게 나을 것이다. 심지어, 리뷰 후보자도 그 개발을 잘 알고 있는 사람이 드문 빨간펜 체점자인 경우가 많음). 

사실, 몇몇 유명하고 까다로운 저널들의 게재 논문들을 잘 살펴보면, 품질(최신성, 차별성, 공헌성)의 편차가 심한 것을 확인할 수 있다. 저널 채택은 각 저널들의 정책(SCIE를 유지하기 위한 상위 규칙을 포함)들, 리뷰어와 편집자 성향에 따라 복잡하고 주관적으로 처리된다. 기술이 빠르게 발전하는 영역에서는 이런 유명 저널에 제출하면, 많은 리뷰 수정 시간을 날리고, 기회비용을 담보잡힌다. 이는 매우 불합리하고 불공평한 것이다(유명저널도 그들이 비난하는 APC 기반 오픈저널들과 똑같은 비지니스로 돈을 번다는 사실은 참 아이러니하다. 링크 참고 - Is the pay-to-publish model for open access pricing scientists out? | Science | AAAS).

레퍼런스
추신. 직접 연구 개발 안하고, 다른 사람 만든 기술을 본인 것으로 포장하고 사고 친 일부 연구자들?로 인해 강한 관리위주로 설정된 R&D 정책이 앞으로 좋은 방향으로 변화할 리는 없을 것이다(일부 문제 연구자들로 인해 전체를 잠재적 세금 도둑으로 보는 자업자득 현상). 개인적으로는 인사 평가 절차가 필요하다면, 연구자가 개발한 기술과 산출물 위주로 arxir 같은 오픈저널에 내고, github 등에 산출물 투명하게 공개, 확인하는 것이, 국가나 산업 기술발전에 도움되는 가성비있는 평가 방법이라 생각한다(사람 불러 여러 개 과제들을 하루 만에 평가하는 국내 시스템. 제대로 돌아갈 리가 없다).

건설 분야 유명 저널에 제출하고, 리뷰 커멘트 하나하나 시간 투입해 수정하는 것이 무슨 의미가 있는지 가끔 생각이나서 글을 남긴다(사실, R&D는 이미 행정화된 지 오래된 지라, 논문 하나만의 문제는 아니다. 대부분 수많은 규정과 절차에 가려, 제대로 된 R&D는 보이지도 않는다. 그 전에 PBS 제도부터 고쳐야. 적다 보니 역시 한 두 개 문제만 해결된다고 제대로 시스템 돌아갈 수 있는 문제가 아니었다).  - 2024.8.1 

2024년 7월 12일 금요일

pandas AI 이용한 표 형식 데이터 생성AI로 처리해 보기

이 글은 pandas AI 이용한 표 형식 데이터 생성AI로 처리해 보는 방법을 간략히 정리한다.

설치는 다음과 같다. 
pip install pandasai

표에서 정보를 질의하고 생성하는 코드는 다음과 같다. 
import os
import pandas as pd
from pandasai import Agent

sales_by_country = pd.DataFrame({
    "country": ["United States", "United Kingdom", "France", "Germany", "Italy", "Spain", "Canada", "Australia", "Japan", "China"],
    "revenue": [5000, 3200, 2900, 4100, 2300, 2100, 2500, 2600, 4500, 7000]
})

in your .env file)
os.environ["PANDASAI_API_KEY"] = "<input your key>"

agent = Agent(sales_by_country)
answer = agent.chat('Which are the top 5 countries by sales?')
print(answer)

결과는 다음과 같다.

표 형식 데이터에서 간단히 필요한 데이터를 질의할 수 있다는 것을 확인할 수 있다. 요즘 PDF에 포함된 표는 OCR 라이브러리로 쉽게 행렬을 추출할 수 있으므로, LangGraph 등을 이용해 다중 에이전트 방식으로 ChatGPT4o와 같은 LLM을 개발할 수 있다는 것을 알 수 있다.

레퍼런스

2024년 7월 4일 목요일

AutoRAG 활용 LLM RAG 최적화하기

이 글은 AutoRAG를 활용해 대형언어모델 LLM RAG를 최적화하는 방법을 보여준다.

머리말
RAG 알고리즘은 매우 다양하다. RAG는 원래부터 잘 학습된 ChatGPT와 같은 파운데이션 모델을 사용해, 학습과정을 거치지 않고, 전문가의 데이터를 효과적으로 검색해, LLM 결과를 증강하기 위한 목적이었으므로, 그 알고리즘이 다양할 수 밖에 없다. 즉, RAG 결과는 RAG에 사용된 데이터를 잘 학습한 LLM에 비해 성능이 높을 수 없다.
RAG 알고리즘

LLM RAG는 질문에 대한 유사 문서를 얼마나 잘 검색할 수 있는 지가 중요하다. 문서 청크 집합에서 질문 관련 자료를 검색하는 방법은 다양한 알고리즘이 존재한다. 이를 자동화할 수 있는 라이브러리가 AutoRAG이다. 

AutoRAG는 다양한 전략을 조합해 튜닝하는 AutoTuning과 유사한 구조를 가지고 있다. 

LLM RAG 전략 조합

다음은 AutoRAG의 설정이다. 전략 파라메터를 먼저 설정해 놓고, 이를 조합해 정확도가 높은 전략을 얻는 식으로 동작한다. 

node_lines:
  - node_line_name: retrieve_node_line
    nodes:
      - node_type: retrieval
        strategy:
          metrics: [ retrieval_f1, retrieval_recall, retrieval_precision ]
        top_k: 3
        modules:
          - module_type: bm25
          - module_type: vectordb
            embedding_model: huggingface_all_mpnet_base_v2
          - module_type: hybrid_rrf
            target_modules: ('bm25', 'vectordb')
            rrf_k: [ 3, 5, 10 ]
          - module_type: hybrid_cc
            target_modules: ('bm25', 'vectordb')
            weights:
              - (0.5, 0.5)
              - (0.3, 0.7)
              - (0.7, 0.3)
          - module_type: hybrid_rsf
            target_modules: ('bm25', 'vectordb')
            weights:
              - (0.5, 0.5)
              - (0.3, 0.7)
              - (0.7, 0.3)
          - module_type: hybrid_dbsf
            target_modules: ('bm25', 'vectordb')
            weights:
              - (0.5, 0.5)
              - (0.3, 0.7)
              - (0.7, 0.3)
  - node_line_name: post_retrieve_node_line
    nodes:
      - node_type: prompt_maker
        strategy:
          metrics: [ meteor, rouge, bert_score ]
        modules:
          - module_type: fstring
            prompt: "Read the passages and answer the given question. \n Question: {query} \n Passage: {retrieved_contents} \n Answer : "
      - node_type: generator
        strategy:
          metrics: [ meteor, rouge, bert_score ]
        modules:
          - module_type: llama_index_llm
            llm: ollama
            model: llama3
            temperature: [ 0.1, 0.5, 1.0 ]
            batch: 1

설정을 확인하면 알 수 있지만, 학습 데이터셋, LLM 모델, RAG 벡터데이터베이스, RAG 정확도를 측정할 메트릭스, RAG 검색 알고리즘, 실행 프롬프트가 파라메터로 정의되어 있다. 

이를 읽어, langchain과 같은 라이브러리를 내부적으로 호출하며, 각 전략 조합 별 정확도 높은 옵션을 제시한다. 

레퍼런스

2024년 7월 2일 화요일

LLM 학습 데이터 개발 및 준비 방법

이 글은 LLM(Large Language Model. 대형언어모델) 학습 데이터 생성 방법을 간략히 정리한다.

LLM 모델 개발을 하면서, 가장 힘든 일 중 하나는 학습 데이터를 어떻게 확보하느냐는 것이다. 본인의 경우, 여러 시행착오를 거쳐, 다음과 같은 데이터 개발 전략을 사용했다.
  • 허깅페이스 등 기존에 있는 데이터가 있다면, 이를 사용해 본다. 
  • 부족하다면, ChatGPT API등을 이용해, 재료가 되는 텍스트, PDF을 업로드하고, 훈련할 질문 답변 등 데이터를 생성한다.
  • 비용이 비싸다면, LLAMA와 같은 오픈 LLM 모델을 사용해 훈련 데이터를 생성한다.
  • 생성된 학습 데이터 중 이상 데이터를 체크해, 가능하다면, 문장 및 토큰 해석기로 자동 필터링하도록 한다.
  • 사람이 샘플링해 이상 데이터를 체크, 제거한다.
  • 데이터 종류 별 분포가 균형되도록 조정한다.
이 중에 QA 데이터셋을 학습하는 원리를 살펴본다. 다음 프로젝트는 원래 소피아 대학교의 AI 과정을 위한 것이었다. 
대상 답변으로 사용되는 키워드를 식별하는 접근 방식은 RANLP2021 컨퍼런스에서 발표되었다. 이 아이디어는 이 복잡한 문제를 더 간단한 단계로 분할하여, 텍스트에서 객관식 답변을 생성하는 것이다. 이 방법은 텍스트에서 키워드를 식별하고 질문에 대한 답변으로 사용한다. 여기서는 문장에서 답변을 빈 공간으로 바꾸고 질문의 기본형식으로 사용한다.
질답 데이터 생성하는 플레인 텍스트 예시

이 방식대로 BIM 데이터를 RAG처리하기 위한 QA 데이터를 생성하면 다음과 같다. 
플레인 텍스트

생성된 학습 데이터

생성된 훈련 데이터를 확인하면, 필터링되어야 할 부분들을 확인할 수 있다. 이를 처리한 후 모델 튜닝 및 학습에 사용하면 된다. 

레퍼런스

2024년 6월 30일 일요일

LangGraph 기반 다중 LLM 에이전트 개발하기

이 글은 Langgraph를 이용해, 간단히 다중 LLM 에이전트를 개발하는 방법을 정리한 것이다.
개발 환경 설정
다음과 같이 개발 환경을 설치한다.
pip install langchain langgraph milvus

RAG 처리 및 에이전트 프롬프트 정의하기
웹 사이트에서 얻은 BIM(Building Information Modeling), GIS, 기술 표준 전문 지식들을 RAG 처리한다. 이후, 에이전트에서 사용할 프롬프트들을 정의한다. 각 프롬프트가 포함된 LangChain Expression Language (LCEL) 체인은 RAG 벡터 데이터베이스에 문서에 대한 QA 질문, 질문 결과에 대한 정확도 및 환각 유무 평가, 만약, 질문에 대한 적절한 대답이 없다면 웹페이지 검색하도록 유도하도록 구성된다.

다음 코드를 입력 후 실행한다. 
import os
from dotenv import load_dotenv
from typing import List
from typing_extensions import TypedDict
from langchain import hub
from langchain.globals import set_verbose, set_debug
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.document_loaders import WebBaseLoader
from langchain_community.vectorstores import Chroma
from langchain_community.chat_models import ChatOllama
from langchain.prompts import PromptTemplate
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.output_parsers import StrOutputParser
from langchain_core.output_parsers import JsonOutputParser
from langchain.schema import Document

# setup environment for LLM RAG
load_dotenv()
set_debug(True)
set_verbose(True)

# Load documents
urls = [
"https://www.mdpi.com/2220-9964/7/5/162/",
"https://www.tandfonline.com/doi/full/10.1080/19475683.2020.1743355/",
"https://knowledge.bsigroup.com/articles/harmonize-digitize-and-rationalize-exchange-information-confidently-with-bs/",
]

docs = [WebBaseLoader(url).load() for url in urls]
docs_list = [item for sublist in docs for item in sublist]
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
chunk_size=250, chunk_overlap=0
)
doc_splits = text_splitter.split_documents(docs_list)

# Create vectorstore and retriever
vectorstore = Chroma.from_documents(
documents=doc_splits,
collection_name="BIM_GIS",
embedding=HuggingFaceEmbeddings(),
persist_directory="./Chroma_rag.db",

)
retriever = vectorstore.as_retriever(search_type="mmr", search_kwargs={"k": 3}) # type={similarity_score_threshold, mmr, bm25}, https://wikidocs.net/234016

# Load LLM model using Ollama
local_llm = 'llama3'
llm = ChatOllama(model=local_llm, format="json", temperature=0)

# Test grader prompt with JSON output
prompt = PromptTemplate(
template="""You are a grader assessing relevance 
of a retrieved document to a user question. If the document contains keywords related to the user question, grade it as relevant. It does not need to be a stringent test. The goal is to filter out erroneous retrievals. 
Give a binary score 'yes' or 'no' score to indicate whether the document is relevant to the question.
Provide the binary score as a JSON with a single key 'score' and no premable or explaination.
 
Here is the retrieved document: 
{document}
Here is the user question: 
{question}
""",
input_variables=["question", "document"],
)

retrieval_grader = prompt | llm | JsonOutputParser()
question = "ISO 19166's BIM to GIS Element Mapping"
docs = retriever.invoke(question, ) # https://www.kaggle.com/code/marcinrutecki/rag-mmr-search-in-langchain
doc_txt = docs[0].metadata if 'description' in docs[0].metadata else docs[0].page_content
answer = retrieval_grader.invoke({"question": question, "document": doc_txt})
print(answer)

# Test QA prompt
prompt = PromptTemplate(
template="""You are an assistant for question-answering tasks. 
Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. 
Use three sentences maximum and keep the answer concise:
Question: {question} 
Context: {context} 
Answer: 
""",
input_variables=["question", "document"],
)

llm = ChatOllama(model=local_llm, temperature=0)

rag_chain = prompt | llm | StrOutputParser() # Chain

question = "ISO 19166's BIM to GIS Element Mapping"
docs = retriever.invoke(question)
answer = rag_chain.invoke({"context": docs, "question": question})
print(answer)

# Test hallucination grader prompt with JSON output
llm = ChatOllama(model=local_llm, format="json", temperature=0)

prompt = PromptTemplate(
template="""You are a grader assessing whether 
an answer is grounded in / supported by a set of facts. Give a binary score 'yes' or 'no' score to indicate whether the answer is grounded in / supported by a set of facts. Provide the binary score as a JSON with a single key 'score' and no preamble or explanation.
Here are the facts:
{documents} 

Here is the answer: 
{generation}
""",
input_variables=["generation", "documents"],
)

hallucination_grader = prompt | llm | JsonOutputParser()
answer = hallucination_grader.invoke({"documents": docs, "generation": answer})
print(answer)

출력이 다음과 같다면, 성공한 것이다.

LLM 기반 다중 에이전트 구현하기
이제 앞에서 정의된 체인들을 이용해, 에이전트를 정의하고, 그래프를 만든 후 빌드한다.
# Web search tool setup
from langchain_community.tools.tavily_search import TavilySearchResults
tavily_api_key = os.environ['TAVILY_API_KEY'] = <Travily API>
web_search_tool = TavilySearchResults(k=3,  tavily_api_key=tavily_api_key)

# Define the graph state using langgraph
from langgraph.graph import END, StateGraph

class GraphState(TypedDict):
"""
Represents the state of our graph.

Attributes:
question: question
generation: LLM generation
web_search: whether to add search
documents: list of documents 
"""
question : str
generation : str
web_search : str
documents : List[str]

def retrieve(state): # node. Retrieve documents from vectorstore
# Args. state (dict): The current graph state
# Returns. state (dict): New key added to state, documents, that contains retrieved documents
print("---RETRIEVE---")
question = state["question"]
documents = retriever.invoke(question)
return {"documents": documents, "question": question}

def generate(state): # node. Generate answer using RAG on retrieved documents
# Args: state (dict): The current graph state
# Returns: state (dict): New key added to state, generation, that contains LLM generation
print("---GENERATE---")
question = state["question"]
documents = state["documents"]
generation = rag_chain.invoke({"context": documents, "question": question})
return {"documents": documents, "question": question, "generation": generation}

def grade_documents(state): # node. Determines whether the retrieved documents are relevant to the question If any document is not relevant, we will set a flag to run web search
# Args: state (dict): The current graph state
# Returns: state (dict): Filtered out irrelevant documents and updated web_search state
print("---CHECK DOCUMENT RELEVANCE TO QUESTION---")
question = state["question"]
documents = state["documents"]
filtered_docs = [] # Score each doc
web_search = "No"
for d in documents:
score = retrieval_grader.invoke({"question": question, "document": d.page_content})
grade = score['score']
if grade.lower() == "yes": # Document relevant
print("---GRADE: DOCUMENT RELEVANT---")
filtered_docs.append(d)
else: # Document not relevant
print("---GRADE: DOCUMENT NOT RELEVANT---")
# We do not include the document in filtered_docs. We set a flag to indicate that we want to run web search
web_search = "Yes"
continue
return {"documents": filtered_docs, "question": question, "web_search": web_search}
def web_search(state): # Web search based based on the question 
# Args: state (dict): The current graph state
# Returns: state (dict): Appended web results to documents
print("---WEB SEARCH---")
question = state["question"]
documents = state["documents"]

docs = web_search_tool.invoke({"query": question}) # Web search
web_results = "\n".join([d["content"] for d in docs])
web_results = Document(page_content=web_results)
if documents is not None:
documents.append(web_results)
else:
documents = [web_results]
return {"documents": documents, "question": question}

def route_question(state): # Conditional edge. Route question to web search or RAG.
# Args: state (dict): The current graph state
# Returns: str: Next node to call
print("---ROUTE QUESTION---")
question = state["question"]
print(question)

source = question_router.invoke({"question": question})  
print(source)
print(source['datasource'])
if source['datasource'] == 'web_search':
print("---ROUTE QUESTION TO WEB SEARCH---")
return "websearch"
elif source['datasource'] == 'vectorstore':
print("---ROUTE QUESTION TO RAG---")
return "vectorstore"

def decide_to_generate(state): # Determines whether to generate an answer, or add web search
# Args: state (dict): The current graph state
# Returns: str: Binary decision for next node to call
print("---ASSESS GRADED DOCUMENTS---")
question = state["question"]
web_search = state["web_search"]
filtered_documents = state["documents"]

if web_search == "Yes": # All documents have been filtered check_relevance. We will re-generate a new query
print("---DECISION: ALL DOCUMENTS ARE NOT RELEVANT TO QUESTION, INCLUDE WEB SEARCH---")
return "websearch"
else: # We have relevant documents, so generate answer
print("---DECISION: GENERATE---")
return "generate"

def grade_generation_v_documents_and_question(state): # Conditional edge. Determines whether the generation is grounded in the document and answers question.
# Args: state (dict): The current graph state
# Returns: str: Decision for next node to call
print("---CHECK HALLUCINATIONS---")
question = state["question"]
documents = state["documents"]
generation = state["generation"]

score = hallucination_grader.invoke({"documents": documents, "generation": generation})
grade = score['score']

if grade == "yes": # Check hallucination
print("---DECISION: GENERATION IS GROUNDED IN DOCUMENTS---")
print("---GRADE GENERATION vs QUESTION---") # Check question-answering
score = answer_grader.invoke({"question": question,"generation": generation})
grade = score['score']
if grade == "yes":
print("---DECISION: GENERATION ADDRESSES QUESTION---")
return "useful"
else:
print("---DECISION: GENERATION DOES NOT ADDRESS QUESTION---")
return "not useful"
else:
pprint("---DECISION: GENERATION IS NOT GROUNDED IN DOCUMENTS, RE-TRY---")
return "not supported"

workflow = StateGraph(GraphState)

# Define the nodes
workflow.add_node("websearch", web_search) # web search
workflow.add_node("retrieve", retrieve) # retrieve
workflow.add_node("grade_documents", grade_documents) # grade documents
workflow.add_node("generate", generate) # generatae

# Build graph and compile
workflow.set_conditional_entry_point(
route_question,
{
"websearch": "websearch",
"vectorstore": "retrieve",
},
)

workflow.add_edge("retrieve", "grade_documents")
workflow.add_conditional_edges(
"grade_documents",
decide_to_generate,
{
"websearch": "websearch",
"generate": "generate",
},
)
workflow.add_edge("websearch", "generate")
workflow.add_conditional_edges(
"generate",
grade_generation_v_documents_and_question,
{
"not supported": "generate",
"useful": END,
"not useful": "websearch",
},
)

app = workflow.compile()

# Test the graph
from pprint import pprint
inputs = {"question": "What are the types of agent memory?"}
for output in app.stream(inputs, {"recursion_limit": 3}):
for key, value in output.items():
pprint(f"Finished running: {key}:")
pprint(value["generation"])

다음과 같이 출력되면 성공한 것이다..

이외에, Langgrpah를 사용하면, 다음과 같은 작업을 에이전트가 수행할 수 있다.

레퍼런스