mcal-ai 0.1.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.
@@ -0,0 +1,206 @@
1
+ """
2
+ Vector Index for MCAL Graph Nodes
3
+
4
+ FAISS-based vector similarity search for semantic queries on graph nodes.
5
+
6
+ Performance (from pre-implementation analysis):
7
+ - Build time: <0.2ms for 1000 nodes
8
+ - Search time: <0.04ms per query
9
+ - Memory: ~1.5KB per node (normalized float32)
10
+
11
+ Index Type: IndexFlatIP (Inner Product)
12
+ - With normalized vectors = cosine similarity
13
+ - Exact search (no approximation)
14
+ - Perfect for small-medium graphs (<10K nodes)
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import logging
20
+ from typing import TYPE_CHECKING, Optional
21
+
22
+ import numpy as np
23
+ import faiss
24
+
25
+ if TYPE_CHECKING:
26
+ from .unified_extractor import GraphNode
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ class VectorIndex:
32
+ """
33
+ FAISS-based vector index for graph nodes.
34
+
35
+ Uses IndexFlatIP (inner product) with L2 normalization for cosine similarity.
36
+ Designed for graphs up to ~10K nodes where exact search is fast enough.
37
+
38
+ Usage:
39
+ index = VectorIndex()
40
+ index.add_batch([(node_id, embedding_bytes), ...])
41
+ results = index.search(query_embedding, k=10)
42
+ # results = [(node_id, similarity_score), ...]
43
+ """
44
+
45
+ DIMENSION = 384 # all-MiniLM-L6-v2 output dimension
46
+
47
+ def __init__(self, dimension: int = DIMENSION):
48
+ """
49
+ Initialize empty FAISS index.
50
+
51
+ Args:
52
+ dimension: Embedding dimension (default: 384 for MiniLM)
53
+ """
54
+ self.dimension = dimension
55
+ self.index = faiss.IndexFlatIP(dimension) # Inner product for cosine sim
56
+ self._id_map: list[str] = [] # node_id at each index position
57
+ self._node_count = 0
58
+
59
+ def add(self, node_id: str, embedding: bytes) -> None:
60
+ """
61
+ Add a single node embedding to the index.
62
+
63
+ Args:
64
+ node_id: Unique identifier for the node
65
+ embedding: Float16 binary embedding (from GraphNode.embedding)
66
+ """
67
+ vec = self._bytes_to_normalized_vector(embedding)
68
+ self.index.add(vec.reshape(1, -1))
69
+ self._id_map.append(node_id)
70
+ self._node_count += 1
71
+
72
+ def add_batch(self, nodes: list[tuple[str, bytes]]) -> None:
73
+ """
74
+ Add multiple node embeddings at once (4.7x faster than individual adds).
75
+
76
+ Args:
77
+ nodes: List of (node_id, embedding_bytes) tuples
78
+ """
79
+ if not nodes:
80
+ return
81
+
82
+ ids = []
83
+ vectors = []
84
+
85
+ for node_id, embedding in nodes:
86
+ ids.append(node_id)
87
+ vectors.append(self._bytes_to_normalized_vector(embedding))
88
+
89
+ # Stack and add to FAISS
90
+ vecs = np.vstack(vectors)
91
+ self.index.add(vecs)
92
+ self._id_map.extend(ids)
93
+ self._node_count += len(nodes)
94
+
95
+ def search(
96
+ self,
97
+ query_embedding: bytes,
98
+ k: int = 10
99
+ ) -> list[tuple[str, float]]:
100
+ """
101
+ Search for k most similar nodes.
102
+
103
+ Args:
104
+ query_embedding: Float16 binary embedding for query
105
+ k: Number of results to return
106
+
107
+ Returns:
108
+ List of (node_id, similarity_score) tuples, sorted by score descending
109
+ """
110
+ if self._node_count == 0:
111
+ return []
112
+
113
+ # Ensure we don't request more than we have
114
+ k = min(k, self._node_count)
115
+
116
+ # Convert and normalize query
117
+ query_vec = self._bytes_to_normalized_vector(query_embedding)
118
+
119
+ # Search
120
+ scores, indices = self.index.search(query_vec.reshape(1, -1), k)
121
+
122
+ # Map indices back to node IDs
123
+ results = []
124
+ for score, idx in zip(scores[0], indices[0]):
125
+ if 0 <= idx < len(self._id_map):
126
+ results.append((self._id_map[idx], float(score)))
127
+
128
+ return results
129
+
130
+ def search_text(
131
+ self,
132
+ query_text: str,
133
+ k: int = 10
134
+ ) -> list[tuple[str, float]]:
135
+ """
136
+ Search using text query (embeds the query first).
137
+
138
+ Args:
139
+ query_text: Text to search for
140
+ k: Number of results
141
+
142
+ Returns:
143
+ List of (node_id, similarity_score) tuples
144
+ """
145
+ from .embeddings import EmbeddingService
146
+
147
+ service = EmbeddingService()
148
+ query_embedding = service.embed_text(query_text)
149
+ return self.search(query_embedding, k)
150
+
151
+ def clear(self) -> None:
152
+ """Clear the index and reset state."""
153
+ self.index = faiss.IndexFlatIP(self.dimension)
154
+ self._id_map = []
155
+ self._node_count = 0
156
+
157
+ def _bytes_to_normalized_vector(self, data: bytes) -> np.ndarray:
158
+ """
159
+ Convert Float16 bytes to normalized Float32 vector.
160
+
161
+ FAISS requires float32, and we normalize for cosine similarity.
162
+ """
163
+ # Convert from Float16 bytes to Float32
164
+ vec = np.frombuffer(data, dtype=np.float16).astype(np.float32)
165
+
166
+ # Normalize for cosine similarity (IndexFlatIP + normalized = cosine)
167
+ norm = np.linalg.norm(vec)
168
+ if norm > 0:
169
+ vec = vec / norm
170
+
171
+ return vec
172
+
173
+ def __len__(self) -> int:
174
+ """Return number of vectors in index."""
175
+ return self._node_count
176
+
177
+ def __contains__(self, node_id: str) -> bool:
178
+ """Check if a node ID is in the index."""
179
+ return node_id in self._id_map
180
+
181
+
182
+ def build_index_from_nodes(nodes: list["GraphNode"]) -> VectorIndex:
183
+ """
184
+ Build a VectorIndex from a list of graph nodes.
185
+
186
+ Only includes nodes that have embeddings.
187
+
188
+ Args:
189
+ nodes: List of GraphNode objects
190
+
191
+ Returns:
192
+ VectorIndex populated with node embeddings
193
+ """
194
+ index = VectorIndex()
195
+
196
+ nodes_with_embeddings = [
197
+ (node.id, node.embedding)
198
+ for node in nodes
199
+ if node.embedding is not None
200
+ ]
201
+
202
+ if nodes_with_embeddings:
203
+ index.add_batch(nodes_with_embeddings)
204
+ logger.debug(f"Built index with {len(nodes_with_embeddings)} nodes")
205
+
206
+ return index
@@ -0,0 +1 @@
1
+ """MCAL Evaluation metrics and benchmarks."""
@@ -0,0 +1,88 @@
1
+ """
2
+ MCAL Framework Integrations
3
+
4
+ Provides seamless integration with popular AI agent frameworks.
5
+
6
+ Installation:
7
+ pip install mcal[langgraph] # LangGraph integration
8
+ pip install mcal[crewai] # CrewAI integration
9
+ pip install mcal[autogen] # AutoGen integration
10
+ pip install mcal[integrations] # All integrations
11
+
12
+ Usage:
13
+ # Option 1: Explicit import (recommended)
14
+ from mcal.integrations.langgraph import MCALMemory
15
+
16
+ # Option 2: Namespace access
17
+ from mcal import integrations
18
+ memory = integrations.langgraph.MCALMemory()
19
+
20
+ # Option 3: Direct shortcut
21
+ from mcal import LangGraphMemory
22
+ """
23
+
24
+ from typing import TYPE_CHECKING
25
+
26
+ # Lazy imports to avoid loading dependencies until needed
27
+ if TYPE_CHECKING:
28
+ from mcal.integrations import langgraph
29
+ from mcal.integrations import crewai
30
+ from mcal.integrations import autogen
31
+ from mcal.integrations import langchain
32
+
33
+
34
+ def __getattr__(name: str):
35
+ """Lazy load integrations to avoid import errors when extras not installed."""
36
+ if name == "langgraph":
37
+ try:
38
+ from mcal.integrations import langgraph as _langgraph
39
+ return _langgraph
40
+ except ImportError as e:
41
+ raise ImportError(
42
+ f"LangGraph integration requires extra dependencies.\n"
43
+ f"Install with: pip install mcal[langgraph]\n"
44
+ f"Original error: {e}"
45
+ ) from e
46
+
47
+ elif name == "crewai":
48
+ try:
49
+ from mcal.integrations import crewai as _crewai
50
+ return _crewai
51
+ except ImportError as e:
52
+ raise ImportError(
53
+ f"CrewAI integration requires extra dependencies.\n"
54
+ f"Install with: pip install mcal[crewai]\n"
55
+ f"Original error: {e}"
56
+ ) from e
57
+
58
+ elif name == "autogen":
59
+ try:
60
+ from mcal.integrations import autogen as _autogen
61
+ return _autogen
62
+ except ImportError as e:
63
+ raise ImportError(
64
+ f"AutoGen integration requires extra dependencies.\n"
65
+ f"Install with: pip install mcal[autogen]\n"
66
+ f"Original error: {e}"
67
+ ) from e
68
+
69
+ elif name == "langchain":
70
+ try:
71
+ from mcal.integrations import langchain as _langchain
72
+ return _langchain
73
+ except ImportError as e:
74
+ raise ImportError(
75
+ f"LangChain integration requires extra dependencies.\n"
76
+ f"Install with: pip install mcal[langchain]\n"
77
+ f"Original error: {e}"
78
+ ) from e
79
+
80
+ raise AttributeError(f"module 'mcal.integrations' has no attribute '{name}'")
81
+
82
+
83
+ def __dir__():
84
+ """List available integrations."""
85
+ return ["langgraph", "crewai", "autogen", "langchain"]
86
+
87
+
88
+ __all__ = ["langgraph", "crewai", "autogen", "langchain"]
@@ -0,0 +1,95 @@
1
+ """
2
+ MCAL AutoGen Integration
3
+
4
+ Provides memory components for Microsoft AutoGen agent workflows.
5
+
6
+ Installation:
7
+ pip install mcal[autogen]
8
+
9
+ Usage:
10
+ from mcal.integrations.autogen import MCALMemoryAgent
11
+
12
+ memory_agent = MCALMemoryAgent(llm_provider="anthropic")
13
+ # Use in AutoGen group chat
14
+
15
+ Status: Coming Soon
16
+ """
17
+
18
+ from typing import Any, Dict, List, Optional
19
+
20
+ # Check for AutoGen availability
21
+ try:
22
+ import autogen
23
+ AUTOGEN_AVAILABLE = True
24
+ except ImportError:
25
+ AUTOGEN_AVAILABLE = False
26
+
27
+
28
+ def _check_autogen():
29
+ """Raise helpful error if AutoGen not installed."""
30
+ if not AUTOGEN_AVAILABLE:
31
+ raise ImportError(
32
+ "AutoGen integration requires pyautogen package.\n"
33
+ "Install with: pip install mcal[autogen]"
34
+ )
35
+
36
+
37
+ class MCALMemoryAgent:
38
+ """
39
+ MCAL Memory agent for AutoGen workflows.
40
+
41
+ Provides a specialized agent that manages goal-aware memory
42
+ for AutoGen group chats and workflows.
43
+
44
+ Status: Coming Soon - Basic structure implemented.
45
+
46
+ Usage:
47
+ from mcal.integrations.autogen import MCALMemoryAgent
48
+ from autogen import AssistantAgent, UserProxyAgent
49
+
50
+ memory_agent = MCALMemoryAgent(
51
+ name="memory_manager",
52
+ llm_provider="anthropic",
53
+ )
54
+
55
+ # Add to group chat
56
+ group_chat = GroupChat(
57
+ agents=[user, assistant, memory_agent],
58
+ ...
59
+ )
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ name: str = "memory_manager",
65
+ llm_provider: str = "anthropic",
66
+ embedding_provider: str = "openai",
67
+ storage_path: Optional[str] = None,
68
+ **mcal_kwargs
69
+ ):
70
+ _check_autogen()
71
+
72
+ self.name = name
73
+
74
+ # Import MCAL here to avoid circular imports
75
+ from mcal import MCAL
76
+
77
+ self._mcal = MCAL(
78
+ llm_provider=llm_provider,
79
+ embedding_provider=embedding_provider,
80
+ storage_path=storage_path,
81
+ **mcal_kwargs
82
+ )
83
+
84
+ async def process_message(self, message: str, sender: str) -> Optional[str]:
85
+ """Process a message and update memory."""
86
+ # TODO: Implement AutoGen-specific message processing
87
+ raise NotImplementedError("AutoGen integration coming soon")
88
+
89
+ async def get_context(self, query: str) -> str:
90
+ """Get relevant context for a query."""
91
+ # TODO: Implement AutoGen-specific context retrieval
92
+ raise NotImplementedError("AutoGen integration coming soon")
93
+
94
+
95
+ __all__ = ["MCALMemoryAgent", "AUTOGEN_AVAILABLE"]
@@ -0,0 +1,92 @@
1
+ """
2
+ MCAL CrewAI Integration
3
+
4
+ Provides memory components for CrewAI agent crews.
5
+
6
+ Installation:
7
+ pip install mcal[crewai]
8
+
9
+ Usage:
10
+ from mcal.integrations.crewai import MCALCrewMemory
11
+
12
+ memory = MCALCrewMemory(llm_provider="anthropic")
13
+ crew = Crew(
14
+ agents=[...],
15
+ memory=memory,
16
+ )
17
+
18
+ Status: Coming Soon
19
+ """
20
+
21
+ from typing import Any, Dict, List, Optional
22
+
23
+ # Check for CrewAI availability
24
+ try:
25
+ import crewai
26
+ CREWAI_AVAILABLE = True
27
+ except ImportError:
28
+ CREWAI_AVAILABLE = False
29
+
30
+
31
+ def _check_crewai():
32
+ """Raise helpful error if CrewAI not installed."""
33
+ if not CREWAI_AVAILABLE:
34
+ raise ImportError(
35
+ "CrewAI integration requires crewai package.\n"
36
+ "Install with: pip install mcal[crewai]"
37
+ )
38
+
39
+
40
+ class MCALCrewMemory:
41
+ """
42
+ MCAL Memory component for CrewAI workflows.
43
+
44
+ Provides goal-aware memory that preserves reasoning context
45
+ across agent crew interactions.
46
+
47
+ Status: Coming Soon - Basic structure implemented.
48
+
49
+ Usage:
50
+ from mcal.integrations.crewai import MCALCrewMemory
51
+ from crewai import Crew, Agent
52
+
53
+ memory = MCALCrewMemory(llm_provider="anthropic")
54
+
55
+ crew = Crew(
56
+ agents=[agent1, agent2],
57
+ tasks=[task1, task2],
58
+ memory=memory, # MCAL provides the memory layer
59
+ )
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ llm_provider: str = "anthropic",
65
+ embedding_provider: str = "openai",
66
+ storage_path: Optional[str] = None,
67
+ **mcal_kwargs
68
+ ):
69
+ _check_crewai()
70
+
71
+ # Import MCAL here to avoid circular imports
72
+ from mcal import MCAL
73
+
74
+ self._mcal = MCAL(
75
+ llm_provider=llm_provider,
76
+ embedding_provider=embedding_provider,
77
+ storage_path=storage_path,
78
+ **mcal_kwargs
79
+ )
80
+
81
+ async def save(self, key: str, value: Any, agent_name: str = "default") -> None:
82
+ """Save information to memory."""
83
+ # TODO: Implement CrewAI-specific save
84
+ raise NotImplementedError("CrewAI integration coming soon")
85
+
86
+ async def search(self, query: str, agent_name: str = "default") -> List[Dict[str, Any]]:
87
+ """Search memory for relevant information."""
88
+ # TODO: Implement CrewAI-specific search
89
+ raise NotImplementedError("CrewAI integration coming soon")
90
+
91
+
92
+ __all__ = ["MCALCrewMemory", "CREWAI_AVAILABLE"]
@@ -0,0 +1,112 @@
1
+ """
2
+ MCAL LangChain Integration
3
+
4
+ Provides memory components for LangChain chains and agents.
5
+
6
+ Installation:
7
+ pip install mcal[langchain]
8
+
9
+ Usage:
10
+ from mcal.integrations.langchain import MCALChatMemory
11
+
12
+ memory = MCALChatMemory(llm_provider="anthropic")
13
+ chain = ConversationChain(llm=llm, memory=memory)
14
+
15
+ Status: Coming Soon
16
+ """
17
+
18
+ from typing import Any, Dict, List, Optional
19
+
20
+ # Check for LangChain availability
21
+ try:
22
+ from langchain_core.memory import BaseMemory
23
+ from langchain_core.messages import BaseMessage
24
+ LANGCHAIN_AVAILABLE = True
25
+ except ImportError:
26
+ LANGCHAIN_AVAILABLE = False
27
+ BaseMemory = object # Placeholder for type hints
28
+
29
+
30
+ def _check_langchain():
31
+ """Raise helpful error if LangChain not installed."""
32
+ if not LANGCHAIN_AVAILABLE:
33
+ raise ImportError(
34
+ "LangChain integration requires langchain-core package.\n"
35
+ "Install with: pip install mcal[langchain]"
36
+ )
37
+
38
+
39
+ class MCALChatMemory(BaseMemory if LANGCHAIN_AVAILABLE else object):
40
+ """
41
+ MCAL Memory for LangChain conversation chains.
42
+
43
+ Provides goal-aware memory that preserves reasoning context
44
+ across LangChain conversations.
45
+
46
+ Status: Coming Soon - Basic structure implemented.
47
+
48
+ Usage:
49
+ from mcal.integrations.langchain import MCALChatMemory
50
+ from langchain.chains import ConversationChain
51
+
52
+ memory = MCALChatMemory(llm_provider="anthropic")
53
+
54
+ chain = ConversationChain(
55
+ llm=llm,
56
+ memory=memory,
57
+ )
58
+ """
59
+
60
+ # LangChain memory interface
61
+ memory_key: str = "history"
62
+ return_messages: bool = True
63
+
64
+ def __init__(
65
+ self,
66
+ llm_provider: str = "anthropic",
67
+ embedding_provider: str = "openai",
68
+ storage_path: Optional[str] = None,
69
+ user_id: str = "default",
70
+ **mcal_kwargs
71
+ ):
72
+ _check_langchain()
73
+
74
+ if LANGCHAIN_AVAILABLE:
75
+ super().__init__()
76
+
77
+ self.user_id = user_id
78
+
79
+ # Import MCAL here to avoid circular imports
80
+ from mcal import MCAL
81
+
82
+ self._mcal = MCAL(
83
+ llm_provider=llm_provider,
84
+ embedding_provider=embedding_provider,
85
+ storage_path=storage_path,
86
+ **mcal_kwargs
87
+ )
88
+ self._messages: List[Any] = []
89
+
90
+ @property
91
+ def memory_variables(self) -> List[str]:
92
+ """Memory variables provided to prompts."""
93
+ return [self.memory_key]
94
+
95
+ def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
96
+ """Load memory variables for chain."""
97
+ # TODO: Implement LangChain-specific memory loading
98
+ if self.return_messages:
99
+ return {self.memory_key: self._messages}
100
+ return {self.memory_key: ""}
101
+
102
+ def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, str]) -> None:
103
+ """Save context from chain run."""
104
+ # TODO: Implement LangChain-specific context saving
105
+ pass
106
+
107
+ def clear(self) -> None:
108
+ """Clear memory."""
109
+ self._messages = []
110
+
111
+
112
+ __all__ = ["MCALChatMemory", "LANGCHAIN_AVAILABLE"]
@@ -0,0 +1,50 @@
1
+ """
2
+ MCAL LangGraph Integration - Backward Compatibility Shim
3
+
4
+ DEPRECATED: This module is deprecated. Use mcal-langgraph package instead.
5
+
6
+ Old way (deprecated):
7
+ from mcal.integrations.langgraph import MCALStore
8
+
9
+ New way (recommended):
10
+ pip install mcal-langgraph
11
+ from mcal_langgraph import MCALStore
12
+
13
+ This module re-exports from mcal_langgraph for backward compatibility.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import warnings
19
+
20
+ # Emit deprecation warning on import
21
+ warnings.warn(
22
+ "mcal.integrations.langgraph is deprecated and will be removed in v1.0. "
23
+ "Install mcal-langgraph and use 'from mcal_langgraph import MCALStore' instead.",
24
+ DeprecationWarning,
25
+ stacklevel=2,
26
+ )
27
+
28
+ # Try to import from new package first
29
+ try:
30
+ from mcal_langgraph import (
31
+ MCALStore,
32
+ MCALMemory,
33
+ MCALMemoryConfig,
34
+ MCALCheckpointer,
35
+ LANGGRAPH_AVAILABLE,
36
+ )
37
+ except ImportError:
38
+ # New package not installed - raise helpful error
39
+ raise ImportError(
40
+ "mcal-langgraph package is required for LangGraph integration.\n"
41
+ "Install with: pip install mcal-langgraph"
42
+ )
43
+
44
+ __all__ = [
45
+ "MCALStore",
46
+ "MCALMemory",
47
+ "MCALMemoryConfig",
48
+ "MCALCheckpointer",
49
+ "LANGGRAPH_AVAILABLE",
50
+ ]