使用crewai创建属于你自己的AI团队 - mingupupup - 博客园

来源: 使用crewai创建属于你自己的AI团队 – mingupupup – 博客园

crewai介绍

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

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

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

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

在crewai中涵盖了AgentsTasksCrewsFlowsKnowledgeLLMsTools等这些核心概念。

接下来我将以一个具体的例子,介绍一下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中还有很多很有趣的工具值得探索,下期介绍代码解释器工具的使用。

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏

登录

注册