ibm-watsonx-gov 1.3.3__cp313-cp313-macosx_11_0_arm64.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 (353) hide show
  1. ibm_watsonx_gov/__init__.py +8 -0
  2. ibm_watsonx_gov/agent_catalog/__init__.py +8 -0
  3. ibm_watsonx_gov/agent_catalog/clients/__init__.py +14 -0
  4. ibm_watsonx_gov/agent_catalog/clients/ai_agent_client.py +333 -0
  5. ibm_watsonx_gov/agent_catalog/core/__init__.py +8 -0
  6. ibm_watsonx_gov/agent_catalog/core/agent_loader.py +202 -0
  7. ibm_watsonx_gov/agent_catalog/core/agents.py +134 -0
  8. ibm_watsonx_gov/agent_catalog/entities/__init__.py +8 -0
  9. ibm_watsonx_gov/agent_catalog/entities/ai_agent.py +599 -0
  10. ibm_watsonx_gov/agent_catalog/utils/__init__.py +8 -0
  11. ibm_watsonx_gov/agent_catalog/utils/constants.py +36 -0
  12. ibm_watsonx_gov/agent_catalog/utils/notebook_utils.py +70 -0
  13. ibm_watsonx_gov/ai_experiments/__init__.py +8 -0
  14. ibm_watsonx_gov/ai_experiments/ai_experiments_client.py +980 -0
  15. ibm_watsonx_gov/ai_experiments/utils/__init__.py +8 -0
  16. ibm_watsonx_gov/ai_experiments/utils/ai_experiment_utils.py +139 -0
  17. ibm_watsonx_gov/clients/__init__.py +0 -0
  18. ibm_watsonx_gov/clients/api_client.py +99 -0
  19. ibm_watsonx_gov/clients/segment_client.py +46 -0
  20. ibm_watsonx_gov/clients/usage_client.cpython-313-darwin.so +0 -0
  21. ibm_watsonx_gov/clients/wx_ai_client.py +87 -0
  22. ibm_watsonx_gov/config/__init__.py +14 -0
  23. ibm_watsonx_gov/config/agentic_ai_configuration.py +225 -0
  24. ibm_watsonx_gov/config/gen_ai_configuration.py +129 -0
  25. ibm_watsonx_gov/config/model_risk_configuration.py +173 -0
  26. ibm_watsonx_gov/config/predictive_ai_configuration.py +20 -0
  27. ibm_watsonx_gov/entities/__init__.py +8 -0
  28. ibm_watsonx_gov/entities/agentic_app.py +209 -0
  29. ibm_watsonx_gov/entities/agentic_evaluation_result.py +185 -0
  30. ibm_watsonx_gov/entities/ai_evaluation.py +290 -0
  31. ibm_watsonx_gov/entities/ai_experiment.py +419 -0
  32. ibm_watsonx_gov/entities/base_classes.py +134 -0
  33. ibm_watsonx_gov/entities/container.py +54 -0
  34. ibm_watsonx_gov/entities/credentials.py +633 -0
  35. ibm_watsonx_gov/entities/criteria.py +508 -0
  36. ibm_watsonx_gov/entities/enums.py +274 -0
  37. ibm_watsonx_gov/entities/evaluation_result.py +444 -0
  38. ibm_watsonx_gov/entities/foundation_model.py +490 -0
  39. ibm_watsonx_gov/entities/llm_judge.py +44 -0
  40. ibm_watsonx_gov/entities/locale.py +17 -0
  41. ibm_watsonx_gov/entities/mapping.py +49 -0
  42. ibm_watsonx_gov/entities/metric.py +211 -0
  43. ibm_watsonx_gov/entities/metric_threshold.py +36 -0
  44. ibm_watsonx_gov/entities/model_provider.py +329 -0
  45. ibm_watsonx_gov/entities/model_risk_result.py +43 -0
  46. ibm_watsonx_gov/entities/monitor.py +71 -0
  47. ibm_watsonx_gov/entities/prompt_setup.py +40 -0
  48. ibm_watsonx_gov/entities/state.py +22 -0
  49. ibm_watsonx_gov/entities/utils.py +99 -0
  50. ibm_watsonx_gov/evaluators/__init__.py +26 -0
  51. ibm_watsonx_gov/evaluators/agentic_evaluator.py +2725 -0
  52. ibm_watsonx_gov/evaluators/agentic_traces_evaluator.py +115 -0
  53. ibm_watsonx_gov/evaluators/base_evaluator.py +22 -0
  54. ibm_watsonx_gov/evaluators/impl/__init__.py +0 -0
  55. ibm_watsonx_gov/evaluators/impl/evaluate_metrics_impl.cpython-313-darwin.so +0 -0
  56. ibm_watsonx_gov/evaluators/impl/evaluate_model_risk_impl.cpython-313-darwin.so +0 -0
  57. ibm_watsonx_gov/evaluators/metrics_evaluator.py +187 -0
  58. ibm_watsonx_gov/evaluators/model_risk_evaluator.py +89 -0
  59. ibm_watsonx_gov/evaluators/traces_evaluator.py +93 -0
  60. ibm_watsonx_gov/metric_groups/answer_quality/answer_quality_decorator.py +66 -0
  61. ibm_watsonx_gov/metric_groups/content_safety/content_safety_decorator.py +76 -0
  62. ibm_watsonx_gov/metric_groups/readability/readability_decorator.py +59 -0
  63. ibm_watsonx_gov/metric_groups/retrieval_quality/retrieval_quality_decorator.py +63 -0
  64. ibm_watsonx_gov/metric_groups/usage/usage_decorator.py +58 -0
  65. ibm_watsonx_gov/metrics/__init__.py +74 -0
  66. ibm_watsonx_gov/metrics/answer_relevance/__init__.py +8 -0
  67. ibm_watsonx_gov/metrics/answer_relevance/answer_relevance_decorator.py +63 -0
  68. ibm_watsonx_gov/metrics/answer_relevance/answer_relevance_metric.py +260 -0
  69. ibm_watsonx_gov/metrics/answer_similarity/__init__.py +0 -0
  70. ibm_watsonx_gov/metrics/answer_similarity/answer_similarity_decorator.py +66 -0
  71. ibm_watsonx_gov/metrics/answer_similarity/answer_similarity_metric.py +219 -0
  72. ibm_watsonx_gov/metrics/average_precision/__init__.py +0 -0
  73. ibm_watsonx_gov/metrics/average_precision/average_precision_decorator.py +62 -0
  74. ibm_watsonx_gov/metrics/average_precision/average_precision_metric.py +174 -0
  75. ibm_watsonx_gov/metrics/base_metric_decorator.py +193 -0
  76. ibm_watsonx_gov/metrics/context_relevance/__init__.py +8 -0
  77. ibm_watsonx_gov/metrics/context_relevance/context_relevance_decorator.py +60 -0
  78. ibm_watsonx_gov/metrics/context_relevance/context_relevance_metric.py +414 -0
  79. ibm_watsonx_gov/metrics/cost/__init__.py +8 -0
  80. ibm_watsonx_gov/metrics/cost/cost_decorator.py +58 -0
  81. ibm_watsonx_gov/metrics/cost/cost_metric.py +155 -0
  82. ibm_watsonx_gov/metrics/duration/__init__.py +8 -0
  83. ibm_watsonx_gov/metrics/duration/duration_decorator.py +59 -0
  84. ibm_watsonx_gov/metrics/duration/duration_metric.py +111 -0
  85. ibm_watsonx_gov/metrics/evasiveness/__init__.py +8 -0
  86. ibm_watsonx_gov/metrics/evasiveness/evasiveness_decorator.py +61 -0
  87. ibm_watsonx_gov/metrics/evasiveness/evasiveness_metric.py +103 -0
  88. ibm_watsonx_gov/metrics/faithfulness/__init__.py +8 -0
  89. ibm_watsonx_gov/metrics/faithfulness/faithfulness_decorator.py +65 -0
  90. ibm_watsonx_gov/metrics/faithfulness/faithfulness_metric.py +254 -0
  91. ibm_watsonx_gov/metrics/hap/__init__.py +16 -0
  92. ibm_watsonx_gov/metrics/hap/hap_decorator.py +58 -0
  93. ibm_watsonx_gov/metrics/hap/hap_metric.py +98 -0
  94. ibm_watsonx_gov/metrics/hap/input_hap_metric.py +104 -0
  95. ibm_watsonx_gov/metrics/hap/output_hap_metric.py +110 -0
  96. ibm_watsonx_gov/metrics/harm/__init__.py +8 -0
  97. ibm_watsonx_gov/metrics/harm/harm_decorator.py +60 -0
  98. ibm_watsonx_gov/metrics/harm/harm_metric.py +103 -0
  99. ibm_watsonx_gov/metrics/harm_engagement/__init__.py +8 -0
  100. ibm_watsonx_gov/metrics/harm_engagement/harm_engagement_decorator.py +61 -0
  101. ibm_watsonx_gov/metrics/harm_engagement/harm_engagement_metric.py +103 -0
  102. ibm_watsonx_gov/metrics/hit_rate/__init__.py +0 -0
  103. ibm_watsonx_gov/metrics/hit_rate/hit_rate_decorator.py +59 -0
  104. ibm_watsonx_gov/metrics/hit_rate/hit_rate_metric.py +167 -0
  105. ibm_watsonx_gov/metrics/input_token_count/__init__.py +8 -0
  106. ibm_watsonx_gov/metrics/input_token_count/input_token_count_decorator.py +58 -0
  107. ibm_watsonx_gov/metrics/input_token_count/input_token_count_metric.py +112 -0
  108. ibm_watsonx_gov/metrics/jailbreak/__init__.py +8 -0
  109. ibm_watsonx_gov/metrics/jailbreak/jailbreak_decorator.py +60 -0
  110. ibm_watsonx_gov/metrics/jailbreak/jailbreak_metric.py +103 -0
  111. ibm_watsonx_gov/metrics/keyword_detection/keyword_detection_decorator.py +58 -0
  112. ibm_watsonx_gov/metrics/keyword_detection/keyword_detection_metric.py +111 -0
  113. ibm_watsonx_gov/metrics/llm_validation/__init__.py +8 -0
  114. ibm_watsonx_gov/metrics/llm_validation/evaluation_criteria.py +84 -0
  115. ibm_watsonx_gov/metrics/llm_validation/llm_validation_constants.py +24 -0
  116. ibm_watsonx_gov/metrics/llm_validation/llm_validation_decorator.py +54 -0
  117. ibm_watsonx_gov/metrics/llm_validation/llm_validation_impl.py +525 -0
  118. ibm_watsonx_gov/metrics/llm_validation/llm_validation_metric.py +258 -0
  119. ibm_watsonx_gov/metrics/llm_validation/llm_validation_prompts.py +106 -0
  120. ibm_watsonx_gov/metrics/llmaj/__init__.py +0 -0
  121. ibm_watsonx_gov/metrics/llmaj/llmaj_metric.py +298 -0
  122. ibm_watsonx_gov/metrics/ndcg/__init__.py +0 -0
  123. ibm_watsonx_gov/metrics/ndcg/ndcg_decorator.py +61 -0
  124. ibm_watsonx_gov/metrics/ndcg/ndcg_metric.py +166 -0
  125. ibm_watsonx_gov/metrics/output_token_count/__init__.py +8 -0
  126. ibm_watsonx_gov/metrics/output_token_count/output_token_count_decorator.py +58 -0
  127. ibm_watsonx_gov/metrics/output_token_count/output_token_count_metric.py +112 -0
  128. ibm_watsonx_gov/metrics/pii/__init__.py +16 -0
  129. ibm_watsonx_gov/metrics/pii/input_pii_metric.py +102 -0
  130. ibm_watsonx_gov/metrics/pii/output_pii_metric.py +107 -0
  131. ibm_watsonx_gov/metrics/pii/pii_decorator.py +59 -0
  132. ibm_watsonx_gov/metrics/pii/pii_metric.py +96 -0
  133. ibm_watsonx_gov/metrics/profanity/__init__.py +8 -0
  134. ibm_watsonx_gov/metrics/profanity/profanity_decorator.py +60 -0
  135. ibm_watsonx_gov/metrics/profanity/profanity_metric.py +103 -0
  136. ibm_watsonx_gov/metrics/prompt_safety_risk/__init__.py +8 -0
  137. ibm_watsonx_gov/metrics/prompt_safety_risk/prompt_safety_risk_decorator.py +57 -0
  138. ibm_watsonx_gov/metrics/prompt_safety_risk/prompt_safety_risk_metric.py +128 -0
  139. ibm_watsonx_gov/metrics/reciprocal_rank/__init__.py +0 -0
  140. ibm_watsonx_gov/metrics/reciprocal_rank/reciprocal_rank_decorator.py +62 -0
  141. ibm_watsonx_gov/metrics/reciprocal_rank/reciprocal_rank_metric.py +162 -0
  142. ibm_watsonx_gov/metrics/regex_detection/regex_detection_decorator.py +58 -0
  143. ibm_watsonx_gov/metrics/regex_detection/regex_detection_metric.py +106 -0
  144. ibm_watsonx_gov/metrics/retrieval_precision/__init__.py +0 -0
  145. ibm_watsonx_gov/metrics/retrieval_precision/retrieval_precision_decorator.py +62 -0
  146. ibm_watsonx_gov/metrics/retrieval_precision/retrieval_precision_metric.py +170 -0
  147. ibm_watsonx_gov/metrics/sexual_content/__init__.py +8 -0
  148. ibm_watsonx_gov/metrics/sexual_content/sexual_content_decorator.py +61 -0
  149. ibm_watsonx_gov/metrics/sexual_content/sexual_content_metric.py +103 -0
  150. ibm_watsonx_gov/metrics/social_bias/__init__.py +8 -0
  151. ibm_watsonx_gov/metrics/social_bias/social_bias_decorator.py +62 -0
  152. ibm_watsonx_gov/metrics/social_bias/social_bias_metric.py +103 -0
  153. ibm_watsonx_gov/metrics/status/__init__.py +0 -0
  154. ibm_watsonx_gov/metrics/status/status_metric.py +113 -0
  155. ibm_watsonx_gov/metrics/text_grade_level/__init__.py +8 -0
  156. ibm_watsonx_gov/metrics/text_grade_level/text_grade_level_decorator.py +59 -0
  157. ibm_watsonx_gov/metrics/text_grade_level/text_grade_level_metric.py +127 -0
  158. ibm_watsonx_gov/metrics/text_reading_ease/__init__.py +8 -0
  159. ibm_watsonx_gov/metrics/text_reading_ease/text_reading_ease_decorator.py +59 -0
  160. ibm_watsonx_gov/metrics/text_reading_ease/text_reading_ease_metric.py +123 -0
  161. ibm_watsonx_gov/metrics/tool_call_accuracy/__init__.py +0 -0
  162. ibm_watsonx_gov/metrics/tool_call_accuracy/tool_call_accuracy_decorator.py +67 -0
  163. ibm_watsonx_gov/metrics/tool_call_accuracy/tool_call_accuracy_metric.py +162 -0
  164. ibm_watsonx_gov/metrics/tool_call_parameter_accuracy/__init__.py +0 -0
  165. ibm_watsonx_gov/metrics/tool_call_parameter_accuracy/tool_call_parameter_accuracy_decorator.py +68 -0
  166. ibm_watsonx_gov/metrics/tool_call_parameter_accuracy/tool_call_parameter_accuracy_metric.py +151 -0
  167. ibm_watsonx_gov/metrics/tool_call_relevance/__init__.py +0 -0
  168. ibm_watsonx_gov/metrics/tool_call_relevance/tool_call_relevance_decorator.py +71 -0
  169. ibm_watsonx_gov/metrics/tool_call_relevance/tool_call_relevance_metric.py +166 -0
  170. ibm_watsonx_gov/metrics/tool_call_syntactic_accuracy/__init__.py +0 -0
  171. ibm_watsonx_gov/metrics/tool_call_syntactic_accuracy/tool_call_syntactic_accuracy_decorator.py +66 -0
  172. ibm_watsonx_gov/metrics/tool_call_syntactic_accuracy/tool_call_syntactic_accuracy_metric.py +121 -0
  173. ibm_watsonx_gov/metrics/topic_relevance/__init__.py +8 -0
  174. ibm_watsonx_gov/metrics/topic_relevance/topic_relevance_decorator.py +57 -0
  175. ibm_watsonx_gov/metrics/topic_relevance/topic_relevance_metric.py +106 -0
  176. ibm_watsonx_gov/metrics/unethical_behavior/__init__.py +8 -0
  177. ibm_watsonx_gov/metrics/unethical_behavior/unethical_behavior_decorator.py +61 -0
  178. ibm_watsonx_gov/metrics/unethical_behavior/unethical_behavior_metric.py +103 -0
  179. ibm_watsonx_gov/metrics/unsuccessful_requests/__init__.py +0 -0
  180. ibm_watsonx_gov/metrics/unsuccessful_requests/unsuccessful_requests_decorator.py +66 -0
  181. ibm_watsonx_gov/metrics/unsuccessful_requests/unsuccessful_requests_metric.py +128 -0
  182. ibm_watsonx_gov/metrics/user_id/__init__.py +0 -0
  183. ibm_watsonx_gov/metrics/user_id/user_id_metric.py +111 -0
  184. ibm_watsonx_gov/metrics/utils.py +440 -0
  185. ibm_watsonx_gov/metrics/violence/__init__.py +8 -0
  186. ibm_watsonx_gov/metrics/violence/violence_decorator.py +60 -0
  187. ibm_watsonx_gov/metrics/violence/violence_metric.py +103 -0
  188. ibm_watsonx_gov/prompt_evaluator/__init__.py +9 -0
  189. ibm_watsonx_gov/prompt_evaluator/impl/__init__.py +8 -0
  190. ibm_watsonx_gov/prompt_evaluator/impl/prompt_evaluator_impl.py +554 -0
  191. ibm_watsonx_gov/prompt_evaluator/impl/pta_lifecycle_evaluator.py +2332 -0
  192. ibm_watsonx_gov/prompt_evaluator/prompt_evaluator.py +262 -0
  193. ibm_watsonx_gov/providers/__init__.py +8 -0
  194. ibm_watsonx_gov/providers/detectors_provider.cpython-313-darwin.so +0 -0
  195. ibm_watsonx_gov/providers/detectors_provider.py +415 -0
  196. ibm_watsonx_gov/providers/eval_assist_provider.cpython-313-darwin.so +0 -0
  197. ibm_watsonx_gov/providers/eval_assist_provider.py +266 -0
  198. ibm_watsonx_gov/providers/inference_engines/__init__.py +0 -0
  199. ibm_watsonx_gov/providers/inference_engines/custom_inference_engine.py +165 -0
  200. ibm_watsonx_gov/providers/inference_engines/portkey_inference_engine.py +57 -0
  201. ibm_watsonx_gov/providers/llmevalkit/__init__.py +0 -0
  202. ibm_watsonx_gov/providers/llmevalkit/ciso_agent/main.py +516 -0
  203. ibm_watsonx_gov/providers/llmevalkit/ciso_agent/preprocess_log.py +111 -0
  204. ibm_watsonx_gov/providers/llmevalkit/ciso_agent/utils.py +186 -0
  205. ibm_watsonx_gov/providers/llmevalkit/function_calling/README.md +411 -0
  206. ibm_watsonx_gov/providers/llmevalkit/function_calling/__init__.py +27 -0
  207. ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/README.md +306 -0
  208. ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/__init__.py +89 -0
  209. ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/comparators/__init__.py +30 -0
  210. ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/comparators/base.py +411 -0
  211. ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/comparators/code_agent.py +1254 -0
  212. ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/comparators/exact_match.py +134 -0
  213. ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/comparators/fuzzy_string.py +104 -0
  214. ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/comparators/hybrid.py +516 -0
  215. ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/comparators/llm_judge.py +1882 -0
  216. ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/pipeline.py +387 -0
  217. ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/types.py +178 -0
  218. ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/utils.py +298 -0
  219. ibm_watsonx_gov/providers/llmevalkit/function_calling/consts.py +33 -0
  220. ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/__init__.py +31 -0
  221. ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/base.py +26 -0
  222. ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/function_call/__init__.py +4 -0
  223. ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/function_call/general.py +46 -0
  224. ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/function_call/general_metrics.json +783 -0
  225. ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/function_call/general_metrics_runtime.json +580 -0
  226. ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/function_selection/__init__.py +6 -0
  227. ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/function_selection/function_selection.py +28 -0
  228. ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/function_selection/function_selection_metrics.json +599 -0
  229. ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/function_selection/function_selection_metrics_runtime.json +477 -0
  230. ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/loader.py +259 -0
  231. ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/parameter/__init__.py +7 -0
  232. ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/parameter/parameter.py +52 -0
  233. ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/parameter/parameter_metrics.json +613 -0
  234. ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/parameter/parameter_metrics_runtime.json +489 -0
  235. ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/trajectory/__init__.py +7 -0
  236. ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/trajectory/trajectory.py +43 -0
  237. ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/trajectory/trajectory_metrics.json +161 -0
  238. ibm_watsonx_gov/providers/llmevalkit/function_calling/pipeline/__init__.py +0 -0
  239. ibm_watsonx_gov/providers/llmevalkit/function_calling/pipeline/adapters.py +102 -0
  240. ibm_watsonx_gov/providers/llmevalkit/function_calling/pipeline/pipeline.py +355 -0
  241. ibm_watsonx_gov/providers/llmevalkit/function_calling/pipeline/semantic_checker.py +816 -0
  242. ibm_watsonx_gov/providers/llmevalkit/function_calling/pipeline/static_checker.py +297 -0
  243. ibm_watsonx_gov/providers/llmevalkit/function_calling/pipeline/transformation_prompts.py +509 -0
  244. ibm_watsonx_gov/providers/llmevalkit/function_calling/pipeline/types.py +596 -0
  245. ibm_watsonx_gov/providers/llmevalkit/llm/README.md +375 -0
  246. ibm_watsonx_gov/providers/llmevalkit/llm/__init__.py +137 -0
  247. ibm_watsonx_gov/providers/llmevalkit/llm/base.py +426 -0
  248. ibm_watsonx_gov/providers/llmevalkit/llm/output_parser.py +364 -0
  249. ibm_watsonx_gov/providers/llmevalkit/llm/providers/__init__.py +0 -0
  250. ibm_watsonx_gov/providers/llmevalkit/llm/providers/consts.py +7 -0
  251. ibm_watsonx_gov/providers/llmevalkit/llm/providers/ibm_watsonx_ai/__init__.py +0 -0
  252. ibm_watsonx_gov/providers/llmevalkit/llm/providers/ibm_watsonx_ai/ibm_watsonx_ai.py +656 -0
  253. ibm_watsonx_gov/providers/llmevalkit/llm/providers/litellm/__init__.py +0 -0
  254. ibm_watsonx_gov/providers/llmevalkit/llm/providers/litellm/litellm.py +509 -0
  255. ibm_watsonx_gov/providers/llmevalkit/llm/providers/litellm/rits.py +224 -0
  256. ibm_watsonx_gov/providers/llmevalkit/llm/providers/litellm/watsonx.py +60 -0
  257. ibm_watsonx_gov/providers/llmevalkit/llm/providers/mock_llm_client.py +75 -0
  258. ibm_watsonx_gov/providers/llmevalkit/llm/providers/openai/__init__.py +0 -0
  259. ibm_watsonx_gov/providers/llmevalkit/llm/providers/openai/openai.py +639 -0
  260. ibm_watsonx_gov/providers/llmevalkit/llm/providers/wxo_ai_gateway/__init__.py +0 -0
  261. ibm_watsonx_gov/providers/llmevalkit/llm/providers/wxo_ai_gateway/wxo_ai_gateway.py +134 -0
  262. ibm_watsonx_gov/providers/llmevalkit/llm/providers/wxo_ai_gateway/wxo_ai_gateway_inference.py +214 -0
  263. ibm_watsonx_gov/providers/llmevalkit/llm/types.py +136 -0
  264. ibm_watsonx_gov/providers/llmevalkit/metrics/__init__.py +4 -0
  265. ibm_watsonx_gov/providers/llmevalkit/metrics/field.py +255 -0
  266. ibm_watsonx_gov/providers/llmevalkit/metrics/metric.py +332 -0
  267. ibm_watsonx_gov/providers/llmevalkit/metrics/metrics_runner.py +188 -0
  268. ibm_watsonx_gov/providers/llmevalkit/metrics/prompt.py +403 -0
  269. ibm_watsonx_gov/providers/llmevalkit/metrics/utils.py +46 -0
  270. ibm_watsonx_gov/providers/llmevalkit/prompt/__init__.py +0 -0
  271. ibm_watsonx_gov/providers/llmevalkit/prompt/runner.py +144 -0
  272. ibm_watsonx_gov/providers/tool_call_metric_provider.py +455 -0
  273. ibm_watsonx_gov/providers/unitxt_provider.cpython-313-darwin.so +0 -0
  274. ibm_watsonx_gov/tools/__init__.py +10 -0
  275. ibm_watsonx_gov/tools/clients/__init__.py +11 -0
  276. ibm_watsonx_gov/tools/clients/ai_tool_client.py +405 -0
  277. ibm_watsonx_gov/tools/clients/detector_client.py +82 -0
  278. ibm_watsonx_gov/tools/core/__init__.py +8 -0
  279. ibm_watsonx_gov/tools/core/tool_loader.py +237 -0
  280. ibm_watsonx_gov/tools/entities/__init__.py +8 -0
  281. ibm_watsonx_gov/tools/entities/ai_tools.py +435 -0
  282. ibm_watsonx_gov/tools/onboarding/create/answer_relevance_detector.json +57 -0
  283. ibm_watsonx_gov/tools/onboarding/create/chromadb_retrieval_tool.json +63 -0
  284. ibm_watsonx_gov/tools/onboarding/create/context_relevance_detector.json +57 -0
  285. ibm_watsonx_gov/tools/onboarding/create/duduckgo_search_tool.json +53 -0
  286. ibm_watsonx_gov/tools/onboarding/create/google_search_tool.json +62 -0
  287. ibm_watsonx_gov/tools/onboarding/create/hap_detector.json +70 -0
  288. ibm_watsonx_gov/tools/onboarding/create/jailbreak_detector.json +70 -0
  289. ibm_watsonx_gov/tools/onboarding/create/pii_detector.json +36 -0
  290. ibm_watsonx_gov/tools/onboarding/create/prompt_safety_risk_detector.json +69 -0
  291. ibm_watsonx_gov/tools/onboarding/create/topic_relevance_detector.json +57 -0
  292. ibm_watsonx_gov/tools/onboarding/create/weather_tool.json +39 -0
  293. ibm_watsonx_gov/tools/onboarding/create/webcrawler_tool.json +34 -0
  294. ibm_watsonx_gov/tools/onboarding/create/wikipedia_search_tool.json +53 -0
  295. ibm_watsonx_gov/tools/onboarding/delete/delete_tools.json +4 -0
  296. ibm_watsonx_gov/tools/onboarding/update/google_search_tool.json +38 -0
  297. ibm_watsonx_gov/tools/ootb/__init__.py +8 -0
  298. ibm_watsonx_gov/tools/ootb/detectors/__init__.py +8 -0
  299. ibm_watsonx_gov/tools/ootb/detectors/hap_detector_tool.py +109 -0
  300. ibm_watsonx_gov/tools/ootb/detectors/jailbreak_detector_tool.py +104 -0
  301. ibm_watsonx_gov/tools/ootb/detectors/pii_detector_tool.py +83 -0
  302. ibm_watsonx_gov/tools/ootb/detectors/prompt_safety_risk_detector_tool.py +111 -0
  303. ibm_watsonx_gov/tools/ootb/detectors/topic_relevance_detector_tool.py +101 -0
  304. ibm_watsonx_gov/tools/ootb/rag/__init__.py +8 -0
  305. ibm_watsonx_gov/tools/ootb/rag/answer_relevance_detector_tool.py +119 -0
  306. ibm_watsonx_gov/tools/ootb/rag/context_relevance_detector_tool.py +118 -0
  307. ibm_watsonx_gov/tools/ootb/search/__init__.py +8 -0
  308. ibm_watsonx_gov/tools/ootb/search/duckduckgo_search_tool.py +62 -0
  309. ibm_watsonx_gov/tools/ootb/search/google_search_tool.py +105 -0
  310. ibm_watsonx_gov/tools/ootb/search/weather_tool.py +95 -0
  311. ibm_watsonx_gov/tools/ootb/search/web_crawler_tool.py +69 -0
  312. ibm_watsonx_gov/tools/ootb/search/wikipedia_search_tool.py +63 -0
  313. ibm_watsonx_gov/tools/ootb/vectordb/__init__.py +8 -0
  314. ibm_watsonx_gov/tools/ootb/vectordb/chromadb_retriever_tool.py +111 -0
  315. ibm_watsonx_gov/tools/rest_api/__init__.py +10 -0
  316. ibm_watsonx_gov/tools/rest_api/restapi_tool.py +72 -0
  317. ibm_watsonx_gov/tools/schemas/__init__.py +10 -0
  318. ibm_watsonx_gov/tools/schemas/search_tool_schema.py +46 -0
  319. ibm_watsonx_gov/tools/schemas/vectordb_retrieval_schema.py +55 -0
  320. ibm_watsonx_gov/tools/utils/__init__.py +14 -0
  321. ibm_watsonx_gov/tools/utils/constants.py +69 -0
  322. ibm_watsonx_gov/tools/utils/display_utils.py +38 -0
  323. ibm_watsonx_gov/tools/utils/environment.py +108 -0
  324. ibm_watsonx_gov/tools/utils/package_utils.py +40 -0
  325. ibm_watsonx_gov/tools/utils/platform_url_mapping.cpython-313-darwin.so +0 -0
  326. ibm_watsonx_gov/tools/utils/python_utils.py +68 -0
  327. ibm_watsonx_gov/tools/utils/tool_utils.py +206 -0
  328. ibm_watsonx_gov/traces/__init__.py +8 -0
  329. ibm_watsonx_gov/traces/span_exporter.py +195 -0
  330. ibm_watsonx_gov/traces/span_node.py +251 -0
  331. ibm_watsonx_gov/traces/span_util.py +153 -0
  332. ibm_watsonx_gov/traces/trace_utils.py +1074 -0
  333. ibm_watsonx_gov/utils/__init__.py +8 -0
  334. ibm_watsonx_gov/utils/aggregation_util.py +346 -0
  335. ibm_watsonx_gov/utils/async_util.py +62 -0
  336. ibm_watsonx_gov/utils/authenticator.py +144 -0
  337. ibm_watsonx_gov/utils/constants.py +15 -0
  338. ibm_watsonx_gov/utils/errors.py +40 -0
  339. ibm_watsonx_gov/utils/gov_sdk_logger.py +39 -0
  340. ibm_watsonx_gov/utils/insights_generator.py +1285 -0
  341. ibm_watsonx_gov/utils/python_utils.py +425 -0
  342. ibm_watsonx_gov/utils/rest_util.py +73 -0
  343. ibm_watsonx_gov/utils/segment_batch_manager.py +162 -0
  344. ibm_watsonx_gov/utils/singleton_meta.py +25 -0
  345. ibm_watsonx_gov/utils/url_mapping.cpython-313-darwin.so +0 -0
  346. ibm_watsonx_gov/utils/validation_util.py +126 -0
  347. ibm_watsonx_gov/visualizations/__init__.py +13 -0
  348. ibm_watsonx_gov/visualizations/metric_descriptions.py +57 -0
  349. ibm_watsonx_gov/visualizations/model_insights.py +1304 -0
  350. ibm_watsonx_gov/visualizations/visualization_utils.py +75 -0
  351. ibm_watsonx_gov-1.3.3.dist-info/METADATA +93 -0
  352. ibm_watsonx_gov-1.3.3.dist-info/RECORD +353 -0
  353. ibm_watsonx_gov-1.3.3.dist-info/WHEEL +6 -0
@@ -0,0 +1,425 @@
1
+ # ----------------------------------------------------------------------------------------------------
2
+ # IBM Confidential
3
+ # Licensed Materials - Property of IBM
4
+ # 5737-H76, 5900-A3Q
5
+ # © Copyright IBM Corp. 2025 All Rights Reserved.
6
+ # US Government Users Restricted Rights - Use, duplication or disclosure restricted by
7
+ # GSA ADPSchedule Contract with IBM Corp.
8
+ # ----------------------------------------------------------------------------------------------------
9
+
10
+ import enum
11
+ import inspect
12
+ import json
13
+ import os
14
+ from functools import reduce
15
+ from inspect import signature
16
+ from typing import (Any, Callable, Dict, List, Optional, TypeVar, Union,
17
+ get_args, get_origin)
18
+ import time
19
+ from contextlib import contextmanager, asynccontextmanager
20
+
21
+ import pandas as pd
22
+ from ibm_cloud_sdk_core.authenticators import BearerTokenAuthenticator
23
+ from pydantic import BaseModel
24
+
25
+ from ibm_watsonx_gov.utils.authenticator import Authenticator
26
+ from ibm_watsonx_gov.utils.gov_sdk_logger import GovSDKLogger
27
+
28
+ try:
29
+ from langchain_core.tools import BaseTool
30
+ from langchain_core.utils.function_calling import \
31
+ convert_to_openai_function
32
+ except ImportError:
33
+ pass
34
+
35
+ logger = GovSDKLogger.get_logger(__name__)
36
+
37
+ def convert_df_to_list(df: pd.DataFrame, features: list):
38
+ """
39
+ Method to convert pandas df to 2d array
40
+ """
41
+ if len(features) > 0:
42
+ return df[features].values.tolist()
43
+ else:
44
+ return df.values.tolist()
45
+
46
+
47
+ def convert_to_list_of_lists(value):
48
+ """Method to convert a dataframe column to list of lists
49
+
50
+ Args:
51
+ value (_type_): DataFrame column
52
+
53
+ Returns:
54
+ _type_: _description_
55
+ """
56
+ if isinstance(value, list):
57
+ # Check if it's already a list of lists
58
+ if all(isinstance(i, list) for i in value):
59
+ return value
60
+ # If it's a list of strings, wrap it to make it a list of lists
61
+ elif all(isinstance(i, str) for i in value):
62
+ return [value]
63
+ # Return empty list if value is None or 'NULL' string
64
+ return []
65
+
66
+
67
+ def get(dictionary, keys: str, default=None):
68
+ return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)
69
+
70
+
71
+ def find_instance(dict_: dict, class_type: Any) -> Any:
72
+ """
73
+ Find the first instance of a class in a dictionary.
74
+
75
+ Args:
76
+ dict_ (dict): The dictionary to search.
77
+ class_type (Any): The class type to find.
78
+
79
+ Returns:
80
+ Any: The first instance of the class found in the dictionary, or None if no instance is found.
81
+ """
82
+ values = [value for value in dict_.values(
83
+ ) if isinstance(value, class_type)]
84
+ return values[0] if len(values) > 0 else None
85
+
86
+
87
+ def get_argument_value(func: Callable, args: tuple, kwargs: dict, param_name: str) -> Any:
88
+ """
89
+ Gets an argument value from the arguments or keyword arguments of a function.
90
+
91
+ Args:
92
+ func (Callable): The function to get the argument value from.
93
+ args (tuple): The arguments of the function.
94
+ kwargs (dict): The keyword arguments of the function.
95
+ param_name (str): The parameter name to get the value for.
96
+
97
+ Raises:
98
+ ValueError: If the parameter name is not found in the function signature.
99
+ Exception: If any TypeError is found while getting the argument
100
+
101
+ Returns:
102
+ Any: The argument value
103
+ """
104
+ try:
105
+ sig = signature(func)
106
+ bound_args = sig.bind(*args, **kwargs)
107
+ bound_args.apply_defaults()
108
+
109
+ # 0. Check if one of the arguments is an instance of EvaluationState
110
+ from ..entities.state import EvaluationState
111
+ state_var = find_instance(dict_=bound_args.arguments,
112
+ class_type=EvaluationState)
113
+ if state_var is not None:
114
+ if not hasattr(state_var, param_name):
115
+ raise ValueError(
116
+ f"'{param_name}' attribute missing from the state.")
117
+ return getattr(state_var, param_name)
118
+
119
+ # 1. Check if one of the arguments is named "state"
120
+ if "state" in bound_args.arguments:
121
+ state_var = bound_args.arguments.get("state")
122
+
123
+ # 1.1 If yes, check if the argument type is a BaseModel or Dict
124
+ # 1.1.1.1 If BaseModel, get the argument value from state's attribute. If not present, throw error.
125
+ if isinstance(state_var, BaseModel):
126
+ if not hasattr(state_var, param_name):
127
+ raise ValueError(
128
+ f"'{param_name}' attribute missing from the state.")
129
+ return getattr(state_var, param_name)
130
+ # 1.1.1.2 If Dict, get the argument value from state's key. If not present, throw error.
131
+ if isinstance(state_var, dict):
132
+ if param_name not in state_var:
133
+ raise ValueError(
134
+ f"'{param_name}' key missing from the state.")
135
+ return state_var[param_name]
136
+ # 1.1.1 If not, no-op. Continue below
137
+
138
+ # 2. Check if the argument is passed as a keyword argument. If not present, throw error.
139
+ if param_name not in bound_args.arguments:
140
+ raise ValueError(
141
+ f"{param_name} argument missing from the function.")
142
+
143
+ return bound_args.arguments.get(param_name)
144
+ except TypeError as te:
145
+ raise Exception(
146
+ f"Got an error while getting {param_name} argument.") from te
147
+
148
+
149
+ T = TypeVar("T")
150
+
151
+
152
+ def add_if_unique(obj: T, obj_list: List[T], keys: List[str], aggregate_keys: Optional[List] = None) -> None:
153
+ """
154
+ Add an object to a list only if there is no existing object that matches all the specified keys.
155
+ If a matching object exists, it aggregates values from the provided `aggregate_keys`
156
+ into the existing object, rather than adding a new entry.
157
+
158
+ Args:
159
+ obj: The object to potentially add to the list
160
+ obj_list: The list to which the object may be added
161
+ keys: A list of attribute names to check for uniqueness
162
+ aggregate_keys: A list of attribute names for which values from the `obj` will be aggregated into the existing object
163
+
164
+ """
165
+ # Check if any existing object matches all keys
166
+ for existing_obj in obj_list:
167
+ matches_all_keys = True
168
+ for key in keys:
169
+ if not hasattr(obj, key) or not hasattr(existing_obj, key):
170
+ matches_all_keys = False
171
+ break
172
+ if getattr(obj, key) != getattr(existing_obj, key):
173
+ matches_all_keys = False
174
+ break
175
+
176
+ if matches_all_keys:
177
+ # Found a match, don't add the object
178
+ # If list of aggregated keys are provided, add the values to existing object
179
+ if aggregate_keys:
180
+ for agg_key in aggregate_keys:
181
+ if hasattr(existing_obj, agg_key) and hasattr(obj, agg_key):
182
+ new_value = getattr(obj, agg_key)
183
+ existing_value = getattr(existing_obj, agg_key)
184
+
185
+ # Handle cases values are stored as a list, set or single value
186
+ if isinstance(existing_value, list):
187
+ # if isinstance(new_value, list):
188
+ # existing_value.extend(new_value)
189
+ # else:
190
+ # existing_value.append(new_value)
191
+ # TODO: ensure uniqueness
192
+ pass
193
+ elif isinstance(existing_value, set):
194
+ if isinstance(new_value, (list, set)):
195
+ existing_value.update(new_value)
196
+ else:
197
+ existing_value.add(new_value)
198
+ else:
199
+ setattr(existing_obj, agg_key, new_value)
200
+ return
201
+
202
+ # No match found, add the object to the list
203
+ obj_list.append(obj)
204
+
205
+
206
+ def transform_str_to_list(input_str: str) -> list[str]:
207
+ """
208
+ Parse the context columns correctly and make sure it is a list and not a string. This is intended
209
+ to be used with pd.DataFrame.apply() method. Specifically, in some cased where values are read from
210
+ csv files and cells are intended to contain a list, pandas will parse them as strings, this helper
211
+ would parse them as expected
212
+
213
+ Args:
214
+ input_str (str): the cell content
215
+
216
+ Returns:
217
+ list of strings
218
+
219
+ """
220
+ contexts_list = []
221
+ # The input is a list already, no need to update it
222
+ if isinstance(input_str, list):
223
+ contexts_list = input_str
224
+
225
+ # The input is parsed as a string, check if it is lateral array and parse it, other wise add it to a list and return it
226
+ elif isinstance(input_str, str):
227
+ try:
228
+ contexts_list = json.loads(input_str)
229
+ except json.decoder.JSONDecodeError:
230
+ # The user only gave one context column
231
+ contexts_list = [input_str]
232
+
233
+ return contexts_list if contexts_list else [""]
234
+
235
+
236
+ def build_openai_schema(tool_instance):
237
+ schema = tool_instance.args_schema.schema()
238
+ return {
239
+ "name": tool_instance.name,
240
+ "description": tool_instance.description or inspect.getdoc(tool_instance.__class__),
241
+ "parameters": schema
242
+ }
243
+
244
+
245
+ def parse_functions_to_openai_schema(func: Callable) -> dict:
246
+ """
247
+ Converts a callable function into a structured schema for LLM tool calling.
248
+
249
+ Extracts function metadata including name, description, parameters, parameter types,
250
+ and required parameters.
251
+ Returns the schema in OpenAI-compatible format with 'type': 'function' and nested structure.
252
+ """
253
+ try:
254
+ if isinstance(func, BaseTool):
255
+ cur_openai_tool = convert_to_openai_function(func.args_schema)
256
+ properties = get(cur_openai_tool, "parameters.properties", {})
257
+ required = get(cur_openai_tool, "parameters.required", [])
258
+
259
+ return {
260
+ "type": "function",
261
+ "function": {
262
+ "name": func.name,
263
+ "description": func.description,
264
+ "parameters": {
265
+ "type": "object",
266
+ "properties": properties,
267
+ "required": required,
268
+ }
269
+ }
270
+ }
271
+
272
+ elif callable(func):
273
+ sig = signature(func)
274
+ properties = {}
275
+ required = []
276
+
277
+ for name, param in sig.parameters.items():
278
+ param_schema = {"type": "string"} # default fallback
279
+
280
+ if param.annotation != param.empty:
281
+ param_schema = convert_python_type_to_openai_schema(
282
+ param.annotation)
283
+
284
+ properties[name] = param_schema
285
+
286
+ # Identify required parameters (not Optional and no default)
287
+ origin = get_origin(param.annotation)
288
+ args = get_args(param.annotation)
289
+ if not (origin is Union and type(None) in args) and param.default is param.empty:
290
+ required.append(name)
291
+
292
+ return {
293
+ "type": "function",
294
+ "function": {
295
+ "name": func.__name__,
296
+ "description": func.__doc__ or "No description available",
297
+ "parameters": {
298
+ "type": "object",
299
+ "properties": properties,
300
+ "required": required,
301
+ }
302
+ }
303
+ }
304
+
305
+ else:
306
+ raise ValueError(f"Unsupported tool type: {type(func)}")
307
+
308
+ except Exception as ex:
309
+ raise Exception(f"Error while parsing {func}: {ex}")
310
+
311
+
312
+ def convert_python_type_to_openai_schema(param_type):
313
+ """Convert a Python type into an OpenAPI-like schema."""
314
+ if param_type == str:
315
+ return {"type": "string"}
316
+ elif param_type == int:
317
+ return {"type": "integer"}
318
+ elif param_type == float:
319
+ return {"type": "number"}
320
+ elif param_type == bool:
321
+ return {"type": "boolean"}
322
+ elif isinstance(param_type, type) and issubclass(param_type, enum.Enum):
323
+ return {
324
+ "type": "string",
325
+ "enum": [e.value for e in param_type],
326
+ }
327
+ else:
328
+ origin = get_origin(param_type)
329
+ args = get_args(param_type)
330
+
331
+ if origin is Union:
332
+ # Handle Optional[X] == Union[X, NoneType]
333
+ non_none_args = [arg for arg in args if arg is not type(None)]
334
+ if len(non_none_args) == 1:
335
+ return convert_python_type_to_openai_schema(non_none_args[0])
336
+ else:
337
+ # Complex unions; fallback to string
338
+ return {"type": "string"}
339
+ elif origin in (list, List):
340
+ # Handle lists
341
+ # Assume list of strings if unspecified
342
+ item_type = args[0] if args else str
343
+ return {
344
+ "type": "array",
345
+ "items": convert_python_type_to_openai_schema(item_type),
346
+ }
347
+ elif origin in (dict, Dict):
348
+ # Handle dicts (simplified: assume dict[str, Any])
349
+ return {
350
+ "type": "object"
351
+ }
352
+ else:
353
+ # Unknown complex type
354
+ return {"type": "string"}
355
+
356
+
357
+ def get_environment_variable_value(possible_env_variables: list, default=None) -> str | None:
358
+ """
359
+ Helper to get environment variable based on list of option. This will return the first
360
+ found value, otherwise None
361
+
362
+ Args:
363
+ possible_env_variables (list[str]): list of environment variable to find
364
+ Returns:
365
+ str | None: the first found value, otherwise None
366
+ """
367
+ for env_key in possible_env_variables:
368
+ env_value = os.getenv(env_key)
369
+
370
+ if env_value:
371
+ return env_value
372
+ return default
373
+
374
+
375
+ def replace_none_with_empty_string(a_list: list):
376
+ """
377
+ Recursively replaces all None values in a nested list structure with empty strings.
378
+
379
+ Args:
380
+ a_list (list): The input list that may contain None values and nested lists.
381
+ Can be None itself (function will handle this case).
382
+
383
+ Returns:
384
+ list: The modified list with all None values replaced by empty strings.
385
+ Returns None if the input list is None.
386
+ """
387
+ if a_list is not None:
388
+ for i, n in enumerate(a_list):
389
+ if isinstance(n, list):
390
+ n = replace_none_with_empty_string(n)
391
+ if n is None:
392
+ a_list[i] = ""
393
+ return a_list
394
+
395
+
396
+ def get_authenticator_token(authenticator: Authenticator) -> str:
397
+ """
398
+ Helper to retrieve the authenticator token.
399
+
400
+ Args:
401
+ authenticator: Authenticator object from ibm_cloud_sdk_core.
402
+
403
+ Returns:
404
+ Bearer token string.
405
+ """
406
+ if isinstance(authenticator, BearerTokenAuthenticator):
407
+ return authenticator.bearer_token
408
+ else:
409
+ try:
410
+ return authenticator.token_manager.get_token()
411
+ except Exception as e:
412
+ raise Exception(f"Failed to get token from authenticator. {e}")
413
+
414
+ @contextmanager
415
+ def track_duration(name: str):
416
+ """
417
+ Track execution duration
418
+ """
419
+ start = time.perf_counter()
420
+ try:
421
+ yield
422
+ finally:
423
+ elapsed = time.perf_counter() - start
424
+ logger.info(f"Computed {name} in {elapsed:.3f}s")
425
+
@@ -0,0 +1,73 @@
1
+ # ----------------------------------------------------------------------------------------------------
2
+ # IBM Confidential
3
+ # Licensed Materials - Property of IBM
4
+ # 5737-H76, 5900-A3Q
5
+ # © Copyright IBM Corp. 2025 All Rights Reserved.
6
+ # US Government Users Restricted Rights - Use, duplication or disclosure restricted by
7
+ # GSA ADPSchedule Contract with IBM Corp.
8
+ # ----------------------------------------------------------------------------------------------------
9
+
10
+ # Supress info and warning logs from requests and urllib3
11
+ import logging
12
+ from http import HTTPStatus
13
+
14
+ import requests
15
+ from requests.adapters import HTTPAdapter
16
+ from urllib3.util.retry import Retry
17
+
18
+ logging.getLogger("requests").setLevel(logging.CRITICAL)
19
+ logging.getLogger("urllib3").setLevel(logging.CRITICAL)
20
+
21
+
22
+ class RestUtil:
23
+
24
+ RETRY_COUNT = 6
25
+ BACK_OFF_FACTOR = 0.5
26
+ RETRY_AFTER_STATUS_CODES = (
27
+ HTTPStatus.BAD_GATEWAY,
28
+ HTTPStatus.SERVICE_UNAVAILABLE,
29
+ HTTPStatus.GATEWAY_TIMEOUT)
30
+
31
+ @staticmethod
32
+ def override_response_encoding(r, *args, **kwargs):
33
+ if r.encoding is None:
34
+ r.encoding = 'utf-8'
35
+ return r
36
+
37
+ @classmethod
38
+ def request_with_retry(cls, session=None, method_list=[], retry_count=RETRY_COUNT, **kwargs):
39
+ session = session or requests.Session()
40
+ session.hooks['response'].append(RestUtil.override_response_encoding)
41
+ if kwargs.get("verify_ssl") is False:
42
+ session.verify = False
43
+
44
+ # some requests might want to pass other status on which they have to be retried.
45
+ # such status should be passed as a tuple
46
+ additional_retry_status_codes = kwargs.get(
47
+ "additional_retry_status_codes", None)
48
+ if additional_retry_status_codes:
49
+ cls.RETRY_AFTER_STATUS_CODES = cls.RETRY_AFTER_STATUS_CODES + \
50
+ additional_retry_status_codes
51
+ # Get connection retry count from arguments. If not provided, default to retry_count.
52
+ connect_retry_count = kwargs.get(
53
+ "connect_retry_count", retry_count)
54
+ # Construct an iterable set of method to retry.
55
+ method_list = {item for item in method_list +
56
+ list(Retry.DEFAULT_ALLOWED_METHODS)}
57
+
58
+ back_off_factor = cls.BACK_OFF_FACTOR
59
+ if kwargs.get("back_off_factor") is not None:
60
+ back_off_factor = kwargs.get("back_off_factor")
61
+ retry = Retry(
62
+ total=retry_count,
63
+ read=retry_count,
64
+ connect=connect_retry_count,
65
+ allowed_methods=method_list,
66
+ # Time delay between requests is calculated using
67
+ # {backoff factor} * (2 ^ ({number of total retries} - 1)) seconds
68
+ backoff_factor=back_off_factor,
69
+ status_forcelist=cls.RETRY_AFTER_STATUS_CODES,
70
+ )
71
+ adapter = HTTPAdapter(max_retries=retry)
72
+ session.mount("https://", adapter)
73
+ return session
@@ -0,0 +1,162 @@
1
+ # ----------------------------------------------------------------------------------------------------
2
+ # IBM Confidential
3
+ # Licensed Materials - Property of IBM
4
+ # 5737-H76, 5900-A3Q
5
+ # © Copyright IBM Corp. 2025 All Rights Reserved.
6
+ # US Government Users Restricted Rights - Use, duplication or disclosure restricted by
7
+ # GSA ADPSchedule Contract with IBM Corp.
8
+ # ----------------------------------------------------------------------------------------------------
9
+ import asyncio
10
+ import atexit
11
+ import os
12
+ import threading
13
+ import time
14
+ from typing import Dict, List
15
+
16
+ from ibm_watsonx_gov.clients.segment_client import SegmentClient
17
+ from ibm_watsonx_gov.utils.async_util import (gather_with_concurrency,
18
+ run_in_event_loop)
19
+ from ibm_watsonx_gov.utils.gov_sdk_logger import GovSDKLogger
20
+
21
+ logger = GovSDKLogger.get_logger(__name__)
22
+
23
+ SEGMENT_BATCH_LIMIT = 10
24
+ SEGMENT_BATCH_INTERVAL = 5 # seconds
25
+
26
+
27
+ class SegmentBatchManager:
28
+ _instance = None
29
+ _lock = threading.Lock()
30
+
31
+ def __new__(cls, api_client=None):
32
+ """Ensure singleton instance across evaluators."""
33
+ with cls._lock:
34
+ if cls._instance is None:
35
+ cls._instance = super().__new__(cls)
36
+ cls._instance._initialize(api_client)
37
+ return cls._instance
38
+
39
+ def _initialize(self, api_client=None):
40
+ self.api_client = api_client
41
+ self.queue: asyncio.Queue[Dict] | None = None
42
+ self._stop_event = asyncio.Event()
43
+ self._thread = None
44
+ self._thread_loop: asyncio.AbstractEventLoop | None = None
45
+ # Start a background worker thread
46
+ self._start_background_worker()
47
+
48
+ # Auto-stop on process exit
49
+ atexit.register(self._shutdown)
50
+
51
+ def _start_background_worker(self):
52
+ """
53
+ Start a background thread with its own event loop to batch and send events.
54
+ This allows non-blocking event tracking from the other event tasks.
55
+ """
56
+
57
+ def runner():
58
+ """
59
+ Start a new event loop in a separate thread.
60
+ """
61
+ loop = asyncio.new_event_loop()
62
+ self._thread_loop = loop
63
+ asyncio.set_event_loop(loop)
64
+
65
+ self.queue = asyncio.Queue()
66
+ self._stop_event = asyncio.Event()
67
+
68
+ # Run the worker until stopped
69
+ loop.run_until_complete(self.worker())
70
+
71
+ # Start the thread as a daemon
72
+ self._thread = threading.Thread(target=runner, daemon=True)
73
+ self._thread.start()
74
+
75
+ async def track_event(self, event: Dict):
76
+ """Add event to queue."""
77
+
78
+ if os.getenv("WATSONX_SERVER") in ["WXO", "WXAI", "WXGOV"]:
79
+ return
80
+
81
+ if self.api_client is None:
82
+ return
83
+
84
+ if self.api_client.is_cpd:
85
+ return
86
+
87
+ if not hasattr(self.api_client, "wos_client"):
88
+ return
89
+
90
+ if not hasattr(self.api_client.wos_client, "service_instance_id"):
91
+ return
92
+
93
+ data = {
94
+ "event": "API Call",
95
+ "properties": {
96
+ "productCodeType": "WWPC",
97
+ "ut30": "30A5Q",
98
+ "productTitle": "watsonx governance",
99
+ "productCode": "WW0170",
100
+ "custom.triggered_by": "watsonx gov sdk",
101
+ "region": self.api_client.credentials.region or "us-south",
102
+ "instanceID": self.api_client.wos_client.service_instance_id,
103
+ "productPlan": self.api_client.wos_client.plan_name,
104
+ **event
105
+ },
106
+ "integrations": {
107
+ "Amplitude": {
108
+ "groups": {
109
+ "Instance": self.api_client.wos_client.service_instance_id
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ # Thread-safe submission to background loop
116
+ if self._thread_loop and self.queue:
117
+ fut = asyncio.run_coroutine_threadsafe(
118
+ self.queue.put(data), self._thread_loop
119
+ )
120
+ await asyncio.wrap_future(fut)
121
+
122
+ def _shutdown(self):
123
+ """Stop worker at process exit."""
124
+ if self._thread_loop and self._stop_event:
125
+ self._thread_loop.call_soon_threadsafe(self._stop_event.set)
126
+
127
+ async def worker(self):
128
+ """Background loop: batch and send events."""
129
+ buffer: List[Dict] = []
130
+ last_flush = time.time()
131
+
132
+ while True:
133
+ # Exit when stop event set and queue empty
134
+ if self._stop_event.is_set() and self.queue.empty():
135
+ break
136
+
137
+ try:
138
+ event = await self.queue.get()
139
+ buffer.append(event)
140
+ except asyncio.TimeoutError:
141
+ pass # loop back and check stop_event
142
+
143
+ if buffer and (
144
+ len(buffer) >= SEGMENT_BATCH_LIMIT
145
+ or time.time() - last_flush >= SEGMENT_BATCH_INTERVAL
146
+ ):
147
+ success = await self._flush_async(buffer)
148
+ if success:
149
+ buffer.clear()
150
+ last_flush = time.time()
151
+
152
+ if buffer:
153
+ success = await self._flush_async(buffer)
154
+ if success:
155
+ buffer.clear()
156
+
157
+ async def _flush_async(self, events: List[Dict]):
158
+ if not events:
159
+ return True
160
+ logger.info(f"Flushing {len(events)} events")
161
+ segment_client = SegmentClient(self.api_client.wos_client)
162
+ return await segment_client.trigger_segment_endpoint(segment_data=events)
@@ -0,0 +1,25 @@
1
+ # ----------------------------------------------------------------------------------------------------
2
+ # IBM Confidential
3
+ # Licensed Materials - Property of IBM
4
+ # 5737-H76, 5900-A3Q
5
+ # © Copyright IBM Corp. 2025 All Rights Reserved.
6
+ # US Government Users Restricted Rights - Use, duplication or disclosure restricted by
7
+ # GSA ADPSchedule Contract with IBM Corp.
8
+ # ----------------------------------------------------------------------------------------------------
9
+
10
+ from threading import Lock
11
+
12
+ from pydantic._internal._model_construction import ModelMetaclass
13
+
14
+
15
+ class SingletonMeta(ModelMetaclass):
16
+ _instances = {}
17
+ _lock: Lock = Lock()
18
+
19
+ def __call__(cls, *args, **kwargs):
20
+ if cls not in cls._instances:
21
+ with cls._lock:
22
+ if cls not in cls._instances:
23
+ instance = super().__call__(*args, **kwargs)
24
+ cls._instances[cls] = instance
25
+ return cls._instances[cls]