Spaces:
Running
Running
Download engine/memory_controls.py from cereeenn120/CharacterChatBot: direct link, hf CLI and curl.
- Browser
- Download file 5.05 kB
-
https://huggingface.co/spaces/cereeenn120/CharacterChatBot/resolve/main/engine/memory_controls.py
- Command line
-
hf download hf://spaces/cereeenn120/CharacterChatBot/engine/memory_controls.py
-
curl -L -o memory_controls.py https://huggingface.co/spaces/cereeenn120/CharacterChatBot/resolve/main/engine/memory_controls.py
5.05 kB
| """User-owned facts and source exclusions, without extra model calls.""" | |
| from hashlib import sha256 | |
| from uuid import uuid4 | |
| from engine.relationship import get_default_relationship | |
| def fingerprint(text): | |
| return sha256(' '.join(text.casefold().split()).encode()).hexdigest() | |
| def upgrade_conversation(state): | |
| state.setdefault('relationship', get_default_relationship()) | |
| state.setdefault('excluded_sources', []) | |
| state.setdefault('forgotten_facts', []) | |
| for i, item in enumerate(state['long_term_memory']): | |
| item.setdefault('id', fingerprint(f'{i}:{item["content"]}')[:32]) | |
| item.setdefault('origin', 'auto') | |
| item.setdefault('start', 0) | |
| item.setdefault('end', state['summarized_count']) | |
| return state | |
| def context_messages(state, start=0, end=None): | |
| end = len(state['recent_messages']) if end is None else end | |
| return [dict(message) if not any(a <= index < b for a, b in state['excluded_sources']) | |
| else {'role': message['role'], 'content': '[Message excluded from memory by the user.]'} | |
| for index, message in enumerate(state['recent_messages'][start:end], start)] | |
| def _exclude_fact(state, item): | |
| blocked = fingerprint(item['content']) | |
| if blocked not in state['forgotten_facts']: | |
| if len(state['forgotten_facts']) >= 1000: | |
| raise ValueError('The forgotten-fact limit has been reached.') | |
| state['forgotten_facts'].append(blocked) | |
| if item['end'] > item['start']: | |
| state['excluded_sources'].append([item['start'], item['end']]) | |
| # Also mask verbatim echoes of the fact later in the transcript. | |
| needle = ' '.join(item['content'].casefold().split()) | |
| for index, message in enumerate(state['recent_messages']): | |
| if needle in ' '.join(message['content'].casefold().split()): | |
| state['excluded_sources'].append([index, index + 1]) | |
| merged = [] | |
| for start, end in sorted(state['excluded_sources']): | |
| if merged and start <= merged[-1][1]: | |
| merged[-1][1] = max(merged[-1][1], end) | |
| else: | |
| merged.append([start, end]) | |
| state['excluded_sources'] = merged | |
| # The old summary may contain a paraphrase; rebuild it from unexcluded text. | |
| state['summary'], state['summarized_count'] = '', 0 | |
| def overlaps(record): | |
| return any(record['start'] < end and start < record['end'] for start, end in merged) | |
| state['long_term_memory'] = [m for m in state['long_term_memory'] | |
| if m['id'] != item['id'] and not (m['origin'] == 'auto' and overlaps(m))] | |
| state['relationship']['events'] = [e for e in state['relationship']['events'] if not overlaps(e)] | |
| def change_memory(session_id, character_id, action, content='', memory_id=None): | |
| from engine.memory import _LOCK, load_conversation, save_conversation | |
| with _LOCK: | |
| state = load_conversation(session_id, character_id) | |
| if action not in ('remember', 'edit', 'forget'): | |
| raise ValueError('Unknown memory action.') | |
| if action != 'forget' and (not isinstance(content, str) or not content.strip() or len(content) > 2000): | |
| raise ValueError('Enter a memory between 1 and 2000 characters.') | |
| item = next((m for m in state['long_term_memory'] if m['id'] == memory_id), None) | |
| if action in ('edit', 'forget'): | |
| if item is None: | |
| raise ValueError('Select an existing memory first.') | |
| _exclude_fact(state, item) | |
| if action != 'forget': | |
| if sum(m['origin'] == 'manual' for m in state['long_term_memory']) >= 60: | |
| raise ValueError('Up to 60 personal memories can be saved.') | |
| content = content.strip() | |
| state['forgotten_facts'] = [f for f in state['forgotten_facts'] if f != fingerprint(content)] | |
| state['long_term_memory'] = [m for m in state['long_term_memory'] if fingerprint(m['content']) != fingerprint(content)] | |
| state['long_term_memory'].append({'id': memory_id if action == 'edit' else uuid4().hex, | |
| 'content': content, 'importance': 'high', 'origin': 'manual', 'start': 0, 'end': 0}) | |
| save_conversation(session_id, character_id, state, state['revision']) | |
| return state | |
| def set_relationship_options(session_id, character_id, romance, attraction): | |
| from engine.memory import _LOCK, load_conversation, save_conversation | |
| if type(romance) is not bool or type(attraction) is not bool: | |
| raise ValueError('Relationship options must be booleans.') | |
| with _LOCK: | |
| state = load_conversation(session_id, character_id) | |
| enabled = {'romance': romance, 'attraction': attraction} | |
| if state['relationship']['enabled'] != enabled: | |
| state['relationship']['enabled'] = enabled | |
| state['relationship']['events'] = [e for e in state['relationship']['events'] | |
| if e['axis'] not in enabled or enabled[e['axis']]] | |
| save_conversation(session_id, character_id, state, state['revision']) | |
| return state | |