-
Notifications
You must be signed in to change notification settings - Fork 7.6k
fix(task): centralize token tracking and preserve usage during guardrail retries #6251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Jacopos311
wants to merge
20
commits into
crewAIInc:main
Choose a base branch
from
Jacopos311:fix/task-token-tracking
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
e9f44cb
feat: add Mimir as persistent cross-session memory backend
SessantaNove 01b3bb5
feat: wire MimirStorage into factory resolution
SessantaNove 2d7815f
fix: address CodeRabbit review comments on MimirStorage integration
SessantaNove aa62068
fix: adjust SDK calls to async format and fix return types
SessantaNove bd02734
fix: switch to MimirSyncClient to satisfy synchronous StorageBackend …
SessantaNove 4fa46f7
fix: sanitize MimirSyncClient config keys and fix comment typo
SessantaNove 21d7956
refactor: upgrade Mimir memory backend to official MCP standard
SessantaNove b9e4349
refactor: complete Mimir integration update in readme and factory
SessantaNove 711e379
fix: address coderabbit recommendations on db flag, expansion and sco…
SessantaNove d6b95cf
Merge branch 'main' into main
Jacopos311 67b8320
Address maintainer feedback for Mimir storage module
SessantaNove 76dc276
Fix Mimir storage implementation to fully conform with StorageBackend…
SessantaNove 0b2c880
Enhance MimirStorage stability with direct imports and subprocess tim…
SessantaNove cd0fbaf
fix(task): centralize token tracking and preserve usage during guardr…
SessantaNove bba8015
fix(task): address token delta calculation for dict types and explici…
SessantaNove 539d2f9
fix(task,memory): address token tracking lifecycle and sync storage l…
SessantaNove d5c3468
fix(memory): align search limit and min_score protocol defaults in mi…
SessantaNove 030fff6
feat: implement deterministic tool permission gating for agents (#6232)
SessantaNove 6bfc9a8
fix(security): propagate capability gating to structured tools
SessantaNove be6e070
Merge branch 'main' into fix/task-token-tracking
Jacopos311 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| import os | ||
| import shutil | ||
| import subprocess | ||
| import json | ||
| import logging | ||
| import hashlib | ||
| import re | ||
| from typing import List, Dict, Any, Optional, Tuple | ||
|
|
||
| from crewai.memory.storage.backend import StorageBackend | ||
| # CodeRabbit Fix: Direct import to fail-fast and avoid masking integration issues | ||
| from crewai.memory.storage.interface import MemoryRecord # type: ignore | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| class MimirStorage(StorageBackend): | ||
| def __init__(self, config: Optional[Dict[str, Any]] = None): | ||
| self.config = config or {} | ||
|
|
||
| # Resolve db_path from config dictionary, expanding '~' to home directory | ||
| raw_db_path = self.config.get("db_path", "~/mimir_db") | ||
| self.db_path = os.path.expanduser(raw_db_path) | ||
| os.makedirs(self.db_path, exist_ok=True) | ||
|
|
||
| # Verify mimir binary availability in common paths or system PATH | ||
| self.mimir_path = self._find_mimir_binary() | ||
| if not self.mimir_path: | ||
| raise FileNotFoundError( | ||
| "The 'mimir' binary could not be found. Please ensure it is installed " | ||
| "and available in PATH or at common locations (~/.cargo/bin/mimir, /usr/local/bin/mimir)." | ||
| ) | ||
|
|
||
| def _find_mimir_binary(self) -> Optional[str]: | ||
| """Checks common paths and system PATH for the mimir binary.""" | ||
| path_binary = shutil.which("mimir") | ||
| if path_binary: | ||
| return path_binary | ||
|
|
||
| common_paths = [ | ||
| os.path.expanduser("~/.cargo/bin/mimir"), | ||
| "/usr/local/bin/mimir", | ||
| "/usr/bin/mimir" | ||
| ] | ||
| for path in common_paths: | ||
| if os.path.isfile(path) and os.access(path, os.X_OK): | ||
| return path | ||
| return None | ||
|
|
||
| def _validate_inputs(self, category: str, query: Optional[str] = None) -> None: | ||
| """Validates input arguments to safeguard against CLI/flag injection attacks.""" | ||
| if category and not re.match(r"^[A-Za-z0-9_-]+$", category): | ||
| raise ValueError(f"Malicious characters detected in scope/category: '{category}'") | ||
| if query and query.startswith("-"): | ||
| raise ValueError("Query string cannot start with a hyphen to prevent flag injection.") | ||
|
|
||
| def save(self, records: List[MemoryRecord]) -> None: | ||
| """Saves a list of MemoryRecords conforming to the StorageBackend protocol.""" | ||
| for record in records: | ||
| value_str = str(record.value) | ||
|
|
||
| # Generate a persistent deterministic hash key using hashlib MD5 | ||
| hash_suffix = hashlib.md5(value_str.encode('utf-8')).hexdigest()[:12] | ||
| key = f"memory_{hash_suffix}" | ||
|
|
||
| # Scope memories using config metadata or default category | ||
| category = record.metadata.get("agent_id", "default") | ||
| self._validate_inputs(category) | ||
|
|
||
| # Prepare payload | ||
| payload = { | ||
| "key": key, | ||
| "value": value_str, | ||
| "category": category, | ||
| "metadata": record.metadata | ||
| } | ||
|
|
||
| # Call the subprocess using '--db' flag per Mimir CLI docs (with 10s timeout) | ||
| try: | ||
| cmd = [self.mimir_path, "--db", self.db_path, "store", json.dumps(payload)] | ||
| subprocess.run(cmd, check=True, capture_output=True, text=True, timeout=10) | ||
| logger.info(f"Successfully stored memory with key: {key}") | ||
| except subprocess.TimeoutExpired as te: | ||
| logger.error(f"Mimir store operation timed out: {te}") | ||
| raise te | ||
| except subprocess.CalledProcessError as e: | ||
| logger.error(f"Failed to store memory in Mimir: {e.stderr}") | ||
| raise e | ||
|
|
||
| def search( | ||
| self, | ||
| query: Any, | ||
| limit: Optional[int] = None, | ||
| scope_prefix: Optional[str] = None, | ||
| categories: Optional[List[str]] = None, | ||
| min_score: Optional[float] = None, | ||
| metadata_filter: Optional[Dict[str, Any]] = None, | ||
| **kwargs | ||
| ) -> List[Tuple[MemoryRecord, float]]: | ||
| """Searches memories and returns a list of (MemoryRecord, score) tuples.""" | ||
| query_str = query if isinstance(query, str) else str(query) | ||
|
|
||
| actual_limit = limit if limit is not None else 3 | ||
|
|
||
| category = scope_prefix if scope_prefix else "default" | ||
|
|
||
| if categories and len(categories) > 0: | ||
| category = categories[0] | ||
|
|
||
| self._validate_inputs(category, query_str) | ||
|
|
||
| try: | ||
| cmd = [ | ||
| self.mimir_path, | ||
| "--db", self.db_path, | ||
| "search", | ||
| query_str, | ||
| "--limit", str(actual_limit), | ||
| "--category", category | ||
| ] | ||
| result = subprocess.run(cmd, check=True, capture_output=True, text=True, timeout=10) | ||
|
|
||
| raw_results = json.loads(result.stdout) | ||
| formatted_results = [] | ||
|
|
||
| for res in raw_results: | ||
| content_text = res.get("value", res.get("text", "")) | ||
| score = float(res.get("score", 0.0)) | ||
| meta = res.get("metadata", {}) | ||
|
|
||
| if min_score is not None and score < min_score: | ||
| continue | ||
|
|
||
| if metadata_filter: | ||
| match = True | ||
| for k, v in metadata_filter.items(): | ||
| if meta.get(k) != v: | ||
| match = False | ||
| break | ||
| if not match: | ||
| continue | ||
|
|
||
| # Construct official MemoryRecord instances | ||
| record = MemoryRecord(value=content_text, metadata=meta) | ||
| formatted_results.append((record, score)) | ||
|
|
||
| return formatted_results | ||
| except subprocess.TimeoutExpired as te: | ||
| logger.error(f"Mimir search operation timed out: {te}") | ||
| raise te | ||
| except subprocess.CalledProcessError as e: | ||
| logger.error(f"Search failed in Mimir: {e.stderr}") | ||
| raise e |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.