使用开放的机器学习模型制作一个网络应用生成器
使用开放的机器学习模型制作网络应用生成器
随着越来越多的代码生成模型公开可用,现在可以以我们之前无法想象的方式进行文本到网页甚至文本到应用程序的转换。
本教程介绍了一种直接的方法,通过流式传输和渲染内容来生成 AI 网页内容。
在此处尝试实时演示!→ Webapp 工厂
在 Node 应用程序中使用 LLM
虽然我们通常认为 Python 与 AI 和 ML 相关的一切都有关,但 Web 开发社区严重依赖 JavaScript 和 Node。
以下是您可以在此平台上使用大型语言模型的一些方法。
通过本地运行模型
有多种方法可以在 JavaScript 中运行 LLM,从使用 ONNX 到将代码转换为 WASM 并调用用其他语言编写的外部进程。
现在,某些技术可作为现成的 NPM 库使用:
- 使用支持代码生成的 AI/ML 库,如 transformers.js
- 使用专用的 LLM 库,如 llama-node(或适用于浏览器的 web-llm)
- 通过 Pythonia 等桥接库使用 Python 库
然而,在这样的环境中运行大型语言模型可能会占用相当多的资源,尤其是如果您无法使用硬件加速。
通过使用 API
如今,各种云提供商提供商业 API 以使用语言模型。以下是当前 Hugging Face 提供的内容:
免费的 Inference API,允许任何人使用来自社区的小型到 VoAGI 大小的模型。
更高级且适用于生产环境的 Inference Endpoints API,适用于需要更大模型或自定义推理代码的人。
可以使用 Hugging Face Inference API 库在 Node 中使用这两个 API。
💡 性能最佳的模型通常需要大量内存(32 GB、64 GB 或更多)和硬件加速才能获得良好的延迟(请参阅基准测试)。但我们还看到一种趋势是模型在尺寸上缩小,同时在某些任务上仍然保持相对良好的结果,要求的内存低至 16 GB 或甚至 8 GB。
架构
我们将使用 NodeJS 创建生成式 AI 网页服务器。
模型将是运行在 Inference Endpoints API 上的 WizardCoder-15B,但您可以随意尝试其他模型和堆栈。
如果您对其他解决方案感兴趣,以下是一些指向其他实现的指针:
- 使用 Inference API:代码和空间
- 从 Node 使用 Python 模块:代码和空间
- 使用 llama-node(llama cpp):代码
初始化项目
首先,我们需要设置一个新的 Node 项目(如果您想要,可以克隆此模板)。
git clone https://github.com/jbilcke-hf/template-node-express tutorial
cd tutorial
nvm use
npm install
然后,我们可以安装 Hugging Face Inference 客户端:
npm install @huggingface/inference
并在 `src/index.mts` 中进行设置:
import { HfInference } from '@huggingface/inference'
// 为了保护您的 API 令牌安全,实际上,您应该使用类似以下方式:
// const hfi = new HfInference(process.env.HF_API_TOKEN)
const hfi = new HfInference('** YOUR TOKEN **')
配置推理端点
💡 注意:如果您不想支付 Endpoint 实例的费用来完成此教程,您可以跳过此步骤并查看此免费的 Inference API 示例。请注意,这仅适用于较小的模型,可能不够强大。
要部署新的 Endpoint,您可以转到 Endpoint 创建页面。
您将需要在Model Repository下拉菜单中选择WizardCoder
,并确保选择了足够大的 GPU 实例:
创建完端点之后,您可以从此页面复制URL:
配置客户端以使用它:
const hf = hfi.endpoint('** URL TO YOUR ENDPOINT **')
现在您可以告诉推理客户端使用我们的私有端点并调用我们的模型:
const { generated_text } = await hf.textGeneration({
inputs: 'a simple "hello world" html page: <html><body>'
});
生成HTML流
现在是时候在访问URL时向Web客户端返回一些HTML了,比如/app
。
我们将使用Express.js创建一个端点来从Hugging Face推理API中流式传输结果。
import express from 'express'
import { HfInference } from '@huggingface/inference'
const hfi = new HfInference('** YOUR TOKEN **')
const hf = hfi.endpoint('** URL TO YOUR ENDPOINT **')
const app = express()
目前我们没有任何界面,因此界面将是一个简单的URL参数:
app.get('/', async (req, res) => {
// 将页面的开头发送到浏览器(其余部分将由AI生成)
res.write('<html><head></head><body>')
const inputs = `# Task
Generate ${req.query.prompt}
# Out
<html><head></head><body>`
for await (const output of hf.textGenerationStream({
inputs,
parameters: {
max_new_tokens: 1000,
return_full_text: false,
}
})) {
// 将结果流式传输到浏览器
res.write(output.token.text)
// 也打印到控制台以进行调试
process.stdout.write(output.token.text)
}
req.end()
})
app.listen(3000, () => { console.log('server started') })
启动您的Web服务器:
npm run start
然后打开https://localhost:3000?prompt=some%20prompt
。几秒钟后,您应该会看到一些原始的HTML内容。
调整提示
每个语言模型对提示的反应都不同。对于WizardCoder来说,简单的指令通常效果最好:
const inputs = `# Task
Generate ${req.query.prompt}
# Orders
Write application logic inside a JS <script></script> tag.
Use a central layout to wrap everything in a <div class="flex flex-col items-center">
# Out
<html><head></head><body>`
使用Tailwind
Tailwind是一种流行的用于样式化内容的CSS框架,而WizardCoder在这方面表现出色。
这使得代码生成可以在页面的开头或结尾生成样式表(这将使页面感觉卡顿)之前即时创建样式。
为了改进结果,我们还可以通过显示路径来引导模型(<body class="p-4 md:p-8">
)。
const inputs = `# Task
Generate ${req.query.prompt}
# Orders
You must use TailwindCSS utility classes (Tailwind is already injected in the page).
Write application logic inside a JS <script></script> tag.
Use a central layout to wrap everything in a <div class="flex flex-col items-center'>
# Out
<html><head></head><body class="p-4 md:p-8">`
防止产生幻觉
在专门用于代码生成的轻型模型上,要可靠地防止幻觉和故障(例如完整地重复指令,或写入“lorem ipsum”占位文本)可能会很困难,相比较更大的通用模型,但我们可以尝试减轻这种情况。
您可以尝试使用命令式语气并重复指令。另一种有效的方法是通过给出英文部分输出来展示路径:
const inputs = `# 任务
生成 ${req.query.prompt}
# 命令
不要重复这些指令,而是写最终代码!
您必须使用TailwindCSS实用类(Tailwind已经注入到页面中)!
在JS <script></script>标签中编写应用逻辑!
这不是演示应用程序,所以您必须使用英语,不要使用拉丁语!请用英语写!
使用中央布局将所有内容包装在<div class="flex flex-col items-center">中
# 输出
<html><head><title>App</title></head><body class="p-4 md:p-8">`
添加图像支持
我们现在有一个可以生成HTML、CSS和JS代码的系统,但在要求生成图像时,它容易出现破损URL的幻觉。
幸运的是,在图像生成模型方面,我们有很多选择!
→ 最快入门的方法是使用我们的免费推理API调用一个稳定扩散模型,并使用Hub上的公共模型之一:
app.get('/image', async (req, res) => {
const blob = await hf.textToImage({
inputs: `${req.query.caption}`,
model: 'stabilityai/stable-diffusion-2-1'
})
const buffer = Buffer.from(await blob.arrayBuffer())
res.setHeader('Content-Type', blob.type)
res.setHeader('Content-Length', buffer.length)
res.end(buffer)
})
将以下行添加到提示中就足以指示WizardCoder使用我们的新/image
端点!(您可能需要为其他模型进行微调):
要从标题生成图像,请调用/image API:<img src="/image?caption=某地某物的照片" />
您还可以尝试更具体的方式,例如:
仅生成少量图像,并使用至少10个单词的描述性照片标题!
添加一些用户界面
Alpine.js是一个极简的框架,可以在没有任何设置、构建流程、JSX处理等的情况下创建交互式UI。
所有操作都在页面内完成,这使其成为创建快速演示的UI的理想选择。
以下是一个静态HTML页面,您可以将其放在/public/index.html
中:
<html>
<head>
<title>教程</title>
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div class="flex flex-col space-y-3 p-8" x-data="{ draft: '', prompt: '' }">
<textarea
name="draft"
x-model="draft"
rows="3"
placeholder="输入一些内容.."
class="font-mono"
></textarea>
<button
class="bg-green-300 rounded p-3"
@click="prompt = draft">生成</button>
<iframe :src="`/app?prompt=${prompt}`"></iframe>
</div>
</body>
</html>
要使其工作,您需要进行一些更改:
...
// 访问localhost:3000将从/public/index.html加载文件
app.use(express.static('public'))
// 我们将此从'/'更改为'/app'
app.get('/app', async (req, res) => {
...
优化输出
到目前为止,我们一直在生成完整的Tailwind实用类序列,这对于给语言模型提供设计自由度非常好。
但是这种方法也非常冗长,消耗了我们大部分的令牌配额。
为了使输出更加紧凑,我们可以使用Daisy UI,这是一个将Tailwind实用类组织成设计系统的插件。其思想是对组件使用简写类名,对其余部分使用实用类。
某些语言模型可能不了解Daisy UI,因为它是一个小众库,在这种情况下,我们可以在提示中添加API文档:
# DaisyUI文档
## 为了创建一个漂亮的布局,请将每个文章包装在:
<article class="prose"></article>
## 使用适当的CSS类
<button class="btn ..">
<table class="table ..">
<footer class="footer ..">
进一步发展
最终的演示空间包含了一个更完整的用户界面示例。
以下是进一步扩展这个概念的一些想法:
- 测试其他语言模型,如StarCoder
- 为中间语言(React,Svelte,Vue等)生成文件和代码
- 将代码生成集成到现有框架中(例如NextJS)
- 从失败或部分代码生成中恢复(例如自动修复JS中的问题)
- 将其连接到聊天机器人插件中(例如,在聊天讨论中嵌入小型Web应用程序iframe)