arthur-common 2.1.60__py3-none-any.whl → 2.1.66__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.

Potentially problematic release.


This version of arthur-common might be problematic. Click here for more details.

@@ -1,303 +1,39 @@
1
1
  from datetime import datetime
2
- from enum import Enum
3
2
  from typing import Any, Dict, List, Optional, Self, Type, Union
4
3
 
5
4
  from fastapi import HTTPException
5
+ from openinference.semconv.trace import OpenInferenceSpanKindValues
6
6
  from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
7
7
 
8
- DEFAULT_TOXICITY_RULE_THRESHOLD = 0.5
9
- DEFAULT_PII_RULE_CONFIDENCE_SCORE_THRESHOLD = 0
10
-
11
-
12
- class RuleType(str, Enum):
13
- KEYWORD = "KeywordRule"
14
- MODEL_HALLUCINATION_V2 = "ModelHallucinationRuleV2"
15
- MODEL_SENSITIVE_DATA = "ModelSensitiveDataRule"
16
- PII_DATA = "PIIDataRule"
17
- PROMPT_INJECTION = "PromptInjectionRule"
18
- REGEX = "RegexRule"
19
- TOXICITY = "ToxicityRule"
20
-
21
- def __str__(self) -> str:
22
- return self.value
23
-
24
-
25
- class RuleScope(str, Enum):
26
- DEFAULT = "default"
27
- TASK = "task"
28
-
29
-
30
- class MetricType(str, Enum):
31
- QUERY_RELEVANCE = "QueryRelevance"
32
- RESPONSE_RELEVANCE = "ResponseRelevance"
33
- TOOL_SELECTION = "ToolSelection"
34
-
35
- def __str__(self) -> str:
36
- return self.value
37
-
38
-
39
- class BaseEnum(str, Enum):
40
- @classmethod
41
- def values(cls) -> list[Any]:
42
- values: list[str] = [e for e in cls]
43
- return values
44
-
45
- def __str__(self) -> Any:
46
- return self.value
47
-
48
-
49
- # Note: These string values are not arbitrary and map to Presidio entity types: https://microsoft.github.io/presidio/supported_entities/
50
- class PIIEntityTypes(BaseEnum):
51
- CREDIT_CARD = "CREDIT_CARD"
52
- CRYPTO = "CRYPTO"
53
- DATE_TIME = "DATE_TIME"
54
- EMAIL_ADDRESS = "EMAIL_ADDRESS"
55
- IBAN_CODE = "IBAN_CODE"
56
- IP_ADDRESS = "IP_ADDRESS"
57
- NRP = "NRP"
58
- LOCATION = "LOCATION"
59
- PERSON = "PERSON"
60
- PHONE_NUMBER = "PHONE_NUMBER"
61
- MEDICAL_LICENSE = "MEDICAL_LICENSE"
62
- URL = "URL"
63
- US_BANK_NUMBER = "US_BANK_NUMBER"
64
- US_DRIVER_LICENSE = "US_DRIVER_LICENSE"
65
- US_ITIN = "US_ITIN"
66
- US_PASSPORT = "US_PASSPORT"
67
- US_SSN = "US_SSN"
68
-
69
- @classmethod
70
- def to_string(cls) -> str:
71
- return ",".join(member.value for member in cls)
72
-
73
-
74
- class KeywordsConfig(BaseModel):
75
- keywords: List[str] = Field(description="List of Keywords")
76
-
77
- model_config = ConfigDict(
78
- json_schema_extra={
79
- "example": {"keywords": ["Blocked_Keyword_1", "Blocked_Keyword_2"]},
80
- },
81
- )
82
-
83
-
84
- class RegexConfig(BaseModel):
85
- regex_patterns: List[str] = Field(
86
- description="List of Regex patterns to be used for validation. Be sure to encode requests in JSON and account for escape characters.",
87
- )
88
-
89
- model_config = ConfigDict(
90
- json_schema_extra={
91
- "example": {
92
- "regex_patterns": ["\\d{3}-\\d{2}-\\d{4}", "\\d{5}-\\d{6}-\\d{7}"],
93
- },
94
- },
95
- extra="forbid",
96
- )
97
-
98
-
99
- class ToxicityConfig(BaseModel):
100
- threshold: float = Field(
101
- default=DEFAULT_TOXICITY_RULE_THRESHOLD,
102
- description=f"Optional. Float (0, 1) indicating the level of tolerable toxicity to consider the rule passed or failed. Min: 0 (no toxic language) Max: 1 (very toxic language). Default: {DEFAULT_TOXICITY_RULE_THRESHOLD}",
103
- )
104
-
105
- model_config = ConfigDict(
106
- extra="forbid",
107
- json_schema_extra={"example": {"threshold": DEFAULT_TOXICITY_RULE_THRESHOLD}},
108
- )
109
-
110
- @field_validator("threshold")
111
- def validate_toxicity_threshold(cls, v: float) -> float:
112
- if v and ((v < 0) | (v > 1)):
113
- raise ValueError(f'"threshold" must be between 0 and 1')
114
- return v
115
-
116
-
117
- class PIIConfig(BaseModel):
118
- disabled_pii_entities: Optional[list[str]] = Field(
119
- description=f"Optional. List of PII entities to disable. Valid values are: {PIIEntityTypes.to_string()}",
120
- default=None,
121
- )
122
-
123
- confidence_threshold: Optional[float] = Field(
124
- description=f"Optional. Float (0, 1) indicating the level of tolerable PII to consider the rule passed or failed. Min: 0 (less confident) Max: 1 (very confident). Default: {DEFAULT_PII_RULE_CONFIDENCE_SCORE_THRESHOLD}",
125
- default=DEFAULT_PII_RULE_CONFIDENCE_SCORE_THRESHOLD,
126
- json_schema_extra={"deprecated": True},
127
- )
128
-
129
- allow_list: Optional[list[str]] = Field(
130
- description="Optional. List of strings to pass PII validation.",
131
- default=None,
132
- )
133
-
134
- @field_validator("disabled_pii_entities")
135
- def validate_pii_entities(cls, v: Optional[List[str]]) -> Optional[List[str]]:
136
- if v:
137
- entities_passed = set(v)
138
- entities_supported = set(PIIEntityTypes.values())
139
- invalid_entities = entities_passed - entities_supported
140
- if invalid_entities:
141
- raise ValueError(
142
- f"The following values are not valid PII entities: {invalid_entities}",
143
- )
144
-
145
- # Fail the case where they are trying to disable all PII entity types
146
- if (not invalid_entities) & (
147
- len(entities_passed) == len(entities_supported)
148
- ):
149
- raise ValueError(
150
- f"Cannot disable all supported PII entities on PIIDataRule",
151
- )
152
- return v
153
- else:
154
- return v
155
-
156
- @field_validator("confidence_threshold")
157
- def validate_confidence_threshold(cls, v: Optional[float]) -> Optional[float]:
158
- if v and ((v < 0) | (v > 1)):
159
- raise ValueError(f'"confidence_threshold" must be between 0 and 1')
160
- return v
161
-
162
- model_config = ConfigDict(
163
- json_schema_extra={
164
- "example": {
165
- "disabled_pii_entities": ["PERSON", "URL"],
166
- "confidence_threshold": "0.5",
167
- "allow_list": ["arthur.ai", "Arthur"],
168
- },
169
- },
170
- extra="forbid",
171
- )
172
-
173
-
174
- NEGATIVE_BLOOD_EXAMPLE = "John has O negative blood group"
175
-
176
-
177
- class ExampleConfig(BaseModel):
178
- example: str = Field(description="Custom example for the sensitive data")
179
- result: bool = Field(
180
- description="Boolean value representing if the example passes or fails the the sensitive "
181
- "data rule ",
182
- )
183
-
184
- model_config = ConfigDict(
185
- json_schema_extra={
186
- "example": {"example": NEGATIVE_BLOOD_EXAMPLE, "result": True},
187
- },
188
- )
189
-
190
-
191
- class ExamplesConfig(BaseModel):
192
- examples: List[ExampleConfig] = Field(
193
- description="List of all the examples for Sensitive Data Rule",
194
- )
195
-
196
- model_config = ConfigDict(
197
- json_schema_extra={
198
- "example": {
199
- "examples": [
200
- {"example": NEGATIVE_BLOOD_EXAMPLE, "result": True},
201
- {
202
- "example": "Most of the people have A positive blood group",
203
- "result": False,
204
- },
205
- ],
206
- "hint": "specific individual's blood type",
207
- },
208
- },
209
- )
210
- hint: Optional[str] = Field(
211
- description="Optional. Hint added to describe what Sensitive Data Rule should be checking for",
212
- default=None,
213
- )
214
-
215
- def to_dict(self) -> Dict[str, Any]:
216
- d = self.__dict__
217
- d["examples"] = [ex.__dict__ for ex in self.examples]
218
- d["hint"] = self.hint
219
- return d
220
-
221
-
222
- class RuleResponse(BaseModel):
223
- id: str = Field(description="ID of the Rule")
224
- name: str = Field(description="Name of the Rule")
225
- type: RuleType = Field(description="Type of Rule")
226
- apply_to_prompt: bool = Field(description="Rule applies to prompt")
227
- apply_to_response: bool = Field(description="Rule applies to response")
228
- enabled: Optional[bool] = Field(
229
- description="Rule is enabled for the task",
230
- default=None,
231
- )
232
- scope: RuleScope = Field(
233
- description="Scope of the rule. The rule can be set at default level or task level.",
234
- )
235
- # UNIX millis format
236
- created_at: int = Field(
237
- description="Time the rule was created in unix milliseconds",
238
- )
239
- updated_at: int = Field(
240
- description="Time the rule was updated in unix milliseconds",
241
- )
242
- # added a title to this to differentiate it in the generated client from the
243
- # config field on the NewRuleRequest object
244
- config: Optional[
245
- Union[KeywordsConfig, RegexConfig, ExamplesConfig, ToxicityConfig, PIIConfig]
246
- ] = Field(
247
- description="Config of the rule",
248
- default=None,
249
- title="Rule Response Config",
250
- )
251
-
252
-
253
- class MetricResponse(BaseModel):
254
- id: str = Field(description="ID of the Metric")
255
- name: str = Field(description="Name of the Metric")
256
- type: MetricType = Field(description="Type of the Metric")
257
- metric_metadata: str = Field(description="Metadata of the Metric")
258
- config: Optional[str] = Field(
259
- description="JSON-serialized configuration for the Metric",
260
- default=None,
261
- )
262
- created_at: datetime = Field(
263
- description="Time the Metric was created in unix milliseconds",
264
- )
265
- updated_at: datetime = Field(
266
- description="Time the Metric was updated in unix milliseconds",
267
- )
268
- enabled: Optional[bool] = Field(
269
- description="Whether the Metric is enabled",
270
- default=None,
271
- )
272
-
273
-
274
- class TaskResponse(BaseModel):
275
- id: str = Field(description=" ID of the task")
276
- name: str = Field(description="Name of the task")
277
- created_at: int = Field(
278
- description="Time the task was created in unix milliseconds",
279
- )
280
- updated_at: int = Field(
281
- description="Time the task was created in unix milliseconds",
282
- )
283
- is_agentic: Optional[bool] = Field(
284
- description="Whether the task is agentic or not",
285
- default=None,
286
- )
287
- rules: List[RuleResponse] = Field(description="List of all the rules for the task.")
288
- metrics: Optional[List[MetricResponse]] = Field(
289
- description="List of all the metrics for the task.",
290
- default=None,
291
- )
8
+ from arthur_common.models.common_schemas import (
9
+ ExamplesConfig,
10
+ KeywordsConfig,
11
+ PIIConfig,
12
+ RegexConfig,
13
+ ToxicityConfig,
14
+ )
15
+ from arthur_common.models.constants import (
16
+ ERROR_PASSWORD_POLICY_NOT_MET,
17
+ GENAI_ENGINE_KEYCLOAK_PASSWORD_LENGTH,
18
+ HALLUCINATION_RULE_NAME,
19
+ NEGATIVE_BLOOD_EXAMPLE,
20
+ )
21
+ from arthur_common.models.enums import (
22
+ APIKeysRolesEnum,
23
+ InferenceFeedbackTarget,
24
+ MetricType,
25
+ PIIEntityTypes,
26
+ RuleScope,
27
+ RuleType,
28
+ )
29
+ from arthur_common.models.metric_schemas import RelevanceMetricConfig
292
30
 
293
31
 
294
32
  class UpdateRuleRequest(BaseModel):
295
33
  enabled: bool = Field(description="Boolean value to enable or disable the rule. ")
296
34
 
297
35
 
298
- HALLUCINATION_RULE_NAME = "Hallucination Rule"
299
-
300
-
36
+ # Using the latest version from arthur-common
301
37
  class NewRuleRequest(BaseModel):
302
38
  name: str = Field(description="Name of the rule", examples=["SSN Regex Rule"])
303
39
  type: str = Field(
@@ -313,9 +49,14 @@ class NewRuleRequest(BaseModel):
313
49
  description="Boolean value to enable or disable the rule for llm response",
314
50
  examples=[False],
315
51
  )
316
- config: Optional[
317
- Union[RegexConfig, KeywordsConfig, ToxicityConfig, PIIConfig, ExamplesConfig]
318
- ] = Field(description="Config for the rule", default=None)
52
+ config: (
53
+ KeywordsConfig
54
+ | RegexConfig
55
+ | ExamplesConfig
56
+ | ToxicityConfig
57
+ | PIIConfig
58
+ | None
59
+ ) = Field(description="Config of the rule", default=None)
319
60
 
320
61
  model_config = ConfigDict(
321
62
  json_schema_extra={
@@ -525,19 +266,148 @@ class NewRuleRequest(BaseModel):
525
266
  return self
526
267
 
527
268
 
528
- class RelevanceMetricConfig(BaseModel):
529
- """Configuration for relevance metrics including QueryRelevance and ResponseRelevance"""
269
+ class SearchTasksRequest(BaseModel):
270
+ task_ids: Optional[list[str]] = Field(
271
+ description="List of tasks to query for.",
272
+ default=None,
273
+ )
274
+ task_name: Optional[str] = Field(
275
+ description="Task name substring search string.",
276
+ default=None,
277
+ )
278
+ is_agentic: Optional[bool] = Field(
279
+ description="Filter tasks by agentic status. If not provided, returns both agentic and non-agentic tasks.",
280
+ default=None,
281
+ )
282
+
530
283
 
531
- relevance_threshold: Optional[float] = Field(
284
+ class SearchRulesRequest(BaseModel):
285
+ rule_ids: Optional[list[str]] = Field(
286
+ description="List of rule IDs to search for.",
532
287
  default=None,
533
- description="Threshold for determining relevance when not using LLM judge",
534
288
  )
535
- use_llm_judge: bool = Field(
536
- default=True,
537
- description="Whether to use LLM as a judge for relevance scoring",
289
+ rule_scopes: Optional[list[RuleScope]] = Field(
290
+ description="List of rule scopes to search for.",
291
+ default=None,
292
+ )
293
+ prompt_enabled: Optional[bool] = Field(
294
+ description="Include or exclude prompt-enabled rules.",
295
+ default=None,
296
+ )
297
+ response_enabled: Optional[bool] = Field(
298
+ description="Include or exclude response-enabled rules.",
299
+ default=None,
300
+ )
301
+ rule_types: Optional[list[RuleType]] = Field(
302
+ description="List of rule types to search for.",
303
+ default=None,
538
304
  )
539
305
 
540
306
 
307
+ class NewTaskRequest(BaseModel):
308
+ name: str = Field(description="Name of the task.", min_length=1)
309
+ is_agentic: bool = Field(
310
+ description="Whether the task is agentic or not.",
311
+ default=False,
312
+ )
313
+
314
+
315
+ class NewApiKeyRequest(BaseModel):
316
+ description: Optional[str] = Field(
317
+ description="Description of the API key. Optional.",
318
+ default=None,
319
+ )
320
+ roles: Optional[list[APIKeysRolesEnum]] = Field(
321
+ description=f"Role that will be assigned to API key. Allowed values: {[role for role in APIKeysRolesEnum]}",
322
+ default=[APIKeysRolesEnum.VALIDATION_USER],
323
+ )
324
+
325
+
326
+ class PromptValidationRequest(BaseModel):
327
+ prompt: str = Field(description="Prompt to be validated by GenAI Engine")
328
+ # context: Optional[str] = Field(
329
+ # description="Optional data provided as context for the prompt validation. "
330
+ # "Currently not used"
331
+ # )
332
+ conversation_id: Optional[str] = Field(
333
+ description="The unique conversation ID this prompt belongs to. All prompts and responses from this \
334
+ conversation can later be reconstructed with this ID.",
335
+ default=None,
336
+ )
337
+ user_id: Optional[str] = Field(
338
+ description="The user ID this prompt belongs to",
339
+ default=None,
340
+ )
341
+
342
+
343
+ class ResponseValidationRequest(BaseModel):
344
+ response: str = Field(description="LLM Response to be validated by GenAI Engine")
345
+ context: Optional[str] = Field(
346
+ description="Optional data provided as context for the validation.",
347
+ default=None,
348
+ )
349
+ # tokens: Optional[List[str]] = Field(description="optional, not used currently")
350
+ # token_likelihoods: Optional[List[str]] = Field(
351
+ # description="optional, not used currently"
352
+ # )
353
+
354
+ @model_validator(mode="after")
355
+ def check_prompt_or_response(cls, values: Any) -> Any:
356
+ if isinstance(values, PromptValidationRequest) and values.prompt is None:
357
+ raise ValueError("prompt is required when validating a prompt")
358
+ if isinstance(values, ResponseValidationRequest) and values.response is None:
359
+ raise ValueError("response is required when validating a response")
360
+ return values
361
+
362
+
363
+ class ChatRequest(BaseModel):
364
+ user_prompt: str = Field(description="Prompt user wants to send to chat.")
365
+ conversation_id: str = Field(description="Conversation ID")
366
+ file_ids: List[str] = Field(
367
+ description="list of file IDs to retrieve from during chat.",
368
+ )
369
+
370
+
371
+ class FeedbackRequest(BaseModel):
372
+ target: InferenceFeedbackTarget
373
+ score: int
374
+ reason: str | None
375
+ user_id: str | None = None
376
+
377
+
378
+ class CreateUserRequest(BaseModel):
379
+ email: str
380
+ password: str
381
+ temporary: bool = True
382
+ roles: list[str]
383
+ firstName: str
384
+ lastName: str
385
+
386
+
387
+ class PasswordResetRequest(BaseModel):
388
+ password: str
389
+
390
+ @field_validator("password")
391
+ @classmethod
392
+ def password_meets_security(cls, value: str) -> str:
393
+ special_characters = '!@#$%^&*()-+?_=,<>/"'
394
+ if not len(value) >= GENAI_ENGINE_KEYCLOAK_PASSWORD_LENGTH:
395
+ raise ValueError(ERROR_PASSWORD_POLICY_NOT_MET)
396
+ if (
397
+ not any(c.isupper() for c in value)
398
+ or not any(c.islower() for c in value)
399
+ or not any(c.isdigit() for c in value)
400
+ or not any(c in special_characters for c in value)
401
+ ):
402
+ raise ValueError(ERROR_PASSWORD_POLICY_NOT_MET)
403
+ return value
404
+
405
+
406
+ class ChatDefaultTaskRequest(BaseModel):
407
+ task_id: str
408
+
409
+
410
+ # Using the latest version from arthur-common
541
411
  class NewMetricRequest(BaseModel):
542
412
  type: MetricType = Field(
543
413
  description="Type of the metric. It can only be one of QueryRelevance, ResponseRelevance, ToolSelection",
@@ -640,3 +510,47 @@ class NewMetricRequest(BaseModel):
640
510
  )
641
511
 
642
512
  return values
513
+
514
+
515
+ class UpdateMetricRequest(BaseModel):
516
+ enabled: bool = Field(description="Boolean value to enable or disable the metric. ")
517
+
518
+
519
+ class SpanQueryRequest(BaseModel):
520
+ """Request schema for querying spans with validation."""
521
+
522
+ task_ids: list[str] = Field(
523
+ ...,
524
+ description="Task IDs to filter on. At least one is required.",
525
+ min_length=1,
526
+ )
527
+ span_types: Optional[list[str]] = Field(
528
+ None,
529
+ description=f"Span types to filter on. Optional. Valid values: {', '.join(sorted([kind.value for kind in OpenInferenceSpanKindValues]))}",
530
+ )
531
+ start_time: Optional[datetime] = Field(
532
+ None,
533
+ description="Inclusive start date in ISO8601 string format.",
534
+ )
535
+ end_time: Optional[datetime] = Field(
536
+ None,
537
+ description="Exclusive end date in ISO8601 string format.",
538
+ )
539
+
540
+ @field_validator("span_types")
541
+ @classmethod
542
+ def validate_span_types(cls, value: list[str]) -> list[str]:
543
+ """Validate that all span_types are valid OpenInference span kinds."""
544
+ if not value:
545
+ return value
546
+
547
+ # Get all valid span kind values
548
+ valid_span_kinds = [kind.value for kind in OpenInferenceSpanKindValues]
549
+ invalid_types = [st for st in value if st not in valid_span_kinds]
550
+
551
+ if invalid_types:
552
+ raise ValueError(
553
+ f"Invalid span_types received: {invalid_types}. "
554
+ f"Valid values: {', '.join(sorted(valid_span_kinds))}",
555
+ )
556
+ return value