复杂AI工具的设计

本节内容可以参考文件"CaseStudy\LLMCaller.Table"的窗口"数据分析专家"。

打开这个对话框之后,你可以按顺序向AI提交下面的问题:

你会发现它不仅能听懂你的问题,还能自主完成各种数据统计和分析,就像一个经验丰富的数据分析师。

这个对话框之所以能解答上述问题,是因为我们在上一节的基础上,增加了一个用于分组统计的AI工具,这个工具是基于Foxtable的GroupTableBuilder类设计的。

GroupTableBuilder是一个非常强大的类,针对这个类设计工具,是有有一定难度的。

如果你能针对这个GroupTableBuilder设计一个能用且好用的工具,那么你在工具设计方面的能力就算合格了。

提示:

1、GroupTableBuilder是Foxtable的基础统计工具,CrosssTableBuilder是基于GroupTableBuilder的结果进行二次加工的,所以基本上针对GroupTableBuilder定义一个工具,就能随心所欲地让AI进行数据分析了。

2、但是GroupTableBuilder的开销比较大,简单的计算还是Compute方法比较轻便,所以保留了上一节设计的"compute_table"方法,让AI决定选择哪个工具。

3、数据统计分析的基础是表、列以及列的数据类型,所以我们还设计了两个辅助工具,分别用于获取所有的表以及指定的表的结构。

4、实际开发的时候,不要像示例文件这样,将前端页面、工具定义、工具执行的代码全部写在窗口的AfterLoad事件,建议分布在不同的自定义函数中。

设计步骤

1、修改窗口的AfterLoad事件代码,增加一个工具定义:

With caller.AddTool("group_statistics", "对指定数据表进行分组统计,使用前应该先获取表结构(同一个表只需获取一次,不用重复获取),按一个或多个列分组,对一个或多个数值列进行求和、平均、计数、最大、最小等汇总计算。返回统计结果数据(最多返回前1000行)。当用户需要按组汇总数据时使用此工具。对于日期列,可按年、按季度、按月、按周、按天分组(参考group_columns参数的说明)。如果有多个分组,可以考虑用交叉表呈现结果,看起来方便一些")
    .AddParameter(
"table_name", "string", "要统计的数据表名称,必须是当前项目中已存在的表名", True)
    .AddParameter(
"group_columns", "array", "分组列列表,每个元素为列名;非日期列的格式为'原列名|新列名',新列名可选,例如['产品|Product','客户'];对于日期列,格式必须严格按照'列名|日期分组颗粒度|新列名',日期分组颗粒度可为'Year'、'Quarter'、'Month'、'Week'、'Day'、'None',默认按月(Month),例如:['日期|Year|年', '日期|Month|月']", True, "string")
    .AddParameter(
"aggregates", "array", "统计列列表,每项格式为'列名|聚合方式|新列名',新列名和聚合方式可选,聚合方式可为:Sum、Avg、Count、Max、Min, 默认为Sum。例如:['数量|Sum|总数量', '分数|Avg','金额']", True, "string")
    .AddParameter(
"filter", "string", "可选筛选条件,格式同compute_table的filter。", False)
    .AddParameter(
"same_period_growth", "boolean", "是否生成同比增长率(与上一年同期对比)。仅当分组中包含按年分组的日期列,例如:'日期|Year',以及一个按季度、月或周分组的日期列,例如:'日期|Quarter'、'日期|Month'、'日期|Week',才有意义", False)
    .AddParameter(
"circle_growth", "boolean", "是否生成环比增长率(与上一个周期对比)。通常与按月、按季度或按周分组配合使用,例如:'日期|Quarter'、'日期|Month'、'日期|Week'", False)
    .AddParameter(
"grand_proportion", "boolean", "是否生成总占比(占全部数据的百分比)。", False)
    .AddParameter(
"group_proportion", "boolean", "是否生成分组占比(占当前分组内数据的百分比)。需要至少两个分组列时才有意义。", False)
End
With

2、修改窗口的AfterLoad事件代码,给执行工具的回调函数增加一个分支:

        Case "group_statistics" '分组统计
           
Dim tableName As String = targs("table_name").ToString() '获取表名
           
If Not DataTables.Contains(tableName) Then
               
Return "{""error"":""表 '" & tableName & "' 不存在""}"
           
End If
           
Dim groupCols As JArray = targs("group_columns") '分组列
           
Dim aggCols As JArray = targs("aggregates") '统计列
           
Dim dt As DataTable = DataTables(tableName)
           
Dim gb As New GroupTableBuilder("临时统计表", dt)
           
For Each gcol As String In groupCols '处理分组列
               
Dim parts() As String = gcol.Split("|"c)
               
Dim colName As String = parts(0) '列名
               
If dt.DataCols(colName).IsDate Then '如果日期列,根据工具定义:日期列格式严格按照'列名|日期分组颗粒度|新列名'
                   
Dim groupMode As DateGroupEnum = DateGroupEnum.Month '默认按月分组
                   
If parts.Length > 1 Then [Enum].TryParse(Of DateGroupEnum)(parts(1), True, groupMode) '出于健壮考虑,还是判断一下
                   
Dim aliasName As String = If(parts.Length > 2, parts(2), "") '新列名,新列名为空会忽略
                    gb.Groups.AddDef(colName, groupMode, aliasName)
               
Else '非日期列
                   
If parts.Length = 1 Then
                        gb.Groups.AddDef(colName)
                   
ElseIf parts.Length > 1 Then
                        gb.Groups.AddDef(colName, parts(1))
'指定了新列名
                   
End If
               
End If
           
Next
           
For Each aggItem As String In aggCols '处理统计列
               
Dim parts() As String = aggItem.Split("|"c)
               
Dim colName As String = parts(0) '列名
               
Dim aggMethod As String = If(parts.Length > 1, parts(1).ToLower(), "sum") '统计类型
               
Dim aliasName As String = If(parts.Length > 2, parts(2), "") '新列名,新列名为空会忽略
               
Dim aggType As AggregateEnum = AggregateEnum.Sum
               
If aggMethod > "" Then
                    [Enum].TryParse(
Of AggregateEnum)(aggMethod, True, aggType)
               
End If
                gb.Totals.AddDef(colName, aggType, aliasName)
           
Next
           
If targs.ContainsKey("filter") Then gb.Filter = targs("filter").ToString()
           
If targs.ContainsKey("same_period_growth") Then gb.SamePeriodGrowth = targs("same_period_growth").ToObject(Of Boolean)()
           
If targs.ContainsKey("circle_growth") Then gb.CircleGrowth = targs("circle_growth").ToObject(Of Boolean)()
           
If targs.ContainsKey("grand_proportion") Then gb.GrandProportion = targs("grand_proportion").ToObject(Of Boolean)()
           
If targs.ContainsKey("group_proportion") Then gb.GroupProportion = targs("group_proportion").ToObject(Of Boolean)()
           
Dim resultTable As DataTable = gb.Build(True)
           
Dim resultJson As New JObject
           
' 列名数组
           
Dim colNames As New JArray
           
For Each dc As DataCol In resultTable.DataCols
                colNames.Add(dc.Caption)
'用的列标题代替列名称,因为统计表的列名可能不具备实际意义
           
Next
            resultJson(
"columns") = colNames
           
'行数据(最多给AI返回1000行)
           
Dim rows As New JArray
           
Dim maxRows As Integer = Math.Min(resultTable.dataRows.Count, 1000)
           
For r As Integer = 0 To maxRows - 1
               
Dim dr As DataRow = resultTable.DataRows(r)
               
Dim rowArray As New JArray
               
For Each dc As DataCol In resultTable.DataCols
                   
If dr.IsNull(dc) Then
                        rowArray.Add(
Nothing)
                   
Else
                        rowArray.Add(dr(dc.Name))
                   
End If
               
Next
                rows.Add(rowArray)
           
Next
            resultJson(
"data") = rows
           
Return resultJson.ToString()
 


本页地址:http://www.foxtable.com/webhelp/topics/7218.htm