adaptive-memory-engine 0.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- adaptive_memory_engine-0.1.6.dist-info/METADATA +228 -0
- adaptive_memory_engine-0.1.6.dist-info/RECORD +72 -0
- adaptive_memory_engine-0.1.6.dist-info/WHEEL +4 -0
- adaptive_memory_engine-0.1.6.dist-info/entry_points.txt +3 -0
- adaptive_memory_engine-0.1.6.dist-info/licenses/LICENSE +21 -0
- ame/__init__.py +1 -0
- ame/agent/__init__.py +1 -0
- ame/agent/mcp.py +474 -0
- ame/agent/memory_api.py +141 -0
- ame/agent/results.py +30 -0
- ame/bronze/schema.py +17 -0
- ame/bronze/store.py +38 -0
- ame/cli/__init__.py +1 -0
- ame/cli/main.py +903 -0
- ame/connectors/base.py +30 -0
- ame/connectors/contract.py +199 -0
- ame/connectors/github.py +66 -0
- ame/connectors/google.py +464 -0
- ame/connectors/google_oauth.py +156 -0
- ame/connectors/jira.py +66 -0
- ame/connectors/json_helpers.py +43 -0
- ame/connectors/markdown.py +116 -0
- ame/connectors/notion.py +59 -0
- ame/connectors/oauth_callback.py +102 -0
- ame/connectors/oauth_provider.py +250 -0
- ame/connectors/obsidian.py +19 -0
- ame/connectors/router.py +155 -0
- ame/connectors/slack.py +66 -0
- ame/connectors/slack_oauth.py +417 -0
- ame/connectors/sync_history.py +73 -0
- ame/context_budget.py +106 -0
- ame/core/config.py +77 -0
- ame/core/corpus.py +17 -0
- ame/core/errors.py +18 -0
- ame/core/paths.py +111 -0
- ame/core/state.py +57 -0
- ame/export/obsidian.py +123 -0
- ame/gold/builder.py +300 -0
- ame/gold/ontology.py +80 -0
- ame/gold/resolver.py +91 -0
- ame/gold/schema.py +40 -0
- ame/gold/store.py +45 -0
- ame/hardware/profiler.py +85 -0
- ame/hardware/tier.py +27 -0
- ame/hermes/__init__.py +3 -0
- ame/hermes/memory.py +209 -0
- ame/models/download.py +243 -0
- ame/models/ollama.py +60 -0
- ame/models/registry.py +101 -0
- ame/models/router.py +22 -0
- ame/pipeline.py +155 -0
- ame/query/diff.py +40 -0
- ame/query/engine.py +919 -0
- ame/query/memory_os.py +313 -0
- ame/query/mql.py +84 -0
- ame/query/multihop.py +264 -0
- ame/query/result.py +20 -0
- ame/sdk.py +52 -0
- ame/security.py +145 -0
- ame/silver/extractor.py +414 -0
- ame/silver/llm_extractor.py +181 -0
- ame/silver/prompts.py +56 -0
- ame/silver/rationale.py +140 -0
- ame/silver/schema.py +51 -0
- ame/silver/store.py +59 -0
- ame/storage/custom_kg.py +33 -0
- ame/storage/lightrag_adapter.py +362 -0
- ame/validation/confidence.py +5 -0
- ame/validation/grounding.py +10 -0
- ame/validation/type_gate.py +22 -0
- ame/writeback.py +173 -0
- memory/__init__.py +3 -0
ame/pipeline.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from ame.bronze.store import BronzeStore
|
|
9
|
+
from ame.connectors.router import ConnectorRouter
|
|
10
|
+
from ame.core.config import load_config
|
|
11
|
+
from ame.core.corpus import require_corpus
|
|
12
|
+
from ame.core.state import CorpusStateStore, IngestedDocumentState
|
|
13
|
+
from ame.gold.builder import GoldBuilder
|
|
14
|
+
from ame.gold.store import GoldStore
|
|
15
|
+
from ame.hardware.profiler import HardwareProfiler
|
|
16
|
+
from ame.models.ollama import OllamaClient
|
|
17
|
+
from ame.models.registry import load_default_registry
|
|
18
|
+
from ame.models.router import ModelRouter
|
|
19
|
+
from ame.silver.extractor import DeterministicExtractor
|
|
20
|
+
from ame.silver.llm_extractor import LlmClient, LlmExtractor
|
|
21
|
+
from ame.silver.rationale import RationaleExtractor
|
|
22
|
+
from ame.silver.schema import SilverEntity
|
|
23
|
+
from ame.silver.store import SilverStore
|
|
24
|
+
from ame.storage.lightrag_adapter import LightRagAdapter
|
|
25
|
+
from ame.validation.confidence import passes_confidence
|
|
26
|
+
from ame.validation.grounding import is_grounded
|
|
27
|
+
from ame.validation.type_gate import relation_type_valid
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class IngestReport(BaseModel):
|
|
31
|
+
mode: str
|
|
32
|
+
documents: int
|
|
33
|
+
silver_entities: int
|
|
34
|
+
silver_relations: int
|
|
35
|
+
silver_decisions: int
|
|
36
|
+
silver_rationales: int
|
|
37
|
+
rejected: int
|
|
38
|
+
gold_nodes: int
|
|
39
|
+
gold_edges: int
|
|
40
|
+
custom_kg_path: Path
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class MemoryPipeline:
|
|
44
|
+
def ingest(
|
|
45
|
+
self,
|
|
46
|
+
corpus_id: str,
|
|
47
|
+
source_path: Path,
|
|
48
|
+
mode: Literal["deterministic", "llm"] = "deterministic",
|
|
49
|
+
llm_client: LlmClient | None = None,
|
|
50
|
+
profile: str | None = None,
|
|
51
|
+
) -> IngestReport:
|
|
52
|
+
root = require_corpus(corpus_id)
|
|
53
|
+
config = load_config()
|
|
54
|
+
connector = ConnectorRouter().resolve(source_path, profile)
|
|
55
|
+
bronze = BronzeStore(root)
|
|
56
|
+
extractor = DeterministicExtractor() if mode == "deterministic" else LlmExtractor(llm_client or self._default_llm_client(config))
|
|
57
|
+
|
|
58
|
+
docs_by_id = {}
|
|
59
|
+
for ref in connector.scan(source_path):
|
|
60
|
+
doc = bronze.put(connector.load(corpus_id, ref))
|
|
61
|
+
docs_by_id[doc.id] = doc
|
|
62
|
+
docs = list(bronze.list())
|
|
63
|
+
doc_map = {doc.id: doc for doc in docs}
|
|
64
|
+
entities = []
|
|
65
|
+
relations = []
|
|
66
|
+
decisions = []
|
|
67
|
+
rejected: list[dict] = []
|
|
68
|
+
|
|
69
|
+
for doc in docs:
|
|
70
|
+
extracted_entities, extracted_relations, extracted_decisions = extractor.extract(doc)
|
|
71
|
+
valid_entities = []
|
|
72
|
+
for entity in extracted_entities:
|
|
73
|
+
if not is_grounded(entity, doc_map) or not passes_confidence(entity.confidence, config.engine.confidence_threshold):
|
|
74
|
+
rejected.append({"kind": "entity", "id": entity.id, "reason": "validation_failed"})
|
|
75
|
+
continue
|
|
76
|
+
valid_entities.append(entity)
|
|
77
|
+
valid_decisions = []
|
|
78
|
+
for decision in extracted_decisions:
|
|
79
|
+
if not passes_confidence(decision.confidence, config.engine.confidence_threshold):
|
|
80
|
+
rejected.append({"kind": "decision", "id": decision.id, "reason": "validation_failed"})
|
|
81
|
+
continue
|
|
82
|
+
valid_decisions.append(decision)
|
|
83
|
+
decision_entities = [
|
|
84
|
+
SilverEntity(
|
|
85
|
+
id=f"entity_{decision.id}",
|
|
86
|
+
corpus_id=decision.corpus_id,
|
|
87
|
+
type="Decision",
|
|
88
|
+
name=decision.title,
|
|
89
|
+
span=decision.title if decision.title in doc.content else None,
|
|
90
|
+
source_ids=decision.source_ids,
|
|
91
|
+
confidence=decision.confidence,
|
|
92
|
+
)
|
|
93
|
+
for decision in valid_decisions
|
|
94
|
+
]
|
|
95
|
+
validation_entities = valid_entities + decision_entities
|
|
96
|
+
for relation in extracted_relations:
|
|
97
|
+
if not passes_confidence(relation.confidence, config.engine.confidence_threshold) or not relation_type_valid(relation, validation_entities):
|
|
98
|
+
rejected.append(
|
|
99
|
+
{
|
|
100
|
+
"kind": "relation",
|
|
101
|
+
"id": relation.id,
|
|
102
|
+
"reason": "validation_failed",
|
|
103
|
+
"subject": relation.subject,
|
|
104
|
+
"predicate": relation.predicate,
|
|
105
|
+
"object": relation.object,
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
continue
|
|
109
|
+
relations.append(relation)
|
|
110
|
+
entities.extend(valid_entities)
|
|
111
|
+
decisions.extend(valid_decisions)
|
|
112
|
+
|
|
113
|
+
rationales = RationaleExtractor().extract(docs, decisions)
|
|
114
|
+
SilverStore(root).replace(entities, relations, decisions, rejected, rationales)
|
|
115
|
+
nodes, edges, timeline = GoldBuilder().build(entities, relations, decisions, rationales)
|
|
116
|
+
GoldStore(root).replace(nodes, edges, timeline)
|
|
117
|
+
kg_path = LightRagAdapter(root).sync(nodes, edges, docs)
|
|
118
|
+
counts = {
|
|
119
|
+
"documents": len(docs),
|
|
120
|
+
"silver_entities": len(entities),
|
|
121
|
+
"silver_relations": len(relations),
|
|
122
|
+
"silver_decisions": len(decisions),
|
|
123
|
+
"silver_rationales": len(rationales),
|
|
124
|
+
"rejected": len(rejected),
|
|
125
|
+
"gold_nodes": len(nodes),
|
|
126
|
+
"gold_edges": len(edges),
|
|
127
|
+
}
|
|
128
|
+
CorpusStateStore(root).record_ingest(
|
|
129
|
+
source_path=source_path,
|
|
130
|
+
mode=mode,
|
|
131
|
+
documents=[
|
|
132
|
+
IngestedDocumentState(id=doc.id, source_id=doc.source_id, content_hash=doc.content_hash)
|
|
133
|
+
for doc in docs
|
|
134
|
+
],
|
|
135
|
+
counts=counts,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return IngestReport(
|
|
139
|
+
mode=mode,
|
|
140
|
+
documents=len(docs),
|
|
141
|
+
silver_entities=len(entities),
|
|
142
|
+
silver_relations=len(relations),
|
|
143
|
+
silver_decisions=len(decisions),
|
|
144
|
+
silver_rationales=len(rationales),
|
|
145
|
+
rejected=len(rejected),
|
|
146
|
+
gold_nodes=len(nodes),
|
|
147
|
+
gold_edges=len(edges),
|
|
148
|
+
custom_kg_path=kg_path,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def _default_llm_client(self, config) -> OllamaClient:
|
|
152
|
+
registry = load_default_registry()
|
|
153
|
+
profile = HardwareProfiler().profile(Path.home())
|
|
154
|
+
plan = ModelRouter(registry).plan(profile)
|
|
155
|
+
return OllamaClient(model=plan.models.extract.model, base_url=config.lightrag.ollama_host)
|
ame/query/diff.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import date, timedelta
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from ame.gold.schema import GoldTimelineEvent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MemoryDiff(BaseModel):
|
|
11
|
+
window_days: int
|
|
12
|
+
since: str | None = None
|
|
13
|
+
accepted_decisions: list[str] = Field(default_factory=list)
|
|
14
|
+
superseded_decisions: list[str] = Field(default_factory=list)
|
|
15
|
+
current_false_decisions: list[str] = Field(default_factory=list)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MemoryDiffEngine:
|
|
19
|
+
def diff_last_days(self, timeline: list[GoldTimelineEvent], days: int = 7) -> MemoryDiff:
|
|
20
|
+
dated = [(event, self._parse_date(event.valid_from)) for event in timeline if event.valid_from]
|
|
21
|
+
if not dated:
|
|
22
|
+
return MemoryDiff(window_days=days)
|
|
23
|
+
newest = max(day for _, day in dated if day is not None)
|
|
24
|
+
since = newest - timedelta(days=days)
|
|
25
|
+
in_window = [event for event, day in dated if day is not None and day >= since]
|
|
26
|
+
return MemoryDiff(
|
|
27
|
+
window_days=days,
|
|
28
|
+
since=since.isoformat(),
|
|
29
|
+
accepted_decisions=[event.title for event in in_window if event.status == "accepted"],
|
|
30
|
+
superseded_decisions=[event.title for event in in_window if event.supersedes],
|
|
31
|
+
current_false_decisions=[event.title for event in timeline if event.current is False and event.superseded_by],
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def _parse_date(self, value: str | None) -> date | None:
|
|
35
|
+
if not value:
|
|
36
|
+
return None
|
|
37
|
+
try:
|
|
38
|
+
return date.fromisoformat(value)
|
|
39
|
+
except ValueError:
|
|
40
|
+
return None
|