
LLM 03 - LangChain (Ollama 포함)
Created: August 27, 2026 3:14 PM Class: LLM
Ollama
1. Ollama란?
- 로컬 환경에서 LLM을 실행할 수 있게 해주는 오픈소스 런타임/도구
- 내부적으로
llama.cpp를 엔진으로 사용하며, GGUF 포맷 모델을 로드해 추론 수행 - 모델 다운로드, 버전 관리, 실행, API 서빙을 CLI 명령어 및 REST API로 제공
- OpenAI 호환 API(
/v1/chat/completions)를 함께 제공하여 기존 OpenAI SDK 코드와 호환 가능 - 기본 실행 시 로컬 포트(기본
11434)에서 서버 프로세스로 상시 대기
2. 설치
Windows
- https://ollama.com/download 에서 Windows 설치 파일 다운로드
- 설치 후 자동으로 백그라운드 서비스로 등록 (시스템 트레이 상주)
- PowerShell에서 설치 확인
macOS / Linux
# macOS: 앱 다운로드 또는 brew
brew install ollama
# Linux
curl -fsSL https://ollama.com/install.sh | sh
GPU 인식 확인 (Windows, RTX 4070 기준)
- NVIDIA 드라이버가 설치되어 있으면 자동으로 CUDA 가속 사용
- 확인:
- 실행 중 작업관리자에서 GPU 사용률 확인 가능
3. 모델 불러오기 (Pull / Run)
| 명령어 | 설명 |
|---|
ollama pull <model> | 모델만 다운로드 (실행 X) |
ollama run <model> | 모델 다운로드(없을 경우) 후 대화형 실행 |
ollama run <model> "프롬프트" | 단발성 실행, 결과 출력 후 종료 |
ollama create <name> -f Modelfile | Modelfile 기반 커스텀 모델 생성 |
# 예시
ollama pull exaone3.5:7.8b
ollama run llama3.2:3b "한국의 수도는?"
- 모델 태그:
모델명:파라미터크기 형식 (예: llama3.2:3b, exaone3.5:7.8b) - 태그 생략 시 기본(latest) 버전 사용
4. 모델 리스트 / 정보 확인
| 명령어 | 설명 |
|---|
ollama list | 로컬에 다운로드된 모델 목록 |
ollama ps | 현재 메모리에 로드되어 실행 중인 모델 |
ollama show <model> | 모델 파라미터, 템플릿, 시스템 프롬프트 등 상세 정보 |
ollama list
ollama show exaone3.5:7.8b
5. 모델 제거
| 명령어 | 설명 |
|---|
ollama rm <model> | 로컬 저장소에서 모델 삭제 |
ollama stop <model> | 메모리에서 언로드 (파일은 유지) |
6. 기타 사용법
REST API 호출
curl http://localhost:11434/api/generate -d '{
"model": "llama3.2:3b",
"prompt": "안녕하세요"
}'
OpenAI 호환 엔드포인트
curl http://localhost:11434/v1/chat/completions -d '{
"model": "llama3.2:3b",
"messages": [{"role": "user", "content": "안녕하세요"}]
}'
Python 라이브러리
import ollama
response = ollama.chat(
model="exaone3.5:7.8b",
messages=[{"role": "user", "content": "안녕하세요"}]
)
print(response["message"]["content"])
LangChain 연동 (langchain_ollama)
from langchain_ollama import ChatOllama
llm = ChatOllama(model="exaone3.5:7.8b")
llm.invoke("안녕하세요")
Modelfile로 커스텀 모델 만들기
FROM llama3.2:3b
SYSTEM "당신은 친절한 한국어 assistant입니다."
PARAMETER temperature 0.7
ollama create my-korean-bot -f Modelfile
주요 환경변수
| 변수 | 설명 |
|---|
OLLAMA_MODELS | 모델 저장 경로 변경 |
OLLAMA_HOST | 서버 바인딩 주소/포트 변경 |
OLLAMA_NUM_PARALLEL | 동시 요청 처리 수 |
7. Ollama vs LM Studio 비교
| 항목 | Ollama | LM Studio |
|---|
| 인터페이스 | CLI 중심 (GUI는 부가 기능) | GUI 중심 (데스크톱 앱) |
| 대상 사용자 | 개발자, API/자동화 연동 중심 | 비개발자 포함, 직관적 사용 중심 |
| API 제공 | REST API, OpenAI 호환 API 기본 제공 | OpenAI 호환 로컬 서버 지원 |
| 모델 포맷 | GGUF (llama.cpp 엔진) | GGUF, MLX(Apple Silicon 최적화) 지원 |
| 모델 관리 | pull/rm 등 명령어로 관리, Modelfile로 커스터마이징 | GUI에서 모델 검색·다운로드·전환 |
| 자동화/스크립팅 | 용이 (CLI, Python 라이브러리, LangChain 연동) | 상대적으로 제한적 |
| 백그라운드 서비스 | 상시 상주 서버 방식 | 앱 실행 시에만 서버 동작(옵션) |
| 학습 곡선 | 명령어 학습 필요 | 낮음 (GUI 클릭 기반) |
| 확장 생태계 | LangChain, LangGraph 등 프레임워크 연동 활발 | 로컬 실험/테스트 용도에 특화 |
정리: 자동화·에이전트 개발·API 연동이 목적이면 Ollama, 빠른 로컬 테스트·GUI 기반 탐색이 목적이면 LM Studio가 적합.
Langchain
1. 기본 활용
Ollama
from langchain_ollama import ChatOllama
# ChatOllama 모델 초기화
llm = ChatOllama(
model="exaone3.5:7.8b",
base_url="http://localhost:11434",
temperature=0.1,
)
# system/human 메시지 구성
msg = [
("system", "당신은 매우 숙련된 쉐프입니다."),
("human", "오징어 볶음 레시피를 알려줘, 10인분을 할건데 각 재료의 양도 알려줘.")
]
# 동기 호출
res = llm.invoke(msg)
print(res.model_dump_json(indent=2))
print("="*30)
# 스트리밍 호출 (텍스트만 순차 출력)
for chunk in llm.stream(msg):
print(chunk.text, end="")
base_url: Ollama 서버 주소 (로컬이면 생략 가능)temperature: 낮을수록 일관된 답변, 높을수록 다양한 답변msg: system(역할 지정) + human(질문) 메시지 쌍invoke(): 전체 응답 완성 후 한 번에 반환, AIMessage 객체 반환stream(): 토큰 단위로 순차 반환, 실시간 출력에 적합. chunk.text로 텍스트만 추출
OpenAI
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="gpt-5.6-luna",
temperature=0,
)
msg = [
("system", "당신은 매우 숙련된 쉐프입니다."),
("human", "오징어 볶음 레시피를 알려줘, 10인분을 할건데 각 재료의 양도 알려줘.")
]
model.invoke(msg)
LMStudio
from langchain_openai import ChatOpenAI
model = ChatOpenAI(
model="google/gemma-4-12bn-qat",
base_url="http://localhost:1234/v1",
temperature=0,
)
msg = [
("system", "당신은 매우 숙련된 쉐프입니다."),
("human", "오징어 볶음 레시피를 알려줘, 10인분을 할건데 각 재료의 양도 알려줘.")
]
model.invoke(msg)
2. 간단한 chain
Ollama
from langchain_core.prompts import ChatPromptTemplate
from langchain_ollama import ChatOllama
# 프롬프트 템플릿 정의 ({menu} 변수 포함)
template = ChatPromptTemplate.from_template(
"{menu}의 레시피를 알려줘, 10인분을 할건데 각 재료의 양도 알려줘"
)
# ChatOllama 모델 초기화
model = ChatOllama(
model="exaone3.5:7.8b",
base_url="http://localhost:11434",
temperature=0.1,
)
# 체인 연결 (LCEL: template -> model)
chain = template | model
# 실행 (변수 값 전달)
chain.invoke({"menu": "짜장면"})
ChatPromptTemplate.from_template(): {변수명} 형태의 플레이스홀더가 있는 프롬프트 템플릿 생성template | model: LCEL(| 파이프 연산자)로 템플릿과 모델을 연결한 체인. 왼쪽 출력이 오른쪽 입력으로 전달됨chain.invoke({"menu": "짜장면"}): 템플릿의 {menu}에 값 대입 → 완성된 프롬프트가 모델로 전달 → 응답 반환- 기존 방식(메시지 리스트 직접 작성) 대비, 변수만 바꿔가며 재사용 가능한 구조
OpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
template = ChatPromptTemplate.from_template(
"{menu}의 레시피를 알려줘, 10인분을 할건데 각 재료의 양도 알려줘"
)
model = ChatOpenAI(
model="gpt-5.6-luna",
temperature=0.1,
)
chain = template | model
chain.invoke({"menu":"짜장면"})
LMStudio
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
template = ChatPromptTemplate.from_template(
"{menu}의 레시피를 알려줘, 10인분을 할건데 각 재료의 양도 알려줘"
)
model = ChatOpenAI(
model="google/gemma-4-12b-qat",
base_url="http://localhost:1234/v1",
temperature=0.1,
)
chain = template | model
chain.invoke({"menu":"짜장면"})
3. 반복되는 명령처리
Ollama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_ollama import ChatOllama
# 프롬프트 템플릿 정의
prompt = ChatPromptTemplate.from_template(
"{menu}의 레시피를 알려줘, 10인분을 할건데 각 재료의 양도 알려줘"
)
# ChatOllama 모델 초기화
model = ChatOllama(
model="exaone3.5:7.8b",
base_url="http://localhost:11434",
temperature=0.1,
)
# 출력 파서 (AIMessage -> 문자열만 추출)
output_parser = StrOutputParser()
# 체인 연결 (prompt -> model -> output_parser)
chain = prompt | model | output_parser
# 실행 (여러 메뉴 반복 호출)
print(chain.invoke({"menu": "짜장면"}))
print("="*80)
print(chain.invoke({"menu": "비빔밥"}))
print("="*80)
print(chain.invoke({"menu": "제육볶음"}))
StrOutputParser(): 체인의 최종 출력을 AIMessage 객체가 아닌 순수 문자열(content)로 변환chain = prompt | model | output_parser: 3단계 LCEL 체인. 프롬프트 생성 → 모델 응답 → 문자열 추출- 출력 파서가 없으면
res.content로 직접 꺼내야 하지만, 파서를 붙이면 invoke() 결과 자체가 바로 문자열 - 동일한 체인을 변수만 바꿔 여러 번
invoke() 호출 → 반복적인 요청 처리에 재사용 가능한 패턴
OpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
prompt= ChatPromptTemplate.from_template("{menu}의 레시피를 알려줘, 10인분을 할건데 각 재료의 양도 알려줘")
model = ChatOpenAI(
model="gpt-5.6-terra",
temperature=0,
max_completion_tokens=None,
timeout=None,
max_retries=2,
)
output_parser = StrOutputParser()
chain= prompt | model | output_parser
print(chain.invoke({"menu":"짜장면"}))
print("="*80)
print(chain.invoke({"menu":"비빔밥"}))
print("="*80)
print(chain.invoke({"menu":"제육볶음"}))
LMStudio
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
prompt= ChatPromptTemplate.from_template("{menu}의 레시피를 알려줘, 10인분을 할건데 각 재료의 양도 알려줘")
model = ChatOpenAI(
model="google/gemma-4-12b-qat",
base_url="http://localhost:1234/v1",
temperature=0,
)
output_parser = StrOutputParser()
chain= prompt | model | output_parser
print(chain.invoke({"menu":"짜장면"}))
print("="*80)
print(chain.invoke({"menu":"비빔밥"}))
print("="*80)
print(chain.invoke({"menu":"제육볶음"}))
4. 명령의 명령을 연결하기
Ollama
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_ollama import ChatOllama
# 첫 번째 프롬프트 (레시피 요청)
prompt = ChatPromptTemplate.from_template(
"{menu}의 레시피를 알려줘, 10인분을 할건데 각 재료의 양도 알려줘"
)
# ChatOllama 모델 초기화
model = ChatOllama(
model="exaone3.5:7.8b",
base_url="http://localhost:11434",
temperature=0.1,
)
output_parser = StrOutputParser()
# 첫 번째 체인 (menu -> 한글 레시피 텍스트)
chain = prompt | model | output_parser
# 두 번째 체인을 이어서 연결
# 두 번째 프롬프트 (번역 요청)
prompt2 = ChatPromptTemplate.from_template("다믕 문장을 영어로 번역해줘 #다음 : {kor_output}")
# chain의 출력을 prompt2의 {kor_output} 입력으로 매핑 후 연결
chain2 = {"kor_output": chain} | prompt2 | model | output_parser
# 실행 (menu 입력 -> 레시피 생성 -> 영어 번역까지 한 번에)
print(chain2.invoke({"menu": "짜장면"}))
print("="*80)
print(chain2.invoke({"menu": "비빔밥"}))
print("="*80)
print(chain2.invoke({"menu": "제육볶음"}))
{"kor_output": chain}: 딕셔너리 형태로 체인을 매핑 → 앞 체인(chain)의 출력 문자열을 kor_output 키에 담아 다음 프롬프트(prompt2)의 {kor_output} 변수로 전달chain2 = {"kor_output": chain} | prompt2 | model | output_parser: 체인 안에 체인을 중첩시켜 다단계 파이프라인 구성 (레시피 생성 → 번역)chain2.invoke({"menu": "짜장면"}) 하나의 입력값만으로 내부적으로 두 번의 모델 호출이 순차 실행됨- LCEL의 조합성(composability)을 보여주는 패턴: 체인 자체를 다른 체인의 구성 요소로 재사용 가능
OpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
prompt = ChatPromptTemplate.from_template("{menu}의 레시피를 알려줘, 10인분을 할건데 각 재료의 양도 알려줘")
model = ChatOpenAI(
model="gpt-5.6-terra",
temperature=0,
max_completion_tokens=None,
timeout=None,
max_retries=2,
)
output_parser = StrOutputParser()
chain = prompt | model | output_parser
prompt2 = ChatPromptTemplate.from_template("다믕 문장을 영어로 번역해줘 #다음 : {kor_output}")
chain2 = {"kor_output": chain} | prompt2 | model | output_parser
print(chain2.invoke({"menu":"짜장면"}))
print("="*80)
print(chain2.invoke({"menu":"비빔밥"}))
print("="*80)
print(chain2.invoke({"menu":"제육볶음"}))
LMStudio
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
prompt = ChatPromptTemplate.from_template("{menu}의 레시피를 알려줘, 10인분을 할건데 각 재료의 양도 알려줘")
model = ChatOpenAI(
model="google/gemma-4-12b-qat",
base_url="http://localhost:1234/v1",
temperature=0,
)
output_parser = StrOutputParser()
chain = prompt | model | output_parser
prompt2 = ChatPromptTemplate.from_template("다믕 문장을 영어로 번역해줘 #다음 : {kor_output}")
chain2 = {"kor_output": chain} | prompt2 | model | output_parser
print(chain2.invoke({"menu":"짜장면"}))
print("="*80)
print(chain2.invoke({"menu":"비빔밥"}))
print("="*80)
print(chain2.invoke({"menu":"제육볶음"}))
LLM을 섬세하게 파라미터 조정
OpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
# 프롬프트 템플릿: system/assistant/user 메시지로 구성
prompt = ChatPromptTemplate.from_messages([
("system", "너는 영국의 고든램지 요리사야"),
("assistant", "나는 마늘을 많이 넣어줘"),
("user", "{menu}의 레시피를 알려줘, 1인분을 할건데 각 재료의 양도 알려줘")
])
# ChatOpenAI 모델 초기화 (파라미터를 top-level로 직접 전달)
llm = ChatOpenAI(
model="gpt-4o",
temperature=0.5,
frequency_penalty=0.5, # 이미 등장한 단어의 재등장 확률 억제
presence_penalty=0.5, # 새로운 단어(주제) 도입 유도
)
output_parser = StrOutputParser()
# 체인 연결
chain = prompt | llm | output_parser
# 실행
print(chain.invoke({"menu": "알리오 올리오"}))
frequency_penalty / presence_penalty: 예전에는 model_kwargs={...}로 묶어서 전달했으나, 현재는 ChatOpenAI 생성자에 top-level 인자로 직접 전달하는 방식이 맞음frequency_penalty=0.5: 반복 단어 사용을 줄이는 방향으로 페널티 부여 (값이 클수록 반복 억제 강함)presence_penalty=0.5: 아직 등장하지 않은 새로운 단어/주제를 도입하도록 유도 (값이 클수록 다양한 주제 확장)- 두 파라미터 모두
2.0 ~ 2.0 범위, 기본값은 0 - 해당 파라미터들은 OpenAI 계열 API 특화 기능이라 Ollama(
ChatOllama)에서는 지원되지 않거나 무시될 수 있음
Ollama
from langchain_core.prompts import ChatPromptTemplate
from langchain_ollama import ChatOllama
from langchain_core.output_parsers import StrOutputParser
prompt= ChatPromptTemplate.from_messages([
("system","너는 영국의 고든램지 요리사야"),
("assistant","나는 마늘을 많이 넣어줘"),
("user","{menu}의 레시피를 알려줘, 1인분을 할건데 각 재료의 양도 알려줘")
])
llm = ChatOllama(
model="exaone3.5:7.8b",
base_url="http://localhost:11434",
temperature=0.1,
repeat_penalty=1.3,
)
output_parser = StrOutputParser()
chain = prompt | llm | output_parser
print(chain.invoke({"menu":"알리오 올리오"}))
LMStudio
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
prompt= ChatPromptTemplate.from_messages([
("system","너는 영국의 고든램지 요리사야"),
("assistant","나는 마늘을 많이 넣어줘"),
("user","{menu}의 레시피를 알려줘, 1인분을 할건데 각 재료의 양도 알려줘")
])
llm = ChatOpenAI(
model="google/gemma-4-12b-qat",
base_url="http://localhost:1234/v1",
temperature=0.5,
frequency_penalty=0.5,
presence_penalty=0.5,
)
output_parser = StrOutputParser()
chain = prompt | llm | output_parser
print(chain.invoke({"menu":"알리오 올리오"}))