{"record":{"id":"4632d82265095502","repo":"zylon-ai/private-gpt","slug":"path-r-is-not-a-tooleventadapter-subclass","errorCode":null,"errorMessage":"{path!r} is not a ToolEventAdapter subclass","messagePattern":"(.+?) is not a ToolEventAdapter subclass","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"private_gpt/components/tools/events/registry.py","lineNumber":24,"sourceCode":"from private_gpt.components.tools.events.adapters import (\n    ClientToolEventAdapter,\n    ServerToolEventAdapter,\n    ToolEventAdapter,\n)\nfrom private_gpt.settings.settings import settings\n\nif TYPE_CHECKING:\n    from private_gpt.components.chat.models.chat_config_models import ToolSpec\n\n\ndef load_tool_event_adapter_class(path: str) -> type[ToolEventAdapter]:\n    module_name, qualname = path.split(\":\", maxsplit=1)\n    module = importlib.import_module(module_name)\n    resolved: object = module\n    for attribute in qualname.split(\".\"):\n        resolved = getattr(resolved, attribute)\n    if not isinstance(resolved, type) or not issubclass(resolved, ToolEventAdapter):\n        raise TypeError(f\"{path!r} is not a ToolEventAdapter subclass\")\n    return resolved\n\n\ndef resolve_tool_event_adapter(tool_spec: ToolSpec) -> ToolEventAdapter:\n    mode = settings().code_execution.tools.server_tool_result_mode\n    if mode == \"client\":\n        return ClientToolEventAdapter()\n    adapter_cls = tool_spec.event_adapter\n    if adapter_cls is None:\n        return (\n            ServerToolEventAdapter()\n            if tool_spec.runtime == \"server\"\n            else ClientToolEventAdapter()\n        )\n    return adapter_cls()\n","sourceCodeStart":6,"sourceCodeEnd":40,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/components/tools/events/registry.py#L6-L40","documentation":"Raised by load_tool_event_adapter_class when a ToolSpec's event_adapter import path ('module.qualname:Class.Path') resolves to an object that is either not a class or not a subclass of ToolEventAdapter. The registry dynamically imports the module, walks the dotted qualname via getattr, then type-checks the result.","triggerScenarios":"Setting tool_spec.event_adapter to a path like 'my_pkg.adapters:MyAdapter' where MyAdapter does not inherit from ToolEventAdapter, points to a function/instance instead of a class, contains a typo in module or attribute name (raises AttributeError/ModuleNotFoundError first), or the class was moved/renamed between versions.","commonSituations":"Custom event adapters registered by string in tool specs or config; refactoring that renamed a class without updating the adapter path string; importing an abstract base or a mixin that never subclassed ToolEventAdapter.","solutions":["Make the referenced class explicitly inherit from ToolEventAdapter (from private_gpt.components.tools.events.registry or its defining module)","Fix the 'module:qualname' string: exact module path, exact class name, no trailing whitespace","If the class moved, update the string to the new module/qualname","Verify in a REPL: import the module and assert issubclass(target, ToolEventAdapter)"],"exampleFix":"# before\ntool_spec.event_adapter = \"my_pkg.adapters:my_adapter_instance\"\n# after\nfrom private_gpt.components.tools.events.registry import ToolEventAdapter\n\nclass MyAdapter(ToolEventAdapter):\n    ...\n\ntool_spec.event_adapter = \"my_pkg.adapters:MyAdapter\"","handlingStrategy":"type-guard","validationCode":"import importlib\nfrom private_gpt.components.tools.events.registry import ToolEventAdapter\n\ndef is_valid_adapter_path(path: str) -> bool:\n    try:\n        module_name, qualname = path.split(\":\", maxsplit=1)\n        resolved = importlib.import_module(module_name)\n        for attr in qualname.split(\".\"):\n            resolved = getattr(resolved, attr)\n    except (ValueError, ImportError, AttributeError):\n        return False\n    return isinstance(resolved, type) and issubclass(resolved, ToolEventAdapter)","typeGuard":"from private_gpt.components.tools.events.registry import ToolEventAdapter\n\ndef is_tool_event_adapter_cls(obj: object) -> bool:\n    return isinstance(obj, type) and issubclass(obj, ToolEventAdapter) and obj is not ToolEventAdapter","tryCatchPattern":"try:\n    adapter_cls = load_tool_event_adapter_class(path)\nexcept (TypeError, ImportError, AttributeError) as e:\n    raise ConfigurationError(f\"bad event_adapter {path!r}: {e}\") from e","preventionTips":["Define adapter paths as constants next to the classes they name, never as free-form config strings","Add a startup check that validates every registered event_adapter path via the same import logic","Run issubclass checks in unit tests for custom adapters"],"tags":["dynamic-import","registry","type-error","configuration"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}