幸福不在于你获得了什么,而在于你比他人多获得了什么 是比较出来的
大家好,我是柒八九。一个专注于前端开发技术/Rust
及AI
应用知识分享的Coder
此篇文章所涉及到的技术有
OpenAI
LangChain
Rust/WebAssembly
Web Worker
react+ts+vite
- 配置环境变量(
env
)
因为,行文字数所限,有些概念可能会一带而过亦或者提供对应的学习资料。请大家酌情观看。
前言
在前几天我们不是写了一篇Rust 赋能前端 -- 写一个 File 转 Img 的功能的文章吗?主要讲了如何用Rust
解析文件并将其内容用图片形式展示。
其中呢,我们在解析文本的时候,只是做了文本的解析,而没有做针对文件内容的summary处理。
下图是我们之前的需求描述:
正如需求描述要求的,我们可以借助AI
对文本进行Summary
处理。而今天我们就来这个点。
前置条件
由于是演示项目,我们使用的是OpenAI
的模型,而不是我们公司的模型。所以,我们需要有一个API_KEY
用于接入OpenAI API
。
最开始,OpenAI
对于所有新注册的账户都会赠送18美元的免费额度,23年年初开始免费额度由18美元缩减为5美元。
2024年3月21日开始,5美元的API免费额度也取消了,所有新注册的OpenAI/ChatGPT
账号都不再赠送5美元API key余额,全部是No credit grants found
(未找到信用赠款),调用API key
时也会提示insufficient_quota,You exceeded your current quota, please check your plan and billing details.
天无绝人之路,我们还可以使用一些魔法手段来调用OpenAI API
。我们可以使用GPT-API-free
大家用自己的github
账号申请即可。切记,将生成的key
保存起来。
好了,天不早了,干点正事哇。
我们能所学到的知识点
- 配置
OpenAI
环境变量- 改进
Rust
逻辑- 方式1:
OpenAI
- 方式2:
OpenAI
+langchain
1. 配置OpenAI环境变量
我们利用GPT-API-free
生成属于自己的key
之后,我们就可以通过它来访问OpenAI API
了。
因为,OpenAI key
算是一种敏感信息
,我们选择将其放置到项目的环境变量
中。(如果对环境变量不是很了解可以看我们之前的文章环境变量:熟悉的陌生人)
由于我们是用f_cli构建的Vite
前端项目。在初始化时,已经新建了用于存放环境变量的文件。
所以,我们只需要将openai key
放置到对应文件即可。
VITE_API_BASE_URL = https://api.dev.example.com
VITE_API_URL_PREFIX = '/ajax'
VITE_OPENAI_API_KEY=sk-xxx // 这里放置你自己的key
这里还有一点需要说明,在Vite
中配置环境变量并且能够在项目中使用(import.mete.env.xxx
),我们需要以VITE_
开头定义一个变量名。
2. 改进 Rust 逻辑
上一篇文章,在讲Rust
的代码时,不知道大家对下面的逻辑有印象没?
其实呢,这里的本意就是,将输入的文本基于空格进行切割,生成单词数组。但是呢,在实践过程中,我们发现上述的处理方式针对英文环境还是可以的,毕竟英文单词它再长也不能超过canvas
的宽度。
嘿,您猜怎么着,在汉字环境下,由于我们设定的方式是见着空格就切割。但是呢,一段优雅的中文文案,它可以连绵不断,没有空格。这您受得了吗。
就像这样。
所以,我们需要换种文本切割规则。 --见到字符就切割。
我们可以通过遍历text
然后将每个字符存入到数组中。类似下面的处理方式。
fn main() {
let text = "前端柒八九,专注于前端开发技术、Rust及AI应用知识分享的开发者";
let chars: Vec<String> = text.chars()
.map(|c| c.to_string())
.collect();
for c in chars {
println!("{}", c);
}
}
在这个代码中,我们将每个字符用 to_string
方法转换为 String
类型,然后收集到一个 Vec<String>
中。这种方法可以更方便地处理 Unicode
字符。
我们也可以选择现成的方案,那就是利用[unicode-segmentation
](https://crates.io/crates/unicode-segmentation)
cargo add unicode-segmentation
然后在对应代码中引入。
extern crate unicode_segmentation;
use unicode_segmentation::UnicodeSegmentation;
// 省去一下方法
let words: Vec<&str> = UnicodeSegmentation
::split_word_bounds(text)
.collect();
最后,在我们不懈的努力下,我们成功将上面的问题给解决了。
TODO
其实呢,针对Rust
处理文本并将其绘制成图像的功能,我们还可以有改进的地方,这不是本篇文章的主要核心点,就不展开说明了。
- 处理文本标题,加粗,居中等常规的布局和显示效果
- 处理图片/Table/无须列表/有序列表的展示
- 新增水印
- ...
针对上面的第一点和第二点,其实我们是有解决方案的,我们可以在解析文案的时候,不是单纯的生成一段文本信息,而是生成一段带有布局/样式信息的JSON
对象。
就像下面所展示的一样。
提起Rust
+JSON
,我们在直接介绍过如何在Rust中操作JSON。有兴趣的同学可以学习一下。
3. 方式1:OpenAI
之前我们在处理文件(Word
)时候,是在Web Worker
中通过mammoth
对上传的File
进行解析,在获取到文件的字符串信息后,我们只是单纯的使用String.substring(0,100)
对文本进行切割处理。
那么现在我们利用AI改造这部分。
下图是openai_docs关于如何在Node
项目中使用的步骤。
第一步
Node
环境就不用过多赘述了,如果有本地环境没有安装的小伙伴可以从对应官网查看。然后,我们来安装openai
对应的包。
yarn add openai
第二步
在官网为我们提供的是在全局配置API KEY
,也不是说不可以。但是呢,我们选择通过环境变量文件安装到本地项目中,这个之前也介绍过,不在赘述。
第三步
其实官网已经为我们提供了很对示例。有对话的,文生图等。而这里我们是使用对话模式来对文本进行总结。
其实,代码逻辑很简单就是拿到文本信息,然后传人到对应的api
中。(chat.completions
)。
但是呢,有几点我们需要指出
- 由于我们使用的是
GPT_API_free
生成的API KEY
,所以我们需要指定baseURL
进行Host
的转发,关于这点可以参考GPT_API_free
的官网 - 在进行对话时,我们提供了一个
messages
的数组。这是用于指定我们想让openAI
更精准的处理我们的文本信息 - 这里我们采用的是直接从
openAI
接收全部处理结果,而不是使用流模式
message
我们来简单介绍一下message
的相关内容。
messages
必须是一个消息对象的数组,其中每个对象都有一个角色(可以是system
、user
或assistant
)和内容(content
)。对话可以只包含一条消息,也可以有多次来回交流。
通常,对话格式首先是一个system
消息,然后是交替的user
和assistant
消息。
system
消息有助于设置助手的行为。- 我们可以修改助手的个性,或者提供具体的指示,说明它在整个对话中应该如何表现。
system
消息是可选的,即使没有system
消息,模型的行为也可能类似于使用一个通用的消息,例如“你是一个乐于助人的助手”。
user
消息提供请求或评论,供助手回应。assistant
消息存储之前助手的回复,但也可以由我们编写,以给出期望的行为示例。
响应信息
API响应如下所示:
{
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "xxxx",
"role": "assistant"
},
"logprobs": null
}
],
"created": 1677664795,
"id": "chatcmpl-7QyqpwdfhqwajicIEznoc6Q47XAyW",
"model": "gpt-3.5-turbo",
"object": "chat.completion",
"usage": {
"completion_tokens": 17,
"prompt_tokens": 57,
"total_tokens": 74
}
}
可以通过以下方式提取助手的回复:
completion.choices[0].message.content
每个响应都会包含一个 finish_reason
。finish_reason
的可能值包括:
stop
: API返回完整消息,或通过提供的停止序列之一终止的消息length
: 由于max_tokens
参数或令牌限制导致的不完整模型输出function_call
: 模型决定调用一个函数content_filter
: 由于内容过滤器的标志而省略的内容null
: API响应仍在进行中或不完整
还记得之前我们在写一个类ChatGPT应用,前后端数据交互有哪几种文章中介绍过,针对对话类应用,最好使用SSE
的处理方式。也就是OpenAI
提供了这种模式。
在我们构建对话时候(chat.completions.create
)传人stream: true
的属性配置即可。
随后,我们就可以通过遍历获取到每个阶段的值。
import OpenAI from "openai";
const openai = new OpenAI();
async function main() {
const stream = await openai.chat.completions.create({
model: "gpt-3.5-turbo",
messages: [{ role: "user", content: "Front789你值得拥有" }],
+ stream: true,
});
+ for await (const chunk of stream) {
+ process.stdout.write(chunk.choices[0]?.delta?.content || "");
+ }
}
main();
效果展示
现在我们有一个word
文件,具体内容如下
现在,当我们将其通过WebAssembly
解析并且通过OpenAI
对其文本信息的Summary
处理,最后绘制成一张图。
就是这么简单
4. 方式2:OpenAI
+ LangChain
其实,到第三节已经达到了我们的目的,利用AI
对文本进行Summary
处理。
但是呢,秉承着好东西要和大家一起分享。然后,再给大家介绍一种使用AI
更加优雅的方式。那就是LangChain。
LangChain 是个啥?
LangChain
是一个强大的工具,用于构建支持大语言模型(LLMs
)的应用。它提供了一整套组件和工具,帮助我们快速创建和部署复杂的语言模型应用,涵盖从数据处理到模型集成再到应用发布的各个方面。
核心概念和组件
链(Chains):
LangChain
中的“链”是指将多个语言模型任务串联在一起的过程。- 例如,可以先进行文本生成,然后进行摘要或问答。这种链式结构允许复杂任务的分步处理,使模型输出更准确和实用。
提示模板(Prompt Templates):
- 提示模板用于生成输入给语言模型的提示。
- 这些模板可以是静态的,也可以根据输入数据动态生成。
- 通过模板,可以更好地控制模型的行为和输出质量。
记忆(Memory):
- 记忆组件允许模型在对话过程中保持状态。
- 这对于创建连续对话或需要上下文记忆的任务(如个性化助手)非常重要。
- 记忆可以是短期的(会话级别)或长期的(跨会话)。
文档加载(Document Loaders)和索引(Indexes):
- 这些组件用于加载和处理大型文档集。
- 加载器可以从各种数据源(如文件系统、数据库、网页等)中提取文档,而索引则用于高效地查询和检索这些文档。
代理(Agents):
- 代理是负责处理复杂任务的智能实体。
- 它们可以根据用户输入,动态选择适当的动作或调用不同的模型。
- 代理可以用于自动化任务、对话管理等。
工具(Tools)和插件(Plugins):
LangChain
提供了各种工具和插件,用于扩展其功能。- 例如,可以集成外部API、执行数据库查询、调用第三方服务等。
使用场景
- 对话系统:通过
链式结构
和记忆功能
,可以构建复杂的对话系统,这些系统能够进行多轮对话,记住用户的偏好和历史对话内容。 - 内容生成:利用
提示模板
和链
,可以创建自动化内容生成系统,例如博客文章写作、产品描述生成等。 - 文档问答:通过
文档加载
和索引
,可以构建强大的文档问答系统,用户可以对大型文档集进行自然语言查询,获取精确的答案。 - 个性化推荐:结合
用户数据
和记忆组件
,可以实现个性化推荐系统,根据用户的历史行为和偏好提供定制化的建议。
优势
- 模块化设计:
LangChain
的模块化设计使得我们可以根据需求灵活组装不同的组件,快速构建和迭代应用。 - 强大的集成能力:支持与各种数据源、API 和服务的集成,扩展了应用的功能和数据获取能力。
- 高效的开发体验:提供了丰富的文档和示例代码,降低了开发门槛,使开发者能够专注于业务逻辑而非底层实现。
- 社区和支持:
LangChain
拥有活跃的社区和丰富的资源,包括教程、论坛和技术支持,为开发者提供了强大的支持体系。
用langchain改造我们的应用
首先,我们需要安装对应的包。
yarn add langchain
然后,我们需要选择对应的模型,LangChain
可以支持市面上常见的模型,例如OpenAI/Anthropic
等。与此同时,我们还需要配置对应模型的API KEY
。
随后,我们需要在代码中引入对应的工具函数
import { ChatOpenAI } from '@langchain/openai';
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
import { StringOutputParser } from '@langchain/core/output_parsers';
然后,在对应的位置初始化对应的AI 模型
实例。构建对应的Message
信息,然后执行invoke
。
使用LangChain
还有一些好处就是它为我们内置了很多方法用于处理Message
和解析返回的数据。这样能够让代码显的更加清晰。
下面就是我们的执行结果。
LangChain
还支持部署自己的模型,这块也是足够吸引我们的地方。这个我们团队也在实践,有时间也聊聊这个。(看到这里,还不感觉点赞,收藏,转发走一波)
后记
分享是一种态度。
全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。
本文由mdnice多平台发布
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。