LangChain 0.3+ ์ต์ ๊ธฐ์ค: LCEL(LangChain Expression Language) ํ์ดํ ์ฐ์ฐ์, ChatModel(OpenAI/Claude/Ollama), ๊ตฌ์กฐํ๋ ์ถ๋ ฅ(Pydantic), RAG ํ์ดํ๋ผ์ธ(Chroma/FAISS), Tool Calling ๋ฐ LangGraph ReAct ์์ด์ ํธ ์ค์ ์๋ฒฝ ๊ฐ์ด๋
## 1. ํ์ ์ฌ์ ์ค์น ์๊ฑด & ๊ฐ๋ฐ ํ๊ฒฝ ๊ตฌ์ฑ (Prerequisites)
LangChain ๋ฐ ๋ฒกํฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค(Chroma, FAISS), ๊ณ ์ฑ๋ฅ ํ ํฌ๋์ด์ (`tiktoken`), Pydantic C-Extension์ ๋น๋ ์ค๋ฅ ์์ด ๊ตฌ๋ํ๊ธฐ ์ํด ์ฌ์ ์ ์ค๋นํด์ผ ํ๋ ์์คํ
ํ์ ์๊ตฌ์ฌํญ์
๋๋ค.
### ๐ 1. Python ๋ฒ์ & ๊ฐ์ํ๊ฒฝ (Virtual Environment)
LangChain 0.3+์ **Python 3.9 ~ 3.12+ (๊ถ์ฅ: Python 3.11 ๋๋ 3.12)** ํ๊ฒฝ์ ์ง์ํฉ๋๋ค. ์ ์ญ ํจํค์ง ์ค์ผ ๋ฐ ๋ฒ์ ์ถฉ๋์ ๋ฐฉ์งํ๊ธฐ ์ํด ๊ฐ์ํ๊ฒฝ ์์ฑ์ด **ํ์**์
๋๋ค.
```bash
# 1. ํ์ค venv ๊ฐ์ํ๊ฒฝ ์์ฑ
python -m venv .venv
# 2. ๊ฐ์ํ๊ฒฝ ํ์ฑํ (Windows PowerShell)
# โป ์ต์ด 1ํ ์คํฌ๋ฆฝํธ ์คํ ๊ถํ ํ์: Set-ExecutionPolicy Unrestricted -Scope Process
.venv\Scripts\Activate.ps1
# ๊ฐ์ํ๊ฒฝ ํ์ฑํ (macOS / Linux / WSL)
source .venv/bin/activate
# 3. ๋๋ ์ฐจ์ธ๋ ์ด๊ณ ์ ํจํค์ง ๊ด๋ฆฌ์ uv ์ฌ์ฉ ์ (๊ฐ๋ ฅ ์ถ์ฒ)
pip install uv
uv venv
# Windows: .venv\Scripts\activate / macOS: source .venv/bin/activate
uv pip install langchain langchain-core langchain-openai
```
### ๐ ๏ธ 2. OS๋ณ C/C++ ๋น๋ ๋๊ตฌ (Native Compiler)
`chromadb`(HNSWlib C++ ์ฝ์ด), `faiss-cpu`, `tiktoken` ๋ฑ C++/Rust ๋ฐ์ธ๋ฉ์ด ํฌํจ๋ ๋ค์ดํฐ๋ธ ํจํค์ง ์ค์น ์ ๋ฐ์ํ ์ ์๋ ๋น๋ ์๋ฌ๋ฅผ ์ฌ์ ์ ๋ฐฉ์งํ๊ธฐ ์ํ ๋๊ตฌ์
๋๋ค.
| ์ด์์ฒด์ (OS) | ํ์ ์ค์น ๋๊ตฌ | ์ค์น ๋ฐฉ๋ฒ |
| :--- | :--- | :--- |
| **Windows** | Microsoft C++ Build Tools | [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) ์ค์น ์ **"C++๋ฅผ ์ฌ์ฉํ ๋ฐ์คํฌํฑ ๊ฐ๋ฐ"** ์ํฌ๋ก๋ ์ฒดํฌ |
| **macOS** | Xcode Command Line Tools | ํฐ๋ฏธ๋์์ `xcode-select --install` ์คํ |
| **Linux (Ubuntu/Debian)** | `build-essential` & `python3-dev` | `sudo apt update && sudo apt install -y build-essential python3-dev` |
### ๐ฆ 3. ๋ก์ปฌ ๋ฌด๋ฃ ์คํ๋ผ์ธ LLM ์์ง: Ollama (์ ํ ์ฌํญ)
OpenAI๋ Claude ์ ๋ฃ API ํค ์์ด ๋ด PC(๋ก์ปฌ GPU/CPU)์์ ์์ ๋ฌด๋ฃ ๋ฐ ์คํ๋ผ์ธ์ผ๋ก LLM๊ณผ ์๋ฒ ๋ฉ ๋ชจ๋ธ์ ์คํํ๋ ค๋ฉด **Ollama**๋ฅผ ์ฌ์ ์ ์ค์นํฉ๋๋ค.
- **๊ณต์ ๋ค์ด๋ก๋**: [https://ollama.com](https://ollama.com) (Windows / macOS / Linux ์ง์)
- **๋ก์ปฌ ๋ชจ๋ธ ๋ค์ด๋ก๋ ๋ฐ ๋ฐฑ๊ทธ๋ผ์ด๋ ์๋น์ค ๊ธฐ๋**:
```bash
# Llama 3.2 3B ๊ณ ์ ๋ชจ๋ธ ๋ค์ด๋ก๋ & ์คํ ํ
์คํธ
ollama run llama3.2
# DeepSeek ์ต์ ์ถ๋ก ๋ชจ๋ธ
ollama run deepseek-r1:8b
# RAG ๊ฒ์์ฉ ๋ฌด๋ฃ ๊ณ ์ฑ๋ฅ ์๋ฒ ๋ฉ ๋ชจ๋ธ
ollama pull nomic-embed-text
```
---
## 2. LangChain 0.3+ ์ํคํ
์ฒ & ํจํค์ง ์ค์น
์ต์ **LangChain 0.3+**์ ๋ชจ๋๋ฆฌ์ ๊ตฌ์กฐ์์ ํํผํ์ฌ ๊ฒฝ๋ ์ฝ์ด(`langchain-core`), ๊ณต๊ธ์๋ณ ํจํค์ง(`langchain-openai`, `langchain-anthropic`), ์ปค๋ฎค๋ํฐ ํ๋ฌ๊ทธ์ธ(`langchain-community`), ๊ทธ๋ฆฌ๊ณ ์์ด์ ํธ ์ค์ผ์คํธ๋ ์ด์
(`langgraph`)์ผ๋ก ๋ชจ๋ํ๋์์ต๋๋ค.
```text
[LangChain 0.3+ ์ํ๊ณ ๊ตฌ์กฐ]
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ LangGraph (์ํ ๊ธฐ๋ฐ ๋ฉํฐ ์์ด์ ํธ, ๋ฃจํ ๋ฐ ๋ถ๊ธฐ ์ ์ด) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ LangChain (๊ณ ์์ค ์ฒด์ธ, ์์ด์ ํธ, RAG ์ค์ผ์คํธ๋ ์ด์
) โ
โโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ langchain-community โ ๊ณต๊ธ์ ํจํค์ง (langchain-openai, โ
โ (์๋ํํฐ ๋๊ตฌ, ์ ์ฅ์) โ langchain-anthropic, ollama ๋ฑ)โ
โโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ langchain-core (LCEL, Runnable ์ธํฐํ์ด์ค, ๋ฉ์์ง, ์คํค๋ง) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
```
### ๐ฆ ์์กด์ฑ ํจํค์ง ์ผ๊ด ์ค์น (pip & uv)
```bash
# 1. ํต์ฌ ํ๋ ์์ํฌ & ์ฃผ์ ๊ณต๊ธ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
pip install langchain langchain-core langchain-community
# 2. ์ ํธํ๋ LLM ๊ณต๊ธ์ ํจํค์ง ์ ํ ์ค์น
pip install langchain-openai # OpenAI (GPT-4o, o1, Text Embedding 3)
pip install langchain-anthropic # Anthropic (Claude 3.5 Sonnet)
pip install langchain-ollama # Ollama (๋ก์ปฌ ๋ฌด๋ฃ ์คํ๋ผ์ธ ๋ชจ๋ธ ์ฐ๋)
# 3. ํ ํฌ๋์ด์ & ํ๊ฒฝ๋ณ์ ๊ด๋ฆฌ & ๋ฐ์ดํฐ ๊ฒ์ฆ
pip install tiktoken python-dotenv pydantic
# 4. RAG ๋ฒกํฐ ์ ์ฅ์ & ๋ฌธ์ ๋ก๋
pip install chromadb faiss-cpu pypdf beautifulsoup4
# 5. ์ฐจ์ธ๋ ReAct ์์ด์ ํธ ํ๋ ์์ํฌ
pip install langgraph
```
### ๐ ํ๊ฒฝ ๋ณ์ ์ค์ (`.env`)
ํ๋ก์ ํธ ๋ฃจํธ ๊ฒฝ๋ก์ `.env` ํ์ผ์ ์์ฑํ๊ณ ๋ฐ๊ธ๋ฐ์ API ํค๋ฅผ ์ง์ ํฉ๋๋ค:
```bash
# OpenAI API ํค
OPENAI_API_KEY="sk-proj-xxxxxxxxxxxxxxxxxxxx"
# Anthropic Claude API ํค (์ ํ)
ANTHROPIC_API_KEY="sk-ant-xxxxxxxxxxxxxxxxxxxx"
# LangSmith ํธ๋ ์ด์ฑ & ๋ชจ๋ํฐ๋ง (์ ํ ์ฌํญ - ๋ฌด๋ฃ ๊ฐ์
๊ฐ๋ฅ)
LANGCHAIN_TRACING_V2="true"
LANGCHAIN_API_KEY="lsv2_pt_xxxxxxxxxxxxxxxxxxxx"
LANGCHAIN_PROJECT="lucky-langchain-prod"
```
ํ์ด์ฌ ์ฝ๋ ์ฒซ ์ค์์ ํ๊ฒฝ๋ณ์๋ฅผ ๋ก๋ํฉ๋๋ค:
```python
from dotenv import load_dotenv
load_dotenv() # .env ํ์ผ ๋ก๋
```
---
## 3. LCEL (LangChain Expression Language) ๊ธฐ์ด
LCEL์ ๋ชจ๋ LangChain ์ปดํฌ๋ํธ๋ฅผ ์ ๋์ค ํ์ดํ๋ผ์ธ(`|`) ์คํ์ผ๋ก ์ฐ๊ฒฐํ๋ ์ ์ธ์ ํ์ค ๋ฌธ๋ฒ์
๋๋ค. ์คํธ๋ฆฌ๋ฐ, ๋น๋๊ธฐ ํธ์ถ, ๋ณ๋ ฌ ์คํ์ด ๊ธฐ๋ณธ ๋ด์ฅ๋์ด ์์ต๋๋ค.
### ๐งฉ ๊ธฐ๋ณธ 3๋จ๊ณ ํ์ดํ๋ผ์ธ: `Prompt | Model | OutputParser`
```python
import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# 1. Chat ๋ชจ๋ธ ์ด๊ธฐํ
llm = ChatOpenAI(model="gpt-4o", temperature=0.7)
# 2. ํ๋กฌํํธ ํ
ํ๋ฆฟ ์ ์
prompt = ChatPromptTemplate.from_messages([
("system", "๋น์ ์ IT ๊ธฐ์ ์ ๋ฌธ ์๋์ด ์ํคํ
ํธ์
๋๋ค. ๊ฐ๊ฒฐํ๊ณ ๋ช
ํํ๊ฒ ๋ต๋ณํ์ธ์."),
("human", "{topic} ๊ธฐ์ ์ ํต์ฌ ์ฅ์ 3๊ฐ์ง๋ฅผ ๋ถ๋ฆฟ ํฌ์ธํธ๋ก ์ค๋ช
ํด์ค.")
])
# 3. ๋ฌธ์์ด ์ถ๋ ฅ ํ์
output_parser = StrOutputParser()
# 4. LCEL ์ฒด์ธ ์กฐํฉ (Prompt -> Model -> Parser)
chain = prompt | llm | output_parser
# 5. ์คํ (๋๊ธฐ ํธ์ถ)
response = chain.invoke({"topic": "Docker ์ปจํ
์ด๋"})
print(response)
```
### โก Runnable ํต์ฌ ๋ฉ์๋ ์นํธ์ํธ
| ๋ฉ์๋ | ์คํ ๋ฐฉ์ | ์ค๋ช
|
| :--- | :--- | :--- |
| `chain.invoke(input)` | ๋๊ธฐ (Sync) | ๋จ์ผ ์
๋ ฅ์ ์ ๋ฌํ๊ณ ์ต์ข
๊ฒฐ๊ณผ๋ฅผ ๋๊ธฐ ๋ฐํ |
| `await chain.ainvoke(input)` | ๋น๋๊ธฐ (Async) | `asyncio` ํ๊ฒฝ์์ ๋น๋๊ธฐ non-blocking ์คํ |
| `chain.stream(input)` | ์คํธ๋ฆฌ๋ฐ (Generator) | LLM ์์ฑ ํ ํฐ์ ์ฒญํฌ ๋จ์๋ก ์ค์๊ฐ yield |
| `chain.batch([in1, in2])` | ๋ณ๋ ฌ ๋ฐฐ์น | ์ฌ๋ฌ ์
๋ ฅ์ ๋ด๋ถ ์ค๋ ๋ํ๋ก ๋ณ๋ ฌ ์ผ๊ด ์ฒ๋ฆฌ |
---
## 4. ChatModel & ๋ค์ํ ๊ณต๊ธ์ ์ฐ๋
LangChain์ ๋์ผํ ์ธํฐํ์ด์ค(`BaseChatModel`)๋ก ์์ฉ ํด๋ผ์ฐ๋ LLM๊ณผ ๋ก์ปฌ ์จํ๋ ๋ฏธ์ค LLM์ ์์ฝ๊ฒ ๊ต์ฒดํ ์ ์์ต๋๋ค.
### ๐ OpenAI, Claude, ๋ก์ปฌ Ollama ๋ชจ๋ธ ํธ์ถ
```python
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
# 1. OpenAI (GPT-4o)
from langchain_openai import ChatOpenAI
gpt = ChatOpenAI(model="gpt-4o", temperature=0.2)
# 2. Anthropic (Claude 3.5 Sonnet)
from langchain_anthropic import ChatAnthropic
claude = ChatAnthropic(model="claude-3-5-sonnet-20241022", temperature=0.2)
# 3. Ollama (๋ก์ปฌ ๋ฌด๋ฃ LLM - ์คํ๋ผ์ธ ๊ตฌ๋ ๊ฐ๋ฅ)
from langchain_ollama import ChatOllama
ollama_llama = ChatOllama(model="llama3.2:latest", base_url="http://localhost:11434")
# ๊ณตํต ๋ฉ์์ง ๋ฆฌ์คํธ ์ ๋ฌ
messages = [
SystemMessage(content="ํ๊ตญ์ด๋ก ์น์ ํ๊ฒ ๋ต๋ณํ๋ AI ์กฐ์์
๋๋ค."),
HumanMessage(content="REST API์ GraphQL์ ๊ฐ์ฅ ํฐ ์ฐจ์ด์ ์ ๋ญ์ผ?")
]
ai_response = gpt.invoke(messages)
print("์๋ต ๋ด์ฉ:", ai_response.content)
print("ํ ํฐ ์๋น๋:", ai_response.response_metadata.get("token_usage"))
```
---
## 5. ํ๋กฌํํธ ํ
ํ๋ฆฟ & ๋์ ๋ฉ์์ง ์ฃผ์
### ๐ฌ `ChatPromptTemplate` & ๋ํ ํ์คํ ๋ฆฌ ์ฌ๋กฏ (`MessagesPlaceholder`)
```python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import HumanMessage, AIMessage
chat_prompt = ChatPromptTemplate.from_messages([
("system", "๋น์ ์ ์น์ ํ ๊ณ ๊ฐ ์ง์ ์๋ด์์
๋๋ค."),
# ์ด์ ๋ํ ๊ธฐ๋ก์ด ๋์ ์ผ๋ก ๋ค์ด์ฌ ์๋ฆฌ
MessagesPlaceholder(variable_name="chat_history"),
("human", "{user_input}")
])
# ๋ํ ์ด๋ ฅ ์๋ฎฌ๋ ์ด์
history = [
HumanMessage(content="์๋
ํ์ธ์, ์ํ ํ๋ถ ๊ท์ ์ด ์ด๋ป๊ฒ ๋๋์?"),
AIMessage(content="๊ตฌ๋งค ํ 7์ผ ์ด๋ด ์ํ ํผ์์ด ์๋ ๊ฒฝ์ฐ ์ ์ก ํ๋ถ ๊ฐ๋ฅํฉ๋๋ค.")
]
# ํฌ๋งทํ
ํ์ธ
formatted_messages = chat_prompt.format_messages(
chat_history=history,
user_input="๋ฐฐ์ก๋น๋ ์ ๊ฐ ๋ถ๋ดํด์ผ ํ๋์?"
)
```
---
## 6. ๊ตฌ์กฐํ๋ ์ถ๋ ฅ (Structured Output with Pydantic)
์ต์ LLM ์ ํ๋ฆฌ์ผ์ด์
์์๋ ์ ๊ท์ ๋์ **Pydantic ์คํค๋ง**๋ฅผ ๋ชจ๋ธ์ ์ง์ ๋ฐ์ธ๋ฉํ์ฌ 100% ๋ณด์ฅ๋ JSON ๊ฐ์ฒด๋ก ์๋ต์ ํ์ฑํฉ๋๋ค.
### ๐ฏ `with_structured_output` (๊ณต์ ๊ถ์ฅ ์ต์ ๋ฐฉ์)
```python
from pydantic import BaseModel, Field
from typing import List
from langchain_openai import ChatOpenAI
# 1. ๊ธฐ๋ํ๋ ์ถ๋ ฅ ๋ฐ์ดํฐ ๋ชจ๋ธ ์ ์
class TechAnalysis(BaseModel):
tech_name: str = Field(description="๊ธฐ์ ์ด๋ฆ")
difficulty_level: str = Field(description="ํ์ต ๋์ด๋ (์ด๊ธ/์ค๊ธ/๊ณ ๊ธ)")
core_features: List[str] = Field(description="ํต์ฌ ๊ธฐ๋ฅ 3๊ฐ์ง ๋ชฉ๋ก")
is_recommended_for_beginners: bool = Field(description="์ด๋ณด์ ์ถ์ฒ ์ฌ๋ถ")
# 2. ๋ชจ๋ธ์ Pydantic ์คํค๋ง ์ง์ ๋ฐ์ธ๋ฉ
llm = ChatOpenAI(model="gpt-4o", temperature=0)
structured_llm = llm.with_structured_output(TechAnalysis)
# 3. ํธ์ถ ์ Pydantic ๊ฐ์ฒด๊ฐ ์ฆ์ ๋ฐํ๋จ
result: TechAnalysis = structured_llm.invoke("Kubernetes์ ๋ํด ๊ธฐ์ ๋ถ์์ ์์ฑํด์ค.")
print(type(result)) # <class '__main__.TechAnalysis'>
print(f"๊ธฐ์ : {result.tech_name}")
print(f"๋์ด๋: {result.difficulty_level}")
print(f"์ด๋ณด์ ์ถ์ฒ: {result.is_recommended_for_beginners}")
for feat in result.core_features:
print(f" - {feat}")
```
---
## 7. RAG (๊ฒ์ ์ฆ๊ฐ ์์ฑ) ์์ ์ ๋ณต ํ์ดํ๋ผ์ธ
์ธ๋ถ ๋ฌธ์(PDF, ํ
์คํธ, ์นํ์ด์ง)๋ฅผ ์ฒญํนํ์ฌ ๋ฒกํฐ DB์ ์ ์ฅํ๊ณ , ์ฌ์ฉ์์ ์ง๋ฌธ๊ณผ ๊ฐ์ฅ ์ ์ฌํ ์ฒญํฌ๋ฅผ ๊ฒ์ํ์ฌ ๋ต๋ณ์ ์์ฑํ๋ ์์ฑํ RAG ํจํด์
๋๋ค.
```text
[RAG ํ์ดํ๋ผ์ธ ํ๋ฆ๋]
[Raw Documents]
โ (TextLoader / PDFLoader)
โผ
[Text Chunks] โโโโโบ [Embedding Model] โโโโโบ [VectorStore (Chroma)]
โ
[User Query] โโโโโโบ [Similarity Search] โโโโโโโโโโโโ
โ (Top-K Chunks)
โผ
[Context + Prompt] โโโโโบ [LLM] โโโโโบ [์ต์ข
๋ต๋ณ]
```
### ๐ ๏ธ ์๋ํฌ์๋ RAG ์์ (In-Memory Chroma + LCEL)
```python
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
# 1. ์ฌ๋ด ๋ฌธ์ ์ํ ์ค๋น (์ค๋ฌด์์๋ PyPDFLoader, TextLoader ์ฌ์ฉ)
raw_docs = [
Document(
page_content="LuckyTech ์ฌ๋ด ๋ณต์ง ๊ท์ : ๋ชจ๋ ์์ง์์ ์ฐ๊ฐ 200๋ง ์์ ๋ณต์ง ํฌ์ธํธ๋ฅผ ์ง์๋ฐ์ผ๋ฉฐ, ๋์ ๊ตฌ๋งค ๋ฐ ์ฒด๋ ฅ ๋จ๋ จ๋น๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ฌํ๊ทผ๋ฌด๋ ์ฃผ 2ํ ์์จ ์ ์ฒญ ๊ฐ๋ฅํฉ๋๋ค.",
metadata={"source": "hr_policy_2026.txt"}
),
Document(
page_content="๋ณด์ ์ง์นจ: ํ์ฌ์ ๋ชจ๋ ์์ค์ฝ๋๋ ๊ฐ์ธ ๋ก์ปฌ ๋๋ผ์ด๋ธ์ ๋ณด๊ดํ ์ ์์ผ๋ฉฐ, ๋ฐ๋์ 2FA ์ธ์ฆ์ด ํ์ฑํ๋ ์ฌ๋ด GitLab ์ํฐํ๋ผ์ด์ฆ ์ ์ฅ์๋ฅผ ๊ฒฝ์ ํด์ผ ํฉ๋๋ค.",
metadata={"source": "security_policy_2026.txt"}
)
]
# 2. ์ฒญํน (Text Splitting)
text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=30)
splits = text_splitter.split_documents(raw_docs)
# 3. ์๋ฒ ๋ฉ ๋ชจ๋ธ & ๋ฒกํฐ ์คํ ์ด ์์ฑ
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(documents=splits, embedding=embeddings)
# 4. ๊ฒ์๊ธฐ(Retriever) ์์ฑ (์์ ์ ์ฌ ์ฒญํฌ 2๊ฐ ๊ฒ์)
retriever = vectorstore.as_retriever(search_kwargs={"k": 2})
# 5. ๊ฒ์๋ ๋ฌธ์ ๋ฆฌ์คํธ๋ฅผ ๋จ์ผ ๋ฌธ์์ด๋ก ํฌ๋งทํ
ํ๋ ํฌํผ ํจ์
def format_docs(docs):
return "\n\n".join(f"[{doc.metadata.get('source')}]: {doc.page_content}" for doc in docs)
# 6. RAG ํ๋กฌํํธ ์ ์
rag_prompt = ChatPromptTemplate.from_template("""
๋น์ ์ ์ฌ๋ด ์ง์ ๊ธฐ๋ฐ Q&A ๋์ฐ๋ฏธ์
๋๋ค. ์๋ ์ ๊ณต๋ [์ฐธ๊ณ ๋ฌธ์] ๋ด์ฉ๋ง์ ๋ฐํ์ผ๋ก ๋ต๋ณํ์ธ์.
๋ง์ฝ ๋ฌธ์์ ์๋ ๋ด์ฉ์ด๋ผ๋ฉด "์ ๊ณต๋ ๋ฌธ์์์ ๊ด๋ จ ์ ๋ณด๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."๋ผ๊ณ ์ ์งํ๊ฒ ๋ตํ์ธ์.
[์ฐธ๊ณ ๋ฌธ์]
{context}
[์ง๋ฌธ]
{question}
""")
llm = ChatOpenAI(model="gpt-4o", temperature=0)
# 7. LCEL RAG ์ฒด์ธ ๊ตฌ์ถ
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| rag_prompt
| llm
| StrOutputParser()
)
# 8. ์ง๋ฌธ ์คํ
answer = rag_chain.invoke("๋ณต์ง ํฌ์ธํธ ๊ธ์ก์ด๋ ์ฌํ๊ทผ๋ฌด ๊ท์ ์ด ์ด๋ป๊ฒ ๋ผ?")
print("๋ต๋ณ:\n", answer)
```
---
## 8. Tool ์ ์ (`@tool`) & Tool Calling (๋๊ตฌ ํธ์ถ)
LLM์ด ์ค์๊ฐ ๊ณ์ฐ๊ธฐ, ๋ ์จ API, ๋ฐ์ดํฐ๋ฒ ์ด์ค ์กฐํ ๋ฑ ์ธ๋ถ ์์คํ
์ ์ง์ ํธ์ถํ๋๋ก ๋ง๋๋ ํต์ฌ ๊ธฐ๋ฅ์
๋๋ค.
### ๐งฐ ์ปค์คํ
๋๊ตฌ ์ ์ ๋ฐ ๋ฐ์ธ๋ฉ
```python
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
# 1. @tool ๋ฐ์ฝ๋ ์ดํฐ๋ก ๋๊ตฌ ์ ์ธ (ํ์
ํํธ์ ๋
์คํธ๋ง์ด ๋ชจ๋ธ์ ๋๊ตฌ ์ค๋ช
์๊ฐ ๋จ)
@tool
def get_current_stock_price(ticker: str) -> str:
"""์ฃผ์ ์ข
๋ชฉ ํฐ์ปค(์: AAPL, MSFT, 005930)๋ฅผ ๋ฐ์ ํ์ฌ ์ฃผ์ ๊ฐ๊ฒฉ์ ์กฐํํฉ๋๋ค."""
# ์ค์ ๋ก๋ ๊ธ์ต API ํธ์ถ
dummy_prices = {"AAPL": "$235.50", "MSFT": "$420.10", "005930": "72,500์"}
price = dummy_prices.get(ticker.upper(), "์ ์ ์์")
return f"{ticker}์ ํ์ฌ ์ฃผ๊ฐ๋ {price}์
๋๋ค."
@tool
def calculate_tax(amount: float, tax_rate: float = 0.1) -> float:
"""๊ธ์ก๊ณผ ์ธ์จ(๊ธฐ๋ณธ 10%)์ ์
๋ ฅ๋ฐ์ ๋ถ๊ฐ์ธ ๊ธ์ก์ ๊ณ์ฐํฉ๋๋ค."""
return amount * tax_rate
# 2. ๋ชจ๋ธ์ ๋๊ตฌ ๋ฐ์ธ๋ฉ
llm = ChatOpenAI(model="gpt-4o", temperature=0)
tools = [get_current_stock_price, calculate_tax]
llm_with_tools = llm.bind_tools(tools)
# 3. ๋๊ตฌ ํธ์ถ ์์ฒญ ํ
์คํธ
response = llm_with_tools.invoke("์ ํ(AAPL) ์ฃผ๊ฐ๊ฐ ์ง๊ธ ์ผ๋ง์ธ์ง ํ์ธํด์ค.")
# LLM์ด ๋๊ตฌ ํธ์ถ ๊ณํ(tool_calls)์ ์๋ฆฝํ๋์ง ํ์ธ
if response.tool_calls:
for call in response.tool_calls:
print(f"ํธ์ถํ ๋๊ตฌ๋ช
: {call['name']}")
print(f"์ ๋ฌํ ์ธ์: {call['args']}")
```
---
## 9. LangGraph ๊ธฐ๋ฐ ReAct ์์ด์ ํธ & ๋ํ ๋ฉ๋ชจ๋ฆฌ
์ต์ LangChain ์ํ๊ณ์์๋ ๊ตฌ๋ฒ์ `AgentExecutor` ๋์ **LangGraph**์ `create_react_agent`๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๊ณต์ ํ์ค์
๋๋ค. ์ํ ์ ์ฅ(MemorySaver)์ ํตํด ์ด์ ๋ํ๋ฅผ ์๋ฒฝํ ๊ธฐ์ตํฉ๋๋ค.
### ๐ค ๋จ 10์ค๋ก ์์ฑํ๋ ์ค์ ReAct ์์ด์ ํธ
```python
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import MemorySaver
# 1. ์์ด์ ํธ๊ฐ ํ์ฉํ ๋๊ตฌ ์ค๋น
@tool
def add(a: int, b: int) -> int:
"""๋ ์ ์๋ฅผ ๋ํฉ๋๋ค."""
return a + b
@tool
def multiply(a: int, b: int) -> int:
"""๋ ์ ์๋ฅผ ๊ณฑํฉ๋๋ค."""
return a * b
tools = [add, multiply]
llm = ChatOpenAI(model="gpt-4o", temperature=0)
# 2. ๋จ๊ธฐ ๋ํ ์ธ์
์ ์ง๋ฅผ ์ํ ์ธ๋ฉ๋ชจ๋ฆฌ ์ฒดํฌํฌ์ธํฐ
memory = MemorySaver()
# 3. LangGraph ReAct ์์ด์ ํธ ์์ฑ
agent_executor = create_react_agent(
model=llm,
tools=tools,
checkpointer=memory
)
# 4. ์ธ์
ID ์ง์ ์คํ
config = {"configurable": {"thread_id": "session-user-123"}}
# ์ฒซ ๋ฒ์งธ ๋ํ (๋๊ตฌ ๋ค์ค ํธ์ถ ์ํ)
res1 = agent_executor.invoke(
{"messages": [("user", "15 ๋ํ๊ธฐ 27์ ๊ณ์ฐํ๊ณ , ๊ทธ ๊ฒฐ๊ณผ์ 3์ ๊ณฑํด์ค.")]},
config=config
)
print("์์ด์ ํธ ๋ต๋ณ:", res1["messages"][-1].content)
# ๋ ๋ฒ์งธ ๋ํ (์ด์ ๋ฌธ๋งฅ ๊ธฐ์ต ํ์ธ)
res2 = agent_executor.invoke(
{"messages": [("user", "๋ฐฉ๊ธ ๋์จ ์ต์ข
์ซ์์ 10์ ๋ํ๋ฉด ์ผ๋ง์ผ?")]},
config=config
)
print("ํ์ ์ง๋ฌธ ๋ต๋ณ:", res2["messages"][-1].content)
```
---
## 10. ์ค์๊ฐ ์คํธ๋ฆฌ๋ฐ & ๋น๋๊ธฐ (SSE / Web API ์ฐ๋)
FastAPI๋ Web ์๋ฒ ํ๊ฒฝ์์ ํ ํฐ์ด ์์ฑ๋๋ ์ฆ์ ์ฌ์ฉ์ ํ๋ฉด์ ํ์ดํ ํจ๊ณผ๋ก ์ ์กํ๋ ์คํธ๋ฆฌ๋ฐ ํจํด์
๋๋ค.
### ๐ ํ ํฐ ์ฒญํฌ ๋จ์ ์คํธ๋ฆฌ๋ฐ
```python
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
llm = ChatOpenAI(model="gpt-4o", streaming=True)
prompt = ChatPromptTemplate.from_template("์๋์ด ๊ฐ๋ฐ์๊ฐ ๊ฐ์ถ์ด์ผ ํ ๋ง์ธ๋์
์ ์กฐ์ธํด์ค: {topic}")
chain = prompt | llm | StrOutputParser()
# stream() ์ ๋๋ ์ดํฐ๋ก ํ ๊ธ์์ฉ ์ค์๊ฐ ์์
print("์คํธ๋ฆฌ๋ฐ ์์: ", end="", flush=True)
for chunk in chain.stream({"topic": "์ฝ๋ ๋ฆฌ๋ทฐ ๋ฌธํ"}):
print(chunk, end="", flush=True)
print("\n[์๋ฃ]")
```
### โก FastAPI ๋น๋๊ธฐ SSE (Server-Sent Events) ์คํธ๋ฆฌ๋ฐ ์ฐ๋
```python
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
app = FastAPI()
@app.get("/chat/stream")
async def chat_stream_endpoint(query: str):
async def event_generator():
async for chunk in chain.astream({"topic": query}):
# SSE ํ์ค ํฌ๋งท (data: <payload>\n\n)
yield f"data: {chunk}\n\n"
await asyncio.sleep(0.01)
return StreamingResponse(event_generator(), media_type="text/event-stream")
```
---
## 11. ํ๋ก๋์
์ด์ & ์ค๋ฌด ๋ฒ ์คํธ ํ๋ํฐ์ค
### ๐ก ์ค๋ฌด ๊ฐ๋ฐ์ ์ฒดํฌ๋ฆฌ์คํธ
1. **Pydantic ์คํค๋ง ํ์ ์ ์ฉ**: ๋น์ ํ ํ
์คํธ ๋์ `with_structured_output`์ ์ฌ์ฉํ์ฌ API ํต์ ์ ๋ขฐ๋๋ฅผ 99.9%๋ก ์ ์งํ์ธ์.
2. **์ฒญํฌ ์ฌ์ด์ฆ ์ต์ ํ**: ํ๊ตญ์ด ํ
์คํธ๋ ํ ํฐ ๋ฐ๋๊ฐ ๋์ผ๋ฏ๋ก `chunk_size=300~500`, `chunk_overlap=50` ๋ด์ธ๊ฐ ๊ฒ์ ์ ํ๋์ ๊ฐ์ฅ ์ ํฉํฉ๋๋ค.
3. **LangSmith ํธ๋ ์ด์ฑ ํ์ฑํ**: ๋ณต์กํ ์ฒด์ธ๊ณผ ์์ด์ ํธ๊ฐ ์ด๋ค ๋๊ตฌ๋ฅผ ํธ์ถํ๊ณ ์ค๊ฐ ์
๋ ฅ/์ถ๋ ฅ์ด ๋ฌด์์ด์๋์ง ์๊ฐ์ ๋์๋ณด๋๋ก ํ์ธํ๋ฉด ๋๋ฒ๊น
์๊ฐ์ด 80% ๋จ์ถ๋ฉ๋๋ค.
4. **AgentExecutor ๋์ LangGraph ์ฑํ**: ๋ณต์กํ ์กฐ๊ฑด ๋ถ๊ธฐ, ์ธ๊ฐ ๊ฐ์
(Human-in-the-loop), ๋ฉํฐ ์์ด์ ํธ ํ์
์ ์ต์ ํ์ค์ธ LangGraph๋ก ์์ฑํด์ผ ์ฅ๊ธฐ ์ ์ง๋ณด์๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
5. **ํ ํฐ ๋น์ฉ ์ ๊ฐ**: ๋จ์ ๋ถ๋ฅ๋ ์๋ ํ์
์ ์ํ ๊ณ ์ ๋ชจ๋ธ(GPT-4o-mini, Claude 3.5 Haiku)์ ๊ฑฐ์น๊ณ , ์ต์ข
๋ณต์ก ๋ต๋ณ ์์ฑ์๋ง ๊ณ ๊ธ ๋ชจ๋ธ(GPT-4o)์ ํธ์ถํ๋ ๋ผ์ฐํ
์ฒด์ธ์ ๊ตฌ์ฑํ์ธ์.
์๊ฒฌ ๋ฐ ์ง๋ฌธ
0์์ง ๋ฑ๋ก๋ ์๊ฒฌ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋๊ธ์ ๋จ๊ฒจ๋ณด์ธ์!
๋๊ธ ์์
๋๊ธ ์ญ์