Skip to content

Commit

Permalink
feat(agent): Draft of agent group feature
Browse files Browse the repository at this point in the history
* Add `divider_and_conquer` strategy for using in agent members
* Add `AgentMember` class
* Add `AgentGroup` class
* Add `hire_agent` and `create_agent` and `create_task`
* Add `test_agent_group` as an intergration test with VCR
  • Loading branch information
MKdir98 committed Mar 4, 2024
1 parent 1f1e8c9 commit a9f2288
Show file tree
Hide file tree
Showing 12 changed files with 2,296 additions and 2 deletions.
122 changes: 122 additions & 0 deletions autogpts/autogpt/autogpt/agents/agent_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import sys
import logging
from typing import Optional
from autogpt.agent_factory.profile_generator import AgentProfileGenerator
from autogpt.agents.agent import Agent, AgentConfiguration
from autogpt.agents.agent_member import AgentMember, AgentMemberSettings
from autogpt.config.config import ConfigBuilder
from autogpt.core.resource.model_providers.openai import OpenAIProvider
from forge.sdk.model import ( Task, TaskRequestBody )

info = "-v" in sys.argv
debug = "-vv" in sys.argv
granular = "--granular" in sys.argv

logging.basicConfig(
level=logging.DEBUG if debug else logging.INFO if info else logging.WARNING
)
logger = logging.getLogger(__name__)


class AgentGroup:

leader: AgentMember
members: dict[str, AgentMember]

def assign_group_to_all_of_member(self):
self.leader.assign_group(self)

def create_list_of_members(self):
members = self.leader.get_list_of_all_your_team_members()
self.members = {}
for agent_member in members:
self.members[agent_member.id] = agent_member

def __init__(
self,
leader: AgentMember
):
self.leader = leader
self.assign_group_to_all_of_member()
self.create_list_of_members()

async def create_task(self, task: TaskRequestBody):
await self.leader.create_task(task)

async def create_agent_member(
role: str,
initial_prompt:str,
boss: Optional['AgentMember'] = None,
recruiter: Optional['AgentMember'] = None,
create_agent: bool = False,
) -> Optional[AgentMember]:
config = ConfigBuilder.build_config_from_env()
config.logging.plain_console_output = True

config.continuous_mode = False
config.continuous_limit = 20
config.noninteractive_mode = True
config.memory_backend = "no_memory"
settings = await generate_agent_profile_for_task(
task=initial_prompt,
app_config=config,
)

agentMember = AgentMember(
role=role,
initial_prompt=initial_prompt,
settings=settings,
boss=boss,
recruiter=recruiter,
create_agent=create_agent
)

if boss:
boss.members.append(agentMember)

return agentMember

async def generate_agent_profile_for_task(
task: str,
app_config
) -> AgentMemberSettings:
openai_settings = OpenAIProvider.default_settings.copy(deep=True)
openai_settings.credentials = app_config.openai_credentials
llm_provider = OpenAIProvider(
settings=openai_settings,
logger=logger.getChild(f"OpenAIProvider"),
)
agent_profile_generator = AgentProfileGenerator(
**AgentProfileGenerator.default_configuration.dict() # HACK
)

prompt = agent_profile_generator.build_prompt(task)
output = (
await llm_provider.create_chat_completion(
prompt.messages,
model_name=app_config.smart_llm,
functions=prompt.functions,
)
).response

ai_profile, ai_directives = agent_profile_generator.parse_response_content(output)

agent_prompt_config = Agent.default_settings.prompt_config.copy(deep=True)
agent_prompt_config.use_functions_api = app_config.openai_functions

return AgentMemberSettings(
name=Agent.default_settings.name,
description=Agent.default_settings.description,
task=task,
ai_profile=ai_profile,
directives=ai_directives,
config=AgentConfiguration(
fast_llm=app_config.fast_llm,
smart_llm=app_config.smart_llm,
allow_fs_access=not app_config.restrict_to_workspace,
use_functions_api=app_config.openai_functions,
plugins=app_config.plugins,
),
prompt_config=agent_prompt_config,
history=Agent.default_settings.history.copy(deep=True),
)
251 changes: 251 additions & 0 deletions autogpts/autogpt/autogpt/agents/agent_member.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import os
import copy
import sys
from uuid import uuid4
import asyncio
import logging
from typing import Optional
from pydantic import Field
from autogpt.agents.utils.prompt_scratchpad import PromptScratchpad
from autogpt.core.resource.model_providers.schema import AssistantChatMessage
from autogpt.llm.providers.openai import get_openai_command_specs
from autogpt.models.action_history import Action
from forge.sdk.db import AgentDB
from autogpt.core.resource.model_providers.openai import OpenAIProvider
from forge.sdk.model import ( Task, TaskRequestBody )
from autogpt.commands import COMMAND_CATEGORIES
from autogpt.config.config import ConfigBuilder
from autogpt.agents.base import BaseAgentSettings
from autogpt.agents.agent import Agent, AgentConfiguration
from autogpt.models.command_registry import CommandRegistry
from autogpt.agents.prompt_strategies.divide_and_conquer import Command, DivideAndConquerAgentPromptConfiguration, DivideAndConquerAgentPromptStrategy
from autogpt.core.prompting.schema import (
ChatMessage,
ChatPrompt,
CompletionModelFunction,
)
info = "-v" in sys.argv
debug = "-vv" in sys.argv
granular = "--granular" in sys.argv

logging.basicConfig(
level=logging.DEBUG if debug else logging.INFO if info else logging.WARNING
)
logger = logging.getLogger(__name__)

class AgentMemberSettings(BaseAgentSettings):
config: AgentConfiguration = Field(default_factory=AgentConfiguration)
prompt_config: DivideAndConquerAgentPromptConfiguration = Field(
default_factory=(
lambda: DivideAndConquerAgentPromptStrategy.default_configuration.copy(deep=True)
)
)

class AgentMember(Agent):

id: str
role: str
initial_prompt: str
boss: Optional['AgentMember']
recruiter: Optional['AgentMember']
task_queue: list[Task]
members: list['AgentMember']
create_agent: bool
db: AgentDB
group: 'AgentGroup'

def assign_group(self, group: 'AgentGroup'):
self.group = group
for members in self.members:
members.assign_group(group)

def get_list_of_all_your_team_members(self) -> list['AgentMember']:
members = []
members.append(self)
for member in self.members:
members.extend(member.get_list_of_all_your_team_members())
return members

def __init__(
self,
role: str,
initial_prompt: str,
settings: AgentMemberSettings,
boss: Optional['AgentMember'] = None,
recruiter: Optional['AgentMember'] = None,
create_agent: bool = False,
):
config = ConfigBuilder.build_config_from_env()
config.logging.plain_console_output = True

config.continuous_mode = False
config.continuous_limit = 20
config.noninteractive_mode = True
config.memory_backend = "no_memory"

commands = copy.deepcopy(COMMAND_CATEGORIES)
commands.append("autogpt.commands.create_task")
if create_agent:
commands.append("autogpt.commands.create_agent")
else:
commands.append("autogpt.commands.hire_agent")

command_registry = CommandRegistry.with_command_modules(commands, config)

hugging_chat_settings = OpenAIProvider.default_settings.copy(deep=True)
hugging_chat_settings.credentials = config.openai_credentials

llm_provider = OpenAIProvider(
settings=hugging_chat_settings,
logger=logger.getChild(f"Role-{role}-OpenAIProvider"),
)

super().__init__(settings, llm_provider, command_registry, config)

self.id = str(uuid4())
self.role = role
self.initial_prompt = initial_prompt
self.boss = boss
self.recruiter = recruiter
self.task_queue = []
self.members = []
self.create_agent = create_agent
database = AgentDB(
database_string=os.getenv("AP_SERVER_DB_URL", "sqlite:///agetn_group.db"),
debug_enabled=debug,
)
self.db = database
self.prompt_strategy = DivideAndConquerAgentPromptStrategy(
configuration=settings.prompt_config,
logger=logger,
)

def build_prompt(
self,
scratchpad: PromptScratchpad,
tasks: list['AgentTask'],
extra_commands: Optional[list[CompletionModelFunction]] = None,
extra_messages: Optional[list[ChatMessage]] = None,
**extras,
) -> ChatPrompt:
"""Constructs a prompt using `self.prompt_strategy`.
Params:
scratchpad: An object for plugins to write additional prompt elements to.
(E.g. commands, constraints, best practices)
extra_commands: Additional commands that the agent has access to.
extra_messages: Additional messages to include in the prompt.
"""
if not extra_commands:
extra_commands = []
if not extra_messages:
extra_messages = []

# Apply additions from plugins
for plugin in self.config.plugins:
if not plugin.can_handle_post_prompt():
continue
plugin.post_prompt(scratchpad)
ai_directives = self.directives.copy(deep=True)
ai_directives.resources += scratchpad.resources
ai_directives.constraints += scratchpad.constraints
ai_directives.best_practices += scratchpad.best_practices
extra_commands += list(scratchpad.commands.values())

prompt = self.prompt_strategy.build_prompt(
include_os_info=True,
tasks=tasks,
agent_member=self,
ai_profile=self.ai_profile,
ai_directives=ai_directives,
commands=get_openai_command_specs(
self.command_registry.list_available_commands(self)
)
+ extra_commands,
event_history=self.event_history,
max_prompt_tokens=self.send_token_limit,
count_tokens=lambda x: self.llm_provider.count_tokens(x, self.llm.name),
count_message_tokens=lambda x: self.llm_provider.count_message_tokens(
x, self.llm.name
),
extra_messages=extra_messages,
**extras,
)

return prompt


async def process_tasks(self, tasks: list['AgentTask']):
try:
self._prompt_scratchpad = PromptScratchpad()
logger.info(f"tasks: {str(tasks)}")
prompt = self.build_prompt(scratchpad=self._prompt_scratchpad, tasks=tasks)
result = await self.llm_provider.create_chat_completion(
prompt.messages,
model_name=self.config.smart_llm,
functions=prompt.functions,
completion_parser=lambda r: self.parse_and_process_response(
r,
prompt,
scratchpad=self._prompt_scratchpad,
),
)
commands:list[Command] = result.parsed_result
# self.log_cycle_handler.log_cycle(
# self.ai_profile.ai_name,
# self.created_at,
# self.config.cycle_count,
# assistant_reply_dict,
# NEXT_ACTION_FILE_NAME,
# )

for command in commands:
print(command.command)
self.event_history.register_action(
Action(
name=command.command,
args=command.args,
reasoning="",
)
)
result = await self.execute(
command_name=command.command,
command_args=command.args,
)
print(result)
except Exception as e:
print(e)
logger.error(f"Error occurred while creating task: {e}")

async def create_task(self, task_request: TaskRequestBody):
try:
task = await self.db.create_agent_task(
input=task_request.input,
additional_input=task_request.additional_input,
status="PENDING"
)
self.push_task(task)
except Exception as e:
logger.error(f"Error occurred while creating task: {e}")

async def run_tasks(self):
while True:
try:
logger.info("tasks of " + self.role + ": " + str(self.task_queue))
if self.task_queue:
current_tasks = self.task_queue[:]
self.task_queue.clear()
await self.process_tasks(current_tasks)
await asyncio.sleep(3)
except Exception as e:
logger.error(f"Error occurred while running tasks: {e}")


def push_task(self, task: Task):
self.task_queue.append(task)

def parse_and_process_response(
self, llm_response: AssistantChatMessage, *args, **kwargs
) -> list[Command]:
result = self.prompt_strategy.parse_response_content(llm_response)
return result

0 comments on commit a9f2288

Please sign in to comment.