membrane-aol 0.2.0__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.
- membrane/__init__.py +209 -0
- membrane/adapters/__init__.py +5 -0
- membrane/adapters/crewai.py +51 -0
- membrane/adapters/haystack.py +51 -0
- membrane/adapters/langchain.py +113 -0
- membrane/adapters/llamaindex.py +90 -0
- membrane/adapters/mcp_resources.py +35 -0
- membrane/adapters/mcp_server.py +451 -0
- membrane/async_preflight.py +118 -0
- membrane/cache/__init__.py +5 -0
- membrane/cache/embedding_cache.py +198 -0
- membrane/cache_format.py +104 -0
- membrane/chunking/__init__.py +21 -0
- membrane/chunking/boundaries.py +90 -0
- membrane/chunking/engine.py +54 -0
- membrane/chunking/strategies.py +229 -0
- membrane/classifier/__init__.py +5 -0
- membrane/classifier/classifier.py +124 -0
- membrane/classifier/features.py +51 -0
- membrane/classifier/weights.json +4 -0
- membrane/cli/__init__.py +1 -0
- membrane/cli/__main__.py +6 -0
- membrane/cli/diagnostics.py +121 -0
- membrane/cli/inspect.py +63 -0
- membrane/cli/main.py +112 -0
- membrane/cli/profile_cmd.py +59 -0
- membrane/cli/quote.py +72 -0
- membrane/compile_wiring.py +136 -0
- membrane/compiler.py +1987 -0
- membrane/config.py +166 -0
- membrane/conversation/__init__.py +5 -0
- membrane/conversation/manager.py +119 -0
- membrane/conversation/summarizer.py +50 -0
- membrane/conversation/window.py +18 -0
- membrane/dedup/__init__.py +5 -0
- membrane/dedup/deduplicator.py +172 -0
- membrane/environment.py +100 -0
- membrane/exceptions.py +28 -0
- membrane/firewall.py +241 -0
- membrane/freshness/__init__.py +16 -0
- membrane/freshness/decay.py +37 -0
- membrane/freshness/manager.py +115 -0
- membrane/glaciator.py +90 -0
- membrane/interrupt.py +46 -0
- membrane/layout.py +107 -0
- membrane/llm_adapters.py +55 -0
- membrane/llm_capabilities.json +38 -0
- membrane/lod.py +132 -0
- membrane/memory_protocol.py +267 -0
- membrane/metrics.py +128 -0
- membrane/observability/__init__.py +16 -0
- membrane/observability/callbacks.py +35 -0
- membrane/observability/collectors.py +60 -0
- membrane/observability/events.py +24 -0
- membrane/observability/profiler.py +61 -0
- membrane/pair_baselines.py +181 -0
- membrane/policies/__init__.py +5 -0
- membrane/policies/engine.py +222 -0
- membrane/profile.py +142 -0
- membrane/provider_registry.py +178 -0
- membrane/ranking_diagnostics.py +133 -0
- membrane/ref_map.py +124 -0
- membrane/runtime/__init__.py +10 -0
- membrane/runtime/signals.py +66 -0
- membrane/schemas/__init__.py +3 -0
- membrane/schemas/kernel_memory_object.schema.json +43 -0
- membrane/serializers.py +246 -0
- membrane/stress.py +72 -0
- membrane/templates/__init__.py +5 -0
- membrane/templates/engine.py +186 -0
- membrane/templates/parser.py +209 -0
- membrane/templates/types.py +60 -0
- membrane/tokenizers/__init__.py +18 -0
- membrane/tokenizers/adapters.py +98 -0
- membrane/tokenizers/registry.py +92 -0
- membrane_aol-0.2.0.dist-info/METADATA +846 -0
- membrane_aol-0.2.0.dist-info/RECORD +81 -0
- membrane_aol-0.2.0.dist-info/WHEEL +4 -0
- membrane_aol-0.2.0.dist-info/entry_points.txt +2 -0
- membrane_aol-0.2.0.dist-info/licenses/LICENSE +202 -0
- membrane_aol-0.2.0.dist-info/licenses/NOTICE +4 -0
membrane/__init__.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""Membrane — prompt budgeting, U-shaped layout, caching, and retrieval hooks."""
|
|
2
|
+
|
|
3
|
+
from membrane.async_preflight import (
|
|
4
|
+
get_preflight_cache,
|
|
5
|
+
get_preflight_cache_key_prefix,
|
|
6
|
+
get_preflight_cache_ttl_default,
|
|
7
|
+
run_async_preflight,
|
|
8
|
+
set_preflight_cache,
|
|
9
|
+
)
|
|
10
|
+
from membrane.cache_format import (
|
|
11
|
+
CacheableBlock,
|
|
12
|
+
anthropic_system_blocks,
|
|
13
|
+
assemble_system_content,
|
|
14
|
+
build_system_messages,
|
|
15
|
+
is_anthropic_model,
|
|
16
|
+
make_cacheable_block,
|
|
17
|
+
)
|
|
18
|
+
from membrane.compiler import (
|
|
19
|
+
PREFLIGHT_ANALYST_SYSTEM,
|
|
20
|
+
INTERRUPT_INSTRUCTION,
|
|
21
|
+
MEMORY_PLACEMENT_END,
|
|
22
|
+
MEMORY_PLACEMENT_SYSTEM,
|
|
23
|
+
CompileQuote,
|
|
24
|
+
CompileResult,
|
|
25
|
+
ContextCompiler,
|
|
26
|
+
SecurityFlag,
|
|
27
|
+
compile_anonymous,
|
|
28
|
+
compile_prompt_async,
|
|
29
|
+
compile_prompt_payload,
|
|
30
|
+
compile_quote_from_result,
|
|
31
|
+
)
|
|
32
|
+
from membrane.environment import (
|
|
33
|
+
EnvironmentSnapshot,
|
|
34
|
+
capture_cpu_load,
|
|
35
|
+
clear_environment_snapshot,
|
|
36
|
+
get_current_environment_state,
|
|
37
|
+
set_environment_snapshot,
|
|
38
|
+
set_environment_snapshot_from_state,
|
|
39
|
+
snapshot_to_stress_inputs,
|
|
40
|
+
)
|
|
41
|
+
from membrane.firewall import (
|
|
42
|
+
TIER_EPISODIC,
|
|
43
|
+
TIER_PROCEDURAL,
|
|
44
|
+
TIER_SEMANTIC,
|
|
45
|
+
PreFlightContextFirewall,
|
|
46
|
+
count_tokens,
|
|
47
|
+
)
|
|
48
|
+
from membrane.glaciator import GlaciateResult, glaciate
|
|
49
|
+
from membrane.interrupt import (
|
|
50
|
+
INTERRUPT_TAG_END,
|
|
51
|
+
INTERRUPT_TAG_START,
|
|
52
|
+
contains_interrupt,
|
|
53
|
+
parse_interrupt,
|
|
54
|
+
strip_all_interrupts,
|
|
55
|
+
)
|
|
56
|
+
from membrane.layout import ContextLayoutEngine
|
|
57
|
+
from membrane.exceptions import (
|
|
58
|
+
FeatureNotAvailable,
|
|
59
|
+
MembraneError,
|
|
60
|
+
MemoryCollisionError,
|
|
61
|
+
)
|
|
62
|
+
from membrane.llm_adapters import (
|
|
63
|
+
CallablePreFlightAnalyst,
|
|
64
|
+
PreFlightAnalyst,
|
|
65
|
+
as_preflight_analyst,
|
|
66
|
+
)
|
|
67
|
+
from membrane.lod import (
|
|
68
|
+
compute_attention_shift,
|
|
69
|
+
compute_attention_shift_with_embeddings,
|
|
70
|
+
cosine_distance,
|
|
71
|
+
embed_texts,
|
|
72
|
+
)
|
|
73
|
+
from membrane.metrics import (
|
|
74
|
+
CompileMetrics,
|
|
75
|
+
LoggingMetricsCallback,
|
|
76
|
+
MetricsCallback,
|
|
77
|
+
NoOpMetricsCallback,
|
|
78
|
+
build_compile_metrics,
|
|
79
|
+
)
|
|
80
|
+
from membrane.memory_protocol import (
|
|
81
|
+
EpisodicMemory,
|
|
82
|
+
KERNEL_MEMORY_SCHEMA_ID,
|
|
83
|
+
KERNEL_MEMORY_SCHEMA_VERSION,
|
|
84
|
+
KernelMemoryObject,
|
|
85
|
+
KernelMemoryRecord,
|
|
86
|
+
MemoryItem,
|
|
87
|
+
ProceduralMemory,
|
|
88
|
+
ProceduralStep,
|
|
89
|
+
SemanticMemory,
|
|
90
|
+
infer_memory_kind,
|
|
91
|
+
kernel_memory_to_memory_item,
|
|
92
|
+
load_kernel_memory_json_schema,
|
|
93
|
+
memory_item_to_kernel_memory,
|
|
94
|
+
normalize_compile_memories,
|
|
95
|
+
)
|
|
96
|
+
from membrane.provider_registry import (
|
|
97
|
+
get_capabilities_for_model,
|
|
98
|
+
get_context_cap_for_model,
|
|
99
|
+
lookup_context_cap,
|
|
100
|
+
get_full_capability_map,
|
|
101
|
+
get_full_context_caps,
|
|
102
|
+
has_prompt_caching,
|
|
103
|
+
has_streaming_interrupt,
|
|
104
|
+
reload_capability_maps,
|
|
105
|
+
)
|
|
106
|
+
from membrane.ranking_diagnostics import RankingWeights, compute_ranking_diagnostics
|
|
107
|
+
from membrane.ref_map import get_ref_map_for_conversation, set_ref_map_for_conversation
|
|
108
|
+
from membrane.serializers import DenseSerializer
|
|
109
|
+
from membrane.stress import (
|
|
110
|
+
compute_stress_from_environment,
|
|
111
|
+
get_recent_stress_for_user,
|
|
112
|
+
set_recent_stress_for_user,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
PreFlightFirewall = PreFlightContextFirewall
|
|
116
|
+
LayoutEngine = ContextLayoutEngine
|
|
117
|
+
|
|
118
|
+
__all__ = [
|
|
119
|
+
"INTERRUPT_INSTRUCTION",
|
|
120
|
+
"INTERRUPT_TAG_END",
|
|
121
|
+
"INTERRUPT_TAG_START",
|
|
122
|
+
"KERNEL_MEMORY_SCHEMA_ID",
|
|
123
|
+
"KERNEL_MEMORY_SCHEMA_VERSION",
|
|
124
|
+
"MEMORY_PLACEMENT_END",
|
|
125
|
+
"MEMORY_PLACEMENT_SYSTEM",
|
|
126
|
+
"PREFLIGHT_ANALYST_SYSTEM",
|
|
127
|
+
"TIER_EPISODIC",
|
|
128
|
+
"TIER_PROCEDURAL",
|
|
129
|
+
"TIER_SEMANTIC",
|
|
130
|
+
"CacheableBlock",
|
|
131
|
+
"CallablePreFlightAnalyst",
|
|
132
|
+
"CompileMetrics",
|
|
133
|
+
"CompileQuote",
|
|
134
|
+
"CompileResult",
|
|
135
|
+
"ContextCompiler",
|
|
136
|
+
"ContextLayoutEngine",
|
|
137
|
+
"DenseSerializer",
|
|
138
|
+
"EnvironmentSnapshot",
|
|
139
|
+
"EpisodicMemory",
|
|
140
|
+
"FeatureNotAvailable",
|
|
141
|
+
"GlaciateResult",
|
|
142
|
+
"KernelMemoryObject",
|
|
143
|
+
"KernelMemoryRecord",
|
|
144
|
+
"LayoutEngine",
|
|
145
|
+
"LoggingMetricsCallback",
|
|
146
|
+
"MembraneError",
|
|
147
|
+
"MemoryCollisionError",
|
|
148
|
+
"MemoryItem",
|
|
149
|
+
"MetricsCallback",
|
|
150
|
+
"NoOpMetricsCallback",
|
|
151
|
+
"PreFlightAnalyst",
|
|
152
|
+
"PreFlightContextFirewall",
|
|
153
|
+
"PreFlightFirewall",
|
|
154
|
+
"ProceduralMemory",
|
|
155
|
+
"ProceduralStep",
|
|
156
|
+
"RankingWeights",
|
|
157
|
+
"SecurityFlag",
|
|
158
|
+
"SemanticMemory",
|
|
159
|
+
"anthropic_system_blocks",
|
|
160
|
+
"as_preflight_analyst",
|
|
161
|
+
"assemble_system_content",
|
|
162
|
+
"build_compile_metrics",
|
|
163
|
+
"build_system_messages",
|
|
164
|
+
"capture_cpu_load",
|
|
165
|
+
"clear_environment_snapshot",
|
|
166
|
+
"compile_anonymous",
|
|
167
|
+
"compile_prompt_async",
|
|
168
|
+
"compile_prompt_payload",
|
|
169
|
+
"compile_quote_from_result",
|
|
170
|
+
"compute_attention_shift",
|
|
171
|
+
"compute_attention_shift_with_embeddings",
|
|
172
|
+
"compute_ranking_diagnostics",
|
|
173
|
+
"compute_stress_from_environment",
|
|
174
|
+
"contains_interrupt",
|
|
175
|
+
"cosine_distance",
|
|
176
|
+
"count_tokens",
|
|
177
|
+
"embed_texts",
|
|
178
|
+
"get_capabilities_for_model",
|
|
179
|
+
"get_context_cap_for_model",
|
|
180
|
+
"get_current_environment_state",
|
|
181
|
+
"get_full_capability_map",
|
|
182
|
+
"get_full_context_caps",
|
|
183
|
+
"get_preflight_cache",
|
|
184
|
+
"get_preflight_cache_key_prefix",
|
|
185
|
+
"get_preflight_cache_ttl_default",
|
|
186
|
+
"get_recent_stress_for_user",
|
|
187
|
+
"get_ref_map_for_conversation",
|
|
188
|
+
"glaciate",
|
|
189
|
+
"has_prompt_caching",
|
|
190
|
+
"has_streaming_interrupt",
|
|
191
|
+
"infer_memory_kind",
|
|
192
|
+
"is_anthropic_model",
|
|
193
|
+
"kernel_memory_to_memory_item",
|
|
194
|
+
"load_kernel_memory_json_schema",
|
|
195
|
+
"lookup_context_cap",
|
|
196
|
+
"make_cacheable_block",
|
|
197
|
+
"memory_item_to_kernel_memory",
|
|
198
|
+
"normalize_compile_memories",
|
|
199
|
+
"parse_interrupt",
|
|
200
|
+
"reload_capability_maps",
|
|
201
|
+
"run_async_preflight",
|
|
202
|
+
"set_environment_snapshot",
|
|
203
|
+
"set_environment_snapshot_from_state",
|
|
204
|
+
"set_preflight_cache",
|
|
205
|
+
"set_recent_stress_for_user",
|
|
206
|
+
"set_ref_map_for_conversation",
|
|
207
|
+
"snapshot_to_stress_inputs",
|
|
208
|
+
"strip_all_interrupts",
|
|
209
|
+
]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""CrewAI: agent-level context hook pattern."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Literal, cast
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
|
|
8
|
+
from membrane.memory_protocol import MemoryItem
|
|
9
|
+
|
|
10
|
+
MemoryType = Literal["semantic", "episodic", "procedural"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _memory_type(value: Any) -> MemoryType:
|
|
14
|
+
text = str(value)
|
|
15
|
+
if text in ("semantic", "episodic", "procedural"):
|
|
16
|
+
return cast(MemoryType, text)
|
|
17
|
+
return "semantic"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def document_to_memory(doc: Any) -> MemoryItem:
|
|
21
|
+
"""Map a generic CrewAI / dict-like document to
|
|
22
|
+
:class:`~membrane.memory_protocol.MemoryItem`.
|
|
23
|
+
"""
|
|
24
|
+
if isinstance(doc, dict):
|
|
25
|
+
return MemoryItem(
|
|
26
|
+
id=str(doc.get("id", "doc")),
|
|
27
|
+
content=str(doc.get("content", "")),
|
|
28
|
+
memory_type=_memory_type(doc.get("memory_type", "semantic")),
|
|
29
|
+
priority=int(doc.get("priority", 2)),
|
|
30
|
+
)
|
|
31
|
+
content = getattr(doc, "content", None) or getattr(doc, "raw", str(doc))
|
|
32
|
+
return MemoryItem(
|
|
33
|
+
id=str(getattr(doc, "id", "doc")),
|
|
34
|
+
content=str(content),
|
|
35
|
+
memory_type="semantic",
|
|
36
|
+
priority=2,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def compile_for_agent(
|
|
41
|
+
compiler: Any,
|
|
42
|
+
*,
|
|
43
|
+
memories: Sequence[Any],
|
|
44
|
+
user_message: str,
|
|
45
|
+
profile: Any = None,
|
|
46
|
+
) -> Any:
|
|
47
|
+
"""Run :meth:`~membrane.compiler.ContextCompiler.compile` for one agent turn."""
|
|
48
|
+
fn = getattr(compiler, "compile", None)
|
|
49
|
+
if fn is None:
|
|
50
|
+
raise TypeError("compiler must have compile()")
|
|
51
|
+
return fn(memories=memories, user_message=user_message, profile=profile)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Haystack: document conversion and prompt assembly hook."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Literal, cast
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
|
|
8
|
+
from membrane.memory_protocol import MemoryItem
|
|
9
|
+
|
|
10
|
+
MemoryType = Literal["semantic", "episodic", "procedural"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _memory_type(value: Any) -> MemoryType:
|
|
14
|
+
text = str(value)
|
|
15
|
+
if text in ("semantic", "episodic", "procedural"):
|
|
16
|
+
return cast(MemoryType, text)
|
|
17
|
+
return "semantic"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def haystack_document_to_memory(doc: Any) -> MemoryItem:
|
|
21
|
+
"""Convert Haystack ``Document`` to :class:`~membrane.memory_protocol.MemoryItem`."""
|
|
22
|
+
try:
|
|
23
|
+
from haystack import Document # type: ignore
|
|
24
|
+
except ImportError as e:
|
|
25
|
+
raise ImportError("membrane-aol[haystack] requires haystack-ai") from e
|
|
26
|
+
if not isinstance(doc, Document):
|
|
27
|
+
raise TypeError("expected haystack.Document")
|
|
28
|
+
meta = doc.meta or {}
|
|
29
|
+
return MemoryItem(
|
|
30
|
+
id=str(meta.get("id", doc.id or "doc")),
|
|
31
|
+
content=doc.content or "",
|
|
32
|
+
memory_type=_memory_type(meta.get("memory_type", "semantic")),
|
|
33
|
+
priority=int(meta.get("priority", 2)),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class MembranePromptBuilder:
|
|
38
|
+
"""Callable that returns Membrane-compiled system text for Haystack pipelines."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, compiler: Any, memories: Sequence[Any], *, profile: Any = None) -> None:
|
|
41
|
+
self._compiler = compiler
|
|
42
|
+
self._memories = memories
|
|
43
|
+
self._profile = profile
|
|
44
|
+
|
|
45
|
+
def __call__(self, query: str) -> str:
|
|
46
|
+
fn = getattr(self._compiler, "compile", None)
|
|
47
|
+
if fn is None:
|
|
48
|
+
raise TypeError("compiler must have compile()")
|
|
49
|
+
res = fn(memories=self._memories, user_message=query, profile=self._profile)
|
|
50
|
+
msgs = getattr(res, "messages", []) or []
|
|
51
|
+
return "\n".join(str(m.get("content", "")) for m in msgs if isinstance(m, dict))
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
LangChain integration.
|
|
3
|
+
|
|
4
|
+
**Collision prevention:** Membrane replaces LangChain's conversation memory when you use
|
|
5
|
+
:class:`MembranePromptTemplate`. Do not combine with ``ConversationBufferMemory`` /
|
|
6
|
+
``ConversationSummaryMemory`` on the same chain — call :func:`disable_langchain_memory` on the
|
|
7
|
+
chain or raise :class:`membrane.exceptions.MemoryCollisionError` if both are detected.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import contextlib
|
|
13
|
+
|
|
14
|
+
from typing import Any, Literal, cast
|
|
15
|
+
from collections.abc import Sequence
|
|
16
|
+
|
|
17
|
+
from membrane.exceptions import MemoryCollisionError
|
|
18
|
+
from membrane.memory_protocol import MemoryItem
|
|
19
|
+
|
|
20
|
+
MemoryType = Literal["semantic", "episodic", "procedural"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _memory_type(value: Any) -> MemoryType:
|
|
24
|
+
text = str(value)
|
|
25
|
+
if text in ("semantic", "episodic", "procedural"):
|
|
26
|
+
return cast(MemoryType, text)
|
|
27
|
+
return "semantic"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _priority(value: Any) -> int:
|
|
31
|
+
try:
|
|
32
|
+
return max(1, min(3, int(value)))
|
|
33
|
+
except (TypeError, ValueError):
|
|
34
|
+
return 2
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def langchain_doc_to_memory(doc: Any) -> MemoryItem:
|
|
38
|
+
"""Convert a LangChain ``Document`` to :class:`~membrane.memory_protocol.MemoryItem`."""
|
|
39
|
+
try:
|
|
40
|
+
from langchain_core.documents import Document # type: ignore
|
|
41
|
+
except ImportError as e:
|
|
42
|
+
raise ImportError("membrane-aol[langchain] requires langchain-core") from e
|
|
43
|
+
if not isinstance(doc, Document):
|
|
44
|
+
raise TypeError("expected langchain_core.documents.Document")
|
|
45
|
+
return MemoryItem(
|
|
46
|
+
id=str(doc.metadata.get("id", doc.metadata.get("source", "doc"))),
|
|
47
|
+
content=doc.page_content,
|
|
48
|
+
memory_type=_memory_type(doc.metadata.get("memory_type", "semantic")),
|
|
49
|
+
priority=_priority(doc.metadata.get("priority", 2)),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class MembranePromptTemplate:
|
|
54
|
+
"""
|
|
55
|
+
Wraps formatting so ``ContextCompiler.compile`` runs before prompt construction.
|
|
56
|
+
|
|
57
|
+
Pass ``compiler`` (:class:`~membrane.compiler.ContextCompiler`), ``memories``, and
|
|
58
|
+
``profile`` / kwargs supported by :meth:`~membrane.compiler.ContextCompiler.compile`.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
compiler: Any,
|
|
64
|
+
template: str,
|
|
65
|
+
*,
|
|
66
|
+
memories: Sequence[Any],
|
|
67
|
+
profile: Any = None,
|
|
68
|
+
**compile_kwargs: Any,
|
|
69
|
+
) -> None:
|
|
70
|
+
self._compiler = compiler
|
|
71
|
+
self._template = template
|
|
72
|
+
self._memories = memories
|
|
73
|
+
self._profile = profile
|
|
74
|
+
self._compile_kwargs = compile_kwargs
|
|
75
|
+
|
|
76
|
+
def format(self, **kwargs: Any) -> str:
|
|
77
|
+
user_message = kwargs.get("user_message") or kwargs.get("input") or ""
|
|
78
|
+
if not user_message:
|
|
79
|
+
raise ValueError("MembranePromptTemplate.format requires user_message or input")
|
|
80
|
+
compile_fn = getattr(self._compiler, "compile", None)
|
|
81
|
+
if compile_fn is None:
|
|
82
|
+
raise TypeError("compiler must have compile()")
|
|
83
|
+
res = compile_fn(
|
|
84
|
+
memories=self._memories,
|
|
85
|
+
user_message=str(user_message),
|
|
86
|
+
profile=self._profile,
|
|
87
|
+
**self._compile_kwargs,
|
|
88
|
+
)
|
|
89
|
+
msgs = getattr(res, "messages", []) or []
|
|
90
|
+
core = "\n".join(str(m.get("content", "")) for m in msgs if isinstance(m, dict))
|
|
91
|
+
fmt_kwargs = dict(kwargs)
|
|
92
|
+
fmt_kwargs["compiled_context"] = core
|
|
93
|
+
return self._template.format(**fmt_kwargs)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def disable_langchain_memory(chain: Any) -> Any:
|
|
97
|
+
"""Remove common LangChain memory components from a chain (best-effort)."""
|
|
98
|
+
mem_attrs = ("memory", "memory_key")
|
|
99
|
+
for a in mem_attrs:
|
|
100
|
+
if hasattr(chain, a):
|
|
101
|
+
with contextlib.suppress(Exception):
|
|
102
|
+
setattr(chain, a, None)
|
|
103
|
+
return chain
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def detect_memory_collision(chain: Any, membrane_active: bool) -> None:
|
|
107
|
+
if not membrane_active:
|
|
108
|
+
return
|
|
109
|
+
if hasattr(chain, "memory") and getattr(chain, "memory", None) is not None:
|
|
110
|
+
raise MemoryCollisionError(
|
|
111
|
+
"LangChain memory is set while Membrane manages context. "
|
|
112
|
+
"Remove ConversationBufferMemory or disable Membrane."
|
|
113
|
+
)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""LlamaIndex: convert retrieved nodes to Membrane memories and post-process."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import contextlib
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from membrane.memory_protocol import KernelMemoryObject
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def node_to_memory(node: Any) -> KernelMemoryObject:
|
|
12
|
+
"""Convert a LlamaIndex ``NodeWithScore`` (or similar) to
|
|
13
|
+
:class:`~membrane.memory_protocol.KernelMemoryObject`.
|
|
14
|
+
|
|
15
|
+
The retrieval score becomes ``relevance_score``; the firewall tier is always 2 (semantic).
|
|
16
|
+
"""
|
|
17
|
+
text = getattr(node, "text", None) or getattr(node, "get_content", lambda: "")()
|
|
18
|
+
if callable(text):
|
|
19
|
+
text = text()
|
|
20
|
+
nid = getattr(node, "id_", None) or getattr(node, "node_id", None) or "node"
|
|
21
|
+
score = getattr(node, "score", None)
|
|
22
|
+
if score is None and hasattr(node, "metadata"):
|
|
23
|
+
score = node.metadata.get("score")
|
|
24
|
+
relevance = 1.0
|
|
25
|
+
if score is not None:
|
|
26
|
+
with contextlib.suppress(TypeError, ValueError):
|
|
27
|
+
relevance = max(0.0, float(score))
|
|
28
|
+
return KernelMemoryObject(
|
|
29
|
+
id=str(nid),
|
|
30
|
+
payload=str(text),
|
|
31
|
+
relevance_score=relevance,
|
|
32
|
+
semantic_tier=2,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
from llama_index.core.bridge.pydantic import PrivateAttr # type: ignore
|
|
38
|
+
from llama_index.core.postprocessor.types import BaseNodePostprocessor # type: ignore
|
|
39
|
+
from llama_index.core.schema import NodeWithScore, TextNode # type: ignore
|
|
40
|
+
|
|
41
|
+
_HAS_LLAMA_INDEX = True
|
|
42
|
+
except ImportError: # pragma: no cover - exercised only without the extra
|
|
43
|
+
_HAS_LLAMA_INDEX = False
|
|
44
|
+
BaseNodePostprocessor = object # type: ignore[assignment,misc]
|
|
45
|
+
|
|
46
|
+
def PrivateAttr(*args: Any, **kwargs: Any) -> Any: # type: ignore[misc]
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class MembraneNodePostprocessor(BaseNodePostprocessor): # type: ignore[misc,valid-type]
|
|
51
|
+
"""
|
|
52
|
+
Postprocessor that runs Membrane's compiler on retrieved nodes.
|
|
53
|
+
|
|
54
|
+
Requires ``llama-index-core`` and a configured :class:`~membrane.compiler.ContextCompiler`.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
_compiler: Any = PrivateAttr()
|
|
58
|
+
_max_tokens: int = PrivateAttr()
|
|
59
|
+
_profile: Any = PrivateAttr()
|
|
60
|
+
|
|
61
|
+
def __init__(self, compiler: Any, *, max_tokens: int = 8192, profile: Any = None) -> None:
|
|
62
|
+
if not _HAS_LLAMA_INDEX:
|
|
63
|
+
raise ImportError("membrane-aol[llamaindex] requires llama-index-core")
|
|
64
|
+
super().__init__()
|
|
65
|
+
self._compiler = compiler
|
|
66
|
+
self._max_tokens = max_tokens
|
|
67
|
+
self._profile = profile
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def class_name(cls) -> str:
|
|
71
|
+
return "MembraneNodePostprocessor"
|
|
72
|
+
|
|
73
|
+
def _postprocess_nodes(self, nodes: list[Any], query_bundle: Any = None) -> list[Any]:
|
|
74
|
+
memories = [node_to_memory(n) for n in nodes]
|
|
75
|
+
q = ""
|
|
76
|
+
if query_bundle is not None:
|
|
77
|
+
q = str(getattr(query_bundle, "query_str", None) or getattr(query_bundle, "query", ""))
|
|
78
|
+
compile_fn = getattr(self._compiler, "compile", None)
|
|
79
|
+
if compile_fn is None:
|
|
80
|
+
return nodes
|
|
81
|
+
res = compile_fn(
|
|
82
|
+
memories=memories,
|
|
83
|
+
user_message=q or "context",
|
|
84
|
+
profile=self._profile,
|
|
85
|
+
)
|
|
86
|
+
# Return a single synthetic node carrying compiled text (caller interprets)
|
|
87
|
+
text = "\n".join(
|
|
88
|
+
str(m.get("content", "")) for m in getattr(res, "messages", []) or []
|
|
89
|
+
)
|
|
90
|
+
return [NodeWithScore(node=TextNode(text=text, metadata={"membrane": True}))]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""MCP resource payloads for profiles, last compile, and metrics (session state)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from membrane.profile import PRESET_NAMES
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_PROFILE_NAMES = PRESET_NAMES
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def list_profile_resource_text() -> str:
|
|
15
|
+
return json.dumps({"presets": list(_PROFILE_NAMES)}, indent=2)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def last_compile_resource_text(last: Any | None) -> str:
|
|
19
|
+
if last is None:
|
|
20
|
+
return json.dumps({"error": "no_compilation_yet"})
|
|
21
|
+
if hasattr(last, "to_debug_dict"):
|
|
22
|
+
return json.dumps(last.to_debug_dict(), default=str, indent=2)
|
|
23
|
+
return json.dumps({"raw": str(last)})
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def metrics_resource_text(snapshot: dict[str, Any] | None) -> str:
|
|
27
|
+
return json.dumps(snapshot or {"session": "membrane-mcp", "metrics": "none"}, indent=2)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def resource_uris() -> list[str]:
|
|
31
|
+
return [
|
|
32
|
+
"membrane://profiles",
|
|
33
|
+
"membrane://last_compile",
|
|
34
|
+
"membrane://metrics",
|
|
35
|
+
]
|