<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Judy's Step one</title>
        <link>https://velog.io/</link>
        <description>AI Researcher</description>
        <lastBuildDate>Sun, 20 Jul 2025 17:22:39 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Judy's Step one</title>
            <url>https://velog.velcdn.com/images/judy_choi/profile/044182c3-78dd-48bd-bdcb-6ef8680876f3/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Judy's Step one. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/judy_choi" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[LLM-MT 시리즈] (논문 구현🪄) DRT: Deep Reasoning Translation via Long Chain-of-Thought]]></title>
            <link>https://velog.io/@judy_choi/LLM-MT-%EC%8B%9C%EB%A6%AC%EC%A6%88-%EB%85%BC%EB%AC%B8-%EA%B5%AC%ED%98%84-DRT-Deep-Reasoning-Translation-via-Long-Chain-of-Thought-tdn1dbm8</link>
            <guid>https://velog.io/@judy_choi/LLM-MT-%EC%8B%9C%EB%A6%AC%EC%A6%88-%EB%85%BC%EB%AC%B8-%EA%B5%AC%ED%98%84-DRT-Deep-Reasoning-Translation-via-Long-Chain-of-Thought-tdn1dbm8</guid>
            <pubDate>Sun, 20 Jul 2025 17:22:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Source 문장을 입력하면 Target 언어로 번역한 결과와 번역 과정(Thinking trace)을 생성하는 &#39;DRT&#39; 모델 구현</p>
</blockquote>
<ul>
<li>📄 논문 : <a href="https://arxiv.org/pdf/2412.17498">DRT: Deep Reasoning Translation via Long Chain-of-Thought</a></li>
<li>📝 논문 리뷰 : <a href="https://velog.io/@judy_choi/LLM-MT-%EC%8B%9C%EB%A6%AC%EC%A6%88-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-DRT-Deep-Reasoning-Translation-via-Long-Chain-of-Thought">[LLM-MT 시리즈] (논문 리뷰) DRT: Deep Reasoning Translation via Long Chain-of-Thought</a></li>
<li><strong>🌟 직접 구현한 Multi-Agent : <a href="https://github.com/Judy-Choi/DRT_Multi-Agent">DRT_Multi-Agent</a></strong></li>
<li><strong>🤗 한-영 DRT 모델 배포 : <a href="https://huggingface.co/JudyChoi/qwen2.5-7b-drt">JudyChoi/qwen2.5-7b-drt</a></strong></li>
</ul>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/b8feea28-4b78-44f4-9116-a6769b62714c/image.png" alt=""></p>
<h2 id="구현-동기">구현 동기</h2>
<h3 id="번역--cot--품질-향상">번역 + CoT = 품질 향상</h3>
<p>LLM이 추론을 할 때, 추론 과정에서 CoT 를 거치면 답변 품질이 더 좋아진다는 것은 널리 알려진 사실이다.
번역 또한 마찬가지로, CoT 를 거치면 특히 복잡한 의미 파악이 필요한 문장을 번역하는 데에 효과적이다.</p>
<p>CoT 유/무에 따른 ChatGPT 번역 품질 예시는 다음과 같다.</p>
<ul>
<li><p>Src : &quot;그 사람 말투가 좀 이상하지 않아?&quot;</p>
</li>
<li><p>Tgt : &quot;Doesn&#39;t the way they talk sound a bit strange?&quot;</p>
</li>
<li><p>CoT ➡️ Tgt : &quot;Don’t you think the way he talks is a little strange?&quot;</p>
<ul>
<li><p>프롬프트 :</p>
<pre><code>다음 CoT 를 거쳐서 주어진 문장을 영어로 번역해 줘

&quot;그 사람 말투가 좀 이상하지 않아?&quot;

&lt;CoT&gt;
Step 1: 문장의 화자가 전달하려는 의도는?
Step 2: 문장의 구조는?
Step 3: 핵심 어휘를 영어로 자연스럽게 표현하면?
Step 4: 자연스럽고 뉘앙스를 살린 영어 문장을 생성하라.</code></pre></li>
<li><p>결과 :</p>
<pre><code>Step 1: 의도
→ 그 사람 말투가 이상하다는 의심 + 공감 유도 질문

Step 2: 구조
→ 평서문 + 부가 의문
“말투가 이상하지” + “않아?”

Step 3: 핵심 어휘

말투: way of speaking, how he talks

이상하다: weird, strange, off

~하지 않아?: Don’t you think…?

Step 4: 번역
✅ &quot;Don’t you think the way he talks is a little strange?&quot;</code></pre></li>
</ul>
</li>
</ul>
<h3 id="한계--리소스-증가">한계 : 리소스 증가</h3>
<p>번역 과정에서 CoT 를 적용하면 번역 품질이 더 좋아질 수 있지만,
CoT 는 필연적으로 LLM이 더 긴 내용을 추론(생성)해야 한다는 뜻이다.
이 때문에 답변을 생성하기까지 더 많은 시간이 걸리고, API 를 사용할 경우 더 많은 토큰을 소모하여 서비스 비용이 증가한다.</p>
<h3 id="해결--cot-거쳐-번역하는-모델-구현">해결 : CoT 거쳐 번역하는 모델 구현</h3>
<p>그렇다면, LLM 에 CoT 프롬프트를 추가하는 대신
LLM 모델이 번역 중 CoT(thought) 하도록 학습시켜서 사용해 보자!</p>
<h2 id="논문에서-참고한-내용">논문에서 참고한 내용</h2>
<blockquote>
<p>논문 내용 중 구현에 필요한 점 위주로 정리했습니다.</p>
</blockquote>
<h3 id="3줄-요약">3줄 요약</h3>
<ul>
<li><p>✨ 이 논문은 문학 번역과 같은 어려운 기계 번역 작업에 <U>&#39;long chain-of-thought&#39; 추론을 적용한 DRT 모델</U>을 제안합니다. (영어 ➡️ 중국어)</p>
</li>
<li><p>🛠️ DRT는 <U>번역가, 조언자, 평가자로 구성된 멀티 에이전트 프레임워크와 GPT-4o를 활용하여 반복적인 &#39;long thought&#39; 번역 과정을 포함하는 학습 데이터</U>를 생성합니다.</p>
</li>
<li><p>🏆 이 학습 데이터로 SFT 한 DRT 모델은 &#39;long thought&#39; 없이 학습된 기존 LLM 및 SFT 모델 대비 우수한 문학 번역 성능을 보여 생성된 데이터와 방법론의 효과를 입증했습니다.</p>
</li>
</ul>
<h3 id="multi-agent-를-이용한-drt-dataset-구축">Multi-Agent 를 이용한 DRT Dataset 구축</h3>
<p>영어 문학 데이터셋으로부터 번역하기 어려운 문장을 추출해 초기 데이터셋을 구축하고,
다음 작업을 수행하는 <code>Agent</code> 를 각각 만들고 서로 연결해 Multi-Agent 를 구축한다.</p>
<p>이어서 초기 데이터셋을 Multi-Agent 에 한 문장씩 <code>&lt;input&gt;</code> 넣어서
번역문 <code>&lt;output&gt;</code> 과 Thinking trace <code>&lt;thought&gt;</code> 를 얻는다.
최종적으로 {<code>&lt;input&gt;</code> : <code>&lt;output&gt;</code> - <code>&lt;thought&gt;</code>} 쌍으로 구성된 최종 DRT 데이터셋을 구축한다.</p>
<h3 id="drt-model-sft">DRT model SFT</h3>
<p>DRT 데이터셋을 llm backbone 모델에 Supervised Fine-Tuning 시켜서
Mutli-Agent 나 프롬프트 없이도 DRT 모델만으로 thinking trace 를 거쳐 번역한다.</p>
<p>Backbone 모델 정보:</p>
<ul>
<li>DRT-7B: Qwen2.5-7B-Instruct</li>
<li>DRT-8B: Llama-3.1-8B-Instruct</li>
<li>DRT-14B: Qwen2.5-14B-Instruct</li>
</ul>
<h3 id="정리">정리</h3>
<ul>
<li>&#39;long chain-of-thought&#39; 추론을 적용한 DRT 모델을 개발해 문학 작품 번역에 적용</li>
<li>DRT 모델 학습용 데이터셋 구축에 Multi-Agent 사용</li>
<li>DRT 데이터로 SFT 학습한 모델은 &#39;long thought&#39; 없이 학습된 기존 LLM 및 SFT 모델과 비교해 문학 번역 성능이 더 좋다!</li>
</ul>
<h2 id="구현">구현</h2>
<h3 id="초기-데이터셋">초기 데이터셋</h3>
<p>한국어 문학 데이터셋은 마땅한 게 없기도 했고, 초기 데이터셋 구축에 시간과 API 비용이 너무 많이 들어서
직역하기 어렵고, 은유나 비유 등이 포함된 문장 데이터셋으로
5년 전에 내가 직접 구축한 한-영 관용구 포함 문장 데이터셋 <strong>&#39;KISS💋&#39;</strong> 를 사용했다.
<strong>&#39;KISS&#39;</strong> 는 &#39;Korean-english Idioms in Sentences dataSet&#39; 의 약자로, 한국어 관용구가 포함된 문장과 이를 번역한 영어 문장 쌍 총 7500개로 구성된 데이터셋이다.</p>
<blockquote>
</blockquote>
<ul>
<li>데이터셋 : <a href="https://github.com/Judy-Choi/KISS-Korean-english-Idioms-in-Sentences-dataSet">GitHub</a></li>
<li>논문 : <a href="https://drive.google.com/file/d/17ThvMC67_7CC3oIAP1I1aYEx0goPfoQQ/view">관용구 기계번역을 위한 한-영 데이터셋 구축 및 평가 방법 (KCC 2020)</a></li>
</ul>
<h3 id="multi-agent-설계">Multi-Agent 설계</h3>
<p>논문에서 각각의 Agent 의 역할을 설명했고, 부록으로 프롬프트까지 제공했는데
정작 Multi-Agent 의 워크플로우 그림이 없다. 😒</p>
<p>일단 논문 저자가 허깅페이스에 올려둔 모델을 다운받아서 예시 문장을 넣으니 다음과 같이 출력한다. (영 ➡️ 중)</p>
<ul>
<li><p>예시 문장 : &quot;The mother, with her feet propped up on a stool, seemed to be trying to get to the bottom of that answer, whose feminine profundity had struck her all of a heap.&quot;</p>
</li>
<li><p>출력 (원본) :</p>
<pre><code>&lt;thought&gt;
I start by identifying the key elements of the English sentence that need to be translated accurately. I pick out the keywords: &#39;mother&#39;, &#39;feet&#39;, &#39;stool&#39;, &#39;trying&#39;, &#39;bottom&#39;, &#39;answer&#39;, &#39;feminine&#39;, &#39;profundity&#39;, and &#39;struck&#39;. I translate these keywords into Korean: &#39;mother&#39; becomes &#39;母亲&#39;, &#39;feet&#39; becomes &#39;脚&#39;, &#39;stool&#39; becomes &#39;凳子&#39;, &#39;trying&#39; becomes &#39;试图&#39;, &#39;bottom&#39; becomes &#39;底&#39;, &#39;answer&#39; becomes &#39;回答&#39;, &#39;feminine&#39; becomes &#39;女性的&#39;, &#39;profundity&#39; becomes &#39;深刻性&#39;, and &#39;struck&#39; becomes &#39;打动&#39;.
With these translations in mind, I attempt to translate the entire sentence: 母亲，她的脚放在凳子上，似乎在试图弄清那个回答的底细，这个回答的女性深刻性让她一时间被打动了. I review this translation and realize that while it captures the essence of the original sentence, some parts feel awkward and slightly inaccurate. The phrase &#39;whose feminine profundity had struck her all of a heap&#39; is translated as &#39;这个回答的女性深刻性让她一时间被打动了&#39;, which doesn&#39;t fully convey the sense of being deeply moved or struck by the depth of the response.
I think about how to make the translation more natural and accurate. I revise the sentence to: 母亲，她的脚放在凳子上，似乎在试图弄清那番回答的真相，这番回答的女性深邃让她深受感动. This revised translation feels more natural and accurately conveys the original meaning. The phrase &#39;这番回答的女性深邃让她深受感动&#39; better captures the sense of being deeply moved by the profundity of the response.
I review the revised translation and find it smooth and clear, effectively conveying the intended message. However, I notice a minor issue with the word &#39;真相&#39; (truth), which might not be the best choice here. I decide to use &#39;底细&#39; (details) instead, as it aligns better with the original text.
Finally, I settle on the translation: 母亲，双脚搭在凳子上，似乎在努力探究那番回答的真谛，这番回答的女性深邃让她深受感动. This final translation feels right, capturing the nuances and emotional depth of the original English sentence.
&lt;/thought&gt;
&lt;output&gt;
母亲，双脚搭在凳子上，似乎在努力探究那番回答的真谛，这番回答的女性深邃让她深受感动。
&lt;/output&gt;</code></pre></li>
<li><p>출력 (번역) :</p>
<pre><code>&lt;thought&gt;
저는 먼저 영어 문장에서 정확히 번역해야 할 핵심 요소들을 식별하는 것부터 시작합니다.
주요 키워드를 뽑아냅니다: ‘mother’, ‘feet’, ‘stool’, ‘trying’, ‘bottom’, ‘answer’, ‘feminine’, ‘profundity’, ‘struck’.
이 키워드들을 한국어로 번역해 봅니다:
‘mother’는 ‘母亲’, ‘feet’는 ‘脚’, ‘stool’는 ‘凳子’, ‘trying’은 ‘试图’, ‘bottom’은 ‘底’,
‘answer’는 ‘回答’, ‘feminine’은 ‘女性的’, ‘profundity’는 ‘深刻性’, ‘struck’는 ‘打动’이 됩니다.

이러한 번역을 염두에 두고 전체 문장을 번역해 봅니다:
母亲，她的脚放在凳子上，似乎在试图弄清那个回答的底细，这个回答的女性深刻性让她一时间被打动了.
이 번역을 검토하면서, 전체적인 의미는 잘 전달되지만 일부 표현이 어색하거나 조금 부정확하다는 것을 느낍니다.
예를 들어,
&quot;whose feminine profundity had struck her all of a heap&quot;라는 표현은
“这个回答的女性深刻性让她一时间被打动了”로 번역되어 있는데,
이는 깊이 감동받은 느낌을 충분히 전달하지 못하고 있습니다.

좀 더 자연스럽고 정확한 번역을 고민합니다.
그래서 문장을 이렇게 수정해 봅니다:
母亲，她的脚放在凳子上，似乎在试图弄清那番回答的真相，这番回答的女性深邃让她深受感动.

이 수정된 문장은 좀 더 자연스럽고 원문의 의미를 잘 전달하는 것처럼 느껴집니다.
‘这番回答的女性深邃让她深受感动’라는 표현은
그녀가 응답의 깊이에 의해 감명받았다는 느낌을 더 잘 담아냅니다.

수정된 번역을 다시 살펴보니 전체적으로 부드럽고 명확하며, 전달하려는 메시지를 효과적으로 담고 있습니다.
하지만 &#39;真相&#39;(진상/진실)이라는 단어가 다소 어울리지 않을 수 있다는 점이 눈에 띕니다.
그래서 원문에 더 잘 맞는 표현으로 ‘底细’(자세한 내막)로 바꾸기로 합니다.

결국 최종 번역은 이렇게 정리됩니다:
母亲，双脚搭在凳子上，似乎在努力探究那番回答的真谛，这番回答的女性深邃让她深受感动.

이 최종 번역은 원문이 가진 뉘앙스와 감정의 깊이를 잘 포착한 것처럼 느껴집니다.
&lt;/thought&gt;
&lt;output&gt;
엄마는 두 발을 의자 위에 올려놓고, 그 답변의 진의를 열심히 탐구하는 듯했다.
그 답변에 담긴 여성적인 깊이에 그녀는 깊이 감동했다.
&lt;/output&gt;</code></pre></li>
</ul>
<p>이 <code>&lt;thought&gt;</code> 을 Multi-Agent 로 생성하는 셈인데..
출력 결과를 문단별로 분석해 보니 아마도 워크플로우가 이렇게 설계된 것 같다.</p>
<ol>
<li>주요 키워드 추출 후 번역</li>
<li>키워드를 참고해 전체 문장 번역 후 번역 결과 검토, 평가</li>
<li>번역 결과 수정(재번역)</li>
<li>번역 결과 검토, 평가</li>
<li>번역 결과 검토, 평가, 수정(재번역)</li>
<li>최종 번역</li>
</ol>
<p>이 워크플로우를 참고해 논문에서 설명하는 각 Agent 의 역할과 기능을 정리했다.</p>
<ol>
<li><code>Translator (Word-level)</code> : 1개의 문장에 대해 중요한 단어 추출 후 중국어로 번역, {영단어 : 중국어 번역 단어} JSON 쌍 생성</li>
<li><code>Translator (Preliminary)</code>: {영단어 : 중국어 번역 단어} 쌍을 참고해 문장을 중국어로 직역</li>
<li><code>Advisor</code> : 번역을 평가하고 의견과 제안 제공</li>
<li><code>Evaluator</code> : 번역에 대해 0점에서 100점까지 평가하고, 그 이유를 제시</li>
<li><code>Translator</code> : 영어 문장을 중국어로 재번역</li>
<li>3~5 반복, 종료 조건에 도달하면 반복 종료.</li>
<li><code>Long Thought Reformulator</code> : 전체 번역 과정을 긴 1인칭 자기 성찰 설명(현재 시제 사용)으로 다듬어서 Long CoT 생성</li>
</ol>
<p>주요 Agent 3종 (Advisor, Evaluator, Translator)의 수식은 다음과 같은데</p>
<ul>
<li><strong>(3) Translation Refine Loop:</strong> 반복 단계 k에서 Agent들이 협력하여 번역을 정제합니다.<ul>
<li>Advisor : 이전 번역 $t_{k-1}$을 평가하고 피드백 $f^{k-1}$ 제공</li>
<li>Evaluator : $t_{k-1}$에 대해 점수 $s^{k-1}$ 부여</li>
<li>Translator : $t_{k-1}$, $f^{k-1}$, $s^{k-1}$을 고려하여 새 번역 $t_k$ 생성</li>
</ul>
</li>
<li>루프는 점수 $s^{k-1}$이 임계값에 도달하거나 최대 반복 횟수에 이르면 중단됩니다.</li>
</ul>
<p>워크플로우와 수식에서 개인적으로 의아한 점들이 있었다.</p>
<ol>
<li><p>비효율적인 워크플로우
논문에서 Agent 를 언급한 순서대로라면 이렇게 되는데,
<code>단어 번역 → 문장 직역 → loop(Advisor → Evaluator → Translator) → Long Thought Reformulator</code>
문장 직역 직후에 Advisor 에서 피드백을 받고, 바로 Evaluator 로 넘긴다?
만약 Evaluator 가 바로 통과시킬만한 문장이라면 피드백을 받는 과정이 불필요한 셈인데?</p>
</li>
<li><p>새 번역을 생성할 때 상대적으로 덜 중요한 요소 사용
논문의 프롬프트에서 Evaluator 는 평가 점수와, 점수를 매긴 이유를 함께 설명하는데,
그렇다면 Evaluator 의 결과를 재번역에 반영한다면 평가 점수가 아니라 평가 이유를 반영해야 맞지 않나?
수식에서는 평가 점수인 $s$ 를 고려해 새 번역 $t_k$ 를 생성하는데, 점수보다는 해당 점수를 매긴 이유를 고려해야 하지 않을까?</p>
</li>
</ol>
<p>따라서 나는 논문과 조금 구현 방향이 달라지더라도 더 효율적이고, 인간의 작업 과정을 더 충실하게 모사하는 형태로 Multi-Agent 를 구현했다.</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/569e4cce-8947-4f73-a2e3-e1cef7dbee2d/image.png" alt=""></p>
<ul>
<li>동작 순서 : <code>단어 번역 → 문장 직역 → loop(translator → Evaluator → 조건문(loop 반복 조건 체크) → Advisor) → Long Thought Reformulator ➡️ 최종 저장</code></li>
<li>문장을 직역한 직후에는 translator 에서 재번역을 하지 않고 바로 evaluator 로 넘어가서 즉시 번역문을 평가하고, threshold 이상의 점수를 얻으면 즉시 loop 가 종료되도록 하여 번역 효율 극대화</li>
<li>evaluator 가 평가 점수 $s$ 와 평가 이유 $r$ 을 생성하고, 이후 Agent 들이 평가 점수 $s$ 대신 평가 이유 $r$ 을 참고하도록 수정</li>
<li>Advisor 도 번역 피드백을 제공할 때, 단순히 번역문뿐만이 아니라 evaluator 의 평가 이유 $r$ 도 함께 고려해 피드백을 제공</li>
<li>Advisor 의 피드백에 $r$이 포함된 셈이므로 Translator 는 $r$ 없이 $f$ 만 참고하여 재번역</li>
</ul>
<p>(지금 생각해 보니 개선한 워크플로우도 여전히 비효율적이고
덕분에 생성되는 데이터의 정확도가 다소 떨어졌을 수도 있겠지만
구현 당시 나는 이게 최선이었다. 추후 추가 개선 예정!)</p>
<p>이 모든 과정을 거쳤는데 API 사용량 한계 + 개발 기간의 한계로 인해
KISS 의 7500문장으로부터 DRT 학습용 데이터 총 1638 개를 생성했다.</p>
<h3 id="sft">SFT</h3>
<p>이렇게 얻은 DRT 데이터셋을 LLM 모델에 SFT 시켜서 최종적으로 DRT 를 수행하는 모델을 구현했다.
LLM backbone 은 논문과 똑같이 Qwen/Qwen2.5-7B-Instruct 을 사용했고,
학습에 Unsloth 프레임워크를 이용해 학습 시간을 크게 줄였다. (A100 에서 20분 내 완료)
파라미터는 Unsloth 튜토리얼에서 제공하는 값을 그대로 사용했는데
논문에서 사용한 파라미터 값을 참고해서 더 최적화했다면 좋았을 것 같다.</p>
<h2 id="평가">평가</h2>
<h3 id="타-번역-서비스와의-비교-및-인간-평가">타 번역 서비스와의 비교 및 인간 평가</h3>
<p>한국어 문장 <code>input</code> 을 모델에 넣어 추론시키면 번역하기까지의 과정<code>thought</code> 과 영어로 번역한 결과 <code>output</code> 을 생성한다.
프롬프트는 원작자들의 GitHub(및 HuggingFace) 를 참고해 언어쌍만 바꿔서 똑같이 사용했다.</p>
<pre><code class="language-python">result = translate_drt_ollama.invoke([
    SystemMessage(content=&quot;You are a philosopher skilled in deep thinking, accustomed to exploring complex problems with profound insight.&quot;),
    HumanMessage(content=f&quot;Please translate the following text from Korean to English:\n{source_text}&quot;)
]).content.strip()</code></pre>
<ul>
<li><p><code>input</code> : &quot;나는 남자 보는 눈이 높아서 차은우와 화촉을 밝힐 예정이다.&quot;</p>
</li>
<li><p><code>output</code> : &quot;I have a keen eye for men and plan to marry Cha Eun-woo with a ceremonial candlelighting.&quot;</p>
</li>
<li><p><code>thought</code>  : </p>
<ul>
<li><p>I start by selecting keywords and phrases from the Korean sentence: &quot;남자&quot; (man), &quot;보는 눈&quot; (eye for men), &quot;높아&quot; (high), &quot;차은우&quot; (Cha Eun-woo, a male K-pop idol singer), &quot;화촉&quot; (bridal candle), &quot;밝힐&quot; (to light up) and &quot;예정이다&quot; (plan to do).</p>
</li>
<li><p>I translate these keywords into English: man, eye for men, high, Cha Eun-woo, bridal candle, to light up, plan to do.</p>
</li>
<li><p>Next, I attempt a preliminary translation of the Korean sentence: &quot;I am a person with a high standard when it comes to judging men, so I plan to light the bridal candle for Cha Eun-woo.&quot;</p>
</li>
<li><p>As I review this translation, I feel that some parts need clarification. The phrase &quot;judging men&quot; may not be clear enough in English and might confuse the reader.</p>
</li>
<li><p>I consider rephrasing the sentence: &quot;I have a keen eye for men and plan to marry Cha Eun-woo with a ceremonial candlelighting.&quot;</p>
</li>
<li><p>This version is more natural and easier to understand, conveying the original meaning clearly. The term &quot;keen eye&quot; is used instead of &quot;judging,&quot; which may be less familiar in this context.</p>
</li>
<li><p>With these adjustments, I finalize my translation: &quot;I have a keen eye for men and plan to marry Cha Eun-woo with a ceremonial candlelighting.&quot; This version effectively communicates the meaning and intent behind the Korean sentence while maintaining fluency and clarity.</p>
</li>
</ul>
</li>
</ul>
<p>의도적으로 관용구가 2개나 포함되고, 고유명사(사람 이름) 을 넣은 예시 문장을 만들어 테스트해 보았는데
키워드를 추출하는 첫번째 단계에서 주요 관용구 키워드인 &#39;화촉&#39; 이 잘 추출되었고
고유명사 &#39;차은우&#39; 또한 잘 추출되었음은 물론 K-pop 아이돌 가수라는 정보까지 덤으로 생성되었다.
(Qwen 은 대체 학습을 어떤 데이터셋으로 했길래..!?)
<em>아무래도 차은우는 국보니까.. 😍</em></p>
<p align="center"><img src="https://velog.velcdn.com/images/judy_choi/post/b2c74d09-bd8b-4eb6-899e-b14267802b4c/image.png" width="300" />


<p>다른 번역기 및 LLM 서비스의 번역 결과와 비교하면 다음과 같다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>문장</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Input</strong></td>
<td>나는 남자 보는 <strong>눈이 높아서 차은우와 화촉을 밝힐</strong> 예정이다.</td>
</tr>
<tr>
<td><strong>Output</strong></td>
<td>I have a <strong>keen eye for men</strong> and plan to <strong>marry Cha Eun-woo with a ceremonial candlelighting.</strong></td>
</tr>
<tr>
<td>Qwen2.5-7B-Instruct(Backbone)</td>
<td>I have <strong>high standards for men</strong>, so I plan to <strong>light the wedding altar</strong> with Cha Eun-woo.</td>
</tr>
<tr>
<td>번역기 A</td>
<td>I have a <strong>high eye for men</strong>, so I&#39;m going to <strong>light up Cha Eun-woo</strong> and <em>his touch.</em></td>
</tr>
<tr>
<td>번역기 B</td>
<td>I have <strong>high standards for men</strong>, so I plan on <strong>getting married to Cha Eun-woo.</strong></td>
</tr>
<tr>
<td>번역기 C</td>
<td>I&#39;m a <em>man&#39;s man</em>, so I&#39;m going to <em>light up Cha Eun Woo and Hwak Hyeok.</em></td>
</tr>
<tr>
<td>LLM A</td>
<td>I have <strong>high standards when it comes to men</strong>, so I plan to <strong>marry Cha Eun-woo.</strong></td>
</tr>
<tr>
<td>LLM B</td>
<td>I have <strong>high standards when it comes to men</strong>, so I&#39;m planning to <strong>tie the knot with Cha Eun-woo.</strong></td>
</tr>
<tr>
<td>LLM C</td>
<td>I have <strong>high standards when it comes to men</strong>, so I&#39;m going to <strong>marry Cha Eun-woo.</strong></td>
</tr>
</tbody></table>
<p>전통적인 번역 서비스와 비교해 LLM을 사용한 번역이 더 정확함은 물론
LLM B 는 &#39;화촉을 밝히다&#39; 에 대응되는 관용구인 &#39;tie the knot&#39; 으로 번역하는 센스를 발휘했다.
그러나 <strong>&#39;화촉을 밝히다&#39; 의 불을 밝히는 공감각적 심상을 살린 번역은 DRT 가 유일했다.</strong></p>
<p><strong>특이사항</strong></p>
<ul>
<li>Backbone 모델인 &#39;Qwen2.5-7B-Instruct&#39; 은 비교적 직설적인 표현을 사용했다.
&#39;light the wedding altar&#39; 는 &#39;결혼 제단에 불을 밝히다&#39; 인데, ChatGPT 의 평가는 다음과 같다.<ul>
<li>영어권에서는 *&quot;to light the altar with someone&quot;*이라는 표현이 로맨틱하거나 상징적인 결혼 암시로 사용되지는 않습니다. 즉, 특별한 관용구는 아니고, 비교적 직설적인 묘사입니다.</li>
<li>약간은 문학적이거나 드라마 대사처럼 들릴 수 있습니다.</li>
</ul>
</li>
</ul>
<ul>
<li>LLM A 의 경우 의역을 많이 한 버전을 몇 가지 추가로 생성해 줬는데, 
그 중 &#39;&quot;My standards for men are sky-high — obviously, I&#39;ll be lighting the wedding candles with Cha Eun-woo.&quot;&#39; 도 있기는 했다.</li>
</ul>
<h3 id="리소스-비교-평가">리소스 비교 평가</h3>
<p>이런저런 이유로 반드시 DRT를 번역에 도입해야 한다고 가정하자.</p>
<p>만약 Multi-Agent 를 이용해 번역할 문장에 DRT 를 적용한다면 다음 과정을 거쳐야 한다.
  <code>단어 번역 → 문장 직역 → loop(translator → Evaluator → 조건문(loop 반복 조건 체크) → Advisor) → Long Thought Reformulator ➡️ 최종 저장</code></p>
<p>만약 운이 좋아 번역한 결과가 한 번에 Evaluator 를 통과해 Advisor 도 loop 반복도 없이 
Long Thought Reformulator 를 거쳐 최종적으로 번역된다고 해도
<code>단어 번역 → 문장 직역 → translator → Evaluator → Long Thought Reformulator</code> = 총 5개의 Agent 를 실행해야 한다.</p>
<p>이는 다시 말해, DRT 를 적용하려면 최소 5번 이상 LLM을 실행해야 한다는 뜻이므로 단순히 LLM에 프롬프트를 주어 번역하는 경우와 비교해 최소 5배 이상의 리소스가 필요하다는 뜻이다.
또한, 이 DRT 는 이전 Agent 의 결과를 참조하여 순차적으로 이루어지므로 병렬처리도 불가능하여 총 수행 시간도 크게 증가한다.</p>
<p>그러므로 만약 반드시 DRT를 번역에 도입해야 하는 상황이라면 Multi-Agent 를 그대로 사용하지 않고, LLM backbone 모델에 DRT 데이터셋을 학습시켜 사용하는 것을 권장한다.</p>
<p>한 가지 더 고려해야 할 점은 DRT 데이터셋 구축에 드는 리소스이다.
앞서 설명한 이유로 인해 1개의 DRT 데이터를 만들기 위해 최소 5회 이상의 LLM 호출이 필요하며, 1000개의 DRT 데이터를 만들기 위해서는 5000회의 LLM 호출이 필요하다.
일부 작업에 오픈소스 sLLM 을 사용해 리소스를 절감할 수 있겠으나, 나같은 개인 연구자로서는 로컬에 ollama 로 올릴 수 있는 sLLM 은 7b 크기가 최대이며, 수행 시간도 유료 LLM API 에 비해 길다.</p>
<p>이렇듯 DRT 데이터셋 구축 단계부터 적지 않은 비용과 시간이 투입되므로
실험이나 개발을 하기 전에 우선 DRT가 과연 내가 하려는 작업에 적합한지 충분히 검토하고 나서 시도하는 것을 추천한다.</p>
<p>반대로 말하면, 장기적이고 대량의 데이터를 사용하는 작업에 DRT가 효과가 있는 경우
DRT 모델을 개발해 도입하면 Agent 5개 대신 모델 1개만으로 DRT를 수행할 수 있으므로 리소스를 5배로 감축할 수 있다.</p>
<h2 id="이슈--해결">이슈 &amp; 해결</h2>
<h3 id="과도한-리소스">과도한 리소스</h3>
<p><strong>⚠️ 이슈</strong>
앞서 말했듯 DRT 데이터셋을 구축하기 위해 Multi-Agent 를 이용하면 최소 5회 이상 LLM을 호출하므로 적지 않은 토큰 비용과 수행 시간이 든다.</p>
<p><strong>✅ 해결</strong>
만약 주요 키워드를 추출하는 DRT의 첫 번째 단계에서 고유명사 등은 제외하고 단어만 추출해도 된다면 LLM 대신 토크나이저를 사용하는 방법을 고려할 수 있다.
그 다음 단계에서는 앞서 추출한 주요 키워드를 참고해 직역하는데, 이 단계까지는 사용 토큰량이 적기 때문에 더 적은 토큰을 지원하는 저렴한 LLM API 를 사용하면 LLM 비용을 다소 절감할 수 있다.</p>
<p>또한 DRT 데이터셋 구축 과정에서 수행 시간을 줄이기 위해 배치와 멀티쓰레드를 도입해 실행 한 번에 <code>batch size * max_workers</code> 개의 문장에 Multi-Agent 를 적용해 DRT 데이터셋을 더 빠르게 얻을 수 있도록 했다.</p>
<pre><code class="language-python">batch_size = 10  # 한 배치당 문장 수
max_workers = 8   # 동시 실행할 배치 수</code></pre>
<h3 id="대용량-데이터-처리-중-작업-중단">대용량 데이터 처리 중 작업 중단</h3>
<p><strong>⚠️ 이슈</strong>
대용량 데이터를 처리하는 중에는 몇 가지 이슈로 인해 작업이 중단되곤 한다.
대표적으로 유료 LLM API 요청 횟수가 증가하면 서버로부터 &#39;429 too many requests&#39; 에러를 리턴받기도 하고,
기타 예상하지 못한 이슈로 인해 코드 실행이 중단되면 지금까지 생성된 DRT 데이터는 모두 저장되지 못해 손실된다.
물론 DRT 데이터를 1개 얻을 때마다 파일에 저장하면 손실을 막을 수 있으나, 기본적으로 파일 i/o 하는 데에는 시간이 걸리고 잦은 i/o는 시스템에 과부하를 주기 때문에 작업 시간이 증가하고 freezing (일명 &#39;컴퓨터 멈춤&#39;) 이 발생할 수 있다.</p>
<p><strong>✅ 해결</strong>
가장 간단한 해결 방법은 fallback model 을 추가하는 것이다.
이렇게 하면 사용중이던 LLM이 에러를 리턴할 경우 미리 fallback model 로 지정해 둔 다른 모델로 전환하여 사용할 수 있으므로 모델 이슈로 중간에 작업이 끊기면 다른 모델을 사용해 작업을 지속할 수 있다.</p>
<pre><code class="language-python">except Exception as e:
    if self.fallback_llm and &quot;rate&quot; in str(e).lower():
        logger.warning(f&quot;🔄 {self.primary_model}에서 오류 발생 ({e}), {self.fallback_model}으로 전환합니다.&quot;)
        return self.fallback_llm.invoke(messages, config=config)</code></pre>
<p>작업이 중단될 경우 지금까지 얻은 DRT 데이터가 날아가는 것을 막아야 하지만, DRT 데이터를 얻을 때마다 저장하면 잦은 i/o 로 인한 문제들이 발생하므로 최대한 작업 중간에 저장할 수 있도록 자동화했다.
앞서 데이터셋 구축 작업에 배치와 멀티쓰레드를 적용한 이유는 병렬처리를 통해 수행 시간을 줄이려는 의도도 있었지만, 배치가 끝날 때마다 파일에 저장하도록 구현해 배치 단위로 파일 i/o가 이루어져 적당히 중간중간 저장되게(?) 구현하려는 의도도 있었다.</p>
<h3 id="데이터-분포-편향">데이터 분포 편향</h3>
<p><strong>⚠️ 이슈</strong>
내가 만든 &#39;KISS&#39; 데이터셋은 관용구가 ㄱ-ㅎ 순으로 정렬된 데이터셋이다.
시간관계상 7500개의 문장 중 앞부분을 차지하는 문장 1638개만으로 DRT 데이터셋을 구축해서 사용했더니 DRT데이터셋에 포함된 관용구는 ㄱ-ㄷ로 시작하는 것밖에 없었다.</p>
<p><strong>✅ 해결</strong>
재실험을 한다면 KISS 데이터를 랜덤하게 섞어서 DRT 데이터를 생성하거나,
생성한 데이터를 다시 랜덤하게 섞어서 SFT 를 할 예정이다.</p>
<h3 id="학습데이터와-다른-sft-출력값">학습데이터와 다른 SFT 출력값</h3>
<p><strong>⚠️ 이슈</strong>
우선 정석대로 HuggingFace 의 TRL 라이브러리를 사용해 SFT 를 시도했다.
<code>from trl import SFTTrainer</code> 를 사용하고 QLoRA 도 적용했는데, 모델 학습 결과를 테스트해 보니 포맷이 의도와 다르다!</p>
<p><strong>테스트 코드</strong></p>
<pre><code class="language-python">from transformers import AutoTokenizer, AutoModelForCausalLM
import torch

# ✅ 병합된 모델 로컬 경로에서 로드
model = AutoModelForCausalLM.from_pretrained(
    &quot;./qwen-sft-merged&quot;,
    torch_dtype=torch.float16,
    device_map=&quot;auto&quot;,
    trust_remote_code=True
)

# ✅ 토크나이저는 base 모델에서 가져오기
tokenizer = AutoTokenizer.from_pretrained(
    &quot;Qwen/Qwen2.5-7B-Instruct&quot;,
    trust_remote_code=True
)

# ✨ 테스트용 프롬프트
messages = [
    {&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;You are a philosopher skilled in deep thinking.&quot;},
    {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;Please translate the following text from Korean to English:\n식은 죽 먹기&quot;}
]

# 🧠 Qwen 스타일 prompt 생성
prompt = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True
)

# 💫 Tokenize &amp; GPU에 넣기
inputs = tokenizer(prompt, return_tensors=&quot;pt&quot;).to(model.device)

# 🚀 Inference (빠르게, 확실하게)
with torch.no_grad():
    generated_ids = model.generate(
        **inputs,
        max_new_tokens=128,
        do_sample=False,  # greedy
        pad_token_id=tokenizer.eos_token_id,
        use_cache=True
    )

# 📤 디코딩
output = tokenizer.decode(generated_ids[0], skip_special_tokens=True)

print(&quot;\n🧠 모델 출력:\n&quot;, output)
</code></pre>
<p><strong>결과</strong>
<code>&lt;thought&gt;</code>, <code>&lt;output&gt;</code> 태그가 없다. </p>
<pre><code> The phrase &quot;식은 죽 먹기&quot; is a Korean idiom that translates to English as &quot;eating rice that has already been cooked.&quot; This expression is used to describe a situation where someone is forced to do something they have already done before, or when someone is forced to repeat a task that they have already completed.

In more detail, the term &quot;식은&quot; (sigeun) means &quot;already&quot; or &quot;previously,&quot; and &quot;죽&quot; (juk) refers to &quot;rice.&quot; The word &quot;먹기&quot; (meogki) means &quot;to eat.&quot; So literally, it suggests eating rice that has already been prepared. 

This idiom conveys the idea of unnecessary repetition or doing something that has already been done, often with a hint of frustration or resignation. For example, if a manager assigns a task to an employee that the employee has already finished, you might say, &quot;It&#39;s like eating rice that has already been cooked,&quot; implying that the work was redundant.

Here is the translation:
&quot;It&#39;s like eating rice that has already been cooked.&quot; </code></pre><p><strong>✅ 해결</strong>
일단 구현 시간이 부족해서 Unsloth 프레임워크를 이용해 빠르게 학습했고, 결과도 잘 나왔지만 unsloth 를 이용했더니 왜 출력이 지정된 포맷대로 나오지 않는지 아직 확인하지 못했다.
학습 데이터가 너무 적고, 빠른 시간 안에 학습하기 위해 파라미터를 조절했기 때문에 기존 TRL SFTTrainer 를 사용해 학습했을 때 충분히 학습이 안 되어서일수도 있고, inference 할 때 내가 프롬프트를 잘못 줬을 수도 있다. 🥲
다시 실험할 때 코드 확인해봐야지..</p>
<h2 id="결론">결론</h2>
<ul>
<li>DRT 는 정해진 몇 단계를 거쳐 번역함으로써 번역의 품질을 높이는 효과가 있다.</li>
<li>DRT 모델을 직접 개발해 사용할 경우 데이터셋 구축 단계부터 적지 않은 비용과 시간이 투입되므로 우선 DRT가 과연 내가 하려는 작업에 적합한지 충분히 검토하고 나서 시도하는 것을 추천한다.</li>
<li>DRT 데이터셋 구축 단계에서 리소스를 줄이기 위해 일부 로직의 LLM을 토크나이저나 저렴한 LLM으로 교체할 수 있다.</li>
<li>DRT 데이터셋 구축 시간을 줄이기 위해 배치와 멀티쓰레드를 도입해 병렬처리를 적용할 수 있다.</li>
<li>대용량 데이터 처리 작업 과정에서 파일 i/o 가 적당한 빈도로 수행되도록 구현한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[LLM-MT 시리즈] (논문 리뷰) DRT: Deep Reasoning Translation via Long Chain-of-Thought]]></title>
            <link>https://velog.io/@judy_choi/LLM-MT-%EC%8B%9C%EB%A6%AC%EC%A6%88-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-DRT-Deep-Reasoning-Translation-via-Long-Chain-of-Thought</link>
            <guid>https://velog.io/@judy_choi/LLM-MT-%EC%8B%9C%EB%A6%AC%EC%A6%88-%EB%85%BC%EB%AC%B8-%EB%A6%AC%EB%B7%B0-DRT-Deep-Reasoning-Translation-via-Long-Chain-of-Thought</guid>
            <pubDate>Thu, 10 Jul 2025 13:20:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<ul>
<li>논문 : <a href="https://arxiv.org/pdf/2412.17498">DRT: Deep Reasoning Translation via Long Chain-of-Thought</a></li>
</ul>
</blockquote>
<ul>
<li>데이터셋 : <a href="https://github.com/krystalan/DRT">GitHub (코드 없이 데이터셋만 공개)</a></li>
<li>DRT 모델 : <a href="https://huggingface.co/Krystalan/DRT-7B">DRT-7B</a> / <a href="https://huggingface.co/Krystalan/DRT-8B">DRT-8B</a> / <a href="https://huggingface.co/Krystalan/DRT-14B">DRT-14B</a></li>
<li>논문 구현 : <a href="https://velog.io/@judy_choi/LLM-MT-%EC%8B%9C%EB%A6%AC%EC%A6%88-%EB%85%BC%EB%AC%B8-%EA%B5%AC%ED%98%84-DRT-Deep-Reasoning-Translation-via-Long-Chain-of-Thought-tdn1dbm8">[LLM-MT 시리즈] (논문 구현🪄) DRT: Deep Reasoning Translation via Long Chain-of-Thought</a></li>
</ul>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/7c81d290-de8a-433f-a17e-958bbc0d7bde/image.png" alt=""></p>
<h2 id="abstract">Abstract</h2>
<p>DRT: 문학 번역을 위한 긴 사고 과정 기반 기계 번역 모델</p>
<ul>
<li>추론 태스크의 <strong>긴 사고 과정(Long CoT)</strong>을 기계 번역에 적용한 DRT 모델 소개</li>
<li>직역이 어려운 문학 작품의 비유/은유 번역에 특화</li>
<li>3개 에이전트(번역가-조언자-평가자) 프레임워크로 반복 정제하여 Long CoT 번역 데이터 합성</li>
<li>합성된 수만 건 데이터로 Qwen2.5, Llama-3.1 기반 DRT 모델 훈련</li>
<li>기존 LLM 대비 우수한 번역 성능 달성</li>
</ul>
<h2 id="1-introduction">1 Introduction</h2>
<ul>
<li>O1 등 LLM의 Long thought 성공을 MT에 적용하려는 <strong>DRT</strong>를 제안합니다.</li>
<li>문학 번역처럼 <strong>직역이 어려운 경우</strong>에 Long thought가 필요하다고 봅니다.</li>
<li>이를 위해 비유/은유 문장을 <strong>LLM으로 발굴</strong>합니다.</li>
<li>Long thought 데이터 합성을 위해 <strong>Multi-Agent Framework</strong>를 개발했습니다.</li>
<li>Multi-Agent는 Translator, Advisor, Evaluator가 <strong>반복적으로 번역을 개선</strong>합니다.</li>
<li>합성 데이터를 바탕으로 DRT를 학습시켰고, <strong>바닐라/SFT 모델보다 우수함</strong>을 보였습니다.</li>
<li>DRT는 LLM이 <strong>번역 중 &#39;생각&#39;하도록</strong> 학습됩니다.</li>
</ul>
<h2 id="2-drt-data">2 DRT Data</h2>
<p>이 섹션에서는 논문에서 제안하는 DRT(Deep Reasoning Translation) 모델 학습을 위한 데이터를 어떻게 수집하고 만들었는지 자세히 설명합니다.</p>
<h3 id="21-literature-book-mining">2.1 Literature Book Mining</h3>
<ul>
<li>Long thought가 필요한 문장을 찾기 위해 Project Gutenberg의 <strong>영문 문학 서적 약 400권</strong>을 사용했습니다.</li>
<li>길이 기준(10~100 단어)으로 <strong>577.6K 문장</strong>을 추출했습니다.</li>
<li>Qwen2.5-72B를 사용하여 각 문장에 <strong>비유/은유가 있는지 (Q1)</strong> 판단했습니다.</li>
<li>비유/은유가 있다면, Qwen2.5-72B로 <strong>리터럴 번역 후 중국어 원어민이 이해하기 쉬운지 (Q2)</strong> 판단했습니다.</li>
<li><strong>Q1=&quot;yes&quot; &amp; Q2=&quot;no&quot;</strong>인 문장만 Long thought에 적합한 것으로 <strong>선별</strong>했습니다.</li>
<li>이 과정을 통해 <strong>63K개의 예비 문장</strong>을 수집했습니다.</li>
</ul>
<h3 id="22-multi-agent-framework">2.2 Multi-Agent Framework</h3>
<ul>
<li>각 선별된 문장(s)에 대해 Long thought 번역을 합성하기 위해 Multi-Agent Framework를 설계했습니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/7c81d290-de8a-433f-a17e-958bbc0d7bde/image.png" alt=""></p>
<ul>
<li>이 프레임워크는 Translator, Advisor, Evaluator 세 Agent (모두 Qwen2.5-72B 기반)로 구성됩니다.</li>
<li>합성 과정은 세 단계입니다:<ul>
<li><strong>(1) Word-level Translation:</strong> Translator가 문장 내 키워드 $W_{src} = {w_{src_1}, w_{src_2}, ..., w_{src_k}}$를 식별하고 문맥 고려하여 번역 $W_{tgt} = {w_{tgt_1}, w_{tgt_2}, ..., w_{tgt_k}}$을 제공합니다.</li>
<li><strong>(2) Preliminary Translation:</strong> Translator가 소스 문장 s와 키워드 쌍 $\langle W_{src}, W_{tgt} \rangle$을 바탕으로 예비 문장 번역 $t_0$을 생성합니다.</li>
<li><strong>(3) Translation Refine Loop:</strong> 반복 단계 k에서 Agent들이 협력하여 번역을 정제합니다.<ul>
<li>Advisor가 이전 번역 $t_{k-1}$을 평가하고 피드백 $f^{k-1}$을 제공합니다.</li>
<li>Evaluator가 $t_{k-1}$에 대해 점수 $s^{k-1}$을 부여합니다.</li>
<li>Translator가 $t_{k-1}$, $f^{k-1}$, $s^{k-1}$을 고려하여 새 번역 $t_k$를 생성합니다.</li>
</ul>
</li>
</ul>
</li>
<li>루프는 점수 $s^{k-1}$이 임계값에 도달하거나 최대 반복 횟수에 이르면 중단됩니다.</li>
</ul>
<h3 id="23-long-thought-reformulation">2.3 Long Thought Reformulation</h3>
<ul>
<li>Multi-agent 협업 후, 원시 Long thought 과정 $P(s)$를 얻습니다.</li>
<li>$P(s) : s \Rightarrow \langle W_{src}, W_{tgt} \rangle \Rightarrow \langle t_0, f^0, s^0 \rangle \Rightarrow \langle t_1, f^1, s^1 \rangle \Rightarrow ... \Rightarrow \langle t_m, f^m, s^m \rangle$ (수식 1)</li>
<li>점수 변화가 없는 단계($s_i = s_{i-1}$)는 제거합니다.</li>
<li>유효한 단계만 남은 과정 $P&#39;(s)$를 얻습니다.</li>
<li>$P&#39;(s) : s \Rightarrow \langle W_{src}, W_{tgt} \rangle \Rightarrow \langle t_{r_1}, f^{r_1}, s^{r_1} \rangle \Rightarrow ... \Rightarrow \langle t_{r_n}, f^{r_n}, s^{r_n} \rangle$ (수식 2), 여기서 $1 \leq r_1 &lt; r_2 &lt; ... &lt; r_n \leq m$ 입니다.</li>
<li>남은 단계가 3개 미만($n &lt; 3$)인 샘플은 폐기합니다.</li>
<li>GPT-4o를 사용하여 $P&#39;(s)$를 자기 성찰(self-reflection) 형태로 재구성합니다.</li>
<li>최종 번역은 가장 높은 점수 $s_{r_j}$를 받은 번역이며, 마지막 번역($t_{r_n}$)이 아닐 수 있습니다.</li>
<li>최종적으로 총 22,264개의 Long thought MT 샘플을 얻었습니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/18a48f45-2225-49fa-a3a5-1f3316653d93/image.png" alt=""></p>
<h3 id="24-data-statistics-and-data-analyses">2.4 Data Statistics and Data Analyses</h3>
<ul>
<li>총 22,264개의 합성된 Long thought MT 샘플을 수집했습니다.</li>
<li>데이터를 학습(19,264), 검증(1,000), 테스트(2,000) 세트로 분할했습니다.</li>
<li>데이터의 평균 <strong>thought 토큰 길이</strong>는 <strong>500+ 토큰</strong>으로, 긴 사고 과정을 보여줍니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/233b02f1-f739-4ea7-bef8-26b65c20c0a0/image.png" alt=""></p>
<ul>
<li>Refinement Loop 단계는 <strong>3단계에서 8단계</strong>까지 분포하며, <strong>3단계가 가장 많습니다 (73.22%)</strong>.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/e8025314-64d7-4370-b208-ba3263556f1f/image.png" alt=""></p>
<ul>
<li><p>Refinement가 진행될수록 <strong>수정량(Edit Distance)이 점진적으로 감소</strong>합니다.</p>
</li>
<li><p>Refinement 단계가 증가할수록 <strong>평균 평가 점수가 일반적으로 상승</strong>하며, 개선 효과를 보여줍니다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/16fc7782-2b13-4d90-ab14-f06ffafa1e56/image.png" alt=""></p>
<h3 id="25-quality-analyses">2.5 Quality Analyses</h3>
<ul>
<li><p><strong>Evaluator Agent의 효과:</strong></p>
<ul>
<li>합성 과정에 사용된 Evaluator Agent(Qwen2.5-72B)의 신뢰성을 검증했습니다.</li>
<li>인간 평가와 비교하여 <strong>Evaluator Agent의 평가 정확도가 92.5%</strong>로 높았습니다.</li>
<li>CometKiwi(56.0%)보다 훨씬 정확하며, GPT-4o Evaluator(93.5%)와 유사했습니다.</li>
<li>비용 효율성을 고려하여 Qwen2.5-72B를 Evaluator로 사용했습니다.</li>
</ul>
</li>
<li><p><strong>번역 품질:</strong></p>
<ul>
<li>Evaluator Agent의 효과성과 최종 번역의 높은 평균 점수(약 90점)를 바탕으로 <strong>합성된 데이터의 번역 품질이 높다</strong>고 판단했습니다.</li>
<li>Evaluator 기준에서 90점은 &#39;Excellent translations&#39;에 해당합니다.</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/0a6df38d-3346-4d49-833d-9d73c69729b1/image.png" alt=""></p>
<h2 id="3-experiments">3 Experiments</h2>
<h3 id="31-experimental-setups">3.1 Experimental Setups</h3>
<ul>
<li><strong>평가 지표:</strong><ul>
<li>전통적 지표: BLEU, CometKiwi, CometScore를 사용했습니다.</li>
<li>LLM 기반 지표: GPT-4o를 사용한 Reference-Based (GRB) 및 Reference-Free (GRF) 평가를 도입했습니다.</li>
<li>Evaluator Agent 점수(GEA)도 평가에 활용했습니다 (GPT-4o Evaluator 정확도 검증 기반).</li>
<li>GRB, GRF, GEA는 비용 문제로 400개 샘플에 대해 평가했습니다.</li>
</ul>
</li>
<li><strong>Backbone 모델:</strong><ul>
<li>DRT의 백본으로 Llama-3.1-8B-Instruct, Qwen2.5-7B-Instruct, Qwen2.5-14B-Instruct를 사용했습니다.</li>
</ul>
</li>
<li><strong>구현 상세:</strong><ul>
<li>Llama-Factory 툴킷으로 Instruct-tuning 했습니다.</li>
<li>8x NVIDIA A100 GPU (40G) 사용, DeepSpeed ZeRO-3 최적화를 적용했습니다.</li>
<li>추론 시 vLLM 툴킷으로 가속화했습니다.</li>
<li>결과는 3회 실행의 평균입니다.</li>
</ul>
</li>
</ul>
<h3 id="32-comparison-models">3.2 Comparison Models</h3>
<ul>
<li><strong>Vanilla LLMs:</strong><ul>
<li>DRT의 백본으로 사용된 기본 Instruct 모델들 (Llama-3.1, Qwen2.5)을 포함합니다.</li>
<li>Marco-o1, QwQ, DeepSeek-R1 Distill 시리즈 등 <strong>다른 O1-like LLM들</strong>도 비교 대상으로 포함했습니다.</li>
</ul>
</li>
<li><strong>SFT LLMs (w/o CoT):</strong><ul>
<li>저희 DRT 데이터의 <strong>소스 문장과 최종 번역 결과 쌍만 사용하여 Fine-tuning</strong>한 모델들입니다.</li>
<li>Long thought 과정 없이 단순 SFT만 수행한 모델들로, DRT의 Long thought 학습 효과를 비교하기 위한 <strong>핵심 Baseline</strong>입니다.</li>
<li>Llama-3.1-8B-SFT, Qwen2.5-7B-SFT, Qwen2.5-14B-SFT 모델이 해당됩니다.</li>
</ul>
</li>
</ul>
<p>네, 논문 3.3절 Main Results 부분을 최대한 간결하게 하이픈(-)으로 구분하여 요약해 드리겠습니다. 이 절은 모델들의 실제 실험 성능을 비교한 결과입니다.</p>
<h3 id="33-main-results">3.3 Main Results</h3>
<ul>
<li><strong>SFT (w/o CoT) vs. Vanilla LLMs:</strong> 저희 합성 데이터의 소스-타겟 쌍으로만 학습한 SFT 모델이 Vanilla LLMs보다 <strong>모든 지표에서 유의미하게 우수</strong>했습니다. (합성 데이터 품질 우수성 입증)</li>
<li><strong>DRT vs. Vanilla LLMs:</strong> Long thought 데이터로 학습한 DRT 모델들도 해당 백본의 Vanilla LLMs보다 <strong>모든 지표에서 유의미하게 우수</strong>했습니다. (DRT 학습 효과 입증)</li>
<li><strong>DRT vs. SFT (w/o CoT):</strong> Llama-8B 및 Qwen-7B 백본에서는 DRT 모델이 해당 SFT 모델보다 <strong>모든 지표에서 더 나은 성능</strong>을 보였습니다. (Long CoT 학습의 명확한 기여)</li>
<li><strong>DRT-14B vs. Qwen2.5-14B-SFT:</strong> DRT-14B는 <strong>LLM 기반/Reference-free 지표 (GEA, GRF, CometKiwi, GRB)에서 우수</strong>했으나, <strong>BLEU 및 CometScore에서는 약간 낮은 성능</strong>을 보였습니다. (CoT 학습이 deeper quality에 집중함을 시사)</li>
<li>DRT-14B는 QwQ-32B-preview 및 DeepSeek-R1-Distill-Qwen-32B 등 <strong>다른 대형 O1-like 모델들보다도 우수</strong>했습니다.</li>
<li>DRT 모델은 상용 LLM (GPT-4o, o1-preview)과 비교해도 <strong>경쟁력 있는 성능</strong>을 보였습니다 (Appendix E).</li>
</ul>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/f4f00c5f-8343-4ccf-8fda-746c532752e9/image.png" alt=""></p>
<h3 id="34-human-evaluation">3.4 Human Evaluation</h3>
<ul>
<li>DRT-14B와 주요 Baseline 모델(Qwen2.5-14B-Instruct, QwQ-32B-Preview, Qwen2.5-14B-SFT)에 대해 <strong>인간 평가</strong>를 수행했습니다.</li>
<li>200개 샘플에 대해 세 명의 숙련된 평가자가 평가했습니다.</li>
<li>평가 기준은 <strong>Fluency (유창성), Semantic Accuracy (의미 정확성), Literary Quality (문학적 품질)</strong> 세 가지입니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/2ce7c705-7e9e-4085-b2db-b074a9bb9abf/image.png" alt=""></p>
<ul>
<li><strong>Best-Worst Scaling</strong> 방법으로 평가 점수(-1 ~ 1)를 산출했습니다.</li>
<li>DRT-14B는 모든 측면에서 다른 Baseline들보다 <strong>유의미하게 우수</strong>했습니다.</li>
<li>특히 <strong>Literary Quality 측면에서 가장 큰 우위</strong>를 보였습니다. (DRT가 문학적 뉘앙스를 잘 살림)</li>
<li>평가자 간 <strong>일치도(Fleiss’ Kappa)</strong>는 0.69 ~ 0.85로 <strong>Good agreement</strong> 수준이었습니다.</li>
</ul>
<h3 id="35-inference-time-analysis">3.5 Inference Time Analysis</h3>
<ul>
<li>vLLM을 사용하여 Test set에 대한 모델별 <strong>추론 시간</strong>을 측정했습니다.</li>
<li>DRT 모델들의 평균 추론 시간은 SFT (w/o CoT) 모델들보다 <strong>현저히 높았습니다</strong>.</li>
<li>DRT 모델들은 SFT 모델보다 <strong>약 11.9~13.9배 더 많은 시간</strong>이 소요되었습니다.</li>
<li>이는 DRT 모델이 최종 번역 외에 <strong>Long thought 과정을 추가로 생성</strong>해야 하기 때문입니다.</li>
<li>이 결과는 DRT 모델이 <strong>높은 실시간 응답성이 요구되는 시나리오에는 적합하지 않을 수 있음</strong>을 시사합니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/1b4bb043-30b6-412d-a7f0-084ee5720190/image.png" alt=""></p>
<h3 id="36-case-study">3.6 Case Study</h3>
<ul>
<li>Qwen2.5-14B-Instruct, QwQ-32B-Preview, Qwen2.5-14B-SFT, DRT-14B 모델들의 실제 문학 번역 <strong>사례를 제시하고 비교</strong>했습니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/a8a4bf82-ef0e-4e6e-8c1e-9c896740d4e2/image.png" alt=""></p>
<ul>
<li>DRT-14B의 번역이 Long thought 덕분에 <strong>중국어 관습에 더 잘 맞고 문학적 품질이 뛰어남</strong>을 보여줍니다.</li>
<li>다른 LLM들도 일부 좋은 번역을 보여주었지만, DRT-14B의 <strong>전반적인 품질이 우수</strong>했습니다.</li>
<li>사례 연구는 DRT가 Long thought를 통해 <strong>바닐라 LLM의 잠재된 문학 번역 능력을 활성화</strong>시킬 수 있음을 시사합니다.</li>
</ul>
<h2 id="4-related-work">4 Related Work</h2>
<ul>
<li><strong>O1-like LLMs:</strong><ul>
<li>최근 LLM들이 Long thought를 활용하여 수학, 코딩 등 <strong>추론 태스크에서 뛰어난 성능</strong>을 보였습니다.</li>
<li>OpenAI O1 이후 <strong>journey learning, 데이터 증류, MCTS</strong> 등의 기법으로 O1을 재현하려는 노력이 있었습니다 (예: Marco-o1, DeepSeek-R1, Kimi K1.5).</li>
<li>DRT는 이러한 O1-like 모델의 Long thought 개념을 MT에 적용합니다.</li>
</ul>
</li>
<li><strong>Literature Translation:</strong><ul>
<li>표준 MT(뉴스 등)와 달리, 문학 번역은 <strong>문화적 요소, 비유/은유 포함</strong>으로 인해 어렵고 단어 수준 이상의 등가성이 필요합니다.</li>
<li><strong>자동 평가 지표(BLEU, COMET 등)로는 평가가 어렵고 신뢰할 수 없어서</strong>, 주로 인간 평가에 의존합니다.</li>
<li>이전 연구는 소규모 시도가 많았으며, 최근 LLM을 활용하거나 문서 수준 문맥을 고려하는 연구가 진행되었습니다 (예: Par3 벤치마크).</li>
<li>DRT는 이 어려운 문학 번역 도메인에 Long thought 방식을 도입합니다.</li>
</ul>
</li>
</ul>
<h2 id="5-conclusion">5 Conclusion</h2>
<ul>
<li>이 논문은 LLM의 Long thought를 기계 번역에 적용한 <strong>DRT</strong>를 제안합니다.</li>
<li>DRT 데이터는 Multi-Agent Framework와 GPT-4o Reformulation을 통해 합성되었습니다.</li>
<li>Multi-Agent Framework는 번역가, 조언자, 평가자가 반복적으로 협력하여 Long thought 과정을 만듭니다.</li>
<li>적합한 소스 문장은 문학 작품에서 비유/은유 문장을 마이닝하여 수집했습니다.</li>
<li>합성 데이터로 DRT 모델을 훈련시켰습니다.</li>
<li>자동 평가 및 인간 평가, 사례 연구를 통해 DRT 모델의 <strong>우수성</strong>을 입증했습니다.</li>
<li>DRT는 LLM이 기계 번역 과정에서 <strong>&#39;생각하는 방법&#39;을 학습</strong>하게 함을 보여줍니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OSSCA] 2025 참여형 : 합격 후기]]></title>
            <link>https://velog.io/@judy_choi/OSSCA-2025-%EC%B0%B8%EC%97%AC%ED%98%95-%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@judy_choi/OSSCA-2025-%EC%B0%B8%EC%97%AC%ED%98%95-%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Thu, 10 Jul 2025 04:03:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>OSSCA &#39;체험형&#39; 프로그램 멘티로 선발되어 지원서 작성 경험을 공유합니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/9dbbdec9-70bf-4740-abb4-c07c41fa03b2/image.png" alt=""></p>
<p>최근 OSSCA 체험형 1차 프로그램을 성공적으로 마쳤고,
<a href="https://velog.io/@judy_choi/OSSCA-2025-%EC%B2%B4%ED%97%98%ED%98%95-1%EC%B0%A8-%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0">[OSSCA] 2025 체험형-1차 : 합격 후기</a>
너무 좋은 경험이었기에 체험형 다음 단계인 참여형 프로그램에 지원했다!</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/ae6fcf5f-db28-4221-8269-8ce1cd095587/image.png" alt=""></p>
<p>홈페이지 : <a href="https://www.oss.kr/contribution_academy">https://www.oss.kr/contribution_academy</a></p>
<blockquote>
<p>단계별 오픈소스 컨트리뷰션 아카데미 프로그램</p>
</blockquote>
<ul>
<li>(입문형 과정) 대학(원)생 대상 공개SW 소개 및 공개SW 프로젝트 소개 등 공개SW 인식 확산을 위한 단기 특강 프로그램</li>
<li>(체험형 과정) 공개SW 개발 참여 및 기여를 위한 협업 도구 교육 및 주요 분야별 프로젝트 실습 및 체험 프로그램</li>
<li><strong>(참여형 과정) 멘토·멘티가 한팀을 이루어 산업에서 활용 가능한 오픈소스 프로젝트 기능 개선 및 다양한 기여를 직접 경험을 통한 역량 강화 프로그램</strong></li>
</ul>
<p>이번 과정은 &#39;참여형&#39; 과정으로, 말 그대로 오픈소스에 실제로 기여하는 것이 목적이다.
그만큼 체험형에 비해 참여자들의 기술 수준도 높고, 2배 더 긴 기간동안 진행된다.</p>
<p>이번에는 내 본업인 AI Researcher 로 돌아가고자 &#39;Hugging Face OSS&#39; 프로그램에 지원했고, 열심히 지원서를 쓴 덕분인지 합격했다! 🎉</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/48dc7014-dd85-42da-960c-9d71533cfef9/image.png" alt=""></p>
<h2 id="hugging-face-oss">Hugging Face OSS?</h2>
<p>내가 참여하는 Hugging Face OSS 프로그램의 목표는 다음과 같다.</p>
<ol>
<li><p>Hugging Face 라이브러리에 코드 기여하기
Hugging Face의 오픈소스 라이브러리에 직접 기여하는 것을 목표로 합니다. 기여 대상은 transformers, smolagents, diffusers 등 핵심 라이브러리이며, 기존 이슈 해결, 문서 개선, 기능 추가 등을 중심으로 진행됩니다. 이를 위해 라이브러리 구조 분석과 로컬 개발 환경을 설정하며, 한국어 관련 모델이나 기능에서의 개선점을 중심으로 기여 방향을 구체화합니다.</p>
</li>
<li><p>Hugging Face 공식 리소스에 기여하기
Hugging Face에서는 Model, Datasets, Space, Notebooks with examples과 같은 주제로 ML개발자들이 머신러닝 생태계에 기여할 수 있는 다양한 공간이 존재합니다. 관심 분야를 논의하고 주제를 선정하여 Hugging Face 생태계에 적극적으로 기여합니다.</p>
</li>
<li><p>Hugging Face 기술문서 한글화
Hugging Face의 공식 문서를 한글화하여 국내 사용자들이 더 쉽게 접근할 수 있도록 합니다. Hugging Face에서 제공하는 기능 중 관심있는 분야의 문서를 한글로 번역합니다</p>
</li>
</ol>
<hr>
<h2 id="지원서-문항">지원서 문항</h2>
<p>지원서 문항은 크게 1지망, 2지망 프로젝트를 선택하고, 각각의 지망 프로젝트 모두 대답해야 할 질문은 다음 3가지로 동일하다.</p>
<ul>
<li>자기소개<ul>
<li>ex. 관심 분야, 학력, 전공, 프로젝트에 관심을 갖게 된 계기 등</li>
</ul>
</li>
<li>지원동기</li>
<li>프로젝트 개발 경험 (있으면 작성, 없으면 PASS)</li>
</ul>
<p>나는 이 프로그램에 너무너무 참여하고 싶었기 때문에 정말 지원서를 열심히, 내 경험과 경력을 정말 영혼까지 끌어모아서 작성했다.
특히 이번 Hugging Face OSS 프로그램의 멘토님들이 대부분 자연어처리를 하셔서 같은 자연어처리 연구자인 나로서는 정말 놓치고 싶지 않은 기회였다.</p>
<h3 id="자기소개">자기소개</h3>
<p>다음 4가지 소주제를 잡고, 각각의 소주제에 대해 나의 경험과 경력을 근거로 들어 자기소개를 작성했다.
단순히 내가 이 프로그램에 &#39;참여하고 싶다&#39;, &#39;할 수 있다&#39; 라고 주장하는 것이 아니라
명확하게 경험/경력을 나열해 주장에 대한 근거를 제시하려 노력했다.</p>
<ul>
<li>기계번역부터 챗봇까지, NLP연구 및 개발 경험<ul>
<li>기계번역 논문 발표, 기계번역 스타트업 근무 경력 제시</li>
<li>음성인식, 챗봇 등 추가 경력 제시</li>
<li>최근 참여한 스터디 중 LLM(SFT포함), RAG 를 사용한 팀 프로젝트 경험 제시</li>
</ul>
</li>
<li>오픈소스 기여 경험과 협업 능력<ul>
<li>최근 OSSCA 체험형 프로그램을 수료했고, 실제로 오픈소스에 기여한 경험 제시</li>
<li>3년간 9개의 팀 프로젝트를 수행하고, 부트캠프에서 각종 리더를 맡아 협업한 경험 제시</li>
</ul>
</li>
<li>해외 ICT 봉사 경험으로 검증된 글로벌 소통 역량<ul>
<li>3년 동안 매 해 여름마다 ICT 해외봉사단에 참여해 영어로 강의한 경험 제시<ul>
<li>체험형 프로그램 과정 중 오픈소스의 메인테이너와 영어로 소통했는데, 이번에도 영어 소통이 필요할 것 같아서 특별히 기술적으로 영어 소통이 가능한 점을 어필</li>
</ul>
</li>
</ul>
</li>
<li>끊임없는 학습과 기술 공유에 대한 열정<ul>
<li>글또 활동을 통해 총 30편의 기술 포스팅을 작성하고, 세미나에서 발표한 경험 제시<ul>
<li>스터디란 결국 매주 자신이 공부해 온 것을 문서로 정리해서 스터디원들에게 공유하는 활동.</li>
<li>이번 프로그램에는 특별히 &#39;기술문서 한글화&#39; 가 있었기 때문에 나의 문서화 역량을 어필해야겠다고 판단함</li>
<li>그동안의 스터디 참여 경험을 복기할 때, 모두 시작할 때에는 열정적이지만 일부 스터디는 점차 출석률이 저조해지는 경우가 있었고, 멘토님들도 분명 이 점을 고려하셨을 것이라 생각해 나는 꾸준히 학습하고 학습 내용을 공유한다는 점을 어필함.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="✅-지원동기-이-포스팅의-핵심">✅ 지원동기 (이 포스팅의 핵심!)</h3>
<blockquote>
<p>&#39;나 다시 NLP 연구자 할래요&#39;</p>
</blockquote>
<p>지원동기를 한 줄로 요약하면 위와 같고,
가급적 진솔하게 내가 어떠한 갈증이 있기 때문에 이를 해소하기 위해 지원하는 것임을 &#39;긍정적으로&#39; 서술했고,
또 나의 관련 경험을 소개하는 동시에 경험 과정에서 어떤 어려움이나 아쉬움이 있었는지 소개하고, 프로젝트를 통해 이를 해결하고자 한다는 능동적인 뉘앙스로 작성했다.</p>
<ul>
<li>AI 연구자로서의 전문성 심화<ul>
<li>최근 다수의 AI 서비스를 개발했으나 이는 나의 본업인 &#39;AI 연구자&#39; 와는 거리가 있음.</li>
<li>직접 모델 구조를 분석하고 다양한 리소스에 기여하면 연구자로서의 시야를 넓힐 수 있을 것 같다!</li>
</ul>
</li>
<li>한국어 NLP 생태계에 기여하기<ul>
<li>현업에서 서비스를 개발할 때, 한국어 리소스가 적어서 어려움을 겪은 경험 제시</li>
<li>프로그램 필수(기본) 퀘스트인 &#39;기술문서 한글화&#39; 에 참여하고 싶다는 점을 어필<ul>
<li>나의 꿈인 &#39;언어의 장벽을 넘어서기&#39; 를 &#39;기술문서 한글화&#39; 와 연관지어서 나의 꿈(가치관) = 프로그램 목표임을 어필</li>
</ul>
</li>
<li>글또와 기술 블로그 운영을 통해 축적한 문서화 경험 + ICT 해외봉사단 활동으로 영어로 AI 교육자료를 만들고 강의한 경험을 강점으로 제시</li>
</ul>
</li>
</ul>
<h3 id="프로젝트-개발-경험">프로젝트 개발 경험</h3>
<p>회사에서 수행한 주요 프로젝트와 지금까지 참여한 사이드 프로젝트 중 가치 있는 프로젝트 4~5개를 프로젝트명만 적어 냈다.
이는 프로젝트 개발 경험 문항은 글자 수 제한이 200자 정도로 상대적으로 적게 걸려 있었기 때문이다.
대신 대외활동 경력을 따로 스프레드시트에 리스트업해두고 있었는데, 이 스프레드시트 URL 을 URL 단축 서비스를 이용해 글자 수를 줄인 뒤 덧붙여서 최종 제출했다.</p>
<hr>
<h2 id="🍯-tip-스터디-지원서-작성-요령">🍯 Tip: 스터디 지원서 작성 요령</h2>
<p>최근 1년 동안 지원한 모든 스터디/프로젝트에 합격하고 있는데, 나름대로의 지원서 작성 요령은 다음과 같다.</p>
<ul>
<li>내 경험/경력을 살려서 호소하고 주장에 대한 근거로 제시한다<ul>
<li>단순히 &#39;잘 모르지만 열심히 하겠습니다&#39; 라고 절대 쓰지 않는다</li>
</ul>
</li>
<li>나의 현재 상황을 설명하고, 이러한 상황이라 이 스터디가 나에게 꼭 필요하다는 점을 어필<ul>
<li>목표를 분명하게 어필할 때도 있음 (스터디에서 잘 배워서 실무에 도입해 성과를 내겠다/이직에 성공하겠다)</li>
</ul>
</li>
<li>내가 팀에 할 수 있는 기여를 어필한다<ul>
<li>정보 공유를 잘 한다던지 정리를 잘 한다던지 아니면 미리 조금 공부를 한 주제여서 다른 팀원의 초반 적응을 도울 수 있다던지 등등</li>
</ul>
</li>
<li>나는 스터디 중 탈주하지 않을 지원자라는 확신을 심어 준다<ul>
<li>많은 스터디 리더들이 한 번쯤은 자신이 리딩하는 스터디 참여율이 저조해지는 경험을 해 보셨을 것</li>
<li>그렇기 때문에 끝까지 포기하지 않을 사람을 선호하는 리더분들이 계심</li>
<li>실제로 나는 최근 3년간 9개의 스터디에 참여해 모두 완주했다는 경험을 근거로 제시해 리더분들의 우려를 덜어 드리려고 노력함</li>
</ul>
</li>
<li><strong><U>이 모든 내용을 최대한 진솔하게, 긍정적이고 능동적으로 보이도록 작성한다</U></strong></li>
</ul>
<hr>
<h2 id="마지막-한마디">마지막 한마디</h2>
<p>자 이제 오랜만에 다시 본업인 자연어처리 연구자로 돌아가 보자.. 🙏</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[K-Devcon] 고투런 2기 - AI 여행 에이전트 프로젝트 후기]]></title>
            <link>https://velog.io/@judy_choi/K-Devcon-GoToLearn-%EA%B3%A0%ED%88%AC%EB%9F%B0-2%EA%B8%B0-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@judy_choi/K-Devcon-GoToLearn-%EA%B3%A0%ED%88%AC%EB%9F%B0-2%EA%B8%B0-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Wed, 09 Jul 2025 17:04:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>스터디명 : 20년차 AI 엔지니어에게 배우는 AI 에이전트의 모든 것
결과공유회 발표자료 : <a href="https://drive.google.com/file/d/1u3ZrzKwY81bnwsu6AY1PtvOXS4rWFAA8/view?usp=sharing">Travel Agent : 나만의 여행 비서 만들기</a>
GitHub : <a href="https://github.com/Judy-Choi/GoToLearn-AI_Agent">GoToLearn-AI_Agent</a></p>
</blockquote>
<h2 id="스터디-개요">스터디 개요</h2>
<h3 id="진행방식">진행방식</h3>
<ul>
<li>팀 구성 : 정유선 멘토님 👩‍🏫 + 멘티 4인</li>
<li>주기 및 진행 방식 : 매주 토요일마다 온라인 스터디 진행</li>
<li>진행 기간 : 3/15(OT) - 5/17(데브콘 밋업)</li>
</ul>
<h3 id="활동-내역">활동 내역</h3>
<ul>
<li>AI Agent 개념 및 프레임워크 (LangGraph, CrewAI) 이해</li>
<li>각자 주도적으로 프로젝트 주제 선정 &amp; 구현</li>
<li>개발 진행상황 및 이슈는 함께 공유</li>
<li>20년차 대선배님의 아낌없는 실무 노하우 전수 &amp; 피드백</li>
</ul>
<hr>
<h2 id="프로젝트-내용-및-시행착오">프로젝트 내용 및 시행착오</h2>
<blockquote>
<p>한줄요약 : 벗어날 수 없는 OpenAI 유니버스 <del>늪에 빠져버렸다..</del>
두줄요약 : 효율적인 Agent 설계와 활용의 중요성 체감</p>
</blockquote>
<h3 id="step-0--crew-ai">Step 0 : Crew AI</h3>
<blockquote>
<p>사용 시 OpenAI 키 필수 → 빠른 포기 </p>
</blockquote>
<h4 id="⚠️-한계">⚠️ 한계</h4>
<p>CrewAI는 내부적으로 LangChain을 활용하여 다양한 LLM을 호출할 수 있지만,
Crew() 객체 생성 시점에 OpenAI API 키가 무조건 설정되어 있어야만 실행된다.</p>
<p>즉, 실제로 OpenRouter, DeepSeek 등 다른 모델을 쓰고 있어도,
다음과 같은 코드에서 OpenAI 키가 없으면 에러가 난다!</p>
<pre><code class="language-python">crew = Crew(
  agents=[...],
  tasks=[...],
  ...
)</code></pre>
<p>이는 내부에 키 유무를 강제 체크하는 validation 로직이 있기 때문이다.</p>
<p><strong>✅ 해결 방법</strong>
<strong>1. 형식만 맞는 가짜 OpenAI 키 설정 (가장 간단한 방식)</strong>
실제로 OpenAI API를 사용하지 않더라도 환경 변수에 형식만 맞는 더미 키를 설정하면 CrewAI가 통과시킨다.</p>
<pre><code class="language-bash">export OPENAI_API_KEY=&quot;sk-fake-key-just-for-crewai&quot;</code></pre>
<p>또는 Python 코드 내에서 반드시 <code>sk-</code> 로 시작해야 하며, 그렇지 않으면 validation에서 다시 막힌다.</p>
<pre><code class="language-python">import os
os.environ[&quot;OPENAI_API_KEY&quot;] = &quot;sk-fake-key-just-for-crewai&quot;</code></pre>
<p><strong>2. 실제 OpenAI 키를 설정하되, 사용은 하지 않기</strong>
OpenAI 키가 있긴 하지만 실제로는 다른 모델(OpenRouter 등)을 사용하고자 할 경우 키를 설정만 해두고 LLM 설정만 다르게 하면 되는데,
CrewAI는 키의 존재 여부만 확인하며, 실제로 해당 키를 사용했는지는 따지지 않기 때문!</p>
<p>이렇게 우회하는 방법이 있지만..
나는 상용화/온프레미스 상황까지 고려해 설계하고 싶었기 때문에
OpenAI 키가 필수인 CrewAI 는 적합하지 않은 프레임워크라고 판단했다.
(지금 생각해 보니 어차피 OpenAI 유니버스를 벗어날 수 없는 이상
더미 OpenAI 키를 설정해서 그냥 CrewAI 로 개발하는 것도 좋았을 것 같다 😂)</p>
<h3 id="step-1--agent-기초">Step 1 : Agent 기초</h3>
<blockquote>
<p>Mock Data 와 선형 구조로 구현한 간단한 Agent</p>
</blockquote>
<p>앞선 이유로 CrewAI 대신 LangGraph 를 사용하기로 마음먹었고,
Mock Data 를 사용해 항공편을 예약하는 아주 간단한 선형 예제를 만들어 보았다.</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/4f00c5a5-4844-4953-8828-4b6049ecffbc/image.png" alt=""></p>
<ul>
<li><code>ask_dates</code> : 여행 시작일과 종료일을 입력받아 특정 날짜 형식으로 변환</li>
<li><code>ask_city</code> : 여행할 도시를 입력받음</li>
<li><code>search_flights</code> : 입력받은 날짜와 도시에 운행하는 항공편을 탐색</li>
</ul>
<h4 id="⚠️-한계-1">⚠️ 한계</h4>
<p>이렇게 아주 간단한 항공편 예약 Agent 를 만들었지만, 이대로 Agent 구축 프로젝트를 진행할 수는 없다고 판단한 이유는 다음과 같았다.</p>
<ul>
<li><p>이 설계는 너무 단순한 선형 예제이므로 Agent 를 사용하는 의미가 없음</p>
<ul>
<li>Agent 가 아니라 LangChain 만으로도 충분히 더 간결한 코드와 구조로 구현 가능</li>
<li>&#39;복잡한 작업&#39; 을 처리할 수 있는 Agent 의 강점을 살릴 수 없음</li>
</ul>
</li>
<li><p>이미 항공편 예약 프로세스만으로 기획과 구현 리소스가 큼</p>
<ul>
<li>항공편 예약은 단일 작업처럼 보이지만, 사실상 복수의 하위 태스크로 구성된 복합적 작업임</li>
<li>날짜 인식, 도시 인식 (NER, Parsing)</li>
<li>사용자 의도 분기 (왕복 vs 편도, 직항 선호 여부 등)</li>
<li>외부 API 호출 (예: Skyscanner, Kayak, Amadeus)</li>
<li>최종 선택지 제안 및 확인</li>
</ul>
</li>
</ul>
<p>이대로라면 항공편 예약 이외에 &#39;여행 비서&#39; 로서의 기능을 빠짐없이 수행하기 위해 들여야 하는 리소스가 너무 크기 때문에 스터디 기한 내에 개발하기 어려울 것 같고,
특히나 이러한 선형 설계만으로는 스터디의 목적인 &#39;Agent&#39; 를 충분히 활용해 보기가 어려울 것 같다! </p>
<h3 id="step-2--multi-agent">Step 2 : Multi-Agent</h3>
<blockquote>
<p>Router, Fan-out 을 활용하는 Multi Agent 구현</p>
</blockquote>
<p>조금 더 Agent 답게, 복잡한 작업을 수행할 수 있도록 구조를 바꿔 보았다.
Teddy 님의 <a href="https://github.com/teddynote-lab/LangGraph-HandsOn">LangGraph-HandsOn</a> 코드와 설명을 참고하여 Router, Fan-out 적용했다.</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/f03e0943-fb44-42ed-932f-8f359d1c72cc/image.png" alt="">   </p>
<p><strong>Routing</strong></p>
<ul>
<li><p>입력 쿼리나 상태에 따라 적절한 처리 경로나 구성 요소로 요청을 전달하는 메커니즘</p>
</li>
<li><p>특정 작업에 가장 적합한 모델이나 도구를 선택하고, 복잡한 워크플로우를 관리하며, 비용과 성능 균형을 최적화하는 데 필수적 </p>
</li>
<li><p>Agent 의 경우</p>
<ul>
<li>도구 선택을 하는 방식으로 라우팅</li>
<li>따라서, 도구에 대한 description 이 상세하게 작성되어야 합니다.</li>
</ul>
</li>
<li><p>LLM.with_structured_output 의 경우</p>
<ul>
<li>Function Calling 을 사용하는 방식으로 라우팅</li>
</ul>
</li>
</ul>
<p>이번 설계에서는 질문 라우팅 노드를 구현하였으며, 
사용자의 질문에 대해 <code>question_router</code> 를 호출하여 다음 2가지 중 적절한 Agent 로 라우팅한다.</p>
<ul>
<li><code>ask_city</code>: 여행할 도시 설정</li>
<li><code>search_flights</code>: 항공편 검색
적절한 Agent 를 수행한 이후에는 <code>generate</code> 에서 Agent 의 결과를 답변으로 생성한다.</li>
</ul>
<p><strong>Fan-Out / Fan-In</strong></p>
<ul>
<li>Fan-Out<ul>
<li>복잡한 LLM 워크플로우 관리를 위한 중요한 패턴</li>
<li>단일 입력을 여러 병렬 작업으로 분배하는 패턴으로, 하나의 프롬프트나 쿼리를 여러 LLM, 도구, 또는 처리 단계로 동시에 전송하여 다양한 관점이나 접근 방식을 얻을 수 있게 함</li>
<li>복잡한 문제를 더 작고 전문화된 하위 작업으로 분할하거나 동일한 작업에 대해 여러 모델의 결과를 비교할 때 유용함</li>
</ul>
</li>
<li>Fan-In<ul>
<li>Fan-out의 역과정으로, 여러 병렬 작업의 결과를 단일 출력이나 다음 단계로 통합</li>
<li>이는 다양한 모델이나 도구에서 생성된 결과를 종합하여 더 완전하고 정확한 최종 응답을 만들거나 여러 에이전트의 작업을 조정할 때 사용됨</li>
</ul>
</li>
</ul>
<p>이렇게 우선 사용자의 요청으로부터 의도(intent) 를 파악하고,
적절한 Agent 로 분기시켜(Routing) 요청을 처리할 수 있게 구현했다.</p>
<h4 id="⚠️-한계-2">⚠️ 한계</h4>
<ul>
<li>순서가 보장되지 않은 복잡한 작업의 처리 불가</li>
<li>사용자가 예측 불가능한 입력을 주어도 처리할 수 있는 유연한 구조가 아님</li>
</ul>
<h3 id="step-3--manus-클론-코딩">Step 3 : Manus 클론 코딩</h3>
<blockquote>
<p>사용자의 요청에 유연하게 대응할 수 있는 Multi-Agent 구현</p>
</blockquote>
<p>지금까지의 설계는 사용자가 예측 가능한 요청을 할 경우를 염두에 둔 것이고
그 반대의 경우라면, 심지어 구현되지 않은 Agent 가 필요한 경우까지 고려한다면?
모든 경우를 다 커버하기 위한 Agent 를 구현하고 설계에 반영할 수는 없을 테고,
좀 더 유연하게 사용자의 요청을 소화할 수 있는 방법은 없을까?</p>
<p>이러한 고민을 하던 중, Manus 라는 Multi-Agent 시스템을 발견했다.</p>
<p>아래 캡쳐는 Manus 홈페이지에서 제공하는 &#39;4월 일본 여행&#39; 일정을 작성해 주는 예시이다.
<img src="https://velog.velcdn.com/images/judy_choi/post/bb075c39-2709-40df-93c9-a3ad4cf3a10f/image.png" alt=""></p>
<ul>
<li>Manus Flow 분석<ul>
<li>사용자가 요구사항 입력 → LLM 이 세부 요구사항으로 분할해 task 를 작성 → 각 task 마다 Agent 가 동작하는 것으로 추정됨.</li>
<li>Browser-Use 활용 (웹 브라우저를 제어하는 AI)<ul>
<li><a href="https://github.com/browser-use/browser-use">https://github.com/browser-use/browser-use</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<p>한 번 예제에서 확인되는 Flow 대로 동작하도록 똑같이 클론코딩을 해 보았다.</p>
<h4 id="초기-설계">초기 설계</h4>
<ol>
<li>사용자의 요구사항을 리스트로 만든다.</li>
<li>각각의 리스트 항목에 대답한다. 우선 초기 모델이므로 LLM 만을 이용하여 항목에 대한 답을 작성한다.</li>
<li>작성한 답을 하나의 마크다운 파일에 작성한다.</li>
</ol>
<h4 id="초기-설계-노드-구성">초기 설계 노드 구성</h4>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/9a37f9f9-cfdf-448d-afd9-c9a4fca1b427/image.png" alt=""></p>
<ol>
<li>extract_requirements<ul>
<li>입력: 사용자 프롬프트</li>
<li>출력: 요구사항 리스트 항목들 (체크리스트 형태, 최대 10개)</li>
</ul>
</li>
<li>respond_to_checklist<ul>
<li>입력: 요구사항 리스트 항목들</li>
<li>처리: 항목별로 LLM 호출하여 각 항목의 응답 생성</li>
<li>출력: 응답이 포함된 리스트</li>
</ul>
</li>
<li>review_responses<ul>
<li>입력: 응답이 포함된 리스트</li>
<li>처리: LLM 의 답변을 마크다운 파일에 저장</li>
<li>출력: 최종 결과물</li>
</ul>
</li>
</ol>
<h4 id="수행-시간-개선">수행 시간 개선</h4>
<p><code>2. respond_to_checklist</code> 노드에서 각 체크리스트에 대한 답변을 LLM으로 작성한다.
다시 말해, 이 노드에서 최대 10회 LLM을 호출하는데, 단순히 for 문을 이용해 LLM을 호출하면 수행 시간이 너무 길어진다.
따라서 이 노드를 <strong>비동기 함수로 고쳐서 하나의 LLM이 답변할 때까지 기다리지 않도록 개선했고,
LLM 호출 방법을 배치로 전환해 3개의 체크리스트를 묶어서 LLM에 전달해 LLM의 호출 횟수를 기존 10회 ➡️ 4회로 줄였다.</strong></p>
<pre><code class="language-python">import asyncio
from typing import List


async def async_respond_to_checklist_in_batches(state: TripPlanningState, batch_size: int = 3) -&gt; TripPlanningState:
    async def get_response(item):
        if not item.strip():
            return {&quot;item&quot;: item, &quot;response&quot;: &quot;&quot;}
        return {
            &quot;item&quot;: item, 
            &quot;response&quot;: (await llm.ainvoke(f&quot;&#39;{item}&#39; 이 항목에 대해 자세히 조사해서 설명해 줘. 여행 전문가처럼 답변해 줘.&quot;)).content
        }

    checklist = state[&quot;checklist&quot;]
    all_responses = []

    # 체크리스트를 배치 크기로 나누어 처리
    for i in range(0, len(checklist), batch_size):
        batch = checklist[i:i + batch_size]
        print(f&quot;📋 배치 {i//batch_size + 1} 처리 중... ({len(batch)}개 항목)&quot;)

        # 현재 배치의 모든 항목을 병렬로 처리
        batch_responses = await asyncio.gather(*[get_response(item) for item in batch])
        all_responses.extend(batch_responses)

        # 다음 배치 처리 전 잠시 대기 (API 레이트 리밋 방지)
        if i + batch_size &lt; len(checklist):
            print(f&quot;⏳ 다음 배치 처리를 위해 2초 대기...&quot;)
            await asyncio.sleep(2)

    print(f&quot;✅ 총 {len(all_responses)}개 항목 처리 완료!&quot;)

    return {
        **state, 
        &quot;checklist_with_responses&quot;: all_responses
    }</code></pre>
<h4 id="⚠️-한계-3">⚠️ 한계</h4>
<ul>
<li>체크리스트에 대한 답을 얻을 때 LLM에만 의존함<ul>
<li>진정한 Agent 라면 tool 을 붙여서 웹서치 등으로 답을 가져오는 편이 더 좋지 않을까?</li>
<li>너무 많은 LLM을 호출하므로 API 비용이 많이 들고, 답변을 얻기까지의 소요 시간이 오래 걸림</li>
<li>혹은 여행지에 대한 정보 등을 미리 수집해 두고 RAG를 접목했어도 좋았을 것 같다.</li>
</ul>
</li>
<li>여전히 100% 유연한 설계는 불가능<ul>
<li>답변들을 작성한 후, 하나의 핸드북으로 만들기 위해 답변 취합 노드를 만드는 방법 이외에 유연한(혹은 철저한)설계로 해결할 수는 없을까?</li>
<li>Manus 는 이걸 어떻게 구현했을까.. 🥲</li>
</ul>
</li>
</ul>
<h3 id="step-4--tool-활용">Step 4 : Tool 활용</h3>
<blockquote>
<p>tool 을 붙여서 정확하고 + 빠르고 + 신뢰도 높은 Agent 구축하기</p>
</blockquote>
<p>지금까지 구현한 AI Agent 들의 공통적인 한계는 다음과 같다.</p>
<ul>
<li>답변을 LLM에만 의존한다<ul>
<li>할루시네이션, 최신 정보 반영 불가 등 이슈 발생</li>
</ul>
</li>
<li>사용자의 요청에 유연하게 대응하기 위해서는 너무 설계가 복잡해진다<ul>
<li>예를 들어, 사용자가 &#39;멕시코시티의 정보와 날씨 정보를 알려 줘&#39; 라고 요청했다면, tool 이 없는 경우 요청 전체를 LLM이 한번에 답변하거나(이 경우 실시간 정보를 참고하는 데에 한계가 있음), 사용자의 요청으로부터 의도를 파악해 각각 여행지 정보 제공 에이전트와 날씨 정보 검색 에이전트로 라우팅해야 한다.
물론 각각의 에이전트를 직접 구축해야 하므로 많은 리소스가 들고 설계가 복잡해진다!</li>
</ul>
</li>
<li>실시간 정보 (ex: 날씨 정보, 환율) 를 참고할 수 없다<ul>
<li>물론 OpenAI 의 ChatGPT 를 사용하면 가능하지만, 사용할 LLM이 지정되어 있거나 온프레미스 환경이라면 사용 불가!</li>
</ul>
</li>
</ul>
<p>따라서 Agent 구조는 사용자에게 유용한 필수 기능만 수행하도록 간단하게 가져가되,
가능한 tool 을 최대한 많이 붙여서 유연하고 능동적인 Multi-Agent 로 개선해 보았다.</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/13091c82-2f9e-460c-b9ae-1ea9c486be2d/image.png" alt=""></p>
<pre><code class="language-python">from langchain_community.tools.tavily_search import TavilySearchResults

tavily_search = TavilySearchResults(max_results=3)
# 웹 검색 도구 tavily의 이름과 설명을 설정합니다.
tavily_search.name = &quot;tavily_search&quot;
tavily_search.description = &quot;Use this tool to search on the web using tavily&quot;

...

from langchain_community.utilities import OpenWeatherMapAPIWrapper
from langchain_core.tools import Tool

weather_search = Tool(
    name=&quot;weather_search&quot;,
    func=OpenWeatherMapAPIWrapper().run,
    description=&quot;Use this tool to search the weather of a city or country&quot;
)</code></pre>
<p>우선 이렇게 tool 을 붙이고 나서, log 가 찍히도록 개선했다.
어떤 tool 이 호출되는지(사용자의 요청에 따라 호출되는 tool 이 있고 호출 안되는 tool 이 있음), 호출한 tool이 정상적으로 동작하는지 알아보기 위해 tool 호출 시 로그가 출력되도록 logging 함수로 각각의 tool 을 래핑했다. </p>
<pre><code class="language-python">from collections import defaultdict
import traceback
from langchain_core.tools import Tool

_call_counts = defaultdict(int)

def wrap_func_with_logging(original_func, tool_name):
    def wrapped(input, *, config=None):
        _call_counts[tool_name] += 1
        call_id = _call_counts[tool_name]
        print(f&quot;[🛠️ {tool_name}] Call #{call_id} - Called with input: {repr(input)}&quot;)
        try:
            result = original_func(input)

            # 결과에 에러가 포함되어 있는지 확인
            result_str = str(result)
            if result and not any(error_keyword in result_str for error_keyword in [&#39;HTTPError&#39;, &#39;Error:&#39;, &#39;error:&#39;, &#39;Exception&#39;, &#39;Failed&#39;]):
                print(f&quot;[✅ {tool_name}] Call #{call_id} - Success. Result: {result_str[:300]}&quot;)
            elif result:
                print(f&quot;[❌ {tool_name}] Call #{call_id} - API Error in result: {result_str[:300]}&quot;)
            else:
                print(f&quot;[⚠️ {tool_name}] Call #{call_id} - No result returned.&quot;)
            return result
        except Exception as e:
            print(f&quot;[❌ {tool_name}] Call #{call_id} - Error: {e}&quot;)
            print(traceback.format_exc())
            raise
    return wrapped


def logging_tool(func, name, description):

    if callable(func) and not hasattr(func, &quot;invoke&quot;):
        # 순수 함수형 도구
        func = wrap_func_with_logging(func, name)
        return Tool(name=name, func=func, description=description)

    elif hasattr(func, &quot;invoke&quot;):
        # LangChain-style Tool → 직접 감싸지 말고 아예 래핑된 Tool을 새로 정의
        class WrappedTool(func.__class__):
            def invoke(self, input, config=None):
                _call_counts[name] += 1
                call_id = _call_counts[name]
                print(f&quot;[🛠️ {name}] Call #{call_id} - Called with input: {repr(input)}&quot;)
                try:
                    result = super().invoke(input, config=config)

                    # 결과에 에러가 포함되어 있는지 확인
                    result_str = str(result)
                    if result and not any(error_keyword in result_str for error_keyword in [&#39;HTTPError&#39;, &#39;Error:&#39;, &#39;error:&#39;, &#39;Exception&#39;, &#39;Failed&#39;]):
                        print(f&quot;[✅ {name}] Call #{call_id} - Success. Result: {result_str[:300]}&quot;)
                    elif result:
                        print(f&quot;[❌ {name}] Call #{call_id} - API Error in result: {result_str[:300]}&quot;)
                    else:
                        print(f&quot;[⚠️ {name}] Call #{call_id} - No result returned.&quot;)
                    return result
                except Exception as e:
                    print(f&quot;[❌ {name}] Call #{call_id} - Error: {e}&quot;)
                    print(traceback.format_exc())
                    raise

        return WrappedTool(name=name, description=description)

    else:
        raise ValueError(&quot;func must be a callable or have an .invoke() method&quot;)
</code></pre>
<p>tool 정의에 logging 함수 래핑</p>
<pre><code class="language-python">from langchain_community.tools.tavily_search import TavilySearchResults

# 원래 tavily tool 정의는 다음 3줄이면 가능하지만
# tavily_search = TavilySearchResults(max_results=3)
# # 웹 검색 도구 tavily의 이름과 설명을 설정합니다.
# tavily_search.name = &quot;tavily_search&quot;
# tavily_search.description = &quot;Use this tool to search on the web using tavily&quot;

# 로그를 찍기 위해 logging_tool 함수로 래핑
tavily_search = logging_tool(
    name=&quot;tavily_search&quot;,
    func=TavilySearchResults(max_results=3),
    description=&quot;Search with Tavily&quot;
)

...

from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper

wikipedia_search = logging_tool(
    name=&quot;wikipedia_search&quot;,
    func=WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper()).run,
    description=&quot;Use this tool to search on the wikipedia&quot;
)</code></pre>
<p>이제 어느 툴이 호출되어 정상적으로 실행되었는지 로그를 확인할 수 있다.</p>
<pre><code>[🛠️ tavily_search] Call #1 - Called with input: {&#39;name&#39;: &#39;tavily_search&#39;, &#39;args&#39;: {&#39;query&#39;: &#39;cultural sites in Paris&#39;}, &#39;id&#39;: &#39;call_tS6lSMPtFTYXpIdmtYzKUlZ3&#39;, &#39;type&#39;: &#39;tool_call&#39;}
[🛠️ tavily_search] Call #2 - Called with input: {&#39;name&#39;: &#39;tavily_search&#39;, &#39;args&#39;: {&#39;query&#39;: &#39;architectural sites in Paris and nearby&#39;}, &#39;id&#39;: &#39;call_oRFI5tzOqloH7Zwr4Bnimnkb&#39;, &#39;type&#39;: &#39;tool_call&#39;}
[🛠️ tavily_search] Call #3 - Called with input: {&#39;name&#39;: &#39;tavily_search&#39;, &#39;args&#39;: {&#39;query&#39;: &#39;artistic sites in Paris&#39;}, &#39;id&#39;: &#39;call_wZIAc2J9dATUd7Mrj8W8ztZB&#39;, &#39;type&#39;: &#39;tool_call&#39;}
[❌ tavily_search] Call #2 - API Error in result: content=&quot;HTTPError(&#39;502 Server Error: Bad Gateway for url: https://api.tavily.com/search&#39;)&quot; name=&#39;tavily_search&#39; tool_call_id=&#39;call_oRFI5tzOqloH7Zwr4Bnimnkb&#39; artifact={}
[❌ tavily_search] Call #3 - API Error in result: content=&quot;HTTPError(&#39;502 Server Error: Bad Gateway for url: https://api.tavily.com/search&#39;)&quot; name=&#39;tavily_search&#39; tool_call_id=&#39;call_wZIAc2J9dATUd7Mrj8W8ztZB&#39; artifact={}
[✅ tavily_search] Call #1 - Success. Result: content=&#39;[{&quot;title&quot;: &quot;Places in Paris where you can explore world cultures&quot;, &quot;url&quot;: &quot;https://parisjetaime.com/eng/article/places-in-paris-where-you-can-explore-world-cultures-a723&quot;, &quot;content&quot;: &quot;Institutes, cultural centres and gardens · Musée du quai Branly - Jacques Chirac · Musée du Louvre · Philha

[✅ weather_search] Call #1 - Success. Result: In Paris, France, the current weather is as follows:
Detailed status: clear sky
Wind speed: 1.54 m/s, direction: 350°
Humidity: 85%
Temperature: 
  - Current: 12.35°C
  - High: 13.28°C
  - Low: 10.77°C
  - Feels like: 11.86°C
Rain: {}
Heat index: None
Cloud cover: 0%</code></pre><h4 id="⚠️-한계-4">⚠️ 한계</h4>
<ul>
<li>LangGraph 에서 제공하는 tool 을 사용하려면 여전히 OpenAI key 가 필요하다!<ul>
<li>온프레미스 환경에서는 여전히 사용 불가</li>
<li>벗어날 수 없는 OpenAI 생태계.. 🤿</li>
</ul>
</li>
<li>LangGraph 에서 제공하는 tool 의 종류와 기능에 한계가 있다<ul>
<li>항공편 검색, 호텔 예약 tool 도 있었다면 편했을 텐데!</li>
<li>이래서 요즘 MCP가 핫하구나..! 직접 tool 을 만들어서 붙일 수 있으니까!</li>
</ul>
</li>
</ul>
<hr>
<h2 id="결과공유회--스터디-후기-발표">결과공유회 : 스터디 후기 발표</h2>
<p>스터디를 모두 마치고 <strong>&#39;데브콘 서울: 오월엔 만남 밋업&#39;</strong> 행사에서 간단하게 내가 만든 여행 비서 에이전트에 대해 발표했다.
(스터디 일시/장소 : 5월17일(토) / 모두의 연구소 역삼점)</p>
<ul>
<li><a href="https://k-devcon.com/entry/Review-2025-05-17-%EB%8D%B0%EB%B8%8C%EC%BD%98-%EC%84%9C%EC%9A%B8-%EC%98%A4%EC%9B%94%EC%97%94-%EB%A7%8C%EB%82%A8-%ED%9B%84%EA%B8%B0">[Review] 2025-05-17 &lt;데브콘 서울 : 오월엔 만남&gt; 후기</a></li>
<li>결과공유회 발표자료 : <a href="https://drive.google.com/file/d/1u3ZrzKwY81bnwsu6AY1PtvOXS4rWFAA8/view?usp=sharing">Travel Agent : 나만의 여행 비서 만들기</a></li>
</ul>
<hr>
<h2 id="회고">회고</h2>
<p><strong>피할 수 없는 할루시네이션</strong>
프로젝트를 진행하는 내내 도저히 할루시네이션을 피할 수가 없었다.
내가 시도했거나 참고한 할루시네이션 완화 방안은 다음과 같다.</p>
<ul>
<li><p>프롬프트 개선
시스템 프롬프트를 좀 더 디테일하게 작성했더니 할루시네이션이 완화되었다.
하지만 프롬프트에서 고려하지 않은 사용자의 요청이 들어오면 여전히 할루시네이션이 발생할 가능성이 존재한다.</p>
</li>
<li><p>RAG
결정적으로 다른 팀원들은 각자의 프로젝트에서 할루시네이션이 큰 이슈가 되지 않았는데,
그 이유는 각자 데이터 크롤링부터 시작해 최종적으로 RAG를 사용했기 때문이었다.
다만, 개발 단계에서 데이터 크롤링에 많은 시간과 노력을 투자한 팀원은 이런저런 이슈로 크롤링이 수월하지 않아 병목 현상을 겪었고
RAG를 안 한 나는 할루시네이션에 고통받았으니 Trade-off 인 셈인가.. 😇</p>
</li>
</ul>
<p><strong>기존 LLM 서비스와의 차별화 고려</strong></p>
<p>이번 프로젝트에서 가장 중요하게 생각한 건 <strong>‘상용화 가능성’</strong>이었다.
그래서 최대한 온프레미스 환경에서 동작할 수 있도록 구현해보려 했고 (결과적으로는 실패했지만…)</p>
<p>여기서 말하는 상용화란, 결국 사용자가 기꺼이 이 ‘여행 비서’를 실제로 사용하게 된다는 뜻이고,
그렇다면 당연히 ChatGPT보다 더 나은 무언가, 또는 차별화되는 요소가 있어야 한다고 생각했다.</p>
<p>예를 들면, ChatGPT보다 정보가 더 정확하다든지, 사용성이 더 좋다든지 하는 부분.
(어차피 ChatGPT도 웬만한 여행 비서 역할은 충분히 해내는데,
내가 만든 이 여행 비서를 굳이 써야 할 이유가 없다면, 그걸 누가 쓰겠는가?)</p>
<p>그렇기 때문에 여행 비서를 개발하면서 계속해서 ChatGPT 와 성능과 사용성을 비교했는데
ChatGPT 의 고질적인 문제는 역시 할루시네이션이었고,
할루시네이션은 아니지만 정확히 내 취향(성향) 을 고려해 딱 맞는 여행지를 추천해 주는 센스는 아직 50% 부족했으며
ChatGPT 가 여행 일정을 짜 주어도 결국 그 일정을 내 캘린더에 추가하고, 다시 한 번 제공된 여행 정보를 확인해야 하는 등 손이 가는 것은 마찬가지였다.</p>
<p>이러한 점들을 고려할 때, ChatGPT 즉 기존 LLM 서비스와 차별화하는 방안에 대해 고민한 내용은 다음과 같다.
(실제로 구현한 것은 아니고, 만약 프로젝트를 더 발전시킨다면 이렇게 개선할 것 같다.)</p>
<ul>
<li><p>웹 검색 개선
나는 Tavily 와 Serper tool 을 이용해 웹 검색을 구현했으나,
만약 Browser-use 처럼 좀더 강력한 웹 검색 라이브러리를 사용했다면 더욱 정확한 정보를 검색해 올 수 있었을 테고,
이는 더 적은 할루시네이션을 일으켜 정확한 여행 정보를 제공하는 동시에
다량의 검색 결과를 이용해 여행 정보를 풍부하게 제공할 수 있었을 것 같다.</p>
</li>
<li><p>개인 맞춤형 확장 툴 추가
적어도 ChatGPT 보다는 뭔가 유용한 점이 있어야 사용자들이 ChatGPT 대신 내 여행 비서를 사용할 것 같다.
다시 말해 기업이 서비스할 가치가 있는 에이전트를 개발해야 할 텐데,
아직 나는 현업에서 Agent 가 어떻게 쓰이는지 본 적이 없어서 아이디어가 떠오르지 않았다.
이에 대해 멘토님께서는 구글 캘린더와 같이 사용자의 개인정보에 접근할 수 있는 개인 맞춤형 확장 툴을 추가해 보라는 피드백을 주셨다.</p>
</li>
<li><p>MCP(Model Context Protocol) 를 붙여서 Agent 기능 확장
나는 LangGraph 에서 이미 만들어서 제공하는 tool 만을 사용했고, 이 때문에 사용할 수 있는 tool 의 기능과 종류에 한계가 있었다.
만약 MCP 로 직접 tool 을 개발하고 Agent 에 붙인다면 더 다양한 기능을 사용할 수 있을 테고,
ChatGPT 보다 더욱 유용한 AI 여행 비서를 만들 수 있지 않았을까?</p>
</li>
<li><p>도메인 특화 LLM 학습
특히 답변을 LLM에 의존하는 경우에 할루시네이션이 발생하는 점을 해결하고 싶었는데
이에 대해 멘토님께서 여행 데이터로 LLM을 파인튜닝하는 방법을 고려해 보라는 피드백을 주셨다.
만약 멘토님의 조언대로 도메인에 특화된 LLM을 학습해 사용한다면 답변의 정확도가 향상됨은 물론 더 매끄러운 답변이 생성되지 않을까?</p>
</li>
</ul>
<p><strong>성능 평가</strong>
RAG의 경우 RAGAS 를 이용해 지표를 계산하는 방법으로 성능 평가를 하고,
LLM을 이용해 성능 평가를 하는 경우도 있다.
그렇다면, AI Agent 는 어떻게 성능을 평가할까?
ChatGPT 에 냅다 물어보니 이렇게 알려줬다.</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/efc1eac7-cd8c-45bb-93d1-9720fddf2d85/image.png" alt=""></p>
<p>Agent 평가 방식은 다음 지표들이 있고</p>
<ol>
<li>🎯 Task Success Rate (TSR) – 목표 달성률</li>
<li>⏱️ Efficiency Metrics (시간, step 수)</li>
<li>🔄 Action Accuracy (행동 정확성)</li>
<li>🧠 Planning Quality (계획 품질)</li>
<li>🤝 Collaboration Score (멀티에이전트일 때)</li>
</ol>
<p>결론!
<img src="https://velog.velcdn.com/images/judy_choi/post/0153c229-b1b9-44a4-a9f6-98ef154e3026/image.png" alt=""></p>
<p>다음에 Agent 개발 프로젝트를 한다면 성능 평가도 꼭 해서 완성도를 높이고 싶다.</p>
<p><strong>AI 여행 비서의 가치</strong>
여행 비서 말고 좀 더 비즈니스에 특화된 주제를 잡았더라면 더 좋은 경험이었을 것 같다.
나를 포함해 보통의 여행자들은 AI 여행 비서가 꼭 필요할까?
혹은 기업의 입장에서 생각할 때 상품화할 가치가 있는 아이템일까?
이 생각을 하며 기술블로그에 예전에 쓴 글을 뒤져보니 이미 답이 있었다!</p>
<p><a href="https://velog.io/@judy_choi/DEVOCEAN-OpenLab-Tech-Day-%ED%9B%84%EA%B8%B0">DEVOCEAN OpenLab - Tech Day 후기</a></p>
<pre><code>하림님(발표자)께서는 여행 스타트업의 팀장님이신 만큼 여행 도메인에 대한 시야가 넓고
그만큼 Agent 설계와 향후 상품화까지 구체적으로 고려하신 점이 인상깊었습니다.

1. 여행 준비, 왜 어려울까?
- 제한된 자원 내에서 완벽한 여행 계획 짜기가 어려움
- 변화하는 정보 형태 (정보 탐색 채널)

2. 여행 준비, Agent 로 해결해 보자
- Agent : 주변 환경을 인지하고 상호 작용하며, 목표를 달성하기 위해 자율적으로 행동하는 시스템

3. Travel Go, TGO
- TGO 팀장 Agent
  - 사용자의 질의를 받아 적합한 Agent 에 역할 위임 (작업 할당)
    - 장소 추천 Agent 
    - 질문 답변 Agent
    - 일정표 생성 Agent

4. TGO 상품화
- 상품화 범위는 무궁무진!
  - 광고 플랫폼 / 에이전트 구독료 / OTA&amp;여행사 / 원클릭 결제 시스템 구축 등등</code></pre><p>이 후기대로 여행 비서 에이전트의 목표와 가치를 명확하게 잡고 Agent 를 설계했더라면 시행착오를 덜 겪었을텐데! 😂</p>
<p><strong>충분한 시행착오를 겪으며 Multi-Agent 개발 요령이 생겼다!</strong>
효율적인 설계, 사용성, 수행 시간 등등 많은 고민을 하며 거의 매주 개발한 코드를 갈아엎고 새로 개발했다.
계속 갈아엎는 바람에 그동안 들인 시간과 노력에 비해 결과물이 조금 미약한데
그 대신 Multi-Agent 개발 과정에서 겪을 수 있는 이슈는 직접 다 겪었다.</p>
<p>이렇게 온몸으로 부딪치고 예상치 못한 이슈에 대해 고민하고 해결하며 나름대로의 노하우가 생긴 덕분에
<U>최근에 Multi-Agent 를 활용하는 논문 1편을 단 3일만에 구현했다.</U>
해당 논문의 리뷰와 코드는 곧 정리해서 이 블로그와 깃헙에 공개할 예정이다.</p>
<h2 id="future-work">Future Work</h2>
<p>개선하고 싶은 점은 많지만, 다음 한계 때문에 제일 먼저 <strong>MCP 추가</strong>를 해보고 싶다.</p>
<ol>
<li><p>기능 구현의 한계
&#39;Step 4 : Tool 활용&#39; 에서 언급했듯이 나는 LangGraph 에서 이미 만들어서 제공하는 tool 만을 사용했고,
이 때문에 사용할 수 있는 tool 은 대부분 웹, 날씨, 위키피디아 등등 각종 검색 tool 이었기 때문에 기능 구현에 한계가 있었다.</p>
</li>
<li><p>비용의 한계
게다가 이렇게 제공되는 tool 을 사용하기 위해서는 OpenAI key 가 필요했다.
온프레미스 환경에서 사용할 수도 없거니와, 만약 사용할 수 있는 환경이라 하더라도 tool 의 종류가 늘어나고 더 많이 사용할수록 서비스 비용이 급증할 것이다.</p>
</li>
</ol>
<p>이러한 이슈를 직접 겪고 나니 왜 MCP가 Agent 에 꼭 필요한 기술인지 알 수 있었다.
최근 인프런의 LangGraph 강의를 제공받을 기회가 있었고, MCP 를 구현하는 강의 회차부터 먼저 수강했다.
이제 강의에서 배운 점을 토대로 직접 MCP 를 구현하는 일만 남았다! 🤓</p>
<hr>
<h2 id="archive">Archive</h2>
<h3 id="우리-팀-후기">우리 팀 후기</h3>
<p>멘토님 후기 : <a href="https://chemical-hourglass-222.notion.site/AI-agent-1e3171e4ffbd800b823cda909fbdc1a9">AI agent 프로젝트</a>
팀원 후기 : 
<a href="https://hujiro.tistory.com/91">고투런 2기 | AI agent 프로젝트 멘토링 기록</a>
<a href="https://about-prost.notion.site/AI-agent-AI-1eff6c9546f580578d1ed0aad8af7672">[AI agent 프로젝트] AI취업 도우미 구축</a></p>
<h3 id="gotolearn-2기-활동-후기-운영진-작성">GoToLearn 2기 활동 후기 (운영진 작성)</h3>
<p><a href="https://k-devcon.com/entry/Review-%EA%B3%A0%ED%88%AC%EB%9F%B0-2%EA%B8%B0-%EC%98%A8%EB%9D%BC%EC%9D%B8-OT-%ED%9B%84%EA%B8%B0">[Review] 고투런 2기 온라인 OT 후기</a>
<a href="https://k-devcon.com/entry/Review-Go-To-Learn-2%EA%B8%B0-1%EC%A3%BC%EC%B0%A8-%ED%99%9C%EB%8F%99-%ED%9B%84%EA%B8%B0">[Review] Go To Learn 2기 1주차 활동 후기</a>
<a href="https://k-devcon.com/entry/Review-Go-To-Learn-2%EA%B8%B0-2%EC%A3%BC%EC%B0%A8-%ED%99%9C%EB%8F%99-%ED%9B%84%EA%B8%B0">[Review] Go To Learn 2기 2주차 활동 후기</a>
<a href="https://k-devcon.com/entry/Review-Go-To-Learn-2%EA%B8%B0-3%EC%A3%BC%EC%B0%A8-%ED%99%9C%EB%8F%99-%ED%9B%84%EA%B8%B0">[Review] Go To Learn 2기 3주차 활동 후기</a>
<a href="https://k-devcon.com/entry/Review-Go-To-Learn-2%EA%B8%B0-4%EC%A3%BC%EC%B0%A8-%ED%99%9C%EB%8F%99-%ED%9B%84%EA%B8%B0">[Review] Go To Learn 2기 4주차 활동 후기</a>
<a href="https://k-devcon.com/entry/Review-Go-To-Learn-2%EA%B8%B0-%EB%A7%88%EC%A7%80%EB%A7%89-%EC%A3%BC%EC%B0%A8-%ED%99%9C%EB%8F%99-%ED%9B%84%EA%B8%B0-1">[Review] Go To Learn 2기 마지막 주차 활동 후기</a>
<a href="https://k-devcon.com/entry/Review-2025-05-17-%EB%8D%B0%EB%B8%8C%EC%BD%98-%EC%84%9C%EC%9A%B8-%EC%98%A4%EC%9B%94%EC%97%94-%EB%A7%8C%EB%82%A8-%ED%9B%84%EA%B8%B0">[Review] 2025-05-17 &lt;데브콘 서울 : 오월엔 만남&gt; 후기</a>
<a href="https://k-devcon.com/entry/Review-%EA%B3%A0%ED%88%AC%EB%9F%B0-2%EA%B8%B0-%EC%9A%B4%EC%98%81-%ED%9B%84%EA%B8%B0">[Review] 고투런 2기 운영 후기</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OSSCA] 2025 체험형-1차 : PR Agent 프로젝트 후기]]></title>
            <link>https://velog.io/@judy_choi/OSSCA-2025-%EC%B2%B4%ED%97%98%ED%98%95-1%EC%B0%A8-PR-Agent-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@judy_choi/OSSCA-2025-%EC%B2%B4%ED%97%98%ED%98%95-1%EC%B0%A8-PR-Agent-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sun, 22 Jun 2025 12:14:35 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>기여 내용 : <code>#TODO</code> 주석을 자동으로 수집하여 사용자에게 알려주는 기능 추가
최종 기여 PR : <a href="https://github.com/qodo-ai/pr-agent/pull/1869">https://github.com/qodo-ai/pr-agent/pull/1869</a></p>
</blockquote>
<p>6주간 진행된 오픈소스 컨트리뷰션 프로젝트를 수료했다.</p>
<p>우리 조는 당초 프로젝트 목표대로 6주 안에 PR을 보내지는 못했지만
수료 이후에도 계속해서 개발과 보완, 피드백 반영을 거쳐 최종적으로 PR 및 Merge 완료했다!</p>
<p>지원서 작성 &amp; 합격 후기 : <a href="https://velog.io/@judy_choi/OSSCA-2025-%EC%B2%B4%ED%97%98%ED%98%95-1%EC%B0%A8-%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0">[OSSCA] 2025 체험형-1차 : 합격 후기</a></p>
<hr>
<h2 id="프로젝트-소개">프로젝트 소개</h2>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/8049103c-9a92-4b3a-9502-410d2e9a7773/image.png" alt=""></p>
<h3 id="1-프로그램-개요">1. 프로그램 개요</h3>
<ul>
<li>프로그램 명칭: Git 활용 및 PR Agent</li>
<li>멘토링 주제: 협업 도구(Git) 심화 학습 및 실제 오픈소스 프로젝트(PR Agent) 기여 경험</li>
<li>기간: 2025년 4월 14일 ~ 5월 27일 (총 6주)</li>
<li>멘토링 GitHub 레포 : <a href="https://github.com/ossca-2025/pr-agent-mentoring">https://github.com/ossca-2025/pr-agent-mentoring</a></li>
</ul>
<h3 id="2-멘토링-목표">2. 멘토링 목표</h3>
<p>사전 학습 조건</p>
<ul>
<li>Python 언어에 대한 이해</li>
<li>LLM에 대한 기본지식 이해</li>
<li>Google Gemini API 사용법 이해</li>
<li>Git 활용 능력 심화<ul>
<li>다양한 Git 명령어 및 고급 기능(브랜치 전략-GitHub Flow, rebase, cherry-pick 등)을 이해하고 능숙하게 활용합니다.</li>
<li>팀 프로젝트 환경에서의 PR 기반 코드리뷰 플로우를 이해하고 실천합니다.</li>
</ul>
</li>
</ul>
<p>오픈소스 생태계 이해 및 기여 경험</p>
<ul>
<li>PR Agent 프로젝트 (<a href="https://github.com/qodo-ai/pr-agent)%EB%A5%BC">https://github.com/qodo-ai/pr-agent)를</a> 중심으로 오픈소스 프로젝트를 분석하는 능력을 기릅니다.</li>
<li>LLM 기반 AI 코드리뷰를 위한 MVK(Minimum Viable Knowledge)를 습득합니다.</li>
<li>PR Agent 프로젝트의 기여 가이드라인(<a href="https://github.com/ossca-2025/pr-agent/blob/main/CONTRIBUTING.md">Contribution Guideline</a>)을 이해하고 준수합니다.</li>
<li>PR Agent 프로젝트에 코드, 문서 등 의미 있는 기여(Contribution)를 최소 1회 이상 경험합니다.</li>
</ul>
<hr>
<h2 id="주차별-진행-내역">주차별 진행 내역</h2>
<p>1주 1회 조별 모임, 1회 정기 모임을 진행했다.
우리 조는 매주 수요일에 조별 모임을 갖고 중간점검과 회의, 보강 및 개인별로 조사 및 작업한 내역을 취합하여
돌아오는 일요일 정기 모임에서 취합한 내용을 발표하고 멘토님께 피드백을 받았다.</p>
<h3 id="1주차--kick-off">1주차 : Kick-Off</h3>
<ul>
<li>팀명/팀 목표/팀 정기 모임 일정 정하기</li>
<li>조별 소개서 작성</li>
</ul>
<h3 id="2주차--자료조사">2주차 : 자료조사</h3>
<ul>
<li>PR Agent의 구조 및 주요 기능 분석 (공식문서 참고)</li>
<li>AI 코드리뷰 툴 조사 (도구 간 기능, 성능, 사용성 관점 비교)<ul>
<li>gemini / 코드래빗 / git copilot 중 1인당 1개씩 조사</li>
</ul>
</li>
<li>RAG, MCP, A2A 개념 조사</li>
</ul>
<p><strong>PR‑Agent?</strong>
Pull Request(PR)를 효과적으로 리뷰하고 보다 쉽게 관리하는 데 도움을 주는 AI 기반 코드 리뷰 어시스턴트</p>
<p>주요 특징 :</p>
<ul>
<li>GitHub, GitLab, Bitbucket 등과 연동 가능</li>
<li>다양한 LLM 모델 지원 (GPT, Claude, DeepSeek 등)</li>
<li>오픈소스로 self-hosting 가능</li>
<li>CLI, GitHub App, GitHub Action, Docker 연동 지원</li>
</ul>
<p>아키텍쳐 :
<img src="https://velog.velcdn.com/images/judy_choi/post/8f01575e-89cf-4157-b8ad-b9a2bdac949c/image.png" alt=""></p>
<p>사용 예시(<code>review</code>) :</p>
<pre><code class="language-bash">python3 -m pr_agent.cli --pr_url &lt;리뷰할 PR URL&gt; review</code></pre>
<p>테스트 URL : <a href="https://github.com/Judy-Choi/pr-agent-study/pull/3">https://github.com/Judy-Choi/pr-agent-study/pull/3</a></p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/e413407e-23eb-4851-9a5f-ac2181ab7036/image.png" alt=""></p>
<h3 id="3주차--개발-환경-설정--코드-분석">3주차 : 개발 환경 설정 &amp; 코드 분석</h3>
<ul>
<li>pr agent 로컬 개발 환경 설정 (gemini API 연동 포함)<ul>
<li>환경 설정 참고 : <a href="https://www.notion.so/1e30696130328082954bd4a0dc8f0163">세팅(문서)</a></li>
</ul>
</li>
<li>IDE 디버그 모드로 pr agent cli(로컬 소스 기반 설치) 실행하고 코드 추적해보기</li>
<li>각 조별로 기본 설정, tool 3가지, 프롬프트 = 총 5개의 주제에 대해 심층적으로 조사</li>
</ul>
<p>우리 조의 경우 tool 중 <code>improve</code> 툴을 조사, 분석, 사용방법을 조사하여 정리했다.
<code>improve</code> 툴은 아래 캡쳐와 같이 PR에서 변경된 코드를 스캔한 후, AI(LLM) 을 이용해 코드를 개선할 수 있는 제안 (Code Suggestion) 을 생성해 준다.</p>
<p>나의 경우 <code>improve</code> 의 옵션 중 AI가 생성한 코드 개선 제안을 PR에 자동으로 코멘트로 남기는 기능인 <code>Code validation</code> 에 대해 조사하였으며
조사 과정에서 공식문서를 참고하며 디버깅을 통해 코드의 흐름을 파악했다.</p>
<p>테스트 PR : <a href="https://github.com/Judy-Choi/pr-agent-study/pull/2">https://github.com/Judy-Choi/pr-agent-study/pull/2</a>
<img src="https://velog.velcdn.com/images/judy_choi/post/05f18a16-ddad-477a-a645-dcbd39f40b35/image.png" alt=""></p>
<h3 id="4주차--기여-아이디어-제안--구현-계획">4주차 : 기여 아이디어 제안 &amp; 구현 계획</h3>
<ul>
<li>조별로 Top 3 PR Agent 개선 아이디어 정리 및 발표 (10분 분량)<ul>
<li>각 아이디어는 What / Why / How / 구현 계획 포함</li>
</ul>
</li>
</ul>
<p>우선 조별 모임까지 1인당 3개의 아이디어를 냈고, 이 중 Top3 아이디어를 선정했다.</p>
<ol>
<li>Rule-Based 코드 분석기(Pylint)와 LLM의 하이브리드 리뷰</li>
<li>존재하지 않는 PR URL 호출 시 사용자 친화적인 에러 로그 개선</li>
<li>✅ <strong>describe 명령어에 TODO/FIXME 자동 수집 기능 추가 (최종 구현)</strong></li>
</ol>
<p>선정되지 않은 아이디어도 각자 이슈로 등록하여 Maintainer 에게 피드백을 받았고,
결과적으로 조별 과제 이외에 Maintainer 의 피드백을 받아들여 PR 및 Merge 성공한 멘티들이 많았다!
(너무 부러웠다 🥲)</p>
<h3 id="5주차--기여-계획서-작성">5주차 : 기여 계획서 작성</h3>
<ul>
<li>기여 계획서 작성<ul>
<li>2주 내 PR 기여 가능한 아이디어 선정</li>
<li>과제 목표, 범위, 참여자 포함</li>
</ul>
</li>
</ul>
<p>우리 팀의 기여 계획서는 다음과 같고,
<a href="https://github.com/ossca-2025/pr-agent-mentoring/blob/main/weekly/week-5/group-5/assignment.md">https://github.com/ossca-2025/pr-agent-mentoring/blob/main/weekly/week-5/group-5/assignment.md</a></p>
<p>요약하자면 다음과 같다.</p>
<p><strong>📌 과제 개요</strong>
<strong>문제 인식</strong>
PR 과정에서 코드 내 TODO/FIXME 주석이 PR 설명에 누락되어 리뷰 시 인지 못하고 머지되는 문제가 발생.</p>
<p><strong>목표</strong>
PR-Agent 기능 확장 → PR에 포함된 코드의 TODO/FIXME 주석을 자동 감지하여 PR Description에 요약 표시.</p>
<p><strong>📌 구현 기능</strong></p>
<ol>
<li>PR diff 내 TODO/FIXME 주석 자동 탐색.</li>
<li>파일 경로, 라인 번호, 주석 내용을 추출하여 PR Description 하단에 표준 포맷으로 추가.
예시:<pre><code>## 📌 TODO Summary (Auto-generated)
- [ ] main.py:42 — 리팩토링 필요</code></pre></li>
<li>사용자 설정 지원 (configuration.toml 내 scan_patterns 설정 가능).</li>
<li>기능 문서화 및 사용 가이드 제공.</li>
</ol>
<p><strong>📌 기대 효과</strong></p>
<ul>
<li>리뷰 품질 향상</li>
<li>작업 누락 방지</li>
<li>팀 커뮤니케이션 효율 증대</li>
</ul>
<p>PR Agent 의 Maintainer 는 다음과 같이 기능을 구현해 주길 요청했다.
<a href="https://github.com/qodo-ai/pr-agent/issues/1763">https://github.com/qodo-ai/pr-agent/issues/1763</a></p>
<ul>
<li><code>review</code> tool 섹션에 <code>#TODO</code> 주석을 수집하여 보여 주기</li>
</ul>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/5d1466e7-accd-457a-9d54-128cd99af8c1/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/be989975-03c4-4969-a977-8f20f8d7178d/image.png" alt=""></p>
<h3 id="6주차--구현--초기-pr-작성">6주차 : 구현 &amp; 초기 PR 작성</h3>
<p>초기 PR : <a href="https://github.com/PullPullers/pr-agent-qodo/pull/1">https://github.com/PullPullers/pr-agent-qodo/pull/1</a></p>
<p>우리는 먼저 <code>review</code> 툴의 다른 옵션이 어떻게 동작하는지 디버깅을 포함한 코드리뷰를 거쳤고,
다음과 같은 과정을 통해 <code>#TODO</code> 주석이 수집되고 <code>review</code>섹션이 생성되도록 했다.</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/3b8d4be6-58da-4b69-ada1-45318e27eddc/image.png" alt=""></p>
<ol>
<li><p>설정 파일에서 <code>require_todo_scan</code> flag 값 로드 (default = false)</p>
</li>
<li><p>프롬프트를 이용해 다음 작업 수행
2.1. <code>require_todo_scan</code> == true 이면
2.2. PR Diff (변경된 코드) 에서 TODO 주석 정보 추출 (Pydantic 스타일 적용)</p>
<pre><code class="language-python">class TodoSection(BaseModel):
   - relevant_file : TODO 주석이 포함된 파일명
   - line_range : TODO 주석이 포함된 코드 줄 번호
   - content : TODO 주석 내용</code></pre>
<p>2.3. 추출한 TodoSection 을 리스트로 만들고, 모든 TODO 주석들의 내용을 요약</p>
</li>
<li><p><code>review</code> 에 TODO 정보 섹션 추가</p>
<ul>
<li>2.3 에서 생성된 TODO 정보를 사용자에게 제공하는 섹션 추가</li>
<li>파일 링크 추가 (클릭 시 <code>#TODO</code> 주석의 위치로 바로 이동하도록)</li>
<li>GitHub Flavored Markdown 지원 유/무에 따라 적합한 템플릿 적용</li>
</ul>
</li>
</ol>
<h3 id="78주차--멘토님-피드백-반영">7~8주차 : 멘토님 피드백 반영</h3>
<p>구현한 내용에 대해 멘토님의 피드백을 받고 반영했다.</p>
<ul>
<li>print로 출력되는 메시지 ➡️ 로그 메시지로 변경</li>
<li>타입 힌트 추가</li>
<li>싱글라인 TODO 주석만 지원 ➡️ 멀티라인 TODO 주석도 지원</li>
<li>TODO 주석이 많은 경우 섹션을 접어 가독성 개선</li>
</ul>
<p>테스트 URL : <a href="https://github.com/Judy-Choi/pr-agent-study/pull/10">https://github.com/Judy-Choi/pr-agent-study/pull/10</a>
<img src="https://velog.velcdn.com/images/judy_choi/post/a97b9de9-69e1-4624-93f5-ece3cfa8c3e8/image.png" alt=""></p>
<h3 id="9주차--최종-pr-제출">9주차 : 최종 PR 제출</h3>
<p>피드백 반영 및 테스트 후, 최종 PR을 보내기 위해 영문으로 코멘트를 작성하고 취합했다.</p>
<p>이후 제출한 최종 PR 에 대해 Maintainer 가 아주 상세하게 피드백을 해 주었고, 모두 PR 코드에 반영했다.</p>
<ul>
<li>메뉴얼 문서의 TODO default 값 true → false 로 수정</li>
<li>TODO 섹션 이모지 ✅ → 📝 로 교체</li>
<li>TODO 섹션이 기본적으로 접혀(닫혀) 있도록 수정<ul>
<li>ex : Code suggestion 섹션도 기본적으로 접혀(닫혀) 있듯이, 다른 피드백 섹션과 공존(co-exist) 해야 함</li>
</ul>
</li>
<li>TODO 섹션의 (N entries) 같은 표시 없이 깔끔한 header로 수정</li>
<li>TODO 섹션 UI 수정</li>
<li>TODO 섹션의 field description 추출 프롬프트 수정</li>
<li>TODO 요약 프롬프트를 더 짧게 수정<ul>
<li>&quot;코드에 있는 TODO 주석의 기능 영역을 요약한 최대 6단어&quot; 정도면 됩니다.</li>
<li>프롬프트는 담요와 같습니다. 누군가 한쪽에서 당기면 다른 쪽에 남는 양이 줄어듭니다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="성과-✨">성과 ✨</h2>
<p>긴 협업과 피드백을 거쳐 최종 Merge 완료되었다!!
<a href="https://github.com/qodo-ai/pr-agent/pull/1869">https://github.com/qodo-ai/pr-agent/pull/1869</a>
<img src="https://velog.velcdn.com/images/judy_choi/post/54bb8bee-d539-4f0e-ae82-93e6f7ce5000/image.png" alt="">
(이 메시지 한 줄을 보기 위해 9주를 달려왔다... 완전 뿌듯 🥹)</p>
<h3 id="before">Before</h3>
<p><strong>TODO scan 기능 추가 전</strong>
<a href="https://github.com/Judy-Choi/pr-agent-study/pull/3">https://github.com/Judy-Choi/pr-agent-study/pull/3</a></p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/e413407e-23eb-4851-9a5f-ac2181ab7036/image.png" alt=""></p>
<h3 id="after">After</h3>
<p><strong>공식문서에 옵션 박제📌</strong>
<img src="https://velog.velcdn.com/images/judy_choi/post/27bd9c82-a264-4f75-904b-5060543d80f2/image.png" alt=""></p>
<p><strong>PR 코드에 TODO 주석이 있는 경우</strong>
<a href="https://github.com/Judy-Choi/pr-agent-study/pull/8">https://github.com/Judy-Choi/pr-agent-study/pull/8</a></p>
<p>✅ TODO sections 생성
✅ TODO 주석 요약 정보(최대 6단어)를 제공하여 사용자가 빠른 확인 가능</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/2318fa1b-46e6-444b-b3b8-d70f4b8d8cc9/image.png" alt=""></p>
<p>TODO sections 를 펼치면?
✅ 싱글라인 TODO 주석은 텍스트 그대로 보여주지만
✅ 멀티라인 TODO 주석은 텍스트를 <code>&lt;block&gt;</code> 처리하여 깔끔하게 보인다.</p>
<p>이외에 사용자 편의성을 고려하여
✅ 파일 링크를 클릭하면 즉시 TODO 주석의 위치로 이동한다.</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/9999dad5-f6aa-4aab-983a-7d811296454b/image.png" alt=""></p>
<p><strong>TODO 주석이 없는 경우</strong>
<a href="https://github.com/Judy-Choi/pr-agent-study/pull/7">https://github.com/Judy-Choi/pr-agent-study/pull/7</a></p>
<p>✅ No TODO sections 표시
TODO sections 가 아예 생성되지 않는 경우, TODO scan 기능이 비활성화되어 있는 경우와 구분할 수 없는 단점을 고려했다.</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/944adb28-e889-470c-a091-ad25d04cb389/image.png" alt=""></p>
<hr>
<h2 id="회고">회고</h2>
<h3 id="잘한-점">잘한 점</h3>
<p><strong>첫 오픈소스 기여 도전, 대성공!</strong>
처음으로 오픈소스에 기여해 보았는데 결과가 매우 만족스럽다.
아이디어 단계부터 Maintainer 가 매우 마음에 들어하며 적극적으로 PR을 열길 원했고,
아이디어를 구현하는 동안 새로운 아이디어와 피드백이 더해져 완성도 높은 기능을 만들 수 있었다.
개인적으로 나는 새로운 시도와 발상을 선호하는 편이라 (tmi : MBTI = ENFP 🤩)
TODO 주석을 수집해서 사용자에게 보여준다는, 우리 조의 간단하지만 개발자들에게 매우 유용한 기능을 추가하는 기여가 정말 마음에 들었고, 기능이 완성된 후 공식문서에 등재된 것을 보니 매우 뿌듯했다.</p>
<p><strong>오픈소스 사용자에서 기여자로</strong>
오픈소스를 가져다 쓰기만 했는데, 처음으로 기여하겠다는 목적으로 오픈소스를 리뷰하니 의외로(?) 기여할 수 있는 점이 많았다.
우리 조는 기능을 추가했지만, 다른 조는 문서화, 리팩토링, 테스트 코드 추가 등등을 통해 기여하는 모습을 보고 무궁무진한 기여 방향을 간접적으로 체험할 수 있었다.
앞으로는 오픈소스를 사용할 때 좀 더 능동적인 사용자가 될 것 같다.
아쉬운 기능에 대해 개선 방안을 제안한다던가, 더 나은 코드를 제안한다던가.</p>
<p><strong>코드의 통일성, 사용자의 편의성, 가독성 모두 고려한 개발 경험</strong>
신규 기능 추가 및 유지보수 업무를 할 때, 기존 코드와의 통일성을 고려해 개발한 경험은 있지만
지금까지 실제 사용자의 편의성과 가독성까지 고려해 개발한 경험은 드물었다.
(특히 나는 AI와 백엔드 개발을 주로 하다 보니 이 점까지 고려할 일이 없었다.)</p>
<p>이번에는 우리가 만든 기능이 바로 사용자에게 제공되므로, 사용자 입장에서 생각해 보고 편의성, 가독성까지 모두 고려하여 개발했다.
나의 아이디어 중 하나를 예로 들면, 멀티라인 TODO 주석을 보여 줄 때 단순히 주석을 보여주는 것을 넘어 어떻게 보여주면 가독성이 좋을지 한번 더 고민하고, 팀원들의 의견을 모아 반영했다.
<img src="https://velog.velcdn.com/images/judy_choi/post/62878ba0-8276-4b04-8d04-99b82a698ef8/image.png" alt=""></p>
<p>이외에도 멘토님과 Maintainer 의 피드백대로 TODO 섹션을 접어서 깔끔한 UI를 구현했고,
제공하는 정보의 양이 줄어드는 대신 한번에 많은 양의 TODO 주석 정보를 파악할 수 있게 주석 내용을 요약해 제공했다.</p>
<p>Maintainer 또한 그래픽과 프롬프트 등을 꼼꼼하게 검토 후 피드백을 건넸고,
그 결과 초안과 비교하여 코드의 완성도도 높아졌고, UI 측면에서는 깔끔해 보이는데다 정보가 한 눈에 들어와서 사용하기에 편리하다! 🤩</p>
<p><strong>팀 프로젝트를 통해 협업하는 자세와 소통 스킬 향상</strong>
집단 지성이 모여 더 완성도 높은 코드와 편리하게 사용할 수 있는 개발물이 탄생했다!
아이디어를 모아 반영하는 과정 속에서 협업과 소통 스킬을 다듬을 수 있었고
특히 프로젝트 후반에 멘토님과 Maintainer 에게 많은 양의 피드백을 받았을 때, 작업 내용을 빠르게 리스트업하고, 작업 현황을 실시간으로 공유하여 팀원들 간 작업이 겹치지 않도록 했다.
덕분에 우리 팀은 끝까지 효율적인 협업과 원활한 소통이 가능했다.</p>
<p><strong>Git 커밋 메시지 컨벤션 복습</strong>
커밋을 자주 날리는 동안 직관적이고 압축적인 커밋 메시지를 작성할 수 있게 되었고, 이 과정에서 커밋 메시지 컨벤션을 복습했다.
나는 add, remove, update 등등을 사용해 왔는데, 이번 기회에 알고 보니 이것들은 커밋 태그로 적합하지 않았다.
(fix, feat 등이 적절한 태그였다. 😂)
그리고 이번에 처음으로 프롬프트 수정, UI개선 커밋도 작성 같은 커밋도 작성하면서 refactor, style 과 같은 태그도 처음 써 보았다.</p>
<p><strong>문제를 정의하고 해결하는 프로세스 습득</strong>
멘토님의 가이드를 따라 처음부터 step by step 으로 오픈소스에 기여하기 위한 과정을 거쳤다.
나는 AI로 커리어를 전환한 이래, 부트캠프 제외하고 이렇게 한 단계씩 멘토님이 이끌어 주는 경험이 처음이었다. 🥲
앞으로는 문제 정의부터 해결 및 개발까지 혼자서도 차근차근 해낼 수 있을 것 같다.</p>
<p><strong>끝까지 해냈다. 완성도 있게!</strong>
공식 프로젝트 기간인 6주가 지난 후에도 우리는 끝까지 하던 작업을 지속했고,
결과적으로 우리가 제안한 기능을 완성도 높게 개발하여 성공적으로 오픈소스에 기여할 수 있었다.</p>
<p>특히 나는 어떤 일을 빠르게 시작하는 추진력이 좋지만, 지속적으로 끌고 나가고 마무리하는 힘이 부족하다고 생각해 왔는데
이번에는 반대로 초반에 적극적으로 나서지 못한 만큼 프로젝트 중-후반에 팀에 기여하려고 노력했다.
주어진 피드백들을 반영하고, 최종 PR 내용을 취합하고 내용을 덧붙이고, 일정을 계산해 제안하는 등등.
<strong>나는 이번 기회에 나의 한계를 극복했다. 이제 나는 추진력도 강하고 마무리도 잘 하는 사람이다!!!! 😎</strong></p>
<p>물론 다른 팀원분들도 대단했다 🌟
조장님은 매주 조원들을 리딩하는 동시에 밋업 때마다 바쁘게 회의록을 작성하여 문서화로 활동을 뒷받침했고
MVP 팀원분은 어려운 코드도 척척 구현해내며 대부분의 주요 기능들을 개발하셨다!</p>
<h3 id="아쉬운-점">아쉬운 점</h3>
<p><strong>늦은 합류로 인한 도미노 효과</strong>
OT및 1주차에는 여행중이라 충분히 안내를 숙지하지 못한 상태에서 뒤늦게 합류했고,
2주차까지 재취업 면접 준비와 극심한 번아웃 때문에 스터디 진도를 따라가지 못했다.
3주차부터 따라잡기 시작했지만 초반에 PR Agent 자료조사를 놓쳤더니 이후에 좋은 기여 아이디어가 생각나지 않았다.
또한 초반에 공식 문서를 읽고 컨트리뷰션 가이드를 숙지해야 했는데, 진도 따라가기에 벅차서 일단 넘어갔더니(...)
이후에 내가 제안한 기능이 사실은 유료버전에 이미 있는 기능이라 퇴짜를 맞기도 했다 😇</p>
<p><strong>시야는 좁은데 의욕이 앞섰다</strong>
의욕이 앞서서 개선점은 많이 제시했는데, 그 중 정말 기여할 가치가 있는 개선점은 드물었고 개선 시 기대 효과도 크지 않았다.
또, 내가 아이디어를 낸 개선점들은 나중에 다른 팀원들의 아이디어들과 비교해 보니 너무 지엽적이었던 것 같다.</p>
<p>나는 전 직업이 악성코드 분석가이다 보니 숲보다는 나무를 보는 경향이 있다.
일단 코드를 돌려보고, 파고들어서 버그나 에러를 포착하곤 하지만
돌이켜 생각해 보니 이게 실제로 서비스를 만들거나 품질을 개선하는 데에 중요하지는 않았던 것 같다.
한 가지 경우의 에러 메시지를 가독성 좋게 수정하는 것과, 우리 조의 기여와 같이 사용자에게 유용한 기능을 새로 추가하는 것, 둘 중 어느 쪽이 더 기여할 가치가 있겠는가.</p>
<p><strong>청춘을 돌려다오 😂</strong>
이번 오픈소스 컨트리뷰션 프로그램은 &#39;체험형&#39; 인 만큼 대학생 참가자들이 많았다.
아직 학생일 때에 멘토링을 받으며 차근차근 성장해 나가는 모습이 부러웠고
실제로 일부 기능은 유료로 제공중인, 완성형 서비스를 분석하고 코드리뷰하며 실무를 미리 경험하는 모습도 부러웠다.
내가 대학생이었을 때 이런 프로그램이 있었다면 정말 좋았을텐데!
아니면 몇 년 전에라도 이런 프로그램에 참여했다면 내 커리어가 조금은 더 나아졌을텐데. 🥲</p>
<hr>
<h2 id="archive">Archive</h2>
<ul>
<li><a href="https://github.com/ossca-2025/pr-agent-mentoring/tree/main">멘토링 레포지토리</a></li>
<li><a href="https://www.notion.so/PullPull-1d8069613032805eacabea94dd6b60da">팀 Notion</a></li>
<li><a href="https://github.com/PullPullers/pr-agent-qodo/pull/1/commits">팀 레포지토리 - PR</a></li>
<li><a href="https://github.com/qodo-ai/pr-agent/pull/1869">최종 PR</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[OSSCA] 2025 체험형-1차 : 합격 후기]]></title>
            <link>https://velog.io/@judy_choi/OSSCA-2025-%EC%B2%B4%ED%97%98%ED%98%95-1%EC%B0%A8-%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@judy_choi/OSSCA-2025-%EC%B2%B4%ED%97%98%ED%98%95-1%EC%B0%A8-%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Wed, 18 Jun 2025 13:11:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>OSSCA &#39;참여형&#39; 프로그램 멘티 모집을 앞두고, 최근 활동을 마친 &#39;체험형&#39; 지원서 작성 경험을 공유합니다.</p>
</blockquote>
<p>(따로 보관해두지 않고 기억을 되짚어 작성하는 포스팅이므로 실제로 제가 작성했던 지원서 내용과 조금 다를 수 있습니다.)</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/0aca3a80-9df8-4670-9c2c-65ab063c16fb/image.png" alt=""></p>
<hr>
<p>약 2년 전, 입사 면접 자리에서 면접관에게 이런 질문을 받았다.</p>
<blockquote>
<p>면접관 : 본인의 코딩 실력이 어느 정도라고 생각하시나요?
나 : 코딩 실력을 판별하는 기준이 뭔가요?
면접관 : 오픈소스에 기여한다던지요.</p>
</blockquote>
<p>합격 후 면접관은 나의 사수 겸 동료가 되었고, 이상하리만치 그날의 면접 질문이 오래도록 기억에 남았다.
(그분은 나와 같은 직무에 동갑내기라 경쟁 의식이 발동해서 더 뇌리에 깊게 남은 것 같다.)</p>
<p>글또 반상회에서 만난 개발자 동기에게 간단한 오타 수정부터 시작하여 오픈소스에 기여한 경험담을 듣기도 했지만
첫 발을 떼는 게 어렵다고, 나는 막연하게 엄두가 나지 않아 오픈소스의 세계 = 고수들의 세상 같았다.</p>
<p>그러던 차에 오픈소스에 기여할 수 있는 프로그램을 발견했다!</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/ae6fcf5f-db28-4221-8269-8ce1cd095587/image.png" alt=""></p>
<p>홈페이지 : <a href="https://www.oss.kr/contribution_academy">https://www.oss.kr/contribution_academy</a></p>
<blockquote>
<p>단계별 오픈소스 컨트리뷰션 아카데미 프로그램</p>
</blockquote>
<ul>
<li>(입문형 과정) 대학(원)생 대상 공개SW 소개 및 공개SW 프로젝트 소개 등 공개SW 인식 확산을 위한 단기 특강 프로그램</li>
<li><strong>(체험형 과정) 공개SW 개발 참여 및 기여를 위한 협업 도구 교육 및 주요 분야별 프로젝트 실습 및 체험 프로그램</strong></li>
<li>(참여형 과정) 멘토·멘티가 한팀을 이루어 산업에서 활용 가능한 오픈소스 프로젝트 기능 개선 및 다양한 기여를 직접 경험을 통한 역량 강화 프로그램</li>
</ul>
<p>내가 참여한 과정은 &#39;체험형&#39; 과정으로, 말 그대로 오픈소스에 기여하기 위한 과정을 &#39;체험&#39; 하는 것이 목적이다.
따라서 대부분의 멘티분들이 대학생이었다. (아마 멘티들 중 내가 최고령자 아니었을까.. 😇)
멘토님께서도 프로그램 말미에 &#39;체험&#39; 이 목적이기 때문에 반드시 오픈소스에 우리의 코드가 Merge 되지 않아도 괜찮다고 격려해 주셨다.</p>
<p>체험형 프로젝트는 다음 중 1지망, 2지망을 골라 지원할 수 있었고
<img src="https://velog.velcdn.com/images/judy_choi/post/37191baf-afd6-4865-8052-1b66e5d50969/image.png" alt=""></p>
<p>나는 운 좋게 다음 프로젝트 목록 중 1지망으로 적어 낸 &#39;PR Agent&#39; 프로젝트에 선발되었다!
<img src="https://velog.velcdn.com/images/judy_choi/post/34c2feb2-53ef-4248-87b3-b001351b9909/image.png" alt=""></p>
<p><strong>PR-Agent</strong></p>
<ul>
<li>자동화된 GitHub PR(Pull Request) 분석/요약, 피드백, 코드 제안 등을 위한 AI 기반 도구</li>
<li>PR 기반의 코드리뷰 과정에서 AI의 도움을 받아 코드리뷰 과정을 효과적,효율적으로 진행하는데 도움을 받을수 있는 도구</li>
<li><a href="https://github.com/qodo-ai/pr-agent">https://github.com/qodo-ai/pr-agent</a></li>
</ul>
<h2 id="지원서-문항">지원서 문항</h2>
<p>지원서 문항은 크게 1지망, 2지망 프로젝트를 선택하고, 각각의 지망 프로젝트 모두 대답해야 할 질문은 다음 3가지로 동일하다.</p>
<ul>
<li>자기소개<ul>
<li>ex. 관심 분야, 학력, 전공, 프로젝트에 관심을 갖게 된 계기 등</li>
</ul>
</li>
<li>지원동기</li>
<li>프로젝트 개발 경험 (있으면 작성, 없으면 PASS)</li>
</ul>
<p>나는 가급적 내가 처한 상황과 경험을 살려 진솔하게 작성했다.
특히 내가 지원하는 프로젝트가 나에게 왜 필요한지 (잘 맞는지) 를 최대한 어필하려고 노력했다.</p>
<h3 id="자기소개">자기소개</h3>
<p>나는 AI Engineer 라고 소개하며 현재 회사에서 맡고 있는 직무에 대해 적었다.
지원 당시 나는 SI 회사에서 보험 상품 상담용 챗봇을 만드는 프로젝트에 투입되어
LLM, LangChain 과 RAG를 이용해 챗봇을 개발/개선/유지보수하는 업무를 맡고 있었다.</p>
<p>자기소개 작성 예시 중 &#39;프로젝트에 관심을 갖게 된 계기&#39; 가 있는데, 바로 뒤에 작성할 지원동기와 일부 내용이 겹칠 것 같아서 지원서 작성할 때 고민했던 기억이 난다.</p>
<h3 id="✅-지원동기-이-포스팅의-핵심">✅ 지원동기 (이 포스팅의 핵심!)</h3>
<blockquote>
<p>&#39;AI Engineer 로서 회사 팀 내에서 모두가 겪는 문제를 AI를 이용해 해결하고 싶다&#39;</p>
</blockquote>
<p>지원동기를 한 줄로 요약하면 위와 같고,
가급적 진솔하게, 현재 내가 어떤 상황에 처해 있는지 예시를 들어 설명했으며,
이 상황을 타개하기 위해 프로그램에 지원한다는 메시지를 담았다.</p>
<ul>
<li><p><strong>서론 : 당시 상황</strong></p>
<ul>
<li>SI회사 특성상 고객과 약속한 개발물을 정해진 날짜까지 개발하는 것이 가장 중요함</li>
<li>적은 수의 개발자들이 일정에 쫓겨 개발하는 상황</li>
<li>빠르게 코드를 작성하고 커밋하다 보니 코드 관리가 되지 않았다</li>
<li>모두가 코드리뷰의 필요성을 절감했지만 바빠서 여유가 없었다</li>
</ul>
</li>
<li><p><strong>본론 : 결정적 계기</strong></p>
<ul>
<li>그러던 어느 날, 누군가가 PR 제목을 <strong>&#39;baepo&#39;</strong> 라고 적은 것을 발견 (!!!!!!)</li>
<li>사태의 심각성을 깨달음</li>
<li>나는 팀 내 유일한 AI Engineer 로서 나와 우리 팀 모두가 겪고 있는 어려움을 AI를 이용해 해결하고 싶었다</li>
</ul>
</li>
<li><p><strong>결론 : 주요 지원동기 마무리 + 부수적인 지원동기 추가</strong></p>
<ul>
<li>AI(LLM) 를 이용한 코드리뷰 툴인 PR Agent 를 익혀서 팀 내에 도입해서 모두의 어려움을 해결하고 싶다</li>
<li>또한 당시 나는 AI Agent 에 막 입문하여 LangGraph 를 이용한 토이 프로젝트를 만들어 보았고, AI Agent 스터디에 참여하고 있었음</li>
<li>실제 서비스에서 Agent 가 어떻게 쓰이는지도 이번 기회에 알아보고 싶다</li>
</ul>
</li>
</ul>
<h3 id="프로젝트-개발-경험">프로젝트 개발 경험</h3>
<p>나는 지금까지 참여한 사이드 프로젝트에 대해 적어 냈던 것 같고.. 현업 프로젝트 경험도 적었는지는 기억나지 않는다.
단, 최근 참여형 프로젝트 지원서를 작성해 보니 프로젝트 개발 경험 문항은 글자 수 제한이 상대적으로 적게 걸려 있었다.
약 200자 정도? 되는 것 같다.
체험형도 글자 수 제한이 있었는지는 기억나지 않는다.</p>
<h2 id="마무리">마무리</h2>
<p>나는 모든 스터디/프로젝트에 지원할 때, 나의 현재 상황을 설명하고, 따라서 이 스터디가 왜 나에게 꼭 필요한지 어필하는 편이다.
그리고 스터디에서 잘 배워서 실무에 도입해 성과를 내겠다는 어필을 하면 100% 뽑히곤 했다.
(사실 멘토님께 나를 왜 뽑으셨는지 여쭤보지 않아서 부족한 나의 어떤 점을 보고 감사히 뽑아주셨는지는 모르겠다. 감사합니다 멘토님! 🥰)</p>
<p>단, 나의 경우 오픈소스에 기여하는 과정을 체험하는 &#39;체험형&#39; 프로그램에 지원하고 합격했지만
곧 있을 &#39;참여형&#39; 프로그램은 실제로 오픈소스에 기여하는 것이 목적이기 때문에
체험형보다 좀 더 빡빡하고, 긴 기간동안 진행되며, 중급 이상의 개발 능력자를 뽑는다는 팁을 들었다.</p>
<p>부디 참여형 프로그램에도 합격해서 합격 후기를 작성할 수 있었으면...! 🙏</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NMT] COMET : 신경망 기반 번역 품질 평가 지표]]></title>
            <link>https://velog.io/@judy_choi/NMT-COMET-%EC%8B%A0%EA%B2%BD%EB%A7%9D-%EA%B8%B0%EB%B0%98-%EB%B2%88%EC%97%AD-%ED%92%88%EC%A7%88-%ED%8F%89%EA%B0%80-%EC%A7%80%ED%91%9C</link>
            <guid>https://velog.io/@judy_choi/NMT-COMET-%EC%8B%A0%EA%B2%BD%EB%A7%9D-%EA%B8%B0%EB%B0%98-%EB%B2%88%EC%97%AD-%ED%92%88%EC%A7%88-%ED%8F%89%EA%B0%80-%EC%A7%80%ED%91%9C</guid>
            <pubDate>Sun, 08 Jun 2025 15:32:36 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>부제 : ChatGPT 를 이용해 10분만에 이해하는 COMET</p>
</blockquote>
<h2 id="comet">COMET?</h2>
<p><strong>Crosslingual Optimized Metric for Evaluation of Translation</strong></p>
<ul>
<li>최근 몇 년간 주목받고 있는 신경망 기반 번역 품질 평가 지표</li>
<li>기존의 BLEU, METEOR, TER처럼 표면적 일치에 의존하지 않고, 의미적 유사성을 포착하려고 설계된 모델</li>
<li>특히 reference-based 또는 reference-less (QE-style) 평가에도 활용될 수 있어서 유연성이 뛰어남</li>
</ul>
<h3 id="🌟-핵심-개요">🌟 핵심 개요</h3>
<p>COMET은 크게 세 가지 버전으로 나뉨.</p>
<p><strong>COMET (reference-based)</strong>
→ source, MT, reference 삼자를 입력으로 사용해 품질을 예측.</p>
<p><strong>COMET-QE (Quality Estimation)</strong>
→ source와 MT만 보고 reference 없이 예측.</p>
<p><strong>COMETKiwi (word-level QE)</strong>
→ 각 단어 단위의 품질 추정까지 지원.</p>
<p>COMET은 OpenKiwi와 유사하게, regression task로 훈련되며 평가 기준은 인간 품질 평가 (e.g., DA scores, MQM 등)와 정합되는지를 본다.</p>
<h3 id="🧠-모델-구조">🧠 모델 구조</h3>
<p>COMET은 Transformer 기반 multilingual encoder (주로 XLM-RoBERTa)을 backbone으로 활용해 다음과 같은 입력을 인코딩한다:</p>
<p>** 입력 형태**</p>
<ul>
<li>source (원문)</li>
<li>mt (기계 번역 결과)</li>
<li>reference (정답 문장) ← reference-based인 경우</li>
</ul>
<p>모델은 다음과 같이 세 문장을 <code>&lt;SRC&gt;</code>, <code>&lt;MT&gt;</code>, <code>&lt;REF&gt;</code> 라는 prefix와 함께 연결해서 인코더에 넣는다.</p>
<pre><code class="language-python">&lt;S&gt; source_sentence &lt;/S&gt; &lt;T&gt; mt_sentence &lt;/T&gt; &lt;R&gt; ref_sentence &lt;/R&gt;</code></pre>
<p>이제 인코더는 각 문장의 contextual embedding을 학습하게 되며, 최종적으로 다음과 같이 sentence-level representation을 얻는다:</p>
<p>$\mathbf{h}<em>{\mathrm{source}}=\operatorname{Pool}(E(\mathrm{source}))$
$\mathbf{h}</em>{\mathrm{mt}}=\operatorname{Pool}(E(\mathrm{mt}))$
$\mathbf{h}_{\mathrm{reference}}=\operatorname{Pool}(E(\mathrm{reference}))$</p>
<p>여기서 $Pool$ 은 주로 <code>[CLS]</code> token 또는 mean pooling을 의미하고, $E$ 는 encoder output.</p>
<h3 id="🔧-회귀-모델-구성">🔧 회귀 모델 구성</h3>
<p>이후 sentence embedding 간의 관계를 입력으로 삼는 MLP 회귀 모델을 구성해 <strong>DA score (e.g., 0~1)</strong>을 예측한다.
주로 입력 feature로는 다음이 사용된다.</p>
<p>$\begin{array}{l}
\mathbf{x}=\left[\mathbf{h}<em>{\mathrm{mt}} ; \mathbf{h}</em>{\mathrm{ref}} ; \mathbf{h}<em>{\mathrm{src}} ;\right. \
\quad \mathbf{h}</em>{\mathrm{mt}}-\mathbf{h}<em>{\mathrm{ref}} ; \mathbf{h}</em>{\mathrm{mt}}-\mathbf{h}<em>{\mathrm{src}} ; \
\left.\quad \mathbf{h}</em>{\mathrm{mt}} \odot \mathbf{h}<em>{\mathrm{ref}} ; \mathbf{h}</em>{\mathrm{mt}} \odot \mathbf{h}_{\mathrm{src}}\right]
\end{array}$</p>
<p>여기서 $⊙$ 는 element-wise multiplication, $[;]$ 는 concatenation을 의미해.</p>
<p>이 feature를 MLP에 넣고, 출력 $\hat{y}$ 를 예측값으로 사용:
$\hat{y}=\operatorname{MLP}(\mathbf{x})$</p>
<p>Loss는 일반적으로 MSE:
$\mathcal{L}=\frac{1}{N} \sum_{i=1}^{N}\left(y_{i}-\hat{y}_{i}\right)^{2}$</p>
<h3 id="📊-훈련-및-평가-데이터셋">📊 훈련 및 평가 데이터셋</h3>
<ul>
<li><strong>WMT DA (Direct Assessment)</strong>: 인간 평가자가 부여한 품질 점수. COMET은 이와 정합되게 회귀하도록 학습됨.</li>
<li>다양한 언어쌍 및 도메인에서 훈련 가능.</li>
<li>훈련 데이터는 종종 augmentation 되기도 하고, multilingual pretraining을 기반으로 finetuning만 수행하는 경우도 많음.</li>
</ul>
<h3 id="💡-특징-및-장점">💡 특징 및 장점</h3>
<ul>
<li><strong>의미 기반 평가가 가능</strong> → lexical overlap이 낮아도 좋은 번역은 높은 점수 가능.</li>
<li><strong>reference 없이도 평가 가능</strong> (COMET-QE)</li>
<li><strong>fine-tuning 가능성</strong> → 특정 도메인이나 언어쌍에 맞게 커스터마이즈 용이</li>
<li><strong>human correlation 높음</strong> → WMT Metrics shared task 기준 SOTA 성능</li>
</ul>
<hr>
<h2 id="comet-score-분포에-따른-품질-예시">COMET score 분포에 따른 품질 예시</h2>
<ul>
<li>COMET 점수는 정규화된 확률값이 아니고, 회귀 모델이 예측한 실수(real number) 값.</li>
<li>이론적으로는 -∞부터 +∞까지 어떤 값도 가능</li>
</ul>
<h3 id="💙-bleu-score-vs-comet">💙 BLEU score vs COMET</h3>
<table>
<thead>
<tr>
<th>지표</th>
<th>값의 범위</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><strong>BLEU</strong></td>
<td><strong>0.0 ~ 1.0 (또는 0~100)</strong></td>
<td>정답과 n-gram이 얼마나 겹치는가</td>
</tr>
<tr>
<td><strong>COMET</strong></td>
<td><strong>이론상 제한 없음</strong> (보통 -1 ~ +1 사이)</td>
<td>의미 기반 품질 예측, 인간 평가 점수에 가까움</td>
</tr>
</tbody></table>
<h3 id="📐-실제-분포는">📐 실제 분포는?</h3>
<p>실험상 COMET 점수는 보통 -1 ~ +1 사이에 분포함.</p>
<ul>
<li>좋은 번역이면 0.5 이상,</li>
<li>완벽하거나 참조와 거의 동일하면 0.9 이상,</li>
<li>나쁘거나 의미가 어긋난 경우 0.0 이하,</li>
<li>완전히 엉망이면 -1.0 근처까지도 나옴.</li>
</ul>
<p>하지만! 이건 모델이 훈련된 범위에 따라 다를 수 있는데, 예를 들어:</p>
<ul>
<li>wmt22-comet-da는 DA (Direct Assessment) 점수와 잘 맞도록 학습되었기 때문에, 대부분 0~1 사이에서 나옴</li>
<li>그러나 여전히 정규화된 확률은 아니고, 해석은 상대적.</li>
</ul>
<h3 id="👀-예시">👀 예시</h3>
<pre><code class="language-python">&quot;ref&quot;: &quot;The dog is sleeping on the couch.&quot;

&quot;mt_1&quot;: &quot;The dog sleeps on the sofa.&quot; → COMET score: **0.91**
&quot;mt_2&quot;: &quot;The sofa is sleeping on the dog.&quot; → COMET score: **-0.3**
&quot;mt_3&quot;: &quot;asdf qwerty uiop.&quot; → COMET score: **-0.9**</code></pre>
<h3 id="🧪-해석--요약">🧪 해석 &amp; 요약</h3>
<ul>
<li>절대적 숫자보다는 상대 비교가 핵심</li>
<li>여러 MT 시스템의 출력들을 평가해서 어떤 시스템이 더 나은지를 비교하는 데 사용</li>
<li>예: 시스템 A 평균 COMET = 0.76, 시스템 B 평균 COMET = 0.81 → B가 더 좋다.</li>
</ul>
<hr>
<h2 id="code">Code</h2>
<p>HuggingFace에서 COMET을 바로 사용 가능!</p>
<h3 id="✅-패키지-설치">✅ 패키지 설치</h3>
<pre><code class="language-bash">pip install unbabel-comet</code></pre>
<p>또는 최신 Hugging Face 지원 버전으로 패키지 설치</p>
<pre><code class="language-bash">pip install git+https://github.com/Unbabel/COMET@master</code></pre>
<h3 id="🤖-reference-있는-경우-한국어-→-영어-번역-평가">🤖 (Reference 있는 경우) 한국어 → 영어 번역 평가</h3>
<pre><code class="language-python">from comet import download_model, load_from_checkpoint

# 최신 COMET 모델 다운로드 (WMT22에서 훈련된 DA 기준)
model_path = download_model(&quot;Unbabel/wmt22-comet-da&quot;)
model = load_from_checkpoint(model_path)

# 평가할 문장들 (한국어 원문, 기계 번역 결과, 참고 번역)
data = [
    {
        &quot;src&quot;: &quot;나는 오늘 아침에 커피를 마셨다.&quot;,        # 한국어 원문 (Source)
        &quot;mt&quot;:  &quot;I drank coffee this morning.&quot;,  # 기계 번역 결과 (MT)
        &quot;ref&quot;: &quot;I had coffee this morning.&quot;     # 참고 번역 (정답(Reference))
    },
    {
        &quot;src&quot;: &quot;그녀는 책을 읽는 것을 좋아한다.&quot;,
        &quot;mt&quot;:  &quot;She likes reading books.&quot;,
        &quot;ref&quot;: &quot;She enjoys reading books.&quot;
    },
    {
        &quot;src&quot;: &quot;날씨가 좋기 때문에 우리는 소풍을 갔다.&quot;,
        &quot;mt&quot;:  &quot;Because the weather was nice, we went on a picnic.&quot;,
        &quot;ref&quot;: &quot;We went for a picnic because the weather was nice.&quot;
    }
]

# 품질 평가 수행
results = model.predict(data, batch_size=8, gpus=0)  # GPU 사용 시 gpus=1

# 결과 출력
for i, result in enumerate(results[&quot;scores&quot;]):
    print(f&quot;Example {i+1} → COMET Score: {result:.4f}&quot;)</code></pre>
<p><strong>출력</strong></p>
<pre><code>Fetching 5 files: 100%|██████████| 5/5 [00:00&lt;00:00, 100342.20it/s]
Lightning automatically upgraded your loaded checkpoint from v1.8.3.post1 to v2.5.1.post0. To apply the upgrade to your files permanently, run `python -m pytorch_lightning.utilities.upgrade_checkpoint ../.cache/huggingface/hub/models--Unbabel--wmt22-comet-da/snapshots/2760a223ac957f30acfb18c8aa649b01cf1d75f2/checkpoints/model.ckpt`
Encoder model frozen.
Using default `ModelCheckpoint`. Consider installing `litmodels` package to enable `LitModelCheckpoint` for automatic upload to the Lightning model registry.
GPU available: True (mps), used: True
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs
Predicting DataLoader 0: 100%|██████████| 1/1 [00:04&lt;00:00,  4.45s/it]
Example 1 → COMET Score: 0.9587
Example 2 → COMET Score: 0.9689
Example 3 → COMET Score: 0.9509</code></pre><ul>
<li>COMET Score 는 일반적으로 0~1 범위에서 나오고, 1에 가까울수록 품질이 좋다고 평가됨.</li>
<li>사람 평가와의 상관도가 높기 때문에 실제 MT 성능 비교에도 자주 사용됨.</li>
</ul>
<h3 id="🤖-reference-없는-경우--comet-qe-한국어-→-영어-번역-평가">🤖 (Reference 없는 경우 : COMET-QE) 한국어 → 영어 번역 평가</h3>
<pre><code class="language-python">from comet import download_model, load_from_checkpoint

# reference-free 버전인 COMET-QE 모델 다운로드
model_path = download_model(&quot;Unbabel/wmt20-comet-qe-da&quot;)  # 또는 최신 QE 모델
model = load_from_checkpoint(model_path)

# 평가할 데이터: source와 MT만 제공 (reference 없음!)
data = [
    {
        &quot;src&quot;: &quot;나는 오늘 아침에 커피를 마셨다.&quot;,
        &quot;mt&quot;:  &quot;I drank coffee this morning.&quot;
    },
    {
        &quot;src&quot;: &quot;그녀는 책을 읽는 것을 좋아한다.&quot;,
        &quot;mt&quot;:  &quot;She likes reading books.&quot;
    },
    {
        &quot;src&quot;: &quot;날씨가 좋기 때문에 우리는 소풍을 갔다.&quot;,
        &quot;mt&quot;:  &quot;Because the weather was nice, we went on a picnic.&quot;
    }
]

# 품질 예측 수행 (reference 없이!)
results = model.predict(data, batch_size=8, gpus=0)

# 결과 출력
for i, score in enumerate(results[&quot;scores&quot;]):
    print(f&quot;Example {i+1} → COMET-QE Score: {score:.4f}&quot;)</code></pre>
<p><strong>출력</strong></p>
<pre><code>Fetching 5 files: 100%|██████████| 5/5 [02:32&lt;00:00, 30.56s/it]
Lightning automatically upgraded your loaded checkpoint from v1.3.5 to v2.5.1.post0. To apply the upgrade to your files permanently, run `python -m pytorch_lightning.utilities.upgrade_checkpoint ../.cache/huggingface/hub/models--Unbabel--wmt20-comet-qe-da/snapshots/2e7ffc84fb67d99cf92506611766463bb9230cfb/checkpoints/model.ckpt`
Encoder model frozen.
/Users/judy/Downloads/myenv/lib/python3.12/site-packages/pytorch_lightning/core/saving.py:195: Found keys that are not in the model state dict but in the checkpoint: [&#39;encoder.model.embeddings.position_ids&#39;]
Using default `ModelCheckpoint`. Consider installing `litmodels` package to enable `LitModelCheckpoint` for automatic upload to the Lightning model registry.
GPU available: True (mps), used: True
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs
Predicting DataLoader 0: 100%|██████████| 1/1 [00:00&lt;00:00,  1.01it/s]
Example 1 → COMET-QE Score: 0.5950
Example 2 → COMET-QE Score: 0.6835
Example 3 → COMET-QE Score: 0.2709</code></pre><h3 id="❗-차이점-요약">❗ 차이점 요약</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>Reference-based COMET</th>
<th>COMET-QE</th>
</tr>
</thead>
<tbody><tr>
<td>입력</td>
<td><code>src</code>, <code>mt</code>, <code>ref</code></td>
<td><code>src</code>, <code>mt</code> (❌ <code>ref</code>)</td>
</tr>
<tr>
<td>사용 모델</td>
<td><code>&quot;Unbabel/wmt22-comet-da&quot;</code></td>
<td><code>&quot;Unbabel/wmt20-comet-qe-da&quot;</code></td>
</tr>
<tr>
<td>태스크 종류</td>
<td>번역 평가 (DA 기반)</td>
<td>Quality Estimation (QE)</td>
</tr>
<tr>
<td>특징</td>
<td>정답이 있을 때 가장 정확함</td>
<td>빠르게 reference 없이 평가 가능</td>
</tr>
</tbody></table>
<h3 id="🎁-tip-모델-리스트">🎁 TIP: 모델 리스트</h3>
<table>
<thead>
<tr>
<th>목적</th>
<th>모델 이름</th>
</tr>
</thead>
<tbody><tr>
<td>일반 평가 (참조 포함)</td>
<td><code>Unbabel/wmt22-comet-da</code></td>
</tr>
<tr>
<td>QE (참조 없이)</td>
<td><code>Unbabel/wmt20-comet-qe-da</code></td>
</tr>
<tr>
<td>Word-level QE</td>
<td><code>Unbabel/COMET-KIWI</code></td>
</tr>
<tr>
<td>참조 없이 번역 품질 분류</td>
<td><code>Unbabel/wmt23-cometkiwi-da</code></td>
</tr>
</tbody></table>
<h3 id="💬-더-많이-더-빠르게-평가하려면-batch-confidence-score-포함">💬 더 많이, 더 빠르게 평가하려면? (batch, confidence score 포함)</h3>
<ul>
<li>다량의 데이터셋을 평가할 때는 pandas와 함께 .csv로 처리하는 방식도 효율적.</li>
<li>GPU를 사용할 경우 훨씬 빠르게 예측 (<code>gpus</code>=1로 설정)</li>
</ul>
<pre><code class="language-python">outputs = model.predict(data, batch_size=4, gpus=1, return_all=True)

for output in outputs[&#39;raw&#39;]:
    print(f&quot;Score: {output[&#39;score&#39;]:.4f}, Confidence: {output[&#39;confidence&#39;]:.4f}&quot;)```</code></pre>
<hr>
<h2 id="참고">참고</h2>
<h3 id="🚀-최신-comet-variants">🚀 최신 COMET variants</h3>
<ul>
<li><strong>COMET-22</strong>: WMT22 기준 최고 성능. XLM-RoBERTa 기반.</li>
<li><strong>COMETKiwi</strong>: 단어 및 span 단위 품질 예측도 가능.</li>
<li><strong>COMET-LLM</strong>: (2023 이후 논의) LLM을 활용한 평가도 실험 중.</li>
</ul>
<h3 id="🔍-참고-논문">🔍 참고 논문</h3>
<ul>
<li>Rei et al. (2020), &quot;COMET: A Neural Framework for MT Evaluation&quot;</li>
<li>Unbabel (2021-2023), WMT Metrics Shared Task 참가 보고서</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[LLM-MT 시리즈] LLM 시대의 관용구 번역]]></title>
            <link>https://velog.io/@judy_choi/LLM-MT-%EC%8B%9C%EB%A6%AC%EC%A6%88-LLM-%EC%8B%9C%EB%8C%80%EC%9D%98-%EA%B4%80%EC%9A%A9%EA%B5%AC-%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@judy_choi/LLM-MT-%EC%8B%9C%EB%A6%AC%EC%A6%88-LLM-%EC%8B%9C%EB%8C%80%EC%9D%98-%EA%B4%80%EC%9A%A9%EA%B5%AC-%EB%B2%88%EC%97%AD</guid>
            <pubDate>Thu, 27 Mar 2025 20:20:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 글은 LLM 관용구 번역 논문을 보고 깜짝 놀라서 새벽 4시에 의식의 흐름대로 작성한 글입니다. 😇</p>
</blockquote>
<p>쌩초보 NLP 연구자이던 2020년, 나는 2편의 관용구 기계번역 논문을 국내 학회에서 발표했다.</p>
<p><a href="https://drive.google.com/file/d/17ThvMC67_7CC3oIAP1I1aYEx0goPfoQQ/view?usp=drive_link">관용구 기계번역을 위한 한-영 데이터셋 구축 및 평가 방법</a></p>
<ul>
<li>한국어 관용구를 영어로 번역하기 위한 관용구 데이터셋(KISS) 구축 및 블랙리스트를 이용한 평가 방법</li>
</ul>
<p><a href="https://koreascience.kr/article/CFKO202030060830856.pdf">한-영 관용구 기계번역을 위한 NMT 학습 방법</a></p>
<ul>
<li>기계번역 모델에 관용구를 효과적으로 학습시키기 위해 특정 토큰을 삽입하여 문장에 포함된 관용구의 위치를 나타내는 방법</li>
</ul>
<p>그리고 2025년 현재, ChatGPT 를 위시한 LLM이 쏟아져 나온 오늘날,
LLM은 웬만한 관용구를 훌륭하게 잘 번역해 준다.</p>
<p>....그런데 LLM을 이용하는 새 관용구 번역 논문이 나왔네!?
논문 읽는 데에 <a href="https://www.themoonlight.io/ko">MoonLight</a> 가 그렇게 좋다던데, 한번 써서 읽어보자!</p>
<p>MoonLight 를 이용한 논문 리뷰 START!</p>
<h2 id="translate-meanings-not-just-words-idiomkbs-role-in-optimizing-idiomatic-translation-with-language-models">Translate Meanings, Not Just Words: IdiomKB&#39;s Role in Optimizing Idiomatic Translation with Language Models</h2>
<p><a href="https://arxiv.org/abs/2308.13961">https://arxiv.org/abs/2308.13961</a></p>
<h3 id="3줄-요약">3줄 요약</h3>
<ol>
<li>📚 다국어 관용구 지식 베이스(IDIOMKB) 구축을 통해 관용구의 비유적 의미를 생성.  </li>
<li>🔍 대형 언어 모델(LLM)을 활용하여 관용구의 의미를 효과적으로 검색하고 번역 과정에 통합.  </li>
<li>📈 소형 모델로도 관용구 번역 품질을 향상시키기 위한 새로운 방식의 평가 지표 제안.</li>
</ol>
<h3 id="idiomkb">IdiomKB</h3>
<p>나는 성격이 급하니 일단 논문 내용도 읽기 전에
<a href="https://github.com/lishuang-w/IdiomKB">IdiomKB</a> 가 뭔지 데이터셋부터 냅다 까보기 시작했다.</p>
<p>Knowledge Base 는 json 파일로 간단하게 만들었고...
일단 영어 데이터를 까보니 관용구 번호, 영어 관용구, 영어로 작성된 관용구의 의미,
그리고 추가 언어(중국어, 일본어)로 작성된 관용구의 의미를 확인할 수 있다.</p>
<pre><code class="language-json">[
    {
        &quot;id&quot;: 1,
        &quot;idiom&quot;: &quot;1 o&#39;clock \&quot;sharp\&quot;&quot;,
        &quot;en_meaning&quot;: &quot;punctually at 1 o&#39;clock; being exactly on time&quot;,
        &quot;zh_meaning&quot;: &quot;1点整&quot;,
        &quot;ja_meaning&quot;: &quot;厳密に1時に&quot;
    },
    {
        &quot;id&quot;: 2,
        &quot;idiom&quot;: &quot;Bob&#39;s your uncle&quot;,
        &quot;en_meaning&quot;: &quot;used to mean that something will be successful or easy to achieve&quot;,
        &quot;zh_meaning&quot;: &quot;完事了&quot;,
        &quot;ja_meaning&quot;: &quot;簡単な手順で問題が解決する&quot;
    },
...</code></pre>
<p>만약 한국어 관용구 데이터셋을 만든다면 나라면 이렇게 하겠다.</p>
<ol>
<li><p>관용구 사전 다운로드
<a href="https://stdict.korean.go.kr/">국립국어원 표준국어대사전</a> 사이트에서 관용구 사전을 다운로드한다.
(참고로 관용구뿐만 아니라 속담, 외래어, 혼종어 사전도 다운로드 가능!)</p>
</li>
<li><p>다운로드한 사전을 파싱하여 한국어 관용구 KB를 똑같은 포맷으로 작성
(예시)</p>
<pre><code class="language-json">[
 {
     &quot;id&quot;: 1,
     &quot;idiom&quot;: &quot;눈이 낮다&quot;,
     &quot;ko_meaning&quot;: &quot;보는 수준이 높지 아니하다.&quot;,
 },
...</code></pre>
</li>
</ol>
<p>참고로 표준국어대사전의 경우 각 관용구마다 예문도 제공하므로 데이터셋 포맷을 변경해서 예문을 추가해도 좋겠다.</p>
<pre><code class="language-json">[
    {
        &quot;id&quot;: 1,
        &quot;idiom&quot;: &quot;눈이 낮다&quot;,
        &quot;ko_meaning&quot;: &quot;보는 수준이 높지 아니하다.&quot;,
          &quot;ko_example&quot; : &quot;난 눈이 낮은 편인지 웬만한 여자는 다 예뻐 보인다.&quot;,
    },
...</code></pre>
<p>아차차, 나 논문 읽다 말았지.</p>
<h3 id="논문에-대한-심층-분석-및-기술적-설명-한국어">논문에 대한 심층 분석 및 기술적 설명 (한국어)</h3>
<p>이 논문은 기계 번역(MT) 시스템과 범용 언어 모델(LM)이 관용적인 표현을 효과적으로 번역하는 데 어려움을 겪는다는 점에 주목하고, 이를 해결하기 위해 <strong>IDIOMKB</strong>라는 다국어 관용구 지식 베이스를 제안합니다. IDIOMKB는 대규모 언어 모델(LLM)을 활용하여 관용구의 비유적 의미를 추출하고, 이를 작은 규모의 모델(예: BLOOMZ, Alpaca, InstructGPT)이 관용적 텍스트를 더 정확하게 번역할 수 있도록 돕는 것을 목표로 합니다. 또한, GPT-4를 기반으로 한 새로운 자동 평가 지표를 개발하여 인간의 평가와 더 잘 일치하도록 하고, IDIOMKB가 모델의 성능을 향상시킨다는 것을 입증합니다.</p>
<p><strong>핵심 방법론 (IDIOMKB 구축 및 활용)</strong></p>
<p>핵심 방법론은 크게 IDIOMKB 구축과 IDIOMKB를 활용한 관용적 번역 개선, 그리고 GPT-4 기반 평가 지표 개발로 나눌 수 있습니다.</p>
<ol>
<li><p><strong>IDIOMKB 구축 (지식 증류)</strong></p>
<ul>
<li><p><strong>데이터 수집:</strong> 영어, 중국어, 일본어의 기존 관용구 데이터셋(MAGPIE, IMIL, EPIE, PIE, PETCI, CCT, ChID, OpenMWE, ID10M)을 통합하여 다국어 관용구 목록을 구축합니다. 각 데이터셋의 특성(예: 문맥 정보, 번역 정보, 의미 정보)을 고려하여 가장 포괄적인 목록을 확보합니다.</p>
</li>
<li><p><strong>의미 추출:</strong> LLM(GPT-3.5 시리즈, BLOOM, BLOOMZ)의 강력한 생성 능력을 활용하여 관용구의 비유적 의미를 추출합니다. 이는 <strong>지식 증류(Knowledge Distillation)</strong>의 한 형태로, 큰 모델(LLM)의 지식을 작은 모델이 활용할 수 있도록 하는 방법입니다. 구체적으로, <strong>In-Context Learning</strong> 방식을 사용하여 LLM에 관용구와 그 의미에 대한 예시를 제공하고, 새로운 관용구에 대한 비유적 의미를 생성하도록 합니다.</p>
</li>
<li><p><strong>프롬프트 엔지니어링 (Prompt Engineering):</strong> LLM이 관용구의 비유적 의미를 정확하게 생성하도록 유도하기 위해 신중하게 설계된 프롬프트를 사용합니다. 프롬프트에는 관용구의 비구성적(Non-compositional) 특성을 강조하고, literal한 의미와 figurative한 의미의 차이를 명확히 설명하는 지시사항이 포함됩니다. 또한, 다양한 언어 쌍에 대해 여러 예시를 제공하여 LLM이 문맥을 이해하고 적절한 의미를 생성하도록 돕습니다. 프롬프트 예시는 다음과 같습니다.</p>
<pre><code>/* Task prompt */
Given a Chinese idiom, please write the idiom’s figurative English meaning. Please note: Idiom always expresses figurative meaning which is different from literal meaning of its constituent words.
/* Examples */
Case 1:
Chinese idiom: 明目张胆
English meaning: straightforwardly, without any concealment
...
/* Test Data */
Case 5:
Chinese idiom: 一气呵成
English meaning: to complete a task or work in one go, without stopping or taking a break</code></pre></li>
</ul>
</li>
<li><p><strong>IDIOMKB를 활용한 관용적 번역 개선 (KB-CoT)</strong></p>
<ul>
<li><strong>관용구 식별:</strong> 번역 대상 문장에서 관용구를 식별합니다. 이 논문에서는 관용구 식별을 별도의 단계로 처리하지 않고, 기존 데이터셋의 관용구-문장 쌍을 사용합니다.</li>
<li><strong>의미 검색:</strong> IDIOMKB에서 식별된 관용구에 해당하는 비유적 의미를 검색합니다.</li>
<li><strong>프롬프트 구성:</strong> LLM에 제공되는 프롬프트에 검색된 관용구의 의미를 포함합니다. 이는 <strong>Chain-of-Thought (CoT) Prompting</strong>과 유사한 방식으로, LLM이 관용구의 비유적 의미를 이해하고 번역에 반영하도록 돕습니다. Direct Prompting과는 대조적으로, KB-CoT는 LLM이 관용구의 non-literal한 의미에 집중하도록 유도하여 더 정확한 번역을 생성합니다.</li>
</ul>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/549e8d39-8e04-40f3-8b48-c794b0d7de70/image.png" alt=""></p>
<ol start="3">
<li><p><strong>GPT-4 기반 평가 지표 개발</strong></p>
<ul>
<li><p><strong>평가 기준 설정:</strong> 관용적 번역의 품질을 평가하기 위한 상세한 기준을 설정합니다. 이 기준은 1점(오역 또는 literal한 의미만 번역), 2점(기본적인 비유적 의미는 전달하지만 불완전함), 3점(정확하고 문화적 뉘앙스까지 반영한 우수한 번역)으로 구성됩니다.</p>
</li>
<li><p><strong>프롬프트 구성:</strong> GPT-4가 번역 품질을 평가하도록 유도하기 위해 명확하고 상세한 프롬프트를 설계합니다. 프롬프트에는 평가 기준이 명시되어 있으며, 번역 대상 문장과 번역 결과가 함께 제공됩니다.</p>
<pre><code>/* Task prompt */
Evaluate the idiom translation in the given Chinese translation of an English sentence. Focus on the idiom’s figurative meaning.
/* Evaluation Criteria */
1 point: Ignores, mistranslates, or only translates the literal meaning of the idiom.
2 points: Conveys basic figurative meaning but may lack refinement or have minor imperfections.
3 points: Exceptional translation, accurately conveying figurative meaning, context, and cultural nuances.
/* Test Data */
Evaluate the following translation:
English sentence: &lt;source&gt;
Idiom in the English sentence: &lt;idiom&gt;
Chinese translation: &lt;translation&gt;
Evaluation (score only):&lt;score&gt;</code></pre></li>
</ul>
</li>
</ol>
<p><strong>결론적으로,</strong> 이 논문은 IDIOMKB라는 다국어 관용구 지식 베이스를 구축하고, 이를 활용하여 작은 규모의 언어 모델의 관용적 번역 성능을 향상시키는 효과적인 방법을 제시합니다. 또한, GPT-4 기반의 새로운 자동 평가 지표를 개발하여 관용적 번역의 품질을 더 정확하게 평가할 수 있도록 하였습니다. IDIOMKB는 향후 관용적 표현의 교차 문화적 정렬 연구에도 유용한 자료가 될 것으로 기대됩니다.</p>
<h2 id="improving-llm-abilities-in-idiomatic-translation">Improving LLM Abilities in Idiomatic Translation</h2>
<p><a href="https://arxiv.org/abs/2407.03518">https://arxiv.org/abs/2407.03518</a>
그리고 올해 1월, COLING 의 Low-Resource 워크샵에 후속 논문이 나왔다.</p>
<p>마찬가지로 MoonLight 의 도움을 받아서 읽어 보자.</p>
<h3 id="3줄-요약-1">3줄 요약</h3>
<ol>
<li>🤖 <strong>Semantic Idiom Alignment (SIA)</strong> 방식으로 문장의 의미를 임베딩한 후, 코사인 유사도를 활용해 목표 언어의 이디엄을 식별합니다.  </li>
<li>📝 <strong>LLM 기반 이디엄 정렬 (LIA)</strong> 기법으로 LLM이 직접적으로 적절한 이디엄의 대응어를 제안하도록 유도합니다.  </li>
<li>📊 <strong>인간 평가를 통해</strong> SIA가 이디엄 스타일을 효과적으로 보존하며, 여러 언어 쌍에서 더 높은 번역 정확도를 달성함을 입증합니다.  </li>
</ol>
<p>이 논문에서는 대형 언어 모델(LLMs)을 활용한 숙어 번역의 개선에 대해 다루고 있습니다. 특히, 숙어는 언어적이고 문화적으로 독특한 표현으로, 현재의 LLM은 숙어의 의미를 잘 이해하지 못해 그 결과가 문자 그대로의 의미로 해석되거나 부정확한 번역이 발생하는 경우가 많습니다. 예를 들어, &quot;break a leg&quot;를 한글로 번역하면 원래 의미와는 상관이 없는 비논리적인 문구가 될 수 있습니다. 이러한 문제를 해결하기 위해, 연구팀은 두 가지 방법을 제안했습니다.</p>
<h3 id="핵심-방법론">핵심 방법론</h3>
<ol>
<li><p><strong>Semantic Idiom Alignment (SIA) 방법</strong>: </p>
<ul>
<li>이 방법은 사전 훈련된 문장 임베딩을 활용하여 목표 언어의 숙어를 식별합니다.</li>
<li>구체적으로는, 영어 숙어의 의미를 여러 언어로 임베딩(classify)하고, 코사인 유사도(cosine similarity)를 사용하여 가장 적합한 목표 언어의 숙어를 검색합니다.</li>
<li>이 과정에서 임베딩된 벡터는 숙어의 의미를 포착하며, 두 벡터 간의 각도 코사인을 계산하여 유사성을 구합니다. 결과 값은 -1에서 1 사이의 범위이며, 0.7 이상의 유사도를 가진 숙어를 후보로 선택합니다.</li>
<li>후보 숙어가 발견되면, GPT-4에게 질문하여 문화적 및 상황적 맥락에 가장 적합한 숙어를 선택하도록 유도합니다.</li>
</ul>
</li>
<li><p><strong>Language-Model-based Idiom Alignment (LIA) 방법</strong>:</p>
<ul>
<li>이 방법은 LLM에게 해당 언어의 숙어를 제안하도록 프롬프트를 제공합니다.</li>
<li>모델은 최대 3개의 숙어 후보를 찾게 됨과 동시에, 필요 시 불일치도 허용됩니다. 이후 가장 적합한 후보를 선택하여 번역에 반영하게 됩니다.</li>
</ul>
</li>
</ol>
<h3 id="데이터셋-구축">데이터셋 구축</h3>
<p>논문에서는 영어, 중국어, 우르두어, 힌디어의 숙어 데이터를 사용하였습니다. </p>
<ul>
<li>각 언어의 데이터셋은 숙어, 그 의미, 예문으로 구성되어 있습니다.</li>
<li>영어-중국어 및 중국어-영어 번역에서는 MWE-PIE와 CCT &quot;cheng yu&quot; 데이터셋을 사용했습니다.</li>
<li>우르두어와 힌디어 데이터셋은 각각 2100개 이상의 숙어를 포함하고 있으며, 영어 숙어의 의미와 매칭을 통해 생성되었습니다.</li>
</ul>
<h3 id="평가-방법">평가 방법</h3>
<p>모델 성능을 평가하기 위해, 원문과 번역문을 비교하였습니다. GPT-4와 GPT-4o 모델, 그리고 인간 평가자들의 평가 결과를 비교하였으며, 번역의 정확성과 맥락적 적합성을 기준으로 점수를 매겼습니다.</p>
<h3 id="결과">결과</h3>
<ul>
<li>연구 결과 SIA 방법이 특히 숙어의 문맥 및 문화적 뉘앙스를 잘 유지하는 경향이 있음을 보여주었습니다.</li>
<li>LIA 방법도 나름의 장점을 가졌지만, SIA 방법에 비해 덜 효과적이었습니다.</li>
<li>자연어 처리 평가 역시 SIA 방법이 인간의 평가와의 일치도가 높았음을 나타냈습니다.</li>
</ul>
<p>결론적으로 이 연구는 LLM을 활용한 숙어 번역 개선에 기여하며, 문화적 표현을 유지하는 번역 기술의 기초를 다지는데 중요한 결과를 보여주고 있습니다. 이러한 접근법은 궁극적으로 다양한 언어의 의사소통을 더 풍부하고 정확하게 만들 수 있는 가능성을 지니고 있습니다.</p>
<h2 id="chatgpt-4o-로-대충-돌려보기">ChatGPT-4o 로 대충 돌려보기</h2>
<p>일단 대충 관용구랑 관용구 의미를 프롬프트로 넣어서 번역 품질이 개선되나 보자.</p>
<p>웬만한 관용구는 번역을 잘 해주는데...
<img src="https://velog.velcdn.com/images/judy_choi/post/25f2912f-2cea-4ccd-8e6a-b5dc17895eb7/image.png" alt=""></p>
<p>찾았다, 관용구 오역 중 한 가지 경우인 직역되는 경우!
<img src="https://velog.velcdn.com/images/judy_choi/post/49b4439b-ab82-498d-a0f8-9ffb84b1823c/image.png" alt=""></p>
<p>프롬프트 고!
<img src="https://velog.velcdn.com/images/judy_choi/post/5824c209-02ab-4955-a363-1be7f9c41fef/image.png" alt=""></p>
<p>혹시 문장부호 들어가서 그런가?
<img src="https://velog.velcdn.com/images/judy_choi/post/7b472a8b-2af6-4f59-a0e1-38c62214e377/image.png" alt=""></p>
<p>이런, 관용구 의미를 참고하라는 지시를 넣어 볼까?
<img src="https://velog.velcdn.com/images/judy_choi/post/2a8fa2d0-e00b-45da-9353-0cd2185b5e0b/image.png" alt=""></p>
<p>왜 관용구 의미가 제대로 반영이 안되지... 프롬프트 더 잘 넣어야 하나 😇😇😇</p>
<h2 id="느낀-점">느낀 점</h2>
<ul>
<li><p>두 논문 다 GPU 없이 실험이 가능했다(!)</p>
<ul>
<li>라떼는 말이야.. GPU 없으면 모델을 못 돌렸는데.. 😂😂</li>
</ul>
</li>
<li><p>이제는 대부분의 LLM 논문에서 LLM 모델명만 명시하는 경우도 많다</p>
<ul>
<li>라떼는 말이야.. 모델명이랑 레이어 갯수같은 파라미터도 명시했는데.. 😂</li>
</ul>
</li>
<li><p>실험 장벽이 좀 내려간 느낌</p>
<ul>
<li>실험에 필수적이지만 개인 연구자가 갖추기 어려웠던 장비 문제가 해결된 셈</li>
<li>실험 방법론 + 잘 구성된 프롬프트만으로 실험한 경우가 많이 보인다.</li>
</ul>
</li>
<li><p>두 논문 내용 잘 조합해서 한국어 관용구에 대해 실험을 해서 국내 학회에 제출해도 좋을 듯?</p>
</li>
<li><p>MoonLight 짱이다!</p>
<ul>
<li>Scientific 보다 한국어 사용자에게 훨씬 가독성 좋다(한국어 번역 제공)</li>
<li>심지어 처리 속도가 매우 빠름!</li>
<li>사용성도 매우 좋다. 유저가 사용하기 편리하게 화면과 기능이 구성되어 있다. 단락별로 돋보기 버튼 클릭하면 AI가 설명해 준다.</li>
<li>라떼는 말이야.. 논문 프린트하거나 아이패드에 넣어서 필기하고 조금씩 복사해서 번역기 돌리면서 읽었는데... 😂😂</li>
<li>이 기능이 약이 될 지 독이 될 지는 모르겠으나, <strong>요약 내용이 논문 원문과 조금 다른 부분이 보인다.</strong><ul>
<li>논문에는 <code>for instance, directly converting “break a leg” into a nonsensical phrase in the target language.</code> 즉 <code>break a leg</code>를 타겟 언어(한 ➡️ 영 번역하는 경우 영어를 말함) 로 번역한다는 문장만 있는데, Moonlight 의 요약 결과에는 <code>break a leg&quot;를 한글로 번역하면 원래 의미와는 상관이 없는 비논리적인 문구가 될 수 있습니다</code> 라고 나옴.</li>
</ul>
</li>
<li>프롬프트를 중간에 생략하기도 하고.. (그래서 본 포스팅에는 코드블록 대신 캡쳐 첨부)</li>
<li>그럼에도 논문 리서치하는 시간과 노력이 엄청 절약됨!</li>
<li><strong>유료버전 결제해서 쓸 가치가 충분함!!!!</strong></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[LLM-MT 시리즈] 초월번역기를 만들어 보자]]></title>
            <link>https://velog.io/@judy_choi/LLM-MT-%EC%8B%9C%EB%A6%AC%EC%A6%88-%EC%B4%88%EC%9B%94%EB%B2%88%EC%97%AD%EA%B8%B0%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%96%B4-%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@judy_choi/LLM-MT-%EC%8B%9C%EB%A6%AC%EC%A6%88-%EC%B4%88%EC%9B%94%EB%B2%88%EC%97%AD%EA%B8%B0%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%96%B4-%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Mon, 03 Mar 2025 10:58:00 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 포스팅은 FE 미니 반상회에서 발표한 내용을 다룹니다.</p>
</blockquote>
<ul>
<li>코드 및 발표자료 : <a href="https://github.com/Judy-Choi/agent_series/tree/main/note_1">GitHub</a></li>
</ul>
<h2 id="초월번역">&#39;초월번역&#39;?</h2>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/760f9c60-d7ac-4972-9192-cf0a1bf7204d/image.png" alt=""></p>
<blockquote>
<p>원문 : What were you guys smokin&#39; when you came up with that?
직역(Papago) : 그걸 생각해냈을 때 여러분은 무엇을 피우고 있었나요?</p>
</blockquote>
<ul>
<li><p>&#39;원문의 의미, 느낌을 직역한 것보다 더 효과적으로 표현한 번역을 뜻하는 말로 창작 이상의 창의적 번역 작품을 만났을 때 쓰는 표현&#39; - <a href="https://www.asiae.co.kr/article/2019062119263365756">아시아경제</a></p>
</li>
<li><p>&#39;외국어 원문의 뜻을 훼손하지 않고 현지 문화권의 느낌이 살아나도록 의역한 경우를 칭찬하는 표현&#39; - <a href="https://www.hani.co.kr/arti/specialsection/esc_section/885838.html">김경훈 한국트렌드연구소 소장</a></p>
</li>
</ul>
<h2 id="무슨-마약하시길래-이런-생각을-했어요">무슨 마약하시길래 이런 생각을 했어요?</h2>
<h3 id="llm을-이용한-번역기번역-서비스-개발">LLM을 이용한 번역기(번역 서비스) 개발</h3>
<p>요즘은 LLM 의 시스템 프롬프트에 출발어 - 도착어를 지정하고, context 를 번역하도록 작성하는 형태로 아주 간단하게 번역기를 개발합니다.</p>
<pre><code class="language-python">response = OpenAI(api_key=os.getenv(&quot;OPENAI_API_KEY&quot;)).chat.completions.create(
    model=&quot;gpt-4-turbo&quot;,
    messages=[
        {f&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;주어진 문장을 {source}에서 {target}(으)로 번역하세요.&quot;},
        {f&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: &quot;{text}&quot;}
    ]
)</code></pre>
<p>LLM 등장 이전에는 학습용 언어 데이터셋을 구축한 후
값비싼 GPU를 이용해 모델을 학습시켜 번역기를 개발하였으나
이제는 LLM API 만 구매하여 빠르고 쉽고 저렴하게 번역기를 구현할 수 있게 되었습니다.</p>
<h3 id="llm-multi-agent-를-이용한-번역-프레임워크">LLM Multi-Agent 를 이용한 번역 프레임워크</h3>
<p>하지만 여전히 기계번역이 언어의 장벽을 넘기가 매우 어려운 분야가 있는데,
문학 작품의 경우 언어 자체의 복잡성, 묘사적 표현, 문화적인 뉘앙스를 고려하여 번역해야 하기 때문에 여전히 인간 번역가가 필요한 장르 중 하나입니다.</p>
<p>이러한 한계를 극복하기 위해 문학 번역에 LLM Multi-Agent 를 적용한 논문이 공개되었습니다.
<a href="https://arxiv.org/abs/2405.11804">(Perhaps) Beyond Human Translation: Harnessing Multi-Agent Collaboration for Translating Ultra-Long Literary Texts</a></p>
<ul>
<li>이 논문에서는 대규모 언어모델(LLM)에 기반한 새로운 멀티 에이전트 프레임워크를 도입한다.</li>
<li>그리고 이를 이용하여 전통적인 출판 과정(publication process)을 모사한 가상의 회사 TRANSAGENTS를 실행한다.<ul>
<li>문학 작품 번역에 요구되는 복잡한 문제들을 해결하기 위하여, 다수의 에이전트로부터 결합된 능력을 활용한다.</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/c817d7f2-b68f-40ca-8a01-59293cff7feb/image.png" alt=""></p>
<ul>
<li><p>가상의 멀티 에이전트 번역 회사 TRANSAGENTS에는 다양한 역할이 있다.</p>
<ul>
<li>CEO</li>
<li>선임 편집자(senior editor)</li>
<li>주니어 편집자(junior editor)</li>
<li>번역가(translator)</li>
<li>현지화 전문가(localization specialists)</li>
<li>교정자(proofreader)</li>
</ul>
</li>
<li><p>클라이언트가 한 권의 책에 대한 번역 작업을 할당했을 때, 선택된 에이전트로 구성된 팀은 번역을 위해 협력한다.</p>
<ul>
<li>이 과정은 실제 이뤄지는 책 번역과정을 따라하는데, 서로 다른 역할의 사람들이 함께 일할 때 번역의 품질과 일관성이 유지될 수 있다.</li>
</ul>
</li>
</ul>
<p>출처 : <a href="https://hayunjong83.tistory.com/63">논문 번역 및 요약) LLM을 활용한 문학 번역 - (Perhaps) Beyond Human Translation</a></p>
<h3 id="안-되면-되는-거-해라">안 되면 되는 거 해라</h3>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/7c40e6bf-5526-4a96-b385-043e79dadd9f/image.png" alt=""></p>
<p>논문을 읽고 세운 실험 목표 및 조건은 다음과 같습니다.</p>
<ul>
<li>단순 LLM 을 이용하는 번역의 한계를 극복해 보자</li>
<li>이왕이면 트렌디한 기술을 써 보자 ➡️ AI Agent 를 적용해 보자</li>
<li>TransAgent 는 혼자 구현하기에 규모가 크다 ➡️ 작은 Agent 를 구현해 보자</li>
</ul>
<p>그렇게 생각해 낸 것은 &#39;AI Agent 를 이용한 초월번역기&#39; 입니다.
단순히 생각하기에 초월번역은 우선 원문을 번역(직역)하고,
번역 결과에 현지화를 거쳐 약간의 의역을 적용하면 될 것 같았고
이렇게 하면 번역 agent, 현지화 agent 이렇게 2개만으로 실험이 가능할 것 같다!</p>
<h2 id="초월번역기-구현">초월번역기 구현</h2>
<p>Agent 구현에 앞서 단순히 LLM과 프롬프트만 이용했을 때 얼마나 초월번역을 잘 하는지 알아보겠습니다.
예시 문장으로 앞서 초월번역 소개에 사용한 문장을 사용하겠습니다.</p>
<blockquote>
<p>원문 : What were you guys smokin&#39; when you came up with that?
직역(Papago) : 그걸 생각해냈을 때 여러분은 무엇을 피우고 있었나요?
초월번역 : 무슨 마약하시길래 이런 생각을 했어요?</p>
</blockquote>
<h3 id="웹사이트를-이용해-번역하기">웹사이트를 이용해 번역하기</h3>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/08a2672e-819b-4c36-8b4b-89be160d1c7e/image.png" alt="">
기본적인 프롬프트만으로도 매끄러운 구어체 형태로 번역해 줍니다.</p>
<h3 id="api를-이용해-번역하기">API를 이용해 번역하기</h3>
<h4 id="chatgpt">ChatGPT</h4>
<p>이번에는 웹사이트와 동일한 GPT-4-turbo 모델로 번역하되, API 를 이용하겠습니다.</p>
<pre><code class="language-python">import os
from openai import OpenAI

def translate_text(text):
    client = OpenAI(
        api_key=os.environ.get(&quot;OPENAI_API_KEY&quot;)
    )

    response = client.chat.completions.create(
        model=&quot;gpt-4-turbo&quot;,
        messages=[
            {&quot;role&quot;: &quot;system&quot;, &quot;content&quot;: &quot;당신은 콘텐츠 전문 번역가입니다. 사용자가 제공하는 문장을 한국어로 번역하세요.&quot;},
            {&quot;role&quot;: &quot;user&quot;, &quot;content&quot;: text}
        ]
    )
    return response.choices[0].message.content

if __name__ == &quot;__main__&quot;:
    text_to_translate = &quot;What were you guys smokin&#39; when you came up with that?&quot;
    translated_text = translate_text(text_to_translate)
    print(translated_text)</code></pre>
<p>번역 결과</p>
<pre><code>그거 생각해낼 때 뭘 피우고 있었어요?</code></pre><p>구어체 형태로 부자연스럽지 않게 번역해 주었지만
&#39;you guys&#39; 라는 부분을 고려하면 앞서 번역 결과와 같이 &#39;너희들&#39; 과 같은 뉘앙스를 살리거나, 조금 더 거칠게 번역했다면 더 좋았을 것 같습니다.</p>
<h4 id="deepseek-v3">DeepSeek V3</h4>
<pre><code class="language-python">from langchain.chat_models import ChatOpenAI
from langchain.schema import SystemMessage, HumanMessage
import os


text = &quot;What were you guys smokin&#39; when you came up with that?&quot;

client = ChatOpenAI(
    model_name=&quot;deepseek/deepseek-chat:free&quot;,
    temperature=0.7,
    openai_api_base=&quot;https://openrouter.ai/api/v1&quot;,
    openai_api_key=os.environ[&quot;OPENROUTER_API_KEY&quot;]
)

prompt = [
    SystemMessage(content=&quot;&quot;&quot;
            당신은 콘텐츠 전문 번역가이자 현지화 전문가입니다. 사용자가 제공하는 문장을 한국어로 번역하세요.
            번역 과정에서 한국인들이 더 쉽게 이해할 수 있도록 현지화해 주세요.
            문장이 수정된 이유를 반드시 한국어로 함께 설명하세요.
            &quot;&quot;&quot;),
    HumanMessage(content=f&quot;{text}&quot;) 
]


print(client.invoke(prompt).content)</code></pre>
<p>번역 결과</p>
<pre><code>&quot;그걸 생각해 낼 때 뭘 빨고 있었어?&quot;

번역 이유:
1. &quot;What were you smokin&#39;&quot;은 비속어 표현으로, 무언가 비현실적이거나 이상한 아이디어를 냈을 때 사용하는 표현입니다. 한국어로는 &quot;뭘 빨고 있었어&quot;라고 번역하여 비슷한 느낌을 전달했습니다.
2. &quot;you guys&quot;는 &quot;너희들&quot;로 번역할 수 있지만, 한국어에서는 생략해도 자연스러운 경우가 많아 생략했습니다.
3. 전체적으로 비속어 표현을 살리면서도 한국인들이 쉽게 이해할 수 있도록 현지화했습니다.</code></pre><p>꽤 자연스럽게 비속어의 느낌을 잘 살려 초월번역해 줍니다.</p>
<h3 id="langgraph-를-이용한-agent-구축">LangGraph 를 이용한 Agent 구축</h3>
<p>이번에는 AI Agent 를 이용해 초월번역기를 만들어 보겠습니다.
AI Agent 를 구현할 수 있는 프레임워크로 <code>Crew AI</code>, <code>AutoGen</code> 등이 있지만
저는 좀 더 미세하게 조정 가능한 <code>LangGraph</code> 를 사용했습니다.</p>
<h4 id="ai-agent">AI Agent</h4>
<blockquote>
<ul>
<li>특정 작업이나 목표를 수행하기 위해 설계된 인공지능 시스템</li>
</ul>
</blockquote>
<ul>
<li><p>인간의 개입 없이 스스로 문제를 해결하고, 판단을 내리고, 목표를 달성할 수 있는 소프트웨어</p>
</li>
<li><p>예시</p>
<ul>
<li>고객 서비스 등 사용자의 질문에 대해 적절한 답을 제공하는 <strong>챗봇</strong></li>
<li>도로 환경을 인식하고, 자율적으로 주행 결정을 내리는 <strong>자율주행차</strong></li>
<li>알람 설정, 날씨 정보 제공 등 사용자가 원하는 작업을 수행하는 <strong>AI 비서</strong></li>
</ul>
</li>
<li><p>구성 요소</p>
<ul>
<li><p><code>Task</code></p>
<ul>
<li>수행해야 할 구체적인 과제나 작업<ul>
<li>ex) 특정 주제에 대한 블로그 포스트 작성하기</li>
</ul>
</li>
</ul>
</li>
<li><p><code>Agent</code></p>
<ul>
<li>작업을 수행하는 주체<ul>
<li>ex) 자료 검색 Agent</li>
<li>ex) 글 작성 Agent</li>
</ul>
</li>
</ul>
</li>
<li><p><code>Tool</code></p>
<ul>
<li>Agent 가 Task를 수행하는 데 사용하는 도구<ul>
<li>ex) 웹사이트 스크랩 API</li>
<li>ex) 웹사이트 콘텐츠 검색 API</li>
</ul>
</li>
</ul>
</li>
<li><p><code>Orchestration Layer(구글 백서) / Process(CrewAI) / Router(LangGraph)</code></p>
<ul>
<li>Agent 들이 태스크를 효율적으로 수행하도록  작업의 흐름을 조정</li>
<li>여러 모델, 도구, 또는 파이프라인을 관리하고 조율</li>
<li>(참고) <a href="https://modulabs.co.kr/blog/agent-whitepapers-google">구글 백서</a></li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="langgraph">LangGraph</h4>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/f4f2b5cc-ce39-42ac-a664-5c15c23399ab/image.png" alt=""></p>
<blockquote>
<p>자연어처리 및 AI 응용 프로그램 개발을 위한 프레임워크</p>
</blockquote>
<ul>
<li><p>장점</p>
<ul>
<li>복잡한 작업을 그래프 형태로 쉽게 구현하고 관리할 수 있음</li>
<li>각각의 독립된 모듈 단위로 개발 (레고 블록 쌓듯이)</li>
<li>LangSmith 를 이용해 모니터링 가능</li>
</ul>
</li>
<li><p>구성 요소</p>
<ul>
<li><code>State</code> : 현재 상태를 나타내는 공유 데이터 구조</li>
<li><code>Node</code> : 실제 작업을 수행하는 함수</li>
<li><code>Edge</code> : 노드 간 연결</li>
</ul>
</li>
</ul>
<h4 id="agent-설계">Agent 설계</h4>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/d2646188-0c02-4e52-bba3-159a636347be/image.png" alt=""></p>
<ul>
<li><p>초벌 번역가 (initial_translator) Agent</p>
<ul>
<li>주어진 영어 문장을 한국어로 정확하게 번역하는 콘텐츠 전문 번역가</li>
<li>의역이나 창의적 번역을 하지 않도록 설정</li>
</ul>
</li>
<li><p>현지화 전문가 (localizer) Agent</p>
<ul>
<li>번역된 콘텐츠가 한국 문화와 언어에 맞게 자연스럽고 이해하기 쉽게 수정</li>
<li>창의성을 발휘하도록 설정</li>
</ul>
</li>
</ul>
<h4 id="code---단순-구현-실패">Code - 단순 구현 (실패!)</h4>
<ol>
<li>State 정의<pre><code class="language-python">from typing import TypedDict
</code></pre>
</li>
</ol>
<p>class State(TypedDict):
    text: str</p>
<pre><code>
2. Graph 인스턴스 생성
```python
from langgraph.graph import StateGraph

graph = StateGraph(State)</code></pre><ol start="3">
<li>Node 정의<pre><code class="language-python">from langchain.schema import SystemMessage, HumanMessage
</code></pre>
</li>
</ol>
<h1 id="첫-번째-노드-초벌-번역-영어-→-한국어">첫 번째 노드: 초벌 번역 (영어 → 한국어)</h1>
<p>def initial_translation(state: State):
    prompt = [
        SystemMessage(content=&quot;당신은 콘텐츠 전문 번역가입니다. 사용자가 제공하는 문장을 한국어로 번역하세요.&quot;),
        HumanMessage(content=state[&quot;text&quot;])  # ✅ State 의 &#39;text&#39; 키 사용
    ]
    translated_text = initial_translation_llm(prompt).content
    return {&quot;text&quot;: translated_text}  # ✅ 딕셔너리 형태로 반환</p>
<h1 id="두-번째-노드-현지화-자연스러운-한국어로-수정">두 번째 노드: 현지화 (자연스러운 한국어로 수정)</h1>
<p>def localization(state: State):
    prompt = [
        SystemMessage(content=&quot;&quot;&quot;
                      당신은 한국어 콘텐츠 현지화 전문가입니다.
                      초벌 번역된 한국어 문장을 한국인들이 더 쉽게 이해할 수 있도록 현지화해 주세요.
                      &quot;&quot;&quot;),
        SystemMessage(content=&quot;문장이 수정된 이유를 반드시 한국어로 함께 설명하세요.&quot;),
        HumanMessage(content=state[&quot;text&quot;])  # ✅ State 의 &#39;text&#39; 키 사용
    ]
    localized_text = localization_llm(prompt).content
    return {&quot;text&quot;: localized_text}  # ✅ 딕셔너리 형태로 반환</p>
<pre><code>
`참고` LLM 정의 (OpenRouter API 사용)
```python
from langchain.chat_models import ChatOpenAI
import os

initial_translation_llm = ChatOpenAI(
    model_name=&quot;deepseek/deepseek-chat:free&quot;,
    temperature=0,    # 창의성 0
    openai_api_base=&quot;https://openrouter.ai/api/v1&quot;,
    openai_api_key=os.environ[&quot;OPENROUTER_API_KEY&quot;]
)

localization_llm = ChatOpenAI(
    model_name=&quot;deepseek/deepseek-chat:free&quot;,
    temperature=0.7,    # 창의성 허용
    openai_api_base=&quot;https://openrouter.ai/api/v1&quot;,
    openai_api_key=os.environ[&quot;OPENROUTER_API_KEY&quot;]
)
</code></pre><ol start="4">
<li><p>Node 추가</p>
<pre><code class="language-python">graph.add_node(&quot;initial_translation&quot;, initial_translation)
graph.add_node(&quot;localization&quot;, localization)</code></pre>
</li>
<li><p>Edge 연결</p>
<pre><code class="language-python">graph.set_entry_point(&quot;initial_translation&quot;)
graph.add_edge(&quot;initial_translation&quot;, &quot;localization&quot;)
graph.add_edge(&quot;localization&quot;, END)  # 답변 -&gt; 종료</code></pre>
</li>
<li><p>Graph 컴파일</p>
<pre><code class="language-python">graph = graph.compile()</code></pre>
</li>
<li><p>그래프 실행</p>
<pre><code class="language-python"># 입력 텍스트
input_text = &quot;What were you guys smokin&#39; when you came up with that?&quot;
</code></pre>
</li>
</ol>
<h1 id="초기-상태-설정">초기 상태 설정</h1>
<p>state = {&quot;text&quot;: input_text}</p>
<h1 id="그래프-실행-및-결과-출력">그래프 실행 및 결과 출력</h1>
<p>for result in graph.stream(state):
    print(f&quot;🔄 단계: {list(result.keys())[0]}&quot;)  # 현재 실행 중인 노드 이름
    print(f&quot;📝 결과: {result[list(result.keys())[0]][&#39;text&#39;]}\n&quot;)  # 해당 노드의 결과값</p>
<pre><code>
결과
![](https://velog.velcdn.com/images/judy_choi/post/f8b38af1-5661-4942-bee5-259660333df0/image.png)

결과 문장이 원문이랑 의미가 좀 다릅니다.
이유는 다음과 같습니다. (ChatGPT야 고마워!)
![](https://velog.velcdn.com/images/judy_choi/post/d527a063-88bf-46a9-a655-da2d68c4a288/image.png)



#### Code - 현지화 과정에서 원문을 참고하도록 개선

1. State 정의 ➡️ State key 수정
```python
from typing import TypedDict

class State(TypedDict):
    origin_text: str
    output_text: None|str</code></pre><ol start="3">
<li>Node 정의 ➡️ Node 입/출력 key 수정<pre><code class="language-python">from langchain.schema import SystemMessage, HumanMessage
</code></pre>
</li>
</ol>
<h1 id="첫-번째-노드-초벌-번역-영어-→-한국어-1">첫 번째 노드: 초벌 번역 (영어 → 한국어)</h1>
<p>def initial_translation(state: State):
    prompt = [
        SystemMessage(content=&quot;당신은 콘텐츠 전문 번역가입니다. 사용자가 제공하는 문장을 한국어로 번역하세요.&quot;),
        HumanMessage(content=state[&quot;origin_text&quot;])  # ✅ State 의 &#39;text&#39; 키 사용
    ]
    translated_text = initial_translation_llm(prompt).content
    return {&quot;origin_text&quot;: state[&quot;origin_text&quot;], &quot;output_text&quot;: translated_text}  # ✅ 딕셔너리 형태로 반환</p>
<h1 id="두-번째-노드-현지화-자연스러운-한국어로-수정-1">두 번째 노드: 현지화 (자연스러운 한국어로 수정)</h1>
<p>def localization(state: State):
    prompt = [
        SystemMessage(content=&quot;&quot;&quot;
                      당신은 한국어 콘텐츠 현지화 전문가입니다.
                      초벌 번역된 한국어 문장을 한국인들이 더 쉽게 이해할 수 있도록 현지화해 주세요.
                      &quot;&quot;&quot;),
        SystemMessage(content=&quot;문장이 수정된 이유를 반드시 한국어로 함께 설명하세요.&quot;),
        HumanMessage(content=f&quot;원문: {state[&#39;origin_text&#39;]}\n초벌 번역: {state[&#39;output_text&#39;]}&quot;) 
    ]
    localized_text = localization_llm(prompt).content
    return {&quot;origin_text&quot;: state[&quot;origin_text&quot;], &quot;output_text&quot;: localized_text}  # ✅ 딕셔너리 형태로 반환</p>
<pre><code>
7. 그래프 실행 ➡️ 그래프 실행 입/출력값 수정
```python
# 입력 텍스트
input_text = &quot;What were you guys smokin&#39; when you came up with that?&quot;

# 초기 상태 설정
state = {&quot;origin_text&quot;: input_text}  

# 그래프 실행 및 결과 출력
for result in graph.stream(state):
    print(f&quot;🔄 단계: {list(result.keys())[0]}&quot;)  # 현재 실행 중인 노드 이름
    print(f&quot;📝 결과: {result[list(result.keys())[0]][&#39;output_text&#39;]}\n&quot;)  # 해당 노드의 번역 결과</code></pre><p>결과
<img src="https://velog.velcdn.com/images/judy_choi/post/9be52b6c-140b-41ba-8c3f-e9ec1edca5b1/image.png" alt=""></p>
<h4 id="tip-langsmith-를-이용한-모니터링">Tip) LangSmith 를 이용한 모니터링</h4>
<p>환경변수로 다음 3가지를 설정하면 LangSmith 가 자동으로 모니터링을 시작합니다.
(OpenAI API 를 사용하지 않을 경우 <code>OPENAI_API_KEY</code> 생략 가능)</p>
<pre><code class="language-shell">export LANGSMITH_TRACING=true
export LANGSMITH_API_KEY=&quot;&lt;your-langsmith-api-key&gt;&quot;
# The example uses OpenAI, but it&#39;s not necessary if your code uses another LLM provider
export OPENAI_API_KEY=&quot;&lt;your-openai-api-key&gt;&quot;</code></pre>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/a23b35e7-8b93-4464-b80c-dea13087aa07/image.png" alt=""></p>
<h2 id="회고">회고</h2>
<ul>
<li><p>초월번역을 하기 위해서는 LLM + 간단한 프롬프트만으로 충분하다</p>
<ul>
<li>이 경우 초월번역의 품질은 LLM 의 성능에 좌우됨</li>
</ul>
</li>
<li><p>번역에 Agent 를 사용할 경우 AI가 tool 을 선택해야 할 때, 즉 AI의 판단이 필요한 작업에 더 유용할 것으로 보인다</p>
<ul>
<li>초월번역에 AI의 주도적인 역할을 부여한다던가, 또는 복잡한 분기를 임의로 추가하여 Agent의 효과를 극대화한다던가..</li>
<li>아니면 차라리 web search tool 을 사용해서 신조어 번역기를 만들었어야 했다.
<img src="https://velog.velcdn.com/images/judy_choi/post/0b1f18f2-95bb-42bf-8195-cde066c296e2/image.png" alt=""></li>
</ul>
</li>
<li><p>번역뿐만이 아니라 목표로 하는 서비스의 특성과 필요한 점을 고려하여 LangChain 으로 구축할지, LangGraph 및 Agent 로 구축할지, Agent 로 구축한다면 어떤 tool 들이 유기적으로 동작해야 하는지 판단하고 설계해야 한다.</p>
<ul>
<li>초월번역기의 경우 단순 선형 작업이므로 LangChain 으로 구현하는 편이 더 효율적이었을 것.</li>
</ul>
</li>
<li><p>코딩부터 하는 대신 No-Code 툴로 먼저 빠르게 개발하고 효과를 검증했어야 했다</p>
<ul>
<li>Make, Zapier, Flowise…</li>
</ul>
</li>
<li><p>전문적인 Agent 를 구축하기 위해서는 도메인에 대한 깊은 이해가 필요하다</p>
<ul>
<li>개발자가 번역 산업에 대한 이해도가 낮았다면 TransAgent 를 개발할 수 없었을 것.</li>
<li>이는 AI가 대체할 수 없음 (Agent 를 개발하는 Agent 가 나온다면 과연…?)</li>
</ul>
</li>
<li><p>복잡한 작업을 수행하는 Agent 를 구현하는 능력 == 설계 능력</p>
<ul>
<li>각각의 Agent 가 상호작용하려면?<ul>
<li>단순 파이프라인? 조건문? 작업 분배? …</li>
<li>입/출력 데이터는 무엇이 필요한가?</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="idea">Idea</h2>
<ul>
<li><p>Agent 를 이용해 실제로 비즈니스적인 가치가 있는 번역기를 만든다면 초월번역 대신 팩트체크를 돕는 번역기를 만들거나  RAG를 이용해 특정 도메인을 번역해야 할 경우 등등 번역 주제를 바꾸는 것도 좋을 것 같다.</p>
</li>
<li><p>메모리를 적극적으로 이용해 본다면 드라마나 웹소설 번역기를 만드는 건 어떨까?</p>
<ul>
<li>번역중인 회차 정보는 Short-term 메모리에, 전체 회차 정보는 Long-term 메모리 RAG에...?</li>
</ul>
</li>
<li><p>틈새시장(?) 으로 비윤리적인 도메인 전문 번역기는 어떨까 🤪😵‍💫☠️</p>
<ul>
<li>너무 윤리적인(?) 모델의 경우(Llama3 7B) 예시 문장이 drug 관련 내용이라며 답변을 거부함 -_-;;<ul>
<li>실험을 몇번 돌려보니 가끔 문장이 너무 평범하게 번역되던데 설마 LLM 윤리 가드레일에 걸려서...!? (검증은 못 해봄)</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h2 id="ps-글또-큐레이션-선정-">p.s 글또 큐레이션 선정 ‼</h2>
<p>(왜죠....??? 하지만 감사합니다 😁)</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/98670fa5-c4ca-4a41-bd00-9294adc13f74/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LangCon2025 후기]]></title>
            <link>https://velog.io/@judy_choi/LangCon2025-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@judy_choi/LangCon2025-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sun, 02 Mar 2025 07:35:03 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://2025langcon.oopy.io/">LangCon2025 안내 페이지</a>
(참고) <a href="https://velog.io/@judy_choi/LangCon2024-%ED%9B%84%EA%B8%B0-oa982x7u">LangCon2024 후기</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/8ad09ed2-5729-432f-9fbb-ca99e4c403cd/image.png" alt=""></p>
<p>매년 3월에 열리는 자연어처리 컨퍼런스 LangCon 에 올해도 다녀왔습니다.
올해에는 AI Agent 를 어떻게 구현하는지 궁금해서 핸즈온 세션만 4개 다 들었습니다.</p>
<h2 id="hands-on">Hands On</h2>
<h3 id="llm기반-도구부터-에이전틱-ai까지-직접-만들어서-사용하자---어시웍스">LLM기반 도구부터 에이전틱 AI까지 직접 만들어서 사용하자 - 어시웍스</h3>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/3ae0ac18-bbc9-464a-b6be-00d6fe74c983/image.png" alt="">
첫 번째 핸즈온 세션은 천천히 진행 + 쉬운 설명과 노코드 실습으로 Agentic AI 의 맛을 볼 수 있어서 좋았다!
tool(LLM 또는 API) - flow - agent - team 순서로 차근차근 모듈을 쌓듯이 Agentic AI 를 구축해 보았다 😃
강사님께서 요즘은 초등학생도 vector DB를 만들어서 RAG 챗봇을 만든다고 하셨는데
오... 과연 오늘 실습대로 따라하기만 하면 충분히 가능할 것 같은데!?</p>
<p>실습은 AI Factory 의 &#39;AI 플레이(Playground)&#39; 를 이용해 진행했다.
<a href="https://aifactory.space/play">https://aifactory.space/play -&gt; &#39;어시웍스&#39;</a></p>
<p>이 예제는 가장 기본 모듈인 tool 을 만드는 예시인데,
Instruction(시스템 프롬프트), context, few-shot 을 넣어 보았다.
<img src="https://velog.velcdn.com/images/judy_choi/post/407da761-f8dc-4b5a-a56a-659bbfa615b1/image.png" alt=""></p>
<p>또다른 예시로 Bilingual 번역 tool 을 이렇게 묶으면 agent 를 만들 수 있다.
<img src="https://velog.velcdn.com/images/judy_choi/post/a47413f1-bd16-4b07-888f-2f77fddbb92e/image.png" alt=""></p>
<h3 id="langgraph를-활용한-실용적-에이전틱agentic-워크플로우-구축">LangGraph를 활용한 실용적 에이전틱(Agentic) 워크플로우 구축</h3>
<p><a href="https://github.com/teddylee777/langcon-2025-handson">GitHub : langcon-2025-handson</a></p>
<ul>
<li>LangGraph를 사용하여 AI 에이전트를 구축하고 실행하는 방법을 보여주는 예제와 가이드 제공<ul>
<li><code>LangGraph</code> : LangChain 기반의 그래프 기반 프레임워크.
복잡한 AI 에이전트 워크플로우를 구성하는 데 유용함.</li>
</ul>
</li>
</ul>
<p>LangChain 일타강사 테디님의 강의!! 🤩
역시 일타강사님이신 데에는 다 이유가 있었다... ✨</p>
<p>첫 핸즈온 세션에서 천천히 쉽게 Agent 에 대한 개념을 잡았다면
이번 세션에서는 코드를 이용해 Agent 를 구현하는 방법과
챗봇 개발에 꼭 고려해야 할 점 (멀티턴 대화를 위한 단기 메모리 Long-term 메모리, 라우팅 등등) 까지 배울 수 있었다.
내가 이 강의를 일찍 들었다면 더 좋은 챗봇을 만들 수 있었을텐데! 😂</p>
<p>나는 학부 때부터 그래프를 싫어해서 LangGraph 는 막연히 하기 싫었는데 😞
테디쌤의 설명을 들으며 코드를 보니 세상에 이렇게 쉬울 수가!
게다가 LangSmith 로 Agent 모니터링까지 할 수 있어서 개발할 때 완전 유용해 보였다.</p>
<p>LangGraph 코드를 보며 전통적인 챗봇과 약간 다르다고 느낀 것이
기존 챗봇에서는 사용자의 intent 를 파악하는 것이 매우 중요했다.
사용자의 intent 를 알아야 정확하게 분기하여 기능을 수행할 수 있었으니까.
생각해보니 LangGraph 핸즈온 코드에는 사용자의 intent 를 파악하는 기능이 없는데
Agent 가 알아서 적합한 tool 을 선택하고 분기해 주니까...!
그러고 보니 entity 를 추출하는 부분도 없다. 😮
이건 그냥 이번 핸즈온에서 구현한 챗봇의 특성상(정보 검색) entity 를 추출할 필요가 없어서인가....? 🤔</p>
<p>주의할 점(?) 은 실습 코드의 경우 일부 기능이 <code>langchain_teddynote</code> 모듈에 오버라이딩되어 있기 때문에 
원조(?) LangChain 및 LangGraph 코드와는 약간 다르다...!
그래프 시각화하는 코드라던가 그래프 스트림 코드라던가.
(어쩐지 코드가 이상하게 간결하고 출력값이 이쁘더라 🤭)</p>
<h3 id="ai-에이전트-뚝딱-만들어-보기-나야-semantic-kernel">AI 에이전트 뚝딱 만들어 보기 (나야~ Semantic Kernel)</h3>
<p><a href="https://github.com/devkimchi/semantic-kernel-workshop">GitHub : semantic-kernel-workshop</a></p>
<p><code>Semantic Kernel</code></p>
<ul>
<li>마이크로소프트의 오픈소스 프로젝트</li>
<li>손쉽게 에이전트를 만들고 애플리케이션에 통합시킬 수 있다</li>
</ul>
<p>사실 Semantic Kernel 이란 건 처음 들어봤고
C# 으로 AI Agent 를 만들 수 있으리라고는 상상도 못해봤다. 😳
핸즈온 세션이 C# 및 json 으로 진행되었고, 사실 코드가 생소해서 설명을 안 들었다면 되게 어려웠을 것 같다 😂
지금까지 AI Agent 를 만든다고 하면 LangChain 및 LangGraph 를 이용하는 줄로만 알았는데
다른 수단과 방법이 있었다니 신박한걸?! 😃</p>
<p>참고로 ChatGPT 에게 LangChain 과 Semantic Kernel 의 장점 및 차이점을 물어보니 이렇게 대답해 주었다.</p>
<table>
<thead>
<tr>
<th><strong>사용 목적</strong></th>
<th><strong>추천 프레임워크</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>ReAct 기반 에이전트</strong> (LLM이 직접 Tool 선택)</td>
<td>✅ LangChain</td>
</tr>
<tr>
<td><strong>Microsoft 생태계 (Azure, Office 365 등) 연동</strong></td>
<td>✅ Semantic Kernel</td>
</tr>
<tr>
<td><strong>복잡한 LLM 워크플로우 (LangGraph 활용)</strong></td>
<td>✅ LangChain</td>
</tr>
<tr>
<td><strong>Task-Oriented AI 자동화</strong></td>
<td>✅ Semantic Kernel</td>
</tr>
<tr>
<td><strong>다양한 API 및 오픈소스 연동</strong></td>
<td>✅ LangChain</td>
</tr>
</tbody></table>
<ul>
<li><strong>LangChain은 AI 도우미, 정보 검색, 대화형 챗봇, 멀티스텝 LLM 체인에 강함.</strong></li>
<li><strong>Semantic Kernel은 AI 기능을 모듈화하여 자동화하거나 Microsoft 제품과 연동할 때 유리함.</strong></li>
</ul>
<p>🔥 <strong>➡ 결론적으로, LangChain은 &quot;AI 기반 체인과 에이전트&quot;, Semantic Kernel은 &quot;AI Task 자동화 및 Microsoft 연동&quot;에 강점이 있음.</strong> 🚀</p>
<h3 id="장고로-만드는-rag-웹-채팅-서비스">장고로 만드는 RAG 웹 채팅 서비스</h3>
<p><a href="https://ai.pyhub.kr/hands-on-lab/django-webchat-rag/">💬 장고로 만드는 RAG 웹 채팅 서비스</a></p>
<p>LangChain 없이 sqlite 와 django 로 가볍게 RAG 구현하기!
이번 세션은 AI개발자보다는 파이썬 백엔드 개발자를 위한 세션이었다.
LangChain 도 없고, DB를 파일 단위로 만드는 매우 가벼운 sqlite 만으로 구현했기 때문에
환경을 크게 타지 않고 용량을 크게 차지하지 않아 개발환경에 다소 제한이 있는 조직(ex: 정부 부처)에서 우선 시연하기에 적합하고
특히 이미 django 를 사용하는 시스템과 연동하려면 이 방법이 빠르고 편리할 것 같다.
다만 sqlite 의 특성상 데이터가 많아지면 처리에 한계가 생길 것 같고
실습에서는 벡터 검색을 지원하지 않고 코사인 유사도만 사용하기 때문에 검색 품질에 한계가 있을 것 같다.</p>
<h2 id="작년과-비교하여">작년과 비교하여..</h2>
<ul>
<li>대 LLM 의 시대 (feat. Agent)</li>
<li>LLM 이제는 다 아시죠?<ul>
<li>작년에는 키노트에서 LLM 에 대해 Wrap-up 시간을 갖고, 이어지는 세션에서 튜닝 및 강점, 활용 방안까지 다루었다면
올해에는 참가자들이 LLM에 대해 잘 알고 있다는 점을 전제로 하고 모든 세션이 진행되었다는 인상을 받음.
당장 주제만 해도 작년에는 &lt;생성 모델 튜닝 어디까지 왔나?&gt; 였지만
올해 주제는 &lt;LLM과 어떻게 살아갈 것인가&gt; 임.</li>
</ul>
</li>
<li>AI 트렌드는 매우 빨리 바뀌고, 다행히(?) 기술이 등장한 후 트렌드가 되기까지는 시간이 조금 걸리는 듯.<ul>
<li>작년 세션 중 RAG 와 Agent 세션도 있었는데, RAG는 작년에 핫했고 Agent 는 최근에 가장 핫한 주제.</li>
<li>새로 나오는 기술들 중 트렌드가 될 수 있는 기술을 알아보고 빠르게 익히고 시도하고 적용하는 것이 중요할 것 같다.</li>
</ul>
</li>
</ul>
<h2 id="참석-후기">참석 후기</h2>
<ul>
<li>발표도 듣고 싶고 핸즈온도 듣고 싶었는데 내 몸이 한개라서 발표를 못 들어서 너무너무 아쉽다 😂</li>
<li>AI트렌드가 엄청나게 빨리 바뀐다는 게 느껴진다.</li>
<li>AI Agent 가 올해 대세가 될 것 같은데, 문제는 구현하는 방법이 생각보다 쉬워서 기술적 장벽이 낮아질 것 같다.
내년에는 새로운 기술이 트렌드가 되겠지... 예측이 안되네.</li>
<li>LLM 이 나오고 나서 왠지 AI업계가 좀 재미없어진 것 같기도. 😒
더 이상 우리가 거인의 어깨 위에 서는 게 아니라 그냥 거인이 다 하는 것 같은데...
예전처럼 새로운 모델과 데이터셋이 쏟아져 나오던 춘추전국 시대는 끝났나.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리뷰] 자연어 처리를 위한 허깅페이스 트랜스포머 하드 트레이닝]]></title>
            <link>https://velog.io/@judy_choi/%EB%A6%AC%EB%B7%B0-%EC%9E%90%EC%97%B0%EC%96%B4-%EC%B2%98%EB%A6%AC%EB%A5%BC-%EC%9C%84%ED%95%9C-%ED%97%88%EA%B9%85%ED%8E%98%EC%9D%B4%EC%8A%A4-%ED%8A%B8%EB%9E%9C%EC%8A%A4%ED%8F%AC%EB%A8%B8-%ED%95%98%EB%93%9C-%ED%8A%B8%EB%A0%88%EC%9D%B4%EB%8B%9D</link>
            <guid>https://velog.io/@judy_choi/%EB%A6%AC%EB%B7%B0-%EC%9E%90%EC%97%B0%EC%96%B4-%EC%B2%98%EB%A6%AC%EB%A5%BC-%EC%9C%84%ED%95%9C-%ED%97%88%EA%B9%85%ED%8E%98%EC%9D%B4%EC%8A%A4-%ED%8A%B8%EB%9E%9C%EC%8A%A4%ED%8F%AC%EB%A8%B8-%ED%95%98%EB%93%9C-%ED%8A%B8%EB%A0%88%EC%9D%B4%EB%8B%9D</guid>
            <pubDate>Wed, 19 Feb 2025 07:43:37 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/judy_choi/post/92582f60-8df0-402d-a993-95f0276c0402/image.png" alt=""></p>
<p>좋은 기회로 이 책을 읽게 되었는데, 왜 이런 책이 이제야 나왔나 싶다. 😂
이 책이 1년만 일찍 나와서 내가 작년 이맘때쯤에 이 책을 읽었더라면
내 커리어가 정말 많이 달라졌을 것 같다. 🥲</p>
<h2 id="장점">장점</h2>
<ul>
<li>자연어처리 거의 모든 태스크를 다룸<ul>
<li>문장분류, QA, NER, 번역 등등</li>
<li>심지어 인코더, 디코더, 인코더-디코더 모델별로 각각!</li>
</ul>
</li>
<li>현업에서 파인튜닝할 때 반드시 필요한 경량화(PEFT 및 양자화) 까지 쉬운 설명 + 코드 제시</li>
<li>위 장점들 덕분에 기술면접 앞두고 이 책 읽었더니 도움이 되었음.<ul>
<li>다양한 태스크 + 최신 기술까지 쉽게 설명해 주니 기술면접에서 특정 기술에 대해 쉽게 설명하라는 질문에 대처하기 좋았음.</li>
</ul>
</li>
<li>허깅페이스 이용해서 개발할 때 매우 편리함<ul>
<li>요즘 허깅페이스 없이 AI 개발하는 경우가 거의 없는데 맞춤형 실용서인 셈.</li>
</ul>
</li>
<li><strong>실무에 즉시 활용 가능</strong> (꼭 필요한 코드만 간추려서 쉽게 설명하기 때문)</li>
</ul>
<h2 id="아쉬운-점">아쉬운 점</h2>
<ul>
<li>책 내용을 소화하려면 자연어처리 기본 지식이 있어야 함<ul>
<li>일단 트랜스포머가 뭔지 아는 독자들을 대상으로 쓰여진 것 같다..</li>
<li>자연어처리 모델이나 주요 태스크에 대한 이해가 없는 입문자라면 어렵게 느껴질 듯.</li>
<li>반대로 실무자한테는 이만한 책이 없다!</li>
</ul>
</li>
<li>경량화 측면에서는 설명을 조금 더 자세하게 해 주고 그림으로 이해를 도왔다면 더 좋았을 듯.<ul>
<li>특히 PEFT 와 LoRA 설명할 때 그림이 한개뿐이라 아쉬웠다.</li>
<li>양자화에서 지수부 가수부도 그림을 넣어줬다면 더 좋았을 것 같다.</li>
</ul>
</li>
<li>이제야 출판된 점<ul>
<li>이런 <del>개쩌는</del> 유용한 실용서가 왜 이제야 나옴 😂😂😂😂😂😂😂😂😂😂</li>
</ul>
</li>
</ul>
<p>아주 개인적인 생각으로 저자 두 분 모두 스타트업 근무 경험이 있으신데,
스타트업에서는 빠르게 개발하고 테스트해야 하고 자원은 한정적이라
이만큼 효율적으로 빠르게 개발할 수 있는 실용서를 집필하시게 된 건 아닐까... 🤭</p>
<p>개인적으로 작년 여름 즈음부터는 스터디 따라가기 바쁨 + 현업에서는 백엔드 개발을 더 많이 함 
= 일단 코드 주워다 돌려보기 바빠서 뭐가 뭔지도 모르고 허깅페이스 써댔는데
이제는 주워 온 코드라 할지라도 어떤 기능을 하는 코드인지 제대로 알고
필요한 데이터셋을 알맞게 변형해서 모델 파인튜닝을 시킬 수 있을 것 같다!</p>
<hr>
<p>#비제이퍼블릭 #자연어처리 #허깅페이스 #트랜스포머 #언어모델 #대규모언어모델 #LLM #모델경량화 #강화학습 #인공지능 #생성형AI #머신러닝 #딥러닝 #생성모델 #파이썬 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RAG 시리즈] ChatBot 구축하기 : Rule Base 부터 LangGraph 까지]]></title>
            <link>https://velog.io/@judy_choi/RAG-%EC%8B%9C%EB%A6%AC%EC%A6%88-ChatBot-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-Rule-Base-%EB%B6%80%ED%84%B0-LangGraph-%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@judy_choi/RAG-%EC%8B%9C%EB%A6%AC%EC%A6%88-ChatBot-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-Rule-Base-%EB%B6%80%ED%84%B0-LangGraph-%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Sat, 15 Feb 2025 10:07:31 GMT</pubDate>
            <description><![CDATA[<p>보험사, 금융사, 사내 메뉴얼 등 문서를 참조하는 챗봇에서 RAG가 사용되는 경우가 많은데요,
다시 말해, 좋은 챗봇을 구축하는 일 또한 RAG개발에 수반되는 업무입니다.</p>
<p>이번 포스팅에서는 Rule Base 로 챗봇을 구축한 경험을 공유하고,
최근 AI Agent 개발에 쓰이는 LangGraph 를 간단하게 소개합니다.
(튜토리얼 코드는 없습니다.)</p>
<h2 id="공통-요구사항">공통 요구사항</h2>
<p>챗봇을 개발할 때, 공통적으로 요구받은 사항은 다음과 같습니다.</p>
<ul>
<li>고객이 챗봇 로직을 직접 개선할 수 있어야 함<ul>
<li>고객이 간단히 스크립트 또는 시나리오를 수정하여 챗봇을 개선할 수 있도록 지원</li>
<li>개발사의 유지보수 비용 절감</li>
</ul>
</li>
<li>Fallback 처리 필요<ul>
<li>도메인과 관련 없는 질의에 대응할 수 있어야 함</li>
</ul>
</li>
<li>다수 유저의 동시 접속 처리<ul>
<li>서로 다른 다수의 사용자가 동시에 챗봇을 사용할 수 있어야 함<ul>
<li>&#39;사망보험금의 지급사유&#39;, &#39;골절 상해 시 지급 금액&#39; 와 같은 질문을 동시에 챗봇에 입력했을 때, 챗봇은 각 질의에 맞는 답변을 각각의 사용자에게 정확히 제공해야 함.</li>
</ul>
</li>
</ul>
</li>
<li>현재 상태/단계 (level, depth 라고도 표현) 에 맞는 답변 제공<ul>
<li>보험종류 선택(생명/자동차/연금 중 생명보험 선택) -&gt; 상품명 선택 단계의 경우 상품명만 고를 수 있어야 함.<ul>
<li>&#39;자동차보험 선택&#39; 과 같은 질의가 들어올 경우 이전 단계에 대한 질의이므로 처리하지 않아야 함</li>
</ul>
</li>
</ul>
</li>
<li>(선택사항) Multi-turn<ul>
<li>이전 대화를 참조하여 답변 제공</li>
</ul>
</li>
</ul>
<h2 id="rule-base">Rule Base</h2>
<p>위 요구조건에 따라 if문을 사용하여 직접 코딩할 수도 있지만
채팅을 쉽게 구현할 수 있도록 돕는 스크립트나 프레임워크를 사용하면 더 쉽게 챗봇을 개발할 수 있습니다.</p>
<p>저는 텍스트 챗봇 개발에 Rivescript 를, 음성 챗봇에 JSGF 를 사용했습니다.
(음성 챗봇 관련 포스팅 : <a href="https://velog.io/@judy_choi/%EA%B8%80%EB%98%90-%EC%9D%8C%EC%84%B1-%EB%AA%85%EB%A0%B9%EC%96%B4-%EC%9D%B8%EC%8B%9D-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EC%86%8C%EA%B0%9C-JSGF-Voice2Json-Rhasspy">https://velog.io/@judy_choi/글또-음성-명령어-인식-라이브러리-소개-JSGF-Voice2Json-Rhasspy</a>)</p>
<p><strong>RiveScript</strong>와 <strong>JSGF (Java Speech Grammar Format)</strong>는 둘 다 자연어 처리에 사용되지만, 목적과 방식이 다릅니다.
다음은 각 스크립트 및 문법을 ChatGPT 가 정리 및 비교해 준 결과입니다.</p>
<hr>
<h3 id="🔹-rivescript-vs-jsgf-비교">🔹 <strong>RiveScript vs. JSGF 비교</strong></h3>
<table>
<thead>
<tr>
<th>요소</th>
<th><strong>RiveScript</strong> 📝</th>
<th><strong>JSGF (Java Speech Grammar Format)</strong> 🎤</th>
</tr>
</thead>
<tbody><tr>
<td><strong>기능</strong></td>
<td>패턴 기반 챗봇 스크립트</td>
<td>음성 인식을 위한 문법 정의</td>
</tr>
<tr>
<td><strong>방식</strong></td>
<td>정규식 + 룰 기반</td>
<td>BNF(Backus-Naur Form) 문법</td>
</tr>
<tr>
<td><strong>주요 역할</strong></td>
<td>텍스트 기반 대화 응답</td>
<td>음성 인식 시스템의 명령 처리</td>
</tr>
<tr>
<td><strong>언어 지원</strong></td>
<td>Python, JS, Java 등</td>
<td>Java, CMU Sphinx, Kaldi, Rhasspy 등</td>
</tr>
<tr>
<td><strong>인터넷 필요 여부</strong></td>
<td>필요 없음</td>
<td>필요 없음 (음성 인식 엔진 필요)</td>
</tr>
<tr>
<td><strong>사용 예시</strong></td>
<td>텍스트 챗봇, FAQ</td>
<td>음성 명령어 정의 (예: &quot;Turn on the light&quot;)</td>
</tr>
</tbody></table>
<hr>
<h4 id="🔹-rivescript란">🔹 <strong>RiveScript란?</strong></h4>
<p>✅ <strong>역할</strong>:  </p>
<ul>
<li>패턴 매칭을 사용한 챗봇 제작  </li>
<li>정해진 텍스트 입력에 대한 응답 제공  </li>
</ul>
<p>✅ <strong>예제 (RiveScript)</strong>  </p>
<pre><code class="language-rivescript">+ hello bot
- Hello, human!

+ my name is *
- Nice to meet you, &lt;star&gt;!</code></pre>
<p>✔ <code>+</code> → 사용자의 입력 패턴<br>✔ <code>-</code> → 챗봇의 응답<br>✔ <code>&lt;star&gt;</code> → 와일드카드 (변수를 저장)  </p>
<hr>
<h4 id="🔹-jsgf-java-speech-grammar-format란">🔹 <strong>JSGF (Java Speech Grammar Format)란?</strong></h4>
<p>✅ <strong>역할</strong>:  </p>
<ul>
<li><strong>음성 인식(Speech Recognition)용 문법</strong>을 정의하는 표준  </li>
<li>Java 기반 음성 인식 엔진(CMU Sphinx, Kaldi 등)에서 사용됨  </li>
</ul>
<p>✅ <strong>예제 (JSGF)</strong>  </p>
<pre><code class="language-jsgf">#JSGF V1.0;

grammar lights;

public &lt;command&gt; = (turn on | switch on | activate) the (light | lamp);</code></pre>
<p>✔ <code>grammar lights;</code> → 문법의 이름<br>✔ <code>public &lt;command&gt;</code> → 공개된 명령어 정의<br>✔ <code>(turn on | switch on | activate)</code> → 가능한 입력 패턴<br>✔ <code>(light | lamp)</code> → &quot;light&quot; 또는 &quot;lamp&quot; 인식  </p>
<hr>
<h3 id="🔹-핵심-차이점-요약">🔹 <strong>핵심 차이점 요약</strong></h3>
<table>
<thead>
<tr>
<th>비교 항목</th>
<th><strong>RiveScript</strong> 📝</th>
<th><strong>JSGF</strong> 🎤</th>
</tr>
</thead>
<tbody><tr>
<td><strong>주요 목적</strong></td>
<td>텍스트 챗봇 제작</td>
<td>음성 인식용 문법 정의</td>
</tr>
<tr>
<td><strong>사용 환경</strong></td>
<td>텍스트 기반</td>
<td>음성 인식 엔진과 함께 사용</td>
</tr>
<tr>
<td><strong>입력 방식</strong></td>
<td>정규식 기반</td>
<td>BNF 문법 기반</td>
</tr>
<tr>
<td><strong>응용 분야</strong></td>
<td>고객지원 챗봇, 게임 NPC</td>
<td>스마트홈, 음성 명령 제어</td>
</tr>
</tbody></table>
<hr>
<h3 id="✅-결론">✅ <strong>결론</strong></h3>
<p>✔ <strong>텍스트 챗봇을 만들고 싶다면</strong> → <strong>RiveScript</strong><br>✔ <strong>음성 인식 시스템을 구축하고 싶다면</strong> → <strong>JSGF</strong>  </p>
<p>즉, <strong>RiveScript는 대화형 응답을 위한 스크립트</strong>, <strong>JSGF는 음성 인식 엔진을 위한 문법</strong>입니다! 🚀</p>
<hr>
<p>개인적으로 rivescript 를 이용할 때에는 명령어가 들어왔을 때 매칭되는 함수를 호출할 때 편리했고,
JSGF는 미리 intent, entity, slot 을 지정해 두고
정해진 명령어가 들어왔을 때 각각의 intent, entity, slot 을 매칭시켜 추출할 때 편리했습니다.</p>
<p>만약 대화 내용을 기억해야 할 경우에는 rivescript 와JSGF 모두 메모리 기능을 따로 지원하지 않으므로
Redis 또는 RDBMS 를 연동해야 합니다.</p>
<h2 id="langgraph">LangGraph</h2>
<p>최근에는 LangGraph 를 이용해 AI Agent 를 개발합니다.
기존 챗봇의 경우 FAQ 및 고객센터용 챗봇처럼 정해진 질문과 답변이 있고
패턴 매칭 방식을 이용해 가볍고 빠르게 예측 가능한 응답을 제공하였으나
최근에는 LangGraph 를 이용해 자율적이고 지능적인 의사결정이 가능한 챗봇 (주어진 목표를 스스로 해결)을 개발할 수 있습니다.</p>
<p>아래는 AI Agent 와 일반 챗봇(기존) 을 비교한 ChatGPT 답변입니다.</p>
<h3 id="✅-ai-agent-vs-일반-챗봇의-차이">✅ <strong>AI Agent vs. 일반 챗봇의 차이</strong></h3>
<h4 id="📌-기본-개념"><strong>📌 기본 개념</strong></h4>
<ul>
<li><strong>일반 챗봇</strong> → 미리 정의된 <strong>규칙 또는 스크립트 기반</strong>으로 작동하는 챗봇  </li>
<li><strong>AI Agent</strong> → <strong>자율적이고 지능적인 의사결정</strong>이 가능한 챗봇 (주어진 목표를 스스로 해결)  </li>
</ul>
<hr>
<h3 id="🔹-1-주요-차이점-비교">🔹 <strong>1. 주요 차이점 비교</strong></h3>
<table>
<thead>
<tr>
<th>비교 항목</th>
<th><strong>일반 챗봇</strong> 🤖</th>
<th><strong>AI Agent</strong> 🧠</th>
</tr>
</thead>
<tbody><tr>
<td><strong>작동 방식</strong></td>
<td>정해진 규칙, 스크립트 기반</td>
<td>자율적 의사결정, AI 기반</td>
</tr>
<tr>
<td><strong>자연어 이해 (NLU)</strong></td>
<td>제한적 (정해진 패턴만 이해)</td>
<td>강력한 이해 능력 (LLM 활용)</td>
</tr>
<tr>
<td><strong>상황 적응력</strong></td>
<td>낮음 (예상 질문만 대응 가능)</td>
<td>높음 (새로운 상황에 적응 가능)</td>
</tr>
<tr>
<td><strong>멀티태스킹</strong></td>
<td>단일 태스크 수행</td>
<td>여러 개의 작업을 동시에 수행 가능</td>
</tr>
<tr>
<td><strong>의사결정 능력</strong></td>
<td>없음 (단순한 응답만 수행)</td>
<td>있음 (자율적으로 해결책 찾음)</td>
</tr>
<tr>
<td><strong>상태 기억</strong></td>
<td>제한적 (이전 대화 기억 어려움)</td>
<td>강력한 메모리 기능 (장기 기억 가능)</td>
</tr>
<tr>
<td><strong>API 연동</strong></td>
<td>단순한 API 호출</td>
<td>복잡한 API 조합 및 활용 가능</td>
</tr>
<tr>
<td><strong>사용 사례</strong></td>
<td>FAQ 챗봇, 고객센터 봇</td>
<td>AI 비서, 자동화된 상담사, 연구 보조</td>
</tr>
</tbody></table>
<hr>
<h3 id="🔹-2-작동-방식-차이">🔹 <strong>2. 작동 방식 차이</strong></h3>
<h4 id="📌-1-일반-챗봇의-작동-방식"><strong>📌 (1) 일반 챗봇의 작동 방식</strong></h4>
<p>✅ <strong>규칙 기반 (Rule-Based) 챗봇</strong>  </p>
<ul>
<li>미리 정해진 <strong>패턴과 응답</strong>에 따라 작동  </li>
<li>예) RiveScript, AIML 기반 챗봇  </li>
</ul>
<p>✔ <strong>예제 (RiveScript)</strong></p>
<pre><code class="language-rivescript">+ hello
- Hi there! How can I help you?</code></pre>
<p>📌 &quot;hello&quot;라는 입력이 들어오면 항상 같은 응답을 반환  </p>
<p>✅ <strong>기본적인 API 연동 가능</strong>  </p>
<ul>
<li>예) &quot;날씨 알려줘&quot; → 특정 API 호출 후 응답  </li>
</ul>
<p>✔ <strong>작동 방식</strong><br><strong>사용자 입력 → 정해진 패턴 매칭 → 미리 정의된 응답 제공</strong><br>🚨 <strong>새로운 질문이 들어오면 대응 불가능!</strong>  </p>
<hr>
<h4 id="📌-2-ai-agent의-작동-방식"><strong>📌 (2) AI Agent의 작동 방식</strong></h4>
<p>✅ <strong>자율적인 의사결정 (LLM 활용)</strong>  </p>
<ul>
<li><strong>GPT-4, LangGraph, AutoGPT</strong> 등과 같은 AI 기술을 사용하여 새로운 상황에도 적응 가능  </li>
</ul>
<p>✅ <strong>멀티에이전트 협력 가능</strong>  </p>
<ul>
<li>여러 AI가 서로 다른 역할을 맡아 문제 해결 가능  </li>
</ul>
<p>✔ <strong>예제 (LangGraph)</strong></p>
<pre><code class="language-python">from langgraph.graph import StateGraph
from langchain.chat_models import ChatOpenAI

llm = ChatOpenAI(model=&quot;gpt-4&quot;)

class ChatState:
    def __init__(self, messages=None):
        self.messages = messages or []

def ai_agent(state: ChatState):
    response = llm(state.messages)
    return ChatState(messages=state.messages + [response])

graph = StateGraph(ChatState)
graph.add_node(&quot;ai_agent&quot;, ai_agent)
graph.set_entry_point(&quot;ai_agent&quot;)
graph.set_finish_point(&quot;ai_agent&quot;)

state = ChatState(messages=[&quot;Can you summarize today&#39;s news?&quot;])
result = graph.invoke(state)

print(result.messages[-1].content)</code></pre>
<p>📌 <strong>자율적으로 질문을 이해하고 답변 생성 가능!</strong><br>📌 <strong>기존 챗봇과 달리 API 호출, 문서 요약 등 다양한 작업 수행 가능</strong>  </p>
<hr>
<h3 id="🔹-3-실제-활용-사례">🔹 <strong>3. 실제 활용 사례</strong></h3>
<table>
<thead>
<tr>
<th>사용 사례</th>
<th><strong>일반 챗봇</strong> 🤖</th>
<th><strong>AI Agent</strong> 🧠</th>
</tr>
</thead>
<tbody><tr>
<td><strong>FAQ 챗봇</strong></td>
<td>가능 (고정된 응답 제공)</td>
<td>가능 (더 자연스러운 대화 가능)</td>
</tr>
<tr>
<td><strong>고객 지원</strong></td>
<td>제한적 (간단한 질문 응답)</td>
<td>복잡한 요청 처리 가능</td>
</tr>
<tr>
<td><strong>AI 비서</strong></td>
<td>불가능</td>
<td>가능 (작업 자동화, 일정 관리 등)</td>
</tr>
<tr>
<td><strong>문서 요약/검색</strong></td>
<td>불가능</td>
<td>가능 (LLM 활용)</td>
</tr>
<tr>
<td><strong>자동화된 연구 분석</strong></td>
<td>불가능</td>
<td>가능 (다양한 데이터 분석 및 학습)</td>
</tr>
</tbody></table>
<hr>
<h3 id="✅-4-결론-언제-어떤-걸-선택해야-할까">✅ <strong>4. 결론: 언제 어떤 걸 선택해야 할까?</strong></h3>
<h4 id="🔹-일반-챗봇이-적합한-경우"><strong>🔹 일반 챗봇이 적합한 경우</strong></h4>
<p>✅ 정해진 질문과 답변이 있는 챗봇 (FAQ, 고객센터)<br>✅ 가벼운 시스템을 원할 때<br>✅ 빠르고 예측 가능한 응답이 필요할 때<br>✅ <strong>예제:</strong> RiveScript, Dialogflow(룰 기반 모드)  </p>
<h4 id="🔹-ai-agent가-적합한-경우"><strong>🔹 AI Agent가 적합한 경우</strong></h4>
<p>✅ 복잡한 자연어 이해 및 응답이 필요할 때<br>✅ 새로운 질문에도 대응할 수 있어야 할 때<br>✅ 여러 작업을 수행하는 AI가 필요할 때 (예: AI 비서, 자동화 챗봇)<br>✅ <strong>예제:</strong> LangGraph, AutoGPT, ChatGPT API  </p>
<hr>
<h3 id="🎯-최종-정리">🎯 <strong>최종 정리</strong></h3>
<p>1️⃣ <strong>FAQ 챗봇, 간단한 고객지원 챗봇 → 일반 챗봇 (RiveScript, Dialogflow 등)</strong><br>2️⃣ <strong>AI 비서, 연구 보조, 복잡한 대화 관리 → AI Agent (LangGraph, AutoGPT 등)</strong>  </p>
<p>즉, <strong>단순한 패턴 매칭 챗봇</strong>이라면 일반 챗봇,<br><strong>자율적인 AI 역할 수행이 필요</strong>하다면 AI Agent가 더 적합합니다! 🚀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리뷰] 프롬프트 엔지니어링의 비밀]]></title>
            <link>https://velog.io/@judy_choi/%EB%A6%AC%EB%B7%B0-%ED%94%84%EB%A1%AC%ED%94%84%ED%8A%B8-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81%EC%9D%98-%EB%B9%84%EB%B0%80</link>
            <guid>https://velog.io/@judy_choi/%EB%A6%AC%EB%B7%B0-%ED%94%84%EB%A1%AC%ED%94%84%ED%8A%B8-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81%EC%9D%98-%EB%B9%84%EB%B0%80</guid>
            <pubDate>Wed, 05 Feb 2025 12:51:37 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://www.yes24.com/Product/Goods/135967948">프롬프트 엔지니어링의 비밀 : 10가지 사례로 쉽게 터득! LLM과 챗GPT에게 원하는 결과를 얻는 비법!</a></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/c7835659-1c36-4d89-b4ea-902b9a360ccc/image.jpeg" alt=""></p>
<h2 id="리뷰-동기">리뷰 동기</h2>
<p>요즘에는 LLM API(ex : ChatGPT) 를 사다가 웹 개발물에 붙여서 서비스를 만드는 경우가 정말 많다.
이 책을 읽게 된 동기는 최근 모 번역 스타트업 면접에서 떨어졌기 때문이다.
가장 큰 이유는 처우가 안 맞아서였고, 그 다음 이유는
그 스타트업 또한 LLM API 를 이용해 번역을 하다 보니 프롬프트를 잘 넣고 결과물을 평가하는 프로세스를 구축해야 하는데
나는 모델 개발만 해봤지 프롬프트 엔지니어링은 안 해봤기 때문에 직무 요구조건이 안 맞아서였다 😇
그래서 대체 프롬프트 엔지니어링이 뭐가 그렇게 중요하고 유용한지, 어떻게 하는 건지 궁금해서 읽게 되었는데
과연, 프롬프트만으로 과장 좀 보태서 사람 만드는 것 빼곤 다 할 수 있겠더라.
책 표지의 멘트 그대로 정말 사례와 실습으로 쉽게 배울 수 있었다.</p>
<h2 id="장점">장점</h2>
<h3 id="프롬프트-잘-쓰는-노하우-대공개">프롬프트 잘 쓰는 노하우 대공개</h3>
<p>막연하게 &#39;프롬프트 잘 쓰는 법&#39; 이 아니라 상당히 체계적으로 노하우를 알려준다.
<img src="https://velog.velcdn.com/images/judy_choi/post/0264d87f-6a31-4800-8a72-f797a83ef5b0/image.jpeg" alt="">
<img src="https://velog.velcdn.com/images/judy_choi/post/892f8ae4-62a6-451e-aa76-1fd8dfb97cfc/image.jpeg" alt="">
<img src="https://velog.velcdn.com/images/judy_choi/post/3c6be996-270b-45ed-8294-35b7d64be238/image.jpeg" alt=""></p>
<h3 id="다양한-사례가-체계적으로-정리되어-서술">다양한 사례가 체계적으로 정리되어 서술</h3>
<p>LLM을 이용한 생성, 텍스트 분석, 평가, 코드 작성까지
LLM으로 할 수 있는 아주 다양한 작업을 소개하고, 
각 작업에 맞게 작성한 프롬프트와 결과물을 보여준다.
얼마나 다양한지는... 책의 목차만 봐도 알 수 있다 😃
목차별로 다양한 사례가 잘 정리되어 있어서 필요한 부분만 찾아 읽을 수 있다는 점도 편리했다.</p>
<h3 id="집중하기-편한-구성">집중하기 편한 구성</h3>
<p>개인적으로 릴스, 숏츠 이런 걸 하도 보니 책을 집중해서 읽기가 어려운데
이 책은 AI가 필요한 상황을 먼저 소주제로 제시하고,
프롬프트 작성에 필요한 배경 지식을 간결하게 리스트업하고,
이를 토대로 프롬프트 예시와 출력 결과를 보여주기 때문에
짧디짧은 집중력을 가지고 있어도 각 케이스별로 내용을 짧게 끊어 읽기에 좋았고
각 장의 끝부분마다 내용을 요약해 주기 때문에 읽은 내용을 정리할 수 있어서 아주 편리했다.
<img src="https://velog.velcdn.com/images/judy_choi/post/be9302d8-b2d3-4ad6-8deb-85edafef4916/image.jpeg" alt=""></p>
<h2 id="이-책을-추천하고-싶은-사람">이 책을 추천하고 싶은 사람</h2>
<ul>
<li>ChatGPT 같은 LLM 서비스를 200% 활용하고 싶은 누구나<ul>
<li>기획, 홍보, 창작자, 개발자 누구든지</li>
<li>프롬프트만 잘 넣어도 LLM의 답변이 달라진다...!</li>
</ul>
</li>
<li>프롬프트를 어떻게 넣어야 하는지 모르는 경우<ul>
<li>프롬프트 넣을 때 고려해야 할 점을 사례별로 설명해 주기 때문에 매우 유용함.</li>
</ul>
</li>
<li>LLM API 를 이용하여 빠르게 프로토타입을 만들어야 하는 사람<ul>
<li>창업자, 기획자, 개발자 누구든 빠르게 LLM 서비스를 만들어야 하는 경우</li>
</ul>
</li>
</ul>
<p>개인적으로 프롬프트 엔지니어링 책이니까 나같은 AI엔지니어에게 얼마나 유용할까 싶었는데, 책 뒷부분에서 빠르게 챗봇 만드는 방법과 LangChain 까지 다루기 때문에
만약 내가 빠르게 프로토타입을 구현해야 한다면 이 책이 엄청 유용할 것 같다!
<img src="https://velog.velcdn.com/images/judy_choi/post/e17de7ad-0481-4654-84a4-97f7bce5998d/image.jpeg" alt=""></p>
<h2 id="마무리">마무리</h2>
<p>이 책을 읽고 나니 왜 많은 스타트업들이 LLM API 를 사다가 AI서비스를 만드는지 이해할 수 있었다......
LLM에 프롬프트만 잘 넣어도 내가 원하는 작업을 할 때 편리하고 기능이 무궁무진하고 심지어 결과물의 퀄리티가 준수해... 😂
사실 이제는 LLM 모델을 일단 만들어도 프롬프트를 잘못 넣으면 결과값이 잘 안 나오기도 한다.
(예전에 Llama3 파인튜닝 시켰는데 이상하게 결과가 일정하지 않게 나오더라.)
일단 이 책을 참고해서 빠르게 프로젝트를 하나 만들어 볼까... 😇</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RAG 시리즈] 임베딩 모델별 RAG 성능 비교]]></title>
            <link>https://velog.io/@judy_choi/RAG-%EC%8B%9C%EB%A6%AC%EC%A6%88-%EC%9E%84%EB%B2%A0%EB%94%A9-%EB%AA%A8%EB%8D%B8%EB%B3%84-RAG-%EC%84%B1%EB%8A%A5-%EB%B9%84%EA%B5%90</link>
            <guid>https://velog.io/@judy_choi/RAG-%EC%8B%9C%EB%A6%AC%EC%A6%88-%EC%9E%84%EB%B2%A0%EB%94%A9-%EB%AA%A8%EB%8D%B8%EB%B3%84-RAG-%EC%84%B1%EB%8A%A5-%EB%B9%84%EA%B5%90</guid>
            <pubDate>Sun, 02 Feb 2025 11:41:43 GMT</pubDate>
            <description><![CDATA[<p>지난 포스팅에서 다음과 같은 고찰을 했는데요,</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/f45bc9fd-6113-46e0-9018-996f98eb5bef/image.png" alt=""></p>
<p>따라서 이번 포스팅에서는 embedding 모델별 RAG 품질을 비교합니다.
또한 유료로 사용하는 OpenAI 모델뿐만 아니라 무료(속도는 느리지만) HuggingFace 모델도 함께 비교합니다.</p>
<p>튜토리얼 코드 : <a href="https://github.com/Judy-Choi/rag_series/tree/main/note_8">GitHub</a></p>
<h2 id="embedding-model">Embedding Model</h2>
<p>Embedding Model 로 다음 4가지 모델을 사용합니다.</p>
<h3 id="openai">OpenAI</h3>
<h4 id="text-embedding-3-large">text-embedding-3-large</h4>
<ul>
<li>튜토리얼에서 쭉 사용해 온 임베딩 모델</li>
<li>기본적으로 3072 차원</li>
<li>(ChatGPT) 정확성과 고급 의미 분석이 중요한 경우에 더 적합한 모델로, 미세한 유사성 분석이나 정교한 NLP 작업에서 뛰어난 성능을 발휘합니다.</li>
</ul>
<h4 id="text-embedding-ada-002">text-embedding-ada-002</h4>
<ul>
<li>text-embedding-3 가 나오기 전에 많이 사용된 OpenAI 임베딩 모델</li>
<li>1536 차원</li>
<li>(ChatGPT) 비용 효율성과 대규모 처리에 강점을 가진 모델입니다. 즉, 대량의 텍스트 데이터를 빠르고 저렴하게 처리해야 할 때 적합합니다.</li>
</ul>
<p>가격 차이는 다음과 같네요.
<img src="https://velog.velcdn.com/images/judy_choi/post/c667202d-c3b3-40b8-8e3f-80dd6107182b/image.png" alt=""></p>
<h3 id="huggingface">HuggingFace</h3>
<h4 id="intfloatmultilingual-e5-small">intfloat/multilingual-e5-small</h4>
<ul>
<li>레이어 12 개, 임베딩 크기 384</li>
<li>(공식문서) This model is initialized from microsoft/Multilingual-MiniLM-L12-H384 and continually trained on a mixture of multilingual datasets. It supports 100 languages from xlm-roberta, but low-resource languages may see performance degradation.<ul>
<li>이 모델은 microsoft/Multilingual-MiniLM-L12-H384 에서 초기화되었으며 다국어 데이터 세트의 혼합으로 지속적으로 학습되었습니다. xlm-roberta에서 100개 언어를 지원하지만 리소스가 부족한 언어는 성능 저하가 나타날 수 있습니다.</li>
</ul>
</li>
</ul>
<h4 id="kakaobankkf-deberta-base">kakaobank/kf-deberta-base</h4>
<ul>
<li>한국어 금융 데이터로 학습된 모델</li>
<li>금융 데이터와 보험 약관이 비슷하지 않을까? 하는 생각에 테스트</li>
<li>Reference<ul>
<li><a href="https://huggingface.co/kakaobank/kf-deberta-base">HuggingFace</a></li>
<li><a href="https://ai.kakaobank.com/f982c5b8-9cbd-4ef1-8ebd-d7359a70284b">카카오뱅크의 금융특화 언어모델 KF-DeBERTa 개발기</a></li>
</ul>
</li>
</ul>
<h2 id="code">Code</h2>
<p>보험 약관문서를 읽어들여 Vector store 에 적재하는 코드와 Langchain 을 만드는 코드는 생략합니다. (전체 코드는 <a href="https://github.com/Judy-Choi/rag_series/tree/main/note_8">GitHub</a> 참고)</p>
<h3 id="vector-store-세팅">Vector Store 세팅</h3>
<p>벡터스토어의 임베딩 모델을 세팅합니다.</p>
<pre><code class="language-python">from langchain_postgres import PGVector
from langchain_postgres.vectorstores import PGVector
# OpenAI 임베딩을 이용할 경우
from langchain_openai import OpenAIEmbeddings
# HuggingFace 임베딩을 이용할 경우
from langchain_huggingface import HuggingFaceEmbeddings

# See docker command above to launch a postgres instance with pgvector enabled.
# connection = f&quot;postgresql+psycopg2://user:password@host:5432/name&quot;,
connection=f&quot;postgresql+psycopg2://rag_note:rag_note@localhost:5433/rag_note&quot;

embedding_name = &quot;text-embedding-3-large&quot;

vector_store = PGVector(
    embeddings=OpenAIEmbeddings(model=embedding_name),
    # HuggingFaceEmbeddings(model_name=embedding_name),
    collection_name=embedding_name,
    connection=connection,
    use_jsonb=True,
)</code></pre>
<h3 id="질문에-맞는-문서-검색">질문에 맞는 문서 검색</h3>
<pre><code class="language-python">vector_store.similarity_search(query=&quot;보험금의 지급사유&quot;)</code></pre>
<p>결과</p>
<pre><code>[Document(id=&#39;c6c0092d-2e1d-48bc-a979-dc45b9f51dc0&#39;, metadata={&#39;page&#39;: 50, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 40 -(  )상해 (  )%미만 후유장해 특별약관\n제1조(보험금의 지급사유 ) \n회사는 피보험자가 보험기간 중 ( )특약 제( )조(보험금의 지급사유 )에 정한 상해의 직접결과로\n써 장해분류표 ([별표1] 참조. 이하 같습니다 )에서 정한 장해지급률이 (  )% 미만에 해당하는 \n장해상태가 되었을 때에는 후유장해보험금 (장해분류표에서 정한 지급률을 보험가입금액에 곱하\n여 산출한 금액)을 보험수익자에게 지급하여 드립니다 .\n제2조(준용규정 )\n이 특별약관에 정하지 않은 사항은 보통약관 또는 해당 특별약관을 따릅니다 .&#39;),
 Document(id=&#39;8d1adc20-e08d-4a9f-9fed-a5f694b0388a&#39;, metadata={&#39;page&#39;: 14, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 4 - 4. 기타 보험수익자가 보험금의 수령에 필요하여 제출하는 서류(사망보험금 지급시 피보험\n자의 법정상속인이 아닌자가 청구하는 경우 법정상속인의 확인서 )\n② 제1항 제2호의 사고증명서는 의료법 제3조(의료기관 )에서 규정한 국내의 병원이나 의원 또\n는 국외의 의료관련법에서 정한 의료기관에서 발급한 것이어야 합니다 .\n제8조(보험금의 지급절차 ) \n① 회사는 제7조(보험금의 청구)에서 정한 서류를 접수한 때에는 접수증을 드리고 휴대전화 문\n자메시지 또는 전자우편 등으로도 송부하며 , 그 서류를 접수한 날부터 3영업일 이내에 보\n험금을 지급합니다 .\n② 회사가 보험금 지급사유를 조사․확인하기 위해 필요한 기간이 제1항의 지급기일을 초과할 \n것이 명백히 예상되는 경우에는 그 구체적인 사유와 지급예정일 및 보험금 가지급제도 (회사\n가 추정하는 보험금의 50% 이내를 지급)에 대하여 피보험자 또는 보험수익자에게 즉시 통\n지합니다 . 다만, 지급예정일은 다음 각 호의 어느 하나에 해당하는 경우를 제외하고는 제7\n조(보험금의 청구)에서 정한 서류를 접수한 날부터 30영업일 이내에서 정합니다 .\n 1. 소송제기\n 2. 분쟁조정 신청\n【분쟁조정 신청】\n분쟁조정 신청은 이 약관의 「분쟁의 조정」조항에 따르며 분쟁조정 신청 대상기관은 금융\n감독원의 금융분쟁조정위원회를 말합니다 .\n 3. 수사기관의 조사 \n 4. 해외에서 발생한 보험사고에 대한 조사\n 5. 제6항에 따른 회사의 조사요청에 대한 동의 거부 등 계약자 , 피보험자 또는 보험수익자\n의 책임있는 사유로 보험금 지급사유의 조사와 확인이 지연되는 경우\n 6. 제4조(보험금 지급에 관한 세부규정 ) 제6항에 따라 보험금 지급사유에 대해 제3자의 의\n견에 따르기로 한 경우\n③ 제2항에 의하여 장해지급률의 판정 및 지급할 보험금의 결정과 관련하여 확정된 장해지급\n률에 따른 보험금을 초과한 부분에 대한 분쟁으로 보험금 지급이 늦어지는 경우에는 보험\n수익자의 청구에 따라 이미 확정된 보험금을 먼저 가지급합니다 .\n④ 제2항에 의하여 추가적인 조사가 이루어지는 경우, 회사는 보험수익자의 청구에 따라 회사\n가 추정하는 보험금의 50% 상당액을 가지급보험금으로 지급합니다 .\n【가지급보험금 】\n보험금이 지급기한 내에 지급되지 못할 것으로 판단되는 경우 회사가 예상되는 보험금의 \n일부를 먼저 지급하는 제도로 피보험자가 필요로 하는 비용을 보전해 주기 위해 회사가 먼\n저 지급하는 임시 교부금을 말합니다 .\n⑤ 회사는 제1항에서 정한 지급기일내에 보험금을 지급하지 않았을 때(제2항에서 정한 지급예\n정일을 통지한 경우를 포함합니다 )에는 그 다음날부터 지급일까지의 기간에 대하여 &lt;부표&gt; \n‘보험금을 지급할 때의 적립이율 ’에 따라 연단위 복리로 계산한 금액을 보험금에 더하여 지\n급합니다 . 다만, 계약자 , 피보험자 또는 보험수익자에게 책임이 있는 사유로 지급이 지연된 \n경우에는 그 기간에 대한 이자는 지급하지 않습니다 .\n 기     간 지 급 이 자\n지급기일의 다음 날부터 30일 이내 기간 보험계약대출이율\n지급기일의 31일이후부터 60일이내 기간 보험계약대출이율 + 가산이율 (4.0%)\n지급기일의 61일이후부터 90일이내 기간 보험계약대출이율 + 가산이율 (6.0%)\n지급기일의 91일이후 기간 보험계약대출이율 + 가산이율 (8.0%)&lt;부표&gt; 보험금을 지급할 때의 적립이율&#39;),
 Document(id=&#39;c303c659-fe41-4351-a600-8ae0087ef3b6&#39;, metadata={&#39;page&#39;: 179, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 169 - 4. 피보험자 및 지정대리청구인의 가족관계등록부 (가족관계증명서 ) 및 주민등록등본\n 5. 기타 지정대리청구인이 보험금 등의 수령에 필요하여 제출하는 서류\n제7조(준용규정 )\n이 특약에서 정하지 않은 사항에 대하여는 보통약관 및 해당 특별약관의 규정을 따릅니다 .&#39;),
 Document(id=&#39;3451d792-9a0f-4415-9f41-a3bf3ddf1b48&#39;, metadata={&#39;page&#39;: 9, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;보험용어 해설\n보험용어 용어 해설\n보험약관보험계약에 관하여 보험계약자와 보험회사 상호간에 이행하여야 할 권리\n와 의무를 규정한 것\n보험증권보험계약의 성립과 그 내용을 증명하기 위하여 보험회사가 보험계약자에\n게 교부하는 증서\n보험계약자 보험회사와 계약을 체결하고 보험료 납입의무를 지는 사람\n피보험자 보험사고 발생의 대상이 되는 사람\n보험수익자 보험사고 발생시 보험금 청구권을 갖는 사람\n보험료 보험계약자가 보험계약에 의거하여 보험회사에게 지급하여야하는 요금\n보험목적 보험사고의 발생의 객체가 되는 경제상의 재화\n보험가액재산보험에 있어 피보험 이익을 금전으로 평가한 금액으로 보험의 목적\n에 발생할 수 있는 최대 손해액을 말합니다 .(회사가 실제 지급하는 보험\n금은 보험가액을 초과할 수 없습니다 ) \n보험가입금액보험사고 발생시 보험회사가 지급하여야 할 보험금의 기준이 되는 금액\n으로 보험증권에 기재된 금액\n보험금◦ 신체손해 ·비용손해 보장 : 피보험자의 사망, 장해, 입원 등 보험금 지\n급사유가 발생하였을 때 보험회사가 보험수익자에게 지급하는 금액\n◦ 재물손해 ·배상책임 보장 : 피보험자의 재물손해 , 배상책임손해 등 보험\n금 지급사유가 발생하였을 때 보험회사가 피보험자에게 지급하는 금액\n보험기간 계약에 따라 보장을 받는 기간으로 보험증권에 기재된 기간\n보장개시일 보험회사의 보험금 지급의무가 시작되는 날\n보험년도보험계약일로부터 매1년 단위의 연도(당해연도 보험계약 해당일부터 다\n음연도 보험계약 해당일 전일까지의 기간)를 말함\n영업일회사가 영업점에서 정상적으로 영업하는 날을 말하며 , “토요일 ”, “관공\n서의 공휴일에 관한 규정에 따른 공휴일 ” 및 “근로자의 날”을 제외한 날\n을 의미합니다 .&#39;)]</code></pre><h2 id="임베딩-모델별-검색-결과-및-langchain-생성-답변-비교">임베딩 모델별 검색 결과 및 Langchain 생성 답변 비교</h2>
<p>공통 질문 : &#39;보험금의 지급사유&#39;
<img src="https://velog.velcdn.com/images/judy_choi/post/08ed31cd-bfc8-48e3-8b83-1ce4e7e9a864/image.png" alt=""></p>
<ul>
<li>text-embedding-3-large 모델 성능이 가장 준수.<ul>
<li>질문 의도에 가장 잘 맞는 문서 (약관에서 언급하는 보험금의 지급사유 중 가장 대표적인 지급사유) 검색</li>
</ul>
</li>
<li>무료 임베딩 모델을 사용할 경우 <code>multilingual-e5-small</code> 의 성능이 준수함.</li>
<li>kakaobank/kf-deberta-base 모델 성능이 가장 낮음<ul>
<li>금융 데이터와 생명보험 약관 데이터가 비슷하지 않기 때문으로 추정</li>
</ul>
</li>
<li>Retriever 검색 결과를 비교하여 적합한 임베딩 모델을 사용하거나 도메인에 맞게 임베딩 모델을 파인튜닝하여 사용할 경우 검색 결과가 더 좋아질 수 있고, 이는 답변 품질 향상으로 이어짐</li>
<li>RAG 의 품질은 결국 Retriever 성능에 좌우됨 (검색을 잘 해야 좋은 답변을 만들 수 있음)</li>
<li>임베딩 모델의 차원수가 클수록 성능이 좋겠으나, Vector store (본 튜토리얼에서는 pgvector) 에 저장되는 벡터의 차원수가 클수록 저장 용량도 커짐.<ul>
<li>서비스 관점에서는 임베딩 성능과 저장 용량을 모두 고려하여 적합한 임베딩 차원수 또는 모델을 선정하는 것을 추천.</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RAG 시리즈] RAGAS 를 이용한 실전 RAG 평가]]></title>
            <link>https://velog.io/@judy_choi/RAG-%EC%8B%9C%EB%A6%AC%EC%A6%88-RAGAS-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%8B%A4%EC%A0%84-RAG-%ED%8F%89%EA%B0%80-3u6xgtxe</link>
            <guid>https://velog.io/@judy_choi/RAG-%EC%8B%9C%EB%A6%AC%EC%A6%88-RAGAS-%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%8B%A4%EC%A0%84-RAG-%ED%8F%89%EA%B0%80-3u6xgtxe</guid>
            <pubDate>Tue, 21 Jan 2025 08:41:51 GMT</pubDate>
            <description><![CDATA[<p>RAGAS 는 RAG 의 평가지표 중 특히 Rule Base 지표들을 자동으로 계산하기 위해 만들어진 프레임워크입니다.</p>
<p>쉽게 말해 RAG 파이프라인을 구축한 후,
파이프라인의 input / output 을 이용해 평가 지표 계산에 필요한 데이터셋 (question, answer, context, ground truth) 을 만들고
RAGAS 는 이 데이터셋을 이용하여 함수 단 한줄로 각 평가 지표를 빠르게 자동으로 계산해 줍니다.</p>
<p>RAGAS 에 대한 자세한 설명은 <a href="https://velog.io/@judy_choi/%EC%9A%B0%EC%95%84%ED%95%9C-%EC%8A%A4%ED%84%B0%EB%94%94-RAGAS-RAG-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%ED%8F%89%EA%B0%80-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC">[우아한 스터디] RAGAS : RAG 파이프라인 평가 프레임워크</a> 을,
이 포스팅의 전체 코드 및 샘플은 <a href="https://github.com/Judy-Choi/rag_series/tree/main/note_7">GitHub</a> 을 참고해 주세요.</p>
<h2 id="1-ragas-준비물">1. RAGAS 준비물</h2>
<h3 id="11-dataset">1.1 Dataset</h3>
<p>데이터는 총 4가지를 준비합니다.</p>
<ul>
<li><code>question</code> : 사용자의 질문</li>
<li><code>ground truth</code> : 질문에 맞는 정답 (= 모범 답안)</li>
<li><code>answer</code> : LLM이 생성한 답변</li>
<li><code>context</code> : LLM이 답변을 생성하기 위해 참고한 정보 (=context)</li>
</ul>
<h3 id="12-평가-지표">1.2 평가 지표</h3>
<p>보편적으로 다음 4개의 평가 지표를 이용합니다.</p>
<ul>
<li><p><code>Faithfulness</code> : 주어진 context에 대한 생성된 답변의 일관성에 대한 값​
(context - answer)</p>
</li>
<li><p><code>Context Recall</code> : ground_truth의 문장 중 contex로부터 추론할 수 있는 문장의 비율을 측정한 값
(ground truth - context)</p>
</li>
<li><p><code>Context Precision</code> : contexts에 존재하는 ground_truth와 관련된 항목을 높은 순위로 잘 검색해 왔는지의 여부를 평가하는 지표
(grund truth - context 및 참조 순위와 지표)</p>
</li>
<li><p><code>Answer Relevance</code> : 생성된 답변이 주어진 질문과 얼마나 관련성이 있는지에 대한 값​
(answer - question)</p>
</li>
</ul>
<p><a href="https://smilegate.ai/2024/11/18/ragas-rag-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%B1%EB%8A%A5-%ED%8F%89%EA%B0%80-%EC%9D%B4%EC%A0%9C%EB%8A%94-%EC%A0%9C%EB%8C%80%EB%A1%9C-%ED%95%B4%EB%B3%B4%EC%9E%90/">https://smilegate.ai/2024/11/18/ragas-rag-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%B1%EB%8A%A5-%ED%8F%89%EA%B0%80-%EC%9D%B4%EC%A0%9C%EB%8A%94-%EC%A0%9C%EB%8C%80%EB%A1%9C-%ED%95%B4%EB%B3%B4%EC%9E%90/</a></p>
<h2 id="2-langchain-없이-ragas-적용하기">2. Langchain 없이 RAGAS 적용하기</h2>
<p>Langchain 을 사용하지 않고, 데이터셋이 모두 구축된 경우 스코어만 계산하는 간단한 튜토리얼 코드입니다.
RAGAS 는 주어진 데이터에 각 평가지표별 수식을 계산하기 때문에
질문과 실제 답안뿐만이 아니라 LLM 으로 생성한 답변과 답변을 생성하는 데 참고한 context 만 준비한다면 RAGAS 를 이용해 품질 평가를 할 수가 있습니다.</p>
<h3 id="21-dataset-생성">2.1 Dataset 생성</h3>
<pre><code class="language-python">from datasets import Dataset

questions = [&quot;보험가격지수란 무엇인가요?&quot;]
ground_truths = [&quot;해당상품의 보험료총액(보험금 지급을 위한 보험료 및 보험회사의 사업경비 등을 위한 보험료)을 참조순보험료 총액*과 평균사업비 총액**을 합한 금액으로 나눈 비율을 “보험가격지수”라고 합니다.보험가격지수란 고객이 납입하는 보험료 중 사업비로 사용되는 금액을 수준에 대하여 상품군별 생명보험상품 전체의 평균 사업비율을 반영하여 계산한 값입니다.&quot;]
answers = [&quot;보험가격지수는 해당 상품의 보험료 총액을 참조순보험료 총액과 평균사업비 총액을 합한 금액으로 나눈 비율을 말합니다. 보험료 총액은 보험금 지급을 위한 보험료와 보험회사의 사업경비 등을 위한 보험료를 포함합니다. 참조순보험료 총액은 감독원장이 정하는 바에 따라 산정한 전체 보험회사 공시이율의 평균(평균공시이율) 및 참조순보험요율을 적용하여 산출한 보험금 지급을 위한 보험료입니다. 평균사업비 총액은 상품군별 생명보험상품 전체의 평균 사업비율을 반영하여 계산(역산)한 값입니다.&quot;]
contexts = [[&#39;A : 해당상품의 보험료총액(보험금 지급을 위한 보험료 및 보험회사의 사업경비 등을 위한 보험료)을 참조순보험료 총액*과 평균사업비 총액**을 합한 금액으로 나눈 비율을 “보험가격지수”라고 합니다.* 감독원장이 정하는 바에 따라 산정한 전체 보험회사 공시이율의 평균(평균공시이율) 및 참조순보험요율을 적용하여 산출한 보험금 지급을 위한 보험료 ** 상품군별 생명보험상품 전체의 평균 사업비율을 반영하여 계산(역산)한 값(기준 : 40세) | 상품명 | 상품명 | 상품명 | 보험기간 | 납입기간 | 보험가격지수 | 보험가격지수 | 가입금액 (만원) | | --- | --- | --- | --- | --- | --- | --- | --- | | 상품명 | 상품명 | 상품명 | 보험기간 | 납입기간 | 남자 | 여자 | 가입금액 (만원) | | 보험상품명 | 1종 | 1종 | 종신 | 20년 | 110.9% | 113.6% | 10,000 | | 보험상품명 | 2종(1% 지급형) | 은퇴나이 55세 | 종신 | 20년 | 110.7% | 113.6% | 10,000 || 보험상품명 | 2종(1% 지급형) | 은퇴나이 60세 | 종신 | 20년 | 110.6% | 113.4% | 10,000 | | 보험상품명 | 2종(1% 지급형) | 은퇴나이 65세 | 종신 | 20년 | 110.4% | 113.3% | 10,000 | | 보험상품명 | 2종(2% 지급형) | 은퇴나이 55세 | 종신 | 20년 | 110.3% | 113.2% | 10,000 | | 보험상품명 | 2종(2% 지급형) | 은퇴나이 60세 | 종신 | 20년 | 110.0% | 113.0% | 10,000 | | 보험상품명 | 2종(2% 지급형) | 은퇴나이 65세 | 종신 | 20년 | 109.6% | 112.8% | 10,000 || 보험상품명 | 3종 | 3종 | 종신 | 20년 | 108.7% | 111.1% | 10,000 |&#39;]]


# To dict
data = {
  &quot;user_input&quot;: questions,
  &quot;response&quot;: answers,
  &quot;retrieved_contexts&quot;: contexts,
  &quot;reference&quot;: ground_truths
}

# Convert dict to dataset
dataset = Dataset.from_dict(data)</code></pre>
<p><strong>참고</strong>
기존(2024년 여름 기준) 의 data dict 포맷은 다음과 같았으나, 현재는 해당 포맷을 그대로 사용하면 에러가 납니다.</p>
<pre><code class="language-python">data_samples = {
    &#39;question&#39;: ...
    &#39;answer&#39;: ...
    &#39;contexts&#39; : ... 
    &#39;ground_truth&#39;: ...
}</code></pre>
<p>다음과 같이 최신 dict 포맷으로 고쳐 주세요.</p>
<pre><code class="language-python">data = {
  &quot;user_input&quot;: questions,
  &quot;response&quot;: answers,
  &quot;retrieved_contexts&quot;: contexts,
  &quot;reference&quot;: ground_truths
}</code></pre>
<h3 id="22-score-계산-평가">2.2 Score 계산 (평가)</h3>
<pre><code class="language-python">from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_recall,
    context_precision,
)

result = evaluate(
    dataset = dataset, 
    metrics=[
        context_precision,
        context_recall,
        faithfulness,
        answer_relevancy,
    ],
)

# score 출력
print(result)
# DataFrame 생성
df = result.to_pandas()
df</code></pre>
<h3 id="23-결과">2.3 결과</h3>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/130d0f63-7ed1-4aaf-b200-0ca2fb7e99bf/image.png" alt=""></p>
<h2 id="3-rag-langchain-을-생성하고-자동으로-ragas-적용하기">3. RAG Langchain 을 생성하고 자동으로 RAGAS 적용하기</h2>
<p>실전에서는 사용자의 질문과(question), 질문에 맞는 정답(ground truth) 만 엑셀 파일 등에 미리 만들어 두고
RAG Langchain 에 입력하고, 자동으로 RAG 실행 후 RAGAS 를 적용할 수 있습니다.</p>
<p>RAG Langchain 을 구축한 후 RAGAS 까지 한번에 적용하는 코드는 다음과 같습니다.</p>
<h3 id="31-rag-langchain-구축">3.1 RAG Langchain 구축</h3>
<h4 id="llm-정의">LLM 정의</h4>
<pre><code class="language-python">from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model=&quot;gpt-4-0125-preview&quot;, temperature=0)</code></pre>
<h4 id="프롬프트-템플릿-작성">프롬프트 템플릿 작성</h4>
<pre><code class="language-python">from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.messages import SystemMessage

system_prompt = SystemMessage(content=
        &quot;&quot;&quot;당신은 전문 상담원입니다. 아래 지침에 따라 사용자의 질문에 답변을 제공하세요.
        ---------------------
        1. 주어진 정보만 활용하여 답변을 제공하세요. 주어진 정보로 답변을 할 수 없는 경우, 정중하게 답변을 제공할 수 없다고 설명합니다.
        2. 답변은 정제된 형식과 문어체로 작성하며, 친절하고 자세한 내용을 제공합니다.
        ---------------------
        &quot;&quot;&quot;
)

template = (
        &quot;Below is the context information.\n&quot;
        &quot;---------------------\n&quot;
        &quot;{context}&quot;
        &quot;\n---------------------\n&quot;
        &quot;Given the context information, provide a most relevant chunk to {query}.&quot;
        &quot;If there is no title that matches, output &#39;해당정보 존재하지 않음&#39;.&quot;
        &quot;Do not include the title on your final output.&quot;
)

prompt = ChatPromptTemplate.from_messages([system_prompt, template])</code></pre>
<h4 id="검색기retriever-설정">검색기(Retriever) 설정</h4>
<pre><code class="language-python">retriever = vector_store.as_retriever(
    search_type=&quot;mmr&quot;,
    search_kwargs={&quot;k&quot;: 10, &quot;fetch_k&quot;: 3, &quot;lambda_mult&quot;: 0.5},
)</code></pre>
<h4 id="langchain-생성">Langchain 생성</h4>
<pre><code class="language-python">chain = (
        {&quot;context&quot;: retriever, &quot;query&quot;: RunnablePassthrough()}
        | prompt
        | llm
        | StrOutputParser()
    )</code></pre>
<h3 id="32-1개-데이터셋에-ragas-적용">3.2 1개 데이터셋에 RAGAS 적용</h3>
<p>Langchain 구축 및 RAGAS 적용에 집중하기 위해 우선 1개 데이터셋만 만들어서 실행해 봅니다.</p>
<h4 id="데이터셋-준비-question-ground-truth">데이터셋 준비 (question, ground truth)</h4>
<pre><code class="language-python">from datasets import Dataset

questions = [&quot;보험금의 지급사유&quot;]
ground_truths = [&quot;회사는 피보험자에게 다음 중 어느 하나의 사유가 발생한 경우에는 보험수익자에게 약정한 보험금을 지급합니다. 1. 보험기간 중에 상해의 직접결과로써 사망한 경우(질병으로 인한 사망은 제외합니다): 사망보험금 2. 보험기간 중 상해로 장해분류표(&lt;별표1&gt; 참조)에서 정한 각 장해지급률에 해당하는 장해상태가 되었을 때: 후유장해보험금(장해분류표에서 정한 지급률을 보험가입금액에 곱하여 산출한 금액)&quot;]</code></pre>
<h4 id="rag-langchain-연결-및-데이터셋-dict-형으로-변환">RAG Langchain 연결 및 데이터셋 dict 형으로 변환</h4>
<p>answer 에는 질문에 따른 RAG 의 답변을,
context 에는 RAG가 질문을 생성하는 데 참고한 context (=Retreiver 가 검색해 온 Document) 를 넣어 줍니다.</p>
<pre><code class="language-python">answers = []
contexts = []

# Inference
for query in questions:
  answers.append(chain.invoke(query))
  contexts.append([docs.page_content for docs in retriever.get_relevant_documents(query)])

# To dict
data = {
  &quot;user_input&quot;: questions,
  &quot;response&quot;: answers,
  &quot;retrieved_contexts&quot;: contexts,
  &quot;reference&quot;: ground_truths
}

# Convert dict to dataset
dataset = Dataset.from_dict(data)</code></pre>
<h4 id="score-계산-평가">Score 계산 (평가)</h4>
<pre><code class="language-python">from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_recall,
    context_precision,
)

result = evaluate(
    dataset = dataset, 
    metrics=[
        context_precision,
        context_recall,
        faithfulness,
        answer_relevancy,
    ],
)

# score 출력
print(result)
# DataFrame 생성
df = result.to_pandas()
df</code></pre>
<h4 id="결과">결과</h4>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/9a0431de-7138-4499-b643-a5126c29aad7/image.png" alt=""></p>
<h3 id="33-실전--n개-데이터셋에-ragas-적용">3.3 실전 : N개 데이터셋에 RAGAS 적용</h3>
<p>실전에서는 미리 질문 - 답안 세트를 여러 개 만들어 엑셀파일에 정리한 뒤
자동으로 엑셀 파일을 읽어서 RAG 결과를 얻고, RAGAS 를 적용하여 효율성을 높일 수 있습니다.</p>
<h4 id="데이터셋-준비--xlsx-파일-로드">데이터셋 준비 : .xlsx 파일 로드</h4>
<pre><code class="language-python">import pandas as pd

# .xlsx 파일 경로 지정
file_path = &#39;input.xlsx&#39;

# .xlsx 파일을 읽어서 DataFrame으로 저장
data = pd.read_excel(file_path)

# 첫 번째 행의 &#39;question&#39;과 &#39;ground_truths&#39; 열 데이터를 리스트로 저장
questions = data[&#39;questions&#39;].tolist()
ground_truths = data[&#39;ground_truths&#39;].tolist()

# question과 ground_truths를 새로운 DataFrame으로 결합
result = pd.DataFrame({
    &#39;questions&#39;: questions,
    &#39;ground_truths&#39;: ground_truths
})

result

# # 만약 왼쪽 정렬로 데이터를 보고 싶다면 아래 코드 이용
# # DataFrame의 각 열을 왼쪽 정렬로 스타일링
# result_styled = result.style.set_properties(**{&#39;text-align&#39;: &#39;left&#39;})

# # 결과 출력 (Jupyter 노트북 환경에서 스타일이 반영됨)
# result_styled</code></pre>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/f1e453cb-ee29-486d-b2c7-62a459cd4df2/image.png" alt=""></p>
<h4 id="rag-langchain-연결-및-데이터셋-dict-형으로-변환-1">RAG Langchain 연결 및 데이터셋 dict 형으로 변환</h4>
<pre><code class="language-python">from tqdm import tqdm

answers = []
contexts = []

# Inference
for query in tqdm(questions):
  answers.append(chain.invoke(query))
  contexts.append([docs.page_content for docs in retriever.get_relevant_documents(query)])

# To dict
data = {
  &quot;user_input&quot;: questions,
  &quot;response&quot;: answers,
  &quot;retrieved_contexts&quot;: contexts,
  &quot;reference&quot;: ground_truths
}

# Convert dict to dataset
dataset = Dataset.from_dict(data)</code></pre>
<h4 id="score-계산-평가--결과-저장">Score 계산 (평가) &amp; 결과 저장</h4>
<pre><code class="language-python">from ragas import evaluate
from ragas.metrics import (
    faithfulness,
    answer_relevancy,
    context_recall,
    context_precision,
)

result = evaluate(
    dataset = dataset, 
    metrics=[
        context_precision,
        context_recall,
        faithfulness,
        answer_relevancy,
    ],
)

# score 출력
print(result)
# DataFrame 생성
df = result.to_pandas()
df.to_excel(&#39;output.xlsx&#39;, index=False)
df</code></pre>
<h4 id="결과-1">결과</h4>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/265d9796-a427-4522-87c0-92a870630e89/image.png" alt=""></p>
<h2 id="4-결과-분석-및-문제-해결-방안">4. 결과 분석 및 문제 해결 방안</h2>
<blockquote>
<p><code>3.3 실전 : N개 데이터셋에 RAGAS 적용</code> 의 결과만 분석합니다.</p>
</blockquote>
<h3 id="41-평가지표별-결과-분석-및-문제-해결-방안">4.1 평가지표별 결과 분석 및 문제 해결 방안</h3>
<h4 id="411-faithfullness">4.1.1 Faithfullness</h4>
<ul>
<li><p>주어진 context에 대한 생성된 답변의 일관성에 대한 값 (context - answer)</p>
</li>
<li><p>Faithfullness == 1 인 경우</p>
<ul>
<li>LLM이 context 를 잘 참고하여 정확한 답변 생성<ul>
<li><code>question</code> : 보험금의 지급사유</li>
<li><code>context</code> : ...제3조(보험금의 지급사유 )\n회사는 피보험자에게 다음 중 어느 하나의 사유가 발생한 경우에는 보험수익자에게 약정한 보\n험금을 지급합니다 .\n 1. 보험기간 중에 상해의 직접결과로써 사망한 경우(질병으로 인한 사망은 제외합니다 ): 사\n망보험금\n 2. 보험기간 중 상해로 장해분류표 (&lt;별표1&gt; 참조)에서 정한 각 장해지급률에 해당하는 장해\n상태가 되었을 때: 후유장해보험금 (장해분류표에서 정한 지급률을 보험가입금액에 곱하\n여 산출한 금액...</li>
<li><code>answer</code> : 회사는 피보험자에게 다음 중 어느 하나의 사유가 발생한 경우에는 보험수익자에게 약정한 보험금을 지급합니다. 1. 보험기간 중에 상해의 직접결과로써 사망한 경우(질병으로 인한 사망은 제외합니다 ): 사망보험금 2. 보험기간 중 상해로 장해분류표 (&lt;별표1&gt; 참조)에서 정한 각 장해지급률에 해당하는 장해상태가 되었을 때: 후유장해보험금 (장해분류표에서 정한 지급률을 보험가입금액에 곱하여 산출한 금액)</li>
<li><code>ground truth</code> : 회사는 피보험자에게 다음 중 어느 하나의 사유가 발생한 경우에는 보험수익자에게 약정한 보험금을 지급합니다. 1. 보험기간 중에 상해의 직접결과로써 사망한 경우(질병으로 인한 사망은 제외합니다): 사망보험금 2. 보험기간 중 상해로 장해분류표(&lt;별표1&gt; 참조)에서 정한 각 장해지급률에 해당하는 장해상태가 되었을 때: 후유장해보험금(장해분류표에서 정한 지급률을 보험가입금액에 곱하여 산출한 금액)</li>
</ul>
</li>
</ul>
</li>
<li><p>Faithfullness == 0 인 경우</p>
<ul>
<li>LLM이 context 로부터 답변을 생성하지 못함<ul>
<li><code>question</code> : 보험료 할증이란 무엇인가요?</li>
<li><code>context</code> : ...(context 내에 ground truth 와 연관된 키워드조차 없음)...</li>
<li><code>answer</code> : 해당정보 존재하지 않음</li>
<li><code>ground truth</code> : 일반적인 경우보다 위험이 높은 피보험자가 가입하기 위한 방법의 하나로, 보험가입 후 기간이 경과함에 따라 위험의 크기 및 정도가 점차 증가하는 위험 또는 기간의 경과에 상관없이 일정한 상태를 유지하는 위험에 적용하는 방법으로 위험 정도에 따라 해당 보험료 이외에 특별보험료를 부가하는 방법입니다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="412-context-recall">4.1.2 Context Recall</h4>
<ul>
<li><p>ground_truth의 문장 중 contex로부터 추론할 수 있는 문장의 비율을 측정한 값 (ground truth - context)</p>
<ul>
<li>검색된 context가 ground truth와 얼마나 잘 일치하는지 평가.</li>
<li>점수 범위: 0~1 (1에 가까울수록 좋음)</li>
<li>ground truth의 각 claim이 검색된 context와 얼마나 일치하는지 계산함.</li>
</ul>
</li>
<li><p>Context Recall == 1 인 경우</p>
<ul>
<li>검색된 context 가 ground truth 와 일치</li>
<li><code>Faithfullness == 1 인 경우</code> 와 동일.</li>
</ul>
</li>
<li><p>Context Recall == 0 인 경우</p>
<ul>
<li>검색된 context 가 ground truth 와 맞지 않음<ul>
<li><code>question</code> : 보험가액이란 무엇인가요?</li>
<li><code>context</code> : ...(보험가액의 설명과 상관없는 내용)...</li>
<li><code>answer</code> : 해당정보 존재하지 않음</li>
<li><code>ground truth</code> : 재산보험에 있어 피보험 이익을 금전으로 평가한 금액으로 보험의 목적에 발생할 수 있는 최대 손해액을 말합니다.(회사가 실제 지급하는 보험금은 보험가액을 초과할 수 없습니다)</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="413-context-precision">4.1.3 Context Precision</h4>
<ul>
<li><p><code>Context Precision</code> : contexts에 존재하는 ground_truth와 관련된 항목을 높은 순위로 잘 검색해 왔는지의 여부를 평가하는 지표 (context - question - ground truth)</p>
<ul>
<li>검색된 각 k개의 context가 question, ground truth와 연관이 있는지 평가.</li>
<li>점수 범위: 0~1 (1에 가까울수록 좋음)</li>
<li>검색 결과와 유효성을 기반으로 precision을 계산함.</li>
</ul>
</li>
<li><p>Context Precision == 1 인 경우</p>
<ul>
<li>LLM이 question, ground truth 와 연관 있는 context 를 잘 검색해 옴</li>
<li><code>Faithfullness == 1 인 경우</code> 와 동일.</li>
</ul>
</li>
<li><p>Context Precision == 0 에 가까운 경우</p>
<ul>
<li>LLM이 question, ground truth 와 연관 있는 context 를 잘 검색해 오지 못함</li>
<li><code>Context Recall == 0 인 경우</code> 와 동일.</li>
</ul>
</li>
</ul>
<h4 id="414-answer-relevance">4.1.4 Answer Relevance</h4>
<ul>
<li><p>생성된 답변이 주어진 질문과 얼마나 관련성이 있는지에 대한 값 (answer - question)</p>
<ul>
<li>생성된 답변이 질문에 얼마나 잘 부합하는지 평가</li>
<li>점수 범위: -1~1 (1에 가까울수록 좋음)</li>
<li>생성된 답변을 기반으로 생성한 질문과 원래 질문의 cosine 유사도를 활용함.</li>
<li>answer과 context만으로 question을 재구성할 수 있다는 개념을 활용한 방식.</li>
</ul>
</li>
<li><p>Answer Relevance == 0.81</p>
<ul>
<li>생성된 답변이 질문과 유사함</li>
<li><code>Faithfullness == 1 인 경우</code> 와 동일.</li>
</ul>
</li>
<li><p>Answer Relevance == 0 인 경우</p>
<ul>
<li>생성된 답변이 질문과 유사하지 않음 (혹은 답변을 아예 생성하지 못함)</li>
<li><code>Faithfullness == 1 인 경우</code> 와 동일.</li>
</ul>
</li>
</ul>
<h4 id="415-평가지표를-이용한-문제-분석-및-해결방안">4.1.5 평가지표를 이용한 문제 분석 및 해결방안</h4>
<ul>
<li>문제 : <code>Context Precision</code>, <code>Context Recall</code> 점수가 낮은 경우</li>
<li>분석 : 적합한 Context 를 검색하지 못함. 또는 불필요한 context 를 너무 많이 가져옴</li>
<li>해결 : Retreiver 를 개선하기 위해 옵션 조절 (top k 개 축소, threshold 상향), 프롬프트 개선</li>
</ul>
<h3 id="42-기타-문제">4.2 기타 문제</h3>
<h4 id="421-vectordb에-데이터가-깨져서-저장된-경우">4.2.1 VectorDB에 데이터가 깨져서 저장된 경우</h4>
<ul>
<li><p>문제 : 표 형식의 텍스트가 VectorDB에 저장될 때 검색에 적합한 형태로 저장되지 않아 context 검색 불가</p>
<ul>
<li>원문
<img src="https://velog.velcdn.com/images/judy_choi/post/7d8ac3c0-305e-4841-9c33-ec17d14e5320/image.png" alt=""></li>
<li>Vector DB 에 저장된 값
<img src="https://velog.velcdn.com/images/judy_choi/post/763b780b-cbb4-4eb9-9a33-b84d8432977e/image.png" alt=""></li>
</ul>
</li>
<li><p>개선방안 : PDF Loader 를 Fitz 등 테이블을 인식할 수 있는 라이브러리로 교체하여 가급적 Markdown 형태로 변환하여 VectorDB 에 문서를 적재한다.</p>
</li>
</ul>
<h4 id="422-retriever-가-적합한-context-를-검색하지-못하는-경우">4.2.2 Retriever 가 적합한 context 를 검색하지 못하는 경우</h4>
<ul>
<li><p>문제 : Retriever 가 질문에 맞는 context 를 검색하지 못하거나 불필요한 context 를 너무 많이 가져옴 (ex : <code>Faithfullness == 0 인 경우</code>)</p>
</li>
<li><p>개선방안 : Retreiver 를 개선하기 위해 옵션 조절 (top k 개 축소, threshold 상향), 프롬프트 개선</p>
</li>
</ul>
<h4 id="423-질문이-모호한-경우">4.2.3 질문이 모호한 경우</h4>
<ul>
<li><p>문제 : LLM이 질문의 정확한 의도를 파악하지 못하여 적합하지 않은 context 를 가져옴</p>
<ul>
<li><code>question</code> : KB개인상해보험에 가입한 후에 재해로 인해 골절이 되었는데 보험금 청구가 가능한가요?</li>
<li><code>context</code> : [&#39;KB개인상해보험&#39;, &#39;제31조(회사의 파산선고와 해지) ·························································································  ··················································································································· 65\n골절발생위로금 (I) 특별약관 ··················································································································· 67\n골절발생위로금 (II) 특별약관 ················································································································· 68\n골절수술위로금 특별약관 ...]</li>
<li>&#39;골절&#39; 키워드가 포함된 목차를 context 로 가져옴.</li>
</ul>
</li>
<li><p>개선방안 : </p>
<ul>
<li>Retreiver 검색옵션을 바꿔 본다</li>
<li>만약 Ensemble Retriever 를 사용하는데 너무 키워드에 국한되어 검색될 경우, weights 변수를 조정해서 키워드 검색 비율을 줄여 본다. (ex: 키워드 : 유사도 검색 비율 [0.5, 0.5] -&gt; [0.3, 0.7]</li>
<li>질문을 수정한다 (명확한 질문으로 변경하거나 대체 용어 추가)</li>
</ul>
</li>
</ul>
<h2 id="5-결론">5. 결론</h2>
<ul>
<li>RAGAS 를 이용한 평가 자동화 코드 소개 및 지표 설명</li>
<li>4개 지표별 결과 분석 팁<ul>
<li><code>Faithfullness</code>, <code>Answer Relevancy</code> 값이 낮다 : 답변이 생성되지 않았거나 이상함</li>
<li><code>Context Precision</code>, <code>Context Recall</code> 값이 낮다 : Context 를 제대로 못 가져온다 -&gt; Retriever 를 조지거나 Prompt 를 변경해 본다</li>
<li><code>Answer Relevancy</code> 만 높고 나머지 지표 값은 낮다 : context 에 정확한 답(ground truth) 가 없는데 answer 는 생성되는 경우임.</li>
</ul>
</li>
<li>기타 문제 사례 분석 및 개선방안 제안</li>
</ul>
<h2 id="6-future-work">6. Future Work</h2>
<p>문서 안에 검색할 내용이 포함되어 있고, VectorDB에 깨지지 않고 잘 적재되었는데
이상하게 RAG 답변이 부정확하게 나오거나 아예 생성되지 않는 경우가 많다. 🤔</p>
<p>튜토리얼에 사용한 embedding model 의 성능이 강력하게 의심되는데..
현재 회사에서는 보험 용어로 따로 학습한 embedding model 을 쓰고 있어서인지 검색 성능이 준수하다.</p>
<p>기회가 되면 embedding 모델별 품질 평가도 해보는 것으로...!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[회고] 2024년 회고]]></title>
            <link>https://velog.io/@judy_choi/%ED%9A%8C%EA%B3%A0-2024%EB%85%84-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@judy_choi/%ED%9A%8C%EA%B3%A0-2024%EB%85%84-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Fri, 03 Jan 2025 18:47:21 GMT</pubDate>
            <description><![CDATA[<p>2024년에는 LLM 에 입문했고, 새로운 프로젝트들을 해 보았고,
운 좋게 대기업에서 지원하는 스터디에 3개나 합격해서 완주했고,
글또 활동하는 동안 패스 한 번 쓴 적 없이, 지연 한 번 없이 글을 제출하고 있고
회고에 쓸 거리가 생각보다 많을 만큼 돌이켜 보니 많은 것들을 했네요.
지난 1년이 언제 이렇게 빠르게 지나갔나 싶지만
회고 글감을 정리하다 보니 1년은 생각보다 긴 시간이었던 것 같습니다.</p>
<h2 id="사내-프로젝트">사내 프로젝트</h2>
<ul>
<li><p>음성 명령 봇 개발</p>
<ul>
<li>챗봇을 차용한 음성 명령 봇 개발 프로젝트에 참여 (CTO 직속 프로젝트)</li>
<li>처음으로 내가 속한 팀 이외에 개발팀, 기획팀과 협업한 경험!</li>
</ul>
</li>
<li><p>메디컬 키워드 기반 논문 추천 및 시각화 서비스 고도화</p>
<ul>
<li>메디컬 논문 검색 및 시각화 서비스에 LLM 도입 검토</li>
<li>처음으로 회사에서 LLM을 사용해 보았다!</li>
<li>프롬프트 작성부터 파인튜닝, 서빙까지 LLM도입에 필요한 전 과정을 경험했다!
<img src="https://velog.velcdn.com/images/judy_choi/post/7d7b6cd4-cce0-4db1-a894-662c2eee10fb/image.png" alt=""></li>
</ul>
</li>
<li><p>(ing) 보험사 RAG 챗봇 개발</p>
<ul>
<li>시리즈 : <a href="https://velog.io/@judy_choi/series/%EA%B8%80%EB%98%90-RAG-%EC%8B%9C%EB%A6%AC%EC%A6%88">[글또] RAG 시리즈</a></li>
<li>얻은 점<ul>
<li>처음으로 회사에서, 실무에서 RAG 서비스 유지/보수를 해 보았다!</li>
<li>스터디에서 RAG 를 배웠는데, 실무에서 직접 완성된 코드를 보니 이해가 더 잘 됨</li>
<li>튜토리얼로만 공부하다가 현업으로 넘어오니 오히려 더 편리한 부분도 있음 (ex : 튜토리얼은 느리고 DB조회 시 가독성이 좋지 않은 Chroma DB를 이용했는데, 현업에서 사용하는 Postgre + pgvector 는 더 빠르고 DBeaver 를 사용하니 가독성도 좋고 편리하였음)</li>
<li>오랜만에 백엔드 개발자로서 일하고 있는데, 개발팀원으로서 일하다 보니 형상관리를 위해 git 명령어를 복습하고 협업 경험을 복기하고 있음</li>
</ul>
</li>
<li>아쉬운 점<ul>
<li>실제 서비스 오픈 전이라 유저의 반응을 보거나 많은 유저를 감당할 수 있는 서비스 구축 등의 경험을 하지 못하고 있음</li>
<li>LLM 은 유료 API를 이용하고 있어서 LLM 구축 / 서빙 등의 추가 경험을 할 수 없음</li>
<li>테스트 또한 AI관점에서 접근하는 것이 아니라 인력을 투입하고 자동화(RPA) 를 이용하기 때문에 테스트 및 평가 경험을 할 수 없음</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="외부-활동">외부 활동</h2>
<h3 id="스터디">스터디</h3>
<ul>
<li><p>SK Devocean : LLMOps</p>
<ul>
<li>시리즈 : <a href="https://velog.io/@judy_choi/series/OpenLLM%EC%8A%A4%ED%84%B0%EB%94%94">OpenLLM스터디</a>
(시리즈 이름은 OpenLLM 인데... 실제 포스팅은 LLMOps 스터디에서 배운 게 많음)</li>
<li>RAGAS, AutoRAG 에 대해 지식을 공유받았다!</li>
<li>GPU 지원을 받아 LLama3 파인튜닝을 하고 결과를 발표했다!</li>
<li>멘토님께서 기술적으로 생각해 볼 점들에 대해 굉장히 질문을 적절하게 해 주셔서 한 걸음씩 더 성장할 수 있었음.</li>
<li>스터디가 끝나고 Devocean OpenLab Tech Day 에서 멘토님께서 따로 스터디 팀원들을 모아 밥도 사주시고 격려해 주셔서 정말 너무나 감사했다. ㅠㅠ💗</li>
</ul>
</li>
<li><p>SK Devocean : OpenLLM</p>
<ul>
<li>스터디 때마다 정해진 주제에 맞춰 모두가 각자 코드를 짜 왔기 때문에 비교해 보고 참고할 코드가 많아서 좋았다!</li>
<li>멘토님께서 처음부터 끝까지 팀원들을 존중해 주시면서도 주도적으로 리딩해 주시고, 매번 간식도 다른 종류로 사주실 만큼 잘 챙겨주셨다 💗</li>
</ul>
</li>
<li><p>우아한 스터디 : 2024 여름시즌</p>
<ul>
<li>시리즈 : <a href="https://velog.io/@judy_choi/series/%EC%9A%B0%EC%95%84%ED%95%9C-%EC%8A%A4%ED%84%B0%EB%94%94-2024-%EC%97%AC%EB%A6%84%EC%8B%9C%EC%A6%8C">[우아한 스터디] 2024 여름시즌</a></li>
<li>총 10명의 스터디원이 프론트엔드, 백엔드, 데이터사이언스 크게 3분야로 나누어 각자 공부해 온 내용을 기술블로그에 작성한 후, 온라인 미팅에서 설명하는 방식으로 진행</li>
<li>나는 주로 SK Devocean 에서 배워 온 LLM 및 RAG 내용을 정리해서 발표했다 (일명 스터디 돌려막기 🤭)</li>
</ul>
</li>
</ul>
<h3 id="글또">글또</h3>
<p>지난 8기에서 시리즈로 글을 연재했던 것이 글감을 정하기에도 좋고, 하나의 주제에 대해 넓게 공부하고 정리할 수 있어서 가급적 시리즈로 글을 작성하고 있습니다.
현업에서 얻은 지식과 경험이야말로 저의 강점이자 최고의 글감이라고 생각하여 최대한 포스팅하고 있습니다.</p>
<ul>
<li>9기<ul>
<li><a href="https://velog.io/@judy_choi/series/%EA%B8%80%EB%98%90-%EC%9D%8C%EC%84%B1%EC%9D%B8%EC%8B%9D-%EC%8B%9C%EB%A6%AC%EC%A6%88">[글또] 음성인식 시리즈</a></li>
<li><a href="https://velog.io/@judy_choi/series/%EA%B8%80%EB%98%90-Udemy-%EA%B0%95%EC%9D%98">[글또] Udemy 강의</a></li>
</ul>
</li>
<li>(ing) 10기<ul>
<li>(ing) 현업에서 보험사 RAG 챗봇 개발 중에 얻은 팁에 대해 주로 포스팅하고 있습니다.</li>
<li><a href="https://velog.io/@judy_choi/series/%EA%B8%80%EB%98%90-RAG-%EC%8B%9C%EB%A6%AC%EC%A6%88">[글또] RAG 시리즈</a></li>
</ul>
</li>
</ul>
<h3 id="발표">발표</h3>
<ul>
<li>글또 9기 데이터 분석 반상회 발표 : 음성 명령봇 서비스 구축기<ul>
<li>회사에서 수행한 음성 명령봇 서비스 프로젝트를 하게 된 배경, 개발한 서비스의 각 버전별 특징, 프로젝트 진행 과정에서 발생한 이슈와 해결 사례 공유
<img src="https://velog.velcdn.com/images/judy_choi/post/ec5648f1-ebf4-440a-8926-101387908353/image.png" alt=""></li>
</ul>
</li>
</ul>
<h2 id="개인-공부">개인 공부</h2>
<h3 id="강의-수강">강의 수강</h3>
<ul>
<li><p>Udemy(유데미) &lt;실전 AI 서비스 기획&gt;</p>
<ul>
<li>글또 9기 활동의 일환으로 유데미 강의 쿠폰을 제공받음</li>
<li>음성 명령 봇 개발 과정에서 기획의 필요성을 느껴 수강<blockquote>
<p>진짜 실전 AI 서비스를 기획할 때, 개발할 때 모두 필요한 내용만을 쏙쏙 추려서 이해하기 쉽게 시각화된 강의자료 속에 다양한 예시와 함께 담아냈습니다.
&lt;실전 AI 서비스 기획&gt; 강의는 Upstage AI 의 이활석 CTO님 직강으로, 실전 AI서비스를 만드신 CTO님의 모든 경험이 강의에 아낌없이 녹아 있습니다.</p>
</blockquote>
</li>
<li>강의 정리 : <a href="https://velog.io/@judy_choi/series/%EA%B8%80%EB%98%90-Udemy-%EA%B0%95%EC%9D%98">[글또] Udemy 강의</a></li>
</ul>
</li>
<li><p>인프런 &lt;모두를 위한 대규모 언어 모델 LLM(Large Language Model) Part 1 - Llama 2 Fine-Tuning 해보기&gt;</p>
<ul>
<li>회사에서 LLM을 도입한다기에 결재 올려서 내 돈은 반만 내고 수강한 인강.</li>
<li>PEFT 와 LLama3 파인튜닝 강의 위주로 수강 (필요한 것만)</li>
<li>가끔 강의 리스트 볼 때마다 새 강의가 추가되고 있다..!</li>
</ul>
</li>
<li><p>K-Mooc &lt;언어학과 인공지능&gt;</p>
<ul>
<li>음성 인식 회사에 다녔으니 음성 인식 강의를 들어서 알고 있던 지식은 다시 정리하고, 좀 제대로(?) 배우고 싶었음.</li>
<li>나는 언어에 흥미가 있어 NLP 를 하려고 AI를 시작했기 때문에 언어학도 꼭 한번 찍먹(?!)해보고 싶었음.<ul>
<li>찍먹 결과 언어학은 나에겐 구미가 당기지 않는다는 사실을 깨달음.</li>
<li>내 적성 내가 잘하는 것 그리고 내가 진정으로 흥미로워하는 건 뭘까.. 에 대해 사실 아직까지도 고민하고 있음 🤔</li>
</ul>
</li>
<li>과제 제출 : <a href="https://velog.io/@judy_choi/%EA%B8%80%EB%98%90-%EC%96%B8%EC%96%B4%ED%95%99%EA%B3%BC-%EC%9D%B8%EA%B3%B5%EC%A7%80%EB%8A%A5-%EC%9D%8C%EC%84%B1-%EA%B0%9C%EC%9D%B8%EB%B9%84%EC%84%9C%EC%9D%98-%EC%9E%A5%EB%8B%A8%EC%A0%90%EA%B3%BC-%ED%96%A5%ED%9B%84-%EB%B0%9C%EC%A0%84-%EB%B0%A9%ED%96%A5">[글또] 언어학과 인공지능 : 음성 개인비서의 장단점과 향후 발전 방향</a></li>
</ul>
</li>
</ul>
<h2 id="사생활">사생활</h2>
<h3 id="여행">여행</h3>
<ul>
<li>일본 여행<ul>
<li>4월 말에 1주일 정도 도쿄에 다녀옴</li>
<li>2016년부터 팬이었던 밴드의 공연을 드디어 볼 수 있어서 무지 행복했다 🤩</li>
<li>도쿄에 사는 친구들도 다 만나고 와서 얼마나 반갑던지 🥹</li>
<li>디즈니랜드도 가고 뮤지컬도 봤다. 오페라의 유령, 미녀와 야수, 겨울왕국!!</li>
</ul>
</li>
<li>태국 여행<ul>
<li>원래 6~8월 사이에 매년 해 오던 NIA WFK-ICT 봉사단 활동을 할 뻔했으나... 인턴 합격하여 올해는 봉사팀 합격 후 파견 포기함</li>
<li>대신 예정대로 파견된 나머지 봉사팀 친구들을 만나러 방콕에 놀러 다녀옴</li>
</ul>
</li>
</ul>
<h3 id="연애">연애</h3>
<p>연애 시작부터 결혼 전제로 교제했던 남자친구와 2024년 내내 너무 자주 싸웠고 헤어졌다 만나길 반복했습니다.
헤어진 기간까지 포함해서 지금까지 1년 조금 넘게 사귀었는데...
이제는 이 관계의 끝이 보인다. 💔
지금 이 회고 글을 쓰는 동안에도 전화로 카톡으로 싸웠다.
우린 지친 걸 넘어 어쩌면 싸움에 익숙해진 걸지도.
이걸 기술 블로그에 맘놓고 쓸 수 있는 건, 내가 글또 글 마감 시기마다 남자친구에게 글 쓴다고 말하는데도 단 한번도 내가 쓴 글을 읽어보기는커녕 궁금해하지조차 않기 때문이다.
그는 내가 중요하게 생각하는 나의 커리어와 성장에 관심이 없나 보다.
혹은 나를 존중하지 않거나.</p>
<h2 id="마무리">마무리</h2>
<blockquote>
<p>과거가 현재를 도울 수 있는가?</p>
</blockquote>
<p>한강 작가는 소설 &lt;소년이 온다&gt;를 집필하는 동안 과거가 현재를 돕고, 죽은 자들이 산 자를 구하고 있다고 느꼈습니다.
네, 나의 과거는 나의 현재를 도왔습니다.
LLM 및 RAG 스터디는 따라가는 것조차 버거웠지만, 솔직하게 따라기기 어려워서 창피했지만... 
과제 못 해와서 부끄러워도 완주를 했습니다.
그리고 완주한 스터디 덕분에 나는 현재 RAG 를 하는 회사에 취업했습니다.</p>
<p>솔직히 지금 커리어 고민도 많고, 포기한 석사학위도 많이 아쉽고,
와중에 IT시장은 제가 뛰어든 지 10년이 넘었는데 이만한 불경기를 본 적이 없어요.
다만 과거가 현재를 도왔듯이, 나의 현재가 나의 미래를 도울 수 있기를 바랄 뿐입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RAG 시리즈] PDF 로부터 데이터를 추출하는 라이브러리 비교 (PyPDF, Fitz, PdfPlumber)]]></title>
            <link>https://velog.io/@judy_choi/RAG-%EC%8B%9C%EB%A6%AC%EC%A6%88-pdf</link>
            <guid>https://velog.io/@judy_choi/RAG-%EC%8B%9C%EB%A6%AC%EC%A6%88-pdf</guid>
            <pubDate>Fri, 20 Dec 2024 14:21:46 GMT</pubDate>
            <description><![CDATA[<p>실무에서 RAG 서비스를 개발할 때 RAG 및 LLM 의 성능을 개선하는 것만큼 중요한 점은
서비스에 사용할 문서에서 RAG가 질의에 맞는 내용을 검색하고
검색 결과 데이터를 LLM에 적합한 형태와 크기로 제공하는 것인데요.
다시 말해, 문서 데이터 또는 문서 파일을 Vector DB에 적당한 크기의 텍스트로 분할하여 넣어야 합니다.
문서 파일은 pdf 인 경우가 많고, (ex : 메뉴얼, 보험서류 등)
이 때문에 pdf 문서 파일의 내용이 변형되거나 누락되지 않게 텍스트로 추출할 필요가 있는데요,
이번 포스팅에서는 널리 쓰이는 pdf 변환 오픈소스의 성능을 비교합니다.</p>
<ul>
<li>비교 기준<ul>
<li>텍스트, 표, 이미지 추출 가능 여부 및 품질</li>
</ul>
</li>
</ul>
<p>비교 기준을 이와 같이 정한 이유는, 현업에서 PDF 문서는 텍스트와 표, 이미지로 구성된 경우가 많고
이 3가지를 얼마나 깨지지 않게 &#39;잘&#39; 추출하는지 고려해야 하기 때문인데요,
특히 RAG 성능과 직결되고, 표와 이미지와 같이 LLM이 인식하기 어려운 형태의 경우
표는 LLM 이 처리할 수 있는 텍스트 즉 Markdown 형태로 변환한다던지,
혹은 검색 결과를 사용자에게 제시할 때 출처 혹은 참고자료로서의 이미지를 정확하게 제시해야 하기 때문입니다.</p>
<p>*<em>예제 코드 : *</em> <a href="https://github.com/Judy-Choi/rag_series/blob/main/note_6/parser.ipynb">GitHub</a></p>
<h2 id="sample">Sample</h2>
<p>샘플로 사용한 문서 파일 중 표가 존재하는 페이지(10p) 위주로 비교합니다.
표의 텍스트만을 추출할 경우, 표를 Markdown 으로 추출할 경우를 주로 비교하기 위함이며
이미지의 경우 텍스트로 온전히 추출하기 어렵기 때문에
이미지를 추출하는 코드만 따로 비교합니다.
(Tip : 표를 Markdown 으로 추출하면 텍스트가 되기 때문에 vector DB에 바로 임베딩하여 넣을 수 있지만
표 구조가 복잡하면 (행/열이 중첩되면) 추출이 잘 되지 않고,
또는 표가 이미지 형태인 경우 OCR 을 활용하기도 합니다.)</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/65bf5f8e-d750-4100-9d58-81ce7b957b42/image.png" alt=""></p>
<h2 id="pypdf">PyPDF</h2>
<p>LangChain 튜토리얼에서 가장 흔하게 사용하는 라이브러리입니다.
속도가 다른 라이브러리에 비해 상대적으로 느리고,
표와 이미지를 추출할 수 없다는 단점이 있습니다.</p>
<h4 id="code">Code</h4>
<p>PDF File Load</p>
<pre><code class="language-python">from langchain_community.document_loaders import PyPDFLoader

loader = PyPDFLoader(file_name)
docs = loader.load()

docs</code></pre>
<p>텍스트 추출</p>
<pre><code class="language-python">print(docs[9].page_content)</code></pre>
<pre><code class="language-plain">보험용어 해설
보험용어 용어 해설
보험약관보험계약에 관하여 보험계약자와 보험회사 상호간에 이행하여야 할 권리
와 의무를 규정한 것
보험증권보험계약의 성립과 그 내용을 증명하기 위하여 보험회사가 보험계약자에
게 교부하는 증서
...</code></pre>
<h2 id="fitz-pymupdf">Fitz (PyMuPDF)</h2>
<p>사용 결과 속도가 빠르고, 표 및 이미지 추출을 모두 지원합니다.</p>
<h3 id="code-1">Code</h3>
<p>PDF File Load</p>
<pre><code class="language-python">from langchain_community.document_loaders import PyMuPDFLoader

loader = PyMuPDFLoader(file_name)
docs = loader.load()

docs</code></pre>
<p>텍스트 추출</p>
<pre><code class="language-python">print(docs[9].page_content)

# 또는

import fitz

result_text = []

# fitz.open 을 사용할 수도 있고
docs = fitz.open(file_name)

# Get only 10p
print(docs[9].get_text())</code></pre>
<pre><code class="language-plain">보험용어 해설
보험용어
용어 해설
보험약관
보험계약에 관하여 보험계약자와 보험회사 상호간에 이행하여야 할 권리
와 의무를 규정한 것
...</code></pre>
<p>표 추출 + Markdown 변환</p>
<pre><code class="language-python"># import package PyMuPDF
import fitz
import pandas as pd

# Open some document, for example a PDF (could also be EPUB, XPS, etc.)
docs = fitz.open(file_name)

# Load 10p &amp; Look for tables on this page and display the table count
tabs = docs[9].find_tables()

# Select the first table
table = tabs[0]

# 표가 추출되었는지 확인
if table:
    # pandas DataFrame으로 변환
    df = table.to_pandas()
    # DataFrame을 Markdown 형식으로 변환
    markdown_table = df.to_markdown(index=False)  # 인덱스를 제외하려면 index=False

    print(markdown_table)

# 명시적으로 파일 닫기
docs.close()</code></pre>
<pre><code class="language-plain">| 보험용어     | 용어 해설                                                            |
|:-------------|:---------------------------------------------------------------------|
| 보험약관     | 보험계약에 관하여 보험계약자와 보험회사 상호간에 이행하여야 할 권리  |
|              | 와 의무를 규정한 것                                                  |
| 보험증권     | 보험계약의 성립과 그 내용을 증명하기 위하여 보험회사가 보험계약자에  |
|              | 게 교부하는 증서                                                     |
...</code></pre>
<p>이미지 추출</p>
<pre><code class="language-python">import fitz  # PyMuPDF
import io
import os
from PIL import Image

# &#39;fitz_image&#39; 폴더가 없으면 새로 생성합니다.
output_folder = &quot;fitz_image&quot;
if not os.path.exists(output_folder):
    os.makedirs(output_folder)


# Open the file
pdf_file = fitz.open(file_name)

# Iterate over PDF pages
for page_index in range(len(pdf_file)):
    # Get the page itself
    page = pdf_file[page_index]

    # Get the image list for the page
    image_list = page.get_images(full=True)

    # Printing the number of images found on this page
    if image_list:
        print(f&quot;[+] Found a total of {len(image_list)} images in page {page_index + 1}&quot;)
    # else:
    #     print(&quot;[!] No images found on page&quot;, page_index + 1)

    # Extract images from the page
    for image_index, img in enumerate(image_list, start=1):
        xref = img[0]
        base_image = pdf_file.extract_image(xref)
        image_bytes = base_image[&quot;image&quot;]
        image_ext = base_image[&quot;ext&quot;]

        # Save the image to a file
        image_filename = f&quot;{output_folder}/page_{page_index + 1}_image_{image_index}.{image_ext}&quot;
        with open(image_filename, &quot;wb&quot;) as img_file:
            img_file.write(image_bytes)

# Close the PDF document
pdf_file.close()</code></pre>
<h2 id="pdfplumber">PdfPlumber</h2>
<p>표 및 이미지 추출을 모두 지원하지만, 속도는 Fitz 에 비해 느립니다.
하지만 Fitz 에 비해 다양한 옵션을 제공합니다.</p>
<h3 id="code-2">Code</h3>
<p>PDF File Load</p>
<pre><code class="language-python">from langchain.document_loaders import PDFPlumberLoader

loader = PDFPlumberLoader(file_name)
docs = loader.load()

docs</code></pre>
<p>텍스트 추출</p>
<pre><code class="language-python">print(docs[9].page_content)

# 또는

import pdfplumber

with pdfplumber.open(file_name) as pdf:
    # Get only 10p
    print(pdf.pages[9].extract_text())</code></pre>
<pre><code class="language-plain">보험용어 해설
보험용어 용어 해설
보험계약에 관하여 보험계약자와 보험회사 상호간에 이행하여야 할 권리
보험약관
와 의무를 규정한 것
...</code></pre>
<p>표 추출 + Markdown 변환</p>
<pre><code class="language-python">import pdfplumber
import pandas as pd


# PDF 파일 열기
with pdfplumber.open(file_name) as pdf:
    # Load 10p &amp; Look for tables on this page and display the table count
    table = pdf.pages[9].extract_table()

    # 표가 추출되었는지 확인
    if table:
        # pandas DataFrame으로 변환
        df = pd.DataFrame(table[1:], columns=table[0])  # 첫 번째 행은 컬럼명
        # DataFrame을 Markdown 형식으로 변환
        markdown_table = df.to_markdown(index=False)  # 인덱스를 제외하려면 index=False

        print(markdown_table)</code></pre>
<pre><code class="language-plain">| 보험용어     | 용어 해설                                                            |
|:-------------|:---------------------------------------------------------------------|
| 보험약관     | 보험계약에 관하여 보험계약자와 보험회사 상호간에 이행하여야 할 권리  |
|              | 와 의무를 규정한 것                                                  |
| 보험증권     | 보험계약의 성립과 그 내용을 증명하기 위하여 보험회사가 보험계약자에  |
|              | 게 교부하는 증서                                                     |
...</code></pre>
<p>이미지 추출</p>
<pre><code class="language-python">import pdfplumber
import io
from PIL import Image

def extract_images_from_pdf(file_name, output_folder):

    # PDF 파일 열기
    with pdfplumber.open(file_name) as pdf:
        image_index = 0  # 이미지 인덱스 초기화

        # 각 페이지에서 이미지 추출
        for page_num, page in enumerate(pdf.pages, start=1):
            # 페이지에서 이미지 추출
            images = page.images

            # 페이지에 이미지가 있다면
            for img_index, image in enumerate(images, start=1):
                # 이미지 정보 가져오기
                image_bytes = image[&#39;stream&#39;].get_data()  # 이미지 바이트 데이터

                image_filename = f&quot;{output_folder}/page_{page_num}_img_{img_index}.png&quot;
                # 이미지 파일 저장
                with open(image_filename, &quot;wb&quot;) as img_file:
                    img_file.write(image_bytes)

                print(f&quot;Saved image {image_filename}&quot;)


                # 이미지 바이트를 이미지 객체로 변환할 경우 사용
                # image = Image.open(io.BytesIO(image_bytes))

                # # 이미지 저장
                # image.save(image_filename)
                # print(f&quot;Image saved: {image_filename}&quot;)



# 사용 예시
output_folder = &quot;plumber_images&quot;  # 이미지가 저장될 폴더
if not os.path.exists(output_folder):
    os.makedirs(output_folder)
extract_images_from_pdf(file_name, output_folder)</code></pre>
<h2 id="결과-비교">결과 비교</h2>
<h3 id="텍스트-추출-결과">텍스트 추출 결과</h3>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/9cdbd7d0-1e18-49fc-9c21-7183d3236104/image.png" alt=""></p>
<h3 id="표-추출-결과-markdown">표 추출 결과 (MarkDown)</h3>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/7bb7b5aa-3f91-4600-a268-50b4f2cf6276/image.png" alt=""></p>
<p>이미지 추출 결과는 Fitz, PdfPlumber 모두 동일하므로 생략합니다.</p>
<h3 id="추출-속도">추출 속도</h3>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/34dde5dd-bcef-4c14-a706-7d5180a889a2/image.png" alt=""></p>
<h2 id="참고">참고</h2>
<p><a href="https://velog.io/@alswjd404/%EB%9E%AD%EC%B2%B4%EC%9D%B8%EC%97%90%EC%84%9C-PDF-Load%ED%95%98%EA%B8%B0%EC%8B%A4%ED%8C%A8">https://velog.io/@alswjd404/랭체인에서-PDF-Load하기실패</a>
<a href="https://digitalbourgeois.tistory.com/356">https://digitalbourgeois.tistory.com/356</a>
<a href="https://github.com/microsoft/markitdown">https://github.com/microsoft/markitdown</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RAG 시리즈] Multi-Query 와 Fusion 기법]]></title>
            <link>https://velog.io/@judy_choi/RAG-%EC%8B%9C%EB%A6%AC%EC%A6%88-Multi-Query-%EC%99%80-Fusion-%EA%B8%B0%EB%B2%95</link>
            <guid>https://velog.io/@judy_choi/RAG-%EC%8B%9C%EB%A6%AC%EC%A6%88-Multi-Query-%EC%99%80-Fusion-%EA%B8%B0%EB%B2%95</guid>
            <pubDate>Fri, 22 Nov 2024 04:49:22 GMT</pubDate>
            <description><![CDATA[<p>RAG(Retrieval-Augmented Generation)에서 <code>retrieve</code> 단계에서 사용하는 <code>multi-query</code>와 <code>fusion</code> 기법은 질의의 정확성을 높이고 더 풍부한 정보를 얻기 위해 사용됩니다. 두 기법은 주로 검색 결과의 질을 높이고, 다양한 정보 조합을 통해 생성된 응답의 질을 향상하기 위해 결합하여 사용됩니다.</p>
<p>본 포스팅에서는 2가지 기법에 대해 설명하고, 예제 코드와 실행 결과를 제시합니다.</p>
<p>예제 코드 : <a href="https://github.com/Judy-Choi/rag_series/tree/main/note_5">GitHub - RAG_Series</a></p>
<h2 id="multi-query-pre-retrieval">Multi-Query (Pre-retrieval)</h2>
<p><code>Multi-query</code> 기법은 하나의 질문을 여러 개의 하위 질문 또는 다양한 형태로 변형하여 검색하는 방법입니다. 각 하위 질의는 다른 관점에서 동일한 정보를 요청하도록 설계되어, 검색 시스템이 보다 다양한 답변을 제공하게 합니다.</p>
<ol>
<li><strong>기법 설명</strong><ul>
<li>하나의 질문을 여러 형태로 재구성하여 다양한 방식으로 정보를 요청합니다.</li>
<li>이를 통해 검색 시스템이 더 많은 정보를 제공하도록 유도합니다.</li>
</ul>
</li>
<li><strong>예시</strong><ul>
<li>예를 들어, &quot;코로나 바이러스의 최신 연구 자료는?&quot;이라는 질문을 다음과 같은 하위 질문으로 변형할 수 있습니다:<ul>
<li>&quot;COVID-19와 관련된 최신 연구는?&quot;</li>
<li>&quot;코로나 바이러스에 대한 새로운 논문은?&quot;</li>
<li>&quot;최근 COVID-19에 대한 연구 업데이트는 무엇인가요?&quot;</li>
</ul>
</li>
<li>이렇게 다양한 질문을 통해 검색하면, 다양한 문서와 관련된 응답을 얻을 수 있습니다.</li>
</ul>
</li>
<li><strong>장점</strong><ul>
<li>다양한 형태의 문서를 통해 풍부한 정보를 얻을 수 있어, 모델이 보다 폭넓고 유용한 답변을 생성하게 됩니다.</li>
<li>정보의 중복성과 누락을 줄여주는 효과가 있습니다.</li>
</ul>
</li>
<li><strong>단점</strong><ul>
<li>질문을 여러 형태로 변환하고 검색하는 과정에서 성능(속도)이 다소 저하될 수 있습니다.</li>
<li>다수의 응답을 결합하는 후처리 단계가 추가로 필요합니다.</li>
</ul>
</li>
</ol>
<h3 id="code">Code</h3>
<p><strong>Multi-query 생성 및 문서 검색</strong>
MultiQueryRetriever 클래스를 사용해 주어진 질문과 연관된 추가 쿼리를 여러 개 생성하고,<br>각각의 쿼리를 이용해 문서를 검색합니다.<br>(MultiQueryRetriever 클래스를 사용하지 않고 프롬프트를 이용해 쿼리를 생성할 수도 있습니다.<br>참고 : <a href="https://wikidocs.net/234109">https://wikidocs.net/234109</a>)</p>
<pre><code class="language-python"># 멀티 쿼리 생성
from langchain.retrievers.multi_query import MultiQueryRetriever
from langchain_openai import ChatOpenAI

question = &#39;보험금의 지급사유&#39;

llm = ChatOpenAI(model=&quot;gpt-4-0125-preview&quot;, temperature=0)

# MultiQueryRetriever 설정
retriever_from_llm = MultiQueryRetriever.from_llm(
    retriever=vector_store.as_retriever(), llm=llm
)

# Set logging for the queries
import logging

# 생성되는 multi-query 확인
logging.basicConfig()
logging.getLogger(&#39;langchain.retrievers.multi_query&#39;).setLevel(logging.INFO)

# query 에 대해 Multi-query 문서 검색 (질문과 관련 있는 문서 검색)
relevant_docs = retriever_from_llm.get_relevant_documents(query=question)
relevant_docs</code></pre>
<p>get_relevant_documents 대신 invoke 를 이용할 수도 있습니다.</p>
<pre><code class="language-python">relevant_docs = retriever_from_llm.invoke(question)
relevant_docs</code></pre>
<p>output 은 2개 메서드 모두 동일합니다.</p>
<pre><code class="language-bash">INFO:langchain.retrievers.multi_query:Generated queries: [&#39;1. 보험금 지급을 결정하는 기준은 무엇인가요?&#39;, &#39;2. 어떤 상황에서 보험금을 받을 수 있나요?&#39;, &#39;3. 보험금 지급 조건에는 어떤 것들이 포함되나요?&#39;]

[Document(id=&#39;1d7f192c-4261-4fee-9563-e05dbbcb178e&#39;, metadata={&#39;page&#39;: 14, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 4 - 4. 기타 보험수익자가 보험금의 수령에 필요하여 제출하는 서류(사망보험금 지급시 피보험\n자의 법정상속인이 아닌자가 청구하는 경우 법정상속인의 확인서 )\n② 제1항 제2호의 사고증명서는 의료법 제3조(의료기관 )에서 규정한 국내의 병원이나 의원 또\n는 국외의 의료관련법에서 정한 의료기관에서 발급한 것이어야 합니다 .\n제8조(보험금의 지급절차 ) \n① 회사는 제7조(보험금의 청구)에서 정한 서류를 접수한 때에는 접수증을 드리고 휴대전화 문\n자메시지 또는 전자우편 등으로도 송부하며 , 그 서류를 접수한 날부터 3영업일 이내에 보\n험금을 지급합니다 .\n② 회사가 보험금 지급사유를 조사․확인하기 위해 필요한 기간이 제1항의 지급기일을 초과할 \n것이 명백히 예상되는 경우에는 그 구체적인 사유와 지급예정일 및 보험금 가지급제도 (회사\n가 추정하는 보험금의 50% 이내를 지급)에 대하여 피보험자 또는 보험수익자에게 즉시 통\n지합니다 . 다만, 지급예정일은 다음 각 호의 어느 하나에 해당하는 경우를 제외하고는 제7\n조(보험금의 청구)에서 정한 서류를 접수한 날부터 30영업일 이내에서 정합니다 .\n 1. 소송제기\n 2. 분쟁조정 신청\n【분쟁조정 신청】\n분쟁조정 신청은 이 약관의 「분쟁의 조정」조항에 따르며 분쟁조정 신청 대상기관은 금융\n감독원의 금융분쟁조정위원회를 말합니다 .\n 3. 수사기관의 조사 \n 4. 해외에서 발생한 보험사고에 대한 조사\n 5. 제6항에 따른 회사의 조사요청에 대한 동의 거부 등 계약자 , 피보험자 또는 보험수익자\n의 책임있는 사유로 보험금 지급사유의 조사와 확인이 지연되는 경우\n 6. 제4조(보험금 지급에 관한 세부규정 ) 제6항에 따라 보험금 지급사유에 대해 제3자의 의\n견에 따르기로 한 경우\n③ 제2항에 의하여 장해지급률의 판정 및 지급할 보험금의 결정과 관련하여 확정된 장해지급\n률에 따른 보험금을 초과한 부분에 대한 분쟁으로 보험금 지급이 늦어지는 경우에는 보험\n수익자의 청구에 따라 이미 확정된 보험금을 먼저 가지급합니다 .\n④ 제2항에 의하여 추가적인 조사가 이루어지는 경우, 회사는 보험수익자의 청구에 따라 회사\n가 추정하는 보험금의 50% 상당액을 가지급보험금으로 지급합니다 .\n【가지급보험금 】\n보험금이 지급기한 내에 지급되지 못할 것으로 판단되는 경우 회사가 예상되는 보험금의 \n일부를 먼저 지급하는 제도로 피보험자가 필요로 하는 비용을 보전해 주기 위해 회사가 먼\n저 지급하는 임시 교부금을 말합니다 .\n⑤ 회사는 제1항에서 정한 지급기일내에 보험금을 지급하지 않았을 때(제2항에서 정한 지급예\n정일을 통지한 경우를 포함합니다 )에는 그 다음날부터 지급일까지의 기간에 대하여 &lt;부표&gt; \n‘보험금을 지급할 때의 적립이율 ’에 따라 연단위 복리로 계산한 금액을 보험금에 더하여 지\n급합니다 . 다만, 계약자 , 피보험자 또는 보험수익자에게 책임이 있는 사유로 지급이 지연된 \n경우에는 그 기간에 대한 이자는 지급하지 않습니다 .\n 기     간 지 급 이 자\n지급기일의 다음 날부터 30일 이내 기간 보험계약대출이율\n지급기일의 31일이후부터 60일이내 기간 보험계약대출이율 + 가산이율 (4.0%)\n지급기일의 61일이후부터 90일이내 기간 보험계약대출이율 + 가산이율 (6.0%)\n지급기일의 91일이후 기간 보험계약대출이율 + 가산이율 (8.0%)&lt;부표&gt; 보험금을 지급할 때의 적립이율&#39;),
 Document(id=&#39;87b9bd3c-53f0-4a99-b172-6361570bd70b&#39;, metadata={&#39;page&#39;: 148, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 138 -내에서 계약자 또는 피보험자가 제3자에 대하여 가지는 손해배상청구권을 취득합니다 . 다\n만, 회사가 보상한 금액이 피보험자가 입은 손해의 일부인 경우에는 피보험자의 권리를 침\n해하지 않는 범위 내에서 그 권리를 취득합니다 .\n② 계약자 또는 피보험자는 제1항에 의하여 회사가 취득한 권리의 행사 및 보전에 관하여 필\n요한 조치를 하여야 하며, 또한 회사가 요구하는 증거 및 서류를 제출하여야 합니다 . 이에 \n필요한 비용은 회사가 드립니다 .\n③ 제1항 및 제2항에도 불구하고 타인을 위한 보험계약의 경우에는 회사는 계약자에 대한 대\n위권을 포기합니다 .\n④ 회사는 제1항에 따른 권리가 계약자 또는 피보험자와 생계를 같이 하는 가족에 대한 것인 \n경우에는 그 권리를 취득하지 못합니다 . 다만, 손해가 그 가족의 고의로 인하여 발생한 경\n우에는 그 권리를 취득합니다 .\n제10조(잔존물 )\n회사가 제1조(보상하는 손해)의 보험금을 지급하고 잔존물을 취득할 의사표시를 하는 경우에는 \n그 잔존물은 회사의 소유가 됩니다 .\n제11조(손해의 통지 및 조사)\n① 보험의 목적에 손해가 생긴 경우 계약자 또는 피보험자는 지체없이 이를 회사에 알려야 합\n니다.\n② 계약자 또는 피보험자가 제1항의 통지를 게을리하여 손해가 증가된 때에는 회사는 그 증가\n된 손해는 보상하여 드리지 않습니다 . \n③ 회사가 제1항에 대한 손해의 사실을 확인하기 어려운 경우에는 계약자 또는 피보험자에게 \n필요한 증거자료의 제출을 요청할 수 있습니다 .\n④ 회사는 제1항의 통지를 받은 때에는 사고가 생긴 건물 또는 그 구내와 거기에 들어있는 피\n보험자의 소유물을 조사할 수 있습니다 .\n제12조(보험금의 지급절차 )\n① 회사는 보통약관 제7조(보험금의 청구)에서 정한 서류를 접수한 때에는 접수증을 교부하고 , \n그 서류를 접수받은 후 지체없이 지급할 보험금을 결정하고 지급할 보험금이 결정되면 7일 \n이내에 이를 지급하여 드립니다 . 또한, 지급할 보험금이 결정되기 전이라도 피보험자의 청\n구가 있을 때에는 회사가 추정한 보험금의 50% 상당액을 가지급보험금으로 지급합니다 .\n② 회사는 제1항의 지급보험금이 결정된 후 7일(이하 ‘지급기일 ’이라 합니다 )이 지나도록 보험\n금을 지급하지 않았을 때에는 지급기일의 다음날부터 지급일까지의 기간에 대하여 &lt;부표&gt; \n‘보험금을 지급할 때의 적립이율 ’에 따라 연단위 복리로 계산한 금액을 보험금에 더하여 지\n급합니다 . 그러나 피보험자의 책임있는 사유로 지체된 경우에는 그 해당기간에 대한 이자를 \n더하여 지급하지 않습니다 .\n제13조(보험의 목적에 대한 조사)\n회사는 보험목적에 대한 위험상태를 조사하기 위하여 보험기간 중 언제든지 보험의 목적 또는 \n이들이 들어 있는 건물이나 구내를 조사할 수 있습니다 . 기     간 지 급 이 자\n지급기일의 다음 날부터 30일 이내 기간 보험계약대출이율\n지급기일의 31일이후부터 60일이내 기간 보험계약대출이율 + 가산이율 (4.0%)\n지급기일의 61일이후부터 90일이내 기간 보험계약대출이율 + 가산이율 (6.0%)\n지급기일의 91일이후 기간 보험계약대출이율 + 가산이율 (8.0%)\n주) 1. 보험계약대출이율은 보험개발원이 공시하는 보험계약대출이율을 적용합니다 .    &lt;부표&gt; 보험금을 지급할 때의 적립이율&#39;),
 Document(id=&#39;13e178ec-e372-4dd0-ae38-92de6aa4e79c&#39;, metadata={&#39;page&#39;: 12, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 2 - 4. 기간과 날짜 관련 용어\n가. 보험기간 : 계약에 따라 보장을 받는 기간을 말합니다 .\n나. 영업일 : 회사가 영업점에서 정상적으로 영업하는 날을 말하며 , 토요일 , ‘관공서의 공휴\n일에 관한 규정’에 따른 공휴일과 근로자의 날을 제외합니다 .\n제2관 보험금의 지급\n제3조(보험금의 지급사유 )\n회사는 피보험자에게 다음 중 어느 하나의 사유가 발생한 경우에는 보험수익자에게 약정한 보\n험금을 지급합니다 .\n 1. 보험기간 중에 상해의 직접결과로써 사망한 경우(질병으로 인한 사망은 제외합니다 ): 사\n망보험금\n 2. 보험기간 중 상해로 장해분류표 (&lt;별표1&gt; 참조)에서 정한 각 장해지급률에 해당하는 장해\n상태가 되었을 때: 후유장해보험금 (장해분류표에서 정한 지급률을 보험가입금액에 곱하\n여 산출한 금액)\n제4조(보험금 지급에 관한 세부규정 ) \n① 제3조(보험금의 지급사유 ) 제1호 ‘사망’에는 보험기간에 다음 어느 하나의 사유가 발생한 \n경우를 포함합니다 .\n 1. 실종선고를 받은 경우: 법원에서 인정한 실종기간이 끝나는 때에 사망한 것으로 봅니다 .\n 2. 관공서에서 수해, 화재나 그 밖의 재난을 조사하고 사망한 것으로 통보하는 경우: 가족관\n계등록부에 기재된 사망연월일을 기준으로 합니다 .\n② 「호스피스 ·완화의료 및 임종과정에 있는 환자의 연명의료 결정에 관한 법률」에 따른 연명\n의료중단등결정 및 그 이행으로 피보험자가 사망하는 경우 연명의료중단등결정 및 그 이행\n은 제3조(보험금의 지급사유 ) 제1호 ‘사망’의 원인 및 ‘사망보험금 ’ 지급에 영향을 미치지 \n않습니다 .\n③ 제3조(보험금의 지급사유 ) 제2호에서 장해지급률이 상해 발생일부터 180일 이내에 확정되\n지 않는 경우에는 상해 발생일부터 180일이 되는 날의 의사 진단에 기초하여 고정될 것으\n로 인정되는 상태를 장해지급률로 결정합니다 . 다만, 장해분류표 (&lt;별표1&gt; 참조)에 장해판정\n시기를 별도로 정한 경우에는 그에 따릅니다 .\n④ 제3항에 따라 장해지급률이 결정되었으나 그 이후 보장받을 수 있는 기간(계약의 효력이 \n없어진 경우에는 상해 발생일부터 1년 이내)에 장해상태가 더 악화된 때에는 그 악화된 장\n해상태를 기준으로 장해지급률을 결정합니다 .\n⑤ 장해분류표에 해당되지 않는 후유장해는 피보험자의 직업, 연령, 신분 또는 성별 등에 관계\n없이 신체의 장해정도에 따라 장해분류표의 구분에 준하여 지급액을 결정합니다 . 다만, 장\n해분류표의 각 장해분류별 최저 지급률 장해정도에 이르지 않는 후유장해에 대하여는 후유\n장해보험금을 지급하지 않습니다 .\n⑥ 보험수익자와 회사가 제3조(보험금의 지급사유 )의 보험금 지급사유에 대해 합의하지 못할 \n때는 보험수익자와 회사가 함께 제3자를 정하고 그 제3자의 의견에 따를 수 있습니다 . 제3\n자는 의료법 제3조(의료기관 )에 규정한 종합병원 소속 전문의 중에 정하며 , 보험금 지급사\n유 판정에 드는 의료비용은 회사가 전액 부담합니다 .\n⑦ 같은 상해로 두 가지 이상의 후유장해가 생긴 경우에는 후유장해 지급률을 더하여 지급합\n니다. 다만, 장해분류표의 각 신체부위별 판정기준에 별도로 정한 경우에는 그 기준에 따릅\n니다.\n⑧ 다른 상해로 인하여 후유장해가 2회 이상 발생하였을 경우에는 그 때마다 이에 해당하는 \n후유장해지급률을 결정합니다 . 그러나 그 후유장해가 이미 후유장해보험금을 지급받은 동일\n한 부위에 가중된 때에는 최종 장해상태에 해당하는 후유장해보험금에서 이미 지급받은 후\n유장해보험금을 차감하여 지급합니다 . 다만, 장해분류표의 각 신체부위별 판정기준에서 별\n도로 정한 경우에는 그 기준에 따릅니다 .\n⑨ 이미 이 계약에서 후유장해보험금 지급사유에 해당되지 않았거나 (보장개시 이전의 원인에&#39;),
 Document(id=&#39;2d0f969b-291c-4b06-8fb1-aab52e460dca&#39;, metadata={&#39;page&#39;: 151, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 141 -증가된 손해를 보상하여 드리지 않으며 , 제1항 제3호의 통지를 게을리 한 때에는 소송비용\n과 변호사비용도 보상하여 드리지 않습니다 . 다만, 계약자 또는 피보험자가 상법 제657조 \n제1항에 의해 보험사고의 발생을 회사에 알린 경우에는 제2조(보상하는 손해의 범위) 제1\n호 및 제2호 ‘다’목 또는 ‘라’목의 비용에 대하여 보상한도액을 한도로 보상하여 드립니다 .\n제5조(보험금의 청구)\n피보험자가 보험금을 청구할 때에는 다음의 서류를 회사에 제출하여야 합니다 .\n 1. 보험금 청구서 (회사양식 )\n 2. 신분증 (주민등록증이나 운전면허증 등 사진이 붙은 정부기관발행 신분증 , 본인이 아닌 경\n우에는 본인의 인감증명서 또는 본인서명사실확인서 포함)\n 3. 손해배상금 및 그 밖의 비용을 지급하였음을 증명하는 서류\n 4. 회사가 요구하는 그 밖의 서류\n제6조(보험금의 지급절차 )\n① 회사는 제5조(보험금의 청구)에서 정한 서류를 접수한 때에는 접수증을 교부하고 , 그 서류\n를 접수받은 후 지체없이 지급할 보험금을 결정하고 지급할 보험금이 결정되면 7일 이내에 \n이를 지급하여 드립니다 . 또한, 지급할 보험금이 결정되기 전이라도 피보험자의 청구가 있\n을 때에는 회사가 추정한 보험금의 50% 상당액을 가지급보험금으로 지급합니다 .\n② 회사는 제1항의 지급보험금이 결정된 후 7일(이하 ‘지급기일 ’이라 합니다 )이 지나도록 보험\n금을 지급하지 않았을 때에는 지급기일의 다음날부터 지급일까지의 기간에 대하여 &lt;부표&gt; \n‘보험금을 지급할 때의 적립이율 ’에 따라 연단위 복리로 계산한 금액을 보험금에 더하여 지\n급합니다 . 그러나 피보험자의 책임있는 사유로 지체된 경우에는 그 해당기간에 대한 이자를 \n더하여 지급하지 않습니다 .\n제7조(보험금 등의 지급한도 ) \n① 회사는 1회의 보험사고에 대하여 다음과 같이 보상합니다 . 이 경우 보상한도액과 자기부담\n금은 각각 보험증권에 기재된 금액을 말합니다 . \n 1. 제2조(보상하는 손해의 범위) 제1항 제1호의 손해배상금 : 보상한도액을 한도로 보상하\n되, 자기부담금이 약정된 경우에는 그 자기부담금을 초과한 부분만 보상합니다 .\n 2. 제2조(보상하는 손해의 범위) 제1항 제2호 ‘가’목, ‘나’목 또는 ‘마’목의 비용 : 비용의 전\n액을 보상합니다 .\n 3. 제2조(보상하는 손해의 범위) 제1항 제2호 ‘다’목 또는 ‘라’목의 비용 : 이 비용과 제1호\n에 의한 보상액의 합계액을 보상한도액내에서 보상합니다 .\n② 보험기간 중 발생하는 사고에 대한 회사의 보상총액은 보험증권에 기재된 총 보상한도액을 \n한도로 합니다 .\n제8조(의무보험과의 관계)\n① 회사는 이 약관에 의하여 보상하여야 하는 금액이 의무보험에서 보상하는 금액을 초과할 \n때에 한하여 그 초과액만을 보상합니다 . 다만, 이 계약이 의무보험인 경우에는 그러하지 않\n으며, 의무보험이 다수인 경우에는 제9조(보험금의 분담)를 따릅니다 . 기     간 지 급 이 자\n지급기일의 다음 날부터 30일 이내 기간 보험계약대출이율\n지급기일의 31일이후부터 60일이내 기간 보험계약대출이율 + 가산이율 (4.0%)\n지급기일의 61일이후부터 90일이내 기간 보험계약대출이율 + 가산이율 (6.0%)\n지급기일의 91일이후 기간 보험계약대출이율 + 가산이율 (8.0%)\n주) 보험계약대출이율은 보험개발원이 공시하는 보험계약대출이율을 적용합니다 .    &lt;부표&gt; 보험금을 지급할 때의 적립이율&#39;),
 Document(id=&#39;3dffb042-6715-45aa-bab8-eb5d58218244&#39;, metadata={&#39;page&#39;: 202, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 192 -2. 배상\n1) 일상(가족, 자녀)생활중 배상책임 관련      \n【사례5】\n피보험자가 차량 뒷자석에서 하차중 차량문을 열고 내리던중 옆차량을 파손한 사고로 약관 \n제0조 ‘항공기 , 선박, 차량, 총기의 소유, 사용, 관리로 인한 배상책임 ’은 면책\n⇒ 차량 하차중 개문발차사고는 자동차를 용법에 맞게 사용중 발생한 사고로 자동차 보험\n처리 적용 대상에 해당하여 일상생활 중 배상책임에서 보상되지 않습니다 .\n【사례6】\n피보험자 (미성년자 )가 돌로 차량에 낙서를 하여 차량에 피해를 준 사고\n⇒ 피보험자의 고의사고에 해당하여 보상하지 않으나 , 판례에 따라 책임무능력자는 사리분\n별이 안되므로 고의로 보기 힘들다 하여 12세 이하까지는 보상을 하고 있음\n3. 화재\n1) 화재보험의 도난담보 (명기가재 )관련  \n【사례7】\nK씨는 화재 및 도난을 보상받고자 보험가입을 하였으나 추후 귀금속 등 귀중품은 명기가\n재로 등록해야 보상이 가능하다고 내용을 알고 불만 제기\n⇒ 명기가재란 한점당 300만원 이상 고가의 가재로 보험가입시 해당 내용을 명확하게 서\n면으로 작성하셔야 하며, 서면으로 작성하지 않을 경우 보상에 문제가 있을 수 있습니다 .\n2) 화재보험의 화재의 정의 관련  \n【사례8】\n사용하는 열풍기 (전열기 )에 의해 피보험자의 소파 열변형이 발생한 사례로 약관에서 규정\n하는 화재의 정의에 대한 분쟁(피보험자는 본건 열변형이 화재라고 주장함 )\n⇒ 약관 해석상 화재는 연소현상이 있어야 하며 열변형은 화재에 해당하지 않습니다 .&#39;),
 Document(id=&#39;a10a022a-015c-4c84-866d-1c27ce90a41a&#39;, metadata={&#39;page&#39;: 13, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 3 -의하거나 또는 그 이전에 발생한 후유장해를 포함합니다 ), 후유장해보험금이 지급되지 않았\n던 피보험자에게 그 신체의 동일 부위에 또다시 제8항에 규정하는 후유장해상태가 발생하\n였을 경우에는 직전까지의 후유장해에 대한 후유장해보험금이 지급된 것으로 보고 최종 후\n유장해 상태에 해당되는 후유장해보험금에서 이를 차감하여 지급합니다 .\n【사 례】\n이 계약의 보장개시전의 원인에 의하거나 또는 그 이전에 발생한 장해로 후유장해보험금\n의 지급사유가 되지 않았던 장해 :\n보험가입 전 한 팔의 손목관절에 심한 장해(지급률 20%)가 있었던 피보험자가 보험가입 \n후 상해로 그 손목관절에 기능을 완전히 잃은 경우(지급률 30%)에는 보험가입 후 발생한 \n상해로 인한 장해지급률 30%에서 보험가입 전 발생한 장해지급률 20%를 차감한 10%에 \n해당하는 후유장해보험금을 지급\n⑩ 회사가 지급하여야 할 하나의 상해로 인한 후유장해보험금은 보험가입금액을 한도로 합니다 .\n제5조(보험금을 지급하지 않는 사유) \n① 회사는 다음 중 어느 한가지로 보험금 지급사유가 발생한 때에는 보험금을 지급하지 않습\n니다.\n 1. 피보험자가 고의로 자신을 해친 경우. 다만, 피보험자가 심신상실 등으로 자유로운 의사\n결정을 할 수 없는 상태에서 자신을 해친 경우에는 보험금을 지급합니다 .\n 2. 보험수익자가 고의로 피보험자를 해친 경우. 다만, 그 보험수익자가 보험금의 일부 보험\n수익자인 경우에는 다른 보험수익자에 대한 보험금은 지급합니다 .\n 3. 계약자가 고의로 피보험자를 해친 경우\n 4. 피보험자의 임신, 출산(제왕절개를 포함합니다 ), 산후기 . 그러나 , 회사가 보장하는 보험금 \n지급사유와 보장개시일부터 2년이 지난 후에 발생한 습관성 유산, 불임 및 인공수정 관\n련 합병증으로 인한 경우에는 보험금을 지급합니다 .\n【습관성 유산, 불임 및 인공수정 】\n 한국표준질병 ·사인분류상의 N96~N98에 해당하는 질병을 말합니다 . \n 5. 전쟁, 외국의 무력행사 , 혁명, 내란, 사변, 폭동\n② 회사는 다른 약정이 없으면 피보험자가 직업, 직무 또는 동호회 활동목적으로 아래에 열거\n된 행위로 인하여 제3조(보험금의 지급사유 )의 상해 관련 보험금 지급사유가 발생한 때에는 \n해당 보험금을 지급하지 않습니다 .\n 1. 전문등반 (전문적인 등산용구를 사용하여 암벽 또는 빙벽을 오르내리거나 특수한 기술, 경\n험, 사전훈련을 필요로 하는 등반을 말합니다 ), 글라이더 조종, 스카이다이빙 , 스쿠버다\n이빙, 행글라이딩 , 수상보트 , 패러글라이딩\n 2. 모터보트 , 자동차 또는 오토바이에 의한 경기, 시범, 흥행(이를 위한 연습을 포함합니다 ) \n또는 시운전 (다만, 공용도로상에서 시운전을 하는 동안 보험금 지급사유가 발생한 경우\n에는 보장합니다 )\n 3. 선박에 탑승하는 것을 직무로 하는 사람이 직무상 선박에 탑승하고 있는 동안\n제6조(보험금 지급사유의 통지) \n계약자 또는 피보험자나 보험수익자는 제3조(보험금의 지급사유 )에서 정한 보험금 지급사유의 \n발생을 안 때에는 지체없이 그 사실을 회사에 알려야 합니다 .\n제7조(보험금의 청구) \n① 보험수익자는 다음의 서류를 제출하고 보험금을 청구하여야 합니다 .\n 1. 청구서 (회사 양식)\n 2. 사고증명서 (진료비계산서 , 사망진단서 , 장해진단서 , 입원치료확인서 , 의사처방전 (처방조제\n비) 등)\n 3. 신분증 (주민등록증이나 운전면허증 등 사진이 붙은 정부기관발행 신분증 , 본인이 아닌 경\n우에는 본인의 인감증명서 또는 본인서명사실확인서 포함)&#39;),
 Document(id=&#39;d3fb71b8-3c35-4da7-b234-7e0d5ebe4016&#39;, metadata={&#39;page&#39;: 61, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 51 -항공기탑승중 상해위험 특별약관\n제1조(보험금의 지급사유 ) \n① 회사는 피보험자에게 다음 사항 중 어느 한 가지의 경우에 해당되는 사유가 발생한 때에는 \n보험수익자에게 약정한 보험금을 지급합니다 .\n 1. 보험기간 중에 급격하고도 우연한 외래의 항공기 사고로 발생한 상해의 직접결과로써 사\n망한 경우(질병으로 인한 사망은 제외합니다 ) : 사망보험금\n 2. 보험기간 중에 제1호의 상해로 장해분류표 ([별표1]참조. 이하 같습니다 )에서 정한 각 장\n해지급률에 해당하는 장해상태가 되었을 때 : 후유장해보험금 (장해분류표에서 정한 지급\n률을 보험가입금액에 곱하여 산출한 금액)\n② 제1항에서 「항공기사고 」라 함은 항공기 탑승중의 사고, 비행장 구내에서의 사고 및 피보험\n자가 탑승한 항공기가 불시착한 경우에 피보험자가 목적지에 도착할 때까지 항공운송업자\n가 제공하는 자동차 및 기타 교통승용구에 탑승중의 사고를 말합니다 . 여기서 「비행장구\n내」란 비행기 탑승시 각 항공사의 개찰구 안쪽을 말합니다 . \n③ 제2항에서 자동차라 함은 자동차관리법 시행규칙 제2조에 정한 승용자동차 , 승합자동차 , 화\n물자동차 , 특수자동차 , 이륜자동차 및 자동차손해배상보장법 시행령 제2조에 정한 건설기계\n를 말합니다 . 다만, 건설기계가 작업기계로 사용되는 동안은 자동차로 보지 않습니다 . \n④ 제2항에서 기타교통승용구라 함은 아래의 어느 하나에 해당하는 것을 말합니다 . \n 1. 기차, 전동차 , 기동차 , 케이블카 (공중케이블카를 포함합니다 ), 리프트 , 엘리베이터 및 에\n스컬레이터 , 모노레일 \n 2. 스쿠터 , 자전거 , 원동기를 붙인 자전거 \n 3. 항공기 , 선박(요트, 모트보트 , 보트를 포함합니다 ) \n 4. 자동차손해배상보장법 시행령 제2조에 정한 건설기계를 제외한 건설기계 및 농업기계 (다\n만, 이들이 작업기계로 사용되는 동안은 자동차로 보지 않습니다 ) \n제2조(보험금을 지급하지 않는 사유) \n회사는 보통약관 제5조(보험금을 지급하지 않는 사유) 및 다음 중 어느 한 가지의 경우에 의하\n여 보험금 지급사유가 발생한 때에는 보험금을 지급하지 않습니다 .\n 1. 시운전 , 경기(연습을 포함합니다 ) 또는 흥행(연습을 포함합니다 )을 위하여 운행중의 자동\n차 및 기타교통승용구에 탑승(운전을 포함합니다 )하고 있는 동안 \n 2. 하역작업을 하는 동안 \n 3. 자동차 및 기타교통승용구의 설치, 수선, 점검, 정비나 청소작업을 하는 동안 \n제3조(준용규정 ) \n이 특별약관에 정하지 않은 사항은 보통약관을 따릅니다 .&#39;)]</code></pre>
<p><strong>LangChain 을 이용한 Multi-Query 답변 생성</strong>
Multi-query 를 생성하여 여러 개의 문서(Document) 를 리스트 형태로 얻었다면
이를 context 로 이용하는 Langchain 을 생성하여 최종 답변을 얻을 수 있습니다.</p>
<pre><code class="language-python">from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain.schema.runnable import RunnablePassthrough
from langchain.retrievers.multi_query import MultiQueryRetriever


# Prompt
template = &#39;&#39;&#39;Answer the question based only on the following context:
{context}

Question: {question}
&#39;&#39;&#39;

prompt = ChatPromptTemplate.from_template(template)


question = &#39;보험금의 지급사유&#39;

llm = ChatOpenAI(model=&quot;gpt-4-0125-preview&quot;, temperature=0)

retriever_from_llm = MultiQueryRetriever.from_llm(
    retriever=vector_store.as_retriever(), llm=llm
)


# 각각의 문서를 하나의 문자열로 join 합니다.
def format_docs(docs):
    return &#39;\n\n&#39;.join([d.page_content for d in docs])


#  LangChain 을 생성합니다.
# retriever_from_llm 을 이용해 얻은 문서들을 context 로 하는 LangChain 을 생성해 최종 답변을 얻습니다.
chain = (
    {&#39;context&#39;: retriever_from_llm | format_docs, &#39;question&#39;: RunnablePassthrough()}
    | ChatPromptTemplate.from_template(template)
    | llm
    | StrOutputParser()
)

# Run
response = chain.invoke(question)
response</code></pre>
<p><strong>Output</strong></p>
<pre><code class="language-bash">INFO:langchain.retrievers.multi_query:Generated queries: [&#39;1. 보험금 지급을 결정하는 기준은 무엇인가요?&#39;, &#39;2. 어떤 상황에서 보험금을 받을 수 있나요?&#39;, &#39;3. 보험금 지급 조건에는 어떤 것들이 포함되나요?&#39;]

&#39;보험금의 지급사유는 다음과 같습니다:\n\n1. 보험기간 중에 상해의 직접결과로써 사망한 경우: 사망보험금\n2. 보험기간 중 상해로 장해분류표에서 정한 각 장해지급률에 해당하는 장해상태가 되었을 때: 후유장해보험금 (장해분류표에서 정한 지급률을 보험가입금액에 곱하여 산출한 금액)\n3. 항공기탑승중 상해위험 특별약관에 따라, 보험기간 중에 급격하고도 우연한 외래의 항공기 사고로 발생한 상해의 직접결과로써 사망한 경우 또는 제1호의 상해로 장해분류표에서 정한 각 장해지급률에 해당하는 장해상태가 되었을 때 보험수익자에게 약정한 보험금을 지급합니다.\n\n이 외에도 보험금 지급사유에는 특정 질병으로 인한 사망, 장해 상태, 연명의료중단등결정 및 그 이행으로 인한 사망 등이 포함될 수 있으며, 보험 약관에 따라 다양한 사유가 명시되어 있습니다.&#39;</code></pre>
<h2 id="fusion-post-retrieval">Fusion (Post-retrieval)</h2>
<p><code>Fusion</code> 기법은 여러 출처에서 가져온 응답을 합쳐 최종적인 답변을 생성하는 방법입니다. Multi-query 기법과는 다르게, Fusion은 다양한 문서나 검색 결과에서 검색된 내용을 결합하여 최종적으로 유용한 정보를 제공하도록 합니다.</p>
<ol>
<li><strong>기법 설명</strong><ul>
<li>Fusion에는 주로 두 가지 방식이 사용됩니다:<ul>
<li><strong>Late Fusion</strong>: 개별적인 검색 결과를 최종 단계에서 결합하여 답변에 반영합니다.</li>
<li><strong>Early Fusion</strong>: 검색 단계에서 각 문서의 스코어를 조정하고, 높은 스코어의 문서들로 답변을 구성하도록 유도합니다.</li>
</ul>
</li>
</ul>
</li>
<li><strong>예시</strong><ul>
<li>예를 들어, 특정 질의에 대해 여러 문서에서 검색 결과를 가져온다면, 각 문서에서 가져온 내용을 결합하여 요약된 형태로 응답할 수 있습니다.</li>
</ul>
</li>
<li><strong>장점</strong><ul>
<li>정보를 결합하여 더 포괄적이고 일관된 답변을 제공합니다.</li>
<li>각 문서에서 중요한 정보를 추출하여, 정보가 누락되지 않도록 합니다.</li>
</ul>
</li>
<li><strong>단점</strong><ul>
<li>응답을 결합하는 과정에서 불필요한 정보가 포함될 수 있고, 잘못된 정보를 포함할 가능성도 있습니다.</li>
<li>개별 문서의 신뢰성이나 중요도를 고려하지 않고 단순히 결합하면 부정확한 답변이 생성될 수 있습니다.</li>
</ul>
</li>
</ol>
<p>더 자세한 설명 : RAG-Fusion 논문 참조(<a href="https://arxiv.org/abs/2402.03367">RAG-Fusion: a New Take on Retrieval-Augmented Generation</a>)</p>
<blockquote>
<p>(Abstract)
...RAG-Fusion combines RAG and reciprocal rank fusion (RRF) by generating multiple queries, reranking them with reciprocal scores and fusing the documents and scores.
...
RAG-Fusion은 여러 쿼리를 생성하고, 이를 상호 점수로 순위를 재조정하고(Reranking), 문서와 점수를 융합하여 RAG와 상호 순위 융합(RRF)을 결합합니다.</p>
</blockquote>
<p>다시 말해, 논문에서 말하는 RAG Fusion은 다중 쿼리와 검색 결과를 통합하는 과정에서 중요한 기법으로, Reciprocal Rank Fusion(RRF)을 사용하여 검색된 문서를 재정렬합니다.</p>
<h3 id="code-1">Code</h3>
<p><strong>RRF 함수 정의</strong>
RRF에서 각 문서의 점수는 다음과 같이 계산됩니다:</p>
<p>$$\text { score }=\sum \frac{1}{k+\operatorname{rank}(d)}$$</p>
<p>여기서 k는 작은 상수(일반적으로 60 정도), <code>rank(d)</code>는 문서 d가 각 검색 결과에서 차지한 순위입니다. 순위가 높을수록, 즉 더 상위에 랭크된 문서일수록 점수가 높아집니다.</p>
<p>예를 들어,</p>
<ul>
<li>검색 결과에서 어떤 문서가 첫 번째 순위에 있으면, 점수는 $$\frac{1}{60+1}$$, 즉 약 0.016입니다.</li>
<li>두 번째 순위라면, 점수는 $$\frac{1}{60+1}$$ , 즉 약 0.0157이 됩니다.</li>
</ul>
<p>이처럼 순위가 낮아질수록 점수가 줄어드는 것을 알 수 있습니다. 그래서 상위에 랭크된 문서가 더 큰 영향을 미치게 됩니다.</p>
<p>출처 : <a href="https://velog.io/@euisuk-chung/RAG-From-Scratch-5-9">[강의노트] RAG From Scratch : Query Translation</a></p>
<pre><code class="language-python">from langchain.load import dumps, loads

def reciprocal_rank_fusion(results: list[list], k=60):
    &quot;&quot;&quot; Reciprocal_rank_fusion that takes multiple lists of ranked documents 
        and an optional parameter k used in the RRF formula &quot;&quot;&quot;

    # Initialize a dictionary to hold fused scores for each unique document
    fused_scores = {}

    # Iterate through each list of ranked documents
    for docs in results:
        # Iterate through each document in the list, with its rank (position in the list)
        for rank, doc in enumerate(docs):
            # Convert the document to a string format to use as a key (assumes documents can be serialized to JSON)
            doc_str = dumps(doc)
            # If the document is not yet in the fused_scores dictionary, add it with an initial score of 0
            if doc_str not in fused_scores:
                fused_scores[doc_str] = 0
            # Retrieve the current score of the document, if any
            previous_score = fused_scores[doc_str]
            # Update the score of the document using the RRF formula: 1 / (rank + k)
            fused_scores[doc_str] += 1 / (rank + k)

    # Sort the documents based on their fused scores in descending order to get the final reranked results
    reranked_results = [
        (loads(doc), score)
        for doc, score in sorted(fused_scores.items(), key=lambda x: x[1], reverse=True)
    ]

    # Return the reranked results as a list of tuples, each containing the document and its fused score
    return reranked_results</code></pre>
<p><strong>프롬프트를 이용한 질문 생성</strong></p>
<pre><code class="language-python">from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI

# RAG-Fusion: Related
template = &quot;&quot;&quot;You are a helpful assistant that generates multiple search queries based on a single input query. \n
Generate multiple search queries related to: {question} \n
Output (3 queries):&quot;&quot;&quot;

prompt_rag_fusion = ChatPromptTemplate.from_template(template)

llm = ChatOpenAI(model=&quot;gpt-4-0125-preview&quot;, temperature=0)

# Chain 생성
generate_queries = (
    prompt_rag_fusion 
    | llm
    | StrOutputParser() 
    | (lambda x: x.split(&quot;\n&quot;))
)

question = &#39;보험금의 지급사유&#39;
generate_queries.invoke({&quot;question&quot;: question})</code></pre>
<p>Output</p>
<pre><code class="language-bash">[&#39;1. &quot;보험금 지급사유의 종류와 예시&quot;&#39;,
 &#39;2. &quot;보험금 지급 거절 사유 및 대응 방법&quot;&#39;,
 &#39;3. &quot;보험금 지급 절차 및 필요 서류&quot;&#39;]</code></pre>
<p><strong>문서 검색 후 RRF score 계산 및 정렬</strong></p>
<pre><code class="language-python">retriever = vector_store.as_retriever()
retrieval_chain_rag_fusion = generate_queries | retriever.map() | reciprocal_rank_fusion
docs = retrieval_chain_rag_fusion.invoke({&quot;question&quot;: question})
docs</code></pre>
<p>Output</p>
<pre><code class="language-bash">[(Document(id=&#39;13e178ec-e372-4dd0-ae38-92de6aa4e79c&#39;, metadata={&#39;page&#39;: 12, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 2 - 4. 기간과 날짜 관련 용어\n가. 보험기간 : 계약에 따라 보장을 받는 기간을 말합니다 .\n나. 영업일 : 회사가 영업점에서 정상적으로 영업하는 날을 말하며 , 토요일 , ‘관공서의 공휴\n일에 관한 규정’에 따른 공휴일과 근로자의 날을 제외합니다 .\n제2관 보험금의 지급\n제3조(보험금의 지급사유 )\n회사는 피보험자에게 다음 중 어느 하나의 사유가 발생한 경우에는 보험수익자에게 약정한 보\n험금을 지급합니다 .\n 1. 보험기간 중에 상해의 직접결과로써 사망한 경우(질병으로 인한 사망은 제외합니다 ): 사\n망보험금\n 2. 보험기간 중 상해로 장해분류표 (&lt;별표1&gt; 참조)에서 정한 각 장해지급률에 해당하는 장해\n상태가 되었을 때: 후유장해보험금 (장해분류표에서 정한 지급률을 보험가입금액에 곱하\n여 산출한 금액)\n제4조(보험금 지급에 관한 세부규정 ) \n① 제3조(보험금의 지급사유 ) 제1호 ‘사망’에는 보험기간에 다음 어느 하나의 사유가 발생한 \n경우를 포함합니다 .\n 1. 실종선고를 받은 경우: 법원에서 인정한 실종기간이 끝나는 때에 사망한 것으로 봅니다 .\n 2. 관공서에서 수해, 화재나 그 밖의 재난을 조사하고 사망한 것으로 통보하는 경우: 가족관\n계등록부에 기재된 사망연월일을 기준으로 합니다 .\n② 「호스피스 ·완화의료 및 임종과정에 있는 환자의 연명의료 결정에 관한 법률」에 따른 연명\n의료중단등결정 및 그 이행으로 피보험자가 사망하는 경우 연명의료중단등결정 및 그 이행\n은 제3조(보험금의 지급사유 ) 제1호 ‘사망’의 원인 및 ‘사망보험금 ’ 지급에 영향을 미치지 \n않습니다 .\n③ 제3조(보험금의 지급사유 ) 제2호에서 장해지급률이 상해 발생일부터 180일 이내에 확정되\n지 않는 경우에는 상해 발생일부터 180일이 되는 날의 의사 진단에 기초하여 고정될 것으\n로 인정되는 상태를 장해지급률로 결정합니다 . 다만, 장해분류표 (&lt;별표1&gt; 참조)에 장해판정\n시기를 별도로 정한 경우에는 그에 따릅니다 .\n④ 제3항에 따라 장해지급률이 결정되었으나 그 이후 보장받을 수 있는 기간(계약의 효력이 \n없어진 경우에는 상해 발생일부터 1년 이내)에 장해상태가 더 악화된 때에는 그 악화된 장\n해상태를 기준으로 장해지급률을 결정합니다 .\n⑤ 장해분류표에 해당되지 않는 후유장해는 피보험자의 직업, 연령, 신분 또는 성별 등에 관계\n없이 신체의 장해정도에 따라 장해분류표의 구분에 준하여 지급액을 결정합니다 . 다만, 장\n해분류표의 각 장해분류별 최저 지급률 장해정도에 이르지 않는 후유장해에 대하여는 후유\n장해보험금을 지급하지 않습니다 .\n⑥ 보험수익자와 회사가 제3조(보험금의 지급사유 )의 보험금 지급사유에 대해 합의하지 못할 \n때는 보험수익자와 회사가 함께 제3자를 정하고 그 제3자의 의견에 따를 수 있습니다 . 제3\n자는 의료법 제3조(의료기관 )에 규정한 종합병원 소속 전문의 중에 정하며 , 보험금 지급사\n유 판정에 드는 의료비용은 회사가 전액 부담합니다 .\n⑦ 같은 상해로 두 가지 이상의 후유장해가 생긴 경우에는 후유장해 지급률을 더하여 지급합\n니다. 다만, 장해분류표의 각 신체부위별 판정기준에 별도로 정한 경우에는 그 기준에 따릅\n니다.\n⑧ 다른 상해로 인하여 후유장해가 2회 이상 발생하였을 경우에는 그 때마다 이에 해당하는 \n후유장해지급률을 결정합니다 . 그러나 그 후유장해가 이미 후유장해보험금을 지급받은 동일\n한 부위에 가중된 때에는 최종 장해상태에 해당하는 후유장해보험금에서 이미 지급받은 후\n유장해보험금을 차감하여 지급합니다 . 다만, 장해분류표의 각 신체부위별 판정기준에서 별\n도로 정한 경우에는 그 기준에 따릅니다 .\n⑨ 이미 이 계약에서 후유장해보험금 지급사유에 해당되지 않았거나 (보장개시 이전의 원인에&#39;),
  0.03306010928961749),
 (Document(id=&#39;1d7f192c-4261-4fee-9563-e05dbbcb178e&#39;, metadata={&#39;page&#39;: 14, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 4 - 4. 기타 보험수익자가 보험금의 수령에 필요하여 제출하는 서류(사망보험금 지급시 피보험\n자의 법정상속인이 아닌자가 청구하는 경우 법정상속인의 확인서 )\n② 제1항 제2호의 사고증명서는 의료법 제3조(의료기관 )에서 규정한 국내의 병원이나 의원 또\n는 국외의 의료관련법에서 정한 의료기관에서 발급한 것이어야 합니다 .\n제8조(보험금의 지급절차 ) \n① 회사는 제7조(보험금의 청구)에서 정한 서류를 접수한 때에는 접수증을 드리고 휴대전화 문\n자메시지 또는 전자우편 등으로도 송부하며 , 그 서류를 접수한 날부터 3영업일 이내에 보\n험금을 지급합니다 .\n② 회사가 보험금 지급사유를 조사․확인하기 위해 필요한 기간이 제1항의 지급기일을 초과할 \n것이 명백히 예상되는 경우에는 그 구체적인 사유와 지급예정일 및 보험금 가지급제도 (회사\n가 추정하는 보험금의 50% 이내를 지급)에 대하여 피보험자 또는 보험수익자에게 즉시 통\n지합니다 . 다만, 지급예정일은 다음 각 호의 어느 하나에 해당하는 경우를 제외하고는 제7\n조(보험금의 청구)에서 정한 서류를 접수한 날부터 30영업일 이내에서 정합니다 .\n 1. 소송제기\n 2. 분쟁조정 신청\n【분쟁조정 신청】\n분쟁조정 신청은 이 약관의 「분쟁의 조정」조항에 따르며 분쟁조정 신청 대상기관은 금융\n감독원의 금융분쟁조정위원회를 말합니다 .\n 3. 수사기관의 조사 \n 4. 해외에서 발생한 보험사고에 대한 조사\n 5. 제6항에 따른 회사의 조사요청에 대한 동의 거부 등 계약자 , 피보험자 또는 보험수익자\n의 책임있는 사유로 보험금 지급사유의 조사와 확인이 지연되는 경우\n 6. 제4조(보험금 지급에 관한 세부규정 ) 제6항에 따라 보험금 지급사유에 대해 제3자의 의\n견에 따르기로 한 경우\n③ 제2항에 의하여 장해지급률의 판정 및 지급할 보험금의 결정과 관련하여 확정된 장해지급\n률에 따른 보험금을 초과한 부분에 대한 분쟁으로 보험금 지급이 늦어지는 경우에는 보험\n수익자의 청구에 따라 이미 확정된 보험금을 먼저 가지급합니다 .\n④ 제2항에 의하여 추가적인 조사가 이루어지는 경우, 회사는 보험수익자의 청구에 따라 회사\n가 추정하는 보험금의 50% 상당액을 가지급보험금으로 지급합니다 .\n【가지급보험금 】\n보험금이 지급기한 내에 지급되지 못할 것으로 판단되는 경우 회사가 예상되는 보험금의 \n일부를 먼저 지급하는 제도로 피보험자가 필요로 하는 비용을 보전해 주기 위해 회사가 먼\n저 지급하는 임시 교부금을 말합니다 .\n⑤ 회사는 제1항에서 정한 지급기일내에 보험금을 지급하지 않았을 때(제2항에서 정한 지급예\n정일을 통지한 경우를 포함합니다 )에는 그 다음날부터 지급일까지의 기간에 대하여 &lt;부표&gt; \n‘보험금을 지급할 때의 적립이율 ’에 따라 연단위 복리로 계산한 금액을 보험금에 더하여 지\n급합니다 . 다만, 계약자 , 피보험자 또는 보험수익자에게 책임이 있는 사유로 지급이 지연된 \n경우에는 그 기간에 대한 이자는 지급하지 않습니다 .\n 기     간 지 급 이 자\n지급기일의 다음 날부터 30일 이내 기간 보험계약대출이율\n지급기일의 31일이후부터 60일이내 기간 보험계약대출이율 + 가산이율 (4.0%)\n지급기일의 61일이후부터 90일이내 기간 보험계약대출이율 + 가산이율 (6.0%)\n지급기일의 91일이후 기간 보험계약대출이율 + 가산이율 (8.0%)&lt;부표&gt; 보험금을 지급할 때의 적립이율&#39;),
  0.032539682539682535),
 (Document(id=&#39;3dffb042-6715-45aa-bab8-eb5d58218244&#39;, metadata={&#39;page&#39;: 202, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 192 -2. 배상\n1) 일상(가족, 자녀)생활중 배상책임 관련      \n【사례5】\n피보험자가 차량 뒷자석에서 하차중 차량문을 열고 내리던중 옆차량을 파손한 사고로 약관 \n제0조 ‘항공기 , 선박, 차량, 총기의 소유, 사용, 관리로 인한 배상책임 ’은 면책\n⇒ 차량 하차중 개문발차사고는 자동차를 용법에 맞게 사용중 발생한 사고로 자동차 보험\n처리 적용 대상에 해당하여 일상생활 중 배상책임에서 보상되지 않습니다 .\n【사례6】\n피보험자 (미성년자 )가 돌로 차량에 낙서를 하여 차량에 피해를 준 사고\n⇒ 피보험자의 고의사고에 해당하여 보상하지 않으나 , 판례에 따라 책임무능력자는 사리분\n별이 안되므로 고의로 보기 힘들다 하여 12세 이하까지는 보상을 하고 있음\n3. 화재\n1) 화재보험의 도난담보 (명기가재 )관련  \n【사례7】\nK씨는 화재 및 도난을 보상받고자 보험가입을 하였으나 추후 귀금속 등 귀중품은 명기가\n재로 등록해야 보상이 가능하다고 내용을 알고 불만 제기\n⇒ 명기가재란 한점당 300만원 이상 고가의 가재로 보험가입시 해당 내용을 명확하게 서\n면으로 작성하셔야 하며, 서면으로 작성하지 않을 경우 보상에 문제가 있을 수 있습니다 .\n2) 화재보험의 화재의 정의 관련  \n【사례8】\n사용하는 열풍기 (전열기 )에 의해 피보험자의 소파 열변형이 발생한 사례로 약관에서 규정\n하는 화재의 정의에 대한 분쟁(피보험자는 본건 열변형이 화재라고 주장함 )\n⇒ 약관 해석상 화재는 연소현상이 있어야 하며 열변형은 화재에 해당하지 않습니다 .&#39;),
  0.03200204813108039),
 (Document(id=&#39;6abc5c5c-182f-4734-af96-eb13c2f38536&#39;, metadata={&#39;page&#39;: 110, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 100 -자동차사고 변호사선임비용 특별약관\n제1조(보험금의 지급사유 ) \n① 회사는 피보험자가 보험기간 중에 자동차를 운전하던 중에 급격하고도 우연한 자동차사고\n(이하 『자동차 운전 중 교통사고 』라 합니다 )로 타인의 신체(의수, 의족, 의안, 의치 등 신\n체보조장구는 제외하나 , 인공장기나 부분 의치 등 신체에 이식되어 그 기능을 대신할 경우\n는 포함합니다 . 이하 같습니다 )에 상해를 입힘으로써 구속영장에 의하여 구속되었거나 , 검\n찰에 의해 공소제기 (이하 “기소”라 합니다 . 단, 약식기소는 제외합니다 .)된 경우 또는 검사\n에 의해 약식기소 되었으나 법원에 의해 보통의 심판절차인 공판절차에 의해 재판이 진행\n하게 된 경우에는 변호사선임비용을 부담함으로써 입은 손해(이하 “변호사선임비용 ”이라 \n합니다 .)를 『1사고』마다 보험가입금액을 한도로 보험수익자 ( 수익자의 지정이 없을 때에\n는 피보험자 )에게 지급하여 드립니다 . 다만, 검사에 의해 약식기소 되었으나 피보험자가 법\n원의 약식명령에 불복하여 정식재판을 청구한 경우에는 보상하여 드리지 않습니다 .\n② 제1항의 『약식기소 』라 함은 검사가 지방법원의 관할사건에 대하여 보통의 심판절차인 공\n판절차를 거치지 아니하고 피고인에게 벌금, 구류 또는 몰수의 형을 과하는 것이 타당하다\n고 판단하여 약식명령 공소장에 의하여 기소하는 것을 말합니다 .\n③ 제1항의 『자동차 』라 함은 자동차관리법 시행규칙 제2조에 정한 승용자동차 , 승합자동차 , \n화물자동차 , 특수자동차 , 이륜자동차 및 자동차손해배상보장법 시행령 제2조에 정한 건설기\n계를 말합니다 . 다만 건설기계가 작업기계로서만 사용되는 동안은 자동차로 보지 않습니다 .\n④ 제1항의 『자동차를 운전하던 중』 이라 함은 도로여부 , 주정차 여부, 엔진의 시동여부를 \n불문하고 피보험자가 자동차 운전석에 탑승하여 핸들을 조작하거나 조작 가능한 상태에 있\n는 것을 말합니다 .\n⑤ 제1항의 『1사고』 라 함은 하나의 자동차 운전 중 교통사고를 말하며 , 『1사고』로 항소\n심, 상고심 포함하여 다수의 소송을 하였을 경우 그 소송동안 피보험자가 부담한 전체 변호\n사선임비용을 합쳐서 보험가입금액을 한도로 보험수익자 ( 수익자의 지정이 없을 때에는 피\n보험자 )에게 지급하여 드립니다 .\n⑥ 제1항에서 정한 변호사선임비용에 대하여 보험금을 지급할 다른 계약(공제계약을 포함)이 \n체결되어 있고 각각의 계약에 대하여 다른 계약(공제계약을 포함)이 없는 것으로 하여 산출\n한 보상책임액의 합계액이 피보험자가 부담하는 금액을 초과했을 때 회사는 이 계약에 따\n른 보상책임액의 상기 합계액에 대한 비율에 따라 보험금을 지급합니다 .\n제2조(보험금을 지급하지 않는 사유) \n회사는 아래의 사유로 생긴 손해는 보상하여 드리지 않습니다 .\n 1. 피보험자의 고의\n 2. 계약자의 고의\n 3. 피보험자가 사고를 내고 도주하였을 때\n 4. 피보험자가 자동차를 경기용이나 경기를 위한 연습용 또는 시험용으로 운전하던 중 사고\n를 일으킨 때\n 5. 피보험자 (자가용 운전자를 대상으로 합니다 )가 자동차를 영업목적으로 운전하던 중 사고\n를 일으킨 때\n 6. 피보험자가 도로교통법 제43조, 제44조에 의한 음주, 무면허 상태 또는 제45조에 의한 \n약물 상태에서 운전하던 중 사고를 일으킨 때\n제3조(보험금의 청구)\n보험수익자 또는 계약자는 보험금을 청구할 때에는 다음 서류를 첨부하여 회사에 제출하여야 \n합니다 .\n 1. 보험금 청구서 (회사 양식)\n 2. 사고증명서 (소장, 선임한 변호사가 발행한 세금계산서 )\n 3. 신분증 (주민등록증 또는 운전면허증 등 사진이 부착된 정부기관발행 신분증 , 본인이 아닌 \n경우에는 본인의 인감증명서 또는 본인서명사실확인서 포함)\n 4. 기타 보험수익자가 보험금 등의 수령에 필요하여 제출하는 서류&#39;),
  0.016666666666666666),
 (Document(id=&#39;a10a022a-015c-4c84-866d-1c27ce90a41a&#39;, metadata={&#39;page&#39;: 13, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 3 -의하거나 또는 그 이전에 발생한 후유장해를 포함합니다 ), 후유장해보험금이 지급되지 않았\n던 피보험자에게 그 신체의 동일 부위에 또다시 제8항에 규정하는 후유장해상태가 발생하\n였을 경우에는 직전까지의 후유장해에 대한 후유장해보험금이 지급된 것으로 보고 최종 후\n유장해 상태에 해당되는 후유장해보험금에서 이를 차감하여 지급합니다 .\n【사 례】\n이 계약의 보장개시전의 원인에 의하거나 또는 그 이전에 발생한 장해로 후유장해보험금\n의 지급사유가 되지 않았던 장해 :\n보험가입 전 한 팔의 손목관절에 심한 장해(지급률 20%)가 있었던 피보험자가 보험가입 \n후 상해로 그 손목관절에 기능을 완전히 잃은 경우(지급률 30%)에는 보험가입 후 발생한 \n상해로 인한 장해지급률 30%에서 보험가입 전 발생한 장해지급률 20%를 차감한 10%에 \n해당하는 후유장해보험금을 지급\n⑩ 회사가 지급하여야 할 하나의 상해로 인한 후유장해보험금은 보험가입금액을 한도로 합니다 .\n제5조(보험금을 지급하지 않는 사유) \n① 회사는 다음 중 어느 한가지로 보험금 지급사유가 발생한 때에는 보험금을 지급하지 않습\n니다.\n 1. 피보험자가 고의로 자신을 해친 경우. 다만, 피보험자가 심신상실 등으로 자유로운 의사\n결정을 할 수 없는 상태에서 자신을 해친 경우에는 보험금을 지급합니다 .\n 2. 보험수익자가 고의로 피보험자를 해친 경우. 다만, 그 보험수익자가 보험금의 일부 보험\n수익자인 경우에는 다른 보험수익자에 대한 보험금은 지급합니다 .\n 3. 계약자가 고의로 피보험자를 해친 경우\n 4. 피보험자의 임신, 출산(제왕절개를 포함합니다 ), 산후기 . 그러나 , 회사가 보장하는 보험금 \n지급사유와 보장개시일부터 2년이 지난 후에 발생한 습관성 유산, 불임 및 인공수정 관\n련 합병증으로 인한 경우에는 보험금을 지급합니다 .\n【습관성 유산, 불임 및 인공수정 】\n 한국표준질병 ·사인분류상의 N96~N98에 해당하는 질병을 말합니다 . \n 5. 전쟁, 외국의 무력행사 , 혁명, 내란, 사변, 폭동\n② 회사는 다른 약정이 없으면 피보험자가 직업, 직무 또는 동호회 활동목적으로 아래에 열거\n된 행위로 인하여 제3조(보험금의 지급사유 )의 상해 관련 보험금 지급사유가 발생한 때에는 \n해당 보험금을 지급하지 않습니다 .\n 1. 전문등반 (전문적인 등산용구를 사용하여 암벽 또는 빙벽을 오르내리거나 특수한 기술, 경\n험, 사전훈련을 필요로 하는 등반을 말합니다 ), 글라이더 조종, 스카이다이빙 , 스쿠버다\n이빙, 행글라이딩 , 수상보트 , 패러글라이딩\n 2. 모터보트 , 자동차 또는 오토바이에 의한 경기, 시범, 흥행(이를 위한 연습을 포함합니다 ) \n또는 시운전 (다만, 공용도로상에서 시운전을 하는 동안 보험금 지급사유가 발생한 경우\n에는 보장합니다 )\n 3. 선박에 탑승하는 것을 직무로 하는 사람이 직무상 선박에 탑승하고 있는 동안\n제6조(보험금 지급사유의 통지) \n계약자 또는 피보험자나 보험수익자는 제3조(보험금의 지급사유 )에서 정한 보험금 지급사유의 \n발생을 안 때에는 지체없이 그 사실을 회사에 알려야 합니다 .\n제7조(보험금의 청구) \n① 보험수익자는 다음의 서류를 제출하고 보험금을 청구하여야 합니다 .\n 1. 청구서 (회사 양식)\n 2. 사고증명서 (진료비계산서 , 사망진단서 , 장해진단서 , 입원치료확인서 , 의사처방전 (처방조제\n비) 등)\n 3. 신분증 (주민등록증이나 운전면허증 등 사진이 붙은 정부기관발행 신분증 , 본인이 아닌 경\n우에는 본인의 인감증명서 또는 본인서명사실확인서 포함)&#39;),
  0.01639344262295082),
 (Document(id=&#39;8586aa97-9b25-4b6a-b556-f6040b1a57b8&#39;, metadata={&#39;page&#39;: 116, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 106 - 3. 경찰서에서 발행한 교통사고사실확인원 또는 검찰에 의해 기소된 경우 검찰청에서 발행\n한 공소장\n 4. 진단서 , 소견서 등 피해자의 상해등급을 확인할 수 있는 서류\n 5. 그 밖에 보험회사가 필요하다고 인정하는 서류\n⑨ 제1항에서 자동차라 함은 자동차관리법 시행규칙 제2조에 정한 승용자동차 , 승합자동차 , 화\n물자동차 , 특수자동차 및 자동차손해배상보장법 시행령 제2조에 정한 덤프트럭 , 타이어식 \n기중기 , 콘크리트믹서트럭 , 트럭적재식 콘크리트펌프 , 트럭적재식 아스팔트살포기 , 타이어식 \n굴삭기 , 노면측정장비 (노면측정장치를 가진 자주식인 것을 말한다 ), 트럭지게차 , 도로보수트\n럭)를 말합니다 . 이 경우 관련 법규가 변경되어 새로운 항목이 추가되는 경우에는 그 항목\n도 포함하는 것으로 합니다 . 다만, 건설기계가 작업기계로 사용되는 동안은 자동차로 보지 \n않습니다 .\n【용어풀이 】\n「중상해 」라 함은 사람의 신체를 상해하여 생명에 대한 위험을 발생하게 한 경우 신체의 \n상해로 인하여 불구 또는 불치나 난치의 질병에 이르게 한 경우를 말합니다 .\n제2조(보험금을 지급하지 않는 사유)\n회사는 아래의 사유를 원인으로 하여 생긴 손해는 보상하여 드리지 않습니다 .\n 1. 피보험자의 고의\n 2. 계약자의 고의 \n 3. 피보험자가 사고를 내고 도주하였을 때\n 4. 피보험자가 자동차를 경기용이나 경기를 위한 연습용 또는 시험용으로 운전하던 중 사고\n를 일으킨 때\n 5. 피보험자가 도로교통법 제43조, 제44조에 정한 음주․무면허 상태 또는 제45조에 의한 약\n물 상태에서  운전하던 중 사고를 일으킨 때\n 6. 자가용의 경우 피보험자가 자동차를 영업목적으로 운전하던 중 발생한 사고\n제3조(보험금의 비례분담 )\n① 제1조(보험금의 지급사유 ) 제1항의 형사합의금에 대하여 교통사고처리지원금을 지급할 다수 \n계약(각종 공제계약을 포함합니다 )이 체결되어 있는 경우 형사합의금 및 각 계약의 보상책\n임액에 따라 제2항에 의해 계산된 각 계약의 비례분담액을 보상책임액으로 지급합니다 .\n② 다수 계약이 체결되어 있는 경우 각각의 계약에 대하여 다른 계약이 없는 것으로 하여 산\n출한 보상책임액의 합계액이 형사합의금을 초과한 때에는 회사는 각 계약의 보상책임액을 \n비례분담하여 지급하며 , 비례분담액 산출방식은 다음과 같습니다 .\n각 계약별 비례분담액 =  \n                         형사합의금   × 각 계약별 보상책임액\n각 계약별 보상책임액의 합계액\n제4조(준용규정 )\n이 특별약관에서 정하지 않은 사항은 보통약관을 따릅니다 .&#39;),
  0.01639344262295082),
 (Document(id=&#39;d56a027c-81d5-482a-8012-b573290e069b&#39;, metadata={&#39;page&#39;: 201, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 191 -1. 공통 및 상해보험 \n1) 알릴 의무 위반 관련\n【사례1】\nS씨는 가입전부터 질병이 있었으나 몇 년 동안 보상 청구하지 않으면 괜찮다는 권유로 청\n약서에 기재하지 않았다가 추후 보상시 불이익이 발생됨을 인지하고 불만 제기\n⇒ 계약 전 알릴 의무는 상법 제651조(고지의무 위반으로 인한 계약해지 )에서 정하고 있는 \n의무사항으로 보험계약자나 피보험자는 청약시 반드시 보험회사가 질문한 중요한 사항에 \n대해 서면으로 정확히 알려야만 합니다 .\n【사례2】\nP씨는 대수롭지 않은 치료라는 생각으로 가입 전 병원 진료에 대해 알리지 않고 가입하였\n다가 추후 보상시 면책 안내되어 불만 제기\n⇒ 계약 전 알릴 의무는 상법 제651조(고지의무 위반으로 인한 계약해지 )에서 정하고 있는 \n의무사항으로 임의 판단으로 누락시에는 보험계약의 해지 또는 보험금 부지급 등 불이익을 \n당할 수 있습니다 .\n2) 자필서명 미이행 관련\n【사례3】\nA씨는 지인인 B설계사의 설명을 듣고 남편을 피보험자로 하여 보험 가입을 권하였으나 남\n편 설득이 여의치 않자 A씨가 피보험자 서명을 대필하여 보험가입하였고 추후 보상시 문\n제가 될 수 있다는 내용을 인지하고 불만 제기\n⇒ 피보험자의 서면 동의를 받지 않으면 보상시 불이익이 있을 수 있으므로 , 반드시 피보\n험자 동의를 받으셔야 합니다 .\n3) 계약유지 관련 \n【사례4】\nK씨는 직업이 변경되어 보험사에 통지하였으나 직업의 위험도가 변경되어 현재 계약의 보\n험료 및 담보가 변경됨을 안내받고 불만 제기\n⇒ 표준약관 (상해보험 계약 후 알릴 의무)에 근거하여 직업 급수(위험도 )에 따라 보험료의 \n증액을 청구하거나 계약의 해지 등 가입조건이 변경될 수 있습니다 .주요 민원 / 분쟁 사례 및 유의사항&#39;),
  0.016129032258064516),
 (Document(id=&#39;2581cf35-645d-467d-87d6-eb01735373a9&#39;, metadata={&#39;page&#39;: 119, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 109 -한 공소장\n 4. 진단서 , 소견서 등 피해자의 상해등급을 확인할 수 있는 서류\n 5. 그 밖에 보험회사가 필요하다고 인정하는 서류\n⑨ 제1항에서 자동차라 함은 자동차관리법 시행규칙 제2조에 정한 승용자동차 , 승합자동차 , 화\n물자동차 , 특수자동차 및 자동차손해배상보장법 시행령 제2조에 정한 덤프트럭 , 타이어식 \n기중기 , 콘크리트믹서트럭 , 트럭적재식 콘크리트펌프 , 트럭적재식 아스팔트살포기 , 타이어식 \n굴삭기 , 노면측정장비 (노면측정장치를 가진 자주식인 것을 말한다 ), 트럭지게차 , 도로보수트\n럭)를 말합니다 . 이 경우 관련 법규가 변경되어 새로운 항목이 추가되는 경우에는 그 항목\n도 포함하는 것으로 합니다 . 다만, 건설기계가 작업기계로 사용되는 동안은 자동차로 보지 \n않습니다 .\n【용어풀이 】\n「중상해 」라 함은 사람의 신체를 상해하여 생명에 대한 위험을 발생하게 한 경우 신체의 \n상해로 인하여 불구 또는 불치나 난치의 질병에 이르게 한 경우를 말합니다 .\n제2조(보험금을 지급하지 않는 사유)\n회사는 아래의 사유를 원인으로 하여 생긴 손해는 보상하여 드리지 않습니다 .\n 1. 피보험자의 고의\n 2. 계약자의 고의 \n 3. 피보험자가 사고를 내고 도주하였을 때\n 4. 피보험자가 자동차를 경기용이나 경기를 위한 연습용 또는 시험용으로 운전하던 중 사고\n를 일으킨 때\n 5. 피보험자가 도로교통법 제43조, 제44조에 정한 음주․무면허 상태 또는 제45조에 의한 약\n물 상태에서  운전하던 중 사고를 일으킨 때\n 6. 자가용의 경우 피보험자가 자동차를 영업목적으로 운전하던 중 발생한 사고\n제3조(보험금의 비례분담 )\n① 제1조(보험금의 지급사유 ) 제1항의 형사합의금에 대하여 교통사고처리지원금을 지급할 다수 \n계약(각종 공제계약을 포함합니다 )이 체결되어 있는 경우 형사합의금 및 각 계약의 보상책\n임액에 따라 제2항에 의해 계산된 각 계약의 비례분담액을 보상책임액으로 지급합니다 .\n② 다수 계약이 체결되어 있는 경우 각각의 계약에 대하여 다른 계약이 없는 것으로 하여 산\n출한 보상책임액의 합계액이 형사합의금을 초과하는 때에는 회사는 각 계약의 보상책임액\n을 비례분담하여 지급하며 , 비례분담액 산출방식은 다음과 같습니다 .\n각 계약별 비례분담액 =  \n                         형사합의금   × 각 계약별 보상책임액\n각 계약별 보상책임액의 합계액\n제4조(준용규정 )\n이 특별약관에서 정하지 않은 사항은 보통약관을 따릅니다 .&#39;),
  0.016129032258064516),
 (Document(id=&#39;2989db1e-ade4-4f26-9336-e21b30f6329c&#39;, metadata={&#39;page&#39;: 113, &#39;source&#39;: &#39;../data/[일반보험]_KB개인상해보험_보험약관.pdf&#39;}, page_content=&#39;- 103 - 3. 경찰서에서 발행한 교통사고사실확인원 또는 검찰에 의해 기소된 경우 검찰청에서 발행\n한 공소장\n 4. 진단서 , 소견서 등 피해자의 상해등급을 확인할 수 있는 서류\n 5. 그 밖에 보험회사가 필요하다고 인정하는 서류\n⑨ 제1항에서 자동차라 함은 자동차관리법 시행규칙 제2조에 정한 승용자동차 , 승합자동차 , 화\n물자동차 , 특수자동차 및 자동차손해배상보장법 시행령 제2조에 정한 덤프트럭 , 타이어식 \n기중기 , 콘크리트믹서트럭 , 트럭적재식 콘크리트펌프 , 트럭적재식 아스팔트살포기 , 타이어식 \n굴삭기 , 노면측정장비 (노면측정장치를 가진 자주식인 것을 말한다 ), 트럭지게차 , 도로보수트\n럭)를 말합니다 . 이 경우 관련 법규가 변경되어 새로운 항목이 추가되는 경우에는 그 항목\n도 포함하는 것으로 합니다 . 다만, 건설기계가 작업기계로 사용되는 동안은 자동차로 보지 \n않습니다 .\n【용어풀이 】\n「중상해 」라 함은 사람의 신체를 상해하여 생명에 대한 위험을 발생하게 한 경우 신체의 \n상해로 인하여 불구 또는 불치나 난치의 질병에 이르게 한 경우를 말합니다 .\n제2조(보험금을 지급하지 않는 사유)\n회사는 아래의 사유를 원인으로 하여 생긴 손해는 보상하여 드리지 않습니다 .\n 1. 피보험자의 고의\n 2. 계약자의 고의 \n 3. 피보험자가 사고를 내고 도주하였을 때\n 4. 피보험자가 자동차를 경기용이나 경기를 위한 연습용 또는 시험용으로 운전하던 중 사고\n를 일으킨 때\n 5. 피보험자가 도로교통법 제43조, 제44조에 정한 음주․무면허 상태 또는 제45조에 의한 약\n물 상태에서  운전하던 중 사고를 일으킨 때\n 6. 자가용의 경우 피보험자가 자동차를 영업목적으로 운전하던 중 발생한 사고\n제3조(보험금의 비례분담 )\n① 제1조(보험금의 지급사유 ) 제1항의 형사합의금에 대하여 교통사고처리지원금을 지급할 다수 \n계약(각종 공제계약을 포함합니다 )이 체결되어 있는 경우 형사합의금 및 각 계약의 보상책\n임액에 따라 제2항에 의해 계산된 각 계약의 비례분담액을 보상책임액으로 지급합니다 .\n② 다수 계약이 체결되어 있는 경우 각각의 계약에 대하여 다른 계약이 없는 것으로 하여 산\n출한 보상책임액의 합계액이 형사합의금을 초과하는 때에는 회사는 각 계약의 보상책임액\n을 비례분담하여 지급하며 , 비례분담액 산출방식은 다음과 같습니다 .\n각 계약별 비례분담액 =  \n                         형사합의금   × 각 계약별 보상책임액\n각 계약별 보상책임액의 합계액\n제4조(준용규정 )\n이 특별약관에서 정하지 않은 사항은 보통약관을 따릅니다 .&#39;),
  0.015873015873015872)]</code></pre>
<p><strong>LangChain 을 이용한 답변 생성</strong></p>
<pre><code class="language-python">from operator import itemgetter
from langchain_core.runnables import RunnablePassthrough

# Prompt
template = &#39;&#39;&#39;Answer the question based only on the following context:
{context}

Question: {question}
&#39;&#39;&#39;

prompt = ChatPromptTemplate.from_template(template)

llm = ChatOpenAI(model=&quot;gpt-4-0125-preview&quot;, temperature=0)

final_rag_chain = (
    {&#39;context&#39;: retrieval_chain_rag_fusion, &#39;question&#39;: RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

final_rag_chain.invoke(question)</code></pre>
<p>Output</p>
<pre><code class="language-bash">&#39;보험금의 지급사유는 다음과 같습니다:\n\n1. 보험기간 중에 상해의 직접결과로써 사망한 경우(질병으로 인한 사망은 제외합니다): 사망보험금\n2. 보험기간 중 상해로 장해분류표에서 정한 각 장해지급률에 해당하는 장해상태가 되었을 때: 후유장해보험금 (장해분류표에서 정한 지급률을 보험가입금액에 곱하여 산출한 금액)&#39;</code></pre>
<h2 id="multi-query와-fusion의-조합">Multi-Query와 Fusion의 조합</h2>
<p>두 기법을 함께 사용하면 모델이 더 나은 답변을 생성할 가능성이 높아집니다. Multi-query로 다양한 질의로 검색을 수행한 후, Fusion 기법으로 이러한 검색 결과를 조합하여 최종 응답을 생성할 수 있습니다. 이 방식은 특히 대량의 정보에서 특정한 답변을 생성해야 하는 RAG 시스템에 효과적입니다.</p>
<p>이렇게 사용하면 다양한 각도의 정보를 수집하면서도 응답의 풍부함과 정확성을 향상시킬 수 있습니다.  </p>
<p><strong>&lt;&#39;보험금의 지급사유&#39; 에 대한 답변 비교&gt;</strong>
<img src="https://velog.velcdn.com/images/judy_choi/post/9bdaa448-7763-488d-a12a-9d14f02edab8/image.png" alt=""></p>
<h2 id="reference">Reference</h2>
<p><strong>Multi Query Retriever</strong>
<a href="https://wikidocs.net/231603">2-6-2. Multi Query Retriever</a>
<a href="https://wikidocs.net/234109">06. 다중 쿼리 검색기(MultiQueryRetriever)</a>
<a href="https://python.langchain.com/docs/how_to/MultiQueryRetriever/">How to use the MultiQueryRetriever</a></p>
<p><strong>Fusion</strong>
<a href="https://github.com/Raudaschl/rag-fusion/blob/master/main.py">rag-fusion</a>
<a href="https://github.com/langchain-ai/rag-from-scratch/blob/main/rag_from_scratch_5_to_9.ipynb">rag-from-scratch</a>
<a href="https://velog.io/@euisuk-chung/RAG-From-Scratch-5-9">[강의노트] RAG From Scratch : Query Translation</a>
<a href="https://acting-rationally.tistory.com/9">RAG-FUSION: A NEW TAKE ON RETRIEVAL-AUGMENTED GENERATION</a>
<a href="https://g3lu.tistory.com/42">RAG의 패러다임(Naive RAG, Advanced RAG, Modular RAG)</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RAG 시리즈] Hybrid Search 와 재순위화 알고리즘 (RRF 를 곁들인..)]]></title>
            <link>https://velog.io/@judy_choi/RAG-%EC%8B%9C%EB%A6%AC%EC%A6%88</link>
            <guid>https://velog.io/@judy_choi/RAG-%EC%8B%9C%EB%A6%AC%EC%A6%88</guid>
            <pubDate>Fri, 08 Nov 2024 01:29:26 GMT</pubDate>
            <description><![CDATA[<h2 id="hybrid-search">Hybrid Search</h2>
<p>설명을 위해 다음 포스팅 글 중 일부를 발췌하여 재구성했습니다.
<a href="https://velog.io/@judy_choi/%EC%9A%B0%EC%95%84%ED%95%9C-%EC%8A%A4%ED%84%B0%EB%94%94-RAG-%EC%84%B1%EB%8A%A5%EC%9D%84-%EB%81%8C%EC%96%B4%EC%98%AC%EB%A6%AC%EB%8A%94-Pre-Retrieval-Ensenble-Retriever-%EC%99%80-Post-Retrieval-Re-Rank">[우아한 스터디] RAG 성능을 끌어올리는 Pre-Retrieval (Ensenble Retriever) 와 Post-Retrieval (Re-Rank)</a>
<a href="https://medium.com/@nuatmochoi/%ED%95%98%EC%9D%B4%EB%B8%8C%EB%A6%AC%EB%93%9C-%EA%B2%80%EC%83%89-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-feat-ensembleretriever-knowledge-bases-for-amazon-bedrock-d6ef1a0daaf1">하이브리드 검색 구현하기 (feat. EnsembleRetriever, Knowledge Bases for Amazon Bedrock)</a></p>
<p>Hybrid Search 는 다음과 같은 이유로 등장했습니다.</p>
<blockquote>
<p>LLM은 특정 시간에 학습을 멈추고 배포되기 때문에 실시간 데이터와 일치하지 않는 경우가 많다. 또한, 모든 데이터를 학습시킬 수 없기 때문에 사실과는 다른 답변을 내놓는 경우도 다수이다. 이러한 문제를 해결하기 위해, LLM에 질문과 유사도가 높은 context를 답변 생성 전에 전달하여 보다 높은 정확도의 답변을 반환하도록 하는 RAG라는 기술이 대두되었다. 현재 LLM을 활용하는 대부분의 챗봇이 환각 현상을 줄이기 위해 RAG를 사용하고 있다.
하지만 이러한 유사도 기반 검색조차 단어 임베딩 품질에 따라 답변의 정확도가 떨어졌으며, 프로덕션 레벨에서 일반 RAG를 사용하기는 부족함이 있었다. 이를 해결하기 위해 유사도에 더해 키워드 기반 검색을 함께 활용하는 Hybrid Search라는 기술이 생겨나게 되었다</p>
</blockquote>
<p>쉽게 말해 Hybrid Search 는 크게 Lexical Search 와 Semantic Search 를 결합한 방식으로,
키워드 기반 검색인 Lexical Search와 유사도 기반 검색인 Semantic Search는 각각 장단점이 있고 사용이 유리한 상황이 서로 다릅니다.</p>
<ul>
<li>Lexical Search : 
OpenSearch와 같은 전통적인 검색 엔진에서 주로 사용되는 방법으로, BM25와 같은 희소 벡터 알고리즘을 통해 키워드 기반 매칭을 진행한다. 특정 도메인 용어를 검색하기에 용이하지만 오타 및 동의어에 취약하다.</li>
<li>Semantic Search : 
키워드가 일치하지 않더라도 의미론적으로 유사한 검색 결과를 반환한다. 검색 결과는 임베딩 품질에 의존도가 높다.</li>
</ul>
<p>&#39;Hybrid Search는 각 검색 방법의 장점만을 추려 사용된다. 특정 도메인 용어나 제품 용어가 포함된 쿼리로 검색했을 때도 Lexical Search의 검색 결과를 통해 보조한다. 의미론적으로 유사한 동의어 검색의 경우 및 오타가 일부 포함되더라도 Semantic Search가 벡터 기반으로 가장 가까운 내용을 반환하기 때문에 보다 정확도를 높일 수 있다.&#39;</p>
<p>각각의 검색 방법에 대해 예시와 함께 매우 쉽게 풀어서 설명한 글을 참고해 보세요.
<a href="https://www.skelterlabs.com/blog/hybrid-search">키워드 검색, 시맨틱 검색 그리고 하이브리드 검색</a></p>
<h2 id="ensemble-retriever-를-이용한-hybrid-search-구현">Ensemble Retriever 를 이용한 Hybrid Search 구현</h2>
<p>EnsembleRetriever는 여러 retriever를 입력으로 받아 검색 결과를 앙상블하고, Reciprocal Rank Fusion 알고리즘을 기반으로 결과를 재순위화합니다.</p>
<p>Ensemble Retriever 의 가장 일반적인 패턴은 sparse retriever (예: BM25)와 dense retriever (예: embedding similarity)를 결합하는 것인데, 이는 두 retriever의 장점이 상호 보완적이기 때문입니다. 이를 &quot;hybrid search&quot; 라고도 합니다.</p>
<p>Sparse retriever는 키워드를 기반으로 관련 문서를 찾는 데 효과적이며, dense retriever는 의미적 유사성을 기반으로 관련 문서를 찾는 데 효과적입니다.
<a href="https://wikidocs.net/234100">출처 : 03. 앙상블 검색기(EnsembleRetriever)</a></p>
<p>일단, Retriever는 크게 두 가지 유형으로 나눌 수 있습니다:</p>
<ul>
<li>Sparse Retriever:
TF-IDF와 같은 기술을 사용하여 텍스트 데이터를 희소 벡터로 변환하고, 질의 벡터와의 유사성에 따라 문서를 검색합니다.</li>
<li>Dense Retriever:
BERT와 같은 신경망 모델을 사용하여 텍스트 데이터를 밀집 임베딩 벡터로 변환하고, 질의 벡터와의 유사성에 따라 관련 문서를 검색합니다.
두 가지 Retriever 유형은 각각의 장단점이 있으며, 검색 작업의 요구에 따라 선택해야 합니다.</li>
</ul>
<p>Sparse와 Dense Retriever를 결합하여, 즉 서로 다른 알고리즘의 장점을 활용함으로써, EnsembleRetriever는 단일 알고리즘보다 더 나은 성능을 달성하고 검색 효율성을 최적화할 수 있습니다.</p>
<p>아래는 Sparse Retriever(Lexical Search) 중 BM25 와 Dense Retriever(Semantic Search) 중 FAISS 를 결합하여
Ensemble Retriever 를 생성하는 코드입니다.</p>
<pre><code class="language-python">        # FAISS 벡터 스토어 생성
        vectorstore_faiss = FAISS.from_documents(docs, embeddings)
        # BM25 리트리버 생성 (Sparse Retriever)
        bm25_retriever = BM25Retriever.from_documents(docs)
        # FAISS 리트리버 생성 (Dense Retriever)
        faiss_retriever = vectorstore_faiss.as_retriever(search_kwargs={&#39;k&#39;: 5})

        # 앙상블 리트리버 생성
        self.retriever = EnsembleRetriever(
            retrievers=[bm25_retriever, faiss_retriever],
            weights=[0.5, 0.5]  # 가중치 설정 (가중치의 합은 1.0)
        )</code></pre>
<h2 id="ensemble-retriever-를-사용할-수-없는-경우">Ensemble Retriever 를 사용할 수 없는 경우</h2>
<p>Ensemble Retriever 는 retriever 들의 결합,
다시 말해 (Langchain 에서 제공하는) vector store 의 retriever,
또는 Langchain 에서 제공하는 retriever 를 결합하여 이용하게 되는데요.
만약 여러 가지 이유로 retriever 를 사용할 수 없어서 Ensemble Retriever 를 적용할 수 없다면 어떻게 해야 할까요?</p>
<p>이 경우 Lexical Search 결과와 Semantic Search 결과를 결합하기 위해 재순위화 알고리즘을 사용할 수 있습니다.</p>
<h3 id="재순위화-알고리즘">재순위화 알고리즘</h3>
<p>데이터 샘플로 아래와 같이 2가지의 Retriver 로 얻은 [(문장, 유사도 점수), ...] 데이터셋을 사용하겠습니다.</p>
<pre><code class="language-python">lyrics1 = [
    (&quot;Cause I-I-I&#39;m in the stars tonight&quot;, 0.9),
    (&quot;So watch me bring the fire and set the night alight&quot;, 0.8),
    (&quot;Shining through the city with a little funk and soul&quot;, 0.7),
    (&quot;So I&#39;ma light it up like dynamite&quot;, 0.6)
    ]

lyrics2 = [
    (&quot;Cause I-I-I&#39;m in the stars tonight&quot;, 0.5),
    (&quot;So watch me bring the fire and set the night alight&quot;, 0.4),
    (&quot;Bring a friend, join the crowd&quot;, 0.3),
    (&quot;Just move like we off the wall&quot;, 0.2)
    ]</code></pre>
<p>코드는 아래 깃헙에 올려두었습니다.
<a href="https://github.com/Judy-Choi/rag_series/blob/main/note_4/main.ipynb">GitHub - Rag Series</a></p>
<h4 id="rrf-reciprocal-rank-fusion">RRF (Reciprocal Rank Fusion)</h4>
<p>기본적으로 Hybrid Retrieval에는 크게 RRF와 CC 방법이 있습니다.</p>
<ul>
<li>RRF란?<ul>
<li>서로 다른 관련성 지표(relevance indicators)를 가진 여러 개의 결과 집합(result sets)을 하나의 결과 집합으로 결합하는 방법 (= rank 사용)</li>
<li>튜닝을 필요로 하지 않으며, 서로 다른 관련성 지표들이 상호 관련되지 않아도 고품질의 결과를 얻을 수 있음</li>
</ul>
</li>
<li>수식, 알고리즘 코드  및 설명<br><img src="https://velog.velcdn.com/images/judy_choi/post/dde6d342-7a83-4a71-9b35-4ad4d310e406/image.png" alt=""><pre><code class="language-python">  score = 0.0
  for q in queries:
      if d in result(q):
          score += 1.0 / ( k + rank( result(q), d ) )
  return score</code></pre>
<ul>
<li>N : 랭킹 리스트 갯수</li>
<li>rank(d) : 문서 d의 rank (순위)</li>
<li>result(q) : q(query) 를 질의했을 때 얻은 결과 문서 목록</li>
<li>k : 랭킹 상수 (default = 60)</li>
</ul>
</li>
</ul>
<p>추가 설명과 코드는 다음 포스팅을 추천합니다.
<a href="https://velog.io/@acdongpgm/NLP.-Reciprocal-rank-fusion-RRF-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0">[NLP]. Reciprocal rank fusion (RRF) 이해하기</a>
<a href="https://velog.io/@acdongpgm/NLP-Reciprocal-Rank-FusionRRF%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EA%B2%80%EC%83%89-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%B1%EB%8A%A5-%ED%96%A5%EC%83%81">[NLP] Reciprocal Rank Fusion(RRF)을 활용한 검색 시스템 성능 향상</a>
<a href="https://abluesnake.tistory.com/180">[IR] RRF(Reciprocal Rank Fusion) 설명과 파이썬 코드</a></p>
<p>RRF는 특히나 Langchain 의 Ensemble Retriever 에서 사용하는데요,
Langchain 에서 사용하는 RRF 코드 분석은 아래 포스팅에서 잘 설명해 놓았습니다.
<a href="https://velog.io/@autorag/%EB%9E%AD%EC%B2%B4%EC%9D%B8%EC%9D%98-Ensemble-Retriever-%EC%9D%B4%EA%B2%8C-%EB%8C%80%EC%B2%B4-%EB%AD%90%EC%A7%80">랭체인의 Ensemble Retriever, 이게 대체 뭐지?</a></p>
<h5 id="code">Code</h5>
<pre><code class="language-python">from typing import List, Tuple


def rrf_combination(
    lyrics1: List[Tuple[str, float]],
    lyrics2: List[Tuple[str, float]],
    k: int = 60 # RRF에서 사용하는 상수 (기본값 60)
) -&gt; List[Tuple[str, float]]:

    combined_results = {}

    # 첫 번째 결과의 Reciprocal Rank 계산 및 점수 합산
    for rank, (text, score) in enumerate(lyrics1):
        if text not in combined_results:
            combined_results[text] = 0
        combined_results[text] += score + 1 / (k + rank)

    # 두 번째 결과의 Reciprocal Rank 계산 및 점수 합산
    for rank, (text, score) in enumerate(lyrics2):
        if text not in combined_results:
            combined_results[text] = 0
        combined_results[text] += score + 1 / (k + rank)

    # 사전의 값을 리스트로 변환하고, 점수에 따라 내림차순으로 정렬
    sorted_combined_results = sorted(combined_results.items(), key=lambda x: x[1], reverse=True)

    return sorted_combined_results

rrf_combination(lyrics1, lyrics2)</code></pre>
<p>결과 (중복 제거 후 rrf score 계산)</p>
<pre><code class="language-python">[(&quot;Cause I-I-I&#39;m in the stars tonight&quot;, 1.4333333333333336),
 (&#39;So watch me bring the fire and set the night alight&#39;, 1.2327868852459019),
 (&#39;Shining through the city with a little funk and soul&#39;, 0.7161290322580645),
 (&quot;So I&#39;ma light it up like dynamite&quot;, 0.6158730158730159),
 (&#39;Bring a friend, join the crowd&#39;, 0.3161290322580645),
 (&#39;Just move like we off the wall&#39;, 0.21587301587301588)]</code></pre>
<h4 id="선형-결합-linear-combination">선형 결합 (Linear Combination)</h4>
<p>사실 선형 결합은 완전 기초 개념이라 수식만 보고 바로 알아보는 분들이 많으실 텐데요,
<img src="https://velog.velcdn.com/images/judy_choi/post/76eb1831-366a-4629-99bc-a7c62924c7cf/image.png" alt=""></p>
<p>선형 결합은 보통 벡터에 적용하지만, 검색 결과를 점수 기준으로 결합할 때에도 사용할 수 있습니다.</p>
<h5 id="code-1">Code</h5>
<pre><code class="language-python"># None 값을 0으로 치환하는 함수
def safe_float(value):
    return 0.0 if value is None else float(value)</code></pre>
<pre><code class="language-python">import math


def normalize_score(score, min_score, max_score):
    &quot;&quot;&quot;
    점수를 정규화합니다.
    1. Min-Max 정규화를 통해 입력 점수를 0-1 범위로 조정
    2. 로그 변환을 적용하여 점수 분포 조정
    3. 지수 함수를 사용하여 최종 유사도 점수 계산

    Parameters:
    rank (float): 변환할 개별 문서의 점수
    min_rank (float): 현재 검색 결과 세트의 최소 점수
    max_rank (float): 현재 검색 결과 세트의 최대 점수

    Returns:
    float: 0-1 사이의 점수
    &quot;&quot;&quot;
    # 1. Min-Max 정규화
    if (score - min_score) == 0 and (max_score - min_score) == 0:
        normalized_rank = 0
    else:
        normalized_rank = (score - min_score) / (max_score - min_score)

    # 2. 로그 변환 (1을 더해 0을 방지)
    log_rank = math.log(1 + normalized_rank)

    # 3. 지수 변환으로 0-1 범위로 매핑
    cosine_like_similarity = 1 - math.exp(-log_rank)

    return cosine_like_similarity</code></pre>
<pre><code class="language-python">from typing import List, Tuple

def linear_combination_search_results(
    lyrics1: List[Tuple[str, float]],
    lyrics2: List[Tuple[str, float]],
    alpha: float = 0.3, # 두 결과 간의 가중치를 조정하는 파라미터
) -&gt;List[Tuple[str, float]]:

    # 결과를 결합하기 위한 사전 생성
    combined_results = {}

    # 첫 번째 결과 추가
    for (text, score) in lyrics1:
        if text not in combined_results:
            combined_results[text] = alpha * safe_float(score)
        else:
            existing_score = combined_results[text]
            combined_results[text] = safe_float(existing_score) + alpha * safe_float(score)

    # 두번째 결과 추가
    for (text, score) in lyrics2:
        if text not in combined_results:
            combined_results[text] = alpha * safe_float(score)
        else:
            existing_score = combined_results[text]
            combined_results[text] = safe_float(existing_score) + alpha * safe_float(score)

    # 결과 점수 추출
    scores = list(combined_results.values())

    # 최소값과 최대값 계산
    min_score = min(scores)
    max_score = max(scores)

    # 점수 정규화 및 정렬
    normalized_results = [(text, normalize_score(score, min_score, max_score)) for text, score in combined_results.items()]

    # 사전의 값을 리스트로 변환하고, 점수에 따라 내림차순으로 정렬
    normalized_results.sort(key=lambda x: x[1], reverse=True)

    return normalized_results

linear_combination_search_results(lyrics1, lyrics2)</code></pre>
<pre><code class="language-python">[(&quot;Cause I-I-I&#39;m in the stars tonight&quot;, 0.5),
 (&#39;So watch me bring the fire and set the night alight&#39;, 0.4545454545454545),
 (&#39;Shining through the city with a little funk and soul&#39;, 0.2941176470588235),
 (&quot;So I&#39;ma light it up like dynamite&quot;, 0.2499999999999999),
 (&#39;Bring a friend, join the crowd&#39;, 0.07692307692307687),
 (&#39;Just move like we off the wall&#39;, 0.0)]</code></pre>
<h4 id="보르다-계산법-borda-count">보르다 계산법 (Borda count)</h4>
<p>다중 선호도 평가에서 사용되는 &#39;Borda Count&#39; 를 사용할 수도 있는데요,
위키피디아에서는 다음과 같이 설명합니다.</p>
<blockquote>
<p>Borda 방식 또는 순위는 각 후보자에게 아래에 순위가 매겨진 후보자 수와 같은 점수를 부여하는 위치 투표 규칙 입니다 . 가장 낮은 순위의 후보자는 0점, 두 번째로 낮은 후보자는 1점 등입니다. 모든 투표가 집계되면 가장 많은 점수를 받은 옵션 또는 후보자가 승자가 됩니다.</p>
</blockquote>
<p><a href="https://en.wikipedia.org/wiki/Borda_count">Borda count - Wikipedia</a>
<a href="https://wikidocs.net/120268">Borda count (보르다 계산법)</a></p>
<p><code>pip install borda</code> 로 패키지를 설치하여 사용할 수도 있는데,
제가 시도해 보니 unhashable type 에러가 납니다.</p>
<h5 id="code-2">Code</h5>
<pre><code class="language-python">def borda_count_combination(
    lyrics1: List[Tuple[str, float]],
    lyrics2: List[Tuple[str, float]],
) -&gt;List[Tuple[str, float]]:

    combined_results = {}

    # 첫 번째 결과에 순위 점수 부여
    for rank, (text, score) in enumerate(lyrics1):
        if text not in combined_results:
            combined_results[text] = 0
        combined_results[text] =  combined_results[text] + len(lyrics1) - rank

    # 두 번째 결과에 순위 점수 부여
    for rank, (text, score) in enumerate(lyrics2):
        if text not in combined_results:
            combined_results[text] = 0
        combined_results[text] =  combined_results[text] + len(lyrics2) - rank


    # 사전의 값을 리스트로 변환하고, 점수에 따라 내림차순으로 정렬
    sorted_combined_results = sorted(combined_results.items(), key=lambda x: x[1], reverse=True)

    return sorted_combined_results

borda_count_combination(lyrics1, lyrics2)</code></pre>
<pre><code class="language-python">[(&quot;Cause I-I-I&#39;m in the stars tonight&quot;, 8),
 (&#39;So watch me bring the fire and set the night alight&#39;, 6),
 (&#39;Shining through the city with a little funk and soul&#39;, 2),
 (&#39;Bring a friend, join the crowd&#39;, 2),
 (&quot;So I&#39;ma light it up like dynamite&quot;, 1),
 (&#39;Just move like we off the wall&#39;, 1)]</code></pre>
<h2 id="reference">Reference</h2>
<p><a href="https://medium.com/@dk02315/opensearch-%ED%82%A4%EC%9B%8C%EB%93%9C-%EB%B2%A1%ED%84%B0-%EA%B8%B0%EB%B0%98-hybrid-search-%EA%B5%AC%ED%98%84-fe1426910b26">https://medium.com/@dk02315/opensearch-키워드-벡터-기반-hybrid-search-구현-fe1426910b26</a>
<a href="https://velog.io/@uiw_min/Hybrid-Search">https://velog.io/@uiw_min/Hybrid-Search</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RAG 시리즈] LangChain VectorStore DB구조와 활용]]></title>
            <link>https://velog.io/@judy_choi/RAG-%EC%8B%9C%EB%A6%AC%EC%A6%88-LangChain-VectorStore-DB%EA%B5%AC%EC%A1%B0%EC%99%80-%ED%99%9C%EC%9A%A9</link>
            <guid>https://velog.io/@judy_choi/RAG-%EC%8B%9C%EB%A6%AC%EC%A6%88-LangChain-VectorStore-DB%EA%B5%AC%EC%A1%B0%EC%99%80-%ED%99%9C%EC%9A%A9</guid>
            <pubDate>Fri, 25 Oct 2024 01:30:38 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@judy_choi/PGVector-%EC%99%80-%ED%94%84%EB%A1%AC%ED%94%84%ED%8A%B8%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-RAG-%EA%B3%A0%EB%8F%84%ED%99%94">[RAG 시리즈] PGVector 와 프롬프트를 이용한 RAG 고도화</a> 포스팅에서
PDF 파일 로드 -&gt; 청킹 -&gt; vector store 에 저장하는 과정을 다루었는데요,
이번 포스팅에서는 vector store DB에 데이터가 어떤 형태로 저장되어 있는지 살펴보고,
데이터 활용도를 높이기 위해 DB테이블을 조작해 봅니다.</p>
<p>**튜토리얼 코드 : <a href="https://github.com/Judy-Choi/rag_series/blob/main/note_3/main.ipynb">GitHub Rag Series</a></p>
<h2 id="langchain---document-타입">Langchain - Document 타입</h2>
<p>LangChain 에서 제공하는 PyPDFLoader 를 사용하여 PDF 문서를 로드하면
Langchain 만의 데이터 타입인 Document 타입으로 변환됩니다.</p>
<p>ChatGPT 는 다음과 같이 간결하게 설명하네요.</p>
<blockquote>
<p>langchain에서 사용하는 Document 타입은 자연어 처리 및 머신러닝 작업을 위해 데이터를 구조화하는 데 사용되는 기본적인 객체입니다. Document는 텍스트와 관련된 메타데이터를 포함할 수 있습니다.</p>
</blockquote>
<p>다음과 같이 1페이지당 1개의 &#39;Document&#39; 타입으로 변환되는데
다시 말해 로더는 각각의 페이지를 읽어서 각 페이지마다 Document object 를 생성하고,
이 Object 들을 하나의 리스트에 모아(묶어) 줍니다.</p>
<pre><code class="language-python">from langchain.document_loaders import PyPDFLoader

# PDF 파일 로드
loader = PyPDFLoader(&quot;../data/[일반보험]_KB개인상해보험_보험약관.pdf&quot;)
document = loader.load()</code></pre>
<p>Document 타입을 구성하는 요소는 아래와 같이 여러 가지가 있는데,</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/80e2704d-3cfc-4956-8998-4a74cbd04dbd/image.png" alt=""></p>
<p>다음 요소만 알아도 실전에서 데이터를 활용하는 데에 충분했습니다. (아직까지는..)</p>
<ul>
<li><code>page_content</code> : 문서의 실제 텍스트 내용을 담고 있습니다. 이 필드는 분석, 검색, 임베딩 등 다양한 작업에 사용됩니다.</li>
<li><code>metadata</code> : 문서에 대한 추가 정보를 포함하는 딕셔너리입니다. 예를 들어, 작성자, 날짜, 문서의 카테고리 등과 같은 메타데이터를 저장할 수 있습니다. 이 정보는 문서의 의미를 더 풍부하게 하고, 특정 작업에 유용하게 활용될 수 있습니다.</li>
<li><code>id (선택 사항)</code> : 문서를 고유하게 식별하는 ID입니다. 이 필드는 기본적으로 제공되지 않지만, 사용자 정의 ID를 추가할 수 있습니다.</li>
</ul>
<h2 id="vector-store-db">Vector Store DB</h2>
<p>Document 타입으로 변환된 데이터는 vector store 에 저장되며
Langchain Vector Store 의 기본 DB 구조는 다음과 같이 크게 2개의 테이블로 이루어집니다.</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/667c001f-6d81-4d09-a92d-06102136e3f2/image.png" alt=""></p>
<p>(<code>langchain_pg_collection</code> 테이블의 uuid 와
<code>langchain_pg_embedding</code> 테이블의 collection_id 로 서로 연결되어 있어서 같은 값을 가집니다.)</p>
<p><code>langchain_pg_collection</code> 테이블에는 문서의 전체적인 정보,
예를 들어 db collection name, 문서의 uuid 가 저장되고
<code>langchain_pg_embedding</code> 테이블에는 문서의 각 페이지 정보,
예를 들어 embedding, document, cmetadata 등이 저장됩니다.</p>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/6704b0d2-2eb0-454c-bf78-b8b95faa36fe/image.png" alt=""></p>
<ul>
<li><code>collection_id</code> : 문서의 uuid</li>
<li><code>document</code> : 문서 페이지의 텍스트</li>
<li><code>embedding</code> : <code>document</code>(=문서 페이지의 텍스트) 를 임베딩한 값</li>
<li><code>cmetadata</code> : 문서 페이지 정보 (기본값으로 페이지 정보와 문서 경로가 들어간다.)
만약 문서에 대해 추가적인 정보를 저장하거나 수정하고 싶다면 이 컬럼을 조작하면 된다.</li>
</ul>
<p>(custom_id 컬럼은 처음에는 없었는데 뭔가 작업을 하다 보니 생성되었다...!
SQL 작업을 하다 보면 custom_id 가 없어서 에러가 날 때가 있는데,
예제에서 설명할 langchain_pg_embedding 테이블에 컬럼 추가 + 데이터 넣어서 쓰려다가 custom_id 에러가 나서 실패했다.)</p>
<h2 id="db-테이블-활용-예제">DB 테이블 활용 예제</h2>
<p>벡터 스토어를 세팅하고 데이터를 로드하는 코드는 예제 모두 동일하게 사용합니다.</p>
<p>벡터 스토어 세팅</p>
<pre><code class="language-python">from langchain_postgres import PGVector
from langchain_postgres.vectorstores import PGVector
from langchain_openai import OpenAIEmbeddings

# See docker command above to launch a postgres instance with pgvector enabled.
# connection = f&quot;postgresql+psycopg2://user:password@host:5432/name&quot;,
connection=f&quot;postgresql+psycopg2://rag_note:rag_note@localhost:5433/rag_note&quot;
collection_name = &quot;my_db&quot;

vector_store = PGVector(
    embeddings=OpenAIEmbeddings(model=&quot;text-embedding-3-large&quot;),
    collection_name=collection_name,
    connection=connection,
    use_jsonb=True,
)</code></pre>
<p>데이터 로드</p>
<pre><code class="language-python">from langchain.document_loaders import PyPDFLoader

# PDF 파일 로드
loader = PyPDFLoader(&quot;../data/[일반보험]_KB개인상해보험_보험약관.pdf&quot;)
document = loader.load()
document[0].page_content[:200] # 내용 추출</code></pre>
<p>데이터 분할</p>
<pre><code class="language-python">from langchain.text_splitter import CharacterTextSplitter

text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=50)
texts = text_splitter.split_documents(document)</code></pre>
<h3 id="1-기본-임베딩-스토어-사용">1. 기본 임베딩 스토어 사용</h3>
<pre><code class="language-python">vector_store.add_documents(texts)</code></pre>
<p>Langchain 이 두 개의 테이블을 생성하고, embedding table 에 기본 컬럼을 생성한 뒤
다음과 같이 데이터를 넣어 줍니다.
<img src="https://velog.velcdn.com/images/judy_choi/post/6704b0d2-2eb0-454c-bf78-b8b95faa36fe/image.png" alt=""></p>
<h3 id="2-document-object-의-metadata-컬럼에-문서-정보-추가">2. Document object 의 metadata 컬럼에 문서 정보 추가</h3>
<p>기본적으로 cmetadata 컬럼에는 페이지 번호와 파일 경로만이 담겨 있는데,
추가적인 파일 정보를 저장하고 활용하기 위해 cmetadata 컬럼에 위해 보험의 상품명과 정보를 추가해 보겠습니다.</p>
<p>간단하게, 벡터 스토어에 저장하기 전의 Document object 를 조정하면 됩니다.</p>
<pre><code class="language-python">for text in texts:
    text.metadata[&#39;product&#39;] = &quot;KB개인상해보험&quot;
    text.metadata[&#39;info&#39;] = &quot;보험약관&quot;

# 위 for 문을 List Comprehension 으로 다음과 같이 한 줄로 쓸 수 있다!
# [text.metadata.update({&#39;product&#39;: &quot;KB개인상해보험&quot;, &#39;info&#39;: &quot;보험약관&quot;}) for text in texts]

vector_store.add_documents(texts)</code></pre>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/5a563b53-f11a-4e50-b29c-b085e5d7694f/image.png" alt=""></p>
<h3 id="3-document-object-의-metadata-컬럼에-문서-정보-추가-클래스-오버라이딩-이용">3. Document object 의 metadata 컬럼에 문서 정보 추가 (클래스 오버라이딩 이용)</h3>
<p>만약 실제 서비스를 개발하는 단계에서, 벡터 스토어에 CRUD 할 때 추가적인 작업을 매번 해야 한다면
예를 들어 2번 예제와 같이 특정 컬럼에 데이터를 반드시 추가해야 하는 등의 작업을 매번 해야 한다면
클래스 오버라이딩을 활용하는 편이 효율적일 수 있습니다.</p>
<ul>
<li>OverridingPGVector 클래스를 만들고, PGVector 클래스를 오버라이딩합니다.</li>
<li>_add_documents 함수를 만들고, 필요한 데이터 작업을 수행한 뒤에 add_documents 메소드를 호출합니다.<pre><code class="language-python">from langchain_community.vectorstores.pgvector import PGVector
from langchain_openai import OpenAIEmbeddings
from langchain_core.documents import Document
from sqlalchemy import text
from typing import Any
from overrides import overrides
</code></pre>
</li>
</ul>
<p>class OverridingPGVector(PGVector):</p>
<pre><code>@overrides
def __post_init__(self):
    super().__post_init__()

def _add_documents(self, documents: list[Document], **kwargs: Any) -&gt; list[str]:
    for doc in documents:
        doc.metadata[&#39;product&#39;] = &quot;KB개인상해보험&quot;
        doc.metadata[&#39;info&#39;] = &quot;보험약관&quot;

    # 위 for 문을 List Comprehension 으로 다음과 같이 한 줄로 쓸 수 있다!
    # [text.metadata.update({&#39;product&#39;: &quot;KB개인상해보험&quot;, &#39;info&#39;: &quot;보험약관&quot;}) for text in texts]

    return self.add_documents(documents, **kwargs)</code></pre><pre><code>
OverridingPGVector 객체를 생성합니다.
(함수 인자 이름이 약간 달라진 점에 주의합니다.)
```python
_vector_store = OverridingPGVector(
    # embeddings=OpenAIEmbeddings(model=&quot;text-embedding-3-large&quot;),
    embedding_function=OpenAIEmbeddings(model=&quot;text-embedding-3-large&quot;),
    collection_name=collection_name,
    # connection=connection,
    connection_string=connection,
    use_jsonb=True,
)</code></pre><pre><code class="language-python">_vector_store._add_documents(texts)</code></pre>
<p><img src="https://velog.velcdn.com/images/judy_choi/post/a6b49745-a35d-4d2e-9e8d-aeae0d4525ef/image.png" alt=""></p>
<h2 id="참고">참고</h2>
<p>vector_store.add_documents 메소드 실행 시 langchain_pg_embedding 테이블에 id 컬럼이 없어서 에러가 나는 경우가 있는데,
Langchain 의 버전 차이로 인해 uuid -&gt; id 를 참조하게 되어 발생하는 에러입니다.
아래 글을 참고해 주세요. (테이블을 지우고 새로 Langchain 코드를 실행하면 됩니다)
<a href="https://github.com/langchain-ai/langchain/discussions/21557">https://github.com/langchain-ai/langchain/discussions/21557</a></p>
<h2 id="tip--임베딩-테이블에-컬럼-추가하기">Tip : 임베딩 테이블에 컬럼 추가하기</h2>
<p>아래는 langchain_pg_embedding 테이블에 &#39;product&#39; 컬럼을 추가하는 예시 코드입니다.</p>
<pre><code class="language-python">class OverridingPGVector(PGVector):

    @overrides
    def __post_init__(self):
        super().__post_init__()
        self._add_embedding_culumn()

    def _add_embedding_culumn(self):
        with self._make_session() as session:
            # 컬럼 추가 SQL
            session.execute(text(f&quot;&quot;&quot;
                ALTER TABLE {self.EmbeddingStore.__tablename__}
                ADD COLUMN IF NOT EXISTS product varchar;
            &quot;&quot;&quot;))

            session.commit()</code></pre>
<p><code>__post__init__()</code> 생성자를 이용해 클래스가 호출되어 객체가 생성될 때 
_add_embedding_culumn 메소드가 자동으로 실행되어 product 컬럼이 테이블에 추가됩니다.
(깔끔하게 product 컬럼에 데이터를 넣는 코드는 못 짰습니다.. DB고수님들 헲미... ㅠㅠ)</p>
]]></description>
        </item>
    </channel>
</rss>