幸福不在于你获得了什么,而在于你比他人多获得了什么 是比较出来的

大家好,我是柒八九。一个专注于前端开发技术/RustAI应用知识分享Coder

此篇文章所涉及到的技术有

  1. OpenAI
  2. LangChain
  3. Rust/WebAssembly
  4. Web Worker
  5. react+ts+vite
  6. 配置环境变量(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保存起来。


好了,天不早了,干点正事哇。

我们能所学到的知识点

  1. 配置OpenAI环境变量
  2. 改进 Rust 逻辑
  3. 方式1:OpenAI
  4. 方式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处理文本并将其绘制成图像的功能,我们还可以有改进的地方,这不是本篇文章的主要核心点,就不展开说明了。

  1. 处理文本标题,加粗,居中等常规的布局和显示效果
  2. 处理图片/Table/无须列表/有序列表的展示
  3. 新增水印
  4. ...

针对上面的第一点和第二点,其实我们是有解决方案的,我们可以在解析文案的时候,不是单纯的生成一段文本信息,而是生成一段带有布局/样式信息的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)。

但是呢,有几点我们需要指出

  1. 由于我们使用的是GPT_API_free生成的API KEY,所以我们需要指定baseURL进行Host的转发,关于这点可以参考GPT_API_free的官网
  2. 在进行对话时,我们提供了一个messages的数组。这是用于指定我们想让openAI更精准的处理我们的文本信息
  3. 这里我们采用的是直接从openAI接收全部处理结果,而不是使用流模式

message

我们来简单介绍一下message的相关内容。

messages必须是一个消息对象的数组,其中每个对象都有一个角色(可以是systemuserassistant)和内容(content)。对话可以只包含一条消息,也可以有多次来回交流。

通常,对话格式首先是一个system消息,然后是交替的userassistant消息。

  • 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_reasonfinish_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)的应用。它提供了一整套组件和工具,帮助我们快速创建和部署复杂的语言模型应用,涵盖从数据处理到模型集成再到应用发布的各个方面。

核心概念和组件

  1. 链(Chains)

    • LangChain 中的“链”是指将多个语言模型任务串联在一起的过程。
    • 例如,可以先进行文本生成,然后进行摘要或问答。这种链式结构允许复杂任务的分步处理,使模型输出更准确和实用。
  2. 提示模板(Prompt Templates)

    • 提示模板用于生成输入给语言模型的提示。
    • 这些模板可以是静态的,也可以根据输入数据动态生成。
    • 通过模板,可以更好地控制模型的行为和输出质量。
  3. 记忆(Memory)

    • 记忆组件允许模型在对话过程中保持状态。
    • 这对于创建连续对话或需要上下文记忆的任务(如个性化助手)非常重要。
    • 记忆可以是短期的(会话级别)或长期的(跨会话)。
  4. 文档加载(Document Loaders)和索引(Indexes)

    • 这些组件用于加载和处理大型文档集。
    • 加载器可以从各种数据源(如文件系统、数据库、网页等)中提取文档,而索引则用于高效地查询和检索这些文档。
  5. 代理(Agents)

    • 代理是负责处理复杂任务的智能实体。
    • 它们可以根据用户输入,动态选择适当的动作或调用不同的模型。
    • 代理可以用于自动化任务、对话管理等。
  6. 工具(Tools)和插件(Plugins)

    • LangChain 提供了各种工具和插件,用于扩展其功能。
    • 例如,可以集成外部API、执行数据库查询、调用第三方服务等。

使用场景

  1. 对话系统:通过链式结构记忆功能,可以构建复杂的对话系统,这些系统能够进行多轮对话,记住用户的偏好和历史对话内容。
  2. 内容生成:利用提示模板,可以创建自动化内容生成系统,例如博客文章写作、产品描述生成等。
  3. 文档问答:通过文档加载索引,可以构建强大的文档问答系统,用户可以对大型文档集进行自然语言查询,获取精确的答案。
  4. 个性化推荐:结合用户数据记忆组件,可以实现个性化推荐系统,根据用户的历史行为和偏好提供定制化的建议。

优势

  1. 模块化设计LangChain 的模块化设计使得我们可以根据需求灵活组装不同的组件,快速构建和迭代应用。
  2. 强大的集成能力:支持与各种数据源、API 和服务的集成,扩展了应用的功能和数据获取能力。
  3. 高效的开发体验:提供了丰富的文档和示例代码,降低了开发门槛,使开发者能够专注于业务逻辑而非底层实现。
  4. 社区和支持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多平台发布


前端柒八九
18 声望3 粉丝