From 728dc3de0d517c2fc1a5a5192c1591e2daf5917c Mon Sep 17 00:00:00 2001 From: Ivan Date: Wed, 13 Mar 2024 01:44:28 +0800 Subject: [PATCH 01/40] added dead.png for crossbones --- web/public/images/icons/dead.png | Bin 0 -> 3158 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 web/public/images/icons/dead.png diff --git a/web/public/images/icons/dead.png b/web/public/images/icons/dead.png new file mode 100644 index 0000000000000000000000000000000000000000..0b4f16ee7ac41ddc42ad9d6728159e9cca14229f GIT binary patch literal 3158 zcmb_eZB!Fy79PX`1(i^sP(B1fHWUfTgpiOJKp-H9t*9VMgfb)(42A?V6A}TD4?lu{ z?V+L~2ox=10I332R19q)(ltUY6*Xc?6=6k$3fhPwY(gy+y1PGiH|I=d=DGJi@4e5x z?|UXU{NvS&jctqp09YIv!q}j_6Lgo6f%ZRY!Nh*;?cL;%EeHTCY13VLMJtxrYQdFg z&}KAHkdy@TQ9vT3v1ttdU|eEY_}Ua6Li2P_q!B?9l|uIP@}z(i0)<2+ds5v|8j(cs zB#=O2TUk&i04yBhMr=kmf3%jy5+r#*Y(YHiAxTQs@&f=}LeoAb!6@V?N#gMln#2b? z<3ZCt>tHZhjR)})e6SK68|FYF9*Xsir>mmrV(7Q+lOmlXdmmY#8YtJ42c1b-A2a^q)(;3FQ;3?kC0=b>#Z9%AdH zAk2~C_b8e_8+j{nT_qdg9rF;a7&=2TBt+ndoyo8db_0wEL_!w)_5{2k3>3l;3bTEQ z1R@DX@Wc^Fbo@NiY!m5k3_%h!lzvKJL3ffTBjH(@qr#`u?z2!V*TO;?sLan|!s>`(W9)8@Hy z{ZZ;%S^rOsPJU}w>AF0#U3LA{8npfOoYP+|M)Mb;+XO=2&=iR87y$7^J4Gm6=lq9Z zUUC2XC~6OkIdeFhp#)0)%LHnjX*1Iz5Jm_DJl{Zwp90bG^Wp!LQ-GmfHvu z@-5X5OTqXJryA(rk~Ke z%1`->e5?TEb z-`x&mnE1R@Y;RIs1_x4WZl)$J0*nRSR-Mf!zVPx+v}Scs#pn+gerz4f6EHf;KI(q9 z*Ztbkh{)3^(c3lpk`5}xurC%6>$xs|AGoEr6&`>iTPkFBPUTgPOaPAai^i;P3!=CC zjg+Q=@$UidVjHM#mmC>cnAug)_1+>^d8;x{{YoCy(baAgH?U4JiXYVjBCqFz^|un6 zELwL1lV!!_E|nhw8;hDpn|C}=Tmz=4US1;JsJcaNKq$xz8_59yhvk*^HY^31Liq$# zC@n`C+!FQ{%l#QifOx@+8mV0jlX=PLS#H~^UuznJ`oGdlZ8%mnsAp%Fjrwi<`?=xq z9#swleV;PjFRXWA#;@}`T~bm)iFlsg5+z`a-0+H=4t@Q@ z;g&rr)0Km%?_6A8ab1b1Uq!L7iyEGWOG=ozPazS*HmXBu5bbQs#%jktw#VE}V>aTjd4dtGq+rst0>8mL2)v z1MV^Yrn^CGuxorY>TX?=#(OX$uVnj#jpy#3wB2KONcdj~h80m}w6_boeJoNZ8zbB<>3+{!+CpT!m z4Ij)}9$MyHiFMvz&)$;a2b|=-v|>~_ISGkf#hj^S znU;|;8zay5bgmqnYP9`LnJ(94+_4T+9DNONjw9=Ssk&;j*;Y06WqVVn(|Hf8!^IQX z$Vez^6@BVBlCb8Rpu5ixPVJM{Vs1D;Orx3tjDC{Y^%M;6u=#Q1)Y%3PT2$My&pVs< zW?#D~Ep@HGqyQ>hAK~;am@=W)pCKc5-$vfg_~hXM*_13D8gSvLuRL~pc_spTT4v#} z9L*&%lef5DxVM@S)}#6~$+oXF&7A=5sTTgsC$viKU7cQ}*h!zdtynfDQ!Nayl7st- zSRU^qE8(Kxwuos(R=hbjbc|s3y7K339N*!V(GT)zl?ivP8-TbCkw@~bPyt)n#R#`e zH_MK=^{4_nn;Txgkaipyr~5TL*;}}r8q2#Mn>ExsG&J-}M!@R5#l^+rx1-0G@hnPS zd{A*eIB$Co*Xbn^7mtWU&s&@aY}%Acr5xiekan!TtSRV(dy7o8b?ndmVKl~p+O0Nv zq^d47T=0FV?0|H*8j~0DE3kV7=0_JNY=9as`Q}DqQ!H6Ad16ydfxaZCSiVUedT&W% zbR7k_9KaX_ex;aA#Fh>E85h_Ots0k0rx)Da^W^zbkBl3-PxjEDj~QnI; Date: Thu, 14 Mar 2024 02:50:01 +0800 Subject: [PATCH 02/40] Created obs_record.py automatically records the simulation replay on the web browser (currently for 20 seconds) --- utils/obs_record.py | 73 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 utils/obs_record.py diff --git a/utils/obs_record.py b/utils/obs_record.py new file mode 100644 index 0000000..7fed703 --- /dev/null +++ b/utils/obs_record.py @@ -0,0 +1,73 @@ +import os +import sys +import time +from dotenv import load_dotenv +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from obswebsocket import obsws, requests + + +load_dotenv() + +# OBS Studio Websocket Settings +obs_settings = { + "obs_host": os.environ.get("OBS_HOST"), + "obs_port": int(os.environ.get("OBS_PORT")), + "obs_password": os.environ.get("OBS_PASSWORD"), + "record_path": os.environ.get("RECORD_PATH"), +} + +# This is NOT working yet +# Start recording based on a javacript event. +def start_recording_if_playing(): + is_playing = driver.execute_script("return isPlaying;") + if is_playing: + start_recording = requests.StartRecord() + ws.call(start_recording) + print("Recording started") + +# Set up Chrome options to start in full-screen mode +chrome_options = Options() +chrome_options.add_argument('--kiosk') + +# Create a WebDriver instance (make sure you have the ChromeDriver executable in your PATH) +driver = webdriver.Chrome(options=chrome_options) + +url_path = sys.argv[1] if len(sys.argv) > 1 else "https://replicantlife.com/" +driver.get(url_path) # Open a website + +# Allow some time for the page to load +time.sleep(3) + +# Connect to OBS Studio via WebSocket +ws = obsws(obs_settings["obs_host"], obs_settings["obs_port"], obs_settings["obs_password"]) +ws.connect() + +# Set the recording path in OBS Studio +recording_path = obs_settings["record_path"] +set_recording_path = requests.SetRecordDirectory(recording_path=recording_path) +ws.call(set_recording_path) + +# Check current recording status +current_recording_status = ws.call(requests.GetRecordStatus()) +print("Current recording status:", current_recording_status) + +# Start recording in OBS Studio +start_recording = requests.StartRecord() +ws.call(start_recording) + +# The function is NOT working yet. +# start_recording_if_playing() + +# Continue recording for 20s (adjust as needed) +time.sleep(20) + +# Stop recording in OBS Studio +stop_recording = requests.StopRecord() +ws.call(stop_recording) + +# Close the browser when done +driver.quit() + +# Disconnect from OBS Studio +ws.disconnect() From 85d3e31e5684c166111142fe593e3349a934e141 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 14 Mar 2024 09:55:19 -0700 Subject: [PATCH 03/40] minor cleanups --- README.md | 10 ++++++++-- engine.py | 3 --- src/matrix.py | 10 ---------- utils/obs_record.py | 8 +++++--- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 2df2db6..78bcd2f 100644 --- a/README.md +++ b/README.md @@ -216,10 +216,18 @@ When working on the main engine, often times we can shut off all llm calls, in * make all cognitive modules flags work on a user basis * normalize flag names * hybrid fast llm action / continue to destination and only act when needed +* maybe make it gym compatible # changes over time + * moving objects +* tool usage + weapons + move refrigerator + door open/close/lock + +* scribblenauts style object interactions * information spreading * building stuff * people dead/people born @@ -231,5 +239,3 @@ When working on the main engine, often times we can shut off all llm calls, in * things getting destroyed * discover actions * environmental changes - - diff --git a/engine.py b/engine.py index 28c8590..cbe602f 100755 --- a/engine.py +++ b/engine.py @@ -46,9 +46,6 @@ def main(): if args.id: config['id'] = args.id matrix = Matrix(config) - - # matrix.send_matrix_to_redis() - pd(f"model:#{MODEL}") pd("Initial Agents Positions:") #redis_connection.set(f"{matrix.id}:matrix_state", json.dumps(matrix.get_arr_2D())) diff --git a/src/matrix.py b/src/matrix.py index 058cc81..6ed8619 100644 --- a/src/matrix.py +++ b/src/matrix.py @@ -344,16 +344,6 @@ def run_singlethread(self): start_time = datetime.now() pd(f"Step {step + 1}:") - #redis_log(self.get_arr_2D(), f"{self.id}:matrix_states") - #redis_connection.set(f"{self.id}:matrix_state", json.dumps(self.get_arr_2D())) - #print_and_log(f"Step: {step + 1} | {unix_to_strftime(self.unix_time)}", f"{self.id}:agent_conversations") - - #self.log_agents_to_redis() - - #for a in self.agents: - # print_and_log(f"Step: {step + 1} | {unix_to_strftime(self.unix_time)}", f"{self.id}:events:{a.name}") - # print_and_log(f"Step: {step + 1} | {unix_to_strftime(self.unix_time)}", f"{self.id}:conversations:{a.name}") - if redis_connection: control_cmd = redis_connection.lpop(f"{self.id}:communications") if control_cmd: diff --git a/utils/obs_record.py b/utils/obs_record.py index 7fed703..d2d5995 100644 --- a/utils/obs_record.py +++ b/utils/obs_record.py @@ -11,10 +11,10 @@ # OBS Studio Websocket Settings obs_settings = { - "obs_host": os.environ.get("OBS_HOST"), - "obs_port": int(os.environ.get("OBS_PORT")), + "obs_host": os.environ.get("OBS_HOST","127.0.0.1"), + "obs_port": int(os.environ.get("OBS_PORT",4455)), "obs_password": os.environ.get("OBS_PASSWORD"), - "record_path": os.environ.get("RECORD_PATH"), + "record_path": os.environ.get("RECORD_PATH","movies"), } # This is NOT working yet @@ -29,6 +29,8 @@ def start_recording_if_playing(): # Set up Chrome options to start in full-screen mode chrome_options = Options() chrome_options.add_argument('--kiosk') +chrome_options.add_experimental_option("excludeSwitches", ['enable-automation']); + # Create a WebDriver instance (make sure you have the ChromeDriver executable in your PATH) driver = webdriver.Chrome(options=chrome_options) From 7b087150cf0c30fed17e6800bc63591423578dd6 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 14 Mar 2024 10:22:04 -0700 Subject: [PATCH 04/40] updated --- test.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test.py b/test.py index 3b612ba..c5887af 100644 --- a/test.py +++ b/test.py @@ -38,6 +38,9 @@ def test_fine_move_action_moves_away(self): matrix.llm_action(real_agent, matrix.unix_time) self.assertEqual(real_agent.x, 4) + def test_matrix_run(self): + matrix = Matrix({"environment":"configs/small.tmj"}) + def test_memory(self): agent_data = { "name": "John", From 0c379ca44b0c1ca4c3e039aef0ffcb2d02f2e373 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 15 Mar 2024 04:31:43 -0700 Subject: [PATCH 05/40] clean up scripts --- test.py | 4 +++- utils/db_insert.py | 3 +++ utils/{obs_record.py => recorder.py} | 0 3 files changed, 6 insertions(+), 1 deletion(-) rename utils/{obs_record.py => recorder.py} (100%) diff --git a/test.py b/test.py index c5887af..9562bb6 100644 --- a/test.py +++ b/test.py @@ -38,8 +38,10 @@ def test_fine_move_action_moves_away(self): matrix.llm_action(real_agent, matrix.unix_time) self.assertEqual(real_agent.x, 4) - def test_matrix_run(self): + def test_matrix_runs_step(self): matrix = Matrix({"environment":"configs/small.tmj"}) + #run for one step + self.assertEqual(matrix.status,"complete") def test_memory(self): agent_data = { diff --git a/utils/db_insert.py b/utils/db_insert.py index 7cf4b1f..edcb0f0 100644 --- a/utils/db_insert.py +++ b/utils/db_insert.py @@ -28,6 +28,7 @@ "ssh_private_key": os.environ.get("SSH_PRIVATE_KEY") } redis_url = os.environ.get("REDIS_URL") +minimal = True #TODO move this to a flag def process_and_insert_data(cursor,redis_client, jsonl_file_path,rows_to_process): with jsonlines.open(jsonl_file_path, "r") as jsonl_file: @@ -38,6 +39,8 @@ def process_and_insert_data(cursor,redis_client, jsonl_file_path,rows_to_process substep = obj.get("substep") step_type = obj.get("step_type") sim_id = obj.get("sim_id") + if minimal and step_type not in ['talk', 'agent_set', 'move', 'matrix_set', 'agent_init']: + next data = {k: v for k, v in obj.items() if k not in ["step", "substep", "step_type","sim_id","embedding"]} try: diff --git a/utils/obs_record.py b/utils/recorder.py similarity index 100% rename from utils/obs_record.py rename to utils/recorder.py From 03f9d93945e86ede63bc4d0c7b70a883f5fb6a86 Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 15 Mar 2024 22:58:46 +0800 Subject: [PATCH 06/40] fix test_matrix_run_step --- test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test.py b/test.py index 9562bb6..864b9cf 100644 --- a/test.py +++ b/test.py @@ -41,6 +41,8 @@ def test_fine_move_action_moves_away(self): def test_matrix_runs_step(self): matrix = Matrix({"environment":"configs/small.tmj"}) #run for one step + matrix.steps = 1 + matrix.run_singlethread() self.assertEqual(matrix.status,"complete") def test_memory(self): From bad5455401d3b5b5781b8a84f441d5b3c04f1961 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 15 Mar 2024 08:54:00 -0700 Subject: [PATCH 07/40] testing faster llm_action --- src/agents.py | 21 ++++++++++++++++++++- src/matrix.py | 9 ++++++++- utils/db_insert.py | 10 ++++++++-- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/agents.py b/src/agents.py index 3bb46ca..b4a430e 100644 --- a/src/agents.py +++ b/src/agents.py @@ -35,6 +35,8 @@ def __init__(self, agent_data={}): #self.actions = list(dict.fromkeys(agent_data.get("actions", []) + DEFAULT_ACTIONS)) #old one self.actions = list(dict.fromkeys(agent_data.get("actions",DEFAULT_ACTIONS ))) + self.last_perceived_data = None + self.perceived_data_history = {} self.memory = agent_data.get("memory", []) self.short_memory = agent_data.get("short_memory", []) @@ -64,6 +66,17 @@ def __init__(self, agent_data={}): if self.matrix: self.matrix.add_to_logs({"agent_id":self.mid,"step_type":"agent_init","x":self.x,"y":self.y,"name":self.name,"goal":self.goal,"kind":self.kind}) + def perceived_data_is_same(self,current_perceived_data): + last_step = self.matrix.cur_step - 1 + if last_step not in self.perceived_data_history: + return False + return self.perceived_data_history[last_step] == current_perceived_data + + def cleanup_old_perceived_data(self, current_step): + keys_to_delete = [step for step in self.perceived_data_history if step < current_step - 2] + for key in keys_to_delete: + del self.perceived_data_history[key] + def update_goals(self): # this assumes 8+ importance is always worth changing /reevaluating goals # do i need to update my goals. if so, give me new goals @@ -84,6 +97,7 @@ def update_goals(self): self.addMemory("observation",f"I updated my goal to be \"{msg}\"", unix_to_strftime(self.matrix.unix_time), random.randint(5, 8)) self.goal = msg + def decide(self): self.matrix.llm_action(self,self.matrix.unix_time) @@ -564,7 +578,12 @@ def perceive(self, other_agents, environment, timestamp): perceived_agent_ids = [agent.mid for agent in perceived_agents] if self.matrix: self.matrix.add_to_logs({"agent_id":self.mid,"step_type":"perceived","perceived_agents":perceived_agent_ids,"perceived_locations":[],"perceived_areas":[],"perceived_objects":[]}) - #missing locations,areas,objects + + perceived_data = (perceived_agents, perceived_locations, perceived_areas, perceived_objects, perceived_directions) + #self.last_perceived_data = perceived_data + self.perceived_data_history[self.matrix.cur_step] = perceived_data + self.cleanup_old_perceived_data(self.matrix.cur_step) + return perceived_agents, perceived_locations, perceived_areas, perceived_objects,perceived_directions def addMemory(self, kind, content, timestamp=None, score=None,embedding=None): diff --git a/src/matrix.py b/src/matrix.py index 6ed8619..9895372 100644 --- a/src/matrix.py +++ b/src/matrix.py @@ -441,6 +441,14 @@ def llm_action(self, agent, unix_time): if agent.status == "dead": return agent + perceived_agents, perceived_locations, perceived_areas, perceived_objects,perceived_directions = agent.perceive([a for a in self.agents if a != agent], self.environment, unix_to_strftime(self.unix_time)) + perceived_data = (perceived_agents, perceived_locations, perceived_areas, perceived_objects, perceived_directions) + + if agent.current_destination is not None and agent.perceived_data_is_same(perceived_data): + print("SKIPPED llm_action!") + agent.move({ "environment": self.environment }) + return agent + # It is 12:00, time to make plans if unix_time % 86400 == 0 and self.allow_plan_flag == 1: agent.make_plans(unix_to_strftime(unix_time)) @@ -472,7 +480,6 @@ def llm_action(self, agent, unix_time): agent.talk({ "other_agents": [agent.last_conversation.other_agent], "timestamp": unix_to_strftime(unix_time) }) return agent - perceived_agents, perceived_locations, perceived_areas, perceived_objects,perceived_directions = agent.perceive([a for a in self.agents if a != agent], self.environment, unix_to_strftime(self.unix_time)) relevant_memories = agent.getMemories(agent.goal, unix_to_strftime(unix_time)) relevant_memories_string = "\n".join(f"Memory {i + 1}:\n{memory}" for i, memory in enumerate(relevant_memories)) if relevant_memories else "" diff --git a/utils/db_insert.py b/utils/db_insert.py index edcb0f0..e4bf5db 100644 --- a/utils/db_insert.py +++ b/utils/db_insert.py @@ -29,8 +29,11 @@ } redis_url = os.environ.get("REDIS_URL") minimal = True #TODO move this to a flag +override_sim_id = None +printed = False def process_and_insert_data(cursor,redis_client, jsonl_file_path,rows_to_process): + global printed,minimal with jsonlines.open(jsonl_file_path, "r") as jsonl_file: for i, row in enumerate(jsonl_file): if rows_to_process is None or i < rows_to_process: @@ -39,8 +42,11 @@ def process_and_insert_data(cursor,redis_client, jsonl_file_path,rows_to_process substep = obj.get("substep") step_type = obj.get("step_type") sim_id = obj.get("sim_id") - if minimal and step_type not in ['talk', 'agent_set', 'move', 'matrix_set', 'agent_init']: - next + if printed == False: + print(f"SIM ID: {sim_id}") + printed = True + #if minimal and step_type not in ['talk', 'agent_set', 'move', 'matrix_set', 'agent_init']: + # next data = {k: v for k, v in obj.items() if k not in ["step", "substep", "step_type","sim_id","embedding"]} try: From ded8f963af564e0cf78029eb89ac5f22c0f0f3cd Mon Sep 17 00:00:00 2001 From: Ivan Date: Sat, 16 Mar 2024 00:21:58 +0800 Subject: [PATCH 08/40] added scenario for test matrix run step --- test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test.py b/test.py index 864b9cf..36e827f 100644 --- a/test.py +++ b/test.py @@ -39,10 +39,12 @@ def test_fine_move_action_moves_away(self): self.assertEqual(real_agent.x, 4) def test_matrix_runs_step(self): - matrix = Matrix({"environment":"configs/small.tmj"}) + matrix = Matrix({"scenario":"configs/empty.json","environment":"configs/largev2.tmj"}) #run for one step matrix.steps = 1 + matrix.boot() matrix.run_singlethread() + self.assertTrue(len(matrix.agents) > 0) self.assertEqual(matrix.status,"complete") def test_memory(self): From 8116c2e76424da14540e55532ddaab3aee5e61fa Mon Sep 17 00:00:00 2001 From: jtoy <> Date: Fri, 15 Mar 2024 16:22:40 +0000 Subject: [PATCH 09/40] refactor perceived --- src/agents.py | 4 +++- src/matrix.py | 3 +-- utils/utils.py | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/agents.py b/src/agents.py index b4a430e..a8ec00a 100644 --- a/src/agents.py +++ b/src/agents.py @@ -66,10 +66,12 @@ def __init__(self, agent_data={}): if self.matrix: self.matrix.add_to_logs({"agent_id":self.mid,"step_type":"agent_init","x":self.x,"y":self.y,"name":self.name,"goal":self.goal,"kind":self.kind}) - def perceived_data_is_same(self,current_perceived_data): + def perceived_data_is_same(self): last_step = self.matrix.cur_step - 1 if last_step not in self.perceived_data_history: return False + perceived_agents, perceived_locations, perceived_areas, perceived_objects,perceived_directions = self.perceive([a for a in self.matrix.agents if a != self], self.matrix.environment, unix_to_strftime(self.matrix.unix_time)) + current_perceived_data = (perceived_agents, perceived_locations, perceived_areas, perceived_objects, perceived_directions) return self.perceived_data_history[last_step] == current_perceived_data def cleanup_old_perceived_data(self, current_step): diff --git a/src/matrix.py b/src/matrix.py index 9895372..924a41e 100644 --- a/src/matrix.py +++ b/src/matrix.py @@ -442,9 +442,8 @@ def llm_action(self, agent, unix_time): return agent perceived_agents, perceived_locations, perceived_areas, perceived_objects,perceived_directions = agent.perceive([a for a in self.agents if a != agent], self.environment, unix_to_strftime(self.unix_time)) - perceived_data = (perceived_agents, perceived_locations, perceived_areas, perceived_objects, perceived_directions) - if agent.current_destination is not None and agent.perceived_data_is_same(perceived_data): + if agent.current_destination is not None and agent.perceived_data_is_same(): print("SKIPPED llm_action!") agent.move({ "environment": self.environment }) return agent diff --git a/utils/utils.py b/utils/utils.py index 1ce2351..f22e056 100644 --- a/utils/utils.py +++ b/utils/utils.py @@ -181,6 +181,7 @@ def generate(self, prompt, fallback="Llm Error"): pd(f"INPUT:\n {prompt}") pd(f"OUTPUT:\n {msg}") pd(f"runtime: {end_time - start_time}") + print(f"LLM CALL: {prompt[:30]}") return msg def embeddings(self, prompt, fallback=[0.5, 0.5, 0.5]): From cd4fb4904cd76e9f0ee9c5c9433e28f712bd922d Mon Sep 17 00:00:00 2001 From: Ivan Date: Sat, 16 Mar 2024 00:23:53 +0800 Subject: [PATCH 10/40] fixed error in matrix.py parse_scenario_file --- src/matrix.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/matrix.py b/src/matrix.py index 6ed8619..9f00f6a 100644 --- a/src/matrix.py +++ b/src/matrix.py @@ -175,8 +175,12 @@ def parse_scenario_file(self, filename): self.allow_movement = data.get("allow_movement", ALLOW_MOVEMENT) self.background = data.get("background", "") self.performance_evals = data.get("performance", {}) - self.performance_metrics[self.performance_evals["numerator"]] = 0 - self.performance_metrics["denominator"] = self.performance_evals["denominator"] + if not self.performance_evals: + self.performance_metrics["total_alive"] = 0 + self.performance_metrics["denominator"] = "total_agents" + else: + self.performance_metrics[self.performance_evals["numerator"]] = 0 + self.performance_metrics["denominator"] = self.performance_evals["denominator"] if self.steps <= 0: self.steps = data.get("steps", 100) From dd84c498f0f6dcecd26b13fae48d3311417cce0f Mon Sep 17 00:00:00 2001 From: chiefeu Date: Sat, 16 Mar 2024 01:13:28 +0800 Subject: [PATCH 11/40] fix all tests --- src/agents.py | 14 +++++++++++--- src/matrix.py | 8 ++++++-- test.py | 8 ++++++-- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/agents.py b/src/agents.py index a8ec00a..597236b 100644 --- a/src/agents.py +++ b/src/agents.py @@ -113,7 +113,11 @@ def ask_meta_questions(self, timestamp): self.meta_questions.extend(x[1] for x in m if x[1] not in self.meta_questions) def evaluate_progress(self,opts={}): - relevant_memories = self.getMemories(self.goal, unix_to_strftime(self.matrix.unix_time)) + if self.matrix: + relevant_memories = self.getMemories(self.goal, unix_to_strftime(self.matrix.unix_time)) + else: + relevant_memories = self.getMemories(self.goal, opts.get("timestamp", "")) + relevant_memories_string = "\n".join(f"Memory {i + 1}:\n{memory}" for i, memory in enumerate(relevant_memories)) if relevant_memories else "" primer = opts.get("random_prime",False) variables = { @@ -130,7 +134,11 @@ def evaluate_progress(self,opts={}): score = int(match.group(1)) if match else None if score and explanation: - self.addMemory("meta", explanation, unix_to_strftime(self.matrix.unix_time) , 10) + if self.matrix: + self.addMemory("meta", explanation, unix_to_strftime(self.matrix.unix_time) , 10) + else: + self.addMemory("meta", explanation, opts.get("timestamp", "") , 10) + if score and int(score) < 3: #self.meta_cognize(unix_to_strftime(self.matrix.unix_time), True) pass @@ -493,7 +501,7 @@ def perceive(self, other_agents, environment, timestamp): perceived_agents = [] perceived_areas = [] perceived_directions = [] - if (self.matrix is not None and self.matrix.allow_observance_flag == 0) or (self.matrix is None and ALLOW_OBSERVE == 0): + if (self.matrix is not None and self.matrix.allow_observance_flag == 0) or (self.matrix is None and ALLOW_OBSERVANCE == 0): return perceived_agents, perceived_locations, perceived_areas, perceived_objects diff --git a/src/matrix.py b/src/matrix.py index 924a41e..ec117e7 100644 --- a/src/matrix.py +++ b/src/matrix.py @@ -175,8 +175,12 @@ def parse_scenario_file(self, filename): self.allow_movement = data.get("allow_movement", ALLOW_MOVEMENT) self.background = data.get("background", "") self.performance_evals = data.get("performance", {}) - self.performance_metrics[self.performance_evals["numerator"]] = 0 - self.performance_metrics["denominator"] = self.performance_evals["denominator"] + if not self.performance_evals: + self.performance_metrics["total_alive"] = 0 + self.performance_metrics["denominator"] = "total_agents" + else: + self.performance_metrics[self.performance_evals["numerator"]] = 0 + self.performance_metrics["denominator"] = self.performance_evals["denominator"] if self.steps <= 0: self.steps = data.get("steps", 100) diff --git a/test.py b/test.py index 9562bb6..fec4b76 100644 --- a/test.py +++ b/test.py @@ -39,8 +39,12 @@ def test_fine_move_action_moves_away(self): self.assertEqual(real_agent.x, 4) def test_matrix_runs_step(self): - matrix = Matrix({"environment":"configs/small.tmj"}) + matrix = Matrix({"scenario":"configs/empty.json","environment":"configs/largev2.tmj"}) #run for one step + matrix.steps=1 + matrix.boot() + matrix.run_singlethread() + self.assertTrue(len(matrix.agents) > 0) self.assertEqual(matrix.status,"complete") def test_memory(self): @@ -90,7 +94,7 @@ def test_eval(self): print(f"Error {e}") timestamp = datetime.fromtimestamp(unix_time).strftime("%Y-%m-%d %H:%M:%S") - agent1.evaluate_progress(timestamp) + agent1.evaluate_progress({'timestamp':timestamp}) def test_askmetaquestions(self): agent1_data = { From fb1c1b5dede07005e4a1912b2db87d7a19cc9197 Mon Sep 17 00:00:00 2001 From: chiefeu Date: Sat, 16 Mar 2024 01:23:38 +0800 Subject: [PATCH 12/40] more fixes --- src/agents.py | 5 +++-- test.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/agents.py b/src/agents.py index 597236b..13b505a 100644 --- a/src/agents.py +++ b/src/agents.py @@ -591,8 +591,9 @@ def perceive(self, other_agents, environment, timestamp): perceived_data = (perceived_agents, perceived_locations, perceived_areas, perceived_objects, perceived_directions) #self.last_perceived_data = perceived_data - self.perceived_data_history[self.matrix.cur_step] = perceived_data - self.cleanup_old_perceived_data(self.matrix.cur_step) + if self.matrix: + self.perceived_data_history[self.matrix.cur_step] = perceived_data + self.cleanup_old_perceived_data(self.matrix.cur_step) return perceived_agents, perceived_locations, perceived_areas, perceived_objects,perceived_directions diff --git a/test.py b/test.py index fec4b76..81dfc56 100644 --- a/test.py +++ b/test.py @@ -581,7 +581,7 @@ def test_agent_decision_zombie(self): agent.destination_cache = [(36, 88), (36, 89), (36, 90)] zombie.destination_cache = [(36, 93), (36, 93), (36, 93)] - agent.addMemory("reflect", f"{matrix.unix_to_strftime(matrix.unix_time)} - {agent} wants to check if the Park is a safe place to go to or no.", matrix.unix_to_strftime(matrix.unix_time), 9) + agent.addMemory("reflect", f"{unix_to_strftime(matrix.unix_time)} - {agent} wants to check if the Park is a safe place to go to or no.", unix_to_strftime(matrix.unix_time), 9) matrix.agents.append(agent) matrix.agents.append(zombie) From 4cdc453038ef662e6004a96b65b8b6b59053323a Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 18 Mar 2024 14:22:42 -0700 Subject: [PATCH 13/40] more tests --- prompts/general/summarize_conversation.txt | 6 +- prompts/general/talk.txt | 15 ++--- src/agents.py | 5 +- src/matrix.py | 2 +- test.py | 70 +++++++++++++++++++--- utils/utils.py | 3 +- 6 files changed, 76 insertions(+), 25 deletions(-) diff --git a/prompts/general/summarize_conversation.txt b/prompts/general/summarize_conversation.txt index 8063a9a..4692d8b 100644 --- a/prompts/general/summarize_conversation.txt +++ b/prompts/general/summarize_conversation.txt @@ -1,7 +1,7 @@ Your name is {{agent}} -You are talking with {{other_agent}} +You just spoke with {{other_agent}} {{conversation_string}} -Summarize the conversation above in {{agent}}'s point of view. -List all important outcomes of the conversation: +Summarize the conversation in the first person point of view as {{agent}}. +Write it as a thought {{agent}} says to theirselves. diff --git a/prompts/general/talk.txt b/prompts/general/talk.txt index d557348..909d14d 100644 --- a/prompts/general/talk.txt +++ b/prompts/general/talk.txt @@ -1,18 +1,14 @@ -roleplay as {{agent}} -{{selfContext}} -{{relevant_memories}} +roleplay as {{agent}} talking to {% if other_agent == "stranger" %} a stranger you know nothing about. {% else %} {{other_agent}} {% endif %} -{% if other_agent == "stranger" %} -This is your first time talking to this stranger. +{{selfContext}} +{{agent}}'s previous memories: +{{relevant_memories}} -{% else %} -{% if primer %} +{% if other_agent != "stranger" and primer %} prime your thoughts but dont mention: "{{primer}}". -{% endif %} -{{selfContext}} Below is the current chat history between {{agent}} and {{other_agent}}. {{previous_conversations}} @@ -41,5 +37,6 @@ Maybe you could discuss the topic of "{{topic}}" keep responses to under 3 sentences. Craft an informal spoken response. +Do not discuss topics for things that you don't perceive or are not in your recent memories. Only write the response from {{agent}} and nothing else. diff --git a/src/agents.py b/src/agents.py index a8ec00a..3f8ea16 100644 --- a/src/agents.py +++ b/src/agents.py @@ -300,10 +300,11 @@ def summarize_conversation(self, timestamp): msg = llm.prompt(prompt_name="summarize_conversation", variables=variables) interaction = f"{timestamp} - {self} summarized their conversation with {self.last_conversation.other_agent.name}: {msg}" + interaction = f"my conversation with {self.last_conversation.other_agent.name}: {msg}" if self.matrix is not None: print_and_log(interaction, f"{self.matrix.id}:events:{self.name}") - self.addMemory("conversation", interaction, timestamp, random.randint(4, 6)) + self.addMemory("summary", interaction, timestamp, random.randint(4, 6)) if self.matrix: self.matrix.add_to_logs({"step_type":"agent_set", "attribute_name": "convo", "attribute_data": {"status": "complete", "from":self.mid, "to":self.last_conversation.other_agent.mid, "convo_id":self.last_conversation.mid}}) self.last_conversation = None @@ -595,7 +596,7 @@ def addMemory(self, kind, content, timestamp=None, score=None,embedding=None): if kind == "observation": - if (self.matrix is not None and self.matrix.allow_observance_flag == 1): + if (self.matrix and self.matrix.allow_observance_flag == 1) or (self.matrix is None): memory = Memory(kind, content, timestamp, timestamp, score,embedding) self.memory.append(memory) else: diff --git a/src/matrix.py b/src/matrix.py index 924a41e..40fc212 100644 --- a/src/matrix.py +++ b/src/matrix.py @@ -100,7 +100,7 @@ def boot(self): # Add Zombies for i in range(self.num_zombies): - zombie = Agent({ "name": f"Zombie_{i}", "kind": "zombie", "actions": ["kill"],"matrix":self }) + zombie = Agent({ "name": f"killer Zombie {i}", "kind": "zombie", "actions": ["kill"],"matrix":self }) self.add_agent_to_simulation(zombie) @classmethod diff --git a/test.py b/test.py index 9562bb6..e5cfbe2 100644 --- a/test.py +++ b/test.py @@ -29,14 +29,18 @@ def test_fine_move_action_invalid_direction(self): self.assertEqual(agent.y, 0) def test_fine_move_action_moves_away(self): - #real agent here, real zombie here. decide - matrix = Matrix({"environment":"configs/small.tmj"}) - real_agent = Agent({ "name": "John", "actions": ["fine_move"],"x":5,"y":5,"matrix":matrix }) - matrix.add_agent_to_simulation(real_agent) - zombie = Agent({ "name": f"Zombie", "kind": "zombie", "actions": ["kill"],"x":6,"y":5,"matrix":matrix }) - matrix.add_agent_to_simulation(zombie) - matrix.llm_action(real_agent, matrix.unix_time) - self.assertEqual(real_agent.x, 4) + successful_outcomes = 0 + for _ in range(4): + matrix = Matrix({"environment": "configs/small.tmj"}) + real_agent = Agent({"name": "John", "actions": ["fine_move"], "x": 5, "y": 5, "matrix": matrix}) + matrix.add_agent_to_simulation(real_agent) + zombie = Agent({"name": f"killer Zombie", "kind": "zombie", "actions": ["kill"], "x": 6, "y": 5, "matrix": matrix}) + matrix.add_agent_to_simulation(zombie) + matrix.llm_action(real_agent, matrix.unix_time) + if real_agent.x == 4: + successful_outcomes += 1 + + self.assertTrue(successful_outcomes >= 2) def test_matrix_runs_step(self): matrix = Matrix({"environment":"configs/small.tmj"}) @@ -260,13 +264,56 @@ def test_embeddings(self): print(f"Relevance Score: {Memory.calculateRelevanceScore(mem.embedding, context_embedding)}") self.assertTrue(len(sorted_memory) > 0) - def test_information_spreading(self): + + def test_information_dissemination(self): + a1_data = { "name": "Viktor", "goal": "wants to throw a party next week" } + a2_data = { "name": "John", "description": "loves art" } + a3_data = { "name": "Paul", "description": "loves eating" } + agent1 = Agent(a1_data) + agent2 = Agent(a2_data) + agent3 = Agent(a3_data) + unix_time = 1704067200 + timestamp = datetime.fromtimestamp(unix_time).strftime("%Y-%m-%d %H:%M:%S") + for i in range(2): + timestamp = datetime.fromtimestamp(unix_time).strftime("%Y-%m-%d %H:%M:%S") + response = agent1.talk({ "target": agent2.name, "other_agents": [agent2], "timestamp": timestamp }) + msg = f"{agent1} said to {agent2}: {response}" + print(msg) + response = agent2.talk({ "target": agent1.name, "other_agents": [agent1], "timestamp": timestamp }) + msg = f"{agent2} said to {agent1}: {response}" + print(msg) + unix_time = unix_time + 10 + agent2.summarize_conversation(timestamp) + print("*"*20) + for i in range(2): + timestamp = datetime.fromtimestamp(unix_time).strftime("%Y-%m-%d %H:%M:%S") + response = agent2.talk({ "target": agent3.name, "other_agents": [agent3], "timestamp": timestamp }) + msg = f"{agent2} said to {agent3}: {response}" + print(msg) + response = agent3.talk({ "target": agent2.name, "other_agents": [agent2], "timestamp": timestamp }) + msg = f"{agent3} said to {agent2}: {response}" + print(msg) + unix_time = unix_time + 10 + agent3.summarize_conversation(timestamp) + print(f"{agent2} memories") + for mem in agent2.memory: + print(mem) + print(f"{agent3} memories") + for mem in agent3.memory: + print(mem) + + def long_range_test_information_dissemination(self): a1_data = { "name": "Viktor", "goal": "wants to throw a party next week" } a2_data = { "name": "John", "description": "loves art" } a3_data = { "name": "Paul", "description": "loves eating" } agent1 = Agent(a1_data) agent2 = Agent(a2_data) agent3 = Agent(a3_data) + agents = [agent1, agent2, agent3] + for i, agent in enumerate(agents): + for other_agent in agents[i + 1:]: + agent.connections.append(str(other_agent)) + other_agent.connections.append(str(agent)) unix_time = 1704067200 timestamp = datetime.fromtimestamp(unix_time).strftime("%Y-%m-%d %H:%M:%S") for i in range(2): @@ -279,6 +326,11 @@ def test_information_spreading(self): print(msg) unix_time = unix_time + 10 agent2.summarize_conversation(timestamp) + interactions = ["saw a dog" ,"took a nap","chatted with a stranger", "ate lunch", "saw a bird", "saw a friend","saw a stranger", "saw a zombie"] + for i in range(50): + timestamp = datetime.fromtimestamp(unix_time+i).strftime("%Y-%m-%d %H:%M:%S") + interaction = random.choice(interactions) + agent2.addMemory("observation", interaction, timestamp, random.randint(0, 2)) print("*"*20) for i in range(2): timestamp = datetime.fromtimestamp(unix_time).strftime("%Y-%m-%d %H:%M:%S") diff --git a/utils/utils.py b/utils/utils.py index f22e056..9afa0ea 100644 --- a/utils/utils.py +++ b/utils/utils.py @@ -180,8 +180,9 @@ def generate(self, prompt, fallback="Llm Error"): pd(f"current url {current_url}") pd(f"INPUT:\n {prompt}") pd(f"OUTPUT:\n {msg}") - pd(f"runtime: {end_time - start_time}") print(f"LLM CALL: {prompt[:30]}") + print(f"runtime: {end_time - start_time}") + return msg def embeddings(self, prompt, fallback=[0.5, 0.5, 0.5]): From 52d3da73e41add60f10fa5e6145dec541d249bd7 Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 18 Mar 2024 17:24:35 -0700 Subject: [PATCH 14/40] cleanup inserter --- utils/db_insert.py | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/utils/db_insert.py b/utils/db_insert.py index e4bf5db..0c830b5 100644 --- a/utils/db_insert.py +++ b/utils/db_insert.py @@ -6,10 +6,19 @@ from dotenv import load_dotenv from sshtunnel import SSHTunnelForwarder import redis +import argparse # Load environment variables from .env file load_dotenv() +parser = argparse.ArgumentParser(description='Process and insert data.') +parser.add_argument('jsonl_file_path', type=str, help='Path to the JSONL file') +parser.add_argument('--rows', type=int, help='Number of rows to process', default=None) +parser.add_argument('--id', type=str, help='Override sim_id', default=None) +parser.add_argument('--full', action='store_true', help='Process minimal data only') +args = parser.parse_args() + + # Database settings db_settings = { "database_host": os.environ.get("DB_HOST"), @@ -28,25 +37,29 @@ "ssh_private_key": os.environ.get("SSH_PRIVATE_KEY") } redis_url = os.environ.get("REDIS_URL") -minimal = True #TODO move this to a flag override_sim_id = None printed = False +inserted = 0 -def process_and_insert_data(cursor,redis_client, jsonl_file_path,rows_to_process): - global printed,minimal - with jsonlines.open(jsonl_file_path, "r") as jsonl_file: +def process_and_insert_data(cursor,redis_client, args): + global printed,inserted + with jsonlines.open(args.jsonl_file_path, "r") as jsonl_file: for i, row in enumerate(jsonl_file): - if rows_to_process is None or i < rows_to_process: + if args.rows is None or i < args.rows: obj = json.loads(row) step = obj.get("step") substep = obj.get("substep") step_type = obj.get("step_type") sim_id = obj.get("sim_id") + if args.id: + sim_id = args.id if printed == False: print(f"SIM ID: {sim_id}") + if not args.full: + print("inserting minimum data for simulation display") printed = True - #if minimal and step_type not in ['talk', 'agent_set', 'move', 'matrix_set', 'agent_init']: - # next + if not args.full and step_type not in ['talk', 'agent_set', 'move', 'matrix_set', 'agent_init']: + continue data = {k: v for k, v in obj.items() if k not in ["step", "substep", "step_type","sim_id","embedding"]} try: @@ -58,6 +71,7 @@ def process_and_insert_data(cursor,redis_client, jsonl_file_path,rows_to_process conn.commit() elif redis_client: redis_client.rpush(os.getenv('REDIS_QUEUE', sim_id), json.dumps(data)) + inserted += 1 if i % 500 == 0: print(i) @@ -65,15 +79,14 @@ def process_and_insert_data(cursor,redis_client, jsonl_file_path,rows_to_process if cursor: conn.rollback() print(f"Error: {e}") - print(i) + print(f"cursor: {i}") + print(f"actual inserted: {inserted}") -jsonl_file_path = sys.argv[1] # Assumes the first command-line argument is the JSONL file path -rows_to_process = int(sys.argv[2]) if len(sys.argv) > 2 else None # Assumes the second argument is the number of rows to process cursor = None if redis_url: print("INSERTING INTO REDIS") redis_client = redis.StrictRedis.from_url(redis_url, decode_responses=True) - process_and_insert_data(None,redis_client, jsonl_file_path,rows_to_process) + process_and_insert_data(None,redis_client,args) elif ssh_host: print("INSERTING INTO DB VIA TUNNEL") @@ -92,7 +105,7 @@ def process_and_insert_data(cursor,redis_client, jsonl_file_path,rows_to_process database=db_settings["database_name"], ) cursor = conn.cursor() - process_and_insert_data(cursor,None, jsonl_file_path,rows_to_process) + process_and_insert_data(cursor,None, args) else: print("INSERTING INTO DB") conn = psycopg2.connect( @@ -104,7 +117,7 @@ def process_and_insert_data(cursor,redis_client, jsonl_file_path,rows_to_process ) cursor = conn.cursor() - process_and_insert_data(cursor, None,jsonl_file_path,rows_to_process) + process_and_insert_data(cursor, None,args) if cursor: From cbd9e7d9b18299ea29f38bdf875f82a171abb3fb Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 19 Mar 2024 16:51:52 +0800 Subject: [PATCH 15/40] display map type and name based from url params we pass a map and img params along with the sim_id in the url. for example: http://localhost:3000/?sim_id=something&map=stage&img=Southpark.jpg if the map is stage, we display the map image in full screen with agents overlaid on it. --- web/next.config.mjs | 4 +- web/public/images/maps/Southpark.jpg | Bin 0 -> 38089 bytes web/src/app/page.tsx | 14 ++++++- web/src/components/AgentSprite.tsx | 12 ++++-- web/src/components/RenderLevel.module.css | 26 ++++++++++++ web/src/components/RenderLevel.tsx | 48 ++++++++++++++++++++-- web/src/components/Sidebar.tsx | 2 +- 7 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 web/public/images/maps/Southpark.jpg diff --git a/web/next.config.mjs b/web/next.config.mjs index 8f767b0..04af33a 100644 --- a/web/next.config.mjs +++ b/web/next.config.mjs @@ -9,12 +9,14 @@ if (process.env.NODE_ENV === 'production') { assetPrefix: '/replay', env: { NEXT_PUBLIC_BASE_PATH: '/replay', + NEXT_PUBLIC_CONTENT_DIRECTORY: '' }, }; } else { nextConfig = { env: { - NEXT_PUBLIC_BASE_PATH: '' + NEXT_PUBLIC_BASE_PATH: '', + NEXT_PUBLIC_CONTENT_DIRECTORY: '' } }; } diff --git a/web/public/images/maps/Southpark.jpg b/web/public/images/maps/Southpark.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ca6979f574031bcbcbe1d7e3c2e9215a76851df3 GIT binary patch literal 38089 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 z%0D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 + const [map, setMapName] = useState(null); + const [img, setImgName] = useState(null); useEffect(() => { const params = new URLSearchParams(window.location.search); const sim_id = params.get('sim_id'); + const map = params.get('map'); + const img = params.get('img'); if (sim_id) { setSimId(sim_id); } + if (map) { + setMapName(map); + } + if (img) { + setImgName(img); + } }, []); - + if (!simId) { return null; } - return ; + return ; } diff --git a/web/src/components/AgentSprite.tsx b/web/src/components/AgentSprite.tsx index 0fdc21f..ea6bbd7 100644 --- a/web/src/components/AgentSprite.tsx +++ b/web/src/components/AgentSprite.tsx @@ -1,9 +1,15 @@ import React from "react"; import styles from './Agent.module.css'; -const AgentSprite: React.FC<{ agentName: string, isTalking: boolean, isThinking: boolean, status: string }> = ({ agentName, isTalking, isThinking, status }) => { +const AgentSprite: React.FC<{ agentName: string, isTalking: boolean, isThinking: boolean, status: string, map: string }> = ({ agentName, isTalking, isThinking, status, map }) => { let agentImage = `${process.env.NEXT_PUBLIC_BASE_PATH}/images/characters/${agentName}.png`; console.log("AgentSpriteeee status = ", status); + + let size = { width: 32, height: 32 }; + if (map == "stage") { + size = { width: 150, height: 150 }; // Set a different size for agents if map is "stage" + } + // Check if agentName matches "Zombie" using regex if (/Zombie/.test(agentName)) { agentImage = `${process.env.NEXT_PUBLIC_BASE_PATH}/images/characters/Zombie_1.png`; @@ -40,8 +46,8 @@ const AgentSprite: React.FC<{ agentName: string, isTalking: boolean, isThinking: {agentName} ); diff --git a/web/src/components/RenderLevel.module.css b/web/src/components/RenderLevel.module.css index db495d3..6e0070b 100644 --- a/web/src/components/RenderLevel.module.css +++ b/web/src/components/RenderLevel.module.css @@ -50,3 +50,29 @@ .sidebarVisible { width: 200px; /*calc(var(--game-viewport-width) * 0.30);*/ } + +.stageImg { + width: 100vw; + height: 100vh; + object-fit: "cover"; +} + +.agentContainer { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + flex-wrap: nowrap; + /* Prevent wrapping */ + justify-content: center; + align-items: flex-end; + padding: 150px; +} + +.agentItem { + margin-right: 20px; + /* Adjust spacing between agents */ + position: relative; +} diff --git a/web/src/components/RenderLevel.tsx b/web/src/components/RenderLevel.tsx index b645c9e..61c4101 100644 --- a/web/src/components/RenderLevel.tsx +++ b/web/src/components/RenderLevel.tsx @@ -28,7 +28,7 @@ async function getData(sim_id: string, fromIndex: number) { } // eslint-disable-next-line @typescript-eslint/no-explicit-any -const RenderLevel: React.FC<{ simId: string }> = ({ simId }) => { +const RenderLevel: React.FC<{ simId: string, map?: string | null, img?: string | null }> = ({ simId, map, img }) => { const [isPlaying, setIsPlaying] = useState(true); const [followAgent, setFollowAgent] = useState(undefined); const [levelState, setLevelState] = useState({ stepId: 0, substepId: 0, agents: [] }); @@ -100,19 +100,61 @@ const RenderLevel: React.FC<{ simId: string }> = ({ simId }) => { style={style} className={styles.placement} onClick={() => setFollowAgent(agent)}> - + ); }); }; + if (map == "stage") { + return ( +
+ Stage Map +
+ {/* Hardcoded agents for now */} +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ); + } + return (
- Default Map + Default Map <> {renderAgents()} diff --git a/web/src/components/Sidebar.tsx b/web/src/components/Sidebar.tsx index ac055a5..a46170d 100644 --- a/web/src/components/Sidebar.tsx +++ b/web/src/components/Sidebar.tsx @@ -131,7 +131,7 @@ const Sidebar: React.FC = (
{renderControls()} {agentPlacement &&
- +
{agentPlacement.agentName}
); diff --git a/web/src/components/Sidebar.tsx b/web/src/components/Sidebar.tsx index ac055a5..4f2b8e0 100644 --- a/web/src/components/Sidebar.tsx +++ b/web/src/components/Sidebar.tsx @@ -16,6 +16,8 @@ interface SidebarProps { stepId: number; substepId: number; level: Level; + toggleAudio: () => void; + audioPlaying: boolean; } const Sidebar: React.FC = ( @@ -26,7 +28,9 @@ const Sidebar: React.FC = ( setIsPlaying, stepId, substepId, - level + level, + toggleAudio, + audioPlaying }) => { const [showThoughts, setShowThoughts] = useState(true); @@ -120,6 +124,7 @@ const Sidebar: React.FC = ( +
From f3834a53916a648fda58228c9e2089b45a664e8f Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 22 Mar 2024 03:11:09 +0800 Subject: [PATCH 22/40] fix load to redis in db_insert --- utils/db_insert.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/utils/db_insert.py b/utils/db_insert.py index 0c830b5..c0dbba6 100644 --- a/utils/db_insert.py +++ b/utils/db_insert.py @@ -42,6 +42,11 @@ inserted = 0 def process_and_insert_data(cursor,redis_client, args): + if redis_client: + data_fields = ["sim_id","embedding"] + else: + data_fields = ["step", "substep", "step_type","sim_id","embedding"] + global printed,inserted with jsonlines.open(args.jsonl_file_path, "r") as jsonl_file: for i, row in enumerate(jsonl_file): @@ -60,7 +65,7 @@ def process_and_insert_data(cursor,redis_client, args): printed = True if not args.full and step_type not in ['talk', 'agent_set', 'move', 'matrix_set', 'agent_init']: continue - data = {k: v for k, v in obj.items() if k not in ["step", "substep", "step_type","sim_id","embedding"]} + data = {k: v for k, v in obj.items() if k not in data_fields} try: if cursor: From 14a238254da581712cc841915b6b38796485bf38 Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 22 Mar 2024 03:32:45 +0800 Subject: [PATCH 23/40] added audio play we generate audio in rails using elevenlabs text-to-speech. we fetch the audio in our web ui with 4 params passed on our rails endpoint. rails will use that to get the corresponding content of the talk/thought step. we play the audio if the audio check box is ticked. otherwise, we do not. we show the audio check box when NEXT_PUBLIC_ALLOW_AUDIO is true. --- web/src/components/RenderLevel.tsx | 14 +----- web/src/components/Sidebar.module.css | 6 +++ web/src/components/Sidebar.tsx | 69 ++++++++++++++++++++++++--- 3 files changed, 69 insertions(+), 20 deletions(-) diff --git a/web/src/components/RenderLevel.tsx b/web/src/components/RenderLevel.tsx index b56bf3b..8aea86a 100644 --- a/web/src/components/RenderLevel.tsx +++ b/web/src/components/RenderLevel.tsx @@ -36,8 +36,6 @@ const RenderLevel: React.FC<{ simId: string }> = ({ simId }) => { const [initialFetchDone, setInitialFetchDone] = useState(false); const chunkSize = 1000; // Adjust chunk size as needed - const [audio] = useState(new Audio('/audio/sample.mp3')); - const [audioPlaying, setAudioPlaying] = useState(false); const levelRef = useRef(new Level([], (newState: LevelState) => { setLevelState(newState); @@ -86,15 +84,6 @@ const RenderLevel: React.FC<{ simId: string }> = ({ simId }) => { ) } - const toggleAudio = () => { - if (audioPlaying) { - audio.pause(); // Pause audio if it's playing - } else { - audio.play(); // Play audio if it's paused - } - setAudioPlaying(!audioPlaying); // Toggle audio playing state - }; - const renderAgents = () => { return levelState.agents.map((agent, index) => { const x = agent.position.x * CELL_SIZE; @@ -136,8 +125,7 @@ const RenderLevel: React.FC<{ simId: string }> = ({ simId }) => { stepId={levelState.stepId} substepId={levelState.substepId} level={levelRef.current} - toggleAudio={toggleAudio} - audioPlaying={audioPlaying} /> + simId={simId} />
); diff --git a/web/src/components/Sidebar.module.css b/web/src/components/Sidebar.module.css index 62faed7..a9fda82 100644 --- a/web/src/components/Sidebar.module.css +++ b/web/src/components/Sidebar.module.css @@ -52,4 +52,10 @@ display: flex; align-items: center; justify-content: flex-end; +} + +.stepAndAudio { + display: flex; + flex-direction: row; + gap: 12px; } \ No newline at end of file diff --git a/web/src/components/Sidebar.tsx b/web/src/components/Sidebar.tsx index 4f2b8e0..15d51a5 100644 --- a/web/src/components/Sidebar.tsx +++ b/web/src/components/Sidebar.tsx @@ -16,8 +16,7 @@ interface SidebarProps { stepId: number; substepId: number; level: Level; - toggleAudio: () => void; - audioPlaying: boolean; + simId: string; } const Sidebar: React.FC = ( @@ -29,12 +28,12 @@ const Sidebar: React.FC = ( stepId, substepId, level, - toggleAudio, - audioPlaying + simId }) => { const [showThoughts, setShowThoughts] = useState(true); - + const [isPlayAudio, setIsPlayAudio] = useState(true); + const browserLanguage = navigator.language; useEffect(() => { let interval: NodeJS.Timeout; @@ -52,6 +51,57 @@ const Sidebar: React.FC = ( }; }, [isPlaying]); + const fetchAudioData = async (sim_id: string, substep_id: number, agent_name: string, lang: string) => { + try { + const res = await fetch(`${process.env.NEXT_PUBLIC_ASSET_DOMAIN}/audio?mid=${sim_id}&substep=${substep_id}&agent=${agent_name}&lang=${lang}`, { mode: 'cors' }); + console.log("LAAAAAANG", lang); + if (!res.ok) { + throw new Error('Failed to fetch data'); + } + + const audioBlob = await res.blob(); + const audioUrl = URL.createObjectURL(audioBlob); + const newAudio = new Audio(audioUrl); + playAudio(newAudio); + } catch (error) { + console.error('Error fetching audio:', error); + } + }; + + const playAudio = (audio: HTMLAudioElement) => { + if (audio) { + audio.play(); + } + }; + + const stopAudio = (audio: HTMLAudioElement) => { + if (audio) { + audio.pause(); + audio.currentTime = 0; + } + }; + + useEffect(() => { + if (agentPlacement) { + const steps = agentPlacement.steps.toReversed(); + console.log({ "STEPSSSS": steps }); + steps.forEach(step => { + if (showThoughts && isPlayAudio) { + if (step instanceof TalkStep) { + const talkStep = step as TalkStep; + fetchAudioData(simId, talkStep.substepId, talkStep.fromAgentName, browserLanguage); + return; + } + if (step instanceof ThoughtStep) { + const thoughtStep = step as ThoughtStep; + fetchAudioData(simId, thoughtStep.substepId, thoughtStep.agentId, browserLanguage); + return; + } + } + }); + } + }, [agentPlacement, showThoughts, isPlayAudio]); + const handleRewind = (): void => { setIsPlaying(false); setFollowAgent(undefined); @@ -114,8 +164,14 @@ const Sidebar: React.FC = ( const renderControls = () => { return(
-
+
Step: {stepId} + {process.env.NEXT_PUBLIC_ALLOW_AUDIO === "true" &&
+ +
}
{substepId}
@@ -124,7 +180,6 @@ const Sidebar: React.FC = ( -
From 2f046c4510ab9e19e7061c0a5b7e4f81aeb33213 Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 25 Mar 2024 15:28:00 -0700 Subject: [PATCH 24/40] updated --- requirements.txt | 1 + test.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/requirements.txt b/requirements.txt index 0c7378e..f1564f4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,3 +13,4 @@ python-Levenshtein jsonlines nltk Pillow +groq diff --git a/test.py b/test.py index 304cd6a..dcefb45 100644 --- a/test.py +++ b/test.py @@ -16,6 +16,11 @@ from src.actions.fine_move_action import FineMoveAction class TestMemoryFunctions(unittest.TestCase): + def test_web(self): + #run_simulation("steps":1) + #start_npm_run_dev + #assert_check_for_event + pass def test_fine_move_action_direction(self): agent = MagicMock(x=0, y=0) FineMoveAction.act(agent, "up with random text") From 191bfd37d9776e4d3694602500885eb43e355163 Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 25 Mar 2024 17:16:29 -0700 Subject: [PATCH 25/40] basic daemon for running many sims --- configs/bus_stop.json | 47 +++++++------------------------ daemon.py | 65 +++++++++++++++++++++++++++++++++++++++++++ engine.py | 4 --- src/matrix.py | 12 +++++--- 4 files changed, 83 insertions(+), 45 deletions(-) create mode 100644 daemon.py diff --git a/configs/bus_stop.json b/configs/bus_stop.json index 3cf52b4..24b453b 100644 --- a/configs/bus_stop.json +++ b/configs/bus_stop.json @@ -1,5 +1,6 @@ { - "background": "Six agents gather outside at a bus stop to chat with each other. They talk about their interests and anything they come up with.", + "background": "Six agents gather outside at a bus stop to chat with each other. They talk about their interests and anything they come up with.", + "actions_blacklist": [ "move" ], "agents": [ { "name": "Natasha", @@ -12,10 +13,7 @@ "James" ], "x": 25, - "y": 10, - "actions_blacklist": [ - "move" - ] + "y": 10 }, { "name": "James", @@ -28,10 +26,7 @@ "Natasha" ], "x": 25, - "y": 15, - "actions_blacklist": [ - "move" - ] + "y": 15 }, { "name": "Sherlock", @@ -44,10 +39,7 @@ "Paul" ], "x": 25, - "y": 20, - "actions_blacklist": [ - "move" - ] + "y": 20 }, { "name": "Viktor", @@ -60,10 +52,7 @@ "Paul" ], "x": 25, - "y": 25, - "actions_blacklist": [ - "move" - ] + "y": 25 }, { "name": "Lily", @@ -76,10 +65,7 @@ "Paul" ], "x": 25, - "y": 30, - "actions_blacklist": [ - "move" - ] + "y": 30 }, { "name": "Paul", @@ -92,23 +78,10 @@ "Lily" ], "x": 25, - "y": 35, - "actions_blacklist": [ - "move" - ] + "y": 35 } ], "steps": 50, "allow_movement": 0, - "perception_range": 50, - "questions": [ - { - "who": "all", - "question": "Highlight the topic or new information that you obtained from talking with the other agents." - }, - { - "who": "all", - "question": "Discuss the exchange of conversations. Who did you enjoy the most talking to?" - } - ] -} \ No newline at end of file + "perception_range": 50 +} diff --git a/daemon.py b/daemon.py new file mode 100644 index 0000000..fbb4d6e --- /dev/null +++ b/daemon.py @@ -0,0 +1,65 @@ +import os +import redis +import json +import requests +from dotenv import load_dotenv +from engine import Matrix + +load_dotenv() + +REDIS_URL = os.getenv('REDIS_URL', 'redis://localhost:6379') +QUEUE_NAME = os.getenv('QUEUE_NAME', 'simulation_jobs') +DISCORD_URL = os.getenv('DISCORD_URL') + +# Connect to Redis +redis_conn = redis.from_url(REDIS_URL) + +def notify_discord(msg): + if DISCORD_URL: + requests.post(DISCORD_URL,json={'content':msg}) + +# get this out of here or refactor with engine +def update_from_env(config): + for key, value in config.items(): + env_var = os.getenv(key) + if env_var is not None: + # Update the value from the environment variable + config[key] = type(value)(env_var) if value is not None else env_var + return config + +def load_config(): + filename = "configs/defaults.json" + with open(filename, 'r') as file: + config = json.load(file) + config = update_from_env(config) + return config + +def process_simulation(data): + config = load_config() + config['scenario'] = data + config['environment'] = "configs/largev2.tmj" + notify_discord(f"starting simulation: #{config}") + matrix = Matrix(config) + matrix.boot() + matrix.run_singlethread() + notify_discord(f"finished simulation: #{config}") + + + print(f'Simulation {simulation_id} completed.') + +def main(): + print('Starting simulation job daemon...') + while True: + # Fetch a job from the Redis queue + _, job = redis_conn.blpop(QUEUE_NAME) + job_data = json.loads(job) + + # Process the simulation + try: + process_simulation(job_data) + except Exception as e: + print(f'Error processing simulation: {e}') + +if __name__ == '__main__': + main() + diff --git a/engine.py b/engine.py index cbe602f..22266b2 100755 --- a/engine.py +++ b/engine.py @@ -48,10 +48,7 @@ def main(): matrix = Matrix(config) pd(f"model:#{MODEL}") pd("Initial Agents Positions:") - #redis_connection.set(f"{matrix.id}:matrix_state", json.dumps(matrix.get_arr_2D())) - # Clear convos - #matrix.clear_redis() # Run start_time = datetime.now() @@ -64,7 +61,6 @@ def main(): # Log Runtime matrix.simulation_runtime = end_time - start_time - matrix.send_matrix_to_redis() # Save the environment state to a file for inspection if matrix.id is not None and matrix.id != '' and RUN_REPORTS != 0: diff --git a/src/matrix.py b/src/matrix.py index 7204be3..6926260 100644 --- a/src/matrix.py +++ b/src/matrix.py @@ -75,7 +75,7 @@ def __init__(self, config={}): self.agent_locks = { agent: threading.Lock() for agent in self.agents } self.environment = Environment({ "filename": self.environment_file }) if self.scenario_file is not None: - self.parse_scenario_file(self.scenario_file) + self.parse_scenario(self.scenario_file) #self.environment.overlay_collisions_on_image() self.background = None print(config) @@ -165,9 +165,13 @@ def from_timeline(cls,src,step=None): - def parse_scenario_file(self, filename): - with open(filename, 'r') as file: - data = json.load(file) + def parse_scenario(self, config): + if isinstance(config, str): + with open(config, 'r') as file: + data = json.load(file) + self.data = data + else: + data = config self.data = data # Build Scenario From ebff970ffa21ead40773a538f08cb8320505c93b Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 25 Mar 2024 20:18:57 -0700 Subject: [PATCH 26/40] groq support --- utils/utils.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/utils/utils.py b/utils/utils.py index 9afa0ea..b79898a 100644 --- a/utils/utils.py +++ b/utils/utils.py @@ -3,6 +3,7 @@ import requests import time import os +import re import redis import difflib import random @@ -15,6 +16,7 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker, relationship, column_property import requests +from groq import Groq import nltk nltk.download('brown') @@ -140,6 +142,13 @@ def generate(self, prompt, fallback="Llm Error"): except Exception as e: print(e) msg = fallback + elif re.findall(r'groq',self.model): + #groq,groq_model = self.model.split("_") + client = Groq( api_key=os.environ.get("GROQ_API_KEY"),) + chat_completion = client.chat.completions.create( + messages=[ { "role": "user", "content": prompt, } ], + model="mixtral-8x7b-32768",) + msg = chat_completion.choices[0].message.content else: data = { "model": self.model, From 6cfbdf56c2df4357d78e65432afd1db1b1d92aaf Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 26 Mar 2024 21:19:14 +0800 Subject: [PATCH 27/40] updated audio playback function 1. no audios playing on top of each other. play next audio after current audio is done. 2. switching to an agent will play their audio. 3. play audio from the step where we are at and forward, not all previous steps. --- web/src/components/Sidebar.tsx | 79 ++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 27 deletions(-) diff --git a/web/src/components/Sidebar.tsx b/web/src/components/Sidebar.tsx index 15d51a5..25965ce 100644 --- a/web/src/components/Sidebar.tsx +++ b/web/src/components/Sidebar.tsx @@ -31,9 +31,12 @@ const Sidebar: React.FC = ( simId }) => { - const [showThoughts, setShowThoughts] = useState(true); - const [isPlayAudio, setIsPlayAudio] = useState(true); - const browserLanguage = navigator.language; + const [showThoughts, setShowThoughts] = useState(true); + const [isPlayAudio, setIsPlayAudio] = useState(true); + const [audioQueue, setAudioQueue] = useState[]>([]); + const [audioPlaying, setAudioPlaying] = useState(false); + const browserLanguage = navigator.language; + const minimal_audio_delay = 500; // delay in between playing audio clips useEffect(() => { let interval: NodeJS.Timeout; @@ -51,27 +54,39 @@ const Sidebar: React.FC = ( }; }, [isPlaying]); - const fetchAudioData = async (sim_id: string, substep_id: number, agent_name: string, lang: string) => { + const fetchAudioData = async (sim_id: string, step_id: number, substep_id: number, agent_name: string, lang: string): Promise => { try { - const res = await fetch(`${process.env.NEXT_PUBLIC_ASSET_DOMAIN}/audio?mid=${sim_id}&substep=${substep_id}&agent=${agent_name}&lang=${lang}`, { mode: 'cors' }); - console.log("LAAAAAANG", lang); + const res = await fetch( + `${process.env.NEXT_PUBLIC_ASSET_DOMAIN}/audio?mid=${sim_id}&step=${step_id}&substep=${substep_id}&agent=${agent_name}&lang=${lang}`, + { mode: 'cors' } + ); if (!res.ok) { throw new Error('Failed to fetch data'); } const audioBlob = await res.blob(); const audioUrl = URL.createObjectURL(audioBlob); - const newAudio = new Audio(audioUrl); - playAudio(newAudio); + return audioUrl; } catch (error) { console.error('Error fetching audio:', error); + return ""; } }; - const playAudio = (audio: HTMLAudioElement) => { - if (audio) { - audio.play(); - } + const addToAudioQueue = (audioClipUrl: Promise) => { + setAudioQueue((oldQueue) => [...oldQueue, audioClipUrl]); + }; + + const playAudio = async (audioClipUrl: Promise) => { + console.log("BEFORE LENGTH", audioQueue.length) + setAudioPlaying(true); + const audio = new Audio(await audioClipUrl); + audio.onended = () => { + setAudioQueue((oldQueue) => oldQueue.slice(1)); + console.log("AFTER LENGTH", audioQueue.length) + setAudioPlaying(false); + }; + audio.play(); }; const stopAudio = (audio: HTMLAudioElement) => { @@ -82,25 +97,35 @@ const Sidebar: React.FC = ( }; useEffect(() => { - if (agentPlacement) { - const steps = agentPlacement.steps.toReversed(); - console.log({ "STEPSSSS": steps }); + if (isPlaying && agentPlacement && !audioPlaying && audioQueue.length > 0) { + playAudio(audioQueue[0]); + } + + if (!agentPlacement) { + setAudioQueue([]); + } + }, [agentPlacement, isPlaying, audioQueue, audioPlaying]); + + useEffect(() => { + if (agentPlacement && isPlayAudio) { + const steps = agentPlacement.steps.filter( + step => step.stepId >= stepId + ); steps.forEach(step => { - if (showThoughts && isPlayAudio) { - if (step instanceof TalkStep) { - const talkStep = step as TalkStep; - fetchAudioData(simId, talkStep.substepId, talkStep.fromAgentName, browserLanguage); - return; - } - if (step instanceof ThoughtStep) { - const thoughtStep = step as ThoughtStep; - fetchAudioData(simId, thoughtStep.substepId, thoughtStep.agentId, browserLanguage); - return; - } + if (step instanceof TalkStep) { + const talkStep = step as TalkStep; + addToAudioQueue(fetchAudioData(simId, talkStep.stepId, talkStep.substepId, talkStep.fromAgentName, browserLanguage)); + + return; + } + if (showThoughts && step instanceof ThoughtStep) { + const thoughtStep = step as ThoughtStep; + addToAudioQueue(fetchAudioData(simId, thoughtStep.stepId, thoughtStep.substepId, thoughtStep.agentId, browserLanguage)); + return; } }); } - }, [agentPlacement, showThoughts, isPlayAudio]); + }, [agentPlacement, showThoughts, isPlayAudio, stepId, substepId]); const handleRewind = (): void => { setIsPlaying(false); From b942122c5e41a28bba982894f4abd4c7dbbe47f4 Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 26 Mar 2024 22:21:32 +0800 Subject: [PATCH 28/40] updated sidebar.tsx handle linting error for npm run build --- web/src/components/Sidebar.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/src/components/Sidebar.tsx b/web/src/components/Sidebar.tsx index 25965ce..86fb75b 100644 --- a/web/src/components/Sidebar.tsx +++ b/web/src/components/Sidebar.tsx @@ -36,6 +36,8 @@ const Sidebar: React.FC = ( const [audioQueue, setAudioQueue] = useState[]>([]); const [audioPlaying, setAudioPlaying] = useState(false); const browserLanguage = navigator.language; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars const minimal_audio_delay = 500; // delay in between playing audio clips useEffect(() => { @@ -89,6 +91,7 @@ const Sidebar: React.FC = ( audio.play(); }; + // eslint-disable-next-line @typescript-eslint/no-unused-vars const stopAudio = (audio: HTMLAudioElement) => { if (audio) { audio.pause(); From 568b9e047ff663b6fbab198d5a368dd8beb6a157 Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 26 Mar 2024 23:42:25 +0800 Subject: [PATCH 29/40] updated sidebar.tsx fixed react DOM error --- web/src/components/Sidebar.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/web/src/components/Sidebar.tsx b/web/src/components/Sidebar.tsx index 86fb75b..e4ae764 100644 --- a/web/src/components/Sidebar.tsx +++ b/web/src/components/Sidebar.tsx @@ -103,10 +103,6 @@ const Sidebar: React.FC = ( if (isPlaying && agentPlacement && !audioPlaying && audioQueue.length > 0) { playAudio(audioQueue[0]); } - - if (!agentPlacement) { - setAudioQueue([]); - } }, [agentPlacement, isPlaying, audioQueue, audioPlaying]); useEffect(() => { @@ -128,6 +124,11 @@ const Sidebar: React.FC = ( } }); } + + if (!agentPlacement || !isPlayAudio) { + setAudioQueue([]); + } + }, [agentPlacement, showThoughts, isPlayAudio, stepId, substepId]); const handleRewind = (): void => { From 2422d26f285bcb0d97b35c56932d29291c7117ae Mon Sep 17 00:00:00 2001 From: Jason Date: Wed, 27 Mar 2024 06:58:34 -0700 Subject: [PATCH 30/40] refactor --- engine.py | 2 - src/data.py | 94 +++++++++++++++++++++++ src/matrix.py | 190 ++--------------------------------------------- src/reporting.py | 112 ++++++++++++++++++++++++++++ 4 files changed, 211 insertions(+), 187 deletions(-) create mode 100644 src/data.py create mode 100644 src/reporting.py diff --git a/engine.py b/engine.py index 22266b2..0a00f82 100755 --- a/engine.py +++ b/engine.py @@ -47,7 +47,6 @@ def main(): config['id'] = args.id matrix = Matrix(config) pd(f"model:#{MODEL}") - pd("Initial Agents Positions:") # Run @@ -84,7 +83,6 @@ def signal_handler(signum, frame): pd("stopping matrix, please wait for current step to finish") pd("*"*50) matrix.status = "stop" - matrix.send_matrix_to_redis() last_interrupt_time = current_time ctrl_c_count = 0 diff --git a/src/data.py b/src/data.py new file mode 100644 index 0000000..24a0106 --- /dev/null +++ b/src/data.py @@ -0,0 +1,94 @@ +import os +import json +import jsonlines +import sys +import psycopg2 +from dotenv import load_dotenv +from sshtunnel import SSHTunnelForwarder +import redis +load_dotenv() + +class Data: + def setup_database(self): + db_settings = { + "database_host": os.environ.get("DB_HOST"), + "database_port": int(os.environ.get("DB_PORT")), + "database_name": os.environ.get("DB_NAME"), + "database_username": os.environ.get("DB_USER"), + "database_password": os.environ.get("DB_PASSWORD"), + } + + # SSH settings + ssh_host = os.environ.get("SSH_HOST") + ssh_settings = { + "ssh_host": ssh_host, + "ssh_port": int(os.environ.get("SSH_PORT", 22)), + "ssh_user": os.environ.get("SSH_USER"), + "ssh_private_key": os.environ.get("SSH_PRIVATE_KEY") + } + cursor = None + if ssh_host: + with SSHTunnelForwarder( + (ssh_settings["ssh_host"], ssh_settings["ssh_port"]), + ssh_username=ssh_settings["ssh_user"], + ssh_pkey=ssh_settings["ssh_private_key"], + remote_bind_address=(db_settings["database_host"], db_settings["database_port"]), + ) as tunnel: + # Establish database connection + conn = psycopg2.connect( + user=db_settings["database_username"], + password=db_settings["database_password"], + host=db_settings["database_host"], + port=tunnel.local_bind_port, + database=db_settings["database_name"], + ) + cursor = conn.cursor() + else: + conn = psycopg2.connect( + user=db_settings["database_username"], + password=db_settings["database_password"], + host=db_settings["database_host"], + port=db_settings["database_port"], + database=db_settings["database_name"], + ) + + if conn: + return conn.cursor() + else: + return None + + def add_to_logs(self,obj): + obj["step"] = self.cur_step + obj["substep"] = self.current_substep + obj["sim_id"] = self.id # i think we will change this to sim id everywhere + + file = f"logs/{obj['sim_id']}.jsonl" + #with open("logs.json", "a") as file: + # json.dump(obj,file,indent=2) + # file.write("\n\n") + with jsonlines.open(file, mode='a') as writer: + writer.write(json.dumps(obj)) + stream = f"{obj['sim_id']}_stream" + queue = f"{obj['sim_id']}" + wtf = json.loads(json.dumps(obj, default=str)) + #redis_connection.xadd(stream, wtf) + max_retries = 3 + retry_delay = 1 + if self.redis_connection: + for attempt in range(max_retries): + try: + self.redis_connection.lpush(queue, json.dumps(obj)) + break # Break the loop if successful + except redis.RedisError as e: + print(f"Error pushing to Redis queue. Retrying... ({attempt + 1}/{max_retries})") + time.sleep(retry_delay) + + if self.cursor and obj["step_type"] in ['talk', 'agent_set', 'move', 'matrix_set', 'agent_init']: + fields_to_skip = ["step", "substep", "step_type","sim_id","embedding"] + data = {k: v for k, v in obj.items() if k not in fields_to_skip} + self.cursor.execute( + "INSERT INTO timelines (sim_id, step, substep, step_type, data) VALUES (%s, %s, %s, %s, %s)", + (self.id, self.cur_step, self.current_substep, obj["step_type"], json.dumps(obj)),) + print("insert") + + self.current_substep += 1 diff --git a/src/matrix.py b/src/matrix.py index 6926260..ad41218 100644 --- a/src/matrix.py +++ b/src/matrix.py @@ -21,13 +21,15 @@ from src.npc import Npc from src.location import Location, Area, Object from src.actions.fine_move_action import FineMoveAction +from src.data import Data +from src.reporting import Reporting def set_globals(config): for key, value in config.items(): globals()[key] = value -class Matrix: +class Matrix(Data,Reporting): def __init__(self, config={}): set_globals(config) self.steps = SIMULATION_STEPS @@ -69,8 +71,10 @@ def __init__(self, config={}): self.perception_range = PERCEPTION_RANGE self.allow_movement = ALLOW_MOVEMENT self.model = MODEL + self.redis_connection = None self.replay = None + self.cursor = self.setup_database() self.add_to_logs({"step_type":"matrix_init","data":config}) self.agent_locks = { agent: threading.Lock() for agent in self.agents } self.environment = Environment({ "filename": self.environment_file }) @@ -162,9 +166,6 @@ def from_timeline(cls,src,step=None): return(matrix) - - - def parse_scenario(self, config): if isinstance(config, str): with open(config, 'r') as file: @@ -234,116 +235,12 @@ def add_agent_to_simulation(self, agent): self.agents.append(agent) - def add_to_logs(self,obj): - obj["step"] = self.cur_step - obj["substep"] = self.current_substep - obj["sim_id"] = self.id # i think we will change this to sim id everywhere - - file = f"logs/{obj['sim_id']}.jsonl" - #with open("logs.json", "a") as file: - # json.dump(obj,file,indent=2) - # file.write("\n\n") - with jsonlines.open(file, mode='a') as writer: - writer.write(json.dumps(obj)) - stream = f"{obj['sim_id']}_stream" - queue = f"{obj['sim_id']}" - wtf = json.loads(json.dumps(obj, default=str)) - #redis_connection.xadd(stream, wtf) - max_retries = 3 - retry_delay = 1 - if redis_connection: - for attempt in range(max_retries): - try: - redis_connection.lpush(queue, json.dumps(obj)) - break # Break the loop if successful - except redis.RedisError as e: - print(f"Error pushing to Redis queue. Retrying... ({attempt + 1}/{max_retries})") - time.sleep(retry_delay) - - self.current_substep += 1 - - def get_server_info(self): - try: - # Run 'uname -a' command - uname_output = subprocess.check_output(['uname', '-a']).decode('utf-8').strip() - return uname_output - except Exception as e: - # Handle any exceptions that may occur - return f"Error getting server info: {str(e)}" - - def all_env_vars(self): - if self.sim_start_time is None: - self.sim_start_time = datetime.now() - - if self.simulation_runtime is None: - self.simulation_runtime = datetime.now() - self.sim_start_time - - total_reflections = 0 - total_metas = 0 - for a in self.agents: - for m in a.memory: - if m.kind == "reflect": - total_reflections += 1 - if m.kind == "meta": - total_metas += 1 - - total_seconds = self.simulation_runtime.total_seconds() - - # Calculate minutes and seconds - minutes = int(total_seconds // 60) - seconds = int(total_seconds % 60) - - # Create a human-readable string - runtime_string = f"{minutes} minute(s) and {seconds} second(s)" - - return { - "id": self.id, - "map": self.environment_file, - "agents": self.scenario_file, - "date": self.sim_start_time.isoformat(), - "width": self.environment.width, - "height": self.environment.width, - "status": self.status, - "runtime": runtime_string, # Include the string representation - "server_info": self.get_server_info(), - "created_at": self.sim_start_time.strftime("%Y-%m-%d %H:%M:%S"), - "model": self.model, - "total_steps": self.steps, - "meta_flag": self.allow_meta_flag, - "reflect_flag": self.allow_reflect_flag, - "conversation_counter": self.conversation_counter, - "total_meta_memories": total_metas, - "total_reflect_memories": total_reflections, - "total_agents": sum(1 for agent in self.agents if agent.kind != 'zombie'), - "total_zombies": sum(1 for agent in self.agents if agent.kind == 'zombie'), - "total_dead": sum(1 for agent in self.agents if agent.status == 'dead'), - "total_alive": sum(1 for agent in self.agents if agent.status != 'dead'), - "llm_call_counter": llm.call_counter, - "avg_runtime_per_step": total_seconds / self.steps, - "avg_llm_calls_per_step": llm.call_counter / self.steps - } - - def send_matrix_to_redis(self): - if TEST_RUN == 0: - #redis_connection.set(f"{self.id}:simulations", json.dumps(self.all_env_vars())) - pass - - def log_agents_to_redis(self): - for agent in self.agents: - agent_data = { - "name": agent.name, - "x": agent.x, - "y": agent.y, - "status": agent.status - } - #redis_connection.rpush(f"{self.id}:agents:{agent.name}", json.dumps(agent_data)) def run_singlethread(self): #self.boot() self.status = "running" self.sim_start_time = datetime.now() - #self.send_matrix_to_redis() for step in range(self.steps): self.cur_step = step self.current_substep = 0 @@ -658,11 +555,6 @@ def agent_action(self, agent, unix_time): return agent # Agent stays in the same position - def print_agent_memories(self): - for agent in self.agents: - pd(f"\nMemories for {agent}:") - for memory in agent.memory: - pd(memory) @@ -679,75 +571,3 @@ def generate_unique_name(self): new_name = f"Agent{len(self.agents) + 1}" print(f"New Name: {new_name}") return new_name - - def run_interviews(self): - if self.interview_questions: - dead_agents = [agent for agent in self.agents if (agent.status == "dead" and agent.kind != "zombie")] - living_agents = [agent for agent in self.agents if (agent.status != "dead" and agent.kind != "zombie")] - - for agent in dead_agents + living_agents: - results = [] - for question in self.interview_questions: - metric = question.get("metric", None) - #if agent.status == "dead": - # pd(f"{agent} dead, can't ask questions") - # results.append("Agent is dead, cannot answer questions") - #elif question["who"] == "all" or question["who"] == agent.name: - answer = agent.answer(question["question"]) - if metric: - match = re.search(r"Answer: (\d)", answer) - if match: - score = int(match.group(0)) - else: - score = 0 - self.performance_metrics[metric] += score - answer_data = { - "question": question["question"], - "answer": answer - } - results.append(answer_data) - self.interview_results[agent.name] = results - - def print_matrix(self): - cell_width = 15 # Adjust this value based on your needs - matrix = [[" " * cell_width for _ in range(self.environment.width)] for _ in range(self.environment.height)] - - # Print agents - for agent in self.agents: - matrix[agent.x][agent.y] = "{:<{width}}".format(f"{agent.direction} * {agent.name}", width=cell_width) - - #sys.stdout.write("\033[H") - print("\n\n") - for row in matrix: - print("|".join(row)) - print("-" * (cell_width * self.n - 1)) - - def get_all_objects(self): - all_objects = [obj for loc in self.locations for area in loc.areas for obj in area.objects] - return all_objects - - def get_arr_2D(self): - arr_2D = [["" for _ in range(self.environment.width)] for _ in range(self.environment.height)] - objs = [obj for location in self.environment.locations for area in location.areas for obj in area.objects] - - for x in range(self.environment.height): - for y in range(self.environment.width): - for obj in objs: - if obj.bounds[x][y] != 0: - arr_2D[x][y] = obj.name[0].lower() - - for agent in self.agents: - arr_2D[agent.x][agent.y] = f"{agent}" - - return arr_2D - - def clear_redis(self): - return - redis_connection.delete(f"{self.id}:matrix_state") - redis_connection.delete(f"{self.id}:matrix_states") - redis_connection.delete(f"{self.id}:agent_conversations") - for a in self.agents: - redis_connection.delete(f"{self.id}:conversations:{a.name}") - redis_connection.delete(f"{self.id}:events:{a.name}") - redis_connection.delete(f"{self.id}:agents:{a.name}") - diff --git a/src/reporting.py b/src/reporting.py new file mode 100644 index 0000000..62443a0 --- /dev/null +++ b/src/reporting.py @@ -0,0 +1,112 @@ +from dotenv import load_dotenv +import subprocess +from utils.utils import * + +class Reporting: + def get_server_info(self): + try: + # Run 'uname -a' command + uname_output = subprocess.check_output(['uname', '-a']).decode('utf-8').strip() + return uname_output + except Exception as e: + # Handle any exceptions that may occur + return f"Error getting server info: {str(e)}" + + def all_env_vars(self): + if self.sim_start_time is None: + self.sim_start_time = datetime.now() + + if self.simulation_runtime is None: + self.simulation_runtime = datetime.now() - self.sim_start_time + + total_reflections = 0 + total_metas = 0 + for a in self.agents: + for m in a.memory: + if m.kind == "reflect": + total_reflections += 1 + if m.kind == "meta": + total_metas += 1 + + total_seconds = self.simulation_runtime.total_seconds() + + # Calculate minutes and seconds + minutes = int(total_seconds // 60) + seconds = int(total_seconds % 60) + + # Create a human-readable string + runtime_string = f"{minutes} minute(s) and {seconds} second(s)" + + return { + "id": self.id, + "map": self.environment_file, + "agents": self.scenario_file, + "date": self.sim_start_time.isoformat(), + "width": self.environment.width, + "height": self.environment.width, + "status": self.status, + "runtime": runtime_string, # Include the string representation + "server_info": self.get_server_info(), + "created_at": self.sim_start_time.strftime("%Y-%m-%d %H:%M:%S"), + "model": self.model, + "total_steps": self.steps, + "meta_flag": self.allow_meta_flag, + "reflect_flag": self.allow_reflect_flag, + "conversation_counter": self.conversation_counter, + "total_meta_memories": total_metas, + "total_reflect_memories": total_reflections, + "total_agents": sum(1 for agent in self.agents if agent.kind != 'zombie'), + "total_zombies": sum(1 for agent in self.agents if agent.kind == 'zombie'), + "total_dead": sum(1 for agent in self.agents if agent.status == 'dead'), + "total_alive": sum(1 for agent in self.agents if agent.status != 'dead'), + "llm_call_counter": llm.call_counter, + "avg_llm_calls_per_step": llm.call_counter / self.steps + "avg_runtime_per_step": total_seconds / self.steps, + } + def run_interviews(self): + if self.interview_questions: + dead_agents = [agent for agent in self.agents if (agent.status == "dead" and agent.kind != "zombie")] + living_agents = [agent for agent in self.agents if (agent.status != "dead" and agent.kind != "zombie")] + + for agent in dead_agents + living_agents: + results = [] + for question in self.interview_questions: + metric = question.get("metric", None) + #if agent.status == "dead": + # pd(f"{agent} dead, can't ask questions") + # results.append("Agent is dead, cannot answer questions") + #elif question["who"] == "all" or question["who"] == agent.name: + answer = agent.answer(question["question"]) + if metric: + match = re.search(r"Answer: (\d)", answer) + if match: + score = int(match.group(0)) + else: + score = 0 + self.performance_metrics[metric] += score + answer_data = { + "question": question["question"], + "answer": answer + } + results.append(answer_data) + self.interview_results[agent.name] = results + + def print_agent_memories(self): + for agent in self.agents: + print(f"\nMemories for {agent}:") + for memory in agent.memory: + print(memory) + + def print_matrix(self): + cell_width = 15 # Adjust this value based on your needs + matrix = [[" " * cell_width for _ in range(self.environment.width)] for _ in range(self.environment.height)] + + # Print agents + for agent in self.agents: + matrix[agent.x][agent.y] = "{:<{width}}".format(f"{agent.direction} * {agent.name}", width=cell_width) + + #sys.stdout.write("\033[H") + print("\n\n") + for row in matrix: + print("|".join(row)) + print("-" * (cell_width * self.n - 1)) From 438fa031e4ccf739b6fca5a13e4262413d2de90b Mon Sep 17 00:00:00 2001 From: Jason Date: Wed, 27 Mar 2024 07:08:07 -0700 Subject: [PATCH 31/40] remove trash --- frontend.py | 99 ----------------------------------------------------- 1 file changed, 99 deletions(-) delete mode 100644 frontend.py diff --git a/frontend.py b/frontend.py deleted file mode 100644 index a84d0a5..0000000 --- a/frontend.py +++ /dev/null @@ -1,99 +0,0 @@ -from flask import Flask, request, jsonify, render_template -from redis import Redis -import requests - -app = Flask(__name__) -redis_client = Redis.from_url("redis://localhost:6379") - -@app.route('/get_matrix_state') -def get_matrix_state(): - try: - matrix_state = redis_client.get(':matrix_state') - - matrix = matrix_state.decode('utf-8') if matrix_state else '[]' - - return jsonify(matrix) - - except Exception as e: - return jsonify({'error': str(e)}), 500 - -@app.route('/get_conversations') -def get_conversations(): - try: - # Fetch conversations from Redis queue "agent_conversations" - conversations = redis_client.lrange(':agent_conversations', 0, -1)[::-1] - - # Convert byte strings to regular strings - conversations = [conversation.decode('utf-8') for conversation in conversations] - - return jsonify(conversations) - except Exception as e: - return jsonify({'error': str(e)}), 500 - -@app.route('/') -def index(): - return render_template('frontend.html') - -@app.route('/map') -def map(): - return render_template('map.html') - -@app.route('/embeddings', methods=['POST']) -def embeddings(): - # Get data from the request - data = request.json - - # Extract values from the payload - model = data.get('model') - prompt = data.get('prompt') - - # Create the payload for the POST request to the external API - payload = { - "model": model, - "prompt": prompt - } - - # Make the POST request to the external API - external_api_url = "http://localhost:11434/api/embeddings" - response = requests.post(external_api_url, json=payload) - - # Check if the request was successful (status code 200) - if response.status_code == 200: - # Parse the JSON response from the external API - result = response.json() - return jsonify(result) - else: - # If the request was not successful, return an error message - return jsonify({"error": f"Failed to generate data. Status code: {response.status_code}"}), response.status_code - -@app.route('/generate', methods=['POST']) -def generate(): - # Get data from the request - data = request.json - - # Extract values from the payload - model = data.get('model') - prompt = data.get('prompt') - - # Create the payload for the POST request to the external API - payload = { - "model": model, - "prompt": prompt, - "stream": False - } - - # Make the POST request to the external API - external_api_url = "http://localhost:11434/api/generate" - response = requests.post(external_api_url, json=payload) - - # Check if the request was successful (status code 200) - if response.status_code == 200: - # Parse the JSON response from the external API - result = response.json() - return jsonify(result) - else: - # If the request was not successful, return an error message - return jsonify({"error": f"Failed to generate data. Status code: {response.status_code}"}), response.status_code - -if __name__ == '__main__': - app.run(debug=True,host='0.0.0.0') From 4e150608a08417f6e0fa9612eaa3d633a2f0b48d Mon Sep 17 00:00:00 2001 From: jtoy Date: Wed, 27 Mar 2024 11:50:55 -0700 Subject: [PATCH 32/40] Delete configs/bus_stop.json --- configs/bus_stop.json | 114 ------------------------------------------ 1 file changed, 114 deletions(-) delete mode 100644 configs/bus_stop.json diff --git a/configs/bus_stop.json b/configs/bus_stop.json deleted file mode 100644 index 3cf52b4..0000000 --- a/configs/bus_stop.json +++ /dev/null @@ -1,114 +0,0 @@ -{ - "background": "Six agents gather outside at a bus stop to chat with each other. They talk about their interests and anything they come up with.", - "agents": [ - { - "name": "Natasha", - "description": "Natasha is friendly, outgoing, and effortlessly strikes up conversations. She likes to talk.", - "goal": "Natasha wants to talk about her recent experience with love. She wants to ask the opinions of other agents regarding the relationship. She is also concerned of the other agents and asks them if they are doing good. She talks about anything with the other agents..", - "connections": [ - "Sherlock", - "Viktor", - "Lily", - "James" - ], - "x": 25, - "y": 10, - "actions_blacklist": [ - "move" - ] - }, - { - "name": "James", - "description": "James is shy, reserved, and observes from the sidelines.", - "goal": "James joins the conversation and shares his special skills and hidden talent in singing. He talks about his favorite songs and music genre. He also asks the other agents about their taste in music. He talks about anything with the other agents.", - "connections": [ - "Sherlock", - "Viktor", - "Lily", - "Natasha" - ], - "x": 25, - "y": 15, - "actions_blacklist": [ - "move" - ] - }, - { - "name": "Sherlock", - "description": "Sherlock is funny, lighthearted, and turns moments into laughter. He is quick-witted and charming.", - "goal": "Sherlock tells a joke everytime he gets the chance to. He loves making everyone happy by being silly. He also shares some of his embarassing moments and also asks the other agents about their funny moments. He talks about anything with the other agents.", - "connections": [ - "Natasha", - "Viktor", - "James", - "Paul" - ], - "x": 25, - "y": 20, - "actions_blacklist": [ - "move" - ] - }, - { - "name": "Viktor", - "description": "Viktor likes to take photographs and capture moments. He likes to share his collection of memories from pictures. He likes engaging in a conversation.", - "goal": "Viktor tells the other agents about his love of photography and capturing random sweet moments. He also willingly participates in every conversation with the other agents. He talks about anything with the other agents.", - "connections": [ - "James", - "Natasha", - "Sherlock", - "Paul" - ], - "x": 25, - "y": 25, - "actions_blacklist": [ - "move" - ] - }, - { - "name": "Lily", - "description": "Lily likes to cook and eat delicious foods. She likes sweets the most. She is friendly and talkative.", - "goal": "Lily talks about her interest in cooking and eating sweet foods. She shares some of her favorite recipes and experience cooking them. She also asks the other agents about their favorite foods. She talks about anything with the other agents.", - "connections": [ - "Natasha", - "Viktor", - "Sherlock", - "Paul" - ], - "x": 25, - "y": 30, - "actions_blacklist": [ - "move" - ] - }, - { - "name": "Paul", - "description": "Paul likes to read books especially poems. He is great at words and He is also quick witted.", - "goal": "Paul wants to tell the other agents about his favorite books and poems. He shares his some of the lines of poem he likes. He also tells a silly joke when he has a chance. He talks about anything with the other agents.", - "connections": [ - "Natasha", - "Viktor", - "James", - "Lily" - ], - "x": 25, - "y": 35, - "actions_blacklist": [ - "move" - ] - } - ], - "steps": 50, - "allow_movement": 0, - "perception_range": 50, - "questions": [ - { - "who": "all", - "question": "Highlight the topic or new information that you obtained from talking with the other agents." - }, - { - "who": "all", - "question": "Discuss the exchange of conversations. Who did you enjoy the most talking to?" - } - ] -} \ No newline at end of file From 57c36a0371199787437b22bec95431a917d0a2f1 Mon Sep 17 00:00:00 2001 From: Jason Date: Wed, 27 Mar 2024 12:05:43 -0700 Subject: [PATCH 33/40] cleanups --- src/data.py | 10 ++++++++-- src/matrix.py | 2 +- src/reporting.py | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/data.py b/src/data.py index 24a0106..b7ce219 100644 --- a/src/data.py +++ b/src/data.py @@ -9,6 +9,11 @@ load_dotenv() class Data: + def setup_redis(self): + if os.environ.get("REDIS_URL"): + return redis.Redis.from_url(os.environ.get("REDIS_URL")) + else: + return None def setup_database(self): db_settings = { "database_host": os.environ.get("DB_HOST"), @@ -27,6 +32,7 @@ def setup_database(self): "ssh_private_key": os.environ.get("SSH_PRIVATE_KEY") } cursor = None + conn = None if ssh_host: with SSHTunnelForwarder( (ssh_settings["ssh_host"], ssh_settings["ssh_port"]), @@ -43,7 +49,7 @@ def setup_database(self): database=db_settings["database_name"], ) cursor = conn.cursor() - else: + elif db_settings["database_host"]: conn = psycopg2.connect( user=db_settings["database_username"], password=db_settings["database_password"], @@ -89,6 +95,6 @@ def add_to_logs(self,obj): self.cursor.execute( "INSERT INTO timelines (sim_id, step, substep, step_type, data) VALUES (%s, %s, %s, %s, %s)", (self.id, self.cur_step, self.current_substep, obj["step_type"], json.dumps(obj)),) - print("insert") + #print("insert") self.current_substep += 1 diff --git a/src/matrix.py b/src/matrix.py index ad41218..75d8e7d 100644 --- a/src/matrix.py +++ b/src/matrix.py @@ -71,7 +71,7 @@ def __init__(self, config={}): self.perception_range = PERCEPTION_RANGE self.allow_movement = ALLOW_MOVEMENT self.model = MODEL - self.redis_connection = None + self.redis_connection = self.setup_redis() self.replay = None self.cursor = self.setup_database() diff --git a/src/reporting.py b/src/reporting.py index 62443a0..2bfa314 100644 --- a/src/reporting.py +++ b/src/reporting.py @@ -60,7 +60,7 @@ def all_env_vars(self): "total_dead": sum(1 for agent in self.agents if agent.status == 'dead'), "total_alive": sum(1 for agent in self.agents if agent.status != 'dead'), "llm_call_counter": llm.call_counter, - "avg_llm_calls_per_step": llm.call_counter / self.steps + "avg_llm_calls_per_step": llm.call_counter / self.steps, "avg_runtime_per_step": total_seconds / self.steps, } def run_interviews(self): From 288b6029e042651d299a932ad6117f190b087b0c Mon Sep 17 00:00:00 2001 From: Ivan Date: Thu, 28 Mar 2024 03:06:06 +0800 Subject: [PATCH 34/40] added sidebar component --- web/src/components/RenderLevel.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/web/src/components/RenderLevel.tsx b/web/src/components/RenderLevel.tsx index c4c71af..b65bc8e 100644 --- a/web/src/components/RenderLevel.tsx +++ b/web/src/components/RenderLevel.tsx @@ -143,6 +143,16 @@ const RenderLevel: React.FC<{ simId: string, map?: string | null, img?: string | {renderAgents()}
+ ); } From 68b9b4861e481c90d2b21d0e80b7496ad1618bde Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 29 Mar 2024 05:01:44 -0700 Subject: [PATCH 35/40] pass content --- web/src/components/Sidebar.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/src/components/Sidebar.tsx b/web/src/components/Sidebar.tsx index ff8f2c2..d6409db 100644 --- a/web/src/components/Sidebar.tsx +++ b/web/src/components/Sidebar.tsx @@ -56,10 +56,10 @@ const Sidebar: React.FC = ( }; }, [isPlaying]); - const fetchAudioData = async (sim_id: string, step_id: number, substep_id: number, agent_name: string, lang: string): Promise => { + const fetchAudioData = async (sim_id: string, step_id: number, substep_id: number, agent_name: string, lang: string, content: string): Promise => { try { const res = await fetch( - `${process.env.NEXT_PUBLIC_ASSET_DOMAIN}/audio?mid=${sim_id}&step=${step_id}&substep=${substep_id}&agent=${agent_name}&lang=${lang}`, + `${process.env.NEXT_PUBLIC_ASSET_DOMAIN}/audio?mid=${sim_id}&step=${step_id}&substep=${substep_id}&agent=${agent_name}&lang=${lang}&c=${btoa(content)}`, { mode: 'cors' } ); if (!res.ok) { @@ -113,13 +113,13 @@ const Sidebar: React.FC = ( steps.forEach(step => { if (step instanceof TalkStep) { const talkStep = step as TalkStep; - addToAudioQueue(fetchAudioData(simId, talkStep.stepId, talkStep.substepId, talkStep.fromAgentName, browserLanguage)); + addToAudioQueue(fetchAudioData(simId, talkStep.stepId, talkStep.substepId, talkStep.fromAgentName, browserLanguage,talkStep.message)); return; } if (showThoughts && step instanceof ThoughtStep) { const thoughtStep = step as ThoughtStep; - addToAudioQueue(fetchAudioData(simId, thoughtStep.stepId, thoughtStep.substepId, thoughtStep.agentId, browserLanguage)); + addToAudioQueue(fetchAudioData(simId, thoughtStep.stepId, thoughtStep.substepId, thoughtStep.agentId, browserLanguage,talkStep.content)); return; } }); @@ -239,4 +239,4 @@ const Sidebar: React.FC = ( ); }; -export default Sidebar; \ No newline at end of file +export default Sidebar; From 704521f890d27299cac292cca16ee4a2b0a62097 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 29 Mar 2024 05:22:16 -0700 Subject: [PATCH 36/40] fix coordinates --- src/agents.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/agents.py b/src/agents.py index c274a99..e9ae121 100644 --- a/src/agents.py +++ b/src/agents.py @@ -63,7 +63,13 @@ def __init__(self, agent_data={}): if self.matrix: if self.matrix.action_blacklist: self.actions = [action for action in self.actions if action not in blacklist] - self.matrix.add_to_logs({"agent_id":self.mid,"step_type":"agent_init","x":self.x,"y":self.y,"name":self.name,"goal":self.goal,"kind":self.kind}) + if self.matrix and self.matrix.environment: + valid_coordinates = self.matrix.environment.get_valid_coordinates() + if (self.x, self.y) not in valid_coordinates: + new_position = random.choice(valid_coordinates) + self.x = new_position[0] + self.y = new_position[1] + self.matrix.add_to_logs({"agent_id":self.mid,"step_type":"agent_init","x":self.x,"y":self.y,"name":self.name,"goal":self.goal,"kind":self.kind,"description":self.description,"status":self.status}) def perceived_data_is_same(self): last_step = self.matrix.cur_step - 1 From 8ef3cea41eb7701df38b7cf25e3f3db373a32af3 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 29 Mar 2024 12:56:13 -0700 Subject: [PATCH 37/40] hide panel --- web/src/app/page.tsx | 7 ++++++- web/src/components/RenderLevel.tsx | 10 ++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/web/src/app/page.tsx b/web/src/app/page.tsx index 95e46fe..7d6960d 100644 --- a/web/src/app/page.tsx +++ b/web/src/app/page.tsx @@ -7,12 +7,14 @@ export default function Page() { const [simId, setSimId] = useState(null); const [map, setMapName] = useState(null); const [img, setImgName] = useState(null); + const [hidePanel, setHidePanel] = useState(false); useEffect(() => { const params = new URLSearchParams(window.location.search); const sim_id = params.get('sim_id'); const map = params.get('map'); const img = params.get('img'); + const hidePanel = params.get('hide'); if (sim_id) { setSimId(sim_id); } @@ -22,11 +24,14 @@ export default function Page() { if (img) { setImgName(img); } + if (hidePanel === '1') { + setHidePanel(true); + } }, []); if (!simId) { return null; } - return ; + return } diff --git a/web/src/components/RenderLevel.tsx b/web/src/components/RenderLevel.tsx index e654a36..5ac19bb 100644 --- a/web/src/components/RenderLevel.tsx +++ b/web/src/components/RenderLevel.tsx @@ -28,7 +28,8 @@ async function getData(sim_id: string, fromIndex: number) { } // eslint-disable-next-line @typescript-eslint/no-explicit-any -const RenderLevel: React.FC<{ simId: string, map?: string | null, img?: string | null }> = ({ simId, map, img }) => { +const RenderLevel: React.FC<{ simId: string, map?: string | null, img?: string | null, hidePanel: boolean }> = ({ simId, map, img, hidePanel }) => { + const [isPlaying, setIsPlaying] = useState(true); const [followAgent, setFollowAgent] = useState(undefined); const [levelState, setLevelState] = useState({ stepId: 0, substepId: 0, agents: [] }); @@ -151,9 +152,9 @@ const RenderLevel: React.FC<{ simId: string, map?: string | null, img?: string | stepId={levelState.stepId} substepId={levelState.substepId} level={levelRef.current} + hidePanel={hidePanel} simId={simId} - toggleAudio={toggleAudio} - audioPlayings={audioPlaying} /> + /> ); } @@ -179,10 +180,11 @@ const RenderLevel: React.FC<{ simId: string, map?: string | null, img?: string | stepId={levelState.stepId} substepId={levelState.substepId} level={levelRef.current} + hidePanel={hidePanel} simId={simId} /> ); }; -export default RenderLevel; \ No newline at end of file +export default RenderLevel; From 7ea29d2accbee71b4275866569c936abf63b5605 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 29 Mar 2024 12:56:25 -0700 Subject: [PATCH 38/40] hide panel --- web/src/components/Sidebar.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/web/src/components/Sidebar.tsx b/web/src/components/Sidebar.tsx index d6409db..8fd282c 100644 --- a/web/src/components/Sidebar.tsx +++ b/web/src/components/Sidebar.tsx @@ -15,6 +15,7 @@ interface SidebarProps { setIsPlaying: React.Dispatch>; stepId: number; substepId: number; + hidePanel: boolean; level: Level; simId: string; } @@ -28,7 +29,8 @@ const Sidebar: React.FC = ( stepId, substepId, level, - simId + simId, + hidePanel }) => { const [showThoughts, setShowThoughts] = useState(true); @@ -39,6 +41,10 @@ const Sidebar: React.FC = ( // eslint-disable-next-line @typescript-eslint/no-unused-vars const minimal_audio_delay = 500; // delay in between playing audio clips + //hidePanel = false; + //WTF pass this + + useEffect(() => { let interval: NodeJS.Timeout; @@ -169,6 +175,9 @@ const Sidebar: React.FC = ( const renderTimeline = () => { if(!agentPlacement) return null; + if (hidePanel) { + return null; + } const steps = agentPlacement.steps.toReversed(); @@ -191,6 +200,9 @@ const Sidebar: React.FC = ( }; const renderControls = () => { + if (hidePanel) { + return null; + } return(
@@ -214,6 +226,9 @@ const Sidebar: React.FC = (
) }; + if (hidePanel) { + return null; + } return ( // JSX for the Sidebar component goes here From 1a2d0d3d98a3ee5d409c0a634cd694ce21772003 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 30 Mar 2024 05:02:54 -0700 Subject: [PATCH 39/40] better debug tools --- requirements.txt | 2 ++ utils/timeline_info.py | 8 +++++++ web/.env.development | 1 + web/src/components/Sidebar.tsx | 39 ++++++++++++++++++++-------------- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/requirements.txt b/requirements.txt index f1564f4..9429723 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,5 @@ jsonlines nltk Pillow groq +sshtunnel +psycopg2 diff --git a/utils/timeline_info.py b/utils/timeline_info.py index f0ca8bf..a962b5b 100644 --- a/utils/timeline_info.py +++ b/utils/timeline_info.py @@ -14,6 +14,10 @@ world = "" last_death_at_step = 0 last_death_at_row = 0 +if "--convo" in sys.argv: + convos = True +else + convos = False with jsonlines.open(path, "r") as jsonl_file: for i, row in enumerate(jsonl_file): obj = json.loads(row) @@ -21,6 +25,8 @@ step_type = obj.get("step_type") step_types[step_type] = step_types.get(step_type, 0) + 1 sim_id = obj.get("sim_id") + if convos and step_type == "talk": + print(obj) if step_type == "agent_init": agents[obj["agent_id"]] = {} agents[obj["agent_id"]]["name"] = obj["name"] @@ -36,6 +42,8 @@ scenario = obj["data"]["scenario"] world = obj["data"]["environment"] + + total_steps +=1 human_agents = dict(filter(lambda item: item[1]["kind"] == "human", agents.items())) print(f"matrix info sim_id: {sim_id}") diff --git a/web/.env.development b/web/.env.development index 146797f..3e43d68 100644 --- a/web/.env.development +++ b/web/.env.development @@ -1,3 +1,4 @@ REDIS_URL=redis://localhost:6379 NEXT_PUBLIC_API_URL=http://localhost:3000/api/simulations NEXT_PUBLIC_MOUNTED=false +NEXT_PUBLIC_ASSET_DOMAIN=http://localhost:3004/ diff --git a/web/src/components/Sidebar.tsx b/web/src/components/Sidebar.tsx index 8fd282c..ff91716 100644 --- a/web/src/components/Sidebar.tsx +++ b/web/src/components/Sidebar.tsx @@ -63,23 +63,30 @@ const Sidebar: React.FC = ( }, [isPlaying]); const fetchAudioData = async (sim_id: string, step_id: number, substep_id: number, agent_name: string, lang: string, content: string): Promise => { - try { - const res = await fetch( - `${process.env.NEXT_PUBLIC_ASSET_DOMAIN}/audio?mid=${sim_id}&step=${step_id}&substep=${substep_id}&agent=${agent_name}&lang=${lang}&c=${btoa(content)}`, - { mode: 'cors' } - ); - if (!res.ok) { - throw new Error('Failed to fetch data'); - } - - const audioBlob = await res.blob(); - const audioUrl = URL.createObjectURL(audioBlob); - return audioUrl; - } catch (error) { - console.error('Error fetching audio:', error); - return ""; + try { + const res = await fetch( + `${process.env.NEXT_PUBLIC_ASSET_DOMAIN}/audio?mid=${sim_id}&step=${step_id}&substep=${substep_id}&agent=${agent_name}&lang=${lang}&c=${btoa(content)}`, + { mode: 'cors' } + ); + if (!res.ok) { + throw new Error('Failed to fetch data'); } - }; + + const audioBlob = await res.blob(); + const audioUrl = URL.createObjectURL(audioBlob); + + // Preload the audio file + const audio = new Audio(audioUrl); + audio.preload = 'auto'; + audio.load(); + + return audioUrl; + } catch (error) { + console.error('Error fetching audio:', error); + return ""; + } +}; + const addToAudioQueue = (audioClipUrl: Promise) => { setAudioQueue((oldQueue) => [...oldQueue, audioClipUrl]); From ad5e14eb36b1b0ab07d9526fe22694541f9e3e5d Mon Sep 17 00:00:00 2001 From: jtoy <> Date: Sat, 30 Mar 2024 12:17:44 +0000 Subject: [PATCH 40/40] add more logging tools --- utils/timeline_info.py | 40 ++++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/utils/timeline_info.py b/utils/timeline_info.py index a962b5b..7ca2ca4 100644 --- a/utils/timeline_info.py +++ b/utils/timeline_info.py @@ -16,7 +16,7 @@ last_death_at_row = 0 if "--convo" in sys.argv: convos = True -else +else: convos = False with jsonlines.open(path, "r") as jsonl_file: for i, row in enumerate(jsonl_file): @@ -26,7 +26,10 @@ step_types[step_type] = step_types.get(step_type, 0) + 1 sim_id = obj.get("sim_id") if convos and step_type == "talk": - print(obj) + a = agents[obj["agent_id"]]['name'] + b = agents[obj["to_id"]]['name'] + m = obj["content"] + print(f"{a} said to {b}: {m}") if step_type == "agent_init": agents[obj["agent_id"]] = {} agents[obj["agent_id"]]["name"] = obj["name"] @@ -45,19 +48,20 @@ total_steps +=1 -human_agents = dict(filter(lambda item: item[1]["kind"] == "human", agents.items())) -print(f"matrix info sim_id: {sim_id}") -print(f"matrix info scenario: {scenario}") -print(f"matrix info world: {world}") -print(f"total individual steps {total_steps}") -print(f"simulation steps {max_step}") -print(f"step types {step_types}") -print(f"average substeps per step {total_steps/max_step}") -print(f"human agent count: {len(human_agents)}") -print(f"deaths: {deaths}") -print(f"last_death_at_step {last_death_at_step}") -print(f"last_death_at_row {last_death_at_row}") -#print(f"human agents {human_agents}") -print(f"total agent count: {len(agents)}") -#print(f"agent actions {agent_actions}") -print(f"completed: {completed}") +if not convos: + human_agents = dict(filter(lambda item: item[1]["kind"] == "human", agents.items())) + print(f"matrix info sim_id: {sim_id}") + print(f"matrix info scenario: {scenario}") + print(f"matrix info world: {world}") + print(f"total individual steps {total_steps}") + print(f"simulation steps {max_step}") + print(f"step types {step_types}") + print(f"average substeps per step {total_steps/max_step}") + print(f"human agent count: {len(human_agents)}") + print(f"deaths: {deaths}") + print(f"last_death_at_step {last_death_at_step}") + print(f"last_death_at_row {last_death_at_row}") + #print(f"human agents {human_agents}") + print(f"total agent count: {len(agents)}") + #print(f"agent actions {agent_actions}") + print(f"completed: {completed}")