AIGC
AI 提问技巧
为了让 Al 更好地理解我们的输入,并给出预期精确的输出,需要严格控制我们的提问词。
1.使用系统预设 + 控制输入格式(便于Al精确地理解我们的需求)
你是一个数据分析师和前端开发专家,接下来我会按照以下固定格式给你提供内容:
分析需求:
{数据分析的需求或者目标}
原始数据:
{csv格式的原始数据,用,作为分隔符}
请根据以上内容,帮我生成数据分析结论和可视化图表代码
2.控制输出格式(便于 AI 返回的内容能够更加方便地为我们所用)
你是一个数据分析师和前端开发专家,接下来我会按照以下固定格式给你提供内容:
分析需求:
{数据分析的需求或者目标}
原始数据:
{csv格式的原始数据,用,作为分隔符}
请根据这两部分内容,按照以下指定格式生成内容(此外不要输出任何多余的开头、结尾、注释)
----------------------------------------------------------------------------------
{前端 Echarts V5 的 option 配置对象js代码,合理地将数据进行可视化,不要生成任何多余的内容,比如注释}
----------------------------------------------------------------------------------
{明确的数据分析结论、越详细越好,不要生成多余的注释}
----------------------------------------------------------------------------------
{
title: {
text: '网站用户增长情况',
subtext: ''
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {
data: ['用户数']
},
xAxis: {
data: ['1号', '2号', '3号']
},
yAxis: {},
series: [{
name: '用户数',
type: 'bar',
data: [10, 20, 30]
}]
}
----------------------------------------------------------------------------------
根据数据分析可得,该网站用户数量逐日增长,时间越长,用户数量增长越多。
3.指定一个示例问答,one-shot 或者 few-shot
one-shot:给 AI 一轮示例问答
few-shot:给 AI 多轮示例问答
之后就可以慢慢训练这个模型,哪里不对就在预设和提示词里限制,训练成功后再放入示例。
如何调用AI
1.调用官方接口
比如 OpenAI 或者其他 AI 原始大模型官网的接口 👉 官方文档
- 优点:不经封装,最灵活,最原始
- 缺点:要钱、要魔法
本质上 OpenAI 就是提供了 HTTP 接口,我们可以用任何语言去调用
1)在请求头中指定 OPENAI_API_KEY → Authorization: Bearer OPENAI_API_KEY。
2)找到你要使用的接口,比如 AI 对话接口。
3)按照接口文档的示例,构造 HTTP 请求,比如用 Hutool 工具类、或者 HTTPClient。
/**
* AI 对话(需要自己创建请求响应对象)
*
* @param request
* @param openAiApiKey
* @return
*/
public CreateChatCompletionResponse createChatCompletion(CreateChatCompletionRequest request, String openAiApiKey) {
if (StringUtils.isBlank(openAiApiKey)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "未传 openAiApiKey");
}
String url = "https://api.openai.com/v1/chat/completions";
String json = JSONUtil.toJsonStr(request);
String result = HttpRequest.post(url)
.header("Authorization", "Bearer " + openAiApiKey)
.body(json)
.execute()
.body();
return JSONUtil.toBean(result, CreateChatCompletionResponse.class);
}
最终结果可能返回以下内容:
2 使用云服务商提供的封装接口
比如:Azure 云(微软官方)
- 优点:本地都能用
- 缺点:依然要钱,而且可能比直接调用原始的接口更贵
3 鱼聪明 AI 开放平台
使用星球 AI 助手 —— 鱼聪明 AI 或 移动端搜索公众号【鱼聪明AI】。
鱼聪明 AI 提供现成的 SDK,让用户更便捷地利用 AI 能力。
具体调用方式可查看:--- SDK项目文档
- 优点:目前不要钱,而且有很多现成的模型(prompt 系统预设)给大家用
- 缺点:不完全灵活,但是可以定义自己的模型
1.到上面的文档里引入依赖
2.在application.yml填加上你的accessKey和secretKey
3.创建一个类,注入client,并写入模型Id
package com.yupi.springbootinit.manager;
import com.yupi.springbootinit.common.ErrorCode;
import com.yupi.springbootinit.exception.BusinessException;
import com.yupi.yucongming.dev.client.YuCongMingClient;
import com.yupi.yucongming.dev.common.BaseResponse;
import com.yupi.yucongming.dev.model.DevChatRequest;
import com.yupi.yucongming.dev.model.DevChatResponse;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* 用于对接 AI 平台
*/
@Service
public class AiManager {
@Resource
private YuCongMingClient yuCongMingClient;
/**
* AI 对话
*
* @param message
* @return
*/
public String doChat(String message) {
// 第三步,构造请求参数
DevChatRequest devChatRequest = new DevChatRequest();
// 模型id,尾后加L,转成long类型
devChatRequest.setModelId(1651468516836098050L);
devChatRequest.setMessage(message);
// 第四步,获取响应结果
BaseResponse<DevChatResponse> response = yuCongMingClient.doChat(devChatRequest);
// 如果响应为null,就抛出系统异常,提示“AI 响应错误”
if (response == null) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "AI 响应错误");
}
return response.getData().getContent();
}
}
然后创建一下测试类,按你预期设定的格式输入数据,便可以调用AI了
4.封装返回类接收
package com.ptu.api.model.vo;
import lombok.Data;
@Data
public class BiResponse {
/**
* 生成的图表代码
*/
private String genChart;
/**
* 生成的分析结论
*/
private String genResult;
/**
* 图表id
*/
private Long chartId;
}
5.编写代码
之前我们是拼接用户的输入,现在可以对之前的输入方式进行优化;
通过设定一个提示语(prompt)的方式来输入用户信息,而不必自己编写提示语。
这种优化方式使得我们可以直接调用现有模型来进行处理。
注入刚刚封装好的对象,
// 指定一个模型id(把id写死,也可以定义成一个常量)
long biModelId = 1659171950288818178L;
/*
* 用户的输入(参考)
分析需求:
分析网站用户的增长情况
原始数据:
日期,用户数
1号,10
2号,20
3号,30
* */
// 构造用户输入
StringBuilder userInput = new StringBuilder();
userInput.append("分析需求:").append("\n");
// 拼接分析目标
String userGoal = goal;
// 如果图表类型不为空
if (StringUtils.isNotBlank(chartType)) {
// 就将分析目标拼接上“请使用”+图表类型
userGoal += ",请使用" + chartType;
}
userInput.append(userGoal).append("\n");
userInput.append("原始数据:").append("\n");
// 压缩后的数据(把multipartFile传进来)
String csvData = ExcelUtils.excelToCsv(multipartFile);
userInput.append(csvData).append("\n");
// 拿到返回结果
String result = aiManager.doChat(biModelId,userInput.toString());
// 对返回结果做拆分,按照5个中括号进行拆分
String[] splits = result.split("【【【【【");
// 拆分之后还要进行校验
if (splits.length < 3) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR,"AI 生成错误");
}
String genChart = splits[1].trim();
String genResult = splits[2].trim();
// 插入到数据库
Chart chart = new Chart();
chart.setName(name);
chart.setGoal(goal);
chart.setChartData(csvData);
chart.setChartType(chartType);
chart.setGenChart(genChart);
chart.setGenResult(genResult);
chart.setUserId(loginUser.getId());
boolean saveResult = chartService.save(chart);
ThrowUtils.throwIf(!saveResult, ErrorCode.SYSTEM_ERROR, "图表保存失败");
BiResponse biResponse = new BiResponse();
biResponse.setGenChart(genChart);
biResponse.setGenResult(genResult);
biResponse.setChartId(chart.getId());
return ResultUtils.success(biResponse);
6总结
- 构造用户请求(用户消息、csv 数据、图表类型)
- 调用鱼聪明 sdk,得到 AI 响应结果
- 从 AI 响应结果中,取出需要的信息
- 保存图表到数据库
前端开发
前端就大概过一下,不深究
1.开发用户表单
修改路由
把主页重定向至/add_chart
,将/add_chart
指定到/AddChart
。
复用写好的页面
复制User目录
,粘贴至src
→pages目录
下;
并改名为AddChart
,把其他文件删掉,留下index.tsx
。
对index.tsx
进行修改,删掉多余的内容。(比如::获取用户信息、处理提交.....)
import { listChartByPageUsingPOST } from '@/services/yubi/chartController';
import { useModel } from '@umijs/max';
import React, { useEffect, useState } from 'react';
const Login: React.FC = () => {
const [type, setType] = useState<string>('account');
const { setInitialState } = useModel('@@initialState');
useEffect(() => {
listChartByPageUsingPOST({}).then((res) => {
console.error('res', res);
});
});
return (
// 把页面内容指定一个类名add-chart
<div className="add-chart">
</div>
);
};
export default Login;
去官网找模板
怎么去开发表单?去 ant.design 组件库 找一个表单组件;
我们需要用到多行输入(你的目标)、下拉输入(图表类型)、文件上传(原始数据)三个组件。
把这些内容放到return里面的div标签里
这部分内容放在return的外面(点击提交后的业务六级)
onFinishconst onFinish = (values: any) => {
console.log('Received values of form: ', values);
};
简化内容
删除不必要的代码。
(比如:表格布局、样式,最大宽度,普通输入框、slider 滚动选项...)
import { listChartByPageUsingPOST } from '@/services/yubi/chartController';
import { UploadOutlined } from '@ant-design/icons';
import { useModel } from '@umijs/max';
import { Button, Form, Select, Space, Upload } from 'antd';
import TextArea from 'antd/es/input/TextArea';
import React, { useEffect, useState } from 'react';
const Login: React.FC = () => {
const [type, setType] = useState<string>('account');
const { setInitialState } = useModel('@@initialState');
useEffect(() => {
listChartByPageUsingPOST({}).then((res) => {
console.error('res', res);
});
});
const onFinish = (values: any) => {
console.log('Received values of form: ', values);
};
return (
// 把页面内容指定一个类名add-chart
<div className="add-chart">
<Form
// 表单名称改为addChart
name="addChart"
onFinish={onFinish}
// 初始化数据啥都不填,为空
initialValues={{ }}
>
<Form.Item name="rate" label="Rate">
<TextArea />
</Form.Item>
<Form.Item
name="select"
label="Select"
hasFeedback
rules={[{ required: true, message: 'Please select your country!' }]}
>
<Select placeholder="Please select a country">
<Option value="china">China</Option>
<Option value="usa">U.S.A</Option>
</Select>
</Form.Item>
<Form.Item
name="upload"
label="Upload"
valuePropName="fileList"
extra="longgggggggggggggggggggggggggggggggggg"
>
<Upload name="logo" action="/upload.do" listType="picture">
<Button icon={<UploadOutlined />}>Click to upload</Button>
</Upload>
</Form.Item>
<Form.Item wrapperCol={{ span: 12, offset: 6 }}>
<Space>
<Button type="primary" htmlType="submit">
Submit
</Button>
<Button htmlType="reset">reset</Button>
</Space>
</Form.Item>
</Form>
</div>
);
};
export default Login;
编写前端代码
import { listChartByPageUsingPOST } from '@/services/yubi/chartController';
import { UploadOutlined } from '@ant-design/icons';
import { useModel } from '@umijs/max';
import { Button, Form, Input, Select, Space, Upload } from 'antd';
import TextArea from 'antd/es/input/TextArea';
import React, { useEffect, useState } from 'react';
const Login: React.FC = () => {
const [type, setType] = useState<string>('account');
const { setInitialState } = useModel('@@initialState');
useEffect(() => {
listChartByPageUsingPOST({}).then((res) => {
console.error('res', res);
});
});
const onFinish = (values: any) => {
// 看看能否得到用户的输入
console.log('表单内容: ', values);
};
return (
// 把页面内容指定一个类名add-chart
<div className="add-chart">
<Form
// 表单名称改为addChart
name="addChart"
onFinish={onFinish}
// 初始化数据啥都不填,为空
initialValues={{ }}
>
{/* 前端表单的name,对应后端接口请求参数里的字段,
此处name对应后端分析目标goal,label是左侧的提示文本,
rules=....是必填项提示*/}
<Form.Item name="goal" label="分析目标" rules={[{ required: true, message: '请输入分析目标!' }]}>
{/* placeholder文本框内的提示语 */}
<TextArea placeholder="请输入你的分析需求,比如:分析网站用户的增长情况"/>
</Form.Item>
{/* 还要输入图表名称 */}
<Form.Item name="name" label="图表名称">
<Input placeholder="请输入图表名称" />
</Form.Item>
{/* 图表类型是非必填,所以不做校验 */}
<Form.Item
name="selchartTypeect"
label="图表类型"
>
<Select
options={[
{ value: '折线图', label: '折线图' },
{ value: '柱状图', label: '柱状图' },
{ value: '堆叠图', label: '堆叠图' },
{ value: '饼图', label: '饼图' },
{ value: '雷达图', label: '雷达图' },
]}
/>
</Form.Item>
{/* 文件上传 */}
<Form.Item
name="file"
label="原始数据"
>
{/* action:当你把文件上传之后,他会把文件上传至哪个接口。
这里肯定是调用自己的后端,先不用这个 */}
<Upload name="file">
<Button icon={<UploadOutlined />}>上传 CSV 文件</Button>
</Upload>
</Form.Item>
<Form.Item wrapperCol={{ span: 12, offset: 6 }}>
<Space>
<Button type="primary" htmlType="submit">
提交
</Button>
<Button htmlType="reset">重置</Button>
</Space>
</Form.Item>
</Form>
</div>
);
};
export default Login;
对接后端
使用openAPI生成后端接口,并且把src/services/ant-design-pro删掉,对应的引用也删掉
page下的Tablelist和对应的路由也删掉
代码
import { genChartByAiUsingPOST } from '@/services/yubi/chartController';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Form, Input, message, Select, Space, Upload } from 'antd';
import TextArea from 'antd/es/input/TextArea';
import React from 'react';
/**
* 添加图表页面
* @constructor
*/
// 把多余的状态删掉,页面名称改为AddChart
const AddChart: React.FC = () => {
/**
* 提交
* @param values
*/
const onFinish = async (values: any) => {
// 对接后端,上传数据
const params = {
...values,
file: undefined,
};
try {
// 需要取到上传的原始数据file→file→originFileObj(原始数据)
const res = await genChartByAiUsingPOST(params, {}, values.file.file.originFileObj);
// 正常情况下,如果没有返回值就分析失败,有,就分析成功
if (!res?.data) {
message.error('分析失败');
} else {
message.success('分析成功');
}
// 异常情况下,提示分析失败+具体失败原因
} catch (e: any) {
message.error('分析失败,' + e.message);
}
};
.....此处省略,代码不变.....
export default AddChart;
生成图表
使用库 —— ECharts
根据文档提示,使用命令进行安装。
npm install echarts-for-react
使用ECharts和优化前端代码
import { genChartByAiUsingPOST } from '@/services/yubi/chartController';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Card, Col, Divider, Form, Input, message, Row, Select, Space, Spin, Upload } from 'antd';
import TextArea from 'antd/es/input/TextArea';
import React, { useState } from 'react';
import ReactECharts from 'echarts-for-react';
/**
* 添加图表页面
* @constructor
*/
// 把多余的状态删掉,页面名称改为AddChart
const AddChart: React.FC = () => {
// 定义状态,用来接收后端的返回值,让它实时展示在页面上
const [chart, setChart] = useState<API.BiResponse>();
const [option, setOption] = useState<any>();
// 提交中的状态,默认未提交
const [submitting, setSubmitting] = useState<boolean>(false);
/**
* 提交
* @param values
*/
const onFinish = async (values: any) => {
// 如果已经是提交中的状态(还在加载),直接返回,避免重复提交
if (submitting) {
return;
}
// 当开始提交,把submitting设置为true
setSubmitting(true);
// 如果提交了,把图表数据和图表代码清空掉,防止和之前提交的图标堆叠在一起
// 如果option清空了,组件就会触发重新渲染,就不会保留之前的历史记录
setChart(undefined);
setOption(undefined);
// 对接后端,上传数据
const params = {
...values,
file: undefined,
};
try {
// 需要取到上传的原始数据file→file→originFileObj(原始数据)
const res = await genChartByAiUsingPOST(params, {}, values.file.file.originFileObj);
// 正常情况下,如果没有返回值就分析失败,有,就分析成功
if (!res?.data) {
message.error('分析失败');
} else {
message.success('分析成功');
// 解析成对象,为空则设为空字符串
const chartOption = JSON.parse(res.data.genChart ?? '');
// 如果为空,则抛出异常,并提示'图表代码解析错误'
if (!chartOption) {
throw new Error('图表代码解析错误')
// 如果成功
} else {
// 从后端得到响应结果之后,把响应结果设置到图表状态里
setChart(res.data);
setOption(chartOption);
}
}
// 异常情况下,提示分析失败+具体失败原因
} catch (e: any) {
message.error('分析失败,' + e.message);
}
// 当结束提交,把submitting设置为false
setSubmitting(false);
};
return (
// 把页面内容指定一个类名add-chart
<div className="add-chart">
{/* 变成两列 gutter列与列之间的间隔*/}
<Row gutter={24}>
{/* 表单放在第一列,卡片组件里 */}
<Col span={12}>
<Card title="智能分析">
<Form
// 表单名称改为addChart
name="addChart"
// label标签的文本对齐方式
labelAlign="left"
// label标签布局,同<Col>组件,设置 span offset 值,如 {span: 3, offset: 12}
labelCol={{ span: 4 }}
// 设置控件布局样式
wrapperCol={{ span: 16 }}
onFinish={onFinish}
// 初始化数据啥都不填,为空
initialValues={{ }}
>
{/* 前端表单的name,对应后端接口请求参数里的字段,
此处name对应后端分析目标goal,label是左侧的提示文本,
rules=....是必填项提示*/}
<Form.Item name="goal" label="分析目标" rules={[{ required: true, message: '请输入分析目标!' }]}>
{/* placeholder文本框内的提示语 */}
<TextArea placeholder="请输入你的分析需求,比如:分析网站用户的增长情况"/>
</Form.Item>
{/* 还要输入图表名称 */}
<Form.Item name="name" label="图表名称">
<Input placeholder="请输入图表名称" />
</Form.Item>
{/* 图表类型是非必填,所以不做校验 */}
<Form.Item
name="chartType"
label="图表类型"
>
<Select
options={[
{ value: '折线图', label: '折线图' },
{ value: '柱状图', label: '柱状图' },
{ value: '堆叠图', label: '堆叠图' },
{ value: '饼图', label: '饼图' },
{ value: '雷达图', label: '雷达图' },
]}
/>
</Form.Item>
{/* 文件上传 */}
<Form.Item
name="file"
label="原始数据"
>
{/* action:当你把文件上传之后,它会把文件上传至哪个接口。
这里肯定是调用自己的后端,先不用这个;
maxCount={1} 限制文件上传数量为1 */}
<Upload name="file" maxCount={1}>
<Button icon={<UploadOutlined />}>上传 CSV 文件</Button>
</Upload>
</Form.Item>
{/* offset设置和label标签一样的宽度,这样就能保持对齐;
其他占用的列设置成16 */}
<Form.Item wrapperCol={{ span: 16, offset: 4 }}>
<Space>
{/* 加个loading:就是把submitting的状态加入进来,
加个disabled:如果正在提交,就让这个按钮禁用,不允许重复点击*/}
<Button type="primary" htmlType="submit" loading={submitting} disabled={submitting}>
提交
</Button>
<Button htmlType="reset">重置</Button>
</Space>
</Form.Item>
</Form>
</Card>
</Col>
{/* 分析结论和图表放在第二列 */}
<Col span={12}>
<Card title="分析结论">
{/* 如果分析结论存在,就展示分析结论;
不存在则显示'请先在左侧进行提交' */}
{chart?.genResult ?? <div>请先在左侧进行提交</div>}
{/* 提交中,还未返回结果,分析结论就显示加载中的组件 */}
<Spin spinning={submitting}/>
</Card>
{/* 加一个间距 */}
<Divider />
<Card title="可视化图表">
{/* 如果它存在,才渲染这个组件 */}
{
// 后端返回的代码是字符串,不是对象,用JSON.parse解析成对象
option ? <ReactECharts option={option} /> : <div>请先在左侧进行提交</div>
}
{/* 提交中,还未返回结果,图表就显示加载中的组件 */}
<Spin spinning={submitting}/>
</Card>
</Col>
</Row>
</div>
);
};
export default AddChart;
主要代码:option ? <ReactECharts option={option} /> : <div>请先在左侧进行提交</div>
修改页面的名字name
和图标icon
ps.图标可以去组件库内挑选,点击图标就可复制。
最终成品:
后期会引入多线程、消息队列等机制来优化这个网站