【指南】多AI代理系统管理

2024年07月05日 由 alex 发表 334 0

最初,当 ChatGPT 刚刚出现时,我们使用简单的提示来获取问题的答案。后来,我们遇到了幻觉问题,于是开始使用 RAG(检索增强生成)为 LLM 提供更多上下文。之后,我们开始尝试使用人工智能代理,让 LLM充当推理引擎,决定下一步该做什么、使用哪些工具以及何时返回最终答案。


下一个进化步骤是创建可以相互协作的人工智能代理团队。这种方法符合逻辑,因为它反映了人类的互动。我们以团队形式工作,每个成员都有特定的角色:


  • 产品经理提出下一个项目。
  • 设计师设计项目的外观和感觉。
  • 软件工程师开发解决方案。
  • 分析师检查数据,确保其性能符合预期,并为客户找出改进产品的方法。


同样,我们可以创建一个人工智能代理团队,每个代理专注于一个领域。他们可以相互协作,共同得出最终结论。正如专业化能提高现实生活中的绩效一样,专业化也能提高人工智能代理的绩效。


这种方法的另一个优点是灵活性更强。每个代理都可以使用自己的提示、工具集甚至 LLM 进行操作。例如,我们可以为系统的不同部分使用不同的模型。需要更多推理的代理可以使用 GPT-4,而只做简单提取的代理则可以使用 GPT-3.5。我们甚至可以针对小型特定任务对模型进行微调,并将其用于我们的代理团队中。


这种方法的潜在缺点是时间和成本。代理之间的多次交互和知识共享需要调用更多的 LLM,并消耗额外的令牌。这可能导致等待时间延长和费用增加。


在本文中,我将向你介绍如何使用 CrewAI。作为分析师,我们是负责记录各种数据源和解决相关问题的领域专家。我们将探讨如何使用多代理框架自动完成这些任务。


设置环境

让我们从设置环境开始。首先,我们需要安装 CrewAI 主软件包和扩展工具。


pip install crewai
pip install 'crewai[tools]'


CrewAI 的开发主要是为了与 OpenAI API 配合使用,但我也想尝试使用本地模型。你可以在笔记本电脑上运行的最佳模型是 Llama 3(8b 参数)。对于我们的用例来说,这将是最可行的选择。


我们可以使用 Ollama 访问 Llama 模型。安装非常简单。你需要从网站上下载 Ollama,然后完成安装。


现在,你可以通过运行以下命令在 CLI 中测试模型。


ollama run llama3


例如,你可以这样问。


2


让我们创建一个自定义的 Ollama 模型,以便稍后在 CrewAI 中使用。


我们将从 ModelFile(文档)开始。我只指定了基本模型(llama3)、温度和停止序列。不过,你可以添加更多的功能。例如,你可以使用 SYSTEM 关键字确定系统信息。


FROM llama3
# set parameters
PARAMETER temperature 0.5
PARAMETER stop Result


我已将其保存到 Llama3ModelFile 文件中。


让我们创建一个 bash 脚本来加载 Ollama 的基础模型,并创建我们在 ModelFile 中定义的自定义模型。


#!/bin/zsh
# define variables
model_name="llama3"
custom_model_name="crewai-llama3"
# load the base model
ollama pull $model_name
# create the model file
ollama create $custom_model_name -f ./Llama3ModelFile


让我们执行这个文件。


chmod +x ./llama3_setup.sh
./llama3_setup.sh


我们需要初始化以下环境变量,以便在 CrewAI 中使用本地 Llama 模型。


os.environ["OPENAI_API_BASE"]='http://localhost:11434/v1'
os.environ["OPENAI_MODEL_NAME"]='crewai-llama3' 
# custom_model_name from the bash script
os.environ["OPENAI_API_KEY"] = "NA"


我们已经完成了设置。


案例:使用文档

作为分析师,我们经常扮演数据和一些数据相关工具的主题专家的角色。在本例中,我将使用 ClickHouse 数据库。


我创建了一个非常简单的数据模型。我们的 DWH(数据仓库)中只有两个表:ecommerce_db.users 和 ecommerce_db.sessions。正如你可能猜到的那样,第一个表包含我们服务的用户信息。


3


ecommerce_db.sessions 表存储用户会话信息。


4


关于数据源管理,分析师通常要处理的任务包括编写和更新文档以及回答有关这些数据的问题。因此,我们将使用 LLM 为数据库中的表格编写文档,并教它回答有关数据或 ClickHouse 的问题。


但在继续实施之前,我们先来了解一下 CrewAI 框架及其核心概念。


CrewAI 基本概念

多代理框架的基石是代理概念。在 CrewAI 中,代理由角色扮演驱动。角色扮演是一种战术,要求代理采用一种角色,表现得像一个一流的后端工程师或乐于助人的客户支持代理。因此,在创建 CrewAI 代理时,你需要指定每个代理的角色、目标和背景故事,以便 LLM 足够了解如何扮演这一角色。


没有工具(代理可以执行并获得结果的功能),代理的能力就会受到限制。有了 CrewAI,你可以使用其中一个预定义工具(例如,搜索互联网、解析网站或对文档进行 RAG 处理),也可以自己创建一个自定义工具或使用 LangChain 工具。因此,创建一个功能强大的代理非常容易。


让我们从代理转到他们正在做的工作。代理正在执行任务(具体任务)。对于每项任务,我们都需要定义描述、预期产出("完成 "的定义)、可用工具集和指定的代理。我非常喜欢这些框架遵循了管理方面的最佳实践,比如明确定义任务的完成时间。


下一个问题是如何定义任务的执行顺序:先处理哪个任务,哪个任务可以并行运行等。CrewAI 采用流程来协调任务。它提供了几个选项:


  • 顺序式--当任务一个接一个被调用时,这是最直接的方法。
  • 分层式--当有一个管理器(指定为 LLM 模型)创建任务并将任务委托给代理时。


此外,CrewAI 还在研究一种协商一致的流程。在这样的流程中,代理将能够以民主的方式合作决策。


你还可以使用其他杠杆来调整任务的执行过程:

  • 你可以将任务标记为 "异步",这样它们就会并行执行,这样你就能更快地得到答案。
  • 你可以在任务上使用 "人工输入 "标记,这样代理就会在最终完成该任务的输出前征求人工批准。这样就可以在流程中增加一个监督环节。


此外,我们还可以为机组人员设置内存。内存对于代理之间的高效协作至关重要。CrewAI 支持三个级别的记忆:

  • 短期记忆存储与当前执行相关的信息。它可以帮助代理共同完成当前任务。
  • 长期记忆是存储在本地数据库中的有关以前执行情况的数据。这类记忆允许代理从以前的迭代中学习,并随着时间的推移不断改进。
  • 实体记忆可以捕捉和构建有关实体(如角色、城市等)的信息。


现在,你只能为一个船员开启所有类型的记忆,而无需进一步的定制。但是,这并不适用于 Llama 模型。


案例:编写文档

让我们从一项简单的任务开始:为我们的 DWH 编写文档。正如我们之前讨论过的,我们的 DWH 中有两个表,我想用 LLM 为它们创建一个详细的说明。


第一种方法

首先,我们需要考虑团队结构。将其视为一项典型的管理任务。你会雇用谁来做这样的工作?


我会把这项任务分成两部分:从数据库中检索数据和编写文档。因此,我们需要一名数据库专家和一名技术撰稿人。数据库专家需要访问数据库,而撰稿人不需要任何特殊工具。


5


现在,我们有了一个高级计划。让我们来创建特工。


我为每个特工指定了角色、目标和背景故事。我已经尽力为特工提供所有需要的背景信息。


database_specialist_agent = Agent(
  role = "Database specialist",
  goal = "Provide data to answer business questions using SQL",
  backstory = '''You are an expert in SQL, so you can help the team 
  to gather needed data to power their decisions. 
  You are very accurate and take into account all the nuances in data.''',
  allow_delegation = False,
  verbose = True
)
tech_writer_agent = Agent(
  role = "Technical writer",
  goal = '''Write engaging and factually accurate technical documentation 
    for data sources or tools''',
  backstory = ''' 
  You are an expert in both technology and communications, so you can easily explain even sophisticated concepts.
  You base your work on the factual information provided by your colleagues.
  Your texts are concise and can be easily understood by a wide audience. 
  You use professional but rather an informal style in your communication.
  ''',
  allow_delegation = False,
  verbose = True
)


我们将使用一个简单的顺序流程,因此不需要代理相互委托任务。这就是我指定 allow_delegation = False 的原因。


下一步是为代理设置任务。但在此之前,我们需要创建一个自定义工具来连接数据库。


首先,我使用 HTTP API 创建了一个执行 ClickHouse 查询的函数。


CH_HOST = 'http://localhost:8123' # default address 
def get_clickhouse_data(query, host = CH_HOST, connection_timeout = 1500):
  r = requests.post(host, params = {'query': query}, 
    timeout = connection_timeout)
  if r.status_code == 200:
      return r.text
  else: 
      return 'Database returned the following error:\n' + r.text


在使用 LLM 代理时,使工具具有容错性非常重要。例如,如果数据库返回错误(status_code != 200),我的代码不会抛出异常。相反,它会将错误描述返回给 LLM,以便其尝试解决问题。


要创建一个 CrewAI 自定义工具,我们需要从 crewai_tools.BaseTool 派生一个类,实现 _run 方法,然后创建该类的一个实例。


from crewai_tools import BaseTool
class DatabaseQuery(BaseTool):
  name: str = "Database Query"
  description: str = "Returns the result of SQL query execution"
  def _run(self, sql_query: str) -> str:
      # Implementation goes here
      return get_clickhouse_data(sql_query)
database_query_tool = DatabaseQuery()


现在,我们可以为代理设定任务了。再次强调,为 LLM 提供清晰的指令和所有背景信息至关重要。


table_description_task = Task(
  description = '''Provide the comprehensive overview for the data 
  in table {table}, so that it's easy to understand the structure 
  of the data. This task is crucial to put together the documentation 
  for our database''',
  expected_output = '''The comprehensive overview of {table} in the md format. 
  Include 2 sections: columns (list of columns with their types) 
  and examples (the first 30 rows from table).''',
  tools = [database_query_tool],
  agent = database_specialist_agent
)
table_documentation_task = Task(
  description = '''Using provided information about the table, 
  put together the detailed documentation for this table so that 
  people can use it in practice''',
  expected_output = '''Well-written detailed documentation describing 
  the data scheme for the table {table} in markdown format, 
  that gives the table overview in 1-2 sentences then then 
  describes each columm. Structure the columns description 
  as a markdown table with column name, type and description.''',
  tools = [],
  output_file="table_documentation.md",
  agent = tech_writer_agent
)


你可能已经注意到,我在任务描述中使用了 {table} 占位符。在执行任务时,我们将使用 table 作为输入变量,并将此值插入到所有占位符中。


此外,我还为表格文档任务指定了输出文件,以便将最终结果保存到本地。


我们需要的都有了。现在,是时候创建一个机组人员并执行进程,同时指定我们感兴趣的表。让我们试试用户表。


crew = Crew(
  agents = [database_specialist_agent, tech_writer_agent],
  tasks = [table_description_task,  table_documentation_task],
  verbose = 2
)
result = crew.kickoff({'table': 'ecommerce_db.users'})


这是一个激动人心的时刻,我非常期待看到结果。如果执行需要一些时间,请不要担心。代理会进行多次 LLM 调用,所以花几分钟是完全正常的。


我们要求 LLM 以 markdown 格式返回文档。我们可以使用下面的代码在 Jupyter Notebook 中查看格式化后的结果。


from IPython.display import Markdown
Markdown(result)


我们已经有了描述用户表的有效标记符文件。


6


但等等,这是不正确的。让我们看看表格中有哪些数据。


7


文档中列出的列与数据库中的列完全不同。这是 LLM 的幻觉。


我们设置了 verbose = 2,以获取 CrewAI 的详细日志。让我们通读执行日志,找出问题的根本原因。


首先,由于引号的复杂性,数据库专家无法查询数据库。


8


专家没能解决这个问题。最后,CrewAI 终止了该链,输出结果如下:Agent stopped due to iteration limit or time limit


这意味着技术作家没有收到任何有关数据的事实信息。然而,代理继续运行,并产生了完全虚假的结果。这就是我们最终得到错误文档的原因。


修复问题

尽管第一次迭代并不成功,但我们还是学到了很多。我们(至少)有两个方面需要改进:


  • 我们的数据库工具对模型来说太难了,而代理在使用它时也很费劲。我们可以通过去掉查询开头和结尾的引号来提高工具的容忍度。这个解决方案并不理想,因为有效的 SQL 可以以引号结尾,但我们还是试试吧。
  • 我们的技术撰写工具并不是根据数据库专家的输入进行输出的。我们需要调整提示,以强调只提供事实信息的重要性。


因此,让我们尝试解决这些问题。首先,我们要解决工具的问题--我们可以利用strip

消除引号。


CH_HOST = 'http://localhost:8123' # default address 
def get_clickhouse_data(query, host = CH_HOST, connection_timeout = 1500):
  r = requests.post(host, params = {'query': query.strip('"').strip("'")}, 
    timeout = connection_timeout)
  if r.status_code == 200:
    return r.text
  else: 
    return 'Database returned the following error:\n' + r.text


然后,就到了更新提示的时候了。我在代理和任务定义中都加入了强调实事求是重要性的语句。


tech_writer_agent = Agent(
  role = "Technical writer",
  goal = '''Write engaging and factually accurate technical documentation 
  for data sources or tools''',
  backstory = ''' 
  You are an expert in both technology and communications, so you 
  can easily explain even sophisticated concepts.
  Your texts are concise and can be easily understood by wide audience. 
  You use professional but rather informal style in your communication.
  You base your work on the factual information provided by your colleagues. 
  You stick to the facts in the documentation and use ONLY 
  information provided by the colleagues not adding anything.''',
  allow_delegation = False,
  verbose = True
)
table_documentation_task = Task(
  description = '''Using provided information about the table, 
  put together the detailed documentation for this table so that 
  people can use it in practice''',
  expected_output = '''Well-written detailed documentation describing 
  the data scheme for the table {table} in markdown format, 
  that gives the table overview in 1-2 sentences then then 
  describes each columm. Structure the columns description 
  as a markdown table with column name, type and description.
  The documentation is based ONLY on the information provided 
  by the database specialist without any additions.''',
  tools = [],
  output_file = "table_documentation.md",
  agent = tech_writer_agent
)


让我们再次执行任务,看看结果如何。


9


我们取得了更好的结果。我们的数据库专家能够执行查询并查看数据,这对我们来说是一个重大胜利。此外,我们还能看到结果表中的所有相关字段,尽管还有很多其他字段。因此,这仍然不完全正确。


我再次查看了 CrewAI 的执行日志,以找出问题所在。问题出在获取列列表上。由于没有按数据库进行筛选,因此返回的结果中出现了一些不相关的列。


SELECT column_name 
FROM information_schema.columns 
WHERE table_name = 'users'


此外,在查看了多次尝试后,我注意到数据库专家不时执行 select * from <table> 查询。这可能会在生产中造成一些问题,因为它可能会生成大量数据并将其发送到 LLM。


更多专业工具

我们可以为代理提供更多专业工具,以改进我们的解决方案。目前,代理有一个执行任何 SQL 查询的工具,它灵活而强大,但容易出错。我们可以创建更有针对性的工具,例如从表中获取表结构和前 N 行。希望这样能减少错误的发生。


class TableStructure(BaseTool):
  name: str = "Table structure"
  description: str = "Returns the list of columns and their types"
  def _run(self, table: str) -> str:
    table = table.strip('"').strip("'")
    return get_clickhouse_data(
      'describe {table} format TabSeparatedWithNames'\
        .format(table = table)
    )
class TableExamples(BaseTool):
  name: str = "Table examples"
  description: str = "Returns the first N rows from the table"
  def _run(self, table: str, n: int = 30) -> str:
    table = table.strip('"').strip("'")
    return get_clickhouse_data(
      'select * from {table} limit {n} format TabSeparatedWithNames'
        \.format(table = table, n = n)
    )
table_structure_tool = TableStructure()
table_examples_tool = TableExamples()


现在,我们需要在任务中指定这些工具,然后重新运行脚本。第一次尝试后,我从技术作家那里得到了以下输出。


Task output: This final answer provides a detailed and factual description 
of the ecommerce_db.users table structure, including column names, types, 
and descriptions. The documentation adheres to the provided information 
from the database specialist without any additions or modifications.


更有针对性的工具帮助数据库专家检索到了正确的表信息。然而,尽管作者掌握了所有必要的信息,我们还是没有得到预期的结果。


我们知道,LLM 是概率性的,所以我又试了一次。万幸的是,这次的结果还不错。


10


它并不完美,因为它仍然包含一些无关紧要的注释,缺少对表格的整体描述。不过,提供更专业的工具肯定会有所收获。当代理试图从表格中加载所有数据时,这也有助于避免问题的发生。


质量保证专家

我们已经取得了相当不错的成果,但让我们看看能否进一步改进。质量保证是多代理设置中的一种常见做法,它在最终确定结果之前增加了最后的审查阶段。


11


让我们创建一个新的代理--质量保证专家,负责审核工作。


qa_specialist_agent = Agent(
  role = "Quality Assurance specialist",
  goal = """Ensure the highest quality of the documentation we provide 
  (that it's correct and easy to understand)""",
  backstory = '''
  You work as a Quality Assurance specialist, checking the work 
  from the technical writer and ensuring that it's inline 
  with our highest standards.
  You need to check that the technical writer provides the full complete 
  answers and make no assumptions. 
  Also, you need to make sure that the documentation addresses 
  all the questions and is easy to understand.
  ''',
  allow_delegation = False,
  verbose = True
)


现在是描述审查任务的时候了。我使用上下文参数指定该任务需要 table_description_task 和 table_documentation_task 的输出。


qa_review_task = Task(
  description = '''
  Review the draft documentation provided by the technical writer.
  Ensure that the documentation fully answers all the questions: 
  the purpose of the table and its structure in the form of table. 
  Make sure that the documentation is consistent with the information 
  provided by the database specialist. 
  Double check that there are no irrelevant comments in the final version 
  of documentation.
  ''',
  expected_output = '''
  The final version of the documentation in markdown format 
  that can be published. 
  The documentation should fully address all the questions, be consistent 
  and follow our professional but informal tone of voice.
  ''',
  tools = [],
  context = [table_description_task, table_documentation_task],
  output_file="checked_table_documentation.md",
  agent = qa_specialist_agent
)


让我们更新我们的团队并运行它。


full_crew = Crew(
  agents=[database_specialist_agent, tech_writer_agent, qa_specialist_agent],
  tasks=[table_description_task,  table_documentation_task, qa_review_task],
  verbose = 2,
  memory = False # don't work with Llama
)
full_result = full_crew.kickoff({'table': 'ecommerce_db.users'})


由于增加了质量保证阶段,我们现在有了更有条理、更详细的文件。


12


授权

随着质量保证专家的加入,对委托机制进行测试将是一件有趣的事情。质量保证专家代理可能会将问题或请求委托给其他代理。


我试着与 Llama 3 一起使用委托机制,但进展并不顺利。Llama 3 难以正确调用同事工具。它无法指定正确的同事姓名。


使用可以在任何笔记本电脑上运行的本地模型,我们取得了相当不错的效果,但现在是时候换一换了,使用功能更强大的模型--GPT-4o。


为此,我们只需更新以下环境变量。


os.environ["OPENAI_MODEL_NAME"] = 'gpt-4o'  
os.environ["OPENAI_API_KEY"] = config['OPENAI_API_KEY'] # your OpenAI key


要打开委托功能,我们应该为 QA 专家代理指定 allow_delegation = True。


此外,如上所述,我们还可以为 OpenAI 模型使用方便的内存功能。记忆功能将允许我们的代理在执行过程中相互分享知识,并利用长期记忆从之前的执行中获取信息。


full_crew = Crew(
  agents = [database_specialist_agent, tech_writer_agent, qa_specialist_agent],
  tasks = [table_description_task,  table_documentation_task, qa_review_task],
  verbose = 2,
  memory = True 
)
full_result = full_crew.kickoff({'table': 'ecommerce_db.users'})


让我们看看 CrewAI 的执行日志,以了解授权是如何运作的。这是 QA 专家的所有日志。我们可以看到,它联系了数据库专家以仔细核对信息。


Thought: I need to review the provided draft documentation to ensure it 
fully answers all the questions and is complete, correct, and easy 
to understand. Specifically, I need to ensure that it explains the purpose 
of the table, its structure, and includes relevant examples without 
any irrelevant comments.
First, I will compare the two drafts to identify any discrepancies and 
missing information. Then, I will verify the accuracy and completeness of 
the information provided.
**Comparison:**
1. **Introduction:**
   - First draft: Includes an introduction and examples.
   - Second draft: Adds a more detailed introduction about the purpose and importance of the table.
2. **Columns:**
   - Both drafts have the same column structure and descriptions.
3. **Additional Information:**
   - Both drafts include similar additional information, but the second draft is more concise.
4. **Examples:**
   - The first draft includes examples, which are missing in the second draft.
5. **Final Checks:**
   - Ensure there are no irrelevant comments.
   - Verify consistency with information provided by the database specialist.
Action: Ask question to co-worker
Action Input: {
"question": "Can you confirm the accuracy of the provided column descriptions 
  and examples for the `ecommerce_db.users` table?", 
"context": "We are finalizing the documentation for the `ecommerce_db.users`
 table. The table includes columns `user_id`, `country`, `is_active`, and 
  `age`. Here are the examples provided:
  | user_id | country        | is_active | age |
  |---------|----------------|-----------|-----|
  | 1000001 | United Kingdom | 0         | 70  |
  | 1000002 | France         | 1         | 87  |
  | 1000003 | France         | 1         | 88  |
  | 1000004 | Germany        | 1         | 25  |
  | 1000005 | Germany        | 1         | 48  |
  | 1000006 | France         | 1         | 78  |
  | 1000007 | Netherlands    | 1         | 65  |
  | 1000008 | Germany        | 1         | 31  |
  | 1000009 | Germany        | 1         | 66  |
  | 1000010 | Germany        | 1         | 73  |", 
"coworker": "database specialist"}
<-- omitted logs from the database specialist -->
Based on the provided context and examples for the `ecommerce_db.users` table, 
the column descriptions and examples appear accurate. Here are the details:
- `user_id`: This column contains a unique identifier for each user. 
  The provided examples (e.g., 1000001, 1000002) align with this description 
  as they are unique numeric identifiers.
- `country`: This column specifies the country of the user. The provided 
  examples (e.g., United Kingdom, France, Germany, Netherlands) are 
  all valid country names.
- `is_active`: This column indicates whether the user is active (1) or not (0). The provided examples (e.g., 0, 1) correctly represent this binary status.
- `age`: This column shows the age of the user. The provided examples (e.g., 70, 
  87, 88, 25, 48, 78, 65, 31, 66, 73) are all valid ages.
Therefore, the column descriptions and examples provided for the 
 `ecommerce_db.users` table are accurate and meet the expected criteria.
Thought: Based on the observation from the database specialist, 
the column descriptions and examples provided for the `ecommerce_db.users` 
table are accurate. Now, I will consolidate the best elements from both drafts,
 ensuring the documentation is complete, clear, and free of irrelevant comments.
Action: Finalize the documentation in markdown format, incorporating 
the detailed introduction, examples, and ensuring the tone is professional 
but informal.


当我第一次尝试授权时,我没有启用内存,导致结果不正确。数据专家和技术撰稿人最初返回了正确的信息。但是,当质量保证专家带着后续问题返回时,他们开始出现幻觉。由此看来,当启用记忆功能时,代表团工作得更好。


这是 GPT-4o 的最终输出结果。现在的结果看起来很不错。我们绝对可以使用 LLM 来实现文档自动化。


13


这样,第一个任务就解决了!


我还用同样的脚本为 ecommerce_db.sessions 表生成了文档。它将为我们的下一个任务提供方便。所以,让我们不要浪费时间,继续前进吧。


用例:回答问题

我们的下一个任务是根据文档回答问题,因为这是许多数据分析师(和其他专家)的常见问题。


我们将从简单的开始,只创建两个代理:

  • 文档支持专家将根据文档回答问题、
  • 支持 QA 代理将在与客户共享答案之前对答案进行审核。


14


我们需要为文档专家提供一些工具,使他们能够查看目录中存储的所有文件并读取文件。这很简单,因为 CrewAI 已经实现了此类工具。


from crewai_tools import DirectoryReadTool, FileReadTool
documentation_directory_tool = DirectoryReadTool(
    directory = '~/crewai_project/ecommerce_documentation')
base_file_read_tool = FileReadTool()


不过,由于 Llama 3 在调用工具时总是难以使用引号,我不得不在 FileReaderTool 的基础上创建一个自定义工具来解决这个问题。


from crewai_tools import BaseTool
class FileReadToolUPD(BaseTool):
    name: str = "Read a file's content"
    description: str = "A tool that can be used to read a file's content."
    def _run(self, file_path: str) -> str:
        # Implementation goes here
        return base_file_read_tool._run(file_path = file_path.strip('"').strip("'"))
        
file_read_tool = FileReadToolUPD()


接下来,与之前一样,我们需要创建代理、任务和工作人员。


data_support_agent = Agent(
  role = "Senior Data Support Agent",
  goal = "Be the most helpful support for you colleagues",
  backstory = '''You work as a support for data-related questions 
  in the company. 
  Even though you're a big expert in our data warehouse, you double check 
  all the facts in documentation. 
  Our documentation is absolutely up-to-date, so you can fully rely on it 
  when answering questions (you don't need to check the actual data 
  in database).
  Your work is very important for the team success. However, remember 
  that examples of table rows don't show all the possible values. 
  You need to ensure that you provide the best possible support: answering 
  all the questions, making no assumptions and sharing only the factual data.
  Be creative try your best to solve the customer problem. 
  ''',
  allow_delegation = False,
  verbose = True
)
qa_support_agent = Agent(
  role = "Support Quality Assurance Agent",
  goal = """Ensure the highest quality of the answers we provide 
  to the customers""",
  backstory = '''You work as a Quality Assurance specialist, checking the work 
  from support agents and ensuring that it's inline with our highest standards.
  You need to check that the agent provides the full complete answers 
  and make no assumptions. 
  Also, you need to make sure that the documentation addresses all 
  the questions and is easy to understand.
  ''',
  allow_delegation = False,
  verbose = True
)
draft_data_answer = Task(
  description = '''Very important customer {customer} reached out to you 
  with the following question:
  ```
  {question}
  ```
  
  Your task is to provide the best answer to all the points in the question 
  using all available information and not making any assumprions. 
  If you don't have enough information to answer the question, just say 
  that you don't know.''',
  expected_output = '''The detailed informative answer to the customer's 
  question that addresses all the point mentioned. 
  Make sure that answer is complete and stict to facts 
  (without any additional information not based on the factual data)''',
  tools = [documentation_directory_tool, file_read_tool], 
  agent = data_support_agent
)
answer_review = Task(
  description = '''
  Review the draft answer provided by the support agent.
  Ensure that the it fully answers all the questions mentioned 
  in the initial inquiry. 
  Make sure that the answer is consistent and doesn't include any assumptions.
  ''',
  expected_output = '''
  The final version of the answer in markdown format that can be shared 
  with the customer. 
  The answer should fully address all the questions, be consistent 
  and follow our professional but informal tone of voice. 
  We are very chill and friendly company, so don't forget to include 
  all the polite phrases.
  ''',
  tools = [], 
  agent = qa_support_agent
)
qna_crew = Crew(
  agents = [data_support_agent, qa_support_agent],
  tasks = [draft_data_answer,  answer_review],
  verbose = 2,
  memory = False # don't work with Llama
)


让我们看看它在实践中是如何运作的。


result = qna_crew.kickoff(
  {'customer': "Max", 
   'question': """Hey team, I hope you're doing well. I need to find 
    the numbers before our CEO presentation tomorrow, so I will really 
    appreciate your help.
    I need to calculate the number of sessions from our Windows users in 2023. I've tried to find the table with such data in our data warehouse, but wasn't able to. 
    Do you have any ideas whether we store the needed data somewhere, 
    so that I can query it? """
  }
)


我们得到了一个礼貌、实用和有益的答复。这真是太好了。


**Hello Max,**
Thank you for reaching out with your question! I'm happy to help you 
find the number of sessions from Windows users in 2023. 
After reviewing our documentation, I found that we do store data 
related to sessions and users in our ecommerce database, specifically in 
the `ecommerce_db.sessions` table.
To answer your question, I can provide you with a step-by-step guide 
on how to query this table using SQL. First, you can use the `session_id` 
column along with the `os` column filtering for "Windows" and 
the `action_date` column filtering for dates in 2023. 
Then, you can group the results by `os` using the `GROUP BY` clause 
to count the number of sessions that meet these conditions.
Here's a sample SQL query that should give you the desired output:
```sql
SELECT COUNT(*) 
FROM ecommerce_db.sessions 
WHERE os = 'Windows' 
AND action_date BETWEEN '2023-01-01' AND '2023-12-31'
GROUP BY os;
```
This query will return the total number of sessions from Windows 
users in 2023. I hope this helps! If you have any further questions or 
need more assistance, please don't hesitate to ask.


让我们把任务复杂化一点。假设我们不仅会收到关于数据的问题,还会收到关于我们的工具(ClickHouse)的问题。因此,我们的团队中将有另一个代理--ClickHouse Guru。为了给我们的 CH 代理提供一些知识,我将与它分享一个文档网站。


from crewai_tools import ScrapeWebsiteTool, WebsiteSearchTool
ch_documenation_tool = ScrapeWebsiteTool(
  'https://clickhouse.com/docs/en/guides/creating-tables')


如果需要处理冗长的文档,可以尝试使用 RAG(Retrieval Augmented generation)- WebsiteSearchTool。它会计算嵌入并将其本地存储在 ChromaDB 中。在我们的案例中,我们将坚持使用简单的网站搜索工具。


现在我们已经有了两位主题专家,我们需要决定由谁来处理这些问题。因此,我们需要使用分层流程,并添加一名管理者来协调所有任务。


15


CrewAI 提供了管理器实现,因此我们只需指定 LLM 模型。我选择了 GPT-4o。


from langchain_openai import ChatOpenAI
from crewai import Process
complext_qna_crew = Crew(
  agents = [ch_support_agent, data_support_agent, qa_support_agent],
  tasks = [draft_ch_answer, draft_data_answer, answer_review],
  verbose = 2,
  manager_llm = ChatOpenAI(model='gpt-4o', temperature=0),  
  process = Process.hierarchical,  
  memory = False 
)


在这一点上,我不得不再次从 Llama 3 切换到 OpenAI 模型,以运行分层流程,因为我在使用 Llama 时没有成功(与此问题类似)。


现在,我们可以用不同类型的问题(与我们的数据或 ClickHouse 数据库相关)来尝试我们的新机组。


ch_result = complext_qna_crew.kickoff(
  {'customer': "Maria", 
   'question': """Good morning, team. I'm using ClickHouse to calculate 
   the number of customers. 
   Could you please remind whether there's an option to add totals 
   in ClickHouse?"""
  }
)
doc_result = complext_qna_crew.kickoff(
  {'customer': "Max", 
   'question': """Hey team, I hope you're doing well. I need to find 
    the numbers before our CEO presentation tomorrow, so I will really 
    appreciate your help.
    I need to calculate the number of sessions from our Windows users 
    in 2023. I've tried to find the table with such data 
    in our data warehouse, but wasn't able to. 
    Do you have any ideas whether we store the needed data somewhere, 
    so that I can query it. """
  }
)


如果我们看一下最终答案和日志,就会发现经理能够正确协调并将任务分配给具备相关知识的同事,以解决客户的问题。对于第一个(与 ClickHouse 相关的)问题,我们得到了详细的回答,其中包括使用 WITH TOTALS 功能的示例和可能的影响。对于与数据相关的问题,模型返回的信息与我们上面看到的大致相同。


因此,我们已经建立了一个可以根据文档(无论是来自本地文件还是网站)回答各种类型问题的团队。我认为这是一个很好的结果。


总结

在这篇文章中,我们探讨了如何使用 CrewAI 多代理框架来创建一个基于表格编写文档并回答相关问题的解决方案。

文章来源:https://towardsdatascience.com/multi-ai-agent-systems-101-bac58e3bcc47
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
写评论取消
回复取消