🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Models & Messages 기초 완벽 가이드 - 슬라이드 1/8
A

AI Generated

2025. 12. 1. · 17 Views

Models & Messages 기초 완벽 가이드

LangChain에서 다양한 AI 모델을 유연하게 다루는 방법과 메시지 시스템의 핵심을 배웁니다. init_chat_model부터 SystemMessage, HumanMessage, AIMessage, ToolMessage까지 실무에서 바로 활용할 수 있는 내용을 담았습니다.


목차

  1. init_chat_model() 심화
  2. 지원 제공자: OpenAI, Anthropic, Google 등
  3. invoke(), stream(), batch() 메서드
  4. SystemMessage 시스템 명령
  5. HumanMessage 사용자 입력
  6. AIMessage 모델 응답
  7. ToolMessage 도구 결과

1. init chat model() 심화

김개발 씨는 회사에서 여러 AI 서비스를 동시에 운영하는 프로젝트를 맡게 되었습니다. OpenAI를 쓰는 서비스, Anthropic을 쓰는 서비스, Google을 쓰는 서비스가 각각 다른 코드로 되어 있어서 유지보수가 너무 힘들었습니다.

"이걸 하나로 통합할 방법이 없을까요?"

**init_chat_model()**은 다양한 AI 제공자의 채팅 모델을 하나의 통일된 인터페이스로 생성하는 함수입니다. 마치 여러 브랜드의 자동차를 운전할 때 핸들과 페달의 위치가 같은 것처럼, 어떤 AI 모델을 사용하든 동일한 방식으로 코드를 작성할 수 있습니다.

이를 통해 모델 교체가 간편해지고, 코드의 일관성을 유지할 수 있습니다.

다음 코드를 살펴봅시다.

from langchain.chat_models import init_chat_model

# 설정 기반으로 모델 생성 - 환경에 따라 쉽게 교체 가능
model = init_chat_model(
    model="gpt-4o",           # 사용할 모델 이름
    model_provider="openai",   # AI 제공자 지정
    temperature=0.7,           # 창의성 조절 (0~1)
    max_tokens=1000            # 최대 응답 길이
)

# configurable 옵션으로 런타임에 모델 변경 가능
flexible_model = init_chat_model(
    configurable_fields=["model", "model_provider"]
)

# 실행 시점에 다른 모델로 교체
response = flexible_model.invoke(
    "안녕하세요",
    config={"configurable": {"model": "claude-3-5-sonnet-20241022"}}
)

김개발 씨는 입사 1년 차 백엔드 개발자입니다. 최근 회사에서 AI 기반 고객 상담 시스템을 맡게 되었는데, 문제가 하나 있었습니다.

기존 시스템은 OpenAI만 사용하도록 되어 있었지만, 비용 절감을 위해 상황에 따라 다른 모델도 써야 했습니다. "OpenAI 코드 따로, Anthropic 코드 따로...

이건 너무 비효율적인데요." 김개발 씨가 한숨을 쉬자, 옆자리 박시니어 씨가 모니터를 힐끗 보며 말했습니다. "init_chat_model() 써봤어요?

LangChain에서 제공하는 통합 인터페이스인데, 아주 편리해요." 그렇다면 **init_chat_model()**이란 정확히 무엇일까요? 쉽게 비유하자면, 이 함수는 마치 만능 리모컨과 같습니다.

집에 TV, 에어컨, 오디오가 각각 다른 브랜드라도 만능 리모컨 하나면 모두 제어할 수 있습니다. init_chat_model()도 마찬가지로, OpenAI든 Anthropic이든 Google이든 하나의 코드로 모두 다룰 수 있게 해줍니다.

이 함수가 없던 시절에는 어땠을까요? 개발자들은 각 AI 제공자마다 별도의 클래스를 임포트하고, 각각 다른 방식으로 초기화해야 했습니다.

ChatOpenAI, ChatAnthropic, ChatGoogleGenerativeAI... 클래스 이름도 다르고, 파라미터 이름도 조금씩 달랐습니다.

모델을 바꾸려면 코드 전체를 수정해야 했고, 실수하기도 쉬웠습니다. 바로 이런 문제를 해결하기 위해 init_chat_model()이 등장했습니다.

이 함수를 사용하면 model_provider 파라미터 하나만 바꾸면 완전히 다른 AI 서비스로 전환됩니다. 더 강력한 기능은 configurable_fields 옵션입니다.

이 옵션을 설정하면 코드 수정 없이 런타임에 모델을 동적으로 교체할 수 있습니다. 위의 코드를 자세히 살펴보겠습니다.

먼저 기본적인 사용법을 봅시다. model 파라미터에는 구체적인 모델 이름을, model_provider에는 제공자를 지정합니다.

temperature와 max_tokens 같은 공통 파라미터는 모든 제공자에서 동일하게 동작합니다. 다음으로 configurable_fields를 활용한 고급 사용법입니다.

이 옵션을 주면 invoke() 호출 시 config 딕셔너리를 통해 어떤 모델을 사용할지 결정할 수 있습니다. A/B 테스트나 사용자별 다른 모델 제공 같은 시나리오에서 매우 유용합니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 SaaS 서비스를 운영한다고 가정해봅시다.

무료 사용자에게는 비용이 저렴한 모델을, 유료 사용자에게는 성능이 좋은 모델을 제공하고 싶을 때 init_chat_model()의 configurable 기능을 활용하면 됩니다. 데이터베이스에서 사용자 등급을 조회한 뒤, 그에 맞는 모델을 config로 전달하면 끝입니다.

하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 model_provider를 생략하는 것입니다.

모델 이름만으로 제공자를 추론할 수도 있지만, 명시적으로 지정하는 것이 안전합니다. 또한 각 제공자별로 필요한 API 키 환경 변수가 다르므로, 해당 환경 변수가 설정되어 있는지 반드시 확인해야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 조언을 듣고 init_chat_model()을 도입한 김개발 씨는 코드량이 절반으로 줄어든 것을 보고 감탄했습니다.

"이렇게 간단한 방법이 있었다니!" init_chat_model()을 제대로 이해하면 멀티 모델 환경에서도 깔끔하고 유지보수하기 쉬운 코드를 작성할 수 있습니다.

실전 팁

💡 - model_provider는 항상 명시적으로 지정하여 코드의 의도를 명확히 하세요

  • configurable_fields를 활용하면 환경 설정 파일만으로 모델을 교체할 수 있습니다
  • 각 제공자의 API 키 환경 변수명이 다르니 문서를 꼭 확인하세요

2. 지원 제공자: OpenAI, Anthropic, Google 등

"GPT-4가 좋다던데요." "아니, Claude가 더 똑똑해요." "Gemini도 무시 못 해요." 점심시간 개발팀 회의실은 AI 모델 논쟁으로 뜨거웠습니다. 김개발 씨는 문득 궁금해졌습니다.

LangChain에서는 도대체 어떤 AI 제공자들을 지원하고, 각각 어떤 특징이 있을까요?

LangChain은 OpenAI, Anthropic, Google, Azure, AWS Bedrock, Mistral, Ollama 등 주요 AI 제공자를 모두 지원합니다. 마치 세계 각국의 항공사 티켓을 하나의 예약 사이트에서 구매할 수 있는 것처럼, LangChain 하나로 다양한 AI 서비스에 접근할 수 있습니다.

각 제공자마다 강점이 다르므로 용도에 맞게 선택하면 됩니다.

다음 코드를 살펴봅시다.

from langchain.chat_models import init_chat_model

# OpenAI - 가장 범용적, 다양한 기능 지원
openai_model = init_chat_model("gpt-4o", model_provider="openai")

# Anthropic - 긴 문맥, 안전한 응답에 강점
anthropic_model = init_chat_model(
    "claude-3-5-sonnet-20241022",
    model_provider="anthropic"
)

# Google - Gemini 시리즈, 멀티모달에 강점
google_model = init_chat_model(
    "gemini-1.5-pro",
    model_provider="google_genai"
)

# Ollama - 로컬 실행, 개인정보 보호에 유리
local_model = init_chat_model(
    "llama3.1",
    model_provider="ollama"
)

# 모든 모델이 동일한 인터페이스 사용
response = openai_model.invoke("안녕하세요")

김개발 씨네 회사는 고객 상담 챗봇을 운영하고 있습니다. 그런데 최근 경영진에서 비용 절감 지시가 내려왔습니다.

"단순 문의는 저렴한 모델로, 복잡한 상담만 고성능 모델로 처리하면 어떨까요?" 팀장님의 제안에 김개발 씨는 고개를 끄덕였지만, 속으로는 걱정이 됐습니다. 모델마다 코드가 다 다르면 어떡하지?

다행히 LangChain은 이런 상황을 위해 설계되었습니다. OpenAI는 가장 널리 사용되는 제공자입니다.

GPT-4o, GPT-4 Turbo, GPT-3.5 Turbo 등 다양한 모델 라인업을 갖추고 있습니다. 특히 함수 호출(Function Calling)과 JSON 모드 같은 고급 기능 지원이 뛰어납니다.

범용적인 작업에 가장 무난한 선택입니다. Anthropic은 Claude 시리즈로 유명합니다.

특히 긴 문맥 처리에 강점이 있어서 긴 문서를 분석하거나 복잡한 대화를 이어갈 때 유용합니다. 또한 안전하고 윤리적인 응답을 생성하는 데 초점을 맞추고 있어서, 민감한 주제를 다루는 서비스에 적합합니다.

Google의 Gemini 시리즈는 멀티모달 능력이 뛰어납니다. 텍스트뿐만 아니라 이미지, 비디오, 오디오까지 처리할 수 있습니다.

Google Cloud 생태계와의 연동도 원활해서, 이미 GCP를 사용하는 기업에서 선호합니다. Ollama는 조금 다른 접근법을 취합니다.

클라우드가 아닌 로컬 컴퓨터에서 모델을 실행합니다. 인터넷 연결 없이도 작동하고, 데이터가 외부로 전송되지 않아 개인정보 보호가 중요한 상황에서 유용합니다.

Llama, Mistral 같은 오픈소스 모델을 실행할 수 있습니다. 그 외에도 Azure OpenAI(기업용 OpenAI), AWS Bedrock(AWS 환경), Mistral(유럽 기반 경량 모델), Cohere(검색 특화) 등 다양한 제공자를 지원합니다.

위의 코드에서 주목할 점은 모든 모델이 동일한 invoke() 메서드를 사용한다는 것입니다. 어떤 제공자를 선택하든 나머지 코드는 바꿀 필요가 없습니다.

이것이 바로 LangChain의 추상화가 주는 가장 큰 이점입니다. 실제로 김개발 씨는 이 특성을 활용해 라우팅 시스템을 구축했습니다.

사용자의 질문을 분석해서 단순 문의면 GPT-3.5로, 복잡한 상담이면 GPT-4o로, 긴 문서 분석이면 Claude로 자동 라우팅되도록 했습니다. 주의할 점은 각 제공자마다 환경 변수 설정이 다르다는 것입니다.

OpenAI는 OPENAI_API_KEY, Anthropic은 ANTHROPIC_API_KEY, Google은 GOOGLE_API_KEY를 사용합니다. 여러 제공자를 사용할 계획이라면 모든 API 키를 미리 설정해두어야 합니다.

또한 각 제공자마다 가격 정책과 속도 제한이 다릅니다. 프로덕션 환경에서는 이런 요소들을 고려해서 적절한 제공자를 선택해야 합니다.

김개발 씨는 비용 보고서를 보며 뿌듯해했습니다. 적재적소에 다른 모델을 배치한 덕분에 월 비용이 40%나 줄었습니다.

"LangChain 덕분에 가능했네요."

실전 팁

💡 - 단순 작업에는 저렴한 모델, 복잡한 작업에는 고성능 모델을 구분해서 사용하세요

  • 민감한 데이터를 다룬다면 Ollama로 로컬 실행을 고려해보세요
  • 각 제공자의 무료 크레딧을 활용해 먼저 테스트해보는 것을 권장합니다

3. invoke(), stream(), batch() 메서드

"응답이 왜 이렇게 느려요?" 사용자 클레임이 들어왔습니다. 김개발 씨가 로그를 확인해보니 AI 응답을 한 번에 받아오느라 10초 넘게 기다리고 있었습니다.

박시니어 씨가 힌트를 줬습니다. "stream() 메서드 써봤어요?

응답을 실시간으로 보여줄 수 있어요."

LangChain의 채팅 모델은 세 가지 핵심 호출 방식을 제공합니다. **invoke()**는 전체 응답을 한 번에 받고, **stream()**은 응답을 토큰 단위로 실시간 수신하며, **batch()**는 여러 요청을 동시에 처리합니다.

마치 물을 받을 때 컵에 한 번에 따르거나, 수도꼭지에서 조금씩 받거나, 여러 컵에 동시에 따르는 것과 같습니다.

다음 코드를 살펴봅시다.

from langchain.chat_models import init_chat_model

model = init_chat_model("gpt-4o", model_provider="openai")

# invoke() - 전체 응답을 한 번에 받기
response = model.invoke("파이썬의 장점을 설명해주세요")
print(response.content)  # 완성된 전체 응답 출력

# stream() - 응답을 토큰 단위로 실시간 수신
for chunk in model.stream("자바스크립트의 장점을 설명해주세요"):
    print(chunk.content, end="", flush=True)  # 글자가 하나씩 출력

# batch() - 여러 요청을 동시에 처리
questions = ["React란?", "Vue란?", "Angular란?"]
responses = model.batch(questions)  # 병렬로 처리되어 시간 절약
for q, r in zip(questions, responses):
    print(f"Q: {q}\nA: {r.content}\n")

김개발 씨가 만든 챗봇은 기능상 문제는 없었습니다. 하지만 사용자들의 불만이 계속됐습니다.

"로딩 중 화면만 10초 넘게 보고 있어야 해요." AI가 긴 답변을 생성하는 동안 사용자는 아무것도 볼 수 없었던 것입니다. 박시니어 씨가 다가와 화면을 보더니 말했습니다.

"ChatGPT 웹사이트 써봤죠? 거기선 글자가 하나씩 나타나잖아요.

그게 바로 stream() 메서드를 사용한 거예요." LangChain의 채팅 모델은 세 가지 호출 방식을 제공합니다. 각각의 특성을 알아봅시다.

먼저 **invoke()**는 가장 기본적인 방식입니다. 요청을 보내면 AI가 응답을 완전히 생성한 뒤에 결과를 반환합니다.

코드가 단순하고 디버깅하기 쉽습니다. 응답이 짧거나, 배치 처리처럼 사용자가 실시간으로 보지 않는 상황에 적합합니다.

**stream()**은 응답을 토큰 단위로 실시간 수신합니다. AI가 한 글자 생성할 때마다 그 글자를 바로 받아볼 수 있습니다.

전체 응답이 완성될 때까지 기다릴 필요가 없어서 사용자 체감 속도가 훨씬 빠릅니다. ChatGPT나 Claude 웹 인터페이스가 이 방식을 사용합니다.

stream()은 제너레이터를 반환합니다. for 루프로 순회하면 chunk 객체가 하나씩 넘어오고, chunk.content에 텍스트 조각이 들어있습니다.

이걸 화면에 출력하면 타이핑 효과가 나타납니다. **batch()**는 여러 요청을 동시에 처리합니다.

10개의 질문이 있다면 순차적으로 처리하면 10배의 시간이 걸리지만, batch()를 사용하면 병렬로 처리되어 시간을 크게 절약할 수 있습니다. 데이터 처리 파이프라인이나 대량 분석 작업에 적합합니다.

위의 코드에서 각 메서드의 사용법을 확인할 수 있습니다. invoke()와 batch()는 결과를 바로 반환하고, stream()은 for 루프로 순회해야 합니다.

실제 현업에서는 이렇게 활용합니다. 웹 챗봇에서는 stream()을 사용해 **Server-Sent Events(SSE)**로 프론트엔드에 실시간 전달합니다.

사용자는 AI가 "생각하고 있다"는 느낌을 받으며 답변을 기다립니다. 백엔드 배치 작업에서는 batch()를 사용해 수천 개의 문서를 동시에 분석합니다.

주의할 점도 있습니다. stream()을 사용할 때 에러 처리가 조금 복잡해집니다.

스트리밍 중간에 에러가 발생하면 이미 출력된 내용을 롤백하기 어렵기 때문입니다. 또한 batch()를 사용할 때는 API 속도 제한을 고려해야 합니다.

너무 많은 요청을 동시에 보내면 제한에 걸릴 수 있습니다. 김개발 씨는 챗봇에 stream()을 적용했습니다.

같은 응답 시간이지만 사용자들의 반응은 완전히 달라졌습니다. "와, 진짜 대화하는 것 같아요!" 체감 속도의 마법이었습니다.

실전 팁

💡 - 사용자 대면 서비스에서는 stream()을 사용해 체감 속도를 높이세요

  • batch()는 max_concurrency 옵션으로 동시 요청 수를 제한할 수 있습니다
  • invoke()는 단순한 테스트나 짧은 응답에 적합합니다

4. SystemMessage 시스템 명령

"이 AI가 왜 이렇게 딱딱하게 말해요?" 마케팅팀에서 불만이 들어왔습니다. 김개발 씨가 확인해보니 AI가 너무 형식적인 답변만 내놓고 있었습니다.

"AI한테 '친근하게 말해'라고 어떻게 시키죠?" 박시니어 씨가 답했습니다. "SystemMessage를 사용하면 돼요.

AI의 성격을 정해주는 거죠."

SystemMessage는 AI에게 역할, 성격, 규칙을 지정하는 시스템 수준의 명령입니다. 마치 연극 배우에게 "너는 친절한 상담사 역할이야"라고 알려주는 것과 같습니다.

대화 시작 전에 AI가 어떻게 행동해야 하는지 설정하여 일관된 응답 스타일을 유지할 수 있습니다.

다음 코드를 살펴봅시다.

from langchain_core.messages import SystemMessage, HumanMessage
from langchain.chat_models import init_chat_model

model = init_chat_model("gpt-4o", model_provider="openai")

# SystemMessage로 AI의 역할과 성격 정의
messages = [
    SystemMessage(content="""당신은 10년 경력의 친절한 파이썬 튜터입니다.
    - 초보자도 이해할 수 있게 쉬운 비유를 사용하세요
    - 코드 예제는 항상 주석과 함께 제공하세요
    - 질문자를 격려하고 응원해주세요
    - 답변은 3단락 이내로 간결하게 유지하세요"""),
    HumanMessage(content="변수가 뭐예요?")
]

response = model.invoke(messages)
print(response.content)
# 친근하고 쉬운 설명이 출력됨

마케팅팀에서 요청한 것은 "MZ세대 감성의 친근한 챗봇"이었습니다. 그런데 김개발 씨가 만든 챗봇은 마치 백과사전처럼 딱딱하게 대답했습니다.

"변수는 데이터를 저장하는 메모리 공간의 이름입니다." 틀린 말은 아니지만, 느낌이 너무 차가웠습니다. 박시니어 씨가 해결책을 알려줬습니다.

"AI는 백지 상태로 시작해요. 어떻게 말해야 하는지 알려주지 않으면 기본값으로 응답하죠.

SystemMessage로 AI의 성격을 정해줘야 해요." SystemMessage는 대화의 맥락에서 특별한 위치를 차지합니다. 항상 메시지 목록의 맨 앞에 위치하며, AI에게 "너는 누구이고, 어떻게 행동해야 하는지" 알려주는 역할을 합니다.

이것은 마치 연극의 캐스팅과 같습니다. 배우에게 "당신은 이번 연극에서 친절한 의사 역할입니다.

환자에게 항상 따뜻하게 말하고, 전문용어는 쉽게 풀어서 설명하세요."라고 알려주는 것과 비슷합니다. 배우는 그 지시에 따라 연기합니다.

SystemMessage에 담을 수 있는 내용은 다양합니다. 역할 정의("당신은 10년 경력의 파이썬 튜터입니다"), 성격 설정("친근하고 유머러스하게"), 응답 규칙("답변은 3단락 이내로"), 금지 사항("가격 정보는 언급하지 마세요") 등을 지정할 수 있습니다.

위의 코드를 보면, messages 리스트의 첫 번째 요소로 SystemMessage가 들어갑니다. 그 안에 AI의 역할(파이썬 튜터)과 구체적인 행동 지침(쉬운 비유, 주석 포함, 격려, 간결함)이 명시되어 있습니다.

실무에서 SystemMessage는 다양하게 활용됩니다. 고객 상담 봇에서는 "회사의 공식 입장만 전달하고, 확실하지 않은 정보는 '확인 후 안내드리겠습니다'라고 답하세요"라고 설정합니다.

교육용 봇에서는 "답을 바로 알려주지 말고, 힌트를 주며 스스로 생각하게 유도하세요"라고 설정합니다. 주의할 점은 SystemMessage가 절대적인 명령은 아니라는 것입니다.

특히 사용자가 교묘하게 유도하면 시스템 메시지를 무시하는 응답이 나올 수 있습니다. 이를 프롬프트 인젝션이라고 합니다.

중요한 보안 규칙은 시스템 메시지만으로 해결하지 말고, 애플리케이션 레벨에서도 검증해야 합니다. 또한 SystemMessage가 너무 길면 토큰을 많이 소비합니다.

모든 요청에 시스템 메시지가 포함되므로, 간결하면서도 핵심을 담는 것이 중요합니다. 김개발 씨는 SystemMessage를 추가한 뒤 마케팅팀에 다시 보여줬습니다.

"변수요? 쉽게 말해서 물건을 담는 상자라고 생각하시면 돼요!

상자에 이름표를 붙여두면 나중에 찾기 쉽잖아요. 변수도 똑같아요." 마케팅팀 반응은?

"이거예요, 이거!"

실전 팁

💡 - SystemMessage는 가능한 구체적으로 작성하되, 핵심만 담아 토큰을 절약하세요

  • 여러 버전의 SystemMessage를 A/B 테스트해서 최적의 결과를 찾으세요
  • 민감한 규칙은 SystemMessage만 믿지 말고 애플리케이션 레벨에서도 검증하세요

5. HumanMessage 사용자 입력

"이상하네, 분명 질문을 보냈는데 AI가 이상한 답을 해요." 김개발 씨가 디버깅을 해보니 사용자 입력이 제대로 전달되지 않고 있었습니다. LangChain에서 사용자의 질문을 AI에게 전달하려면 단순 문자열이 아닌 특별한 형식이 필요했습니다.

HumanMessage는 사용자가 AI에게 보내는 메시지를 나타내는 클래스입니다. 마치 편지 봉투에 "보내는 사람: 김개발"이라고 적는 것처럼, 이 메시지가 사용자로부터 온 것임을 AI에게 명확히 알려줍니다.

멀티턴 대화에서 누가 한 말인지 구분하는 핵심 역할을 합니다.

다음 코드를 살펴봅시다.

from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
from langchain.chat_models import init_chat_model

model = init_chat_model("gpt-4o", model_provider="openai")

# 멀티턴 대화 구성 - 각 메시지의 역할이 명확함
messages = [
    SystemMessage(content="당신은 요리 전문가입니다."),
    HumanMessage(content="파스타 만드는 법 알려주세요"),
    AIMessage(content="파스타는 먼저 면을 삶고..."),  # 이전 AI 응답
    HumanMessage(content="소스는 어떻게 만들어요?")   # 현재 사용자 질문
]

# AI는 대화 맥락을 이해하고 응답
response = model.invoke(messages)
print(response.content)  # 파스타 소스 만드는 법 설명

# 추가 정보와 함께 메시지 생성
detailed_message = HumanMessage(
    content="이 이미지 분석해주세요",
    additional_kwargs={"image_url": "https://example.com/image.jpg"}
)

김개발 씨는 간단한 챗봇을 만들고 있었습니다. 사용자가 입력한 텍스트를 그대로 model.invoke()에 넣었더니 작동은 했습니다.

하지만 대화를 이어가려고 하니 문제가 생겼습니다. AI가 이전 대화를 기억하지 못하는 것이었습니다.

박시니어 씨가 코드를 보더니 말했습니다. "AI 모델 자체는 상태가 없어요.

매 요청마다 대화 이력을 함께 보내야 맥락을 이해해요. 그리고 누가 한 말인지 구분하려면 HumanMessageAIMessage를 써야 해요." HumanMessage는 "이 메시지는 사용자가 보낸 것"이라는 라벨입니다.

마치 법정에서 "증인의 발언입니다"라고 기록하는 것처럼, 대화에서 각 발언의 주체를 명확히 합니다. 왜 이런 구분이 필요할까요?

AI가 대화를 이해하려면 역할 구분이 필수입니다. "파스타 만드는 법 알려주세요"가 사용자의 질문인지, AI의 자문자답인지 알아야 적절히 응답할 수 있습니다.

HumanMessage는 이 메시지가 "질문자의 입력"임을 명시합니다. 위의 코드에서 멀티턴 대화를 구성하는 방법을 볼 수 있습니다.

messages 리스트에 SystemMessage, HumanMessage, AIMessage가 차례로 들어갑니다. 이전 대화 이력(첫 번째 HumanMessage와 AIMessage)을 함께 보내면 AI는 맥락을 이해하고, "소스"가 파스타 소스를 의미한다는 것을 파악합니다.

HumanMessage의 content 속성에는 텍스트가 들어갑니다. 추가로 additional_kwargs를 통해 이미지 URL 같은 부가 정보를 전달할 수도 있습니다.

멀티모달 모델을 사용할 때 유용합니다. 실무에서는 대화 이력을 데이터베이스세션 저장소에 보관합니다.

사용자가 새 메시지를 보내면 이전 이력을 불러와서 새 HumanMessage와 함께 AI에게 전달합니다. 대화가 길어지면 토큰 한도를 초과할 수 있으므로, 오래된 메시지는 요약하거나 삭제하는 전략이 필요합니다.

주의할 점은 단순 문자열과 HumanMessage의 차이입니다. invoke("안녕하세요")처럼 문자열을 직접 넣어도 작동합니다.

LangChain이 내부적으로 HumanMessage로 변환하기 때문입니다. 하지만 멀티턴 대화를 구성하거나 메타데이터를 추가하려면 명시적으로 HumanMessage를 사용해야 합니다.

김개발 씨는 대화 이력 관리 로직을 추가했습니다. Redis에 세션별 메시지 목록을 저장하고, 매 요청마다 불러와서 함께 전송했습니다.

이제 사용자가 "아까 그거요"라고 해도 AI가 맥락을 이해했습니다. "대화가 진짜 자연스러워졌어요!"

실전 팁

💡 - 대화 이력이 길어지면 토큰 한도를 초과하니 적절히 관리하세요

  • 중요한 맥락은 SystemMessage에 요약해서 포함하는 것도 방법입니다
  • 보안상 민감한 정보는 대화 이력에서 마스킹 처리하세요

6. AIMessage 모델 응답

"AI 응답에서 어디까지가 텍스트고 어디부터가 함수 호출이에요?" 김개발 씨는 AI의 응답 구조가 복잡해서 헷갈렸습니다. 단순한 텍스트 응답인 줄 알았는데, 그 안에 도구 호출 정보, 토큰 사용량 등 다양한 정보가 담겨 있었습니다.

AIMessage는 AI 모델이 생성한 응답을 담는 클래스입니다. 마치 택배 상자처럼 겉보기에는 하나지만, 안에는 텍스트 응답, 도구 호출 정보, 메타데이터 등 다양한 정보가 담겨 있습니다.

이 구조를 이해하면 AI 응답을 더 풍부하게 활용할 수 있습니다.

다음 코드를 살펴봅시다.

from langchain_core.messages import HumanMessage
from langchain.chat_models import init_chat_model

model = init_chat_model("gpt-4o", model_provider="openai")

# AI 응답 받기
response = model.invoke([HumanMessage(content="안녕하세요")])

# AIMessage의 다양한 속성 활용
print(response.content)           # 텍스트 응답: "안녕하세요! 무엇을..."
print(response.response_metadata) # 토큰 사용량, 모델명 등 메타정보
print(response.id)                # 응답 고유 ID

# 도구 호출이 있는 경우
print(response.tool_calls)        # [{"name": "search", "args": {...}}]

# 대화 이력에 추가하여 맥락 유지
messages = [
    HumanMessage(content="파이썬이란?"),
    response,  # AIMessage를 그대로 추가
    HumanMessage(content="더 자세히 설명해주세요")
]
followup = model.invoke(messages)

김개발 씨는 AI 응답을 받아서 화면에 출력하는 것까지는 성공했습니다. response.content를 출력하면 텍스트가 나왔습니다.

그런데 선배가 물었습니다. "이번 요청에서 토큰 몇 개 썼어요?" 김개발 씨는 대답하지 못했습니다.

박시니어 씨가 설명해줬습니다. "invoke()의 반환값은 단순 문자열이 아니라 AIMessage 객체야.

그 안에 다양한 정보가 담겨 있어." AIMessage는 AI의 응답을 담는 컨테이너입니다. 마치 택배 상자 안에 물건뿐 아니라 송장, 완충재, 설명서가 함께 들어있는 것처럼, AIMessage 안에도 여러 정보가 함께 담겨 있습니다.

가장 중요한 속성은 content입니다. AI가 생성한 텍스트 응답이 여기에 들어있습니다.

대부분의 경우 이것만 사용해도 충분합니다. response_metadata에는 메타 정보가 담깁니다.

토큰 사용량(입력, 출력 각각), 모델 이름, 처리 시간 등을 확인할 수 있습니다. 비용 추적이나 성능 모니터링에 유용합니다.

tool_calls는 AI가 도구를 호출하려 할 때 채워집니다. 예를 들어 "오늘 날씨 알려줘"라고 했을 때 AI가 날씨 API를 호출해야 한다고 판단하면, 직접 호출하는 대신 "이 도구를 이렇게 호출해주세요"라는 정보를 반환합니다.

이에 대해서는 ToolMessage에서 자세히 다룹니다. 위의 코드에서 AIMessage의 다양한 속성을 활용하는 방법을 볼 수 있습니다.

특히 주목할 점은 AIMessage를 그대로 대화 이력에 추가할 수 있다는 것입니다. 이전 AI 응답을 messages 리스트에 넣으면 다음 요청에서 맥락이 유지됩니다.

실무에서 response_metadata는 비용 관리에 활용됩니다. 매 요청마다 토큰 사용량을 로깅하고, 일별/사용자별 사용량을 집계합니다.

과도한 사용을 감지하면 알림을 보내거나 제한을 걸 수 있습니다. 또한 AIMessage의 id는 디버깅에 유용합니다.

문제가 생겼을 때 특정 응답을 추적할 수 있습니다. 로그에 함께 기록해두면 나중에 분석할 때 도움이 됩니다.

주의할 점은 content가 빈 문자열일 수 있다는 것입니다. AI가 도구만 호출하고 텍스트 응답 없이 끝나는 경우가 있습니다.

content만 확인하면 "응답이 없다"고 오해할 수 있으니, tool_calls도 함께 확인해야 합니다. 김개발 씨는 이제 모든 AI 응답에서 토큰 사용량을 추적합니다.

월말 비용 보고서를 만들 때 정확한 데이터를 제시할 수 있게 되었습니다. "AIMessage 구조를 알고 나니까 할 수 있는 게 많아졌네요."

실전 팁

💡 - response_metadata에서 토큰 사용량을 추적하여 비용을 관리하세요

  • content가 비어있어도 tool_calls에 데이터가 있을 수 있으니 확인하세요
  • 대화 이력 관리 시 AIMessage 객체를 그대로 재사용하면 편리합니다

7. ToolMessage 도구 결과

"AI가 날씨 정보를 알려준다고 했는데, 어떻게 실제 날씨 API를 호출하는 거예요?" 김개발 씨의 질문에 박시니어 씨가 웃으며 답했습니다. "AI가 직접 호출하는 게 아니에요.

AI는 '이 도구를 호출해달라'고 요청하고, 우리가 호출한 다음 결과를 ToolMessage로 돌려주는 거죠."

ToolMessage는 외부 도구의 실행 결과를 AI에게 전달하는 메시지입니다. 마치 비서가 상사 대신 전화를 걸고 결과를 보고하는 것처럼, 개발자가 도구를 실행하고 그 결과를 AI에게 알려줍니다.

AI는 이 결과를 바탕으로 최종 응답을 생성합니다.

다음 코드를 살펴봅시다.

from langchain_core.messages import HumanMessage, AIMessage, ToolMessage
from langchain.chat_models import init_chat_model

model = init_chat_model("gpt-4o", model_provider="openai")

# 1. 사용자가 날씨를 물어봄
messages = [HumanMessage(content="서울 날씨 어때요?")]
response = model.invoke(messages)

# 2. AI가 도구 호출 요청 (tool_calls에 정보가 담김)
if response.tool_calls:
    tool_call = response.tool_calls[0]
    # {"name": "get_weather", "args": {"city": "서울"}, "id": "call_abc123"}

    # 3. 개발자가 실제 API 호출 (여기서는 예시)
    weather_result = {"temperature": "15도", "condition": "맑음"}

    # 4. ToolMessage로 결과 전달
    messages.append(response)  # AI의 도구 호출 요청
    messages.append(ToolMessage(
        content=str(weather_result),
        tool_call_id=tool_call["id"]  # 어떤 호출의 결과인지 연결
    ))

    # 5. AI가 결과를 보고 최종 응답 생성
    final_response = model.invoke(messages)
    print(final_response.content)  # "서울은 현재 15도이고 맑습니다."

AI 챗봇에 "오늘 서울 날씨 알려줘"라고 물으면 어떻게 될까요? AI가 직접 기상청 API를 호출할까요?

그렇지 않습니다. AI 모델은 텍스트를 생성할 뿐, 직접 외부 시스템에 접근할 수 없습니다.

대신 **도구 호출(Tool Calling)**이라는 패턴을 사용합니다. AI가 "날씨를 알려면 get_weather 함수를 호출해야 해"라고 알려주면, 개발자가 실제로 호출하고, 결과를 ToolMessage로 AI에게 돌려주는 방식입니다.

이것은 마치 비서와 상사의 관계와 같습니다. 상사(AI)가 "박 대리한테 전화해서 일정 확인해줘"라고 지시하면, 비서(개발자)가 전화를 걸고 결과를 보고합니다.

"박 대리님이 목요일 2시에 가능하다고 합니다." 상사는 그 정보를 듣고 최종 결정을 내립니다. 위의 코드에서 전체 흐름을 볼 수 있습니다.

먼저 사용자가 날씨를 물어봅니다. AI는 tool_calls에 "get_weather를 호출하라"는 정보를 담아 응답합니다.

개발자는 이 정보를 보고 실제 날씨 API를 호출합니다. 그 결과를 ToolMessage에 담아 AI에게 전달합니다.

마지막으로 AI는 원시 데이터를 자연어로 변환하여 사용자에게 응답합니다. tool_call_id가 핵심입니다.

여러 도구를 동시에 호출할 수 있으므로, 어떤 요청에 대한 결과인지 연결해야 합니다. AI가 요청할 때 id를 부여하고, ToolMessage에서 같은 id를 사용합니다.

실무에서 이 패턴은 다양하게 활용됩니다. 데이터베이스 조회("고객 정보 가져와"), 외부 API 호출("환율 확인해"), 계산("이 수식 계산해") 등을 AI의 판단에 따라 실행할 수 있습니다.

AI가 언제 어떤 도구를 쓸지 결정하고, 개발자는 실행만 담당합니다. LangChain의 Tool 시스템과 함께 사용하면 더 강력해집니다.

도구를 정의하고 모델에 바인딩하면, 도구 호출과 결과 전달이 자동화됩니다. 하지만 기본 원리를 이해하려면 ToolMessage를 직접 다뤄보는 것이 좋습니다.

주의할 점은 도구 결과의 형식입니다. ToolMessage의 content는 문자열이어야 합니다.

JSON 객체라면 str()이나 json.dumps()로 변환해야 합니다. AI는 이 문자열을 해석해서 자연어 응답을 만듭니다.

또한 에러 처리도 중요합니다. 도구 호출이 실패하면 어떻게 할까요?

에러 메시지를 ToolMessage로 전달하면 AI가 "죄송합니다, 날씨 정보를 가져오는 데 실패했습니다"라고 적절히 응답할 수 있습니다. 김개발 씨는 ToolMessage를 활용해 다양한 기능을 챗봇에 추가했습니다.

재고 조회, 주문 상태 확인, 배송 추적까지. AI가 대화 맥락에서 적절한 도구를 선택하고, 결과를 자연스럽게 안내했습니다.

"진짜 똑똑한 비서 같아요!"

실전 팁

💡 - tool_call_id를 정확히 매칭하지 않으면 AI가 결과를 인식하지 못합니다

  • 도구 실행 실패 시에도 ToolMessage로 에러를 전달하면 AI가 적절히 대응합니다
  • 복잡한 도구 호출은 LangChain의 Tool 시스템을 활용하면 더 편리합니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#LangChain#ChatModel#Messages#init_chat_model#SystemMessage#HumanMessage#AIMessage#AI,LLM,Python,LangChain

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.