gpt-4-1106-vision-preview 模型出来的时候我还在埋头撸代码,然后在公众号中看到了chatgpt已经开放了图片上传并识别对话的功能,想起年初的时候openai 的gpt4 放出来的时候,拍一张图片上传给它就可以帮你写一个简单的html页面的新闻
不得不感概,人间一天 AI百年啊,人工智能的飞速发展让我等码农感受到了威胁。
在使用chatgpt官网的页面的时候,我发现了它还有一个图片生成的模型 dall-e-3 单独用它时候发现生成的质量还不错,最重要的是它支持中文自然语言描述,mj的使用,在我看来还是不够人性化,它其中涉及到了大量专业参数,需要一定的使用门槛,如果opanai 的 dall-e-3模型持续
迭代的话,可能有一天就不太需要单独开通一个Midjourney账户了,回到正文,我在单独使用 dall-e-3 后,忘记了自己是在哪个模型下提问了,就误打误撞的让chatgpt4 来生成图片,没想到它也可以给我生成图片,这就引发了我的思考,这种模型调模型的方式是怎么实现的?
openai 的接口有没有提供过类似的功能? 该如何实现? 我们的AI机器人应用是不是也能集成进去?这样是不是就无需等待gpt4的进一步开放,我们都可以使用上最新的跟官网一样的功能了。
带着这些问题,我开始在社区上查找,啧啧,别说调用生成图片的例子了,连图文对话的客户端实现都没有看到有,放弃搜索开始琢磨openai 的文档,直到我看到这段代码
发生了什么??? 它在响应的结果返回这里,调用了一个本地函数?? 这是什么操作??
带着这个疑问,我重新用nestjs搭建了一个项目,前端的话,就用公司的chat-web项目吧,我也不准备再重新画一个页面了,准备用官方提供的sdk来重新接入下openai的接口
nestjs 是mvc架构后端nodejs服务框架,提供了一整套的后端开发解决方案,比较适合开发io密集型的服务,在海外相当流行,很多创业公司是用它来进行后端开发的,我在之前了解使用过,没想到它现在更完善了,如果你之前写过angler2 或者使用过spring-boot 的话,相信上手应该会很快
- 首先安装nestjs 的脚手架 npm i -g @nestjs/cli
- 创建项目nest new get_nest_server, 然后再安装下依赖
- 一切顺利,现在我的目录结构是这样的
4.再安装两个依赖 npm i openai https-proxy-agent, 其中 openai 是官方的sdk包,https-proxy-agent用于本地联调接口的时候代理用的
- 先来新建模块和service ,nest-cli 提供了快捷新建模块和service的命令行,先来扫一眼都有什么吧
嗯,很齐全,首先我们新建openai模块,nest g mo openai
不仅模块目录建好了,还顺带帮你引入到了app模块下,ok 我们继续执行,nest g service openai && nest g controller openai 执行完后你的openai目录大概会是像这样的,模块已经帮你自动引入了,是不是很方便
6.模块建好了,准备撸代码了,那就从service开始吧
其中最核心的还是 streamChat 方法,主要是用来进行对话的,这个方法里面的核心是这里
这便是我们实现流式对话的基础了,其他的方法都是用来辅助的
- 基本的流式对话实现了,但是在用流式对话的时候,注意不要去参照官方的来,他是非流式对话的,但是文档中也没有发现有流式对话的函数式调用例子,
怎么办呢?去官方的github仓库扫一眼,还真让我找到了
它在根目录下有一个examples 的目录,里面是各种调用的demo,里面有一个 function-call-stream.ts 的文件,这不就是我们想要的吗。ok,咱们先看看它做了啥
大致的意思就是它在代码中使用了函数式调用并模拟了db 的读取和搜索能力,主要是为了告诉我们,它有能力接入你自己的系统, 知道怎么用了后,一切都好办了,比葫芦画瓢,撸代码
ts
import { Injectable } from '@nestjs/common';
import { OpenAI } from 'openai';
import {
ChatCompletionChunk,
ChatCompletionMessageParam,
ChatCompletionMessage,
} from 'openai/resources/chat/completions';
import { HttpsProxyAgent } from 'https-proxy-agent';
import ChatMessageStreamDto from './dto/ChatMessageStreamDto';
import {
getCurrentWeather,
functions,
getCurStockByName,
getToutiao,
getLottery,
} from './functions';
@Injectable()
export class OpenaiService {
api = new OpenAI({
httpAgent: new HttpsProxyAgent(process.env.PROXY_URL),
apiKey: process.env.OPENAI_API_KEY,
});
constructor() {}
// 获取模型列表
async getModels() {
const list = await this.api.models.list();
return list.data;
}
// 流式对话
async streamChat(
input: ChatMessageStreamDto,
cb: (item: ChatCompletionChunk) => void,
) {
const messages: Array<ChatCompletionMessageParam> = [
{
role: 'user',
content: input.prompt,
},
];
const stream = this.api.chat.completions.create({
model: 'gpt-3.5-turbo',
stream: true,
messages: messages,` `
functions: functions,
});
let chatMessage = {} as ChatCompletionMessage;
for await (const chunk of await stream) {
cb && cb(chunk);
chatMessage = this.messageReducer(chatMessage, chunk);
}
messages.push(chatMessage);
if (chatMessage.function_call) {
const result = await this.callFunction(chatMessage.function_call);
const newMessage = {
role: 'function' as const,
name: chatMessage.function_call.name!,
content: JSON.stringify(result),
};
messages.push(newMessage);
const stream = this.api.chat.completions.create({
model: 'gpt-3.5-turbo',
stream: true,
messages: messages,
});
for await (const chunk of await stream) {
cb && cb(chunk);
chatMessage = this.messageReducer(chatMessage, chunk);
}
}
}
messageReducer(
previous: ChatCompletionMessage,
item: ChatCompletionChunk,
): ChatCompletionMessage {
const reduce = (acc: any, delta: any) => {
acc = { ...acc };
for (const [key, value] of Object.entries(delta)) {
if (acc[key] === undefined || acc[key] === null) {
acc[key] = value;
} else if (typeof acc[key] === 'string' && typeof value === 'string') {
(acc[key] as string) += value;
} else if (typeof acc[key] === 'object' && !Array.isArray(acc[key])) {
acc[key] = reduce(acc[key], value);
}
}
return acc;
};
return reduce(previous, item.choices[0]!.delta) as ChatCompletionMessage;
}
async callFunction(
function_call: ChatCompletionMessage.FunctionCall,
): Promise<any> {
const args = JSON.parse(function_call.arguments!);
switch (function_call.name) {
case 'get_current_weather':
return getCurrentWeather(args['location']);
case 'get_cur_stock_by_name':
return getCurStockByName();
case 'get_toutiao':
return getToutiao();
case 'get_lottery':
return getLottery();
default:
throw new Error('No function found');
}
}
}
差点忘记了,我们还有函数的定义,我自己准备了几个函数,连同给gpt的入参我也一并贴出来吧
ok 一切都准备好了,开始准备接口了,chat-web 前端有一个必须要给的接口,先定义出来
然后是对话的接口
![]( "研发学习指南 > GPT4 的函数调用是怎么使用的? > image2023-11-24_14-22-26.png")
对话接口里面长这样
ok 一切看起来都准备好了,前端配置下反向代理的接口地址,再改下接口路径
ok,现在开始测试我们定义的函数
至此。我们实现了四个基本的函数式调用,并且从返回的结果来看,似乎内容质量还可以。
让gpt来帮我总结下今天这篇文章吧
- OpenAI定价信息:我查阅了一些关于图片接口和新模型的定价信息,特别是关于gpt-4-1106-preview和gpt-4-1106-vision-preview模型的费用。
- DALL-E 3论文:我阅读了一篇有关DALL-E 3模型的研究论文,该论文讨论了这一模型如何生成图像及其相关技术。
- 函数调用接口文档:我翻阅了OpenAI官方提供的关于如何实现函数调用的接口文档。
- OpenAI GitHub示例:我在OpenAI的官方GitHub仓库中发现了一个名为function-call-stream.ts的示例文件,学习了如何利用OpenAI的SDK进行函数式流调用。
- Midjourney使用教程:我观看了一个介绍Midjourney使用的视频教程,这可能是一个提供与DALL-E 3相似服务或工具的指导视频。
- NestJS文档:我还深入了解了NestJS的官方文档,它是一个流行的后端Node.js框架,适合开发IO密集型服务,在海外创业公司广泛使用。
- OpenAI功能和集成:我对人工智能技术的快速进展表示惊叹,并且我探讨了如何将OpenAI的多种功能集成进我的AI机器人应用。
- 代码实践:我利用NestJS框架构建了一个项目,并计划使用官方的SDK重新接入OpenAI的接口。在此过程中,我定义了一些必要的函数和模块,并尝试实现了流式对话。
- 函数式调用和测试:我成功地完成并测试了四个基本的函数式调用,内容质量也达到了预期。
综上所述,我在技术探索和实践中深入研究了OpenAI的各个方面,包括API的调用、模型的集成以及后端服务的构建,这充分展现了我对AI和软件开发的浓厚兴趣和不断追求的精神。