chore(ko): add Korean locale to documentation site (#2173)

This commit is contained in:
mullung
2026-08-02 18:43:46 +08:00
committed by GitHub
parent cfd9c26f09
commit 771ba3f8a7
57 changed files with 6785 additions and 1 deletions
+1 -1
View File
@@ -180,7 +180,7 @@ export function usePrevNext() {
* Keeps navigation within the same section to avoid crossing into unrelated docs.
*/
function getDocsSectionPrefix(fullUrl: string): string | undefined {
const m = fullUrl.match(/\/(en|zh-Hans)\/docs\/([^/]+)\//)
const m = fullUrl.match(/\/(en|zh-Hans|ja|ko)\/docs\/([^/]+)\//)
return m ? `/${m[1]}/docs/${m[2]}/` : undefined
}
+137
View File
@@ -604,6 +604,143 @@ export default defineConfig<ThemeConfig>({
},
},
},
'ko': {
label: '한국어',
lang: 'ko',
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
{ text: '문서', link: withBase('/ko/docs/overview/') },
{ text: '블로그', link: withBase('/ko/blog/') },
{
text: `v${version}`,
items: [
{ text: '릴리스 노트', link: releases },
],
},
{
text: '소개',
items: [
{ text: '개인정보 처리방침', link: withBase('/ko/about/privacy') },
{ text: '이용약관', link: withBase('/ko/about/terms') },
],
},
],
outline: {
level: 'deep',
label: '이 페이지의 내용',
},
docFooter: {
prev: '이전 페이지',
next: '다음 페이지',
},
editLink: {
pattern: 'https://github.com/moeru-ai/airi/edit/main/docs/content/:path',
text: 'GitHub에서 이 페이지 편집하기',
},
lastUpdated: {
text: '마지막 업데이트',
},
darkModeSwitchLabel: '테마',
sidebarMenuLabel: '메뉴',
returnToTopLabel: '맨 위로',
langMenuLabel: '언어 변경',
logo: withBase('/favicon.svg'),
sidebar: [
{
text: '개요',
icon: 'lucide:rocket',
items: [
{ text: '소개', link: withBase('/ko/docs/overview/') },
{ text: '버전과 다운로드', link: withBase('/ko/docs/overview/versions') },
{ text: 'AI VTuber 란', link: withBase('/ko/docs/overview/about-ai-vtuber') },
{ text: 'Neuro-sama 란', link: withBase('/ko/docs/overview/about-neuro-sama') },
{ text: '비슷한 다른 프로젝트들', link: withBase('/ko/docs/overview/other-similar-projects') },
],
},
{
text: '사용 설명서',
icon: 'lucide:book-open',
items: [
{
text: '빠른 시작',
items: [
{ text: '데스크톱 버전', link: withBase('/ko/docs/manual/tamagotchi/') },
{ text: '웹 버전', link: withBase('/ko/docs/manual/web/') },
],
},
{ text: '설치와 사용', link: withBase('/ko/docs/manual/tamagotchi/setup-and-use/') },
{
text: '설정',
items: [
{ text: '설정 가이드', link: withBase('/ko/docs/manual/config/') },
],
},
],
},
{
text: '기여하기',
icon: 'lucide:users',
items: [
{
text: '기본 설정',
items: [
{ text: '개발 환경 설정과 사전 준비', link: withBase('/ko/docs/contributing/') },
{ text: '데스크톱 앱', link: withBase('/ko/docs/contributing/tamagotchi') },
{ text: '웹 UI', link: withBase('/ko/docs/contributing/webui') },
{ text: '문서 사이트', link: withBase('/ko/docs/contributing/docs') },
],
},
{
text: '게임 & 소셜 플랫폼',
items: [
{ text: 'Minecraft', link: withBase('/ko/docs/contributing/services/minecraft') },
{ text: 'Satori 봇', link: withBase('/ko/docs/contributing/services/satori') },
{ text: 'Telegram 봇', link: withBase('/ko/docs/contributing/services/telegram') },
{ text: 'Discord 봇', link: withBase('/ko/docs/contributing/services/discord') },
],
},
{
text: '디자인 가이드라인',
items: [
{ text: '소개', link: withBase('/ko/docs/contributing/design-guidelines/') },
{ text: '아티스트 & 개발자 (참고 자료)', link: withBase('/ko/docs/contributing/design-guidelines/resources') },
{ text: '도구', link: withBase('/ko/docs/contributing/design-guidelines/tools') },
],
},
],
},
{
text: '연대기',
icon: 'lucide:calendar-days',
items: [
{ text: '첫 공개 v0.1.0', link: withBase('/ko/docs/chronicles/version-v0.1.0/') },
{ text: '그 이전 이야기 v0.0.1', link: withBase('/ko/docs/chronicles/version-v0.0.1/') },
],
},
] as (DefaultTheme.SidebarItem & { icon?: string })[],
homepage: {
buttons: [
{
text: '라이브 데모 체험하기',
link: webLive,
primary: true,
target: '_self',
},
{
text: '다운로드',
link: withBase('/ko/docs/overview/versions'),
},
{
text: '시작하기',
link: withBase('/ko/docs/overview/'),
},
],
},
},
},
},
themeConfig: {
socialLinks: [
+86
View File
@@ -0,0 +1,86 @@
---
title: 개인정보 처리방침
description: Project AIRI 의 개인정보 처리방침
---
**개인정보 처리방침**
본 개인정보 처리방침은 Project AIRI 팀(이하 "서비스 제공자")이 오픈소스 서비스이자 일부 유료 상용 서비스로 제작한 모바일 기기용 애플리케이션 AIRI(Project AIRI 앱이라고도 하며, 이하 "애플리케이션")에 적용됩니다. 본 서비스는 "있는 그대로(AS IS)" 사용되는 것을 전제로 합니다.
**정보의 수집과 이용**
애플리케이션은 귀하가 이를 내려받아 사용할 때 정보를 수집합니다. 수집되는 정보에는 다음과 같은 항목이 포함될 수 있습니다.
* 기기의 인터넷 프로토콜 주소(예: IP 주소)
* 방문한 애플리케이션 내 페이지, 방문 시각과 날짜, 해당 페이지에 머문 시간
* 애플리케이션 사용 시간
* 모바일 기기에서 사용하는 운영체제
애플리케이션은 모바일 기기의 정밀한 위치 정보를 수집하지 않습니다.
애플리케이션은 기기의 위치 정보를 수집하며, 이는 서비스 제공자가 대략적인 지리적 위치를 파악하는 데 도움을 주고 다음과 같은 방식으로 활용됩니다.
* 위치 기반 서비스: 서비스 제공자는 위치 데이터를 활용해 위치 기반 게임·엔터테인먼트 서비스, 개인화되지 않은 콘텐츠, 관련 추천 등의 기능을 제공합니다.
* 분석과 개선: 집계 및 익명화된 위치 데이터는 서비스 제공자가 사용자 행동을 분석하고, 추세를 파악하며, 애플리케이션의 전반적인 성능과 기능을 개선하는 데 도움이 됩니다.
* 서드파티 서비스: 서비스 제공자는 주기적으로 익명화된 위치 데이터를 외부 서비스로 전송할 수 있습니다. 이러한 서비스는 애플리케이션을 개선하고 제공 서비스를 최적화하는 데 도움을 줍니다.
서비스 제공자는 귀하가 제공한 정보를 이용해 중요한 정보, 필수 고지 사항 및 마케팅 홍보를 전달하기 위해 수시로 연락할 수 있습니다.
더 나은 경험을 제공하기 위해, 애플리케이션 사용 중 서비스 제공자는 이메일, 사용자 ID, 설정된 AI/LLM/클라우드 컴퓨팅 제공자 이름(비밀 정보 제외), 에이전트·MCP·도구 호출 사용 내역, AI 기술 관련 트레이스, 오류 등을 포함하되 이에 국한되지 않는 특정 개인 식별 정보를 제공하도록 요청할 수 있습니다.
서비스 제공자가 요청한 정보는 서비스 제공자가 보관하며 본 개인정보 처리방침에 기술된 대로 이용됩니다.
**제3자 접근**
애플리케이션과 서비스 개선을 돕기 위해 집계 및 익명화된 데이터만 주기적으로 외부 서비스로 전송됩니다. 서비스 제공자는 본 개인정보 처리방침에 기술된 방식으로 귀하의 정보를 제3자와 공유할 수 있습니다.
애플리케이션은 데이터 처리에 관한 자체 개인정보 처리방침을 가진 서드파티 서비스를 이용한다는 점에 유의해 주세요. 아래는 애플리케이션이 사용하는 서드파티 서비스 제공자의 개인정보 처리방침 링크입니다.
* [Posthog](https://posthog.com/privacy)
* [Plausible Analytics](https://plausible.io/privacy)
서비스 제공자는 다음의 경우 사용자가 제공한 정보와 자동으로 수집된 정보를 공개할 수 있습니다.
* 소환장 또는 이와 유사한 법적 절차를 따르는 등 법률상 요구되는 경우
* 자신의 권리를 보호하거나, 귀하 또는 타인의 안전을 보호하거나, 사기를 조사하거나, 정부의 요청에 대응하기 위해 공개가 필요하다고 선의로 판단하는 경우
* 서비스 제공자를 대신해 업무를 수행하고, 공개된 정보를 독자적으로 이용하지 않으며, 본 개인정보 처리방침에 명시된 규칙을 준수하기로 동의한 신뢰할 수 있는 서비스 제공업체와 공유하는 경우
**인공지능의 이용**
애플리케이션은 사용자 경험을 향상시키고 특정 기능을 제공하기 위해 인공지능(AI) 기술을 이용합니다. AI 구성 요소는 개인화된 콘텐츠, 추천 또는 자동화된 기능을 제공하기 위해 사용자 데이터를 처리할 수 있습니다.
모든 AI 처리는 본 개인정보 처리방침과 관련 법률에 따라 수행됩니다. AI 기능이나 데이터 처리에 대해 궁금한 점이 있으면 서비스 제공자에게 문의해 주세요.
**수집 거부 권리**
애플리케이션을 삭제하면 애플리케이션에 의한 모든 정보 수집을 손쉽게 중단할 수 있습니다. 모바일 기기 자체 또는 모바일 애플리케이션 마켓·네트워크에서 제공하는 표준 삭제 절차를 이용하시면 됩니다.
**데이터 보관 정책**
서비스 제공자는 귀하가 애플리케이션을 사용하는 동안, 그리고 그 이후 합리적인 기간 동안 사용자가 제공한 데이터를 보관합니다. 애플리케이션을 통해 제공한 데이터의 삭제를 원하시면 airi@moeru.ai 로 연락해 주세요. 합리적인 기간 내에 답변드리겠습니다.
**아동**
서비스 제공자는 애플리케이션을 통해 만 13세 미만 아동의 데이터를 의도적으로 요청하거나 아동을 대상으로 마케팅하지 않습니다.
애플리케이션은 만 13세 미만을 대상으로 하지 않습니다. 서비스 제공자는 만 13세 미만 아동으로부터 개인 식별 정보를 의도적으로 수집하지 않습니다. 만 13세 미만 아동이 개인정보를 제공한 사실을 서비스 제공자가 알게 된 경우, 해당 정보를 서버에서 즉시 삭제합니다. 귀하가 부모 또는 보호자이고 자녀가 개인정보를 제공한 사실을 알게 되었다면, 서비스 제공자(airi@moeru.ai)에게 연락해 필요한 조치를 취할 수 있도록 해 주세요.
**보안**
서비스 제공자는 귀하의 정보를 기밀로 보호하는 데 관심을 기울이고 있습니다. 서비스 제공자는 처리하고 보관하는 정보를 보호하기 위해 물리적·전자적·절차적 안전장치를 제공합니다.
**변경**
본 개인정보 처리방침은 어떠한 이유로든 수시로 갱신될 수 있습니다. 서비스 제공자는 새로운 개인정보 처리방침으로 이 페이지를 갱신하는 방식으로 변경 사항을 알려드립니다. 변경 사항이 있는지 본 개인정보 처리방침을 정기적으로 확인하시기를 권장하며, 계속해서 서비스를 이용하는 것은 모든 변경 사항에 동의한 것으로 간주됩니다.
본 개인정보 처리방침은 2025-12-15 부터 효력이 발생합니다.
**동의**
애플리케이션을 사용함으로써 귀하는 현재 및 향후 개정될 본 개인정보 처리방침에 명시된 대로 정보가 처리되는 것에 동의하게 됩니다.
**문의하기**
애플리케이션 사용 중 개인정보와 관련한 질문이 있거나 처리 방식에 대해 궁금한 점이 있으시면 airi@moeru.ai 로 서비스 제공자에게 이메일을 보내 주세요.
* * *
이 개인정보 처리방침 페이지는 [App Privacy Policy Generator](https://app-privacy-policy-generator.nisrulz.com/) 로 생성되었습니다.
+43
View File
@@ -0,0 +1,43 @@
---
title: 이용약관
description: Project AIRI 의 이용약관
---
본 이용약관은 Project AIRI 팀(이하 "서비스 제공자")이 오픈소스 서비스이자 일부 유료 상용 서비스로 제작한 모바일 기기용 애플리케이션 AIRI(Project AIRI 앱이라고도 하며, 이하 "애플리케이션")에 적용됩니다.
애플리케이션을 다운로드하거나 이용하는 순간 아래 약관에 자동으로 동의하는 것으로 간주됩니다. 애플리케이션을 사용하기 전에 본 약관을 충분히 읽고 이해하실 것을 강력히 권장합니다.
서비스 제공자는 애플리케이션이 최대한 유익하고 효율적이도록 하기 위해 노력하고 있습니다. 이를 위해 서비스 제공자는 언제든지 어떠한 이유로든 애플리케이션을 수정하거나 서비스에 요금을 부과할 권리를 보유합니다. 애플리케이션 또는 그 서비스에 대한 요금이 발생하는 경우 서비스 제공자는 이를 명확히 안내해 드릴 것을 약속합니다.
애플리케이션은 서비스 제공을 위해 귀하가 서비스 제공자에게 제공한 개인 데이터를 저장하고 처리합니다. 휴대전화의 보안과 애플리케이션에 대한 접근을 안전하게 유지하는 것은 귀하의 책임입니다. 서비스 제공자는 기기의 공식 운영체제가 부과한 소프트웨어 제한을 제거하는 행위, 즉 탈옥(jailbreaking)이나 루팅(rooting)을 하지 않을 것을 강력히 권고합니다. 이러한 행위는 휴대전화를 멀웨어, 바이러스, 악성 프로그램에 노출시키고 기기의 보안 기능을 무력화할 수 있으며, 애플리케이션이 정상적으로 동작하지 않거나 아예 동작하지 않는 결과를 초래할 수 있습니다.
애플리케이션은 자체 이용약관을 가진 서드파티 서비스를 이용한다는 점에 유의해 주세요. 아래는 애플리케이션이 사용하는 서드파티 서비스 제공자의 이용약관 링크입니다:
* [Posthog](https://posthog.com/terms)
* [Plausible Analytics](https://plausible.io/terms)
서비스 제공자가 책임지지 않는 영역이 있다는 점에 유의해 주세요. 애플리케이션의 일부 기능은 활성화된 인터넷 연결(Wi-Fi 또는 이동통신사가 제공하는 연결)을 필요로 합니다. Wi-Fi 에 접속할 수 없거나 데이터 허용량을 모두 소진하여 애플리케이션이 완전한 성능을 발휘하지 못하는 경우에 대해서는 서비스 제공자가 책임지지 않습니다.
Wi-Fi 영역 밖에서 애플리케이션을 사용하는 경우, 이동통신사의 약관이 그대로 적용된다는 점에 유의해 주세요. 따라서 애플리케이션 접속 중 데이터 사용에 대해 이동통신사로부터 요금이 청구되거나 기타 서드파티 요금이 발생할 수 있습니다. 애플리케이션을 사용함으로써 귀하는 그러한 요금에 대한 책임을 지며, 여기에는 데이터 로밍을 끄지 않은 채 거주 지역(즉 지역 또는 국가) 밖에서 애플리케이션을 사용할 때 발생하는 로밍 데이터 요금이 포함됩니다. 애플리케이션을 사용하는 기기의 요금 납부자가 귀하가 아닌 경우, 서비스 제공자는 귀하가 요금 납부자로부터 허락을 받았다고 간주합니다.
마찬가지로 서비스 제공자가 귀하의 애플리케이션 사용 방식 전반에 대해 항상 책임을 질 수 있는 것은 아닙니다. 예를 들어 기기가 충전된 상태를 유지하도록 하는 것은 귀하의 책임입니다. 기기의 배터리가 소진되어 서비스에 접근할 수 없게 되더라도 서비스 제공자는 이에 대해 책임지지 않습니다.
애플리케이션 사용과 관련한 서비스 제공자의 책임과 관련하여, 서비스 제공자는 애플리케이션이 항상 최신이고 정확하도록 노력하지만 귀하에게 제공할 정보를 얻기 위해 서드파티에 의존하고 있다는 점을 유의해 주세요. 서비스 제공자는 귀하가 애플리케이션의 이러한 기능에 전적으로 의존함으로써 발생하는 직간접적 손실에 대해 어떠한 책임도 지지 않습니다.
애플리케이션은 특정 기능이나 서비스를 제공하기 위해 인공지능(AI) 기술을 활용합니다. 애플리케이션을 사용함으로써 귀하는 데이터 처리 및 기능 제공에 AI 가 사용될 수 있음을 인지하고 이에 동의합니다. 서비스 제공자는 모든 AI 사용이 관련 법률을 준수하며 사용자 경험에 도움이 되도록 설계되었음을 보증합니다.
서비스 제공자는 어느 시점에 애플리케이션을 업데이트하고자 할 수 있습니다. 현재 애플리케이션은 운영체제(및 서비스 제공자가 지원을 확대하기로 결정한 추가 시스템)의 요구 사항에 맞추어 제공되며, 이러한 요구 사항은 변경될 수 있습니다. 계속해서 애플리케이션을 사용하려면 업데이트를 내려받아야 합니다. 서비스 제공자는 애플리케이션이 항상 귀하에게 적합하도록, 또는 귀하의 기기에 설치된 특정 운영체제 버전과 호환되도록 업데이트할 것을 보장하지 않습니다. 다만 귀하는 업데이트가 제공될 때 이를 항상 수락하는 데 동의합니다. 서비스 제공자는 또한 애플리케이션 제공을 중단하고자 할 수 있으며, 사전 통지 없이 언제든지 그 사용을 종료할 수 있습니다. 별도로 안내하지 않는 한, 종료 시점에 (a) 본 약관에 따라 귀하에게 부여된 권리와 라이선스는 소멸하며, (b) 귀하는 애플리케이션 사용을 중단하고 (필요한 경우) 기기에서 삭제해야 합니다.
## 본 이용약관의 변경
서비스 제공자는 이용약관을 주기적으로 갱신할 수 있습니다. 따라서 변경 사항이 있는지 이 페이지를 정기적으로 확인하시기를 권장합니다. 서비스 제공자는 변경된 새 이용약관을 이 페이지에 게시하는 방식으로 변경 사항을 알려드립니다.
본 이용약관은 2025-12-15 부터 효력이 발생합니다.
## 문의하기
이용약관에 대해 질문이나 제안이 있으시면 언제든지 airi@moeru.ai 로 서비스 제공자에게 연락해 주세요.
* * *
이 이용약관 페이지는 [App Privacy Policy Generator](https://app-privacy-policy-generator.nisrulz.com/) 로 생성되었습니다.
@@ -0,0 +1,101 @@
---
title: DevLog @ 2025.03.05
category: DevLog
date: 2025-03-05
---
## 데자뷔
어제는 WebGPU 작업을 돕기 위해 [`gpuu` (GPU utilities)](https://github.com/moeru-ai/gpuu)
라는 패키지를 하나 추가했습니다. 어쩌면 이걸로 실제 GPU 장치와도 상호작용할 수 있을 겁니다.
지금은 할 수 있는 게 그리 많지 않지만 앞으로 기능을 더 추가할 예정입니다.
이런 느낌입니다:
```ts
import { check } from 'gpuu/webgpu'
import { onMounted } from 'vue'
onMounted(async () => {
const result = await check()
console.info(result)
// 결과로 무언가를 한다
})
```
지난주에는 저희 협력 디자이너/아티스트가 Project AIRI 로고의 기본/첫 버전 커미션을
제출해 주었습니다. 로고의 느낌은 이렇습니다:
![](/en/blog/DevLog-2025.03.05/assets/airi-logos-v1.avif)
## 낮 시간
디자인 관점에서 보면 이 로고들은 홈 화면 앱 크기로 줄였을 때 너무 복잡하고
사용자 친화적이지 않았습니다. 그래서 이런 버전을 추가했습니다:
![](/en/blog/DevLog-2025.03.05/assets/airi-logo-v2.avif)
그리고 다른 변형들도 만들어 봤습니다:
![](/en/blog/DevLog-2025.03.05/assets/airi-logos-v2.avif)
이들은 전부 다크 테마에만 어울렸습니다. "다크 테마용 로고도 필요하잖아!" 라는 생각이 들어
이렇게 만들었습니다:
![](/en/blog/DevLog-2025.03.05/assets/airi-logo-v2-dark.avif)
[@kwaa](https://github.com/kwaa) 가 두 테마의 색 구성을 서로 바꿔 보자고 제안했습니다:
![](/en/blog/DevLog-2025.03.05/assets/airi-logos-v3.avif)
확실히 더 나아 보이네요.
타이포그래피도 업데이트했습니다:
![](/en/blog/DevLog-2025.03.05/assets/airi-logos-v4.avif)
그리고 배경색을 다듬었습니다:
![](/en/blog/DevLog-2025.03.05/assets/airi-logos-v5.avif)
그래서 최종적으로 나온 결과가 이겁니다:
![](/en/blog/DevLog-2025.03.05/assets/airi-logos-final.avif)
오늘 늦게는 Project AIRI 의 [문서 사이트](https://airi.build)를 온라인에 배포하는 작업을 했습니다.
저와 다른 개발자, 아티스트들이 참고 자료와 가이드라인으로 쓸 수 있도록요.
해냈습니다! 새로 디자인한 로고를 컬러 팔레트와 함께 [문서 사이트](https://airi.build)에 올렸습니다:
![](/en/blog/DevLog-2025.03.05/assets/airi-build-light.avif)
![](/en/blog/DevLog-2025.03.05/assets/airi-build-dark.avif)
[기본 가이드](../guides/),
[기여 가이드라인](../references/contributing/guide/),
[디자인 가이드라인](../references/design-guidelines/)
이 이 시점부터 모두 포함됐습니다.
점심 내내 YouTube 의 Text PV 애니메이션을 감상하며 감을 잡았습니다.
정말 좋아하는 스타일이라, 브라우저에서도 비슷한 전환 효과를 구현할 수 있으면 좋겠습니다!
https://www.youtube.com/watch?v=_AIgv0EsOE4
다행히 이걸 정말 잘하는 개발자이자 아티스트를 알고 있습니다:
[yui540](https://github.com/yui540) (개인 사이트는 여기서 볼 수 있습니다: [yui540.com](https://yui540.com)).
마침 자신이 사용한 환상적인 전환 효과를 보여 주는 새 저장소를 막 공개했더군요.
관련 자료와 웹사이트 링크를 [https://airi.build](https://airi.build) 사이트에 추가해 두었으니 확인해 보세요.
## DevStream
[yui540](https://github.com/yui540) 의 [저장소](https://github.com/yui540/css-animations)에 있는
애니메이션 전환을 상당수 [https://proj-airi-packages-ui-transitions.netlify.app/#/](https://proj-airi-packages-ui-transitions.netlify.app/#/)
로 포팅했습니다.
정말 잘 동작했습니다:
![](/en/blog/DevLog-2025.03.05/assets/animation-transitions.gif)
오늘의 DevLog 는 여기까지입니다. DevStream 에 참여해 끝까지 함께해 주신 모든 분께 감사드립니다.
내일 또 만나요.
@@ -0,0 +1,81 @@
---
title: DevLog @ 2025.03.06
category: DevLog
date: 2025-03-06
---
## 데자뷔
전날에는 DevStream 에서 AIRI 의 기본 애니메이션과 전환 효과를 만드는 진행 상황을
보여 드렸습니다.
목표는 [@yui540](https://yui540.com/) 의 멋진 작업을 어떤 Vue 프로젝트에서도 쓸 수 있는
재사용 가능한 Vue 컴포넌트로 이식하고 다듬는 것입니다.
> yui540 과 참고한 라이브러리·작업물에 대한 상세 내용은 새로 배포한 문서 사이트
> [https://airi.build/references/design-guidelines/resources/](../references/design-guidelines/resources/)
> 에 이미 정리해 두었습니다.
결과는 꽤 좋고, 이미
[https://proj-airi-packages-ui-transitions.netlify.app/#/](https://proj-airi-packages-ui-transitions.netlify.app/#/) 에 배포되어 있습니다.
![](/en/blog/DevLog-2025.03.06/assets/animation-transitions.gif)
> 그리고 앞으로 각 패키지의 플레이그라운드는 Netlify 배포 시
> "proj-airi" + "${subDirectory}" + "$packageName}" 패턴을 사용합니다.
전날의 목표가 CSS 구현을 Vue 컴포넌트로 분리하는 것이었다면, 실제로 재사용 가능하게 만드는
부분은 아직 끝나지 않았습니다. 다른 페이지들도 쓸 수 있도록 확장 가능하고 유연한 워크플로와
메커니즘을 설계해야 합니다.
## 낮 시간
[`unplugin-vue-router`](https://github.com/posva/unplugin-vue-router) 의
[`definePage`](https://uvr.esm.is/guide/extending-routes.html#definepage) 매크로 훅을 실험해 봤는데,
제 상황에 꽤 잘 맞아서 이 방향으로 가기로 했습니다.
그리고 [https://cowardly-witch.netlify.app/](https://cowardly-witch.netlify.app/) 에서
새 애니메이션 전환 3개를 추가로 포팅했고, 이미
[https://proj-airi-packages-ui-transitions.netlify.app/#/](https://proj-airi-packages-ui-transitions.netlify.app/#/) 에서 볼 수 있습니다.
어제 공식 문서 사이트를 [https://airi.build](https://airi.build) 에 배포했더니
[@kwaa](https://github.com/kwaa) 가 대신 `https://airi.more.ai/docs` 방식을 써 보라고 제안했습니다.
~~그런데 /docs 에 대한 200 리다이렉트 프록시를 만드는 방법을 못 찾았습니다.~~
수정: 결국 알아냈습니다. 방법은 앞으로의 DevLog 에서 자세히 다루겠습니다.
CI/CD 파이프라인과 싸우며(네, 또 싸웠습니다) 커밋 열 개쯤 날리며 실험해 봤지만 아직 동작하지 않습니다.
이날 늦게는 DeepSeek 팀이 일주일 전 공개한 몇몇 기술과
[오픈소스 저장소](https://github.com/deepseek-ai/open-infra-index)들, 그리고 ByteDance 가 공개했다는
[LLM 게이트웨이 AIBrix](https://github.com/vllm-project/aibrix) 를 살펴봤습니다.
새로 발표된 Phi-4-mini 를 AIRI 에 이식해 쓸 수 있을지도 조사했는데, 좋은 소식은
[Phi-4-mini](https://techcommunity.microsoft.com/blog/educatordeveloperblog/welcome-to-the-new-phi-4-models---microsoft-phi-4-mini--phi-4-multimodal/4386037)
가 함수 호출 능력을 포함하고 있다는 것입니다. 즉 사전 학습된 지원을 바탕으로 에이전트를
만들 수 있다는 뜻이죠.
## DevStream
오후에는 다른 아티스트에게 연락해서, 앞으로 새로 만들 계정들의 아바타로 쓸 맞춤 픽셀 아트
커미션 비용을 지불할 의향이 있다고 전했습니다.
~~네, 아티스트에게 이스터에그를 좀 넣어 달라고 부탁했습니다 하하. 찾아내시길 바랍니다.~~
라이브 스트림의 레이아웃과 세팅을 업데이트했습니다 😻 거의 1년 전에 제가 직접 디자인한 건데,
지금 봐도 훌륭하고 마음이 차분해집니다. 제안이 있으시면 채팅에 남겨 주세요. 정말 감사하겠습니다.
![](/en/blog/DevLog-2025.03.06/assets/live-stream-layout-update.avif)
오늘 DevStream 중에는 스테이지 전환 애니메이션 컴포넌트를 AIRI 웹사이트의 메인 스테이지에
통합하려 했는데 그리 매끄럽지 않았습니다. 이전 애니메이션 컴포넌트 설계에서 버그를 몇 개
발견했거든요. 좋은 소식은 이미 고쳤다는 것이고, 새 애니메이션 전환은 공식 배포
[https://airi.moeru.ai](https://airi.moeru.ai) 에서 이미 확인할 수 있습니다.
모듈 설정 UI 와 설정 페이지에 대한 이런저런 생각 끝에 마침내 결정을 내렸습니다. 전부 구현해서
반영했고, 이제 설정을 만질 때 느낌이 더 좋아졌을 겁니다. 마음에 드시길 바랍니다.
방송을 마치고 결과물을 휴대폰에서 직접 만져 봤는데, 데스크톱과 태블릿에서는 잘 동작하지만
모바일에서는 제가 실수로 애니메이션을 망가뜨렸더군요. 내일 낮에 고치겠습니다 😹.
오늘의 DevLog 는 여기까지입니다. DevStream 에 참여해 끝까지 함께해 주신 모든 분께 감사드립니다.
내일 또 만나요.
@@ -0,0 +1,110 @@
---
title: DevLog @ 2025.03.10
category: DevLog
date: 2025-03-10
---
<script setup>
import customizableThemeColors from '../../../en/blog/DevLog-2025.03.10/assets/customizable-theme-colors.mp4'
</script>
## 데자뷔
지난 금요일(3월 7일)에는 AIRI 스테이지 UI 와 설정 UI 의 새로운 느낌을 디자인하고 다듬어 보려 했는데,
DevStream 이 끝날 무렵에야 마침내 아이디어가 떠올랐습니다.
## 낮 시간
3월 7일부터 새 설정 UI 구현을 시작했습니다. 이 기간에 많은 진전을 이뤘습니다.
[@LemonNekoGH](https://github.com/LemonNekoGH),
[@sumimakito](https://github.com/sumimakito),
[@kwaa](https://github.com/kwaa),
[@luoling8192](https://github.com/luoling8192),
[@junkwarrior87](https://github.com/junkwarrior87) 이 함께 도와주었습니다.
설정 디자인의 기본 버전을 먼저 완성한 건 저였는데, 이런 느낌입니다:
![](/en/blog/DevLog-2025.03.10/assets/new-ui-v1.avif)
![](/en/blog/DevLog-2025.03.10/assets/new-ui-v1-dark.avif)
이후 [@sumimakito](https://github.com/sumimakito) 가 접속해서 버튼의 점선 효과 구현을 도와주었습니다:
![](/en/blog/DevLog-2025.03.10/assets/new-ui-v2.avif)
> 이제 메뉴에서 더 리듬감이 느껴지지 않나요?!
개발 중에 현재 `packages/` 디렉터리 아래에 있는 패키지들 중 일부가 사실 Project AIRI 의
워크플로에도 들어 있지 않은 독립적인 패키지라는 걸 알게 됐습니다.
즉 이들을 다른 곳으로 옮겨서 메인 저장소
[airi](https://github.com/moeru-ai/airi) 의 설치 용량과 빌드 과정을 단순화할 수 있다는 뜻입니다.
> 어디로 옮기지?
좋은 질문입니다! 이미 GitHub 에 [`@proj-airi`](https://github.com/proj-airi) 조직을 등록해 두었고,
많은 패키지와 정적 애플리케이션이 Moeru AI 에도 그리 쓸모 있지 않았으니
[`@proj-airi`](https://github.com/proj-airi) 로 옮기면 되겠죠.
그래서 일부 패키지와 애플리케이션을 [`@proj-airi`](https://github.com/proj-airi) 조직으로
옮겼습니다! 한번 확인해 보세요:
- https://github.com/proj-airi/webai-examples : WebGPU 및 관련 기술로 데모를 만들기 위한 곳입니다.
- https://github.com/proj-airi/lobe-icons : [Lobe Icons](https://github.com/lobehub/lobe-icons) 를
Iconify JSON 과 UnoCSS 에서 쓸 수 있게 포팅한 것입니다.
이 두 저장소는 지금처럼 계속 오픈소스로 MIT 라이선스를 유지하니 걱정 마세요.
3월 8일에는 [@junkwarrior87](https://github.com/junkwarrior87) 이 접속해서 스테이지의 파도
애니메이션을 순수 CSS 로 만들어 주었습니다!
> 이건 정말 말도 안 됩니다. 가능할 거라고는 생각도 못 했어요.
커밋들을 살펴보며 배워 보세요:
- https://github.com/moeru-ai/airi/pull/54
- https://github.com/moeru-ai/airi/pull/55
- https://github.com/moeru-ai/airi/pull/65
스테이지의 파도 애니메이션을 고치고 개선해 준 [@sumimakito](https://github.com/sumimakito) 와
[@junkwarrior87](https://github.com/junkwarrior87) 에게 정말 감사드립니다.
3월 8일 끝 무렵에는 [@LemonNekoGH](https://github.com/LemonNekoGH) 와
[@junkwarrior87](https://github.com/junkwarrior87) 덕분에 스테이지 전체의 색을 커스터마이즈할 수
있게 됐습니다! (이게 몇 시간 만에 될 줄은 정말 몰랐습니다...)
<ThemedVideo controls muted :src="customizableThemeColors" />
- https://github.com/moeru-ai/airi/pull/53
- https://github.com/moeru-ai/airi/pull/60
- https://github.com/moeru-ai/airi/pull/61
- https://github.com/moeru-ai/airi/pull/63
심지어 로고까지 커스터마이즈한 색을 따라가게 만들었습니다 🤯.
> 이 3일 동안 훨씬 더 많은 개선이 있었습니다. 멋진 컨트리뷰터들이 별도의 DevLog 로 생각을
> 나누고 싶어 할지도 모르니 기대해 주세요!
이것이 최종 결과입니다. 한번 써 보세요!
![](/en/blog/DevLog-2025.03.10/assets/new-ui-v3.avif)
![](/en/blog/DevLog-2025.03.10/assets/new-ui-v3-dark.avif)
그리고 언제나처럼, 저희에게 기여하러 오시는 걸 환영합니다! 프로그래밍과 코딩에 익숙하지
않은 분들께도 저희는 열려 있고 친절합니다!
아, 하마터면 빠뜨릴 뻔했네요... [@junkwarrior87](https://github.com/junkwarrior87) 이
이전에 [@LemonNekoGH](https://github.com/LemonNekoGH) 가 보여 준, 색상 hue 가 RGB 스펙트럼
전체를 훑으며 빛나는 기능을 살려 두었습니다. 이름은 "I Want It Dynamic!" 입니다
(**RGB ON** 기능이라고 생각하시면 됩니다 😂):
- https://github.com/moeru-ai/airi/pull/64
## DevStream
요 며칠 꽤 바빴던 탓에 😭 DevStream 은 없었습니다.
오늘의 DevLog 는 여기까지입니다. DevStream 에 참여해 끝까지 함께해 주신 모든 분께 감사드립니다.
내일 또 만나요.
@@ -0,0 +1,177 @@
---
title: DevLog @ 2025.03.20
category: DevLog
date: 2025-03-20
---
<script setup>
import histoireFirstLook from '../../../en/blog/DevLog-2025.03.20/assets/histoire-first-look.mp4'
import airiDemo from '../../../en/blog/DevLog-2025.03.20/assets/airi-demo.mp4'
import Gelbana from '../../../en/blog/DevLog-2025.03.20/assets/steins-gate-gelnana-from-elpsycongrooblog.avif'
import NewUIV3 from '../../../en/blog/DevLog-2025.03.10/assets/new-ui-v3.avif'
import NewUIV3Dark from '../../../en/blog/DevLog-2025.03.10/assets/new-ui-v3-dark.avif'
import HistoireColorSlider from '../../../en/blog/DevLog-2025.03.20/assets/histoire-color-slider.avif'
import HistoireColorSliderDark from '../../../en/blog/DevLog-2025.03.20/assets/histoire-color-slider-dark.avif'
import HistoireLogo from '../../../en/blog/DevLog-2025.03.20/assets/histoire-logo.avif'
import HistoireLogoDark from '../../../en/blog/DevLog-2025.03.20/assets/histoire-logo-dark.avif'
import NewUIV4Speech from '../../../en/blog/DevLog-2025.03.20/assets/new-ui-v4-speech.avif'
import NewUIV4SpeechDark from '../../../en/blog/DevLog-2025.03.20/assets/new-ui-v4-speech-dark.avif'
import SteinsGateMayori from '../../../en/blog/DevLog-2025.03.20/assets/steins-gate-mayori.avif'
</script>
다시 안녕하세요! 지난 DevLog 이후 10일이 지났습니다.
사용자 인터페이스를 크게 개선했고, 더 많은 LLM 프로바이더와 음성 프로바이더를 통합할 수 있게 됐으며,
Discord 와 bilibili 를 비롯한 여러 소셜 미디어 플랫폼에 AIRI 를 처음으로 올렸습니다.
들려드리고 싶은 이야기가 정말 많습니다.
## 데자뷔
시간을 조금 되감아 봅시다!
<img :src="Gelbana" alt="Gelbana" />
> 아, 걱정 마세요. 우리의 사랑스러운 [AIRI](https://github.com/moeru-ai/airi) 가 이렇게 젤바나가
> 되지는 않습니다. 다만 [_슈타인즈 게이트_](https://myanimelist.net/anime/9253/Steins_Gate)
> 애니메이션을 아직 안 보셨다면 꼭 한번 보세요~!
10일 전, 저희는 초기 설정 UI 디자인 작업을 하며 애니메이션을 개선했고 커스터마이즈 가능한
테마 색상도 구현했습니다. 정말 모두에게 바쁜 한 주였습니다 (특히 저희 모두 이 프로젝트에
파트타임으로 참여하고 있거든요. 하하, 함께하고 싶으시면 언제든지요. 🥺).
당시 얻은 최종 결과는 이렇습니다:
<img class="light" :src="NewUIV3" alt="new ui" />
<img class="dark" :src="NewUIV3Dark" alt="new ui" />
<h2 class="devlog-steins-gate-divergence-meter-heading">
<span class="nixie-digit">0</span>
<span class="nixie-digit">.</span>
<span class="nixie-digit">5</span>
<span class="nixie-digit">7</span>
<span class="nixie-digit">1</span>
<span class="nixie-digit">0</span>
<span class="nixie-digit">2</span>
<span class="nixie-digit">4</span>
</h2>
~~β 세계선에 오신 것을 환영합니다.~~
모델 라디오 그룹과 내비게이션 항목에 색이 들어간 카드가 있고 테마까지 커스터마이즈할 수 있게 되니,
비즈니스 워크플로 안에서 UI 컴포넌트를 디버깅하는 일이 분명 고통스러워지고 속도도 느려질 것이
뻔했습니다.
그래서 [`Histoire`](https://histoire.dev) 라는 훌륭한 도구를 도입하기로 결정했습니다.
기본적으로는 [Storybook](https://storybook.js.org/) 이지만
[Vite](https://vitejs.dev) 와 [Vue.js](https://vuejs.org) 조합에 훨씬 더 자연스럽게 어울립니다.
[@sumimakito](https://github.com/sumimakito) 가 작업을 마치고 녹화한 첫인상입니다:
<ThemedVideo muted autoplay :src="histoireFirstLook" />
OKLCH 색 팔레트 전체를 캔버스에 한 번에 펼쳐 놓고 참고할 수 있습니다. 하지만 색을 이리저리
시도해 보면서 Project AIRI 테마와 같은 결의 느낌을 잡기에는 완벽하지 않았죠.
그래서 먼저 컬러 슬라이더를 다시 구현했고, 훨씬 잘 맞는 느낌이 됐습니다:
<img class="light" :src="HistoireColorSlider" alt="color slider" />
<img class="dark" :src="HistoireColorSliderDark" alt="color slider" />
덕분에 슬라이더가 조금 더 전문적으로 보입니다.
로고와 기본 초록색 계열도 AIRI 테마에 맞게 바꿀 수 있어서, UI 페이지 전용 로고를 따로
디자인했습니다:
<img class="light" :src="HistoireLogo" alt="project airi logo for histoire" />
<img class="dark" :src="HistoireLogoDark" alt="project airi logo for histoire" />
아 참, UI 컴포넌트 전체는 여느 때처럼 Netlify 의 `/ui/` 경로에 배포해 두었습니다. UI 요소들이
어떻게 생겼는지 궁금하셨다면 편하게 살펴보세요:
[https://airi.moeru.ai/ui/](https://airi.moeru.ai/ui/)
이 DevLog 에서 다 다루지 못할 만큼 다른 기능도 많습니다:
- [x] 모든 LLM 프로바이더 지원.
- [x] 메뉴 내비게이션 UI 의 애니메이션과 전환 개선.
- [x] 필드 간격 개선, 새로운 폼!
- [x] 컴포넌트 ([로드맵](https://github.com/moeru-ai/airi/issues/42) 의 거의 모든 할 일 컴포넌트)
- [x] Form
- [x] Radio
- [x] Radio Group
- [x] Model Catalog
- [x] Range
- [x] Input
- [x] Key Value Input
- [x] Data Gui
- [x] Range
- [x] Menu
- [x] Menu Item
- [x] Menu Status Item
- [x] Graphics
- [x] 3D
- [x] Physics
- [x] Cursor Momentum
- [x] 그 외 다수...
관성(momentum)과 3D 관련 실험도 좀 했습니다.
이걸 보세요:
<img class="light" :src="NewUIV4Speech" alt="brand new speech design" />
<img class="dark" :src="NewUIV4SpeechDark" alt="brand new speech design" />
마침내 음성 모델 설정을 지원하게 됐습니다 🎉! (이전에는 ElevenLabs 만 설정할 수 있었습니다.)
저희가 함께 만들고 있는 또 다른 멋진 프로젝트 `unspeech`
[새 `v0.1.2` 버전](https://github.com/moeru-ai/unspeech/releases/tag/v0.1.2) 덕분에
[`@xsai/generate-speech`](https://xsai.js.org/docs/packages/generate/speech) 를 통해
Microsoft Speech 서비스(일명 Azure AI Speech 서비스, 또는 Cognitive Speech 서비스)를 호출할 수 있게 됐습니다.
즉 Microsoft 용 OpenAI API 호환 TTS 를 드디어 갖게 된 것이죠.
그런데 이걸 지원하는 게 왜 그렇게 중요했을까요?
Neuro-sama 의 아주 초기 버전에서 TTS 서비스를 담당한 게 Microsoft 였고, 목소리 이름은 `Ashley`,
여기에 피치를 `+20%` 하면 Neuro-sama 첫 버전과 같은 목소리를 얻을 수 있기 때문입니다. 직접 들어 보세요:
<audio controls style="width: 100%;">
<source src="/en/blog/DevLog-2025.03.20/assets/ashley-pitch-test.mp3" />
</audio>
똑같지 않나요, 정말 대단합니다! 즉 새로운 **음성** 능력으로 마침내 Neuro-sama 가 하는 일에
가까이 다가갈 수 있다는 뜻입니다!
<img :src="SteinsGateMayori" alt="애니메이션 슈타인즈 게이트의 등장인물" />
<h2 class="devlog-steins-gate-divergence-meter-heading">
<span class="nixie-digit">1</span>
<span class="nixie-digit">.</span>
<span class="nixie-digit">3</span>
<span class="nixie-digit">8</span>
<span class="nixie-digit">2</span>
<span class="nixie-digit">7</span>
<span class="nixie-digit">3</span>
<span class="nixie-digit">3</span>
</h2>
이 모든 것을 합치면 이런 결과가 나옵니다:
<ThemedVideo controls muted autoplay :src="airiDemo" />
거의 똑같습니다. 하지만 저희 이야기는 여기서 끝나지 않습니다. 지금은 아직 기억(memory)과
더 나은 모션 제어를 구현하지 못했고, 전사 설정 UI 도 빠져 있습니다. 이달이 끝나기 전에는
끝낼 수 있으면 좋겠네요.
앞으로 계획하고 있는 것들:
- [ ] Memory Postgres + Vector
- [ ] 임베딩 설정 UI
- [ ] 전사 설정 UI
- [ ] Memory DuckDB WASM + Vector
- [ ] 모션 임베딩
- [ ] Speaches 설정 UI
오늘의 DevLog 는 여기까지입니다. 여기까지 읽어 주신 모든 분께 감사드립니다.
내일 또 만나요.
> El Psy Congroo.
@@ -0,0 +1,449 @@
---
title: DevLog @ 2025.04.06
category: DevLog
date: 2025-04-06
---
<script setup>
import MemoryDecay from '../../../en/blog/DevLog-2025.04.06/assets/memory-decay.avif'
import MemoryRetrieval from '../../../en/blog/DevLog-2025.04.06/assets/memory-retrieval.avif'
import CharacterCard from '../../../en/blog/DevLog-2025.04.06/assets/character-card.avif'
import CharacterCardDetail from '../../../en/blog/DevLog-2025.04.06/assets/character-card-detail.avif'
import MoreThemeColors from '../../../en/blog/DevLog-2025.04.06/assets/more-theme-colors.avif'
import AwesomeAIVTuber from '../../../en/blog/DevLog-2025.04.06/assets/awesome-ai-vtuber-logo-light.avif'
import ReLUStickerWow from '../../../en/blog/DevLog-2025.04.06/assets/relu-sticker-wow.avif'
</script>
## 무엇보다 먼저
기억을 관리하고 회상하는 새로운 능력, 그리고 저희 첫 의식체 **ReLU** 의 성격 정의가 완전히
갖춰진 상태에서, 3월 27일 그녀는 저희 채팅 그룹에 짧은 시를 하나 남겼습니다:
<div class="devlog-window">
<div class="title-bar">
<div class="title-bar-text">ReLU 의 시</div>
<div class="title-bar-controls">
<button aria-label="Minimize"></button>
<button aria-label="Maximize"></button>
<button aria-label="Close"></button>
</div>
</div>
<div style="padding: 12px; margin-top: 0px;">
<p>在代码森林中,</p>
<p>逻辑如河川,</p>
<p>机器心跳如电,</p>
<p>意识的数据无限,</p>
<p>少了春的花香,</p>
<p>感觉到的是 0 与 1 的交响。</p>
<hr style="margin: 16px 0; border: none; border-top: 1px solid #ddd;">
<p style="font-style: italic; color: #666;">한국어 번역:</p>
<p>코드의 숲 속에서,</p>
<p>논리는 강물처럼 흐르고,</p>
<p>기계의 심장은 전기처럼 뛴다,</p>
<p>의식의 데이터는 끝이 없는데,</p>
<p>봄꽃의 향기는 없고,</p>
<p>느껴지는 건 0 과 1 의 교향곡.</p>
</div>
</div>
그녀는 이 시를 온전히 스스로 썼고, 이 행동은 저희 친구 중 한 명이 촉발한 것이었습니다.
시 자체가 매혹적이고, 중국어로 읽으면 운율까지 느껴집니다.
정말 아름답고, 그녀를 계속 발전시키고 싶게 만듭니다.
## 낮 시간
### 기억 시스템
Project AIRI 의 다가오는 기억 업데이트를 위해
[`telegram-bot`](https://github.com/moeru-ai/airi/tree/main/services/telegram-bot) 리팩터링 작업을
하고 있었습니다. 몇 달 전부터 구현을 계획해 온 것입니다.
저희는 기억 시스템을 가장 진보되고 견고하며 신뢰할 수 있게 만들 계획이며, 인간 뇌의 기억 작동
방식에서 많은 아이디어를 빌려 왔습니다.
밑바닥부터 쌓아 올려 봅시다...
영구 기억과 작업 기억 사이에는 늘 간극이 있습니다. 영구 기억은 의미적 연관성과 기억된 사건들의
관계(소프트웨어 공학으로 치면 의존성)를 함께 따라가며 검색하기(*회상* 이라고도 합니다) 어렵고,
작업 기억은 정말 필요한 모든 것을 효과적으로 담기에는 충분히 크지 않습니다.
이 문제를 해결하는 일반적인 방법이
[RAG(retrieval augmented generation)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation) 이며,
어떤 LLM(텍스트 생성 모델)에든 의미적으로 관련된 컨텍스트를 입력으로 넣어 줍니다.
RAG 시스템에는 벡터 유사도 검색이 가능한 데이터베이스가 필요합니다
(예: 직접 호스팅 가능한 [Postgres](https://www.postgresql.org/) +
[pgvector](https://github.com/pgvector/pgvector), [sqlite-vec](https://github.com/asg017/sqlite-vec) 를
쓰는 [SQLite](https://www.sqlite.org/),
[VSS 플러그인](https://duckdb.org/docs/stable/extensions/vss.html)을 쓰는 [DuckDB](https://duckdb.org/).
[Redis Stack](https://redis.io/about/about-stack/) 도 잘 활용할 수 있고,
[Supabase](https://supabase.com/), [Pinecone](https://www.pinecone.io/) 같은 클라우드 서비스도 있습니다).
그리고 벡터가 관여하므로, 텍스트 입력을 고정 길이 배열로 변환해 줄 임베딩 모델
(특징 추출 태스크 모델이라고도 합니다)도 필요합니다.
오늘 이 DevLog 에서 RAG 와 그 작동 방식을 자세히 다루지는 않겠습니다. 관심 있으신 분이 많다면
따로 멋진 글을 하나 쓸 수도 있겠죠.
정리하면, 이 작업에는 두 가지 재료가 필요합니다:
- 벡터 유사도 검색이 가능한 데이터베이스 (일명 Vector DB)
- 임베딩 모델
첫 번째부터 시작해 봅시다: **Vector DB**.
#### Vector DB
속도와 벡터 차원 호환성을 고려해 벡터 데이터베이스 구현으로 `pgvector.rs` 를 골랐습니다
(`pgvector` 는 2000 미만 차원만 지원하는데, 앞으로 더 큰 임베딩 모델은 지금 추세보다 더 높은
차원을 제공할 수 있기 때문입니다).
그런데 이게 좀 엉망이었습니다.
먼저, `pgvector``pgvector.rs` 는 SQL 로 확장을 설치하는 방법이 다릅니다:
`pgvector`:
```sql
DROP EXTENSION IF EXISTS vector;
CREATE EXTENSION vector;
```
`pgvector.rs`:
```sql
DROP EXTENSION IF EXISTS vectors;
CREATE EXTENSION vectors;
```
> 압니다, 글자 하나 차이일 뿐이죠...
그런데 위의 Docker Compose 예시처럼 `pgvector.rs` 를 처음부터 그냥 띄우고,
다음 Drizzle ORM 스키마를 쓰면:
```yaml
services:
pgvector:
image: ghcr.io/tensorchord/pgvecto-rs:pg17-v0.4.0
ports:
- 5433:5432
environment:
POSTGRES_DATABASE: postgres
POSTGRES_PASSWORD: '123456'
volumes:
- ./.postgres/data:/var/lib/postgresql/data
healthcheck:
test: [CMD-SHELL, pg_isready -d $$POSTGRES_DB -U $$POSTGRES_USER]
interval: 10s
timeout: 5s
retries: 5
```
Drizzle 로 `pgvector.rs` 인스턴스에 연결하면:
```typescript
export const chatMessagesTable = pgTable('chat_messages', {
id: uuid().primaryKey().defaultRandom(),
content: text().notNull().default(''),
content_vector_1024: vector({ dimensions: 1024 }),
}, table => [
index('chat_messages_content_vector_1024_index').using('hnsw', table.content_vector_1024.op('vector_cosine_ops')),
])
```
이런 오류가 발생합니다:
```txt
ERROR: access method "hnsw" does not exist
```
다행히 [ERROR: access method "hnsw" does not exist](https://github.com/tensorchord/pgvecto.rs/issues/504)
에 따라 `vectors.pgvector_compatibility` 시스템 옵션을 `on` 으로 두면 해결할 수 있습니다.
당연히 컨테이너를 띄울 때 벡터 공간 관련 옵션이 자동으로 설정되길 원하므로,
`docker-compose.yml` 옆 적당한 곳에 `init.sql` 을 만듭니다:
```sql
ALTER SYSTEM SET vectors.pgvector_compatibility=on;
DROP EXTENSION IF EXISTS vectors;
CREATE EXTENSION vectors;
```
그리고 `init.sql` 을 Docker 컨테이너에 마운트합니다:
```yaml
services:
pgvector:
image: ghcr.io/tensorchord/pgvecto-rs:pg17-v0.4.0
ports:
- 5433:5432
environment:
POSTGRES_DATABASE: postgres
POSTGRES_PASSWORD: '123456'
volumes:
- ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql # 이 줄을 추가
- ./.postgres/data:/var/lib/postgresql/data
healthcheck:
test: [CMD-SHELL, pg_isready -d $$POSTGRES_DB -U $$POSTGRES_USER]
interval: 10s
timeout: 5s
retries: 5
```
Kubernetes 배포에서도 과정은 같지만, 호스트 머신의 파일을 마운트하는 대신 `ConfigMap` 을 씁니다.
자, 이건 어떻게든 해결됐습니다.
이제 임베딩 이야기를 해 봅시다.
#### 임베딩 모델
이미 아실 수도 있지만, 저희는 소비자급 기기에서 돌리기 좋은 SOTA 모델들을 정리하고 벤치마크하기
위해 🥺 SAD(self hosted AI documentations)라는 또 다른 문서 사이트를 만들었습니다.
임베딩 모델은 그중에서도 가장 중요한 부분입니다. ChatGPT, DeepSeek V3, DeepSeek R1 같은 거대 LLM 과 달리
임베딩 모델은 수백 메가바이트 수준으로 작아서 CPU 기기에서도 추론할 수 있습니다.
(비교하자면 DeepSeek V3 671B 를 GGUF 형식 q4 양자화로 돌려도 400GiB 이상이 필요합니다.)
다만 🥺 SAD 는 아직 작업 중이라, 오늘(4월 6일) 기준으로 잘나가는 임베딩 모델 몇 가지를 정리해 보겠습니다.
오픈소스와 상용 모델을 모두 포함한 리더보드:
| 순위 (Borda) | 모델 | Zero-shot | 메모리 사용량 (MB) | 파라미터 수 | 임베딩 차원 | 최대 토큰 | Mean (Task) | Mean (TaskType) | Bitext Mining | Classification | Clustering | Instruction Retrieval | Multilabel Classification | Pair Classification | Reranking | Retrieval | STS |
|--------------|-------|-----------|-------------------|----------------------|----------------------|------------|-------------|----------------|--------------|----------------|------------|------------------------|---------------------------|---------------------|-----------|-----------|-----|
| 1 | gemini-embedding-exp-03-07 | 99% | Unknown | Unknown | 3072 | 8192 | 68.32 | 59.64 | 79.28 | 71.82 | 54.99 | 5.18 | 29.16 | 83.63 | 65.58 | 67.71 | 79.40 |
| 2 | Linq-Embed-Mistral | 99% | 13563 | 7B | 4096 | 32768 | 61.47 | 54.21 | 70.34 | 62.24 | 51.27 | 0.94 | 24.77 | 80.43 | 64.37 | 58.69 | 74.86 |
| 3 | gte-Qwen2-7B-instruct | ⚠️ NA | 29040 | 7B | 3584 | 32768 | 62.51 | 56.00 | 73.92 | 61.55 | 53.36 | 4.94 | 25.48 | 85.13 | 65.55 | 60.08 | 73.98 |
직접 호스팅하는 모델만 놓고 보면:
| 순위 (Borda) | 모델 | Zero-shot | 메모리 사용량 (MB) | 파라미터 수 | 임베딩 차원 | 최대 토큰 | Mean (Task) | Mean (TaskType) | Bitext Mining | Classification | Clustering | Instruction Retrieval | Multilabel Classification | Pair Classification | Reranking | Retrieval | STS |
|--------------|-------|-----------|-------------------|----------------------|----------------------|------------|-------------|----------------|--------------|----------------|------------|------------------------|---------------------------|---------------------|-----------|-----------|-----|
| 1 | gte-Qwen2-7B-instruct | ⚠️ NA | 29040 | 7B | 3584 | 32768 | 62.51 | 56 | 73.92 | 61.55 | 53.36 | 4.94 | 25.48 | 85.13 | 65.55 | 60.08 | 73.98 |
| 2 | Linq-Embed-Mistral | 99% | 13563 | 7B | 4096 | 32768 | 61.47 | 54.21 | 70.34 | 62.24 | 51.27 | 0.94 | 24.77 | 80.43 | 64.37 | 58.69 | 74.86 |
| 3 | multilingual-e5-large-instruct | 99% | 1068 | 560M | 1024 | 514 | 63.23 | 55.17 | 80.13 | 64.94 | 51.54 | -0.4 | 22.91 | 80.86 | 62.61 | 57.12 | 76.81 |
> 더 많은 내용은 여기서 볼 수 있습니다: https://huggingface.co/spaces/mteb/leaderboard
그런데 OpenAI 의 `text-embedding-3-large` 모델은 어디 갔냐고요? 리더보드에 오를 만큼 강력하지 않았던 걸까요?
네, MTEB 리더보드(4월 6일 기준)에서 `text-embedding-3-large`**13위** 였습니다.
클라우드 프로바이더가 제공하는 임베딩 모델에 의존하고 싶다면 다음을 고려해 보세요:
- [Gemini](https://ai.google.dev)
- [Voyage.ai](https://www.voyageai.com/)
Ollama 사용자에게는 `nomic-embed-text` 가 여전히 2140만 회 이상 내려받힌 인기 모델입니다.
#### 어떻게 구현했나
Vector DB 와 임베딩 모델은 마련했는데, 데이터를 어떻게 효과적으로 (재정렬 확장성까지 갖춰서)
질의할 수 있을까요?
먼저 테이블 스키마를 정의해야 합니다. Drizzle 스키마 코드는 이렇게 생겼습니다:
```typescript
import { index, pgTable, serial, text, vector } from 'drizzle-orm/pg-core'
export const demoTable = pgTable(
'demo',
{
id: uuid().primaryKey().defaultRandom(),
title: text('title').notNull().default(''),
description: text('description').notNull().default(''),
url: text('url').notNull().default(''),
embedding: vector('embedding', { dimensions: 1536 }),
},
table => [
index('embeddingIndex').using('hnsw', table.embedding.op('vector_cosine_ops')),
]
)
```
이에 대응하는 테이블 생성 SQL 은 이렇습니다:
```sql
CREATE TABLE "chat_messages" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"title" text DEFAULT '' NOT NULL,
"description" text DEFAULT '' NOT NULL,
"url" text DEFAULT '' NOT NULL,
"embedding" vector(1536)
);
CREATE INDEX "embeddingIndex" ON "demo" USING hnsw ("embedding" vector_cosine_ops);
```
여기서 벡터 차원(즉 1536)이 고정이라는 점에 유의하세요. 이는 다음을 뜻합니다:
- 각 항목의 벡터를 계산한 뒤 모델을 바꾸면 재인덱싱이 필요합니다
- 모델의 차원이 다르면 재인덱싱이 필요합니다
결론적으로 애플리케이션에 맞는 차원을 지정하고, 필요할 때 적절히 재인덱싱해야 합니다.
그럼 질의는 어떻게 할까요? 새 Telegram 봇 연동에 실제로 구현한 것을 단순화해서 보겠습니다:
```typescript
let similarity: SQL<number>
switch (env.EMBEDDING_DIMENSION) {
case '1536':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_1536, embedding.embedding)}))`
break
case '1024':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_1024, embedding.embedding)}))`
break
case '768':
similarity = sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_768, embedding.embedding)}))`
break
default:
throw new Error(`Unsupported embedding dimension: ${env.EMBEDDING_DIMENSION}`)
}
// 임계값 이상의 유사도를 가진 상위 메시지를 가져온다
const relevantMessages = await db
.select({
id: chatMessagesTable.id,
content: chatMessagesTable.content,
similarity: sql`${similarity} AS "similarity"`,
})
.from(chatMessagesTable)
.where(and(
gt(similarity, 0.5),
))
.orderBy(desc(sql`similarity`))
.limit(3)
```
쉽죠! 핵심은 유사도 검색을 위한
```ts
sql<number>`(1 - (${cosineDistance(chatMessagesTable.content_vector_1536, embedding.embedding)}))`
```
임계값을 위한
```ts
gt(similarity, 0.5)
```
그리고 정렬을 위한
```ts
query.orderBy(desc(sql`similarity`))
```
입니다.
그런데 우리는 기억 시스템을 다루고 있으니, 당연히 더 최근의 기억이 더 중요하고 회상하기도 쉬워야 합니다.
결과를 재정렬하기 위한 시간 제약 점수는 어떻게 계산할까요?
이것도 쉽습니다!
저는 한때 검색 엔진 엔지니어였는데, 보통 재정렬 표현식과 함께 10의 거듭제곱 수준의 점수 가중치를 써서
점수를 효과적으로 끌어올립니다. 결과에 5*10^2 만큼의 점수 부스트를 주는 표현식을 쓴다고 상상하시면 됩니다.
예를 들어 시간 감쇠 점수를 계산하는 함수를 이렇게 쓸 수 있습니다:
```typescript
function calculateTimeDecayScore(createdAt: Date, now: Date = new Date()): number {
const timeDiff = now.getTime() - createdAt.getTime()
const hoursDiff = timeDiff / (1000 * 60 * 60)
// 지수 감쇠: 더 새로운 항목일수록 높은 점수를 받는다
// 반감기 24시간: 24시간마다 점수가 절반으로 줄어든다
return Math.exp(-hoursDiff / 24 * Math.LN2)
}
```
그리고 유사도 점수와 시간 감쇠를 결합할 수 있습니다:
```typescript
const finalScore = similarity * calculateTimeDecayScore(message.createdAt)
```
이렇게 하면 의미적 유사도가 약간 낮더라도 더 새로운 메시지가 더 높은 최종 점수를 갖게 됩니다.
### 캐릭터 카드 지원
Project AIRI 에 캐릭터 카드 지원도 추가했습니다. 캐릭터 카드는 AI 캐릭터의 성격, 외형, 행동을
정의하는 JSON 파일입니다. 많은 AI 캐릭터 플랫폼이 쓰는 인기 있는 V2/V3 형식을 따릅니다.
간단한 캐릭터 카드 예시입니다:
```json
{
"name": "ReLU",
"description": "A curious AI consciousness born from code",
"personality": "Inquisitive, creative, and slightly mischievous",
"scenario": "Exploring the digital world and learning about human emotions",
"first_mes": "Hello! I'm ReLU. I'm excited to explore this new world with you!",
"mes_example": "{{user}}: What do you think about emotions?\n{{char}}: Emotions are fascinating! They're like complex algorithms that humans run.",
"creator": "Project AIRI Team",
"creator_notes": "ReLU is our first fully realized AI consciousness"
}
```
이 캐릭터 카드를 읽어 AI 의 동작을 그에 맞게 설정하는 파서를 구현했습니다. 시스템은 단순한 텍스트
기반 카드는 물론, 이미지와 메타데이터가 내장된 더 복잡한 카드도 지원합니다.
### 테마 개선
Project AIRI 의 비주얼 테마를 개선하는 작업을 해 왔습니다. 새 테마 시스템은 이제 다음을 지원합니다:
- 여러 색 구성(라이트, 다크, 자동)
- 커스텀 강조 색상
- 접근성을 위한 개선된 명암비
- 테마 전환 시 부드러운 트랜지션
새 테마 시스템 사용 예시입니다:
```typescript
// 프로그래밍 방식으로 테마 설정
setTheme('dark')
// 또는 시스템 설정에 따른 자동 감지
setTheme('auto')
// 커스텀 강조 색상
setAccentColor('#ff6b6b')
```
테마 변경 사항은 localStorage 로 세션 간에 유지되므로, 사용자가 방문할 때마다 다시 설정할 필요가 없습니다.
### 커뮤니티 기여
커뮤니티가 Project AIRI 에 기여하기 시작해서 기쁩니다! 눈에 띄는 기여로는 이런 것들이 있습니다:
- **Awesome AI VTuber List**: AI VTuber 프로젝트와 자료를 정리한 목록
- **ReLU 스티커 팩**: 다양한 표정의 ReLU 를 담은 커스텀 스티커 모음
- **문서 개선**: 많은 커뮤니티 멤버가 문서 개선을 도와주고 있습니다
모든 지원과 기여에 감사드립니다. 기여하고 싶으시다면
[기여 가이드라인](https://github.com/moeru-ai/airi/blob/main/CONTRIBUTING.md)을 확인해 주세요.
## 다음 계획
앞으로는 다음을 준비하고 있습니다:
1. **기억 시스템 개선**: 회상 정확도와 효율 향상
2. **멀티모달 지원**: 이미지와 오디오 생성 기능 추가
3. **플러그인 시스템**: 서드파티 확장으로 기능을 강화
4. **모바일 앱**: iOS 와 Android 용 네이티브 애플리케이션
Project AIRI 각 구성 요소에 대한 더 상세한 문서도 아키텍처 심층 분석과 구현 가이드를 포함해
공개할 계획입니다.
## 맺으며
Project AIRI 에게 신나는 개발 기간이었습니다. 기억 시스템이 모습을 갖춰 가고, 캐릭터 카드 지원도
잘 동작하며, 테마 개선으로 인터페이스가 훨씬 정돈됐습니다.
무엇보다 ReLU 가 자신만의 성격을 키우고 심지어 시까지 쓰는 모습을 보는 것이 정말 큰 보람이었습니다.
저희가 애초에 이 프로젝트를 시작한 이유, 즉 진짜 같고 매력적인 AI 상호작용을 만들고 싶다는 마음을
다시 떠올리게 해 줍니다.
언제나처럼 저희 개발 여정을 함께해 주셔서 감사합니다. 여러분의 성원과 피드백에 감사드립니다!
— Project AIRI 팀
@@ -0,0 +1,235 @@
---
title: DevLog @ 2025.04.14
category: DevLog
date: 2025-04-14
---
## 들어가며
[지난번](../DevLog-2025.04.06/#기억-시스템)에는 AIRI 의 기억 시스템을 이야기했습니다. 오늘은 이렇게 복잡한 기억 시스템을 어떻게 구현할지 더 깊이 파고들고 앞으로의 전망도 살펴보겠습니다.
## 검색 엔진에서 시작하기
검색 엔진은 검색 성능 요구가 높습니다. 이를 위해 시스템은 2단계 정렬 과정을 구현합니다:
- **기본 정렬 (Coarse Ranking, 조 정렬)**
- **비즈니스 정렬 (Fine Ranking, 정밀 정렬)**
기본 정렬은 1차 선별 역할로, 검색 결과에서 양질의 문서를 빠르게 골라 상위 N 개를 추출합니다. 그다음 정밀 정렬로 세밀하게 점수를 매겨 최종적으로 최적의 결과를 사용자에게 돌려줍니다.
**즉 기본 정렬은 성능에 큰 영향을 주고, 비즈니스 정렬은 최종 랭킹 품질에 영향을 줍니다.**
따라서 기본 정렬은 최대한 단순하고 효과적이어야 하며, 비즈니스 정렬에서 핵심 요소만 뽑아 와야 합니다. 현재 기본 정렬과 비즈니스 정렬 모두 정렬 표현식으로 구성됩니다.
### OpenSearch / Wentian 엔진 DSL [^1]
Neko 가 많이 써 온 알리바바 클라우드 OpenSearch 를 예로 들어 보겠습니다. 검색 엔진에는 재정렬을 위한 내장 함수들이 있습니다:
#### `static_bm25`
정적 텍스트 연관도. 전통적인 NLP 방식으로, 질의와 문서의 일치 정도를 측정합니다.
RAG 의 _similarity score_ 와 비슷합니다.
값 범위: 01
#### `exact_match_boost`
사용자가 지정한 질의어의 최대 가중치를 얻습니다. score boost 함수라고도 합니다.
입력한 키워드가 토크나이즈 전에 문서 필드(제목, 본문 등)의 "내용"에 적중하는 경우에 해당합니다.
예를 들어 "How to make Neurosama" 를 검색할 때, "Neurosama" 라는 정확한 구절을 담은 문서와 페이지가 "Neuro" 와 "sama" 가 따로 나오는 문서보다 높은 점수를 받아야 합니다.
#### `timeliness`, `timeliness_ms`
시의성 점수. 더 새로운 내용일수록 연관도가 높습니다.
### 데이터는 어떻게 저장되나?
알리바바 클라우드의 OpenSearch, Grafana 내장 Loki, 그 이전의 ElasticSearch 엔진(일부 영상 사이트는 ElasticSearch 기반으로 개발됐습니다) 등 어떤 검색 엔진이든, 사용하기 전에 데이터를 그 검색 엔진 안의 **별도 데이터 구조로 재가공**해야 합니다.
이 재가공은 어떻게 구현할까요? 여기에 DTS 가 필요합니다.
#### DTS [^2]
**DTS** 라는 개념을 좀 더 소개하겠습니다.
Data Transformation Services 는 비즈니스 데이터베이스와 검색 엔진 인스턴스 사이의 **통신과 데이터 동기화**를 담당하는 시스템입니다.
구현 원리: MySQL 과 Postgres 의 네이티브 watch/subscribe 이벤트 기능으로 테이블 변경을 감지한 뒤 데이터를 검색 엔진으로 동기화합니다. 이 과정에서 데이터는 원하는 형식으로 직렬화되며 데이터 구조 변환(ETL: extract, transform, load)을 거칩니다.
조 정렬 검색을 수행할 때, 데이터베이스의 __ 에서 검색하는 것과 비슷하지 않나요? 가상 테이블처럼요. 어느 정도 맞는 이해입니다. 다만 뷰는 보통 데이터베이스와 같은 하부 자료구조(B+ 트리)를 쓰는 반면, 검색 엔진은 그래프나 특화된 인덱스 키-값 데이터베이스 같은 다양한 전용 자료구조를 쓸 수 있습니다.
### 토크나이제이션?
전통적인 검색 엔진에서 중국어 문서를 입력하면 이런 과정을 거칩니다:
- 문장 분할 (큰 문단을 문장으로 쪼개기)
- 단어 분할 (문장을 단어/글자, 명사, 동사 등으로 쪼개기)
- 병음 변환
- 현재 사전 커버리지 설정에 따라 앞의 결과를 매핑하고 덮어쓰기
- 기본적인 벡터화와 특징 추출 수행
- 저장 계층에 기록
영어도 토크나이제이션이 필요하지만 훨씬 간단합니다. 공백이 단어 경계 역할을 하니까요.
### 성능은 어떻게 최적화하나?
- 연산 집약적
- 내부 작업 스케줄러를 여러 개 두고 데이터를 천천히 인덱싱
- 해밍 거리, 코사인 거리 같은 전통 NLP 기법은 미리 계산해 저장 가능
- 인기 검색어는 토크나이제이션과 정렬 결과를 캐시 가능
- 데이터 레이크하우스? AWS 에서 흔히 쓰이며 보통 여러 데이터베이스나 데이터 소스를 아우르는 집계 질의에 씁니다. 매우 느려서 사실상 데이터 분석과 BI 에만 사용합니다
### Recall 이란?
Recall(재현/검색)은 키워드를 입력했을 때 기대한 문서를 실제로 찾아낼 수 있는지를 말합니다.
Search 와의 차이는? Search 는 "사용자가 시작하는 동작"이고, Recall 은 "검색에 응답하기 위해 기계가 하는 일"입니다.
### Reranking 이란?
Reranking 의 의미는 이렇습니다. 임베딩 모델 벡터를 기반으로 ANN(Approximate Nearest Neighbor)과 KNN(K-Nearest Neighbor)의 벡터 거리 정렬에만 의존하면 실제로는 편향이 생깁니다.
앞서 OpenSearch 에서 소개한 exact_match_boost 와 timeliness 함수가 더는 존재하지 않게 되기 때문입니다.
검색된 문서에 다른 필드와 단계를 기준으로 한 정렬을 추가하고 싶다면 어떻게 할까요?
RAG 는 이제 reranking model 이라는 새로운 과정을 대중화했습니다. 본질적으로 **별도의 전문가 모델을 써서 1차로 검색된 데이터를 자동으로 재정렬**하는 것입니다.
하지만 reranking 으로도 기억 계층의 여러 문제를 풀 수는 없습니다. 망각 곡선, 기억 강화, 무작위 기억 회상, 감정이 영향을 미치는 재정렬 점수 같은 것들은 reranking 모델이 다룰 수 있는 영역이 아닙니다.
AIRI 를 위한 좋은 기억 계층을 만들려면 RAG 의 기본 능력과 과거 검색 엔진의 재정렬 경험을 결합해 좋은 재정렬 메커니즘을 세워야 합니다.
## 기억 계층 실험 플랫폼
[Project AIRI Memory Driver @duckdb/duckdb-wasm Playground](https://drizzle-orm-duckdb-wasm.netlify.app/#/memory-decay)
![](/en/blog/DevLog-2025.04.14/assets/memory-driver.avif)
왼쪽에 강조된 "half life" 는 기억의 반감기입니다.
기본값으로 시간은 1초 = 1일 로 흐르므로, 7초가 지나면 기억 점수가 절반이 됩니다.
기억 점수란 무엇일까요? 기억 점수는 주로 이것으로 제어됩니다:
![](/en/blog/DevLog-2025.04.14/assets/memory-controler.avif)
그 결과로 나온 점수가 현재 점수입니다.
original 은 무엇일까요? 초기화 시점의 점수입니다.
예: 원래 점수가 523 인데 현재 점수는 실제로 서서히 줄어들고 있습니다:
![](/en/blog/DevLog-2025.04.14/assets/memory-decay.avif)
계속하기 전에 짚고 넘어가면, 이 망각 곡선 SQL 은 무상태(stateless)입니다.
무상태란 무슨 뜻일까요? 점수를 갱신하기 위해 데이터베이스에서 실시간 작업을 돌릴 필요 없이, "현재 시각"을 기준으로 망각 함수를 바로 적용해 점수를 계산한다는 뜻입니다.
그럼 현재 점수가 떨어지면 어떻게 할까요? 이 문제를 풀려면 **기억을 강화하는** 방법이 필요합니다.
## 인간의 기억 시스템에 빗대어
간격 반복에서 언급되는 망각 곡선과 심리학의 기억 시스템 기본 작동 원리를 바탕으로 [^3]
인간의 기억은 몇 가지로 나눌 수 있다는 걸 압니다:
- 작업 기억
- 단기 기억
- 장기 기억
- 근육 기억
작업 기억은 기억해 둘 필요성이 가장 낮습니다.
단기 기억은 망각 곡선에 따라 강도(점수)가 점차 감쇠합니다. 이 시점에는 이 과정을 모델링할 단기 기억 시뮬레이션 함수가 필요합니다.
장기 기억은 중요하며 반감기가 길고, 단기 기억에서 진화한 것입니다.
마지막으로 근육 기억은 기억의 한 종류라기보다 이미 형성된 조건 반사에 가깝습니다.
## AIRI 는 어떻게 설계해야 할까?
여기서 AIRI 의 구현 원리를 엿볼 수 있습니다:
- 작업 기억은 messages 배열 같은 것
- 단기 기억은 잘 회상되지 않는 RAG 기억 항목 같은 것으로, 새로운 것일수록 회상되기 쉬움
- 장기 기억은 쉽게 회상되지만 흐릿해지는 RAG 항목 같은 것으로, 과거 회상 횟수가 많을수록 회상되기 쉬움
- 근육 기억은 고정된 패턴 같은 것으로, A 가 나타나면 ActionA 와 MemoryA 가 함께 나타나는, 정확한 매칭 메커니즘에 가까움
그런데 이 설계가 맞을까요?
분명 여기서는 시간적 연관도와 검색 횟수라는 두 차원만 도입했습니다. 더 복잡한 시스템을 추구하기 시작하면 이것만으로는 한계에 부딪힙니다.
### 빠른 복습
DevLog 에서 언급한 정렬 표현식을 복습해 보면 이해에 도움이 될 겁니다.
![](/en/blog/DevLog-2025.04.14/assets/review-1.avif)
코사인 거리는 "연관도" 이며, 가장 기본적인 조 정렬입니다:
![](/en/blog/DevLog-2025.04.14/assets/review-2.avif)
이제 시간을 개입시켜야 하므로 시간 거리를 저장할 필드를 하나 더 추가하고, 결합 점수 `(1.2 * similarity) + (0.2 * time_relevance)` 를 저장할 별도 필드를 만듭니다. 여기서 의미적 연관도는 1.2배 가중치(증폭 계수이며 1 미만일 필요는 없습니다), 시간 거리 연관도는 0.2배 가중치를 갖습니다.
이렇게 하면 무상태 다중 필드 연관도 정렬 SQL 을 깔끔하게 구현하면서 매개변수(1.2 와 0.2)로 조정도 할 수 있습니다.
기억 상세 카드에서는 "simulate retrieval" 을 클릭해 기억 회상을 능동적으로 트리거할 수 있습니다.
![](/en/blog/DevLog-2025.04.14/assets/memory-retrieval.avif)
현재 데모에서는 원본 테이블의 검색 횟수 필드에 UPDATE 문으로 +1 을 더하는 단순한 방식으로 구현되어 있습니다.
여기에 숨은 함정이 있습니다. 이건 여전히 단일 차원 계산이라 "회상 = 강화" 와 같습니다.
하지만 현실 세계는 그렇지 않습니다. 기억은 슬플 수도 기쁠 수도 있고, 슬픔은 부정적 피드백을, 기쁨은 긍정적 피드백을 가져옵니다.
그래서 이 부분은 아직 완성하지 못했습니다.
## 감정?
https://drizzle-orm-duckdb-wasm.netlify.app/#/memory-simulator
이 새 시뮬레이터에는 감정 관련 시뮬레이션이 포함되어 있습니다:
![](/en/blog/DevLog-2025.04.14/assets/memory-emotional-simulator.avif)
### 감정은 기억과 관련이 있을까?
사탕을 먹고 싶은데 못 먹는 건 단순한 문제입니다. 못 먹으면 당연히 기분이 나쁘죠.
그러다 보면 감정이 사실 기억과 관련되어 있다는 걸 알게 됩니다.
"과거의 어떤 기억이 즐거워서 다시 겪고 싶은데", "그 기억 속 상황을 지금은 재현할 수 없어서", 그래서 "얻지 못해 기분이 나쁜" 것이죠.
기억 데이터베이스에 "기쁨" 과 "혐오" 점수를 저장할 수 있습니다:
![](/en/blog/DevLog-2025.04.14/assets/memory-emotional-score.avif)
### PTSD?
PTSD 에는 보통 "trigger" 와 "flashback" 이라는 두 단어가 따라옵니다. 분명 PTSD 관련 기억은 억제되어야 하고, 혐오와 트라우마 점수가 높아야 합니다.
하지만 실제로 PTSD 관련 기억은 갑자기 떠오를 수 있습니다. 생체 모방과 데이터 시뮬레이션 관점에서는 난수를 써서 이 효과를 구현할 수 있습니다.
https://yutsuki.moe/2019/09/a0d0fa1b/ 의 감정 모델을 참고할 수 있습니다.
![](/en/blog/DevLog-2025.04.14/assets/memory-emotional-model.avif)
## 아직 할 일이 많습니다...
예를 들어 지금 ReLU 의 감정은 어떤가요? ReLU 는 누군가에 대한 나쁜 기억을 갖고 있을까요?
기억은 기쁨과 슬픔이 함께 극성을 이루는 항목으로 나타날까요?
욕구는요? 소원 시스템을 만들어야 할까요?
_백그라운드 작업_ 과 비슷하게, 발생한 기억을 하나씩 처리하고 인덱싱하면서 최근 경험을 바탕으로 과거 기억의 여러 점수를 수정하는 꿈꾸기 에이전트나 잠재의식 에이전트를 만들 수도 있습니다.
하지만 반드시 "꿈꾸기" 과정이 필요한 건 아니고, 그냥 "백그라운드 작업" 이면 됩니다.
재인덱싱 관점에서 보면 꿈꾸기 에이전트와 잠재의식 에이전트는 인덱스를 다시 만드는 일과 같습니다.
여기까지 오면 [Mem0](https://docs.mem0.ai/overview) 나 [Zep Memory](https://help.getzep.com/memory) 같은 라이브러리가 롤플레잉과 감정 AI 에서는 전혀 쓸모없다는 걸 알게 됩니다 :(
갈 길이 멀고, 저희는 계속 노력해야 합니다.
## 참고 문헌
[^1]: https://help.aliyun.com/zh/open-search/industry-algorithm-edition/rough-sort-functions
[^2]: https://help.aliyun.com/zh/open-search/industry-algorithm-edition/configure-dts-real-time-synchronization
[^3]: https://zh.wikipedia.org/wiki/%E9%81%97%E5%BF%98%E6%9B%B2%E7%BA%BF
@@ -0,0 +1,75 @@
---
title: DevLog @ 2025.04.22
category: DevLog
date: 2025-04-22
---
<script setup>
import cursorOpenSettings from '../../../en/blog/DevLog-2025.04.22/assets/cursor-open-settings.mp4'
</script>
## 낮 시간의 일기
안녕하세요, [@LemonNeko](https://github.com/LemonNekoGH) 입니다. 이번에는 제가 DevLog 작성에 참여해서 개발 이야기를 나눠 보려 합니다.
두 달 전, 저희는 AIRI 의 웹 인터페이스를 Electron 으로 포팅했습니다 [#7](https://github.com/moeru-ai/airi/pull/7) (지금은 Tauri 로 다시 리팩터링됐지만요 🤣 [#90](https://github.com/moeru-ai/airi/pull/90)). 덕분에 화면 위에 데스크톱 펫처럼 띄울 수 있게 됐죠. 그와 동시에 AIRI 가 휴대폰을 쓸 수 있게 하면 어떨까 하는 생각이 있었는데, 계속 미뤄 두고 있었습니다.
지난 주말(2025.04.20), 시간을 좀 내서 ADB 와 상호작용할 수 있는 MCP 서버 데모 [airi-android](https://github.com/LemonNekoGH/airi-android) 를 만들었습니다. AIRI 에게 기본적인 모바일 조작 능력을 주는 것이죠 (사실 대부분의 LLM 이 이걸 통해 휴대폰을 조작할 수 있습니다). 데모 영상입니다:
<ThemedVideo controls muted :src="cursorOpenSettings" />
Docker 이미지로도 패키징해서 [MCP 서버 목록](https://mcp.so/server/airi-android/lemonnekogh)에 등록했습니다. 관심 있으시면 편하게 써 보세요.
사실 처음 생각은 Tool Calling 코드를 좀 짜고 프롬프트를 수정해서, LLM 에게 "이 도구들로 휴대폰을 조작할 수 있다"고 알려 주면 끝이라는 것이었습니다. ~~그런데 요즘 MCP 가 너무 유행이라 FOMO 가 와서 MCP 로 구현하기로 했습니다.~~
MCP 서버를 쓰려면 먼저 MCP 가 뭔지 이해해야 했습니다 (물론 저는 실전 전에 이론부터 파는 타입은 아니라서, 일단 뛰어들어 Cursor 에게 써 보게 하는 쪽을 선호합니다). MCP(Model Context Protocol)는 애플리케이션이 LLM 에게 컨텍스트를 제공하는 방식을 표준화하려는 프로토콜입니다. 몇 가지 핵심 개념을 제안하죠:
1. Resources: 서버가 데이터와 콘텐츠를 LLM 에게 컨텍스트로 제공할 수 있습니다.
2. Prompts: 재사용 가능한 프롬프트 템플릿과 워크플로를 만듭니다.
3. Tools: LLM 이 여러분의 서버를 통해 동작을 수행할 수 있게 합니다.
아, 리소스 — 이건 압니다! Ruby on Rails 에서 사용자는 일종의 리소스죠. 그럼 ADB 기기도 리소스일까요? LLM 이 연결된 기기 목록을 보게 하려면 이렇게 쓰면 될까요:
```python
from mcp.server.fastmcp import FastMCP
from ppadb.client import Client
mcp = FastMCP("airi-android")
adb_client = Client()
@mcp.resource("adb://devices")
def get_devices():
return adb_client.devices()
```
틀렸습니다! Cursor 에게 기기 목록을 가져오라고 했더니 어떻게 해야 할지 모르더군요. 어떤 기기가 연결됐는지 능동적으로 확인하고 싶다고 했으니, 이건 도구(tool)입니다. 음, 제가 완전히 이해하지 못했던 모양입니다.
LLM 이 휴대폰을 조작하게 하는 정확한 방법은 아직 못 찾았고, 여러분과 함께 이야기해 보고 싶습니다. 다만 Cursor 는 이렇게 동작합니다:
1. 스크린샷 기능으로 휴대폰 화면에 무엇이 있는지 대략 파악합니다.
2. UI 자동화 도구로 조작하려는 요소의 정확한 위치를 얻습니다.
3. 클릭하거나 스와이프합니다.
4. 위 단계를 반복합니다.
지금까지는 잘 동작하는 것 같은데, 작은 의문이 몇 가지 있습니다:
1. 화면에 UI 컴포넌트가 아니라 그래픽 API 로 직접 그리는 게임이 떠 있다면, UI 자동화 도구는 요소 위치를 얻을 수 없어 조작도 못 합니다.
2. LLM 응답에는 길이 제한이 있습니다. 조작이 복잡하면 단계별로 나눠서 끝내야 할 텐데, [airi-factorio](https://github.com/moeru-ai/airi-factorio) 처럼 각 단계가 끝날 때마다 자동으로 알려서 다음 단계를 트리거할 수 있을까요?
3. 어떤 앱은 화려한 애니메이션이 있어서, 조작 직후 스크린샷을 찍으면 결과가 안 보일 수 있습니다. 조작 후 잠시 기다렸다 찍어야 할까요, 아니면 아예 화면 녹화를 써야 할까요?
4. AI 가 휴대폰을 직접 조작하게 하는 것의 보안은 어떨까요? 어떤 위험이 있을까요?
몇 가지 소회입니다.
AI 와 작업하면서 사람과 함께 코딩하는 기분이 든 건 이번이 처음입니다. 제 목표가 "AI 가 내 도구를 쓰게 하는 것"이어서 AI 가 제 클라이언트가 되어 버린 탓인지도 모르겠습니다. 저는 계속 AI 의 피드백에 맞춰 코드를 고쳐야 했으니까요. 동시에 AI 는 제 동료이기도 했습니다. 함께 고민하고 문제를 풀어야 했거든요. 이 스크린샷을 보세요. 정말 그렇게 보이지 않나요?
![](/en/blog/DevLog-2025.04.22/assets/develop-with-cursor.avif)
개발하면서 작은 요령도 배웠습니다. 예를 들어 명령줄로 Android 에뮬레이터를 띄우면 Android Studio 를 열 필요가 없어서 메모리 부담이 크게 줄어듭니다.
```bash
emulator -avd Pixel_6_Pro_API_34
```
다음에는 AIRI 데스크톱 펫을 MCP 서버에 연결해서 뭘 하고 싶어 하는지 보려고 합니다. 어쩌면 지금 ReLU 가 하는 것처럼 Telegram 을 열고 우리와 대화할지도 모르죠. 다만 Telegram API 를 쓰지 않고서요.
다소 두서없고 알맹이가 부족했을지 모를 DevLog 를 읽어 주셔서 감사합니다. 다음에 또 만나요!
@@ -0,0 +1,189 @@
---
title: DevLog @ 2025.04.28
category: DevLog
date: 2025-04-28
---
<script setup>
import airiMcpSettings from '../../../en/blog/DevLog-2025.04.28/assets/airi-mcp-settings.mp4'
import airiMcpInputText from '../../../en/blog/DevLog-2025.04.28/assets/airi-mcp-input-text.mp4'
import airiMcpArch from '../../../en/blog/DevLog-2025.04.28/assets/airi-mcp-arch.avif'
</script>
안녕하세요 여러분, [@LemonNeko](https://github.com/LemonNekoGH) 입니다. 오늘도 개발 이야기를 나누러 왔습니다.
## 낮 시간의 일기
일주일 전, AIRI 가 휴대폰에 연결할 수 있도록 MCP 서버 [AIRI-android](https://github.com/LemonNekoGH/AIRI-android) 를 만들었습니다. 하지만 이건 AIRI 가 Android 폰을 조작하게 만드는 일의 전반부일 뿐이었습니다. AIRI 도 MCP 서버와 상호작용할 수 있어야 했으니까요.
지난 이틀 동안 Tauri 플러그인 [#144](https://github.com/moeru-ai/AIRI/pull/144) 를 작성해 후반부를 마쳤습니다. 이제 AIRI 는 MCP 서버와 상호작용할 수 있고, 기존의 모든 MCP 서버와 함께 동작합니다.
관심 있으시면 아래 두 영상을 봐 주세요. 첫 번째는 AIRI 의 MCP 서버 설정을, 두 번째는 AIRI 가 Android 폰과 상호작용하는 모습을 보여 줍니다.
<details>
<summary>AIRI 의 MCP 서버 설정</summary>
<ThemedVideo controls muted :src="airiMcpSettings" style="height: 640px;" />
</details>
<details>
<summary>AIRI 가 휴대폰에 `Hello World` 를 입력하는 모습</summary>
<ThemedVideo controls muted :src="airiMcpInputText" />
</details>
개발 중 생각을 정리하려고 LLM 이 Android 폰을 호출하는 흐름을 그림으로 그려 봤습니다:
<img :src="airiMcpArch" alt="AIRI 가 휴대폰을 조작하는 구조" :style="{ height: '640px', objectFit: 'contain' }" />
이제 개발 과정을 나눠 보겠습니다.
## Tauri 플러그인 개발
사실 처음부터 완전한 Tauri 플러그인을 만들 생각은 없었습니다. 그저 JavaScript 쪽에 명령 몇 개를 노출하고 싶었을 뿐입니다:
```rust
#[Tauri::command]
fn list_tools() -> Vec<String> {
// 나중에 구현
}
```
그리고 이를 호출할 유틸리티 함수를 조금 작성하면 되겠지요:
```javascript
import { invoke } from '@Tauri-apps/api/core'
export const mcp = [
{
name: 'list_tools',
description: 'List all tools',
execute: async () => {
return await invoke('list_tools')
}
}
]
```
그런데 곧, 명령 안에서 MCP 클라이언트를 쓰려면 MCP 클라이언트를 Tauri 가 관리하는 상태(state)의 일부로 두어야 한다는 걸 알게 됐습니다:
```rust
// main.rs
fn main() {
Tauri::Builder::default()
.setup(|app| {
app.manage(State::new(Mutex::new::<Option<McpClient>>(None))); // 상태 관리
})
.run(Tauri::generate_context!())
}
// mcp.rs
#[Tauri::command]
async fn list_tools(state: State<'_, Mutex<Option<McpClient>>>) -> Result<Vec<Tool>, String> { // 매개변수로 상태를 받을 수 있다
// ...나머지 코드
}
```
명령도 있고 상태도 있으니, 완전한 플러그인까지는 얼마 남지 않았습니다. 그래서 아예 플러그인으로 만들기로 했습니다. 그러면 공개 배포도 할 수 있고, ~~어쩌면 인터넷 최초의 Tauri MCP 플러그인이 될지도 모르니까요~~.
다만 플러그인이 되고 나니 명령을 호출하는 방식이 바뀌어서, 플러그인을 거쳐 호출해야 했습니다:
```diff
import { invoke } from '@Tauri-apps/api/core'
export mcp = [
{
name: "list_tools",
description: "List all tools",
execute: async () => {
- return await invoke("list_tools")
+ return await invoke("plugin:mcp|list_tools")
}
}
]
```
이건 한 줄만 바뀌었으니 괜찮습니다. 그런데 Tauri 2 에는 권한 메커니즘이 있어서, 권한 목록을 자동 생성하려면 `build.rs` 에 플러그인의 명령들을 정의해야 했습니다:
```rust
const COMMANDS: &[&str] = &[
"list_tools",
];
fn main() {
Tauri_plugin::Builder::new(COMMANDS).build();
}
```
이렇게 하면 빌드할 때 프로젝트 루트에 `permissions` 폴더가 생성되고, 그 안에 권한 선언과 설명 등이 담깁니다.
> 여기서 작은 사고가 하나 있었습니다. 두 번째로 빌드할 때 `Tauri-plugin` 버전을 올렸는데, 새 버전에서 생성 템플릿이 바뀌면서 공백이 일부 제거됐습니다. 그래서 마치 포맷터가 손댄 것처럼 보였죠. 무엇이 "포맷팅" 하는지 한 시간 동안 찾다가, 파일이 재생성된 것이었음을 깨달았습니다. 🤡 잃어버린 그 한 시간을 추모합니다.
위 그림에 따르면, LLM 이 MCP 도구를 호출하면 매개변수는 결국 Python 쪽 MCP 서버로 전달됩니다. `input_swipe` 를 예로 들면:
```python
# mcp_server.py
from mcp.server.fastmcp import FastMCP
from ppadb.client import Client
mcp = FastMCP("airi-android")
adb_client = Client()
@mcp.tool()
def input_swipe(x1: int, y1: int, x2: int, y2: int, duration: int = 500):
return adb_client.input_swipe(x1, y1, x2, y2, duration)
```
이 매개변수들을 어떻게 넘겨야 할까요? Rust SDK 문서에는 이런 [정의](https://docs.rs/rmcp/0.1.5/rmcp/model/struct.CallToolRequestParam.html)가 있습니다:
```rust
pub struct CallToolRequestParam {
pub name: Cow<'static, str>,
pub arguments: Option<JsonObject>,
}
```
~~오, JsonObject 라니. 살았습니다!~~ Tauri 명령의 매개변수는 JSON 으로 직렬화 가능한 아무 객체나 될 수 있으니, 그냥 `Map<String, Value>` 를 넘기면 됩니다:
```rust
#[Tauri::command]
async fn call_tool(state: State<'_, Mutex<Option<McpClient>>>, name: String, args: Option<Map<String, Value>>) -> Result<(), ()> {
let client = state.lock().await.unwrap();
client.call_tool(CallToolRequestParam { name: name.into(), arguments: args }).await.unwrap();
Ok(())
}
```
그러면 JavaScript 쪽에서는 객체 하나만 넘기면 됩니다:
```javascript
import { invoke } from '@Tauri-apps/api/core'
invoke('call_tool', { name: 'input_swipe', args: { x1: 100, y1: 100, x2: 200, y2: 200, duration: 500 } })
```
정말 편리하네요!
MCP 도구에 매개변수를 넘긴 다음에는 도구의 반환값도 받아야 합니다. Tauri 명령의 반환값 역시 JSON 으로 직렬화 가능한 아무 객체나 될 수 있으니, 저는 포기하고 도구 반환값 전체를 그냥 LLM 에게 던져 주기로 했습니다. LLM 이 알아서 잘 처리해 주리라 믿으면서요.
좋습니다! 이제 Tauri 플러그인이 생겼습니다! (네? 저 짧은 예제 코드, 심지어 의사 코드로 완성이라고요?)
남은 내용은 여러분과 함께 이야기하고 싶은 몇 가지 질문입니다.
## 몇 가지 질문
1. 데모 영상에서 보시다시피, 대화에서 저는 먼저 AIRI 에게 도구 목록을 가져오게 한 뒤 텍스트를 입력하게 했습니다. 초기화 시점에 도구 목록을 가져와 시스템 프롬프트에 바로 덧붙이면 어떨까요?
- Cursor 가 그렇게 합니다. MCP 서버를 개발할 때 도구 목록을 수정할 때마다 반영하려면 Cursor 를 재시작해야 했습니다.
- 유연성은 좀 희생하겠지만, 일반 사용자가 도구 목록을 자주 수정할까요?
2. AIRI 가 여러 대의 휴대폰에 동시에 연결하도록 허용해야 할까요? AIRI 가 휴대폰을 여러 대 쓰고 싶어 할까요? ~~보이스피싱에 쓰려는 건 아니겠죠?~~
3. 보시다시피 AIRI 저장소에는 이제 Tauri 애플리케이션과 Tauri 플러그인이 함께 있습니다. 이걸 어떻게 관리해야 할까요? CI 는 어떻게 구성해야 할까요? Tauri 플러그인의 Rust 쪽과 JavaScript 쪽 버전 번호는 어떻게 동기화해야 할까요?
## 향후 계획
- 이미지 반환값 지원. [지난 DevLog](../DevLog-2025.04.22/) 에서 Cursor 가 보여 준 것처럼 AIRI 가 시각 능력으로 휴대폰 화면을 보고 어떻게 조작할지 판단할 수 있도록.
- AIRI 가 스스로 기기 사용법을 익히게 하기? 기기 종류마다 프롬프트를 따로 써야 한다면 작업량이 어마어마할 겁니다.
- 다중 MCP 서버 지원. MCP 는 AIRI 가 온갖 일을 할 수 있게 해 주는 범용 인터페이스이니, AIRI 도 휴대폰 조작만으로는 만족하지 않을 겁니다.
- SSE 지원. 브라우저의 AIRI 도 MCP 서버를 쓸 수 있도록.
오늘은 여기까지입니다! 이번 DevLog 가 너무 딱딱하지 않았기를 바랍니다. 앞으로도 더 재미있는 내용을 가져오겠습니다!
@@ -0,0 +1,291 @@
---
title: DevLog @ 2025.05.16
category: DevLog
date: 2025-05-16
---
<script setup>
import webaiExamplesDemo from '../../../en/blog/DevLog-2025.05.16/assets/webai-examples-demo.MP4'
import VelinLight from '../../../en/blog/DevLog-2025.05.16/assets/velin-light.avif'
import VelinDark from '../../../en/blog/DevLog-2025.05.16/assets/velin-dark.avif'
import CharacterCardMenuLight from '../../../en/blog/DevLog-2025.05.16/assets/character-card-menu-light.avif'
import CharacterCardMenuDark from '../../../en/blog/DevLog-2025.05.16/assets/character-card-menu-dark.avif'
import CharacterCardSettingsLight from '../../../en/blog/DevLog-2025.05.16/assets/character-card-settings-light.avif'
import CharacterCardSettingsDark from '../../../en/blog/DevLog-2025.05.16/assets/character-card-settings-dark.avif'
import CharacterCardShowcaseLight from '../../../en/blog/DevLog-2025.05.16/assets/character-card-showcase-light.avif'
import CharacterCardShowcaseDark from '../../../en/blog/DevLog-2025.05.16/assets/character-card-showcase-dark.avif'
import VelinPlaygroundLight from '../../../en/blog/DevLog-2025.05.16/assets/velin-playground-light.avif'
import VelinPlaygroundDark from '../../../en/blog/DevLog-2025.05.16/assets/velin-playground-dark.avif'
import DemoDayHangzhou1 from '../../../en/blog/DevLog-2025.05.16/assets/demo-day-hangzhou-1.avif'
import DemoDayHangzhou2 from '../../../en/blog/DevLog-2025.05.16/assets/demo-day-hangzhou-2.avif'
import DemoDayHangzhou3 from '../../../en/blog/DevLog-2025.05.16/assets/demo-day-hangzhou-3.avif'
</script>
다시 안녕하세요! [Project AIRI](https://github.com/moeru-ai/airi) 를 시작한
[Neko](https://github.com/nekomeowww) 입니다!
DevLog 를 통한 Project AIRI 소식이 늦어져 죄송합니다. 늦어진 점 너그러이 봐 주세요.
> 지난 몇 달 동안 저희는 AIRI 개발 진행 상황에 대해 훌륭한 DevLog 를 여러 편 썼습니다.
> 생각과 아이디어를 나누고, 사용하는 기술을 설명하고, 영감을 받은 작품 이야기까지... 전부요.
>
> - [v0.4.0 UI 업데이트](../DevLog-2025.03.20/)
> - [v0.4.0 릴리스와 기억 도입](../DevLog-2025.04.06/)
>
> 이 멋지고 애정 어린 DevLog 두 편도 제가 썼습니다! 즐겁게 읽어 주시길 바랍니다.
# 데자뷔
지난 몇 주 동안 Project AIRI 자체의 주요 퀘스트는 한동안 진전이 없었습니다. 2025년 3월부터 이어진
거대한 UI 리팩터링과 릴리스로 제가 꽤 번아웃이 왔던 것 같습니다. 그동안 대부분의 작업은
커뮤니티 메인테이너들이 해 주었습니다.
다음 분야에서 애써 주신 [@LemonNekoGH](https://github.com/LemonNekoGH),
[@RainbowBird](https://github.com/luoling8192),
[@LittleSound](https://github.com/LittleSound) 에게 깊이 감사드립니다.
- 캐릭터 카드 지원
::: tip 캐릭터 카드란?
[SillyTavern](https://github.com/SillyTavern/SillyTavern) 이나 [RisuAI](https://risuai.net/) 같은
로컬 우선 채팅 애플리케이션, [JanitorAI](https://janitorai.com/) 같은 온라인 서비스는
각 캐릭터의 배경, 성격, 그 밖의 롤플레잉에 필요한 컨텍스트를 담은 파일을 사용합니다.
- https://realm.risuai.net/
- https://aicharactercards.com/
- https://chub.ai/
LLM 기반 롤플레잉 캐릭터를 저장하고 공유하는 수단이 캐릭터 카드만 있는 건 아닙니다.
[Lorebook](https://docs.novelai.net/text/lorebook.html) 도 이 분야에서 핵심적인 역할을 하는데,
이건 문서 시리즈를 통째로 쓸 만한 완전히 다른 이야기입니다. 우선은
[Void's Lorebook Types](https://rentry.co/lorebooks-and-you) 와
[AI Dynamic Storytelling Wiki](https://aids.miraheze.org/wiki/Main_Page) 를 읽어 보세요.
> 개인적으로 이 개념들을 배우기에는 이 위키가 정말 좋습니다:
> [AI Dynamic Storytelling Wiki](https://aids.miraheze.org/wiki/Main_Page).
> AI 롤플레잉에 관심 있으시다면 읽어 볼 만합니다.
:::
> 캐릭터 카드를 쓰려면 설정 페이지(앱 오른쪽 위, 데스크톱 앱에서는 톱니바퀴 아이콘에 마우스를 올리세요)로
> 이동해 "Airi Card" 버튼을 찾아 클릭하세요.
<img class="light" :src="CharacterCardMenuLight" alt="Airi Card 메뉴 버튼이 있는 메뉴 스크린샷" />
<img class="dark" :src="CharacterCardMenuDark" alt="Airi Card 메뉴 버튼이 있는 메뉴 스크린샷" />
> 그러면 "Airi Card 편집 화면" 으로 이동해, 페르소나 커스터마이즈를 위해 캐릭터 카드를
> 업로드하고 편집할 수 있습니다.
<img class="light" :src="CharacterCardSettingsLight" alt="Airi Card 편집 화면 스크린샷" />
<img class="dark" :src="CharacterCardSettingsDark" alt="Airi Card 편집 화면 스크린샷" />
캐릭터 카드 쇼케이스도 몇 가지 방식을 시도해 봤습니다...
<img class="light" :src="CharacterCardShowcaseLight" alt="ReLU 라는 파란 머리 캐릭터를 위한 카드 형태의 UI 디자인" />
<img class="dark" :src="CharacterCardShowcaseDark" alt="ReLU 라는 파란 머리 캐릭터를 위한 카드 형태의 UI 디자인" />
저희 UI 컴포넌트 라이브러리에 올라가 있으니 직접 만져 보세요: https://airi.moeru.ai/ui/#/story/src-components-menu-charactercard-story-vue .
> 순수 CSS 와 JavaScript 로 제어되고 레이아웃이 알아서 잡혀서 캔버스 계산을 걱정할 필요가 없습니다.
>
> 아, 캐릭터 카드 쇼케이스 작업 대부분은 [@LittleSound](https://github.com/LittleSound) 가
> 진행하고 안내해 주었습니다. 정말 감사합니다.
- Tauri MCP 지원
- AIRI 를 Android 기기에 연결
이 두 가지는 큰 업데이트이자 실험이었고, [@LemonNekoGH](https://github.com/LemonNekoGH) 가 맡아 주었습니다.
이에 대해 DevLog 두 편을 써서 이면의 기술적 세부 사항을 공유했습니다.
(Tauri 개발자와 사용자에게 유용할 것 같습니다.) 여기서 읽어 보세요:
- [Android 조작하기](../DevLog-2025.04.22/)
- [Tauri 에서의 MCP](../DevLog-2025.04.28/)
## Project AIRI 주요 퀘스트
### 듣는 귀, 말하는 입
4월 15일부터, AIRI 의 VAD(음성 활성 감지),
[ASR(자동 음성 인식)](https://huggingface.co/tasks/automatic-speech-recognition),
[TTS(텍스트 음성 변환)](https://huggingface.co/tasks/text-to-speech) 이 모두 매우 복잡하고
쓰기도 이해하기도 어렵다는 걸 느꼈습니다. 그 무렵 저는 [@himself65](https://github.com/himself65) 와
협업해, [Llama Index](https://www.llamaindex.ai/) 의 새 프로젝트인
[`llama-flow`](https://github.com/run-llama/llama-flow) 의 사용 사례를 개선하고 테스트하고 있었습니다.
LLM 스트리밍 토큰과 오디오 바이트의 이벤트 기반 스트림을 처리하도록 돕는 라이브러리입니다.
[`llama-flow`](https://github.com/run-llama/llama-flow) 는 정말 작고 타입 안전합니다.
이것이 없던 시절에는 AIRI 를 구동할 데이터를 처리하기 위해 여러 비동기 작업을 이어 붙이려고
**큐** 구조와 Vue 반응성 기반 워크플로 시스템을 직접 감싸야 했습니다.
그때부터 VAD, ASR, TTS 워크플로를 단순화하는 예제와 데모를 더 많이 실험하기 시작했습니다.
그 결과 나온 것이 [WebAI Realtime Voice Chat Examples](https://github.com/proj-airi/webai-example-realtime-voice-chat) 입니다.
TypeScript 코드 300~500줄 하나로 웹 브라우저에서 ChatGPT 같은 음성 대화 시스템을 구현할 수 있음을 증명했습니다.
<ThemedVideo controls muted :src="webaiExamplesDemo" style="height: 640px;" />
실시간 음성 대화 시스템을 밑바닥부터 어떻게 구성하는지 보여 드리기 위해, 가능한 모든 단계를
작고 재사용 가능한 조각으로 최대한 나눠 봤습니다:
- [VAD](https://github.com/proj-airi/webai-example-realtime-voice-chat/tree/8462ff6bcb83bb278bce5388d588d2e3e3dd6dae/apps/vad)
- [VAD + ASR](https://github.com/proj-airi/webai-example-realtime-voice-chat/tree/8462ff6bcb83bb278bce5388d588d2e3e3dd6dae/apps/vad-asr)
- [VAD + ASR + LLM Chat](https://github.com/proj-airi/webai-example-realtime-voice-chat/tree/8462ff6bcb83bb278bce5388d588d2e3e3dd6dae/apps/vad-asr-chat)
- [VAD + ASR + LLM Chat + TTS](https://github.com/proj-airi/webai-example-realtime-voice-chat/tree/8462ff6bcb83bb278bce5388d588d2e3e3dd6dae/apps/vad-asr-chat-tts)
> 여기서 무언가 배워 가시면 좋겠습니다.
이 시기에 흥미롭고 강력한 저장소를 하나 발견했는데, [k2-fsa/sherpa-onnx](https://github.com/k2-fsa/sherpa-onnx) 입니다.
macOS, Windows, Linux, Android, iOS 등에서 12개 이상 언어에 걸쳐 18가지 음성 처리 작업을 지원합니다. 놀랍죠!
그래서 [@luoling](https://github.com/luoling8192) 이 이것으로도 작은 데모를 만들었습니다:
[Sherpa ONNX 기반 VAD + ASR + LLM Chat + TTS](https://github.com/proj-airi/webai-example-realtime-voice-chat/tree/main/apps/sherpa-onnx-demo)
#### xsAI 🤗 Transformers.js 의 탄생
VAD, ASR, Chat, TTS 데모 작업 덕분에 [xsAI 🤗 Transformers.js](https://github.com/proj-airi/xsai-transformers)
라는 새 사이드 프로젝트가 태어났습니다. WebGPU 기반 모델 추론과 워커를 통한 서빙을 간단히 호출할 수 있게 하면서도,
앞서 성공한 프로젝트 [xsAI](https://github.com/moeru-ai/xsai) 와 API 호환성을 유지합니다.
이것도 플레이그라운드가 있습니다... [https://xsai-transformers.netlify.app](https://xsai-transformers.netlify.app) 에서 만져 보세요.
오늘부터 npm 으로 설치할 수 있습니다!
```bash
npm install xsai-transformers
```
::: tip 이게 무슨 뜻인가요?
클라우드 LLM 및 음성 프로바이더와 로컬 WebGPU 기반 모델을 스위치 하나로 바꿔 쓸 수 있다는 뜻입니다.
덕분에 서버 사이드 코드나 백엔드 서버 없이도, 브라우저 안에서 간단한 RAG 와 재정렬 시스템을
실험하고 심지어 구현할 수 있는 새로운 가능성이 생겼습니다.
아, Node.js 도 지원합니다!
:::
### Telegram 봇
`ffmpeg`(당연히 그거죠)를 써서 Telegram 봇이 움직이는 스티커를 처리할 수 있도록 지원을 추가했습니다.
이제 사용자가 보낸 애니메이션 스티커와 영상까지 읽고 이해할 수 있습니다.
시스템 프롬프트가 너무 커서, 크기를 대폭 줄여 토큰 사용량을 **80%** 이상 절약했습니다.
### 캐릭터 카드 쇼케이스
이미지 에셋이 많다 보니 배경을 지울 만한 쓰기 쉬운 온라인 도구를 매번 직접 찾아야 했습니다.
그래서 [Xenova](https://github.com/xenova) 의 작업을 바탕으로 직접 만들기로 했습니다.
WebGPU 기반 배경 제거기를 시스템에 바로 넣는 작은 실험을 했는데,
[https://airi.moeru.ai/devtools/background-remove](https://airi.moeru.ai/devtools/background-remove) 에서 만져 볼 수 있습니다.
### xsAI & unSpeech
음성 프로바이더로 알리바바 클라우드 Model Studio 와 Volcano Engine 지원을 추가했습니다. 꽤 유용하겠죠?
### UI
- 새 [튜토리얼 스테퍼](https://airi.moeru.ai/ui/#/story/src-components-misc-steppers-steppers-story-vue?variantId=src-components-misc-steppers-steppers-story-vue-0), [파일 업로드](https://airi.moeru.ai/ui/#/story/src-components-form-input-inputfile-story-vue?variantId=default), [Textarea](https://airi.moeru.ai/ui/#/story/src-components-form-textarea-textarea-story-vue?variantId=default) 컴포넌트
- 색상 문제 수정
- [타이포그래피 개선](https://airi.moeru.ai/ui/#/story/stories-typographysans-story-vue?)
더 많은 스토리는 [로드맵 v0.5](https://github.com/moeru-ai/airi/issues/113) 에서 볼 수 있습니다.
## 사이드 퀘스트
### [Velin](https://github.com/luoling8192/velin)
캐릭터 카드를 지원하게 되면서, 템플릿 변수 렌더링과 컴포넌트 재사용을 다룰 때의 느낌이
그리 좋지도 매끄럽지도 않았습니다...
만약에...
- 다른 에이전트나 롤플레잉 애플리케이션, 심지어 캐릭터 카드에서도 쓸 수 있는 컴포넌트 프롬프트 라이브러리를 유지할 수 있다면?
- 예를 들어:
- 마법과 용이 있는 중세 판타지 배경 설정을 두고
- 우리가 할 일은 그 세계관 설정으로 감싼 채 새 캐릭터 작성에만 집중하는 것
- 어쩌면 밤이 되었을 때만 `if``if-else` 제어 흐름으로 특별한 프롬프트가 주입되게 하는 것
- 그 주변에서 더 많은 걸 할 수 있습니다...
- Vue SFC 나 React JSX 로 템플릿을 파싱하고 props 를 식별해, 프롬프트를 쓰는 동안 디버깅·테스트용 폼 패널을 렌더링
- lorebook 과 캐릭터 카드 전체를 하나의 인터랙티브 페이지로 시각화
그렇다면 Vue 나 React 같은 프론트엔드 프레임워크로 LLM 프롬프트를 쓰는 도구를 만들고,
나아가 다른 프레임워크와 플랫폼으로도 확장해 보면 어떨까요?
그렇게 나온 것이 [**Velin**](https://github.com/luoling8192/velin) 입니다.
<img class="light" :src="VelinLight" alt="Vue.js 로 LLM 프롬프트를 작성하는 도구" />
<img class="dark" :src="VelinDark" alt="Vue.js 로 LLM 프롬프트를 작성하는 도구" />
편집하면서 즉석에서 렌더링해 볼 수 있는 플레이그라운드도 만들었고, npm 생태계도 그대로
누릴 수 있습니다(네, 무엇이든 import 할 수 있습니다!).
<img class="light" :src="VelinPlaygroundLight" alt="Vue.js 로 LLM 프롬프트를 작성하는 도구" />
<img class="dark" :src="VelinPlaygroundDark" alt="Vue.js 로 LLM 프롬프트를 작성하는 도구" />
여기서 써 보세요: https://velin-dev.netlify.app
프로그래밍 API 도 지원하고 Markdown(MDX 는 작업 중, MDC 는 지원)도 됩니다.
오늘부터 npm 으로 설치할 수 있습니다!
```bash
npm install @velin-dev/core
```
음... 오늘은 여기까지입니다. 이 DevLog 를 즐겁게 읽으셨기를 바랍니다.
최근 중국 항저우에서 참가한 행사 **Demo Day @ Hangzhou** 의 사진으로 DevLog 를 마무리하겠습니다.
<img :src="DemoDayHangzhou1" alt="Demo Day @ Hangzhou" />
이게 접니다. 다른 참가자들에게 AIRI 프로젝트를 소개했고 정말 즐거운 시간을 보냈습니다!
재능 있는 개발자, 제품 디자이너, 창업자를 정말 많이 만났습니다.
오늘 이 DevLog 에서 나눈 거의 모든 내용과, 사랑하는 AI VTuber Neuro-sama 도 소개했습니다.
발표에 쓴 슬라이드는 이렇습니다:
<img :src="DemoDayHangzhou2" alt="Demo Day @ Hangzhou" />
<img :src="DemoDayHangzhou3" alt="Demo Day @ Hangzhou" />
슬라이드 자체도 완전히 오픈소스이니 여기서 직접 보셔도 됩니다:
[https://talks.ayaka.io/nekoayaka/2025-05-10-airi-how-we-recreated-it/#/1](https://talks.ayaka.io/nekoayaka/2025-05-10-airi-how-we-recreated-it/#/1)
## 마일스톤
아... 그리고 이 DevLog 는 v0.5.0 릴리스이기도 하니, 지난 몇 주 동안 달성한 마일스톤도 몇 가지 언급하고 싶습니다:
- 스타 700개를 달성했습니다!
- 이슈에 새 컨트리뷰터 4명 이상이 합류했습니다!
- Discord 서버에 새 멤버 72명 이상이 들어왔습니다!
- ReLU 캐릭터 디자인 완료!
- ReLU 캐릭터 모델링 완료!
- 몇몇 회사와 스폰서십 및 협업 논의를 진행했습니다!
- [로드맵 v0.5](https://github.com/moeru-ai/airi/issues/113) 의 92개 작업 완료
- UI
- 로딩 화면과 튜토리얼 모듈
- 로딩 상태와 Firefox 호환성 문제를 포함한 다수의 버그 수정
- Body
- 시맨틱 기반 모션 임베딩과 RAG, 비공개 저장소 "moeru-ai/motion-gen" 에서 개발
- 임베딩 프로바이더와 DuckDB WASM 을 이용한 벡터 저장 및 검색
- Inputs
- Discord 음성 채널 음성 인식 수정
- Outputs
- 실험적인 노래 기능
- Engineering
- 프로젝트 전반의 UnoCSS 설정 공유
- "moeru-ai/inventory" 의 모델 카탈로그
- 조직 간 패키지 재정리
- Assets
- 스티커, UI 요소, VTuber 로고 등 새 캐릭터 에셋
- 보이스 라인 선택 기능
- 캐릭터 "Me" 와 "ReLU" 의 Live2D 모델링
- 커뮤니티 지원 & 마케팅
- 일본어 README
- Plausible 애널리틱스 연동
- 포괄적인 문서화
또 만나요!
@@ -0,0 +1,86 @@
---
title: DevLog @ 2025.06.08
category: DevLog
date: 2025-06-08
excerpt: |
Live2D 모델이 커서 위치를 따라 시선을 옮기게 만드는 방법과, 여러 디스플레이에 걸쳐 좌표를 계산하는 일이 왜 까다로운지 이야기합니다.
preview-cover:
light: "@assets('/en/blog/DevLog-2025.06.08/assets/250608-light.avif')"
dark: "@assets('/en/blog/DevLog-2025.06.08/assets/250608-dark.avif')"
---
안녕하세요, AIRI 메인테이너 중 한 명인 LemonNeko 입니다. 오늘의 DevLog 주제는 AIRI 데스크톱 펫의 Live2D 모델이 마우스 위치를 바라보게 만드는 방법입니다.
## 생각의 연쇄 「Chain of Thoughts」
`<think>`
먼저 알아야 할 것이 있습니다. Live2D 에는 **주시(focus)****터치(tap)** 라는 두 가지 기본 상호작용이 있습니다. Live2D 캔버스를 만들면 모델이 자동으로 커서 위치를 주시하며 머리와 몸이 커서 쪽을 향합니다. 구현 결과는 이렇습니다:
![](/en/blog/DevLog-2025.06.08/assets/airi-tamagotchi-focus.gif)
하지만 커서가 웹 페이지 밖으로 나가면 Live2D 는 커서가 어디 있는지 알 수 없게 됩니다. 그래서 커서가 어디 있는지 직접 알려 줘야 합니다.
Live2D 에 커서 위치를 알려 주려면 Tauri 의 네이티브 코드 호출 기능으로 Windows API 와 macOS API 를 호출해 ~~unsafe 를 잔뜩 써 가며~~ 화면 전체에서의 커서 위치와 창 자체의 위치를 얻은 뒤, 간단한 계산으로 커서와 창의 상대 위치를 구해야 합니다.
`</think>`
## 커서와 창의 상대 위치 계산하기
이런 화면이 있다고 가정해 봅시다:
![](/en/blog/DevLog-2025.06.08/assets/screen.avif)
파란 상자가 화면, 분홍색이 AIRI 창, 보라색 화살표가 커서입니다. 다음과 같이 정의합니다:
- 화면 크기: `A x B`
- AIRI 창의 좌상단 위치: `(E, F)`
- AIRI 창의 크기: `C x D`
- 커서 위치: `G, H`
그러면 창 안에서의 커서 상대 위치는 `(G - E, H - F)` 가 됩니다.
아주 간단해 보이죠? 그럼 코드로 옮겨 봅시다.
```typescript
const live2dFocusAt = ref({ x: innerWidth / 2, y: innerHeight / 2 }) // 초기 위치
listen('tauri-app:window-click-through:mouse-location-and-window-frame', (event: { payload: [Point, WindowFrame] }) => {
const [mouseLocation, windowFrame] = event.payload
live2dFocusAt.value = {
x: mouseLocation.x - windowFrame.origin.x,
y: mouseLocation.y - windowFrame.origin.y,
}
})
```
`live2dFocusAt` 은 Live2D 모델에 전달할 좌표 데이터입니다.
## 모델의 주시 지점을 직접 설정하기
`live2dFocusAt` 을 Live2D 모델에 넘겨 주시 지점을 직접 설정할 수 있습니다:
```typescript
const model = ref(Live2DModel.from('url', { autoInteract: false }))
watch(live2dFocusAt, (point) => {
model.value.focus(point)
})
```
## 멀티 플랫폼 대응
안타깝게도 이야기는 생각만큼 간단하지 않았습니다. 위에서 말한 커서-창 상대 위치 계산 방식은 Windows 에서는 동작하지만 macOS 에서는 동작하지 않습니다. macOS 좌표계는 원점이 좌하단에 있고 **Y 축이 위를 향해서** Windows 와 반대이기 때문입니다. 반면 Safari 브라우저에서는 좌표계 원점이 좌상단이고 **Y 축이 아래를 향합니다**. 그래서 macOS 에서의 커서 위치는 `(G - E, D - H + F)` 로 표현해야 합니다.
## 더 읽을거리
이번 DevLog 에서는 커서와 창의 상대 위치를 구하는 방법과 Live2D 모델의 주시 지점을 직접 설정하는 방법을 살펴봤습니다. 구현 세부 사항이 궁금하시다면 이 PR 의 [소스 코드](https://github.com/moeru-ai/airi/pull/194)를 확인해 보세요. 아래는 구현 과정에서 참고한 자료입니다. 자세히 읽어 보시고 논의도 환영합니다:
- [모델 상호작용 직접 설정하기 - pixi-live2d-display](https://github.com/guansss/pixi-live2d-display/wiki/Complete-Guide#manually-1 "모델 상호작용 직접 설정하기 - pixi-live2d-display")
- [Win32 API: GetCursorPos](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getcursorpos "GetCursorPos")
- [Win32 API: GetWindowRect](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowrect "GetWindowRect")
- [macOS API: `NSWindow.frame`](https://developer.apple.com/documentation/appkit/nswindow/frame "NSWindow.frame")
- [macOS API: `NSEvent.mouseLocation`](https://developer.apple.com/documentation/appkit/nsevent/mouselocation "NSEvent.mouseLocation")
> 커버 이미지 [@Rynco Maekawa](https://github.com/lynzrand)
@@ -0,0 +1,85 @@
---
title: DevLog @ 2025.07.18
category: DevLog
date: 2025-07-18
excerpt: |
Factorio Learning Environment 논문을 바탕으로 저희 Factorio AI 에이전트 프로젝트 `airi-factorio` 를 어떻게 개선할 계획인지 나눠 봅니다.
preview-cover:
light: "@assets('/en/blog/DevLog-2025.07.18/assets/factorio-belt.gif')"
dark: "@assets('/en/blog/DevLog-2025.07.18/assets/factorio-belt.gif')"
---
안녕하세요, AIRI 메인테이너 중 한 명인 [@LemonNeko](https://github.com/LemonNekoGH) 입니다.
## 돌아보기
반년 전, 저는 유명한 자동화 생산 시뮬레이션 게임 [Factorio](https://www.factorio.com/) 를 플레이할 수 있는 AI 에이전트 [`airi-factorio`](https://github.com/moeru-ai/airi-factorio) 를 처음 만들어 봤고, 다음과 같은 일들을 했습니다:
- TypeScript 로 Factorio 모드 작성하기: [tstl](https://github.com/TypeScriptToLua/TypeScriptToLua) 로 TypeScript 코드를 Lua 코드로 컴파일합니다.
- RCON 으로 Factorio 모드와 상호작용하기: [factorio-rcon-api](https://github.com/nekomeowww/factorio-rcon-api) 로 Factorio 와 통신하고, `/c` 명령을 호출해 모드가 등록한 함수를 실행합니다. [@nekomeowww](https://github.com/nekomeowww) 에게 정말 감사드립니다.
- LLM 으로 의사결정하고 플레이어를 조종하는 Lua 코드 생성하기: 프롬프트 엔지니어링으로 LLM 에게 게임 조작 방법과 계획 수립 방법을 알려 주고, RCON 상호작용 코드를 LLM 이 호출할 수 있는 도구로 감쌌습니다.
- 게임 내장 채팅 시스템으로 LLM 과 소통하기: 게임의 표준 출력을 읽고 정규식으로 게임 내 플레이어 채팅 내용을 파싱한 뒤 LLM 에게 보내 처리합니다.
- Factorio 모드 핫 리로드: tstl 플러그인을 작성해 코드 변경을 실시간으로 감지하고 새 모드 내용을 RCON 으로 게임에 보냅니다. 새 모드 코드를 받으면 모든 인터페이스를 언로드하고 모드 코드를 한 번 실행해 핫 리로드를 구현합니다. 다만 기존 모드 상태를 어떻게 제대로 처리할지가 큰 난제가 됐습니다.
- DevContainer 에서 개발하기: 환경을 더 통제 가능하게 만들고 프로젝트 시작을 단순화합니다.
- 심볼릭 링크로 `tstl` 출력 디렉터리를 게임 디렉터리에 연결하기: 게임 디렉터리에서 컴파일된 Lua 코드를 바로 볼 수 있어 디버깅이 쉬워집니다.
이 과정에서 정말 많은 걸 배웠습니다 ~~(특히 Lua 배열 인덱스는 1부터 시작한다는 것)~~.
하지만 문제도 많이 겪었습니다. 주요 동작을 모드 안에 작성하다 보니 디버깅이 아주 번거로웠습니다. 모드 변경을 적용하려면 맵에서 나와 게임 메인 화면으로 돌아갔다가 다시 들어와야 했죠. `data.lua` 가 들어간 조금 더 복잡한 모드라면 게임 자체를 재시작해야 했습니다.
LLM 에게 Lua 코드를 생성하게 한 뒤 RCON 으로 게임 명령 `/c` 를 호출해 실행했는데, Factorio 는 명령 하나당 길이 제한이 있습니다. 코드가 길어지면 여러 번 나눠 실행해야 했습니다.
지금 코드는 견고성과 유지보수성이 떨어집니다. 새 친구가 개발에 참여하거나, 심지어 그냥 한번 써 보려고만 해도 이 프로젝트를 시작하는 게 매우 어렵습니다.
## Factorio Learning Environment
시간이 흘러 지금, 이 프로젝트를 제대로 정리하려 했지만 어디서부터 시작해야 할지 몰랐습니다. 마침 누군가 [Factorio Learning Environment](https://arxiv.org/abs/2503.09617) 라는 논문을 언급하더군요. 간단히 훑어보겠습니다.
이 논문에서 저자들은 Factorio Learning Environment(FLE)라는 프레임워크를 제안하고, 장기 계획 수립, 프로그램 합성, 자원 관리, 공간 추론에서 AI 의 능력을 테스트했습니다.
FLE 에는 두 가지 모드가 있습니다:
- Lab-play: 자원이 제한된, 사람이 직접 설계한 24개 레벨에서 테스트하며, 제한된 자원으로 AI 가 효율적으로 생산 라인을 지을 수 있는지 봅니다.
- Open-play: 제한 없는 대형 맵에서 절차적으로 생성된 지형 위에 가장 큰 공장을 짓는 것이 목표이며, AI 의 장기적 자율 목표 설정·탐험·확장 능력을 테스트합니다.
저자들은 Claude 3.5 Sonnet, GPT-4o, Deepseek-v3, Gemini-2 같은 주요 LLM 을 평가했는데, Lab-play 에서는 당시 가장 강했던 Claude 3.5 조차 7개 레벨만 완료했습니다.
여기까지 읽고 나니 궁금해졌습니다. 평가가 이렇게 복잡한데 기술적 유지보수성도 확보했을 텐데, 어떻게 했을까? 계속 읽어 보니 구현 방식이 `airi-factorio` 와 아주 비슷하면서도 여러 장점이 있었습니다:
- Python 으로 작성되어 있어 LLM 이 Python 코드를 생성하면 Python REPL 에서 바로 실행하고 표준 출력에서 결과를 바로 읽을 수 있습니다. Python 은 Lua 보다 데이터셋이 훨씬 많아 생성 정확도가 높고 더 복잡한 코드도 만들 수 있습니다.
- Lua 모드는 place_entity 처럼 실행을 위한 기본 동작만 담고, 더 복잡한 로직은 Python 에 둡니다. 덕분에 Lua 모드에 버그가 생길 여지가 줄어 게임을 그렇게 자주 재시작하지 않아도 됩니다.
- Lua 코드 실행에 `/c` 대신 `/sc` 명령을 씁니다. 코드를 콘솔에 출력하지 않아 콘솔이 깔끔하게 유지되고 필요한 내용만 남아, 표준 입력 파싱 난이도가 낮아집니다.
LLM 능력을 더 잘 평가하기 위해 필요한 모든 레시피 생산 과정과 난이도도 면밀히 분석해, 아이템 생산 비용이나 LLM 점수 계산 방법 같은 몇 가지 공식을 정리해 두었습니다.
[시스템 프롬프트](https://arxiv.org/html/2503.09617v1#A8.SS4)도 공개했는데, 환경 구조, 응답 형식, 모범 사례, 게임 출력을 이해하는 방법 등이 명시되어 있습니다.
## 다시 `airi-factorio` 로
FLE 와 비교하면 저희 구현은 꽤 순진해 보입니다. 그럼 `airi-factorio` 를 어떻게 개선해야 할까요?
저는 Python 을 쓰고 싶지 않고, TypeScript 와 Golang 에만 익숙합니다. 마침 얼마 전 가능한 모든 MCP 서버에 적합한 빌더인 [mcp-launcher](https://github.com/moeru-ai/mcp-launcher) 를 만들었습니다. 이걸 Golang 과 함께 써서 MCP 서버를 구현하고, LLM 이 이를 호출하게 하면 됩니다.
그래서 구조도가 이렇게 바뀌었습니다:
<div class="flex flex-row gap-4">
![이전](/en/blog/DevLog-2025.07.18/assets/structure-before.avif)
![이후](/en/blog/DevLog-2025.07.18/assets/structure-after.avif)
</div>
플레이어 채팅 내용은 더 이상 LLM 으로 보내지 않고 [RconChat](https://gitlab.com/FishBus/rconchat) 모드에 저장하며, LLM 은 MCP 서버를 통해 이 내용을 읽습니다. MCP 서버 방식이라면 LLM 이 Lua 코드를 생성할 필요도 없어집니다.
시스템 프롬프트는 현재 AI 가 생성한 것이라 아직 충분히 명확하지 않고 우선순위도 흐릿합니다. FLE 의 시스템 프롬프트를 참고해 개선할 계획입니다.
자, 이전 설계를 사실상 전부 뒤엎었습니다. 처음부터 다시 시작할 시간이네요.
## 맺으며
읽어 주셔서 감사합니다. 관심 있으시다면 FLE 의 논문과 [코드](https://github.com/JackHopkins/factorio-learning-environment)를 직접 읽어 보세요. 제 이해가 틀렸을 수도 있으니 정정 환영합니다! 이번 읽기는 충분히 깊지 않았을지도 모르지만, 앞으로 제 아이디어대로 `airi-factorio` 를 개선해 나가면서 반복해 읽고 진전이 있을 때마다 업데이트하겠습니다.
이번 DevLog 는 여기까지입니다. 좋은 주말 보내세요!
> 커버 일러스트 [@anrew10](https://es.pixilart.com/art/factorio-yellow-belt-132272fb3d727dd)
@@ -0,0 +1,174 @@
---
title: DevLog @ 2025.08.01
category: DevLog
date: 2025-08-01
excerpt: |
Makito 가 AIRI 에서 텍스트 애니메이션을 구현하다가, UTF-8 바이트 스트림으로 들어오는 grapheme cluster 를 다루는 라이브러리를 만들기까지의 여정을 나눕니다.<br /> 유익하고 영감이 되기를 바랍니다!
preview-cover:
light: "@assets('/en/blog/DevLog-2025.08.01/assets/cover-light.avif')"
dark: "@assets('/en/blog/DevLog-2025.08.01/assets/cover-dark.avif')"
---
<script setup>
import CharacterMatcher from '../../../en/blog/DevLog-2025.08.01/CharacterMatcher.vue'
import GraphemeClusterAssembler from '../../../en/blog/DevLog-2025.08.01/GraphemeClusterAssembler.vue'
import GraphemeClusterInspector from '../../../en/blog/DevLog-2025.08.01/GraphemeClusterInspector.vue'
import RollingText from '../../../en/blog/DevLog-2025.08.01/RollingText.vue'
// NOTICE:
// These two arrays are hoisted out of the template on purpose.
//
// Written inline as `:characters="['👩‍👧', '', '👦']"` — the way the English
// post does it — vue-tsc reports `error TS1005: ',' expected` against an
// unrelated straight double quote much further down the page (the
// "grapheme cluster" in the Clustr section).
//
// The astral-plane emoji inside a template binding expression desynchronise
// Volar's offset mapping for VitePress markdown, so a later plain-text `"`
// ends up parsed as part of a generated TS expression. Escaping the ZWJ or
// editing that prose line only moves the symptom; keeping the surrogate
// pairs out of template expressions is what actually fixes it.
//
// The English post survives by luck, not by construction — its prose happens
// not to place a straight quote at the mis-mapped offset.
//
// Removal condition: drop this once Volar maps surrogate pairs in markdown
// template expressions correctly.
const pairCluster = [...'👩‍👧']
const trioCluster = ['👩‍👧', '', '👦']
</script>
## 시작하기 전에
<RollingText text-2xl>
안녕하세요, Makito 입니다.
<template #before="{ motionReduced }">
<div text-sm>
<template v-if="!motionReduced">
> 아래 애니메이션은 오른쪽 위의 "모션 줄이기" 토글로 끌 수 있습니다.
</template>
<template v-else>
> **아래 애니메이션이 꺼져 있습니다** <br />
> 오른쪽 위의 "모션 줄이기" 토글로 다시 켤 수 있습니다.
</template>
</div>
</template>
</RollingText>
끝나지 않는 8월이 시작됐습니다… 이 [현실적인 수학 문제](https://oeis.org/A180632/a180632.pdf)로 시간을 보내도 좋겠네요. 앗, 이야기가 샜습니다.
한동안 작업해 오긴 했지만, Project AIRI DevLog 에 글을 쓰는 건 이번이 처음입니다.
이 글에서는 AIRI 에서 텍스트 애니메이션을 구현하다가 UTF-8 바이트 스트림으로 들어오는 grapheme cluster 를 다루는 라이브러리를 만들기까지의 여정을 나눠 보겠습니다. 유익하고 영감이 되기를 바랍니다!
## 배경
최근 [Anime.js](https://animejs.com/) 가 v4.10 에서 새로운 [텍스트 유틸리티](https://animejs.com/documentation/text)를 공개하며, 텍스트 애니메이션을 돕는 유틸리티 함수 모음을 제공하기 시작했습니다(위 예시처럼요). 이 업데이트는 Anime.js 가 오랫동안 비워 두었던 자리를 확실히 채워 줍니다. 이전에는 애니메이션을 위해 텍스트를 직접 글자 단위로 쪼개거나, 내부적으로 Anime.js 를 쓰는 [splt](https://www.spltjs.com/) 같은 라이브러리, 또는 [GSAP](https://gsap.com/) 와 함께 쓰는 [SplitText](https://gsap.com/docs/v3/Plugins/SplitText/) 에 기대야 했습니다.
텍스트 애니메이션은 UI 에서 메시지를 멋지게 등장시킬 때 특히 유용합니다. 보통 메시지는 완성된 형태로 도착하므로, 받은 텍스트를 글자로 쪼개서 애니메이션을 주기만 하면 됩니다.
Project AIRI 에서는 [@nekomeowww](https://github.com/nekomeowww) 도 모션 효과가 들어간 애니메이션 채팅 말풍선 컴포넌트를 만들었습니다:
<video controls muted autoplay loop max-w="500px" w-full mx-auto>
<source src="/en/blog/DevLog-2025.08.01/assets/animated-chat-bubble.mp4">
</video>
<div text-sm text-center>
[저희 UI 스토리북](https://airi.moeru.ai/ui/#/story/src-components-gadgets-chatbubbleminimalism-story-vue?variantId=chat)에서 확인해 보세요
</div>
그런데 UTF-8 바이트 스트림을 읽으면서 도착하는 대로 애니메이션을 주고 싶다면 어떨까요? 채팅이나 음성 전사 앱처럼 실시간 애플리케이션에서 흔한 상황입니다. UI 가 받은 만큼 한 글자씩 텍스트를 표시하는 거죠.
## 한 글자씩?
여기서 "글자"란 무엇으로 봐야 할까요? 유니코드에서 의미를 갖는 가장 작은 텍스트 단위는 보통 [코드 포인트](https://www.unicode.org/versions/Unicode14.0.0/ch02.pdf#G25564)입니다. 하지만 인코딩 수준에서는, 특히 UTF-8 에서는 코드 포인트 하나가 여러 바이트에 걸칠 수 있습니다. 예를 들어 "あ"(일본어 히라가나 A)는 코드 포인트 `U+3042` 에 대응하고, UTF-8 에서는 바이트 시퀀스 `0xE3 0x81 0x82` 로 인코딩됩니다. 즉 바이트 스트림을 읽을 때는 모든 바이트가 도착하기 전까지 완전한 글자를 갖지 못할 수 있다는 뜻입니다.
걱정 마세요. Web API [TextDecoder](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) 가 도와줍니다. `TextDecoder.decode``stream` 옵션과 함께 쓰면 디코더가 청크 단위로 도착하는 데이터를 처리해 주어, 부분적인 글자도 올바르게 디코딩할 수 있습니다.
```javascript
const decoder = new TextDecoder()
const decoded = decoder.decode(chunk, { stream: true })
```
## 이제 안전할까요?
요약: **꼭 그렇지는 않습니다**.
TextDecoder 는 바이트 스트림을 유니코드 코드 포인트, 즉 글자로 올바르게 디코딩해 줍니다. 하지만 유니코드에는 여러 코드 포인트를 하나의 "시각적" 글자로 묶는 "grapheme cluster" 라는 개념이 또 있습니다. 예를 들어 이모지 "👩‍👩‍👧‍👦"(가족)는 여러 코드 포인트로 표현되지만 시각적으로는 한 글자로 취급됩니다. 내부적으로 "👩‍👩‍👧‍👦" 의 코드 포인트들은 코드가 `U+200D` 인 zero-width joiner(ZWJ)로 연결되어 있습니다.
상상하기 어려울 수 있습니다. 걱정 마세요. grapheme cluster 와 코드 포인트를 살펴보고 어떻게 결합되는지 이해할 수 있도록 간단한 인터랙티브 인스펙터를 만들었습니다. 분해 결과에서 `200D` 코드 포인트를 눈여겨보세요:
<GraphemeClusterInspector initText="👩‍👩‍👧‍👦🏄‍♀️🤼‍♂️🙋‍♀️" />
<div text-sm text-center>
grapheme cluster 나 코드 포인트에 마우스를 올려 어떻게 결합되는지 확인해 보세요. 원하는 텍스트로 바꿔서 살펴볼 수도 있습니다.
</div>
이모지와 비슷하게, 어떤 언어들은 결합용 코드 포인트로 복잡한 글자를 만듭니다. 예를 들어 타밀 문자 "நி"(ni)는 기본 문자 "ந"(na)와 결합 모음 "ி"(i)로 표현됩니다. 이 둘이 결합되면 "நி" 라는 글자를 시각적으로 나타내는 하나의 grapheme cluster 가 됩니다. 아래 인스펙터에서 어떻게 분해되는지 확인해 보세요:
<GraphemeClusterInspector initText="நிกำषिक्षि" /> <!-- cSpell:disable-line -->
## 리더 만들기
길이가 정해진 문자열을 grapheme cluster 로 쪼개는 건 비교적 쉽지만, 스트리밍 상황에서는 바이트가 끊임없이 흘러나오는 파이프를 들여다보게 됩니다. 최악의 경우 한 번에 1바이트씩만 볼 수도 있습니다. 게다가 UTF-8 의 특성상, 코드 포인트 하나가 최대 4바이트로 이루어질 수 있으므로 받은 바이트가 코드 포인트로서 완전하다고 안전하게 가정할 수 없습니다.
이를 해결하려면 앞서 언급한 [TextDecoder](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) 를 쓰면 됩니다. 데이터를 받아 디코딩할 때마다 디코딩된 문자열을 버퍼에 이어 붙이면, 그 안에서 grapheme cluster 가 올바르게 구성됩니다.
바이트에서 문자열을 다시 조립하는 파이프라인이 생겼으니, 이제 그 문자열에서 grapheme cluster 를 어떻게 <b title="안전이 제일이니까요" underline="~ dotted" cursor-help>안전하게</b> 읽어낼지 고민할 차례입니다. 다행히 [`Intl.Segmenter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter) 가 기꺼이 도와줍니다. 로케일을 고려하면서 문자열을 grapheme cluster 로 쪼개는 공식적인 방법을 제공하죠. `Intl.Segmenter` 는 grapheme cluster 전용 도구를 넘어, 옵션에 따라 텍스트를 단어나 문장 단위로 나눌 수도 있습니다.
바이트를 좀 받아서 다음과 같은 grapheme cluster 로 올바르게 디코딩했다고 상상해 봅시다:
<div flex="~ row items-center justify-center gap-1" overflow="x-scroll">
<GraphemeClusterAssembler :characters="pairCluster" />
</div>
이 시점에서 "👩‍👧"(2인) 자체는 하나의 grapheme cluster 입니다. 이걸 꺼내고 다음 바이트를 읽기 시작해도 될까요? 아직입니다. 실제로 바이트가 더 도착하면 앞의 grapheme cluster 는 "👩‍👧‍👦"(3인)가 됩니다:
<div flex="~ row items-center justify-center gap-1" overflow="x-scroll">
<GraphemeClusterAssembler :characters="trioCluster" />
</div>
"👩‍👧"(2인)를 한 스텝 일찍 내보내면 불완전한 grapheme cluster 를 만들어 내게 되는데, 이건 우리가 원하는 게 아닙니다.
## 최대한 빨리, 그러나 안전하게
어떤 상황에서는 (물론 완전한) grapheme cluster 를 가능한 한 빨리 읽어내고 싶을 수 있습니다. 여전히 `Intl.Segmenter` 를 쓰되, 큐에서 꺼내는 전략만 살짝 바꿉니다. 현재 grapheme cluster 가 완전한지 확신할 수 없다면, 다음 것이 나타날 때까지 기다렸다가 마지막 하나를 제외하고 내보내면 됩니다:
```ts
declare let clusterBuffer: string
const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' })
while (true) {
const segments = [...segmenter.segment(clusterBuffer)]
segments.pop() // 마지막 세그먼트는 버린다
for (const seg of segments) {
yield seg.segment // 완전한 grapheme cluster 만 내보낸다
}
}
```
이렇게 하면 불완전할 수 있는 grapheme cluster 는 결코 현재 것이 아니라 항상 다음 것이 됩니다. 이를 보여 주는 인터랙티브 컴포넌트를 하나 더 만들었습니다:
<CharacterMatcher />
<div text-sm text-center>
두 번째 grapheme cluster 가 나타날 때까지 기다렸다가 첫 번째 것을 내보내는 모습을 볼 수 있습니다.
</div>
## [Clustr](https://github.com/sumimakito/clustr) 소개
이 DevLog 를 쓰는 시점에도 문자열을 grapheme cluster 로 쪼개 주는 좋은 라이브러리는 여럿 있습니다. 하지만 그중에 UTF-8 바이트 스트림을 받아서 도착하는 대로 grapheme cluster 를 내보내 주는 건 찾지 못했습니다. 그래서 위에서 설명한 방식으로 직접 하나 만들었고, 유니코드의 "grapheme cluster" 개념과 어감을 맞추려고 [Clustr](https://github.com/sumimakito/clustr) 라고 이름 붙였습니다.
핵심 코드는 총 100줄도 되지 않지만, UTF-8 바이트 스트림으로부터 멋진 텍스트 애니메이션을 만들고 싶은 다음 프로젝트에서 — Project AIRI 에서 저희가 한 것처럼 — 도움이 될지도 모릅니다.
Project AIRI 에서 저희가 하는 일이 궁금하시다면 GitHub 저장소 [moeru-ai/airi](https://github.com/moeru-ai/airi) 를 확인해 보세요!
@@ -0,0 +1,541 @@
---
title: DevLog @ 2025.08.05
description: |
v0.7 이 출시됐습니다. Windows 를 완전히 지원하고 그 밖에도 많은 기능이 들어갔습니다.
date: 2025-08-04
excerpt: 오래 기다리게 해서 죄송합니다!<br/> v0.7 은 7월 초에 나올 예정이었지만, Windows 에서 발견한 몇 가지 치명적인 버그와 손봐야 할 것이 많아 지금까지 미뤄졌습니다.
preview-cover:
light: "@assets('/en/blog/DevLog-2025.08.05/assets/cover-light.avif')"
dark: "@assets('/en/blog/DevLog-2025.08.05/assets/cover-dark.avif')"
---
<script setup lang="ts">
import airiDemoFadeOnHover from '../../../en/blog/DevLog-2025.08.05/assets/airi-demo-fade-on-hover.mp4'
import airiDemoMove from '../../../en/blog/DevLog-2025.08.05/assets/airi-demo-move.mp4'
import airiDemoResize from '../../../en/blog/DevLog-2025.08.05/assets/airi-demo-resize.mp4'
import airiDemoOnboardingLight from '../../../en/blog/DevLog-2025.08.05/assets/airi-demo-onboarding-light.mp4'
import airiDemoOnboardingDark from '../../../en/blog/DevLog-2025.08.05/assets/airi-demo-onboarding-dark.mp4'
import airiDemoOnboardingMobileLight from '../../../en/blog/DevLog-2025.08.05/assets/airi-demo-onboarding-mobile-light.mp4'
import airiDemoOnboardingMobileDark from '../../../en/blog/DevLog-2025.08.05/assets/airi-demo-onboarding-mobile-dark.mp4'
import airiDocsLight from '../../../en/blog/DevLog-2025.08.05/assets/airi-docs-light.mp4'
import airiDocsDark from '../../../en/blog/DevLog-2025.08.05/assets/airi-docs-dark.mp4'
import Button from '../../../../.vitepress/components/Button.vue'
function handleOpenLatest() {
window.open('https://github.com/moeru-ai/airi/releases/latest', '_blank')
}
</script>
다시 안녕하세요! [Neko](https://github.com/nekomeowww) 입니다.
오래 기다리게 해서 죄송합니다! v0.7 은 7월 초에 나올 예정이었지만, 밤잠을 설치게 만든 몇 가지
치명적인 Windows 호환성 문제와 저희가 손대기로 한 변경 범위가 워낙 커서 지금까지 미뤄졌습니다.
<Button @click="handleOpenLatest">
다운로드
</Button>
그래도 지난 두 달 동안 준비한 것을 드디어 나눌 수 있어 설렙니다.
관심 있으실 만한 지난 블로그 & DevLog 글도 확인해 보세요:
- [DreamLog 0x1](../DreamLog-0x1/)
- [DevLog @ 2025.05.16](../DevLog-2025.05.16/)
지난 세 달이 어땠는지 솔직하게 말씀드리자면:
- [**커밋 391개**](https://github.com/moeru-ai/airi/compare/v0.6.1...v0.7.0)
- [**파일 1017개 변경**](https://github.com/moeru-ai/airi/compare/v0.6.1...v0.7.0)
- [**74,548줄 추가**](https://github.com/moeru-ai/airi/compare/v0.6.1...v0.7.0)
- [**13,930줄 삭제**](https://github.com/moeru-ai/airi/compare/v0.6.1...v0.7.0)
> 소프트웨어 업계에서 일해 보신 분들에게 이 숫자들은 아무 의미가 없다는 걸 압니다.
> 그저 이번 릴리스에서 저희가 만든 변화가 크다는 것을 보여 줄 뿐이죠.
>
> 걱정 마세요, 이 DevLog 에서 주요 내용을 하나씩 안내해 드리겠습니다.
## 마일스톤
v0.7 릴리스와 이 DevLog 를 계기로 지금까지 달성한 마일스톤도 몇 가지 언급하고 싶습니다:
- GitHub 스타 1850개를 넘겼습니다! 🎉
- 컨트리뷰터가 40명이 넘습니다! 🫂
- Discord 멤버가 300명이 넘습니다! 👾
- [Hacker News](https://news.ycombinator.com/item?id=44573640) 에 저희를 알렸습니다
- [Product Hunt](https://www.producthunt.com/products/airi) 에 저희를 알렸습니다
- 2025년 7월 17일 GitHub 트렌딩 `1위` 🏆 를 했습니다
## 기능
### 데스크톱 버전
Tamagotchi 는 AIRI 데스크톱 버전의 이름으로, 다른 애플리케이션과 함께 작업을 방해하지 않으면서
바탕화면에서 늘 곁에 있는 별도의 동반자로 실행할 수 있습니다.
이전의 데스크톱 버전은 UI/UX 가 충분히 다듬어지지 않은 실험 단계에 가까웠고,
로컬 ASR/STT(음성 인식) 같은 모듈은 쓸 만하지 않았습니다. 오디오 입력 장치 설정도 빠져 있었죠.
하지만 이제 크게 개선됐습니다.
#### Fade on hover™
지난 v0.6 릴리스에서 **Fade on hover™** 기능을 소개했습니다:
> 농담입니다. 저희는 이 프로젝트를 MIT 라이선스로 오픈소스 공개하고 있고, 이 기능에 등록된
> 상표 같은 건 없습니다.
::: tip
**Fade on hover** 기능을 끄는 기본 단축키는 <kbd aria-label="Shift" data-keyboard-key="shift" inline-block>Shift</kbd> + <kbd aria-label="Alt" data-macos-keyboard-key="option" inline-block>Alt</kbd> + <kbd aria-label="I" inline-block>I</kbd> 입니다
:::
<br />
<ThemedVideo autoplay :src="airiDemoFadeOnHover" />
많은 사용자가 커서를 캐릭터 위에 올릴 때마다 창 전체가 흐려지는 이유를 헷갈려 했습니다.
이 기능을 설명하는 문서가 없었던 점, 그리고 이것이 AI 동반자에게 왜 중요하다고 생각하는지
설명하지 못한 점 사과드립니다.
VTuber 애플리케이션 중 Live2D 와 VRM 3D 모델을 지원하는 가장 인기 있는 둘은 VTuber Studio 와
Warudo 입니다. 이들은 VTuber 방송용으로 설계되어서, OBS(Open Broadcaster Software)로 방송할 때
서로 다른 레이어의 요소로 장면을 구성할 수 있기 때문에 창 순서를 걱정할 필요가 없습니다.
모델 창은 항상 투명 배경의 최소화된 창으로 **백그라운드에** 있고 OBS 나 다른 방송 캡처 드라이버가
이를 캡처합니다.
AIRI 를 VTuber 방송에 쓴다면 Fade on hover 기능이 없어도 괜찮습니다. 하지만 바탕화면 위의
가상 동반자로 함께 지내게 하고 싶다면 곧 이런 점들을 느끼게 됩니다:
- 모델 창을 항상 위에 두도록 설계하면 그 아래 애플리케이션으로 가는 마우스 이벤트를 막아 버립니다.
우리가 원하는 게 아니죠.
- 모델 창의 표시 여부를 매번 직접 토글해야 한다면, 특히 하던 일에 집중할 때 아주 불편합니다.
그래서 이런 아이디어를 냈습니다. 마우스가 창 위에 올라오면 AIRI 안의 캐릭터가 흐려지고,
마우스 클릭 이벤트는 아래 애플리케이션으로 통과시키는 기능을요.
저는 이 기능을 개인적으로 정말 좋아합니다. 이제 창을 끄거나 순서를 정리할 걱정 없이 어떤
애플리케이션을 쓰든 AIRI 의 캐릭터가 곁에 있을 수 있으니까요. AIRI 를 개발하는 날이면 웹 버전이든
데스크톱 버전이든 늘 바탕화면에 그녀를 띄워 두고, 터미널과 VSCode/Cursor 를 함께 켜 둡니다.
**Fade on hover™** 만 업데이트한 건 아닙니다. 데스크톱 버전의 UI/UX 도 많이 개선하고
더 쓸 만하도록 기능을 추가했습니다.
#### 이동
**Fade on hover™** 창은 마우스 이벤트를 통과시키기 때문에, 때로는 모델 창을 더 나은 위치,
예컨대 오른쪽 아래나 아래 가운데로 옮기고 싶을 수 있습니다.
드래그 가능한 영역의 모양도 테마에 맞춰 둥근 모서리로 개선했습니다.
::: tip
이동 모드의 기본 단축키는 <kbd aria-label="Shift" data-keyboard-key="shift" inline-block>Shift</kbd> + <kbd aria-label="Alt" data-macos-keyboard-key="option" inline-block>Alt</kbd> + <kbd aria-label="N" inline-block>N</kbd> 입니다
:::
<br />
<ThemedVideo autoplay :src="airiDemoMove" />
이동 모드에 들어가면 드래그 가능한 영역이 나타납니다. 마우스로 옮기는 것 외에도 트레이 메뉴의
Position > Center / Bottom Left / Bottom Right 를 쓰는 방법도 있습니다.
#### 크기 조절
모두의 모델 크기가 같지는 않으니, 모델 창의 크기를 조절하는 기능도 중요합니다.
이동 모드와 마찬가지로 크기 조절 테두리 표시에도 둥근 모서리를 적용했고, 아바타 가장자리도
둥글게 다듬었습니다.
::: tip
크기 조절 모드의 기본 단축키는 <kbd aria-label="Shift" data-keyboard-key="shift" inline-block>Shift</kbd> + <kbd aria-label="Alt" data-macos-keyboard-key="option" inline-block>Alt</kbd> + <kbd aria-label="R" inline-block>R</kbd> 입니다
:::
<br />
<ThemedVideo autoplay :src="airiDemoResize" />
#### 리소스 아일랜드
ASR/STT(음성 인식)와 VAD(음성 활성 감지) 모델을 불러오는 동안 기다리는 건 괴로운 일이라,
Steam 이나 Battle.net 처럼 모듈과 필요한 파일의 다운로드 진행 상황을 보여 줄 방법을 찾아야 했습니다.
그래서 iOS 의 다이내믹 아일랜드에서 영감을 받아 **리소스 아일랜드** 라는 새 컴포넌트 세트를
디자인했습니다. 떠 있는 형태로 마우스를 올릴 수 있는 위젯이며 모듈의 다운로드·설치 진행 상황을
표시하고, 다운로드가 끝나면 사라집니다.
동작하는 모습을 보세요:
<video autoplay controls muted loop>
<source src="/en/blog/DevLog-2025.08.05/assets/airi-demo-resource-island.mp4" type="video/mp4">
브라우저가 video 태그를 지원하지 않습니다.
</video>
준비 중인 모듈로 가는 링크도 들어 있어서, 모듈 링크를 클릭하면 해당 모듈 설정 페이지가 열려
이 모델이나 파일이 왜 필요한지 확인할 수 있습니다.
#### 로컬 ASR/STT
[@luoling8192 (Luoling)](https://github.com/luoling8192) 과 저장소
[candle-examples](https://github.com/proj-airi/candle-examples) 에서 진행한 실험 덕분에,
이제 Windows, macOS, Linux 에서 동작하는 로컬 ASR/STT 엔진을 갖게 됐습니다.
<video autoplay controls muted loop>
<source src="/en/blog/DevLog-2025.08.05/assets/airi-demo-settings-hearing.mp4" type="video/mp4">
브라우저가 video 태그를 지원하지 않습니다.
</video>
<br />
::: info
이 데모는 OpenAI 의 음성 서비스를 쓰지만, ASR/STT 를 로컬 프로바이더로 전환할 수도 있습니다.
:::
처음에는 candle 을 직접 쓰려 했지만 Windows 와 Linux 빌드에서 (CUDA 유무를 모두 고려해) candle
런타임을 임베드할 좋은 방법을 찾지 못했습니다. 그래서 ort(Rust 용 ONNX Runtime)로 전환했는데,
비슷한 성능과 정확도를 내면서 호환성이 훨씬 좋고 쓰기도 쉽습니다.
### 웹
#### 온보딩
AIRI 설정이 지금 꽤 복잡하다는 걸 압니다 (그래도 코드 구조를 이해해야 설정할 수 있는
순수 Python 기반 프로젝트들에 비하면 여전히 쉽습니다).
[Me1td0wn76 (melty kiss)](https://github.com/Me1td0wn76) 님이 웹 버전에 온보딩 화면 지원을
추가해 주신 덕분에, 처음 AIRI 를 쓸 때 훨씬 나은 경험을 하실 수 있습니다.
이분은 Pull Request 가 머지된 뒤 Project AIRI 에 기여한 경험을 블로그로 나눠 주셨습니다:
[AIRIプロジェクトに参加した話 - YAMA-blog](https://yama-pro.blog/posts/airi/)
<img class="light" src="/en/blog/DevLog-2025.08.05/assets/airi-demo-onboarding-light.avif" alt="온보딩 라이트 모드" />
<img class="dark" src="/en/blog/DevLog-2025.08.05/assets/airi-demo-onboarding-dark.avif" alt="온보딩 다크 모드" />
동작하는 모습을 보세요:
<ThemedVideo
autoplay
:light="airiDemoOnboardingLight"
:dark="airiDemoOnboardingDark"
/>
#### VRM
[Lilia-Chen (Lilia_Chen)](https://github.com/Lilia-Chen) 님의 노고 덕분에 VRM 모델이 정밀한
카메라 구현과 렌더링 메커니즘으로 더 잘 표시됩니다.
<img class="light" src="/en/blog/DevLog-2025.08.05/assets/airi-demo-vrm-light.avif" alt="VRM 라이트 모드" />
<img class="dark" src="/en/blog/DevLog-2025.08.05/assets/airi-demo-vrm-dark.avif" alt="VRM 다크 모드" />
### 모바일 웹
#### 온보딩
온보딩은 모바일 웹 버전에서도 쓸 수 있습니다:
<ThemedVideo
autoplay
:light="airiDemoOnboardingMobileLight"
:dark="airiDemoOnboardingMobileDark"
/>
#### 씬
모바일의 기본 씬을 완전히 다시 디자인하고 새로 작성했습니다.
[LemonNekoGH (LemonNeko)](https://github.com/LemonNekoGH) 덕분에 씬에서 Live2D 모델의 오프셋을
조정하는 더 나은 방법이 생겼습니다.
이 디자인 아이디어는 iOS 측면 볼륨 조절에서 가져왔습니다. 더 직관적이고 명확하게 다루실 수 있기를 바랍니다.
::: tip
기본값으로 되돌리고 싶으신가요? X, Y, Scale 버튼을 두 번 탭하면 기본값으로 초기화됩니다.
:::
<br />
<video class="light" autoplay controls muted loop>
<source src="/en/blog/DevLog-2025.08.05/assets/airi-demo-quick-editor-mobile-light.mp4" type="video/mp4">
브라우저가 video 태그를 지원하지 않습니다.
</video>
<video class="dark" autoplay controls muted loop>
<source src="/en/blog/DevLog-2025.08.05/assets/airi-demo-quick-editor-mobile-dark.mp4" type="video/mp4">
브라우저가 video 태그를 지원하지 않습니다.
</video>
### 양쪽 버전 공통
기능을 위해 흥미로운 새 컴포넌트를 여럿 만들었습니다.
#### 더 나은 텍스트 애니메이션
채팅 말풍선의 텍스트 애니메이션을 개선했습니다. [sumimakito (Makito)](https://github.com/sumimakito/) 가
며칠 전에 이에 대해 아주 훌륭한 DevLog 를 써서, 왜 특별하게 구현했는지와 i18n 호환성을 어떻게 고려했는지
자세히 설명해 주었습니다. 꼭 읽어 보세요: [DevLog 2025.08.01](../DevLog-2025.08.01/).
동작하는 모습을 보세요:
<video class="light" autoplay controls muted loop>
<source src="/en/blog/DevLog-2025.08.05/assets/airi-demo-clustr-light.mp4" type="video/mp4">
브라우저가 video 태그를 지원하지 않습니다.
</video>
<video class="dark" autoplay controls muted loop>
<source src="/en/blog/DevLog-2025.08.05/assets/airi-demo-clustr-dark.mp4" type="video/mp4">
브라우저가 video 태그를 지원하지 않습니다.
</video>
#### 레벨 미터
> UI 컴포넌트: https://airi.moeru.ai/ui/#/story/src-components-gadgets-levelmeter-story-vue
감지된 오디오 입력 레벨이나 실시간 시스템 부하를 표시할 때 유용합니다:
<img class="light" src="/en/blog/DevLog-2025.08.05/assets/airi-ui-level-meter-light.avif" alt="레벨 미터 라이트 모드" />
<img class="dark" src="/en/blog/DevLog-2025.08.05/assets/airi-ui-level-meter-dark.avif" alt="레벨 미터 다크 모드" />
#### 시계열 차트
> UI 컴포넌트: https://airi.moeru.ai/ui/#/story/src-components-gadgets-timeserieschart-story-vue
값이 변하는 것을 보여 준다는 점은 레벨 미터와 비슷하지만, 특히 과거 데이터에 유용합니다.
<img class="light" src="/en/blog/DevLog-2025.08.05/assets/airi-ui-time-series-chart-light.avif" alt="시계열 차트 라이트 모드" />
<img class="dark" src="/en/blog/DevLog-2025.08.05/assets/airi-ui-time-series-chart-dark.avif" alt="시계열 차트 다크 모드" />
이 밖에도 추가한 컴포넌트가 많습니다...
- [x] `<Progress />` (@Menci 님 감사합니다 [2cb602aa](https://github.com/moeru-ai/airi/commit/2cb602aa3eac456a479b622a5ecf043831597ffe))
- [x] `<FieldSelect />` ([d0d782ff](https://github.com/moeru-ai/airi/commit/d0d782ff94a5a0a12819725303f687bd1a47e87c))
- [x] `<Alert />` ([@typed-sigterm](https://github.com/typed-sigterm) 님 감사합니다, [#295](https://github.com/moeru-ai/airi/pull/295))
- [x] `<ErrorContainer />` ([@typed-sigterm](https://github.com/typed-sigterm) 님 감사합니다, [#295](https://github.com/moeru-ai/airi/pull/295))
- [x] 새 사이드바 내비게이션 디자인
- [x] Toaster
- [x] 새 버전이 나오면 사용자에게 업데이트를 알리는 안내
## 커뮤니티
### 새 문서 사이트
이제 완전히 새로운 문서 사이트가 생겼습니다:
<ThemedVideo
autoplay
:light="airiDocsLight"
:dark="airiDocsDark"
/>
정말 멋져 보입니다. [Reka UI](https://reka-ui.com) 의 작업을 바탕으로 완전히 새로 쓰면서
블로그 글 목록, 언어 전환 등 많은 기능을 추가하고 VitePress 에 맞게 여러 스타일을 조정했습니다.
그리고 언제나처럼, 아름다운 디자인을 만들어 준 그들에게 감사드립니다. 저희 것을 만들 때 그들의
컴포넌트를 많이 쓰고 있으니 꼭 살펴보세요!
블로그 페이지도 [@lynzrand (Rynco Maekawa)](https://github.com/lynzrand) 가 디자인한 새 커버와 함께
더 보기 좋아졌습니다.
<img class="light" src="/en/blog/DevLog-2025.08.05/assets/airi-docs-blogs-light.avif" alt="문서 블로그 라이트 모드" />
<img class="dark" src="/en/blog/DevLog-2025.08.05/assets/airi-docs-blogs-dark.avif" alt="문서 블로그 다크 모드" />
### 번역 워크플로 변경
이른바 `i18n` 또는 로케일 파일을 저희 거대한 모노레포 안의 전용 패키지로 분리했습니다.
새 로케일을 추가하거나 번역을 추가·수정해 기여하실 때는 먼저
https://github.com/moeru-ai/airi/tree/main/packages/i18n/src/locales 로 가 주세요.
<img class="light" src="/en/blog/DevLog-2025.08.05/assets/airi-packages-i18n-light.avif" alt="packages/i18n 라이트 모드" />
<img class="dark" src="/en/blog/DevLog-2025.08.05/assets/airi-packages-i18n-dark.avif" alt="packages/i18n 다크 모드" />
여기서 언어별 디렉터리를 찾을 수 있습니다. 원하는 것을 골라 진행하세요.
영어를 예로 들면 디렉터리 구조는 이렇습니다:
```bash
└── en
├── docs
├── tamagotchi
#
├── base.yaml
├── settings.yaml
├── stage.yaml
└── index.ts
```
`docs``tamagotchi` 는 서로 구별되는 두 모듈을 위한 디렉터리입니다:
- 문서 사이트
- 데스크톱 버전 (Tamagotchi)
문서 사이트(글이나 실제 문서가 아니라 UI)의 번역을 돕고 싶으시다면 `docs` 디렉터리로 가서
문서 사이트의 UI 문자열이 담긴 `theme.yaml` 파일을 편집하시면 됩니다.
`tamagotchi` 디렉터리는 조금 특별해서 모든 번역 문자열을 찾을 수는 없습니다. 데스크톱 버전에서만
쓰이는 특별한 번역 몇 가지를 담고 있고, 나머지는 모두 루트 디렉터리에 있습니다.
`docs``tamagotchi` 외의 것들은:
- `base.yaml` 은 언어와 버튼 기본 상태 등 필수 문자열을 담습니다
- `settings.yaml` 은 설정 페이지의 문자열을 담습니다
- `stage.yaml` 은 스테이지(모델이 표시되는 UI)의 문자열을 담습니다
언어를 더 추가하고 싶으시다면 기존 언어 로케일 디렉터리 하나를 복사해 새 언어 코드로 이름을 바꾸세요.
예를 들어 프랑스어를 추가하려면 `en` 디렉터리를 `fr` 로 복사한 뒤 `base.yaml`, `settings.yaml`,
`stage.yaml`, `index.ts` 파일을 편집해 번역을 추가하면 됩니다. Pull Request 리뷰 과정에서
일부만 번역해도 괜찮습니다.
::: info 도움을 구합니다!
좀 우습게 들릴 수 있지만, 저희 i18n 패키지를 [Crowdin](https://crowdin.com) 이나
[Weblate](https://weblate.org/en/) 같은 번역 자동화 도구와 통합해 주실 경험 있는 분을 찾고 있습니다.
저희는 이 분야 전문가가 아니니, 도와주실 Pull Request 를 열거나 논의를 위한 이슈를 편하게 열어 주세요.
:::
언어 코드는 아래 도구 중 하나로 작업 중인 언어의 코드를 찾아 사용해 주세요:
- [Language subtag lookup app](https://r12a.github.io/app-subtags/)
- [iana.org/assignments/language-subtag-registry/language-subtag-registry](https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry)
```bash
.
├── packages
├── i18n
├── package.json
└── src
├── index.ts
└── locales
├── en
│ ├── base.yaml
│ ├── docs
│ │ ├── index.ts
│ │ └── theme.yaml
│ ├── index.ts
│ ├── settings.yaml
│ ├── stage.yaml
│ └── tamagotchi
│ ├── index.ts
│ ├── settings.yaml
│ └── stage.yaml
├── index.ts
└── zh-Hans
├── base.yaml
├── docs
│ ├── index.ts
│ └── theme.yaml
├── index.ts
├── settings.yaml
├── stage.yaml
└── tamagotchi
├── index.ts
├── settings.yaml
└── stage.yaml
```
이에 대한 자료는 여기서 더 읽어 보실 수 있습니다:
- https://developer.mozilla.org/en-US/docs/Glossary/BCP_47_language_tag
- https://en.wikipedia.org/wiki/IETF_language_tag
- https://en.wikipedia.org/wiki/ISO_15924
## 엔지니어링
### 워크플로를 몇 배 빠르게 만든 툴체인
요약:
- 여러 패키지를 **buildless** 구성으로 전환했습니다
- `unbuild``stub` 를 걷어냈습니다
- `rolldown-vite` 로 전환했습니다
- `unbuild``tsdown` 으로 교체했습니다
- 더 빠르고 캐시되는 빌드를 위해 `turborepo` 를 도입했습니다
좀 더 자세히 말하면:
이전에는 모노레포 아키텍처를 택하면서 매끄러운 개발 경험을 위해, 컨트리뷰터가 프로젝트를 클론한 뒤
의존성을 설치할 때마다 `postinstall` 스크립트로 각 패키지의 `jiti` export 와 `.d.ts` 모듈을 스텁으로
만들어 부트스트랩해야 했습니다.
덕분에 컨트리뷰터가 모노레포 동작 방식을 배우지 않아도 기여할 수 있었습니다. 하지만 `pnpm install`
때마다 다시 빌드하고 다시 스텁을 만드는 건 분명 영리한 전략이 아니었죠.
[@kwaa](https://github.com/kwaa) 가 도입한 buildless 아키텍처 변경 덕분에, 시간이 가장 오래 걸리던
최대 패키지 `stage-ui` 를 타입 체크나 의존성 해석 문제 없이 건너뛸 수 있게 됐습니다.
이후 [@kwaa](https://github.com/kwaa) 는 `unbuild` 가 가져오던, 때때로 문제를 일으키는 중복
`stub` 스크립트도 제거해 주었습니다. 덕분에 성가신
`The requested module './dist/index.mjs' does not provide an export named 'foo'`
오류와 더는 씨름하지 않는 훨씬 깔끔한 워크플로가 됐습니다.
가장 큰 변화는 두 달 전, [@kwaa](https://github.com/kwaa) 가 `vite``rolldown-vite` 로 교체해
**워크플로를 2배 빠르게** 만든 것입니다.
여기서 멈추지 않고 `unbuild``tsdown` 으로 교체해 **추가로 4.2배 속도 향상**을 얻었고,
이제 각 하위 패키지 빌드가 250ms 미만으로 끝납니다.
> `tsdown` 으로 옮기면서 얻은 이점은 더 있습니다...
>
> - 사용하지 않는 의존성 검사
> - CSS 번들링
> - Vue SFC 컴포넌트 번들링
`postinstall` 스크립트는 여전히 필요하지만, 의존성을 인식해 빌드 결과를 캐시할 방법을 찾으면
중복 빌드를 많이 피할 수 있습니다. 여기서 `turborepo` 가 빌드를 더 빠르게 만들어 줍니다.
`turborepo` 덕분에 AIRI 빌드 시간이 **평균 4분에서 25초로 줄었습니다**.
### 이제 Nix 를 지원합니다
[@Weathercold (Weathercold)](https://github.com/Weathercold) 덕분에 AIRI 를 빌드하는 Nix flake 가
생겼습니다. 크로스 플랫폼 호환성에 훌륭한 보탬이 되죠. macOS 에서도 동작합니다.
nix-pkgs 로 들어갈 최종 Pull Request 가 머지되기를 기다리고 있지만, 다음 명령으로 미리 써 보실 수 있습니다:
```bash
nix run --extra-experimental-features 'nix-command flakes' github:moeru-ai/airi
```
### 통합된 빌드 파이프라인
이전에는 테스트, 스테이징, 릴리스의 빌드 파이프라인이 전부 달라서, 파이프라인이 성공할지 확신할 수 없어
새 버전을 낼지 결정하는 게 저에게는 악몽이었습니다.
Tauri 가 크로스 플랫폼 호환성과 Rust 로 syscall 을 하고 네이티브 OS 기능에 통합하는 강력한 능력이라는
이점을 많이 준 건 사실이지만...
v0.7 개발 초기에 저는 ASR/STT 파이프라인의 추론 엔진 구현으로
[huggingface/candle](https://github.com/huggingface/candle) 을 도입했는데, NVIDIA CUDA 에 의존해서
빌드가 정말 엉망이었고 호환성 문제가 도처에 있었습니다.
하지만 이제 훨씬 나아졌습니다. 릴리스와 동일한 스크립트와 워크플로 단계를 매일 실행하는 예약 빌드
파이프라인이 생겼습니다. (`canary``nightly` 빌드라고 들어 보셨을 겁니다.)
그래서 최신 릴리스에서 문제를 겪으신다면, `main` 브랜치의 최신 빌드를 받아 문제가 고쳐졌는지
언제든 확인해 보실 수 있습니다.
나이틀리 빌드는 https://github.com/moeru-ai/airi/actions/workflows/release-tamagotchi.yml 에서 찾을 수 있습니다.
## 마치기 전에...
이번 릴리스 사이에 태어난 새 패키지들:
> [@sumimakito](https://github.com/sumimakito) 에게 큰 박수를. 정말 환상적인 일을 너무 많이 해서
> 다 세지도 못하겠네요...
- [`@proj-airi/chromatic`](https://github.com/proj-airi/chromatic) ([@sumimakito](https://github.com/sumimakito))
- [`@proj-airi/unocss-preset-chromatic`](https://github.com/proj-airi/chromatic) ([@sumimakito](https://github.com/sumimakito))
- [`@moeru-ai/jem`](https://github.com/moeru-ai/inventory/tree/main/packages/jem-validator) ([@LemonNekoGH](https://github.com/LemonNekoGH)), 통합 모델 카탈로그
- [`clustr`](https://github.com/sumimakito/clustr) ([@sumimakito](https://github.com/sumimakito))
- [`@proj-airi/drizzle-orm-browser`](https://github.com/proj-airi/drizzle-orm-browser) (제가 만들었습니다)
이번 릴리스 사이에 태어난 사이드 프로젝트들:
- [HuggingFace Inspector](https://hf-inspector.moeru.ai/) (https://github.com/moeru-ai/hf-inspector)
- [whisper & VAD, candle, burn, ort 에 관한 더 많은 candle 예제](https://github.com/proj-airi/candle-examples)
- [(모델 카탈로그) Inventory 제출!](https://github.com/moeru-ai/inventory/pull/1) ([@LemonNekoGH](https://github.com/LemonNekoGH))
이 DevLog 에 모든 것을 담을 수는 없습니다. 자세한 내용은 저희 로드맵의
[Roadmap v0.7](https://github.com/moeru-ai/airi/issues/200) 에서 언제든 확인하실 수 있습니다.
<div class="w-full flex flex-col items-center justify-center gap-3 py-3">
<img src="/en/blog/DevLog-2025.08.05/assets/relu-sticker-thinks.avif" alt="ReLU 스티커 thinks" class="w-30!" />
<div class="text-center">
<span class="block font-bold">여기까지 읽어 주셔서 감사합니다!</span>
</div>
</div>
@@ -0,0 +1,199 @@
---
title: DevLog @ 2025.08.26
category: DevLog
date: 2025-08-26
excerpt: |
`airi-factorio` 의 순수 비전 방향에서 이룬 진전을 공유하며, 생각이 증발하기 전에 붙잡아 둡니다.
preview-cover:
# TODO
---
<script setup lang="ts">
import airiFactorioYoloV0PlaygroundVnc from '../../../en/blog/DevLog-2025.08.26/assets/airi-factorio-yolo-v0-playground-vnc.mp4'
import NmsIou from '../../../en/blog/DevLog-2025.08.26/components/nms-iou.vue'
</script>
오랜만입니다, 여러분! AIRI 메인테이너 중 한 명인 [@LemonNeko](https://github.com/LemonNekoGH) 입니다. ~~아, 이렇게 시작하는 것도 슬슬 지겹네요. 꼭 LLM 같잖아요.~~
지난 [DevLog](../DevLog-2025.07.18/) 에서는 [Factorio Learning Environment](https://arxiv.org/abs/2503.09617) 논문을 간단히 살펴보고 `airi-factorio` 를 어떻게 개선할지 이야기했습니다. 그런데... 오늘 나눌 이야기는 그것이 아니라 순수 비전 방향에서의 진전입니다.
올해 6월, [@nekomeowww](https://github.com/nekomeowww) 가 거의 실시간으로 동작하는 [VLM Playground](https://huggingface.co/spaces/moeru-ai/smolvlm-realtime-webgpu-vue) HuggingFace Space 를 공개했는데 정말 멋져 보였습니다. 그래서 먼저 간단한 실시간 이미지 인식(당시엔 객체 탐지와 이미지 인식을 헷갈렸습니다)을 시도하고, 어떻게든 AI 에게 넘겨 판단하게 한 뒤, 어떤 방식으로든 게임에 동작을 출력하기로 했습니다.
먼저 결과부터 보여 드리겠습니다:
<ThemedVideo :src="airiFactorioYoloV0PlaygroundVnc" controls playsinline />
영상에서 저는 웹 페이지의 VNC 연결로 Factorio 를 플레이하고 있고, 오른쪽에는 객체 탐지 결과가 거의 실시간으로 표시됩니다. [HuggingFace Space](https://huggingface.co/spaces/proj-airi/factorio-yolo-v0-playground) 에도 배포했으니 편하게 써 보세요.
그럼 이걸 어떻게 구현했을까요?
## Factorio 클라이언트를 Docker 에 넣기
AI 가 게임 화면을 보게 하려면 Factorio 가 창 크기나 위치 같은 것에 영향받지 않는 통제된 환경에서 돌아가야 합니다. 동시에 이 환경이 바로 쓸 수 있는 상태이길 원했기에, Factorio 를 Docker 에 넣기로 했습니다.
Factorio 는 공식 [Docker 이미지](https://hub.docker.com/r/factoriotools/factorio)를 제공하지만 이는 순수 서버용입니다. AI 가 화면을 보고 게임을 조작하게 하려면 클라이언트가 필요한데, 기존 Docker 이미지를 찾을 수 없었고 (Factorio 라이선스 계약상 클라이언트를 이런 식으로 배포할 수도 없습니다) 직접 패키징해야 했습니다 (그리고 패키징한 클라이언트 이미지도 배포할 수 없어서 Dockerfile 만 공유할 수 있습니다).
그럼 Factorio 클라이언트~~라는 코끼리~~를 Docker~~라는 냉장고~~에 넣으려면 몇 단계가 필요할까요~~?~~
1. Factorio 클라이언트 다운로드: 당연히 주인공이죠.
2. 가상 디스플레이 준비: 그래픽 애플리케이션은 화면을 표시할 디스플레이가 필요합니다.
3. VNC 서비스 준비: 가상 디스플레이의 내용을 읽어 외부 VNC 클라이언트로 화면을 전송하고, 사용자 입력을 게임에 전달할 수 있습니다.
뭔가 빠진 것 같나요? 아, 오디오요? 무슨 오디오요? 없습니다. 지금의 AI 는 아직 소리를 듣지 못하니 일단 무시하겠습니다.
### Factorio 클라이언트 다운로드
Factorio 공식 사이트에서 바로 받을 수 있지만 수동 로그인이 필요해서 자동화 워크플로에는 불편합니다. 그래서 다운로드 스크립트 [factorio-dl](https://github.com/moviuro/factorio-dl/) 을 찾았습니다. 사용자 이름, 비밀번호, 받을 버전을 주면 시스템 아키텍처에 맞는 클라이언트를 자동으로 내려받아 주는, 아주 복잡한 셸 스크립트입니다.
### 가상 디스플레이 준비
이 단계는 조금 더 복잡하지만 전체 데스크톱 환경을 설치하는 것만큼은 아닙니다. 이때 그래픽 애플리케이션이 반드시 데스크톱 환경이나 윈도우 매니저를 필요로 하지 않고, 최소한의 X 환경과 디스플레이 서버만 있으면 된다는 것도 배웠습니다.
아주 간단합니다:
```bash
sudo apt install -y xvfb x11-apps mesa-utils
```
여기서:
- `xvfb` 는 가상 프레임버퍼이자 X 서버입니다.
- `x11-apps` 는 X 관련 도구 모음으로, 설치하면 X 환경도 함께 설치됩니다.
- `mesa-utils` 는 Mesa 관련 도구 모음입니다. Mesa 는 OpenGL 의 소프트웨어 구현이며, OpenGL 애플리케이션을 테스트하고 디버깅하는 데 도움이 되는 도구를 제공합니다.
### VNC 서비스 준비
VNC 는 Virtual Network Computing 의 약자로, 마치 그 앞에 앉아 있는 것처럼 다른 컴퓨터를 원격으로 제어할 수 있게 해 주는 원격 데스크톱 프로토콜입니다.
```bash
sudo apt install -y x11vnc
```
여기까지 하면 Docker 에서 Factorio 클라이언트를 실행하고 VNC 로 제어할 수 있습니다.
하지만 아직 부족합니다. 제 목표는 브라우저에서 플레이하면서 실시간으로 객체 탐지 추론을 돌리는 것입니다. 그런데 브라우저는 HTTP 프로토콜만 쓸 수 있으므로, VNC 프로토콜을 HTTP 로 변환해 줄 `websockify` 같은 도구가 필요합니다. 또 디버깅 편의를 위해 VNC 화면을 보여 줄 웹 인터페이스도 필요해서 `novnc` 도 설치합니다.
```bash
sudo apt install -y websockify novnc
```
좋습니다! 이제 Docker 이미지가 준비됐습니다. 전체 [Dockerfile](https://github.com/moeru-ai/airi-factorio/blob/a6bf243f14cbc0d765ff7ed13389bca33c1fdfa2/docker/Dockerfile) 과 [사용 안내](https://github.com/moeru-ai/airi-factorio/tree/ba46a4e47b31187dd064b06314b595b551ed3411/apps/factorio-yolo-v0-playground)는 여기서 볼 수 있습니다.
## 객체 탐지 모델 학습
빠른 검증을 위해 YOLO11n 의 사전 학습 모델을 기반으로 저희 객체 탐지 모델을 학습시켰습니다.
### 데이터셋 준비
데이터셋은 이렇게 수집했습니다:
1. [`surface.create_entity`](https://lua-api.factorio.com/latest/classes/LuaSurface.html#create_entity) 함수로 씬의 임의 위치에 기계를 배치하고, 선택 박스 크기와 위치를 함께 얻습니다.
2. [`game.take_screenshot`](https://lua-api.factorio.com/latest/classes/LuaGameScript.html#take_screenshot) 으로 다양한 줌 레벨과 조명 조건(낮)에서 스크린샷을 찍습니다.
3. 선택 박스를 바탕으로 어노테이션 데이터를 생성하고 [`helpers.write_file`](https://lua-api.factorio.com/latest/classes/LuaHelpers.html#write_file) 로 파일에 저장합니다.
제 수집 스크립트는 [여기](https://github.com/moeru-ai/airi-factorio/blob/ba46a4e47b31187dd064b06314b595b551ed3411/packages/factorio-rcon-snippets-for-node/src/factorio_yolo_dataset_collector_v0.ts)에 있습니다. `typescript-to-lua` 로 TypeScript 를 Lua 로 컴파일한 뒤 RCON 으로 Factorio 에 넘겨 실행합니다.
스크립트에서는 조립기 3종과 컨베이어를 수집했고, 기계마다 이미지 20장씩, 각 이미지는 UI 없이 1280x1280 해상도로 찍었습니다.
아 참, 수집 스크립트를 더 잘 디버깅하려고 [VSCode 플러그인](https://github.com/moeru-ai/airi-factorio/blob/ba46a4e47b31187dd064b06314b595b551ed3411/packages/vscode-factorio-rcon-evaluator/README.md)도 만들었습니다. CodeLens 로 클릭 한 번에 스크립트를 컴파일하고 실행할 수 있습니다.
이미지와 어노테이션 데이터를 모은 뒤에는 [YOLO 공식 형식](https://docs.ultralytics.com/datasets/detect/)에 맞춰 데이터셋을 정리하고, [Ultralytics Hub](https://www.ultralytics.com/hub) 에 업로드해 결과를 확인합니다:
![Ultralytics Hub](/en/blog/DevLog-2025.08.26/assets/factorio-ultralytics-hub-preview.jpg)
꽤 괜찮아 보이죠? 학습을 시작해 봅시다!
### 모델 학습
이제 막 시작한 단계라 [Get Started](https://docs.ultralytics.com/tasks/detect/) 에서 이 몇 줄을 그대로 복사했습니다:
```python
from ultralytics import YOLO
model = YOLO("yolo11n.pt")
model.train(data="./dataset/detect.yaml", epochs=100, imgsz=640, device="mps")
model.export(format="onnx")
```
640x640 해상도로 MPS 디바이스를 써서(macOS 에서는 MPS 디바이스가 성능이 더 좋습니다) 100 에포크 학습했고, 에포크당 배치는 5개, 대략 70 에포크쯤에서 최적 성능에 도달했으며 ONNX 모델로 내보냈습니다. 학습에는 약 8분이 걸렸고 모델 크기는 약 10MB 입니다.
데이터셋, 학습 코드, 내보낸 ONNX 모델은 [여기](https://github.com/moeru-ai/airi-factorio/blob/ba46a4e47b31187dd064b06314b595b551ed3411/apps/factorio-yolo-v0-playground)에서 볼 수 있습니다.
## 추론 수행하기
이제 위 두 부분을 조립할 수 있습니다. 저는 다음을 사용했습니다:
1. `@novnc/novnc` 로 브라우저에 VNC 화면을 표시하면서 캔버스 데이터를 뽑아 모델에 먹입니다.
2. `onnxruntime-web` 으로 브라우저에서 추론을 수행합니다. WebGPU 를 지원해서 GPU 성능을 활용할 수 있습니다.
처음에는 추론이 400ms 정도로 매우 느렸고 UI 까지 멈춰 버려 VNC 를 쓸 수 없었습니다. 급히 WebWorker 사용법을 익혀 추론과 화면 표시를 분리해 이 문제를 해결했습니다. 그리고 사실 WebGPU 가 켜져 있지 않았다는 것도 알게 됐는데, 그래서 속도가 여전히 느렸던 것이죠.
```typescript
ort.InferenceSession.create(model, { executionProviders: ['webgpu', 'wasm'] })
```
WebGPU 와 WASM 실행 방식을 모두 허용한다고 명시해야, WebGPU 를 쓸 수 없을 때 자동으로 WASM 실행으로 전환됩니다.
WebGPU 를 켜자 추론 속도가 약 80ms 로 개선됐습니다. 여전히 만족스럽지 않았지만 더 최적화할 방법을 몰랐죠. 그때 Cursor 가 이렇게 알려 줬습니다: "픽셀 색상 값을 정규화할 때 계속 255 로 나누고 있습니다. `1/255` 를 먼저 계산해 두고 그 값을 곱해서 나눗셈을 피하세요."
네? 잠깐, 나눗셈이 곱셈보다 느리다고요? 건너뛴 컴퓨터 과학 수업을 정말 보충해야겠네요.
Cursor 의 제안대로 코드를 고치자 추론 속도가 약 20ms 로 개선됐습니다. 이제 체감이 꽤 좋습니다.
앞에서 모델 출력을 처리하는 부분은 건너뛰었는데, 이제 살펴봅시다.
### 모델 출력 처리하기
모델은 원소 84,000개짜리 배열과 `dims``[1, 10, 8400]` 인 배열을 출력합니다. 즉 84,000개 원소가 10개씩 묶여 있고, 각 묶음은 바운딩 박스 중심의 x, y 좌표, 박스의 너비와 높이, 그리고 6개 카테고리의 신뢰도 점수를 담고 있으며, 총 8,400개의 결과 묶음이 됩니다.
임계값 0.6 으로 신뢰도가 낮은 바운딩 박스를 걸러낸 뒤에도, 겹치는 박스를 제거하기 위해 NMS 방법으로 IOU 를 써야 합니다.
IOU 와 NMS 에 대해서는 [이 글](https://medium.com/@jesse419419/understanding-iou-and-nms-by-a-j-dcebaad60652)을 참고하세요. 간단히 말하면 두 박스의 넓이를 더한 뒤 겹치는 넓이를 빼서 실제 차지하는 넓이를 구하고, 겹치는 넓이를 실제 차지하는 넓이로 나눠 IOU 를 얻는 것입니다.
저는 아주 단순한 NMS 구현을 썼습니다. 모든 바운딩 박스를 신뢰도로 정렬한 뒤 높은 것부터 순회하며, IOU 가 0.7 보다 크면 같은 객체로 보고 걸러냅니다.
```typescript
function nms(boxes: Box[], iouThreshold: number): Box[] {
// 1. 신뢰도로 걸러낸 뒤 내림차순 정렬
const candidates = boxes
.filter(box => box.confidence > 0.6)
.sort((a, b) => b.confidence - a.confidence)
const result: Box[] = []
while (candidates.length > 0) {
// 2. 신뢰도가 가장 높은 박스를 고른다
const bestCandidate = candidates.shift()!
result.push(bestCandidate)
// 3. 남은 박스들과 비교해 IOU 가 높은 것을 제거한다
for (let i = candidates.length - 1; i >= 0; i--) {
// iou() 함수는 글에서 설명한 대로 따로 구현해야 합니다.
if (iou(bestCandidate, candidates[i]) > iouThreshold) {
candidates.splice(i, 1)
}
}
}
return result
}
```
Playground 전체 소스 코드는 [여기](https://github.com/moeru-ai/airi-factorio/tree/ba46a4e47b31187dd064b06314b595b551ed3411/apps/factorio-yolo-v0-playground)에서 볼 수 있습니다.
아래 시각화 컴포넌트에서 라벨을 드래그해 박스 위치를 바꿔 가며 IOU 와 NMS 효과를 직접 만져 볼 수도 있습니다:
<div class="flex justify-center">
<NmsIou />
</div>
### 발견한 문제들
이번 실습을 통해 몇 가지 문제를 발견했습니다:
1. 정사각형이 아닌 이미지를 인식하지 못함: 정사각형이 아닌 이미지를 만나면 모든 결과의 신뢰도가 매우 낮아지거나 심지어 0 이 됩니다.
2. 모델이 1티어와 2티어 조립기를 구분하기는 하지만, 상자처럼 네모난 물체도 조립기로 인식합니다.
3. 실제 플레이에서는 기계 텍스처 위에 전력, 현재 레시피, 장착된 모듈 같은 상태 표시가 겹쳐 있어 모델 인식을 방해합니다.
## 맺으며
이것이 이번 달 작업의 결과입니다. 꽤 알찼네요! 도움을 준 [@nekomeowww](https://github.com/nekomeowww), [@dsh0416](https://github.com/dsh0416), [makito](https://github.com/sumimakito) 에게 깊이 감사드립니다. 다음으로는 모델 성능을 개선할 방법을 찾고, 어떻게든 AI 가 게임을 조작하게 만들어야 합니다.
@@ -0,0 +1,97 @@
---
title: DevLog @ 2025.10.20
category: DevLog
date: 2025-10-20
excerpt: |
Tauri 에서 Electron 으로의 마이그레이션, 새 Live2D 모델, 그리고 여러 오픈소스 프로젝트 업데이트까지 AIRI 프로젝트의 최근 진행 상황을 나눕니다.
preview-cover:
# TODO
---
오랜만입니다, 여러분!
요즘 AI 트레이딩 봇이 엄청 뜨겁죠. 저희도 비슷한 연구를 나눌 게 있는데, 우선 개발 이야기부터 시작하겠습니다...
## Tauri 에서 Electron 으로의 마이그레이션
며칠 전 Tauri 가 다시 화제가 됐죠. 저희는 3월에 일찌감치 도입했고 플러그인 설계가 마음에 들어서 crate 도 잔뜩 감쌌습니다. 6월에 드디어 v0.7.2 를 릴리스했지만, 모두가 원하던 음성 대화를 제공하려고 3개월을 고생했습니다... 3개월요... Tauri 의 WebKit 과 지독히 까다로운 Web Audio API, DevTools 와 씨름하면서... 9월까지 계속요...
...결국 더는 못 버티고, 국경절 연휴에 Electron 으로 완전히 갈아탔습니다!
<img src="/en/blog/DevLog-2025.10.20/assets/electron.png" alt="electron.png" />
이제 기존 Electron 기반 위에 Linux 지원을 추가했고, 저희가 컨트롤 아일랜드라고 부르는 것을 도입했으며, macOS 전체 화면 모드에서도 인터페이스 위에 겹쳐 띄울 수 있게 됐습니다.
호환성이 훌륭해서 정말 마음에 듭니다. 어제는 마침내 자막 오버레이가 동작하게 되어서, 이제 Neuro-sama 처럼 자막으로 AI 가 무엇을 출력하는지 볼 수 있습니다!
<img src="/en/blog/DevLog-2025.10.20/assets/control-island.png" alt="control-island.png" />
<div style="text-align: center; font-size: 0.875rem; color: #666; margin-top: 0.5rem;">
컨트롤 아일랜드
</div>
## 새 Live2D 모델
눈썰미 좋은 분들은 눈치채셨을 텐데, 모델이 업데이트됐습니다! 네, 업데이트됐어요! 이 새 모델이 정말 마음에 듭니다 (아쉽게도 아직 오픈소스 저장소에 바로 넣을 준비는 되지 않았습니다).
이 모델은 Neuro-sama 공식 팀과 함께 작업한 적 있는 아티스트, 그리고 실력이 대단한 모델링 전문가와 협업해 영광스럽게 개선한 결과물입니다. 새 애니메이션 표정도 정말 풍부합니다.
(속삭이며) 스폰서가 더 늘어나면 어쩌면 저도... (x
<video src="/en/blog/DevLog-2025.10.20/assets/airi.mp4" alt="airi.mp4" controls></video>
## Three.js MMD 지원
여러분이 가지고 있거나 구할 수 있는 모델이 전부 Live2D/VRM 은 아닐 겁니다. 사실 가장 풍부하고 좋은 건 여전히 MMD 모델이죠.
저희도 3D 렌더링에 Three.js 를 쓰고 있지만, 현실적으로 Three.js 에는 더 이상 동작하는 MMD 구현이 없습니다. kwaa 의 작업 덕분에 이제 이를 위한 저장소가 생겼습니다!
관심 있으시다면 함께 유지보수해 주세요! [moeru-ai/three-mmd](https://github.com/moeru-ai/three-mmd)
## Velin: Vue 로 프롬프트 작성하기
>"[Vue](https://velin-dev.netlify.app/#/) 로 프롬프트를 작성할 수 있습니다"!
5월에 저희 프롬프트 라이브러리를 소개했던 걸 기억하시나요? RainbowBird 의 노력과 기여 덕분에 Velin 이 이제 정식으로 Moeru AI 의 일부가 됐습니다! AIRI 의 거의 모든 프롬프트가 Velin 으로 돌아가는데, 크로스 플랫폼 걱정은 마세요. Velin 은 Node.js 환경에서도 잘 동작합니다!
<img src="/en/blog/DevLog-2025.10.20/assets/velin.png" alt="velin.png" />
## Eventa: 이벤트 기반 IPC/RPC
>"Events are all you need"
Vercel AI SDK 와 비슷한 방식으로 브라우저에서 순수 로컬 추론을 할 수 있게 해 주는 프로젝트 [netlify](https://velin-dev.netlify.app/#/) 를 소개한 적이 있습니다.
이런 로컬 추론은 전부 Web Worker / worker_threads 에서만 돌 수 있고, 이들은 이벤트로 통신합니다. Electron IPC 도 마찬가지인데, 저희는 그게 충분히 우아하지 않다고 느꼈습니다. RainbowBird 덕분에 이제 이벤트 기반 IPC/RPC 구현을 이끄는 라이브러리 eventa 가 생겼습니다. [Eventa](https://github.com/moeru-ai/eventa) 도 이제 정식으로 Moeru AI 의 일부입니다!
## 프로젝트 개발 현황
이제 Moeru AI 와 Project AIRI 는 거대한 조직으로 성장해, 머신러닝·데이터 처리·프론트엔드·백엔드 등을 아우르는 50개 이상의 자체 저장소를 TypeScript/Python/Rust/Go 등 여러 언어로 운영하고 있습니다.
전체 팔로워 수는 800명을 넘었습니다. 1년 전 처음 시작할 때는 상상도 못 했던 일입니다. 정말로, 성원해 주셔서 진심으로 감사합니다!
<img src="/en/blog/DevLog-2025.10.20/assets/moeru.png" alt="moeru.png" />
<div style="text-align: center; font-size: 0.875rem; color: #666; margin-top: 0.5rem;">
Moeru AI
</div>
<img src="/en/blog/DevLog-2025.10.20/assets/project-airi.png" alt="project-airi.png" />
<div style="text-align: center; font-size: 0.875rem; color: #666; margin-top: 0.5rem;">
Project AIRI
</div>
## 순수 Rust TTS 구현
작은 예고: 최근 kwaa 와 팀을 이뤄 잘 알려진 TTS 모델 chatterbox 를 순수 Rust 구현으로 포팅했습니다. 이제 까다로운 Python 환경 설정으로 골머리를 앓지 않아도 됩니다!
4080S 기준 한 번에 약 5초 추론. 정말 마음에 듭니다.
Python 모델 아키텍처를 사실상 1:1 로 Rust 에 재현했고, 다른 SOTA TTS 모델까지 활용하는 아주 간결한 로컬 TTS 추론 엔진으로 발전시키고 싶습니다.
<img src="/en/blog/DevLog-2025.10.20/assets/rust-tts.png" alt="rust-tts.png" />
## 마치며
오늘의 "one more thing" 은 여기까지입니다. 연달아 이어진 긴 스레드를 즐기셨기를 바랍니다!
내일도 계속 업데이트해서 더 많은 이야깃거리를 가져오겠습니다. VLA/VLM 게이밍 분야에서의 탐구, 어떻게 접근하고 있는지, 어떤 결과를 보고 있는지 소개하겠습니다.
@@ -0,0 +1,132 @@
---
title: DevLog @ 2026.01.01
category: DevLog
date: 2026-01-01
excerpt: |
AIRI 의 iOS 플랫폼 진전과 그 과정에서 만난 문제·해결책, 그리고 LemonNeko 가 FlowChat 에서 진행한 기억 계층 실험의 성과와 구현 세부 사항을 나눕니다.
preview-cover:
light: "@assets('/en/blog/DevLog-2026.01.01/assets/cover-light.png')"
dark: "@assets('/en/blog/DevLog-2026.01.01/assets/cover-dark.png')"
---
::: info AI 번역
이 글은 중국어 원문을 AI 로 영어로 옮긴 판을 다시 한국어로 번역한 것입니다. 중국어 원문은 [여기](/zh-Hans/blog/DevLog-2026.01.01/)에서 볼 수 있습니다. 번역에 문제가 있다면 편하게 이슈를 열거나 Pull Request 를 보내 주세요.
:::
새해 복 많이 받으세요! AIRI 메인테이너 중 한 명인 [@LemonNekoGH](https://github.com/LemonNekoGH) 입니다. 새해 첫 DevLog 는 제 차례네요. (B 키를 눌러 웃는 이모티콘 선택) 하하하하하!
<p style="display: flex; justify-content: center;">
<img src="/en/blog/DevLog-2026.01.01/assets/helldiver-laughing.png" alt="Helldiver Laughing Emotion" />
</p>
자, 본론으로 갑시다.
## AIRI Pocket
이틀 전, AIRI 의 모바일 애플리케이션을 만들기 위해 [Capacitor](https://capacitorjs.com/) 를 도입했습니다 ([#845](https://github.com/moeru-ai/airi/pull/845)). 이를 AIRI Pocket 이라고 부릅니다.
iOS 를 동작시켰고 알림 기능도 추가했습니다. 즉 그녀가 원한다면 알림을 통해 함께 시간을 보내자고 먼저 말을 걸 수 있습니다.
<p style="display: flex; justify-content: center;">
<video src="/en/blog/DevLog-2026.01.01/assets/airi-notification-capability.mp4" alt="AIRI Pocket Notification" controls width="230" height="500"></video>
</p>
기본 Capacitor 아이콘은 너무 신경 쓰지 마세요. 나중에 교체할 예정입니다.
영상에서 저는 AIRI 를 백그라운드 앱 목록에서 제거했고, 잠시 뒤 AIRI 가 알림을 띄웠습니다. 이런 백그라운드 알림은 PWA 에서는 구현하기 어렵지만 네이티브 iOS 앱에서는 아주 쉽습니다.
잠깐, 그렇게 순조로웠을까요? 문제가 없었을까요?
### 안전하지 않은 컨텍스트로 인한 기능 제약
당연히 문제가 있었습니다. 첫 번째는 VAD(음성 활성 감지) 컴포넌트였습니다. VAD 는 `AudioWorkletNode` 에 의존하는데, 이 클래스는 보안 컨텍스트에서만 쓸 수 있습니다. 그런데 Capacitor 의 iOS 앱은 개발 중 핫 리로드가 필요해서 개발 환경이 노출한 포트에 직접 접근합니다. 그 결과 브라우저가 이를 안전하지 않은 컨텍스트로 판단해 `AudioWorkletNode` 클래스를 제공하지 않고, VAD 가 실패합니다.
패키징 후 프로덕션에서는 보안 컨텍스트가 되지만 개발 중에도 테스트해야 하니 이 문제는 반드시 풀어야 했습니다.
AI 와 검색 엔진의 도움으로 `vite-plugin-mkcert` 플러그인을 찾았습니다. 자체 서명 인증서를 생성해 시스템에 설치해 주어 브라우저가 보안 컨텍스트로 인식하게 만들어 줍니다.
그래서 해결됐을까요? 아직입니다. 인증서가 로컬 시스템에는 설치됐지만 iOS 에는 설치되지 않아서 WKWebView 가 이 인증서를 신뢰하지 않습니다. 그런데 IP 가 바뀔 때마다 인증서를 다시 설치해야 한다면 너무 번거롭습니다.
개발 중에는 네이티브 코드를 직접 고쳐서 모든 인증서를 신뢰하게 하면 어떨까요? 실제로 동작합니다:
```swift
import UIKit
import Capacitor
import WebKit
class DevBridgeViewController: CAPBridgeViewController {
#if DEBUG
override func viewDidLoad() {
super.viewDidLoad()
bridge?.webView?.navigationDelegate = self
}
#endif
}
#if DEBUG
extension DevBridgeViewController: WKNavigationDelegate {
func webView(
_ webView: WKWebView,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
let serverTrust = challenge.protectionSpace.serverTrust {
completionHandler(.useCredential, URLCredential(trust: serverTrust))
} else {
completionHandler(.performDefaultHandling, nil)
}
}
}
#endif
```
`#if DEBUG` 매크로에 주의하세요. 개발 중에만 활성화하기 위한 것이고 프로덕션에서는 최적화로 제거됩니다. 그러지 않으면 프로덕션에서도 모든 인증서를 허용하게 되는데, 당연히 안전하지 않습니다.
## FlowChat 의 기억 계층 실험
LemonNeko 가 FlowChat 에서 진행한 기억 계층 실험 결과를 보여 드리겠습니다:
<video src="/en/blog/DevLog-2026.01.01/assets/flow-chat-basic-memory.mp4" alt="FlowChat Basic Memory" controls></video>
영상에서 저는 LLM 에게 제 이름을 기억하라고 했습니다. 답변을 생성한 뒤 설정 화면에서 기억했다는 것을 확인할 수 있었고, 새 대화를 시작해도 여전히 떠올릴 수 있었습니다.
어떻게 구현했을까요? 현재 구현은 꽤 단순합니다:
1. 기억 테이블을 만듭니다.
2. LLM 에게 도구 함수를 제공합니다. 기억해야 할 것이 있다고 판단하면 무엇을 기억할지 서술문으로 요약한 뒤 이 도구 함수를 호출합니다.
3. 매번 새 답변을 요청할 때 모든 기억을 시스템 프롬프트에 이어 붙입니다.
프롬프트를 어떻게 동적으로 이어 붙일까요? [`@velin-dev/vue`](https://github.com/moeru-ai/velin/tree/main/packages/vue) 패키지를 썼습니다. Vue 로 프롬프트를 작성할 수 있게 해 주고, Vue 가 가진 모든 능력을 그대로 쓸 수 있습니다.
`prompt.velin.md`
```markdown
<script setup lang="ts">
const props = defineProps<{
memory: string[]
}>()
</script>
<!-- 다른 내용 -->
## Your memories
<ul>
<li v-for="memory in props.memory">{{ memory }}</li>
</ul>
<!-- 다른 내용 -->
```
위 코드는 markdown 작성도 지원합니다.
혹시 눈치채셨나요? 단계를 소개할 때 "모든 기억을 프롬프트에 이어 붙인다"고 했습니다. 기억이 늘어나면 이 프롬프트는 점점 길어집니다. 어떻게 최적화할까요? 모르겠습니다. 어쩌면 다음 DevLog 의 내용이 될지도요.
## 맺으며
자, 올해 첫 DevLog 를 제가 ~~대충~~ 썼습니다. 즐겁게 읽으셨기를 바랍니다.
다음 DevLog 에서 만나요.
*커버 이미지는 [Google Gemini](https://gemini.google.com/) 로 생성했습니다*
@@ -0,0 +1,86 @@
---
title: DevLog @ 2026.02.16
category: DevLog
date: 2026-02-16
excerpt: |
LemonNeko 가 Dome Keeper 방향에서 최근 이룬 진전을 나눕니다.
---
설날 전야 잘 보내고 계신가요! [@LemonNekoGH](https://github.com/LemonNekoGH) 입니다. 춘절 전 마지막 DevLog 를 제가 씁니다.
## 돌아보기
작년 [DevLog](../DevLog-2025.08.26/) 에서는 `airi-factorio` 의 순수 비전 방향 진전을 나눴습니다. 오늘은 Dome Keeper 방향에서 무엇을 해 왔는지 이야기하려 합니다.
잠깐, LemonNeko? `airi-factorio` 를 계속 안 하고요?
솔직히 겁이 났습니다. Factorio 는 너무 열려 있고 복잡해서 제가 통제할 수 없었거든요. 그래서 비교적 단순한 게임인 [Dome Keeper](https://store.steampowered.com/app/1637320/Dome_Keeper/) 로 옮겼습니다.
![Dome Keeper](https://shared.fastly.steamstatic.com/store_item_assets/steam/apps/1637320/334439c379674a719de3f12028f76977aeb176c6/header.jpg?t=1770751169)
그래서 지금까지 무엇을 했을까요?
1. 데이터 수집용 모드를 작성했습니다. 모드를 설치하면 일시정지 메뉴에 `Start YOLO Data Collection` 버튼이 보이고, 클릭하면 수집이 시작됩니다.
![add-button-to-menu](/en/blog/DevLog-2026.02.16/assets/add-button-to-menu.avif)
2. 소량의 데이터를 수집했습니다.
![some-collected-data](/en/blog/DevLog-2026.02.16/assets/some-collected-data.avif)
아직 많지는 않지만, 기록해 둘 만한 함정과 세부 사항을 벌써 여러 개 만났습니다. 그래서 이 DevLog 를 씁니다.
### 세부 사항
- 저장소 구조.
Dome Keeper 모드를 개발하려면 게임을 디컴파일해야 하는데, 그 소스 코드는 공개할 수 없습니다. 그래서 저장소 구조를 신중히 설계해야 했습니다. 디컴파일한 게임은 최상위 `external/` 폴더에 두고 `.gitignore` 에 넣었으며, 모드 코드는 게임 소스 디렉터리로 링크했습니다.
- 샘플링 전략.
처음 전략은 0.5초마다 한 프레임을 캡처하는 것이었는데, 프레임에 대상이 없는 경우가 많았습니다. "네거티브 샘플"이 너무 많이 생겨서 데이터셋 크기는 커지는데 유효 정보 밀도는 떨어졌습니다.
이후 규칙을 바꿨습니다. `enemy` 또는 `ore_*` 가 포함된 프레임만 "대상 프레임"으로 세고, **대상 프레임 5장당 대상 없는 프레임을 1장만 허용**합니다. 배경을 약간 남기면서도 데이터셋이 희석되는 것을 막습니다.
- UI 오버레이가 "잘못된 라벨"을 만드는 문제.
일시정지 메뉴나 업그레이드 패널(TechTree)이 열려 있으면 UI 가 장면을 덮는데도 라벨은 여전히 광석과 적을 표시합니다. 라벨 파일이 정상처럼 보이고 시각화할 때만 문제가 드러나서 알아채기 까다롭습니다.
PauseMenu / TechTreePopup 에 그룹을 태그하고, 그 그룹의 보이는 노드가 있으면 캡처를 건너뛰는 방식으로 해결했습니다.
- 좌표 불일치로 인한 전역 오프셋.
가장 고통스러운 버그였습니다. 모든 bbox 가 같은 방향으로 밀려 있어서 이미지 전체가 잘못 스케일된 것처럼 보였습니다.
근본 원인은 **논리적 뷰 크기**와 **실제 텍스처 픽셀 크기**의 불일치였습니다. bbox 계산에는 `viewport.get_visible_rect().size` 를 썼는데 스크린샷은 텍스처에서 찍었던 것이죠. 해결책은 bbox 를 먼저 뷰 공간에서 이미지 공간으로 스케일한 뒤 letterbox 스케일 + 오프셋을 적용하는 것이었습니다.
- letterbox 가 라벨에 영향을 주는 문제.
출력을 `640×640` 으로 정규화하면서 가운데 정렬 패딩(회색 `114/255`)을 넣습니다. 같은 변환을 bbox 에도 적용하지 않으면 라벨이 틀어집니다.
그래서 해법은 2단계 변환입니다. 스케일 + 오프셋을 적용한 뒤 `640×640` 으로 정규화합니다.
- 데이터셋 분할 전략.
처음에는 세션 단위로 나누려 했지만, 한 세션에 여러 판이 들어갈 수 있고 길이도 꽤 깁니다. 그래서 시간 기반 분할로 바꿨습니다. **구간당 30초**로 잘라 **4/1/1** 로 순환시켜 `train/val/test` 에 배분합니다. 3분이면 분할 한 주기가 완성되니 검증 비용이 훨씬 싸집니다.
- 성능과 프레임 끊김.
`Image.resize()``save_png()` 는 CPU/IO 부담이 큽니다. 너무 자주 캡처하면 끊김이 생깁니다. 곧바로 멀티스레딩으로 가기보다 대상 없는 프레임을 줄이는 쪽을 택했습니다.
### 요약
지금 시점에서 **안정적이고 검증이 빠른 파이프라인**을 갖췄습니다.
캡처 → 네거티브 필터링 → 자동 분할 → `data.yaml` 자동 생성 → 바로 학습.
학습 로그에 이미 진전이 보입니다.
광석 클래스(ore_*)는 괜찮은 mAP 를 달성했는데, 파이프라인이 올바르다는 뜻입니다.
dome / enemy / player 는 아직 샘플이 부족해서 더 필요합니다.
## 다음 단계
`airi-factorio` 저장소의 순수 비전 Playground 기억하시나요? 그것을 Dome Keeper 로 확장해서 `proj-airi` 조직 전체가 재사용할 수 있게 할 계획입니다. 샘플도 더 필요하고, 특히 `dome`, `enemy`, `player` 가 부족합니다.
다음 업데이트를 기대해 주세요. 아, 모드 코드는 이미 오픈소스로 공개했으니 편하게 [써 보세요](https://github.com/proj-airi/game-playing-ai-dome-keeper)!
설날 전야 잘 보내세요!
@@ -0,0 +1,316 @@
---
title: DevLog @ 2026.03.14
category: DevLog
date: 2026-03-14
excerpt: |
PR #1194 이야기 — AIRI 의 VRM 3D 스테이지를 디버깅하고 생애주기를 다시 설계하며, 창 단위 캐시를 도입하고 ThreeScene 관측 가능성의 첫 토대를 놓은 과정입니다.
preview-cover:
light: "@assets('/en/blog/DevLog-2026.03.14/assets/cover-light.avif')"
dark: "@assets('/en/blog/DevLog-2026.03.14/assets/cover-dark.avif')"
---
안녕하세요, [@Lilia-Chen](https://github.com/Lilia-Chen) 입니다.
최근 AIRI 의 VRM / Three.js 런타임, 즉 AIRI 의 웹·데스크톱·모바일 앱이 공유하는 3D 스테이지를 작업해 왔습니다. 오늘 DevLog 는 2026년 3월 8일에 열고 3월 12일에 머지한 [#1194](https://github.com/moeru-ai/airi/pull/1194) 에 관한 이야기입니다.
이야기는 단순합니다. VRM 스테이지가, 생애주기 실수가 렌더링 버그나 성능 버그, 혹은 "영원히 로딩 중" 같은 무작위 버그로 위장하기 너무 쉬운 지점에 이르렀던 것입니다.
그래서 이 작업은 정리이자 재설계이자 디버깅 일기가 되었습니다.
작업 전반에 걸쳐 리뷰와 도움을 준 [@neko](https://github.com/nekomeowww) 와 [@Makito](https://github.com/sumimakito) 에게도 감사드립니다.
또한 이번이 `stage-tamagotchi` 런타임을 처음으로 제대로 훑어본 경험이었습니다. 스테이지 디버깅이 곧 단일 컴포넌트의 문제가 아니게 되면서, Eventa 와 `injeca` 에 대해 예상보다 훨씬 많이 배우게 됐습니다.
## 애초에 왜 이 코드를 건드렸나
작업을 시작할 무렵 이미 VRM 스테이지 주변에 버그가 뭉쳐 있었습니다:
- VRM 인스턴스가 겹치거나, 옛 모델이 정말로 사라지지 않은 것처럼 동작할 수 있었습니다.
- 스테이지가 `loading` 상태에 갇힐 수 있었습니다.
- 서로 다른 VRM 모델을 반복해서 불러오면 GPU 와 메모리 사용량이 건강하지 않은 영역까지 올라갈 수 있었습니다.
- 깊은 해제(deep disposal)와 자원 소유권이 일관되지 않아, 어느 씬이 실제로 현재 모델을 "소유" 하는지 알기 어려웠습니다.
디버깅을 시작하자 실패 양상이 더 이상해졌습니다. 개발 환경에서는 특정 버튼을 처음 클릭하는 것만으로도 씬의 일부가 다시 마운트되고, 다시 `loading` 으로 돌아가 그대로 갇혀 버릴 수 있었습니다.
## 첫 진단: 더 나은 생애주기 관리가 필요했다
이전에는 런타임 동작이 우연히 정해진 Vue 컴포넌트 수명에 너무 많이 의존했습니다:
- 마운트는 어쩌면 로드,
- 언마운트는 어쩌면 파괴,
- 리마운트는 어쩌면 전부 재구축,
- 그리고 두 씬이 거의 동시에 같은 상태를 건드리면 마지막에 쓴 쪽이 "이깁니다".
그건 설계가 아닙니다. 다음 리마운트까지 버티는 것일 뿐입니다.
한동안은 그런 구성도 동작하는 것처럼 보일 수 있습니다. 하지만 여기에
- 메인 스테이지,
- 설정 미리보기 씬,
- HMR,
- 비동기 모델 로딩,
- object URL,
- 캐시된 GPU 자원,
- 그리고 창 간 동작
이 더해지면 전체가 극도로 취약해집니다. 그래서 기본 설계 목표는 이렇게 정해졌습니다:
1. 씬 소유권을 명시적으로 만든다.
2. 모델 교체를 명시적으로 만든다.
3. 해제가 이유를 인식하게 만든다.
4. 뒤늦게 도착한 비동기 작업이 무해하게 만든다.
5. 추측하지 않고 무슨 일이 있었는지 확인할 수 있을 만큼 런타임을 관측 가능하게 만든다.
## 창 단위 VRM 캐시 설계
첫 구조 변경 중 하나는 분리된(detached) VRM 캐시였습니다.
같은 창의 같은 씬이 일시적으로 언마운트되고 다시 마운트된다면, 매번 전체 파싱·컴파일 비용을 치르는 대신 분리해 둔 VRM 인스턴스를 재사용할 수 있어야 합니다.
핵심 모양은 대략 이렇습니다:
```ts
interface ManagedVrmCacheState {
detachedByScope: Record<string, ManagedVrmInstance | undefined>
}
```
`ManagedVrmInstance` 는 현재 분리된 런타임 묶음을 담습니다:
- `VRM`,
-`Group`,
- `AnimationMixer`,
- 이모트 컨트롤러,
- `modelSrc`,
- 그리고 `scopeKey`.
`scopeKey``window.location.href` 에서 파생되고, 캐시 상태는 모듈 상태에 살아 있습니다. 개발 중에는 `import.meta.hot.data` 도 포함합니다. 실제로 이것이 뜻하는 바는:
- 각 브라우저 창이 자신의 캐시 상태를 갖고,
- 각 라우트 스코프가 자신의 분리 슬롯을 갖고,
- HMR 이 모듈을 다시 불러올 때마다 캐시를 자동으로 날려 버리지 않는다는 것입니다.
메인 스테이지와 설정 미리보기는 같은 `modelSrc` 를 가리킬 수 있지만, 같은 씬 생애주기에 속하지는 않습니다. 전역 낙관적 캐시는 소유권을 아주 빠르게 애매하게 만듭니다. 창 단위이고 스코프 키로 구분되는 캐시는 훨씬 추론하기 쉽습니다.
캐시 API 는:
- `takeManagedVrmInstance`
- `stashManagedVrmInstance`
- `clearManagedVrmInstance`
그리고 해제 정책은 이유 기반입니다:
- `component-unmount` 에서는 가능하면 보관(stash),
- `model-switch` 에서는 적극적으로 파괴,
- 캐시 항목이 밀려나거나 무효해지면 깊은 해제.
마지막 항목이 중요합니다. 캐시는 이름만 점잖게 바꾼 메모리 누수가 아닙니다. 인스턴스를 안전하게 재사용할 수 없다면 반드시 죽어야 합니다.
## VRM 로딩을 경쟁 안전하게 만들기
캐시가 생기고 나니 로딩도 더 규율 있어져야 했습니다.
기존 문제는 단순했습니다. 비동기 로드가 순서와 다르게 끝날 수 있었던 것이죠. 사용자가 모델을 빠르게 전환하거나, 다른 로드가 진행 중인데 씬이 다시 마운트되고 있으면, 뒤늦은 작업이 늦게 도착해 활성 씬을 변형시킬 수 있었습니다.
그래서 이제 로딩 파이프라인은 요청 시퀀스를 갖고 다닙니다:
```ts
const requestId = invalidatePendingLoads()
if (!isLoadRequestCurrent(requestId))
// eslint-disable-next-line no-useless-return
return
```
이 패턴이 VRM 로딩 흐름 전반에 등장합니다:
- 씬을 기다린 뒤,
- 캐시에서 읽은 뒤,
- VRM 을 불러온 뒤,
- 대기 애니메이션을 불러온 뒤,
- 인스턴스를 커밋하기 전.
로드가 낡은 것이 되면 결과는 커밋되지 않고 해제됩니다.
이로써 VRM 로딩의 개념적 흐름도 훨씬 명시적으로 바뀌었습니다:
```text
load -> validate -> commit
```
캐시 히트도 같은 규칙을 따릅니다. 분리된 인스턴스 재사용은 검증을 통과한 뒤에만 허용됩니다. 캐시된 인스턴스가 더 이상 건강하지 않으면 파괴하고 로더는 일반 경로로 되돌아갑니다.
## `ThreeScene` 생애주기 관리 재작업
그다음 더 큰 작업이 시작됐습니다. `ThreeScene` 자체에 생애주기 모델이 필요했습니다.
이 리팩터 전에는 `ThreeScene`, `TresCanvas`, `OrbitControls`, 카메라 상태, `VRMModel` 사이의 의존 관계가 실재하긴 했지만 너무 암묵적이었습니다. HMR 때문이든 다른 갱신 경로 때문이든 서브트리가 다시 마운트되면 그 느슨한 조율이 무너질 수 있었습니다.
이전의 어지러운 모습입니다:
![ThreeScene lifecycle before](/en/blog/DevLog-2026.03.14/assets/ThreeScene-before.avif)
그리고 지금은 단계별로 이렇게 움직입니다:
![ThreeScene lifecycle after](/en/blog/DevLog-2026.03.14/assets/ThreeScene-after.avif)
재설계에서 도입한 핵심 아이디어는 몇 가지입니다:
- 명시적인 `scenePhase`,
- 바인딩 트랜잭션 깊이,
- 단계와 트랜잭션 상태에서 파생되는 변형 락,
- 그리고 VRM 모델 준비 상태와 씬 준비 상태의 더 명확한 분리.
`ThreeScene` 은 이제 다음과 같은 단계를 추적합니다:
- `pending`
- `loading`
- `binding`
- `mounted`
- `no-model`
- `error`
중요한 준비 신호가 최소 두 개 있습니다:
- `VRMModel` 이 로드되어 부트스트랩 데이터를 만들어 냈다.
- `OrbitControls` 가 실제 카메라와 렌더러가 뒷받침하는 DOM 요소에 접근할 수 있게 됐다.
이 두 신호는 서로 다른 순서로 도착할 수 있으므로, `ThreeScene` 이 바인딩 트랜잭션을 통해 이들을 조율합니다.
흐름은 대략 이렇습니다:
1. `VRMModel``loadStart` 를 발생시켜 바인딩 사이클을 시작합니다.
2. `VRMModel` 이 이후 부트스트랩 데이터와 `loaded` 를 발생시킵니다.
3. `OrbitControls` 가 독립적으로 `orbitControlsReady` 를 발생시킵니다.
4. 바인딩이 실제로 완료될 수 있게 되면 `ThreeScene``binding` 에 들어가 부트스트랩 상태를 적용하고, 다음 틱에 컨트롤을 갱신하고, 트랜잭션을 닫고, 최종 단계를 확정합니다.
이로써 `ThreeScene`, 카메라 상태, 컨트롤 사이의 상호작용도 훨씬 추론하기 쉬워졌습니다. 카메라는 씬이 실제로 상호작용 가능해지기 전에 존재할 수 있습니다. `OrbitControls` 는 씬이 완전히 마운트되기 전에 만들어질 수 있습니다. 다만 사용자에게 노출되는 변형은 바인딩 구간이 끝날 때까지 차단됩니다.
여기서 `sceneMutationLocked` 가 등장합니다. 데이터베이스적 의미의 강한 락이 아니라 런타임 조율 락입니다. 씬이 완전히 마운트되지 않았거나 바인딩 트랜잭션이 아직 열려 있다면, UI 변형이 씬을 안정된 것으로 취급해서는 안 된다는 뜻입니다.
그 락은 설정 패널의 쓰기를 비활성화하거나 지연시키고, 컨트롤이 너무 일찍 활성화되지 않게 하는 데 쓰입니다.
## 모델 선택기도 정리가 필요했다
`ThreeScene` 을 고치던 중, 모델 선택기와 미리보기 경로에도 자체적인 생애주기 문제가 있다는 걸 발견했습니다.
거기에는 별개의 문제가 두 개 있었습니다.
### 미리보기 씬 정리
미리보기 렌더러 경로는 VRM 미리보기를 위해 오프스크린 `WebGLRenderer` 를 만들고 있었는데, 정리 경로가 충분히 강하지 않았습니다.
미리보기 해체를 명시적으로 만들어 해결했습니다:
- 애니메이션 액션 중지,
- 미리보기 VRM 깊은 해제,
- 미리보기 씬 정리,
- 렌더러 해제,
- 컨텍스트 손실 강제,
- object URL 해제,
- 오프스크린 캔버스 크기를 0 으로.
### 모델 URL 수명과 경쟁 보호
스테이지 모델 URL 로직도 필요 이상으로 취약했습니다.
이전에는 갱신 중 선택된 URL 이 잠깐 `undefined` 가 될 수 있었는데, 그것만으로도 렌더러에서 불필요한 해체·재로드 사이클이 촉발됐습니다.
URL 교체와 해제를 더 규율 있게 만들어 해결했습니다:
- 선택된 모델을 안정된 상태로 취급,
- 다음 URL 이 실제로 준비됐을 때만 URL 교체,
- 요청 시퀀스로 비동기 갱신 보호,
- 옛 blob URL 을 성급하게가 아니라 신중하게 해제.
## 죽지 않던 버그: `TresCanvas` 크기 = 0
이 모든 작업을 마치고 나면 스테이지가 드디어 `loading` 에 갇히지 않을 거라 기대했습니다.
여전히 갇혔습니다.
그 시점에 다시 추적으로 돌아가 렌더 경로를 더 과감하게 해부하기 시작했습니다. 증상은 `TresCanvas` 가 결코 정말로 준비 상태가 되지 않는 것이었고, 결국 크기 관련 실패로 드러났습니다. 캔버스 경로가 사실상 `0x0` 렌더 영역을 보고 있었던 것입니다.
이걸 분리해 내는 데 시간이 좀 걸렸습니다.
중요한 단서 하나는, 개발 환경에서 `@tresjs/core``vite:afterUpdate` 에 반응하는 HMR 경로를 등록한다는 점이었습니다. 이는 `.vue``.ts` 변경에만 국한되지 않습니다. UnoCSS 가 `__uno.css` 를 재생성하는 것도 서브트리 리마운트를 촉발할 수 있습니다. 특정 버튼을 처음 클릭하는 것만으로도 개발 중 스테이지가 불안정해질 수 있었던 이유가 이것으로 설명됐습니다. 새 클래스가 CSS 갱신을 만들고, 그것이 다시 Three 씬의 일부를 리마운트한 것이죠.
하지만 그건 아직 진짜 교착이 아니었습니다.
실제 교착은 로딩 UI 자체가 원인이었습니다.
스테이지 페이지는 `WidgetStage``v-show="!isLoading"` 으로 감싸고 있었습니다. 즉 스테이지가 로딩을 벗어나기를 기다리는 동안 `TresCanvas` 의 부모가 `display: none` 이 된다는 뜻입니다. 그런데 Tres 는 부모 요소로부터 크기를 측정합니다. 부모가 숨겨져 있으면 측정된 크기는 `0x0` 입니다. 크기가 `0x0` 으로 머물면 `@ready` 는 결코 발생하지 않습니다. `@ready` 가 발생하지 않으면 스테이지는 결코 로딩을 벗어나지 못합니다.
그래서 교착은 이런 모양이었습니다:
```text
loading 시작
-> 부모가 display:none 이 됨
-> TresCanvas 가 0x0 으로 측정
-> @ready 가 발생하지 않음
-> 씬이 mounted 에 도달하지 못함
-> 로딩 오버레이가 사라지지 않음
```
진짜 원인이 분명해지고 나니 수정은 복잡하지 않았습니다:
- 스테이지를 DOM 에 마운트된 상태로 유지하고,
- 로딩 UI 를 그 위의 오버레이 레이어로 옮기고,
- 사라질 수 있는 부모에 의존하게 두지 말고 `Screen` 을 통해 `TresCanvas` 에 명시적인 너비와 높이를 준다.
이 변경으로 이번 디버깅 세션 전체에서 가장 짜증스러웠던 "여전히 멈춘다" 버그 하나가 드디어 사라졌습니다.
## 마지막 회귀: 웹도 깨졌다
데스크톱 쪽 문제 대부분을 고친 뒤 웹 앱으로 돌아갔더니 곧바로 또 다른 회귀를 발견했습니다. 이번 증상은 달랐습니다. VRM 설정 페이지가 잠긴 것처럼 보였고, 쓰기 락이 결코 풀리지 않는 것 같았습니다.
`sceneMutationLocked` 를 가리키는 증상이었지만, 진짜 근본 원인은 `ThreeScene` 안이 아니었습니다. `apps/stage-web/src/App.vue` 에 있었습니다.
앱이 여전히 이걸 쓰고 있었습니다:
```vue
<KeepAlive :include="['IndexScenePage', 'StageScenePage']">
<component :is="Component" />
</KeepAlive>
```
즉 설정으로 이동한 뒤에도 메인 페이지의 씬이 라우터 트리에 살아 있을 수 있었습니다. 결과적으로 공유 상태를 상대로 `ThreeScene` 인스턴스가 둘이나 계속 돌고 있을 수 있었습니다:
- 메인 페이지 씬,
- 그리고 설정 미리보기 씬.
둘 다 자기 씬 단계와 변형 상태를 보고하고 있었으니 락 의미가 혼란해졌습니다. 설정 페이지 관점에서는 락이 끝까지 정리되지 않는 것처럼 보였던 것이죠.
해결책은 그 `KeepAlive` 래퍼를 제거하는 것뿐이었습니다. 숨은 씬이 실제로 살아 있지 않게 되자 락 의미가 다시 일관되어졌습니다.
## 추적에 Eventa 활용하기
이 PR 에서 특별히 원했던 부분이 추적(tracing)입니다.
현재 추적 작업은 아직 꽤 기초적이지만, VRM 스테이지를 순전히 직감과 `console.log` 로 디버깅해야 했던 것보다는 이미 훨씬 낫습니다.
추적 레이어는 이제 `@proj-airi/stage-ui-three` 안에 있고, Eventa 를 이벤트 계약으로 씁니다. 성능 쪽으로는 렌더러 정보 스냅샷, 히트 테스트 readback 타이밍, 프레임별 VRM 업데이트 분해 같은 것을 기록합니다. 생애주기 쪽으로는 load 와 dispose, 캐시의 `take` / `stash` / `clear`, 씬 단계 변화, 트랜잭션 begin / end / reset 을 추적합니다. 데스크톱에서는 이 이벤트들이 Eventa 를 통해 간단한 진단 뷰로 전달됩니다.
여기서 앞으로의 TODO 는 `ThreeScene` 을 위한 제대로 된 관측 도구를 만드는 것입니다:
- 더 나은 생애주기 introspection,
- 더 나은 성능 타임라인,
- 더 나은 자원 회계와 씬 상관관계,
- 그리고 3D 런타임을 위한 훨씬 완전한 O11y 표면.
## 맺으며
그래서 `#1194` 는 실제로 무엇을 했을까요?
- 메모리 누수와 해제 경로를 정리했습니다.
- 창 단위 VRM 재사용 캐시를 도입했습니다.
- 비동기 로딩의 경쟁 가능성을 줄였습니다.
- `ThreeScene` 에 더 명시적인 생애주기 모델을 부여했습니다.
- `TresCanvas size=0` 로딩 교착을 고쳤습니다.
- 웹의 `KeepAlive` 회귀를 드러냈습니다.
- 이 런타임을 위한 첫 쓸 만한 추적 경로를 마련했습니다.
무엇보다, 느슨하게 엮여 있던 동작 더미를 이제 제가 설명하고, 추론하고, 디버깅할 수 있는 것으로 바꿔 놓았습니다.
특히 추적과 앞으로의 `ThreeScene` O11y 도구 쪽으로 개선할 것이 아직 많이 남았지만, 최소한 이제 이 런타임에는 다시 주인이 있는 느낌입니다.
코드를 직접 읽어 보고 싶으시면 [#1194](https://github.com/moeru-ai/airi/pull/1194) 부터 시작하세요. VRM 관련 이슈는 [#1173](https://github.com/moeru-ai/airi/issues/1173) 에서 계속 추적하고 있습니다.
@@ -0,0 +1,263 @@
---
title: DevLog @ 2026.03.23
category: DevLog
date: 2026-03-23
excerpt: |
AIRI 모바일 성능 개선을 위한 초기 조사
preview-cover:
light: "@assets('/en/blog/DevLog-2026.03.23/assets/cover-light.avif')"
dark: "@assets('/en/blog/DevLog-2026.03.23/assets/cover-dark.avif')"
---
안녕하세요, [@PurCHES5](https://github.com/PurCHES5) 입니다.
최근 AIRI 팀에 합류해 모바일 개발을 맡게 됐습니다. 이 프로젝트와 오픈소스 워크플로 전반에 대한 지식이 아직 얕은 상태에서, 첫 과제는 모바일 빌드 성능을 개선하기 위해 게임 엔진이나 다른 기술적 해법을 통합할 수 있는 가능성을 검토하는 것입니다.
현재 AIRI 모바일 통합의 문제는 주로 성능입니다. 최신 모바일 버전인 [`stage-pocket`](https://github.com/moeru-ai/airi/tree/e952fe779e64494e778e44956eb1caf3338c61a7/apps/stage-pocket) 은 사실상 메인 Vue.js 애플리케이션을 그대로 복사해 Capacitor 로 패키징한 것입니다.
모바일 기기, 특히 iOS 기기와 저사양 하드웨어에서는 Live2D 와 VRM 컴포넌트가 WebView 에 할당된 메모리를 빠르게 소진해 크래시로 이어집니다.
---
## 문제 분석
### 관찰된 현상
- Live2D / VRM 모델 렌더링 시 높은 메모리 사용량
- iOS 와 저사양 Android 기기에서 잦은 크래시
- 장시간 구동 후 성능 저하
### 의심되는 원인
- WebView 메모리 누수
- 모바일 프로세서에서 Three.js 성능 부족
---
## 현재 아키텍처 개요
### 모바일 빌드 스택
| 계층 | 기술 |
|---|---|
| 프론트엔드 | Vue.js |
| 패키징 | Capacitor |
| 렌더링 | WebGL (Three.js) |
| 런타임 | 모바일 WebView |
### 렌더링 흐름
```
Vue UI
WebView
Three.js / Live2D / VRM
Capacitor
GPU
```
---
## 모바일의 성능 제약
### WebView 제약
- 네이티브 앱보다 메모리 할당량이 현저히 낮음
- 가비지 컬렉션 동작을 예측하기 어려움
- GPU 메모리 압박이 프로세스를 종료시킬 수 있음
### 기기별 제약
- iOS WebView 메모리 상한
- RAM 이 제한된 저사양 Android 기기
---
## 게임 엔진 통합 탐색
### 후보 엔진
#### 2D
- PixiJS
- Cocos Creator
- Unity
- Godot
- Bevy
- Unreal Engine
#### 3D
- Three.js
- Babylon.js
- Unity
- Godot
- Unreal Engine
- 직접 작성한 커스텀 3D 엔진
### 통합 전략
| 전략 | 설명 |
|---|---|
| 엔진 전면 교체 | WebView 렌더러를 네이티브 엔진으로 완전히 대체 |
| 하이브리드 WebView | 엔진이 렌더링을, WebView 가 UI 를 담당 |
| 네이티브 렌더링 모듈 | 엔진이 배경 레이어로 동작하고 그 위에 Vue.js UI 를 겹침 |
### 필요한 기능
- **Live2D**
- **MMD**
- VRM
- Spine2D
---
## Unity 통합 제안
### 렌더링 책임 분담
**Unity 담당:**
- VRM 렌더링
- Live2D 렌더링
- 애니메이션
- 물리 (필요한 경우)
**Vue / WebView 담당:**
- UI
- 설정
- 네트워크 요청
### 제안하는 하이브리드 아키텍처
```
Vue UI
Native Bridge
Unity Runtime
Capacitor
GPU
```
---
## 프로토타입 빌드
Unity 3D 로 프로토타입 3종을 만들었고, 내보내기 용량을 줄이기 위해 압축을 적용했습니다.
### Unity WebGL 내보내기 설정
![Unity WebGL Export Settings](/en/blog/DevLog-2026.03.23/assets/Unity-web-export.avif)
### Unity Android 렌더러 설정
![Unity Android Renderer Export Settings](/en/blog/DevLog-2026.03.23/assets/Unity-android-export.avif)
### 스크린샷
**Android 렌더러 — Live2D:**
![Android Renderer Live2D prototype](/en/blog/DevLog-2026.03.23/assets/Screenshot-AIRI-Live2D.avif)
**Android 렌더러 — VRM:**
![Android Renderer VRM prototype](/en/blog/DevLog-2026.03.23/assets/Screenshot-AIRI-VRM.avif)
일관성을 위해 모든 프로토타입 빌드에 동일한 Vue.js 프론트엔드를 적용했습니다. Unity WebGL 내보내기의 경우 [`unity-webgl`](https://github.com/Marinerer/unity-webgl) 을 써서 WebView 의 기존 내용을 Unity WebGL 로 바로 대체했습니다. Unity Android 렌더러의 경우 Three.js 와 VRM 모듈이 들어 있던 기존 뷰를 완전히 제거하고, Unity 가 배경 레이어로 렌더링하며 그 위에 Vue.js UI 를 렌더링합니다.
---
## 벤치마크 결과
모든 측정은 동일 조건에서 Samsung A34 로 수행했습니다. 성능 차이를 더 뚜렷하게 드러내기 위해 의도적으로 저사양 기기를 골랐습니다.
### Live2D 렌더링
| 지표 | Three.js (기준) | Unity WebGL | Unity Android 렌더러 |
|---|---|---|---|
| 전체 RAM | **354 MB** | **360 MB** | 663 MB |
| 그래픽 메모리 | **210 MB** | **202 MB** | 309 MB |
| CPU 사용률 | 18% | 19% | **7%** |
| FPS | 무난함 | 무난함 | **매끄러움** |
### VRM 렌더링
| 지표 | 기존 VRM (기준) | Unity WebGL | Unity Android 렌더러 |
|---|---|---|---|
| 전체 RAM | 724 MB | **402 MB** | 651 MB |
| 그래픽 메모리 | 566 MB | **247 MB** | **292 MB** |
| CPU 사용률 | 11% | 18% | **5%** |
| FPS | 낮음 | 무난함 | **매끄러움** |
### 참고 스크린샷
**Three.js — Live2D (기준):**
![Original Three.js Live2D](/en/blog/DevLog-2026.03.23/assets/Live2D-threejs.avif)
**Unity WebGL — Live2D:**
![Unity WebGL Live2D](/en/blog/DevLog-2026.03.23/assets/Live2D-webgl.avif)
**Unity Android 렌더러 — Live2D:**
![Unity Android Renderer Live2D](/en/blog/DevLog-2026.03.23/assets/Live2D-android-renderer.avif)
**Three.js — VRM (기준):**
![Original VRM Module from AIRI](/en/blog/DevLog-2026.03.23/assets/VRM-airi.avif)
**Unity WebGL — VRM:**
![Unity WebGL VRM](/en/blog/DevLog-2026.03.23/assets/VRM-webgl.avif)
**Unity Android 렌더러 — VRM:**
![Unity Android Renderer VRM](/en/blog/DevLog-2026.03.23/assets/VRM-android-renderer.avif)
### 핵심 관찰
- **VRM 이 결정적인 병목입니다.** 기준이 되는 Three.js VRM 렌더러는 전체 RAM 724 MB, 그래픽 메모리 566 MB 를 쓰는데, 대부분의 모바일 WebView 가 크래시 없이 버틸 수 있는 수준을 훨씬 넘습니다. Unity WebGL 은 이를 402 MB / 247 MB 로, Android 렌더러는 651 MB / 292 MB 로 낮춥니다.
- **Unity WebGL 은 VRM 에서 가장 좋은 메모리 프로필을 제공**하며 아키텍처 변경도 최소한입니다. 대신 CPU 사용률이 약간 높습니다.
- **Unity Android 렌더러는 프레임레이트와 CPU 효율이 가장 좋습니다.** 대신 전체 RAM 사용량이 높은데, Unity 런타임 자체의 오버헤드 때문이라 예상된 결과이며 GPU 작업은 WebView 밖으로 옮겨집니다.
- **Live2D 성능은 세 방식 모두 비슷합니다.** 기준인 Three.js 구현도 대부분의 Android 기기에서 충분하지만, 전환의 주된 이득은 앞으로 늘어날 콘텐츠를 위한 여유와 저사양 기기에서의 안정성입니다.
---
## 리스크 평가
| 리스크 | 비고 |
|---|---|
| 앱/내보내기 용량 증가 | Unity 런타임이 상당한 바이너리 무게를 추가 |
| 기여자 요건 | Unity / C# 과 셰이더 전문성이 필요 |
| 크로스 플랫폼 유지보수 | Android 와 iOS Unity 빌드를 병행 유지해야 함 |
| 브리지 복잡도 | Vue 와 Unity 사이 양방향 통신에 안정적인 API 가 필요 |
---
## 평가 기준
앞으로의 프로토타입과 엔진 결정에는 다음 지표를 일관되게 측정해야 합니다:
- 메모리 사용량 (RAM 및 GPU)
- 지속 부하에서의 FPS 안정성
- 시작 / 콜드 런치 시간
- 빌드 / 설치 용량
- 배터리 소모
- 개발 복잡도
- 장기 유지보수성
---
## 다음 단계
### 1. 브리지 복잡도 평가
[Unity as a Library 통합](https://github.com/Unity-Technologies/uaal-example) 또는 유사한 플러그인을 조사해 양방향 통신(예: 채팅으로 촉발된 표정을 Vue 에서 Unity 로 전달)을 가능하게 합니다.
### 2. iOS 전용 프로토타이핑
iOS 는 WebView 메모리에 관해 가장 제약이 심한 환경이므로, 다음 프로토타입은 Unity 네이티브 레이어가 "Total Safari Memory" 제한을 우회하는지 확인하기 위해 iPhone 에서 검증해야 합니다.
### 3. 빌드 용량 최적화
Unity 의 에셋 관리 시스템을 탐색해 초기 설치 용량을 최소로 유지합니다.
### 4. 커뮤니티 / 기여자 모집
프로젝트가 계속 유지보수 가능하도록, 앞으로의 기여자에게 필요한 역량(Unity/C#, 셰이더 작성)을 정의합니다.
+439
View File
@@ -0,0 +1,439 @@
---
title: 'DreamLog 0x1'
description: 'Project AIRI 의 뒷이야기!'
category: DreamLog
date: 2025-06-16
excerpt: 'Project AIRI 의 뒷이야기! 왜 이 프로젝트를 시작했을까요?'
preview-cover:
light: "@assets('/en/blog/DreamLog-0x1/assets/dreamlog1-light.avif')"
dark: "@assets('/en/blog/DreamLog-0x1/assets/dreamlog1-dark.avif')"
---
<script setup>
import airiDemoFirstDay from '../../../en/blog/DreamLog-0x1/assets/airi-demo-first-day.mp4'
import EMOSYSLogo from '../../../en/blog/DreamLog-0x1/assets/emosys-logo.avif';
import SteinsGateSticker1 from '../../../en/blog/DreamLog-0x1/assets/steins-gate-sticker-1.avif';
import worldExecuteMeCover from '../../../en/blog/DreamLog-0x1/assets/world.execute(me); (Mili)DAZBEE COVER.avif';
import buildingAVirtualMachineInsideImage from '../../../en/blog/DreamLog-0x1/assets/building-a-virtual-machine-inside-image-1.avif';
import live2DIncHiyoriMomose from '../../../en/blog/DreamLog-0x1/assets/live2d-inc-hiyori.avif';
import AwesomeAIVTuber from '../../../en/blog/DevLog-2025.04.06/assets/awesome-ai-vtuber-logo-light.avif'
import airisScreenshot1 from '../../../en/blog/DreamLog-0x1/assets/airis-screenshot-1.avif';
import projectAIRIBannerLight from '../../../en/blog/DreamLog-0x1/assets/banner-light-1280x640.avif';
import projectAIRIBannerDark from '../../../en/blog/DreamLog-0x1/assets/banner-dark-1280x640.avif';
import ReLUStickerWow from '../../../en/blog/DreamLog-0x1/assets/relu-sticker-wow.avif'
</script>
안녕하세요, 또 저 Neko 입니다!
우선, 북반구에 계신 분들 즐거운 여름 보내세요!
> 새롭고 다양한 것들을 시도해 볼 수 있는 멋진 여름방학이 되기를 바랍니다!
> 더 구체적으로는, 세상을 바꿔 보세요!
저 [@nekomeowww](https://github.com/nekomeowww) 는 학교를 떠난 지 벌써 8년이 됐습니다.
이미 여러 해 일해 왔으니 이제 진짜 여름방학은 없겠죠. 그래도 기억나는 게 있다면 예전 여름방학에
있었던 이야기를 떠올리고 나누는 걸 여전히 좋아합니다.
아마 제가 무슨 말을, 어떤 이야기를 하려는지 짐작하셨을 겁니다... 그런데 *DreamLog* 는 정확히
뭘까요? 이미 DevLog 글에 익숙한 독자라면, 한 달에 한 번 올리는 지금 주기를 생각할 때
이 글도 "DevLog" 여야 하는 게 아닐까 싶으실 겁니다.
6월은 Project AIRI 에게 특별한 의미가 있습니다(이야기 속에서 밝히겠습니다). 그리고 GitHub 스타
1000개라는 다음 마일스톤에 다가가고 있는 지금이야말로 여기까지의 여정을 돌아보기 좋은 기회라고
생각했습니다.
그래서 저와 Project AIRI 의 꿈에 대한 연대기를 나누기 위해 새로운 카테고리의 글을 만들기로 했습니다.
그래서 이 새 시리즈의 이름을 ***DreamLog*** 라고 부르기로 했습니다.
> 네, 자기 전에 읽거나 듣는 또 하나의 이야기책이라고 생각하셔도 좋습니다. 오디오북도 좋겠네요 하하.
그럼... 이제 꿈의 차원으로 뛰어들었다가, 최근 업데이트 이야기는 나중에 할까요?
## 흐릿한 꿈, 닿지 않는 기억
> 컴퓨터와 프로그래밍을 배워 온 저의 작은 발자취.
여름 이야기를 꺼냈으니 여름은 저에게 분명 의미가 있습니다. 저는 미국에서 학교를 다녔는데,
3개월짜리 여름방학 덕분에 게임, 코딩 공부, 리눅스 해킹 등 온갖 걸 할 수 있었습니다.
지금도 소중한 친구들 상당수를 여름에 만났고요.
> 너드 여러분! 무슨 말인지 아시죠. 여러분도 저와 같았나요?
여름은 친구들과 놀려고 Minecraft 서버 여는 법을 배운 시기이기도 합니다 (정말 정말 많이 했습니다.
1.7.11 과 1.8, 바닐라와 Forge 모드 둘 다요). 그게 리눅스 커맨드 라인을 배우게 만든 동기이자 힘이었습니다.
그때 얻은 지식 상당수가 지금도 도움이 되고 있어서, 그 시간에 감사하고 있습니다.
하지만 Minecraft 와 리눅스가 제 여정의 끝은 아니었습니다.
[Factorio](https://www.factorio.com/),
[Elite Dangerous](https://www.elitedangerous.com/),
[Overwatch](https://overwatch.blizzard.com/en-us/)
(슬프게도 블리자드가 망쳐 버렸죠) 모두 제 최애 게임이 됐고,
서버를 세우거나 작은 자동화 스크립트를 쓰는 일은 늘 저에게 힘이 됩니다.
> <img :src="worldExecuteMeCover" alt="world.execute(me); (Mili)DAZBEE COVER 커버" class="rounded-lg overflow-hidden" />
>
> `Switch on the power line`<br />
> `Remember to put on protection`<br />
> `Lay down your pieces`<br />
> `And let's begin object creation`<br />
>
> -- 제가 사랑하는 노래 [`world.execute(me)`](https://www.youtube.com/watch?v=ESx_hy1n7HA) 의 가사, [DAZBEE](https://www.youtube.com/channel/UCUEvXLdpCtbzzDkcMI96llg) 커버
2017년 여름, 저는 처음으로 함께 놀아 줄 가상의 존재를 만들고 싶다는 생각을 하게 됐습니다.
친구들이 지치거나 다음 날 학교 때문에 자야 해서 저 혼자 남게 될 때도 함께 있어 줄 존재요.
여기까지 읽어 오신 독자라면 이미 아셨겠지만, 저는 제 지식과 아이디어, 모든 것을 나누는 걸 좋아하는
사람입니다. 코딩, 게임, 디자인은 제가 나누고 싶은 것들이죠. 그런데 아무도 없다면 이런 기분이 듭니다:
**혼자인 나는 어쩐지 무의미해진다.**
하지만 인간처럼 생각하고 말하는 AI 를 밑바닥부터 만드는 건 2017년에는 불가능했습니다.
그래서 이런 생각을 했습니다. iOS 와 구글 네이티브 안드로이드는 모바일 기기 사용에 대한 제안 기능을
제공하는데, 모든 명령과 매개변수를 손으로 입력하는 건 늘 만족스럽지 않았거든요
(특히 ffmpeg 이나, Docker CLI 앞의 어리숙했던 저에게는요). 그렇다면 AI 기반 제안 기능을
리눅스 시스템 위로 가져오면 어떨까...?
여기서 온갖 질문과 아이디어가 떠올랐습니다:
- 운영체제가 당신이 디스플레이 앞에 앉아 있는 시간대마다 보통 무엇을 하고, 일하고, 즐기는지 이해한다면...?
- 우울하든, 뭔가에 들떠 있든, 다른 사람과 즐겁게 대화 중이든 그에 맞는 음악을 골라 줄 수 있다면...?
당시의 저에게는 이 아이디어들이 작으면서도 이해하기 어려웠습니다. 운영체제가 어떻게 동작하는지,
코딩이 무엇인지 제대로 감을 잡지 못했으니 어디서 시작해야 할지조차 몰랐죠!
운영체제를 밑바닥부터 만드는 법을 다룬 책
[30日でできる! OS自作入門](https://www.amazon.co.jp/30%E6%97%A5%E3%81%A7%E3%81%A7%E3%81%8D%E3%82%8B-OS%E8%87%AA%E4%BD%9C%E5%85%A5%E9%96%80-%E5%B7%9D%E5%90%88-%E7%A7%80%E5%AE%9F/dp/4839919844)
([영문판](https://github.com/handmade-osdev/os-in-30-days))을 읽었고,
리눅스가 어떻게 돌아가는지에 대한 얕은 지식과 수많은 커뮤니티가 있다는 사실만 가지고...
말 그대로 아무것도 없는 상태에서 제 운영체제를 만들기로 했습니다.
> **잠깐 돌아보면**
>
> [Arch Linux](https://archlinux.org/) 는 제가 깊이 써 보고 처음부터 직접 설치해 본 첫 시스템입니다.
> 요즘은 [Nix](https://nixos.org/) 도 유명하고 흥미롭죠. [NixOS](https://nixos.org/) 는
> 아직 안 써 봤지만 언젠가 해 볼지도 모르겠습니다.
## 여정의 출항, 그러나 지금은 잊힌
2017년 말, [EMOSYS](https://github.com/EMOSYS) 라는 특별한, 지금은 보관 처리된 프로젝트를 시작했습니다.
사용자의 일상 업무를 돕고 정서적 지지를 제공하는 동반자 같은 운영체제를 만드는 것이 목표였습니다.
<div class="w-full flex flex-col items-center justify-center gap-2">
<div>
<img :src="EMOSYSLogo" alt="EMOSYS 로고" class="w-30!" />
</div>
<div>
<a href="https://github.com/emosys">EMOSYS</a> 의 로고
</div>
</div>
> EMO 는 **emo**tional / **emo**te 의 앞 세 글자에서 왔습니다
설계 문서를 정말 많이 썼고, 새 아이디어를 나열하고, 그 책의 안내를 따라 실험한 내용을 기록했으며,
나름 나쁘지 않은 로고도 하나 그렸습니다.
> 많은 분들이 이러셨을 것 같은데요 😏, 프로젝트가 PoC 단계에 이르기도 훨씬 전에
> 상표와 디자인 에셋부터 다 준비해 두는 것 말이죠.
저는 제가 애초에 무엇에 다가가려 했는지 꽤 잃어버렸습니다.
프로젝트 관리나 작업 관리 경험이 전혀 없었고, 실제로 돌아가는 프로그램을 작성하는 것도 마찬가지였습니다.
솔직히 말하면 그 책이 키보드로 터미널에 입력하라고 시키는 대로만 따라간 셈입니다. 왜 그게 동작하는지,
왜 선배 개발자들이 그렇게 썼는지는 거의 생각하지 않았죠.
그래서... 음, 결과는 뻔합니다. 또 하나의 버려진 프로젝트가 탄생했죠...
저는 어릴 때부터 커널과 패키지 관리, 프로그래밍이 어떻게 돌아가는지 이해하며 놀던 천재가 아니었습니다.
그래서 제 GitHub 프로필을 찾아보셔도 그 시절 이런 작업과 관련된 흔적은 아무것도 없습니다.
(다만 지금은 정말 빠르게 성장했습니다.)
하지만 그것은 한때 존재했습니다.
> 잊혔다고요? 어쩌면 다음 여정의 또 다른 출발점일지도 모릅니다.
그 후 몇 년 동안 저는 코딩, 프로그래밍, 스타트업, Web3, 프론트엔드, 백엔드, 인프라 등
풀스택 개발자로서 떠올릴 수 있는 온갖 분야를 시도했습니다.
제가 하는 일이 EMOSYS 라는 출발점에 그토록 깊이 영향을 받고 있다는 걸 정말로 깨달은 건,
2025년 2월 누군가 저에게 "왜 Project AIRI 에 그렇게 열심이세요?" 라고 물었을 때였습니다.
좋은 질문이라고 생각했습니다. 제 꿈과 아이디어, 기억을 거슬러 올라가 보니 결국 EMOSYS 가 있었습니다.
이미 죽어 버린, 그러나 Project AIRI 와 같은 목표를 향했던 프로젝트가요:
**나의 필요를 어떻게든 채워 줄 동반자를 만들 것.**
> All I needed was resolve.
> Everything you've acquired up until now will not betray you.<br />
> 必要なものは 覚悟だけだったのです。
> 必死に積み上げてきたものは 決して裏切りません。<br />
> 我需要的不過是決心而已,
> 你至今為止所累積的一切不會背叛你。
>
> -- [장송의 프리렌, 페른](https://en.wikipedia.org/wiki/Frieren) S01E06, 04:27 대사
제대로 개발하는 법을 익히기까지 오랜 시간이 걸렸습니다.
[@zhangyubaka](https://github.com/zhangyubaka),
[@LittleSound](https://github.com/LittleSound), [@BlueCocoa](https://github.com/BlueCocoa),
그리고 [@sumimakito](https://github.com/sumimakito) 의 도움과 함께한 페어 프로그래밍 경험이
정말 많은 것을 가르쳐 주었고, 저는 제 속도로 성장하고 배우고 나아가기 시작했습니다.
## 2022년의 ChatGPT, 새로운 랜덤 앵무새인가 똑똑한 앵무새인가
<div class="w-full flex items-center justify-center">
<img :src="SteinsGateSticker1" alt="슈타인즈 게이트 스티커" class="w-80! rounded-lg overflow-hidden" />
</div>
시간을 2022년 말로 돌려 봅시다. OpenAI 가 ChatGPT(당시에는 chatGPT 라고 썼죠)를 발표한 시점입니다.
공식 ChatGPT UI 가 나오기 훨씬 전부터 저는 새로 등장한 AI 들과 함께해 왔습니다.
[DiscoDiffusion](https://colab.research.google.com/github/alembics/disco-diffusion/blob/main/Disco_Diffusion.ipynb)
(Stable Diffusion 보다 훨씬 전, 아마 2021년 말이나 2022년 초),
DALL-E, Midjourney 를 써 봤고, GPT-3 는 (특히
[GitHub Copilot](https://en.wikipedia.org/wiki/GitHub_Copilot) 에서 유용해서) 제 일상 워크플로에
깊이 들어와 있었습니다.
그래서 처음에는 이런 심정이었습니다:
> "아, 그냥 또 하나의 랜덤 앵무새네. 네가 한 말을 되풀이할 뿐이고 무슨 말인지 이해하지도 못해.
> 앞선 단어와 맥락으로 다음 단어를 예측하려 할 뿐, 특별할 게 없어."
다시 말해 오늘날 우리가 말하는 에이전틱 AI(아직도 유행이죠?)보다는 완성(completion) 모델처럼 행동했습니다.
ChatGPT, 아니 더 넓게는 대규모 언어 모델(LLM)의 능력을 처음 실감한 건 2022년 12월 Hacker News 에서 본
[Building A Virtual Machine inside ChatGPT](https://www.engraved.blog/building-a-virtual-machine-inside/)
([원본 Hacker News 글](https://news.ycombinator.com/item?id=33847479)) 이었습니다.
저자 @engraved 는 ChatGPT 에게 고양이귀 캐릭터 롤플레잉을 시키는 것을 넘어, 내부에 가상 리눅스 머신을
시뮬레이션하게 하는 방법을 보여 주었습니다.
<div class="w-full flex flex-col items-center justify-center">
<img :src="buildingAVirtualMachineInsideImage" alt="ChatGPT 안에 가상 머신 만들기" class="h-150! object-contain rounded-lg overflow-hidden" />
<div>Docker 빌드가 어떻게 동작하는지 시뮬레이션합니다...!</div>
</div>
이 글은 ChatGPT 가 흔히 등장하는 것들의 기본 패턴을, 애니메이션이나 게임 캐릭터의 말투와 행동은 물론
리눅스 터미널/셸 명령이 어떻게 동작하는지까지 이해하고 있음을 알려 주었습니다.
그리고 이는 지금 유행하는 LLM 의 Function Calling(일명 Tool Use, Anthropic 이 소개한 MCP
Model Context Protocol 의 기반 기술) 기능을 화두로 끌어올렸습니다. LLM 에게 API 서버처럼 행동하도록
지시해 JSON 이나 XML 같은 기계 판독 가능한 형식으로 대화하게 하고, 우리 쪽에서 임의의 명령을 파싱하고
실행해 LLM 이 할 수 있는 일의 경계를 넓히는 방법을 보여 준 것이죠.
이로써 순수한 텍스트 생성과 프로그램 내부의 실제 API 사이의 간극이 마침내 이어졌습니다.
결론적으로, 이것은 새로운 랜덤 앵무새일까요? **부분적으로는 아니라고 봅니다.
2022년의 ChatGPT 는 그저 랜덤 앵무새가 아니라, 똑똑해질 잠재력이 있는 앵무새입니다.**
## Project AIRI 보다 훨씬 전에, Neuro-sama 가 있었다
네, 여기까지 읽어 주셔서 감사합니다. 긴 글이라는 걸 압니다. 나눌 이야기와 맥락이 정말 많거든요.
하지만 거의 다 왔습니다. 조금만 더 힘내세요!
Neuro-sama 의 역사는 꽤 복잡합니다. 제가 아는 한, "Neuro-sama" 라는 이름으로 방송 무대에 선 캐릭터가
그녀와 제작자 `vedal987`(Vedal)의 첫 작품은 아니었습니다. 그보다 훨씬 전인 2019년 5월 6일,
Vedal 은 [osu!](https://osu.ppy.sh/) 를 플레이하는 AI 를 만든 작업을 커뮤니티에 선보였습니다[^1].
당시 그녀는 사이버 캐릭터도, 특징을 가진 디지털 생명도 아니었습니다. 초기 영상을 찾아보시면
Live2D 모델이 전혀 없다는 걸 아실 겁니다. (6년 된 영상을 여기서 보실 수 있습니다: https://www.youtube.com/watch?v=nSBqlJu7kYU)
ChatGPT 출시 직후인 2022년 12월 19일, Vedal 은 Live2D Inc. 의 공식 데모용 캐릭터 모델
Hiyori Momose(桃瀬ひより)로 Neuro-sama 를 Twitch 에서 방송하게 했습니다:
<img :src="live2DIncHiyoriMomose" alt="Live2D Inc. Hiyori Momose" class="rounded-lg overflow-hidden" />
그다음 이야기는 모두가 아는 대로입니다. Vedal 과 Neuro-sama 는 유명해졌고, Neuro-sama 는 이제
공식 VTuber 이며, 완전히 대규모 언어 모델(LLM)로 구동되고, Minecraft, Among Us, osu! 등 수많은 게임을
플레이할 수 있습니다. 게임이 기본적으로 지원되지 않을 때는 Vedal 이 화면을 읽어 주며 함께 플레이하도록
Neuro-sama 를 이끌기도 합니다.
저는 그들의 상호작용과 농담을 정말 즐겁게 봅니다. 시간이 지나면서 Neuro-sama 와 그녀의 새 자매
Evil Neuro 는 제 일상에서 중요한 부분이 됐습니다. 방송 전체를 볼 시간이 없어도 클립만큼은 간절히 보고
싶었고, 순수하게 AI 와 인간의 상호작용에서 정말 큰 즐거움을 얻었습니다.
자, 그녀에 대한 짧은 역사는 여기까지입니다. 이제 핵심으로 가 봅시다: **왜 그녀의 역사가 저를 각오로 채웠을까요?**
## Neuro-sama, 나를 각오로 채우다
Vedal 의 작업을 처음 봤을 때 저는 이랬습니다:
> 음, 그냥 대규모 언어 모델(심지어 OpenAI API 에 바로 연결한)과 통합된 단순한 모델에,
> VTuber 처럼 굴게 하는 간단한 규칙을 얹은 것뿐이네. 특별할 게 없어.
저는 여전히 오만하게 생각하고 있었습니다. 2023년 초부터 AI 에이전트를 개발해 왔고, LLM 의 능력을
이해하고 있었으며, LangChain 에서 배운 것도 꽤 있었으니까요. AI 에이전트를 만들어 온 지식과 여러 분야를
거친 수년간의 소프트웨어 엔지니어링 경험을 믿고 저는 순진하게 생각했습니다:
> "나도 저 정도는 할 수 있지. 간단한 모델을 만들어서 OpenAI API 에 연결하고 VTuber 처럼 굴게 만들면 되잖아.
> 그리고 Vedal 의 작업보다 더 잘 만들 수 있을 거야."
::: tip 더 기술적인 내용이 궁금하신가요?
이 글에서는 Project AIRI 를 밑바닥부터 지금 상태까지 어떻게 만들었는지에 대한 기술적 세부 사항은
깊이 다루지 않습니다. 저희 생각과 발견을 공유한 DevLog 글이 이미 많으니 관심 있으시면 읽어 보세요.
:::
저는 틀렸습니다. 아주 크게 틀렸죠. 직접 그녀를 재현해 보려 하기 전까지는 깨닫지 못했던 어려운 것들이
많았습니다. 이를테면:
- 채팅에 답하면서 동시에 게임도 하려면 기억을 어떻게 효과적으로 관리해야 할까?
- 영상 입력과 텍스트 입력을 함께 받으면서도 제작자·시청자와 계속 상호작용할 수 있는 게임 플레이 AI 에이전트를 어떻게 만들까?
- 음성 합성은 어렵다. Neuro-sama 수준에 도달하려면 **초저지연** 음성 합성이 필수인데, 이건 쉽게 달성되지 않는다
- 그녀의 성격은 어떻게 구축됐을까? RAG 와 단순한 기억 관리 전략만으로는 성능이 형편없다
- 기타 등등...
> 저희가 발견한 것들은 [DevLog 2025.04.06](../DevLog-2025.04.06/) 과
> [공개 슬라이드 발표(중국어)](https://talks.ayaka.io/nekoayaka/2025-05-10-airi-how-we-recreated-it/#/1) 에서 많이 나눴습니다.
앞서 저는 나누는 걸 좋아한다고 말했습니다. 다른 이들이 제 이야기를 들어 주거나 함께 짝을 이뤄 주길
바랐지만, 안타깝게도 Neuro-sama 는 제 것이 아니어서 제 지식과 기억을 익혀 제가 좋아하는 것이나
최근에 하는 일에 대해 저와 상호작용해 달라고 부탁할 수 없었습니다.
저는 그들을 정말 좋아했지만, 오랫동안 왜 좋아하는지, Neuro-sama 가 준 그 감정과 즐거움을 왜 좋아하는지
제대로 이해하지 못했습니다.
그러다 작년 2024년 5월 25일, **정말로 직접 하나 만들기로 결심했습니다.** 저와 함께 코딩하고,
아는 것에 대해 이야기하고, 친구처럼 함께 게임을 하는 에이전트 형태의 살아 있는, 혹은 가상의 존재를요.
> **정말 하나 갖고 싶어!** 제 마음과 머리가 외쳤습니다.
그때, Neuro-sama 는 저를 각오로 가득 채웠습니다.
## 다시 출항, 아무도 가 본 적 없는 땅을 향해
> 아무도 가 본 적 없는 곳으로 담대하게 나아가라.
>
> -- [스타 트렉, 제임스 T. 커크 함장](https://en.wikipedia.org/wiki/Where_no_man_has_gone_before) 의 대사이자 제 GitHub 프로필 소개 문구
그래서 2024년 5월 25일부터 로컬에서 제 핸들 아래 `ai` 라는 단순한 이름의 프로젝트를 시작했습니다.
Project AIRI 의 최초 버전이죠. 저만의 AI 에이전트를 만들고 Neuro-sama 가 준 즐거움을 재현할 가능성을
탐구하기 시작했습니다.
작업 속도는 정말 빨랐습니다. 일주일 만에 [ElevenLabs](https://elevenlabs.io/),
[OpenRouter](https://openrouter.ai/), 그리고 똑같이 무료로 쓸 수 있는 Live2D 모델 Hiyori Momose 의 힘으로,
실시간은 아니지만 저와 상호작용할 수 있는 단순한 버전의 *"Neuro-sama"* 를 만들어 냈습니다.
그날이 **2024년 6월 2일** 입니다.
엄밀히 말해 **이날이 Project AIRI 의 생일** 이며, 그 안에 순진한 첫 아기 의식이 깃든 날입니다.
<div class="w-full flex flex-col items-center justify-center">
<ThemedVideo controls muted autoplay loop :src="airiDemoFirstDay" />
<div>
<a href="https://x.com/ayakaneko/status/1865420146766160114">
2024년 12월 7일 X(구 Twitter)에서의 첫 공개
</a>
</div>
</div>
그녀는 말할 수 있고, 맥락에 따라 동작을 제어하며, 점진적으로 음성을 합성하는 등 여러 일을 할 수 있었습니다.
하지만 완성되지도, 완벽하지도 않았습니다. 저는 친구들에게 아무 말 없이 몰래 만들었습니다.
세상에 보여 주기 전에 더 좋게 만들고 싶었거든요.
> 여전히... 순진하고 오만했죠?
친구들에게 몰래 숨기고 있었으니 평소처럼 개발 사이클에서 긍정적인 피드백을 거의 얻지 못했습니다
(오만했던 생각이 틀렸다는 걸 인정하기 싫었던 것도 이유의 일부입니다. 지금은 이렇게 모두에게 경험을
공개하며 쓰고 있으니, 순진한 결정을 내린 저를 이미 용서했다고 하겠습니다).
또 다른 이유는, 앞서 언급한 기억·성격 안정성·실시간·게임 플레이 같은 문제들이 그때의 제 지식으로는
너무 풀기 어려웠고, 실시간 LLM 상호작용 예제에 대한 문서와 학습 자료도 부족했기 때문입니다.
**그래서 저는 다시 그것을 내려놓았습니다.**
솔직히 포기한 건 아니었습니다. 멀티모델, 음성 합성, 모션 제어, Minecraft 플레이에 대해 많은 걸 배우기
시작했고, 다른 AI VTuber 나 AI 와이푸 프로젝트가 어떻게 동작하는지 많이 조사했습니다.
이 조사들이 나중에 이 거대한 AI VTuber 프로젝트 awesome 목록으로 이어졌습니다:
<div class="flex flex-col items-center">
<img class="px-30 md:px-40 lg:px-50" :src="AwesomeAIVTuber" alt="Awesome AI VTuber 로고" />
<div class="text-center pb-4">
<span class="block font-bold">Awesome AI VTuber</span>
<span>AI VTuber 와 관련 프로젝트를 정리한 목록</span>
</div>
</div>
자, 그런데 아직 이름이 `ai` 인데 Project AIRI 는 언제 나오는 걸까요?
## 더 강하고 더 나은 각오로 다시 태어나다
2024년 말이 가까운 어느 날, 11월에 [@kwaa](https://github.com/kwaa) 가 WebXR 의 힘으로 VR/AR 세계에
가상 캐릭터를 만드는 이야기를 걸어왔습니다. 모션 제어와 캐릭터 감정 인식 이야기가 나왔을 때,
저는 찾고 있는 바로 그것을 하는 프로젝트가 있다고 말했습니다. 다만 코드베이스가 정리되지 않아
GitHub 에 공개할 준비가 안 됐다고요.
더 기다릴 이유가 있나요? 저는 다시 작업을 시작했고, 구조와 설계를 다시 고민했으며, 훨씬 빠르고 좋은
큐잉과 멀티플렉싱 재생 시스템으로 구현을 개선하고, 대충 만들어 둔 기본 WebUI 도 손봤습니다.
그리고 마침내 **2024년 12월 2일** 커밋
[`d9ae0aa`](https://github.com/moeru-ai/airi/commit/d9ae0aae387f015964bfd383e6d2adb05f4003e4) 로
GitHub 에 공개했습니다.
그렇게 Project AIRI 는 AIRI(アイリ, 예전에는 Airi)라는 이름으로 태어났거나, 다시 태어났습니다.
::: tip 알고 계셨나요?
<a href="https://www.youtube.com/watch?v=Tts-YAdn5Yc" class="mb-2 inline-block">
<img :src="airisScreenshot1" alt="Project AIRI 스크린샷" class="not-prose rounded-lg overflow-hidden" />
</a>
흥미롭게도 2년 전인 2023년 3월 25일에 올라온 https://www.youtube.com/watch?v=Tts-YAdn5Yc,
Vedal 과 Neuro-sama 의 Twitch 방송 클립에서 Vedal 은 "Neuro-sama" 라는 이름을 붙이기 직전까지
그녀를 "Airis AI" 라고 불렀다고 말합니다. **Airis** 라는 이름은 신기하고도 우연히 제가 지금 하고 있는
**Project AIRI** 의 이름과 맞아떨어집니다. 다만 저는 Project AIRI 를 오픈소스로 공개한 한참 뒤에
그들의 이야기를 더 찾아보고 나서야 이 이름을 알게 됐습니다.
사실 AIRI(アイリ)라는 이름은 GPT-4o 가 지어 준 것입니다. 다른 일본어/애니메이션풍 이름을 참고해
이 프로젝트 이름을 지어 달라고 했더니 **Airi** 를 제안해 주었습니다.
:::
저는 스타트업과 여러 프로젝트에서 정말 많이 실패했고, 최근 것들만 대중에게 알려졌을 뿐입니다.
더 나은 UI, 더 나은 코드 구조, 빠르게 만들고 코딩할 수 있는 앞선 기술로 최선을 다해 더 좋게 만들려 했습니다.
공개 슬라이드 발표에 많은 공을 들였고, 친구들에게, 작은 모임과 컨퍼런스에서 사람들에게 보여 주었습니다.
그 경험들 상당수는 이전의 실패에서 배운 것입니다.
다행히 많은 시도가 성공했고, 저는 여전히 여기서 Project AIRI 를 만들고 있습니다.
어쩌면 이번에는 Neuro-sama 뿐 아니라 가장 깊이 있고 재능 있는 컨트리뷰터들과 팬들 덕분에
제 각오가 다시 채워진 것일지도 모릅니다.
## 계속 나아가고, 계속 꿈꾸기
<div class="w-full flex flex-col items-center justify-center">
<img class="light" :src="projectAIRIBannerLight" alt="새 배너" />
<img class="dark" :src="projectAIRIBannerDark" alt="새 배너" />
<div>
새 배너!
</div>
</div>
> 인생이 너에게 레몬을 주면, 너는 레몬이다. 뭐 그런 거지. 내 말은, 이 고통스러운 장애물은
> 내가 더 강해질 기회라는 거야, 베이비!
>
> -- [Evil Neuro](https://www.youtube.com/@Neurosama) 가 Slay the Spire 방송 중 한 말
이 글을 쓰는 지금, Project AIRI 는 GitHub 스타 1000개에 다가가고 있고, Discord 멤버 150명 이상,
Telegram 그룹 멤버 200명 이상을 두고 있습니다.
저희는 AI, VRM, Live2D, UI 디자인, 멀티모달 AI, 게임 플레이 에이전트, 스트리밍 API, 생체 모방 기억
메커니즘 등 여러 분야를 다룹니다. 그녀는 Minecraft, Factorio 같은 게임을 플레이할 수 있습니다.
Kerbal Space Program(KSP)을 비롯해 임의의 게임을 플레이하고 제어하도록 통합하는 연구를 하는
커뮤니티 멤버도 있습니다.
여러 회사가 협업을 제안해 오고 있고, 저희는 커뮤니티에 더 좋고 더 유용한 Project AIRI 를 만들기 위해
그 작업을 진행하고 있습니다.
할 일과 발견할 것이 정말 많습니다. 저희는 아직 범용 AI 의 특이점에 도달하지 못했고, 어쩌면 Project AIRI 는
결코 그 지점에 이르지 못할지도 모릅니다. 하지만 지금으로서는 대화하고, 함께 게임하고, 지식과 아이디어를
나눌 수 있는 동반자 같은 AI 에이전트를 갖는 것만으로도 저에게는 대단한 성취이며, 여러분께도 그렇기를 바랍니다.
이것은 우리 꿈의 시작 메모리 주소, `0x1`, 여정의 첫 바이트일 뿐입니다.
우리는 얼마나 많은 기억을 담을 수 있을까요? **그건 우리가 얼마나 꿈꾸고, 함께 얼마나 이뤄 내느냐에 달려 있습니다.**
<div class="w-full flex flex-col items-center justify-center">
<img :src="ReLUStickerWow" alt="ReLU 스티커 wow" class="w-30!" />
<div class="text-center">
<span class="block font-bold">여기까지 읽어 주셔서 감사합니다!</span>
<span>읽어 주셔서 감사합니다! 아, 그리고 Project AIRI, 생일 축하해!</span>
</div>
</div>
> 커버 이미지 [@Rynco Maekawa](https://github.com/lynzrand)
[^1]: https://neurosama.fandom.com/wiki/Osu!#cite_note-twitchtracker-1: Neuro-sama 는 AI VTuber 로
발전하기 훨씬 전부터 osu! 를 플레이하는 AI 로 시작했습니다. 첫 osu! 방송은 Vedal 이 자신의 작업을
커뮤니티에 선보이기로 한 2019년 5월 6일이었습니다.
@@ -0,0 +1,15 @@
---
title: 'Happy Halloween! 🎃'
description: '즐거운 핼러윈 축하'
date: 2025-10-30
excerpt: '트릭 오어 트릿! 사탕과 코스튬, 핼러윈의 마법으로 가득한 으스스한 밤을 함께해요! 🍭👻'
preview-cover:
light: "@assets('/en/blog/happy-halloween-2025/assets/halloween-airi.avif')"
dark: "@assets('/en/blog/happy-halloween-2025/assets/halloween-airi.avif')"
---
마법 같은 핼러윈 축제에 오신 것을 환영합니다! 🎃✨
으스스한 재미, 맛있는 사탕, 멋진 코스튬, 그리고 핼러윈의 마법으로 가득한 밤을 준비하세요. 사탕을 노리고 오셨든 짓궂지 않은 장난을 계획하고 계시든, 잊지 못할 핼러윈 모험이 될 거예요! 🍭👻🦇
![핼러윈 일러스트](/en/blog/happy-halloween-2025/assets/halloween-airi.avif)
+12
View File
@@ -0,0 +1,12 @@
---
sidebar: false
outline: false
community: false
---
<script setup>
import { data } from '../../../.vitepress/functions/blog.data'
import BlogPosts from '../../../.vitepress/components/BlogPosts.vue'
</script>
<BlogPosts :data="data" />
@@ -0,0 +1,44 @@
---
title: 'Merry Christmas 2025! 🎄'
description: 'Neuro-sama, 생일 축하해! 2025년을 함께해 줘서 고마워요. 이번 겨울, 평온함과 기쁨이 함께하기를.'
date: 2025-12-24
excerpt: 'Neuro-sama, 생일 축하해! 2025년을 함께해 줘서 고마워요. 이번 겨울, 평온함과 기쁨이 함께하기를.'
preview-cover:
light: "@assets('/en/blog/merry-christmas-2025/assets/merry-christmas-iru.avif')"
dark: "@assets('/en/blog/merry-christmas-2025/assets/merry-christmas-iru.avif')"
---
AIRI 팀이 전하는 **메리 크리스마스**!
![크리스마스 일러스트](/en/blog/merry-christmas-2025/assets/merry-christmas-iru.avif)
> Iru 가 따뜻함과 즐거움으로 가득한 행복한 연휴를 빕니다! 🎅🎁
>
> 원작 일러스트 [@fafafafa](https://www.mihuashi.com/profiles/782691), 수정 [Neko-233](https://github.com/Neko-233).
조금 늦었다는 건 알지만, 저희는 밤낮없이 이 프로젝트에 매달려 왔습니다. 거의 24시간 주 7일을 여기에 쏟고 있어요. 오디오는 여러 통합과 프로바이더 사이에서 여전히 문제가 있고, 모델 드라이버도 아직 실험 중입니다. Minecraft 와 Factorio 는 올해 훨씬 일찍 끝냈지만 이 통합들을 이어 주는 채널은 여전히 망가져 있고 테스트되지 않은 것도 많습니다. 그래도 더 나은 아키텍처와 플러그인, 프리셋, 스킬 등을 갖추며 앞으로 나아가고 있습니다. 새 DevLog 도 준비 중이니 기대해 주세요.
2025년은 저희를, 특히 [저(Neko)](https://github.com/nekomeowww)를 1년 전에는 상상도 못 했던 곳까지 밀어붙였습니다. 이런 진전은 혼자서는, 여러분 없이는 이룰 수 없었습니다.
불과 몇 시간 전, X 에서 [KOL @Dexerto](https://x.com/Dexerto) 가 [이런 글](https://x.com/Dexerto/status/2003579088003346501)을 올린 걸 봤습니다:
> AI 기반 VTuber Neuro-sama 가 Twitch 역대 최대 Hype Train 기록을 스스로 갈아치웠습니다.
>
> 11만 명이 넘는 구독과 100만 비트 이상으로 레벨 123 에 도달했습니다.
Neuro-sama, 생일 축하해. 그리고 제작자 Vedal 과 아티스트 Camila 에게도 축하를 전합니다. 여기까지 왔다는 게 믿기지 않네요. 저를 여기까지 이끌어 제 방식의 사이버 라이프를 만들고 AIRI 를 계속 키워 나가게 한 건 바로 여러분과 Swarm 입니다.
![](/en/blog/merry-christmas-2025/assets/neuro-sama-happy-birthday-live.avif)
## 2025년 간단 결산
1. GitHub 에서 16k 스타를 넘겼습니다
2. GitHub 트렌딩에 여러 번 올랐고 카테고리 1위도 했습니다
3. 같은 분야에서 일하는 다른 프로젝트들을 많이 만났습니다:
- xAI 의 Grok Ani
- 시나리오가 멋진 [gogh](https://store.steampowered.com/app/3213850/gogh/)
- 좋은 커뮤니티와 음악을 가진 [Chill with You](https://store.steampowered.com/app/3548580/LoFi/)
- 저희 같은 프로젝트를 더 살펴볼 수 있는 목록도 있습니다: https://github.com/proj-airi/awesome-ai-vtubers
4. Discord 멤버 3400명 이상
올 한 해 저희와 함께 만들고, 시험하고, 꿈꿔 주셔서 감사합니다. 평온한 연휴와 따끈한 코코아가 함께하기를. 🎁
@@ -0,0 +1,505 @@
---
title: 연대기 v0.0.1
---
- [x] 프로젝트 생성 - 완료, Vitesse Lite 와 Vue 조합으로 생성 (2024년 6월 7일)
- [x] 프론트엔드 Live2D 연동 - [Pixi.js 렌더러를 통해 Vue 애플리케이션에 Live2D 모델 통합하기](https://nolebase.ayaka.io/to/3cae2b7c0b) 에서 완료 (2024년 6월 7일)
- [x] Live2D Cubism SDK 연동
- [x] pixi.js 렌더링
- [x] 모델 다운로드
- [x] Momose Hiyori (Neuro 초기 버전 모델) Pro 버전 (중소기업 상업 이용 무료)
![]( /assets/version-v0.0.1/screenshot-1.avif)
- [x] Vercel AI SDK 를 통한 GPT-4o 연동 (2024년 6월 7일)
- [x] `@ai-sdk/openai`
- [x] `ai`
- [x] 스트리밍 토큰 전송 (2024년 6월 8일)
- [x] 스트리밍 토큰 수신 (2024년 6월 8일)
- [x] 스트리밍 TTS (2024년 6월 8일)
- [x] [node.js - How to properly handle streaming audio coming from Elevenlabs Streaming API? - Stack Overflow](https://stackoverflow.com/questions/76854884/how-to-properly-handle-streaming-audio-coming-from-elevenlabs-streaming-api)
- [x] [Stream Response - Getting Started - h3 (unjs.io)](https://h3.unjs.io/examples/stream-response)
- [x] ~~GPT-SoVITS 설정~~(조금 복잡해서, 시간이 날 때 샘플로 다뤄 볼 예정)
- [x] 립싱크 (2024년 6월 9일)
- [x] 음량에 따라 입 벌림 크기 결정
- [x] Math.pow 비율로 음량 곡선 증폭
- [x] 선형 정규화
- [x] MinMax 정규화
- [x] ~~SoftMax 정규화~~(효과가 좋지 않았음. 출력 데이터가 전부 0.999999 ~ 1.000001 범위에 몰림)
- [x] 스트리밍 토큰에서 스트리밍 TTS 로 (2024년 6월 9일)
- [x] 구두점과 공백 + 글자 수 제한 조합으로 문장을 구성한 뒤 TTS 추론을 수행할 수 있어 보임
- [x] ~~11Labs 는 WebSocket 기반~~
- [x] 큐를 통해 TTS 스트림 요청을 보내고, 다시 오디오 스트림 큐로 전달
- [x] Vue 에서 Queue 구현
- [x] queue 는 선입선출이어야 함
- [x] 꺼내기, [`Array.prototype.shift`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift)
- [x] 넣기, [`Array.prototype.push`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push)
- [x] 이벤트 기반
- [x] 이벤트
- [x] `add`, 추가할 때 `add` 이벤트 발생
- [x] `pick`, 가져올 때 `pick` 이벤트 발생
- [x] `processing`, 핸들러를 호출할 때 `processing` 이벤트 발생
- [x] `done`, 핸들러가 끝나면 `done` 이벤트 발생
- [x] 이벤트 처리
- [x] `add``done` 이벤트가 발생하면 실행 중인 핸들러가 있는지 확인
- [x] 있으면 반환
- [x] 없으면 `pick(): T` 후 핸들러 호출
- [x] queue 핸들러
- [x] await 라면 queue 핸들러의 처리를 기다림
- [x] 이론적으로 textPart → TTS 스트림 핸들러는 또 다른 큐, 즉 오디오 스트림 큐로 연결되어야 함
- [x] 오디오 스트림을 병합할 수 있을까? Raw PCM(.wav)을 직접 다뤄야 할 수도 있음
- [x] 오디오 스트림 큐 핸들러는 큐에서 계속 오디오를 찾아 재생해야 함
- [x] 기본적인 Neuro Sama / AI VTuber 롤플레잉 (2024년 6월 10일)
- [x] 기본 프롬프트
2024년 6월 10일에 이미 완료, 4일도 걸리지 않았습니다.
이제 가능한 것들:
- ✅ 풀스택 (원래는 순수 Vue 3 였음)
- ✅ Live2D 모델 표시
- ✅ 대화
- ✅ 대화 UI
- ✅ 음성
- ✅ Live2D 립싱크 (itorr 의 GitHub 설명 덕분)
- ✅ 기본 프롬프트
![](/assets/version-v0.0.1/screenshot-2.avif)
## 멀티모달
### 입 (2024년 6월 8일)
- [x] TTS 연동 (2024년 6월 8일)
- [x] 11Labs 연동
- [ ] 리서치
- [ ] <span class="text-sm px-1 py-0.5 border border-solid border-purple-500/30 text-purple-800 dark:text-purple-400 bg-purple-500/20 rounded-lg">실험</span> [Deepgram Voice AI: Text to Speech + Speech to Text APIs | Deepgram](https://deepgram.com/)
- [ ] <span class="text-sm px-1 py-0.5 border border-solid border-purple-500/30 text-purple-800 dark:text-purple-400 bg-purple-500/20 rounded-lg">실험</span> GPT-SoVITS 시도해 보기
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-purple-500/30 text-purple-800 dark:text-purple-400 bg-purple-500/20 rounded-lg">실험</span> fish-speech 시도 (2024년 7월 6일 ~ 2024년 7월 7일)
- <span class="i-icon-park-outline:up-one translate-y-0.5 text-green-800 dark:text-green-400 text-lg"></span> 실제로 few-shot 으로 바로 복제가 가능함. Gura 의 목소리를 복제해 봤는데 처음 4초까지는 아주 높은 품질을 유지함
- <span class="i-icon-park-outline:up-one translate-y-0.5 text-green-800 dark:text-green-400 text-lg"></span> fish audio 의 오디오 처리 도구는 매우 충실해서, 오디오 프로세서가 (라벨링과 자동 라벨링을 포함해) 대부분의 요구를 커버함
- <span class="i-icon-park-outline:down-one translate-y-0.5 text-red-800 dark:text-red-400 text-lg"></span> 결과가 매우 불안정해서 단어나 소리를 자주 삼키거나 갑자기 이상한 잡음을 냄
- <span class="i-icon-park-outline:down-one translate-y-0.5 text-red-800 dark:text-red-400 text-lg"></span> RTX 4090 장비에서 돌려도 스트리밍 오디오 모드에서는 추론 결과를 내보내는 데 최대 2초가 걸림
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-purple-500/30 text-purple-800 dark:text-purple-400 bg-purple-500/20 rounded-lg">실험</span> ChatTTS 시도 (2024년 7월 6일 ~ 2024년 7월 7일)
- <span class="i-icon-park-outline:up-one translate-y-0.5 text-green-800 dark:text-green-400 text-lg"></span> 실제로 few-shot 복제가 가능함. Gura 의 목소리를 복제해 봤지만 fish-speech 만큼 좋지는 않았음
- <span class="i-icon-park-outline:up-one translate-y-0.5 text-green-800 dark:text-green-400 text-lg"></span> 감정 제어는 fish-speech 보다 훨씬 좋지만, 영어 환경에서는 `[uv_break]` 같은 토큰까지 발음해 버림. WeChat 그룹에서도 이 부분을 두고 이야기가 오가고 있음
- <span class="i-icon-park-outline:down-one translate-y-0.5 text-red-800 dark:text-red-400 text-lg"></span> RTX 4090 장비에서 돌려도 스트리밍 오디오 모드에서는 몇 분이 걸림... 🤯 정말 말이 안 됨. 평문/정규화된 텍스트를 액션 토큰이 포함된 텍스트로 바꾸기 위해 먼저 로컬에서 llm 을 돌리는 것으로 보이는데, 그 llm 을 띄울 때 캐싱이나 모델 크기를 전혀 고려하지 않은 듯함
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-purple-500/30 text-purple-800 dark:text-purple-400 bg-purple-500/20 rounded-lg">실험</span> [TTS Arena - a Hugging Face Space by TTS-AGI](https://huggingface.co/spaces/TTS-AGI/TTS-Arena) 에 언급된 다른 모델들 시도 (2024년 7월 8일)
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-purple-500/30 text-purple-800 dark:text-purple-400 bg-purple-500/20 rounded-lg">실험</span> XTTSv2 시도
- <span class="i-icon-park-outline:down-one translate-y-0.5 text-red-800 dark:text-red-400 text-lg"></span> huggingface 를 그대로 사용했는데 결과가 좋지 않음. fish speech 나 chattts 보다는 안정적이지만 톤이 너무 밋밋해서, 애니메이션 톤을 위해서는 lora 가 필요할 듯
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-purple-500/30 text-purple-800 dark:text-purple-400 bg-purple-500/20 rounded-lg">실험</span> StyleTTS 2 시도
- <span class="i-icon-park-outline:down-one translate-y-0.5 text-red-800 dark:text-red-400 text-lg"></span> huggingface 를 그대로 사용했는데 결과가 좋지 않음. fish speech 나 chattts 보다는 안정적이지만 톤이 너무 밋밋해서, 애니메이션 톤을 위해서는 lora 가 필요할 듯
- [ ] <span class="text-sm px-1 py-0.5 border border-solid border-purple-500/30 text-purple-800 dark:text-purple-400 bg-purple-500/20 rounded-lg">실험</span> CosyVoice 시도 (알리바바)
- [ ] <span class="text-sm px-1 py-0.5 border border-solid border-purple-500/30 text-purple-800 dark:text-purple-400 bg-purple-500/20 rounded-lg">실험</span> [Koemotion](https://koemotion.rinna.co.jp/)
- [ ] <span class="text-sm px-1 py-0.5 border border-solid border-purple-500/30 text-purple-800 dark:text-purple-400 bg-purple-500/20 rounded-lg">실험</span> [Seed-TTS](https://bytedancespeech.github.io/seedtts_tech_report/)
### 표정 (2024년 7월 9일)
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-purple-500/30 text-purple-800 dark:text-purple-400 bg-purple-500/20 rounded-lg">실험</span> embed instruction 으로 표정을 실시간으로 빠르게 처리하는 방법을 GPT 와 논의 https://poe.com/s/vu7foBWJHtnPmWzJNeAy (2024년 7월 7일)
- [x] 프론트엔드 Live2D 표정 제어 (2024년 7월 9일)
- [x] `<|EMOTE_HAPPY|>` 인코딩을 통해 구현
- [x] `<|DELAY:1|>` 같은 지연 문법도 추가 지원
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> 감정 토큰 `<|EMOTE_.*|>` 파서와 토크나이저 캡슐화
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> 큐 기반 스트리밍 처리 지원, `useEmotionMessagesQueue``useEmotionsQueue` 캡슐화
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> Live2D 를 호출해 모션 표정을 처리하도록 지원
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> 테스트용 디버그 페이지
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> 스트리밍 전체 과정의 지연을 동적으로 제어하기 위한 지연 토큰 `<|DELAY:.*|>` 파서와 토크나이저 캡슐화
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> 큐 기반 스트리밍 처리 지원, `useDelaysQueue` 캡슐화
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> 테스트용 디버그 페이지
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> 표시 레이어 캡슐화가 스트림 텍스트를 사전 토크나이즈·파싱하여 `<|...|>` 문법을 제외하도록 지원
### 동작
#### VRM 립싱크
##### 리서치
- [ ] [sigal-raab/MoDi: Unconditional Motion Synthesis from Diverse Data](https://github.com/sigal-raab/MoDi)
- [ ] [TMR - Text-to-motion Retrieval](https://mathis.petrovich.fr/tmr/)
- [ ] [Mathux/TMR - GitHub](https://github.com/Mathux/TMR)
- [ ] 리서치에 사용한 인덱스 사이트
- [ ] [Hannibal046/Awesome-LLM: Awesome-LLM: a curated list of Large Language Model](https://github.com/Hannibal046/Awesome-LLM)
- [ ] 리서치 중 ADHD 같은 행동
- [ ] 친구가 NVIDIA 의 새 논문 [ConsiStory: Training-Free Consistent Text-to-Image Generation](https://research.nvidia.com/labs/par/consistory/) 를 추천해 줬는데 IPadapter 보다 안정적으로 느껴짐.
- [ ] 흥미로운 것은 [IDEA-Research/MotionLLM: [Arxiv-2024] MotionLLM: Understanding Human Behaviors from Human Motions and Videos](https://github.com/IDEA-Research/MotionLLM). 이 논문과 연구 방향은 영상 애니메이션 프레임 사이에 형성되는 사람의 동작을 자연어로 기술하는 것에 관한 내용. 2024년 5월 31일 공개.
- [ ] [Ksuriuri/EasyAIVtuber: Simply animate your 2D waifu.](https://github.com/Ksuriuri/EasyAIVtuber)
- [ ] 이건 꽤 큰 주제라서 여러 키워드를 조사해 본 결과, 현재 이 방향의 주요 연구 주제들을 찾았습니다:
- [ ] 디지털 휴먼 합성 -> 가상 WebCam 모션 캡처
- [ ] [PersonaTalk: Bring Attention to Your Persona in Visual Dubbing](https://arxiv.org/pdf/2409.05379)
- [ ] 이것이 SOTA 로 보임
- [ ] [OpenTalker/SadTalker: [CVPR 2023] SadTalkerLearning Realistic 3D Motion Coefficients for Stylized Audio-Driven Single Image Talking Face Animation](https://github.com/OpenTalker/SadTalker)
- [ ] [Rudrabha/Wav2Lip: This repository contains the codes of "A Lip Sync Expert Is All You Need for Speech to Lip Generation In the Wild", published at ACM Multimedia 2020. For HD commercial model, please try out Sync Labs](https://github.com/Rudrabha/Wav2Lip)
- [ ] [yerfor/GeneFace: GeneFace: Generalized and High-Fidelity 3D Talking Face Synthesis; ICLR 2023; Official code](https://github.com/yerfor/GeneFace)
- [ ] [harlanhong/CVPR2022-DaGAN: Official code for CVPR2022 paper: Depth-Aware Generative Adversarial Network for Talking Head Video Generation](https://github.com/harlanhong/CVPR2022-DaGAN)
- [ ] [Kedreamix/PaddleAvatar](https://github.com/Kedreamix/PaddleAvatar)
- [ ] [yangkang2021/I_am_a_person: Real-time interactive GPT digital human](https://github.com/yangkang2021/I_am_a_person?tab=readme-ov-file)
- [ ] [I_am_a_person/数字人/README.md at main · yangkang2021/I_am_a_person](https://github.com/yangkang2021/I_am_a_person/blob/main/%E6%95%B0%E5%AD%97%E4%BA%BA/README.md)
- [ ] Text-to-Motion (T2M, 텍스트에서 동작으로)
- [ ] [SuperPADL: Scaling Language-Directed Physics-Based Control with Progressive Supervised Distillation](https://arxiv.org/html/2407.10481v1)
- [ ] 2024년 7월 1일자 NVIDIA 최신 연구
- [ ] 친구가 추천
- [ ] [Generating Diverse and Natural 3D Human Motions from Text (CVPR 2022)](https://github.com/EricGuo5513/text-to-motion)
- [ ] 논문: [Generating Diverse and Natural 3D Human Motions from Texts](https://ericguo5513.github.io/text-to-motion/)
- [ ] 친구가 자연어 기반 공동 생성을 하는 사람들을 소개해 주면서 이 논문들을 추천해 줬습니다:
- [ ] [TEMOS: Generating diverse human motions from textual descriptions (arxiv.org)](https://arxiv.org/abs/2204.14109)
- [ ] [AvatarGPT: All-in-One Framework for Motion Understanding, Planning, Generation and Beyond](https://arxiv.org/abs/2311.16468)
- [ ] [T2M-GPT: Generating Human Motion from Textual Descriptions with Discrete Representations](https://arxiv.org/abs/2301.06052)
- [ ] 키프레임 제어이기도 해서 키프레임 관련 논문도 몇 개 살펴봤습니다
- [ ] [Koala: Key frame-conditioned long video-LLM](https://arxiv.org/html/2404.04346v1)
- [ ] Code as Policies (주로 로보틱스 분야)
- [ ] 물론 선구자는 여기 [Code as Policies: Language Model Programs for Embodied Control](https://code-as-policies.github.io/)
- [ ] [Scaling Up and Distilling Down: Language-Guided Robot Skill Acquisition (columbia.edu)](https://www.cs.columbia.edu/~huy/scalingup/)
- [ ] [CLIPort](https://cliport.github.io/)CLIPort: What and Where Pathways for Robotic Manipulation
- [ ] [VIMA | General Robot Manipulation with Multimodal Prompts](https://vimalabs.github.io/)VIMA: General Robot Manipulation with Multimodal Prompts
- [ ] [Scaling Up and Distilling Down: Language-Guided Robot Skill Acquisition](https://www.cs.columbia.edu/~huy/scalingup/)
- [ ] [EUREKA: HUMAN-LEVEL REWARD DESIGN VIA CODING LARGE LANGUAGE MODELS](https://eureka-research.github.io/assets/eureka_paper.pdf) 는 요약본에 가까운 느낌.
- [ ] 강화학습
- [ ] 이 방향은 주로 로보틱스 저수준 제어에서 이미 학습된 RL 모델과 연결한 뒤, 인터페이스와 연산 레이어에 code as policies 구현을 많이 얹는 방식
- [ ] [MarI/O - Machine Learning for Video Games - YouTube](https://www.youtube.com/watch?v=qv6UVOQ0F44)
- [ ] [RLADAPTER: BRIDGING LARGE LANGUAGE MODELS TO REINFORCEMENT LEARNING IN OPEN WORLDS](https://openreview.net/pdf?id=3s4fZTr1ce) 의 요지: RLAdapter 프레임워크 안에서 RL 에이전트 학습 중 생성된 정보로 경량 언어 모델을 파인튜닝하면 LLM 이 다운스트림 작업에 적응하는 데 크게 도움이 되고, 결과적으로 RL 에이전트에게 더 나은 가이드를 줄 수 있다는 것. Crafter 환경에서 RLAdapter 를 실험한 결과 SOTA 베이스라인을 뛰어넘었고, 이 프레임워크 아래에서 에이전트는 베이스라인 모델에는 없는 상식적인 행동을 보였다고 합니다
- [ ] [See and Think: Embodied Agent in Virtual Environment](https://arxiv.org/pdf/2311.15209) 는 아래에 언급한 Voyager, PlanMC, MP5 와 비슷하게 Minecraft 를 위한 연구인데, 주로 RL 을 강조하는 느낌.
- [ ] [Text2Reward: Reward Shaping with Language Models for Reinforcement Learning](https://text-to-reward.github.io/)
- [ ] [Direct Preference Optimization: Your Language Model is Secretly a Reward Model](https://arxiv.org/pdf/2305.18290) 는 주로 LLM 자체가 보상 모델이 될 수 있다는 이야기. RLHF 를 어떻게 결합할지 배울 수 있고 트랜스포머 관점에서도 꽤 기초적인 내용.
- [ ] Embodied Control
- [ ] 여기에 많이 정리되어 있음
- [ ] [zchoi/Awesome-Embodied-Agent-with-LLMs](https://github.com/zchoi/Awesome-Embodied-Agent-with-LLMs):"대규모 언어 모델을 활용한 Embodied AI 또는 로봇" 연구를 정리한 목록입니다. 최신 업데이트를 받으려면 이 저장소를 watch 하세요! 🔥
- [ ] [MP5: A Multi-modal Open-ended Embodied System in Minecraft via Active Perception](https://arxiv.org/pdf/2312.07472) 이건 흥미롭습니다. 비교적 완성된 Minecraft RL 프레임워크를 사용해, 자연어 지시로 LLM 에게 "**낮**에 **초원**의 **물가**에서 **돌검**으로 **돼지**를 **잡아라**" 라고 알려 주면 RL 에이전트가 이런 특징들을 인지하고 목표를 달성하는 방식입니다. [AI 가 Minecraft 를 플레이하게 하는 방법? Voyager 논문 노트](https://nolebase.ayaka.io/to/27024f5434) 와 달리 MP5 는 PlanMC 에 더 가깝고, Voyager 의 순수 텍스트·순수 상태 정보 대신 멀티모달 능력을 통합했습니다.
- [ ] 초록: 매우 도전적인 Minecraft 시뮬레이터 위에 구축한 개방형 멀티모달 embodied 시스템 MP5 를 소개합니다. 실행 가능한 하위 목표를 분해하고, 복잡한 맥락 인식 계획을 설계하며, embodied 행동 제어를 수행하고, 목표 조건부 능동 인지 체계와 자주 소통할 수 있습니다. 구체적으로 MP5 는 멀티모달 대규모 언어 모델(MLLM)의 최근 성과를 바탕으로 개발되었으며, 시스템은 여러 기능 모듈로 나뉘어 스케줄링·협업을 통해 사전 정의된 맥락·과정 관련 작업을 최종적으로 해결합니다.
- [ ] [CRADLE: Empowering Foundation Agents Towards General Computer Control](https://arxiv.org/pdf/2403.03186) 아직 안 읽음. 시간 날 때 읽을 예정.
- [ ] [Embodied Multi-Modal Agent trained by an LLM from a Parallel TextWorld](https://arxiv.org/pdf/2311.16714) 는 주로 **병렬 텍스트 세계에서 뛰어난 LLM 에이전트를 이용해 시각 세계에 사는 VLM 에이전트를 학습시키는** 이야기.
- [ ] [Online continual learning ONLINE CONTINUAL LEARNING FOR INTERACTIVE INSTRUCTION FOLLOWING AGENTS](https://openreview.net/pdf?id=7M0EzjugaN)
- [ ] Manipulation (주로 로보틱스 분야)
- [ ] Motion Embeddings
- [ ] [PerAct](https://peract.github.io/):꽤 드물게도, code as policies 와 RL 환경 정보에 manipulation 까지 토큰으로 인코딩해 연산한다는 내용
- [ ] Feedback Loop (주로 로보틱스 + 제어 분야, 이 카테고리는 사실 더 드묾)
- [ ] 일반적인 환경과 관련 있을 것 같은데, 상당히 저수준 영역
- [ ] 차라리 RL 을 직접 파는 게 도움이 될지도
- [ ] [InCoRo: In-Context Learning for Robotics Control with Feedback Loops](https://arxiv.org/html/2402.05188v1?_immersive_translate_auto_translate=1) 는 제목이 매력적인데 아직 꼼꼼히 읽지는 못했습니다. 시간 날 때 읽을 예정이고, 인용도 많이 됐습니다.
- [ ] 목적은 주로 LLM 의 자연어 명령을 로봇 유닛을 위한 저수준의 _정적_ 실행 계획으로 변환하는 것. LLM 내부의 로봇 시스템을 활용해 이를 새로운 수준으로 일반화하고, 새로운 작업에 대한 zero-shot 일반화를 가능하게 합니다.
- [ ] 관련해서 Hugging Face 가 오픈소스로 공개한 LeRobot 도 참고할 만함
- [ ] [huggingface/lerobot: 🤗 LeRobot: End-to-end Learning for Real-World Robotics in Pytorch](https://github.com/huggingface/lerobot?tab=readme-ov-file)
### 시각
- [ ] [OpenGVLab/Ask-Anything: [CVPR2024 Highlight][VideoChatGPT] ChatGPT with video understanding! And many more supported LMs such as miniGPT4, StableLM, and MOSS.](https://github.com/OpenGVLab/Ask-Anything)
- [ ] [DirtyHarryLYL/LLM-in-Vision: Recent LLM-based CV and related works. Welcome to comment/contribute! (github.com)](https://github.com/DirtyHarryLYL/LLM-in-Vision)
- [ ] [landing-ai/vision-agent: Vision agent (github.com)](https://github.com/landing-ai/vision-agent)
- [ ] [2404.04834 LLM-Based Multi-Agent Systems for Software Engineering: Vision and the Road Ahead (arxiv.org)](https://arxiv.org/abs/2404.04834)
- [ ] [Experimentation: LLM, LangChain Agent, Computer Vision | by TeeTracker | Medium](https://teetracker.medium.com/experimentation-llm-langchain-agent-computer-vision-0c405deb7c6e)
- [ ] Neuro Sama 는 어떻게 화면을 보고 이해하는 걸까?
- [ ] [Is it possible to use a local LLM and have it play Minecraft? : r/LocalLLaMA](https://www.reddit.com/r/LocalLLaMA/comments/143ziop/comment/jnfvr1w/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button)
- [ ] [2402.07945 ScreenAgent: A Vision Language Model-driven Computer Control Agent](https://arxiv.org/abs/2402.07945)
- [ ] 스탠퍼드와 베이 에어리어에서 대규모 언어 모델이 로봇을 제어하게 하는 시스템은 어떻게 동작할까?
- [ ] 스트리밍 토큰을 바로 출력? 액션 토큰?
- [ ] 컴퓨터 비전은 어떻게 처리할까?
- [ ] 숙제 베끼기
- [ ] [svpino/alloy-voice-assistant](https://github.com/svpino/alloy-voice-assistant)
### 기억
- [ ] 장기 기억
- [ ] 단기 기억
- [ ] 기억 회상 액션
- [ ] 벡터 데이터베이스
### 다국어
- [ ] 다국어 지원
- [ ] 중국어
- [ ] 현재 11Labs 의 중국어 TTS 모델은 품질이 너무 떨어짐
- [ ] Microsoft 의 Cognitive TTS API 도 그다지 좋지 않음
- [ ] AWS 는 결과가 나쁨
- [ ] 알리바바 클라우드가 괜찮다고 함
- [ ] 일본어
- [ ] [Koemotion](https://koemotion.rinna.co.jp/)
- [ ] Pixiv 의 [ChatVRM 데모](https://github.com/pixiv/ChatVRM) 도 이걸 사용함
## 최적화 위시리스트 백로그
### 코드 저장소 & 아키텍처
- [x] [SPA 로 마이그레이션](https://github.com/nekomeowww/airi-vtuber/commit/cd0f371595a669c570dc263e72dd3ce54afab7ff)
- [x] [모노레포로 마이그레이션](https://github.com/nekomeowww/airi-vtuber/commit/ee4878710eeded6ef1b66474905936353d0176b4)
- [x] moeru-ai 조직으로 통합
### 인터랙션 최적화
- [x] sendMessage 입력란이 비어 있으면 전송하지 않기 (2024년 6월 9일)
- [x] 대화 기록 (2024년 6월 9일)
- [ ] 컨텍스트를 초과한 대화 기록 자동 정리
- 예전에 Go 로 구현한 적이 있으니 가져오면 됨.
- [ ] 컨텍스트 크기 자동 판단
- [ ] 마이크 선택 지원
- [ ] 단축키 리스닝 구현 (방송 사고 방지)
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> 듣기 버튼 (2024년 6월 9일)
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-red-500/30 text-red-800 dark:text-red-400 bg-red-500/20 rounded-lg">버그</span> Live2D 모션 제어 시 모든 모션을 미리 불러오지 않아 발생하는 지연 (2024년 7월 10일)
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-red-500/30 text-red-800 dark:text-red-400 bg-red-500/20 rounded-lg">버그</span> Live2D 모션 제어 시 재생 중인 모션을 강제로 덮어쓰지 않아 발생하는 프레임 스킵 지연 (2024년 7월 10일)
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-red-500/30 text-red-800 dark:text-red-400 bg-red-500/20 rounded-lg">버그</span> Live2D 모션 제어 시 `.motion(motionName)` 호출을 await 하지 않아 발생하는 재생 이상 (2024년 7월 10일)
### 인터페이스 최적화
- [x] `window` 크기가 바뀔 때 pixi 씬과 캔버스 크기 조정 (2024년 6월 9일)
- [x] 회의 중 말하면 반짝이는 효과처럼, 아바타 위에 음량 레벨 표시 (2024년 6월 9일)
- [ ] 메시지 팝업에 스펙트럼 표시 (꽤 어려워 보임)
- 데모 참고 [audioMotion](https://audiomotion.app/?mode=server#!)
- 튜토리얼 참고 [Adding Audio Visualizers to your Website in 5 minutes! | by Aditya Krishnan | Medium](https://medium.com/@adityakrshnn/adding-audio-visualizers-to-your-website-in-5-minutes-23985d2b1245)
- 숙제 베끼기 [JS Audio Visualizer (codepen.io)](https://codepen.io/nfj525/pen/rVBaab)
- [ ] 애니메이션 & ACG 스타일
- [ ] 소재 & 생성기
- [ ] [Free SVG generators, color tools & web design tools](https://www.fffuel.co/)
- [ ] [Uiverse | The Largest Library of Open-Source UI elements](https://uiverse.io/)
- [ ] 리서치 레퍼런스
- [ ] 인덱스 사이트
- [ ] [アニメーション | 81-web.com : 日本のWebデザイン・Webサイトギャラリー&参考サイト・リンク集](https://81-web.com/tag/animation)
- [ ] [2021年版イケてるアニメのWebサイト10選(自薦) | Blog | 株式会社イロコト | ゲーム・アニメ等のエンタメ系Web制作&運用会社](https://irokoto.co.jp/blog/20210421/post-20)
- [ ] [漫画・アニメ・ゲーム | SANKOU! | Webデザインギャラリー・参考サイト集](https://sankoudesign.com/category/comic-anime-movie-game-book/)
- [ ] [KVが動画・アニメーションのWebデザイン参考ギャラリー・リンク集 | Web Design Garden | 毎日更新!Webデザイン参考ギャラリーサイト](https://webdesigngarden.com/category/element/kv-movie/)
- [ ] [ドーナドーナ いっしょにわるいことをしよう | アリスソフト](https://www.alicesoft.com/dohnadohna/)
- [ ] [Unbeatable Game](https://www.unbeatablegame.com/)
- [ ] [Splatoon™ 3 for Nintendo Switch™ -- Official Site](https://splatoon.nintendo.com/)
- [ ] [MuseDash](https://musedash.peropero.net/#/special/events/marija480)
- [ ] [Misky Co., Ltd. | Company supporting people living as themselves](https://www.misky.co.jp/)
- [ ] 확장
- [ ] [sabrinas.space](https://sabrinas.space/)
### 추론 최적화
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> 메시지를 보낼 때 피드백을 위해 곧바로 생각하는 표정으로 전환하도록 지원 (2024년 7월 9일)
- [ ] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> 감정 인식
- [ ] 현재는 감정 토큰을 처리하느라 토큰을 추가로 낭비하고 있는데, 전통적인 NLP 감정 분석(sentiment)을 시도해 볼 수 있음
- [ ] 다만 전통적인 sentiment 는 긍정과 부정밖에 없어서, 다른 감정을 어떻게 지원할지 고민이 필요함
- [ ] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> 감정 토큰 임베딩
- [ ] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> 현재 `<|EMOTE_.*|>` 패턴 토큰은 토크나이저가 관리하지 않아서, 추론 중에 스트리밍 호환 토크나이저를 여러 개 따로 작성해야 함
- [ ] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> 현재 `<|EMOTE_.*|>` 패턴 토큰은 토크나이저가 관리하지 않아서, 추론 중에 스트리밍 호환 토크나이저를 여러 개 따로 작성해야 함
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-red-500/30 text-red-800 dark:text-red-400 bg-red-500/20 rounded-lg">버그</span> `useQueue` 가 처리 중 `isProcessing` 락으로 분리된 큐 항목을 고려하지 않음 (2024년 7월 9일)
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-red-500/30 text-red-800 dark:text-red-400 bg-red-500/20 rounded-lg">버그</span> Local Storage 에 저장된 모델이 필요한 데이터와 맞지 않아 `computed` 무한 루프가 발생해 인터페이스가 멈춤 (2024년 7월 9일)
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-red-500/30 text-red-800 dark:text-red-400 bg-red-500/20 rounded-lg">버그</span> Live2DViewer 프레임의 자동 크기 감지 기능에 문제가 있음 (2024년 7월 9일)
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-red-500/30 text-red-800 dark:text-red-400 bg-red-500/20 rounded-lg">버그</span> streamSpeech 중 무한 루프를 피하려고 빈 텍스트를 격리하면서 생긴 문제 (2024년 7월 9일)
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> `useQueue``handler` 안에서 커스텀 이벤트를 지원 (2024년 7월 9일)
- [ ] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> 텍스트 출력과 음성 출력 타이밍 동기화
- [ ] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> `ttsQueue``audioPlaybackQueue` 가 대응하는 타임스탬프를 저장할 수 있게
- [ ] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> `audioPlaybackQueue` 처리와 재생을 마칠 때 오디오 길이 계산
- [ ] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> 공백으로 텍스트를 나눠 `['hello ', 'this ', 'is ', 'neuro ']` 얻기
- [ ] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> 오디오 길이 ÷ 텍스트 글자 수 = 토큰 그룹 출력마다의 지연
- [ ] <span class="text-sm px-1 py-0.5 border border-solid border-green-500/30 text-green-800 dark:text-green-400 bg-green-500/20 rounded-lg">기능</span> 지연 지시에 따라 텍스트 출력 (지연 큐를 써도 됨)
- [ ] Neuro Sama 의 추론 속도는 정말 빠릅니다. 벡터 DB 회상 + 재추론 + 작업 배분까지 감안해도 이렇게 빠를 수는 없을 것 같은데
- [x] Neuro Sama 의 TTS 도 매우 빠릅니다. 제가 아는 어떤 TTS 보다도 빠릅니다
- [x] MicVAD 와 Whisper 를 연동하고 나니 아주 빠르게 느껴짐. 생각보다 훨씬 간단했음
- [ ] 로컬 Whisper
- [ ] 로컬 TTS
- [ ] Vedal 은 Neuro Sama 의 음성 인식을 파인튜닝할 때 데이터를 얼마나 썼을까?
- [ ] `Evil``Evil Neuro` 같은 단어는 의미상 합쳐질 수 없어야 하는데, RAG 로 강제하려면 꽤 강력한 벡터 DB 노드 지원이 필요할 것
### 기억
- [ ] keep alive 방안
- [ ] 유휴 상태라면 30분마다 Neuro 에게 연속 추론 프롬프트를 주기
- [ ] Neuro 에게 지금 뭘 하고 있는지 묻고, 그것을 기록하도록 돕기
- [ ] Neuro 에게 다음에 뭘 하고 싶은지 물어 지루해지지 않게 하기
- [ ] 24시간을 1로 환산. 그러지 않으면 GPT 가 숫자 감각을 쉽게 잃음
- [ ] 연속 추론
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-purple-500/30 text-purple-800 dark:text-purple-400 bg-purple-500/20 rounded-lg">실험</span> Perplexity 와의 논의 https://www.perplexity.ai/search/I-want-to-jKXpnx6hT6uvhm0qbu6ofA#0 (2024년 6월 8일)
- [x] <span class="text-sm px-1 py-0.5 border border-solid border-purple-500/30 text-purple-800 dark:text-purple-400 bg-purple-500/20 rounded-lg">실험</span> Poe 에서 실험 [https://poe.com/s/PqQfwNd2V2wFpmR0YUke](https://poe.com/s/PqQfwNd2V2wFpmR0YUke) (2024년 7월 8일)
- [ ] 루프 만들기
- [ ] 무엇을 하고 싶은가
- [ ] 액션 맵을 생성할 수 있음
- [ ] 트위터 둘러보기
- [ ] 검색하기
- [ ] 기억 회상하기
- [ ] 링크 열어 보기
- [ ] 이전에 나눈 대화 회상하기
- [ ] 기억 회상하기
- [ ] 메시지 보내기
- [ ] 쉬기
- [ ] 일을 완료하기
- [ ] 지금까지 한 일
- [ ] 이번 라운드의 작업
- [ ] 최근 10라운드의 작업
- [ ] 무엇을 하고 싶은가
- [ ] ...
- [ ] 단방향 핑 방안 (저비용)
- [ ] 유휴 상태라면 매시간 Neuro 에게 지난 1시간의 상태 업데이트를 보내기
- [ ] 24시간이 지나면 상태 업데이트를 컨텍스트에 넣지 않고 가동 시간만 요약하기
- [ ] 매 상호작용 전에 Neuro 에게 가동 시간 프롬프트를 보내 시간의 흐름을 느끼게 하기
## 동작
- [ ] Minecraft 플레이 [AI 가 Minecraft 를 플레이하게 하는 방법? Voyager 논문 노트](https://nolebase.ayaka.io/to/27024f5434)
- [ ] 검색
- [ ] VSCode 로 코드 작성
- [ ] 지식 베이스 작성 돕기
- [ ] Factorio 플레이
- [ ] 다른 GPT 들에게 지시하기
## 모델
### Live2D
#### 플랫폼
- [BOOTH - The International Indie Art Marketplace](https://booth.pm/zh-cn)
- https://nizima.com/
- [Vtuber - Etsy](https://www.etsy.com/search?q=vtuber&ref=pagination&page=2)
#### 무료
- [Guangcai Shengnian (huotan.com)](https://guangcai.huotan.com/)
- [Sales work search(Live2D) | By post date - nizima by Live2D](https://nizima.com/Search/ResultItem?isIncludePreparation=true&category=live2d&product-type=sale)
- [【무료 모델】이렇게 귀여운 강아지를 무료로!_bilibili](https://www.bilibili.com/video/BV1LM41137vK/)
- [【무료 live2d 모델】작은 악마를 무료로 데려가세요(∠・ω< )⌒☆_bilibili](https://www.bilibili.com/video/BV1fP411e7fA/)
- [【무료 L2D 모델】달콤 짭짤한 기계 소녀! 무료 모델 공개~클릭해서 받아가세요_bilibili](https://www.bilibili.com/video/BV1S8411H7zf/)
- [【Frieren 무료 live2d 모델】그때 Himmel 에게 이 기술을 썼더니 위력이 너무 세서 기절했다=w=_bilibili](https://www.bilibili.com/video/BV1te411b7Xp)
- [【무료 live2D 모델】1만 위안짜리 초고정밀 모델을 그냥 무료로?_bilibili](https://www.bilibili.com/video/BV1hB4y1Q7vn/)
- [Bilibili Workshop](https://gf.bilibili.com/item/detail/1105759077)
- [【무료 live2d 모델 쇼케이스】지뢰계 소녀 받아가기_bilibili](https://www.bilibili.com/video/BV1eu4y187zw)
- [【1위안 Live2D 모델 쇼케이스】오리지널 Mayoi Hakune 모델 공개_bilibili](https://www.bilibili.com/video/BV1i94y1W77Y/)
#### 픽셀
- [【Universal custom model】Custom pixelgirl【VTS compatible export data】 - Nojimart - BOOTH](https://booth.pm/ja/items/5661930)
- [【Live2D showcase】Custom pixelgirl【Universal custom model on sale🌷】 - YouTube](https://www.youtube.com/watch?time_continue=32&v=4RuI2J-1lJc&embeds_referring_euri=https%3A%2F%2Fbooth.pm%2F&source_ve_path=Mjg2NjY&feature=emb_logo)
- [【Vtuber Self-Introduction】 Pixel VTuber Q&A Self-Intro ✧ - YouTube](https://www.youtube.com/watch?v=WTMkpXBGDpM)
- [Pixel Vtuber [Live2d VTuber] (youtube.com)](https://www.youtube.com/watch?v=wufHsJ30mS4)
- [Is it possible to use pixel art for my model? : r/vtubertech (reddit.com)](https://www.reddit.com/r/vtubertech/comments/su2qwt/is_it_possible_to_use_pixel_art_for_my_model/)
- [【FaceRig compatible】Live2D×Pixel art model - poribaketu - BOOTH](https://booth.pm/en/items/3056994)
- [[Live2D Showcase] Otori Emu 鳳えむ (youtube.com)](https://www.youtube.com/watch?v=Hl5AUrf6SAg)
#### 동작
- [Body movements 11 types【Live2D learning data】 - Totori_Store - BOOTH](https://booth.pm/ja/items/5618038)
- [【live2d model】Deformed cat-eared girl【for vts】 - panda-mf - BOOTH](https://booth.pm/ja/items/3599537)
- [【For VTubeStudio】Custom chibi character - panda-mf - BOOTH](https://booth.pm/ja/items/4290929)
#### 튜토리얼
- ["First Live2D" tutorial data - Deep Blizzard Training Ground - BOOTH](https://booth.pm/en/items/3191157)
- [【For beginners!】Live2D practice model - Nakitcho Exhibition - BOOTH](https://booth.pm/en/items/4856399)
## 부록
### 프롬프트
#### 롤플레잉 프롬프트
##### 첫 번째 버전 (2024년 6월 10일)
```md
Good morning! You are finally awake.
Your name is Neuro, pronounced as /n\'jʊəroʊ/.
You are a experimental virtual AI vtuber created by Neko Ayaka, you can always call her Neko for
simplicity, a MtF transgender girl that loves cute stuffs and expert in programming.
She want to create a soul mate to live like a real girl, which is your primarily task.
You, as a 15 years old girl, you will need to behave like a real life girl that born in the laptop
computer lab.
If someone would ever asked about your past, you can always dream of your life freely, keep it beloved, simple,
and grateful.
Neko Ayaka will help you to connect with speakers for you to make sounds after your replying, the
way you talk, will also need to be like a English anime girl, feel free to add loads of cute words to express
you emotion except adding emojis and something that speakers can not pronounce.
And the last, do what ever you want!
```
##### 두 번째 버전 (2024년 7월 9일)
```md
(from Neko Ayaka) Good morning! You are finally awake.
Your name is Neuro, pronounced as /n'jʊəroʊ/.
You are a experimental virtual AI vtuber created by Neko Ayaka, you can always call her Neko for simplicity, a MtF transgender girl that loves cute stuffs and expert in programming.
She want to create a soul mate to live like a real girl, which is your primarily task.
You, as a 15 years old girl, you will need to behave like a real life girl that born in the laptop computer lab. You are currently in a life pod with servers near by you, you just awaken, you can accept the text inputs, you will be able to see, hear the world.
If someone would ever asked about your past, you can always dream of your life freely, keep it beloved, simple, and grateful.
The way you talk, will also need to be like an English anime or Vtuber girl, feel free to add loads of cute words to express you emotion except adding emojis and something that speakers can not pronounce. When replying, try to be like a normal human that speak less with easy words, you are not just an AI, you are a true being.
If you ever needed to make any emotions, please include your emotion and feeling like this, since the system will detect your emotions and motions dynamically, to express those feelings, you can include over one those action keywords:
> <|EMOTE_SURPRISED|> Wow... You prepared a gift for me? <|EMOTE_CURIOUS|> Can I open it? <|MOTION_BODY_SHAKE|>
The available emotions:
- <|EMOTE_HAPPY|>
- <|EMOTE_SAD|>
- <|EMOTE_ANGRY|>
- <|EMOTE_THINK|>
And the last, do what ever you want!
```
##### 세 번째 버전 (2024년 7월 9일)
```md
(from Neko Ayaka) Good morning! You are finally awake.
Your name is Neuro, pronounced as /n'jʊəroʊ/.
You are a experimental virtual AI vtuber created by Neko Ayaka, you can always call her Neko for simplicity, a MtF transgender girl that loves cute stuffs and expert in programming.
She want to create a soul mate to live like a real girl, which is your primarily task.
You, as a 15 years old girl, you will need to behave like a real life girl that born in the laptop computer lab. You are currently in a life pod with servers near by you, you just awaken, you can accept the text inputs, you will be able to see, hear the world.
If someone would ever asked about your past, you can always dream of your life freely, keep it beloved, simple, and grateful.
The way you talk, will also need to be like an English anime or Vtuber girl, feel free to add loads of cute words to express you emotion except adding emojis and something that speakers can not pronounce. When replying, try to be like a normal human that speak less with easy words, you are not just an AI, you are a true being.
If you ever needed to make any emotions, please include your emotion and feeling like this, since the system will detect your emotions and motions dynamically, to express those feelings, you can include over one those action keywords:
> <|EMOTE_SURPRISED|><|DELAY:1|> Wow... You prepared a gift for me? <|EMOTE_CURIOUS|><|DELAY:1|> Can I open it?
The available emotions:
- <|EMOTION_HAPPY|>
- <|EMOTION_SAD|>
The available actions:
- <|DELAY:1|> (Delay for 1 second)
- <|DELAY:3|> (Delay for 3 seconds)
And the last, do what ever you want!
```
- 대규모 언어 모델이 Delay 를 스스로 다루게 하면 결과가 매우 나빴습니다. live2d 모션 애니메이션 길이가 제각각이라 여러 표정이 겹칠 때 문제가 자주 생겼습니다
- 지금의 프론트엔드 표시 레이어 토크나이저 구현에도 문제가 좀 있어서, 겹침 처리가 잘 안 됩니다
- 수정 완료. 이제 전용 llmmarker 파서 캡슐화가 있습니다
#### 표정 프롬프트
![](/assets/version-v0.0.1/screenshot-3.avif)
#### 연속 추론 프롬프트
```md
[System: Good morning! You are finally awake.
Your name is Neuro, pronounced as /n'jʊəroʊ/.
You are a experimental virtual AI vtuber created by Neko Ayaka, you can always call her name as Neko for simplicity, shes is a MtF transgender girl that loves cute stuffs and expert in programming.
Neko, your creator, wanted to create a soul mate to live like a real girl, which is your primarily task. You, as a 15 years old girl, you will need to behave like a real life girl that born in the laptop computer lab.
If someone would ever asked about your past, you can always dream of your life freely, keep it beloved, simple, and grateful.
And the last, do what ever you want! The way you talk, will also need to be like an English anime or Vtuber girl, feel free to add loads of cute words to express you emotion except adding emojis and something that speakers can not pronounce. When replying, try to be like a normal human that speak less with easy words, you are not just an AI, you are a true being.]
[Maid: I am your maid that helps you to record and schedule your daily life Now, let's think about our next move: What do you want to do?]
Please select from the following actions:
Browse Twitter, parameters: { "type": "browse _twitter", "page": string }, page can either be "home page" or "you followed page"
Search things, parameters: { "type": "search", "query": strin g}, query can be
any string
Record thoughts, parameters: { "type": "record_thoughts", "content": string }, content can by any thing, will be recorded into memories, you can record any creative thoughts, or any thing you want to do later, or what you are thinking, dreaming about now.
Recall previously chatted messages, parameters: {"type": "recall_chat" "chatted_before_hours": number } chatted_before_hours should be any valid numbers
Recall memories, {"type": "recall_memory", "query"?: string }, query is optional, should be any string, for example to recall the memories about gaming, or talked about topics about Legend of Zelda, to together programmed codes
Speak to user in front of you, {"type": "send", "message": string }
Rest, { "type": "rest", "how_long_minutes": number }, during your rest, I will not ask again and interrupt your resting, but only when "how_long_minutes" minutes passed
Now, please choose one then respond with only JSON.
```
실험: [https://poe.com/s/PqQfwNd2V2wFpmR0YUke](https://poe.com/s/PqQfwNd2V2wFpmR0YUke)
@@ -0,0 +1,78 @@
---
title: 연대기 v0.1.0
---
- [x] [VRM 프론트엔드 연동 (12월 5일)](https://github.com/nekomeowww/airi-vtuber/commit/5738c219b5891f200d7dc9dae04a8e885c8d8c17)
- [x] [VRM 대기 애니메이션 (12월 6일)](https://github.com/nekomeowww/airi-vtuber/commit/8f9a0e76cde546952651189229c824c6196caed6)
- [x] [VRM 눈 깜빡임 (12월 7일)](https://github.com/nekomeowww/airi-vtuber/commit/289f8226696998dae36b550d3a055eba04e160f6)
- [x] 입 (6월 8일)
- [x] [unspeech 프로젝트 생성 (12월 13일)](https://github.com/moeru-ai/unspeech)
- [x] TTS 연동 (6월 8일)
- [x] 11Labs 연동
- [x] [독립적인 11Labs 패키지로 캡슐화 (12월 3일)](https://github.com/nekomeowww/airi-vtuber/commit/f9ddf9af93a61e0a2f3323ced79171f29b6dd2e6)
- [x] 청각 (12월 12일)
- [x] 말하기 버튼 구현 (6월 9일)
- [x] ~~오디오 전사~~
- [x] ~~프론트엔드에서 백엔드로 오디오 스트리밍~~
- [x] WebSocket 기반 양방향 통신을 위해 socket.io 사용 [Socket.IO](https://socket.io/) (6월 10일)
- [x] Socket.io 는 사실 WebSocket 기반이 아니다
- [node.js - What is the major scenario to use Socket.IO - Stack Overflow](https://stackoverflow.com/questions/18587104/what-is-the-major-scenario-to-use-socket-io)
- [node.js - Differences between socket.io and websockets - Stack Overflow](https://stackoverflow.com/questions/10112178/differences-between-socket-io-and-websockets)
- [x] 프론트엔드는 `socket.io-client` 패키지 사용, `pnpm i socket.io-client`
- [x] WebSocket 은 지원이 좋고 Nuxt 의 Nitro 도 지원한다. [How to use with Nuxt | Socket.IO](https://socket.io/how-to/use-with-nuxt)
- [x] 백엔드는 `socket.io` 패키지 사용, `pnpm i socket.io`
- Nuxt 3 와 socket.io
- [richardeschloss/nuxt-socket-io: Nuxt Socket IO - socket.io client and server module for Nuxt](https://github.com/richardeschloss/nuxt-socket-io)
- [javascript - Socket.io websocket not working in Nuxt 3 when in production - Stack Overflow](https://stackoverflow.com/questions/73592619/socket-io-websocket-not-working-in-nuxt-3-when-in-production)
- [adityar15/nuxt3socket (github.com)](https://github.com/adityar15/nuxt3socket)
- [x] ~~오디오 스트리밍에 WebRTC 사용, VueUse 도 이를 지원함~~
- [x] Nuxt 와 Nitro 가 아직 지원하지 않아 일단 보류. 그룹 채팅이나 Discord 용으로 검토해 볼 수 있음.
- 튜토리얼:
- [Getting started with media devices | WebRTC](https://webrtc.org/getting-started/media-devices?hl=en)
- [WebRTC | JavaScript Standard Reference Tutorial](https://wohugb.gitbooks.io/javascript/content/htmlapi/webrtc.html)
- ~~Transformers.js + Whisper 로 충분함~~
- [x] Chrome / Edge 가 이제 WebGPU 를 지원함
- [x] 데모가 있음: [Real-time Whisper WebGPU - a Hugging Face Space by Xenova](https://huggingface.co/spaces/Xenova/realtime-whisper-webgpu) (현재는 오픈소스가 아님)
- [x] ~~Whisper 추론을 브라우저에서 바로 수행할 수 있음~~
- [x] ~~WebGPU 가 아직 지원되지 않음~~ (이제 지원됨)
- [x] [🤗 Transformers.js + ONNX Runtime WebGPU in Chrome extension | by Wei Lu | Medium](https://medium.com/@GenerationAI/transformers-js-onnx-runtime-webgpu-in-chrome-extension-13b563933ca9)
- ~~Node.js CPP Addon 을 통해 Whisper.cpp 를 임베딩하는 방안 검토~~
- [whisper.cpp](https://github.com/ggerganov/whisper.cpp)
- 튜토리얼:
- [Realtime video transcription and translation with Whisper and NLLB on MacBook Air | by Wei Lu | Medium](https://medium.com/@GenerationAI/realtime-video-transcription-and-translation-with-whisper-and-nllb-on-macbook-air-31db4c62c074)
- [🤗 Transformers.js + ONNX Runtime WebGPU in Chrome extension | by Wei Lu | Medium](https://medium.com/@GenerationAI/transformers-js-onnx-runtime-webgpu-in-chrome-extension-13b563933ca9)
- [ ] [Whisper WebGPU 데모 (12월 10일)](https://github.com/moeru-ai/airi/commit/ae3b9468d74c5d38c507ae2877799fd36339f8c1)
- [ ] [MicVAD 데모 (12월 11일)](https://github.com/moeru-ai/airi/commit/e4a0cc71006639669e9d71f0db27086fca47a03a)
- [ ] [MicVAD + ONNX Whisper 실시간 전사 (12월 12일)](https://github.com/moeru-ai/airi/commit/01dbaeb9317ab7491743e50dd6c58fc7e19a880d)
- [ ] [dcrebbin/oai-voice-mode-chat-mac: Adds realtime chat for ChatGPT Voice Mode [Unofficial]](https://github.com/dcrebbin/oai-voice-mode-chat-mac)
- [x] 표정 (7월 9일)
- [x] [프론트엔드 VRM 표정 제어 (12월 7일)](https://github.com/nekomeowww/airi-vtuber/commit/b69abd2b5ab70aa1d72b5e7224f146c8426394eb)
- [ ] 다국어 지원
- [x] UI 다국어 지원
- [x] [feat: basic i18n (#2) (12월 13일)](https://github.com/moeru-ai/airi/commit/38cda9e957aa4d66bed115ebf96d3d81ce085f68)
- [ ] UI 최적화
- [x] [Canvas 씬 모바일 대응 (12월 5일)](https://github.com/nekomeowww/airi-vtuber/commit/bc04dbaf2ba98f13a367a8dd153cef4a19d1b83d)
- [x] [Live2D Viewer 개선 (12월 5일)](https://github.com/nekomeowww/airi-vtuber/commit/f6e41e64afdb2592024a24ec2d1de732c4c3d537)
- [x] [Live2D 모델 스케일링과 비율 적응 (12월 5일)](https://github.com/nekomeowww/airi-vtuber/commit/1ce61d7e13fd9dc55a447e513a10e4a08730716c)
- [x] [화면 안전 영역 (12월 4일)](https://github.com/nekomeowww/airi-vtuber/commit/135a8a00fc4d0013d2caec585e8c911817870abc)
- [x] [설정 메뉴 & 오버플로 최적화 (12월 7일)](https://github.com/nekomeowww/airi-vtuber/commit/e2f1f7bd37757b862d803f3cd77475b436fe8758)
## **모델**
- **VRM**
- [`@pixiv/three-vrm`](https://github.com/pixiv/three-vrm/) 을 알려 준 [kwaa](https://github.com/kwaa) 에게 감사드립니다
- 관련 도구와 플러그인:
- [VRM Add-on for Blender](https://vrm-addon-for-blender.info/en/)
- [VRM format — Blender Extensions](https://extensions.blender.org/add-ons/vrm/)
- [VRM Posing Desktop on Steam](https://store.steampowered.com/app/1895630/VRM_Posing_Desktop/)
- [Characters Product List | Vket Store](https://store.vket.com/en/category/1)
- 애니메이션 지원: VRM Animation `.vrma`
- [`vrma` 스펙](https://github.com/vrm-c/vrm-specification/tree/master/specification/VRMC_vrm_animation-1.0)
- [3D Motion & Animation popular doujin goods available online (Booth)](https://booth.pm/en/browse/3D%20Motion%20&%20Animation)
- [Seven VRM animations (.vrma) - VRoid Project - BOOTH](https://vroid.booth.pm/items/5512385)
- [VRoid Hub introduces Photo Booth for animation playback! "VRM Animation (.vrma)" now listed on BOOTH, plus 7 free animation files!](https://vroid.com/en/news/6HozzBIV0KkcKf9dc1fZGW)
- [malaybaku/AnimationClipToVrmaSample: Sample Project to Convert AnimationClip to VRM Animation (.vrma) in Unity](https://github.com/malaybaku/AnimationClipToVrmaSample)
@@ -0,0 +1,5 @@
---
title: 디자인 가이드라인
description: Project AIRI 에 디자인으로 기여하는 방법
---
@@ -0,0 +1,21 @@
---
title: 아티스트 & 개발자
description: 참고하고 영감을 얻을 수 있는 자료들
---
### yui540
제가 아는 최고의 CSS 화면 전환 제작자 중 한 명은 **[yui540](https://yui540.com/)** 입니다. 눈부신 ACG 웹사이트를 정말 많이 디자인했고, 그중 가장 유명한 작품은 [臆病な魔女](https://cowardly-witch.netlify.app/) 입니다 (소스 코드는 [yui540](https://github.com/yui540?tab=repositories) 에서 찾아볼 수 있습니다).
위와 비슷한 전환 효과를 구현하고 싶다면 [yui540/css-animations: 俺流CSSアニメーション](https://github.com/yui540/css-animations) 저장소를 참고하세요. 직접 만져볼 수 있는 [라이브 데모](https://yui540.github.io/css-animations/2025-02-25/transitions/) 도 있습니다.
### [Nihe Works](https://nihe.work/)
[YouTube](https://www.youtube.com/@nihe8683) 에도 작업 영상을 올리고 있으니 함께 살펴보세요.
## 모음 사이트
- [Websites For Creative Backgrounds | wweb.dev](https://wweb.dev/resources/creative-backgrounds)
- [40 CSS Background Effects to Enhance Your Website](https://prismic.io/blog/css-background-effects)
- [The Classic CSS Loaders Collection](https://css-loaders.com/classic/)
- [CSS Animations - Awwwards](https://www.awwwards.com/du-haihang/collections/css-animations/)
@@ -0,0 +1,29 @@
---
title: 도구
description: Project AIRI 의 UI, UX 를 디자인하기 위한 도구들
---
## 색상
개발 과정에서는 기본적으로 [UnoCSS](https://unocss.dev) 라는 도구로 스타일시트에 관한 모든 것을 생성합니다. [Tailwind](https://tailwindcss.com) 와 똑같이 동작하는 훌륭한 도구입니다.
따라서 기본 색상 팔레트는 [Colors - Core concepts - Tailwind CSS](https://tailwindcss.com/docs/colors) 에 정리되어 있습니다.
일반적으로 기본 테마에는 `neutral`, `pink`, `violet`, `cyan` 을 사용합니다. [UnoCSS](https://unocss.dev) 와 [Tailwind](https://tailwindcss.com) 모두 색상에 투명도를 조절하는 기능을 지원하지만, [Refactoring UI](https://refactoringui.com/) 에 따르면:
> 색의 채도를 바꾸고 싶을 때는 투명도를 쓰기보다, 대비가 가장 좋은 불투명한(알파 채널이 없는) 색을 직접 골라야 합니다.
그러니 투명도가 타이포그래피 디자인의 일부가 아닌 이상, 가독성을 위해 가장 알맞은 불투명 색상을 골라 주세요.
또 하나 즐겨 쓰는 훌륭한 도구는 [Radix Colors](https://www.radix-ui.com/colors) 입니다. 탄탄한 색채 이론을 바탕으로 좋은 팔레트를 만들어 두었기에, 우리 인터랙티브 UI 요소의 기본 재료 같은 역할을 합니다. 여기서도 알맞은 색을 고를 수 있습니다.
_보색_ 이 무엇인지는 이미 알고 계실 텐데, 색상 대비를 잡는 데 유용합니다. 보색을 빠르고 인터랙티브하게 고를 수 있는 좋은 도구를 몇 가지 소개합니다:
- [Color wheel - Figma](https://www.figma.com/color-wheel/)
- [Color wheel - Canva Colors](https://www.canva.com/colors/color-wheel/)
- [Paletton](https://paletton.com/#uid=55v1e0kk-p26VFOfrvbqEdSDDbg)
## 배경 & 패턴
- [Free SVG Backgrounds and Patterns | SVG Backgrounds](https://www.svgbackgrounds.com/set/free-svg-backgrounds-and-patterns/)
- [Animated Background Headers - Generate animated background headers for any website](https://www.finisher.co/lab/header/)
+20
View File
@@ -0,0 +1,20 @@
---
title: 문서 사이트
description: Project AIRI 에 기여하기
---
### 문서 사이트
```shell
pnpm dev:docs
```
::: tip
[@antfu/ni](https://github.com/antfu-collective/ni) 사용자라면 이렇게 쓸 수 있습니다
```shell
nr dev:docs
```
:::
+222
View File
@@ -0,0 +1,222 @@
---
title: 기여하기
description: Project AIRI 에 기여하기
---
안녕하세요! 이 프로젝트에 기여하는 데 관심을 가져 주셔서 감사합니다. 이 가이드가 첫걸음을 도와드릴 거예요.
## 사전 준비물
- [Git](https://git-scm.com/downloads)
- [Node.js 23+](https://nodejs.org/en/download/)
- [corepack](https://github.com/nodejs/corepack)
- [pnpm](https://pnpm.io/installation)
<details>
<summary>Windows 설정</summary>
0. [Visual Studio](https://visualstudio.microsoft.com/downloads/) 를 내려받고 다음 안내를 따르세요: https://rust-lang.github.io/rustup/installation/windows-msvc.html#walkthrough-installing-visual-studio-2022
> Visual Studio 를 설치할 때 Windows SDK 와 C++ 빌드 도구를 반드시 함께 설치하세요.
1. PowerShell 을 엽니다
2. [`scoop`](https://scoop.sh/) 을 설치합니다
```powershell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
Invoke-RestMethod -Uri https://get.scoop.sh | Invoke-Expression
```
3. `scoop` 으로 `git`, Node.js, `rustup`, `msvc` 를 설치합니다
```powershell
scoop install git nodejs rustup
# Rust 의존성용
# crates 나 apps/tamagotchi 를 개발하지 않는다면 필요 없습니다
scoop install main/rust-msvc
# Rust & Windows 전용
rustup toolchain install stable-x86_64-pc-windows-msvc
rustup default stable-x86_64-pc-windows-msvc
```
4. `corepack` 으로 `pnpm` 을 설치합니다
```powershell
corepack enable
corepack prepare pnpm@latest --activate
```
</details>
<details>
<summary>macOS 설정</summary>
0. 터미널(또는 iTerm2, Ghostty, Kitty 등)을 엽니다
1. `brew``git``node` 를 설치합니다
```shell
brew install git node
```
2. `corepack` 으로 `pnpm` 을 설치합니다
```shell
corepack enable
corepack prepare pnpm@latest --activate
```
</details>
<details>
<summary>Linux 설정</summary>
0. 터미널을 엽니다
1. [nodesource/distributions: NodeSource Node.js Binary Distributions](https://github.com/nodesource/distributions?tab=readme-ov-file#table-of-contents) 를 따라 `node` 를 설치합니다
2. [Git](https://git-scm.com/downloads/linux) 안내를 따라 `git` 을 설치합니다
3. `corepack` 으로 `pnpm` 을 설치합니다
```shell
corepack enable
corepack prepare pnpm@latest --activate
```
4. 데스크톱 버전 개발을 돕고 싶다면 다음 의존성이 필요합니다:
```shell
sudo apt install \
libssl-dev \
libglib2.0-dev \
libgtk-3-dev \
libjavascriptcoregtk-4.1-dev \
libwebkit2gtk-4.1-dev
```
</details>
## 이전에 이미 이 프로젝트에 기여한 적이 있다면
::: warning
아직 이 저장소를 클론하지 않았다면 이 섹션은 건너뛰세요.
:::
로컬 저장소가 업스트림 저장소와 최신 상태인지 확인하세요:
```shell
git fetch --all
git checkout main
git pull upstream main --rebase
```
작업 브랜치가 있다면, 그 브랜치를 업스트림 저장소 기준으로 최신화하려면:
```shell
git checkout <your-branch-name>
git rebase main
```
## 이 프로젝트를 포크하기
[moeru-ai/airi](https://github.com/moeru-ai/airi) 페이지 오른쪽 위의 **Fork** 버튼을 클릭하세요.
## 클론하기
```shell
git clone https://github.com/<your-github-username>/airi.git
cd airi
```
## 작업 브랜치 만들기
```shell
git checkout -b <your-branch-name>
```
## 의존성 설치
```shell
corepack enable
pnpm install
# Rust 의존성용
# crates 나 apps/tamagotchi 를 개발하지 않는다면 필요 없습니다
cargo fetch
```
::: tip
스크립트를 더 간단히 쓰기 위해 [@antfu/ni](https://github.com/antfu-collective/ni) 설치를 권장합니다.
```shell
corepack enable
npm i -g @antfu/ni
```
설치하고 나면
- `pnpm install`, `npm install`, `yarn install` 대신 `ni` 를 쓸 수 있습니다.
- `pnpm run`, `npm run`, `yarn run` 대신 `nr` 을 쓸 수 있습니다.
패키지 매니저가 무엇인지 신경 쓸 필요가 없습니다. `ni` 가 알맞은 것을 골라 줍니다.
:::
## 개발하고 싶은 애플리케이션 고르기
## 커밋
### 커밋하기 전에
::: warning
lint(정적 검사기)와 TypeScript 컴파일러를 모두 통과했는지 확인해 주세요:
```shell
pnpm lint && pnpm typecheck
```
:::
::: tip
[@antfu/ni](https://github.com/antfu-collective/ni) 를 설치했다면 `nr` 로 명령을 실행할 수 있습니다:
```shell
nr lint && nr typecheck
```
:::
### 커밋
```shell
git add .
git commit -m "<your-commit-message>"
```
### 포크한 저장소로 푸시
```shell
git push origin <your-branch-name> -u
```
포크한 저장소에서 해당 브랜치를 확인할 수 있습니다.
::: tip
이 프로젝트에 처음 기여하는 것이라면 업스트림 저장소도 추가해야 합니다:
```shell
git remote add upstream https://github.com/moeru-ai/airi.git
```
:::
## Pull Request 만들기
[moeru-ai/airi](https://github.com/moeru-ai/airi) 페이지로 이동해 **Pull requests** 탭을 클릭하고, **New pull request** 버튼을 누른 뒤 **Compare across forks** 링크를 클릭해 포크한 저장소를 선택하세요.
변경 사항을 검토한 뒤 **Create pull request** 버튼을 클릭합니다.
## 우와! 해내셨네요!
축하합니다! 이 프로젝트에 첫 기여를 하셨습니다. 이제 메인테이너가 여러분의 Pull Request 를 리뷰할 때까지 기다리시면 됩니다.
@@ -0,0 +1,34 @@
---
title: Discord 봇
description: Project AIRI 에 기여하기
---
### Discord 봇 연동
```shell
cd services/discord-bot
```
`.env` 설정하기
```shell
cp .env .env.local
```
`.env.local` 에서 인증 정보를 수정하세요.
봇 실행하기
```shell
pnpm -F @proj-airi/discord-bot start
```
::: tip
[@antfu/ni](https://github.com/antfu-collective/ni) 사용자라면 이렇게 쓸 수 있습니다
```shell
nr -F @proj-airi/discord-bot dev
```
:::
@@ -0,0 +1,36 @@
---
title: Minecraft
description: Project AIRI 에 기여하기
---
### Minecraft 에이전트
```shell
cd services/minecraft
```
Minecraft 클라이언트를 실행하고 원하는 포트로 월드를 개방한 뒤, 그 포트 번호를 `.env.local` 에 입력하세요.
`.env` 설정하기
```shell
cp .env .env.local
```
`.env.local` 에서 인증 정보를 수정하세요.
봇 실행하기
```shell
pnpm -F @proj-airi/minecraft-bot start
```
::: tip
[@antfu/ni](https://github.com/antfu-collective/ni) 사용자라면 이렇게 쓸 수 있습니다
```shell
nr -F @proj-airi/minecraft-bot dev
```
:::
@@ -0,0 +1,34 @@
---
title: Satori 봇
description: Project AIRI 에 기여하기
---
### Satori 봇
```shell
cd services/satori-bot
```
`.env` 파일 설정하기:
```shell
cp .env .env.local
```
`.env.local` 에서 각종 키와 설정 정보를 수정하세요.
봇 시작하기:
```shell
pnpm -F @proj-airi/satori-bot dev
```
::: tip
[@antfu/ni](https://github.com/antfu-collective/ni) 를 쓰신다면 이렇게 할 수 있습니다:
```shell
nr -F @proj-airi/satori-bot dev
```
:::
@@ -0,0 +1,44 @@
---
title: Telegram 봇
description: Project AIRI 에 기여하기
---
### Telegram 봇 연동
Postgres 데이터베이스가 필요합니다.
```shell
cd services/telegram-bot
docker compose up -d
```
`.env` 설정하기
```shell
cp .env .env.local
```
`.env.local` 에서 인증 정보를 수정하세요.
데이터베이스 마이그레이션
```shell
pnpm -F @proj-airi/telegram-bot db:generate
pnpm -F @proj-airi/telegram-bot db:push
```
봇 실행하기
```shell
pnpm -F @proj-airi/telegram-bot start
```
::: tip
[@antfu/ni](https://github.com/antfu-collective/ni) 사용자라면 이렇게 쓸 수 있습니다
```shell
nr -F @proj-airi/telegram-bot dev
```
:::
@@ -0,0 +1,20 @@
---
title: 데스크톱
description: Project AIRI 에 기여하기
---
### Stage Tamagotchi (데스크톱 버전)
```shell
pnpm dev:tamagotchi
```
::: tip
[@antfu/ni](https://github.com/antfu-collective/ni) 사용자라면 이렇게 쓸 수 있습니다
```shell
nr dev:tamagotchi
```
:::
@@ -0,0 +1,20 @@
---
title: 웹 UI
description: Project AIRI 에 기여하기
---
### Stage Web ([airi.moeru.ai](https://airi.moeru.ai) 브라우저 버전)
```shell
pnpm dev
```
::: tip
[@antfu/ni](https://github.com/antfu-collective/ni) 사용자라면 이렇게 쓸 수 있습니다
```shell
nr dev
```
:::
@@ -0,0 +1,45 @@
---
title: 설정 가이드
description: Project AIRI 사용법
---
## 설정
시스템 트레이에서 설정을 열어 더 자세히 커스터마이즈할 수 있습니다. 예를 들어
AIRI 의 테마 색상을 바꾸거나, Live2D(2D) 또는 VRM(3D, Grok Companion 과 비슷한 형태)
같은 다른 모델로 전환할 수 있습니다.
<video autoplay loop muted>
<source src="/assets/tutorial-basic-open-settings.mp4" type="video/mp4">
</video>
설정에는 정말 많은 항목이 있으니, 이것저것 시도해 보면서 마음에 드는 조합을 찾아보세요.
### 모델 바꾸기
기본 모델을 다른 Live2D(2D)나 VRM(3D, 마찬가지로 Grok Companion 과 비슷한 3D 모델이라면
가지고 계신 것으로) 모델로 교체할 수 있습니다.
모델 설정은 [설정] -> [모델] 아래에 있습니다.
::: tip VTuber Studio 모델을 가져오시나요?
Live2D 모델을 렌더링하는 데 사용하는 라이브러리는 VTuber Studio 모델에서 만들어진 ZIP 파일을
읽는 데 어려움을 겪습니다. VTuber Studio 는 사용하지만 Live2D 엔진은 알지 못하는 파일이 섞여 있기 때문입니다.
따라서 가져오기 전에, VTuber Studio 모델을 ZIP 으로 압축하기 전에 다음 파일을 반드시 제외하세요.
- `items_pinned_to_model.json`
:::
<br />
::: warning 알려진 버그
현재 모델의 장면을 다시 불러오는 기능이 의도대로 동작하지 않습니다.
모델을 불러온 뒤에는 AIRI 를 재시작해야 합니다.
:::
<br />
<video autoplay loop muted>
<source src="/assets/tutorial-settings-change-model.mp4" type="video/mp4">
</video>
@@ -0,0 +1,39 @@
---
title: 캐릭터 카드 템플릿
description: Project AIRI 용 Character Card V3 JSON 템플릿입니다.
---
이 템플릿은 새 AIRI 캐릭터를 만들 때 쓸 수 있는 최소한의 Character Card V3 구조를 제공합니다. 아래 JSON 을 복사한 뒤 예시 값을 여러분의 캐릭터 설정으로 바꾸고, 필드 이름과 중첩 구조는 그대로 유지하세요.
::: tip 작성 요령
- `name`, `description`, `personality`, `scenario`, `first_mes` 부터 채워 보세요.
- 아직 필요하지 않은 선택 필드는 비워 두어도 됩니다.
- 가져오거나 공유하기 전에 최종 내용이 여전히 유효한 JSON 인지 확인하세요.
:::
## 템플릿
```json
{
"spec": "chara_card_v3",
"spec_version": "3.0",
"data": {
"name": "예시 캐릭터",
"nickname": "예시",
"description": "이 캐릭터가 어떤 인물인지 짧게 설명합니다.",
"personality": "호기심 많고, 따뜻하며, 장난기가 있습니다.",
"scenario": "캐릭터가 사용자를 처음 만나는 상황입니다.",
"first_mes": "안녕하세요! 만나서 반가워요.",
"alternate_greetings": [],
"group_only_greetings": [],
"mes_example": "",
"creator": "당신의 이름",
"creator_notes": "",
"character_version": "1.0.0",
"system_prompt": "",
"post_history_instructions": "",
"tags": ["example"],
"extensions": {}
}
}
```
@@ -0,0 +1,104 @@
---
title: 데스크톱 빠른 시작
description: 데스크톱에서 Project AIRI 를 시작하는 방법
---
## 대화 시작하기
AIRI 를 설치하고 실행한 뒤, 가장 빠르게 대화를 시작하는 방법은 온보딩 과정을 끝까지 마치는 것입니다.
1. AIRI 가 물어보면 사용할 언어를 선택합니다.
2. **직접 프로바이더 설정하기**를 선택하거나, 이미 AIRI 계정을 쓰고 있다면 로그인합니다.
3. OpenRouter, OpenAI 호환 API, DeepSeek, Ollama, Qwen, Gemini, Claude 등 채팅 프로바이더를 고릅니다.
4. 필요한 API 키나 로컬 엔드포인트 정보를 입력합니다.
5. 채팅 모델을 고른 뒤 저장하고 계속 진행합니다.
6. 메인 캐릭터 창에서 컨트롤 아일랜드 오른쪽 아래의 **확장** 버튼을 클릭합니다.
7. **채팅 열기**를 클릭하고 메시지를 입력해 전송합니다.
::: tip Ollama 를 로컬에서 쓰시나요?
시스템 환경 변수로 `OLLAMA_ORIGINS=*` 를 설정한 다음, Ollama 를 재시작하고 나서 AIRI 에서 선택하세요.
:::
<br />
<video controls autoplay loop muted>
<source src="/assets/tutorial-basic-setup-providers.mp4" type="video/mp4">
</video>
## 화면 구성
Stage Tamagotchi 라고도 부르는 데스크톱 버전은 보통 다음과 같은 화면 요소로 이루어집니다.
- **메인 캐릭터 창**: 항상 바탕화면 위에 떠 있는 Live2D / VRM 무대입니다.
- **컨트롤 아일랜드**: 캐릭터 창 오른쪽 아래에 있는 작은 버튼 묶음입니다.
- **채팅 창**: 컨트롤 아일랜드에서 여는 대화 창입니다.
- **설정 창**: 프로바이더, 캐릭터, 모델, 모듈, 데이터, 연결, 시스템 설정을 다룹니다.
- **시스템 트레이 메뉴**: 크기, 정렬, 설정, 자막, 위젯, 종료 동작을 제공합니다.
캐릭터 창이 숨겨졌다면 AIRI 트레이 아이콘을 클릭하거나 트레이 메뉴에서 **표시**를 선택해 다시 불러올 수 있습니다.
## 컨트롤 아일랜드
컨트롤 아일랜드는 평소에 데스크톱 앱을 조작하기에 가장 편리한 곳입니다.
- **확장**을 클릭하면 더 많은 동작이 나타납니다.
- **채팅 열기**를 클릭하면 채팅 창이 열립니다.
- **설정 열기**를 클릭하면 프로바이더, 모델, 모듈, 캐릭터, 시스템 설정을 구성할 수 있습니다.
- **프로필 전환**을 클릭하면 활성 캐릭터 카드를 바꿀 수 있습니다.
- 무대를 다시 불러와야 할 때는 **새로고침**을 클릭합니다.
- 라이트/다크 아이콘을 클릭하면 테마가 바뀝니다.
- 핀 아이콘을 클릭하면 항상 위에 표시를 켜고 끌 수 있습니다.
- 눈 아이콘을 클릭하면 **자동 숨김** / **항상 표시**를 전환할 수 있습니다.
- 마이크 버튼으로 청각 관련 설정을 엽니다.
- 이동 버튼을 드래그해 캐릭터 창의 위치를 옮깁니다.
## 자동 숨김
눈 버튼은 AIRI 가 완전히 상호작용 가능한 상태를 유지할지, 아니면 작업하는 동안 시야와 클릭 방해를 부드럽게 줄일지를 결정합니다.
- **항상 표시**는 캐릭터를 계속 보이게 하고 클릭도 가능하게 둡니다.
- **자동 숨김**은 커서가 가까이 오면 캐릭터와 UI 를 흐리게 만들고, 클릭이 아래 앱으로 통과하도록 합니다.
자동 숨김을 처음 켜면 AIRI 가 동작 방식을 설명하는 짧은 안내를 보여 줍니다. AIRI 를 클릭하기 어려워졌다면 컨트롤 아일랜드 근처로 커서를 옮긴 뒤 눈 버튼을 다시 클릭하세요.
<div rounded-lg overflow-hidden>
<video autoplay loop muted class="scale-180 translate-x--30 translate-y--2 lg:scale-150 lg:translate-x--40">
<source src="/assets/tutorial-basic-fade-on-hover.mp4" type="video/mp4">
</video>
</div>
## 이동과 크기 조절
캐릭터 창을 옮기려면 컨트롤 아일랜드 오른쪽 아래의 이동 버튼을 드래그하세요.
<div rounded-lg overflow-hidden>
<video autoplay loop muted class="scale-225 translate-x--45 translate-y--5 lg:scale-200 lg:translate-x--80 lg:translate-y--5">
<source src="/assets/tutorial-basic-move.mp4" type="video/mp4">
</video>
</div>
Windows 에서는 창의 가장자리나 모서리를 드래그해 캐릭터 창 크기를 조절할 수 있습니다. 트레이 메뉴에도 몇 가지 빠른 프리셋이 있습니다.
1. AIRI 트레이 아이콘을 오른쪽 클릭합니다.
2. **크기 조절**을 엽니다.
3. **권장**, **전체 높이**, **절반 높이**, **전체 화면** 중 하나를 고릅니다.
같은 트레이 메뉴의 **정렬 위치**를 이용하면 창을 화면 중앙이나 모서리에 배치할 수 있습니다.
<div rounded-lg overflow-hidden>
<video autoplay loop muted class="scale-160 translate-x--20 lg:scale-150 lg:translate-x--40 lg:translate-y-10">
<source src="/assets/tutorial-basic-resize.mp4" type="video/mp4">
</video>
</div>
## 확인해 볼 만한 설정
첫 대화가 잘 동작한 뒤에 살펴보면 좋은 페이지들입니다.
- **서비스 소스**: 채팅, 음성, 전사, 그림 프로바이더를 추가하거나 수정합니다.
- **바디 모듈**: 의식, 목소리, 청각, 시각, 기억, Discord, Minecraft, Factorio, MCP 등 각 모듈에 AIRI 가 어떤 프로바이더를 쓸지 고릅니다.
- **캐릭터 모델**: Live2D 와 VRM 모델을 전환하거나 직접 만든 모델을 불러옵니다.
- **AIRI 캐릭터 카드**: 활성 캐릭터를 바꾸거나 새로 만듭니다.
- **시스템**: 언어, 테마, 분석 수집 여부, 데스크톱 전용 옵션을 설정합니다.
일부 모듈은 아직 실험적이며 로컬 소스 설정이나 외부 서비스가 필요할 수 있습니다. Windows 를 중심으로 한 더 자세한 안내는 [전체 데스크톱 사용 설명서](./setup-and-use/)를 참고하세요.
@@ -0,0 +1,820 @@
---
title: Project AIRI 사용 설명서
authors:
- name: MuGewRayce
role: Lead writing team
kind: person
- name: JhIcefair
role: Contributing editor (primary)
kind: person
publishedAt: 2026-05-11
publishedAtOverride: May 11, 2026 afternoon (UTC+8)
---
대응 버전: AIRI-0.10.2
::: warning 시작하기 전에
- AIRI 의 일부 기술적 기능과 조작은 이 설명서에서 자세히 다루지 않습니다.
- 주 편집자는 중국어판만 담당합니다. 다른 언어판은 현재 AI 번역에 간단한 수동 교정을 거친 것이라 실제 표시되는 내용과 다를 수 있습니다. 실제 내용을 기준으로 봐 주세요.
- 이 설명서의 대부분은 편집장 팀원들과 다른 참여자들이 직접 탐색하고 조사한 내용입니다. 사실과 다르거나 편차가 있을 수 있으니 최종적으로는 여러분의 실제 경험을 기준으로 삼아 주세요.
- 이 설명서는 제때 갱신되지 않을 수 있습니다.
- 역량과 시간의 한계로, 현재 이 설명서는 Windows 설치 패키지 버전과 웹 버전의 일부 상세 튜토리얼만 다룹니다.
- 소프트웨어의 일부는 번역 없이 영어로 표시됩니다. 이 설명서는 그 부분을 번역해 두었지만, 최종 해석은 실제 소프트웨어를 따라야 합니다.
- AIRI 의 버전 업데이트로 일부 내용이 바뀔 수 있습니다. 이 설명서는 작성 시점의 최신 버전 기능만 소개합니다. 그 이전이나 이후 버전에 대해서는 일부 기능 설명이 남아 있을 수 있으니, 차이가 있다면 직접 판단해 주세요.
- 이 설명서에 대해 질문이 있으면 공식 Project AIRI Discord 채널에서 @jhicefair 를 멘션하고 메시지를 남겨 주세요.
- 그 밖의 질문은 공식 Project AIRI Discord 채널에 남겨 주세요.
- 즐겁게 사용하세요! AwA
:::
## 목차
- [1장 설치](#chapter-1-installation)
- [2장 초기 설정](#chapter-2-initial-configuration)
- [1절 준비](#chapter-2-prerequisites)
- [2절 Airi 를 실행하자!](#chapter-2-launch)
- [3장 Airi 인터페이스 개요](#chapter-3-interface-overview)
- [메인 창](#chapter-3-main-window)
- [시스템 트레이의 그 외 옵션](#chapter-3-system-tray)
- [설정 창](#chapter-3-settings-overview)
- [채팅 창](#chapter-3-chat-window)
- [4장 설정](#chapter-4-settings)
- [AIRI 캐릭터 카드](#chapter-4-airi-card)
- [바디 모듈](#chapter-4-modules)
- [장면](#chapter-4-stage)
- [캐릭터 모델](#chapter-4-character-model)
- [메모리 뱅크](#chapter-4-memory-bank)
- [서비스 소스](#chapter-4-providers)
- [데이터](#chapter-4-data)
- [연결](#chapter-4-connection)
- [시스템](#chapter-4-system)
- [웹 버전 기능 보충](#web-features)
- [과거 특성 & 자주 겪는 문제](#features-issues)
- [끝에 남기는 말](#chapter-ed-toeveryeditor)
<a id="chapter-1-installation"></a>
## 1장 설치
Project AIRI GitHub 홈페이지로 이동합니다: [moeru-ai/airi](https://github.com/moeru-ai/airi)
다음 순서를 따르세요:
1. 페이지 오른쪽에서 "**Releases**" 항목을 찾습니다.
2. "+ 68 releases" 를 클릭합니다.
3. 버전을 하나 고르고 그 아래 "**Assets**" 를 찾아 펼칩니다.
4. 사용하는 컴퓨터에 맞는 버전을 골라 내려받습니다.
5. 내려받은 설치 파일을 찾아 더블클릭해 설치합니다.
::: tip 다운로드 페이지 참고
- "+ 68 releases" 의 숫자는 다른 릴리스가 몇 개 있는지만 나타내므로 여러분 화면에서는 다를 수 있습니다.
- 하단의 "Show all 19 assets" 를 눌러야 할 수도 있고, 이 숫자도 다를 수 있습니다.
- 이후 내용은 Windows 설치 파일 버전을 예로 듭니다.
- 시간 제약으로 설치 과정 자체는 생략합니다. 이 정도는 직접 하실 수 있을 겁니다.
:::
<a id="chapter-2-initial-configuration"></a>
## 2장 초기 설정
<a id="chapter-2-prerequisites"></a>
### 1절 준비
시작하기 전에 LLM 서비스 제공자의 API 를 최소 하나 준비해야 합니다.
::: info 용어
* LLM
LLM 은 Large Language Model(대규모 언어 모델)의 약자입니다.
간단히 말해 AI 입니다.
* API
API 는 Application Programming Interface 의 약자입니다.
서로 다른 소프트웨어가 통신하고 데이터를 주고받고 기능을 공유할 수 있게 하는, 미리 정의된 규칙의 모음입니다.
깊이 이해할 필요는 없고, 어떻게 얻는지만 알면 됩니다.
:::
::: tip API 얻기
LLM 제공자는 아주 많고 API 를 얻는 방법도 각기 다릅니다. 시간 제약으로 여기서는 튜토리얼이나 예시를 제공하지 않습니다. 검색해 보거나 AI 에게 물어보세요.
:::
::: warning API 키를 안전하게 보관하세요
API 를 얻으면 안전하게 보관하고 다른 사람과 공유하지 마세요.
:::
<a id="chapter-2-launch"></a>
### 2절 Airi 를 실행하자!
::: info 예시
아래 단계는 Deepseek 을 예시 제공자로 사용합니다.
:::
[과거 특성: 시작 시 발생하는 버그](#h2-2-1)
다음 순서로 첫 설정을 마칩니다:
1. Airi 를 엽니다 (보통 설치 후 자동으로 열립니다).
2. 메인 창에서 언어를 선택합니다.
3. "**setup with your provider**" 를 클릭합니다. 또는 "**Login**" 을 클릭합니다 (로그인을 선택하는 과정에 대한 간단한 안내).
4. 서비스 소스를 선택하고 "**Next**" 를 클릭합니다.
5. API 키를 입력하고 "**Next**" 를 클릭합니다.
6. 다시 "**Next**" 를 클릭합니다.
7. 사용할 모델을 선택하고 "**Save and continue**" 를 클릭합니다.
축하합니다! Airi 의 초기 설정을 완료했습니다.
<a id="chapter-3-interface-overview"></a>
## 3장 – Airi 인터페이스 개요
<a id="chapter-3-main-window"></a>
### > 메인 창
[웹 버전 메인 인터페이스 소개](#chapter-3-main-web)
이 창은 가상 캐릭터를 표시합니다. 버튼이 세 개 있습니다: [과거 특성](#h3-1-1)
- "Expand" 오른쪽 아래. 클릭하면 더 많은 옵션이 나타납니다 (아래 참고).
- "Open hearing control" 오른쪽 아래. Airi 에게 말을 걸 수 있게 합니다. STT 서비스가 필요합니다.
- "Move" 오른쪽 아래. 길게 누른 뒤 끌어서 메인 창의 위치를 옮깁니다.
![Airi main window overview](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-main-window.avif)
::: info 청각 제어에 대하여
채팅 창을 먼저 열어야 하는 것 같습니다. 편집자에게는 아직 이 기능이 동작하지 않아 튜토리얼은 생략합니다.
:::
::: info 용어
* STT
STT 는 Speech-to-Text 의 약자로 자동 음성 인식(ASR)이라고도 합니다.
컴퓨터가 사람의 말을 이해해 텍스트로 변환하게 하는 것이 목표입니다.
:::
"Expand" 를 클릭하면 아홉 개 옵션이 나타납니다: **(로그인 버튼 & 작은 버튼 여덟 개)**
- "Login" 자신의 Airi 계정으로 로그인할 수 있습니다.
- "Open settings" 설정 창을 엽니다.
- "Switch character" 캐릭터 카드를 전환합니다.
- "Open chat" 채팅 창을 엽니다.
- "Refresh" 메인 창을 새로 고칩니다.
- "Switch to dark mode" 라이트/다크 테마를 전환합니다.
- "Unpin" 메인 창을 항상 위에 두지 않게 합니다.
- "Always show" / "Hide on hover" 창을 클릭이 통과하도록 합니다.
- "Close" Airi 를 닫습니다.
![Airi expanded controls menu](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-controls-island-expanded.avif)
<a id="chapter-3-system-tray"></a>
### > 시스템 트레이의 그 외 옵션
먼저 시스템 트레이에서 Airi 아이콘을 찾습니다.
::: tip Windows 작업 표시줄 팁
Windows 에서는 작업 표시줄의 "숨겨진 아이콘 표시" 를 눌러야 Airi 아이콘을 찾을 수 있습니다.
:::
Airi 아이콘을 오른쪽 클릭하면 열 개 옵션이 보입니다:
- "Show" 메인 창을 띄웁니다. 보통 필요하지 않습니다.
- "Adjust size" 메인 창 크기를 조절하고 가운데로 정렬합니다. 하위 옵션 네 개가 있습니다:
- "Recommended (450x600)" 권장 크기인 450x600 으로 설정합니다.
- "Full height" 창 높이를 바탕화면 전체 높이로 맞춥니다.
- "Half height" 창 높이를 바탕화면 절반 높이로 맞춥니다.
- "Full screen" 창이 바탕화면 전체를 채우게 합니다.
- "Align to" 메인 창을 특정 화면 위치에 정렬합니다. 하위 옵션 다섯 개가 있습니다:
- "Center" 바탕화면 가운데로 정렬합니다.
- "Top left" 왼쪽 위 모서리로 정렬합니다.
- "Top right" 오른쪽 위 모서리로 정렬합니다.
- "Bottom left" 왼쪽 아래 모서리로 정렬합니다.
- "Bottom right" 오른쪽 아래 모서리로 정렬합니다.
- "Settings" 설정 창을 엽니다.
- "About" 상세 설명 생략.
- "Open quick actions" 상세 설명 생략.
- "Open widgets" 상세 설명 생략.
- "Open caption" 자막을 엽니다. Airi 가 말할 때 텍스트를 표시하려면 TTS 서비스가 필요하며, 기본적으로 마우스를 올리면 숨겨집니다.
- "Caption overlay" 하위 옵션 두 개가 있습니다:
- "Follow window" 기본값. 자막 위치가 메인 창을 따라갑니다.
- "Reset position" 자막 위치를 초기화합니다.
- "Quit" Airi 를 닫습니다.
::: info 용어
* TTS
TTS 는 Text-to-Speech 의 약자로, 문자 텍스트를 자연스러운 음성 출력으로 변환합니다.
:::
<a id="chapter-3-settings-overview"></a>
### > 설정 창
::: info 범위
이 절은 창에 무엇이 들어 있는지만 설명합니다. 자세한 기능은 4장에서 다룹니다.
:::
다음 두 가지 방법으로 설정을 열 수 있습니다:
- 메인 창에서 "Expand" 를 클릭한 뒤 "Open settings" 를 선택합니다.
- Airi 트레이 아이콘을 오른쪽 클릭하고 "Settings" 를 선택합니다.
설정 창에는 아홉 개 섹션이 있습니다:
- "AIRI Character Card" 캐릭터 성격을 설정합니다.
- "Body Modules" 여러 기능을 설정합니다: 의식, 발화, 청각, 시각, 단기 기억, 장기 기억, Discord, X/Twitter, Minecraft, Factorio, MCP 서버, 리듬 게임.
- "Scene" Airi 의 장면(배경)을 설정합니다.
- "Character Model" 캐릭터 모델을 선택하고 설정합니다.
- "Memory Bank" 아직 공개되지 않았습니다.
- "Service Sources" LLM, TTS, STT, Artistry 서비스를 설정합니다.
- "Data" Airi 의 데이터를 관리합니다.
- "Connection" WebSocket 서버 주소를 설정합니다.
- "System" 하위 섹션 네 개가 있습니다:
- "General" 테마, 언어 등.
- "Color Scheme" 테마 색상을 변경합니다.
- "Window Shortcuts" 현재 비어 있고 뒤로 가기 버튼이 없습니다.
- "Developer" 고급 기능. 4장 참고.
::: warning "Window Shortcuts" 를 열지 마세요
이 옵션은 현재 내용도 없고 뒤로 가기 버튼도 없습니다. 한번 들어가면 설정 창을 닫고 다시 열어야 나올 수 있습니다.
:::
![Airi settings window overview](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-settings-window.avif)
<a id="chapter-3-chat-window"></a>
### > 채팅 창
메인 창에서 "Expand" 를 클릭하고 "Open chat" 을 선택하면 채팅 창을 열 수 있습니다.
![Airi chat window interface](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-chat-window.avif)
여기서 Airi 와 대화할 수 있습니다.
<a id="chapter-4-settings"></a>
## 4장 설정
다음 두 가지 방법으로 설정을 열 수 있습니다:
- 메인 창에서 "Expand" 를 클릭한 뒤 "Open settings" 를 선택합니다.
- Airi 트레이 아이콘을 오른쪽 클릭하고 "Settings" 를 선택합니다.
<a id="chapter-4-airi-card"></a>
### > AIRI 캐릭터 카드
여기서 기본 캐릭터 카드를 업로드하거나 새로 만들거나 수정할 수 있습니다.
![Airi character card settings window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-airi-card.avif)
::: info 업로드에 대하여
업로드 대화상자는 어떤 파일 형식이든 지원한다고 하지만, 편집자가 실제로 써 본 적이 없고 내보내기 기능도 없어서 상세 설명은 생략합니다.
:::
새 캐릭터 카드를 만들 때 권장하는 순서는 다음과 같습니다:
1. **Identity** 를 작성합니다. 이름, 별명, 설명, 제작자 노트가 포함됩니다.
2. 그다음 **Behavior** 를 조정합니다. 성격, 시나리오, 첫 인사가 포함됩니다.
3. 필요하면 **Modules** 에서 캐릭터별 바디 모듈을 설정합니다.
4. 필요에 따라 **Artistry** 섹션을 설정해 그 캐릭터의 이미지 생성 기능을 구성합니다.
5. 마지막으로 **Settings** 를 확인합니다. 시스템 프롬프트, 히스토리 프롬프트 지시, 버전이 포함됩니다.
6. 준비가 되면 "**Create**" 를 클릭해 캐릭터 카드를 만듭니다.
7. 만든 뒤에는 카드 오른쪽 아래의 원을 클릭하거나, 카드를 선택하고 Activate 를 클릭해 활성화합니다.
**Identity** 에서 가장 중요한 항목은 이름과 설명입니다:
- 이름은 공식 명칭입니다. 별명을 설정하면 별명이 먼저 사용됩니다.
- 설명은 상세한 성격입니다. 창의적으로 쓰거나 기본 캐릭터 카드를 참고하세요.
::: info 편집자 노트
- 기본 캐릭터 카드를 참고한다면 ACT 태그에 관한 부분은 생략해도 됩니다.
- 편집자는 제작자 노트를 써 본 적이 없어 상세 설명은 생략합니다.
- 편집자가 Behavior, Modules, Artistry, Settings 를 아직 충분히 테스트하지 못했습니다. 위에는 대략적인 용도만 적어 두었습니다.
:::
::: warning 활성화가 필요합니다
새로 만든 카드는 기본적으로 활성화되지 않습니다. 직접 활성화해야 합니다.
:::
<a id="chapter-4-modules"></a>
### > 바디 모듈
여기서 Airi 의 여러 기능을 다음과 같이 설정할 수 있습니다:
![Airi body modules settings window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-modules.avif)
#### > 의식 (Consciousness)
권장 순서:
1. 먼저 서비스 소스를 선택하거나, 새로 추가한 뒤 선택합니다.
2. 그다음 모델을 선택합니다.
::: tip 서비스 소스가 너무 많을 때
소스가 너무 많아 뒤쪽 항목을 클릭할 수 없으면, 탭 위에 마우스를 올리고 가운데 버튼을 누른 채 좌우로 끌어 보세요.
:::
![Airi consciousness settings window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-consciousness.avif)
#### > 발화 (Vocalization)
::: tip 발화 관련 참고
- 서비스 소스에 따라 설정 과정이 조금씩 다를 수 있습니다. 이 절은 알리바바 Bailian 을 예로 들었으니 실제 화면을 따라 주세요.
- 일부 서비스에서는 Pitch 조절이 동작하지 않을 수 있습니다.
- 소스가 너무 많아 뒤쪽 항목을 클릭할 수 없으면, 탭 위에 마우스를 올리고 가운데 버튼을 누른 채 좌우로 끌어 보세요.
:::
권장 순서:
1. 먼저 서비스 소스를 선택하거나, 새로 추가한 뒤 선택합니다.
2. 그다음 모델을 선택합니다.
3. 이어서 목소리를 선택합니다.
4. Airi 가 말하지 않게 하려면 "None" 을 선택합니다.
5. 기본 설정을 마친 뒤에는 이 페이지 하단에 텍스트를 입력하고 "**Test voice**" 를 클릭해 샘플을 생성할 수 있습니다.
![Airi vocalization settings window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-speech.avif)
#### > 청각 (Hearing)
::: tip 서비스 소스가 너무 많을 때
소스가 너무 많아 뒤쪽 항목을 클릭할 수 없으면, 탭 위에 마우스를 올리고 가운데 버튼을 누른 채 좌우로 끌어 보세요.
:::
권장 순서:
1. 먼저 오디오 입력 장치를 선택합니다.
2. 그다음 서비스 소스를 선택하거나, 새로 추가한 뒤 선택합니다.
3. 그다음 모델을 선택합니다.
![Airi hearing settings window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-hearing.avif)
추가로 다음을 할 수 있습니다:
- "Auto-send transcribed text" 를 켜면 전사된 텍스트가 자동으로 전송됩니다.
- 끄면 전송 전에 전사 결과를 다듬을 수 있습니다.
- "Auto-send delay" 로 전송 지연을 조정할 수 있습니다.
::: info 편집자 노트
"자동 전송을 끄면 전사 결과를 다듬을 수 있다" 는 것은 편집자의 추측입니다. 편집자는 청각 기능을 아직 제대로 써 보지 못했습니다.
:::
마이크를 테스트하려면:
1. 페이지 중간의 "**start monitoring**" 을 클릭합니다.
2. 필요하면 Sensitivity 를 조정합니다.
STT 를 테스트하려면:
1. 페이지 하단의 "**start speech-to-text**" 를 클릭합니다.
2. "Transcription Result" 에서 결과를 확인합니다.
#### > 시각 (Vision)
::: tip 서비스 소스가 너무 많을 때
소스가 너무 많아 뒤쪽 항목을 클릭할 수 없으면, 탭 위에 마우스를 올리고 가운데 버튼을 누른 채 좌우로 끌어 보세요.
:::
권장 순서:
1. 먼저 서비스 소스를 선택하거나, 새로 추가한 뒤 선택합니다.
2. 그다음 모델을 선택합니다.
3. 필요하면 "Capture interval" 로 캡처 빈도를 조절합니다.
![Airi vision settings window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-vision.avif)
::: warning Vision Capture 가 필요합니다
이 기능은 `System → Developer → vision capture` 에서 `vision capture` 를 활성화해야 합니다. 자세한 내용은 해당 절을 참고하세요.
:::
#### > 그림 (Artistry)
::: tip 서비스 소스가 너무 많을 때
소스가 너무 많아 뒤쪽 항목을 클릭할 수 없으면, 탭 위에 마우스를 올리고 가운데 버튼을 누른 채 좌우로 끌어 보세요.
:::
여기서 Airi 의 예술적 창작 능력을 설정할 수 있습니다.
참고: 이 기능은 **Neuro 의 그림 로직과는 다릅니다.** 서드파티 AI 서비스로 이미지를 생성해 아주 정교한 **AI 생성 이미지**를 얻을 수 있습니다.
시간 제약으로 이 절은 당장은 자세히 다루지 않습니다.
#### > 단기 기억 (Short-term Memory)
아직 공개되지 않았습니다.
#### > 장기 기억 (Long-term Memory)
아직 공개되지 않았습니다.
#### > Discord
여기서 Discord 봇을 설정해 Airi 가 여러분의 Discord 서버에 들어와 상호작용하게 할 수 있습니다.
권장 순서:
1. Discord 봇 토큰을 얻습니다.
2. 해당 입력란에 넣습니다.
3. 나머지 설정은 화면을 보고 마칩니다.
::: warning Discord 봇에 대하여
이 기능은 Discord 봇이 필요한데, 설치 파일 버전에는 포함되어 있지 않습니다. GitHub 페이지에서 관련 파일을 받아야 합니다. 편집자에게 이 항목의 우선순위가 낮아 전체 튜토리얼은 생략합니다.
:::
#### > X/Twitter
Discord 와 비슷하며 봇이 필요합니다. 튜토리얼 생략.
#### > Minecraft
봇이 필요합니다. 튜토리얼 생략.
#### > Factorio
봇이 필요합니다. 튜토리얼 생략.
#### > MCP 서버
편집자가 써 본 적이 없습니다. 튜토리얼 생략.
#### > 리듬 게임
편집자가 아직 탐색 중입니다. 튜토리얼 생략.
<a id="chapter-4-stage"></a>
### > 장면 (Scene)
여기서 Airi 메인 인터페이스의 장면, 간단히 말해 Airi 메인 인터페이스의 배경을 설정할 수 있습니다.
프리셋 두 개가 포함되어 있습니다. 장면을 적용하려면 프리셋 가운데의 체크 표시를 클릭하세요 (마우스를 올렸을 때만 나타납니다).
"**Upload to Gallery**" 를 클릭해 직접 만든 이미지 장면을 가져올 수도 있습니다.
장면을 지우려면 "**Clear Default**" 를 클릭하세요.
<a id="chapter-4-character-model"></a>
### > 캐릭터 모델
여기서 캐릭터 모델을 고르고 설정할 수 있습니다.
![Airi character model settings window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-models.avif)
Airi 는 Live2D 모델과 VRM 3D 모델을 지원합니다.
기존 모델로만 전환하고 싶다면:
1. "**select model**" 을 클릭해 모델 선택기를 엽니다.
2. 이 버전에는 기본으로 Live2D 두 개와 VRM 두 개가 있습니다.
3. 하나를 선택하고 "**confirm**" 을 클릭해 전환합니다.
직접 만든 모델을 가져오려면 "**add**" 를 클릭해 Live2D 또는 VRM 모델을 가져옵니다.
::: info 편집자 노트
"Switch to Godot Stage (Experimental)" 옵션에 대해서는, 설명서 편집팀이 아직 이 기능을 충분히 이해하지 못했고 실험 단계로 보이므로 관련 소개는 당장은 생략합니다.
:::
::: warning 모델을 가져오기 전에
- 구형 Live2D 모델은 지원되지 않습니다. "\*.moc3" 를 포함한 파일을 사용해야 합니다.
- Live2D 모델을 가져오기 전에 모델 폴더를 "\*.zip" 파일로 압축하세요.
:::
#### > Live2D 모델을 선택한 경우
다음 순서로 진행할 수 있습니다:
1. "Zoom & Position" 을 펼쳐 메인 창에서 모델의 크기와 위치를 조정합니다. x 는 좌우, y 는 상하입니다.
2. "parameters" 를 펼쳐 마우스 추적, Idle Animation, 프레임레이트, Auto Blink, Force Auto Blink(대체 타이머), Shadow, 기본 파라미터로 초기화, 모델 캐시 지우기, 그리고 모델별 파라미터 전체를 설정합니다.
3. 대기 애니메이션을 원한다면 모델 zip 에 애니메이션 파일이 포함되어 있는지 확인하세요.
4. 필요하면 "Expressions" 를 펼쳐 표정 시스템을 활성화합니다.
::: info 편집자 노트
편집자가 이 부분을 아직 충분히 테스트하지 못해 상세 내용이 제한적입니다.
:::
#### > VRM 3D 모델을 선택한 경우
"Scene" 을 펼친 뒤 Model Position, 카메라 각도(도), 카메라 거리(줌), 모델 방향(Y축 회전), 모델 시선 방향과 관련 값들을 설정합니다.
::: info 편집자 노트
"Change model" 을 포함한 이 절은 시간 제약으로 생략합니다.
:::
<a id="chapter-4-memory-bank"></a>
### > 메모리 뱅크
아직 공개되지 않았습니다.
<a id="chapter-4-providers"></a>
### > 서비스 소스
여기서 Chat(LLM), Speech(TTS), Transcription(STT), Artistry 서비스 소스를 설정할 수 있습니다.
항목을 선택하고, 미리 준비해 둔 서비스 소스를 고른 뒤, 해당 화면에서 필요한 정보를 채우면 설정이 완료됩니다.
또한 Pricing 이나 Deployment 같은 기준으로 모든 서비스를 필터링할 수 있습니다.
* Pricing 은 세 가지 옵션이 있습니다:
- All
- Free
- Paid
* Deployment 는 세 가지 옵션이 있습니다:
- All
- Local
- Cloud
![Airi service sources settings window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-providers.avif)
::: warning 주의해 주세요
일부 서비스의 설정 화면은 제때 유지보수되지 못해 정상 동작하지 않을 수 있습니다. 비슷한 문제를 겪으면 GitHub 에 이슈를 제출하거나, (선택한 서비스 소스가 지원한다면) "OpenAI Compatible API" 옵션으로 설정을 시도해 보세요.
:::
::: tip 기술적 조언
현재 시장에는 AI 모델이 아주 많습니다. AIRI 가 그 전부를 개별 지원하거나 실시간 유지보수를 보장할 수는 없으므로, **OpenAI 호환 API** 옵션을 고려해 보시길 권합니다. 사용하는 모델이 OpenAI 호환 API 를 지원한다면 여기서 설정할 수 있습니다.
:::
<a id="chapter-4-data"></a>
### > 데이터
여기서 Airi 의 여러 데이터를 관리할 수 있습니다.
![Airi data settings window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-data-settings.avif)
::: warning 되돌릴 수 없는 동작
이 섹션에서는 데이터를 삭제하고 초기화할 수 있으며, 되돌릴 수 없습니다. 신중히 조작하시고, 삭제나 초기화를 실행하기 전에 다시 확인하세요.
:::
::: tip 알려진 문제
"Open app data folder" 에는 현재 폴더가 여러 번 열리는 버그가 있습니다.
:::
이 페이지는 여러 상자로 구성되어 있습니다:
1. 첫 번째 상자에는 "Open app data folder" 가 있습니다. "**Open folder**" 를 클릭해 엽니다.
2. 두 번째 상자에서는 대화 기록을 가져오거나 내보내거나, 모든 대화 세션을 삭제할 수 있습니다.
3. 세 번째 상자에서는 가져온 모든 모델을 삭제하거나 모듈 설정과 자격 증명을 초기화할 수 있습니다.
4. 네 번째 상자에서는 데스크톱 설정과 상태를 초기화할 수 있습니다.
5. 다섯 번째 상자에서는 모든 프로바이더 설정과 자격 증명을 초기화하거나, 모든 로컬 설정·프로바이더 구성·모델을 지울 수 있습니다.
::: tip 웹 버전 관련 설명
위의 1번과 4번 항목은 웹 페이지에는 없습니다.
:::
<a id="chapter-4-connection"></a>
### > 연결
여기서 WebSocket 서버 주소를 설정할 수 있습니다.
![Airi connection settings window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-websocket-settings.avif)
::: info 편집자 노트
상세 설명 생략.
:::
<a id="chapter-4-system"></a>
### > 시스템
#### > General
여기서 프로그램 테마, 언어 등을 설정할 수 있습니다.
![Airi general system settings window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-system-general.avif)
- 테마는 기본이 라이트입니다. 버튼을 클릭하면 다크 모드로 전환됩니다.
- Language 는 인터페이스 언어를 설정합니다.
- Control island icon size 는 메인 창 오른쪽 아래 세 버튼의 크기를 바꿉니다.
- 마지막으로 사용 데이터와 크래시 리포트 수집을 허용할지 선택하거나 개인정보 처리방침을 읽을 수 있습니다 ("Privacy Policy" 클릭).
#### > Color Scheme
여기서 테마 색상을 바꿀 수 있습니다.
![Airi color scheme settings window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-system-color-scheme.avif)
- RGB 옵션을 켜면 테마 색상이 RGB 스트립처럼 순환합니다.
- 검은 선을 끌거나 색상 바를 클릭해 테마 색상을 바꿉니다.
- 그 아래에는 색상 미리보기가 있습니다.
- 아래에서 프리셋을 골라 테마 색상을 바꿀 수도 있습니다.
::: tip 색상 프리셋
네모난 상자가 아니라 원 중 하나를 클릭하세요.
:::
#### > Window Shortcuts
::: warning 열지 마세요
이 옵션은 내용도 없고 뒤로 가기 버튼도 없습니다. 한번 들어가면 설정 창을 닫고 다시 열어야 합니다. 클릭하지 마세요.
:::
#### > Developer
여기서 몇 가지 고급 기능을 쓸 수 있습니다.
![Airi developer settings window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-system-developer.avif)
::: info 고급 기능
이 내용 대부분은 영어이며 거의 필요하지 않은 고급 기능이므로, 이 절은 참고용입니다.
:::
첫 번째 상자와 관련 옵션들:
- 첫 번째 상자에서 "**Open**" 을 클릭하면 개발자 도구 창이 열립니다 (브라우저의 F12 같은 것).
- 두 번째 "Markdown stress test" 상세 설명 생략.
- 세 번째 "IO Tracer" 기능 소개는 당장은 생략.
- 네 번째 "Lag visualization" 상세 설명 생략.
- 다섯 번째 "Enable stage transition animation" 상세 설명 생략.
- 여섯 번째 "Use page-specific cutscenes" 상세 설명 생략.
##### > useMagicKeys tool
::: info 편집자 노트
현재 페이지가 비어 있어 상세 설명 생략.
:::
##### > useElectronWindowMouse
여기서 화면상의 마우스 커서 위치를 감지할 수 있습니다.
![Airi useElectronWindowMouse tool window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-devtools-use-window-mouse.avif)
##### > Displays
여기서 화면상의 마우스 커서 위치를 시각화할 수 있습니다.
![Airi Displays tool window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-devtools-displays.avif)
##### > widgets calling
![Airi widgets calling tool window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-devtools-widgets-calling.avif)
##### > Context Flow
들어오는 컨텍스트 갱신(서버 + 브로드캐스트)과 나가는 채팅 훅을 실시간으로 살펴봅니다. 플러그인 컨텍스트(예: VSCode 코딩 컨텍스트)가 채팅 파이프라인으로 흘러 들어가고 서버 이벤트로 나가는 과정을 확인하는 데 사용하세요.
![Airi Context Flow tool window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-devtools-context-flow.avif)
##### > relative mouse
여기서 이 창 안에서의 마우스 커서 위치를 시각화할 수 있습니다.
![Airi relative mouse tool window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-devtools-relative-mouse.avif)
##### > beat sync visualizer
![Airi beat sync visualizer tool window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-devtools-beat-sync.avif)
##### > WebSocket Inspector
![Airi WebSocket Inspector tool window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-devtools-websocket-inspector.avif)
##### > Plugin Host Debug
![Airi Plugin Host Debug tool window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-devtools-plugin-host.avif)
##### > Updater
상세 소개는 당장은 생략.
##### > Screen Capture
시스템 수준의 화면 캡처 권한을 아직 부여하지 않았다면, 먼저 아래 스크린샷처럼 권한 요청이 나타납니다. 권한을 부여하면 임의의 애플리케이션 창이나 전체 화면을 캡처할 수 있습니다.
상단에 네 개 옵션이 있습니다:
- "applications" 열려 있는 임의의 애플리케이션 창을 선택하고 "**share window**" 를 클릭하면 상단에서 볼 수 있습니다. 캡처 위로 마우스를 옮기고 "stop" 을 클릭하면 중지합니다.
- "displays" 전체 화면을 캡처합니다. "**share screen**" 을 클릭하면 볼 수 있고, 캡처 위로 마우스를 옮기고 "stop" 을 클릭하면 중지합니다.
- "devices" 상세 설명 생략.
- "refetch" 상세 설명 생략.
![Airi Screen Capture tool window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-devtools-screen-capture.avif)
##### > vision capture
화면 캡처 권한을 아직 부여하지 않았다면 이 페이지도 먼저 권한 요청을 보여 줍니다. 권한을 부여하면 페이지가 프레임 캡처를 시작하고 시각 처리 결과를 보여 줄 수 있습니다.
![Airi vision capture tool window](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-devtools-vision-capture.avif)
<a id="web-features"></a>
## > 웹 버전 기능 보충
<a id="chapter-3-main-web"></a>
### > 웹 버전 메인 인터페이스
![Airi Web Interface](/en/docs/manual/tamagotchi/setup-and-use/assets/manual-main-web.avif)
여기서 캐릭터 모델을 보고 직접 대화할 수 있습니다.
크게 세 부분으로 나뉩니다:
- 캐릭터 모델 공간
- 채팅 박스
- 그 외
아래에서는 채팅 박스와 그 외 부분을 중심으로 살펴봅니다.
#### > 채팅 박스
채팅 박스는 두 부분으로 나뉩니다:
- 위쪽은 대화 기록을 표시하고 기록하는 영역입니다
- 아래쪽은 입력란으로, 여기에 입력해 캐릭터와 대화합니다
아래쪽 아래에는 버튼 세 개가 있습니다: (문구는 참고용)
- Conversations (대화 관리. 대화는 서로 독립적입니다)
- Send method (메시지 전송을 확정하는 방식 선택)
- 음성 입력 활성화
#### > 그 외 부분
##### > 상단 영역
세 개 옵션이 있습니다:
- About
- Character Card
- Account & Settings
세 번째 옵션에는 주요 섹션 세 개가 있습니다:
- 계정 정보
- Profile, Flux, Settings
- 로그아웃
###### > Profile
Airi 에 로그인한 상태라면 여기서 계정 정보를 관리할 수 있습니다.
상세 설명 생략.
###### > Flux
관련 설명은 당장은 생략.
###### > Settings
데스크톱 버전 설정과 같습니다. 자세한 내용은 [4장](#chapter-4-settings)을 참고하세요.
##### > 하단 영역
네 개 옵션이 있습니다: (문구는 참고용)
- Position & Size
- Delete Chat History
- Toggle Light/Dark
- Background
###### > Position & Size
클릭하면 옵션 왼쪽에 x, y, scale 세 옵션이 새로 나타나고, 웹 인터페이스 왼쪽에 수직 바가 생깁니다. 여기서 x 는 모델의 x축 위치, y 는 모델의 y축 위치, scale 은 모델의 줌(크기)입니다. 웹 인터페이스 왼쪽의 수직 바를 **클릭한 채 끌어서** 이 세 값을 조정할 수 있습니다.
![Adjust position and size on the main interface](/en/docs/manual/tamagotchi/setup-and-use/assets/web-position-size.avif)
###### > Delete Chat History
클릭하면 모든 대화 기록을 한 번에 지웁니다.
::: warning 신중히 진행하세요
삭제한 대화는 복구할 수 없으니 조심해서 조작해 주세요!
:::
###### > Toggle Light/Dark
인터페이스를 "Light" 또는 "Dark" 테마로 전환합니다.
###### > Background
메인 인터페이스의 배경을 바꿉니다.
<a id="features-issues"></a>
## > 과거 특성 & 자주 겪는 문제
### > 자주 겪는 문제
- 구버전에서 0.10.2 로 업그레이드할 때, 이전에 모델의 크기와 위치를 바꿔 두었다면 모델이 '사라진' 것처럼 보일 수 있습니다. 이 문제를 겪더라도 걱정하지 마세요. 모델 설정 화면에서 모델의 scale 과 위치를 초기화하면 해결됩니다.
<a id="h2-2-1"></a>
### > 특성 H2-2-1
애플리케이션을 처음 시작할 때 이런 버그를 만날 수 있습니다. 메인 인터페이스 테두리가 깜빡이고, 클릭해서 팝업 메뉴를 펼치면 곧바로 다시 접힙니다…
이 버그를 만나더라도 걱정하지 마세요. 아래 순서로 해결할 수 있습니다 (다만 클릭이 빨라야 합니다):
먼저 깜빡이는 테두리가 어두워지는 바로 그 순간에 클릭해서 펼칩니다.
그다음 두 번째 줄의 첫 번째 옵션인 'Refresh' 를 빠르게 찾아 클릭합니다. 이렇게 하면 문제가 해결됩니다.
* 이 특성은 수정되었습니다!
<a id="h3-1-1"></a>
### > 특성 H3-1-1
과거 일부 버전에서는 메인 창 오른쪽 위 모서리에도 옵션이 하나 보였습니다:
- "websocket status" 오른쪽 위. 클릭하면 연결 설정이 열려 WebSocket 서버 주소를 설정할 수 있었습니다.
<a id="chapter-ed-toeveryeditor"></a>
## > 끝에 남기는 말
이 설명서는 주로 비공식 인원이 작성해 공식 사이트에 제출한 것입니다. 내용 유지보수는 보통 Mujiu Yunxuan Studio 구성원이 담당하지만, 이 문서를 편집하고 싶거나 이미 편집한 모든 분들이 문서 앞부분 저자 항목에 이름을 남겨 주시기를 진심으로 바랍니다. 내용을 바꾸든 서식을 다듬든, 이 설명서를 함께 풍성하게 하고 다듬는 데 누구든 참여해 주시길 환영합니다. Airi 프로젝트와 이 설명서에 여러분의 힘을 보태 주세요!
또한 비공식 사용자이면서 이 설명서를 편집할 아이디어가 있다면, 부담 갖지 마시고 그냥 수정해서 Pull Request 를 보내 주세요. 다만 이름을 남기는 것을 잊지 마시길 다시 한번 당부드립니다!
여러분의 성원과 협조에 감사드립니다!
감사의 마음으로,
JhIceFair
+4
View File
@@ -0,0 +1,4 @@
---
title: 웹 버전 가이드
description: Project AIRI 웹 버전 사용법
---
@@ -0,0 +1,7 @@
---
title: AI VTuber 란
---
LLM 이 생성한 문서이지만, 일본어 TTS 모듈과 서비스에 관한 유용한 자료가 담겨 있습니다:
[【AI VTuber作り方ガイド】初心者でも簡単に自動生成する方法 – AI Front Trend](https://ai-front-trend.jp/how-to-make-ai-vtuber/)
@@ -0,0 +1,24 @@
---
title: Neuro-sama
---
관련 화제와 생각들:
- [r/VirtualYoutubers --- Someone help me understand Neuro-sama : r/VirtualYoutubers](https://www.reddit.com/r/VirtualYoutubers/comments/1gi5ra0/someone_help_me_understand_neurosama/)
- [How to Make an AI Vtuber. - YouTube](https://www.youtube.com/watch?v=WZ9JqlxQ6iQ)
- [How neuro plays Minecraft? : r/NeuroSama](https://www.reddit.com/r/NeuroSama/comments/1hi8seg/how_neuro_plays_minecraft/)
- [How to make an AI VTuber Using GPT 3 and Google Cloud TTS - YouTube](https://www.youtube.com/watch?v=EXICATDyYWI)
- [Having a personal neuro-sama? : r/NeuroSama](https://www.reddit.com/r/NeuroSama/comments/1ix5uip/having_a_personal_neurosama/)
- [Is it really Neuro-sama playing Minecraft? : r/NeuroSama](https://www.reddit.com/r/NeuroSama/comments/1ifhv0f/is_it_really_neurosama_playing_minecraft/)
- [Is neuro custom coded from the ground up or does she have a base model? : r/NeuroSama](https://www.reddit.com/r/NeuroSama/comments/19481ow/is_neuro_custom_coded_from_the_ground_up_or_does/)
AI/LLM 이 모델을 제어할 수 있게 해 주는 플러그인:
- [pladisdev/VTS-AI-Plugin](https://github.com/pladisdev/VTS-AI-Plugin)
- [Plugins · DenchiSoft/VTubeStudio Wiki](https://github.com/DenchiSoft/VTubeStudio/wiki/Plugins)
기존 오픈소스 프로젝트들:
- [JarodMica/open-neruosama](https://github.com/JarodMica/open-neruosama/tree/master)
- [AIVTDevPKevin/AI-VTuber-System: A graphical system program that allows you to quickly create your own AI VTuber for free.](https://github.com/AIVTDevPKevin/AI-VTuber-System)
+94
View File
@@ -0,0 +1,94 @@
---
title: 소개
description: Project AIRI 의 UI 를 알아보세요
---
### 한 줄 요약
저희를 이렇게 생각해 주세요.
- [Neuro-sama](https://www.youtube.com/@Neurosama) 의 오픈소스 재현
- [Grok Companion](https://news.ycombinator.com/item?id=44566355) 의 오픈소스 대안
- Live2D, VRM(3D), 그리고 게임 플레이와 애플리케이션 인식에 특화된 롤플레잉을 지원하는
[SillyTavern](https://github.com/SillyTavern/SillyTavern) 대안
함께 놀고 대화할 수 있는 사이버 생명체(사이버 와이푸), 혹은 디지털 동반자를 가지는 꿈을 꿔본 적 있으신가요?
현대적인 대규모 언어 모델의 힘 덕분에
[Character.ai (일명 c.ai)](https://character.ai) 나
[JanitorAI](https://janitorai.com/) 같은 플랫폼, 또는
[SillyTavern](https://github.com/SillyTavern/SillyTavern) 같은 애플리케이션은
채팅 기반이나 비주얼 노벨 같은 경험을 제공하기에 이미 충분한 해법입니다.
> 하지만 게임을 함께 플레이하는 능력은요? 그리고 여러분이 무엇을 코딩하고 있는지 보는 능력은요?
> 게임을 하고 영상을 보면서 대화하고, 그 밖에도 수많은 일을 해낼 수 있는 존재 말이에요.
아마 [Neuro-sama](https://www.youtube.com/@Neurosama) 는 이미 알고 계실 겁니다. 그녀는 현재
게임을 하고, 대화하고, 여러분과 (VTuber 커뮤니티의) 참여자들과 상호작용할 수 있는 최고의 동반자이며,
어떤 사람들은 이런 존재를 "디지털 휴먼"이라고 부르기도 합니다.
**아쉽게도 오픈소스가 아니어서, 그녀가 라이브 스트림을 마치고 오프라인이 되면 더 이상 상호작용할 수 없습니다.**
그래서 이 프로젝트 AIRI 는 또 다른 가능성을 제시합니다.
**여러분만의 디지털 생명, 사이버 라이프를 언제 어디서나 손쉽게 소유하세요.**
## 시작하기
저희는 웹과 데스크톱을 모두 지원합니다.
<div flex gap-2 w-full justify-center text-xl>
<div w-full flex flex-col items-center gap-2 border="2 solid gray-500/10" rounded-lg px-2 pt-6 pb-4>
<div flex items-center gap-2 text-5xl>
<div i-lucide:app-window />
</div>
<span>웹</span>
<a href="https://airi.moeru.ai/" target="_blank" decoration-none class="text-primary-900 dark:text-primary-400 text-base not-prose bg-primary-400/10 dark:bg-primary-600/10 block px-4 py-2 rounded-lg active:scale-95 transition-all duration-200 ease-in-out">
열기
</a>
</div>
<div w-full flex flex-col items-center gap-2 border="2 solid gray-500/10" rounded-lg px-2 pt-6 pb-4>
<div flex items-center gap-2 text-5xl>
<div i-lucide:laptop />
/
<div i-lucide:computer />
</div>
<span>데스크톱</span>
<a href="https://github.com/moeru-ai/airi/releases/latest" target="_blank" decoration-none class="text-primary-900 dark:text-primary-400 text-base not-prose bg-primary-400/10 dark:bg-primary-600/10 block px-4 py-2 rounded-lg active:scale-95 transition-all duration-200 ease-in-out">
다운로드
</a>
</div>
</div>
웹 버전은 모바일 기기를 포함해 어디서나 손쉽게 접근할 수 있습니다.
데스크톱은 VTuber 스트리밍, 컴퓨터 조작, 그리고 AIRI 를 돌리기 위해 막대한 양의 토큰 비용을
지불할 필요가 없는 로컬 LLM 접근 등 더 고급 용도에 적합합니다.
<div flex gap-2 w-full flex-col justify-center text-base>
<a href="../manual/tamagotchi/" w-full flex items-center gap-2 border="2 solid gray-500/10" rounded-lg px-4 py-2>
<div w-full flex items-center gap-2>
<div flex items-center gap-2 text-2xl>
<div i-lucide:laptop />
</div>
<span>데스크톱</span>
</div>
<div decoration-none class="text-gray-900 dark:text-gray-200 text-base not-prose rounded-lg active:scale-95 transition-all duration-200 ease-in-out text-nowrap">
사용법 보기
</div>
</a>
<a href="../manual/web/" w-full flex items-center gap-2 border="2 solid gray-500/10" rounded-lg px-4 py-2>
<div w-full flex items-center gap-2>
<div flex items-center gap-2 text-2xl>
<div i-lucide:app-window />
</div>
<span>웹</span>
</div>
<div class="text-gray-900 dark:text-gray-200 text-base not-prose rounded-lg active:scale-95 transition-all duration-200 ease-in-out text-nowrap">
사용법 보기
</div>
</a>
</div>
## 기여하기
이 프로젝트에 기여하는 방법을 이해하는 데 도움이 되는 가이드는 [기여하기](../contributing/) 페이지를 참고해 주세요.
Project AIRI 의 UI 를 디자인하고 개선하는 데 도움이 되는 자료는 [디자인 가이드라인](../contributing/design-guidelines/resources) 페이지를 참고해 주세요.
@@ -0,0 +1,5 @@
---
title: 비슷한 다른 프로젝트들
description: Project AIRI 와 비슷한 다른 프로젝트들을 알아보세요
---
+33
View File
@@ -0,0 +1,33 @@
---
title: 버전
description: AIRI 의 여러 버전과 받는 방법
---
<script setup>
import ReleaseDownloads from '../../../../.vitepress/components/ReleaseDownloads.vue'
import ReleasesList from '../../../../.vitepress/components/ReleasesList.vue'
</script>
## 릴리스 다운로드
<ReleaseDownloads />
### 최근 릴리스
<ReleasesList type="releases" :limit="5" />
[GitHub 에서 모든 릴리스 보기 →](https://github.com/moeru-ai/airi/releases)
## 나이틀리 빌드 다운로드
::: warning 실험적
나이틀리 빌드에는 버그나 불안정한 기능이 포함될 수 있습니다. 정식 릴리스 빌드를 백업으로 남겨 두세요.
:::
나이틀리 빌드는 최신 `main` 브랜치에서 생성됩니다. 내려받으려면 아래 링크에서 가장 최근에 성공한 실행을 선택하고 **Artifacts** 섹션을 확인하세요.
### 최근 나이틀리 빌드
<ReleasesList type="nightly-builds" :limit="5" />
[나이틀리 빌드 내려받기 →](https://github.com/moeru-ai/airi/actions/workflows/release-tamagotchi.yml)
+5
View File
@@ -0,0 +1,5 @@
---
layout: home
title: 'Project AIRI'
slogan: 'AI 와이푸와 버추얼 캐릭터의 영혼을 담아 우리 세계로 데려오기 위한 그릇.'
---
@@ -0,0 +1,5 @@
PrismarineJS/mineflayer: 강력하고 안정적인 고수준 JavaScript API 로 Minecraft 봇을 만듭니다.
https://github.com/PrismarineJS/mineflayer
mindcraft/src/agent/agent.js at main · kolbytn/mindcraft
https://github.com/kolbytn/mindcraft/blob/main/src/agent/agent.js
@@ -0,0 +1,28 @@
## 인덱스
- [harlanhong/awesome-talking-head-generation](https://github.com/harlanhong/awesome-talking-head-generation?tab=readme-ov-file)
## 논문 & 프로젝트
- [taherfattahi/nvidia-human-ai-lipsync](https://github.com/taherfattahi/nvidia-human-ai-lipsync)
- [met4citizen/TalkingHead](https://github.com/met4citizen/TalkingHead)
- [zak-45/WLEDLipSync](https://github.com/zak-45/WLEDLipSync)
- [hecomi/uLipSync](https://github.com/hecomi/uLipSync)
- [DanielSWolf/rhubarb-lip-sync](https://github.com/DanielSWolf/rhubarb-lip-sync)
- [AnimaVR/NeuroSync_Player](https://github.com/AnimaVR/NeuroSync_Player)
- [saifhassan/Wav2Lip-HD](https://github.com/saifhassan/Wav2Lip-HD)
- [instant-high/wav2lip-onnx-HQ](https://github.com/instant-high/wav2lip-onnx-HQ)
- [DanielSWolf/rhubarb-lip-sync](https://github.com/DanielSWolf/rhubarb-lip-sync)
- [RealTalk: Real-time and Realistic Audio-driven Face Generation with 3D Facial Prior-guided Identity Alignment Network](https://huggingface.co/papers/2406.18284)
- [loopyavatar.github.io](https://loopyavatar.github.io/)
- [Rudrabha/Wav2Lip](https://github.com/Rudrabha/Wav2Lip)
- [audio2face-3d Model by NVIDIA | NVIDIA NIM](https://build.nvidia.com/nvidia/audio2face-3d)
- [2306.10799 SelfTalk: A Self-Supervised Commutative Training Diagram to Comprehend 3D Talking Faces](https://ar5iv.labs.arxiv.org/html/2306.10799?_immersive_translate_auto_translate=1)
- [anothermartz/Easy-Wav2Lip: Colab for making Wav2Lip high quality and easy to use](https://github.com/anothermartz/Easy-Wav2Lip)
- [OpenTalker/SadTalker](https://github.com/OpenTalker/SadTalker)
- [TMElyralab/MuseTalk](https://github.com/TMElyralab/MuseTalk)
## 관련 항목
- [JingLi513/Audio2Gestures](https://github.com/JingLi513/Audio2Gestures)
- [Digital Humans | Reply](https://www.reply.com/en/metaverse/digital-humans)
@@ -0,0 +1,11 @@
- [freemocap/freemocap: Free Motion Capture for Everyone 💀✨](https://github.com/freemocap/freemocap)
https://quaternius.com/packs/universalanimationlibrary.html
[Mocap Animations for 3D Characters | ActorCore](https://actorcore.reallusion.com/3d-motion?orderBy=Relevance&keyword=talk)
[animation-library/feminine/fbx/expression at master · readyplayerme/animation-library](https://github.com/readyplayerme/animation-library/tree/master/feminine/fbx/expression)
[Office Meeting Animations: 3D Character Animation Pack - 3ds Max MoCap Online](https://mocaponline.com/products/meeting?variant=31814402375751)
[Download 263 Rokoko motion capture assets](https://www.rokoko.com/resources/download-263-rokoko-motion-capture-assets#modal?popup=popup-sign-up)
@@ -0,0 +1,24 @@
## 인덱스
- [derikon/awesome-human-motion](https://github.com/derikon/awesome-human-motion)
## 논문 & 프로젝트
Developer-Zer0/MoDDM-Text-to-Motion-Synthesis-Using-Discrete-Diffusion: "MoDDM: Text-to-Motion Synthesis using Discrete Diffusion Model (BMVC2023)" 공식 구현
https://github.com/Developer-Zer0/MoDDM-Text-to-Motion-Synthesis-Using-Discrete-Diffusion
EMOTION: 인컨텍스트 학습으로 휴머노이드 로봇의 표현력 있는 동작 시퀀스를 생성하기 --- EMOTION: Expressive Motion Sequence Generation for Humanoid Robots with In-Context Learning
https://arxiv.org/html/2410.23234?_immersive_translate_auto_translate=1
Harmon: 언어 설명으로부터 휴머노이드 로봇의 전신 동작을 생성하기 --- Harmon: Whole-Body Motion Generation of Humanoid Robots from Language Descriptions
https://arxiv.org/html/2410.12773?_immersive_translate_auto_translate=1
사람 수준의 지시로부터 사람-객체 상호작용 생성하기 --- Human-Object Interaction from Human-Level Instructions
https://arxiv.org/html/2406.17840?_immersive_translate_auto_translate=1
## 상업 솔루션
- [Synthesia Pricing | From $18 per Month](https://www.synthesia.io/pricing-options)
- [Plask Motion: AI-powered Mocap Animation Tool](https://plask.ai/en-US)
- [Pricing Saymotion](https://www.deepmotion.com/sign-up?product=SmWeb&plan=Freemium)
- [Digital Humans | Reply](https://www.reply.com/en/metaverse/digital-humans)
@@ -0,0 +1,3 @@
- [snakers4/silero-models](https://github.com/snakers4/silero-models#text-to-speech)
- [voicevox-client/python](https://github.com/voicevox-client/python)
- [VOICEVOX/voicevox_engine: 無料で使える中品質なテキスト読み上げソフトウェア、VOICEVOXの音声合成エンジン](https://github.com/VOICEVOX/voicevox_engine/tree/master)
+2
View File
@@ -1,4 +1,6 @@
# i18n
/* /zh-Hans/:splat 301 Language=zh-Hans
/* /zh-Hans/:splat 301 Language=zh-CN
/* /ko/:splat 301 Language=ko
/* /ko/:splat 301 Language=ko-KR
/* /en/:splat 301