이 글은 최근 LLM 개발자 관심 중 하나인 LLM 기반 구조화된 형식의 데이터 생성하는 방법을 간략히 소개한다. 이를 위해, JSON 입출력이 가능하도록 RAG처리하는 방법을 개발한다.
본 글은 OpenAI ChatGPT와 같이 API를 사용하려면 구독해야 하는 상용 모델 대신 라마, 미스랄과 같은 오픈소스 모델을 사용한다.
JSON 파일을 RAG하기 위해서는 여러가지 부분을 고려해야 한다. 보통, RAG를 위해서는 랭체인, 라마 인덱스 등 라이브러리를 사용하는 데, 의존성 변화가 심한데다, 급격히 발전하고 있어 설치, 빌드에 여러 에러가 발생하고 있다. 벡터 데이터베이스를 이용한 RAG도 한계가 있어, 필요한 정보를 제대로 검색하지 못하는 이슈들이 있다. 이런 문제들을 고려하고, RAG처리해야 한다.
개발 환경 준비
다음과 같이 개발환경을 설치한다. 그리고, ollama 도구를 설치하도록 한다.
pip install llama-cpp-python
pip install 'crewai[tools]'
pip install langchain
TEXT TO JSON
from llama_cpp.llama import Llama, LlamaGrammar
import httpx
grammar_text = httpx.get("https://raw.githubusercontent.com/ggerganov/llama.cpp/master/grammars/json_arr.gbnf").text
grammar = LlamaGrammar.from_string(grammar_text)
llm = Llama("llama-2-13b.Q8_0.gguf")
response = llm(
"JSON list of name strings of attractions in SF:",
grammar=grammar, max_tokens=-1
)
import json
print(json.dumps(json.loads(response['choices'][0]['text']), indent=4))
출력 결과는 다음과 같이, 샌프란시스코에 있는 놀이 시설을 보여준다.
[
{
"address": {
"country": "US",
"locality": "San Francisco",
"postal_code": 94103,
"region": "CA",
"route": "Museum Way",
"street_number": 151
},
"geocode": {
"latitude": 37.782569,
"longitude": -122.406605
},
"name": "SFMOMA",
"phone": "(415) 357-4000",
"website": "http://www.sfmoma.org/"
}
]
이와 같이, LLM 출력을 컴퓨터 처리하기 용이한 구조로 생성할 수 있다.
참고로, 여기서 사용한 JSON 문법은 다음과 같이 정형 규칙 언어로 정의된 것을 사용한 것이다.
TEXT TO XML
다음은 XML에서 데이터를 검색하는 방법을 보여준다.
from langchain.output_parsers import XMLOutputParser
from langchain_community.chat_models import ChatAnthropic
from langchain_core.prompts import PromptTemplate
model = ChatAnthropic(model="claude-2", max_tokens_to_sample=512, temperature=0.1)
actor_query = "Generate the shortened filmography for Tom Hanks."
output = model.invoke(
f"""{actor_query}
Please enclose the movies in <movie></movie> tags"""
)
print(output.content)
JSON RAG 처리
이 예시는 특정 웹사이트 내용과 제품 정보가 포함된 JSON파일(예제 다운로드)를 RAG 한다. 예제파일은 input_json 폴더를 만들고, 그 아래 복사한다.
이제 다음과 같이 랭체인를 이용해 JSON 파일을 RAG 처리한다.
from langchain_community.vectorstores import Chroma
from langchain_community.chat_models import ChatOllama
from langchain_community.embeddings.fastembed import FastEmbedEmbeddings
from langchain_community.document_loaders import WebBaseLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain_core.prompts import ChatPromptTemplate
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain.chains import create_retrieval_chain
from langchain_core.documents import Document
import sys, os, json
class ChatWebDoc:
vector_store = None
retriever = None
chain = None
def __init__(self):
self.model = ChatOllama(model="mistral:instruct")
#Loading embedding
self.embedding = FastEmbedEmbeddings()
self.text_splitter = CharacterTextSplitter(chunk_size=1024, chunk_overlap=100)
self.prompt = ChatPromptTemplate.from_messages(
[
("system",
"""You are an assistant for question-answering tasks. Use only the following
context to answer the question. If you don't know the answer, just say that you don't know.
CONTEXT:
{context}
"""),
("human", "{input}"),
]
)
def ingest(self, url_list):
#Load web pages
docs = WebBaseLoader(url_list).load()
chunks = self.text_splitter.split_documents(docs)
#Create vector store
vector_store = Chroma.from_documents(documents=chunks,
embedding=self.embedding, persist_directory="./chroma_db")
def ingest_json(self, input_folder):
all_chunks = []
for filename in os.listdir(input_folder):
if filename.endswith('.json') == False:
continue
file_path = os.path.join(input_folder, filename)
with open(file_path, 'r', encoding='utf-8') as file:
dataset = json.load(file)
for data in dataset:
text = json.dumps(data) if isinstance(data, dict) else str(data)
document = Document(page_content=text, metadata={"source": "local"})
all_chunks.append(document)
vector_store = Chroma.from_documents(documents=all_chunks,
embedding=self.embedding,
persist_directory="./chroma_db")
def load(self):
vector_store = Chroma(persist_directory="./chroma_db",
embedding_function=self.embedding)
self.retriever = vector_store.as_retriever(
search_type="similarity_score_threshold",
search_kwargs={
"k": 3,
"score_threshold": 0.5,
},
)
document_chain = create_stuff_documents_chain(self.model, self.prompt)
self.chain = create_retrieval_chain(self.retriever, document_chain)
def ask(self, query: str):
if not self.chain:
self.load()
result = self.chain.invoke({"input": query})
print(result["answer"])
for doc in result["context"]:
print("Source: ", doc.metadata["source"])
def build():
w = ChatWebDoc()
w.ingest([
"https://www.webagesolutions.com/courses/WA3446-comprehensive-angular-programming",
"https://www.webagesolutions.com/courses/AZ-1005-configuring-azure-virtual-desktop-for-the-enterprise",
"https://www.webagesolutions.com/courses/AZ-305T00-designing-microsoft-azure-infrastructure-solutions",
])
w.ingest_json("./input_json")
def chat():
w = ChatWebDoc()
w.load()
build()
while True:
query = input(">>> ")
if len(query) == 0:
continue
if query == "/exit":
break
w.ask(query)
if len(sys.argv) < 2:
chat()
elif sys.argv[1] == "--ingest":
build()
실행 결과는 다음과 같다.
결론
앞서 다양한 방식으로 목적 달성을 위해 솔류션 테크 트리를 탐색하고, 시도했으나, 아직 완벽하지 않고, 공개된 자료들도 에러가 많이 발생하였다. 앞에 예시된 내용은 그 테크트리 중 일부 성공한 것만 기술한 것이다.
사실, LLM 튜닝은 리소스 제약으로 인해 대부분 열악한 인프라 환경?인 국내에서는 RAG를 대안으로 선택하지만, 한계가 명확하다. 사실, LLM 튜닝 및 풀튜닝 할 수 있는 능력있는 국내 IT기업은 거의 없다고 봐야 한다. 참고로, 라마3 8B모델 훈련에는 H100 GPU(80GB) 하나를 사용했을 때 학습 기간은 1,388.9 개월이 걸린다(계산방법 참고 - Transformer Math 101).
Llama-3-8B-Instruct | NVIDIA NGC (How large an LLM can I train from scratch on a single A100 GPU with 80Gb memory?)
이보다 더 적은 파라메터수를 가진 라마2 7B 모델 훈련에는 114.5개월(약 10년)/A100 GPU 이 소요된다.
대안인 파인튜닝, 양자화, LoRA방식은 많은 학습 데이터(최소 수만건 이상 잘 정재된 데이터셋)이 필요하고, 그 결과 또한 한계가 있다(계산된 logit 확률로만 이야기하는 논문이나 리더보드 발표 결과를 믿을 수 없음. 실제 결과는 사용하기 어려운 경우가 많다). 이런 이유로 RAG를 하지만, 이 또한, 연구가 필요한 이슈들이 많다.
레퍼런스
- LangChain Structured Output Parser Using OpenAI | by Cobus Greyling | Apr, 2024 | Medium
- Output Parsers in LangChain: Pydantic (JSON) Parsing | by Shubham Shardul | Medium
- Query Pipeline for Advanced Text-to-SQL - LlamaIndex
- Creating a Natural Language to SQL System using Llama Index | by Plaban Nayak | AI Planet
- (23) Generative AI Part 10 Text to sql using Langchain ,OpenAI and Llama Index | LinkedIn
- How to use JSON files in vector stores with Langchain | how.wtf
- How to Buy Mistral AI Stock in 2024 | The Motley Fool
- Build a Local RAG Application | 🦜️🔗 Langchain
- Build RAG Application Using a LLM Running on Local Computer with Ollama and Langchain | by (λx.x)eranga | Effectz.AI | Medium
- JSON-based Agents With Ollama & LangChain | by Tomaz Bratanic | Neo4j Developer Blog | Medium
- langchain/templates/neo4j-semantic-ollama at master · langchain-ai/langchain (github.com)
- blogs/llm/ollama_semantic_layer.ipynb at master · tomasonjo/blogs (github.com)
- This application serves as a demonstration of the integration of langchain.js, Ollama, and ChromaDB
JSON에만 특화된 RAG는 다음과 같다.
부록: RAG 기반 SQL 코딩 에이전트 개발
- Simple Wonders of RAG using Ollama, Langchain and ChromaDB | HackerNoon
- JSON-based Agents With Ollama & LangChain | by Tomaz Bratanic | Neo4j Developer Blog | Medium
- sql-ollama | 🦜️🔗 LangChain
- LlamaIndex - LlamaIndex
- LangChain: A Complete Guide & Tutorial (nanonets.com)
부록: 구조적(structured) 출력 RAG 및 파인튜닝 모델 개발
- LangChain: A Complete Guide & Tutorial (nanonets.com)
- Llama.cpp | 🦜️🔗 LangChain
- Using llama-cpp-python grammars to generate JSON | Simon Willison’s TILs
- Finetune LLM to convert a receipt image to json or xml | by Minyang Chen | Medium
- Fine Tuning Llama2 for Better Structured Outputs With Gradient and LlamaIndex - LlamaIndex
- run-llama/finetune-embedding: Fine-Tuning Embedding for RAG with Synthetic Data (github.com)
- Fine-Tuning LLaMA 2: A Step-by-Step Guide to Customizing the Large Language
댓글 없음:
댓글 쓰기