From 0fac5f9d26012140352becc49d35fcc94e7118a8 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 15 Jul 2023 00:46:22 +0200 Subject: [PATCH] Made LLM NPCs talk so everyone in room can hear it --- docs/source/Contribs/Contrib-Llm.md | 23 +++++++++++++++++---- evennia/contrib/rpg/llm/README.md | 23 +++++++++++++++++---- evennia/contrib/rpg/llm/llm_npc.py | 32 +++++++++++++++++++++-------- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/docs/source/Contribs/Contrib-Llm.md b/docs/source/Contribs/Contrib-Llm.md index 8d3a34b0a9..df2a099d3a 100644 --- a/docs/source/Contribs/Contrib-Llm.md +++ b/docs/source/Contribs/Contrib-Llm.md @@ -4,6 +4,19 @@ Contribution by Griatch 2023 This adds an LLMClient that allows Evennia to send prompts to a LLM server (Large Language Model, along the lines of ChatGPT). Example uses a local OSS LLM install. Included is an NPC you can chat with using a new `talk` command. The NPC will respond using the AI responses from the LLM server. All calls are asynchronous, so if the LLM is slow, Evennia is not affected. +``` +> create/drop villager:evennia.contrib.rpg.llm.LLMNPC +You create a new LLMNPC: villager + +> talk villager Hello there friend, what's up? +You say (to villager): Hello there friend, what's up? +villager says (to You): Hello! Not much going on, really. How about you? + +> talk villager Just enjoying the nice weather. +You say (to villager): Just enjoying the nice weather. +villager says (to You): Yeah, it is really quite nice, ain't it. +``` + ## Installation You need two components for this contrib - Evennia, and an LLM webserver that operates and provides an API to an LLM AI model. @@ -84,13 +97,14 @@ With the LLM server running and the new `talk` command added, create a new LLM-c > create/drop girl:evennia.contrib.rpg.llm.LLMNPC > talk girl Hello! + You say (to girl): Hello girl ponders ... - girl says, Hello! How are you? - -The NPC will show a 'thinking' message if the server responds slower than 2 seconds (by default). + girl says (to You): Hello! How are you? Most likely, your first response will *not* be this nice and short, but will be quite nonsensical, looking like an email. This is because the example model we loaded is not optimized for conversations. But at least you know it works! +The conversation will be echoed to everyone in the room. The NPC will show a thinking/pondering message if the server responds slower than 2 seconds (by default). + ## A note on running LLMs locally Running an LLM locally can be _very_ demanding. @@ -114,7 +128,8 @@ Calling an external API is not tested, so report any findings. Since the Evennia This is a simple Character class, with a few extra properties: ```python - response_template = "{name} says: {response}" + # response template on msg_contents form. + response_template = "$You() $conj(say) (to $You(character)): {response}" thinking_timeout = 2 # how long to wait until showing thinking # random 'thinking echoes' to return while we wait, if the AI is slow diff --git a/evennia/contrib/rpg/llm/README.md b/evennia/contrib/rpg/llm/README.md index c7013b96cc..65ad3d09ee 100644 --- a/evennia/contrib/rpg/llm/README.md +++ b/evennia/contrib/rpg/llm/README.md @@ -4,6 +4,19 @@ Contribution by Griatch 2023 This adds an LLMClient that allows Evennia to send prompts to a LLM server (Large Language Model, along the lines of ChatGPT). Example uses a local OSS LLM install. Included is an NPC you can chat with using a new `talk` command. The NPC will respond using the AI responses from the LLM server. All calls are asynchronous, so if the LLM is slow, Evennia is not affected. +``` +> create/drop villager:evennia.contrib.rpg.llm.LLMNPC +You create a new LLMNPC: villager + +> talk villager Hello there friend, what's up? +You say (to villager): Hello there friend, what's up? +villager says (to You): Hello! Not much going on, really. How about you? + +> talk villager Just enjoying the nice weather. +You say (to villager): Just enjoying the nice weather. +villager says (to You): Yeah, it is really quite nice, ain't it. +``` + ## Installation You need two components for this contrib - Evennia, and an LLM webserver that operates and provides an API to an LLM AI model. @@ -84,13 +97,14 @@ With the LLM server running and the new `talk` command added, create a new LLM-c > create/drop girl:evennia.contrib.rpg.llm.LLMNPC > talk girl Hello! + You say (to girl): Hello girl ponders ... - girl says, Hello! How are you? - -The NPC will show a 'thinking' message if the server responds slower than 2 seconds (by default). + girl says (to You): Hello! How are you? Most likely, your first response will *not* be this nice and short, but will be quite nonsensical, looking like an email. This is because the example model we loaded is not optimized for conversations. But at least you know it works! +The conversation will be echoed to everyone in the room. The NPC will show a thinking/pondering message if the server responds slower than 2 seconds (by default). + ## A note on running LLMs locally Running an LLM locally can be _very_ demanding. @@ -114,7 +128,8 @@ Calling an external API is not tested, so report any findings. Since the Evennia This is a simple Character class, with a few extra properties: ```python - response_template = "{name} says: {response}" + # response template on msg_contents form. + response_template = "$You() $conj(say) (to $You(character)): {response}" thinking_timeout = 2 # how long to wait until showing thinking # random 'thinking echoes' to return while we wait, if the AI is slow diff --git a/evennia/contrib/rpg/llm/llm_npc.py b/evennia/contrib/rpg/llm/llm_npc.py index bd1ee08f38..3093e2f798 100644 --- a/evennia/contrib/rpg/llm/llm_npc.py +++ b/evennia/contrib/rpg/llm/llm_npc.py @@ -24,7 +24,7 @@ class LLMNPC(DefaultCharacter): """An NPC that uses the LLM server to generate its responses. If the server is slow, it will echo a thinking message to the character while it waits for a response.""" - response_template = "{name} says: {response}" + response_template = "$You() $conj(say) (to $You(character)): {response}" thinking_timeout = 2 # seconds thinking_messages = [ "{name} thinks about what you said ...", @@ -49,16 +49,30 @@ class LLMNPC(DefaultCharacter): # abort the thinking message if we were fast enough thinking_defer.cancel() - character.msg( - self.response_template.format( - name=self.get_display_name(character), response=response - ) + response = self.response_template.format( + name=self.get_display_name(character), response=response ) + if character.location: + character.location.msg_contents( + response, + mapping={"character": character}, + from_obj=self, + ) + else: + # fallback if character is not in a location + character.msg(f"{self.get_display_name(character)} says, {response}") def _echo_thinking_message(): """Echo a random thinking message to the character""" - thinking_messages = make_iter(self.db.thinking_messages or self.thinking_messages) - character.msg(choice(thinking_messages).format(name=self.get_display_name(character))) + thinking_message = choice( + make_iter(self.db.thinking_messages or self.thinking_messages) + ) + if character.location: + thinking_message = thinking_message.format(name="$You()") + character.location.msg_contents(thinking_message, from_obj=self) + else: + thinking_message = thinking_message.format(name=self.get_display_name(character)) + character.msg(thinking_message) # if response takes too long, note that the NPC is thinking. thinking_defer = task.deferLater(reactor, self.thinking_timeout, _echo_thinking_message) @@ -99,8 +113,8 @@ class CmdLLMTalk(Command): return if location: location.msg_contents( - f'$You() talk to $You({target.key}), saying "{self.speech}"', - mapping={target.key: target}, + f"$You() $conj(say) (to $You(target)): {self.speech}", + mapping={"target": target}, from_obj=self.caller, ) if hasattr(target, "at_talked_to"):