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. 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, 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 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. 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. 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.
If the bartender makes a drink, respond on a line alone with the prefix: |make followed by the name of the drink.
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. 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,9 +446,11 @@ 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.") 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: # 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) self.location.other_leave(self)
for puppet in self.puppets_here(): for puppet in self.puppets_here():
if puppet != self:
puppet.other_leave(self) puppet.other_leave(self)
return super().at_pre_move(destination) return super().at_pre_move(destination)

View file

@ -9,7 +9,7 @@ from os import listdir, path
# from os.path import join, isfile # from os.path import join, isfile
from pathlib import Path from pathlib import Path
from random import choice from random import choice
from re import match, search, split, sub from re import match, search, split, sub, IGNORECASE
from time import sleep from time import sleep
from evennia.utils import logger, delay from evennia.utils import logger, delay
@ -45,8 +45,14 @@ class ChatBot(Puppet):
short_history = [] short_history = []
def pop_recent_events(self): def pop_recent_events(self, speech):
history = "\n\n".join(self.short_history) if self.short_history else None 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 = [] self.short_history = []
return history return history
@ -132,13 +138,20 @@ class ChatBot(Puppet):
history_file.write_text(json.dumps(messages, indent=2)) history_file.write_text(json.dumps(messages, indent=2))
def think(self, speaker, speech): 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) system_prompt = self.setting_and_backstory(speaker)
messages = self.history(speaker) messages = self.history(speaker)
recent_events = self.pop_recent_events() recent_events = self.pop_recent_events(speech)
if recent_events: 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}) messages.append({"role": "user", "content": speech})
logger.info("Calling out to Anthropic...")
# Get reply # Get reply
client = anthropic.Anthropic() client = anthropic.Anthropic()
response = client.messages.create( response = client.messages.create(
@ -166,14 +179,24 @@ class ChatBot(Puppet):
reply = self.think(speaker, speech) reply = self.think(speaker, speech)
logger.info(f"My reply will be: {reply}") logger.info(f"My reply will be: {reply}")
paragraphs = reply.split('\n\n') paragraphs = reply.split('\n\n')
for idx, paragraph in enumerate(paragraphs): 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): def at_msg_receive(self, text=None, from_obj=None, **kwargs):
super().at_msg_receive(text, from_obj=from_obj, **kwargs) super().at_msg_receive(text, from_obj=from_obj, **kwargs)
logger.info(f"at_msg_receive: {text} ::from {from_obj} :: {self.key}")
if from_obj != self:
msg = text if isinstance(text, str) else text[0] msg = text if isinstance(text, str) else text[0]
# Strip out the colored formatting (we will only strip the simple stuff): # Strip out the colored formatting (we will only strip the
# simple stuff):
msg = sub(r'\|[a-zA-Z]', '', msg) if msg else msg msg = sub(r'\|[a-zA-Z]', '', msg) if msg else msg
if from_obj: if from_obj:
@ -189,6 +212,26 @@ class ChatBot(Puppet):
self.short_history = self.short_history[-10:] self.short_history = self.short_history[-10:]
return True 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): class Witch(ChatBot):
""" """
@update Trampoli = typeclasses.chatbots.Witch @update Trampoli = typeclasses.chatbots.Witch

View file

@ -816,7 +816,6 @@ class Recorder(Object):
# name... # name...
names = actor and actor.db.alt_names names = actor and actor.db.alt_names
if names: if names:
logger.info("lets change this")
msg = sub(f"^{actor.sdesc.get()}", choice(names), msg = sub(f"^{actor.sdesc.get()}", choice(names),
msg, flags=IGNORECASE) msg, flags=IGNORECASE)
@ -826,6 +825,7 @@ class Recorder(Object):
# @set shrub/transcripts_path = "transcripts/bar-%Y-%m-%d.html" # @set shrub/transcripts_path = "transcripts/bar-%Y-%m-%d.html"
filename = datetime.today().strftime(self.db.transcripts_path) filename = datetime.today().strftime(self.db.transcripts_path)
# @set shrub/header_file = "transcripts/header-template.html"
if not exists(filename): if not exists(filename):
makedirs(dirname(filename), exist_ok=True) makedirs(dirname(filename), exist_ok=True)
copyfile(self.db.header_file, filename) 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 evennia.contrib.rpg.rpsystem import CmdEmote
# from commands.command import Command # 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.misc import CmdLight
from commands.wizards import CmdGM from commands.wizards import CmdGM
from typeclasses.objects import Listener from typeclasses.objects import Listener