
Python×LangChain으로 시작하는 Gemini 개발 입문! Web 검색 연동·대화 이력·정확도 평가까지 철저 해설
요약
Python과 LangChain을 활용하여 Google Gemini 모델을 연동하고 구현하는 방법을 다룹니다. 스트리밍 응답 처리, 대화 이력 관리, Google 검색 연동(Grounding) 등 실무적인 개발 가이드를 제공합니다.
핵심 포인트
- langchain-google-genai 패키지를 이용한 Gemini 연동 방법
- stream 메서드를 활용한 실시간 타이핑 효과 구현
- Prompt Template과 Chat History를 이용한 대화 문맥 유지
- Google Search Grounding을 통한 최신 정보 검색 연동
Python과 LangChain을 사용하여 Gemini (Google Generative AI)와 통신하는 기본적인 구현 방법에 대한 해설입니다.
일부 코드(code)가 포함되어 있으므로 취급에 주의해 주세요. 코드 게재에는 충분히 주의를 기울이고 있습니다만,
매일 API 사양이 진화하고 있기 때문에 확실한 동작을 보장하는 것은 아닙니다. 이 점 유의해 주시기 바랍니다.
최신 LangChain에서는 Google Gemini를 이용하기 위해 langchain-google-genai 패키지를 사용하는 것이 주류입니다.
먼저 필요한 라이브러리를 설치합니다.
pip install langchain-google-genai
Google AI Studio에서 생성한 API 키를 취득하여 환경 변수에 설정해 주세요.
# Mac/Linux의 경우
export GOOGLE_API_KEY="your-api-key-here"
# Windows (PowerShell)의 경우
...
심플한 일문일답 샘플 코드입니다.
import os
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.messages import HumanMessage
...
Gemini로부터의 답변을 실시간(타이핑 방식)으로 표시하고 싶다면, stream 메서드를 사용합니다. 이렇게 함으로써 시각적으로 서버로부터 응답이 빠르게 느껴지기 때문에 유효한 수단입니다.
※ 단, 결과가 모두 갖춰지지 않으면 최종적인 답변을 화면에 표시할 수 없으므로, 기분 전환 정도밖에 되지 않겠지요.
from langchain_google_genai import ChatGoogleGenerativeAI
model = ChatGoogleGenerativeAI(model="gemini-2.5-flash")
# 스트리밍 응답 루프
...
LangChain의 ChatGoogleGenerativeAI 인테그레이션 (langchain-google-genai 패키지)을 사용하여 Google의 Gemini 모델을 호출하는 객체를 생성하고 있습니다. model.stream(...)을 사용함으로써, AI가 답변 작성을 마칠 때까지 기다리는 것이 아니라, 생성된 텍스트의 파편 (청크: chunk)을 순차적으로 취득합니다.
chunk.content: 전달받은 텍스트 파편 데이터의 내용 (문자열)입니다.
end="": Python 표준의 print()는 통상 마지막에 개행을 넣지만, 텍스트를 매끄럽게 연결하기 위해 개행을 무효화하고 있습니다.
flush=True: 버퍼 (내부 메모리)에 쌓아두지 않고 화면에 즉시 출력 (그리기) 시킵니다. 이를 통해 "AI가 타닥타닥 글자를 치고 있는 듯한" 실시간 표시를 실현합니다.
스트리밍 출력이 모두 완료된 후에 개행을 넣어 터미널 등의 표시를 정돈하고 있습니다.
프롬프트 템플릿 (Prompt Template)과 조합하여, 과거의 대화 문맥을 고려한 주고받기를 수행하는 예시는 다음과 같습니다.
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.chat_history import InMemoryChatMessageHistory
...
Python+LangChain과 Gemini를 조합한 Web 검색 연동 (Grounding with Google Search) 구현 방법의 구현 예시입니다. Gemini에는 Google 검색 기능 (Grounding)이 내장되어 있으므로, LangChain 측에서 도구 (Tool)로서 바인딩함으로써 최신 정보나 실시간 데이터를 자동으로 검색하여 답변하게 하는 것도 가능해집니다.
ChatGoogleGenerativeAI의 bind_tools 메서드를 사용하여 Google 검색 기능을 직접 활성화합니다.
import os
from langchain_google_genai import ChatGoogleGenerativeAI
# 모델 인스턴스화
...
포인트
{"google_search": {}}
를 바인딩(bind)함으로써, Gemini 모델 스스로가 "이 질문에는 웹 검색이 필요하다"라고 판단한 시점에 자동으로 Google 검색을 실행합니다.
Gemini가 검색을 수행하여 답변한 경우, 응답의 메타데이터(response_metadata) 내에 참조한 웹 페이지의 URL이나 검색 쿼리 등의 메타 정보(Grounding Metadata)가 포함됩니다.
from langchain_google_genai import ChatGoogleGenerativeAI
llm = ChatGoogleGenerativeAI(model="gemini-2.5-flash")
llm_with_search = llm.bind_tools([{"google_search": {}}])
...
Gemini 내장 Google 검색이 아니라, 독자적으로 커스터마이징 가능한 외부 검색 엔진 API(예: DuckDuckGo, Google Custom Search 등)를 사용하여 RAG (검색 증강 생성, Retrieval-Augmented Generation) 채팅을 구축하는 것도 가능합니다. 다음은 LLM 애플리케이션 개발에서 자주 사용되는 Tavily API를 사용한 LangChain의 Agent 예시입니다.
pip install langchain-community tavily-python
import os
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_community.tools.tavily_search import TavilySearchResults
...
| 수법 | 장점 | 단점 |
|---|---|---|
| Google Search (Grounding) | 코드 몇 줄로 도입 가능. Gemini 공식 검색 결과와 자연스럽게 통합됨. | 검색 프로세스의 세밀한 컨트롤은 모델에 일임됨. |
| 외부 검색 Tool (Tavily 등) | 검색 대상 도메인 제한이나 검색 쿼리 커스텀 등 유연한 제어가 가능함. | API 키가 별도로 필요하며 코드가 늘어남. |
용도에 맞춰 선택해 보세요.
--
웹 애플리케이션 개발이나 시스템 운용에 있어 "세션이 끊겼는지(유효 기간이 만료되었는지)"를 확인하는 방법은, **"프론트엔드(브라우저 등)에서 판정하는 경우"**와 **"백엔드(서버/LangChain 등)에서 판정하는 경우"**로 접근 방식이 다릅니다.
각각의 관점에서의 구체적인 확인 및 판정 방법에 대해 아래에서 해설합니다.
사용자의 브라우저 상에서 세션이 끊겼는지를 판정하는 주요 수법입니다.
구체적으로는 세션 ID 등을 Cookie에 저장하고 있는 경우, JavaScript를 통해 Cookie의 존재를 확인합니다.
// 쿠키에서 특정 세션 키가 존재하는지 확인
function isSessionCookieAlive(cookieName) {
const cookies = document.cookie.split('; ');
...
유효 기간이 만료된 세션으로 API를 호출했을 때, 서버로부터 401 Unauthorized 또는 403 Forbidden이 반환되는 것으로 판정합니다.
fetch('/api/user-profile')
.then(response => {
if (response.status === 401) {
...
서버 사이드 측에서 요청을 받았을 때, 해당 세션이 유효한지 검증하는 방법을 아래에서 해설합니다.
세션 스토리지(Redis나 데이터베이스)에 저장된 "최종 액세스 일시"와 "현재 일시"를 비교하여 판정합니다.
from datetime import datetime, timedelta
# 예: 최종 활성 시간으로부터 30분 이상 경과했다면 세션 만료
SESSION_TIMEOUT_MINUTES = 30
...
앞서 소개한 RunnableWithMessageHistory
위와 같은 대화 이력(Memory) 기능을 사용하는 경우, LangChain 자체에는 자동 세션 타임아웃 기능이 없는 경우가 많기 때문에, 스토리지 측(Redis 등)에서 TTL(Time To Live, 생존 기간)을 설정하여 판정 및 관리하는 것이 일반적입니다.
from langchain_community.chat_message_histories import RedisChatMessageHistory
# Redis 측에서 ttl=1800 (30분)을 설정
def get_redis_history(session_id: str):
...
Gemini(및 LLM 전반)를 사용하면서, "컨텍스트 오염 (Context Contamination)" 이나 "할루시네이션 (Hallucination)" 이 발생하고 있는지 확인 및 판정하려면, 크게 "육안/간이 판정" 과 "프로그램에 의한 자동 판정 (LLM-as-a-Judge)" 두 가지 접근 방식으로 확인하는 수법이 있습니다.
여기서는 각각의 체크 수법과 구현 접근 방식에 대해 해설하겠습니다.
체크를 수행하기 전에, 발생하고 있는 현상이 어느 쪽인지 정리하는 것이 중요합니다.
깔끔하게 정리 및 복구하였으니, 이것을 이용해 주세요!
| 현상 | 내용 | 주요 원인 |
|---|---|---|
| 컨텍스트 오염 (Context Contamination) | 과거의 대화나 무관한 컨텍스트 정보가 끌려와서, 지시와 관계없는 토픽이 답변에 섞임 | ・대화 이력 (History)의 삭제 누락 또는 너무 김 ・RAG에서 무관한 문서를 검색하여 혼입시킴 |
| 할루시네이션 (Hallucination) | 주어진 문장에 적혀 있지 않은 거짓 정보나 사실무근의 정보를 진지한 얼굴로 출력함 | ・지시 (Prompt)의 제약이 느슨함 ・모델의 지식으로 보충(망상)해 버림 |
"지금 준 명령문/컨텍스트 이외의 내용으로 답변하고 있지 않은가"를 조사하는 테스트용 프롬프트를 던지는 방법입니다.
검증 프롬프트 예시:
"다음 문장만을 근거로 질문에 답해 주세요. 문장 내에 기재되어 있지 않은 경우에는 반드시 '정보가 없습니다'라고 답해 주세요."
- 오염 있음/할루시네이션 동작: 문장 내에 없어야 할 일반적인 지식(예: "Python의 문법" 등)을 술술 대답해 버림.
- 정상적인 동작: 올바르게 "정보가 없습니다"라고 답변함.
Gemini에 웹 검색 도구 (Grounding)를 부여한 경우, 답변의 메타데이터 (grounding_metadata)를 직접 확인합니다.
# 응답의 메타데이터를 확인
metadata = response.response_metadata
if "grounding_metadata" in metadata:
...
groundingChunks(참조원)가 존재하지 않는데도 구체적인 수치나 최신 데이터를 답변하고 있다면 할루시네이션일 가능성이 높습니다.
RAG 시스템이나 LangChain을 도입한 경우, "LLM-as-a-Judge (별도의 LLM을 심사위원으로 사용)" 평가 프레임워크 (Ragas 또는 DeepEval)를 사용하는 것이 현재의 데파크토 스탠다드(De facto standard)입니다.
즉, 실제 질의를 담당하는 AI와는 별개로, 평가 전용 AI를 백그라운드에서 동작시키게 됩니다. 주로 다음의 두 가지 지표 (Metrics) 를 점수화하여 판정합니다.
-
Faithfulness (성실성 / Groundedness) ➔ 할루시네이션 판정
- 답변 내의 모든 주장 (Claim)을 추출하여, "전달된 컨텍스트 내에 사실로서 존재하는가"를 판정 (0.0~1.0).
-
Context Relevance (컨텍스트 관련성) ➔ 컨텍스트 오염 판정
- 검색/부여된 컨텍스트 안에 불필요하거나 무관한 노이즈 정보가 얼마나 섞여 있는지를 판정 (0.0~1.0).
Ragas 등의 외부 라이브러리를 사용하지 않고도, 전용 "판정용 체인"을 하나 구성하는 것만으로 간단히 체크하는 것이 가능합니다.
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Field
...
만약 체크를 통해 "컨텍스트 오염/할루시네이션"을 확인하거나 검지했을 경우에는,
- 컨텍스트 오염 (Context Contamination)에 대한 대책:
- LangChain의
InMemoryChatMessageHistory등을 사용하고 있는 경우, 과거 로그의 주고받은 내용을 최근 $N$건으로 제한한다 (TrimMessages등을 사용). - 시스템 프롬프트 (System Prompt)의 서두에
[System Alert: Ignore previous chat topic if the topic changes.]와 같은 경계선을 명시한다.
- LangChain의
- 할루시네이션 (Hallucination)에 대한 대책:
temperature파라미터를0.0~0.2로 낮춘다 (무작위성을 최대한 배제).- 프롬프트에 "답변의 근거가 된 문장을 반드시 인용하여 답변해 주세요"라는 제약 (Chain-of-Thought / Grounding)을 부여한다.
설계 단계에서 사양화할 수 있는 것이 베스트이지만, 어려운 경우에는 테스트를 반복하며 구체적인 대책안을 요구사항에 포함하는 것을 권장한다.
Gemini API는 "무료 티어 (Free Tier)"와 "종량제 (Pay-as-you-go)"의 두 가지 플랜이 있습니다.
각사 API의 이용 요금 과금 형태가 다르므로 확인해 주세요.
개발 및 테스트 용도로 제한된 범위 내에서 무료로 이용할 수 있습니다.
- 특징: 요금은 전혀 발생하지 않습니다.
- 제한: 분당 요청 수 (RPM)나 일일 요청 수 (RPD)에 제한이 있습니다.
- 주의사항: 입력 및 출력 데이터가 Google의 모델 개선을 위해 사용될 수 있습니다 (프라이버시 설정에 주의가 필요합니다).
본격적인 운영이나 대규모 이용을 위한 플랜으로, 사용한 만큼 지불하는 시스템입니다.
- 과금 단위: "토큰 (Token)" (문자나 단어의 최소 단위. 일본어는 1글자당 1~2토큰 정도)이라는 단위로 계산됩니다.
- 입력 (Input) 요금: 모델에 전송한 텍스트나 이미지 등의 양에 따른 요금.
- 출력 (Output) 요금: 모델이 생성/답변한 텍스트의 양에 따른 요금 (※ 출력이 단가가 더 높게 설정되어 있습니다).
- 프라이버시: 전송된 데이터가 모델 학습에 사용되지 않습니다.
모델의 성능이나 컨텍스트 윈도우 (Context Window, 한 번에 처리할 수 있는 길이)에 따라 요금이 달라집니다.
| 모델 | 역할·특징 | 입력 요금 (目安) | 출력 요금 (目安) |
|---|---|---|---|
| Gemini 1.5 Flash | 경량·고속·저비용 (Web 앱이나 일상적인 자동화용) | 약 $0.075 / 100만 토큰 | 약 $0.30 / 100만 토큰 |
| Gemini 1.5 Pro | 고정밀·복잡한 사고·장문 처리용 | 약 $1.25~2.50 / 100만 토큰 | 약 $5.00~10.00 / 100만 토큰 |
※ 컨텍스트 길이를 초과하는 경우나 캐시 기능 (Context Caching)을 이용하는 경우에는 단가가 달라집니다.
포인트:
개인 개발이나 테스트 단계라면, 우선 Google AI Studio에서 "Free Tier" 키를 발급받아 이용하는 것을 추천합니다. 제한을 초과하더라도 자동으로 과금되는 것이 아니라 에러 (429 Too Many Requests)가 발생할 뿐이므로 안심하고 테스트할 수 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기