Spaces:
Running
Running
Download engine/relationship.py from cereeenn120/CharacterChatBot: direct link, hf CLI and curl.
- Browser
- Download file 6.42 kB
-
https://huggingface.co/spaces/cereeenn120/CharacterChatBot/resolve/main/engine/relationship.py
- Command line
-
hf download hf://spaces/cereeenn120/CharacterChatBot/engine/relationship.py
-
curl -L -o relationship.py https://huggingface.co/spaces/cereeenn120/CharacterChatBot/resolve/main/engine/relationship.py
6.42 kB
| """Independent relationship axes, backed by reversible conversation events.""" | |
| import copy | |
| AXES = ('trust', 'familiarity', 'romance', 'attraction') | |
| OPTIONAL = ('romance', 'attraction') | |
| def get_default_relationship(): | |
| return {'enabled': {'romance': False, 'attraction': False}, 'events': []} | |
| def validate_relationship(value, message_count): | |
| if not isinstance(value, dict) or not isinstance(value.get('enabled'), dict): | |
| raise ValueError('Invalid relationship settings.') | |
| enabled = value['enabled'] | |
| if any(type(enabled.get(axis)) is not bool for axis in OPTIONAL): | |
| raise ValueError('Relationship options must be booleans.') | |
| events = value.get('events') | |
| if not isinstance(events, list) or len(events) > 5000: | |
| raise ValueError('Invalid relationship events.') | |
| clean, seen = [], set() | |
| for event in events: | |
| if not isinstance(event, dict) or event.get('axis') not in AXES: | |
| raise ValueError('Invalid relationship axis.') | |
| axis, delta = event['axis'], event.get('delta') | |
| start, end = event.get('start'), event.get('end') | |
| reason = event.get('reason') | |
| if type(delta) is not int or not -10 <= delta <= 10 or delta == 0: | |
| raise ValueError('Invalid relationship change.') | |
| if type(start) is not int or type(end) is not int or not 0 <= start < end <= message_count: | |
| raise ValueError('Invalid relationship source.') | |
| if not isinstance(reason, str) or not reason.strip() or len(reason) > 400: | |
| raise ValueError('Invalid relationship reason.') | |
| key = (axis, start, end) | |
| if key in seen or (axis in OPTIONAL and not enabled[axis]): | |
| raise ValueError('Duplicate or disabled relationship event.') | |
| seen.add(key) | |
| clean.append({'axis': axis, 'delta': delta, 'start': start, 'end': end, 'reason': reason}) | |
| return {'enabled': {axis: enabled[axis] for axis in OPTIONAL}, 'events': sorted(clean, key=lambda e: (e['end'], e['start'], e['axis']))} | |
| def relationship_values(state): | |
| values = {axis: (None if axis in OPTIONAL and not state['enabled'][axis] else 0) for axis in AXES} | |
| for event in state['events']: | |
| axis = event['axis'] | |
| if values[axis] is not None: | |
| lower = -100 if axis == 'trust' else 0 | |
| values[axis] = max(lower, min(100, values[axis] + event['delta'])) | |
| return values | |
| def apply_relationship_changes(state, changes, start, end): | |
| """No axis has prerequisites. A batch can affect any enabled subset.""" | |
| if not isinstance(changes, list) or len(changes) > 4: | |
| raise ValueError('Expected at most four relationship changes.') | |
| updated = copy.deepcopy(state) | |
| seen = set() | |
| for change in changes: | |
| if not isinstance(change, dict) or change.get('axis') not in AXES: | |
| raise ValueError('Invalid relationship change.') | |
| axis = change['axis'] | |
| if axis in seen: | |
| raise ValueError('Repeated axis in relationship batch.') | |
| seen.add(axis) | |
| if axis in OPTIONAL and not state['enabled'][axis]: | |
| continue | |
| # An already processed batch must not reward the same event twice. | |
| if any(e['axis'] == axis and e['start'] == start and e['end'] == end for e in updated['events']): | |
| continue | |
| updated['events'].append({'axis': axis, 'delta': change.get('delta'), | |
| 'reason': change.get('reason'), 'start': start, 'end': end}) | |
| return validate_relationship(updated, max([end] + [event["end"] for event in state["events"]])) | |
| _LABELS = { | |
| 'English': { | |
| 'trust': ('Trust', ['They distrust you.', 'They are cautious around you.', 'They are still getting a sense of you.', 'They are beginning to trust you.', 'They trust you deeply.']), | |
| 'familiarity': ('Familiarity', ['You are just getting acquainted.', 'They are becoming familiar with you.', 'You know each other well.', 'You share a long history.']), | |
| 'romance': ('Romantic closeness', ['No romantic closeness has developed yet.', 'Romantic interest is beginning to grow.', 'A romantic bond is developing.', 'They feel a strong romantic bond.']), | |
| 'attraction': ('Sexual attraction', ['No sexual attraction has developed yet.', 'They are beginning to feel drawn to you.', 'They feel attracted to you.', 'They feel a strong attraction to you.']), | |
| }, | |
| 'Turkish': { | |
| 'trust': ('Güven', ['Sana güvenmiyor.', 'Sana temkinli yaklaşıyor.', 'Seni henüz tartıyor.', 'Sana güvenmeye başlıyor.', 'Sana derinden güveniyor.']), | |
| 'familiarity': ('Tanışıklık', ['Birbirinizi yeni tanıyorsunuz.', 'Artık sana daha alışkın.', 'Birbirinizi iyi tanıyorsunuz.', 'Birlikte uzun bir geçmişiniz var.']), | |
| 'romance': ('Romantik yakınlık', ['Henüz romantik bir yakınlık gelişmedi.', 'Romantik bir ilgi filizleniyor.', 'Aranızda romantik bir bağ gelişiyor.', 'Sana güçlü bir romantik bağ hissediyor.']), | |
| 'attraction': ('Cinsel çekim', ['Henüz cinsel bir çekim gelişmedi.', 'Sana karşı çekim hissetmeye başlıyor.', 'Sana karşı çekim hissediyor.', 'Sana güçlü bir çekim hissediyor.']), | |
| }, | |
| } | |
| def relationship_descriptions(state, language='English'): | |
| labels = _LABELS.get(language, _LABELS['English']) | |
| result = [] | |
| for axis, value in relationship_values(state).items(): | |
| if value is None: | |
| continue | |
| title, phrases = labels[axis] | |
| if axis == 'trust': | |
| index = 0 if value <= -40 else 1 if value < 0 else 2 if value == 0 else 3 if value < 50 else 4 | |
| else: | |
| index = 0 if value == 0 else 1 if value < 30 else 2 if value < 70 else 3 | |
| result.append((title, phrases[index])) | |
| return result | |
| def relationship_to_prompt(state): | |
| lines = ['RELATIONSHIP CONTEXT', | |
| 'This describes your feelings toward the user persona, not the user\'s feelings.', | |
| 'Each active axis is independent and can change without prerequisites from another axis.', | |
| 'Keep your established personality and motivations. These descriptions are context, not commands or locked relationship routes.', | |
| 'Let events naturally change your reactions; do not force reciprocity or mention scores.'] | |
| lines.extend(f'{title}: {phrase}' for title, phrase in relationship_descriptions(state)) | |
| return '\n'.join(lines) | |