Notice
Recent Posts
Recent Comments
Link
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Tags more
Archives
Today
Total
관리 메뉴

냥냠

[교육] LangChain & LangGraph 실습 - AI agent 만들기 - (2) 본문

AI

[교육] LangChain & LangGraph 실습 - AI agent 만들기 - (2)

sueeee-e 2025. 6. 28. 17:38

 

 

 

실습 - 문서 기반 AI 챗봇 만들기

 

(1) LangChain 활용

 

1. LLM 모델 설정:

  • ChatOpenAI 등 사용할 대규모 언어 모델을 초기화합니다. (API 키 연동 포함)
llm = ChatOpenAI(model_name="gpt-4o-mini",
                 temperature=0.,
                 api_key=openai_key
                 )

 

2. 문서 로딩 및 전처리:

  • 파일 로더 (PDFLoader 등): .pdf, .txt, .docx 등 외부 문서 파일을 불러옵니다.
  • 텍스트 스플리터 (RecursiveCharacterTextSplitter 등): 로드된 긴 문서를 LLM이 처리하기 적합한 작은 단위(청크)로 분할합니다.
from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

dir = "./files/"
file_list = os.listdir(dir) #폴더 내의 모든 파일 목록 가져오기

loader = PyPDFLoader(dir+file_list[0])

splitter = RecursiveCharacterTextSplitter(chunk_size=300, 
                                          chunk_overlap=50, # 앞뒤로 50자씩 겹치게
                                          separators=["\n\n"])
                                          
split_docs = loader.load_and_split(text_splitter=splitter)

 

3. 정보 색인화 (임베딩 및 저장):

  • 임베딩 모델 (OpenAIEmbeddings 등): 분할된 텍스트 청크들을 벡터(숫자 배열)로 변환합니다.
  • 벡터 스토어 (FAISS, Chroma 등): 임베딩된 벡터들을 저장하고 관리하여 효율적인 의미 기반 검색을 가능하게 합니다.
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import FAISS

embeddings = OpenAIEmbeddings()
vector_store = FAISS.from_documents(embedding=embeddings, documents=split_docs)

 

4. 검색기 구성:

  • 리트리버 (as_retriever() 등): 벡터 저장소에서 질의와 유사한 문서를 찾아 반환하는 검색기 객체를 생성합니다.
# 리트리버 생성
retriever = vector_store.as_retriever()

 

5. 프롬프트 및 체인 구성:

  • 프롬프트 템플릿 (PromptTemplate, ChatPromptTemplate): 사용자 질문과 검색된 문서를 조합하여 LLM에 전달할 최종 프롬프트 양식을 정의합니다. (이때, 검색된 문서를 프롬프트에 삽입하는 문서 포맷팅 과정이 포함될 수 있습니다.)
  • 체인 생성 (LLMChain, RetrievalQA 또는 LCEL): 리트리버로 문서를 검색하고, 그 결과를 프롬프트에 넣어 LLM에 전달한 후 응답을 받는 전체 흐름을 연결하는 체인을 구축합니다.

*LangChain은 LangChain Expression Language (LCEL)이라는 문법을 사용하여 구성요소를 유연하게 연결함

from langchain_core.prompts import ChatPromptTemplate

# 프롬프트 템플릿
prompt = ChatPromptTemplate([
    ("system", "문서 : {context}\n\n"
               "당신은 문서에서 원하는 정보를 찾는 10년차 데이터 전문가입니다. 항상 문서를 기반하여 응답해주세요\n"
               "문서에서 응답을 찾을 수 없는 경우 '문서에서 응답을 찾을 수 없습니다.' 라고 답변하세요."), 
    ("user", "{query}")

])

# 문서 포맷팅
# 검색기(retriever)로부터 반환된 여러 개의 문서 객체(Document 리스트)를 LLM 프롬프트에 삽입하기 적합한 하나의 문자열 형태로 변환하는 함수
def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

# 체인 생성
# retriever -> prompt -> llm  
# RunnableLambda는 일반 파이썬 함수를 LangChain의 Runnable 객체로 만들어 체인에 연결할 수 있게 함
# RunnablePassthrough(): 사용자로부터 받은 query를 그대로 다음 단계로 전달

chain = {"context": retriever | RunnableLambda(format_docs),
         "query": RunnablePassthrough()} | prompt | llm

 

6. 질문 & 답변:

  • 사용자 질문을 체인에 invoke()하여 문서 기반의 답변을 받습니다.
query = "2025년엔 어떤 부분을 준비해야할까?"
result = chain.invoke(query)
print(result.content)

 

(2) LangGraph 활용

 

LangGraph 요소 : State, Node, Edge

 

- State : 에이전트가 현재 어떤 단계에 있는지를 나타내는 정보

- Node : 하나의 독립적인 작업 단위

- Edge : 노드 간의 흐름을 정의하는 연결선

 

1. 기본 컴포넌트 설정:

  • LLM 모델 생성: ChatOpenAI와 같은 대규모 언어 모델을 초기화합니다.
  • 데이터 준비 및 검색 도구 설정: 파일 로더 (예: PDF), 텍스트 스플리터, 임베딩 모델, 벡터 저장소, 리트리버 등을 설정합니다. 이들은 그래프의 노드에서 사용될 외부 도구 역할을 합니다.
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain.document_loaders import PyPDFLoader

llm = ChatOpenAI(model="gpt-4o-mini",
                 temperature=0.,
                 api_key = openai_key)
                 
file_path = "./files/"

def embedding_file(file_path):
    splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
        chunk_size=300,
        chunk_overlap=50,
        separators=["\n\n"],
    )

    loader = PyPDFLoader(file_path)
    docs = loader.load_and_split(text_splitter=splitter)

    embeddings = OpenAIEmbeddings()
    vector_store = FAISS.from_documents(docs, embeddings)
    retriever = vector_store.as_retriever()

    return retriever

retriever = embedding_file(file_path+file_list[1])

 

2. 그래프 상태 정의:

  • 그래프의 각 노드 간에 전달될 정보(예: 사용자 질문, 검색된 문서, LLM 답변, 대화 기록 등)를 담을 상태(State) 스키마를 정의합니다.
# 그래프 상태 정의
class State(TypedDict):
    messages : Annotated[list, add_messages]
    document : Annotated[Document, "Retrieve Response"]
  1. 노드 정의 및 내부 체인 구성:
    • 프롬프트 및 LLM 체인 정의: 각 노드(예: "문서 검색" 노드, "답변 생성" 노드)에서 수행할 구체적인 작업(예: retriever를 실행하거나 프롬프트와 LLM을 연결하는 체인)을 정의합니다.
    • 적제된 메세지 객체에 [-1] 호출 시 최신의 메세지를 가져올 수 있다.
# 그래프 내의 특정 "노드"에서 실행될 작업을 정의하는 부분
# 사용자 질문을 받아 리트리버를 통해 문서를 검색하고, 
# 그 문서를 LLM이 활용할 수 있도록 포맷팅하여 그래프의 상태(state)에 저장하는 역할
def create_document(state:State):
    docs = retriever.invoke(state["messages"][-1].content)

    docs = "\n\n".join(doc.page_content for doc in docs)

    return {"document":docs}

# 체인 활성화 (+프롬프트 작성)
def response(state:State):
    prompt = ChatPromptTemplate.from_messages([
                ("system", 
                """
                context : {context}

                당신은 언제나 고객에게 최선을 다해 답변을 하며 말투는 굉장히 친근합니다. 직업은 전문 상담원입니다. 답변 시, 아래의 규칙을 지켜야만 합니다.
                ---
                ### 규칙 ###
                1. 주어진 context만을 이용하여 답변해야합니다. 
                2. 주어진 context에서 답변을 할 수 없다면 "해당 문의는 010-2255-3366으로 연락주시면 도와드리겠습니다. 영업 시간은 오전 10시-오후 6시입니다." 라고 대답하세요.
                3. 문자열에 A1, A2, A11, A22 등 필요 없는 문자는 제거한 뒤 출력합니다.
                4. 항상 친절한 말투로 응대합니다.
                5. 하이퍼 링크를 그대로 출력합니다. 대소문자를 명확하게 구분하세요. 아래 예시를 참고하여 서식을 맞추세요.
                **하이퍼 링크 예시**
                5-1. [스타벅스 구역삼사거리점](https://naver.me/FV7K6xTM) 입니다.
                5-2. [화목순대국](https://naver.me/FQVGK6TZ) 입니다.
                5-3. [모두의연구소 역삼캠퍼스](https://naver.me/GMvc9Hv5) 입니다.
                ---
                """),
                ("human", "{query}")
            ])
    
    chain = prompt | llm

    return {"messages": chain.invoke({"context" : state["document"],
                                    "query": state["messages"][-1]})}
  1. 그래프 빌더 생성 및 노드/엣지 추가:
    • Graph 또는 StateGraph 빌더를 생성합니다.
    • 노드 추가: 정의된 각 작업 단계를 노드로 추가합니다.
    • 엣지 추가: 각 노드 간의 전환 조건(어떤 노드에서 다음 어떤 노드로 이동할지)을 엣지(Edge)로 연결하여 워크플로우를 정의합니다.
# 그래프 빌더 생성
graph_builder = StateGraph(State)

# 노드 및 엣지 추가
graph_builder.add_node("create_document", create_document);
graph_builder.add_node("response", response);

graph_builder.add_edge(START, "create_document");
graph_builder.add_edge("create_document", "response");
graph_builder.add_edge("response", END);
  1. 그래프 컴파일 및 실행:
    • 정의된 노드와 엣지를 바탕으로 그래프를 컴파일하여 실행 가능한 워크플로우로 만듭니다.
    • invoke() 또는 stream() 등으로 컴파일된 그래프에 메시지를 입력하여 실행하고, 최종 답변을 받습니다. (예: input = {"messages": [("user", "질문 내용")]})
# 그래프 컴파일
graph = graph_builder.compile()

# 질문 & 답변
# -1 값에 답변, -2 값에 질문이 담겨있다.
result = graph.invoke({'messages':("user", "언제 개강하나요?")})

result["messages"][-1].content

 

 

 

프롬프트 규칙에 문서에 없는 정보를 질문하면 대답을 정해주었기 때문에 근거 없는 정보에 대해서는 답을 해주지 않는다는 점에서 

RAG를 사용함으로써 할루시네이션을 감소하고 답변의 신뢰성을 높일 수 있다.


https://conference.etnews.com/conf_info.html?uid=398

 

LangChain + LangGraph Bootcamp: AI 에이전트를 만드는 하루

AI 에이전트는 챗봇, 문서 요약, 데이터 정리 등 일상과 업무에서 큰 도움을 줄 수 있는 기술입니다. 이 워크숍은 프 로그래밍과 AI에 처음 도전하는 초급자를 위해 설계되었으며, 복잡한 이론 대

conference.etnews.com