structuremappingmemory 1.0.0__tar.gz → 1.1.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.
- {structuremappingmemory-1.0.0/structuremappingmemory.egg-info → structuremappingmemory-1.1.0}/PKG-INFO +17 -8
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/README.md +14 -7
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/pyproject.toml +5 -1
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/__init__.py +1 -1
- structuremappingmemory-1.1.0/sma/mcp.py +393 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0/structuremappingmemory.egg-info}/PKG-INFO +17 -8
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/structuremappingmemory.egg-info/SOURCES.txt +2 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/structuremappingmemory.egg-info/entry_points.txt +1 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/structuremappingmemory.egg-info/requires.txt +3 -0
- structuremappingmemory-1.1.0/tests/test_mcp.py +138 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/LICENSE +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/setup.cfg +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/__main__.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/agent/__init__.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/agent/adapter_draft.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/agent/api.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/agent/comparison.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/agent/llm.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/agent/policies.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/agent/service.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/cli.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/__init__.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/agentobs.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/base.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/code_treesitter.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/coverage.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/draft_adapter.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/healthcare.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/logs_drain.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/prose_tier1.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/structured.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/encoders/traces.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/__init__.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/__init__.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/arms/__init__.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/arms/cyber.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/arms/discovery.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/arms/finance.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/arms/legal.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/arms/medicine.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/harness.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/memories.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic/metrics.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic_qa/__init__.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic_qa/agent.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic_qa/metrics.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/agentic_qa/pools.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/arn.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/__init__.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/bge_dense.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/bm25.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/dense.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/hipporag.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/hybrid_rrf.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/longcontext_llm.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/rerank.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/splade.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/baselines/wl_kernel.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/bugsinpy.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/bugsinpy_families.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/crossdomain.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/diabetes.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/drift_env.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/drift_metrics.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/family_labels.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/fraud_elliptic/__init__.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/fraud_elliptic/encoder.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/fraud_elliptic/eval.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/fraud_elliptic/test_encoder.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/ieee_cis.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/loghub.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/loghub_eval.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/longmemeval.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/memory_backends/__init__.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/memory_backends/base.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/memory_backends/context_only.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/memory_backends/rag_notes.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/memory_backends/shared_llm.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/memory_backends/sma_memory.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/memory_backends/zep_graphiti.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/metrics.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/ontology_bench.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/report.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/ssb_eval.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/ssb_generator.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/stats.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/eval/transfer_eval.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/index/__init__.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/index/ann.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/index/content_vectors.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/index/inverted.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/index/macfac.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ir/__init__.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ir/canon.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ir/schema.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ir/sexpr.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ir/signatures.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/__init__.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/conflicts.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/engine.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/explain.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/infer.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/kernels.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/mdl.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/merge_cpsat.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/merge_greedy.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/mh.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/ses.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/types.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/match/verifier.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/__init__.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/attack.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/cpc.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/graph.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/loader.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/mitre_xml.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/mount.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/rdf_loader.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/registry.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/router.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ontology/usgaap.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/sage/__init__.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/sage/assimilate.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/sage/pools.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/sage/probabilities.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/store/__init__.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/store/lmdb_store.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/store/registry.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/store/wal.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/sma/ui/app.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/structuremappingmemory.egg-info/dependency_links.txt +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/structuremappingmemory.egg-info/top_level.txt +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/tests/test_bugsinpy_t3.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/tests/test_drift.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/tests/test_gates.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/tests/test_hipporag.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/tests/test_production_loop.py +0 -0
- {structuremappingmemory-1.0.0 → structuremappingmemory-1.1.0}/tests/test_stats.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: structuremappingmemory
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: SMA-1: Structure-Mapping Agentic Memory — a structure-mapping retrieval memory that grounds language models in curated ontologies
|
|
5
5
|
Author-email: Ayaz Khan <aak2259@columbia.edu>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -50,11 +50,14 @@ Requires-Dist: networkx>=3; extra == "eval"
|
|
|
50
50
|
Provides-Extra: local-llm
|
|
51
51
|
Requires-Dist: huggingface-hub>=0.23; extra == "local-llm"
|
|
52
52
|
Requires-Dist: llama-cpp-python>=0.2.90; extra == "local-llm"
|
|
53
|
+
Provides-Extra: mcp
|
|
54
|
+
Requires-Dist: mcp>=1.0; extra == "mcp"
|
|
53
55
|
Dynamic: license-file
|
|
54
56
|
|
|
55
57
|
# SMA-1: Structure-Mapping Agentic Memory
|
|
56
58
|
|
|
57
59
|
[](https://github.com/ayazkhan27/SMA-1/actions/workflows/ci.yml)
|
|
60
|
+
[](https://pypi.org/project/structuremappingmemory/)
|
|
58
61
|
[](LICENSE)
|
|
59
62
|
[](https://huggingface.co/spaces/zephyr27/SMA-1-demo)
|
|
60
63
|
|
|
@@ -103,13 +106,13 @@ adapters.
|
|
|
103
106
|
On a **memory-swap benchmark** where only the retriever varies, SMA beats a strong
|
|
104
107
|
RAG + KG baseline suite on the rare/long-tail slice across five domains:
|
|
105
108
|
|
|
106
|
-
| Domain | SMA tail top-5 | best RAG | Δ | Holm-significant |
|
|
107
|
-
|
|
108
|
-
| Medicine (HPO/MONDO) | 0.949 | 0.606 | **+0.
|
|
109
|
-
| Finance (US-GAAP) | 0.418 | 0.231 | **+0.
|
|
110
|
-
| Genomics (GO) | 0.849 | 0.682 | **+0.
|
|
111
|
-
| Legal (CPC, all-query) | 0.941 | 0.870 | **+0.
|
|
112
|
-
| Cyber (ATT&CK) | 0.766 | 0.749 | +0.073 | directional* |
|
|
109
|
+
| Domain | SMA tail top-5 | best RAG | Δ tail top-5 | Cliff's δ | Holm-significant |
|
|
110
|
+
|---|:--:|:--:|:--:|:--:|:--:|
|
|
111
|
+
| Medicine (HPO/MONDO) | 0.949 | 0.606 | **+0.343** | 0.333 | yes |
|
|
112
|
+
| Finance (US-GAAP) | 0.418 | 0.231 | **+0.187** | 0.167 | yes |
|
|
113
|
+
| Genomics (GO) | 0.849 | 0.682 | **+0.167** | 0.156 | yes |
|
|
114
|
+
| Legal (CPC, all-query) | 0.941 | 0.870 | **+0.071** | 0.064 | yes |
|
|
115
|
+
| Cyber (ATT&CK) | 0.766 | 0.749 | +0.017 | 0.073 | directional* |
|
|
113
116
|
|
|
114
117
|
\* Cyber survives Holm across domains but not a conservative
|
|
115
118
|
Bonferroni-over-baselines selection correction (p=0.035 → 0.17); reported as
|
|
@@ -142,6 +145,12 @@ logistic-regression baseline wins. The advantage is specific to structure.
|
|
|
142
145
|
|
|
143
146
|
## Quick start
|
|
144
147
|
|
|
148
|
+
```bash
|
|
149
|
+
pip install structuremappingmemory # from PyPI (import name: sma)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Or for development, with the evaluation/encoder extras:
|
|
153
|
+
|
|
145
154
|
```bash
|
|
146
155
|
python -m venv .venv && . .venv/bin/activate
|
|
147
156
|
pip install -e ".[encoders,eval]"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# SMA-1: Structure-Mapping Agentic Memory
|
|
2
2
|
|
|
3
3
|
[](https://github.com/ayazkhan27/SMA-1/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/structuremappingmemory/)
|
|
4
5
|
[](LICENSE)
|
|
5
6
|
[](https://huggingface.co/spaces/zephyr27/SMA-1-demo)
|
|
6
7
|
|
|
@@ -49,13 +50,13 @@ adapters.
|
|
|
49
50
|
On a **memory-swap benchmark** where only the retriever varies, SMA beats a strong
|
|
50
51
|
RAG + KG baseline suite on the rare/long-tail slice across five domains:
|
|
51
52
|
|
|
52
|
-
| Domain | SMA tail top-5 | best RAG | Δ | Holm-significant |
|
|
53
|
-
|
|
54
|
-
| Medicine (HPO/MONDO) | 0.949 | 0.606 | **+0.
|
|
55
|
-
| Finance (US-GAAP) | 0.418 | 0.231 | **+0.
|
|
56
|
-
| Genomics (GO) | 0.849 | 0.682 | **+0.
|
|
57
|
-
| Legal (CPC, all-query) | 0.941 | 0.870 | **+0.
|
|
58
|
-
| Cyber (ATT&CK) | 0.766 | 0.749 | +0.073 | directional* |
|
|
53
|
+
| Domain | SMA tail top-5 | best RAG | Δ tail top-5 | Cliff's δ | Holm-significant |
|
|
54
|
+
|---|:--:|:--:|:--:|:--:|:--:|
|
|
55
|
+
| Medicine (HPO/MONDO) | 0.949 | 0.606 | **+0.343** | 0.333 | yes |
|
|
56
|
+
| Finance (US-GAAP) | 0.418 | 0.231 | **+0.187** | 0.167 | yes |
|
|
57
|
+
| Genomics (GO) | 0.849 | 0.682 | **+0.167** | 0.156 | yes |
|
|
58
|
+
| Legal (CPC, all-query) | 0.941 | 0.870 | **+0.071** | 0.064 | yes |
|
|
59
|
+
| Cyber (ATT&CK) | 0.766 | 0.749 | +0.017 | 0.073 | directional* |
|
|
59
60
|
|
|
60
61
|
\* Cyber survives Holm across domains but not a conservative
|
|
61
62
|
Bonferroni-over-baselines selection correction (p=0.035 → 0.17); reported as
|
|
@@ -88,6 +89,12 @@ logistic-regression baseline wins. The advantage is specific to structure.
|
|
|
88
89
|
|
|
89
90
|
## Quick start
|
|
90
91
|
|
|
92
|
+
```bash
|
|
93
|
+
pip install structuremappingmemory # from PyPI (import name: sma)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Or for development, with the evaluation/encoder extras:
|
|
97
|
+
|
|
91
98
|
```bash
|
|
92
99
|
python -m venv .venv && . .venv/bin/activate
|
|
93
100
|
pip install -e ".[encoders,eval]"
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
# PyPI distribution name (the import name stays `sma`); `sma` is taken on PyPI.
|
|
7
7
|
name = "structuremappingmemory"
|
|
8
|
-
version = "1.
|
|
8
|
+
version = "1.1.0"
|
|
9
9
|
description = "SMA-1: Structure-Mapping Agentic Memory — a structure-mapping retrieval memory that grounds language models in curated ontologies"
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
requires-python = ">=3.10"
|
|
@@ -60,6 +60,9 @@ local-llm = [
|
|
|
60
60
|
"huggingface-hub>=0.23",
|
|
61
61
|
"llama-cpp-python>=0.2.90"
|
|
62
62
|
]
|
|
63
|
+
mcp = [
|
|
64
|
+
"mcp>=1.0"
|
|
65
|
+
]
|
|
63
66
|
|
|
64
67
|
[project.urls]
|
|
65
68
|
Homepage = "https://github.com/ayazkhan27/SMA-1"
|
|
@@ -68,6 +71,7 @@ Issues = "https://github.com/ayazkhan27/SMA-1/issues"
|
|
|
68
71
|
|
|
69
72
|
[project.scripts]
|
|
70
73
|
sma = "sma.cli:main"
|
|
74
|
+
sma-mcp = "sma.mcp:main"
|
|
71
75
|
|
|
72
76
|
[tool.setuptools.packages.find]
|
|
73
77
|
include = ["sma*"]
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"""SMA-1 Model Context Protocol (MCP) server.
|
|
2
|
+
|
|
3
|
+
Exposes the structure-mapping memory as MCP tools so an MCP client (Codex CLI,
|
|
4
|
+
Claude, the OpenAI Agents SDK, ...) can:
|
|
5
|
+
|
|
6
|
+
* mount curated ontologies (one shared core, many domains),
|
|
7
|
+
* index a case base (records expressed as ontology term-ids),
|
|
8
|
+
* retrieve *structurally-analogous* prior cases by logical structure
|
|
9
|
+
(is-a subsumption + typed relations + rarity weighting), with a checkable
|
|
10
|
+
structural citation, a cite-or-abstain decision, and an expectation-violation
|
|
11
|
+
**novelty** flag.
|
|
12
|
+
|
|
13
|
+
This is the analogical-memory layer for a discovery loop: the LLM generates and
|
|
14
|
+
verifies; SMA grounds each step in structurally-analogous precedent and flags the
|
|
15
|
+
genuinely never-seen, which surface-similarity (vector RAG) cannot do.
|
|
16
|
+
|
|
17
|
+
Design notes
|
|
18
|
+
------------
|
|
19
|
+
* The engine (:class:`SmaEngine`) is import-light: it uses only the SMA core
|
|
20
|
+
(``sma.ontology`` / ``sma.index`` / ``sma.sage``), NOT the eval baselines
|
|
21
|
+
(bm25 / dense / hipporag) and NOT the ``mcp`` SDK -- so it is unit-testable
|
|
22
|
+
without the transport dependency. ``mcp`` is imported lazily in
|
|
23
|
+
:func:`build_server` / :func:`main`.
|
|
24
|
+
* Cite-or-abstain gates on the RAW structural grounding score (the codebase is
|
|
25
|
+
explicit that the normalized confidence saturates and does not separate
|
|
26
|
+
known/unknown). A per-ontology ``ground_threshold`` is calibrated offline; when
|
|
27
|
+
unset there is no gate (every non-empty result is "grounded") -- calibrate it.
|
|
28
|
+
|
|
29
|
+
Run as a stdio MCP server::
|
|
30
|
+
|
|
31
|
+
python -m sma.mcp # or: sma-mcp
|
|
32
|
+
|
|
33
|
+
Configure a manifest of ontologies to auto-register via the ``SMA_MANIFEST`` env
|
|
34
|
+
var (see ``examples/sma_manifest.example.json``).
|
|
35
|
+
"""
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import json
|
|
39
|
+
import os
|
|
40
|
+
from dataclasses import dataclass, field
|
|
41
|
+
from typing import Any
|
|
42
|
+
|
|
43
|
+
from sma.index.macfac import MacFacIndex
|
|
44
|
+
from sma.ontology import (
|
|
45
|
+
MountedOntology,
|
|
46
|
+
load_attack_stix,
|
|
47
|
+
load_capec,
|
|
48
|
+
load_cpc,
|
|
49
|
+
load_cwe,
|
|
50
|
+
load_mitre_xml,
|
|
51
|
+
load_obo,
|
|
52
|
+
load_ontology,
|
|
53
|
+
load_owl,
|
|
54
|
+
load_owl_dir,
|
|
55
|
+
load_rdflib,
|
|
56
|
+
load_usgaap,
|
|
57
|
+
mount,
|
|
58
|
+
)
|
|
59
|
+
from sma.sage.pools import SagePool
|
|
60
|
+
|
|
61
|
+
# Format -> loader. "auto" dispatches obo/owl by file extension.
|
|
62
|
+
_LOADERS = {
|
|
63
|
+
"auto": load_ontology,
|
|
64
|
+
"obo": load_obo,
|
|
65
|
+
"owl": load_owl,
|
|
66
|
+
"owl_dir": load_owl_dir,
|
|
67
|
+
"rdf": load_rdflib,
|
|
68
|
+
"ttl": load_rdflib,
|
|
69
|
+
"stix": load_attack_stix,
|
|
70
|
+
"attack": load_attack_stix,
|
|
71
|
+
"cpc": load_cpc,
|
|
72
|
+
"xbrl": load_usgaap,
|
|
73
|
+
"usgaap": load_usgaap,
|
|
74
|
+
"cwe": load_cwe,
|
|
75
|
+
"capec": load_capec,
|
|
76
|
+
"mitre_xml": load_mitre_xml,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
SUPPORTED_FORMATS = sorted(_LOADERS)
|
|
80
|
+
|
|
81
|
+
_SENTINEL = object()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _load_graph(path: str, fmt: str | None):
|
|
85
|
+
fmt = (fmt or "auto").lower()
|
|
86
|
+
loader = _LOADERS.get(fmt)
|
|
87
|
+
if loader is None:
|
|
88
|
+
raise ValueError(f"unknown ontology format {fmt!r}; supported: {SUPPORTED_FORMATS}")
|
|
89
|
+
return loader(path)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _envfloat(name: str, default: float | None) -> float | None:
|
|
93
|
+
val = os.environ.get(name)
|
|
94
|
+
if val is None or val == "":
|
|
95
|
+
return default
|
|
96
|
+
try:
|
|
97
|
+
return float(val)
|
|
98
|
+
except ValueError:
|
|
99
|
+
return default
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class _Ont:
|
|
104
|
+
"""A registered ontology and (lazily) its mounted lattice + case index."""
|
|
105
|
+
|
|
106
|
+
name: str
|
|
107
|
+
path: str
|
|
108
|
+
fmt: str = "auto"
|
|
109
|
+
ground_threshold: float | None = None
|
|
110
|
+
novelty_threshold: float = 0.5
|
|
111
|
+
mounted: MountedOntology | None = None
|
|
112
|
+
cases: list[dict] = field(default_factory=list)
|
|
113
|
+
_index: Any = None
|
|
114
|
+
_pool: Any = None
|
|
115
|
+
_keymap: dict[str, str] = field(default_factory=dict)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class SmaEngine:
|
|
119
|
+
"""The MCP-free core: register/mount ontologies, index cases, retrieve, novelty.
|
|
120
|
+
|
|
121
|
+
Mirrors ``sma.eval.agentic.memories.SmaMemory`` (build_case + MacFacIndex +
|
|
122
|
+
SagePool) without importing the eval module's heavy baseline dependencies.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
def __init__(self) -> None:
|
|
126
|
+
self.onts: dict[str, _Ont] = {}
|
|
127
|
+
self.default_ground_threshold = _envfloat("SMA_GROUND_THRESHOLD", None)
|
|
128
|
+
self.default_novelty_threshold = _envfloat("SMA_NOVELTY_THRESHOLD", 0.5) or 0.5
|
|
129
|
+
|
|
130
|
+
# -- registration / mounting -------------------------------------------
|
|
131
|
+
def register(
|
|
132
|
+
self,
|
|
133
|
+
name: str,
|
|
134
|
+
path: str,
|
|
135
|
+
fmt: str = "auto",
|
|
136
|
+
ground_threshold: float | None = None,
|
|
137
|
+
novelty_threshold: float | None = None,
|
|
138
|
+
) -> None:
|
|
139
|
+
self.onts[name] = _Ont(
|
|
140
|
+
name=name,
|
|
141
|
+
path=path,
|
|
142
|
+
fmt=(fmt or "auto"),
|
|
143
|
+
ground_threshold=(
|
|
144
|
+
ground_threshold if ground_threshold is not None else self.default_ground_threshold
|
|
145
|
+
),
|
|
146
|
+
novelty_threshold=(
|
|
147
|
+
novelty_threshold if novelty_threshold is not None else self.default_novelty_threshold
|
|
148
|
+
),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def _get(self, name: str) -> _Ont:
|
|
152
|
+
o = self.onts.get(name)
|
|
153
|
+
if o is None:
|
|
154
|
+
raise KeyError(f"ontology {name!r} is not registered; mount it first ({sorted(self.onts)})")
|
|
155
|
+
return o
|
|
156
|
+
|
|
157
|
+
def _ensure_mounted(self, name: str) -> _Ont:
|
|
158
|
+
o = self._get(name)
|
|
159
|
+
if o.mounted is None:
|
|
160
|
+
o.mounted = mount(_load_graph(o.path, o.fmt))
|
|
161
|
+
return o
|
|
162
|
+
|
|
163
|
+
def mount_ontology(self, name: str, path: str, fmt: str = "auto") -> dict:
|
|
164
|
+
self.register(name, path, fmt)
|
|
165
|
+
o = self._ensure_mounted(name)
|
|
166
|
+
return {
|
|
167
|
+
"ontology": name,
|
|
168
|
+
"format": o.fmt,
|
|
169
|
+
"concepts": len(o.mounted.graph.terms),
|
|
170
|
+
"indexed_cases": len(o.cases),
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# -- indexing -----------------------------------------------------------
|
|
174
|
+
def _rebuild(self, o: _Ont) -> None:
|
|
175
|
+
o._keymap = {}
|
|
176
|
+
o._pool = SagePool("mcp", assimilation_threshold=0.2)
|
|
177
|
+
cases = []
|
|
178
|
+
for c in o.cases:
|
|
179
|
+
case = o.mounted.build_case(frozenset(c["term_ids"]), metadata={"key": c["key"]})
|
|
180
|
+
o._keymap[case.case_id] = c["key"]
|
|
181
|
+
cases.append(case)
|
|
182
|
+
o._pool.assimilate(case)
|
|
183
|
+
o._index = MacFacIndex(config=o.mounted.config, canon=o.mounted.canon)
|
|
184
|
+
o._index.build(cases)
|
|
185
|
+
|
|
186
|
+
def index_cases(self, name: str, cases: list[dict]) -> int:
|
|
187
|
+
"""Append cases (each ``{key, term_ids, text?}``) and (re)build the index."""
|
|
188
|
+
o = self._ensure_mounted(name)
|
|
189
|
+
for c in cases:
|
|
190
|
+
if "key" not in c or "term_ids" not in c:
|
|
191
|
+
raise ValueError("each case needs 'key' and 'term_ids'")
|
|
192
|
+
o.cases.append({"key": str(c["key"]), "term_ids": list(c["term_ids"]), "text": c.get("text", "")})
|
|
193
|
+
self._rebuild(o)
|
|
194
|
+
return len(o.cases)
|
|
195
|
+
|
|
196
|
+
# -- encoding (no LLM): raw text -> ontology term-ids -------------------
|
|
197
|
+
def encode_text(self, name: str, text: str, max_terms: int = 20) -> dict:
|
|
198
|
+
"""Deterministic lexical match of term NAMES in ``text`` -> term-ids.
|
|
199
|
+
|
|
200
|
+
A starter encoder (no LLM, honoring the no-LLM-in-extraction principle).
|
|
201
|
+
Replace with a domain-specific encoder for production recall.
|
|
202
|
+
"""
|
|
203
|
+
o = self._ensure_mounted(name)
|
|
204
|
+
hay = f" {text.lower()} "
|
|
205
|
+
hits: list[tuple[str, str]] = []
|
|
206
|
+
for tid, term in o.mounted.graph.terms.items():
|
|
207
|
+
nm = (getattr(term, "name", "") or "").strip().lower()
|
|
208
|
+
if len(nm) >= 3 and f" {nm} " in hay:
|
|
209
|
+
hits.append((tid, term.name))
|
|
210
|
+
hits.sort(key=lambda h: -len(h[1])) # prefer more specific (longer) names
|
|
211
|
+
hits = hits[:max_terms]
|
|
212
|
+
return {"ontology": name, "term_ids": [h[0] for h in hits], "matched_names": [h[1] for h in hits]}
|
|
213
|
+
|
|
214
|
+
def _resolve_terms(self, o: _Ont, term_ids, text) -> frozenset[str]:
|
|
215
|
+
if term_ids:
|
|
216
|
+
return frozenset(term_ids)
|
|
217
|
+
if text:
|
|
218
|
+
return frozenset(self.encode_text(o.name, text)["term_ids"])
|
|
219
|
+
raise ValueError("provide either term_ids or text")
|
|
220
|
+
|
|
221
|
+
# -- retrieval / novelty ------------------------------------------------
|
|
222
|
+
def retrieve(
|
|
223
|
+
self,
|
|
224
|
+
name: str,
|
|
225
|
+
term_ids=None,
|
|
226
|
+
text: str = "",
|
|
227
|
+
k: int = 5,
|
|
228
|
+
ground_threshold=_SENTINEL,
|
|
229
|
+
) -> dict:
|
|
230
|
+
o = self._ensure_mounted(name)
|
|
231
|
+
tids = self._resolve_terms(o, term_ids, text)
|
|
232
|
+
if not tids:
|
|
233
|
+
return {"ontology": name, "abstain": True, "reason": "no query term-ids (encoding matched nothing)",
|
|
234
|
+
"citations": [], "novelty": 1.0, "novelty_flag": True, "query_term_ids": []}
|
|
235
|
+
if o._index is None:
|
|
236
|
+
return {"ontology": name, "abstain": True, "reason": "no cases indexed",
|
|
237
|
+
"citations": [], "novelty": 1.0, "novelty_flag": True, "query_term_ids": sorted(tids)}
|
|
238
|
+
qc = o.mounted.build_case(tids)
|
|
239
|
+
res = o._index.retrieve(qc, k=k, shortlist=80, fac_budget=40)
|
|
240
|
+
nov = float(o._pool.expectation_violation(qc))
|
|
241
|
+
thr = o.ground_threshold if ground_threshold is _SENTINEL else ground_threshold
|
|
242
|
+
top = max((r.score for r in res), default=0.0) or 1.0
|
|
243
|
+
citations = [
|
|
244
|
+
{
|
|
245
|
+
"id": o._keymap.get(r.case_id, ""),
|
|
246
|
+
"score": round(float(r.score), 4),
|
|
247
|
+
"confidence": round(min(max(r.score / top, 0.0), 1.0), 4),
|
|
248
|
+
"rank": i,
|
|
249
|
+
}
|
|
250
|
+
for i, r in enumerate(res, 1)
|
|
251
|
+
]
|
|
252
|
+
grounded = bool(res) and (thr is None or res[0].score >= thr)
|
|
253
|
+
return {
|
|
254
|
+
"ontology": name,
|
|
255
|
+
"query_term_ids": sorted(tids),
|
|
256
|
+
"abstain": not grounded,
|
|
257
|
+
"abstain_threshold": thr,
|
|
258
|
+
"novelty": round(nov, 4),
|
|
259
|
+
"novelty_flag": nov >= o.novelty_threshold,
|
|
260
|
+
"citations": citations if grounded else [],
|
|
261
|
+
"note": (
|
|
262
|
+
None if grounded
|
|
263
|
+
else "top grounding score below threshold -> no structural precedent; do not fabricate an answer"
|
|
264
|
+
),
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
def novelty(self, name: str, term_ids=None, text: str = "") -> dict:
|
|
268
|
+
o = self._ensure_mounted(name)
|
|
269
|
+
tids = self._resolve_terms(o, term_ids, text)
|
|
270
|
+
if o._pool is None or not tids:
|
|
271
|
+
return {"ontology": name, "novelty": 1.0, "novelty_flag": True, "query_term_ids": sorted(tids)}
|
|
272
|
+
qc = o.mounted.build_case(tids)
|
|
273
|
+
nov = float(o._pool.expectation_violation(qc))
|
|
274
|
+
return {
|
|
275
|
+
"ontology": name,
|
|
276
|
+
"query_term_ids": sorted(tids),
|
|
277
|
+
"novelty": round(nov, 4),
|
|
278
|
+
"novelty_flag": nov >= o.novelty_threshold,
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
def list_ontologies(self) -> dict:
|
|
282
|
+
return {
|
|
283
|
+
"ontologies": [
|
|
284
|
+
{
|
|
285
|
+
"name": o.name,
|
|
286
|
+
"format": o.fmt,
|
|
287
|
+
"mounted": o.mounted is not None,
|
|
288
|
+
"concepts": (len(o.mounted.graph.terms) if o.mounted is not None else None),
|
|
289
|
+
"indexed_cases": len(o.cases),
|
|
290
|
+
"ground_threshold": o.ground_threshold,
|
|
291
|
+
"novelty_threshold": o.novelty_threshold,
|
|
292
|
+
}
|
|
293
|
+
for o in self.onts.values()
|
|
294
|
+
],
|
|
295
|
+
"supported_formats": SUPPORTED_FORMATS,
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def load_manifest(engine: SmaEngine, path: str) -> None:
|
|
300
|
+
"""Register ontologies (and optionally eager-index cases) from a JSON manifest.
|
|
301
|
+
|
|
302
|
+
``{"ontologies": [{"name","path","format"?,"ground_threshold"?,"novelty_threshold"?}],
|
|
303
|
+
"cases": {"<ontology>": [{"key","term_ids","text"?}, ...]}}``
|
|
304
|
+
"""
|
|
305
|
+
with open(path) as fh:
|
|
306
|
+
data = json.load(fh)
|
|
307
|
+
for o in data.get("ontologies", []):
|
|
308
|
+
engine.register(
|
|
309
|
+
o["name"], o["path"], o.get("format", "auto"),
|
|
310
|
+
o.get("ground_threshold"), o.get("novelty_threshold"),
|
|
311
|
+
)
|
|
312
|
+
for name, cases in (data.get("cases") or {}).items():
|
|
313
|
+
if cases:
|
|
314
|
+
engine.index_cases(name, cases)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def build_server(engine: SmaEngine | None = None):
|
|
318
|
+
"""Construct the FastMCP server (imports the ``mcp`` SDK lazily)."""
|
|
319
|
+
from mcp.server.fastmcp import FastMCP
|
|
320
|
+
|
|
321
|
+
engine = engine or SmaEngine()
|
|
322
|
+
manifest = os.environ.get("SMA_MANIFEST")
|
|
323
|
+
if manifest and os.path.exists(manifest):
|
|
324
|
+
load_manifest(engine, manifest)
|
|
325
|
+
|
|
326
|
+
server = FastMCP("sma-1")
|
|
327
|
+
|
|
328
|
+
@server.tool()
|
|
329
|
+
def list_ontologies() -> dict:
|
|
330
|
+
"""List registered ontologies, their concept counts, indexed-case counts, and supported source formats."""
|
|
331
|
+
return engine.list_ontologies()
|
|
332
|
+
|
|
333
|
+
@server.tool()
|
|
334
|
+
def mount_ontology(name: str, path: str, format: str = "auto") -> dict:
|
|
335
|
+
"""Register and mount a curated ontology as a structural lattice.
|
|
336
|
+
|
|
337
|
+
`format` is auto-detected for OBO/OWL; pass one of the supported formats
|
|
338
|
+
(stix, cpc, xbrl, cwe, capec, mitre_xml, rdf, ...) otherwise. Returns the
|
|
339
|
+
concept count once mounted."""
|
|
340
|
+
return engine.mount_ontology(name, path, format)
|
|
341
|
+
|
|
342
|
+
@server.tool()
|
|
343
|
+
def index_cases(ontology: str, cases: list[dict]) -> dict:
|
|
344
|
+
"""Add cases to an ontology's memory. Each case is {"key","term_ids",["text"]}.
|
|
345
|
+
|
|
346
|
+
`key` is what gets cited back; `term_ids` are the ontology term-ids that
|
|
347
|
+
encode the case's structure. Returns the total number of indexed cases."""
|
|
348
|
+
total = engine.index_cases(ontology, cases)
|
|
349
|
+
return {"ontology": ontology, "indexed_cases": total}
|
|
350
|
+
|
|
351
|
+
@server.tool()
|
|
352
|
+
def encode_text(ontology: str, text: str, max_terms: int = 20) -> dict:
|
|
353
|
+
"""Deterministically map free text to ontology term-ids via term-name matching (no LLM).
|
|
354
|
+
|
|
355
|
+
Use to turn an abstract/finding into `term_ids` before retrieve/novelty.
|
|
356
|
+
This is a starter encoder; swap in a domain encoder for better recall."""
|
|
357
|
+
return engine.encode_text(ontology, text, max_terms)
|
|
358
|
+
|
|
359
|
+
@server.tool()
|
|
360
|
+
def retrieve(
|
|
361
|
+
ontology: str,
|
|
362
|
+
text: str = "",
|
|
363
|
+
term_ids: list[str] | None = None,
|
|
364
|
+
k: int = 5,
|
|
365
|
+
ground_threshold: float | None = None,
|
|
366
|
+
) -> dict:
|
|
367
|
+
"""Retrieve STRUCTURALLY-ANALOGOUS prior cases by logical structure (not surface similarity).
|
|
368
|
+
|
|
369
|
+
Provide `term_ids` (preferred) or `text` (encoded on the fly). Returns ranked
|
|
370
|
+
citations {id, score, confidence, rank}, a calibrated cite-or-abstain decision
|
|
371
|
+
(`abstain`=True means no structural precedent -- do NOT fabricate), and a
|
|
372
|
+
`novelty` score (high = a case unlike anything indexed)."""
|
|
373
|
+
gt = _SENTINEL if ground_threshold is None else ground_threshold
|
|
374
|
+
return engine.retrieve(ontology, term_ids=term_ids, text=text, k=k, ground_threshold=gt)
|
|
375
|
+
|
|
376
|
+
@server.tool()
|
|
377
|
+
def novelty(ontology: str, text: str = "", term_ids: list[str] | None = None) -> dict:
|
|
378
|
+
"""Score how novel a case is vs. the indexed memory (expectation-violation).
|
|
379
|
+
|
|
380
|
+
High `novelty` / `novelty_flag`=True means the case is structurally unlike
|
|
381
|
+
anything seen -- a candidate genuinely-new pattern."""
|
|
382
|
+
return engine.novelty(ontology, term_ids=term_ids, text=text)
|
|
383
|
+
|
|
384
|
+
return server, engine
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def main() -> None:
|
|
388
|
+
server, _ = build_server()
|
|
389
|
+
server.run()
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
if __name__ == "__main__":
|
|
393
|
+
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: structuremappingmemory
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: SMA-1: Structure-Mapping Agentic Memory — a structure-mapping retrieval memory that grounds language models in curated ontologies
|
|
5
5
|
Author-email: Ayaz Khan <aak2259@columbia.edu>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -50,11 +50,14 @@ Requires-Dist: networkx>=3; extra == "eval"
|
|
|
50
50
|
Provides-Extra: local-llm
|
|
51
51
|
Requires-Dist: huggingface-hub>=0.23; extra == "local-llm"
|
|
52
52
|
Requires-Dist: llama-cpp-python>=0.2.90; extra == "local-llm"
|
|
53
|
+
Provides-Extra: mcp
|
|
54
|
+
Requires-Dist: mcp>=1.0; extra == "mcp"
|
|
53
55
|
Dynamic: license-file
|
|
54
56
|
|
|
55
57
|
# SMA-1: Structure-Mapping Agentic Memory
|
|
56
58
|
|
|
57
59
|
[](https://github.com/ayazkhan27/SMA-1/actions/workflows/ci.yml)
|
|
60
|
+
[](https://pypi.org/project/structuremappingmemory/)
|
|
58
61
|
[](LICENSE)
|
|
59
62
|
[](https://huggingface.co/spaces/zephyr27/SMA-1-demo)
|
|
60
63
|
|
|
@@ -103,13 +106,13 @@ adapters.
|
|
|
103
106
|
On a **memory-swap benchmark** where only the retriever varies, SMA beats a strong
|
|
104
107
|
RAG + KG baseline suite on the rare/long-tail slice across five domains:
|
|
105
108
|
|
|
106
|
-
| Domain | SMA tail top-5 | best RAG | Δ | Holm-significant |
|
|
107
|
-
|
|
108
|
-
| Medicine (HPO/MONDO) | 0.949 | 0.606 | **+0.
|
|
109
|
-
| Finance (US-GAAP) | 0.418 | 0.231 | **+0.
|
|
110
|
-
| Genomics (GO) | 0.849 | 0.682 | **+0.
|
|
111
|
-
| Legal (CPC, all-query) | 0.941 | 0.870 | **+0.
|
|
112
|
-
| Cyber (ATT&CK) | 0.766 | 0.749 | +0.073 | directional* |
|
|
109
|
+
| Domain | SMA tail top-5 | best RAG | Δ tail top-5 | Cliff's δ | Holm-significant |
|
|
110
|
+
|---|:--:|:--:|:--:|:--:|:--:|
|
|
111
|
+
| Medicine (HPO/MONDO) | 0.949 | 0.606 | **+0.343** | 0.333 | yes |
|
|
112
|
+
| Finance (US-GAAP) | 0.418 | 0.231 | **+0.187** | 0.167 | yes |
|
|
113
|
+
| Genomics (GO) | 0.849 | 0.682 | **+0.167** | 0.156 | yes |
|
|
114
|
+
| Legal (CPC, all-query) | 0.941 | 0.870 | **+0.071** | 0.064 | yes |
|
|
115
|
+
| Cyber (ATT&CK) | 0.766 | 0.749 | +0.017 | 0.073 | directional* |
|
|
113
116
|
|
|
114
117
|
\* Cyber survives Holm across domains but not a conservative
|
|
115
118
|
Bonferroni-over-baselines selection correction (p=0.035 → 0.17); reported as
|
|
@@ -142,6 +145,12 @@ logistic-regression baseline wins. The advantage is specific to structure.
|
|
|
142
145
|
|
|
143
146
|
## Quick start
|
|
144
147
|
|
|
148
|
+
```bash
|
|
149
|
+
pip install structuremappingmemory # from PyPI (import name: sma)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Or for development, with the evaluation/encoder extras:
|
|
153
|
+
|
|
145
154
|
```bash
|
|
146
155
|
python -m venv .venv && . .venv/bin/activate
|
|
147
156
|
pip install -e ".[encoders,eval]"
|
|
@@ -4,6 +4,7 @@ pyproject.toml
|
|
|
4
4
|
sma/__init__.py
|
|
5
5
|
sma/__main__.py
|
|
6
6
|
sma/cli.py
|
|
7
|
+
sma/mcp.py
|
|
7
8
|
sma/agent/__init__.py
|
|
8
9
|
sma/agent/adapter_draft.py
|
|
9
10
|
sma/agent/api.py
|
|
@@ -130,5 +131,6 @@ tests/test_bugsinpy_t3.py
|
|
|
130
131
|
tests/test_drift.py
|
|
131
132
|
tests/test_gates.py
|
|
132
133
|
tests/test_hipporag.py
|
|
134
|
+
tests/test_mcp.py
|
|
133
135
|
tests/test_production_loop.py
|
|
134
136
|
tests/test_stats.py
|