Run pre-commit hooks

In the title !
This commit is contained in:
Greyson Lalonde
2023-12-27 13:23:45 -05:00
parent 542a794e64
commit a4e93cea75
11 changed files with 582 additions and 562 deletions

View File

@@ -1,4 +1,4 @@
from .task import Task
from .crew import Crew
from .agent import Agent
from .process import Process
from .crew import Crew
from .process import Process
from .task import Task

View File

@@ -1,128 +1,120 @@
"""Generic agent."""
from typing import List, Any, Optional
from pydantic.v1 import BaseModel, PrivateAttr, Field, root_validator
from typing import Any, List, Optional
from langchain.agents import AgentExecutor
from langchain.chat_models import ChatOpenAI
from langchain.tools.render import render_text_description
from langchain.agents.format_scratchpad import format_log_to_str
from langchain.agents.output_parsers import ReActSingleInputOutputParser
from langchain.chat_models import ChatOpenAI
from langchain.memory import ConversationSummaryMemory
from langchain.tools.render import render_text_description
from pydantic.v1 import BaseModel, Field, PrivateAttr, root_validator
from .prompts import Prompts
class Agent(BaseModel):
"""
Represents an agent in a system.
"""
Represents an agent in a system.
Each agent has a role, a goal, a backstory, and an optional language model (llm).
The agent can also have memory, can operate in verbose mode, and can delegate tasks to other agents.
Each agent has a role, a goal, a backstory, and an optional language model (llm).
The agent can also have memory, can operate in verbose mode, and can delegate tasks to other agents.
Attributes:
agent_executor: An instance of the AgentExecutor class.
role: The role of the agent.
goal: The objective of the agent.
backstory: The backstory of the agent.
llm: The language model that will run the agent.
memory: Whether the agent should have memory or not.
verbose: Whether the agent execution should be in verbose mode.
allow_delegation: Whether the agent is allowed to delegate tasks to other agents.
"""
agent_executor: AgentExecutor = None
role: str = Field(description="Role of the agent")
goal: str = Field(description="Objective of the agent")
backstory: str = Field(description="Backstory of the agent")
llm: Optional[Any] = Field(description="LLM that will run the agent")
memory: bool = Field(
description="Whether the agent should have memory or not",
default=True
)
verbose: bool = Field(
description="Verbose mode for the Agent Execution",
default=False
)
allow_delegation: bool = Field(
description="Allow delegation of tasks to agents",
default=True
)
tools: List[Any] = Field(
description="Tools at agents disposal",
default=[]
)
_task_calls: List[Any] = PrivateAttr()
Attributes:
agent_executor: An instance of the AgentExecutor class.
role: The role of the agent.
goal: The objective of the agent.
backstory: The backstory of the agent.
llm: The language model that will run the agent.
memory: Whether the agent should have memory or not.
verbose: Whether the agent execution should be in verbose mode.
allow_delegation: Whether the agent is allowed to delegate tasks to other agents.
"""
@root_validator(pre=True)
def check_llm(_cls, values):
if not values.get('llm'):
values['llm'] = ChatOpenAI(
temperature=0.7,
model_name="gpt-4"
)
return values
agent_executor: AgentExecutor = None
role: str = Field(description="Role of the agent")
goal: str = Field(description="Objective of the agent")
backstory: str = Field(description="Backstory of the agent")
llm: Optional[Any] = Field(description="LLM that will run the agent")
memory: bool = Field(
description="Whether the agent should have memory or not", default=True
)
verbose: bool = Field(
description="Verbose mode for the Agent Execution", default=False
)
allow_delegation: bool = Field(
description="Allow delegation of tasks to agents", default=True
)
tools: List[Any] = Field(description="Tools at agents disposal", default=[])
_task_calls: List[Any] = PrivateAttr()
def __init__(self, **data):
super().__init__(**data)
agent_args = {
"input": lambda x: x["input"],
"tools": lambda x: x["tools"],
"tool_names": lambda x: x["tool_names"],
"agent_scratchpad": lambda x: format_log_to_str(x['intermediate_steps']),
}
executor_args = {
"tools": self.tools,
"verbose": self.verbose,
"handle_parsing_errors": True,
}
@root_validator(pre=True)
def check_llm(_cls, values):
if not values.get("llm"):
values["llm"] = ChatOpenAI(temperature=0.7, model_name="gpt-4")
return values
if self.memory:
summary_memory = ConversationSummaryMemory(
llm=self.llm,
memory_key='chat_history',
input_key="input"
)
executor_args['memory'] = summary_memory
agent_args['chat_history'] = lambda x: x["chat_history"]
prompt = Prompts.TASK_EXECUTION_WITH_MEMORY_PROMPT
else:
prompt = Prompts.TASK_EXECUTION_PROMPT
def __init__(self, **data):
super().__init__(**data)
agent_args = {
"input": lambda x: x["input"],
"tools": lambda x: x["tools"],
"tool_names": lambda x: x["tool_names"],
"agent_scratchpad": lambda x: format_log_to_str(x["intermediate_steps"]),
}
executor_args = {
"tools": self.tools,
"verbose": self.verbose,
"handle_parsing_errors": True,
}
execution_prompt = prompt.partial(
goal=self.goal,
role=self.role,
backstory=self.backstory,
)
if self.memory:
summary_memory = ConversationSummaryMemory(
llm=self.llm, memory_key="chat_history", input_key="input"
)
executor_args["memory"] = summary_memory
agent_args["chat_history"] = lambda x: x["chat_history"]
prompt = Prompts.TASK_EXECUTION_WITH_MEMORY_PROMPT
else:
prompt = Prompts.TASK_EXECUTION_PROMPT
bind = self.llm.bind(stop=["\nObservation"])
inner_agent = agent_args | execution_prompt | bind | ReActSingleInputOutputParser()
execution_prompt = prompt.partial(
goal=self.goal,
role=self.role,
backstory=self.backstory,
)
self.agent_executor = AgentExecutor(
agent=inner_agent,
**executor_args
)
bind = self.llm.bind(stop=["\nObservation"])
inner_agent = (
agent_args | execution_prompt | bind | ReActSingleInputOutputParser()
)
def execute_task(self, task: str, context: str = None, tools: List[Any] = None) -> str:
"""
Execute a task with the agent.
Parameters:
task (str): Task to execute
Returns:
output (str): Output of the agent
"""
if context:
task = "\n".join([
task,
"\nThis is the context you are working with:",
context
])
self.agent_executor = AgentExecutor(agent=inner_agent, **executor_args)
tools = tools or self.tools
self.agent_executor.tools = tools
return self.agent_executor.invoke({
"input": task,
"tool_names": self.__tools_names(tools),
"tools": render_text_description(tools),
})['output']
def execute_task(
self, task: str, context: str = None, tools: List[Any] = None
) -> str:
"""
Execute a task with the agent.
Parameters:
task (str): Task to execute
Returns:
output (str): Output of the agent
"""
if context:
task = "\n".join(
[task, "\nThis is the context you are working with:", context]
)
def __tools_names(self, tools) -> str:
return ", ".join([t.name for t in tools])
tools = tools or self.tools
self.agent_executor.tools = tools
return self.agent_executor.invoke(
{
"input": task,
"tool_names": self.__tools_names(tools),
"tools": render_text_description(tools),
}
)["output"]
def __tools_names(self, tools) -> str:
return ", ".join([t.name for t in tools])

View File

@@ -1,89 +1,88 @@
import json
from typing import List, Optional
from pydantic.v1 import BaseModel, Field, Json, root_validator
from .process import Process
from .agent import Agent
from .process import Process
from .task import Task
from .tools.agent_tools import AgentTools
class Crew(BaseModel):
"""
Class that represents a group of agents, how they should work together and
their tasks.
"""
tasks: Optional[List[Task]] = Field(description="List of tasks")
agents: Optional[List[Agent]] = Field(description="List of agents in this crew.")
process: Process = Field(
description="Process that the crew will follow.",
default=Process.sequential
)
verbose: bool = Field(
description="Verbose mode for the Agent Execution",
default=False
)
config: Optional[Json] = Field(
description="Configuration of the crew.",
default=None
)
"""
Class that represents a group of agents, how they should work together and
their tasks.
"""
@root_validator(pre=True)
def check_config(_cls, values):
if (
not values.get('config')
and (
not values.get('agents') and not values.get('tasks')
)
):
raise ValueError('Either agents and task need to be set or config.')
if values.get('config'):
config = json.loads(values.get('config'))
if not config.get('agents') or not config.get('tasks'):
raise ValueError('Config should have agents and tasks.')
values['agents'] = [Agent(**agent) for agent in config['agents']]
tasks = []
for task in config['tasks']:
task_agent = [agt for agt in values['agents'] if agt.role == task['agent']][0]
del task['agent']
tasks.append(Task(**task, agent=task_agent))
values['tasks'] = tasks
return values
tasks: Optional[List[Task]] = Field(description="List of tasks")
agents: Optional[List[Agent]] = Field(description="List of agents in this crew.")
process: Process = Field(
description="Process that the crew will follow.", default=Process.sequential
)
verbose: bool = Field(
description="Verbose mode for the Agent Execution", default=False
)
config: Optional[Json] = Field(
description="Configuration of the crew.", default=None
)
def kickoff(self) -> str:
"""
Kickoff the crew to work on it's tasks.
Returns:
output (List[str]): Output of the crew for each task.
"""
if self.process == Process.sequential:
return self.__sequential_loop()
@root_validator(pre=True)
def check_config(_cls, values):
if not values.get("config") and (
not values.get("agents") and not values.get("tasks")
):
raise ValueError("Either agents and task need to be set or config.")
def __sequential_loop(self) -> str:
"""
Loop that executes the sequential process.
Returns:
output (str): Output of the crew.
"""
task_outcome = None
for task in self.tasks:
# Add delegation tools to the task if the agent allows it
if task.agent.allow_delegation:
tools = AgentTools(agents=self.agents).tools()
task.tools += tools
if values.get("config"):
config = json.loads(values.get("config"))
if not config.get("agents") or not config.get("tasks"):
raise ValueError("Config should have agents and tasks.")
self.__log(f"\nWorking Agent: {task.agent.role}")
self.__log(f"Starting Task: {task.description} ...")
values["agents"] = [Agent(**agent) for agent in config["agents"]]
task_outcome = task.execute(task_outcome)
tasks = []
for task in config["tasks"]:
task_agent = [
agt for agt in values["agents"] if agt.role == task["agent"]
][0]
del task["agent"]
tasks.append(Task(**task, agent=task_agent))
self.__log(f"Task output: {task_outcome}")
return task_outcome
def __log(self, message):
if self.verbose:
print(message)
values["tasks"] = tasks
return values
def kickoff(self) -> str:
"""
Kickoff the crew to work on it's tasks.
Returns:
output (List[str]): Output of the crew for each task.
"""
if self.process == Process.sequential:
return self.__sequential_loop()
def __sequential_loop(self) -> str:
"""
Loop that executes the sequential process.
Returns:
output (str): Output of the crew.
"""
task_outcome = None
for task in self.tasks:
# Add delegation tools to the task if the agent allows it
if task.agent.allow_delegation:
tools = AgentTools(agents=self.agents).tools()
task.tools += tools
self.__log(f"\nWorking Agent: {task.agent.role}")
self.__log(f"Starting Task: {task.description} ...")
task_outcome = task.execute(task_outcome)
self.__log(f"Task output: {task_outcome}")
return task_outcome
def __log(self, message):
if self.verbose:
print(message)

View File

@@ -1,9 +1,11 @@
from enum import Enum
class Process(str, Enum):
"""
Class representing the different processes that can be used to tackle tasks
"""
sequential = 'sequential'
# TODO: consensual = 'consensual'
# TODO: hierarchical = 'hierarchical'
"""
Class representing the different processes that can be used to tackle tasks
"""
sequential = "sequential"
# TODO: consensual = 'consensual'
# TODO: hierarchical = 'hierarchical'

View File

@@ -2,32 +2,41 @@
from textwrap import dedent
from typing import ClassVar
from pydantic.v1 import BaseModel
from langchain.prompts import PromptTemplate
from pydantic.v1 import BaseModel
class Prompts(BaseModel):
"""Prompts for generic agent."""
"""Prompts for generic agent."""
TASK_SLICE: ClassVar[str] = dedent("""\
TASK_SLICE: ClassVar[str] = dedent(
"""\
Begin! This is VERY important to you, your job depends on it!
Current Task: {input}
{agent_scratchpad}
""")
"""
)
MEMORY_SLICE: ClassVar[str] = dedent("""\
MEMORY_SLICE: ClassVar[str] = dedent(
"""\
This is the summary of your work so far:
{chat_history}
""")
"""
)
ROLE_PLAYING_SLICE: ClassVar[str] = dedent("""\
ROLE_PLAYING_SLICE: ClassVar[str] = dedent(
"""\
You are {role}.
{backstory}
Your personal goal is: {goal}
""")
"""
)
TOOLS_SLICE: ClassVar[str] = dedent("""\
TOOLS_SLICE: ClassVar[str] = dedent(
"""\
TOOLS:
------
@@ -50,9 +59,11 @@ class Prompts(BaseModel):
Thought: Do I need to use a tool? No
Final Answer: [your response here]
```
""")
"""
)
VOTING_SLICE: ClassVar[str] = dedent("""\
VOTING_SLICE: ClassVar[str] = dedent(
"""\
You are working on a crew with your co-workers and need to decide who will execute the task.
These are your format instructions:
@@ -60,16 +71,17 @@ class Prompts(BaseModel):
These are your co-workers and their roles:
{coworkers}
""")
"""
)
TASK_EXECUTION_WITH_MEMORY_PROMPT: ClassVar[str] = PromptTemplate.from_template(
ROLE_PLAYING_SLICE + TOOLS_SLICE + MEMORY_SLICE + TASK_SLICE
)
TASK_EXECUTION_WITH_MEMORY_PROMPT: ClassVar[str] = PromptTemplate.from_template(
ROLE_PLAYING_SLICE + TOOLS_SLICE + MEMORY_SLICE + TASK_SLICE
)
TASK_EXECUTION_PROMPT: ClassVar[str] = PromptTemplate.from_template(
ROLE_PLAYING_SLICE + TOOLS_SLICE + TASK_SLICE
)
TASK_EXECUTION_PROMPT: ClassVar[str] = PromptTemplate.from_template(
ROLE_PLAYING_SLICE + TOOLS_SLICE + TASK_SLICE
)
CONSENSUNS_VOTING_PROMPT: ClassVar[str] = PromptTemplate.from_template(
ROLE_PLAYING_SLICE + VOTING_SLICE + TASK_SLICE
)
CONSENSUNS_VOTING_PROMPT: ClassVar[str] = PromptTemplate.from_template(
ROLE_PLAYING_SLICE + VOTING_SLICE + TASK_SLICE
)

View File

@@ -1,42 +1,41 @@
from typing import List, Optional
from pydantic.v1 import BaseModel, Field, root_validator
from langchain.tools import Tool
from pydantic.v1 import BaseModel, Field, root_validator
from .agent import Agent
class Task(BaseModel):
"""
Class that represent a task to be executed.
"""
description: str = Field(description="Description of the actual task.")
agent: Optional[Agent] = Field(
description="Agent responsible for the task.",
default=None
)
tools: Optional[List[Tool]] = Field(
description="Tools the agent are limited to use for this task.",
default=[]
)
"""
Class that represent a task to be executed.
"""
@root_validator(pre=False)
def _set_tools(_cls, values):
if (values.get('agent')) and not (values.get('tools')):
values['tools'] = values.get('agent').tools
return values
description: str = Field(description="Description of the actual task.")
agent: Optional[Agent] = Field(
description="Agent responsible for the task.", default=None
)
tools: Optional[List[Tool]] = Field(
description="Tools the agent are limited to use for this task.", default=[]
)
def execute(self, context: str = None) -> str:
"""
Execute the task.
Returns:
output (str): Output of the task.
"""
if self.agent:
return self.agent.execute_task(
task = self.description,
context = context,
tools = self.tools
)
else:
raise Exception(f"The task '{self.description}' has no agent assigned, therefore it can't be executed directly and should be executed in a Crew using a specific process that support that, either consensual or hierarchical.")
@root_validator(pre=False)
def _set_tools(_cls, values):
if (values.get("agent")) and not (values.get("tools")):
values["tools"] = values.get("agent").tools
return values
def execute(self, context: str = None) -> str:
"""
Execute the task.
Returns:
output (str): Output of the task.
"""
if self.agent:
return self.agent.execute_task(
task=self.description, context=context, tools=self.tools
)
else:
raise Exception(
f"The task '{self.description}' has no agent assigned, therefore it can't be executed directly and should be executed in a Crew using a specific process that support that, either consensual or hierarchical."
)

View File

@@ -1,61 +1,74 @@
from typing import List, Any
from pydantic.v1 import BaseModel, Field
from textwrap import dedent
from typing import List
from langchain.tools import Tool
from pydantic.v1 import BaseModel, Field
from ..agent import Agent
class AgentTools(BaseModel):
"""Tools for generic agent."""
agents: List[Agent] = Field(description="List of agents in this crew.")
def tools(self):
return [
Tool.from_function(
func=self.delegate_work,
name="Delegate Work to Co-Worker",
description=dedent(f"""Useful to delegate a specific task to one of the
class AgentTools(BaseModel):
"""Tools for generic agent."""
agents: List[Agent] = Field(description="List of agents in this crew.")
def tools(self):
return [
Tool.from_function(
func=self.delegate_work,
name="Delegate Work to Co-Worker",
description=dedent(
f"""Useful to delegate a specific task to one of the
following co-workers: [{', '.join([agent.role for agent in self.agents])}].
The input to this tool should be a pipe (|) separated text of length
three, representing the role you want to delegate it to, the task and
information necessary. For example, `coworker|task|information`.
""")
),
Tool.from_function(
func=self.ask_question,
name="Ask Question to Co-Worker",
description=dedent(f"""Useful to ask a question, opinion or take from on
"""
),
),
Tool.from_function(
func=self.ask_question,
name="Ask Question to Co-Worker",
description=dedent(
f"""Useful to ask a question, opinion or take from on
of the following co-workers: [{', '.join([agent.role for agent in self.agents])}].
The input to this tool should be a pipe (|) separated text of length
three, representing the role you want to ask it to, the question and
information necessary. For example, `coworker|question|information`.
""")
),
]
"""
),
),
]
def delegate_work(self, command):
"""Useful to delegate a specific task to a coworker."""
return self.__execute(command)
def delegate_work(self, command):
"""Useful to delegate a specific task to a coworker."""
return self.__execute(command)
def ask_question(self, command):
"""Useful to ask a question, opinion or take from a coworker."""
return self.__execute(command)
def ask_question(self, command):
"""Useful to ask a question, opinion or take from a coworker."""
return self.__execute(command)
def __execute(self, command):
"""Execute the command."""
try:
agent, task, information = command.split("|")
except ValueError:
return "Error executing tool. Missing exact 3 pipe (|) separated values. For example, `coworker|task|information`."
def __execute(self, command):
"""Execute the command."""
try:
agent, task, information = command.split("|")
except ValueError:
return "Error executing tool. Missing exact 3 pipe (|) separated values. For example, `coworker|task|information`."
if not agent or not task or not information:
return "Error executing tool. Missing exact 3 pipe (|) separated values. For example, `coworker|question|information`."
if not agent or not task or not information:
return "Error executing tool. Missing exact 3 pipe (|) separated values. For example, `coworker|question|information`."
agent = [available_agent for available_agent in self.agents if available_agent.role == agent]
agent = [
available_agent
for available_agent in self.agents
if available_agent.role == agent
]
if len(agent) == 0:
return "Error executing tool. Co-worker not found, double check the co-worker."
if len(agent) == 0:
return (
"Error executing tool. Co-worker not found, double check the co-worker."
)
agent = agent[0]
result = agent.execute_task(task, information)
return result
agent = agent[0]
result = agent.execute_task(task, information)
return result