SKN/Remind

sk네트웍스 family AI 캠프 11기 4월 4주차 회고록

claovy☘️ 2025. 4. 28. 15:23

회고기간 : 2025.04.21월~2025.04.25금

이번 주차는 langchain에 대해 배웠다

 

LangChain

LangChain 은 LLM의 기능을 확장하고 체계화하여 복잡한 애플리케이션을 구축할 수 있도록 지원하는 프레임워크다.
여러 프롬프트, 도구, 데이터 소스를 체인으로 연결해 효율적으로 작업을 수행할 수 있다.

1. Prompt Template

프롬프트 템플릿은 반복적인 작업에서 재사용 가능한 프롬프트를 설계하고, 동적 입력 변수를 통해 작업을 수행하는 구조

  • 자동화된 입력 구성
  • 대화형 응답
  • 샘플 기반 학습
  • 결과 파싱 
키를 활용하기 전 환경변수를 로드하는 것은 필수! 
from dotenv import load_dotenv
load_dotenv()
프롬프트 예시 (few shot)
from langchain.prompts import FewShotPromptTemplate

examples = [
    {"question" : "2 + 2 는 무엇인가요?", "answer" : "2 + 2 = 4"}, 
    {"question" : "3 + 4 는 무엇인가요?", "answer" : "3 + 4 = 7"}
]

example_prompt = PromptTemplate(
    template="Q : {question} \nA : {answer}",
    input_variables=['question', 'answer']
)

fewshot_prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=example_prompt,
    prefix="다음 계산 문제를 해결하세요",
    suffix="Q : {question} 은 무엇인가요?\nA:",
    input_variables=['question']
)

print(fewshot_prompt.format(question='34 + 78'))
<출력>

다음 계산 문제를 해결하세요
Q : 2 + 2 는 무엇인가요?
A : 2 + 2 = 4

Q : 3 + 4 는 무엇인가요?
A : 3 + 4 = 7

Q : 34 + 78 은 무엇인가요?
A:

2. Output Parser

Output Parsers 는 LLM이 생성한 텍스트 출력을 특정 형식으로 변환하거나 처리하는 데 사용된다.
이는 모델의 응답을 해석하고, 이를 구조화된 데이터로 바꿔 후속 작업에 활용하기 위해 설계된다.
ex ) LLM 응답이 name:John, Age:30 이라면 -> 딕셔너리 형태로 바꿔줌

 

[output parser의 주요 메서드]

  • get_format_instructions()
    • 언어 모델이 출력해야 할 정보의 형식을 정의하는 지침(instruction) 을 제공
  • parser()
    • 언어 모델의 출력(문자열로 가정)을 받아들여 이를 특정 구조로 분석하고 변환

[output parser의 종류]

  1. BaseOutputParser: Output Parsers의 기본 클래스, 커스텀 파서 구현 시 사용한다.
  2. CommaSeparatedListOutputParser: 콤마로 구분된 문자열을 리스트로 변환한다.
  3. RegexParser: 정규식을 사용해 특정 패턴을 추출하고 키-값 형태로 반환한다.
  4. StructuredOutputParser: 출력의 JSON 또는 구조화된 형식을 강제한다.
  5. PydanticOutputParser: Pydantic 모델을 기반으로 출력 검증 및 변환한다.
  6. MarkdownOutputParser: 마크다운 형식의 텍스트에서 데이터를 추출한다.
# OutputParser 인스턴트 및 PromptTemplate 인스턴스 생성 
from langchain_openai import ChatOpenAI
from langchain.output_parsers import CommaSeparatedListOutputParser

output_parser = CommaSeparatedListOutputParser()
format_instructions = output_parser.get_format_instructions()     # 파싱해주는 형식 정보 

prompt_tpl = PromptTemplate(
    template="{subject} 5개의 팀을 알려주세요. \n형식 지정 : {format}",
    input_variables=['subject'],                                  # 사용자 입력 변수 
    partial_variables={'format' : format_instructions}            # 고정 설정 변수 
)

# 프롬프트 생성
query = "한국의 프로야구팀"
prompt = prompt_tpl.format(subject=query)
print(prompt)

# open ai api를 사용하기 위한 인스턴스 생성 및 요청 처리 
model = ChatOpenAI(
    model_name="gpt-4o-mini",
    temperature=0,
    max_tokens=2048
)

response = model.invoke(prompt) # prompt에 질의 보냄 

# content 출력하기
print(response.content)
print(type(response.content))

# 형식 변경해주기
print(output_parser.parse(response.content))
print(type(output_parser.parse(response.content)))
<출력>
['두산 베어스', 'LG 트윈스', '삼성 라이온즈', '키움 히어로즈', 'NC 다이노스']
<class 'list'>

 

3. Chain

체인은 여러 작업 단계를 순차적으로 연결해 작업을 수행하는 LangChain의 핵심 개념이다. 각 체인은 다양한 구성 요소를 단계별로 연결하여 더 복잡한 태스크를 처리할 수 있도록 한다.

 

[output parser의 종류]

  1. Simple Chains
    • 하나의 입력과 출력으로 구성된 가장 단순한 체인
    • 예: 입력 프롬프트를 생성하고, LLM의 응답을 출력
  2. Sequential Chains
    • 여러 단계를 순차적으로 실행하는 체인
    • 각 단계의 출력이 다음 단계의 입력으로 사용
    • 예: 텍스트 요약 → 질문 생성
  3. Conditonal Chains
    • 특정 조건에 따라 다른 체인을 실행한다
    • 예: 입력 유형에 따라 다양한 프롬프트를 사용
  4. Memory Chains
    • 대화형 응용 프로그램에 적합하며, 이전 데이터를 저장 및 참조한다
    • 예: 챗봇 응용 프로그램
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder  
from langchain_core.runnables.history import RunnableWithMessageHistory

prompt = ChatPromptTemplate.from_messages([
    ('system', '너는 {skill}을 잘하는 AI 어시스턴스야.'),
    MessagesPlaceholder(variable_name='history'),   # 메세지 목록을 전달하는 데 사용 
    ('human', '{query}')
])

model = ChatOpenAI(model_name='gpt-4o-mini', temperature=0.5)
chain = prompt | model ##### 프롬프트와 model을 순서대로 작동되도록 연결

 

4. Agent & Tool

에이전트(Agent) 는 LLM이 외부 도구와 상호작용하며 복잡한 작업을 처리하는 시스템이다.
계산, 데이터 검색, API 호출 등 여러 단계를 자동으로 실행할 수 있다.

도구(Tool) 는 에이전트가 외부 작업(계산, 데이터 검색, API 호출 등)을 수행할 수 있도록 돕는 기능이다.에이전트는 도구를 활용해 LLM의 한계를 보완하고 복잡한 작업을 처리한다.

 

from langchain.agents import AgentType, initialize_agent, load_tools

tools = load_tools(['wikipedia', 'llm-math'], llm=model)     # load_tools : 툴 사용 
                                                             # 위키피디아와 수학연산자(llm 기반)

agent = initialize_agent(      # initialize_agent : agent 생성 
    tools,
    model,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,    # AgentType : 프롬프트 추론 방식
    handle_parsing_error=True,
    verbose=True
)

 

사용예시 : agent.invoke('3+4'는 ?)

 

5. Memory

메모리(Memory) 는 LLM이 대화나 작업의 문맥을 유지하도록 지원하는 기능으로, 이전 대화 내용이나 작업 결과를 기억해 일관성 있는 응답을 생성한다.
chain_with_history = RunnableWithMessageHistory(
    chain,
    get_session_history=get_by_session_id,	# 세션 기록을 가져오는 함수
    input_messages_key='query',		# 입력 메시지의 키
    history_messages_key='history'	# 기록 메시지의 키 
)

 

 

이러한 방식으로 session_id를 입력하면 이전 대화를 불러와 대화가 가능하다.

 

 

6. RAG ⭐⭐⭐⭐

RAG (Retrieval-Augmented Generation) 는 Vector Database 와 LLM을 결합하여 정보 검색생성을 통합하는 방법론

 

[ RAG 워크플로우 ] == 크게 두가지로 나눠 진행된다.

< Indexing Phase >

1. 도큐먼트 로드

from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader('./data/snow-white.pdf')
documents = loader.load()

# 개행문자 제거
for doc in documents:
    doc.page_content = doc.page_content.replace('\n', ' ')

documents

 

2. 텍스트 분할

from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=100,     # 각 chunk의 최대 문자 수 (기본값: 1000)         
    chunk_overlap=20    # 인접한 텍스트 조각 간 겹치는 문자 수 (기본값: 200)
)                       # seperators: 텍스트 분할 구분자 우선순위 (기본값: ['\n\n', '\n', ' ', ''])

docs = splitter.split_documents(documents)
docs

 

3. 임베딩(벡터 스토어 저장)

# 임베딩 모델 생성
from langchain_openai.embeddings import OpenAIEmbeddings

embedding_model = OpenAIEmbeddings(model='text-embedding-3-small')

!pip install langchain-chroma
from langchain_chroma.vectorstores import Chroma

vector_store = Chroma.from_documents(docs, embedding_model)
# Retriever을 사용한 유사도 기반 검색
query = "백설공주와 왕비 중에 누가 더 아름답나요?"

# vector store 직접 조회 
retrievals = vector_store.similarity_search_with_score(query)
retrievals

# Retriever를 사용한 검색
retriever = vector_store.as_retriever(
    search_type='similarity',
    search_kwargs={'k': 3}
)
retriever_result = retriever.batch([query])
retriever_result

 

 

< Retrieval and Generation Phase >

1. 프롬프트 생성

from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate([
    ("system", "당신은 어린 아이에게 꿈과 희망을 심어주는 유치원 교사입니다. 질문하는 아이에게 최대한 호응하면서 context 기반으로만 답변해 주세요."), 
    ("user", """
어린이의 질문에 context만을 이용해 답변하세요.
질문: {query}
context: {context}
""")
])

prompt.invoke({'query': query, 'context': retrievals})

 

2. LLM 모델 생성

from langchain_openai import ChatOpenAI

model = ChatOpenAI(
    model_name='gpt-4o-mini',
    temperature=0.5
)

 

3. 체인 생성 

from langchain_core.output_parsers import StrOutputParser

# context 생성
query = "왕비가 백설공주에게 먹인 것은 무엇인가요?"
retrievals = retriever.batch([query])
context_text = "\n".join([doc.page_content for doc in retrievals[0]])

chain = prompt | model | StrOutputParser()

 

4. 사용

chain.invoke({'query': query, 'context': context_text})

 

<출력>

'왕비가 백설공주에게 먹인 것은 맛있어 보이는 사과였어요. 그런데 그 사과에는 독이 발라져 있었답니다. 왕비는 백설공주를 속이기 위해 과일 장수로 변장하고 사과를 가지고 갔어요. 정말 무서운 이야기지만, 백설공주와 난쟁이들이 결국에는 잘 이겨낼 거예요!'

 

7. CoT

Chain of Thought (CoT) 는 LLM이 복잡한 문제를 해결하기 위해 단계적 사고 과정을 명시적으로 전개하도록 설계된 프롬프트 전략이다.
이는 수학 문제, 논리 퍼즐, 과학적 추론 등 복잡한 작업에서 정확성을 향상시킨다.

 

💡 CoT의 핵심은 "단계적 사고 과정을 밟는 것" 이다. 따라서 프롬프팅을 다르게 적용해보도록 하자

# 일반 프롬프트
prompt = '125 x 31은 얼마인가요?'

# Few-shot COT 적용 프롬프트
few_shot_cot = """
Q: 123 x 31 은 얼마인가요?
A: 
1. 123를 분해합니다: 123 = 100 + 20 + 3
2. 각 항을 31과 곱합니다:
   - 100 x 31 = 3100
   - 20 x 31 = 620
   - 3 x 31 = 93

3. 이제 이 결과들을 더합니다:
   - 3100 + 620 + 93

4. 계산을 진행하면:
   - 3100 + 620 = 3720
   - 3720 + 93 = 3813 

따라서, 123 x 31 = 3813입니다.

Q : 789 x 56 은 얼마인가요?
A :

"""
# 일반 프롬프트 응답
res  = client.chat.completions.create(
    model = 'gpt-4',
    messages=[{"role" : "user", "content":prompt}]
)

# 퓨샷 프롬프트 응답 (CoT)
cot_res = client.chat.completions.create(
    model = 'gpt-4',
    messages=[{"role" : "user", "content" : cot_prompt}]
)

# res로 응답 받아오기
print(res.choices[0].message.content)

# cot_res로 응답 받아오기 
print(cot_res.choices[0].message.content)
<출력>

# 일반 프롬프트 응답 (res)
125 x 31 = 3875

# Few shot 프롬프트 응답(cot)
125 x 31을 계산하려면, 다음의 단계를 따릅니다.
1. 먼저, 1 x 125을 계산하면, 그 결과는 125입니다.
2. 다음으로, 3 x 125는 375이며, 이는 실제로는 30 x 125입니다.
3. 마지막으로, 이 두 결과를 더하면 답이 나옵니다: 125 (1 x 125의 결과) + 3750 (30 x 125의 결과) = 3875
따라서, 125 x 31은 3875입니다.

 


💡 Keep

교육을 듣기 전, 학교에서 langchain 관련 프로젝트를 했었기 때문인지 langchain이 조금은 익숙하게 다룰 수 있어 좋았다 (아직 덜 까먹은 것 같아 기분이 좋았다ㅋㅋ..)그치만 복습은 제때제때 해야함을 매번 깨닫는다. 이제는 한번 놓치면 돌아올 수 없는 강을 건너버리는...

 

⚠️ Problem

1. 개인 프로젝트에서 runpod으로 gpu 환경을 돌리는데 pod 관리를 잘 못하는 것 같다. 

프로젝트 설계에서 뼈대를 잡고 로직을 구현한 후 기동을 시켜야하는데 계속 오류가 나니까 이도 저도 못하면서 gpu만 날린다

큰 그림을 그리고 프로세스를 잡아가면서 개발을 해야함을 느끼는 요즘이다.

 

2. langchain 내용만 익숙하다뿐이지 새로운 패키지나 모델이 있으면 바로바로 적용하는 데에 시간이 걸리는 것 같다 

연습해야지 머

 

🔥 Try

복습과 운동 꾸준히!!

(진짜 심각한 문젠데 안그러다가 요즘 버스 탈때마저도 헉헉댄다,,, 체력관리가 시급하다)