From 913c896598e2a5d02fc4253684fff3b9c718e6cf Mon Sep 17 00:00:00 2001 From: Christophe Bornet Date: Fri, 11 Apr 2025 22:26:33 +0200 Subject: [PATCH] core: Add ruff rules FBT001 and FBT002 (#30695) Add ruff rules [FBT001](https://docs.astral.sh/ruff/rules/boolean-type-hint-positional-argument/) and [FBT002](https://docs.astral.sh/ruff/rules/boolean-default-value-positional-argument/). Mostly `noqa`s to not introduce breaking changes and possible non-breaking fixes have already been done in a [previous PR](https://github.com/langchain-ai/langchain/pull/29424). These rules will prevent new violations to happen. --- libs/core/langchain_core/callbacks/base.py | 28 ++++++++++++++--- libs/core/langchain_core/callbacks/manager.py | 4 +-- libs/core/langchain_core/exceptions.py | 2 +- libs/core/langchain_core/globals.py | 4 +-- libs/core/langchain_core/load/load.py | 2 +- libs/core/langchain_core/messages/ai.py | 1 + libs/core/langchain_core/messages/base.py | 5 ++- libs/core/langchain_core/prompts/chat.py | 14 +++++++-- libs/core/langchain_core/prompts/few_shot.py | 2 ++ libs/core/langchain_core/prompts/image.py | 5 ++- libs/core/langchain_core/prompts/string.py | 5 ++- libs/core/langchain_core/runnables/graph.py | 2 +- .../langchain_core/runnables/graph_png.py | 2 +- libs/core/langchain_core/tools/simple.py | 2 +- libs/core/langchain_core/tools/structured.py | 4 +-- libs/core/langchain_core/tracers/context.py | 2 +- .../core/langchain_core/tracers/evaluation.py | 2 +- libs/core/langchain_core/utils/mustache.py | 12 ++++--- libs/core/langchain_core/utils/pydantic.py | 4 +-- libs/core/pyproject.toml | 2 -- libs/core/tests/unit_tests/pydantic_utils.py | 8 +++-- libs/core/tests/unit_tests/test_tools.py | 31 +++++++++++-------- 22 files changed, 96 insertions(+), 47 deletions(-) diff --git a/libs/core/langchain_core/callbacks/base.py b/libs/core/langchain_core/callbacks/base.py index 787a85562cc..1a792521f07 100644 --- a/libs/core/langchain_core/callbacks/base.py +++ b/libs/core/langchain_core/callbacks/base.py @@ -988,7 +988,11 @@ class BaseCallbackManager(CallbackManagerMixin): """Whether the callback manager is async.""" return False - def add_handler(self, handler: BaseCallbackHandler, inherit: bool = True) -> None: + def add_handler( + self, + handler: BaseCallbackHandler, + inherit: bool = True, # noqa: FBT001,FBT002 + ) -> None: """Add a handler to the callback manager. Args: @@ -1012,7 +1016,9 @@ class BaseCallbackManager(CallbackManagerMixin): self.inheritable_handlers.remove(handler) def set_handlers( - self, handlers: list[BaseCallbackHandler], inherit: bool = True + self, + handlers: list[BaseCallbackHandler], + inherit: bool = True, # noqa: FBT001,FBT002 ) -> None: """Set handlers as the only handlers on the callback manager. @@ -1025,7 +1031,11 @@ class BaseCallbackManager(CallbackManagerMixin): for handler in handlers: self.add_handler(handler, inherit=inherit) - def set_handler(self, handler: BaseCallbackHandler, inherit: bool = True) -> None: + def set_handler( + self, + handler: BaseCallbackHandler, + inherit: bool = True, # noqa: FBT001,FBT002 + ) -> None: """Set handler as the only handler on the callback manager. Args: @@ -1034,7 +1044,11 @@ class BaseCallbackManager(CallbackManagerMixin): """ self.set_handlers([handler], inherit=inherit) - def add_tags(self, tags: list[str], inherit: bool = True) -> None: + def add_tags( + self, + tags: list[str], + inherit: bool = True, # noqa: FBT001,FBT002 + ) -> None: """Add tags to the callback manager. Args: @@ -1058,7 +1072,11 @@ class BaseCallbackManager(CallbackManagerMixin): self.tags.remove(tag) self.inheritable_tags.remove(tag) - def add_metadata(self, metadata: dict[str, Any], inherit: bool = True) -> None: + def add_metadata( + self, + metadata: dict[str, Any], + inherit: bool = True, # noqa: FBT001,FBT002 + ) -> None: """Add metadata to the callback manager. Args: diff --git a/libs/core/langchain_core/callbacks/manager.py b/libs/core/langchain_core/callbacks/manager.py index 98f66c34536..8c721252485 100644 --- a/libs/core/langchain_core/callbacks/manager.py +++ b/libs/core/langchain_core/callbacks/manager.py @@ -1547,7 +1547,7 @@ class CallbackManager(BaseCallbackManager): cls, inheritable_callbacks: Callbacks = None, local_callbacks: Callbacks = None, - verbose: bool = False, + verbose: bool = False, # noqa: FBT001,FBT002 inheritable_tags: Optional[list[str]] = None, local_tags: Optional[list[str]] = None, inheritable_metadata: Optional[dict[str, Any]] = None, @@ -2073,7 +2073,7 @@ class AsyncCallbackManager(BaseCallbackManager): cls, inheritable_callbacks: Callbacks = None, local_callbacks: Callbacks = None, - verbose: bool = False, + verbose: bool = False, # noqa: FBT001,FBT002 inheritable_tags: Optional[list[str]] = None, local_tags: Optional[list[str]] = None, inheritable_metadata: Optional[dict[str, Any]] = None, diff --git a/libs/core/langchain_core/exceptions.py b/libs/core/langchain_core/exceptions.py index a2acfc9b644..cde6d6ff8ce 100644 --- a/libs/core/langchain_core/exceptions.py +++ b/libs/core/langchain_core/exceptions.py @@ -26,7 +26,7 @@ class OutputParserException(ValueError, LangChainException): # noqa: N818 error: Any, observation: Optional[str] = None, llm_output: Optional[str] = None, - send_to_llm: bool = False, + send_to_llm: bool = False, # noqa: FBT001,FBT002 ): """Create an OutputParserException. diff --git a/libs/core/langchain_core/globals.py b/libs/core/langchain_core/globals.py index 47480f85e2b..c30cdf88048 100644 --- a/libs/core/langchain_core/globals.py +++ b/libs/core/langchain_core/globals.py @@ -16,7 +16,7 @@ _debug: bool = False _llm_cache: Optional["BaseCache"] = None -def set_verbose(value: bool) -> None: +def set_verbose(value: bool) -> None: # noqa: FBT001 """Set a new value for the `verbose` global setting. Args: @@ -89,7 +89,7 @@ def get_verbose() -> bool: return _verbose or old_verbose -def set_debug(value: bool) -> None: +def set_debug(value: bool) -> None: # noqa: FBT001 """Set a new value for the `debug` global setting. Args: diff --git a/libs/core/langchain_core/load/load.py b/libs/core/langchain_core/load/load.py index b141933b8b3..e7c4cadbedc 100644 --- a/libs/core/langchain_core/load/load.py +++ b/libs/core/langchain_core/load/load.py @@ -52,7 +52,7 @@ class Reviver: self, secrets_map: Optional[dict[str, str]] = None, valid_namespaces: Optional[list[str]] = None, - secrets_from_env: bool = True, + secrets_from_env: bool = True, # noqa: FBT001,FBT002 additional_import_mappings: Optional[ dict[tuple[str, ...], tuple[str, ...]] ] = None, diff --git a/libs/core/langchain_core/messages/ai.py b/libs/core/langchain_core/messages/ai.py index 40adbde7356..6c2a08e6db1 100644 --- a/libs/core/langchain_core/messages/ai.py +++ b/libs/core/langchain_core/messages/ai.py @@ -235,6 +235,7 @@ class AIMessage(BaseMessage): return values + @override def pretty_repr(self, html: bool = False) -> str: """Return a pretty representation of the message. diff --git a/libs/core/langchain_core/messages/base.py b/libs/core/langchain_core/messages/base.py index 11b32e77f35..f68c49739da 100644 --- a/libs/core/langchain_core/messages/base.py +++ b/libs/core/langchain_core/messages/base.py @@ -122,7 +122,10 @@ class BaseMessage(Serializable): prompt = ChatPromptTemplate(messages=[self]) # type: ignore[call-arg] return prompt + other - def pretty_repr(self, html: bool = False) -> str: + def pretty_repr( + self, + html: bool = False, # noqa: FBT001,FBT002 + ) -> str: """Get a pretty representation of the message. Args: diff --git a/libs/core/langchain_core/prompts/chat.py b/libs/core/langchain_core/prompts/chat.py index d649ec2fbc3..720d7db6942 100644 --- a/libs/core/langchain_core/prompts/chat.py +++ b/libs/core/langchain_core/prompts/chat.py @@ -102,7 +102,10 @@ class BaseMessagePromptTemplate(Serializable, ABC): List of input variables. """ - def pretty_repr(self, html: bool = False) -> str: + def pretty_repr( + self, + html: bool = False, # noqa: FBT001,FBT002 + ) -> str: """Human-readable representation. Args: @@ -270,6 +273,7 @@ class MessagesPlaceholder(BaseMessagePromptTemplate): """ return [self.variable_name] if not self.optional else [] + @override def pretty_repr(self, html: bool = False) -> str: """Human-readable representation. @@ -406,6 +410,7 @@ class BaseStringMessagePromptTemplate(BaseMessagePromptTemplate, ABC): """ return self.prompt.input_variables + @override def pretty_repr(self, html: bool = False) -> str: """Human-readable representation. @@ -675,6 +680,7 @@ class _StringImageMessagePromptTemplate(BaseMessagePromptTemplate): content=content, additional_kwargs=self.additional_kwargs ) + @override def pretty_repr(self, html: bool = False) -> str: """Human-readable representation. @@ -777,7 +783,10 @@ class BaseChatPromptTemplate(BasePromptTemplate, ABC): """Async format kwargs into a list of messages.""" return self.format_messages(**kwargs) - def pretty_repr(self, html: bool = False) -> str: + def pretty_repr( + self, + html: bool = False, # noqa: FBT001,FBT002 + ) -> str: """Human-readable representation. Args: @@ -1331,6 +1340,7 @@ class ChatPromptTemplate(BaseChatPromptTemplate): """ raise NotImplementedError + @override def pretty_repr(self, html: bool = False) -> str: """Human-readable representation. diff --git a/libs/core/langchain_core/prompts/few_shot.py b/libs/core/langchain_core/prompts/few_shot.py index 4552d7a5b11..8b6651c6a0b 100644 --- a/libs/core/langchain_core/prompts/few_shot.py +++ b/libs/core/langchain_core/prompts/few_shot.py @@ -10,6 +10,7 @@ from pydantic import ( Field, model_validator, ) +from typing_extensions import override from langchain_core.example_selectors import BaseExampleSelector from langchain_core.messages import BaseMessage, get_buffer_string @@ -453,6 +454,7 @@ class FewShotChatMessagePromptTemplate( messages = await self.aformat_messages(**kwargs) return get_buffer_string(messages) + @override def pretty_repr(self, html: bool = False) -> str: """Return a pretty representation of the prompt template. diff --git a/libs/core/langchain_core/prompts/image.py b/libs/core/langchain_core/prompts/image.py index 96a137dbe15..a648c6afc92 100644 --- a/libs/core/langchain_core/prompts/image.py +++ b/libs/core/langchain_core/prompts/image.py @@ -133,7 +133,10 @@ class ImagePromptTemplate(BasePromptTemplate[ImageURL]): """ return await run_in_executor(None, self.format, **kwargs) - def pretty_repr(self, html: bool = False) -> str: + def pretty_repr( + self, + html: bool = False, # noqa: FBT001,FBT002 + ) -> str: """Return a pretty representation of the prompt. Args: diff --git a/libs/core/langchain_core/prompts/string.py b/libs/core/langchain_core/prompts/string.py index d2a0f86c201..e63d54dbbde 100644 --- a/libs/core/langchain_core/prompts/string.py +++ b/libs/core/langchain_core/prompts/string.py @@ -293,7 +293,10 @@ class StringPromptTemplate(BasePromptTemplate, ABC): """ return StringPromptValue(text=await self.aformat(**kwargs)) - def pretty_repr(self, html: bool = False) -> str: + def pretty_repr( + self, + html: bool = False, # noqa: FBT001,FBT002 + ) -> str: """Get a pretty representation of the prompt. Args: diff --git a/libs/core/langchain_core/runnables/graph.py b/libs/core/langchain_core/runnables/graph.py index 5b548ba4498..47fb550290b 100644 --- a/libs/core/langchain_core/runnables/graph.py +++ b/libs/core/langchain_core/runnables/graph.py @@ -359,7 +359,7 @@ class Graph: source: Node, target: Node, data: Optional[Stringifiable] = None, - conditional: bool = False, + conditional: bool = False, # noqa: FBT001,FBT002 ) -> Edge: """Add an edge to the graph and return it. diff --git a/libs/core/langchain_core/runnables/graph_png.py b/libs/core/langchain_core/runnables/graph_png.py index 116bb28917e..2c2663c4e89 100644 --- a/libs/core/langchain_core/runnables/graph_png.py +++ b/libs/core/langchain_core/runnables/graph_png.py @@ -104,7 +104,7 @@ class PngDrawer: source: str, target: str, label: Optional[str] = None, - conditional: bool = False, + conditional: bool = False, # noqa: FBT001,FBT002 ) -> None: """Adds an edge to the graph. diff --git a/libs/core/langchain_core/tools/simple.py b/libs/core/langchain_core/tools/simple.py index 21f1e61f5be..e450dafc58c 100644 --- a/libs/core/langchain_core/tools/simple.py +++ b/libs/core/langchain_core/tools/simple.py @@ -142,7 +142,7 @@ class Tool(BaseTool): func: Optional[Callable], name: str, # We keep these required to support backwards compatibility description: str, - return_direct: bool = False, + return_direct: bool = False, # noqa: FBT001,FBT002 args_schema: Optional[ArgsSchema] = None, coroutine: Optional[ Callable[..., Awaitable[Any]] diff --git a/libs/core/langchain_core/tools/structured.py b/libs/core/langchain_core/tools/structured.py index e8ebea8be35..4e202dfea3a 100644 --- a/libs/core/langchain_core/tools/structured.py +++ b/libs/core/langchain_core/tools/structured.py @@ -122,9 +122,9 @@ class StructuredTool(BaseTool): coroutine: Optional[Callable[..., Awaitable[Any]]] = None, name: Optional[str] = None, description: Optional[str] = None, - return_direct: bool = False, + return_direct: bool = False, # noqa: FBT001,FBT002 args_schema: Optional[ArgsSchema] = None, - infer_schema: bool = True, + infer_schema: bool = True, # noqa: FBT001,FBT002 *, response_format: Literal["content", "content_and_artifact"] = "content", parse_docstring: bool = False, diff --git a/libs/core/langchain_core/tracers/context.py b/libs/core/langchain_core/tracers/context.py index 30a40a3441f..466bc2f9f11 100644 --- a/libs/core/langchain_core/tracers/context.py +++ b/libs/core/langchain_core/tracers/context.py @@ -186,7 +186,7 @@ _configure_hooks: list[ def register_configure_hook( context_var: ContextVar[Optional[Any]], - inheritable: bool, + inheritable: bool, # noqa: FBT001 handle_class: Optional[type[BaseCallbackHandler]] = None, env_var: Optional[str] = None, ) -> None: diff --git a/libs/core/langchain_core/tracers/evaluation.py b/libs/core/langchain_core/tracers/evaluation.py index df73f20683f..6595aa6141a 100644 --- a/libs/core/langchain_core/tracers/evaluation.py +++ b/libs/core/langchain_core/tracers/evaluation.py @@ -62,7 +62,7 @@ class EvaluatorCallbackHandler(BaseTracer): evaluators: Sequence[langsmith.RunEvaluator], client: Optional[langsmith.Client] = None, example_id: Optional[Union[UUID, str]] = None, - skip_unfinished: bool = True, + skip_unfinished: bool = True, # noqa: FBT001,FBT002 project_name: Optional[str] = "evaluators", max_concurrency: Optional[int] = None, **kwargs: Any, diff --git a/libs/core/langchain_core/utils/mustache.py b/libs/core/langchain_core/utils/mustache.py index d2324f0dbcd..a7ba8883b0d 100644 --- a/libs/core/langchain_core/utils/mustache.py +++ b/libs/core/langchain_core/utils/mustache.py @@ -68,7 +68,7 @@ def grab_literal(template: str, l_del: str) -> tuple[str, str]: def l_sa_check( template: str, # noqa: ARG001 literal: str, - is_standalone: bool, + is_standalone: bool, # noqa: FBT001 ) -> bool: """Do a preliminary check to see if a tag could be a standalone. @@ -91,7 +91,11 @@ def l_sa_check( return False -def r_sa_check(template: str, tag_type: str, is_standalone: bool) -> bool: +def r_sa_check( + template: str, + tag_type: str, + is_standalone: bool, # noqa: FBT001 +) -> bool: """Do a final check to see if a tag could be a standalone. Args: @@ -422,8 +426,8 @@ def render( def_ldel: str = "{{", def_rdel: str = "}}", scopes: Optional[Scopes] = None, - warn: bool = False, - keep: bool = False, + warn: bool = False, # noqa: FBT001,FBT002 + keep: bool = False, # noqa: FBT001,FBT002 ) -> str: """Render a mustache template. diff --git a/libs/core/langchain_core/utils/pydantic.py b/libs/core/langchain_core/utils/pydantic.py index 2078527dd91..5b36933dceb 100644 --- a/libs/core/langchain_core/utils/pydantic.py +++ b/libs/core/langchain_core/utils/pydantic.py @@ -420,7 +420,7 @@ def _create_root_model( def schema( cls: type[BaseModel], - by_alias: bool = True, + by_alias: bool = True, # noqa: FBT001,FBT002 ref_template: str = DEFAULT_REF_TEMPLATE, ) -> dict[str, Any]: # Complains about schema not being defined in superclass @@ -432,7 +432,7 @@ def _create_root_model( def model_json_schema( cls: type[BaseModel], - by_alias: bool = True, + by_alias: bool = True, # noqa: FBT001,FBT002 ref_template: str = DEFAULT_REF_TEMPLATE, schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema, mode: JsonSchemaMode = "validation", diff --git a/libs/core/pyproject.toml b/libs/core/pyproject.toml index c7f6f6a72ad..27166b28765 100644 --- a/libs/core/pyproject.toml +++ b/libs/core/pyproject.toml @@ -100,8 +100,6 @@ ignore = [ "ANN401", "BLE", "ERA", - "FBT001", - "FBT002", "PLR2004", "RUF", "SLF", diff --git a/libs/core/tests/unit_tests/pydantic_utils.py b/libs/core/tests/unit_tests/pydantic_utils.py index 3ef18520a0e..8e7a9a078d7 100644 --- a/libs/core/tests/unit_tests/pydantic_utils.py +++ b/libs/core/tests/unit_tests/pydantic_utils.py @@ -105,7 +105,7 @@ def _remove_additionalproperties_from_untyped_dicts(schema: dict) -> dict[str, A """ def _remove_dict_additional_props( - obj: Union[dict[str, Any], list[Any]], inside_properties: bool = False + obj: Union[dict[str, Any], list[Any]], *, inside_properties: bool = False ) -> None: if isinstance(obj, dict): if ( @@ -120,11 +120,13 @@ def _remove_additionalproperties_from_untyped_dicts(schema: dict) -> dict[str, A # We are "inside_properties" if the *current* key is "properties", # or if we were already inside properties in the caller. next_inside_properties = inside_properties or (key == "properties") - _remove_dict_additional_props(value, next_inside_properties) + _remove_dict_additional_props( + value, inside_properties=next_inside_properties + ) elif isinstance(obj, list): for item in obj: - _remove_dict_additional_props(item, inside_properties) + _remove_dict_additional_props(item, inside_properties=inside_properties) _remove_dict_additional_props(schema, inside_properties=False) return schema diff --git a/libs/core/tests/unit_tests/test_tools.py b/libs/core/tests/unit_tests/test_tools.py index 48fd209cdd3..db1b09ffa3c 100644 --- a/libs/core/tests/unit_tests/test_tools.py +++ b/libs/core/tests/unit_tests/test_tools.py @@ -119,10 +119,10 @@ class _MockStructuredTool(BaseTool): args_schema: type[BaseModel] = _MockSchema description: str = "A Structured Tool" - def _run(self, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str: + def _run(self, *, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str: return f"{arg1} {arg2} {arg3}" - async def _arun(self, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str: + async def _arun(self, *, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str: raise NotImplementedError @@ -146,11 +146,13 @@ def test_misannotated_base_tool_raises_error() -> None: args_schema: BaseModel = _MockSchema # type: ignore[assignment] description: str = "A Structured Tool" - def _run(self, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str: + def _run( + self, *, arg1: int, arg2: bool, arg3: Optional[dict] = None + ) -> str: return f"{arg1} {arg2} {arg3}" async def _arun( - self, arg1: int, arg2: bool, arg3: Optional[dict] = None + self, *, arg1: int, arg2: bool, arg3: Optional[dict] = None ) -> str: raise NotImplementedError @@ -163,11 +165,11 @@ def test_forward_ref_annotated_base_tool_accepted() -> None: args_schema: "type[BaseModel]" = _MockSchema description: str = "A Structured Tool" - def _run(self, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str: + def _run(self, *, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str: return f"{arg1} {arg2} {arg3}" async def _arun( - self, arg1: int, arg2: bool, arg3: Optional[dict] = None + self, *, arg1: int, arg2: bool, arg3: Optional[dict] = None ) -> str: raise NotImplementedError @@ -180,11 +182,11 @@ def test_subclass_annotated_base_tool_accepted() -> None: args_schema: type[_MockSchema] = _MockSchema description: str = "A Structured Tool" - def _run(self, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str: + def _run(self, *, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str: return f"{arg1} {arg2} {arg3}" async def _arun( - self, arg1: int, arg2: bool, arg3: Optional[dict] = None + self, *, arg1: int, arg2: bool, arg3: Optional[dict] = None ) -> str: raise NotImplementedError @@ -197,14 +199,14 @@ def test_decorator_with_specified_schema() -> None: """Test that manually specified schemata are passed through to the tool.""" @tool(args_schema=_MockSchema) - def tool_func(arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str: + def tool_func(*, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str: return f"{arg1} {arg2} {arg3}" assert isinstance(tool_func, BaseTool) assert tool_func.args_schema == _MockSchema @tool(args_schema=cast("ArgsSchema", _MockSchemaV1)) - def tool_func_v1(arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str: + def tool_func_v1(*, arg1: int, arg2: bool, arg3: Optional[dict] = None) -> str: return f"{arg1} {arg2} {arg3}" assert isinstance(tool_func_v1, BaseTool) @@ -216,7 +218,7 @@ def test_decorated_function_schema_equivalent() -> None: @tool def structured_tool_input( - arg1: int, arg2: bool, arg3: Optional[dict] = None + *, arg1: int, arg2: bool, arg3: Optional[dict] = None ) -> str: """Return the arguments directly.""" return f"{arg1} {arg2} {arg3}" @@ -1397,14 +1399,17 @@ class _MockStructuredToolWithRawOutput(BaseTool): response_format: Literal["content_and_artifact"] = "content_and_artifact" def _run( - self, arg1: int, arg2: bool, arg3: Optional[dict] = None + self, + arg1: int, + arg2: bool, # noqa: FBT001 + arg3: Optional[dict] = None, ) -> tuple[str, dict]: return f"{arg1} {arg2}", {"arg1": arg1, "arg2": arg2, "arg3": arg3} @tool("structured_api", response_format="content_and_artifact") def _mock_structured_tool_with_artifact( - arg1: int, arg2: bool, arg3: Optional[dict] = None + *, arg1: int, arg2: bool, arg3: Optional[dict] = None ) -> tuple[str, dict]: """A Structured Tool.""" return f"{arg1} {arg2}", {"arg1": arg1, "arg2": arg2, "arg3": arg3}