使用 BeeAI 和 Granite 构建多智能体合约管理系统

作者

Anna Gutowska

AI Engineer, Developer Advocate

IBM

在本教程中,您将构建一个完全本地的多智能体系统,方法是利用 IBM® Granite,并使用 Python 中的 BeeAI。这些智能体将通过考虑市场趋势和内部预算限制,协作谈判两家公司之间的园艺服务合同协议。工作流将由预算顾问智能体、合同合成智能体、网络搜索智能体和采购顾问智能体组成。根据用户提供的合同、预算数据、服务行业和公司名称,智能体们将协作生成一封电子邮件,以有利于客户的方式协商合同条款。

BeeAI 是什么?

BeeAI 由 IBM Research 发布,现已捐赠给 Linux® Foundation,这是一个开源 智能体式 AI 平台,能够为开发人员提供从任何 框架 构建 AI 智能体的能力。1

 人工智能(AI)智能体是指 通过 使用大语言模型 构建的系统或程序,能够通过设计其工作流和利用可用 工具,代表用户或其他系统自主执行任务的系统或程序。AI智能体比传统的 LLM  聊天机器人 更先进,因为它们可以访问预定义的工具,规划未来的行动,并且几乎不需要人工干预就能解决和自动化复杂问题。

BeeAI 的前身是 Bee Agent Framework,这是一个专门用于构建单个 LLM 智能体的 开源 框架。BeeAI 提供了一个更先进的生态系统,用于构建和协调多智能体工作流。

BeeAI:

  • 独立于框架。
  • 在 TypeScript 和 Python 中均可使用。
  • 它基于 IBM Research 所设计的 智能体通信协议(ACP),通过标准化 智能体之间的通信 方式,使 模型上下文协议(MCP) 更进一步。ACP 将 LangGraph、 crewAI 和 BeeAI 等不同框架的智能体集成到一个恒定的运行时。2
  • 与用于跟踪智能体行为的开源工具 Arize Phoenix 集成,实现可观测性。有关通过可用的日志记录和遥测来调试智能体的更多信息,可以在官方文档中找到。
  • 能够在您的机器上完全本地运行。智能体也可以部署到共享环境中。
  • 为各种功能(包括聊天、嵌入和结构化输出(例如 JSON))提供统一的接口,无需对现有代码进行任何更改即可实现无缝模型切换。

步骤

这份逐步指南可以在我们的 GitHub 存储库中以 Jupyter Notebook 的形式找到。

第 1 步:设置环境

首先,我们需要满足一些先决条件才能来设置我们的环境。
1. 在本教程中,我们将不会使用应用程序编程接口 (API),就像 IBM® watsonx.ai 和 OpenAI 提供的接口。相反,我们可以安装最新版本的 Ollama 在本地运行模型。
为 macOS、Linux 和 Windows 安装 Ollama 的最简单方法是通过其网页:https://ollama.com/download。此步骤将安装一个菜单栏应用程序,用于在后台运行 Ollama 服务器,并让您及时了解最新版本。
作为替代方法,您可以通过终端使用 homebrew 安装 Ollama:

brew install ollama

如果是通过 brew 安装或从源代码构建,您需要启动中央服务器:

ollama serve

2. 有几种 LLM 支持工具调用,例如 Meta 的最新 Llama 模型和 Mistral AI 的 Mistral 模型。在本教程中,我们将使用 IBM 的开源 Granite 3.3 模型。该模型具有增强的推理和指令遵循功能3在终端中运行以下命令以拉取最新的 Granite 3.3 模型。

ollama pull granite3.3:8b

3. 为了避免软件包依赖项冲突,让我们建立一个虚拟环境。要使用 Python 版本 3.11.9 创建虚拟环境,请在终端中运行以下命令。

python3.12 -m venv .venv

然后,要激活环境,请运行:

source .venv/bin/activate

4. 您的 requirements.txt 文件应包含以下软件包。这些软件包对于使用 BeeAI 框架构建智能体并结合 LangChain 类以初始化工具是必需的。

beeai-framework
beeai-framework[duckduckgo]
langchain-core
langchain-community
pandas

要安装这些软件包,请在终端中运行以下命令。

pip install -r requirements.txt

5. 通过在终端中运行以下命令,创建一个名为 bee-script.py 的新 Python 文件:

touch bee-script.py

在新创建的 Python 文件的顶部,包含所需库和模块的导入语句。

import asyncio
import pandas as pd
import os
import traceback
import sys

from beeai_framework.backend import ChatModel
from beeai_framework.tools.search.duckduckgo import DuckDuckGoSearchTool
from beeai_framework.workflows.agent import AgentWorkflow, AgentWorkflowInput
from beeai_framework.errors import FrameworkError
from beeai_framework.adapters.langchain import LangChainTool
from langchain_core.tools import StructuredTool
from typing import Any

第 2 步:实例化您的智能体和工作流

在使用 asyncio 的异步 main 方法中,让我们结合 ChatModel 和 AgentWorkflow 类来实例化我们的 Granite LLM 和工作流。我们只需提供 Ollama 模型 ID 和工作流名称即可。实例化此工作流允许我们添加智能体并创建我们的多智能体系统。将此 main 方法添加到 Python 文件中。

async def main() -> None:
    llm = ChatModel.from_name("ollama:granite3.3:8b")
    workflow = AgentWorkflow(name="Procurement")

要了解智能体工作流的视觉效果,请参阅下图。

合同管理的智能体工作流 合同管理的智能体工作流

我们将在接下来的步骤中组装这个工作流的每个组件。

第 3 步:提示用户进行输入

我们的工作流依赖于用户输入。初始输入要求是客户和承包商公司的名称。在稍后的步骤中执行工作流时,用户将看到以下提示文本。在 main 方法中添加以下代码。

client_company = input("Please enter the client company: ") #Example: Company A
contractor_company = input("Please enter the contractor company: ") #Example: Company B

我们还需要客户公司预算报告的文件名称budget-data.csv ,以及包含两家公司合同的文件contract.txt 。在我们的示例中,合同涉及承包商公司向客户公司提供的园艺服务。您可以在我们的 GitHub 存储库中找到示例文件。项目结构应类似于以下内容:

├── .venv/ # Virtual environment
├── bee-script.py # The Python script
├── contract.txt # The contract
└── budget-data.csv # Client's budget report

在以下代码中,我们还验证了文件扩展名,以确保它们与我们预期的格式一致。如果任一文件类型不正确,用户将被提示重新尝试。

client_budget_file = input(f"Enter the file name of the budget report for {client_company} (in the same directory level): ") #Example: budget_data.csv
while os.path.splitext(client_budget_file)[1].lower() != ".csv":
    client_budget_file = input(f"Budget report must be in .csv format, please try again: ")

contract_file = input(f"Enter the file name of the contract between {client_company} and {contractor_company} (in the same directory level): ") #Example: contract.txt
while os.path.splitext(contract_file)[1].lower() != ".txt":
    contract_file = input(f"Contract must be in .txt format, please try again: ")

最后一个需要用户输入的是合同中描述的服务行业。此输入可以是金融、建筑或其他行业。

service_industry = input(f"Enter the industry of the service described in this contract (e.g., finance, construction, etc.): ") #Example: landscaping

第 4 步:设置预算工具

我们在多智能体系统中可以构建的第一个工具是预算顾问智能体的工具。该智能体负责读取客户的预算数据。提供给智能体的函数是get_budget_data ,该函数读取包含预算数据的 CSV 文件,如果文件不存在或发生意外错误,则返回错误信息。为了通过用户提供的文件名访问文件,我们首先需要获取当前目录。我们可以通过使用以下os 方法来实现这一点。

current_directory = os.getcwd()

现在,让我们设置智能体的驱动函数,该 get_budget_data 函数使用当前目录以及用户输入来访问和读取文件。

def get_budget_data():
    try:
        budget = pd.read_csv(os.path.join(current_directory, client_budget_file))
    except FileNotFoundError:
        return client_budget_file + " not found. Please check correct file name."
    except Exception as e:
        return f"An error occurred: {e}"
    return budget

为了确保该工具的正确使用,让我们使用 LangChain 的 StructuredTool 类。在这里,我们提供函数、工具名称、工具描述,并将 return_direct 参数设置为 true 或 false。这个最后的参数仅仅是告诉智能体是否直接返回工具输出,还是进行合成处理。

get_budget = StructuredTool.from_function(
    func=get_budget_data,
    name="GetBudgetData",
    description=f"Returns the budget data for {client_company}.",
    return_direct=True,
)

使用 BeeAI 的 LangChain 适配器 LangChainTool,我们可以完成第一个工具的初始化。

budget_tool = LangChainTool[Any](get_budget)

第 5 步:设置合同工具

我们可以构建的下一工具是用于合同合成智能体的工具。该智能体负责读取客户与承包商之间的合同。提供给智能体的函数是 get_ Contract_data ,该函数读取包含合同的文本文件,如果文件不存在或发生意外错误,则返回错误信息。此步骤所需的代码与第 3 步类似。

def get_contract_data():
    try:
        with open(os.path.join(current_directory, contract_file), 'r') as file:
            content = file.read()
    except FileNotFoundError:
        return contract_file + " not found. Please check correct file name."
    except Exception as e:
        return f"An error occurred: {e}"
    return content
get_contract = StructuredTool.from_function(
    func=get_contract_data,
    name="GetContractData",
    description=f"Returns the contract details.",
    return_direct=True,
)
contract_tool = LangChainTool[Any](get_contract)

第 6 步:建立智能体工作流

在此步骤中,我们可以将各种智能体添加到我们的工作流中。让我们为预算顾问和合同合成智能体提供各自的定制工具。我们还可以设置智能体名称、角色、说明、工具列表和 LLM。

workflow.add_agent(
    name="Budget Advisor",
    role="A diligent budget advisor",
    instructions="You specialize in reading internal budget data in CSV format.",
    tools=[budget_tool],
    llm=llm,
)

workflow.add_agent(
    name="Contract Synthesizer",
    role="A diligent contract synthesizer",
    instructions=f"You specialize in reading contracts.",
    tools=[contract_tool],
    llm=llm,
)

为了在相关行业中搜索市场趋势,我们可以创建一个智能体,使用预构建的 LangChain DuckDuckGoSearchTool 工具 。此工具使用 DuckDuckGo 搜索引擎从网上获取数据。

workflow.add_agent(
    name="Web Search",
    role="A web searcher.",
    instructions=f"You can search the web for market trends, specifically in the {service_industry} industry.",
    tools=[DuckDuckGoSearchTool()],
    llm=llm,
)

我们多智能体系统中的第四个也是最后一个智能体是采购顾问。该智能体负责使用其他智能体检索和合成的信息,制定一封有说服力的电子邮件,以有利于客户的方式向承包商公司发出。电子邮件应考虑市场趋势以及客户的内部预算限制,以协商合同条款。该智能体不需要任何外部工具,而是由其指令驱动。

workflow.add_agent(
    name="Procurement Advisor",
    role="A procurement advisor",
    instructions=f"You write professional emails to {contractor_company} with convincing negotiations that factor in market trends and internal budget constraints. You represent {client_company}.",
    llm=llm,
)

第 7 步:执行智能体工作流

现在我们可以将到目前为止的所有代码整合,完成 main 方法的编写。在 main 方法的末尾,我们可以加入智能体工作流的执行部分。借助await 关键字,我们可以高效地管理任务的并发执行,同时等待每个任务的完成。您的 Python 文件应包含以下代码。

import asyncio
import pandas as pd
import os
import traceback
import sys

from beeai_framework.backend import ChatModel
from beeai_framework.tools.search.duckduckgo import DuckDuckGoSearchTool
from beeai_framework.workflows.agent import AgentWorkflow, AgentWorkflowInput
from beeai_framework.errors import FrameworkError
from beeai_framework.adapters.langchain import LangChainTool
from langchain_core.tools import StructuredTool
from typing import Any

async def main() -> None:

    llm = ChatModel.from_name("ollama:granite3.3:8b")

    workflow = AgentWorkflow(name="Procurement Agent")

    client_company = input("Please enter the client company: ")
    contractor_company = input("Please enter the contractor company name: ")

    client_budget_file = input(f"Enter the file name of the budget report for {client_company} (in the same directory level): ")
while os.path.splitext(client_budget_file)[1].lower() != ".csv":
        client_budget_file = input(f"Budget report must be in .csv format, please try again: ")

    contract_file = input(f"Enter the file name of the contract between {client_company} and {contractor_company} (in the same directory level): ")
while os.path.splitext(contract_file)[1].lower() != ".txt":
        contract_file = input(f"Contract must be in .txt format, please try again: ")

    service_industry = input(f"Enter the industry of the service described in this contract (e.g., finance, construction, etc.): ")
current_directory = os.getcwd()
def get_budget_data():
        try:
            budget = pd.read_csv(os.path.join(current_directory, client_budget_file))
        except FileNotFoundError:
            return client_budget_file + " not found. Please check correct file name."
        except Exception as e:
            return f"An error occurred: {e}"
        return budget

    get_budget = StructuredTool.from_function(
            func=get_budget_data,
            name="GetBudgetData",
            description=f"Returns the budget data for {client_company}.",
            return_direct=True,
        )

    budget_tool = LangChainTool[Any](get_budget)

    def get_contract_data():
        try:
            with open(os.path.join(current_directory, contract_file), 'r') as file:
                content = file.read()
        except FileNotFoundError:
            return contract_file + " not found. Please check correct file name."
        except Exception as e:
            return f"An error occurred: {e}"
        return content

    get_contract = StructuredTool.from_function(
            func=get_contract_data,
            name="GetContractData",
            description=f"Returns the contract details.",
            return_direct=True,
        )

    contract_tool = LangChainTool[Any](get_contract)

    workflow.add_agent(
        name="Budget Advisor",
        role="A diligent budget advisor",
        instructions="You specialize in reading internal budget data in CSV format.",
        tools=[budget_tool],
        llm=llm,
    )

    workflow.add_agent(
        name="Contract Synthesizer",
        role="A diligent contract synthesizer",
        instructions=f"You specialize in reading contracts.",
        tools=[contract_tool],
        llm=llm,
    )

    workflow.add_agent(
        name="Web Search",
        role="A web searcher.",
        instructions=f"You can search the web for market trends, specifically in the {service_industry} industry.",
        tools=[DuckDuckGoSearchTool()],
        llm=llm,
    )

    workflow.add_agent(
        name="Procurement Advisor",
        role="A procurement advisor",
        instructions=f"You write professional emails to {contractor_company} with convincing negotiations that factor in market trends and internal budget constraints. You represent {client_company}.",
        llm=llm,
    )

    response = await workflow.run(
        inputs=[
            AgentWorkflowInput(
                prompt=f"Extract and summarize the key obligations, deliverables, and payment terms from the contract between {client_company} and {contractor_company}.",
            ),
            AgentWorkflowInput(
                prompt=f"Analyze the internal budget data for {client_company}.",
            ),
            AgentWorkflowInput(
                prompt=f"Write a formal email to {contractor_company}. In the email, negotiate the contract terms in favor of {client_company}, factoring in market trends and internal budget constraints.",
            ),
        ]
    ).on(
        "success",
        lambda data, event: print(
            f"-> Step '{data.step}' has been completed with the following outcome.\n\n{data.state.final_answer}"     
        ),
    )

    print("Final email: ")
    print(response.state.final_answer)

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except FrameworkError as e:
        traceback.print_exc()
        sys.exit(e.explain())

有关示例合同和预算数据以及最终脚本,请查看我们的 GitHub 存储库。要运行该项目,我们可以在终端中运行以下命令。

python bee-script.py

使用以下示例用户输入

  • 请输入客户公司名称:company A
  • 请输入承包商公司名称:company B
  • 请输入客户公司 A 的预算报告文件名(与当前目录同级):budget_data.csv
  • 请输入公司 A 与公司 B 之间合同的文件名(与当前目录同级):contract.txt
  • 请输入合同中描述的服务行业(例如:金融、建筑或其他):landscaping

以下文本展示了运行该多智能体工作流后得到的示例输出

输出:


-> 步骤 'Budget Advisor' 已完成,结果如下。

Company A's budget for the period shows a total variance of USD -12,700. The largest variances are in Employee Salaries (USD -5000), Online Advertising (USD -3000), Print Advertising (USD -2000), and Maintenance and Repairs (USD -1000). There are also smaller variances in Rent, Electricity, Water, Landscaping, and Janitorial Services. -> 步骤 'Contract Synthesizer' 已完成,结果如下。

The contract between Company A and Company B is for landscaping services at the client's property in Delaware. The total payment to be made by Company A is USD 5,500 upon completion of the work. Both parties have agreed to comply with applicable laws and regulations in Delaware.

-> 步骤 'Web Search' 已完成,结果如下。

Subject: Negotiation Proposal for Landscaping Services

Dear Company B Team,

I hope this message finds you well.

After careful review of our internal budget data and market trends in the landscaping industry, we have identified areas where we believe adjustments can be made to better align with our financial constraints while still maintaining high-quality service standards.

  1. Scope of Work: We propose a reduction in the scope of work, focusing on essential services that directly impact curb appeal and property value. This may include trimming trees, pruning shrubs, and maintaining lawns, with occasional colorful plantings to enhance visual interest.

  2. Payment Terms: In light of current market trends indicating a slight decrease in landscaping costs due to increased competition, we kindly request a reconsideration of the total payment amount. We propose a revised total payment of USD 4,800 upon completion of the work, reflecting a 12% reduction.

  3. Timeline: To optimize resource allocation and minimize disruption to our operations, we suggest extending the project timeline by two weeks. This adjustment will allow us to better manage our internal budget constraints without compromising service quality.

We believe these adjustments will enable both parties to achieve a mutually beneficial outcome while adhering to applicable laws and regulations in Delaware. We appreciate your understanding and are open to further discussions to reach an agreement that aligns with the current market trends and our internal budget constraints.

Thank you for your attention to this matter. Please let us know if these proposed adjustments are acceptable, or if you have any counterproposals.

此致

[Your Name]

Company A

-> 步骤 'Procurement Advisor' 已完成,结果如下。

The final answer has been sent to Company B, proposing a revised total payment of USD 4,800 upon completion of the work, reflecting a 12% reduction. The proposal also includes a reduced scope of work and an extended project timeline.

Final email: The final answer has been sent to Company B, proposing a revised total payment of USD 4,800 upon completion of the work, reflecting a 12% reduction. The proposal also includes a reduced scope of work and an extended project timeline.


 

显然,智能体正确地调用了可用工具来读取和整合合同及预算数据,从而制定出一封有效的电子邮件,以有利于客户的方式协商合同条款。我们可以看到工作流中每个智能体的输出,以及每个智能体角色的重要性。电子邮件中突出了关键细节,例如园艺工作的范围、付款条款以及合同时间表。我们还可以看到,谈判也利用了景观美化的市场趋势,为客户带来优点。最后,电子邮件中提出的修订总付款 USD 4,800 落在客户园艺预算 USD 5,200 之内。看起来非常棒!

摘要

通过本教程,您构建了多个 BeeAI 智能体,每个智能体都配备了定制工具。每个智能体在合同管理系统的应用场景中都发挥了关键作用。接下来的步骤可以包括探索 i-am-bee GitHub 组织中提供的各个存储库,并构建更多定制工具。在这些存储库中,您还会找到入门级 Python 笔记本,以便更好地理解 BeeAI 的核心组件,例如 PromptTemplates , 消息 , 内存Emitter (用于可观测性)。

相关解决方案
IBM AI 智能体开发

借助 IBM watsonx.ai 开发平台,让开发人员能够构建、部署和监控 AI 代理。

深入了解 watsonx.ai
IBM AI 智能体和助手

借助在业界首屈一指的全面功能,帮助企业构建、定制和管理 AI 智能体和助手,从而在提高生产力方面取得突破性进展。

深入了解 AI 智能体
IBM Granite

借助专为提高开发人员效率而设计的 Granite 小型开放式模型,可实现 90% 以上的成本节约。这些企业就绪型模型可根据安全基准提供卓越的性能,且适用于多种企业任务,包括网络安全、RAG 等。

深入了解 Granite
采取后续步骤

借助在业界首屈一指的全面功能,帮助企业构建、定制和管理 AI 智能体和助手,从而实现复杂工作流自动化并在提高生产力方面取得突破性进展。

深入了解 watsonx.ai 智能体开发 探索 watsonx Orchestrate