Memory 활용, Tool 설계, LCEL 체인 아키텍처
‘LangChain 시리즈’ 세 번째 글입니다. 본문은 LangChain 0.3.x를 기준으로 Memory(기억 시스템), Tools(외부 기능 노출), LCEL(파이프라인 DSL) 세 축을 깊이 있게 다룹니다. 간단한 예제뿐 아니라 설계·성능·보안·운영 관점까지 포함했으며, 모든 코드는 Python 3.11 + LangChain 0.3.1 환경에서 확인했습니다.
1. Memory – LLM에 장기 기억력 부여하기
1‑1. Memory가 필요한 이유
LLM API는 본질적으로 **무상태(stateless)**입니다. 프롬프트에 과거 문맥을 포함하지 않으면 모델은 대화를 잊어버립니다. 실서비스에서는 다음과 같은 이유로 Memory가 필요합니다.
- 대화형 서비스 – 이전 대화를 토큰 한도 내에서 유지해야 함
- 멀티스텝 워크플로 – 중간 산출물·사용자 설정을 보존해야 함
- 개인화 – 사용자 프로필·선호도 등의 장기 기억 필요
LangChain Memory는 저장소 + _프롬프트 변환기_라는 두 가지 기능을 제공합니다.
1‑2. 메모리 계층 구조
Memory
├─ Short‑Term (Thread)
│ ├─ Buffer / Summary / TokenWindow
│ └─ Hybrid(Buffer+Summary)
└─ Long‑Term (Global)
├─ VectorStoreRetrieverMemory
├─ SQLStorageMemory
└─ Custom Backend (Redis, Mongo, S3)
- Short‑Term : 한 세션 동안의 문맥을 관리해 프롬프트에 삽입합니다.
- Long‑Term : 여러 세션에 걸친 정보를 검색 가능한 지식으로 저장합니다.
1‑3. 메모리 타입 비교
분류 | 클래스 | 주요 파라미터 | 장점 | 한계 |
---|---|---|---|---|
Buffer | ConversationBufferMemory |
– | 구현 간단, 디버깅 용이 | 토큰 초과·비용 증가 |
Summary | ConversationSummaryMemory |
max_token_limit |
길이 제어, 비용↓ | 요약 손실 가능성 |
BufferWindow | ConversationBufferWindowMemory |
k (최근 메시지 수) |
최근 맥락 유지 | 오래된 정보 손실 |
TokenWindow | ConversationTokenBufferMemory |
max_token_limit |
토큰 기준 관리 | 슬라이딩 비용 |
VectorStore | VectorStoreRetrieverMemory |
k , distance_metric |
장기 기억 · 빠른 검색 | 인덱싱 비용 |
SQLStorage | SQLChatMessageHistory |
DB URI | 트랜잭션·백업 쉬움 | 검색 로직 별도 필요 |
현장 팁 : 대화 길이 < 100 → Buffer, 100‑500 → BufferWindow, 500+ → Summary + VectorStore 조합이 실무에서 안정적입니다.
1‑4. Summary Memory 사용 예제
from langchain.memory import ConversationSummaryMemory
from langchain_openai import ChatOpenAI
from langchain.prompts import PromptTemplate
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
summary_prompt = PromptTemplate(
"""다음 대화 내용을 세 문장 이하로 요약해 주세요.\n===\n{summary}\n==="""
)
mem = ConversationSummaryMemory(
llm=llm,
max_token_limit=512,
summary_prompt=summary_prompt,
)
- max_token_limit를 넘으면 LangChain이 자동으로 요약 프롬프트를 호출해 과거 기록을 요약으로 대체합니다.
1‑5. Vector Memory 고급 기능
- 메타데이터 필터 – 작성자·태그 기반 검색으로 Recall ↑
- 온디스크 인덱스 – FAISS
save_local()
·load_local()
로 빠른 재시작 - Streaming Upsert – 실시간 로그를 받아 지속적으로 벡터 DB 갱신
1‑6. Memory 패턴 요약
패턴 | 설명 | 적용 사례 |
---|---|---|
Siamese | 세션마다 Short‑Term + 공용 Global | 채팅형 SaaS |
Episodic | 대화→요약→Vector 저장 | 개인 비서 |
Time‑Aware | 오래된 기록 가중치 감소 | 뉴스 요약 봇 |
Role‑Based | 역할별 Memory Pool 구분 | 멀티 테넌트 플랫폼 |
2. Tools – 모델에게 ‘행동’과 ‘감각’ 심어 주기
2‑1. @tool
데코레이터 작동 방식
@tool
은 함수를 래핑해 타입 스키마(Pydantic), 실행 래퍼, JSON Schema 세 가지를 자동 생성합니다. 결과적으로 LLM은 OpenAI Function Calling 포맷으로 함수를 사용할 수 있습니다.
2‑2. 입력 스키마 커스터마이징 예제
from langchain_core.tools import tool
@tool(spec={
"title": "Wiki Search",
"description": "키워드로 위키피디아 요약 검색",
"parameters": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"]
}
})
def wiki_search(query: str) -> str:
...
2‑3. Artifact 패턴 – 대용량 파일 처리
from langchain_core.tools import Artifact
@tool
def export_csv(n: int) -> Artifact:
"""n행 CSV 파일을 만들어 Artifact로 반환합니다."""
path = f"/tmp/data_{n}.csv"
open(path, "w").write("...csv...")
return Artifact(path)
- LLM은 요약 정보만 받고 실제 파일은 서버에 저장됩니다.
2‑4. ToolGroup β – 도구 카테고리 관리
from langchain_core.tools import ToolGroup
search_tools = ToolGroup(
name="Search",
description="웹·위키 검색",
tools=[wiki_search, news_search],
)
- 프롬프트에 그룹 정보가 네임스페이스처럼 표시되어 LLM이 관련 도구를 손쉽게 선택합니다.
2‑5. Agent 통합 예제
from langchain.agents import create_react_agent
a = create_react_agent(
llm,
tools=[multiply, search_tools],
system_message="""당신은 연구 보조원입니다. 자료 조사나 계산이 필요하면 적절한 함수를 사용하세요.""",
)
print(a.invoke("파이썬 출시 연도를 찾아 제곱해 줘"))
루프 제어 :
max_iterations
또는max_execution_time
옵션으로 무한 루프를 예방하세요.
3. LCEL – 데이터 흐름을 선언적으로 구성하기
3‑1. 설계 철학
- Composable – 작은 Runnable을 합성해 복잡도를 제어합니다.
- Transparent –
invoke
·stream
·batch
API가 통일돼 있습니다. - Traceable – 체인 단계별 태그를 LangSmith에 기록할 수 있습니다.
3‑2. 주요 연산자·헬퍼
문법/함수 | 설명 | 예시 |
---|---|---|
.map() |
리스트 입력 → 각 요소에 적용 | Embedding 배치 |
.filter() |
조건 함수로 필터 | score > 0.7 |
.retry() |
실패 시 재시도 정책 | 지수 백오프 |
.tee() |
입력을 N 분기 | 다중 체인 |
.with_fallbacks() |
실패 시 예비 체인 호출 | 모델 다운시 대체 |
.bind() |
매개변수 고정 | llm.bind(temperature=0) |
3‑3. Map‑Reduce 요약 체인 예제
from langchain_core.runnables import RunnableMap, RunnableReduce
doc_chunks = [...] # 논문 분절 리스트
map_step = ChatOpenAI().bind(prompt="문단 요약:\n{input}")
reduce_step = ChatOpenAI().bind(prompt="요약 리스트를 200자로 종합:\n{input}")
summary_chain = RunnableMap(map_step) | RunnableReduce(
reduce_step, aggregation=lambda xs: "\n".join(xs)
)
print(summary_chain.invoke(doc_chunks))
- 50k 토큰 논문이 2k 토큰 요약으로 축약됩니다.
3‑4. 클래스로부터 LCEL로 마이그레이션
LCEL 파이프라인은 가독성과 커스터마이징 측면에서 *Chain
클래스를 대체합니다. 유지보수 시 API 변화에 덜 민감해집니다.
4. 종합 데모 – Personal GPT Helper v2
from langchain_openai import ChatOpenAI
from langchain.memory import ConversationSummaryMemory
from langchain_core.prompts import ChatPromptTemplate
from langchain.evaluation import Criteria, evaluate
from langsmith import Client
# 1) LLM
llm = ChatOpenAI(model="gpt-4.1-mini", temperature=0)
# 2) Memory
mem = ConversationSummaryMemory(llm=llm, max_token_limit=800)
# 3) Tool – today
@tool
def today(_: str) -> str:
"""현재 날짜를 YYYY-MM-DD 형식으로 반환합니다."""
from datetime import date; return date.today().isoformat()
# 4) Assistant 체인 (LCEL)
prompt = ChatPromptTemplate(
"""당신은 개인 비서입니다.
[대화 기록]
{history}
[사용자]
{input}
"""
)
assistant_core = prompt | llm | StrOutputParser()
assistant = create_openai_functions_agent(llm, [today], assistant_core)
# 5) 대화 + 평가 루프
client = Client()
def chat(q: str):
history = mem.load_memory_variables({})["history"]
answer = assistant.invoke({"history": history, "input": q})
mem.save_context({"input": q}, {"response": answer})
score = evaluate(answer, criteria=[Criteria.CONCISENESS, Criteria.RELEVANCE])
client.log_metric("assistant_eval", score.overall_score)
return answer
- 자동 평가 : LangSmith Eval로 응답 품질을 체크해 CI에서 실패 조건을 트리거할 수 있습니다.
5. 베스트 프랙티스 & 안티패턴
권장 | 지양 |
---|---|
요약 프롬프트에 도메인 키워드 포함 | “Summarize the conversation” 같은 추상 프롬프트 |
툴 타입 힌트·Docstring 명확 | 반환형 없음 → JSON 파싱 실패 |
체인 단계별 .with_config() 태그 삽입 |
Trace 태그 미사용 → 디버깅 어려움 |
max_iterations 제한 |
무한 루프 → 비용 폭증 |
토큰 사용량 모니터링·알람 | 비용 추적 안 함 → 예산 초과 |
6. 성능·비용 최적화 가이드
- 배치 임베딩 – 배치 크기 96이 OpenAI TPS 대비 안정적입니다.
- Chunk Size – RAG에서 300‑600토큰이 Precision/Recall 밸런스 최적입니다.
- 툴 캐시 – 동일 입력은 Redis로 5분 캐싱해 호출 비용을 줄입니다.
- Async Streaming –
llm.stream()
으로 사용자 체감 지연 50% 감소. - Prompt Compression – 유사 문맥 압축으로 토큰 30‑40% 절감.
7. 보안·개인정보 보호 체크리스트
- PII 마스킹 – LLM에 전달하기 전 정규식으로 민감 정보 제거.
- 함수 화이트리스트 – 모델에 노출되는 툴을 최소화.
- 로그 암호화 – LangSmith 키를 Vault Secret으로 분리.
- Rate Limit – 서드파티 API 429 오류 대비 Backoff 정책 설정.
- 취약점 스캔 – Docker 이미지를 Trivy로 정기 검사.
'인공지능 (AI) > LangChain&LangGraph' 카테고리의 다른 글
6 – 챗봇 배포와 최적화 (2) | 2025.06.18 |
---|---|
5 – 멀티 에이전트 챗봇 예제 프로젝트 (1) | 2025.06.18 |
4 – LangGraph로 멀티 에이전트 구성하기 (1) | 2025.06.18 |
2 – LangGraph 소개 (0) | 2025.06.18 |
1 – LangChain 개요와 아키텍처 (0) | 2025.06.18 |