人工智能TODO应用程序演示https://ivan-tolkunov–surukoto-run.modal.run/(警告:该应用程序可能需要长达30秒才能启动)。所有数据在不活动5分钟后重置。试着告诉它:“添加彩虹的每一种颜色”,然后“标记所有提到绿色和紫色之间的待办事项”和“清理完成的待办事项。”
每个人都在构建TODO应用程序,以便开始使用编程语言或技术。我问自己一个问题:在人工智能时代,TODO应用程序会是什么样子?
所以我想出了一个主意,构建一个TODO应用程序,你可以简单地与之交谈并给出指示。我从一个简单的用例开始,告诉应用程序“将牛奶添加到购物清单中”。但后来我意识到,使用现代LLMs,我还可以让应用程序根据用户命令检查TODO项目或删除它们。这个应用程序使用起来真的很有趣!
它是一个简单的Django网络应用程序。模型非常简单:
from django.db import models
class Todo(models.Model):
title = models.CharField(max_length=100)
created_at = models.DateTimeField('Created', auto_now_add=True)
update_at = models.DateTimeField('Updated', auto_now=True)
isCompleted = models.BooleanField(default=False)
def __str__(self):
return self.title
然而,在后端,我有一个接受语音命令的端点,而不是典型的CRUD(创建、读取、更新、删除)操作:
from django.urls import path
from . import views
app_name='todos'
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('process-voice-command/', views.process_voice_command, name='process_voice_command'),
]
端点是使用简单的HTML音频API从前端触发的:
const recordButton = document.getElementById("record");
const recordButtonText = document.getElementById("record-text");
let recorder = null;
recordButton.onclick = async () => {
if (recorder) {
recorder.stop();
recorder = null;
return;
}
const chunks = [];
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
recorder = new MediaRecorder(stream);
recorder.ondataavailable = (e) => chunks.push(e.data);
recorder.onstop = async () => {
const blob = new Blob(chunks, { type: "audio/webm;" });
const formData = new FormData();
formData.append("audio_file", blob, "voice-command.webm");
const response = await fetch("/todos/process-voice-command/", {
method: "POST",
body: formData,
});
window.location.reload();
};
recorder.start();
};
当用户提交他们的音频时,我使用Whisper来转录它(使用 medium.en 模型在大小和理解我的英语能力之间取得良好平衡):
def process_voice_command(request):
audio_file = request.FILES.get('audio_file', None)
file_name = default_storage.save(voice.name, voice)
try:
audio = whisper.load_audio(MEDIA_ROOT + file_name)
audio = whisper.pad_or_trim(audio)
result = self.model.transcribe(MEDIA_ROOT + file_name)
text = result["text"].strip()
Todo.objects.create(title=text)
finally:
default_storage.delete(file_name)
使用LLM使TODO应用程序执行操作
我不只是说我想做什么,而是想告诉TODO应用程序为我做事。例如,当我买牛奶时,我想告诉应用程序:“我买了牛奶”,并让它为我核对待办事项。
为了做到这一点,我使用了一个预先训练过的LLM和一些巧妙的提示工程。
我最初的计划是在本地运行LLM。然而,由于圣诞节假期,我的时间很短,所以我现在决定使用托管版本。我绝对不想被OpenAI锁定,所以我找到了一个强大的开源模型( mixtral-8x7b-instruct ),并使用OpenRouter查询托管版本。
这个想法的要点是:我有一个系统提示,向LLM解释它的任务是管理TODO。作为输出,我告诉模型我需要一个JSON格式的指令列表。指令可以是 add 、 complete 、 delete 和 error (当不确定要做什么时)。
首先,我使用OpenRouter聊天UI测试了一些提示。当我对提示感到满意时,我用所需的数据进行了一个简单的API调用
def get_command(self, text):
response = requests.post(
url="https://openrouter.ai/api/v1/chat/completions",
headers= {
"Authorization" : f"Bearer {os.environ['OPENROUTER_KEY']}"
},
data=json.dumps({
"model": "mistralai/mixtral-8x7b-instruct",
"messages": [
{"role": "system", "content": self.prompt + self.todo_to_string()},
{"role": "user", "content": text},
]
})
)
print(response.json()["choices"][0]["message"]["content"])
return json.loads(response.json()["choices"][0]["message"]["content"])
然后,根据响应,我让它对数据库执行以下操作:
def process_voice_command(request):
audio_file = request.FILES.get('audio_file', None)
text = util.get_voice_text(audio_file)
commands = util.get_command(text)
for command in commands:
action = command['action']
if action == "add":
util.add(command['text'])
elif action == "complete":
util.complete(command['task_id'])
elif action == "delete":
util.delete(command['task_id'])
elif action == "error":
messages.add_message(request, messages.ERROR, command['text'])
else:
print(f"Unknown action: {action}")
return JsonResponse({'success': True})
这并不完美。主要问题是它容易提示注入,即用户可以告诉它做一些不应该做的事情,作为TODO文本的一部分。我还注意到,它甚至可以将TODO的文本解释为指令的一部分,而且事情变得非常混乱。
但我很惊讶这一切的效果如此之好!
我能够为我的TODO应用程序提供如下命令:
- 添加彩虹的每一种颜色
- 标记所有提到绿色和紫色之间颜色的待办事项
- clean up completed todos 清理已完成的待办事项
- 现在我的TODO应用程序拥有超能力!
与我的其他项目类似,我将该应用程序部署到Modal.com,供每个人玩。它使用容器内的本地SQLite数据库和5分钟 container_idle_timeout ,因此数据会在一段时间后重置。非常适合演示!
完整的源代码可在https://github.com/ivan-tolkunov/surukoto.欢迎PR!