Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add reflection mechanism #18

Merged
merged 34 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
49a32fc
addalice_home yaml config file
Dr-Left May 18, 2023
f1b0408
add OPREnvironment.py
Dr-Left May 18, 2023
d4ef881
finish LongtermMemory and Reflection adjustment
Dr-Left May 18, 2023
b4ac153
Unit test for memory finished(no bug). Debug.Performance waited to be…
Dr-Left May 18, 2023
9446058
add openai direct chat method and get_embedding
Dr-Left May 18, 2023
a7c46ba
modify ltmelement unit test
Dr-Left May 18, 2023
da2d7c2
base police s1->p->r
chanchimin May 21, 2023
fc2da3b
Merge branch 'remove_langchain' of https://github.com/OpenBMB/AgentVe…
chanchimin May 21, 2023
60c7667
add initialization for OPR
Dr-Left May 24, 2023
8d30e19
merge from main
Dr-Left May 24, 2023
0f082fb
prisoner
chanchimin May 24, 2023
f547a6f
merge from zjw
chanchimin May 24, 2023
e7894c2
planner unittest
chanchimin May 26, 2023
4ededdf
change get_next_plan to get_plan with time input
Dr-Left May 26, 2023
0e1c4d1
basic runnable generative agents, need to insert plan in prompt
chanchimin May 29, 2023
c019a91
add plan and event_memory in prompt
chanchimin May 30, 2023
3cf12cc
fix llm.generate_response bug in reflection
chanchimin May 30, 2023
513f78f
merge from main
chanchimin May 30, 2023
81ea48b
fix annotation
chanchimin Jun 12, 2023
f9668b0
add car, basic logic without communication, lack collision and reache…
chanchimin Jun 26, 2023
1b7d894
reflection
chanchimin Jul 1, 2023
7a023bf
remove redundancy
chanchimin Jul 1, 2023
41d2c4d
code cleaning
chanchimin Jul 1, 2023
fb4d6b1
code cleaning
chanchimin Jul 1, 2023
e41e36b
comment bmtools in AgentVerse/agentverse/initialization.py.
yushengsu-thu Jul 3, 2023
bb63742
package release
chenweize1998 Jul 4, 2023
24a125f
package release
chenweize1998 Jul 4, 2023
9433044
update README.md
chenweize1998 Jul 4, 2023
d6cdc99
resolve conflict
chenweize1998 Jul 4, 2023
5d66ddf
Delete setup.py
chenweize1998 Jul 5, 2023
de0d345
remove traffic
chanchimin Jul 5, 2023
b3bb7cf
resolve confliction
chanchimin Jul 5, 2023
b052598
remove 127.0.0.1
chanchimin Jul 5, 2023
50891cf
code cleaning
chanchimin Jul 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .idea/AgentVerse.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions agentverse/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
from .conversation_agent import ConversationAgent
from .tool_agent import ToolAgent
from .prisoner_dilema_agent import PoliceAgent, PrisonerAgent
from .reflection_agent import ReflectionAgent
3 changes: 2 additions & 1 deletion agentverse/agents/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from agentverse.memory import BaseMemory, ChatHistoryMemory
from agentverse.message import Message
from agentverse.parser import OutputParser

from agentverse.memory_manipulator import BaseMemoryManipulator

class BaseAgent(BaseModel):
name: str
Expand All @@ -17,6 +17,7 @@ class BaseAgent(BaseModel):
prompt_template: str
role_description: str = Field(default="")
memory: BaseMemory = Field(default_factory=ChatHistoryMemory)
memory_manipulator: BaseMemoryManipulator = Field(default_factory=BaseMemoryManipulator)
max_retry: int = Field(default=3)
receiver: Set[str] = Field(default=set({"all"}))
async_mode: bool = Field(default=True)
Expand Down
230 changes: 230 additions & 0 deletions agentverse/agents/reflection_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
from __future__ import annotations

"""
An agent based upon Observation-Planning-Reflection architecture.
"""

from logging import getLogger

from abc import abstractmethod
from typing import List, Set, Union, NamedTuple, TYPE_CHECKING

from pydantic import BaseModel, Field, validator

from agentverse.llms import BaseLLM
from agentverse.memory import BaseMemory, ChatHistoryMemory
from agentverse.message import Message
from agentverse.parser import OutputParser

from agentverse.message import Message
from agentverse.agents.base import BaseAgent

from datetime import datetime as dt
import datetime

from . import agent_registry
from string import Template


logger = getLogger(__file__)

if TYPE_CHECKING:
from agentverse.environments.base import BaseEnvironment


@agent_registry.register("reflection")
class ReflectionAgent(BaseAgent):
async_mode: bool = True,
current_time: str = None,
environment: BaseEnvironment = None
step_cnt: int = 0

manipulated_memory: str = Field(default="", description="one fragment used in prompt construction")

@validator('current_time')
def convert_str_to_dt(cls, current_time):
if not isinstance(current_time, str):
raise ValueError('current_time should be str')
return dt.strptime(current_time, "%Y-%m-%d %H:%M:%S")

def step(self, current_time: dt, env_description: str = "") -> Message:
"""
Call this method at each time frame
"""
self.current_time = current_time

self.manipulated_memory = self.memory_manipulator.manipulate_memory()

prompt = self._fill_prompt_template(env_description)

parsed_response, reaction, target = None, None, None
for i in range(self.max_retry):
try:
response = self.llm.agenerate_response(prompt)
parsed_response = self.output_parser.parse(response)

if 'say(' in parsed_response.return_values["output"]:
reaction, target = eval("self._" + parsed_response.return_values["output"].strip())
elif 'act(' in parsed_response.return_values["output"]:
reaction, target = eval("self._" + parsed_response.return_values["output"].strip())
elif 'do_nothing(' in parsed_response.return_values["output"]:
reaction, target = None, None
else:
raise Exception(f"no valid parsed_response detected, "
f"cur response {parsed_response.return_values['output']}")
break

except Exception as e:
logger.error(e)
logger.warning("Retrying...")
continue

if parsed_response is None:
logger.error(f"{self.name} failed to generate valid response.")

if reaction is None:
reaction = "Keep doing last action ..."

message = Message(
content=""
if reaction is None
else reaction,
sender=self.name,
receiver=self.get_receiver() if target is None else self.get_valid_receiver(target),
)

self.step_cnt += 1

return message

def check_status_passive(self, ):
"""Check if the current status needs to be finished. If so, examine the plan and initiate the next action.
"""
if self.status_start_time is None: # fixing empty start time
self.status_start_time = self.current_time

if self.status_start_time+datetime.timedelta(self.status_duration) <= self.current_time:
next_plan = self.memory.planner.get_plan(current_time=self.current_time)
self.status_start_time = self.current_time
self.status = next_plan['status']
self.status_duration = next_plan['duration']
else:
logger.debug(f"{self.name} don't change status by plan: {self.status_start_time}, {datetime.timedelta(self.status_duration)}, {self.current_time}")

async def astep(self, current_time: dt, env_description: str = "") -> Message:
"""Asynchronous version of step"""
#use environment's time to update agent's time
self.current_time = current_time
# Before the agent step, we check current status,
# TODO add this func after
# self.check_status_passive()

self.manipulated_memory = self.memory_manipulator.manipulate_memory()

prompt = self._fill_prompt_template(env_description)

parsed_response, reaction, target = None, None, None
for i in range(self.max_retry):
try:
response = await self.llm.agenerate_response(prompt)
parsed_response = self.output_parser.parse(response)

if 'say(' in parsed_response.return_values["output"]:
reaction, target = eval("self._" + parsed_response.return_values["output"].strip())
elif 'act(' in parsed_response.return_values["output"]:
reaction, target = eval("self._" + parsed_response.return_values["output"].strip())
elif 'do_nothing(' in parsed_response.return_values["output"]:
reaction, target = None, None
else:
raise Exception(f"no valid parsed_response detected, "
f"cur response {parsed_response.return_values['output']}")

break

except Exception as e:
logger.error(e)
logger.warning("Retrying...")
continue

if parsed_response is None:
logger.error(f"{self.name} failed to generate valid response.")

if reaction is None:
reaction = "Keep doing last action ..."

message = Message(
content=""
if reaction is None
else reaction,
sender=self.name,
receiver=self.get_receiver() if target is None else self.get_valid_receiver(target),
)

self.step_cnt += 1

return message

def _act(self, description=None, target=None):
if description is None:
return ""
if target is None:
reaction_content = f"{self.name} performs action: '{description}'."
else:
reaction_content = f"{self.name} performs action to {target}: '{description}'."
# self.environment.broadcast_observations(self, target, reaction_content)
return reaction_content, target

def _say(self, description, target=None):
if description is None:
return ""
if target is None:
reaction_content = f"{self.name} says: '{description}'."
else:
reaction_content = f"{self.name} says to {target}: '{description}'."
# self.environment.broadcast_observations(self, target, reaction_content)
return reaction_content, target

def get_valid_receiver(self, target: str) -> set():

all_agents_name = []
for agent in self.environment.agents:
all_agents_name.append(agent.name)

if not (target in all_agents_name):
return {"all"}
else:
return {target}

def _fill_prompt_template(self, env_description: str = "") -> str:
"""Fill the placeholders in the prompt template

In the conversation agent, three placeholders are supported:
- ${agent_name}: the name of the agent
- ${env_description}: the description of the environment
- ${role_description}: the description of the role of the agent
- ${chat_history}: the chat history of the agent
"""
input_arguments = {
"agent_name": self.name,
"role_description": self.role_description,
"chat_history": self.memory.to_string(add_sender_prefix=True),
"current_time": self.current_time,
"env_description": env_description,
}
return Template(self.prompt_template).safe_substitute(input_arguments)

def add_message_to_memory(self, messages: List[Message]) -> None:
self.memory.add_message(messages)

# Should call this when status changed, plan==status
def add_plan_to_memory(self,) -> None:
self.memory.add_plan(content=self.status, time=self.current_time)

def reset(self, environment: BaseEnvironment) -> None:
"""Reset the agent"""
self.environment = environment
self.memory.reset()
self.memory_manipulator.agent = self
self.memory_manipulator.memory = self.memory


5 changes: 5 additions & 0 deletions agentverse/environments/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,10 @@
from .basic import BasicEnvironment
from .pokemon import PokemonEnvironment
from .prisoner_dilema import PrisonerDilemaEnvironment
from .traffic_junction import TrafficEnvironment

from .reflection import ReflectionEnvironment

from .sde_team import SdeTeamEnvironment
from .sde_team_given_tests import SdeTeamGivenTestsEnvironment

Loading