增强旧版兼容性,添加多个旧路径入口和相关功能

This commit is contained in:
whatevertogo
2026-03-13 18:26:13 +08:00
parent 4680cb405b
commit 7b2f6f5497
14 changed files with 297 additions and 2 deletions

View File

@@ -32,6 +32,7 @@
- 2026-03-13: `filter.llm_tool()` must resolve deferred annotations from `from __future__ import annotations` when inferring JSON schema. Reading `inspect.Parameter.annotation` directly degrades typed params like `a: int` into `"int"` strings and silently turns tool argument schemas into generic strings.
- 2026-03-13: Legacy result hooks must reuse the same `AstrMessageEvent` instance across `on_decorating_result` and `after_message_sent`. Re-wrapping the original v4 `MessageEvent` for the second hook drops decorated `event.set_result(...)` mutations and makes post-send hooks observe an empty result.
- 2026-03-13: `src-new/astrbot_sdk/_legacy_runtime.py` already exists as the intended compat execution boundary. When cleaning runtime architecture, wire `loader` / `handler_dispatcher` / `bootstrap` through that adapter instead of adding new direct `legacy_context` branches in runtime files, or the compat logic will spread again.
- 2026-03-13: Real legacy plugins may still load through deep `astrbot.core.*` imports even when their public entrypoint only looks like `astrbot.api.*`. `astrbot_plugin_self_learning` hits `astrbot.core.utils.astrbot_path`, `astrbot.core.provider.*`, `astrbot.core.agent.message`, and `astrbot.core.db.po` during load; keep those deep-path shims minimal and whitelist-driven, but do not assume the `api` facade alone is enough.
# 开发命令

View File

@@ -32,6 +32,7 @@
- 2026-03-13: `filter.llm_tool()` must resolve deferred annotations from `from __future__ import annotations` when inferring JSON schema. Reading `inspect.Parameter.annotation` directly degrades typed params like `a: int` into `"int"` strings and silently turns tool argument schemas into generic strings.
- 2026-03-13: Legacy result hooks must reuse the same `AstrMessageEvent` instance across `on_decorating_result` and `after_message_sent`. Re-wrapping the original v4 `MessageEvent` for the second hook drops decorated `event.set_result(...)` mutations and makes post-send hooks observe an empty result.
- 2026-03-13: `src-new/astrbot_sdk/_legacy_runtime.py` already exists as the intended compat execution boundary. When cleaning runtime architecture, wire `loader` / `handler_dispatcher` / `bootstrap` through that adapter instead of adding new direct `legacy_context` branches in runtime files, or the compat logic will spread again.
- 2026-03-13: Real legacy plugins may still load through deep `astrbot.core.*` imports even when their public entrypoint only looks like `astrbot.api.*`. `astrbot_plugin_self_learning` hits `astrbot.core.utils.astrbot_path`, `astrbot.core.provider.*`, `astrbot.core.agent.message`, and `astrbot.core.db.po` during load; keep those deep-path shims minimal and whitelist-driven, but do not assume the `api` facade alone is enough.
# 开发命令

View File

@@ -5,7 +5,7 @@ from loguru import logger
from astrbot_sdk._shared_preferences import sp
from astrbot_sdk.api.basic import AstrBotConfig
from . import config, message, platform, utils
from . import agent, config, db, message, platform, provider, utils
class _HtmlRendererCompat:
@@ -31,11 +31,14 @@ html_renderer = _HtmlRendererCompat()
__all__ = [
"AstrBotConfig",
"agent",
"config",
"db",
"html_renderer",
"logger",
"message",
"platform",
"provider",
"sp",
"utils",
]

View File

@@ -0,0 +1,5 @@
"""旧版 ``astrbot.core.agent`` 兼容入口。"""
from .message import TextPart
__all__ = ["TextPart"]

View File

@@ -0,0 +1,13 @@
"""旧版 ``astrbot.core.agent.message`` 兼容入口。"""
from __future__ import annotations
from dataclasses import dataclass
@dataclass(slots=True)
class TextPart:
text: str
__all__ = ["TextPart"]

View File

@@ -0,0 +1,5 @@
"""旧版 ``astrbot.core.db`` 兼容入口。"""
from .po import Personality
__all__ = ["Personality"]

View File

@@ -0,0 +1,5 @@
"""旧版 ``astrbot.core.db.po`` 兼容入口。"""
from astrbot.api.provider import Personality
__all__ = ["Personality"]

View File

@@ -0,0 +1,24 @@
"""旧版 ``astrbot.core.provider`` 兼容入口。"""
from .entities import (
LLMResponse,
Personality,
ProviderMetaData,
ProviderRequest,
ProviderType,
RerankResult,
)
from .provider import EmbeddingProvider, Provider, RerankProvider, STTProvider
__all__ = [
"EmbeddingProvider",
"LLMResponse",
"Personality",
"Provider",
"ProviderMetaData",
"ProviderRequest",
"ProviderType",
"RerankProvider",
"RerankResult",
"STTProvider",
]

View File

@@ -0,0 +1,29 @@
"""旧版 ``astrbot.core.provider.entities`` 兼容入口。"""
from __future__ import annotations
from dataclasses import dataclass
from astrbot.api.provider import (
LLMResponse,
Personality,
ProviderMetaData,
ProviderRequest,
ProviderType,
)
@dataclass(slots=True)
class RerankResult:
index: int
relevance_score: float
__all__ = [
"LLMResponse",
"Personality",
"ProviderMetaData",
"ProviderRequest",
"ProviderType",
"RerankResult",
]

View File

@@ -0,0 +1,61 @@
"""旧版 ``astrbot.core.provider.provider`` 兼容入口。"""
from __future__ import annotations
from typing import Any
from .entities import ProviderMetaData
class Provider:
"""旧版 Provider 基类占位。"""
async def text_chat(self, *args, **kwargs): # pragma: no cover - compat stub
raise NotImplementedError("compat facade does not implement core providers")
def meta(self) -> ProviderMetaData: # pragma: no cover - compat stub
raise NotImplementedError("compat facade does not implement core providers")
def get_model(self) -> str: # pragma: no cover - compat stub
raise NotImplementedError("compat facade does not implement core providers")
class STTProvider(Provider):
pass
class EmbeddingProvider(Provider):
async def get_embeddings(self, texts: list[str]) -> list[list[float]]:
raise NotImplementedError("compat facade does not implement embeddings")
async def get_embeddings_batch(
self,
texts: list[str],
*,
batch_size: int = 16,
tasks_limit: int = 3,
max_retries: int = 3,
progress_callback: Any | None = None,
) -> list[list[float]]:
raise NotImplementedError("compat facade does not implement embeddings")
def get_dim(self) -> int:
raise NotImplementedError("compat facade does not implement embeddings")
class RerankProvider(Provider):
async def rerank(
self,
query: str,
documents: list[str],
top_n: int | None = None,
):
raise NotImplementedError("compat facade does not implement rerank")
__all__ = [
"EmbeddingProvider",
"Provider",
"RerankProvider",
"STTProvider",
]

View File

@@ -1,5 +1,37 @@
"""旧版 ``astrbot.core.utils`` 兼容入口。"""
from .astrbot_path import (
get_astrbot_backups_path,
get_astrbot_config_path,
get_astrbot_data_path,
get_astrbot_knowledge_base_path,
get_astrbot_path,
get_astrbot_plugin_data_path,
get_astrbot_plugin_path,
get_astrbot_root,
get_astrbot_site_packages_path,
get_astrbot_skills_path,
get_astrbot_t2i_templates_path,
get_astrbot_temp_path,
get_astrbot_webchat_path,
)
from .session_waiter import SessionController, SessionWaiter, session_waiter
__all__ = ["SessionController", "SessionWaiter", "session_waiter"]
__all__ = [
"SessionController",
"SessionWaiter",
"get_astrbot_backups_path",
"get_astrbot_config_path",
"get_astrbot_data_path",
"get_astrbot_knowledge_base_path",
"get_astrbot_path",
"get_astrbot_plugin_data_path",
"get_astrbot_plugin_path",
"get_astrbot_root",
"get_astrbot_site_packages_path",
"get_astrbot_skills_path",
"get_astrbot_t2i_templates_path",
"get_astrbot_temp_path",
"get_astrbot_webchat_path",
"session_waiter",
]

View File

@@ -0,0 +1,73 @@
"""旧版 ``astrbot.core.utils.astrbot_path`` 兼容入口。"""
from __future__ import annotations
import os
from pathlib import Path
def get_astrbot_path() -> str:
"""返回当前兼容 SDK 的项目根路径。"""
return str(Path(__file__).resolve().parents[4])
def get_astrbot_root() -> str:
"""返回 AstrBot 运行根目录。
旧版优先读取 ``ASTRBOT_ROOT``否则默认当前工作目录。compat 层保持
这个约定,方便旧插件继续把数据写到 ``<root>/data`` 下。
"""
root = os.environ.get("ASTRBOT_ROOT")
if root:
return str(Path(root).resolve())
return str(Path.cwd().resolve())
def _data_child(*parts: str) -> str:
return str(Path(get_astrbot_data_path(), *parts).resolve())
def get_astrbot_data_path() -> str:
return str(Path(get_astrbot_root(), "data").resolve())
def get_astrbot_config_path() -> str:
return _data_child("config")
def get_astrbot_plugin_path() -> str:
return _data_child("plugins")
def get_astrbot_plugin_data_path() -> str:
return _data_child("plugin_data")
def get_astrbot_t2i_templates_path() -> str:
return _data_child("t2i_templates")
def get_astrbot_webchat_path() -> str:
return _data_child("webchat")
def get_astrbot_temp_path() -> str:
return _data_child("temp")
def get_astrbot_skills_path() -> str:
return _data_child("skills")
def get_astrbot_site_packages_path() -> str:
return _data_child("site-packages")
def get_astrbot_knowledge_base_path() -> str:
return _data_child("knowledge_base")
def get_astrbot_backups_path() -> str:
return _data_child("backups")

View File

@@ -17,6 +17,16 @@
"known_unsupported": [
"依赖 playwright 时会退回纯文本帮助输出"
]
},
{
"name": "self_learning",
"repo": "https://github.com/NickCharlie/astrbot_plugin_self_learning.git",
"command": "learning_status",
"event_text": "learning_status",
"expected_text": "自学习插件状态报告",
"known_unsupported": [
"矩阵当前只验证管理命令,未覆盖需要真实 provider/persona 系统的深层能力"
]
}
]
}

View File

@@ -287,6 +287,39 @@ class TestAstrbotImportAlias:
with pytest.raises(NotImplementedError, match="register_platform_adapter"):
register_platform_adapter()
def test_legacy_astrbot_core_utils_astrbot_path_imports(self):
"""astrbot.core.utils.astrbot_path should expose legacy path helpers."""
from astrbot.core.utils.astrbot_path import (
get_astrbot_data_path,
get_astrbot_temp_path,
)
assert callable(get_astrbot_data_path)
assert callable(get_astrbot_temp_path)
def test_legacy_astrbot_core_provider_imports(self):
"""astrbot.core.provider old import paths should remain available."""
from astrbot.core.provider.entities import ProviderType, RerankResult
from astrbot.core.provider.provider import (
EmbeddingProvider,
Provider,
RerankProvider,
)
assert Provider is not None
assert EmbeddingProvider is not None
assert RerankProvider is not None
assert ProviderType.CHAT_COMPLETION.value == "chat_completion"
assert RerankResult(index=1, relevance_score=0.5).index == 1
def test_legacy_astrbot_core_agent_and_db_imports(self):
"""astrbot.core.agent/db old import paths should remain available."""
from astrbot.core.agent.message import TextPart
from astrbot.core.db.po import Personality
assert TextPart(text="hi").text == "hi"
assert Personality is not None
def test_legacy_astrbot_event_filter_module_exports(self):
"""astrbot.api.event.filter should be importable from the old module path."""
from astrbot.api.event.filter import EventMessageType, command, llm_tool