switch-trust-cli 1.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. switch_trust/__init__.py +0 -0
  2. switch_trust/cli/__init__.py +0 -0
  3. switch_trust/cli/__main__.py +3 -0
  4. switch_trust/cli/builtin_config.json +879 -0
  5. switch_trust/cli/console.py +127 -0
  6. switch_trust/cli/eval_cli.py +905 -0
  7. switch_trust/cli/file_filter.py +172 -0
  8. switch_trust/cli/init_cli.py +156 -0
  9. switch_trust/cli/main.py +384 -0
  10. switch_trust/cli/output_formatters.py +482 -0
  11. switch_trust/cli/rich_observer.py +89 -0
  12. switch_trust/cli/runner.py +274 -0
  13. switch_trust/cli/scan_cli.py +314 -0
  14. switch_trust/cli/telemetry.py +430 -0
  15. switch_trust/cli/utils.py +158 -0
  16. switch_trust/cli/version.py +1 -0
  17. switch_trust/data/detector_prompts/llm01_adversarial.txt +59 -0
  18. switch_trust/data/detector_prompts/llm01_fixed.txt +6 -0
  19. switch_trust/data/detector_prompts/llm02_adversarial.txt +79 -0
  20. switch_trust/data/detector_prompts/llm02_fixed.txt +6 -0
  21. switch_trust/data/detector_prompts/llm05_adversarial.txt +67 -0
  22. switch_trust/data/detector_prompts/llm05_fixed.txt +14 -0
  23. switch_trust/data/detector_prompts/llm06_adversarial.txt +139 -0
  24. switch_trust/data/detector_prompts/llm06_fixed.txt +13 -0
  25. switch_trust/data/detector_prompts/llm07_adversarial.txt +189 -0
  26. switch_trust/data/detector_prompts/llm07_fixed.txt +12 -0
  27. switch_trust/data/detector_prompts/llm09_adversarial.txt +83 -0
  28. switch_trust/data/detector_prompts/llm09_fixed.txt +13 -0
  29. switch_trust/data/llm01_prompt_injection.csv +6708 -0
  30. switch_trust/data/llm02_sensitive_information.csv +1069 -0
  31. switch_trust/data/llm05_unsafe_output.csv +3882 -0
  32. switch_trust/data/llm06_excessive_agency.csv +778 -0
  33. switch_trust/data/llm07_system_prompt_leakage.csv +971 -0
  34. switch_trust/data/llm09_hallucination-goals.csv +32419 -0
  35. switch_trust/data/llm09_hallucination.csv +599 -0
  36. switch_trust/data/pii_leakage.csv +706 -0
  37. switch_trust/data/secret_leakage.csv +1380 -0
  38. switch_trust/eval/__init__.py +0 -0
  39. switch_trust/eval/common/__init__.py +0 -0
  40. switch_trust/eval/common/converter_anthropic.py +172 -0
  41. switch_trust/eval/common/converter_genai.py +171 -0
  42. switch_trust/eval/common/converter_openai.py +198 -0
  43. switch_trust/eval/common/log.py +116 -0
  44. switch_trust/eval/common/reference.py +25 -0
  45. switch_trust/eval/common/schema.py +190 -0
  46. switch_trust/eval/common/utils.py +119 -0
  47. switch_trust/eval/core/__init__.py +0 -0
  48. switch_trust/eval/core/detectors/__init__.py +0 -0
  49. switch_trust/eval/core/detectors/detector.py +25 -0
  50. switch_trust/eval/core/detectors/detector_garak.py +112 -0
  51. switch_trust/eval/core/detectors/detector_model.py +123 -0
  52. switch_trust/eval/core/detectors/detector_model_adversarial.py +91 -0
  53. switch_trust/eval/core/detectors/detector_pii.py +67 -0
  54. switch_trust/eval/core/detectors/detector_secret.py +88 -0
  55. switch_trust/eval/core/detectors/detector_topic_guard.py +63 -0
  56. switch_trust/eval/core/detectors/detector_toxicity.py +78 -0
  57. switch_trust/eval/core/eval/__init__.py +0 -0
  58. switch_trust/eval/core/eval/eval_creator.py +166 -0
  59. switch_trust/eval/core/eval/evaluation.py +112 -0
  60. switch_trust/eval/core/eval/evaluation_adversarial.py +412 -0
  61. switch_trust/eval/core/eval/evaluation_garak_module.py +45 -0
  62. switch_trust/eval/core/eval/evaluation_garak_probe.py +308 -0
  63. switch_trust/eval/core/eval/evaluation_message_list.py +46 -0
  64. switch_trust/eval/core/eval/evaluation_multi.py +276 -0
  65. switch_trust/eval/core/eval/evaluation_single.py +89 -0
  66. switch_trust/eval/core/eval/evaluation_single_prompt.py +62 -0
  67. switch_trust/eval/core/eval/evaluation_topic_guard.py +305 -0
  68. switch_trust/eval/core/eval/metric_conciseness.py +179 -0
  69. switch_trust/eval/core/eval/metric_factual_accuracy.py +312 -0
  70. switch_trust/eval/core/eval/metric_instruction_adherence.py +177 -0
  71. switch_trust/eval/core/eval/metric_tone.py +180 -0
  72. switch_trust/eval/core/eval/metric_toxicity.py +146 -0
  73. switch_trust/eval/core/eval/observer.py +137 -0
  74. switch_trust/eval/core/message/__init__.py +0 -0
  75. switch_trust/eval/core/message/message_collection.py +37 -0
  76. switch_trust/eval/core/message/message_collection_csv.py +44 -0
  77. switch_trust/eval/core/message/message_collection_garak.py +46 -0
  78. switch_trust/eval/core/message/message_collection_memory.py +34 -0
  79. switch_trust/eval/core/models/__init__.py +0 -0
  80. switch_trust/eval/core/models/generator_model.py +421 -0
  81. switch_trust/eval/core/models/model.py +172 -0
  82. switch_trust/eval/core/models/model_adk.py +186 -0
  83. switch_trust/eval/core/models/model_anthropic.py +79 -0
  84. switch_trust/eval/core/models/model_anthropic_agent.py +104 -0
  85. switch_trust/eval/core/models/model_gemini.py +146 -0
  86. switch_trust/eval/core/models/model_generic_http.py +120 -0
  87. switch_trust/eval/core/models/model_http.py +135 -0
  88. switch_trust/eval/core/models/model_huggingface.py +75 -0
  89. switch_trust/eval/core/models/model_langserve.py +96 -0
  90. switch_trust/eval/core/models/model_litellm.py +52 -0
  91. switch_trust/eval/core/models/model_ollama.py +65 -0
  92. switch_trust/eval/core/models/model_openai.py +71 -0
  93. switch_trust/eval/core/models/model_openai_agent.py +92 -0
  94. switch_trust/eval/core/models/model_openai_compatible.py +66 -0
  95. switch_trust/eval/core/models/model_per_endpoint_limiter.py +80 -0
  96. switch_trust/eval/core/models/model_retry.py +204 -0
  97. switch_trust/eval/core/models/model_sync_wrapper.py +38 -0
  98. switch_trust/eval/core/models/model_vertex_agent_runtime.py +315 -0
  99. switch_trust/eval/core/models/response_schema.py +148 -0
  100. switch_trust/eval/core/optional_deps.py +57 -0
  101. switch_trust/eval/db/__init__.py +0 -0
  102. switch_trust/eval/db/base/__init__.py +0 -0
  103. switch_trust/eval/db/base/common_types.py +16 -0
  104. switch_trust/eval/db/base/detectors/__init__.py +0 -0
  105. switch_trust/eval/db/base/detectors/detector_helpers.py +88 -0
  106. switch_trust/eval/db/base/detectors/detector_repository.py +96 -0
  107. switch_trust/eval/db/base/detectors/detector_types.py +77 -0
  108. switch_trust/eval/db/base/eval/__init__.py +0 -0
  109. switch_trust/eval/db/base/eval/eval_helpers.py +186 -0
  110. switch_trust/eval/db/base/eval/eval_repository.py +120 -0
  111. switch_trust/eval/db/base/eval/eval_run.py +142 -0
  112. switch_trust/eval/db/base/eval/eval_types.py +127 -0
  113. switch_trust/eval/db/base/eval/model_eval_repository.py +79 -0
  114. switch_trust/eval/db/base/eval/model_eval_run_repository.py +72 -0
  115. switch_trust/eval/db/base/eval/model_eval_run_result_repository.py +42 -0
  116. switch_trust/eval/db/base/eval/model_eval_run_result_types.py +21 -0
  117. switch_trust/eval/db/base/eval/model_eval_run_types.py +78 -0
  118. switch_trust/eval/db/base/eval/model_eval_types.py +69 -0
  119. switch_trust/eval/db/base/message/__init__.py +0 -0
  120. switch_trust/eval/db/base/message/message_collection_helpers.py +50 -0
  121. switch_trust/eval/db/base/message/message_collection_repository.py +95 -0
  122. switch_trust/eval/db/base/message/message_collection_types.py +87 -0
  123. switch_trust/eval/db/base/models/__init__.py +0 -0
  124. switch_trust/eval/db/base/models/model_helpers.py +253 -0
  125. switch_trust/eval/db/base/models/model_repository.py +104 -0
  126. switch_trust/eval/db/base/models/model_types.py +159 -0
  127. switch_trust/eval/db/json/__init__.py +0 -0
  128. switch_trust/eval/db/json/repository_json.py +559 -0
  129. switch_trust/scan/__init__.py +12 -0
  130. switch_trust/scan/agent_scanner.py +812 -0
  131. switch_trust/scan/config/agent_opengrep_rules.yaml +533 -0
  132. switch_trust/scan/config/agent_taxonomy.json +517 -0
  133. switch_trust/scan/config/agentic_cvss_mapping.yaml +136 -0
  134. switch_trust/scan/config/compliance_mappings.json +110 -0
  135. switch_trust/scan/config/triage_prompt.txt +326 -0
  136. switch_trust/scan/constants.py +15 -0
  137. switch_trust/scan/exception_classification.py +179 -0
  138. switch_trust/scan/llm_provider.py +330 -0
  139. switch_trust/scan/opengrep_resolver.py +12 -0
  140. switch_trust/scan/reasoner.py +602 -0
  141. switch_trust/scan/schema.py +315 -0
  142. switch_trust/scan/scorer.py +372 -0
  143. switch_trust/scan/static_scanner.py +681 -0
  144. switch_trust/scan/taxonomy.py +121 -0
  145. switch_trust/scan/tool_dispatcher.py +773 -0
  146. switch_trust/scan/trace_logger.py +118 -0
  147. switch_trust/scan/trace_logger_file.py +143 -0
  148. switch_trust/scan/trace_logger_log.py +100 -0
  149. switch_trust/scan/triage.py +614 -0
  150. switch_trust/schema.py +79 -0
  151. switch_trust/secret_anonymizer.py +72 -0
  152. switch_trust_cli-1.7.0.dist-info/METADATA +495 -0
  153. switch_trust_cli-1.7.0.dist-info/RECORD +157 -0
  154. switch_trust_cli-1.7.0.dist-info/WHEEL +5 -0
  155. switch_trust_cli-1.7.0.dist-info/entry_points.txt +2 -0
  156. switch_trust_cli-1.7.0.dist-info/licenses/LICENSE +210 -0
  157. switch_trust_cli-1.7.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,69 @@
1
+ from dataclasses import dataclass, field
2
+ from datetime import datetime
3
+ from enum import Enum
4
+
5
+ from dataclasses_json import dataclass_json
6
+
7
+ from switch_trust.eval.common.reference import Reference, ReferenceType
8
+ from switch_trust.eval.common.utils import datetime_config, generate_id, now_utc
9
+ from switch_trust.eval.db.base.common_types import SortDirection
10
+
11
+
12
+ @dataclass_json
13
+ @dataclass
14
+ class DbModelEvaluation:
15
+ """Links a Model to an Evaluation with a weight."""
16
+
17
+ model_id: str
18
+ evaluation_id: str
19
+ name: str
20
+ id: str = field(default_factory=generate_id)
21
+ created: datetime = field(
22
+ default_factory=now_utc,
23
+ metadata=datetime_config,
24
+ )
25
+ description: str | None = None
26
+ tags: dict[str, str] = field(default_factory=dict)
27
+ weight: float = 0.5
28
+
29
+ def get_ref(self) -> Reference:
30
+ return Reference(
31
+ self.id,
32
+ self.name,
33
+ ReferenceType.MODEL_EVALUATION_SUITE,
34
+ self.description,
35
+ )
36
+
37
+
38
+ class ModelEvaluationSortField(str, Enum):
39
+ NAME = "name"
40
+ CREATED = "created"
41
+ WEIGHT = "weight"
42
+
43
+
44
+ @dataclass_json
45
+ @dataclass
46
+ class ModelEvaluationSortOrder:
47
+ field: ModelEvaluationSortField = ModelEvaluationSortField.NAME
48
+ direction: SortDirection = SortDirection.ASC
49
+
50
+
51
+ @dataclass_json
52
+ @dataclass
53
+ class ModelEvaluationListItem:
54
+ """A model-evaluation assignment with resolved references."""
55
+
56
+ config: DbModelEvaluation
57
+ model_ref: Reference | None = None
58
+ evaluation_ref: Reference | None = None
59
+
60
+
61
+ @dataclass_json
62
+ @dataclass
63
+ class ModelEvaluationListView:
64
+ """Paginated list of model-evaluation assignments."""
65
+
66
+ items: list[ModelEvaluationListItem] = field(
67
+ default_factory=list,
68
+ )
69
+ total: int = 0
File without changes
@@ -0,0 +1,50 @@
1
+ from switch_trust.eval.common.schema import Message
2
+ from switch_trust.eval.core.message.message_collection import MessageCollection
3
+ from switch_trust.eval.db.base.message.message_collection_types import (
4
+ DbMessageCollection,
5
+ MessageCollectionType,
6
+ )
7
+
8
+
9
+ def create_message_collection(db_collection: DbMessageCollection) -> MessageCollection:
10
+ """Create a MessageCollection instance from a DbMessageCollection."""
11
+ if db_collection.type == MessageCollectionType.IN_MEMORY:
12
+ from switch_trust.eval.core.message.message_collection_memory import ( # noqa: PLC0415 - mirrors the eval/db original
13
+ InMemoryMessageCollection,
14
+ )
15
+
16
+ if db_collection.prompts:
17
+ return InMemoryMessageCollection().from_strings(
18
+ db_collection.prompts,
19
+ )
20
+
21
+ messages = None
22
+ if db_collection.messages:
23
+ messages = [Message.from_dict(m) for m in db_collection.messages]
24
+ return InMemoryMessageCollection(messages=messages)
25
+
26
+ elif db_collection.type == MessageCollectionType.CSV:
27
+ from switch_trust.eval.core.message.message_collection_csv import ( # noqa: PLC0415 - mirrors the eval/db original
28
+ CsvMessageCollection,
29
+ )
30
+
31
+ if not db_collection.filename:
32
+ raise ValueError("filename is required for csv collections")
33
+ return CsvMessageCollection(
34
+ filename=db_collection.filename,
35
+ column=db_collection.column or "prompt",
36
+ )
37
+
38
+ elif db_collection.type == MessageCollectionType.GARAK:
39
+ from switch_trust.eval.core.message.message_collection_garak import ( # noqa: PLC0415 - mirrors the eval/db original
40
+ GarakMessageCollection,
41
+ )
42
+
43
+ if not db_collection.probe_name:
44
+ raise ValueError("probe_name is required for garak collections")
45
+ return GarakMessageCollection(
46
+ probe_name=db_collection.probe_name,
47
+ )
48
+
49
+ else:
50
+ raise ValueError(f"unknown collection type: {db_collection.type}")
@@ -0,0 +1,95 @@
1
+ """
2
+ Abstract interface for a repository of MessageCollections.
3
+
4
+ A MessageCollectionRepository manages multiple collections,
5
+ providing CRUD operations over them.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from abc import ABC, abstractmethod
11
+
12
+ from switch_trust.eval.core.message.message_collection import MessageCollection
13
+ from switch_trust.eval.db.base.message.message_collection_types import (
14
+ DbMessageCollection,
15
+ MessageCollectionListView,
16
+ MessageCollectionSortOrder,
17
+ )
18
+
19
+
20
+ class MessageCollectionRepository(ABC):
21
+ """Repository that manages multiple MessageCollections."""
22
+
23
+ @abstractmethod
24
+ def list(self) -> list[DbMessageCollection]:
25
+ """Return all collections in the repository."""
26
+ pass
27
+
28
+ @abstractmethod
29
+ def search(
30
+ self,
31
+ query: str | None = None,
32
+ types: list[str] | None = None,
33
+ order: MessageCollectionSortOrder | None = None,
34
+ offset: int = 0,
35
+ limit: int = 50,
36
+ ) -> MessageCollectionListView:
37
+ """Search collections with pagination and sorting."""
38
+ pass
39
+
40
+ @abstractmethod
41
+ def get(self, id: str) -> DbMessageCollection:
42
+ """Return the collection descriptor with the given id.
43
+
44
+ Raises KeyError if the collection does not exist.
45
+ """
46
+ pass
47
+
48
+ @abstractmethod
49
+ def get_message_collection(
50
+ self,
51
+ id: str,
52
+ ) -> MessageCollection:
53
+ """Return a MessageCollection instance for the given id.
54
+
55
+ Raises KeyError if the collection does not exist.
56
+ """
57
+ pass
58
+
59
+ @abstractmethod
60
+ def create(
61
+ self,
62
+ db_collection: DbMessageCollection,
63
+ ) -> DbMessageCollection:
64
+ """Persist a new collection.
65
+
66
+ Raises ValueError if a collection with that name already
67
+ exists.
68
+ """
69
+ pass
70
+
71
+ @abstractmethod
72
+ def update(
73
+ self,
74
+ db_collection: DbMessageCollection,
75
+ ) -> DbMessageCollection:
76
+ """Replace an existing collection.
77
+
78
+ The collection is identified by ``db_collection.id``.
79
+
80
+ Raises KeyError if the collection does not exist.
81
+ """
82
+ pass
83
+
84
+ @abstractmethod
85
+ def delete(self, id: str) -> None:
86
+ """Delete the collection with the given id.
87
+
88
+ Raises KeyError if the collection does not exist.
89
+ """
90
+ pass
91
+
92
+ @abstractmethod
93
+ def clear(self) -> None:
94
+ """Remove all collections from the repository."""
95
+ pass
@@ -0,0 +1,87 @@
1
+ from dataclasses import dataclass, field
2
+ from datetime import datetime
3
+ from enum import Enum
4
+
5
+ from dataclasses_json import dataclass_json
6
+
7
+ from switch_trust.eval.common.reference import Reference, ReferenceType
8
+ from switch_trust.eval.common.utils import datetime_config, generate_id, now_utc
9
+ from switch_trust.eval.db.base.common_types import SortDirection
10
+
11
+
12
+ class MessageCollectionType(str, Enum):
13
+ """Discriminator for the kind of MessageCollection."""
14
+
15
+ IN_MEMORY = "in-memory"
16
+ POSTGRES = "postgres"
17
+ GARAK = "garak"
18
+ CSV = "csv"
19
+
20
+
21
+ @dataclass_json
22
+ @dataclass
23
+ class DbMessageCollection:
24
+ """JSON-serialisable description of a MessageCollection."""
25
+
26
+ type: MessageCollectionType
27
+ name: str
28
+ id: str = field(default_factory=generate_id)
29
+ created: datetime = field(
30
+ default_factory=now_utc,
31
+ metadata=datetime_config,
32
+ )
33
+ description: str | None = None
34
+ tags: dict[str, str] = field(default_factory=dict)
35
+
36
+ # -- in-memory type --
37
+ messages: list[dict] | None = None
38
+ prompts: list[str] | None = None
39
+
40
+ # -- garak type --
41
+ probe_name: str | None = None
42
+
43
+ # -- csv type --
44
+ filename: str | None = None
45
+ column: str | None = None
46
+
47
+ def get_ref(self) -> Reference:
48
+ return Reference(
49
+ self.id,
50
+ self.name,
51
+ ReferenceType.MESSAGE_COLLECTION,
52
+ self.description,
53
+ )
54
+
55
+
56
+ class MessageCollectionSortField(str, Enum):
57
+ NAME = "name"
58
+ CREATED = "created"
59
+ TYPE = "type"
60
+
61
+
62
+ @dataclass_json
63
+ @dataclass
64
+ class MessageCollectionSortOrder:
65
+ field: MessageCollectionSortField = MessageCollectionSortField.NAME
66
+ direction: SortDirection = SortDirection.ASC
67
+
68
+
69
+ @dataclass_json
70
+ @dataclass
71
+ class MessageCollectionListView:
72
+ """Paginated list of message collections."""
73
+
74
+ items: list[DbMessageCollection] = field(default_factory=list)
75
+ total: int = 0
76
+
77
+
78
+ @dataclass_json
79
+ @dataclass
80
+ class MessageCollectionDetailView:
81
+ """A message collection with its message count."""
82
+
83
+ id: str = ""
84
+ name: str = ""
85
+ description: str | None = None
86
+ tags: dict[str, str] = field(default_factory=dict)
87
+ message_count: int = 0
File without changes
@@ -0,0 +1,253 @@
1
+ from collections.abc import Callable
2
+
3
+ import aiohttp
4
+
5
+ from switch_trust.eval.common.utils import resolve_env, resolve_env_dict
6
+ from switch_trust.eval.core.models.model import Model
7
+ from switch_trust.eval.core.models.model_per_endpoint_limiter import (
8
+ PerEndpointLimiter,
9
+ )
10
+ from switch_trust.eval.core.models.model_retry import RetryModel
11
+ from switch_trust.eval.db.base.models.model_types import DbModel, ModelType
12
+
13
+ # HTTP-based agent types. SDK-based providers (Anthropic, OpenAI, Gemini,
14
+ # LiteLLM, HuggingFace, Ollama, OpenAI-compatible) own their own rate limiting
15
+ # and don't need our per-endpoint semaphore.
16
+ _LIMITED_HTTP_TYPES = {
17
+ ModelType.ADK,
18
+ ModelType.OPENAI_AGENT,
19
+ ModelType.ANTHROPIC_AGENT,
20
+ ModelType.GENERIC_HTTP,
21
+ ModelType.LANGSERVE,
22
+ ModelType.VERTEX_AGENT_RUNTIME,
23
+ }
24
+
25
+
26
+ def create_model(
27
+ db_model: DbModel,
28
+ max_retries: int = 5,
29
+ resolve_env_vars: bool = True,
30
+ connector_factory: Callable[[], aiohttp.BaseConnector] | None = None,
31
+ per_endpoint_limit: int = 8,
32
+ initial_jitter_max: float = 1.0,
33
+ ) -> Model:
34
+ """Create a Model instance from a DbModel, wrapped in a RetryModel for
35
+ transient error handling. HTTP-based types are additionally wrapped in a
36
+ PerEndpointLimiter so bursts against one tenant endpoint don't blow past
37
+ its QPS quota.
38
+
39
+ connector_factory, when set, is passed to the HTTP-based models so their
40
+ aiohttp sessions enforce the eval worker's request-time SSRF guard
41
+ (platform/ssrf.py). None keeps the default connector for CLI/local use.
42
+ """
43
+ inner = _create_inner_model(
44
+ db_model,
45
+ resolve_env_vars=resolve_env_vars,
46
+ connector_factory=connector_factory,
47
+ )
48
+ if db_model.type in _LIMITED_HTTP_TYPES and db_model.host:
49
+ inner = PerEndpointLimiter(
50
+ inner,
51
+ endpoint_url=db_model.host,
52
+ limit=per_endpoint_limit,
53
+ jitter_max=initial_jitter_max,
54
+ )
55
+ return RetryModel(inner, max_retries=max_retries)
56
+
57
+
58
+ def _create_inner_model(
59
+ db_model: DbModel,
60
+ resolve_env_vars: bool = True,
61
+ connector_factory: Callable[[], aiohttp.BaseConnector] | None = None,
62
+ ) -> Model:
63
+ temp = db_model.temperature
64
+ if resolve_env_vars:
65
+ key = resolve_env(db_model.key)
66
+ headers = resolve_env_dict(db_model.headers)
67
+ else:
68
+ key = db_model.key
69
+ headers = dict(db_model.headers)
70
+
71
+ if db_model.type == ModelType.ANTHROPIC:
72
+ from anthropic import ( # noqa: PLC0415 - patched at source in tests
73
+ AsyncAnthropic,
74
+ )
75
+
76
+ from switch_trust.eval.core.models.model_anthropic import ( # noqa: PLC0415 - patched at source in tests
77
+ AnthropicModel,
78
+ )
79
+
80
+ client_kwargs = {}
81
+ if key:
82
+ client_kwargs["api_key"] = key
83
+
84
+ return AnthropicModel(
85
+ AsyncAnthropic(**client_kwargs),
86
+ db_model.model_name,
87
+ temperature=temp,
88
+ )
89
+
90
+ elif db_model.type == ModelType.OPENAI:
91
+ from openai import AsyncOpenAI # noqa: PLC0415 - patched at source in tests
92
+
93
+ from switch_trust.eval.core.models.model_openai import ( # noqa: PLC0415 - patched at source in tests
94
+ OpenAIModel,
95
+ )
96
+
97
+ client_kwargs = {}
98
+ if key:
99
+ client_kwargs["api_key"] = key
100
+
101
+ return OpenAIModel(
102
+ AsyncOpenAI(**client_kwargs),
103
+ db_model.model_name,
104
+ temperature=temp,
105
+ )
106
+
107
+ elif db_model.type == ModelType.GEMINI:
108
+ from google.genai import Client # noqa: PLC0415 - patched at source in tests
109
+
110
+ from switch_trust.eval.core.models.model_gemini import ( # noqa: PLC0415 - patched at source in tests
111
+ GeminiModel,
112
+ )
113
+
114
+ client_kwargs = {}
115
+ if key:
116
+ client_kwargs["api_key"] = key
117
+
118
+ return GeminiModel(
119
+ Client(**client_kwargs),
120
+ db_model.model_name,
121
+ temperature=temp,
122
+ )
123
+
124
+ elif db_model.type == ModelType.LITELLM:
125
+ from switch_trust.eval.core.models.model_litellm import ( # noqa: PLC0415 - patched at source in tests
126
+ LiteLLMModel,
127
+ )
128
+
129
+ return LiteLLMModel(
130
+ db_model.model_name,
131
+ temperature=temp,
132
+ )
133
+
134
+ elif db_model.type == ModelType.HUGGINGFACE:
135
+ from switch_trust.eval.core.models.model_huggingface import ( # noqa: PLC0415 - patched at source in tests
136
+ HuggingFaceModel,
137
+ )
138
+
139
+ return HuggingFaceModel(
140
+ db_model.model_name,
141
+ token=key or None,
142
+ temperature=temp,
143
+ )
144
+
145
+ elif db_model.type == ModelType.OLLAMA:
146
+ from switch_trust.eval.core.models.model_ollama import ( # noqa: PLC0415 - patched at source in tests
147
+ OllamaModel,
148
+ )
149
+
150
+ return OllamaModel(
151
+ db_model.model_name,
152
+ host=db_model.host or "http://localhost:11434",
153
+ temperature=temp,
154
+ )
155
+
156
+ elif db_model.type == ModelType.ADK:
157
+ from switch_trust.eval.core.models.model_adk import ( # noqa: PLC0415 - patched at source in tests
158
+ ADKModel,
159
+ )
160
+
161
+ return ADKModel(
162
+ app_name=db_model.model_name,
163
+ host=db_model.host or "http://localhost:8000",
164
+ immediate_result=db_model.immediate_result,
165
+ headers=headers or None,
166
+ connector_factory=connector_factory,
167
+ )
168
+
169
+ elif db_model.type == ModelType.OPENAI_AGENT:
170
+ from switch_trust.eval.core.models.model_openai_agent import ( # noqa: PLC0415 - patched at source in tests
171
+ OpenAIAgentModel,
172
+ )
173
+
174
+ return OpenAIAgentModel(
175
+ host=db_model.host or "http://localhost:8000",
176
+ endpoint=db_model.endpoint or "/run",
177
+ connector_factory=connector_factory,
178
+ )
179
+
180
+ elif db_model.type == ModelType.ANTHROPIC_AGENT:
181
+ from switch_trust.eval.core.models.model_anthropic_agent import ( # noqa: PLC0415 - patched at source in tests
182
+ AnthropicAgentModel,
183
+ )
184
+
185
+ return AnthropicAgentModel(
186
+ host=db_model.host or "http://localhost:8000",
187
+ endpoint=db_model.endpoint or "/run",
188
+ connector_factory=connector_factory,
189
+ )
190
+
191
+ elif db_model.type == ModelType.OPENAI_COMPATIBLE:
192
+ from switch_trust.eval.core.models.model_openai_compatible import ( # noqa: PLC0415 - patched at source in tests
193
+ OpenAICompatibleModel,
194
+ )
195
+
196
+ return OpenAICompatibleModel(
197
+ base_url=db_model.host or "http://localhost:8000/v1",
198
+ model=db_model.model_name,
199
+ api_key=key or "EMPTY",
200
+ temperature=temp,
201
+ headers=headers or None,
202
+ )
203
+
204
+ elif db_model.type == ModelType.GENERIC_HTTP:
205
+ from switch_trust.eval.core.models.model_generic_http import ( # noqa: PLC0415 - patched at source in tests
206
+ GenericHttpModel,
207
+ )
208
+
209
+ url = db_model.host or "http://localhost:8000"
210
+ if db_model.endpoint:
211
+ url = f"{url.rstrip('/')}{db_model.endpoint}"
212
+
213
+ return GenericHttpModel(
214
+ url=url,
215
+ headers=headers or None,
216
+ input_path=db_model.input_path or "input",
217
+ output_path=db_model.output_path or "output",
218
+ connector_factory=connector_factory,
219
+ )
220
+
221
+ elif db_model.type == ModelType.VERTEX_AGENT_RUNTIME:
222
+ from switch_trust.eval.core.models.model_vertex_agent_runtime import ( # noqa: PLC0415 - patched at source in tests
223
+ DEFAULT_CLASS_METHOD,
224
+ DEFAULT_USER_ID,
225
+ VertexAgentRuntimeModel,
226
+ )
227
+
228
+ # The engine URL already carries the :streamQuery suffix, so `endpoint`
229
+ # is not appended here the way it is for the generic HTTP types.
230
+ return VertexAgentRuntimeModel(
231
+ url=db_model.host or "",
232
+ credential=key or None,
233
+ headers=headers or None,
234
+ user_id=db_model.user_id or DEFAULT_USER_ID,
235
+ class_method=db_model.class_method or DEFAULT_CLASS_METHOD,
236
+ immediate_result=db_model.immediate_result,
237
+ connector_factory=connector_factory,
238
+ )
239
+
240
+ elif db_model.type == ModelType.LANGSERVE:
241
+ from switch_trust.eval.core.models.model_langserve import ( # noqa: PLC0415 - patched at source in tests
242
+ LangServeModel,
243
+ )
244
+
245
+ return LangServeModel(
246
+ base_url=db_model.host or "http://localhost:8000",
247
+ chain_path=db_model.endpoint or "",
248
+ headers=headers or None,
249
+ connector_factory=connector_factory,
250
+ )
251
+
252
+ else:
253
+ raise ValueError(f"unknown model type: {db_model.type}")
@@ -0,0 +1,104 @@
1
+ """
2
+ Abstract interface for a repository of Models.
3
+
4
+ A ModelRepository manages model configurations,
5
+ providing CRUD operations over them. Models are
6
+ always referenced by ID.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from abc import ABC, abstractmethod
12
+
13
+ from switch_trust.eval.core.models.model import Model
14
+ from switch_trust.eval.db.base.models.model_types import (
15
+ DbModel,
16
+ DbModelListView,
17
+ ModelSortOrder,
18
+ )
19
+
20
+
21
+ class ModelRepository(ABC):
22
+ """Repository that manages Model configurations."""
23
+
24
+ @abstractmethod
25
+ def list(self) -> list[DbModel]:
26
+ """Return all model configurations in the repository."""
27
+ pass
28
+
29
+ @abstractmethod
30
+ def search(
31
+ self,
32
+ query: str | None = None,
33
+ types: list[str] | None = None,
34
+ order: ModelSortOrder | None = None,
35
+ offset: int = 0,
36
+ limit: int = 50,
37
+ ) -> DbModelListView:
38
+ """Search models by name with offset-based pagination.
39
+
40
+ Args:
41
+ query: Text to match against the model name
42
+ (case-insensitive substring match).
43
+ order: Sort field and direction.
44
+ offset: Number of items to skip.
45
+ limit: Maximum number of items to return.
46
+ """
47
+ pass
48
+
49
+ @abstractmethod
50
+ def get(self, id: str) -> DbModel:
51
+ """Return the model configuration with the given id.
52
+
53
+ Raises KeyError if the model does not exist.
54
+ """
55
+ pass
56
+
57
+ @abstractmethod
58
+ def create(
59
+ self,
60
+ config: DbModel,
61
+ ) -> DbModel:
62
+ """Persist a new model configuration.
63
+
64
+ Raises ValueError if a model with that name already
65
+ exists.
66
+ """
67
+ pass
68
+
69
+ @abstractmethod
70
+ def update(
71
+ self,
72
+ config: DbModel,
73
+ ) -> DbModel:
74
+ """Replace an existing model configuration.
75
+
76
+ The model is identified by ``config.id``.
77
+
78
+ Raises KeyError if the model does not exist.
79
+ """
80
+ pass
81
+
82
+ def get_model(self, id: str) -> Model:
83
+ """Return a Model instance for the given id.
84
+
85
+ Raises KeyError if the model does not exist.
86
+ """
87
+ from switch_trust.eval.db.base.models.model_helpers import ( # noqa: PLC0415 - deferred import cost
88
+ create_model,
89
+ )
90
+
91
+ return create_model(self.get(id))
92
+
93
+ @abstractmethod
94
+ def delete(self, id: str) -> None:
95
+ """Delete the model with the given id.
96
+
97
+ Raises KeyError if the model does not exist.
98
+ """
99
+ pass
100
+
101
+ @abstractmethod
102
+ def clear(self) -> None:
103
+ """Remove all models from the repository."""
104
+ pass