0. 注意
本文只是提供一个思路,由于现在大模型正在飞速发展,整个生态在不久的将来或许会发生巨大的变化,文章中的代码仅供参考
1. 自定义LLM
类
除了看文档还可以看源码是怎么封装的。
其实很简单,大部分无论本地还是在线的模型,都会提供api
接口,我们只需要调用就行了。
下面使用阿里的通义千问为例。
申请key
和对接文档我就不赘述了,感兴趣的可以去阿里云百炼上面看看。
总体架构:
graph TD
A["express搭建后台(连接大模型)"] --> 前端通过调用服务间接访问大模型
ts
// 这是我使用express简单搭建的后台
// 主要任务就是连接通义千问的在线平台
import express from 'express';
import * as path from 'path';
// 获取token的文档
// https://help.aliyun.com/document_detail/2587930.html?spm=a2c4g.2638479.0.0.4d4757a7OELeVS
import BaiLianClient, {
CreateTokenResponseBodyData,
} from '@alicloud/bailian20230601';
import axios, { AxiosResponse } from 'axios';
import { nanoid } from 'nanoid';
import dayjs from 'dayjs';
const app = express();
app.use(express.json());
// @ts-ignore
const client = new BaiLianClient({
accessKeyId: process.env.ALI_KEY_ID,
accessKeySecret: process.env.ALI_KEY_SECRET,
endpoint: process.env.ALI_END_POINT,
});
/**
* 获取客户端token
*/
let tokenData: Partial<CreateTokenResponseBodyData> = {
expiredTime: dayjs('1990-01-01').unix(),
};
async function createToken() {
try {
// @ts-ignore
const response = await client.createToken({
agentKey: process.env.ALI_AGENT_KEY,
});
const responseBody = response.body;
if (responseBody === null) {
throw new Error('failed to create token');
}
if (responseBody.success !== true) {
let requestId = responseBody.requestId;
if (requestId == null) {
requestId = response.headers['x-acs-request-id'];
}
const error =
'failed to create token ' +
responseBody.message +
', requestId: ' +
requestId;
throw new Error(error);
}
const data = responseBody.data;
if (data === null) {
throw new Error('failed to create token');
}
tokenData = data;
return data;
} catch (err) {
console.log('failed to create token, err: ', err);
}
}
app.use(async (req, res, next) => {
if (dayjs.unix(tokenData.expiredTime).isBefore(dayjs())) {
await createToken();
}
next();
});
// 自定义的一个接口
// 前端传入prompt参数
// 服务返回通义千问的响应
app.post('/llm', async (req, res) => {
const { prompt } = req.body;
const aiRes = await axios.post<
Completion.Body,
AxiosResponse<Completion.Response>,
Completion.Body
>(
'https://bailian.aliyuncs.com/v2/app/completions',
{
RequestId: nanoid(10),
Prompt: prompt,
AppId: process.env.ALI_APP_ID,
TopP: 0,
},
{
headers: {
Authorization: `Bearer ${tokenData.token}`,
},
},
);
res.send({ message: aiRes.data.Data.Text });
});
const port = process.env.PORT || 3333;
const server = app.listen(port, () => {
console.log(`Listening at http://localhost:${port}/`);
});
server.on('error', console.error);
ts
// qwenLlm.ts
// 封装一个简单langchain LLM类
import { LLM, BaseLLMCallOptions } from '@langchain/core/language_models/llms';
import { CallbackManagerForLLMRun } from '@langchain/core/dist/callbacks/manager';
import axios, { AxiosResponse } from 'axios';
// 继承LLM类
// 根据文档必选实现_llmType和_call
export default class QwenLLM extends LLM {
constructor(options: BaseLLMCallOptions) {
super(options);
}
// 返回一个llm的唯一标识
_llmType(): string {
return 'Qwen';
}
// 接收输入,返回字符串结果
// prompt:输入文字
// options:初始化的一些选项
// runManager:执行过程中的一些hook
async _call(
prompt: string,
options: this['ParsedCallOptions'],
runManager?: CallbackManagerForLLMRun | undefined,
): Promise<string> {
const res = await axios.post<
{ prompt: string },
AxiosResponse<{ message: string }>,
{ prompt: string }
>(
'/llm',
{
prompt,
},
// 本地代理
{ baseURL: '/api' },
);
return res.data.message;
}
}
ts
// 前端很简单就是直接调用
import { Button, Card, Form, Input } from 'antd';
import QwenLLM from './qwenLlm';
import { SendOutlined } from '@ant-design/icons';
import { useState } from 'react';
// 使用方法和上一篇文章一样
const qwen = new QwenLLM({});
export function App() {
const [res, setRes] = useState<string>();
return (
<Form
wrapperCol={{ span: 6 }}
onFinish={async values => {
const { prompt } = values;
const res = await qwen.predict(prompt);
setRes(res);
}}
>
<Form.Item
label='输入'
name='prompt'
initialValue={`假设现在是2024年2月28日,从今天到5天以后的开始结束时间分别是什么,请用下面格式回答{startTime: '开始时间,格式YYYY-MM-DD', startTime: '结束时间,格式YYYY-MM-DD'}`}
>
<Input.TextArea />
</Form.Item>
<Button
type='primary'
icon={<SendOutlined />}
htmlType='submit'
>
提交
</Button>
<Card style={{ width: 300 }}>
<div>输出结果:</div>
<div>{res}</div>
</Card>
</Form>
);
}
export default App;
说实话,就这一个问题,我问了文心一言
、星火大模型
、通义千问-7B
、豆包大模型
,没有一个回答正确的。
只有通义千问-plus
、通义千问-max
升级版还能正确理解,不过有时候没有区分判断闰年。不过gpt-3.5
发挥很稳定可以正确理解格式并回答正确。
这意味着如果你可能需要更多的精力去调整你的输入。
2. 总结
如果你想用本地离线的模型,或者你自己的模型,都可以用这个原理来实现。
但是现在大模型大多数都是基于python
、PyTorch
、tenserflow
这种框架实现的,而且对于稍微准确一点的问答模型,需要的cpu
、内存
、gpu
不是一般的服务器能支持的,就算勉强支持推理速度也是问题。
短时间内如果想要应用在项目上还是的掏钱给这些服务商。
本来我想搞个本地模型试试,一想到环境装一堆,又没有linux
系统就算了,感兴趣的同学可以自己试试。
- transformers:用来快速从
huggingface
拉取模型运行模型,可以用python
做个后端。这个有transformers.js
但是由于考虑到运行环境的性能,很多模型并不支持,语言问答这个任务分类上基本上没有什么可用的模型。 - 阿里的魔搭:基本上就是翻版
huggingface
,ModelScope Library
就是和transformers
差不多的作用,从魔搭上面拉模型,快速验证运行。只有python
版本。