From ee7de61025ec53df21fff4344f9f6d08b738c056 Mon Sep 17 00:00:00 2001 From: better629 Date: Tue, 3 Oct 2023 17:58:06 +0800 Subject: [PATCH] update code and fix reference --- examples/st_game/.gitignore | 1 + examples/st_game/README.md | 6 +- examples/st_game/__init__.py | 3 + examples/st_game/actions/__init__.py | 3 + .../st_game/actions/agent_chat_sum_rel.py | 4 +- examples/st_game/actions/decide_to_talk.py | 4 +- examples/st_game/actions/gen_iter_chat_utt.py | 8 +- .../st_game/actions/new_decomp_schedule.py | 4 +- .../st_game/actions/run_reflect_action.py | 29 ++++--- examples/st_game/actions/st_action.py | 2 +- examples/st_game/actions/summarize_conv.py | 2 +- examples/st_game/maze.py | 60 ++++++------- examples/st_game/maze_environment.py | 4 +- examples/st_game/memory/agent_memory.py | 13 ++- examples/st_game/memory/retrieve.py | 31 ++++--- examples/st_game/memory/scratch.py | 6 +- examples/st_game/memory/spatial_memory.py | 4 +- examples/st_game/plan/__init__.py | 3 + examples/st_game/plan/converse.py | 18 ++-- examples/st_game/plan/st_plan.py | 12 +-- examples/st_game/prompts/run_gpt_prompts.py | 10 ++- examples/st_game/reflect/__init__.py | 9 -- .../st_game/reflect/ga_prompt_generator.py | 36 ++------ examples/st_game/reflect/reflect.py | 25 +++--- examples/st_game/reflect/st_reflect.py | 11 +-- examples/st_game/roles/__init__.py | 3 + examples/st_game/roles/st_role.py | 87 ++++++++++++------- examples/st_game/run_st_game.py | 3 + examples/st_game/tests/actions/__init__.py | 3 + .../tests/actions/test_summarize_conv.py | 4 + examples/st_game/utils/__init__.py | 3 + examples/st_game/utils/mg_ga_transform.py | 4 +- examples/st_game/utils/utils.py | 63 +++++++++----- metagpt/roles/role.py | 1 - 34 files changed, 265 insertions(+), 214 deletions(-) create mode 100644 examples/st_game/.gitignore create mode 100644 examples/st_game/actions/__init__.py create mode 100644 examples/st_game/plan/__init__.py create mode 100644 examples/st_game/roles/__init__.py create mode 100644 examples/st_game/tests/actions/__init__.py create mode 100644 examples/st_game/utils/__init__.py diff --git a/examples/st_game/.gitignore b/examples/st_game/.gitignore new file mode 100644 index 000000000..2cc25948c --- /dev/null +++ b/examples/st_game/.gitignore @@ -0,0 +1 @@ +storage/test* \ No newline at end of file diff --git a/examples/st_game/README.md b/examples/st_game/README.md index f0aef9923..6b6661f42 100644 --- a/examples/st_game/README.md +++ b/examples/st_game/README.md @@ -1,5 +1,9 @@ ## Stanford Town Game -执行入口为:`python3 run_st_game.py "Host a activity"` +### 后端服务启动 +执行入口为:`python3 run_st_game.py "Host a open lunch party at 13:00 pm" "base_the_ville_isabella_maria_klaus" "test_sim" 10` `idea`为用户给第一个Agent的用户心声,并通过这个心声进行传播,看最后多智能体是否达到举办、参加活动的目标。 + +### 前端服务启动 +进入`generative_agents/environment/frontend_server`,使用`~~python manage.py runserver~~`启动前端服务。 diff --git a/examples/st_game/__init__.py b/examples/st_game/__init__.py index e69de29bb..2bcf8efd0 100644 --- a/examples/st_game/__init__.py +++ b/examples/st_game/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Desc : diff --git a/examples/st_game/actions/__init__.py b/examples/st_game/actions/__init__.py new file mode 100644 index 000000000..2bcf8efd0 --- /dev/null +++ b/examples/st_game/actions/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Desc : diff --git a/examples/st_game/actions/agent_chat_sum_rel.py b/examples/st_game/actions/agent_chat_sum_rel.py index 9a26ccf02..c6cfda806 100644 --- a/examples/st_game/actions/agent_chat_sum_rel.py +++ b/examples/st_game/actions/agent_chat_sum_rel.py @@ -5,8 +5,8 @@ from metagpt.logs import logger from metagpt.schema import Message -from ..roles.st_role import STRole -from ..actions.st_action import STAction +from examples.st_game.roles.st_role import STRole +from examples.st_game.actions.st_action import STAction class AgentChatSumRel(STAction): diff --git a/examples/st_game/actions/decide_to_talk.py b/examples/st_game/actions/decide_to_talk.py index 5a3452d98..f8df51d26 100644 --- a/examples/st_game/actions/decide_to_talk.py +++ b/examples/st_game/actions/decide_to_talk.py @@ -5,8 +5,8 @@ from metagpt.logs import logger from metagpt.schema import Message -from ..roles.st_role import STRole -from ..actions.st_action import STAction +from examples.st_game.roles.st_role import STRole +from examples.st_game.actions.st_action import STAction class DecideToTalk(STAction): diff --git a/examples/st_game/actions/gen_iter_chat_utt.py b/examples/st_game/actions/gen_iter_chat_utt.py index ff4728ad8..b3e67e7c6 100644 --- a/examples/st_game/actions/gen_iter_chat_utt.py +++ b/examples/st_game/actions/gen_iter_chat_utt.py @@ -5,10 +5,10 @@ from metagpt.logs import logger from metagpt.schema import Message -from ..roles.st_role import STRole -from ..actions.st_action import STAction -from ..utils.utils import extract_first_json_dict -from ..maze import Maze +from examples.st_game.roles.st_role import STRole +from examples.st_game.actions.st_action import STAction +from examples.st_game.utils.utils import extract_first_json_dict +from examples.st_game.maze import Maze class GenIterChatUTT(STAction): diff --git a/examples/st_game/actions/new_decomp_schedule.py b/examples/st_game/actions/new_decomp_schedule.py index bf7bbeeff..f38bb56cc 100644 --- a/examples/st_game/actions/new_decomp_schedule.py +++ b/examples/st_game/actions/new_decomp_schedule.py @@ -7,8 +7,8 @@ import datetime from metagpt.logs import logger from metagpt.schema import Message -from ..roles.st_role import STRole -from ..actions.st_action import STAction +from examples.st_game.roles.st_role import STRole +from examples.st_game.actions.st_action import STAction class NewDecompSchedule(STAction): diff --git a/examples/st_game/actions/run_reflect_action.py b/examples/st_game/actions/run_reflect_action.py index d4a2b2f28..ab83a22ee 100644 --- a/examples/st_game/actions/run_reflect_action.py +++ b/examples/st_game/actions/run_reflect_action.py @@ -3,11 +3,12 @@ # @Desc : Integration Reflect Action import re -from ..roles.st_role import STRole -from ..actions.st_action import STAction -from ..memory.agent_memory import BasicMemory from metagpt.logs import logger +from examples.st_game.actions.st_action import STAction +from examples.st_game.memory.agent_memory import BasicMemory + + # Run GPT Prompt Focal Point method class AgentFocusPt(STAction): @@ -31,8 +32,8 @@ class AgentFocusPt(STAction): def _func_fail_default_resp(self) -> str: pass - async def run(self, role: STRole, statements: str, n: int, test_input=None) -> str: - def create_prompt_input(role: STRole, statements, n, test_input=None): + async def run(self, role: "STRole", statements: str, n: int, test_input=None) -> str: + def create_prompt_input(role: "STRole", statements, n, test_input=None): prompt_input = [statements, str(n)] return prompt_input @@ -77,8 +78,8 @@ class AgentInsightAndGuidance(STAction): def _func_fail_default_resp(self) -> str: pass - async def run(self, role: STRole, statements: str, n: int, test_input=None) -> str: - def create_prompt_input(role: STRole, statements, n, test_input=None): + async def run(self, role: "STRole", statements: str, n: int, test_input=None) -> str: + def create_prompt_input(role: "STRole", statements, n, test_input=None): prompt_input = [statements, str(n)] return prompt_input @@ -113,7 +114,7 @@ class AgentEventTriple(STAction): def _func_fail_default_resp(self) -> str: pass - async def run(self, statements: str, role: STRole, verbose=False) -> str: + async def run(self, statements: str, role: "STRole", verbose=False) -> str: def create_prompt_input(statements, role): if "(" in statements: statements = statements.split("(")[-1].split(")")[0] @@ -151,8 +152,8 @@ class AgentEventPoignancy(STAction): def _func_fail_default_resp(self) -> str: pass - async def run(self, role: STRole, statements: str, test_input=None, verbose=False) -> str: - def create_prompt_input(role: STRole, statements: str, test_input=None): + async def run(self, role: "STRole", statements: str, test_input=None, verbose=False) -> str: + def create_prompt_input(role: "STRole", statements: str, test_input=None): prompt_input = [role._rc.scratch.name, role._rc.scratch.get_str_iss(), role._rc.scratch.name, @@ -192,8 +193,8 @@ class AgentChatPoignancy(STAction): def _func_fail_default_resp(self) -> str: pass - async def run(self, role: STRole, statements: str, test_input=None, verbose=False) -> str: - def create_prompt_input(role: STRole, statements, test_input=None): + async def run(self, role: "STRole", statements: str, test_input=None, verbose=False) -> str: + def create_prompt_input(role: "STRole", statements, test_input=None): prompt_input = [role._rc.scratch.name, role._rc.scratch.get_str_iss(), role._rc.scratch.name, @@ -232,7 +233,7 @@ class AgentPlanThoughtOnConvo(STAction): def _func_fail_default_resp(self) -> str: pass - async def run(self, role: STRole, statements: str, test_input=None, verbose=False) -> str: + async def run(self, role: "STRole", statements: str, test_input=None, verbose=False) -> str: def create_prompt_input(role, statements, test_input=None): prompt_input = [statements, role._rc.scratch.name, @@ -268,7 +269,7 @@ class AgentMemoryOnConvo(STAction): def _func_fail_default_resp(self) -> str: pass - async def run(self, role: STRole, statements: str, test_input=None, verbose=False) -> str: + async def run(self, role: "STRole", statements: str, test_input=None, verbose=False) -> str: def create_prompt_input(role, statements, test_input=None): prompt_input = [statements, role._rc.scratch.name, diff --git a/examples/st_game/actions/st_action.py b/examples/st_game/actions/st_action.py index 15a4e58b6..60f73f36d 100644 --- a/examples/st_game/actions/st_action.py +++ b/examples/st_game/actions/st_action.py @@ -9,7 +9,7 @@ import json from metagpt.actions.action import Action from metagpt.schema import Message -from ..utils.const import PROMPTS_DIR +from examples.st_game.utils.const import PROMPTS_DIR class STAction(Action): diff --git a/examples/st_game/actions/summarize_conv.py b/examples/st_game/actions/summarize_conv.py index 9c355e89b..7d4840f06 100644 --- a/examples/st_game/actions/summarize_conv.py +++ b/examples/st_game/actions/summarize_conv.py @@ -4,7 +4,7 @@ from metagpt.schema import Message -from ..actions.st_action import STAction +from examples.st_game.actions.st_action import STAction class SummarizeConv(STAction): diff --git a/examples/st_game/maze.py b/examples/st_game/maze.py index 98edbff7c..73440a2a3 100644 --- a/examples/st_game/maze.py +++ b/examples/st_game/maze.py @@ -14,8 +14,8 @@ import json import math from pathlib import Path import networkx as nx -from .utils.const import MAZE_ASSET_PATH -from .utils.utils import read_csv_to_list +from utils.const import MAZE_ASSET_PATH +from utils.utils import read_csv_to_list class Maze: @@ -212,13 +212,13 @@ class Maze: else: self.address_tiles[add] = set([(j, i)]) - # Build an nx.Graph. - grid_graph = nx.grid_2d_graph(m=self.maze_width, n=self.maze_height) - for i in range(self.maze_height): - for j in range(self.maze_width): - if self.collision_maze[i][j] != 0: - grid_graph.remove_node((i, j)) - self.nx_graph = grid_graph + # # Build an nx.Graph. + # grid_graph = nx.grid_2d_graph(m=self.maze_width, n=self.maze_height) + # for i in range(self.maze_height): + # for j in range(self.maze_width): + # if self.collision_maze[i][j] != 0: + # grid_graph.remove_node((i, j)) + # self.nx_graph = grid_graph def turn_coordinate_to_tile(self, px_coordinate: tuple[int, int]) -> tuple[int, int]: """ @@ -389,24 +389,24 @@ class Maze: if event[0] == subject: self.tiles[tile[1]][tile[0]]["events"].remove(event) - def _find_closest_node(self, coords: tuple[int, int]) -> tuple[int, int]: - target_coords = self.nx_graph.nodes - min_dist = None - closest_coordinate = None - for target in target_coords: - dist = math.dist(coords, target) - if not closest_coordinate: - min_dist = dist - closest_coordinate = target - else: - if min_dist > dist: - min_dist = dist - closest_coordinate = target - return closest_coordinate - - def find_path(self, start: tuple[int, int], end: tuple[int, int]) -> list[tuple[int, int]]: - if start not in self.nx_graph.nodes: - start = self._find_closest_node(start) - if end not in self.nx_graph.nodes: - end = self._find_closest_node(end) - return nx.shortest_path(self.nx_graph, start, end) + # def _find_closest_node(self, coords: tuple[int, int]) -> tuple[int, int]: + # target_coords = self.nx_graph.nodes + # min_dist = None + # closest_coordinate = None + # for target in target_coords: + # dist = math.dist(coords, target) + # if not closest_coordinate: + # min_dist = dist + # closest_coordinate = target + # else: + # if min_dist > dist: + # min_dist = dist + # closest_coordinate = target + # return closest_coordinate + # + # def find_path(self, start: tuple[int, int], end: tuple[int, int]) -> list[tuple[int, int]]: + # if start not in self.nx_graph.nodes: + # start = self._find_closest_node(start) + # if end not in self.nx_graph.nodes: + # end = self._find_closest_node(end) + # return nx.shortest_path(self.nx_graph, start, end) diff --git a/examples/st_game/maze_environment.py b/examples/st_game/maze_environment.py index 94f39cbff..e0a16f3de 100644 --- a/examples/st_game/maze_environment.py +++ b/examples/st_game/maze_environment.py @@ -7,12 +7,12 @@ from pydantic import Field from metagpt.environment import Environment from metagpt.roles.role import Role -from .maze import Maze +from maze import Maze class MazeEnvironment(Environment): - maze: Maze = Field(default=Maze) + maze: Maze = Field(default_factory=Maze) def add_role(self, role: Role): role.set_env(self) diff --git a/examples/st_game/memory/agent_memory.py b/examples/st_game/memory/agent_memory.py index 8cce0964d..60aa4ae81 100644 --- a/examples/st_game/memory/agent_memory.py +++ b/examples/st_game/memory/agent_memory.py @@ -2,11 +2,12 @@ # -*- coding: utf-8 -*- # @Desc : BasicMemory,AgentMemory实现 -from metagpt.memory.memory import Memory -from metagpt.schema import Message import json from datetime import datetime +from metagpt.memory.memory import Memory +from metagpt.schema import Message + class BasicMemory(Message): @@ -93,13 +94,13 @@ class AgentMemory(Memory): 3. kw_strength.json """ - def __init__(self, memory_saved: str): + def __init__(self): """ AgentMemory类继承自Memory类,重写storage替代GA中id_to_node,一方面存储所有信息,一方面作为JSON转化 index存储与不同Agent的chat信息 @李嵩@张凯 这里的storage是List,你们需要写一个JSON转化器,将List修改为node.json一致的格式 """ - super.__init__() + super(AgentMemory, self).__init__() self.id_to_node = dict() # TODO jiayi add self.storage: list[BasicMemory] = [] # 重写Stroage,存储BasicMemory所有节点 self.event_list = [] # 存储event记忆 @@ -113,6 +114,10 @@ class AgentMemory(Memory): self.kw_strength_event = dict() # 关键词影响存储 self.kw_strength_thought = dict() + # self.load(memory_saved) + + def set_mem_path(self, memory_saved: str): + self.memory_saved = memory_saved self.load(memory_saved) def save(self, memory_saved: str): diff --git a/examples/st_game/memory/retrieve.py b/examples/st_game/memory/retrieve.py index 423ff224a..9f19a41d4 100644 --- a/examples/st_game/memory/retrieve.py +++ b/examples/st_game/memory/retrieve.py @@ -8,11 +8,12 @@ from typing import Union from numpy import dot from numpy.linalg import norm -from ..memory.agent_memory import AgentMemory, BasicMemory -from ..utils.utils import get_embedding -from ..roles.st_role import STRole +from examples.st_game.memory.agent_memory import AgentMemory, BasicMemory +from examples.st_game.utils.utils import get_embedding -def agent_retrieve(agent_memory: AgentMemory, curr_time: datetime.datetime, memory_forget: float, query: str, topk: int = 4) -> list[BasicMemory]: + +def agent_retrieve(agent_memory: AgentMemory, curr_time: datetime.datetime, memory_forget: float, query: str, + topk: int = 4) -> list[BasicMemory]: """ Retrieve需要集合Role使用,原因在于Role才具有AgentMemory,scratch 逻辑:Role调用该函数,self._rc.AgentMemory,self._rc.scratch.curr_time,self._rc.scratch.memory_forget @@ -46,25 +47,27 @@ def agent_retrieve(agent_memory: AgentMemory, curr_time: datetime.datetime, memo result = top_highest_x_values(total_dict, topk) - return result # 返回的是一个BasicMemory列表 + return result # 返回的是一个BasicMemory列表 -def new_agent_retrieve(strole: STRole, focus_points: list, n_count = 30): + +def new_agent_retrieve(strole: "STRole", focus_points: list, n_count=30): """ 输入为Strole,关注点列表,返回记忆数量 输出为字典,键为focus_point,值为对应的记忆列表 """ - retrieved = dict() - for focal_pt in focus_points: + retrieved = dict() + for focal_pt in focus_points: nodes = [[i.last_accessed, i] - for i in strole._rc.memory.event_list + strole._rc.memory.thought_list - if "idle" not in i.embedding_key] + for i in strole._rc.memory.event_list + strole._rc.memory.thought_list + if "idle" not in i.embedding_key] nodes = sorted(nodes, key=lambda x: x[0]) nodes = [i for created, i in nodes] - results = agent_retrieve(strole._rc.memory, strole._rc.scratch.curr_time, strole._rc.scratch.recency_decay, focal_pt, n_count) - for n in results: + results = agent_retrieve(strole._rc.memory, strole._rc.scratch.curr_time, strole._rc.scratch.recency_decay, + focal_pt, n_count) + for n in results: n.last_accessed = strole._rc.scratch.curr_time - retrieved[focal_pt] = results + retrieved[focal_pt] = results def top_highest_x_values(d, x): @@ -159,4 +162,4 @@ def normalize_score_floats(score_list, target_min, target_max): score_list[i]['relevance'] = relevance_list[i] score_list[i]['recency'] = recency_list[i] - return score_list \ No newline at end of file + return score_list diff --git a/examples/st_game/memory/scratch.py b/examples/st_game/memory/scratch.py index 972ccd678..39e116fef 100644 --- a/examples/st_game/memory/scratch.py +++ b/examples/st_game/memory/scratch.py @@ -5,11 +5,11 @@ import datetime import json -from ..utils.utils import check_if_file_exists +from examples.st_game.utils.utils import check_if_file_exists class Scratch: - def __init__(self, f_saved): + def __init__(self): # 类别1:人物超参 self.vision_r = 4 self.att_bandwidth = 3 @@ -77,6 +77,7 @@ class Scratch: self.act_path_set = False self.planned_path = [] + def set_scratch_path(self, f_saved: str): if check_if_file_exists(f_saved): # If we have a bootstrap file, load that here. scratch_load = json.load(open(f_saved)) @@ -510,4 +511,3 @@ class Scratch: minute = curr_min_sum % 60 ret += f"{hour:02}:{minute:02} || {row[0]}\n" return ret - \ No newline at end of file diff --git a/examples/st_game/memory/spatial_memory.py b/examples/st_game/memory/spatial_memory.py index 73bdc552b..f9a4847c8 100644 --- a/examples/st_game/memory/spatial_memory.py +++ b/examples/st_game/memory/spatial_memory.py @@ -10,8 +10,10 @@ import os class MemoryTree: - def __init__(self, f_saved: str) -> None: + def __init__(self) -> None: self.tree = {} + + def set_mem_path(self, f_saved: str): if os.path.isfile(f_saved) and os.path.exists(f_saved): with open(f_saved) as f: self.tree = json.load(f) diff --git a/examples/st_game/plan/__init__.py b/examples/st_game/plan/__init__.py new file mode 100644 index 000000000..2bcf8efd0 --- /dev/null +++ b/examples/st_game/plan/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Desc : diff --git a/examples/st_game/plan/converse.py b/examples/st_game/plan/converse.py index cf1d92f08..c740629ea 100644 --- a/examples/st_game/plan/converse.py +++ b/examples/st_game/plan/converse.py @@ -6,11 +6,11 @@ from typing import Union, Tuple from metagpt.logs import logger -from ..maze import Maze -from ..roles.st_role import STRole -from ..memory.retrieve import new_retrieve -from ..actions.agent_chat_sum_rel import AgentChatSumRel -from ..actions.gen_iter_chat_utt import GenIterChatUTT +from examples.st_game.maze import Maze +from examples.st_game.roles.st_role import STRole +from examples.st_game.memory.retrieve import new_agent_retrieve +from examples.st_game.actions.agent_chat_sum_rel import AgentChatSumRel +from examples.st_game.actions.gen_iter_chat_utt import GenIterChatUTT def agent_conversation(maze: Maze, init_role: STRole, target_role: STRole) -> list[str]: @@ -23,7 +23,7 @@ def agent_conversation(maze: Maze, init_role: STRole, target_role: STRole) -> li target_scratch = target_role._rc.scratch focal_points = [f"{target_scratch.name}"] - retrieved = new_retrieve(init_role, focal_points, 50) + retrieved = new_agent_retrieve(init_role, focal_points, 50) relationship = generate_summarize_agent_relationship(init_role, target_role, retrieved) print("-------- relationship: ", relationship) last_chat = "" @@ -36,7 +36,7 @@ def agent_conversation(maze: Maze, init_role: STRole, target_role: STRole) -> li else: focal_points = [f"{relationship}", f"{target_scratch.name} is {target_scratch.act_description}"] - retrieved = new_retrieve(init_role, focal_points, 15) + retrieved = new_agent_retrieve(init_role, focal_points, 15) utt, end = generate_one_utterance(maze, init_role, target_role, retrieved, curr_chat) curr_chat += [[scratch.name, utt]] @@ -44,7 +44,7 @@ def agent_conversation(maze: Maze, init_role: STRole, target_role: STRole) -> li break focal_points = [f"{scratch.name}"] - retrieved = new_retrieve(target_role, focal_points, 50) + retrieved = new_agent_retrieve(target_role, focal_points, 50) relationship = generate_summarize_agent_relationship(target_role, init_role, retrieved) print("-------- relationship: ", relationship) last_chat = "" @@ -57,7 +57,7 @@ def agent_conversation(maze: Maze, init_role: STRole, target_role: STRole) -> li else: focal_points = [f"{relationship}", f"{scratch.name} is {scratch.act_description}"] - retrieved = new_retrieve(target_role, focal_points, 15) + retrieved = new_agent_retrieve(target_role, focal_points, 15) utt, end = generate_one_utterance(maze, target_role, init_role, retrieved, curr_chat) curr_chat += [[target_scratch.name, utt]] diff --git a/examples/st_game/plan/st_plan.py b/examples/st_game/plan/st_plan.py index 85e40ed9e..9bdfa28a2 100644 --- a/examples/st_game/plan/st_plan.py +++ b/examples/st_game/plan/st_plan.py @@ -7,12 +7,12 @@ from typing import Union, Tuple from datetime import datetime import math -from ..maze import Maze -from ..plan.converse import agent_conversation -from ..roles.st_role import STRole -from ..actions.decide_to_talk import DecideToTalk -from ..actions.summarize_conv import SummarizeConv -from ..actions.new_decomp_schedule import NewDecompSchedule +from examples.st_game.maze import Maze +from examples.st_game.plan.converse import agent_conversation +from examples.st_game.roles.st_role import STRole +from examples.st_game.actions.decide_to_talk import DecideToTalk +from examples.st_game.actions.summarize_conv import SummarizeConv +from examples.st_game.actions.new_decomp_schedule import NewDecompSchedule def plan(role: STRole, maze: Maze, roles: list[STRole], new_day: bool, retrieved: dict): diff --git a/examples/st_game/prompts/run_gpt_prompts.py b/examples/st_game/prompts/run_gpt_prompts.py index d817d5907..34f5ce79d 100644 --- a/examples/st_game/prompts/run_gpt_prompts.py +++ b/examples/st_game/prompts/run_gpt_prompts.py @@ -2,11 +2,12 @@ # -*- coding: utf-8 -*- # @Desc : 调用Prompts中模板,实现相关Action -from wrapper_prompt import special_response_generate, prompt_generate -from memory.scratch import Scratch -from examples.st_game.memory.agent_memory import BasicMemory import json +from examples.st_game.prompts.wrapper_prompt import special_response_generate, prompt_generate +from examples.st_game.memory.scratch import Scratch +from examples.st_game.memory.agent_memory import BasicMemory + def get_poignancy_action(scratch: Scratch, content: BasicMemory.content) -> str: """ @@ -31,7 +32,8 @@ def get_poignancy_action(scratch: Scratch, content: BasicMemory.content) -> str: return str(poi_dict['poignancy']) # 将返回值强制转换为字符串 except json.JSONDecodeError as e: return poignancy - + + def get_poignancy_chat(scratch: Scratch, content: BasicMemory.content) -> str: """ 衡量会话心酸度 diff --git a/examples/st_game/reflect/__init__.py b/examples/st_game/reflect/__init__.py index 6acb42c9e..da486a131 100644 --- a/examples/st_game/reflect/__init__.py +++ b/examples/st_game/reflect/__init__.py @@ -1,12 +1,3 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # @Desc : reflection module - -from metagpt.reflect import agent_reflect -from metagpt.reflect import ga_prompt_generator -__all__ = [ - "agent_reflect", - "LongTermMemory", - "ga_po" - "ga_prompt_generator" -] diff --git a/examples/st_game/reflect/ga_prompt_generator.py b/examples/st_game/reflect/ga_prompt_generator.py index 6b7bdbad7..81ed5e7f7 100644 --- a/examples/st_game/reflect/ga_prompt_generator.py +++ b/examples/st_game/reflect/ga_prompt_generator.py @@ -37,19 +37,19 @@ async def final_response(prompt, special_instruction, example_output=None): def prompt_generate(curr_input, prompt_lib_file): """ - Takes in the current input (e.g. comment that you want to classifiy) and + Takes in the current input (e.g. comment that you want to classifiy) and the path to a prompt file. The prompt file contains the raw str prompt that - will be used, which contains the following substr: !! -- this - function replaces this substr with the actual curr_input to produce the - final promopt that will be sent to the GPT3 server. + will be used, which contains the following substr: !! -- this + function replaces this substr with the actual curr_input to produce the + final promopt that will be sent to the GPT3 server. ARGS: curr_input: the input we want to feed in (IF THERE ARE MORE THAN ONE INPUT, THIS CAN BE A LIST.) - prompt_lib_file: the path to the promopt file. - RETURNS: - a str prompt that will be sent to OpenAI's GPT server. + prompt_lib_file: the path to the promopt file. + RETURNS: + a str prompt that will be sent to OpenAI's GPT server. """ - if type(curr_input) is type("string"): + if isinstance(curr_input, str): curr_input = [curr_input] curr_input = [str(i) for i in curr_input] @@ -62,23 +62,3 @@ def prompt_generate(curr_input, prompt_lib_file): prompt = prompt.split( "###")[1] return prompt.strip() - -# 使用OpenAI embedding库进行存储 - - -def embedding(query): - """ - Generates an embedding for the given query. - - Args: - query (str): The text query to be embedded. - - Returns: - str: The embedding key generated for the query. - """ - embedding_result = openai.Embedding.create( - model="text-embedding-ada-002", - input=query - ) - embedding_key = embedding_result['data'][0]["embedding"] - return embedding_key diff --git a/examples/st_game/reflect/reflect.py b/examples/st_game/reflect/reflect.py index 358870d4c..6c19cf3fc 100644 --- a/examples/st_game/reflect/reflect.py +++ b/examples/st_game/reflect/reflect.py @@ -1,23 +1,20 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # @Desc : Reflect function -import datetime -import random -import sys -from numpy import dot -from numpy.linalg import norm -from ..roles.st_role import STRole -from ..utils.utils import get_embedding +import datetime + from metagpt.logs import logger -from ..actions.run_reflect_action import ( + +from examples.st_game.utils.utils import get_embedding +from examples.st_game.actions.run_reflect_action import ( AgentFocusPt, AgentInsightAndGuidance, AgentEventTriple, AgentEventPoignancy, AgentChatPoignancy, AgentPlanThoughtOnConvo, AgentMemoryOnConvo ) -def generate_focal_points(role: STRole, n=3): +def generate_focal_points(role: "STRole", n=3): nodes = [ [i.last_accessed, i] for i in role._rc.memory.event_list + role._rc.memory.thought_list @@ -65,7 +62,7 @@ def generate_action_event_triple(act_desp, role): return AgentEventTriple(act_desp, role) -def generate_poig_score(role: STRole, event_type, description): +def generate_poig_score(role: "STRole", event_type, description): if "is idle" in description: return 1 @@ -89,7 +86,7 @@ def generate_memo_on_convo(role, all_utt): # Done -def run_reflect(role: STRole): +def run_reflect(role: "STRole"): """ Run the actual reflection. We generate the focal points, retrieve any relevant nodes, and generate thoughts and insights. @@ -128,7 +125,7 @@ def run_reflect(role: STRole): # Done -def reflection_trigger(role: STRole): +def reflection_trigger(role: "STRole"): """ Given the current role, determine whether the role should run a reflection. @@ -155,7 +152,7 @@ def reflection_trigger(role: STRole): # Done -def reset_reflection_counter(role: STRole): +def reset_reflection_counter(role: "STRole"): """ We reset the counters used for the reflection trigger. @@ -170,7 +167,7 @@ def reset_reflection_counter(role: STRole): # Question 1 chat函数 -def reflect(role: STRole): +def reflect(role: "STRole"): """ The main reflection module for the role. We first check if the trigger conditions are met, and if so, run the reflection and reset any of the diff --git a/examples/st_game/reflect/st_reflect.py b/examples/st_game/reflect/st_reflect.py index da31a22eb..f4b4c448c 100644 --- a/examples/st_game/reflect/st_reflect.py +++ b/examples/st_game/reflect/st_reflect.py @@ -6,8 +6,9 @@ import asyncio import json import time from metagpt.logs import logger -from ..prompts.wrapper_prompt import special_response_generate -from ..memory.agent_memory import BasicMemory + +from examples.st_game.prompts.wrapper_prompt import special_response_generate +from examples.st_game.memory.agent_memory import BasicMemory async def agent_reflect(memories_list): @@ -21,7 +22,7 @@ async def agent_reflect(memories_list): B = await generate_insights_and_evidence(memories_list, question=i) -async def generate_focus_point(memories_list: list[MemoryBasic], n=3): +async def generate_focus_point(memories_list: list[BasicMemory], n=3): """ 生成关注点函数:根据记忆列表生成关注点 """ @@ -47,7 +48,7 @@ async def generate_focus_point(memories_list: list[MemoryBasic], n=3): return out -async def generate_insights_and_evidence(memories_list: list[MemoryBasic], question: str, n=5): +async def generate_insights_and_evidence(memories_list: list[BasicMemory], question: str, n=5): """ 生成洞察和证据函数:根据问题生成洞察和证据 """ @@ -68,7 +69,7 @@ async def generate_insights_and_evidence(memories_list: list[MemoryBasic], quest try: insight_list = json.loads(ret) for insight, index in insight_list: - agent.memory_list.append(MemoryBasic( + agent.memory_list.append(BasicMemory( time.time(), None, insight, None, None)) return insight_list except: diff --git a/examples/st_game/roles/__init__.py b/examples/st_game/roles/__init__.py new file mode 100644 index 000000000..2bcf8efd0 --- /dev/null +++ b/examples/st_game/roles/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Desc : diff --git a/examples/st_game/roles/st_role.py b/examples/st_game/roles/st_role.py index e634b2edd..518b9cc13 100644 --- a/examples/st_game/roles/st_role.py +++ b/examples/st_game/roles/st_role.py @@ -23,24 +23,24 @@ from metagpt.roles.role import Role, RoleContext from metagpt.schema import Message from metagpt.logs import logger -from ..memory.agent_memory import AgentMemory, BasicMemory -from ..memory.spatial_memory import MemoryTree -from ..actions.dummy_action import DummyAction -from ..actions.user_requirement import UserRequirement -from ..maze_environment import MazeEnvironment -from ..memory.retrieve import new_agent_retrieve -from ..memory.scratch import Scratch -from ..utils.utils import get_embedding, generate_poig_score, path_finder -from ..utils.const import collision_block_id -from ..reflect.st_reflect import agent_reflect -from ..utils.mg_ga_transform import save_movement, get_role_environment +from examples.st_game.memory.agent_memory import AgentMemory, BasicMemory +from examples.st_game.memory.spatial_memory import MemoryTree +from examples.st_game.actions.dummy_action import DummyAction, DummyMessage +from examples.st_game.actions.user_requirement import UserRequirement +from examples.st_game.maze_environment import MazeEnvironment +from examples.st_game.memory.retrieve import new_agent_retrieve +from examples.st_game.memory.scratch import Scratch +from examples.st_game.utils.utils import get_embedding, path_finder +from examples.st_game.utils.const import collision_block_id, STORAGE_PATH +from examples.st_game.reflect.reflect import generate_poig_score +from examples.st_game.utils.mg_ga_transform import save_movement, get_role_environment class STRoleContext(RoleContext): - env: 'MazeEnvironment' = Field(default=MazeEnvironment) - memory: AgentMemory = Field(default=AgentMemory) - scratch: Scratch = Field(default=Scratch) - spatial_memory: MemoryTree = Field(default=MemoryTree) + env: 'MazeEnvironment' = Field(default_factory=MazeEnvironment) + memory: AgentMemory = Field(default_factory=AgentMemory) + scratch: Scratch = Field(default_factory=Scratch) + spatial_memory: MemoryTree = Field(default_factory=MemoryTree) class STRole(Role): @@ -65,9 +65,18 @@ class STRole(Role): self.role_tile = (0, 0) self.game_obj_cleanup = dict() - self._rc = STRoleContext() super(STRole, self).__init__(name=name, profile=profile) + self._rc = STRoleContext() + memory_saved = str(STORAGE_PATH.joinpath(f"{sim_code}/personas/{self.name}/" + f"bootstrap_memory/associative_memory")) + self._rc.memory.set_mem_path(memory_saved) + sp_mem_saved = str(STORAGE_PATH.joinpath(f"{sim_code}/personas/{self.name}/" + f"bootstrap_memory/spatial_memory.json")) + self._rc.spatial_memory.set_mem_path(f_saved=sp_mem_saved) + scratch_f_saved = str(STORAGE_PATH.joinpath(f"{sim_code}/personas/{self.name}/" + f"bootstrap_memory/scratch.json")) + self._rc.scratch.set_scratch_path(f_saved=scratch_f_saved) self._init_actions([]) @@ -104,6 +113,19 @@ class STRole(Role): """ pass + async def _observe(self) -> int: + if not self._rc.env: + return 0 + + observed = self._rc.env.memory.get_by_actions(self._rc.watch) + self._rc.news = self._rc.memory.remember(observed) + if len(self._rc.news) == 1 and isinstance(self._rc.news[0], UserRequirement): + # add inner voice + # TODO + logger.warning(f"Role: {self.name} add inner voice: {self._rc.news[0].content}") + + return 1 # always return 1 to execute role's `_react` + async def observe(self) -> list[BasicMemory]: # TODO observe info from maze_env """ @@ -247,7 +269,7 @@ class STRole(Role): async def retrieve(self, focus_points, n=30): # TODO retrieve memories from agent_memory - retrieve_memories = new_agent_retrieve(self,focus_points,n) + retrieve_memories = new_agent_retrieve(self, focus_points, n) return retrieve_memories async def plan(self): @@ -435,7 +457,7 @@ class STRole(Role): ret = self.update_role_env() if not ret: # TODO add message - return + return DummyMessage() # TODO observe # get maze_env from self._rc.env, and observe env info @@ -443,20 +465,23 @@ class STRole(Role): # TODO retrieve, use self._rc.memory 's retrieve functions # TODO plan - plan = self.plan() - - # TODO reflect - - # TODO execute(feed-back into maze_env) - next_tile, pronunciatio, description = self.execute(plan) - role_move = { - "movement": next_tile, - "pronunciatio": pronunciatio, - "description": description, - "chat": self.scratch.chat - } - save_movement(self.name, role_move, step=self.step, sim_code=self.sim_code, curr_time=self.curr_time) + # plan = self.plan() + # + # # TODO reflect + # + # # TODO execute(feed-back into maze_env) + # next_tile, pronunciatio, description = self.execute(plan) + # role_move = { + # "movement": next_tile, + # "pronunciatio": pronunciatio, + # "description": description, + # "chat": self.scratch.chat + # } + # save_movement(self.name, role_move, step=self.step, sim_code=self.sim_code, curr_time=self.curr_time) # step update + logger.info(f"Role: {self.name} run at {self.step} step on {self.curr_time}") self.step += 1 self.curr_time += datetime.timedelta(seconds=self.sec_per_step) + + return DummyMessage() diff --git a/examples/st_game/run_st_game.py b/examples/st_game/run_st_game.py index ca73c8fff..954b3f946 100644 --- a/examples/st_game/run_st_game.py +++ b/examples/st_game/run_st_game.py @@ -9,6 +9,7 @@ from stanford_town import StanfordTown from roles.st_role import STRole from utils.mg_ga_transform import get_reverie_meta from utils.const import STORAGE_PATH +from utils.utils import copy_folder async def startup(idea: str, @@ -16,6 +17,8 @@ async def startup(idea: str, sim_code: str, investment: float = 30.0, n_round: int = 500): + # copy `storage/{fork_sim_code}` to `storage/{sim_code}` + copy_folder(str(STORAGE_PATH.joinpath(fork_sim_code)), str(STORAGE_PATH.joinpath(sim_code))) # get role names from `storage/{simulation_name}/reverie/meta.json` and then init roles reverie_meta = get_reverie_meta(fork_sim_code) diff --git a/examples/st_game/tests/actions/__init__.py b/examples/st_game/tests/actions/__init__.py new file mode 100644 index 000000000..2bcf8efd0 --- /dev/null +++ b/examples/st_game/tests/actions/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Desc : diff --git a/examples/st_game/tests/actions/test_summarize_conv.py b/examples/st_game/tests/actions/test_summarize_conv.py index d9efd4aa5..ad9c68aee 100644 --- a/examples/st_game/tests/actions/test_summarize_conv.py +++ b/examples/st_game/tests/actions/test_summarize_conv.py @@ -2,6 +2,10 @@ # -*- coding: utf-8 -*- # @Desc : unittest of actions/summarize_conv +import sys +import os +sys.path.append(os.path.abspath(os.path.dirname(__file__) + "./../../../")) + from metagpt.logs import logger from st_game.actions.summarize_conv import SummarizeConv diff --git a/examples/st_game/utils/__init__.py b/examples/st_game/utils/__init__.py new file mode 100644 index 000000000..2bcf8efd0 --- /dev/null +++ b/examples/st_game/utils/__init__.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Desc : diff --git a/examples/st_game/utils/mg_ga_transform.py b/examples/st_game/utils/mg_ga_transform.py index dd7d8840a..911d626a7 100644 --- a/examples/st_game/utils/mg_ga_transform.py +++ b/examples/st_game/utils/mg_ga_transform.py @@ -6,8 +6,8 @@ import json from metagpt.logs import logger -from .const import STORAGE_PATH -from .utils import read_json_file, write_json_file +from examples.st_game.utils.const import STORAGE_PATH +from examples.st_game.utils.utils import read_json_file, write_json_file def get_reverie_meta(sim_code: str) -> dict: diff --git a/examples/st_game/utils/utils.py b/examples/st_game/utils/utils.py index fb2341e0d..a2cfabb54 100644 --- a/examples/st_game/utils/utils.py +++ b/examples/st_game/utils/utils.py @@ -2,13 +2,17 @@ # -*- coding: utf-8 -*- # @Desc : utils -from typing import Any, Union -import os -import json -import openai -from pathlib import Path import csv -from ..prompts.run_gpt_prompts import get_poignancy_action, get_poignancy_chat +import errno +import json +import os +import shutil +from pathlib import Path +from typing import Any, Union + +import openai + +from metagpt.logs import logger def read_json_file(json_file: str, encoding=None) -> list[Any]: @@ -37,23 +41,24 @@ def read_csv_to_list(curr_file: str, header=False, strip_trail=True): RETURNS: List of list where the component lists are the rows of the file. """ + logger.info(f"start read csv: {curr_file}") if not header: analysis_list = [] with open(curr_file) as f_analysis_file: data_reader = csv.reader(f_analysis_file, delimiter=",") - for count, row in enumerate(data_reader): - if strip_trail: - row = [i.strip() for i in row] - analysis_list += [row] + for count, row in enumerate(data_reader): + if strip_trail: + row = [i.strip() for i in row] + analysis_list += [row] return analysis_list else: analysis_list = [] with open(curr_file) as f_analysis_file: data_reader = csv.reader(f_analysis_file, delimiter=",") - for count, row in enumerate(data_reader): - if strip_trail: - row = [i.strip() for i in row] - analysis_list += [row] + for count, row in enumerate(data_reader): + if strip_trail: + row = [i.strip() for i in row] + analysis_list += [row] return analysis_list[0], analysis_list[1:] @@ -65,15 +70,6 @@ def get_embedding(text, model: str = "text-embedding-ada-002"): input=[text], model=model)['data'][0]['embedding'] -def generate_poig_score(scratch, event_type, description): - if "is idle" in description: - return 1 - if event_type == "action": - return get_poignancy_action(scratch, description)[0] - elif event_type == "chat": - return get_poignancy_chat(scratch, description)[0] - - def extract_first_json_dict(data_str: str) -> Union[None, dict]: # Find the first occurrence of a JSON object within the string start_idx = data_str.find("{") @@ -178,6 +174,7 @@ def path_finder(maze: "Maze", start: list[int], end: list[int], collision_block_ return path + def create_folder_if_not_there(curr_path): """ Checks if a folder in the curr_path exists. If it does not exist, creates @@ -207,6 +204,7 @@ def create_folder_if_not_there(curr_path): return False + def find_filenames(path_to_dir, suffix=".csv"): """ Given a directory, find all files that end with the provided suffix and @@ -219,4 +217,21 @@ def find_filenames(path_to_dir, suffix=".csv"): """ filenames = os.listdir(path_to_dir) return [path_to_dir + "/" + filename - for filename in filenames if filename.endswith(suffix)] \ No newline at end of file + for filename in filenames if filename.endswith(suffix)] + + +def check_if_file_exists(curr_file: str): + return Path(curr_file).exists() + + +def copy_folder(src_folder: str, dest_folder: str): + try: + if Path(dest_folder).exists(): + logger.warning(f"{dest_folder} exist, start to remove.") + shutil.rmtree(dest_folder) + shutil.copytree(src_folder, dest_folder) + except OSError as exc: # python >2.5 + if exc.errno in (errno.ENOTDIR, errno.EINVAL): + shutil.copy(src_folder, dest_folder) + else: + raise diff --git a/metagpt/roles/role.py b/metagpt/roles/role.py index fef31dc61..21b6b5b8a 100644 --- a/metagpt/roles/role.py +++ b/metagpt/roles/role.py @@ -18,7 +18,6 @@ from metagpt.llm import LLM from metagpt.logs import logger from metagpt.memory import Memory, LongTermMemory from metagpt.schema import Message -from metagpt.reflect import agent_reflect PREFIX_TEMPLATE = """You are a {profile}, named {name}, your goal is {goal}, and the constraint is {constraints}. """