Doubles API calls when enabled. Disabled by default.
## Summary
Adds configurable case-insensitive search to handle Qdrant embedding case sensitivity.
## Changes
- New config: `MEM0_CASE_INSENSITIVE` (default: false)
- Dual search: When enabled, searches with original + lowercase query
- Smart merge: Keeps highest score for each memory ID
- Generalized after-install.md with placeholders
## Problem
Qdrant shows case sensitivity: "mattermost" search → 57% match, "Mattermost" → 41%
## Config
```env
MEM0_CASE_INSENSITIVE=true
```
Or in `~/.hermes/mem0-local.json`:
```json
{"case_insensitive": true}
```
## Trade-off
Doubles API calls when enabled. Disabled by default.
- Add MEM0_CASE_INSENSITIVE config option (default: false)
- When enabled, searches with both original and lowercase query
- Merges results, keeping highest score for each memory
- Fixes case sensitivity issues with Qdrant embeddings
- Generalize after-install.md with placeholders instead of personal values
There is a recursion error in the client.py file that will cause the application to crash when case_insensitive=True.
The Issue
In LocalMem0Client.search, the code calls self._search_with_query to perform the dual search. However, the method _search_with_query is defined after the logic in search that calls it, but the search method's logic overwrites the original search method's body without preserving the original functionality for the helper.
Wait, looking closer at the diff:
The search method is modified to handle case_insensitive.
If case_insensitive is True, it calls self._search_with_query.
The _search_with_query method is defined after the search method.
BUT: The search method's original logic (the if not case_insensitive block) is fine.
The Real Bug: The _search_with_query method is defined inside the diff, but let's check the indentation and scope.
The diff shows _search_with_query being added at the end of the class.
However, look at the search method implementation in the diff:
# Case-insensitive mode: search with both original and lowercase# Fetch 2x limit to ensure we get top N after mergingresults_original=self._search_with_query(query,user_id,limit*2)results_lower=self._search_with_query(query.lower(),user_id,limit*2)
Missing Return Statement: The _search_with_query method in the diff ends abruptly. It constructs the payload but never calls self._request and never returns the result.
The original search method (before the if not case_insensitive block) had the _request call. The new helper method is missing this critical line.
Impact
When MEM0_CASE_INSENSITIVE=true:
The code calls self._search_with_query(...).
_search_with_query creates a payload but returns None (implicitly).
results_original and results_lower become None.
The line for result in results_original + results_lower: will raise a TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'.
Even if it didn't crash immediately, no search data would be returned.
Fix
You need to add the request and return statement to _search_with_query.
Corrected client.py snippet:
def_search_with_query(self,query:str,user_id:Optional[str]=None,limit:int=5,)->List[Dict]:"""Internal search helper for case-insensitive mode."""payload={"query":query,"limit":limit}ifuser_id:payload["user_id"]=user_id# MISSING LINES ADDED HERE:result=self._request("POST","/search",json=payload)returnresult.get("results",[])
Minor Observation (Documentation)
In after-install.md, the curl command indentation is slightly inconsistent in the "Check server connectivity" section:
The indentation changed from 3 spaces to 4 spaces. This is a minor formatting issue and won't break functionality, but it's worth noting for consistency.
✅Summary: The primary blocker is the missing return and self._request call in the new _search_with_query helper method. Without this, the feature will crash.
🚨 **Critical Logic Error Found**
There is a **recursion error** in the `client.py` file that will cause the application to crash when `case_insensitive=True`.
### The Issue
In `LocalMem0Client.search`, the code calls `self._search_with_query` to perform the dual search. However, the method `_search_with_query` is defined **after** the logic in `search` that calls it, but the `search` method's logic **overwrites** the original `search` method's body without preserving the original functionality for the helper.
Wait, looking closer at the diff:
1. The `search` method is modified to handle `case_insensitive`.
2. If `case_insensitive` is True, it calls `self._search_with_query`.
3. The `_search_with_query` method is defined **after** the `search` method.
4. **BUT**: The `search` method's original logic (the `if not case_insensitive` block) is fine.
5. **The Real Bug**: The `_search_with_query` method is defined **inside** the diff, but let's check the indentation and scope.
* The diff shows `_search_with_query` being added at the end of the class.
* However, look at the `search` method implementation in the diff:
```python
# Case-insensitive mode: search with both original and lowercase
# Fetch 2x limit to ensure we get top N after merging
results_original = self._search_with_query(query, user_id, limit * 2)
results_lower = self._search_with_query(query.lower(), user_id, limit * 2)
```
* Now look at the definition of `_search_with_query`:
```python
def _search_with_query(
self,
query: str,
user_id: Optional[str] = None,
limit: int = 5,
) -> List[Dict]:
"""Internal search helper for case-insensitive mode."""
payload = {"query": query, "limit": limit}
if user_id:
payload["user_id"] = user_id
```
* **Missing Return Statement**: The `_search_with_query` method in the diff **ends abruptly**. It constructs the `payload` but **never calls `self._request`** and **never returns the result**.
* The original `search` method (before the `if not case_insensitive` block) had the `_request` call. The new helper method is missing this critical line.
### Impact
When `MEM0_CASE_INSENSITIVE=true`:
1. The code calls `self._search_with_query(...)`.
2. `_search_with_query` creates a payload but returns `None` (implicitly).
3. `results_original` and `results_lower` become `None`.
4. The line `for result in results_original + results_lower:` will raise a **`TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'`**.
5. Even if it didn't crash immediately, no search data would be returned.
### Fix
You need to add the request and return statement to `_search_with_query`.
**Corrected `client.py` snippet:**
```python
def _search_with_query(
self,
query: str,
user_id: Optional[str] = None,
limit: int = 5,
) -> List[Dict]:
"""Internal search helper for case-insensitive mode."""
payload = {"query": query, "limit": limit}
if user_id:
payload["user_id"] = user_id
# MISSING LINES ADDED HERE:
result = self._request("POST", "/search", json=payload)
return result.get("results", [])
```
---
### Minor Observation (Documentation)
In `after-install.md`, the `curl` command indentation is slightly inconsistent in the "Check server connectivity" section:
```diff
- ```bash
- curl http://10.0.0.150:8889/health
- ```
+ ```bash
+ curl http://<YOUR_MEM0_HOST>:<YOUR_MEM0_PORT>/health
+ ```
```
The indentation changed from 3 spaces to 4 spaces. This is a minor formatting issue and won't break functionality, but it's worth noting for consistency.
✅ **Summary**: The primary blocker is the missing `return` and `self._request` call in the new `_search_with_query` helper method. Without this, the feature will crash.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Adds configurable case-insensitive search to handle Qdrant embedding case sensitivity.
Changes
MEM0_CASE_INSENSITIVE(default: false)Problem
Qdrant shows case sensitivity: "mattermost" search → 57% match, "Mattermost" → 41%
Config
Or in
~/.hermes/mem0-local.json:Trade-off
Doubles API calls when enabled. Disabled by default.
🚨 Critical Logic Error Found
There is a recursion error in the
client.pyfile that will cause the application to crash whencase_insensitive=True.The Issue
In
LocalMem0Client.search, the code callsself._search_with_queryto perform the dual search. However, the method_search_with_queryis defined after the logic insearchthat calls it, but thesearchmethod's logic overwrites the originalsearchmethod's body without preserving the original functionality for the helper.Wait, looking closer at the diff:
searchmethod is modified to handlecase_insensitive.case_insensitiveis True, it callsself._search_with_query._search_with_querymethod is defined after thesearchmethod.searchmethod's original logic (theif not case_insensitiveblock) is fine._search_with_querymethod is defined inside the diff, but let's check the indentation and scope.- The diff shows
- However, look at the
- Now look at the definition of
def _search_with_query(_search_with_querybeing added at the end of the class.searchmethod implementation in the diff:_search_with_query:self,
query: str,
user_id: Optional[str] = None,
limit: int = 5,
) -> List[Dict]:
"""Internal search helper for case-insensitive mode."""
payload = {"query": query, "limit": limit}
if user_id:
payload["user_id"] = user_id
```
_search_with_querymethod in the diff ends abruptly. It constructs thepayloadbut never callsself._requestand never returns the result.searchmethod (before theif not case_insensitiveblock) had the_requestcall. The new helper method is missing this critical line.Impact
When
MEM0_CASE_INSENSITIVE=true:self._search_with_query(...)._search_with_querycreates a payload but returnsNone(implicitly).results_originalandresults_lowerbecomeNone.for result in results_original + results_lower:will raise aTypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'.Fix
You need to add the request and return statement to
_search_with_query.Corrected
client.pysnippet:Minor Observation (Documentation)
In
after-install.md, thecurlcommand indentation is slightly inconsistent in the "Check server connectivity" section:The indentation changed from 3 spaces to 4 spaces. This is a minor formatting issue and won't break functionality, but it's worth noting for consistency.
✅ Summary: The primary blocker is the missing
returnandself._requestcall in the new_search_with_queryhelper method. Without this, the feature will crash.