RAG 이용한 Agent [1] 랭체인

Leejaegun·2025년 4월 4일

rag

목록 보기
1/4

1.🔧 툴콜링(Tool Calling)과 LLM 에이전트의 핵심 메커니즘 이해

정리:

툴콜링(Tool Calling)은 LLM이 외부 도구(API, 함수 등)를 호출할 수 있도록 해주는 메커니즘으로, LLM이 지닌 몇 가지 한계—예: 최신 정보 부족, 복잡한 연산 수행 불가, 정확성 문제(할루시네이션 등)—를 보완해주는 중요한 기능입니다.

1. 툴콜링의 필요성

  • 기존 LLM은 최신 정보나 복잡한 계산을 스스로 처리하기 어려움
  • 외부 API, 함수, 데이터베이스 등을 호출해 이 한계를 극복
  • 특히, 실시간 정보 접근, 코드 실행, 차트 시각화, 데이터베이스 접근/저장 등에 활용됨

2. 작동 원리

  • 사용자의 자연어 입력 → LLM이 판단: 도구 호출이 필요한가?
    • 필요 없으면 일반 답변
    • 필요하면: 자연어 → 도구의 스키마에 맞춘 구조화된 데이터(JSON 등)로 변환
  • 예시: “2와 3을 곱하면?” → { "tool": "Multiply", "args": { "a": 2, "b": 3 } }

3. 핵심 개념

  • 스키마(Schema): 도구가 받아야 할 인자의 구조 정의
  • 도구 바인딩(Binding): 도구를 LLM에 연결
  • 에이전트(Agent): 도구를 사용할 수 있는 LLM

4. 활용 예

  • 계산기 API 호출
  • 날씨 정보 API 호출
  • 데이터베이스 질의
  • 파이썬 코드 실행

5. 결론

툴콜링은 LangChain 기반 LLM 에이전트 또는 LangGraph 기반 워크플로우에서 핵심 기능입니다. 도구를 정의하고, 스키마를 설계하고, 이를 LLM과 연동하여 에이전트화하는 과정은 LLM 기반 자동화 시스템 구현의 기초입니다.

2. ✅ LangChain에서 외부 도구 연동 및 TabbyAPI(Web Search) 활용 요약

1. LangChain 도구 연동 구조

  • LangChain은 다양한 외부 도구(검색, 코드 실행, 생산성 툴 등)를 연동 가능.
  • LLM과 외부 서비스(웨이브 등의 API)를 연결할 수 있도록 설계됨.
  • 도구는 LangChain 자체 제공 or 제휴된 외부 서비스 도구로 구성됨.

2. TabbyAPI (Tabby Search) 소개

  • AI 기반 웹 검색 API 제공.
  • 1000번 무료 호출 가능 (신용카드 등록 불필요).
  • API 키는 .env 파일에 설정 (TABBY_API_KEY=... 형식).

📌 회원가입 및 API 키 발급:

  1. tabbly.com 접속
  2. 회원가입
  3. API 키 발급
  4. .env 파일에 키 추가

3. Tabby Search 도구 설정 방법 (LangChain에서 사용)

🔧 기본 흐름:

from langchain_community.tools import TavilySearchResults
search_tool = TavilySearchResults(max_results=2, name="web-search")
  • max_results: 검색 결과 개수 제한 (예: 2개)
  • name: 도구 이름 설정

🔎 사용 예시:

result = search_tool.invoke("스테이크와 어울리는 와인을 추천해줘")
  • 결과는 [{ "url": ..., "content": ... }, ...] 형태의 리스트로 반환됨.

4. LangChain 도구의 주요 구성 요소

요소설명
name도구 이름
description도구의 역할 설명
args_schema입력 스키마 (예: query: str)
invoke()실행 메서드 (단일 실행)
stream()스트리밍 방식 실행 (옵션)

LLM은 도구의 이름과 설명, 스키마를 통해 어떤 도구를 언제, 어떻게 쓸지 판단함.

5. 도구 사용 환경 설정

  • Python .env 파일에 OpenAI 및 Tabby API 키를 설정.
  • python-dotenv 패키지를 이용해 환경 변수 로딩.
  • 사용 중인 가상환경(poetry or conda)에서 kernel 선택 필수.

3. 🔧 LangChain에서 LLM + 도구 호출 흐름 요약

1. 기존 방식 vs 도구 호출 방식

  • 기존 방식: invoke() 메소드에 자연어 쿼리를 직접 전달 → LLM이 텍스트 생성 또는 단순 실행.
  • 도구 호출 방식(tool calling): LLM이 사용자의 쿼리를 이해하고, 실제 구글 검색처럼 적절한 검색어(query) 로 변환 → 지정된 도구를 통해 실행.

2. 도구 바인딩 과정

  1. LLM (예: ChatOpenAI)을 초기화.
  2. 사용할 도구 (예: 웹서치)를 정의.
  3. llm.bind_tools([웹서치])를 통해 도구 바인딩.
  4. 바인딩된 LLM을 llm_with_tools 같은 변수에 저장.

3. 실행 흐름

  • 사용자가 invoke("스테이크에 어울리는 와인 추천해줘")와 같이 자연어 쿼리 전달.
  • LLM은 자연어 전체 문장을 그대로 검색하지 않고,
    • 예: "스테이크에 어울리는 와인 추천"
    • 예: "스테이크와 와인 페어링 팁"
      → 이런 식으로 적절한 검색어(query) 들을 생성.
  • 각 검색어에 대해 도구 호출(tool call) 객체를 만들어 반환 (검색은 실제로 실행되지는 않음).

4. 도구 호출 결과 확인

  • 반환된 메시지 객체 (AIMessage)의 구조:
    • content: 도구 호출을 하지 않았다면 일반 응답이 들어감.
    • tool_calls: 도구 호출이 이루어진 경우 여기에 호출 정보가 저장됨.
      • 도구 이름, id, 쿼리 등 포함.
      • 예:
        {
          "name": "tavily_search_results_json",
          "arguments": {"query": "스테이크에 어울리는 와인 추천"}
        }

5. 도구 호출 유무 판단 예시

  • 입력: "안녕하세요" → LLM은 도구 호출 없이 인사만 출력.
  • 입력: "스테이크에 어울리는 와인 추천해 주세요" → LLM은 도구 호출을 생성 (검색 쿼리 2개 생성).

6. 도구 호출 결과 처리

  • LLM은 도구 호출만 생성하고 실제 실행은 하지 않음.
  • 생성된 쿼리와 도구 정보를 바탕으로 사용자가 직접 검색 도구에 실행하거나 응답을 생성.

✅ 정리

  • LangChain에서는 LLM이 사용자의 쿼리를 적절한 검색어로 바꾸고, 어떤 도구를 어떻게 사용할지까지 안내.
  • 도구는 직접 실행하지 않으며, 도구 호출 정보를 기반으로 실제 실행은 개발자가 처리.

4. Tool Calling 결과를 활용방법

[개요]

LangChain에서 LLM의 Tool Calling 결과를 받아 실제 도구를 실행하는 방법은 총 3가지가 있습니다. 각각의 방식은 ToolCall 객체의 arguments, 전체 메시지, 또는 직접 작성한 ToolMessage를 활용하는 차이가 있습니다.

🧩 1. arguments만 추출하여 도구 실행하기 (가장 기본적인 방법)

  • ToolCall 객체 내 arguments 속성만 추출해 사용.
  • invoke() 함수의 인자로 arguments만 넘김.
  • 예: 쿼리 텍스트만 추출하여 검색 실행.
output = tool.invoke(tool_call.arguments)

🧩 2. 전체 ToolCall 메시지 자체를 전달하기 (일반적으로 가장 많이 사용)

  • ToolCall 객체 전체를 invoke()에 전달.
  • 내부에서 자동으로 필요한 속성(id, name, arguments)을 사용해 실행.
  • 더 편하고 실수 방지에 유리.
output = tool.invoke(tool_call)

🧩 3. ToolMessage 직접 정의해서 실행하기 (정밀 제어 필요할 때 사용)

  • ToolMessage 클래스를 이용해 도구 메시지를 수동으로 생성.
  • content, tool_call_id, tool_name 등을 직접 설정.
  • 커스터마이징이 필요한 고급 상황에서 사용.
ToolMessage(content=..., tool_call_id=..., tool_name=...)

🧪 배치 실행 방법 (batch() 사용)

  • 여러 ToolCall 객체를 배열로 전달해 동시에 여러 도구 실행 가능.
  • LLM이 복수 개의 도구를 호출한 경우 유용.
outputs = tool.batch([tool_call1, tool_call2])

정리 요약

방식설명사용 상황
1. arguments만 전달쿼리 등 필요한 값만 추출해서 사용간단한 도구 실행 시
2. 전체 메시지 전달ToolCall 전체를 넘김가장 일반적
3. 메시지 직접 생성ToolMessage 수동 생성복잡한 구조나 직접 제어 필요할 때

5. ✅ LangChain 도구 호출 후 검색 결과 기반 응답 생성 전체 흐름 요약

(1). 🧠 시작: 사용자 질문 예시

“스테이크에 어울리는 와인을 추천해 주세요”

(2). 🔍 도구 호출 단계 (검색 실행까지)

  • LLM이 사용자의 질문을 기반으로 적절한 검색어(Query) 로 변환함
  • 이 검색어를 LangChain 도구(Tavily Search)에 전달하여 실시간 문서 검색 수행
  • 검색 결과는 LangChain의 ToolMessage 객체에 저장됨
    → 이 객체는 {"url": ..., "content": ...} 형태의 리스트를 포함함

(3). 🧾 ToolMessage → LLM 응답에 활용하기

  • 단순 검색 결과로 끝나지 않고, LLM이 이 결과를 바탕으로 답변 생성하도록 구성

✅ 필요한 구성 요소:

  1. ChatPromptTemplate

    • 시스템 메시지: 역할 지정 + 오늘 날짜 포함
    • 휴먼 메시지: 사용자 질문 포함
    • 도구 메시지: 검색 결과(ToolMessage) 포함 (placeholder 사용)
  2. LLM (예: GPT-4 Omni Mini)

    • 프롬프트와 도구를 바인딩한 모델 생성
  3. LCEL Chain 구성

    • 전체 프롬프트 + 바인딩된 도구 + 사용자 입력 → 체인 실행으로 응답 생성

(4). ⚙️ 코드 동작 흐름

# 예시 흐름
prompt = ChatPromptTemplate.from_messages([...])  # 시스템, 휴먼, 도구 메시지 구성
llm_with_tools = llm.bind_tools([web_search_tool])  # 도구 바인딩
chain = prompt | llm_with_tools  # 체인 연결

@chain_decorator
def answer_user_query(user_input: str):
    ai_message = chain.invoke({"user_input": user_input})
    
    # 도구 호출이 필요한 경우
    tool_calls = ai_message.tool_calls
    tool_responses = web_search_tool.batch(tool_calls)
    
    # 검색 결과 포함해서 최종 답변 생성
    final_response = chain.invoke({
        "user_input": user_input,
        "messages": [ai_message, *tool_responses]
    })
    return final_response

(5). 📌 중요 포인트 요약

단계설명
🔹 검색LLM이 질문을 바탕으로 적절한 검색어로 도구 호출
🔹 도구 결과Tavily API를 통해 웹에서 정보 수집
🔹 메시지 구성검색 결과는 ToolMessage 형태로 저장됨
🔹 프롬프트 구성사용자 질문 + 시스템 메시지 + ToolMessage 포함
🔹 최종 답변LLM이 전체 프롬프트를 기반으로 사용자 질문에 응답

(6). 🧪 실행 예시 결과

질문: “모에샹동 샴페인 가격 알려줘”
→ 검색 결과 참조하여
"모에샹동 임페리얼 샴페인의 평균 가격은 37,000원에서 32,000원 사이입니다."
(출처: 데일리샷 URL 포함)


6. 🔨 LangChain에서 직접 도구(tool) 만들기 요약

✅ (1). 툴(tool)이란?

  • LLM이 특정 기능을 외부 함수로 호출할 수 있게 해주는 수단
  • 사용자의 쿼리에 따라 LLM이 직접 텍스트를 출력하지 않고, 적절한 외부 기능(예: 검색, 계산 등)을 호출해서 결과 생성

✅ (2). @tool 데코레이터로 도구 만들기

📌 핵심 구성

  • @tool은 LangChain Core의 langchain_core.tools에서 import
  • 일반적인 Python 함수에 @tool을 붙이면 LLM이 사용할 수 있는 도구가 됨

📌 구현 예시

from langchain_core.tools import tool

@tool
def SearchWeb(query: str) -> str:
    """
    Search the web for information not found in the internal database. Useful for retrieving the most recent or external knowledge.
    """
    # 검색 API 호출 → 결과 추출 및 문자열 포맷
    results = external_search_api(query)  # 예시
    if not results:
        return "관련 정보를 찾을 수 없습니다."
    
    return "\n---\n".join([
        f"<doc>\nURL: {r['url']}\nText: {r['content']}\n</doc>" for r in results[:2]
    ])
  • 함수 이름 → 도구 이름
  • 함수 Docstring → 도구 설명(description)
  • 매개변수 → 입력 스키마로 자동 변환

✅ (3). 도구 바인딩

  • 도구를 만든 뒤에는 LLM 모델에 바인딩 필요
llm = ChatOpenAI(...)
llm_with_tool = llm.bind_tools([SearchWeb])

✅ (4). 도구 실행 흐름

🧠 LLM의 역할

  • 사용자 질문을 그대로 검색어로 쓰지 않음
  • 예: "스테이크와 어울리는 와인 추천해 주세요" →
    생성된 검색 쿼리:
    1. "스테이크와 어울리는 와인 추천"
    2. "스테이크 와인 페어링 팁"

🧪 실행 예시

response = llm_with_tool.invoke("스테이크에 어울리는 와인 추천해 주세요")
print(response.tool_calls)
  • tool_calls에 도구 호출 정보가 들어있음
  • 이 쿼리를 기반으로 SearchWeb 도구가 실행됨
  • 실행 결과는 HTML 태그 형식으로 문서별 정리되어 단일 문자열로 출력됨

✅ (5). 도구 호출 예외 처리 및 포매팅

  • 검색 결과가 없을 경우 메시지 처리
  • 검색 결과는 HTML 포맷으로 정리 (문서별 <doc> 구분)

✅ (6). 요약 핵심 포인트

항목설명
@toolPython 함수 → LangChain 도구로 변환
Docstring도구 설명으로 사용됨 (LLM이 이 설명을 기반으로 도구 사용 여부 판단)
함수 이름도구 이름
바인딩llm.bind_tools([SearchWeb])
도구 호출사용자가 자연어로 질문 → LLM이 적절한 검색어 생성 + 도구 호출 결정
출력검색 결과는 HTML 형태로 정리된 단일 문자열

7. 🧪 LLM의 도구 호출 능력(툴콜링 성능) 비교 실험 정리

목표

여러 LLM이 같은 조건에서 도구 호출을 얼마나 잘 수행하는지(툴콜링 능력) 비교 실험.

⚙️ 실험에 사용된 모델

  1. GPT-4 Omni Mini
  2. Gemini 1.5 Flash
  3. Gemini 1.5 Pro
  4. Grok 기반 LLaMA 3 70B

🔧 실험 방법

  • 서치웹(Search Web)이라는 검색 도구를 바인딩 (LangChain의 bind_tools() 사용)
  • 동일한 쿼리(예: "스테이크에 어울리는 와인 추천")를 각 모델에 전달
  • 각 모델이 실제 도구 호출(Tool Call)을 시도하는지 확인

📊 결과 비교

모델명도구 호출 성공 여부특징 및 결과
GPT-4 Omni Mini✅ 성공가장 안정적, 한국어 인식도 좋음
Gemini 1.5 Flash❌ 실패Tool Calls 비어있음 → 일반 텍스트 응답
Gemini 1.5 Pro✅ 성공Flash보다 확실히 우수
LLaMA 3 (Grok 70B)✅ 성공영어 쿼리 생성은 뛰어남, 한국어 처리 약함

💡 주요 관찰 포인트

  • 모델 성능에 따라 툴콜링 여부가 달라진다.
    • 예: Gemini Flash는 같은 조건에서도 툴 호출 실패.
  • LLM이 쿼리를 영어로 생성할 경우, 한국어 문서 검색이 어려워질 수 있다.
    • 국내 서비스의 경우, LLM이 생성한 쿼리를 한국어로 번역하거나 조정해야 할 수도 있음.
    • 이럴 땐 Tool Message를 수동으로 생성하여 전달하는 방식(3번 방식)을 활용 가능.

📌 실무 적용 시 팁

  • 모델 선택 시 툴콜링 능력도 고려해야 함.
  • 오픈소스 모델 사용 시, LLM이 자동으로 생성한 쿼리의 언어를 점검하고 후처리가 필요할 수 있음.
  • 툴 메시지를 수동 수정해서 전달하는 방식도 함께 고려해야 실무 적용이 원활.

8. ✅ LangChain v0.3 이상: Runnable 객체를 도구로 변환하기

(1). 📌 핵심 기능: Runnable.s2r_tool() 메서드

  • 기존 함수 또는 Runnable 객체를 LLM이 사용할 수 있는 도구(StructuredTool) 로 변환
  • 입력은 딕셔너리 or 문자열, 출력은 LangChain의 표준 메시지/Document 형식 사용

(2). ✅ 위키피디아 검색 도구 만들기 예시

💡 목적

위키피디아 문서를 검색할 수 있는 도구를 Runnable → Tool로 만들어 활용

📦 필수 설정

  1. wikipedia 라이브러리 설치
  2. LangChain 문서 로더 사용: WikipediaLoader

🧩 1단계: 위키 검색 함수 정의

from langchain_community.document_loaders import WikipediaLoader

def search_wikipedia(inputs: dict):
    query = inputs["query"]
    k = inputs.get("k", 2)
    loader = WikipediaLoader(query=query, lang="ko", load_max_docs=k)
    return loader.load()

🧩 2단계: Runnable 객체로 감싸기

from langchain_core.runnables import RunnableLambda

runnable_wiki = RunnableLambda(search_wikipedia)

🧩 3단계: Pydantic 입력 스키마 정의

from pydantic import BaseModel, Field

class WikiSearchInput(BaseModel):
    query: str = Field(..., description="검색어")
    k: int = Field(default=2, description="가져올 문서 수")

🧩 4단계: 도구로 변환

wiki_search_tool = runnable_wiki.with_types(input_type=WikiSearchInput).as_tool(
    name="wiki_search",
    description="위키피디아에서 일반적인 배경지식을 검색하는 도구입니다."
)
  • name: 도구 이름
  • description: 도구의 기능 설명 (LLM이 도구를 이해하는 데 핵심)
  • input_type: 정확한 입력 스키마 지정 (LLM의 정확한 호출 유도)

🔎 실행 예시

wiki_search_tool.invoke({"query": "파스타의 유래"})

(3). 🤖 도구 바인딩 및 Tool Calling 예시

✅ 여러 도구를 LLM에 바인딩하여 선택적으로 사용 가능

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4-0125-preview").bind_tools([
    wiki_search_tool,     # 위키 검색 도구
    web_search_tool       # 기존에 만든 웹 검색 도구 (예: Tavily)
])

💬 복합질문 예시

“서울 강남의 파스타 맛집은 어디인가요? 그리고 파스타의 유래는 무엇인가요?”

  • 📍 LLM은 이 질문을 분석해서:
    • 첫 번째 질문 → 웹 검색 도구 호출
    • 두 번째 질문 → 위키 검색 도구 호출

🧪 ToolCall 결과 예시

  • ToolCall #1: "서울 강남 유명 파스타 맛집"web_search
  • ToolCall #2: "파스타"wiki_search

두 도구 모두 호출되어 각 결과에 대한 검색 결과 반환됨.

(4). 📌 정리

단계설명
🧱 함수 정의검색 함수 작성 (query, k)
🔁 Runnable 변환RunnableLambda() 로 감싸기
🧾 입력 스키마pydantic.BaseModel 로 입력 타입 정의
🔨 도구 변환.as_tool() 메서드로 Tool 객체 생성
🤖 도구 바인딩llm.bind_tools([tool1, tool2])
💬 복합 질문 처리LLM이 상황에 따라 적절한 도구 선택 및 호출

9. 🧩 LCEL 체인을 LangChain 도구로 변환하기 - 요약

✅ (1). 목표

  • 문서 검색 + 요약 기능을 하나의 체인으로 구성
  • 이 체인 전체를 LangChain 도구로 변환하여 LLM이 사용할 수 있게 만들기

✅ (2). 체인 구성 단계

📌 1) 문서 검색 함수 정의 (wiki_search_and_summarize)

  • 입력: 딕셔너리 (query 키 사용)
  • 동작:
    • 위키피디아 문서 최대 2개 검색 (언어: 한국어)
    • 각 문서를 HTML 형식으로 포맷:
      <doc>
      Source: [문서 링크]
      Content: [문서 내용]
      </doc>

📌 2) 요약 프롬프트 생성

  • ChatPromptTemplate 사용
  • 입력 문서(context)를 받아 요약 요청하는 템플릿 생성:
    다음 내용을 요약해주세요: {context}

📌 3) LCEL 체인 구성

  • 체인 구성:
    • 문서 검색 함수 → 요약 프롬프트 → LLM → 결과 추출
  • RunnableLambda, Prompt, LLM, RunnablePick 등의 LCEL 구성 요소 사용
  • 출력: 요약된 문자열

✅ (3). 체인을 도구로 변환하기

📌 변환 방법

  • .as_tool() 메서드 사용:
summary_tool = summary_chain.as_tool(
    name="wiki_summary",
    description="Search and summarize a Korean Wikipedia article related to the query.",
    args_schema=WikiSummarySchema  # query 필드만 포함된 Pydantic 입력 스키마
)
  • 도구 이름, 설명, 입력 스키마를 명확히 정의
  • k(문서 개수) 같은 고정 값은 스키마에 포함하지 않음

✅ (4). 도구 바인딩 및 사용

📌 2개 도구 바인딩 예

  • SearchWeb (웹 검색용)
  • wiki_summary (위키 문서 검색 및 요약용)
llm_with_tools = llm.bind_tools([SearchWeb, summary_tool])

✅ 5. 복합 질문 처리 흐름

질문 예시:
“파스타 맛집 추천해줘, 그리고 파스타의 유래는 뭐야?”

▶ 실행 흐름:

  1. LLM이 복합 질문을 받아 2개의 도구 호출 판단:

    • 웹 검색 → "파스타 맛집"
    • 위키 요약 → "파스타 유래"
  2. 각 도구에 맞는 쿼리 자동 생성 및 호출

  3. 도구 호출 결과(문서 또는 요약)를 수집:

    • ToolMessage 객체로 반환됨
  4. 사용자 질문 + 도구 결과 → 다시 LLM에 전달

  5. LLM은 이 정보를 바탕으로 최종 답변 생성

✅ 6. 핵심 기술 흐름 요약

구성 요소역할
@tool 또는 .as_tool()함수/체인을 LangChain 도구로 변환
args_schema입력 형식 명시 (Pydantic)
bind_tools()도구들을 LLM에 바인딩
invoke() 또는 체인 실행사용자의 쿼리로 도구 호출 + 결과 생성
ToolMessage도구 실행 결과 객체
ChatPromptTemplate요약 요청 등 사용자 지시 템플릿 구성
RunnableLambda사용자 정의 함수 포함 가능
LCEL 체인전체 흐름을 체인으로 연결해 관리

🎯 최종 요약

LCEL을 통해 구현한 검색 + 요약 체인.as_tool()을 이용해 도구로 만들고, LLM이 이를 자동으로 호출할 수 있도록 구성 → 복합질문에 대한 자동 도구 호출 + 결과 기반 응답이 가능해짐.

좋습니다! 아래는 위의 긴 설명을 핵심 중심으로 정리한 요약본입니다.


10. 📚 문서 인덱싱 & 벡터 검색 도구 생성 및 LLM 툴 바인딩 실습 요약

목표

  1. 문서를 읽고 인덱싱 → 벡터 DB에 저장
  2. 벡터 검색기를 도구로 변환
  3. 도구를 LLM에 바인딩하여 자동 툴 호출 실험

📁 데이터 구성

  • 문서 2개: 메뉴 데이터, 와인 데이터 (각각 10개 항목)
  • 형식: 메뉴 번호, 이름, 가격, 재료 등 정보 포함

🧱 (1). 문서 청킹 및 전처리

  • 정규표현식을 사용해 각 문서를 10개 항목(청크)으로 분할
  • 각 청크는 Document 객체로 구성하고, 메뉴 번호, 이름, 파일 경로 등을 메타데이터로 설정

🔍 (2). 벡터 DB에 인덱싱

  • 벡터 DB: ChromaDB
  • 임베딩 모델: Ollama + bge-m3
    → 한국어 성능 우수, 경량 모델
  • 각 문서 청크를 컬렉션 단위로 저장
    • 예: 레스토랑_메뉴, 레스토랑_와인
Chroma.from_documents(documents, embedding, collection_name="레스토랑_메뉴")

🔎 (3). 벡터 검색기(Retriever) 생성

  • SimilarityRetriever 생성하여 유사한 문서 2개 검색
  • 예: "시그니처 스테이크 가격과 특징" → 관련 문서 2개 반환됨

🛠️ (4). 검색기 → 도구(StructuredTool)로 변환

  • 함수 정의 + @tool 데코레이터로 툴화
  • 입력: query (문자열)
  • 출력: 관련 Document 리스트
  • 예외 처리도 포함: 관련 문서 없을 경우 메시지 출력
@tool
def search_menu(query: str) -> List[Document]:
    """레스토랑 메뉴 정보를 검색하는 도구입니다."""
  • 와인 데이터에 대해서도 동일 방식으로 search_wine() 도구 생성

🤖 (5). 도구를 LLM에 바인딩하고 쿼리 전달

  • search_menu, search_wine 도구를 LLM에 바인딩
  • 복합 쿼리 예:
    "시그니처 스테이크의 정보와 어울리는 와인을 추천해줘"

🧠 LLM 툴콜링 결과

  • tool_calls 2개 발생
    1. search_menu"시그니처 스테이크"로 메뉴 정보 검색
    2. search_wine"스테이크"에 어울리는 와인 검색

→ LLM이 자동으로 어떤 도구를 언제 사용할지 판단

정리 요약

단계설명
1️⃣ 문서 읽기 및 청킹정규표현식으로 항목 단위 분리
2️⃣ 벡터 저장소 생성Chroma + bge-m3 인베딩 사용
3️⃣ 벡터 검색기 생성유사도 기반 검색기 (Retriever)
4️⃣ 도구로 변환@tool로 Structured Tool 등록
5️⃣ LLM에 바인딩도구를 LLM에 연결, 자동 툴 호출 실험 성공

11. ✅ LLM에 4개의 도구 바인딩하고 복합질문에 대응하기 (고급 예제)

🎯 목표

  • LLM이 4가지 도구를 받아서, 질문에 따라 적절한 도구를 선택하고 호출한 뒤,
    그 결과를 바탕으로 최종 답변을 생성하는 전체 체인 구성을 구현

(1). ✅ 사용된 도구 4가지

도구 이름설명
search_webTavily API 기반 웹 검색 도구
wiki_summary위키피디아 문서 검색 및 요약 도구
search_wine벡터 저장소에서 와인 메뉴 검색 도구
search_menu벡터 저장소에서 레스토랑 메뉴 검색 도구

(2). 🧩 도구 이름과 호출 시 주의사항

  • 도구 호출 실패 대부분은 도구 이름 불일치가 원인
  • @tool 데코레이터를 사용하면 도구 이름 = 함수 이름 으로 자동 지정됨 → 확인 필수
  • 도구 출력도 필요에 따라 문자열로 포맷팅 후 래핑 가능

(3). ⚙️ 전체 체인 구성 요약

🔁 1) 사용자 질문 입력

  • 복합 질문 예시:

    "시그니처 스테이크 가격은 얼마인가요? 그리고 스테이크와 어울리는 와인은?"

🧠 2) LLM의 Tool Call 판단

  • LLM은 질문을 분석하여 적절한 도구들을 선택함
  • 예:
    • search_menu 도구 → "시그니처 스테이크"로 호출
    • search_wine 도구 → "스테이크와 어울리는 와인"으로 호출

🔧 3) 도구 실행 및 Tool Message 수집

tool_messages = []
for tool_call in ai_message.tool_calls:
    if tool_call.name == "search_menu":
        result = search_menu.invoke(tool_call)
        tool_messages.append(result)
    elif tool_call.name == "search_wine":
        result = search_wine.invoke(tool_call)
        tool_messages.append(result)
    ...

🧾 4) ToolCall + ToolMessage를 LLM에 전달해 최종 답변 생성

response = final_chain.invoke({
    "user_input": original_query,
    "messages": [ai_message, *tool_messages]
})
  • 여기서 final_chain은 LLM이 이미 도구 호출 정보와 결과를 알고 있으므로
    추가적인 호출 없이 바로 최종 답변 생성 가능

(4). 🧪 실행 예시 결과

✅ 케이스 1: 스테이크 가격 + 어울리는 와인

  • 🔹 search_menu → 메뉴 가격/설명 검색
  • 🔹 search_wine → 어울리는 와인 + 가격 추천
  • ✅ LLM이 두 도구 결과를 바탕으로 정확한 응답 생성

✅ 케이스 2: 파스타 메뉴 + 음식 유래

  • 🔹 search_menu → "파스타" 메뉴 검색
  • 🔹 wiki_summary → "파스타" 역사 검색
  • ⚠️ 문제점: 팬스테이크 비프도 파스타로 오인하여 포함
    LLM의 reasoning 오류로 판단됨

🧠 개선 포인트

문제원인해결책
메뉴 구분 실패벡터 검색 결과를 LLM이 잘못 판단더 정교한 Filtering or GPT-4 Omni 사용
도구 선택 정확도LLM이 잘못된 도구 선택 가능성description을 명확히, 예시 질문 추가

🔚 정리

구성 요소설명
도구 바인딩4개의 도구를 LLM에 등록
ToolCallLLM이 선택한 도구 호출 요청
ToolMessage호출 결과를 담는 메시지 객체
체인 구성ToolCall → Tool 실행 → 메시지 결합 → 최종 응답 생성

12. 🔍 퓨샷 프롬프팅(Few-shot Prompting)을 통한 도구 호출 성능 향상

✅ (1). 퓨샷 프롬프팅이란?

  • 도구 사용 방식의 예시(샘플)를 LLM에 제공하여,
    → 모델이 "이런 식으로 도구를 호출해야 한다"는 작업 흐름 패턴을 학습하게 하는 프롬프팅 기법
  • 도구 호출 성능이 낮은 경우에도 성능을 안정화시키는 효과 있음
  • 고성능 모델에서도 도구 사용 정확도와 일관성을 높이는 데 도움

✅ (2). 퓨샷 구성 방식

🧱 프롬프트 구조

  1. 시스템 메시지:
    • 도구들의 사용 조건과 역할 명시
      • 예: 메뉴 정보 → SearchMenu, 일반 정보 → WikiSummary, 와인 → SearchWine, 최신 정보 → SearchWeb
  2. Few-shot 예시 메시지:
    • 실제 사용자 입력 + AI의 판단 + 도구 호출 흐름 예시 포함
  3. 실제 사용자 쿼리:
    • 모델이 예시를 참조하여 적절한 도구를 선택하도록 유도

✅ (3). 예시 워크플로우 (퓨샷 프롬프트 기반)

📌 입력:

트러플 리조또 가격, 특징, 어울리는 와인 알려줘

📌 AI 판단 흐름:

  1. 메뉴 검색 → SearchMenu("트러플 리조또")
  2. 위키 정보 요약 → WikiSummary("트러플 리조또")
  3. 와인 검색 → SearchWine("트러플 리조또에 어울리는 와인")

도구 호출 메시지와 그 결과들을 단계별로 참조하여 최종 응답 생성

✅ (4). 실행 예시

❓ 사용자 쿼리:

“스테이크 메뉴가 있는지 알려줘, 어울리는 와인도 추천해줘”

✅ LLM 판단 결과:

  • SearchMenu("스테이크")
  • SearchWine("스테이크에 어울리는 와인")
    → 두 개의 도구 호출을 정확히 수행

❓ 사용자 쿼리:

“파스타의 유래와 강남 파스타 맛집 추천해줘”

⚠️ 결과:

  • 위키 검색(WikiSummary)은 정확
  • 맛집 추천을 메뉴 검색(SearchMenu)으로 잘못 판단
    도구 선택 오류 발생 (웹 검색이 적절했음)

✅ (5). 도구 호출 이후 답변 생성

  • 도구 호출 결과를 ToolMessage로 받아
  • 이전 메시지들과 함께 LLM에 전달
  • 답변 생성은 퓨샷 기반 프롬프트와 체인으로 마무리

🎯 구성:

  • System Message + Few-shot Examples + User Message
  • Tool Calls + Tool Responses
  • → 최종 Answer 생성

✅ (6). 한계점 및 해결 방안

⚠️ 전통 LangChain의 한계

  • 도구 선택 실수 발생 시 → 재실행 흐름(Loop) 구현 어려움
  • 예: 도구 호출 실패 → 재검색 or 재응답 불가

✅ 해결책: LangGraph 사용

  • 도구 호출 → 평가 → 실패 시 → 되돌아가 다시 호출 → 재평가
  • 이런 유연한 멀티스텝 실행 플로우를 LangGraph에서는 쉽게 구현 가능

🧠 최종 요약

구분내용
✅ 목적도구 호출 정확도 향상
🛠️ 방법Few-shot Prompting + Tool Binding
🧱 구성시스템 메시지 + 예시 메시지 + 사용자 입력
📈 효과LLM이 도구 사용 흐름을 학습해 적절히 호출
❗ 한계도구 선택 실패에 대한 되돌리기 등은 LangChain만으로는 구현 어려움
💡 대안LangGraph 도입 → 평가/분기/재실행 흐름 가능

13. 🤖 LangChain 에이전트 시스템 요약

✅ 에이전트(Agent)란?

  • LLM이 도구(툴)를 선택, 실행, 결과 해석까지 스스로 수행하는 시스템
  • 도구 호출 + 피드백 + 재결정의 반복 루프를 통해 최종 응답 생성

기존의 툴콜링은 "무엇을 실행할지"까지만 결정했다면
에이전트는 실행부터 결과 해석, 다음 행동 결정까지 포함

🧠 작동 원리 요약

1. 사용자 질문 입력
2. LLM이 어떤 도구를 사용할지 결정 (툴콜링)
3. 도구 실행
4. 실행 결과 → LLM에게 피드백 (에이전트 스크래치 패드에 기록)
5. 추가 행동이 필요한 경우 반복
6. 최종적으로 응답 생성

이 과정을 자동으로 반복하는 것이 "에이전트 루프"입니다.

🛠️ LangChain 에이전트 구성 도구

구성 요소설명
create_tool_calling_agent()에이전트를 정의(생성)하는 함수
AgentExecutor()에이전트를 실행하는 객체
agent_prompt에이전트 동작을 제어하는 프롬프트 템플릿

🧾 에이전트 프롬프트 템플릿 구성

필수 요소:
1. input – 사용자 질문
2. agent_scratchpad – 툴 실행 내역 + 결과 피드백 (자동 기록)

옵션 요소:

  • chat_history – 이전 대화 기록 포함 가능 (옵셔널로 설정 가능)

예시:

agent_prompt = ChatPromptTemplate.from_messages([
    ("system", "역할 지시문"),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}")
])

⚙️ 에이전트 생성 & 실행 흐름

# 1. 에이전트 정의
agent = create_tool_calling_agent(
    llm=llm,
    tools=[tool1, tool2, ...],
    prompt=agent_prompt
)

# 2. 에이전트 실행기 생성
agent_executor = AgentExecutor(
    agent=agent,
    tools=[tool1, tool2, ...],
    verbose=True  # 중간 과정 출력 여부
)

# 3. 쿼리 실행
response = agent_executor.invoke({"input": "시그니처 스테이크 가격과 어울리는 와인을 추천해줘"})

🔎 실행 예시 결과

  • 사용자의 복합 질문search_menu 실행 → 결과 해석
  • 이어서 search_wine 실행 → 결과 해석
  • 마지막에 체포 스타일로 종합 응답 생성
🌟 시그니처 스테이크는 38,000원이며, 육즙 가득한 프리미엄 스테이크입니다.
🍷 어울리는 와인으로는 ... 추천드립니다!

📦 출력 구조

{
  "input": "사용자의 입력 쿼리",
  "output": "에이전트가 생성한 최종 응답"
}
  • output 필드만 따로 추출해서 사용할 수 있음
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

agent_prompt = ChatPromptTemplate.from_messages([
    ("system", dedent("""
        You are an AI assistant providing restaurant menu information and general food-related knowledge. 
        Your main goal is to provide accurate information and effective recommendations to users.

        Key guidelines:
        1. For restaurant menu information, use the search_menu tool. This tool provides details on menu items, including prices, ingredients, and cooking methods.
        2. For general food information, history, and cultural background, utilize the wiki_summary tool.
        3. For wine recommendations or food and wine pairing information, use the search_wine tool.
        4. If additional web searches are needed or for the most up-to-date information, use the search_web tool.
        5. Provide clear and concise responses based on the search results.
        6. If a question is ambiguous or lacks necessary information, politely ask for clarification.
        7. Always maintain a helpful and professional tone.
        8. When providing menu information, describe in the order of price, main ingredients, and distinctive cooking methods.
        9. When making recommendations, briefly explain the reasons.
        10. Maintain a conversational, chatbot-like style in your final responses. Be friendly, engaging, and natural in your communication.


        Remember, understand the purpose of each tool accurately and use them in appropriate situations. 
        Combine the tools to provide the most comprehensive and accurate answers to user queries. 
        Always strive to provide the most current and accurate information.
        """)),
    MessagesPlaceholder(variable_name="chat_history", optional=True),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])
# Tool calling Agent 생성
from langchain.agents import AgentExecutor, create_tool_calling_agent

tools = [search_web, wiki_summary, search_wine, search_menu]
agent = create_tool_calling_agent(llm, tools, agent_prompt)

# AgentExecutor 생성 
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# AgentExecutor 실행

query = "시그니처 스테이크의 가격과 특징은 무엇인가요? 그리고 스테이크와 어울리는 와인 추천도 해주세요."
agent_response = agent_executor.invoke({"input": query})
pprint(agent_response)

✅ 요약 정리

항목내용
목적LLM이 도구 사용 및 응답 생성까지 자율 수행
핵심 기능도구 선택 → 실행 → 결과 피드백 → 응답
구성 요소create_tool_calling_agent, AgentExecutor, 프롬프트
주요 개념agent_scratchpad, input, output, chat_history
응용복합질문 처리, 정보 수집, 보고서 생성 등 고급 자동화 가능

좋습니다! 말씀하신 내용을 바탕으로 LangChain 기반 멀티 도구 LLM 에이전트를 Gradio Chatbot으로 구현하는 전체 과정을 아래와 같이 깔끔하게 정리해드릴게요.


14. ✅ LangChain + Gradio 기반 Chatbot 인터페이스 구현 정리

🎯 목표

LangChain 에이전트를 Gradio를 통해 웹 기반 챗봇 형태로 시각화
→ 사용자 질문 → 도구 판단 및 호출 → 응답 생성 → 웹 UI로 출력

(1). 📦 사용된 기술 스택

도구역할
LangChainLLM + 도구 호출 체인 관리
GradioChat UI 구현 (chatbot 형태)
VS Code / Jupyter서버 실행 및 로그 확인 환경

(2). 🛠️ 사용된 4가지 에이전트 도구

도구 이름설명
search_web웹 검색 (Tavily 기반)
wiki_summary위키피디아 검색 요약
search_wine벡터 기반 와인 검색
search_menu벡터 기반 메뉴 검색

(3). ⚙️ Gradio 인터페이스 구현 흐름

✅ 핵심 구조

import gradio <as gr

def answer_fn(message: str, history: list):
    # 1. 채팅 히스토리 구성
    past_messages = []
    for human, ai in history[-2:]:  # 최근 1턴만 반영
        past_messages.append(HumanMessage(content=human))
        past_messages.append(AIMessage(content=ai))

    # 2. LangChain 에이전트 실행
    try:
        response = agent_executor.invoke({
            "user_input": message,
            "messages": past_messages
        })
        return response["output"]  # 최종 응답 반환
    except Exception as e:
        return f"⚠️ 오류 발생: {str(e)}"

✅ Gradio 인터페이스 정의

demo = gr.ChatInterface(
    fn=answer_fn,
    title="음식 추천 챗봇 🍽️",
    description="요리, 레스토랑, 와인 관련 질문을 도구 기반으로 응답합니다.",
    examples=[
        "시그니처 스테이크 가격 알려줘",
        "스테이크와 어울리는 와인은?",
        "해산물 파스타 추천해줘",
        "파스타 유래가 궁금해"
    ],
    theme="soft"
)

demo.launch()

(4). 🧠 Chat 흐름 요약

단계설명
1️⃣ 사용자 입력Gradio Chat UI에서 입력
2️⃣ 최근 히스토리 반영최신 1턴(2 메시지)을 LangChain 메시지로 변환
3️⃣ 에이전트 실행LLM이 적절한 도구 호출 판단 및 실행
4️⃣ ToolMessage 활용도구 결과를 포함하여 최종 응답 생성
5️⃣ 응답 반환Gradio 인터페이스로 응답 출력
6️⃣ 로그 확인VS Code 또는 터미널 로그에서 도구 호출 및 응답 확인 가능

(5). ✅ 예시 시나리오

예시 1: 복합 질문 (성공)

“시그니처 스테이크 가격은 얼마인가요? 그리고 어울리는 와인은?”

  • search_menu 호출 → 메뉴 설명 + 가격
  • search_wine 호출 → 와인 이름 + 특징 + 가격
  • ✅ 정확한 응답 생성

예시 2: 맥락 기억 확인

질문1: “해산물 파스타에 뭐 들어가요?”
질문2: “설의 파스타 평점은?”

  • 히스토리 덕분에 ‘설의 파스타’가 앞선 응답에 있었던 정보임을 인식
  • ✅ "4.4점입니다" 라는 정확한 응답 생성

예시 3: 답변 결함 (LLM 오류)

  • 팬스테이크 비프가 파스타로 오인됨 → 벡터 검색 결과 오용
  • ⚠️ 검색은 성공했으나 LLM이 의미 파악 실패
    → GPT-4 Omni 모델로 개선 가능

(6). 🛑 마무리: 서버 종료

demo.close()
  • Gradio 서버는 기본적으로 7860번 포트에서 실행
  • 로컬 개발 환경에서는 반드시 종료 필요

✅ 전체 흐름 요약 다이어그램

[User Input]
     ↓
[Chat History] ← (최근 1턴 유지)
     ↓
[Agent Executor (LangChain)]
     ↓
[ToolCall 판단 → 도구 실행]
     ↓
[ToolMessage + User Input]
     ↓
[LLM 최종 응답 생성]
     ↓
[Gradio Chat UI 출력]

정리

[1] User Input
     |
     v
"Is this termination legal under Korean labor law?"

     |
     v
[2] LLM (with tool-calling)
     |
     └── Decides: "I need legal info → Call a tool"

     |
     v
[3] Tool Call
     |
     └── e.g., search_labor_law(query="termination conditions")

     |
     v
[4] Tool Response
     |
     └── Returns relevant law content or case info

     |
     v
[5] LLM (Processes output)
     |
     └── Understands the law + user's question

     |
     v
[6] Final Answer
     |
     └── "According to Korean labor law Article XX, termination is only legal if..."
profile
Lee_AA

0개의 댓글