SKN_13th

[플레이데이터 SK네트웍스 Family AI 캠프 13기] 13주차 회고

8000gam 2025. 6. 22. 22:26

💻 What?

  • 6월 16일 (월): Vector DB에서 질문과의 유사도와 함께, 정보의 다양성을 고려하여 검색하는 기법인 MMR(Maximal Marginal Relevance)에 대해 배웠다. 그리고 여러 Vector DB 중 ChromaPinecone을 사용해봤다.
  • 6월 17일 (화): Vector DB에 저장한 정보들을 검색하는 Retrieval에 대해 배웠다. ContextualCompressionRetriever, MultiQueryRetriever 등 여러 Retriever 클래스를 사용하여 정보를 검색하고, 가상의 문서를 생성하여 검색 관련성을 극대화하는 HyDE(Hypothetical Document Embeddings), 검색된 문서들 중에서 제일 유사한 문서들만을 선별하는 MapReduce, 유사도 기반 검색 이후 또 다른 정밀한 모델을 사용하여 검색된 문서들의 순위를 다시 평가하는 Rerank 기법에 대해 배웠다. 저번 주에 배운 chaining을 활용하여 실제 RAG 기반 QA Chain을 구성해보기도 했다.
  • 6월 18일 (수): RAG의 성능 평가 기법으로 RAGAS(Automated Evaluation of Retrieval Augmented Generation)에 대해 배웠다. User Question, Retrieval, LLM Answer, Ground Truth 간의 비교를 통해 네 가지 성능 평가 지표를 이끌어내는데, LLM Answer에 대한 평가 지표로 Faithfulness, Answer Relevancy를, Retrieved Contexts에 대한 평가 지표로 Context Precision, Context Recall을 조회해 볼 수 있다.
  • 6월 19일 (목): Agent에 대해 배웠다. 수업 시간에는 Tavily Search라는 검색 Tool과 WikipediaLoader를 통해 위키백과에서 검색을 수행하는 Custom Tool을 이용하여, User Question에 대해 검색을 통한 정보 보충이 필요하다고 판단되는 경우 tool을 호출하여 답변하는 검색 Agent를 만드는 실습을 진행했다.
  • 6월 20일 (금): LangGraph에 대해 배웠다. LangChain의 여러 구조적인 한계점들을 LangGraph를 통해 해결할 수 있었다. 아울러 3차 미니 프로젝트의 스포일러와 함께 한 주가 마무리되었다.

So What?

도와줘요, LangGraph!!

LangChain을 통해 Advance Rag, Agent 등의 고급 기법을 배우면서 체감되는 것은 구조가 복잡해질 수록 Chain을 구성하는 것이 점점 어려워진다는 것이었다.

# search_web, search_wikipedia, search_menu라는 tool을 정의해서 메뉴 추천 및 해당 메뉴 정보 제공 Chatbot 구현

@tool
def search_menu(query:str) -> dict:
    """
    Tool searching menu data from our Vector Store
    Given query is concerning request of menu recommendation.
    Search and Return most appropriate menu based on the request
    When it comes to seaching data concerning Restaurant Menu, USE THIS TOOL!
    """
    # ...생략...

@tool
def search_web(query:str,  max_results:int=3,  time_range:Literal["day", "week", "month", "year"]|None=None) -> dict:
    """A tool for websearching informations which are real-time or not in DB"""
    # ...생략...

@chain
def wikipedia_search(input_dict:dict) -> dict:
    """Runnable which searches k documents relevant to user query from wikipedia"""
    # ...생략...

search_wikipedia = wikipedia_search.as_tool(
    name='search_wikipedia',
    description=('Tool which is used to search informations from Wikipedia.\n'
                 'Search k documents relevant to user query.\n'
                 'Useful when we need common-sense knowledge or background informations'),
    args_schema = SearchWikiArgsSchema   # Custom BaseModel Schema
)

tool_model = model.bind_tools(tools=[search_web, search_wikipedia, search_menu])
model_chain = agent_prompt | tool_model

@chain
def agent_chain(query:str):
    ai_message = model_chain.invoke({'query':query})
    if ai_message.tool_calls:
        tool_message_list = []
        for tool_call in ai_message.tool_calls:
            tool_name = tool_call['name']
            tool_message = globals()[tool_name].invoke(tool_call)
            tool_message_list.append(tool_message)
        input_data = {'query':query, 'agent_scratchpad':[ai_message, *tool_message_list]}
        result = model_chain.invoke(input_data)
        return result
    return ai_message

 

이런 식의 구조는 단방향적 처리밖에 할 수 없고, 위와 같은 복잡한 구조는 LCEL로 정의하기에 가독성이 떨어질 뿐더러 각 Runnable에서의 I/O 형식을 고려하여 완벽하게 정의하는 것이 굉장히 까다롭다.

 

그러나 LangGraph가 등장하면서 모든 문제를 해결해주었다. LangGraph는 Graph 형식의 구조를 채택하여, Node 간 양방향적 처리를 지원해주고, Conditional Edge를 통해 특정 Node에서의 조건부 분기를 가능하게 해준다. 양방향적 처리는 반복 구조를 만들 수 있게 해주는데, 이로 인해 검색 tool을 이용한다고 하더라도 검색 품질을 만족할 때까지 여러 번 반복하는 과정을 구현할 수 있다. 또한 State라는 구조를 통해 각 Node의 처리 결과들을 저장하여, 특정 Node가 작업을 수행하기 위해 필요한 Input Data를 직전 Node에서 받는 것이 아닌 State에서 선택할 수 있게 한다. 앞서 말한 것 LCEL로 체인을 구성할 때는 직전 단계의 Output과 다음 단계의 Input 형태를 고려하며 RunnableParallel을 복잡하게 구성했어야 했는데, State의 존재만으로 데이터의 흐름을 중앙집중적으로 관리하여 이런 번거로움을 덜어내고, Node 간의 데이터 의존성을 느슨하게 만들어준다.

 

아래 코드는 Tavily Search tool을 이용하는 Chatbot을 LangGraph 프레임워크로 구현한 형태이다 .

class State(TypedDict):
    messages: Annotated[list, add_messages]

model = ChatOpenAI(model='gpt-4.1-mini')

def chatbot(state:State):
    # LATEST MESSAGE -> LLM Query
    response = model.invoke(state['messages'])
    # save response to state -> dictionary {"state name to save":value to save}
    return {'messages':[response]}   # in this case, return means to save to our state

class CustomToolNode:
    def __init__(self, tools): # Tool List
        self.tools_by_name = {tool.name: tool for tool in tools}

    def __call__(self, inputs:State)
        if messages := inputs.get("messages", []):
            message = messages[-1]  
        else:
            raise ValueError("No messages found in inputs.")
        outputs = []
        for tool_call in message.tool_calls:
            tool_message = self.tools_by_name[tool_call['name']].invoke(tool_call)
            outputs.append(tool_message)
        return {"messages": outputs}
tool_node = CustomToolNode(tools=tools)   # Must make it callable

def custom_tools_condition(state:State):
    if messages := state.get("messages", []):
        ai_message = messages[-1]
    else:
        raise Exception('No message in the STATE')
    if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0:
        return "tools"
    return END

#############################
#### 아래는 구조 구현부 ####
########### 깰끔 ############
#############################


# graph
workflow = StateGraph(State)

# nodes
workflow.add_node("chatbot", chatbot)
workflow.add_node("tools", tool_node)

# edges
workflow.add_edge(START, 'chatbot')
workflow.add_conditional_edges('chatbot', custom_tools_condition, {'tools':'tools', END:END})  # conditional edge

workflow.add_edge('tools', 'chatbot')
graph = workflow.compile()

query = 'LangGraph가 무엇인지 설명해주세요. 검색을 통해 정확한 정보를 알려주세요.'
init_state = {'messages':[HumanMessage(content=query)]}
graph.invoke(init_state)

Now What?

 

 

Agent와 LangGraph 관련 부분만 기술했지만, 이번 주에 배운 내용이 정말 많다. 특히 RAG 부분을 처음부터 천천히 다뤄보는 것은 꼭 필요하다고 생각하여 시간을 들여 복습해야겠다. 이렇게 다져 놓은 기본기는 3차 미니 프로젝트에서 꼭 빛을 발휘하리라.

 

지금까지 정말 많은 것들을 배웠지만, 가장 인상 깊었던 것은 Agent 부분이다. 열심히 배운 RAG와 Tool을 Chaining해서 활용하는 것부터, 간단한 LangGraph 구조를 만들기까지 해봤다. 최근 수 년이 RAG의 해였다면, 앞으로는 Agent 분야가 많이 발전될 것이라는 전망이다. 조사해 보니 AI가 사용자를 위해 선제적으로 행동하는 Proactive & Autonomous Agent, 나만의 비서를 만드는 Hyper-Personalization & On-Device AI, 어시스턴트가 사용자에게 맥락적으로 적절한 톤앤매너로 소통하는 기술(Emotional Intelligence & Social Context) 등 여러 관련 연구들이 활발히 이루어지고 있는 모양이다. 얼마 전 우연히 본 논문에서는 현실 세계의 사람은 단 한 명도 없고, 연구 Agent들만이 존재하는 가상의 연구실이라는 흥미로운 컨셉을 소개했다. 연구 흐름을 들여다 보니 더욱 깊이 있게 배우고 싶은 욕심이 생겨, LangGraph 공식 튜토리얼 문서들을 정독하고 실습하는 것으로 시작해서, 여러 연구들을 살펴봐야겠다는 생각이다.