{"record":{"id":"fe3ea240b57a31a8","repo":"agentscope-ai/agentscope","slug":"type-self-name-must-be-called-with-keyword","errorCode":null,"errorMessage":"{type(self).__name__} must be called with keyword arguments only, but got {len(args)} positional argument(s).","messagePattern":"(.+?) must be called with keyword arguments only, but got (.+?) positional argument\\(s\\)\\.","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"src/agentscope/tool/_base.py","lineNumber":196,"sourceCode":"        **kwargs: Any,\n    ) -> ToolChunk | AsyncGenerator[ToolChunk, None]:\n        \"\"\"Invoke the tool, layering any registered middlewares around\n        :meth:`call`.\n\n        Tools are always invoked with keyword arguments only. ``*args`` is\n        accepted in the signature solely to stay Liskov-compatible with\n        subclasses that override ``__call__`` with their own positional\n        parameters; any positional argument actually passed here is rejected\n        (raising :exc:`TypeError`) so it fails loudly instead of being silently\n        dropped.\n\n        Middlewares are applied in an onion fashion: the first registered\n        middleware is the outermost layer and runs its pre-logic before\n        any inner layers, then its post-logic after all inner layers\n        have completed.\n        \"\"\"\n        if args:\n            raise TypeError(\n                f\"{type(self).__name__} must be called with keyword arguments \"\n                f\"only, but got {len(args)} positional argument(s).\",\n            )\n        # ``getattr`` with a default so the no-middleware path keeps working\n        # even if a subclass overrides ``__init__`` without calling\n        # ``super().__init__()``.\n        middlewares = getattr(self, \"_middlewares\", [])\n        if not middlewares:\n            if inspect.isasyncgenfunction(self.call):\n                return self.call(**kwargs)\n            return await self.call(**kwargs)\n\n        async def execute_chain(\n            index: int = 0,\n            **chain_kwargs: Any,\n        ) -> AsyncGenerator[ToolChunk, None]:\n            \"\"\"Execute the tool middleware chain.\"\"\"\n            if index >= len(middlewares):","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/agentscope-ai/agentscope/blob/e90f1c7592896cc95f6e5ee506194f533378247d/src/agentscope/tool/_base.py#L178-L214","documentation":"Tool.__call__ enforces keyword-only invocation: any positional arguments raise TypeError with their count. Tool arguments must be passed by name so they can be mapped to the tool schema.","triggerScenarios":"my_tool('hello') or my_tool('a', 'b') instead of my_tool(query='hello'); also *args unpacking of a tuple instead of **kwargs of a dict.","commonSituations":"Refactoring from a plain function to a Tool wrapper and keeping positional call sites; dynamic invocation using *args instead of **kwargs.","solutions":["Pass every argument by keyword: my_tool(query='hello')","Change dynamic invocation to **kwargs: my_tool(**params_dict)","Update tests/call sites after wrapping functions as tools"],"exampleFix":"# before\nawait my_tool('hello', 5)\n# after\nawait my_tool(query='hello', limit=5)","handlingStrategy":"validation","validationCode":"assert not args, 'tools accept keyword arguments only'","typeGuard":null,"tryCatchPattern":"try:\n    await tool(*args)\nexcept TypeError as e:\n    if 'keyword arguments' in str(e):\n        await tool(**dict(zip(param_names, args)))\n    else: raise","preventionTips":["Always invoke tools with **kwargs","When wrapping functions into tools, update call sites to keyword style"],"tags":["tool","keyword-arguments","typeerror"],"backgroundTag":"positional-args-not-allowed","analyzedSha":"e90f1c7592896cc95f6e5ee506194f533378247d","analyzedAt":"2026-08-28T18:24:12.087Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}