mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-08 15:48:29 +00:00
Merge remote-tracking branch 'upstream/main'
# Conflicts: # src/crewai/crew.py
This commit is contained in:
@@ -27,6 +27,8 @@ A crew in crewAI represents a collaborative group of agents working together to
|
||||
| **Step Callback** *(optional)* | A function that is called after each step of every agent. This can be used to log the agent's actions or to perform other operations; it won't override the agent-specific `step_callback`. |
|
||||
| **Task Callback** *(optional)* | A function that is called after the completion of each task. Useful for monitoring or additional operations post-task execution. |
|
||||
| **Share Crew** *(optional)* | Whether you want to share the complete crew information and execution with the crewAI team to make the library better, and allow us to train models. |
|
||||
| **Output Log File** *(optional)* | Whether you want to have a file with the complete crew output and execution. You can set it using True and it will default to the folder you are currently and it will be called logs.txt or passing a string with the full path and name of the file. |
|
||||
|
||||
|
||||
!!! note "Crew Max RPM"
|
||||
The `max_rpm` attribute sets the maximum number of requests per minute the crew can perform to avoid rate limits and will override individual agents' `max_rpm` settings if you set it.
|
||||
|
||||
@@ -96,26 +96,26 @@ This is useful when you have a task that depends on the output of another task t
|
||||
# ...
|
||||
|
||||
research_ai_task = Task(
|
||||
description='Find and summarize the latest AI news',
|
||||
expected_output='A bullet list summary of the top 5 most important AI news',
|
||||
async_execution=True,
|
||||
agent=research_agent,
|
||||
tools=[search_tool]
|
||||
description='Find and summarize the latest AI news',
|
||||
expected_output='A bullet list summary of the top 5 most important AI news',
|
||||
async_execution=True,
|
||||
agent=research_agent,
|
||||
tools=[search_tool]
|
||||
)
|
||||
|
||||
research_ops_task = Task(
|
||||
description='Find and summarize the latest AI Ops news',
|
||||
expected_output='A bullet list summary of the top 5 most important AI Ops news',
|
||||
async_execution=True,
|
||||
agent=research_agent,
|
||||
tools=[search_tool]
|
||||
description='Find and summarize the latest AI Ops news',
|
||||
expected_output='A bullet list summary of the top 5 most important AI Ops news',
|
||||
async_execution=True,
|
||||
agent=research_agent,
|
||||
tools=[search_tool]
|
||||
)
|
||||
|
||||
write_blog_task = Task(
|
||||
description="Write a full blog post about the importance of AI and its latest news",
|
||||
expected_output='Full blog post that is 4 paragraphs long',
|
||||
agent=writer_agent,
|
||||
context=[research_ai_task, research_ops_task]
|
||||
description="Write a full blog post about the importance of AI and its latest news",
|
||||
expected_output='Full blog post that is 4 paragraphs long',
|
||||
agent=writer_agent,
|
||||
context=[research_ai_task, research_ops_task]
|
||||
)
|
||||
|
||||
#...
|
||||
@@ -131,24 +131,24 @@ You can then use the `context` attribute to define in a future task that it shou
|
||||
#...
|
||||
|
||||
list_ideas = Task(
|
||||
description="List of 5 interesting ideas to explore for an article about AI.",
|
||||
expected_output="Bullet point list of 5 ideas for an article.",
|
||||
agent=researcher,
|
||||
async_execution=True # Will be executed asynchronously
|
||||
description="List of 5 interesting ideas to explore for an article about AI.",
|
||||
expected_output="Bullet point list of 5 ideas for an article.",
|
||||
agent=researcher,
|
||||
async_execution=True # Will be executed asynchronously
|
||||
)
|
||||
|
||||
list_important_history = Task(
|
||||
description="Research the history of AI and give me the 5 most important events.",
|
||||
expected_output="Bullet point list of 5 important events.",
|
||||
agent=researcher,
|
||||
async_execution=True # Will be executed asynchronously
|
||||
description="Research the history of AI and give me the 5 most important events.",
|
||||
expected_output="Bullet point list of 5 important events.",
|
||||
agent=researcher,
|
||||
async_execution=True # Will be executed asynchronously
|
||||
)
|
||||
|
||||
write_article = Task(
|
||||
description="Write an article about AI, its history, and interesting ideas.",
|
||||
expected_output="A 4 paragraph article about AI.",
|
||||
agent=writer,
|
||||
context=[list_ideas, list_important_history] # Will wait for the output of the two tasks to be completed
|
||||
description="Write an article about AI, its history, and interesting ideas.",
|
||||
expected_output="A 4 paragraph article about AI.",
|
||||
agent=writer,
|
||||
context=[list_ideas, list_important_history] # Will wait for the output of the two tasks to be completed
|
||||
)
|
||||
|
||||
#...
|
||||
@@ -162,20 +162,20 @@ The callback function is executed after the task is completed, allowing for acti
|
||||
# ...
|
||||
|
||||
def callback_function(output: TaskOutput):
|
||||
# Do something after the task is completed
|
||||
# Example: Send an email to the manager
|
||||
print(f"""
|
||||
Task completed!
|
||||
Task: {output.description}
|
||||
Output: {output.raw_output}
|
||||
""")
|
||||
# Do something after the task is completed
|
||||
# Example: Send an email to the manager
|
||||
print(f"""
|
||||
Task completed!
|
||||
Task: {output.description}
|
||||
Output: {output.raw_output}
|
||||
""")
|
||||
|
||||
research_task = Task(
|
||||
description='Find and summarize the latest AI news',
|
||||
expected_output='A bullet list summary of the top 5 most important AI news',
|
||||
agent=research_agent,
|
||||
tools=[search_tool],
|
||||
callback=callback_function
|
||||
description='Find and summarize the latest AI news',
|
||||
expected_output='A bullet list summary of the top 5 most important AI news',
|
||||
agent=research_agent,
|
||||
tools=[search_tool],
|
||||
callback=callback_function
|
||||
)
|
||||
|
||||
#...
|
||||
@@ -188,27 +188,27 @@ Once a crew finishes running, you can access the output of a specific task by us
|
||||
```python
|
||||
# ...
|
||||
task1 = Task(
|
||||
description='Find and summarize the latest AI news',
|
||||
expected_output='A bullet list summary of the top 5 most important AI news',
|
||||
agent=research_agent,
|
||||
tools=[search_tool]
|
||||
description='Find and summarize the latest AI news',
|
||||
expected_output='A bullet list summary of the top 5 most important AI news',
|
||||
agent=research_agent,
|
||||
tools=[search_tool]
|
||||
)
|
||||
|
||||
#...
|
||||
|
||||
crew = Crew(
|
||||
agents=[research_agent],
|
||||
tasks=[task1, task2, task3],
|
||||
verbose=2
|
||||
agents=[research_agent],
|
||||
tasks=[task1, task2, task3],
|
||||
verbose=2
|
||||
)
|
||||
|
||||
result = crew.kickoff()
|
||||
|
||||
# Returns a TaskOutput object with the description and results of the task
|
||||
print(f"""
|
||||
Task completed!
|
||||
Task: {task1.output.description}
|
||||
Output: {task1.output.raw_output}
|
||||
Task completed!
|
||||
Task: {task1.output.description}
|
||||
Output: {task1.output.raw_output}
|
||||
""")
|
||||
```
|
||||
|
||||
|
||||
@@ -22,15 +22,15 @@ from crewai_tools import GithubSearchTool
|
||||
|
||||
# Initialize the tool for semantic searches within a specific GitHub repository
|
||||
tool = GithubSearchTool(
|
||||
github_repo='https://github.com/example/repo',
|
||||
content_types=['code', 'issue'] # Options: code, repo, pr, issue
|
||||
github_repo='https://github.com/example/repo',
|
||||
content_types=['code', 'issue'] # Options: code, repo, pr, issue
|
||||
)
|
||||
|
||||
# OR
|
||||
|
||||
# Initialize the tool for semantic searches within a specific GitHub repository, so the agent can search any repository if it learns about during its execution
|
||||
tool = GithubSearchTool(
|
||||
content_types=['code', 'issue'] # Options: code, repo, pr, issue
|
||||
content_types=['code', 'issue'] # Options: code, repo, pr, issue
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
2
poetry.lock
generated
2
poetry.lock
generated
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "agentops"
|
||||
|
||||
@@ -25,12 +25,13 @@ from crewai.process import Process
|
||||
from crewai.task import Task
|
||||
from crewai.telemetry import Telemetry
|
||||
from crewai.tools.agent_tools import AgentTools
|
||||
from crewai.utilities import I18N, Logger, RPMController
|
||||
from crewai.utilities import I18N, Logger, RPMController, FileHandler
|
||||
try:
|
||||
import agentops
|
||||
except ImportError:
|
||||
agentops = None
|
||||
|
||||
|
||||
class Crew(BaseModel):
|
||||
"""
|
||||
Represents a group of agents, defining how they should collaborate and the tasks they should perform.
|
||||
@@ -58,6 +59,7 @@ class Crew(BaseModel):
|
||||
_execution_span: Any = PrivateAttr()
|
||||
_rpm_controller: RPMController = PrivateAttr()
|
||||
_logger: Logger = PrivateAttr()
|
||||
_file_handler: FileHandler = PrivateAttr()
|
||||
_cache_handler: InstanceOf[CacheHandler] = PrivateAttr(default=CacheHandler())
|
||||
_short_term_memory: Optional[InstanceOf[ShortTermMemory]] = PrivateAttr()
|
||||
_long_term_memory: Optional[InstanceOf[LongTermMemory]] = PrivateAttr()
|
||||
@@ -118,6 +120,10 @@ class Crew(BaseModel):
|
||||
default=None,
|
||||
description="Path to the language file to be used for the crew.",
|
||||
)
|
||||
output_log_file: Optional[Union[bool, str]] = Field(
|
||||
default=False,
|
||||
description="output_log_file",
|
||||
)
|
||||
|
||||
@field_validator("id", mode="before")
|
||||
@classmethod
|
||||
@@ -148,6 +154,8 @@ class Crew(BaseModel):
|
||||
"""Set private attributes."""
|
||||
self._cache_handler = CacheHandler()
|
||||
self._logger = Logger(self.verbose)
|
||||
if self.output_log_file:
|
||||
self._file_handler = FileHandler(self.output_log_file)
|
||||
self._rpm_controller = RPMController(max_rpm=self.max_rpm, logger=self._logger)
|
||||
self._telemetry = Telemetry()
|
||||
self._telemetry.set_tracer()
|
||||
@@ -284,6 +292,11 @@ class Crew(BaseModel):
|
||||
"info", f"== Starting Task: {task.description}", color="bold_purple"
|
||||
)
|
||||
|
||||
if self.output_log_file:
|
||||
self._file_handler.log(
|
||||
agent=role, task=task.description, status="started"
|
||||
)
|
||||
|
||||
output = task.execute(context=task_output)
|
||||
if not task.async_execution:
|
||||
task_output = output
|
||||
@@ -291,6 +304,9 @@ class Crew(BaseModel):
|
||||
role = task.agent.role if task.agent is not None else "None"
|
||||
self._logger.log("debug", f"== [{role}] Task output: {task_output}\n\n")
|
||||
|
||||
if self.output_log_file:
|
||||
self._file_handler.log(agent=role, task=task_output, status="completed")
|
||||
|
||||
self._finish_execution(task_output)
|
||||
return self._format_output(task_output)
|
||||
|
||||
@@ -312,12 +328,22 @@ class Crew(BaseModel):
|
||||
self._logger.log("debug", f"Working Agent: {manager.role}")
|
||||
self._logger.log("info", f"Starting Task: {task.description}")
|
||||
|
||||
if self.output_log_file:
|
||||
self._file_handler.log(
|
||||
agent=manager.role, task=task.description, status="started"
|
||||
)
|
||||
|
||||
task_output = task.execute(
|
||||
agent=manager, context=task_output, tools=manager.tools
|
||||
)
|
||||
|
||||
self._logger.log("debug", f"[{manager.role}] Task output: {task_output}")
|
||||
|
||||
if self.output_log_file:
|
||||
self._file_handler.log(
|
||||
agent=manager.role, task=task_output, status="completed"
|
||||
)
|
||||
|
||||
self._finish_execution(task_output)
|
||||
return self._format_output(task_output), manager._token_process.get_summary()
|
||||
|
||||
|
||||
@@ -5,3 +5,4 @@ from .logger import Logger
|
||||
from .printer import Printer
|
||||
from .prompts import Prompts
|
||||
from .rpm_controller import RPMController
|
||||
from .fileHandler import FileHandler
|
||||
|
||||
20
src/crewai/utilities/fileHandler.py
Normal file
20
src/crewai/utilities/fileHandler.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class FileHandler:
|
||||
"""take care of file operations, currently it only logs messages to a file"""
|
||||
|
||||
def __init__(self, file_path):
|
||||
if isinstance(file_path, bool):
|
||||
self._path = os.path.join(os.curdir, "logs.txt")
|
||||
elif isinstance(file_path, str):
|
||||
self._path = file_path
|
||||
else:
|
||||
raise ValueError("file_path must be either a boolean or a string.")
|
||||
|
||||
def log(self, **kwargs):
|
||||
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
message = f"{now}: ".join([f"{key}={value}" for key, value in kwargs.items()])
|
||||
with open(self._path, "a") as file:
|
||||
file.write(message + "\n")
|
||||
160
tests/cassettes/test_crew_log_file_output.yaml
Normal file
160
tests/cassettes/test_crew_log_file_output.yaml
Normal file
@@ -0,0 +1,160 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '{"messages": [{"role": "user", "content": "You are Researcher. You''re
|
||||
an expert researcher, specialized in technology, software engineering, AI and
|
||||
startups. You work as a freelancer and is now working on doing research and
|
||||
analysis for a new customer.\nYour personal goal is: Make the best research
|
||||
and analysis on content about AI and AI agentsTo give my best complete final
|
||||
answer to the task use the exact following format:\n\nThought: I now can give
|
||||
a great answer\nFinal Answer: my best complete final answer to the task.\nYour
|
||||
final answer must be the great and the most complete as possible, it must be
|
||||
outcome described.\n\nI MUST use these formats, my job depends on it!\nCurrent
|
||||
Task: Say Hi\n\nThis is the expect criteria for your final answer: The word:
|
||||
Hi \n you MUST return the actual complete content as the final answer, not a
|
||||
summary.\n\nBegin! This is VERY important to you, use the tools available and
|
||||
give your best Final Answer, your job depends on it!\n\nThought: \n"}], "model":
|
||||
"gpt-4", "n": 1, "stop": ["\nObservation"], "stream": true, "temperature": 0.7}'
|
||||
headers:
|
||||
accept:
|
||||
- application/json
|
||||
accept-encoding:
|
||||
- gzip, deflate, br
|
||||
connection:
|
||||
- keep-alive
|
||||
content-length:
|
||||
- '1082'
|
||||
content-type:
|
||||
- application/json
|
||||
cookie:
|
||||
- _cfuvid=zVKnitRNLhrt8b2P3MHGfXS_82YiqkGpi46seIwshAM-1709396719694-0.0.1.1-604800000
|
||||
host:
|
||||
- api.openai.com
|
||||
user-agent:
|
||||
- OpenAI/Python 1.16.2
|
||||
x-stainless-arch:
|
||||
- other:amd64
|
||||
x-stainless-async:
|
||||
- 'false'
|
||||
x-stainless-lang:
|
||||
- python
|
||||
x-stainless-os:
|
||||
- Windows
|
||||
x-stainless-package-version:
|
||||
- 1.16.2
|
||||
x-stainless-runtime:
|
||||
- CPython
|
||||
x-stainless-runtime-version:
|
||||
- 3.10.10
|
||||
method: POST
|
||||
uri: https://api.openai.com/v1/chat/completions
|
||||
response:
|
||||
body:
|
||||
string: 'data: {"id":"chatcmpl-9AjeorqO0QfP8DJ8NwGIUKSlhQqav","object":"chat.completion.chunk","created":1712345814,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"role":"assistant","content":""},"logprobs":null,"finish_reason":null}]}
|
||||
|
||||
|
||||
data: {"id":"chatcmpl-9AjeorqO0QfP8DJ8NwGIUKSlhQqav","object":"chat.completion.chunk","created":1712345814,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"I"},"logprobs":null,"finish_reason":null}]}
|
||||
|
||||
|
||||
data: {"id":"chatcmpl-9AjeorqO0QfP8DJ8NwGIUKSlhQqav","object":"chat.completion.chunk","created":1712345814,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
|
||||
now"},"logprobs":null,"finish_reason":null}]}
|
||||
|
||||
|
||||
data: {"id":"chatcmpl-9AjeorqO0QfP8DJ8NwGIUKSlhQqav","object":"chat.completion.chunk","created":1712345814,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
|
||||
can"},"logprobs":null,"finish_reason":null}]}
|
||||
|
||||
|
||||
data: {"id":"chatcmpl-9AjeorqO0QfP8DJ8NwGIUKSlhQqav","object":"chat.completion.chunk","created":1712345814,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
|
||||
give"},"logprobs":null,"finish_reason":null}]}
|
||||
|
||||
|
||||
data: {"id":"chatcmpl-9AjeorqO0QfP8DJ8NwGIUKSlhQqav","object":"chat.completion.chunk","created":1712345814,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
|
||||
a"},"logprobs":null,"finish_reason":null}]}
|
||||
|
||||
|
||||
data: {"id":"chatcmpl-9AjeorqO0QfP8DJ8NwGIUKSlhQqav","object":"chat.completion.chunk","created":1712345814,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
|
||||
great"},"logprobs":null,"finish_reason":null}]}
|
||||
|
||||
|
||||
data: {"id":"chatcmpl-9AjeorqO0QfP8DJ8NwGIUKSlhQqav","object":"chat.completion.chunk","created":1712345814,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
|
||||
answer"},"logprobs":null,"finish_reason":null}]}
|
||||
|
||||
|
||||
data: {"id":"chatcmpl-9AjeorqO0QfP8DJ8NwGIUKSlhQqav","object":"chat.completion.chunk","created":1712345814,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"\n\n"},"logprobs":null,"finish_reason":null}]}
|
||||
|
||||
|
||||
data: {"id":"chatcmpl-9AjeorqO0QfP8DJ8NwGIUKSlhQqav","object":"chat.completion.chunk","created":1712345814,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"Final"},"logprobs":null,"finish_reason":null}]}
|
||||
|
||||
|
||||
data: {"id":"chatcmpl-9AjeorqO0QfP8DJ8NwGIUKSlhQqav","object":"chat.completion.chunk","created":1712345814,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
|
||||
Answer"},"logprobs":null,"finish_reason":null}]}
|
||||
|
||||
|
||||
data: {"id":"chatcmpl-9AjeorqO0QfP8DJ8NwGIUKSlhQqav","object":"chat.completion.chunk","created":1712345814,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":":"},"logprobs":null,"finish_reason":null}]}
|
||||
|
||||
|
||||
data: {"id":"chatcmpl-9AjeorqO0QfP8DJ8NwGIUKSlhQqav","object":"chat.completion.chunk","created":1712345814,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{"content":"
|
||||
Hi"},"logprobs":null,"finish_reason":null}]}
|
||||
|
||||
|
||||
data: {"id":"chatcmpl-9AjeorqO0QfP8DJ8NwGIUKSlhQqav","object":"chat.completion.chunk","created":1712345814,"model":"gpt-4-0613","system_fingerprint":null,"choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}
|
||||
|
||||
|
||||
data: [DONE]
|
||||
|
||||
|
||||
'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 86fbfd5e4e45012b-GRU
|
||||
Cache-Control:
|
||||
- no-cache, must-revalidate
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- text/event-stream
|
||||
Date:
|
||||
- Fri, 05 Apr 2024 19:36:55 GMT
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- __cf_bm=Dup92ckJhbI_FxZty6XIjohW.sTaChSMX.8lwju_iA8-1712345815-1.0.1.1-DYCBIKozKcyEYWv.mE5gRee5frdxJU8EBOeZrex7BOH_U4HLjPJ4IMUP0m_YMiO3fKf5IClhW3KIzE8cl2C.ww;
|
||||
path=/; expires=Fri, 05-Apr-24 20:06:55 GMT; domain=.api.openai.com; HttpOnly;
|
||||
Secure; SameSite=None
|
||||
- _cfuvid=.Ctsps4oQpopvSdn2mneN2jnLB0vatjzGjPz1HgR734-1712345815450-0.0.1.1-604800000;
|
||||
path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
access-control-allow-origin:
|
||||
- '*'
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
openai-model:
|
||||
- gpt-4-0613
|
||||
openai-organization:
|
||||
- crewai-iuxna1
|
||||
openai-processing-ms:
|
||||
- '363'
|
||||
openai-version:
|
||||
- '2020-10-01'
|
||||
strict-transport-security:
|
||||
- max-age=15724800; includeSubDomains
|
||||
x-ratelimit-limit-requests:
|
||||
- '10000'
|
||||
x-ratelimit-limit-tokens:
|
||||
- '300000'
|
||||
x-ratelimit-remaining-requests:
|
||||
- '9999'
|
||||
x-ratelimit-remaining-tokens:
|
||||
- '299750'
|
||||
x-ratelimit-reset-requests:
|
||||
- 6ms
|
||||
x-ratelimit-reset-tokens:
|
||||
- 50ms
|
||||
x-request-id:
|
||||
- req_83d3bc5e55b3d012f700b51707cc46e0
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -894,3 +894,21 @@ def test_disabled_memory_using_contextual_memory():
|
||||
with patch.object(ContextualMemory, "build_context_for_task") as contextual_mem:
|
||||
crew.kickoff()
|
||||
contextual_mem.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.vcr(filter_headers=["authorization"])
|
||||
def test_crew_log_file_output(tmp_path):
|
||||
test_file = tmp_path / "logs.txt"
|
||||
tasks = [
|
||||
Task(
|
||||
description="Say Hi",
|
||||
expected_output="The word: Hi",
|
||||
agent=researcher,
|
||||
)
|
||||
]
|
||||
|
||||
test_message = {"agent": "Researcher", "task": "Say Hi"}
|
||||
|
||||
crew = Crew(agents=[researcher], tasks=tasks, output_log_file=str(test_file))
|
||||
crew.kickoff()
|
||||
assert test_file.exists()
|
||||
|
||||
Reference in New Issue
Block a user