diff --git a/backend/open_webui/utils/memory.py b/backend/open_webui/utils/memory.py index 5a01f4c5f2..3f46b0a233 100644 --- a/backend/open_webui/utils/memory.py +++ b/backend/open_webui/utils/memory.py @@ -122,7 +122,7 @@ def search_memory_rows( def sort_key(memory): rank = _path_rank(memory.path, lookup_path) if lookup_path else None - return rank if rank is not None else (9, 0), -(memory.updated_at or 0) + return rank if rank is not None else (9, 0), -(memory.updated_at or 0), memory.id or '' return sorted(rows, key=sort_key)[: max(1, min(limit or 20, 100))] @@ -213,10 +213,10 @@ def read_memory_path_rows( def sort_key(memory): if memory.path == lookup_path: - return (0, 0, -(memory.updated_at or 0)) + return (0, 0, -(memory.updated_at or 0), memory.id or '') if memory.path and memory.path.startswith(f'{lookup_path}/'): - return (1, len(_path_parts(memory.path)), -(memory.updated_at or 0)) - return (2, -len(_path_parts(memory.path)), -(memory.updated_at or 0)) + return (1, len(_path_parts(memory.path)), -(memory.updated_at or 0), memory.id or '') + return (2, -len(_path_parts(memory.path)), -(memory.updated_at or 0), memory.id or '') return { 'path': lookup_path, @@ -232,7 +232,7 @@ def memory_path_hints(query: str, memories: list, limit: int = 6) -> list[str]: return [] hints: list[str] = [] - for memory in memories or []: + for memory in sorted(memories or [], key=lambda item: (item.path or '', item.content or '', item.id or '')): path = memory.path if not path or path in hints: continue @@ -286,6 +286,13 @@ def model_allows_memory(model: dict | None) -> bool: return ((model or {}).get('info', {}).get('meta', {}).get('capabilities') or {}).get('memory', True) +def _format_memory_context_section(title: str, memories: list[str]) -> str | None: + if not memories: + return None + ordered = sorted(memories, key=lambda memory: (memory.casefold(), memory)) + return f'[{title}]\n' + '\n'.join(f'- {memory}' for memory in ordered) + + async def add_memory_context(request, form_data: dict, user, model: dict | None = None): if not model_allows_memory(model): return form_data @@ -319,7 +326,7 @@ async def add_memory_context(request, form_data: dict, user, model: dict | None seen_ids = set() for memory in sorted( [memory for memory in (all_memories or []) if memory.type == 'user'], - key=lambda item: (item.path or '', item.updated_at), + key=lambda item: (item.path or '', item.updated_at or 0, item.id or ''), ): seen_ids.add(memory.id) sections['user'].append(memory_label(memory)) @@ -360,12 +367,14 @@ async def add_memory_context(request, form_data: dict, user, model: dict | None sections[Memories.normalize_memory_type(metadata.get('type'))].append(label) parts = [] - if sections['user']: - parts.append('[User Memory]\n' + '\n'.join(f'- {memory}' for memory in sections['user'])) - if sections['neighborhood']: - parts.append('[Memory Neighborhood]\n' + '\n'.join(f'- {memory}' for memory in sections['neighborhood'])) - if sections['context']: - parts.append('[Relevant Context]\n' + '\n'.join(f'- {memory}' for memory in sections['context'])) + for title, key in ( + ('User Memory', 'user'), + ('Memory Neighborhood', 'neighborhood'), + ('Relevant Context', 'context'), + ): + part = _format_memory_context_section(title, sections[key]) + if part: + parts.append(part) if not parts: return form_data