IT기술/랭체인 (langchain)

LangChain과 로컬 LLM 통합 완벽 가이드: 클라우드 없이 강력한 AI 애플리케이션 구축하기

후스파 2025. 7. 7. 19:37
반응형

LangChain과 로컬 LLM을 활용하면 클라우드 API에 의존하지 않고도 강력한 AI 애플리케이션을 구축할 수 있습니다.
이 글에서는 로컬 환경에서 LLM을 실행하고 LangChain과 통합하는 방법을 알아보겠습니다.


로컬 LLM의 장점

로컬 LLM을 사용하는 것은 여러 장점이 있습니다:

  • 비용 절감: API 호출 비용이 발생하지 않음
  • 데이터 보안: 민감한 데이터가 외부로 전송되지 않음
  • 안정성: 인터넷 연결이나 클라우드 서비스 상태에 의존하지 않음
  • 커스터마이징: 특정 도메인에 맞게 모델을 조정할 수 있음
  • 프라이버시 보장: 모든 데이터가 로컬 환경에서 처리됨
  • 오프라인 작업: 인터넷 연결 없이도 AI 기능 사용 가능

Ollama를 활용한 로컬 LLM 실행

Ollama는 로컬 환경에서 LLM을 쉽게 실행할 수 있게 해주는 도구입니다.

Ollama 설치 및 모델 다운로드

# 1. Ollama 설치 (macOS)
brew install ollama

# 2. Ollama 서비스 시작
brew services start ollama

# 3. 인기 모델 다운로드
ollama pull llama3.2:latest
ollama pull mistral:latest
ollama pull codellama:latest

# 4. 설치된 모델 확인
ollama list

Ollama로 모델 실행하기

# 터미널에서 직접 모델 실행
ollama run llama3.2:latest

# 대화 예시
>>> hi
Hello! It's nice to meet you. Is there something I can help you with or would you like to chat?

>>> 파이썬으로 간단한 웹 서버를 만드는 방법을 알려줘
파이썬으로 간단한 웹 서버를 만드는 방법을 알려드리겠습니다...

Ollama API 서버 모드

# API 서버로 실행 (백그라운드)
ollama serve

# 다른 터미널에서 API 호출 테스트
curl http://localhost:11434/api/generate -d '{
  "model": "llama3.2:latest",
  "prompt": "Why is the sky blue?",
  "stream": false
}'

LangChain과 로컬 LLM 통합하기

LangChain은 대규모 언어 모델(LLM)로 구동되는 애플리케이션을 개발하기 위한 오픈소스 프레임워크입니다. LangChain을 사용하면 로컬 LLM을 쉽게 통합하여 더 복잡한 애플리케이션을 구축할 수 있습니다.

LangChain 설치

pip install langchain langchain-community langchain-ollama

Ollama와 LangChain 연결하기

from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

# Ollama 모델 초기화
llm = ChatOllama(
    model="llama3.2:latest",
    temperature=0.7,
    num_predict=256
)

# 간단한 질의 실행
response = llm.invoke("안녕하세요! 파이썬에 대해 설명해주세요.")
print(response.content)

# 프롬프트 템플릿 사용
prompt = ChatPromptTemplate.from_messages([
    ("system", "당신은 도움이 되는 AI 어시스턴트입니다."),
    ("human", "{question}")
])

# 체인 구성
chain = prompt | llm | StrOutputParser()

# 체인 실행
result = chain.invoke({"question": "머신러닝의 기본 개념을 설명해주세요."})
print(result)

고급 설정 및 최적화

from langchain_ollama import ChatOllama
from langchain.callbacks.manager import CallbackManager
from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler

# 스트리밍 콜백 설정
callback_manager = CallbackManager([StreamingStdOutCallbackHandler()])

# 고급 설정으로 모델 초기화
llm = ChatOllama(
    model="llama3.2:latest",
    temperature=0.7,
    top_p=0.9,
    top_k=40,
    repeat_penalty=1.1,
    num_predict=512,
    callback_manager=callback_manager,
    verbose=True
)

# 메모리를 활용한 대화 체인
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain

memory = ConversationBufferMemory()
conversation = ConversationChain(
    llm=llm,
    memory=memory,
    verbose=True
)

# 연속 대화
print(conversation.predict(input="안녕하세요! 저는 개발자입니다."))
print(conversation.predict(input="파이썬으로 웹 개발을 하고 싶은데 어떤 프레임워크를 추천하시나요?"))

대화형 애플리케이션 만들기

Streamlit을 활용하여 간단한 대화형 애플리케이션을 만들 수 있습니다.

# app.py
import streamlit as st
from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationChain

# 페이지 설정
st.set_page_config(
    page_title="로컬 LLM 챗봇",
    page_icon="🤖",
    layout="wide"
)

# 사이드바 설정
st.sidebar.title("모델 설정")

# 모델 선택
model_options = ["llama3.2:latest", "mistral:latest", "codellama:latest"]
selected_model = st.sidebar.selectbox("모델 선택", model_options)

# 파라미터 설정
temperature = st.sidebar.slider("Temperature", 0.0, 2.0, 0.7, 0.1)
max_tokens = st.sidebar.slider("최대 토큰", 50, 1000, 256, 50)

# 모델 초기화
@st.cache_resource
def load_model(model_name, temp, max_tok):
    return ChatOllama(
        model=model_name,
        temperature=temp,
        num_predict=max_tok
    )

llm = load_model(selected_model, temperature, max_tokens)

# 세션 상태 초기화
if "messages" not in st.session_state:
    st.session_state.messages = []

if "memory" not in st.session_state:
    st.session_state.memory = ConversationBufferMemory()

# 메인 페이지
st.title("🤖 로컬 LLM 챗봇")
st.write(f"현재 모델: **{selected_model}**")

# 대화 기록 표시
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

# 사용자 입력
if prompt := st.chat_input("메시지를 입력하세요..."):
    # 사용자 메시지 추가
    st.session_state.messages.append({"role": "user", "content": prompt})
    with st.chat_message("user"):
        st.markdown(prompt)

    # AI 응답 생성
    with st.chat_message("assistant"):
        with st.spinner("생각 중..."):
            try:
                # 대화 체인 생성
                conversation = ConversationChain(
                    llm=llm,
                    memory=st.session_state.memory,
                    verbose=False
                )

                response = conversation.predict(input=prompt)
                st.markdown(response)

                # 응답 메시지 추가
                st.session_state.messages.append({"role": "assistant", "content": response})

            except Exception as e:
                st.error(f"오류가 발생했습니다: {str(e)}")

# 대화 기록 초기화 버튼
if st.sidebar.button("대화 기록 초기화"):
    st.session_state.messages = []
    st.session_state.memory = ConversationBufferMemory()
    st.rerun()

# 실행 방법 안내
st.sidebar.markdown("---")
st.sidebar.markdown("### 실행 방법")
st.sidebar.code("streamlit run app.py")

실행

streamlit run app.py

RAG 시스템 구축

로컬 LLM과 RAG 통합

from langchain_community.document_loaders import PyPDFLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.vectorstores import FAISS
from langchain.chains import RetrievalQA
from langchain_ollama import ChatOllama

class LocalRAGSystem:
    def __init__(self, model_name="llama3.2:latest"):
        self.llm = ChatOllama(model=model_name, temperature=0.1)
        self.embeddings = OllamaEmbeddings(model="nomic-embed-text")
        self.text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=1000,
            chunk_overlap=200
        )
        self.vectorstore = None
        self.qa_chain = None

    def load_documents(self, file_paths):
        """문서 로드 및 처리"""
        documents = []

        for file_path in file_paths:
            if file_path.endswith('.pdf'):
                loader = PyPDFLoader(file_path)
            elif file_path.endswith('.txt'):
                loader = TextLoader(file_path, encoding='utf-8')
            else:
                continue

            docs = loader.load()
            documents.extend(docs)

        # 텍스트 분할
        splits = self.text_splitter.split_documents(documents)

        # 벡터 스토어 생성
        self.vectorstore = FAISS.from_documents(splits, self.embeddings)

        # QA 체인 생성
        self.qa_chain = RetrievalQA.from_chain_type(
            llm=self.llm,
            chain_type="stuff",
            retriever=self.vectorstore.as_retriever(search_kwargs={"k": 3}),
            return_source_documents=True
        )

        return len(splits)

    def query(self, question):
        """질의응답 실행"""
        if not self.qa_chain:
            return "먼저 문서를 로드해주세요."

        result = self.qa_chain({"query": question})
        return {
            "answer": result["result"],
            "sources": [doc.page_content[:200] + "..." for doc in result["source_documents"]]
        }

# 사용 예시
rag_system = LocalRAGSystem()

# 문서 로드
document_count = rag_system.load_documents(["document1.pdf", "document2.txt"])
print(f"로드된 문서 청크 수: {document_count}")

# 질의응답
result = rag_system.query("이 문서의 주요 내용은 무엇인가요?")
print("답변:", result["answer"])
print("출처:", result["sources"])

다양한 로컬 LLM 옵션

HuggingFace 모델 직접 사용

from langchain_huggingface import HuggingFacePipeline
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
import torch

# GPU 사용 가능 여부 확인
device = 0 if torch.cuda.is_available() else -1

model_id = "microsoft/DialoGPT-medium"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id)

# 파이프라인 생성
pipe = pipeline(
    "text-generation",
    model=model,
    tokenizer=tokenizer,
    max_new_tokens=100,
    device=device,
    do_sample=True,
    temperature=0.7
)

# LangChain 통합
hf_llm = HuggingFacePipeline(pipeline=pipe)

# 사용 예시
response = hf_llm.invoke("안녕하세요! 오늘 날씨가 어떤가요?")
print(response)

LM Studio 활용

from langchain_openai import ChatOpenAI

# LM Studio는 OpenAI 호환 API를 제공
lm_studio_llm = ChatOpenAI(
    base_url="http://localhost:1234/v1",  # LM Studio 기본 주소
    api_key="lm-studio",  # 더미 키
    model="local-model",
    temperature=0.7
)

# 사용 예시
response = lm_studio_llm.invoke("파이썬으로 웹 스크래핑하는 방법을 알려주세요.")
print(response.content)

Docker를 활용한 로컬 LLM 배포

# docker-compose.yml
version: '3.8'

services:
  ollama:
    image: ollama/ollama:latest
    ports:
      - "11434:11434"
    volumes:
      - ollama_data:/root/.ollama
    environment:
      - OLLAMA_HOST=0.0.0.0

  langchain-app:
    build: .
    ports:
      - "8501:8501"
    depends_on:
      - ollama
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434

volumes:
  ollama_data:
# Dockerfile
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 8501

CMD ["streamlit", "run", "app.py", "--server.address", "0.0.0.0"]

성능 최적화 및 모니터링

모델 성능 최적화

import time
import psutil
from langchain_ollama import ChatOllama

class OptimizedLocalLLM:
    def __init__(self, model_name="llama3.2:latest"):
        self.llm = ChatOllama(
            model=model_name,
            temperature=0.7,
            num_predict=256,
            # 성능 최적화 설정
            num_ctx=2048,  # 컨텍스트 크기
            num_batch=512,  # 배치 크기
            num_gpu=1 if torch.cuda.is_available() else 0
        )
        self.performance_stats = []

    def invoke_with_monitoring(self, prompt):
        """성능 모니터링과 함께 모델 호출"""
        start_time = time.time()
        start_memory = psutil.Process().memory_info().rss / 1024 / 1024

        try:
            response = self.llm.invoke(prompt)
            success = True
        except Exception as e:
            response = f"오류: {str(e)}"
            success = False

        end_time = time.time()
        end_memory = psutil.Process().memory_info().rss / 1024 / 1024

        stats = {
            "duration": end_time - start_time,
            "memory_used": end_memory - start_memory,
            "success": success,
            "prompt_length": len(prompt),
            "response_length": len(str(response))
        }

        self.performance_stats.append(stats)

        return response, stats

    def get_performance_summary(self):
        """성능 통계 요약"""
        if not self.performance_stats:
            return "성능 데이터가 없습니다."

        avg_duration = sum(s["duration"] for s in self.performance_stats) / len(self.performance_stats)
        avg_memory = sum(s["memory_used"] for s in self.performance_stats) / len(self.performance_stats)
        success_rate = sum(s["success"] for s in self.performance_stats) / len(self.performance_stats)

        return {
            "총 호출 수": len(self.performance_stats),
            "평균 응답 시간": f"{avg_duration:.2f}초",
            "평균 메모리 사용량": f"{avg_memory:.2f}MB",
            "성공률": f"{success_rate*100:.1f}%"
        }

# 사용 예시
optimized_llm = OptimizedLocalLLM()

response, stats = optimized_llm.invoke_with_monitoring("파이썬의 장점을 설명해주세요.")
print(f"응답: {response}")
print(f"성능: {stats}")

# 여러 호출 후 통계 확인
summary = optimized_llm.get_performance_summary()
print(f"성능 요약: {summary}")

마무리

LangChain과 로컬 LLM을 활용하면 클라우드 API에 의존하지 않고도 강력한 AI 애플리케이션을 구축할 수 있습니다. 비용 절감, 데이터 보안, 안정성 등 여러 장점이 있어 특히 기업 환경이나 특정 도메인에 특화된 애플리케이션을 개발할 때 유용합니다.
Ollama, HuggingFace, LM Studio 등 다양한 도구를 활용하여 로컬 LLM을 실행하고 LangChain과 통합할 수 있습니다.
핵심 포인트:

  • Ollama를 통한 간편한 로컬 LLM 설치와 관리
  • LangChain과의 완벽한 통합으로 복잡한 AI 애플리케이션 구축
  • RAG 시스템 구현으로 문서 기반 질의응답 서비스 제공
  • Streamlit을 활용한 대화형 웹 애플리케이션 개발
  • 성능 모니터링과 최적화를 통한 안정적인 서비스 운영
  • Docker 컨테이너화로 일관된 배포 환경 제공

로컬 LLM과 LangChain의 조합은 프라이버시가 중요한 기업 환경이나 오프라인 환경에서 AI 기능이 필요한 애플리케이션 개발에 최적의 솔루션을 제공합니다.

반응형