langchain / / 2024. 9. 1. 21:11

[langchain] 개념 가이드 (Conceptual guide)

[langchain] Conceptual guide

이 내용은 langchain conceptual guide 문서를 정리한 내용입니다. (v0.2 기준)

출처: https://python.langchain.com/v0.2/docs/concepts/

이 섹션에서는 LangChain의 주요 부분에 대한 소개를 제공한다.

아키텍처

LangChain은 여러 패키지로 구성된 프레임워크이다.

langchain-core

이 패키지는 다양한 컴포넌트의 기본 추상화와 이들을 함께 구성하는 방법을 포함하고 있다. LLM(대규모 언어 모델), 벡터 스토어(vector stores), 검색기(retrievers) 등 핵심 컴포넌트의 인터페이스가 여기에서 정의된다. 이 패키지에는 타사 통합에 대한 내용은 여기에 정의되어 있지 않으며, 의존성은 의도적으로 최소한만 사용된다.

langchain

주요 langchain 패키지는 애플리케이션의 인지 아키텍처를 구성하는 체인, 에이전트, 검색 전략을 포함하고 있다. 이 패키지에는 타사(third party) 통합을 위한 내용이 포함되지 않는다. 이곳의 모든 체인, 에이전트, 검색 전략은 특정 제품에 국한되지 않고 모든 제품에 일반적으로 사용될 수 있다.

langchain-community

이 패키지는 LangChain 커뮤니티에서 유지 관리하는 타사(third party) 통합을 포함하고 있다. 주요 파트너 패키지들은 별도로 분리되어 있다(아래 참조). 이 패키지에는 다양한 컴포넌트(LLM, 벡터 스토어, 검색기)의 모든 통합이 포함된다. 패키지를 최소한만 사용하기 위해 이 패키지의 모든 의존성은 선택적(optional)으로 사용될 수 있다.

파트너(Partner) 패키지

langchain-community에는 다양한 통합에 대한 내용이 포함되어 있지만, 중요한 통합은 별도의 패키지로 분리되었다(예: langchain-openai, langchain-anthropic 등). 이것은 중요한 통합을 지원하기 위해 분리되었다.

langgraph

langgraph는 LangChain의 확장으로, 그래프에서 단계를 엣지와 노드로 모델링하여 LLM을 사용하는 강력하고 상태적인 다중 액터 애플리케이션을 구축하는 것을 목표로 한다.

LangGraph는 일반적인 유형의 에이전트를 생성하기 위한 고수준 인터페이스뿐만 아니라 사용자 지정 흐름을 구성하기 위한 저수준 API를 제공한다.

langserve

LangChain 체인을 REST API로 배포할 수 있는 패키지이다. 프로덕션 준비가 된 API를 쉽게 실행할 수 있도록 도와준다.

LangSmith

LLM 애플리케이션을 디버그, 테스트, 평가 및 모니터링할 수 있는 개발자 플랫폼이다.

[출처] langchain conceptual guide (https://python.langchain.com/v0.2/docs/concepts/)

LangChain 표현 언어 (LCEL)

LangChain 표현 언어(LCEL)는 LangChain 컴포넌트를 체인화하는 선언적 방식이다. LCEL은 작업을 처음 시작할 때부터 프로토타입을 코드 변경 없이 프로덕션에 배포할 수 있도록 설계되었으며, 가장 간단한 "프롬프트 + LLM" 체인부터 복잡한 체인까지 모두 지원한다. 실제로 LCEL 체인을 사용하여 수백 개의 단계로 구성된 체인을 프로덕션에서 성공적으로 실행한 사례도 있다. LCEL을 사용하는 몇 가지 이유는 다음과 같다.

  • 일급(first-class) 스트리밍 지원: LCEL로 체인을 구축하면 첫 번째 토큰이 나올 때까지의 시간이 최적화된다. 예를 들어, 일부 체인의 경우 LLM에서 나오는 토큰을 스트리밍 출력 파서로 바로 전송하여, LLM 제공자가 원시 토큰을 출력하는 속도와 동일한 속도로 파싱된 출력 청크를 받을 수 있다.
  • 비동기 지원: LCEL로 구축된 모든 체인은 동기 API(예: Jupyter 노트북에서 프로토타입으로 사용)와 비동기 API(예: LangServe 서버에서 사용) 모두에서 호출할 수 있다. 이를 통해 프로토타입과 프로덕션에서 동일한 코드를 사용할 수 있으며, 높은 성능과 같은 서버에서 다수의 동시 요청을 처리할 수 있는 능력을 제공한다.
  • 최적화된 병렬 실행: LCEL 체인에 병렬로 실행할 수 있는 단계가 있을 경우(예: 여러 검색기에서 문서를 가져오는 경우) 자동으로 병렬 처리가 수행된다. 이는 동기 및 비동기 인터페이스 모두에서 가능한 최소 지연 시간으로 실행된다.
  • 재시도 및 폴백: LCEL 체인의 모든 부분에 대해 재시도와 폴백을 설정할 수 있다. 이는 대규모에서 체인을 더욱 안정적으로 만드는 훌륭한 방법이다. 현재 스트리밍 지원이 추가된 재시도/폴백 기능을 개발 중이며, 이를 통해 지연 시간 없이 안정성을 높일 수 있다.
  • 중간 결과 접근: 복잡한 체인의 경우, 최종 출력이 생성되기 전이라도 중간 단계의 결과에 접근할 수 있는 것이 매우 유용할 때가 많다. 이를 통해 최종 사용자에게 무언가 진행 중임을 알리거나 체인을 디버깅하는 데 사용할 수 있다. 중간 결과를 스트리밍할 수 있으며, 이는 모든 LangServe 서버에서 사용할 수 있다.
  • 입출력 스키마: 입출력 스키마는 모든 LCEL 체인에 Pydantic 및 JSONSchema 스키마를 제공한다. 이 스키마는 체인의 구조에서 자동으로 추론되며, 입력 및 출력의 유효성 검증에 사용할 수 있다. 이는 LangServe의 중요한 부분이다.
  • Seamless LangSmith 추적: 체인이 점점 더 복잡해짐에 따라 각 단계에서 정확히 무엇이 일어나는지 이해하는 것이 점점 더 중요해진다. LCEL을 사용하면 모든 단계가 자동으로 LangSmith에 로깅되어 최대의 관찰 가능성과 디버깅 가능성을 제공한다.

LCEL은 LLMChainConversationalRetrievalChain과 같은 기존 서브클래스 체인보다 행동 일관성과 맞춤화를 제공하는 것을 목표로 한다. 많은 기존 체인은 프롬프트와 같은 중요한 세부 사항을 숨기고 있으며, 더 다양한 모델이 등장함에 따라 커스트마이징이 점점 더 중요해지고 있다.

현재 이러한 기존 체인 중 하나를 사용 중이라면, 마이그레이션 가이드를 참조하라.

LCEL을 사용하여 특정 작업을 수행하는 방법에 대한 가이드는 관련된 안내서를 참조하라.

Runnable 인터페이스

사용자 정의 체인을 쉽게 만들 수 있도록 "Runnable" 프로토콜을 구현했다. 많은 LangChain 컴포넌트는 Runnable 프로토콜을 구현하고 있으며, 여기에는 챗 모델, LLM, 출력 파서, 검색기, 프롬프트 템플릿 등이 포함된다. Runnable을 사용하는 데 유용한 몇 가지 기본 요소도 있으며, 이에 대한 자세한 내용은 아래에서 확인할 수 있다.

Runnable은 표준 인터페이스를 제공하여 사용자 정의 체인을 쉽게 정의하고 표준 방식으로 호출할 수 있다. 표준 인터페이스에는 다음과 같다.

  • stream: 응답의 청크를 스트리밍한다.
  • invoke: 입력에 대해 체인을 호출한다.
  • batch: 입력 목록에 대해 체인을 호출한다.

여기에는 asyncio await 구문을 사용하여 동시 실행을 위한 비동기 메서드도 있다.

  • astream: 응답의 청크를 비동기 스트리밍한다.
  • ainvoke: 입력에 대해 체인을 비동기 호출한다.
  • abatch: 입력 목록에 대해 체인을 비동기 호출한다.
  • astream_log: 최종 응답과 함께 발생하는 중간 단계를 스트리밍한다.
  • astream_events: 체인에서 발생하는 이벤트를 스트리밍하는 베타 기능이다(0.1.14 버전에 도입됨).

입력 및 출력 유형은 컴포넌트에 따라 다르다.

컴포넌트 입력 유형 출력 유형
Prompt Dictionary PromptValue
ChatModel 단일 문자열, chat message 리스트 또는 PromptValue ChatMessage
LLM 단일 문자열, chat message 리스트 또는 PromptValue 문자열
OutputParser LLM 또는 챗 모델의 출력 파서에 따라 다름
Retriever 단일 문자열 Document List
Tool Tool에 따라 단일 문자열 또는 사전 Tool에 따라 다름

모든 Runnable은 입력과 출력을 검사할 수 있는 입출력 스키마를 제공한다.

  • input_schema: Runnable의 구조에서 자동 생성된 입력 Pydantic 모델이다.
  • output_schema: Runnable의 구조에서 자동 생성된 출력 Pydantic 모델이다.

구성 요소(Component)

LangChain은 LLM(대규모 언어 모델)을 활용한 애플리케이션 구축에 유용한 다양한 구성 요소에 대해 표준화된 확장 가능 인터페이스와 외부 통합 기능을 제공한다. 일부 구성 요소는 LangChain에서 직접 구현하고, 일부는 서드파티 통합에 의존하며, 일부는 이 둘을 혼합하여 사용한다.

Chat models

Chat models은 메시지의 시퀀스를 입력으로 받아 chat messages로 반환하는 언어 모델이다(일반 텍스트를 사용하는 것과는 다르다). 이 모델들은 전통적으로 최신 모델이며, 이전의 모델들은 주로 LLM(대규모 언어 모델)이다. Chat models은 대화 메시지에 고유한 역할을 할당할 수 있도록 지원하여, AI, 사용자, 시스템 메시지와 같은 지시 메시지를 구분할 수 있게 한다.

기본적으로 Chat models은 메시지를 입력받아 메시지를 출력하지만, LangChain의 래퍼(wrapper)를 사용하면 문자열을 입력으로 받아들일 수도 있다. 이를 통해 Chat models을 LLM 대신 쉽게 사용할 수 있다.

문자열이 입력으로 전달되면, 이는 HumanMessage로 변환되어 기본 모델에 전달된다.

LangChain은 Chat models을 호스팅하지 않으며, 서드파티 통합에 의존한다.

Chat models을 구성할 때 몇 가지 표준화된 매개변수가 있다.

  • model: 모델의 이름
  • temperature: 샘플링 온도
  • timeout: 요청 시간 초과
  • max_tokens: 생성할 최대 토큰 수
  • stop: 기본 종료 시퀀스
  • max_retries: 요청을 재시도할 최대 횟수
  • api_key: 모델 제공자의 API 키
  • base_url: 요청을 보낼 엔드포인트

중요한 사항 몇 가지를 주목해야 한다.

  • 표준 매개변수는 해당 기능을 제공하는 모델 제공자에게만 적용된다. 예를 들어, 일부 제공자는 최대 출력 토큰 수에 대한 설정을 제공하지 않으므로, 이 경우 max_tokens는 지원되지 않는다.
  • 표준 매개변수는 현재 자체 통합 패키지를 가진 통합에만 적용된다(예: langchain-openai, langchain-anthropic 등). langchain-community에 있는 모델에는 적용되지 않는다.
  • Chat models은 해당 통합에 특정한 다른 매개변수도 받아들인다. 챗 모델이 지원하는 모든 매개변수를 확인하려면 해당 모델의 API 참조를 확인하라.

참고

일부 Chat models은 도구 호출에 특화된 미세 조정을 거쳤으며, 이를 위한 전용 API를 제공한다. 일반적으로 이러한 모델은 도구 호출에 비미세 조정된 모델보다 더 우수하며, 도구 호출이 필요한 사용 사례에 권장된다. 자세한 내용은 도구 호출 섹션을 참조하라.

챗 모델 사용 방법에 대한 자세한 내용은 관련 가이드를 참조하라.

멀티모달리티 (Multimodality)

일부 챗 모델은 멀티모달로서 이미지, 오디오, 심지어 비디오까지 입력으로 받을 수 있다. 그러나 이러한 모델은 아직 일반적이지 않으며, 모델 제공자들이 최적의 API 정의 방법에 대해 표준화하지 않은 상태이다. 멀티모달 출력은 더 드물며, 이에 따라 우리의 멀티모달 추상화는 가볍게 유지하고 있으며, 이 분야가 성숙해짐에 따라 API와 상호작용 패턴을 더욱 확고히 할 계획이다.

LangChain에서 멀티모달 입력을 지원하는 대부분의 챗 모델은 OpenAI의 콘텐츠 블록 형식으로 입력값을 받는다. 현재로서는 이미지 입력에만 제한되어 있다. 비디오 및 기타 바이트 입력을 지원하는 Gemini와 같은 모델의 경우, API는 모델 고유의 표현 방식을 지원한다.

멀티모달 모델 사용 방법에 대한 자세한 내용은 관련 가이드를 참조하라.

멀티모달 모델을 지원하는 LangChain 모델 제공자의 전체 목록은 이 표를 확인하라.

LLMs (대규모 언어 모델)

주의

단지 텍스트를 입력받고 텍스트를 출력하는 LLM은 대체로 오래되었거나 낮은 수준의 모델이다. 많은 최신 인기 모델들은 비대화형 사용 사례에서도 챗 모델로 사용하는 것이 더 적합하다.

위에 언급된 챗 모델 섹션을 참조하는 것이 좋다.

LLM은 문자열을 입력으로 받아 문자열을 반환하는 언어 모델이다. 이는 전통적으로 오래된 모델이다(새로운 모델은 일반적으로 챗 모델이다).

기본적으로 이러한 모델들은 문자열을 입력받아 문자열을 출력하지만, LangChain의 래퍼를 사용하면 이러한 모델들도 메시지를 입력으로 받을 수 있다. 이를 통해 챗 모델과 동일한 인터페이스를 제공할 수 있다. 메시지가 입력으로 전달되면, 이는 내부적으로 문자열로 포맷되어 기본 모델에 전달된다.

LangChain은 LLM을 호스팅하지 않으며, 서드파티 통합에 의존한다.

LLM 사용 방법에 대한 자세한 내용은 관련 가이드를 참조하라.

Messages (메시지)

일부 언어 모델은 메시지 리스트를 입력으로 받아 메시지를 반환한다. 이러한 메시지에는 몇 가지 유형이 있다. 모든 메시지에는 role, content, response_metadata 속성이 있다.

  • role: 메시지를 말하는 주체를 설명한다. 표준 역할로는 "user", "assistant", "system", "tool"이 있습니다. LangChain은 각 역할에 대해 다른 메시지 클래스를 제공한다.
  • content: 메시지의 내용을 설명한다. 이는 다음과 같이 다양할 수 있다.
    • 문자열(대부분의 모델이 이 유형의 내용을 처리함)
    • 딕셔너리 목록(멀티모달 입력에서 사용되며, 딕셔너리에는 해당 입력 유형 및 위치에 대한 정보가 포함됨)
  • 선택적으로, 메시지에는 name 속성이 있을 수 있으며, 이를 통해 동일한 역할을 가진 여러 발화자를 구분할 수 있다. 예를 들어, 두 명의 사용자가 대화 기록에 있을 때 이를 구분하는 데 유용하다. 모든 모델이 이를 지원하지는 않는다.

HumanMessage (인간 메시지)

이 메시지는 "user" 역할을 나타낸다.

AIMessage (AI 메시지)

이 메시지는 "assistant" 역할을 나타낸다. content 속성 외에도 이 메시지에는 response_metadata 속성이 있다.

  • response_metadata: 응답에 대한 추가 메타데이터를 포함한다. 이 데이터는 모델 제공자에 따라 다를 수 있으며, 로그 확률 및 토큰 사용량과 같은 정보가 저장될 수 있다.
  • tool_calls: 언어 모델이 도구를 호출하려는 결정을 나타내며, AIMessage 출력의 일부로 포함된다. 이 속성은 .tool_calls 속성을 통해 접근할 수 있으며, 도구 호출에 대한 정보를 담고 있는 딕셔너리 목록을 반환한다.

SystemMessage (시스템 메시지)

이 메시지는 "system" 역할을 나타내며, 모델에게 특정 행동을 지시한다. 모든 모델 제공자가 이를 지원하지는 않는다.

ToolMessage (도구 메시지)

이 메시지는 "tool" 역할을 나타내며, 도구 호출의 결과를 포함한다. rolecontent 외에도, 이 메시지에는 다음과 같은 속성이 있다.

  • tool_call_id: 호출된 도구에 대한 ID를 전달한다.
  • artifact: 도구 실행의 임의의 결과물을 전달할 수 있으며, 이는 추적에는 유용하지만 모델에 보내지 않아야 할 항목들이다.

(Legacy) FunctionMessage (기능 메시지)

이 메시지는 OpenAI의 레거시 기능 호출 API에 해당하는 메시지 유형이다. 최신 도구 호출 API에 해당하는 ToolMessage를 대신 사용해야 한다. FunctionMessage는 기능 호출의 결과를 나타내며, rolecontent 외에도 결과를 생성하는데 사용된 기능의 이름을 전달하는 name 속성을 가지고 있다.

프롬프트 템플릿 (Prompt Templates)

프롬프트 템플릿은 사용자 입력과 매개변수를 언어 모델에 대한 지시사항으로 변환하는 데 도움을 준다. 이를 통해 모델이 맥락을 이해하고 관련성 있고 일관된 언어 기반 출력을 생성할 수 있도록 유도할 수 있다.

프롬프트 템플릿은 딕셔너리를 입력으로 받는다. 이 딕셔너리의 각 키는 프롬프트 템플릿에서 채워야 할 변수에 해당한다.

프롬프트 템플릿은 PromptValue를 출력한다. 이 PromptValue는 LLM이나 ChatModel에 전달될 수 있으며, 문자열이나 메시지 목록으로도 변환될 수 있다. PromptValue는 문자열과 메시지 사이를 쉽게 전환할 수 있도록 만들어졌다.

프롬프트 템플릿에는 몇 가지 다른 유형이 있다.

String PromptTemplates (문자열 프롬프트 템플릿)

이 템플릿은 단일 문자열을 포맷하는 데 사용되며, 일반적으로 간단한 입력에 사용된다. 예를 들어, 다음과 같은 방식으로 PromptTemplate을 구성하고 사용할 수 있다.

from langchain_core.prompts import PromptTemplate

prompt_template = PromptTemplate.from_template("Tell me a joke about {topic}")
prompt_template.invoke({"topic": "cats"})

API Reference: PromptTemplate

ChatPromptTemplates (채팅 프롬프트 템플릿)

이 템플릿은 메시지 목록을 포맷하는 데 사용된다. 이러한 "템플릿"은 자체적으로 여러 템플릿으로 구성되어 있다. 예를 들어, 다음과 같은 방식으로 ChatPromptTemplate을 구성하고 사용할 수 있다.

from langchain_core.prompts import ChatPromptTemplate

prompt_template = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant"),
    ("user", "Tell me a joke about {topic}")
])

prompt_template.invoke({"topic": "cats"})

위 예에서, ChatPromptTemplate은 호출될 때 두 개의 메시지를 생성한다. 첫 번째는 변수를 포맷할 필요가 없는 시스템 메시지이며, 두 번째는 사용자가 전달한 topic 변수로 포맷되는 인간 메시지(HumanMessage)이다.

API Reference: PromptTemplate

MessagesPlaceholder (메시지 플레이스홀더)

이 프롬프트 템플릿은 특정 위치에 메시지 목록을 추가하는 역할을 한다. 앞서 언급한 ChatPromptTemplate에서는 두 개의 메시지를 각각 하나의 문자열로 구성하는 방법을 보았다. 하지만 사용자가 특정 위치에 삽입할 메시지 목록을 전달하고자 한다면 어떻게 할까? MessagesPlaceholder를 사용하여 이를 수행할 수 있다.

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage

prompt_template = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant"),
    MessagesPlaceholder("msgs")
])

prompt_template.invoke({"msgs": [HumanMessage(content="hi!")]})

API Reference:ChatPromptTemplate | MessagesPlaceholder | HumanMessage

이 코드는 두 개의 메시지 목록을 생성하며, 첫 번째는 시스템 메시지이고, 두 번째는 우리가 전달한 HumanMessage이다. 만약 5개의 메시지를 전달했다면, 총 6개의 메시지(시스템 메시지 + 전달된 5개의 메시지)가 생성된다. 이는 특정 위치에 메시지 목록을 삽입하는 데 유용하다.

MessagesPlaceholder 클래스를 명시적으로 사용하지 않고 동일한 작업을 수행하는 또 다른 방법은 다음과 같다.

prompt_template = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant"),
    ("placeholder", "{msgs}")  # <-- 변경된 부분
])

프롬프트 템플릿 사용에 대한 자세한 내용은 관련 가이드를 참조하라.

예제 선택기 (Example Selectors)

더 나은 성능을 얻기 위한 일반적인 프롬프트 기법 중 하나는 프롬프트의 일부로 예제를 포함하는 것이다. 이를 Few-Shot Prompting이라고 한다. 이 기법은 언어 모델에게 어떻게 행동해야 하는지에 대한 구체적인 예제를 제공한다. 때로는 이러한 예제가 프롬프트에 하드코딩되지만, 더 복잡한 상황에서는 동적으로 선택하는 것이 좋을 수 있다. Example Selectors는 예제를 선택하고 이를 프롬프트로 포맷하는 역할을 하는 클래스이다.

예제 선택기 사용에 대한 자세한 내용은 관련 가이드를 참조하라.

출력 파서 (Output Parsers)

참고:
이 정보는 모델의 텍스트 출력을 받아 더 구조화된 표현으로 파싱하는 파서에 대한 것입니다. 점점 더 많은 모델들이 함수(또는 도구) 호출을 지원하고 있으며, 이 방식은 이러한 작업을 자동으로 처리한다. 출력 파싱보다는 함수/도구 호출을 사용하는 것이 권장된다. 관련 문서는 여기를 참조하라.

출력 파서는 모델의 출력을 받아서 후속 작업에 더 적합한 형식으로 변환하는 역할을 한다. 이는 LLM(대규모 언어 모델)을 사용하여 구조화된 데이터를 생성하거나, 채팅 모델과 LLM의 출력을 정규화할 때 유용하다.

LangChain에는 다양한 종류의 출력 파서가 있다. 아래 표는 LangChain이 지원하는 출력 파서들의 목록과 그들의 다양한 정보를 보여준다.

Name: 출력 파서의 이름이다.

Supports Streaming: 출력 파서가 스트리밍을 지원하는지 여부를 나타낸다.

Has Format Instructions: 출력 파서에 형식 지침이 있는지 여부를 나타낸다. 일반적으로 사용 가능하지만, 다음의 경우에는 예외이다.

  • (a) 원하는 스키마가 프롬프트가 아닌 다른 매개변수(예: OpenAI 함수 호출)에 지정된 경우
  • (b) OutputParser가 다른 OutputParser를 래핑하는 경우

Calls LLM: 이 출력 파서 자체가 LLM을 호출하는지 여부를 나타낸다. 이는 보통 잘못된 형식의 출력을 수정하려는 출력 파서에서만 발생한다.

Input Type: 예상 입력 타입을 나타낸다. 대부분의 출력 파서는 문자열과 메시지 둘 다 처리할 수 있지만, OpenAI Functions와 같은 일부 파서는 특정 kwargs가 포함된 메시지를 필요로 한다.

Output Type: 파서가 반환하는 객체의 출력 타입입이다.

Description: 이 출력 파서에 대한 설명과 사용 시기를 제공한다.

이름 스트리밍 지원 포맷 지침 있음 LLM 호출 입력 타입 출력 타입 설명
JSON str | Message JSON Object 지정된 JSON 객체를 반환한다. Pydantic 모델을 지정하면 해당 모델의 JSON을 반환한다. 함수 호출을 사용하지 않는 구조화된 데이터를 얻는 데 가장 신뢰할 수 있는 출력 파서이다.
XML str | Message dict 태그의 딕셔너리를 반환한다. XML 출력이 필요할 때 사용하라. XML 작성에 능한 모델(예: Anthropic의 모델)과 함께 사용하라.
CSV str | Message List[str] 콤마로 구분된 값을 리턴한다.
OutputFixing str | Message 다른 출력 파서(output parser)를 감싼다. 만약 그 출력 파서에서 오류가 발생하면, 이 파서는 오류 메시지와 잘못된 출력을 LLM에 전달하고 출력을 수정하도록 요청한다.
RetryWithError str | Message 다른 출력 파서(output parser)를 감싼다. 만약 그 출력 파서에서 오류가 발생하면, 이 파서는 원래 입력값, 잘못된 출력값, 오류 메시지와 함께 원래의 지시사항도 LLM에 전달하여 수정하도록 요청한다. OutputFixingParser와 비교했을 때, 이 파서는 원래 지시사항도 함께 보낸다.
Pydantic str | Message pydantic.BaseModel 사용자가 정의한 Pydantic 모델을 받아 해당 형식으로 데이터를 반환한다.
YAML str | Message pydantic.BaseModel 사용자가 정의한 Pydantic 모델을 받아 해당 형식으로 데이터를 반환한다.
PandasDataFrame str | Message Dict[str, str] pandas DataFrame과 함께 작업할 때 유용하다.
Enum str | Message Enum 응답을 제공된 열거형 값 중 하나로 파싱한다.
Datetime str | Message datetime.datetime 응답을 날짜 및 시간 문자열로 파싱한다.
Structured str | Message Dict[str, str] 구조화된 정보를 반환하는 출력 파서이다. 필드에 문자열만 허용하기 때문에 다른 출력 파서들보다 덜 강력하다. 작은 LLM과 함께 작업할 때 유용할 수 있다.

출력 파서를 사용하는 방법에 대한 자세한 내용은 관련 가이드를 참조하라.

채팅 기록 (Chat history)

대부분의 LLM 애플리케이션은 대화형 인터페이스를 가지고 있다. 대화의 필수 요소 중 하나는 이전 대화에서 언급된 정보를 참조할 수 있는 능력이다. 최소한의 요구 사항으로, 대화 시스템은 과거 메시지의 일부를 직접 접근할 수 있어야 한다.

ChatHistory는 LangChain에서 임의의 체인을 래핑할 수 있는 클래스이다. 이 ChatHistory는 기본 체인의 입력과 출력을 추적하며, 이를 메시지 데이터베이스에 메시지로 추가한다. 이후의 상호작용에서는 이 메시지들을 불러와 체인의 입력으로 전달하게 된다.

문서 (Documents)

LangChain의 Document 객체는 특정 데이터에 대한 정보를 포함한다. 이 객체에는 두 가지 속성이 있다.

  • page_content: str: 이 문서의 콘텐츠로, 현재는 문자열 형식이다.
  • metadata: dict: 이 문서와 연관된 임의의 메타데이터이다. 문서 ID, 파일 이름 등을 추적할 수 있다.

문서 로더 (Document loaders)

이 클래스들은 Document 객체를 로드한다. LangChain은 다양한 데이터 소스(예: Slack, Notion, Google Drive 등)와의 수백 가지 통합을 통해 데이터를 로드할 수 있다.

각 DocumentLoader는 고유한 특정 매개변수를 가지지만, 모두 .load 메서드로 호출될 수 있다. 사용 예는 다음과 같다.

from langchain_community.document_loaders.csv_loader import CSVLoader

loader = CSVLoader(
    ...  # <-- 통합에 필요한 특정 매개변수
)
data = loader.load()

API Reference:CSVLoader

문서 로더 사용법에 대해서 더 알고 싶으면 여기를 참조하라.

텍스트 분할기 (Text splitters)

문서를 로드한 후에는 애플리케이션에 더 적합하게 변환해야 할 경우가 많다. 가장 간단한 예는 긴 문서를 모델의 컨텍스트 윈도우에 맞출 수 있는 작은 청크로 분할하는 것이다. LangChain에는 문서를 쉽게 분할, 결합, 필터링 및 조작할 수 있도록 돕는 여러 내장 문서 변환기가 있다.

긴 텍스트를 다룰 때는 텍스트를 여러 청크로 분할하는 것이 필요하다. 이 과정은 간단해 보이지만, 많은 잠재적 복잡성을 내포하고 있다. 이상적으로는 의미론적으로 관련된 텍스트 조각을 함께 유지하는 것이 좋다. "의미론적으로 관련된" 것이 무엇을 의미하는지는 텍스트 유형에 따라 다를 수 있다.

텍스트 분할기는 다음과 같은 방식으로 작동한다.

  1. 텍스트를 작고 의미론적으로 의미 있는 청크(종종 문장)로 분할한다.
  2. 이 작은 청크들을 결합하여 특정 크기(일부 함수로 측정됨)에 도달할 때까지 큰 청크를 만든다.
  3. 해당 크기에 도달하면, 해당 청크를 하나의 텍스트 조각으로 만들고, 새로운 텍스트 청크를 생성하는 동안 약간의 중복을 유지하여 청크 간의 컨텍스트를 유지한다.

따라서 텍스트 분할기를 두 가지 축을 따라 사용자 지정할 수 있다.

  • 텍스트를 분할하는 방법
  • 청크 크기를 측정하는 방법

텍스트 분할기의 사용법에 대해서 더 알고 싶으면 여기를 참조하라.

임베딩 모델 (Embedding models)

임베딩 모델은 텍스트 조각의 벡터 표현을 생성한다. 벡터는 텍스트의 의미를 포착하는 숫자 배열로 생각할 수 있다. 이렇게 텍스트를 표현하면, 수학적 연산을 통해 의미가 가장 유사한 다른 텍스트를 검색하는 등의 작업을 수행할 수 있다. 이러한 자연어 검색 기능은 LLM이 쿼리에 효과적으로 응답하기 위해 필요한 관련 데이터를 제공하는 다양한 유형의 컨텍스트 검색의 기반이 된다.

[출처] langchain conceptual guide (https://python.langchain.com/v0.2/docs/concepts/)

Embeddings 클래스는 텍스트 임베딩 모델과의 인터페이스를 위해 설계된 클래스이다. OpenAI, Cohere, Hugging Face 등과 같은 다양한 임베딩 모델 제공자 및 로컬 모델이 있으며, 이 클래스는 이들 모두에 대해 표준 인터페이스를 제공하기 위해 설계되었다.

LangChain의 기본 Embeddings 클래스는 문서를 임베딩하는 메서드와 쿼리를 임베딩하는 메서드, 두 가지 메서드를 제공한다. 전자는 여러 텍스트를 입력으로 받고, 후자는 단일 텍스트를 입력으로 받는다. 이를 별도의 메서드로 분리한 이유는 일부 임베딩 제공자가 문서(검색 대상)와 쿼리(검색 쿼리 자체)에 대해 서로 다른 임베딩 방법을 제공하기 때문이다.

임베딩 모델을 사용하는 방법에 대한 자세한 내용은 관련 사용 방법 가이드를 참조하라.

벡터 스토어 (Vector stores)

비정형 데이터를 저장하고 검색하는 가장 일반적인 방법 중 하나는 데이터를 임베딩하고, 결과로 나온 임베딩 벡터를 저장한 후 쿼리 시 비정형 쿼리를 임베딩하여 임베딩된 쿼리와 '가장 유사한' 임베딩 벡터를 검색하는 것이다. 벡터 스토어는 임베딩된 데이터를 저장하고 벡터 검색을 수행하는 작업을 처리해 준다.

대부분의 벡터 스토어는 임베딩된 벡터에 대한 메타데이터를 저장하고, 유사성 검색 전에 해당 메타데이터를 기반으로 필터링을 지원하여 반환된 문서에 대한 제어를 더할 수 있다.

벡터 스토어를 검색기 인터페이스로 변환하려면 다음과 같이 하면 된다.

vectorstore = MyVectorStore()
retriever = vectorstore.as_retriever()

벡터 스토어 사용 방법에 대한 자세한 내용은 관련 사용 방법 가이드를 참조하라.

검색기 (Retrievers)

검색기는 비정형 쿼리를 입력받아 문서를 반환하는 인터페이스이다. 이는 벡터 스토어보다 일반적인 개념입니다. 검색기는 문서를 저장할 필요는 없으며, 단지 문서를 반환(또는 검색)하면 된다. 검색기는 벡터 스토어에서 생성할 수 있지만, 위키피디아 검색이나 아마존 Kendra 같은 다양한 검색 방식도 포함할 수 있을 정도로 넓은 범위를 다룬다.

검색기는 문자열 쿼리를 입력으로 받아 문서의 리스트를 출력으로 반환한다.

검색기 사용 방법에 대한 자세한 내용은 관련 사용 방법 가이드를 참조하라.

키-값 스토어 (Key-value stores)

문서당 여러 개의 벡터를 사용하는 인덱싱 및 검색이나 임베딩 캐싱과 같은 일부 기술에서는 키-값(KV) 저장소 형태가 유용할 수 있다.

LangChain은 임의의 데이터를 저장할 수 있는 BaseStore 인터페이스를 포함하고 있다. 그러나 KV 저장소가 필요한 LangChain 컴포넌트는 이진 데이터를 저장하는 BaseStore[str, bytes] 인스턴스를 사용하며, 내부적으로는 특정 요구에 맞게 데이터를 인코딩 및 디코딩하는 작업을 처리한다.

사용자로서, 다양한 유형의 데이터를 위해 서로 다른 스토어를 생각할 필요 없이 하나의 스토어만 생각하면 된다.

인터페이스

모든 BaseStores는 다음과 같은 인터페이스를 지원한다. 이 인터페이스는 여러 개의 키-값 쌍을 한 번에 수정할 수 있도록 허용한다.

  • mget(key: Sequence[str]) -> List[Optional[bytes]]: 여러 키의 내용을 가져오며, 키가 존재하지 않을 경우 None을 반환한다.
  • mset(key_value_pairs: Sequence[Tuple[str, bytes]]) -> None: 여러 키의 내용을 설정한다.
  • mdelete(key: Sequence[str]) -> None: 여러 키를 삭제한다.
  • yield_keys(prefix: Optional[str] = None) -> Iterator[str]: 스토어 내의 모든 키를 반환하며, 선택적으로 접두사를 기준으로 필터링할 수 있다.

키-값 저장소 구현에 대한 자세한 내용은 관련 섹션을 참조하라.

도구 (Tools)

도구는 모델에 의해 호출되도록 설계된 유틸리티이다. 입력은 모델이 생성하도록 설계되었고, 출력은 다시 모델로 전달되도록 설계되었다. 도구는 모델이 코드의 일부를 제어하거나 외부 API를 호출해야 할 때 필요하다.

도구의 구성 요소

  • 도구 이름: 도구의 이름
  • 설명: 도구가 수행하는 작업에 대한 설명
  • JSON 스키마: 도구의 입력을 정의하는 JSON 스키마
  • 함수: 도구가 수행할 작업을 정의한 함수 (비동기 함수도 선택적으로 포함 가능)

도구가 모델에 연결되면, 도구의 이름, 설명, JSON 스키마가 모델에 컨텍스트로 제공된다. 도구 목록과 명령 집합이 주어지면, 모델은 특정 입력을 사용하여 하나 이상의 도구를 호출할 수 있다. 일반적인 사용 예는 다음과 같다.

tools = [...]  # 도구 목록 정의
llm_with_tools = llm.bind_tools(tools)
ai_msg = llm_with_tools.invoke("do xyz...")
# -> AIMessage(tool_calls=[ToolCall(...), ...], ...)

모델에서 반환된 AIMessage는 도구 호출(tool_calls)을 포함할 수 있다. 도구가 선택되어 호출되면, 그 결과는 모델로 다시 전달되어 작업을 완료할 수 있다. 도구를 호출하고 응답을 전달하는 방법에는 일반적으로 두 가지가 있다.

인자(arguments)만으로 도구 호출

인자만으로 도구를 호출하면, 도구의 원시 출력(일반적으로 문자열)을 반환받는다. 일반적으로 아래와 같은 방식이다.

# You will want to previously check that the LLM returned tool calls
tool_call = ai_msg.tool_calls[0]
# ToolCall(args={...}, id=..., ...)
tool_output = tool.invoke(tool_call["args"])
tool_message = ToolMessage(
    content=tool_output,
    tool_call_id=tool_call["id"],
    name=tool_call["name"]
)

출력 내용(content) 필드는 일반적으로 모델로 다시 전달된다. 원시 도구 응답을 모델로 전달하지 않으려면, 출력 내용을 변환하여 아티팩트로 전달할 수 있다.

... # Same code as above
response_for_llm = transform(response)
tool_message = ToolMessage(
    content=response_for_llm,
    tool_call_id=tool_call["id"],
    name=tool_call["name"],
    artifact=tool_output
)

ToolCall을 사용한 도구 호출

도구를 호출하는 다른 방법은 모델이 생성한 전체 ToolCall을 사용하는 것이다. 이 방법으로 호출하면 도구는 ToolMessage를 반환한다. 이 방법의 장점은 도구 출력을 ToolMessage로 변환하는 로직을 직접 작성할 필요가 없다는 것이다.

tool_call = ai_msg.tool_calls[0]
# -> ToolCall(args={...}, id=..., ...)
tool_message = tool.invoke(tool_call)
# -> ToolMessage(
    content="tool result foobar...",
    tool_call_id=...,
    name="tool_name"
)

이 방법으로 도구를 호출하고 ToolMessage아티팩트를 포함하려면, 도구가 두 가지 항목을 반환해야 한다. 아티팩트를 반환하는 도구 정의에 대한 자세한 내용은 여기에서 확인할 수 있다.

Best Practices

모델에서 사용할 도구를 설계할 때 다음을 고려하는 것이 중요하다.

  • 명시적인 도구 호출 API가 있는 챗 모델이 비세분화된 모델보다 도구 호출에 더 능숙하다.
  • 도구의 이름, 설명, JSON 스키마가 잘 설계된 경우 모델의 성능이 향상된다. 이는 또 다른 형태의 프롬프트 엔지니어링이다.
  • 단순하고 좁은 범위의 도구가 복잡한 도구보다 모델 사용에 더 적합하다.

관련 항목

툴킷 (Toolkits)

툴킷은 특정 작업을 위해 함께 사용되도록 설계된 도구 모음이다. 툴킷은 편리한 로딩 방법을 제공한다.

모든 툴킷은 도구 목록을 반환하는 get_tools 메서드를 노출한다. 다음과 같이 사용할 수 있다.

# 툴킷 초기화
toolkit = ExampleToolkit(...)

# 도구 목록 가져오기
tools = toolkit.get_tools()

에이전트 (Agents)

언어 모델은 자체적으로는 행동을 취할 수 없으며, 단순히 텍스트를 출력할 뿐이다. LangChain의 주요 사용 사례 중 하나는 에이전트를 만드는 것이다. 에이전트는 LLM(대형 언어 모델)을 추론 엔진으로 사용하여 어떤 행동을 취할지, 그 행동에 필요한 입력이 무엇인지 결정하는 시스템이다. 이러한 행동의 결과는 다시 에이전트에 피드백되어 추가 행동이 필요한지, 아니면 종료해도 되는지를 결정한다.

LangGraph는 특히 제어 가능하고 사용자 정의가 가능한 에이전트를 만들기 위해 LangChain을 확장한 것이다. 에이전트 개념에 대한 보다 깊이 있는 개요는 LangGraph의 문서를 참조하라.

LangChain에는 이전부터 사용된 에이전트 개념인 AgentExecutor가 있다. AgentExecutor는 기본적으로 에이전트의 실행 환경으로, 시작하기에 좋은 도구였으나, 에이전트가 점점 더 커스터마이즈될수록 충분히 유연하지 못했다. 이를 해결하기 위해 LangGraph를 개발하여 보다 유연하고 고도로 제어 가능한 런타임 환경을 제공하게 되었다.

만약 여전히 AgentExecutor를 사용 중이라면 걱정하지 마라. 여전히 AgentExecutor를 사용하는 방법에 대한 가이드가 있으며, 그러나 LangGraph로의 전환을 시작하는 것이 권장된다. 이를 돕기 위해 전환 가이드를 준비했다.

ReAct 에이전트

에이전트를 구축하는 데 인기 있는 아키텍처 중 하나는 ReAct이다. ReAct는 "Reason"과 "Act"의 결합을 나타내며, 이름에서도 알 수 있듯이 추론과 행동을 반복하는 과정을 의미한다.

일반적인 흐름은 다음과 같다.

  1. 모델이 입력과 이전의 관찰에 대한 응답으로 어떤 단계를 취할지 "고민"한다.
  2. 모델이 사용 가능한 도구에서 하나를 선택하거나 사용자에게 응답할지 결정한다.
  3. 모델이 선택된 도구에 대한 인수를 생성한다.
  4. 에이전트 런타임(Executor)이 선택된 도구를 호출하고 생성된 인수로 실행한다.
  5. Executor는 도구 호출 결과를 모델에 관찰값으로 다시 전달한다.
  6. 이 과정은 에이전트가 응답을 선택할 때까지 반복된다.

일반적인 프롬프트 기반 구현도 존재하지만, 가장 신뢰할 수 있는 구현은 도구 호출과 같은 기능을 사용하여 출력 형식을 신뢰성 있게 만들고 변동성을 줄이는 방식이다.

자세한 내용은 LangGraph 문서나 LangGraph로의 전환에 대한 구체적인 정보를 제공하는 가이드를 참조하라.

콜백 (Callbacks)

LangChain은 LLM 애플리케이션의 다양한 단계에 훅(hook)을 걸 수 있는 콜백 시스템을 제공합니다. 이는 로깅, 모니터링, 스트리밍 등의 작업에 유용하다.

이벤트에 구독하려면 API 전반에서 사용할 수 있는 callbacks 인자를 사용하면 된다. 이 인자는 하나 이상의 메서드를 구현하는 핸들러 객체들의 리스트로, 아래에 자세히 설명된 메서드들을 구현해야 한다.

콜백 이벤트 (Callback Events)

이벤트 트리거 조건 관련 메서드
Chat 모델 시작 채팅 모델이 시작될 때 on_chat_model_start
LLM 시작 LLM이 시작될 때 on_llm_start
LLM 새 토큰 LLM 또는 채팅 모델이 새 토큰을 생성할 때 on_llm_new_token
LLM 종료 LLM 또는 채팅 모델이 종료될 때 on_llm_end
LLM 오류 LLM 또는 채팅 모델에 오류가 발생할 때 on_llm_error
체인 시작 체인이 실행되기 시작할 때 on_chain_start
체인 종료 체인이 종료될 때 on_chain_end
체인 오류 체인에서 오류가 발생할 때 on_chain_error
도구 시작 도구가 실행될 때 on_tool_start
도구 종료 도구가 종료될 때 on_tool_end
도구 오류 도구에서 오류가 발생할 때 on_tool_error
에이전트 액션 에이전트가 액션을 취할 때 on_agent_action
에이전트 종료 에이전트가 종료될 때 on_agent_finish
리트리버 시작 리트리버가 시작될 때 on_retriever_start
리트리버 종료 리트리버가 종료될 때 on_retriever_end
리트리버 오류 리트리버에서 오류가 발생할 때 on_retriever_error
텍스트 임의의 텍스트가 실행될 때 on_text
재시도 재시도 이벤트가 실행될 때 on_retry

콜백 핸들러 (Callback Handlers)

콜백 핸들러는 동기(sync) 또는 비동기(async) 방식으로 구현할 수 있다.

런타임 시, LangChain은 적절한 콜백 매니저(예: CallbackManager 또는 AsyncCallbackManager)를 설정하여, 이벤트가 발생할 때마다 각 "등록된" 콜백 핸들러에서 적절한 메서드를 호출한다.

콜백 전달 (Passing Callbacks)

API 전반의 대부분의 객체(모델, 도구, 에이전트 등)에 callbacks 속성이 두 가지 방식으로 제공된다.

  1. 요청 시점의 콜백(Request Time Callbacks): 요청 시점에 입력 데이터와 함께 전달된다. 모든 표준 실행 가능한 객체에서 사용할 수 있으며, 이 콜백은 정의된 객체의 모든 하위 객체에 상속된다. 예: chain.invoke({"number": 25}, {"callbacks": [handler]}).
  2. 생성자 콜백(Constructor Callbacks): 객체의 생성자에 인수로 전달된다. 이 콜백은 정의된 객체에만 한정되며, 하위 객체에는 상속되지 않는다. 예: chain = TheNameOfSomeChain(callbacks=[handler]).

주의사항

  • 생성자 콜백은 정의된 객체에만 한정되며, 하위 객체에는 상속되지 않는다.

커스텀 체인이나 실행 가능한 객체를 생성하는 경우, 요청 시점의 콜백을 모든 하위 객체로 전파하는 것을 기억해야 한다.

Python <= 3.10에서의 비동기 실행

RunnableLambda, RunnableGenerator, 또는 다른 실행 가능한 객체를 호출하는 도구가 Python 3.10 이하에서 비동기로 실행되는 경우, 콜백을 하위 객체로 수동으로 전파해야 한다. 이 경우 LangChain은 자동으로 콜백을 전파할 수 없기 때문이다.

이로 인해 커스텀 실행 가능한 객체나 도구에서 이벤트가 발생하지 않을 수 있다.

콜백 사용 방법에 대한 구체적인 정보는 관련 가이드를 참조하라.

기술 (Techniques)

스트리밍 (Streaming)

개별 LLM 호출은 전통적인 리소스 요청보다 더 오랜 시간이 걸리는 경우가 많다. 더 복잡한 체인이나 여러 추론 단계를 요구하는 에이전트를 구축할 때 이 문제는 더욱 심각해진다.

다행히도 LLM은 출력물을 반복적으로 생성하므로 최종 응답이 준비되기 전에 의미 있는 중간 결과를 표시할 수 있다. 출력물이 사용 가능해지는 즉시 이를 소비하는 것은 LLM을 사용해 애플리케이션을 구축할 때 지연 문제를 해결하는 중요한 UX 요소가 되었다. LangChain은 스트리밍에 대해 일급 지원을 제공하는 것을 목표로 한다.

아래에서는 LangChain에서 스트리밍과 관련된 개념과 고려 사항에 대해 논의하겠다.

.stream().astream()

LangChain의 대부분의 모듈에는 편리한 스트리밍 인터페이스로서 .stream() 메서드(비동기 환경에서는 이에 상응하는 .astream() 메서드)가 포함되어 있다. .stream()은 이터레이터를 반환하며, 이를 단순한 for 루프로 소비할 수 있다. 다음은 채팅 모델을 사용하는 예제이다.

from langchain_anthropic import ChatAnthropic

model = ChatAnthropic(model="claude-3-sonnet-20240229")

for chunk in model.stream("what color is the sky?"):
    print(chunk.content, end="|", flush=True)

API Reference:ChatAnthropic

모델(또는 다른 구성 요소)이 스트리밍을 네이티브로 지원하지 않는 경우에도 이 이터레이터는 단일 청크를 반환하므로, 호출할 때 동일한 일반 패턴을 사용할 수 있다. .stream()을 사용하면 추가 설정 없이 스트리밍 모드에서 자동으로 모델을 호출한다.

출력된 각 청크의 유형은 구성 요소의 유형에 따라 다르다. 예를 들어, 채팅 모델은 AIMessageChunks를 반환한다. 이 메서드는 LangChain Expression Language의 일부이므로, 출력 파서를 사용하여 각 청크의 형식을 변환할 수 있다.

자세한 사용법은 이 가이드를 참고하라.

.astream_events()

.stream() 메서드는 직관적이지만 체인의 최종 생성 값만 반환할 수 있다. 이는 단일 LLM 호출에는 적합하지만, 여러 LLM 호출을 결합한 더 복잡한 체인을 구축할 때는 체인의 중간 값을 최종 출력과 함께 사용하고 싶을 수 있다. 예를 들어, 문서 위에서 채팅을 구축할 때 최종 생성과 함께 소스를 반환하고 싶을 때가 있다.

이를 해결하기 위해 콜백을 사용하거나, .assign() 호출을 연결하여 중간 값을 최종 결과로 전달하는 체인을 구성하는 방법이 있다. 하지만 LangChain에는 이러한 유연성과 .stream()의 편리함을 결합한 .astream_events() 메서드도 포함되어 있다. 이 메서드를 호출하면 다양한 이벤트 유형을 필터링하고 프로젝트의 필요에 따라 처리할 수 있는 이터레이터를 반환한다.

다음은 스트리밍된 채팅 모델 출력이 포함된 이벤트만 출력하는 작은 예제이다.

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_anthropic import ChatAnthropic

model = ChatAnthropic(model="claude-3-sonnet-20240229")

prompt = ChatPromptTemplate.from_template("tell me a joke about {topic}")
parser = StrOutputParser()
chain = prompt | model | parser

async for event in chain.astream_events({"topic": "parrot"}, version="v2"):
    kind = event["event"]
    if kind == "on_chat_model_stream":
        print(event, end="|", flush=True)

API Reference:StrOutputParser | ChatPromptTemplate | ChatAnthropic

이는 콜백 이벤트를 이터레이터로 처리하는 것과 유사하게 생각할 수 있으며, 거의 모든 LangChain 구성 요소에서 사용할 수 있다.

.astream_events()의 자세한 사용법과 사용 가능한 이벤트 목록은 이 가이드를 참고하라.

콜백 (Callbacks)

LangChain에서 LLM의 출력을 스트리밍하는 가장 기본적인 방법은 콜백 시스템을 사용하는 것이다. 콜백 핸들러를 전달하여 on_llm_new_token 이벤트를 처리할 수 있다. 이 컴포넌트가 호출되면, 해당 컴포넌트에 포함된 LLM이나 채팅 모델은 생성된 토큰을 콜백에 전달한다. 콜백 내에서 토큰을 HTTP 응답 같은 다른 대상으로 전달할 수 있다. on_llm_end 이벤트를 처리하여 필요한 정리 작업을 수행할 수도 있다.

콜백 사용에 대한 구체적인 정보는 이 섹션을 참조하라.

콜백은 LangChain에서 스트리밍을 도입한 첫 번째 기술이었다. 강력하고 일반화 가능하지만, 개발자에게는 다소 번거로울 수 있다. 예를 들어:

  • 결과를 수집하기 위해 명시적으로 집계기 또는 다른 스트림을 초기화하고 관리해야 한다.
  • 실행 순서가 명시적으로 보장되지 않으며, 이론적으로 콜백이 .invoke() 메서드가 종료된 후에 실행될 수 있다.
  • 제공업체는 종종 스트리밍 출력을 전달하기 위해 추가 매개변수를 요구하며, 한 번에 모든 결과를 반환하는 대신 이를 전달해야 할 수 있다.
  • 종종 실제 모델 호출의 결과보다 콜백 결과를 우선하게 된다.

토큰 (Tokens)

대부분의 모델 제공업체는 입력과 출력을 측정하기 위해 "토큰"이라는 단위를 사용한다. 토큰은 언어 모델이 텍스트를 처리하거나 생성할 때 읽고 생성하는 기본 단위이다. 특정 모델의 학습 방식에 따라 토큰의 정의는 다를 수 있다. 예를 들어, 영어에서 토큰은 "apple"과 같은 단어일 수도 있고, "app"과 같은 단어의 일부일 수도 있다.

모델에 프롬프트를 보내면 프롬프트의 단어와 문자가 토크나이저에 의해 토큰으로 인코딩된다. 그런 다음 모델은 생성된 출력 토큰을 스트리밍하며, 토크나이저는 이를 사람이 읽을 수 있는 텍스트로 디코딩한다. 아래 예제는 OpenAI 모델이 "LangChain is cool!"이라는 문장을 토크나이즈하는 방식을 보여준다.

[출처] langchain conceptual guide (https://python.langchain.com/v0.2/docs/concepts/)

텍스트가 5개의 서로 다른 토큰으로 분할되는 것을 볼 수 있으며, 이 토큰 간의 경계는 단어 경계와 정확히 일치하지 않는다.

언어 모델이 "문자"처럼 직관적인 단위 대신 토큰을 사용하는 이유는 텍스트를 처리하고 이해하는 방식과 관련이 있다. 고차원적으로 볼 때, 언어 모델은 초기 입력과 이전에 생성된 출력을 바탕으로 다음에 생성할 출력을 반복적으로 예측한다. 모델을 토큰을 사용해 학습시키면 의미를 가지는 언어 단위(예: 단어 또는 서브워드)를 처리할 수 있게 되며, 개별 문자가 아닌 이러한 단위를 학습함으로써 언어 구조, 문법 및 문맥을 이해하기가 더 쉬워진다. 또한 토큰을 사용하면 문자 단위 처리에 비해 텍스트 단위를 적게 처리하므로 효율성도 향상될 수 있다.

함수/도구 호출

INFO

여기서 함수 호출과 도구 호출이라는 용어를 혼용해서 사용한다. 함수 호출이 단일 함수를 호출하는 경우를 의미할 때도 있지만, 우리는 모든 모델이 각 메시지에서 여러 도구나 함수를 호출할 수 있는 것처럼 취급한다.

도구 호출은 채팅 모델이 주어진 프롬프트에 대해 사용자 정의 스키마와 일치하는 출력을 생성할 수 있도록 한다.

이름에서 알 수 있듯이 모델이 어떤 작업을 수행하는 것처럼 보이지만, 실제로는 그렇지 않다. 모델은 단지 도구에 전달할 인수를 생성할 뿐이며, 실제로 도구를 실행할지 여부는 사용자가 결정한다. 예를 들어, 비정형 텍스트에서 특정 스키마와 일치하는 구조화된 출력을 추출하려는 경우에는 생성된 인수로 함수를 호출하지 않을 수도 있다. 이 경우 모델에게 원하는 스키마와 일치하는 매개변수를 갖는 "추출" 도구를 제공하고, 생성된 출력을 최종 결과로 처리하면 된다.

[출처] langchain conceptual guide (https://python.langchain.com/v0.2/docs/concepts/)

도구 호출은 모든 곳에서 사용되는 것은 아니지만, Anthropic, Cohere, Google, Mistral, OpenAI 등 많은 인기 있는 LLM(대규모 언어 모델) 제공업체에서 지원되며, Ollama를 통해 로컬에서 실행되는 모델에서도 사용할 수 있다.

LangChain은 다양한 모델에서 일관된 방식으로 도구 호출을 지원하는 표준화된 인터페이스를 제공한다.

표준 인터페이스는 다음으로 구성된다.

  • ChatModel.bind_tools(): 모델이 호출할 수 있는 도구를 지정하는 메서드이다. 이 메서드는 LangChain 도구뿐만 아니라 Pydantic 객체도 허용한다.
  • AIMessage.tool_calls: 모델이 요청한 도구 호출에 접근하기 위해 모델이 반환하는 AIMessage의 속성이다.

도구 사용 방법

모델이 도구를 호출한 후, 해당 도구를 호출하고 인수를 다시 모델에 전달할 수 있다. LangChain은 이를 처리하기 위한 도구 추상화를 제공한다.

일반적인 흐름은 다음과 같다.

  1. 쿼리에 대한 응답으로 채팅 모델이 도구 호출을 생성한다.
  2. 생성된 도구 호출을 인수로 사용하여 적절한 도구를 호출한다.
  3. 도구 호출의 결과를 ToolMessages로 포맷한다.
  4. 전체 메시지 목록을 모델에 다시 전달하여 최종 답변을 생성하거나 추가 도구를 호출한다.

[출처] langchain conceptual guide (https://python.langchain.com/v0.2/docs/concepts/)

도구 호출 에이전트는 작업을 수행하고 쿼리에 응답하는 방법이다.

아래에서 좀 더 구체적인 가이드를 확인해봐라.

구조화된 출력

LLM(대규모 언어 모델)은 임의의 텍스트를 생성할 수 있다. 이는 모델이 다양한 입력에 적절히 응답할 수 있게 하지만, 특정 용도에서는 LLM의 출력을 특정 형식이나 구조로 제한하는 것이 유용할 수 있다. 이를 구조화된 출력이라고 한다.

예를 들어, 출력이 관계형 데이터베이스에 저장되어야 하는 경우, 모델이 정의된 스키마나 형식에 맞는 출력을 생성하는 것이 훨씬 더 쉬워진다. 비정형 텍스트에서 특정 정보를 추출하는 것도 이런 경우에 특히 유용하다. 가장 일반적으로는 출력 형식이 JSON이지만, YAML과 같은 다른 형식도 유용할 수 있다. 아래에서 LangChain에서 모델로부터 구조화된 출력을 얻는 몇 가지 방법을 논의한다.

.with_structured_output()

편의성을 위해 일부 LangChain 채팅 모델은 .with_structured_output() 메서드를 지원한다. 이 메서드는 스키마만 입력으로 요구하며, dict 또는 Pydantic 객체를 반환한다. 일반적으로 이 메서드는 아래에 설명된 더 고급 방법 중 하나를 지원하는 모델에서만 존재하며, 내부적으로 이러한 방법을 사용한다. 적절한 출력 파서를 가져오고 모델에 맞게 스키마를 포맷하는 작업을 처리한다.

예를 들어:

from typing import Optional
from langchain_core.pydantic_v1 import BaseModel, Field

class Joke(BaseModel):
    """사용자에게 농담을 전합니다."""

    setup: str = Field(description="농담의 설정")
    punchline: str = Field(description="농담의 결말")
    rating: Optional[int] = Field(description="농담의 재미, 1부터 10까지")

structured_llm = llm.with_structured_output(Joke)

structured_llm.invoke("Tell me a joke about cats")
Joke(setup='Why was the cat sitting on the computer?', punchline='To keep an eye on the mouse!', rating=None)

이 방법은 구조화된 출력 작업을 시작할 때 권장된다.

  • 모델의 고유 기능을 내부적으로 사용하여 별도의 출력 파서를 가져올 필요가 없다.
  • 도구 호출을 지원하는 모델의 경우, 특별한 프롬프트가 필요하지 않다.
  • 여러 기본 기술을 지원하는 경우, 메서드 매개변수를 제공하여 사용하는 방법을 전환할 수 있다.

다음과 같은 경우에는 다른 기술을 사용할 수 있다.

  • 사용하는 채팅 모델이 도구 호출을 지원하지 않는 경우.
  • 매우 복잡한 스키마로 작업할 때 모델이 출력을 생성하는 데 어려움을 겪는 경우.

자세한 정보는 이 가이드를 확인하라.

.with_structured_output()을 지원하는 모델 목록은 이 표에서 확인할 수 있다.

원시 프롬프트

모델로부터 구조화된 출력을 얻는 가장 직관적인 방법은 간단히 요청하는 것이다. 쿼리 외에 출력 형식을 설명하는 지침을 제공한 다음, 출력 파서를 사용하여 원시 모델 메시지 또는 문자열 출력을 쉽게 조작할 수 있는 형태로 변환한다.

원시 프롬프트의 가장 큰 장점은 유연성이다.

  • 원시 프롬프트는 특별한 모델 기능이 필요 없으며, 전달된 스키마를 이해할 수 있는 충분한 추론 능력만 필요하다.
  • JSON뿐만 아니라 원하는 모든 형식으로 프롬프트할 수 있다. 이는 모델이 XML이나 YAML과 같은 특정 데이터 유형으로 더 많이 훈련된 경우 유용할 수 있다.

그러나 몇 가지 단점도 있다.

  • LLM은 비결정적이므로, LLM이 정확한 형식으로 데이터를 일관되게 출력하도록 요청하는 것은 surprisingly 어려울 수 있으며, 모델에 따라 다를 수 있다.
  • 모델의 데이터에 따라 특성이 다를 수 있으며, 프롬프트를 최적화하는 것이 매우 어려울 수 있다. 일부는 JSON 스키마 해석에 더 능숙할 수 있고, 다른 모델은 TypeScript 정의에 더 적합할 수 있으며, 또 다른 모델은 XML을 선호할 수 있다.
  • 모델 제공자가 제공하는 기능이 신뢰성을 높일 수 있지만, 어떤 방법을 선택하든 프롬프트 기술은 결과를 조정하는 데 여전히 중요하다.

JSON 모드

Mistral, OpenAI, Together AI, Ollama와 같은 일부 모델은 일반적으로 구성으로 활성화되는 JSON 모드라는 기능을 지원한다.

JSON 모드가 활성화되면 모델의 출력을 항상 유효한 JSON으로 제한한다. 종종 사용자 지정 프롬프트가 필요하지만, 완전한 원시 프롬프트보다 부담이 훨씬 적으며 "항상 JSON을 반환해야 한다"는 형태로 지시합니다. 출력은 일반적으로 파싱하기도 쉽다.

또한 도구 호출보다 일반적으로 직접 사용하기 더 간단하고, 프롬프트와 결과 조정에 있어 도구 호출보다 더 많은 유연성을 제공할 수 있다.

다음은 예시이다.

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain.output_parsers.json import SimpleJsonOutputParser

model = ChatOpenAI(
    model="gpt-4o",
    model_kwargs={ "response_format": { "type": "json_object" } },
)

prompt = ChatPromptTemplate.from_template(
    "Answer the user's question to the best of your ability."
    'You must always output a JSON object with an "answer" key and a "followup_question" key.'
    "{question}"
)

chain = prompt | model | SimpleJsonOutputParser()

chain.invoke({ "question": "What is the powerhouse of the cell?" })

API 참조: ChatPromptTemplate | ChatOpenAI | SimpleJsonOutputParser

{'answer': 'The powerhouse of the cell is the mitochondrion. It is responsible for producing energy in the form of ATP through cellular respiration.', 'followup_question': 'Would you like to know more about how mitochondria produce energy?'}

모델 제공자가 JSON 모드를 지원하는 전체 목록은 이 표에서 확인할 수 있다.

도구 호출

도구 호출을 지원하는 모델의 경우, 도구 호출은 구조화된 출력을 얻는 데 매우 유용할 수 있다. 이는 스키마를 프롬프트할 때의 추측 작업을 제거하고, 내장된 모델 기능을 활용할 수 있게 해준다.

도구 호출은 먼저 원하는 스키마를 직접 또는 LangChain 도구를 사용하여 채팅 모델에 .bind_tools() 메서드를 통해 바인딩한다. 모델은 이후 AIMessage를 생성하며, 이 메시지에는 원하는 형식에 맞는 인수가 포함된 tool_calls 필드가 포함된다.

LangChain에서는 도구를 모델에 바인딩하는 몇 가지 허용된 형식이 있다. 하나의 예시는 다음과 같다.

from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_openai import ChatOpenAI

class ResponseFormatter(BaseModel):
    """항상 이 도구를 사용하여 사용자에게 응답을 구조한다."""

    answer: str = Field(description="사용자의 질문에 대한 답변")
    followup_question: str = Field(description="사용자가 물어볼 수 있는 후속 질문")

model = ChatOpenAI(
    model="gpt-4o",
    temperature=0,
)

model_with_tools = model.bind_tools([ResponseFormatter])

ai_msg = model_with_tools.invoke("What is the powerhouse of the cell?")

ai_msg.tool_calls[0]["args"]

API 참조: ChatOpenAI
출력:

{'answer': "The powerhouse of the cell is the mitochondrion. It generates most of the cell's supply of adenosine triphosphate (ATP), which is used as a source of chemical energy.", 'followup_question': 'How do mitochondria generate ATP?'}

도구 호출은 모델이 구조화된 출력을 생성하도록 하는 일반적으로 일관된 방법이며, 모델이 지원하는 경우 .with_structured_output() 메서드에서 기본적으로 사용되는 기술이다.

다음은 구조화된 출력을 위한 함수/도구 호출을 사용하는 데 유용한 실제 가이드이다.

도구 호출을 지원하는 모델 제공자의 전체 목록은 이 표에서 확인할 수 있다.

Few-shot 프롬프트

모델 성능을 개선하는 가장 효과적인 방법 중 하나는 모델에 원하는 작업의 예시를 제공하는 것이다. 입력과 예상 출력의 예를 모델 프롬프트에 추가하는 기법을 "few-shot 프롬프트"라고 한다. 이 기법은 "Language Models are Few-Shot Learners" 논문을 기반으로 한다. few-shot 프롬프트를 사용할 때 고려해야 할 사항은 다음과 같다.

  1. 예시 생성 방법
  2. 각 프롬프트에 포함될 예시 수
  3. 런타임에서 예시 선택 방법
  4. 프롬프트에서 예시 형식

각 항목에 대한 고려 사항은 다음과 같다.

1. 예시 생성

few-shot 프롬프트의 첫 번째이자 가장 중요한 단계는 적절한 예시 데이터셋을 준비하는 것이다. 좋은 예시는 런타임에서 관련성이 있어야 하고, 명확하고, 정보가 풍부하며, 모델이 이미 알고 있지 않은 정보를 제공해야 한다.

기본적으로 예시를 생성하는 방법은 다음과 같다.

  • 수동: 사람이 유용하다고 생각되는 예시를 생성한다.
  • 더 나은 모델: 더 좋은(비용이 더 비쌀 가능성이 있는) 모델의 응답을 사용하여 더 나쁜(비용이 더 저렴한) 모델을 위한 예시를 생성한다.
  • 사용자 피드백: 사용자(또는 라벨러)가 애플리케이션과의 상호작용에 대해 피드백을 남기고, 이 피드백을 바탕으로 예시를 생성한다(예: 긍정적인 피드백이 있는 모든 상호작용을 예시로 변환).
  • LLM 피드백: 사용자 피드백과 유사하지만, 모델이 스스로 평가하여 자동화된 프로세스를 통해 피드백을 생성한다.

어떤 접근 방식이 가장 좋은지는 작업에 따라 다르다. 핵심 원칙을 잘 이해해야 하는 작업의 경우, 몇 가지 정말 좋은 예시를 수동으로 제작하는 것이 유용할 수 있다. 올바른 행동의 범위가 넓고 미묘한 경우에는 많은 예시를 더 자동화된 방식으로 생성하는 것이 런타임 입력에 대해 높은 관련성을 가진 예시를 확보할 가능성을 높일 수 있다.

2. 예시 수

예시 데이터셋을 준비한 후, 각 프롬프트에 포함할 예시 수를 고려해야 한다. 주요 균형점은 예시가 많을수록 성능이 향상되지만, 프롬프트가 커지면 비용과 지연이 증가한다는 점이다. 예시가 너무 많으면 모델이 혼란스러워질 수도 있다. 적절한 예시 수를 찾는 것은 모델, 작업, 예시의 품질, 비용 및 지연 제약 조건에 크게 의존한다. 일반적으로 모델이 좋을수록 적은 수의 예시로도 좋은 성능을 내며, 예시를 추가할수록 빠르게 수익이 감소한다. 그러나 이 질문에 신뢰성 있게 답하기 위한 최선의 방법은 다양한 예시 수로 실험을 해보는 것이다.

3. 예시 선택

전체 예시 데이터셋을 각 프롬프트에 추가하지 않는다고 가정하면, 주어진 입력에 기반해 데이터셋에서 예시를 선택하는 방법이 필요합니다. 방법은 다음과 같다.

  • 무작위로
  • 입력의 의미적 유사성(또는 키워드 기반)으로
  • 토큰 크기와 같은 다른 제약 조건에 따라

LangChain에는 이러한 기술을 쉽게 사용할 수 있는 여러 ExampleSelectors가 제공된다.

일반적으로 의미적 유사성으로 선택하는 것이 모델 성능을 가장 잘 향상시킨다. 그러나 이것의 중요성은 모델과 작업에 따라 다르며, 실험해 볼 가치가 있다.

4. 예시 형식

현재 대부분의 최신 모델은 채팅 모델이므로, 이들에 대한 예시 형식에 초점을 맞춘다. 기본적인 옵션은 예시를 다음과 같이 삽입하는 것이다.

  • 시스템 프롬프트에 문자열로 삽입
  • 개별 메시지로 삽입

시스템 프롬프트에 문자열로 예시를 삽입할 경우, 각 예시의 시작 위치와 입력과 출력 부분이 명확히 구분되도록 해야 한다. 다양한 모델은 ChatML, XML, TypeScript 등 서로 다른 구문에 대해 반응이 다를 수 있다.

예시를 메시지로 삽입할 경우, 각 예시를 Human과 AI 메시지의 시퀀스로 표현하며, 예시 메시지에 "example_user" 및 "example_assistant"와 같은 이름을 지정하여 최신 입력 메시지와 다른 역할을 명확히 할 수 있다.

도구 호출 예시 형식

도구 호출이 포함된 예시를 메시지로 형식화할 때는 다소 까다로울 수 있다. 이유는 모델마다 도구 호출을 생성할 때 허용되는 메시지 시퀀스 유형에 제약이 다르기 때문이다.

  • 일부 모델은 도구 호출이 있는 모든 AIMessage 뒤에 ToolMessages가 즉시 와야 한다고 요구한다.
  • 일부 모델은 ToolMessages 뒤에 다음 HumanMessage 이전에 AIMessage가 즉시 와야 한다고 요구한다.
  • 일부 모델은 채팅 기록에 도구 호출 / ToolMessages가 있을 경우 도구를 모델에 전달해야 한다고 요구한다.

이 요구 사항은 모델별로 다르며, 사용하는 모델에 대해 확인해야 한다. 모델이 도구 호출 후 ToolMessages와 / 또는 ToolMessages 후 AIMessages를 요구하고 예시에 예상 도구 호출만 포함되어 있고 실제 도구 출력이 포함되어 있지 않은 경우, API 제약 조건을 만족시키기 위해 각 예시의 끝에 더미 ToolMessages / AIMessages를 추가해 볼 수 있다. 이 경우, 예시를 문자열로 삽입할지 메시지로 삽입할지 실험해 보는 것이 좋다. 더미 메시지가 특정 모델에 부정적인 영향을 줄 수 있기 때문이다.

두 가지 도구 호출 벤치마크에서 Anthropic과 OpenAI가 어떻게 다양한 few-shot 프롬프트 기법에 반응하는지에 대한 사례 연구를 여기에서 확인할 수 있다.

검색 (Retrieval)

LLM은 크지만 고정된 데이터셋에서 훈련되므로, 개인적이거나 최신 정보를 추론하는 데 제한이 있다. LLM을 특정 사실로 미세 조정하는 것이 하나의 방법이지만, 이는 사실 기억에 적합하지 않고 비용이 많이 들 수 있다. 검색은 주어진 입력에 대해 LLM의 응답을 개선하기 위해 관련 정보를 제공하는 과정이다. Retrieval Augmented Generation (RAG) 논문은 검색된 정보를 사용하여 LLM 생성을 기반으로 하는 과정이다.

RAG의 성과는 검색된 문서의 관련성과 품질에 따라 달라진다. 다행히도, RAG 시스템을 설계하고 개선하는 데 사용할 수 있는 기술들이 새롭게 등장하고 있다. 이 기술들을 분류하고 요약하는 데 중점을 두었으며, 다음 섹션에서는 높은 수준의 전략적 지침을 공유할 것이다. 다양한 조합을 실험해 보고, 앱의 다양한 반복을 평가하는 데 유용한 LangSmith 가이드를 참조할 수 있다.

[출처] langchain conceptual guide (https://python.langchain.com/v0.2/docs/concepts/)

쿼리 번역

첫째, RAG 시스템에 대한 사용자 입력을 고려하라. 이상적으로, RAG 시스템은 형식이 좋지 않은 질문부터 복잡한 다중 쿼리까지 다양한 입력을 처리할 수 있어야 한다. LLM을 사용하여 입력을 검토하고 선택적으로 수정하는 것이 쿼리 번역의 핵심 아이디어이다. 이는 원시 사용자 입력을 검색 시스템에 맞게 최적화하는 일반적인 완충 역할을 한다. 예를 들어, 키워드를 추출하거나 복잡한 쿼리에 대해 여러 하위 질문을 생성하는 것처럼 간단할 수도 있다.

이름 사용 시기 설명
Multi-query 질문의 여러 관점을 다뤄야 할 때 사용자의 질문을 여러 관점에서 재작성하고, 각 재작성된 질문에 대해 문서를 검색하여, 모든 쿼리에 대한 고유 문서를 반환한다.
Decomposition 질문을 더 작은 하위 문제로 나눌 수 있을 때 질문을 하위 문제 / 질문 세트로 분해하고, 이 문제들을 순차적으로(첫 번째 답변 + 검색을 사용하여 두 번째 답변) 또는 병렬적으로(각 답변을 통합하여 최종 답변) 해결한다.
Step-back 더 높은 수준의 개념적 이해가 필요할 때 LLM에 일반적인 고차원 개념이나 원칙에 대한 질문을 먼저 던지고, 관련 사실을 검색하여 이 기반을 사용하여 사용자 질문에 답한다.
HyDE 원시 사용자 입력을 사용하여 관련 문서를 검색하는 데 어려움이 있을 때 LLM을 사용하여 질문을 가상의 문서로 변환하고, 이러한 가상의 문서를 사용하여 실제 문서를 검색한다. 문서-문서 유사성 검색이 더 관련성 있는 결과를 생성할 수 있다는 가정하에 수행한다.


다양한 구체적인 접근 방식에 대한 비디오를 확인하라.

라우팅

둘째, RAG 시스템에서 사용할 수 있는 데이터 소스를 고려하라. 하나 이상의 데이터베이스 또는 구조화된 데이터와 비구조화된 데이터 소스 전반에 걸쳐 쿼리하고 싶다. LLM을 사용하여 입력을 검토하고 적절한 데이터 소스로 라우팅하는 것은 소스 전반에 걸쳐 쿼리하는 간단하고 효과적인 접근 방식이다.

이름 사용 시기 설명
Logical routing 입력을 라우팅할 위치를 결정하기 위한 규칙을 LLM으로 프롬프트할 수 있을 때 LLM을 사용하여 쿼리에 대한 추론을 하고 가장 적합한 데이터 저장소를 선택한다.
Semantic routing 의미적 유사성이 입력을 라우팅할 위치를 결정하는 효과적인 방법일 때 의미적 라우팅은 쿼리와 일반적으로 프롬프트 집합을 임베딩한 후, 유사성에 따라 적절한 프롬프트를 선택한다.


라우팅에 대한 비디오를 확인하라.

쿼리 구성

셋째, 데이터 소스 중 일부가 특정 쿼리 형식을 요구하는지 고려하라. 많은 구조화된 데이터베이스는 SQL을 사용한다. 벡터 저장소는 문서 메타데이터에 대한 키워드 필터를 적용하는 특정 구문이 있을 수 있다. 자연어 쿼리를 쿼리 구문으로 변환하는 데 LLM을 사용하는 것은 인기 있고 강력한 접근 방식이다. 특히, text-to-SQL, text-to-Cypher메타데이터 필터를 위한 쿼리 분석은 각각 구조화된 데이터베이스, 그래프 데이터베이스 및 벡터 데이터베이스와 상호작용하는 데 유용하다.

이름 사용 시기 설명
Text to SQL 사용자가 SQL을 통해 접근할 수 있는 관계형 데이터베이스에 있는 정보를 요구할 때 LLM을 사용하여 사용자 입력을 SQL 쿼리로 변환한다.
Text-to-Cypher 사용자가 Cypher를 통해 접근할 수 있는 그래프 데이터베이스에 있는 정보를 요구할 때 LLM을 사용하여 사용자 입력을 Cypher 쿼리로 변환한다.
Self Query 사용자가 텍스트와의 유사성보다는 메타데이터 기반으로 문서를 가져오는 것이 더 적절할 때 LLM을 사용하여 사용자 입력을 두 가지로 변환한다. (1) 의미적으로 검색할 문자열, (2) 함께 사용할 메타데이터 필터. 이 방식은 질문이 문서의 메타데이터(내용 자체가 아닌)와 관련이 있는 경우 유용하다.


쿼리 구성에 대한 블로그 게시물 개요와 text-to-DSL 과정(DSL은 주어진 데이터베이스와 상호작용하는 데 필요한 도메인 특화 언어)에 대한 RAG from Scratch 비디오를 확인하라. 이 과정은 사용자 질문을 구조화된 쿼리로 변환한다.

인덱싱

넷째, 문서 인덱스의 설계를 고려하라. 문서 검색을 위해 인덱싱하는 문서와 LLM에 생성 작업을 위해 전달하는 문서를 분리하는 것이 간단하고 강력한 아이디어이다. 인덱싱은 일반적으로 문서의 의미 정보를 고정 크기 벡터로 압축하는 임베딩 모델과 벡터 저장소를 사용한다.

많은 RAG 접근 방식은 문서를 청크로 나누고, LLM의 입력 질문과 유사성을 기준으로 일부를 검색하는 데 중점을 둔다. 그러나 청크 크기와 청크 수를 설정하는 것은 어려울 수 있으며, LLM이 질문에 답할 수 있는 전체 컨텍스트를 제공하지 않으면 결과에 영향을 미칠 수 있다. 또한, LLM은 점점 더 많은 토큰을 처리할 수 있다.

이러한 긴장을 해결할 수 있는 두 가지 접근 방식이 있다.

  1. Multi Vector: LLM을 사용하여 문서를 인덱싱에 적합한 형식(예: 종종 요약)으로 변환하지만, 생성 작업을 위해 전체 문서를 반환한다.
  2. ParentDocument: 문서 청크를 임베딩하지만, 전체 문서도 반환한다. 이는 검색에는 간결한 표현(요약 또는 청크)을 사용하고, 답변 생성을 위해 전체 문서를 사용하는 방식이다.
이름 인덱스 유형 LLM 사용 사용 시기 설명
Vector store 벡터 저장소 아니오 간단하고 빠른 시작을 원할 때 가장 간단한 방법으로, 각 텍스트 조각에 대해 임베딩을 생성한다.
ParentDocument 벡터 저장소 + 문서 저장소 아니오 페이지에 독립적인 작은 정보 조각이 많고 이들을 함께 검색하는 것이 좋을 때 각 문서에 대해 여러 청크를 인덱싱하고, 임베딩 공간에서 가장 유사한 청크를 찾은 후 전체 부모 문서를 검색하여 반환한다.
Multi Vector 벡터 저장소 + 문서 저장소 가끔 인덱싱 중 문서에서 텍스트 자체보다 더 관련성이 있는 정보를 추출할 수 있을 때 각 문서에 대해 여러 벡터를 생성하며, 각 벡터는 텍스트 요약 및 가상의 질문 등 다양한 방식으로 생성될 수 있다.
Time-Weighted Vector store 벡터 저장소 아니오 문서에 타임스탬프가 있으며 가장 최근의 문서를 검색하려고 할 때 의미적 유사성(일반 벡터 검색)과 최신성(인덱스된 문서의 타임스탬프)을 조합하여 문서를 검색한다.

  • 인덱싱 기본에 관한 RAG from Scratch 비디오를 확인하라.
  • 다중 벡터 검색기 비디오를 확인하라.

유사성 검색 품질 향상

다섯째, 유사성 검색 자체의 품질을 개선할 방법을 고려하라. 임베딩 모델은 텍스트를 고정 길이(벡터) 표현으로 압축하여 문서의 의미 콘텐츠를 캡처한다. 이 압축은 검색/검색에 유용하지만, 문서의 의미적 뉘앙스/세부 정보를 캡처하는 단일 벡터 표현에 큰 부담을 준다. 경우에 따라 관련 없는 내용이나 중복된 내용이 임베딩의 의미적 유용성을 희석시킬 수 있다.

ColBERT는 이 문제를 해결하기 위한 흥미로운 접근 방식으로, 더 높은 세분화의 임베딩을 사용한다.

  1. 문서와 쿼리의 각 토큰에 대해 컨텍스트에 영향을 받는 임베딩을 생성한다.
  2. 각 쿼리 토큰과 모든 문서 토큰 간의 유사성을 점수화한다.
  3. 최대 점수를 취한다.
  4. 모든 쿼리 토큰에 대해 이 작업을 수행한다.
  5. 모든 쿼리 토큰의 최대 점수(3단계에서)를 합산하여 쿼리-문서 유사성 점수를 얻는다. 이 토큰 단위 점수화는 강력한 결과를 제공할 수 있다.

[출처] langchain conceptual guide (https://python.langchain.com/v0.2/docs/concepts/)

검색 품질 향상

검색 품질을 개선하기 위한 추가적인 방법들이 있다. 임베딩은 의미 정보를 캡처하는 데 뛰어나지만, 키워드 기반 쿼리에는 어려움을 겪을 수 있다. 많은 벡터 저장소는 키워드와 의미적 유사성을 결합하는 하이브리드 검색 기능을 제공하여 두 접근 방식의 장점을 결합한다. 또한, 많은 벡터 저장소는 최대한의 여백 중요성(Maximal Marginal Relevance, MMR)을 지원하여 검색 결과를 다양화하고 유사하고 중복된 문서가 반환되는 것을 방지한다.

이름 사용 시기 설명
ColBERT 더 높은 세분화 임베딩이 필요한 경우 ColBERT는 문서와 쿼리의 각 토큰에 대해 컨텍스트 영향을 받는 임베딩을 사용하여 세밀한 쿼리-문서 유사성 점수를 얻는다. 논문
Hybrid search 키워드 기반 및 의미적 유사성을 결합할 때 하이브리드 검색은 키워드와 의미적 유사성을 결합하여 두 접근 방식의 장점을 결합한다. 논문
Maximal Marginal Relevance (MMR) 검색 결과를 다양화할 때 MMR은 검색 결과를 다양화하여 유사하고 중복된 문서가 반환되는 것을 방지한다.

ColBERT에 관한 RAG from Scratch 비디오를 확인하라.

후처리 (Post-processing)

여섯째, 검색된 문서를 필터링하거나 순위를 매길 방법을 고려하라. 이는 여러 출처에서 반환된 문서를 결합하는 경우 매우 유용할 수 있다. 불필요한 문서를 낮은 순위로 매기거나 유사한 문서를 압축할 수 있다.

이름 인덱스 유형 LLM 사용 사용 시기 설명
Contextual Compression 모든 가끔 검색된 문서에 너무 많은 불필요한 정보가 포함되어 LLM을 산만하게 하는 경우 다른 검색기 위에 후처리 단계를 추가하여 검색된 문서에서 가장 관련성 높은 정보만 추출한다. 임베딩 또는 LLM을 사용할 수 있다.
Ensemble 모든 아니오 여러 검색 방법을 조합해 보고 싶은 경우 여러 검색기에서 문서를 가져와서 결합한다.
Re-ranking 모든 검색된 문서를 관련성에 따라 순위를 매기고 싶거나 여러 검색 방법의 결과를 결합하려는 경우 쿼리와 문서 목록이 주어졌을 때, 문서의 의미적 관련성을 기준으로 문서의 순위를 다시 매긴다.

여러 쿼리에서 후처리를 위한 RAG-Fusion(논문)에 관한 RAG from Scratch 비디오를 확인하라. 사용자 질문을 여러 관점에서 재작성하고, 각 재작성된 질문에 대해 문서를 검색한 후, 여러 검색 결과 목록의 순위를 결합하여 단일 통합 순위를 생성한다. Reciprocal Rank Fusion (RRF)을 사용한다.

생성 (Generation)

마지막으로, RAG 시스템에 자체 수정 기능을 구축하는 방법을 고려하라. RAG 시스템은 검색 품질이 낮거나(예: 사용자 질문이 인덱스의 도메인 외부에 있는 경우) 생성에서 환각이 발생할 수 있다. 단순한 검색-생성 파이프라인은 이러한 오류를 감지하거나 자가 수정할 수 없다. "흐름 공학" 개념이 코드 생성 맥락에서 도입되었다. 단위 테스트를 사용하여 코드 질문에 대한 답변을 반복적으로 작성하고 오류를 확인하고 수정한다. 여러 연구에서 Self-RAG 및 Corrective-RAG와 같은 RAG 접근 방식을 적용했다. 두 경우 모두 문서 관련성, 환각 및/또는 답변 품질에 대한 검사를 RAG 답변 생성 흐름에서 수행한다.

그래프는 논리적 흐름을 신뢰성 있게 표현할 수 있는 훌륭한 방법으로, 아래 그림에서 LangGraph를 사용한 여러 아이디어를 구현했다(빨강 - 라우팅, 파랑 - 폴백, 초록 - 자체 수정):

  • 라우팅: Adaptive RAG(논문). 질문을 다양한 검색 접근 방식으로 라우팅한다.
  • 폴백: Corrective RAG(논문). 문서가 쿼리에 적합하지 않으면 웹 검색으로 폴백한다.
  • 자체 수정: Self-RAG(논문). 환각이 있거나 질문을 해결하지 않는 답변을 수정한다.

[출처] langchain conceptual guide (https://python.langchain.com/v0.2/docs/concepts/)

이름 사용 시기 설명
Self-RAG 답변에서 환각이나 관련 없는 내용이 있을 때 Self-RAG는 RAG 답변 생성 과정에서 문서의 관련성, 환각, 답변 품질을 검사하며, 반복적으로 답변을 작성하고 오류를 수정한다.
Corrective-RAG 문서의 관련성이 낮을 때 폴백 메커니즘이 필요할 때 Corrective-RAG는 검색된 문서가 쿼리와 관련이 없을 경우 웹 검색과 같은 폴백 메커니즘을 포함하여 더 높은 품질과 더 관련성 있는 검색을 보장한다.

LangGraph와 함께하는 RAG에 관한 여러 비디오와 요리책을 확인하라.

텍스트 분할

LangChain은 다양한 텍스트 분할기를 제공한다. 이들은 모두 langchain-text-splitters 패키지에 포함되어 있다.

이름: 텍스트 분할기의 이름

클래스: 이 텍스트 분할기를 구현하는 클래스

분할 기준: 이 텍스트 분할기가 텍스트를 어떻게 분할하는지

메타데이터 추가: 이 텍스트 분할기가 각 청크가 어디에서 왔는지에 대한 메타데이터를 추가하는지 여부

설명: 분할기에 대한 설명과 사용 시 추천 사항

이름 클래스 분할 기준 메타데이터 추가 설명
Recursive RecursiveCharacterTextSplitter, RecursiveJsonSplitter 사용자 정의 문자 목록 아니오 텍스트를 재귀적으로 분할한다. 관련된 텍스트 조각이 서로 가까이 유지되도록 시도한다. 텍스트 분할을 시작하는 추천 방법이다.
HTML HTMLHeaderTextSplitter, HTMLSectionSplitter HTML 특수 문자 HTML 특수 문자를 기준으로 텍스트를 분할한다. 이 방법은 각 청크가 어디에서 왔는지에 대한 관련 정보를 추가한다.
Markdown MarkdownHeaderTextSplitter Markdown 특수 문자 Markdown 특수 문자를 기준으로 텍스트를 분할한다. 이 방법은 각 청크가 어디에서 왔는지에 대한 관련 정보를 추가한다.
Code 다양한 언어 코드(파이썬, JS) 특수 문자 아니오 코드 언어의 특수 문자를 기준으로 텍스트를 분할한다. 15개 언어를 선택할 수 있다.
Token 다양한 클래스 토큰 아니오 토큰을 기준으로 텍스트를 분할한다. 토큰을 측정하는 여러 가지 방법이 있다.
Character CharacterTextSplitter 사용자 정의 문자 아니오 사용자 정의 문자를 기준으로 텍스트를 분할한다. 가장 간단한 방법 중 하나이다.
Semantic Chunker (Experimental) SemanticChunker 문장 아니오 문장을 기준으로 먼저 분할한 후, 의미적으로 유사한 문장들을 함께 결합한다. Greg Kamradt에서 가져왔다.
Integration: AI21 Semantic AI21SemanticTextSplitter 일관된 텍스트 조각을 형성하는 개별 주제를 식별하고, 그에 따라 분할한다.

평가

평가는 LLM 기반 애플리케이션의 성능과 효과성을 평가하는 과정이다. 모델의 응답을 미리 정의된 기준이나 벤치마크에 대해 테스트하여 원하는 품질 기준을 충족하고 의도한 목적을 달성하는지 확인하는 것이 포함된다. 이 과정은 신뢰할 수 있는 애플리케이션을 구축하는 데 필수적이다.

[출처] langchain conceptual guide (https://python.langchain.com/v0.2/docs/concepts/)

LangSmith는 이 과정에서 몇 가지 방법으로 도움을 준다.

  • 데이터셋 생성 및 관리 용이성: 추적 및 주석 기능을 통해 데이터셋을 쉽게 생성하고 관리할 수 있다.
  • 평가 프레임워크 제공: 지표를 정의하고 데이터셋에 대해 애플리케이션을 실행할 수 있도록 도와준다.
  • 결과 추적 및 자동 실행: 시간에 따라 결과를 추적하고 평가자를 일정에 맞게 자동으로 실행하거나 CI/코드의 일부로 사용할 수 있다.

자세한 내용을 보려면 LangSmith 가이드를 확인하라.

추적

추적(trace)은 애플리케이션이 입력에서 출력으로 이동하는 과정을 단계별로 기록한 것이다. 추적에는 개별 단계가 포함되며, 이를 '런(run)'이라고 한다. 이러한 런은 모델, 검색기, 도구 또는 하위 체인으로부터의 개별 호출일 수 있다. 추적은 체인 및 에이전트 내부의 가시성을 제공하며, 문제를 진단하는 데 중요하다.

자세한 내용은 LangSmith 개념 가이드를 확인하라.

출처: https://python.langchain.com/v0.2/docs/concepts/

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유