260105 책리뷰 러닝 랭체인
05 Jan 2026
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
- 채팅모델이 생성한 컨텐츠
- system
p46 채팅모델 호출
model = ClaudeAI("sonet...")
prompt = [HumanMsg("프랑스의 수도는 어디인가?")]
model.invoke(prompt)
AIMsg(content="프랑스의 수도는 파리입니다.")
- 채팅메시지 인터페이스
- HumanMsg
- 사용자 역할
- 인간관점의 메시지
- AIMsg
- 어시스턴트 역할
- AI의 관점 메시지
- SystemMsg
- 시스템 역할
- AI가 준수할 지침 설정
- ChatMsg
- 임의의 역할
- HumanMsg
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()
물리 프롬프트 사용
의미론적 라우팅 결과 : ...