dexteryao 发表于 2023-6-6 18:04:41

在SpreadJS集成ChatGPT - 代码篇

本帖最后由 dexteryao 于 2023-7-7 10:36 编辑

在上一篇文章中介绍了SpreadJS中集成ChatGPT的一些场景以及演示效果,本文讲介绍其中主要功能的实现。

一. OpenAI 接口调用
(OpenAI的注册和apiKey的获取这里就不赘述了)
OpenAI 提供了Node.js library,可以通过npm直接安装。
$ npm install openai为了快速验证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.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.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可以参考附件

代码也同步到了GitHub了
有兴趣可以关注https://github.com/SpreadJSHero已获取最新SpreadJS案例资源

dexteryao 发表于 2023-7-7 10:31:36

OpenAI官方建议:
Moving from text completions to chat completions
We introduced the Chat Completions API in March, and it now accounts for 97% of our API GPT usage.

The initial Completions API was introduced in June 2020 to provide a freeform text prompt for interacting with our language models. We’ve since learned that we can often provide better results with a more structured prompt interface. The chat-based paradigm has proven to be powerful, handling the vast majority of previous use cases and new conversational needs, while providing higher flexibility and specificity. In particular, the Chat Completions API’s structured interface (e.g., system messages, function calling) and multi-turn conversation capabilities enable developers to build conversational experiences and a broad range of completion tasks. It also helps lower the risk of prompt injection attacks, since user-provided content can be structurally separated from instructions.



从文本完成转向聊天完成
我们在 3 月份推出了 Chat Completions API,它现在占我们 API GPT 使用量的 97%。

最初的 Completions API 于 2020 年 6 月推出,旨在提供自由格式的文本提示,以便与我们的语言模型进行交互。 从那时起,我们了解到,通过更结构化的提示界面,我们通常可以提供更好的结果。 基于聊天的范例已被证明是强大的,可以处理绝大多数以前的用例和新的对话需求,同时提供更高的灵活性和特异性。 特别是,聊天完成 API 的结构化接口(例如系统消息、函数调用)和多轮对话功能使开发人员能够构建对话体验和广泛的完成任务。 它还有助于降低即时注入攻击的风险,因为用户提供的内容可以在结构上与指令分离。

页: [1]
查看完整版本: 在SpreadJS集成ChatGPT - 代码篇