Ⅰ. 오전 수업
A. 1교시
1. 지난 시간 복습
2. GET 방식
3. POST 방식
B. 2교시
1. routes 폴더 사용하기
C. 3교시
1. routes 폴더 사용하기 (cont.)
Ⅱ. 오후 수업
A. 4교시
1. 지난 시간 복습
B. 5교시
1. FastAPI: HTML 문서 반환하기
2. 내가 학습시킨 모델을 활용하여 웹으로 챗봇 구현
3. Local LLM 연동하기
C. 6교시
1. Ollama
Ⅲ. CAREER UP
수업 복습 / 추가 공부
Ⅳ. 하루 돌아보기
__dirname 키워드__dirname: 현재 작업 중인 파일을 포함하는 폴더까지가 기준app.use(express.static("public"));목적 파일이 상위 폴더에 위치한다면
path.join(__dirname, '../test.html')와 같이 path 모듈을 사용해 경로를 명시const path = require('path'); const filePath = path.join(__dirname, '../test.html'); const filePath = path.resolve(__dirname, '../test.html'); // path 모듈 활용 시 코드 안정성이 높아짐
__dirname + "/../test.html"는 안 되나요? → 직접 문자열 덧셈은 OS별 경로구분자가 달라서 에러가 나는 경우가 많아 추천하지 않음
app.get("/",(req,res)=>{res.sendFile(__dirname+"/public/login.html")});가 처리할게요~ → 사용자에게 절대 경로 상에 있는 파일 보여주기action="http://localhost:3000/getLogin"으로 데이터 보낼게요 → app.js: app.get("/getLogin",(req,res)=>{});로 받을게요😊action="http://localhost:3000/"으로 보내버리면 무한 루프에 빠짐 (req에 정보가 담겨 있긴 하지만 이걸 쓰면 메인 경로 안에서 전부 처리해야 해서 역할 분담이 안 됨)req.query라는 객체로 바로 쓸 수 있음TIP: 파일 자체를 redirect로 리턴해버리면 url에 파일명이 나오니까 파일명을 바로 명시하지 말고 다른 경로로 보내기!




매번 서버 수정하고 껐다 켜는 거 힘드니까 라이브러리 하나 설치해요~
→ 자동으로 서버 업데이트(내용 바뀌면 알아서 껐다 켜줌)
→ 로컬 환경에서만 쓰기!
npm i nodemon -g
-g: 전역설치(모든 프로젝트에서 쓸 수 있게 설치한다는 의미)
body-parserapp.use(bp.urlencoded({extended:true}));

Express 등장 이유를 잊지말자!
업무의 과중화 → 업무를 분배해서 관리하자!
지금까지 작성한 내용은 사실 업무의 분배가 잘 되지 않은 상태임 → 업무를 쪼개자!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>스포츠 페이지입니다!</h1>
<a href="/baseball">야구 페이지</a>
<a href="/soccer">축구 페이지</a>
</body>
</html>

const express = require("express");
const router = express.Router();
router.get("/",(req,res)=>{
});
router.get("/baseball",(req,res)=>{
});
router.get("/soccer",(req,res)=>{
});
module.exports = router;
module.exports = router; 로 꼭 exports 하고 app.js에서는 반드시 호출해야 함!핵심: 서버는 페이지의 흐름을 파악하는 게 가장 중요 → 어떤 파일이 실행되는지를 생각하자
const mainRouter = require("./routes/mainRouter");
app.use("/",mainRouter);
app.use("/",mainRouter);: 내가 호출하고자 하는 경로의 맨 앞이 그냥 일반적인 슬래시(메인)인 경우 mainRouter에게 가라는 뜻path

router는 경로를 담당하는 모듈 path와 항상 함께 다닌다!
app.use("/",mainRouter);)app.use("/",mainRouter);)이벤트 방식이라 ①, ② 한꺼번에 다 기억하고 있다가 상황에 맞게 이동함
app.use("/",mainRouter); // ① app.use("/esport",subRouter); // ②→
http://localhost:3000/esport로 접근하면 바로 ②로 이동(①은 고려되지 않음)
answer = await chain_2.ainvoke({"region":"싱가포르", "target":"맛집"})추가: 데코레이터(@기호)
함수 정의 위에 위치하여, 해당 함수를 인자로 받아 데코레이션(기능 추가, 변형)하는 파이썬 문법 요소
예를 들어 @app.get("/")는 FastAPI 프레임워크에게 해당 함수가 특정 경로의 GET HTTP 요청을 처리한다고 알려주는 역할을 함
from fastapi import FastAPI
import nest_asyncio
import uvicorn
from fastapi.responses import HTMLResponse
app = FastAPI()
nest_asyncio.apply()
@app.get("/", response_class=HTMLResponse)
async def index():
return """
<html>
<body>
<h1>Hello World !! </h1>
</body>
</html>
"""
if __name__ == '__main__':
uvicorn.run(app, host="127.0.0.1", port=5000)
import os
if not os.path.isdir("./templates"):
os.makedirs("./templates")
print("폴더 생성 완료")
else:
print("이미 폴더가 존재합니다!")
폴더 생성 완료
%%writefile ./templates/hello2.html
<html>
<head>
<meta charset="UTF-8">
<title>Hello World Page</title>
</head>
<body>
<h1>Hello World !!</h1>
<!-- 간단한 테이블 -->
<h2>교육생 명단</h2>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<th>번호</th>
<th>이름</th>
<th>이메일</th>
</tr>
<tr>
<td>1</td>
<td>홍길동</td>
<td>hong@example.com</td>
</tr>
<tr>
<td>2</td>
<td>김철수</td>
<td>kim@example.com</td>
</tr>
</table>
<br>
<!-- 입력값 받는 form -->
<h2>교육생 등록</h2>
<form action="/submit" method="post">
이름: <input type="text" name="name"><br><br>
이메일: <input type="email" name="email"><br><br>
<input type="submit" value="등록">
</form>
</body>
</html>
# Python 내에서 HTML 작성 후 파일로 내보내기
html_text = """
<html>
<head>
<meta charset="UTF-8">
<title>Hello World Page</title>
</head>
<body>
<h1>Hello World !!</h1>
<h2>교육생 명단</h2>
<table border="1" cellpadding="5" cellspacing="0">
<tr>
<th>번호</th>
<th>이름</th>
<th>이메일</th>
</tr>
<tr>
<td>1</td>
<td>홍길동</td>
<td>hong@example.com</td>
</tr>
<tr>
<td>2</td>
<td>김철수</td>
<td>kim@example.com</td>
</tr>
</table>
<br>
<h2>교육생 등록</h2>
<form action="/submit" method="post">
이름: <input type="text" name="name"><br><br>
이메일: <input type="email" name="email"><br><br>
<input type="submit" value="등록">
</form>
</body>
</html>
"""
with open('./templates/hello2.html', 'w', encoding='utf-8') as f:
f.write(html_text)
from fastapi import FastAPI
import nest_asyncio
import uvicorn
from fastapi.responses import FileResponse
app = FastAPI()
nest_asyncio.apply()
@app.get("/", response_class=FileResponse)
async def index():
return "./templates/hello2.html"
if __name__ == '__main__':
uvicorn.run(app, host="127.0.0.1", port=5000)


from langchain_community.llms import Ollama
# 실행할 모델명 설정
llm = Ollama(
model="gemma3:1b"
)
while True:
q=input("입력: ")
if q=="exit":
break
print(llm.invoke(q))

chain = prompt | llm | StrOutParser()from langchain_community.llms import Ollama
from langchain_core.prompts import PromptTemplate
from langchain_core.output_parsers import StrOutputParser
# 실행할 모델명 설정
llm = Ollama(model="gemma3:1b")
# 프롬프트 형식 정의
template = """
당신은 질문-답변을 수행하는 정확하고 친절한 AI 어시스턴트입니다.
당신의 역할은 주어진 질문(question)에 답하는 것입니다.
한글로 답변해 주세요. 단, 기술적인 용어나 고유 이름은 번역하지 않고 그대로 사용해주세요.
질문: {question}
답:
"""
prompt = PromptTemplate.from_template(template)
chain = prompt | llm | StrOutputParser()
res = chain.invoke({"question":"팬케이크 만드는 법 알려줘"})
print(res)
팬케이크를 만드는 방법은 다음과 같습니다.
1. 계란, 밀가루, 우유, 베이킹파우더, 설탕, 바닐라 익스트랙 등 재료를 준비합니다.
2. 팬에 기름을 두르고 따뜻하게 데웁니다.
3. 계란물을 붓고 덩어리가 지지 않도록 잘 섞습니다.
4. 팬에 얇게 반죽을 붓고 2~3분 정도 익힙니다.
5. 마지막에 바닐라 익스트랙을 뿌려주면 완성입니다.
맛있게 드세요!
from langchain_community.llms import Ollama
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
llm = Ollama(model="gemma3:1b")
prompt = ChatPromptTemplate.from_messages([
('system', "너는 여행 전문가이고 여행지의 중요한 부분에 대해 전문적으로 대답할 수 있어"),
('human', '{area}에서 유명한 {topic} {num}가지를 추천해 줘~'),
('ai',""" 아래의 형식처럼 답변을 작성해주세요
======================
타이틀
======================
- 응답 1
- 설명
- 응답 2
- 설명
- 응답 3
- 설명
""")
])
chain = prompt | llm | StrOutputParser()
res = chain.invoke({
"area":"일본"
, "topic": "관광지"
, "num": 3
})
print(res)
======================
**타이틀:** 일본의 대표적인 관광지 3곳
**======================**
**- 응답 1**
* **일본 교토:** 일본의 역사와 문화를 대표하는 도시입니다. 금빛 나라이시의 웅장한 사찰, 아름다운 정원, 고풍스러운 전통 건축물 등 다양한 볼거리가 있습니다. 특히, 기요미즈데라, 료안지, 금각사 등은 꼭 방문해야 할 명소입니다.
* **일본 오사카:** 활기 넘치는 분위기와 맛있는 음식으로 유명한 도시입니다. 오사카성, 도톤보리, 우메다 공원 등 다양한 볼거리가 있으며, 특히 오사카바비와 타코야키는 꼭 맛봐야 할 음식입니다.
* **일본 하코네:** 아름다운 자연과 몽환적인 분위기로 유명한 곳입니다. 100개의 작은 산 꼭대기 위에 자리 잡은 하코네는, 아름다운 풍경을 감상하며 걷거나 자전거를 타는 것을 즐길 수 있습니다.
**======================**
**- 응답 2**
* **일본 다카야마:** 일본의 전통적인 분위기를 느낄 수 있는 도시입니다. 아름다운 거리, 전통 가옥, 숲길 등 다양한 볼거리가 있으며, 특히 다카야마 시장은 활기 넘치는 분위기를 느낄 수 있습니다.
* **일본 후쿠오카:** 아름다운 해변과 맛있는 음식으로 유명한 도시입니다. 후쿠오카성, 겐로쿠 시장, 텐진 거리 등 다양한 볼거리가 있으며, 특히 텐진 거리는 젊은이들의 거리로 알려져 있습니다.
* **일본 나라이시:** 웅장한 댐과 아름다운 자연이 어우러진 곳입니다. 나라이시 댐은 일본의 대표적인 댐 중 하나로, 댐의 아름다운 경관을 감상할 수 있습니다.
**======================**
**- 응답 3**
* **일본 아즈하기:** 아름다운 자연과 조용한 분위기로 유명한 곳입니다. 아즈하기 호수는, 신선한 물과 아름다운 풍경을 감상할 수 있는 곳입니다. 또한, 아즈하기 지역에는 다양한 식물과 동물이 서식하고 있어, 자연을 즐기기에 좋은 곳입니다.
* **일본 홋카카:** 웅장한 산과 아름다운 풍경으로 유명한 곳입니다. 홋카카 주변에는 다양한 관광 명소가 있어, 자연과 문화를 동시에 즐길 수 있습니다.
* **일본 고치:** 일본의 전통적인 문화와 아름다운 자연을 동시에 느낄 수 있는 곳입니다. 고치는, 훌륭한 음식과 풍경을 자랑하며, 일본의 전통 문화를 체험할 수 있는 좋은 장소입니다.
이 외에도 일본에는 수많은 아름다운 관광지가 있습니다. 여행 계획에 맞춰 다양한 관광지를 방문해 보시길 바랍니다.
from langchain_community.llms import Ollama
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
# 실행할 모델명 설정
llm = Ollama(
model="gemma3:1b"
)
# 프롬프트를 한국어용으로 정의
prompt = PromptTemplate(
input_variables=["history", "input"],
template=(
"다음은 사람과 AI의 한국어 대화입니다.\n"
"대화 기록:\n{history}\n\n"
"사람: {input}\nAI:"
),
)
# 메모리 객체 생성
memory = ConversationBufferMemory()
# 2. 체인 구성: 대화형 모델과 메모리를 결합
conversation = ConversationChain(
llm=llm
, memory=memory
, prompt=prompt
, verbose=True
)
# 챗봇 실행
while True:
q = input("입력 :")
if q == "exit":
print("채팅 종료")
break
# 모델 응답
res = conversation.predict(input=q)
# 아니면 conversation.invoke(q)["response"] 로 접근해야 함
print(res)
print() # 개행용
입력 : 한국 전통 음식 5가지 알려줘
네, 한국 전통 음식 5가지 알려드릴게요!
1. **비빔밥:** 나라별로 다양한 비빔밥이 있지만, 밥 위에 다양한 채소와 고기, 계란 등을 올려 먹는 음식입니다.
2. **갈비찜:** 부드러운 갈비를 푹 익혀 리소스에 재워 먹는 음식으로, 달콤 짭짤한 맛이 특징입니다.
3. **불고기:** 얇게 썬 소고기를 간장, 참기름, 마늘 등으로 양념하여 구워 먹는 음식입니다.
4. **떡볶이:** 쫄깃한 떡을 고추장, 참치 등을 넣어 매콤하게 볶은 음식입니다.
5. **김치:** 한국의 대표적인 발효 음식으로, 배추, 무 등을 갈아 찌거나 삶아 김치통에 담아 먹습니다.
어떠신가요? 혹시 다른 질문이 있으신가요?
입력 : 일본 전통 음식 5가지 알려줘
네, 일본 전통 음식 5가지 알려드릴게요!
1. **스시:** 밥 위에 생선을 말아 올린 음식으로, 얇게 썬 생선과 밥, 그리고 다양한 튀김, 장아찌 등을 곁들여 먹습니다.
2. **라멘:** 면을 끓인 바탕에, 육수를 부어 지은 음식으로, 다양한 재료를 넣어 취향에 맞게 즐겨 먹습니다.
3. **우동:** 면을 끓인 바탕에, 육수를 부어 만든 면 요리로, 밥과 함께 먹거나 튀김과 함께 먹기도 합니다.
4. **타코야끼:** 노층으로 지은 따뜻한 라면처럼 보이는 일본식 튀김 요리로, 튀김옷을 입혀 맛을 더했습니다.
5. **오코노미야키:** 얇게 썬 고기와 재료를 튀김옷을 입혀 볶아 먹는 음식으로, 다양한 재료를 넣어 맛을 냅니다.
어떠신가요? 혹시 다른 질문이 있으시나요?
입력 : 앞에서 알려준 한국 전통 음식 5가지 순서까지 정확하게 다시 말해줄래?
네, 알겠습니다. 한국 전통 음식 5가지 순서를 다시 말씀드리겠습니다.
1. **비빔밥**
2. **갈비찜**
3. **불고기**
4. **떡볶이**
5. **김치**
이게 맞나요?
입력 : 프랑스 전통 음식 5가지 알려줘
네, 알겠습니다. 프랑스 전통 음식 5가지 알려드릴게요!
1. **크루아튼:** 프랑스어로 '마른 빵'이라고도 하며, 얇고 부드러운 빵으로, 꿀, 버터, 견과류 등을 곁들여 먹습니다.
2. **마카롱:** 솜털처럼 부드러운 젤리 형태의 디저트로, 다양한 맛과 색으로 출시됩니다.
3. **마카롱 시럽:** 마카롱을 굽는 과정에서 사용되는 시럽으로, 마카롱의 풍미를 더합니다.
4. **마카롱 퐁듀:** 마카롱에 곁들이는 부드러운 소금물로, 마카롱의 맛을 더욱 돋보이게 합니다.
5. **마카롱 퐁듀 시럽:** 마카롱 퐁듀를 끓일 때 사용하는 시럽으로, 마카롱의 맛을 더욱 돋보이게 합니다.
어떠신가요? 혹시 다른 질문이 있으시나요?
입력 : 이탈리아 전통 음식 5가지 알려줘
네, 알겠습니다. 이탈리아 전통 음식 5가지 알려드릴게요!
1. **파스타:** 이탈리아어로 '파스타'는 '면'을 의미합니다. 다양한 토핑과 소스와 함께 먹는 면 요리입니다.
2. **피자:** 이탈리아어로 '피자'는 '면'을 의미합니다. 얇은 면을 빵처럼 구워 만든 음식으로, 다양한 토핑을 올려 먹습니다.
3. **라자냐:** 얇은 파스타 면을 굽고 안에 치즈, 토마토 소스, 고기 등을 넣어 만듭니다. 겉은 바삭하고 속은 촉촉한 맛입니다.
4. **젤라또:** 이탈리아어로 '젤라또'는 'Ice Cream'를 의미합니다. 다양한 맛과 색깔로 출시되는 아이스크림입니다.
5. **트러플 파스타:** 이탈리아어로 '트러플'은 '마늘'을 의미합니다. 트러플 오일이나 트러플을 넣어 만든 파스타 요리입니다.
어떠신가요? 혹시 다른 질문이 있으신가요?
입력 : 오스트리아 전통 음식 5가지 알려줘
네, 알겠습니다. 오스트리아 전통 음식 5가지 알려드릴게요!
1. **슈니첼 (Schnitzel):** 얇게 썬 고기를 심게로 기름에 튀긴 음식으로, 빵가루를 입혀 먹습니다.
2. **하트 (Hert):** 닭고기를 얇게 썰어 튀긴 음식으로, 겉은 바삭하고 속은 촉촉한 것이 특징입니다.
3. **프라츠 (Sausage):** 오스트리아식 고기 소자으로, 꼬투리에 넣어 튀거나 구워 먹습니다.
4. **브로이클 (Brezel):** 빵에 젖은 빵을 굽는 음식으로, 겉은 바삭하고 속은 촉촉합니다.
5. **베이컨 젤라또 (Bacon Gelato):** 베이컨을 젤라또에 넣어 만든 디저트로, 독특한 풍미를 자랑합니다.
어떠신가요? 혹시 다른 질문이 있으신가요?
입력 : 노르웨이 전통 음식 5가지 알려줘
네, 알겠습니다. 노르웨이 전통 음식 5가지 알려드릴게요!
1. **스모크 salmon (훈제 연어):** 연어를 훈제 방식으로 조리하여 맛과 향을 더한 음식입니다.
2. **해산물 샐러드 (해산물 샐러드):** 신선한 해산물과 채소를 함께 넣어 만든 샐러드입니다.
3. **브뤼헐 롤 (브뤼헐 롤):** 얇게 펴서 만든 빵 안에 새우, 훈제 연어, 샐러드 등을 넣어 만든 롤입니다.
4. **요크셔 샐러드 (요크셔 샐러드):** 샐러드에 훈제 연어, 크림치즈, 고기 등을 넣어 만든 요크셔 주의의 대표적인 샐러드입니다.
5. **스모트라트 꼬치 (스모트라트 꼬치):** 훈제 연어, 꼬 stick, 샐러드, 빵 등을 꼬치에 꽂아 먹는 음식입니다.
어떠신가요? 혹시 다른 질문이 있으신가요?
입력 : 앞에서 알려준 일본 전통 음식 5가지 순서까지 정확하게 다시 말해줄래?
네, 알겠습니다. 일본 전통 음식 5가지 순서를 다시 말씀드리겠습니다.
1. **스시**
2. **라멘**
3. **우동**
4. **덮밥**
5. **가츠동**
어떠신가요? 혹시 다른 질문이 있으시나요?
입력 : exit
대화 종료
conversation.predict(input=q) 쓰기conversation.invoke(q)["response"] 로 접근
질문 자체는 전혀 잘못되지 않았습니다. 실제로 체인이 출력하는 프롬프트와 대화 기록을 보면 “두 번째 음식”을 맞추기에 충분한 정보가 있습니다.
그런데도 모델이 “비빔밥”이 아니라 “불고기”가 아닌지 혼동하는 이유는 다음과 같습니다.
원인 분석
1. LLM의 인식 및 위치 정보 한계
대부분의 LLM은 목록(리스트) 안에서 “첫 번째”, “두 번째” 같은 위치 정보를 정확히 추론하지 못하는 경우가 많습니다. 특히,
- 목록이 여러 줄로 나뉘거나,
- 답변 사이에 설명이 포함되거나,
- 사용된 포맷이 일관적이지 않으면
모델이 “두 번째”의 기준을 헷갈릴 수 있습니다.2. 대화 기록 포맷 문제
대화 기록이 영어 “Human”, “AI”로 저장될 때와 “사람”, “AI”로 나올 때 혼동이 생길 수 있고,
- 프롬프트 템플릿을 한국어로 재정의했을 때도 내부적으로 영어 기반 히스토리 포맷이 영향을 줄 수 있습니다.
- 일부 모델(특히 Gemma, Llama3 기본 버전)은 아직 한국어 포맷에서 정보 추출·위치 인식이 약한 편입니다.
3. 사용한 모델의 언어 특성
기본 Gemma3:1b 모델 등은 한국어에 최적화되어 있지 않아서,
- 위치 기반 추론, 목록 인식이 다소 부정확할 수 있습니다.
- 구체적으로 “비빔밥”과 “불고기” 중 두 번째가 어느 것인지 정확히 파악하지 못하는 사례가 빈번합니다.
실전 해결책
A. 프롬프트 개선
질문을 명확하게 해주면 더 나은 추론(답변) 확률이 올라갑니다.
예시로
“앞서 알려준 한국 전통 음식 목록에서 두 번째로 나온 음식이 무엇이었나요?”
같이 목록의 위치정보를 강조해서 질문해 보세요.B. 모델 교체/튜닝
- 한국어에 특화된 LLM(예: Bllossom-Korean, DeepSeek-R1 8B/70B 등)을 Ollama에서 설정하면 위치 추론력이 크게 향상됩니다.
- 특히 최근 공개된 Bllossom-Korean(3B/8B/70B) 모델은 “목록안 위치 연상”에 강점이 있어 추천합니다.
C. 목록 포맷 일관성
프롬프트에서 목록의 각 행이 "숫자. 음식명: 설명" 형식으로 일관되게 나오도록 조정하면 혼동이 줄어듭니다.
결론
- 질문은 잘못되거나 부족하지 않습니다.
- 현재 모델(Gemma3:1b 등)의 한계와 목록/포맷 인식 문제로 답변이 불안정하게 나오는 것입니다.
- 질문을 “두 번째 음식이 무엇인가요?” → “앞서 알려준 목록에서 2번 음식명을 알려주세요” 형태로 구체화하거나, 한국어 전용 튜닝 모델로 교체해 보십시오.
> Entering new ConversationChain chain...
Prompt after formatting:
The following is a friendly conversation between a human and an AI.
Current conversation:
Human: 3x2는 몇이야?
AI: 3x2는 6이야.
Human: 거기에 2를 곱하면?
AI:
> Finished chain.
3x2x2는 12이야.

지금 상황을 종합해보면 다음 두 가지 주요 문제가 교차하고 있습니다:
ConversationBufferWindowMemory 를 기본적으로 쓰면 history 변수가 메시지 객체 리스트가 되어야 하는데, 실제로는 문자열(string)이 들어가서 오류 발생 chat_memory=True 로 설정하려 하면 타입 오류가 나서 못 씀 이는 LangChain의 메모리 구조와 프롬프트 템플릿의 MessagesPlaceholder 간에 타입 불일치 문제이고, 버전별로 인터페이스가 다소 바뀌어 혼선이 발생하는 전형적인 케이스입니다.
ConversationBufferMemory (기본 문자열 메모리) + PromptTemplate 사용ConversationBufferMemory 는 단순 문자열 기반 메모리라서, MessagesPlaceholder 대신 일반 PromptTemplate에 {history} 문자열을 넣어 처리합니다.from langchain_community.llms import Ollama
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
from langchain.memory import ConversationBufferMemory
llm = Ollama(model="gemma3:1b")
memory = ConversationBufferMemory() # 문자열 형태의 메모리
prompt = PromptTemplate(
input_variables=["history", "input"],
template=(
"다음은 사람과 AI의 대화 내용입니다:\n"
"{history}\n"
"사람: {input}\n"
"AI:"
),
)
chain = LLMChain(llm=llm, prompt=prompt, memory=memory)
while True:
q = input("입력 : ")
if q.lower() == "exit":
print("종료합니다.")
break
res = chain.invoke({"input": q})
# res는 dict, 여기선 'text' 키로 결과 문자열 들어있음
print(res["text"])
print()
ConversationBufferMemory 는 history를 문자열로 관리합니다.PromptTemplate 에서 {history}도 문자열 변수로 처리해서 타입 에러 없습니다.MessagesPlaceholder 등 구조화된 메시지 기반 프롬프트는 ConversationBufferWindowMemory(chat_memory=True) 같이 엄격히 메시지 객체 리스트를 주는 메모리와만 쓸 수 있습니다.ChatMessageHistory 객체를 메모리에 주입하는 등 추가 작업이 필요한데, 초기 단계라면 위처럼 문자열 메모리 + 일반 프롬프트 템플릿 조합이 가장 무난합니다.prompt = ChatPromptTemplate.from_messages([
("system", "당신은 친절한 AI 챗봇입니다. AI는 기억을 참고해 항상 명확하게 한국어로 대답해주세요."),
MessagesPlaceholder(variable_name="history"), 대화 기록 삽입 위치 (중요)
("user", "{input}")
])
from langchain_community.llms import Ollama
from langchain.memory import ConversationBufferWindowMemory
from langchain.chains import ConversationChain
from langchain_core.prompts import PromptTemplate
llm = Ollama(model="gemma3:1b")
memory = ConversationBufferWindowMemory(k=2)
template = (
"다음은 사람과 AI의 대화 기록입니다:\n{history}\n"
"질문: {input}\n"
"AI는 기억을 참고해 한국어로 명확하게 대답하세요."
)
prompt = PromptTemplate(input_variables=["history", "input"], template=template)
conversation = ConversationChain(llm=llm, memory=memory, prompt=prompt)
while True:
q = input("입력 : ")
if q == "exit":
break
res = conversation.predict(input=q)
print(res)
print()
ConversationBufferMemory 또는 ConversationBufferWindowMemory)는 최근 대화들을 단순히 순서대로 누적합니다.ConversationSummaryMemory 같은 요약형 메모리를 쓰면 대화 히스토리가 정돈되어 모델이 핵심 정보를 더 명확히 인식