(保姆级)在react中使用openAI

之前尝试过和后端小伙伴一起写过openAI的网站,最近研究了一下openAI在react的包,发现真的非常适合入门,而且不需要再求着后端小伙伴给你写接口了,自己在前端就能用

+开箱即用,简单易上手

+可以实现绝大部分你可以想到的openAI使用场景

+也可以直接在node中使用,编写openAI的后端接口

- 需要申请key

- 在线上需要后端传递key密文,防止key在浏览器泄露

openAI和chatGpt的关系?

openAI是时下最火的人工智能研究机构,而chatGpt是旗下的一个大语言模型,本文的演示使用了openAI的包,而语言模型选用了chatGpt3.5。

一、前置准备

1.申请apiKey

奇迹和魔法不是免费的,想要使用openAI首先得在官网注册并购买key OpenAI,建议大家买最便宜的就可以,需要绑定海外银行卡支付。同时十分推荐在国内的购物网站看看有没有key售卖(大概一个key也就10块钱不到,网上偶尔又会有富哥放一些免费的key)

2.搭建工程

这里照例使用umi,大家也可以用自己喜欢的框架

yarn create umi

3.下载openAI的npm

openai - npm

yarn add openai

二、尝试写一个简单的问答吧

我们首先新建一个openai的对象,这个实例对象可以直接用来请求外部接口,也是开箱即用的(甚至不需要考虑本地代理的问题,也不需要用复杂的axios,厉害吧)

import OpenAI from 'openai';

//这里写你的sdk
const sdk = 'sk-XXX'

//新建了一个OpenAI对象
const openai = new OpenAI({
    apiKey: sdk,
    dangerouslyAllowBrowser: true// 这个属性可以让你讲sdk写在前端工程中(本地测试用可以,线上环境不要这样用)
  });

在线上环境,我们一般通过后端接口,密文传输key,也可以和用户信息结合传递不同的key

之后我们写一个输入框,一个发送按钮,和展示AI返回内容的块

 return (
    <>
      <Input
        type="text"
        style={{ width: '300px' }}
        onChange={(e) => setValue(e.target.value)}
      />
      <Button type={'primary'} onClick={() => {
        callChatGPT(value)
      }}>发送</Button>
      <p>{chatHistory|| ''}</p>
    </>
  );

简单到甚至都不想写样式

之后我们对发送按钮写ai逻辑,具体可以看注释,注意使用该接口需要可以连接到api.openai.com

  const callChatGPT = async (inputText: string) => {
    console.log('inputText', inputText)
    //通过await,通过请求拿到openai的返回
    const chatCompletion: any = await openai.chat.completions.create({
      messages: [{ role: 'user', content: inputText }],//传递我们的输入框内容
      model: 'gpt-3.5-turbo',//根据你申请的key类型,选择合适的模型,一般都为3.5
    });
    console.log('chatCompletion', chatCompletion)
    setChatHistory(chatCompletion?.choices?.[0].message?.content || [])
  };

恭喜你迈出了第一步

完整代码如下

import { PageContainer } from '@ant-design/pro-components';
import { useState } from 'react';
import { Button, Input } from 'antd';
import OpenAI from 'openai';

const sdk = 'sk-XXX'

const HomePage: React.FC = () => {
  const [chatHistory, setChatHistory] = useState('');
  const [value, setValue] = useState('');
  const openai = new OpenAI({
    apiKey: sdk,
    dangerouslyAllowBrowser: true
  });
  const callChatGPT = async (inputText: string) => {
    console.log('inputText', inputText)
    //通过await,通过请求拿到openai的返回
    const chatCompletion: any = await openai.chat.completions.create({
      messages: [{ role: 'user', content: inputText }],
      model: 'gpt-3.5-turbo',//根据你申请的key类型,选择合适的模型,一般都为3.5
    });
    console.log('chatCompletion', chatCompletion)
    setChatHistory(chatCompletion?.choices?.[0].message?.content || [])
  };

 return (
    <>
      <Input
        type="text"
        style={{ width: '300px' }}
        onChange={(e) => setValue(e.target.value)}
      />
      <Button type={'primary'} onClick={() => {
        callChatGPT(value)
      }}>发送</Button>
      <p>{chatHistory|| ''}</p>
    </>
  );

export default HomePage;

三、太慢了!能不能逐字输出

chatGpt在回答一些问题的时候会耗时非常长,如果等回答完再完成接口的话我们的用户早就不耐烦了,可以考虑采用流式传输,逐字输出

一般现在主流的chatGpt网站都是采用的流式传输,具体实现原理可以参考我之前写的一篇react实现类GhatGPT逐字输出效果-CSDN博客

这里直接贴出代码,原理是通过监听去渲染页面

import { useState } from 'react';
import { Button, Input, message } from 'antd';
import OpenAI from 'openai';

const sdk = 'sk-XXX'

const HomePage: React.FC = () => {
  const [value, setValue] = useState('');
  const [output, setOutput] = useState('');
  const openai = new OpenAI({
    apiKey: sdk,
    dangerouslyAllowBrowser: true
  });
  const callChatGPT = async (inputText: string) => {
    console.log('inputText', inputText)
    //这里定义接口为流式传输
    const stream = await openai.beta.chat.completions.stream({
      model: 'gpt-3.5-turbo',
      messages: [{ role: 'user', content: inputText }],
      stream: true,
    });

    stream.on('content', (delta, snapshot) => {
      //在这里即可在每次接受到流式时就可以
      console.log('delta', delta)
      //通过TextDecoder解析出字符串
      //触发setState,触发渲染
      setOutput(prevState => prevState + delta)
    });
  };

  return (
    <>
      <Input
        type="text"
        style={{ width: '300px' }}
        onChange={(e) => setValue(e.target.value)}
      />
      <Button type={'primary'} onClick={() => {
        callChatGPT(value)
      }}>发送</Button>
      <div><p>{output}</p></div>
    </>
  );
};

export default HomePage;

可以看到实现了非常流畅的流式传输效果

react的escape机制?

react的监听函数需要小心使用,stream.on是监听函数,挂载的作用域和页面的作用域是不一致的

四、对话

到这里已经可以实现绝大部分的gpt功能了,但是目前还是一问一答,没什么技术含量,要实现多语句的对话,需要把之前的所有对话传输进去

相当于每一次发送请求的时候,我们的所有之前对话也要装进message数组,传给chatGpt(到后面会越传越多,gpt会自己读取上下文,给出更合语境的解答)

import { PageContainer } from '@ant-design/pro-components';
import { useCallback, useEffect, useState } from 'react';
import { Button, Input, message } from 'antd';
import OpenAI from 'openai';

const sdk = 'sk-XXX'

const HomePage: React.FC = () => {
  const [chatHistory, setChatHistory] = useState<any>([]);//对话的列表
  const [value, setValue] = useState('');
  const openai = new OpenAI({
    apiKey: sdk,
    dangerouslyAllowBrowser: true
  });

  const callChatGPT = async (inputText: string) => {
    console.log('inputText', inputText)
    const tempList = [...chatHistory]
    //将用户的语句装载进去,role使用为user
    tempList.push({ role: 'user', content: inputText })
    console.log('tempList', tempList)
    setChatHistory(tempList)
  };

  useEffect(() => {
    //当对话数组的最后一条为对象时,触发接口,传递给chatGpt
    if (chatHistory[chatHistory?.length - 1]?.role === 'user') {
      openai.chat.completions.create({
        messages: chatHistory,//这里传递所有之前的对话
        model: 'gpt-3.5-turbo',
      }).then((res: any) => {
        const tempList = [...chatHistory]
        tempList.push(res?.choices[0].message)
        console.log('tempList', tempList)
        setChatHistory(tempList)
      });
    }
  }, [chatHistory])
  return (
    <>
      <Input
        type="text"
        style={{ width: '300px' }}
        onChange={(e) => setValue(e.target.value)}
      />
      <Button type={'primary'} onClick={() => {
        callChatGPT(value)
      }}>发送</Button>
      <div>
        //一条一条的渲染,就像是对话一样
        {chatHistory?.map((message: any, index: number) => (
          <p style={{ margin: '10px', }} key={index}>{message?.content || ''}</p>
        ))}
      </div>
    </>
  );
};

export default HomePage;

你可以将代码中的实现改为流式传输,试试看!

我们可以看到gpt已经在和我们愉快的交流了

现在,你已经实现了绝大部分的语言模型功能,很神奇吧,你可以将它改造成最适合自己使用的状态,或者打磨一下发到线上让更多的人使用到

chatGpt的token

chatGpt会根据token来分隔我们的语句,一般而言对于英文语句,chatGpt会分隔每个词作为token,并对每个token进行逐个分析,而对于中文大部分都是逐个汉字进行分析的。

其中,prompt_tokens是我们的提示词,而completion_tokens即gpt的回答语句。

五、画一画自己喜欢的图

openAI不光是有chatGpt和我们聊天,我们也可以使用ai进行画画自己喜欢的图片

我们可以使用 openai.images接口,直接生成图片

  const createImage = (value: string) => {
    openai.images.generate({
      prompt: value,//用户的提示词
      size: '512x512',//图片尺寸
      response_format: 'url',//通过url的方式进行返回
      n: 1//生成一张图片
    }).then((res: any) => {
      console.log('res', res?.data?.[0]?.url)
      setImgUrl(res?.data?.[0]?.url)
    })
  }

同时可以试试createVariation接口,生成相似图片

目前这个绘图的api支持更换模型(但是只能用线上的模型)相对于专业的绘图接口来说简陋一点,但是完成大部分场景还是可以的

当你完成了上面的教程后,你已经是入门openAI了,接下来你可以去看看源码文档查看其他调用方法,或者尝试在node中实现上面的功能(需要注意node不能直接import ES6模块)

参考文献

openAI的npm仓库openai - npm

 openAI的gitlhub工程地址(里面有文档)https://github.com/openai/openai-node