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.
- ibm_watsonx_gov/__init__.py +8 -0
- ibm_watsonx_gov/agent_catalog/__init__.py +8 -0
- ibm_watsonx_gov/agent_catalog/clients/__init__.py +14 -0
- ibm_watsonx_gov/agent_catalog/clients/ai_agent_client.py +333 -0
- ibm_watsonx_gov/agent_catalog/core/__init__.py +8 -0
- ibm_watsonx_gov/agent_catalog/core/agent_loader.py +202 -0
- ibm_watsonx_gov/agent_catalog/core/agents.py +134 -0
- ibm_watsonx_gov/agent_catalog/entities/__init__.py +8 -0
- ibm_watsonx_gov/agent_catalog/entities/ai_agent.py +599 -0
- ibm_watsonx_gov/agent_catalog/utils/__init__.py +8 -0
- ibm_watsonx_gov/agent_catalog/utils/constants.py +36 -0
- ibm_watsonx_gov/agent_catalog/utils/notebook_utils.py +70 -0
- ibm_watsonx_gov/ai_experiments/__init__.py +8 -0
- ibm_watsonx_gov/ai_experiments/ai_experiments_client.py +980 -0
- ibm_watsonx_gov/ai_experiments/utils/__init__.py +8 -0
- ibm_watsonx_gov/ai_experiments/utils/ai_experiment_utils.py +139 -0
- ibm_watsonx_gov/clients/__init__.py +0 -0
- ibm_watsonx_gov/clients/api_client.py +99 -0
- ibm_watsonx_gov/clients/segment_client.py +46 -0
- ibm_watsonx_gov/clients/usage_client.cpython-313-darwin.so +0 -0
- ibm_watsonx_gov/clients/wx_ai_client.py +87 -0
- ibm_watsonx_gov/config/__init__.py +14 -0
- ibm_watsonx_gov/config/agentic_ai_configuration.py +225 -0
- ibm_watsonx_gov/config/gen_ai_configuration.py +129 -0
- ibm_watsonx_gov/config/model_risk_configuration.py +173 -0
- ibm_watsonx_gov/config/predictive_ai_configuration.py +20 -0
- ibm_watsonx_gov/entities/__init__.py +8 -0
- ibm_watsonx_gov/entities/agentic_app.py +209 -0
- ibm_watsonx_gov/entities/agentic_evaluation_result.py +185 -0
- ibm_watsonx_gov/entities/ai_evaluation.py +290 -0
- ibm_watsonx_gov/entities/ai_experiment.py +419 -0
- ibm_watsonx_gov/entities/base_classes.py +134 -0
- ibm_watsonx_gov/entities/container.py +54 -0
- ibm_watsonx_gov/entities/credentials.py +633 -0
- ibm_watsonx_gov/entities/criteria.py +508 -0
- ibm_watsonx_gov/entities/enums.py +274 -0
- ibm_watsonx_gov/entities/evaluation_result.py +444 -0
- ibm_watsonx_gov/entities/foundation_model.py +490 -0
- ibm_watsonx_gov/entities/llm_judge.py +44 -0
- ibm_watsonx_gov/entities/locale.py +17 -0
- ibm_watsonx_gov/entities/mapping.py +49 -0
- ibm_watsonx_gov/entities/metric.py +211 -0
- ibm_watsonx_gov/entities/metric_threshold.py +36 -0
- ibm_watsonx_gov/entities/model_provider.py +329 -0
- ibm_watsonx_gov/entities/model_risk_result.py +43 -0
- ibm_watsonx_gov/entities/monitor.py +71 -0
- ibm_watsonx_gov/entities/prompt_setup.py +40 -0
- ibm_watsonx_gov/entities/state.py +22 -0
- ibm_watsonx_gov/entities/utils.py +99 -0
- ibm_watsonx_gov/evaluators/__init__.py +26 -0
- ibm_watsonx_gov/evaluators/agentic_evaluator.py +2725 -0
- ibm_watsonx_gov/evaluators/agentic_traces_evaluator.py +115 -0
- ibm_watsonx_gov/evaluators/base_evaluator.py +22 -0
- ibm_watsonx_gov/evaluators/impl/__init__.py +0 -0
- ibm_watsonx_gov/evaluators/impl/evaluate_metrics_impl.cpython-313-darwin.so +0 -0
- ibm_watsonx_gov/evaluators/impl/evaluate_model_risk_impl.cpython-313-darwin.so +0 -0
- ibm_watsonx_gov/evaluators/metrics_evaluator.py +187 -0
- ibm_watsonx_gov/evaluators/model_risk_evaluator.py +89 -0
- ibm_watsonx_gov/evaluators/traces_evaluator.py +93 -0
- ibm_watsonx_gov/metric_groups/answer_quality/answer_quality_decorator.py +66 -0
- ibm_watsonx_gov/metric_groups/content_safety/content_safety_decorator.py +76 -0
- ibm_watsonx_gov/metric_groups/readability/readability_decorator.py +59 -0
- ibm_watsonx_gov/metric_groups/retrieval_quality/retrieval_quality_decorator.py +63 -0
- ibm_watsonx_gov/metric_groups/usage/usage_decorator.py +58 -0
- ibm_watsonx_gov/metrics/__init__.py +74 -0
- ibm_watsonx_gov/metrics/answer_relevance/__init__.py +8 -0
- ibm_watsonx_gov/metrics/answer_relevance/answer_relevance_decorator.py +63 -0
- ibm_watsonx_gov/metrics/answer_relevance/answer_relevance_metric.py +260 -0
- ibm_watsonx_gov/metrics/answer_similarity/__init__.py +0 -0
- ibm_watsonx_gov/metrics/answer_similarity/answer_similarity_decorator.py +66 -0
- ibm_watsonx_gov/metrics/answer_similarity/answer_similarity_metric.py +219 -0
- ibm_watsonx_gov/metrics/average_precision/__init__.py +0 -0
- ibm_watsonx_gov/metrics/average_precision/average_precision_decorator.py +62 -0
- ibm_watsonx_gov/metrics/average_precision/average_precision_metric.py +174 -0
- ibm_watsonx_gov/metrics/base_metric_decorator.py +193 -0
- ibm_watsonx_gov/metrics/context_relevance/__init__.py +8 -0
- ibm_watsonx_gov/metrics/context_relevance/context_relevance_decorator.py +60 -0
- ibm_watsonx_gov/metrics/context_relevance/context_relevance_metric.py +414 -0
- ibm_watsonx_gov/metrics/cost/__init__.py +8 -0
- ibm_watsonx_gov/metrics/cost/cost_decorator.py +58 -0
- ibm_watsonx_gov/metrics/cost/cost_metric.py +155 -0
- ibm_watsonx_gov/metrics/duration/__init__.py +8 -0
- ibm_watsonx_gov/metrics/duration/duration_decorator.py +59 -0
- ibm_watsonx_gov/metrics/duration/duration_metric.py +111 -0
- ibm_watsonx_gov/metrics/evasiveness/__init__.py +8 -0
- ibm_watsonx_gov/metrics/evasiveness/evasiveness_decorator.py +61 -0
- ibm_watsonx_gov/metrics/evasiveness/evasiveness_metric.py +103 -0
- ibm_watsonx_gov/metrics/faithfulness/__init__.py +8 -0
- ibm_watsonx_gov/metrics/faithfulness/faithfulness_decorator.py +65 -0
- ibm_watsonx_gov/metrics/faithfulness/faithfulness_metric.py +254 -0
- ibm_watsonx_gov/metrics/hap/__init__.py +16 -0
- ibm_watsonx_gov/metrics/hap/hap_decorator.py +58 -0
- ibm_watsonx_gov/metrics/hap/hap_metric.py +98 -0
- ibm_watsonx_gov/metrics/hap/input_hap_metric.py +104 -0
- ibm_watsonx_gov/metrics/hap/output_hap_metric.py +110 -0
- ibm_watsonx_gov/metrics/harm/__init__.py +8 -0
- ibm_watsonx_gov/metrics/harm/harm_decorator.py +60 -0
- ibm_watsonx_gov/metrics/harm/harm_metric.py +103 -0
- ibm_watsonx_gov/metrics/harm_engagement/__init__.py +8 -0
- ibm_watsonx_gov/metrics/harm_engagement/harm_engagement_decorator.py +61 -0
- ibm_watsonx_gov/metrics/harm_engagement/harm_engagement_metric.py +103 -0
- ibm_watsonx_gov/metrics/hit_rate/__init__.py +0 -0
- ibm_watsonx_gov/metrics/hit_rate/hit_rate_decorator.py +59 -0
- ibm_watsonx_gov/metrics/hit_rate/hit_rate_metric.py +167 -0
- ibm_watsonx_gov/metrics/input_token_count/__init__.py +8 -0
- ibm_watsonx_gov/metrics/input_token_count/input_token_count_decorator.py +58 -0
- ibm_watsonx_gov/metrics/input_token_count/input_token_count_metric.py +112 -0
- ibm_watsonx_gov/metrics/jailbreak/__init__.py +8 -0
- ibm_watsonx_gov/metrics/jailbreak/jailbreak_decorator.py +60 -0
- ibm_watsonx_gov/metrics/jailbreak/jailbreak_metric.py +103 -0
- ibm_watsonx_gov/metrics/keyword_detection/keyword_detection_decorator.py +58 -0
- ibm_watsonx_gov/metrics/keyword_detection/keyword_detection_metric.py +111 -0
- ibm_watsonx_gov/metrics/llm_validation/__init__.py +8 -0
- ibm_watsonx_gov/metrics/llm_validation/evaluation_criteria.py +84 -0
- ibm_watsonx_gov/metrics/llm_validation/llm_validation_constants.py +24 -0
- ibm_watsonx_gov/metrics/llm_validation/llm_validation_decorator.py +54 -0
- ibm_watsonx_gov/metrics/llm_validation/llm_validation_impl.py +525 -0
- ibm_watsonx_gov/metrics/llm_validation/llm_validation_metric.py +258 -0
- ibm_watsonx_gov/metrics/llm_validation/llm_validation_prompts.py +106 -0
- ibm_watsonx_gov/metrics/llmaj/__init__.py +0 -0
- ibm_watsonx_gov/metrics/llmaj/llmaj_metric.py +298 -0
- ibm_watsonx_gov/metrics/ndcg/__init__.py +0 -0
- ibm_watsonx_gov/metrics/ndcg/ndcg_decorator.py +61 -0
- ibm_watsonx_gov/metrics/ndcg/ndcg_metric.py +166 -0
- ibm_watsonx_gov/metrics/output_token_count/__init__.py +8 -0
- ibm_watsonx_gov/metrics/output_token_count/output_token_count_decorator.py +58 -0
- ibm_watsonx_gov/metrics/output_token_count/output_token_count_metric.py +112 -0
- ibm_watsonx_gov/metrics/pii/__init__.py +16 -0
- ibm_watsonx_gov/metrics/pii/input_pii_metric.py +102 -0
- ibm_watsonx_gov/metrics/pii/output_pii_metric.py +107 -0
- ibm_watsonx_gov/metrics/pii/pii_decorator.py +59 -0
- ibm_watsonx_gov/metrics/pii/pii_metric.py +96 -0
- ibm_watsonx_gov/metrics/profanity/__init__.py +8 -0
- ibm_watsonx_gov/metrics/profanity/profanity_decorator.py +60 -0
- ibm_watsonx_gov/metrics/profanity/profanity_metric.py +103 -0
- ibm_watsonx_gov/metrics/prompt_safety_risk/__init__.py +8 -0
- ibm_watsonx_gov/metrics/prompt_safety_risk/prompt_safety_risk_decorator.py +57 -0
- ibm_watsonx_gov/metrics/prompt_safety_risk/prompt_safety_risk_metric.py +128 -0
- ibm_watsonx_gov/metrics/reciprocal_rank/__init__.py +0 -0
- ibm_watsonx_gov/metrics/reciprocal_rank/reciprocal_rank_decorator.py +62 -0
- ibm_watsonx_gov/metrics/reciprocal_rank/reciprocal_rank_metric.py +162 -0
- ibm_watsonx_gov/metrics/regex_detection/regex_detection_decorator.py +58 -0
- ibm_watsonx_gov/metrics/regex_detection/regex_detection_metric.py +106 -0
- ibm_watsonx_gov/metrics/retrieval_precision/__init__.py +0 -0
- ibm_watsonx_gov/metrics/retrieval_precision/retrieval_precision_decorator.py +62 -0
- ibm_watsonx_gov/metrics/retrieval_precision/retrieval_precision_metric.py +170 -0
- ibm_watsonx_gov/metrics/sexual_content/__init__.py +8 -0
- ibm_watsonx_gov/metrics/sexual_content/sexual_content_decorator.py +61 -0
- ibm_watsonx_gov/metrics/sexual_content/sexual_content_metric.py +103 -0
- ibm_watsonx_gov/metrics/social_bias/__init__.py +8 -0
- ibm_watsonx_gov/metrics/social_bias/social_bias_decorator.py +62 -0
- ibm_watsonx_gov/metrics/social_bias/social_bias_metric.py +103 -0
- ibm_watsonx_gov/metrics/status/__init__.py +0 -0
- ibm_watsonx_gov/metrics/status/status_metric.py +113 -0
- ibm_watsonx_gov/metrics/text_grade_level/__init__.py +8 -0
- ibm_watsonx_gov/metrics/text_grade_level/text_grade_level_decorator.py +59 -0
- ibm_watsonx_gov/metrics/text_grade_level/text_grade_level_metric.py +127 -0
- ibm_watsonx_gov/metrics/text_reading_ease/__init__.py +8 -0
- ibm_watsonx_gov/metrics/text_reading_ease/text_reading_ease_decorator.py +59 -0
- ibm_watsonx_gov/metrics/text_reading_ease/text_reading_ease_metric.py +123 -0
- ibm_watsonx_gov/metrics/tool_call_accuracy/__init__.py +0 -0
- ibm_watsonx_gov/metrics/tool_call_accuracy/tool_call_accuracy_decorator.py +67 -0
- ibm_watsonx_gov/metrics/tool_call_accuracy/tool_call_accuracy_metric.py +162 -0
- ibm_watsonx_gov/metrics/tool_call_parameter_accuracy/__init__.py +0 -0
- ibm_watsonx_gov/metrics/tool_call_parameter_accuracy/tool_call_parameter_accuracy_decorator.py +68 -0
- ibm_watsonx_gov/metrics/tool_call_parameter_accuracy/tool_call_parameter_accuracy_metric.py +151 -0
- ibm_watsonx_gov/metrics/tool_call_relevance/__init__.py +0 -0
- ibm_watsonx_gov/metrics/tool_call_relevance/tool_call_relevance_decorator.py +71 -0
- ibm_watsonx_gov/metrics/tool_call_relevance/tool_call_relevance_metric.py +166 -0
- ibm_watsonx_gov/metrics/tool_call_syntactic_accuracy/__init__.py +0 -0
- ibm_watsonx_gov/metrics/tool_call_syntactic_accuracy/tool_call_syntactic_accuracy_decorator.py +66 -0
- ibm_watsonx_gov/metrics/tool_call_syntactic_accuracy/tool_call_syntactic_accuracy_metric.py +121 -0
- ibm_watsonx_gov/metrics/topic_relevance/__init__.py +8 -0
- ibm_watsonx_gov/metrics/topic_relevance/topic_relevance_decorator.py +57 -0
- ibm_watsonx_gov/metrics/topic_relevance/topic_relevance_metric.py +106 -0
- ibm_watsonx_gov/metrics/unethical_behavior/__init__.py +8 -0
- ibm_watsonx_gov/metrics/unethical_behavior/unethical_behavior_decorator.py +61 -0
- ibm_watsonx_gov/metrics/unethical_behavior/unethical_behavior_metric.py +103 -0
- ibm_watsonx_gov/metrics/unsuccessful_requests/__init__.py +0 -0
- ibm_watsonx_gov/metrics/unsuccessful_requests/unsuccessful_requests_decorator.py +66 -0
- ibm_watsonx_gov/metrics/unsuccessful_requests/unsuccessful_requests_metric.py +128 -0
- ibm_watsonx_gov/metrics/user_id/__init__.py +0 -0
- ibm_watsonx_gov/metrics/user_id/user_id_metric.py +111 -0
- ibm_watsonx_gov/metrics/utils.py +440 -0
- ibm_watsonx_gov/metrics/violence/__init__.py +8 -0
- ibm_watsonx_gov/metrics/violence/violence_decorator.py +60 -0
- ibm_watsonx_gov/metrics/violence/violence_metric.py +103 -0
- ibm_watsonx_gov/prompt_evaluator/__init__.py +9 -0
- ibm_watsonx_gov/prompt_evaluator/impl/__init__.py +8 -0
- ibm_watsonx_gov/prompt_evaluator/impl/prompt_evaluator_impl.py +554 -0
- ibm_watsonx_gov/prompt_evaluator/impl/pta_lifecycle_evaluator.py +2332 -0
- ibm_watsonx_gov/prompt_evaluator/prompt_evaluator.py +262 -0
- ibm_watsonx_gov/providers/__init__.py +8 -0
- ibm_watsonx_gov/providers/detectors_provider.cpython-313-darwin.so +0 -0
- ibm_watsonx_gov/providers/detectors_provider.py +415 -0
- ibm_watsonx_gov/providers/eval_assist_provider.cpython-313-darwin.so +0 -0
- ibm_watsonx_gov/providers/eval_assist_provider.py +266 -0
- ibm_watsonx_gov/providers/inference_engines/__init__.py +0 -0
- ibm_watsonx_gov/providers/inference_engines/custom_inference_engine.py +165 -0
- ibm_watsonx_gov/providers/inference_engines/portkey_inference_engine.py +57 -0
- ibm_watsonx_gov/providers/llmevalkit/__init__.py +0 -0
- ibm_watsonx_gov/providers/llmevalkit/ciso_agent/main.py +516 -0
- ibm_watsonx_gov/providers/llmevalkit/ciso_agent/preprocess_log.py +111 -0
- ibm_watsonx_gov/providers/llmevalkit/ciso_agent/utils.py +186 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/README.md +411 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/__init__.py +27 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/README.md +306 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/__init__.py +89 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/comparators/__init__.py +30 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/comparators/base.py +411 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/comparators/code_agent.py +1254 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/comparators/exact_match.py +134 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/comparators/fuzzy_string.py +104 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/comparators/hybrid.py +516 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/comparators/llm_judge.py +1882 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/pipeline.py +387 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/types.py +178 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/comparison/utils.py +298 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/consts.py +33 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/__init__.py +31 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/base.py +26 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/function_call/__init__.py +4 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/function_call/general.py +46 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/function_call/general_metrics.json +783 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/function_call/general_metrics_runtime.json +580 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/function_selection/__init__.py +6 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/function_selection/function_selection.py +28 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/function_selection/function_selection_metrics.json +599 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/function_selection/function_selection_metrics_runtime.json +477 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/loader.py +259 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/parameter/__init__.py +7 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/parameter/parameter.py +52 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/parameter/parameter_metrics.json +613 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/parameter/parameter_metrics_runtime.json +489 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/trajectory/__init__.py +7 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/trajectory/trajectory.py +43 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/metrics/trajectory/trajectory_metrics.json +161 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/pipeline/__init__.py +0 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/pipeline/adapters.py +102 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/pipeline/pipeline.py +355 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/pipeline/semantic_checker.py +816 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/pipeline/static_checker.py +297 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/pipeline/transformation_prompts.py +509 -0
- ibm_watsonx_gov/providers/llmevalkit/function_calling/pipeline/types.py +596 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/README.md +375 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/__init__.py +137 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/base.py +426 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/output_parser.py +364 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/providers/__init__.py +0 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/providers/consts.py +7 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/providers/ibm_watsonx_ai/__init__.py +0 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/providers/ibm_watsonx_ai/ibm_watsonx_ai.py +656 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/providers/litellm/__init__.py +0 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/providers/litellm/litellm.py +509 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/providers/litellm/rits.py +224 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/providers/litellm/watsonx.py +60 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/providers/mock_llm_client.py +75 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/providers/openai/__init__.py +0 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/providers/openai/openai.py +639 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/providers/wxo_ai_gateway/__init__.py +0 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/providers/wxo_ai_gateway/wxo_ai_gateway.py +134 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/providers/wxo_ai_gateway/wxo_ai_gateway_inference.py +214 -0
- ibm_watsonx_gov/providers/llmevalkit/llm/types.py +136 -0
- ibm_watsonx_gov/providers/llmevalkit/metrics/__init__.py +4 -0
- ibm_watsonx_gov/providers/llmevalkit/metrics/field.py +255 -0
- ibm_watsonx_gov/providers/llmevalkit/metrics/metric.py +332 -0
- ibm_watsonx_gov/providers/llmevalkit/metrics/metrics_runner.py +188 -0
- ibm_watsonx_gov/providers/llmevalkit/metrics/prompt.py +403 -0
- ibm_watsonx_gov/providers/llmevalkit/metrics/utils.py +46 -0
- ibm_watsonx_gov/providers/llmevalkit/prompt/__init__.py +0 -0
- ibm_watsonx_gov/providers/llmevalkit/prompt/runner.py +144 -0
- ibm_watsonx_gov/providers/tool_call_metric_provider.py +455 -0
- ibm_watsonx_gov/providers/unitxt_provider.cpython-313-darwin.so +0 -0
- ibm_watsonx_gov/tools/__init__.py +10 -0
- ibm_watsonx_gov/tools/clients/__init__.py +11 -0
- ibm_watsonx_gov/tools/clients/ai_tool_client.py +405 -0
- ibm_watsonx_gov/tools/clients/detector_client.py +82 -0
- ibm_watsonx_gov/tools/core/__init__.py +8 -0
- ibm_watsonx_gov/tools/core/tool_loader.py +237 -0
- ibm_watsonx_gov/tools/entities/__init__.py +8 -0
- ibm_watsonx_gov/tools/entities/ai_tools.py +435 -0
- ibm_watsonx_gov/tools/onboarding/create/answer_relevance_detector.json +57 -0
- ibm_watsonx_gov/tools/onboarding/create/chromadb_retrieval_tool.json +63 -0
- ibm_watsonx_gov/tools/onboarding/create/context_relevance_detector.json +57 -0
- ibm_watsonx_gov/tools/onboarding/create/duduckgo_search_tool.json +53 -0
- ibm_watsonx_gov/tools/onboarding/create/google_search_tool.json +62 -0
- ibm_watsonx_gov/tools/onboarding/create/hap_detector.json +70 -0
- ibm_watsonx_gov/tools/onboarding/create/jailbreak_detector.json +70 -0
- ibm_watsonx_gov/tools/onboarding/create/pii_detector.json +36 -0
- ibm_watsonx_gov/tools/onboarding/create/prompt_safety_risk_detector.json +69 -0
- ibm_watsonx_gov/tools/onboarding/create/topic_relevance_detector.json +57 -0
- ibm_watsonx_gov/tools/onboarding/create/weather_tool.json +39 -0
- ibm_watsonx_gov/tools/onboarding/create/webcrawler_tool.json +34 -0
- ibm_watsonx_gov/tools/onboarding/create/wikipedia_search_tool.json +53 -0
- ibm_watsonx_gov/tools/onboarding/delete/delete_tools.json +4 -0
- ibm_watsonx_gov/tools/onboarding/update/google_search_tool.json +38 -0
- ibm_watsonx_gov/tools/ootb/__init__.py +8 -0
- ibm_watsonx_gov/tools/ootb/detectors/__init__.py +8 -0
- ibm_watsonx_gov/tools/ootb/detectors/hap_detector_tool.py +109 -0
- ibm_watsonx_gov/tools/ootb/detectors/jailbreak_detector_tool.py +104 -0
- ibm_watsonx_gov/tools/ootb/detectors/pii_detector_tool.py +83 -0
- ibm_watsonx_gov/tools/ootb/detectors/prompt_safety_risk_detector_tool.py +111 -0
- ibm_watsonx_gov/tools/ootb/detectors/topic_relevance_detector_tool.py +101 -0
- ibm_watsonx_gov/tools/ootb/rag/__init__.py +8 -0
- ibm_watsonx_gov/tools/ootb/rag/answer_relevance_detector_tool.py +119 -0
- ibm_watsonx_gov/tools/ootb/rag/context_relevance_detector_tool.py +118 -0
- ibm_watsonx_gov/tools/ootb/search/__init__.py +8 -0
- ibm_watsonx_gov/tools/ootb/search/duckduckgo_search_tool.py +62 -0
- ibm_watsonx_gov/tools/ootb/search/google_search_tool.py +105 -0
- ibm_watsonx_gov/tools/ootb/search/weather_tool.py +95 -0
- ibm_watsonx_gov/tools/ootb/search/web_crawler_tool.py +69 -0
- ibm_watsonx_gov/tools/ootb/search/wikipedia_search_tool.py +63 -0
- ibm_watsonx_gov/tools/ootb/vectordb/__init__.py +8 -0
- ibm_watsonx_gov/tools/ootb/vectordb/chromadb_retriever_tool.py +111 -0
- ibm_watsonx_gov/tools/rest_api/__init__.py +10 -0
- ibm_watsonx_gov/tools/rest_api/restapi_tool.py +72 -0
- ibm_watsonx_gov/tools/schemas/__init__.py +10 -0
- ibm_watsonx_gov/tools/schemas/search_tool_schema.py +46 -0
- ibm_watsonx_gov/tools/schemas/vectordb_retrieval_schema.py +55 -0
- ibm_watsonx_gov/tools/utils/__init__.py +14 -0
- ibm_watsonx_gov/tools/utils/constants.py +69 -0
- ibm_watsonx_gov/tools/utils/display_utils.py +38 -0
- ibm_watsonx_gov/tools/utils/environment.py +108 -0
- ibm_watsonx_gov/tools/utils/package_utils.py +40 -0
- ibm_watsonx_gov/tools/utils/platform_url_mapping.cpython-313-darwin.so +0 -0
- ibm_watsonx_gov/tools/utils/python_utils.py +68 -0
- ibm_watsonx_gov/tools/utils/tool_utils.py +206 -0
- ibm_watsonx_gov/traces/__init__.py +8 -0
- ibm_watsonx_gov/traces/span_exporter.py +195 -0
- ibm_watsonx_gov/traces/span_node.py +251 -0
- ibm_watsonx_gov/traces/span_util.py +153 -0
- ibm_watsonx_gov/traces/trace_utils.py +1074 -0
- ibm_watsonx_gov/utils/__init__.py +8 -0
- ibm_watsonx_gov/utils/aggregation_util.py +346 -0
- ibm_watsonx_gov/utils/async_util.py +62 -0
- ibm_watsonx_gov/utils/authenticator.py +144 -0
- ibm_watsonx_gov/utils/constants.py +15 -0
- ibm_watsonx_gov/utils/errors.py +40 -0
- ibm_watsonx_gov/utils/gov_sdk_logger.py +39 -0
- ibm_watsonx_gov/utils/insights_generator.py +1285 -0
- ibm_watsonx_gov/utils/python_utils.py +425 -0
- ibm_watsonx_gov/utils/rest_util.py +73 -0
- ibm_watsonx_gov/utils/segment_batch_manager.py +162 -0
- ibm_watsonx_gov/utils/singleton_meta.py +25 -0
- ibm_watsonx_gov/utils/url_mapping.cpython-313-darwin.so +0 -0
- ibm_watsonx_gov/utils/validation_util.py +126 -0
- ibm_watsonx_gov/visualizations/__init__.py +13 -0
- ibm_watsonx_gov/visualizations/metric_descriptions.py +57 -0
- ibm_watsonx_gov/visualizations/model_insights.py +1304 -0
- ibm_watsonx_gov/visualizations/visualization_utils.py +75 -0
- ibm_watsonx_gov-1.3.3.dist-info/METADATA +93 -0
- ibm_watsonx_gov-1.3.3.dist-info/RECORD +353 -0
- 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]
|
|
Binary file
|