<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>싱커의 익스코드</title>
    <link>https://excode.tistory.com/</link>
    <description>싱커의 익스코드

안녕하세요! 저는 XinKer입니다. OOTP 연구소에서 쌓은 경험을 바탕으로 이제 새로운 도전, '익스코드'를 시작합니다. 이 블로그는 개발자로서의 여정을 공유하는 공간입니다. </description>
    <language>ko</language>
    <pubDate>Sat, 25 Jul 2026 10:02:31 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>싱커</managingEditor>
    <image>
      <title>싱커의 익스코드</title>
      <url>https://tistory1.daumcdn.net/tistory/7120752/attach/ed40540a553e4338b37c0f28bac749a1</url>
      <link>https://excode.tistory.com</link>
    </image>
    <item>
      <title>[스나이퍼팩토리] 한컴AI 2기 - 성과발표회 회고록</title>
      <link>https://excode.tistory.com/37</link>
      <description>&lt;h1 data-end=&quot;203&quot; data-start=&quot;170&quot;&gt;문서를 이해하는 시스템을 만들며&lt;/h1&gt;
&lt;p data-end=&quot;340&quot; data-start=&quot;205&quot; data-ke-size=&quot;size16&quot;&gt;이번 성과발표회는 단순히 결과물을 보여주는 자리가 아니라, &lt;b&gt;그동안 내가 어떤 문제를 고민했고, 어떤 방식으로 해결해 왔는지를 스스로 정리하는 과정&lt;/b&gt;이었다. 프로젝트를 시작할 때와 지금을 비교해 보면, 생각보다 많이 달라져 있었다.&lt;/p&gt;
&lt;hr data-end=&quot;345&quot; data-start=&quot;342&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;380&quot; data-start=&quot;347&quot; data-ke-size=&quot;size26&quot;&gt;(1) 이번 프로젝트/성과발표회를 통해 배우고 느낀 점&lt;/h2&gt;
&lt;h3 data-end=&quot;397&quot; data-start=&quot;382&quot; data-ke-size=&quot;size23&quot;&gt;가장 크게 깨달은 점&lt;/h3&gt;
&lt;p data-end=&quot;461&quot; data-start=&quot;399&quot; data-ke-size=&quot;size16&quot;&gt;가장 크게 느낀 점은 기술을 구현하는 것과, 기술을 설명하는 것은 완전히 다른 능력이라는 것이다. 기능 자체는 어느 정도 구현되어 있었지만, 발표를 준비하면서&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;559&quot; data-start=&quot;501&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;518&quot; data-start=&quot;501&quot;&gt;왜 이 구조를 선택했는지&lt;/li&gt;
&lt;li data-end=&quot;537&quot; data-start=&quot;519&quot;&gt;기존 방식과 무엇이 다른지&lt;/li&gt;
&lt;li data-end=&quot;559&quot; data-start=&quot;538&quot;&gt;이게 실제로 어떤 가치를 주는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;608&quot; data-start=&quot;561&quot; data-ke-size=&quot;size16&quot;&gt;를 설명하려다 보니, 내가 만든 시스템을 &lt;b&gt;내 언어로 다시 이해해야만 했다&lt;/b&gt;.&lt;/p&gt;
&lt;hr data-end=&quot;682&quot; data-start=&quot;679&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;707&quot; data-start=&quot;684&quot; data-ke-size=&quot;size23&quot;&gt;발표 준비&amp;middot;협업 과정에서 느낀 변화&lt;/h3&gt;
&lt;p data-end=&quot;801&quot; data-start=&quot;709&quot; data-ke-size=&quot;size16&quot;&gt;초반에는 일단 되는 걸 만들자에 가까웠다면, 발표를 준비하면서부터는 &lt;b&gt;이게 왜 필요한가, 왜 이렇게 설계했는가&lt;/b&gt;를 항상 같이 생각하게 됐다. 특히&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;876&quot; data-start=&quot;813&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;831&quot; data-start=&quot;813&quot;&gt;이 슬라이드는 왜 필요한지&lt;/li&gt;
&lt;li data-end=&quot;850&quot; data-start=&quot;832&quot;&gt;이 설명은 너무 기술적인지&lt;/li&gt;
&lt;li data-end=&quot;876&quot; data-start=&quot;851&quot;&gt;이 부분은 시연으로 보여주는 게 맞는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;933&quot; data-start=&quot;878&quot; data-ke-size=&quot;size16&quot;&gt;를 계속 고민해가며, &lt;b&gt;기술 중심 사고에서 사용자&amp;middot;청중 중심 사고로 전환되는 경험&lt;/b&gt;을 했다.&lt;/p&gt;
&lt;hr data-end=&quot;938&quot; data-start=&quot;935&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;966&quot; data-start=&quot;940&quot; data-ke-size=&quot;size23&quot;&gt;이번 과정을 통해 이전의 나와 달라진 점&lt;/h3&gt;
&lt;p data-end=&quot;976&quot; data-start=&quot;968&quot; data-ke-size=&quot;size16&quot;&gt;이전의 나는&lt;/p&gt;
&lt;blockquote data-end=&quot;997&quot; data-start=&quot;977&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;997&quot; data-start=&quot;979&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;이 정도면 설명 없이도 알겠지&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1019&quot; data-start=&quot;999&quot; data-ke-size=&quot;size16&quot;&gt;라고 생각하는 편이었다면, 지금은&lt;/p&gt;
&lt;blockquote data-end=&quot;1050&quot; data-start=&quot;1021&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1050&quot; data-start=&quot;1023&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;설명할 수 없다면, 아직 정리가 안 된 상태다&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1063&quot; data-start=&quot;1052&quot; data-ke-size=&quot;size16&quot;&gt;라고 생각하게 됐다.&lt;/p&gt;
&lt;hr data-end=&quot;1068&quot; data-start=&quot;1065&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1089&quot; data-start=&quot;1070&quot; data-ke-size=&quot;size26&quot;&gt;(2) 내가 맡은 역할과 성과&lt;/h2&gt;
&lt;h3 data-end=&quot;1100&quot; data-start=&quot;1091&quot; data-ke-size=&quot;size23&quot;&gt;맡은 역할&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1200&quot; data-start=&quot;1117&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1137&quot; data-start=&quot;1117&quot;&gt;&lt;b&gt;백엔드 (Fast API 및 MySQL) 전반 설계 및 구현&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1156&quot; data-start=&quot;1138&quot;&gt;인프라와 문서 처리 파이프라인(RAG) 통합&lt;/li&gt;
&lt;li data-end=&quot;1179&quot; data-start=&quot;1157&quot;&gt;비동기 처리 구조(Celery 기반)&lt;/li&gt;
&lt;li data-end=&quot;1200&quot; data-start=&quot;1180&quot;&gt;발표 및 시연&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1216&quot; data-start=&quot;1213&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1233&quot; data-start=&quot;1218&quot; data-ke-size=&quot;size23&quot;&gt;구현한 기능 및 기여&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1407&quot; data-start=&quot;1235&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1344&quot; data-start=&quot;1302&quot;&gt;서비스 기획&lt;/li&gt;
&lt;li data-end=&quot;1344&quot; data-start=&quot;1302&quot;&gt;서비스를 위한 각종 API&lt;/li&gt;
&lt;li data-end=&quot;1344&quot; data-start=&quot;1302&quot;&gt;임베딩 &amp;rarr; 검색 &amp;rarr; 리랭커 &amp;rarr; LLM 응답으로 이어지는 RAG 파이프라인 통합&lt;/li&gt;
&lt;li data-end=&quot;1383&quot; data-start=&quot;1345&quot;&gt;API 서버와 Worker를 분리한 Docker 기반 비동기 구조&lt;/li&gt;
&lt;li data-end=&quot;1407&quot; data-start=&quot;1384&quot;&gt;ACL 기반 문서 접근 제어 흐름 설계&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1482&quot; data-start=&quot;1479&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1505&quot; data-start=&quot;1484&quot; data-ke-size=&quot;size23&quot;&gt;해결한 문제 또는 책임졌던 과제&lt;/h3&gt;
&lt;p data-end=&quot;1521&quot; data-start=&quot;1507&quot; data-ke-size=&quot;size16&quot;&gt;가장 신경 썼던 부분은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1622&quot; data-start=&quot;1522&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1592&quot; data-start=&quot;1561&quot;&gt;문서 승인 &amp;rarr; 처리 &amp;rarr; 완료로 이어지는 상태 관리&lt;/li&gt;
&lt;li data-end=&quot;1622&quot; data-start=&quot;1593&quot;&gt;시연 중 실패 가능성을 고려한 영상/흐름 설계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1627&quot; data-start=&quot;1624&quot; data-ke-size=&quot;size16&quot;&gt;였다. 발표 당일 안정적으로 돌아가야 한다는 관점에서, 기술뿐 아니라 &lt;b&gt;운영 시나리오까지 함께 책임졌던 경험&lt;/b&gt;이었다.&lt;/p&gt;
&lt;hr data-end=&quot;1700&quot; data-start=&quot;1697&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1727&quot; data-start=&quot;1702&quot; data-ke-size=&quot;size26&quot;&gt;(3) 아쉬운 점 및 개선하고 싶은 부분&lt;/h2&gt;
&lt;h3 data-end=&quot;1745&quot; data-start=&quot;1729&quot; data-ke-size=&quot;size23&quot;&gt;기술적으로 아쉬웠던 점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1828&quot; data-start=&quot;1747&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1784&quot; data-start=&quot;1747&quot;&gt;관리자 자동 업로드 파이프라인이 워커로 완전히 분리되지 못한 점&lt;/li&gt;
&lt;li data-end=&quot;1828&quot; data-start=&quot;1785&quot;&gt;표 데이터를 HTML로 리턴하는 OWPML 기능을 백엔드에 온전히 통합하지 못한 점.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1890&quot; data-start=&quot;1830&quot; data-ke-size=&quot;size16&quot;&gt;핵심 기능은 구현했지만, &lt;b&gt;사용자 경험 측면에서 한 단계 더 다듬지 못한 부분이 아쉬움&lt;/b&gt;으로 남았다.&lt;/p&gt;
&lt;hr data-end=&quot;1895&quot; data-start=&quot;1892&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1920&quot; data-start=&quot;1897&quot; data-ke-size=&quot;size23&quot;&gt;발표에서 충분히 전달하지 못한 부분&lt;/h3&gt;
&lt;p data-end=&quot;1946&quot; data-start=&quot;1922&quot; data-ke-size=&quot;size16&quot;&gt;기술적으로 준비했던 내용이 많았던 만큼,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1984&quot; data-start=&quot;1947&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1954&quot; data-start=&quot;1947&quot;&gt;모델 비교 및 선정 이유&lt;/li&gt;
&lt;li data-end=&quot;1968&quot; data-start=&quot;1955&quot;&gt;트러블 슈팅&lt;/li&gt;
&lt;li data-end=&quot;1984&quot; data-start=&quot;1969&quot;&gt;성능평가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2004&quot; data-start=&quot;1986&quot; data-ke-size=&quot;size16&quot;&gt;등은 시간 관계상 생략해야 했다. 발표 분량 컨트롤은 괜찮았지만, 시연을 잘 못한 부분이 큰 아쉬움이었다.&lt;/p&gt;
&lt;hr data-end=&quot;2052&quot; data-start=&quot;2049&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2086&quot; data-start=&quot;2054&quot; data-ke-size=&quot;size26&quot;&gt;(4) 정규 교육 과정 중 나에게 도움이 되었던 파트&lt;/h2&gt;
&lt;p data-end=&quot;219&quot; data-start=&quot;159&quot; data-ke-size=&quot;size16&quot;&gt;정규 교육 과정 중 &lt;b&gt;가장 실질적으로 도움이 되었던 파트는 AI 교육 과정&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-end=&quot;339&quot; data-start=&quot;221&quot; data-ke-size=&quot;size16&quot;&gt;특히 임베딩과 벡터 검색을 단순히 &amp;ldquo;모델을 호출해 결과를 받는 과정&amp;rdquo;이 아니라, &lt;b&gt;데이터를 어떤 단위로 표현하고, 그 표현을 어떤 기준으로 검색하고 해석할 것인가의 문제&lt;/b&gt;로 다룬 점이 인상 깊었다. 이 관점 덕분에 Vector DB를 단순한 저장소로 사용하는 대신,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;468&quot; data-start=&quot;383&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;408&quot; data-start=&quot;383&quot;&gt;하나의 벡터가 &lt;b&gt;무엇을 대표하는지&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;468&quot; data-start=&quot;409&quot;&gt;검색 결과가 &lt;b&gt;왜 이 순서로 나오는지&lt;/b&gt;를&lt;br /&gt;설계 단계에서부터 설명 가능하도록 구조를 잡고자 했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;626&quot; data-start=&quot;470&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에서는 문서 전체나 임의 길이의 텍스트 조각이 아니라, &lt;b&gt;장&amp;middot;조&amp;middot;항&amp;middot;호 단위의 의미 블록을 하나의 벡터 단위로 정의&lt;/b&gt;했고, 이를 통해 벡터 간 거리가 단순 문장 유사도가 아니라 &lt;b&gt;같은 규정 맥락 안에서의 의미적 근접성&lt;/b&gt;을 반영하도록 설계했다.&lt;/p&gt;
&lt;p data-end=&quot;757&quot; data-start=&quot;628&quot; data-ke-size=&quot;size16&quot;&gt;또한 검색 과정 역시 단일 점수에 의존하지 않고, ACL 및 메타데이터 기반 구조 필터링으로 검색 대상 문서를 먼저 제한한 뒤,&lt;br /&gt;벡터 검색으로 후보를 수집하고 구조 정보와 함께 리랭킹하는 단계적 흐름으로 구성했다.&lt;/p&gt;
&lt;p data-end=&quot;757&quot; data-start=&quot;628&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 함으로써 &amp;ldquo;왜 어떤 문서는 검색되지 않는지&amp;rdquo;, &amp;ldquo;왜 이 조항이 다른 결과보다 위에 노출되는지&amp;rdquo;를 &lt;b&gt;검색 파이프라인 전체 관점에서 설명할 수 있게 되었다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;872&quot; data-start=&quot;759&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;514&quot; data-start=&quot;414&quot; data-ke-size=&quot;size16&quot;&gt;반면, 이번 프로젝트에서 일부 요구되었던 &lt;b&gt;클라우드 인프라 관련 작업&lt;/b&gt;은 프로젝트의 핵심 목표와는 다소 거리가 있었고,&lt;br /&gt;사실상 해야 해서 한 작업에 가까웠다. 그 과정에서 시간과 에너지가 분산되면서 문서 처리 엔진이나 검색 품질처럼 내가 더 집중하고 싶었던 영역의 완성도를 충분히 끌어올리지 못한 점은 아쉬움으로 남는다.&lt;/p&gt;
&lt;p data-end=&quot;721&quot; data-start=&quot;616&quot; data-ke-size=&quot;size16&quot;&gt;이번 경험을 통해 모든 기술을 억지로 끼워 넣기보다는, &lt;b&gt;프로젝트의 성격에 맞는 기술을 선택하고 집중하는 것이 결과물의 완성도에 훨씬 중요하다는 점&lt;/b&gt;을 분명히 느끼게 되었다.&lt;/p&gt;
&lt;p data-end=&quot;2307&quot; data-start=&quot;2259&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-end=&quot;2312&quot; data-start=&quot;2309&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2340&quot; data-start=&quot;2314&quot; data-ke-size=&quot;size26&quot;&gt;(5) 향후 목표 및 포트폴리오 개선 방향&lt;/h2&gt;
&lt;h3 data-end=&quot;2356&quot; data-start=&quot;2342&quot; data-ke-size=&quot;size23&quot;&gt;강화하고 싶은 기술&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2415&quot; data-start=&quot;2358&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2373&quot; data-start=&quot;2358&quot;&gt;문서 기반 RAG 고도화&lt;/li&gt;
&lt;li data-end=&quot;2415&quot; data-start=&quot;2399&quot;&gt;멀티모달(표&amp;middot;이미지) 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;2420&quot; data-start=&quot;2417&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;2437&quot; data-start=&quot;2422&quot; data-ke-size=&quot;size23&quot;&gt;포트폴리오 개선 방향&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2535&quot; data-start=&quot;2439&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2493&quot; data-start=&quot;2439&quot;&gt;이번 프로젝트를 단순 결과물이 아니라 설계 의도가 드러나는 포트폴리오로 정리&lt;/li&gt;
&lt;li data-end=&quot;2535&quot; data-start=&quot;2494&quot;&gt;코드뿐 아니라 왜 이렇게 만들었는지를 설명할 수 있는 문서 보강&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;2540&quot; data-start=&quot;2537&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;2560&quot; data-start=&quot;2542&quot; data-ke-size=&quot;size23&quot;&gt;취업 준비에서의 다음 단계&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2684&quot; data-start=&quot;2562&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2630&quot; data-start=&quot;2562&quot;&gt;&amp;ldquo;이 기술을 쓸 줄 안다&amp;rdquo;가 아니라 &lt;b&gt;&amp;ldquo;이 상황에서 이 구조를 선택했다&amp;rdquo;를 말할 수 있는 개발자&lt;/b&gt;가 되는 것&lt;/li&gt;
&lt;li data-end=&quot;2684&quot; data-start=&quot;2631&quot;&gt;면접에서 이 프로젝트 하나로도 충분히 대화가 이어질 수 있도록 준비하는 것이 목표다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;2689&quot; data-start=&quot;2686&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;750&quot; data-start=&quot;728&quot; data-ke-size=&quot;size26&quot;&gt;(6) 팀원들과의 협업에서 느낀 점&lt;/h2&gt;
&lt;p data-end=&quot;790&quot; data-start=&quot;752&quot; data-ke-size=&quot;size16&quot;&gt;이번 팀 프로젝트는 항상 이상적인 협업만 있었던 경험은 아니었다. 모든 팀원이 처음부터 끝까지 같은 온도로 몰입했다고 말하기는 어렵고, 중간중간 역할 분담이나 책임감의 차이로 부담이 한쪽으로 쏠리는 순간도 있었다.&lt;/p&gt;
&lt;p data-end=&quot;967&quot; data-start=&quot;882&quot; data-ke-size=&quot;size16&quot;&gt;그럼에도 불구하고 이 경험이 의미 있었던 이유는, 그 상황 속에서 &lt;b&gt;&amp;ldquo;어디까지 내가 책임져야 하는지&amp;rdquo;를 스스로 정리하게 되었기 때문&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;990&quot; data-start=&quot;969&quot; data-ke-size=&quot;size16&quot;&gt;누군가 대신 해주길 기다리기보다는,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1047&quot; data-start=&quot;991&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1020&quot; data-start=&quot;991&quot;&gt;이건 지금 내가 정리하지 않으면 안 되는 부분인지&lt;/li&gt;
&lt;li data-end=&quot;1047&quot; data-start=&quot;1021&quot;&gt;이건 굳이 끌고 갈 필요가 없는 영역인지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1097&quot; data-start=&quot;1049&quot; data-ke-size=&quot;size16&quot;&gt;를 구분하면서 기술적인 판단뿐 아니라 협업에 대한 기준도 함께 세울 수 있었다.&lt;/p&gt;
&lt;p data-end=&quot;1192&quot; data-start=&quot;1099&quot; data-ke-size=&quot;size16&quot;&gt;또한 팀 내에서 의견이 다를 때 조용히 넘기기보다는 최소한의 근거를 가지고 설명하려 노력했던 경험은, 이후 어떤 협업 환경에서도 도움이 될 것이라 생각한다.&lt;/p&gt;
&lt;p data-end=&quot;1322&quot; data-start=&quot;1194&quot; data-ke-size=&quot;size16&quot;&gt;이번 프로젝트를 통해 &amp;ldquo;모든 팀원이 항상 같은 방향을 보지는 않는다&amp;rdquo;는 현실을 인정하게 되었고, 그 안에서 &lt;b&gt;내가 통제할 수 있는 부분에 집중하는 태도&lt;/b&gt;가 결과적으로 나 자신을 지키는 방법이라는 것도 배우게 되었다.&lt;/p&gt;
&lt;hr data-end=&quot;2917&quot; data-start=&quot;2914&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-end=&quot;2984&quot; data-start=&quot;2919&quot; data-ke-size=&quot;size16&quot;&gt;이번 성과발표회는 &amp;ldquo;잘 끝냈다&amp;rdquo;기보다는 다음 단계로 가기 위한 기준을 하나 세웠다는 느낌에 가깝다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;3043&quot; data-start=&quot;2986&quot; data-ke-size=&quot;size16&quot;&gt;이 경험을 발판 삼아, 앞으로는 더 명확한 문제의식과 설명 가능한 설계를 하는 개발자가 되고 싶다.&lt;/p&gt;</description>
      <category>한컴AI 2기</category>
      <category>ai개발자</category>
      <category>ai개발자교육</category>
      <category>ai전문가양성</category>
      <category>개발자교육</category>
      <category>개발자취업</category>
      <category>부트캠프</category>
      <category>스나이퍼팩토리</category>
      <category>한국생산성본부</category>
      <category>한글과컴퓨터</category>
      <category>한컴ai아카데미2기</category>
      <author>싱커</author>
      <guid isPermaLink="true">https://excode.tistory.com/37</guid>
      <comments>https://excode.tistory.com/37#entry37comment</comments>
      <pubDate>Thu, 18 Dec 2025 13:06:54 +0900</pubDate>
    </item>
    <item>
      <title>[스나이퍼팩토리] 한컴AI 2기 - BACKEND 통합 테스트 및 오류 수정 리포트</title>
      <link>https://excode.tistory.com/36</link>
      <description>&lt;h3 data-end=&quot;319&quot; data-start=&quot;308&quot; data-ke-size=&quot;size23&quot;&gt;제출 목적&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;433&quot; data-start=&quot;320&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;360&quot; data-start=&quot;320&quot;&gt;전체 Backend API에 대한 &lt;b&gt;통합 테스트 수행 결과 정리&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;393&quot; data-start=&quot;361&quot;&gt;테스트 과정에서 발견된 &lt;b&gt;오류 및 수정 내역 기록&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;433&quot; data-start=&quot;394&quot;&gt;Docker 기반 배포 환경에서 &lt;b&gt;재현 가능한 실행 상태 검증&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;438&quot; data-start=&quot;435&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;452&quot; data-start=&quot;440&quot; data-ke-size=&quot;size26&quot;&gt;1. 테스트 개요&lt;/h2&gt;
&lt;p data-end=&quot;532&quot; data-start=&quot;454&quot; data-ke-size=&quot;size16&quot;&gt;본 테스트는 5차 과제 요구사항에 따라&lt;br /&gt;&lt;b&gt;Swagger Docs 기반 API 테스트 및 실제 Docker 배포 환경&lt;/b&gt;에서 수행하였다.&lt;/p&gt;
&lt;h3 data-end=&quot;544&quot; data-start=&quot;534&quot; data-ke-size=&quot;size23&quot;&gt;테스트 범위&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;642&quot; data-start=&quot;545&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;599&quot; data-start=&quot;545&quot;&gt;문서 업로드 &amp;rarr; 파싱/청킹 &amp;rarr; 임베딩 &amp;rarr; Vector Store 저장 &amp;rarr; 검색 &amp;rarr; RAG 응답&lt;/li&gt;
&lt;li data-end=&quot;622&quot; data-start=&quot;600&quot;&gt;비동기 작업(Celery) 연동 여부&lt;/li&gt;
&lt;li data-end=&quot;642&quot; data-start=&quot;623&quot;&gt;스트리밍 응답 API 동작 여부&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;654&quot; data-start=&quot;644&quot; data-ke-size=&quot;size23&quot;&gt;테스트 환경&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;449&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDQVlk/dJMcacn6DWy/DkMdCckPI2XnESVFIvaEtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDQVlk/dJMcacn6DWy/DkMdCckPI2XnESVFIvaEtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDQVlk/dJMcacn6DWy/DkMdCckPI2XnESVFIvaEtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDQVlk%2FdJMcacn6DWy%2FDkMdCckPI2XnESVFIvaEtK%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;449&quot; height=&quot;425&quot; data-origin-width=&quot;449&quot; data-origin-height=&quot;425&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;785&quot; data-start=&quot;655&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;685&quot; data-start=&quot;655&quot;&gt;API Server: FastAPI (Docker)&lt;/li&gt;
&lt;li data-end=&quot;711&quot; data-start=&quot;686&quot;&gt;Worker: Celery (Docker)&lt;/li&gt;
&lt;li data-end=&quot;723&quot; data-start=&quot;712&quot;&gt;DB: MySQL&lt;/li&gt;
&lt;li data-end=&quot;761&quot; data-start=&quot;724&quot;&gt;Vector Store: ChromaDB (Persistent)&lt;/li&gt;
&lt;li data-end=&quot;785&quot; data-start=&quot;762&quot;&gt;배포 방식: docker-compose&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;790&quot; data-start=&quot;787&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;815&quot; data-start=&quot;792&quot; data-ke-size=&quot;size26&quot;&gt;2. 엔드투엔드 통합 테스트 시나리오&lt;/h2&gt;
&lt;h3 data-end=&quot;844&quot; data-start=&quot;817&quot; data-ke-size=&quot;size23&quot;&gt;2.1 문서 업로드 및 비동기 처리 테스트&lt;/h3&gt;
&lt;p data-end=&quot;856&quot; data-start=&quot;846&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관련 API&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;915&quot; data-start=&quot;857&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;883&quot; data-start=&quot;857&quot;&gt;POST /documents/upload&lt;/li&gt;
&lt;li data-end=&quot;915&quot; data-start=&quot;884&quot;&gt;POST /requests/{id}/approve&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;297&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/co8oYx/dJMcaaqh4h5/19GWKZkM5Xov9GXR15uza0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/co8oYx/dJMcaaqh4h5/19GWKZkM5Xov9GXR15uza0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/co8oYx/dJMcaaqh4h5/19GWKZkM5Xov9GXR15uza0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fco8oYx%2FdJMcaaqh4h5%2F19GWKZkM5Xov9GXR15uza0%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;760&quot; height=&quot;297&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;297&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;927&quot; data-start=&quot;917&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 항목&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;999&quot; data-start=&quot;928&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;947&quot; data-start=&quot;928&quot;&gt;파일 업로드 시 DB 저장 여부&lt;/li&gt;
&lt;li data-end=&quot;977&quot; data-start=&quot;948&quot;&gt;승인 시 Celery Worker 작업 실행 여부&lt;/li&gt;
&lt;li data-end=&quot;999&quot; data-start=&quot;978&quot;&gt;작업 완료 후 문서 상태 변경 여부&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOrvrX/dJMb995ZDNL/B5dLAFCpZITSrkg0aA7sk1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOrvrX/dJMb995ZDNL/B5dLAFCpZITSrkg0aA7sk1/img.webp&quot; data-origin-width=&quot;440&quot; data-origin-height=&quot;317&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.2953%; margin-right: 10px;&quot; data-widthpercent=&quot;49.88&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOrvrX/dJMb995ZDNL/B5dLAFCpZITSrkg0aA7sk1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOrvrX%2FdJMb995ZDNL%2FB5dLAFCpZITSrkg0aA7sk1%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;440&quot; height=&quot;317&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OfnYf/dJMcabW16SV/Sq9Pg5d4yu7q6QJxz6OJe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OfnYf/dJMcabW16SV/Sq9Pg5d4yu7q6QJxz6OJe0/img.png&quot; data-origin-width=&quot;498&quot; data-origin-height=&quot;357&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.5419%;&quot; data-widthpercent=&quot;50.12&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OfnYf/dJMcabW16SV/Sq9Pg5d4yu7q6QJxz6OJe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOfnYf%2FdJMcabW16SV%2FSq9Pg5d4yu7q6QJxz6OJe0%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;498&quot; height=&quot;357&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;요청 승인과 완료&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1007&quot; data-start=&quot;1001&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1070&quot; data-start=&quot;1008&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1022&quot; data-start=&quot;1008&quot;&gt;업로드 요청 정상 처리&lt;/li&gt;
&lt;li data-end=&quot;1044&quot; data-start=&quot;1023&quot;&gt;승인 후 비동기 작업 큐 정상 전달&lt;/li&gt;
&lt;li data-end=&quot;1070&quot; data-start=&quot;1045&quot;&gt;파싱/임베딩 완료 후 문서 상태 정상 갱신&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1151&quot; data-origin-height=&quot;989&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blT7oT/dJMcag47ho2/HQexwSNBXwrcciJdYmual0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blT7oT/dJMcag47ho2/HQexwSNBXwrcciJdYmual0/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blT7oT/dJMcag47ho2/HQexwSNBXwrcciJdYmual0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblT7oT%2FdJMcag47ho2%2FHQexwSNBXwrcciJdYmual0%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;1151&quot; height=&quot;989&quot; data-origin-width=&quot;1151&quot; data-origin-height=&quot;989&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-end=&quot;1205&quot; data-start=&quot;1202&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1236&quot; data-start=&quot;1207&quot; data-ke-size=&quot;size23&quot;&gt;2.2 Vector Search API 테스트&lt;/h3&gt;
&lt;p data-end=&quot;1248&quot; data-start=&quot;1238&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관련 API&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1279&quot; data-start=&quot;1249&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1279&quot; data-start=&quot;1249&quot;&gt;POST /api/v1/vector-search&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;139&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OkD2r/dJMcajna5mz/83m0QOaH4F7AYzPtoeYvZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OkD2r/dJMcajna5mz/83m0QOaH4F7AYzPtoeYvZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OkD2r/dJMcajna5mz/83m0QOaH4F7AYzPtoeYvZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOkD2r%2FdJMcajna5mz%2F83m0QOaH4F7AYzPtoeYvZK%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;139&quot; data-origin-width=&quot;509&quot; data-origin-height=&quot;139&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1291&quot; data-start=&quot;1281&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 항목&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1349&quot; data-start=&quot;1292&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1310&quot; data-start=&quot;1292&quot;&gt;top_k 파라미터 동작 여부&lt;/li&gt;
&lt;li data-end=&quot;1330&quot; data-start=&quot;1311&quot;&gt;문서 기반 검색 결과 반환 여부&lt;/li&gt;
&lt;li data-end=&quot;1349&quot; data-start=&quot;1331&quot;&gt;검색 결과 정렬 및 개수 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DGKT8/dJMcaiaMQxF/6eX8XZko3S2ZqRNmmhExgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DGKT8/dJMcaiaMQxF/6eX8XZko3S2ZqRNmmhExgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DGKT8/dJMcaiaMQxF/6eX8XZko3S2ZqRNmmhExgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDGKT8%2FdJMcaiaMQxF%2F6eX8XZko3S2ZqRNmmhExgk%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;362&quot; height=&quot;96&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;96&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1357&quot; data-start=&quot;1351&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1407&quot; data-start=&quot;1358&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1383&quot; data-start=&quot;1358&quot;&gt;top_k 값에 따라 결과 개수 정상 반환&lt;/li&gt;
&lt;li data-end=&quot;1407&quot; data-start=&quot;1384&quot;&gt;문서 임베딩 완료 이후 검색 정상 동작&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;487&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGfSvr/dJMcacVWglm/hw6rBbLizwE8I9aXKi0HyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGfSvr/dJMcacVWglm/hw6rBbLizwE8I9aXKi0HyK/img.png&quot; data-alt=&quot;rag search 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGfSvr/dJMcacVWglm/hw6rBbLizwE8I9aXKi0HyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGfSvr%2FdJMcacVWglm%2Fhw6rBbLizwE8I9aXKi0HyK%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;1440&quot; height=&quot;487&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;487&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;rag search 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-end=&quot;1526&quot; data-start=&quot;1523&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1552&quot; data-start=&quot;1528&quot; data-ke-size=&quot;size23&quot;&gt;2.3 RAG 질의응답 API 테스트&lt;/h3&gt;
&lt;p data-end=&quot;1564&quot; data-start=&quot;1554&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관련 API&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1615&quot; data-start=&quot;1565&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1586&quot; data-start=&quot;1565&quot;&gt;POST /api/v1/chat&lt;/li&gt;
&lt;li data-end=&quot;1615&quot; data-start=&quot;1587&quot;&gt;POST /api/v1/chat/stream&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1627&quot; data-start=&quot;1617&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트 항목&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1693&quot; data-start=&quot;1628&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1649&quot; data-start=&quot;1628&quot;&gt;검색 결과 기반 컨텍스트 생성 여부&lt;/li&gt;
&lt;li data-end=&quot;1667&quot; data-start=&quot;1650&quot;&gt;LLM 응답 생성 정상 여부&lt;/li&gt;
&lt;li data-end=&quot;1693&quot; data-start=&quot;1668&quot;&gt;스트리밍 응답 종료 이벤트 정상 수신 여부&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1701&quot; data-start=&quot;1695&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1752&quot; data-start=&quot;1702&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1726&quot; data-start=&quot;1702&quot;&gt;검색 결과를 기반으로 한 답변 정상 생성&lt;/li&gt;
&lt;li data-end=&quot;1752&quot; data-start=&quot;1727&quot;&gt;스트리밍 응답에서 end 이벤트 정상 수신&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;463&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YJ8vB/dJMcahiD7lA/nL0ptx6QIa7SxP4beCCEy1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YJ8vB/dJMcahiD7lA/nL0ptx6QIa7SxP4beCCEy1/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YJ8vB/dJMcahiD7lA/nL0ptx6QIa7SxP4beCCEy1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYJ8vB%2FdJMcahiD7lA%2FnL0ptx6QIa7SxP4beCCEy1%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;650&quot; height=&quot;463&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;463&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;1763&quot; data-start=&quot;1754&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;수정 내역&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1826&quot; data-start=&quot;1764&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1826&quot; data-start=&quot;1764&quot;&gt;스트리밍 응답 처리 방식 불일치로 인한 응답 누락 문제 발생&lt;br /&gt;&amp;rarr; 스트리밍 전용 처리 로직 분리하여 안정화&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1831&quot; data-start=&quot;1828&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1861&quot; data-start=&quot;1833&quot; data-ke-size=&quot;size26&quot;&gt;3. 통합 테스트 중 주요 오류 및 수정 내역&lt;/h2&gt;
&lt;h3 data-end=&quot;1888&quot; data-start=&quot;1863&quot; data-ke-size=&quot;size23&quot;&gt;3.1 저장소 및 배포 환경 관련 오류&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;항목내용
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;2027&quot; data-start=&quot;1890&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;2027&quot; data-start=&quot;1914&quot;&gt;
&lt;tr data-end=&quot;1945&quot; data-start=&quot;1914&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1919&quot; data-start=&quot;1914&quot;&gt;오류&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1945&quot; data-start=&quot;1919&quot;&gt;ChromaDB deprecated 오류&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;1972&quot; data-start=&quot;1946&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1951&quot; data-start=&quot;1946&quot;&gt;원인&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1972&quot; data-start=&quot;1951&quot;&gt;구버전 Client API 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2005&quot; data-start=&quot;1973&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;1978&quot; data-start=&quot;1973&quot;&gt;조치&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2005&quot; data-start=&quot;1978&quot;&gt;0.4.x 기준 Client 방식으로 수정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2027&quot; data-start=&quot;2006&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2011&quot; data-start=&quot;2006&quot;&gt;결과&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2027&quot; data-start=&quot;2011&quot;&gt;API 서버 정상 기동&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;항목내용
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;2182&quot; data-start=&quot;2029&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;2182&quot; data-start=&quot;2053&quot;&gt;
&lt;tr data-end=&quot;2094&quot; data-start=&quot;2053&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2058&quot; data-start=&quot;2053&quot;&gt;오류&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2094&quot; data-start=&quot;2058&quot;&gt;API / Worker vector store 경로 불일치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2124&quot; data-start=&quot;2095&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2100&quot; data-start=&quot;2095&quot;&gt;원인&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2124&quot; data-start=&quot;2100&quot;&gt;Docker volume 설정 불일치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2161&quot; data-start=&quot;2125&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2130&quot; data-start=&quot;2125&quot;&gt;조치&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2161&quot; data-start=&quot;2130&quot;&gt;global persist directory 고정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2182&quot; data-start=&quot;2162&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2167&quot; data-start=&quot;2162&quot;&gt;결과&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2182&quot; data-start=&quot;2167&quot;&gt;검색 결과 정상 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;2187&quot; data-start=&quot;2184&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;2214&quot; data-start=&quot;2189&quot; data-ke-size=&quot;size23&quot;&gt;3.2 실행 환경 및 처리 안정성 오류&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;항목내용
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;2344&quot; data-start=&quot;2216&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;2344&quot; data-start=&quot;2240&quot;&gt;
&lt;tr data-end=&quot;2271&quot; data-start=&quot;2240&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2245&quot; data-start=&quot;2240&quot;&gt;오류&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2271&quot; data-start=&quot;2245&quot;&gt;API / Worker GPU 자원 충돌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2296&quot; data-start=&quot;2272&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2277&quot; data-start=&quot;2272&quot;&gt;원인&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2296&quot; data-start=&quot;2277&quot;&gt;모든 컨테이너에 GPU 할당&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2322&quot; data-start=&quot;2297&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2302&quot; data-start=&quot;2297&quot;&gt;조치&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2322&quot; data-start=&quot;2302&quot;&gt;API 컨테이너만 GPU 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2344&quot; data-start=&quot;2323&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2328&quot; data-start=&quot;2323&quot;&gt;결과&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2344&quot; data-start=&quot;2328&quot;&gt;GPU 인식 문제 해결&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;항목내용
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;2466&quot; data-start=&quot;2346&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;2466&quot; data-start=&quot;2370&quot;&gt;
&lt;tr data-end=&quot;2399&quot; data-start=&quot;2370&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2375&quot; data-start=&quot;2370&quot;&gt;오류&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2399&quot; data-start=&quot;2375&quot;&gt;병렬 요청 시 DB commit 충돌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2421&quot; data-start=&quot;2400&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2405&quot; data-start=&quot;2400&quot;&gt;원인&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2421&quot; data-start=&quot;2405&quot;&gt;전역 싱글턴 DB 세션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2445&quot; data-start=&quot;2422&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2427&quot; data-start=&quot;2422&quot;&gt;조치&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2445&quot; data-start=&quot;2427&quot;&gt;요청 단위 DB 세션 분리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;2466&quot; data-start=&quot;2446&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2451&quot; data-start=&quot;2446&quot;&gt;결과&lt;/td&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;2466&quot; data-start=&quot;2451&quot;&gt;트랜잭션 안정성 확보&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-end=&quot;2471&quot; data-start=&quot;2468&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2493&quot; data-start=&quot;2473&quot; data-ke-size=&quot;size26&quot;&gt;4. 테스트 결과 로그 (요약)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2603&quot; data-start=&quot;2495&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2526&quot; data-start=&quot;2495&quot;&gt;Swagger Docs 기반 전체 API 테스트 완료&lt;/li&gt;
&lt;li data-end=&quot;2570&quot; data-start=&quot;2527&quot;&gt;Docker 환경에서 API / Worker 재시작 후에도 동일 결과 확인&lt;/li&gt;
&lt;li data-end=&quot;2603&quot; data-start=&quot;2571&quot;&gt;문서 업로드 &amp;rarr; 검색 &amp;rarr; 응답 전 구간 정상 동작 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;2630&quot; data-start=&quot;2627&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2640&quot; data-start=&quot;2632&quot; data-ke-size=&quot;size26&quot;&gt;5. 결론&lt;/h2&gt;
&lt;p data-end=&quot;2725&quot; data-start=&quot;2642&quot; data-ke-size=&quot;size16&quot;&gt;본 과제에서는 전체 API 기능 구현 여부뿐만 아니라, &lt;b&gt;실제 배포 환경에서 발생할 수 있는 오류를 통합 테스트를 통해 발견하고 수정&lt;/b&gt;하였다.&amp;nbsp;이를 통해 최종 발표 시연이 가능한 수준의 안정적인 Backend 파이프라인을 구축하였다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div style=&quot;color: #333333;&quot;&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-end=&quot;4844&quot; data-start=&quot;4751&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;본 후기는 [&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;한글과컴퓨터&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;x한국생산성본부x스나이퍼팩토리]&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;한컴&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;AI 아카데미 2기 (&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;B&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;) 리뷰&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;로 작성 되었습니다&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>한컴AI 2기</category>
      <category>ai개발자</category>
      <category>ai개발자교육</category>
      <category>ai전문가양성</category>
      <category>개발자교육</category>
      <category>개발자취업</category>
      <category>부트캠프</category>
      <category>스나이퍼팩토리</category>
      <category>한국생산성본부</category>
      <category>한글과컴퓨터</category>
      <category>한컴ai아카데미2기</category>
      <author>싱커</author>
      <guid isPermaLink="true">https://excode.tistory.com/36</guid>
      <comments>https://excode.tistory.com/36#entry36comment</comments>
      <pubDate>Fri, 12 Dec 2025 23:35:41 +0900</pubDate>
    </item>
    <item>
      <title>[스나이퍼팩토리] 한컴AI 2기 - Vercel - FastAPI Mixed Content 트러블 슈팅 (무료 도메인, Nginx https, 서트봇)</title>
      <link>https://excode.tistory.com/34</link>
      <description>&lt;div&gt;
&lt;style&gt;
/* 전체 글 스타일 */
.post-body {
    font-family: &quot;Pretendard&quot;, &quot;Noto Sans KR&quot;, sans-serif;
    line-height: 1.7;
    font-size: 16px;
    color: #333;
}

/* 강조박스 */
.info-box {
    padding: 16px 20px;
    border-left: 4px solid #4a90e2;
    background: #f5f9ff;
    margin: 20px 0;
    border-radius: 6px;
}

/* 코드블록 */
.code-block {
    background: #1e1e1e;
    color: #cfcfcf;
    padding: 15px;
    border-radius: 6px;
    overflow-x: auto;
    margin: 20px 0;
    font-family: &quot;JetBrains Mono&quot;, monospace;
    font-size: 14px;
}

/* 테이블 */
.table-box table {
    width: 100%;
    border-collapse: collapse;
    margin: 20px 0;
}
.table-box th, .table-box td {
    border: 1px solid #ccc;
    padding: 10px;
    text-align: left;
}
.table-box tr:nth-child(odd) {
    background: #fafafa;
}

/* 구분선 */
.section-line {
    border: 0;
    border-top: 1px solid #e2e2e2;
    margin: 40px 0;
}
&lt;/style&gt;
&lt;/div&gt;
&lt;div class=&quot;post-body&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글은 내가 진행했던 RAG 프로젝트 환경 세팅 중,&lt;br /&gt;&lt;b&gt;무료 도메인 확보 &amp;rarr; 서버 연결 &amp;rarr; HTTPS 적용 &amp;rarr; Nginx 리팩토링&lt;/b&gt; 전 과정을 정리한 기록이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS EC2에서 FastAPI 백엔드를 운영하고 있었는데,&lt;br /&gt;Vercel 프론트엔드에서 API 요청을 보내려면 반드시 HTTPS가 필요했다.&lt;br /&gt;하지만 초기 상태는 &lt;b&gt;HTTP IP 기반 서버&lt;/b&gt;뿐이라&lt;br /&gt;브라우저에서 &quot;&lt;b&gt;Mixed Content&lt;/b&gt;&quot; 에러가 터졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 결국 &lt;b&gt;무료 도메인 확보 &amp;rarr; Nginx HTTPS 구성 &amp;rarr; Certbot 발급&lt;/b&gt;을 직접 해보기로 했다.&lt;/p&gt;
&lt;hr class=&quot;section-line&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 무료 도메인 확보 &amp;mdash; alain.r-e.kr&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로덕션용이 아니라 연구 프로젝트였기 때문에, 우선 HTTPS를 빠르게 적용하는 것이 목표였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무료 도메인을 제공하는 &lt;a href=&quot;https://xn--220b31d95hq8o.xn--3e0b707e/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;내도메인.한국&lt;/a&gt; 사이트에서 &lt;b&gt;alain.r-e.kr&lt;/b&gt;을 선택했다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;553&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOG5Mj/dJMcahv78bl/kpdHf4cQe3z5vECScvTUx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOG5Mj/dJMcahv78bl/kpdHf4cQe3z5vECScvTUx0/img.png&quot; data-alt=&quot;내도메인.한국의 도메인 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOG5Mj/dJMcahv78bl/kpdHf4cQe3z5vECScvTUx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOG5Mj%2FdJMcahv78bl%2FkpdHf4cQe3z5vECScvTUx0%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;476&quot; height=&quot;441&quot; data-origin-width=&quot;553&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;내도메인.한국의 도메인 설정&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;DNS A레코드에 EC2 퍼블릭 IP(v4)만 등록하면 끝. DNS 전파는 보통 1~5분 내로 완료되었다.&lt;/p&gt;
&lt;hr class=&quot;section-line&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 기존 문제 상황 &amp;mdash; Certbot이 실패했다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 Certbot을 실행했을 때 다음과 같은 오류가 발생했다:&lt;/p&gt;
&lt;div class=&quot;info-box&quot;&gt;&lt;b&gt;Error while running nginx -t:&lt;/b&gt;&lt;br /&gt;cannot load certificate &quot;/etc/letsencrypt/live/alain.r-e.kr/fullchain.pem&quot;&lt;/div&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;Nginx 설정에 이미 &lt;b&gt;HTTPS server block이 존재&lt;/b&gt;하고 있었고,&lt;br /&gt;아직 발급되지 않은 인증서를 로딩하려다가 nginx test가 실패한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 전략을 바꿨다:&lt;/p&gt;
&lt;div class=&quot;info-box&quot;&gt;1) &lt;b&gt;발급 전 &amp;rarr; HTTP-only 구성 &amp;rarr; Certbot 성공&lt;/b&gt;&lt;br /&gt;2) &lt;b&gt;발급 후 &amp;rarr; HTTPS 블록 다시 적용 &amp;rarr; Nginx Reload&lt;/b&gt;&lt;/div&gt;
&lt;hr class=&quot;section-line&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Nginx 설정 &amp;mdash; HTTP 전용 버전으로 리팩토링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Certbot이 인증서를 발급하려면, Nginx 설정에 HTTPS 블록이 존재하면 안 된다.&lt;br /&gt;그래서 HTTPS 관련 내용을 모두 제거한 &amp;ldquo;HTTP-only 구성&amp;rdquo;으로 바꿨다.&lt;/p&gt;
&lt;div class=&quot;code-block&quot;&gt;server {&lt;br /&gt;&amp;nbsp;&amp;nbsp;listen 80;&lt;br /&gt;&amp;nbsp;&amp;nbsp;server_name alain.r-e.kr;&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;location /.well-known/acme-challenge/ {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;root /var/www/certbot;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;location / {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;proxy_pass http://localhost:8000;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/div&gt;
&lt;hr class=&quot;section-line&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Certbot 설치 &amp;amp; 인증서 발급&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치 명령어는 다음과 같다:&lt;/p&gt;
&lt;div class=&quot;code-block&quot;&gt;sudo apt install certbot python3-certbot-nginx -y&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증서 발급:&lt;/p&gt;
&lt;div class=&quot;code-block&quot;&gt;sudo certbot --nginx -d alain.r-e.kr --agree-tos --email my@email.com&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 문구가 뜨면 성공이다:&lt;/p&gt;
&lt;div class=&quot;info-box&quot;&gt;&lt;b&gt;Congratulations! Your certificate has been successfully issued!&lt;/b&gt;&lt;/div&gt;
&lt;hr class=&quot;section-line&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. HTTPS 서버블록 추가 &amp;mdash; 본격 안전 API 구축&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증서 발급 후 HTTPS server block을 다시 추가하고, nginx -t &amp;rarr; reload 를 수행했다.&lt;/p&gt;
&lt;div class=&quot;code-block&quot;&gt;sudo nginx -t&lt;br /&gt;sudo systemctl reload nginx&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 아래 URL에서 Swagger가 HTTPS로 열린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;&lt;a href=&quot;https://alain.r-e.kr/docs&quot;&gt;https://alain.r-e.kr/docs&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;474&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OZ90F/dJMcaf59r6U/TrMS5unf41zhuN3ifUVrvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OZ90F/dJMcaf59r6U/TrMS5unf41zhuN3ifUVrvk/img.png&quot; data-alt=&quot;swagger docs&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OZ90F/dJMcaf59r6U/TrMS5unf41zhuN3ifUVrvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOZ90F%2FdJMcaf59r6U%2FTrMS5unf41zhuN3ifUVrvk%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;382&quot; height=&quot;302&quot; data-origin-width=&quot;474&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;swagger docs&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;hr class=&quot;section-line&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. Mixed Content 문제 해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS API가 준비되면서, Vercel에서 발생하던 Mixed Content 오류가 모두 사라졌다.&lt;/p&gt;
&lt;div class=&quot;info-box&quot;&gt;&lt;b&gt;proxy_set_header X-Forwarded-Proto https;&lt;/b&gt;&lt;br /&gt;&amp;rarr; Mixed Content 방지의 핵심 설정&lt;/div&gt;
&lt;hr class=&quot;section-line&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 마무리하며 &amp;mdash; 배운 점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 도메인/HTTPS 구축 과정에서 배운 핵심은 다음과 같다.&lt;/p&gt;
&lt;div class=&quot;info-box&quot;&gt;&lt;b&gt;① Certbot은 &amp;ldquo;HTTPS 블록이 없는 상태&amp;rdquo;에서 실행해야 한다.&lt;/b&gt;&lt;br /&gt;&amp;rarr; 인증서 파일이 없는데 HTTPS server block이 미리 있으면 nginx test가 죽는다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;② 도메인은 HTTPS 구축의 필수 요소다.&lt;/b&gt;&lt;br /&gt;&amp;rarr; IP만으로는 무료/정식 인증서 발급 불가능.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;③ Nginx는 HTTP &amp;rarr; HTTPS 단계적 관리가 중요하다.&lt;/b&gt;&lt;br /&gt;&amp;rarr; 특히 SSE, CORS가 필요할 때는 더 세심한 설정 필요.&lt;/div&gt;
&lt;hr class=&quot;section-line&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;  정리&lt;/h1&gt;
&lt;div class=&quot;table-box&quot;&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;무료 도메인 선택 (alain.r-e.kr)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;DNS A레코드 &amp;rarr; EC2 IP 매핑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Nginx HTTP-only 구성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Certbot으로 SSL 발급&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;HTTPS 서버블록 최종 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;h1&gt;  마치며&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기록이, 나처럼 &amp;ldquo;무료 도메인 + EC2 + FastAPI + Nginx + Certbot&amp;rdquo; 조합으로 HTTPS를 적용하려는 사람에게 도움이 되길 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cloudflare SSL Mode는 본 글에서 다루지 않았지만 필요하다면 후속 포스팅으로 정리해볼 수도 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;4751&quot; data-end=&quot;4844&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;본 후기는 [&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;한글과컴퓨터&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;x한국생산성본부x스나이퍼팩토리]&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;한컴&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;AI 아카데미 2기 (&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;B&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;) 리뷰&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;로 작성 되었습니다&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>한컴AI 2기</category>
      <category>ai개발자</category>
      <category>ai개발자교육</category>
      <category>ai전문가양성</category>
      <category>개발자교육</category>
      <category>개발자취업</category>
      <category>부트캠프</category>
      <category>스나이퍼팩토리</category>
      <category>한국생산성본부</category>
      <category>한글과컴퓨터</category>
      <category>한컴ai아카데미2기</category>
      <author>싱커</author>
      <guid isPermaLink="true">https://excode.tistory.com/34</guid>
      <comments>https://excode.tistory.com/34#entry34comment</comments>
      <pubDate>Thu, 4 Dec 2025 10:09:26 +0900</pubDate>
    </item>
    <item>
      <title>[스나이퍼팩토리] 한컴AI 2기 - 알랭 서비스 통합</title>
      <link>https://excode.tistory.com/33</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;아키텍처 흐름도&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;565&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qjrV3/dJMcac9lOrh/1l2wY0yFXSCY76i1CJMT70/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qjrV3/dJMcac9lOrh/1l2wY0yFXSCY76i1CJMT70/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qjrV3/dJMcac9lOrh/1l2wY0yFXSCY76i1CJMT70/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqjrV3%2FdJMcac9lOrh%2F1l2wY0yFXSCY76i1CJMT70%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;1024&quot; height=&quot;565&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;565&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2차멘토링과 중간발표에서 사용자 흐름에 따른 데이터 이동에 대한 질문을 받았었다. 말로 설명했지만 충분한 전달이 이뤄지지 않았는데, 결국 그림이 필요했다. gemini3.0이 공개되고, &lt;a style=&quot;background-color: #ffffff; color: #174ea6; text-align: start;&quot; href=&quot;http://deepmind.google/models/gemini-image/pro&quot;&gt;나노 바나나 프로&lt;/a&gt;가 사용가능해진 타이밍이 실로 적절했다. 우리의 서비스에 대한 설명을 기반으로 이미지를 만들어달라고 했더니, 꽤 그럴싸하게 만들어줬다. 해당 이미지에서 몇가지 수정 또한 프롬프팅으로 가능했다.&lt;/p&gt;
&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: #e9eef6; color: #1f1f1f; text-align: start;&quot;&gt;비동기 문서 업로드 파이프라인&lt;/span&gt;과 &lt;span style=&quot;background-color: #e9eef6; color: #1f1f1f; text-align: start;&quot;&gt;질의 응답 파이프라인&lt;/span&gt;은 서비스의 중요한 축이고, 전체 흐름도로부터 별도로 보여줄 필요가 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfsRRp/dJMcahbNgTx/gXKnzPlnDsIQyxLheBjBFK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfsRRp/dJMcahbNgTx/gXKnzPlnDsIQyxLheBjBFK/img.webp&quot; data-alt=&quot;문서 업로드 흐름도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfsRRp/dJMcahbNgTx/gXKnzPlnDsIQyxLheBjBFK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfsRRp%2FdJMcahbNgTx%2FgXKnzPlnDsIQyxLheBjBFK%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;1024&quot; height=&quot;559&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&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;rarr; 프론트엔드에 문서 업로드 &amp;rarr; 백엔드 FastAPI로 전달 &amp;rarr; Celery 워커의 처리(파싱-청킹-임베딩) &amp;rarr; DB 의 흐름이 잘 표현되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PDdoH/dJMcabCEbW8/7IwehgiaycoxLvy6fvRNkK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PDdoH/dJMcabCEbW8/7IwehgiaycoxLvy6fvRNkK/img.webp&quot; data-alt=&quot;사용자 질의응답 시나리오&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PDdoH/dJMcabCEbW8/7IwehgiaycoxLvy6fvRNkK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPDdoH%2FdJMcabCEbW8%2F7IwehgiaycoxLvy6fvRNkK%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;1024&quot; height=&quot;559&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&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;rarr; 프론트엔드에 질문 &amp;rarr; 백엔드 FastAPI로 전달 &amp;rarr; 사전필터링과 임베딩 &amp;rarr; 벡터DB에서의 검색 &amp;rarr; LLM의 응답 까지의 흐름이 역시 잘 표현되었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경 분리(Docker Profile)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rryL3/dJMcagRuhNb/dTT9KoKNAyZdOjuJUNJoHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rryL3/dJMcagRuhNb/dTT9KoKNAyZdOjuJUNJoHK/img.png&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;534&quot; data-is-animation=&quot;false&quot; style=&quot;width: 20.9526%; margin-right: 10px;&quot; data-widthpercent=&quot;21.2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rryL3/dJMcagRuhNb/dTT9KoKNAyZdOjuJUNJoHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrryL3%2FdJMcagRuhNb%2FdTT9KoKNAyZdOjuJUNJoHK%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;504&quot; height=&quot;534&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cXt3wc/dJMcaaRhm1q/kALhMMjapu4tu6wSTjNTm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cXt3wc/dJMcaaRhm1q/kALhMMjapu4tu6wSTjNTm1/img.png&quot; data-origin-width=&quot;1049&quot; data-origin-height=&quot;299&quot; data-is-animation=&quot;false&quot; style=&quot;width: 77.8847%;&quot; data-widthpercent=&quot;78.8&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cXt3wc/dJMcaaRhm1q/kALhMMjapu4tu6wSTjNTm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcXt3wc%2FdJMcaaRhm1q%2FkALhMMjapu4tu6wSTjNTm1%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;1049&quot; height=&quot;299&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모든 과정이 하나의 파이프라인으로 자동화되어 있고, Docker 환경에서 각 서비스가 독립적으로 동작하면서도 유기적으로 연결되어 있다. 각 도커 프로필의 역할은 다음과 같다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Redis (Message Broker)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;5&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;역할:&lt;/b&gt; 비동기 작업 큐(Task Queue)를 관리하는 메시지 브로커.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;학습:&lt;/b&gt; API 서버와 워커 사이의 &lt;b&gt;결합도를 낮추는 핵심 미들웨어&lt;/b&gt;다. API가 요청받은 무거운 작업을 Redis 큐에 넣어두면(Push), 워커가 상황에 맞춰 가져가서(Pull) 처리한다. 덕분에 트래픽이 몰려도 요청이 유실되지 않고 큐에 안전하게 쌓인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. FastAPI (API Server)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스펙:&lt;/b&gt; FastAPI, Port 8000&lt;/li&gt;
&lt;li&gt;&lt;b&gt;역할:&lt;/b&gt; 사용자 요청 수신, 파일 저장, Task 발행, 상태 조회.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;학습:&lt;/b&gt; 무거운 연산(임베딩 등)은 직접 하지 않고 &lt;b&gt;'접수창구' 역할에만 집중&lt;/b&gt;하도록 설계했다. 덕분에 동시 접속자가 많아져도 메인 스레드가 차단되지 않고 즉각적인 응답(Non-blocking)을 줄 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Celery Worker (Task Consumer)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;역할:&lt;/b&gt; 실제 문서 파싱, 청킹, 임베딩(GPU 연산) 수행.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;학습:&lt;/b&gt; Redis에서 대기 중인 작업을 가져와 처리하는 일꾼이다. 가장 중요한 점은 &lt;b&gt;스케일 아웃(Scale-out)이 용이하다는 것&lt;/b&gt;이다. 지금은 컨테이너가 하나지만, 향후 문서 처리량이 급증하면 이 워커 컨테이너의 레플리카만 늘려서 병렬 처리 능력을 손쉽게 확장할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. Celery Flower (Monitoring)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스펙:&lt;/b&gt; Port 5555&lt;/li&gt;
&lt;li&gt;&lt;b&gt;역할:&lt;/b&gt; 비동기 작업 상태 모니터링 및 대시보드.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;학습:&lt;/b&gt; 눈에 보이지 않는 백그라운드 작업들의 상태(처리 중, 성공, 실패)를 &lt;b&gt;시각화(Observability)&lt;/b&gt; 해준다. 비동기 로직 개발 시 디버깅 툴로 매우 유용하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;989&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUaL2S/dJMb995UpW8/u96EYHHFZ5O6XNyvIzeokK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUaL2S/dJMb995UpW8/u96EYHHFZ5O6XNyvIzeokK/img.png&quot; data-alt=&quot;환경 분리의 장점&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUaL2S/dJMb995UpW8/u96EYHHFZ5O6XNyvIzeokK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUaL2S%2FdJMb995UpW8%2Fu96EYHHFZ5O6XNyvIzeokK%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;989&quot; height=&quot;456&quot; data-origin-width=&quot;989&quot; data-origin-height=&quot;456&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;이렇게 분리하면서 얻은 장점들은 위와 같다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;문서 파싱 (OWPML)&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1379&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMFmyy/dJMcacuM8Y8/LEW9HmlXbGNnaYaHr71Zk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMFmyy/dJMcacuM8Y8/LEW9HmlXbGNnaYaHr71Zk1/img.png&quot; data-alt=&quot;문서 파싱 파이프라인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMFmyy/dJMcacuM8Y8/LEW9HmlXbGNnaYaHr71Zk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMFmyy%2FdJMcacuM8Y8%2FLEW9HmlXbGNnaYaHr71Zk1%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;1379&quot; height=&quot;120&quot; data-origin-width=&quot;1379&quot; data-origin-height=&quot;120&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;파싱되는 문서는 다섯가지의 단계를 거친다. 먼저 확장자를 확인하고, 형식에 맞는 파싱을 한다. 이후 데이터 정규화를 거쳐 문서의 구조정보, 메타데이터 등을 추출한다. 이 결과는 디렉토리에 .txt, .json 형태로 저장된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HWP 파일&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;레거시 바이너리 파일 포맷이다. JPype 브릿지를 통해 hwplib을 활용하여 파싱한다. JVM 환경에서 텍스트를 추출하며, 텍스트 추출은 가능하나 표와 이미지는 제한적이다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HWPX 파일&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;HWPX는 ZIP 파일이다. 압축을 풀면 XML 파일들이 나오는데, 이 파일들을 파싱하고, OWPML 스키마를 따라 구조화 시킨다. (표준 라이브러리인 zipfile과 ElementTree를 사용해서 이 XML을 파싱한다.)&lt;br /&gt;HWPX에서는 텍스트뿐만 아니라 표와 이미지도 추출할 수 있다. 표는 행과 열 구조를 그대로 유지한 채 JSON으로 변환하고, 이미지는 원본 포맷 그대로 파일로 저장한다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;파싱 전략&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1325&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4NFsm/dJMcac9lPmi/TenkqyhKxmKcmVFRzadwq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4NFsm/dJMcac9lPmi/TenkqyhKxmKcmVFRzadwq0/img.png&quot; data-alt=&quot;정규식 기반 계층 구조 분석&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4NFsm/dJMcac9lPmi/TenkqyhKxmKcmVFRzadwq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4NFsm%2FdJMcac9lPmi%2FTenkqyhKxmKcmVFRzadwq0%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;1325&quot; height=&quot;298&quot; data-origin-width=&quot;1325&quot; data-origin-height=&quot;298&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;공공기관의 문서 데이터를 주로 사용하기에, 이러한 공문서의 작성규칙에 맞춘 파싱 전략을 세웠다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;근거 추적 메커니즘&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;323&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3VHJS/dJMcaiPgsd0/06GmqQAq586IaLU2UWMi9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3VHJS/dJMcaiPgsd0/06GmqQAq586IaLU2UWMi9K/img.png&quot; data-alt=&quot;청크 메타데이터&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3VHJS/dJMcaiPgsd0/06GmqQAq586IaLU2UWMi9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3VHJS%2FdJMcaiPgsd0%2F06GmqQAq586IaLU2UWMi9K%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;323&quot; height=&quot;176&quot; data-origin-width=&quot;323&quot; data-origin-height=&quot;176&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;청크 메타데이터&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-path-to-node=&quot;3&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Hierarchy Path 자동 태깅&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;작동 원리:&lt;/b&gt; 단순히 문서를 500자, 1000자 단위로 뚝뚝 자르기만 하면, 그 조각(Chunk)이 원래 문서의 &lt;b&gt;어느 소속&lt;/b&gt;인지 문맥을 잃어버리기 쉽다. 이를 해결하기 위해 청킹 시점에 상위 헤더를 역추적하는 로직을 구현했다. _find_structure_context 메서드는 현재 청크의 위치에서 가장 가까운 상위 '장(Chapter)'과 '조(Article)'를 찾아내어, 메타데이터에 hierarchy_path(&quot;제3장 급여의 지급 &amp;gt; 제15조&quot;)를 자동으로 각인시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RAG 서비스의 핵심은 &lt;b&gt;답변의 신뢰도&lt;/b&gt;다. 사용자가 &quot;급여는 언제 나오나요?&quot;라고 물었을 때, AI가 내용을 뭉뚱그려 답하는 것과&amp;nbsp;&lt;b&gt;&quot;공무원연금규정 제3장 제15조에 따르면...&quot;&lt;/b&gt;이라며 정확한 조항을 인용하는 것은 천지 차이다. 메타데이터를 꼼꼼하게 관리한 덕분에, 이제 우리 서비스는 LLM이 답변할 때 정확한&amp;nbsp;&lt;b&gt;근거(Source)&lt;/b&gt;를 함께 제시할 수 있게 되었다.&lt;span style=&quot;color: #222222; letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;color: #222222;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;본 후기는 [&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;한글과컴퓨터&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;x한국생산성본부x스나이퍼팩토리]&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;한컴&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;AI 아카데미 2기 (&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;B&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;) 리뷰&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;로 작성 되었습니다&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;
&lt;div id=&quot;reaction-32&quot; data-tistory-react-app=&quot;Reaction&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>한컴AI 2기</category>
      <category>ai개발자</category>
      <category>ai개발자교육</category>
      <category>ai전문가양성</category>
      <category>개발자교육</category>
      <category>개발자취업</category>
      <category>부트캠프</category>
      <category>스나이퍼팩토리</category>
      <category>한국생산성본부</category>
      <category>한글과컴퓨터</category>
      <category>한컴ai아카데미2기</category>
      <author>싱커</author>
      <guid isPermaLink="true">https://excode.tistory.com/33</guid>
      <comments>https://excode.tistory.com/33#entry33comment</comments>
      <pubDate>Fri, 28 Nov 2025 18:00:04 +0900</pubDate>
    </item>
    <item>
      <title>[스나이퍼팩토리] 한컴AI 2기 - 알랭 프로젝트 중간발표 회고</title>
      <link>https://excode.tistory.com/32</link>
      <description>&lt;h1 data-end=&quot;206&quot; data-start=&quot;163&quot;&gt;&amp;nbsp;&lt;/h1&gt;
&lt;p data-end=&quot;354&quot; data-start=&quot;208&quot; data-ke-size=&quot;size16&quot;&gt;중간발표가 끝났다. 결과만 놓고 보면 발표는 조금 흔들렸지만,&lt;br /&gt;발표 과정 자체가 이번 프로젝트에서 가장 많은 배움을 남긴 순간이었다.&lt;br /&gt;이 글에서는 &lt;b&gt;중간발표 준비 과정에서 배운 점&lt;/b&gt;과 &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-origin-width=&quot;665&quot; data-origin-height=&quot;919&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TpN6c/dJMcaf54V5j/jf6nshEH5XPIKXCRuKivZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TpN6c/dJMcaf54V5j/jf6nshEH5XPIKXCRuKivZ1/img.png&quot; data-alt=&quot;백엔드 API 스웨거 문서&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TpN6c/dJMcaf54V5j/jf6nshEH5XPIKXCRuKivZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTpN6c%2FdJMcaf54V5j%2Fjf6nshEH5XPIKXCRuKivZ1%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;665&quot; height=&quot;919&quot; data-origin-width=&quot;665&quot; data-origin-height=&quot;919&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;백엔드 API 스웨거 문서&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-end=&quot;359&quot; data-start=&quot;356&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;385&quot; data-start=&quot;361&quot; data-ke-size=&quot;size26&quot;&gt;  &lt;b&gt;1. 중간발표가 남긴 것들&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;463&quot; data-start=&quot;387&quot; data-ke-size=&quot;size16&quot;&gt;발표를 준비하면서 처음 느낀 건,&lt;br /&gt;우리가 만든 기능들이 많아질수록 &lt;b&gt;설명해야 할 맥락도 비례해 폭발적으로 많아진다는 사실&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-end=&quot;582&quot; data-start=&quot;465&quot; data-ke-size=&quot;size16&quot;&gt;HWP/HWPX 파싱, OWPML, 청킹, 임베딩, RAG, 권한 시스템, 문서 Diff, 프론트 구조, 크롤러&amp;hellip;&lt;br /&gt;하고 있는 건 분명 많았지만, 그것들을 &lt;b&gt;한 문장으로 압축해서 말하는 능력&lt;/b&gt;은 부족했다.&lt;/p&gt;
&lt;p data-end=&quot;606&quot; data-start=&quot;584&quot; data-ke-size=&quot;size16&quot;&gt;이번 발표는 그 공백을 정확히 드러냈다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;687&quot; data-start=&quot;608&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;628&quot; data-start=&quot;608&quot;&gt;&amp;ldquo;핵심 메시지가 너무 많다.&amp;rdquo;&lt;/li&gt;
&lt;li data-end=&quot;658&quot; data-start=&quot;629&quot;&gt;&amp;ldquo;한 번에 전달할 스토리가 정리되지 않았다.&amp;rdquo;&lt;/li&gt;
&lt;li data-end=&quot;687&quot; data-start=&quot;659&quot;&gt;&amp;ldquo;기술을 보여주려다 보니 흐름이 길어졌다.&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;727&quot; data-start=&quot;689&quot; data-ke-size=&quot;size16&quot;&gt;하지만 덕분에 내가 무엇을 더 배워야 하는지 명확하게 알 수 있었다.&lt;/p&gt;
&lt;hr data-end=&quot;732&quot; data-start=&quot;729&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;762&quot; data-start=&quot;734&quot; data-ke-size=&quot;size26&quot;&gt;  &lt;b&gt;2. 발표 준비 과정에서 배운 것&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-end=&quot;791&quot; data-start=&quot;764&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1) 기술은 &amp;lsquo;순서&amp;rsquo;로 설명된다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;871&quot; data-start=&quot;792&quot; data-ke-size=&quot;size16&quot;&gt;우리는 서비스를 만들 때는 흐름을 알고 있지만,&lt;br /&gt;발표에서는 청중이 그 흐름을 &lt;b&gt;처음부터 다시 따라갈 수 있게 순서를 재정렬&lt;/b&gt;해야 한다.&lt;/p&gt;
&lt;p data-end=&quot;903&quot; data-start=&quot;873&quot; data-ke-size=&quot;size16&quot;&gt;이번 발표 준비에서 다음과 같은 구조를 확실히 익혔다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;982&quot; data-start=&quot;905&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;915&quot; data-start=&quot;905&quot;&gt;문제 정의&lt;/li&gt;
&lt;li data-end=&quot;937&quot; data-start=&quot;916&quot;&gt;왜 기존 방식으로는 안 되는가&lt;/li&gt;
&lt;li data-end=&quot;957&quot; data-start=&quot;938&quot;&gt;우리가 선택한 기술적 접근&lt;/li&gt;
&lt;li data-end=&quot;971&quot; data-start=&quot;958&quot;&gt;실제 구현 결과&lt;/li&gt;
&lt;li data-end=&quot;982&quot; data-start=&quot;972&quot;&gt;미래 계획&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;1011&quot; data-start=&quot;984&quot; data-ke-size=&quot;size16&quot;&gt;이 단순한 구조가 발표의 생명줄이라는 걸 느꼈다.&lt;/p&gt;
&lt;hr data-end=&quot;1016&quot; data-start=&quot;1013&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1048&quot; data-start=&quot;1018&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2) 핵심 메시지를 줄여라&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1110&quot; data-start=&quot;1049&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 100장 가까운 자료를 만들었고,&lt;br /&gt;그걸 40여장까지 줄여서 리허설에서 해볼 때만 해도 괜찮을 줄 알았다.&lt;/p&gt;
&lt;p data-end=&quot;1110&quot; data-start=&quot;1049&quot; data-ke-size=&quot;size16&quot;&gt;급격한 변화를 수용하지 말았어야했는데, 결국 내 파트는 날림 뒷파트는 타임오버가 되어버렸다.&lt;/p&gt;
&lt;p data-end=&quot;1110&quot; data-start=&quot;1049&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-start=&quot;1013&quot; data-end=&quot;1016&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1312&quot; data-start=&quot;1279&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3) 대상에 맞춘 설명이 필요하다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1361&quot; data-start=&quot;1313&quot; data-ke-size=&quot;size16&quot;&gt;중간발표의 타깃은&lt;br /&gt;&lt;b&gt;공공기관&amp;middot;기업 실무자 / 팀장&amp;middot;중간관리자 / 어드민&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-end=&quot;1361&quot; data-start=&quot;1313&quot; data-ke-size=&quot;size16&quot;&gt;그러나 우리를 심사하는 멘토들은 대체로 개발자들이었다.&lt;/p&gt;
&lt;p data-end=&quot;1361&quot; data-start=&quot;1313&quot; 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;style6&quot; /&gt;
&lt;h2 data-end=&quot;1563&quot; data-start=&quot;1533&quot; data-ke-size=&quot;size26&quot;&gt;  &lt;b&gt;3. 프로젝트 자체에서 얻은 인사이트&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-end=&quot;1603&quot; data-start=&quot;1565&quot; data-ke-size=&quot;size23&quot;&gt;✔ &lt;b&gt;1) HWP/HWPX는 한국형 문서 RAG의 험지다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1676&quot; data-start=&quot;1604&quot; data-ke-size=&quot;size16&quot;&gt;HWPX는 XML 기반이지만 구조가 제각각이고,&lt;br /&gt;표, 각주, 문단 스타일, heading 처리 등 신경 써야 할 부분이 많다.&lt;/p&gt;
&lt;p data-end=&quot;1702&quot; data-start=&quot;1678&quot; data-ke-size=&quot;size16&quot;&gt;이걸 OWPML 기반으로 처리하며 느낀 건,&lt;/p&gt;
&lt;blockquote data-end=&quot;1757&quot; data-start=&quot;1704&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1757&quot; data-start=&quot;1706&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;한국어 문서에 특화된 RAG는 글로벌 기술을 그대로 가져다 쓰는 것으로는 충분하지 않다.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1827&quot; data-start=&quot;1759&quot; data-ke-size=&quot;size16&quot;&gt;이를 위해 청킹 전략, 텍스트 정리, 메타데이터 스키마 설계를 하나씩 조정해가며&lt;br /&gt;우리만의 파이프라인을 만들 수 있었다.&lt;/p&gt;
&lt;hr data-end=&quot;1832&quot; data-start=&quot;1829&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;1876&quot; data-start=&quot;1834&quot; data-ke-size=&quot;size23&quot;&gt;✔ &lt;b&gt;2) ChromaDB 선택은 단순한 비교 이상의 결정이었다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1960&quot; data-start=&quot;1877&quot; data-ke-size=&quot;size16&quot;&gt;FAISS와 Chroma를 비교했을 때&lt;br /&gt;FAISS는 확실히 강력하지만,&lt;br /&gt;우리가 필요한 것은 &lt;b&gt;메타데이터 중심 검색 + 개발 생산성&lt;/b&gt;이었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2009&quot; data-start=&quot;1962&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1973&quot; data-start=&quot;1962&quot;&gt;구조화된 검색&lt;/li&gt;
&lt;li data-end=&quot;1988&quot; data-start=&quot;1974&quot;&gt;프로젝트/부서 샤딩&lt;/li&gt;
&lt;li data-end=&quot;2009&quot; data-start=&quot;1989&quot;&gt;FastAPI와의 통합 편의성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2057&quot; data-start=&quot;2011&quot; data-ke-size=&quot;size16&quot;&gt;이 모든 점에서 Chroma는 우리의 &amp;ldquo;프로젝트 기간 내 완성 가능성&amp;rdquo;을 높여줬다.&lt;/p&gt;
&lt;hr data-end=&quot;2062&quot; data-start=&quot;2059&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;2103&quot; data-start=&quot;2064&quot; data-ke-size=&quot;size23&quot;&gt;✔ &lt;b&gt;3) RBAC은 단순한 UI 아니라 &amp;lsquo;업무 로직&amp;rsquo;이다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;2162&quot; data-start=&quot;2104&quot; data-ke-size=&quot;size16&quot;&gt;실무자-팀장-어드민의 요구사항을 기술적으로 녹여내면서&lt;br /&gt;권한 구조가 단순한 UI 토글이 아님을 배웠다.&lt;/p&gt;
&lt;p data-end=&quot;2231&quot; data-start=&quot;2164&quot; data-ke-size=&quot;size16&quot;&gt;실제로 문서 변경 요청 플로우를 넣으면서&lt;br /&gt;&lt;b&gt;실제 조직의 문서 흐름과 거의 흡사한 비즈니스 로직&lt;/b&gt;을 구현하게 됐다.&lt;/p&gt;
&lt;hr data-end=&quot;2236&quot; data-start=&quot;2233&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2260&quot; data-start=&quot;2238&quot; data-ke-size=&quot;size26&quot;&gt;  &lt;b&gt;4&lt;/b&gt;&lt;b&gt;. 발표는 완벽하지 않았지만, 성장의 순간이었다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;2573&quot; data-start=&quot;2516&quot; data-ke-size=&quot;size16&quot;&gt;발표는 솔직히 잘 되진 않았다.&lt;br /&gt;하지만 &lt;b&gt;발표를 망친 만큼 프로젝트는 더 잘 보이기 시작했다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2649&quot; data-start=&quot;2575&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2592&quot; data-start=&quot;2575&quot;&gt;무엇을 더 줄여야 하는지&lt;/li&gt;
&lt;li data-end=&quot;2608&quot; data-start=&quot;2593&quot;&gt;왜 설명이 어려웠는지&lt;/li&gt;
&lt;li data-end=&quot;2627&quot; data-start=&quot;2609&quot;&gt;어떤 기능이 진짜 가치인지&lt;/li&gt;
&lt;li data-end=&quot;2649&quot; data-start=&quot;2628&quot;&gt;어떤 기술이 프로젝트의 중심인지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ywIGF/dJMcabo4n7Y/TkhGGATYoWJIK3n7EJRhT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ywIGF/dJMcabo4n7Y/TkhGGATYoWJIK3n7EJRhT1/img.png&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;417&quot; data-is-animation=&quot;false&quot; style=&quot;width: 63.487%; margin-right: 10px;&quot; data-widthpercent=&quot;64.23&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ywIGF/dJMcabo4n7Y/TkhGGATYoWJIK3n7EJRhT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FywIGF%2FdJMcabo4n7Y%2FTkhGGATYoWJIK3n7EJRhT1%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;664&quot; height=&quot;417&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpBhzG/dJMcabvP5hl/Lo15mLGVRXviAXApVv3ji0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpBhzG/dJMcabvP5hl/Lo15mLGVRXviAXApVv3ji0/img.png&quot; data-origin-width=&quot;696&quot; data-origin-height=&quot;785&quot; data-is-animation=&quot;false&quot; style=&quot;width: 35.3502%;&quot; data-widthpercent=&quot;35.77&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpBhzG/dJMcabvP5hl/Lo15mLGVRXviAXApVv3ji0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpBhzG%2FdJMcabvP5hl%2FLo15mLGVRXviAXApVv3ji0%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;696&quot; height=&quot;785&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발의 병목이 되지 않기 위해, 더 많은 백엔드 API를 만들고, 앞으로의 통합 개선을 더욱 도모해야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;본 후기는 [&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;한글과컴퓨터&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;x한국생산성본부x스나이퍼팩토리]&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;한컴&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;AI 아카데미 2기 (&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;B&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;) 리뷰&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;로 작성 되었습니다&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;</description>
      <category>한컴AI 2기</category>
      <category>ai개발자</category>
      <category>ai개발자교육</category>
      <category>ai전문가양성</category>
      <category>개발자교육</category>
      <category>개발자취업</category>
      <category>부트캠프</category>
      <category>스나이퍼팩토리</category>
      <category>한국생산성본부</category>
      <category>한글과컴퓨터</category>
      <category>한컴ai아카데미2기</category>
      <author>싱커</author>
      <guid isPermaLink="true">https://excode.tistory.com/32</guid>
      <comments>https://excode.tistory.com/32#entry32comment</comments>
      <pubDate>Fri, 21 Nov 2025 17:46:43 +0900</pubDate>
    </item>
    <item>
      <title>[스나이퍼팩토리] 한컴AI 2기 - Docker CE 및 Nginx를 활용한 GPU 서버 구축 과정</title>
      <link>https://excode.tistory.com/31</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AWS EC2에서 &lt;b&gt;NVIDIA GPU&lt;/b&gt;를 사용하는 FastAPI 백엔드 서버를 Docker Compose로 구동하고, &lt;b&gt;Nginx 리버스 프록시&lt;/b&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;우선 프로젝트 경로에 back 레포지토리를 클론했다.&lt;/p&gt;
&lt;pre id=&quot;code_1762924649161&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@hancom-team2:~/project$ git clone https://github.com/Newrun-way/back.git
'back'에 복제합니다...
remote: Enumerating objects: 175, done.
remote: Counting objects: 100% (175/175), done.
remote: Compressing objects: 100% (116/116), done.
remote: Total 175 (delta 68), reused 148 (delta 41), pack-reused 0 (from 0)
오브젝트를 받는 중: 100% (175/175), 918.49 KiB | 61.23 MiB/s, 완료.
델타를 알아내는 중: 100% (68/68), 완료.
ubuntu@hancom-team2:~/project$ ls
OWPML1  back&lt;/code&gt;&lt;/pre&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_1762924708398&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@hancom-team2:~/project/back$ sudo docker compose up -d --build
sudo: docker: 명령이 없습니다
ubuntu@hancom-team2:~/project/back$ docker compose up -d --build
명령어 'docker' 을(를) 찾을 수 없습니다. 그러나 다음을 통해 설치할 수 있습니다:
sudo snap install docker         # version 28.4.0, or
sudo snap install docker         # version 28.1.1+1
sudo apt  install podman-docker  # version 3.4.4+ds1-1ubuntu1.22.04.3
sudo apt  install docker.io      # version 28.2.2-0ubuntu1~22.04.1
'snap info &amp;lt;snapname&amp;gt;'에서 추가 버전을 확인하십시오.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커가 설치되지 않은 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sudo 명령어로 설치를 할 수 있는지 권한을 확인해보려면, sudo whami를 사용해서 root가 출력되는지 확인하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1762924754122&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@hancom-team2:~/project/back$ sudo whoami
root
ubuntu@hancom-team2:~/project/back$ sudo apt update
기존:1 http://us-east-1.ec2.archive.ubuntu.com/ubuntu jammy InRelease
받기:2 http://us-east-1.ec2.archive.ubuntu.com/ubuntu jammy-updates InRelease [128 kB]
받기:3 http://us-east-1.ec2.archive.ubuntu.com/ubuntu jammy-backports InRelease [127 kB]
받기:4 http://security.ubuntu.com/ubuntu jammy-security InRelease [129 kB]
받기:5 http://us-east-1.ec2.archive.ubuntu.com/ubuntu jammy-updates/main amd64 Packages [3,076 kB]
받기:6 http://us-east-1.ec2.archive.ubuntu.com/ubuntu jammy-updates/main Translation-en [475 kB]
받기:7 http://us-east-1.ec2.archive.ubuntu.com/ubuntu jammy-updates/main amd64 c-n-f Metadata [19.0 kB]
받기:8 http://us-east-1.ec2.archive.ubuntu.com/ubuntu jammy-updates/restricted amd64 Packages [4,853 kB]
받기:9 http://us-east-1.ec2.archive.ubuntu.com/ubuntu jammy-updates/restricted Translation-en [903 kB]
받기:10 http://us-east-1.ec2.archive.ubuntu.com/ubuntu jammy-updates/universe amd64 Packages [1,242 kB]
받기:11 http://us-east-1.ec2.archive.ubuntu.com/ubuntu jammy-updates/universe Translation-en [308 kB]
받기:12 http://security.ubuntu.com/ubuntu jammy-security/main amd64 Packages [2,815 kB]
받기:13 http://security.ubuntu.com/ubuntu jammy-security/main Translation-en [408 kB]
받기:14 http://security.ubuntu.com/ubuntu jammy-security/restricted amd64 Packages [4,700 kB]
받기:15 http://security.ubuntu.com/ubuntu jammy-security/restricted Translation-en [876 kB]
받기:16 http://security.ubuntu.com/ubuntu jammy-security/universe amd64 Packages [1,009 kB]
받기:17 http://security.ubuntu.com/ubuntu jammy-security/universe Translation-en [220 kB]
내려받기 21.3 M바이트, 소요시간 2초 (9,767 k바이트/초)
패키지 목록을 읽는 중입니다... 완료
의존성 트리를 만드는 중입니다... 완료
상태 정보를 읽는 중입니다... 완료
4 패키지를 업그레이드할 수 있습니다. 확인하려면 'apt list --upgradable'를 실행하십시오.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 root가 출력되었고, 도커를 설치하기 위해 sudo apt update를 우선 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 도커 설치를 해야하는데, 여러 옵션이 있어서 이를 gemini에게 물어봤다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;481&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/see8D/dJMcaa4HDuh/VoUiBkKPzzvDR9TQEVCvb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/see8D/dJMcaa4HDuh/VoUiBkKPzzvDR9TQEVCvb1/img.png&quot; data-alt=&quot;io와 community edition&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/see8D/dJMcaa4HDuh/VoUiBkKPzzvDR9TQEVCvb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsee8D%2FdJMcaa4HDuh%2FVoUiBkKPzzvDR9TQEVCvb1%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;871&quot; height=&quot;481&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;481&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;io와 community edition&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안을 고려하여 커뮤니티 에디션을 설치하려고 했으나, GPG키 등록과 같은 복잡한 절차가 지금으로서는 불필요하다고 여겨 snap파일을 설치하기로 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1762926900403&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ubuntu@hancom-team2:~/project/back$ sudo snap install docker
docker 28.4.0 from Canonical✓ installed
ubuntu@hancom-team2:~/project/back$ sudo usermod -aG docker ${USER}&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;도커 설치 후 sudo usermod -aG docker ${USER} 명령어를 사용해 권한을 주었고, docker compose를 실행했더니, 명령어 부재 문제가 있었다. 이 오류 대응으로 많은 시간을 소비하게되었다. (snap방식 뿐만 아니라 io쪽에서도 문제가 있었으나 생략)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. GPU 컨테이너 구동 문제 최종 해결&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.1 초기 문제와 원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에는 docker compose 실행 시 &lt;b&gt;libnvidia-ml.so.1: cannot open shared object file&lt;/b&gt; 오류가 반복됐다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;원인:&lt;/b&gt; nvidia-smi로 드라이버는 설치된 것을 확인했지만, &lt;b&gt;Snap Docker&lt;/b&gt;가 시스템의 NVIDIA 라이브러리 경로를 컨테이너에 연결하지 못하는 &lt;b&gt;격리 문제&lt;/b&gt;가 근본 원인이었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.2 해결: 공식 Docker CE로 전환&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 해결하기 위해 Docker 환경 자체를 표준으로 바꿨다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Snap 제거:&lt;/b&gt; sudo snap remove docker로 기존 환경을 정리.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Docker CE 설치:&lt;/b&gt; GPG 키 등록 후 &lt;b&gt;docker-ce&lt;/b&gt; 및 &lt;b&gt;docker-compose-plugin&lt;/b&gt;을 설치해서 공식 Docker 환경을 구축했다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과:&lt;/b&gt; 환경 전환 후 GPU 컨테이너 구동이 성공적으로 이루어지면서 NVIDIA 연동 문제를 해결했다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 포트 및 서비스 충돌 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPU 연동 후에도 &lt;b&gt;8000번 포트 충돌&lt;/b&gt; 오류가 발생했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;원인:&lt;/b&gt; &lt;span style=&quot;color: #ef5369;&quot;&gt;docker-compose.yml&lt;/span&gt;에서 backend-cpu 서비스에 &lt;b&gt;profiles&lt;/b&gt; 설정이 누락(&lt;span style=&quot;color: #666666;&quot;&gt;개발의 편의를 위해 주석처리 했었다!)&lt;/span&gt;되어, &lt;b&gt;--profile gpu&lt;/b&gt;를 써도 &lt;b&gt;CPU 컨테이너가 자동으로 시작되어 포트를 선점&lt;/b&gt;했기 때문이었다.&lt;br /&gt;또한 삭제한 도커에 남아있는 컨테이너 이름 충돌도 있었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 최종 실행 및 서비스 분리&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;포트를 점유 중이던 컨테이너를 &lt;b&gt;docker stop&lt;/b&gt; 및 &lt;b&gt;docker rm&lt;/b&gt;으로 제거&lt;/li&gt;
&lt;li&gt;&lt;b&gt;backend-cpu 서비스&lt;/b&gt;의 &lt;span style=&quot;color: #009a87;&quot;&gt;container_name&lt;/span&gt;을 owpml_backend에서 다른 이름으로 변경했다.&lt;/li&gt;
&lt;li&gt;명령어에 &lt;b&gt;backend-gpu&lt;/b&gt; 서비스 이름만 명시해서 실행했다.&lt;/li&gt;
&lt;/ol&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjHn4WV7OuQAxUAAAAAHQAAAAAQlww&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;docker compose --profile gpu up -d backend-gpu
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 명령으로 backend-gpu 컨테이너만 백그라운드에서 성공적으로 구동됐다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Nginx 리버스 프록시로 외부 접속 오픈&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에 올리는 이유는, 동료 개발자를 포함한 다른 일반 사용자도 접속이 가능하게 하기 위함이었다. AWS 보안 그룹에서 8000번 포트가 막혀 있었고, 내가 그걸 풀 권한은 없었다. &lt;b&gt;Nginx 리버스 프록시&lt;/b&gt;를 써서 이 문제를 해결했다. nginx 서버블록은 전 직장에서 경험했어서 비교적 익숙했다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Nginx를 설치하고, 설정 파일(/etc/nginx/sites-available/fastapi)을 만들었다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프록시 설정:&lt;/b&gt; Nginx가 열려 있는 &lt;b&gt;80번 포트&lt;/b&gt;로 들어오는 요청을 Docker 컨테이너의 내부 &lt;b&gt;8000번 포트&lt;/b&gt;로 전달하도록 구성했다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과:&lt;/b&gt; Nginx 설정 후, 인스턴스 공인 IP를 통해 &lt;b&gt;http://52.21.185.171/docs&lt;/b&gt;로 접속했을 때 FastAPI Swagger UI에 정상적으로 접근되는 것을 확인하며 외부 서비스 환경 구축을 마무리했다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1762937380002&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server {
    listen 80;
    server_name _; # 인스턴스의 공인 IP 또는 도메인 이름 (IP 사용 시: 52.21.185.171)

    location / {
        # 요청을 Docker 컨테이너의 8000번 포트로 전달
        proxy_pass http://localhost:8000;
        # Docker Compose 컨테이너 이름을 사용해도 됩니다: http://back_gpu:8000;
        
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}&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-origin-width=&quot;744&quot; data-origin-height=&quot;940&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFkNZs/dJMcagqjCyI/f76s15VVo9fvidkJ1uflbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFkNZs/dJMcagqjCyI/f76s15VVo9fvidkJ1uflbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFkNZs/dJMcagqjCyI/f76s15VVo9fvidkJ1uflbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFkNZs%2FdJMcagqjCyI%2Ff76s15VVo9fvidkJ1uflbK%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;744&quot; height=&quot;940&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;940&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 style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;본 후기는 [&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;한글과컴퓨터&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;x한국생산성본부x스나이퍼팩토리]&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;한컴&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;AI 아카데미 2기 (&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;B&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;) 리뷰&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;로 작성 되었습니다&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;</description>
      <category>한컴AI 2기</category>
      <category>ai개발자</category>
      <category>ai개발자교육</category>
      <category>ai전문가양성</category>
      <category>개발자교육</category>
      <category>개발자취업</category>
      <category>부트캠프</category>
      <category>스나이퍼팩토리</category>
      <category>한국생산성본부</category>
      <category>한글과컴퓨터</category>
      <category>한컴ai아카데미2기</category>
      <author>싱커</author>
      <guid isPermaLink="true">https://excode.tistory.com/31</guid>
      <comments>https://excode.tistory.com/31#entry31comment</comments>
      <pubDate>Wed, 12 Nov 2025 17:51:28 +0900</pubDate>
    </item>
    <item>
      <title>[스나이퍼팩토리] 한컴AI 2기 - 알랭 RAG 로컬 도커에서의 구동기 (w FAISS 샤딩)</title>
      <link>https://excode.tistory.com/30</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;샤딩 (Sharding)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FAISS의 특성상, (성능 개선을 기대할 수 없는) 사후필터링만이 가능해서, 초기의 업로드부터 권한별로 분산해서 저장하면 어떨까? 하는 아이디어로 샤딩을 시도했다. (&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;샤딩은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;DB 트래픽을 분산할 수 있는 중요한 수단이다.)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1621&quot; data-origin-height=&quot;859&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I00pB/dJMcaiaAhqz/WsfG0XdkOOLYzi75X9U26K/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I00pB/dJMcaiaAhqz/WsfG0XdkOOLYzi75X9U26K/img.webp&quot; data-alt=&quot;시도와 성공&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I00pB/dJMcaiaAhqz/WsfG0XdkOOLYzi75X9U26K/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI00pB%2FdJMcaiaAhqz%2FWsfG0XdkOOLYzi75X9U26K%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;1621&quot; height=&quot;859&quot; data-origin-width=&quot;1621&quot; data-origin-height=&quot;859&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;경로 통일 이전의 스크린샷이라, 벡터스토어는 app/data/uploads에 들어가있고, 문서파일은 data/uploads에 들어가있다 ㅎㅎ.. 나중에 다시 app/data/uploads로 통합했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;hwpx RAG 구현 in 도커&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklab(0.988044 0.0000450313 0.0000197887); color: oklab(0.322425 0.00154591 -0.010555); text-align: left;&quot;&gt;metadata 내에 dept_id, project_id, user_id 임의로 넣어봤다. 구조 확장엔 큰 문제가 없어보인다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;859&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xhdAs/dJMcad8bySD/uJltp5DS6TM6HOuwvvXXGK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xhdAs/dJMcad8bySD/uJltp5DS6TM6HOuwvvXXGK/img.webp&quot; data-alt=&quot;hwp 파싱 테스트&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xhdAs/dJMcad8bySD/uJltp5DS6TM6HOuwvvXXGK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxhdAs%2FdJMcad8bySD%2FuJltp5DS6TM6HOuwvvXXGK%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;1440&quot; height=&quot;859&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;859&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;hwp 파싱 테스트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hwp를 파싱했는데, 왜인지 text_content가 하나의 통문자열 리스트로 왔다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1621&quot; data-origin-height=&quot;859&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/enBjzh/dJMcah3OKfz/kkKY23StopctL2ztfyAczK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/enBjzh/dJMcah3OKfz/kkKY23StopctL2ztfyAczK/img.webp&quot; data-alt=&quot;hwpx의 파싱, 청킹, 임베딩&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/enBjzh/dJMcah3OKfz/kkKY23StopctL2ztfyAczK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FenBjzh%2FdJMcah3OKfz%2FkkKY23StopctL2ztfyAczK%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;1621&quot; height=&quot;859&quot; data-origin-width=&quot;1621&quot; data-origin-height=&quot;859&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;hwpx의 파싱, 청킹, 임베딩&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hwpx파일은 hwp와 달리 파싱-청킹-임베딩의 과정이 모두 잘 이뤄졌다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;923&quot; data-origin-height=&quot;910&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzovp6/dJMcahpddky/yE6CgjOLECpArhLuf7upsk/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzovp6/dJMcahpddky/yE6CgjOLECpArhLuf7upsk/img.webp&quot; data-alt=&quot;도커 환경에서의 RAG 수행&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzovp6/dJMcahpddky/yE6CgjOLECpArhLuf7upsk/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbzovp6%2FdJMcahpddky%2FyE6CgjOLECpArhLuf7upsk%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;923&quot; height=&quot;910&quot; data-origin-width=&quot;923&quot; data-origin-height=&quot;910&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;도커 환경에서의 RAG 수행&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스폰스를 받았지만, 데이터가 없었다. &lt;span style=&quot;background-color: oklab(0.988044 0.0000450313 0.0000197887); color: oklab(0.322425 0.00154591 -0.010555); text-align: left;&quot;&gt;검색권한쪽 컨트롤이 필요한건지, 성능이슈인지 원인을 찾지 못했다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;hwp RAG 구현 in 도커&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklab(0.988044 0.0000450313 0.0000197887); color: oklab(0.322425 0.00154591 -0.010555); text-align: left;&quot;&gt;hwp 파일은 청킹-임베딩-저장이 되지 않던 문제가 있었다. (jar, jpype, JAVA_HOME으로 고통받던 지난 주가 생각났다...)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: oklab(0.988044 0.0000450313 0.0000197887); color: oklab(0.322425 0.00154591 -0.010555); text-align: left;&quot;&gt;이전 응답에서 text_content가 분할되지 않은 것에 주목했다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1639&quot; data-origin-height=&quot;859&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bo2gp9/dJMcadtzYq1/rhMCOnzWVZuGuXj7G9HSyK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bo2gp9/dJMcadtzYq1/rhMCOnzWVZuGuXj7G9HSyK/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bo2gp9/dJMcadtzYq1/rhMCOnzWVZuGuXj7G9HSyK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbo2gp9%2FdJMcadtzYq1%2FrhMCOnzWVZuGuXj7G9HSyK%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;1639&quot; height=&quot;859&quot; data-origin-width=&quot;1639&quot; data-origin-height=&quot;859&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hwp는 단락(&lt;span style=&quot;background-color: oklab(0.988044 0.0000450313 0.0000197887); color: oklab(0.322425 0.00154591 -0.010555); text-align: left;&quot;&gt;para&lt;/span&gt;)생성이 안되던 문제가 있었는데, 이를 해결하기위해 문단 보정을 해주기로 했다.&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; _iter_paragraph_texts&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: oklab(0.988044 0.0000450313 0.0000197887); color: oklab(0.322425 0.00154591 -0.010555); text-align: left;&quot;&gt;라는 유틸 함수를 &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;background-color: #dddddd; color: #000000;&quot;&gt;app/services/rag/rag_service.py&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: oklab(0.988044 0.0000450313 0.0000197887); color: oklab(0.322425 0.00154591 -0.010555); text-align: left;&quot;&gt;에다 넣어 구현했다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hlotj/dJMcaelIFR9/KMtJYMGBQI9e2QZ1dSses1/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hlotj/dJMcaelIFR9/KMtJYMGBQI9e2QZ1dSses1/img.webp&quot; data-origin-width=&quot;616&quot; data-origin-height=&quot;840&quot; data-is-animation=&quot;false&quot; style=&quot;width: 27.7861%; margin-right: 10px;&quot; data-widthpercent=&quot;28.11&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hlotj/dJMcaelIFR9/KMtJYMGBQI9e2QZ1dSses1/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHlotj%2FdJMcaelIFR9%2FKMtJYMGBQI9e2QZ1dSses1%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;616&quot; height=&quot;840&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6chWD/dJMcajN6feS/hwXPalz0kyVNPi9bwFs3Q0/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6chWD/dJMcajN6feS/hwXPalz0kyVNPi9bwFs3Q0/img.webp&quot; data-origin-width=&quot;1277&quot; data-origin-height=&quot;681&quot; data-is-animation=&quot;false&quot; style=&quot;width: 71.0511%;&quot; data-widthpercent=&quot;71.89&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6chWD/dJMcajN6feS/hwXPalz0kyVNPi9bwFs3Q0/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6chWD%2FdJMcajN6feS%2FhwXPalz0kyVNPi9bwFs3Q0%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;1277&quot; height=&quot;681&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;RAG 디버그&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;샤딩 검색이 제대로 안되어서, 디버그를 추가해보았다. 문제는 config 안에 있었다. 컨피그에 샤딩검색을 &lt;span style=&quot;background-color: oklab(0.988044 0.0000450313 0.0000197887); color: oklab(0.322425 0.00154591 -0.010555); text-align: left;&quot;&gt;False로 두었던 것을, True로 바꾸었다. 이제 req.body에 user_id를 포함해 전달하면, 해당 폴더의 FAISS 벡터 스토어로부터 검색이 이뤄지고, LLM이 응답을 생성한다.&lt;/span&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 style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;본 후기는 [&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;한글과컴퓨터&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;x한국생산성본부x스나이퍼팩토리]&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;한컴&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;AI 아카데미 2기 (&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;B&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;-&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;log&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;) 리뷰&lt;/span&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;로 작성 되었습니다&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;</description>
      <category>한컴AI 2기</category>
      <category>ai개발자</category>
      <category>ai개발자교육</category>
      <category>ai전문가양성</category>
      <category>개발자교육</category>
      <category>개발자취업</category>
      <category>부트캠프</category>
      <category>스나이퍼팩토리</category>
      <category>한국생산성본부</category>
      <category>한글과컴퓨터</category>
      <category>한컴ai아카데미2기</category>
      <author>싱커</author>
      <guid isPermaLink="true">https://excode.tistory.com/30</guid>
      <comments>https://excode.tistory.com/30#entry30comment</comments>
      <pubDate>Mon, 10 Nov 2025 17:28:24 +0900</pubDate>
    </item>
    <item>
      <title>[스나이퍼팩토리] 한컴AI 2기 - OWPML 프로젝트 알랭 1주차 회고</title>
      <link>https://excode.tistory.com/29</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 2팀 '뉴런웨이'의 1주차가 정신없이 지나갔다. &lt;span&gt;우리 팀의 목표는 OWPML 기반의 문서 Q&amp;amp;A 서비스인 &lt;b&gt;알랭(AlAIn)&lt;/b&gt;을 개발하는 것. &lt;/span&gt;&lt;span&gt;이번 1주차의 핵심 목표는 인프라, 백엔드, 프론트엔드의 완전한 통합 개발 환경을 구축하고, 가장 핵심적인 문서 파싱 기능을 구현하는 것이었다. &lt;/span&gt; 돌아보니, 단순한 '환경 설정'을 넘어 프로젝트의 뼈대 전체를 세운 한 주였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. INFRA: 견고한 개발 생태계 구축기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 성패는 팀원 모두가 동일하고 안정적인 환경에서 개발할 수 있느냐에 달려있다고 생각한다. 우리는 1주차에 이 '개발 생태계'를 구축하는 데 총력을 기울였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;A. 협업 도구 통일&lt;/b&gt; 우선 파편화된 소통을 막기 위해 협업 도구를 명확히 정했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;Notion:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 모든 프로젝트 관리의 허브로 사용한다. &lt;/span&gt;&lt;span&gt;주차별 과제 관리, 개인별 회고록 작성, 회의록 아카이빙, 전체 일정표를 노션에서 관리한다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;856&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dchx8/dJMcahirsIE/EE89wQ9rKNhwNKDkGN9gBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dchx8/dJMcahirsIE/EE89wQ9rKNhwNKDkGN9gBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dchx8/dJMcahirsIE/EE89wQ9rKNhwNKDkGN9gBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDchx8%2FdJMcahirsIE%2FEE89wQ9rKNhwNKDkGN9gBK%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;631&quot; height=&quot;856&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;856&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;Discord:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 실시간 토론 및 자료 공유 채널로 활용한다. &lt;/span&gt;&lt;span&gt;특히 '임베딩 모델'이나 'HWPX 파싱' 같은 특정 주제에 대해 스레드를 열어 깊이 있는 논의를 진행했다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;856&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBiwr5/dJMcai9uvMU/pHt65FB4FCua6aea8JuuQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBiwr5/dJMcai9uvMU/pHt65FB4FCua6aea8JuuQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBiwr5/dJMcai9uvMU/pHt65FB4FCua6aea8JuuQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBiwr5%2FdJMcai9uvMU%2FpHt65FB4FCua6aea8JuuQK%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;856&quot; data-origin-width=&quot;1211&quot; data-origin-height=&quot;856&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;GitHub:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 백엔드와 프론트엔드의 형상 관리를 담당한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;863&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6HcAm/dJMcadAlrdk/XU5VVNriypP6SvTlbovBr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6HcAm/dJMcadAlrdk/XU5VVNriypP6SvTlbovBr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6HcAm/dJMcadAlrdk/XU5VVNriypP6SvTlbovBr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6HcAm%2FdJMcadAlrdk%2FXU5VVNriypP6SvTlbovBr1%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;922&quot; height=&quot;863&quot; data-origin-width=&quot;922&quot; data-origin-height=&quot;863&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Figma/Canva:&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; 프론트엔드 와이어프레임 및 발표 자료 디자인을 위해 세팅했다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;B. Docker 기반의 표준 개발 환경&lt;/b&gt; 이번 프로젝트에서 가장 공들인 부분이다. &lt;span&gt;&quot;로컬 Python 실행 금지&quot;라는 원칙을 세우고, 모든 개발 환경을 Docker 기반으로 통일했다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image02.png&quot; data-origin-width=&quot;1427&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd3MzI/dJMcaaKn3GK/xhTkxksTb3CnP8aVOiEa71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd3MzI/dJMcaaKn3GK/xhTkxksTb3CnP8aVOiEa71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd3MzI/dJMcaaKn3GK/xhTkxksTb3CnP8aVOiEa71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd3MzI%2FdJMcaaKn3GK%2FxhTkxksTb3CnP8aVOiEa71%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;1427&quot; height=&quot;814&quot; data-filename=&quot;image02.png&quot; data-origin-width=&quot;1427&quot; data-origin-height=&quot;814&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;Backend Stack:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 백엔드는 &lt;/span&gt;&lt;b&gt;&lt;span&gt;FastAPI&lt;/span&gt;&lt;/b&gt;&lt;span&gt;를 기반으로 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Java 연동 (JPype):&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; OWPML 문서(HWP, HWPX) 파싱을 위해 Java 라이브러리를 사용해야 했다. &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이를 위해 &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;python:3.11-slim&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;베이스 이미지에 &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;openjdk-17-jdk&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;를 설치하고 &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;JPype1&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; 패키지를 통해 Python과 Java를 연동하는 환경을 구축했다. &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;ENV JAVA_HOME&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt; 설정에 꽤 공을 들였다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;Docker Compose:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docker-compose.yml&lt;/span&gt;&lt;span&gt;을 통해 개발 환경을 정의했다. &lt;/span&gt;&lt;span&gt;특히 &lt;/span&gt;&lt;span&gt;cpu&lt;/span&gt;&lt;span&gt;와 &lt;/span&gt;&lt;span&gt;gpu&lt;/span&gt;&lt;span&gt; 프로파일을 분리하여 로컬 개발 환경(CPU)과 향후 서버 배포(GPU) 환경에 모두 대응할 수 있도록 설계했다. &lt;/span&gt;&lt;span&gt;또한 Hugging Face 모델 캐시(&lt;/span&gt;&lt;span&gt;hf_cache&lt;/span&gt;&lt;span&gt;&lt;span&gt;) 등을 볼륨으로 관리하여 불필요한 재다운로드를 방지했다.&lt;/span&gt;&lt;/span&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image01.png&quot; data-origin-width=&quot;1269&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmVT5y/dJMcajAySU5/8kMnTLTa30Bd7gUrnnOOT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmVT5y/dJMcajAySU5/8kMnTLTa30Bd7gUrnnOOT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmVT5y/dJMcajAySU5/8kMnTLTa30Bd7gUrnnOOT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdmVT5y%2FdJMcajAySU5%2F8kMnTLTa30Bd7gUrnnOOT0%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;1269&quot; height=&quot;924&quot; data-filename=&quot;image01.png&quot; data-origin-width=&quot;1269&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;C. 개발 지원을 위한 커스텀 GPT&lt;/b&gt; &lt;span&gt;팀원들의 개발 효율을 극대화하기 위해 'OWPML RAG 개발 어시스턴트'라는 이름의 커스텀 GPT를 구축했다. &lt;/span&gt;&lt;span&gt;이 GPT는 우리 프로젝트의 전체 코드베이스(Dockerfile, &lt;/span&gt;&lt;span&gt;jpype_setup.py&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;rag.py&lt;/span&gt;&lt;span&gt; 등)를 '지식'으로 학습했다. &lt;/span&gt;&lt;span&gt;덕분에 Dockerfile 설정이나 JPype 관련 문제 해결, FAISS와 LangChain을 사용한 RAG 파이프라인 설계 등 우리 코드에 최적화된 답변을 즉시 얻을 수 있게 되었다. &lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;859&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2BpRD/dJMcaiPbN5a/8QvFKniYHniR8sgxjkYfvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2BpRD/dJMcaiPbN5a/8QvFKniYHniR8sgxjkYfvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2BpRD/dJMcaiPbN5a/8QvFKniYHniR8sgxjkYfvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2BpRD%2FdJMcaiPbN5a%2F8QvFKniYHniR8sgxjkYfvK%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;838&quot; height=&quot;859&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;859&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. BACKEND: 핵심 기능의 첫걸음, 문서 파싱 API&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인프라가 튼튼하게 구축되었음을 확인한 것은 백엔드 API의 성공적인 동작이었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;Health Check:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;docker compose up&lt;/span&gt;&lt;span&gt; 명령어로 서버를 실행하고, FastAPI의 자동 문서(&lt;/span&gt;&lt;span&gt;http://localhost:8000/docs&lt;/span&gt;&lt;span&gt;)에 접속해 API가 정상적으로 동작하는 것을 확인했다. &lt;/span&gt;&lt;span&gt;/api/v1/health&lt;/span&gt;&lt;span&gt; 엔드포인트는 &lt;/span&gt;&lt;span&gt;{&quot;status&quot;: &quot;ok&quot;}&lt;/span&gt;&lt;span&gt;를 성공적으로 반환했다. &lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;HWP/HWPX 파싱 성공:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 1주차 핵심 목표였던 문서 파싱 API(&lt;/span&gt;&lt;span&gt;api/v1/parsing/upload_and_parse&lt;/span&gt;&lt;span&gt;)를 구현했다. &lt;/span&gt;&lt;span&gt;Swagger UI에서 HWP 파일과 HWPX 파일을 직접 업로드하여 테스트한 결과, 두 형식의 파일 모두에서 텍스트 콘텐츠를 성공적으로 추출하여 JSON 형태로 반환하는 것을 확인했다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1215&quot; data-origin-height=&quot;820&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckJRUY/dJMcaaXVhyg/cyusKN7PDo4KekNPfNePck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckJRUY/dJMcaaXVhyg/cyusKN7PDo4KekNPfNePck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckJRUY/dJMcaaXVhyg/cyusKN7PDo4KekNPfNePck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckJRUY%2FdJMcaaXVhyg%2FcyusKN7PDo4KekNPfNePck%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;1215&quot; height=&quot;820&quot; data-origin-width=&quot;1215&quot; data-origin-height=&quot;820&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;3. FRONTEND: 서비스의 얼굴을 그리다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드가 엔진을 만드는 동안, 프론트엔드에서는 서비스의 얼굴을 디자인했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;Figma 와이어프레임:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 'Alain'의 메인 채팅 페이지와 사이드바 구조를 Figma로 신속하게 설계했다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;Vercel 초기 배포:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 놀랍게도 1주차 만에 이 기본 구조를 Vercel을 통해 실제 웹에 배포까지 완료했다. &lt;/span&gt; 물론 아직 기능은 없지만, 눈에 보이는 결과물이 있다는 것은 팀의 사기를 높이는 데 큰 도움이 되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciu03l/dJMcafx94Uz/fe42APpZLO8KqoW33viocK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciu03l/dJMcafx94Uz/fe42APpZLO8KqoW33viocK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciu03l/dJMcafx94Uz/fe42APpZLO8KqoW33viocK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fciu03l%2FdJMcafx94Uz%2Ffe42APpZLO8KqoW33viocK%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;1180&quot; height=&quot;533&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1주차를 마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;첫 주였지만 인프라, 백엔드, 프론트엔드 전반에 걸쳐 예상보다 더 견고한 기반을 다졌다. &lt;/span&gt;&lt;span&gt;특히 Docker와 JPype라는 복잡한 기술 스택을 성공적으로 연동하고, HWP/HWPX라는 특수한 문서를 파싱하는 API까지 구현해낸 것은 큰 성과다. &lt;/span&gt; 이제 이 튼튼한 기반 위에 본격적으로 RAG 파이프라인을 얹을 2주차가 기대된다.&lt;/p&gt;</description>
      <category>한컴AI 2기</category>
      <category>ai개발자</category>
      <category>ai개발자교육</category>
      <category>ai전문가양성</category>
      <category>개발자교육</category>
      <category>개발자취업</category>
      <category>부트캠프</category>
      <category>스나이퍼팩토리</category>
      <category>한국생산성본부</category>
      <category>한글과컴퓨터</category>
      <category>한컴ai아카데미2기</category>
      <author>싱커</author>
      <guid isPermaLink="true">https://excode.tistory.com/29</guid>
      <comments>https://excode.tistory.com/29#entry29comment</comments>
      <pubDate>Sun, 9 Nov 2025 10:13:24 +0900</pubDate>
    </item>
    <item>
      <title>[스나이퍼팩토리] 한컴AI 2기 - OWPML 프로젝트 CLI와 백엔드 FastAPI 세팅</title>
      <link>https://excode.tistory.com/28</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;br&gt;처음 목표는 명확했다. 한글(HWP, HWPX) 문서를 AI가 이해하게 만들자.&lt;br&gt;그런데 막상 열어보니 구조가 너무 복잡했다.&lt;br&gt;텍스트는 있지만 의미 단위로 구분되지 않았고, 문단과 표, 각주가 한데 엉켜 있었다.&lt;br&gt;&lt;br&gt;OWPML(Open Word Processor Markup Language) 필터를 활용해&lt;br&gt;한글 문서를 XML 구조로 풀 수 있다면, AI가 이해할 수 있는 데이터로 바꿀 수 있을 것 같았다.&lt;br&gt;그 순간 “이걸 파싱해서 RAG 구조에 얹으면 되겠다”는 감이 왔다.&lt;br&gt;&lt;br&gt;&lt;br&gt;2️⃣ extract.py — 문서를 뜯어내는 과정&lt;br&gt;&lt;br&gt;HWPX 파일은 내부적으로 ZIP 형식이었다.&lt;br&gt;압축을 풀면 XML 파일들이 나오고, 그 안에 &amp;lt;hp:p&amp;gt;는 문단, &amp;lt;hp:tbl&amp;gt;은 표를 의미했다.&lt;br&gt;파이썬의 xml.etree.ElementTree로 태그를 읽고, 문단과 표를 각각 리스트로 정리했다.&lt;br&gt;이미지는 BinData 폴더에서 추출했다.&lt;br&gt;&lt;br&gt;이 과정을 자동화하기 위해 extract.py를 만들었다.&lt;br&gt;파일을 넣으면 자동으로 텍스트, 표, 이미지, 구조 정보를 모두 뽑아 JSON으로 저장하도록 했다.&lt;br&gt;결과를 처음 봤을 때는 꽤 감동적이었다.&lt;br&gt;&lt;br&gt;예시 데이터로 충주의료원 규정집을 파싱했는데, 문단이 1만 5천 개, 표가 400개가 넘었다.&lt;br&gt;그 방대한 데이터가 전부 구조화된 순간, “이제 이걸 AI가 읽을 수 있겠구나”라는 확신이 들었다.&lt;br&gt;&lt;br&gt;&lt;br&gt;3️⃣ RAG 구상 — 문서 이해의 뼈대를 세우다&lt;br&gt;&lt;br&gt;문서 구조를 추출했으니, 이제는 ‘이해’를 시켜야 했다.&lt;br&gt;핵심은 RAG(Retrieval-Augmented Generation)였다.&lt;br&gt;문단 단위로 청킹하고, 임베딩 후 벡터 검색을 수행해 GPT가 근거를 포함한 답변을 생성하도록 하는 구조다.&lt;br&gt;&lt;br&gt;문제는 표였다.&lt;br&gt;“이건 표입니다”라는 텍스트 형태로 들어가는데, 표 데이터와 결합해 전달할 방식이 필요하다.&lt;br&gt;문서의 형태적 구조가 답변의 품질을 결정한다는 걸 실감했다.&lt;br&gt;&lt;br&gt;4️⃣ FastAPI&lt;br&gt;&lt;br&gt;AI 모델을 실제 서비스처럼 다루려면 API가 필요했다.&lt;br&gt;그래서 FastAPI를 선택했다&lt;/p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/psm27/dJMcacuBUT7/NKpX8e6pruLcLpAfUtOKN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/psm27/dJMcacuBUT7/NKpX8e6pruLcLpAfUtOKN1/img.png&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;760&quot; style=&quot;width: 53.6691%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/psm27/dJMcacuBUT7/NKpX8e6pruLcLpAfUtOKN1/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpsm27%2FdJMcacuBUT7%2FNKpX8e6pruLcLpAfUtOKN1%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;759&quot; height=&quot;760&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QzI4Z/dJMcabboOEO/d5KFz946MhtpaeZWAvf8I0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QzI4Z/dJMcabboOEO/d5KFz946MhtpaeZWAvf8I0/img.png&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;884&quot; style=&quot;width: 45.1682%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QzI4Z/dJMcabboOEO/d5KFz946MhtpaeZWAvf8I0/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQzI4Z%2FdJMcabboOEO%2Fd5KFz946MhtpaeZWAvf8I0%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;743&quot; height=&quot;884&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추출함수를 모듈화하여 FastAPI에서 구동하여 API 라우트를 제공할 수 있기를 기대했다. jpype임포트에 문제가 있어서 난항을 겪었다. hwpx에는 jar파일이 필요하지 않아서, 그쪽에는 로컬에서도 문제가 없었다.&lt;/p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0oCL4/dJMb995IUgi/PF10kKBlCNWqVPWzfzprR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0oCL4/dJMb995IUgi/PF10kKBlCNWqVPWzfzprR0/img.png&quot; data-origin-width=&quot;633&quot; data-origin-height=&quot;743&quot; style=&quot;width: 38.0816%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0oCL4/dJMb995IUgi/PF10kKBlCNWqVPWzfzprR0/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0oCL4%2FdJMb995IUgi%2FPF10kKBlCNWqVPWzfzprR0%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;633&quot; height=&quot;743&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/by5C2K/dJMcajgcrQe/ktkkKMuFu6bgUl0hSeybM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/by5C2K/dJMcajgcrQe/ktkkKMuFu6bgUl0hSeybM1/img.png&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;657&quot; style=&quot;width: 60.7556%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/by5C2K/dJMcajgcrQe/ktkkKMuFu6bgUl0hSeybM1/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fby5C2K%2FdJMcajgcrQe%2FktkkKMuFu6bgUl0hSeybM1%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;893&quot; height=&quot;657&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 환경을 만들기 위해 도커 빌드가 필요했다.&lt;br&gt;&amp;nbsp;&lt;br&gt;docker&amp;nbsp;build&amp;nbsp;-t&amp;nbsp;owpml-fastapi-backend&lt;br&gt;의 형태로 명령어를 실행할 수&amp;nbsp;있었다.&lt;/p&gt;</description>
      <category>한컴AI 2기</category>
      <category>ai개발자</category>
      <category>ai개발자교육</category>
      <category>ai전문가양성</category>
      <category>개발자교육</category>
      <category>개발자취업</category>
      <category>부트캠프</category>
      <category>스나이퍼팩토리</category>
      <category>한국생산성본부</category>
      <category>한글과컴퓨터</category>
      <category>한컴ai아카데미2기</category>
      <author>싱커</author>
      <guid isPermaLink="true">https://excode.tistory.com/28</guid>
      <comments>https://excode.tistory.com/28#entry28comment</comments>
      <pubDate>Mon, 3 Nov 2025 08:47:43 +0900</pubDate>
    </item>
    <item>
      <title>[스나이퍼팩토리] 한컴AI 2기 - OWPML 프로젝트 0주차</title>
      <link>https://excode.tistory.com/26</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;OWPML필터 기반의 문서검색 서비스를 만들기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 역할은 팀장, PM, 백엔드 전담이다. 요구 기술스택에 대한 이해를 바탕으로, 전반적인 통합을 내가 이끌게 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성한 프로젝트 기획서는 아래와 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로젝트 기획서&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;1. 프로젝트 개요&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1.1. 기획 목적&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;OWPML 기반 문서 이해 파이프라인을 구축하여, 한글 문서(HWPX)의 내용을 구조적으로 분석하고 질의응답이 가능한 AI 문서 어시스턴트 「알랭(AlAin)」을 개발한다. &amp;lsquo;문서 이해 및 질의응답&amp;rsquo; 핵심 기능인 RAG 기반 검색&amp;middot;응답 시스템 개발을 지원하고, 문서 구조 인식, 벡터 임베딩, 프롬프트 엔지니어링을 통합한 완성형 프로토타입을 구현한다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1.2. 기획 배경&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;기업&amp;middot;기관에서 사용하는 다수의 공문서와 보고서가 HWPX 형식으로 저장되어 있으나, 이 형식은 일반 텍스트 검색이나 LLM 학습에 바로 활용하기 어렵다. 따라서, OWPML 필터를 활용해 문서의 구조적 정보를 보존하며 텍스트를 추출하고, 이를 기반으로 문서 내 질문에 정확한 근거와 함께 답변을 제시하는 한국어 문서 특화 RAG 시스템이 필요하다. 본 프로젝트는 한글 문서 파싱&amp;ndash;임베딩&amp;ndash;검색&amp;ndash;응답까지의 엔드투엔드 파이프라인을 구현하고, 사용자 입장에서는 문서를 업로드하면 내용을 이해하고 대답하는 웹 서비스로 경험할 수 있도록 프론트엔드&amp;ndash;백엔드 통합형 문서지능 서비스(AlAin)를 개발하기 위해 진행한다. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;1.3. 주요 타겟&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;HWPX 문서에서 필요한 정보를 빠르게 찾고자 하는 공공기관&amp;middot;기업 실무자 (예: 사업공고 검토자, 지침서 담당자, 내부문서 검색자) 문서 기반 AI 챗봇을 구축하려는 개발자 및 연구자 AlAin의 구조를 통해 국산 문서 포맷(HWPX) 기반 RAG 구현 사례를 학습하고 자체 문서 Q&amp;amp;A 시스템으로 확장하고자 하는 사용자 문서 이해 자동화에 관심 있는 일반 사용자 &amp;ldquo;문서를 올리면, 문서가 말해주는 AI&amp;rdquo; 경험을 통해 문서 이해의 새로운 방식 체험&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기능정의서 초안&lt;/h3&gt;
&lt;table id=&quot;28dd8844-b387-812d-9b8d-e6d72b3950b8&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr id=&quot;28dd8844-b387-8134-8c11-e78b2893839b&quot;&gt;
&lt;td id=&quot;JScU&quot;&gt;&lt;b&gt;구분&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;KMG|&quot;&gt;&lt;b&gt;필요 기능&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;y}mr&quot;&gt;&lt;b&gt;래퍼런스/상세설명&lt;/b&gt;&lt;/td&gt;
&lt;td id=&quot;t}Sk&quot;&gt;필요기술&lt;/td&gt;
&lt;td id=&quot;T_HU&quot;&gt;중요도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;28dd8844-b387-81d9-85aa-c422648dbc40&quot;&gt;
&lt;td id=&quot;JScU&quot;&gt;회원&lt;br /&gt;FE + BE&lt;/td&gt;
&lt;td id=&quot;KMG|&quot;&gt;이메일 로그인&lt;br /&gt;구글 로그인&lt;br /&gt;회원가입&lt;br /&gt;비밀번호 찾기&lt;/td&gt;
&lt;td id=&quot;y}mr&quot;&gt;MySQL 사용&lt;br /&gt;&amp;nbsp;&lt;br /&gt;회원가입: 이메일, 비밀번호, 닉네임&lt;/td&gt;
&lt;td id=&quot;t}Sk&quot;&gt;DBMS&lt;/td&gt;
&lt;td id=&quot;T_HU&quot;&gt;★☆&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;28dd8844-b387-8188-8c9e-ed865e3b93cf&quot;&gt;
&lt;td id=&quot;JScU&quot;&gt;관리자&lt;br /&gt;FE + BE&lt;/td&gt;
&lt;td id=&quot;KMG|&quot;&gt;문서 업로드 및 처리&lt;/td&gt;
&lt;td id=&quot;y}mr&quot;&gt;HWPX 파일 업로드 후 OWPML 파싱&lt;/td&gt;
&lt;td id=&quot;t}Sk&quot;&gt;Python, FastAPI&lt;/td&gt;
&lt;td id=&quot;T_HU&quot;&gt;★★★★&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;28dd8844-b387-817b-9e00-c187db4311cc&quot;&gt;
&lt;td id=&quot;JScU&quot;&gt;관리자&lt;br /&gt;FE + BE&lt;/td&gt;
&lt;td id=&quot;KMG|&quot;&gt;어드민 페이지 (대시보드, 문서관리, 프로젝트 관리 등)&lt;/td&gt;
&lt;td id=&quot;y}mr&quot;&gt;업로드된 문서 리스트 관리&lt;/td&gt;
&lt;td id=&quot;t}Sk&quot;&gt;FastAPI + Admin UI&lt;/td&gt;
&lt;td id=&quot;T_HU&quot;&gt;★★★&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;28dd8844-b387-81be-be0d-dc56722211e7&quot;&gt;
&lt;td id=&quot;JScU&quot;&gt;AI개발 + FE&lt;/td&gt;
&lt;td id=&quot;KMG|&quot;&gt;추천질문 기능&lt;/td&gt;
&lt;td id=&quot;y}mr&quot;&gt;추천 질문 생성 및 바로 실행&lt;/td&gt;
&lt;td id=&quot;t}Sk&quot;&gt;FastAPI + React&lt;/td&gt;
&lt;td id=&quot;T_HU&quot;&gt;★★&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;28dd8844-b387-8133-bd9d-c6217ed07659&quot;&gt;
&lt;td id=&quot;JScU&quot;&gt;AI개발 + FE&lt;/td&gt;
&lt;td id=&quot;KMG|&quot;&gt;근거 하이라이트&lt;/td&gt;
&lt;td id=&quot;y}mr&quot;&gt;원문 내 근거 문단 강조&lt;/td&gt;
&lt;td id=&quot;t}Sk&quot;&gt;React HighLNight.js&lt;/td&gt;
&lt;td id=&quot;T_HU&quot;&gt;★★★&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;28dd8844-b387-8122-b8d7-ec0d9934244a&quot;&gt;
&lt;td id=&quot;JScU&quot;&gt;AI개발 + FE&lt;/td&gt;
&lt;td id=&quot;KMG|&quot;&gt;질의응답&lt;/td&gt;
&lt;td id=&quot;y}mr&quot;&gt;문맥 기반 Q&amp;amp;A 생성 + 근거 표시&lt;/td&gt;
&lt;td id=&quot;t}Sk&quot;&gt;GPT API + RAG&lt;/td&gt;
&lt;td id=&quot;T_HU&quot;&gt;★★★★★&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;28dd8844-b387-81be-ae96-ee7067f550c9&quot;&gt;
&lt;td id=&quot;JScU&quot;&gt;AI개발&lt;/td&gt;
&lt;td id=&quot;KMG|&quot;&gt;텍스트&amp;middot;메타 추출&lt;/td&gt;
&lt;td id=&quot;y}mr&quot;&gt;문단&amp;middot;제목&amp;middot;섹션 태그 구조화&lt;/td&gt;
&lt;td id=&quot;t}Sk&quot;&gt;OWPML Parser&lt;/td&gt;
&lt;td id=&quot;T_HU&quot;&gt;★★★★&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;28dd8844-b387-81db-a1d8-ebd92715d8cf&quot;&gt;
&lt;td id=&quot;JScU&quot;&gt;AI개발&lt;/td&gt;
&lt;td id=&quot;KMG|&quot;&gt;청킹 &amp;amp; 임베딩&lt;/td&gt;
&lt;td id=&quot;y}mr&quot;&gt;문단 단위로 쪼개고 벡터화&lt;/td&gt;
&lt;td id=&quot;t}Sk&quot;&gt;bge-m3-korean, pgvector&lt;/td&gt;
&lt;td id=&quot;T_HU&quot;&gt;★★★★★&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;28dd8844-b387-817a-b2b2-e3cb7745215d&quot;&gt;
&lt;td id=&quot;JScU&quot;&gt;FE&lt;/td&gt;
&lt;td id=&quot;KMG|&quot;&gt;FAQ버튼&lt;/td&gt;
&lt;td id=&quot;y}mr&quot;&gt;주요 질문 바로 실행&lt;/td&gt;
&lt;td id=&quot;t}Sk&quot;&gt;React, JSON Config&lt;/td&gt;
&lt;td id=&quot;T_HU&quot;&gt;★&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 필수적인 기능들만 분류했고, 간단한 설명 및 요구 기술에 대해 작성했다. 또한 프로젝트 관점에서의 중요도 또한 병기했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;WBS - 간트차트&lt;/h3&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;1774&quot; data-origin-height=&quot;587&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nt1s7/dJMb9M3ZTjQ/Tm3jvHH5CQpbn0Z8pjuE0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nt1s7/dJMb9M3ZTjQ/Tm3jvHH5CQpbn0Z8pjuE0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nt1s7/dJMb9M3ZTjQ/Tm3jvHH5CQpbn0Z8pjuE0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnt1s7%2FdJMb9M3ZTjQ%2FTm3jvHH5CQpbn0Z8pjuE0k%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;1774&quot; height=&quot;587&quot; data-origin-width=&quot;1774&quot; data-origin-height=&quot;587&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TASK별로 정, 부 담당자를 기입했고, 계획 시작일 및 예상 완료일을 기입했다.&amp;nbsp;놓친 내용들이 분명 있을 수 있어서, 일자별 백업을 해두고, 지속 업데이트 하기로 했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아키텍처 설계도&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dX9hyo/dJMb9OncCMd/EdiIPYLMPGcPXxk6ZVCHi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dX9hyo/dJMb9OncCMd/EdiIPYLMPGcPXxk6ZVCHi1/img.png&quot; data-origin-width=&quot;544&quot; data-origin-height=&quot;448&quot; data-is-animation=&quot;false&quot; style=&quot;width: 72.5917%; margin-right: 10px;&quot; data-widthpercent=&quot;73.45&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dX9hyo/dJMb9OncCMd/EdiIPYLMPGcPXxk6ZVCHi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdX9hyo%2FdJMb9OncCMd%2FEdiIPYLMPGcPXxk6ZVCHi1%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;544&quot; height=&quot;448&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bL5Mr5/dJMb9OU2wB9/KNopck1kc70S1RYFTneKZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bL5Mr5/dJMb9OU2wB9/KNopck1kc70S1RYFTneKZK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;943&quot; data-filename=&quot;Group 36.png&quot; width=&quot;338&quot; height=&quot;770&quot; style=&quot;width: 26.2455%;&quot; data-widthpercent=&quot;26.55&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bL5Mr5/dJMb9OU2wB9/KNopck1kc70S1RYFTneKZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbL5Mr5%2FdJMb9OU2wB9%2FKNopck1kc70S1RYFTneKZK%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;414&quot; height=&quot;943&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;최초버전 -&amp;gt; 이후버전&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초에는 깃허브 README.MD와 유사한 마크다운 느낌으로 작성했었는데, 강사님의 피드백을 받고 다이어그램 이미지로 변경하였다. 머메이드 다이어그램 또한 고려했으나, 최종적으로 피그마로 만들었다.&lt;/p&gt;</description>
      <category>한컴AI 2기</category>
      <category>ai개발자</category>
      <category>ai개발자교육</category>
      <category>ai전문가양성</category>
      <category>개발자교육</category>
      <category>개발자취업</category>
      <category>부트캠프</category>
      <category>스나이퍼팩토리</category>
      <category>한국생산성본부</category>
      <category>한글과컴퓨터</category>
      <category>한컴ai아카데미2기</category>
      <author>싱커</author>
      <guid isPermaLink="true">https://excode.tistory.com/26</guid>
      <comments>https://excode.tistory.com/26#entry26comment</comments>
      <pubDate>Mon, 27 Oct 2025 13:24:34 +0900</pubDate>
    </item>
  </channel>
</rss>