<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>tasker_dev.log</title>
        <link>https://velog.io/</link>
        <description>ML Engineer 🧠 | AI 모델 개발과 최적화 경험을 기록하며 성장하는 개발자 🚀 The light that burns twice as bright burns half as long ✨</description>
        <lastBuildDate>Mon, 04 May 2026 09:03:37 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>tasker_dev.log</title>
            <url>https://velog.velcdn.com/images/tasker_dev/profile/6d5baf12-e153-440d-af9e-ffa3385f21f3/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. tasker_dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/tasker_dev" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[LLM으로 Knowledge Graph 구축하기]]></title>
            <link>https://velog.io/@tasker_dev/LLM%EC%9C%BC%EB%A1%9C-Knowledge-Graph-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@tasker_dev/LLM%EC%9C%BC%EB%A1%9C-Knowledge-Graph-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 04 May 2026 09:03:37 GMT</pubDate>
            <description><![CDATA[<p>이 챕터의 thesis는 단순하다. <strong>LLM은 KG를 직접 생산하지 않는다. LLM은 KG를 만들기 위한 중간 산출물(metagraph)을 생산하고, 정규화·entity resolution을 거쳐야 비로소 KG가 된다.</strong></p>
<p>이전 편까지 vector RAG와 graph RAG의 구조적 차이, 그리고 chunk 기반 검색의 한계를 다뤘다면, 이번 글은 그 graph RAG의 입력이 되는 <strong>KG를 비정형 텍스트로부터 어떻게 구축하는가</strong>에 대한 실제 파이프라인을 다룬다. 사례는 록펠러 재단(RAC) 프로젝트로, 1939년에 타이핑된 Warren Weaver의 다이어리 10,000여 페이지를 KG로 변환하는 작업이다.</p>
<h2 id="1-비정형-아카이브를-kg로-만들-때-마주치는-문제">1. 비정형 아카이브를 KG로 만들 때 마주치는 문제</h2>
<p>KG 구축 파이프라인 설명에 앞서, 어떤 입력 조건에서 어떤 어려움이 발생하는지 먼저 정의한다. 이 문제들이 이후 등장하는 3-layer 설계와 entity resolution 전략의 동기다.</p>
<table>
<thead>
<tr>
<th>문제 유형</th>
<th>내용</th>
<th>함의</th>
</tr>
</thead>
<tbody><tr>
<td>Analog 문서</td>
<td>OCR 처리 필요 (Tesseract, Amazon, Microsoft 등)</td>
<td>OCR 오류로 인한 entity 표기 변형이 entity resolution 부담을 가중</td>
</tr>
<tr>
<td>Historical 문서</td>
<td>더 이상 연구되지 않는 분야가 다수 → 참조용 NER dictionary/knowledge base 부재</td>
<td>WikiData 등 외부 disambiguation 기반 약함</td>
</tr>
<tr>
<td>비표준 표기</td>
<td>&quot;S.&quot; for &quot;J. R. Smith&quot;, &quot;U.Cal.&quot; for &quot;University of California&quot; 같은 축약</td>
<td>off-the-shelf coreference 모델 실패. LLM이 implicit하게 NER+RE+resolution 수행 가능</td>
</tr>
<tr>
<td>Domain-specific entity</td>
<td>자연과학 분야의 연구 disciplines, treatments, diseases 등 — granularity가 제각각</td>
<td>전통 NER 모델 부재. custom ML 기반 NER + unsupervised entity resolution 필요</td>
</tr>
<tr>
<td>높은 relational complexity</td>
<td>한 페이지에 수십 개 relation 등장</td>
<td>RE schema 설계가 정확도와 직결</td>
</tr>
<tr>
<td>다중 source 매칭</td>
<td>diary와 board minutes를 하나의 KG로 연결 시 추가 정규화</td>
<td>본 챕터 범위 밖</td>
</tr>
</tbody></table>
<p>여기서 핵심 통찰은 &quot;LLM era 이전이라면 traditional ML 모델 구축에 막대한 자원이 들었을 작업이, 적절한 knowledge representation + LLM + prompt engineering 조합으로 해결 가능해졌다&quot;는 점이다.</p>
<h2 id="2-3-layer-graph-설계">2. 3-Layer Graph 설계</h2>
<p>문서를 KG로 직접 변환하는 single-shot 접근은 실패한다. 중간 표현이 필요하고, 책은 이를 세 layer로 분리한다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/b9ab5b1a-7e76-48f4-9ee3-8d4c3576ceef/image.png" alt=""></p>
<p>이 설계의 핵심은 <strong>metagraph layer를 중간 단계로 두는 것</strong>이다. LLM이 추출한 entity를 곧바로 최종 노드로 merge하지 않고, 일단 페이지에 link된 mention 단위로 저장한다. 이 구조가 주는 이점은 다음과 같다.</p>
<ul>
<li>각 entity mention의 <strong>출처 페이지를 보존</strong>한다. 시각화 플랫폼에서 &quot;이 entity는 어디서 추출되었는가&quot;를 즉시 보여줄 수 있어 explainability가 자연스럽게 확보된다.</li>
<li>entity resolution 로직을 수정하면 <strong>metagraph로부터 KG layer를 언제든 재생성</strong>할 수 있다. resolution 알고리즘은 한 번에 완성되지 않으며, 시행착오가 필수적이다.</li>
<li>페이지 간 분산된 entity 정보를 aggregate하면서도 원본 정보 손실이 없다.</li>
</ul>
<h2 id="3-normalization과-cleansing">3. Normalization과 Cleansing</h2>
<p>metagraph가 만들어지면 통계 분석으로 연결성 향상 기회를 찾는다. 대표적인 정규화 작업은 다음과 같다.</p>
<ul>
<li><strong>lowercasing</strong>: Occupation처럼 case가 의미 없는 entity는 소문자로 통일하면 동일 개념의 분산 저장을 막을 수 있다.</li>
<li><strong>token stripping</strong>: GPT는 prompt에서 분리하라고 지시해도 종종 person name에 title을 포함시킨다 (&quot;Dr. Eleanor Smith&quot;). title을 제거하지 않으면 동일 인물이 KG에 두 번 등장한다.</li>
</ul>
<p>핵심은 정규화 결과를 원본 <code>name</code> 속성을 덮어쓰지 않고 <code>name_normalized</code>라는 <strong>새 속성에 저장</strong>한다는 것이다. 원본은 trace 용으로 유지되고, KG layer에 link할 때만 정규화된 값을 쓴다.</p>
<pre><code class="language-cypher">// title 제거 예시 (의사코드 수준)
MATCH (e:Entity)
WHERE e.name STARTS WITH &quot;Dr. &quot; OR e.name STARTS WITH &quot;Prof. &quot;
SET e.name_normalized = trim(replace(replace(e.name, &quot;Dr. &quot;, &quot;&quot;), &quot;Prof. &quot;, &quot;&quot;))

// case 정규화
MATCH (e:Entity {class: &quot;Occupation&quot;})
SET e.name_normalized = toLower(e.name)</code></pre>
<h2 id="4-entity-resolution-같은-사람을-같은-노드로">4. Entity Resolution: 같은 사람을 같은 노드로</h2>
<p>Generative LLM은 traditional NER/RE 모델과 달리 prompt가 잘 설계되었다면 <strong>entity의 full clean form만 반환</strong>한다. 이는 implicit coreference resolution이 일어난다는 뜻이고, 후처리로서의 entity resolution 부담을 줄인다. 그러나 줄인다는 것이지 없앤다는 것이 아니다. 문서 간 resolution은 여전히 별도 작업이다. 한 다이어리에서 &quot;Eleanor Smith&quot;가 다른 다이어리의 &quot;E. Smith&quot;와 같은 사람인지를 판정해야 한다.</p>
<h3 id="41-string-similarity만으로-부족하다">4.1 String similarity만으로 부족하다</h3>
<p>이름 문자열이 비슷하다는 것만으로는 신뢰도가 낮다. 사람 이름은 first / middle / surname 구조를 갖고, middle name은 축약되거나 생략된다. surname만 같으면 false positive가 폭증하고, surname + first name 일부 조합 정도에서 신뢰도가 생긴다. 또한 도메인 stopword 처리가 중요하다. 많은 재단 명칭에 &quot;Foundation&quot;이 포함되어 있는데, 이를 무시하지 않으면 무관한 organization들이 모조리 SIMILAR로 묶인다.</p>
<h3 id="42-관계-그래프를-entity-resolution에-활용한다">4.2 관계 그래프를 entity resolution에 활용한다</h3>
<p>여기가 graph 기반 접근의 진가다. metagraph의 mention들은 이미 다른 mention들과 관계로 연결되어 있고, 이 관계 자체가 resolution 신호가 된다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/59977964-9c95-4d5b-afe3-7ef1d934dcdc/image.png" alt=""></p>
<p>세 mention이 같은 인물(노벨 물리학상 수상자 Ernest Lawrence)이라는 판정 근거는 다음 신호의 결합이다.</p>
<ul>
<li>이름 문자열 유사도 (rule 기반)</li>
<li>동일 organization (<code>WORKS_FOR</code> → University of California) 공유 → 3-hop 거리</li>
<li>의미적으로 연관된 occupation (<code>cyclotron</code> ↔ <code>100,000,000 to 200,000,000 volt cyclotron</code>) 공유</li>
</ul>
<p>특히 &quot;Ernest Orlando Lawrence&quot;와 단독 &quot;Lawrence&quot;는 6-hop 거리에 있다. <strong>관계형 DB라면 이런 traversal은 매우 비싸지만, graph DB에서는 자연스러운 query</strong>다.</p>
<h3 id="43-알고리즘-단계">4.3 알고리즘 단계</h3>
<p>resolution 파이프라인은 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/49ceacbe-372b-4593-b1fc-aad0de318c51/image.png" alt=""></p>
<p>추가 옵션으로 다음이 거론된다.</p>
<ul>
<li><strong>Community detection (Louvain 등)</strong>: TALKED_ABOUT, TALKED_WITH 같은 intellectual network에 community detection을 적용하면, 동명이인을 분리할 수 있다. 남극에서 해양 연구를 하는 John Doe와 cosmology를 연구하는 John Doe는 다른 community에 속할 가능성이 높다.</li>
<li><strong>Embedding 기반 의미적 유사도</strong>: 문자열이 전혀 닮지 않았지만 개념적으로 가까운 occupation들 (<code>fertility</code>와 <code>human ovulation</code>)은 GPT embedding으로 vector화 후 agglomerative clustering으로 묶을 수 있다. 이 부분은 vector approach와 graph approach가 만나는 지점이다.</li>
</ul>
<h2 id="5-완성된-kg로-무엇을-하는가-지적-네트워크-분석">5. 완성된 KG로 무엇을 하는가: 지적 네트워크 분석</h2>
<p>KG는 만드는 것 자체가 목적이 아니다. 주된 용도는 graph analytics다. 책에서 RAC 프로젝트가 보여주는 분석은 &quot;과학자들의 지적 네트워크&quot;를 추출하는 것이다. TALKED_ABOUT, TALKED_WITH, WORKS_WITH, STUDENT_OF 같은 관계로 구성된 subgraph에 Neo4j Graph Data Science library의 알고리즘을 적용한다.</p>
<table>
<thead>
<tr>
<th>분석 목표</th>
<th>알고리즘</th>
<th>해석</th>
</tr>
</thead>
<tbody><tr>
<td>Influencers</td>
<td>PageRank, out-degree</td>
<td>다른 사람의 연구를 추천하는 빈도가 높은 인물</td>
</tr>
<tr>
<td>Influencees</td>
<td>in-degree, eigenvector centrality</td>
<td>추천·언급의 주된 대상</td>
</tr>
<tr>
<td>Bridges</td>
<td>Betweenness centrality</td>
<td>분리된 community를 잇는 연결자</td>
</tr>
</tbody></table>
<p>betweenness centrality 시각화에서는 노드 크기가 통과하는 최단경로 수에 비례한다. 이 시각화에서 Niels Bohr나 Ernest Lawrence처럼 유명한 과학자가 부각되는 것은 예상대로지만, 덜 알려진 인물도 함께 드러난다. 이 &quot;덜 알려진 bridge&quot;가 도메인 분석가에게 새로운 가설의 출발점이 된다.</p>
<p>KG는 또한 더 좁은 질문에도 답한다. &quot;cyclotron 연구와 그 펀딩에 핵심 역할을 한 사람은 누구인가&quot; 같은 질문은 cyclotron을 연구한 인물에서 시작해 최대 2-hop 거리에 있는 인물로 path를 확장하면서 referral 패턴을 추적하는 query로 표현된다.</p>
<p>실용 시나리오 하나는 다음과 같다. project officer Warren Weaver가 자리를 떠나 후임이 들어온다. 후임은 Johns Hopkins와 Harvard 양쪽에 physics 도메인 연결이 있는 인물에게 비공식 자문을 받고 싶다. 이 질문은 KG의 영향력 네트워크에서 직접 답이 나온다. 게다가 TALKED_ABOUT relation에 sentiment 속성이 있다면, &quot;Bernal은 Dorothy Wrinch에 부정적 태도를 보인다, Irving Langmuir는 Bernal에 부정적 태도를 보인다&quot;처럼 정성적 정보까지 활용한 균형 잡힌 인터뷰 대상자 선정이 가능하다.</p>
<h2 id="6-한계와-트레이드오프">6. 한계와 트레이드오프</h2>
<p>LLM 기반 KG 구축은 마술이 아니다. 책 본문에서 명시적으로 또는 암묵적으로 드러나는 한계는 다음과 같다.</p>
<p><strong>1) RE 실패의 silent propagation.</strong> 책의 사례에서 어떤 인물이 cyclotron subgraph에 잘못 등장한 사건이 보고된다. 이는 LLM의 RE 단계 오류이며, 다운스트림 분석 결과를 오염시킨다. 분석가가 graph 내용을 검증·반려할 수 있는 <strong>feedback loop가 application에 내장되지 않으면, 잘못된 entity가 의사결정에 영향을 미친다.</strong></p>
<p><strong>2) Entity resolution은 보수적일수록 유실, 공격적일수록 오염된다.</strong> Irving Langmuir가 두 노드(Langmuir와 Irving Langmuir)로 남은 사례는 책에 직접 등장한다. 한 페이지에서 surname만 등장하면서 다른 어떤 관계도 함께 추출되지 않으면 resolution에 사용할 신호가 없어 두 mention이 분리된 채로 KG에 들어간다. resolution rule을 더 공격적으로 풀면 동명이인 false merge가 발생한다. 이 trade-off는 알고리즘 튜닝만으로 해소되지 않는다.</p>
<p><strong>3) Token limit과 multi-page 처리.</strong> LLM은 한 번에 처리할 수 있는 입력·출력 토큰이 제한된다. 다이어리가 수백 페이지일 때 단순 chunking은 entry 경계를 무시하고, 그러면 entity 간 관계가 끊긴다. RAC 프로젝트는 ChatGPT-3.5-Turbo로 entry boundary detection을 별도 단계로 두는 식으로 우회했지만, 이는 추가 LLM 호출 비용과 또 다른 오류 표면을 만든다.</p>
<p><strong>4) Schema 설계는 사후 변경 비용이 크다.</strong> RE schema가 너무 좁으면 유용한 관계를 놓치고, 너무 넓으면 noise가 KG를 채운다. metagraph layer 덕분에 KG layer는 재생성 가능하지만, schema 변경은 LLM 추출 단계까지 거슬러 올라가야 한다.</p>
<h2 id="7-실무-적용-고려사항">7. 실무 적용 고려사항</h2>
<p>위 한계를 인지한 상태에서 production-quality KG 시스템을 구축할 때, 책이 제시하는 next steps는 다음과 같이 압축된다.</p>
<ul>
<li><strong>Knowledge extraction 정확도 향상</strong>: prompt engineering 추가 iteration 또는 LLM fine-tuning. 어느 시점부터는 prompt 튜닝의 한계가 분명해지므로, 도메인 데이터로 fine-tuning이 비용 대비 효과가 더 클 수 있다.</li>
<li><strong>외부 knowledge base 연동</strong>: WikiData 등을 활용한 entity disambiguation은 historical 도메인이 아닌 현대 도메인에서 baseline resolution을 강력하게 끌어올린다.</li>
<li><strong>Occupation entity의 hierarchical resolution</strong>: <code>nuclear physics</code> ⊃ <code>isotopes</code> ⊃ <code>heavy nitrogen</code> 같은 granularity 차이를 단순 cluster로 처리하지 않고, embedding (SentenceBERT, GPT) + agglomerative hierarchical clustering으로 계층을 보존한다. 이렇게 해야 &quot;주제의 전체 역사&quot;를 query할 수 있다.</li>
<li><strong>Conversation 노드 도입</strong>: date, interviewer, interviewee, topic을 갖는 Conversation 노드를 만들면 follow-up chain 분석과 grant 연결이 가능해진다.</li>
<li><strong>Cross-source 통합</strong>: 다이어리(diaries)와 이사회 의사록(board of directors minutes)을 같은 KG에 통합하면 &quot;어떤 conversation이 어떤 grant로 이어졌는가&quot; 같은 질문에 답할 수 있다.</li>
</ul>
<h2 id="8-왜-llm에-직접-묻지-않고-kg를-만드는가">8. 왜 LLM에 직접 묻지 않고 KG를 만드는가</h2>
<p>이 챕터의 마지막 질문은 사실 시리즈 전체의 thesis를 압축한다. 답은 다섯 가지로 정리된다.</p>
<table>
<thead>
<tr>
<th>가치</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>Explainability</td>
<td>KG는 응답의 근거가 되는 노드·관계·원본 텍스트를 모두 추적 가능하게 한다. LLM 단독 응답은 &quot;왜 이 답인가&quot;를 답하지 못한다.</td>
</tr>
<tr>
<td>Demystification</td>
<td>LLM을 black box로 사용하지 않고, 정보 추출 단계에서만 LLM의 언어 이해 능력을 빌린다. 결과물(KG)은 검증 가능한 정형 데이터다.</td>
</tr>
<tr>
<td>Democratization</td>
<td>LLM 학습·fine-tuning은 비싸다. 한 번 LLM을 써서 KG를 만들고 이후엔 KG만 운용하는 방식은 비용을 분산시킨다.</td>
</tr>
<tr>
<td>Explorability</td>
<td>Graph 시각화는 사용자가 데이터를 새로운 각도에서 탐색·드릴다운하게 한다. 가설 생성 도구로 기능한다.</td>
</tr>
<tr>
<td>Advanced analytics</td>
<td>PageRank, community detection, centrality 등 graph algorithm은 LLM의 black box reasoning에 의존하지 않는 분석 경로를 제공한다.</td>
</tr>
</tbody></table>
<blockquote>
<p>결론: LLM은 KG의 직접 생산자가 아니라 raw text에서 entity와 relation을 뽑는 추출기이며, KG의 가치는 추출 이후의 정규화·resolution·graph analytics 단계에서 결정된다.</p>
</blockquote>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li>Knowledge Graphs and LLMs in Action. Manning, 2024 — 
Chapter 6: Building knowledge graphs with large language models</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[비정형 데이터에서 도메인 특화 지식 추출]]></title>
            <link>https://velog.io/@tasker_dev/%EB%B9%84%EC%A0%95%ED%98%95-%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%97%90%EC%84%9C-%EB%8F%84%EB%A9%94%EC%9D%B8-%ED%8A%B9%ED%99%94-%EC%A7%80%EC%8B%9D-%EC%B6%94%EC%B6%9C</link>
            <guid>https://velog.io/@tasker_dev/%EB%B9%84%EC%A0%95%ED%98%95-%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%97%90%EC%84%9C-%EB%8F%84%EB%A9%94%EC%9D%B8-%ED%8A%B9%ED%99%94-%EC%A7%80%EC%8B%9D-%EC%B6%94%EC%B6%9C</guid>
            <pubDate>Mon, 04 May 2026 08:04:58 GMT</pubDate>
            <description><![CDATA[<h2 id="1-비정형-데이터에서-kg로-두-개의-축">1. 비정형 데이터에서 KG로: 두 개의 축</h2>
<p>이전 챕터까지 다룬 KG는 테이블·관계형 DB·기존 knowledge base 같은 구조화된 데이터를 전제로 했다. 그러나 실무에서 마주하는 도메인 지식 대부분은 논문·보고서·아카이브 문서 같은 비정형 텍스트에 잠겨 있다. 이 챕터의 thesis는 다음과 같다. <strong>비정형 데이터에서 KG를 만드는 작업은 &quot;지식 표현(knowledge representation)&quot;과 &quot;지식 학습(knowledge learning)&quot;이라는 두 축의 결합이며, LLM은 후자의 비용 구조를 근본적으로 바꾼다.</strong></p>
<ul>
<li>Knowledge representation: 정보를 컴퓨터와 사람이 모두 자율적으로 접근 가능한 형태로 모델링하는 방법. 분산되고 고립된 정보를 정렬되고 연결된 형태로 만드는 것이 KG의 본질이다.</li>
<li>Knowledge learning: NLP·LLM 같은 프레임워크와 기술의 조합으로 비정형 문서에서 insight를 채굴하는 과정이다.</li>
</ul>
<p>전체 파이프라인은 digitization → NER → RE → custom knowledge extraction의 순서로 흐른다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/4603ae02-da93-4158-9ccd-80338f987deb/image.png" alt=""></p>
<p>NER과 RE 단계에 전통적 NLP 모델 또는 LLM이 투입된다. 강조된 두 노드가 이 챕터의 핵심 결정 지점이다.</p>
<h2 id="2-ner과-re-kg-구축의-최소-단위">2. NER과 RE: KG 구축의 최소 단위</h2>
<h3 id="21-named-entity-recognition">2.1 Named Entity Recognition</h3>
<p>NER은 raw text에서 named entity의 mention을 식별하고 사전 정의된 카테고리로 분류하는 ML classification system으로 정의된다. 다수의 open source NLP 라이브러리는 Person, Location, Organization 같은 generic 카테고리를 즉시 제공하지만, 실제 도메인 작업에서는 거의 충분하지 않다.</p>
<p>예를 들어 화학 연구 아카이브에서 &quot;Professor of Chemistry&quot;라는 직위, 특정 연구 분야명, 화학 물질명을 식별해야 한다면 generic NER 모델로는 부족하다. 단순한 dictionary 기반 시스템도 도메인 어휘 커버리지가 낮다. 따라서 custom entity-extraction system이 필요하며, 이는 custom NER 모델 학습 또는 LLM 활용 두 경로로 해결된다.</p>
<h3 id="22-relation-extraction">2.2 Relation Extraction</h3>
<p>RE는 텍스트 내 entity pair 사이의 semantic relation을 식별하는 작업이다. 다음 문장을 보자.</p>
<blockquote>
<p>&quot;Jane Austen, Victorian era writer, is currently employed by Google.&quot;</p>
</blockquote>
<p>이 문장은 PERSON(&quot;Jane Austen&quot;)과 ORGANIZATION(&quot;Google&quot;)이라는 두 named entity를 포함하며, 둘은 employment 관계로 묶인다. <strong>이 relatedness를 포착하는 것이 진짜 KG를 만드는 작업이다.</strong> 단순히 entity 목록만 추출하면 그래프가 아니라 리스트일 뿐이다.</p>
<h2 id="3-전통적-nlp에서-llm으로-패러다임-전환">3. 전통적 NLP에서 LLM으로: 패러다임 전환</h2>
<h3 id="31-모델-중심에서-데이터-중심으로">3.1 모델 중심에서 데이터 중심으로</h3>
<p>전통적 NLP는 task-specific training dataset 구축, 최적 model architecture 탐색, hyperparameter fine-tuning 같은 model-centric 접근을 취해왔다. 이 접근의 극단적 형태는 데이터 품질 문제를 무시하는데, faulty data를 넣으면 faulty prediction이 나온다는 단순한 사실을 간과한다.</p>
<p>시간이 지나면서 data-centric paradigm이 주목받기 시작했다. 고복잡도 ML 모델을 학습시키기 위한 데이터의 품질과 양을 개선하는 데이터 엔지니어링이 핵심으로 자리 잡았다.</p>
<h3 id="32-llm의-등장이-바꾼-것">3.2 LLM의 등장이 바꾼 것</h3>
<p>2022년 말 OpenAI의 GPT-3.5 시리즈와 ChatGPT 공개 이후, Transformer 아키텍처 기반 generative model이 NLP 작업의 표준 옵션이 되었다. LLM의 핵심 가치는 다음 두 가지다.</p>
<ul>
<li><strong>Transfer learning</strong>: 방대한 데이터셋으로 학습된 weight를 재사용하므로, 동일한 정확도를 더 적은 human-labeled data로 달성한다. 모델을 from scratch로 학습할 필요가 없다.</li>
<li><strong>Prompt engineering으로 충분</strong>: 자연어로 task를 기술(prompt engineering)하면 모델이 답을 생성한다. Model engineering이 필요 없다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/9d57e6c2-8779-4ee5-ab15-c14f7c090882/image.png" alt=""></p>
<h3 id="33-llm의-한계-hallucination">3.3 LLM의 한계: hallucination</h3>
<p>LLM의 강력함에도 시스템 설계 시 반드시 고려할 한계가 있다. 가장 중요한 것은 <strong>hallucination</strong>, 즉 학습 데이터에 근거가 없는 경우에도 &quot;사실&quot;을 만들어내고 잘못된 추론을 수행하는 경향이다. 이 때문에 LLM을 KG 구축 도구로 사용할 때는 LLM이 직접 사실을 단정하게 하기보다, 고품질의 named entity·관계·속성을 추출하도록 유도하는 방식이 권장된다.</p>
<h2 id="4-llm-기반-kg-구축-워크플로우">4. LLM 기반 KG 구축 워크플로우</h2>
<p>LLM을 활용한 KG 구축은 prompt-based inference로 즉시 시작하거나, 도메인 특화 정확도를 위해 fine-tuning으로 확장할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/dd00c02a-6b61-42f5-8456-d16ef6f8c112/image.png" alt=""></p>
<p>각 단계의 역할은 다음과 같다.</p>
<ul>
<li><strong>Data exploration</strong>: 도메인의 복잡도와 합리적 기대치를 파악하는 초기 단계. 데이터 이해 없이는 의미 있는 prompt를 작성할 수 없다.</li>
<li><strong>Prompt engineering</strong>: 가장 효과적인 prompt를 찾기 위한 반복 과정. 단어 몇 개를 바꾸는 것만으로도 성능에 큰 차이가 발생한다.</li>
<li><strong>Pre-annotation + Fine-tuning</strong>: prompt-based 접근이 한계에 부딪히면 진입한다. 비용과 시간이 들지만, 도메인에 specialized된 더 안정적이고 정확한 모델을 얻는다.</li>
</ul>
<h2 id="5-prompt-engineering의-실제-세-번의-반복">5. Prompt Engineering의 실제: 세 번의 반복</h2>
<p>generic prompt(&quot;모든 entity와 관계를 식별하라&quot;)만으로 KG를 만들면 다음과 같은 문제가 드러난다.</p>
<ul>
<li><strong>공지칭 해결과 약어 복원의 자연스러운 처리</strong>: LLM의 generative 특성 덕분에 coreference resolution이 implicit하게 수행된다. 예를 들어 &quot;Prof. Chem.&quot;이나 &quot;Assoc. in Chem.&quot; 같은 축약 직위가 &quot;Professor of Chemistry&quot;, &quot;Associate in Chemistry&quot;로 복원된다. 전통적 NER 모델이 할 수 없는 작업이다.</li>
<li><strong>과도한 관계 세분화</strong>: specializes in, is measuring, is interested in, demonstrates처럼 모두 정확하지만 KG 관점에서 같은 개념을 표현하는 관계들이 분리된다.</li>
<li><strong>비일관된 노드 라벨</strong>: 같은 단락의 네 가지 연구 주제에 field, research, theory 세 가지 다른 라벨이 부여된다.</li>
<li><strong>예측 불안정성</strong>: 동일한 prompt를 동일 텍스트에 반복 실행해도 결과가 달라진다.</li>
</ul>
<p>이러한 문제는 prompt를 점진적으로 정교화하면서 해결된다.</p>
<table>
<thead>
<tr>
<th>버전</th>
<th>전략</th>
<th>결과</th>
</tr>
</thead>
<tbody><tr>
<td>v1: 일반 지시</td>
<td>&quot;모든 entity와 관계를 식별하라&quot;</td>
<td>공지칭/약어 처리는 우수, 라벨 비일관·과도 세분화·불안정</td>
</tr>
<tr>
<td>v2: 목록 추가</td>
<td>entity class 목록과 relation type 목록을 prompt에 포함, 성능이 낮은 관계 유형에 설명 추가, 도메인 노트 2개 추가 (이니셜로 사람을 지칭하는 관행, 대학·학과명 별칭)</td>
<td>정규화 향상, 일부 안정성 확보</td>
</tr>
<tr>
<td>v3: 풍부한 예시</td>
<td>예시를 확장해 명확한 가이드라인 제공</td>
<td>entity·relation class 부여의 높은 안정성, 관심 관계 모두 정확히 식별</td>
</tr>
</tbody></table>
<p>세 번째 버전에서 원하는 모든 결과를 달성한다. 핵심 교훈은 모델 자체의 선택보다 <strong>근본 원리</strong>, 즉 task 명확성·도메인 노트·다양한 예시가 더 결정적이라는 점이다. 이 챕터의 prompt는 ChatGPT 계열 모델용으로 설계되어 GPT-4o mini에서 테스트되었으나, 새로운 모델이 등장해도 동일한 원칙이 적용된다.</p>
<h3 id="prompt-설계-일반-원칙">Prompt 설계 일반 원칙</h3>
<table>
<thead>
<tr>
<th>원칙</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>Task description</td>
<td>모호성을 제거할 만큼 정확하게 task를 기술. 모델이 답을 &quot;지나치게 창의적으로&quot; 만들 여지를 줄임</td>
</tr>
<tr>
<td>Naming entity/relation classes</td>
<td>관심 있는 entity·relation class 목록을 prompt에 포함. 작업을 명료화하고 정규화된 출력 유도</td>
</tr>
<tr>
<td>Complex representative examples</td>
<td>입력과 기대 출력 예시를 prompt에 포함. 실제 텍스트의 압축본에 중요한 entity·relation을 담는 형태</td>
</tr>
<tr>
<td>LLM configuration</td>
<td>temperature는 낮을수록 deterministic, 높을수록 variation·hallucination 증가</td>
</tr>
<tr>
<td>Prediction stability</td>
<td>LLM은 generative이므로 동일 prompt에서도 결과가 다를 수 있음. 모호하지 않은 prompt + 낮은 temperature로 통제</td>
</tr>
<tr>
<td>Unit-testing</td>
<td>prompt 업데이트마다 텍스트와 기대 출력을 테스트 리스트에 추가. 주기적으로 일괄 실행해 성공률 측정</td>
</tr>
<tr>
<td>Eyeballing a mini-KG</td>
<td>prompt milestone마다 수십 페이지 분량으로 KG를 만들어 직접 탐색. 정성적 검증 단계</td>
</tr>
<tr>
<td>Evaluation</td>
<td>만족스러운 prompt에 도달하면 정량 평가. QA 담당자가 correct/incorrect/missing으로 라벨링</td>
</tr>
</tbody></table>
<h2 id="6-전통적-nlp-vs-llm-선택의-기준">6. 전통적 NLP vs LLM: 선택의 기준</h2>
<p>LLM이 모든 상황에서 우월한 것은 아니다. 도메인·비용·운영 제약에 따라 선택이 달라진다.</p>
<table>
<thead>
<tr>
<th>기준</th>
<th>전통적 NLP</th>
<th>LLM</th>
</tr>
</thead>
<tbody><tr>
<td>예측 속도</td>
<td>빠름 (CPU 가능)</td>
<td>느림 (GPU 필수)</td>
</tr>
<tr>
<td>인프라 비용</td>
<td>단순·저렴</td>
<td>복잡·고비용</td>
</tr>
<tr>
<td>예측 단위 비용</td>
<td>매우 낮음</td>
<td>데이터셋 규모에 따라 가변</td>
</tr>
<tr>
<td>보안·on-premise</td>
<td>격리 배포 용이</td>
<td>GPU cluster 자체 운영 부담</td>
</tr>
<tr>
<td>초기 도메인 커스터마이징</td>
<td>높은 초기 투자 (전문 인력, annotation)</td>
<td>Prompt engineering으로 빠른 진입</td>
</tr>
<tr>
<td>학습 곡선</td>
<td>가파름 (data scientist 필수)</td>
<td>완만 (최소 기술로 prompt engineering 가능)</td>
</tr>
<tr>
<td>파이프라인</td>
<td>NER · RE · coreference · entity resolution 등 다단계 체이닝</td>
<td>One-pass 처리</td>
</tr>
<tr>
<td>출력 품질</td>
<td>후처리 (cleansing, normalization) 필요량 많음</td>
<td>생성적 특성으로 후처리 부담 적음</td>
</tr>
<tr>
<td>Fine-tuning 비용</td>
<td>해당 없음 (모델별 학습)</td>
<td>prompt로 부족할 때 추가 비용 발생 (OpenAI 기준 custom 모델 추론은 약 10배)</td>
</tr>
</tbody></table>
<p>선택은 이분법적이지 않다. 두 접근은 공존 가능하며, LLM을 활용해 전통적 NLP 모델 학습용 데이터를 빠르게 pre-annotation하는 하이브리드 전략도 유효하다.</p>
<h2 id="7-한계와-트레이드오프">7. 한계와 트레이드오프</h2>
<p>LLM 기반 KG 구축이 실무에서 직면하는 한계는 다음 세 가지로 정리된다.</p>
<ul>
<li><strong>Hallucination 리스크</strong>: LLM이 근거 없는 entity나 관계를 생성할 수 있다. KG는 downstream 추론에 사용되므로, 잘못된 사실 하나가 multi-hop traversal을 통해 확산될 위험이 있다. 따라서 LLM 출력을 그대로 신뢰하기보다 high-quality entity·relation·property 추출에 활용하고, 사실 단정은 원본 문서나 신뢰 가능한 source로 우회하는 설계가 안전하다.</li>
<li><strong>출력 안정성과 정규화 비용의 trade-off</strong>: temperature를 낮추면 안정성은 확보되지만, 미묘한 도메인 변형에 대한 적응력이 떨어진다. 반대로 prompt를 정교화해 entity/relation class를 강하게 명시하면 안정성은 높아지나 prompt 자체가 길어져 token 비용과 유지보수 부담이 늘어난다. 제3의 옵션인 fine-tuning은 이 trade-off를 우회하지만 초기 비용을 다시 부과한다.</li>
<li><strong>대규모 데이터셋에서의 비용 역전</strong>: 소~중규모 데이터셋에서는 LLM이 전통적 NLP 파이프라인 대비 초기 투자가 적어 유리하지만, 데이터 규모가 커질수록 GPU 추론 비용이 누적되어 경제성이 역전될 수 있다. streaming 시나리오나 실시간 추론 요구가 있는 환경에서는 더욱 그렇다.</li>
</ul>
<h2 id="8-실무-적용-시-고려사항">8. 실무 적용 시 고려사항</h2>
<p>LLM으로 KG를 구축할 때 production 단계에서 자주 간과되는 지점이 있다. 첫째, prompt engineering은 코드와 동일하게 버전 관리되어야 한다. 단어 하나의 변경이 출력 품질에 비대칭적 영향을 미치므로, prompt와 기대 출력을 unit test 형태로 묶고 회귀 테스트를 자동화하는 것이 안전하다. 둘째, eyeballing a mini-KG 단계를 생략하지 않아야 한다. 정량 지표만으로는 잡히지 않는 노드 라벨 비일관성·관계 세분화 문제가 그래프 시각화에서 즉시 드러난다. KG는 결국 그래프로 사용되므로, 그래프 형태로 점검하지 않으면 downstream에서야 문제가 발견된다.</p>
<p>셋째, 보안 요구가 강한 도메인이라면 LLM 직접 사용 대신 <strong>LLM을 데이터 어노테이션 가속기로 활용하는 우회 경로</strong>를 검토한다. LLM으로 pre-annotation한 데이터를 사람이 검수해 전통적 NLP 모델 학습에 사용하면, on-premise 배포 가능한 모델과 LLM의 transfer learning 이점을 동시에 얻을 수 있다. Hyperscaler(AWS, Azure, GCP)를 통한 LLM 호출이 가능한 환경이라면 일부 인프라 부담이 완화된다.</p>
<blockquote>
<p>결론: 비정형 텍스트에서 KG를 구축하는 작업의 본질은 모델 선택이 아니라 task 정의의 명확성이다. LLM은 진입 비용을 낮췄지만, 도메인 클래스 목록·정밀한 task 기술·대표 예시라는 세 축은 여전히 사람의 몫으로 남는다.</p>
</blockquote>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li>Knowledge Graphs and LLMs in Action. Manning, 2024 — 
Chapter 5: Extracting domain-specific knowledge from unstructured data</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[단순 네트워크에서 다중 소스 통합으로]]></title>
            <link>https://velog.io/@tasker_dev/%EB%8B%A8%EC%88%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC%EC%97%90%EC%84%9C-%EB%8B%A4%EC%A4%91-%EC%86%8C%EC%8A%A4-%ED%86%B5%ED%95%A9%EC%9C%BC%EB%A1%9C</link>
            <guid>https://velog.io/@tasker_dev/%EB%8B%A8%EC%88%9C-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC%EC%97%90%EC%84%9C-%EB%8B%A4%EC%A4%91-%EC%86%8C%EC%8A%A4-%ED%86%B5%ED%95%A9%EC%9C%BC%EB%A1%9C</guid>
            <pubDate>Sun, 03 May 2026 07:25:07 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>단일 출처 그래프는 정보를 <strong>저장</strong>하지만, 다중 소스 통합 KG는 정보를 <strong>연결</strong>한다.</p>
</blockquote>
<h2 id="1-챕터의-위치와-thesis">1. 챕터의 위치와 thesis</h2>
<p>이전까지의 논의가 단일 도메인·단일 출처에서 knowledge graph(KG)를 구축하는 방법에 머물렀다면, 이 챕터는 이질적인 데이터셋을 하나의 동질 그래프(homogeneous graph)로 통합하고, 그 위에서 LLM 기반 intelligent advisor system(IAS)을 운용하기 위한 분석 기법을 다룬다. 핵심 thesis는 다음과 같다. <strong>다중 소스 KG의 가치는 그래프를 키우는 데 있지 않고, 서로 다른 출처의 entity와 relationship을 일관된 의미 단위로 정렬(align)하는 데 있다.</strong></p>
<p>이전 편에서 vector retrieval이 chunk 간 유사도에 머무는 한계를 보였다면, multisource KG는 출처 간 cross-reference를 명시적 edge로 보존한다는 점에서 본질적으로 다르다. 즉, retrieval의 단위가 &quot;비슷한 텍스트&quot;에서 &quot;검증된 관계&quot;로 옮겨간다.</p>
<h2 id="2-다중-소스-통합-파이프라인">2. 다중 소스 통합 파이프라인</h2>
<p>구조화·반구조화 데이터를 하나의 그래프로 합치는 과정은 단순 merge가 아니라 다단계 파이프라인이다. 이 파이프라인의 각 단계는 직렬로 의존하므로, 어느 한 단계의 품질 저하가 후속 분석 전체로 전파된다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/2da0f60c-095d-41e3-98c4-00a389e21b99/image.png" alt=""></p>
<p>각 단계의 역할은 다음과 같이 정리된다.</p>
<table>
<thead>
<tr>
<th>단계</th>
<th>입력</th>
<th>핵심 작업</th>
<th>실패 시 영향</th>
</tr>
</thead>
<tbody><tr>
<td>Schema alignment</td>
<td>이질적 스키마</td>
<td>속성·타입·관계 명명 일치화</td>
<td>동일 개념이 별도 노드로 분리</td>
</tr>
<tr>
<td>Entity resolution</td>
<td>정렬된 레코드</td>
<td>동일 실체 식별·식별자 매칭</td>
<td>중복 entity, 단절된 subgraph</td>
</tr>
<tr>
<td>Quality validation</td>
<td>병합 후보</td>
<td>결측·충돌·일관성 검증</td>
<td>잘못된 edge가 분석 결과 왜곡</td>
</tr>
<tr>
<td>Post-processing</td>
<td>검증 통과 그래프</td>
<td>entity/relationship 병합</td>
<td>그래프 밀도·지표 신뢰도 저하</td>
</tr>
</tbody></table>
<p>entity resolution은 단순 문자열 매칭을 넘어 식별자(identifier) 수준의 reconciliation을 요구한다. 같은 단백질이 데이터셋마다 다른 ID로 등록되어 있는 경우, ID 매핑 테이블 없이는 두 노드가 별개로 남는다.</p>
<h2 id="3-분석-기법-클러스터링과-subgraph-지표">3. 분석 기법: 클러스터링과 subgraph 지표</h2>
<p>통합된 KG의 품질은 시각 점검만으로 판단되지 않는다. 정량적 분석 지표가 그래프의 구조와 완성도를 평가하는 도구가 된다.</p>
<h3 id="31-클러스터링-알고리즘">3.1 클러스터링 알고리즘</h3>
<p>전역 구조와 커뮤니티 조직을 파악하기 위해 두 가지 알고리즘이 사용된다.</p>
<table>
<thead>
<tr>
<th>알고리즘</th>
<th>목적</th>
<th>산출물</th>
<th>해석</th>
</tr>
</thead>
<tbody><tr>
<td>Weakly Connected Components (WCC)</td>
<td>연결 컴포넌트 식별</td>
<td>분리된 subgraph 집합</td>
<td>통합 누락·고립 영역 탐지</td>
</tr>
<tr>
<td>Louvain</td>
<td>모듈성 기반 community detection</td>
<td>밀집 군집 분할</td>
<td>도메인 내 기능 그룹화 발견</td>
</tr>
</tbody></table>
<p>WCC는 &quot;그래프가 얼마나 연결되어 있는가&quot;라는 거시적 질문에, Louvain은 &quot;연결된 그래프 내부에 어떤 응집 구조가 있는가&quot;라는 미시적 질문에 답한다. 두 결과는 상호보완적이며, 다중 소스 통합의 성공 여부를 판단하는 1차 지표 역할을 한다.</p>
<h3 id="32-subgraph-품질-지표">3.2 Subgraph 품질 지표</h3>
<pre><code class="language-python">def evaluate_subgraph(G, subgraph_nodes):
    sub = G.subgraph(subgraph_nodes)
    n = sub.number_of_nodes()
    m = sub.number_of_edges()

    # density: 밀도
    density = (2 * m) / (n * (n - 1)) if n &gt; 1 else 0

    # conductance: 외부와의 연결 비율
    boundary_edges = sum(
        1 for u, v in G.edges()
        if (u in subgraph_nodes) ^ (v in subgraph_nodes)
    )
    internal_edges = m
    conductance = boundary_edges / (2 * internal_edges + boundary_edges) \
                  if (internal_edges + boundary_edges) &gt; 0 else 0

    # 최대 연결 컴포넌트의 상대 크기
    largest_cc = max(nx.connected_components(sub), key=len, default=set())
    relative_lcc = len(largest_cc) / n if n &gt; 0 else 0

    return density, conductance, relative_lcc</code></pre>
<ul>
<li><strong>density</strong>: subgraph 내부 연결의 조밀함. 너무 낮으면 entity resolution 누락 가능성을 시사한다.</li>
<li><strong>conductance</strong>: subgraph가 외부와 얼마나 연결되어 있는지. 낮을수록 자기 완결적 community다.</li>
<li><strong>largest connected component의 상대 크기</strong>: 1에 가까울수록 단일 거대 연결 구조. 이 값이 작으면 그래프가 파편화되어 있다.</li>
</ul>
<h3 id="33-경로-기반-고급-지표-dwpc">3.3 경로 기반 고급 지표: DWPC</h3>
<p>Degree-Weighted Path Count(DWPC)는 단순 경로 개수 계산의 한계를 보완한다. 단순 path counting은 hub 노드(연결이 매우 많은 노드)를 거치는 경로를 과대평가한다. DWPC는 경로상의 각 노드 degree에 가중치를 부여해 hub 효과를 감쇠시킨다.</p>
<pre><code class="language-python">def dwpc(paths, degrees, w=0.4):
    # w: damping exponent. 일반적으로 0.3~0.5
    score = 0.0
    for path in paths:
        path_weight = 1.0
        for node in path[1:-1]:  # 중간 노드만 가중
            path_weight *= degrees[node] ** (-w)
        score += path_weight
    return score</code></pre>
<p><code>w</code> 값이 클수록 hub 페널티가 강해진다. 이 지표는 entity 간 관련성(relevance)을 평가할 때 단순 도달 가능성보다 훨씬 풍부한 신호를 제공한다.</p>
<h2 id="4-대표-kg-사례">4. 대표 KG 사례</h2>
<p>다중 소스 통합 기법의 실증 사례로 다음 KG들이 testbed 역할을 한다.</p>
<table>
<thead>
<tr>
<th>KG</th>
<th>도메인</th>
<th>통합 출처 성격</th>
</tr>
</thead>
<tbody><tr>
<td>Hetionet</td>
<td>생의학 (질병·약물·유전자)</td>
<td>29개 공개 데이터베이스 통합</td>
</tr>
<tr>
<td>PPI network</td>
<td>단백질 상호작용</td>
<td>실험·문헌 기반 상호작용</td>
</tr>
<tr>
<td>Clinical Knowledge Graph (CKG)</td>
<td>임상·프로테오믹스</td>
<td>환자 데이터·문헌·온톨로지</td>
</tr>
</tbody></table>
<p>이들 KG는 entity resolution, schema alignment, 분석 지표 적용을 모두 요구하는 복잡도를 가지므로 통합 기법의 검증 대상으로 적합하다.</p>
<h2 id="5-llm-기반-결과-해석">5. LLM 기반 결과 해석</h2>
<p>KG 분석은 정량 지표를 산출하지만, 그 자체로는 도메인 의미가 뚜렷하지 않다. density 0.12, conductance 0.31 같은 숫자가 어떤 임상적·연구적 함의를 갖는지는 별도의 해석 단계가 필요하다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/b1a4e579-0b40-49f9-a6e3-388096784e6c/image.png" alt=""></p>
<p>LLM은 이 단계에서 정량 지표를 도메인 가설로 번역하는 역할을 한다. &quot;이 community는 평균 대비 density가 높고 특정 단백질 cluster를 공유한다&quot;는 분석 결과를 &quot;공통 신호 경로의 후보군&quot;이라는 연구 가설로 변환하는 식이다.</p>
<h2 id="6-한계와-트레이드오프">6. 한계와 트레이드오프</h2>
<p>다중 소스 통합 KG는 표현력 측면에서 강력하지만, 다음 한계가 따른다.</p>
<ol>
<li><p><strong>Entity resolution의 정확도 천장</strong>: 동일 실체를 식별하는 작업은 본질적으로 휴리스틱·확률적 추론에 의존한다. 식별자가 없는 경우 문자열·맥락 기반 매칭이 사용되며, false merge(다른 실체를 합치는 오류)와 false split(같은 실체를 분리하는 오류)이 동시에 발생한다. 두 오류는 trade-off 관계에 있어 한쪽을 줄이면 다른 쪽이 늘어난다.</p>
</li>
<li><p><strong>지표의 도메인 의존성</strong>: density·conductance·DWPC 같은 지표는 그래프 구조의 통계적 속성일 뿐, 도메인 의미를 보장하지 않는다. 같은 density 값이 임상 KG와 소셜 네트워크에서 전혀 다른 의미를 갖는다. 지표 임계값을 일반화하려는 시도는 실패하기 쉽다.</p>
</li>
<li><p><strong>확장성 비용</strong>: 출처가 늘어날수록 schema alignment·entity resolution의 조합 폭발이 발생한다. n개 출처 통합 시 매핑 작업은 단순히 n에 비례하지 않고, 출처 간 페어와이즈 충돌 해소를 포함하면 빠르게 가중된다. 또한 후처리 병합 단계의 재계산 비용이 그래프 크기에 비선형적으로 증가한다.</p>
</li>
</ol>
<h2 id="7-실무-적용-시-고려사항">7. 실무 적용 시 고려사항</h2>
<p>실제 시스템에서 다중 소스 KG를 운용할 때 가장 먼저 결정해야 할 것은 <strong>entity resolution의 신뢰 임계값</strong>이다. 자동 병합 임계를 높게 잡으면 분리된 중복 entity가 남고, 낮게 잡으면 의미가 다른 노드가 합쳐져 분석 결과를 오염시킨다. 도메인 전문가의 검증 루프를 단계적으로 삽입하는 hybrid 방식이 안정적이다.</p>
<p>또한 KG는 일회성 산출물이 아니라 <strong>유지보수 대상</strong>이다. 출처 데이터의 스키마 변경, 신규 출처 추가, 기존 매핑 규칙의 수정이 누적되면서 그래프 일관성이 흔들린다. WCC·Louvain 결과를 정기적으로 비교해 구조적 drift를 감지하는 모니터링 체계가 필요하다. LLM 기반 해석 모듈을 운용할 경우, 정량 지표와 도메인 어휘 사이의 매핑 규칙을 명시적으로 두지 않으면 해석이 hallucination에 취약해진다.</p>
<blockquote>
<p>결론: 다중 소스 KG의 품질은 그래프 크기가 아니라 entity resolution과 정량 지표 검증 루프의 정밀도로 결정된다.</p>
</blockquote>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li>Knowledge Graphs and LLMs in Action. Manning, 2024 — Chapter 4: From simple networks to multisource integration</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[온톨로지로부터 첫 Knowledge Graph 구축하기]]></title>
            <link>https://velog.io/@tasker_dev/%EC%98%A8%ED%86%A8%EB%A1%9C%EC%A7%80%EB%A1%9C%EB%B6%80%ED%84%B0-%EC%B2%AB-Knowledge-Graph-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@tasker_dev/%EC%98%A8%ED%86%A8%EB%A1%9C%EC%A7%80%EB%A1%9C%EB%B6%80%ED%84%B0-%EC%B2%AB-Knowledge-Graph-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 02 May 2026 12:41:52 GMT</pubDate>
            <description><![CDATA[<h2 id="1-kg-구축이-복잡한-이유-의미적-heterogeneous-문제">1. KG 구축이 복잡한 이유: 의미적 heterogeneous 문제</h2>
<p>Knowledge Graph 구축의 본질적 난점은 단순한 포맷 변환이 아니라 <strong>의미(semantics)의 통합</strong>에 있다. 책에서는 이 난점을 네 층위로 분해한다.</p>
<ul>
<li><strong>포맷 차이</strong>: XML, CSV, JSON 등 직렬화 형식이 제각각이다.</li>
<li><strong>저장 기술 차이</strong>: 관계형 DB와 문서지향 DB의 모델이 다르다.</li>
<li><strong>구문(syntax) 차이</strong>: 같은 날짜를 <code>2022-08-09</code>로 쓰기도, <code>9 August 2022</code>로 쓰기도 한다.</li>
<li><strong>의미(semantics) 차이</strong>: 가장 까다로운 층위다. 헬스케어 도메인의 예가 분명하다. <code>type 2 diabetes</code>와 <code>ketosis resistant diabetes</code>는 다른 표현이지만 동일 개념이며, 반대로 <code>PE</code>라는 동일 약어는 <code>physical examination</code>일 수도 <code>pulmonary embolism</code>일 수도 있다. 거기에 <code>necrosis</code>와 <code>lobular necrosis</code>처럼 정보의 granularity 차이까지 더해진다.</li>
</ul>
<p>이 네 층위 중 앞의 셋은 ETL 도구로 흡수 가능하지만, 의미 차이는 별도의 reference schema 없이는 풀리지 않는다. 그 reference 역할을 하는 것이 ontology다.</p>
<h2 id="2-ontology-heterogeneous-정보의-중재자">2. Ontology: heterogeneous 정보의 중재자</h2>
<p>Ontology는 데이터에 등장하는 entity의 formal name, property, category, relationship을 표준 vocabulary로 모델링한 것이다. 책에서 ontology는 &quot;semantically heterogeneous information의 intermediary&quot;로 정의된다. 즉 각 데이터 소스의 local schema를 ontology의 reference schema로 mapping하는 구조다.</p>
<p>이 mapping 구조를 도식화하면 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/19b6ced6-eed5-4513-91f8-82145c9af3a3/image.png" alt=""></p>
<p>KG의 목표는 이렇게 단일 view로 융합된, <strong>unified·well-grounded·meaningful</strong>한 표현을 얻는 것이다. 개별 데이터 조각이 일관된 view 안에서 의미를 갖게 되어야 한다.</p>
<h2 id="3-kg-구축-프로세스-crisp-dm의-변형">3. KG 구축 프로세스: CRISP-DM의 변형</h2>
<p>책은 데이터 마이닝 표준 방법론인 CRISP-DM을 KG 구축용으로 각색해 제시한다. 단계별 책임이 명확히 분리된다.</p>
<table>
<thead>
<tr>
<th>Phase</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>Data Understanding</td>
<td>정의된 목표에 부합하는 데이터를 식별</td>
</tr>
<tr>
<td>Data Preparation</td>
<td>현재 scope에서 관련 데이터를 추려 다음 단계 입력으로 만든다</td>
</tr>
<tr>
<td>Modeling</td>
<td>ML task 알고리즘을 정의</td>
</tr>
<tr>
<td>KG Model Creation/Update</td>
<td>정의된 데이터·모델 기반으로 KG를 생성·갱신</td>
</tr>
<tr>
<td>Evaluation</td>
<td>구축된 KG가 목표를 충족하는지 검증</td>
</tr>
<tr>
<td>Deployment</td>
<td>운영 환경에 적용</td>
</tr>
</tbody></table>
<p>이 절차는 일회성 빌드가 아니라 <strong>순환 구조</strong>라는 점이 중요하다. 도메인 이해가 깊어지거나 데이터 소스가 추가될 때마다 ontology 자체가 진화하고, 그에 따라 후속 phase가 다시 돌아간다.</p>
<h2 id="4-사례-hpo-기반-희귀질환-진단-지원">4. 사례: HPO 기반 희귀질환 진단 지원</h2>
<p>책의 실제 use case는 임상 현장이다. 임상의가 진단을 내리는 과정에는 명확한 검사 결과뿐 아니라 <strong>gray area</strong>가 포함된다. 이 불확실성을 다루기 위해 두 속성을 가진 structured knowledge base가 필요하다.</p>
<ul>
<li><strong>phenotype 도메인의 contextual description</strong>: 같은 장기·계통과 관련된 phenotypic anomaly가 명시적으로 연결되어 있어야 한다.</li>
<li><strong>phenotypic anomaly와 disease 간 관계 추적성</strong>: 임상의가 연결의 근거 출처에 도달할 수 있어야 한다.</li>
</ul>
<p>이 요구를 만족시키는 것이 <strong>HPO (Human Phenotype Ontology)</strong> 와 그 annotation 데이터다. HPO는 phenotype 계층을 제공하고, annotation 파일은 phenotypic abnormality와 disease를 연결한다.</p>
<h2 id="5-rdf-vs-lpg-두-갈래-기술-선택">5. RDF vs LPG: 두 갈래 기술 선택</h2>
<p>KG를 구축하는 가장 대표적 두 모델이 <strong>RDF (Resource Description Framework)</strong> 와 <strong>LPG (Labeled Property Graph)</strong> 다. 둘은 같은 그래프를 표현하지만 설계 철학이 갈린다.</p>
<h3 id="rdf의-구조">RDF의 구조</h3>
<p>RDF는 W3C가 정의·관리하는 web 기반 데이터 교환 표준이다. 모든 statement가 <strong>subject–predicate–object의 triple</strong>로 구성된다. Subject는 노드, predicate은 edge, object는 또 다른 노드다. KG 전체가 이런 statement의 집합으로 모델링된다. 도메인 ontology 작성에 특히 적합하다.</p>
<h3 id="lpg의-구조">LPG의 구조</h3>
<p>LPG는 노드와 relationship에 <strong>key–value pair</strong>를 직접 붙일 수 있는 모델이다. 그래프 traversal과 path 분석에 최적화되어 있고, 저장·접근 효율이 강점이다.</p>
<h3 id="핵심-차이-메타데이터의-부착-위치">핵심 차이: 메타데이터의 부착 위치</h3>
<p>RDF에서 relationship(triple)은 <strong>전역적으로 정의</strong>된다. 즉 어떤 predicate에 메타데이터를 붙이면 그래프 전체에서 해당 relationship의 모든 instance에 영향이 간다. LPG는 반대로 <strong>edge별 unique 속성</strong>을 허용하므로 개별 relationship에 따로 메타데이터를 부여할 수 있다.</p>
<p>이 차이를 보완하기 위해 양쪽 모두 진화 중이다. RDF 측은 <strong>named graph</strong>로 triple 묶음에 컨텍스트를 부여하고, <strong>RDF-star</strong>로 edge에 property를 붙이는 길을 열고 있다. LPG 측은 Neo4j의 <strong>Neosemantics 플러그인</strong>으로 OWL·RDFS·SKOS 같은 RDF vocabulary를 LPG 위에서 활용 가능하게 하며, Amazon Neptune은 RDF 데이터에 Cypher를 실행하는 우회 경로를 제공한다.</p>
<table>
<thead>
<tr>
<th>비교 축</th>
<th>RDF</th>
<th>LPG</th>
</tr>
</thead>
<tbody><tr>
<td>표준화</td>
<td>W3C 표준</td>
<td>벤더별 구현</td>
</tr>
<tr>
<td>기본 단위</td>
<td>Triple (subject–predicate–object)</td>
<td>Node·Relationship + key-value</td>
</tr>
<tr>
<td>메타데이터 부착</td>
<td>Predicate 단위 (전역)</td>
<td>Edge별 (지역)</td>
</tr>
<tr>
<td>강점</td>
<td>Knowledge representation, ontology 작성</td>
<td>Traversal·path 분석 성능, 저장 효율</td>
</tr>
<tr>
<td>Edge property</td>
<td>RDF-star로 부분 지원</td>
<td>Native 지원</td>
</tr>
<tr>
<td>추론</td>
<td>OWL 기반 inference 자연스러움</td>
<td>추가 도구 필요 (예: Neosemantics)</td>
</tr>
<tr>
<td>컨텍스트 표현</td>
<td>Named graph</td>
<td>Relationship property</td>
</tr>
</tbody></table>
<h3 id="rdf-내부의-세-가지-모델링-전략">RDF 내부의 세 가지 모델링 전략</h3>
<p>RDF가 edge에 속성을 붙이지 못한다는 한계를 우회하기 위해 세 가지 패턴이 통용된다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/0b390314-ec98-465d-8343-a20393355161/image.png" alt=""></p>
<ul>
<li><strong>N-ary relations</strong>: 데이터를 연결하는 새로운 concept을 만든다. 보통 <code>_:Annotation</code> 같은 blank node로 표현되는데, 이는 전역 식별자 없이 관련 정보를 묶기 위한 unnamed resource다. ontology 진화에 따른 backward compatibility가 약점이 될 수 있다.</li>
<li><strong>Named graphs</strong>: triple에 네 번째 요소를 추가해 statement가 어느 sub-graph에 속하는지 명시한다. provenance·context 표현에 강력하지만, named graph 수가 많아지면 저장·교환 효율이 떨어지고 fine-grained update가 어려워진다.</li>
<li><strong>RDF-star</strong>: triple 자체에 property를 붙일 수 있게 한 RDF 확장이다. 더 읽기 쉬운 SPARQL 쿼리가 가능하지만, query 성능이 아직 부족하고 새 syntax 확장이라는 특성상 RDF 엔진별 구현이 필요해 채택이 제한적이다.</li>
</ul>
<p>이 외에 reification이나 singleton property 같은 방법도 존재하지만, 실무에서는 named graph와 n-ary relation이 더 자주 선택된다.</p>
<h2 id="6-neo4j로-hpo-kg-만들기-구축-단계">6. Neo4j로 HPO KG 만들기: 구축 단계</h2>
<p>책은 LPG 진영의 Neo4j와 Neosemantics 플러그인을 사용해 HPO KG를 실제로 구축한다. 절차는 두 단계다.</p>
<ol>
<li><strong>Ontology 로딩</strong>: HPO vocabulary 자체를 그래프에 적재</li>
<li><strong>Annotation 데이터 ingest</strong>: ontology를 reference로 삼아 disease–phenotype 연결을 채워 넣기</li>
</ol>
<p>전체 파이프라인을 정리하면 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/7497e420-a894-401b-8ed3-b4dddfab8e8e/image.png" alt=""></p>
<p>특히 <code>HAS_PHENOTYPIC_FEATURE</code> relationship에 속성을 붙이는 단계는 LPG의 강점을 잘 보여준다. 다음 의사코드는 TSV의 각 row에서 값이 존재하는 경우에만 property를 부여하는 패턴이다.</p>
<pre><code class="language-cypher">LOAD CSV WITH HEADERS FROM &#39;file:///annotations.tsv&#39; AS row
FIELDTERMINATOR &#39;\t&#39;
MATCH (d:HpoDisease {id: row.disease_id})
MATCH (p:HpoPhenotype {id: row.phenotype_id})
MATCH (d)-[r:HAS_PHENOTYPIC_FEATURE]-&gt;(p)
FOREACH (_ IN CASE WHEN row.frequency IS NOT NULL THEN [1] ELSE [] END |
  SET r.frequency = row.frequency
)
FOREACH (_ IN CASE WHEN row.onset IS NOT NULL THEN [1] ELSE [] END |
  SET r.onset = row.onset
)</code></pre>
<p><code>FOREACH</code> 블록은 해당 column 값이 null이 아닐 때만 property를 set하는 가드다. 결과적으로 missing data에 resilient하면서 기존 값을 null로 덮어쓰지 않는다. RDF의 전역 predicate 모델에서는 이런 per-edge 부분 갱신이 부자연스럽다.</p>
<h2 id="7-inference-kg의-진짜-가치">7. Inference: KG의 진짜 가치</h2>
<p>KG의 가장 강력한 활용은 저장된 정보를 그대로 조회하는 것이 아니라, <strong>logical rule 기반의 deductive reasoning</strong>으로 implicit 정보를 끌어내는 것이다.</p>
<p>예를 들어 &quot;내분비계 이상(<code>HP:0000818</code>)을 동반하는 disease는 무엇인가&quot;라는 질의를 생각해보자. 이 phenotype에 직접 annotation된 disease만 반환하면 답이 충분치 않다. 임상의는 갑상선 등 더 구체적인 hierarchy 하위의 phenotype까지 묶어 보고 싶어한다. HPO의 계층 구조가 ontology 안에 박혀 있기 때문에, subclass를 따라 traversal하는 단일 query로 implicit한 연결까지 회수할 수 있다.</p>
<p>이 지점이 단순 검색·문서 retrieval과 KG가 갈리는 지점이다. 계층·관계가 schema에 명시적으로 표현되어 있기 때문에, 질의 시점에 추론이 가능해진다.</p>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li>Knowledge Graphs and LLMs in Action. Manning, 2024 — Chapter 3: Create your first knowledge graph from ontologies</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Neo4j Vector Index와 RAG 통합]]></title>
            <link>https://velog.io/@tasker_dev/Neo4j-Vector-Index%EC%99%80-RAG-%ED%86%B5%ED%95%A9</link>
            <guid>https://velog.io/@tasker_dev/Neo4j-Vector-Index%EC%99%80-RAG-%ED%86%B5%ED%95%A9</guid>
            <pubDate>Fri, 01 May 2026 07:40:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>임베딩은 의미를 고차원 공간에 새기고, 그래프는 그 사이를 잇는다. Neo4j Vector Index는 두 검색 방식을 한 데이터베이스 안에서 결합한다.</p>
</blockquote>
<p>Neo4j Vector Index는 vector 기반 유사도 검색과 graph traversal을 하나의 storage 계층에서 통합 운용할 수 있게 만든 indexing 구조다.</p>
<h2 id="1-vector-index가-필요한-이유">1. Vector Index가 필요한 이유</h2>
<p>전통적인 데이터베이스의 index는 key·attribute 기반으로 탐색을 가속한다. 그러나 데이터가 고차원 vector로 표현되는 순간, 이러한 색인 방식은 적합하지 않다. 임베딩 공간에서 의미가 가까운 항목을 찾으려면 &quot;정확히 일치하는 key&quot;가 아니라 &quot;공간상 가까운 좌표&quot;를 질의해야 한다.</p>
<p>Vector index는 고차원 공간에서의 <strong>공간적 근접성(spatial proximity)</strong> 을 효율적으로 탐색하도록 설계된 특수 index다. 일반 index가 정렬된 key를 따라 트리 구조로 좁혀가는 것과 달리, vector index는 임베딩 벡터 사이의 거리 또는 각도를 기준으로 유사 항목을 회수한다.</p>
<p>이 구조의 실질적 이점은 두 가지다. 첫째, 대규모 임베딩을 메모리에 모두 적재하지 않고 데이터베이스에 위임할 수 있다. 둘째, Neo4j의 경우 vector 질의를 graph 질의와 동일한 storage 계층에서 처리하므로, 유사 문서 회수 후 그 문서가 속한 entity의 관계망을 곧바로 traversal 할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/4cc752a7-6b0c-485c-913a-8645297ad155/image.png" alt=""></p>
<h2 id="2-neo4j-vector-index-생성">2. Neo4j Vector Index 생성</h2>
<p>Neo4j에서 vector index는 Cypher의 procedure 호출로 생성된다. 다음은 Tesla 관련 텍스트를 위한 index 정의 예시다.</p>
<pre><code class="language-cypher">CALL db.index.vector.createNodeIndex(
    &#39;TeslaEmbeddings&#39;,   // index 이름
    &#39;Chunk&#39;,             // node label
    &#39;tesla_embedding&#39;,   // property 이름
    1536,                // vector 차원 수
    &#39;cosine&#39;             // similarity metric
)</code></pre>
<p>각 parameter의 역할은 다음과 같이 정리된다.</p>
<table>
<thead>
<tr>
<th>Parameter</th>
<th>역할</th>
<th>결정 기준</th>
</tr>
</thead>
<tbody><tr>
<td>index 이름</td>
<td>질의 시 참조할 식별자</td>
<td>도메인·용도별 명명</td>
</tr>
<tr>
<td>node label</td>
<td>embedding을 저장할 node 종류</td>
<td>보통 <code>Chunk</code> 또는 <code>Document</code></td>
</tr>
<tr>
<td>property 이름</td>
<td>embedding이 들어갈 속성명</td>
<td>동일 node에 여러 embedding 공존 가능</td>
</tr>
<tr>
<td>vector 차원 수</td>
<td>임베딩 벡터의 길이</td>
<td>사용한 embedding 모델에 종속 (OpenAI <code>text-embedding-ada-002</code>는 1536)</td>
</tr>
<tr>
<td>similarity metric</td>
<td>유사도 계산 방식</td>
<td>NLP에서는 주로 <code>cosine</code></td>
</tr>
</tbody></table>
<p>vector 차원은 임의로 정할 수 없다. 임베딩 모델이 출력하는 차원과 정확히 일치해야 하며, 모델을 교체하면 index도 재생성해야 한다.</p>
<h2 id="3-cosine-similarity와-거리-척도">3. Cosine Similarity와 거리 척도</h2>
<p>NLP 임베딩 분석에서 cosine similarity가 표준에 가까운 이유는 <strong>벡터의 크기(magnitude)가 아니라 방향(direction)</strong> 만으로 의미적 유사성을 판정하기 때문이다. 두 벡터를 고차원 공간에 투영했을 때 사잇각이 작을수록 유사하다고 본다.</p>
<p>cosine similarity 외에도 Euclidean distance, dot product 등이 사용 가능하지만, 텍스트 임베딩은 일반적으로 정규화되어 있어 cosine이 가장 안정적인 결과를 보인다. 문서의 길이가 임베딩 norm에 영향을 주는 경우, 크기 정보를 무시하는 cosine이 노이즈에 덜 민감하다.</p>
<h2 id="4-chunking과-overlap">4. Chunking과 Overlap</h2>
<p>LLM과 임베딩 모델은 입력 길이에 한계를 가진다. 따라서 긴 문서는 작은 단위로 분할해 각각 임베딩한다. 이때 두 가지 parameter가 결정적이다.</p>
<ul>
<li><strong>chunk_size</strong>: 한 chunk에 포함되는 token 길이. 너무 작으면 의미 단위가 끊기고, 너무 크면 임베딩이 평균화되어 검색 정밀도가 떨어진다.</li>
<li><strong>chunk_overlap</strong>: 인접한 chunk 사이에서 겹치는 token 수. 문장 경계에서 끊긴 context를 보존하기 위한 장치다.</li>
</ul>
<p>chunking은 단순한 전처리 단계로 보이지만, RAG 품질의 상한을 결정짓는 변수다. chunk가 의미 단위와 어긋나면, 아무리 좋은 retrieval 알고리즘도 잘못된 단위로 검색하게 된다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/e3825627-9791-4f21-95de-7848a5344b85/image.png" alt=""></p>
<h2 id="5-embedding-생성과-저장">5. Embedding 생성과 저장</h2>
<p>Cypher로 index를 만든 뒤, 실제 embedding 적재는 LangChain의 <code>Neo4jVector</code>와 같은 wrapper로 처리할 수 있다.</p>
<pre><code class="language-python">hybrid_db = Neo4jVector.from_documents(
    docs,
    OpenAIEmbeddings(),
    url=url,
    username=username,
    password=password,
    index_name=&quot;TeslaEmbeddings&quot;,
    search_type=&quot;hybrid&quot;
)</code></pre>
<p>여기서 <code>search_type=&quot;hybrid&quot;</code>는 vector 유사도와 keyword 검색(전문 검색)을 결합하는 옵션이다. 임베딩 검색은 의미적으로 가까운 항목을 잘 찾지만, 고유명사나 정확한 ID 매칭에는 약하다. hybrid 모드는 두 약점을 상호 보완한다.</p>
<h2 id="6-vector-검색과-graph-traversal의-결합">6. Vector 검색과 Graph Traversal의 결합</h2>
<p>Neo4j Vector Index의 진짜 가치는 vector 검색 결과를 곧바로 graph 질의와 이어 붙일 수 있다는 점이다. 일반적인 vector store는 유사 chunk를 반환하면 거기서 끝나지만, Neo4j는 회수된 <code>Chunk</code> node가 어떤 <code>Document</code>, <code>Entity</code>, <code>Topic</code> 등과 연결되어 있는지 같은 질의 안에서 추적할 수 있다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>Pure Vector Store</th>
<th>Neo4j Vector Index</th>
</tr>
</thead>
<tbody><tr>
<td>저장 단위</td>
<td>임베딩 + 메타데이터</td>
<td>node + 임베딩 + 관계</td>
</tr>
<tr>
<td>검색 결과</td>
<td>유사 chunk 목록</td>
<td>유사 chunk + 연결된 subgraph</td>
</tr>
<tr>
<td>후속 질의</td>
<td>별도 시스템 필요</td>
<td>Cypher로 즉시 traversal</td>
</tr>
<tr>
<td>적합 use case</td>
<td>단순 의미 검색</td>
<td>의미 검색 + 관계 추론</td>
</tr>
</tbody></table>
<p>비슷한 문서를 찾는 작업은 vector가 강점을, 그 문서를 둘러싼 관계를 따라가는 작업은 graph가 강점을 가진다. Neo4j Vector Index는 이 둘을 단일 backend에서 묶어 결과를 풍부하게 만든다.</p>
<h2 id="7-한계와-트레이드오프">7. 한계와 트레이드오프</h2>
<p>vector index를 graph 데이터베이스 안에 통합하는 설계는 여러 장점을 제공하지만, 다음과 같은 비용을 동반한다.</p>
<p><strong>첫째, 임베딩 모델 종속성</strong>. index 차원은 모델에 묶여 있다. embedding 모델을 1536차원에서 3072차원으로 교체하려면 index를 재생성하고 전체 chunk를 다시 임베딩해야 한다. 모델 ABI가 깨지는 셈이다.</p>
<p><strong>둘째, chunking 전략의 비가역성</strong>. 한 번 chunk_size·overlap을 정해 임베딩을 만들고 나면, 이를 변경하려면 전체 데이터를 재처리해야 한다. 운영 중인 시스템에서 chunking 정책을 바꾸는 것은 단순한 hyperparameter 튜닝이 아니라 데이터 마이그레이션 작업이 된다.</p>
<p><strong>셋째, vector 검색과 graph 검색의 인지 부담</strong>. 두 검색 방식을 결합하면 시스템은 강력해지지만, 질의를 설계하는 쪽의 책임도 커진다. 어느 단계까지를 vector로 회수하고, 어디서부터 graph traversal로 정밀도를 높일 것인가는 도메인별로 답이 다르다. 이 결정이 잘못되면 두 방식 모두의 장점을 잃는다.</p>
<h2 id="8-실무-적용-시-고려사항">8. 실무 적용 시 고려사항</h2>
<p>운영 환경에서 Neo4j Vector Index를 도입할 때 우선 점검할 항목은 데이터 volume과 갱신 주기다. 임베딩 생성 비용은 token 수에 비례하므로, 정적 corpus에서는 한 번 적재하고 끝나지만 매일 수만 건의 문서가 추가되는 환경에서는 embedding API 호출 비용이 빠르게 누적된다. 이 경우 변경분만 재임베딩하는 incremental indexing 전략이 필수다.</p>
<p>또한 hybrid search를 활성화할 때는 keyword index와 vector index 중 어느 쪽 결과를 우선할지 가중치 조정이 필요하다. 두 결과의 점수 척도가 다르기 때문에, 단순 합산은 한쪽 신호를 압도하기 쉽다. 정규화 또는 reciprocal rank fusion 같은 결합 방식을 검토해야 한다.</p>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li>Graph Data Science with Python and Neo4j. Chapter 7: Neo4j Vector Index and Retrieval-Augmented Generation (RAG).</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Python과 Neo4j 환경 구축]]></title>
            <link>https://velog.io/@tasker_dev/Python%EA%B3%BC-Neo4j-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95</link>
            <guid>https://velog.io/@tasker_dev/Python%EA%B3%BC-Neo4j-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95</guid>
            <pubDate>Thu, 30 Apr 2026 05:49:27 GMT</pubDate>
            <description><![CDATA[<h2 id="1-핵심-구성-요소-개관">1. 핵심 구성 요소 개관</h2>
<p>Neo4j 기반 그래프 분석 환경은 네 가지 축으로 구성된다. <strong>데이터베이스 엔진, 플러그인 라이브러리, 클라이언트 인터페이스, 언어 바인딩</strong>이 그것이다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/7574c17e-eb11-4047-8083-3e1f7890dcb7/image.png" alt=""></p>
<p>각 구성 요소는 독립적으로 교체 가능하다. 실행 환경(Desktop vs Aura), 플러그인의 활성화 여부, 접근 인터페이스(Browser vs Python)는 분석 목적에 따라 조합된다.</p>
<h2 id="2-cypher-query-language">2. Cypher Query Language</h2>
<p>Neo4j의 질의 언어는 Cypher이다. SQL이 테이블 기반 관계 대수를 표현하기 위해 설계된 것처럼, Cypher는 그래프 패턴 매칭을 표현하기 위해 설계되었다. 핵심 문법은 <strong>ASCII art 형태의 패턴 표현</strong>으로, 노드는 <code>()</code>, relationship은 <code>-[]-&gt;</code>로 표기한다.</p>
<pre><code class="language-cypher">// 노드 생성
CREATE (alice:Person {name: &#39;Alice&#39;, age: 30})

// 관계 패턴 매칭
MATCH (p:Person)-[:ACTED_IN]-&gt;(m:Movie)
WHERE m.released &gt; 2000
RETURN p.name, m.title

// 다단계 traversal
MATCH (p:Person)-[:FRIEND]-&gt;(:Person)-[:FRIEND]-&gt;(fof:Person)
WHERE p.name = &#39;Alice&#39;
RETURN DISTINCT fof.name</code></pre>
<p>세 번째 예시는 그래프 DB의 강점을 단적으로 드러낸다. &quot;친구의 친구&quot;를 찾는 질의가 RDB에서는 self-join 두 번을 요구하지만, Cypher에서는 패턴 자체로 자연스럽게 표현된다. 이 표현력 차이가 multi-hop traversal이 빈번한 도메인에서 그래프 DB를 선택하게 만드는 핵심 근거이다.</p>
<h2 id="3-gds와-apoc-두-가지-핵심-플러그인">3. GDS와 APOC: 두 가지 핵심 플러그인</h2>
<p>기본 Neo4j 엔진은 그래프 저장과 Cypher 질의 실행을 담당한다. 그 위에서 데이터 분석과 운영 편의를 제공하는 두 라이브러리가 있다.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>GDS (Graph Data Science)</th>
<th>APOC (Awesome Procedures on Cypher)</th>
</tr>
</thead>
<tbody><tr>
<td>제공 주체</td>
<td>Neo4j 공식</td>
<td>커뮤니티 기반 (Neo4j 공식 지원)</td>
</tr>
<tr>
<td>주된 역할</td>
<td>그래프 알고리즘 50종 이상 (centrality, community detection, similarity, embedding 등)</td>
<td>유틸리티·데이터 통합·운영 보조</td>
</tr>
<tr>
<td>대표 용도</td>
<td>PageRank, Louvain community, node2vec 등 분석 워크로드</td>
<td>JSON/XML 통합, batch 처리, UUID 생성, 병렬 Cypher 실행, geospatial 함수, time series</td>
</tr>
<tr>
<td>Python 연동</td>
<td><code>graphdatascience</code> 드라이버를 통한 호출</td>
<td>Cypher 내에서 직접 procedure 호출</td>
</tr>
</tbody></table>
<p>GDS는 분석가·데이터 과학자의 도구이고, APOC는 데이터 엔지니어의 도구라는 단순화된 구분이 가능하다. 다만 실무에서는 두 라이브러리를 함께 활성화하는 것이 일반적이다. 외부 데이터 소스로부터 그래프를 적재할 때 APOC가, 적재된 그래프 위에서 분석을 수행할 때 GDS가 동원되기 때문이다.</p>
<p>GraphRAG 맥락에서는 GDS의 임베딩 알고리즘(node2vec, FastRP)이 특히 중요하다. 그래프 구조를 벡터 공간으로 사용하여 vector similarity search와 결합하는 기반을 제공한다.</p>
<h2 id="4-실행-환경-선택-desktop-vs-aura">4. 실행 환경 선택: Desktop vs Aura</h2>
<p>Neo4j는 두 가지 주된 실행 환경을 제공한다. 선택 기준은 학습 목적, 데이터 민감도, 운영 부담 수용 의지에 따라 갈린다.</p>
<table>
<thead>
<tr>
<th>기준</th>
<th>Neo4j Desktop</th>
<th>Neo4j Aura</th>
</tr>
</thead>
<tbody><tr>
<td>실행 위치</td>
<td>로컬 머신</td>
<td>관리형 클라우드</td>
</tr>
<tr>
<td>연결 방식</td>
<td>localhost (예: <code>bolt://localhost:7687</code>)</td>
<td>URI 연결 문자열 (<code>neo4j+s://...</code>)</td>
</tr>
<tr>
<td>운영 부담</td>
<td>사용자가 직접 관리 (메모리, 백업, 보안)</td>
<td>Neo4j가 관리</td>
</tr>
<tr>
<td>비용</td>
<td>무료 (로컬 자원만 사용)</td>
<td>무료 티어 제공, 확장 시 유료</td>
</tr>
<tr>
<td>적합 용도</td>
<td>학습, 프로토타이핑, 오프라인 분석</td>
<td>협업, 운영 환경, 신속한 시작</td>
</tr>
<tr>
<td>플러그인 설치</td>
<td>사용자가 직접 설치 (GDS, APOC)</td>
<td>환경에 따라 사전 통합</td>
</tr>
</tbody></table>
<p>학습 단계에서는 Desktop이 자유도가 높다. Cypher 콘솔에서 <code>:play movies</code>를 실행하면 People과 Movie를 node로, ACTED_IN·PRODUCED·DIRECTED를 relationship으로 가지는 예제 그래프가 생성되어 즉시 시각적 탐색이 가능하다. 반면 Aura는 환경 구성 부담 없이 바로 시작하려는 경우, 또는 노트북 자원이 제한된 경우 적합하다.</p>
<h2 id="5-python-neo4j-연결과-bolt-protocol">5. Python-Neo4j 연결과 Bolt Protocol</h2>
<p>Python에서 Neo4j에 접근할 때는 <code>graphdatascience</code> 라이브러리를 통한 API 연결이 표준이다.</p>
<pre><code class="language-python">from graphdatascience import GraphDataScience

# Desktop 로컬 연결
URI = &quot;bolt://localhost:7687&quot;
AUTH = (&quot;neo4j&quot;, &quot;your_password&quot;)

gds = GraphDataScience(URI, auth=AUTH)

# 연결 확인 및 Cypher 실행
result = gds.run_cypher(&quot;&quot;&quot;
    MATCH (p:Person)-[:ACTED_IN]-&gt;(m:Movie)
    RETURN p.name AS actor, count(m) AS movie_count
    ORDER BY movie_count DESC
    LIMIT 5
&quot;&quot;&quot;)
print(result)</code></pre>
<p>URI에 사용된 <strong>Bolt protocol</strong>은 Neo4j 전용 binary protocol이다. HTTP/HTTPS 대비 두 가지 차별점이 있다. 첫째, 그래프 데이터 전송에 최적화된 binary 직렬화로 전송 효율이 높다. 둘째, TLS 암호화를 기본 지원하여 별도 설정 없이 보안 통신이 성립한다. Aura 환경에서는 <code>neo4j+s://</code> 스키마가 사용되며 <code>+s</code>는 암호화 활성화를 의미한다.</p>
<p>연결이 성립하면 Python 측에서는 Neo4j를 사실상 <strong>원격 그래프 분석 엔진</strong>으로 활용할 수 있다. node·relationship의 CRUD, Cypher 질의 실행, GDS 알고리즘 호출이 모두 가능하다.</p>
<h2 id="6-한계와-트레이드오프">6. 한계와 트레이드오프</h2>
<p>본 챕터의 환경 구성 자체에서 도출되는 한계는 다음과 같다.</p>
<p><strong>Desktop 환경의 확장성 제약.</strong> 로컬 머신 자원에 직접적으로 종속된다. 수백만 노드 규모를 넘어서면 메모리·디스크 I/O 병목이 발생하며, 학습 목적을 넘어선 분석에는 부적합하다. 본 챕터에서도 on-premise·cloud 클러스터 구성은 책의 범위 밖으로 명시되어 있다.</p>
<p><strong>Aura 무료 티어의 운영 한계.</strong> 관리형 환경의 편의성은 데이터 크기·연결 수·플러그인 가용성에 대한 제약과 교환된다. 특히 GDS의 일부 고급 알고리즘은 유료 티어에서만 활성화되는 경우가 있어, 분석 깊이를 추구하는 단계에서는 검토가 필요하다.</p>
<p><strong>플러그인 의존성.</strong> GDS와 APOC는 사실상 필수에 가깝지만, Neo4j 코어와 분리된 라이브러리이다. Neo4j 버전 업그레이드 시 플러그인 호환성을 별도로 확인해야 하며, 클라우드와 로컬에서 사용 가능한 함수 집합이 미묘하게 다를 수 있다.</p>
<p><strong>Bolt protocol의 락인.</strong> Bolt는 Neo4j 생태계에 특화된 protocol이다. 표준 SQL/HTTP 기반 도구체인과의 통합이 직접적이지 않으며, BI 도구나 오케스트레이션 시스템과 연결할 때 별도의 어댑터·드라이버가 요구된다.</p>
<h2 id="7-실무-적용-시-고려사항">7. 실무 적용 시 고려사항</h2>
<p>환경 구성 단계에서 결정되는 사항이 이후 분석 흐름 전체에 영향을 미친다.</p>
<p>첫째, 학습과 운영 환경을 일찍부터 분리한다. Desktop에서 작성한 Cypher와 Python 스크립트가 Aura에서도 동작하는지를 초기에 검증해두면, 이후 운영 환경으로의 이행이 매끄럽다. URI 문자열만 환경 변수로 분리하는 것이 일반적인 패턴이다.</p>
<p>둘째, GDS·APOC를 처음부터 함께 활성화한다. 학습 단계에서는 코어 Cypher만으로 충분해 보이지만, GraphRAG로 진입하는 순간 임베딩 생성·외부 데이터 통합·배치 처리가 필요해진다. 사후에 플러그인을 추가하면 기존 데이터 구조를 재검토해야 하는 경우가 발생한다.</p>
<p>셋째, 자격 증명 관리를 코드 외부로 분리한다. URI·사용자명·비밀번호를 스크립트에 직접 작성하는 패턴은 학습용으로만 허용된다. <code>.env</code> 파일 또는 시크릿 매니저를 사용하는 습관이 운영 단계에서 필수가 된다.</p>
<blockquote>
<p>결론: Desktop은 학습의 자유도, Aura는 운영의 편의를 제공하며 — 어느 쪽을 선택하든 GDS·APOC·Bolt라는 세 축이 이후 GraphRAG 실습의 공통 기반이 된다.</p>
</blockquote>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li>Graph Data Science with Python and Neo4j. Chapter 2: Getting Started with Python and Neo4j.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[그래프 데이터 사이언스 입문]]></title>
            <link>https://velog.io/@tasker_dev/%EA%B7%B8%EB%9E%98%ED%94%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%82%AC%EC%9D%B4%EC%96%B8%EC%8A%A4-%EC%9E%85%EB%AC%B8</link>
            <guid>https://velog.io/@tasker_dev/%EA%B7%B8%EB%9E%98%ED%94%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%82%AC%EC%9D%B4%EC%96%B8%EC%8A%A4-%EC%9E%85%EB%AC%B8</guid>
            <pubDate>Thu, 30 Apr 2026 05:34:50 GMT</pubDate>
            <description><![CDATA[<h2 id="1-그래프란-무엇인가">1. 그래프란 무엇인가</h2>
<p>그래프 기반 시스템을 다루기에 앞서, 그래프 자료구조의 정의를 명확히 할 필요가 있다. <strong>관계 자체를 일급 객체로 취급하는 데이터 모델</strong>이 그래프이다.</p>
<p>이산수학과 그래프 이론에서 그래프는 객체(node)와 그 객체들 사이의 연결(relationship)로 구성된 구조로 정의된다. 객체는 vertex, node, point 등으로 불리며, 객체 사이의 연결은 edge, relationship, link로 불린다. 본 글에서는 객체를 node, 연결을 relationship으로 통일한다.</p>
<p>가장 직관적인 예시는 사회 관계망이다. 한 사람을 node로, 두 사람 사이의 친구 관계를 relationship으로 표현하면 social graph가 만들어지며, 이를 통해 사람들이 어떻게 상호 연결되어 있고 정보가 어떻게 흐르는지 분석할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/e9757e38-d614-41cf-b97c-fbd9ca018c56/image.png" alt=""></p>
<p>Neo4j 같은 그래프 데이터베이스에서는 node뿐 아니라 relationship에도 데이터를 저장할 수 있고, 이를 property라고 부른다. 즉 &quot;Alice와 Bob이 친구다&quot;라는 사실 위에 &quot;2019년부터 친구이며 신뢰도 0.8&quot; 같은 속성을 relationship 자체에 부여할 수 있다. 관계형 DB가 join table에 별도 컬럼을 두어 흉내내는 것과 달리, 그래프 DB는 이를 구조적으로 자연스럽게 표현한다.</p>
<h2 id="2-neo4j-native-graph-database">2. Neo4j: native graph database</h2>
<p>Neo4j는 native graph database로 분류된다. 여기서 <strong>native</strong>라는 표현이 핵심이다. 그래프 자료구조와 그 위에서 수행되는 알고리즘·연산을 처리하기 위해 처음부터 설계되었다는 의미이며, 이는 다른 데이터베이스들이 그래프 모델을 &quot;지원&quot;하는 방식과 구별된다.</p>
<p>저장소 종류를 정리하면 다음과 같다.</p>
<table>
<thead>
<tr>
<th>분류</th>
<th>대표 제품</th>
<th>특징</th>
</tr>
</thead>
<tbody><tr>
<td>Native graph DB</td>
<td>Neo4j</td>
<td>그래프 구조에 최적화된 저장·탐색 엔진. relationship 자체가 1급 객체</td>
</tr>
<tr>
<td>Non-native graph (NoSQL/RDB 기반)</td>
<td>MongoDB, Amazon Neptune, SQL Server, ArangoDB</td>
<td>다른 데이터 모델 위에 그래프 추상을 얹은 형태</td>
</tr>
<tr>
<td>RDF database</td>
<td>Virtuoso, Stardog</td>
<td>웹 데이터 교환(triple 기반)에 특화</td>
</tr>
<tr>
<td>Vector database</td>
<td>Pinecone, Milvus, FAISS</td>
<td>벡터 임베딩의 similarity search에 특화</td>
</tr>
</tbody></table>
<p>Neo4j는 vector database가 아니다. 다만 vector를 저장하고 similarity search를 수행하는 기능을 제공하므로, 그래프 구조와 벡터 검색을 한 시스템에서 결합하려는 경우 선택지가 된다. 이 결합 가능성이 GraphRAG 아키텍처의 기술적 토대가 된다.</p>
<p>Neo4j가 널리 채택되는 이유는 세 가지로 요약된다. 그래프 자료구조에 대한 native 처리, 확장성, 그리고 오픈소스 기반의 활발한 커뮤니티이다. 데이터 포인트 간 연결이 많고 그 연결의 구조 자체를 분석해야 하는 도메인 — 추천, 사기 탐지, 지식 그래프, 네트워크 분석 — 에서 특히 적합하다.</p>
<h2 id="3-데이터베이스-선택의-의사결정-흐름">3. 데이터베이스 선택의 의사결정 흐름</h2>
<p>저장소 선택은 단일 기준으로 결정되지 않는다. 데이터의 형태와 질의 패턴이 함께 고려되어야 한다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/b799af1f-ce0c-439e-95c8-0ef453ef0da2/image.png" alt=""></p>
<p>이 흐름도는 본 챕터에서 다룬 데이터베이스 분류에 기반한 의사결정 보조 도구로 이해하면 된다. 실제 시스템 설계에서는 단일 저장소를 선택하기보다, 그래프 DB와 벡터 DB를 병행 운용하거나 한 시스템 안에서 통합하는 polyglot persistence 전략이 일반적이다.</p>
<h2 id="4-그래프-시각화-도구-생태계">4. 그래프 시각화 도구 생태계</h2>
<p>그래프 데이터의 가치는 시각화를 통해 직관적으로 드러나는 경우가 많다. 본 챕터에서 언급된 주요 도구들은 다음과 같다.</p>
<table>
<thead>
<tr>
<th>도구</th>
<th>유형</th>
<th>주요 용도</th>
</tr>
</thead>
<tbody><tr>
<td>Linkurious</td>
<td>상용 시각화 플랫폼</td>
<td>사기 탐지, 정보 분석, 사이버 보안</td>
</tr>
<tr>
<td>Hume (GraphAware)</td>
<td>Neo4j 기반 분석 플랫폼</td>
<td>시각화·분석·머신러닝 통합</td>
</tr>
<tr>
<td>VizNetwork</td>
<td>R 패키지</td>
<td>인터랙티브 네트워크 그래프</td>
</tr>
<tr>
<td>Gephi</td>
<td>오픈소스 데스크톱 도구</td>
<td>학술·연구용 네트워크 탐색</td>
</tr>
<tr>
<td>D3.js</td>
<td>JavaScript 라이브러리</td>
<td>웹 기반 커스텀 시각화</td>
</tr>
<tr>
<td>Power BI</td>
<td>BI 대시보드</td>
<td>일반 BI 리포트에 그래프 시각화 부분 통합. 커스터마이징 제약 존재</td>
</tr>
</tbody></table>
<p>선택 기준은 사용 환경(웹/데스크톱/노트북), 인터랙티브 요구 수준, 그리고 분석 파이프라인과의 통합 여부에 따라 갈린다. 탐색적 분석에는 Gephi, 운영 환경의 대시보드 임베딩에는 D3.js, Neo4j 데이터의 즉시 시각화에는 Hume이 자연스러운 선택이 된다.</p>
<h2 id="5-한계와-트레이드오프">5. 한계와 트레이드오프</h2>
<p>그래프 데이터베이스가 모든 문제의 해답은 아니다. 본 챕터의 개념에서 도출 가능한 한계는 다음과 같다.</p>
<p><strong>관계가 희박한 데이터에서의 오버헤드.</strong> 그래프 DB의 강점은 relationship traversal에 있다. 데이터 포인트 간 연결이 적거나, 분석이 단순 lookup·aggregation 중심이라면 RDB나 document DB 대비 얻는 이점이 작다. 오히려 graph 모델링과 운영 비용이 부담으로 작용할 수 있다.</p>
<p><strong>대규모 분석 워크로드의 한계.</strong> Neo4j는 OLTP 성격의 그래프 traversal에 강하지만, 수십억 노드 규모의 그래프 알고리즘 일괄 실행은 별도의 분산 처리 엔진(Spark GraphX, Pregel 계열) 또는 Neo4j Graph Data Science 라이브러리의 클러스터 구성을 요구한다.</p>
<p><strong>의미 기반 검색의 부재.</strong> 그래프 traversal은 명시적으로 정의된 relationship을 따라 이동한다. &quot;비슷한 문서 찾기&quot;처럼 의미적 유사도가 핵심인 질의는 vector DB의 영역이다. 이 차이가 vector RAG와 graph RAG를 구분하는 근본 지점이며, 두 접근의 결합이 GraphRAG의 동기가 된다.</p>
<h2 id="6-실무-적용-시-고려사항">6. 실무 적용 시 고려사항</h2>
<p>그래프 DB 도입을 검토하는 실무 관점에서 몇 가지 고려사항을 정리한다.</p>
<p>첫째, 도메인의 질의 패턴을 먼저 분석한다. &quot;고객 A와 연결된 모든 거래의 2단계 거래 상대를 찾으라&quot;처럼 multi-hop traversal이 반복된다면 그래프 DB가 적합하다. 반면 &quot;지난달 매출 합계&quot;처럼 집계가 주된 질의라면 데이터 웨어하우스가 우선이다.</p>
<p>둘째, 단일 저장소 선택을 강제할 필요가 없다. 운영 데이터는 RDB에 두고, 그래프 분석용 view를 Neo4j에 별도 구축하거나, similarity search를 위해 vector DB를 병행하는 구성이 일반적이다. 본 시리즈 후속편에서 다룰 GraphRAG는 이 polyglot 전략의 구체적인 한 형태이다.</p>
<p>셋째, 그래프 모델링은 스키마 설계 단계에서 결정된다. 무엇을 node로 두고 무엇을 relationship으로 둘지, 어떤 property를 어디에 부여할지는 이후 질의 성능과 분석 가능성을 좌우한다. 관계형 모델링과 사고방식이 다르므로 초기 학습 비용이 발생한다.</p>
<blockquote>
<p>결론: 그래프 데이터베이스는 &quot;연결 그 자체가 데이터인 도메인&quot;에서 가장 큰 가치를 만들어내며, vector·관계형 저장소와 경쟁하기보다 보완하는 위치에 놓일 때 GraphRAG로 이어지는 자연스러운 토대가 된다.</p>
</blockquote>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li>Graph Data Science with Python and Neo4j. Chapter 1: Introduction to Graph Data Science.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[SEOCHO — AI에게 우리 회사 자료를 제대로 가르치는 방법]]></title>
            <link>https://velog.io/@tasker_dev/SEOCHO-AI%EC%97%90%EA%B2%8C-%EC%9A%B0%EB%A6%AC-%ED%9A%8C%EC%82%AC-%EC%9E%90%EB%A3%8C%EB%A5%BC-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EA%B0%80%EB%A5%B4%EC%B9%98%EB%8A%94-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@tasker_dev/SEOCHO-AI%EC%97%90%EA%B2%8C-%EC%9A%B0%EB%A6%AC-%ED%9A%8C%EC%82%AC-%EC%9E%90%EB%A3%8C%EB%A5%BC-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EA%B0%80%EB%A5%B4%EC%B9%98%EB%8A%94-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Thu, 30 Apr 2026 05:13:03 GMT</pubDate>
            <description><![CDATA[<p>ChatGPT한테 &quot;우리 회사 작년 계약서 내용 좀 정리해줘&quot;라고 물어봤다가, 알고 보니 절반은 지어낸 내용이었던 적 있으시죠? 또는 분명 회사 위키에 있는 내용인데도 AI가 &quot;그런 정보는 없어요&quot;라고 답해서 답답했던 경험도요.</p>
<p>이 문제, 사실 AI가 멍청해서가 아닙니다. <strong>AI가 우리 자료를 어떻게 읽어야 할지 모르기 때문</strong>입니다. 오늘은 이 문제를 정공법으로 풀어보려는 오픈소스 프로젝트, <strong>SEOCHO</strong>(서초)를 함께 살펴보겠습니다.</p>
<blockquote>
<p>한 줄 요약: 흩어진 문서를 &#39;연결된 지식&#39;으로 만들고, AI 에이전트가 안전하게 꺼내 쓰게 도와주는 미들웨어.</p>
</blockquote>
<br>

<h2 id="1-📚-seocho가-뭐하는-애야">1. 📚 SEOCHO가 뭐하는 애야?</h2>
<p>SEOCHO는 한마디로 <strong>&quot;도서관 사서&quot;</strong> 같은 존재입니다.</p>
<p>도서관에 책이 수만 권 있어도, 사서가 분류하지 않으면 그냥 종이 더미일 뿐이지요. 사서는 책마다 &quot;이건 소설, 저건 역사책, 이 작가는 저 작가의 제자&quot;라는 식으로 <strong>기준에 맞춰 정리</strong>해둡니다. 그래야 누군가 와서 &quot;조선시대 여성 작가 책 보여주세요&quot;라고 했을 때 정확히 꺼내올 수 있습니다.</p>
<p>SEOCHO가 하는 일도 똑같습니다. 다만 정리 대상이 책이 아니라 <strong>회사 문서·계약서·보고서</strong>고, 손님이 사람이 아니라 <strong>AI 에이전트</strong>(=시키는 일을 알아서 처리해주는 AI 일꾼)라는 점이 다를 뿐입니다.</p>
<p>여기서 핵심 단어 두 개만 짚고 가겠습니다.</p>
<ul>
<li><strong>온톨로지(Ontology)</strong>: 우리 분야에서 무엇을 무엇이라고 부를지 미리 정해둔 사전입니다. 예를 들어 &quot;직원은 회사에 <em>소속</em>되고, 회사는 제품을 <em>판매</em>한다&quot; 같은 규칙을 적어둔 약속집이죠.</li>
<li><strong>그래프 데이터베이스</strong>: 정보를 점(=대상)과 선(=관계)으로 연결해서 저장하는 창고입니다. 마치 사람 관계도처럼요.</li>
</ul>
<p>SEOCHO는 이 두 개를 묶어서, <strong>&quot;흩어진 문서를 연결된 지식으로 만들고, AI가 안전하게 꺼내 쓰게&quot; 도와주는 도구</strong>입니다. 만든 분은 GraphRAG 분야의 멘토 정이태(@tteon) 님이고, MIT 라이선스로 누구나 자유롭게 쓸 수 있습니다.</p>
<br>

<h2 id="2-🎯-어디에-쓰이는데">2. 🎯 어디에 쓰이는데?</h2>
<p>말로만 들으면 좀 추상적이죠. 실제 시나리오 3개로 풀어보겠습니다.</p>
<h3 id="1-💰-금융--계약서·채권-분석">(1) 💰 금융 — 계약서·채권 분석</h3>
<p>은행에서 일한다고 상상해보겠습니다. 채권 계약서 수백 장에서 &quot;이 채권의 발행자가 누구고, 만기는 언제고, 담보는 뭐고…&quot; 같은 정보를 뽑아야 합니다. 그런데 금융 업계에는 이미 <strong>FIBO</strong>라는 국제 표준 사전(=Financial Industry Business Ontology)이 있습니다. SEOCHO는 이 FIBO를 그대로 가져다 적용할 수 있습니다. &quot;처음부터 우리 회사 사전을 만드세요&quot;가 아니라, <strong>이미 검증된 업계 표준을 바로 쓸 수 있다</strong>는 점이 강점입니다.</p>
<h3 id="2-🏢-사내-지식-관리--부서-자동-연결">(2) 🏢 사내 지식 관리 — 부서 자동 연결</h3>
<p>&quot;마케팅팀이 작년에 진행한 캠페인 중에 법무팀 검토를 받은 게 뭐였지?&quot; 이런 질문, 사람이 답하려면 메일 뒤지고 슬랙 검색하고 난리도 아닙니다. SEOCHO는 문서들을 미리 그래프로 엮어두기 때문에 <strong>&quot;마케팅팀 → 캠페인 → 법무 검토&quot;</strong> 라는 관계를 따라가서 답을 찾아줍니다. 부서 간 협업 흔적이 자동으로 연결되는 셈입니다.</p>
<h3 id="3-🔬-연구·컴플라이언스--출처-추적">(3) 🔬 연구·컴플라이언스 — 출처 추적</h3>
<p>논문이나 규제 문서를 다룰 땐 &quot;그래서 그 주장이 <em>어느 문서 몇 페이지</em>에 나왔어?&quot;가 정말 중요합니다. SEOCHO는 답변마다 출처를 추적할 수 있도록 흔적(trace)을 남깁니다. AI가 그럴듯하게 지어내는 것이 아니라, <strong>&quot;이 답은 이 문서에서 나왔습니다&quot;</strong>라고 보여줄 수 있는 구조입니다.</p>
<br>

<h2 id="3-🗺️-vectorrag-vs-graphrag--왜-그래프인가">3. 🗺️ VectorRAG vs GraphRAG — 왜 그래프인가?</h2>
<p>여기서 잠깐, 비슷한 기술인 <strong>VectorRAG</strong>와 비교해보겠습니다. 둘 다 &quot;AI에게 우리 자료 알려주기&quot; 기술이지만 접근 방식이 다릅니다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>VectorRAG</th>
<th>GraphRAG (SEOCHO 방식)</th>
</tr>
</thead>
<tbody><tr>
<td>한 줄 설명</td>
<td>비슷한 글자 찾기</td>
<td>관계 따라 추론하기</td>
</tr>
<tr>
<td>비유</td>
<td>인덱스카드 더미</td>
<td>지하철 노선도</td>
</tr>
<tr>
<td>잘 하는 일</td>
<td>&quot;비슷한 문장 찾아줘&quot;</td>
<td>&quot;A에서 B 거쳐 C로 가는 길 찾아줘&quot;</td>
</tr>
<tr>
<td>약점</td>
<td>관계 추론이 어려움</td>
<td>사전 설계가 필요함</td>
</tr>
<tr>
<td>예시 질문</td>
<td>&quot;근로계약 관련 조항 보여줘&quot;</td>
<td>&quot;ACME가 인수한 회사가 또 인수한 회사는?&quot;</td>
</tr>
</tbody></table>
<p>VectorRAG는 <strong>카드 더미를 빠르게 뒤지는 방식</strong>입니다. &quot;계약&quot;이라는 단어와 비슷한 문장을 휙 찾아주는 데는 강합니다. 하지만 &quot;A 회사가 B를 인수했고, B는 C를 인수했는데 그럼 A가 결국 가진 자회사는?&quot; 같이 <strong>관계를 따라가야 답이 나오는 질문</strong>에서는 헷갈리기 시작합니다.</p>
<p>GraphRAG는 <strong>지하철 노선도</strong>처럼 점과 선을 따라갑니다. 강남역에서 시청역 가는 길이 노선도에 그려져 있듯, 회사·인물·계약 사이의 관계를 따라 답을 추론할 수 있습니다. SEOCHO는 이 그래프 방식을 채택해서, 단순 검색을 넘어 <strong>&quot;이어진 지식&quot;을 다룰 수 있게</strong> 만들어줍니다.</p>
<br>

<h2 id="4-⚙️-seocho의-작동-방식-4단계">4. ⚙️ SEOCHO의 작동 방식 4단계</h2>
<p>저장소에 있는 흐름도를 일상 언어로 풀어보겠습니다. 이번에도 도서관 사서 비유를 그대로 가져갈게요.</p>
<p><strong>📄 (1) 내 문서 입력</strong> → 사서에게 책 한 박스를 통째로 가져다주는 단계입니다. PDF든 마크다운이든 CSV든, &quot;이거 정리해주세요&quot; 하고 던져주면 됩니다.</p>
<p><strong>🔍 (2) 추출</strong> → 사서가 책을 한 권씩 펼쳐서 &quot;저자는 누구, 주제는 뭐, 등장인물은 누구&quot;를 메모하는 단계입니다. SEOCHO에서는 LLM(=대형 언어 모델, 즉 GPT 같은 AI)이 이 일을 합니다. 문서에서 핵심 정보를 자동으로 뽑아내죠.</p>
<p><strong>✅ (3) 검증</strong> → &quot;이 책은 분명 소설이라고 했는데 왜 저자 자리에 출판사 이름이 적혀 있지?&quot; 같은 오류를 잡아내는 단계입니다. 미리 정해둔 온톨로지(=사전)와 어긋나는 정보가 들어오면 걸러줍니다. 잘못된 정보가 그대로 저장되는 사고를 막아주는 안전장치입니다.</p>
<p><strong>🗄️ (4) 그래프 DB에 저장 → 💬 (5) 질문하면 답변</strong> → 검증을 통과한 정보만 그래프 데이터베이스에 차곡차곡 쌓입니다. 그러고 나면 누군가 &quot;올해 우리가 인수한 회사 알려줘&quot;라고 물었을 때, 사서(=SEOCHO)가 정리된 노선도를 따라 정합성이 보장된 답을 가져다줍니다.</p>
<br>

<h2 id="5-💻-실제-코드는-얼마나-간단한가">5. 💻 실제 코드는 얼마나 간단한가?</h2>
<p>여기서 짧게 코드 한 토막만 보고 가겠습니다. 비개발자라면 &quot;아 이 정도면 개발자 한 명한테 부탁해서 며칠 안에 시도해볼 수 있겠구나&quot; 정도로만 느끼시면 됩니다. 저장소 README의 hello world 예제를 그대로 가져왔습니다.</p>
<pre><code class="language-python">ontology = Ontology(...)              # 1. 우리 도메인의 &#39;단어장&#39;을 만들고
s = Seocho.local(ontology)            # 2. SEOCHO에 넘겨주고
s.add(&quot;마리 퀴리는 파리 대학교에서 일했다.&quot;)  # 3. 문장 하나 던져주면
print(s.ask(&quot;마리 퀴리는 어디서 일했나요?&quot;))   # 4. 자연스럽게 답해줍니다</code></pre>
<p>핵심은 <strong>정말 4줄</strong>이라는 점입니다. 사전을 정의하고, 도구에 넘기고, 문서를 넣고, 질문하면 끝. 별도의 서버 설치도 필요 없습니다. 내 노트북 안에서 그대로 돌아갑니다.</p>
<br>

<h2 id="6-⭐-무엇이-차별점인가">6. ⭐ 무엇이 차별점인가?</h2>
<p>비슷한 도구가 많은데 SEOCHO만의 차별점은 뭘까요? 세 가지로 정리해보겠습니다.</p>
<p><strong>(1) 온톨로지 우선(Ontology-first)</strong> — 일반 RAG는 즉흥적입니다. AI가 그때그때 알아서 해석합니다. SEOCHO는 다릅니다. <strong>먼저 약속을 정합니다.</strong> &quot;우리 회사에서 &#39;고객&#39;은 이런 뜻이고, &#39;계약&#39;은 이런 속성을 가진다&quot;라고 사전에 합의해두기 때문에, 부서마다 다른 해석으로 헷갈릴 일이 줄어듭니다.</p>
<p><strong>(2) 검증 가능(Verifiable)</strong> — 모든 답변에 출처와 흔적이 남습니다. AI가 &quot;이 답은 어디서 나왔어?&quot;라는 질문에 &quot;이 문서 이 위치에서 나왔습니다&quot;라고 답할 수 있다는 뜻입니다. 컴플라이언스가 중요한 회사일수록 이 점이 결정적입니다.</p>
<p><strong>(3) 로컬 우선(Local-first)</strong> — 데이터를 외부 클라우드로 보내지 않고 <strong>내 컴퓨터 안에서 그대로 돌릴 수 있습니다.</strong> 회사 기밀 문서를 외부 서비스에 올리기 부담스러운 환경에서 특히 매력적인 부분입니다.</p>
<br>

<h2 id="7-🌱-마무리--누가-써보면-좋을까">7. 🌱 마무리 — 누가 써보면 좋을까?</h2>
<p>다음과 같은 분이라면 한번 살펴볼 만합니다.</p>
<ul>
<li>사내에 흩어진 문서를 AI로 연결해보고 싶은 <strong>기획자·PM</strong></li>
<li>출처가 명확해야 하는 영역에서 일하는 <strong>금융·법무·연구 직군</strong></li>
<li>VectorRAG는 써봤는데 &quot;관계 질문&quot;에 약해서 답답했던 <strong>개발자</strong></li>
</ul>
<blockquote>
<p>흩어진 문서가 &#39;연결된 지식&#39;으로 변하는 순간, AI는 비로소 우리 회사를 이해하기 시작합니다.</p>
</blockquote>
<p>저장소: <a href="https://github.com/tteon/seocho">https://github.com/tteon/seocho</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Prompt Engineering과 Fine-tuning 두 갈래로 본 LLM 기반 Text-to-SQL]]></title>
            <link>https://velog.io/@tasker_dev/Prompt-Engineering%EA%B3%BC-Fine-tuning-%EB%91%90-%EA%B0%88%EB%9E%98%EB%A1%9C-%EB%B3%B8-LLM-%EA%B8%B0%EB%B0%98-Text-to-SQL</link>
            <guid>https://velog.io/@tasker_dev/Prompt-Engineering%EA%B3%BC-Fine-tuning-%EB%91%90-%EA%B0%88%EB%9E%98%EB%A1%9C-%EB%B3%B8-LLM-%EA%B8%B0%EB%B0%98-Text-to-SQL</guid>
            <pubDate>Wed, 29 Apr 2026 05:56:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Prompt Engineering과 Fine-tuning을 두 축으로 LLM 기반 Text-to-SQL의 전체 파이프라인을 체계화한 ACM Computing Surveys 서베이</p>
</blockquote>
<h2 id="0-논문-개요">0. 논문 개요</h2>
<h3 id="01-한눈에-보기">0.1 한눈에 보기</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>제목</td>
<td>A Survey on Employing Large Language Models for Text-to-SQL Tasks</td>
</tr>
<tr>
<td>저자</td>
<td>Liang Shi, Zhengju Tang, Nan Zhang, Xiaotong Zhang, Zhi Yang</td>
</tr>
<tr>
<td>소속</td>
<td>Peking University, SINGDATA CLOUD</td>
</tr>
<tr>
<td>발표</td>
<td>ACM Computing Surveys (2025년 5월 수락, v5는 6월 갱신)</td>
</tr>
<tr>
<td>분야</td>
<td>NLP, Database, LLM 응용</td>
</tr>
<tr>
<td>핵심 키워드</td>
<td>Large Language Models, Text-to-SQL, Prompt Engineering, Fine-tuning</td>
</tr>
</tbody></table>
<h3 id="02-사전-지식">0.2 사전 지식</h3>
<p>이 서베이를 효과적으로 읽기 위해 필요한 개념을 정리한다.</p>
<ul>
<li><strong>Prompt Engineering</strong>: 모델 파라미터를 갱신하지 않고 입력 프롬프트만 설계해 LLM 행동을 제어하는 기법.</li>
<li><strong>Fine-tuning</strong>: 사전학습된 LLM을 특정 도메인(여기선 Text-to-SQL)의 데이터로 추가 학습.</li>
<li><strong>In-Context Learning (ICL)</strong>: 프롬프트 안에 제공된 예시만으로 새 과제를 학습하는 LLM의 emergent ability.</li>
<li><strong>Chain-of-Thought (CoT)</strong>: 추론 과정을 명시적으로 단계별로 풀어내는 프롬프트 패턴.</li>
<li><strong>Least-to-Most</strong>: 복잡한 문제를 더 쉬운 서브문제로 환원해 푸는 프롬프트 전략.</li>
<li><strong>Self-Consistency / Cross-Consistency</strong>: 같은 모델이나 여러 모델이 만든 다수 답안을 투표로 종합.</li>
<li><strong>LoRA / QLoRA</strong>: 일부 파라미터만 학습해 fine-tuning 비용을 줄이는 PEFT 기법.</li>
<li><strong>Spider, BIRD, Spider 2.0</strong>: 각 시대를 대표하는 Text-to-SQL 벤치마크.</li>
<li><strong>Schema Linking</strong>: NL의 표현을 DB의 테이블·컬럼으로 매핑하는 핵심 단계.</li>
</ul>
<h3 id="03-논문-구조">0.3 논문 구조</h3>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/28464230-09aa-4f94-b668-f7acb5af233b/image.png" alt=""></p>
<hr>
<h2 id="1-abstract--introduction">1. Abstract &amp; Introduction</h2>
<h3 id="11-논문이-풀고자-하는-문제">1.1 논문이 풀고자 하는 문제</h3>
<p>이 서베이가 답하려는 질문은 한 줄로 요약할 수 있다.</p>
<blockquote>
<p>LLM 시대에 Text-to-SQL을 푸는 방법은 어떻게 분류·이해해야 하며, 각 방법은 언제 어떻게 써야 하는가?</p>
</blockquote>
<p>저자들은 LLM 활용을 <strong>프롬프트 엔지니어링</strong>과 <strong>파인튜닝</strong> 두 갈래로 명확히 가르고, 각 갈래의 전체 파이프라인을 체계화한다. 이는 두 갈래가 단순한 alternative가 아니라 trade-off 관계임을 부각한다.</p>
<h3 id="12-기존-접근의-한계">1.2 기존 접근의 한계</h3>
<p>전통적 Text-to-SQL은 두 흐름이 있었다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/5d883f89-80d1-426d-bb90-dd2aa6af958f/image.png" alt=""></p>
<p>저자들은 전통적 방법과 LLM 기반 방법의 차이를 두 가지로 압축한다.</p>
<table>
<thead>
<tr>
<th>비교 축</th>
<th>전통 방법</th>
<th>LLM 방법</th>
</tr>
</thead>
<tbody><tr>
<td>패러다임</td>
<td>학습 필수</td>
<td>프롬프트만으로 가능</td>
</tr>
<tr>
<td>아키텍처</td>
<td>LSTM·Transformer·GNN 혼재</td>
<td>통일된 transformer 디코더</td>
</tr>
</tbody></table>
<p>특히 <strong>&quot;학습 없이도 가능&quot;</strong>은 LLM의 instruction-following 능력에서 나오는 본질적 변화다. 데이터 라벨링 비용·재학습 비용을 거의 0으로 만들 수 있다는 의미다.</p>
<h3 id="13-이-논문의-기여">1.3 이 논문의 기여</h3>
<p>저자들이 명시한 기여는 다음과 같다.</p>
<ol>
<li><strong>체계적 분류 체계 제시</strong>: Prompt Engineering의 3단계(pre/inference/post)와 Fine-tuning의 4축(목표·방법·데이터·평가)을 격자로 정리.</li>
<li><strong>각 절 끝의 Key Takeaways</strong>: 단순 정리가 아닌 실무 적용 가능한 요약을 명시. 각 섹션의 마지막에 &quot;이것만은 챙겨라&quot;를 박스로 제시한다.</li>
<li><strong>모델 사용 트렌드 분석</strong>: 28개 LLM의 사용 빈도와 시간에 따른 변화를 정량 분석. 클로즈드와 오픈소스의 비중 변화가 두드러진다.</li>
<li><strong>세 벤치마크 비교 분석</strong>: Spider 1.0, BIRD, Spider 2.0의 리더보드 데이터를 직접 비교하며 트렌드를 도출.</li>
<li><strong>실용적 미래 방향</strong>: 프라이버시, 복잡 스키마, 도메인 지식, 자율 에이전트, 데이터 거버넌스의 5가지 축으로 미래 도전 정리.</li>
</ol>
<h3 id="14-논문의-위치">1.4 논문의 위치</h3>
<p>이 서베이는 같은 시기 발표된 다른 LLM Text-to-SQL 서베이들[43, 68, 139, 147]과 차별화된다. 특히 Liu et al.의 HKUST 서베이(2024)가 모듈러 시스템 관점이라면, 이 서베이는 <strong>Prompt Engineering vs Fine-tuning의 이항 대립</strong>을 강하게 밀고 나간다. 모듈을 잘게 쪼개기보다는, 실무자가 어느 갈래로 갈지 의사결정하는 데 도움을 주는 구조다.</p>
<hr>
<h2 id="2-overview-llm과-llm-기반-text-to-sql">2. Overview: LLM과 LLM 기반 Text-to-SQL</h2>
<h3 id="21-llm의-두-emergent-ability">2.1 LLM의 두 emergent ability</h3>
<p>LLM은 PLM의 단순 확장이 아니라 emergent abilities를 갖춘 새로운 패러다임이다. 본 논문은 두 가지를 강조한다.</p>
<ul>
<li><strong>Few-shot learning</strong>: 프롬프트에 몇 개 예시만 주면 새 과제를 수행</li>
<li><strong>Instruction following</strong>: 자연어 지시만으로 unseen task에 적응</li>
</ul>
<p>이 두 능력이 Text-to-SQL에서 특히 강력하다. 왜냐하면 Text-to-SQL은 (a) 도메인마다 스키마가 달라 학습 데이터가 부족하고 (b) 자연어 지시로 SQL 작성 규칙을 알려주기 쉬운 과제이기 때문이다.</p>
<h3 id="22-자기회귀-디코딩-관점">2.2 자기회귀 디코딩 관점</h3>
<p>논문은 LLM의 작동을 다음 수식으로 형식화한다.</p>
<p>$$
y_t = \arg\max P(y_t \mid y_{1:t-1}, x)
$$</p>
<p>여기서 $x$는 프롬프트, $y_t$는 다음 토큰. 이 수식의 함의는 <strong>프롬프트 설계가 곧 출력 분포 설계</strong>라는 점이다. 즉 프롬프트 엔지니어링은 단순한 트릭이 아니라 분포를 형성하는 행위다.</p>
<h3 id="23-왜-llm-기반-text-to-sql인가">2.3 왜 LLM 기반 Text-to-SQL인가</h3>
<p>저자들은 세 가지 이유를 든다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/bd53bc3c-faeb-4c0f-b520-2ed3952b5fc5/image.png" alt=""></p>
<p>특히 일반화 능력이 결정적이다. 새 도메인마다 학습할 필요 없이 프롬프트만 바꾸면 된다는 점은 산업적으로 매우 큰 가치다.</p>
<h3 id="24-전체-파이프라인">2.4 전체 파이프라인</h3>
<p>논문은 LLM 기반 Text-to-SQL을 두 갈래로 분리한다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/19b4d944-0a13-419b-9be8-5c143b4a0af6/image.png" alt=""></p>
<p>이 분기는 단순한 기술 선택이 아니라 <strong>데이터·자원·프라이버시 제약에 따른 의사결정</strong>이라는 시각이 깔려 있다.</p>
<hr>
<h2 id="3-벤치마크와-평가-지표">3. 벤치마크와 평가 지표</h2>
<h3 id="31-벤치마크-분류">3.1 벤치마크 분류</h3>
<p>논문은 벤치마크를 LLM 등장 이전과 이후로 가른다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/a3ed28ef-72ff-499c-8861-2facc31818f5/image.png" alt=""></p>
<p>LLM 시대 벤치마크는 다음 새 도전에 초점을 맞춘다.</p>
<ul>
<li><strong>도메인 특화 지식</strong>: ScienceBenchmark는 천체물리학, 암 연구, 정책 결정 같은 실제 도메인에서 SQL 전문가와 협업해 만들었다. 기존 데이터셋의 한계, 즉 일반 도메인에서 학습한 시스템이 특수 도메인에서 곧바로 무너진다는 문제를 정면으로 본다.</li>
<li><strong>더티 데이터·도메인 지식·SQL 효율</strong>: BIRD는 33.4GB 규모, 95개 DB, 37개 도메인. GPT-4가 54.89%인 반면 인간은 92.96%로, 격차가 여전히 크다.</li>
<li><strong>17종 견고성 perturbation</strong>: Dr.Spider는 가장 강한 모델조차 평균 14% 성능 하락, 가장 어려운 perturbation에선 50.7% 하락을 보인다.</li>
<li><strong>엔터프라이즈 워크플로우</strong>: Spider 2.0은 BigQuery, Snowflake, PostgreSQL 같은 실제 클라우드 DB. 1,000개 이상 컬럼을 가진 DB도 포함.</li>
<li><strong>오류 진단</strong>: BIRD-Critic 1.0은 SQL 생성을 넘어 SQL 디버깅 능력을 평가. 가장 강한 o1-preview조차 38.5% pass rate로, 이 새 axis는 모델들에게 어렵다.</li>
</ul>
<h3 id="32-평가-지표">3.2 평가 지표</h3>
<p>논문은 다섯 지표를 다룬다.</p>
<p><strong>Exact Set Match Accuracy (EM)</strong>: SQL 절 단위 문자열 일치. 같은 의미를 다른 표현으로 쓴 SQL을 모두 틀렸다고 처리해 <strong>과소평가</strong> 위험이 있다.</p>
<p><strong>Execution Accuracy (EX)</strong>: 실행 결과 비교. 의미가 다른데 우연히 같은 결과를 내는 경우 <strong>과대평가</strong> 위험이 있다.</p>
<p><strong>Test-suite Accuracy (TS)</strong>: 무작위 생성된 다수 DB 중 코드 커버리지 높은 작은 테스트 스위트로 평가. 의미적 정확도의 strict upper bound를 측정한다.</p>
<p><strong>Valid Efficiency Score (VES)</strong>: BIRD에서 도입한 효율 지표.</p>
<p>$$
\text{VES} = \frac{\sum_{n=1}^{N} \mathbf{1}(V_n, \hat{V}_n) \cdot R(Y_n, \hat{Y}_n)}{N}, \quad R(Y_n, \hat{Y}_n) = \sqrt{\frac{E(Y_n)}{E(\hat{Y}_n)}}
$$</p>
<p>여기서 $\mathbf{1}$은 정답일 때만 1을 반환하고, $R$은 실행 시간 비율의 제곱근이다. 정확하면서도 빠른 SQL을 보상한다.</p>
<p><strong>ESM+</strong>: EM의 개선판. LEFT/RIGHT JOIN, OUTER/INNER JOIN, DISTINCT, LIMIT, IN, foreign key, schema check, alias 등에 대한 새 규칙을 추가해 false positive와 false negative를 줄인다.</p>
<p>지표들의 trade-off를 정리하면 다음과 같다.</p>
<table>
<thead>
<tr>
<th>지표</th>
<th>강점</th>
<th>약점</th>
</tr>
</thead>
<tbody><tr>
<td>EM</td>
<td>엄밀함</td>
<td>표현 다양성 처벌</td>
</tr>
<tr>
<td>EX</td>
<td>실행 검증</td>
<td>의미 다른데 같은 결과</td>
</tr>
<tr>
<td>TS</td>
<td>의미 정확도 상한</td>
<td>테스트 스위트 구축 비용</td>
</tr>
<tr>
<td>VES</td>
<td>효율 반영</td>
<td>환경 의존성</td>
</tr>
<tr>
<td>ESM+</td>
<td>EM 보완</td>
<td>새 규칙 익혀야 함</td>
</tr>
</tbody></table>
<h3 id="33-벤치마킹-연구">3.3 벤치마킹 연구</h3>
<p>저자들은 서베이 외 <strong>벤치마킹 연구</strong>들도 정리한다.</p>
<ul>
<li>Rajkumar et al.(2022): Codex와 GPT-3로 프롬프트 구성 비교</li>
<li>DAIL-SQL의 모태가 된 비교 연구: question representation, example selection, organization</li>
<li>Zhang et al.(2024): Text-to-SQL, SQL Debugging, SQL Optimization, Schema Linking, SQL-to-Text 5종 과제</li>
<li>DB-GPT-Hub: 오픈소스 모델 fine-tuning에 특화된 모듈러 코드베이스</li>
</ul>
<p>저자들은 빠른 모델·기법 변화 때문에 <strong>수치 비교는 빠르게 낡는다</strong>고 경고한다. 이 경고는 모든 LLM 서베이의 본질적 한계이기도 하다.</p>
<hr>
<h2 id="4-prompt-engineering-3단계-파이프라인">4. Prompt Engineering: 3단계 파이프라인</h2>
<h3 id="41-전체-구조">4.1 전체 구조</h3>
<p>논문의 핵심 분류다. 전체 파이프라인을 다음과 같이 형식화한다.</p>
<p>$$
\text{pred_SQL} = \text{Post_process}(\text{LLM}(\text{QuestionRep}, \text{Demonstration}, \text{Reasoning}))
$$</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/31fae5dd-a96c-48e1-a233-518b0dd8f1f6/image.png" alt=""></p>
<p>세 단계를 차례로 풀어본다.</p>
<h3 id="42-pre-processing">4.2 Pre-processing</h3>
<h4 id="421-question-representation">4.2.1 Question Representation</h4>
<p>질문 자체와 DB 정보를 어떻게 프롬프트로 표현할지 결정하는 단계다. 세 요소로 분해된다.</p>
<p><strong>(a) Layouts</strong>: 두 가지가 표준이다.</p>
<ul>
<li><strong>OpenAI Template</strong>: SQLite 주석 형태로 <code>Table(Column1, Column2, ...)</code> 줄을 나열하고 마지막에 <code>SELECT</code>로 시작</li>
<li><strong>CREATE TABLE Layout</strong>: 실제 DDL인 <code>CREATE TABLE</code> 문으로 데이터 타입과 PK/FK까지 명시</li>
</ul>
<p><strong>(b) Sample Data</strong>: 실제 DB 콘텐츠 몇 행을 함께 제공. <code>SELECT * FROM Table LIMIT 3</code> 같은 형식. 데이터의 형식과 분포를 모델이 이해하게 만든다.</p>
<p><strong>(c) Knowledge</strong>: 두 종류다.</p>
<ul>
<li><strong>SQL Knowledge</strong>: SQL 키워드·문법·작성 관습. 예: C3는 &quot;COUNT(*)는 특정 경우만, LEFT JOIN/IN/OR 피하고 JOIN/INTERSECT 사용, DISTINCT/LIMIT 권장&quot; 같은 명시적 가이드를 프롬프트에 포함</li>
<li><strong>External Knowledge</strong>: 도메인 용어·약어·은어. BIRD의 evidence가 대표적이고, CHESS는 컨텍스트 검색으로 DB 카탈로그·테이블·컬럼의 설명·약어를 가져온다</li>
</ul>
<h4 id="422-분석-layout-선택의-의미">4.2.2 분석: Layout 선택의 의미</h4>
<p>논문이 정리한 실험 결과를 종합하면 다음과 같다.</p>
<table>
<thead>
<tr>
<th>변경</th>
<th>영향</th>
</tr>
</thead>
<tbody><tr>
<td>구조화 → 비구조화 layout</td>
<td>큰 성능 하락 (C3)</td>
</tr>
<tr>
<td>OpenAI Template ↔ CREATE TABLE</td>
<td>거의 동등 (DAIL-SQL은 OpenAI 우세, QDecomp는 동등)</td>
</tr>
<tr>
<td>Sample Data 추가</td>
<td>일반적으로 도움 (단 너무 많으면 역효과)</td>
</tr>
<tr>
<td>Foreign Key 추가</td>
<td>일반적으로 도움 (PK/FK는 멀티테이블 추론의 핵심)</td>
</tr>
<tr>
<td>External Knowledge 추가</td>
<td>SQLfuse, DEA-SQL 모두 큰 개선</td>
</tr>
</tbody></table>
<p>핵심 통찰은 <strong>layout 자체보다 그 안에 든 정보(PK/FK, sample data, knowledge)가 결정적</strong>이라는 점이다.</p>
<h4 id="423-schema-linking">4.2.3 Schema Linking</h4>
<p>Pre-processing의 또 다른 핵심 모듈이다. 큰 DB에서 모든 테이블을 프롬프트에 넣을 수 없기 때문에 관련 부분만 추려야 한다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/00184daf-d931-494a-aa60-02e301d36d3f/image.png" alt=""></p>
<p><strong>LLM 기반 접근의 세 갈래</strong>:</p>
<p>(1) <strong>단계별 프롬프트</strong>: 직접 시키되 효율을 위해 분할</p>
<ul>
<li>C3, MCS-SQL: 테이블 → 컬럼 두 단계</li>
<li>DEA-SQL: 질문 요소 식별 → 스키마 필터링</li>
<li>CHESS: 컬럼 필터링 → 테이블 선택 → 최종 컬럼 필터링의 3단계</li>
<li>PET-SQL: 흥미로운 역접근 - <strong>먼저 SQL을 그려보고 그 SQL에서 테이블·컬럼 추출</strong>. LLM이 SQL 작성을 schema linking보다 잘한다는 통찰이다</li>
<li>RSL-SQL: bidirectional, 전체 스키마와 LLM이 만든 SQL 모두 고려</li>
<li>E-SQL: pre-generated SQL을 NL에 직접 통합</li>
</ul>
<p>(2) <strong>일반 LLM 기법 활용</strong>: few-shot, CoT, self-consistency, fine-tuning</p>
<ul>
<li>DIN-SQL: random examples + &quot;Let&#39;s think step by step&quot;</li>
<li>C3, MCS-SQL: self-consistency 투표</li>
<li>ACT-SQL: 임베딩 모델로 phrase-schema 관계를 식별, CoT 형식의 예시 구성</li>
<li>SQLfuse: schema linking을 직접 fine-tuning</li>
</ul>
<p>(3) <strong>전통적 방법과 결합</strong>:</p>
<ul>
<li>CRUSH4SQL: LLM이 hallucinate한 schema를 기준 삼아 유사도 기반 검색</li>
<li>OpenSearch-SQL: LLM이 선택한 schema에 vector retrieval로 보완</li>
</ul>
<p><strong>전통적 접근의 두 갈래</strong>:</p>
<p>(a) <strong>유사도 기반</strong>: BERT, RoBERTa 같은 PLM으로 query와 schema를 임베딩해 유사도 비교. De-semanticization은 직접 매칭 + DB 값 매칭을 결합</p>
<p>(b) <strong>연결성 기반</strong>:</p>
<ul>
<li>DBCopilot: DB·테이블 그래프 + Seq2Seq 라우터</li>
<li>PURPLE: PK-FK 그래프</li>
<li>SGU-SQL: query-schema 통합 그래프</li>
</ul>
<h4 id="424-distillery의-통찰">4.2.4 Distillery의 통찰</h4>
<p>흥미로운 반론이 있다. Distillery는 <strong>모델의 SQL 생성 능력이 향상될수록 무관한 컬럼이 컨텍스트에 있어도 영향이 줄어든다</strong>고 보여준다. 즉 컨텍스트 윈도우가 커지고 모델이 강해질수록 schema linking의 비용 대비 가치가 떨어진다는 것이다. 그럼에도 실서비스 DB는 컨텍스트 한계를 초과하기 때문에 여전히 필요하다.</p>
<blockquote>
<p>Key Takeaways</p>
<ul>
<li>대부분의 시스템은 CoT 또는 decomposition을 기본 워크플로우로 채택</li>
<li>LLM 기반 schema linking의 주류는 단계별 프롬프트와 일반 LLM 기법 활용</li>
<li>유사도와 연결성 모두 고려할 가치가 있음</li>
</ul>
</blockquote>
<h3 id="43-inference">4.3 Inference</h3>
<h4 id="431-workflow">4.3.1 Workflow</h4>
<p>복잡한 Text-to-SQL을 단발 LLM 호출로 푸는 것은 무리다. 따라서 워크플로우 설계가 중요하다. 네 종류로 분류된다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/085773a4-108d-4c15-a906-65b38837cb41/image.png" alt=""></p>
<p><strong>(1) Chain-of-Thought (CoT)</strong></p>
<p>전통적 CoT는 &quot;Let&#39;s think step by step&quot;으로 추론을 유도한다. Text-to-SQL에 응용된 변형들은 다음과 같다.</p>
<ul>
<li><strong>DIN-SQL</strong>: 복잡 질의에 대해 인간이 설계한 CoT 단계</li>
<li><strong>Divide-and-Prompt</strong>: 절(clause) 단위 SQL 생성</li>
<li><strong>CoE-SQL</strong>: Chain-of-Edition. 14개 SQL 편집 규칙(SELECT 항목 편집, WHERE 논리 연산자 편집 등)</li>
<li><strong>ACT-SQL</strong>: Auto-CoT - CoT 예시를 자동 생성해 라벨링 비용 감소</li>
<li><strong>Open-SQL</strong>: skeleton 기반 query framework를 중간 표현으로 사용</li>
</ul>
<p><strong>(2) Least-to-Most</strong></p>
<p>서브문제로의 환원이 핵심이다. CoT가 SQL 자체를 단계화한다면, Least-to-Most는 NL 질문을 더 단순한 질문으로 환원한다. LTMP-DA-GP가 대표 사례로, NL을 분해하고 NatSQL에 매핑한 뒤 SQL로 변환한다.</p>
<p><strong>(3) Decomposition</strong></p>
<p>작업 자체를 여러 LLM 상호작용으로 분해한다. 병렬과 순차 두 형태가 있다.</p>
<ul>
<li><strong>DIN-SQL (병렬 분해)</strong>: SQL을 Easy/Nested Complex/Non-Nested Complex로 분류해 각각 다른 처리</li>
<li><strong>MAC-SQL (순차 분해)</strong>: Selector → Decomposer → Refiner 3-agent. Decomposer가 sub-question과 sub-SQL을 만들고 최종 SQL로 통합</li>
<li><strong>DEA-SQL</strong>: Information determination → Classification &amp; Hint → SQL generation → Self-correction → Active learning</li>
<li><strong>OpenSearch-SQL</strong>: Preprocessing → Extraction → Generation → Refinement → Alignment 5모듈</li>
<li><strong>R³</strong>: SQL Writer + 여러 Reviewer의 negotiation 기반 합의</li>
</ul>
<p><strong>(4) Autonomous Agents</strong></p>
<p>ReAct 프레임워크에 기반한 자율 에이전트. <strong>다른 워크플로우와 차별화되는 핵심</strong>은 다음과 같다.</p>
<ul>
<li>다중 턴 상호작용</li>
<li>긴 추론 시간</li>
<li>장기 기억</li>
<li>조건부 종료</li>
</ul>
<p>대표 사례:</p>
<ul>
<li><strong>Spider-Agent (Spider 2.0)</strong>: 커맨드라인 인터페이스로 DB와 다중 턴 상호작용. 같은 결과를 3번 출력하거나 timeout이면 자동 종료</li>
<li><strong>REFORCE</strong>: self-refinement + 도메인 특화 기법(table compression, format restriction). self-consistency, max iterations, empty result로 종료</li>
</ul>
<h4 id="432-워크플로우-비교">4.3.2 워크플로우 비교</h4>
<table>
<thead>
<tr>
<th>워크플로우</th>
<th>적합한 시나리오</th>
</tr>
</thead>
<tbody><tr>
<td>CoT</td>
<td>SQL을 단계별로 생성, 인간 사고 과정 모방</td>
</tr>
<tr>
<td>Least-to-Most</td>
<td>서브문제 환원이 자연스러운 질문</td>
</tr>
<tr>
<td>Decomposition</td>
<td>사전 준비(스키마 링킹)나 후처리(refinement)가 필요할 때</td>
</tr>
<tr>
<td>Autonomous Agents</td>
<td>실세계 복잡 과제, DB 동적 탐색 필요</td>
</tr>
</tbody></table>
<h4 id="433-demonstrations">4.3.3 Demonstrations</h4>
<p>워크플로우와 함께 시연(예시)도 핵심이다. Zero-shot vs Few-shot 분류 위에서 demonstration의 역할은 두 가지다.</p>
<p><strong>(a) 작업 설명 대체</strong>: DIN-SQL의 fine-designed 단계처럼 인간 언어로 설명하기 어려운 워크플로우를 예시로 보여준다. BINDER도 SQL+API 호출이라는 확장 언어를 예시로 가르친다.</p>
<p><strong>(b) SQL 코딩 능력 증대</strong>: 적절한 예시 선택이 핵심이다. 단순 질문 유사도만으론 부족하다. 같은 의도라도 스키마가 다르면 SQL이 크게 다르기 때문이다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/1aa039bd-ba75-489b-8346-4132d6ac10f5/image.png" alt=""></p>
<p>대표 전략들:</p>
<ul>
<li><strong>De-semanticization, Retrieval &amp; Revision, DAIL-SQL, DEA-SQL, PURPLE</strong>: 도메인 특화 단어를 마스킹한 skeleton으로 검색. PURPLE은 4단계 SQL skeleton 추상화</li>
<li><strong>Retrieval &amp; Revision</strong>: 원래 질문 + LLM이 단순화한 질문 모두 사용</li>
<li><strong>DAIL-SQL</strong>: 질문 + SQL 모두로 검색. 토큰 비용을 위해 (질문, SQL)만 포함하는 압축 형식 채택</li>
<li><strong>Open-SQL</strong>: 질문 + 스키마 + SQL 모두 사용</li>
<li><strong>OpenSearch-SQL</strong>: mask question similarity</li>
<li><strong>MCS-SQL</strong>: 질문 유사도 + masked 질문 유사도 두 전략 결합</li>
<li><strong>ACT-SQL</strong>: 무작위 + 유사 예시 혼합으로 다양성 확보</li>
</ul>
<blockquote>
<p>Key Takeaways</p>
<ul>
<li>복잡한 워크플로우를 가진 시스템은 demonstration을 task description의 일부로 활용</li>
<li>Question skeleton이 원본 질문보다 의도를 더 잘 잡음</li>
<li>정확도와 토큰 비용 사이의 trade-off는 항상 고려</li>
</ul>
</blockquote>
<h3 id="44-post-processing">4.4 Post-processing</h3>
<h4 id="441-self-correction">4.4.1 Self-Correction</h4>
<p>생성된 SQL을 다시 검토해 수정하는 단계다.</p>
<ul>
<li><strong>공백 처리</strong>: Generic은 테이블 값의 잉여 공백을 다시 확인</li>
<li><strong>재시도</strong>: RSL-SQL, OpenSearch-SQL은 빈 결과나 max round까지 재생성</li>
<li><strong>스키마 확장 후 재시도</strong>: Retrieval 계열은 부분 schema가 실패하면 전체 schema로 재생성</li>
<li><strong>에러 로그 활용</strong>: DIN-SQL, CHESS, MAC-SQL은 incorrect SQL과 실행 에러 정보를 프롬프트로 주고 재생성</li>
<li><strong>DEA-SQL</strong>: 필드 매칭과 SQL 문법의 오류 포인트별 특화 프롬프트</li>
<li><strong>SQLfuse SQL Critic</strong>: 외부 SQL 지식 베이스 + few-shot ICL로 hindsight feedback</li>
</ul>
<h4 id="442-consistency-methods">4.4.2 Consistency Methods</h4>
<p><strong>Self-Consistency</strong>: 같은 LLM이 다양한 SQL을 생성하고 다수결.</p>
<ul>
<li>직접 다수결: Open-SQL, BINDER, C3, DAIL-SQL, CHESS, OpenSearch-SQL 등 다수</li>
<li>실행 결과 기반 다수결: PURPLE</li>
<li>Confidence + 실행 시간으로 선택: MCS-SQL</li>
</ul>
<p><strong>Cross-Consistency</strong>: 서로 다른 LLM/agent가 답을 만들고 비교.</p>
<ul>
<li><strong>PET-SQL</strong>: 여러 LLM이 낮은 temperature로 SQL 생성 후 실행 결과로 투표</li>
<li><strong>R³</strong>: 다중 agent의 negotiation. Cross-Consistency + Self-Correction의 결합</li>
<li><strong>CHESS</strong>: Self-Correction과 Self-Consistency를 순차 적용</li>
</ul>
<h4 id="443-분석">4.4.3 분석</h4>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/1b9c1041-0922-44c2-ae62-46848b8f70e6/image.png" alt=""></p>
<p>각 방법의 trade-off는 다음과 같다.</p>
<table>
<thead>
<tr>
<th>방법</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>Self-Correction</td>
<td>구체적 오류 수정</td>
<td>&#39;critic&#39; 능력의 한계</td>
</tr>
<tr>
<td>Self-Consistency</td>
<td>적응성·간단함</td>
<td>호출 N배 비용</td>
</tr>
<tr>
<td>Cross-Consistency</td>
<td>단일 모델 편향 감소</td>
<td>모델 운영 복잡도</td>
</tr>
</tbody></table>
<blockquote>
<p>Key Takeaways</p>
<ul>
<li>Self-Correction은 &#39;refine&#39;(수정)에 강하지만 &#39;critic&#39;(판단)은 탐색 여지</li>
<li>Self-Consistency는 비용↑이지만 안정적 성능 향상</li>
<li>Cross-Consistency는 단일 LLM 편향 완화</li>
<li>두 가지를 결합하는 것이 promising</li>
</ul>
</blockquote>
<hr>
<h2 id="5-fine-tuning">5. Fine-tuning</h2>
<h3 id="51-왜-fine-tuning인가">5.1 왜 Fine-tuning인가</h3>
<p>저자들은 Fine-tuning이 필요한 이유를 두 가지로 본다.</p>
<ol>
<li><strong>프라이버시</strong>: GPT-4 API는 데이터 유출 위험. 오픈소스를 로컬에서 fine-tuning하면 해결.</li>
<li><strong>성능 보강</strong>: 오픈소스 LLM은 SQL 관련 corpus가 적어 그대로는 약하다. Fine-tuning으로 보완.</li>
</ol>
<p>수식으로 정리하면 다음과 같다.</p>
<p>$$
\min_M \sum_{i=0}^{|\tau|} \text{Loss}(M(q_i, d_i), gt_i)
$$</p>
<p>여기서 $\tau = {(q_i, d_i, gt_i)}$는 (질문, DB 정보, 정답 SQL) 데이터셋이다.</p>
<h3 id="52-fine-tuning-objectives">5.2 Fine-tuning Objectives</h3>
<p>대부분 SQL generation을 직접 fine-tuning한다. 그러나 흥미로운 변형들이 있다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/09c5a60a-d8d2-46c1-9c40-d4aa555bf52c/image.png" alt=""></p>
<p>특히 <strong>DELLM</strong>은 흥미롭다. SQL을 직접 만드는 게 아니라 <strong>질문과 DB 정보로부터 도메인 지식을 생성하는 LLM</strong>을 fine-tuning한다. 그리고 그 지식을 다른 SQL 생성 LLM의 프롬프트에 주입한다. 즉 fine-tuning 대상이 SQL이 아닌 <strong>외부 지식 생산자</strong>가 되는 사례다.</p>
<h3 id="53-training-methods">5.3 Training Methods</h3>
<p>두 흐름이 있다.</p>
<p><strong>(a) Full Fine-Tuning (FFT)</strong>: 모든 파라미터를 학습. 자원 많이 필요. MAC-SQL, CodeS, SQL-Palm.</p>
<p><strong>(b) Parameter-Efficient Fine-Tuning (PEFT)</strong>: 일부 파라미터만 학습.</p>
<ul>
<li><strong>LoRA</strong>: Transformer 각 층에 trainable rank decomposition matrix 주입. 사전학습 가중치는 frozen</li>
<li><strong>QLoRA</strong>: LoRA + 4-bit NormalFloat + double quantization + Paged Optimizers. 메모리 사용량 감소</li>
</ul>
<p>PEFT의 장점은 <strong>학습 효율, 비용, 그리고 catastrophic forgetting 저항성</strong>이다. 후자는 특히 중요하다. FFT는 LLM의 일반 능력을 잊을 위험이 있는데 PEFT는 덜하다.</p>
<table>
<thead>
<tr>
<th>방법</th>
<th>사용 사례</th>
</tr>
</thead>
<tbody><tr>
<td>FFT</td>
<td>MAC-SQL, CodeS, SQL-Palm</td>
</tr>
<tr>
<td>LoRA/QLoRA</td>
<td>OpenSQL, DELLM, FinSQL, SQLfuse</td>
</tr>
</tbody></table>
<h3 id="54-training-data">5.4 Training Data</h3>
<p>대부분 Spider/BIRD 학습셋을 그대로 쓴다. 그러나 두 가지 흥미로운 사례가 있다.</p>
<p><strong>(a) FinSQL</strong>: 금융 도메인 특화 BULL 벤치마크 구축. Hundsun Technologies의 실제 펀드·주식·거시경제 분석 업무에서 수집. 공개 벤치마크가 산업 특성을 반영하지 못한다는 문제 제기.</p>
<p><strong>(b) CodeS의 두 흐름</strong>:</p>
<ul>
<li><strong>일반 능력 강화</strong>: 21.5GB(SQL 11GB + NL-to-code 6GB + NL 4.5GB) 수집</li>
<li><strong>도메인 적응</strong>: 소수의 실제 사용자 질의 + GPT-3.5로 few-shot 합성, SQL 템플릿에 새 도메인 데이터 plug-in</li>
</ul>
<p>후자는 <strong>prompt engineering으로 fine-tuning 데이터를 만든다</strong>는 점에서 흥미롭다. 두 갈래가 서로 보완하는 사례다.</p>
<h3 id="55-model-evaluation">5.5 Model Evaluation</h3>
<p>평가는 단순한 EX/EM 측정을 넘어서 다음을 다룬다.</p>
<ul>
<li><strong>난이도별</strong> 분류 (Spider의 4단계)</li>
<li><strong>도메인별</strong> 분류 (BIRD의 37개 도메인)</li>
<li><strong>Perturbation 유형별</strong> (Dr.Spider의 17종)</li>
<li><strong>오류 유형별</strong> 분석 (schema-linking, JOIN, nested, group by)</li>
<li><strong>LLM Comparator</strong> 같은 모델 간 차이 시각화 도구</li>
</ul>
<h3 id="56-현황과-한계">5.6 현황과 한계</h3>
<p>저자들은 fine-tuning 연구가 prompt engineering보다 적은 이유를 두 가지 든다.</p>
<ol>
<li><strong>API 비용이 낮은 closed-source 모델</strong>의 강력한 성능 때문에 prompt engineering이 우세</li>
<li><strong>Fine-tuning은 알고리즘적 혁신 포인트가 적음</strong>. 학습 기법, base 모델, 데이터 품질이 결정적인데 이는 점진적 발전이다</li>
</ol>
<p>이 진단은 본 서베이의 prompt engineering 섹션이 fine-tuning보다 훨씬 긴 이유를 설명해준다.</p>
<blockquote>
<p>Key Takeaways</p>
<ul>
<li>Fine-tuning은 SQL 생성뿐 아니라 워크플로우 각 단계의 성능 강화에 사용 가능</li>
<li>PEFT(LoRA/QLoRA)가 FFT보다 선호됨</li>
<li>공개 벤치마크는 산업 DB 특성 부족</li>
<li>LLM으로 데이터셋 자체를 만드는 것이 promising</li>
</ul>
</blockquote>
<hr>
<h2 id="6-model">6. Model</h2>
<h3 id="61-closed-source-models">6.1 Closed-source Models</h3>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/2bf6bcdc-bb2e-4584-bc91-6948d0e0fe0c/image.png" alt=""></p>
<p>GPT-4는 가장 인기 있는 base 모델이다. ~13T 토큰으로 사전학습되었고 약 1.8T 파라미터 / 120 레이어 규모. CodeX는 14K 이상의 Python 코드를 메모리에 갖고 있어 코드 생성에 특화된다.</p>
<p>저자들의 흥미로운 관찰: <strong>&quot;As of the time of writing, there is no closed-source LLM specifically for SQL code generation.&quot;</strong> 두 가지 추측 이유는 다음과 같다.</p>
<ol>
<li>SQL은 비즈니스 데이터의 프라이버시와 얽혀 있어 학습 데이터 확보가 어렵다</li>
<li>SQL 전용 모델은 일반화 능력이 떨어질 수 있다</li>
</ol>
<h3 id="62-open-source-models">6.2 Open-source Models</h3>
<p>오픈소스 모델은 다양한 크기로 제공된다.</p>
<ul>
<li><strong>Llama 3</strong>: 8B, 70B</li>
<li><strong>Code Llama</strong>: 7B, 13B, 34B, 70B</li>
<li><strong>DeepSeek</strong>: 다양한 버전</li>
<li><strong>Qwen</strong>: 다양한 버전</li>
<li><strong>SQLCoder</strong>: Text-to-SQL 전용. 70B는 GPT-4를, 15B는 GPT-3.5-turbo를 능가하는 새 데이터셋 결과</li>
</ul>
<p>특히 <strong>SQLCoder</strong>는 closed-source가 채우지 못한 &quot;Text-to-SQL 전용 모델&quot; 빈 칸을 채우려는 시도다.</p>
<h3 id="63-사용-트렌드">6.3 사용 트렌드</h3>
<p>논문 Figure 7이 보여주는 흐름을 정리하면 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/e3840773-0f58-4160-95cd-0778ee24d923/image.png" alt=""></p>
<p>저자들은 이를 <strong>오픈소스 모델의 파라미터 규모 증가</strong>와 <strong>fine-tuning을 통한 도메인 적응 가능성</strong>이 결합된 결과로 본다. 미래 전망은 <strong>가장 강한 closed-source와 가장 강한 open-source가 양강 구도를 형성</strong>하리라는 것이다.</p>
<blockquote>
<p>Key Takeaways</p>
<ul>
<li>Closed-source: 사용 편의성 + 강력한 코드 생성. 하드웨어 부족 사용자에게 적합</li>
<li>Open-source: 독립 배포 + 프라이버시 + 도메인 fine-tuning 가능</li>
<li>선호 시리즈: Closed는 GPT, Open은 DeepSeek/Llama/Code Llama/Qwen</li>
<li>오픈소스 사용 빈도가 점차 closed-source와 비등해짐</li>
</ul>
</blockquote>
<hr>
<h2 id="7-분석-세-벤치마크-비교">7. 분석: 세 벤치마크 비교</h2>
<h3 id="71-평가-방법론">7.1 평가 방법론</h3>
<p>저자들은 Spider 1.0, BIRD, Spider 2.0의 공식 리더보드를 비교 기준으로 삼는다. 다음 5가지 선택 기준을 적용한다.</p>
<ol>
<li>LLM 활용 방법만</li>
<li>방법당 최대 2개 LLM</li>
<li>2022년 이전 모델 제외 (pre-ChatGPT)</li>
<li>비공개 모델 제외</li>
<li>비재현 가능 방법 제외</li>
</ol>
<h3 id="72-spider-10-결과">7.2 Spider 1.0 결과</h3>
<p>대부분의 LLM 방법이 EX 80% 이상을 달성. 결론은 명확하다. <strong>Spider 1.0의 기본 Text-to-SQL은 이미 LLM에 의해 풀린 문제</strong>다. 더 어려운 벤치마크가 필요한 이유다.</p>
<h3 id="73-bird-결과-분석">7.3 BIRD 결과 분석</h3>
<p>여기가 가장 흥미로운 부분이다. 저자들은 두 차원으로 분석한다.</p>
<p><strong>(a) 모델 차원</strong>: closed-source와 open-source의 격차가 좁혀짐. Compact + fine-tuning 모델이 거대 LLM에 견줄 만함.</p>
<p><strong>(b) 방법 차원</strong>: 같은 LLM 위에서 비교했을 때 상위 시스템들의 공통점은 다음 두 가지다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/121857b3-1c3e-4082-9f2c-5e4dbf915016/image.png" alt=""></p>
<p><strong>Characteristic 1: Inference-time Scaling</strong></p>
<ul>
<li><strong>OpenSearch-SQL</strong>: 다중 SQL 생성 + correction + self-consistency</li>
<li><strong>Distillery</strong>: iterative correction + self-consistency</li>
<li><strong>MCS-SQL</strong>: schema linking과 SQL 생성 모두에 다중 프롬프트</li>
</ul>
<p>vs 베이스라인 (DIN-SQL은 단일 step correction, DAIL-SQL은 correction과 consistency 자체가 없음)</p>
<p><strong>Characteristic 2: Enhanced Schema Linking</strong></p>
<ul>
<li><strong>Distillery</strong>: 모던 LLM이 무관한 컬럼에 관용적이라는 발견 → 컨텍스트가 허용되면 가능한 많은 컬럼 포함 권장</li>
<li><strong>MCS-SQL</strong>: inference-time scaling 자체가 schema linking 탐색 공간 확장</li>
</ul>
<p>이 두 특성은 <strong>&quot;답을 한 번에 잘 맞추려 하기보다, 여러 후보를 만들고 잘 고른다&quot;</strong>는 공통 철학을 드러낸다.</p>
<h3 id="74-spider-20-결과-분석">7.4 Spider 2.0 결과 분석</h3>
<p>Spider 2.0은 현실 엔터프라이즈 워크플로우를 측정한다. 주요 관찰점은 다음과 같다.</p>
<ul>
<li><strong>오픈소스가 여전히 closed-source에 못 미침</strong>: 다양한 SQL 방언, 복잡 문법, nested 컬럼 등 실제 엔터프라이즈 복잡성이 작용</li>
<li><strong>Reasoning-enhanced 모델 우세</strong>: o1-preview 같은 추론 강화 모델이 좋은 성과</li>
<li><strong>Non-agent vs Agent 격차</strong>: agent 기반이 큰 차이로 우세</li>
</ul>
<p>대표 사례는 <strong>REFORCE</strong>다. 표준 Spider Agent를 다음으로 개선한다.</p>
<ul>
<li>table compression (컨텍스트 최적화)</li>
<li>structured output formatting</li>
<li>iterative column exploration (스키마 이해 향상)</li>
<li>parallelized voting + CTE-based resolution의 self-refinement</li>
</ul>
<p>그러나 SOTA가 31.26%에 그친다. 학술 진보와 실서비스 배포 사이의 큰 격차를 보여준다.</p>
<blockquote>
<p>Key Takeaways</p>
<ul>
<li>Spider 1.0: 기본 Text-to-SQL은 LLM에 의해 거의 해결</li>
<li>BIRD: inference-time scaling과 enhanced schema linking이 결정적</li>
<li>Spider 2.0: agent 기반이 우세, 그러나 절대 성능은 아직 낮음</li>
</ul>
</blockquote>
<hr>
<h2 id="8-미래-방향">8. 미래 방향</h2>
<h3 id="81-오류-분석-정리">8.1 오류 분석 정리</h3>
<p>기존 연구들의 오류 분류는 다음 5범주로 수렴한다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/dad4716c-d11f-4229-93e8-ac430f1d2f8a/image.png" alt=""></p>
<p>평균 비율을 정리하면:</p>
<ul>
<li><strong>Schema Linking 29-49%</strong>: 가장 큰 오류 원인. 여전히 핵심 개선 대상</li>
<li><strong>JOIN 21-26%</strong>: 멀티테이블 처리의 어려움</li>
<li><strong>GROUP BY, Nested 각 20% 이하</strong>: 상대적으로 작은 비중</li>
</ul>
<p>각 카테고리 안에서도 wrong column, wrong table, wrong condition 등 세부 분류가 가능하다. DIN-SQL의 경우 SELECT/FROM/WHERE/ORDER BY/GROUP BY 키워드별로 error/redundancy/insufficiency 같은 더 세밀한 분류를 한다.</p>
<p><strong>충격적인 관찰</strong>: MAC-SQL이 SPIDER와 BIRD의 정답 자체에 20-30% 오류가 있다고 발견한 점이다. 이는 데이터 클리닝의 필요성을 직접 보여준다.</p>
<h3 id="82-다섯-가지-도전과-방향">8.2 다섯 가지 도전과 방향</h3>
<h4 id="821-프라이버시">8.2.1 프라이버시</h4>
<p>API 호출 시 프롬프트가 외부 서버로 전송되는 위험. 해결책은 오픈소스 LLM의 사설 배포 + fine-tuning이지만, 다음 문제가 있다.</p>
<ul>
<li>더티 데이터</li>
<li>LLM의 일반 능력 손상 가능</li>
<li>catastrophic forgetting</li>
</ul>
<p>핵심은 <strong>고품질 학습 데이터 추출 기법</strong>이다.</p>
<h4 id="822-복잡-스키마와-부족한-벤치마크">8.2.2 복잡 스키마와 부족한 벤치마크</h4>
<p>저자들이 인용한 Microsoft 사례가 인상적이다. 내부 금융 데이터 웨어하우스는 632개 테이블 + 4,000개 이상 컬럼 + 200개 뷰 + 7,400개 이상 컬럼을 포함한다. 이런 규모에서는 다음 문제가 발생한다.</p>
<ul>
<li>Schema linking의 어려움</li>
<li>LLM 주의력 분산 (토큰 폭증)</li>
<li>긴 추론 시간</li>
</ul>
<p>해결 방향:</p>
<ul>
<li><strong>Agent 기반 동적 탐색</strong> (REFORCE, Spider 2.0)</li>
<li><strong>Self-consistency로 schema linking 오류 감소</strong> (MCS-SQL)</li>
<li><strong>Dynamic graph attention</strong> 같은 새 메커니즘으로 적응형 그래프 표현</li>
</ul>
<p>기존 벤치마크의 한계도 명확하다. Spider는 너무 단순하고, BIRD조차 실제 규모엔 못 미친다. <strong>Spider 2.0이 BigQuery·Snowflake 같은 실제 시스템을 도입해 좋은 방향</strong>을 제시한다.</p>
<h4 id="823-도메인-지식">8.2.3 도메인 지식</h4>
<p>LLM의 일반 지식만으론 산업 용어·약어·은어를 처리하기 어렵다. 두 갈래가 있고 각각 한계가 있다.</p>
<table>
<thead>
<tr>
<th>접근</th>
<th>한계</th>
</tr>
</thead>
<tbody><tr>
<td>RAG 기반 prompt engineering</td>
<td>노이즈 많은 비구조화 문서가 KB 구축 방해, 유사도 검색이 무관 정보 가져옴</td>
</tr>
<tr>
<td>Fine-tuning</td>
<td>catastrophic forgetting, 지식 갱신 시 비싼 재학습</td>
</tr>
</tbody></table>
<h4 id="824-자율-에이전트">8.2.4 자율 에이전트</h4>
<p>ReAct 프레임워크에 기반한 autonomous agent의 부상. 인간이 SQL을 작성하는 방식 자체가 trial-and-error의 반복인데, 이는 agent 모델링에 잘 맞는다.</p>
<p><strong>REFORCE</strong>와 <strong>Spider 2.0</strong>의 Spider-Agent가 첫 사례다. Spider 2.0 리더보드에서 <strong>non-agent가 agent 기반보다 현저히 떨어진다</strong>는 결과는 이 방향의 잠재력을 보여준다.</p>
<h4 id="825-데이터-거버넌스">8.2.5 데이터 거버넌스</h4>
<p>현 데이터셋의 두 한계가 강조된다.</p>
<ul>
<li><strong>Ambiguity</strong>: 의미적으로 다른 여러 SQL이 같은 NL의 답이 될 수 있음</li>
<li><strong>Semantic Mismatch</strong>: NL이 표현하는 의도를 DB가 (부분적으로) 답할 수 없음</li>
</ul>
<p>데이터 거버넌스의 세 효익:</p>
<ol>
<li>도메인 지식 구조화 → RAG 효율 ↑, 모호성 ↓</li>
<li>학습 데이터 품질 향상 → fine-tuning 강화</li>
<li>벤치마크 정제 → 평가 신뢰성 ↑</li>
</ol>
<hr>
<h2 id="9-정리">9. 정리</h2>
<h3 id="91-핵심-한-줄">9.1 핵심 한 줄</h3>
<blockquote>
<p>LLM 시대 Text-to-SQL은 Prompt Engineering(저데이터·빠름)과 Fine-tuning(프라이버시·도메인) 두 갈래로 나뉘며, 각 갈래는 pre/inference/post 또는 목표/방법/데이터/평가의 서브모듈로 더 분해된다.</p>
</blockquote>
<h3 id="92-의의">9.2 의의</h3>
<p>이 서베이의 의의는 <strong>실무 의사결정을 지원하는 분류 체계</strong>를 제공한 점이다. &quot;어느 갈래를 택할까&quot;, &quot;그 안에서 어느 워크플로우를 쓸까&quot;, &quot;어떤 LLM을 base로 삼을까&quot; 같은 질문에 명확한 가이드를 준다. 각 섹션 끝의 Key Takeaways는 그 자체로 압축된 의사결정 트리다.</p>
<h2 id="prompt-engineering-의사결정-트리">Prompt Engineering 의사결정 트리</h2>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/594c1400-27fc-4f8c-bd6d-28d47e6b0325/image.png" alt=""></p>
<h2 id="fine-tuning-의사결정-트리">Fine-tuning 의사결정 트리</h2>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/69484682-8f9e-46ca-a341-29f5744f1db2/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LLM 시대의 Text-to-SQL]]></title>
            <link>https://velog.io/@tasker_dev/LLM-%EC%8B%9C%EB%8C%80%EC%9D%98-Text-to-SQL</link>
            <guid>https://velog.io/@tasker_dev/LLM-%EC%8B%9C%EB%8C%80%EC%9D%98-Text-to-SQL</guid>
            <pubDate>Wed, 29 Apr 2026 04:57:04 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Text-to-SQL의 전 생애주기(모델·데이터·평가·오류분석)를 LLM 관점에서 체계적으로 재정리한 서베이</p>
</blockquote>
<hr>
<h2 id="0-논문-개요">0. 논문 개요</h2>
<h3 id="01-한눈에-보기">0.1 한눈에 보기</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>제목</td>
<td>A Survey of Text-to-SQL in the Era of LLMs: Where are we, and where are we going?</td>
</tr>
<tr>
<td>저자</td>
<td>Xinyu Liu, Shuyu Shen, Boyan Li, Peixian Ma, Runzhi Jiang, Yuxin Zhang, Ju Fan, Guoliang Li, Nan Tang, Yuyu Luo</td>
</tr>
<tr>
<td>소속</td>
<td>HKUST(Guangzhou), Renmin University of China, Tsinghua University</td>
</tr>
<tr>
<td>발표</td>
<td>arXiv preprint (v6, 2025년 12월 갱신)</td>
</tr>
<tr>
<td>분야</td>
<td>Database, NLP, LLM 응용</td>
</tr>
<tr>
<td>핵심 키워드</td>
<td>Text-to-SQL, NL2SQL, Database Interface, LLM, Schema Linking</td>
</tr>
</tbody></table>
<h3 id="02-사전-지식">0.2 사전 지식</h3>
<p>이 서베이를 효과적으로 읽기 위해 알아두면 좋은 개념들이다.</p>
<ul>
<li><strong>Text-to-SQL (NL2SQL)</strong>: 자연어 질의를 실행 가능한 SQL로 변환하는 과제. 데이터베이스 접근 장벽을 낮추는 핵심 기술이다.</li>
<li><strong>스키마 링킹(schema linking)</strong>: 자연어 질의에서 언급된 개체·속성을 데이터베이스의 어떤 테이블·컬럼에 매핑할지 판단하는 단계.</li>
<li><strong>PLM(Pre-trained Language Model)</strong>: BERT, T5와 같이 대규모 코퍼스에서 사전학습된 인코더/디코더 모델. 일반적으로 추가 파인튜닝이 필요하다.</li>
<li><strong>LLM(Large Language Model)</strong>: GPT-4, DeepSeek과 같이 emergent capabilities를 갖춘 대규모 모델. in-context learning만으로도 Text-to-SQL을 수행할 수 있다.</li>
<li><strong>In-Context Learning (ICL)</strong>: 모델 파라미터를 갱신하지 않고 프롬프트 내 예시·지시문만으로 새로운 과제를 수행하게 하는 기법.</li>
<li><strong>Spider / BIRD</strong>: Text-to-SQL의 양대 벤치마크. Spider는 cross-domain 일반화에, BIRD는 대규모 더티 데이터·도메인 지식 활용에 초점을 둔다.</li>
<li><strong>Execution Accuracy(EX)</strong>: 예측 SQL과 정답 SQL의 실행 결과가 같은지로 평가하는 지표. 현재 가장 널리 쓰이는 메인 지표다.</li>
</ul>
<h3 id="03-논문-구조">0.3 논문 구조</h3>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/6c031e09-e324-40b3-bba0-bf83a1b23b72/image.png" alt=""></p>
<hr>
<h2 id="1-abstract--introduction">1. Abstract &amp; Introduction</h2>
<h3 id="11-논문이-풀고자-하는-문제">1.1 논문이 풀고자 하는 문제</h3>
<p>LLM 시대에 들어서면서 Text-to-SQL은 단일 모델이 한 번에 SQL을 뽑아내는 end-to-end 과제가 아니라, 여러 모듈이 협업하는 <strong>모듈러 시스템</strong>으로 진화했다. 이 서베이는 그 변화를 다음 네 축으로 종합한다.</p>
<ol>
<li><strong>Model</strong>: 자연어의 모호성과 underspecification, 그리고 스키마·인스턴스 매핑까지 다루는 번역 기법</li>
<li><strong>Data</strong>: 학습 데이터 수집·합성·벤치마크</li>
<li><strong>Evaluation</strong>: 다각도 지표와 시나리오 기반 평가</li>
<li><strong>Error Analysis</strong>: 오류 분류와 근본 원인 추적</li>
</ol>
<p>저자들은 단순한 정리에 그치지 않고 &quot;어떻게 LLM을 Text-to-SQL에 최적화할 것인가&quot;에 대한 실무적 로드맵까지 제시한다.</p>
<h3 id="12-기존-접근의-한계">1.2 기존 접근의 한계</h3>
<p>이전 서베이들은 주로 PLM 시대까지의 기법을 다루거나, LLM 시대를 다루더라도 모듈 단위 분해가 부족했다. 저자들은 LLM 시대 Text-to-SQL 시스템이 갖는 <strong>모듈러 구조</strong>를 본격적으로 분해해 보여주는 첫 서베이임을 강조한다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/596d72bd-f376-4a78-bc8e-831318f35ac1/image.png" alt=""></p>
<table>
<thead>
<tr>
<th>기존 서베이의 한계</th>
<th>이 서베이가 채우는 부분</th>
</tr>
</thead>
<tbody><tr>
<td>PLM 중심, LLM 시대 진단 부족</td>
<td>LLM 시대 모듈러 구조의 첫 종합 정리</td>
</tr>
<tr>
<td>End-to-end 시각</td>
<td>Pre/Translation/Post 3단계 분해</td>
</tr>
<tr>
<td>평가 단일 지표 위주</td>
<td>다각도·시나리오 기반 평가 정리</td>
</tr>
<tr>
<td>오류 분석 비표준</td>
<td>2단계 오류 분류 체계 제안</td>
</tr>
</tbody></table>
<h3 id="13-이-논문의-기여">1.3 이 논문의 기여</h3>
<p>저자가 명시하는 기여를 정리하면 다음과 같다.</p>
<ol>
<li><strong>생애주기 관점의 종합 리뷰</strong>: 문제 정의부터 평가, 오류분석, 실무 가이드까지 한 번에 다룬다. Text-to-SQL을 단발성 모델이 아닌 시스템 엔지니어링 과제로 다루는 시각이다.</li>
<li><strong>모듈 단위 분해</strong>: Schema Linking, DB Content Retrieval, Encoding/Decoding, IR, Self-correction 등을 독립 모듈로 분리해 비교한다. 이는 다음 절(Table I)에서 39개 시스템의 모듈 선택을 한 눈에 볼 수 있게 한다.</li>
<li><strong>2단계 오류 분류 체계</strong>: &quot;오류 위치(SELECT/WHERE/...)&quot;와 &quot;원인(스키마 링킹 실패, 값 매칭 실패 등)&quot;의 두 축을 제안한다. 1.8%만 Others로 분류된다는 점에서 분류의 실용성을 검증한다.</li>
<li><strong>실무 의사결정 흐름 제공</strong>: 데이터 프라이버시·볼륨·하드웨어 자원에 따른 LLM 최적화 로드맵, 시나리오별 모듈 선택 가이드를 제시한다.</li>
</ol>
<h3 id="14-논문의-위치">1.4 논문의 위치</h3>
<p>이 서베이는 Katsogiannis-Meimarakis &amp; Koutrika의 VLDB Journal 서베이(2023), Deng et al.의 COLING 서베이(2022) 등을 잇는 흐름에 있다. 다만 LLM 시대 모듈러 시스템의 첫 종합 분해라는 점에서 차별화된다. 구체적으로는 DAIL-SQL(2023)·DIN-SQL(2023)·CHESS(2024)·CHASE-SQL(2025)·Alpha-SQL(2025) 등 최신 시스템을 동일한 모듈 격자에 정렬해 비교 가능하게 만든다는 점이 핵심 가치다.</p>
<hr>
<h2 id="2-text-to-sql-문제-정의와-도전-과제">2. Text-to-SQL 문제 정의와 도전 과제</h2>
<h3 id="21-문제-정의">2.1 문제 정의</h3>
<blockquote>
<p>Text-to-SQL is the task of converting natural language queries into corresponding SQL queries that can be executed on a relational database.</p>
</blockquote>
<p>수식으로 표현하면 다음과 같다.</p>
<p>$$
F(\text{NL}, \text{DB}, K) \rightarrow \text{SQL}
$$</p>
<p>여기서 $K$는 도메인 지식·추가 정보(예: BIRD 벤치마크의 evidence). 핵심은 단순한 일대일 매핑이 아니라는 점이다. 저자들은 다음 두 가지 다대일/일대다 관계를 강조한다.</p>
<ul>
<li>동일한 NL이 모호성으로 인해 여러 SQL에 대응될 수 있다.</li>
<li>NL·DB가 명확해도 의미적으로 동치인 SQL이 여럿 존재할 수 있다.</li>
</ul>
<p>이 점은 평가 지표 설계(특히 Execution Accuracy 대 Exact Match)에 직접적인 영향을 준다.</p>
<h3 id="22-인간-워크플로우">2.2 인간 워크플로우</h3>
<p>논문은 DBA가 &quot;Find the names of all customers who checked out books on exactly 3 different genres on Labor Day in 2023&quot;이라는 질의를 처리하는 과정을 3단계로 분해한다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/3cb7249c-bbd3-4ded-bd07-22b1f54091d7/image.png" alt=""></p>
<p>흥미로운 지점은 &quot;Labor Day in 2023&quot;이 미국에서는 9월 4일, 중국에서는 5월 1일을 가리킨다는 사례다. 이는 외부 도메인 지식 없이는 정확한 SQL을 만들 수 없음을 보여주며, 후술할 <strong>Additional Information Acquisition</strong> 모듈의 존재 이유가 된다.</p>
<h3 id="23-세-가지-본질적-도전-과제">2.3 세 가지 본질적 도전 과제</h3>
<p><strong>C1. 자연어의 불확실성(Uncertain NL)</strong></p>
<ul>
<li>어휘 중의성(lexical ambiguity): &quot;bat&quot;이 동물인지 야구방망이인지</li>
<li>통사 중의성(syntactic ambiguity): &quot;Mary saw the man with the telescope&quot;</li>
<li>Underspecification: &quot;Labor Day&quot;가 어느 나라 기준인지</li>
</ul>
<p><strong>C2. 복잡한 DB와 더티 데이터</strong></p>
<ul>
<li>수백 개의 테이블, 복잡한 외래키 관계</li>
<li>컬럼명·값의 모호성(LiteraryGenre vs SubjectGenre)</li>
<li>도메인 특화 스키마 패턴</li>
<li>결측·중복·일관성 결여</li>
</ul>
<p><strong>C3. NL→SQL 번역 그 자체</strong></p>
<ul>
<li>자유 형식 vs 엄격한 문법</li>
<li>같은 NL에 대한 복수의 정답 SQL</li>
<li>스키마 의존성: 같은 NL이 스키마가 바뀌면 다른 SQL이 됨</li>
</ul>
<p>이 세 도전 과제는 단순한 분류가 아니라, 후속 모듈(Pre-processing, Translation, Post-processing)이 각각 어느 도전 과제를 겨냥하는지 추적하는 격자 역할을 한다.</p>
<h3 id="24-text-to-sql-솔루션의-진화">2.4 Text-to-SQL 솔루션의 진화</h3>
<p>저자들은 Text-to-SQL의 발전을 4단계로 정리한다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/4745a52e-e53b-41fb-aaaa-2360145b53d6/image.png" alt=""></p>
<p>각 단계에서 풀린 도전 과제와 타깃 사용자가 달라진다.</p>
<table>
<thead>
<tr>
<th>단계</th>
<th>대표 모델</th>
<th>풀린 도전 과제</th>
<th>타깃 사용자</th>
</tr>
</thead>
<tbody><tr>
<td>Rule-based</td>
<td>n-gram 기반 파서</td>
<td>단일 테이블 토큰 매칭</td>
<td>전문가</td>
</tr>
<tr>
<td>Neural</td>
<td>LSTM, Seq2Seq, GNN</td>
<td>다중 테이블·동의어 처리</td>
<td>전문가</td>
</tr>
<tr>
<td>PLM</td>
<td>BERT, T5, RAT-SQL</td>
<td>복잡 스키마 일부, Spider 80% 해결</td>
<td>전문가→일반인 일부</td>
</tr>
<tr>
<td>LLM</td>
<td>GPT-4, CodeS, DAIL-SQL</td>
<td>대규모 테이블, 도메인 지식, BIRD 등</td>
<td>일반인</td>
</tr>
</tbody></table>
<p>저자들은 다섯 번째 단계(향후 5년)로 &quot;오픈 도메인 Text-to-SQL&quot;을 제시한다. 즉 단일 DB가 아니라 여러 DB를 가로질러 질의·집계하는 시나리오다. 이는 마지막 X장에서 본격적으로 다뤄진다.</p>
<h3 id="25-llm-시대의-두-갈래">2.5 LLM 시대의 두 갈래</h3>
<p>LLM을 Text-to-SQL에 활용하는 방법은 크게 둘로 나뉜다.</p>
<p><strong>(1) In-Context Learning</strong> — LLM 파라미터를 건드리지 않고 프롬프트만 설계한다.</p>
<p>$$
F_{\text{LLM}}(P \mid \text{NL}, \text{DB}, K) \rightarrow \text{SQL}
$$</p>
<p>여기서 $P$는 프롬프트 함수다. DAIL-SQL, DIN-SQL이 대표적이다.</p>
<p><strong>(2) Pre-train &amp; Fine-tune</strong> — LLM 자체를 Text-to-SQL에 특화시킨다.</p>
<p>$$
\text{LLM}^{*} = F_{\text{fine-tune}}(F_{\text{pre-train}}(\text{LLM}, D_p), D_f)
$$</p>
<p>$D_p$는 일반 코퍼스, $D_f$는 Text-to-SQL 특화 데이터셋이다. CodeS가 StarCoder 기반으로 이 경로를 택한 대표 사례다.</p>
<p>이 두 갈래의 구분은 IX장 실무 가이드의 핵심 분기 조건이 된다.</p>
<hr>
<h2 id="3-lm-기반-text-to-sql-모듈-개관">3. LM 기반 Text-to-SQL 모듈 개관</h2>
<h3 id="31-3단계-모듈러-구조">3.1 3단계 모듈러 구조</h3>
<p>LLM 시대 Text-to-SQL의 표준 아키텍처는 다음 3단계로 정리된다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/33122179-4757-4915-8b2d-d52fd4a65497/image.png" alt=""></p>
<p>이 구조는 두 가지 의미를 갖는다. 첫째, 각 단계가 서로 다른 도전 과제를 겨냥한다(Pre-processing은 C2, Translation은 C3, Post-processing은 C1·C3 모두). 둘째, 각 모듈은 선택적이다. 단순한 시스템은 Translation만 쓰고, 강력한 시스템은 세 단계 모두에 모듈을 둔다.</p>
<h3 id="32-멀티-에이전트-협업으로의-진화">3.2 멀티 에이전트 협업으로의 진화</h3>
<p>최근 트렌드는 단일 모놀리식 시스템이 아닌 <strong>멀티 에이전트 협업</strong>으로 가고 있다. 저자들이 강조하는 사례는 다음과 같다.</p>
<ul>
<li><strong>MAC-SQL</strong> (3-agent): Selector(스키마 링킹), Decomposer(질의 분해·생성), Refiner(실행 기반 정정)</li>
<li><strong>CHASE-SQL</strong> (divide-and-conquer): 다중 CoT 경로로 후보 SQL을 만들고 self-correction과 ranking으로 정제</li>
<li><strong>Alpha-SQL</strong>: Monte Carlo Tree Search 기반 자율 에이전트가 모듈을 동적으로 선택·활성화</li>
</ul>
<p>특히 Alpha-SQL의 MCTS 접근은 파이프라인의 경직성을 깨고 컨텍스트 추론과 실행 피드백에 따라 다음 모듈을 선택한다는 점에서 의미가 크다. 즉 &quot;어떤 순서로, 어떤 모듈을 쓸 것인가&quot;마저 학습/탐색의 대상이 된다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/c68ff8c7-500c-463a-a165-8ffd16cc2d9d/image.png" alt=""></p>
<h3 id="33-table-i이-보여주는-트렌드">3.3 Table I이 보여주는 트렌드</h3>
<p>논문의 Table I은 39개 대표 시스템을 모듈 선택의 격자로 정렬한 표다. 이를 읽고 나면 몇 가지 트렌드가 드러난다.</p>
<ul>
<li><strong>Schema Linking은 거의 표준</strong>: 39개 중 30개 이상이 사용. PLM 시대(Encoder-Decoder)부터 LLM 시대(Decoder-Only)까지 일관된다.</li>
<li><strong>Encoding은 Sequential로 수렴</strong>: 2022년 이전엔 Graph-based가 강했지만, LLM 시대에 와선 거의 모두 Sequential. LLM이 self-attention으로 관계를 암묵적으로 처리하기 때문이다.</li>
<li><strong>Decoding은 Greedy가 우세</strong>: GPT 계열이 기본 greedy인 영향. PLM 시대의 Beam Search나 Constraint-aware Incremental은 점차 줄어드는 추세다.</li>
<li><strong>CoT/Decomposition은 LLM에서만 등장</strong>: PLM 시대에는 거의 없던 모듈로, LLM의 추론 능력에 의존한다.</li>
<li><strong>Post-processing은 다양화</strong>: Correction, Consistency, Execution-Guided가 결합되어 사용된다.</li>
</ul>
<p>이는 LLM 시대로 오면서 <strong>인코더의 부담은 줄고, 디코더 후처리의 다양성이 늘어난다</strong>는 큰 흐름을 말해준다.</p>
<hr>
<h2 id="4-pre-processing-전략">4. Pre-Processing 전략</h2>
<h3 id="41-스키마-링킹">4.1 스키마 링킹</h3>
<p>스키마 링킹은 NL과 관련된 테이블·컬럼을 식별하는 단계다. LLM의 컨텍스트 길이 한계 때문에 LLM 시대에는 더욱 중요해졌다.</p>
<h4 id="411-세-갈래-접근">4.1.1 세 갈래 접근</h4>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/a6a2fb54-8808-4ad6-a174-e276cecf8ebf/image.png" alt=""></p>
<p><strong>(1) String Matching 기반</strong>: IRNet은 정확 매칭, ValueNet은 Damerau-Levenshtein 거리 기반 근사 매칭을 쓴다. 단순하고 빠르지만 동의어와 어휘 변이에 약하다.</p>
<p><strong>(2) Neural Network 기반</strong>: RESDSQL은 cross-encoder로 테이블·컬럼을 분류 확률에 따라 정렬한다. FinSQL은 parallel cross-encoder로 시간 비용을 줄인다. SLSQL은 Spider에 스키마 링킹 어노테이션을 추가해 데이터 기반 연구를 가능하게 했다. 의미 관계는 잘 잡지만 도메인 일반화가 어렵다.</p>
<p><strong>(3) In-Context Learning 기반</strong>: GPT-4 시대의 표준이 되어가는 방식이다.</p>
<ul>
<li><strong>C3-SQL</strong>: zero-shot 프롬프트 + self-consistency. 테이블 정렬 후 컬럼을 사전 형태로 출력</li>
<li><strong>MAC-SQL</strong>: 멀티에이전트의 Selector 에이전트가 담당하며, 스키마가 길 때만 활성화</li>
<li><strong>CHESS</strong>: GPT-4로 NL과 evidence에서 키워드 추출 후 3단계 스키마 프루닝</li>
</ul>
<h4 id="412-분석">4.1.2 분석</h4>
<p>스키마 링킹 모듈은 본질적으로 &quot;검색 정확도&quot;와 &quot;컨텍스트 절약&quot; 사이의 trade-off다. 너무 적게 가져오면 정답에 필요한 컬럼을 놓치고, 너무 많이 가져오면 LLM이 노이즈에 흔들린다. 최근 연구가 ICL 기반으로 수렴하는 이유는 단순 문자열 매칭이 가진 동의어·표현 변이 약점을 LLM의 의미 이해로 보완하면서, 신경망 학습보다 도메인 적응이 쉽기 때문이다.</p>
<h3 id="42-데이터베이스-콘텐츠-검색">4.2 데이터베이스 콘텐츠 검색</h3>
<p>WHERE 절 등에 들어갈 셀 값을 어떻게 찾을 것인가의 문제다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/62b5389d-44c4-449e-bd0f-802232a8660b/image.png" alt=""></p>
<p><strong>(1) String Matching</strong>: BRIDGE는 anchor text matching으로 NL에서 셀 값을 자동 추출한다. 휴리스틱으로 최대 시퀀스 매치를 계산해 매칭 경계를 정한다. 동의어 처리가 약점.</p>
<p><strong>(2) Neural Network</strong>: TaBERT는 &quot;database content snapshots&quot;를 어텐션으로 인코딩한다. IRNet은 ConceptNet 같은 지식 그래프를 활용한다. 학습 비용이 크다.</p>
<p><strong>(3) Index 전략</strong>: 대규모 DB에서 결정적으로 중요한 접근.</p>
<ul>
<li><strong>CHESS</strong>: Locality-Sensitive Hashing(LSH)으로 유니크 셀 값을 인덱싱하고 근사 최근접 검색을 수행. 편집 거리·시맨틱 임베딩 비교를 가속.</li>
<li><strong>CodeS</strong>: Coarse-to-fine 매칭. BM25로 후보를 좁힌 뒤 Longest Common Substring으로 재순위.</li>
</ul>
<p>인덱스는 검색을 빠르게 하지만 빌드 비용과 콘텐츠 변경 시 재구축 부담이 있다. 그래서 대규모 분석 DB에서 매력적이고, 빈번히 변경되는 OLTP DB에서는 부담스럽다.</p>
<h3 id="43-추가-정보-수집">4.3 추가 정보 수집</h3>
<p>도메인 지식, 시연 예제, 공식 증거 등을 프롬프트에 주입하는 모듈이다.</p>
<p><strong>Sample-based</strong>: DIN-SQL이 few-shot 예시를 단계별로 삽입. BIRD의 도메인 지식(evidence)을 프롬프트에 포함하는 방식이 대표적.</p>
<p><strong>Retrieval-based</strong>: 효율을 위해 유사도 기반 검색을 한다.</p>
<ul>
<li><strong>PET-SQL</strong>: question frame 풀에서 가장 유사한 k개 예시를 선택</li>
<li><strong>REGROUP</strong>: 도메인별 공식 지식 베이스를 만들고 Dense Passage Retriever로 검색</li>
<li><strong>ReBoost</strong>: Explain-Squeeze 2단계 스키마 링킹</li>
</ul>
<p>핵심 통찰은 <strong>BIRD 같은 현실적 벤치마크에서는 도메인 지식 없이는 성능이 안 나온다</strong>는 점이다. 즉 추가 정보 수집은 선택 모듈이 아니라 사실상 필수 모듈로 자리잡고 있다.</p>
<hr>
<h2 id="5-translation-모듈">5. Translation 모듈</h2>
<h3 id="51-인코딩-전략">5.1 인코딩 전략</h3>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/a4d1971e-fec4-4c39-bb06-b061be4da21f/image.png" alt=""></p>
<h4 id="511-sequential-encoding">5.1.1 Sequential Encoding</h4>
<p>NL과 스키마를 하나의 토큰 시퀀스로 처리한다. T5 기반 모델, 그리고 모든 LLM 기반 시스템이 이 방식을 쓴다(LLM은 명시적으로 인코딩 전략을 정의하지 않지만, NL+스키마를 concatenate하는 순간 암묵적으로 sequential).</p>
<ul>
<li><strong>BRIDGE</strong>: 매칭된 셀 값(anchor text)을 해당 필드 옆에 삽입해 정렬을 강화</li>
<li><strong>RESDSQL</strong>: rank-enhanced encoder로 스키마 항목을 정렬·필터링</li>
</ul>
<p>장점은 유연성, 단점은 깊게 중첩된 SQL의 관계 구조를 잡기 어렵다는 점.</p>
<h4 id="512-graph-based-encoding">5.1.2 Graph-based Encoding</h4>
<p>NL과 스키마를 그래프로 표현해 관계 구조를 보존한다.</p>
<ul>
<li><strong>RAT-SQL</strong>: relation-aware self-attention으로 질문과 스키마를 함께 인코딩</li>
<li><strong>S²SQL</strong>: ELECTRA 기반에 syntactic structure를 주입</li>
<li><strong>G³R</strong>: LGESQL + GAT(Graph Attention Network)</li>
<li><strong>Graphix-T5</strong>: 그래프 인식 레이어를 직접 인코딩 과정에 추가</li>
</ul>
<p>복잡한 그래프 구성·처리 알고리즘이 필요하고 대규모 학습 데이터를 요구한다. <strong>LLM 시대에 이 흐름이 사라진 이유는 LLM의 self-attention이 충분히 강력해서 명시적 그래프 인코딩의 이득이 크지 않기 때문</strong>으로 해석할 수 있다.</p>
<h4 id="513-separate-encoding">5.1.3 Separate Encoding</h4>
<p>NL과 스키마를 분리 인코딩하거나, 작업을 서브태스크로 나눠 각각 인코딩.</p>
<ul>
<li><strong>TKK</strong>: 작업 분해 + 멀티태스크 학습</li>
<li><strong>SC-Prompt</strong>: 텍스트 인코딩을 structure(구조)와 content(내용) 두 단계로 분리</li>
</ul>
<p>연산 오버헤드는 늘지만 모듈성·해석성을 얻는다.</p>
<h3 id="52-디코딩-전략">5.2 디코딩 전략</h3>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/31407898-f6e9-411f-9d63-8cf65abbdbae/image.png" alt=""></p>
<p><strong>Greedy Search</strong>: GPT 계열의 기본. 단순·빠르지만 long-term 의존성을 놓치고 초기 오류가 전파된다.</p>
<p><strong>Beam Search</strong>: top-k 빔을 유지해 더 넓은 탐색.</p>
<ul>
<li>RAT-SQL은 그래프 구조 + beam search로 후보 SQL을 만들고 재랭킹</li>
<li>SmBoP은 semi-autoregressive bottom-up 디코딩으로 sub-tree를 병렬 구축(로그 시간 복잡도)</li>
<li>ZeroNL2SQL은 sketch 단계에서 top-k 가설을 유지</li>
</ul>
<p><strong>Constraint-aware Incremental</strong>: SQL 문법을 디코딩 루프에 삽입해 매 토큰의 문법 적합성을 검증.</p>
<ul>
<li><strong>PICARD</strong>: 가장 유명한 구현. 매 스텝마다 부분 SQL의 문법 유효성을 검사</li>
<li><strong>BRIDGE</strong>: schema-consistency guided decoding으로 스키마 정렬도 강제</li>
</ul>
<p>문법 위반은 거의 0%로 만들 수 있지만 토큰별 검증이 추가 비용을 만든다.</p>
<h3 id="53-task-specific-prompt-전략">5.3 Task-specific Prompt 전략</h3>
<p>LLM 시대의 핵심 모듈이다. 두 갈래가 있다.</p>
<p><strong>Chain-of-Thought (CoT)</strong>: 추론 과정을 노출시켜 정확도와 해석성을 동시에 잡는다.</p>
<ul>
<li>CHESS는 entity/context retrieval → schema selection → SQL generation → revision의 파이프라인 전체에 CoT 적용</li>
<li>ACT-SQL, COE-SQL은 CoT + ICL 결합</li>
<li>C3-SQL, G³R은 CoT + 힌트 calibration</li>
</ul>
<p><strong>Decomposition</strong>: 작업을 서브태스크로 분해.</p>
<ul>
<li><strong>TKK</strong>: SELECT/FROM/WHERE 절 단위로 분해</li>
<li><strong>MAC-SQL</strong>: Decomposer 에이전트가 사용자 질의를 서브 문제로 쪼갬</li>
<li>DEA-SQL, G³R도 비슷한 전략</li>
</ul>
<p>CoT와 Decomposition은 종종 결합된다. 차이는 CoT가 &quot;한 모델 안의 reasoning trace&quot;라면 Decomposition은 &quot;여러 모듈/호출 간의 작업 분할&quot;이라는 점이다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/38793867-cb09-448a-8a47-41f51d24c283/image.png" alt=""></p>
<h3 id="54-intermediate-representation-ir">5.4 Intermediate Representation (IR)</h3>
<p>NL과 SQL의 간극을 메우기 위한 중간 표현이다.</p>
<h4 id="541-sql-like-syntax-language">5.4.1 SQL-like Syntax Language</h4>
<p>SQL을 단순화한 언어다. 발전 흐름은 <strong>단순화의 단계</strong>를 보여준다.</p>
<table>
<thead>
<tr>
<th>표현</th>
<th>단순화 정도</th>
</tr>
</thead>
<tbody><tr>
<td>Schema-free SQL</td>
<td>FROM 절 단순화</td>
</tr>
<tr>
<td>SyntaxSQLNet</td>
<td>FROM/JOIN 일부 제거</td>
</tr>
<tr>
<td>SemQL</td>
<td>FROM/JOIN/ON/GROUP BY 제거, WHERE/HAVING 통합</td>
</tr>
<tr>
<td>NatSQL</td>
<td>드문 연산자·키워드 제거, 스키마 항목 최소화</td>
</tr>
</tbody></table>
<p>NatSQL은 RESDSQL과 결합해 강한 성능을 보인다. 한계는 큰 스키마와 도메인 특화 SQL을 모두 커버하기 어렵다는 점, 수동 설계 비용이 있다는 점이다.</p>
<h4 id="542-sql-like-sketch-structure">5.4.2 SQL-like Sketch Structure</h4>
<p>SQL의 구조를 슬롯이 있는 템플릿으로 표현한다.</p>
<pre><code>SELECT [column]
FROM [table]
JOIN [table] ON [table].[column] = [table].[column]
GROUP BY [column]
HAVING count([column]) &gt; [n]</code></pre><p>LLM의 cloze(빈칸 채우기) 능력을 활용해 슬롯을 채우는 전략이다.</p>
<ul>
<li><strong>CatSQL</strong>: 일반 템플릿 sketch + 슬롯 채우기</li>
<li><strong>RESDSQL</strong>: skeleton-aware decoding으로 SQL skeleton 먼저 생성, 그다음 실제 SQL로 변환</li>
<li><strong>ZeroNL2SQL</strong>, <strong>SC-Prompt</strong>, <strong>TA-SQL</strong>도 sketch 활용</li>
</ul>
<p>장점은 LLM 의존이 줄고 다른 전략(decomposition, syntax language)과 결합이 쉽다는 점.</p>
<h4 id="543-ir의-의미">5.4.3 IR의 의미</h4>
<p>IR은 본질적으로 <strong>SQL의 표현력을 일부 포기하는 대신 NL→SQL 매핑을 단순화</strong>하는 trade-off다. 모든 SQL을 표현하지 못해도, 자주 등장하는 패턴을 깔끔히 잡을 수 있으면 정확도가 올라간다. LLM 시대에 IR의 비중이 줄어든 것은 LLM이 이미 SQL을 직접 잘 생성하기 때문이지만, 복잡한 nested query에서 IR은 여전히 유효한 도구다.</p>
<hr>
<h2 id="6-post-processing-전략">6. Post-Processing 전략</h2>
<h3 id="61-sql-교정">6.1 SQL 교정</h3>
<p>DIN-SQL의 self-correction 모듈이 대표적이다. 두 종류 프롬프트를 쓴다.</p>
<ul>
<li><strong>General prompt</strong> (CodeX용): 직접 오류 식별·수정 요청</li>
<li><strong>Mild prompt</strong> (GPT-4용): 오류가 있다고 가정하지 않고 잠재 이슈만 탐색</li>
</ul>
<p>ZeroNL2SQL은 predicate 오류(컬럼·값 오인식)에 대한 multi-level matching을 제안. 컬럼→테이블→DB로 매칭 범위를 점진 확장한다.</p>
<p>한계는 대부분 syntax 오류 교정에 집중되어 있고, <strong>semantic 오류</strong>(잘못된 join, 조건 불일치, 집계 오류)는 잘 다루지 못한다는 점. NL2SQL-BUGs(2025)가 이 빈틈을 본격적으로 다루는 후속 연구다.</p>
<h3 id="62-출력-일관성">6.2 출력 일관성</h3>
<p>self-consistency 아이디어를 차용한다. 여러 추론 경로를 샘플링한 뒤 다수결로 답을 고른다.</p>
<ul>
<li><strong>DAIL-SQL</strong>: self-consistency로 0.4%p 개선</li>
<li><strong>FinSQL</strong>: n개 후보를 병렬 생성, 키워드 일관성 클러스터링 후 최대 클러스터에서 선택</li>
<li><strong>PET-SQL</strong>: cross-consistency. 여러 LLM이 낮은 temperature로 SQL을 만들고 실행 결과로 투표</li>
</ul>
<p>단점은 추론 비용이 N배가 된다는 것. 그리고 단일 모델 샘플링은 다양성이 한정적이라는 최근 연구도 있다.</p>
<h3 id="63-실행-가이드-전략">6.3 실행 가이드 전략</h3>
<p>생성된 SQL을 실제 실행해 결과를 보고 다시 수정하는 전략.</p>
<ul>
<li><strong>CHESS</strong>: 초안 SQL → 실행 결과 → syntax 오류 수정 → 반복</li>
<li><strong>CodeS</strong>: beam search로 4개 후보를 만들고 첫 번째 실행 가능한 것을 선택</li>
</ul>
<p>장점은 실행 결과로 검증이 되니 신뢰도가 높다는 것. 단점은 대형 DB에서는 실행 자체가 느려 latency가 폭증한다는 점.</p>
<h3 id="64-n-best-재랭킹">6.4 N-best 재랭킹</h3>
<p>PLM 기반 시스템에서 활발하게 쓰인 전략이다. 모델이 생성한 top-N 후보를 다른 (더 큰) 모델로 재정렬한다.</p>
<ul>
<li><strong>Bertrand-dr</strong>: BERT 기반 reranker. 효과가 임계값에 민감해 부정적 효과도 발생</li>
<li><strong>G³R</strong>: feature-enhanced reranker, hybrid prompt tuning + contrastive learning</li>
<li><strong>ReFSQL</strong>: retriever와 generator의 결과를 결합</li>
</ul>
<p>LLM 기반 시스템에서는 LLM 자체의 추론력이 강해서 재랭킹이 덜 쓰인다. 다만 다중 후보 + voting/ranking은 CHASE-SQL에서 다시 부상하는 패턴이다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/de8f3f7e-995a-4fd7-a375-1220d7725e03/image.png" alt=""></p>
<hr>
<h2 id="7-벤치마크">7. 벤치마크</h2>
<h3 id="71-데이터셋-분류">7.1 데이터셋 분류</h3>
<p>논문은 벤치마크를 8가지 시나리오로 분류한다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/0c1bead2-a293-4be4-a29a-f4bdfd8f9c7f/image.png" alt=""></p>
<p>대표 흐름은 다음과 같다.</p>
<ul>
<li><strong>단일 도메인 → 크로스 도메인</strong>: ATIS, GeoQuery에서 시작해 WikiSQL, Spider로 일반화 능력을 측정.</li>
<li><strong>Spider → BIRD</strong>: BIRD는 더 큰 DB(평균 4.5M 레코드/DB), 도메인 지식, scalar function/수학 연산을 포함.</li>
<li><strong>Robustness 강조</strong>: Spider-Syn(동의어), Dr.Spider(17가지 perturbation), AmbiQT(모호성).</li>
<li><strong>현실 시나리오</strong>: BookSQL(회계), FinSQL/BULL(금융), Archer(추론), Spider2-Lite(엔터프라이즈).</li>
</ul>
<h3 id="72-통계-분석으로-본-트렌드">7.2 통계 분석으로 본 트렌드</h3>
<p>논문 Table II의 통계에서 의미 있는 수치를 골라 해석한다.</p>
<table>
<thead>
<tr>
<th>데이터셋</th>
<th>#-DBs</th>
<th>#-Tables/DB</th>
<th>#-Records/DB</th>
<th>Avg #-Selects</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>Spider</td>
<td>206</td>
<td>5.13</td>
<td>8,980</td>
<td>1.17</td>
<td>Cross-domain 표준</td>
</tr>
<tr>
<td>BIRD</td>
<td>80</td>
<td>7.64</td>
<td>4,585,335</td>
<td>1.09</td>
<td>대규모 데이터·도메인 지식</td>
</tr>
<tr>
<td>Spider2-Lite</td>
<td>264</td>
<td>23.71</td>
<td>-</td>
<td>5.10</td>
<td>엔터프라이즈 복잡도</td>
</tr>
<tr>
<td>Archer</td>
<td>10</td>
<td>6.8</td>
<td>31,365</td>
<td>3.07</td>
<td>추론·산술 강조</td>
</tr>
<tr>
<td>BULL</td>
<td>3</td>
<td>26</td>
<td>85,631</td>
<td>1.0</td>
<td>금융 도메인 단일</td>
</tr>
</tbody></table>
<p><strong>관찰점</strong>: Spider→BIRD→Spider2-Lite로 갈수록 DB 크기와 SQL 복잡도가 모두 증가. 특히 Spider2-Lite는 평균 23개 테이블/DB, 평균 5개 SELECT로 현실 엔터프라이즈에 가깝다.</p>
<p>저자들이 강조하는 &quot;현재 데이터셋의 한계&quot;는 다음과 같다.</p>
<ul>
<li>중첩 쿼리(nested SELECT)와 복잡 set operation 부족</li>
<li>Scalar function·수학 연산이 적게 포함됨</li>
<li>Open-domain(다수 DB 가로지르는) 시나리오 거의 없음</li>
</ul>
<p>이는 X장의 open problem과 직접 연결된다.</p>
<h3 id="73-데이터-합성">7.3 데이터 합성</h3>
<p>학습 데이터 부족은 본질적 문제다. 합성 방법은 시간순으로 다음과 같이 진화했다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/4c1eae25-1fbe-422d-92df-f69fa62f7a68/image.png" alt=""></p>
<ul>
<li><strong>Human annotation</strong>: Spider, ATIS. 품질은 높지만 비싸다.</li>
<li><strong>Rule-based synthesis</strong>: MIMICSQL은 템플릿 기반 SQL 생성 후 NL 작성.</li>
<li><strong>LLM-based synthesis</strong>: ScienceBenchmark는 SQL을 템플릿으로 만들고 GPT-3로 NL을 역생성.</li>
</ul>
<p>LLM 기반 합성의 가능성은 크지만, 합성 데이터의 다양성·정확성·도메인 적합성을 어떻게 보장할 것인가가 X장의 &quot;Adaptive Training Data Synthesis&quot; 문제로 이어진다.</p>
<hr>
<h2 id="8-평가와-오류-분석">8. 평가와 오류 분석</h2>
<h3 id="81-평가-지표">8.1 평가 지표</h3>
<p>논문은 6가지 지표를 정의한다.</p>
<p><strong>Execution Accuracy (EX)</strong>: 가장 널리 쓰이는 메인 지표.</p>
<p>$$
\text{EX} = \frac{\sum_{i=1}^{N} \mathbf{1}(V_i = \hat{V_i})}{N}
$$</p>
<p>$V_i$는 정답 SQL의 실행 결과 집합, $\hat{V_i}$는 예측 SQL의 결과 집합. 의미적으로 다른 SQL이 우연히 같은 결과를 낼 수 있다는 false positive 가능성이 한계다.</p>
<p><strong>String-Match Accuracy (SM)</strong>: 문자열 정확 일치. 의미적으로 같은 SQL을 다르게 작성한 경우(예: WHERE 절 순서 변경)를 모두 틀렸다고 처리하는 약점.</p>
<p><strong>Component-Match Accuracy (CM)</strong>: SELECT, WHERE 등 컴포넌트 단위 정확도.</p>
<p>$$
\text{CM}<em>C = \frac{\sum</em>{i=1}^{N} \mathbf{1}(Y_i^C = \hat{Y_i}^C)}{N}
$$</p>
<p>WHERE 절 같은 일부 컴포넌트는 순서를 무시하고 매칭한다.</p>
<p><strong>Exact-Match Accuracy (EM)</strong>: CM의 모든 컴포넌트가 일치할 때만 정답.</p>
<p>$$
\text{EM} = \frac{\sum_{i=1}^{N} \mathbf{1}(\bigwedge_{C_k \in C} Y_i^{C_k} = \hat{Y_i}^{C_k})}{N}
$$</p>
<p><strong>Valid Efficiency Score (VES)</strong>: BIRD가 도입한 실행 효율 평가.</p>
<p>$$
\text{VES} = \frac{\sum_{i=1}^{N} \mathbf{1}(V_i = \hat{V_i}) \cdot R(Y_i, \hat{Y_i})}{N}, \quad R(Y_i, \hat{Y_i}) = \sqrt{\frac{E(Y_i)}{E(\hat{Y_i})}}
$$</p>
<p>$E(\cdot)$는 SQL의 효율(실행 시간 등). 정확하지만 느린 SQL이 패널티를 받는다.</p>
<p><strong>Query Variance Testing (QVT)</strong>: 같은 SQL에 대응되는 여러 NL 변형에 대한 모델의 일관성을 측정.</p>
<p>$$
\text{QVT} = \frac{1}{N}\sum_{i=1}^{N}\left(\frac{\sum_{j=1}^{m_i} \mathbf{1}(F(Q_{ij}) = Y_i)}{m_i}\right)
$$</p>
<p>이 지표들의 의미를 정리하면 다음과 같다.</p>
<table>
<thead>
<tr>
<th>지표</th>
<th>측정 대상</th>
<th>약점</th>
</tr>
</thead>
<tbody><tr>
<td>EX</td>
<td>결과 동치성</td>
<td>의미 다른데 우연히 같은 결과</td>
</tr>
<tr>
<td>SM/EM</td>
<td>문자열·구성요소 일치</td>
<td>표현 다양성 미반영</td>
</tr>
<tr>
<td>VES</td>
<td>효율성</td>
<td>정확도와 효율 분리 어려움</td>
</tr>
<tr>
<td>QVT</td>
<td>NL 표현 변동에 대한 견고성</td>
<td>변형 NL 데이터 필요</td>
</tr>
</tbody></table>
<h3 id="82-평가-툴킷">8.2 평가 툴킷</h3>
<p><strong>MT-TEQL</strong>: 메타모픽 테스팅. NL 변형(prefix insertion 등) 4종, 스키마 변형(table shuffle 등) 8종을 자동 생성해 견고성을 평가.</p>
<p><strong>NL2SQL360</strong>: 다각도 평가 프레임워크. 6개 컴포넌트(Dataset, Model Zoo, Metrics, Filter, Evaluator, Analysis)로 구성. SQL 특성(집계 함수, 중첩 쿼리, top-k)별로 데이터셋을 필터링해 시나리오별 성능을 측정.</p>
<p>이 툴킷들의 의미는 <strong>&quot;단일 벤치마크의 단일 점수&quot;로는 실서비스 성능을 알 수 없다</strong>는 인식이다. BI 시나리오는 집계·top-k 쿼리가 많고, 정형 보고는 join이 깊다. 각각의 부분집합에서 성능을 따로 봐야 한다.</p>
<h3 id="83-오류-분류-체계">8.3 오류 분류 체계</h3>
<h4 id="831-기존-분류-체계-정리">8.3.1 기존 분류 체계 정리</h4>
<ul>
<li><strong>Ning et al.</strong>: Syntactic 차원(SQL 키워드별)과 Semantic 차원(NL 해석 오류) 두 축.</li>
<li><strong>SQL-PaLM</strong>: Schema Linking, Database Content, Knowledge Evidence, Reasoning, Syntax 5종.</li>
<li><strong>NL2SQL-BUGs</strong>: semantic 오류에 집중. 9개 메인 카테고리, 31개 서브카테고리.</li>
</ul>
<p>이들은 데이터셋·모델 의존적이라는 한계가 있다.</p>
<h4 id="832-저자의-2단계-분류-원칙">8.3.2 저자의 2단계 분류 원칙</h4>
<p>저자들은 분류 체계가 갖춰야 할 4원칙을 제시한다.</p>
<ol>
<li><strong>Comprehensiveness</strong>: 모든 오류 타입 포괄</li>
<li><strong>Mutual Exclusivity</strong>: 분류 모호성 제거</li>
<li><strong>Extensibility</strong>: 새 오류 유형 수용</li>
<li><strong>Practicality</strong>: 실제 디버깅에 활용 가능</li>
</ol>
<p>이 원칙으로 만든 2단계 분류는 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/d6f14437-ba9c-4525-b06f-364a9a9cd7e4/image.png" alt=""></p>
<p><strong>Level 1 (위치)</strong>: 어느 SQL 절에서 오류가 발생했는가
<strong>Level 2 (원인)</strong>: 왜 그 오류가 발생했는가</p>
<h4 id="833-적용-결과-분석">8.3.3 적용 결과 분석</h4>
<p>DIN-SQL을 Spider에 적용한 오류 분포(Figure 1d)를 정리하면 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/beae45ec-9c38-45ea-a361-7fae1c01b36f/image.png" alt=""></p>
<p>흥미로운 관찰점은 다음과 같다.</p>
<ul>
<li><strong>SELECT 오류가 25.5%</strong>로 가장 큼: 어떤 컬럼을 출력할지 결정하는 단계가 의외로 약하다. 스키마 링킹의 한계.</li>
<li><strong>GROUP BY 16.6%</strong>: 집계의 핵심 절. 누락·중복·서브쿼리화 오류가 섞여 있음.</li>
<li><strong>Equality 15.9%, WHERE 15.2%</strong>: 조건절의 값 매칭이 여전히 큰 골칫거리. DB content retrieval 모듈의 중요성을 뒷받침.</li>
<li><strong>FROM 15.3%</strong>: JOIN 결정과 테이블 선택. 복잡 스키마의 본질적 어려움.</li>
<li><strong>Other가 1.8%</strong>: 분류 체계의 실용성 검증.</li>
</ul>
<p>각 위치 안에서 원인은 더 세분된다. WHERE 절 오류(15.2%) 내부를 들여다보면 Value 매칭이 11.6%로 압도적이다. 즉 <strong>WHERE의 어려움 대부분은 &quot;어떤 값으로 필터링할지&quot;를 못 맞추는 것</strong>이라는 진단이다. 이는 indexed retrieval(LSH, BM25)이 왜 중요한지를 정량적으로 뒷받침한다.</p>
<hr>
<h2 id="9-실무-가이드">9. 실무 가이드</h2>
<h3 id="91-데이터-기반-llm-최적화-로드맵">9.1 데이터 기반 LLM 최적화 로드맵</h3>
<p>저자들은 두 조건(데이터 프라이버시, 데이터 볼륨)에 따른 의사결정 흐름을 제안한다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/e21ff0f5-a43b-4fba-87db-8d18c5411316/image.png" alt=""></p>
<p>핵심 원칙은 다음과 같다.</p>
<ul>
<li><strong>프라이버시가 민감하면 무조건 오픈소스 LLM</strong>: 외부 API는 데이터 유출 위험.</li>
<li><strong>오픈소스는 학습+추론 모두 최적화 가능, 클로즈드는 추론만 가능</strong>.</li>
<li><strong>하드웨어 vs API 비용</strong>: 오픈소스는 GPU 자원을, 클로즈드는 API 예산을 요구.</li>
<li><strong>저데이터 환경에선 few-shot, zero-shot 우선</strong>: 작은 라벨링 데이터로 fine-tuning을 시도하는 건 종종 손해.</li>
</ul>
<h3 id="92-모듈-선택-의사결정-흐름">9.2 모듈 선택 의사결정 흐름</h3>
<p>저자들은 시나리오별 모듈 추천을 표로 정리했는데, 핵심을 다음과 같이 재구성할 수 있다.</p>
<table>
<thead>
<tr>
<th>시나리오</th>
<th>추천 모듈</th>
<th>장점</th>
<th>단점</th>
</tr>
</thead>
<tbody><tr>
<td>복잡한 스키마(테이블·컬럼 多)</td>
<td>Schema Linking</td>
<td>토큰·노이즈 ↓</td>
<td>시간 ↑</td>
</tr>
<tr>
<td>DB 콘텐츠와 NL 표현 불일치</td>
<td>DB Content Retrieval</td>
<td>값 매칭 정확도 ↑</td>
<td>토큰·시간 ↑</td>
</tr>
<tr>
<td>도메인 특화 NL/DB</td>
<td>Additional Information</td>
<td>의미 이해·도메인 적응 ↑</td>
<td>토큰·시간 ↑</td>
</tr>
<tr>
<td>모델 가이드 필요</td>
<td>Task-specific Prompt</td>
<td>이해도 ↑</td>
<td>토큰 ↑</td>
</tr>
<tr>
<td>NL-SQL 격차 큼</td>
<td>IR</td>
<td>격차 ↓</td>
<td>시간·복잡도 ↑</td>
</tr>
<tr>
<td>동일 NL에 결과 불일치</td>
<td>Output Consistency</td>
<td>일관성 ↑</td>
<td>시간 ↑ (토큰 ↑)</td>
</tr>
<tr>
<td>Syntax 오류 발생</td>
<td>SQL Correction</td>
<td>실행 성공률 ↑</td>
<td>시간 ↑ (토큰 ↑)</td>
</tr>
<tr>
<td>실행 결과 접근 가능</td>
<td>Execution-Guided</td>
<td>비실행 SQL 필터링</td>
<td>시간 ↑</td>
</tr>
</tbody></table>
<p>이 표의 핵심은 <strong>모든 모듈이 시간/토큰 비용 증가를 동반한다</strong>는 점이다. 모듈 선택은 항상 trade-off이고, 시나리오에 맞춰 선택해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/cb964e0f-3537-4ec6-a6ee-e4dfcaebecce/image.png" alt=""></p>
<h3 id="93-자원-비용-비교">9.3 자원 비용 비교</h3>
<p>논문 Table III는 Spider에서의 자원 사용을 비교한다.</p>
<table>
<thead>
<tr>
<th>Method</th>
<th>Base</th>
<th>Tokens/SQL</th>
<th>Latency/SQL (s)</th>
</tr>
</thead>
<tbody><tr>
<td>RESDSQL</td>
<td>PLM</td>
<td>-</td>
<td>1.91</td>
</tr>
<tr>
<td>RESDSQL+NatSQL</td>
<td>PLM</td>
<td>-</td>
<td>1.97</td>
</tr>
<tr>
<td>ZeroNet</td>
<td>PLM+LLM</td>
<td>377</td>
<td>3.72</td>
</tr>
<tr>
<td>DIN-SQL</td>
<td>LLM</td>
<td>3,579</td>
<td>10.34</td>
</tr>
</tbody></table>
<p><strong>관찰</strong>: DIN-SQL은 RESDSQL보다 5배 이상 느리고 토큰 소비도 거의 10배다. 정확도는 더 높지만 운영 비용 차이가 크다. 실무에서는 <strong>PLM과 LLM을 혼합</strong>하거나 <strong>복잡도에 따라 라우팅</strong>(EllieSQL의 접근)이 합리적인 선택이 될 수 있다.</p>
<hr>
<h2 id="10-한계와-열린-문제">10. 한계와 열린 문제</h2>
<h3 id="101-현재-llm-기반-방법의-한계">10.1 현재 LLM 기반 방법의 한계</h3>
<p>저자들이 정리한 한계는 다음과 같다.</p>
<ol>
<li><strong>단일 DB 가정</strong>: 학습·추론 모두 단일 DB 위에서 이뤄짐. cross-DB 시나리오 미지원.</li>
<li><strong>추론 비용</strong>: 강력한 NLU에도 불구하고 토큰 소비가 크다.</li>
<li><strong>해석성·디버그 부재</strong>: 사용자가 SQL 생성 과정을 이해·검증할 수단이 부족.</li>
<li><strong>도메인 적응 약함</strong>: 새 도메인마다 고품질 데이터가 필요하지만 자동 합성 메커니즘이 미숙.</li>
</ol>
<h3 id="102-open-domain-text-to-sql">10.2 Open-Domain Text-to-SQL</h3>
<p>가장 큰 미해결 과제다. 정부 오픈데이터 같은 환경에서는 한 NL이 여러 DB(세금기록, 처리로그, 통계보고서 등)에 걸쳐 있다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/4eabef5e-d125-4b04-8028-9d557f31f763/image.png" alt=""></p>
<p>이 시나리오의 도전 과제는 다음과 같다.</p>
<ul>
<li><strong>DB 검색</strong>: 거대한 데이터 소스 풀에서 관련 DB를 찾는 검색 단계</li>
<li><strong>이질적 스키마</strong>: 서로 다른 명명·구조 통합</li>
<li><strong>답 집계</strong>: 여러 SQL 결과를 종합해 최종 답 도출</li>
<li><strong>도메인 적응</strong>: 도메인별 용어·구조 차이</li>
<li><strong>확장성·효율</strong>: 대규모 데이터 처리</li>
<li><strong>평가</strong>: 현실 복잡도를 반영하는 메트릭·데이터셋 필요</li>
</ul>
<h3 id="103-cost-effective-text-to-sql">10.3 Cost-Effective Text-to-SQL</h3>
<p>LLM 단독 사용은 비용이 너무 크다. 저자들이 제시하는 방향은 다음과 같다.</p>
<ul>
<li><strong>PLM + LLM 하이브리드</strong>: 단순 쿼리는 PLM, 복잡 쿼리는 LLM</li>
<li><strong>EllieSQL</strong> 같은 complexity-aware routing: 쿼리 복잡도에 따라 적합한 생성기로 분배</li>
</ul>
<p>이는 실서비스 관점에서 매우 현실적인 방향이다. 모든 쿼리에 GPT-4를 쓰는 것은 비용·latency 모두 부담스럽고, 실제로 대부분의 쿼리는 단순하기 때문이다.</p>
<h3 id="104-trustworthy-text-to-sql">10.4 Trustworthy Text-to-SQL</h3>
<p>신뢰성을 위한 4가지 방향이 제시된다.</p>
<p><strong>(a) 해석 가능성</strong>: surrogate model, saliency map 등 XAI 기법의 Text-to-SQL 적용. 멀티 에이전트 LLM이 자연스럽게 각 단계의 출력을 보여주므로 일부 해석성을 제공.</p>
<p><strong>(b) 디버깅 도구</strong>: 컴파일러처럼 SQL 오류를 잡되, syntax뿐 아니라 semantic 오류까지 검증. NL2SQL-BUGs가 이 방향의 시작.</p>
<p><strong>(c) 인터랙티브 도구</strong>: DBA가 50줄 이상의 복잡 쿼리를 작성할 때, 모델이 분해하고 사용자가 부분별로 정정·확인하는 워크플로우. bottom-up과 top-down 모두 지원.</p>
<p><strong>(d) 적응형 데이터 합성</strong>: 모델 약점을 평가 결과에서 식별하고, LLM으로 해당 약점을 보완하는 (NL, SQL) 쌍을 자동 생성하는 피드백 루프.</p>
<hr>
<h2 id="11-정리">11. 정리</h2>
<h3 id="111-핵심-한-줄">11.1 핵심 한 줄</h3>
<blockquote>
<p>LLM 시대의 Text-to-SQL은 단일 모델이 SQL을 한 번에 뱉는 게 아니라, Pre/Translation/Post의 모듈러 시스템이며, 각 모듈은 시나리오별 비용·정확도 trade-off의 산물이다.</p>
</blockquote>
<h3 id="112-의의">11.2 의의</h3>
<p>이 서베이의 의의는 두 가지다. <strong>학술적</strong>으로는 LLM 시대 Text-to-SQL을 &quot;에이전트·모듈러 시스템&quot;으로 정식화한 첫 종합 정리다. <strong>실무적</strong>으로는 어떤 모듈을 언제 쓸지에 대한 의사결정 프레임을 제공해, 시스템 설계자가 길을 잃지 않게 돕는다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[gpt-oss의 원본 CoT를 안전하게 다루는 법 — Chat Completions와 Responses API 구현 가이드]]></title>
            <link>https://velog.io/@tasker_dev/gpt-oss%EC%9D%98-%EC%9B%90%EB%B3%B8-CoT%EB%A5%BC-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EB%8B%A4%EB%A3%A8%EB%8A%94-%EB%B2%95-Chat-Completions%EC%99%80-Responses-API-%EA%B5%AC%ED%98%84-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
            <guid>https://velog.io/@tasker_dev/gpt-oss%EC%9D%98-%EC%9B%90%EB%B3%B8-CoT%EB%A5%BC-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EB%8B%A4%EB%A3%A8%EB%8A%94-%EB%B2%95-Chat-Completions%EC%99%80-Responses-API-%EA%B5%AC%ED%98%84-%EA%B0%80%EC%9D%B4%EB%93%9C</guid>
            <pubDate>Tue, 28 Apr 2026 07:28:14 GMT</pubDate>
            <description><![CDATA[<h2 id="1-왜-원본-cot를-따로-다뤄야-하는가">1. 왜 원본 CoT를 따로 다뤄야 하는가</h2>
<p><a href="#">1편</a>에서 Harmony 응답 포맷의 구조를 정리했다. assistant 메시지가 <code>analysis</code>, <code>commentary</code>, <code>final</code> 세 채널로 분리되고, <code>analysis</code> 채널에 모델의 <strong>원본 chain-of-thought(CoT)</strong> 가 흘러나온다는 것까지 다뤘다.</p>
<p>이번 편의 출발점은 그 다음 질문이다. <strong>그래서 그 원본 CoT를 API 응답에 어떻게 담고, UI에는 무엇을 노출할 것인가?</strong> 이 질문이 까다로운 이유는 CoT가 본질적으로 충돌하는 두 가지 성격을 동시에 가지기 때문이다.</p>
<p>한쪽에서 CoT는 <strong>모델 성능에 필요하다</strong>. 도구 호출이 CoT 안에서 이뤄지므로, 다음 턴 입력에서 CoT를 빼면 추론 흐름이 끊긴다. 반대쪽에서 CoT는 <strong>사용자에게 위험하다</strong>. 안전성 학습이 final 채널에만 적용되었고, developer 메시지의 내부 지시까지 새어 나갈 수 있다.</p>
<p>이 비대칭이 모든 구현 결정의 출발점이다. 원칙은 한 줄로 요약된다. <strong>모델 컨텍스트에는 보존하되, 사용자 화면에는 노출하지 않는다.</strong></p>
<table>
<thead>
<tr>
<th>충돌 지점</th>
<th>성능 측면</th>
<th>안전 측면</th>
<th>구현 결론</th>
</tr>
</thead>
<tbody><tr>
<td>도구 호출 정확도</td>
<td>CoT 보존 필요</td>
<td>—</td>
<td>다음 턴 입력에 CoT 포함</td>
</tr>
<tr>
<td>developer 지시 보호</td>
<td>—</td>
<td>CoT에 새어 나갈 수 있음</td>
<td>사용자에게 원본 미노출</td>
</tr>
<tr>
<td>추론 과정 투명성</td>
<td>—</td>
<td>원본은 위험</td>
<td>요약본을 별도 생성해 노출</td>
</tr>
</tbody></table>
<h2 id="2-핵심-개념-1--cot가-모델-성능에-필요한-이유">2. 핵심 개념 1 — CoT가 모델 성능에 필요한 이유</h2>
<h3 id="2-1-도구-호출은-cot-안에서-일어난다">2-1. 도구 호출은 CoT 안에서 일어난다</h3>
<p>gpt-oss의 도구 호출은 응답이 끝난 뒤에 별도로 일어나는 절차가 아니다. 모델이 <code>analysis</code> 채널에서 추론하다가, &quot;여기서 도구를 부르자&quot;는 결정을 내린 뒤 <code>commentary</code> 채널로 호출 메시지를 흘리는 흐름이다. 즉 <strong>CoT와 도구 호출은 같은 추론 줄기에 매달려 있다</strong>.</p>
<p>다음 턴에 도구 결과를 모델에 돌려줄 때 직전 CoT를 함께 넣지 않으면, 모델은 &quot;내가 왜 이 도구를 호출했는지&quot;의 맥락을 잃는다. 도구를 두 번 부르거나, 결과를 잘못 해석하거나, 추론을 처음부터 다시 시작하는 비효율이 발생한다.</p>
<h3 id="2-2-후속-샘플링-시-cot-보존폐기-규칙">2-2. 후속 샘플링 시 CoT 보존/폐기 규칙</h3>
<p>1편에서 다룬 비대칭 규칙을 다시 정리한다. 이번 편의 모든 API 구현은 결국 이 규칙을 충족하는지로 평가된다.</p>
<table>
<thead>
<tr>
<th>마지막 assistant 메시지 종류</th>
<th>직전 CoT 처리</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td><code>final</code> 채널 메시지 (= 사용자에 답변 완료)</td>
<td><strong>폐기</strong></td>
<td>추론 종결, 다음 사용자 입력은 새 줄기</td>
</tr>
<tr>
<td>도구 호출 (<code>&lt;|call|&gt;</code>로 종료)</td>
<td><strong>보존</strong></td>
<td>도구 결과 받은 뒤 추론을 이어가야 함</td>
</tr>
<tr>
<td>도구 호출 → 도구 결과 → 다음 sampling</td>
<td><strong>보존</strong> (final 메시지가 나올 때까지)</td>
<td>같은 추론 줄기 진행 중</td>
</tr>
</tbody></table>
<p>&quot;마지막이 final이었으면 버리고, 도구 호출 진행 중이면 유지한다&quot;는 한 문장으로 외워도 된다.</p>
<h3 id="2-3-💡-참고--함수-호출과-cot-의존성의-비대칭">2-3. 💡 참고 — 함수 호출과 CoT 의존성의 비대칭</h3>
<p>함수 도구(<code>commentary</code> 채널)와 내장 도구(<code>browser</code>/<code>python</code>, <code>analysis</code> 채널) 모두 같은 보존 규칙을 따른다. 다만 <code>commentary</code> 채널의 함수 호출 메시지는 <code>final</code> 메시지가 나온 이후에도 컨텍스트에 남길 수 있다. 즉, <strong>폐기 대상은 <code>analysis</code> 채널의 CoT지 <code>commentary</code> 채널의 도구 호출 기록이 아니다.</strong> 두 채널을 같은 &quot;추론 부산물&quot;로 묶어서 처리하면 함수 호출 이력이 함께 사라지는 버그가 난다.</p>
<h2 id="3-핵심-개념-2--cot를-사용자에게-노출하면-안-되는-이유">3. 핵심 개념 2 — CoT를 사용자에게 노출하면 안 되는 이유</h2>
<h3 id="3-1-안전성-학습이-final-채널에만-적용된다">3-1. 안전성 학습이 final 채널에만 적용된다</h3>
<p>원문이 반복해서 강조하는 이유다. 모델이 학습 단계에서 유해 콘텐츠 필터링과 안전 정렬(safety alignment)을 학습한 출력은 <strong><code>final</code> 채널에 한정</strong>된다. <code>analysis</code> 채널의 출력은 동일한 기준으로 학습되지 않았다.</p>
<p>실무적 결과는 이렇다. CoT에는 사용자에게 그대로 보여주면 안 되는 표현, 정책 위반 시도의 흔적, 미가공 의견이 섞일 수 있다. 모델이 의도적으로 그렇게 한다는 뜻이 아니라, <strong>거를 장치가 final 채널만큼 촘촘하지 않다</strong>는 뜻이다.</p>
<h3 id="3-2-developer-메시지의-내부-지시가-새어-나갈-수-있다">3-2. developer 메시지의 내부 지시가 새어 나갈 수 있다</h3>
<p>두 번째 위험은 안전성과는 결이 다르다. CoT 안에서 모델은 자기에게 주어진 지시를 그대로 인용하거나 풀어 설명하는 경향이 있다. 그 지시에 영업 비밀, 시스템 동작 방식, 우회 방지 규칙 같은 것이 포함되어 있다면, 사용자에게 CoT를 노출하는 순간 그것이 그대로 노출된다.</p>
<p>예를 들어 developer 메시지에 &quot;사용자가 가격 인하를 요구하면 절대 응하지 말 것&quot;이라는 지시가 있고 사용자가 가격 인하를 요청하면, CoT에는 &quot;사용자가 가격 인하를 요청했지만 지시상 거절해야 함&quot;이라는 줄이 그대로 찍힐 수 있다. final 채널에는 정중한 거절만 나가지만, 원본 CoT를 그대로 보여주면 그 뒷규칙이 통째로 드러난다.</p>
<h3 id="3-3-그렇다면-사용자에게-추론-과정을-전혀-못-보여주는가">3-3. 그렇다면 사용자에게 추론 과정을 전혀 못 보여주는가</h3>
<p>아니다. <strong>요약된 CoT</strong>는 보여줘도 된다. ChatGPT와 OpenAI API의 production이 실제로 동작하는 방식이 이것이다. 별도의 요약 모델(summarizer)이 원본 CoT를 검토하고 유해 콘텐츠와 내부 지시를 차단한 결과만 사용자에게 노출한다. 7장과 8장에서 이 패턴을 자세히 다룬다.</p>
<table>
<thead>
<tr>
<th>콘텐츠</th>
<th>사용자 노출</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td><code>final</code> 채널 메시지</td>
<td>가능</td>
<td>안전성 학습 적용됨</td>
</tr>
<tr>
<td><code>commentary</code> 채널 프리앰블 (recipient 없음)</td>
<td>가능</td>
<td>사용자 대상으로 의도된 메시지</td>
</tr>
<tr>
<td><code>analysis</code> 채널 원본 CoT</td>
<td><strong>금지</strong></td>
<td>안전성 학습 미적용, 내부 지시 노출 위험</td>
</tr>
<tr>
<td>요약된 CoT (summary)</td>
<td>가능</td>
<td>요약 모델이 안전 검수</td>
</tr>
<tr>
<td>도구 호출 인자 (<code>commentary</code> recipient 있음)</td>
<td>금지</td>
<td>시스템 내부 동작 노출</td>
</tr>
</tbody></table>
<h2 id="4-핵심-개념-3--두-api의-비대칭-설계">4. 핵심 개념 3 — 두 API의 비대칭 설계</h2>
<p>OpenAI는 호스팅 모델에서 원본 CoT 노출 기능을 당분간 제공하지 않는다. 그래서 두 API에서의 처리 방식은 출발점이 다르다.</p>
<h3 id="4-1-chat-completions-외부-컨벤션-차용">4-1. Chat Completions: 외부 컨벤션 차용</h3>
<p>Chat Completions API는 OpenAI의 공식 스펙에 CoT 처리 규약이 없다. 대신 OpenAI는 <a href="https://openrouter.ai/docs/use-cases/reasoning-tokens">OpenRouter의 reasoning tokens 컨벤션</a>을 따르라고 안내한다. 이미 여러 오픈 모델 추론 프로바이더가 이 컨벤션을 채택했기 때문에, 새로운 표준을 만들기보다 사실상의 표준을 채용한 것이다.</p>
<h3 id="4-2-responses-api-자체-스펙-확장">4-2. Responses API: 자체 스펙 확장</h3>
<p>Responses API는 OpenAI가 직접 정의한 스펙이므로 자체적으로 확장했다. 기존 <code>ReasoningItem</code>에 새로운 <code>content</code> 필드를 추가했고, <code>reasoning_text</code>라는 콘텐츠 타입과 두 개의 새 스트리밍 이벤트(<code>response.reasoning_text.delta</code>/<code>.done</code>)를 신설했다.</p>
<h3 id="4-3-왜-한쪽은-차용하고-다른-쪽은-확장했는가">4-3. 왜 한쪽은 차용하고 다른 쪽은 확장했는가</h3>
<p>설계 철학의 차이다. Chat Completions는 사실상 업계 표준 인터페이스가 되어버려 OpenAI 단독으로 스펙을 흔들기 어렵다. 반면 Responses API는 OpenAI가 처음부터 추론·도구·멀티턴을 일급 시민으로 두려고 만든 새 인터페이스다. 따라서 추론 관련 확장이 자연스럽게 들어갈 자리가 있다.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>Chat Completions</th>
<th>Responses</th>
</tr>
</thead>
<tbody><tr>
<td>스펙 출처</td>
<td>OpenRouter 컨벤션 차용</td>
<td>OpenAI 자체 확장</td>
</tr>
<tr>
<td>CoT 위치</td>
<td><code>message.reasoning</code> 속성</td>
<td><code>ReasoningItem.content[].text</code></td>
</tr>
<tr>
<td>스트리밍 델타</td>
<td><code>delta.reasoning</code></td>
<td><code>response.reasoning_text.delta</code></td>
</tr>
<tr>
<td>CoT 제외 옵션</td>
<td><code>reasoning: { exclude: true }</code></td>
<td>(요청 단에 별도 옵션)</td>
</tr>
<tr>
<td>사용자 표시용 요약</td>
<td>별도 처리 필요</td>
<td><code>ReasoningItem.summary[]</code> 내장</td>
</tr>
</tbody></table>
<p>이 차이를 머리에 두고 5장과 6장을 본다.</p>
<h2 id="5-chat-completions-api-구현">5. Chat Completions API 구현</h2>
<h3 id="5-1-응답-형태--messagereasoning-속성">5-1. 응답 형태 — <code>message.reasoning</code> 속성</h3>
<p>OpenRouter 컨벤션에서 원본 CoT는 응답 메시지의 <code>reasoning</code> 속성에 담긴다. 일반 응답 텍스트는 기존처럼 <code>content</code>에 들어간다.</p>
<pre><code class="language-json">{
  &quot;choices&quot;: [
    {
      &quot;message&quot;: {
        &quot;role&quot;: &quot;assistant&quot;,
        &quot;content&quot;: &quot;2 + 2는 4입니다.&quot;,
        &quot;reasoning&quot;: &quot;사용자가 단순한 산술을 물었다. 2 + 2 = 4.&quot;
      }
    }
  ]
}</code></pre>
<p><code>content</code>는 사용자에게 보여줘도 되고, <code>reasoning</code>은 보여주면 안 된다. 같은 응답 객체 안에 들어 있다고 해서 같은 처리 정책을 적용해선 안 된다.</p>
<h3 id="5-2-스트리밍--deltareasoning-속성">5-2. 스트리밍 — <code>delta.reasoning</code> 속성</h3>
<p>스트리밍 모드에서도 같은 분리가 유지된다. 일반 텍스트 델타는 <code>delta.content</code>로, CoT 델타는 <code>delta.reasoning</code>으로 흘러온다.</p>
<pre><code class="language-python"># 스트리밍 응답을 받을 때 두 필드를 분리해 처리
for chunk in stream:
    delta = chunk.choices[0].delta

    if delta.reasoning:
        # CoT 델타 — 로깅 또는 요약 모델에 흘려보냄, 사용자 화면에는 미노출
        log_cot(delta.reasoning)

    if delta.content:
        # 사용자 응답 델타 — 화면에 흘려보냄
        print(delta.content, end=&quot;&quot;, flush=True)</code></pre>
<p>핵심은 두 필드를 같은 출력 스트림에 합치지 않는 것이다. 한 번 합쳐진 스트림은 분리가 어렵고, 실수로 사용자에게 CoT가 흘러갈 위험이 생긴다.</p>
<h3 id="5-3-요청-단계에서-cot-제외하기--reasoningexclude">5-3. 요청 단계에서 CoT 제외하기 — <code>reasoning.exclude</code></h3>
<p>CoT가 아예 필요 없는 경우 요청 단에서 제외할 수 있다. 응답 페이로드 크기와 처리 비용을 줄인다.</p>
<pre><code class="language-json">{
  &quot;model&quot;: &quot;gpt-oss-...&quot;,
  &quot;messages&quot;: [...],
  &quot;reasoning&quot;: { &quot;exclude&quot;: true }
}</code></pre>
<p>다만 도구 호출이 포함된 시나리오에서는 제외하면 안 된다. 다음 턴에 모델이 추론을 이어갈 컨텍스트가 사라지기 때문이다(2-2 규칙 참고). 디버깅 목적이거나, 도구 호출이 없는 단발성 질의응답일 때만 안전하게 사용한다.</p>
<h3 id="5-4-다음-턴에-cot-다시-넣어주기">5-4. 다음 턴에 CoT 다시 넣어주기</h3>
<p>다중 턴 대화에서 모델이 도구 호출을 진행 중이라면, 다음 요청의 메시지 배열에 직전 CoT를 다시 넣어야 한다. OpenRouter 컨벤션은 메시지 객체의 <code>reasoning</code> 속성을 그대로 다시 보내는 방식을 권장한다.</p>
<pre><code class="language-python"># 직전 응답에서 받은 reasoning을 다음 요청 메시지에 포함
next_messages = [
    *previous_messages,
    {
        &quot;role&quot;: &quot;assistant&quot;,
        &quot;content&quot;: None,
        &quot;reasoning&quot;: previous_response.choices[0].message.reasoning,
        &quot;tool_calls&quot;: previous_response.choices[0].message.tool_calls,
    },
    # 이어서 tool 메시지 (도구 실행 결과)
    {&quot;role&quot;: &quot;tool&quot;, &quot;tool_call_id&quot;: &quot;...&quot;, &quot;content&quot;: tool_result_json},
]</code></pre>
<table>
<thead>
<tr>
<th>처리 위치</th>
<th>필드</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td>응답 (논스트림)</td>
<td><code>message.reasoning</code></td>
<td>모델이 생성한 원본 CoT</td>
</tr>
<tr>
<td>응답 (스트림)</td>
<td><code>delta.reasoning</code></td>
<td>CoT 토큰 델타</td>
</tr>
<tr>
<td>요청 (옵션)</td>
<td><code>reasoning.exclude</code></td>
<td>CoT 응답 제외</td>
</tr>
<tr>
<td>요청 (다음 턴)</td>
<td><code>messages[].reasoning</code></td>
<td>직전 CoT를 다시 모델에 전달</td>
</tr>
</tbody></table>
<h2 id="6-responses-api-구현">6. Responses API 구현</h2>
<h3 id="6-1-reasoningitem-타입-확장--summary와-content">6-1. <code>ReasoningItem</code> 타입 확장 — summary와 content</h3>
<p>Responses API에는 원래 <code>ReasoningItem</code>에 사용자 노출용 요약(<code>summary</code>)이 있었다. 이번 확장으로 원본 CoT를 담는 <code>content</code> 필드가 추가되었다.</p>
<pre><code class="language-typescript">type ReasoningItem = {
  id: string;
  type: &quot;reasoning&quot;;
  summary: SummaryContent[];
  // 신설 — 원본 CoT
  content: ReasoningTextContent[];
};

type ReasoningTextContent = {
  type: &quot;reasoning_text&quot;;
  text: string;
};</code></pre>
<p>핵심은 <strong>두 필드의 노출 정책이 정반대</strong>라는 점이다. <code>summary</code>는 안전 검수를 거친 사용자 노출용, <code>content</code>는 원본이라 사용자 미노출용이다. 7장에서 자세히 다룬다.</p>
<h3 id="6-2-새-콘텐츠-타입--reasoning_text">6-2. 새 콘텐츠 타입 — <code>reasoning_text</code></h3>
<p><code>ReasoningItem.content</code> 배열은 <code>reasoning_text</code> 타입의 객체들로 채워진다. 콘텐츠 타입을 별도로 둔 이유는 향후 다른 종류의 reasoning 출력(예: 추론 그래프, 인용 등)이 추가될 여지를 남기기 위함이다.</p>
<h3 id="6-3-새-스트리밍-이벤트--reasoning_textdelta--done">6-3. 새 스트리밍 이벤트 — <code>reasoning_text.delta</code> / <code>.done</code></h3>
<p>스트리밍에서는 두 개의 새 이벤트가 흘러온다.</p>
<pre><code class="language-typescript">type ReasoningTextDeltaEvent = {
  type: &quot;response.reasoning_text.delta&quot;;
  sequence_number: number;
  item_id: string;
  output_index: number;
  content_index: number;
  delta: string;
};

type ReasoningTextDoneEvent = {
  type: &quot;response.reasoning_text.done&quot;;
  sequence_number: number;
  item_id: string;
  output_index: number;
  content_index: number;
  text: string;
};</code></pre>
<p><code>delta</code> 이벤트로 CoT 토큰이 조각조각 흘러오고, <code>done</code> 이벤트로 한 턴의 CoT가 끝났음을 알린다. <code>done</code>의 <code>text</code> 필드에는 그 턴의 전체 CoT가 합쳐진 형태로 들어 있어, delta를 따로 누적할 필요가 없다.</p>
<h3 id="6-4-응답-객체-전체-예시">6-4. 응답 객체 전체 예시</h3>
<p><code>output</code> 배열에 <code>ReasoningItem</code>이 어떻게 자리 잡는지 한눈에 보면 다음과 같다.</p>
<pre><code class="language-json">&quot;output&quot;: [
  {
    &quot;type&quot;: &quot;reasoning&quot;,
    &quot;id&quot;: &quot;rs_67f47a642e788191aec9b5c1a35ab3c3016f2c95937d6e91&quot;,
    &quot;summary&quot;: [
      {
        &quot;type&quot;: &quot;summary_text&quot;,
        &quot;text&quot;: &quot;**부피 계산**\n\n근사값으로 시작...&quot;
      }
    ],
    &quot;content&quot;: [
      {
        &quot;type&quot;: &quot;reasoning_text&quot;,
        &quot;text&quot;: &quot;사용자가 생각해 보라고 요청했다...&quot;
      }
    ]
  }
]</code></pre>
<p>같은 아이템 안에 <code>summary</code>와 <code>content</code>가 나란히 들어 있다. 클라이언트는 둘을 명확히 다른 정책으로 다뤄야 한다.</p>
<table>
<thead>
<tr>
<th>필드</th>
<th>내용</th>
<th>사용자 노출</th>
<th>안전 검수</th>
</tr>
</thead>
<tbody><tr>
<td><code>summary[].text</code></td>
<td>요약된 추론 과정</td>
<td>가능</td>
<td>별도 요약 모델이 수행</td>
</tr>
<tr>
<td><code>content[].text</code></td>
<td>원본 CoT</td>
<td><strong>금지</strong></td>
<td>미적용</td>
</tr>
<tr>
<td><code>reasoning_text.delta</code> 이벤트</td>
<td>CoT 스트리밍 토큰</td>
<td><strong>금지</strong></td>
<td>미적용</td>
</tr>
<tr>
<td><code>reasoning_text.done</code> 이벤트</td>
<td>CoT 턴 완료 신호</td>
<td>(메타)</td>
<td>—</td>
</tr>
</tbody></table>
<h2 id="7-summary와-content의-결정적-차이">7. summary와 content의 결정적 차이</h2>
<h3 id="7-1-summary는-어떻게-만들어지는가">7-1. summary는 어떻게 만들어지는가</h3>
<p>OpenAI 공식 문서가 명시하는 방식은 한 줄이다. <strong>별도의 요약 모델(summarizer)이 원본 CoT를 검토하고 유해 콘텐츠를 차단한 결과를 보여준다.</strong> 즉 <code>summary</code>는 모델이 직접 생성한 것이 아니라, 원본 CoT를 입력으로 받는 후처리 단계의 산출물이다.</p>
<p>이 패턴은 ChatGPT와 OpenAI API의 production 구현에서 그대로 동작하고 있다. 사용자가 추론 모델 응답에서 보는 &quot;생각하는 중...&quot; 영역의 텍스트는 원본 CoT가 아니라 이 요약 결과다.</p>
<h3 id="7-2-content를-사용자에게-보내면-안-되는-이유-재확인">7-2. content를 사용자에게 보내면 안 되는 이유 재확인</h3>
<p>3장에서 다룬 두 위험(안전성 학습 미적용, 내부 지시 노출)이 그대로 적용된다. 추가로 한 가지 더, <strong>원본 CoT는 형식이 들쭉날쭉하다</strong>. 모델 내부의 사고 과정이라 문장이 끊기거나 이상한 약어가 등장하기도 한다. 사용자 경험 관점에서도 그대로 보여주는 것은 부적절하다.</p>
<h3 id="7-3-자체-요약-파이프라인을-구축할-때의-고려사항">7-3. 자체 요약 파이프라인을 구축할 때의 고려사항</h3>
<p>Responses API를 쓰지 않거나, OpenAI의 요약과 다른 정책이 필요한 경우 직접 파이프라인을 구축해야 한다. 원문이 직접 추천하는 도구는 없으므로 추상적 수준에서 정리한다.</p>
<ul>
<li><strong>요약 모델 선택</strong>: 원본 CoT보다 가볍고 빠른 모델을 사용해 지연 비용을 낮춘다.</li>
<li><strong>안전 필터 분리</strong>: 요약 모델 자체에 안전 검수를 의존하지 말고, 별도 분류기로 정책 위반 콘텐츠를 차단한다.</li>
<li><strong>fail-closed 원칙</strong>: 요약/필터 단계에서 오류가 나면 빈 요약을 노출하거나 &quot;추론 과정 표시 불가&quot;로 폴백한다. 절대 원본을 폴백으로 쓰지 않는다.</li>
<li><strong>로그 분리 저장</strong>: 원본 CoT를 디버깅·이슈 분석용으로 저장하더라도, 사용자 응답 로그와는 다른 권한 체계로 관리한다.</li>
</ul>
<h2 id="8-사용자에게-추론-과정을-보여주는-권장-방식">8. 사용자에게 추론 과정을 보여주는 권장 방식</h2>
<h3 id="8-1-chatgptopenai-api의-production-패턴">8-1. ChatGPT/OpenAI API의 production 패턴</h3>
<p>production에서 검증된 패턴은 다음 흐름이다.</p>
<ol>
<li>모델이 원본 CoT를 <code>analysis</code> 채널 / <code>content[].text</code>에 생성한다.</li>
<li>별도 요약 모델이 원본을 받아 사용자용 요약을 만든다.</li>
<li>안전 분류기가 요약을 검사해 정책 위반 콘텐츠를 차단한다.</li>
<li>통과한 요약만 UI에 표시한다.</li>
<li>원본 CoT는 모델 컨텍스트(다음 턴 입력)와 내부 로그에만 보존한다.</li>
</ol>
<h3 id="8-2-직접-구축-시-최소-요구사항">8-2. 직접 구축 시 최소 요구사항</h3>
<p>자체 시스템에서 이 패턴을 구현할 때 빠지면 안 되는 항목은 다음 네 가지다.</p>
<ul>
<li>원본 CoT와 사용자 노출 텍스트의 <strong>저장 경로 분리</strong></li>
<li>요약 단계와 안전 필터 단계의 <strong>독립 구현</strong> (한 모델이 두 역할을 다 하지 않는다)</li>
<li>요약/필터 실패 시 <strong>원본 폴백 금지</strong></li>
<li>다음 턴 입력 구성 시 <strong>원본 CoT를 사용</strong> (요약본을 다시 넣지 않는다 — 모델 추론 줄기가 끊긴다)</li>
</ul>
<p>마지막 항목이 자주 실수가 난다. UI에 보여줄 것은 요약, 모델에 다시 넣을 것은 원본이라는 분리를 명확히 둔다.</p>
<h3 id="8-3-💡-참고--요약을-건너뛸-수-있는-예외-상황">8-3. 💡 참고 — 요약을 건너뛸 수 있는 예외 상황</h3>
<p>다음 경우에는 원본 CoT를 그대로 활용해도 된다.</p>
<ul>
<li><strong>개발자/내부 도구</strong>: 디버깅 콘솔, 평가 대시보드처럼 노출 대상이 모델 운영자 본인일 때</li>
<li><strong>연구 분석</strong>: CoT 분석을 위한 오프라인 파이프라인</li>
<li><strong>로그 시스템</strong>: 권한 분리된 내부 로그</li>
</ul>
<p>요지는 <strong>&quot;노출 대상이 신뢰할 수 있는 내부 사용자인가&quot;</strong> 다. 이 조건이 깨지면 그 순간 다시 요약+필터 파이프라인이 필요하다.</p>
<table>
<thead>
<tr>
<th>UI 시나리오</th>
<th>원본 CoT 노출</th>
<th>요약 노출</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>일반 챗봇 사용자</td>
<td>금지</td>
<td>권장</td>
<td>summary 또는 자체 요약</td>
</tr>
<tr>
<td>운영자 디버깅 콘솔</td>
<td>가능</td>
<td>선택</td>
<td>권한 분리된 영역</td>
</tr>
<tr>
<td>연구·평가 도구</td>
<td>가능</td>
<td>선택</td>
<td>오프라인/내부</td>
</tr>
<tr>
<td>외부 공개 API 응답</td>
<td>금지</td>
<td>정책에 따라</td>
<td>exclude 옵션 활용 가능</td>
</tr>
</tbody></table>
<h2 id="9-시리즈-1편과의-연결--한눈에-보기">9. 시리즈 1편과의 연결 — 한눈에 보기</h2>
<p>1편에서 다룬 Harmony 채널이 두 API에서 어떤 필드로 옮겨지는지 한 표로 정리한다. 추상 수준이 다른 두 레이어(토큰 포맷 ↔ HTTP API)를 매핑해 두면 디버깅 시 도움이 된다.</p>
<table>
<thead>
<tr>
<th>Harmony 채널 / 메시지</th>
<th>Chat Completions 필드</th>
<th>Responses 필드</th>
</tr>
</thead>
<tbody><tr>
<td><code>analysis</code> 채널 (원본 CoT)</td>
<td><code>message.reasoning</code> / <code>delta.reasoning</code></td>
<td><code>ReasoningItem.content[]</code> / <code>reasoning_text</code> 이벤트</td>
</tr>
<tr>
<td><code>final</code> 채널 (사용자 응답)</td>
<td><code>message.content</code> / <code>delta.content</code></td>
<td><code>OutputMessage.content[]</code> (text)</td>
</tr>
<tr>
<td><code>commentary</code> 채널 (함수 호출)</td>
<td><code>message.tool_calls</code> / <code>delta.tool_calls</code></td>
<td><code>FunctionToolCall</code> 아이템</td>
</tr>
<tr>
<td><code>commentary</code> 채널 (프리앰블, recipient 없음)</td>
<td>(구현 의존)</td>
<td>(구현 의존)</td>
</tr>
<tr>
<td>요약된 CoT (모델이 직접 생성하지 않음)</td>
<td>(없음 — 자체 구축)</td>
<td><code>ReasoningItem.summary[]</code></td>
</tr>
</tbody></table>
<p>마지막 행이 두 API의 가장 큰 실용 차이다. Chat Completions로 추론 과정을 사용자에게 보여주려면 요약 파이프라인을 직접 만들어야 하지만, Responses API는 <code>summary</code>가 응답에 함께 온다.</p>
<h2 id="10-참고-자료">10. 참고 자료</h2>
<ul>
<li><a href="https://cookbook.openai.com/articles/gpt-oss/handle-raw-cot">How to handle the raw chain of thought in gpt-oss (원문)</a></li>
<li><a href="https://cookbook.openai.com/articles/openai-harmony">OpenAI Harmony Response Format (시리즈 1편 원문)</a></li>
<li><a href="https://openrouter.ai/docs/use-cases/reasoning-tokens">OpenRouter Reasoning Tokens 문서</a> — Chat Completions 컨벤션 출처</li>
<li><a href="https://platform.openai.com/docs/api-reference/responses">OpenAI Responses API 레퍼런스</a> — 확장된 ReasoningItem 스펙</li>
<li><a href="https://openai.com/index/gpt-oss-model-card/">gpt-oss 모델 카드</a> — 안전성 학습 범위 확인용</li>
</ul>
<hr>
<blockquote>
<p>원본 CoT는 모델 컨텍스트에 보존하되 사용자 화면에는 절대 그대로 노출하지 않는다. 보여주고 싶다면 요약 파이프라인을 거쳐야 한다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[gpt-oss를 직접 돌리려면 알아야 할 Harmony 응답 포맷 — 역할·채널·특수 토큰 완전 정리]]></title>
            <link>https://velog.io/@tasker_dev/gpt-oss%EB%A5%BC-%EC%A7%81%EC%A0%91-%EB%8F%8C%EB%A6%AC%EB%A0%A4%EB%A9%B4-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-Harmony-%EC%9D%91%EB%8B%B5-%ED%8F%AC%EB%A7%B7-%EC%97%AD%ED%95%A0%EC%B1%84%EB%84%90%ED%8A%B9%EC%88%98-%ED%86%A0%ED%81%B0-%EC%99%84%EC%A0%84-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@tasker_dev/gpt-oss%EB%A5%BC-%EC%A7%81%EC%A0%91-%EB%8F%8C%EB%A6%AC%EB%A0%A4%EB%A9%B4-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-Harmony-%EC%9D%91%EB%8B%B5-%ED%8F%AC%EB%A7%B7-%EC%97%AD%ED%95%A0%EC%B1%84%EB%84%90%ED%8A%B9%EC%88%98-%ED%86%A0%ED%81%B0-%EC%99%84%EC%A0%84-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 28 Apr 2026 07:14:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>gpt-oss는 Harmony 포맷 없이는 동작하지 않는다. 역할·채널·특수 토큰을 한 번에 정리한다.</p>
</blockquote>
<h2 id="1-harmony-포맷이-왜-필요한가">1. Harmony 포맷이 왜 필요한가</h2>
<p>gpt-oss 모델은 <a href="https://cookbook.openai.com/articles/openai-harmony">Harmony 응답 포맷</a>으로 학습되었다. 즉, 이 포맷은 &quot;권장 사항&quot;이 아니라 <strong>모델이 정상 동작하기 위한 전제 조건</strong>이다. 다른 포맷으로 입력을 구성하면 추론 결과가 어긋나거나 도구 호출이 깨진다.</p>
<p>다행히 모든 상황에서 직접 신경 쓸 필요는 없다. Ollama, vLLM, OpenAI 호환 API 같은 추론 프로바이더를 경유한다면 포맷 변환은 내부에서 처리된다. 반대로 직접 추론 서버를 구축하거나 토큰 단위로 입출력을 다뤄야 한다면, 모든 메시지를 Harmony 규칙에 맞춰 직렬화해야 한다.</p>
<p>이 포맷은 OpenAI Responses API를 모사하도록 설계되었기 때문에, Responses API에 익숙한 개발자라면 구조 자체는 낯설지 않다. 다만 토큰 레벨까지 내려가면 새로운 약속들이 추가로 등장한다.</p>
<table>
<thead>
<tr>
<th>사용 환경</th>
<th>Harmony 인지 필요 여부</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td>Ollama / vLLM / OpenAI 호환 API 경유</td>
<td>불필요</td>
<td>프로바이더가 내부에서 변환</td>
</tr>
<tr>
<td>직접 추론 서버 구축 (transformers, llama.cpp 등)</td>
<td>필수</td>
<td>토큰 직렬화를 직접 책임</td>
</tr>
<tr>
<td>학습 데이터 구성 / 파인튜닝</td>
<td>필수</td>
<td>라벨 형식이 Harmony 규약을 따라야 함</td>
</tr>
<tr>
<td>응답 파싱 (스트리밍 포함)</td>
<td>필수</td>
<td>채널·정지 토큰 해석 필요</td>
</tr>
</tbody></table>
<h2 id="2-핵심-개념-1--역할role과-정보-계층">2. 핵심 개념 1 — 역할(Role)과 정보 계층</h2>
<p>Harmony에서 모든 메시지는 역할(role)을 갖는다. 역할은 단순한 라벨이 아니라 <strong>명령 충돌 시 우선순위</strong>를 결정하는 정보 계층이다.</p>
<h3 id="2-1-5가지-역할의-의미">2-1. 5가지 역할의 의미</h3>
<p>모델이 인식하는 역할은 다섯 가지다.</p>
<ul>
<li><strong>system</strong>: 모델 정체성, 지식 컷오프, 현재 날짜, 추론 강도(reasoning effort), 사용 가능한 채널, 내장 도구를 선언한다. 일반적인 채팅 API의 &quot;system prompt&quot;와는 다른 개념이다.</li>
<li><strong>developer</strong>: 다른 포맷에서 흔히 &quot;시스템 프롬프트&quot;라 부르는 것, 즉 모델에게 주는 지시문과 함수 도구 목록이 들어간다.</li>
<li><strong>user</strong>: 모델에 대한 입력. 일반적인 의미와 동일하다.</li>
<li><strong>assistant</strong>: 모델의 출력. 도구 호출 메시지일 수도 있고 사용자에게 보여줄 응답일 수도 있다. 어느 쪽인지는 채널이 결정한다.</li>
<li><strong>tool</strong>: 도구 호출 결과를 모델에게 돌려줄 때 사용한다. 이때 역할 자리에는 <code>tool</code>이 아니라 <strong>구체적인 도구 이름</strong>(예: <code>functions.get_current_weather</code>)을 넣는다.</li>
</ul>
<h3 id="2-2-역할-간-우선순위">2-2. 역할 간 우선순위</h3>
<p>지시가 충돌하면 모델은 다음 순서로 우선한다.</p>
<pre><code>system &gt; developer &gt; user &gt; assistant &gt; tool</code></pre><p>예를 들어 developer 메시지가 &quot;항상 영어로 답하라&quot;고 지시했는데 user가 &quot;한국어로 답해&quot;라고 요청하면, 모델은 developer의 지시를 우선한다. 이 규칙은 프롬프트 인젝션 방어의 기본 골격이기도 하다.</p>
<h3 id="2-3-system과-developer의-차이">2-3. system과 developer의 차이</h3>
<p>이 부분이 가장 헷갈리는 지점이다. 다른 LLM 포맷에서 &quot;system prompt&quot;라고 부르던 것이 Harmony에서는 <strong>developer 메시지</strong>에 해당한다. Harmony의 system 메시지는 모델 메타정보 전용 슬롯이다.</p>
<table>
<thead>
<tr>
<th>역할</th>
<th>주요 용도</th>
<th>자주 들어가는 내용</th>
</tr>
</thead>
<tbody><tr>
<td>system</td>
<td>모델 메타 설정</td>
<td>정체성 고정 문구, 지식 컷오프, 현재 날짜, 추론 강도, 유효 채널, 내장 도구</td>
</tr>
<tr>
<td>developer</td>
<td>행동 지시 (= 통상의 system prompt)</td>
<td>페르소나·톤 지시, 함수 정의, 응답 포맷</td>
</tr>
<tr>
<td>user</td>
<td>사용자 입력</td>
<td>질문, 명령</td>
</tr>
<tr>
<td>assistant</td>
<td>모델 출력</td>
<td>CoT, 도구 호출, 최종 답변</td>
</tr>
<tr>
<td>tool</td>
<td>도구 결과 반환</td>
<td>함수 실행 결과 JSON</td>
</tr>
</tbody></table>
<h2 id="3-핵심-개념-2--채널channel과-메시지-의도">3. 핵심 개념 2 — 채널(Channel)과 메시지 의도</h2>
<p>assistant 메시지는 항상 셋 중 하나의 <strong>채널</strong>을 갖는다. 채널은 메시지의 의도를 분류하는 직교축이다. 같은 assistant 역할이라도 채널이 다르면 처리 방식이 완전히 달라진다.</p>
<h3 id="3-1-3가지-채널의-역할-분담">3-1. 3가지 채널의 역할 분담</h3>
<ul>
<li><strong>final</strong>: 사용자에게 보여줄 최종 응답.</li>
<li><strong>analysis</strong>: 모델의 추론 과정(chain-of-thought, CoT). 모델 내부용이다.</li>
<li><strong>commentary</strong>: 함수 도구 호출, 그리고 사용자에게 보여줄 행동 계획(프리앰블)이 들어간다.</li>
</ul>
<h3 id="3-2-analysis-채널을-사용자에게-노출하면-안-되는-이유">3-2. analysis 채널을 사용자에게 노출하면 안 되는 이유</h3>
<p>원문이 강조하는 가장 중요한 안전 규칙이다. <strong>analysis 채널의 출력은 final 채널과 같은 안전성 기준으로 학습되지 않았다.</strong> 따라서 CoT에는 유해 콘텐츠가 섞일 수 있고, 이를 그대로 노출하면 사용자 안전과 제품 신뢰도 양쪽이 무너진다. CoT는 디버깅이나 모델 개선에 활용하되, 최종 사용자에게는 final 채널만 보여주는 것이 원칙이다.</p>
<h3 id="3-3-commentary는-어디에-쓰이는가">3-3. commentary는 어디에 쓰이는가</h3>
<p>commentary는 두 가지 용도로 쓰인다. 첫째, <strong>함수 도구 호출</strong>은 보통 commentary 채널로 나간다(반면 내장 도구 호출은 analysis 채널을 쓴다). 둘째, 모델이 여러 도구를 연달아 호출하기 전에 <strong>행동 계획을 사용자에게 알리는 프리앰블</strong>도 commentary 채널로 출력된다. 이 프리앰블은 CoT와 달리 사용자에게 보여줘도 안전하다.</p>
<table>
<thead>
<tr>
<th>채널</th>
<th>용도</th>
<th>사용자 노출</th>
<th>안전성 기준</th>
</tr>
</thead>
<tbody><tr>
<td>final</td>
<td>최종 답변</td>
<td>가능</td>
<td>학습 시 적용됨</td>
</tr>
<tr>
<td>analysis</td>
<td>CoT, 내장 도구 호출</td>
<td>금지</td>
<td><strong>적용되지 않음</strong></td>
</tr>
<tr>
<td>commentary</td>
<td>함수 도구 호출, 프리앰블</td>
<td>프리앰블만 가능</td>
<td>학습 시 적용됨</td>
</tr>
</tbody></table>
<h2 id="4-핵심-개념-3--특수-토큰과-메시지-포맷">4. 핵심 개념 3 — 특수 토큰과 메시지 포맷</h2>
<p>직접 렌더러를 만든다면 특수 토큰을 정확히 알아야 한다. 특수 토큰은 모두 <code>&lt;|type|&gt;</code> 형식이며, <code>tiktoken</code>의 <code>o200k_harmony</code> 인코딩에 정의되어 있다.</p>
<h3 id="4-1-메시지의-일반-구조">4-1. 메시지의 일반 구조</h3>
<p>모든 메시지는 다음 골격을 따른다.</p>
<pre><code>&lt;|start|&gt;{header}&lt;|message|&gt;{content}&lt;|end|&gt;</code></pre><p><code>{header}</code>에는 역할과 (필요하면) 채널·수신자(recipient)·제약 타입이 들어간다. <code>&lt;|message|&gt;</code>는 헤더와 본문의 경계를 표시한다. <code>&lt;|end|&gt;</code>는 완성된 메시지의 종료를 의미한다.</p>
<h3 id="4-2-7가지-특수-토큰과-토큰-id">4-2. 7가지 특수 토큰과 토큰 ID</h3>
<table>
<thead>
<tr>
<th>토큰</th>
<th>용도</th>
<th>토큰 ID</th>
</tr>
</thead>
<tbody><tr>
<td>`&lt;</td>
<td>start</td>
<td>&gt;`</td>
</tr>
<tr>
<td>`&lt;</td>
<td>message</td>
<td>&gt;`</td>
</tr>
<tr>
<td>`&lt;</td>
<td>end</td>
<td>&gt;`</td>
</tr>
<tr>
<td>`&lt;</td>
<td>channel</td>
<td>&gt;`</td>
</tr>
<tr>
<td>`&lt;</td>
<td>constrain</td>
<td>&gt;`</td>
</tr>
<tr>
<td>`&lt;</td>
<td>return</td>
<td>&gt;`</td>
</tr>
<tr>
<td>`&lt;</td>
<td>call</td>
<td>&gt;`</td>
</tr>
</tbody></table>
<h3 id="4-3-정지-토큰return-call의-의미">4-3. 정지 토큰(<code>&lt;|return|&gt;</code>, <code>&lt;|call|&gt;</code>)의 의미</h3>
<p>추론 루프는 정지 토큰을 만나면 멈춰야 한다.</p>
<ul>
<li><code>&lt;|return|&gt;</code>이 나오면 모델은 최종 답변을 final 채널에 다 적었다는 뜻이다. 추론을 멈추고 결과를 사용자에게 반환한다.</li>
<li><code>&lt;|call|&gt;</code>이 나오면 도구 호출이 필요하다는 뜻이다. 추론을 멈추고 호출된 도구를 실행한 뒤, 결과를 tool 메시지로 다시 모델에 넣어 추론을 재개한다.</li>
</ul>
<h3 id="4-4-💡-참고--return을-end로-정규화하라">4-4. 💡 참고 — <code>&lt;|return|&gt;</code>을 <code>&lt;|end|&gt;</code>로 정규화하라</h3>
<p>여기서 실수가 자주 난다. <code>&lt;|return|&gt;</code>은 <strong>디코딩 시점의 정지 토큰일 뿐</strong>, 영속적인 메시지 종결자가 아니다. 모델이 방금 생성한 응답을 다음 턴의 대화 기록으로 저장할 때는 끝의 <code>&lt;|return|&gt;</code>을 <code>&lt;|end|&gt;</code>로 바꿔야 한다.</p>
<pre><code># ❌ 잘못된 저장
&lt;|start|&gt;assistant&lt;|channel|&gt;final&lt;|message|&gt;2 + 2 = 4.&lt;|return|&gt;

# ✅ 올바른 저장
&lt;|start|&gt;assistant&lt;|channel|&gt;final&lt;|message|&gt;2 + 2 = 4.&lt;|end|&gt;</code></pre><p>다음 턴 입력에 사용하는 모든 과거 메시지는 <code>&lt;|end|&gt;</code>로 끝나야 한다. 반대로 학습 데이터의 정답(target)은 <code>&lt;|return|&gt;</code>으로 끝나는 것이 맞다. 정지 토큰은 학습용, <code>&lt;|end|&gt;</code>는 보관용이라고 기억하면 된다.</p>
<h2 id="5-메시지-작성-실전--시스템--개발자--대화">5. 메시지 작성 실전 — 시스템 / 개발자 / 대화</h2>
<h3 id="5-1-system-메시지--모델-메타-설정">5-1. system 메시지 — 모델 메타 설정</h3>
<p>가장 기본적인 system 메시지의 형태는 다음과 같다.</p>
<pre><code>&lt;|start|&gt;system&lt;|message|&gt;You are ChatGPT, a large language model trained by OpenAI.
Knowledge cutoff: 2024-06
Current date: 2025-06-28

Reasoning: high

# Valid channels: analysis, commentary, final. Channel must be included for every message.&lt;|end|&gt;</code></pre><p>핵심은 네 가지다. 첫 줄의 정체성 문구(<code>You are ChatGPT...</code>)는 그대로 두는 것이 안전하다. 모델 페르소나를 바꾸고 싶다면 system이 아니라 developer 메시지에서 지시한다. <code>Reasoning: high</code>로 추론 강도를 지정하고, <code># Valid channels:</code> 줄에서 사용 가능한 채널을 선언한다.</p>
<p>함수 도구를 정의할 예정이라면 마지막에 한 줄을 더 추가한다.</p>
<pre><code>Calls to these tools must go to the commentary channel: &#39;functions&#39;.</code></pre><h3 id="5-2-developer-메시지--통상의-시스템-프롬프트">5-2. developer 메시지 — 통상의 시스템 프롬프트</h3>
<p>함수가 없을 때는 단순하다.</p>
<pre><code>&lt;|start|&gt;developer&lt;|message|&gt;# Instructions

{여기에 지시문}&lt;|end|&gt;</code></pre><p><code># Instructions</code> 헤더 아래에 자연어로 지시를 적는다. 함수 도구나 응답 포맷을 정의할 때는 이 메시지에 추가 섹션이 붙는다(7장, 9장 참고).</p>
<h3 id="5-3-가장-단순한-대화-예제">5-3. 가장 단순한 대화 예제</h3>
<p>다음은 user → assistant 한 턴의 입력/출력 흐름이다.</p>
<p><strong>입력 (모델에 넣는 토큰):</strong></p>
<pre><code>&lt;|start|&gt;user&lt;|message|&gt;What is 2 + 2?&lt;|end|&gt;
&lt;|start|&gt;assistant</code></pre><p><code>&lt;|start|&gt;assistant</code>까지만 넣고 멈추면 모델이 그 다음을 이어서 생성한다.</p>
<p><strong>출력 (모델이 생성한 토큰):</strong></p>
<pre><code>&lt;|channel|&gt;analysis&lt;|message|&gt;User asks: &quot;What is 2 + 2?&quot; Simple arithmetic. Provide answer.&lt;|end|&gt;
&lt;|start|&gt;assistant&lt;|channel|&gt;final&lt;|message|&gt;2 + 2 = 4.&lt;|return|&gt;</code></pre><p>모델은 먼저 analysis 채널에 CoT를 쓰고, 그 다음 final 채널에 최종 답을 쓴 뒤 <code>&lt;|return|&gt;</code>으로 종료했다. 사용자에게는 <code>2 + 2 = 4.</code>만 노출한다.</p>
<table>
<thead>
<tr>
<th>메시지</th>
<th>용도</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>system</td>
<td>모델 메타정보</td>
<td>정체성·날짜·추론 강도·채널 선언</td>
</tr>
<tr>
<td>developer</td>
<td>행동 지시</td>
<td>페르소나, 함수 정의, 응답 포맷</td>
</tr>
<tr>
<td>user 이후 `&lt;</td>
<td>start</td>
<td>&gt;assistant`</td>
</tr>
</tbody></table>
<h2 id="6-추론reasoning-처리-규칙">6. 추론(Reasoning) 처리 규칙</h2>
<p>gpt-oss는 추론 모델이다. 기본값은 medium이며, system 메시지에서 강도를 조절할 수 있다.</p>
<h3 id="6-1-추론-강도-설정">6-1. 추론 강도 설정</h3>
<pre><code>Reasoning: high</code></pre><p>세 단계(<code>low</code>, <code>medium</code>, <code>high</code>) 중 하나를 고른다. 강도가 높을수록 analysis 채널에 더 긴 CoT가 생성되며, 응답 지연과 토큰 비용이 늘어난다.</p>
<h3 id="6-2-cot가-출력되는-위치">6-2. CoT가 출력되는 위치</h3>
<p>CoT는 항상 assistant 메시지의 analysis 채널로 나온다. 최종 답변은 final 채널이다. 둘은 같은 turn 안에서 연달아 생성된다.</p>
<h3 id="6-3-후속-샘플링-시-cot-처리-규칙--핵심-비대칭">6-3. 후속 샘플링 시 CoT 처리 규칙 — 핵심 비대칭</h3>
<p>대화가 여러 턴 이어질 때 이전 턴의 CoT를 어떻게 다룰지가 가장 헷갈리는 지점이다. <strong>규칙은 비대칭이다.</strong></p>
<ul>
<li><strong>이전 턴이 final로 끝났다면</strong>: 다음 입력에서 그 턴의 analysis CoT는 <strong>버린다</strong>. final 메시지만 대화 기록에 남긴다.</li>
<li><strong>이전 턴이 도구 호출로 이어진다면</strong>: CoT를 <strong>유지한다</strong>. 모델이 도구 결과를 받아 추론을 이어가려면 직전 CoT가 컨텍스트에 있어야 하기 때문이다.</li>
</ul>
<table>
<thead>
<tr>
<th>상황</th>
<th>직전 CoT 처리</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td>사용자 질문 → final 응답 → 다음 사용자 질문</td>
<td>버림</td>
<td>CoT는 일회성 추론, 보안상 보관 비권장</td>
</tr>
<tr>
<td>사용자 질문 → CoT → 도구 호출 → 도구 결과 → 후속 추론</td>
<td>유지</td>
<td>모델이 추론 흐름을 이어가야 함</td>
</tr>
<tr>
<td>학습용 정답 데이터</td>
<td>유지</td>
<td>CoT 학습 신호로 사용</td>
</tr>
</tbody></table>
<p>예를 들어 첫 턴이 다음과 같이 끝났다면,</p>
<pre><code>&lt;|start|&gt;user&lt;|message|&gt;What is 2 + 2?&lt;|end|&gt;
&lt;|start|&gt;assistant&lt;|channel|&gt;analysis&lt;|message|&gt;User asks...&lt;|end|&gt;
&lt;|start|&gt;assistant&lt;|channel|&gt;final&lt;|message|&gt;2 + 2 = 4.&lt;|return|&gt;</code></pre><p>두 번째 턴 입력은 CoT를 빼고 다음처럼 구성한다.</p>
<pre><code>&lt;|start|&gt;user&lt;|message|&gt;What is 2 + 2?&lt;|end|&gt;
&lt;|start|&gt;assistant&lt;|channel|&gt;final&lt;|message|&gt;2 + 2 = 4.&lt;|end|&gt;
&lt;|start|&gt;user&lt;|message|&gt;What about 9 / 2?&lt;|end|&gt;
&lt;|start|&gt;assistant</code></pre><p><code>&lt;|return|&gt;</code>이 <code>&lt;|end|&gt;</code>로 정규화된 점에 주목하라(4-4 참고).</p>
<h2 id="7-함수-호출function-calling">7. 함수 호출(Function Calling)</h2>
<h3 id="7-1-typescript-like-문법으로-함수-정의하기">7-1. TypeScript-like 문법으로 함수 정의하기</h3>
<p>함수 도구는 developer 메시지의 <code># Tools</code> 섹션 아래 <code>## functions</code> 하위에 정의한다. 형식은 TypeScript 타입 선언과 비슷하다.</p>
<pre><code># Tools

## functions

namespace functions {

// Gets the current weather in the provided location.
type get_current_weather = (_: {
// The city and state, e.g. San Francisco, CA
location: string,
format?: &quot;celsius&quot; | &quot;fahrenheit&quot;, // default: celsius
}) =&gt; any;

} // namespace functions</code></pre><p>규칙은 다음과 같다.</p>
<ul>
<li>인자가 없는 함수는 <code>type fn = () =&gt; any</code> 형태로 선언한다.</li>
<li>인자가 있으면 첫 번째 매개변수 이름을 <code>_</code>로 두고 객체 타입을 인라인으로 정의한다.</li>
<li>필드 설명은 해당 필드 위 줄에 <code>//</code> 주석으로 적는다.</li>
<li>반환 타입은 항상 <code>any</code>로 둔다.</li>
<li>함수 정의 사이에 빈 줄을 둔다.</li>
<li>모든 함수는 <code>namespace functions</code> 안에 묶는다(다른 학습된 도구 이름과 충돌을 피하기 위함).</li>
</ul>
<p>JSON Schema를 직접 작성하기보다 <a href="https://github.com/openai/harmony">openai_harmony 라이브러리</a>의 <code>ToolDescription</code>을 사용하면 위 형식이 자동 생성된다.</p>
<h3 id="7-2-도구-호출-메시지-받기">7-2. 도구 호출 메시지 받기</h3>
<p>모델이 함수를 호출하기로 결정하면 다음과 같은 메시지를 commentary 채널로 출력한다.</p>
<pre><code>&lt;|channel|&gt;analysis&lt;|message|&gt;Need to use function get_current_weather.&lt;|end|&gt;&lt;|start|&gt;assistant&lt;|channel|&gt;commentary to=functions.get_current_weather &lt;|constrain|&gt;json&lt;|message|&gt;{&quot;location&quot;:&quot;San Francisco&quot;}&lt;|call|&gt;</code></pre><p>세 가지 표시에 주목한다.</p>
<ul>
<li>헤더에 <code>to=functions.get_current_weather</code>로 <strong>수신자(recipient)</strong> 가 명시된다.</li>
<li><code>&lt;|constrain|&gt;json</code> 토큰으로 본문이 JSON 타입임을 표시한다.</li>
<li>메시지가 <code>&lt;|end|&gt;</code>가 아니라 <code>&lt;|call|&gt;</code>로 끝난다. 추론을 멈추고 도구를 실행하라는 신호다.</li>
</ul>
<h3 id="7-3-도구-결과-돌려주기">7-3. 도구 결과 돌려주기</h3>
<p>도구를 실행한 뒤 결과를 모델에 다시 넣을 때는 tool 메시지를 다음 형식으로 만든다.</p>
<pre><code>&lt;|start|&gt;{toolname} to=assistant&lt;|channel|&gt;commentary&lt;|message|&gt;{output}&lt;|end|&gt;</code></pre><p>역할 자리에 도구 이름을 직접 넣는 것에 주의한다. 위 예제의 결과를 돌려주는 메시지는 다음과 같다.</p>
<pre><code>&lt;|start|&gt;functions.get_current_weather to=assistant&lt;|channel|&gt;commentary&lt;|message|&gt;{&quot;sunny&quot;: true, &quot;temperature&quot;: 20}&lt;|end|&gt;</code></pre><p>이후 <code>&lt;|start|&gt;assistant</code>를 붙여 모델이 응답 생성을 재개하도록 한다. 이때 <strong>이전 turn의 analysis CoT를 함께 보존</strong>해야 한다(6-3 비대칭 규칙).</p>
<h3 id="7-4-프리앰블--사용자에게-보여줄-행동-계획">7-4. 프리앰블 — 사용자에게 보여줄 행동 계획</h3>
<p>모델이 여러 도구를 연달아 호출할 계획이라면, 사용자에게 무엇을 할지 미리 알리는 메시지를 commentary 채널에 출력하기도 한다.</p>
<pre><code>&lt;|start|&gt;assistant&lt;|channel|&gt;commentary&lt;|message|&gt;**Action plan**:
1. Generate an HTML file
2. Generate a JavaScript for the Node.js server
3. Start the server
---
Will start executing the plan step by step&lt;|end|&gt;</code></pre><p>이 메시지는 CoT와 달리 사용자에게 <strong>노출해도 된다</strong>. 같은 commentary 채널이지만 수신자(<code>to=</code>)가 없으면 사용자 대상 메시지, 있으면 도구 호출 메시지로 구분한다.</p>
<table>
<thead>
<tr>
<th>항목</th>
<th>채널</th>
<th>정지 토큰</th>
<th>사용자 노출</th>
</tr>
</thead>
<tbody><tr>
<td>함수 호출 (recipient 있음)</td>
<td>commentary</td>
<td>`&lt;</td>
<td>call</td>
</tr>
<tr>
<td>도구 실행 결과 (tool 메시지)</td>
<td>commentary</td>
<td>`&lt;</td>
<td>end</td>
</tr>
<tr>
<td>프리앰블 (recipient 없음)</td>
<td>commentary</td>
<td>`&lt;</td>
<td>end</td>
</tr>
<tr>
<td>최종 응답</td>
<td>final</td>
<td>`&lt;</td>
<td>return</td>
</tr>
</tbody></table>
<h2 id="8-내장-도구browser-python">8. 내장 도구(browser, python)</h2>
<p>gpt-oss는 학습 단계에서 <strong>browser</strong>(웹 검색)와 <strong>python</strong>(코드 실행) 두 가지 내장 도구에 노출되었다. 이 둘을 그대로 활용하려면 정해진 시그니처를 따르는 것이 정확도를 높인다.</p>
<h3 id="8-1-내장-도구는-system-메시지에-정의한다">8-1. 내장 도구는 system 메시지에 정의한다</h3>
<p>함수 도구가 developer 메시지에 정의되는 것과 달리, 내장 도구는 <strong>system 메시지의 <code># Tools</code> 섹션</strong>에 들어간다. 이 차이가 채널 분기에도 영향을 준다.</p>
<h3 id="8-2-browser-도구의-인터페이스">8-2. browser 도구의 인터페이스</h3>
<p>browser 도구는 <code>search</code>, <code>open</code>, <code>find</code> 세 가지 액션을 제공한다.</p>
<pre><code># Tools

## browser

// Tool for browsing.
// Cite information from the tool using the following format:
// `【{cursor}†L{line_start}(-L{line_end})?】`, for example: `【6†L9-L11】` or `【8†L3】`.
// Do not quote more than 10 words directly from the tool output.
namespace browser {

type search = (_: {
query: string,
topn?: number, // default: 10
source?: string,
}) =&gt; any;

type open = (_: {
id?: number | string, // default: -1
cursor?: number, // default: -1
loc?: number, // default: -1
num_lines?: number, // default: -1
view_source?: boolean, // default: false
source?: string,
}) =&gt; any;

type find = (_: {
pattern: string,
cursor?: number, // default: -1
}) =&gt; any;

} // namespace browser</code></pre><p>인용 포맷(<code>【cursor†L시작-L끝】</code>)과 직접 인용 10단어 제한은 모델 학습 단계에서 강제된 규약이다. 자체 검색 도구를 만들 때 같은 형식을 따르면 모델이 자연스럽게 인용을 생성한다.</p>
<h3 id="8-3-python-도구의-인터페이스">8-3. python 도구의 인터페이스</h3>
<p>python 도구는 별도 타입 선언 없이 자연어 설명만 둔다.</p>
<pre><code># Tools

## python

Use this tool to execute Python code in your chain of thought. The code will not be shown to the user. This tool should be used for internal reasoning, but not for code that is intended to be visible to the user (e.g. when creating plots, tables, or files).

When you send a message containing Python code to python, it will be executed in a stateful Jupyter notebook environment. python will respond with the output of the execution or time out after 120.0 seconds. The drive at &#39;/mnt/data&#39; can be used to save and persist user files. Internet access for this session is UNKNOWN. Depends on the cluster.</code></pre><p>stateful Jupyter 환경, 120초 타임아웃, <code>/mnt/data</code> 영속 디렉터리 같은 약속이 모델 안에 박혀 있다. 자체 코드 실행기를 만든다면 이 사양을 흉내 내는 것이 호환성에 유리하다.</p>
<h3 id="8-4-채널-차이--analysis-vs-commentary">8-4. 채널 차이 — analysis vs commentary</h3>
<p>내장 도구와 사용자 정의 함수의 가장 큰 차이는 호출 채널이다.</p>
<table>
<thead>
<tr>
<th>도구 종류</th>
<th>정의 위치</th>
<th>호출 채널</th>
<th>수신자 형식</th>
</tr>
</thead>
<tbody><tr>
<td>사용자 정의 함수</td>
<td>developer 메시지 <code># Tools</code></td>
<td>commentary</td>
<td><code>to=functions.{name}</code></td>
</tr>
<tr>
<td>내장 browser</td>
<td>system 메시지 <code># Tools</code></td>
<td>analysis</td>
<td><code>to=browser.search</code> 등</td>
</tr>
<tr>
<td>내장 python</td>
<td>system 메시지 <code># Tools</code></td>
<td>analysis</td>
<td><code>to=python</code></td>
</tr>
</tbody></table>
<p>내장 도구가 analysis로 가는 이유는 모델 입장에서 이들을 &quot;추론을 위한 보조 수단&quot;으로 학습했기 때문이다. 단, 실무에서는 가끔 commentary로 출력되는 경우도 있으므로 두 채널 모두 처리할 수 있도록 파서를 만든다.</p>
<h2 id="9-구조화된-출력structured-output">9. 구조화된 출력(Structured Output)</h2>
<p>응답을 정해진 JSON 스키마에 맞추고 싶다면 developer 메시지 끝에 <code># Response Formats</code> 섹션을 추가한다.</p>
<pre><code># Response Formats

## shopping_list

{&quot;properties&quot;:{&quot;items&quot;:{&quot;type&quot;:&quot;array&quot;,&quot;description&quot;:&quot;entries on the shopping list&quot;,&quot;items&quot;:{&quot;type&quot;:&quot;string&quot;}}},&quot;type&quot;:&quot;object&quot;}</code></pre><p><code>## {format name}</code> 아래에 JSON Schema를 한 줄로 적는다. 이 이름은 OpenAI Responses API에서 스키마에 부여하는 <code>name</code>과 동일한 역할이다.</p>
<p>다만 <strong>이 프롬프트만으로는 스키마 준수가 보장되지 않는다.</strong> 모델의 출력 경향만 유도할 뿐, 토큰 단위 강제는 별도 grammar/sampling 제약이 필요하다. <code>outlines</code>, <code>lm-format-enforcer</code>, <code>xgrammar</code> 같은 라이브러리로 디코딩 단계에서 스키마를 강제해야 완전한 보장이 된다.</p>
<h2 id="10-직접-구현-vs-harmony-라이브러리">10. 직접 구현 vs Harmony 라이브러리</h2>
<p>직접 토큰을 조립하는 대신 <a href="https://pypi.org/project/openai-harmony/">openai_harmony</a> 공식 라이브러리를 쓰는 편이 일반적으로 안전하다.</p>
<h3 id="10-1-openai_harmony-라이브러리로-메시지-만들기">10-1. openai_harmony 라이브러리로 메시지 만들기</h3>
<p>다음은 시스템·개발자·사용자·CoT·함수 호출·도구 결과까지 한 번에 구성하는 예제다.</p>
<pre><code class="language-python">from openai_harmony import (
    Author,
    Conversation,
    DeveloperContent,
    HarmonyEncodingName,
    Message,
    Role,
    SystemContent,
    ToolDescription,
    load_harmony_encoding,
    ReasoningEffort,
)

# Harmony 인코딩 로드 (gpt-oss 전용)
encoding = load_harmony_encoding(HarmonyEncodingName.HARMONY_GPT_OSS)

# system 메시지: 추론 강도와 현재 날짜 설정
system_message = (
    SystemContent.new()
    .with_reasoning_effort(ReasoningEffort.HIGH)
    .with_conversation_start_date(&quot;2025-06-28&quot;)
)

# developer 메시지: 지시문과 함수 도구 정의
developer_message = (
    DeveloperContent.new()
    .with_instructions(&quot;Always respond in riddles&quot;)
    .with_function_tools([
        ToolDescription.new(
            &quot;get_current_weather&quot;,
            &quot;Gets the current weather in the provided location.&quot;,
            parameters={
                &quot;type&quot;: &quot;object&quot;,
                &quot;properties&quot;: {
                    &quot;location&quot;: {&quot;type&quot;: &quot;string&quot;, &quot;description&quot;: &quot;The city and state, e.g. San Francisco, CA&quot;},
                    &quot;format&quot;: {&quot;type&quot;: &quot;string&quot;, &quot;enum&quot;: [&quot;celsius&quot;, &quot;fahrenheit&quot;], &quot;default&quot;: &quot;celsius&quot;},
                },
                &quot;required&quot;: [&quot;location&quot;],
            },
        ),
    ])
)

convo = Conversation.from_messages([
    Message.from_role_and_content(Role.SYSTEM, system_message),
    Message.from_role_and_content(Role.DEVELOPER, developer_message),
    Message.from_role_and_content(Role.USER, &quot;What is the weather in Tokyo?&quot;),
    # 이전 턴의 CoT (analysis 채널)
    Message.from_role_and_content(
        Role.ASSISTANT,
        &#39;User asks: &quot;What is the weather in Tokyo?&quot; We need to use get_current_weather tool.&#39;,
    ).with_channel(&quot;analysis&quot;),
    # 함수 호출 메시지 (commentary 채널, recipient 지정)
    Message.from_role_and_content(Role.ASSISTANT, &#39;{&quot;location&quot;: &quot;Tokyo&quot;}&#39;)
        .with_channel(&quot;commentary&quot;)
        .with_recipient(&quot;functions.get_current_weather&quot;)
        .with_content_type(&quot;&lt;|constrain|&gt; json&quot;),
    # 도구 실행 결과 (tool 역할에 도구 이름 지정)
    Message.from_author_and_content(
        Author.new(Role.TOOL, &quot;functions.get_current_weather&quot;),
        &#39;{ &quot;temperature&quot;: 20, &quot;sunny&quot;: true }&#39;,
    ).with_channel(&quot;commentary&quot;),
])

# 다음 응답 생성을 위한 토큰 시퀀스 생성
tokens = encoding.render_conversation_for_completion(convo, Role.ASSISTANT)

# 모델이 토큰을 반환한 뒤, 정지 토큰을 제외하고 파싱
parsed_response = encoding.parse_messages_from_completion_tokens(new_tokens, Role.ASSISTANT)</code></pre>
<p>라이브러리가 채널·수신자·<code>&lt;|constrain|&gt;</code> 토큰을 모두 알아서 직렬화한다. 직접 문자열을 만들 때 흔히 빠지는 공백·줄바꿈 실수를 피할 수 있다.</p>
<h3 id="10-2-streamableparser로-토큰-스트리밍-파싱하기">10-2. StreamableParser로 토큰 스트리밍 파싱하기</h3>
<p>스트리밍 응답을 받을 때는 <code>StreamableParser</code>로 토큰을 하나씩 흘려넣으면서 현재 채널·역할·콘텐츠를 추적할 수 있다.</p>
<pre><code class="language-python">from openai_harmony import (
    HarmonyEncodingName,
    Role,
    StreamableParser,
    load_harmony_encoding,
)

encoding = load_harmony_encoding(HarmonyEncodingName.HARMONY_GPT_OSS)
stream = StreamableParser(encoding, role=Role.ASSISTANT)

# 모델이 흘려보내는 토큰을 한 개씩 process()에 전달
for token in incoming_tokens:
    stream.process(token)

    # 현재 어느 채널의 무슨 내용을 받고 있는지 실시간 확인 가능
    if stream.current_channel == &quot;final&quot;:
        # 사용자에게 흘려보낼 텍스트
        print(stream.last_content_delta, end=&quot;&quot;, flush=True)</code></pre>
<p><code>current_channel</code>이 <code>final</code>일 때만 사용자 화면에 출력하고, <code>analysis</code>일 때는 로그로만 남기는 식으로 안전하게 분기할 수 있다. 유니코드 문자가 토큰 경계에서 잘리는 문제도 파서가 처리해 준다.</p>
<h3 id="10-3-어떤-경우에-직접-렌더러를-만들어야-하나">10-3. 어떤 경우에 직접 렌더러를 만들어야 하나</h3>
<table>
<thead>
<tr>
<th>상황</th>
<th>권장 방식</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td>Python/Rust 환경 + 일반적인 추론</td>
<td>openai_harmony 라이브러리</td>
<td>검증된 직렬화, 유지보수 부담 ↓</td>
</tr>
<tr>
<td>다른 언어(Go, Java 등)에서 직접 호출</td>
<td>직접 렌더러 구현</td>
<td>공식 라이브러리 미지원</td>
</tr>
<tr>
<td>임베디드/극저지연 환경</td>
<td>직접 렌더러 구현</td>
<td>라이브러리 의존성 제거</td>
</tr>
<tr>
<td>학습 데이터 정제 / 평가 도구</td>
<td>둘 다 가능</td>
<td>규모와 통제 수준에 따라 선택</td>
</tr>
</tbody></table>
<p>직접 만든다면 4장의 특수 토큰 표와 본 문서 곳곳의 메시지 예시를 골든 샘플로 두고, 라이브러리 출력과 바이트 단위로 비교하며 검증하는 것이 안전하다.</p>
<h2 id="11-참고-자료">11. 참고 자료</h2>
<ul>
<li><a href="https://cookbook.openai.com/articles/openai-harmony">OpenAI Harmony Response Format 공식 문서</a> — 본 글의 원문</li>
<li><a href="https://pypi.org/project/openai-harmony/">openai-harmony PyPI 패키지</a> — Python 렌더러</li>
<li><a href="https://crates.io/crates/openai-harmony">openai-harmony crates.io 패키지</a> — Rust 렌더러</li>
<li><a href="https://github.com/openai/harmony">openai/harmony GitHub 저장소</a> — 소스 코드와 추가 예제</li>
<li><a href="https://openai.com/index/gpt-oss-model-card/">gpt-oss 모델 카드</a> — 안전성 기준과 학습 정보</li>
<li><a href="https://github.com/openai/tiktoken">tiktoken o200k_harmony 인코딩</a> — 특수 토큰 ID 확인용</li>
</ul>
<hr>
<blockquote>
<p>Harmony 포맷의 핵심은 역할(role)과 채널(channel)을 분리해 추론 과정과 최종 답변을 명확히 구분하는 데 있다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[지식 그래프 기반 RAG]]></title>
            <link>https://velog.io/@tasker_dev/%EC%A7%80%EC%8B%9D-%EA%B7%B8%EB%9E%98%ED%94%84-%EA%B8%B0%EB%B0%98-RAG</link>
            <guid>https://velog.io/@tasker_dev/%EC%A7%80%EC%8B%9D-%EA%B7%B8%EB%9E%98%ED%94%84-%EA%B8%B0%EB%B0%98-RAG</guid>
            <pubDate>Tue, 28 Apr 2026 06:47:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>vector RAG는 의미가 비슷한 chunk를 모아오고, graph RAG는 관계를 따라 추론한다. 두 retrieval은 서로 다른 질문에 답한다.</p>
</blockquote>
<h2 id="1-ai-agent와-llm의-한계">1. AI Agent와 LLM의 한계</h2>
<p>LLM 기반 챗봇은 단순 질의응답을 넘어 <strong>AI agent</strong> 형태로 진화하고 있다. AI agent는 환경과 상호작용하면서 자율적으로 판단·행동하는 시스템으로 정의된다. 사전에 정해진 명령만 따르는 전통적 프로그램과 달리, agent는 사용 가능한 tool을 스스로 선택하고 결과를 평가해 다음 행동을 결정한다.</p>
<p>이전 편들에서 다룬 내용 — 비정형 문서로부터 entity·relationship을 추출해 KG로 모델링하는 과정 — 은 결국 이 agent가 활용할 외부 지식 소스를 만드는 작업이다. 본 편은 그렇게 구축된 KG를 retrieval 백엔드로 사용하는 RAG 구조를 다룬다.</p>
<p>production 환경에서 LLM 단독 사용이 어려운 이유는 다음과 같이 정리된다.</p>
<table>
<thead>
<tr>
<th>문제</th>
<th>정의</th>
<th>영향</th>
</tr>
</thead>
<tbody><tr>
<td>Hallucination</td>
<td>학습되지 않은 주제에 대해 그럴듯한 거짓을 생성</td>
<td>신뢰도 저하</td>
</tr>
<tr>
<td>Freshness (knowledge cutoff)</td>
<td>재학습 주기가 길어 최신 정보 반영 불가</td>
<td>정보 정확성 저하</td>
</tr>
<tr>
<td>Transparency</td>
<td>답변의 근거·추론 과정을 추적 불가</td>
<td>엔터프라이즈 도입 장벽</td>
</tr>
<tr>
<td>Data privacy</td>
<td>민감한 사내 데이터 학습 시 유출 위험</td>
<td>컴플라이언스 위반</td>
</tr>
<tr>
<td>Cost</td>
<td>대규모 모델의 학습·운영 비용</td>
<td>접근성 제약</td>
</tr>
<tr>
<td>Bias</td>
<td>학습 데이터의 편향이 출력에 재생산</td>
<td>윤리적 리스크</td>
</tr>
</tbody></table>
<p>LLM은 본질적으로 다음 token의 확률을 예측하는 모델이다. 학습 분포를 벗어난 질문에도 모델은 &quot;가장 그럴듯한 출력&quot;을 생성하므로, 응답이 틀려도 표면적으로는 일관되게 보인다. RAG는 이 구조적 한계를 외부 context 주입으로 보완하기 위한 접근이다.</p>
<h2 id="2-vector-rag의-동작과-한계">2. Vector RAG의 동작과 한계</h2>
<p>RAG는 사전학습된 LLM의 언어 이해력에 외부 데이터로부터 retrieval한 context를 결합하는 grounding 기법이다. 초기 RAG 구현은 거의 전적으로 텍스트 문서를 chunk 단위로 쪼개고 embedding으로 변환해 vector database에 색인하는 방식이었다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/42ba73be-c270-4e1f-b376-80db403706ca/image.png" alt=""></p>
<p>질문이 들어오면 동일한 embedding 모델로 질문을 vector로 변환하고, vector database에서 의미적으로 가장 가까운 chunk들을 retrieve한다. retrieved chunk들은 prompt context로 주입되어 LLM이 최종 답변을 생성한다. Neo4j는 graph 구조를 가진 데이터베이스이지만 <code>Neo4jVector</code> 같은 인터페이스를 통해 vector index도 제공한다.</p>
<pre><code class="language-python"># 문서 chunk를 embedding으로 변환하고 vector index 생성
vector_index = Neo4jVector.from_existing_graph(
    embedding=OpenAIEmbeddings(),
    url=NEO4J_URL,
    username=NEO4J_USER,
    password=NEO4J_PWD,
    database=NEO4J_DB,
)</code></pre>
<p>이 구조는 단순하고 강력하지만 다음과 같은 한계를 가진다.</p>
<ul>
<li><strong>Context fragmentation으로 인한 추론 제약</strong>: chunk 단위로 독립 retrieve되므로 여러 문서·entity를 가로지르는 multi-hop 관계 추론이 어렵다. 정답에 필요한 정보가 두 chunk에 걸쳐 있고 그중 하나만 retrieve되면 답변은 부정확해진다.</li>
<li><strong>Scalability</strong>: corpus 규모가 커질수록 정확한 nearest neighbor search 비용이 급증해 approximate search로 후퇴할 수밖에 없다.</li>
<li><strong>Embedding의 표현 한계</strong>: 문서 전체 의미를 단일 dense vector로 압축하는 과정에서 fine-grained semantic·도메인 특화 뉘앙스가 손실된다. embedding 모델 학습 데이터에서 sparse하게 등장한 용어는 더 부정확하게 표현된다.</li>
<li><strong>Retrieval noise</strong>: vector similarity가 높지만 실제로는 무관한 문서가 섞여 LLM의 distraction을 유발한다. 긴 context에서는 noise 증가가 출력 품질을 악화시킨다.</li>
<li><strong>Retrieval miss</strong>: &quot;이 데이터셋의 핵심 연구 주제는?&quot; 같은 집계형 질문은 의미 유사도와 정답 적합도가 일치하지 않아 vector search 자체로는 답할 수 없다.</li>
</ul>
<p>요약하면 vector RAG는 &quot;의미적으로 비슷한 텍스트&quot;를 잘 찾지만, &quot;관계를 따라간 결과로서의 사실&quot;을 답하는 데는 약하다.</p>
<h2 id="3-graph-rag-kg를-retrieval-백엔드로">3. Graph RAG: KG를 retrieval 백엔드로</h2>
<p>위 한계의 해법으로 등장한 것이 <strong>Graph RAG</strong>이다. KG는 단순 텍스트가 아닌 entity·relationship과 메타데이터, 구조화된 데이터(table, ontology 등)를 한 곳에 통합한 central knowledge repository로 작동한다. 도메인 전문가가 직접 검증·수정할 수 있어 transparency가 높고, 사실의 추가·갱신이 쉬워 freshness 문제도 완화된다.</p>
<p>Graph RAG agent는 KG를 활용하는 방식에 따라 여러 retriever tool을 가진다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/add3acac-2289-474d-9892-5900027b1910/image.png" alt=""></p>
<p>각 tool의 역할은 다음과 같이 구분된다.</p>
<table>
<thead>
<tr>
<th>Tool 종류</th>
<th>입력</th>
<th>처리</th>
<th>적합한 질문 유형</th>
</tr>
</thead>
<tbody><tr>
<td>Semantic retriever</td>
<td>자연어 질문</td>
<td>embedding similarity</td>
<td>&quot;X와 관련된 문서&quot;</td>
</tr>
<tr>
<td>KG retriever</td>
<td>자연어 질문 + KG schema</td>
<td>Cypher query 자동 생성, subgraph 반환</td>
<td>&quot;X와 Y의 관계&quot;, multi-hop</td>
</tr>
<tr>
<td>KG-enhanced document retriever</td>
<td>entity·relationship</td>
<td>KG로 후보 좁힌 후 문서 retrieve</td>
<td>&quot;X를 언급한 문서만&quot;</td>
</tr>
<tr>
<td>Combined retrieval</td>
<td>복합 질문</td>
<td>KG로 entity 식별 → 문서 검색</td>
<td>다중 소스 결합 질문</td>
</tr>
</tbody></table>
<p><strong>KG retriever</strong>는 사용자 질문과 KG schema를 LLM에 제공해 Cypher query를 생성하게 한다. 또는 질문에 등장한 entity들을 받아 그 사이의 shortest path subgraph를 반환하고, 어떤 부분을 답변에 사용할지는 LLM이 결정하게 한다.</p>
<p><strong>KG-enhanced document retriever</strong>는 KG가 text-paired graph일 때 가능한 방식이다. node·relationship이 원본 문서까지 추적 가능하므로, &quot;사용자가 언급한 모든 entity를 포함하는 문서만&quot; 같은 정밀 필터링이 가능하다. vector search의 retrieval miss를 직접 해소한다.</p>
<p><strong>Combined retrieval</strong>은 질문이 여러 소스에 걸칠 때 사용된다. 예를 들어 &quot;범죄조직 X의 두목의 올해 거래 내역은?&quot;이라는 질문은 법 집행 기관 KG에서 두목의 실명을 추출한 뒤, 그 이름으로 금융 문서 DB를 검색하는 두 단계로 분해된다.</p>
<h2 id="4-text-attributed-graph와-text-paired-graph">4. Text-attributed graph와 Text-paired graph</h2>
<p>Graph RAG의 retrieval 정밀도는 KG 설계에 직접적으로 좌우된다. 가장 유용한 설계는 두 종류의 graph 구조를 결합하는 형태이다.</p>
<ul>
<li><strong>Text-attributed graph</strong>: node와 relationship이 텍스트 속성을 가진다. entity 자체에 description, type 등이 붙어 LLM이 직접 활용할 수 있다.</li>
<li><strong>Text-paired graph</strong>: node와 relationship이 출처 문서로 추적 가능하다. KG로 좁힌 결과를 문서 retrieval로 연결할 수 있다.</li>
</ul>
<p>이 둘이 결합되면 정형화된 지식과 원문 문서를 함께 활용할 수 있어 metadata 기반 community 식별, 최신 버전 문서 우선 retrieval 등 다양한 전략이 가능해진다. 답변 예시로 &quot;Harvard와 Johns Hopkins의 공통 연구 주제는?&quot;이라는 질문에 KG의 university-topic 관계를 traversal 해 &quot;astronomy, climatology&quot;라는 정확한 사실을 반환할 수 있다.</p>
<h2 id="5-한계와-트레이드오프">5. 한계와 트레이드오프</h2>
<p>Graph RAG가 vector RAG의 약점을 상쇄한다고 해서 만능은 아니다. 본 챕터의 개념에서 도출되는 한계는 다음과 같이 정리된다.</p>
<ul>
<li><strong>KG 구축 비용이 선행 부담으로 전이된다</strong>: vector RAG는 chunking·embedding으로 인덱스가 빠르게 구축되지만, KG는 entity·relationship 추출과 ontology 설계에 사람의 손이 필요하다. retrieval 단계의 정확도를 얻는 대가로 ETL 비용이 앞단에 집중된다.</li>
<li><strong>Cypher query 생성 자체가 또 다른 hallucination 지점</strong>: KG retriever는 LLM이 schema를 보고 Cypher를 생성하게 한다. schema가 복잡하거나 질문이 모호하면 잘못된 query가 생성되어 빈 결과·오답으로 이어진다. retrieval의 정확성을 확보하려면 schema 설계 자체가 자기 설명적이어야 한다.</li>
<li><strong>Entity coverage의 함정</strong>: KG에 존재하지 않는 entity·관계는 graph traversal로는 절대 도달할 수 없다. text-paired graph가 없는 환경에서는 &quot;KG에 없는 사실을 묻는 질문&quot;이 silent failure를 일으킨다. 이 때문에 vector retriever와 fallback 구조가 여전히 필요하다.</li>
<li><strong>확률적 모델의 한계는 잔존</strong>: RAG는 hallucination 확률을 낮출 뿐 제거하지 못한다. context가 정확해도 LLM은 여전히 가장 그럴듯한 token을 생성하는 모델이며, 시스템은 사람을 대체하기보다 augment하는 방향으로 설계되어야 한다. feedback·검증 루프 없이는 production 도입이 어렵다.</li>
</ul>
<h2 id="6-실무-적용-시-고려사항">6. 실무 적용 시 고려사항</h2>
<p>Graph RAG를 실제 시스템에 도입할 때 결정적인 설계 포인트는 <strong>어떤 retriever를 어떤 질문에 어떻게 라우팅할 것인가</strong>이다. 모든 질문을 KG retriever로 보내면 단순한 의미 검색에서 오히려 비효율적이고, 모든 질문을 vector retriever로 보내면 multi-hop 추론이 끊긴다. agent의 tool 선택 prompt와 fallback 정책 설계가 시스템 품질을 좌우한다.</p>
<p>또한 KG는 살아있는 자산이다. 도메인 전문가가 검증·수정 가능한 인터페이스를 제공해 출력 품질을 직접 개선할 수 있게 만들면 transparency와 user confidence가 함께 올라간다. 반대로 한 번 구축하고 방치된 KG는 stale knowledge로 변해 freshness 문제를 다시 끌어들인다. KG의 운영 거버넌스 — 누가, 어떤 주기로, 어떤 검증을 거쳐 갱신하는가 — 가 retrieval 알고리즘만큼 중요하다.</p>
<h2 id="7-정리">7. 정리</h2>
<table>
<thead>
<tr>
<th>측면</th>
<th>Vector RAG</th>
<th>Graph RAG</th>
</tr>
</thead>
<tbody><tr>
<td>표현 단위</td>
<td>chunk + embedding</td>
<td>entity, relationship, subgraph</td>
</tr>
<tr>
<td>강점</td>
<td>의미 유사도, 빠른 구축</td>
<td>multi-hop 추론, transparency</td>
</tr>
<tr>
<td>약점</td>
<td>추론 제한, retrieval noise/miss</td>
<td>구축 비용, schema 의존성</td>
</tr>
<tr>
<td>적합 질문</td>
<td>&quot;비슷한 내용 찾기&quot;</td>
<td>&quot;관계 따라가기&quot;, 집계, multi-source</td>
</tr>
<tr>
<td>transparency</td>
<td>낮음</td>
<td>높음 (graph 추적 가능)</td>
</tr>
<tr>
<td>갱신 비용</td>
<td>재임베딩</td>
<td>node/edge 단위 수정</td>
</tr>
</tbody></table>
<p>두 방식은 대체재가 아니라 보완재이다. production 시스템은 보통 두 retriever를 함께 두고 질문 유형에 따라 라우팅하거나 결과를 병합한다.</p>
<blockquote>
<p>결론: Graph RAG는 vector RAG의 &quot;비슷한 chunk 모으기&quot;가 놓치는 관계·집계·multi-hop 질문을 풀기 위한 구조이며, 두 retrieval은 경쟁이 아니라 역할 분담 관계로 설계되어야 한다.</p>
</blockquote>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li>Knowledge Graphs and LLMs in Action. Manning Publications, 2025. Chapter 13: Knowledge graph-powered retrieval-augmented generation.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[지능형 시스템과 하이브리드 접근]]></title>
            <link>https://velog.io/@tasker_dev/%EC%A7%80%EB%8A%A5%ED%98%95-%EC%8B%9C%EC%8A%A4%ED%85%9C%EA%B3%BC-%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C-%EC%A0%91%EA%B7%BC-xwjue133</link>
            <guid>https://velog.io/@tasker_dev/%EC%A7%80%EB%8A%A5%ED%98%95-%EC%8B%9C%EC%8A%A4%ED%85%9C%EA%B3%BC-%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C-%EC%A0%91%EA%B7%BC-xwjue133</guid>
            <pubDate>Mon, 27 Apr 2026 07:10:05 GMT</pubDate>
            <description><![CDATA[<h2 id="1-지능형-시스템의-정의와-분해">1. 지능형 시스템의 정의와 분해</h2>
<p>지능형 시스템(intelligent system)은 사용자를 AI/ML과 연결해 의미 있는 목적을 달성하는 시스템으로 정의된다. 핵심 속성은 사용자와의 상호작용을 관찰하며 시간에 따라 지능이 진화한다는 점이다. 외부에서 보면 복잡한 작업을 수행하는 black box이지만, 이를 이해하기 위한 첫 단계는 기능 단위로 분해하는 것이다.</p>
<p>지능형 에이전트는 두 개의 핵심 컴포넌트로 분해된다. <strong>knowledge representation</strong>은 AI 시스템이 도메인 정보를 구조화·인코딩·전달하기 위해 사용하는 &quot;언어&quot;이며, AI가 추론하고 예측하기 위한 전제이자 그 결과를 해석하는 수단이다. 어떤 표현을 선택하느냐가 시스템의 능력과 한계를 결정한다. <strong>reasoning</strong>은 정보를 분석하고 규칙을 적용해 결론을 도출하는 인지 과정으로, deductive·inductive 등 여러 형태가 있다. KG와 LLM을 결합한 시스템은 이 reasoning 능력을 확장하는 방향으로 발전하고 있다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/5d95a004-965c-49c3-ab76-5e8dcb80b8e9/image.png" alt=""></p>
<h2 id="2-autonomous-system-vs-advisor-system">2. Autonomous System vs Advisor System</h2>
<p>지능형 시스템은 사용자를 <strong>대신해 행동하는가</strong>, 아니면 <strong>지원하는가</strong>에 따라 두 범주로 나뉜다. intelligent autonomous system은 사용자를 대체해 의사결정과 실행을 수행하고, intelligent advisory system(IAS)은 정보와 추천을 제공하되 실행 권한은 사용자에게 남긴다.</p>
<table>
<thead>
<tr>
<th>비교 축</th>
<th>Autonomous System</th>
<th>Advisory System (IAS)</th>
</tr>
</thead>
<tbody><tr>
<td>의사결정 주체</td>
<td>시스템</td>
<td>사용자</td>
</tr>
<tr>
<td>자동화 수준</td>
<td>full automation, 인간 입력 최소</td>
<td>decision support, 실행은 사용자</td>
</tr>
<tr>
<td>핵심 요건</td>
<td>real-time decision-making, adaptability</td>
<td>context awareness, user interaction</td>
</tr>
<tr>
<td>대표 예시</td>
<td>자율주행, 자동 트레이딩</td>
<td>추천 시스템, 진단 보조</td>
</tr>
<tr>
<td>실패 비용</td>
<td>높음 (직접 행동)</td>
<td>낮음 (사용자가 필터)</td>
</tr>
</tbody></table>
<p>두 경우 모두 사용자를 대체하는 것이 아니라 사용자를 지원·보조한다는 점이 핵심이다. 책에서 채택하는 설계 방향은 autonomous advisor 쪽이다. 즉, 행동이 아닌 제안을 생성하되, 일반 지식이 아니라 도메인 전문가가 정제한 established knowledge base 위에서 동작하고, 제안 결과의 피드백으로 지식 베이스를 확장한다.</p>
<h2 id="3-지능형-시스템의-네-가지-설계-축">3. 지능형 시스템의 네 가지 설계 축</h2>
<p>설계와 구현을 동시에 견인하는 네 가지 특성이 존재한다. <strong>meaningful objective</strong>는 시스템이 존재하는 구체적이고 달성 가능한 목적이며, 전체 개발 과정을 끌고 가는 축이다. <strong>intelligent experience</strong>는 예측 결과를 사용자에게 어떻게 제시하느냐의 문제로, 예측이 맞을 때 가치를 극대화하고 틀렸을 때 비용을 최소화하는 인터페이스를 요구한다. 동시에 implicit/explicit 피드백을 수집할 수 있어야 한다. <strong>knowledge creation and update</strong>는 변화하는 지식과 사용자 피드백을 지속적으로 반영하는 능력이며, LLM과 KG의 결합이 이 지점을 보강한다. <strong>orchestration</strong>은 다수의 알고리즘과 도구가 서로의 출력을 입력으로 삼아 협업하도록 조율하는 일을 가리킨다.</p>
<h2 id="4-knowledge-acquisition-kg와-llm의-비대칭">4. Knowledge Acquisition: KG와 LLM의 비대칭</h2>
<p>knowledge acquisition은 원천 데이터를 reasoning engine이 사용할 수 있는 형태로 변환하는 단계다. KG와 LLM은 이 단계에서 근본적으로 다른 경로를 밟는다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/02fd295e-302f-4229-99b5-b5807255a3ec/image.png" alt="">
<img src="https://velog.velcdn.com/images/tasker_dev/post/33c61e05-60b7-4396-8ccb-59ad03b388e3/image.png" alt=""></p>
<p>KG의 acquisition은 entity·relationship·property 단위로 데이터를 명시적 구조에 매핑하는 작업이며, 도메인 전문가가 데이터 소스 선정·스키마 설계·결과 검증 전 단계에 개입할 수 있다. LLM의 acquisition은 주로 unsupervised 학습으로 텍스트를 통계적 파라미터에 흡수하는 과정이며, 도메인 전문가의 개입은 데이터 선택과 결과 평가에 제한된다. 모델 내부 표현을 직접 보거나 수정할 수 없고, 커스텀 fine-tuning이 거의 유일한 보강 수단이다.</p>
<p>이 비대칭은 세 가지 설계 차이로 귀결된다.</p>
<table>
<thead>
<tr>
<th>차원</th>
<th>KG</th>
<th>LLM</th>
</tr>
</thead>
<tbody><tr>
<td>Access</td>
<td>explicit. 사람/기계 모두 해석 가능</td>
<td>implicit. 파라미터 공간, 불투명</td>
</tr>
<tr>
<td>Updates</td>
<td>node·relationship 추가/수정</td>
<td>재학습 또는 fine-tuning 필요</td>
</tr>
<tr>
<td>Capabilities</td>
<td>접근 패턴·스키마 설계 의존</td>
<td>자연어 이해·생성에 본질적으로 강함</td>
</tr>
<tr>
<td>전문가 검증</td>
<td>가능 (graph를 직접 검토)</td>
<td>결과 평가 단계로 제한</td>
</tr>
</tbody></table>
<h2 id="5-reasoning과-하이브리드-ias의-근거">5. Reasoning과 하이브리드 IAS의 근거</h2>
<p>reasoning은 acquisition된 knowledge 위에서 수행되며, 새로운 지식을 생성하거나 답·제안·자율 행동을 만들어낸다. KG는 정밀한 rule-based 추론과 explicit 표현이 필요한 작업에 강하고, LLM은 패턴 인식·맥락 이해·모호하거나 불완전한 정보 처리에 강하다. 두 접근 모두 인간 수준의 common-sense reasoning은 본질적으로 갖추지 못하며, 직관적 비약이나 암묵적 맥락 파악에서 한계를 드러낸다. 이 한계가 역설적으로 hybrid IAS의 설계 근거가 된다.</p>
<p>ML이 KG에 기여하는 두 가지 경로가 있다. 첫째, 관련 ontology와 relationship을 학습·구축해 knowledge base의 커버리지를 확장한다. 둘째, 불확실성 하의 inference를 제공해 데이터가 불완전한 경우에도 예측을 가능하게 한다. 반대 방향에서, KG는 generative AI의 hallucination을 완화하고 최신 데이터를 공급하는 grounding 장치로 기능한다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/70beeba5-12ea-4780-91d0-b85b9dfd3663/image.png" alt=""></p>
<h2 id="6-kg-구축-방식-top-down-vs-bottom-up">6. KG 구축 방식: Top-down vs Bottom-up</h2>
<p>KG를 어떤 순서로 구축하느냐에 따라 프로젝트 성공률이 달라진다. <strong>bottom-up</strong>은 가용한 모든 데이터를 먼저 적재하고 그래프로 변환한 뒤, 어떤 task에 활용할지 사후에 고민하는 방식이다. 데이터가 이미 다 들어가 있어 스키마 변경이 어렵고, 결과적으로 functional task와 무관하게 비대해진 그래프가 나오기 쉽다. <strong>top-down</strong>은 비즈니스 목표와 functional task에서 출발해 필요한 데이터·스키마·모델을 역으로 정의하는 CRISP-DM 유형의 사이클을 따른다.</p>
<table>
<thead>
<tr>
<th>비교 축</th>
<th>Bottom-up</th>
<th>Top-down (purpose-driven)</th>
</tr>
</thead>
<tbody><tr>
<td>출발점</td>
<td>가용 데이터</td>
<td>비즈니스 목표</td>
</tr>
<tr>
<td>스키마 진화</td>
<td>적재 후 고정 (변경 비용 큼)</td>
<td>iteration마다 재정의</td>
</tr>
<tr>
<td>실패 양상</td>
<td>&quot;데이터는 있는데 쓸 곳이 없음&quot;</td>
<td>scope 정의 자체가 어려움</td>
</tr>
<tr>
<td>추천 상황</td>
<td>탐색적 데이터 통합</td>
<td>production 시스템 구축</td>
</tr>
</tbody></table>
<p>책은 top-down 방식을 권고한다. 명확한 목적이 없는 데이터 통합은 프로젝트 실패의 주요 원인이기 때문이다.</p>
<h2 id="7-한계와-트레이드오프">7. 한계와 트레이드오프</h2>
<p>하이브리드 접근은 강력하지만 무비용은 아니다. 본문 개념에서 도출되는 트레이드오프는 다음과 같다.</p>
<p>첫째, <strong>운영 복잡도의 증가</strong>. KG와 LLM을 동시에 관리한다는 것은 두 개의 서로 다른 lifecycle을 다룬다는 의미다. KG는 스키마 변경·데이터 일관성·전문가 검증 주기를 가지며, LLM은 재학습·fine-tuning·평가 셋 관리 주기를 가진다. orchestration 레이어가 늘어날수록 디버깅 지점도 늘어난다.</p>
<p>둘째, <strong>업데이트 비대칭의 비용</strong>. KG는 노드/엣지 단위로 즉시 갱신할 수 있지만, LLM의 implicit 지식은 재학습 없이는 갱신되지 않는다. 따라서 &quot;최신성&quot;이 중요한 도메인일수록 사실 정보는 KG로 외부화하고 LLM은 언어 처리 계층으로 좁혀 사용하는 분업이 합리적이다. 이는 GraphRAG 계열 아키텍처가 일관되게 따르는 설계 원칙이기도 하다.</p>
<p>셋째, <strong>common-sense의 빈자리</strong>. 두 접근 모두 인간의 직관적 추론은 갖추지 못한다. 시스템이 &quot;당연한&quot; 맥락을 놓쳐 잘못된 제안을 내놓을 수 있으므로, autonomous 모드보다 advisory 모드가 안전하다는 결론은 단순한 보수적 선택이 아니라 기술적 한계에 근거한 설계 결정이다.</p>
<h2 id="8-실무-적용-시-고려사항">8. 실무 적용 시 고려사항</h2>
<p>production 환경에서 하이브리드 IAS를 도입할 때 가장 먼저 결정할 것은 <strong>autonomous와 advisory 사이의 위치</strong>다. 의료·금융·법률처럼 실패 비용이 큰 도메인일수록 advisory 쪽으로 무게중심을 이동시키고, intelligent experience 단계에서 신뢰도와 근거를 함께 제시하는 인터페이스를 설계해야 한다. 사용자의 implicit/explicit 피드백을 KG로 환류시키는 경로를 처음부터 확보하지 않으면, 시스템은 시간이 지나도 진화하지 않는다.</p>
<p>다음으로 <strong>KG의 scope를 task에 맞춰 좁게 시작</strong>해야 한다. bottom-up으로 시작한 프로젝트가 실패하는 흔한 이유는 그래프가 충분히 풍부해도 그것을 활용할 reasoning task가 정의되어 있지 않기 때문이다. top-down 방식으로 단일 functional task를 먼저 해결하고, 거기서 검증된 스키마를 옆으로 확장하는 incremental 전략이 안전하다.</p>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li>Knowledge Graphs and LLMs in Action, Manning Publications, 2024 — Chapter 2: Intelligent systems: A hybrid approach</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[벡터 RAG와 그래프 RAG: 의미 유사성에서 관계 추론으로]]></title>
            <link>https://velog.io/@tasker_dev/%EB%B2%A1%ED%84%B0-RAG%EC%99%80-%EA%B7%B8%EB%9E%98%ED%94%84-RAG-%EC%9D%98%EB%AF%B8-%EC%9C%A0%EC%82%AC%EC%84%B1%EC%97%90%EC%84%9C-%EA%B4%80%EA%B3%84-%EC%B6%94%EB%A1%A0%EC%9C%BC%EB%A1%9C</link>
            <guid>https://velog.io/@tasker_dev/%EB%B2%A1%ED%84%B0-RAG%EC%99%80-%EA%B7%B8%EB%9E%98%ED%94%84-RAG-%EC%9D%98%EB%AF%B8-%EC%9C%A0%EC%82%AC%EC%84%B1%EC%97%90%EC%84%9C-%EA%B4%80%EA%B3%84-%EC%B6%94%EB%A1%A0%EC%9C%BC%EB%A1%9C</guid>
            <pubDate>Sun, 26 Apr 2026 06:58:52 GMT</pubDate>
            <description><![CDATA[<h2 id="1-두-패러다임의-출발점">1. 두 패러다임의 출발점</h2>
<p>RAG(Retrieval-Augmented Generation)는 외부 지식을 LLM에 주입해 hallucination을 줄이는 기법이다. 그러나 &quot;외부 지식을 어떻게 표현하고 검색하는가&quot;에 따라 두 갈래로 나뉜다.</p>
<ul>
<li><strong>Vector RAG</strong>: 텍스트를 chunk로 쪼개 embedding한 뒤 의미 유사성으로 검색</li>
<li><strong>Graph RAG</strong>: 텍스트에서 entity와 relationship을 추출해 knowledge graph로 구성한 뒤 관계망을 따라 검색</li>
</ul>
<p>질문의 성격이 검색 방식을 결정한다. &quot;비슷한 문서 찾기&quot;는 vector, &quot;관계 따라 추론하기&quot;는 graph다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>Vector RAG</th>
<th>Graph RAG</th>
</tr>
</thead>
<tbody><tr>
<td>검색 단위</td>
<td>chunk</td>
<td>entity, subgraph, path</td>
</tr>
<tr>
<td>유사도 척도</td>
<td>cosine similarity</td>
<td>traversal, pattern matching</td>
</tr>
<tr>
<td>인덱스</td>
<td>ANN (FAISS 등)</td>
<td>property graph, RDF</td>
</tr>
<tr>
<td>강점 영역</td>
<td>local retrieval</td>
<td>global sensemaking</td>
</tr>
<tr>
<td>표현 한계</td>
<td>관계가 암묵적</td>
<td>구조화 비용 높음</td>
</tr>
</tbody></table>
<h2 id="2-vector-rag-평면-표현의-강점과-한계">2. Vector RAG: 평면 표현의 강점과 한계</h2>
<h3 id="21-동작-구조">2.1 동작 구조</h3>
<p>표준 파이프라인은 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/f8b48feb-ac85-4032-ab54-708fb804ebd3/image.png" alt=""></p>
<p>ANN 인덱스 위에서 단일 단위인 chunk를 빠르게 돌려주는 단순한 구조다. 긴 문서 하나를 통째로 embedding하면 여러 주제가 섞여 정보성이 희석되므로, 작게 쪼개야 의미 포착이 살아난다.</p>
<h3 id="22-강점이-드러나는-영역">2.2 강점이 드러나는 영역</h3>
<p>Vector RAG가 잘 작동하는 자리는 명확하다.</p>
<ul>
<li>문서 QA: long-form 문서에서 답이 있는 문단 찾기</li>
<li>Technical support: Jira, Slack, 콜센터 로그 검색</li>
<li>사내 문서·FAQ: 근무 규정, 온보딩, 제품 매뉴얼</li>
<li>Code/doc search: 함수명·설명·주석을 넘나드는 검색</li>
</ul>
<h3 id="23-한계가-드러나는-지점">2.3 한계가 드러나는 지점</h3>
<p>Vector RAG는 본질적으로 평면 표현이다. chunk A와 B의 관계, entity 간 참여·소유·계층, 시간적 선후 관계는 embedding 안에 암묵적으로만 들어간다. 질문이 &quot;비슷한 문서 찾기&quot;에서 &quot;관계 따라 추론하기&quot;로 바뀌는 순간 이 한계가 표면화된다.</p>
<h2 id="3-graph-rag-개체와-관계의-저장소">3. Graph RAG: 개체와 관계의 저장소</h2>
<h3 id="31-핵심-발상">3.1 핵심 발상</h3>
<p>Graph RAG는 문서가 아니라 개체와 관계를 저장한다. unstructured 텍스트에서 entity와 relationship을 추출해 knowledge graph 또는 property graph로 구성하는 방식이다.</p>
<p>Microsoft GraphRAG의 사례에서, source document로부터 entity graph를 만든 뒤 인접 entity group에 대해 community summary를 미리 생성하고, query 시점에 partial response를 합성한다. &quot;데이터셋 전체의 주요 테마는 무엇인가&quot;와 같은 global question에서 naive RAG 대비 comprehensiveness와 diversity가 개선된다고 보고되었다.</p>
<h3 id="32-구축·검색-파이프라인">3.2 구축·검색 파이프라인</h3>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/17332e21-f291-44b5-b2d7-10de93154200/image.png" alt=""></p>
<p>검색 대상이 chunk 한 단위가 아니라 구조 전체라는 점이 vector RAG와의 결정적 차이다.</p>
<h2 id="4-네-가지-graph-패턴">4. 네 가지 Graph 패턴</h2>
<p>&quot;Graph RAG&quot;라는 이름 안에는 최소 네 가지 서로 다른 graph 구성 방식이 존재한다. 답해야 할 질문 유형에 따라 선택이 달라진다.</p>
<table>
<thead>
<tr>
<th>패턴</th>
<th>노드 구성</th>
<th>적합 시나리오</th>
</tr>
</thead>
<tbody><tr>
<td>Lexical Graph</td>
<td>Document → Chunk</td>
<td>가장 기본 형태. Basic Retriever</td>
</tr>
<tr>
<td>Hierarchical Lexical Graph</td>
<td>+ Chapter / Section / Subsection</td>
<td>문서 자체 구조 계승. bottom-up·top-down</td>
</tr>
<tr>
<td>Domain Graph</td>
<td>Entity ↔ Entity</td>
<td>실세계 비즈니스 지식 (Movie graph 등)</td>
</tr>
<tr>
<td>Lexical + Entities + Communities</td>
<td>Chunk · Entity · Community</td>
<td>Global/Local Retriever 기반</td>
</tr>
</tbody></table>
<h3 id="41-lexical-graph">4.1 Lexical Graph</h3>
<p>가장 단순한 형태다. Document 노드는 문서명·출처를 갖고, Chunk 노드는 human-readable text와 vector embedding을 가지며, 두 노드는 PART_OF 관계로 연결된다. 질문 embedding과 chunk embedding 간 vector similarity로 top-k를 찾는 Basic Retriever의 그래프 기반이 된다.</p>
<h3 id="42-hierarchical-lexical-graph">4.2 Hierarchical Lexical Graph</h3>
<p>문서 구조를 그대로 계승한다. HAS_CHAPTER, HAS_SECTION, HAS_SUBSECTION, PART_OF 등의 edge로 계층을 구성한다. 두 가지 retrieval 모드가 가능하다.</p>
<ul>
<li><strong>Bottom-up</strong>: leaf에서 similarity search 후 상위 chunk로 확장</li>
<li><strong>Top-down</strong>: 상위 노드에서 subtree 선택 후 좁혀가며 탐색</li>
</ul>
<h3 id="43-domain-graph">4.3 Domain Graph</h3>
<p>실세계 entity와 그 사이 관계를 담는 그래프다. Movie graph, Northwind schema가 대표적이다. 도메인마다 모양이 다르기 때문에 일반 blueprint를 제공하기 어렵지만, schema에 따르는 structured data라는 공통점을 갖는다. 자연어 질문이 deterministic한 구조적 조회로 풀릴 때 유용하며, Text2Cypher나 Pattern Matching의 기반이 된다.</p>
<h3 id="44-lexical--entities--communities">4.4 Lexical + Entities + Communities</h3>
<p>세 층의 그래프를 결합한 가장 풍부한 형태다.</p>
<ul>
<li>Chunk에서 entity와 relationship 추출</li>
<li>Domain Graph 위에서 Leiden 알고리즘으로 계층 커뮤니티 형성</li>
<li>각 커뮤니티에 대해 LLM이 community summary 생성</li>
</ul>
<p>Community 노드는 level, name, summary, weight 속성을 갖는다. &quot;데이터셋 전체 테마는 무엇인가&quot;와 같은 global 질문에서 특히 강하다.</p>
<h2 id="5-kg와-llm의-상호-보완">5. KG와 LLM의 상호 보완</h2>
<p>책에서는 두 기술의 결합을 단순한 조합이 아닌 패러다임 전환으로 본다. KG는 명시적·검증 가능·갱신 가능한 지식 표현을 제공하고, LLM은 자연어 이해·생성 능력을 제공한다.</p>
<p><img src="https://velog.velcdn.com/images/tasker_dev/post/6bce6fec-ad4e-4c13-9766-cec55fa38fe6/image.png" alt=""></p>
<p>LLM은 unstructured 텍스트에서 entity와 relationship을 추출해 KG 구축을 자동화하고, KG는 신뢰할 수 있는 도메인 지식으로 LLM 응답을 grounding한다. 구체적으로는 다음 세 문제를 해결한다.</p>
<ul>
<li><strong>Hallucination</strong>: KG의 verified knowledge가 사실 기반을 제공한다. text-to-cypher 변환은 자연어 질문을 정확한 graph query로 바꿔 신뢰 가능한 정보를 직접 추출한다.</li>
<li><strong>Stale information</strong>: LLM은 상시 재학습이 어렵지만 KG는 지속 갱신이 가능하다. GraphRAG는 community summary를 통해 최신 정보를 LLM에 주입한다.</li>
<li><strong>Explainability</strong>: KG는 추론 경로를 추적·검증 가능하게 만든다. LLM의 자연어 처리와 결합되어 결과가 사람이 읽을 수 있는 형태로 정리된다.</li>
</ul>
<h2 id="6-kg의-네-기둥">6. KG의 네 기둥</h2>
<p>KG를 다음과 같이 정의할 수 있다. Knowledge graph는 typed entity, 속성, 의미 있는 named relationship으로 구성된 지속적으로 진화하는 graph data structure이며, 특정 도메인을 위해 structured·unstructured 데이터를 통합한다. 이 정의는 네 가지 축으로 정리된다.</p>
<table>
<thead>
<tr>
<th>기둥</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>Evolution</td>
<td>새 정보를 단일 출처로 지속 통합. 전면 개편 없이 확장 가능</td>
</tr>
<tr>
<td>Semantics</td>
<td>typed entity와 relationship으로 의미를 명시. explainability의 기반</td>
</tr>
<tr>
<td>Integration</td>
<td>structured/unstructured, 다중 소스의 중앙 참조점</td>
</tr>
<tr>
<td>Learning</td>
<td>centrality, network analysis, community detection으로 새 지식 추론</td>
</tr>
</tbody></table>
<h2 id="7-한계와-주의사항">7. 한계와 주의사항</h2>
<p>Graph RAG가 만능은 아니다. 다음 난제가 존재한다.</p>
<ul>
<li><strong>Graph construction의 품질·비용 trade-off</strong>: LLM 기반 추출은 recall이 높지만 hallucinated edge를 만든다. rule-based는 precision이 좋지만 recall이 낮다. ontology를 정교하게 설계할수록 비싸고, 느슨할수록 결과가 지저분해진다.</li>
<li><strong>튜닝 포인트의 차이</strong>: vector RAG가 chunk 크기·embedding 모델 중심이라면, graph RAG는 ontology, entity canonicalization, relation type 설계, source provenance, graph update 전략, confidence score, temporal validity 등 지식 모델링의 축을 다뤄야 한다.</li>
<li><strong>평가 지표의 부재</strong>: &quot;정답 맞췄나&quot;만 측정하면 graph RAG의 가치를 제대로 보지 못한다. global sensemaking, explainability, multi-hop reasoning을 평가할 별도 지표가 필요하다.</li>
<li><strong>KG 자체의 채택 장벽</strong>: 책은 KG가 널리 보급되지 못한 이유로 구축·유지비용, 다중 hop을 요구하는 복잡한 access pattern, 다수 노드·관계에 분산된 결과를 든다.</li>
</ul>
<blockquote>
<p>결론: Vector RAG는 의미 유사성에 강하고 Graph RAG는 관계 추론에 강하다. 둘은 선택의 문제가 아니라 조합의 문제이며, KG와 LLM의 결합은 hallucination·stale information·explainability라는 LLM의 구조적 한계를 보완하는 패러다임 전환이다.</p>
</blockquote>
<h2 id="참고-자료">참고 자료</h2>
<ul>
<li><em>Knowledge Graphs and LLMs in Action</em>, Chapter 1: <em>Knowledge graphs and LLMs: A killer combination</em></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[슬라이싱, `0`과 `len()`은 생략하는 게 파이썬답습니다]]></title>
            <link>https://velog.io/@tasker_dev/%EC%8A%AC%EB%9D%BC%EC%9D%B4%EC%8B%B1-0%EA%B3%BC-len%EC%9D%80-%EC%83%9D%EB%9E%B5%ED%95%98%EB%8A%94-%EA%B2%8C-%ED%8C%8C%EC%9D%B4%EC%8D%AC%EB%8B%B5%EC%8A%B5%EB%8B%88%EB%8B%A4</link>
            <guid>https://velog.io/@tasker_dev/%EC%8A%AC%EB%9D%BC%EC%9D%B4%EC%8B%B1-0%EA%B3%BC-len%EC%9D%80-%EC%83%9D%EB%9E%B5%ED%95%98%EB%8A%94-%EA%B2%8C-%ED%8C%8C%EC%9D%B4%EC%8D%AC%EB%8B%B5%EC%8A%B5%EB%8B%88%EB%8B%A4</guid>
            <pubDate>Sun, 19 Apr 2026 07:23:01 GMT</pubDate>
            <description><![CDATA[<h2 id="🤔-이게-왜-문제인가">🤔 이게 왜 문제인가</h2>
<p>파이썬의 슬라이싱(<code>list[start:end:step]</code>)은 강력합니다. 리스트, 문자열, 튜플, <code>bytes</code> 등 시퀀스 타입이면 다 통하죠. 그런데 다른 언어에서 배열을 다루던 습관이 남아 있으면 <strong>필요 없는 <code>0</code>이나 <code>len()</code>을 끝까지 꾸역꾸역 쓰는</strong> 코드가 나옵니다. 틀린 건 아니지만, <strong>파이썬다운 코드</strong>는 아니에요.</p>
<p>또 슬라이싱이 &quot;원본의 일부를 참조하는 건지, 복사본을 만드는 건지&quot; 헷갈려서 이상한 버그를 만드는 경우도 자주 봅니다. 리스트 슬라이싱은 <strong>새로운 리스트</strong>를 반환한다는 점, 이걸 꼭 기억해야 해요.</p>
<h2 id="💣-흔한-실수">💣 흔한 실수</h2>
<pre><code class="language-python">numbers = [10, 20, 30, 40, 50, 60, 70, 80]

# 처음부터 / 끝까지인데도 굳이 0과 len() 써주기
first_three = numbers[0:3]        # ❌ 0은 생략 가능
last_two = numbers[len(numbers)-2:len(numbers)]  # ❌ 장황함

# 음수 인덱스 활용 못 하고 복잡하게
last = numbers[len(numbers)-1]    # ❌

# 슬라이스가 &quot;뷰&quot;인 줄 알고 원본 수정된다고 착각
copy = numbers[:]
copy.append(999)
print(numbers)  # 원본은 그대로 (새 리스트이기 때문)</code></pre>
<p><code>numbers[0:3]</code>은 <code>numbers[:3]</code>과 완전히 같고, <code>numbers[len(numbers)-2:]</code>은 그냥 <code>numbers[-2:]</code>로 쓰면 됩니다. <strong><code>len()</code>을 슬라이싱 안에서 쓰는 순간 코드에 냄새가 난다</strong>고 봐도 돼요.</p>
<h2 id="✅-파이썬다운-방법">✅ 파이썬다운 방법</h2>
<pre><code class="language-python">numbers = [10, 20, 30, 40, 50, 60, 70, 80]

# 처음부터 → start 생략
first_three = numbers[:3]         # [10, 20, 30]

# 끝까지 → end 생략
from_fourth = numbers[3:]         # [40, 50, 60, 70, 80]

# 끝에서 몇 개 → 음수 인덱스
last_two = numbers[-2:]           # [70, 80]
except_last = numbers[:-1]        # 마지막 하나만 빼고

# 전체 복사 (얕은 복사)
copy = numbers[:]                 # 원본 그대로 새 리스트

# step 활용
every_other = numbers[::2]        # [10, 30, 50, 70]
reversed_nums = numbers[::-1]     # 뒤집기</code></pre>
<p>슬라이싱은 <strong>범위를 벗어나도 에러 없이 조용히 처리</strong>한다는 특징도 있어요. <code>numbers[:100]</code>은 <code>IndexError</code>가 아니라 그냥 전체 리스트를 돌려줍니다. 반면 <code>numbers[100]</code>처럼 단일 인덱스로 접근하면 에러가 나요. 이 차이 때문에 방어적 코드를 덜 쓸 수 있습니다.</p>
<p>그리고 <strong>슬라이스 대입(slice assignment)</strong>도 알아두면 좋습니다. <code>numbers[2:5] = [99]</code>처럼 쓰면 리스트 일부를 다른 길이의 시퀀스로 바꿀 수 있어요. <code>del numbers[2:5]</code>로 일부만 삭제할 수도 있고요.</p>
<pre><code class="language-python">nums = [1, 2, 3, 4, 5]
nums[1:3] = [20, 30, 40]          # 길이가 달라도 됨
print(nums)                        # [1, 20, 30, 40, 4, 5]</code></pre>
<p>슬라이싱이 <strong>새 리스트</strong>를 만든다는 건 꼭 기억하세요. 원본을 수정하지 않는 안전한 연산이라는 뜻입니다. 단, 원소가 객체(딕셔너리·리스트 같은 가변 객체)라면 <strong>얕은 복사</strong>라서 내부 객체는 공유한다는 점만 주의하면 돼요.</p>
<h2 id="📎-기억할-것">📎 기억할 것</h2>
<ul>
<li>처음이나 끝을 가리키는 <code>0</code>, <code>len(x)</code>는 생략하세요. <code>x[:3]</code>, <code>x[-2:]</code>가 파이썬다운 방식입니다.</li>
<li>끝에서부터 셀 땐 <strong>음수 인덱스</strong>(<code>-1</code>, <code>-2</code>)를 적극 활용하세요.</li>
<li>슬라이싱은 <strong>새로운 리스트</strong>를 반환합니다(얕은 복사). <code>x[:]</code>이 전체 복사 관용구예요.</li>
<li>범위를 벗어난 슬라이싱은 에러 없이 처리되지만, 단일 인덱스(<code>x[100]</code>)는 <code>IndexError</code>를 냅니다.</li>
<li><code>x[a:b] = [...]</code>, <code>del x[a:b]</code> 같은 슬라이스 대입·삭제도 강력한 기능입니다.</li>
</ul>
<h2 id="🛠-실무에서-어디-쓸까">🛠 실무에서 어디 쓸까</h2>
<p>데이터 처리에서 거의 매일 씁니다. <strong>대용량 CSV에서 헤더만 떼어내기</strong>(<code>header, *rows = lines</code> 언패킹과 조합), <strong>로그 파일의 최근 N줄만 보기</strong>(<code>lines[-100:]</code>), <strong>페이지네이션</strong>(<code>items[offset:offset+page_size]</code>)이 전형적인 예시예요. 특히 <strong>문자열도 시퀀스</strong>라서 슬라이싱이 그대로 통합니다. URL에서 쿼리스트링 떼어내기(<code>url[:url.index(&#39;?&#39;)]</code>), 파일 확장자 제거(<code>name[:-4]</code>) 같은 상황에서 유용하죠.</p>
<p>성능이 중요한 상황에서는 한 가지 주의점이 있어요. 매우 큰 리스트에서 슬라이싱은 <strong>새 리스트를 만들기 때문에 메모리와 시간을 씁니다</strong>. 이런 경우에는 <code>itertools.islice()</code>나 제너레이터를 고려하는 게 낫습니다. 평범한 상황에서는 신경 쓸 필요 없지만, GB 단위 데이터에서는 체감이 달라집니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[문자열 자동 연결, 편해 보여도 버그의 온상입니다]]></title>
            <link>https://velog.io/@tasker_dev/%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%9E%90%EB%8F%99-%EC%97%B0%EA%B2%B0-%ED%8E%B8%ED%95%B4-%EB%B3%B4%EC%97%AC%EB%8F%84-%EB%B2%84%EA%B7%B8%EC%9D%98-%EC%98%A8%EC%83%81%EC%9E%85%EB%8B%88%EB%8B%A4</link>
            <guid>https://velog.io/@tasker_dev/%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%9E%90%EB%8F%99-%EC%97%B0%EA%B2%B0-%ED%8E%B8%ED%95%B4-%EB%B3%B4%EC%97%AC%EB%8F%84-%EB%B2%84%EA%B7%B8%EC%9D%98-%EC%98%A8%EC%83%81%EC%9E%85%EB%8B%88%EB%8B%A4</guid>
            <pubDate>Sun, 19 Apr 2026 07:09:41 GMT</pubDate>
            <description><![CDATA[<h2 id="🤔-이게-왜-문제인가">🤔 이게 왜 문제인가</h2>
<p>파이썬에는 C에서 물려받은 이상한 기능이 하나 있습니다. <strong>문자열 리터럴 두 개가 나란히 붙어 있으면 자동으로 하나로 합쳐지는</strong> 것. <code>&quot;hello&quot; &quot;world&quot;</code>라고 쓰면 <code>+</code> 없이도 <code>&quot;helloworld&quot;</code>가 됩니다. 얼핏 보면 편리해 보이지만, 이게 <strong>리스트 안에서 쉼표 하나를 빼먹는 실수</strong>와 만나면 조용한 대참사가 벌어져요.</p>
<p>타입 에러도 없고, 문법 에러도 없고, 그냥 원소 개수가 슬쩍 줄어서 돌아갑니다. 찾기도 힘들어요.</p>
<h2 id="💣-흔한-실수">💣 흔한 실수</h2>
<pre><code class="language-python"># 리스트에 쉼표 하나 빼먹었을 뿐인데…
users = [
    &quot;alice&quot;,
    &quot;bob&quot;,
    &quot;charlie&quot;    # ← 쉼표 빠짐
    &quot;david&quot;,
    &quot;eve&quot;,
]

print(len(users))  # 4 (5가 아님!)
print(users)       # [&#39;alice&#39;, &#39;bob&#39;, &#39;charliedavid&#39;, &#39;eve&#39;] 😱</code></pre>
<p><code>&quot;charlie&quot;</code>와 <code>&quot;david&quot;</code> 사이에 쉼표가 없어서 <strong>암시적으로 연결</strong>됐고, 결국 <code>&quot;charliedavid&quot;</code>라는 이상한 원소가 태어났습니다. 리스트 길이는 5가 아니라 4. 이걸 DB에 넣거나 API에 보내면 <code>charliedavid</code>라는 유령 사용자가 생기는 거예요. 에러도 없이 조용히.</p>
<pre><code class="language-python"># 여러 줄 문자열을 암시적으로 이어 쓰기
message = (
    &quot;안녕하세요, &quot;
    &quot;오늘 날씨가 &quot;
    &quot;정말 좋네요.&quot;
)
# 동작은 함. 하지만 의도가 명확하지 않음 — 한 줄이어야 했나? 실수인가?</code></pre>
<p>이 예는 &quot;합쳐지길 원해서 그렇게 쓴 것&quot;이지만, 보는 사람은 <strong>의도인지 실수인지 구분하기 어렵습니다</strong>. 코드 리뷰할 때 매번 멈칫하게 만들죠.</p>
<h2 id="✅-파이썬다운-방법">✅ 파이썬다운 방법</h2>
<pre><code class="language-python"># 리스트는 끝 원소에도 쉼표를 찍는 습관(trailing comma)
users = [
    &quot;alice&quot;,
    &quot;bob&quot;,
    &quot;charlie&quot;,    # ← 쉼표! 줄 추가/삭제할 때도 diff가 깔끔
    &quot;david&quot;,
    &quot;eve&quot;,
]


# 긴 문자열을 잇고 싶으면 + 로 명시적으로
message = (
    &quot;안녕하세요, &quot;
    + &quot;오늘 날씨가 &quot;
    + &quot;정말 좋네요.&quot;
)


# 여러 조각을 조립한다면 join이 가장 파이썬답고 빠름
parts = [&quot;안녕하세요&quot;, &quot;오늘 날씨가&quot;, &quot;정말 좋네요&quot;]
message = &quot; &quot;.join(parts)</code></pre>
<p><strong>명시적 <code>+</code> 연산자</strong>를 쓰면 &quot;이건 의도적으로 합친 거야&quot;가 드러나고, 리스트에서는 <strong>마지막 원소 뒤에도 쉼표를 찍어두는 trailing comma 스타일</strong>이 쉼표 실수를 원천 차단합니다. 여기에 더해, 문자열 조각을 여러 개 합칠 일이 있으면 <code>+</code>보다 <code>&quot; &quot;.join()</code>이 훨씬 효율적이에요. <code>+</code>는 매번 새 문자열을 만들지만 <code>join</code>은 한 번에 처리하거든요.</p>
<p>린터(<code>ruff</code>, <code>pylint</code>)들도 이 암시적 연결 패턴을 감지하는 규칙을 제공합니다. <code>ruff</code>의 <code>ISC001</code>, <code>ISC002</code> 같은 규칙을 켜두면 실수가 생기자마자 에디터에 빨간 줄이 뜹니다.</p>
<h2 id="📎-기억할-것">📎 기억할 것</h2>
<ul>
<li><code>&quot;foo&quot; &quot;bar&quot;</code>처럼 문자열 리터럴이 나란히 있으면 파이썬이 자동으로 합쳐줍니다(암시적 연결).</li>
<li>리스트에서 쉼표 하나를 빼먹으면 두 원소가 조용히 합쳐져서 <strong>길이가 줄고 원소 내용도 망가지는</strong> 버그가 생깁니다.</li>
<li>의도적으로 합치려는 거라면 <code>+</code>를 명시적으로 쓰거나, 조각이 여럿이면 <code>&quot; &quot;.join(parts)</code>를 쓰세요.</li>
<li>리스트·딕셔너리의 <strong>마지막 원소 뒤에도 쉼표</strong>를 찍는 습관(trailing comma)이 이런 실수를 예방합니다.</li>
<li><code>ruff</code> 같은 린터가 이 패턴을 잡아주니 규칙을 켜두면 사람 실수를 기계가 막아줍니다.</li>
</ul>
<h2 id="🛠-실무에서-어디-쓸까">🛠 실무에서 어디 쓸까</h2>
<p>설정 파일이나 상수 목록에서 특히 조심해야 합니다. 허용된 도메인 리스트, 이메일 수신자 리스트, CSV 컬럼 헤더처럼 <strong>문자열이 나열된 리스트</strong>가 가장 위험해요. 여기서 쉼표 하나 빠지면 <code>&quot;admin@company.com&quot;</code>과 <code>&quot;user@company.com&quot;</code>이 <code>&quot;admin@company.comuser@company.com&quot;</code>이라는 존재하지 않는 주소로 합쳐져서, 메일 발송 코드가 조용히 실패합니다. 에러 로그에도 &quot;주소 형식 오류&quot;라고만 뜨고, 원인을 찾는 데 몇 시간을 쓰게 돼요.</p>
<p>팀 프로젝트에서는 <strong><code>ruff</code>에 <code>ISC</code> 규칙을 활성화하고 trailing comma를 강제</strong>하는 포매터(<code>black</code>, <code>ruff format</code>) 설정을 <code>pyproject.toml</code>에 박아두세요. 사람이 주의하는 것보다 도구가 자동으로 잡아주는 게 훨씬 안전합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[디버깅의 핵심 repr(obj)를 활용하세요]]></title>
            <link>https://velog.io/@tasker_dev/%EB%94%94%EB%B2%84%EA%B9%85%EC%9D%98-%ED%95%B5%EC%8B%AC-reprobj%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%84%B8%EC%9A%94</link>
            <guid>https://velog.io/@tasker_dev/%EB%94%94%EB%B2%84%EA%B9%85%EC%9D%98-%ED%95%B5%EC%8B%AC-reprobj%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%98%EC%84%B8%EC%9A%94</guid>
            <pubDate>Sun, 19 Apr 2026 07:01:04 GMT</pubDate>
            <description><![CDATA[<h2 id="🤔-이게-왜-문제인가">🤔 이게 왜 문제인가</h2>
<p>디버깅하려고 <code>print(some_value)</code>를 찍었는데, 숫자 <code>5</code>와 문자열 <code>&quot;5&quot;</code>가 똑같이 <code>5</code>로 출력돼서 타입 때문에 벌어진 버그를 놓친 경험, 한 번쯤 있으실 거예요. 아니면 내가 만든 클래스를 <code>print</code>했더니 <code>&lt;__main__.User object at 0x7f...&gt;</code> 같은 <strong>우주 주소</strong>만 나와서 당황했거나요.</p>
<p>이건 파이썬이 객체를 문자열로 바꾸는 <strong>두 가지 방식</strong>을 가지고 있기 때문입니다. 하나는 <strong><code>str()</code> — 사람이 읽기 좋은 형태</strong>, 다른 하나는 <strong><code>repr()</code> — 개발자가 디버깅하기 좋은 형태</strong>. 이 둘의 차이를 알면 디버깅 속도가 확 올라가고, 내 클래스도 더 친절하게 만들 수 있습니다.</p>
<h2 id="💣-흔한-실수">💣 흔한 실수</h2>
<pre><code class="language-python"># print는 내부적으로 str()을 호출 → 타입 정보가 사라짐
print(5)       # 5
print(&#39;5&#39;)     # 5 — 어라, 똑같이 나오네?
print([1, &#39;2&#39;, 3])  # [1, &#39;2&#39;, 3] — 리스트 안에서는 repr이 쓰여서 다행히 구분됨


# 내 클래스를 그냥 print하면 쓸모없는 출력
class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age

u = User(&quot;지훈&quot;, 30)
print(u)  # &lt;__main__.User object at 0x7f8b1c0e3a90&gt;  😵</code></pre>
<p>숫자 5와 문자열 &#39;5&#39;가 겉보기엔 똑같이 찍혀서 타입 버그의 온상이 됩니다. 내 클래스도 <code>print</code>해봐야 <strong>메모리 주소</strong>만 보여서 안의 값이 뭔지 알 수가 없어요.</p>
<h2 id="✅-파이썬다운-방법">✅ 파이썬다운 방법</h2>
<p>디버깅할 때는 <code>repr()</code>을 쓰거나 f-string의 <code>!r</code> 플래그를 활용하세요. 내 클래스에는 <code>__repr__</code>을 직접 정의해주는 게 기본입니다.</p>
<pre><code class="language-python"># repr은 타입을 구분해준다
print(repr(5))     # 5
print(repr(&#39;5&#39;))   # &#39;5&#39; — 따옴표가 붙어서 문자열임이 드러남

# f-string에서 !r 플래그로 간편하게
x = &#39;5&#39;
print(f&quot;{x!r}&quot;)    # &#39;5&#39;
print(f&quot;{x=}&quot;)     # x=&#39;5&#39;  ← !r이 기본 적용됨, 디버깅 최강


# 내 클래스에 __repr__과 __str__ 직접 정의
class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        # 개발자용: 객체를 재구성할 수 있을 정도로 상세하게
        return f&quot;User(name={self.name!r}, age={self.age!r})&quot;

    def __str__(self):
        # 사람용: 읽기 좋게
        return f&quot;{self.name}({self.age}세)&quot;


u = User(&quot;지훈&quot;, 30)
print(u)          # 지훈(30세)              ← __str__
print(repr(u))    # User(name=&#39;지훈&#39;, age=30) ← __repr__
print(f&quot;{u!r}&quot;)   # User(name=&#39;지훈&#39;, age=30)
[u]               # [User(name=&#39;지훈&#39;, age=30)] ← 컨테이너 안에선 항상 __repr__</code></pre>
<p>핵심 규칙 세 가지입니다. 첫째, <strong><code>str()</code>은 사람을 위한 표현</strong>, <code>repr()</code>은 <strong>개발자를 위한 표현</strong>. 둘째, <strong>리스트·딕셔너리 같은 컨테이너는 내부 원소를 출력할 때 항상 <code>repr()</code>을 사용</strong>합니다. 그래서 <code>[1, &#39;2&#39;, 3]</code>에서 <code>&#39;2&#39;</code>에 따옴표가 붙는 거예요. 셋째, <strong><code>__str__</code>을 따로 정의하지 않으면 <code>print()</code>는 <code>__repr__</code>로 폴백</strong>합니다. 그러니 <strong><code>__repr__</code>만 잘 정의해도 절반은 성공</strong>이에요.</p>
<p>한 가지 관례가 있는데, <code>__repr__</code>이 반환하는 문자열은 <strong>그 값을 다시 만들 수 있는 파이썬 식</strong>처럼 쓰는 게 이상적입니다. <code>User(name=&#39;지훈&#39;, age=30)</code>처럼요. 가능하면 <code>eval()</code>해서 원본이 복원될 정도로 상세하게.</p>
<h2 id="📎-기억할-것">📎 기억할 것</h2>
<ul>
<li><code>str()</code>은 사람이 읽기 위한 것, <code>repr()</code>은 개발자가 디버깅하기 위한 것입니다.</li>
<li><code>print()</code>는 기본적으로 <code>str()</code>을 호출하지만, <strong>리스트·딕셔너리 내부 원소는 <code>repr()</code>로 출력</strong>됩니다.</li>
<li>f-string에서 <code>!r</code>을 붙이면 <code>repr</code> 형태로, <code>f&quot;{x=}&quot;</code>는 변수명+<code>repr</code> 자동 출력이라 디버깅에 최고입니다.</li>
<li>내 클래스를 만들 때는 <strong><code>__repr__</code>은 거의 필수</strong>, <code>__str__</code>은 사용자에게 직접 노출할 때만 추가로 정의하세요.</li>
<li><code>__repr__</code>은 이상적으로 &quot;그 값을 재구성할 수 있는 파이썬 식&quot;처럼 생겨야 합니다.</li>
</ul>
<h2 id="🛠-실무에서-어디-쓸까">🛠 실무에서 어디 쓸까</h2>
<p><strong>도메인 클래스를 만들 때마다 <code>__repr__</code>을 정의해두는 습관</strong>이 생산성에 큰 차이를 만듭니다. 예를 들어 주문, 결제, 사용자 같은 핵심 엔티티에 <code>__repr__</code>이 없으면 <code>print(order)</code> 한 줄로 디버깅하려 할 때마다 객체 내부를 까봐야 해요. 반대로 <code>__repr__</code>을 잘 정의해두면 <code>logger.debug(f&quot;처리 중: {order!r}&quot;)</code> 같은 로그 한 줄이 진짜 쓸 만한 정보가 됩니다.</p>
<p>참고로 <code>dataclasses.dataclass</code>나 Pydantic 모델, SQLAlchemy의 일부 설정은 <strong><code>__repr__</code>을 자동으로 생성</strong>해줍니다. 그래서 요즘 실무에서는 <code>@dataclass</code>로 만들고 거기서 나오는 기본 <code>__repr__</code>에 만족하다가, 필요할 때만 오버라이드하는 패턴이 흔해요. 커스텀할 때는 민감 정보(비밀번호, API 키 등)를 <code>__repr__</code>에 노출하지 않도록만 주의하면 됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[문자열 포매팅은 그냥 f-string 쓰세요]]></title>
            <link>https://velog.io/@tasker_dev/%EB%AC%B8%EC%9E%90%EC%97%B4-%ED%8F%AC%EB%A7%A4%ED%8C%85%EC%9D%80-%EA%B7%B8%EB%83%A5-f-string-%EC%93%B0%EC%84%B8%EC%9A%94</link>
            <guid>https://velog.io/@tasker_dev/%EB%AC%B8%EC%9E%90%EC%97%B4-%ED%8F%AC%EB%A7%A4%ED%8C%85%EC%9D%80-%EA%B7%B8%EB%83%A5-f-string-%EC%93%B0%EC%84%B8%EC%9A%94</guid>
            <pubDate>Sun, 19 Apr 2026 06:48:44 GMT</pubDate>
            <description><![CDATA[<h2 id="🤔-이게-왜-문제인가">🤔 이게 왜 문제인가</h2>
<p>파이썬에는 문자열에 변수를 끼워 넣는 방법이 <strong>4가지</strong>나 있습니다. <code>%</code> 연산자(C 스타일), <code>str.format()</code>, f-string, 그리고 최근에 추가된 t-string까지. 옛날 코드베이스에서는 <code>%s</code>, <code>%d</code>를 자주 볼 수 있고, 중간 세대 코드에서는 <code>&quot;{name}&quot;.format(name=...)</code>이 흔해요. 문제는 <strong>앞의 두 방식이 읽기도 어렵고, 수정하기도 번거롭고, 버그도 만들기 쉽다</strong>는 겁니다.</p>
<p>Python 3.6부터 들어온 <strong>f-string</strong>은 사실상 이 모든 문제를 한 방에 정리한 문법이에요. 이제 새로 작성하는 코드에서 <code>%</code>나 <code>.format()</code>을 쓸 이유는 거의 없습니다.</p>
<h2 id="💣-흔한-실수">💣 흔한 실수</h2>
<pre><code class="language-python">name = &quot;지훈&quot;
age = 30
city = &quot;서울&quot;

# ① C 스타일 — 순서 맞추기 고역, 값 바뀌면 다시 맞춰야 함
msg = &quot;%s(%d세)은 %s에 삽니다&quot; % (name, age, city)

# 값 하나 더 넣고 싶을 때 튜플 순서 다 맞춰야 함
msg = &quot;%s(%d세, %s 거주)님 환영합니다. %s님!&quot; % (name, age, city, name)  # name 두 번 중복

# ② 딕셔너리로 풀면 덜 아프지만 번잡함
msg = &quot;%(name)s은 %(age)d세입니다&quot; % {&quot;name&quot;: name, &quot;age&quot;: age}

# ③ str.format — 좀 낫지만 여전히 두 번 쓰는 느낌
msg = &quot;{name}은 {age}세입니다&quot;.format(name=name, age=age)</code></pre>
<p><code>%</code> 방식은 <strong>좌우 순서·개수·타입이 모두 일치</strong>해야 하고, 값 하나만 살짝 바꾸려 해도 튜플 전체를 다시 조립해야 합니다. 같은 값을 여러 번 쓰면 튜플에 중복으로 넣어야 하고요. <code>str.format</code>은 키워드 인자로 가독성이 좀 나아지지만, <strong>변수명을 두 번 쓰는 중복</strong>(<code>{name}</code>과 <code>name=name</code>)이 여전히 거슬립니다.</p>
<h2 id="✅-파이썬다운-방법">✅ 파이썬다운 방법</h2>
<pre><code class="language-python">name = &quot;지훈&quot;
age = 30
city = &quot;서울&quot;

# f-string: 변수 이름을 그대로 문자열 안에 쓴다
msg = f&quot;{name}({age}세)은 {city}에 삽니다&quot;

# 식(expression)도 그대로 들어간다
msg = f&quot;{name}님은 내년에 {age + 1}세가 됩니다&quot;

# 포매팅 스펙도 동일하게 지원
pi = 3.141592
msg = f&quot;파이는 {pi:.2f}입니다&quot;              # 3.14
msg = f&quot;가격: {1234567:,}원&quot;                 # 1,234,567원
msg = f&quot;{0.85:.1%}&quot;                          # 85.0%

# 디버깅용 = 표기 (Python 3.8+) — 변수명과 값을 한꺼번에
x, y = 10, 20
print(f&quot;{x=}, {y=}&quot;)                         # x=10, y=20</code></pre>
<p>f-string의 핵심은 <strong>변수 이름을 문자열 안에 바로 쓴다</strong>는 점입니다. 중복이 사라지고, <code>{age + 1}</code>처럼 <strong>식도 그대로 평가</strong>돼요. <code>:.2f</code>, <code>:,</code>, <code>:%</code> 같은 포매팅 스펙도 기존 방식과 똑같이 쓸 수 있어서 손해 보는 것도 없습니다.</p>
<p>디버깅할 때 유용한 <code>f&quot;{x=}&quot;</code> 표기법도 꼭 기억하세요. <code>print(f&quot;{user=}&quot;)</code> 한 줄이면 <code>user=&lt;User object&gt;</code>처럼 변수명과 값을 같이 찍어주니까, 찍어볼 변수가 많을 때 엄청 편합니다.</p>
<h2 id="📎-기억할-것">📎 기억할 것</h2>
<ul>
<li><code>%</code> 스타일, <code>str.format()</code>, f-string 중에서 <strong>새 코드는 무조건 f-string</strong>이 정답입니다.</li>
<li>f-string 안에는 변수뿐 아니라 <strong>임의의 파이썬 식</strong>을 넣을 수 있습니다(<code>{a + b}</code>, <code>{user.name.upper()}</code>).</li>
<li>기존 포매팅 스펙(<code>:.2f</code>, <code>:,</code>, <code>:&gt;10</code>, <code>:%</code> 등)이 f-string에서도 그대로 동작합니다.</li>
<li><code>f&quot;{x=}&quot;</code> 형태는 디버깅 로그 찍을 때 특히 유용합니다(Python 3.8+).</li>
<li>중괄호를 문자 그대로 쓰려면 <code>{{</code>, <code>}}</code>로 이스케이프하세요.</li>
</ul>
<h2 id="🛠-실무에서-어디-쓸까">🛠 실무에서 어디 쓸까</h2>
<p>사실상 모든 곳입니다. 로깅 메시지, 사용자에게 보여줄 에러 메시지, SQL 쿼리 생성(단, <strong>사용자 입력으로 쿼리 만들 땐 f-string 대신 파라미터 바인딩 쓰세요</strong> — SQL 인젝션 위험), API 응답 포매팅, CLI 출력 등. 특히 <strong>데이터 분석 리포트</strong>나 <strong>Slack 알림봇</strong> 만들 때 <code>f&quot;오늘 신규 가입 {new_users:,}명 (전일 대비 {growth:+.1%})&quot;</code> 같은 한 줄이면 깔끔한 출력이 완성됩니다. </p>
<p>한 가지 유의점은 <strong>로깅 라이브러리에서는 f-string이 항상 최선은 아니라는</strong> 점이에요. <code>logger.info(f&quot;user={user.name}&quot;)</code>보다 <code>logger.info(&quot;user=%s&quot;, user.name)</code> 쪽이 로그 레벨에 따라 문자열 포매팅 자체를 건너뛸 수 있어 성능상 유리합니다. 고빈도 로그에서만 신경 쓰면 되고, 평범한 애플리케이션에서는 f-string이 가독성 면에서 압도적이니 편하게 쓰세요.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[bytes와 str, 섞어 쓰면 반드시 터집니다]]></title>
            <link>https://velog.io/@tasker_dev/bytes%EC%99%80-str-%EC%84%9E%EC%96%B4-%EC%93%B0%EB%A9%B4-%EB%B0%98%EB%93%9C%EC%8B%9C-%ED%84%B0%EC%A7%91%EB%8B%88%EB%8B%A4</link>
            <guid>https://velog.io/@tasker_dev/bytes%EC%99%80-str-%EC%84%9E%EC%96%B4-%EC%93%B0%EB%A9%B4-%EB%B0%98%EB%93%9C%EC%8B%9C-%ED%84%B0%EC%A7%91%EB%8B%88%EB%8B%A4</guid>
            <pubDate>Sun, 19 Apr 2026 06:37:52 GMT</pubDate>
            <description><![CDATA[<h2 id="🤔-이게-왜-문제인가">🤔 이게 왜 문제인가</h2>
<p>파일에서 데이터를 읽다가 <code>TypeError: a bytes-like object is required, not &#39;str&#39;</code> 에러를 만난 적 있나요? 아니면 HTTP 응답을 파싱하는데 <code>b&#39;...&#39;</code> 형태로 나와서 <code>.replace()</code>가 먹히지 않아 당황했던 적은요? 파이썬의 <code>bytes</code>와 <code>str</code>은 <strong>겉보기엔 비슷하지만 완전히 다른 타입</strong>이고, 이 차이를 모르면 한글 깨짐, 파일 읽기 오류, 네트워크 통신 버그로 끝없이 시달리게 됩니다.</p>
<p>핵심 차이는 간단합니다. <strong><code>bytes</code>는 0~255 정수의 나열(raw 바이트)</strong>이고, <strong><code>str</code>은 사람이 읽는 문자(유니코드 코드 포인트)의 나열</strong>입니다. 디스크나 네트워크는 바이트만 알고, 사람은 글자로 보고 싶어 하죠. 이 둘 사이의 변환이 <strong>인코딩/디코딩</strong>입니다.</p>
<h2 id="💣-흔한-실수">💣 흔한 실수</h2>
<pre><code class="language-python"># 파일을 바이너리로 열고 문자열처럼 다루기
with open(&#39;log.txt&#39;, &#39;rb&#39;) as f:
    data = f.read()
    if &#39;error&#39; in data:  # 💥 TypeError: a bytes-like object is required
        print(&quot;에러 발견&quot;)


# bytes와 str을 그냥 합치려 하기
greeting = b&#39;hello, &#39;
name = &#39;세계&#39;
print(greeting + name)  # 💥 TypeError


# 한글을 인코딩 안 하고 바이트 취급
data = &#39;안녕&#39;
with open(&#39;out.bin&#39;, &#39;wb&#39;) as f:
    f.write(data)  # 💥 TypeError: a bytes-like object is required</code></pre>
<p><code>&#39;rb&#39;</code>로 연 파일은 <code>bytes</code>를 반환하는데 <code>&#39;error&#39;</code>(문자열)로 검색하려니 타입이 안 맞습니다. <code>bytes</code>와 <code>str</code>은 <strong>더하기도, 비교도, 포함 검사도 안 돼요</strong>. 파이썬이 &quot;비슷하니까 알아서 해주겠지&quot;를 거부하는 몇 안 되는 영역입니다.</p>
<h2 id="✅-파이썬다운-방법">✅ 파이썬다운 방법</h2>
<p><strong>&quot;유니코드 샌드위치&quot;</strong> 패턴이 정답입니다. <strong>입구에서 바로 <code>str</code>로 디코딩, 내부에선 <code>str</code>로만 처리, 나갈 때 다시 <code>bytes</code>로 인코딩</strong>하는 구조예요.</p>
<pre><code class="language-python"># 입구: 바이트를 문자열로 디코딩
with open(&#39;log.txt&#39;, &#39;rb&#39;) as f:
    raw = f.read()                  # bytes
    text = raw.decode(&#39;utf-8&#39;)      # str

# 내부: str로만 처리
if &#39;error&#39; in text:
    print(&quot;에러 발견&quot;)

# 출구: 다시 바이트로 인코딩
with open(&#39;out.bin&#39;, &#39;wb&#39;) as f:
    f.write(&#39;안녕&#39;.encode(&#39;utf-8&#39;))  # str → bytes


# 타입이 헷갈릴 때 쓸 수 있는 도우미 함수
def to_str(value) -&gt; str:
    if isinstance(value, bytes):
        return value.decode(&#39;utf-8&#39;)
    return value  # 이미 str

def to_bytes(value) -&gt; bytes:
    if isinstance(value, str):
        return value.encode(&#39;utf-8&#39;)
    return value  # 이미 bytes</code></pre>
<p>변환의 핵심은 <strong>인코딩 이름을 명시하는 것</strong>입니다. UTF-8이 사실상의 표준이지만, 레거시 시스템에서는 <code>cp949</code>, <code>euc-kr</code>, <code>latin-1</code> 같은 걸 만날 수도 있어요. 디코딩할 때 인코딩이 다르면 <code>UnicodeDecodeError</code>가 나거나, 최악의 경우 조용히 글자가 깨집니다.</p>
<p>파일을 <strong>텍스트 모드(<code>&#39;r&#39;</code>, <code>&#39;w&#39;</code>)</strong>로 열면 파이썬이 자동으로 인코딩/디코딩을 해주니까, 한글 텍스트 파일 다룰 땐 그냥 <code>open(&#39;file.txt&#39;, &#39;r&#39;, encoding=&#39;utf-8&#39;)</code>을 쓰는 게 마음 편합니다. 바이너리 모드(<code>&#39;rb&#39;</code>, <code>&#39;wb&#39;</code>)는 이미지/오디오/네트워크 패킷처럼 진짜 바이트 단위가 필요할 때만 쓰세요.</p>
<h2 id="📎-기억할-것">📎 기억할 것</h2>
<ul>
<li><code>bytes</code>는 0~255 정수의 시퀀스(raw 데이터), <code>str</code>은 유니코드 문자의 시퀀스입니다.</li>
<li><code>bytes + str</code>, <code>bytes == str</code>, <code>&#39;x&#39; in bytes_value</code> 같은 혼용은 전부 에러입니다.</li>
<li><code>bytes.decode(&#39;utf-8&#39;)</code>으로 문자열화, <code>str.encode(&#39;utf-8&#39;)</code>으로 바이트화합니다.</li>
<li><strong>유니코드 샌드위치</strong>: 입력받자마자 <code>str</code>로 디코딩, 처리는 <code>str</code>로, 출력 직전에 <code>bytes</code>로 인코딩.</li>
<li>파일은 기본적으로 텍스트 모드(<code>&#39;r&#39;</code>, <code>&#39;w&#39;</code>)로 열고 <code>encoding=&#39;utf-8&#39;</code>을 명시하세요. 바이너리 모드는 진짜 바이트 단위가 필요할 때만.</li>
</ul>
<h2 id="🛠-실무에서-어디-쓸까">🛠 실무에서 어디 쓸까</h2>
<p>웹 서버/클라이언트가 대표적입니다. <code>requests.get(url).content</code>는 <code>bytes</code>를, <code>.text</code>는 자동 디코딩된 <code>str</code>을 반환해요. 한글 페이지 크롤링할 때 <code>.text</code>가 깨지면 대부분 인코딩을 잘못 추측한 경우라, <code>response.encoding = &#39;utf-8&#39;</code>로 지정하거나 <code>.content.decode(&#39;euc-kr&#39;)</code>로 직접 풀어줘야 합니다. 또 <strong>AWS S3, Redis, 카프카</strong> 같은 서비스의 파이썬 클라이언트는 대부분 <code>bytes</code>를 주고받기 때문에, 애플리케이션 경계에서 반드시 인코딩/디코딩을 거쳐야 해요. 암호화(<code>hashlib</code>, <code>hmac</code>), 해시(<code>sha256</code>), JWT 서명 같은 보안 관련 API도 <code>bytes</code>를 요구하는 경우가 많아서, <code>&quot;비밀번호&quot;.encode(&#39;utf-8&#39;)</code>을 습관적으로 붙이는 게 실수를 줄입니다.</p>
]]></description>
        </item>
    </channel>
</rss>