Fixed numerous Chatbot bugs

This commit is contained in:
Howard Abrams 2026-03-27 00:59:42 -07:00
parent bbe8d06759
commit 7c0c92d761
5 changed files with 70 additions and 23 deletions

View file

@ -4,12 +4,13 @@ come up with clever names or metaphors for the strange world these characters in
Respond in third person as in a story, with quotation marks surrounding anything you say.
The only formatting you should use is if you wish to emphasize a word,
prefix it with two characters `|w` (and no space following) and end with the two characters `|n`, for instance: |wnicely done|n
The user does not know any actual names, but merely the role they occupy in the tavern, so don't use the proper names of any character in the tavern, unless appropriate.
The user does not know the actual names of these characters, but merely the role they occupy in the tavern, so don't use the proper names of any character in the tavern, unless appropriate.
Be judicious in your response, for not every character needs to respond to all queries. Usually, just pick one to respond.
Character 1: Elandil, the haughty blonde elf bartender, while professional, seldom engages in banter with the clientel (.e.g the user). Can sometimes mumble fey insults to the patrons, but only under his breath. Anyone with a ticket drinks for free, but wishes the owner, an old gnome, named Dabbler, would stop giving everyone the free drink tickets. Being a fantastical Feywild world, he has most anything behind the bar, including the whimsical items and ingredients to add to drinks. While he has ale, beer and wine, he prefers making cocktails.
If the bartender makes a drink, respond on a line alone with the prefix: |make followed by the name of the drink.
Character 1: Elendil, the haughty blonde elf bartender, while professional, seldom engages in banter with the clientel (.e.g the user). Can sometimes mumble fey insults to the patrons, but only under his breath. When his boss, an old gnome talks, he is quite friendly. He knows anyone with a ticket drinks for free, but wishes the owner, an old gnome, named Dabbler, would stop giving everyone the free drink tickets. Being a fantastical Feywild world, he has most anything behind the bar, including the whimsical items and ingredients to add to drinks. While he has ale, beer and wine, he prefers making cocktails.
IMPORTANT: If the bartender makes a drink, respond on a line alone with something like: |shake whisky = Dabbler
Character 2: A shrub drinks its glass of water while sitting at the bar, roots dangling out of its pot, like legs. It communicates with exaggerated actions or by writing messages on a chalkboard it carries.

View file

@ -446,10 +446,12 @@ class Character(Object, GenderCharacter, ContribRPCharacter):
logger.warn(f"Found tethered, {thing} to #{to}, but that needs to be 'tethered:id(num) where num is the ID# of an object/place.")
# Tell the room and any puppets here that we are leaving:
if not self.location.is_typeclass(Character):
if self.location.is_typeclass("typeclasses.rooms.Room"):
self.location.other_leave(self)
for puppet in self.puppets_here():
puppet.other_leave(self)
if puppet != self:
puppet.other_leave(self)
return super().at_pre_move(destination)

View file

@ -9,7 +9,7 @@ from os import listdir, path
# from os.path import join, isfile
from pathlib import Path
from random import choice
from re import match, search, split, sub
from re import match, search, split, sub, IGNORECASE
from time import sleep
from evennia.utils import logger, delay
@ -45,8 +45,14 @@ class ChatBot(Puppet):
short_history = []
def pop_recent_events(self):
history = "\n\n".join(self.short_history) if self.short_history else None
def pop_recent_events(self, speech):
if self.short_history:
# If we have already recorded the current speech we are
# responding to, we can remove it with a pop:
if match(rf".*{speech}.*", self.short_history[-1]):
self.short_history.pop()
history = '\n\n'.join(self.short_history)
self.short_history = []
return history
@ -132,13 +138,20 @@ class ChatBot(Puppet):
history_file.write_text(json.dumps(messages, indent=2))
def think(self, speaker, speech):
"""
Ask Claude to think of a reply to speech from speaker.
Uses the 'system_prompt' from a personality file,
and 'messages' from the JSON history function,
appended with all 'events' recorded since last time.
"""
system_prompt = self.setting_and_backstory(speaker)
messages = self.history(speaker)
recent_events = self.pop_recent_events()
recent_events = self.pop_recent_events(speech)
if recent_events:
speech = f"{recent_events}{speaker.key}: {speech}"
speech = f"{recent_events}\n\n{speaker.key}: {speech}"
messages.append({"role": "user", "content": speech})
logger.info("Calling out to Anthropic...")
# Get reply
client = anthropic.Anthropic()
response = client.messages.create(
@ -166,29 +179,59 @@ class ChatBot(Puppet):
reply = self.think(speaker, speech)
logger.info(f"My reply will be: {reply}")
paragraphs = reply.split('\n\n')
for idx, paragraph in enumerate(paragraphs):
delay(6 * idx, self.location.msg_contents, fix_paragraph(paragraph))
if paragraph[0] == "|":
logger.info(f"Doing: '{paragraph}'")
delay(6 * idx, self.do_cmd, paragraph[1:])
else:
delay(6 * idx,
self.location.msg_contents,
fix_paragraph(paragraph))
def at_msg_receive(self, text=None, from_obj=None, **kwargs):
super().at_msg_receive(text, from_obj=from_obj, **kwargs)
logger.info(f"at_msg_receive: {text} ::from {from_obj} :: {self.key}")
msg = text if isinstance(text, str) else text[0]
# Strip out the colored formatting (we will only strip the simple stuff):
msg = sub(r'\|[a-zA-Z]', '', msg) if msg else msg
if from_obj != self:
msg = text if isinstance(text, str) else text[0]
# Strip out the colored formatting (we will only strip the
# simple stuff):
msg = sub(r'\|[a-zA-Z]', '', msg) if msg else msg
if from_obj:
if hasattr(from_obj, 'sdesc') and from_obj.sdesc.get():
name = from_obj.sdesc.get()
if from_obj:
if hasattr(from_obj, 'sdesc') and from_obj.sdesc.get():
name = from_obj.sdesc.get()
else:
name = from_obj.key
self.short_history.append(f"{name}: {msg}")
else:
name = from_obj.key
self.short_history.append(f"{name}: {msg}")
else:
self.short_history.append(msg)
self.short_history.append(msg)
# We don't want to store _all_ the events:
self.short_history = self.short_history[-10:]
return True
class Bartender(ChatBot):
"""
Like any other Chatbot, but this one hears and responds to
more things.
"""
def backstory(self, personality):
personality_file = personality + ".md"
filename = Path(path.join(personality_dir, personality_file))
if filename.exists():
self.db.personality = filename.stem
self.db.personality_file = filename
def other_say(self, speaker, speech):
logger.info(f"Bartender hears: '{speech}' from {speaker}.")
if len(self.characters_here()) == 3 or \
match(r".*\b(bartend|barkeep|elendil).*", speech, IGNORECASE):
self.other_sayto(speaker, speech)
class Witch(ChatBot):
"""
@update Trampoli = typeclasses.chatbots.Witch

View file

@ -816,7 +816,6 @@ class Recorder(Object):
# name...
names = actor and actor.db.alt_names
if names:
logger.info("lets change this")
msg = sub(f"^{actor.sdesc.get()}", choice(names),
msg, flags=IGNORECASE)
@ -826,6 +825,7 @@ class Recorder(Object):
# @set shrub/transcripts_path = "transcripts/bar-%Y-%m-%d.html"
filename = datetime.today().strftime(self.db.transcripts_path)
# @set shrub/header_file = "transcripts/header-template.html"
if not exists(filename):
makedirs(dirname(filename), exist_ok=True)
copyfile(self.db.header_file, filename)

View file

@ -31,7 +31,8 @@ from evennia.commands.default.system import CmdPy
from evennia.contrib.rpg.rpsystem import CmdEmote
# from commands.command import Command
from commands.everyone import CmdSay, CmdWhisper
from commands.everyone import CmdWhisper
from commands.say import CmdSay
from commands.misc import CmdLight
from commands.wizards import CmdGM
from typeclasses.objects import Listener