Ⅰ. 오전 수업
A. 1교시: Node.js 개념 익히기
1. 웹이란 무엇인가
2. Node.js
3. 노드의 특성
4. Node.js 설치
B. 2교시: 노드 기초 실습
1. 모듈
2. http 모듈
C. 3교시: Node.js
1. http 모듈 (cont.)
2. req
Ⅱ. 오후 수업
A. 4교시: LangChain
1. 지난 시간 복습
2. Zero-Shot Prompting 정리
3. 퓨샷 프롬프팅
B. 5교시: LangChain
1. 퓨샷 프롬프팅 (cont.)
2. ExampleSelector
C. 6교시: LangChain
1. Callbacks: 스트리밍
Ⅲ. CAREER UP
미니 프로젝트 전체 발표
Ⅳ. 하루 돌아보기
내 손 안의 Web Application을 만들어보자!



네이버 로그인 후 화면 예시로 들어주신 거 기억하기: 과거에는 개발자가 모든 페이지를 하나하나 다 만들어야 했다면, 현재는 개발자가 껍데기만 만들고 실제 안의 값을 갈아끼우는 건 컴퓨터가 알아서 자동으로 함 (HTML 파일 하나를 가지고 컴퓨터가 값을 끼워 넣어서 그 데이터를 수많은 유저에게 전송)

web browser 제어 → Computer 제어
JavaScript Node.js
서비스의 특징에 맞는 개발이 필요! → 24시간 돌아가는 서버가 필요하면 Node.js는 X
동기와 비동기 특징
- 동기 통신:
- 많은 양의 데이터를 처리
- 단순 업무 처리에는 부족한 부분이 있음
- 앞쪽부터 순차적으로 처리해 뒤쪽은 기다리게 만들 수 있음 → 티켓팅 서비스
- 비동기 통신:
- 업무를 한 번에 받아서 처리 & 빨리 끝나는 것부터 처리 → 짧은 업무에 효율적
- 짧은 업무에는 효율적이지만 많은 업무를 처리할 때는 불편 (코드 순서 짜는 게 어려움)
- '빨리 끝나는 것'은 컴퓨터가 스스로 판단 → 연관성이 있는 코드라면(앞선 결과를 뒤에서 쓰는 경우) 비동기로 작업 시 오류 → 이럴 때는 동기로 따로 지정
| Blocking(동기) | Non-Blocking(비동기) |
|---|---|
| 설계가 매우 간단하고 직관적 | 결과가 주어지는데 시간이 걸리더라도 그 시간 동안 다른 작업 가능 |
| 결과가 주어질 때까지 대기 → 시간 효율이 좋지 못함 | 동기보다 코드 짜기 복잡 |




Node.js is a cross-platform, open-source JavaScript runtime environment that can run on Windows, Linux, Unix, macOS, and more. Node.js runs on the V8 JavaScript engine, and executes JavaScript code outside a web browser. Node.js lets developers use JavaScript to write command line tools and server-side scripting.
node -v 실행 시 아래와 같이 나오면 OK
let num1 = 10;
let num2 = 20;
let result = num1 + num2;
module.exports = {result};
module.exports = {result};const {result} = require("./0_add_module");
// './'는 내 위치라는 뜻. 값을 가져올 때는 중괄호로 받는다.
// 모델을 불러올 때 모델명 뒤 확장자(.js)는 생략 가능
console.log(result)


설치 반영이 안 된 경우 아래와 같이 오류가 뜰 수 있음 (vscode 다 껐다가 다시 켜보기
const http = require("http");
http.createServer((req,res)=>{
console.log("누군가 접속했어요!!");
}).listen(3000);
const http = require("http");node .\1_http모듈.js





서버를 열어서 내 컴퓨터에 접속한 사람의 ip를 출력해보기
const http = require("http");
npm i request-ip 명령어로 설치하기설치가 안 돼요😭
- 보안상 막아놓았기 때문
- 해결 방법: powershell 관리자 권한으로 실행 → Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned 복사 → powershell 창에서 마우스 우클릭 → Get-ExecutionPolicy -List 확인
CurrentUser가 RemoteSigned인 걸 확인 후 vscode로 돌아가서 다시 설치해보면 잘 됨

let ip = req_ip.getClientIp(req);
console.log(ip);
llm_model = ChatOpenAI(model_name="gpt-4o-mini", temperature=0, max_tokens=500)str_output = StrOutputParser()prompt = ChatPromptTemplate.from_template("{words}를 영어로 번역해 주세요.") : 단순 템플릿 지정.from_messages 사용chain = prompt | llm_model | str_outputresult = chain.invoke({"words": "사느냐 죽느냐 그것이 문제로다. 가혹한 운명의 화살과 돌멩이를 견디는 것 또는 고난의 바다에 무기를 들고 맞서는 것, 어떤 것이 더 고귀한가?"})print(result): String 출력 파서를 사용했기 때문에 그냥 print하면 됨from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, FewShotPromptTemplate, PromptTemplate
from langchain_core.output_parsers import StrOutputParser
# 예시
examples1 = [
{"input": "happy", "output": "sad"}
, {"input": "tall", "output": "short"}
, {"input": "sunny", "output": "rainy"}
, {"input": "surprised", "output": "calm"}
, {"input": "dry", "output": "humid"}
, {"input": "hot", "output": "cold"}
, {"input": "satisfied", "output": "dissatisfied"}
]
# 모델: 앞서 사용한 llm_model 그대로 사용
# 예시 데이터 표현을 위한 기본 템플릿 생성
prompt = PromptTemplate.from_template("입력: {input} \n 출력: {output}")
AttributeError: 'ChatPromptTemplate' object has no attribute 'get'# 퓨샷 프롬프트 템플릿 생성
fewshot_prompt = FewShotPromptTemplate(
examples=examples1
, example_prompt=prompt
, prefix="""입력의 반대말을 출력합니다.
반드시 사전에 등장하는 반대말만 출력하세요.
사전에 반대말이 존재하지 않으면 반대말이 없다고 출력해 주세요."""
, suffix="입력: {input}" # 전체 프롬프트 마지막에 추가되는 텍스트
, input_variables=["input"] # 퓨샷 프롬프트 탬플릿에서 사용되는 입력 변수
)
# 체인 연결
chain = fewshot_prompt | llm_model | str_output
# 체인 실행
chain.invoke({"input":"어둡다"})
출력: 밝다
prefix: 전체 프롬프트 앞쪽에 추가될 설명문suffix: 전체 프롬프트 마지막에 추가되는 텍스트. 어떤 값으로 입력을 해야 하는지를 작성해 주는 부분# 예시
examples2 = [
{
"question": "상품명: '에어프라이어', 특징: '간편 조리, 1300W 출력', 가격: '120,000원'일 때 단계별 대본 가이드 예시를 보여주세요.",
"answer": (
"1) 인사 및 상품 소개: \"안녕하세요 여러분! 오늘은 집에서 간편하게 튀김 요리를 할 수 있는 '에어프라이어'를 소개합니다!\"\n"
"2) 주요 특징 설명: \"1300W의 강력한 출력으로 바삭함을 살려주고, 조리 시간이 단축됩니다.\"\n"
"3) 사용 시나리오 제시: \"감자튀김, 치킨너겟, 채소구이까지 모두 간편하게 조리해보세요.\"\n"
"4) 가격 및 혜택 안내: \"지금 구매하시면 120,000원에 무료 배송과 1년 무상 A/S가 제공됩니다.\"\n"
"5) 콜 투 액션: \"아래 구매 버튼을 눌러 할인 혜택을 놓치지 마세요!\""
)
},
{
"question": "상품명: '블루투스 헤드폰', 특징: '노이즈 캔슬링, 20시간 배터리', 가격: '80,000원'일 때 단계별 대본 가이드 예시를 보여주세요.",
"answer": (
"1) 인사 및 관심 유도: \"안녕하세요! 오늘은 음악 감상에 최적화된 '블루투스 헤드폰'을 소개드립니다.\"\n"
"2) 기능 강조: \"능동형 노이즈 캔슬링으로 외부 소음을 완벽 차단, 최대 20시간 배터리로 하루 종일 사용 가능해요.\"\n"
"3) 데모 시연 제안: \"실제 사용해볼까요? 소음 많은 카페에서도 깨끗한 사운드를 경험해보세요.\"\n"
"4) 특별 할인 안내: \"오늘 한정 5% 할인 쿠폰 코드: LIVE5 적용하세요!\"\n"
"5) 구매 유도 문구: \"지금 바로 구매 버튼을 클릭하고, 편안한 음악 시간을 즐겨보세요!\""
)
}
]
# 예시용 프롬프트 템플릿 생성 → 단순 프롬프트 템플릿 사용
prompt = PromptTemplate.from_template("Q: {question} \n A: {answer}")
# 퓨샷 프롬프트 템플릿 생성
fewshot_prompt = FewShotPromptTemplate(
examples=examples2
, example_prompt=prompt
, prefix="구어체로 답변해 주세요."
, suffix="Q: {input} \n A:"
, input_variables=["input"]
)
# 체인 구성 및 호출
chain = fewshot_prompt | llm_model | StrOutputParser()
res = chain.invoke({"input": "상품명: '무선 진공청소기', 특징: '강력 흡입, 30분 연속 사용', 가격: '200,000원'일 때 단계별 대본 가이드 예시를 보여주세요."})
print(res)
# 체인 구성 및 호출
chain = fewshot_prompt | llm_model | StrOutputParser()
res = chain.invoke({"input": "상품명: '무선 진공청소기', 특징: '강력 흡입, 30분 연속 사용', 가격: '200,000원'일 때 단계별 대본 가이드 예시를 보여주세요."})
print(res)
1) 인사 및 상품 소개: "안녕하세요 여러분! 오늘은 집안 청소를 혁신적으로 바꿔줄 '무선 진공청소기'를 소개합니다!"
2) 주요 특징 설명: "이 청소기는 강력한 흡입력으로 먼지와 이물질을 쉽게 제거해주며, 30분 동안 연속으로 사용할 수 있어요. 무선이기 때문에 이동이 편리하고, 어떤 공간에서도 자유롭게 사용할 수 있습니다."
3) 사용 시나리오 제시: "거실, 주방, 심지어 자동차 안까지! 다양한 장소에서 손쉽게 청소해보세요. 바닥은 물론 카펫과 소파까지 완벽하게 청소할 수 있습니다."
4) 가격 및 혜택 안내: "지금 구매하시면 200,000원에 무료 배송과 2년 무상 A/S가 제공됩니다. 또한, 첫 구매 고객에게는 추가 청소 도구 세트를 드립니다!"
5) 콜 투 액션: "지금 바로 아래 구매 버튼을 클릭하고, 집안 청소의 새로운 경험을 시작해보세요! 놓치지 마세요!"
SemanticSimilarityExampleSelector# 입력과 예시의 코사인 유사도를 계산
from langchain.prompts.example_selector import SemanticSimilarityExampleSelector
# 텍스트 임베딩: 텍스트 데이터를 벡터화하는 과정(수치화)
from langchain_openai import OpenAIEmbeddings
# 수치화한 벡터 데이터를 저장하는 저장소 → 유사도를 비교하는 알고리즘도 제공
from langchain.vectorstores import FAISS
# 첫 번째 예시: 회의록 작성, 두 번째 예시: 요약
examples3 = [
{
"input": (
"2023년 12월 25일, XYZ 회사의 마케팅 전략 회의가 오후 3시에 시작되었다. "
"회의에는 마케팅 팀장인 김수진, 디지털 마케팅 담당자인 박지민, 소셜 미디어 관리자인 이준호가 참석했다. "
"회의의 주요 목적은 2024년 상반기 마케팅 전략을 수립하고, 새로운 소셜 미디어 캠페인에 대한 아이디어를 논의하는 것이었다. "
"팀장인 김수진은 최근 시장 동향에 대한 간략한 개요를 제공했으며, 이어서 각 팀원이 자신의 분야에서의 전략적 아이디어를 발표했다."
),
"answer": (
"회의록: XYZ 회사 마케팅 전략 회의\n"
"일시: 2023년 12월 25일\n"
"장소: XYZ 회사 회의실\n"
"참석자: 김수진 (마케팅 팀장), 박지민 (디지털 마케팅 담당자), 이준호 (소셜 미디어 관리자)\n\n"
"1. 개회\n"
" - 김수진 팀장의 개회사\n"
"2. 시장 동향 개요 (김수진)\n"
" - 최근 시장 동향 분석\n"
"3. 디지털 마케팅 전략 (박지민)\n"
" - SEO 최적화 및 온라인 광고 방안\n"
"4. 소셜 미디어 캠페인 (이준호)\n"
" - 인플루언서 마케팅 제안\n"
"5. 종합 논의\n"
" - 예산 및 자원 배분\n"
"6. 마무리\n"
" - 다음 회의 일정 및 회의록 배포 담당자 지정"
)
},
{
"input": (
"이 문서는 '지속 가능한 도시 개발을 위한 전략'에 대한 20페이지 분량의 보고서입니다. "
"보고서는 지속 가능한 도시 개발의 중요성, 현재 도시화의 문제점, 그리고 지속 가능한 개발 전략을 다루고 있습니다. "
"여러 국가의 성공 사례와 얻은 교훈도 포함되어 있습니다."
),
"answer": (
"문서 요약: 지속 가능한 도시 개발 전략 보고서\n\n"
"- 중요성: 사회적·경제적·환경적 이점 강조\n"
"- 문제점: 환경 오염, 자원 고갈, 불평등 분석\n"
"- 전략: 친환경 건축, 대중교통 개선, 에너지 효율성 증대\n"
"- 사례 연구: 코펜하겐, 요코하마 성공 사례\n"
"- 교훈: 다각적 접근, 지역사회 협력, 장기 계획 필요"
)
}
]
# 유사도 비교 후 샘플을 선택하는 클래스 생성
example_selector = SemanticSimilarityExampleSelector.from_examples(
examples3 # 유사도 검사를 진행할 예시 데이터
, OpenAIEmbeddings() # 임베딩 도구
, FAISS #저장소
, k=1 # 선택할 예시 개수
)
# 예시 데이터를 프롬프트로 변경해주는 프롬프트 템플릿
prompt = PromptTemplate.from_template(
"""
문서: {input}
결과: {answer}
"""
)
# 퓨삿 프롬프트 템플릿
fewshot_prompt = FewShotPromptTemplate(
example_selector = example_selector
, example_prompt = prompt
, prefix = "입력된 문서에 대해 적절한 형식으로 데이커를 생성하세요"
, suffix = "문서 : {input} \n 결과:"
, input_variables = ["input"]
)

# 체인 연결
chain = fewshot_prompt | llm_model | str_output
# 체인 호출
rs = chain.invoke({
"input": (
"2025년 5월 20일 오후 4시부터 약 90분간, 서울 본사 3층 대회의실에서 "
"신제품 출시 킥오프 회의가 진행되었습니다. 회의에는 개발팀장 김민준, "
"디자인팀장 이지은, 영업팀장 박성호, 마케팅팀장 오세훈, 품질관리 담당 최유리가 참석했으며, "
"주요 안건은 제품 디자인 확정, 출시 일정, 마케팅 채널 전략, 리스크 관리 방안이었습니다."
)
})
print(res)
회의록: 신제품 출시 킥오프 회의
일시: 2025년 5월 20일 오후 4시
장소: 서울 본사 3층 대회의실
참석자: 김민준 (개발팀장), 이지은 (디자인팀장), 박성호 (영업팀장), 오세훈 (마케팅팀장), 최유리 (품질관리 담당)
1. 개회
- 김민준 팀장의 개회사
2. 제품 디자인 확정 (이지은)
- 최종 디자인안 발표 및 논의
3. 출시 일정 (박성호)
- 제품 출시 일정 및 단계별 계획
4. 마케팅 채널 전략 (오세훈)
- 마케팅 채널 및 캠페인 계획
5. 리스크 관리 방안 (최유리)
- 잠재적 리스크 분석 및 대응 방안
6. 종합 논의
- 각 팀의 의견 수렴 및 조율
7. 마무리
- 다음 회의 일정 및 회의록 배포 담당자 지정
rs = chain.invoke({
"input": (
"이 문서는 '머신러닝 모델 성능 최적화 기법'에 대한 15페이지 분량의 기술 보고서입니다. "
"보고서는 하이퍼파라미터 튜닝(그리드 서치, 베이지안 최적화), 데이터 증강(회전·크롭·색상 보정), "
"앙상블 기법(배깅·부스팅·스태킹), 모델 경량화(지식 증류·양자화), 성능 평가(Accuracy·Precision·Recall·F1) 등을 상세히 다룹니다."
)
})
print(rs)
문서 요약: 머신러닝 모델 성능 최적화 기법 보고서
- 하이퍼파라미터 튜닝: 그리드 서치, 베이지안 최적화 방법 설명
- 데이터 증강: 회전, 크롭, 색상 보정 기법 소개
- 앙상블 기법: 배깅, 부스팅, 스태킹 기법의 장단점 분석
- 모델 경량화: 지식 증류, 양자화 기법의 적용 및 효과
- 성능 평가: Accuracy, Precision, Recall, F1 스코어의 정의 및 활용 방법
StreamingStdOutCallbackHandler()from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
# 퓨샷 프롬프팅을 통한 결과 확인
# 예시
examples4 = [
{"input" : "Agent", "output" : "프롬프트를 통해 도구를 호출하며 워크플로우를 제어할 수 있는 자동화 컴포넌트"}
, {"input" : "Chain", "output" : "여러 프롬프트와 모델 호출을 순차적으로 연결해 파이프라인을 구성하는 구조"}
, {"input" : "PromptTemplate", "output" : "입력 변수를 포함해 모델에 보낼 프롬프트 형식을 정의하는 클래스"}
]
# 예시 프롬프트 템플릿
prompt = PromptTemplate.from_template("키워드: {input} \n 설명: {output}")
# 퓨샷 프롬프트 템플릿
fewshot_prompt = FewShotPromptTemplate(
examples = examples4
, example_prompt = prompt
, prefix = "입력된 키워드에 대해 다음 예시처럼 설명하세요."
# '다음'이라는 말이 가능한 이유: prefix + prompt + suffix순으로 텍스트가 연결되기 때문
, suffix = "키워드: {input} \n 설명: "
, input_variables = ["input"]
)
# 핸들러 객체 생성
stream_handler = StreamingStdOutCallbackHandler()
# 핸들러가 포함된 모델 객체 생성
stream_model = ChatOpenAI(
model_name="gpt-4o-mini"
, temperature = 0
, streaming = True
, callbacks = [stream_handler]
)
- Callbacks to add to the run trace. (default는 None)
- callbacks 파라미터는 추가적으로 원하는 동작이 있을 때만 명시적으로 넣어주는 선택적 요소
- 모델 객체를 생성할 때 사용
- 모델의 실행 과정 중 특정 이벤트가 발생할 때 자동으로 추가 작업을 수행할 수 있도록 해주는 함수(혹은 객체)들의 목록을 지정하는 역할
- 모델이 동작하는 중간과정에 추가 동작(로깅, 스트리밍 출력, 체크포인트 저장 등)을 자동화·확장해주는 역할
- LangChain 및 딥러닝 프레임워크에서의 callbacks 역할
- 실행 중간 상태 모니터링: 모델이 예측/추론/스트리밍할 때, 중간 결과나 토큰 생성, 에러 발생 등 다양한 이벤트를 실시간으로 감지해서, 이를 콘솔에 출력하거나 별도로 저장/로깅/시각화합
- 자동화 및 로깅: 학습 중 체크포인트 저장, EarlyStopping(성능 개선 없으면 멈춤), TensorBoard 시각화 등의 다양한 자동화 기능을 구현할 때 사용
- 사용자 정의 콜백 구현 가능: 콜백을 직접 만들어서, 필요한 이벤트에 맞춰 코드를 실행
- 콜백 핸들러가 없을 때
- 특별한 이벤트 처리 없이, 모델의 가장 기본적인 반환값(결과만) 받아서 사용
# 체인 생성
chain = fewshot_prompt | stream_model | StrOutputParser()
# 체인 호출
res=chain.invoke("example_selector")
print(res)
키워드: example_selector
설명: 주어진 입력에 가장 적합한 예제를 선택하여 모델의 응답 품질을 향상시키는 기능 또는 컴포넌트.키워드: example_selector
설명: 주어진 입력에 가장 적합한 예제를 선택하여 모델의 응답 품질을 향상시키는 기능 또는 컴포넌트.
왜 두 번 출력되지?
- StreamingStdOutCallbackHandler는 응답을 생성하는 즉시 터미널(콘솔)에 한 번 출력함
- 그런데 지금 우리는 체인에 'StrOutputParser()'가 붙어 있고 print()로 출력도 하고 있음 → chain.invoke()의 결과(예: res.content)를 또 print하면, 똑같은 답이 한 번 더 표준출력에 표시됨
- 즉, 한 번은 스트리밍 콜백으로, 한 번은 후처리 print로 중복 출력
- 실시간 스트리밍이 중요하다면, chain.invoke() 후 따로 res.content를 print하지 않아야 함!
- StreamingStdOutCallbackHandler를 사용하면 모델이 생성하는 토큰들을 실시간으로 콘솔에 출력해 줍니다. 그런데 여기서 print(res.content)를 함께 쓰면, 스트리밍 출력과 함께 결과가 한 번 더 출력되어 두 번 나오는 겁니다.
- chain.invoke() 결과 (res)는 모델 응답을 담고 있습니다. 스트리밍 핸들러가 이미 콘솔에 실시간 출력을 하므로, print(res.content)를 하면 최종 결과를 다시 한 번 출력하게 됩니다. 그래서 둘 중 하나만 사용해야 중복 출력이 안 됩니다.
| 상황 | 코드 실행 방식 | 출력 방식 |
|---|---|---|
| 스트리밍 출력 필요 시 | StreamingStdOutCallbackHandler를 쓰고, print(res.content)는 하지 않음 | 토큰 단위 실시간 출력됨 |
| 최종 결과만 단 한 번 보고 싶을 때 | 스트리밍 핸들러 빼고 streaming=False, print(res.content) 사용 | 완성된 결과 한 번 출력 |
# 체인 생성
chain = fewshot_prompt | stream_model
# 체인 호출
res=chain.invoke("example_selector") # 이 호출시 토큰 하나씩 실시간 출력됨
# stream_handler만 사용한다면, print(res)를 호출하지 않아야 함
# print(res) 하면 한 번은 핸들러로 스트리밍, 한 번은 최종 결과 전체 출력을 해서 총 두 번 출력됨
# StreamingStdOutCallbackHandler를 사용하면 실시간 출력도 같이 되고, invoke() 자체도 출력 결과를 반환하기에 중복 출력이 발생
- 다른 핸들러도 있음: FinalStreamingStdOutCallbackHandler
- StreamingStdOutCallbackHandler → 중복 표출(토큰마다, 결과)
- 토큰 생성 시마다 실시간으로 바로 표준출력에 출력
- 코랩의 출력 셀은 실시간 출력이 제한적이나, 토큰별 출력을 여러 번 발생시키면서 출력 업데이트를 어느 정도 압축해 표시하므로 어느 정도 스트리밍 효과가 있음
- FinalStreamingStdOutCallbackHandler → 한 번만(최종 결과)
- 토큰을 내부적으로 버퍼링 후, 최종 완성된 응답이 완성되는 시점에 한 번만 출력
- 콘솔 환경에서 실행해야 streaming 출력이 정상적으로 작동
- Jupyter 등에서는 streaming 출력이 제대로 안 될 수 있음
- 코랩의 출력 셀은 표준 터미널 콘솔 환경과 달리 즉각적인 표준출력 스트림 반영이 제한적이기 때문에 StreamingStdOutCallbackHandler로 하는 실시간 토큰 스트리밍 출력이 제대로 작동하지 않는 경우가 많음
- StreamingStdOutCallbackHandler는 코랩 환경에서 토큰 단위로 표준출력에 실시간으로 출력하는 구조라, 코랩 셀에서 어느 정도 작동할 수 있습니다. 반면, FinalStreamingStdOutCallbackHandler는 스트리밍 도중 중간 토큰 출력 없이 최종 결과만 한 번에 출력하도록 설계된 핸들러라서 코랩 셀의 출력 뷰 특성상, 스트리밍 효과가 전혀 없으며, 최종 결과가 한꺼번에 출력되고 결과적으로 "스트리밍이 안 되는 것처럼" 보이게 됩니다.
- 코랩처럼 출력이 버퍼링되고 묶여서 출력되는 환경에서 스트리밍 효과가 실질적으로 구현되지 않음
- 별도 print(res) 없이도 한 번만 결과가 표출됨










1. 실시간 딜리버리 시장 선점 및 확대



