人家都说chatgpt好,但人家只是单模态。官方话说:ChatGPT 是一个专注于文本处理的模型,它能够生成高质量的文本内容,支持对话生成、文章写作等多种任务。今天要用多模态GPT4o处理图像,下面是使用OpenAI 提供的某些模型,去接受图像作为输入并产生相应的文本描述。
处理的图片:
前置知识
1.请求与响应(重)
在面向 OpenAI 接口编程时,请求和响应构成了核心交互模式。请求通常包括模型选择model、输入数据message(如文本或图像)、参数配置max_tokens等;而响应response 则可能包含模型输出的数据、状态码以及可能的错误信息(error,使用try catch 保持代码健壮性)。
参数配置:
max_tokens
:指定生成的最大 token 数量,控制输出的长度。temperature
:控制输出的随机性,值越大,输出越随机。top_p
:控制采样的范围,值越小,采样范围越窄。n
:指定生成的回复数量。stop
:指定停止生成的条件,可以是一个字符串或字符串数组
2.全局安装工具
-
OpenAI SDK : 使用
npm i -g openai
命令可以全局安装 OpenAI 的 Node.js SDK,这使得开发者能够在任何地方使用该库来调用 OpenAI API。 -
pnpm : 通过
npm i -g pnpm
安装 pnpm,这是一种更快、更节省磁盘空间的包管理器。相比 npm,pnpm 更好地避免了全局安装带来的环境污染问题。 -
为什么用pnpm不直接npm尼? pnpm 不仅速度快,而且不会重复安装依赖包 ,这样可以显著减少磁盘占用。此外,pnpm 使用符号链接指向已存在的安装,从而避免了全局环境的污染。
-
推荐使用国内加速 npm
对于位于中国的开发者来说,由于网络原因,使用默认的 npm 源下载包可能会非常慢。可以通过以下命令切换到阿里云的 npm 镜像源以加速下载速度:
- 切换至阿里云镜像:
npm config set registry https://registry.npmmirror.com
- 恢复为官方镜像:
npm config set registry https://registry.npmjs.org
- 查看当前使用的源:
npm config get registry
3.入口文件与API密钥管理
-
main.mjs:通常作为项目的入口文件,使用 ES6 模块语法(如 import/export)组织代码。 虽然这里没有直接提到 ejs,但它是用于生成 HTML 页面的模板引擎之一,支持 ES6 模块化开发。
-
调试技巧:使用
console.log()
输出变量值或 JSON 结构是常用的调试手段,可以帮助开发者快速定位问题所在。特别是当处理复杂的 API 响应时,查看完整的 JSON 数据结构尤为重要。 -
API 密钥是访问 OpenAI 服务的关键凭证,因此必须妥善保管。推荐采用在项目根目录下创建
.env
文件存储密钥等敏感信息,并通过环境变量的方式引入到代码中,避免直接硬编码在源码中泄露。
示例代码分析
javascript
import dotenv from 'dotenv';
import OpenAI from 'openai';
// 加载环境变量
dotenv.config();
// 初始化 OpenAI 客户端
const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
baseURL: 'https://api.302.ai/v1'
});
// 主函数
const main = async () => {
try {
//调用 API:将这个请求对象发送给多模态模型的 API
const response = await client.chat.completions.create({
model: 'gpt-4o',
messages: [{
role: 'user',
content: [
// 文本指令,通常是字符串text
{ type: 'text', text: '请描述以下图片的内容' },
// 图片指令,给url,让模型根据http/https 下载图片
{ type: 'image_url', image_url: { url: '图片URL' } }
]
}],
max_tokens: 300
});
//输出第一个回复
console.log(response.choices[0].message.content);
} catch (error) {
console.error("发生错误:", error.message);
}
};
main();
这段代码展示了如何利用 OpenAI 的多模态功能来处理包含文本和图像的请求。通过 .env
文件管理 API 密钥,确保了安全性。同时,采用异步函数和错误处理机制保证了程序的稳定运行。 结果展示:
问题来了:client.chat.completions.create
是什么?
client.chat.completions.create
是client (OpenAI API )提供的一个方法,通过chat聊天方式,完成completion用户需求,生成create文本回复。
具体来说,它是一个异步函数,通过 OpenAI 的客户端库(如
openai
Node.js SDK)调用,发送一个请求到 OpenAI 的服务器,请求生成文本回复。
客户端库:
client
是你通过 OpenAI SDK 初始化的客户端对象。- 初始化时需要提供 API 密钥和其他配置信息。
方法调用:
chat.completions.create
是客户端库提供的一个方法,用于生成聊天回复。- 这个方法接受一个包含请求参数的对象,并返回一个 Promise,该 Promise 在请求成功时解析为包含响应数据的对象。
解释一下response.choices[0].message.content
:
在Openai API响应中,response.choices
是一个数组,包含了模型生成的所有可能回复。每个元素都是一个对象,代表了一次可能的回复。通常情况下,模型会返回一个或多个候选回复,具体数量取决于请求中的配置。
而在示例代码中,response.choices[0].message.content
表示选择了第一个候选回复的内容。大多数情况下,我们只需要模型给出的最佳答案,即第一个候选回复。如果希望获取更多的候选回复,可以遍历整个 choices
数组。
使用 async
和 await
的好处
在 JavaScript 中,异步编程是一种常见的模式,特别是在处理网络请求、文件操作等耗时任务时。传统的回调函数虽然可以实现异步操作,但容易导致"回调地狱"(callback hell)(下文用一个实例演示) ,使代码难以阅读和维护。async
和 await
关键字的引入,大大简化了异步代码的编写和理解。
1. 代码可读性更高
使用 async
和 await
可以让异步代码看起来更像同步代码,逻辑更加清晰。例如:
javascript
const main = async () => {
try {
const response = await client.chat.completions.create({
model: 'gpt-4o',
messages: [
{
role: 'user',
content: [
{ type: 'text', text: '请描述以下图片的内容' },
{ type: 'image_url', image_url: { url: '图片URL' } }
]
}
],
max_tokens: 300
});
console.log(response.choices[0].message.content);
} catch (error) {
console.error("发生错误:", error.message);
}
};
相比之下,使用回调函数的代码会显得更加复杂:
javascript
client.chat.completions.create({
model: 'gpt-4o',
messages: [
{
role: 'user',
content: [
{ type: 'text', text: '请描述以下图片的内容' },
{ type: 'image_url', image_url: { url: '图片URL' } }
]
}
],
max_tokens: 300
}, (error, response) => {
if (error) {
console.error("发生错误:", error.message);
} else {
console.log(response.choices[0].message.content);
}
});
2. 错误处理更简单
使用 try...catch
结构可以轻松地捕获和处理异步操作中抛出的错误,而不需要层层传递回调函数中的错误处理逻辑。
javascript
const main = async () => {
try {
const response = await client.chat.completions.create({
// 请求参数
});
console.log(response.choices[0].message.content);
} catch (error) {
console.error("发生错误:", error.message);
}
};
3. 支持并发操作
使用 Promise.all
等方法可以轻松地并发执行多个异步操作,而不需要嵌套回调函数。
javascript
javascript
深色版本
const main = async () => {
try {
const [response1, response2] = await Promise.all([
client.chat.completions.create({ /* 请求参数 */ }),
client.images.generate({ /* 请求参数 */ })
]);
console.log(response1.choices[0].message.content);
console.log(response2.data.url);
} catch (error) {
console.error("发生错误:", error.message);
}
};
示例:回调地狱
啥是"回调地狱"?是不是一朝回调深尸骸
官方话语:"回调地狱"(Callback Hell)是指在使用回调函数进行异步编程时,由于多层嵌套的回调函数导致代码结构变得非常混乱和难以维护的现象。这种现象常见于早期的 Node.js 应用和其他基于事件驱动的编程环境中。
下面用实例做对比,假设你需要依次完成以下几个异步操作:
- 从数据库中读取用户信息。
- 根据用户信息发送邮件。
- 记录邮件发送的状态。
一个异步使用传统的回调函数可能还想,那三个异步代码可能会写成这样:(if else 齐家)
javascript
readUserFromDatabase(userId, (err, user) => {
if (err) {
console.error("读取用户信息失败:", err);
} else {
sendEmail(user.email, (err, emailResult) => {
if (err) {
console.error("发送邮件失败:", err);
} else {
logEmailStatus(emailResult, (err, logResult) => {
if (err) {
console.error("记录邮件状态失败:", err);
} else {
console.log("邮件发送成功并记录状态:", logResult);
}
});
}
});
}
});
这有什么问题尼?
- 可读性差:多层嵌套的回调函数使得代码层次结构不清晰,难以阅读和理解。
- 错误处理复杂:每层回调都需要单独处理错误,导致代码冗余且难以维护。
- 扩展困难:添加新的异步操作需要进一步嵌套回调,使得代码越来越臃肿。
解决方案:使用 async
和 await
通过使用 async
和 await
关键字,可以将异步代码写得更像同步代码,从而避免回调地狱。
javascript
const readUserFromDatabase = (userId) => {
//返回promise对象,参数分别表示异步成功resolve,调用失败reject
return new Promise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
const user = { id: userId, email: 'user@example.com' };
//操作成功,调用 `resolve(user)`,将用户对象传递出去
resolve(user);
}, 1000);
});
};
const sendEmail = (email) => {
return new Promise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
const emailResult = { to: email, status: 'sent' };
resolve(emailResult);
}, 1000);
});
};
const logEmailStatus = (emailResult) => {
return new Promise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
const logResult = { emailId: emailResult.to, status: emailResult.status };
resolve(logResult);
}, 1000);
});
};
const main = async () => {
try {
const user = await readUserFromDatabase(userId);
const emailResult = await sendEmail(user.email);
const logResult = await logEmailStatus(emailResult);
console.log("邮件发送成功并记录状态:", logResult);
} catch (err) {
console.error("发生错误:", err);
}
};
main();
那优点也挺明显的
- 代码更清晰:异步操作按顺序排列,逻辑一目了然。
- 错误处理简单 :使用
try...catch
结构可以集中处理所有异步操作中的错误。 - 易于扩展 :添加新的异步操作只需在
async
函数中按顺序添加await
语句即可
落幕!欢迎评论谈论学习