基于coze平台数据可视化智能体开发

3 阅读13分钟

使用 Coze 工作流开发科研数据可视化智能体:从文件上传到柱状图展示

1. 项目背景

在科研写作过程中,经常需要把实验结果表格转换为可视化图表,例如:

  • 不同数据集上的 Precision、Recall、F1-Score 对比;
  • 不同方法的性能指标对比;
  • 不同类别的分类效果展示;
  • 论文实验结果的柱状图、折线图或雷达图生成。

传统做法通常需要手动整理 Excel、Python 画图、导出图片,再插入论文或报告中。为了减少重复操作,可以使用 Coze 搭建一个“数据可视化智能体”,让用户上传实验结果文件后,自动完成数据解析、指标抽取和图表生成。

本文介绍一个基于 Coze 工作流实现的科研数据可视化智能体,核心流程如下:

上传数据文件
→ 文本提取
→ 数据整理
→ Python 代码生成图表 URL
→ 结束节点输出
→ 页面图片组件展示

Coze 的低代码工作流支持通过可视化画布编排节点,开始节点和结束节点用于定义工作流的输入输出,代码节点可以编写自定义逻辑并返回结果,用户界面编辑器则可以拖拽组件并绑定工作流数据。(扣子)


2. 智能体功能目标

本智能体的目标是:

用户上传一个包含实验指标的数据文件,智能体自动读取其中的类别、Precision、Recall、F1-Score,并生成对应的柱状图,在页面中直接预览。

例如,用户上传的数据内容可能是:

Dataset,TON-loT
Class,Precision,Recall,F1-Score
Backdoor,1.00,0.99,0.99
DDoS,0.90,0.87,0.88
DoS,0.99,0.88,0.83
injection,0.77,0.76,0.68
mitm,0.61,0.72,0.69
password,0.82,0.88,0.85
ransomware,0.75,0.69,0.72
scanning,0.98,0.78,0.87
xss,0.95,0.86,0.90

系统最终生成一张分组柱状图:

  • 横轴:类别名称;

  • 纵轴:指标得分;

  • 每个类别包含三根柱子:

    • Precision;
    • Recall;
    • F1-Score。

3. 整体架构设计

当前工作流可以拆成 5 个核心模块:

开始节点
  ↓
文本提取节点
  ↓
数据整理节点
  ↓
代码节点
  ↓
结束节点

对应的功能如下:

节点作用
开始节点接收用户上传的文档或文件
文本提取节点从 docx、pdf、txt、csv 等文件中提取文本内容
数据整理节点使用大模型把原始文本整理成结构化 CSV
代码节点使用 Python 解析 CSV,并生成柱状图 URL
结束节点输出 Markdown 结果和纯图片 URL

4. 工作流节点配置

4.1 开始节点配置

开始节点用于定义整个工作流需要接收哪些输入。官方文档中说明,开始节点负责配置低代码工作流启动时所需的信息,结束节点负责输出工作流结果。(扣子)

本项目中,开始节点建议只保留一个输入参数:

参数名类型是否必填说明
inputDoc / File用户上传的数据文件

如果你的 Coze 页面中使用的是文件上传组件,则在用户界面中需要将上传组件的 value 绑定到这个参数。文件上传组件的值可以传递给工作流的文件类型入参,系统会自动解析并获取文件 URL,再传递给下游节点。(扣子)

推荐配置:

参数名:input
参数类型:Doc 或 File
必填:开启

4.2 文本提取节点配置

文本提取节点负责从上传的文件中读取文本内容。

输入配置:

url / file / input:引用开始节点 input

输出一般包括:

message:提取出来的文本内容

如果上传的是 .docx.txt.csv 或论文实验结果表格,文本提取节点会把其中内容转换为后续大模型节点可以处理的文本。


4.3 数据整理节点配置

数据整理节点的作用是把文本提取结果整理成统一格式。

输入:

input = 文本提取节点.message

推荐使用大模型节点进行结构化整理。Coze 的大模型节点可以根据输入参数和提示词生成回复,适合做文本抽取、格式转换和结构化输出。(扣子)

推荐提示词

你是一个科研实验数据整理助手。

你的任务是从用户上传的文本中提取分类实验指标,并整理为标准 CSV 格式。

请只输出如下 JSON,不要输出解释、说明或 Markdown:

{
  "input": "Dataset,数据集名称\nClass,Precision,Recall,F1-Score\n类别1,precision,recall,f1\n类别2,precision,recall,f1"
}

要求:
1. 如果文本中存在 Dataset 或数据集名称,请填入 Dataset 行;
2. 如果没有数据集名称,则 Dataset 填写 Unknown;
3. 只保留 Class、Precision、Recall、F1-Score 四类指标;
4. 数值保留原始小数格式;
5. 不要添加额外字段;
6. 不要输出代码块;
7. 不要输出自然语言解释。

推荐输出格式

{
  "input": "Dataset,TON-loT\nClass,Precision,Recall,F1-Score\nBackdoor,1.00,0.99,0.99\nDDoS,0.90,0.87,0.88\nDoS,0.99,0.88,0.83"
}

这样做的好处是:后续代码节点只需要处理统一格式,不需要面对复杂的原始文档结构。


4.4 代码节点配置

代码节点用于解析大模型输出,并生成图表链接。Coze 的代码节点支持通过编写代码来生成返回值,适合处理输入参数并输出结构化结果。(扣子)

输入配置

参数名类型绑定来源
inputString数据整理节点.output 或 reasoning_content

输出配置

建议配置两个输出:

输出变量类型作用
outputStringMarkdown 图片展示内容
chart_urlString纯图片 URL,供图片组件绑定

这里一定要注意:

  • output 可以是 ![柱状图](url)
  • chart_url 必须是纯 URL;
  • 图片组件要绑定 chart_url,不要绑定 Markdown 字符串。

5. 代码节点完整 Python 代码

下面代码可以同时兼容两种常见输入格式。

格式一:标准 CSV

Dataset,Class,Precision,Recall,F1-Score
TON-loT,Backdoor,1.00,0.99,0.99
TON-loT,DDoS,0.90,0.87,0.88

格式二:两段式 CSV

Dataset,TON-loT
Class,Precision,Recall,F1-Score
Backdoor,1.00,0.99,0.99
DDoS,0.90,0.87,0.88

Python 代码

import json
import re
import csv
from io import StringIO
from urllib.parse import quote

async def main(args: Args) -> Output:
    try:
        params = args.params or {}
        raw_input = params.get("input", "")

        # 1. 兼容输入为 dict、字符串、JSON 字符串
        if isinstance(raw_input, dict):
            csv_text = raw_input.get("input", "")
        else:
            csv_text = raw_input

        if not isinstance(csv_text, str):
            csv_text = str(csv_text or "")

        csv_text = csv_text.strip()

        # 2. 去掉可能的大模型 Markdown 代码块
        csv_text = csv_text.replace("```json", "").replace("```csv", "").replace("```", "").strip()

        # 3. 如果是 JSON 字符串,提取其中的 input 字段
        if csv_text.startswith("{") and csv_text.endswith("}"):
            try:
                obj = json.loads(csv_text)
                if isinstance(obj, dict) and "input" in obj:
                    csv_text = obj.get("input", "")
            except Exception:
                pass

        # 4. 处理转义换行
        csv_text = csv_text.replace("\n", "\n").replace('\"', '"').strip()

        if not csv_text:
            return {
                "output": "未接收到有效输入。",
                "chart_url": ""
            }

        def parse_value(val):
            if val is None:
                return 0.0
            s = str(val).strip()
            if not s:
                return 0.0
            if s.endswith("%"):
                try:
                    return float(s[:-1]) / 100.0
                except Exception:
                    return 0.0
            try:
                return float(s)
            except Exception:
                return 0.0

        def clean_key(key):
            key = str(key or "").replace("\ufeff", "").strip().lower()
            key = re.sub(r"[^a-z0-9]+", "", key)
            return key

        lines = [line.strip() for line in csv_text.split("\n") if line.strip()]
        rows = []
        dataset_name = "Unknown"

        # 5. 兼容两段式格式:
        # Dataset,TON-loT
        # Class,Precision,Recall,F1-Score
        # Backdoor,1.00,0.99,0.99
        if len(lines) >= 3 and lines[0].lower().startswith("dataset,") and lines[1].lower().startswith("class,"):
            first_row = next(csv.reader([lines[0]]))
            if len(first_row) > 1:
                dataset_name = first_row[1].strip() or "Unknown"

            headers = next(csv.reader([lines[1]]))
            data_lines = lines[2:]

            for line in data_lines:
                values = next(csv.reader([line]))
                while len(values) < len(headers):
                    values.append("")
                row = dict(zip(headers, values))
                rows.append(row)

        # 6. 兼容标准 CSV 格式:
        # Dataset,Class,Precision,Recall,F1-Score
        # TON-loT,Backdoor,1.00,0.99,0.99
        else:
            reader = csv.DictReader(StringIO(csv_text))
            rows = list(reader)

        if not rows:
            return {
                "output": "未解析到有效数据。",
                "chart_url": ""
            }

        labels = []
        precision_data = []
        recall_data = []
        f1_data = []

        for row in rows:
            normalized = {}
            for k, v in row.items():
                normalized[clean_key(k)] = v

            current_dataset = normalized.get("dataset", "")
            if current_dataset:
                dataset_name = str(current_dataset).strip()

            class_name = (
                normalized.get("class")
                or normalized.get("category")
                or normalized.get("label")
                or ""
            )
            class_name = str(class_name).strip()

            if not class_name:
                continue

            class_key = re.sub(r"[^a-z0-9]+", "", class_name.lower())
            if class_key in ["overall", "accuracy", "macroavg", "weightedavg"]:
                continue

            labels.append(class_name)
            precision_data.append(parse_value(normalized.get("precision", 0)))
            recall_data.append(parse_value(normalized.get("recall", 0)))
            f1_data.append(parse_value(normalized.get("f1score", normalized.get("f1", 0))))

        if not labels:
            return {
                "output": "未解析到类别和指标数据,请检查输入中是否包含 Class、Precision、Recall、F1-Score。",
                "chart_url": ""
            }

        chart_config = {
            "type": "bar",
            "data": {
                "labels": labels,
                "datasets": [
                    {
                        "label": "Precision",
                        "data": precision_data
                    },
                    {
                        "label": "Recall",
                        "data": recall_data
                    },
                    {
                        "label": "F1-Score",
                        "data": f1_data
                    }
                ]
            },
            "options": {
                "responsive": True,
                "plugins": {
                    "title": {
                        "display": True,
                        "text": f"{dataset_name} - Classification Metrics"
                    },
                    "legend": {
                        "position": "top"
                    }
                },
                "scales": {
                    "y": {
                        "beginAtZero": True,
                        "max": 1,
                        "title": {
                            "display": True,
                            "text": "Score"
                        }
                    },
                    "x": {
                        "ticks": {
                            "maxRotation": 45,
                            "minRotation": 45
                        },
                        "title": {
                            "display": True,
                            "text": "Class"
                        }
                    }
                }
            }
        }

        config_str = json.dumps(chart_config, ensure_ascii=False, separators=(",", ":"))
        chart_url = (
            "https://quickchart.io/chart"
            "?width=1200"
            "&height=600"
            "&format=png"
            "&backgroundColor=white"
            "&c="
            + quote(config_str, safe="")
        )

        return {
            "output": f"![柱状图]({chart_url})",
            "chart_url": chart_url
        }

    except Exception as e:
        return {
            "output": f"生成柱状图失败:{str(e)}",
            "chart_url": ""
        }

6. 结束节点配置

结束节点负责把代码节点的结果返回给页面。

建议结束节点配置两个输出字段:

输出名类型绑定来源
outputString代码节点.output
chart_urlString代码节点.chart_url

配置完成后,整个工作流最终会输出:

{
  "output": "![柱状图](https://quickchart.io/chart?...)",
  "chart_url": "https://quickchart.io/chart?..."
}

其中:

  • output 适合绑定 Markdown 或文本组件;
  • chart_url 适合绑定图片组件。

7. 用户界面设计

在 Coze 的用户界面中,可以设计一个简单页面:

文件上传组件
↓
生成柱状图按钮
↓
图片预览组件

用户界面编辑器支持通过拖放组件构建页面,并设置组件属性和样式;文件上传组件可以作为组件值传递给工作流输入。(扣子)


7.1 文件上传组件配置

组件名称建议:

FileUpload1

配置建议:

配置项推荐值
支持格式doc、docx、txt、csv、pdf
最大数量1
是否必填
文案请上传实验数据文件

7.2 按钮组件配置

按钮名称建议:

Button1

按钮文案:

生成柱状图

按钮事件配置:

事件类型:点击时
执行动作:调用工作流
Workflow:shujukeshihua

工作流入参配置:

工作流参数绑定值
inputFileUpload1.value

注意,这里不要绑定:

Button1.content

也不要绑定:

FileUpload1

而是要绑定:

FileUpload1.value

原因是 FileUpload1 表示整个上传组件对象,而 FileUpload1.value 才是用户上传文件对应的值。

如果按钮在工作流运行过程中可以被重复点击,建议将按钮的禁用态和加载态绑定到工作流 loading 状态,避免重复触发。Coze 的 UI Builder FAQ 也建议内容生成类场景为生成按钮绑定工作流 loading 值。(扣子)


7.3 图片组件配置

组件名称建议:

Image1

图片组件属性配置:

配置项推荐值
来源绑定数据
绑定值工作流输出 chart_url
填充方式包含 / 覆盖
宽度100% 或固定宽度
高度自适应或固定高度
支持大图可开启

关键点:

Image1 的数据源要绑定到工作流输出的 chart_url

不要绑定:

{{ Button1.content }}

因为 Button1.content 只是按钮文案“生成柱状图”,不是图片地址。

也不要绑定:

output

如果 output 是 Markdown 格式:

![柱状图](https://quickchart.io/chart?...)

图片组件通常需要的是纯图片地址,所以应该绑定:

chart_url

8. 推荐页面布局

页面可以设计为:

标题:科研数据可视化助手

上传区域:
[文件上传组件]

操作区域:
[生成柱状图按钮]

结果区域:
[图片组件 Image1]
[可选 Markdown 组件 output]

如果想让页面更适合科研写作,可以增加说明文字:

请上传包含 Class、Precision、Recall、F1-Score 的实验结果文件,系统将自动生成分类指标柱状图。

9. 完整运行流程

用户实际使用时的流程如下:

1. 用户上传 data.docx / data.csv / data.txt
2. 点击“生成柱状图”
3. 工作流读取上传文件
4. 文本提取节点提取实验数据
5. 数据整理节点把文本转换为统一 CSV
6. 代码节点解析 CSV 并生成图表 URL
7. 结束节点返回 output 和 chart_url
8. 页面图片组件读取 chart_url 并展示柱状图

10. 常见问题与解决方案

10.1 点击发布时提示 UI 页面没有绑定工作流

原因通常是只搭建了业务逻辑工作流,但用户界面中的按钮没有绑定工作流。

解决方法:

用户界面
→ 选中按钮
→ 事件
→ 新建事件
→ 点击时
→ 调用工作流
→ 选择 shujukeshihua
→ 绑定 input = FileUpload1.value

10.2 工作流入参中出现 USER_INPUT、CONVERSATION_NAME

这通常是旧的绑定关系或旧 schema 残留。

解决方法:

1. 删除按钮原来的调用工作流事件
2. 保存页面
3. 刷新页面
4. 重新创建按钮事件
5. 重新选择工作流
6. 重新绑定 input = FileUpload1.value

10.3 图片组件显示空白

优先检查三点:

第一,结束节点是否输出了 chart_url

第二,chart_url 是否是纯 URL,例如:

https://quickchart.io/chart?width=1200&height=600...

第三,图片组件是否绑定到了:

工作流输出 chart_url

不要绑定:

Button1.content

也不要绑定 Markdown 字符串:

![柱状图](https://quickchart.io/chart?...)

10.4 代码节点提示没有解析到数据

常见原因是大模型输出格式不稳定。

建议在数据整理节点提示词中明确要求:

只输出 JSON,不要输出 Markdown,不要输出解释。

同时要求固定结构:

{
  "input": "Dataset,TON-loT\nClass,Precision,Recall,F1-Score\nBackdoor,1.00,0.99,0.99"
}

10.5 上传文件后传入的不是文件

如果运行日志中看到类似:

{
  "input": {
    "hidden": false,
    "value": {
      "file_name": "data.docx"
    }
  }
}

说明传的是整个组件对象,而不是文件值。

应该改成:

input = FileUpload1.value

而不是:

input = FileUpload1

11. 项目扩展方向

当前版本主要生成 Precision、Recall、F1-Score 的柱状图,后续可以扩展为:

扩展功能说明
多图表类型支持柱状图、折线图、雷达图、热力图
多数据集对比同时比较 TON-IoT、CICIDS、UNSW-NB15 等数据集
多模型对比比较不同算法在同一数据集上的性能
自动论文描述根据图表自动生成论文实验分析段落
LaTeX 表格生成自动把数据转换为 IEEE 风格表格
图表标题优化自动生成适合论文的图题
图片下载输出可下载的 PNG 链接
Markdown 报告一次性生成图表、表格和分析文本

12. 总结

本文实现了一个面向科研写作场景的数据可视化智能体。它通过 Coze 工作流完成文件读取、数据整理和图表生成,并通过用户界面中的图片组件展示最终结果。

核心实现思路是:

文件上传组件 value
→ 工作流 input
→ 文本提取
→ 大模型整理为 CSV
→ Python 代码生成 chart_url
→ 结束节点输出
→ 图片组件绑定 chart_url

其中最关键的两个点是:

1. 上传文件时,按钮事件要绑定 FileUpload1.value
2. 图片展示时,Image1 要绑定工作流输出 chart_url

这样,一个适用于科研实验结果可视化的 Coze 智能体就可以完整运行起来。