Aventuras 静态分析:模块设计与工作原理
对象:AventurasTeam/Aventuras · 方法:纯静态分析
一、项目概览与技术栈
Aventuras 是一个「Interactive fiction with an AI that remembers」——AI 互动小说引擎, 支持冒险(多选动作 + 世界追踪)与协作写作(自由文本 + AI 建议)两种模式,桌面 + Android 双端。 技术栈(README.md:75-77):TypeScript(strict) · SvelteKit 2 · Svelte 5 runes · Tauri 2(Rust) · Tailwind + shadcn-svelte · SQLite(tauri-plugin-sql / sqlx) · Vercel AI SDK · LiquidJS · Zod · Vitest。
TypeScript 承载全部业务逻辑,Rust 只负责会撑爆 WebView 堆的字节搬运(备份/导入/同步),SQLite 落盘。 仓库结构见 docs/architecture/overview.md:7-34。
二、总体架构(ASCII)
┌────────────────────── src/ (SvelteKit 前端, TS strict, Svelte 5 runes) ─────────────────────┐
│ routes/ 页面 (+page.svelte / +layout.svelte) │
│ lib/components/ UI 组件;lib/stores/ 状态 (story/ui/settings 等 *.svelte.ts) │
│ lib/services/ 业务逻辑:ai/ (生成·检索·记忆·图像) · generation/ (流水线 phases) · ... │
│ lib/types/ 数据模型 (index.ts);lib/utils/ 工具 │
└──────────────┬──────────────────────────────────────────────────────────────────────────────┘
│ IPC (仅传 path/id,大 payload 由 Rust 流式搬运,persistence.md:19-24)
┌──────────────▼────────── src-tauri/ (Rust, Tauri 2) ─────────────────────────────────────────┐
│ src/backup.rs 备份/恢复 · src/avt_import.rs .avt 导入 · src/sync/ LAN 同步(QR 配对) │
│ migrations/ 001_initial.sql … 037_kept_separate.sql(sqlx 顺序迁移,LF 行尾) │
│ gen/android/ 跟踪在库内的 Android 脚手架(勿 tauri android init) │
└──────────────┬──────────────────────────────────────────────────────────────────────────────┘
│ tauri-plugin-sql / sqlx
┌──────▼──────┐ ┌──────────────────────────────────────┐
│ SQLite │ │ LLM 云端/本地 (Vercel AI SDK) │
│ aventura.db │ │ OpenRouter/OpenAI/Anthropic/Google/ │
└─────────────┘ │ xAI/Groq/DeepSeek/Mistral/Z.AI/ │
│ NanoGPT/Chutes/Pollinations/NIM │
│ + 本地 Ollama/LM Studio/llama.cpp │
└──────────────────────────────────────┘
多平台组织:同一前端 + Tauri 壳,产出 Windows/macOS/Linux 桌面安装包与 Android APK(README.md:38-43)。
compileApk.sh 与 Android NDK/JDK 文档见 docs/development/release.md。
桌面与移动共享全部 AI/存储逻辑,差异仅在 Rust 能力(Android 有 WebView 堆硬上限,图片 base64 由 Rust 直通 SQLite,persistence.md:21-24)。
三、核心工作流(玩家输入 → 生成 → 记忆更新)
一条回合的编排是 GenerationPipeline.execute()(src/lib/services/generation/GenerationPipeline.ts:105)。
各阶段是注入依赖的 async 生成器(phases/ 目录),顺序为:
PreGeneration(存 retry 备份) → Retrieval(检索/记忆) → Narrative(流式叙述, 唯一致命)
→ [ Classification ∥ Translation → Image ] ∥ BackgroundImage ∥ PostGeneration(建议/风格)
阶段 1:上下文装配(phases/RetrievalPhase.ts:78),分两阶段:
- Stage A(L119-186)并行跑「世界状态注入
WorldStateInjector」与「lorebook 条目检索EntryRetrievalService」。 lorebook 等待世界状态先交出 Tier1+Tier2 场景实体(onSceneEntities,L114-141),作为第二遍匹配的种子。 - Stage B(L201-232)跑记忆检索——Agentic 或 Static(
shouldUseAgenticRetrieval())。 并把 Stage A 已注入内容以formatAlreadyInContext告知(L214)。
阶段 2:叙述生成(phases/NarrativePhase.ts:67):
- 流式输出文本/推理 chunk,空响应最多重试 3 次(L25、L77)。
NarrativeService.buildPrompts(generation/NarrativeService.ts:386)用 Liquid 模板拼 system prompt (含章节摘要、检索块、世界状态块),buildUserPrompt(L525)拼历史与当前动作。
阶段 3:记忆/世界状态更新:
- 叙述后
ClassifierService.classify(generation/ClassifierService.ts:74)用 Zod 结构化输出抽取世界状态变更。 ChapterService.checkAndCreateChapter(generation/ChapterService.ts:81)在 token 超阈值时 触发MemoryService.summarizeChapter(generation/MemoryService.ts:67)生成章节摘要。- 回合后
BackgroundTaskCoordinator再跑 lore 管理与风格审查(overview.md:96-97)。
四、模块逐一分析
4.1 AI 记忆实现(重点)
- 章节摘要如何触发:
ChapterService.checkAndCreateChapter(generation/ChapterService.ts:81) 以tokensOutsideBuffer ≥ tokenThreshold为触发(L97-103),默认阈值 16000、缓冲 10 条(MemoryConfig,types/index.ts:104-111)。 - 摘要如何生成:
MemoryService.analyzeForChapter(L120)先判是否建章 + 最优终点;summarizeChapter(L67)产出 summary/keywords/characters/locations/plotThreads/emotionalTone, 组装Chapter落库(ChapterService.ts:179-197)。细化度由SummaryDetail(concise/auto/precise) 控制(MemoryService.ts:40-46)。 - 摘要如何存储:
Chapter行含 start/endEntryId、summary、时间跨度、关键词/角色/地点/情节线/情绪基调(types/index.ts:395-423)。 - 摘要如何注入:
NarrativeService.buildChapterSummariesBlock(L155)把摘要写入<story_history>块; 记忆检索结果经buildTimelineFillBlock(L221)或buildRetrievalContext(retrieval/AgenticRetrievalService.ts:437) 以[RELEVANT STORY DATA]注入。 - Agentic 检索:
AgenticRetrievalService.runRetrieval(retrieval/AgenticRetrievalService.ts:155) 用工具search_entries/get_entry/grep_chapters/query_chapter/inspect_world_state/finish_retrieval(清单见 context-injection.md:85-92);最后一步强制finish_retrieval,失败则从事件日志 salvage 已付费章节答案(L370-388)。 - Static 检索:
TimelineFillService(retrieval/)生成≤5 个问题、按章节覆盖归组批量回答, 预算由chapterContentBudget.ts限制(context-injection.md:192-205)。
4.2 LLM Provider 抽象(重点)
- 元数据单一来源:
sdk/providers/config.ts的PROVIDERS(L75)记录每 provider 的名称/baseUrl/能力 (reasoning·structuredOutput·imageGeneration·reasoningExtraction)。 - 实例化单一 switch:
sdk/providers/registry.ts的createProviderFromProfile(L46)按providerType实例化 Vercel AI SDK 提供者——云端 14 种 + 本地 Ollama/LM Studio/llama.cpp(均走 OpenAI 兼容,L105-130)。 - 生成入口:
sdk/generate.ts提供generateStructured(L208,Zod + jsonrepair + 429 重试中间件)、generatePlainText(L242)、streamNarrative(L277)。 - Agent 入口:
sdk/agents/factory.ts的createAgentFromPreset(L104)造ToolLoopAgent驱动 agentic 循环。 - 每任务一模型(Agent Profiles):每个 AI 任务是一个
ServiceId(如 classifier/memory/agenticRetrieval), 经BaseAIService.presetId(ai/BaseAIService.ts:12)解析到 Agent Profile;模型从不硬编码(ai-services.md:5-12)。 - 服务构造唯一入口:
ai/core/factory.ts的ServiceFactory(L30)+ 单例serviceFactory(L166)。
4.3 故事状态模型(重点)
- 核心是追加式
StoryEntry列表(type:user_action|narration|system|retry,types/index.ts:132-151), 每行带position、branchId、metadata.tokenCount/timeStart/timeEnd,并记录worldStateDelta(L913)以支持回滚。 - Branches:在
forkEntryId处分叉;story.entries是当前分支视图 = 本分支行 + 祖先继承;visibleEntries= 去掉已折叠进章节的尾部(story.svelte.ts:416)。COW 轻量分支(migration 026/028/029)。 - Chapters:
startEntryId/endEntryId覆盖连续区间,用摘要替换正文进 prompt(overview.md:45-48)。 - 世界状态:
Character/Location/Item/StoryBeat(types/index.ts:171-387)由分类器每回合重写; 与 LorebookEntry是两套不相交的池子,分别由WorldStateInjector与EntryRetrievalService注入(overview.md:56-58)。 - 回滚:
worldStateDelta记录分类器改动前后状态,rollbackService据此反转重试/删除/重生(story.svelte.ts:1022)。
4.4 协作写作模式(重点)
StoryMode='creative-writing'(types/index.ts:3)与 adventure 并列。NarrativeService.buildCreativeWritingPriming(L621)切换为「小说作者」口吻(一/二/三人称 × 时态)。SuggestionsService生成情节建议、StyleReviewerService做重复措辞风格分析(见 ai/generation/,factory.ts:12-14)。- 自由输入 + 建议,无多选动作;世界状态仍由分类器维护。
4.5 其余关键模块
- ai/index.ts(编排门面
AIService,1348 行):所有子服务统一入口。streamNarrative(L231)、classifyResponse(L282)、runLoreManagement(L627)、runAgenticRetrieval(L738)、runTimelineFill(L791)、generateImagesForNarrative(L865)。 - 世界状态注入
WorldStateInjector(generation/WorldStateInjector.ts, 944 行):buildContext(L195)三段式—— Tier1 恒注入(getTier1EntriesL336)、Tier2 名称模糊匹配两遍(getTier2EntriesL526)、 Tier3 先问量再问相关度(selectTier3WithLLML711);buildContextBlock(L825)区分状态/相关两个轴。 - Lorebook 检索
EntryRetrievalService(retrieval/EntryRetrievalService.ts, 714 行): 作用于作者编写的静态Entry[];STICKINESS_BY_TYPE(L51)给各类型不同粘性窗口;getRelevantEntries(L236)三段式 + 激活记录;buildContextBlock(L624)输出[LOREBOOK CONTEXT]。 - 分类器
ClassifierService(generation/ClassifierService.ts, 494 行):classify(L74)抽取世界状态变更 + scene(在场/时间推进);失败时recover(L201)jsonrepair 抢救;formatExistingCharacters(L411)名字独占一行防重复角色。 - Lore 管理
LoreManagementService(ai/lorebook/):唯一自动写 Lorebook 的 agent(lore-management.md:50-54);sessionChanges.ts用「索引槽位永不变」保证 create-then-update 落成一次 create(L74-81)。 - 图像后端:9 种 provider(ai/image/providers/,含本地 ComfyUI/A1111,overview.md:100-102)。
五、数据模型与持久化
- 存储引擎:SQLite(
sqlite:aventura.db落在 app config dir),37 个顺序迁移 (src-tauri/migrations/001_initial.sql … 037_kept_separate.sql)。 - Rust 层职责:
backup.rs备份/恢复、avt_import.rs.avt 导入(两遍流式、峰值仅一张图)、sync/LAN 同步(QR 配对);规则是「JS 管结构,Rust 管字节」(persistence.md:19-24)。 - 设置持久化:单 JSON blob,经
stores/settingsMigrations.ts幂等迁移,不回删 legacy key(persistence.md:42-54)。 - 激活/粘性持久化:按 story 存
lorebook_activation_<storyId>,加载时恢复(context-injection.md:249-250)。
六、与 Z.R.I.C 功能对照表
| Z.R.I.C 功能 | Aventuras 对应实现 | 代码位置 | 结论 |
|---|---|---|---|
| 剧本解析 | 无固定剧本;靠 Wizard 生成初始设定 + SillyTavern/Character Card/.avt 导入 | stChatImporter、avt_import.rs、story.svelte.ts:697 | 部分(导入 ≠ 剧本解析) |
| 推演分支 | Branch + forkEntryId + COW 轻量分支 | types/index.ts:452、migrations 013/026、story.svelte.ts:124 | ✅ 有 |
| 自动判定 | ClassifierService 抽取世界状态变更/在场/时间推进 | generation/ClassifierService.ts:74 | ✅ 有 |
| 世界状态 | Character/Location/Item/StoryBeat + 三段注入 | generation/WorldStateInjector.ts:195 | ✅ 有(较强) |
| NPC 情绪记忆 | 章节级 emotionalTone + 角色 relationship/traits/status;无逐 NPC 情绪史(Entry.state 字段存在但从未被写,overview.md:50-53) |
Chapter.emotionalTone、ClassifierService.ts:411 | ⚠️ 部分/间接 |
| 地图 | Location.connections: string[] 字段存在,未见拓扑/地图 UI |
types/index.ts:341 | ⚠️ 弱 |
| 触发器 | StoryBeat 状态机(quest/milestone/event…)+ 关键词注入,无脚本式事件触发器 | types/index.ts:370、classifier.ts:129-151 | 部分 |
| 时间线 | TimeTracker(years/days/hours/minutes) + timeProgression + 章节 start/endTime | types/index.ts:19、classifier.ts:182-186、ChapterService.ts:176-177 | ✅ 有 |
| 记忆系统 | 章节自动摘要 + Agentic/Static 检索 + 粘性/stickiness | generation/MemoryService.ts:67、AgenticRetrievalService.ts:155 | ✅ 有(最强项) |
| 本地模型 | Ollama / LM Studio / llama.cpp + 本地 ComfyUI/A1111 图像 | sdk/providers/registry.ts:105-130、ai/image/providers/ | ✅ 有 |
| 许可 | AGPL-3.0 | LICENSE.md、README.md:93 | ✅ AGPL-3.0 |
七、设计评价
优点(附代码依据):
- 分层清晰、依赖注入、可测试:生成阶段是注入依赖的纯 async 生成器,阶段本身不 import store/provider,
依赖在
ActionInput.svelte组装(overview.md:91-94);phase 均可独立单测(WorldStateInjector.test.ts 等)。 - 记忆/上下文工程极其扎实:三段式注入 + 「量先于相关度」的 Tier3 预算 + 粘性 + 前缀缓存排序(prompts.md:67-88)
- 章节读预算,均有实测数据支撑且写进 docs/。
- Provider 抽象统一且可扩展:
PROVIDERS单一元数据源 +createProviderFromProfile单一 switch- 每子系统一个 ServiceId/Agent Profile,检索/分类/叙述可各用不同模型(ai-services.md:5-12)。
- 健壮的降级与抢救:叙述外所有阶段失败不致命(overview.md:82-84);分类器失败用 jsonrepair 抢救(ClassifierService.ts:201); agentic 检索死亡仍 salvage 已付费答案(AgenticRetrievalService.ts:370)。
缺陷(附代码依据):
- 单一巨型门面与巨型文件:
ai/index.ts达 1348 行、story.svelte.ts超 1390 行、WorldStateInjector.ts944 行,职责边界靠注释维持,阅读与重构成本高。 - 数据模型存在死代码/两套体系:
Entry.state(角色情绪/关系史/阵营声望等)设计完整但从未被写入(overview.md:50-53); Lorebook 与 WorldState 两套并行实体易混淆,注释反复强调「不要搞混」(WorldStateInjector.ts:14-33)。 - 地图/情绪/触发器等「游戏系统」偏薄:Location 只有
connections数组无拓扑;NPC 无情绪记忆史; StoryBeat 靠 LLM 抽状态而非确定性判定,复杂机制(如触发器)缺位——较 Z.R.I.C 的规则化剧本引擎,偏「纯 LLM 驱动」。 - 复杂度与认知负担高:大量「曾踩过的坑」以长注释固化在代码里(如 tier 顺序、索引稳定性、粘性不刷新), 说明隐蔽不变量多、易回归,新贡献者门槛高(CLAUDE.md 明确要求先读对应 docs/)。
八、结论
Aventuras 是一个工程化程度很高的纯 LLM 互动小说引擎: 以「追加式 StoryEntry + 章节摘要记忆 + 双池三段上下文注入 + 可替换 Provider + Tauri 跨桌面/移动」为核心, 记忆与上下文管理是其压倒性强项(自动摘要、Agentic/Static 检索、粘性、前缀缓存均有量化依据)。 相比之下,世界状态、分支、时间线具备,但地图、NPC 情绪史、确定性触发器/判定等「规则游戏系统」较弱, 更接近「会记事的 AI 讲故事机」而非规则化 TRPG 引擎。 整体适合作为记忆型 AI 叙事前端的参考实现,AGPL-3.0 许可。