Skip to content

feat: add SillyTavern worldbook import#216

Merged
EterUltimate merged 3 commits into
NickCharlie:mainfrom
EterUltimate:codex/issue-209-worldbook-import
Jun 18, 2026
Merged

feat: add SillyTavern worldbook import#216
EterUltimate merged 3 commits into
NickCharlie:mainfrom
EterUltimate:codex/issue-209-worldbook-import

Conversation

@EterUltimate

@EterUltimate EterUltimate commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add WorldBookImporter for SillyTavern worldbook JSON preview/import/history.
  • Support entries as object or array, normalize key/secondaryKeys, and preserve ST metadata.
  • Map entries into existing Self Learning tables without schema changes: persona review memories, jargon candidates, and KG entry/keyword relations.
  • Expose WebUI endpoints and document the integration contract.

Issue Evidence

  • Fixes [Feature] 支持导入酒馆(SillyTavern)格式世界书 JSON #209.
  • Issue asks for services/integration/worldbook_importer.py, compatibility with entries object/list, mapping content to review/KG and key + secondaryKeys to jargon, plus preview/import/imports endpoints.
  • This PR intentionally limits the first slice to importer + API + docs/tests. Automatic _inject_worldbook_context is left for a follow-up because it needs config, scan-depth, and token-budget behavior wired into services/hooks/llm_hook_handler.py safely.

Tests

  • python -m pytest tests\unit\test_worldbook_importer.py -q
  • python -m pytest tests\integration\test_worldbook_integration_routes.py -q
  • python -m ruff check services\integration\worldbook_importer.py webui\blueprints\integrations.py tests\unit\test_worldbook_importer.py tests\integration\test_worldbook_integration_routes.py
  • python -m py_compile services\integration\worldbook_importer.py webui\blueprints\integrations.py
  • git diff --check

Install Smoke Test

Could not complete the requested install smoke test under C:\Users\zacza\Desktop\x\AstrBot\data\plugins\astrbot_plugin_self_learning: the directory returns Access is denied even for Test-Path, Get-ChildItem, icacls, and git -C. I did not change ACLs or force ownership.

Summary by Sourcery

Add support for importing SillyTavern worldbook JSON into the Self Learning plugin and expose corresponding WebUI APIs and documentation.

New Features:

  • Introduce a WorldBookImporter service to normalize SillyTavern worldbook JSON and map it into existing persona reviews, jargon candidates, and knowledge graph tables.
  • Expose WebUI endpoints to preview worldbook content, perform imports with configurable targets, and list recent worldbook import history.

Enhancements:

  • Register the WorldBookImporter in the integration service registry and surface its APIs in the Self Learning integration endpoint list.

Documentation:

  • Document the new worldbook preview, import, and history APIs and describe the expected SillyTavern worldbook JSON format and data mapping behavior.

Tests:

  • Add unit tests for WorldBookImporter parsing, import behavior, idempotency, and backfill flows, and integration tests for the new worldbook WebUI routes.

@sourcery-ai

sourcery-ai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds a SillyTavern worldbook JSON importer wired into existing Self Learning review/jargon/knowledge-graph tables, with new WebUI integration endpoints, documentation, and tests for parsing, import behavior, and HTTP routes.

Sequence diagram for SillyTavern worldbook import flow

sequenceDiagram
    actor Client
    participant IntegrationsBP as integrations_bp
    participant WorldBookImporter
    participant DB as database_manager

    Client->>IntegrationsBP: POST /integrations/worldbook/preview
    IntegrationsBP->>WorldBookImporter: preview(payload, json_text, json_path)
    WorldBookImporter->>WorldBookImporter: load_package(...)
    WorldBookImporter->>WorldBookImporter: package_summary(package)
    WorldBookImporter-->>IntegrationsBP: summary
    IntegrationsBP-->>Client: 200 { success, data }

    Client->>IntegrationsBP: POST /integrations/worldbook/import
    IntegrationsBP->>DB: get_session()
    IntegrationsBP->>WorldBookImporter: import_from_source(payload, json_text, json_path, ...)
    WorldBookImporter->>WorldBookImporter: load_package(...)
    WorldBookImporter->>WorldBookImporter: import_package(package, ...)
    loop for each entry
        WorldBookImporter->>WorldBookImporter: _entry_review_exists(...)
        alt import_memories
            WorldBookImporter->>DB: _add_memory_review(session, package, entry, ...)
        end
        alt import_jargons
            WorldBookImporter->>DB: _import_jargon_candidates(session, package, entry, ...)
        end
        alt import_knowledge_graph
            WorldBookImporter->>DB: _import_knowledge_graph(session, package, entry, ...)
        end
    end
    WorldBookImporter-->>IntegrationsBP: { success, entries_imported, ... }
    IntegrationsBP-->>Client: 200 { success, data }

    Client->>IntegrationsBP: GET /integrations/worldbook/imports
    IntegrationsBP->>DB: get_session()
    IntegrationsBP->>WorldBookImporter: import_history(limit, offset)
    WorldBookImporter->>DB: select PersonaLearningReview
    WorldBookImporter-->>IntegrationsBP: { total, items, imports }
    IntegrationsBP-->>Client: 200 { success, data }
Loading

File-Level Changes

Change Details Files
Introduce WorldBookImporter service to normalize SillyTavern worldbook JSON and map it into existing review, jargon, and knowledge graph tables without schema changes.
  • Define WorldBookEntry and WorldBookPackage dataclasses to represent normalized entries and packages, including metadata and helper methods.
  • Implement load/preview/export logic that accepts payload/json text/file path, supports SillyTavern entries as dict or list, and normalizes keys/secondaryKeys, enabled/disabled flags, and metadata.
  • Implement import pipeline that writes persona memory reviews, jargon candidates, and KG entities/relations (with idempotency for reviews, optional toggles for each destination, and aggregation of import statistics).
  • Add import_history query that derives worldbook import batches from PersonaLearningReview metadata and returns both per-review items and grouped import summaries.
  • Provide private helpers for entry normalization, JSON decoding, timestamp/bool/number coercion, safe slugs, and SQL patterns used in idempotency checks and KG upserts.
services/integration/worldbook_importer.py
Expose new WebUI integration endpoints for worldbook preview/import/history and share parsing helpers.
  • Wire WorldBookImporter into integrations blueprint with import-safe imports for both package and local paths.
  • Add /api/integrations/worldbook/preview POST route that instantiates the importer without a DB manager and returns a package summary derived from payload/json_text/json_path.
  • Add /api/integrations/worldbook/import POST route that resolves database_manager from the DI container, runs import_from_source with body-controlled toggles and group id, and returns the import result.
  • Add /api/integrations/worldbook/imports GET route that uses import_history with limit/offset query params to list recent worldbook imports.
  • Introduce shared helpers _worldbook_source_args, _body_bool, and _query_int for request parsing and validation.
webui/blueprints/integrations.py
Document the worldbook integration contract and surface endpoints in the integration service metadata.
  • Extend webui-api docs with the three new worldbook endpoints, example request payloads, and a description of how content/keys map into persona reviews, jargon, and knowledge-graph tables and metadata-derived history.
  • Update integrations overview docs to describe supported worldbook JSON formats, reuse of existing tables, and how import history is aggregated from review metadata.
  • Register the worldbook endpoints in SELF_LEARNING_API_ENDPOINTS so they appear in integration status/manifest responses.
  • Export WorldBookImporter from the services.integration package for external use.
docs/webui-api.md
docs/integrations.md
webui/services/integration_service.py
services/integration/__init__.py
Add unit and integration tests to validate worldbook parsing, import semantics, idempotency, and HTTP routes.
  • Unit-test WorldBookImporter parsing of dict and list entry formats, preview summary counts, and JSON-string payload handling.
  • Unit-test importing into a temporary SQLite DB for review, jargon, and KG entities/relations; verify counts, stored metadata, and import_history aggregation.
  • Unit-test idempotent behavior for persona memory reviews when re-importing the same worldbook for the same group, and the ability to backfill jargon/KG after a memory-only import.
  • Integration-test Quart routes for worldbook preview/import/imports using a monkeypatched container that provides a SQLAlchemyDatabaseManager, asserting both HTTP contract and DB side effects.
tests/unit/test_worldbook_importer.py
tests/integration/test_worldbook_integration_routes.py

Assessment against linked issues

Issue Objective Addressed Explanation
#209 Implement a SillyTavern worldbook importer service that parses standard worldbook JSON (supporting both object and array forms of entries), maps content into persona learning review memories and knowledge graph entities, maps key/secondaryKeys into jargon candidates and KG keyword relations, and preserves/imports relevant metadata.
#209 Expose WebUI integration endpoints for SillyTavern worldbook import (preview, import with configurable targets and flags, and imports history), and document their API and behavior.
#209 Implement an injection strategy in services/hooks/llm_hook_handler.py (e.g., _inject_worldbook_context) to scan recent messages by keywords, always inject constant entries, respect order and token budget, and integrate imported worldbook knowledge into LLM context. The PR explicitly defers the _inject_worldbook_context implementation in llm_hook_handler.py to a follow-up and does not add any LLM hook changes or worldbook context injection logic.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The json_path/worldbook_path handling in _worldbook_source_args and WorldBookImporter.load_package will read arbitrary server-side paths supplied by the caller; consider restricting this to a known base directory or explicitly disabling json_path for WebUI-driven requests to avoid unintended file disclosure.
  • worldbook_importer.py currently mixes parsing/normalization logic and persistence concerns in a single, ~800-line module; consider splitting out the pure parsing layer (e.g., WorldBookPackage construction and helpers) from the DB import layer to keep each unit smaller and easier to reason about and reuse.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `json_path`/`worldbook_path` handling in `_worldbook_source_args` and `WorldBookImporter.load_package` will read arbitrary server-side paths supplied by the caller; consider restricting this to a known base directory or explicitly disabling `json_path` for WebUI-driven requests to avoid unintended file disclosure.
- `worldbook_importer.py` currently mixes parsing/normalization logic and persistence concerns in a single, ~800-line module; consider splitting out the pure parsing layer (e.g., `WorldBookPackage` construction and helpers) from the DB import layer to keep each unit smaller and easier to reason about and reuse.

## Individual Comments

### Comment 1
<location path="services/integration/worldbook_importer.py" line_range="126" />
<code_context>
+            source_paths["worldbook_json"] = str(path.resolve())
+        if isinstance(payload, str):
+            payload = _json_decode(payload)
+        if not isinstance(payload, Mapping):
+            raise ValueError("请提供 SillyTavern 世界书 JSON 对象或 JSON 字符串")
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Top-level list payloads can never reach the list-handling branch in `_parse_sillytavern_payload`.

Because `load_package` rejects any non-mapping payloads, `_parse_sillytavern_payload` will only ever receive a `Mapping`. The branch that tries to handle a top-level `list` (`if entries_payload is None and isinstance(payload, list):`) is therefore dead code. If SillyTavern is supposed to export a bare `list`, this path will never be taken and a `ValueError` will be raised instead; if that case is impossible, the list-handling logic should be removed. Please either allow `list` payloads in `load_package` or simplify `_parse_sillytavern_payload` to only handle mappings.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread services/integration/worldbook_importer.py Outdated
@EterUltimate

Copy link
Copy Markdown
Collaborator Author

Addressed review feedback in 13249cc:

  • WebUI worldbook preview/import now ignores caller-provided json_path/worldbook_path so requests cannot trigger arbitrary server-side file reads.
  • WorldBookImporter now accepts top-level list payloads and parses them before Mapping-only access.
  • Added regression coverage for both paths.

Validation:

  • python -m pytest tests\unit\test_worldbook_importer.py -q
  • python -m pytest tests\integration\test_worldbook_integration_routes.py -q
  • python -m ruff check services\integration\worldbook_importer.py webui\blueprints\integrations.py tests\unit\test_worldbook_importer.py tests\integration\test_worldbook_integration_routes.py
  • python -m py_compile services\integration\worldbook_importer.py webui\blueprints\integrations.py
  • git diff --check

@EterUltimate EterUltimate requested a review from NickCharlie June 18, 2026 00:37
@EterUltimate

Copy link
Copy Markdown
Collaborator Author

Added release bump in 33d0f85:

  • metadata.yaml: 3.4.1 -> 3.4.2
  • README / README_EN badges updated to 3.4.2
  • CHANGELOG.md added 3.4.2 entry for the worldbook import and review fixes

All PR checks are passing; merge is currently waiting on required maintainer review.

@EterUltimate EterUltimate merged commit a6e2e63 into NickCharlie:main Jun 18, 2026
4 checks passed
@EterUltimate EterUltimate deleted the codex/issue-209-worldbook-import branch June 18, 2026 05:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] 支持导入酒馆(SillyTavern)格式世界书 JSON

1 participant