Repository: UKPLab/sentence-transformers

Issue:
Parse arguments with HfArgumentParser
I was using HfArgumentParser to parse `SentenceTransformerTrainingArguments` and met the following error:

```
parser = HfArgumentParser(SentenceTransformerTrainingArguments)`
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/admin/.local/lib/python3.10/site-packages/transformers/hf_argparser.py", line 137, in __init__
    self._add_dataclass_arguments(dtype)
  File "/home/admin/.local/lib/python3.10/site-packages/transformers/hf_argparser.py", line 264, in _add_dataclass_arguments
    self._parse_dataclass_field(parser, field)
  File "/home/admin/.local/lib/python3.10/site-packages/transformers/hf_argparser.py", line 172, in _parse_dataclass_field
    field.type.__args__[0] if isinstance(None, field.type.__args__[1]) else field.type.__args__[1]
  File "/opt/conda/envs/python3.10/lib/python3.10/typing.py", line 994, in __instancecheck__
    return self.__subclasscheck__(type(obj))
  File "/opt/conda/envs/python3.10/lib/python3.10/typing.py", line 997, in __subclasscheck__
    raise TypeError("Subscripted generics cannot be used with"
TypeError: Subscripted generics cannot be used with class and instance checks
```

This error is due to that `SentenceTransformerTrainingArguments.prompts` has a type of `dict[str, dict[str, str]] | dict[str, str] | str | None`, while in `_parse_dataclass_field`, `field.type.__args__[1]` got a generic type of `dict[str, str]`, and `isinstance(None, dict[str, str])` caused the error. Could you fix this? thanks.

Discussion:
tomaarsen: Test failures are clear, I'll go for `Optional[str]`.

Patch:
diff --git a/sentence_transformers/training_args.py b/sentence_transformers/training_args.py
index 68a659e..da9a65d 100644
--- a/sentence_transformers/training_args.py
+++ b/sentence_transformers/training_args.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import logging
 from dataclasses import dataclass, field
+from typing import Optional, Union
 
 from transformers import TrainingArguments as TransformersTrainingArguments
 from transformers.training_args import ParallelMode
@@ -170,11 +171,18 @@ class SentenceTransformerTrainingArguments(TransformersTrainingArguments):
             for valid options. Defaults to ``MultiDatasetBatchSamplers.PROPORTIONAL``.
     """
 
-    prompts: dict[str, dict[str, str]] | dict[str, str] | str | None = None
-    batch_sampler: BatchSamplers | str = field(
+    prompts: Optional[str] = field(  # noqa: UP007
+        default=None,
+        metadata={
+            "help": "The prompts to use for each column in the datasets. "
+            "Either 1) a single string prompt, 2) a mapping of column names to prompts, 3) a mapping of dataset names "
+            "to prompts, or 4) a mapping of dataset names to a mapping of column names to prompts."
+        },
+    )
+    batch_sampler: Union[BatchSamplers, str] = field(  # noqa: UP007
         default=BatchSamplers.BATCH_SAMPLER, metadata={"help": "The batch sampler to use."}
     )
-    multi_dataset_batch_sampler: MultiDatasetBatchSamplers | str = field(
+    multi_dataset_batch_sampler: Union[MultiDatasetBatchSamplers, str] = field(  # noqa: UP007
         default=MultiDatasetBatchSamplers.PROPORTIONAL, metadata={"help": "The multi-dataset batch sampler to use."}
     )
 


Test patch:
diff --git a/tests/test_training_args.py b/tests/test_training_args.py
new file mode 100644
index 0000000..21bb6fc
--- /dev/null
+++ b/tests/test_training_args.py
@@ -0,0 +1,27 @@
+from __future__ import annotations
+
+import json
+
+from transformers import HfArgumentParser
+
+from sentence_transformers import SentenceTransformerTrainingArguments
+
+
+def test_hf_argument_parser():
+    # See https://github.com/UKPLab/sentence-transformers/issues/3090;
+    # Ensure that the HfArgumentParser can be used to parse SentenceTransformerTrainingArguments.
+    parser = HfArgumentParser(SentenceTransformerTrainingArguments)
+    args = parser.parse_args(
+        args=[
+            "--output_dir",
+            "test_output_dir",
+            "--prompts",
+            '{"query_column": "query_prompt", "positive_column": "positive_prompt", "negative_column": "negative_prompt"}',
+        ]
+    )
+    assert args.output_dir == "test_output_dir"
+    assert json.loads(args.prompts) == {
+        "query_column": "query_prompt",
+        "positive_column": "positive_prompt",
+        "negative_column": "negative_prompt",
+    }