1
0
Fork 0

hack in a message broadcast system

This commit is contained in:
Sean Sube 2024-05-04 15:35:42 -05:00
parent ebf4ccf1c4
commit 8706badec5
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
5 changed files with 198 additions and 93 deletions

View File

@ -3,7 +3,7 @@ from logging import getLogger
from packit.utils import could_be_json
from adventure.context import get_actor_agent_for_name, get_current_context
from adventure.context import broadcast, get_actor_agent_for_name, get_current_context
logger = getLogger(__name__)
@ -16,29 +16,29 @@ def action_look(target: str) -> str:
target: The name of the target to look at.
"""
_, action_room, action_actor = get_current_context()
logger.info(f"{action_actor.name} looks at {target}")
broadcast(f"{action_actor.name} looks at {target}")
if target == action_room.name:
logger.info(f"{action_actor.name} saw the {action_room.name} room")
broadcast(f"{action_actor.name} saw the {action_room.name} room")
return action_room.description
for actor in action_room.actors:
if actor.name == target:
logger.info(
broadcast(
f"{action_actor.name} saw the {actor.name} actor in the {action_room.name} room"
)
return actor.description
for item in action_room.items:
if item.name == target:
logger.info(
broadcast(
f"{action_actor.name} saw the {item.name} item in the {action_room.name} room"
)
return item.description
for item in action_actor.items:
if item.name == target:
logger.info(
broadcast(
f"{action_actor.name} saw the {item.name} item in their inventory"
)
return item.description
@ -65,7 +65,7 @@ def action_move(direction: str) -> str:
if not destination_room:
return f"The {destination_name} room does not exist."
logger.info(f"{action_actor.name} moves {direction} to {destination_name}")
broadcast(f"{action_actor.name} moves {direction} to {destination_name}")
action_room.actors.remove(action_actor)
destination_room.actors.append(action_actor)
@ -83,7 +83,7 @@ def action_take(item_name: str) -> str:
item = next((item for item in action_room.items if item.name == item_name), None)
if item:
logger.info(f"{action_actor.name} takes the {item_name} item")
broadcast(f"{action_actor.name} takes the {item_name} item")
action_room.items.remove(item)
action_actor.items.append(item)
return "You take the {item_name} item and put it in your inventory."
@ -118,7 +118,7 @@ def action_ask(character: str, question: str) -> str:
if not question_agent:
return f"The {character} character does not exist."
logger.info(f"{action_actor.name} asks {character}: {question}")
broadcast(f"{action_actor.name} asks {character}: {question}")
answer = question_agent(
f"{action_actor.name} asks you: {question}. Reply with your response to them. "
f"Do not include the question or any JSON. Only include your answer for {action_actor.name}."
@ -128,7 +128,7 @@ def action_ask(character: str, question: str) -> str:
answer = loads(answer).get("parameters", {}).get("message", "")
if len(answer.strip()) > 0:
logger.info(f"{character} responds to {action_actor.name}: {answer}")
broadcast(f"{character} responds to {action_actor.name}: {answer}")
return f"{character} responds: {answer}"
return f"{character} does not respond."
@ -161,7 +161,7 @@ def action_tell(character: str, message: str) -> str:
if not question_agent:
return f"The {character} character does not exist."
logger.info(f"{action_actor.name} tells {character}: {message}")
broadcast(f"{action_actor.name} tells {character}: {message}")
answer = question_agent(
f"{action_actor.name} tells you: {message}. Reply with your response to them. "
f"Do not include the message or any JSON. Only include your reply to {action_actor.name}."
@ -171,7 +171,7 @@ def action_tell(character: str, message: str) -> str:
answer = loads(answer).get("parameters", {}).get("message", "")
if len(answer.strip()) > 0:
logger.info(f"{character} responds to {action_actor.name}: {answer}")
broadcast(f"{character} responds to {action_actor.name}: {answer}")
return f"{character} responds: {answer}"
return f"{character} does not respond."
@ -197,7 +197,7 @@ def action_give(character: str, item_name: str) -> str:
if not item:
return f"You do not have the {item_name} item in your inventory."
logger.info(f"{action_actor.name} gives {character} the {item_name} item")
broadcast(f"{action_actor.name} gives {character} the {item_name} item")
action_actor.items.remove(item)
destination_actor.items.append(item)
@ -218,7 +218,7 @@ def action_drop(item_name: str) -> str:
if not item:
return f"You do not have the {item_name} item in your inventory."
logger.info(f"{action_actor.name} drops the {item_name} item")
broadcast(f"{action_actor.name} drops the {item_name} item")
action_actor.items.remove(item)
action_room.items.append(item)

View File

@ -1,12 +1,13 @@
from typing import Dict, Tuple
from typing import Callable, Dict, Tuple
from packit.agent import Agent
from adventure.models import Actor
from adventure.models import Actor, Room, World
current_world = None
current_room = None
current_actor = None
current_broadcast: Callable[[str], None] | None = None
current_world: World | None = None
current_room: Room | None = None
current_actor: Actor | None = None
current_step = 0
@ -41,6 +42,20 @@ def get_current_actor():
return current_actor
def get_current_broadcast():
return current_broadcast
def broadcast(message):
if current_broadcast:
current_broadcast(message)
def set_current_broadcast(broadcast):
global current_broadcast
current_broadcast = broadcast
def set_current_world(world):
global current_world
current_world = world

View File

@ -1,6 +1,6 @@
from logging import getLogger
from random import choice, randint
from typing import List
from typing import Callable, List
from packit.agent import Agent
@ -9,7 +9,9 @@ from adventure.models import Actor, Item, Room, World
logger = getLogger(__name__)
def generate_room(agent: Agent, world_theme: str, existing_rooms: List[str]) -> Room:
def generate_room(
agent: Agent, world_theme: str, existing_rooms: List[str], callback
) -> Room:
name = agent(
"Generate one room, area, or location that would make sense in the world of {world_theme}. "
"Only respond with the room name, do not include the description or any other text. "
@ -17,7 +19,7 @@ def generate_room(agent: Agent, world_theme: str, existing_rooms: List[str]) ->
world_theme=world_theme,
existing_rooms=existing_rooms,
)
logger.info(f"Generating room: {name}")
callback(f"Generating room: {name}")
desc = agent(
"Generate a detailed description of the {name} area. What does it look like? "
"What does it smell like? What can be seen or heard?",
@ -36,9 +38,10 @@ def generate_room(agent: Agent, world_theme: str, existing_rooms: List[str]) ->
def generate_item(
agent: Agent,
world_theme: str,
existing_items: List[str],
callback,
dest_room: str | None = None,
dest_actor: str | None = None,
existing_items: List[str] = [],
) -> Item:
if dest_actor:
dest_note = "The item will be held by the {dest_actor} character"
@ -56,7 +59,7 @@ def generate_item(
existing_items=existing_items,
world_theme=world_theme,
)
logger.info(f"Generating item: {name}")
callback(f"Generating item: {name}")
desc = agent(
"Generate a detailed description of the {name} item. What does it look like? What is it made of? What does it do?",
name=name,
@ -68,7 +71,7 @@ def generate_item(
def generate_actor(
agent: Agent, world_theme: str, dest_room: str, existing_actors: List[str] = []
agent: Agent, world_theme: str, dest_room: str, existing_actors: List[str], callback
) -> Actor:
name = agent(
"Generate one person or creature that would make sense in the world of {world_theme}. The character will be placed in the {dest_room} room. "
@ -79,7 +82,7 @@ def generate_actor(
existing_actors=existing_actors,
world_theme=world_theme,
)
logger.info(f"Generating actor: {name}")
callback(f"Generating actor: {name}")
description = agent(
"Generate a detailed description of the {name} character. What do they look like? What are they wearing? "
"What are they doing? Describe their appearance from the perspective of an outside observer."
@ -106,9 +109,10 @@ def generate_world(
theme: str,
room_count: int | None = None,
max_rooms: int = 5,
callback: Callable[[str], None] = lambda x: None,
) -> World:
room_count = room_count or randint(3, max_rooms)
logger.info(f"Generating a {theme} with {room_count} rooms")
callback(f"Generating a {theme} with {room_count} rooms")
existing_actors: List[str] = []
existing_items: List[str] = []
@ -117,30 +121,48 @@ def generate_world(
rooms = []
for i in range(room_count):
existing_rooms = [room.name for room in rooms]
room = generate_room(agent, theme, existing_rooms)
room = generate_room(agent, theme, existing_rooms, callback=callback)
rooms.append(room)
item_count = randint(0, 3)
callback(f"Generating {item_count} items for room {room.name}")
for j in range(item_count):
item = generate_item(
agent, theme, dest_room=room.name, existing_items=existing_items
agent,
theme,
dest_room=room.name,
existing_items=existing_items,
callback=callback,
)
room.items.append(item)
existing_items.append(item.name)
actor_count = randint(0, 3)
callback(f"Generating {actor_count} actors for room {room.name}")
for j in range(actor_count):
actor = generate_actor(
agent, theme, dest_room=room.name, existing_actors=existing_actors
agent,
theme,
dest_room=room.name,
existing_actors=existing_actors,
callback=callback,
)
room.actors.append(actor)
existing_actors.append(actor.name)
# generate the actor's inventory
item_count = randint(0, 3)
callback(f"Generating {item_count} items for actor {actor.name}")
for k in range(item_count):
item = generate_item(
agent, theme, dest_room=room.name, existing_items=existing_items
agent,
theme,
dest_room=room.name,
existing_items=existing_items,
callback=callback,
)
actor.items.append(item)
existing_items.append(item.name)

View File

@ -1,5 +1,6 @@
from importlib import import_module
from json import load
from logging.config import dictConfig
from os import environ, path
from typing import Callable, Dict, Sequence, Tuple
@ -10,6 +11,23 @@ from packit.results import multi_function_or_str_result
from packit.toolbox import Toolbox
from packit.utils import logger_with_colors
from adventure.context import set_current_broadcast
# Configure logging
LOG_PATH = "logging.json"
# LOG_PATH = "dev-logging.json"
try:
if path.exists(LOG_PATH):
with open(LOG_PATH, "r") as f:
config_logging = load(f)
dictConfig(config_logging)
else:
print("logging config not found")
except Exception as err:
print("error loading logging config: %s" % (err))
if True:
from adventure.actions import (
action_ask,
action_give,
@ -32,7 +50,7 @@ from adventure.generate import generate_world
from adventure.models import Actor, Room, World, WorldState
from adventure.state import create_agents, save_world, save_world_state
logger = logger_with_colors(__name__)
logger = logger_with_colors(__name__, level="INFO")
load_dotenv(environ.get("ADVENTURE_ENV", ".env"), override=True)
@ -65,12 +83,21 @@ def simulate_world(
systems: Sequence[
Tuple[Callable[[World, int], None], Callable[[Dict[str, str]], str] | None]
] = [],
event_callbacks: Sequence[Callable[[str], None]] = [],
input_callbacks: Sequence[Callable[[Room, Actor, str], None]] = [],
result_callbacks: Sequence[Callable[[Room, Actor, str], None]] = [],
):
logger.info("Simulating the world")
set_current_world(world)
# set up a broadcast callback
def broadcast_callback(message):
logger.info(message)
for callback in event_callbacks:
callback(message)
set_current_broadcast(broadcast_callback)
# build a toolbox for the actions
action_tools = Toolbox(
[
@ -222,6 +249,25 @@ def main():
if args.player:
players.append(args.player)
# set up callbacks
event_callbacks = []
input_callbacks = []
result_callbacks = []
if args.server:
from adventure.server import (
launch_server,
server_action,
server_event,
server_result,
server_system,
)
launch_server()
event_callbacks.append(server_event)
input_callbacks.append(server_action)
result_callbacks.append(server_result)
memory = {}
if path.exists(world_state_file):
logger.info(f"Loading world state from {world_state_file}")
@ -246,12 +292,23 @@ def main():
{},
llm,
)
world = None
def broadcast_callback(message):
logger.info(message)
for callback in event_callbacks:
callback(message)
if args.server and world:
server_system(world, 0)
world = generate_world(
agent,
args.world,
args.theme,
room_count=args.rooms,
max_rooms=args.max_rooms,
callback=broadcast_callback,
)
save_world(world, world_file)
@ -281,22 +338,9 @@ def main():
)
extra_systems.append(module_systems)
# make sure the server system is last
input_callbacks = []
result_callbacks = []
# make sure the server system runs after any updates
if args.server:
from adventure.server import (
launch_server,
server_input,
server_result,
server_system,
)
launch_server()
extra_systems.append((server_system, None))
input_callbacks.append(server_input)
result_callbacks.append(server_result)
# start the sim
logger.debug("Simulating world: %s", world)

View File

@ -1,60 +1,75 @@
import asyncio
from collections import deque
from json import dumps
from logging import getLogger
from threading import Thread
import websockets
from flask import Flask, send_from_directory
from adventure.models import Actor, Room, World
from adventure.state import snapshot_world, world_json
logger = getLogger(__name__)
app = Flask(__name__)
connected = set()
@app.route("/<path:page>")
def send_report(page: str):
print(f"Sending {page}")
return send_from_directory(
"/home/ssube/code/github/ssube/llm-adventure/web-ui", page
)
recent_events = deque(maxlen=10)
recent_world = None
async def handler(websocket):
logger.info("Client connected")
connected.add(websocket)
try:
if recent_world:
await websocket.send(recent_world)
for message in recent_events:
await websocket.send(message)
except Exception:
logger.exception("Failed to send recent messages to new client")
while True:
try:
# await websocket.wait_closed()
message = await websocket.recv()
print(message)
except websockets.ConnectionClosedOK:
break
connected.remove(websocket)
logger.info("Client disconnected")
socket_thread = None
static_thread = None
def server_json(obj):
if isinstance(obj, Actor):
return obj.name
if isinstance(obj, Room):
return obj.name
return world_json(obj)
def send_and_append(message):
json_message = dumps(message, default=server_json)
recent_events.append(json_message)
websockets.broadcast(connected, json_message)
return json_message
def launch_server():
global socket_thread, static_thread
def run_sockets():
asyncio.run(server_main())
def run_static():
app.run(port=8000)
socket_thread = Thread(target=run_sockets)
socket_thread.start()
static_thread = Thread(target=run_static)
static_thread.start()
async def server_main():
async with websockets.serve(handler, "", 8001):
@ -63,28 +78,37 @@ async def server_main():
def server_system(world: World, step: int):
global recent_world
json_state = {
**snapshot_world(world, step),
"type": "world",
}
websockets.broadcast(connected, dumps(json_state, default=world_json))
recent_world = send_and_append(json_state)
def server_result(room: Room, actor: Actor, action: str):
json_action = {
"actor": actor.name,
"actor": actor,
"result": action,
"room": room.name,
"room": room,
"type": "result",
}
websockets.broadcast(connected, dumps(json_action))
send_and_append(json_action)
def server_input(room: Room, actor: Actor, message: str):
def server_action(room: Room, actor: Actor, message: str):
json_input = {
"actor": actor.name,
"actor": actor,
"input": message,
"room": room.name,
"type": "input",
"room": room,
"type": "action",
}
websockets.broadcast(connected, dumps(json_input))
send_and_append(json_input)
def server_event(message: str):
json_broadcast = {
"message": message,
"type": "event",
}
send_and_append(json_broadcast)