
토큰 소모가 아닌 기사 간의 인연을 시각화! Sakura AI Engine RAG로 챌린지 참여 기사의 상관도를 그려보았다🌸
요약
Sakura AI Engine의 RAG 기능을 활용하여 Qiita의 기술 기사 간 의미적 유사성을 분석하고 Mermaid 상관도로 시각화한 사례를 소개합니다. 단순한 답변 생성을 넘어, 문서 간의 코사인 거리를 측정하여 기사 간의 관계를 파악하는 실험적 접근을 다룹니다.
핵심 포인트
- Sakura AI Engine의 RAG API를 활용한 문서 간 유사도 측정
- Qiita API로 수집한 기사 데이터를 임베딩하여 상관도 생성
- RAG 사용 시 발생하는 벡터화 비용 및 문서 삭제를 통한 비용 관리 팁
- 단순 랭킹이 아닌 의미적 유사성에 기반한 '화제의 허브' 발견
TL;DR: 「Sakura AI 챌린지」의 Qiita 참여 기사를 Sakura AI Engine의 RAG(문서 검색)에 투입하여, 의미적으로 가까운 기사끼리 Mermaid 상관도로 연결해 보았습니다. 22건을 수집하고 이벤트 페이지 링크가 없는 10건을 제외한 나머지 12건을 RAG에 투입하여 15개의 에지(edge)가 생성되었으며, '좋아요' 수 랭킹과는 다른 '화제의 허브'가 보였습니다. 실행 후에는 RAG 상의 문서를 (임베딩이 완료되지 않은 분량을 포함하여) 삭제하여 유지 비용이 발생하지 않도록 하고 있습니다.
대상 독자: Sakura AI Engine으로 RAG를 실제로 시도해 보고 싶은 엔지니어, 기사나 문서 간의 '의미적 유사성'을 직접 손으로 움직여 시각화해 보고 싶은 사람. (주의: 기반 모델 무상 플랜에서도 RAG의 문서 보관 및 벡터화에는 무상 범위가 없으며 종량제 과금이 된다는 점에 유의하십시오. Chat Completions · 음성 · Embeddings에는 무상 범위가 있지만, RAG는 대상에서 제외됩니다. 참고로 계정 전체의 이번 달 embeddings 이용 요금은 (전일 시점 집계 기준) 3엔 · 603개 요청이었습니다. 이 집계는 전일까지의 반영이므로 이번 검증분은 아직 포함되지 않았을 가능성이 높지만, 그럼에도 소규모 검증이라면 금액적인 장벽은 낮아 보입니다)
RAG(Retrieval-Augmented Generation)는 LLM에 질문을 던지기 전 관련 있는 문서를 검색하여 그 내용도 함께 전달함으로써 답변의 근거를 넓히는 메커니즘이다. 사내 문서나 최신 정보와 같이 LLM이 원래 알지 못하는 지식을 답하게 하고 싶을 때 자주 사용된다.
이번에는 이 '관련 문서 검색'이라는 부분만을 빌려왔다. 검색을 통해 얻을 수 있는 거리(가까운지 먼지)를 답변 생성을 위해서가 아니라 "이 기사와 저 기사는 의미적으로 얼마나 가까운가"를 측정하기 위해 사용하고 있다. RAG의 '검색'을 기사 간의 인연을 측정하는 돋보기로 사용해 보았다는 것이 이번 취지다.
발단은 @torifukukaiou 님의 이 기사였다.
【매일 자동 업데이트】 Sakura AI Engine 3,000 요청 다 쓰기 챌린지 좋아요 랭킹!
Elixir/Nerves로 작성한 자동 업데이트 프로그램이 Qiita API를 통해 「Sakura AI 챌린지」의 참여 기사를 수집하고, 좋아요 수 랭킹이나 태그 집계를 매일 업데이트해 준다. 게다가 이 기사 자체가 캠페인 응모 작품이기도 하여, 「Sakura AI Engine 스스로가 응모하고 있는 구도」를 즐기고 있다. 이 중첩된 구도와 "token 소모가 아닌, 투혼 승화"라는 언어유희에 완전히 마음을 빼앗겨 버렸다.
랭킹이 기사의 열기를 보여준다면, 좋아요 수나 태그와는 다른 관점에서 참여 기사를 바라본다면 어떤 풍경이 펼쳐질까. 그렇게 생각하여, 본문의 의미적 유사성을 이용해 참여 기사의 상관도를 만들어 보기로 했다. 이것 또한 일종의 캠페인 참여 작품이 된다.
수행한 작업은 다음 5단계로 나뉜다.
- Qiita API v2로 「Sakura AI」 태그 기사를 수집하고, 이벤트 페이지 링크를 포함하는 기사로 압축
- 각 기사 본문을 Sakura AI Engine의 RAG 문서 API (
/v1/documents/upload/)로 업로드 (embedding 모델은multilingual-e5-large사용) - 임베딩 완료 (
status: available)를 기다린 후, 각 기사 본문을 사용하여/v1/documents/query/에 질의하여 코사인 거리(cosine distance)가 가까운 다른 기사를 검색 - 기사별로 가까운 상위 k건을 '에지(edge)'로 연결하여 Mermaid 상관도 생성
- 유지 비용을 피하기 위해 실행 후 RAG 문서는
/v1/documents/{id}/를 통해 DELETE로 삭제
직접 해보니 API 문서를 읽는 것만으로는 알아채기 어려운 점이 몇 가지 있었다.
첫 번째는, /v1/documents/query/가 기사 단위가 아니라 청크(chunk) 단위로 매칭을 반환한다는 점이었다. 긴 기사는 512 토큰마다 분할되어 업로드되기 때문에, 그대로 상위 k건을 가져오면 동일한 기사의 여러 청크로 채워지게 된다. 따라서 문서 ID(기사) 단위로 중복을 제거한 후, 기사 단위의 top-k를 선택하도록 했다. 다만 /v1/documents/query/ 자체가 반환하는 청크 수에도 상한이 있기 때문에, 중복 제거 후에 정확히 top_k개의 서로 다른 기사를 확보할 수 있다고 단정할 수는 없다. 실제로는 "상위 청크에서 중복 등을 제외하고 그 시점에서 찾아낸 인근 기사"라는 위치를 갖게 된다.
둘째는 임베딩 (Embedding) 처리가 비동기(Asynchronous)라는 점이다. 업로드 직후에는 아직 검색 대상이 되지 않기 때문에, 문서 목록 API의 status가 available이 될 때까지 폴링(Polling)하며 기다리는 구조가 필요했다.
셋째는 Mermaid 라벨의 이스케이프 (Escape) 처리다. 기사 제목을 그대로 노드의 라벨로 사용하면 [나 ] , " 같은 기호가 구문을 깨뜨릴 수 있다. 표시용 라벨에 대해서만 기호를 치환해 두어야 했다.
참고로, 기사 간의 근접도를 측정하는 쿼리(Query)에는 본문 전체가 아니라 「제목 + 본문 도입부 500자」를 사용하고 있다. 임베딩 대상(RAG에 업로드되는 청크 측)은 본문 전체이지만, 비교의 기점이 되는 쿼리 측은 도입부로 한정된다는 비대칭적인 비교가 이루어지고 있다는 점은 유의해 두어야 한다.
나아가, 기사 A에서 기사 B로의 쿼리 결과와 기사 B에서 기사 A로의 쿼리 결과는 원리적으로 서로 다른 거리가 될 수 있다 (방향에 따라 다른 청크가 히트하기 때문). 상관도에서는 양방향 후보가 모두 얻어진 경우, 그중 작은 값(=더 가깝다고 판정된 쪽)을 채택하여 A-B 사이의 무방향 에지(Edge) 1개로 통합했다. 즉, 표시되는 거리는 「A와 B의 본문 사이의 대칭적인 거리」가 아니라, 「어느 한쪽 가까운 방향에서 관측된 거리의 최솟값」이다.
수집된 참가 기사는 22건이다. 그중 이벤트 페이지로의 링크를 포함하지 않는 10건을 새니티 필터(Sanity Filter)로 제외하고, 남은 12건을 RAG에 투입했다 (업로드 실패는 0건). 생성된 에지는 15개였다. 수치는 각 에지의 코사인 거리(Cosine Distance)이며, 작을수록 의미적으로 가깝다.
| 기사 제목 | URL | 좋아요 수 | 차수 (Degree) |
|---|---|---|---|
| Laravel×さくらのAI Engineで自分専用の記事下書きツールを作る。 | https://qiita.com/miyacha/items/f4db3964afad5142113e | 1 | 2 |
| ... |
동일한 상관도를 원의 크기·농도(차수)와 선의 굵기(근접도)로 보기 쉽게 만든 이미지는 다음과 같다.
드래그나 호버(Hover)를 통해 각 기사의 제목·좋아요 수·차수를 확인할 수 있는 인터랙티브(Interactive) 버전도 만들었다 (GitHub 상에서는 실행할 수 없으므로, 다운로드하여 브라우저로 열어주길 바란다): correlation_network_interactive.html
가장 많은 기사와 연결된 것은 좋아요 0·차수 7인 「Playground에 수상 팁을 물어보았습니다」였다. Playground, 무료 티어, 캠페인 공략이라는 화제는 기술 스택과 관계없이 다른 기사들과도 공명하기 쉬운 것일지도 모른다. 「매일 자동 업데이트되는 좋아요 랭킹」 기사도 차수 5로, 또 다른 허브(Hub) 역할을 하고 있다. 둘 다 특정 API 용도라기보다 캠페인 그 자체를 화제로 삼은 메타적인 기사들끼리 가까워지는 것은 생각해보면 자연스러운 결과다.
반면, 좋아요 10인 「gpt-oss-120b를 사용하기」 기사는 차수가 2에 그쳤다. 인기와 의미적인 연결 수는 별개의 지표인 듯하다. 「즈んだもん(Zundamon)」이 읽어주는 기사나, Playground에 기사를 써달라고 한 기사는 차수가 1로, 음성 합성이나 시연 소재라는 내용의 독자성이 선의 적음으로 그대로 나타나 있는 것처럼 보인다.
좋아요 수(가로축)와 차수(세로축)를 나란히 놓고 보면, 이 「별개의 지표 같음」이 명확해진다.
부수적으로, 기사 본문 대신 Sakura AI Engine의 각 기능을 설명한 문장(기능명 그 자체가 아니라, 기능의 내용을 수십~백여 자로 설명한 텍스트)을 RAG에 쿼리해보는 실험도 진행해 보았다.
| 기능 | 가까웠던 기사 (코사인 거리) |
|---|---|
| Chat Completions | gpt-oss-120b를 사용하는 기사(0.11), PowerShell 첫 API 호출 기사(0.12), Moodle 기사(0.12) |
| ... |
기사 대 기사의 비교뿐만 아니라, 관심 있는 기능의 설명문을 통해 기사를 찾아가는 입구로서도 RAG는 유용해 보인다.
환경 변수 SAKURA_AI_ACCOUT_KEY에 Sakura AI Engine에서 발행한 API 키를 설정하고 (ACCOUT은 오타로 보이지만, 이 스크립트가 읽어들이는 변수명으로서 그대로 사용), pip install requests를 한 뒤 실행할 수 있다.
#!/usr/bin/env python3
"""Qiita의 태그 검색 결과를 Sakura AI Engine의 RAG(문서 검색)에 투입하여,
기사 간의 유사도로 상관도(Mermaid)를 만드는 샘플 스크립트.
...
실행 다음 날, 이용 상황 대시보드를 다시 확인해 보니 이용 요금은 3엔 그대로였다. 이 결과를 보아하니, 삭제하더라도 과금 대상에서는 벗어나지 않았을 가능성이 높아 보인다. 아마도 해당 월에 저장한 최대 청크 (Chunk) 수에 대해 과금되는 구조가 아닌가 하고 지금은 추측하고 있다. 정확한 과금 규칙은 공식적으로 확인되지 않았으므로, 계속해서 관찰해 나가고 싶다.
좋아요 랭킹이 기사의 열기를 보여주는 한편, RAG의 상관도는 기사들 사이에 흐르는 공통된 화제를 떠오르게 해주었다. 임의의 기능 설명문을 쿼리 (Query)로 사용한다면, '음성 읽기에 가까운 기사', '호환 API에 가까운 기사'와 같이 또 다른 입구도 만들 수 있을 것 같다.
기사가 더욱 늘어났을 때, 지금의 허브 (Hub)는 허브로 남아 있을까? 아니면 새로운 테마를 중심으로 또 다른 가지가 뻗어 나갈까? 3,000개의 요청 (Request)을 단순히 소모하는 것이 아니라, 기사 간의 인연으로 승화시켜 보는 것도 나쁘지 않은 선택일지도 모른다.
- さくらのAI Engine (서비스 페이지)
- 「さくらのAI Engine」 3,000리퀘스트 다 쓰기 챌린지 (Qiita 공식 이벤트 페이지) — 본 기사도 이 캠페인에 참여한 작품
- さくらのAI Engine 문서 · RAG API 사용법 (공식 매뉴얼) — 본 기사의 코드가 사용 중인
/v1/documents/upload/,/v1/documents/query/등의 API 사양 - 【매일 자동 업데이트】 さくらのAI Engine 3,000리퀘스트 다 쓰기 챌린지 좋아요 랭킹! (@torifukukaiou 님, 영감을 준 원문 기사)
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기