mirror of
https://github.com/dataelement/bisheng.git
synced 2026-08-30 17:58:00 +08:00
fix: include tool identity in assistant onboarding errors
This commit is contained in:
@@ -588,7 +588,7 @@ class AssistantService(BaseService, AssistantUtils):
|
||||
await tmp_agent.init_assistant()
|
||||
except Exception as e:
|
||||
logger.exception("online agent init failed")
|
||||
raise AssistantInitError(exception=e)
|
||||
raise AssistantInitError(exception=e, msg=f"Assistant onboarding failed: {e}") from e
|
||||
assistant.status = status
|
||||
AssistantDao.update_assistant(assistant)
|
||||
await telemetry_service.log_event(
|
||||
|
||||
@@ -28,6 +28,15 @@ from bisheng_langchain.gpts.load_tools import load_tools
|
||||
from bisheng_langchain.gpts.tools.api_tools.openapi import OpenApiTools
|
||||
|
||||
|
||||
class ToolInitializationError(RuntimeError):
|
||||
"""Add a safe tool identity to an initialization failure."""
|
||||
|
||||
def __init__(self, tool: GptsTools, cause: Exception):
|
||||
tool_id = getattr(tool, "id", None)
|
||||
tool_name = getattr(tool, "name", None) or getattr(tool, "tool_key", None) or "unknown"
|
||||
super().__init__(f'Tool "{tool_name}" (id={tool_id}) initialization failed: {type(cause).__name__}: {cause}')
|
||||
|
||||
|
||||
def wrapper_tool(func):
|
||||
@functools.wraps(func)
|
||||
async def inner(*args, **kwargs):
|
||||
@@ -223,6 +232,33 @@ class ToolExecutor(BaseTool):
|
||||
base_tool=tool_instance, tool=tool, app_id=app_id, app_name=app_name, app_type=app_type, user_id=user_id
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _init_by_tool_and_type_with_context(
|
||||
cls,
|
||||
tool: GptsTools,
|
||||
tool_type: GptsToolsType,
|
||||
*,
|
||||
app_id: str,
|
||||
app_name: str,
|
||||
app_type: ApplicationTypeEnum,
|
||||
user_id: int,
|
||||
**kwargs,
|
||||
) -> BaseTool:
|
||||
try:
|
||||
return cls._init_by_tool_and_type(
|
||||
tool=tool,
|
||||
tool_type=tool_type,
|
||||
app_id=app_id,
|
||||
app_name=app_name,
|
||||
app_type=app_type,
|
||||
user_id=user_id,
|
||||
**kwargs,
|
||||
)
|
||||
except ToolInitializationError:
|
||||
raise
|
||||
except Exception as exc:
|
||||
raise ToolInitializationError(tool, exc) from exc
|
||||
|
||||
@classmethod
|
||||
async def init_by_tool_id(
|
||||
cls,
|
||||
@@ -245,7 +281,7 @@ class ToolExecutor(BaseTool):
|
||||
if not tool_type:
|
||||
raise ValueError(f"Tool type with id {tool.type} not found.")
|
||||
await cls._ensure_use_permission_async(tool_type, user_id)
|
||||
return cls._init_by_tool_and_type(
|
||||
return cls._init_by_tool_and_type_with_context(
|
||||
tool=tool,
|
||||
tool_type=tool_type,
|
||||
app_id=app_id,
|
||||
@@ -276,7 +312,7 @@ class ToolExecutor(BaseTool):
|
||||
if enforce_permission:
|
||||
cls._ensure_use_permission_sync(tool_type, user_id)
|
||||
result.append(
|
||||
cls._init_by_tool_and_type(
|
||||
cls._init_by_tool_and_type_with_context(
|
||||
tool=tool,
|
||||
tool_type=tool_type,
|
||||
app_id=app_id,
|
||||
@@ -353,7 +389,7 @@ class ToolExecutor(BaseTool):
|
||||
raise ValueError(f"Tool type with id {tool.type} not found.")
|
||||
cls._ensure_use_permission_sync(tool_type, user_id)
|
||||
|
||||
return cls._init_by_tool_and_type(
|
||||
return cls._init_by_tool_and_type_with_context(
|
||||
tool=tool,
|
||||
tool_type=tool_type,
|
||||
app_id=app_id,
|
||||
|
||||
@@ -4,7 +4,7 @@ from unittest.mock import AsyncMock
|
||||
import pytest
|
||||
|
||||
from bisheng.common.constants.enums.telemetry import ApplicationTypeEnum
|
||||
from bisheng.tool.domain.services.executor import ToolExecutor
|
||||
from bisheng.tool.domain.services.executor import ToolExecutor, ToolInitializationError
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -15,35 +15,35 @@ async def test_init_by_tool_ids_can_skip_unauthorized_tools(monkeypatch):
|
||||
type_two = SimpleNamespace(id=20)
|
||||
|
||||
monkeypatch.setattr(
|
||||
'bisheng.tool.domain.services.executor.GptsToolsDao.aget_list_by_ids',
|
||||
"bisheng.tool.domain.services.executor.GptsToolsDao.aget_list_by_ids",
|
||||
AsyncMock(return_value=[tool_one, tool_two]),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
'bisheng.tool.domain.services.executor.GptsToolsDao.aget_all_tool_type',
|
||||
"bisheng.tool.domain.services.executor.GptsToolsDao.aget_all_tool_type",
|
||||
AsyncMock(return_value=[type_one, type_two]),
|
||||
)
|
||||
|
||||
async def ensure_permission(tool_type, user_id):
|
||||
if tool_type.id == 20:
|
||||
raise PermissionError('no tool permission')
|
||||
raise PermissionError("no tool permission")
|
||||
|
||||
monkeypatch.setattr(ToolExecutor, '_ensure_use_permission_async', ensure_permission)
|
||||
monkeypatch.setattr(ToolExecutor, "_ensure_use_permission_async", ensure_permission)
|
||||
monkeypatch.setattr(
|
||||
ToolExecutor,
|
||||
'_init_by_tool_and_type',
|
||||
lambda tool, tool_type, **kwargs: f'tool:{tool.id}',
|
||||
"_init_by_tool_and_type",
|
||||
lambda tool, tool_type, **kwargs: f"tool:{tool.id}",
|
||||
)
|
||||
|
||||
result = await ToolExecutor.init_by_tool_ids(
|
||||
[1, 2],
|
||||
app_id='assistant-1',
|
||||
app_name='assistant',
|
||||
app_id="assistant-1",
|
||||
app_name="assistant",
|
||||
app_type=ApplicationTypeEnum.ASSISTANT,
|
||||
user_id=7,
|
||||
skip_unauthorized=True,
|
||||
)
|
||||
|
||||
assert result == ['tool:1']
|
||||
assert result == ["tool:1"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -52,24 +52,91 @@ async def test_init_by_tool_ids_still_raises_by_default(monkeypatch):
|
||||
tool_type = SimpleNamespace(id=20)
|
||||
|
||||
monkeypatch.setattr(
|
||||
'bisheng.tool.domain.services.executor.GptsToolsDao.aget_list_by_ids',
|
||||
"bisheng.tool.domain.services.executor.GptsToolsDao.aget_list_by_ids",
|
||||
AsyncMock(return_value=[tool]),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
'bisheng.tool.domain.services.executor.GptsToolsDao.aget_all_tool_type',
|
||||
"bisheng.tool.domain.services.executor.GptsToolsDao.aget_all_tool_type",
|
||||
AsyncMock(return_value=[tool_type]),
|
||||
)
|
||||
|
||||
async def ensure_permission(tool_type, user_id):
|
||||
raise PermissionError('no tool permission')
|
||||
raise PermissionError("no tool permission")
|
||||
|
||||
monkeypatch.setattr(ToolExecutor, '_ensure_use_permission_async', ensure_permission)
|
||||
monkeypatch.setattr(ToolExecutor, "_ensure_use_permission_async", ensure_permission)
|
||||
|
||||
with pytest.raises(PermissionError):
|
||||
await ToolExecutor.init_by_tool_ids(
|
||||
[2],
|
||||
app_id='direct-tool-use',
|
||||
app_name='direct',
|
||||
app_id="direct-tool-use",
|
||||
app_name="direct",
|
||||
app_type=ApplicationTypeEnum.ASSISTANT,
|
||||
user_id=7,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_init_by_tool_ids_error_includes_tool_identity(monkeypatch):
|
||||
tool = SimpleNamespace(id=3, type=30, name="broken-api-tool")
|
||||
tool_type = SimpleNamespace(id=30)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"bisheng.tool.domain.services.executor.GptsToolsDao.aget_list_by_ids",
|
||||
AsyncMock(return_value=[tool]),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"bisheng.tool.domain.services.executor.GptsToolsDao.aget_all_tool_type",
|
||||
AsyncMock(return_value=[tool_type]),
|
||||
)
|
||||
monkeypatch.setattr(ToolExecutor, "_ensure_use_permission_async", AsyncMock())
|
||||
|
||||
def fail_initialization(*args, **kwargs):
|
||||
raise KeyError("properties")
|
||||
|
||||
monkeypatch.setattr(ToolExecutor, "_init_by_tool_and_type", fail_initialization)
|
||||
|
||||
with pytest.raises(ToolInitializationError) as exc_info:
|
||||
await ToolExecutor.init_by_tool_ids(
|
||||
[3],
|
||||
app_id="assistant-1",
|
||||
app_name="assistant",
|
||||
app_type=ApplicationTypeEnum.ASSISTANT,
|
||||
user_id=7,
|
||||
)
|
||||
|
||||
assert str(exc_info.value) == ("Tool \"broken-api-tool\" (id=3) initialization failed: KeyError: 'properties'")
|
||||
assert isinstance(exc_info.value.__cause__, KeyError)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_init_by_tool_id_error_includes_tool_identity(monkeypatch):
|
||||
tool = SimpleNamespace(id=4, type=40, name="single-broken-tool")
|
||||
tool_type = SimpleNamespace(id=40)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"bisheng.tool.domain.services.executor.GptsToolsDao.aget_one_tool",
|
||||
AsyncMock(return_value=tool),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"bisheng.tool.domain.services.executor.GptsToolsDao.aget_one_tool_type",
|
||||
AsyncMock(return_value=tool_type),
|
||||
)
|
||||
monkeypatch.setattr(ToolExecutor, "_ensure_use_permission_async", AsyncMock())
|
||||
|
||||
def fail_initialization(*args, **kwargs):
|
||||
raise ValueError("invalid configuration")
|
||||
|
||||
monkeypatch.setattr(ToolExecutor, "_init_by_tool_and_type", fail_initialization)
|
||||
|
||||
with pytest.raises(ToolInitializationError) as exc_info:
|
||||
await ToolExecutor.init_by_tool_id(
|
||||
tool_id=4,
|
||||
app_id="assistant-1",
|
||||
app_name="assistant",
|
||||
app_type=ApplicationTypeEnum.ASSISTANT,
|
||||
user_id=7,
|
||||
)
|
||||
|
||||
assert str(exc_info.value) == (
|
||||
'Tool "single-broken-tool" (id=4) initialization failed: ValueError: invalid configuration'
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user