通过crewai Agent来对Arduino编程

虽然我并不认为自己是 Arduino 编程专家,但我非常喜欢在业余时间制作电子项目。所以前几天我突然冒出一个想法:我懂一点人工智能,也懂一点 Arduino,那么让它们一起工作怎么样?
在过去几周尝试了 CrewAI之后,我想出了以下实验:将 CrewAI 与 Arduino 连接起来。此外,我认为使用一些本地的 LLM 会很有趣,比如 Ollama提供的那些。
但是,在我们开始编写代码之前,你可能不熟悉 Arduino。那么,让我们从最基础的部分开始吧。

什么是 Arduino?

Arduino 是一款小型的可编程计算机,能让你创建自己的电子项目,从闪烁 LED 的基本电路到能够移动的先进机器人,无所不包。简单来说,在使用 Arduino 平台时,你需要了解有两个主要“部分”需要协同工作。

Arduino电路板

Arduino 电路板是物理硬件,其主要组件是微控制器芯片。该芯片负责运行您通过代码提供的指令。

你使用 Arduino 编程语言(基于 C/C++)编写代码,这些代码被称为“sketches”(草图)。这些草图会指示 Arduino 执行何种操作,例如点亮一盏灯、读取传感器数据或控制一个伺服电机。

例如,下面这个草图的作用就是让一个 LED 灯亮一秒,然后灭一秒,并如此循环。

void setup() {
  	pinMode(11, OUTPUT);
}

void loop() {
    digitalWrite(11, HIGH);   // 打开LED灯 (HIGH表示高电压)
    delay(1000);                       // 等待1秒
    digitalWrite(11, LOW);    //通过写入LOW拉低电压关闭LED灯
    delay(1000);                       // 等待1秒
} 

我不会深入探讨 sketch 编程的细节,因为这超出了本教程的范围。但是,如果你对这个迷人的世界感兴趣,我建议你阅读 Arduino 教程。

构建一个简单的电路

为了探索大型语言模型(LLM)与 Arduino 之间的联系,我选择了一个简单的项目:让三个 LED 灯循环闪烁。你可以在上图中看到电路的设置。

现在电路已经搭建好了,合乎逻辑的下一步将是手动编写一个 sketch 来控制这些 LED 灯。然而,这正是我们想要利用 LLM 来自动化的事情!

所以,我们将让一个 CrewAI 代理来处理编程工作,而不是自己编写代码。同样,我们也会让另一个 CrewAI 代理来负责为我们编译代码并将其上传到 Arduino,而不是手动执行这些操作

实现一个Crewai的Agents

让我们把重点放在上方的图表上,它概述了我们即将构建的 CrewAI 应用程序。

如你所见,这个设置很简单,由两个代理 组成:Sketch 编程代理和 Arduino 上传代理。第一个代理将接收电路的描述及其预期行为,然后生成一个扩展名为 .ino 的 sketch 文件。第二个代理将编译生成的 sketch 并将代码上传到 Arduino。

让我们来详细研究一下每个代理。

Sketch 编程代理

在本文前面,我提到过我们将使用本地 LLM。为此,我们将使用 Ollama。

如果你不熟悉 Ollama,Matthew Berman 有一个关于它的非常好的视频。

要将 Ollama 与 CrewAI 集成,只需在定义代理之前添加以下几行代码即可。

llama3 = ChatOpenAI(
    model="llama3",
    base_url="http://localhost:11434/v1"
)

请注意,我正在使用 Llama 3 模型。如果你也想这样做,请确保它已经在 Ollama 中下载。如果还没有,你可以通过运行这个简单的命令来下载它。

ollama pull llama3

现在,是时候进行代理的实现了。

sketch_programmer_agent = Agent(
    role="Sketch Programmer",
    goal=dedent(
        """为 Arduino 编写一个 Sketch 脚本,该脚本可以点亮数字引脚 11 上的红色 LED,数字引脚 10 上的蓝色 LED 和数字引脚 9 上的绿色 LED,三个 LED 之间的间隔时间为 1 秒。"""),
    backstory=dedent(
        """你是一位经验丰富的 Sketch 程序员,非常喜欢对 Arduino 进行编程。        """
    ),
    verbose=True,
    allow_delegation=False,
    llm=llama3,
)

该Agent的目标包含了我们已经实现的电路信息,以及其预期的行为。此外,你会注意到该代理正在使用 llama3 作为其大型语言模型(LLM)。

sketch_programming_task = Task(
    description=dedent(
        "编写一个可以立即上传到 Arduino 的 Sketch 脚本。 只要 Arduino 代码,别的什么都不要。"),
    expected_output=dedent(
        "一段可直接复制粘贴到 Arduino CLI 的纯 Sketch 脚本"),
    agent=sketch_programmer_agent,
    output_file="./tmp/tmp.ino",
)

请注意 output_file 属性。这一点至关重要,因为 Sketch 编程代理会将生成的代码保存到 ./tmp/tmp.ino 文件中。

Arduino 上传Agent

既然前一个代理已经生成了 sketch 文件,Arduino 上传代理就需要编译它并将其上传到 Arduino。我们如何实现这一点呢?

解决方案是使用一个自定义工具。

您可以按照这篇 CrewAI 文档 学习如何实现您自己的自定义工具。

import re
import subprocess
from crewai_tools import BaseTool

class CompileAndUploadToArduinoTool(BaseTool):
    name: str = "编译和上传至arduino工具"
    description: str = """编译和上传一个 Arduino Sketch Script 
                         给arduino"""
    ino_file_dir: str = "包含 ino 文件的目录"
    board_fqbn: str = "电路板类型,例如 'arduino:avr:uno'"port: str = "Arduino 连接的端口"

    def __init__(self, ino_file_dir: str, board_fqbn: str, port: str, **kwargs):
        super().__init__(**kwargs)
        self.ino_file_dir = ino_file_dir
        self.board_fqbn = board_fqbn
        self.port = port

    def _fix_ino_file(self):
        """
        这是一个辅助方法,用于修复输出的 .ino 文件,以防 Llama3 添加一些意想不到    的文本导致编译无效

        """
        with open(f"{self.ino_file_dir}/tmp.ino", "r") as f:
            content = f.read()

        pattern = r'```.*?\n(.*?)```'
        match = re.search(pattern, content, re.DOTALL).group(1).strip()

        with open(f"{self.ino_file_dir}/tmp.ino", "w") as f:
            f.write(match)

    def _run(self):
        self._fix_ino_file()

        try:
            subprocess.check_call([
                "arduino-cli", "compile", "--fqbn", 
                self.board_fqbn, self.ino_file_dir
            ])
            subprocess.check_call([
                "arduino-cli", "upload", "--port", 
                self.port, "--fqbn", self.board_fqbn, self.ino_file_dir
            ])
        except subprocess.CalledProcessError:
            return "编译失败"

        return "代码已经成功上传给Board"

该工具需要三个参数:

  • ino_file_dir:包含 sketch 的目录。请记住,我们使用的是 tmp 目录。
  • board_fqbn:电路板类型。我使用的是 Arduino UNO,所以我的电路板类型是 arduino:avr:uno,但你的情况可能会有所不同。
  • port:Arduino 连接的端口。我的连接在 /dev/cu.usbmodem1201 端口。

当代理使用该工具时(通过访问 _run 方法),它会编译代码,然后将其上传到 Arduino。您可以在下面查看代理的实现——在这种情况下,我使用了 GPT-4,因为它在使用工具时效果要好得多:

tool = CompileAndUploadToArduinoTool(
    ino_file_dir="./tmp",
    board_fqbn="arduino:avr:uno",
    port="/dev/cu.usbmodem1201"
)

arduino_uploader_agent = Agent(
    role="Arduino 上传 Agent",
    goal="""你的目标是使用工具编译和上传接收到的Arduino脚本l""",
    backstory=dedent(
        """
        You are a hardware geek.
        """
    ),
    verbose=True,
    allow_delegation=False,
    tools=[tool]
)

arduino_uploading_task = Task(
    description=dedent(
        "将草图(Sketch)编译并上传到Arduino"),
    expected_output=dedent(
        """仅将代码编译并上传到Arduino中"""),
    agent=arduino_uploader_agent,
)

最终实现

虽然花了一些时间,但我们现在已经组装好了构建 CrewAI 应用程序所需的所有组件。是时候创建“船员”(Crew)了。

from crewai import Crew
from dotenv import load_dotenv
from agents import sketch_programmer_agent, arduino_uploader_agent
from tasks import sketch_programming_task, arduino_uploading_task

load_dotenv()

crew = Crew(
            agents=[sketch_programmer_agent, arduino_uploader_agent],
            tasks=[sketch_programming_task, arduino_uploading_task],
        )

result = crew.kickoff()

print(result)
原文链接:,转发请注明来源!