使用LLMs为您的移动应用程序提供语音和自然语言输入
使用LLMs提供语音和自然语言输入的移动应用程序
如何利用OpenAI GPT-4函数来导航您的GUI
导言
大型语言模型(LLM)是一种能够有效处理自然语言的机器学习系统。目前最先进的LLM是GPT-4,它为ChatGPT的付费版本提供支持。本文将介绍如何使用GPT-4函数调用为您的应用程序提供高度灵活的语音解释,并与应用程序的图形用户界面(GUI)完全协同工作。本文适用于产品所有者、用户体验设计师和移动开发人员。
背景
移动电话(Android和iOS)上的数字助手由于多种原因未能受到青睐;其中包括它们的故障、功能有限且常常令人厌烦。LLM现在尤其是OpenAI GPT-4具有潜力在这方面产生影响,它们能够更深入地理解用户的意图,而不是粗略地模式匹配口头表达。
Android拥有Google助手的“应用程序操作”,而iOS拥有SiriKit意图。它们提供了简单的模板,您可以使用这些模板注册您的应用程序可以处理的语音请求。Google助手和Siri在过去几年中已经有了很大的改进-比您可能意识到的要多。然而,它们的覆盖范围在很大程度上取决于哪些应用程序实现了对它们的支持。尽管如此,例如,您可以使用语音在Spotify上播放您最喜欢的歌曲。然而,这些操作系统提供的服务的自然语言解释在LLM带来的这一领域的巨大进展之前就已经存在,所以现在是时候利用LLM的能力使语音输入更加可靠和灵活了。
虽然我们可以预期操作系统服务(如Siri和Google助手)很快会调整它们的策略以利用LLM的优势,但我们已经可以让我们的应用程序在不受这些服务限制的情况下使用语音。一旦您采用了本文中的概念,您的应用程序也将准备好在新的助手可用时使用。
您选择的LLM(GPT、PaLM、LLama2、MPT、Falcon等)确实会影响可靠性,但您在这里学习的核心原则可以应用于其中任何一个。我们将允许用户通过一次表达来访问应用程序的所有功能。LLM将自然语言表达映射到我们应用程序的导航结构和功能上的函数调用。而且它不必是像机器人一样说的句子。LLM的解释能力允许用户像人类一样说话,使用他们自己的词语或语言,犹豫、犯错误并纠正错误。当用户因为语音助手经常无法理解他们的意思而拒绝使用时,LLM的灵活性可以使交互感觉更加自然和可靠,从而提高用户采用率。
为什么在您的应用程序中使用语音输入,为什么现在?
优点:
- 通过一次语音表达导航到屏幕并提供所有参数
- 学习曲线较浅:用户无需查找数据在应用程序中的位置或如何操作GUI
- 免提
- 互补而不是无关(如语音用户界面或VUI):语音和GUI协同工作
- 适用于视觉障碍者
- 现在:因为通过LLM对自然语言的解释已经达到了新的水平,所以响应更加可靠
缺点:
- 说话时的隐私
- 准确性/误解
- 仍然相对较慢
- 知识在头脑中而不是在世界中(我可以说什么?):用户不知道系统理解和回答的口语表达
可以从语音输入中受益的应用示例包括用于汽车或自行车驾驶辅助的应用。通常情况下,当用户无法轻松使用双手时,例如在移动中、戴手套或忙于手工作业时,他们可能不想通过触摸来精确导航应用。
购物应用也可以从这个功能中受益,因为用户可以用自己的话来表达他们的需求,而不是通过购物屏幕和设置筛选器进行导航。
当将这种方法应用于增加视觉障碍人士的可访问性时,您可能要考虑添加自然语言输出和文本转语音功能。
您的应用
下图显示了一个典型应用的导航结构,以一个您可能熟悉的火车行程规划应用为例。顶部显示了触摸导航的默认导航结构。此结构由导航组件控制。所有导航点击都委托给导航组件,然后执行导航操作。底部展示了我们如何使用语音输入来利用这个结构。
用户说出他们想要的内容,然后语音识别器将语音转换为文本。系统构建一个包含该文本的提示,并将其发送给LLM。LLM向应用程序响应数据,告诉它激活哪个屏幕以及使用哪些参数。这个数据对象被转换成深链接,并交给导航组件。导航组件使用正确的参数激活正确的屏幕:在这个例子中,是带有参数“阿姆斯特丹”的“外出”屏幕。请注意,这是一个简化。我们将在下面详细介绍细节。
许多现代应用程序在幕后都有一个集中的导航组件。Android有Jetpack Navigation,Flutter有Router,iOS有NavigationStack。集中的导航组件允许深链接,这是一种技术,允许用户直接导航到移动应用程序中的特定屏幕,而不是通过应用程序的主屏幕或菜单。为了使本文中的概念起作用,导航组件和集中的深链接不是必需的,但它们可以使概念的实现更容易。
深链接涉及创建一个指向应用程序中特定内容或特定部分的唯一(URI)路径。此路径还可以包含控制深链接指向的屏幕上GUI元素状态的参数。
用于您的应用的函数调用
我们通过提示工程技术告诉LLM将自然语言表达式映射到导航函数调用。基本上,提示内容类似于:“给定以下具有参数的函数模板,将以下自然语言问题映射到其中一个函数模板并返回它。”
大多数LLM都能够做到这一点。LangChain通过零-shot ReAct Agents和所谓的Tools有效地利用了它。OpenAI已经使用特殊版本(当前为gpt-3.5-turbo-0613和gpt-4-0613)对其GPT-3.5和GPT-4模型进行了微调,这些模型在这方面非常出色,并为此目的制定了特定的API入口。在本文中,我们将采用OpenAI的符号表示法,但这些概念也可以应用于任何LLM,例如使用上面提到的ReAct机制。此外,LangChain在底层具有特定的代理类型(AgentType.OPENAI_FUNCTIONS),它将Tools转换为OpenAI函数模板。对于LLama2,您将能够使用与OpenAI相同的语法使用llama-api。
LLM的函数调用工作原理如下:
- 您在提示中插入函数模板的JSON模式,以及用户的自然语言表达式作为用户消息。
- LLM尝试将用户的自然语言表达式映射到其中一个模板。
- LLM返回生成的JSON对象,以便您的代码可以进行函数调用。
在本文中,函数定义直接映射到(移动)应用程序的图形用户界面(GUI),其中每个函数对应一个屏幕,每个参数对应该屏幕上的GUI元素。发送到LLM的自然语言表达式返回一个包含函数名称及其参数的JSON对象,您可以使用该对象导航到正确的屏幕,并在视图模型中触发正确的函数,从而获取正确的数据并根据参数设置该屏幕上相关GUI元素的值。
这在下面的图中说明:
它显示了添加到LLM提示符的函数模板的简化版本。要查看用户消息“在阿姆斯特丹可以做什么?”的完整长度提示,请点击此处(Github Gist)。它包含一个完整的curl请求,您可以从命令行中使用或导入到Postman中。您需要在占位符中放入自己的OpenAI密钥才能运行它。
无需参数的屏幕
您的应用程序中的某些屏幕没有任何参数,或者至少没有LLM需要知道的参数。为了减少标记使用和混乱,我们可以将许多这些屏幕触发器合并为一个具有一个参数的函数:要打开的屏幕
{ "name": "show_screen", "description": "确定用户想要查看的屏幕", "parameters": { "type": "object", "properties": { "screen_to_show": { "description": "要显示的屏幕类型。可以是'account':用户的所有个人数据,'settings':如果用户想要更改应用程序的设置", "enum": [ "account", "settings" ], "type": "string" } }, "required": [ "screen_to_show" ] }},
触发函数是否需要参数的标准是用户是否有选择权:屏幕上正在进行某种搜索或导航,即屏幕上是否有任何搜索(类似)字段或选项卡可供选择。
如果没有,那么LLM不需要知道它,并且屏幕触发可以添加到应用程序的通用屏幕触发函数中。这主要是通过对屏幕用途的描述进行实验。如果您需要更长的描述,可以考虑给它自己的函数定义,以在描述上更加分离地强调它,而不是使用通用参数的枚举。
提示指导和修复:
在提示的系统消息中,您提供了通用的指导信息。在我们的示例中,这可能对LLM知道现在的日期和时间很重要,例如,如果您想计划明天的旅行。另一个重要的事情是引导其假设性。通常,我们宁愿LLM过于自信,也不愿打扰用户的不确定性。我们示例应用程序的一个好的系统消息是:
"messages": [ { "role": "system", "content": "当前日期和时间为2023-07-13T08:21:16+02:00。在猜测函数参数的值时,要非常自信。" },
函数参数的描述可能需要进行相当多的调整。例如,在计划火车旅行时,trip_date_time就是一个例子。一个合理的参数描述是:
"trip_date_time": { "description": "要求出发或到达行程的日期时间,格式为'YYYY-MM-DDTHH:MM:SS+02:00'。用户将使用12小时制的时间,根据24小时制的智能猜测,推测用户最有可能是什么意思,例如不计划过去。", "type": "string" },
因此,如果现在是15:00,用户说他们想在8点离开,他们的意思是20:00,除非他们特别提到了白天的时间。上述指示对于GPT-4来说效果还不错。但在某些边缘情况下,它仍然失败。然后,我们可以在函数模板中添加额外的参数,以便在我们自己的代码中进行进一步修复。例如,我们可以添加:
"explicit_day_part_reference": { "description": "始终首选'none'!如果请求是指当前日期,则为None;否则为请求所指的一天的一部分。" "enum": ["none", "morning", "afternoon", "evening", "night"], }
在您的应用程序中,您可能会发现需要进行后处理以提高其成功率的参数。
系统请求澄清
有时用户的请求缺乏信息以继续。可能没有适合处理用户请求的功能。在这种情况下,LLM将以自然语言进行回应,您可以将其显示给用户,例如通过Toast。
LLM可能也会识别到一个潜在的可调用函数,但缺少填充所有必需的函数参数所需的信息。在这种情况下,如果可能的话,考虑将参数设为可选。但是,如果不可能,LLM可能会以用户的语言发送一条请求,请求缺失的参数。您应该向用户显示此文本,例如通过Toast或文本到语音,以便他们可以提供缺失的信息(口头)。例如,当用户说“我想去阿姆斯特丹”(并且您的应用程序没有通过系统消息提供默认或当前位置)时,LLM可能会回应“我明白您想要进行一次火车旅行,您从哪里出发?”。
这引出了对话历史的问题。我建议您始终在提示中包含用户的最后4条消息,以便可以在多个轮次中传递信息请求。为简化事情,只需从历史记录中省略系统的响应,因为在此用例中,它们往往会弊大于利。
语音识别
语音识别是将语音转化为应用程序中参数化导航操作的关键部分。当解释质量较高时,劣质的语音识别可能是最薄弱的环节。移动电话具有内置的语音识别功能,质量合理,但基于LLM的语音识别,如Whisper、Google Chirp/USM、Meta MMS或DeepGram往往能够获得更好的结果。
架构
最好将函数定义存储在服务器上,但也可以由应用程序管理并随每个请求发送。两者各有利弊。随每个请求发送它们更灵活,函数和屏幕的对齐可能更容易维护。然而,函数模板不仅包含函数名称和参数,还包含我们可能希望比应用程序商店的更新流程更快地更新的描述。这些描述或多或少依赖于LLM,并为有效工作而设计。您可能希望在某些时候更换LLM为更好或更便宜的一个,甚至动态地进行交换。如果您的应用程序在iOS和Android上是原生的,则将函数模板放在服务器上可能还具有将其在一个地方维护的优势。如果您同时使用OpenAI的语音识别和自然语言处理服务,则流程的技术大局如下所示:
用户发出他们的请求,它被记录到一个m4a缓冲区/文件(或者您喜欢的mp3),然后发送到您的服务器,服务器将其中继到Whisper。Whisper以转录形式回复,并将其与系统消息和函数模板结合成LLM的提示。您的服务器接收原始函数调用JSON,然后将其处理为您的应用程序的函数调用JSON对象。
从函数调用到深链接
为了说明函数调用如何转换为深链接,我们以初始示例的函数调用响应为例:
"function_call": { "name": "outings", "arguments": "{\n \"area\": \"Amsterdam\"\n}" }
在不同的平台上,处理方式有很大不同,而且随着时间的推移,许多不同的导航机制已被使用,并且通常仍在使用中。本文不涉及实现细节,但大致上来说,各个平台在其最新版本中可以采用如下的深链接:
在Android上:
navController.navigate("outings/?area=Amsterdam")
在Flutter上:
Navigator.pushNamed( context, '/outings', arguments: ScreenArguments( area: 'Amsterdam', ), );
在iOS上,情况稍微不太标准化,但可以使用NavigationStack:
NavigationStack(path: $router.path) { ...}
然后执行以下操作:
router.path.append("outing?area=Amsterdam")
有关深层链接的更多信息,请参见:Android,Flutter,iOS
应用程序的自由文本字段
有两种自由文本输入模式:语音和输入。我们主要讨论了语音,但输入文本字段也是一种选择。自然语言通常很长,因此可能很难与GUI交互竞争。然而,GPT-4往往能够从缩写中猜测参数,因此即使是非常短的缩写输入通常也可以正确解释。
在提示中使用带有参数的函数通常会大大缩小LLM的解释上下文。因此,它需要很少的上下文,如果您指示它具有假设性,那就更少。这是移动交互的一个新现象,有着很大的潜力。在火车站到火车站规划器的例子中,当使用本文中的示例提示结构时,LLM进行了以下解释。您可以使用上面提到的提示清单自行尝试。
例子:
‘ams utr’:显示从阿姆斯特丹中央车站到乌特勒支中央车站的火车行程列表,从现在开始
‘utr ams arr 9’:(假设现在是13:00)。显示从乌特勒支中央车站到阿姆斯特丹中央车站的火车行程列表,到达时间在21:00之前
后续交互
就像在ChatGPT中一样,如果您在交互历史中发送一小段内容,您可以进一步完善您的查询:
使用历史功能,以下内容也非常有效(假设现在是早上9点):
输入:‘ams utr’,并获得与上述相同的答案。然后在下一个回合中输入‘arr 7’。是的,它实际上可以将其翻译为从阿姆斯特丹中央到乌特勒支中央的计划行程,到达时间在19:00之前。我制作了一个关于此内容的示例Web应用程序,您可以在此处找到有关此应用程序的视频。实际应用程序的链接在说明中。
未来
您可以期望此深层链接结构处理应用程序中的函数成为手机操作系统(Android或iOS)的一个组成部分。手机上的全局助手将处理语音请求,应用程序可以向操作系统公开其函数,以便以深层链接的方式触发它们。这与ChatGPT提供插件的方式相似。显然,现在已经通过AndroidManifest中的意图和Android上的App Actions以及iOS上的SiriKit意图提供了此功能的粗略形式。您对这些的控制权有限,并且用户必须像机器人一样说话才能可靠地激活它们。毫无疑问,随着由LLM提供动力的助手的普及,这种情况将会改善。
VR和AR(XR)为语音识别提供了巨大的机会,因为用户的手通常忙于其他活动。
不久之后,任何人都可以运行自己的高质量LLM。成本将会降低,速度将会迅速提高。很快,LoRA LLM将会在智能手机上可用,因此推理可以在您的手机上进行,从而降低成本和速度。此外,将出现越来越多的竞争,包括开源项目(如Llama2)和闭源项目(如PaLM)。
最后,模态的协同作用可以进一步推动,而不仅仅是提供对整个应用程序GUI的随机访问。LLM的强大之处在于结合多个来源,这为更好的辅助提供了希望。一些有趣的文章:多模态对话,Google关于GUI和LLM的博客,将GUI交互解释为语言。
结论
在本文中,您学习了如何应用函数调用以使应用程序具备语音功能。使用提供的Gist作为出发点,您可以在Postman中或从命令行中进行实验,以了解函数调用的强大之处。如果您想在应用程序中运行一个语音启用的POC,我建议直接将架构部分中的服务器部分放入您的应用程序中。所有这些都归结为2个HTTP调用,一些提示构建以及实现麦克风录制。根据您的技能和代码库,您将在几天内使POC投入运行状态。
愉快的编码!
关注我的领英
本文中的所有图片(除非另有说明)均由作者提供