mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-05 12:20:17 +08:00
Compare commits
6 Commits
codex/fix-
...
codex/keep
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
319b2570be | ||
|
|
a7533aacda | ||
|
|
46a846b88b | ||
|
|
2d98d38078 | ||
|
|
1b0f5cb0d3 | ||
|
|
cdfb0bdf91 |
@@ -278,10 +278,11 @@ async def _apply_kb(
|
||||
)
|
||||
if not kb_result:
|
||||
return
|
||||
if req.system_prompt is not None:
|
||||
req.system_prompt += (
|
||||
f"\n\n[Related Knowledge Base Results]:\n{kb_result}"
|
||||
)
|
||||
req.extra_user_content_parts.append(
|
||||
TextPart(
|
||||
text=f"[Related Knowledge Base Results]:\n{kb_result}",
|
||||
).mark_as_temp()
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.error("Error occurred while retrieving knowledge base: %s", exc)
|
||||
else:
|
||||
@@ -456,10 +457,10 @@ async def _ensure_persona_and_skills(
|
||||
cfg: dict,
|
||||
plugin_context: Context,
|
||||
event: AstrMessageEvent,
|
||||
) -> set[str] | None:
|
||||
) -> None:
|
||||
"""Ensure persona and skills are applied to the request's system prompt or user prompt."""
|
||||
if not req.conversation:
|
||||
return None
|
||||
return
|
||||
|
||||
(
|
||||
persona_id,
|
||||
@@ -526,13 +527,11 @@ async def _ensure_persona_and_skills(
|
||||
|
||||
# inject toolset in the persona
|
||||
if (persona and persona.get("tools") is None) or not persona:
|
||||
persona_allowed_tools = None
|
||||
persona_toolset = tmgr.get_full_tool_set()
|
||||
for tool in list(persona_toolset):
|
||||
if not tool.active:
|
||||
persona_toolset.remove_tool(tool.name)
|
||||
else:
|
||||
persona_allowed_tools = {str(tool_name) for tool_name in persona["tools"]}
|
||||
persona_toolset = ToolSet()
|
||||
if persona["tools"]:
|
||||
for tool_name in persona["tools"]:
|
||||
@@ -613,7 +612,6 @@ async def _ensure_persona_and_skills(
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return persona_allowed_tools
|
||||
|
||||
|
||||
async def _request_img_caption(
|
||||
@@ -946,13 +944,12 @@ async def _decorate_llm_request(
|
||||
plugin_context: Context,
|
||||
config: MainAgentBuildConfig,
|
||||
provider: Provider | None = None,
|
||||
) -> set[str] | None:
|
||||
) -> None:
|
||||
cfg = config.provider_settings or plugin_context.get_config(
|
||||
umo=event.unified_msg_origin
|
||||
).get("provider_settings", {})
|
||||
|
||||
_apply_prompt_prefix(req, cfg)
|
||||
persona_allowed_tools = None
|
||||
|
||||
main_provider_supports_image = provider is not None and _provider_supports_modality(
|
||||
provider, "image"
|
||||
@@ -961,9 +958,7 @@ async def _decorate_llm_request(
|
||||
quote_images_already_captioned = False
|
||||
|
||||
if req.conversation:
|
||||
persona_allowed_tools = await _ensure_persona_and_skills(
|
||||
req, cfg, plugin_context, event
|
||||
)
|
||||
await _ensure_persona_and_skills(req, cfg, plugin_context, event)
|
||||
|
||||
if img_cap_prov_id and req.image_urls and not main_provider_supports_image:
|
||||
await _ensure_img_caption(
|
||||
@@ -992,7 +987,6 @@ async def _decorate_llm_request(
|
||||
tz = plugin_context.get_config().get("timezone")
|
||||
_append_system_reminders(event, req, cfg, tz)
|
||||
_apply_workspace_extra_prompt(event, req)
|
||||
return persona_allowed_tools
|
||||
|
||||
|
||||
def _plugin_tool_fix(event: AstrMessageEvent, req: ProviderRequest) -> None:
|
||||
@@ -1521,9 +1515,7 @@ async def build_main_agent(
|
||||
else:
|
||||
return None
|
||||
|
||||
persona_allowed_tools = await _decorate_llm_request(
|
||||
event, req, plugin_context, config, provider=provider
|
||||
)
|
||||
await _decorate_llm_request(event, req, plugin_context, config, provider=provider)
|
||||
|
||||
await _apply_kb(event, req, plugin_context, config)
|
||||
|
||||
@@ -1559,11 +1551,6 @@ async def build_main_agent(
|
||||
)
|
||||
)
|
||||
|
||||
if persona_allowed_tools is not None and req.func_tool:
|
||||
req.func_tool.tools = [
|
||||
tool for tool in req.func_tool.tools if tool.name in persona_allowed_tools
|
||||
]
|
||||
|
||||
fallback_providers = _get_fallback_chat_providers(
|
||||
provider, plugin_context, config.provider_settings
|
||||
)
|
||||
|
||||
22
changelogs/v4.26.0-beta.10.md
Normal file
22
changelogs/v4.26.0-beta.10.md
Normal file
@@ -0,0 +1,22 @@
|
||||
- [更新日志(简体中文)](#chinese)
|
||||
- [Changelog(English)](#english)
|
||||
|
||||
<a id="chinese"></a>
|
||||
|
||||
## What's Changed
|
||||
|
||||
### 修复
|
||||
|
||||
- 恢复 WebUI 在接口返回 401 时跳转登录页,避免会话失效后停留在异常状态。([#8903](https://github.com/AstrBotDevs/AstrBot/pull/8903))
|
||||
- 保持 Core 版本与 WebUI 静态资源版本同步,修复打包或升级后可能加载旧 dist、资源版本错配的问题。([#8901](https://github.com/AstrBotDevs/AstrBot/pull/8901))
|
||||
- 将知识库上下文作为临时 user 内容注入,修复模型请求中知识库上下文角色不准确的问题。([#8904](https://github.com/AstrBotDevs/AstrBot/pull/8904))
|
||||
|
||||
<a id="english"></a>
|
||||
|
||||
## What's Changed (EN)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Restored the WebUI login redirect when API requests return 401, preventing expired sessions from staying in a broken state. ([#8903](https://github.com/AstrBotDevs/AstrBot/pull/8903))
|
||||
- Kept Core and WebUI static asset versions in sync, fixing stale dist loading and asset version mismatches after packaging or upgrades. ([#8901](https://github.com/AstrBotDevs/AstrBot/pull/8901))
|
||||
- Injected knowledge base context as temporary user content, fixing the role used for knowledge context in model requests. ([#8904](https://github.com/AstrBotDevs/AstrBot/pull/8904))
|
||||
@@ -48,6 +48,55 @@ function attachAxiosHeaders(config: InternalAxiosRequestConfig) {
|
||||
}
|
||||
|
||||
function normalizeAxiosError(error: AxiosError) {
|
||||
if (error.response?.status === 401) {
|
||||
let requestPath = '';
|
||||
try {
|
||||
const url = error.config?.url || '';
|
||||
const baseURL = error.config?.baseURL;
|
||||
const resolvedUrl =
|
||||
url && baseURL && !/^([a-z][a-z\d+\-.]*:)?\/\//i.test(url)
|
||||
? `${baseURL.replace(/\/+$/, '')}/${url.replace(/^\/+/, '')}`
|
||||
: url;
|
||||
const requestUrl = new URL(resolvedUrl || '/', window.location.origin);
|
||||
if (requestUrl.origin === window.location.origin) {
|
||||
requestPath = requestUrl.pathname;
|
||||
}
|
||||
} catch {
|
||||
requestPath = '';
|
||||
}
|
||||
|
||||
const isAuthChallenge =
|
||||
[
|
||||
'/api/auth/login',
|
||||
'/api/auth/setup',
|
||||
'/api/auth/setup-status',
|
||||
'/api/v1/auth/login',
|
||||
'/api/v1/auth/setup',
|
||||
'/api/v1/auth/setup-status',
|
||||
].includes(requestPath) ||
|
||||
Boolean(
|
||||
(
|
||||
error.response.data as
|
||||
| { data?: { totp_required?: boolean } }
|
||||
| undefined
|
||||
)?.data?.totp_required,
|
||||
);
|
||||
|
||||
if (requestPath.startsWith('/api/') && !isAuthChallenge) {
|
||||
[
|
||||
'user',
|
||||
'token',
|
||||
'change_pwd_hint',
|
||||
'md5_pwd_hint',
|
||||
'password_upgrade_required',
|
||||
].forEach((key) => localStorage.removeItem(key));
|
||||
|
||||
if (!window.location.hash.startsWith('#/auth/login')) {
|
||||
window.location.hash = '/auth/login';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (error.response?.status === 429) {
|
||||
const data = error.response.data as { message?: string } | undefined;
|
||||
if (data?.message) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "AstrBot"
|
||||
version = "4.26.0-beta.9"
|
||||
version = "4.26.0-beta.10"
|
||||
description = "Easy-to-use multi-platform LLM chatbot and development framework"
|
||||
readme = "README.md"
|
||||
license = { text = "AGPL-3.0-or-later" }
|
||||
|
||||
@@ -8,6 +8,7 @@ import pytest
|
||||
|
||||
from astrbot.core import astr_main_agent as ama
|
||||
from astrbot.core.agent.mcp_client import MCPTool
|
||||
from astrbot.core.agent.message import Message, dump_messages_with_checkpoints
|
||||
from astrbot.core.agent.tool import FunctionTool, ToolSet
|
||||
from astrbot.core.conversation_mgr import Conversation
|
||||
from astrbot.core.message.components import File, Image, Plain, Reply, Video
|
||||
@@ -377,8 +378,18 @@ class TestApplyKb:
|
||||
):
|
||||
await module._apply_kb(mock_event, req, mock_context, config)
|
||||
|
||||
assert "[Related Knowledge Base Results]:" in req.system_prompt
|
||||
assert "KB result" in req.system_prompt
|
||||
assert req.system_prompt == "System prompt"
|
||||
assert len(req.extra_user_content_parts) == 1
|
||||
kb_part = req.extra_user_content_parts[0]
|
||||
assert kb_part.text == "[Related Knowledge Base Results]:\nKB result"
|
||||
|
||||
message = Message.model_validate(await req.assemble_context())
|
||||
assert isinstance(message.content, list)
|
||||
assert message.content[0].text == "test question"
|
||||
assert message.content[1].text == "[Related Knowledge Base Results]:\nKB result"
|
||||
assert dump_messages_with_checkpoints([message]) == [
|
||||
{"role": "user", "content": [{"type": "text", "text": "test question"}]}
|
||||
]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_apply_kb_with_agentic_mode(self, mock_event, mock_context):
|
||||
@@ -998,7 +1009,7 @@ class TestEnsurePersonaAndSkills:
|
||||
assert req.func_tool is not None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_persona_empty_tools_filters_late_builtin_tools(
|
||||
async def test_persona_empty_tools_keeps_late_builtin_tools(
|
||||
self, mock_event, mock_context, mock_provider
|
||||
):
|
||||
module = ama
|
||||
@@ -1006,6 +1017,7 @@ class TestEnsurePersonaAndSkills:
|
||||
mock_context.persona_manager.resolve_selected_persona = AsyncMock(
|
||||
return_value=("locked", persona, None, False)
|
||||
)
|
||||
mock_event.platform_meta.support_proactive_message = False
|
||||
mock_context.get_config.return_value = {
|
||||
"provider_settings": {
|
||||
"web_search": True,
|
||||
@@ -1019,6 +1031,7 @@ class TestEnsurePersonaAndSkills:
|
||||
"websearch_provider": "baidu_ai_search",
|
||||
},
|
||||
computer_use_runtime="none",
|
||||
add_cron_tools=False,
|
||||
)
|
||||
req = ProviderRequest(prompt="hello")
|
||||
req.conversation = MagicMock(persona_id="locked", history="[]")
|
||||
@@ -1041,9 +1054,52 @@ class TestEnsurePersonaAndSkills:
|
||||
)
|
||||
assert result is not None
|
||||
try:
|
||||
assert result.provider_request.func_tool is None or (
|
||||
result.provider_request.func_tool.empty()
|
||||
assert result.provider_request.func_tool is not None
|
||||
assert result.provider_request.func_tool.names() == ["web_search_baidu"]
|
||||
finally:
|
||||
if result.reset_coro:
|
||||
result.reset_coro.close()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_persona_empty_tools_keeps_local_runtime_builtin_tools(
|
||||
self, mock_event, mock_context, mock_provider
|
||||
):
|
||||
module = ama
|
||||
persona = {"name": "locked", "prompt": "No tools.", "tools": []}
|
||||
mock_context.persona_manager.resolve_selected_persona = AsyncMock(
|
||||
return_value=("locked", persona, None, False)
|
||||
)
|
||||
mock_event.platform_meta.support_proactive_message = False
|
||||
config = module.MainAgentBuildConfig(
|
||||
tool_call_timeout=60,
|
||||
computer_use_runtime="local",
|
||||
add_cron_tools=False,
|
||||
)
|
||||
req = ProviderRequest(prompt="hello")
|
||||
req.conversation = MagicMock(persona_id="locked", history="[]")
|
||||
|
||||
with (
|
||||
patch("astrbot.core.astr_main_agent.AgentRunner") as mock_runner_cls,
|
||||
patch("astrbot.core.astr_main_agent.AstrAgentContext"),
|
||||
):
|
||||
mock_runner = MagicMock()
|
||||
mock_runner.reset = AsyncMock()
|
||||
mock_runner_cls.return_value = mock_runner
|
||||
|
||||
result = await module.build_main_agent(
|
||||
event=mock_event,
|
||||
plugin_context=mock_context,
|
||||
config=config,
|
||||
provider=mock_provider,
|
||||
req=req,
|
||||
apply_reset=False,
|
||||
)
|
||||
assert result is not None
|
||||
try:
|
||||
assert result.provider_request.func_tool is not None
|
||||
tool_names = result.provider_request.func_tool.names()
|
||||
assert "astrbot_execute_shell" in tool_names
|
||||
assert "astrbot_execute_python" in tool_names
|
||||
finally:
|
||||
if result.reset_coro:
|
||||
result.reset_coro.close()
|
||||
|
||||
Reference in New Issue
Block a user