AI知识库

53AI知识库

学习大模型的前沿技术与行业应用场景


利用 CrewAI 和 Streamlit 将客户反馈转化为可操作的洞察力
发布日期:2024-12-26 12:34:42 浏览次数: 1661 来源:barry的异想世界


构建一个 AI 驱动的应用程序,以分析非结构化反馈,生成洞察报告,并创建互动可视化

新的 AI 代理工具使得自动化数据分析变得异常简单。当与像 Streamlit 这样的可视化平台结合时,创建一个视觉上引人注目的商业报告应用程序变得轻而易举。

在本教程中,我们将重点分析客户反馈。这种分析和对客户情绪的良好理解对于确保产品满足期望并解决质量问题至关重要。被认为质量差的产品根本就无法销售。

然而,分析客户反馈可能涉及处理大量非结构化数据——这是一项令人畏惧的任务。这正是大型语言模型的优势所在,使得从这些数据中提取有意义的见解成为可能。

我们的第一步是明确目标。

假设我们有一份关于在线零售商产品的客户反馈消息列表。我们的目标是生成一份高管报告,突出表现优异的产品,识别表现不佳的产品存在的问题,并包含客户情绪的引人注目的可视化。

我们将使用 CrewAI 来协调一组 AI 代理,结构化数据,分析数据,创建一份洞察报告,最后构建一个 Streamlit 应用程序来展示结果。

它将看起来像下面的截图。

首先,我们需要准确地定义我们想要做的事情。

我有一份关于在线零售商产品的虚构客户反馈消息列表。这些消息采用 Markdown 格式。我想生成一份高管报告,突出表现良好的产品,识别表现不佳的产品存在的问题,并包括一些关于客户情绪的互动图表。而且我希望所有这些都能在一个互动网页应用程序中展示。

所以,让我们定义一下我们需要采取的步骤。

定义流程

以下是步骤列表:

  1. 1. 转换原始客户数据并创建结构化版本——可能是 CSV。这将使进一步分析变得更容易,并且这是我可以直接在最终应用中使用的内容。
  2. 2. 计算每条客户消息的情感并将其添加到结构化数据中。
  3. 3. 撰写一份报告,总结以表格形式呈现的数据,并包括识别最佳表现产品、存在质量问题的产品以及需要解决的一般问题的部分。报告应格式化为 Markdown。
  4. 4. 创建一个 Streamlit 应用,第一列显示报告,第二列显示一些互动图表,展示客户满意度。
  5. 5. 在应用中将结构化客户反馈数据作为表格提供。

与我之前的文章 *AI for BI: Building a Business Information Report with CrewAI and OpenAI* 一样,我使用 CrewAI 和 OpenAI 来构建此软件。原因相同,并不是因为它们必然是这个问题的最佳技术解决方案,而是它们方便、易于使用,并且能够完成任务。

我使用 Jupyter Notebook 编写分析代码,因此您在下面看到的大部分代码可以直接按顺序写入笔记本单元格中。(要创建独立应用,只需将单元格连接起来。)

Streamlit 中的报告程序是独立的,由 AI 创建。您将在我的 GitHub 仓库中找到所有代码和数据。

由于我们将使用 OpenAI API,您需要一个 API 密钥,该密钥应作为环境变量可访问。如果尚未将其存储为环境变量,您可以先运行以下代码块以确保其可用。

## Omit this if your API key is already set as an environment variable

import os
os.environ["OPENAI_API_KEY"] = "your api key"

OpenAI 将对使用其 LLM 收取费用。不过,运行以下代码不应超过几美分的费用(但请在 OpenAI 仪表板上关注您的使用情况)。我们使用的 CrewAI 软件是开源的,且不收费。

让我们开始编码吧。

编写代码

我已经概述了我们将要经历的步骤。这些步骤可以分为三个阶段:将原始数据转换为CSV,编写报告并创建Streamlit应用。我们将为每个阶段定义代理和任务,并保存结果。因此,我们将最终得到三个文件:结构化数据的CSV文件、Markdown格式的报告和Streamlit应用。

保存中间文件有两个优点。第一个是,如果我们愿意,可以独立运行这三个阶段中的每一个。例如,如果我们想调整报告的结构,就不需要重新创建CSV文件,我们可以使用之前准备好的文件。如果Streamlit应用需要修改,也可以独立于其余代码进行处理。第二个优点是,生成的应用可以使用这些文件。

这就是我们将要进行的步骤,但在进入主要代码之前,需要进行一些设置。

我们需要从CrewAI导入一些内容并设置LLM模型。正如我之前所说,CrewAI默认使用OpenAI API,因此,只要我们拥有API密钥,所需做的就是在变量llm中设置我们想要使用的模型。我还将temperature设置为零。这减少了随机性,使响应更加一致。

我还设置了一个标志DEBUG。这个标志用于控制代理响应的详细程度。出于调试目的,获取完整的响应是一个好主意,以查看代理如何处理请求。将DEBUG设置为True即可。

from crewai import Agent, Task, Crew, LLM

llm = LLM(
    model="gpt-4o-mini",
    temperature=0.0
)
DEBUG = False

代码的下一部分定义了一些文件名。第一个是原始客户数据文件。接下来的两个文件将存储Streamlit应用的中间数据,最后一个是应用本身。

## files
fb_raw =    "./data/shoes.md"
fb_csv =    "./data/fb.csv"
report_md = "./data/report.md"
st_app =    "./data/report.py"

现在我们导入CrewAI工具,以便读取和写入文件,并将其分配给变量。

from crewai_tools import FileReadTool, FileWriterTool

file_read_tool = FileReadTool()
file_writer_tool = FileWriterTool()

所有的前导代码都完成了,我们可以开始主要程序。

CrewAI应用有三个基本组件:代理、任务和团队。代理是与LLM的接口;它有特定的目的,并可以提供功能(工具)来帮助它。任务详细说明了代理需要完成的事情。最后,团队执行一系列任务和代理,并返回结果。

我们将定义三个代理,分别对应上述阶段。一个用于创建和修改CSV文件,另一个用于以Markdown格式编写报告,第三个用于构建Streamlit应用。

每个代理都提供一个或多个任务,这些代理/任务组合将由一个团队进行管理。

CSV agent

第一个代理的任务是从客户反馈的原始Markdown文件构建数据结构,然后计算客户情感并将其添加到数据结构中。

代理的属性与任务之间没有严格的区分。任务有一个description,而代理有三个描述性属性rolegoalbackstory。每个属性都为CrewAI生成有意义的提示做出贡献,但最终我们如何使用它们是由我们决定的。

我的方法是在代理定义中简明扼要,并为代理的任务留下详细的描述。

通过这种方法,我可以将同一个代理用于多个任务,正如我们将看到的那样。

这是我对CSV代理的定义。

csv_agent = Agent(
        role="Extract, process data and record data",
        goal="""Extract data and organise as instructed. 
                The result MUST be valid CSV."""
,
        backstory="""You are a data processing agent""",
        tools=[file_read_tool],
        llm=llm,
    )

这是第一个任务。

create_CSV = Task(
    description=""" 
                Analyse '{input_file}' the data provided - it is in 
                Markdown format. 
                Your output should be in CSV format. Respond without 
                using Markdown code fences.

                The data is about the range of items in an online shop.
                Following this is a set of messages from customers giving 
                feedback about the products that they have purchased.
                Your task is to:
                   Create a structured file in CSV format that records a 
                   list of all customer feedback messages.
                   Each item in the list should have its columns 
                   populated as follows.
                        "Product": insert the name of the item, 
                        "Overall_Rating": insert the rating as given by customer, 
                        "Issue": insert any issues identified 
                                 - if no issue can be identified write 'None', 
                        "Review": insert the customer message 
                """
,
    expected_output="A correctly formatted CSV data structure",
    agent=csv_agent,   
    tools=[file_read_tool]
)

任务更加明确,并告诉代理确切的操作。结果应该是一个具有四列的CSV结构。

我们还希望有一列第五列,记录每条消息的情感。为了不使提示过于复杂,我将其分配给第二个任务。

add_sentiment = Task(
    description=""" 
                Analyse CSV data and calculate the sentiment of each 
                message in the 'Review' column. Add a new column to the 
                CSV that records that sentiment.
                Your output should be in CSV format. Respond without 
                using Markdown code fences.              
                """
,
    expected_output="A correctly formatted CSV data file",
    agent=csv_agent,   
    output_file=fb_csv,
    tools=[file_read_tool]
)

注意,在这个任务中有一个额外的属性output_file,它设置为CSV文件的文件名。这告诉任务自动将结果放入该文件中。

定义任务后,我们执行它们。

crew = Crew(
    agents=[csv_agent, csv_agent],
    tasks=[create_CSV, add_sentiment],
    verbose=DEBUG,
)
result1 = crew.kickoff(inputs={'input_file': fb_raw})

在这个Crew中,有一个代理和任务的列表,这些任务将按顺序执行,当Crew运行时,输入文件名作为参数设置。注意我们有两个任务使用同一个代理。

完成后,我们将得到一个结构类似于以下内容的CSV文件:

我们的下一个任务是写一份报告。

报告编写代理

我们现在有一些格式良好的数据,这些数据经过客户情感评估的增强,并已保存到文件中以备后用。

我们的下一个任务是尝试使用一个新的代理对数据进行更深入的分析。

再次强调,代理的定义很简短,工作的细节在任务中。

## Define agent
report_generator = Agent(
        role="Compile and present results",
        goal="""Deliver a polished, structured report on customer 
                satisfaction.
             """
,
        backstory="""You are an agent, that generates clear, 
                     well-designed and professional reports"""
,
        tools=[file_read_tool],
        llm=llm,
    )

任务明确规定了报告的外观和内容。

create_report = Task(
    description="""
            Read the CSV data in '{csv_file}', create a summary report.
            The report must consolidate and summarize the customer 
            feedback, it should be in Markdown file format 
            (without ``` fences). 

            The report should be structured as follows:
                # Product review report
                ### Summary
                Insert a Markdown table with a row for every product.
                The table header should look like this:
                | Product | Average Rating | Number of reviews | Positive | Neutral | Negative |
                The should be a row for every product like this:
                | insert the product name here 
                | insert the average of all the rating for this product 
                | insert total number of reviews
                | insert number of positive reviews 
                | insert number of neutral reviews 
                | insert number of negative reviews |
                ### Insights
                #### Best performers
                insert a short report on the products with the best reviews
                #### Underperformers
                insert a short report on the products that are underperforming
                #### Issues
                insert a short report on what steps need to be taken to improve products and sales
    """
,
    expected_output="""A Markdown report file""",
    agent=report_generator,
    output_file = report_md,
    tools=[file_read_tool]
)

CrewAI 在生成报告方面表现良好,结果通过执行以下 crew 保存在 report_md 中。

crew = Crew(
    agents=[report_generator],
    tasks=[create_report],
    verbose=DEBUG,
)
result2 = crew.kickoff(inputs={'input_file':fb_raw, 'csv_file': fb_csv})

您可以在我的 GitHub 仓库中查看完整报告,但下面是应用程序报告部分的截图,以便您可以看到生成的内容。

接下来,我们想要生成应用程序。

Streamlit 应用程序

我尽量使应用程序尽可能通用,以便能够处理关于不同产品的不同反馈消息集。

因此,您只需运行这部分代码一次。然后,您将拥有一个 Streamlit 应用程序,它将读取我们之前生成的 CSV 文件和 Markdown 报告,或者实际上是我们稍后创建的文件。

我将应用程序生成分为两个部分,使用两个任务,但使用相同的 Streamlit 生成代理。

这是代理的定义。


## 定义代理
app_generator = Agent(
        role="创建或修改一个 Streamlit 程序",
        goal="""交付一个有效的 Streamlit 程序,使用 Python 编写, 
                变量和函数名称要有意义。
                确保输出严格符合 Python 语法。
                不要包含 ``` 包围。
           """
,
        backstory="""您是一个生成清晰、设计良好的
                     Streamlit 程序的代理"""
,
        tools=[file_writer_tool, file_read_tool],
        llm=llm,
    )

现在进行第一个任务。它设置了基本应用并将 CSV 数据加载为 Pandas 数据框。我不得不告诉它关于 Streamlit API 的一个(现在相当旧的)更新。早期版本使用了装饰器 st.cache,这个已经被弃用,我们应该使用 st.cache_data 来缓存 CSV 数据。我对此感到相当惊讶,因为新的装饰器已经存在一段时间了。然而,问题很容易解决,如您所见。

create_app = Task(
    description="""
            创建一个 Streamlit 应用,如下所示:
            - 将显示设置为宽格式
            - 包含 pandas 和 plotly express 库
            - 从 csv 文件 {csv_file} 创建一个名为 "df" 的 pandas 数据框 
              包含所有字段
            请注意,st.cache 已被弃用,请改用 st.cache_data。
            不要将程序保存到文件中。
    """
,
    expected_output="""一个有效且语法正确的 Python 程序""",
    agent=app_generator,
    tools=[file_read_tool]
)

现在我们有了一个基本程序。在下一个任务中,我给出了明确的指示,说明如何在此程序中添加以显示数据和构建图表。

add_content = Task(
    description="""
            修改 Streamlit 代码,如下所示:
            - 创建两个标签,第一个命名为 "report_tab",第二个 
              命名为 "messages_tab"
            - 在 "messages_tab" 中加载数据框 "df",在 st.table 中显示
            - 在 report_tab 中创建两个相等宽度的列,分别称为 
              "report_column" 和 "chart_column"
            - 在 "report_column" 列中从 {report_file} 读取报告,并在 
              st.markdown 中显示
            - 在 "chart_column" 列中绘制 'Product' 与 
              'Overall_Rating' 的条形图
            - 在 "chart_column" 列中绘制 'Sentiment' 的直方图, 
              条形按 'Product' 着色"""
,
    expected_output="一个有效且语法正确的 Python 程序",
    output_file = st_app,
    agent=app_generator,
    tools=[file_read_tool]   
)

和之前一样,我们用一个代理运行两个任务。

crew = Crew(
    agents=[app_generator, app_generator],
    tasks=[create_app, add_content],
    verbose=DEBUG,
)
result3 = crew.kickoff(inputs={'csv_file': fb_csv ,
                               'report_file': report_md})

这是最后一个阶段。运行 Streamlit 应用,结果将是我们之前看到的截图。请注意,由于我组织文件夹的方式,您需要从父文件夹运行 Streamlit,如下所示:

 streamlit run data/report.py

CrewAI的最终想法

总体来说,我对结果相当满意。CrewAI是一个容易上手的框架,尽管它有一些小毛病。有很多描述性字符串在竞争产品中并不存在,虽然我相信这些对CrewAI的优秀团队来说是有意义的,但我并不完全相信它们都是必要的。

尽管如此,我确实喜欢我们可以将任务与代理分开的方式,给代理提供其能力的广泛描述,并提供任务中需要完成的详细信息。我最近查看的另一个框架(Swarm)不允许这样做。

我在CrewAI中遇到了一个问题,我可能通过这个功能解决了它。

当我运行其中一个小组时,我收到了以下错误:

Tool Output:
Error: the Action Input is not a valid key, value dictionary.
 Error parsing LLM output, agent will retry: I did it wrong. 
Invalid Format: I missed the 'Action:' after 'Thought:'
I will do right nextand don't use a tool I have already used.

这有点难以理解。

这似乎与任务的复杂性有关,因为当我将任务拆分时,问题消失了。

然而,我不确定我的复杂性诊断是否正确,因为我与其他开发者保持联系,他们找到了解决看似相同问题的不同方案(例如,降低温度——这对我没用)。在撰写本文时,这是CrewAI GitHub仓库上的一个实时问题。

关于应用的最终想法

我在定义 Streamlit 应用时非常明确。你可以说我明确得甚至可以自己编写这个应用。这是一个简单的程序,所以这确实有其价值。

但我想要制作一个从原始数据到最终产品的端到端程序。也许,我可以对程序的定义模糊一点。也许那样会导致一个更好的程序!但除了缓存装饰器的问题,AI 没有产生任何错误(而且它的输入速度比我快得多!)所以我并不抱怨。

最后一个问题是,为什么选择 Streamlit?虽然它是这种应用的一个非常好的框架,但真正的原因是,因为 ChatGPT-4o mini 具备使用它的编程知识!

让 ChatGPT 使用一个更新的或不太知名的框架,比如 Mesop 或 Taipy,可能会产生更差的结果。大型语言模型对这些产品的了解更少,更有可能犯错误和产生幻觉。

本文的代码和数据可以在这个 GitHub 仓库 的 AI4BI-fb 文件夹中找到。生成的图表和报告也在同一文件夹中。

https://github.com/alanjones2/CrewAIapps


53AI,企业落地应用大模型首选服务商

产品:大模型应用平台+智能体定制开发+落地咨询服务

承诺:先做场景POC验证,看到效果再签署服务协议。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

与创始人交个朋友

回到顶部

 
扫码咨询