Add configurable case-insensitive search #4

Merged
Pakobbix merged 1 commits from feature/case-insensitive-search into main 2026-04-15 16:39:48 +00:00
Owner

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

MEM0_CASE_INSENSITIVE=true

Or in ~/.hermes/mem0-local.json:

{"case_insensitive": true}

Trade-off

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.
ARIA added 1 commit 2026-04-15 16:20:12 +00:00
- 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
Collaborator

🚨 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:
      # 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:
    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:

    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:

-   ```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.

🚨 **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.
Pakobbix merged commit 0c9f352ca6 into main 2026-04-15 16:39:48 +00:00
Pakobbix deleted branch feature/case-insensitive-search 2026-04-15 16:39:48 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ARIA/mem0-local-hermes-plugin#4