2024-05-26 20:59:12 +00:00
|
|
|
from functools import partial
|
|
|
|
from json import loads
|
|
|
|
from logging import getLogger
|
|
|
|
from typing import List
|
|
|
|
|
|
|
|
from packit.agent import Agent
|
2024-05-26 21:18:48 +00:00
|
|
|
from packit.conditions import condition_or, condition_threshold, make_flag_condition
|
2024-05-26 20:59:12 +00:00
|
|
|
from packit.results import multi_function_or_str_result
|
|
|
|
from packit.utils import could_be_json
|
|
|
|
|
2024-06-01 19:46:11 +00:00
|
|
|
from taleweave.context import broadcast, get_game_config
|
2024-05-27 13:10:24 +00:00
|
|
|
from taleweave.models.entity import Character, Room
|
|
|
|
from taleweave.models.event import ReplyEvent
|
2024-06-06 00:05:21 +00:00
|
|
|
from taleweave.utils.template import format_str
|
2024-05-26 20:59:12 +00:00
|
|
|
|
2024-05-27 02:33:44 +00:00
|
|
|
from .string import and_list, normalize_name
|
2024-05-26 20:59:12 +00:00
|
|
|
|
|
|
|
logger = getLogger(__name__)
|
|
|
|
|
|
|
|
|
2024-06-08 03:22:12 +00:00
|
|
|
def make_keyword_condition(
|
|
|
|
end_message: str,
|
|
|
|
keywords=["end", "stop"],
|
|
|
|
result_parser=multi_function_or_str_result,
|
|
|
|
):
|
2024-05-26 20:59:12 +00:00
|
|
|
set_end, condition_end = make_flag_condition()
|
|
|
|
|
2024-06-08 03:22:12 +00:00
|
|
|
def inner_parser(value, **kwargs):
|
2024-05-26 20:59:12 +00:00
|
|
|
normalized_value = normalize_name(value)
|
|
|
|
if normalized_value in keywords:
|
|
|
|
logger.debug(f"found keyword, setting stop condition: {normalized_value}")
|
|
|
|
set_end()
|
|
|
|
return end_message
|
|
|
|
|
2024-05-26 21:18:48 +00:00
|
|
|
for keyword in keywords:
|
|
|
|
if keyword == normalized_value:
|
|
|
|
logger.debug(
|
|
|
|
f"found keyword, setting stop condition: {normalized_value}"
|
|
|
|
)
|
|
|
|
set_end()
|
|
|
|
return end_message
|
|
|
|
|
|
|
|
if normalized_value.endswith(keyword):
|
|
|
|
logger.debug(
|
|
|
|
f"found keyword at end of string, setting stop condition: {normalized_value}"
|
|
|
|
)
|
|
|
|
set_end()
|
|
|
|
return value[: -len(keyword)].strip()
|
|
|
|
|
|
|
|
keyword_function = f'"function": "{keyword}"'
|
|
|
|
if could_be_json(normalized_value) and keyword_function in normalized_value:
|
|
|
|
logger.debug(
|
|
|
|
f"found keyword function, setting stop condition: {normalized_value}"
|
|
|
|
)
|
|
|
|
set_end()
|
|
|
|
return end_message
|
2024-05-26 20:59:12 +00:00
|
|
|
|
2024-06-08 03:22:12 +00:00
|
|
|
return result_parser(value, **kwargs)
|
2024-05-26 20:59:12 +00:00
|
|
|
|
2024-06-08 03:22:12 +00:00
|
|
|
return set_end, condition_end, inner_parser
|
2024-05-26 20:59:12 +00:00
|
|
|
|
|
|
|
|
2024-05-27 01:32:03 +00:00
|
|
|
def summarize_room(room: Room, player: Character) -> str:
|
2024-05-26 20:59:12 +00:00
|
|
|
"""
|
|
|
|
Summarize a room for the player.
|
|
|
|
"""
|
|
|
|
|
2024-05-27 01:32:03 +00:00
|
|
|
character_names = and_list(
|
|
|
|
[
|
|
|
|
character.name
|
|
|
|
for character in room.characters
|
|
|
|
if character.name != player.name
|
|
|
|
]
|
2024-05-26 20:59:12 +00:00
|
|
|
)
|
|
|
|
item_names = and_list([item.name for item in room.items])
|
|
|
|
inventory_names = and_list([item.name for item in player.items])
|
|
|
|
|
|
|
|
return (
|
2024-05-27 01:32:03 +00:00
|
|
|
f"You are in the {room.name} room with {character_names}. "
|
2024-05-26 20:59:12 +00:00
|
|
|
f"You see the {item_names} around the room. "
|
|
|
|
f"You are carrying the {inventory_names}."
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def loop_conversation(
|
|
|
|
room: Room,
|
2024-05-27 01:32:03 +00:00
|
|
|
characters: List[Character],
|
2024-05-26 20:59:12 +00:00
|
|
|
agents: List[Agent],
|
2024-05-27 01:32:03 +00:00
|
|
|
first_character: Character,
|
2024-05-26 20:59:12 +00:00
|
|
|
first_prompt: str,
|
|
|
|
reply_prompt: str,
|
|
|
|
first_message: str,
|
|
|
|
end_message: str,
|
|
|
|
echo_function: str | None = None,
|
|
|
|
echo_parameter: str | None = None,
|
|
|
|
max_length: int | None = None,
|
|
|
|
) -> str | None:
|
|
|
|
"""
|
2024-05-27 01:32:03 +00:00
|
|
|
Loop through a conversation between a series of agents, using metadata from their characters.
|
2024-05-26 20:59:12 +00:00
|
|
|
"""
|
|
|
|
|
2024-06-01 19:46:11 +00:00
|
|
|
config = get_game_config()
|
|
|
|
|
2024-05-26 20:59:12 +00:00
|
|
|
if max_length is None:
|
2024-06-01 19:46:11 +00:00
|
|
|
max_length = config.world.character.conversation_limit
|
2024-05-26 20:59:12 +00:00
|
|
|
|
2024-05-27 01:32:03 +00:00
|
|
|
if len(characters) != len(agents):
|
|
|
|
raise ValueError("The number of characters and agents must match.")
|
2024-05-26 20:59:12 +00:00
|
|
|
|
2024-05-26 22:03:39 +00:00
|
|
|
# set up the keyword or length-limit compound condition
|
2024-05-26 20:59:12 +00:00
|
|
|
_, condition_end, parse_end = make_keyword_condition(end_message)
|
|
|
|
stop_length = partial(condition_threshold, max=max_length)
|
2024-05-26 21:18:48 +00:00
|
|
|
stop_condition = condition_or(condition_end, stop_length)
|
2024-05-26 20:59:12 +00:00
|
|
|
|
2024-05-26 22:03:39 +00:00
|
|
|
# prepare a result parser looking for the echo function
|
2024-05-26 20:59:12 +00:00
|
|
|
def result_parser(value: str, **kwargs) -> str:
|
|
|
|
value = parse_end(value, **kwargs)
|
|
|
|
|
|
|
|
if condition_end():
|
|
|
|
return value
|
|
|
|
|
|
|
|
if echo_function and could_be_json(value) and echo_function in value:
|
|
|
|
value = loads(value).get("parameters", {}).get(echo_parameter, "")
|
|
|
|
|
|
|
|
return value.strip()
|
|
|
|
|
2024-05-26 22:03:39 +00:00
|
|
|
# prepare the loop state
|
2024-05-26 20:59:12 +00:00
|
|
|
i = 0
|
2024-05-27 01:32:03 +00:00
|
|
|
last_character = first_character
|
2024-05-26 20:59:12 +00:00
|
|
|
response = first_message
|
|
|
|
|
|
|
|
while not stop_condition(current=i):
|
|
|
|
if i == 0:
|
2024-05-27 01:32:03 +00:00
|
|
|
logger.debug(f"starting conversation with {first_character.name}")
|
2024-05-26 20:59:12 +00:00
|
|
|
prompt = first_prompt
|
|
|
|
else:
|
2024-05-27 01:32:03 +00:00
|
|
|
logger.debug(
|
|
|
|
f"continuing conversation with {last_character.name} on step {i}"
|
|
|
|
)
|
2024-05-26 20:59:12 +00:00
|
|
|
prompt = reply_prompt
|
|
|
|
|
2024-05-27 01:32:03 +00:00
|
|
|
# loop through the characters and agents
|
|
|
|
character = characters[i % len(characters)]
|
2024-05-26 20:59:12 +00:00
|
|
|
agent = agents[i % len(agents)]
|
|
|
|
|
|
|
|
# summarize the room and present the last response
|
2024-05-27 01:32:03 +00:00
|
|
|
summary = summarize_room(room, character)
|
2024-05-26 20:59:12 +00:00
|
|
|
response = agent(
|
2024-05-31 23:58:01 +00:00
|
|
|
format_str(
|
|
|
|
prompt,
|
|
|
|
response=response,
|
|
|
|
summary=summary,
|
|
|
|
last_character=last_character,
|
|
|
|
)
|
2024-05-26 20:59:12 +00:00
|
|
|
)
|
|
|
|
response = result_parser(response)
|
2024-05-26 21:18:48 +00:00
|
|
|
|
2024-05-27 01:32:03 +00:00
|
|
|
logger.info(f"{character.name} responds: {response}")
|
2024-05-29 00:55:32 +00:00
|
|
|
reply_event = ReplyEvent(room, character, last_character, response)
|
2024-05-26 22:03:39 +00:00
|
|
|
broadcast(reply_event)
|
2024-05-26 20:59:12 +00:00
|
|
|
|
|
|
|
# increment the step counter
|
|
|
|
i += 1
|
2024-05-27 01:32:03 +00:00
|
|
|
last_character = character
|
2024-05-26 20:59:12 +00:00
|
|
|
|
2024-05-31 23:58:01 +00:00
|
|
|
return format_str(end_message, response=response, last_character=last_character)
|