본문 바로가기

개발/AI

[SillyTavern] 실리태번의 월드 인포(로어북) 기능 알아보기

728x90

 

요즘 세상에 AI를 안 쓰는 사람은 거의 없을 것이라고 생각한다.

하지만 AI챗? 그뭔씹 마이너 장르는 맞다. 그래도 꽤나 매력을 느껴서 이것저것 알아보던 와중에 실리태번(SillyTavern)이라는 프론트엔드를 알게 되었다.

쓰다 보면 알다시피 본인들도 'for Power User'라고 설명할 만큼 학습 곡선이 크지만 다양한 부가 기능을 제공한다.

개인적으로 타 AI챗 서비스에는 거의 없는 로어북 개념(특히 키워드 트리거)과 시스템 프롬프트를 쓰다보면 이게 어떻게 돌아가서 프롬프트를 넘길까? 라는 생각이 들게 되는데 오픈소스니까 한번 코드를 열어봤다.

 

 

‼️ 이 글에서는 실리태번 설치나 사용법을 작성하지는 않을 예정입니다.

설치, 사용법을 원한다면 다른 글을 찾는 것을 추천합니다🙏🏻

 

 


 

 

월드 인포(World Info)/로어북(Lorebook)이란?

SillyTavern에서 월드 인포, 로어북은 AI가 대화 맥락을 더 잘 이해하도록 돕는 설정 데이터라고 볼 수 있다.
사용자는 스토리와 세계관에 대한 정보를 AI에게 제공하고 AI는 이 정보를 바탕으로 보다 구체적이고 맥락 있는 응답을 생성할 수 있게 된다.

 

특징은 대략 다음과 같이 정리할 수 있다.

 

  • 키워드 트리거: 사용자의 입력에 특정 키워드가 포함되었을 때만 관련 정보가 추가된다.
  • 전략적 데이터 삽입: 설정된 전략(strategy)에 따라 월드 인포 항목이 대화 프롬프트에 자동으로 포함된다.

이 외에도 사용자가 원하는 방식으로 설정과 조건을 자유롭게 지정할 수 있다.

 

 

공식문서 참고)

 

https://docs.sillytavern.app/usage/core-concepts/worldinfo/#world-info

 

World Info | docs.ST.app

World Info (also known as Lorebooks or Memory Books) is a powerful tool available in ST to insert prompts dynamically into your chat to help guide...

docs.sillytavern.app

 

 

 

Strategy

Strategy(전략)을 설정해 해당 정보를 항상 포함하여 프롬프트를 전송할지, 키워드가 포함되어 있을 때만 발동할지를 결정한다.

실리태번에서 제공하는 월드 인포 항목의 삽입 전략은 다음과 같다.

🔵 Always Present 항목이 항상 프롬프트에 포함된다.
🟢 Keyword Trigger 지정한 키워드가 사용자 입력에 포함되었을 때만 프롬프트에 추가된다.
🔗 Embedding Similarity 문맥적으로 입력과 유사한 항목이 프롬프트에 추가된다.

 

 

 

키워드 트리거의 동작

내가 궁금했던 부분은 바로 이 키워드 트리거 동작이다. 월드 인포 항목에 키워드를 설정하면, 사용자의 입력에 해당 키워드가 포함된 경우에만 해당 항목이 AI 프롬프트에 추가된다.

 

 

 

월드 인포의 키워드 트리거는 다음 단계로 처리되는 것으로 보인다.

  1. 키워드 정의: 월드 인포 항목에 기본 키워드와 선택적 필터를 설정
  2. 사용자 입력 분석: 사용자가 입력한 텍스트에서 설정된 키워드와 일치하는 항목을 검색
  3. 조건 만족 여부 확인: 기본 키워드 및 선택적 필터의 조건(로직 부분)을 만족하는 항목만 활성화
  4. 프롬프트에 추가: 조건을 만족한 항목만 AI 프롬프트에 포함

선택적 필터의 공식 문서의 설명은 다음과 같다.

 

 

기본 키워드 검사

const textToScan = buffer.get(entry, scanState);

let primaryKeyMatch = entry.key.find(key => {
    const substituted = substituteParams(key);
    return substituted && buffer.matchKeys(textToScan, substituted.trim(), entry);
});

if (!primaryKeyMatch) {
    continue;
}

 

  • entry.key: 기본 키워드 배열을 의미한다.
  • buffer.matchKeys:
    • 사용자의 입력(textToScan)에서 키워드가 포함되어 있는지 확인한다.
    • 정규식 지원 및 단어 경계 검사를 포함한다.

 

matchKeys 함수 작동 방식

/**
 * Matches the given string against the buffer.
 * @param {string} haystack The string to search in
 * @param {string} needle The string to search for
 * @param {WIScanEntry} entry The entry that triggered the scan
 * @returns {boolean} True if the string was found in the buffer
 */
matchKeys(haystack, needle, entry) {
    // If the needle is a regex, we do regex pattern matching and override all the other options
    const keyRegex = parseRegexFromString(needle);
    if (keyRegex) {
        return keyRegex.test(haystack);
    }

    // Otherwise we do normal matching of plaintext with the chosen entry settings
    haystack = this.#transformString(haystack, entry);
    const transformedString = this.#transformString(needle, entry);
    const matchWholeWords = entry.matchWholeWords ?? world_info_match_whole_words;

    if (matchWholeWords) {
        const keyWords = transformedString.split(/\s+/);

        if (keyWords.length > 1) {
            return haystack.includes(transformedString);
        }
        else {
            // Use custom boundaries to include punctuation and other non-alphanumeric characters
            const regex = new RegExp(`(?:^|\\W)(${escapeRegex(transformedString)})(?:$|\\W)`);
            if (regex.test(haystack)) {
                return true;
            }
        }
    } else {
        return haystack.includes(transformedString);
    }

    return false;
}

 

  1. 정규식인지 확인:
    • 키워드(needle)가 정규식 형태라면 정규식 매칭 수행
  2. 일반 문자열 변환:
    • 키워드와 대상 문자열을 변환(정규화)하여 비교
  3. 단어 전체 일치 옵션:
    • 활성화된 경우, 단어 경계나 공백을 고려한 정교한 매칭 수행
    • 비활성화된 경우, 단순 포함 여부 확인

 

 

선택적 필터 검사

if (entry.selective && Array.isArray(entry.keysecondary) && entry.keysecondary.length) {
    const matched = matchSecondaryKeys();
    if (!matched) {
        log('skipped. Secondary keywords not satisfied', entry.keysecondary);
        continue;
    }
}

 

 

selective(로직 조건)이 설정되고 keysecondary(선택적 필터)가 있는 경우에는 해당 코드가 실행된다.

 

 

조건이 만족된 항목 추가

activatedNow.add(entry);
log('activated by primary key match', primaryKeyMatch);

 

조건이 만족된 항목이 추가되며 이를 계속 반복하여 월드 인포를 검사한다.

이때 설정된 토큰 값을 초과하면 작업이 중단된다.

 

 

 

예시

사용자가 다음과 같이 로어북을 저장한다.

Lorebook 1:
Key: ["마법"]
Content: "마법은 세상의 본질을 바꾸는 힘이다."

Lorebook 2:
Key: ["전투"]
Content: "전투는 용맹한 전사의 상징이다."

 

 

해당 로어북을 캐릭터에 연결한 후 사용자가 "마법에 대해 알려줘" 라고 요청을 날리면

  1. Lorebook 1 활성화:
    • "마법" 키워드가 사용자 입력에 포함되어 활성화.
  2. Lorebook 2 비활성화:
    • "전투" 키워드가 사용자 입력에 없으므로 비활성화.

이러한 로직으로 Lorebook 1만 프롬프트에 추가되어 AI에게 요청을 날리게 된다.

 

 


 

 

실리태번 코드가 한두 줄도 아니고 Typescript로 작성된 것이 아니라 하나하나 찾아봐야 하는 번거로움이 있지만, 확실히 관심분야다 보니 나름 분석하는 재미가 있었다.

물론 분석 역시 AI의 도움을 받았다고 한다... 따봉 지피티야 고마워ㅎ

 

300x250