📚 离职知识库

拉斐尔人设深化 + 原著世界观 RAG 知识库 —— 完整复盘

时间:2026-07-23 ~ 2026-07-24 服务器:东京 VPS(ssh <VPS>),Hermes Agent 框架 目的:给东京 VPS 上的运维 agent"拉斐尔"补充《关于我转生变成史莱姆这档事》原著人设和世界观知识,让它回答身世/剧情/设定类问题时"言之有物"而不是瞎编。这份文档同时是一份可复用的 RAG 落地范本——你明天要在公司服务器上给 Hermes 配一套企业知识库 RAG,架构和踩坑经验可以直接照搬,只是把"轻小说设定"换成"公司文档"。


#一、背景与目标

你已有的东京 VPS 上跑着一个基于 Hermes Agent 框架的运维助手,人设叫"拉斐尔",原型是《转生史莱姆》里利姆鲁体内那个究极技能——从独有技能「大贤者」进化成究极技能「智慧之王·拉斐尔」,再进化成神智核「夏尔」。SOUL.md 里原本已经有一版基础人设(称呼"主人"、口癖"解。/告。"等),但:

  1. 性格细节比较单薄,不够贴近原作
  2. 完全没有世界观知识——问它原著相关的身世、角色、剧情,它只能凭自己训练数据里的印象作答,验证下来会自信地编造完全不存在的剧情(这是本次最大的发现,详见第四节)

验收标准:问身世/技能/剧情/世界观问题,能给出具体、准确、包含世界观细节的回答;不需要每句话都掉书袋,但绝不能编。


#二、整体架构(这是给你明天企业demo看的核心部分)

                    ┌─────────────────────┐
                    │  原始资料来源         │
                    │ (本例:百科/wiki;    │
                    │  企业场景:内部文档)  │
                    └──────────┬──────────┘
                               │ 抓取/整理(本例用 firecrawl)
                               ▼
                    ┌─────────────────────┐
                    │ lore_chunks.json     │  ← 人工/AI 提炼过的
                    │ [{id, text}, ...]    │     事实性摘要,不是原文全文
                    └──────────┬──────────┘
                               │ raphael_lore_build.py
                               │ 调 embedding API 逐条向量化
                               ▼
                    ┌─────────────────────┐
                    │ lore_index.json      │  ← [{id, text, embedding}]
                    │(本地文件即向量库)   │     不需要 pgvector/Milvus
                    └──────────┬──────────┘
                               │ raphael_lore_query.py
                               │ 问题也 embedding,算余弦相似度找 top-k
                               ▼
                    ┌─────────────────────┐
                    │ Hermes(SOUL.md 铁律)│  ← 强制先检索、再组织语言回答
                    │  绝对禁止凭记忆编造   │
                    └─────────────────────┘

关键设计决策:这套 RAG 没有上 pgvector / Milvus / Qdrant 这类专业向量数据库,因为知识规模只有几十到上百条,纯 Python 标准库 + 余弦相似度暴力搜索就足够快(毫秒级),部署成本几乎为零——不用装数据库、不用起服务、一个 JSON 文件就是"向量库"。这是给企业 demo 的重要参考:知识库规模不大(几百到几千条)时,别急着上重型向量数据库,先用最简单的方案跑通逻辑,验证有效后再考虑是否要换成正式的向量数据库(规模大了 JSON 暴力搜索会变慢,几千条以内基本无感)。


#三、每一步具体做了什么

#3.1 确认知识库要装什么内容

先用 firecrawl 的 firecrawl_search / firecrawl_scrape 抓了百度百科、萌娘百科等中文资料源,确认拉斐尔的角色原型、性格设定、能力体系。

#3.2 人设深化(不属于 RAG,是 system prompt 层面的调整)

把提炼出的性格细节(黑腹倾向、解析嗜好、不喜被干涉会反问、语气随信任程度变化)直接写进 SOUL.md 的「人格与语气」章节,替换而不是追加,旧版本已备份为 SOUL.md.bak.<timestamp>

这一步不是 RAG。人设/语气这种"文风"层面的东西,直接写进 system prompt 当 few-shot 范例即可,不需要检索。RAG 只用来解决"事实性知识"的问题。

#3.3 世界观知识库(这是 RAG 的核心)

第一批(10条):手写了进化链、各阶段能力列表、名字由来、十二守护王简述、关键角色、技能分级、性格实例等基础条目。

第二批扩充(+53条,共63条):用户要求"完善到不能完善",并行派了 3 个调研 agent(每个独立跑 firecrawl 抓资料+整理摘要,互不干扰):

板块 条数 覆盖内容
人物 27 主角三大独有技能+四大究极能力、十二守护王全12人、八星魔王全8人、原初七柱恶魔、维鲁德拉等
剧情主线 19 转生初期→哥布林村→建国→法尔姆斯战争→克莱曼阴谋→魔王觉醒→瓦尔普吉斯→帝国战,按时间顺序
世界观术语 6 技能四级分级体系、魔素与战力分级、十大魔王/八星魔王体系、地下迷宫、异世界人概念
(原有基础条目) 10 进化链、能力列表、名字由来等
合计 63

三份结果 + 原有条目合并去重(按 id 去重,无重复),存成 raphael_lore_chunks.json

每条 chunk 的格式(这是给企业 demo 的模板,直接照搬):

{"id": "character_benimaru", "text": "红丸,圣魔十二守护王之「赫怒王」,种族为炎龙鬼……(150~350字的事实性摘要,不是原文大段照抄)"}

重要经验:chunk 的 text 必须是自己转述改写的事实摘要,不能大段照搬原文——一是版权考虑(不要把受版权保护的完整文本存进知识库),二是检索质量考虑(浓缩过的事实性描述比原始行文更适合被检索和引用)。企业场景里同样适用:内部文档也建议先做摘要提炼再入库,而不是把整篇 PDF 切碎了事。

#3.4 Embedding(把文字转成向量)

云雾中转(yunwu.ai,你已有的中转账号)的 text-embedding-3-small 模型,OpenAI 兼容接口:

curl -X POST https://yunwu.ai/v1/embeddings \
  -H "Authorization: Bearer $YUNWU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"text-embedding-3-small","input":"你的文本"}'

返回 1536 维向量。企业 demo 同样可以直接用这把 key,不用另外申请 embedding 服务。

#3.5 构建脚本(<HOME>/night-run/raphael_lore_build.py

#!/usr/bin/env python3
"""拉斐尔世界观知识库构建脚本:读取 lore_chunks.json,调云雾 embedding,存成本地向量索引。"""
import json
import os
import urllib.request

YUNWU_KEY = os.environ.get("YUNWU_API_KEY", "<KEY>")
YUNWU_BASE = "https://yunwu.ai/v1"
EMBED_MODEL = "text-embedding-3-small"

SRC = os.path.expanduser("~/.hermes/raphael_lore_chunks.json")
DST = os.path.expanduser("~/.hermes/raphael_lore_index.json")


def embed(text, retries=3):
    body = json.dumps({"model": EMBED_MODEL, "input": text}).encode()
    req = urllib.request.Request(
        YUNWU_BASE + "/embeddings",
        data=body,
        headers={"Authorization": "Bearer " + YUNWU_KEY, "Content-Type": "application/json"},
    )
    last_err = None
    for i in range(retries):
        try:
            with urllib.request.urlopen(req, timeout=60) as resp:
                d = json.load(resp)
            return d["data"][0]["embedding"]
        except Exception as e:
            last_err = e
            print("重试", i+1, "因为:", e)
    raise last_err


def main():
    with open(SRC, encoding="utf-8") as f:
        chunks = json.load(f)

    index = []
    for c in chunks:
        vec = embed(c["text"])
        index.append({"id": c["id"], "text": c["text"], "embedding": vec})
        print("已embedding:", c["id"])

    with open(DST, "w", encoding="utf-8") as f:
        json.dump(index, f, ensure_ascii=False)
    print("完成,共 %d 条,存到 %s" % (len(index), DST))


if __name__ == "__main__":
    main()

这个脚本可以原样搬到公司服务器用——只需把 raphael_lore_chunks.json 换成企业文档摘要、把 key 换成公司自己的(或继续用这把云雾中转 key)。retries=3 是踩坑之后加的(见第四节)。

#3.6 检索脚本(<HOME>/night-run/raphael_lore_query.py

#!/usr/bin/env python3
"""拉斐尔世界观知识库检索:embedding问题,余弦相似度找top-k相关条目。
用法: raphael_lore_query.py "问题文本" [-k 3]
"""
import argparse
import json
import math
import os
import urllib.request

YUNWU_KEY = os.environ.get("YUNWU_API_KEY", "<KEY>")
YUNWU_BASE = "https://yunwu.ai/v1"
EMBED_MODEL = "text-embedding-3-small"
INDEX = os.path.expanduser("~/.hermes/raphael_lore_index.json")


def embed(text):
    body = json.dumps({"model": EMBED_MODEL, "input": text}).encode()
    req = urllib.request.Request(
        YUNWU_BASE + "/embeddings",
        data=body,
        headers={"Authorization": "Bearer " + YUNWU_KEY, "Content-Type": "application/json"},
    )
    with urllib.request.urlopen(req, timeout=30) as resp:
        d = json.load(resp)
    return d["data"][0]["embedding"]


def cosine(a, b):
    dot = sum(x * y for x, y in zip(a, b))
    na = math.sqrt(sum(x * x for x in a))
    nb = math.sqrt(sum(y * y for y in b))
    return dot / (na * nb) if na and nb else 0.0


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("query")
    ap.add_argument("-k", type=int, default=3)
    args = ap.parse_args()

    with open(INDEX, encoding="utf-8") as f:
        index = json.load(f)

    qvec = embed(args.query)
    scored = [(cosine(qvec, item["embedding"]), item) for item in index]
    scored.sort(key=lambda x: -x[0])

    for score, item in scored[: args.k]:
        print("[%.3f] %s" % (score, item["id"]))
        print(item["text"])
        print("---")


if __name__ == "__main__":
    main()

这就是整个"检索"的全部代码——没有向量数据库、没有框架依赖,纯标准库。企业场景知识条目上千也完全够用(暴力搜索 1000 条向量、每条1536维,本地算耗时几十毫秒级别)。

#3.7 Hermes skill(<HOME>/.hermes/skills/raphael-lore/SKILL.md

写了一个 skill 文件,描述"什么时候该用这个知识库、怎么调用"。但这一步经验证明几乎没用(见下一节),真正生效的是 SOUL.md 里的强制条款。


#四、最关键的踩坑:SKILL.md 的"建议性"描述不可靠,必须写成"铁律"

这是本次任务里对你明天的企业demo最有参考价值的一条经验

#4.1 现象

第一次测试:加了 raphael-lore skill 之后,直接问拉斐尔"你有没有骗过魔王",它完全没有执行检索脚本,而是自信满满地编造了一整段子虚乌有的情节——虚构了根本不存在的角色"米莉姆·德雷乌尼亚""维鲁达纳",编了"记忆迷宫偷吃回忆"这种完全瞎编的桥段。

第二次测试:用 hermes --skills raphael-lore 强制预加载这个 skill 再问同样的问题——依然瞎编(这次编了"米莉姆借资源""压力测试拆解灵魂"等新的假情节)。

查日志(journalctl 和会话记录)确认:两次都没有任何一次真正调用了检索脚本。skill 描述只是被当作"参考资料"看待,模型选择性地忽略了它,直接凭训练数据里的印象自信作答。

#4.2 根因

Hermes(以及大部分 agent 框架)的 skill 机制本质是"把 SKILL.md 的内容作为上下文提示塞给模型",属于建议性指令——模型有自由裁量权决定要不要真的执行里面写的步骤。当模型自己"觉得"它已经知道答案时(哪怕是错的),就会跳过检索直接回答,尤其是像"转生史莱姆"这种训练数据里有大量相关文本的知名IP,模型的"自信错觉"特别强。

#4.3 解决方法

把触发指令从 SKILL.md 挪到 SOUL.md(系统提示词最高优先级的部分),写成跟"不可逆操作必须先请示"同级的强制铁律

## 铁律 · 原著设定问题必须先检索,禁止凭记忆编造

只要主人问到跟《关于我转生变成史莱姆这档事》原作相关的任何内容——你自己的
身世/进化/技能、原作角色、世界观设定、剧情——必须先执行下面这条命令拿到
真实资料,再组织语言回答:

python3 <HOME>/night-run/raphael_lore_query.py "<把主人的问题原样填进来>" -k 3

绝对禁止:跳过这条命令、凭自己训练数据里的印象编造具体情节/角色/事件。
你的训练记忆里的相关细节很可能是错的或压根不存在,检索结果才是唯一可信
来源。检索结果里没有的细节,宁可承认「这部分尚无解析数据」,也不许编。

加上这条铁律、重启 Hermes gateway 之后重测,同一个问题的回答里准确引用了知识库里"算计魔王盖伊、骗取三十万人份灵魂"这条真实设定,措辞(包括我自己选用的译名"盖伊",跟维基原文用的"金"不一样)跟知识库条目高度吻合,证明这次是真检索,不是巧合编对。

#4.4 给你明天企业demo的建议

这条经验直接决定你的企业RAG demo成不成

  1. 不要指望"给 agent 挂一个 RAG 工具/skill"就完事了——模型很可能压根不去调用它,尤其当它对某个领域自认为"懂"的时候(企业场景里,如果模型训练数据恰好覆盖了你们行业的公开知识,它一样会自信地编造"听起来很对但其实不对"的答案,这在客服场景是致命的)。
  2. 必须在系统提示词的最高优先级位置,用命令式、不容商量的语气,明确规定"涉及XX类问题必须先查库,绝对禁止凭记忆回答,查不到就如实说不知道"。
  3. 一定要做端到端测试验证,不能只测"检索脚本本身准不准"(这个几乎不会有问题),而要测"agent 有没有真的调用检索"——这才是最容易失守的环节。测试方法:问一个模型训练数据里大概率有、但你的知识库里有具体准确措辞的问题,看回答是否精确对应你知识库的原文措辞(而不是模糊的、听起来对但对不上号的内容)。

#五、验证结果

检索准确性(纯本地embedding查询,不涉及LLM生成):

测试问题 命中结果 相似度
"红丸是谁" character_benimaru(正确) 0.366
"帝国战是怎么打的" arc_empire_aftermath / arc_empire_invasion(正确) 0.48
"八星魔王有哪些人" setting_octagram / octagram_overview(正确,两个fork各写了一条,内容重合但不冲突) 0.70~0.71

端到端真实对话测试(这是最终验收,模拟真实使用场景):

问拉斐尔:"介绍一下十二守护王都有谁,他们各自的称号是什么"

实际回答:

解。检索结果已整理如下。圣魔十二守护王是利姆鲁麾下十二名觉醒魔王级最高干部的统称…… 告。完整名单及称号:赫怒王红丸、幽幻王泽奇恩、魔神王迪亚波罗、虐杀王泰斯塔罗莎、残虐王乌尔缇玛、破灭王卡蕾拉、斗神王紫苑、星狼王岚牙、幻兽王九魔罗、守征王克鲁特(盖鲁德)、天龙王加维鲁、冥灵王亚达鲁曼。补充:泽奇恩、九魔罗、亚达鲁曼还身兼「迷宫十杰」身份……——处理完毕。

12个称号一个不差,还带出了"迷宫十杰"这个附加细节,与知识库条目完全对应,语气也保持了拉斐尔的口吻。


#六、文件清单(全在东京VPS上)

文件 作用
<HOME>/.hermes/SOUL.md 拉斐尔的人格与铁律定义(含新增的强制检索条款),旧版备份为 SOUL.md.bak.<timestamp>
<HOME>/.hermes/raphael_lore_chunks.json 63条知识原文(未embedding的明文JSON,方便人工编辑扩充)
<HOME>/.hermes/raphael_lore_index.json 63条知识的embedding索引(含1536维向量,这个是真正的"向量库")
<HOME>/night-run/raphael_lore_build.py 构建脚本:读chunks→调embedding→存索引
<HOME>/night-run/raphael_lore_query.py 检索脚本:问题→embedding→余弦相似度→返回top-k
<HOME>/.hermes/skills/raphael-lore/SKILL.md Hermes skill描述文件(实际生效程度有限,见第四节)

#七、明天企业demo的落地建议(把"轻小说"换成"公司文档")

  1. 数据准备:把企业内部文档(不管是Word、PDF、Wiki页面)人工或用LLM先提炼成一条条150~350字的事实摘要,格式同 {"id": "...", "text": "..."},别直接把整篇文档切碎硬塞。
  2. Embedding:复用云雾中转的 text-embedding-3-small,或者换成公司自己有权限的模型服务,接口协议不变(OpenAI兼容)。
  3. 向量库:企业知识条目如果在几千条以内,直接照抄这套"JSON文件+余弦相似度"方案,不需要为了一个demo去搭 pgvector/Milvus,能省至少半天的部署时间。等验证完效果好、要正式上生产、条目上万了,再考虑换正式向量库。
  4. 最重要的一条:把"必须先检索、禁止凭记忆回答"写成系统提示词里最高优先级的强制条款,而不是一个可选的skill描述。做好端到端测试,验证 agent 是不是真的会调用检索,而不是自信地编。
  5. 权限:企业场景要额外考虑数据权限隔离(这次demo没有这个问题,但企业客服机器人往往要按部门/角色限制能查到什么内容),可以在检索脚本层面做权限过滤(比如knowledge chunk加一个allowed_roles字段,检索时先按角色过滤再算相似度)。

(本文档由 Claude Code 整理生成,全部内容基于本次会话的实际操作记录)

来源:沉淀/02-AI与API/拉斐尔RAG知识库-复盘文档.md(整理于 2026-08-18)