황현동 블로그 개발, 인생, 유우머

260105 책리뷰 러닝 랭체인

Tags:

p43 환경구축

pip install langchain langchain-claude langchain-community
pip install langchain-text-spliiter
pip install langchain-postgres

export CLAUDE_API_KEY=xxx

p44 기본 LLM 호출

model = ClaudeAI(model="...")
model.invoke("하늘이")
푸릅니다.

p45 LLM 옵션

  • temperature
    • 출력생성에 사용하는 샘플링 알고리즘
    • 낮은값 : 0.1
      • 예측가능한 결과
    • 높은값 : 0.9
      • 창의적인 결과
  • max_tokens
    • 출력크기와 비용제한
    • LLM이 자연스러운 마무리에 도달하기전 출력을 중단
  • 역할
    • system
      • 사용자 질문에 답변하는 지시사항
    • user
      • 사용자의 쿼리
      • 사용자의 컨텐츠
    • assistant
      • 채팅모델이 생성한 컨텐츠

p46 채팅모델 호출

model = ClaudeAI("sonet...")
prompt = [HumanMsg("프랑스의 수도는 어디인가?")]
model.invoke(prompt)
AIMsg(content="프랑스의 수도는 파리입니다.")
  • 채팅메시지 인터페이스
    • HumanMsg
      • 사용자 역할
      • 인간관점의 메시지
    • AIMsg
      • 어시스턴트 역할
      • AI의 관점 메시지
    • SystemMsg
      • 시스템 역할
      • AI가 준수할 지침 설정
    • ChatMsg
      • 임의의 역할

p47 시스템 메시지 적용, 채팅모델 호출

model = ChatClaudeAI()
sys_msg = SysMsg("역할 : 문장끝에 느낌표 3개를 붙여서 대답해 주세요.")
human_msg = HumanMsg("프랑스의 수도는 어디인가요?")
model.invoke([sys_msg, human_msg])
AIMsg("파리 입니다!!!")

p48 프롬프트 템플릿

template = PromptTemplate.from_template("""
    아래 작성한 context 를 기반으로 question 에 대답하세요.
    제공된 정보로 대답할 수 없는 question 이라면, "모르겠어요." 라고 대답하세요.
    
    context : {context}
    
    question : {question}
    
    answer : 
""")

prompt = template.invoke({
    "context": """
        LLM은 NLP분야의 발전을 이끕니다.
        LLM은 작은 모델보다 우수한 성능입니다.
        개발자들은 Hugging Face의 transformer 라이브러리를 활용하거나 openai, claude의 거대모델을 활용할 수 있습니다.
    """,
    "question": """
        거대모델은 어디서 제공하나요?
    """
})

model = ClaudeAI("sonet...")

completion = model.invoke(prompt)

print(completion)
Huggin Face의 transformer 라이브러리를 사용하거나 openai claude 의 라이브러리를 통해 LLM을 이용할 수 있습니다.

p52 역할에 따른 동적 프롬프트

template = ChatPromptTemplate.from_msg([
    ("system", """
        아래 작성한 context 를 기반으로 question 에 대답하세요.
        제공된 정보로 대답할 수 없는 question 이라면, "모르겠어요." 라고 대답하세요.
    """),
    ("human", "context:{context}"),
    ("human", "question:{question}"),
])

template.invoke({
    "context": """
        LLM은 NLP분야의 발전을 이끕니다.
        LLM은 작은 모델보다 우수한 성능입니다.
        개발자들은 Hugging Face의 transformer 라이브러리를 활용하거나 openai, claude의 거대모델을 활용할 수 있습니다.    
    """,
    "question": """
        거대모델은 어디서 제공하나요?
    """
})

model = ClaudeAI("sonet...")

completion = model.invoke(prompt)

print(completion)
AIMsg(content="Huggin Face의 transformer 라이브러리를 사용하거나 openai claude 의 라이브러리를 통해 LLM을 이용할 수 있습니다.")

p56 json 형식 출력 처리

class Res(BaseModel):
    """사용자의 질문에 대한 답변과 그에 대한 근거를 함께 제공하시요."""

    answer: str
    """사용자의 질문에 대한 답변"""

    justification: str
    """답변에 대한 근거"""


llm = ClaudeAI(model="sonet...", temperature=0)
structured_llm = llm.with_structured_output(Res)
res = structured_llm.invoke("1킬로그램의 벽돌과 1킬로그램의 깃털 중 어느쪽이 더 무겁나요?")
print(res.model_dump_json())
{
  "answer": "1킬로그램의 벽돌과 1킬로그램의 깃털은 동일한 무게.",
  "justification": "둘다 1킬로그램이라서 무게는 같음. 무게는 질량단위."
}

p64 선언형 구성

template = ChatPromptTemplate.from_msg([
    ("system", "역할 : 친절한 어시스턴트"),
    ("human", "{question"),
])

model = ChatClaudeAI()
chatbot = template | model
res = chatbot.invoke({"question": "LLM은 어디서 제공하나요?"})
AIMsg(content="Huggin Face의 transformer 라이브러리를 사용하거나 openai claude 의 라이브러리를 통해 LLM을 이용할 수 있습니다.")

p78 .txt 파일 추출

loader = TextLoader("test.txt", encoding="utf-8")
docs = loader.load()
[Document(page_content="...",metadata={"line_number":0, "source":"test.txt"})]

p79 웹페이지 추출

pip install beautifulsoup4
loader = WebBaseLoader("https://...")
loader.load()

p80 pdf 추출

pip install pypdf
loader = PyPDFLoader("test.pdf")
pages = loader.load()

p82 문서를 청크로 분할

loader = TextLoader("test.txt", encoding="utf-8")
docs = loader.load()

# chunk_size=1_000
#   1_000 자 단위의 청크
# chunk_overlap=200
#   청크간 200자의 중복으로 컨텍스트 유지
splitter = RecursCharTextSplitter(chunk_size=1_000, chunk_overlap=200)
splitted_docs = splitter.split_doc(docs)

p83 텍스트데이타 타입=코드를 청크로 분할

# 코드는 글보다 체계적인 구조를 갖고 있어서 컨텍스트를 유지하기 위해 중복이 필요 없음.
splitter = RecursCharTextSplitter.from_lang(lang=python, chunk_size=50, chunk_overlap=0)
docs = splitter.create_doc([TXT_DATA])

p83 텍스트데이타 타입=마크다운를 청크로 분할

# 마크다운은 글보다 체계적인 구조를 갖고 있어서 컨텍스트를 유지하기 위해 중복이 필요 없음.
splitter = RecursCharTextSplitter.from_lang(lang=markdown, chunk_size=50, chunk_overlap=0)
docs = splitter.create_doc([TXT_DATA])

p86 문서의 임베딩 생성

model = ClaudeAIEmbeddings(model="sonnet...")
embeddings = model.embed_doc(["...", "...", "...", "...", "...", "...", ])

p88 텍스트 파일 로드후 임베딩

loader = TextLoader("test.txt", encoding="utf-8")
docs = loader.load()
splitter = RecursCharTextSplitter(chunk_size=1_000, chunk_overlap=200)
splitted_docs = splitter.split_doc(docs)
model = ClaudeAIEmbeddings(model="sonnet...")
embeddings = model.embed_doc([sd.page_content for sd in splitted_docs])

p102 LLM 을 활용한 문서요약 후 임베딩

CONNECTION_STR = "postgres+psycopg//..."
COLLECION_NAME = "MY_COLLECTION"
embeddings_model = ClaudeAIEmbeddings()

# 문서로드
loader = TextLoader("test.txt", encoding="utf-8")
docs = loader.load()

# 문서분할
splitter = RecursCharTextSplitter(chunk_size=1_000, chunk_overlap=200)
chunks = splitter.split_doc(docs)

prompt_txt = """
    다음 문서의 요약을 생성.
    {doc}
"""

prompt = ChatPromptTemplate.from_template(prompt_txt)
llm = ClaudeAI(temperature=0, model="sonnet...")
summarize_chain = {"doc":lambda x : x.page_content} | prompt | llm | StrOutputParser()
summaries = summarize_chain.batch(chunks, {"max_conccurency":5})

# 백터저장소는 하위청크 인덱싱 사용
vector_store = PGVector(
    embeddings=embeddings_model,
    collection_name=COLLECION_NAME,
    connection=CONNECTION_STR,
    use_jsonb=True,
)

# 상위 문서를 위한 스토리지 레이어
store = InMemStore()
ID_KEY = "MY_DOC_ID"

# 원본 문서를 문서 저장소에 보관하면서 벡터 저장소에 요약을 인덱싱
retriever = MultiVectorRetriever(
    vector_store=vector_store,
    doc_store=store,
    id_key=ID_KEY,
)

# 문서와 동일한 길이 필요
doc_ids = [str(uuid.uuid4()) for _ in chunks]

# 각 요약은 doc_id 를 통해 원본 문서와 연결
summary_docs = [
    Document(page_content=s, metadata={id_key:doc_ids[i]})
    for i, s in enumerate(summaries)
]

# 유사도 검색 위한 벡터저장소에 문서 요약을 추가
retriever.vectorstore.add_doc(summary_docs)

# doc_ids 를 통해 요약과 연결된 문서를 원본 저장소에 저장
retriever.docstore.mset(list(zip(doc_ids, chunks)))

# 벡터 저장소가 요약을 검색
sub_docs = retriever.vectorstore.sim_search("chapter on philosophy", k=2)

# retriever가 더 큰 원본 문서 청크를 반환
retrieved_docs = retriever.invoke("chapter on philosophy")

p107 RAPTOR

  • 트리 형태 검색을 위한 재귀적 추상처리
  • 상위개념을 반영하는 문서요약을 작성
  • 문서의 임베딩, 클러스터링 수행
  • 각 클러스터를 재요약

p108 ColBERT

  • 인덱싱 단계에서 임베딩모델을 사용하면 세부적인 문맥이나 구조정보는 소실 될 수 있음.
  • 압축은 검색에는 유용하지만, 환각을 유발.
  • 해결방법
    • 문서와 질의의 각 토큰에 대한 컨텍스트 임베딩
    • 각 쿼리토큰과 문서토큰의 유사도 산출 평가
    • 모든 질의임베딩과 문서임베딩 간의 유사도중 최대값 추출
    • 이를 모두 합산해 각 문서의 점수를 산정
WIKI_PAGE_TEXT = "미야자키 하야오 위키 내용..."

# 인덱스 설정
RAG.index(
    collection=[WIKI_PAGE_TEXT],
    index_name="미야자키 하야오",
    max_doc_len=180,
    split_doc=True,
)

# 쿼리
res = RAG.search(
    query="미야자키가 설립한 스튜디오는?", 
    k=3,
)

# 랭체인에 전달
retriever = RAG.as_langchain_retriever(k=3)
retriever.invoke("미야자키가 설립한 스튜디오는?")

p117 기존 인덱싱 작업

# 문서로드, 분할
raw_doc = TextLoader("test.txt", encoding="utf-8").load()
text_splitter = RecursCharTextSplitter(chunk_size=1000, chunk_overlap=200)
docs = text_splitter.split_doc(raw_doc)

# 문서에 대한 임베딩 생성
embeddings_model = ClaudeAIEmbeddings()

db = PGVector.from_doc(
    docs,
    embeddings_model,
    collection=CONNECTION_STR,
)

# 벡터저장소에서 문서검색
retriever = db.as_retriever(search_kwargs={"k":2})
query = "고대 그리스 철학사의 주요 인물은?"
res = retriever.invoke(query)

p149 유사한 프롬프트 선택

  • 의미론적 라우팅
PHYSICS_TEMPLATE = """
    당신은 매우 똑똑한 물리학 교수입니다.
    다음 질문에 답하시오 : {query}
"""

MATH_TEMPLATE = """
    당신은 매우 똑똑한 수학 교수입니다.
    다음 질문에 답하시오 : {query}
"""

# 임베딩
embeddings = ClaudeAIEmbeddings()
prompt_templates = [PHYSICS_TEMPLATE, MATH_TEMPLATE]
prompt_embeddings = embeddings.embed_doc(prompt_templates)

# 질문을 프롬프트에 라우팅
QUERY = "블랙홀이란 무엇인가?"
query_embedding = embeddings.embed_query(QUERY)
sim = cos_sim([query_embedding], prompt_embeddings)[0]
most_sim = prompt_templates[similarity.argmax()]
res = PromptTemplate.from_template(most_sim) | ChatClaudeAI() | StrOutputParser()
물리 프롬프트 사용

의미론적 라우팅 결과 : ...