本帖最后由 dexteryao 于 2023-7-7 10:36 编辑
在上一篇文章中介绍了SpreadJS中集成ChatGPT的一些场景以及演示效果,本文讲介绍其中主要功能的实现。
一. OpenAI 接口调用
(OpenAI的注册和apiKey的获取这里就不赘述了)
OpenAI 提供了Node.js library,可以通过npm直接安装。
为了快速验证Demo,直接在VUE项目前端页面中使用openai的npm库,经过测试在网页中也可以正常使用。(正常项目中,这部分应该在服务端实现。)
对于单一提问创建一个Completion即可,Completion.data.choices会返回对应的信息,以下是官方提供的调用示例,需要设置apiKey
- const { Configuration, OpenAIApi } = require("openai");
- const configuration = new Configuration({
- apiKey: process.env.OPENAI_API_KEY,
- });
- const openai = new OpenAIApi(configuration);
- const completion = await openai.createCompletion({
- model: "text-davinci-003",
- prompt: "Hello world",
- });
- console.log(completion.data.choices[0].text);
复制代码 对于对话聊天,需要使用createChatCompletion
- openai.createChatCompletion({
- model:"gpt-3.5-turbo",
- messages:[
- {"role": "system", "content": "You are a helpful assistant."},
- {"role": "user", "content": "Who won the world series in 2020?"},
- {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
- {"role": "user", "content": "Where was it played?"}
- ]})
复制代码 OpenAI是不记录数据的,每一次请求时需要将之前的聊天记录全部放到messages中。
在messages中有三个角色system、user和assistant。简单来说
user: 代表用户发的消息,即用户的提问或者指令
assistant: 就是GPT,需要将GPT上一次的回复或者反馈加入到messages中
system: 代表系统或者客户端通过代码发出的消息。系统消息的目的是给 assistant 提供进一步的指导或者指示,从而让 assistant 作出更好的响应。
一次对话可以从system开始,指导此次对话的主题,然后交替 user 和 assistant 类型的消息。
可以看到createCompletion和createChatCompletion使用了text-davinci-003和gpt-3.5-turbo两个不同的model,那两者有何异同。
text-davinci-003和gpt-3.5-turbo都是OpenAI的语言模型,它们都是基于GPT-3的。text-davinci-003是GPT-3的改进版,支持更长的上下文窗口(即4097个标记),并且使用了更新的数据集 。gpt-3.5-turbo是在text-davinci-003的基础上进行了改进,针对聊天应用进行了优化 。 从效果来看,gpt-3.5-turbo比text-davinci-003更优秀。而且,gpt-3.5-turbo的API价格只有text-davinci-003的1/10 。
从官方描述中gpt-3.5-turbo更强大(强大但是便宜?),但是在实际测试中,对于类似生成公式这样的明确指令,text-davinci-003表现的更好,问题回复简洁直接,便于程序分析使用,而针对聊天优化的gpt-3.5-turbo有时还会返回“请稍等”的反馈。
(2023年7月7日更新:可以通过更好的prompt和function calling解决这些问题)
二. 创建GPT公式
对于OpenAI的请求是异步的,这里需要使用SpreadJS的异步函数来创建自定义的GPT公式。对于返回一个值的公式可以直接设置结果,如果返回的是区域数据,可以使用动态数组
动态数组功能在SpreadJS中是默认关闭的,需要设置开启
- workbook.options.allowDynamicArray = true;
复制代码
以GPT.FILTER公式为例,演示通过使用OpenAI对于选择区域的数据进行过滤,公式接收两个参数,选择区域和过滤条件的文字描述
设置acceptsReference配置公式参数是否是引用区域。
- var GPT_Filter = function () {};
- GPT_Filter.prototype = new GC.Spread.CalcEngine.Functions.AsyncFunction('GPT.FILTER', 2, 2, {
- description: "对选择的数据区域做描述行的过滤",
- parameters: [
- {
- name: "数据区域"
- },
- {
- name: "过滤条件描述"
- }]});
- GPT_Filter.prototype.defaultValue = function () { return 'Loading...'; };
- GPT_Filter.prototype.acceptsArray = function () {
- return true;
- }
- GPT_Filter.prototype.acceptsReference = function (argIndex) {
- return true;
- }
- GPT_Filter.prototype.evaluateAsync = function (context, range, desc) {
- if (!range || !desc) {
- return GC.Spread.CalcEngine.Errors.NotAvailable;
- }
-
- let tempArray = range.toArray && range.toArray();
- if (!Array.isArray(tempArray)) {
- return GC.Spread.CalcEngine.Errors.NotAvailable;
- }
- const response = openai.createCompletion({
- model: "text-davinci-003",
- prompt: "对最后的JSON数据,过滤" + desc + ",返回和原来结构一样的双层JSON数组。\n" + JSON.stringify(tempArray),
- max_tokens: 500,
- temperature: 0.5
- });
- response.then(function(completion){
- let array = JSON.parse(completion.data.choices[0].text.trim())
- context.setAsyncResult(new GC.Spread.CalcEngine.CalcArray(array));
- })
- };
复制代码 在调用OpenAI时,将公式引用区域的数据转换成二维数组的文本,同时通过文字描述,让api返回一个同样的二维数据json
在接受到json后parse成Arary,最后创建动态数组对象,设置到单元格中。
⚠️这里需要注意一点,不要在evaluateAsync中使用await,await会将promise对象直接return,而异步函数只有接收不到return值才会显示defaultValue。
3. 其他场景
对于数据分析建议,和Filter类似,将数据stringfly后提交OpenAI,除了用二维数组,也可以通过getCsv获取带有换行的表格数据。
具体的Demo可以参考附件
spreadjs-chatgpt.zip
(31.71 KB, 下载次数: 440)
|
|