开发智能写作助手:一场技术与创意的双重挑战


大家好!我是双越老师,同时也是 wangEditor 项目的作者。

今年我专注于开发一个名为划水AI的项目,它是一个结合了AI写作和多人协同编辑功能的全栈Node.js AIGC知识库。这个项目复杂而真实地上线了,欢迎大家去注册并试用一下。

今天我想和大家分享一下如何开发AI编辑器,以及在这个过程中遇到了哪些问题,并且是如何进行优化的。

AIGC和AI应用领域并不简单,许多人只关注于调接口这个层面,并未深入理解其中的技术含量。

研发发动机需要高技术含量,将发动机装配到汽车并调整好同样需要高水平的技术能力。只不过是在不同的领域应用了这项技能。

借助AI服务开发一个AI编辑器并不简单。首先富文本编辑器本身就很复杂,再集成AI功能更是挑战重重。比如:

如何实现流式打印效果,提高用户体验

如何合理传递消息给AI?总不能把文档全文都发送过去吧?这样会浪费资源,并且效率低下。

如何将生成的内容合理插入或替换到编辑器中?需要考虑编辑器当前的内容和选择状态。

如何让AI生成富文本,如大纲标题、表格、列表、代码块?并把这些内容插入到编辑器

如何处理暂停重试?在暂停时如何保留已消耗的资源?

如何记录资源使用数量,并设置合理限制?否则无限制免费使用会成本过高,且容易被滥用。

如何对AI接口进行合理鉴权,防止别人盗用?

如何应对AI服务的调用频率限制问题?

如何限制用户对自己AI接口的调用频率以防范恶意频繁访问

如何保证AI接口稳定性,做好监控和报警?

这些问题可能让你体会到:这项工作虽然不简单,但其应用场景非常复杂——这其实很像我们在日常工作中处理的各种项目。而我们的价值就在于利用技术手段解决复杂的业务场景和功能问题,而不是深入研究内部原理。

你可以说熟悉V8引擎源码,但在找工作的过程中每天就是增删改查,并拿着相应的工资。除非有能力自己创业。设计了哪些功能?

AI生成文本

如文章开头的gif动图,通过输入指令(或点击一个菜单)根据当前文章标题和内容,让AI为你写一段文字。你可以使用它来写作大纲、头脑风暴,或者对已有的文章进行续写和总结。

使用 AI 处理文本

请选中您想要处理的段落文字。

AI 将会帮助您进行优化和生成新版本,包括但不限于:扩展内容、简化表达、进行语言翻译和调整语气。这样的操作将极大提升您的文档质量并使其更易于理解和传达信息。

步骤如下:

1. 选择需要处理的文字片段;

2. AI 将自动检测和分析选中的文字,然后根据需求进行优化和生成新的版本;

3. 可以选择扩展现有段落的长度或者简化表达方式;

4. 进行语言翻译,确保内容与目标受众相符;

5. 调整语气,使其更加符合您想要传达的信息风格。

AI 的功能可以帮助您在短时间内完成复杂的工作任务。例如处理长文档、进行多语言转换和优化语句等。通过使用 AI 工具,您可以节省时间和精力,专注于更重要的事务。

前几天,我和一位同学在评审他的简历时遇到了问题。他对于工作的职责部分不太了解,于是我就给他提供了一个大致的框架和想法作为参考。这样一来,他就可以利用这个框架去进一步编写自己的工作职责,同时在面试中也可以以此为基础进行充分的表达。

未来扩展

AI 的发展前景非常广阔。当前,我们可以利用 AI 技术来处理多种类型的文件,包括文本、图片、视频和文档等。

可用的 AI 接口服务

几个月前我在进行调研时写过一篇关于使用 Node.js 调用 ChatGPT API 实现流式聊天效果的文章。ChatGPT 的实现相对简单,主要目的是了解如何使用这些接口,当时它们并没有被国内禁用。

然而,后来 OpenAI 禁止了对国内的访问服务,并且我们无法直接调用他们的 API。幸运的是,我们可以利用中转服务,比如 Deepbricks。和 ChatGPT 类似,Deepbricks 的使用方法也很简单,而且费用也非常便宜。

```

import OpenAI from 'openai'

const openai = new OpenAI({ apiKey: 'xxxxxxxxxxxx' })

async function main() {

const completion = await openai.chat.completions.create({

messages: [{ role: 'user', content: 'How are you today?' }], // 消息内容

model: 'gpt-3.5-turbo',

max_tokens: 20, // 限制返回字符,帮你节省额度

})

const result = completion.choices[0]

console.log('result: ', result)

}

main()

```

我当时被这个问题困扰了很久,后来想到了一个非常简单的方法:通过在 token 中加密用户 ID,并使用该用户 ID 对应的 token 使用量来解决这个问题。

在发送请求时需要对 token 进行加密,包括时间戳和用户的 ID。以下是具体步骤:

1. 获取当前时间戳:

```javascript

const dt = Date.now()

```

2. 构建包含时间戳和用户 ID 的内容对象:

```javascript

const content = { dt, userId: user.id }

```

3. 使用 `CryptoJS.AES.encrypt` 方法对 JSON 格式的内容进行加密,并将结果转换为字符串形式,得到 token :

```javascript

const token = CryptoJS.AES.encrypt(JSON.stringify(content), AUTH_KEY).toString()

```

4. 将加密后的 token 设置到 URL 参数中:

```javascript

url.searchParams.set('x-auth-token', token)

```

在 AI 服务中使用同样的密钥进行解密,并检查解密失败的情况,如果失败则返回错误信息。

```javascript

// decrypt token

const authTokenRaw = query['x-auth-token'] || ''

const tokenInfo = decryptToken(authTokenRaw)

if (tokenInfo == null || !tokenInfo.userId) {

const errMsg = 'invalid token'

console.log('error: ', errMsg)

ctx.res.write(`data: [ERROR]${errMsg}\n\n`) // 格式必须是 `data: xxx\n\n`!!!

return

}

```

然后根据用户 ID 查询是否还有使用量,如果使用量小于等于 0,则返回错误信息。

```javascript

// check token usage

const { userId } = tokenInfo

const usage = await getTokenUsage(userId)

if (usage == null) {

const errMsg = 'token usage not found'

console.log('error: ', errMsg)

ctx.res.write(`data: [ERROR]${errMsg}\n\n`)

return

}

const { tokensLimit, totalTokens } = usage

if (tokensLimit <= 0) {

const errMsg = 'token usage limit exceeded'

console.log('error: ', errMsg)

ctx.res.write(`data: [ERROR]${errMsg}\n\n`)

return

}

```

总结一下,虽然调用第三方的 AI 服务,但在实际使用时需要设计和实现很多情况,比如加密 token、解密 token、检查 token 使用量等。我记录了详细的研发过程,包括调研、设计、代码修改记录、测试用例等,并且分享遇到的各种问题 bug 和思考过程。如果有兴趣的同学可以私聊我了解更多细节。