ioc-scope-mce-core 2.0.0__tar.gz

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 (78) hide show
  1. ioc_scope_mce_core-2.0.0/PKG-INFO +47 -0
  2. ioc_scope_mce_core-2.0.0/pyproject.toml +79 -0
  3. ioc_scope_mce_core-2.0.0/setup.cfg +4 -0
  4. ioc_scope_mce_core-2.0.0/src/ioc_scope_mce_core.egg-info/PKG-INFO +47 -0
  5. ioc_scope_mce_core-2.0.0/src/ioc_scope_mce_core.egg-info/SOURCES.txt +76 -0
  6. ioc_scope_mce_core-2.0.0/src/ioc_scope_mce_core.egg-info/dependency_links.txt +1 -0
  7. ioc_scope_mce_core-2.0.0/src/ioc_scope_mce_core.egg-info/requires.txt +48 -0
  8. ioc_scope_mce_core-2.0.0/src/ioc_scope_mce_core.egg-info/top_level.txt +1 -0
  9. ioc_scope_mce_core-2.0.0/src/mce/__init__.py +27 -0
  10. ioc_scope_mce_core-2.0.0/src/mce/core/catalog.py +208 -0
  11. ioc_scope_mce_core-2.0.0/src/mce/core/config.py +33 -0
  12. ioc_scope_mce_core-2.0.0/src/mce/core/helpers.py +184 -0
  13. ioc_scope_mce_core-2.0.0/src/mce/core/metadata.py +192 -0
  14. ioc_scope_mce_core-2.0.0/src/mce/core/metric.py +163 -0
  15. ioc_scope_mce_core-2.0.0/src/mce/core/provider.py +36 -0
  16. ioc_scope_mce_core-2.0.0/src/mce/core/registry/__init__.py +98 -0
  17. ioc_scope_mce_core-2.0.0/src/mce/core/registry/discovery.py +739 -0
  18. ioc_scope_mce_core-2.0.0/src/mce/core/registry/provider.py +40 -0
  19. ioc_scope_mce_core-2.0.0/src/mce/core/registry/service.py +89 -0
  20. ioc_scope_mce_core-2.0.0/src/mce/core/specs/__init__.py +21 -0
  21. ioc_scope_mce_core-2.0.0/src/mce/core/specs/_registry.py +223 -0
  22. ioc_scope_mce_core-2.0.0/src/mce/core/specs/agent_to_agent_interactions.py +53 -0
  23. ioc_scope_mce_core-2.0.0/src/mce/core/specs/agent_to_tool_interactions.py +14 -0
  24. ioc_scope_mce_core-2.0.0/src/mce/core/specs/answer_correctness.py +18 -0
  25. ioc_scope_mce_core-2.0.0/src/mce/core/specs/answer_relevancy.py +18 -0
  26. ioc_scope_mce_core-2.0.0/src/mce/core/specs/bias.py +18 -0
  27. ioc_scope_mce_core-2.0.0/src/mce/core/specs/call_count.py +14 -0
  28. ioc_scope_mce_core-2.0.0/src/mce/core/specs/coherence.py +18 -0
  29. ioc_scope_mce_core-2.0.0/src/mce/core/specs/completion_rate.py +53 -0
  30. ioc_scope_mce_core-2.0.0/src/mce/core/specs/component_conflict_rate.py +18 -0
  31. ioc_scope_mce_core-2.0.0/src/mce/core/specs/consistency.py +18 -0
  32. ioc_scope_mce_core-2.0.0/src/mce/core/specs/context_preservation.py +18 -0
  33. ioc_scope_mce_core-2.0.0/src/mce/core/specs/conversation_completeness.py +18 -0
  34. ioc_scope_mce_core-2.0.0/src/mce/core/specs/cost.py +14 -0
  35. ioc_scope_mce_core-2.0.0/src/mce/core/specs/cycles_count.py +14 -0
  36. ioc_scope_mce_core-2.0.0/src/mce/core/specs/duration.py +14 -0
  37. ioc_scope_mce_core-2.0.0/src/mce/core/specs/general_structure_and_style.py +18 -0
  38. ioc_scope_mce_core-2.0.0/src/mce/core/specs/goal_success_rate.py +18 -0
  39. ioc_scope_mce_core-2.0.0/src/mce/core/specs/graph_determinism_score.py +14 -0
  40. ioc_scope_mce_core-2.0.0/src/mce/core/specs/groundedness.py +18 -0
  41. ioc_scope_mce_core-2.0.0/src/mce/core/specs/hallucination.py +18 -0
  42. ioc_scope_mce_core-2.0.0/src/mce/core/specs/information_retention.py +18 -0
  43. ioc_scope_mce_core-2.0.0/src/mce/core/specs/intent_recognition_accuracy.py +18 -0
  44. ioc_scope_mce_core-2.0.0/src/mce/core/specs/llm_average_confidence.py +14 -0
  45. ioc_scope_mce_core-2.0.0/src/mce/core/specs/llm_error_rate.py +76 -0
  46. ioc_scope_mce_core-2.0.0/src/mce/core/specs/llm_maximum_confidence.py +14 -0
  47. ioc_scope_mce_core-2.0.0/src/mce/core/specs/llm_minimum_confidence.py +14 -0
  48. ioc_scope_mce_core-2.0.0/src/mce/core/specs/response_completeness.py +18 -0
  49. ioc_scope_mce_core-2.0.0/src/mce/core/specs/role_adherence.py +18 -0
  50. ioc_scope_mce_core-2.0.0/src/mce/core/specs/sentiment.py +18 -0
  51. ioc_scope_mce_core-2.0.0/src/mce/core/specs/task_completion.py +18 -0
  52. ioc_scope_mce_core-2.0.0/src/mce/core/specs/task_delegation_accuracy.py +18 -0
  53. ioc_scope_mce_core-2.0.0/src/mce/core/specs/token_count.py +14 -0
  54. ioc_scope_mce_core-2.0.0/src/mce/core/specs/tonality.py +18 -0
  55. ioc_scope_mce_core-2.0.0/src/mce/core/specs/tool_error.py +14 -0
  56. ioc_scope_mce_core-2.0.0/src/mce/core/specs/tool_error_rate.py +14 -0
  57. ioc_scope_mce_core-2.0.0/src/mce/core/specs/tool_utilization_accuracy.py +95 -0
  58. ioc_scope_mce_core-2.0.0/src/mce/core/specs/topic_adherence.py +18 -0
  59. ioc_scope_mce_core-2.0.0/src/mce/core/specs/toxicity.py +18 -0
  60. ioc_scope_mce_core-2.0.0/src/mce/core/specs/workflow_cohesion_index.py +18 -0
  61. ioc_scope_mce_core-2.0.0/src/mce/core/specs/workflow_efficiency.py +90 -0
  62. ioc_scope_mce_core-2.0.0/src/mce/core/types.py +99 -0
  63. ioc_scope_mce_core-2.0.0/src/mce/engine/__init__.py +29 -0
  64. ioc_scope_mce_core-2.0.0/src/mce/engine/caching.py +611 -0
  65. ioc_scope_mce_core-2.0.0/src/mce/engine/context_builder.py +298 -0
  66. ioc_scope_mce_core-2.0.0/src/mce/engine/data/model_prices.json +1 -0
  67. ioc_scope_mce_core-2.0.0/src/mce/engine/engine.py +886 -0
  68. ioc_scope_mce_core-2.0.0/src/mce/engine/llm.py +338 -0
  69. ioc_scope_mce_core-2.0.0/src/mce/engine/pricing.py +146 -0
  70. ioc_scope_mce_core-2.0.0/src/mce/engine/scheduler.py +61 -0
  71. ioc_scope_mce_core-2.0.0/src/mce/engine/strategies.py +129 -0
  72. ioc_scope_mce_core-2.0.0/src/mce/engine/usage.py +84 -0
  73. ioc_scope_mce_core-2.0.0/src/mce/engine/worker.py +12 -0
  74. ioc_scope_mce_core-2.0.0/src/mce/providers/__init__.py +9 -0
  75. ioc_scope_mce_core-2.0.0/src/mce/providers/base_wrapper.py +133 -0
  76. ioc_scope_mce_core-2.0.0/src/mce/providers/catalog.py +28 -0
  77. ioc_scope_mce_core-2.0.0/src/mce/providers/template/__init__.py +4 -0
  78. ioc_scope_mce_core-2.0.0/src/mce/providers/virtual/aggregators.py +159 -0
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: ioc-scope-mce-core
3
+ Version: 2.0.0
4
+ Summary: Metrics Computation Engine core
5
+ Author-email: Outshift Open <oss-github@cisco.com>
6
+ License-Expression: Apache-2.0
7
+ Requires-Python: <3.15,>=3.13
8
+ Requires-Dist: pydantic>=2.0.0
9
+ Requires-Dist: click>=8.0.0
10
+ Requires-Dist: python-dotenv>=1.0.0
11
+ Requires-Dist: requests>=2.28.0
12
+ Requires-Dist: networkx>=3.0.0
13
+ Requires-Dist: packaging>=23.0
14
+ Requires-Dist: pandas>=1.5.0
15
+ Requires-Dist: pydantic-settings>=2.10.1
16
+ Requires-Dist: typer>=0.9.0
17
+ Requires-Dist: rdflib>=7.0.0
18
+ Requires-Dist: pyyaml>=6.0.0
19
+ Requires-Dist: ioc-scope-ontology>=1.1.0
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest; extra == "dev"
22
+ Requires-Dist: pytest-asyncio; extra == "dev"
23
+ Requires-Dist: black; extra == "dev"
24
+ Requires-Dist: flake8; extra == "dev"
25
+ Requires-Dist: mypy; extra == "dev"
26
+ Requires-Dist: httpx; extra == "dev"
27
+ Provides-Extra: neo4j
28
+ Requires-Dist: neo4j>=6.1.0; extra == "neo4j"
29
+ Provides-Extra: deepeval
30
+ Requires-Dist: ioc-scope-mce-provider-deepeval>=2.0.0; extra == "deepeval"
31
+ Provides-Extra: llm
32
+ Requires-Dist: ioc-scope-mce-provider-native>=2.0.0; extra == "llm"
33
+ Provides-Extra: opik
34
+ Requires-Dist: ioc-scope-mce-provider-opik>=2.0.0; extra == "opik"
35
+ Provides-Extra: ragas
36
+ Requires-Dist: ioc-scope-mce-provider-ragas>=2.0.0; extra == "ragas"
37
+ Provides-Extra: server
38
+ Requires-Dist: fastapi>=0.111.0; extra == "server"
39
+ Requires-Dist: uvicorn>=0.30.0; extra == "server"
40
+ Provides-Extra: all
41
+ Requires-Dist: ioc-scope-mce-provider-native>=2.0.0; extra == "all"
42
+ Requires-Dist: ioc-scope-mce-provider-sdk>=2.0.0; extra == "all"
43
+ Requires-Dist: ioc-scope-mce-provider-deepeval>=2.0.0; extra == "all"
44
+ Requires-Dist: ioc-scope-mce-provider-opik>=2.0.0; extra == "all"
45
+ Requires-Dist: ioc-scope-mce-provider-ragas>=2.0.0; extra == "all"
46
+ Requires-Dist: fastapi>=0.111.0; extra == "all"
47
+ Requires-Dist: uvicorn>=0.30.0; extra == "all"
@@ -0,0 +1,79 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ioc-scope-mce-core"
7
+ version = "2.0.0"
8
+ authors = [
9
+ { name = "Outshift Open", email = "oss-github@cisco.com" }
10
+ ]
11
+ license = "Apache-2.0"
12
+ description = "Metrics Computation Engine core"
13
+ requires-python = ">=3.13,<3.15"
14
+ dependencies = [
15
+ "pydantic>=2.0.0",
16
+ "click>=8.0.0",
17
+ "python-dotenv>=1.0.0",
18
+ "requests>=2.28.0",
19
+ "networkx>=3.0.0",
20
+ "packaging>=23.0",
21
+ "pandas>=1.5.0",
22
+ "pydantic-settings>=2.10.1",
23
+ "typer>=0.9.0",
24
+ "rdflib>=7.0.0",
25
+ "pyyaml>=6.0.0",
26
+ "ioc-scope-ontology>=1.1.0",
27
+ ]
28
+
29
+ [project.optional-dependencies]
30
+ dev = [
31
+ "pytest",
32
+ "pytest-asyncio",
33
+ "black",
34
+ "flake8",
35
+ "mypy",
36
+ "httpx",
37
+ ]
38
+ neo4j = [
39
+ "neo4j>=6.1.0",
40
+ ]
41
+ deepeval = [
42
+ "ioc-scope-mce-provider-deepeval>=2.0.0",
43
+ ]
44
+ llm = [
45
+ "ioc-scope-mce-provider-native>=2.0.0",
46
+ ]
47
+ opik = [
48
+ "ioc-scope-mce-provider-opik>=2.0.0",
49
+ ]
50
+ ragas = [
51
+ "ioc-scope-mce-provider-ragas>=2.0.0",
52
+ ]
53
+ server = [
54
+ "fastapi>=0.111.0",
55
+ "uvicorn>=0.30.0",
56
+ ]
57
+ all = [
58
+ "ioc-scope-mce-provider-native>=2.0.0",
59
+ "ioc-scope-mce-provider-sdk>=2.0.0",
60
+ "ioc-scope-mce-provider-deepeval>=2.0.0",
61
+ "ioc-scope-mce-provider-opik>=2.0.0",
62
+ "ioc-scope-mce-provider-ragas>=2.0.0",
63
+ "fastapi>=0.111.0",
64
+ "uvicorn>=0.30.0",
65
+ ]
66
+
67
+ [tool.setuptools.packages.find]
68
+ where = ["src"]
69
+
70
+ [tool.setuptools.package-data]
71
+ "mce.engine" = ["data/*.json"]
72
+
73
+ [tool.uv.sources]
74
+ ioc-scope-mce-provider-native = { workspace = true }
75
+ ioc-scope-mce-provider-sdk = { workspace = true }
76
+ ioc-scope-mce-provider-deepeval = { workspace = true }
77
+ ioc-scope-mce-provider-opik = { workspace = true }
78
+ ioc-scope-mce-provider-ragas = { workspace = true }
79
+
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: ioc-scope-mce-core
3
+ Version: 2.0.0
4
+ Summary: Metrics Computation Engine core
5
+ Author-email: Outshift Open <oss-github@cisco.com>
6
+ License-Expression: Apache-2.0
7
+ Requires-Python: <3.15,>=3.13
8
+ Requires-Dist: pydantic>=2.0.0
9
+ Requires-Dist: click>=8.0.0
10
+ Requires-Dist: python-dotenv>=1.0.0
11
+ Requires-Dist: requests>=2.28.0
12
+ Requires-Dist: networkx>=3.0.0
13
+ Requires-Dist: packaging>=23.0
14
+ Requires-Dist: pandas>=1.5.0
15
+ Requires-Dist: pydantic-settings>=2.10.1
16
+ Requires-Dist: typer>=0.9.0
17
+ Requires-Dist: rdflib>=7.0.0
18
+ Requires-Dist: pyyaml>=6.0.0
19
+ Requires-Dist: ioc-scope-ontology>=1.1.0
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest; extra == "dev"
22
+ Requires-Dist: pytest-asyncio; extra == "dev"
23
+ Requires-Dist: black; extra == "dev"
24
+ Requires-Dist: flake8; extra == "dev"
25
+ Requires-Dist: mypy; extra == "dev"
26
+ Requires-Dist: httpx; extra == "dev"
27
+ Provides-Extra: neo4j
28
+ Requires-Dist: neo4j>=6.1.0; extra == "neo4j"
29
+ Provides-Extra: deepeval
30
+ Requires-Dist: ioc-scope-mce-provider-deepeval>=2.0.0; extra == "deepeval"
31
+ Provides-Extra: llm
32
+ Requires-Dist: ioc-scope-mce-provider-native>=2.0.0; extra == "llm"
33
+ Provides-Extra: opik
34
+ Requires-Dist: ioc-scope-mce-provider-opik>=2.0.0; extra == "opik"
35
+ Provides-Extra: ragas
36
+ Requires-Dist: ioc-scope-mce-provider-ragas>=2.0.0; extra == "ragas"
37
+ Provides-Extra: server
38
+ Requires-Dist: fastapi>=0.111.0; extra == "server"
39
+ Requires-Dist: uvicorn>=0.30.0; extra == "server"
40
+ Provides-Extra: all
41
+ Requires-Dist: ioc-scope-mce-provider-native>=2.0.0; extra == "all"
42
+ Requires-Dist: ioc-scope-mce-provider-sdk>=2.0.0; extra == "all"
43
+ Requires-Dist: ioc-scope-mce-provider-deepeval>=2.0.0; extra == "all"
44
+ Requires-Dist: ioc-scope-mce-provider-opik>=2.0.0; extra == "all"
45
+ Requires-Dist: ioc-scope-mce-provider-ragas>=2.0.0; extra == "all"
46
+ Requires-Dist: fastapi>=0.111.0; extra == "all"
47
+ Requires-Dist: uvicorn>=0.30.0; extra == "all"
@@ -0,0 +1,76 @@
1
+ pyproject.toml
2
+ src/ioc_scope_mce_core.egg-info/PKG-INFO
3
+ src/ioc_scope_mce_core.egg-info/SOURCES.txt
4
+ src/ioc_scope_mce_core.egg-info/dependency_links.txt
5
+ src/ioc_scope_mce_core.egg-info/requires.txt
6
+ src/ioc_scope_mce_core.egg-info/top_level.txt
7
+ src/mce/__init__.py
8
+ src/mce/core/catalog.py
9
+ src/mce/core/config.py
10
+ src/mce/core/helpers.py
11
+ src/mce/core/metadata.py
12
+ src/mce/core/metric.py
13
+ src/mce/core/provider.py
14
+ src/mce/core/types.py
15
+ src/mce/core/registry/__init__.py
16
+ src/mce/core/registry/discovery.py
17
+ src/mce/core/registry/provider.py
18
+ src/mce/core/registry/service.py
19
+ src/mce/core/specs/__init__.py
20
+ src/mce/core/specs/_registry.py
21
+ src/mce/core/specs/agent_to_agent_interactions.py
22
+ src/mce/core/specs/agent_to_tool_interactions.py
23
+ src/mce/core/specs/answer_correctness.py
24
+ src/mce/core/specs/answer_relevancy.py
25
+ src/mce/core/specs/bias.py
26
+ src/mce/core/specs/call_count.py
27
+ src/mce/core/specs/coherence.py
28
+ src/mce/core/specs/completion_rate.py
29
+ src/mce/core/specs/component_conflict_rate.py
30
+ src/mce/core/specs/consistency.py
31
+ src/mce/core/specs/context_preservation.py
32
+ src/mce/core/specs/conversation_completeness.py
33
+ src/mce/core/specs/cost.py
34
+ src/mce/core/specs/cycles_count.py
35
+ src/mce/core/specs/duration.py
36
+ src/mce/core/specs/general_structure_and_style.py
37
+ src/mce/core/specs/goal_success_rate.py
38
+ src/mce/core/specs/graph_determinism_score.py
39
+ src/mce/core/specs/groundedness.py
40
+ src/mce/core/specs/hallucination.py
41
+ src/mce/core/specs/information_retention.py
42
+ src/mce/core/specs/intent_recognition_accuracy.py
43
+ src/mce/core/specs/llm_average_confidence.py
44
+ src/mce/core/specs/llm_error_rate.py
45
+ src/mce/core/specs/llm_maximum_confidence.py
46
+ src/mce/core/specs/llm_minimum_confidence.py
47
+ src/mce/core/specs/response_completeness.py
48
+ src/mce/core/specs/role_adherence.py
49
+ src/mce/core/specs/sentiment.py
50
+ src/mce/core/specs/task_completion.py
51
+ src/mce/core/specs/task_delegation_accuracy.py
52
+ src/mce/core/specs/token_count.py
53
+ src/mce/core/specs/tonality.py
54
+ src/mce/core/specs/tool_error.py
55
+ src/mce/core/specs/tool_error_rate.py
56
+ src/mce/core/specs/tool_utilization_accuracy.py
57
+ src/mce/core/specs/topic_adherence.py
58
+ src/mce/core/specs/toxicity.py
59
+ src/mce/core/specs/workflow_cohesion_index.py
60
+ src/mce/core/specs/workflow_efficiency.py
61
+ src/mce/engine/__init__.py
62
+ src/mce/engine/caching.py
63
+ src/mce/engine/context_builder.py
64
+ src/mce/engine/engine.py
65
+ src/mce/engine/llm.py
66
+ src/mce/engine/pricing.py
67
+ src/mce/engine/scheduler.py
68
+ src/mce/engine/strategies.py
69
+ src/mce/engine/usage.py
70
+ src/mce/engine/worker.py
71
+ src/mce/engine/data/model_prices.json
72
+ src/mce/providers/__init__.py
73
+ src/mce/providers/base_wrapper.py
74
+ src/mce/providers/catalog.py
75
+ src/mce/providers/template/__init__.py
76
+ src/mce/providers/virtual/aggregators.py
@@ -0,0 +1,48 @@
1
+ pydantic>=2.0.0
2
+ click>=8.0.0
3
+ python-dotenv>=1.0.0
4
+ requests>=2.28.0
5
+ networkx>=3.0.0
6
+ packaging>=23.0
7
+ pandas>=1.5.0
8
+ pydantic-settings>=2.10.1
9
+ typer>=0.9.0
10
+ rdflib>=7.0.0
11
+ pyyaml>=6.0.0
12
+ ioc-scope-ontology>=1.1.0
13
+
14
+ [all]
15
+ ioc-scope-mce-provider-native>=2.0.0
16
+ ioc-scope-mce-provider-sdk>=2.0.0
17
+ ioc-scope-mce-provider-deepeval>=2.0.0
18
+ ioc-scope-mce-provider-opik>=2.0.0
19
+ ioc-scope-mce-provider-ragas>=2.0.0
20
+ fastapi>=0.111.0
21
+ uvicorn>=0.30.0
22
+
23
+ [deepeval]
24
+ ioc-scope-mce-provider-deepeval>=2.0.0
25
+
26
+ [dev]
27
+ pytest
28
+ pytest-asyncio
29
+ black
30
+ flake8
31
+ mypy
32
+ httpx
33
+
34
+ [llm]
35
+ ioc-scope-mce-provider-native>=2.0.0
36
+
37
+ [neo4j]
38
+ neo4j>=6.1.0
39
+
40
+ [opik]
41
+ ioc-scope-mce-provider-opik>=2.0.0
42
+
43
+ [ragas]
44
+ ioc-scope-mce-provider-ragas>=2.0.0
45
+
46
+ [server]
47
+ fastapi>=0.111.0
48
+ uvicorn>=0.30.0
@@ -0,0 +1,27 @@
1
+ # Copyright (c) 2026 Cisco Systems, Inc. and its affiliates
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ from __future__ import annotations
4
+ from pkgutil import extend_path
5
+
6
+ import warnings
7
+
8
+ # Allow multiple workspace packages to contribute to top-level `mce`.
9
+ __path__ = extend_path(__path__, __name__)
10
+
11
+ # Suppress Pydantic v1 compatibility warning from opik in Python 3.14+
12
+ # Must be at the top level before any other imports
13
+ warnings.filterwarnings(
14
+ "ignore",
15
+ message="Core Pydantic V1 functionality isn't compatible with Python 3.14 or greater",
16
+ category=UserWarning,
17
+ )
18
+
19
+ # Expose key public components
20
+ try:
21
+ from .core.types import MetricResult as MetricResult
22
+ from .core.metric import Metric as Metric
23
+ from .engine.engine import MetricEngine as MetricEngine
24
+
25
+ __all__ = ["MetricResult", "Metric", "MetricEngine"]
26
+ except ImportError:
27
+ __all__: list[str] = []
@@ -0,0 +1,208 @@
1
+ # Copyright (c) 2026 Cisco Systems, Inc. and its affiliates
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import importlib
7
+ import inspect
8
+ import pkgutil
9
+ import threading
10
+ import statistics
11
+ from mce.core.metric import Metric
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Lazy imports for heavy dependencies
16
+ try:
17
+ from scope_ontology import OntologyService
18
+ except ImportError:
19
+ OntologyService = None
20
+
21
+
22
+ class MetricCatalogService:
23
+ """
24
+ Core Service: unified registry for Metric instances.
25
+ Supports lazy creation of templated aggregate (virtual) metrics.
26
+ """
27
+
28
+ _instance = None
29
+ _lock = threading.Lock() # Guards singleton creation
30
+
31
+ def __init__(self):
32
+ self._contracts: dict[str, Metric | type[Metric]] = {}
33
+ self._unified_metrics: dict[str, list[Metric]] = {}
34
+
35
+ @classmethod
36
+ def get_instance(cls):
37
+ """Thread-safe singleton accessor."""
38
+ if cls._instance is None:
39
+ with cls._lock:
40
+ if cls._instance is None: # double-checked locking
41
+ cls._instance = MetricCatalogService()
42
+ return cls._instance
43
+
44
+ def register_metric(self, metric: Metric | type[Metric]) -> None:
45
+ """Register a unified Metric plugin or abstract Base."""
46
+ if isinstance(metric, type):
47
+ # Abstract Class registration
48
+ if not hasattr(metric, "metadata"):
49
+ return # Skip if no metadata
50
+ mid = metric.metadata.name
51
+ self._contracts[mid] = metric
52
+ return
53
+
54
+ # Instance registration
55
+ mid = metric.metric_id
56
+ if mid not in self._contracts:
57
+ self._contracts[mid] = metric
58
+
59
+ if mid not in self._unified_metrics:
60
+ self._unified_metrics[mid] = []
61
+ self._unified_metrics[mid].append(metric)
62
+
63
+ def get_contract(self, metric_id: str) -> Metric | type[Metric] | None:
64
+ """Look up a registered contract (or unified metric) by ID."""
65
+ return self._contracts.get(metric_id)
66
+
67
+ def get_metric(self, metric_id: str) -> Metric | None:
68
+ """
69
+ Get a unified metric instance.
70
+
71
+ **Lazy Loading for Templated Aggregates** (Option C):
72
+ If metric_id matches pattern "operation_BaseMetric" (e.g., "avg_AnswerRelevancy"),
73
+ automatically create and cache an AggregateMetric instance.
74
+
75
+ Supported operations:
76
+ - avg_* → AverageMetric(base_metric)
77
+ - max_* → AggregateMetric(base_metric, "max", max)
78
+ - min_* → AggregateMetric(base_metric, "min", min)
79
+ - sum_* → AggregateMetric(base_metric, "sum", sum)
80
+ """
81
+ # Check if already registered
82
+ metrics = self._unified_metrics.get(metric_id, [])
83
+ if metrics:
84
+ return metrics[0]
85
+
86
+ # Lazy loading: detect templated aggregate pattern
87
+ operation_mapping = {
88
+ "avg": ("avg", "AverageMetric"),
89
+ "max": ("max", "max"),
90
+ "min": ("min", "min"),
91
+ "sum": ("sum", "sum"),
92
+ }
93
+
94
+ for prefix, (op_name, op_type) in operation_mapping.items():
95
+ if metric_id.startswith(f"{prefix}_"):
96
+ base_metric_id = metric_id[len(prefix) + 1 :] # Remove "avg_" prefix
97
+
98
+ # Validate base metric exists
99
+ if base_metric_id not in self._contracts:
100
+ return None # Base metric not found, cannot create aggregate
101
+
102
+ # Import and instantiate appropriate aggregate metric
103
+ if op_type == "AverageMetric":
104
+ from mce.providers.virtual.aggregators import AverageMetric
105
+
106
+ aggregate_metric = AverageMetric(target_metric_id=base_metric_id)
107
+ else:
108
+ # max / min / sum operations
109
+ from mce.providers.virtual.aggregators import AggregateMetric
110
+
111
+ # Fix #20: statistics imported at module level
112
+ op_fn_map = {
113
+ "max": max,
114
+ "min": min,
115
+ "sum": sum,
116
+ "avg": statistics.mean,
117
+ }
118
+ aggregate_metric = AggregateMetric(
119
+ target_metric_id=base_metric_id,
120
+ operation_name=op_name,
121
+ operation_fn=op_fn_map[op_name],
122
+ )
123
+
124
+ # Resolve target_types for the new metric
125
+ self._resolve_target_types(aggregate_metric)
126
+
127
+ # Register the lazily created metric
128
+ self.register_metric(aggregate_metric)
129
+ return aggregate_metric
130
+
131
+ return None
132
+
133
+ def _resolve_target_types(self, metric: "Metric") -> None:
134
+ """Resolve target_types for a lazily created aggregate metric.
135
+
136
+ Fix #16: delegates to the single source of truth in discovery.py
137
+ (ensure_target_types) instead of duplicating the mapping logic here.
138
+ The lazy import avoids the circular dependency
139
+ (catalog → discovery → catalog).
140
+ """
141
+ from mce.core.registry.discovery import (
142
+ ensure_target_types,
143
+ ) # lazy — avoids circular import
144
+
145
+ ensure_target_types(metric)
146
+
147
+ # If ensure_target_types could not resolve (no scope/attachment_point),
148
+ # fall back to ontology_class-based mapping for aggregate metrics which
149
+ # inherit the base metric's class name rather than a MAS prefix.
150
+ md = metric.metadata
151
+ if not getattr(md, "target_types", None):
152
+ ap = md.attachment_point
153
+ if ap and OntologyService is not None:
154
+ try:
155
+ onto = OntologyService.get_instance()
156
+ md.target_types = onto.get_subclasses_of(ap)
157
+ return
158
+ except Exception as e:
159
+ logger.warning(
160
+ "OntologyService.get_subclasses_of(%r) failed: %s — "
161
+ "falling back to ontology_class mapping for '%s'.",
162
+ ap,
163
+ e,
164
+ md.name,
165
+ )
166
+ # Last resort: map known ontology_class strings.
167
+ _class_map = {
168
+ "Session": {"mas:Session"},
169
+ "LLMCall": {"mas:LLMCall"},
170
+ "mas:LLMCall": {"mas:LLMCall"},
171
+ "ToolCall": {"mas:ToolCall"},
172
+ "mas:ToolCall": {"mas:ToolCall"},
173
+ }
174
+ md.target_types = _class_map.get(md.ontology_class, set())
175
+
176
+ def get_all_contracts(self) -> list[Metric | type[Metric]]:
177
+ return list(self._contracts.values())
178
+
179
+ def discover_metrics(self, package_path: str = "mce.metrics"):
180
+ """Auto-discover concrete Metric subclasses in the specified package."""
181
+ try:
182
+ module = importlib.import_module(package_path)
183
+ path = getattr(module, "__path__", [])
184
+ # NOTE: use module_name (not `name`) so the inner member loop cannot
185
+ # shadow the outer iteration variable — that was a silent bug.
186
+ # Fix #5: use walk_packages (recursive) instead of iter_modules (single-level)
187
+ # so metrics in sub-packages are not silently dropped.
188
+ for _, module_name, _ in pkgutil.walk_packages(
189
+ path, prefix=f"{package_path}."
190
+ ):
191
+ sub_module = importlib.import_module(module_name)
192
+ for member_name, obj in inspect.getmembers(sub_module):
193
+ if (
194
+ inspect.isclass(obj)
195
+ and issubclass(obj, Metric)
196
+ and obj is not Metric
197
+ ):
198
+ if inspect.isabstract(obj):
199
+ continue # Skip abstract bases
200
+ try:
201
+ instance = obj()
202
+ self.register_metric(instance)
203
+ except Exception as e:
204
+ logger.warning(
205
+ "Failed to instantiate metric %s: %s", member_name, e
206
+ )
207
+ except ImportError as e:
208
+ logger.warning("Could not import package %s: %s", package_path, e)
@@ -0,0 +1,33 @@
1
+ # Copyright (c) 2026 Cisco Systems, Inc. and its affiliates
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ from __future__ import annotations
4
+ from pydantic_settings import BaseSettings, SettingsConfigDict
5
+ from pydantic import Field
6
+
7
+
8
+ class MceSettings(BaseSettings):
9
+ """
10
+ Configuration for the MCE library.
11
+ Settings can be overridden by environment variables with the prefix MCE_.
12
+
13
+ Provider availability is determined automatically: if a package is importable
14
+ (e.g. deepeval, ragas, opik) its metrics are discovered and registered.
15
+ Install the metapackage to get everything, or install individual provider
16
+ packages to pick only what you need.
17
+ """
18
+
19
+ strict_fail_fast: bool = Field(
20
+ default=False, description="Raise exceptions on provider initialization failure"
21
+ )
22
+
23
+ model_config = SettingsConfigDict(
24
+ env_prefix="MCE_",
25
+ env_file=".env",
26
+ env_file_encoding="utf-8",
27
+ case_sensitive=False,
28
+ extra="ignore",
29
+ )
30
+
31
+
32
+ # Global settings instance
33
+ settings = MceSettings()