<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Eunhaa Tech Developer Blog</title>
    <link>https://eun-haa.tistory.com/</link>
    <description>공부하는 개발자의 테크 블로그</description>
    <language>ko</language>
    <pubDate>Sun, 21 Jun 2026 05:47:16 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>EunHaa</managingEditor>
    <item>
      <title>검색 증강 생성(RAG) - PDF RAG 학습 앱 (2)</title>
      <link>https://eun-haa.tistory.com/34</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이전 시간에 배운 RAG 기본 개념을 이용해 PDF RAG 분석 애플리케이션을 만들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 gradio 프레임워크를 이용해서 Python에서만 구성해볼 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 gradio로 화면단만 구성한다. 사용된 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1781965589785&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import gradio as gr

# 화면 구성

with gr.Blocks() as app:
    gr.Markdown(&quot;# PDF RAG 학습 앱&quot;)

    with gr.Tabs():
        with gr.Tab(&quot;1단계 - PDF &amp;amp; Chunk 확인&quot;):
            # 파일 업로드 컴포넌트
            pdf_input = gr.File(label=&quot;PDF 업로드&quot;, file_types=[&quot;.pdf&quot;])
            btn1 = gr.Button(&quot;분석 시작&quot;)

            textConfig1 = [
                {&quot;label&quot;: &quot;총 페이지수&quot;, &quot;lines&quot;: 1},
                {&quot;label&quot;: &quot;첫 페이지 내용&quot;, &quot;lines&quot;: 20},
                {&quot;label&quot;: &quot;총 chunk 수&quot;, &quot;lines&quot;: 1},
                {&quot;label&quot;: &quot;첫번째 chunk&quot;, &quot;lines&quot;: 10},
                {&quot;label&quot;: &quot;첫번째 chunk_metadata&quot;, &quot;lines&quot;: 5},
            ]

            # Textbox 5개
            text_list1 = [gr.Textbox(**chunk1) for chunk1 in textConfig1]

            btn1.click(fn=process_pdf, inputs=[pdf_input], outputs=text_list1)
        with gr.Tab(&quot;2단계 - RAG QA&quot;):
            pdf_input2 = gr.File(label=&quot;PDF 업로드&quot;)
            q_input = gr.Textbox(label=&quot;질문 입력&quot;, lines=1)
            btn2 = gr.Button(&quot;질문하기&quot;)

            textConfig2 = [
                {&quot;label&quot;: &quot;검색된 Chunk&quot;, &quot;lines&quot;: 20},
                {&quot;label&quot;: &quot;최종 답변&quot;, &quot;lines&quot;: 10},
            ]

            text_list2 = [gr.Textbox(**chunk2) for chunk2 in textConfig2]

            btn2.click(fn=rag_chat, inputs=[pdf_input2, q_input], outputs=text_list2)
app.launch()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 11.33.11.png&quot; data-origin-width=&quot;1665&quot; data-origin-height=&quot;917&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W44KW/dJMcacwM7kg/RhFuyB0hslNDKGcOGMbNSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W44KW/dJMcacwM7kg/RhFuyB0hslNDKGcOGMbNSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W44KW/dJMcacwM7kg/RhFuyB0hslNDKGcOGMbNSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW44KW%2FdJMcacwM7kg%2FRhFuyB0hslNDKGcOGMbNSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1665&quot; height=&quot;917&quot; data-filename=&quot;스크린샷 2026-06-20 오후 11.33.11.png&quot; data-origin-width=&quot;1665&quot; data-origin-height=&quot;917&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 11.34.01.png&quot; data-origin-width=&quot;1665&quot; data-origin-height=&quot;917&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yAhnz/dJMcacQ2sRF/URwJXVQb8hLKOtWtkpExpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yAhnz/dJMcacQ2sRF/URwJXVQb8hLKOtWtkpExpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yAhnz/dJMcacQ2sRF/URwJXVQb8hLKOtWtkpExpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyAhnz%2FdJMcacQ2sRF%2FURwJXVQb8hLKOtWtkpExpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1665&quot; height=&quot;917&quot; data-filename=&quot;스크린샷 2026-06-20 오후 11.34.01.png&quot; data-origin-width=&quot;1665&quot; data-origin-height=&quot;917&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1단계 : PDF &amp;amp; Chunk 확인하는 탭&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 11.06.46.png&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blVORf/dJMcacwM6SN/kYXlZ3iRluik4l64ofk8o0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blVORf/dJMcacwM6SN/kYXlZ3iRluik4l64ofk8o0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blVORf/dJMcacwM6SN/kYXlZ3iRluik4l64ofk8o0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblVORf%2FdJMcacwM6SN%2FkYXlZ3iRluik4l64ofk8o0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;848&quot; height=&quot;188&quot; data-filename=&quot;스크린샷 2026-06-20 오후 11.06.46.png&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;188&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781966727165&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1단계
def process_pdf(pdf_file):
    if pdf_file is None:
        return (&quot;PDF 파일을 업로드 해주세요&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;)

    # PDF 로드
    loader = PyPDFLoader(pdf_file)
    docs = loader.load()

    # 총 페이지 수
    total_pages = len(docs)

    # 첫 페이지 내용
    first_page_content = docs[0].page_content[:1000]

    # 총 chunk 수
    splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=30)
    chunks = splitter.split_documents(docs)
    total_chunks = len(chunks)

    # 첫번째 chunk
    first_chunk = chunks[0].page_content

    # 첫번쨰 chunk metadata
    first_chunk_metadata = chunks[0].metadata

    return (
        total_pages,
        first_page_content,
        total_chunks,
        first_chunk,
        first_chunk_metadata,
    )&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 11.47.53.png&quot; data-origin-width=&quot;1665&quot; data-origin-height=&quot;917&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1lceu/dJMcaglCoCS/NTaFsqLR8A1wea8nAvYaD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1lceu/dJMcaglCoCS/NTaFsqLR8A1wea8nAvYaD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1lceu/dJMcaglCoCS/NTaFsqLR8A1wea8nAvYaD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1lceu%2FdJMcaglCoCS%2FNTaFsqLR8A1wea8nAvYaD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1665&quot; height=&quot;917&quot; data-filename=&quot;스크린샷 2026-06-20 오후 11.47.53.png&quot; data-origin-width=&quot;1665&quot; data-origin-height=&quot;917&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2단계 : RAG QA&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 11.58.17.png&quot; data-origin-width=&quot;859&quot; data-origin-height=&quot;195&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XZ2kY/dJMcacQ2tcj/gwq074DzGik2mEkd5EuRpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XZ2kY/dJMcacQ2tcj/gwq074DzGik2mEkd5EuRpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XZ2kY/dJMcacQ2tcj/gwq074DzGik2mEkd5EuRpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXZ2kY%2FdJMcacQ2tcj%2Fgwq074DzGik2mEkd5EuRpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;859&quot; height=&quot;195&quot; data-filename=&quot;스크린샷 2026-06-20 오후 11.58.17.png&quot; data-origin-width=&quot;859&quot; data-origin-height=&quot;195&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781968758983&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 모델 LLM, Embedding
ollama_embedding = OllamaEmbeddings(model=&quot;nomic-embed-text-v2-moe&quot;)
llm = ChatOllama(model=&quot;qwen2.5&quot;)

# 2단계
def rag_chat(pdf_file, question):
    if pdf_file is None:
        return (&quot;PDF 파일을 업로드 해주세요&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;)

    # PDF 로드
    loader = PyPDFLoader(pdf_file)
    docs = loader.load()

    # TextSplitter
    splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=30)
    split_docs = splitter.split_documents(docs)

    # embedding
    faiss_store = FAISS.from_documents(documents=split_docs, embedding=ollama_embedding)

    # retriever
    retriever = faiss_store.as_retriever(search_kwargs={&quot;k&quot;: 3})

    retriever_docs = retriever.invoke(question)

    # 컨텍스트 생성
    context = &quot;\n\n&quot;.join([doc.page_content for doc in result])

    # prompt
    system_message = &quot;&quot;&quot;\
    당신은 pdf 기반 RAG AI입니다.
    다음 문서를 참고하여 질문에 답변하세요.

    문서:
    {context}

    질문:
    {question}
    &quot;&quot;&quot;

    prompt = ChatPromptTemplate.from_template(system_message)

    # RAG Chain
    chain = prompt | llm | StrOutputParser()

    answer = chain.invoke({&quot;context&quot;: context, &quot;question&quot;: question})

    # 답변, rag 결과 반환
    retrieved_text = &quot;&quot;

    for i, doc in enumerate(retriever_docs):
        retrieved_text += f&quot;&quot;&quot;
        [검색 문서 {i}]

        내용 :
        {doc.page_content}

        메타데이터:
        {doc.metadata}
        {&quot;=&quot; * 50}
        &quot;&quot;&quot;

    return retrieved_text, answer&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-21 오전 12.23.14.png&quot; data-origin-width=&quot;1656&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRVIhh/dJMcaaZ07s0/NfAKKqXOuQMEZGsS5pzHfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRVIhh/dJMcaaZ07s0/NfAKKqXOuQMEZGsS5pzHfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRVIhh/dJMcaaZ07s0/NfAKKqXOuQMEZGsS5pzHfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRVIhh%2FdJMcaaZ07s0%2FNfAKKqXOuQMEZGsS5pzHfk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1656&quot; height=&quot;844&quot; data-filename=&quot;스크린샷 2026-06-21 오전 12.23.14.png&quot; data-origin-width=&quot;1656&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>AI/LLM</category>
      <author>EunHaa</author>
      <guid isPermaLink="true">https://eun-haa.tistory.com/34</guid>
      <comments>https://eun-haa.tistory.com/34#entry34comment</comments>
      <pubDate>Sun, 21 Jun 2026 00:26:04 +0900</pubDate>
    </item>
    <item>
      <title>검색 증강 생성(RAG) - 개념 (1)</title>
      <link>https://eun-haa.tistory.com/33</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. RAG&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올라마(Ollama)에서 임베딩 개념을 이야기하면서 RAG 개념이 시작되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임베딩 모델을 통해 어떤 input을 고차원의 숫자 리스트로 바꾸어줬고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 임베딩된 숫자를 통해 RAG라는 것을 할 수 있다고 공부했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 RAG는 &lt;b&gt;Retrieval Augmented Generation&lt;/b&gt;의 약자로써, 검색 증강 생성을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성형 AI가 임베딩한 외부 문서를 검색한 뒤 그 내용을 기반으로 답변한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 문서 처리 : DocumentLoader&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 3.55.39.png&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6PNOw/dJMcaccsQmQ/ZNIvyaLUlZRmg7Yme2l8x1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6PNOw/dJMcaccsQmQ/ZNIvyaLUlZRmg7Yme2l8x1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6PNOw/dJMcaccsQmQ/ZNIvyaLUlZRmg7Yme2l8x1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6PNOw%2FdJMcaccsQmQ%2FZNIvyaLUlZRmg7Yme2l8x1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;848&quot; height=&quot;214&quot; data-filename=&quot;스크린샷 2026-06-20 오후 3.55.39.png&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;214&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로, PyPDFLoader, JSONLoader, TextLoader, WebBaseLoader 등이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. PyPDFLoader&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781939089948&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader(&quot;../data/서울대 미대.pdf&quot;)
docs = loader.load()

print(f&quot;총 페이지 수 : {len(docs)}&quot;)
print(f&quot;첫 페이지 텍스트 : {docs[0].page_content[:100]}&quot;)
print(f&quot;메타 데이타 : {docs[0].metadata}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 4.05.12.png&quot; data-origin-width=&quot;1135&quot; data-origin-height=&quot;175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKvRsP/dJMcaglCgjx/hKFno6klZKNrpgl6khJHM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKvRsP/dJMcaglCgjx/hKFno6klZKNrpgl6khJHM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKvRsP/dJMcaglCgjx/hKFno6klZKNrpgl6khJHM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKvRsP%2FdJMcaglCgjx%2FhKFno6klZKNrpgl6khJHM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1135&quot; height=&quot;175&quot; data-filename=&quot;스크린샷 2026-06-20 오후 4.05.12.png&quot; data-origin-width=&quot;1135&quot; data-origin-height=&quot;175&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. CSVLoader&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781939439291&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from langchain_community.document_loaders import CSVLoader

csv_loader = CSVLoader(&quot;../data/restaurant_reviews.csv&quot;, encoding=&quot;utf-8&quot;, csv_args={'delimiter': &quot;,&quot;})
csv_docs = csv_loader.load()

print(f&quot;총 페이지 수 : {len(csv_docs)}&quot;)
print(f&quot;첫 페이지 텍스트 : {csv_docs[0].page_content[:100]}&quot;)
print(f&quot;메타 데이타 : {csv_docs[0].metadata}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 4.10.54.png&quot; data-origin-width=&quot;1023&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dm7IGF/dJMcabdu6xt/HfXqkAPUGbBKoGn7vxoAF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dm7IGF/dJMcabdu6xt/HfXqkAPUGbBKoGn7vxoAF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dm7IGF/dJMcabdu6xt/HfXqkAPUGbBKoGn7vxoAF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdm7IGF%2FdJMcabdu6xt%2FHfXqkAPUGbBKoGn7vxoAF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1023&quot; height=&quot;72&quot; data-filename=&quot;스크린샷 2026-06-20 오후 4.10.54.png&quot; data-origin-width=&quot;1023&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. WebBaseLoader&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781939609230&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# BeautifulSoup4 라이브러리 필요
from langchain_community.document_loaders import WebBaseLoader

web_loader = WebBaseLoader(web_paths=['https://python.org','https://langchain.com'])
web_docs = web_loader.load()

print(f&quot;총 페이지 수 : {len(web_docs)}&quot;)
print(f&quot;첫 페이지 텍스트 : {web_docs[0].page_content[:100]}&quot;)
print(f&quot;메타 데이타 : {web_docs[0].metadata}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 4.13.16.png&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;629&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1CIEI/dJMcadPU5bO/jfoWECiOEhYjmkYK1OHT01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1CIEI/dJMcadPU5bO/jfoWECiOEhYjmkYK1OHT01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1CIEI/dJMcadPU5bO/jfoWECiOEhYjmkYK1OHT01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1CIEI%2FdJMcadPU5bO%2FjfoWECiOEhYjmkYK1OHT01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1134&quot; height=&quot;629&quot; data-filename=&quot;스크린샷 2026-06-20 오후 4.13.16.png&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;629&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. DirectoryLoader&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781939800493&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from langchain_community.document_loaders import DirectoryLoader

dir_loader = DirectoryLoader(path=&quot;../data&quot;, glob=&quot;**/*.pdf&quot;, loader_cls=PyPDFLoader, show_progress=True)
dir_docs = dir_loader.load()

print(f&quot;총 페이지 수 : {len(dir_docs)}&quot;)
print(f&quot;첫 페이지 텍스트 : {dir_docs[0].page_content[:100]}&quot;)
print(f&quot;메타 데이타 : {dir_docs[0].metadata}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 4.17.00.png&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G4PVp/dJMcaiKtkQk/o5g1128A59nziAffpvZ44k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G4PVp/dJMcaiKtkQk/o5g1128A59nziAffpvZ44k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G4PVp/dJMcaiKtkQk/o5g1128A59nziAffpvZ44k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG4PVp%2FdJMcaiKtkQk%2Fo5g1128A59nziAffpvZ44k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1134&quot; height=&quot;119&quot; data-filename=&quot;스크린샷 2026-06-20 오후 4.17.00.png&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;119&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 유튜브 스크립트 가져오기 예제&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781940247579&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from youtube_transcript_api import YouTubeTranscriptApi

video_id = 'VRQzJMPf1ug'

yt_api = YouTubeTranscriptApi()

transcript = yt_api.fetch(video_id, languages=['ko'])

for idx, t in enumerate(transcript, 1):
    print(f&quot;{idx} : {t.text}&quot;)
    print(f&quot;{idx} : {t.start}&quot;)
    print(f&quot;{idx} : {t.duration}&quot;)

text = &quot; &quot;.join(t.text for t in transcript)

print(text)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 4.24.24.png&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;547&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWXG8D/dJMcajbvpEt/Okl1rTGz9hRKI1emfwEji1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWXG8D/dJMcajbvpEt/Okl1rTGz9hRKI1emfwEji1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWXG8D/dJMcajbvpEt/Okl1rTGz9hRKI1emfwEji1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWXG8D%2FdJMcajbvpEt%2FOkl1rTGz9hRKI1emfwEji1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1134&quot; height=&quot;547&quot; data-filename=&quot;스크린샷 2026-06-20 오후 4.24.24.png&quot; data-origin-width=&quot;1134&quot; data-origin-height=&quot;547&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781941792624&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;doc = Document(page_content=text, metadata={
    &quot;source&quot;: f&quot;https://www.youtube.com/watch?v={video_id}&quot;,
    &quot;video_id&quot;: video_id
})

print(docs.page_content[:100])
print(docs.metadata)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 4.54.30.png&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;76&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5XrZT/dJMcaiw0bIY/tsMQgjoorYoYkG2K3ejBwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5XrZT/dJMcaiw0bIY/tsMQgjoorYoYkG2K3ejBwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5XrZT/dJMcaiw0bIY/tsMQgjoorYoYkG2K3ejBwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5XrZT%2FdJMcaiw0bIY%2FtsMQgjoorYoYkG2K3ejBwk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;723&quot; height=&quot;76&quot; data-filename=&quot;스크린샷 2026-06-20 오후 4.54.30.png&quot; data-origin-width=&quot;723&quot; data-origin-height=&quot;76&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 문서 쪼개기 : RecursiveCharacterTextSplitter&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 RAG에서 주로 사용하며, LLM의 Context Window이 제한이 있어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서를 한 번에 넣을 수 없으므로 TextSplitter 이용해 적절한 크기의 청크로 분할한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;chunk_size (청크 최대 크기), chunk_overlap (청크 겹치는 부분 - 문맥 유지) 개념이 있다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781942827161&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 1. 문서 로드
loader = PyPDFLoader(&quot;../data/서울대 미대.pdf&quot;)
docs = loader.load()

# 2. 문서 분할
splitter = RecursiveCharacterTextSplitter(
    chunk_size=500, chunk_overlap=50, length_function=len, separators=['\n\n','\n',' ','']
)

chunks = splitter.split_documents(docs)

print(f&quot;원본 페이지 수 : {len(docs)}&quot;)
print(f&quot;분할한 청크 수 : {len(chunks)}&quot;)
print(f&quot;첫 청크 길이 : {len(chunks[0].page_content)}&quot;)
print(f&quot;첫 청크 내용 : {chunks[0].page_content}&quot;)
print(f&quot;메타 데이타 : {chunks[0].metadata}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 5.07.23.png&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;221&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLgwU5/dJMcaccsRIn/UlSveoe1xwYC96XHxsQ8vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLgwU5/dJMcaccsRIn/UlSveoe1xwYC96XHxsQ8vk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLgwU5/dJMcaccsRIn/UlSveoe1xwYC96XHxsQ8vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLgwU5%2FdJMcaccsRIn%2FUlSveoe1xwYC96XHxsQ8vk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1494&quot; height=&quot;221&quot; data-filename=&quot;스크린샷 2026-06-20 오후 5.07.23.png&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;221&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) 청크 파라미터&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;overlap이 0일 경우, 앞의 문맥에 대해 알지 못해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM에게 앞선 컨텍스트를 전달하지 못하는 문제를 확인하기 위함이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;overlap이 많을수록 앞선 내용에 대해 기억하는 경우가 많을 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781943354900&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# chunk 파라미터 비교

text = &quot;파이썬은 1991년에 발표된 언어이다&quot; * 20

configs = [
    { &quot;chunk_size&quot;: 100, &quot;chunk_overlap&quot;: 0},
    { &quot;chunk_size&quot;: 100, &quot;chunk_overlap&quot;: 20},
    { &quot;chunk_size&quot;: 200, &quot;chunk_overlap&quot;: 50}
]

for cfg in configs:
    sp = RecursiveCharacterTextSplitter(**cfg)
    chunks = sp.split_text(text)
    print(f&quot;size={cfg['chunk_size']}, overlap={cfg['chunk_overlap']}&quot;)
    print(f&quot;{len(chunks)}개 청크, 첫 청크 : {chunks[0][:30]}....&quot;)
    if len(chunks) &amp;gt; 1:
        print(f&quot;첫 청크 끝 : '{chunks[0][-20:]}'&quot;)
        print(f&quot;둘째 청크 시작 : '{chunks[1][:20]}'&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 5.19.28.png&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBSZ3S/dJMcagy9MJj/12hKvXpw68HObkZCDqPVaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBSZ3S/dJMcagy9MJj/12hKvXpw68HObkZCDqPVaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBSZ3S/dJMcagy9MJj/12hKvXpw68HObkZCDqPVaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBSZ3S%2FdJMcagy9MJj%2F12hKvXpw68HObkZCDqPVaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1494&quot; height=&quot;267&quot; data-filename=&quot;스크린샷 2026-06-20 오후 5.19.28.png&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4) 임베딩(Embedding)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트를 고차원 벡터로 변환하는 과정이며, 의미가 비슷할수록 벡터가 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG에서 임베딩은 다음과 같을 때 사용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;문서 청크를 저장할 때 사용&lt;/li&gt;
&lt;li&gt;질문 검색할 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1781954757171&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from langchain_ollama import OllamaEmbeddings

ollama_embedding = OllamaEmbeddings(model='nomic-embed-text-v2-moe')

# 단일 텍스트 쿼리
vector = ollama_embedding.embed_query(&quot;파이썬이란?&quot;)

print(f&quot;벡터 차원 : {len(vector)}&quot;)
print(f&quot;첫 5개 값 : {vector[:5]}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 8.26.13.png&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;52&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/capH3G/dJMcabq7cuU/gbjSvaKqmai9LKOGWWJTX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/capH3G/dJMcabq7cuU/gbjSvaKqmai9LKOGWWJTX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/capH3G/dJMcabq7cuU/gbjSvaKqmai9LKOGWWJTX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcapH3G%2FdJMcabq7cuU%2FgbjSvaKqmai9LKOGWWJTX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;52&quot; data-filename=&quot;스크린샷 2026-06-20 오후 8.26.13.png&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;52&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781954976136&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;texts = [&quot;파이썬이란?&quot;, &quot;자바란 무엇인가?&quot;, &quot;오늘 날씨는?&quot;]

# 여러 개 쿼리
vectors = ollama_embedding.embed_documents(texts)

print(f&quot;임베딩 문서 수 : {len(vectors)}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 8.29.51.png&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;32&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbJxAI/dJMcaaZ03wc/ARNyMCd1MBqiUV3lmY3nCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbJxAI/dJMcaaZ03wc/ARNyMCd1MBqiUV3lmY3nCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbJxAI/dJMcaaZ03wc/ARNyMCd1MBqiUV3lmY3nCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbJxAI%2FdJMcaaZ03wc%2FARNyMCd1MBqiUV3lmY3nCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;32&quot; data-filename=&quot;스크린샷 2026-06-20 오후 8.29.51.png&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;32&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 벡터값을 가지고 벡터 유사도를 추출해 볼수 있다. (여기선 코사인 유사도 사용)&lt;/p&gt;
&lt;pre id=&quot;code_1781955469366&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 벡터 유사도
import numpy as np

def cosine_similarity(a, b):
    a, b = np.array(a), np.array(b)
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

vec1 = ollama_embedding.embed_query(&quot;파이썬이란?&quot;)
vec2 = ollama_embedding.embed_query(&quot;python이란?&quot;)
vec3 = ollama_embedding.embed_query(&quot;오늘 저녁 뭐 먹지?&quot;)

print(f&quot;파이썬 vs Python : {cosine_similarity(vec1, vec2):.3f}&quot;)
print(f&quot;파이썬 vs 저녁 : {cosine_similarity(vec1, vec3):.3f}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 8.38.02.png&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;52&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcayIT/dJMcaaeGE6j/vfZMk42XC8mJVwa9POJg51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcayIT/dJMcaaeGE6j/vfZMk42XC8mJVwa9POJg51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcayIT/dJMcaaeGE6j/vfZMk42XC8mJVwa9POJg51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcayIT%2FdJMcaaeGE6j%2FvfZMk42XC8mJVwa9POJg51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;52&quot; data-filename=&quot;스크린샷 2026-06-20 오후 8.38.02.png&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;52&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5) 벡터 DB&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리가 아닌 DB에 벡터 값을 벡터 저장소에 저장한다. 여기서는 chromaDB와 faiss를 사용할 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 9.16.53.png&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;269&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bN6Ocw/dJMcaf73kdk/VfxFXs88dcT4VZlDiixBaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bN6Ocw/dJMcaf73kdk/VfxFXs88dcT4VZlDiixBaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bN6Ocw/dJMcaf73kdk/VfxFXs88dcT4VZlDiixBaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbN6Ocw%2FdJMcaf73kdk%2FVfxFXs88dcT4VZlDiixBaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;590&quot; height=&quot;269&quot; data-filename=&quot;스크린샷 2026-06-20 오후 9.16.53.png&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;269&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;chromaDB&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781956195749&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from langchain_chroma import Chroma

# 1. 문서 로드
loader = PyPDFLoader(&quot;../data/서울대 미대.pdf&quot;)
docs = loader.load()

# 2. 문서 분할
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50, length_function=len, separators=['\n\n','\n',' ',''])

chunks = splitter.split_documents(docs)

# 3. 임베딩 처리
ollama_embedding = OllamaEmbeddings(model='nomic-embed-text-v2-moe')

# 4. 벡터 스토어에 저장(chromaDB)
vector_store = Chroma.from_documents(
    documents=chunks,
    embedding=ollama_embedding,
    persist_directory=&quot;./db/chroma_db&quot;,
    collection_name=&quot;my_docs&quot;
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 8.51.21.png&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QdsJY/dJMcaglClFJ/fIZWNl2fUw51vHNSMlmf2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QdsJY/dJMcaglClFJ/fIZWNl2fUw51vHNSMlmf2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QdsJY/dJMcaglClFJ/fIZWNl2fUw51vHNSMlmf2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQdsJY%2FdJMcaglClFJ%2FfIZWNl2fUw51vHNSMlmf2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;984&quot; height=&quot;441&quot; data-filename=&quot;스크린샷 2026-06-20 오후 8.51.21.png&quot; data-origin-width=&quot;984&quot; data-origin-height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781956974782&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 5. 검색(Retrieval)
# 질문도 벡터로 변경해야 함, 유사도 높은 걸로 몇개 추출할 것인지 설정

results = vector_store.similarity_search(&quot;근로장학생 신청 절차는?&quot;,k=3)

for idx, doc in enumerate(results, 1):
    print(f&quot;\n[결과] {idx} 출처 : {doc.metadata}&quot;)
    print(doc.page_content[:300])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 9.03.34.png&quot; data-origin-width=&quot;1498&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceUffi/dJMcafUBqXo/ejPTIKU5Z4SLCLHi3usWf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceUffi/dJMcafUBqXo/ejPTIKU5Z4SLCLHi3usWf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceUffi/dJMcafUBqXo/ejPTIKU5Z4SLCLHi3usWf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceUffi%2FdJMcafUBqXo%2FejPTIKU5Z4SLCLHi3usWf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1498&quot; height=&quot;373&quot; data-filename=&quot;스크린샷 2026-06-20 오후 9.03.34.png&quot; data-origin-width=&quot;1498&quot; data-origin-height=&quot;373&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781957357438&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# DB 확인

data = vector_store.get()

print(data['documents'][0][:300])
print(data['metadatas'][0])
print(f&quot;청크 개수 : {vector_store._collection.count()}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 9.09.28.png&quot; data-origin-width=&quot;1498&quot; data-origin-height=&quot;183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btBf1b/dJMcaaeGFBf/RfvbpG35HeItEjzs3KJokK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btBf1b/dJMcaaeGFBf/RfvbpG35HeItEjzs3KJokK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btBf1b/dJMcaaeGFBf/RfvbpG35HeItEjzs3KJokK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtBf1b%2FdJMcaaeGFBf%2FRfvbpG35HeItEjzs3KJokK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1498&quot; height=&quot;183&quot; data-filename=&quot;스크린샷 2026-06-20 오후 9.09.28.png&quot; data-origin-width=&quot;1498&quot; data-origin-height=&quot;183&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781958477083&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 저장 후 불러오기
chromadb = Chroma(persist_directory=&quot;./db/chroma_db&quot;, embedding_function=ollama_embedding, collection_name=&quot;my_docs&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;faiss&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781958072812&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# faiss
from langchain_community.vectorstores import FAISS

# 1. 문서 로드
loader = PyPDFLoader(&quot;../data/서울대 미대.pdf&quot;)
docs = loader.load()

# 2. 문서 분할
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50, length_function=len, separators=['\n\n','\n',' ',''])

chunks = splitter.split_documents(docs)

# 3. 임베딩 처리
ollama_embedding = OllamaEmbeddings(model='nomic-embed-text-v2-moe')

# 벡터 스토어에 저장(faiss)
faiss_vector_store = FAISS.from_documents(documents=chunks, embedding=ollama_embedding)

results_score = faiss_vector_store.similarity_search_with_score(&quot;수강신청 내역 확인 방법은?&quot;,k=3)

for doc, score in results_score:
    print(f&quot;유사도 : {score:.4f}, {doc.page_content[:20]}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 9.23.43.png&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZexS2/dJMcaiDLBc1/dKmms75oFqKJeyhGtuoBI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZexS2/dJMcaiDLBc1/dKmms75oFqKJeyhGtuoBI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZexS2/dJMcaiDLBc1/dKmms75oFqKJeyhGtuoBI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZexS2%2FdJMcaiDLBc1%2FdKmms75oFqKJeyhGtuoBI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;590&quot; height=&quot;116&quot; data-filename=&quot;스크린샷 2026-06-20 오후 9.23.43.png&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781958299442&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 로컬 저장
faiss_vector_store.save_local(&quot;./db/faiss_index&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781958539704&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 저장 후 불러오기
faissdb = FAISS.load_local(&quot;./db/faiss_index&quot;,embeddings=ollama_embedding, allow_dangerous_deserialization=True)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6) LLM에게 전송&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 위에서 나온 결과를 LLM에게 전송해야 한다. (다듬는 과정)&lt;/p&gt;
&lt;pre id=&quot;code_1781959705425&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1. 문서 로드
pdf_doc = PyPDFLoader(&quot;../data/Summary of ChatGPTGPT-4 Research.pdf&quot;)
loader = pdf_doc.load()

# 2. 문서 분할
splitter = RecursiveCharacterTextSplitter(chunk_size=500,chunk_overlap=50)
chunks = splitter.split_documents(loader)

# 3. 인덱싱 (임베딩)
embeddings = OllamaEmbeddings(model='nomic-embed-text-v2-moe')

# 4. 벡터 스토어(chromaDB)
chroma_store = Chroma.from_documents(documents=chunks,embedding=embeddings,persist_directory=&quot;./db/chroma_db&quot;, collection_name=&quot;research&quot;)

# 5. as_retriever() : 벡터 스토어를 검색 가능한 Retriever 형태로 변환 (랭체인과 연동)
retriever = chroma_store.as_retriever(search_type=&quot;similarity&quot;, search_kwargs={&quot;k&quot;: 3})

system_message = &quot;&quot;&quot;\
다음 컨텍스트를 참고하여 질문에 답하세요.
컨텍스트에 없는 내용은 모른다고 답하세요.

컨텍스트:
{context}

&quot;&quot;&quot;

# 6. RAG 프롬프트 생성
prompt = ChatPromptTemplate.from_messages([
    (&quot;system&quot;,system_message),
    (&quot;human&quot;,&quot;{question}&quot;)
])

qwen_llm = ChatOllama(model='qwen2.5')

def format_docs(docs):
    return &quot;\n\n&quot;.join(doc.page_content for doc in docs)

rag_chain = {
    &quot;context&quot;: retriever | format_docs,
    &quot;question&quot;: RunnablePassthrough()
} | prompt | qwen_llm | StrOutputParser()

ans = rag_chain.invoke(&quot;Where can I use ChatGPT?&quot;)

print(ans)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 9.48.54.png&quot; data-origin-width=&quot;1505&quot; data-origin-height=&quot;33&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UUERK/dJMcag0b4lv/rvg3M2MNuWCWyi4Y6vCMr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UUERK/dJMcag0b4lv/rvg3M2MNuWCWyi4Y6vCMr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UUERK/dJMcag0b4lv/rvg3M2MNuWCWyi4Y6vCMr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUUERK%2FdJMcag0b4lv%2Frvg3M2MNuWCWyi4Y6vCMr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1505&quot; height=&quot;33&quot; data-filename=&quot;스크린샷 2026-06-20 오후 9.48.54.png&quot; data-origin-width=&quot;1505&quot; data-origin-height=&quot;33&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처 페이지를 함께 병렬 계산하고 싶은 경우 아래와 같이 처리한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781960174459&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 출처 페이지 정보와 답변 함께 반환
def format_docs_with_source(docs):
    result = []
    for doc in docs:
        src = doc.metadata.get(&quot;source&quot;,&quot;알 수 없음&quot;)
        page = doc.metadata.get(&quot;page&quot;,&quot;&quot;)
        result.append(f&quot;[출처 : {src} p.{page}\n{doc.page_content}]&quot;)
    return &quot;\n\n&quot;.join(result)
    
rag_chain = {
    &quot;context&quot;: retriever | format_docs_with_source,
    &quot;question&quot;: RunnablePassthrough()
} | prompt | qwen_llm | StrOutputParser()

rag_with_source = RunnableParallel(answer=rag_chain, sources=retriever)
result = rag_with_source.invoke(&quot;where can I use ChatGPT?&quot;)

print(&quot;======답변======&quot;)
print(result['answer'])
print(&quot;======출처======&quot;)
for doc in result['sources']:
    print(f&quot; - {doc.metadata.get('source')} p.{doc.metadata.get('page','')}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 9.57.19.png&quot; data-origin-width=&quot;1505&quot; data-origin-height=&quot;142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIV3bk/dJMcahdN27Y/FYsnAt7XhF32AZ2NrqttKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIV3bk/dJMcahdN27Y/FYsnAt7XhF32AZ2NrqttKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIV3bk/dJMcahdN27Y/FYsnAt7XhF32AZ2NrqttKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIV3bk%2FdJMcahdN27Y%2FFYsnAt7XhF32AZ2NrqttKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1505&quot; height=&quot;142&quot; data-filename=&quot;스크린샷 2026-06-20 오후 9.57.19.png&quot; data-origin-width=&quot;1505&quot; data-origin-height=&quot;142&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI/LLM</category>
      <author>EunHaa</author>
      <guid isPermaLink="true">https://eun-haa.tistory.com/33</guid>
      <comments>https://eun-haa.tistory.com/33#entry33comment</comments>
      <pubDate>Sat, 20 Jun 2026 21:58:33 +0900</pubDate>
    </item>
    <item>
      <title>랭체인(LangChain) - 심화 내용 (3)</title>
      <link>https://eun-haa.tistory.com/32</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. RunnableSequence&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프라인 문법에서 자세히 보면, &lt;b&gt;RunnableSequence&lt;/b&gt;라고 하는 것을 내부적으로 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에는 다음과 같은 종류가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;RunnablePassthrough : 입력을 통과시켜 다른 키와 병합&lt;/li&gt;
&lt;li&gt;RunnableLambda : 체인을 파이썬 함수와 연동&lt;/li&gt;
&lt;li&gt;RunnableParallel : 여러 체인을 동시에 실행하고 결과를 dict 구조로 병합&lt;/li&gt;
&lt;li&gt;.assign() : 체인 중간에 새 키를 추가하는 메소드&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) RunnableLambda&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chain은 RunnableSequence를 생성하기 때문에 일반 파이썬 함수는 바로 연결 불가하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이럴 때 일반 함수를 감싸는 용도로 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781887354518&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from langchain_core.runnables import RunnableLambda

def upper(text):
    return text.upper()

prompt = ChatPromptTemplate.from_messages([
    (&quot;system&quot;,&quot;당신은 언어 전문가입니다.&quot;),
    (&quot;human&quot;,&quot;{topic}에 대해 설명해줘.&quot;)
])

chain = prompt | qwen_llm | StrOutputParser() | RunnableLambda(upper)

result = chain.invoke({ &quot;topic&quot; : &quot;python&quot; })

print(result)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오전 1.42.54.png&quot; data-origin-width=&quot;1384&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2P6Lz/dJMcab5Hx26/KFkeCuAMTitA354LHP23Yk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2P6Lz/dJMcab5Hx26/KFkeCuAMTitA354LHP23Yk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2P6Lz/dJMcab5Hx26/KFkeCuAMTitA354LHP23Yk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2P6Lz%2FdJMcab5Hx26%2FKFkeCuAMTitA354LHP23Yk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1384&quot; height=&quot;298&quot; data-filename=&quot;스크린샷 2026-06-20 오전 1.42.54.png&quot; data-origin-width=&quot;1384&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781898758638&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 후처리 함수
def remove_newline(text):
    return text.replace(&quot;\n&quot;,&quot; &quot;)

prompt = ChatPromptTemplate.from_template(&quot;{question}에 대해 답변해줘.&quot;)

chain = prompt | qwen_llm | StrOutputParser() | RunnableLambda(remove_newline)

print(chain.invoke({ &quot;question&quot;: &quot;Python의 리스트&quot;}))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오전 4.54.02.png&quot; data-origin-width=&quot;1476&quot; data-origin-height=&quot;30&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UxNhB/dJMcacKmp0o/o7gHoyKlJd5aNEPxGDNgDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UxNhB/dJMcacKmp0o/o7gHoyKlJd5aNEPxGDNgDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UxNhB/dJMcacKmp0o/o7gHoyKlJd5aNEPxGDNgDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUxNhB%2FdJMcacKmp0o%2Fo7gHoyKlJd5aNEPxGDNgDk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1476&quot; height=&quot;30&quot; data-filename=&quot;스크린샷 2026-06-20 오전 4.54.02.png&quot; data-origin-width=&quot;1476&quot; data-origin-height=&quot;30&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) RunnablePassThrough&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수에 들어온 값이 RunnablePassthrough()에 전달되어 통과되는 개념이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781899275090&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from langchain_core.runnables import RunnablePassthrough

prompt = ChatPromptTemplate.from_template(&quot;{question}에 대해 답변해줘.&quot;)

chain = (
    { &quot;question&quot;: RunnablePassthrough(), &quot;answer&quot;: prompt | qwen_llm | StrOutputParser()} | RunnableLambda(lambda x: f&quot;Q: {x['question']}\nA:{x['answer']}&quot;)
)

result = chain.invoke(&quot;파이썬이란?&quot;)
print(result)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오전 5.03.02.png&quot; data-origin-width=&quot;1031&quot; data-origin-height=&quot;95&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8M9cg/dJMcaiDLkab/GQL1QSLreZokEhBWxdIqr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8M9cg/dJMcaiDLkab/GQL1QSLreZokEhBWxdIqr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8M9cg/dJMcaiDLkab/GQL1QSLreZokEhBWxdIqr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8M9cg%2FdJMcaiDLkab%2FGQL1QSLreZokEhBWxdIqr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1031&quot; height=&quot;95&quot; data-filename=&quot;스크린샷 2026-06-20 오전 5.03.02.png&quot; data-origin-width=&quot;1031&quot; data-origin-height=&quot;95&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) RunnableParallel&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 체인을 호출할 때 사용하며, 병렬처리 성능은 실제 컴퓨터 자원에 영향을 받는다.&lt;/p&gt;
&lt;pre id=&quot;code_1781900080457&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from langchain_core.runnables import RunnableParallel

parser = StrOutputParser()

# 요약
summary_chain = ChatPromptTemplate.from_template(&quot;다음 텍스트를 한 문장으로 요약하세요. 한국어로. : {text}&quot;) | qwen_llm | parser

# 키워드 추출
keyword_chain = ChatPromptTemplate.from_template(&quot;다음 텍스트의 핵심 키워드 3개를 ,로 나열해줘 : {text}&quot;) | qwen_llm | parser

parallel_chain = RunnableParallel(summary=summary_chain,keyword=keyword_chain)

result = parallel_chain.invoke({ &quot;text&quot; : &quot;인공지능은 인간의 지능을 모방하는 기술로...&quot; })

print(result)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오전 5.20.08.png&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;34&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/szqrv/dJMcafAhhXs/2jYhwJYiqVTMGs6CF9NzlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/szqrv/dJMcafAhhXs/2jYhwJYiqVTMGs6CF9NzlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/szqrv/dJMcafAhhXs/2jYhwJYiqVTMGs6CF9NzlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fszqrv%2FdJMcafAhhXs%2F2jYhwJYiqVTMGs6CF9NzlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;751&quot; height=&quot;34&quot; data-filename=&quot;스크린샷 2026-06-20 오전 5.20.08.png&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;34&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4) assign()&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781901109364&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;analysis_chain = ChatPromptTemplate.from_template(&quot;다음 리뷰를 분석해줘 : {review}&quot;) | qwen_llm | parser | {&quot;text&quot;: RunnablePassthrough() }

full_chain = (RunnablePassthrough.assign(summary=ChatPromptTemplate.from_template(&quot;{review} 한 줄 요약&quot;) | qwen_llm | parser)
                                 .assign(sentiment=ChatPromptTemplate.from_template(&quot;{review} 감정 분석 : 긍정, 부정, 중립 중 하나만&quot;) | qwen_llm | parser))
                                 .assign(analysis=analysis_chain)

result = full_chain.invoke({&quot;review&quot;: &quot;정말 맛있는 음식이었어요.&quot;})

print(result)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오전 6.09.00.png&quot; data-origin-width=&quot;1453&quot; data-origin-height=&quot;30&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/co4XET/dJMcaicKkVt/s0t3CVVSkUngiw5YWRUuj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/co4XET/dJMcaicKkVt/s0t3CVVSkUngiw5YWRUuj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/co4XET/dJMcaicKkVt/s0t3CVVSkUngiw5YWRUuj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fco4XET%2FdJMcaicKkVt%2Fs0t3CVVSkUngiw5YWRUuj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1453&quot; height=&quot;30&quot; data-filename=&quot;스크린샷 2026-06-20 오전 6.09.00.png&quot; data-origin-width=&quot;1453&quot; data-origin-height=&quot;30&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 멀티 턴 대화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM은 기본적으로 Stateless 특성을 가진다. 이전 내용을 유지하는 방법을 배워보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이력 관리를 위해 messages 리스트에 누적이 필요하다. 그리고 그 내용을 보내줘야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781904162429&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from langchain_core.messages import HumanMessage, SystemMessage, AIMessage

messages = [
    SystemMessage(content=&quot;당신은 친절한 파이썬 튜터입니다. 한국어로 답변하세요.&quot;)
]

def chat(user_input):
    messages.append(HumanMessage(user_input))
    response = qwen_llm.invoke(messages)
    messages.append(AIMessage(response.content))
    return response.content

print(chat(&quot;파이썬이란?&quot;))
print(chat(&quot;방금 말한 내용의 장점 3가지는?&quot;))
print(chat(&quot;그 중 첫번째 장점에 대한 예시 코드 작성해줘.&quot;))
print(chat(&quot;두 번째 장점에 대한 예시 코드 작성해줘.&quot;))

for m in messages:
    print(f&quot;[{m.type}] {m.content[:40]}...&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오전 6.24.27.png&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfVWEu/dJMcaf1if6b/s8aMK0LH2JsJvojTkPNHD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfVWEu/dJMcaf1if6b/s8aMK0LH2JsJvojTkPNHD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfVWEu/dJMcaf1if6b/s8aMK0LH2JsJvojTkPNHD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfVWEu%2FdJMcaf1if6b%2Fs8aMK0LH2JsJvojTkPNHD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;574&quot; height=&quot;241&quot; data-filename=&quot;스크린샷 2026-06-20 오전 6.24.27.png&quot; data-origin-width=&quot;574&quot; data-origin-height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 이렇게 하지 않고, 자동으로 이력 관리를 해주는 개념이 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 &lt;b&gt;RunnableWithMessageHistory&lt;/b&gt;이다. 세션별로 대화 이력 관리를 하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, &lt;b&gt;InMemoryChatMessageHistory&lt;/b&gt; 객체를 이용한다. (메모리에 대화를 저장)&lt;/p&gt;
&lt;pre id=&quot;code_1781905229853&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from langchain_core.chat_history import InMemoryChatMessageHistory

history = InMemoryChatMessageHistory()

def chat(user_input):
    history.add_user_message(user_input)
    response = qwen_llm.invoke(history.messages) # 전체 대화 맥락 보내주기
    history.add_ai_message(response.content)
    return response.content

chat(&quot;파이썬이란?&quot;)
chat(&quot;방금 말한 내용의 장점 3가지는?&quot;)
chat(&quot;그 중 첫번째 장점에 대한 예시 코드 작성해줘.&quot;)
chat(&quot;두 번째 장점에 대한 예시 코드 작성해줘.&quot;)

for m in history.message:
    print(f&quot;[{m.type}] {m.content[:40]}...&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오전 6.42.46.png&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mXyQz/dJMcadWJyOn/zlCigm5i6VXR80Gq8OVue0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mXyQz/dJMcadWJyOn/zlCigm5i6VXR80Gq8OVue0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mXyQz/dJMcadWJyOn/zlCigm5i6VXR80Gq8OVue0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmXyQz%2FdJMcadWJyOn%2FzlCigm5i6VXR80Gq8OVue0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;557&quot; height=&quot;232&quot; data-filename=&quot;스크린샷 2026-06-20 오전 6.42.46.png&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이 방식에는 한계가 있다. 만약 메모리에다가 계속 쌓는다면, 메모리 부하가 생길지도 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 모델의 한계 컨텍스트 크기도 넘어갈 수 있다. (컨텍스트 오버플로우)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그전에 먼저 세션에 저장해서 기록하는 방법부터 알아보자. (여러 사람과 대화하는 것을 고민)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것이 &lt;b&gt;RunnableWithMessageHistory&lt;/b&gt; 객체이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781907182466&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 여러 사람과 대화하는 부분 저장 =&amp;gt; 세션별로 저장

store = {}

def get_session_history(session_id) -&amp;gt; BaseChatMessageHistory:
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
    return store[session_id]

# 프롬프트 생성
prompt = ChatPromptTemplate.from_messages([
    (&quot;system&quot;, &quot;당신은 {role}입니다. 한국어로 답변하세요.&quot;),
    MessagesPlaceholder(variable_name=&quot;history&quot;), # 대화 이력이 삽입될 위치임을 알려줌
    (&quot;human&quot;, &quot;{input}&quot;)
])

# LLM 생성 문구
qwen_llm = ChatOllama(model='qwen2.5')

# 체인 생성
chain = prompt | qwen_llm | StrOutputParser()

# 1. session_id 이력 조회

# 2. 없다면 history 키에 이력 삽입 -&amp;gt; prompt -&amp;gt; llm 실행 -&amp;gt; 대화 나눈 이력을 history에 추가

with_history = RunnableWithMessageHistory(chain, get_session_history=get_session_history, input_messages_key=&quot;input&quot;, history_messages_key=&quot;history&quot;)

# 세션 생성
config_a = { &quot;configurable&quot;: {&quot;session_id&quot;: &quot;user_alice&quot;}}
config_b = { &quot;configurable&quot;: {&quot;session_id&quot;: &quot;user_bob&quot;}}

res1 = with_history.invoke({ &quot;role&quot;: &quot;파이썬 튜터&quot;, &quot;input&quot;: &quot;안녕&quot;}, config=config_a)
res2 = with_history.invoke({ &quot;role&quot;: &quot;파이썬 튜터&quot;, &quot;input&quot;: &quot;내 이름은 Alice야.&quot;}, config=config_a)
res4 = with_history.invoke({ &quot;role&quot;: &quot;파이썬 튜터&quot;, &quot;input&quot;: &quot;내 이름 기억해?&quot;}, config=config_a)

res3 = with_history.invoke({ &quot;role&quot;: &quot;파이썬 튜터&quot;, &quot;input&quot;: &quot;파이썬 변수 타입은?&quot;}, config=config_b)


print(&quot;Alice &quot;, res2)
print(&quot;Alice &quot;, res4)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오전 7.14.30.png&quot; data-origin-width=&quot;1205&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5bpXX/dJMcagMLsGh/JkPitrRx2RTmZiGshIePmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5bpXX/dJMcagMLsGh/JkPitrRx2RTmZiGshIePmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5bpXX/dJMcagMLsGh/JkPitrRx2RTmZiGshIePmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5bpXX%2FdJMcagMLsGh%2FJkPitrRx2RTmZiGshIePmk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1205&quot; height=&quot;50&quot; data-filename=&quot;스크린샷 2026-06-20 오전 7.14.30.png&quot; data-origin-width=&quot;1205&quot; data-origin-height=&quot;50&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텍스트 관리를 위해 개선하는 방향을 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 최근 K 턴만 유지하기 (K=8)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781907740886&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def get_session_history(session_id) -&amp;gt; BaseChatMessageHistory:
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()

    # 최근 몇개만 유지
    history = store[session_id]

    if len(history.messages) &amp;gt; 8:
        history.messages[:] = history.messages[-8:]

    return history

with_history = RunnableWithMessageHistory(chain, get_session_history=get_session_history, input_messages_key=&quot;input&quot;, history_messages_key=&quot;history&quot;)

for i in range(10):
    with_history.invoke({ &quot;role&quot;: &quot;파이썬 튜터&quot;, &quot;input&quot;: f'{i}번째 질문'}, config=config_a)

history = get_session_history(&quot;user_alice&quot;)

for msg in history.messages:
    print(msg)

print(len(history.messages))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오전 7.23.06.png&quot; data-origin-width=&quot;1505&quot; data-origin-height=&quot;197&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xvSXS/dJMcaa6P60K/ykf3jC6wTyXTBfEr5XcThK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xvSXS/dJMcaa6P60K/ykf3jC6wTyXTBfEr5XcThK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xvSXS/dJMcaa6P60K/ykf3jC6wTyXTBfEr5XcThK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxvSXS%2FdJMcaa6P60K%2Fykf3jC6wTyXTBfEr5XcThK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1505&quot; height=&quot;197&quot; data-filename=&quot;스크린샷 2026-06-20 오전 7.23.06.png&quot; data-origin-width=&quot;1505&quot; data-origin-height=&quot;197&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 오래된 내용은 압축해서 보여주기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781910583417&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 2. 오래된 대화 요약

store = {}

# 요약 프롬프트
summary_prompt = ChatPromptTemplate.from_messages([
    (&quot;system&quot;, &quot;다음 대화 내용을 핵심만 3문장 이내로 한국어로 요약하세요.&quot;),
    (&quot;human&quot;, &quot;{conversation}&quot;)
])

# 요약 체인
summaries_chain = summary_prompt | qwen_llm | StrOutputParser()

# 요약 기능이 포함된 클래스
class SummarizedChatHistory(InMemoryChatMessageHistory):
    &quot;&quot;&quot;대화가 max_turns 초과 시 오래된 메시지 요약하기&quot;&quot;&quot;
    max_turns: int = Field(default=6)
    summary: str = Field(default=&quot;&quot;)

    def _maybe_summarize(self):
        if len(self.messages) &amp;gt; self.max_turns * 2:
            # 오래된 내용 찾기
            cutoff = len(self.messages) // 2
            old_msgs = self.messages[:cutoff]
            new_msgs = self.messages[cutoff:]

            # 요약할 텍스트 구성
            conv_text = &quot;\n&quot;.join(f&quot;{'사용자' if isinstance(m, HumanMessage) else &quot;AI&quot;}: {m.content}&quot; for m in old_msgs)

            # 기존 요약이 있으며 함께 포함
            if self.summary:
                conv_text = f&quot;[이전 요약]\n{self.summary}\n\n[새 대화]\n{conv_text}&quot;

            # 요약 실행
            self.summary = summary_chain.invoke({ &quot;conversation&quot;: conv_text })

            # 오래된 메시지 제거 후 요약본 SystemMessage로 앞에 삽입
            self.messages.clear()
            self.messages.append(SystemMessage(content=f&quot;[이전 대화 요약]\n{self.summary}&quot;))
            self.messages.extend(new_msgs)

    def add_message(self, message):
        super().add_message(message)
        self._maybe_summarize()
        

def get_session_history(session_id) -&amp;gt; SummarizedChatHistory:
    if session_id not in store:
        store[session_id] = SummarizedChatHistory(max_turns=6)
    
    return store[session_id]

prompt = ChatPromptTemplate.from_messages([
    (&quot;system&quot;, &quot;당신은 친절한 AI 어시스턴트입니다. 한국어로 답변하세요&quot;),
    MessagesPlaceholder(variable_name=&quot;history&quot;),
    (&quot;human&quot;, &quot;{input}&quot;)
])

chain = prompt | qwen_llm | StrOutputParser()

with_history = RunnableWithMessageHistory(chain, get_session_history=get_session_history, input_messages_key=&quot;input&quot;, history_messages_key=&quot;history&quot;)

# 세션 임의 생성
cfg_a = {&quot;configurable&quot;: { &quot;session_id&quot;: &quot;user_alice&quot;}}

# 임의 대화 생성
questions = [
    &quot;파이썬이란?&quot;,
    &quot;방금 설명한 파이썬의 장점은?&quot;,
    &quot;단점은 뭐야?&quot;,
    &quot;어떤 분야에 많이 쓰여?&quot;,
    &quot;입문자에게 추천하는 학습 순서는?&quot;,
    &quot;좋은 파이썬 책 추천해줘&quot;,
    &quot;무료로 배울 수 있는 사이트는?&quot;,
    &quot;이전에 내가 뭘 물어봤는지 기억해?&quot;
]

for i, q in enumerate(questions, i):
    response = with_history.invoke({&quot;input&quot;: q}, config=cfg_a)
    print(f&quot;\n{i}턴 Q:{q}&quot;)
    print(f&quot;\n      A: {response[:60]}....&quot;)

    # 요약 여부 확인
    history = get_session_history(&quot;user_alice&quot;)
    if history.summary:
        print(f&quot;\n 요약 처리! 현재 메시지 수 {len(history.messages)}&quot;)
        print(f&quot;\n 요약 내용 {history.summary[:80]}....&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) 요리 전문가 챗봇 백엔드 수정하기 (수작업X, 메시지 히스토리 객체 이용)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781934660589&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from fastapi import APIRouter
from app.schemas.chat import ChatRequest, ChatResponse, ChatPairResponse, LLMChatOutput
from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.chat_history import (
    InMemoryChatMessageHistory,
    BaseChatMessageHistory,
)
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.runnables.history import RunnableWithMessageHistory

router = APIRouter()

store = {}


def chain_return():
    structured_llm = ChatOllama(model=&quot;qwen2.5&quot;)

    system_prompt = &quot;&quot;&quot;
    당신은 20년 경력의 전문 세프이자, 요리 연구가입니다.
    사용자의 요리 질문에 대해 재료, 조리방법, 실패 방지 팁, 대체 재료를 포함하여 답변하세요.
    항상 한국어로 답변하세요.
    &quot;&quot;&quot;

    template = ChatPromptTemplate.from_messages(
        [
            (&quot;system&quot;, system_prompt),
            MessagesPlaceholder(variable_name=&quot;history&quot;),
            (&quot;human&quot;, &quot;{question}&quot;),
        ]
    )

    chain = template | structured_llm

    return RunnableWithMessageHistory(
        chain,
        get_session_history,
        input_messages_key=&quot;question&quot;,
        history_messages_key=&quot;history&quot;,
    )


def get_session_history(session_id: int) -&amp;gt; BaseChatMessageHistory:
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
    return store[session_id]


@router.post(&quot;/chat-input&quot;, response_model=ChatPairResponse)
async def chat(body: ChatRequest) -&amp;gt; str:
    session_id = body.session_id
    message = body.message

    chain = chain_return()

    cfg_user = {&quot;configurable&quot;: {&quot;session_id&quot;: session_id}}

    # llm_chat_output
    response = chain.invoke({&quot;question&quot;: message}, config=cfg_user)

    chat_user_response = ChatResponse(
        session_id=session_id, sender=&quot;me&quot;, content=message
    )

    chat_bot_response = ChatResponse(
        session_id=session_id + 1, sender=&quot;bot&quot;, content=response.content
    )

    return ChatPairResponse(chats=[chat_user_response, chat_bot_response])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 3.02.44.png&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mDT9R/dJMcaiKtjkd/1rF88wL9BHZRortqcMckpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mDT9R/dJMcaiKtjkd/1rF88wL9BHZRortqcMckpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mDT9R/dJMcaiKtjkd/1rF88wL9BHZRortqcMckpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmDT9R%2FdJMcaiKtjkd%2F1rF88wL9BHZRortqcMckpK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1060&quot; height=&quot;344&quot; data-filename=&quot;스크린샷 2026-06-20 오후 3.02.44.png&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 번외 : 허깅 페이스 모델 사용하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;허깅 페이스에서 Models 항목에서 이 옵션을 체크한다. (외부에서 불러다 쓸 수 있는 모델)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 3.05.51.png&quot; data-origin-width=&quot;435&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Thnfw/dJMcaaMunLE/5YLoEP8umG0yj8E08AlE11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Thnfw/dJMcaaMunLE/5YLoEP8umG0yj8E08AlE11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Thnfw/dJMcaaMunLE/5YLoEP8umG0yj8E08AlE11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FThnfw%2FdJMcaaMunLE%2F5YLoEP8umG0yj8E08AlE11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;435&quot; height=&quot;64&quot; data-filename=&quot;스크린샷 2026-06-20 오후 3.05.51.png&quot; data-origin-width=&quot;435&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 &lt;b&gt;Qwen2.5-Coder-7B-Instruct&lt;/b&gt; 모델을 사용해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 상세페이지에 들어가 &lt;b&gt;View Code Snippets&lt;/b&gt; 클릭한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 3.08.59.png&quot; data-origin-width=&quot;611&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mxOsI/dJMcaaFMC71/HWEHZtdLLAqQk3mXUkVEWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mxOsI/dJMcaaFMC71/HWEHZtdLLAqQk3mXUkVEWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mxOsI/dJMcaaFMC71/HWEHZtdLLAqQk3mXUkVEWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmxOsI%2FdJMcaaFMC71%2FHWEHZtdLLAqQk3mXUkVEWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;611&quot; height=&quot;422&quot; data-filename=&quot;스크린샷 2026-06-20 오후 3.08.59.png&quot; data-origin-width=&quot;611&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 openai를 이용하여 접근할 것인데, 코드 보면 알다시피 HF_TOKEN이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 Access Token 탭에 들어가 발급받는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 3.11.15.png&quot; data-origin-width=&quot;766&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bObcYt/dJMcahrmiFf/7ei4pP6pH8ZVayVjljbBf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bObcYt/dJMcahrmiFf/7ei4pP6pH8ZVayVjljbBf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bObcYt/dJMcahrmiFf/7ei4pP6pH8ZVayVjljbBf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbObcYt%2FdJMcahrmiFf%2F7ei4pP6pH8ZVayVjljbBf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;766&quot; height=&quot;386&quot; data-filename=&quot;스크린샷 2026-06-20 오후 3.11.15.png&quot; data-origin-width=&quot;766&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781936333260&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;load_dotenv()

hf_token = os.getenv(&quot;HF_TOKEN&quot;)

client = OpenAI(
    base_url=&quot;https://router.huggingface.co/v1&quot;,
    api_key=hf_token,
)

completion = client.chat.completions.create(
    model=&quot;Qwen/Qwen2.5-Coder-7B-Instruct:nscale&quot;,
    messages=[
        {
            &quot;role&quot;: &quot;user&quot;,
            &quot;content&quot;: &quot;What is the capital of France?&quot;
        }
    ],
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고, langchain에서도 openai 항목을 지원한다.&lt;/p&gt;
&lt;pre id=&quot;code_1781937621240&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from langchain_openai import ChatOpenAI

hugging_llm = ChatOpenAI(model=&quot;Qwen/Qwen2.5-Coder-7B-Instruct&quot;,api_key=hf_token,base_url=&quot;https://router.huggingface.co/v1&quot;)

response = hugging_llm.invoke(&quot;생성형 AI 설명해줘.&quot;)

print(response.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오후 3.40.45.png&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eaoiPM/dJMcabdu5Wy/F1M3nwZza4BbSefk66SBQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eaoiPM/dJMcabdu5Wy/F1M3nwZza4BbSefk66SBQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eaoiPM/dJMcabdu5Wy/F1M3nwZza4BbSefk66SBQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeaoiPM%2FdJMcabdu5Wy%2FF1M3nwZza4BbSefk66SBQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1067&quot; height=&quot;378&quot; data-filename=&quot;스크린샷 2026-06-20 오후 3.40.45.png&quot; data-origin-width=&quot;1067&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>AI/LLM</category>
      <category>RunnableSequence</category>
      <author>EunHaa</author>
      <guid isPermaLink="true">https://eun-haa.tistory.com/32</guid>
      <comments>https://eun-haa.tistory.com/32#entry32comment</comments>
      <pubDate>Sat, 20 Jun 2026 15:42:03 +0900</pubDate>
    </item>
    <item>
      <title>랭체인(LangChain) - 요리 전문가 챗봇 실습 (2)</title>
      <link>https://eun-haa.tistory.com/31</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;다음 조건으로 요리 전문가 챗봇 앱을 제작해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-19 오후 10.51.09.png&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwqpJI/dJMcaaMt6ZU/CAzTeOKBOciS22osI3eRXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwqpJI/dJMcaaMt6ZU/CAzTeOKBOciS22osI3eRXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwqpJI/dJMcaaMt6ZU/CAzTeOKBOciS22osI3eRXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwqpJI%2FdJMcaaMt6ZU%2FCAzTeOKBOciS22osI3eRXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;841&quot; height=&quot;182&quot; data-filename=&quot;스크린샷 2026-06-19 오후 10.51.09.png&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;182&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 UI 먼저 구성해보자. 다음과 같이 구성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-19 오후 11.58.51.png&quot; data-origin-width=&quot;1367&quot; data-origin-height=&quot;872&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buUXXu/dJMcagTulQm/EelCuTQKlIBCGIX66SDjU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buUXXu/dJMcagTulQm/EelCuTQKlIBCGIX66SDjU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buUXXu/dJMcagTulQm/EelCuTQKlIBCGIX66SDjU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuUXXu%2FdJMcagTulQm%2FEelCuTQKlIBCGIX66SDjU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1367&quot; height=&quot;872&quot; data-filename=&quot;스크린샷 2026-06-19 오후 11.58.51.png&quot; data-origin-width=&quot;1367&quot; data-origin-height=&quot;872&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/app/food/page.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781881185568&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import ChatBotDiv from &quot;@/components/food/ChatBotDiv&quot;;

export default function FoodPage() {
    return (
        &amp;lt;main className=&quot;w-full h-screen bg-[#ece0cb]&quot;&amp;gt;
            &amp;lt;div id=&quot;wrap&quot; className=&quot;max-w-lg mx-auto h-full bg-[#f6eddf]&quot;&amp;gt;
                &amp;lt;ChatBotDiv /&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/main&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/app/components/food/ChatBotDiv.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781881235909&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;use client&quot;;

import { useEffect, useState } from &quot;react&quot;;
import { useRouter } from &quot;next/navigation&quot;;
import { IoArrowForwardSharp } from &quot;react-icons/io5&quot;;
import BotChatting from &quot;./BotChatting&quot;;
import ChatBotProfile from &quot;./ChatBotProfile&quot;;
import HumanChatting from &quot;./HumanChatting&quot;;



export interface ChatContent {
    session_id: number;
    sender: &quot;bot&quot; | &quot;me&quot;;
    content: string;
    added_button_list: string[] | null
}

export default function ChatBotDiv() {

    const [inputText, setInputText] = useState&amp;lt;string&amp;gt;(&quot;&quot;);

    const [chatContents, setChatContents] = useState&amp;lt;ChatContent[]&amp;gt;([{ session_id: 0, sender: &quot;bot&quot;, content: &quot;안녕~ 어서와요 ^^ 오늘은 뭐가 드시고 싶어요?|냉장고에 뭐가 있는지 말하면 딱 맞는 메뉴 말해줄게요.&quot;, added_button_list: [&quot;10분 안에 되는 메뉴&quot;, &quot;자취 첫 요리 추천&quot;, &quot;계란으로 뭐 해먹지?&quot;] }]);

    const [showedManyButton, setShowedManyButton] = useState&amp;lt;boolean&amp;gt;(true);

    const changeShowed = (bool: boolean) =&amp;gt; {
        setShowedManyButton(bool)
    }

    const router = useRouter();

    const changeInput = (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
        setInputText(e.target.value)
    }


    return (
        &amp;lt;div id=&quot;chatBotDiv&quot; className=&quot;w-full h-full relative&quot;&amp;gt;
            &amp;lt;div id=&quot;chatBotHeader&quot; className=&quot;w-full h-[70px] relative box-border bg-[#faf3e9] flex items-center gap-[10px] px-[16px] border-b-[1px] border-[#e6e0d2] &quot;&amp;gt;
                &amp;lt;ChatBotProfile /&amp;gt;
                &amp;lt;div id=&quot;chatBotInfo&quot; className=&quot;flex flex-col justify-between&quot;&amp;gt;
                    &amp;lt;span className=&quot;font-[800]&quot;&amp;gt;정이 이모&amp;lt;/span&amp;gt;
                    &amp;lt;div className=&quot;status flex items-center gap-[5px] text-[#9a7b52] text-[14px]&quot;&amp;gt;
                        &amp;lt;span className=&quot;block w-[8px] h-[8px] rounded-full bg-[#7fa968]&quot;&amp;gt;&amp;lt;/span&amp;gt;
                        &amp;lt;span&amp;gt;온라인&amp;lt;/span&amp;gt;
                        &amp;lt;span&amp;gt;오늘 뭐 해먹지?&amp;lt;/span&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;button className=&quot;text-[14px] absolute right-[20px] cursor-pointer&quot; onClick={() =&amp;gt; router.push(&quot;/&quot;)}&amp;gt;돌아가기&amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div id=&quot;chatBotBody&quot; className=&quot;w-full h-[calc(100%-150px)] overflow-y-auto p-[16px] flex flex-col gap-[20px]&quot;&amp;gt;
                {
                    chatContents.map((chat: ChatContent) =&amp;gt; (
                        chat.sender === 'bot' ? &amp;lt;BotChatting key={chat.session_id} chatting={chat} showed={showedManyButton} changeShowed={changeShowed} /&amp;gt; : &amp;lt;HumanChatting key={chat.session_id} chatting={chat} /&amp;gt;
                    ))
                }
            &amp;lt;/div&amp;gt;
            &amp;lt;div id=&quot;chatBotFooter&quot; className=&quot;w-full h-[80px] bg-[#f9f2e8] absolute bottom-0 left-0 p-[20px] box-border&quot;&amp;gt;
                &amp;lt;div id=&quot;inputWrap&quot; className=&quot;w-full h-[50px] box-border px-[10px] rounded-[20px] bg-[#fffdf8] border-[#7a5636] border-[1px] border-[#7a5636] flex items-center gap-[10px]&quot;&amp;gt;
                    &amp;lt;input type=&quot;text&quot; className=&quot;flex-1 h-full outline-none px-[20px] placeholder:text-[#c9baa7]&quot; placeholder=&quot;냉장고에 뭐가 있어요?&quot; value={inputText} onChange={changeInput} /&amp;gt;
                    &amp;lt;button className=&quot;w-[36px] h-[36px] rounded-full bg-[#c98a52] flex justify-center items-center disabled:opacity-45 cursor-pointer&quot; disabled={!inputText.trim()}&amp;gt;
                        &amp;lt;IoArrowForwardSharp className=&quot;text-white text-[20px]&quot; /&amp;gt;
                    &amp;lt;/button&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/app/components/food/BotChatting.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781881292308&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { type ChatContent } from &quot;./ChatBotDiv&quot;;
import ChatBotProfile from &quot;./ChatBotProfile&quot;;

export default function BotChatting({ chatting, showed, changeShowed }: { chatting: ChatContent, showed: boolean, changeShowed: (bool: boolean) =&amp;gt; void }) {
    return (
        &amp;lt;div className=&quot;bot__chat flex flex-wrap gap-[10px]&quot;&amp;gt;
            &amp;lt;ChatBotProfile className=&quot;self-end&quot; /&amp;gt;
            &amp;lt;div className=&quot;bot__chat__content text-[15px] p-[18px] bg-[#fffdf8] rounded-[20px_20px_20px_6px] font-[400]&quot;&amp;gt;
                {chatting.content.includes(&quot;|&quot;) ? chatting.content.split(&quot;|&quot;).map((content: string, idx: number) =&amp;gt; (&amp;lt;span key={idx}&amp;gt;{content}&amp;lt;br /&amp;gt;&amp;lt;/span&amp;gt;)) : &amp;lt;span&amp;gt;{chatting.content}&amp;lt;/span&amp;gt;}
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/app/components/food/HumanChatting.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781881324915&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { type ChatContent } from &quot;./ChatBotDiv&quot;;

export default function HumanChatting({ chatting }: { chatting: ChatContent }) {
    return (
        &amp;lt;div className=&quot;human__chat__content rounded-[20px_20px_6px_20px] inline-block p-[18px] bg-[#c98a52] text-white text-[15px] font-[400] self-end&quot;&amp;gt;{chatting.content}&amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 나서 조건대로 FastAPI 백엔드를 구성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라우터는 다음과 같이 구성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건에서는 StrOutputParser를 이용하라고 했으나, 내용 특성상 Pydantic를 이용하자.&lt;/p&gt;
&lt;pre id=&quot;code_1781883390728&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from fastapi import APIRouter
from app.schemas.chat import ChatRequest, ChatResponse, ChatPairResponse, LLMChatOutput
from langchain_ollama import ChatOllama
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import ChatPromptTemplate

router = APIRouter()

qwen_llm = ChatOllama(model=&quot;qwen2.5&quot;)


@router.post(&quot;/chat-input&quot;, response_model=ChatPairResponse)
async def chat(body: ChatRequest) -&amp;gt; ChatPairResponse:
    session_id = body.session_id
    message = body.message

    parser = PydanticOutputParser(pydantic_object=LLMChatOutput)

    prompt = ChatPromptTemplate.from_messages(
        [
            (
                &quot;system&quot;,
                &quot;당신은 사용자의 냉장고 재료를 바탕으로 요리를 추천해주는 AI 어시스턴트입니다. 반드시 아래 형식에 맞게 JSON으로만 답변하세요. 단, content 항목은 적절히 | 으로 구분해주세요. : {format_instructions}&quot;,
            ),
            (
                &quot;human&quot;,
                &quot;냉장고에 있는 재료 또는 원하는 음식 : {message}&quot;,
            ),
        ]
    ).partial(format_instructions=parser.get_format_instructions())

    chain = prompt | qwen_llm | parser

    llm_output: LLMChatOutput = chain.invoke({&quot;message&quot;: message})

    human_chat = ChatResponse(
        session_id=session_id,
        sender=&quot;me&quot;,
        content=message,
        added_button_list=None,
    )
    bot_chat = ChatResponse(
        session_id=session_id + 1,
        sender=&quot;bot&quot;,
        content=llm_output.content,
        added_button_list=llm_output.added_button_list,
    )
    return ChatPairResponse(chats=[human_chat, bot_chat])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로, human 채팅 내용과 bot 채팅 내용을 합쳐서 계속 채팅 내역이 쌓이게끔 하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1781885875811&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [chatContents, setChatContents] = useState&amp;lt;ChatContent[]&amp;gt;([{ session_id: 0, sender: &quot;bot&quot;, content: &quot;안녕~ 어서와요 ^^ 오늘은 뭐가 드시고 싶어요?|냉장고에 뭐가 있는지 말하면 딱 맞는 메뉴 말해줄게요.&quot; }]);

const [showedManyButton, setShowedManyButton] = useState&amp;lt;boolean&amp;gt;(true);

const [loading, setLoading] = useState&amp;lt;boolean&amp;gt;(false);

const postInput = async () =&amp;gt; {
        try {
            setLoading(true);
            setShowedManyButton(false)
            const response = await fetch(&quot;http://localhost:8000/api/chat-input&quot;, {
                method: 'POST',
                body: JSON.stringify({
                    message: inputText,
                    session_id: chatContents[chatContents.length - 1].session_id + 1
                }),
                headers: {
                    'Content-Type': 'application/json'
                }
            })
            const json = await response.json();
            setChatContents(prev =&amp;gt; [...prev, ...json.chats])
            setInputText(&quot;&quot;);
        } catch (error) {
            console.error('Error post input:', error);
        } finally {
            setLoading(false);
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-20 오전 1.18.34.png&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;872&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvJFFN/dJMcaf720pd/DIy9I5uVRMEbJ5s4HKtC00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvJFFN/dJMcaf720pd/DIy9I5uVRMEbJ5s4HKtC00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvJFFN/dJMcaf720pd/DIy9I5uVRMEbJ5s4HKtC00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvJFFN%2FdJMcaf720pd%2FDIy9I5uVRMEbJ5s4HKtC00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;566&quot; height=&quot;872&quot; data-filename=&quot;스크린샷 2026-06-20 오전 1.18.34.png&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;872&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>AI/LLM</category>
      <author>EunHaa</author>
      <guid isPermaLink="true">https://eun-haa.tistory.com/31</guid>
      <comments>https://eun-haa.tistory.com/31#entry31comment</comments>
      <pubDate>Sat, 20 Jun 2026 01:19:19 +0900</pubDate>
    </item>
    <item>
      <title>랭체인(LangChain) - 개념 (1)</title>
      <link>https://eun-haa.tistory.com/30</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 랭체인 (LangChain)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;LLM 기반 애플리케이션 개발을 위해서 사용하는 JS/Python 기반 프레임워크&lt;/li&gt;
&lt;li&gt;Prompt - LLM - OutputParser 흐름을 체인으로 연결하는 파이프라인 도구&lt;/li&gt;
&lt;li&gt;RAG, Agent, Memory, Tools 등 복잡한 패턴을 표준화된 인터페이스로 제공&lt;/li&gt;
&lt;li&gt;Ollama, OpenAI, HuggingFace, Watsonx 등 LLM 백엔드를 동일한 코드로 교체&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;1) 구성요소&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;langchain-core : 프롬프트, 템플릿, 아웃풋 파서 등 랭체인의 기본적인 추상화와 인터페이스 제공&lt;/li&gt;
&lt;li&gt;LCEL : 여러 단계를 거쳐 처리되는 작업 흐름(체인)을 코드 몇줄로 간단하고 직관적으로 만드는 문법&lt;/li&gt;
&lt;li&gt;LangGraph : 복잡한 워크플로우와 에이전트 시스템을 구축하는 그래프 기반 프레임워크&lt;/li&gt;
&lt;li&gt;LangServe : LangChain 애플리케이션을 API로 제공하는 서빙 프레임워크&lt;/li&gt;
&lt;li&gt;LangSmith : 개발, 테스트, 모니터링을 위한 디버깅 관찰 도구&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 설치 및 환경 구성&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780726163661&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install langchain langchain-community langchain-ollama langchain-core&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1780726304841&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from langchain_ollama import ChatOllama
from langchain_ibm import ChatWatsonx
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser, PydanticOutputParser&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) LangChain + Watsonx&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780726352883&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Watsonx 설정
load_dotenv()

api_key = os.getenv(&quot;WATSONX_API_KEY&quot;)
project_id = os.getenv(&quot;WATSONX_PROJECT_ID&quot;)
watsonx_ai_url = os.getenv(&quot;WATSONX_AI_URL&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1780726369248&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Langchain Frameworks 안에 Watsonx 사용하기

watson_llm = ChatWatsonx(
    model_id=&quot;ibm/granite-4-h-small&quot;,
    url=watsonx_ai_url,
    api_key=api_key,
    project_id=project_id,
    max_tokens = 2000
)

response = watson_llm.invoke(&quot;생성형 AI를 설명해줘.&quot;)
print(response.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-06 오후 3.13.10.png&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQxN5G/dJMcagZ1RIe/0qE7j8UfX5fZxEzSPaHoQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQxN5G/dJMcagZ1RIe/0qE7j8UfX5fZxEzSPaHoQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQxN5G/dJMcagZ1RIe/0qE7j8UfX5fZxEzSPaHoQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQxN5G%2FdJMcagZ1RIe%2F0qE7j8UfX5fZxEzSPaHoQ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1858&quot; height=&quot;394&quot; data-filename=&quot;스크린샷 2026-06-06 오후 3.13.10.png&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4) LangChain + Ollama&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780726458784&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Langchain Frameworks 안에 Ollama 사용하기
qwen_llm = ChatOllama(model=&quot;qwen3.5:4b&quot;)

response = qwen_llm.invoke(&quot;생성형 AI를 설명해줘&quot;)
print(response.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-06 오후 3.14.36.png&quot; data-origin-width=&quot;1840&quot; data-origin-height=&quot;754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfyq6l/dJMcagsemVr/Rns11YTWmUFyJgHkkHk8sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfyq6l/dJMcagsemVr/Rns11YTWmUFyJgHkkHk8sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfyq6l/dJMcagsemVr/Rns11YTWmUFyJgHkkHk8sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcfyq6l%2FdJMcagsemVr%2FRns11YTWmUFyJgHkkHk8sk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1840&quot; height=&quot;754&quot; data-filename=&quot;스크린샷 2026-06-06 오후 3.14.36.png&quot; data-origin-width=&quot;1840&quot; data-origin-height=&quot;754&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5) PromptTemplate&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수가 있는 프롬프트 틀로써, f-string과 유사하지만, 타입 검증과 재사용성을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 문자열 프롬프트를 생성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1780726684415&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;### 기본 생성
template = PromptTemplate(input_variables=['topic','level'],template=&quot;{level} 수준으로 {topic}를 설명해줘. 예시 포함&quot;)
print(template.input_variables)
prompt_txt = template.format(topic=&quot;재귀 함수&quot;, level=&quot;초급자&quot;)
print(prompt_txt)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-06 오후 3.18.26.png&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3pAFt/dJMcacpRIIP/t9Id6zyWI34GLXNbi1E1u1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3pAFt/dJMcacpRIIP/t9Id6zyWI34GLXNbi1E1u1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3pAFt/dJMcacpRIIP/t9Id6zyWI34GLXNbi1E1u1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3pAFt%2FdJMcacpRIIP%2Ft9Id6zyWI34GLXNbi1E1u1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1852&quot; height=&quot;128&quot; data-filename=&quot;스크린샷 2026-06-06 오후 3.18.26.png&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 input_variables 설정을 없애고 간소화한 코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780726800011&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;template = PromptTemplate.from_template(&quot;{level} 수준으로 {topic}을 설명해줘. 예시 포함&quot;)
print(template.input_variables)
prompt_txt = template.format(topic=&quot;재귀함수&quot;,level=&quot;초급자&quot;)
print(prompt_txt)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-06 오후 3.18.26.png&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwNOiO/dJMcabYNB5X/PDRFtWTMkX8rJ1hwXzXmx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwNOiO/dJMcabYNB5X/PDRFtWTMkX8rJ1hwXzXmx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwNOiO/dJMcabYNB5X/PDRFtWTMkX8rJ1hwXzXmx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwNOiO%2FdJMcabYNB5X%2FPDRFtWTMkX8rJ1hwXzXmx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1852&quot; height=&quot;128&quot; data-filename=&quot;스크린샷 2026-06-06 오후 3.18.26.png&quot; data-origin-width=&quot;1852&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1780726843851&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;template = PromptTemplate.from_template(&quot;&quot;&quot;
다음 질문에 답변하세요.

질문:
{question}
&quot;&quot;&quot;)

formatted_prompt = template.format(question=&quot;SQL Injection 이란?&quot;)
response = watson_llm.invoke(formatted_prompt)
print(response.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-06 오후 3.20.58.png&quot; data-origin-width=&quot;1934&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRuQuv/dJMcajvI0vZ/G1XHX6jP5bwbCASqXscoL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRuQuv/dJMcajvI0vZ/G1XHX6jP5bwbCASqXscoL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRuQuv/dJMcajvI0vZ/G1XHX6jP5bwbCASqXscoL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRuQuv%2FdJMcajvI0vZ%2FG1XHX6jP5bwbCASqXscoL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1934&quot; height=&quot;302&quot; data-filename=&quot;스크린샷 2026-06-06 오후 3.20.58.png&quot; data-origin-width=&quot;1934&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6) ChatPromptTemplate&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;채팅 메시지 형식을 제공하고, from_template()과 from_messages() 메소드를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;from_template()의 경우 human 메시지만 생성하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;from_messages()의 경우 여러 메시지를 구성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;from_template&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780727015450&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;template = ChatPromptTemplate.from_template(&quot;&quot;&quot;\
다음 질문에 답변하시오.

질문:
{question}

&quot;&quot;&quot;)

formatted_prompt = template.format(question=&quot;SQL Injection에 대해 설명해줘&quot;)
response = watson_llm.invoke(formatted_prompt)
print(response.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;from_messages&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780727056065&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;template = ChatPromptTemplate.from_messages(
    [
        (&quot;system&quot;, &quot;당신은 {role} 전문가입니다. {language}로 답변하세요&quot;),
        (&quot;human&quot;,&quot;{question}&quot;)
    ]
)

formatted_prompt = template.format(role=&quot;파이썬&quot;,language=&quot;한국어&quot;,question=&quot;리스트 컴프리헨션 이란?&quot;)
response = watson_llm.invoke(formatted_prompt)
print(response.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7) LCEL 파이프라인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;| 연산자로 컴포넌트를 연결하는 파이프라인 문법으로써, 왼쪽에서 오른쪽으로 데이터가 흐르는 흐름을 제어할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;invoke(), stream(), batch() 3가지 메소드를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;invoke : 단일 동기 호출&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780727165346&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;prompt = ChatPromptTemplate.from_messages(
    [
        (&quot;system&quot;, &quot;당신은 {role} 전문가입니다.&quot;),
        (&quot;human&quot;, &quot;{question}&quot;)
    ]
)

parser = StrOutputParser()

chain = prompt | watson_llm | parser

response = chain.invoke({
    &quot;role&quot;: &quot;보안&quot;,
    &quot;question&quot;: &quot;XSS란?&quot;
})

print(response)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-19 오전 3.13.12.png&quot; data-origin-width=&quot;1133&quot; data-origin-height=&quot;124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tpisV/dJMcacQ09L5/Zfngm8406tESChuUlV0nT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tpisV/dJMcacQ09L5/Zfngm8406tESChuUlV0nT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tpisV/dJMcacQ09L5/Zfngm8406tESChuUlV0nT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtpisV%2FdJMcacQ09L5%2FZfngm8406tESChuUlV0nT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1133&quot; height=&quot;124&quot; data-filename=&quot;스크린샷 2026-06-19 오전 3.13.12.png&quot; data-origin-width=&quot;1133&quot; data-origin-height=&quot;124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;stream : 스트리밍 형태&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780727247607&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for chunk in chain.stream({&quot;role&quot;: &quot;보안&quot;,&quot;question&quot;: &quot;XSS란?&quot;}):
    print(chunk, end=&quot;&quot;,flush=True)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-19 오전 3.15.08.png&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;129&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MExHv/dJMcaftsVmD/8BpjkhlRKFiSjF6u7ppFik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MExHv/dJMcaftsVmD/8BpjkhlRKFiSjF6u7ppFik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MExHv/dJMcaftsVmD/8BpjkhlRKFiSjF6u7ppFik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMExHv%2FdJMcaftsVmD%2F8BpjkhlRKFiSjF6u7ppFik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1128&quot; height=&quot;129&quot; data-filename=&quot;스크린샷 2026-06-19 오전 3.15.08.png&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;129&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;batch : 여러 요청을 병렬 처리&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780727275888&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# batch() : 여러 입력을 병렬로 처리

results = chain.batch([
    {
        &quot;role&quot;: &quot;보안&quot;,
        &quot;question&quot;: &quot;XSS란?&quot;
    },
    {
        &quot;role&quot;: &quot;Python&quot;,
        &quot;question&quot;: &quot;List란?&quot;
    },
    {
        &quot;role&quot;: &quot;Python&quot;,
        &quot;question&quot;: &quot;Dictionary란?&quot;
    },
    {
        &quot;role&quot;: &quot;Java&quot;,
        &quot;question&quot;: &quot;Class란?&quot;
    }
])

for result in results:
    print(result)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;8) 출력 파서(OutputParser)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-06 오후 3.30.15.png&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lV0nV/dJMcacXFogh/LBZyGlmPs2jQ3GJ5a9nkK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lV0nV/dJMcacXFogh/LBZyGlmPs2jQ3GJ5a9nkK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lV0nV/dJMcacXFogh/LBZyGlmPs2jQ3GJ5a9nkK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlV0nV%2FdJMcacXFogh%2FLBZyGlmPs2jQ3GJ5a9nkK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1132&quot; height=&quot;378&quot; data-filename=&quot;스크린샷 2026-06-06 오후 3.30.15.png&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. StrOutputParser&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781874364245&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;parser = StrOutputParser()

str_chain = ChatPromptTemplate.from_template(&quot;{topic}을 한 문장으로 설명해줘.&quot;) | qwen_llm | parser

result = str_chain.invoke({&quot;topic&quot;: &quot;머신러닝&quot;})

print(result)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-19 오후 10.09.10.png&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;27&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0DX5y/dJMcafttRY6/CcuWDTjLn3APH4j9YMzrb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0DX5y/dJMcafttRY6/CcuWDTjLn3APH4j9YMzrb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0DX5y/dJMcafttRY6/CcuWDTjLn3APH4j9YMzrb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0DX5y%2FdJMcafttRY6%2FCcuWDTjLn3APH4j9YMzrb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;896&quot; height=&quot;27&quot; data-filename=&quot;스크린샷 2026-06-19 오후 10.09.10.png&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;27&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. JsonOutputParser&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781874766559&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;parser = JsonOutputParser()

json_chain = ChatPromptTemplate.from_messages([
    (&quot;system&quot;,&quot;JSON 형식으로만 응답하세요.&quot;),
    (&quot;human&quot;,&quot;{text}의 감정을 분석해서 {{'sentiment': '...', 'score': 0.0}} 형태로 출력하세요.&quot;)
]) | qwen_llm | parser

result = json_chain.invoke({ &quot;text&quot;: &quot;오늘 정말 행복한 하루였어요.&quot; })
print(result['sentiment'])
print(result['score'])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-19 오후 10.16.57.png&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;44&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciZJ8U/dJMcahENTi2/akzGEWURknjgKyeKooubI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciZJ8U/dJMcahENTi2/akzGEWURknjgKyeKooubI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciZJ8U/dJMcahENTi2/akzGEWURknjgKyeKooubI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciZJ8U%2FdJMcahENTi2%2FakzGEWURknjgKyeKooubI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;896&quot; height=&quot;44&quot; data-filename=&quot;스크린샷 2026-06-19 오후 10.16.57.png&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;44&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. PydanticOutputParser&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;반환 타입은 객체이다.&lt;/li&gt;
&lt;li&gt;입력 데이터 타입을 검증, 타입 자동 변환&lt;/li&gt;
&lt;li&gt;에러 메시지가 정확함.&lt;/li&gt;
&lt;li&gt;코드가 간결해짐&lt;/li&gt;
&lt;li&gt;FastAPI와 호환 잘됨&lt;/li&gt;
&lt;li&gt;json 변환이 쉽다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 Pydantic 입력 데이터 타입 검증 예제이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781875611646&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User(BaseModel):
    name:str
    age:int

# 객체 생성
user = User(name=&quot;홍길동&quot;,age=&quot;20&quot;)
print(user)
print(type(user.age))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-19 오후 10.27.03.png&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;44&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6fbe3/dJMcaasdyb9/Mkdwy6fKd9DadANRgf2uF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6fbe3/dJMcaasdyb9/Mkdwy6fKd9DadANRgf2uF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6fbe3/dJMcaasdyb9/Mkdwy6fKd9DadANRgf2uF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6fbe3%2FdJMcaasdyb9%2FMkdwy6fKd9DadANRgf2uF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;896&quot; height=&quot;44&quot; data-filename=&quot;스크린샷 2026-06-19 오후 10.27.03.png&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;44&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781875673734&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;user = User(name=&quot;홍길동&quot;,age=&quot;스무살&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-19 오후 10.28.12.png&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4OLLf/dJMcahSkssl/oxcFEcglEQrNAZpSAgAQO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4OLLf/dJMcahSkssl/oxcFEcglEQrNAZpSAgAQO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4OLLf/dJMcahSkssl/oxcFEcglEQrNAZpSAgAQO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4OLLf%2FdJMcahSkssl%2FoxcFEcglEQrNAZpSAgAQO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1120&quot; height=&quot;94&quot; data-filename=&quot;스크린샷 2026-06-19 오후 10.28.12.png&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781875792657&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class User(BaseModel):
    name:str
    age:int = Field(gt=0)
    
user = User(name=&quot;홍길동&quot;, age=&quot;0&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-19 오후 10.30.30.png&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;94&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9MD4E/dJMcafNRsYy/vHXrjIFkkfP7KVZ6uTIzpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9MD4E/dJMcafNRsYy/vHXrjIFkkfP7KVZ6uTIzpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9MD4E/dJMcafNRsYy/vHXrjIFkkfP7KVZ6uTIzpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9MD4E%2FdJMcafNRsYy%2FvHXrjIFkkfP7KVZ6uTIzpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1120&quot; height=&quot;94&quot; data-filename=&quot;스크린샷 2026-06-19 오후 10.30.30.png&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;94&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 직접 체이닝 예제로 적용시켜보자.&lt;/p&gt;
&lt;pre id=&quot;code_1781876813036&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 감정, 점수, 이유, 키워드 3개
# Pydantic 모델 정의

class SentimentResult(BaseModel):
    sentiment: Literal[&quot;postive&quot;,&quot;negative&quot;,&quot;neutral&quot;]
    score: float = Field(ge=0.0,le=1.0,description=&quot;감정 강도&quot;)
    reason:str = Field(description=&quot;판단 근거 한 문장&quot;)
    keywords:list[str] = Field(description=&quot;핵심 키워드 3개 이내&quot;)

# 파서 정의
pydantic_parser = PydanticOutputParser(pydantic_object=SentimentResult)

# 프롬프트
# {format_instructions} : pydantic_parser에서 필드를 참고해서 작성
pydantic_prompt = ChatPromptTemplate.from_messages([
    (&quot;system&quot;,&quot;감정 분석 전문가입니다.\n\n {format_instructions}&quot;),
    (&quot;human&quot;,&quot;다음 텍스트의 감정을 분석하세요 :\n {text}&quot;)
]).partial(format_instructions=pydantic_parser.get_format_instructions())

pydantic_chain = pydantic_prompt | qwen_llm | pydantic_parser

result = pydantic_chain.invoke({
    &quot;text&quot;: &quot;오늘 최악의 하루였습니다. 모든게 잘못됐어요.&quot;
})

print(result)
print(result.sentiment)
print(result.score)
print(result.keywords)
print(result.reason)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-19 오후 10.47.15.png&quot; data-origin-width=&quot;1133&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0ZgUA/dJMcabEBHVi/BW98GQETbYNLwnz0AfWyU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0ZgUA/dJMcabEBHVi/BW98GQETbYNLwnz0AfWyU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0ZgUA/dJMcabEBHVi/BW98GQETbYNLwnz0AfWyU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0ZgUA%2FdJMcabEBHVi%2FBW98GQETbYNLwnz0AfWyU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1133&quot; height=&quot;112&quot; data-filename=&quot;스크린샷 2026-06-19 오후 10.47.15.png&quot; data-origin-width=&quot;1133&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>AI/LLM</category>
      <author>EunHaa</author>
      <guid isPermaLink="true">https://eun-haa.tistory.com/30</guid>
      <comments>https://eun-haa.tistory.com/30#entry30comment</comments>
      <pubDate>Sat, 6 Jun 2026 15:37:09 +0900</pubDate>
    </item>
    <item>
      <title>올라마(Ollama) - 개념 및 실습</title>
      <link>https://eun-haa.tistory.com/29</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 로컬 LLM (Local LLM)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 상용화된 LLM인 ChatGPT, Claude, Gemini 등은 우리가 돈을 지불하고 사용을 해야 하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 허깅 페이스 플랫폼에서 오픈 소스로 제공해주는 무료 모델을 사용했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 무료 모델들을 로컬에서 쓰고 싶다는 생각에서 유래했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 이야기 했던 LLM 아키텍처를 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 트랜스포머 핵심 아키텍쳐&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-06 오후 1.05.24.png&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOkbyJ/dJMcabdk6cp/7so9zjcS5cSNg1U9C7L5ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOkbyJ/dJMcabdk6cp/7so9zjcS5cSNg1U9C7L5ak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOkbyJ/dJMcabdk6cp/7so9zjcS5cSNg1U9C7L5ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOkbyJ%2FdJMcabdk6cp%2F7so9zjcS5cSNg1U9C7L5ak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1366&quot; height=&quot;434&quot; data-filename=&quot;스크린샷 2026-06-06 오후 1.05.24.png&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;434&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) LLM 추론 파이프라인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;대부분의 현업의 상용화된 LLM은 이런 과정을 거친다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-06 오후 1.08.19.png&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lorko/dJMcai4Asm7/EigIFCjIQUqaLkryh9zHaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lorko/dJMcai4Asm7/EigIFCjIQUqaLkryh9zHaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lorko/dJMcai4Asm7/EigIFCjIQUqaLkryh9zHaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Florko%2FdJMcai4Asm7%2FEigIFCjIQUqaLkryh9zHaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1366&quot; height=&quot;502&quot; data-filename=&quot;스크린샷 2026-06-06 오후 1.08.19.png&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-06 오후 1.08.59.png&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;614&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvwZQz/dJMcaglrSfk/memPbDoDwmFvlPKS9Ro9v0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvwZQz/dJMcaglrSfk/memPbDoDwmFvlPKS9Ro9v0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvwZQz/dJMcaglrSfk/memPbDoDwmFvlPKS9Ro9v0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdvwZQz%2FdJMcaglrSfk%2FmemPbDoDwmFvlPKS9Ro9v0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1366&quot; height=&quot;614&quot; data-filename=&quot;스크린샷 2026-06-06 오후 1.08.59.png&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;614&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-06 오후 1.09.49.png&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baEWca/dJMcahEDHKG/ot9YePnzkdKDaEUi3p0ZL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baEWca/dJMcahEDHKG/ot9YePnzkdKDaEUi3p0ZL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baEWca/dJMcahEDHKG/ot9YePnzkdKDaEUi3p0ZL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaEWca%2FdJMcahEDHKG%2Fot9YePnzkdKDaEUi3p0ZL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1366&quot; height=&quot;234&quot; data-filename=&quot;스크린샷 2026-06-06 오후 1.09.49.png&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;입력 텍스트는 컴퓨터가 이해할 수 있게 토큰 단위로 나누는 토크나이저(토큰화) 단계를 거친다.&lt;/li&gt;
&lt;li&gt;토큰화된 리스트를 차원을 늘려 임베딩 과정을 거친다 (벡터화)&lt;/li&gt;
&lt;li&gt;임베딩 과정까지는 글자를 나누는 것이 의미있게 가져오는 과정은 아니다.&lt;/li&gt;
&lt;li&gt;앞 뒤 &lt;b&gt;문맥(Context)&lt;/b&gt; 참고하여 의미 파악한다. -&amp;gt; &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;셀프 어텐션(Self Attention)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;위 &lt;b&gt;문맥 인코딩&lt;/b&gt;을 여러번 반복한다.&lt;/li&gt;
&lt;li&gt;이제 의미를 알아냈으니까 어떤 의미를 가지고 있는지 서로 확률 분포 확인&lt;/li&gt;
&lt;li&gt;최종적으로 우리가 원하는 텍스트를 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) 올라마(Ollama) 아키텍쳐&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;올라마(Ollama)는 llama.cpp를 기반으로 하는 로컬 LLM 서버&lt;/li&gt;
&lt;li&gt;GPU와 CPU 기반 엔진 제공&lt;/li&gt;
&lt;li&gt;RestAPI 서버 제공, OpenAI 호환 API 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4) 올라마(Ollama) 설치 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://ollama.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://ollama.com/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780719773386&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Ollama&quot; data-og-description=&quot;Ollama is the easiest way to automate your work using open models, while keeping your data safe.&quot; data-og-host=&quot;ollama.com&quot; data-og-source-url=&quot;https://ollama.com/&quot; data-og-url=&quot;https://ollama.com&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vwsmG/dJMb88e8oRd/44mkiwejhVkRPskmK7h641/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/JDO74/dJMb9iaYKPh/luDPSQw5OaadMLUFnzw7v0/img.png?width=960&amp;amp;height=720&amp;amp;face=0_0_960_720&quot;&gt;&lt;a href=&quot;https://ollama.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ollama.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vwsmG/dJMb88e8oRd/44mkiwejhVkRPskmK7h641/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/JDO74/dJMb9iaYKPh/luDPSQw5OaadMLUFnzw7v0/img.png?width=960&amp;amp;height=720&amp;amp;face=0_0_960_720');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Ollama&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Ollama is the easiest way to automate your work using open models, while keeping your data safe.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ollama.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;Downloads&lt;/b&gt; 버튼을 통해 설치하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치되었으면 아래와 같은 화면이 뜰 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-06 오후 1.26.13.png&quot; data-origin-width=&quot;2462&quot; data-origin-height=&quot;1364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NN6RG/dJMcag6PryS/72GOgl9lARkwAePKLNagW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NN6RG/dJMcag6PryS/72GOgl9lARkwAePKLNagW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NN6RG/dJMcag6PryS/72GOgl9lARkwAePKLNagW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNN6RG%2FdJMcag6PryS%2F72GOgl9lARkwAePKLNagW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2462&quot; height=&quot;1364&quot; data-filename=&quot;스크린샷 2026-06-06 오후 1.26.13.png&quot; data-origin-width=&quot;2462&quot; data-origin-height=&quot;1364&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CLI 명령어로 다음과 같이 모델을 다운로드 받을 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-06 오후 1.36.14.png&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;1016&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eJBNcW/dJMcacDpF96/dxJP3JT2pjifRksK3Kqgk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eJBNcW/dJMcacDpF96/dxJP3JT2pjifRksK3Kqgk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eJBNcW/dJMcacDpF96/dxJP3JT2pjifRksK3Kqgk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeJBNcW%2FdJMcacDpF96%2FdxJP3JT2pjifRksK3Kqgk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1746&quot; height=&quot;1016&quot; data-filename=&quot;스크린샷 2026-06-06 오후 1.36.14.png&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;1016&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5) 양자화(Quantization)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 정보를 32비트로 표현하면 공간도 많이 사용하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정밀하기 때문에 느리기도 하는데, 로컬 컴퓨터의 환경 조차도 좋지 않을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 정밀도 부분에서 손실을 보더라도 가중치를 조금 낮추어 추론 속도를 높이는 핵심 기술&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5E2YM/dJMcageHKTR/My8D3wrmcPIaJtkOwPrdk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5E2YM/dJMcageHKTR/My8D3wrmcPIaJtkOwPrdk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5E2YM/dJMcageHKTR/My8D3wrmcPIaJtkOwPrdk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5E2YM%2FdJMcageHKTR%2FMy8D3wrmcPIaJtkOwPrdk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;556&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6) 정밀도별 특성 비교&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-06 오후 1.49.12.png&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AZLFR/dJMcacpRG5P/EFL5TjaTXCCKaO3xBHQQe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AZLFR/dJMcacpRG5P/EFL5TjaTXCCKaO3xBHQQe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AZLFR/dJMcacpRG5P/EFL5TjaTXCCKaO3xBHQQe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAZLFR%2FdJMcacpRG5P%2FEFL5TjaTXCCKaO3xBHQQe1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1428&quot; height=&quot;628&quot; data-filename=&quot;스크린샷 2026-06-06 오후 1.49.12.png&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7) GGUF(GPT-Generated Unified Format)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;모델을 어떤 포맷으로 저장할 지에 관한 것 (허깅 페이스에서도 다운로드 가능)&lt;/li&gt;
&lt;li&gt;Apple Slicon , CUDA 가속 가능&lt;/li&gt;
&lt;li&gt;CPU Only 실행 파일 지원 (GPU 없어도 동작)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;8) 오픈 소스 모델 비교&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-06 오후 1.52.06.png&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;750&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwArve/dJMcabkcTpA/MYkkD0PEDpgrwj755gs1g1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwArve/dJMcabkcTpA/MYkkD0PEDpgrwj755gs1g1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwArve/dJMcabkcTpA/MYkkD0PEDpgrwj755gs1g1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwArve%2FdJMcabkcTpA%2FMYkkD0PEDpgrwj755gs1g1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1428&quot; height=&quot;750&quot; data-filename=&quot;스크린샷 2026-06-06 오후 1.52.06.png&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;750&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-06 오후 1.52.58.png&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;784&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ox53D/dJMcaaldRdL/ENwVQBWT1YFVFZVbKNN7v0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ox53D/dJMcaaldRdL/ENwVQBWT1YFVFZVbKNN7v0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ox53D/dJMcaaldRdL/ENwVQBWT1YFVFZVbKNN7v0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOx53D%2FdJMcaaldRdL%2FENwVQBWT1YFVFZVbKNN7v0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1458&quot; height=&quot;784&quot; data-filename=&quot;스크린샷 2026-06-06 오후 1.52.58.png&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;784&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 올라마(Ollama) 샘플 코드&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chat 이라는 공통 인터페이스를 사용하여 질의한다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;1) 간단한 자기소개&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780721858776&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from ollama import chat

response = chat(
    model='qwen2.5',
    messages = [
        {
            'role': 'user',
            'content': '안녕하세요! 자기소개 해줘!'
        }
    ]
)

print(response.message.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-16 오후 10.47.25.png&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;34&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NOGPX/dJMcag6Ww59/iIlU7B87xKuZBcU1T5ziJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NOGPX/dJMcag6Ww59/iIlU7B87xKuZBcU1T5ziJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NOGPX/dJMcag6Ww59/iIlU7B87xKuZBcU1T5ziJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNOGPX%2FdJMcag6Ww59%2FiIlU7B87xKuZBcU1T5ziJK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1046&quot; height=&quot;34&quot; data-filename=&quot;스크린샷 2026-06-16 오후 10.47.25.png&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;34&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;2) 어디까지 학습되었는지 샘플 코드&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781617742988&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from ollama import chat

response = chat(
    model='qwen2.5',
    messages = [
        {
            'role': 'user',
            'content': '대한민국 대통령은 누구야?'
        }
    ]
)

print(response.message.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-16 오후 10.49.22.png&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;34&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5Tbya/dJMcagslP4W/djcJ6vHKMwmYT7S8qKIrk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5Tbya/dJMcagslP4W/djcJ6vHKMwmYT7S8qKIrk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5Tbya/dJMcagslP4W/djcJ6vHKMwmYT7S8qKIrk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5Tbya%2FdJMcagslP4W%2FdjcJ6vHKMwmYT7S8qKIrk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;906&quot; height=&quot;34&quot; data-filename=&quot;스크린샷 2026-06-16 오후 10.49.22.png&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;34&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 답변하는 시점까지 학습이 되었으므로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이재명 대통령&lt;/b&gt;이 아닌 &lt;b&gt;윤석열 대통령&lt;/b&gt;으로 답변해줌을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 어느 시점까지 학습이 되었는지도 중요하다고 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;3) DeepSeek R1 모델 : 추론에 강하다&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CoT(Chain Of Thought)&lt;/b&gt;으로써 단계별 사고적 흐름으로 추론하는 것에 특화되어 있는 모델이다.&lt;/p&gt;
&lt;pre id=&quot;code_1781618153503&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from ollama import chat

response = chat(
    model='deepseek-r1:1.5b',
    messages = [
        {
            'role': 'user',
            'content': '파이썬으로 피보나치 수열 구현해줘.'
        }
    ]
)

print(response.message.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-16 오후 10.56.53.png&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mHPVM/dJMcab5Exzk/Qvc5eBCWuEM3GfcakeEwP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mHPVM/dJMcab5Exzk/Qvc5eBCWuEM3GfcakeEwP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mHPVM/dJMcab5Exzk/Qvc5eBCWuEM3GfcakeEwP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmHPVM%2FdJMcab5Exzk%2FQvc5eBCWuEM3GfcakeEwP0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1076&quot; height=&quot;412&quot; data-filename=&quot;스크린샷 2026-06-16 오후 10.56.53.png&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;4) 산수문제 테스트 : 어떤 모델이 더 특화되어 있는지&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버젼에 따라 답변이 다르긴 하지만, R1 모델이 수학적으로 추론해서 접근하는 것에 더 특화되어 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1781618643423&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;models = [
    'qwen2.5',
    'deepseek-r1:1.5b'
]

for model in models:
    response = chat(
        model = model,
        messages = [
            {
                'role': 'user',
                'content': '사과 3개가 있다. 2개를 친구에게 주고 1개를 다시 받았다. 현재 몇 개인가?'
            }
        ]
    )
    print(model)
    print(response.message.content)
    print(&quot;\n&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-16 오후 11.07.03.png&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eaaOub/dJMcaaeDpe7/nNBDgRUBnYQny5MVtG0MEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eaaOub/dJMcaaeDpe7/nNBDgRUBnYQny5MVtG0MEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eaaOub/dJMcaaeDpe7/nNBDgRUBnYQny5MVtG0MEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeaaOub%2FdJMcaaeDpe7%2FnNBDgRUBnYQny5MVtG0MEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;952&quot; height=&quot;404&quot; data-filename=&quot;스크린샷 2026-06-16 오후 11.07.03.png&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;404&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;5) 퓨샷 메시지 예제&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781626531849&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;messages = [
    # 시스템 프롬프트
    {
        &quot;role&quot;: &quot;system&quot;,
        &quot;content&quot;: &quot;너는 문장의 감정을 긍정, 부정, 중립 중 하나로 분리하는 AI야&quot;
    },
    # 퓨샷
    {
        &quot;role&quot;: &quot;user&quot;,
        &quot;content&quot;: &quot;정말 만족스러운 서비스였어요&quot;
    },
    {
        &quot;role&quot;: &quot;assistant&quot;,
        &quot;content&quot;: &quot;긍정&quot;
    },
    {
        &quot;role&quot;: &quot;user&quot;,
        &quot;content&quot;: &quot;다시는 이용 안할 것 같아요.&quot;
    },
    {
        &quot;role&quot;: &quot;assistant&quot;,
        &quot;content&quot;: &quot;부정&quot;
    },
    # 사용자 프롬프트
    {
        &quot;role&quot;: &quot;user&quot;,
        &quot;content&quot;: &quot;배송이 너무 늦었어요&quot;
    }
]

response = chat(
    model='qwen2.5',
    messages=messages
)

print(response.message.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-17 오전 1.15.55.png&quot; data-origin-width=&quot;41&quot; data-origin-height=&quot;31&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2ml1O/dJMcajiiR3X/ZAkhaVAj4SZLK8hNgDYDf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2ml1O/dJMcajiiR3X/ZAkhaVAj4SZLK8hNgDYDf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2ml1O/dJMcajiiR3X/ZAkhaVAj4SZLK8hNgDYDf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2ml1O%2FdJMcajiiR3X%2FZAkhaVAj4SZLK8hNgDYDf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;41&quot; height=&quot;31&quot; data-filename=&quot;스크린샷 2026-06-17 오전 1.15.55.png&quot; data-origin-width=&quot;41&quot; data-origin-height=&quot;31&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;6) 스트림 효과 예제&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781626745543&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from ollama import chat

response = chat(
    model='qwen2.5',
    messages = [
        {
            'role': 'user',
            'content': '안녕하세요! 자기소개 해줘!'
        }
    ],
    stream=True
)

for chunk in response:
    content = chunk['message']['content']
    print(content, end=&quot;&quot;, flush=True)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 구조화된 출력&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 파이썬에서는 딕셔너리 구조로, 자바스크립트에서는 오브젝트 구조로 받기 위해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON으로 받아주는 것이 편한데, 그 방법으로는 여러가지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 사용자 프롬프트에 지정&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781627232114&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;prompt = &quot;&quot;&quot;
    다음 문장의 감정을 분석하라.
    JSON으로만 반환하시오.

    {
        &quot;sentiment&quot; : &quot;&quot;,
        &quot;score&quot; : 0
    }

    문장: 오늘 정말 행복한 하루였다.
&quot;&quot;&quot;

response = chat(
    model='qwen2.5',
    messages=[
        {
            &quot;role&quot;: &quot;user&quot;,
            &quot;content&quot;: prompt
        }
    ]
)

print(response.message.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-17 오전 1.27.28.png&quot; data-origin-width=&quot;221&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJOY8Z/dJMcaaeDrgB/jlEHCfBjdD2x9VbeDpa9T0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJOY8Z/dJMcaaeDrgB/jlEHCfBjdD2x9VbeDpa9T0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJOY8Z/dJMcaaeDrgB/jlEHCfBjdD2x9VbeDpa9T0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJOY8Z%2FdJMcaaeDrgB%2FjlEHCfBjdD2x9VbeDpa9T0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;221&quot; height=&quot;96&quot; data-filename=&quot;스크린샷 2026-06-17 오전 1.27.28.png&quot; data-origin-width=&quot;221&quot; data-origin-height=&quot;96&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781627517923&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;prompt = &quot;&quot;&quot;
    다음 뉴스 기사를 분석하시오.
    JSON으로만 반환하시오.

    {
        'summary': '',
        'sentiment': '',
        'keywords': []
    }

    기사 내용 ----------
    스위스 &quot;19일 뷔르겐슈토크서 종전 MOU 서명식&quot;(종합)

    (베를린=연합뉴스) 김계연 특파원 = 미국과 이란의 종전 양해각서(MOU) 서명식이 오는 19일(현지시간) 스위스 휴양지 뷔르겐슈토크에서 열린다고 스위스 정부가 16일 밝혔다.

    서명식은 당초 유엔 사무국 등 국제기구가 모여 있는 스위스 제네바에서 열린다고 알려졌었다. 스위스 외무부는 미국과 이란, 중재국 파키스탄과 카타르가 뷔르겐슈토크를 서명식 장소로 제안했다면서 이들 4개국과 소통하며 외교적 여건을 조성하고 있다고 말했다.

    스위스 중부 니드발덴주에 있는 뷔르겐슈토크는 루체른 호수가 내려다보이는 알프스 산악지대 휴양지다. 100여개국 대표단이 참석한 2024년 우크라이나 평화회의 등 국제행사가 종종 열린다.

    스위스 외무부는 현재로서는 서명식 절차와 세부 사항을 알릴 수 없다고 덧붙였다. 미국과 이란은 이미 지난 14일 종전 MOU에 전자 서명했다. 양측은 협상 대표인 JD 밴스 미국 부통령과 모하마드 바게르 갈리바프 이란의회 의장 등이 참석하는 MOU 공식 서명식에 이어 후속 실무협상을 할 예정이다.

    서명식 장소를 바꾼 데는 제네바와 달리 일반인 접근이 어려워 보안&amp;middot;경호에 유리한 점도 작용한 것으로 보인다. 제네바는 인근 프랑스 에비앙레뱅에서 17일 끝나는 주요 7개국(G7) 정상회의 반대 시위대가 세계 곳곳에서 몰려들어 몸살을 앓고 있다.

    서명식이 열리는 뷔르겐슈토크 리조트는 중재국 카타르 국부펀드인 카타르투자청(QIA) 자회사 카타라호스피탤러티가 소유하고 있다. 스위스 일간 노이에취르허차이퉁(NZZ)은 카타르가 서명식 주최 역할을 할 것으로 내다봤다.


&quot;&quot;&quot;

response = chat(
    model='qwen2.5',
    messages=[
        {
            &quot;role&quot;: &quot;user&quot;,
            &quot;content&quot;: prompt
        }
    ]
)

print(response.message.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-17 오전 1.32.14.png&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;115&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqML1G/dJMcajiiSoo/pnVOtVHX33ZfHERdGqqxxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqML1G/dJMcajiiSoo/pnVOtVHX33ZfHERdGqqxxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqML1G/dJMcajiiSoo/pnVOtVHX33ZfHERdGqqxxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqML1G%2FdJMcajiiSoo%2FpnVOtVHX33ZfHERdGqqxxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1056&quot; height=&quot;115&quot; data-filename=&quot;스크린샷 2026-06-17 오전 1.32.14.png&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;115&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) JSON Schema를 넘기는 방식&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781627818056&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;schema = {
    &quot;type&quot;: &quot;object&quot;,
    &quot;properties&quot; : {
        &quot;sentiment&quot;: {
            &quot;type&quot;: &quot;string&quot;,
            &quot;description&quot;: &quot;sentiment&quot;
        },
        &quot;score&quot;: {
            &quot;type&quot;: &quot;integer&quot;,
            &quot;description&quot;: &quot;score&quot;
        }
    }
}

prompt = &quot;&quot;&quot;
    다음 문장의 감정을 분석하라.
    
    단, format으로 주어진 구조로 출력하시오.

    문장: 오늘 정말 행복한 하루였다.
&quot;&quot;&quot;

response = chat(
    model='qwen2.5',
    messages=[
        {
            &quot;role&quot;: &quot;user&quot;,
            &quot;content&quot;: prompt
        }
    ],
    format=schema
)

print(response.message.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-17 오전 1.37.14.png&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;101&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n55AV/dJMcadbfwiE/BwEPeQAcR0KL13wSoq8S1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n55AV/dJMcadbfwiE/BwEPeQAcR0KL13wSoq8S1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n55AV/dJMcadbfwiE/BwEPeQAcR0KL13wSoq8S1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn55AV%2FdJMcadbfwiE%2FBwEPeQAcR0KL13wSoq8S1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1056&quot; height=&quot;101&quot; data-filename=&quot;스크린샷 2026-06-17 오전 1.37.14.png&quot; data-origin-width=&quot;1056&quot; data-origin-height=&quot;101&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781628395168&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;schema = {
    &quot;type&quot;: &quot;object&quot;,
    &quot;properties&quot;: {
        &quot;summary&quot;: {
            &quot;type&quot;: &quot;string&quot;,
            &quot;description&quot;: &quot;news writer summary&quot;
        },
        &quot;sentiment&quot;: {
            &quot;type&quot;: &quot;string&quot;,
            &quot;enum&quot;: [&quot;positive&quot;,&quot;negative&quot;,&quot;neutral&quot;]
        },
        &quot;keywords&quot;: {
            &quot;type&quot;: &quot;array&quot;
        }
    },
    &quot;required&quot;: [
        &quot;summary&quot;,
        &quot;sentiment&quot;,
        &quot;keywords&quot;
    ]
}

prompt = &quot;&quot;&quot;
    다음 뉴스 기사를 분석하시오.
    단, format으로 주어진 구조로 반환하시오.

    기사 내용 ----------
    스위스 &quot;19일 뷔르겐슈토크서 종전 MOU 서명식&quot;(종합)

    (베를린=연합뉴스) 김계연 특파원 = 미국과 이란의 종전 양해각서(MOU) 서명식이 오는 19일(현지시간) 스위스 휴양지 뷔르겐슈토크에서 열린다고 스위스 정부가 16일 밝혔다.

    서명식은 당초 유엔 사무국 등 국제기구가 모여 있는 스위스 제네바에서 열린다고 알려졌었다. 스위스 외무부는 미국과 이란, 중재국 파키스탄과 카타르가 뷔르겐슈토크를 서명식 장소로 제안했다면서 이들 4개국과 소통하며 외교적 여건을 조성하고 있다고 말했다.

    스위스 중부 니드발덴주에 있는 뷔르겐슈토크는 루체른 호수가 내려다보이는 알프스 산악지대 휴양지다. 100여개국 대표단이 참석한 2024년 우크라이나 평화회의 등 국제행사가 종종 열린다.

    스위스 외무부는 현재로서는 서명식 절차와 세부 사항을 알릴 수 없다고 덧붙였다. 미국과 이란은 이미 지난 14일 종전 MOU에 전자 서명했다. 양측은 협상 대표인 JD 밴스 미국 부통령과 모하마드 바게르 갈리바프 이란의회 의장 등이 참석하는 MOU 공식 서명식에 이어 후속 실무협상을 할 예정이다.

    서명식 장소를 바꾼 데는 제네바와 달리 일반인 접근이 어려워 보안&amp;middot;경호에 유리한 점도 작용한 것으로 보인다. 제네바는 인근 프랑스 에비앙레뱅에서 17일 끝나는 주요 7개국(G7) 정상회의 반대 시위대가 세계 곳곳에서 몰려들어 몸살을 앓고 있다.

    서명식이 열리는 뷔르겐슈토크 리조트는 중재국 카타르 국부펀드인 카타르투자청(QIA) 자회사 카타라호스피탤러티가 소유하고 있다. 스위스 일간 노이에취르허차이퉁(NZZ)은 카타르가 서명식 주최 역할을 할 것으로 내다봤다.


&quot;&quot;&quot;

response = chat(
    model='qwen2.5',
    messages=[
        {
            &quot;role&quot;: &quot;user&quot;,
            &quot;content&quot;: prompt
        }
    ],
    format=schema
)

print(response.message.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-17 오전 1.46.51.png&quot; data-origin-width=&quot;1037&quot; data-origin-height=&quot;115&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MwX5f/dJMcahkyAcH/NiUlCwqajiTGjgE3WCbIAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MwX5f/dJMcahkyAcH/NiUlCwqajiTGjgE3WCbIAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MwX5f/dJMcahkyAcH/NiUlCwqajiTGjgE3WCbIAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMwX5f%2FdJMcahkyAcH%2FNiUlCwqajiTGjgE3WCbIAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1037&quot; height=&quot;115&quot; data-filename=&quot;스크린샷 2026-06-17 오전 1.46.51.png&quot; data-origin-width=&quot;1037&quot; data-origin-height=&quot;115&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) pydantic&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781628875653&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pydantic import BaseModel, Field

class SentimentResult(BaseModel):
    sentiment: str
    score: float = Field(ge=0.0, le=1.0) #범위지정
    keywords: list[str]

prompt = '''
다음 문장의 감정을 분석하라.

문장 : 오늘 정말 행복한 하루였다.
'''

response = chat(
    model='qwen2.5',
    messages=[
        {
            &quot;role&quot;: &quot;user&quot;,
            &quot;content&quot;: prompt
        }
    ],
    format=SentimentResult.model_json_schema()
)

result = SentimentResult.model_validate_json(response.message.content)

print(result.sentiment)
print(result.score)
print(result.keywords)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-17 오전 1.54.52.png&quot; data-origin-width=&quot;1037&quot; data-origin-height=&quot;77&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUQ9yZ/dJMcah5U1Qn/1b63WPmkcHn0eAMYRTZck0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUQ9yZ/dJMcah5U1Qn/1b63WPmkcHn0eAMYRTZck0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUQ9yZ/dJMcah5U1Qn/1b63WPmkcHn0eAMYRTZck0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUQ9yZ%2FdJMcah5U1Qn%2F1b63WPmkcHn0eAMYRTZck0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1037&quot; height=&quot;77&quot; data-filename=&quot;스크린샷 2026-06-17 오전 1.54.52.png&quot; data-origin-width=&quot;1037&quot; data-origin-height=&quot;77&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1781629177577&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from typing import Literal

class NewsWriteResult(BaseModel):
    summary: str
    sentiment: Literal['positive','neutral','negative']
    keywords: list[str]

prompt = &quot;&quot;&quot;
    다음 뉴스 기사를 분석하시오.

    기사 내용 ----------
    스위스 &quot;19일 뷔르겐슈토크서 종전 MOU 서명식&quot;(종합)

    (베를린=연합뉴스) 김계연 특파원 = 미국과 이란의 종전 양해각서(MOU) 서명식이 오는 19일(현지시간) 스위스 휴양지 뷔르겐슈토크에서 열린다고 스위스 정부가 16일 밝혔다.

    서명식은 당초 유엔 사무국 등 국제기구가 모여 있는 스위스 제네바에서 열린다고 알려졌었다. 스위스 외무부는 미국과 이란, 중재국 파키스탄과 카타르가 뷔르겐슈토크를 서명식 장소로 제안했다면서 이들 4개국과 소통하며 외교적 여건을 조성하고 있다고 말했다.

    스위스 중부 니드발덴주에 있는 뷔르겐슈토크는 루체른 호수가 내려다보이는 알프스 산악지대 휴양지다. 100여개국 대표단이 참석한 2024년 우크라이나 평화회의 등 국제행사가 종종 열린다.

    스위스 외무부는 현재로서는 서명식 절차와 세부 사항을 알릴 수 없다고 덧붙였다. 미국과 이란은 이미 지난 14일 종전 MOU에 전자 서명했다. 양측은 협상 대표인 JD 밴스 미국 부통령과 모하마드 바게르 갈리바프 이란의회 의장 등이 참석하는 MOU 공식 서명식에 이어 후속 실무협상을 할 예정이다.

    서명식 장소를 바꾼 데는 제네바와 달리 일반인 접근이 어려워 보안&amp;middot;경호에 유리한 점도 작용한 것으로 보인다. 제네바는 인근 프랑스 에비앙레뱅에서 17일 끝나는 주요 7개국(G7) 정상회의 반대 시위대가 세계 곳곳에서 몰려들어 몸살을 앓고 있다.

    서명식이 열리는 뷔르겐슈토크 리조트는 중재국 카타르 국부펀드인 카타르투자청(QIA) 자회사 카타라호스피탤러티가 소유하고 있다. 스위스 일간 노이에취르허차이퉁(NZZ)은 카타르가 서명식 주최 역할을 할 것으로 내다봤다.


&quot;&quot;&quot;

response = chat(
    model='qwen2.5',
    messages=[
        {
            &quot;role&quot;: &quot;user&quot;,
            &quot;content&quot;: prompt
        }
    ],
    format=NewsWriteResult.model_json_schema()
)

result = NewsWriteResult.model_validate_json(response.message.content)

print(result.summary)
print(result.sentiment)
print(result.keywords)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-17 오전 1.59.51.png&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;77&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bybTYW/dJMcacKjnN7/MiqAFiW9OGbf8F6c4mxSl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bybTYW/dJMcacKjnN7/MiqAFiW9OGbf8F6c4mxSl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bybTYW/dJMcacKjnN7/MiqAFiW9OGbf8F6c4mxSl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbybTYW%2FdJMcacKjnN7%2FMiqAFiW9OGbf8F6c4mxSl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1072&quot; height=&quot;77&quot; data-filename=&quot;스크린샷 2026-06-17 오전 1.59.51.png&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;77&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4. 툴 콜링 (Tool Calling)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈 소스 모델을 가지고 날씨에 대해 물어보면 모델은 대답을 해주지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 날씨를 알려주는 API에게 가서 받은 날씨를 가져오라고 해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;195&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6M0eX/dJMcagePbAd/wLrJThKOpSifSCU7SKI7K1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6M0eX/dJMcagePbAd/wLrJThKOpSifSCU7SKI7K1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6M0eX/dJMcagePbAd/wLrJThKOpSifSCU7SKI7K1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6M0eX%2FdJMcagePbAd%2FwLrJThKOpSifSCU7SKI7K1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;638&quot; height=&quot;195&quot; data-origin-width=&quot;638&quot; data-origin-height=&quot;195&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 1단계 : 함수 정의&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781629912841&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def calculator(a, b):
    return a + b&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) 2단계 : 함수를 적용할 Tools 지정&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781629943307&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tools = [
    {
        &quot;type&quot;: &quot;function&quot;,
        &quot;function&quot;: {
            &quot;name&quot;: &quot;calculator&quot;,
            &quot;description&quot;: &quot;두 숫자를 더한다.&quot;,
            &quot;parameters&quot;: {
                &quot;type&quot;: &quot;object&quot;,
                &quot;properties&quot;: {
                    &quot;a&quot; : {
                        &quot;type&quot;: &quot;integer&quot;,
                        &quot;description&quot;: &quot;첫번째 숫자&quot;
                    },
                    &quot;b&quot; : {
                        &quot;type&quot;: &quot;integer&quot;,
                        &quot;description&quot;: &quot;두번째 숫자&quot;
                    }
                },
                &quot;required&quot;: [&quot;a&quot;,&quot;b&quot;]
            }
        }
    }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) 3단계 : 사용자의 메시지와 메세지 파악한 LLM이 필요하다면 호출할 Tools를 넘겨줌.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 계산은 안들어가고 요청을 받아주는 상태&lt;/p&gt;
&lt;pre id=&quot;code_1781630193052&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;response = chat(
    model='qwen2.5',
    messages=[{&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;25와 36을 더해줘.&quot;}],
    tools=tools
)

print(response.message)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-17 오전 2.16.45.png&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;33&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btPdFl/dJMcabxMPnp/MP8jdHHweZFiQTKRvCc821/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btPdFl/dJMcabxMPnp/MP8jdHHweZFiQTKRvCc821/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btPdFl/dJMcabxMPnp/MP8jdHHweZFiQTKRvCc821/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtPdFl%2FdJMcabxMPnp%2FMP8jdHHweZFiQTKRvCc821%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1072&quot; height=&quot;33&quot; data-filename=&quot;스크린샷 2026-06-17 오전 2.16.45.png&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;33&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4) 4단계: Tool을 실행해달라고 요청&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781630478592&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tool_call = response.message.tool_calls[0]
args = tool_call.function.arguments

result = calculator(args[&quot;a&quot;], args[&quot;b&quot;])
print(result)

messages = [
    {
        &quot;role&quot;: &quot;user&quot;,
        &quot;content&quot;: &quot;25와 6을 더해줘.&quot;
    },
    response.message,
    {
        &quot;role&quot;: &quot;tool&quot;,
        &quot;content&quot;: str(result)
    }
]

final_response = chat(
    model='qwen2.5',
    messages=messages
)

print(final_response.message.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-17 오전 2.21.30.png&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nij4a/dJMcadChV5V/k5wf1kxIDmDwVphX3rBBuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nij4a/dJMcadChV5V/k5wf1kxIDmDwVphX3rBBuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nij4a/dJMcadChV5V/k5wf1kxIDmDwVphX3rBBuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnij4a%2FdJMcadChV5V%2Fk5wf1kxIDmDwVphX3rBBuK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1072&quot; height=&quot;50&quot; data-filename=&quot;스크린샷 2026-06-17 오전 2.21.30.png&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;50&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5) 날씨 함수 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781744868826&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import json

# 함수 정의
def get_weather(city:str) -&amp;gt; dict:
    data = {
        &quot;서울&quot; : { &quot;temp&quot;: 22, &quot;condition&quot;: &quot;맑음&quot; },
        &quot;부산&quot; : { &quot;temp&quot;: 25, &quot;condition&quot;: &quot;흐림&quot; }
    }
    return data.get(city, { &quot;temp&quot;: 0, &quot;condition&quot;: &quot;알 수 없음&quot;})

# 툴 스키마
tools = [
    {
    &quot;type&quot;: &quot;function&quot;,
    &quot;function&quot;: {
        &quot;name&quot;: &quot;get_weather&quot;,
        &quot;description&quot;: &quot;특정 도시의 날씨를 조회한다.&quot;,
        &quot;parameters&quot;: {
            &quot;type&quot;: &quot;object&quot;,
            &quot;properties&quot;: {
                &quot;city&quot;: {
                    &quot;type&quot;: &quot;string&quot;,
                    &quot;description&quot;: &quot;날씨를 조회할 도시 이름 (예 : 서울, 부산)&quot;
                }
            },
            &quot;required&quot;: [&quot;city&quot;]
        }
    }
}
]

messages = [{ &quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;서울 날씨 알려줘&quot;}]

# LLM 호출 - Tool 목록 전달
response = chat(
    model='qwen2.5',
    messages=messages,
    tools=tools
)

print(response.message)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781744901076&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if response.message.tool_calls:
    for tool_call in response.message.tool_calls:
        name = tool_call.function.name
        args = tool_call.function.arguments

        tool_map = {&quot;get_weather&quot;: get_weather}
        result = tool_map[name](**args)
        print(f&quot;Tool 실행 : {name}({args}) -&amp;gt; {result}&quot;)

        messages.append(response.message)
        messages.append(
            {
                &quot;role&quot;: &quot;tool&quot;,
                &quot;content&quot;: json.dumps(result, ensure_ascii=False)
            }
        )

final = chat(model='qwen2.5', messages=messages)
print(final.message.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-18 오전 10.08.40.png&quot; data-origin-width=&quot;667&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cViLkq/dJMcag6XxkX/bydCplg1kEH8sv3hDxYSOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cViLkq/dJMcag6XxkX/bydCplg1kEH8sv3hDxYSOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cViLkq/dJMcag6XxkX/bydCplg1kEH8sv3hDxYSOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcViLkq%2FdJMcag6XxkX%2FbydCplg1kEH8sv3hDxYSOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;667&quot; height=&quot;50&quot; data-filename=&quot;스크린샷 2026-06-18 오전 10.08.40.png&quot; data-origin-width=&quot;667&quot; data-origin-height=&quot;50&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6) 여러 툴 등록 및 자동 루프 처리&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781745803964&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def get_weather(city):
    return {&quot;temp&quot;: 12, &quot;condition&quot;: &quot;비&quot; }

def need_umbrella(condition):
    return { &quot;result&quot; : condition in [&quot;비&quot;,&quot;눈&quot;] }

def calculate(expression):
    return { &quot;result&quot;: eval(expression) }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781745822061&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;tools = [
    {
        &quot;type&quot;: &quot;function&quot;,
        &quot;function&quot;: {
            &quot;name&quot;: &quot;get_weather&quot;,
            &quot;description&quot;: &quot;도시 날씨 조회&quot;,
            &quot;parameters&quot;: {
                &quot;type&quot;: &quot;object&quot;,
                &quot;properties&quot;: {
                    &quot;city&quot;: { &quot;type&quot; : &quot;string&quot; }
                },
                &quot;required&quot;: [&quot;city&quot;]
            }
        }
    },
    {
        &quot;type&quot;: &quot;function&quot;,
        &quot;function&quot;: {
            &quot;name&quot;: &quot;need_umbrella&quot;,
            &quot;description&quot; : &quot;날씨 상태로 우산 필요 여부 판단&quot;,
            &quot;parameters&quot;: {
                &quot;type&quot;: &quot;object&quot;,
                &quot;properties&quot;: {
                    &quot;condition&quot;: {
                        &quot;type&quot;: &quot;string&quot;
                    }
                },
                &quot;required&quot;: [&quot;condition&quot;]
            }
        }
    }
]

tool_map = {
    &quot;get_weather&quot;: get_weather,
    &quot;need_umbrella&quot;: need_umbrella
}

messages = [ { &quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;서울 날씨 보고서 우산이 필요한지 알려줘.&quot;}]

while True:
    response = chat(model='qwen2.5',messages=messages,tools=tools)

    if not response.message.tool_calls:
        print(response.message.content)
        break

    messages.append(response.message)

    for tc in response.message.tool_calls:
        name = tc.function.name
        args = tc.function.arguments
        result = tool_map[name](**args)
        print(f&quot;[Tool] {name}({args}) -&amp;gt; {result}&quot;)

        messages.append(
            {
                &quot;role&quot;: &quot;tool&quot;,
                &quot;content&quot;: json.dumps(result, ensure_ascii=False)
            }
        )

f = chat(model='qwen2.5', messages=messages)
print(f.message.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-18 오전 10.24.00.png&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p5TDl/dJMcabdtb1F/pAVkNWNdqyxiEKKwaLkDy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p5TDl/dJMcabdtb1F/pAVkNWNdqyxiEKKwaLkDy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p5TDl/dJMcabdtb1F/pAVkNWNdqyxiEKKwaLkDy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp5TDl%2FdJMcabdtb1F%2FpAVkNWNdqyxiEKKwaLkDy0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;794&quot; height=&quot;92&quot; data-filename=&quot;스크린샷 2026-06-18 오전 10.24.00.png&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7) LLM 요청 시 REST 방식으로 요청&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1781746745838&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# post

import requests

response = requests.post(&quot;http://localhost:11434/api/chat&quot;, json= {
    &quot;model&quot;:'qwen2.5',
    &quot;messages&quot;: [
        {&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;당신은 친절한 AI 어시스턴트입니다.&quot;},
        {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;파이썬의 장점을 3가지 알려주세요&quot;}
    ],
    &quot;stream&quot;: False
})

data = response.json()
print(data['message']['content'])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-18 오전 10.39.26.png&quot; data-origin-width=&quot;1126&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b51S2j/dJMcafUzh2L/JfOIiW0qYUIGIkfqxSEfGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b51S2j/dJMcafUzh2L/JfOIiW0qYUIGIkfqxSEfGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b51S2j/dJMcafUzh2L/JfOIiW0qYUIGIkfqxSEfGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb51S2j%2FdJMcafUzh2L%2FJfOIiW0qYUIGIkfqxSEfGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1126&quot; height=&quot;166&quot; data-filename=&quot;스크린샷 2026-06-18 오전 10.39.26.png&quot; data-origin-width=&quot;1126&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용되는 API 엔드포인트들은 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-18 오전 10.41.09.png&quot; data-origin-width=&quot;1142&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b63s2h/dJMcabEz3sm/yFFLN5hGCT1LIkPEKzgG6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b63s2h/dJMcabEz3sm/yFFLN5hGCT1LIkPEKzgG6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b63s2h/dJMcabEz3sm/yFFLN5hGCT1LIkPEKzgG6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb63s2h%2FdJMcabEz3sm%2FyFFLN5hGCT1LIkPEKzgG6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1142&quot; height=&quot;612&quot; data-filename=&quot;스크린샷 2026-06-18 오전 10.41.09.png&quot; data-origin-width=&quot;1142&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;5. 임베딩(Embedding)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰 리스트를 고차원 벡터 변환 해주는 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Ollama 사이트에서 임베딩 모델을 설치한다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-18 오후 7.00.49.png&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWOQAu/dJMcaalmneG/ltTAE0t8J4aa0htMLtdRKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWOQAu/dJMcaalmneG/ltTAE0t8J4aa0htMLtdRKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWOQAu/dJMcaalmneG/ltTAE0t8J4aa0htMLtdRKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWOQAu%2FdJMcaalmneG%2FltTAE0t8J4aa0htMLtdRKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;509&quot; height=&quot;164&quot; data-filename=&quot;스크린샷 2026-06-18 오후 7.00.49.png&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-18 오후 7.01.16.png&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blrwc6/dJMcaayXS8A/N60Am2KPx5kDLh8Rad5jm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blrwc6/dJMcaayXS8A/N60Am2KPx5kDLh8Rad5jm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blrwc6/dJMcaayXS8A/N60Am2KPx5kDLh8Rad5jm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fblrwc6%2FdJMcaayXS8A%2FN60Am2KPx5kDLh8Rad5jm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;798&quot; height=&quot;277&quot; data-filename=&quot;스크린샷 2026-06-18 오후 7.01.16.png&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;277&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드를 샘플 코드로 입력한다. (몇 차원인지도 확인한다)&lt;/p&gt;
&lt;pre id=&quot;code_1781777057293&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import ollama

response = ollama.embed(
    model='nomic-embed-text-v2-moe',
    input='The sky is blue because of Rayleigh scattering',
)
print(response.embeddings)
print(len(response.embeddings[0]))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-18 오후 7.06.35.png&quot; data-origin-width=&quot;1133&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/px1oY/dJMcahY6xzK/7jwnFbot4gPUFVgfmJsHw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/px1oY/dJMcahY6xzK/7jwnFbot4gPUFVgfmJsHw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/px1oY/dJMcahY6xzK/7jwnFbot4gPUFVgfmJsHw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpx1oY%2FdJMcahY6xzK%2F7jwnFbot4gPUFVgfmJsHw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1133&quot; height=&quot;48&quot; data-filename=&quot;스크린샷 2026-06-18 오후 7.06.35.png&quot; data-origin-width=&quot;1133&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임베딩 값들은 다음과 같은 경우에 이용할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;RAG : 주어진 문서 안에서 질의 응답 가능한 형태&lt;/li&gt;
&lt;li&gt;분류, 군집 : 비슷한 의미의 문서끼리 그룹화한다.&lt;/li&gt;
&lt;li&gt;벡터 DB : 임베딩 값을 저장할 수 있는 데이터베이스&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 문자들의 비슷한 의미를 따지는 코사인 유사도 코드로 알아볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1781777526658&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 코사인 유사도

from numpy import dot
from numpy.linalg import norm

def cosine_similarity(a, b):
    return dot(a, b) / (norm(a) * norm(b))&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1781777712240&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 두 개의 문장이 유사한가?

s1 = '파이썬은 프로그래밍 언어이다.'
s2 = 'Python은 개발에 사용되는 언어이다'

s1 = ollama.embed(model='nomic-embed-text-v2-moe', input=s1).embeddings[0]
s2 = ollama.embed(model='nomic-embed-text-v2-moe', input=s2).embeddings[0]

print(cosine_similarity(s1,s2))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-18 오후 7.15.29.png&quot; data-origin-width=&quot;169&quot; data-origin-height=&quot;28&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVyvPv/dJMcaaMsSJf/6ILSF9Z10RhQDG6qLJgpBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVyvPv/dJMcaaMsSJf/6ILSF9Z10RhQDG6qLJgpBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVyvPv/dJMcaaMsSJf/6ILSF9Z10RhQDG6qLJgpBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVyvPv%2FdJMcaaMsSJf%2F6ILSF9Z10RhQDG6qLJgpBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;169&quot; height=&quot;28&quot; data-filename=&quot;스크린샷 2026-06-18 오후 7.15.29.png&quot; data-origin-width=&quot;169&quot; data-origin-height=&quot;28&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에, pdf 문서를 다 쪼개고, 모두 벡터화(임베딩) 처리를 해서 질문을 했을 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문도 벡터화 시켜, 기존의 벡터화 시킨 숫자와 유사도를 비교해서 답변을 해주는 방식으로 응용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시로 다음과 같이 구성해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1781778262908&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;documents = [
    &quot;파이썬은 프로그래밍 언어이다.&quot;,
    &quot;축구는 세계적으로 인기있는 스포츠이다.&quot;,
    &quot;인공지능은 머신러닝을 활용한다.&quot;,
    &quot;서울은 대한민국의 수도이다.&quot;
]

doc_vectors = []

for doc in documents:
    vector = ollama.embed(model='nomic-embed-text-v2-moe', input=doc).embeddings[0]
    doc_vectors.append(vector)

query = &quot;AI 기술이란?&quot;
query_vector = ollama.embed(model='nomic-embed-text-v2-moe', input=query).embeddings[0]

scores = []
for doc, vec in zip(documents, doc_vectors):
    score = cosine_similarity(query_vector, vec)
    scores.append((doc, score))

scores.sort(key=lambda x:x[1], reverse=True)

for doc, score in scores:
    print(doc, score)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-18 오후 7.24.36.png&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRuhHW/dJMb99Ujzoo/OkrJWStUXy6wW5pjejju61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRuhHW/dJMb99Ujzoo/OkrJWStUXy6wW5pjejju61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRuhHW/dJMb99Ujzoo/OkrJWStUXy6wW5pjejju61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRuhHW%2FdJMb99Ujzoo%2FOkrJWStUXy6wW5pjejju61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;451&quot; height=&quot;96&quot; data-filename=&quot;스크린샷 2026-06-18 오후 7.24.36.png&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;96&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1781778793934&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;context = scores[0][0]

prompt = f'''
문서: {context}

질문: {query}
'''

response = chat(model='qwen2.5', messages=[{
    &quot;role&quot;: &quot;user&quot;,
    &quot;content&quot;: prompt
}])

print(response.message.content)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-18 오후 7.33.29.png&quot; data-origin-width=&quot;1131&quot; data-origin-height=&quot;113&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cX1ZOO/dJMb99UjzEl/UwKQxooh2DOpXJX7E6H1wK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cX1ZOO/dJMb99UjzEl/UwKQxooh2DOpXJX7E6H1wK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cX1ZOO/dJMb99UjzEl/UwKQxooh2DOpXJX7E6H1wK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcX1ZOO%2FdJMb99UjzEl%2FUwKQxooh2DOpXJX7E6H1wK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1131&quot; height=&quot;113&quot; data-filename=&quot;스크린샷 2026-06-18 오후 7.33.29.png&quot; data-origin-width=&quot;1131&quot; data-origin-height=&quot;113&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;6. 정해진 인터페이스&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델 플랫폼에 따라, 지금 인터페이스가 조금씩 다르고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 정형화된 템플릿이 필요한데, 이것은 다음 시간에 배우게 될 LangChain 프레임워크로 이어진다.&lt;/p&gt;</description>
      <category>AI/LLM</category>
      <category>ollama</category>
      <category>로컬 llm</category>
      <category>임베딩</category>
      <author>EunHaa</author>
      <guid isPermaLink="true">https://eun-haa.tistory.com/29</guid>
      <comments>https://eun-haa.tistory.com/29#entry29comment</comments>
      <pubDate>Sat, 6 Jun 2026 14:57:17 +0900</pubDate>
    </item>
    <item>
      <title>허깅 페이스(Hugging Face) - 음성 비서 앱 (7)</title>
      <link>https://eun-haa.tistory.com/28</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 모달을 활용하는 개발 경험에서 &lt;b&gt;음성 비서 앱&lt;/b&gt;도 추가할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 다음과 같은 기능을 제공할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오후 5.56.27.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;227&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Jlz3F/dJMcajoRuwC/RirRmOohTKtk3d5GyXZ0xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Jlz3F/dJMcajoRuwC/RirRmOohTKtk3d5GyXZ0xk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Jlz3F/dJMcajoRuwC/RirRmOohTKtk3d5GyXZ0xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJlz3F%2FdJMcajoRuwC%2FRirRmOohTKtk3d5GyXZ0xk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;862&quot; height=&quot;227&quot; data-filename=&quot;스크린샷 2026-06-05 오후 5.56.27.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;227&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음성을 텍스트로 변환하고, 그것을 기준으로 질문 처리하여, 그것을 또 음성으로 변환하는 기능을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 단계는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오후 5.58.13.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;712&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIQdef/dJMcabRWMkd/BabVJ80KiNozolweWRTiHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIQdef/dJMcabRWMkd/BabVJ80KiNozolweWRTiHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIQdef/dJMcabRWMkd/BabVJ80KiNozolweWRTiHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIQdef%2FdJMcabRWMkd%2FBabVJ80KiNozolweWRTiHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;862&quot; height=&quot;712&quot; data-filename=&quot;스크린샷 2026-06-05 오후 5.58.13.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오후 5.59.15.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AGIX5/dJMcabqWolT/gKFqudfF09LPxzEBKjmiK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AGIX5/dJMcabqWolT/gKFqudfF09LPxzEBKjmiK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AGIX5/dJMcabqWolT/gKFqudfF09LPxzEBKjmiK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAGIX5%2FdJMcabqWolT%2FgKFqudfF09LPxzEBKjmiK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;862&quot; height=&quot;295&quot; data-filename=&quot;스크린샷 2026-06-05 오후 5.59.15.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;295&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 화면 구성 먼저 진행해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1단계 : 화면 구성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용된 Next.js는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;app/sound_assistant/page.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780657430484&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import AudioComponent from &quot;@/components/AudioComponent&quot;;
import AudioResponse from &quot;@/components/AudioResponse&quot;;
import CommonPut from &quot;@/components/CommonPut&quot;;

export default function SoundAssistantPage() {
    return (
        &amp;lt;main&amp;gt;
            &amp;lt;div id=&quot;wrap&quot; className=&quot;w-[1080px] mx-auto&quot;&amp;gt;
                &amp;lt;h2 className=&quot;mt-[30px] mb-[20px]&quot;&amp;gt;AI 음성 비서&amp;lt;/h2&amp;gt;
                &amp;lt;div id=&quot;gra__block&quot; className=&quot;grid grid-cols-2 gap-[10px]&quot;&amp;gt;
                    &amp;lt;AudioComponent /&amp;gt;
                    &amp;lt;CommonPut put={&quot;output&quot;} label={&quot;텍스트 변환&quot;} height={110} highHeight={150} /&amp;gt;
                    &amp;lt;CommonPut put={&quot;input&quot;} label={&quot;question&quot;} height={40} hasButton={&quot;질문하기&quot;} /&amp;gt;
                    &amp;lt;CommonPut put={&quot;input&quot;} label={&quot;answer&quot;} height={40} hasButton={&quot;답변하기&quot;} /&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div className=&quot;mt-[10px]&quot;&amp;gt;
                    &amp;lt;AudioResponse /&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/main&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;src/components/AudioComponent.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780657478938&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;'use client';

import { useState, useRef } from &quot;react&quot;;
import { MdSpatialAudioOff, MdUpload } from &quot;react-icons/md&quot;;
import { HiOutlineMicrophone } from &quot;react-icons/hi2&quot;;
import { IoCloseCircle } from &quot;react-icons/io5&quot;;
import { MdPlayArrow, MdPause } from &quot;react-icons/md&quot;;
import { MdSkipPrevious, MdSkipNext } from &quot;react-icons/md&quot;;

export default function AudioComponent() {
    const [activeTab, setActiveTab] = useState&amp;lt;'upload' | 'record'&amp;gt;('upload');
    const [uploadedAudio, setUploadedAudio] = useState&amp;lt;string&amp;gt;(&quot;&quot;);
    const [isUploading, setIsUploading] = useState(false);
    const [isPlaying, setIsPlaying] = useState(false);
    const [duration, setDuration] = useState(0);
    const [currentTime, setCurrentTime] = useState(0);
    const inputRef = useRef&amp;lt;HTMLInputElement | null&amp;gt;(null);
    const audioRef = useRef&amp;lt;HTMLAudioElement | null&amp;gt;(null);

    const handleAudioUpload = () =&amp;gt; {
        inputRef.current?.click();
    };

    const handleFileChange = async (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
        const file = e.target.files?.[0];
        if (!file) return;

        try {
            setIsUploading(true);
            const formData = new FormData();
            formData.append(&quot;file&quot;, file);
            const response = await fetch(&quot;http://localhost:8000/api/v1/file-upload&quot;, {
                method: &quot;POST&quot;,
                body: formData,
            });
            const data = await response.json();
            setUploadedAudio(data.url);
        } catch (error) {
            console.error(&quot;Upload failed:&quot;, error);
        } finally {
            setIsUploading(false);
        }
    };

    const clearAudio = () =&amp;gt; {
        setUploadedAudio(&quot;&quot;);
        setIsPlaying(false);
        setCurrentTime(0);
        if (inputRef.current) inputRef.current.value = &quot;&quot;;
    };

    const handlePlayPause = () =&amp;gt; {
        if (audioRef.current) {
            if (isPlaying) {
                audioRef.current.pause();
            } else {
                audioRef.current.play();
            }
            setIsPlaying(!isPlaying);
        }
    };

    const handleTimeUpdate = () =&amp;gt; {
        if (audioRef.current) {
            setCurrentTime(audioRef.current.currentTime);
        }
    };

    const handleLoadedMetadata = () =&amp;gt; {
        if (audioRef.current) {
            setDuration(audioRef.current.duration);
        }
    };

    const handleProgressChange = (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
        const newTime = parseFloat(e.target.value);
        setCurrentTime(newTime);
        if (audioRef.current) {
            audioRef.current.currentTime = newTime;
        }
    };

    const formatTime = (time: number) =&amp;gt; {
        if (isNaN(time)) return &quot;0:00&quot;;
        const minutes = Math.floor(time / 60);
        const seconds = Math.floor(time % 60);
        return `${minutes}:${seconds.toString().padStart(2, &quot;0&quot;)}`;
    };

    return (
        &amp;lt;div id=&quot;audioWrap&quot;&amp;gt;
            &amp;lt;div id=&quot;audioWrapBox&quot; className=&quot;w-full h-[250px] border border-[#e6e6e8] rounded-[5px] flex flex-col&quot;&amp;gt;
                &amp;lt;div id=&quot;audioWrapBoxTitle&quot; className=&quot;w-[80px] h-[30px] border-r border-b border-[#e6e6e8] flex items-center justify-center gap-[5px]&quot;&amp;gt;
                    &amp;lt;MdSpatialAudioOff /&amp;gt;
                    &amp;lt;span className=&quot;text-[12px]&quot;&amp;gt;오디오&amp;lt;/span&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div id=&quot;audioWrapBoxContent&quot; className=&quot;flex-1 flex flex-col justify-center items-center gap-[12px]&quot;&amp;gt;
                    {activeTab === 'upload' ? (
                        &amp;lt;&amp;gt;
                            {uploadedAudio ? (
                                &amp;lt;div className=&quot;w-[280px] flex flex-col items-center gap-[12px]&quot;&amp;gt;
                                    {/* 음파 시각화 */}
                                    &amp;lt;div className=&quot;w-full h-[40px] flex items-center justify-center gap-[2px] mb-[4px]&quot;&amp;gt;
                                        {[...Array(40)].map((_, i) =&amp;gt; (
                                            &amp;lt;div
                                                key={i}
                                                className=&quot;w-[1.5px] bg-[#999] rounded-full&quot;
                                                style={{
                                                    height: `${20 + Math.sin(i * 0.5 + currentTime) * 10}px`,
                                                    opacity: i / 40 &amp;lt;= (currentTime / duration) ? 1 : 0.3,
                                                }}
                                            /&amp;gt;
                                        ))}
                                    &amp;lt;/div&amp;gt;

                                    {/* 플레이어 컨트롤 */}
                                    &amp;lt;div className=&quot;w-full flex items-center justify-center gap-[8px] px-[8px]&quot;&amp;gt;
                                        &amp;lt;button className=&quot;text-[#999] hover:text-[#333] transition-colors&quot;&amp;gt;
                                            &amp;lt;MdSkipPrevious size={16} /&amp;gt;
                                        &amp;lt;/button&amp;gt;
                                        &amp;lt;button
                                            onClick={handlePlayPause}
                                            className=&quot;text-[#999] hover:text-[#333] transition-colors&quot;
                                        &amp;gt;
                                            {isPlaying ? &amp;lt;MdPause size={16} /&amp;gt; : &amp;lt;MdPlayArrow size={16} /&amp;gt;}
                                        &amp;lt;/button&amp;gt;
                                        &amp;lt;button className=&quot;text-[#999] hover:text-[#333] transition-colors&quot;&amp;gt;
                                            &amp;lt;MdSkipNext size={16} /&amp;gt;
                                        &amp;lt;/button&amp;gt;
                                        &amp;lt;span className=&quot;text-[10px] text-[#999] min-w-[24px]&quot;&amp;gt;{formatTime(currentTime)}&amp;lt;/span&amp;gt;
                                    &amp;lt;/div&amp;gt;

                                    {/* 진행률 바 */}
                                    &amp;lt;input
                                        type=&quot;range&quot;
                                        min=&quot;0&quot;
                                        max={duration || 0}
                                        value={currentTime}
                                        onChange={handleProgressChange}
                                        className=&quot;w-full h-[2px] bg-[#ddd] rounded-full appearance-none cursor-pointer&quot;
                                        style={{
                                            background: `linear-gradient(to right, #999 0%, #999 ${(currentTime / duration) * 100}%, #ddd ${(currentTime / duration) * 100}%, #ddd 100%)`
                                        }}
                                    /&amp;gt;

                                    &amp;lt;button
                                        onClick={clearAudio}
                                        className=&quot;flex items-center gap-[6px] text-[#919096] hover:text-[#333] transition-colors text-[12px]&quot;
                                    &amp;gt;
                                        &amp;lt;IoCloseCircle size={16} /&amp;gt;
                                        &amp;lt;span&amp;gt;제거&amp;lt;/span&amp;gt;
                                    &amp;lt;/button&amp;gt;

                                    &amp;lt;audio
                                        ref={audioRef}
                                        onTimeUpdate={handleTimeUpdate}
                                        onLoadedMetadata={handleLoadedMetadata}
                                        onEnded={() =&amp;gt; setIsPlaying(false)}
                                    &amp;gt;
                                        &amp;lt;source src={uploadedAudio} type=&quot;audio/mpeg&quot; /&amp;gt;
                                    &amp;lt;/audio&amp;gt;
                                &amp;lt;/div&amp;gt;
                            ) : (
                                &amp;lt;button
                                    onClick={handleAudioUpload}
                                    disabled={isUploading}
                                    className=&quot;flex flex-col justify-center items-center gap-[5px]&quot;
                                &amp;gt;
                                    &amp;lt;MdUpload size={22} color={&quot;#919096&quot;} /&amp;gt;
                                    &amp;lt;span className=&quot;text-[#d6d9d6]&quot;&amp;gt;오디오를 여기에 드롭&amp;lt;/span&amp;gt;
                                    &amp;lt;span className=&quot;text-[#919096]&quot;&amp;gt;-또는-&amp;lt;/span&amp;gt;
                                    &amp;lt;span className=&quot;text-[#919096]&quot;&amp;gt;{isUploading ? &quot;업로드 중...&quot; : &quot;클릭하여 업로드&quot;}&amp;lt;/span&amp;gt;
                                &amp;lt;/button&amp;gt;
                            )}
                            &amp;lt;input
                                type=&quot;file&quot;
                                ref={inputRef}
                                onChange={handleFileChange}
                                accept=&quot;audio/mp3,audio/wav,audio/m4a,audio/ogg,audio/flac,.mp3,.wav,.m4a,.ogg,.flac&quot;
                                className=&quot;hidden&quot;
                            /&amp;gt;
                        &amp;lt;/&amp;gt;
                    ) : (
                        &amp;lt;button className=&quot;flex flex-col justify-center items-center gap-[5px]&quot;&amp;gt;
                            &amp;lt;HiOutlineMicrophone size={22} color={&quot;#919096&quot;} /&amp;gt;
                            &amp;lt;span className=&quot;text-[#d6d9d6]&quot;&amp;gt;녹음 시작&amp;lt;/span&amp;gt;
                            &amp;lt;span className=&quot;text-[#919096]&quot;&amp;gt;클릭하여 녹음&amp;lt;/span&amp;gt;
                        &amp;lt;/button&amp;gt;
                    )}
                &amp;lt;/div&amp;gt;
                &amp;lt;div id=&quot;audioWrapBoxTab&quot; className=&quot;h-[40px] border-t border-[#e6e6e8] flex justify-center items-center gap-[20px] px-[20px]&quot;&amp;gt;
                    &amp;lt;button
                        onClick={() =&amp;gt; setActiveTab('upload')}
                        className={`flex items-center gap-[6px] transition-colors ${activeTab === 'upload'
                            ? 'text-[#333]'
                            : 'text-[#919096]'
                            }`}
                    &amp;gt;
                        &amp;lt;MdUpload size={16} /&amp;gt;
                    &amp;lt;/button&amp;gt;
                    &amp;lt;button
                        onClick={() =&amp;gt; setActiveTab('record')}
                        className={`flex items-center gap-[6px] transition-colors ${activeTab === 'record'
                            ? 'text-[#333]'
                            : 'text-[#919096]'
                            }`}
                    &amp;gt;
                        &amp;lt;HiOutlineMicrophone size={16} /&amp;gt;
                    &amp;lt;/button&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;button className=&quot;w-full py-[10px] bg-[#e4e4e6] rounded-[5px] mt-[20px] disabled:opacity-45&quot; disabled={!uploadedAudio}&amp;gt;텍스트 변환&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/components/CommonPut.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780657553773&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function CommonPut({ put, label, height, highHeight, hasButton }: { put: string, label: string, height: number, highHeight?: number, hasButton?: string }) {
    return (
        &amp;lt;div id=&quot;commonPut&quot;&amp;gt;
            &amp;lt;div id=&quot;commonPutBox&quot; className=&quot;border border-[#e6e6e8] rounded-[5px] px-[16px] py-[10px]&quot; style={{ height: highHeight ? `${highHeight}px` : &quot;auto&quot; }}&amp;gt;
                &amp;lt;h2 className=&quot;text-[13px] font-[500] mb-[10px]&quot;&amp;gt;{label}&amp;lt;/h2&amp;gt;
                {put === &quot;output&quot; ? (
                    &amp;lt;div className=&quot;text__content w-full border border-[#e6e6e8] box-border rounded-[5px]&quot; style={{ height: `${height}px` }}&amp;gt;

                    &amp;lt;/div&amp;gt;
                ) : (
                    &amp;lt;input type=&quot;text&quot; className=&quot;text__content px-[10px] outline-none w-full h-[112px] border border-[#d2d2d3] box-border rounded-[5px]&quot; style={{ height: `${height}px` }}&amp;gt;

                    &amp;lt;/input&amp;gt;
                )}
            &amp;lt;/div&amp;gt;
            {hasButton &amp;amp;&amp;amp; &amp;lt;button className=&quot;w-full py-[10px] px-[14px] bg-[#e6e6e8] text-white rounded-[5px] mt-[10px]&quot;&amp;gt;{hasButton}&amp;lt;/button&amp;gt;}
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/components/AudioResponse.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780657595350&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;'use client';

import { useState, useRef } from &quot;react&quot;;
import { MdSpatialAudioOff } from &quot;react-icons/md&quot;;
import { IoCloseCircle } from &quot;react-icons/io5&quot;;
import { MdPlayArrow, MdPause } from &quot;react-icons/md&quot;;
import { MdSkipPrevious, MdSkipNext } from &quot;react-icons/md&quot;;
import { IoIosMusicalNotes } from &quot;react-icons/io&quot;;

interface AudioResponseProps {
    audioUrl?: string;
    onClear?: () =&amp;gt; void;
}

export default function AudioResponse({ audioUrl, onClear }: AudioResponseProps) {
    const [isPlaying, setIsPlaying] = useState(false);
    const [duration, setDuration] = useState(0);
    const [currentTime, setCurrentTime] = useState(0);
    const audioRef = useRef&amp;lt;HTMLAudioElement | null&amp;gt;(null);

    const handlePlayPause = () =&amp;gt; {
        if (audioRef.current) {
            if (isPlaying) {
                audioRef.current.pause();
            } else {
                audioRef.current.play();
            }
            setIsPlaying(!isPlaying);
        }
    };

    const handleTimeUpdate = () =&amp;gt; {
        if (audioRef.current) {
            setCurrentTime(audioRef.current.currentTime);
        }
    };

    const handleLoadedMetadata = () =&amp;gt; {
        if (audioRef.current) {
            setDuration(audioRef.current.duration);
        }
    };

    const handleProgressChange = (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
        const newTime = parseFloat(e.target.value);
        setCurrentTime(newTime);
        if (audioRef.current) {
            audioRef.current.currentTime = newTime;
        }
    };

    const formatTime = (time: number) =&amp;gt; {
        if (isNaN(time)) return &quot;0:00&quot;;
        const minutes = Math.floor(time / 60);
        const seconds = Math.floor(time % 60);
        return `${minutes}:${seconds.toString().padStart(2, &quot;0&quot;)}`;
    };

    const clearAudio = () =&amp;gt; {
        setIsPlaying(false);
        setCurrentTime(0);
        onClear?.();
    };

    return (
        &amp;lt;div id=&quot;audioResponse&quot;&amp;gt;
            &amp;lt;div id=&quot;audioResponseBox&quot; className=&quot;w-full border border-[#e6e6e8] rounded-[5px] flex flex-col&quot;&amp;gt;
                &amp;lt;div id=&quot;audioResponseTitle&quot; className=&quot;w-[80px] h-[30px] border-r border-b border-[#e6e6e8] flex items-center justify-center gap-[5px]&quot;&amp;gt;
                    &amp;lt;MdSpatialAudioOff /&amp;gt;
                    &amp;lt;span className=&quot;text-[12px]&quot;&amp;gt;답변&amp;lt;/span&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div id=&quot;audioResponseContent&quot; className=&quot;flex-1 flex justify-center items-center p-[20px]&quot;&amp;gt;
                    {audioUrl ? (
                        &amp;lt;div className=&quot;w-[280px] flex flex-col items-center gap-[12px]&quot;&amp;gt;
                            {/* 음파 시각화 */}
                            &amp;lt;div className=&quot;w-full h-[40px] flex items-center justify-center gap-[2px] mb-[4px]&quot;&amp;gt;
                                {[...Array(40)].map((_, i) =&amp;gt; (
                                    &amp;lt;div
                                        key={i}
                                        className=&quot;w-[1.5px] bg-[#999] rounded-full&quot;
                                        style={{
                                            height: `${20 + Math.sin(i * 0.5 + currentTime) * 10}px`,
                                            opacity: i / 40 &amp;lt;= (currentTime / duration) ? 1 : 0.3,
                                        }}
                                    /&amp;gt;
                                ))}
                            &amp;lt;/div&amp;gt;

                            {/* 플레이어 컨트롤 */}
                            &amp;lt;div className=&quot;w-full flex items-center justify-center gap-[8px] px-[8px]&quot;&amp;gt;
                                &amp;lt;button className=&quot;text-[#999] hover:text-[#333] transition-colors&quot;&amp;gt;
                                    &amp;lt;MdSkipPrevious size={16} /&amp;gt;
                                &amp;lt;/button&amp;gt;
                                &amp;lt;button
                                    onClick={handlePlayPause}
                                    className=&quot;text-[#999] hover:text-[#333] transition-colors&quot;
                                &amp;gt;
                                    {isPlaying ? &amp;lt;MdPause size={16} /&amp;gt; : &amp;lt;MdPlayArrow size={16} /&amp;gt;}
                                &amp;lt;/button&amp;gt;
                                &amp;lt;button className=&quot;text-[#999] hover:text-[#333] transition-colors&quot;&amp;gt;
                                    &amp;lt;MdSkipNext size={16} /&amp;gt;
                                &amp;lt;/button&amp;gt;
                                &amp;lt;span className=&quot;text-[10px] text-[#999] min-w-[24px]&quot;&amp;gt;{formatTime(currentTime)}&amp;lt;/span&amp;gt;
                            &amp;lt;/div&amp;gt;

                            {/* 진행률 바 */}
                            &amp;lt;input
                                type=&quot;range&quot;
                                min=&quot;0&quot;
                                max={duration || 0}
                                value={currentTime}
                                onChange={handleProgressChange}
                                className=&quot;w-full h-[2px] bg-[#ddd] rounded-full appearance-none cursor-pointer&quot;
                                style={{
                                    background: `linear-gradient(to right, #999 0%, #999 ${(currentTime / duration) * 100}%, #ddd ${(currentTime / duration) * 100}%, #ddd 100%)`
                                }}
                            /&amp;gt;

                            &amp;lt;button
                                onClick={clearAudio}
                                className=&quot;flex items-center gap-[6px] text-[#919096] hover:text-[#333] transition-colors text-[12px]&quot;
                            &amp;gt;
                                &amp;lt;IoCloseCircle size={16} /&amp;gt;
                                &amp;lt;span&amp;gt;제거&amp;lt;/span&amp;gt;
                            &amp;lt;/button&amp;gt;

                            &amp;lt;audio
                                ref={audioRef}
                                onTimeUpdate={handleTimeUpdate}
                                onLoadedMetadata={handleLoadedMetadata}
                                onEnded={() =&amp;gt; setIsPlaying(false)}
                            &amp;gt;
                                &amp;lt;source src={audioUrl} type=&quot;audio/mpeg&quot; /&amp;gt;
                            &amp;lt;/audio&amp;gt;
                        &amp;lt;/div&amp;gt;
                    ) : (
                        &amp;lt;div className=&quot;not__answer&quot;&amp;gt;
                            &amp;lt;IoIosMusicalNotes color={&quot;#c3c0c0ff&quot;} /&amp;gt;
                        &amp;lt;/div&amp;gt;
                    )}
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오후 8.07.43.png&quot; data-origin-width=&quot;1229&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pEgag/dJMb997HKnn/cCO5Y8UCakrMZJ70OMahk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pEgag/dJMb997HKnn/cCO5Y8UCakrMZJ70OMahk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pEgag/dJMb997HKnn/cCO5Y8UCakrMZJ70OMahk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpEgag%2FdJMb997HKnn%2FcCO5Y8UCakrMZJ70OMahk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1229&quot; height=&quot;702&quot; data-filename=&quot;스크린샷 2026-06-05 오후 8.07.43.png&quot; data-origin-width=&quot;1229&quot; data-origin-height=&quot;702&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2단계 : 음성을 입력으로 받아 텍스트로 변환 후 출력&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 음성을 업로드 후 오디오 컴포넌트에 표시되어 있는 것은 아래와 같이 구현되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오후 8.10.04.png&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1quxg/dJMcagMz62m/qQ7aizgvEViZqiEdKxPmU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1quxg/dJMcagMz62m/qQ7aizgvEViZqiEdKxPmU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1quxg/dJMcagMz62m/qQ7aizgvEViZqiEdKxPmU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1quxg%2FdJMcagMz62m%2FqQ7aizgvEViZqiEdKxPmU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;603&quot; height=&quot;350&quot; data-filename=&quot;스크린샷 2026-06-05 오후 8.10.04.png&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위 파일을 인풋으로 보내어 FastAPI에서 처리하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 변환 응답값을 내려받아 처리하는 함수를 만들고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Transformer의 pipeline 함수에서 다음과 같은 태스크를 선언하자.&lt;/p&gt;
&lt;pre id=&quot;code_1780658575570&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;whisper = pipeline(&quot;automatic-speech-recognition&quot;, model=&quot;openai/whisper-base&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다음과 같은 라우터를 선언할 예정이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780658689363&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@router.post(&quot;/audio_to_text&quot;, summary=&quot;transfer audio to text&quot;)
async def audioToText(input_audio_path: str):
    pass&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 로컬 서버에 업로드 된 파일을 실제 로컬 폴더의 path로 변환하는 과정을 거쳐야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1780658866453&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;file_path = input_audio_path.replace(&quot;http://localhost:8000&quot;, &quot;&quot;).lstrip(&quot;/&quot;)
project_root = Path(__file__).parent.parent.parent.parent.parent
full_path = project_root / file_path&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 path를 whisper에 연결하여 결과를 확인한다.&lt;/p&gt;
&lt;pre id=&quot;code_1780658906109&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;result = whisper(str(full_path))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 확인하기 위해 프론트엔드단에서 처리하자.&lt;/p&gt;
&lt;pre id=&quot;code_1780659073983&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const handleTransferText = async () =&amp;gt; {
        try {
            const response = await fetch(`http://localhost:8000/api/v1/audio_to_text?input_audio_path=${uploadedAudio}`, {
                method: &quot;POST&quot;
            });
            const data = await response.json();
            console.log(data)
        } catch (error) {
            console.error(&quot;Transfer failed:&quot;, error);
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과 값은 아래와 같이 디버깅 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오후 8.38.49.png&quot; data-origin-width=&quot;1249&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7TVeQ/dJMcada7508/P9CSLl0qMjPgKohUhXnlr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7TVeQ/dJMcada7508/P9CSLl0qMjPgKohUhXnlr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7TVeQ/dJMcada7508/P9CSLl0qMjPgKohUhXnlr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7TVeQ%2FdJMcada7508%2FP9CSLl0qMjPgKohUhXnlr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1249&quot; height=&quot;114&quot; data-filename=&quot;스크린샷 2026-06-05 오후 8.38.49.png&quot; data-origin-width=&quot;1249&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올바로 처리 된 것 같다. 이제 이것을 응답값으로 넘기고 프론트엔드에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따로 상태 값으로 저장해 UI에 뿌려주면 될 것으로 보인다.&lt;/p&gt;
&lt;pre id=&quot;code_1780659627219&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@router.post(&quot;/audio_to_text&quot;, summary=&quot;transfer audio to text&quot;)
async def audioToText(input_audio_path: str):

    file_path = input_audio_path.replace(&quot;http://localhost:8000&quot;, &quot;&quot;).lstrip(&quot;/&quot;)

    project_root = Path(__file__).parent.parent.parent.parent.parent
    full_path = project_root / file_path

    # 음성 인식 처리
    result = whisper(str(full_path))

    return {&quot;text&quot;: result[&quot;text&quot;]}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 값을 저장하기 위해 먼저 &lt;b&gt;app/sound_assistant/page.tsx&lt;/b&gt;에서 useState 통해 상태 값을 선언한다.&lt;/p&gt;
&lt;pre id=&quot;code_1780660189297&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [audioToTextValue, setAudioToTextValue] = useState&amp;lt;string&amp;gt;(&quot;&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 값을 받아 값을 바꿔주는 함수를 선언해서 넘겨주자.&lt;/p&gt;
&lt;pre id=&quot;code_1780660267290&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const changeAudioToText = (data: string) =&amp;gt; {
    setAudioToTextValue(data);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1780660285978&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;AudioComponent changeAudioToText={changeAudioToText} /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;b&gt;src/components/AudioComponent.tsx&lt;/b&gt; 에서 change 함수 처리를 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1780660383148&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const handleTransferText = async () =&amp;gt; {
        try {
            const response = await fetch(`http://localhost:8000/api/v1/audio_to_text?input_audio_path=${uploadedAudio}`, {
                method: &quot;POST&quot;
            });
            const data = await response.json();
            changeAudioToText(data.text)
        } catch (error) {
            console.error(&quot;Transfer failed:&quot;, error);
        }
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 그 값을 첫번째 CommonPut.tsx 컴포넌트에 넣어주어 UI에 업데이트한다.&lt;/p&gt;
&lt;pre id=&quot;code_1780660441323&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;CommonPut put={&quot;output&quot;} label={&quot;텍스트 변환&quot;} height={110} highHeight={150} value={audioToTextValue} /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 다음과 같이 결과 UI가 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오후 8.54.30.png&quot; data-origin-width=&quot;1207&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7eY5q/dJMcaaevuX8/tIkcQX8qfWkeH8kLl9YPq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7eY5q/dJMcaaevuX8/tIkcQX8qfWkeH8kLl9YPq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7eY5q/dJMcaaevuX8/tIkcQX8qfWkeH8kLl9YPq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7eY5q%2FdJMcaaevuX8%2FtIkcQX8qfWkeH8kLl9YPq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1207&quot; height=&quot;354&quot; data-filename=&quot;스크린샷 2026-06-05 오후 8.54.30.png&quot; data-origin-width=&quot;1207&quot; data-origin-height=&quot;354&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3단계 : 변환된 텍스트를 이용해 질문하기&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오후 9.25.29.png&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DP740/dJMcacpRqvf/Mrzk0AmKNZEVKKO3xj8C51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DP740/dJMcacpRqvf/Mrzk0AmKNZEVKKO3xj8C51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DP740/dJMcacpRqvf/Mrzk0AmKNZEVKKO3xj8C51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDP740%2FdJMcacpRqvf%2FMrzk0AmKNZEVKKO3xj8C51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;866&quot; height=&quot;295&quot; data-filename=&quot;스크린샷 2026-06-05 오후 9.25.29.png&quot; data-origin-width=&quot;866&quot; data-origin-height=&quot;295&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 질문에 대한 답변을 얻기 위해 두 번째 pipeline 태스크가 필요한 상황으로 보인다.&lt;/p&gt;
&lt;pre id=&quot;code_1780662606234&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;text_generation = pipeline(&quot;text-generation&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 직접 프롬프팅을 통해 답변을 도출해내야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 텍스트 변환 내용 기반으로 답변을 해주어야 하므로, 기존 텍스트 변환 내용도 넘겨주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이전 라우팅에서 내용과 현재에서도 이 변수를 공유하려면, 전역 변수를 이용하자.&lt;/p&gt;
&lt;pre id=&quot;code_1780662870465&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;voice_txt, current_answer = &quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 current_answer는 다음 단계에서 똑같은 과정을 거칠 것이므로 일단 참고만 해두자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 텍스트 변환 라우터를 다음과 같이 수정하자.&lt;/p&gt;
&lt;pre id=&quot;code_1780663093225&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@router.post(&quot;/audio_to_text&quot;, summary=&quot;transfer audio to text&quot;)
async def audioToText(input_audio_path: str):
    global voice_txt

    file_path = input_audio_path.replace(&quot;http://localhost:8000&quot;, &quot;&quot;).lstrip(&quot;/&quot;)

    project_root = Path(__file__).parent.parent.parent.parent.parent
    full_path = project_root / file_path

    # 음성 인식 처리
    result = whisper(str(full_path))
    voice_text = result[&quot;text&quot;]

    return {&quot;text&quot;: result[&quot;text&quot;]}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 질문 라우팅은 다음과 같이 설정하자.&lt;/p&gt;
&lt;pre id=&quot;code_1780663311939&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@router.post(&quot;/text_generation&quot;, summary=&quot;text generation&quot;)
async def generate_text(question: str):
    global voice_txt

    if not voice_txt:
        return {&quot;status&quot;: &quot;fail&quot;, &quot;message&quot;: &quot;음성을 텍스트로 변환 후 질문하세요&quot;}

    # 간단한 응답 생성 (영어 프롬프트 사용)
    prompt = f&quot;Based on: {voice_txt[:100]}\nQuestion: {question}\nAnswer: &quot;

    try:
        result = text_generation_pipeline(prompt, max_new_tokens=100, do_sample=True, top_p=0.95)
        generated_text = result[0][&quot;generated_text&quot;].strip()

        # 프롬프트 부분 제거하고 답변만 추출
        answer_start = generated_text.find(&quot;Answer:&quot;)
        if answer_start != -1:
            final_answer = generated_text[answer_start + 7:].strip()
        else:
            final_answer = generated_text

        return {
            &quot;status&quot;: &quot;success&quot;,
            &quot;message&quot;: final_answer,
        }
    except Exception as e:
        return {
            &quot;status&quot;: &quot;fail&quot;,
            &quot;message&quot;: f&quot;텍스트 생성 중 오류가 발생했습니다: {str(e)}&quot;
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 토대로 프론트엔드 설정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 questionValue를 통해 질문 데이터를 관리한다.&lt;/p&gt;
&lt;pre id=&quot;code_1780663383874&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [questionValue, setQuestionValue] = useState&amp;lt;string&amp;gt;(&quot;&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 onChange 함수를 연결한다.&lt;/p&gt;
&lt;pre id=&quot;code_1780663510348&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const changeQuestionValue = (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
   setQuestionValue(e.target.value)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 answerValue를 관리하자.&lt;/p&gt;
&lt;pre id=&quot;code_1780663600890&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const [answerValue, setAnswerValue] = useState&amp;lt;string&amp;gt;(&quot;&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, change 함수를 통해 순수함수를 구현하여, answerValue를 받아오자.&lt;/p&gt;
&lt;pre id=&quot;code_1780663717484&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; const changeAnswerValue = (data: string) =&amp;gt; {
    setAnswerValue(data)
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;b&gt;PutCommon.tsx&lt;/b&gt; 컴포넌트에서 status가 성공할 경우에만 응답값을 받아오자.&lt;/p&gt;
&lt;pre id=&quot;code_1780665914284&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const quest = async () =&amp;gt; {
        try {
            const response = await fetch(`http://localhost:8000/api/v1/text_generation?question=${questionValue}`, {
                method: &quot;POST&quot;
            })
            const data = await response.json()
            if (data.status === &quot;success&quot;) {
                changeAnswerValue(data.message)
                console.log(data.message)
            } else {
                console.error(data.message)
            }
        } catch (error) {
            console.log(error)
        }
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오후 10.26.30.png&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;637&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwkXl8/dJMcahdCEOY/IpZm0w6ODuhGkkxkQllW5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwkXl8/dJMcahdCEOY/IpZm0w6ODuhGkkxkQllW5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwkXl8/dJMcahdCEOY/IpZm0w6ODuhGkkxkQllW5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwkXl8%2FdJMcahdCEOY%2FIpZm0w6ODuhGkkxkQllW5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1211&quot; height=&quot;637&quot; data-filename=&quot;스크린샷 2026-06-05 오후 10.26.30.png&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;637&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4단계 : 답변을 음성으로 듣기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 받은 답변을 기반으로 다시 음성으로 변환하는 TTS 기능을 구현해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 전역 변수로 선언한 current_answer를 이용해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 TTS 함수 만들자.&lt;/p&gt;
&lt;pre id=&quot;code_1780666801226&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;async def text_to_voice(text):
    voice = &quot;ko-KR-InJoonNeural&quot;

    # 타임스탬프를 포함한 파일명 생성
    timestamp = datetime.now().strftime(&quot;%Y%m%d_%H%M%S&quot;)
    filename = f&quot;answer_{timestamp}.mp3&quot;
    filepath = ANSWER_DIR / filename

    communicate = edge_tts.Communicate(text, voice)
    await communicate.save(str(filepath))

    # 파일 URL 반환
    file_url = f&quot;http://localhost:8000/answer/{filename}&quot;
    return file_url&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 이용해 API 함수 만들자.&lt;/p&gt;
&lt;pre id=&quot;code_1780666831809&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@router.post(&quot;/tts&quot;, summary=&quot;text-to-speech&quot;)
async def tts():
    global current_answer
    if not current_answer:
        return {&quot;status&quot;: &quot;fail&quot;, &quot;message&quot;: &quot;변환할 텍스트가 없습니다&quot;}

    try:
        file_url = await text_to_voice(current_answer)
        return {
            &quot;status&quot;: &quot;success&quot;,
            &quot;audio_url&quot;: file_url
        }
    except Exception as e:
        return {
            &quot;status&quot;: &quot;fail&quot;,
            &quot;message&quot;: f&quot;음성 변환 중 오류가 발생했습니다: {str(e)}&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오후 10.58.21.png&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;883&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1pOjT/dJMcadvql97/7rAjvxThG3dGkDQw4gS0O0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1pOjT/dJMcadvql97/7rAjvxThG3dGkDQw4gS0O0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1pOjT/dJMcadvql97/7rAjvxThG3dGkDQw4gS0O0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1pOjT%2FdJMcadvql97%2F7rAjvxThG3dGkDQw4gS0O0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1211&quot; height=&quot;883&quot; data-filename=&quot;스크린샷 2026-06-05 오후 10.58.21.png&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;883&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI/LLM</category>
      <author>EunHaa</author>
      <guid isPermaLink="true">https://eun-haa.tistory.com/28</guid>
      <comments>https://eun-haa.tistory.com/28#entry28comment</comments>
      <pubDate>Fri, 5 Jun 2026 22:58:55 +0900</pubDate>
    </item>
    <item>
      <title>허깅 페이스(Hugging Face) - 감정 분석 앱 (6)</title>
      <link>https://eun-haa.tistory.com/27</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 실습으로 만들어 볼 웹 애플리케이션은 &lt;b&gt;감정 분석 애플리케이션&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 허깅 페이스 Transformer로 할 수 있는 기능 중에 &lt;b&gt;감정 분석&lt;/b&gt;이 있었는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것을 애플리케이션화 한다고 보면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-02 오후 3.23.20.png&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1QGDu/dJMb99T7P30/HFXIK2BUkYaOR6K3v4uy90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1QGDu/dJMb99T7P30/HFXIK2BUkYaOR6K3v4uy90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1QGDu/dJMb99T7P30/HFXIK2BUkYaOR6K3v4uy90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1QGDu%2FdJMb99T7P30%2FHFXIK2BUkYaOR6K3v4uy90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;994&quot; height=&quot;316&quot; data-filename=&quot;스크린샷 2026-06-02 오후 3.23.20.png&quot; data-origin-width=&quot;994&quot; data-origin-height=&quot;316&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;11111.png&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCsI6m/dJMcabRUo7O/E36Z2DjE1kSjAsM7HHzNAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCsI6m/dJMcabRUo7O/E36Z2DjE1kSjAsM7HHzNAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCsI6m/dJMcabRUo7O/E36Z2DjE1kSjAsM7HHzNAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCsI6m%2FdJMcabRUo7O%2FE36Z2DjE1kSjAsM7HHzNAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1186&quot; height=&quot;634&quot; data-filename=&quot;11111.png&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;634&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-02 오후 3.28.56.png&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;1016&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kEHRe/dJMb99T7QlY/rKkry03Cp93E4EuObrKHhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kEHRe/dJMb99T7QlY/rKkry03Cp93E4EuObrKHhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kEHRe/dJMb99T7QlY/rKkry03Cp93E4EuObrKHhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkEHRe%2FdJMb99T7QlY%2FrKkry03Cp93E4EuObrKHhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1152&quot; height=&quot;1016&quot; data-filename=&quot;스크린샷 2026-06-02 오후 3.28.56.png&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;1016&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 6단계에 걸쳐 UI를 구현할 예정인데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 1단계는 텍스트 인풋을 통해 감정과 확률을 아웃풋으로 받는 형태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1단계 : 영어 문장&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcXKSk/dJMb990XKss/oICwFIMij2qWkGAKR42vKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcXKSk/dJMb990XKss/oICwFIMij2qWkGAKR42vKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcXKSk/dJMb990XKss/oICwFIMij2qWkGAKR42vKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcXKSk%2FdJMb990XKss%2FoICwFIMij2qWkGAKR42vKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;586&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 Next.js 코드로 화면단(프론트엔드) 먼저 구현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;app/emotion/page.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780562591748&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;use client&quot;;

import { useState } from &quot;react&quot;;
import PutColumn from &quot;@/components/PutColumn&quot;;


export default function EmotionPage() {
    const putArray = [
        {
            ele: 'input',
            label: 'text'
        },
        {
            ele: 'output',
            label: 'greeting'
        }
    ]

    const [inputValue, setInputValue] = useState&amp;lt;string&amp;gt;(&quot;&quot;);
    const [outputValue, setOutputValue] = useState&amp;lt;string&amp;gt;(&quot;&quot;);

    const inputChange = (e: React.ChangeEvent&amp;lt;HTMLTextAreaElement&amp;gt;) =&amp;gt; {
        setInputValue(e.target.value)
    }

    const outputChange = (data: string) =&amp;gt; {
        setOutputValue(data)
    }


    const handleClear = () =&amp;gt; {
        setInputValue(&quot;&quot;);
        setOutputValue(&quot;&quot;);
    }

    return (
        &amp;lt;main&amp;gt;
            &amp;lt;div id=&quot;wrap&quot; className=&quot;w-[1080px] mx-auto&quot;&amp;gt;
                &amp;lt;h2 className=&quot;text-center mb-[30px]&quot;&amp;gt;AI 감정 분석 웹앱&amp;lt;/h2&amp;gt;
                &amp;lt;p className=&quot;text-[14px] mb-[16px]&quot;&amp;gt;
                    Hugging Face Transformer 모델 기반 감정 분석
                &amp;lt;/p&amp;gt;
                &amp;lt;div className=&quot;put__rc grid grid-cols-2 gap-[10px]&quot;&amp;gt;
                    {putArray.map((e, i) =&amp;gt; (
                        &amp;lt;PutColumn
                            key={i}
                            ele={e.ele}
                            label={e.label}
                            value={e.ele === 'input' ? inputValue : outputValue}
                            onChange={e.ele === 'input' ? inputChange : outputChange}
                            onClear={e.ele === 'input' ? handleClear : undefined}
                        /&amp;gt;
                    ))}
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/main&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/components/PutColumn.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780562659016&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { cn } from &quot;@/lib/cn&quot;;
import PutBox from &quot;./PutBox&quot;;

interface PutColumnProps {
    ele: string;
    label: string;
    value: string;
    onChange?: ((e: React.ChangeEvent&amp;lt;HTMLTextAreaElement&amp;gt;) =&amp;gt; void) | ((data: string) =&amp;gt; void);
    onClear?: () =&amp;gt; void;
}

export default function PutColumn({
    ele,
    label,
    value,
    onChange,
    onClear,
}: PutColumnProps) {
    return (
        &amp;lt;div className={cn(&quot;put__column__&quot; + ele)}&amp;gt;
            &amp;lt;PutBox textColor={&quot;#8c8e99&quot;} label={label} ele={ele} value={value} onChange={onChange} /&amp;gt;
            {ele === &quot;output&quot; ? (
                &amp;lt;button className=&quot;w-full font-[600] py-[10px] bg-[#e4e4e6] rounded-[5px] cursor-pointer&quot;&amp;gt;
                    Flag
                &amp;lt;/button&amp;gt;
            ) : (
                &amp;lt;div className=&quot;buttons flex gap-[10px]&quot;&amp;gt;
                    &amp;lt;button
                        className=&quot;flex-1 font-[600] py-[10px] bg-[#f87315] text-[#fff] rounded-[5px] cursor-pointer disabled:opacity-45&quot;
                        disabled={!value.trim()}
                    &amp;gt;
                        Submit
                    &amp;lt;/button&amp;gt;
                    &amp;lt;button
                        className=&quot;flex-1 font-[600] py-[10px] bg-[#e4e4e6] rounded-[5px] cursor-pointer&quot;
                        onClick={onClear}
                    &amp;gt;
                        Clear
                    &amp;lt;/button&amp;gt;
                &amp;lt;/div&amp;gt;
            )}
        &amp;lt;/div&amp;gt;
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/components/PutBox.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780562707943&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function PutBox({
    label,
    ele,
    value,
    onChange,
    textColor
}: {
    label: string;
    ele: string;
    value: string | number[];
    onChange?: ((e: React.ChangeEvent&amp;lt;HTMLTextAreaElement&amp;gt;) =&amp;gt; void) | ((data: string) =&amp;gt; void);
    textColor?: string;
}) {

    return (
        &amp;lt;div className=&quot;put__box p-[14px] border border-[#e7e7e9] rounded-[5px] mb-[30px]&quot;&amp;gt;
            &amp;lt;h2 className=&quot;text-[14px] font-[700] mb-[8px]&quot; style={{ color: `${textColor}` }}&amp;gt;{label}&amp;lt;/h2&amp;gt;
            {
                ele === 'input' ?
                    &amp;lt;textarea
                        className=&quot;resize-none w-full block box-border h-[200px] p-[14px] border border-[#e7e7e9] outline-none rounded-[5px]&quot;
                        value={value as string}
                        onChange={(e) =&amp;gt; {
                            if (onChange) {
                                (onChange as (e: React.ChangeEvent&amp;lt;HTMLTextAreaElement&amp;gt;) =&amp;gt; void)(e);
                            }
                        }}
                    /&amp;gt;
                    :
                    &amp;lt;div className=&quot;p-[14px] h-[200px] border border-[#e7e7e9] rounded-[5px] box-border&quot;&amp;gt;
                    &amp;lt;/div&amp;gt;
            }
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드 실행 시 아래와 같은 UI가 도출될 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-04 오후 5.45.57.png&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LGzxD/dJMcagZ0vEZ/eIvSMB3uWJ39nL28zKUGyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LGzxD/dJMcagZ0vEZ/eIvSMB3uWJ39nL28zKUGyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LGzxD/dJMcagZ0vEZ/eIvSMB3uWJ39nL28zKUGyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLGzxD%2FdJMcagZ0vEZ%2FeIvSMB3uWJ39nL28zKUGyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1226&quot; height=&quot;476&quot; data-filename=&quot;스크린샷 2026-06-04 오후 5.45.57.png&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한건 AI 기능이므로 프론트엔드 코드에 대한 자세한 설명은 넘어가도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;FastAPI&amp;nbsp;&lt;/b&gt;엔드포인트를 구축하여, 입력한 텍스트를 받아 LLM을 통해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감정의 긍/부정 여부 및 긍/부정 확률을 도출하는 간단한 앱을 구성할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, Transformer의 pipeline을 가져와 sentiment-analysis 태스크를 가져오자.&lt;/p&gt;
&lt;pre id=&quot;code_1780563007886&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;analyzer = pipeline(&quot;sentiment-analysis&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수 변수를 input_text (단, 반드시 한 문장이어야 한다)에 적용시켜 결과값을 리턴하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780563149731&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@router.post(&quot;/sentiment-analysis&quot;, summary=&quot;Analyze text sentiment&quot;)
async def get_sentiment(input_text: str):
    result = analyzer(input_text)
    return {&quot;result&quot;: result}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제 간단한 API 구축에 성공했으니, 프론트엔드와 연결시켜보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;app/emotion/page.tsx&lt;/b&gt; 에서 handleSubmit 함수를 추가할 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780563526251&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const handleSubmit = async () =&amp;gt; {
        try {
            const response = await fetch(`http://localhost:8000/api/v1/sentiment-analysis?input_text=${inputValue}`, {
                method: &quot;POST&quot;,
            })
            const json = await response.json()
            console.log(json)
        } catch (error) {
            console.error(error)
        }
 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드를 통해 콘솔에 출력된 결과값은 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-04 오후 6.00.08.png&quot; data-origin-width=&quot;253&quot; data-origin-height=&quot;106&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLwoIK/dJMcacwBd3u/sF0TYGhMv5KrIRNk3t5mpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLwoIK/dJMcacwBd3u/sF0TYGhMv5KrIRNk3t5mpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLwoIK/dJMcacwBd3u/sF0TYGhMv5KrIRNk3t5mpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLwoIK%2FdJMcacwBd3u%2FsF0TYGhMv5KrIRNk3t5mpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;253&quot; height=&quot;106&quot; data-filename=&quot;스크린샷 2026-06-04 오후 6.00.08.png&quot; data-origin-width=&quot;253&quot; data-origin-height=&quot;106&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올바로 응답값이 내려왔으므로, 이 응답값을 상태값으로 저장 후&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;greeting UI에 형식대로 출력하도록 하면 되겠다.&lt;/p&gt;
&lt;pre id=&quot;code_1780563771582&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;setOutputValue(`감정:${json.result[0].label}|확률:${json.result[0].score}`)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;src/components/PutBox.tsx&lt;/b&gt; 에서 아래와 같이 처리한다.&lt;/p&gt;
&lt;pre id=&quot;code_1780563881829&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div className=&quot;p-[14px] h-[200px] border border-[#e7e7e9] rounded-[5px] box-border overflow-auto&quot;&amp;gt;
      {(value as string).split(&quot;|&quot;).map((e, i) =&amp;gt; (
           &amp;lt;p key={i} className=&quot;leading-[1.6]&quot;&amp;gt;{e}&amp;lt;/p&amp;gt;
      ))}
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-04 오후 6.10.36.png&quot; data-origin-width=&quot;1213&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJ3O8A/dJMcabqVryQ/zJkbvaPgHZZ7RonV2vprPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJ3O8A/dJMcabqVryQ/zJkbvaPgHZZ7RonV2vprPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJ3O8A/dJMcabqVryQ/zJkbvaPgHZZ7RonV2vprPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJ3O8A%2FdJMcabqVryQ%2FzJkbvaPgHZZ7RonV2vprPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1213&quot; height=&quot;486&quot; data-filename=&quot;스크린샷 2026-06-04 오후 6.10.36.png&quot; data-origin-width=&quot;1213&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 보여주는 감정과 확률 수치는 가장 높은 확률이 나온 것을 분석해서 보여주는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 이 상황에서는 Negative일 확률이 99%, 나머지일 확률이 1%이므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Negative, 99%를 보여주는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2단계 : 영어 + 한국어 지원&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-04 오후 6.24.57.png&quot; data-origin-width=&quot;899&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/103cL/dJMcafUo1Du/lhyyVW3T1fuUSGAfxpwfCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/103cL/dJMcafUo1Du/lhyyVW3T1fuUSGAfxpwfCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/103cL/dJMcafUo1Du/lhyyVW3T1fuUSGAfxpwfCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F103cL%2FdJMcafUo1Du%2FlhyyVW3T1fuUSGAfxpwfCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;899&quot; height=&quot;486&quot; data-filename=&quot;스크린샷 2026-06-04 오후 6.24.57.png&quot; data-origin-width=&quot;899&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국어로 입력했을 때 분석해주는 한국어 인공지능 모델들을 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국어로 처리하도록 개선하는 데에는 여러가지 방법이 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 한국어 모델을 찾는 방법도 있겠지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;번역 모델을 중간에 미들웨어처럼 끼워 넣을 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 번역 모델을 통해 한국어를 영어로 번역 후&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그것을 분석해달라고 요청할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 실습에서는 간단한 한국어 모델을 사용하는 쪽으로 구성을 잡도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한국어 모델을 3가지 정도 테스트&lt;/b&gt;해보도록 할 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(감정 분석이 가능한 한국어 모델 3가지를 가져와봤다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;1) WhitePeak/bert-base-cased-Korean-sentiment&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음과 같이 모델명을 지정해보자. 그리고 바로 화면단에서 테스트해보면 된다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780565765855&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;analyzer = pipeline(
    &quot;sentiment-analysis&quot;, model=&quot;WhitePeak/bert-base-cased-Korean-sentiment&quot;
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-04 오후 6.37.27.png&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lNDsy/dJMcadhYEy5/xuwOAuQOkbPRWpPvzUJg3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lNDsy/dJMcadhYEy5/xuwOAuQOkbPRWpPvzUJg3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lNDsy/dJMcadhYEy5/xuwOAuQOkbPRWpPvzUJg3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlNDsy%2FdJMcadhYEy5%2FxuwOAuQOkbPRWpPvzUJg3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1233&quot; height=&quot;486&quot; data-filename=&quot;스크린샷 2026-06-04 오후 6.37.27.png&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감정 부분 데이터가 우리가 아는 형식과 다르게 나오므로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Negative / Postive / Neutral&lt;/b&gt; 형식으로 맵핑할 필요성이 있겠다.&lt;/p&gt;
&lt;pre id=&quot;code_1780566142789&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;model_map = {
    &quot;LABEL_0&quot;: &quot;NEGATIVE&quot;,
    &quot;LABEL_1&quot;: &quot;POSITIVE&quot;,
    &quot;LABEL_2&quot;: &quot;NEUTRAL&quot;,
}

def normalize_label(label: str) -&amp;gt; str:
    &quot;&quot;&quot;Normalize sentiment label to standard format&quot;&quot;&quot;
    return model_map.get(label, label)

@router.post(&quot;/sentiment-analysis&quot;, summary=&quot;Analyze text sentiment&quot;)
async def get_sentiment(input_text: str):
    result = analyzer(input_text)

    result[0][&quot;label&quot;] = normalize_label(result[0][&quot;label&quot;])

    return {&quot;result&quot;: result}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-04 오후 6.46.23.png&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dioKFd/dJMcagZ0x7z/2OJsksdIryLG4A9rMgewdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dioKFd/dJMcagZ0x7z/2OJsksdIryLG4A9rMgewdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dioKFd/dJMcagZ0x7z/2OJsksdIryLG4A9rMgewdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdioKFd%2FdJMcagZ0x7z%2F2OJsksdIryLG4A9rMgewdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1233&quot; height=&quot;486&quot; data-filename=&quot;스크린샷 2026-06-04 오후 6.46.23.png&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;2) snunlp/KR-FinBert-SC&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780566507815&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;analyzer = pipeline(
    &quot;sentiment-analysis&quot;, model=&quot;snunlp/KR-FinBert-SC&quot;
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-04 오후 6.54.24.png&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhAR0D/dJMcah5LGZD/Y4DLq4sgnSoqXqMh51P5J0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhAR0D/dJMcah5LGZD/Y4DLq4sgnSoqXqMh51P5J0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhAR0D/dJMcah5LGZD/Y4DLq4sgnSoqXqMh51P5J0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhAR0D%2FdJMcah5LGZD%2FY4DLq4sgnSoqXqMh51P5J0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1233&quot; height=&quot;486&quot; data-filename=&quot;스크린샷 2026-06-04 오후 6.54.24.png&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 형식도 맵핑 딕셔너리에 키-속성으로 추가 후 테스트 해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 결과가 우리가 원하던 형식으로 잘 나오는 것 같으나, 분석 정확도는 첫번째 모델보다는 아닌 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 우선순위를 뒤로 미뤄놓고 생각해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;3) nlptown/bert-base-multilingual-uncased-sentiment&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인해보면 알겠지만, 이 모델도 label 결과값이 우리가 원하는 형식과 다르므로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;label_map 딕셔너리에 형식을 추가해줄 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780569151181&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;analyzer = pipeline(
    &quot;sentiment-analysis&quot;, model=&quot;nlptown/bert-base-multilingual-uncased-sentiment&quot;
)

model_map = {
    &quot;LABEL_0&quot;: &quot;NEGATIVE&quot;,
    &quot;LABEL_1&quot;: &quot;POSITIVE&quot;,
    &quot;LABEL_2&quot;: &quot;NEUTRAL&quot;,
    &quot;NEGATIVE&quot;: &quot;NEGATIVE&quot;,
    &quot;POSITIVE&quot;: &quot;POSITIVE&quot;,
    &quot;NEUTRAL&quot;: &quot;NEUTRAL&quot;,
    &quot;1 star&quot;: &quot;NEGATIVE&quot;,
    &quot;2 stars&quot;: &quot;NEGATIVE&quot;,
    &quot;3 stars&quot;: &quot;NEUTRAL&quot;,
    &quot;4 stars&quot;: &quot;POSITIVE&quot;,
    &quot;5 stars&quot;: &quot;POSITIVE&quot;,
}


def normalize_label(label: str) -&amp;gt; str:
    &quot;&quot;&quot;Normalize sentiment label to standard format&quot;&quot;&quot;
    return model_map.get(label, label)


@router.post(&quot;/sentiment-analysis&quot;, summary=&quot;Analyze text sentiment&quot;)
async def get_sentiment(input_text: str):
    result = analyzer(input_text)

    result[0][&quot;label&quot;] = normalize_label(result[0][&quot;label&quot;])

    return {&quot;result&quot;: result}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-04 오후 7.33.55.png&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;429&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beGrAS/dJMcaicxVzT/IuSucESMEkhRL5G4i5eYi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beGrAS/dJMcaicxVzT/IuSucESMEkhRL5G4i5eYi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beGrAS/dJMcaicxVzT/IuSucESMEkhRL5G4i5eYi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeGrAS%2FdJMcaicxVzT%2FIuSucESMEkhRL5G4i5eYi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1233&quot; height=&quot;429&quot; data-filename=&quot;스크린샷 2026-06-04 오후 7.33.55.png&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;429&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 이런 테스트를 통해 어떤 모델이 감정 분석에 효율적인지 찾는 과정을 거쳐야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 WhitePeak/bert-base-cased-Korean-sentiment 모델을 사용하도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3단계: 감정 시각화&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;719&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnKvpM/dJMcadvpzsm/ledbd4wC98KEeFhfsVvDO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnKvpM/dJMcadvpzsm/ledbd4wC98KEeFhfsVvDO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnKvpM/dJMcadvpzsm/ledbd4wC98KEeFhfsVvDO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnKvpM%2FdJMcadvpzsm%2Fledbd4wC98KEeFhfsVvDO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;719&quot; height=&quot;242&quot; data-origin-width=&quot;719&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이, 감정 분석한 것을 시각화하는 과정을 거치겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Output 부분 화면단을 만들어야 하므로, Next.js를 사용하여 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;app/emotion_visual/page.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780619924307&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;use client&quot;;

import OutputColumn from &quot;@/components/OutputColumn&quot;;
import PutColumn from &quot;@/components/PutColumn&quot;;
import { useState } from &quot;react&quot;;

export interface Output {
    model_lang: string;
    result: Result[];
}

interface Result {
    label: string;
    score: number;
}

export default function EmotionVisualPage() {
    const [inputValue, setInputValue] = useState&amp;lt;string&amp;gt;(&quot;&quot;);
    const [isSubmitting, setIsSubmitting] = useState&amp;lt;boolean&amp;gt;(false)
    const [outputValue, setOutputValue] = useState&amp;lt;Output | null&amp;gt;(null)

    const inputChange = (e: React.ChangeEvent&amp;lt;HTMLTextAreaElement&amp;gt;) =&amp;gt; {
        setInputValue(e.target.value)
    }

    const inputClear = () =&amp;gt; {
        setInputValue(&quot;&quot;);
    }

    const inputSubmit = async () =&amp;gt; {
        try {
            setIsSubmitting(true);
            const response = await fetch(`http://localhost:8000/api/v1/sentiment-analysis?input_text=${inputValue}`, {
                method: &quot;POST&quot;
            })
            const json = await response.json()
            setOutputValue(json)
        } catch (error) {
            console.log(error)
        } finally {
            setIsSubmitting(false);
        }
    }

    return (
        &amp;lt;main&amp;gt;
            &amp;lt;div id=&quot;wrap&quot; className=&quot;w-[1080px] mx-auto&quot;&amp;gt;
                &amp;lt;h2 className=&quot;text-center mb-[30px]&quot;&amp;gt;AI 감정 분석 웹앱&amp;lt;/h2&amp;gt;
                &amp;lt;p className=&quot;text-[14px] mb-[16px]&quot;&amp;gt;
                    Hugging Face Transformer 모델 기반 감정 분석
                &amp;lt;/p&amp;gt;
                &amp;lt;div className=&quot;put__rc grid grid-cols-2 gap-[10px]&quot;&amp;gt;
                    &amp;lt;PutColumn ele=&quot;input&quot; label=&quot;text&quot; value={inputValue} onSubmit={inputSubmit} onChange={inputChange} onClear={inputClear} isSubmitting={isSubmitting} /&amp;gt;
                    &amp;lt;OutputColumn output={outputValue} /&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/main&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/components/OutputColumn.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780619984235&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Output } from &quot;@/app/emotion_visual/page&quot;;
import { VscOutput } from &quot;react-icons/vsc&quot;;
import OutputBar from &quot;./OutputBar&quot;;

export default function OutputColumn({ output }: { output: Output | null }) {
    return (
        &amp;lt;div className=&quot;new__output w-full&quot;&amp;gt;
            &amp;lt;div id=&quot;outputArea&quot; className=&quot;w-full border border-[#e7e7e9] rounded-[5px] overflow-auto flex flex-col&quot;&amp;gt;
                &amp;lt;div id=&quot;outputAreaTitle&quot; className=&quot;w-[80px] h-[30px] border-r border-b border-[#e7e7e9] flex justify-center items-center gap-[6px]&quot;&amp;gt;
                    &amp;lt;VscOutput size={14} /&amp;gt;
                    &amp;lt;span className=&quot;text-[12px]&quot;&amp;gt;Output&amp;lt;/span&amp;gt;
                &amp;lt;/div&amp;gt;
                {output ? (
                    &amp;lt;div id=&quot;output&quot; className=&quot;flex flex-col pb-[50px]&quot;&amp;gt;
                        &amp;lt;h2 className=&quot;text-[19px] text-center mb-[20px]&quot;&amp;gt;{output.result[0].label}&amp;lt;/h2&amp;gt;
                        &amp;lt;OutputBar current={[output.result[0].label, output.result[0].score]} /&amp;gt;
                        &amp;lt;OutputBar current={[&quot;others&quot;, 1 - output.result[0].score]} /&amp;gt;
                    &amp;lt;/div&amp;gt;
                ) : (
                    &amp;lt;div id=&quot;output&quot; className=&quot;flex flex-col pb-[50px]&quot;&amp;gt;
                        &amp;lt;h2 className=&quot;text-[19px] text-center mb-[20px]&quot;&amp;gt;No output&amp;lt;/h2&amp;gt;
                    &amp;lt;/div&amp;gt;
                )}
            &amp;lt;/div&amp;gt;
            &amp;lt;button className=&quot;w-full font-[600] py-[10px] bg-[#e4e4e6] rounded-[5px] cursor-pointer mt-[30px]&quot;&amp;gt;
                Flag
            &amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/components/OutputBar.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780620018882&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function OutputBar({ current }: { current: any[] }) {
    return (
        &amp;lt;div className=&quot;bar__container mb-[10px] self-center w-[90%]&quot;&amp;gt;
            &amp;lt;div className=&quot;bar h-[6px] rounded-[6px]&quot; style={{ width: `${Math.floor(current[1] * 100)}%`, backgroundImage: `linear-gradient(to right,#f39b4b,#fadcb1)` }}&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;div className=&quot;info w-full flex justify-between items-center&quot;&amp;gt;
                &amp;lt;span className=&quot;text-[14px]&quot;&amp;gt;{current[0]}&amp;lt;/span&amp;gt;
                &amp;lt;span className=&quot;text-[14px]&quot;&amp;gt;{Math.floor(current[1] * 100)}%&amp;lt;/span&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오전 9.40.41.png&quot; data-origin-width=&quot;1221&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdPqlJ/dJMcaar1Tvz/gekyoKS2jbKgoFvRXYzxb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdPqlJ/dJMcaar1Tvz/gekyoKS2jbKgoFvRXYzxb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdPqlJ/dJMcaar1Tvz/gekyoKS2jbKgoFvRXYzxb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdPqlJ%2FdJMcaar1Tvz%2FgekyoKS2jbKgoFvRXYzxb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1221&quot; height=&quot;482&quot; data-filename=&quot;스크린샷 2026-06-05 오전 9.40.41.png&quot; data-origin-width=&quot;1221&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 json 데이터 기반으로 현재 감정과 반대 감정을 동시에 보내서 표현해주면 끝이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4단계: 다중 문장 감정 분석&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;415&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lR0KH/dJMcabLhoM6/77OYFihkTaMGf8GfcZnYdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lR0KH/dJMcabLhoM6/77OYFihkTaMGf8GfcZnYdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lR0KH/dJMcabLhoM6/77OYFihkTaMGf8GfcZnYdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlR0KH%2FdJMcabLhoM6%2F77OYFihkTaMGf8GfcZnYdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;974&quot; height=&quot;415&quot; data-origin-width=&quot;974&quot; data-origin-height=&quot;415&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 동안 하나의 문장으로 분석한 결과를 알려주는 앱을 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 여러 문장을 넣어서, 한 문장씩 결과를 알려주는 앱을 만들어 보고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 여러 문장을 받아서 리스트로 처리해야 하는데, 여기서 splitlines() 메소드를 사용해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780622061738&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@router.post(&quot;/multi-sentiment&quot;, summary=&quot;multi-sentiment&quot;)
async def post_multi_sentiment(data: TextInput):
    # 리스트로 분리
    input_lists = input_text.splitlines()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오전 10.21.25.png&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4Tgd2/dJMcai4zsPO/iVYuVKct9fkMRmkDAk7s6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4Tgd2/dJMcai4zsPO/iVYuVKct9fkMRmkDAk7s6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4Tgd2/dJMcai4zsPO/iVYuVKct9fkMRmkDAk7s6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4Tgd2%2FdJMcai4zsPO%2FiVYuVKct9fkMRmkDAk7s6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;316&quot; height=&quot;138&quot; data-filename=&quot;스크린샷 2026-06-05 오전 10.21.25.png&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오전 10.20.52.png&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;42&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwDL6P/dJMcah5L0Jh/RmjYuae4C3XrknsofH9IKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwDL6P/dJMcah5L0Jh/RmjYuae4C3XrknsofH9IKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwDL6P/dJMcah5L0Jh/RmjYuae4C3XrknsofH9IKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwDL6P%2FdJMcah5L0Jh%2FRmjYuae4C3XrknsofH9IKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;546&quot; height=&quot;42&quot; data-filename=&quot;스크린샷 2026-06-05 오전 10.20.52.png&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;42&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 상태로 분리되므로 우리가 원한 결과가 나왔다고 할 수 있겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 여백이 존재할 수도 있으므로 strip() 처리를 추가적으로 해 줄 필요는 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780622316800&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 여백 처리
sentences = [s.strip() for s in input_lists if s.strip()]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 각각 for를 돌면서 각 문장마다 결과값을 보내주어야 할 것 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1780625214870&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@router.post(&quot;/multi-sentiment&quot;, summary=&quot;multi-sentiment&quot;)
async def post_multi_sentiment(data: TextInput):

    results_text = []
    input_text = data.input_text

    # 리스트로 분리
    input_lists = input_text.splitlines()

    # 여백 처리
    sentences = [s.strip() for s in input_lists if s.strip()]

    # 각 문장별로 개별 분석
    analyzer = korean_analyzer if is_korean(input_text) else english_analyzer

    for sentence in sentences:
        result = analyzer(sentence)
        # result: [{&quot;label&quot;: &quot;LABEL_0&quot;, &quot;score&quot;: 0.999}]
        best = result[0]
        label = best[&quot;label&quot;]
        label = normalize_label(label)
        score = best[&quot;score&quot;]

        results_text.append({&quot;text&quot;: sentence, &quot;label&quot;: label, &quot;score&quot;: score})

    return results_text&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드에서 이 결과값을 map 메소드로 돌려주면 끝일 것 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1780625830417&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{outputs!.length &amp;gt; 0 ? (
                    &amp;lt;div id=&quot;output&quot; className=&quot;flex flex-col pb-[50px]&quot;&amp;gt;
                        {
                            outputs!.map((out, idx) =&amp;gt; (
                                &amp;lt;div key={idx} className=&quot;flex flex-col pt-[6px] px-[10px]&quot;&amp;gt;
                                    &amp;lt;p className=&quot;text-[14px]&quot;&amp;gt;문장: {out.text}&amp;lt;/p&amp;gt;
                                    &amp;lt;p className=&quot;text-[14px]&quot;&amp;gt;감정: {out.label}&amp;lt;/p&amp;gt;
                                    &amp;lt;p className=&quot;text-[14px]&quot;&amp;gt;확률: {out.score}&amp;lt;/p&amp;gt;
                                &amp;lt;/div&amp;gt;
                            ))
                        }
                    &amp;lt;/div&amp;gt;
                ) : (
                    &amp;lt;div id=&quot;output&quot; className=&quot;flex flex-col pb-[50px]&quot;&amp;gt;
                        &amp;lt;h2 className=&quot;text-[19px] text-center mb-[20px]&quot;&amp;gt;No output&amp;lt;/h2&amp;gt;
                    &amp;lt;/div&amp;gt;
                )}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오전 11.21.03.png&quot; data-origin-width=&quot;1253&quot; data-origin-height=&quot;485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWgdRl/dJMcaftiFHm/xpzEFB6Kelyi4jmVnLVDJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWgdRl/dJMcaftiFHm/xpzEFB6Kelyi4jmVnLVDJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWgdRl/dJMcaftiFHm/xpzEFB6Kelyi4jmVnLVDJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWgdRl%2FdJMcaftiFHm%2FxpzEFB6Kelyi4jmVnLVDJk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1253&quot; height=&quot;485&quot; data-filename=&quot;스크린샷 2026-06-05 오전 11.21.03.png&quot; data-origin-width=&quot;1253&quot; data-origin-height=&quot;485&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로, Output UI를 데이터프레임(테이블) 형태로 구현해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;979&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IGSg3/dJMcadhY05v/v2RFHjTRLCKk5fMPG5k2q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IGSg3/dJMcadhY05v/v2RFHjTRLCKk5fMPG5k2q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IGSg3/dJMcadhY05v/v2RFHjTRLCKk5fMPG5k2q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIGSg3%2FdJMcadhY05v%2Fv2RFHjTRLCKk5fMPG5k2q0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;979&quot; height=&quot;242&quot; data-origin-width=&quot;979&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1780626635421&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{outputs!.length &amp;gt; 0 ? (
                    &amp;lt;div id=&quot;output&quot; className=&quot;flex flex-col pb-[50px] pt-[20px] px-[20px]&quot;&amp;gt;
                        &amp;lt;table className=&quot;border-collapse border border-[#e7e7e7]&quot;&amp;gt;
                            &amp;lt;thead&amp;gt;
                                &amp;lt;tr&amp;gt;
                                    &amp;lt;th className=&quot;border border-[#e7e7e7] pl-[10px] py-[8px] text-left&quot;&amp;gt;문장&amp;lt;/th&amp;gt;
                                    &amp;lt;th className=&quot;border border-[#e7e7e7] pl-[10px] py-[8px] text-left&quot;&amp;gt;감정&amp;lt;/th&amp;gt;
                                    &amp;lt;th className=&quot;border border-[#e7e7e7] pl-[10px] py-[8px] text-left&quot;&amp;gt;확률&amp;lt;/th&amp;gt;
                                &amp;lt;/tr&amp;gt;
                            &amp;lt;/thead&amp;gt;
                            &amp;lt;tbody&amp;gt;
                                {
                                    outputs?.map((output: Outputs, index: number) =&amp;gt; (
                                        &amp;lt;tr key={index}&amp;gt;
                                            &amp;lt;td className=&quot;border border-[#e7e7e7] pl-[10px] py-[8px]&quot;&amp;gt;{output.text}&amp;lt;/td&amp;gt;
                                            &amp;lt;td className=&quot;border border-[#e7e7e7] pl-[10px] py-[8px]&quot;&amp;gt;{output.label}&amp;lt;/td&amp;gt;
                                            &amp;lt;td className=&quot;border border-[#e7e7e7] pl-[10px] py-[8px]&quot;&amp;gt;{output.score}&amp;lt;/td&amp;gt;
                                        &amp;lt;/tr&amp;gt;
                                    ))
                                }
                            &amp;lt;/tbody&amp;gt;
                        &amp;lt;/table&amp;gt;
                    &amp;lt;/div&amp;gt;
                ) : (
                    &amp;lt;div id=&quot;output&quot; className=&quot;flex flex-col pb-[50px]&quot;&amp;gt;
                        &amp;lt;h2 className=&quot;text-[19px] text-center mb-[20px]&quot;&amp;gt;No output&amp;lt;/h2&amp;gt;
                    &amp;lt;/div&amp;gt;
                )}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오전 11.30.48.png&quot; data-origin-width=&quot;1253&quot; data-origin-height=&quot;485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/39iGQ/dJMcaijgv8w/UkBqZomwzUIM3BL4hUfXh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/39iGQ/dJMcaijgv8w/UkBqZomwzUIM3BL4hUfXh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/39iGQ/dJMcaijgv8w/UkBqZomwzUIM3BL4hUfXh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F39iGQ%2FdJMcaijgv8w%2FUkBqZomwzUIM3BL4hUfXh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1253&quot; height=&quot;485&quot; data-filename=&quot;스크린샷 2026-06-05 오전 11.30.48.png&quot; data-origin-width=&quot;1253&quot; data-origin-height=&quot;485&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;5단계 : csv 파일 업로드 + 통계&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;890&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2Hz4B/dJMcahdCbbn/5rKmTAAvrqnXKeoWyZFbp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2Hz4B/dJMcahdCbbn/5rKmTAAvrqnXKeoWyZFbp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2Hz4B/dJMcahdCbbn/5rKmTAAvrqnXKeoWyZFbp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2Hz4B%2FdJMcahdCbbn%2F5rKmTAAvrqnXKeoWyZFbp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1233&quot; height=&quot;890&quot; data-origin-width=&quot;1233&quot; data-origin-height=&quot;890&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이제는 파일로 데이터 분석을 실행해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;새로운 UI로 사용자에게 보여줄 예정이므로, 프론트엔드 코드를 다시 만들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Next.js 코드는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;app/emotion_csv/page.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780648051326&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;use client&quot;;

import { useState } from 'react';
import CsvResults from &quot;@/components/CsvResults&quot;;
import FileAnalysis from &quot;@/components/FileAnalysis&quot;;
import CsvOutputs from '@/components/CsvOutputs';

export interface AnalyzedData {
    all_reviews: number;
    positive_reviews: number;
    neutral_reviews: number;
    negative_reviews: number;
    positive_rate: number;
    neutral_rate: number;
    negative_rate: number;
}

export default function EmotionCsvPage() {
    const [results, setResults] = useState&amp;lt;string[]&amp;gt;([]);
    const [analyzedData, setAnalyzedData] = useState&amp;lt;AnalyzedData | null&amp;gt;(null);

    const changeResults = (data: string[]) =&amp;gt; {
        setResults(data);
    }

    const changeAnalyzedData = (data: AnalyzedData) =&amp;gt; {
        setAnalyzedData(data);
    }

    return (
        &amp;lt;main&amp;gt;
            &amp;lt;div id=&quot;wrap&quot; className=&quot;w-[1080px] mx-auto&quot;&amp;gt;
                &amp;lt;h2 className=&quot;text-center mb-[30px]&quot;&amp;gt;AI 감정 분석 웹앱&amp;lt;/h2&amp;gt;
                &amp;lt;p className=&quot;text-[14px] mb-[16px]&quot;&amp;gt;
                    Hugging Face Transformer 모델 기반 감정 분석
                &amp;lt;/p&amp;gt;
                &amp;lt;FileAnalysis changeResults={changeResults} changeAnalyzedData={changeAnalyzedData} /&amp;gt;
                &amp;lt;CsvResults results={results} /&amp;gt;
                &amp;lt;CsvOutputs analyzedData={analyzedData} /&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/main&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/components/CsvResults.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780648088381&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { cn } from &quot;@/lib/cn&quot;;

export default function CsvResults({ results }: { results: string[] }) {
    return (
        &amp;lt;div id=&quot;csvResults&quot; className=&quot;mt-[60px] w-full h-[400px] border border-[#e6e6e8] rounded-[8px] overflow-hidden flex flex-col&quot;&amp;gt;
            &amp;lt;h2 className=&quot;pl-[6px] pt-[8px] pb-[10px] h-[15px] bg-[#fff] border-b-[10px] border-[#e6e6e8] text-[14px] font-[500]&quot;&amp;gt;문장&amp;lt;/h2&amp;gt;
            &amp;lt;div className={cn(&quot;flex-1 overflow-y-scroll flex flex-col&quot;, results.length == 0 &amp;amp;&amp;amp; &quot;justify-center items-center&quot;)}&amp;gt;
                {results.length &amp;gt; 0 ? results.map((result: string, index: number) =&amp;gt; &amp;lt;p key={index} className={cn(&quot;px-[6px] text-[14px] py-[10px]&quot;, index % 2 === 1 ? &quot;bg-[#fafafa]&quot; : &quot;bg-[#fff]&quot;)}&amp;gt;{result}&amp;lt;/p&amp;gt;) : &amp;lt;p className=&quot;text-[14px]&quot;&amp;gt;파일이 비어있습니다.&amp;lt;/p&amp;gt;}
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/components/CsvOutputs.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780648131741&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { AnalyzedData } from &quot;@/app/emotion_csv/page&quot;;

export default function CsvOutputs({ analyzedData }: { analyzedData: AnalyzedData | null }) {
    return (
        &amp;lt;div id=&quot;csvOutputs&quot; className=&quot;w-full h-[200px] border border-[#e6e6e8] rounded-[6px] mt-[40px] p-[10px] flex flex-col&quot;&amp;gt;
            &amp;lt;h2 className=&quot;text-[14px] font-[500] mb-[20px] h-[20px] flex items-center&quot;&amp;gt;통계 결과&amp;lt;/h2&amp;gt;
            {analyzedData ? (
                &amp;lt;div id=&quot;csvOutputsBox&quot; className=&quot;w-full h-[140px] overflow-y-auto border border-[#e6e6e8] rounded-[6px] p-[10px] box-border flex-1 py-[30px] px-[20px]&quot;&amp;gt;
                    &amp;lt;p className=&quot;text-[14px]&quot;&amp;gt;
                        총 리뷰 수 : {analyzedData.all_reviews} &amp;lt;br /&amp;gt;
                        &amp;lt;br /&amp;gt;
                        긍정 리뷰 : {analyzedData.positive_reviews} 개 &amp;lt;br /&amp;gt;
                        부정 리뷰 : {analyzedData.negative_reviews} 개 &amp;lt;br /&amp;gt;
                        중립 리뷰 : {analyzedData.neutral_reviews}
                        &amp;lt;br /&amp;gt;
                        &amp;lt;br /&amp;gt;
                        긍정 비율 : {analyzedData.positive_rate} % &amp;lt;br /&amp;gt;
                        부정 비율 : {analyzedData.negative_rate} % &amp;lt;br /&amp;gt;
                        중립 비율 : {analyzedData.neutral_rate} %
                    &amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
            ) : (
                &amp;lt;div id=&quot;csvOutputsBox&quot; className=&quot;w-full h-[140px] border border-[#e6e6e8] rounded-[6px] p-[10px] box-border flex-1 flex flex-col justify-center items-center&quot;&amp;gt;
                    &amp;lt;p className=&quot;text-[14px]&quot;&amp;gt;감정분석을 실행해주세요.&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
            )}
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FastAPI 코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1780648173203&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@router.post(&quot;/csv_result&quot;, summary=&quot;result of csv&quot;)
async def post_csv_result(data: CsvInput):
    csv_array = []

    file_path = data.input_path.replace(&quot;http://localhost:8000&quot;, &quot;&quot;).lstrip(&quot;/&quot;)

    # 프로젝트 루트 기준 전체 경로로 변환
    project_root = Path(__file__).parent.parent.parent.parent.parent
    full_path = project_root / file_path

    # csv 파일 다루기
    with open(full_path, &quot;r&quot;, encoding=&quot;utf-8&quot;) as f:
        reader = pd.read_csv(f)
        for r in reader[&quot;review&quot;]:
            csv_array.append(r)
        return {&quot;data&quot;: csv_array}


@router.post(&quot;/csv_chart&quot;, summary=&quot;chart of csv&quot;)
async def post_csv_chart(data: CsvInput):
    file_path = data.input_path.replace(&quot;http://localhost:8000&quot;, &quot;&quot;).lstrip(&quot;/&quot;)

    project_root = Path(__file__).parent.parent.parent.parent.parent
    full_path = project_root / file_path

    pd_result = pd.read_csv(full_path)
    pd_list = pd_result[&quot;review&quot;].to_list()

    # 총 리뷰 수
    all_reviews = len(pd_list)

    # 긍정 리뷰 수
    positive_reviews = 0
    # 중립 리뷰 수
    neutral_reviews = 0
    # 부정 리뷰 수
    negative_reviews = 0

    # 긍정 비율
    positive_rate = 0
    # 중립 비율
    neutral_rate = 0
    # 부정 비율
    negative_rate = 0

    for sentence in pd_list:
        if is_korean(sentence):
            new_result = korean_analyzer(sentence)
        else:
            new_result = english_analyzer(sentence)

        if normalize_label(new_result[0][&quot;label&quot;]) == &quot;긍정  &quot;:
            positive_reviews += 1
        elif normalize_label(new_result[0][&quot;label&quot;]) == &quot;중립  &quot;:
            neutral_reviews += 1
        elif normalize_label(new_result[0][&quot;label&quot;]) == &quot;부정  &quot;:
            negative_reviews += 1

    positive_rate = round(positive_reviews / all_reviews * 100, 4)
    neutral_rate = round(neutral_reviews / all_reviews * 100, 4)
    negative_rate = round(negative_reviews / all_reviews * 100, 4)

    return {
        &quot;all_reviews&quot;: all_reviews,
        &quot;positive_reviews&quot;: positive_reviews,
        &quot;neutral_reviews&quot;: neutral_reviews,
        &quot;negative_reviews&quot;: negative_reviews,
        &quot;positive_rate&quot;: positive_rate,
        &quot;neutral_rate&quot;: neutral_rate,
        &quot;negative_rate&quot;: negative_rate,
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오후 5.30.55.png&quot; data-origin-width=&quot;1631&quot; data-origin-height=&quot;387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4nzaM/dJMcabLh2lL/s2upVBN7kHvulHTkV5ABkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4nzaM/dJMcabLh2lL/s2upVBN7kHvulHTkV5ABkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4nzaM/dJMcabLh2lL/s2upVBN7kHvulHTkV5ABkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4nzaM%2FdJMcabLh2lL%2Fs2upVBN7kHvulHTkV5ABkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1631&quot; height=&quot;387&quot; data-filename=&quot;스크린샷 2026-06-05 오후 5.30.55.png&quot; data-origin-width=&quot;1631&quot; data-origin-height=&quot;387&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오후 5.31.49.png&quot; data-origin-width=&quot;1614&quot; data-origin-height=&quot;764&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sJU7H/dJMcah5MFFl/YsCDTihl3yIcwI7IhDq5B0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sJU7H/dJMcah5MFFl/YsCDTihl3yIcwI7IhDq5B0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sJU7H/dJMcah5MFFl/YsCDTihl3yIcwI7IhDq5B0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsJU7H%2FdJMcah5MFFl%2FYsCDTihl3yIcwI7IhDq5B0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1614&quot; height=&quot;764&quot; data-filename=&quot;스크린샷 2026-06-05 오후 5.31.49.png&quot; data-origin-width=&quot;1614&quot; data-origin-height=&quot;764&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밑에 있는 통계 결과를 추가로 차트 형식으로 변경해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;recharts 라이브러리를 이용하여 아래와 같이 변경하면 될 것 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1780648743001&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;'use client';

import { AnalyzedData } from &quot;@/app/emotion_csv/page&quot;;
import { PieChart, Pie, Cell, Legend, Tooltip, ResponsiveContainer } from &quot;recharts&quot;;

export default function CsvOutputs({ analyzedData }: { analyzedData: AnalyzedData | null }) {
    const chartData = analyzedData ? [
        { name: &quot;긍정&quot;, value: analyzedData.positive_reviews },
        { name: &quot;부정&quot;, value: analyzedData.negative_reviews },
    ] : [];

    const COLORS = [&quot;#4CAF50&quot;, &quot;#F44336&quot;];

    const renderCustomLabel = (entry: any) =&amp;gt; {
        const { cx, cy, midAngle, innerRadius, outerRadius, name, value, percent } = entry;
        const radius = innerRadius + (outerRadius - innerRadius) * 0.2;
        const x = cx + radius * Math.cos(-midAngle * Math.PI / 180);
        const y = cy + radius * Math.sin(-midAngle * Math.PI / 180);
        const percentage = (percent * 100).toFixed(1);

        return (
            &amp;lt;text
                x={x}
                y={y}
                fill=&quot;white&quot;
                textAnchor={x &amp;gt; cx ? 'start' : 'end'}
                dominantBaseline=&quot;central&quot;
                className=&quot;text-[14px] font-[600]&quot;
            &amp;gt;
                {`${name}\n(${percentage}%)`}
            &amp;lt;/text&amp;gt;
        );
    };

    return (
        &amp;lt;div id=&quot;csvOutputs&quot; className=&quot;w-full h-[400px] border border-[#e6e6e8] rounded-[6px] mt-[40px] p-[10px] flex flex-col&quot;&amp;gt;
            &amp;lt;h2 className=&quot;text-[14px] font-[500] mb-[20px] h-[20px] flex items-center&quot;&amp;gt;통계 결과&amp;lt;/h2&amp;gt;
            {analyzedData ? (
                &amp;lt;div id=&quot;csvOutputsBox&quot; className=&quot;w-full h-[340px] border border-[#e6e6e8] rounded-[6px] p-[10px] box-border flex-1&quot;&amp;gt;
                    &amp;lt;ResponsiveContainer width=&quot;100%&quot; height=&quot;100%&quot;&amp;gt;
                        &amp;lt;PieChart&amp;gt;
                            &amp;lt;Pie
                                data={chartData}
                                cx=&quot;50%&quot;
                                cy=&quot;50%&quot;
                                labelLine={false}
                                label={renderCustomLabel}
                                outerRadius={100}
                                fill=&quot;#8884d8&quot;
                                dataKey=&quot;value&quot;
                            &amp;gt;
                                {chartData.map((entry, index) =&amp;gt; (
                                    &amp;lt;Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} /&amp;gt;
                                ))}
                            &amp;lt;/Pie&amp;gt;
                            &amp;lt;Tooltip /&amp;gt;
                        &amp;lt;/PieChart&amp;gt;
                    &amp;lt;/ResponsiveContainer&amp;gt;
                &amp;lt;/div&amp;gt;
            ) : (
                &amp;lt;div id=&quot;csvOutputsBox&quot; className=&quot;w-full h-[340px] border border-[#e6e6e8] rounded-[6px] p-[10px] box-bower flex-1 flex flex-col justify-center items-center&quot;&amp;gt;
                    &amp;lt;p className=&quot;text-[14px]&quot;&amp;gt;감정분석을 실행해주세요.&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
            )}
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오후 5.39.31.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dihusp/dJMcafz6rFv/rbKfdTeVV83t8PGXOPcwsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dihusp/dJMcafz6rFv/rbKfdTeVV83t8PGXOPcwsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dihusp/dJMcafz6rFv/rbKfdTeVV83t8PGXOPcwsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdihusp%2FdJMcafz6rFv%2FrbKfdTeVV83t8PGXOPcwsK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1260&quot; height=&quot;412&quot; data-filename=&quot;스크린샷 2026-06-05 오후 5.39.31.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;6단계 : 가장 긍정적인 가장 부정적인 리뷰 찾기&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KUSjE/dJMcafGS9AB/DK0njv4EP0QDhcTcC8CCnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KUSjE/dJMcafGS9AB/DK0njv4EP0QDhcTcC8CCnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KUSjE/dJMcafGS9AB/DK0njv4EP0QDhcTcC8CCnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKUSjE%2FdJMcafGS9AB%2FDK0njv4EP0QDhcTcC8CCnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;978&quot; height=&quot;370&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긍정 및 부정 점수를 통해 가장 긍정적이고, 가장 부정적인 리뷰 찾아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780648969722&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; # 가장 긍정/부정적인 리뷰 저장
    most_positive_review = None
    most_positive_score = -1
    most_negative_review = None
    most_negative_score = -1

    for sentence in pd_list:
        if is_korean(sentence):
            new_result = korean_analyzer(sentence)
        else:
            new_result = english_analyzer(sentence)

        label = normalize_label(new_result[0][&quot;label&quot;])
        score = new_result[0][&quot;score&quot;]

        if label == &quot;긍정  &quot;:
            positive_reviews += 1
            if score &amp;gt; most_positive_score:
                most_positive_score = score
                most_positive_review = sentence
        elif label == &quot;중립  &quot;:
            neutral_reviews += 1
        elif label == &quot;부정  &quot;:
            negative_reviews += 1
            if most_negative_review is None or score &amp;gt; most_negative_score:
                most_negative_score = score
                most_negative_review = sentence&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1780649014457&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{analyzedData &amp;amp;&amp;amp; (
                &amp;lt;div className=&quot;mt-[20px] grid grid-cols-2 gap-[10px]&quot;&amp;gt;
                    &amp;lt;div className=&quot;border border-[#4CAF50] rounded-[6px] p-[12px] bg-[#f1f8f4]&quot;&amp;gt;
                        &amp;lt;h3 className=&quot;text-[12px] font-[600] text-[#4CAF50] mb-[8px]&quot;&amp;gt;가장 긍정적인 리뷰&amp;lt;/h3&amp;gt;
                        &amp;lt;p className=&quot;text-[12px] text-[#333] leading-[1.5] line-clamp-2&quot;&amp;gt;{analyzedData.most_positive_review}&amp;lt;/p&amp;gt;
                        &amp;lt;p className=&quot;text-[10px] text-[#666] mt-[6px]&quot;&amp;gt;신뢰도: {analyzedData.most_positive_score}&amp;lt;/p&amp;gt;
                    &amp;lt;/div&amp;gt;
                    &amp;lt;div className=&quot;border border-[#F44336] rounded-[6px] p-[12px] bg-[#fef4f3]&quot;&amp;gt;
                        &amp;lt;h3 className=&quot;text-[12px] font-[600] text-[#F44336] mb-[8px]&quot;&amp;gt;가장 부정적인 리뷰&amp;lt;/h3&amp;gt;
                        &amp;lt;p className=&quot;text-[12px] text-[#333] leading-[1.5] line-clamp-2&quot;&amp;gt;{analyzedData.most_negative_review}&amp;lt;/p&amp;gt;
                        &amp;lt;p className=&quot;text-[10px] text-[#666] mt-[6px]&quot;&amp;gt;신뢰도: {analyzedData.most_negative_score}&amp;lt;/p&amp;gt;
                    &amp;lt;/div&amp;gt;
                &amp;lt;/div&amp;gt;
            )}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-05 오후 5.46.38.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIawM4/dJMcaaevk2O/n3V0BZhkDzI1hoPi9B2yB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIawM4/dJMcaaevk2O/n3V0BZhkDzI1hoPi9B2yB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIawM4/dJMcaaevk2O/n3V0BZhkDzI1hoPi9B2yB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIawM4%2FdJMcaaevk2O%2Fn3V0BZhkDzI1hoPi9B2yB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1260&quot; height=&quot;485&quot; data-filename=&quot;스크린샷 2026-06-05 오후 5.46.38.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;485&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>AI/LLM</category>
      <category>Ai</category>
      <category>models</category>
      <category>감정분석</category>
      <author>EunHaa</author>
      <guid isPermaLink="true">https://eun-haa.tistory.com/27</guid>
      <comments>https://eun-haa.tistory.com/27#entry27comment</comments>
      <pubDate>Fri, 5 Jun 2026 17:48:27 +0900</pubDate>
    </item>
    <item>
      <title>허깅 페이스(Hugging Face) - 멀티 모달 (5)</title>
      <link>https://eun-haa.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;앞서서 Transformer의 pipeline 함수를 이용해 텍스트를 이용한 여러 태스크를 알아봤었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 이미지 관련된 태스크를 알아볼 예정인데, 이러면서 나오는 개념이 &lt;b&gt;멀티 모달&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 모달이란, 여러 종류의 데이터를 처리하는 AI 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 모달 종류에는 다음과 같은 종류가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;텍스트 + 이미지&lt;/li&gt;
&lt;li&gt;음성 + 텍스트&lt;/li&gt;
&lt;li&gt;영상 + 텍스트&lt;/li&gt;
&lt;li&gt;이미지 + 텍스트&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 것을 하기 위해서 이미지 처리를 어떻게 할 것인지 필요한데 이미지 관련 태스크를 가져오면 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;허깅 페이스(Hugging Face)에 접속해서 태스크 및 모델을 찾을 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://huggingface.co/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://huggingface.co/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1780265236021&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Hugging Face &amp;ndash; The AI community building the future.&quot; data-og-description=&quot;We&amp;rsquo;re on a journey to advance and democratize artificial intelligence through open source and open science.&quot; data-og-host=&quot;huggingface.co&quot; data-og-source-url=&quot;https://huggingface.co/&quot; data-og-url=&quot;https://huggingface.co/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/UtKZk/dJMb9b3Y0ru/MQ2nCtiikVnitDk1sIKS20/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648,https://scrap.kakaocdn.net/dn/6phdC/dJMb9eTWxwO/9YHr6M0U2796QhCQ2cMOPK/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648&quot;&gt;&lt;a href=&quot;https://huggingface.co/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://huggingface.co/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/UtKZk/dJMb9b3Y0ru/MQ2nCtiikVnitDk1sIKS20/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648,https://scrap.kakaocdn.net/dn/6phdC/dJMb9eTWxwO/9YHr6M0U2796QhCQ2cMOPK/img.png?width=1200&amp;amp;height=648&amp;amp;face=0_0_1200_648');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Hugging Face &amp;ndash; The AI community building the future.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;We&amp;rsquo;re on a journey to advance and democratize artificial intelligence through open source and open science.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;huggingface.co&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Models 탭에 들어간다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-01 오전 7.07.40.png&quot; data-origin-width=&quot;97&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b13vO7/dJMcacwyCvn/JyMD0yQluZYKX9XJxkEiI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b13vO7/dJMcacwyCvn/JyMD0yQluZYKX9XJxkEiI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b13vO7/dJMcacwyCvn/JyMD0yQluZYKX9XJxkEiI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb13vO7%2FdJMcacwyCvn%2FJyMD0yQluZYKX9XJxkEiI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;97&quot; height=&quot;48&quot; data-filename=&quot;스크린샷 2026-06-01 오전 7.07.40.png&quot; data-origin-width=&quot;97&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 항목에서 Tasks 항목에 들어간다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-01 오전 7.08.47.png&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;655&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7Sa9j/dJMcaaFxm0o/1K4WbXngb8zjGwLgHBZSK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7Sa9j/dJMcaaFxm0o/1K4WbXngb8zjGwLgHBZSK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7Sa9j/dJMcaaFxm0o/1K4WbXngb8zjGwLgHBZSK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7Sa9j%2FdJMcaaFxm0o%2F1K4WbXngb8zjGwLgHBZSK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;434&quot; height=&quot;655&quot; data-filename=&quot;스크린샷 2026-06-01 오전 7.08.47.png&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;655&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;이미지 분류(Image Classification)&lt;/b&gt;을 선택해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-01 오전 7.10.01.png&quot; data-origin-width=&quot;176&quot; data-origin-height=&quot;41&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkcwO3/dJMcah5IQMB/TV76x3fu1ppqbZyzdGX3j0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkcwO3/dJMcah5IQMB/TV76x3fu1ppqbZyzdGX3j0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkcwO3/dJMcah5IQMB/TV76x3fu1ppqbZyzdGX3j0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkcwO3%2FdJMcah5IQMB%2FTV76x3fu1ppqbZyzdGX3j0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;176&quot; height=&quot;41&quot; data-filename=&quot;스크린샷 2026-06-01 오전 7.10.01.png&quot; data-origin-width=&quot;176&quot; data-origin-height=&quot;41&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택 후, 옆 레이아웃에 있는 여러가지 모델들을 확인하면 될 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 뭐가 잘 되는지는 테스트를 해 봐야 안다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 창으로 돌아가서 pipeline 코드를 작성해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780265713170&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from transformers import pipeline
from PIL import Image

image = Image.open(&quot;./sample_concert.gif&quot;)
classifier = pipeline(&quot;image-classification&quot;) # 모델 선택하지 않을 경우 기본 모델 가져옴
result = classifier(image)
print(result)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에 대한 설명을 하자면, 가져온 이미지를 Image-Classification 태스크에 맞춰 파이프라인을 거쳐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나온 결과를 출력해주는 코드이다. 결과 예시는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-01 오전 7.18.25.png&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVrg7v/dJMcafGPrN5/VRc2k8fljqgsLcVoXlnmw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVrg7v/dJMcafGPrN5/VRc2k8fljqgsLcVoXlnmw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVrg7v/dJMcafGPrN5/VRc2k8fljqgsLcVoXlnmw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVrg7v%2FdJMcafGPrN5%2FVRc2k8fljqgsLcVoXlnmw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;647&quot; height=&quot;476&quot; data-filename=&quot;스크린샷 2026-06-01 오전 7.18.25.png&quot; data-origin-width=&quot;647&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 나오는 경고 콘솔은 model이 선택되지 않아 기본 모델을 선택한다는 뜻으로 무시해도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 위와 같이 분석해서 JSON 형태로 출력해주는 걸 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음으로, 많이 하는 Tasks가 &lt;b&gt;이미지에 캡션 넣어주는 것(Image-to-text)&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 코드를 작성해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 샘플 테스트이므로, 모델은 기본 모델로 사용할 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780266161674&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from transformers import pipeline
from PIL import Image
import json

capper = pipeline(&quot;image-to-text&quot;)

image = Image.open(&quot;./sample_concert.gif&quot;)

result = capper(image)

print(json.dumps(result,indent=4,ensure_ascii=False))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-01 오전 7.23.30.png&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;97&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/peI25/dJMcaiQ2g9w/8AQTMDeFdg1oi4cCo6FeZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/peI25/dJMcaiQ2g9w/8AQTMDeFdg1oi4cCo6FeZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/peI25/dJMcaiQ2g9w/8AQTMDeFdg1oi4cCo6FeZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpeI25%2FdJMcaiQ2g9w%2F8AQTMDeFdg1oi4cCo6FeZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;616&quot; height=&quot;97&quot; data-filename=&quot;스크린샷 2026-06-01 오전 7.23.30.png&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;97&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 그 다음으로 많이 하는 것은 &lt;b&gt;텍스트를 통해 이미지로 생성&lt;/b&gt;하는 태스크이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 개념을 주로 &lt;b&gt;스테이블&lt;/b&gt;&amp;nbsp;&lt;b&gt;디퓨전(Stable Diffusion)&lt;/b&gt;이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지를 생성할 때 이미지를 처음부터 만들어주는 것이 아니라, 점점 이미지를 좋아지게 만드는 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 처음에는 노이즈 같은 이미지부터 시작해서 텍스트를 입력해서 텍스트 의미를 이해해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점차적으로 노이즈를 제거하고 이미지를 완성하는 개념인 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 GPU 기반으로 움직이면 빨리 결과가 도출되는 듯 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google Colab 같은 외부 환경에서는 런타임 유형 변경 설정이 필요하나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 GPU 기반 맥북에서 진행하고 있으므로, 이 부분은 넘어가도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, Diffusers 개념이 필요하므로, 새로운 패키지를 설치할 필요가 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780266736629&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install diffusers==0.29.2 peft==0.11.1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다음과 같은 코드를 작성해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780266988280&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from diffusers import StableDiffusionPipeline
import torch

pipe = StableDiffusionPipeline.from_pretrained(&quot;runwayml/stable-diffusion-v1-5&quot;, torch_dtype = torch.float16)

# Use Apple Silicon GPU acceleration
pipe = pipe.to(&quot;mps&quot;)

image = pipe(&quot;서울의 밤 풍경&quot;).images[0]

image.save(&quot;./saved/seoul.png&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과 이미지는 개인보다 다르므로, 직접 확인해보길 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다음은 오디오 처리 중 음성을 텍스트로 바꾸는 것에 대해 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;허깅 페이스(Hugging Face)에서는 다음과 같은 태스크에 속한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-01 오전 7.53.10.png&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WC6uz/dJMb997D7li/AIEHHNZhdjLZqDuryVQPrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WC6uz/dJMb997D7li/AIEHHNZhdjLZqDuryVQPrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WC6uz/dJMb997D7li/AIEHHNZhdjLZqDuryVQPrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWC6uz%2FdJMb997D7li%2FAIEHHNZhdjLZqDuryVQPrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;403&quot; height=&quot;108&quot; data-filename=&quot;스크린샷 2026-06-01 오전 7.53.10.png&quot; data-origin-width=&quot;403&quot; data-origin-height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 많이 쓰는 태스크 개념이 whisper이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델을 보면 whisper 기반으로 만들어진 모델들이 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 코드로 테스트해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780268325766&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from transformers import pipeline
import json

whisper = pipeline(&quot;automatic-speech-recognition&quot;, model=&quot;openai/whisper-base&quot;)
result = whisper(&quot;../data/obama.mp3&quot;, return_timestamps=True)
print(json.dumps(result,indent=4,ensure_ascii=False))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-01 오전 7.59.40.png&quot; data-origin-width=&quot;925&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUs3TC/dJMcaiDwu1I/fZJFcbLX6NQxofU88x3wPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUs3TC/dJMcaiDwu1I/fZJFcbLX6NQxofU88x3wPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUs3TC/dJMcaiDwu1I/fZJFcbLX6NQxofU88x3wPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUs3TC%2FdJMcaiDwu1I%2FfZJFcbLX6NQxofU88x3wPK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;925&quot; height=&quot;528&quot; data-filename=&quot;스크린샷 2026-06-01 오전 7.59.40.png&quot; data-origin-width=&quot;925&quot; data-origin-height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음성이 너무 길어, timestamp로 분리하여, chunks 설정을 통해 문장 별로 분리한 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대인 Text to Speech는 경량 TTS 모델들을 이용할 수가 있는데 간단히 테스트해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780268718902&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import edge_tts

text = &quot;안녕하세요 반갑습니다&quot;
voice = &quot;ko-KR-SunHiNeural&quot;

output_file = &quot;./saved/voice.mp3&quot;

async def main():
    communicate = edge_tts.Communicate(text, voice)
    await communicate.save(output_file)

await main()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과물들은 직접 확인해서 테스트해보길 바란다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 위에서 공부했던 개념들을 통해 간단한 웹앱을 만들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 Next.js로 프론트엔드 화면단을 만들고, FastAPI로 백엔드 API 서버단을 구성했지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면단 구현이 부담스러운 사람들은 Gradio나 Streamlit 같은 웹 프레임워크를 사용해도 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표 화면은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.webp&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlvX3J/dJMcacJ8aRx/6QPDkjFrgYfEKSbrRLif31/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlvX3J/dJMcacJ8aRx/6QPDkjFrgYfEKSbrRLif31/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlvX3J/dJMcacJ8aRx/6QPDkjFrgYfEKSbrRLif31/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlvX3J%2FdJMcacJ8aRx%2F6QPDkjFrgYfEKSbrRLif31%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;682&quot; height=&quot;406&quot; data-filename=&quot;image.webp&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 화면을 위해 구현한 Next.js 코드는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;app/page.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780325514329&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import ChatBox from &quot;@/components/ChatBox&quot;;

export default function Home() {

  return (
    &amp;lt;main&amp;gt;
      &amp;lt;div id=&quot;wrap&quot; className=&quot;w-[1080px] mx-auto&quot;&amp;gt;
        &amp;lt;h2 className=&quot;text-center&quot;&amp;gt;멀티모달 AI 챗봇&amp;lt;/h2&amp;gt;
        &amp;lt;p className=&quot;text-[14px] mb-[20px]&quot;&amp;gt;이미지를 업로드하면 이미지에 대한 설명을 생성하는 챗봇입니다. 텍스트로 질문도 가능합니다.&amp;lt;/p&amp;gt;
        &amp;lt;ChatBox /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/main&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;app/components/ChatBox.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780325540491&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;use client&quot;;

import React, { useRef, useState, useEffect } from &quot;react&quot;;
import { IoChatboxEllipsesSharp } from &quot;react-icons/io5&quot;;
import { MdFileUpload, MdOutlineFileDownloadDone } from &quot;react-icons/md&quot;;
import ChatMessage from &quot;./ChatMessage&quot;;
import ImageUrlBox from &quot;./ImageUrlBox&quot;;

export interface ChatBoxMessages {
    chat_id: string;
    message: string;
    fileUrl: string;
    isMe: boolean;
    timestamp: string;
}

export default function ChatBox() {
    const [chatMessage, setChatMessage] = useState&amp;lt;string&amp;gt;(&quot;&quot;);
    const [imageUrl, setImageUrl] = useState&amp;lt;string&amp;gt;(&quot;&quot;);
    const [inputHeight, setInputHeight] = useState&amp;lt;number&amp;gt;(0);
    const [chattingArray, setChattingArray] = useState([]);
    const [isLoading, setIsLoading] = useState&amp;lt;boolean&amp;gt;(false);
    const [isInitialLoading, setIsInitialLoading] = useState&amp;lt;boolean&amp;gt;(true);
    const chatBoxRef = useRef&amp;lt;HTMLDivElement&amp;gt;(null);

    const handleChatMessageChange = (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
        setChatMessage(e.target.value);
    }

    const fileRef = useRef&amp;lt;HTMLInputElement | null&amp;gt;(null);

    const fileClick = () =&amp;gt; {
        fileRef.current?.click();
    }

    const localFileUpload = async (file: File) =&amp;gt; {
        try {
            const formData = new FormData();
            formData.append('file', file);

            const response = await fetch(`http://localhost:8000/api/v1/file-upload`, {
                method: &quot;POST&quot;,
                body: formData
            });
            const json = await response.json();
            setImageUrl(json.url)
        } catch (e) {
            console.log(e);
        }
    }

    const handleFile = async (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
        const file = e.target.files?.[0];
        if (file) {
            await localFileUpload(file);
        }
    }

    const closeImageUrl = () =&amp;gt; {
        setImageUrl(&quot;&quot;)
    }

    const getChatMessage = async () =&amp;gt; {
        try {
            const response = await fetch(&quot;http://localhost:8000/api/v1/get-chat&quot;);
            const json = await response.json();
            setChattingArray(json);
        } catch (e) {
            console.log(e)
        } finally {
            setIsInitialLoading(false);
        }
    }

    const submitChat = async () =&amp;gt; {
        try {
            const currentMessage = chatMessage;
            const currentImageUrl = imageUrl;

            const params = new URLSearchParams({
                message: currentMessage,
                fileUrl: currentImageUrl
            });
            await fetch(`http://localhost:8000/api/v1/add-chat?${params}`, {
                method: &quot;POST&quot;,
                headers: {
                    &quot;Content-Type&quot;: &quot;application/json&quot;
                }
            })

            setChatMessage(&quot;&quot;);
            setImageUrl(&quot;&quot;);
            await getChatMessage();

            setIsLoading(true);
            const llmParams = new URLSearchParams({
                message: currentMessage,
                fileUrl: currentImageUrl,
            });
            const response2 = await fetch(`http://localhost:8000/api/v1/llm-chat?${llmParams}`, {
                method: &quot;POST&quot;
            });
            const llmResult = await response2.json();
            console.log(&quot;LLM Response:&quot;, llmResult);

            // 백엔드에서 자동으로 저장되므로 리스트 새로고침
            await getChatMessage();
            setIsLoading(false);
        } catch (e) {
            console.log(e);
            setIsLoading(false);
        }
    }

    useEffect(() =&amp;gt; {
        const updateInputHeight = () =&amp;gt; {
            if (chatBoxRef.current) {
                setInputHeight(chatBoxRef.current.clientHeight);

            }
        };

        updateInputHeight();

        const observer = new ResizeObserver(updateInputHeight);
        if (chatBoxRef.current) {
            observer.observe(chatBoxRef.current);
        }

        return () =&amp;gt; observer.disconnect();
    }, []);

    useEffect(() =&amp;gt; {
        getChatMessage()
    }, [])


    return (
        &amp;lt;div id=&quot;chatBox&quot; className=&quot;w-full h-[400px] border border-solid border-[#e7e7e9] relative&quot;&amp;gt;
            &amp;lt;div id=&quot;chatBoxLabel&quot; className=&quot;w-[80px] h-[25px] border-r border-b border-[#e7e7e9] flex justify-center items-center gap-[5px] text-[#8d8b8f]&quot;&amp;gt;
                &amp;lt;IoChatboxEllipsesSharp color={&quot;#e7e7e9&quot;} size={14} /&amp;gt;
                &amp;lt;span className=&quot;text-[12px]&quot;&amp;gt;Chatbot&amp;lt;/span&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div className=&quot;chatbox_contents w-[95%] mx-auto flex flex-col gap-[10px] overflow-y-auto&quot; style={{ height: `calc(400px - 37px - ${inputHeight}px)` }}&amp;gt;
                {isInitialLoading ? (
                    &amp;lt;div className=&quot;flex items-center justify-center h-full text-[14px] text-[#8d8b8f]&quot;&amp;gt;
                        데이터 로드 중...
                    &amp;lt;/div&amp;gt;
                ) : (
                    &amp;lt;&amp;gt;
                        {
                            chattingArray.map((chat: ChatBoxMessages) =&amp;gt; (&amp;lt;ChatMessage chatContent={chat} key={chat.chat_id} onDelete={getChatMessage} /&amp;gt;))
                        }
                        {isLoading &amp;amp;&amp;amp; &amp;lt;div className=&quot;text-[14px] text-[#8d8b8f]&quot;&amp;gt;LLM 처리 중...&amp;lt;/div&amp;gt;}
                    &amp;lt;/&amp;gt;
                )}
            &amp;lt;/div&amp;gt;
            &amp;lt;div id=&quot;chatBoxInput&quot; className=&quot;p-[10px] w-[95%] border-[#e7e7e9] border absolute bottom-[10px] left-[0] right-[0] mx-auto&quot; ref={chatBoxRef}&amp;gt;
                &amp;lt;h2 className=&quot;text-[13px] font-[300]&quot;&amp;gt;Chatbot Input&amp;lt;/h2&amp;gt;
                {
                    imageUrl &amp;amp;&amp;amp; &amp;lt;ImageUrlBox url={imageUrl} closeImageUrl={closeImageUrl} /&amp;gt;
                }
                &amp;lt;div className=&quot;real__input flex mt-[20px] items-center f&quot;&amp;gt;
                    &amp;lt;input type=&quot;file&quot; className=&quot;hidden&quot; ref={fileRef} onChange={handleFile} /&amp;gt;
                    &amp;lt;button className=&quot;w-[30px] h-[30px] flex justify-center items-center hover:bg-[#fafafa] cursor-pointer&quot; onClick={fileClick}&amp;gt;
                        &amp;lt;MdFileUpload size={20} /&amp;gt;
                    &amp;lt;/button&amp;gt;

                    &amp;lt;input type=&quot;text&quot; className=&quot;block h-[30px] outline-none flex-1 border-none box-border placeholder:text-[#bbbbc2]&quot; placeholder={&quot;Enter message or upload file&quot;} onChange={handleChatMessageChange} value={chatMessage} /&amp;gt;
                    &amp;lt;button className=&quot;w-[30px] h-[30px] flex justify-center items-center&quot; disabled={!chatMessage.trim()} onClick={submitChat}&amp;gt;
                        &amp;lt;MdOutlineFileDownloadDone size={20} color={!chatMessage.trim() ? &quot;#f5f5f5&quot; : &quot;#232323&quot;} /&amp;gt;
                    &amp;lt;/button&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;app/components/ChatMessage.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780325599689&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { IoClose } from &quot;react-icons/io5&quot;;
import { ChatBoxMessages } from &quot;./ChatBox&quot;;


export default function ChatMessage({ chatContent, onDelete }: { chatContent: ChatBoxMessages; onDelete: () =&amp;gt; void }) {
    const deleteChat = async () =&amp;gt; {
        try {
            const response = await fetch(`http://localhost:8000/api/v1/delete-chat?chat_id=${chatContent.chat_id}`, {
                method: &quot;DELETE&quot;
            })
            const json = await response.json();
            console.log(json);
            onDelete();
        } catch (e) {
            console.log(e);
        }
    }

    return (
        &amp;lt;div className={`relative flex flex-col text-[14px] border max-h-[150px] p-[15px] ${chatContent.isMe ? &quot;bg-[#fef7ec] border-[#fef7e3] self-end w-[25%]&quot; : &quot;bg-[#fafafa] border-[#e7e7e9] self-start max-w-[50%]&quot;} `}&amp;gt;
            {chatContent.fileUrl &amp;amp;&amp;amp; (
                &amp;lt;img src={chatContent.fileUrl} alt=&quot;fileImage&quot; className=&quot;w-[50px] h-[50px] object-cover mb-[10px]&quot; /&amp;gt;
            )}
            &amp;lt;span&amp;gt;{chatContent.message}&amp;lt;/span&amp;gt;
            {chatContent.isMe &amp;amp;&amp;amp; &amp;lt;button className=&quot;absolute right-[10px] top-[10px]&quot; onClick={deleteChat}&amp;gt;&amp;lt;IoClose /&amp;gt;&amp;lt;/button&amp;gt;}
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;app/components/ImageUrlBox.tsx&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780325638634&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { IoClose } from &quot;react-icons/io5&quot;;

export default function ImageUrlBox({ url, closeImageUrl }: { url: string, closeImageUrl: () =&amp;gt; void }) {
    return (
        &amp;lt;div id=&quot;imageUrlBox&quot; className=&quot;w-[55px] h-[50px] border border-[#e5e5e5] border-solid flex justify-center items-center relative&quot;&amp;gt;
            &amp;lt;img src={url} alt=&quot;fileImage&quot; className=&quot;w-[40px] h-[40px] object-cover&quot; /&amp;gt;
            &amp;lt;button className=&quot;absolute top-[0] right-[0] p-[0] cursor-pointer&quot; onClick={closeImageUrl}&amp;gt;
                &amp;lt;IoClose /&amp;gt;
            &amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드 적용 시 구현되는 UI는 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-01 오후 11.54.43.png&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;567&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bR2SAO/dJMcabRTTyY/avQWkdfAQgt964WKe6Ios0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bR2SAO/dJMcabRTTyY/avQWkdfAQgt964WKe6Ios0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bR2SAO/dJMcabRTTyY/avQWkdfAQgt964WKe6Ios0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbR2SAO%2FdJMcabRTTyY%2FavQWkdfAQgt964WKe6Ios0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1284&quot; height=&quot;567&quot; data-filename=&quot;스크린샷 2026-06-01 오후 11.54.43.png&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;567&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면단은 사람에 따라 다를 수 있으니 참고만 하고 넘어가도록 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 기능에 필요한 FastAPI 코드를 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 다음과 같이 파일을 첨부했을 때 등장하는 이미지 컴포넌트가 필요하므로 /file-upload 엔드포인트를 만들자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-01 오후 11.56.40.png&quot; data-origin-width=&quot;1177&quot; data-origin-height=&quot;215&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDcsKP/dJMcaaesmTz/hlh1tacPZuchNVQzUbwRD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDcsKP/dJMcaaesmTz/hlh1tacPZuchNVQzUbwRD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDcsKP/dJMcaaesmTz/hlh1tacPZuchNVQzUbwRD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDcsKP%2FdJMcaaesmTz%2Fhlh1tacPZuchNVQzUbwRD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1177&quot; height=&quot;215&quot; data-filename=&quot;스크린샷 2026-06-01 오후 11.56.40.png&quot; data-origin-width=&quot;1177&quot; data-origin-height=&quot;215&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 엔드포인트 라우터 부분과 main.py 부분을 나누어서 작업했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;./main.py&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780325909349&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import uvicorn

if __name__ == &quot;__main__&quot;:
    # Start the FastAPI application via uvicorn
    # reload=True makes the server reload on code changes
    uvicorn.run(&quot;app.main:app&quot;, host=&quot;localhost&quot;, port=8000, reload=True)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;./app/main.py&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780325972075&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from app.core.config import settings
from app.api.v1.router import api_router
from pathlib import Path

# Initialize FastAPI app with metadata
app = FastAPI(
    title=settings.PROJECT_NAME,
    description=&quot;FastAPI Base Template with modern structure and weather API proxy.&quot;,
    version=&quot;1.0.0&quot;,
    openapi_url=f&quot;{settings.API_V1_STR}/openapi.json&quot; if settings.DEBUG else None,
)

# Configure CORS Middleware
# Allows all origins for local development, customize as needed
app.add_middleware(
    CORSMiddleware,
    allow_origins=[&quot;*&quot;],
    allow_credentials=True,
    allow_methods=[&quot;*&quot;],
    allow_headers=[&quot;*&quot;],
)

# Include Central API Router
app.include_router(api_router, prefix=settings.API_V1_STR)

# Mount static files for uploads
upload_path = Path(&quot;uploads&quot;)
upload_path.mkdir(exist_ok=True)
app.mount(&quot;/uploads&quot;, StaticFiles(directory=&quot;uploads&quot;), name=&quot;uploads&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;./app/core/config.py&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780326032525&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file=&quot;.env&quot;, env_file_encoding=&quot;utf-8&quot;, extra=&quot;ignore&quot;
    )

    PROJECT_NAME: str = &quot;FastAPI Project&quot;
    API_V1_STR: str = &quot;/api/v1&quot;
    DEBUG: bool = True

    WEATHER_API_URL: str = (
        &quot;https://dev-agri.todayjeju.net/api/datahub/v0/weather/nearest-grid&quot;
    )


settings = Settings()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;./app/api/v1/file_upload.py&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780326077359&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from fastapi import APIRouter, File, UploadFile, HTTPException
from fastapi.staticfiles import StaticFiles
import shutil
from pathlib import Path
from datetime import datetime
import os

router = APIRouter()

UPLOAD_FOLDER = Path(os.getcwd()) / &quot;uploads&quot;
UPLOAD_FOLDER.mkdir(exist_ok=True)

ALLOWED_EXTENSIONS = {&quot;png&quot;, &quot;jpg&quot;, &quot;jpeg&quot;, &quot;gif&quot;, &quot;webp&quot;}


@router.post(&quot;/file-upload&quot;, summary=&quot;file upload&quot;)
async def fileUpload(file: UploadFile = File(...)):

    file_ext = file.filename.split(&quot;.&quot;)[-1].lower()
    if file_ext not in ALLOWED_EXTENSIONS:
        raise HTTPException(status_code=400, detail=&quot;Invalid File Type&quot;)

    timestamp = datetime.now().strftime(&quot;%Y%m%d_%H%M%S_&quot;)
    filename = timestamp + file.filename
    filepath = UPLOAD_FOLDER / filename

    with open(filepath, &quot;wb&quot;) as f:
        shutil.copyfileobj(file.file, f)

    file_url = f&quot;http://localhost:8000/uploads/{filename}&quot;

    return {&quot;url&quot;: file_url, &quot;filename&quot;: filename}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 업로드 엔드포인트 파일을 중점적으로 보자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업로드 폴더를 만들어, File 형태의 데이터를 받아 로컬 서버에 올려주는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 배포 시 따로 Superbase나 AWS_S3 같은 서버를 이용할 수도 있지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자같은 경우 아직 배포 단계까진 가지 않을 예정이므로, 로컬에서만 처리해주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제, 파일 업로딩하는 작업을 해주었으니, 채팅 보내기, 채팅 가져오기, 채팅 지우기, LLM 채팅 같은 채팅 작업을 해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 로컬에서 1:1 대화를 가정하고 할 것이므로 WebSocket은 사용하지 않겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 메시지 히스토리에 계속 저장하는 방식으로 해 줄 예정이다. (물론 서버 재시작 시에는 초기화될 것이다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;./app/api/v1/chat.py&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780326368212&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from fastapi import APIRouter
from datetime import datetime
import uuid
from transformers import pipeline
from PIL import Image
from typing import Optional
import asyncio
from concurrent.futures import ThreadPoolExecutor

router = APIRouter()

messageHistory = []

# 가벼운 모델 전역 로드
captioner = None
executor = ThreadPoolExecutor(max_workers=1)


def load_model():
    global captioner
    try:
        captioner = pipeline(&quot;image-to-text&quot;)
        print(&quot;LLM 모델 로드 완료&quot;)
    except Exception as e:
        print(f&quot;LLM 모델 로드 실패: {e}&quot;)


@router.on_event(&quot;startup&quot;)
async def startup_event():
    load_model()


@router.post(&quot;/add-chat&quot;, summary=&quot;add-chat&quot;)
async def addChat(message: str, fileUrl: str):
    chat_id = str(uuid.uuid4())
    timestamp = datetime.now().isoformat()

    chat_item = {
        &quot;chat_id&quot;: chat_id,
        &quot;message&quot;: message,
        &quot;fileUrl&quot;: fileUrl,
        &quot;timestamp&quot;: timestamp,
        &quot;isMe&quot;: True,
    }

    messageHistory.append(chat_item)

    return chat_item


@router.get(&quot;/get-chat&quot;, summary=&quot;get-chat&quot;)
async def getChat():
    return messageHistory


@router.delete(&quot;/delete-chat&quot;, summary=&quot;delete-chat&quot;)
async def deleteChat(chat_id: str):
    global messageHistory

    # 삭제할 메시지 찾기
    deleted_index = None
    for i, chat in enumerate(messageHistory):
        if chat[&quot;chat_id&quot;] == chat_id:
            deleted_index = i
            break

    if deleted_index is None:
        return {&quot;status&quot;: &quot;not found&quot;, &quot;chat_id&quot;: chat_id}

    # 내 메시지(isMe=True)인 경우, 그 다음 LLM 응답도 함께 삭제
    deleted_chat = messageHistory[deleted_index]
    if deleted_chat.get(&quot;isMe&quot;) and deleted_index + 1 &amp;lt; len(messageHistory):
        next_chat = messageHistory[deleted_index + 1]
        if not next_chat.get(&quot;isMe&quot;):  # 다음이 LLM 응답이면
            messageHistory = [chat for chat in messageHistory if chat[&quot;chat_id&quot;] not in [chat_id, next_chat[&quot;chat_id&quot;]]]
        else:
            messageHistory = [chat for chat in messageHistory if chat[&quot;chat_id&quot;] != chat_id]
    else:
        messageHistory = [chat for chat in messageHistory if chat[&quot;chat_id&quot;] != chat_id]

    return {&quot;status&quot;: &quot;deleted&quot;, &quot;chat_id&quot;: chat_id}


def analyze_image(fileUrl):
    &quot;&quot;&quot;이미지 분석 (동기 함수)&quot;&quot;&quot;
    try:
        result = captioner(fileUrl)
        return result[0].get(&quot;generated_text&quot;, &quot;&quot;) if result else &quot;이미지 분석 실패&quot;
    except Exception as e:
        return f&quot;이미지 분석 중 오류: {str(e)}&quot;


@router.post(&quot;/llm-chat&quot;, summary=&quot;llm-chat&quot;)
async def llmChat(message: Optional[str] = None, fileUrl: Optional[str] = None):
    global messageHistory, captioner

    llm_response = &quot;&quot;

    # 이미지가 있으면 분석 (비동기)
    if fileUrl and fileUrl.strip() and captioner:
        loop = asyncio.get_event_loop()
        llm_response = await loop.run_in_executor(executor, analyze_image, fileUrl)
    # 이미지가 없으면 메시지 사용
    elif message and message.strip():
        llm_response = message
    else:
        return {&quot;error&quot;: &quot;message 또는 fileUrl이 필요합니다&quot;}

    # LLM 응답을 messageHistory에 저장
    llm_chat_id = str(uuid.uuid4())
    llm_timestamp = datetime.now().isoformat()

    llm_chat_item = {
        &quot;chat_id&quot;: llm_chat_id,
        &quot;message&quot;: llm_response,
        &quot;fileUrl&quot;: &quot;&quot;,
        &quot;timestamp&quot;: llm_timestamp,
        &quot;isMe&quot;: False,
    }

    messageHistory.append(llm_chat_item)

    return llm_chat_item&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 addChat() 함수를 살펴보면, message와 fileUrl을 가져와서, 전체 messageHistory에 추가하는 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 messageHistory()를 getChat()에서 받아오는 형태이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;deleteChat()에서 자신의 메시지와, LLM 메시지를 모두 삭제하는 형태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM 메시지 로직이 조금 복잡한데, 간단하게 설명하자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;transformer의 pipeline을 이용하여 image-to-text 태스크를 통해 result를 받아 넘겨주는 형태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 이미지 파일이 넘어오는지 안 넘어오는지를 비동기 처리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 받는 데이터는 LLM이 실제 이미지를 받아 캡셔닝한 형태가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-02 오전 12.11.56.png&quot; data-origin-width=&quot;411&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAOXEH/dJMcah5JC4m/uECy1CgsYQ7IPEkOqnkE0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAOXEH/dJMcah5JC4m/uECy1CgsYQ7IPEkOqnkE0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAOXEH/dJMcah5JC4m/uECy1CgsYQ7IPEkOqnkE0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAOXEH%2FdJMcah5JC4m%2FuECy1CgsYQ7IPEkOqnkE0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;411&quot; height=&quot;72&quot; data-filename=&quot;스크린샷 2026-06-02 오전 12.11.56.png&quot; data-origin-width=&quot;411&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 다음 엔드포인트를 router.py에서 연결해줌으로, API 구성이 완료되었다.&lt;/p&gt;
&lt;pre id=&quot;code_1780326838524&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from fastapi import APIRouter
from app.api.v1.endpoints import hello, fileUpload, chat

api_router = APIRouter()

# Include endpoints routers
api_router.include_router(hello.router, tags=[&quot;default&quot;])
api_router.include_router(fileUpload.router, tags=[&quot;fileUpload&quot;])
api_router.include_router(chat.router, tags=[&quot;chat&quot;])&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 좀 더 발전해서, 다음 같이 답변 형식을 정해주는 형태로 변경해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (1).webp&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;734&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L8Z02/dJMcajh7tSw/P03yCRd1lrhlEf35GkIP7k/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L8Z02/dJMcajh7tSw/P03yCRd1lrhlEf35GkIP7k/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L8Z02/dJMcajh7tSw/P03yCRd1lrhlEf35GkIP7k/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL8Z02%2FdJMcajh7tSw%2FP03yCRd1lrhlEf35GkIP7k%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;734&quot; data-filename=&quot;image (1).webp&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;734&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FastAPI에서 LLM 챗 코드만 수정해주면 될 것 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1780373289584&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import textwrap

prompt = textwrap.dedent(
        f&quot;&quot;&quot;이미지 설명:
            {llm_response}

            사용자 질문:
            {message if message else 'N/A'}
        &quot;&quot;&quot;
    )

llm_chat_item = {
    &quot;chat_id&quot;: llm_chat_id,
    &quot;message&quot;: prompt,
    &quot;fileUrl&quot;: &quot;&quot;,
    &quot;timestamp&quot;: llm_timestamp,
    &quot;isMe&quot;: False,
}

messageHistory.append(llm_chat_item)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 prompt 형식 (띄어쓰기 등)을 맞춰야 하므로, 프론트엔드에서 조금 처리가 필요할 것 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1780373707874&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;span className=&quot;whitespace-pre-line&quot;&amp;gt;{chatContent.message}&amp;lt;/span&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 다음과 같이 구현할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-02 오후 1.15.38.png&quot; data-origin-width=&quot;2444&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNMyMS/dJMcabqTP4C/MdAd1XK6BIjE4AGvTTGbak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNMyMS/dJMcabqTP4C/MdAd1XK6BIjE4AGvTTGbak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNMyMS/dJMcabqTP4C/MdAd1XK6BIjE4AGvTTGbak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNMyMS%2FdJMcabqTP4C%2FMdAd1XK6BIjE4AGvTTGbak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2444&quot; height=&quot;638&quot; data-filename=&quot;스크린샷 2026-06-02 오후 1.15.38.png&quot; data-origin-width=&quot;2444&quot; data-origin-height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 마지막으로, 분석한 이미지에 대한 추가 질문에 대한 답변을 구현해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;693&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2Bgz7/dJMb990V89X/Wak6PautMwbLC65OgzN3Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2Bgz7/dJMb990V89X/Wak6PautMwbLC65OgzN3Kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2Bgz7/dJMb990V89X/Wak6PautMwbLC65OgzN3Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2Bgz7%2FdJMb990V89X%2FWak6PautMwbLC65OgzN3Kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;649&quot; height=&quot;693&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;693&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러기 위해서는 태스크를 하나 더 추가해야 한다. 추가 질문에 대한 답변 태스크는 &lt;b&gt;text-generation&lt;/b&gt; 인데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 위의 코드와 같이 전역 변수를 선언한 후 지역 변수처럼 사용할 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780377657408&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;global generator = None&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1780377685382&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def load_model():
    global captioner, generator
    try:
        captioner = pipeline(&quot;image-to-text&quot;)
        generator = pipeline(&quot;text-generation&quot;, model=&quot;Qwen/Qwen2.5-1.5B-Instruct&quot;)
        print(&quot;LLM 모델 로드 완료&quot;)
    except Exception as e:
        print(f&quot;LLM 모델 로드 실패: {e}&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지역 변수처럼 사용을 위해서는 함수 내에서 global 예약어를 사용해야 하며, 파이프라인으로 연결해서 변수에 저장시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수는 위에서 처럼 앱이 시작하는 동시에 로드될 것이므로, 정상적으로 처리된다면 &lt;b&gt;LLM 로드 완료&lt;/b&gt;가 콘솔에 뜰 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-02 오후 2.23.03.png&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;84&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLyxYr/dJMcaicwjE8/ZaRunbSkyy58pWGVLOQkrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLyxYr/dJMcaicwjE8/ZaRunbSkyy58pWGVLOQkrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLyxYr/dJMcaicwjE8/ZaRunbSkyy58pWGVLOQkrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLyxYr%2FdJMcaicwjE8%2FZaRunbSkyy58pWGVLOQkrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;593&quot; height=&quot;84&quot; data-filename=&quot;스크린샷 2026-06-02 오후 2.23.03.png&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;84&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해 메인 로직은 llm-chat에서 수정해줄 것인데, 이미지 설명을 가져오는 것이 공통되므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;current_caption 전역변수로 로직을 조금 수정해줄 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780377886992&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;current_caption = &quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1780377910671&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if fileUrl and fileUrl.strip() and captioner:
        loop = asyncio.get_event_loop()
        llm_response = await loop.run_in_executor(executor, analyze_image, fileUrl)
        current_caption = llm_response

        prompt = textwrap.dedent(
            f&quot;&quot;&quot;이미지 설명:
                {current_caption}

                사용자 질문:
                {message if message else 'N/A'}
            &quot;&quot;&quot;
        ).strip()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 했던 이미지를 보냈을 때 이미지를 분석하는 로직이다. 코드가 복잡하긴 하지만 간단하게 설명하자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM을 통해 캡션명 받은 것을 current_caption으로 받아주어, 그 뒤에 사용자가 이미지에 대해 추가로 질문했을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LLM에 current_caption를 한번 더 넘겨주겠다는 의미로 변수를 관리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780378051753&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;else:
            new_prompt = textwrap.dedent(
                f&quot;&quot;&quot;\
                당신은 이미지 분석 AI입니다.
                다음 이미지 설명을 참고해서 사용자의 질문에 한 문장으로 답변하세요.

                이미지 설명:
                {current_caption}

                질문:
                {message}

                답변:\
            &quot;&quot;&quot;
            )
            print(new_prompt)

            # 비동기로 generator 실행 (FastAPI 이벤트 루프가 차단되는 것 방지)
            if generator:
                loop = asyncio.get_event_loop()
                result = await loop.run_in_executor(
                    executor,
                    lambda: generator(
                        new_prompt,
                        max_new_tokens=50,
                        return_full_text=False,
                        pad_token_id=generator.tokenizer.eos_token_id,
                    ),
                )
                prompt = (
                    result[0].get(&quot;generated_text&quot;, &quot;&quot;).strip()
                    if result
                    else &quot;답변을 생성하지 못했습니다.&quot;
                )
            else:
                prompt = &quot;텍스트 생성 모델이 로드되지 않았습니다.&quot;
    else:
        return {&quot;error&quot;: &quot;message 또는 fileUrl이 필요합니다&quot;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 이미지가 아니라 텍스트를 보냈을 때 처리 방법이다. LLM 프롬프트를 통해 앞서 받았던 이미지 설명과 질문을 같이 보내고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그에 대한 대답을 같이 받아오는 형식이다. 최종적으로 JSON에는 prompt 변수가 들어가므로, 비동기 설정을 해주어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;generator 함수를 통해 답변을 받거나, 답변이 생성하지 못했다는 경고메시지를 받게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-06-02 오후 2.32.39.png&quot; data-origin-width=&quot;1227&quot; data-origin-height=&quot;569&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs4ulP/dJMcaaFyAs2/CaeCOEtZlKNqEM8C1qV3Rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs4ulP/dJMcaaFyAs2/CaeCOEtZlKNqEM8C1qV3Rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs4ulP/dJMcaaFyAs2/CaeCOEtZlKNqEM8C1qV3Rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs4ulP%2FdJMcaaFyAs2%2FCaeCOEtZlKNqEM8C1qV3Rk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1227&quot; height=&quot;569&quot; data-filename=&quot;스크린샷 2026-06-02 오후 2.32.39.png&quot; data-origin-width=&quot;1227&quot; data-origin-height=&quot;569&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>AI/LLM</category>
      <author>EunHaa</author>
      <guid isPermaLink="true">https://eun-haa.tistory.com/26</guid>
      <comments>https://eun-haa.tistory.com/26#entry26comment</comments>
      <pubDate>Tue, 2 Jun 2026 14:35:13 +0900</pubDate>
    </item>
    <item>
      <title>데이터 분석 - 판다스 (2)</title>
      <link>https://eun-haa.tistory.com/25</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;images.png&quot; data-origin-width=&quot;192&quot; data-origin-height=&quot;125&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkUYBV/dJMcafmrliK/rA6F7RBd3FzXU6oqASbkik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkUYBV/dJMcafmrliK/rA6F7RBd3FzXU6oqASbkik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkUYBV/dJMcafmrliK/rA6F7RBd3FzXU6oqASbkik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkUYBV%2FdJMcafmrliK%2FrA6F7RBd3FzXU6oqASbkik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;327&quot; height=&quot;213&quot; data-filename=&quot;images.png&quot; data-origin-width=&quot;192&quot; data-origin-height=&quot;125&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;판다스(Pandas)&lt;/b&gt;는 Python에서 데이터를 쉽고 효율적으로 분석하고 처리하기 위해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하는 대표적인 데이터 분석 라이브러리이다. 엑셀과 비슷한 형태의 &lt;b&gt;표(Tabular Data)&lt;/b&gt;를&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다룰 수 있으며, &lt;span style=&quot;background-color: #ee2323; color: #ffffff;&quot;&gt;&lt;b&gt;Series와 DataFrame&lt;/b&gt; &lt;/span&gt;이라는 강력한 자료구조를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSV, Excel, SQL 등의 다양한 데이터 파일을 불러와 정렬, 필터링, 결측치 처리, 통계 분석 등을&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간편하게 수행할 수 있으며, 대량의 데이터를 빠르게 가공할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;설치방법&lt;/blockquote&gt;
&lt;pre id=&quot;code_1779971786330&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install pandas

python -m pip install pandas&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 방법은 글로벌 설치이고, 두번째는 가상 환경 내의 설치이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 외부 라이브러리이므로, 사용하려면 import 문으로 라이브러리를 불러와야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1779971891626&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import pandas as pd&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;시리즈 (Series)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;판다스(Pandas)에서 제공하는 가장 기본적인 1차원 데이터 구조이며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;값(Value)과 인덱스(Index)&lt;/b&gt;가 함께 구성된 자료형이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;넘파이(Numpy)의 1차원 배열과 비슷하나, 각 데이터에 이름표 역할을 하는 인덱스가 추가되어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 더 체계적으로 관리할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coF1yQ/dJMcaaS0tI9/vjBxNKsVMbyZXOC79MkGT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coF1yQ/dJMcaaS0tI9/vjBxNKsVMbyZXOC79MkGT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coF1yQ/dJMcaaS0tI9/vjBxNKsVMbyZXOC79MkGT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoF1yQ%2FdJMcaaS0tI9%2FvjBxNKsVMbyZXOC79MkGT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;514&quot; height=&quot;290&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 값(Value)이 들어가면 그 값에 이름표 역할을 하는 인덱스(Index)가 부여되는 형식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시를 살펴보면서 이해해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1779972224159&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;idx = ['김사과','반하나','오렌지','이메론','배애리']
data = [67, 75, 90, 62, 98]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 시리즈를 만들려면, 판다스(Pandas) 안의 함수(메소드)를 통해 만드는데 Series 메소드를 통해 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1779972330769&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pd.Series(data,idx)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 매개변수에 데이터 값(Value)를 넣고, 두번째 매개변수에 인덱스(Index) 항목을 넣는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 만약 다음처럼 인덱스(Index)를 안 넣을 경우에는&lt;/p&gt;
&lt;pre id=&quot;code_1779972396273&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pd.Series(data)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-28 오후 9.46.53.png&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biSIFP/dJMcafz0Z8D/AAknRWdPsJ3kNguKSHYS30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biSIFP/dJMcafz0Z8D/AAknRWdPsJ3kNguKSHYS30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biSIFP/dJMcafz0Z8D/AAknRWdPsJ3kNguKSHYS30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiSIFP%2FdJMcafz0Z8D%2FAAknRWdPsJ3kNguKSHYS30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;288&quot; height=&quot;286&quot; data-filename=&quot;스크린샷 2026-05-28 오후 9.46.53.png&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 결과가 나오며, 자동으로 &lt;b&gt;번호(인덱스)&lt;/b&gt;가 추가되는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 인덱스를 넣을 경우에는 번호(인덱스)에 idx가 차례대로 배치되는 모습이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-28 오후 9.49.02.png&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CNRah/dJMcagyS4Q3/EkUSB3gHw1JK3KtR0soVG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CNRah/dJMcagyS4Q3/EkUSB3gHw1JK3KtR0soVG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CNRah/dJMcagyS4Q3/EkUSB3gHw1JK3KtR0soVG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCNRah%2FdJMcagyS4Q3%2FEkUSB3gHw1JK3KtR0soVG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;288&quot; height=&quot;286&quot; data-filename=&quot;스크린샷 2026-05-28 오후 9.49.02.png&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 시리즈를 변수에 저장해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1779972635229&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ser1 = pd.Series(data, idx)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 시리즈의 속성들을 출력해보자. 먼저 index를 출력해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1779972762814&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(ser1.index)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-28 오후 9.53.19.png&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vhIg3/dJMcaiXJyXt/k1ECGSjpTojsmDMueT6h8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vhIg3/dJMcaiXJyXt/k1ECGSjpTojsmDMueT6h8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vhIg3/dJMcaiXJyXt/k1ECGSjpTojsmDMueT6h8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvhIg3%2FdJMcaiXJyXt%2Fk1ECGSjpTojsmDMueT6h8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1172&quot; height=&quot;64&quot; data-filename=&quot;스크린샷 2026-05-28 오후 9.53.19.png&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력해보니까 Index 객체라고 정의되어 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 Series의 values를 출력해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1779972909392&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(ser1.values)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-28 오후 9.55.53.png&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FX2uk/dJMcahdwI42/N5gmSfUEF0eenqy0akuHk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FX2uk/dJMcahdwI42/N5gmSfUEF0eenqy0akuHk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FX2uk/dJMcahdwI42/N5gmSfUEF0eenqy0akuHk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFX2uk%2FdJMcahdwI42%2FN5gmSfUEF0eenqy0akuHk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;320&quot; height=&quot;48&quot; data-filename=&quot;스크린샷 2026-05-28 오후 9.55.53.png&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시리즈는 이렇게 &lt;b&gt;인덱스 객체&lt;/b&gt;와 &lt;b&gt;다차원 배열(ndarray)인 values&lt;/b&gt;로 구성되어 있다는 점을 알았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 인덱스 객체는 처음 보는 객체여서 설명이 필요할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;인덱스(Index) 객체&lt;/b&gt;는 시리즈(Series)와 데이터 프레임(DataFrame)에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 위치를 식별하고 관리하기 위한 이름표(Label) 역할을 하는 자료구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반 리스트처럼 보이지만, &lt;b&gt;단순한 데이터 저장용이 아니라 데이터를 빠르게 검색하고 정렬하며&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 데이터끼리 매칭할 수 있도록 최적화된 판다스(Pandas) 전용 객체이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;리스트와 달리, 데이터를 인덱싱으로 접근해서 수정하지 못한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;데이터 프레임(DataFrame)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;판다스(Pandas) 라이브러리에서 제공하는 중요하고 강력한 데이터 구조로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2차원 테이블 형태의 데이터를 다루는데 사용된다. 각 요소는 인덱스(Index), 열(Column), 값(Value)으로 구성되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 프레임은 행과 열로 이루어져 있고, 각 열은 다양한 데이터 타입을 가질 수 있다. 값은 ndarray 기반으로 저장된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;383&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cO5a5i/dJMcacXzlSr/LtVKkjuGyZwsaxiUOi4Okk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cO5a5i/dJMcacXzlSr/LtVKkjuGyZwsaxiUOi4Okk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cO5a5i/dJMcacXzlSr/LtVKkjuGyZwsaxiUOi4Okk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcO5a5i%2FdJMcacXzlSr%2FLtVKkjuGyZwsaxiUOi4Okk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1000&quot; height=&quot;383&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;383&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림에서 보면 알수 있듯이 시리즈(Series)가 모이면, 데이터 프레임(DataFrame)이 되는 구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 합칠 때는 시리즈(Series)의 인덱스가 같아야 합쳐진다. 합쳐진 각 시리즈의 제목이 한 열(컬럼)이 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 예제를 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1779973926888&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data = [[67, 93, 91],
        [75, 68, 96],
        [87, 81, 82],
        [62, 70, 75],
        [98, 56, 87]]
        
idx = ['김사과', '반하나', '오렌지', '이메론', '배애리']
col = ['국어', '영어', '수학']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 데이터, 인덱스, 컬럼 변수들이다. 이걸 가지고 데이터프레임을 한번 만들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 데이터 값만 먼저 넣을 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1779974015216&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pd.DataFrame(data)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-28 오후 10.14.01.png&quot; data-origin-width=&quot;224&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2xc70/dJMcab5qUvw/hNRs3LbrBd6b5xERMkERsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2xc70/dJMcab5qUvw/hNRs3LbrBd6b5xERMkERsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2xc70/dJMcab5qUvw/hNRs3LbrBd6b5xERMkERsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2xc70%2FdJMcab5qUvw%2FhNRs3LbrBd6b5xERMkERsk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;224&quot; height=&quot;302&quot; data-filename=&quot;스크린샷 2026-05-28 오후 10.14.01.png&quot; data-origin-width=&quot;224&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과는 들어가지 않은 인덱스와 컬럼은 0,1,2,.... 같은 형태로 채워지고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2차원 데이터(값)이 그에 맞게 구성되는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 인덱스까지 넣어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1779974139106&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pd.DataFrame(data, idx)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-28 오후 10.15.52.png&quot; data-origin-width=&quot;266&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E2wDO/dJMcaijbsxa/eTENfwPiaLGH8gDkCh9gxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E2wDO/dJMcaijbsxa/eTENfwPiaLGH8gDkCh9gxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E2wDO/dJMcaijbsxa/eTENfwPiaLGH8gDkCh9gxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE2wDO%2FdJMcaijbsxa%2FeTENfwPiaLGH8gDkCh9gxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;266&quot; height=&quot;328&quot; data-filename=&quot;스크린샷 2026-05-28 오후 10.15.52.png&quot; data-origin-width=&quot;266&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 왼쪽 행에 각각 인덱스가 표시되고, 넣지 않은 컬럼만 0,1,2... 같이 자동 배치될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 컬럼까지 전부 다 넣어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(참고로 매개변수의 순서를 맞추지 않으면 당연히 오류가 난다.)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1779974229354&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pd.DataFrame(data, idx, col)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-28 오후 10.18.27.png&quot; data-origin-width=&quot;340&quot; data-origin-height=&quot;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfo9h1/dJMcaaFvgdr/dMk6K4rHl7j1gcwPkNkbD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfo9h1/dJMcaaFvgdr/dMk6K4rHl7j1gcwPkNkbD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfo9h1/dJMcaaFvgdr/dMk6K4rHl7j1gcwPkNkbD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdfo9h1%2FdJMcaaFvgdr%2FdMk6K4rHl7j1gcwPkNkbD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;340&quot; height=&quot;330&quot; data-filename=&quot;스크린샷 2026-05-28 오후 10.18.27.png&quot; data-origin-width=&quot;340&quot; data-origin-height=&quot;330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 완벽한 데이터 프레임이 구성이 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 앞선 내용에서 설명을 하긴 했지만, 매개변수가 몇십개가 넘어가는 메소드나 함수에서 다 순서를 맞출 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아니다. 매개변수를 미리 셋팅해놓는 기본 매개변수로 해결할 수 있을 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 만약 진짜 넘겨야 되는 매개변수라면 어떻게 해야 할까? 전부 순서를 맞춰 주는 것이 아니라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;매개변수 = 값&lt;/b&gt; 형태로 매개변수로 넣어주면 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 위와 같은 경우도, 전부 순서를 맞출 필요 없이 매개변수 = 값 형태로 구성할 수 있다. 다음처럼 말이다.&lt;/p&gt;
&lt;pre id=&quot;code_1779974610183&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df = pd.DataFrame(index=idx,columns=col,data=data)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 매개변수의 이름을 쓰면 순서가 달라도 관계없기 때문에 매개변수의 순서를 일일이 확인할 필요가 없는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 Python의 기본에서 학습했던 내용이므로, 꼭 기억해두자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 저장한 데이터프레임 변수에서 각 속성들을 출력해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1779974756576&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(df.index)
print(df.columns)
print(df.values)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-28 오후 10.26.31.png&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGLHkH/dJMcaftdJ3M/0xYDQRRMZaKDLjlmuLk76K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGLHkH/dJMcaftdJ3M/0xYDQRRMZaKDLjlmuLk76K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGLHkH/dJMcaftdJ3M/0xYDQRRMZaKDLjlmuLk76K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGLHkH%2FdJMcaftdJ3M%2F0xYDQRRMZaKDLjlmuLk76K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1170&quot; height=&quot;326&quot; data-filename=&quot;스크린샷 2026-05-28 오후 10.26.31.png&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 여기서 columns 속성이 인덱스 객체로 인식되는 점에 주목해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 데이터가 기하급수적으로 많을 경우, 값은 다차원 데이터이므로 &lt;b&gt;컬럼을 통해서 데이터 값을 찾아야 할 수도 있다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 인덱스와 컬럼 항목은 어떤 데이터를 찾는데 속도가 빨라야 하므로 인덱스 객체를 이용하는 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 딕셔너리를 사용해서 데이터프레임을 만들어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1779975176469&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dic = {
   '국어':[67, 75, 76, 62, 98],
   '영어':[93, 68, 81, 70, 56],
   '수학':[91, 96, 82, 75, 87]
}

df = pd.DataFrame(data=dic, index=idx)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키와 값의 구조가 데이터에 들어가게 되면, 자동으로 컬럼까지 만들어주는 특징이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스는 그대로 구조대로 가고, 딕셔너리의 키가 컬럼이 되고, 딕셔너리의 값은 값이 되는 특징이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 프레임 만들기가 편한 구조로, 딕셔너리를 이용하기도 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-28 오후 10.34.47.png&quot; data-origin-width=&quot;346&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daCWWO/dJMcahExOd2/IzWKu3Ui0Z2EEi3xiF8tU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daCWWO/dJMcahExOd2/IzWKu3Ui0Z2EEi3xiF8tU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daCWWO/dJMcahExOd2/IzWKu3Ui0Z2EEi3xiF8tU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaCWWO%2FdJMcahExOd2%2FIzWKu3Ui0Z2EEi3xiF8tU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;346&quot; height=&quot;326&quot; data-filename=&quot;스크린샷 2026-05-28 오후 10.34.47.png&quot; data-origin-width=&quot;346&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;CSV 파일 읽기&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSV 파일이란 Comma-Separated Values 파일의 약자로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 단순한 텍스트 형식으로 저장하는데 사용하는 파일 형식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 CSV 파일 하나를 읽어오겠다. 동시에 이 파일을 메모리에 올리겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리에 올리면서 동시에 csv 파일을 데이터프레임으로 만들 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780032396508&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df = pd.read_csv(&quot;./광고모델_브랜드평판.csv&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 2.27.16.png&quot; data-origin-width=&quot;523&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/He9k0/dJMb99T5eTM/R9nf0kvAOi44qirQKRm1bK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/He9k0/dJMb99T5eTM/R9nf0kvAOi44qirQKRm1bK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/He9k0/dJMb99T5eTM/R9nf0kvAOi44qirQKRm1bK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHe9k0%2FdJMb99T5eTM%2FR9nf0kvAOi44qirQKRm1bK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;523&quot; height=&quot;506&quot; data-filename=&quot;스크린샷 2026-05-29 오후 2.27.16.png&quot; data-origin-width=&quot;523&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 타입을 찍어보면 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1780032642427&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(type(df))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 2.31.19.png&quot; data-origin-width=&quot;283&quot; data-origin-height=&quot;33&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lKIaJ/dJMcajh5gPh/Pp50AEBUoaFfFKEvPmD0n0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lKIaJ/dJMcajh5gPh/Pp50AEBUoaFfFKEvPmD0n0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lKIaJ/dJMcajh5gPh/Pp50AEBUoaFfFKEvPmD0n0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlKIaJ%2FdJMcajh5gPh%2FPp50AEBUoaFfFKEvPmD0n0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;283&quot; height=&quot;33&quot; data-filename=&quot;스크린샷 2026-05-29 오후 2.31.19.png&quot; data-origin-width=&quot;283&quot; data-origin-height=&quot;33&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 판다스(Pandas)의 데이터프레임을 만들고 나서 데이터 분석 위해 해야 할 것들 몇가지를 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 데이터프레임(DataFrame)의 info라는 메소드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780045244357&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.info()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메소드를 실행하면 다음과 같은 분석 결과가 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.01.46.png&quot; data-origin-width=&quot;283&quot; data-origin-height=&quot;257&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYWOF6/dJMcadIR9o0/BPYrz6kGs3Ad3sLzRIScc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYWOF6/dJMcadIR9o0/BPYrz6kGs3Ad3sLzRIScc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYWOF6/dJMcadIR9o0/BPYrz6kGs3Ad3sLzRIScc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYWOF6%2FdJMcadIR9o0%2FBPYrz6kGs3Ad3sLzRIScc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;283&quot; height=&quot;257&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.01.46.png&quot; data-origin-width=&quot;283&quot; data-origin-height=&quot;257&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요한 이 분석 결과를 볼 줄 알아야 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 위에 있는 것은 csv 파일을 불러와 저장한 변수의 데이터 타입인데, 여기서는 데이터프레임 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째에 있는 RangeIndex는 전체 데이터 갯수 및 인덱스 범위를 지칭한다. 여기서는 총 20개로 지칭된다. (0 ~ 19)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세번째에 있는 Data Columns는 말 그대로 데이터 컬럼의 개수이다. 여기서는 밑에 나오듯이 총 7개이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 컬럼 중에 중요한 것이 Non-Null Count 항목이다. 말 그대로, Null이 아닌 데이터 개수를 지칭한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 NaN이 있었던 혈액형을 제외하고 다 채워져 모두 20개로 지칭되고 있다. 혈액형만 2개의 결측값이 있어 18개이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, DType은 데이터 타입을 의미한다. 여기선 컬럼의 각 요소의 데이터 타입을 말하는데 object로 표시된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(자세히 들어가면 str으로 이해해도 된다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적으로 이 메소드의 목적은 Null 값이 몇개 있는지 한 눈에 보기 위함이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Null 값이 중요한 이유는 나중에 머신러닝/딥러닝에서 데이터 학습을 시킬 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Null 값이 있으면 위험하기 때문이다. (잘못된 연산 발생)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 df (데이터프레임 변수)의 컬럼 부분을 출력해보겠다.&lt;/p&gt;
&lt;pre id=&quot;code_1780045909534&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.columns&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 살펴봤듯이 데이터프레임이므로, 컬럼 부분은 인덱스 객체로 출력될 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.12.42.png&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;32&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzGJaQ/dJMcacXAd65/gs6wPkzlUfdtiv8wppR0f0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzGJaQ/dJMcacXAd65/gs6wPkzlUfdtiv8wppR0f0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzGJaQ/dJMcacXAd65/gs6wPkzlUfdtiv8wppR0f0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzGJaQ%2FdJMcacXAd65%2Fgs6wPkzlUfdtiv8wppR0f0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;610&quot; height=&quot;32&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.12.42.png&quot; data-origin-width=&quot;610&quot; data-origin-height=&quot;32&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 컬럼 이름을 바꿔주는 방법을 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780046049857&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;new_columns = ['name','company','gender','birthday','height','blood','brand']
df.columns = new_columns
df&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬럼의 개수와 동일한 문자열이 들어간 새로운 리스트를 만든 후에 column 속성에 한꺼번에 할당하는 방법이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.15.36.png&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzLqxs/dJMcajh5AYO/k00kJ6q4vm0B95rdaNdQgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzLqxs/dJMcajh5AYO/k00kJ6q4vm0B95rdaNdQgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzLqxs/dJMcajh5AYO/k00kJ6q4vm0B95rdaNdQgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzLqxs%2FdJMcajh5AYO%2Fk00kJ6q4vm0B95rdaNdQgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;551&quot; height=&quot;146&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.15.36.png&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;146&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 컬럼이 바뀐 문자열로 변경됨을 확인할 수 있다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 통계 정보를 보는 방법을 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780046273058&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.describe()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과는 다음과 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.18.24.png&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Eyugo/dJMcada3rhq/sqFuVobBJKdqw6vGAhNraK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Eyugo/dJMcada3rhq/sqFuVobBJKdqw6vGAhNraK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Eyugo/dJMcada3rhq/sqFuVobBJKdqw6vGAhNraK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEyugo%2FdJMcada3rhq%2FsqFuVobBJKdqw6vGAhNraK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;507&quot; height=&quot;120&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.18.24.png&quot; data-origin-width=&quot;507&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;저 메소드에서는 수치 값으로 볼 수 있는 통계 정보가 나오는데, 그러는데 사실 데이터 값 중에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수치값으로 볼 수 있는게 없다는 것을 알 것이다. (데이터 타입이 모두 str 이기 때문에)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 위는 문자열에 관련된 통계 정보만 나온 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 먼저 키(height)에 해당하는 height 값을 숫자형으로 바꿔줘야 할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래야 다음과 같은 describe 정보를 얻을 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.22.52.png&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rWJq7/dJMcagyTZml/VCpjbqBgknWriCPeItoOR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rWJq7/dJMcagyTZml/VCpjbqBgknWriCPeItoOR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rWJq7/dJMcagyTZml/VCpjbqBgknWriCPeItoOR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrWJq7%2FdJMcagyTZml%2FVCpjbqBgknWriCPeItoOR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1010&quot; height=&quot;524&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.22.52.png&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 지금은 다음과 같이 문자에 관련 정보만 나오고 있는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780046657719&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.describe(include=object)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.24.29.png&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nGBUt/dJMcaf029Ed/5GKpT4vWsoxrkr0kkKpWFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nGBUt/dJMcaf029Ed/5GKpT4vWsoxrkr0kkKpWFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nGBUt/dJMcaf029Ed/5GKpT4vWsoxrkr0kkKpWFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnGBUt%2FdJMcaf029Ed%2F5GKpT4vWsoxrkr0kkKpWFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1010&quot; height=&quot;308&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.24.29.png&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제, 숫자에 관한 정보를 보기 전에 먼저 원하는 갯수의 데이터 보는 방법부터 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 상위 5개의 row(행) 출력&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 데이터에서 위에서부터 5개의 행만 출력하는 메소드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무것도 설정하지 않을 경우 기본 값이 5이기 때문에 상위 5개만 출력된다.&lt;/p&gt;
&lt;pre id=&quot;code_1780046857890&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.head()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.28.04.png&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;149&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8tuOk/dJMcacwxzve/CuKwxvIWBjkRTAXFsm1t21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8tuOk/dJMcacwxzve/CuKwxvIWBjkRTAXFsm1t21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8tuOk/dJMcacwxzve/CuKwxvIWBjkRTAXFsm1t21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8tuOk%2FdJMcacwxzve%2FCuKwxvIWBjkRTAXFsm1t21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;149&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.28.04.png&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;149&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 상위 3개만 보고 싶다면, 다음과 같이 설정해주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1780046996137&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.head(3)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.30.14.png&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;102&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/srUa8/dJMb997C1OK/1aFo0UNhlNzRQ0YlkAify0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/srUa8/dJMb997C1OK/1aFo0UNhlNzRQ0YlkAify0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/srUa8/dJMb997C1OK/1aFo0UNhlNzRQ0YlkAify0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsrUa8%2FdJMb997C1OK%2F1aFo0UNhlNzRQ0YlkAify0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;102&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.30.14.png&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;102&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 하위 5개의 row(행) 출력&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약에 내가 끝에서부터 데이터를 가져오고 싶다면, 다음과 같이 설정하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것 역시 기본 값은 5이므로, 하위 5개의 행이 출력된다.&lt;/p&gt;
&lt;pre id=&quot;code_1780047085056&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.tail()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.32.01.png&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqgLTL/dJMcadISbrM/dqF7itHvJdL0IHHc7TMlBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqgLTL/dJMcadISbrM/dqF7itHvJdL0IHHc7TMlBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqgLTL/dJMcadISbrM/dqF7itHvJdL0IHHc7TMlBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqgLTL%2FdJMcadISbrM%2FdqF7itHvJdL0IHHc7TMlBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;502&quot; height=&quot;154&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.32.01.png&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;데이터 정렬&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;오름차순 정렬&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index 기준으로 오름차순 정렬하는 메소드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이미 처음부터 index 기준으로 정렬이 되어 있는 형태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러므로 변화는 없을 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780047211968&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.sort_index()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.34.26.png&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;503&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cggMfZ/dJMcadhUJeL/yrRK9CZEhcR4Mhkn4rHcO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cggMfZ/dJMcadhUJeL/yrRK9CZEhcR4Mhkn4rHcO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cggMfZ/dJMcadhUJeL/yrRK9CZEhcR4Mhkn4rHcO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcggMfZ%2FdJMcadhUJeL%2FyrRK9CZEhcR4Mhkn4rHcO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;538&quot; height=&quot;503&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.34.26.png&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;503&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;내림차순 정렬&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index를 기준으로 내림차순 정렬하는 메소드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780047441293&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.sort_index(ascending=False)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.38.23.png&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bukJCp/dJMcaa6zIxB/v0mRLg8MSCfgSPR2770unk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bukJCp/dJMcaa6zIxB/v0mRLg8MSCfgSPR2770unk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bukJCp/dJMcaa6zIxB/v0mRLg8MSCfgSPR2770unk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbukJCp%2FdJMcaa6zIxB%2Fv0mRLg8MSCfgSPR2770unk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;538&quot; height=&quot;506&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.38.23.png&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정렬 기준 바꾸기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키(height)를 기준으로 오름차순 정렬을 하려고 한다. 다음과 같은 설정을 추가해주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1780047640768&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.sort_values(by=&quot;height&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.41.34.png&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pPM9d/dJMcahklvhk/p4nl6BoCJSWkJrubEtUlg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pPM9d/dJMcahklvhk/p4nl6BoCJSWkJrubEtUlg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pPM9d/dJMcahklvhk/p4nl6BoCJSWkJrubEtUlg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpPM9d%2FdJMcahklvhk%2Fp4nl6BoCJSWkJrubEtUlg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;538&quot; height=&quot;506&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.41.34.png&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 정렬이 잘 된 것처럼 보이지만, 여기에서는 문제가 하나 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 비교는 사전(Dictionary) 순서로 비교하므로 182cm &amp;gt; 182.2cm 같은 이상한 결과가 발생하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음처럼 말이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.43.48.png&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;73&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGxTjE/dJMcaiDvnuq/IAGYeV7cMtQKlwSlHID9k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGxTjE/dJMcaiDvnuq/IAGYeV7cMtQKlwSlHID9k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGxTjE/dJMcaiDvnuq/IAGYeV7cMtQKlwSlHID9k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGxTjE%2FdJMcaiDvnuq%2FIAGYeV7cMtQKlwSlHID9k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;538&quot; height=&quot;73&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.43.48.png&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;73&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 원리는 182cm와 182.2cm를 비교하게 되면, 182 부분까지는 똑같고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;c와 .이랑 비교를 하게 되므로, .의 유니코드가 우선이라 오름차순 순위가 올라가는 사태가 발생하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 인덱스가 height인 시리즈를 선택해서 그 값들을 숫자로 바꾸는 과정이 지금 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 말이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780048067914&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df['height'] = df['height'].str.replace('cm','').astype(float)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 중 cm을 제거하고 숫자만 남은 문자열을 숫자형으로 캐스팅하는 코드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 height 시리즈는 다음과 같이 실수형으로 변화한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.50.55.png&quot; data-origin-width=&quot;56&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCGNEu/dJMcag6Kxvc/lIV4sDwYcfxAdJWlUCO6fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCGNEu/dJMcag6Kxvc/lIV4sDwYcfxAdJWlUCO6fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCGNEu/dJMcag6Kxvc/lIV4sDwYcfxAdJWlUCO6fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCGNEu%2FdJMcag6Kxvc%2FlIV4sDwYcfxAdJWlUCO6fk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;56&quot; height=&quot;502&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.50.55.png&quot; data-origin-width=&quot;56&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 바꾸었으면 height 컬럼은 숫자로써 사용할 수 있겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 김에 숫자처럼 생긴 문자열은 brand 시리즈도 숫자형으로 캐스팅해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780048397840&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df['brand'] = df['brand'].str.replace(&quot;,&quot;, '').astype(int)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 다음과 같은 결과가 나온다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.53.55.png&quot; data-origin-width=&quot;66&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byNKvs/dJMcada3tvI/rzBBCE6mLGQkAktCAzc79K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byNKvs/dJMcada3tvI/rzBBCE6mLGQkAktCAzc79K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byNKvs/dJMcada3tvI/rzBBCE6mLGQkAktCAzc79K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyNKvs%2FdJMcada3tvI%2FrzBBCE6mLGQkAktCAzc79K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;66&quot; height=&quot;502&quot; data-filename=&quot;스크린샷 2026-05-29 오후 6.53.55.png&quot; data-origin-width=&quot;66&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 데이터 타입이 제대로 바뀌었는지 info 메소드를 통해 정보를 확인해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780056020831&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.info()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 8.58.51.png&quot; data-origin-width=&quot;287&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciiG0w/dJMcadvlQjm/3UvyJddIGa2sfj0TKVnFu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciiG0w/dJMcadvlQjm/3UvyJddIGa2sfj0TKVnFu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciiG0w/dJMcadvlQjm/3UvyJddIGa2sfj0TKVnFu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciiG0w%2FdJMcadvlQjm%2F3UvyJddIGa2sfj0TKVnFu1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;287&quot; height=&quot;56&quot; data-filename=&quot;스크린샷 2026-05-29 오후 8.58.51.png&quot; data-origin-width=&quot;287&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 보면 알겠지만, height은 float 타입으로, brand는 int 타입으로 변경되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 통계 정보도 바뀌지 않았을까? 확인해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780056029095&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.describe()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 9.00.45.png&quot; data-origin-width=&quot;251&quot; data-origin-height=&quot;209&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckAJWE/dJMcahLm5uk/kMlx5JuGDMY9CDP8FfMXAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckAJWE/dJMcahLm5uk/kMlx5JuGDMY9CDP8FfMXAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckAJWE/dJMcahLm5uk/kMlx5JuGDMY9CDP8FfMXAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckAJWE%2FdJMcahLm5uk%2FkMlx5JuGDMY9CDP8FfMXAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;251&quot; height=&quot;209&quot; data-filename=&quot;스크린샷 2026-05-29 오후 9.00.45.png&quot; data-origin-width=&quot;251&quot; data-origin-height=&quot;209&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 숫자 위주의 통계 정보가 보임을 확인할 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 키(height)으로 다시 내림차순 정렬해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780056198724&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.sort_values(by='height', ascending=False)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-29 오후 9.03.47.png&quot; data-origin-width=&quot;529&quot; data-origin-height=&quot;505&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zhaaM/dJMcaijcskK/tAbkf4FBxr9BpvOTUZeM21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zhaaM/dJMcaijcskK/tAbkf4FBxr9BpvOTUZeM21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zhaaM/dJMcaijcskK/tAbkf4FBxr9BpvOTUZeM21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzhaaM%2FdJMcaijcskK%2FtAbkf4FBxr9BpvOTUZeM21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;529&quot; height=&quot;505&quot; data-filename=&quot;스크린샷 2026-05-29 오후 9.03.47.png&quot; data-origin-width=&quot;529&quot; data-origin-height=&quot;505&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 문제였던 182.2cm와 182cm의 비교가 해결됨을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 한 가지 해결할 문제가 생기는데&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-30 오후 10.21.17.png&quot; data-origin-width=&quot;529&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CefIC/dJMcad3dKLx/kcsQ50QsL6RI1gx98HW9zK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CefIC/dJMcad3dKLx/kcsQ50QsL6RI1gx98HW9zK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CefIC/dJMcad3dKLx/kcsQ50QsL6RI1gx98HW9zK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCefIC%2FdJMcad3dKLx%2FkcsQ50QsL6RI1gx98HW9zK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;529&quot; height=&quot;48&quot; data-filename=&quot;스크린샷 2026-05-30 오후 10.21.17.png&quot; data-origin-width=&quot;529&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 키가 똑같은 중복 데이터가 생겼을 때는 추가로 정렬 기준이 있을까라는 의문이 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 때 키가 같을 때는 브랜드 평판지수(brand)가 높은 사람이 상단으로 올라가게 하고 싶다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러기 위해서 2차 정렬 셋팅이 필요하다.&lt;/p&gt;
&lt;pre id=&quot;code_1780147534289&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 1차 정렬: 키 (내림차순) 2차 정렬: 브랜드 (내림차순)
df.sort_values(by=[&quot;height&quot;,&quot;brand&quot;], ascending=[False, False], na_position='first')&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-30 오후 10.27.50.png&quot; data-origin-width=&quot;529&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AgQ4D/dJMcabxA3ML/hE8pys2PuHJumbNn5bSx2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AgQ4D/dJMcabxA3ML/hE8pys2PuHJumbNn5bSx2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AgQ4D/dJMcabxA3ML/hE8pys2PuHJumbNn5bSx2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAgQ4D%2FdJMcabxA3ML%2FhE8pys2PuHJumbNn5bSx2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;529&quot; height=&quot;48&quot; data-filename=&quot;스크린샷 2026-05-30 오후 10.27.50.png&quot; data-origin-width=&quot;529&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 같은 키일때는 brand 크기 순서대로 내림차순됨을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 na_position 옵션은 기준 값이 NaN이 되었을 때 어디에 위치시킬지 정하는 옵션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 같은 경우, first이므로, 맨 위에 위치하는 경우이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;인덱싱&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 맨 앞 5개의 행 데이터만 먼저 뽑아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780148015412&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.head()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-30 오후 10.33.47.png&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Y9K5K/dJMcacJ6Ur0/OluJCoPGEDecoAPPEd2lW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Y9K5K/dJMcacJ6Ur0/OluJCoPGEDecoAPPEd2lW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Y9K5K/dJMcacJ6Ur0/OluJCoPGEDecoAPPEd2lW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FY9K5K%2FdJMcacJ6Ur0%2FOluJCoPGEDecoAPPEd2lW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;494&quot; height=&quot;148&quot; data-filename=&quot;스크린샷 2026-05-30 오후 10.33.47.png&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 blood에 해당하는 시리즈만 보고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 2가지 방법으로 blood 시리즈만 뽑을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780148107977&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df['blood']
df.blood&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-30 오후 10.36.23.png&quot; data-origin-width=&quot;206&quot; data-origin-height=&quot;111&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9CiL1/dJMcaaeq5IC/pcwXDP5IY8jPKN3Uf3qAmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9CiL1/dJMcaaeq5IC/pcwXDP5IY8jPKN3Uf3qAmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9CiL1/dJMcaaeq5IC/pcwXDP5IY8jPKN3Uf3qAmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9CiL1%2FdJMcaaeq5IC%2FpcwXDP5IY8jPKN3Uf3qAmK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;206&quot; height=&quot;111&quot; data-filename=&quot;스크린샷 2026-05-30 오후 10.36.23.png&quot; data-origin-width=&quot;206&quot; data-origin-height=&quot;111&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 2가지 경우를 제공하는 것일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약에 &quot;blood&quot;를 다음과 같이 변수에 저장했다고 해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780148286159&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;a = &quot;blood&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 다음과 같이 사용할 수도 있지 않을까?&lt;/p&gt;
&lt;pre id=&quot;code_1780148318951&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df[a]
df.a&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로, [] 안에 변수 여러 개를 넣을 수도 있고, for 문을 돌리거나 변수를 이용해 내가 원하는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터프레임의 시리즈를 뽑을 수가 있다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 경우는 &lt;b&gt;프로그램의 확장성&lt;/b&gt;을 위한 것이고, 두번째는 &lt;b&gt;가독성&lt;/b&gt;을 위해 제공한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로, 아까 우리가 데이터 맨 위 3개의 행을 가져오는 방법을 공부했었다.&lt;/p&gt;
&lt;pre id=&quot;code_1780148531476&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.head(3)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 똑같은 의미의 코드를 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780148581372&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df[:3]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열은 다 가져오고, 행은 0부터 2까지 3개 가져오라는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 확장된 개념은 로케이션 인덱싱(loc)에 대해 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로케이션 인덱싱은 컬럼 인덱싱인데 다음과 같이 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1780148723442&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 컬럼 인덱싱
# df.loc(행관련인덱싱, 열관련인덱싱)

df.loc[:,'name']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행은 모두 가져오라는 뜻이고, 열은 'name' 컬럼만 가져오라는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 다음과 같은 코드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780148806473&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df['name']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 굳이 df['name'] 이 있는데 왜 확장된 개념을 제공하는 것일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 예시를 한번 더 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780148899335&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; df.loc[2:5, 'name']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 2부터 5까지의 행을 가져오고, 그 중 'name' 컬럼만 가져오라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 앞서 배운 슬라이싱과 조금 달라서 뒷 값인 5를 포함한다는 점을 꼭 기억해두자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 컬럼을 여러 개 가져오려면 어떻게 해야 할까? 여러 개를 뭉칠려면 리스트로 뭉치면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 말이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780149068709&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.loc[2:5, ['name','gender','height']]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, 이번엔 행 중에 2번과 5번만 뽑아오고 싶다고 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 다음과 같이 작성할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780149167764&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.loc[[2,5], ['name','gender','height']]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;범위를 지정해주는 것과 하나 하나 인덱스를 찍는 것을 구분해야 할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 예제는 무슨 의미일까?&lt;/p&gt;
&lt;pre id=&quot;code_1780149292572&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.loc[2:5, 'name':'gender']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행은 2부터 5까지이고, 컬럼은 name 컬럼부터 gender 컬럼까지 출력해달라는 의미이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-30 오후 10.55.46.png&quot; data-origin-width=&quot;224&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pDu4T/dJMcai4v9sy/vkxP22zLZFkySLVrLPy49K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pDu4T/dJMcai4v9sy/vkxP22zLZFkySLVrLPy49K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pDu4T/dJMcai4v9sy/vkxP22zLZFkySLVrLPy49K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpDu4T%2FdJMcai4v9sy%2FvkxP22zLZFkySLVrLPy49K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;224&quot; height=&quot;118&quot; data-filename=&quot;스크린샷 2026-05-30 오후 10.55.46.png&quot; data-origin-width=&quot;224&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 더 확장된 개념인 iloc(index-location)에 대해 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 예시를 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780149521841&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.iloc[:,0]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한마디로 인덱스 숫자만으로 로케이팅을 하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 의미는 행은 모두 가져오고, 컬럼은 0번째 컬럼만 가져오라는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 예시를 살펴보면 어떨까.&lt;/p&gt;
&lt;pre id=&quot;code_1780150235753&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.iloc[:,0:3]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 모든 행을 가져오고, 컬럼은 0부터 2까지 가져오라는 것이다. (이번에는 끝 값을 포함하지 않는다)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 인덱싱에 조건식을 넣는 방식을 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780150389088&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; df['height'] &amp;gt;= 180&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.13.45.png&quot; data-origin-width=&quot;224&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T33Dn/dJMb990UBfe/o0fK6CwvVPzsQhtRSPCpG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T33Dn/dJMb990UBfe/o0fK6CwvVPzsQhtRSPCpG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T33Dn/dJMb990UBfe/o0fK6CwvVPzsQhtRSPCpG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT33Dn%2FdJMb990UBfe%2Fo0fK6CwvVPzsQhtRSPCpG0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;224&quot; height=&quot;388&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.13.45.png&quot; data-origin-width=&quot;224&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과값이 각 인덱스 별로 bool 타입으로 결과가 나온다. 이것을 통해 bool Indexing을 할 수가 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780150513749&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df[df['height'] &amp;gt;= 180]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 데이터프레임에서 True인 것만 뽑아서 나올 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.15.57.png&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4VnPf/dJMcaicuzkp/ex18VQTacD8oWVwqRIhty0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4VnPf/dJMcaicuzkp/ex18VQTacD8oWVwqRIhty0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4VnPf/dJMcaicuzkp/ex18VQTacD8oWVwqRIhty0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4VnPf%2FdJMcaicuzkp%2Fex18VQTacD8oWVwqRIhty0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;526&quot; height=&quot;268&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.15.57.png&quot; data-origin-width=&quot;526&quot; data-origin-height=&quot;268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 조금 더 확장(응용)해서 이 중 이름만 보고 싶다면 어떻게 해야할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780150645980&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df[df['height'] &amp;gt;= 180]['name']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 loc를 통해 만들면 다음과 같이 할 수도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780150728044&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.loc[df['height'] &amp;gt;= 180,'name']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;결측값&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결측값(Missing Value)은 데이터가 존재하지 않거나 비어있는 값을 의미하고, 보통 NaN 형태로 표현된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 키 / 몸무게 / 나이 같은 정보가 입력되지 않았거나 수집 과정에서 누락된 경우 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 분석과 머신러닝에서는 결측값이 매우 중요하며, 그대로 두면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평균 계산, 통계 분석, 모델 학습과정에서 오류나 성능저하가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결측값 처리는 데이터 품질을 높이고 정확한 분석 결과를 얻기 위한 매우 &lt;b&gt;중요한 데이터 전처리 과정이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결측값 처리의 방법에는 몇가지가 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째, &lt;b&gt;NaN이 들어있는 행을 과감히 지워버리는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째, &lt;b&gt;NaN 데이터 값에 평균값을 넣는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세번째, &lt;b&gt;NaN 데이터 값에 중위값을 넣는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 비어있는 값이 blood 처럼 str 형식인 경우는 어떻게 해야할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많이 나온 데이터 값을 넣는 것을 생각해볼 수 있다 (최빈값 넣기)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 날씨 데이터 중에 컬럼이 강수량이 있는 경우를 생각해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강수량의 데이터가 몇개가 빠져 있다면? 이 때는 0을 넣는 방법도 생각해볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 결측값을 지우거나, 다른 것으로 채우는 방안을 데이터를 파악해서 결정해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 결측치가 다음과 같이 있다고 해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.28.17.png&quot; data-origin-width=&quot;281&quot; data-origin-height=&quot;20&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Romlc/dJMcabYIMjO/Pn5wcAUw5oWGiduUWJmAck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Romlc/dJMcabYIMjO/Pn5wcAUw5oWGiduUWJmAck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Romlc/dJMcabYIMjO/Pn5wcAUw5oWGiduUWJmAck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRomlc%2FdJMcabYIMjO%2FPn5wcAUw5oWGiduUWJmAck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;281&quot; height=&quot;20&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.28.17.png&quot; data-origin-width=&quot;281&quot; data-origin-height=&quot;20&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제, 결측치 여부를 알아보는 메소드를 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780151350105&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.isna()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.29.31.png&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clpk18/dJMcahLnxjm/3AWzcksyfKdlY0IFLEOpp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clpk18/dJMcahLnxjm/3AWzcksyfKdlY0IFLEOpp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clpk18/dJMcahLnxjm/3AWzcksyfKdlY0IFLEOpp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fclpk18%2FdJMcahLnxjm%2F3AWzcksyfKdlY0IFLEOpp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;427&quot; height=&quot;485&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.29.31.png&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;485&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 비슷한 의미인 isnull() 메소드도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780151482202&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.isnull()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.31.43.png&quot; data-origin-width=&quot;433&quot; data-origin-height=&quot;487&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCpvzS/dJMcaf03HDu/kD2BwE7S3dwFzkWDa8wKK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCpvzS/dJMcaf03HDu/kD2BwE7S3dwFzkWDa8wKK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCpvzS/dJMcaf03HDu/kD2BwE7S3dwFzkWDa8wKK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCpvzS%2FdJMcaf03HDu%2FkD2BwE7S3dwFzkWDa8wKK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;433&quot; height=&quot;487&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.31.43.png&quot; data-origin-width=&quot;433&quot; data-origin-height=&quot;487&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 반대의 의미인 notnull()을 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780151554297&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df.notnull()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.32.52.png&quot; data-origin-width=&quot;437&quot; data-origin-height=&quot;491&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JvVrq/dJMcaicuzA6/7ap7NkTyUvnFz8pjSesLaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JvVrq/dJMcaicuzA6/7ap7NkTyUvnFz8pjSesLaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JvVrq/dJMcaicuzA6/7ap7NkTyUvnFz8pjSesLaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJvVrq%2FdJMcaicuzA6%2F7ap7NkTyUvnFz8pjSesLaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;437&quot; height=&quot;491&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.32.52.png&quot; data-origin-width=&quot;437&quot; data-origin-height=&quot;491&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이것들을 이용해 불린 인덱싱하면 우리가 원하는 결과를 얻을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780151664492&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df[df['blood'].isna()]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.34.57.png&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;76&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/95iFB/dJMcahq6uAX/QGRKhhrgFFqF3Qb6tIb9Fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/95iFB/dJMcahq6uAX/QGRKhhrgFFqF3Qb6tIb9Fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/95iFB/dJMcahq6uAX/QGRKhhrgFFqF3Qb6tIb9Fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F95iFB%2FdJMcahq6uAX%2FQGRKhhrgFFqF3Qb6tIb9Fk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;492&quot; height=&quot;76&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.34.57.png&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;76&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이번엔 테스트를 위해 height의 일부값들을 NaN으로 바꾸기 위해 다음과 같은 코드를 적용할 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780151904874&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import numpy as np

df.loc[8, 'height'] = np.nan
df.loc[19, 'height'] = np.nan&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.39.32.png&quot; data-origin-width=&quot;56&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oQsCP/dJMcabLdHYT/3UKv1DHYhK2DUki2K72qNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oQsCP/dJMcabLdHYT/3UKv1DHYhK2DUki2K72qNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oQsCP/dJMcabLdHYT/3UKv1DHYhK2DUki2K72qNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoQsCP%2FdJMcabLdHYT%2F3UKv1DHYhK2DUki2K72qNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;56&quot; height=&quot;500&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.39.32.png&quot; data-origin-width=&quot;56&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 기준으로 height이 NaN인 행을 살펴보면 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1780152025718&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df[df['height'].isna()]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.40.41.png&quot; data-origin-width=&quot;481&quot; data-origin-height=&quot;70&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKpL6u/dJMcaak9Bsu/hROkMEjWp9LOI8xs8QyZU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKpL6u/dJMcaak9Bsu/hROkMEjWp9LOI8xs8QyZU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKpL6u/dJMcaak9Bsu/hROkMEjWp9LOI8xs8QyZU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKpL6u%2FdJMcaak9Bsu%2FhROkMEjWp9LOI8xs8QyZU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;481&quot; height=&quot;70&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.40.41.png&quot; data-origin-width=&quot;481&quot; data-origin-height=&quot;70&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상황에서 원본 데이터프레임인 df의 오염을 방지하기 위해 데이터프레임을 복사해서 사용하자.&lt;/p&gt;
&lt;pre id=&quot;code_1780152127465&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df_copy = df.copy()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 df_copy에서 결측값을 채우는 처리를 해보자. 먼저 결측값을 채워주는 함수는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1780152210484&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df_copy['height'].fillna(0, inplace=True)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 결측값을 0으로 채워주라는 의미이고, inplace=True 셋팅은 실제로 메모리까지 적용되는 설정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 실제로 0으로 채우고 싶지는 않으므로 다음과 같이 확인만 할 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780152345003&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df_copy['height'].fillna(0)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서, 평균값을 한번 구해보자. 평균값 구하는 메소드는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1780152413611&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;height = df_copy['height'].mean()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 평균값을 실제로 결측치에 적용할 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780152470201&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df_copy['height'] = df_copy['height'].fillna(df_copy['height'].mean())&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.48.21.png&quot; data-origin-width=&quot;132&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TzHzx/dJMcafUlJcz/mo9T1X8Lk0tsS9hj7f6VQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TzHzx/dJMcafUlJcz/mo9T1X8Lk0tsS9hj7f6VQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TzHzx/dJMcafUlJcz/mo9T1X8Lk0tsS9hj7f6VQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTzHzx%2FdJMcafUlJcz%2Fmo9T1X8Lk0tsS9hj7f6VQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;132&quot; height=&quot;366&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.48.21.png&quot; data-origin-width=&quot;132&quot; data-origin-height=&quot;366&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 평균값인 178.527778 값이 결측치 대신 적용되었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 다시 다음과 같이 초기화 하고 중위값을 넣어볼 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780152619545&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df_copy = df.copy()

height = df_copy['height'].median()

df['height'] = df['height'].fillna(height)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.51.13.png&quot; data-origin-width=&quot;132&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QEg4l/dJMcafUlJc9/H9SweJjcN9RpPwNgdO5lok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QEg4l/dJMcafUlJc9/H9SweJjcN9RpPwNgdO5lok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QEg4l/dJMcafUlJc9/H9SweJjcN9RpPwNgdO5lok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQEg4l%2FdJMcafUlJc9%2FH9SweJjcN9RpPwNgdO5lok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;132&quot; height=&quot;368&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.51.13.png&quot; data-origin-width=&quot;132&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중위값인 180.5가 결측치 대신 잘 입력되었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 NaN에 해당하는 데이터를 지워버리는 방법을 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780152739093&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df_copy = df_copy.dropna()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 결측값이 있는 행 또는 열을 제거하는데, 결측값이 한개라도 있는 경우 삭제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;axis 값을 설정할 수 있는데, 기본값은 0으로써 행 삭제이고, 1인 경우 열을 삭제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;행, 열 추가 및 삭제&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 딕셔너리 구조를 보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780152914667&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dic = {
    'name': '김사과',
    'company': '애플',
    'gender': '여자',
    'birthday': '2000-01-01',
    'height': 160.0,
    'blood': 'A',
    'brand': 1234567
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 딕셔너리 값을 통해 새로운 행을 추가해주고 싶다. 이럴 땐 어떻게 해야할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 해보자. 맨 끝 행에 자료를 추가하는 방법이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780152994372&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df_copy.loc[len(df_copy)] = dic&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고, 데이터 프레임에서 새로운 열을 추가할 때 값을 하나로 통일시켜주고 싶다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 실행해주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1780153128893&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df_copy['nation'] = '대한민국'&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.59.04.png&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QME2B/dJMcaf7O1cJ/kfKCG0KJt7x1fdDrRzHstK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QME2B/dJMcaf7O1cJ/kfKCG0KJt7x1fdDrRzHstK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QME2B/dJMcaf7O1cJ/kfKCG0KJt7x1fdDrRzHstK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQME2B%2FdJMcaf7O1cJ%2FkfKCG0KJt7x1fdDrRzHstK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;551&quot; height=&quot;158&quot; data-filename=&quot;스크린샷 2026-05-30 오후 11.59.04.png&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 여기서 name이 김사과인 행의 nation 컬럼을 미국으로 바꾸고 싶다면 다음과 같이 또 해주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1780153227293&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df_copy.loc[df_copy['name'] === '김사과', 'nation'] = '미국'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 행을 제거하는 방법을 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780153267686&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df_copy.drop(20, axis=0)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 매개변수로 제거할 행또는 열의 인덱스와, axis 값을 통해 제거할 것이 행인지 열인지 명시해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서와 똑같이 0은 행이고 1은 열이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;통계 함수&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통계 함수는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1780153391134&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(df_copy['height'].sum()) # 합계
print(df_copy['height'].count()) # 개수, NaN은 포함하지 않음
print(df_copy['height'].mean()) # 평균
print(df_copy['height'].median()) # 중앙값
print(df_copy['height'].max()) # 최대값
print(df_copy['height'].min()) # 최소값
print(df_copy['height'].var()) # 분산
print(df_copy['height'].std()) # 표준편차&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 평균과 중앙값은 무슨 차이일지 정리해보자. 동시에 분산과 표준편차도 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;평균과 중앙값&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평균(Mean)은 데이터의 모든 값을 더한 뒤 데이터 개수로 나눈 값으로 전체 데이터의 중심적인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경향을 나타내는 대표적인 통계값이다. 반면, 중앙값은 데이터를 크기순으로 정렬했을 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 가운데 위치한 값을 의미하며, 이상치의 영향을 적게 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평균은 전체 데이터를 모두 반영하는 대표 값이고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중앙값은 데이터의 정중앙 위치를 나타내는 대표값이라고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;분산 &amp;amp; 표준편차&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산(Variance)은 데이터가 평균으로부터 얼마나 퍼져있는지 수치로 나타낸 값으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 데이터와 평균의 차이를 제곱한 뒤 평균을 내어 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 클수록 데이터의 흩어짐이 크다는 의미이며, 값이 작을수록 평균에 모여있다는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 분산은 차이를 제곱해서 계산하므로, 단위가 원래 데이터와 달라진다는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 사용하는 것이 표준편차(Standard Deviation)이며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표준편차는 분산에 제곱근을 취한 값이다. 따라서 원래 데이터와 같은 단위를 사용해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해석이 더 직관적이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqxbOP/dJMcadvmj1t/iko9IYX11qio0vNdxBPKx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqxbOP/dJMcadvmj1t/iko9IYX11qio0vNdxBPKx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqxbOP/dJMcadvmj1t/iko9IYX11qio0vNdxBPKx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqxbOP%2FdJMcadvmj1t%2Fiko9IYX11qio0vNdxBPKx1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;682&quot; height=&quot;266&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 통계 함수를 사용할 때는 다음과 같이 groupby() 함수를 맺어 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780153817265&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df_copy.groupby('blood').count()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오전 12.10.51.png&quot; data-origin-width=&quot;463&quot; data-origin-height=&quot;158&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drILcB/dJMcajh6cIx/V74dfmjJDcEYn8dedqnqRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drILcB/dJMcajh6cIx/V74dfmjJDcEYn8dedqnqRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drILcB/dJMcajh6cIx/V74dfmjJDcEYn8dedqnqRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrILcB%2FdJMcajh6cIx%2FV74dfmjJDcEYn8dedqnqRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;463&quot; height=&quot;158&quot; data-filename=&quot;스크린샷 2026-05-31 오전 12.10.51.png&quot; data-origin-width=&quot;463&quot; data-origin-height=&quot;158&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 데이터 분석할 때나 머신러닝/딥러닝 전처리에 많이 사용되는 다음 함수를 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780154019184&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df_copy['blood'].value_counts()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 함수는 열의 각 값에 대한 데이터를 반환하고, 기본은 NaN을 생략한 채로 반환한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오전 12.14.39.png&quot; data-origin-width=&quot;463&quot; data-origin-height=&quot;122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1jaCe/dJMcaayJYUS/gRgwH6KqxgEUSO7VXaE3e1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1jaCe/dJMcaayJYUS/gRgwH6KqxgEUSO7VXaE3e1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1jaCe/dJMcaayJYUS/gRgwH6KqxgEUSO7VXaE3e1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1jaCe%2FdJMcaayJYUS%2FgRgwH6KqxgEUSO7VXaE3e1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;463&quot; height=&quot;122&quot; data-filename=&quot;스크린샷 2026-05-31 오전 12.14.39.png&quot; data-origin-width=&quot;463&quot; data-origin-height=&quot;122&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;데이터 프레임 다루기&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 2개의 데이터가 있다고 해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780198358400&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df1 = pd.read_csv(&quot;./광고모델_브랜드평판.csv&quot;)
df2 = pd.read_csv(&quot;./광고모델_상세정보.csv&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 12.33.00.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;509&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bst0XX/dJMcahLnIRZ/eLegGKOw3qRlJXYVS3wRoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bst0XX/dJMcahLnIRZ/eLegGKOw3qRlJXYVS3wRoK/img.png&quot; data-alt=&quot;광고모델_브랜드평판&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bst0XX/dJMcahLnIRZ/eLegGKOw3qRlJXYVS3wRoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbst0XX%2FdJMcahLnIRZ%2FeLegGKOw3qRlJXYVS3wRoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;522&quot; height=&quot;509&quot; data-filename=&quot;스크린샷 2026-05-31 오후 12.33.00.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;509&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;광고모델_브랜드평판&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 12.33.38.png&quot; data-origin-width=&quot;237&quot; data-origin-height=&quot;509&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dtyUBM/dJMcahq6HB2/49kerHz2eG8qnDfVaOqfzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dtyUBM/dJMcahq6HB2/49kerHz2eG8qnDfVaOqfzK/img.png&quot; data-alt=&quot;광고모델_상세정보&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dtyUBM/dJMcahq6HB2/49kerHz2eG8qnDfVaOqfzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdtyUBM%2FdJMcahq6HB2%2F49kerHz2eG8qnDfVaOqfzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;237&quot; height=&quot;509&quot; data-filename=&quot;스크린샷 2026-05-31 오후 12.33.38.png&quot; data-origin-width=&quot;237&quot; data-origin-height=&quot;509&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;광고모델_상세정보&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 오염을 방지하기 위해 2개의 데이터프레임 모두 복사해주자.&lt;/p&gt;
&lt;pre id=&quot;code_1780198483704&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df1_copy = df1.copy()
df2_copy = df2.copy()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 두 개의 데이터프레임을 연결하는 함수를 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;concat은 두 개의 데이터프레임을 연결해주는 역할을 한다. 행으로 다음과 같이 연결한다.&lt;/p&gt;
&lt;pre id=&quot;code_1780198554725&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pd.concat([df1, df1_copy])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 12.37.24.png&quot; data-origin-width=&quot;519&quot; data-origin-height=&quot;745&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rWT2W/dJMcabj8tmv/esPpCIHIiYKkMcgHbQNw9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rWT2W/dJMcabj8tmv/esPpCIHIiYKkMcgHbQNw9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rWT2W/dJMcabj8tmv/esPpCIHIiYKkMcgHbQNw9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrWT2W%2FdJMcabj8tmv%2FesPpCIHIiYKkMcgHbQNw9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;519&quot; height=&quot;745&quot; data-filename=&quot;스크린샷 2026-05-31 오후 12.37.24.png&quot; data-origin-width=&quot;519&quot; data-origin-height=&quot;745&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 0번부터 19번까지가 원래 데이터인데 밑에 0번부터 19번까지가 한번 더 합쳐졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 여기서 0부터 19번까지 인덱스가 2번 출력되어 보기가 좋지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 인덱스를 리셋하는 방법이 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780198780643&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df1_concat = pd.concat([df1, df1_copy])
df1_concat.reset_index(drop=True)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 다음과 같이 인덱스가 리셋된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 12.41.14.png&quot; data-origin-width=&quot;519&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chOnBC/dJMcaipYRzK/eMsWNEPTBNzeDSqSgIUgRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chOnBC/dJMcaipYRzK/eMsWNEPTBNzeDSqSgIUgRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chOnBC/dJMcaipYRzK/eMsWNEPTBNzeDSqSgIUgRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchOnBC%2FdJMcaipYRzK%2FeMsWNEPTBNzeDSqSgIUgRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;519&quot; height=&quot;486&quot; data-filename=&quot;스크린샷 2026-05-31 오후 12.41.14.png&quot; data-origin-width=&quot;519&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스가 리셋되어 20부터 다시 시작하는 걸 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 여기서 &lt;b&gt;drop = True&lt;/b&gt; 셋팅은 새로운 인덱스 컬럼이 생기는 것을 방지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음처럼 기존 인덱스가 drop이 되는 형태인 것이다. 다음과 같이 생기는 걸 방지하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 12.42.03.png&quot; data-origin-width=&quot;166&quot; data-origin-height=&quot;706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cs9Vhp/dJMb99T6j41/aKLruiCnKoty3eavVLX3cK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cs9Vhp/dJMb99T6j41/aKLruiCnKoty3eavVLX3cK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cs9Vhp/dJMb99T6j41/aKLruiCnKoty3eavVLX3cK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcs9Vhp%2FdJMb99T6j41%2FaKLruiCnKoty3eavVLX3cK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;166&quot; height=&quot;706&quot; data-filename=&quot;스크린샷 2026-05-31 오후 12.42.03.png&quot; data-origin-width=&quot;166&quot; data-origin-height=&quot;706&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 옆으로 붙이는 (컬럼 단위로 붙이는) 것을 해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780199061160&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pd.concat([df1_copy, df2_copy], axis=1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;axis 값을 1로 주어 붙이는 기준을 컬럼으로 설정해주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 12.45.29.png&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;509&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBjAm4/dJMcaglnDPP/ygjYTkNmVW5KaHIk8TFCC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBjAm4/dJMcaglnDPP/ygjYTkNmVW5KaHIk8TFCC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBjAm4/dJMcaglnDPP/ygjYTkNmVW5KaHIk8TFCC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBjAm4%2FdJMcaglnDPP%2FygjYTkNmVW5KaHIk8TFCC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;730&quot; height=&quot;509&quot; data-filename=&quot;스크린샷 2026-05-31 오후 12.45.29.png&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;509&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 컬럼으로 합쳐지는 것이다. 그러나 결합할 때 합치는 기준이 인덱스 기준이다. (즉, 중복되는 컬럼이 나올 수 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 2개의 데이터프레임이 합쳐질 수 있었던 건 두 데이터프레임의 인덱스가 완전히 일치했기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 일부 행들을 drop 시켜서 인덱스가 완전히 일치하지 않는다면 어떻게 될까? 다음과 같이 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780199352658&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df2_copy = df2_copy.drop([1, 3, 5, 7, 9])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 12.49.50.png&quot; data-origin-width=&quot;240&quot; data-origin-height=&quot;387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brg7ax/dJMcafGO7O3/JUo6ZLGIZHdaXgWKEjfOdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brg7ax/dJMcafGO7O3/JUo6ZLGIZHdaXgWKEjfOdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brg7ax/dJMcafGO7O3/JUo6ZLGIZHdaXgWKEjfOdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbrg7ax%2FdJMcafGO7O3%2FJUo6ZLGIZHdaXgWKEjfOdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;387&quot; data-filename=&quot;스크린샷 2026-05-31 오후 12.49.50.png&quot; data-origin-width=&quot;240&quot; data-origin-height=&quot;387&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보시다시피 1, 3, 5, 7, 9번 행이 데이터프레임에서 지워졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서 다시 concat 함수를 통해 합쳐보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780199456723&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pd.concat([df1_copy, df2_copy], axis=1)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 12.51.17.png&quot; data-origin-width=&quot;747&quot; data-origin-height=&quot;511&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnMlmk/dJMcac4lEkC/oXJO42YiPnArSYHo148ER1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnMlmk/dJMcac4lEkC/oXJO42YiPnArSYHo148ER1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnMlmk/dJMcac4lEkC/oXJO42YiPnArSYHo148ER1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnMlmk%2FdJMcac4lEkC%2FoXJO42YiPnArSYHo148ER1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;747&quot; height=&quot;511&quot; data-filename=&quot;스크린샷 2026-05-31 오후 12.51.17.png&quot; data-origin-width=&quot;747&quot; data-origin-height=&quot;511&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 인덱스 번호 일부가 지워지긴 했지만, 인덱스 번호가 흐트러진게 아니므로, 같은 번호끼리는 잘 합쳐질 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(비어있는 인덱스는 보기와 같이 NaN 처리된다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 2번째 데이터프레임을 인덱스 재정렬하고 한번 확인해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780207174190&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df2_copy = df2_copy.reset_index(drop=True)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.00.00.png&quot; data-origin-width=&quot;240&quot; data-origin-height=&quot;387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZftbA/dJMcahkmh2d/S24CyuBP9LKJhmThFIXthk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZftbA/dJMcahkmh2d/S24CyuBP9LKJhmThFIXthk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZftbA/dJMcahkmh2d/S24CyuBP9LKJhmThFIXthk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZftbA%2FdJMcahkmh2d%2FS24CyuBP9LKJhmThFIXthk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;387&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.00.00.png&quot; data-origin-width=&quot;240&quot; data-origin-height=&quot;387&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0번부터 14번까지로 재정렬 되었으므로 인덱스가 흐트러졌다고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 첫번째 데이터프레임 인덱스와 두번째 데이터프레임 인덱스가 다른 상황이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에다가 또 데이터를 하나 더 추가해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780207343024&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dic = {
	'이름': '김사과',
    '연봉': 99000000,
    '주소': '서울 강남구'
}

df2_copy.loc[len(df2_copy)] = dic&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.04.21.png&quot; data-origin-width=&quot;244&quot; data-origin-height=&quot;412&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FwTp4/dJMcadWugOO/IKJUoKJ5odNCXGghD7XeLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FwTp4/dJMcadWugOO/IKJUoKJ5odNCXGghD7XeLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FwTp4/dJMcadWugOO/IKJUoKJ5odNCXGghD7XeLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFwTp4%2FdJMcadWugOO%2FIKJUoKJ5odNCXGghD7XeLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;244&quot; height=&quot;412&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.04.21.png&quot; data-origin-width=&quot;244&quot; data-origin-height=&quot;412&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 상황에서 concat을 통해 합쳐보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780207534299&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pd.concat([df1_copy, df2_copy], axis=1)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.05.58.png&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RLvxk/dJMcaiDwaYa/dsuGJQ1z9gQWuS983Tamt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RLvxk/dJMcaiDwaYa/dsuGJQ1z9gQWuS983Tamt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RLvxk/dJMcaiDwaYa/dsuGJQ1z9gQWuS983Tamt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRLvxk%2FdJMcaiDwaYa%2FdsuGJQ1z9gQWuS983Tamt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;750&quot; height=&quot;514&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.05.58.png&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;514&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 기준으로 합쳐지므로, 같은 인덱스의 이름이 서로 다르게 합쳐지는 오류가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 인덱스 번호가 다르면 열로 붙일 수 없는 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 그 문제점을 해결하는 merge 함수에 대해 알아보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780207719372&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pd.merge(df1_copy, df2_copy, on=&quot;이름&quot;, how=&quot;left&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.09.01.png&quot; data-origin-width=&quot;705&quot; data-origin-height=&quot;514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YFiBN/dJMcab5sCi1/kOM9wF3pS3ciZCTxOIMZR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YFiBN/dJMcab5sCi1/kOM9wF3pS3ciZCTxOIMZR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YFiBN/dJMcab5sCi1/kOM9wF3pS3ciZCTxOIMZR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYFiBN%2FdJMcab5sCi1%2FkOM9wF3pS3ciZCTxOIMZR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;705&quot; height=&quot;514&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.09.01.png&quot; data-origin-width=&quot;705&quot; data-origin-height=&quot;514&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 특정 고유한 키(key) 값을 기준으로 합치는데 내가 원하는 열을 보고 서로 열이 같은 것 끼리 합칠 수 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 on에 유니크 값(키값)에 해당하는 컬럼을 설정해주면 된다. 여기서는 이름 컬럼으로 설정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;how에는 병합의 기준을 넣어야 하는데, &lt;b&gt;left / right / inner / cross&lt;/b&gt; 4가지 값이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기준값을 보면 알겠지만 RDB의 JOIN 연산과 매우 유사하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;how = &quot;left&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, LEFT의 의미는 왼쪽 데이터프레임을 기준으로 먼저 합쳐달라는 의미이다. 여기서 왼쪽 데이터프레임은 df1_copy인데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;df1_copy의 데이터는 다 뿌리고, 그것에 맞춰서 df2_copy를 합쳐달라는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 가정해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 데이터의 행의 갯수가 20개이고, 오른쪽 데이터 행의 갯수가 14개라고 가정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.17.23.png&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl9mNc/dJMcacDk67a/Kr9AENjaB7uk46PcxItw1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl9mNc/dJMcacDk67a/Kr9AENjaB7uk46PcxItw1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl9mNc/dJMcacDk67a/Kr9AENjaB7uk46PcxItw1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl9mNc%2FdJMcacDk67a%2FKr9AENjaB7uk46PcxItw1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;682&quot; height=&quot;502&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.17.23.png&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 20개를 그대로 보여주고, 오른쪽 데이터 프레임을 왼쪽 프레임에 맞춰서 보여주는 형태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른쪽 프레임에 맞지 않는 컬럼의 값에 NaN이 나온 것도 Left 연산이기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.18.51.png&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bprVye/dJMcacJ7aAy/LI01ileSCepvRcrXekh8P0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bprVye/dJMcacJ7aAy/LI01ileSCepvRcrXekh8P0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bprVye/dJMcacJ7aAy/LI01ileSCepvRcrXekh8P0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbprVye%2FdJMcacJ7aAy%2FLI01ileSCepvRcrXekh8P0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;682&quot; height=&quot;502&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.18.51.png&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;how = &quot;right&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 오른쪽 기준으로 합쳐달라는 의미이다. 방금 가정한 것에서 예시를 적용하자면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.17.23.png&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg65PC/dJMcafz2FNM/kU2O48jQdGbl8QRgZZgU80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg65PC/dJMcafz2FNM/kU2O48jQdGbl8QRgZZgU80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg65PC/dJMcafz2FNM/kU2O48jQdGbl8QRgZZgU80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg65PC%2FdJMcafz2FNM%2FkU2O48jQdGbl8QRgZZgU80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;682&quot; height=&quot;502&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.17.23.png&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 오른쪽을 기준으로 합쳐져야 하므로, 왼쪽 데이터프레임 중 오른쪽 데이터프레임과 겹치는 부분만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력될 것이고, 최종적으로 14개의 행만 보여질 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.21.37.png&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bM4Wnu/dJMcahR6MOO/EBGaLnp51tqvmI4l6TIjF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bM4Wnu/dJMcahR6MOO/EBGaLnp51tqvmI4l6TIjF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bM4Wnu/dJMcahR6MOO/EBGaLnp51tqvmI4l6TIjF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbM4Wnu%2FdJMcahR6MOO%2FEBGaLnp51tqvmI4l6TIjF1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1198&quot; height=&quot;502&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.21.37.png&quot; data-origin-width=&quot;1198&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.33.01.png&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;413&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDOGt0/dJMcajvEv6g/6k69K34rkuwmCMSlOA6RTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDOGt0/dJMcajvEv6g/6k69K34rkuwmCMSlOA6RTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDOGt0/dJMcajvEv6g/6k69K34rkuwmCMSlOA6RTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDOGt0%2FdJMcajvEv6g%2F6k69K34rkuwmCMSlOA6RTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;725&quot; height=&quot;413&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.33.01.png&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;413&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 데이터프레임 중에 오른쪽 데이터프레임에 포함되어 있지 않는 데이터는 당연히 제외될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(즉, 왼쪽 데이터프레임이 오른쪽 데이터프레임 기준으로 맵핑되는 것만 포함될 것이라는 얘기이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 오른쪽 데이터프레임 기준으로 출력되는 것이므로 포함되지 않는 데이터는 NaN 포함된 채로 출력될 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.27.54.png&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;26&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6iuOI/dJMcag6LiIg/YvrAMUrVwdAgKFv29sLtNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6iuOI/dJMcag6LiIg/YvrAMUrVwdAgKFv29sLtNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6iuOI/dJMcag6LiIg/YvrAMUrVwdAgKFv29sLtNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6iuOI%2FdJMcag6LiIg%2FYvrAMUrVwdAgKFv29sLtNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;26&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.27.54.png&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;26&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단 여기서 세부적인 사항에 따라 inner 연산이냐 cross 연산이냐로 나뉘는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;inner 연산은 서로 교집합인 경우에만 출력되는 경우이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.29.31.png&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;385&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkVtyz/dJMcahEzsvN/RxsfTnN2LfcjCtlzFBA2D1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkVtyz/dJMcahEzsvN/RxsfTnN2LfcjCtlzFBA2D1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkVtyz/dJMcahEzsvN/RxsfTnN2LfcjCtlzFBA2D1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkVtyz%2FdJMcahEzsvN%2FRxsfTnN2LfcjCtlzFBA2D1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;385&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.29.31.png&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;385&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cross 연산은 왼쪽 데이터프레임의 한 사람당, 오른쪽 데이터프레임의 모든 데이터들을 다 연결해보는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;집합 연산의 카디널 프로덕트(곱 연산)과 비슷하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.32.07.png&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UZjt4/dJMcaiDwbCk/avjwZvlvmYpoB8EU3aCC00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UZjt4/dJMcaiDwbCk/avjwZvlvmYpoB8EU3aCC00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UZjt4/dJMcaiDwbCk/avjwZvlvmYpoB8EU3aCC00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUZjt4%2FdJMcaiDwbCk%2FavjwZvlvmYpoB8EU3aCC00%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;725&quot; height=&quot;294&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.32.07.png&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;날짜 타입&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 날짜 부분을 처리하기 위해 다시 데이터프레임의 info를 확인해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780209322176&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df_copy.info()&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.35.53.png&quot; data-origin-width=&quot;282&quot; data-origin-height=&quot;21&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsS7vh/dJMcajh6r3Z/UuEukbuwKUPNkp4fwBOf8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsS7vh/dJMcajh6r3Z/UuEukbuwKUPNkp4fwBOf8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsS7vh/dJMcajh6r3Z/UuEukbuwKUPNkp4fwBOf8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsS7vh%2FdJMcajh6r3Z%2FUuEukbuwKUPNkp4fwBOf8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;282&quot; height=&quot;21&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.35.53.png&quot; data-origin-width=&quot;282&quot; data-origin-height=&quot;21&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 생년월일에 해당하는 birthday 컬럼의 데이터 값의 데이터 타입은 object(str)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 날짜 타입으로 한번 바꾸어보자. 다음과 같이 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1780209455031&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df_copy['birthday'] = pd.to_datetime(df_copy['birthday'])&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.38.29.png&quot; data-origin-width=&quot;282&quot; data-origin-height=&quot;24&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXhEgS/dJMcadPGppc/irKgpgHpJAk8y9fGLor7V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXhEgS/dJMcadPGppc/irKgpgHpJAk8y9fGLor7V1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXhEgS/dJMcadPGppc/irKgpgHpJAk8y9fGLor7V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXhEgS%2FdJMcadPGppc%2FirKgpgHpJAk8y9fGLor7V1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;282&quot; height=&quot;24&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.38.29.png&quot; data-origin-width=&quot;282&quot; data-origin-height=&quot;24&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 날짜 타입으로 변경됨을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 다음과 같은 속성 접근으로 날짜만 뽑을 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780209604336&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df_copy['birthday'].dt.year # 년도
df_copy['birthday'].dt.month # 월
df_copy['birthday'].dt.day # 일
df_copy['birthday'].dt.hour # 시간
df_copy['birthday'].dt.minute # 분
df_copy['birthday'].dt.second # 초
df_copy['birthday'].dt.dayofweek # 요일
df_copy['birthday'].dt.isocalendar().week # 1년중에 몇번째 주인지&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;자주 사용하는 함수&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;apply&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터프레임이나 시리즈의 데이터를 사용자 정의 함수 혹은 내장 함수에 적용하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 값을 계산하거나 변환할 때 사용한다. 데이터를 행 또는 열 단위로 처리하는 강력한 도구이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.47.12.png&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;149&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czIpoS/dJMcacpNagT/u89hHkdFqEwIqqXJO7h6F1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czIpoS/dJMcacpNagT/u89hHkdFqEwIqqXJO7h6F1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czIpoS/dJMcacpNagT/u89hHkdFqEwIqqXJO7h6F1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczIpoS%2FdJMcacpNagT%2Fu89hHkdFqEwIqqXJO7h6F1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;490&quot; height=&quot;149&quot; data-filename=&quot;스크린샷 2026-05-31 오후 3.47.12.png&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;149&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 데이터가 있을 때 성별을 남자는 1, 여자는 0으로 변환하고 싶다고 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(머신러닝/딥러닝 때 필요한 수치화)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 다음과 같이 할 수 있을 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1780210210019&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df_copy.loc[df_copy['성별'] == '남자', '성별'] = 1
df_copy.loc[df_copy['성별'] == '여자', '성별'] = 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 함수를 통해서 바꾸는 방법을 생각해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1780210376682&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def male_or_female(x):
  if x == '남':
    return 1
  elif x == '여':
    return 0
  else:
    return None&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 다음과 같이 적용시킬 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780210461698&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df_copy['성별'].apply(male_or_female)&lt;/code&gt;&lt;/pre&gt;</description>
      <category>AI/Python</category>
      <author>EunHaa</author>
      <guid isPermaLink="true">https://eun-haa.tistory.com/25</guid>
      <comments>https://eun-haa.tistory.com/25#entry25comment</comments>
      <pubDate>Sun, 31 May 2026 00:16:05 +0900</pubDate>
    </item>
  </channel>
</rss>