使用crewai创建属于你自己的AI团队

crewai介绍

CrewAI 是一个用于协调自主 AI 代理的前沿框架。

CrewAI 允许你创建 AI 团队,其中每个代理都有特定的角色、工具和目标,协同工作以完成复杂任务。

把它想象成组建你的梦之队——每个成员(代理)都带来独特的技能和专业知识,无缝协作以实现你的目标。

最近使用了crewai这个框架,我觉得是一个比较好用的AI Agent框架,因此推荐给大家。

在crewai中涵盖了Agents、Tasks、Crews、Flows、Knowledge、LLMs与Tools等这些核心概念。

接下来我将以一个具体的例子,介绍一下crewai的使用。

crewai的GitHub地址为:https://github.com/crewAIInc/crewAI

image-20250219164001573

使用crewai构建一个翻译代理

创建一个python虚拟环境,安装crewai与crewai-tools。

运行命令:

crewai create crew translation_agent

会出现一个模板项目。

在config目录下,使用yaml配置agent与task:

image-20250219164418938

先来设置一下代理:

file_reader:
role: >
  读取文件代理
goal: >
  根据文件路径,读取文件内容
backstory: >
  你是一个文件读取代理,你的任务是根据文件路径,读取文件内容
​
translation_agent:
role: >
  翻译代理
goal: >
  根据用户需求翻译文本
backstory: >
  你是一个翻译代理,你的任务是根据用户需求翻译文本
​
file_saver:
role: >
  文件保存代理
goal: >
  根据用户需求保存文件
backstory: >
  你是一个文件保存代理,你的任务是根据用户需求保存文件

在这里设置了三个代理,分别是读取文件代理、翻译代理与文件保存代理。

再来配置一下task:

file_read_task:
description: >
  根据用户需求:{question}
  获取需要读取的文件路径
  使用工具读取文件内容
expected_output: >
  返回文件内容
agent: file_reader
​
translation_task:
description: >
  根据file_reader获取的文件内容,将文本翻译成英文
expected_output: >
  返回翻译后的文本内容
agent: translation_agent
​
file_save_task:
description: >
  根据用户需求:{question}提取出需要保存到的文件路径及相关信息
  将translation_agent的翻译内容,保存至指定文件
expected_output: >
  返回保存结果
agent: file_saver

设置了三个任务,分别是file_read_task、translation_task与file_save_task。

完成这些任务,需要代理能够使用读取文件与保存文件的工具。

在tools目录下可以写工具代码:

image-20250219165027457

file_read_tool工具代码:

from typing import Any, Optional, Type
​
from crewai.tools import BaseTool
from pydantic import BaseModel, Field
​
​
class FileReadToolSchema(BaseModel):
   """Input for FileReadTool."""
​
   # Mandatory file full path to read the file
   # 必须的文件全路径,以读取文件
   file_path: str = Field(..., description="Mandatory file full path to read the file")
​
​
class FileReadTool(BaseTool):
   """A tool for reading file contents.
​
  This tool inherits its schema handling from BaseTool to avoid recursive schema
  definition issues. The args_schema is set to FileReadToolSchema which defines
  the required file_path parameter. The schema should not be overridden in the
  constructor as it would break the inheritance chain and cause infinite loops.
​
  The tool supports two ways of specifying the file path:
  1. At construction time via the file_path parameter
  2. At runtime via the file_path parameter in the tool's input
​
  Args:
      file_path (Optional[str]): Path to the file to be read. If provided,
          this becomes the default file path for the tool.
      **kwargs: Additional keyword arguments passed to BaseTool.
​
  Example:
      >>> tool = FileReadTool(file_path="/path/to/file.txt")
      >>> content = tool.run() # Reads /path/to/file.txt
      >>> content = tool.run(file_path="/path/to/other.txt") # Reads other.txt
​
  用于读取文件内容的工具。
​
  该工具继承自 BaseTool 的 schema 处理,以避免递归 schema 定义问题。args_schema 设置为 FileReadToolSchema,定义了必需的 file_path 参数。构造函数中不应该覆盖 schema,否则会破坏继承链并导致无限循环。
​
  该工具支持两种指定文件路径的方法:
​
  在构造时通过 file_path 参数
  在运行时通过工具的输入参数 file_path
​
  参数:
  file_path (可选[str]): 要读取的文件路径。如果提供,则成为工具的默认文件路径。
  **kwargs: 传递给 BaseTool 的其他关键字参数。
​
  示例:
  >>> tool = FileReadTool(file_path="/path/to/file.txt")
  >>> content = tool.run() # 读取 /path/to/file.txt
  >>> content = tool.run(file_path="/path/to/other.txt") # 读取 other.txt
  """
​
   name: str = "Read a file's content"
   description: str = "A tool that reads the content of a file. To use this tool, provide a 'file_path' parameter with the path to the file you want to read."
   args_schema: Type[BaseModel] = FileReadToolSchema
   file_path: Optional[str] = None
​
   def __init__(self, file_path: Optional[str] = None, **kwargs: Any) -> None:
       """
      Initialize the FileReadTool.
​
      Args:
          file_path (Optional[str]): Path to the file to be read. If provided,
              this becomes the default file path for the tool.
          **kwargs: Additional keyword arguments passed to BaseTool.
​
      初始化 FileReadTool。
​
      参数:
      file_path(可选[str]):要读取的文件路径。如果提供,则此路径成为工具的默认文件路径。
      **kwargs:传递给 BaseTool 的其他关键字参数。
      """
​
       if file_path is not None:
           kwargs['description'] = f"A tool that reads file content. The default file is {file_path}, but you can provide a different 'file_path' parameter to read another file."
​
       super().__init__(**kwargs)
       self.file_path = file_path
​
   def _run(
       self,
       **kwargs: Any,
  ) -> str:
       file_path = kwargs.get("file_path", self.file_path)
       if file_path is None:
           return "Error: No file path provided. Please provide a file path either in the constructor or as an argument."
​
       try:
           with open(file_path, "r",encoding='utf-8') as file:
               return file.read()
       except FileNotFoundError:
           return f"Error: File not found at path: {file_path}"
       except PermissionError:
           return f"Error: Permission denied when trying to read file: {file_path}"
       except Exception as e:
           return f"Error: Failed to read file {file_path}. {str(e)}"

file_writer_tool工具代码:

import os
from ast import literal_eval
from typing import Any, Optional, Type
​
from crewai.tools import BaseTool
from pydantic import BaseModel
​
​
class FileWriterToolInput(BaseModel):
   filename: str
   directory: Optional[str] = "./"
   overwrite: str = "False"
   content: str
​
​
class FileWriterTool(BaseTool):
   name: str = "File Writer Tool"
   description: str = "A tool to write content to a specified file. Accepts filename, content, and optionally a directory path and overwrite flag as input,overwrite flag is True or False."
   args_schema: Type[BaseModel] = FileWriterToolInput
​
   def _run(self, **kwargs: Any) -> str:
       try:
           # Create the directory if it doesn't exist
           if kwargs.get("directory") and not os.path.exists(kwargs["directory"]):
               os.makedirs(kwargs["directory"])
​
           # Construct the full path
           filepath = os.path.join(kwargs.get("directory") or "", kwargs["filename"])
​
           # Convert overwrite to boolean
           kwargs["overwrite"] = bool(literal_eval(kwargs["overwrite"]))
​
           # Check if file exists and overwrite is not allowed
           if os.path.exists(filepath) and not kwargs["overwrite"]:
               return f"File {filepath} already exists and overwrite option was not passed."
​
           # Write content to the file
           mode = "w" if kwargs["overwrite"] else "x"
           with open(filepath, mode) as file:
               content = kwargs["content"]
               file.write(content)
           return f"Content successfully written to {filepath}"
       except FileExistsError:
           return (
               f"File {filepath} already exists and overwrite option was not passed."
          )
       except KeyError as e:
           return f"An error occurred while accessing key: {str(e)}"
       except Exception as e:
           return f"An error occurred while writing to the file: {str(e)}"

现在需要构建一个团队。

构建团队的代码:

from crewai import Agent, Crew, Process, Task,LLM
from crewai.project import CrewBase, agent, crew, task
from translation_agent.tools.file_read_tool import FileReadTool
from translation_agent.tools.file_writer_tool import FileWriterTool
import os
from dotenv import load_dotenv
load_dotenv()
​
file_read_tool = FileReadTool()
file_writer_tool = FileWriterTool()
​
api_key = os.getenv('OPENAI_API_KEY')
base_url = os.getenv('OPENAI_API_BASE')
model = os.getenv('OPENAI_MODEL_NAME', 'Qwen/Qwen2.5-72B-Instruct')  # Provide a default model if not set
agent_llm = LLM(
model=model,
   base_url=base_url,
   api_key=api_key
)
​
# If you want to run a snippet of code before or after the crew starts,
# you can use the @before_kickoff and @after_kickoff decorators
# https://docs.crewai.com/concepts/crews#example-crew-class-with-decorators
​
​
@CrewBase
class TranslationAgent():
"""TranslationAgent crew"""
​
# Learn more about YAML configuration files here:
# Agents: https://docs.crewai.com/concepts/agents#yaml-configuration-recommended
# Tasks: https://docs.crewai.com/concepts/tasks#yaml-configuration-recommended
agents_config = 'config/agents.yaml'
tasks_config = 'config/tasks.yaml'

# If you would like to add tools to your agents, you can learn more about it here:
# https://docs.crewai.com/concepts/agents#agent-tools
# @agent
# def researcher(self) -> Agent:
# return Agent(
# config=self.agents_config['researcher'],
# verbose=True
# )
​
# @agent
# def reporting_analyst(self) -> Agent:
# return Agent(
# config=self.agents_config['reporting_analyst'],
# verbose=True
# )
   
@agent
def file_reader(self) -> Agent:
return Agent(
config=self.agents_config['file_reader'],
verbose=True,
llm=agent_llm,
tools=[file_read_tool],
)

@agent
def translation_agent(self) -> Agent:
return Agent(
config=self.agents_config['translation_agent'],
verbose=True,
llm=agent_llm,
)

@agent
def file_saver(self) -> Agent:
return Agent(
config=self.agents_config['file_saver'],
verbose=True,
llm=agent_llm,
tools=[file_writer_tool],
)

# To learn more about structured task outputs,
# task dependencies, and task callbacks, check out the documentation:
# https://docs.crewai.com/concepts/tasks#overview-of-a-task
@task
def file_read_task(self) -> Task:
return Task(
config=self.tasks_config['file_read_task'],
)
​
@task
def translation_task(self) -> Task:
return Task(
config=self.tasks_config['translation_task'],
)

@task
def file_save_task(self) -> Task:
return Task(
config=self.tasks_config['file_save_task'],
)
​
@crew
def crew(self) -> Crew:
"""Creates the TranslationAgent crew"""
# To learn how to add knowledge sources to your crew, check out the documentation:
# https://docs.crewai.com/concepts/knowledge#what-is-knowledge
​
return Crew(
agents=self.agents, # Automatically created by the @agent decorator
tasks=self.tasks, # Automatically created by the @task decorator
process=Process.sequential,
verbose=True,
# process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/
)

其中我想让代理使用硅基流动的模型可以这样写:

image-20250219165445665

需要在模型名称前加上openai才行,不如会报错。

如果你还没注册的话,可以点击邀请链接进行注册:https://cloud.siliconflow.cn/i/Ia3zOSCU。

这里我以具有工具调用能力的meta-llama/Llama-3.3-70B-Instruct为例。

然后可以这样使用:

import os
from dotenv import load_dotenv
load_dotenv()
​
file_read_tool = FileReadTool()
file_writer_tool = FileWriterTool()
​
api_key = os.getenv('OPENAI_API_KEY')
base_url = os.getenv('OPENAI_API_BASE')
model = os.getenv('OPENAI_MODEL_NAME', 'Qwen/Qwen2.5-72B-Instruct')  # Provide a default model if not set
agent_llm = LLM(
model=model,
   base_url=base_url,
   api_key=api_key
)

在创建代理时,记得使用这个大模型,并且记得使用工具:

@agent
def file_reader(self) -> Agent:
return Agent(
config=self.agents_config['file_reader'],
verbose=True,
llm=agent_llm,
tools=[file_read_tool],
)

这样这个团队就构建成功了。

在main.py中这样写:

#!/usr/bin/env python
import sys
import warnings
​
from datetime import datetime
​
from translation_agent.crew import TranslationAgent
​
warnings.filterwarnings("ignore", category=SyntaxWarning, module="pysbd")
​
# This main file is intended to be a way for you to run your
# crew locally, so refrain from adding unnecessary logic into this file.
# Replace with inputs you want to test with, it will automatically
# interpolate any tasks and agents information
​
def run():
   """
  Run the crew.
  """
   inputs = {
       'question': '读取test.txt文件内容,将其翻译为英文,然后写入test4.txt文件',
  }
   
   try:
       TranslationAgent().crew().kickoff(inputs=inputs)
   except Exception as e:
       raise Exception(f"An error occurred while running the crew: {e}")
​
​
def train():
   """
  Train the crew for a given number of iterations.
  """
   inputs = {
       "topic": "AI LLMs"
  }
   try:
       TranslationAgent().crew().train(n_iterations=int(sys.argv[1]), filename=sys.argv[2], inputs=inputs)
​
   except Exception as e:
       raise Exception(f"An error occurred while training the crew: {e}")
​
def replay():
   """
  Replay the crew execution from a specific task.
  """
   try:
       TranslationAgent().crew().replay(task_id=sys.argv[1])
​
   except Exception as e:
       raise Exception(f"An error occurred while replaying the crew: {e}")
​
def test():
   """
  Test the crew execution and returns the results.
  """
   inputs = {
       "topic": "AI LLMs"
  }
   try:
       TranslationAgent().crew().test(n_iterations=int(sys.argv[1]), openai_model_name=sys.argv[2], inputs=inputs)
​
   except Exception as e:
       raise Exception(f"An error occurred while testing the crew: {e}")

主要关注这里:

def run():
   """
  Run the crew.
  """
   inputs = {
       'question': '读取test.txt文件内容,将其翻译为英文,然后写入test4.txt文件',
  }
   
   try:
       TranslationAgent().crew().kickoff(inputs=inputs)
   except Exception as e:
       raise Exception(f"An error occurred while running the crew: {e}")

在inputs中输入task中的question占位符的内容。

现在创建一个test.txt,输入内容为:

CrewAI:用于编排复杂 AI 代理系统的生产级框架。从简单的自动化到复杂的现实世界应用,CrewAI 提供精确控制和深度定制。通过灵活的、可投入生产的架构促进协作智能,CrewAI 使代理能够无缝协作,以可预测和一致的结果解决复杂的商业挑战。

现在输入crewai run,看看这个翻译代理的效果。

image-20250219171523468

可以发现读取文件代理做的不够好的地方是多了一些内容。

需要进行改进。

改成这样再试试:

file_reader:
role: >
  读取文件代理
goal: >
  根据文件路径,读取文件内容
backstory: >
  你是一个文件读取代理,你的任务是根据文件路径,读取文件内容,只需返回文件内容即可

现在效果就很好了,如下所示:

image-20250219172033798

翻译代理很好地进行翻译了,如下所示:

image-20250219172055009

文件保存代理将翻译结果进行保存,如下所示:

image-20250219172229853

image-20250219172317889

最后

这就是使用crewai构建一个翻译代理的步骤与效果。在crewai中还有很多很有趣的工具值得探索,下期介绍代码解释器工具的使用。

posted @ 2025-02-19 18:11  mingupupup  阅读(2599)  评论(0)    收藏  举报