hack in a message broadcast system
This commit is contained in:
parent
ebf4ccf1c4
commit
8706badec5
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue