大模型,小探索 —— AI 帮写个飞书插件,赚个"零花钱" ^_^
微信公众号内容地址: https://mp.weixin.qq.com/s/NHRTEBYb29hLnoaiNZPFpg
AI 协同的三种模式: Embedding 嵌入、Copilot 副手 和 Agent 智能体。
随着探索的逐层深入,目前基本实验完成了大模型的 API 调用、部署和微调。
本着“既要钻研,也要吃饭,还要可持续”的思路,小狗屁这次打算探索一下 AI 协同 + 飞书插件变现。
AI 协同
未来的竞争,是 使用 AI 进行提效 和 对 AI 结果进行价值判断 的能力的竞争。
举例来解释上图,假设小刀同学擅长服务端编程和项目整体结构,那么他与 AI 的协同方式可能会是:
- Embedding——帮忙处理擅长的事情:GPT 根据细节要求帮忙写一部分 Python 或 Java 代码,帮助小刀节省时间。
- Copilot——帮忙处理不擅长的事情:GPT 根据项目需求帮忙写完整的前端网页代码,小刀再拿来和服务端整合完成项目,提升人效。
- Agent——帮忙处理有挑战的事情:GPT 根据业务理解帮忙设计、训练和部署推荐算法,供项目接入,增强能力。
需要指出的是,即使是相同水平的 AI,对不同的人来说可能也会属于不同的协同方式。
变 现
飞书多维表格 & 插件
https://www.feishu.cn/product/base
相比于 Excel,多维表格可以提供更强的在线协同能力;相比于数据库,多维表格可以提供更强的可视化展示和交互能力。
整体效果与在线表格类似,最右侧提供了插件功能。
插件开发和激励计划
插件给多维表格带来了更多操作和可能性,而激励计划也让小狗屁看到了通过大模型的协作来变现的可能 😏。
多维表格插件开发
需求
结合表格记账、运行插件计算结果的场景,小狗屁很快想到一个 idea
- 分账计算:即基于付款人和付款金额的表格,计算给出 AA 分账、互相转账的策略。
初步设计
总体设计思路也比较简单,
- 开发环境和部署测试流程:开发指南中有写,适合人工完成。
- 表格数据读取为变量:同样在开发指南中有对应的操作,适合人工完成。
- 基于输入,使用核心分账算法得到输出:为功能核心代码,且不算很难、易于测试,与外部环境独立,适合 GPT 完成;且可以与上一步骤对调,上步骤中的输入处理,向 GPT 给出的代码对齐。
- 将输出展示在表格中:需要在开发指南中找到对应的操作,适合人工完成。
- 提交插件、与飞书工作人员沟通:暂时只能人工完成。
开发环境
参照开发指南,使用 Replit 在线开发,同时飞书也提供了 UIBuilder 模板,只需要在 runUIBuilder.tsx 文件中写输入、计算、输出逻辑即可。
部署测试
参照开发指南,Replit 运行后会得到临时链接,同时在飞书多维表格中用该临时链接创建临时插件,进行测试。
需要留意以下两点:
- 需要支持国际化能力:参照 UIBuilder 的开发手册,即适配中文和英文。
- 编译:Typescript 相比于 Javascript,类型编译要求较高,因此运行之前,在 Replit 上使用 tsc && vite build 进行校验。
分账功能
具体代码细节如下:
function splitExpenses(transactions) {
// transactions是一个包含每次支付信息的数组,每个元素是一个对象,包含payer(支付者)、amount(支付金额)、participants(参与者数组)
// 创建一个对象来跟踪每个人的余额
let balances = {};
// 计算每个人的余额
transactions.forEach(transaction => {
// 支付者扣除支付金额
balances[transaction.payer] = (balances[transaction.payer] || 0) - transaction.amount;
// 参与者每人增加平均支付金额
let perParticipantAmount = transaction.amount / transaction.participants.length;
transaction.participants.forEach(participant => {
balances[participant] = (balances[participant] || 0) + perParticipantAmount;
});
});
// 找到余额最大的人和最小的人
let maxBalancePerson = Object.keys(balances).reduce((a, b) => balances[a] > balances[b] ? a : b);
let minBalancePerson = Object.keys(balances).reduce((a, b) => balances[a] < balances[b] ? a : b);
// 将最大余额的人付款给最小余额的人,直到两者之一的余额接近零
while (balances[maxBalancePerson] > 0.01 || balances[minBalancePerson] < -0.01) {
let amountToTransfer = Math.min(Math.abs(balances[maxBalancePerson]), Math.abs(balances[minBalancePerson]));
// 更新余额
balances[maxBalancePerson] -= amountToTransfer;
balances[minBalancePerson] += amountToTransfer;
// 打印每次的转账情况
console.log(`${maxBalancePerson} 支付 ${amountToTransfer.toFixed(2)} 给 ${minBalancePerson}`);
// 重新计算最大和最小余额的人
maxBalancePerson = Object.keys(balances).reduce((a, b) => balances[a] > balances[b] ? a : b);
minBalancePerson = Object.keys(balances).reduce((a, b) => balances[a] < balances[b] ? a : b);
}
}
// 示例使用
const transactions = [
{ payer: "PersonA", amount: 30, participants: ["PersonA", "PersonB", "PersonC"] },
{ payer: "PersonB", amount: 20, participants: ["PersonA", "PersonB", "PersonC"] },
{ payer: "PersonC", amount: 10, participants: ["PersonA", "PersonB", "PersonC"] },
];
splitExpenses(transactions);
输入部分
由分账功能的代码可以看出,需要将表格数据转换成 transactions 字典数组。因此,输入部分主要分为两步:
- 提供插件的界面,让用户指明付款人和付款金额字段;
- 以及将这两列的数据挨个读取,并转换成目标数组
export default async function main(uiBuilder: UIBuilder, { t }: UseTranslationResponse<'translation', undefined>) {
uiBuilder.form(form => ({
formItems: [
form.tableSelect('table', { label: `${t('Select data table')}` }),
form.viewSelect('view', { label: `${t('Select view')}`, sourceTable: 'table' }),
form.fieldSelect('payerField', { label: `${t('Select field for payer')}`, sourceTable: 'table', multiple: false }),
form.fieldSelect('amountField', { label: `${t('Select field for amount')}`, sourceTable: 'table', multiple: false }),
form.select('modeSelect', { label: `${t('Select the way to show results')}`, options: [{ label: `${t('Only Text')}`, value: 'Text' }, { label: `${t('Text + new field in table')}`, value: 'Table' }], defaultValue: 'Text' }),
],
buttons: [`${t('Submit')}`],
}), async ({ values }) => {
const { table, view, payerField, amountField, modeSelect } = values as { table: ITable, view: IView, payerField: ITextField | IUserField, amountField: INumberField | ICurrencyField, modeSelect: string };
/**
基于借贷记账法,实现分账功能;保持每次支付都是由一个人单独支付,但是通过互相转账,实现每个人都支付了相同的金额
*/
const recordIdList: (string | undefined)[] = await view.getVisibleRecordIdList();
// 支付记录 payer:amount
let transactionMap: { [key: string]: number } = {}
// 遍历,并更新支付记录
for (let i = 0; i < recordIdList.length; i++) {
const payerValue = await payerField.getValue(recordIdList[i] as string);
const amountValue = await amountField.getValue(recordIdList[i] as string);
if (isNullOrUndefined(payerValue) || isNullOrUndefined(amountValue)) {
continue;
}
// 付款人 支持 文本字段 || 人员字段
const payer = (payerValue[0] as IOpenTextSegment).text! || (payerValue[0] as IOpenUser).name!
// 金额 支持 货币字段 || 数字字段
const amount = amountValue;
transactionMap[payer] = (transactionMap[payer] || 0) + amount;
}
// 生成transactions transactions = [{ payer: "PersonA", amount: 30, participants: ["PersonA", "PersonB", "PersonC"] },...]
const transactions = Object.keys(transactionMap).map(payer => {
return {
payer,
amount: transactionMap[payer],
participants: Object.keys(transactionMap)
}
})
// 调用分账计算算法
const results = splitExpenses(transactions);
...
输出部分
同样由分账算法可知,输出的内容其实也是一个三元字典数组,每一行是是 payer 需要付给 payee 的金额为 amount。
// text输出结果
results.forEach(result => { uiBuilder.text(`${result.payer} ${t('pay')} ${result.amount} ${t('for')} ${result.payee}`) });
整体效果
总 结
本次分账计算插件的开发过程中,AI 的协同模式可以认为是Embedding,主要帮助缩短了开发时间。
实际上,利用微调,还可能更进一步,即让 GPT 完成开发指南中内容的学习,进而尝试完成整套代码的开发,而人则可以解放双手,负责创意和想法。这就有待小狗屁的继续探索了~.~。