Reliability improvements (#77)

* fixing identation for AgentTools
* updating gitignore to exclude quick test script
* startingprompt translation
* supporting individual task output
* adding agent to task output
* cutting new version
* Updating README example
This commit is contained in:
João Moura
2024-01-07 12:43:23 -03:00
committed by GitHub
parent 234a2c72b0
commit 7954f6b51c
11 changed files with 124 additions and 122 deletions

3
.gitignore vendored
View File

@@ -4,4 +4,5 @@ __pycache__
dist/ dist/
.env .env
assets/* assets/*
.idea .idea
test.py

View File

@@ -44,18 +44,14 @@ pip install duckduckgo-search
import os import os
from crewai import Agent, Task, Crew, Process from crewai import Agent, Task, Crew, Process
os.environ["OPENAI_API_KEY"] = "YOUR KEY"
# You can choose to use a local model through Ollama for example. # You can choose to use a local model through Ollama for example.
# In this case we will use OpenHermes 2.5 as an example.
# #
# from langchain.llms import Ollama # from langchain.llms import Ollama
# ollama_llm = Ollama(model="openhermes") # ollama_llm = Ollama(model="openhermes")
# If you are using an ollama like above you don't need to set OPENAI_API_KEY.
os.environ["OPENAI_API_KEY"] = "Your Key"
# Define your tools, custom or not.
# Install duckduckgo-search for this example: # Install duckduckgo-search for this example:
#
# !pip install -U duckduckgo-search # !pip install -U duckduckgo-search
from langchain.tools import DuckDuckGoSearchRun from langchain.tools import DuckDuckGoSearchRun
@@ -65,41 +61,46 @@ search_tool = DuckDuckGoSearchRun()
researcher = Agent( researcher = Agent(
role='Senior Research Analyst', role='Senior Research Analyst',
goal='Uncover cutting-edge developments in AI and data science in', goal='Uncover cutting-edge developments in AI and data science in',
backstory="""You are a Senior Research Analyst at a leading tech think tank. backstory="""You work at a leading tech think tank.
Your expertise lies in identifying emerging trends and technologies in AI and Your expertise lies in identifying emerging trends.
data science. You have a knack for dissecting complex data and presenting You have a knack for dissecting complex data and presenting
actionable insights.""", actionable insights.""",
verbose=True, verbose=True,
allow_delegation=False, allow_delegation=False,
tools=[search_tool] tools=[search_tool]
# (optional) llm=ollama_llm, If you wanna use a local modal through Ollama, default is GPT4 with temperature=0.7 # You can pass an optional llm attribute specifying what mode you wanna use.
# It can be a local model through Ollama / LM Studio or a remote
# model like OpenAI, Mistral, Antrophic of others (https://python.langchain.com/docs/integrations/llms/)
#
# Examples:
# llm=ollama_llm # was defined above in the file
# llm=ChatOpenAI(model_name="gpt-3.5", temperature=0.7)
) )
writer = Agent( writer = Agent(
role='Tech Content Strategist', role='Tech Content Strategist',
goal='Craft compelling content on tech advancements', goal='Craft compelling content on tech advancements',
backstory="""You are a renowned Tech Content Strategist, known for your insightful backstory="""You are a renowned Content Strategist, known for
and engaging articles on technology and innovation. With a deep understanding of your insightful and engaging articles.
the tech industry, you transform complex concepts into compelling narratives.""", You transform complex concepts into compelling narratives.""",
verbose=True, verbose=True,
# (optional) llm=ollama_llm, If you wanna use a local modal through Ollama, default is GPT4 with temperature=0.7 allow_delegation=True,
allow_delegation=True # (optional) llm=ollama_llm
) )
# Create tasks for your agents # Create tasks for your agents
task1 = Task( task1 = Task(
description="""Conduct a comprehensive analysis of the latest advancements in AI in 2024. description="""Conduct a comprehensive analysis of the latest advancements in AI in 2024.
Identify key trends, breakthrough technologies, and potential industry impacts. Identify key trends, breakthrough technologies, and potential industry impacts.
Compile your findings in a detailed report. Your final answer MUST be a full analysis report""", Your final answer MUST be a full analysis report""",
agent=researcher agent=researcher
) )
task2 = Task( task2 = Task(
description="""Using the insights from the researcher's report, develop an engaging blog description="""Using the insights provided, develop an engaging blog
post that highlights the most significant AI advancements. post that highlights the most significant AI advancements.
Your post should be informative yet accessible, catering to a tech-savvy audience. Your post should be informative yet accessible, catering to a tech-savvy audience.
Aim for a narrative that captures the essence of these breakthroughs and their Make it sound cool, avoid complex words so it doesn't sound like AI.
implications for the future. Your final answer MUST be the full blog post of at least 3 paragraphs.""", Your final answer MUST be the full blog post of at least 4 paragraphs.""",
agent=writer agent=writer
) )
@@ -107,8 +108,7 @@ task2 = Task(
crew = Crew( crew = Crew(
agents=[researcher, writer], agents=[researcher, writer],
tasks=[task1, task2], tasks=[task1, task2],
verbose=2, # Crew verbose more will let you know what tasks are being worked on, you can set it to 1 or 2 to different logging levels verbose=2, # You can set it to 1 or 2 to different logging levels
process=Process.sequential # Sequential process will have tasks executed one after the other and the outcome of the previous one is passed as extra content into this next.
) )
# Get your crew to work! # Get your crew to work!

View File

@@ -155,9 +155,9 @@ class Agent(BaseModel):
) )
executor_args["memory"] = summary_memory executor_args["memory"] = summary_memory
agent_args["chat_history"] = lambda x: x["chat_history"] agent_args["chat_history"] = lambda x: x["chat_history"]
prompt = Prompts.TASK_EXECUTION_WITH_MEMORY_PROMPT prompt = Prompts().task_execution_with_memory()
else: else:
prompt = Prompts.TASK_EXECUTION_PROMPT prompt = Prompts().task_execution()
execution_prompt = prompt.partial( execution_prompt = prompt.partial(
goal=self.goal, goal=self.goal,

View File

@@ -111,21 +111,21 @@ class Crew(BaseModel):
Returns: Returns:
Output of the crew. Output of the crew.
""" """
task_outcome = None task_output = None
for task in self.tasks: for task in self.tasks:
# Add delegation tools to the task if the agent allows it # Add delegation tools to the task if the agent allows it
if task.agent.allow_delegation: if task.agent.allow_delegation:
tools = AgentTools(agents=self.agents).tools() agent_tools = AgentTools(agents=self.agents).tools()
task.tools += tools task.tools += agent_tools
self.__log("debug", f"Working Agent: {task.agent.role}") self.__log("debug", f"Working Agent: {task.agent.role}")
self.__log("info", f"Starting Task: {task.description} ...") self.__log("info", f"Starting Task: {task.description}")
task_outcome = task.execute(task_outcome) task_output = task.execute(task_output)
self.__log(
self.__log("debug", f"Task output: {task_outcome}") "debug", f"\n\n[{task.agent.role}] Task output: {task_output}\n\n"
)
return task_outcome return task_output
def __log(self, level, message): def __log(self, level, message):
"""Log a message""" """Log a message"""

View File

@@ -1,84 +1,53 @@
"""Prompts for generic agent.""" """Prompts for generic agent."""
import json
from textwrap import dedent import os
from typing import ClassVar from typing import ClassVar, Dict, Optional
from langchain.prompts import PromptTemplate from langchain.prompts import PromptTemplate
from pydantic import BaseModel from pydantic import BaseModel, Field, PrivateAttr, model_validator
class Prompts(BaseModel): class Prompts(BaseModel):
"""Prompts for generic agent.""" """Prompts for generic agent."""
TASK_SLICE: ClassVar[str] = dedent( _prompts: Optional[Dict[str, str]] = PrivateAttr()
"""\ language: Optional[str] = Field(
Begin! This is VERY important to you, your job depends on it! default="en",
description="Language of crewai prompts.",
Current Task: {input}"""
) )
@model_validator(mode="after")
def load_prompts(self) -> "Prompts":
"""Load prompts from file."""
dir_path = os.path.dirname(os.path.realpath(__file__))
prompts_path = os.path.join(dir_path, f"prompts/{self.language}.json")
with open(prompts_path, "r") as f:
self._prompts = json.load(f)["slices"]
return self
SCRATCHPAD_SLICE: ClassVar[str] = "\n{agent_scratchpad}" SCRATCHPAD_SLICE: ClassVar[str] = "\n{agent_scratchpad}"
MEMORY_SLICE: ClassVar[str] = dedent( def task_execution_with_memory(self) -> str:
"""\ return PromptTemplate.from_template(
This is the summary of your work so far: self._prompts["role_playing"]
{chat_history}""" + self._prompts["tools"]
) + self._prompts["memory"]
+ self._prompts["task"]
+ self.SCRATCHPAD_SLICE
)
ROLE_PLAYING_SLICE: ClassVar[str] = dedent( def task_execution_without_tools(self) -> str:
"""\ return PromptTemplate.from_template(
You are {role}. self._prompts["role_playing"]
{backstory} + self._prompts["task"]
+ self.SCRATCHPAD_SLICE
)
Your personal goal is: {goal}""" def task_execution(self) -> str:
) return PromptTemplate.from_template(
self._prompts["role_playing"]
TOOLS_SLICE: ClassVar[str] = dedent( + self._prompts["tools"]
"""\ + self._prompts["task"]
+ self.SCRATCHPAD_SLICE
)
TOOLS:
------
You have access to the following tools:
{tools}
To use a tool, please use the exact following format:
```
Thought: Do I need to use a tool? Yes
Action: the action to take, should be one of [{tool_names}], just the name.
Action Input: the input to the action
Observation: the result of the action
```
When you have a response for your task, or if you do not need to use a tool, you MUST use the format:
```
Thought: Do I need to use a tool? No
Final Answer: [your response here]
```"""
)
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:
{format_instructions}
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 + SCRATCHPAD_SLICE
)
TASK_EXECUTION_PROMPT: ClassVar[str] = PromptTemplate.from_template(
ROLE_PLAYING_SLICE + TOOLS_SLICE + TASK_SLICE + SCRATCHPAD_SLICE
)
CONSENSUNS_VOTING_PROMPT: ClassVar[str] = PromptTemplate.from_template(
ROLE_PLAYING_SLICE + VOTING_SLICE + TASK_SLICE + SCRATCHPAD_SLICE
)

8
crewai/prompts/en.json Normal file
View File

@@ -0,0 +1,8 @@
{
"slices": {
"task": "Begin! This is VERY important to you, your job depends on it!\n\nCurrent Task: {input}",
"memory": "This is the summary of your work so far:\n{chat_history}",
"role_playing": "You are {role}.\n{backstory}\n\nYour personal goal is: {goal}",
"tools": "TOOLS:\n------\nYou have access to the following tools:\n\n{tools}\n\nTo use a tool, please use the exact following format:\n\n```\nThought: Do I need to use a tool? Yes\nAction: the action to take, should be one of [{tool_names}], just the name.\nAction Input: the input to the action\nObservation: the result of the action\n```\n\nWhen you have a response for your task, or if you do not need to use a tool, you MUST use the format:\n\n```\nThought: Do I need to use a tool? No\nFinal Answer: [your response here]"
}
}

View File

@@ -5,6 +5,7 @@ from pydantic import UUID4, BaseModel, Field, field_validator, model_validator
from pydantic_core import PydanticCustomError from pydantic_core import PydanticCustomError
from crewai.agent import Agent from crewai.agent import Agent
from crewai.tasks.task_output import TaskOutput
class Task(BaseModel): class Task(BaseModel):
@@ -19,6 +20,9 @@ class Task(BaseModel):
default_factory=list, default_factory=list,
description="Tools the agent are limited to use for this task.", description="Tools the agent are limited to use for this task.",
) )
output: Optional[TaskOutput] = Field(
description="Task output, it's final result.", default=None
)
id: UUID4 = Field( id: UUID4 = Field(
default_factory=uuid.uuid4, default_factory=uuid.uuid4,
frozen=True, frozen=True,
@@ -46,9 +50,12 @@ class Task(BaseModel):
Output of the task. Output of the task.
""" """
if self.agent: if self.agent:
return self.agent.execute_task( result = self.agent.execute_task(
task=self.description, context=context, tools=self.tools task=self.description, context=context, tools=self.tools
) )
self.output = TaskOutput(description=self.description, result=result)
return result
else: else:
raise Exception( 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." 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

@@ -0,0 +1,17 @@
from typing import Optional
from pydantic import BaseModel, Field, model_validator
class TaskOutput(BaseModel):
"""Class that represents the result of a task."""
description: str = Field(description="Description of the task")
summary: Optional[str] = Field(description="Summary of the task", default=None)
result: str = Field(description="Result of the task")
@model_validator(mode="after")
def set_summary(self):
excerpt = " ".join(self.description.split(" ")[0:10])
self.summary = f"{excerpt}..."
return self

View File

@@ -8,7 +8,7 @@ from crewai.agent import Agent
class AgentTools(BaseModel): class AgentTools(BaseModel):
"""Tools for generic agent.""" """Default tools around agent delegation"""
agents: List[Agent] = Field(description="List of agents in this crew.") agents: List[Agent] = Field(description="List of agents in this crew.")
@@ -20,12 +20,12 @@ class AgentTools(BaseModel):
description=dedent( description=dedent(
f"""\ f"""\
Useful to delegate a specific task to one of the Useful to delegate a specific task to one of the
following co-workers: [{', '.join([agent.role for agent in self.agents])}]. following co-workers: [{', '.join([agent.role for agent in self.agents])}].
The input to this tool should be a pipe (|) separated text of length The input to this tool should be a pipe (|) separated text of length
three, representing the co-worker you want to ask it to (one of the options), three, representing the co-worker you want to ask it to (one of the options),
the task and all actual context you have for the task. the task and all actual context you have for the task.
For example, `coworker|task|context`. For example, `coworker|task|context`.
""" """
), ),
), ),
Tool.from_function( Tool.from_function(
@@ -34,12 +34,12 @@ class AgentTools(BaseModel):
description=dedent( description=dedent(
f"""\ f"""\
Useful to ask a question, opinion or take from on Useful to ask a question, opinion or take from on
of the following co-workers: [{', '.join([agent.role for agent in self.agents])}]. 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 The input to this tool should be a pipe (|) separated text of length
three, representing the co-worker you want to ask it to (one of the options), three, representing the co-worker you want to ask it to (one of the options),
the question and all actual context you have for the question. the question and all actual context you have for the question.
For example, `coworker|question|context`. For example, `coworker|question|context`.
""" """
), ),
), ),
] ]

View File

@@ -1,7 +1,7 @@
[tool.poetry] [tool.poetry]
name = "crewai" name = "crewai"
version = "0.1.16" version = "0.1.23"
description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks." description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks."
authors = ["Joao Moura <joaomdmoura@gmail.com>"] authors = ["Joao Moura <joaomdmoura@gmail.com>"]
readme = "README.md" readme = "README.md"

View File

@@ -180,11 +180,11 @@ def test_crew_verbose_output(capsys):
captured = capsys.readouterr() captured = capsys.readouterr()
expected_strings = [ expected_strings = [
"Working Agent: Researcher", "Working Agent: Researcher",
"Starting Task: Research AI advancements. ...", "Starting Task: Research AI advancements.",
"Task output:", "[Researcher] Task output:",
"Working Agent: Senior Writer", "Working Agent: Senior Writer",
"Starting Task: Write about AI in healthcare. ...", "Starting Task: Write about AI in healthcare.",
"Task output:", "[Senior Writer] Task output:",
] ]
for expected_string in expected_strings: for expected_string in expected_strings:
@@ -205,7 +205,7 @@ def test_crew_verbose_levels_output(capsys):
crew.kickoff() crew.kickoff()
captured = capsys.readouterr() captured = capsys.readouterr()
expected_strings = ["Working Agent: Researcher", "Task output:"] expected_strings = ["Working Agent: Researcher", "[Researcher] Task output:"]
for expected_string in expected_strings: for expected_string in expected_strings:
assert expected_string in captured.out assert expected_string in captured.out
@@ -216,8 +216,8 @@ def test_crew_verbose_levels_output(capsys):
captured = capsys.readouterr() captured = capsys.readouterr()
expected_strings = [ expected_strings = [
"Working Agent: Researcher", "Working Agent: Researcher",
"Starting Task: Write about AI advancements. ...", "Starting Task: Write about AI advancements.",
"Task output:", "[Researcher] Task output:",
] ]
for expected_string in expected_strings: for expected_string in expected_strings: