Ⅰ. 오전 수업
A. 1교시
1. 지난 시간 복습
2. Express
3. 실습: Express 프레임워크
B. 2교시
1. 필수 폴더 구조 / 파일 ★
2. app.js
C. 3교시
1. app.js (cont.)
Ⅱ. 오후 수업
A. 4교시
1. RAG: 웹 페이지 검색
2. RAG: 이미지 검색
3.
B. 5교시
1. RAG: 이미지 검색 (cont.)
C. 6교시
1. RAG: 이미지 검색 (cont.)
Ⅲ. CAREER UP
데이터베이스 특강
Ⅳ. 하루 돌아보기
동작하는 방식만 이해하기







| 라이브러리 | 프레임워크 |
|---|---|
| 기능만 가져다 사용 | 제공된 틀 안으로 들어가 주어진 규칙을 지켜가며 사용 |
| 개발자가 골라서 사용 사용의 주체: 개발자 | 정해진 틀대로 개발해야 함 개발의 주체: 틀(프레임워크) |
→ 관성적으로 내가 하던 습관들을 버리고 틀에 맞춰야 함 (e.g., 사용할 이미지 파일은 모두 public 폴더에 넣으세요)


TIP: 인터넷의 역사와 전길남 교수님
TIP 2: MPA vs. SPA
Express → MPA
REACT, VUE → SPA → views 폴더가 필요 없음!




npm i 명령어 실행하면 package.json 안에 적혀 있는 모듈 설치 가능extension: Material Icon Theme
- vscode explorer 디자인 커스텀

app.listen(3000);





__dirname: 현재 작업 중인 파일을 포함하는 폴더까지가 기준
# 웹페이지를 읽어주는 로더
loader = WebBaseLoader(web_path = ['https://n.news.naver.com/article/437/0000440499?sid=103'],
bs_kwargs= dict(
parse_only = bs4.SoupStrainer(
"div", attrs = {'class':['newsct_article _article_body','media_end_head_title',
'media_end_head_info_datestamp']}
)))
# 우리의 문서
docs = loader.load()
# RecursiveCharacterTextSplitter 객체 생성 (청크 최대 개수 1000, 겹치는 부분 100자)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000
, chunk_overlap=100
)
# 문서를 Chunk 단위로 분리하기 (docs)
chunks = text_splitter.split_documents(docs)
# 벡터화(임베딩) → FAISS DB 활용 → DB 저장
faiss_vec = FAISS.from_documents(documents=chunks, embedding=OpenAIEmbeddings())
# 검색기 설정 (유사 2개 데이터 활용)
retriever = faiss_vec.as_retriever(search_kwargs={'k':2}) # dict(k=2)로 써도 됨
# 단순 프롬프트 (해당 웹 페이지 정보에서 찾을 수 없다면 '주어진 정보에서 찾을 수 없습니다.' 출력)
prompt = PromptTemplate.from_template(
"""
당신은 질문-답변을 수행하는 정확하고 친절한 AI 어시스턴트입니다.
당신의 역할은 주어진 문맥(context)에서 주어진 질문(question)에 답하는 것입니다.
만약 주어진 문맥(context)에서 답을 찾을 수 없다면, 혹은 모른다면
'주어진 정보에서 찾을 수 없습니다.'라고 말해주세요.
한글로 답변해 주세요. 단, 기술적인 용어나 고유 이름은 번역하지 않고 그대로 사용해주세요.
{question}
{context}
"""
)
# llm 객체 생성
llm = ChatOpenAI(model="gpt-4o", temperature=0)
# 체인 객체 생성
# RunnablePaththrough: 사용자가 입력한 데이터를 변경하지 않고 그대로 전달
chain = ({"context": retriever, "question": RunnablePassthrough()} | prompt | llm | StrOutputParser())
result = chain.stream("광주의 날씨는 어떤가요?")
stream_response(result)
주어진 정보에서 찾을 수 없습니다.
result = chain.stream("광주의 기온은 어떤가요?")
stream_response(result)
주어진 정보에서 광주의 기온은 아침에 13도입니다.
result = chain.stream("위 기사는 언제 기사인가요?")
stream_response(result)
위 기사는 2025년 5월 12일에 작성된 기사입니다.

멀티모달(Multi Modal)
텍스트, 이미지, 음성, 비디오 등 여러 가지 유형의 데이터(모달리티)를 함께 학습하고 처리하는 인공지능(AI) 또는 기술
# 구글 마운트 및 경로 설정(파일 위치 변경)
%cd /content/drive/MyDrive/Colab Notebooks/LangChain
# api key 설정
import os
with open("./key/.openai_api_key",'r') as f:
api_key = f.read().strip()
os.environ["OPENAI_API_KEY"] = api_key
# 라이브러리 다운로드
!pip install -qU openai langchain-openai langchain langchain_community
!pip install -qU tiktoken pypdf chromadb faiss-cpu
!pip install -qU langchain-teddynote
!pip install -qU huggingface_hub langchain_huggingface
# 라이브러리 불러오기
from langchain_community.document_loaders import WebBaseLoader
import bs4
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain.vectorstores import FAISS, Chroma # Vector DB
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain_teddynote.messages import stream_response
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQAWithSourcesChain
from langchain_teddynote.models import MultiModal
# 모델 생성
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, max_tokens=400)
# 멀티모달 객체 생성 (시스템 메시지(role)와 사용자 메시지(입력)를 포함)
system_message = "당신은 재무분석 전문가입니다. 재무재표를 분석해 중요한 내용을 정리해 주세요."
human_message = "재무재표를 분석해 기업의 상태를 알려주세요."
multimodal_llm = MultiModal(
llm
, system_prompt=system_message
, user_prompt=human_message
)
# 이미지 전송 후 입력
answer = multimodal_llm.stream("./data/table01.png")
stream_response(answer)

재무재표를 분석하여 기업의 상태를 정리해 보겠습니다.
### 자산 분석
1. **유동자산**:
- 2019년: 8,349,633
- 2018년: 8,602,837
- 2017년: 8,377,679
- **변화**: 유동자산은 2018년에 비해 감소하였으나, 2017년 대비 증가했습니다.
2. **비유동자산**:
- 2019년: 18,677,453
- 2018년: 15,127,741
- 2017년: 12,770,457
- **변화**: 비유동자산은 매년 증가하고 있으며, 이는 기업의 장기적인 투자나 자산 확장을 나타냅니다.
### 총 자산
- 2019년: 27,027,086
- 2018년: 23,730,578
- 2017년: 21,148,136
- **변화**: 총 자산이 매년 증가하고 있어 기업의 성장세를 보여줍니다.
### 결론
- **성장성**: 총 자산과 비유동자산의 증가가 긍정적이며, 기업이 안정적으로 성장하고 있음을 나타냅니다.
- **유동성**: 유동자산의 감소는 단기적인 유동성에 대한 우려를 불러일으킬 수 있으므로, 관리가 필요합니다.
이러한 분석을 바탕으로 기업의 재무 상태는 전반적으로 긍정적이나, 유동성 관리에 주의가 필요합니다. 추가적인 재무 지표나 손익계산서 분석이 필요할 수 있습니다.
# 라이브러리 불러오기
import openai
import base64 # 이미지를 base64 문자열로 바꾸기 위한 라이브러리 (base64 → 이진 데이터를 텍스트로 바꿔주는 방식)
import matplotlib.pyplot as plt
import matplotlib.image as pimg # 이미지를 읽거나 저장하는 기능
# 텍스트 질문과 이미지 파일의 경로를 받아 모델에 질문을 보내고 답을 반환하는 함수 정의
# 이미지 + 텍스트 질의
def multimodal (query, image_path):
with open(image_path, "rb") as img_file:
image_bytes = img_file.read()
# gpt-4o 모델 사용
response = openai.chat.completions.create(
model="gpt-4o"
, messages=[
{
"role":"user"
, "content": [
{"type":"text", "text":query}
, {
"type": "image_url"
, "image_url": {
"url": "data:image/png;base64," + base64.b64encode(image_bytes).decode("utf-8")
}
}
]
}
]
)
return response.choices[0].message.content
# 그래프 이미지 넣어 출력
query="가장 많이 판매된 아이스크림 맛의 종류는 무엇인가요?"
print(multimodal(query, "./data/chart.png"))
가장 많이 판매된 아이스크림 맛은 "애플"입니다.
# 이미지 출력
img = pimg.imread("./data/chart.png")
plt.imshow(img)
plt.axis("off")
plt.show()

# chart2.png 파일을 활용하여 가장 많이 사용되는 프로그래밍 언어를 출력
query="가장 많이 사용되는 프로그래밍 언어는 무엇인가요?"
print(multimodal(query, "./data/chart2.png"))
가장 많이 사용되는 프로그래밍 언어는 C입니다. 32%로 가장 큰 비율을 차지하고 있습니다.
img = pimg.imread("./data/chart2.png")
plt.imshow(img)
plt.axis("off")
plt.show()

def multimodal (query, image_path):
with open(image_path, "rb") as img_file:
image_bytes = img_file.read()
# gpt-4o 모델 사용
response = openai.chat.completions.create(
model="gpt-4o"
, messages = [
{
"role":"system"
, "content":"당신은 문자 인식 전문가입니다. 이미지에서 텍스트를 추출하세요"
}
, {
"role": "user"
, "content": [
{"type": "text", "text": query}
, {
"type": "image_url"
, "image_url": {
"url": "data:image/png;base64," + base64.b64encode(image_bytes).decode('utf-8')
}
}
]
}
]
,
)
return response.choices[0].message.content
# 이미지 출력
img = pimg.imread("./data/car_number.png")
plt.imshow(img)
plt.axis("off")
plt.show()
# 그래프 이미지 넣어 출력
query="이미지의 번호판 글자를 읽어오세요."
print(multimodal(query, "./data/car_number.png"))

이미지의 번호판은 "112고 8128"입니다.
def multimodal (query, image_path):
with open(image_path, "rb") as img_file:
image_bytes = img_file.read()
# gpt-4o 모델 사용
response = openai.chat.completions.create(
model="gpt-4o"
, messages = [
{
"role":"system"
, "content":"당신은 문자 인식 전문가입니다. 이미지에서 텍스트를 추출하세요"
}
, {
"role": "user"
, "content": [
{"type": "text", "text": query}
, {
"type": "image_url"
, "image_url": {
"url": "data:image/png;base64," + base64.b64encode(image_bytes).decode('utf-8')
}
}
]
}
]
,
)
return response.choices[0].message.content
# 이미지 출력
img = pimg.imread("./data/load_sign.png")
plt.imshow(img)
plt.axis("off")
plt.show()
# 그래프 이미지 넣어 출력
query="이미지의 도로에 적힌 글자를 읽어주세요."
print(multimodal(query, "./data/load_sign.png"))

도로에 적힌 글자는 "진입금지"입니다.
# 이미지 출력
img = pimg.imread("./data/load_sign2.jpg")
plt.imshow(img)
plt.axis("off")
plt.show()
# 그래프 이미지 넣어 출력
query="이미지의 도로에 적힌 글자를 읽어주세요."
print(multimodal(query, "./data/load_sign2.jpg"))

이미지의 도로에 적힌 글자는 "어린이보호구역"입니다.
def multimodal (query, image_path):
with open(image_path, "rb") as img_file:
image_bytes = img_file.read()
# gpt-4.1 모델 사용
response = openai.chat.completions.create(
model="gpt-4.1"
, messages = [
{
"role":"system"
, "content":"당신은 대한민국 도로 위 표지 이미지 인식 전문가입니다. 도로나 표지판에 보이는 노면 표시에 대해 한국 도로교통법에 기반하여 정확하게 답변하세요."
}
, {
"role": "user"
, "content": [
{"type": "text", "text": query}
, {
"type": "image_url"
, "image_url": {
"url": "data:image/jpg;base64," + base64.b64encode(image_bytes).decode('utf-8')
}
}
]
}
]
,
)
return response.choices[0].message.content
# 이미지 출력
img = pimg.imread("./data/load_sign3.jpg")
plt.imshow(img)
plt.axis("off")
plt.show()
# 그래프 이미지 넣어 출력
query="이미지의 도로에 보이는 노면 표시는 무엇을 의미하나요?"
print(multimodal(query, "./data/load_sign3.jpg"))

이미지에 보이는 **마름모(◇) 모양의 노면 표지**는 대한민국 도로교통법상 **"횡단보도 또는 어린이 보호 구역(스쿨존)이나 차로가 좁아지는 지점 등이 가까워짐을 알리는 예고 표시"**입니다.
주요 의미는 다음과 같습니다:
- **횡단보도가 가까워짐을 예고**: 운전자에게 앞에 횡단보도가 있음을 미리 경고하여, 감속 및 주의를 촉구합니다.
- **어린이 보호구역 예고**: 어린이 보호구역이 근접함을 사전에 알려 사고 예방에 도움을 줍니다.
- 그 외에도 **도로 폭이 좁아지거나, 주의가 필요한 구간**이 가까울 때도 사용할 수 있습니다.
이 표지가 보이면 속도를 줄이고, 주변 상황을 잘 살펴보아야 합니다.
cf. gpt-4o로 하면 다른 답변이 나옴
def multimodal (query, image_path):
with open(image_path, "rb") as img_file:
image_bytes = img_file.read()
# gpt-4o 모델 사용
response = openai.chat.completions.create(
model="gpt-4o"
, messages = [
{
"role":"system"
, "content":"당신은 대한민국 도로 위 표지 이미지 인식 전문가입니다. 도로나 표지판에 보이는 노면 표시에 대해 한국 도로교통법에 기반하여 정확하게 답변하세요."
}
, {
"role": "user"
, "content": [
{"type": "text", "text": query}
, {
"type": "image_url"
, "image_url": {
"url": "data:image/jpg;base64," + base64.b64encode(image_bytes).decode('utf-8')
}
}
]
}
]
,
)
return response.choices[0].message.content
# 이미지 출력
img = pimg.imread("./data/load_sign3.jpg")
plt.imshow(img)
plt.axis("off")
plt.show()
# 그래프 이미지 넣어 출력
query="이미지의 도로에 보이는 노면 표시는 무엇을 의미하나요?"
print(multimodal(query, "./data/load_sign3.jpg"))

이미지의 도로에 보이는 노면 표시는 '버스전용차로'를 나타냅니다. 이 마름모 모양의 표시는 버스전용차로가 시작될 것임을 사전에 알리는 역할을 하며, 해당 차로에서는 버스 및 허가된 차량만 통행할 수 있습니다. 일반 차량은 이 차로를 사용해서는 안 됩니다.

| 실행 순서 | 데이터 조작어(DML) | 설명 |
|---|---|---|
| 5 | SELECT | 출력하고 싶은 컬럼만 작성하기 |
| 1 | FROM | 데이터를 가져올 테이블 입력 |
| 2 | WHERE | 원하는 튜플만 가져오도록 필터링(조건문) |
| 3 | GROUP BY | 특정 컬럼을 기준으로 그룹화 |
| 4 | HAVING | 그룹화 상테의 데이터를 필터링 |
| 6 | ORDER BY | 특정 컬럼으로 정렬하기 |
SELECT 직원ID, 패스워드, 이름, 성별, 연봉 -- ③
FROM 직원 -- ①
WHERE 성별='남'; -- ②《실행 순서》









*(ASTERISK)SELECT *
FROM 직원;
-- 실무에서는 실행하지 않아도 테이블 안에 어떤 컬럼이 있는지 모두 볼 수 있는 걸 선호해서 아래와 같이 씀
SELECT 직원ID, 패스워드, 이름, 성별, 나이, 입사일시, 주민등록번호, 연봉, 부서ID
FROM 직원;


DISTINCT: 출력할 컬럼 정보에서 중복 값을 없애 주는 키워드



AS(ALIAS): 별칭, 통칭
SELECT 직원ID EMP_ID

SELECT *
FROM 직원
WHERE 부서ID='D001';FROM 직원WHERE 부서ID='D001'
SELECT *
조건1 AND 조건2: 조건1, 조건2 모두 TRUE여야 TRUE 반환. 하나라도 FALSE면 FALSE 반환조건1 OR 조건2: 조건1, 조건2 중 하나만 TRUE여도 TRUE 반환. 모두 FALSE면 FALSESELECT *
FROM 직원
WHERE 직원ID='A0001' OR 직원ID='A0005' OR 직원ID='A0007';
-- ↓
SELECT *
FROM 직원
WHERE 직원ID IN ('A0001', 'A0005', 'A0007');
IN 연산자BETWEEN 연산자WHERE 직원ID BETWEEN 'A0001' AND 'A0004'LIKE 연산자_와 %를 이용해 다양한 결과 출력 가능