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.
- mcal/__init__.py +165 -0
- mcal/backends/__init__.py +42 -0
- mcal/backends/base.py +383 -0
- mcal/baselines/__init__.py +1 -0
- mcal/core/__init__.py +101 -0
- mcal/core/embeddings.py +266 -0
- mcal/core/extraction_cache.py +398 -0
- mcal/core/goal_retriever.py +539 -0
- mcal/core/intent_tracker.py +734 -0
- mcal/core/models.py +445 -0
- mcal/core/rate_limiter.py +372 -0
- mcal/core/reasoning_store.py +1061 -0
- mcal/core/retry.py +188 -0
- mcal/core/storage.py +456 -0
- mcal/core/streaming.py +254 -0
- mcal/core/unified_extractor.py +1466 -0
- mcal/core/vector_index.py +206 -0
- mcal/evaluation/__init__.py +1 -0
- mcal/integrations/__init__.py +88 -0
- mcal/integrations/autogen.py +95 -0
- mcal/integrations/crewai.py +92 -0
- mcal/integrations/langchain.py +112 -0
- mcal/integrations/langgraph.py +50 -0
- mcal/mcal.py +1697 -0
- mcal/providers/bedrock.py +217 -0
- mcal/storage/__init__.py +1 -0
- mcal_ai-0.1.0.dist-info/METADATA +319 -0
- mcal_ai-0.1.0.dist-info/RECORD +32 -0
- mcal_ai-0.1.0.dist-info/WHEEL +5 -0
- mcal_ai-0.1.0.dist-info/entry_points.txt +2 -0
- mcal_ai-0.1.0.dist-info/licenses/LICENSE +21 -0
- mcal_ai-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS Bedrock LLM Provider for MCAL.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import asyncio
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Dict, List, Any, Optional
|
|
9
|
+
import boto3
|
|
10
|
+
from botocore.exceptions import ClientError
|
|
11
|
+
|
|
12
|
+
from ..core.retry import (
|
|
13
|
+
llm_retry,
|
|
14
|
+
classify_boto_error,
|
|
15
|
+
LLMThrottlingError,
|
|
16
|
+
LLMServerError,
|
|
17
|
+
LLMTimeoutError,
|
|
18
|
+
)
|
|
19
|
+
from ..core.rate_limiter import (
|
|
20
|
+
TokenBucketRateLimiter,
|
|
21
|
+
RateLimitConfig,
|
|
22
|
+
create_rate_limiter,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BedrockProvider:
|
|
29
|
+
"""AWS Bedrock LLM provider for MCAL."""
|
|
30
|
+
|
|
31
|
+
MODELS = {
|
|
32
|
+
# Fast models (simple tasks: intent detection, graph updates)
|
|
33
|
+
# Use cross-region inference profile IDs for on-demand throughput
|
|
34
|
+
"llama-3.1-8b": {"id": "us.meta.llama3-1-8b-instruct-v1:0", "max_tokens": 4096, "type": "llama", "tier": "fast"},
|
|
35
|
+
"llama-3.2-3b": {"id": "us.meta.llama3-2-3b-instruct-v1:0", "max_tokens": 4096, "type": "llama", "tier": "fast"},
|
|
36
|
+
# Smart models (complex tasks: decision extraction, reasoning)
|
|
37
|
+
"llama-3.3-70b": {"id": "us.meta.llama3-3-70b-instruct-v1:0", "max_tokens": 4096, "type": "llama", "tier": "smart"},
|
|
38
|
+
"llama-4-maverick": {"id": "us.meta.llama4-maverick-17b-instruct-v1:0", "max_tokens": 4096, "type": "llama", "tier": "smart"},
|
|
39
|
+
"llama-3.1-70b": {"id": "us.meta.llama3-1-70b-instruct-v1:0", "max_tokens": 4096, "type": "llama", "tier": "smart"},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Default models for tiered selection
|
|
43
|
+
DEFAULT_FAST_MODEL = "llama-3.1-8b"
|
|
44
|
+
DEFAULT_SMART_MODEL = "llama-3.3-70b"
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
model: str = "llama-3.3-70b",
|
|
49
|
+
region: str = "us-east-1",
|
|
50
|
+
temperature: float = 0.7,
|
|
51
|
+
max_tokens: int = 2048,
|
|
52
|
+
# Rate limiting options (Issue #39)
|
|
53
|
+
enable_rate_limiting: bool = False,
|
|
54
|
+
rate_limit_rpm: int = 60,
|
|
55
|
+
rate_limit_tpm: int = 100_000,
|
|
56
|
+
max_cost_per_hour: Optional[float] = None,
|
|
57
|
+
enable_cost_logging: bool = True,
|
|
58
|
+
):
|
|
59
|
+
"""
|
|
60
|
+
Initialize Bedrock provider.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
model: Model name (e.g., "llama-3.3-70b")
|
|
64
|
+
region: AWS region
|
|
65
|
+
temperature: Sampling temperature
|
|
66
|
+
max_tokens: Maximum tokens per response
|
|
67
|
+
enable_rate_limiting: Enable rate limiting (default: False)
|
|
68
|
+
rate_limit_rpm: Requests per minute limit (default: 60)
|
|
69
|
+
rate_limit_tpm: Tokens per minute limit (default: 100,000)
|
|
70
|
+
max_cost_per_hour: Maximum cost per hour in USD (None = unlimited)
|
|
71
|
+
enable_cost_logging: Log cost estimates for each call
|
|
72
|
+
"""
|
|
73
|
+
if model not in self.MODELS:
|
|
74
|
+
raise ValueError(f"Unknown model: {model}")
|
|
75
|
+
self.model_name = model
|
|
76
|
+
self.model_config = self.MODELS[model]
|
|
77
|
+
self.model_id = self.model_config["id"]
|
|
78
|
+
self.temperature = temperature
|
|
79
|
+
self.max_tokens = min(max_tokens, self.model_config["max_tokens"])
|
|
80
|
+
self.bedrock = boto3.client('bedrock-runtime', region_name=region)
|
|
81
|
+
|
|
82
|
+
# Token tracking
|
|
83
|
+
self.total_prompt_tokens = 0
|
|
84
|
+
self.total_completion_tokens = 0
|
|
85
|
+
|
|
86
|
+
# Rate limiting (Issue #39)
|
|
87
|
+
self._rate_limiter: Optional[TokenBucketRateLimiter] = None
|
|
88
|
+
if enable_rate_limiting:
|
|
89
|
+
self._rate_limiter = create_rate_limiter(
|
|
90
|
+
model=model,
|
|
91
|
+
provider="bedrock",
|
|
92
|
+
rpm=rate_limit_rpm,
|
|
93
|
+
tpm=rate_limit_tpm,
|
|
94
|
+
max_cost_per_hour=max_cost_per_hour,
|
|
95
|
+
enable_cost_logging=enable_cost_logging,
|
|
96
|
+
)
|
|
97
|
+
logger.info(
|
|
98
|
+
f"Rate limiting enabled for {model}: "
|
|
99
|
+
f"{rate_limit_rpm} RPM, {rate_limit_tpm} TPM"
|
|
100
|
+
+ (f", max ${max_cost_per_hour}/hr" if max_cost_per_hour else "")
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def get_token_usage(self) -> dict:
|
|
104
|
+
"""Get total token usage."""
|
|
105
|
+
return {
|
|
106
|
+
"prompt_tokens": self.total_prompt_tokens,
|
|
107
|
+
"completion_tokens": self.total_completion_tokens,
|
|
108
|
+
"total_tokens": self.total_prompt_tokens + self.total_completion_tokens
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
def reset_token_usage(self):
|
|
112
|
+
"""Reset token counters."""
|
|
113
|
+
self.total_prompt_tokens = 0
|
|
114
|
+
self.total_completion_tokens = 0
|
|
115
|
+
|
|
116
|
+
@llm_retry(max_attempts=3, min_wait=1.0, max_wait=10.0)
|
|
117
|
+
async def generate(self, messages: List[Dict[str, str]], **kwargs) -> str:
|
|
118
|
+
"""
|
|
119
|
+
Generate a response from Bedrock with automatic retry on transient failures.
|
|
120
|
+
|
|
121
|
+
Retries on:
|
|
122
|
+
- ThrottlingException (rate limits)
|
|
123
|
+
- ServiceUnavailableException (5xx errors)
|
|
124
|
+
- Connection timeouts
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
messages: List of message dicts with 'role' and 'content'
|
|
128
|
+
**kwargs: Additional parameters (max_tokens, temperature)
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Generated text response
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
LLMThrottlingError: After max retries on throttling
|
|
135
|
+
LLMServerError: After max retries on server errors
|
|
136
|
+
PermissionError: On access denied (not retried)
|
|
137
|
+
RuntimeError: On other non-retryable errors or cost limit exceeded
|
|
138
|
+
"""
|
|
139
|
+
# Rate limiting (Issue #39)
|
|
140
|
+
if self._rate_limiter:
|
|
141
|
+
# Estimate tokens based on message content
|
|
142
|
+
total_chars = sum(len(m.get("content", "")) for m in messages)
|
|
143
|
+
estimated_tokens = max(total_chars // 4, 500) # Rough estimate
|
|
144
|
+
await self._rate_limiter.acquire(estimated_tokens=estimated_tokens)
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
# Convert message format for Converse API (content must be a list)
|
|
148
|
+
formatted_messages = []
|
|
149
|
+
for msg in messages:
|
|
150
|
+
formatted_messages.append({
|
|
151
|
+
"role": msg["role"],
|
|
152
|
+
"content": [{"text": msg["content"]}]
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
response = await asyncio.get_event_loop().run_in_executor(
|
|
156
|
+
None,
|
|
157
|
+
lambda: self.bedrock.converse(
|
|
158
|
+
modelId=self.model_id,
|
|
159
|
+
messages=formatted_messages,
|
|
160
|
+
inferenceConfig={
|
|
161
|
+
"maxTokens": kwargs.get("max_tokens", self.max_tokens),
|
|
162
|
+
"temperature": kwargs.get("temperature", self.temperature),
|
|
163
|
+
"topP": 0.9,
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Track token usage from Bedrock response
|
|
169
|
+
input_tokens = 0
|
|
170
|
+
output_tokens = 0
|
|
171
|
+
if 'usage' in response:
|
|
172
|
+
input_tokens = response['usage'].get('inputTokens', 0)
|
|
173
|
+
output_tokens = response['usage'].get('outputTokens', 0)
|
|
174
|
+
self.total_prompt_tokens += input_tokens
|
|
175
|
+
self.total_completion_tokens += output_tokens
|
|
176
|
+
|
|
177
|
+
# Record usage for rate limiting (Issue #39)
|
|
178
|
+
if self._rate_limiter:
|
|
179
|
+
self._rate_limiter.record_usage(input_tokens, output_tokens)
|
|
180
|
+
|
|
181
|
+
return response['output']['message']['content'][0]['text']
|
|
182
|
+
except ClientError as e:
|
|
183
|
+
error_code = e.response['Error']['Code']
|
|
184
|
+
error_msg = e.response['Error']['Message']
|
|
185
|
+
|
|
186
|
+
# Non-retryable: access denied
|
|
187
|
+
if error_code == "AccessDeniedException":
|
|
188
|
+
raise PermissionError(f"Bedrock access denied: {error_msg}")
|
|
189
|
+
|
|
190
|
+
# Classify and potentially retry
|
|
191
|
+
classified_error = classify_boto_error(error_code, error_msg)
|
|
192
|
+
logger.warning(f"Bedrock error ({error_code}): {error_msg} - {'retrying' if isinstance(classified_error, (LLMThrottlingError, LLMServerError)) else 'not retrying'}")
|
|
193
|
+
raise classified_error
|
|
194
|
+
|
|
195
|
+
def get_rate_limit_stats(self) -> Optional[Dict[str, Any]]:
|
|
196
|
+
"""Get rate limiter statistics (if enabled)."""
|
|
197
|
+
if self._rate_limiter:
|
|
198
|
+
return self._rate_limiter.get_stats()
|
|
199
|
+
return None
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
async def test_bedrock_provider():
|
|
203
|
+
print("Testing Bedrock Provider...")
|
|
204
|
+
print("=" * 70)
|
|
205
|
+
for model in ["llama-3.3-70b", "llama-4-maverick"]:
|
|
206
|
+
try:
|
|
207
|
+
print(f"\nTesting {model}...")
|
|
208
|
+
provider = BedrockProvider(model=model, max_tokens=100)
|
|
209
|
+
messages = [{"role": "user", "content": "What is 2+2?"}]
|
|
210
|
+
response = await provider.generate(messages)
|
|
211
|
+
print(f"✅ Response: {response[:100]}")
|
|
212
|
+
except Exception as e:
|
|
213
|
+
print(f"❌ Error: {str(e)[:150]}")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
if __name__ == "__main__":
|
|
217
|
+
asyncio.run(test_bedrock_provider())
|
mcal/storage/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""MCAL Storage backends."""
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcal-ai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Memory-Context Alignment Layer for Goal-Coherent AI Agents
|
|
5
|
+
Author: MCAL Team
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Shivakoreddi/mcal-ai
|
|
8
|
+
Project-URL: Documentation, https://github.com/Shivakoreddi/mcal-ai#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/Shivakoreddi/mcal-ai.git
|
|
10
|
+
Project-URL: Issues, https://github.com/Shivakoreddi/mcal-ai/issues
|
|
11
|
+
Keywords: llm,memory,agents,context,ai,nlp
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: anthropic>=0.18.0
|
|
24
|
+
Requires-Dist: openai>=1.0.0
|
|
25
|
+
Requires-Dist: boto3>=1.28.0
|
|
26
|
+
Requires-Dist: pydantic>=2.0.0
|
|
27
|
+
Requires-Dist: numpy>=1.24.0
|
|
28
|
+
Requires-Dist: faiss-cpu>=1.7.4
|
|
29
|
+
Requires-Dist: sentence-transformers>=2.2.0
|
|
30
|
+
Requires-Dist: sqlalchemy>=2.0.0
|
|
31
|
+
Requires-Dist: aiosqlite>=0.19.0
|
|
32
|
+
Requires-Dist: tiktoken>=0.5.0
|
|
33
|
+
Requires-Dist: tenacity>=8.2.0
|
|
34
|
+
Requires-Dist: rich>=13.0.0
|
|
35
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
36
|
+
Provides-Extra: langgraph
|
|
37
|
+
Requires-Dist: langgraph>=0.0.40; extra == "langgraph"
|
|
38
|
+
Requires-Dist: langchain-core>=0.1.0; extra == "langgraph"
|
|
39
|
+
Provides-Extra: crewai
|
|
40
|
+
Requires-Dist: crewai>=0.28.0; extra == "crewai"
|
|
41
|
+
Provides-Extra: autogen
|
|
42
|
+
Requires-Dist: pyautogen>=0.2.0; extra == "autogen"
|
|
43
|
+
Provides-Extra: langchain
|
|
44
|
+
Requires-Dist: langchain>=0.1.0; extra == "langchain"
|
|
45
|
+
Requires-Dist: langchain-core>=0.1.0; extra == "langchain"
|
|
46
|
+
Provides-Extra: integrations
|
|
47
|
+
Requires-Dist: langgraph>=0.0.40; extra == "integrations"
|
|
48
|
+
Requires-Dist: langchain-core>=0.1.0; extra == "integrations"
|
|
49
|
+
Requires-Dist: crewai>=0.28.0; extra == "integrations"
|
|
50
|
+
Requires-Dist: pyautogen>=0.2.0; extra == "integrations"
|
|
51
|
+
Provides-Extra: mem0
|
|
52
|
+
Requires-Dist: mem0ai>=0.1.0; extra == "mem0"
|
|
53
|
+
Provides-Extra: dev
|
|
54
|
+
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
55
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
56
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
57
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
58
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
59
|
+
Requires-Dist: mypy>=1.5.0; extra == "dev"
|
|
60
|
+
Requires-Dist: pre-commit>=3.4.0; extra == "dev"
|
|
61
|
+
Requires-Dist: ipykernel>=6.25.0; extra == "dev"
|
|
62
|
+
Requires-Dist: jupyter>=1.0.0; extra == "dev"
|
|
63
|
+
Provides-Extra: eval
|
|
64
|
+
Requires-Dist: pandas>=2.0.0; extra == "eval"
|
|
65
|
+
Requires-Dist: matplotlib>=3.7.0; extra == "eval"
|
|
66
|
+
Requires-Dist: seaborn>=0.12.0; extra == "eval"
|
|
67
|
+
Requires-Dist: wandb>=0.15.0; extra == "eval"
|
|
68
|
+
Requires-Dist: scipy>=1.11.0; extra == "eval"
|
|
69
|
+
Provides-Extra: all
|
|
70
|
+
Requires-Dist: mcal[dev,eval,integrations]; extra == "all"
|
|
71
|
+
Dynamic: license-file
|
|
72
|
+
|
|
73
|
+
# MCAL: Memory-Context Alignment Layer
|
|
74
|
+
|
|
75
|
+
> **Beyond Retrieval:** Intent-Preserving Memory for Goal-Coherent AI Agents
|
|
76
|
+
|
|
77
|
+
[](https://www.python.org/downloads/)
|
|
78
|
+
[](https://opensource.org/licenses/MIT)
|
|
79
|
+
[](https://arxiv.org/)
|
|
80
|
+
[](docs/MCAL_DESIGN.md)
|
|
81
|
+
|
|
82
|
+
## What's New in v2.0
|
|
83
|
+
|
|
84
|
+
MCAL is now **fully standalone** - no external dependencies on Mem0 or other memory providers. The architecture includes:
|
|
85
|
+
|
|
86
|
+
- **Built-in Embedding Service** - OpenAI or Bedrock embeddings
|
|
87
|
+
- **Native Vector Search** - Cosine similarity with HNSW-like indexing
|
|
88
|
+
- **Graph Deduplication** - Automatic node merging with similarity detection
|
|
89
|
+
- **JSON Persistence** - Zero-config file-based storage
|
|
90
|
+
|
|
91
|
+
## The Problem
|
|
92
|
+
|
|
93
|
+
Current AI agent memory systems store **facts** but lose **meaning**:
|
|
94
|
+
|
|
95
|
+
| What's Stored | What's Lost |
|
|
96
|
+
|--------------------------------------|--------------------------------------------------|
|
|
97
|
+
| "User wants to visit Japan" | **WHY** they chose Japan over other destinations |
|
|
98
|
+
| "User booked a hotel in Shibuya" | **WHAT** alternatives were considered (Shinjuku, Ginza, Asakusa) |
|
|
99
|
+
| "User plans to visit Kyoto" | **HOW** this fits into the overall trip plan |
|
|
100
|
+
|
|
101
|
+
This creates the **Memory-Context Alignment Paradox**: as conversations grow, agents remember *what* was said but forget *why* it mattered.
|
|
102
|
+
|
|
103
|
+
## Our Solution: Three Pillars
|
|
104
|
+
|
|
105
|
+
### 1. Intent Graph Preservation
|
|
106
|
+
Hierarchical goal structures that persist across sessions:
|
|
107
|
+
```
|
|
108
|
+
MISSION: Plan a 2-week vacation to Japan
|
|
109
|
+
├── GOAL: Book travel [✓ COMPLETED]
|
|
110
|
+
│ ├── TASK: Find flights [✓]
|
|
111
|
+
│ └── TASK: Reserve hotels [✓]
|
|
112
|
+
├── GOAL: Plan activities [ACTIVE]
|
|
113
|
+
│ ├── TASK: Research Tokyo attractions [✓]
|
|
114
|
+
│ ├── TASK: Plan Kyoto day trips [IN PROGRESS]
|
|
115
|
+
│ └── TASK: Book restaurants [PENDING]
|
|
116
|
+
└── GOAL: Pack and prepare [PENDING]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 2. Reasoning Chain Storage
|
|
120
|
+
Preserve **WHY** decisions were made, not just conclusions:
|
|
121
|
+
```
|
|
122
|
+
Decision: "Stay in Shibuya for Tokyo accommodation"
|
|
123
|
+
├── Alternatives: [Shinjuku, Ginza, Asakusa]
|
|
124
|
+
├── Rationale: "Central location, good nightlife, easy metro access"
|
|
125
|
+
├── Evidence: ["User wants to explore at night", "Prefers walkable areas"]
|
|
126
|
+
└── Trade-offs: ["More expensive but saves daily transit time"]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 3. Goal-Aware Retrieval
|
|
130
|
+
Retrieve based on **objective achievement**, not just similarity:
|
|
131
|
+
```
|
|
132
|
+
Score = α × semantic_similarity
|
|
133
|
+
+ β × goal_alignment ← NEW
|
|
134
|
+
+ γ × decision_relevance ← NEW
|
|
135
|
+
+ δ × recency_decay
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Installation
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Install from source (recommended)
|
|
142
|
+
git clone https://github.com/Shivakoreddi/mcla-research.git
|
|
143
|
+
cd mcla-research
|
|
144
|
+
pip install -e .
|
|
145
|
+
|
|
146
|
+
# Development installation with test dependencies
|
|
147
|
+
pip install -e ".[dev]"
|
|
148
|
+
|
|
149
|
+
# Optional: Install with legacy Mem0 support
|
|
150
|
+
pip install -e ".[mem0]"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Requirements
|
|
154
|
+
- Python 3.11+
|
|
155
|
+
- `anthropic` - For LLM extraction (Claude)
|
|
156
|
+
- `openai` - For embeddings (optional, can use Bedrock instead)
|
|
157
|
+
|
|
158
|
+
## Quick Start
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
import asyncio
|
|
162
|
+
from mcal import MCAL
|
|
163
|
+
|
|
164
|
+
async def main():
|
|
165
|
+
# Initialize MCAL (standalone by default)
|
|
166
|
+
mcal = MCAL(
|
|
167
|
+
llm_provider="anthropic", # or "bedrock", "openai"
|
|
168
|
+
embedding_provider="openai", # or "bedrock"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Add conversation messages
|
|
172
|
+
messages = [
|
|
173
|
+
{"role": "user", "content": "I'm building a fraud detection ML pipeline"},
|
|
174
|
+
{"role": "assistant", "content": "Great! Let's start with data ingestion..."},
|
|
175
|
+
{"role": "user", "content": "I chose PostgreSQL over MongoDB for the data store"},
|
|
176
|
+
{"role": "assistant", "content": "PostgreSQL is a solid choice for structured fraud data..."}
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
result = await mcal.add(messages, user_id="user_123")
|
|
180
|
+
|
|
181
|
+
# Access the unified graph with goals, decisions, and reasoning
|
|
182
|
+
print(f"Extracted {result.unified_graph.node_count} nodes")
|
|
183
|
+
print(f"Active goals: {result.unified_graph.get_active_goals()}")
|
|
184
|
+
print(f"Decisions: {result.unified_graph.get_all_decisions_with_detail()}")
|
|
185
|
+
|
|
186
|
+
# Search for relevant context
|
|
187
|
+
search_results = await mcal.search(
|
|
188
|
+
query="What database did the user choose?",
|
|
189
|
+
user_id="user_123"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Get formatted context for LLM
|
|
193
|
+
context = mcal.get_context(
|
|
194
|
+
query="What should we focus on next?",
|
|
195
|
+
user_id="user_123",
|
|
196
|
+
max_tokens=4000
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
asyncio.run(main())
|
|
200
|
+
|
|
201
|
+
## Architecture
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
205
|
+
│ MCAL v2.0 │
|
|
206
|
+
│ ┌───────────────────────────────────────────────────────────┐ │
|
|
207
|
+
│ │ Application Layer │ │
|
|
208
|
+
│ │ mcal.add() │ mcal.search() │ mcal.get_context() │ │
|
|
209
|
+
│ └───────────────────────────────────────────────────────────┘ │
|
|
210
|
+
│ │ │
|
|
211
|
+
│ ┌───────────────────────────────────────────────────────────┐ │
|
|
212
|
+
│ │ Unified Deep Extractor │ │
|
|
213
|
+
│ │ Single LLM call extracts: GOALS | DECISIONS | FACTS │ │
|
|
214
|
+
│ └───────────────────────────────────────────────────────────┘ │
|
|
215
|
+
│ │ │
|
|
216
|
+
│ ┌───────────────────────────────────────────────────────────┐ │
|
|
217
|
+
│ │ Unified Graph (6 Nodes, 13 Edges) │ │
|
|
218
|
+
│ │ PERSON | THING | CONCEPT | GOAL | DECISION | ACTION │ │
|
|
219
|
+
│ └───────────────────────────────────────────────────────────┘ │
|
|
220
|
+
│ │ │
|
|
221
|
+
│ ┌───────────────────────────────────────────────────────────┐ │
|
|
222
|
+
│ │ Standalone Services │ │
|
|
223
|
+
│ │ ┌──────────────┐ ┌─────────────┐ ┌─────────────────┐ │ │
|
|
224
|
+
│ │ │ Embeddings │ │Vector Search│ │ Deduplication │ │ │
|
|
225
|
+
│ │ │ (OpenAI/ │ │(Cosine Sim) │ │ (Similarity │ │ │
|
|
226
|
+
│ │ │ Bedrock) │ │ │ │ Merging) │ │ │
|
|
227
|
+
│ │ └──────────────┘ └─────────────┘ └─────────────────┘ │ │
|
|
228
|
+
│ └───────────────────────────────────────────────────────────┘ │
|
|
229
|
+
│ │ │
|
|
230
|
+
│ ┌───────────────────────────────────────────────────────────┐ │
|
|
231
|
+
│ │ JSON File Persistence │ │
|
|
232
|
+
│ │ ~/.mcal/users/{user_id}/graph.json │ │
|
|
233
|
+
│ └───────────────────────────────────────────────────────────┘ │
|
|
234
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Project Structure
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
mcla-research/
|
|
241
|
+
├── src/mcal/
|
|
242
|
+
│ ├── mcal.py # Main MCAL class
|
|
243
|
+
│ ├── core/
|
|
244
|
+
│ │ ├── unified_extractor.py # Single-pass extraction
|
|
245
|
+
│ │ ├── unified_graph.py # Graph with rich attributes
|
|
246
|
+
│ │ ├── embedding_service.py # Embedding generation
|
|
247
|
+
│ │ ├── vector_index.py # Similarity search
|
|
248
|
+
│ │ └── deduplication.py # Node merging
|
|
249
|
+
│ ├── providers/
|
|
250
|
+
│ │ └── llm_providers.py # Anthropic, OpenAI, Bedrock
|
|
251
|
+
│ └── storage/
|
|
252
|
+
│ └── sqlite_store.py # Persistence layer
|
|
253
|
+
├── experiments/
|
|
254
|
+
├── data/
|
|
255
|
+
│ ├── synthetic/ # Generated conversations
|
|
256
|
+
│ └── benchmarks/ # MCAL-Bench dataset
|
|
257
|
+
├── tests/
|
|
258
|
+
└── docs/
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Evaluation: MCAL-Bench
|
|
262
|
+
|
|
263
|
+
We introduce **MCAL-Bench**, the first benchmark for reasoning preservation and goal coherence:
|
|
264
|
+
|
|
265
|
+
| Metric | What It Measures |
|
|
266
|
+
|--------|------------------|
|
|
267
|
+
| **RPS** (Reasoning Preservation Score) | Can the system explain WHY a decision was made? |
|
|
268
|
+
| **GCS** (Goal Coherence Score) | Do responses align with user's active objectives? |
|
|
269
|
+
| **TER** (Token Efficiency Ratio) | Quality-per-token vs full context baseline |
|
|
270
|
+
|
|
271
|
+
## Results (Preliminary)
|
|
272
|
+
|
|
273
|
+
| System | RPS | GCS | TER |
|
|
274
|
+
|--------|-----|-----|-----|
|
|
275
|
+
| Full Context | 0.85 | 0.82 | 1.0x |
|
|
276
|
+
| Summarization | 0.45 | 0.58 | 2.1x |
|
|
277
|
+
| Mem0 | 0.52 | 0.61 | 3.2x |
|
|
278
|
+
| **MCAL (Ours)** | **0.78** | **0.79** | **3.8x** |
|
|
279
|
+
|
|
280
|
+
## Roadmap
|
|
281
|
+
|
|
282
|
+
- [x] Problem formulation & research
|
|
283
|
+
- [ ] Week 1: Foundation (baseline + data)
|
|
284
|
+
- [ ] Week 2: Core algorithms
|
|
285
|
+
- [ ] Week 3: Benchmark & evaluation
|
|
286
|
+
- [ ] Week 4: Paper draft
|
|
287
|
+
- [ ] Week 5: Release & arXiv
|
|
288
|
+
|
|
289
|
+
## Citation
|
|
290
|
+
|
|
291
|
+
```bibtex
|
|
292
|
+
@article{mcal2026,
|
|
293
|
+
title={MCAL: Memory-Context Alignment for Goal-Coherent AI Agents},
|
|
294
|
+
author={Koreddi, Shiva},
|
|
295
|
+
journal={arXiv preprint},
|
|
296
|
+
year={2026}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## License
|
|
301
|
+
|
|
302
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
303
|
+
|
|
304
|
+
## Acknowledgments
|
|
305
|
+
|
|
306
|
+
Built on insights from:
|
|
307
|
+
- [MemGPT](https://github.com/cpacker/MemGPT) - OS-inspired memory hierarchy
|
|
308
|
+
- [Reflexion](https://arxiv.org/abs/2303.11366) - Verbal self-reflection
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Migration from v1.x
|
|
313
|
+
|
|
314
|
+
If you were using MCAL with Mem0 backend, see [STANDALONE_MIGRATION.md](docs/STANDALONE_MIGRATION.md) for migration guide.
|
|
315
|
+
|
|
316
|
+
**Key changes:**
|
|
317
|
+
- `mem0_config` and `mem0_api_key` parameters are deprecated
|
|
318
|
+
- `use_standalone_backend` is deprecated (standalone is now default)
|
|
319
|
+
- Install `mcal[mem0]` for legacy Mem0 support
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
mcal/__init__.py,sha256=8paMduzZUQ1oDhJQtt9lj01fqWohvqoo5m296Swx2pY,4276
|
|
2
|
+
mcal/mcal.py,sha256=-CIODdF6XK6cnFjJBvSEy_3dcZOFjP1tuXFf2LoAvDE,69662
|
|
3
|
+
mcal/backends/__init__.py,sha256=bYNXulnb39Fl6BSpqb7sEswJIORVnY9pe1L2-qTY2Bk,1200
|
|
4
|
+
mcal/backends/base.py,sha256=zELlkIuqPG7Kk11j_gF0rvCaYAey_mPm0jvHSGOIJ_Y,11683
|
|
5
|
+
mcal/baselines/__init__.py,sha256=0CnVJQxNp9sHfUCMxyBGoWjijlBmYV4hIprETWzuD-E,52
|
|
6
|
+
mcal/core/__init__.py,sha256=EWpI1hJrozDcx0T7QxmRGNZDC5xaA5W0JyRjMuRZRXo,2205
|
|
7
|
+
mcal/core/embeddings.py,sha256=mqXhuyDAfqctSZSEQPTe8ZhuSKS0ZwflVKt1RYqX0wY,8465
|
|
8
|
+
mcal/core/extraction_cache.py,sha256=YJKJIDmPrnhWQYq1WS6qDnDlC28gjWRpl7QkERLhn1M,13717
|
|
9
|
+
mcal/core/goal_retriever.py,sha256=WlkyL6Wk3GBMQHsBGtVWnblnZdC9CDyhwvDixVLwujI,17946
|
|
10
|
+
mcal/core/intent_tracker.py,sha256=KDTlhiVA4CZrD9rMyt2syTsOMQqRB9U7yspDlpBZxRo,25322
|
|
11
|
+
mcal/core/models.py,sha256=CSC3lqeaVovP8B-R4eU720PbCef4fYLUWGr2vT-wfKc,15505
|
|
12
|
+
mcal/core/rate_limiter.py,sha256=L1vUwWvtVrrIy5EDIZxWGEGxYtiP9sfmBbv8sroXAuc,14243
|
|
13
|
+
mcal/core/reasoning_store.py,sha256=lj4cR7sUwWinxsMaKGkEWE5SQV0AobpvDnOjmnbIzVY,38277
|
|
14
|
+
mcal/core/retry.py,sha256=fM9xhIEUMKROGxWG02j_aZqE6dz7QL5EGK_UdcPWsAk,5488
|
|
15
|
+
mcal/core/storage.py,sha256=sB6d49d1Z8ez4RTVEOOhJA6RefKyNfgybMj8ubtJVkU,15309
|
|
16
|
+
mcal/core/streaming.py,sha256=9Og_RO8AK3l15Dfwkc_Lz6TV_AHzvv0sIqtVXvtSFcs,7402
|
|
17
|
+
mcal/core/unified_extractor.py,sha256=GFxSGxarHVG6hz2L1biIgyiNGXOldPdFvqH_QbyInRM,53172
|
|
18
|
+
mcal/core/vector_index.py,sha256=Z67ZuKh0Vol8zeQ0ruFJ94x9vzesMWtcmKt7O6Q3MvM,6091
|
|
19
|
+
mcal/evaluation/__init__.py,sha256=J1JtxdEVFocjJKk0nhuqoagpRVmuCpa46xKOonq5m0s,46
|
|
20
|
+
mcal/integrations/__init__.py,sha256=4YN2Rg1ZG38qN-SyVgb6DYE4Gj6zaXGy8jtr32bmGEc,2878
|
|
21
|
+
mcal/integrations/autogen.py,sha256=uaIEAktLsl8mgEc64PK1Nu9vQJDy9PAXiXWvf57_gLk,2593
|
|
22
|
+
mcal/integrations/crewai.py,sha256=FVfOWxnKnxhXB7KEhLgQMdEv5HFv2OK0RfDPcH3jTZ4,2462
|
|
23
|
+
mcal/integrations/langchain.py,sha256=J_xOUIUoWYcTA5iFiwyt9rTLt1R4lltAJ3hj957B4Vs,3171
|
|
24
|
+
mcal/integrations/langgraph.py,sha256=nIE7U9aQQLgG_qRQmZuy2ZOJbg_ZRCIbmYOWc0bZklA,1257
|
|
25
|
+
mcal/providers/bedrock.py,sha256=zUI07KouzOAXfMgDwloJuor0COElIBrE5_PVtjn6g7E,8632
|
|
26
|
+
mcal/storage/__init__.py,sha256=9CsHK4EtBOoSk04EcOFzCAP8LjvqX3K2ZE-hxA96jPU,29
|
|
27
|
+
mcal_ai-0.1.0.dist-info/licenses/LICENSE,sha256=zdp5kxDzb-kYvBiEZ_h1Hi96z-o6e5oXoXFx2IIefCs,1062
|
|
28
|
+
mcal_ai-0.1.0.dist-info/METADATA,sha256=MK_rggbEuc13eRezR3xHVIproSKNJ_RrZXdTjx6_KL8,14262
|
|
29
|
+
mcal_ai-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
30
|
+
mcal_ai-0.1.0.dist-info/entry_points.txt,sha256=ICilsPI5krXkoM9espdv7jtJnRMO2hTdTcwiocNDfJs,39
|
|
31
|
+
mcal_ai-0.1.0.dist-info/top_level.txt,sha256=aJ6Ay5tUlQgkrlbGXc90wuOWQpncaHNFN036i9hIWj0,5
|
|
32
|
+
mcal_ai-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shiva
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mcal
|