Trim_messages
기능trim_messages
trim_messages
유틸 함수를 사용한다.trim_messages()
는 채팅 메시지의 목록의 길이(토큰 수나 메시지)를 줄여주는 함수로 LangChain에서 채팅 히스토리를 LLM 에게 전달할 떄, 모델의 context window(입력 한계)를 초과하지 않도록 하기 위해 사용된다.전제조건
langchain-core >= 0.2.9
SystemMessage, HumanMessage, AIMessage, ToolMessage
), Chat history, LLM chaining, ChatModel 에 대한 기본적인 이해유효한 대화 히스토리의 조건
trim_messages
로 자른 결과는 반드시 모델이 받아들일 수 있는 형태여야 한다.<형태>
시작 : HumanMessage
, SystemMessage
+ HumanMessage
종료 : HumanMessage
또는 ToolMessage
ToolMessage
는 반드시 AImessage
다음에 와야 함SystemMessage
는 가급적 포함(설정 = include_system=True)
strategy="last"
사용함수 시그니처
langchain_core.messages.utils.trim_messages(
messages: Sequence[MessageLikeRepresentation] | None = None,
**kwargs: Any,
) → list[BaseMessage]
주요 용도는 전체 채팅 이력 중 최근 메시지만 유지하고, 오래된 메시지를 제거한다.
전체 토큰 수나 메시지 개수 제한을 초과하지 않도록 잘라내며, SystemMessage를 유지하고 채팅 이력의 형식을 모델이 기대하는 구조로 맞출 수 있도록 옵션을 제공한다.
주요 파라미터 설명
messages
: 자를 메시지 목록(list of Message - 예를 들면 SystemMessage, HumanMesage, AIMessage)max_tokens
: 최대 허용 토큰 수 또는 메시지 수 (token_counter
가 무엇인지에 따라 다름)token_counter
: 각 메시지의 토큰 수를 계산하는 함수 또는 BaseLanguageModel
예 : len, ChatOpenAI()strategy
: 어떤 방식으로 자를지 전략 선택 (first
: 앞에서부터 자르기, last
: 뒤에서부터 자르기-기본값)allow_partial
: 메시지 하나가 너무 길 경우 일부만 포함할지 여부 (True
: 일부 메시지도 포함 가능, False
: 완전한 메시지만 포함)include_system
: 첫 번째 메시지가 SystemMessage일 경우, 자른 결과에 포함시킬지 여부. ("strategy"="last"일 때만 적용)start_on
: "last" 전략에서 어떤 메시지부터 시작할지 지정. 예 : "human" 이면 인간 메시지부터 시작end_on
: 어떤 메시지 이후는 무시할지 지정. 예 "ai" 이면 AIMessage 이후는 무시text_splitter
: "allow_partial=True"일 때 메시지 문자열을 자르는 방법. 기본은 줄바꿈 기준kwargs
: 기타 추가 옵션반환값
list[BaseMessage]
)Runnable
객체 (함수형 구성 시)예외 사항
ValueError
발생유의 사항
include_system=True
로 SystemMessage가 유지되도록 설정count_tokens_approximately
추천사용 예시
[1] 토큰 수 기반 자르기
from langchain_core.messages import trim_messages, HumanMessage, AIMessage, SystemMessage
from langchain_core.messages.utils import count_tokens_approximately
messages = [
SystemMessage("너는 웹 검색 도구를 활용해 정보를 조사하고 요약해주는 인턴 AI야. 답변할 때는 출처나 맥락을 명확히 전달하고, 필요한 경우 검색 도구를 사용해서 가장 최신 정보를 찾아야 해."),
HumanMessage("최근에 발표된 LangChain 관련 주요 업데이트를 알려줘."),
AIMessage("LangChain에서는 최근에 'LangGraph' 기능이 도입되었으며, 에이전트 워크플로우를 그래프 형태로 구성할 수 있게 되었습니다. (출처: LangChain 공식 블로그)"),
HumanMessage("LangChain의 공동 창업자인 Harrison은 현재 무슨 프로젝트를 주도하고 있어?"),
AIMessage("Harrison Chase는 LangChain의 CEO로, 최근에는 LangGraph 기반의 멀티에이전트 프레임워크 개발을 리드하고 있습니다. GitHub에서 관련 리포지토리를 운영 중입니다."),
HumanMessage("웹 기반 에이전트 개발을 위한 대표적인 오픈소스 예시 알려줘."),
]
trimmed = trim_messages(
messages,
max_tokens=45,
strategy = "last",
token_counter = count_tokens_approximately,
start_on="human",
end_on = ("human", "tool"),
include_system=True,
allow_partial=False,
)
strategy = "last"
: 마지막 메시지들 중에서 토큰 수가 max_tokens 이하가 되도록 함
대부분의 Chat 모델은 HumanMessage로 시작하거나 SystemMessage 다음에 HumanMessage가 오는 경우의 대화 히스토리를 기대한다. 따라서 `start_on="human" 으로 유효한 대화 히스토리를 생성한다.
또한 대부분 Chat 모델은 HumanMessage로 끝나거나 ToolMessage로 끝나는 대화 히스토리를 기대하기 때문에 `end_on=("human", "tool")로 설정한다.
여기서 count_tokens_approximately
는 대략적인 토큰 수를 계산하고, allow_partial=True
로 하면 메시지 하나 전체가 들어가기에는 토큰 수가 초과라면 그 메시지의 일부 텍스트만 잘라서 포함시키는 옵션이다. allow_partial=False
인 경우에는 전체 메시지가 들어갈 수 없을 때는 그 메시지를 포함하지 않는다.
위의 messages를 토큰 수 기반으로 자르면 아래와 같이 출력된다.
[SystemMessage(content='너는 웹 검색 도구를 활용해 정보를 조사하고 요약해주는 인턴 AI야. 답변할 때는 출처나 맥락을 명확히 전달하고, 필요한 경우 검색 도구를 사용해서 가장 최신 정보를 찾아야 해.', additional_kwargs={}, response_metadata={}),
HumanMessage(content='웹 기반 에이전트 개발을 위한 대표적인 오픈소스 예시 알려줘.', additional_kwargs={}, response_metadata={})]
[2] 메시지 개수 기반 자르기
messages,
max_tokens=5,
strategy="last",
token_counter=len, #각 메시지를 1토큰으로 취급
start_on = "human",
end_on = ("human", "tool"),
include_system=True,
)
token_counter
에 len이 들어가는데, 각 메시지를 1토큰으로 취급한다는 이야기이다.위 token_counter를 len으로 max_tokens를 5를 준 trimmed를 출력해보면 기존에 messages는 SystemMessage-> HumanMessage ->AIMessage -> HumanMessage, AIMessage -> HumanMessage 총 6개의 메시지가 담겨 있었지만
[SystemMessage(content='너는 웹 검색 도구를 활용해 정보를 조사하고 요약해주는 인턴 AI야. 답변할 때는 출처나 맥락을 명확히 전달하고, 필요한 경우 검색 도구를 사용해서 가장 최신 정보를 찾아야 해.', additional_kwargs={}, response_metadata={}),
HumanMessage(content='LangChain의 공동 창업자인 Harrison은 현재 무슨 프로젝트를 주도하고 있어?', additional_kwargs={}, response_metadata={}),
AIMessage(content='Harrison Chase는 LangChain의 CEO로, 최근에는 LangGraph 기반의 멀티에이전트 프레임워크 개발을 리드하고 있습니다. GitHub에서 관련 리포지토리를 운영 중입니다.', additional_kwargs={}, response_metadata={}),
HumanMessage(content='웹 기반 에이전트 개발을 위한 대표적인 오픈소스 예시 알려줘.', additional_kwargs={}, response_metadata={})]
현재는 4개의 메시지만 출력되는 것을 볼 수 있다.
위에서 유효한 대화 히스토리의 조건은 HumanMessage 혹은 ToolMessage 로 종료되어야 한다고 했기 때문인다. 그래서 max_tokens 수를 넘어가지 않는 한에서 마지막 HumanMessage 까지만 출력된다.
예를 들어
trimmed = trim_messages(
messages,
max_tokens=3,
strategy="last",
token_counter=len, #각 메시지를 1토큰으로 취급
start_on = "human",
end_on = ("human", "tool"),
include_system=True,
)
trimmed
# [SystemMessage(content='너는 웹 검색 도구를 활용해 정보를 조사하고 요약해주는 인턴 AI야. 답변할 때는 출처나 맥락을 명확히 전달하고, 필요한 경우 검색 도구를 사용해서 가장 최신 정보를 찾아야 해.', additional_kwargs={}, response_metadata={}),
# HumanMessage(content='웹 기반 에이전트 개발을 위한 대표적인 오픈소스 예시 알려줘.', additional_kwargs={}, response_metadata={})]
max_tokens가 3개인 경우에도 원래 SystemMessage, HumanMessage, AIMessage 까지지만 HumanMessage로 끝나야하기 때문에 바로 앞까지 메시지가 잘라서 나오게 된다.
참고로 max_tokens=1로 줄 경우에는 SystemMessage만 나오게 되는데, SystemMessage 하나만 있는 경우에는 히스토리로 넘겨줘도 괜찮나보다.
trimmed = trim_messages(
messages,
max_tokens=1,
strategy="last",
token_counter=len, #각 메시지를 1토큰으로 취급
start_on = "human",
end_on = ("human", "tool"),
include_system=True,
)
trimmed
# [SystemMessage(content='너는 웹 검색 도구를 활용해 정보를 조사하고 요약해주는 인턴 AI야. 답변할 때는 출처나 맥락을 명확히 전달하고, 필요한 경우 검색 도구를 사용해서 가장 최신 정보를 찾아야 해.', additional_kwargs={}, response_metadata={})]
고급 사용법
strategy="first"
의 경우 처음 메시지를 유지한다.trimmed = trim_messages(
messages,
max_tokens=45,
strategy="first",
token_counter=count_tokens_approximately,
)
trimmed
# [SystemMessage(content='너는 웹 검색 도구를 활용해 정보를 조사하고 요약해주는 인턴 AI야. 답변할 때는 출처나 맥락을 명확히 전달하고, 필요한 경우 검색 도구를 사용해서 가장 최신 정보를 찾아야 해.', additional_kwargs={}, response_metadata={}),
# HumanMessage(content='최근에 발표된 LangChain 관련 주요 업데이트를 알려줘.', additional_kwargs={}, response_metadata={})]
include_system=False
로 주면 시스템 메시지를 제외한다.trim_messages(messages, max_tokens=2, include_system=False, token_counter=len)
# [AIMessage(content='Harrison Chase는 LangChain의 CEO로, 최근에는 LangGraph 기반의 멀티에이전트 프레임워크 개발을 리드하고 있습니다. GitHub에서 관련 리포지토리를 운영 중입니다.', additional_kwargs={}, response_metadata={}),
# HumanMessage(content='웹 기반 에이전트 개발을 위한 대표적인 오픈소스 예시 알려줘.', additional_kwargs={}, response_metadata={})]
trim_messages(messages, max_tokens=2, include_system=True, token_counter=len)
# [SystemMessage(content='너는 웹 검색 도구를 활용해 정보를 조사하고 요약해주는 인턴 AI야. 답변할 때는 출처나 맥락을 명확히 전달하고, 필요한 경우 검색 도구를 사용해서 가장 최신 정보를 찾아야 해.', additional_kwargs={}, response_metadata={}),
# HumanMessage(content='웹 기반 에이전트 개발을 위한 대표적인 오픈소스 예시 알려줘.', additional_kwargs={}, response_metadata={})]
LLM을 토큰 카운터로 사용하기
from langchain_openai import ChatOpenAI
trim_messages(
messages,
max_tokens=100,
strategy="first",
token_counter=ChatOpenAI(model='gpt-4o-mini')
)
#[SystemMessage(content='너는 웹 검색 도구를 활용해 정보를 조사하고 요약해주는 인턴 AI야. 답변할 때는 출처나 맥락을 명확히 전달하고, 필요한 경우 검색 도구를 사용해서 가장 최신 정보를 찾아야 해.', additional_kwargs={}, response_metadata={}),
# HumanMessage(content='최근에 발표된 LangChain 관련 주요 업데이트를 알려줘.', additional_kwargs={}, response_metadata={})]
사용자 정의 토큰 계산기 사용
from typing import List
from langchain_core.messages import BaseMessage
import tiktoken
def tiktoken_counter(messages: List[BaseMessage]) -> int:
enc = tiktoken.get_encoding('o200k_base')
return sum(len(enc.encode(msg.content)) +3 for msg in messages)
trim_messages(messages, token_counter=tiktoken_counter, max_tokens=45)
# [HumanMessage(content='웹 기반 에이전트 개발을 위한 대표적인 오픈소스 예시 알려줘.', additional_kwargs={}, response_metadata={})]
체이닝: Chain으로 연결해서 사용
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model='gpt-4o-mini')
trimmer = trim_messages(
max_tokens=45,
strategy="last",
token_counter=llm,
start_on="human",
include_system=True,
)
chain = trimmer | llm
response = chain.invoke(messages)
response
# AIMessage(content='저는 웹 검색 도구를 사용할 수 없지만, 최신 정보에 대한 질문에 대해 알고 있는 내용을 바탕으로 답변을 드릴 수 있습니다. 질문이 있으면 말씀해 주세요!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 41, 'prompt_tokens': 61, 'total_tokens': 102, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrHQxZ5dXzQED97niPn6U4Yh4wcsD', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--f06ad4aa-6c56-4204-99cc-81969afe2546-0', usage_metadata={'input_tokens': 61, 'output_tokens': 41, 'total_tokens': 102, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})
Chat History와 함께 사용하기
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
history = InMemoryChatMessageHistory(messages=messages[:-1])
def get_history(session_id):
return history if session_id == "1" else InMemoryChatMessageHistory()
chain_with_history = RunnableWithMessageHistory(chain, get_history)
response = chain_with_history.invoke(
[HumanMessage("최근 서울 날씨 알려줘")],
config= {"configurable" : {"session_id" : "1"}},
)
response
# AIMessage(content='알겠습니다! 필요한 정보를 조사하고 요약하여 드리겠습니다. 질문이나 요청이 있으시면 말씀해 주세요.', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 24, 'prompt_tokens': 61, 'total_tokens': 85, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_34a54ae93c', 'id': 'chatcmpl-BrHU3andVDODdlu005QoEVb5HDXtu', 'service_tier': 'default', 'finish_reason': 'stop', 'logprobs': None}, id='run--e44f76e3-119a-4ce0-b908-98257ae54be7-0', usage_metadata={'input_tokens': 61, 'output_tokens': 24, 'total_tokens': 85, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})
max_tokens
: 자를 최대 토큰 수 or 메시지 수
strategy
: "last" 또는 "first"
token_counter
: 토큰 수 계산 함수 or 모델 객체
include_system
: 시스템 메시지 포함 여부
allow_partial
: 메시지를 잘라서라도 포함할지 여부
start_on
: 자른 메시지 시작 조건
end_on
: 자른 메시지 종료 조건
The article's ideas are creative, hitting the reader's current research needs. The title is very attractive, making readers want to click on it immediately papa's freezeria.