前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Text-to-DSL,为您的系统搭载基于ChatGPT的自然语言交互模块

Text-to-DSL,为您的系统搭载基于ChatGPT的自然语言交互模块

作者头像
腾讯灯塔小明
发布2023-03-30 11:10:30
4.3K0
发布2023-03-30 11:10:30
举报
文章被收录于专栏:敏捷分析

前言

随着ChatGPT的横空出世,很多人应该都感觉到新的人工智能时代即将到来。

但随着最初的兴奋感退却,我们会发现ChatGPT好像只能输出文本,难以应用到其他系统上。

诚然微软爸爸随即推出了new bing 和office 365 copilot,但这种产品大概率是在现有大语言模型基础上,用bing/offce的数据进行fine tune(微调)得到的。但目前gpt3.5以上的模型还没开放fine tune。

那么全面人工智能时代还要再等等?

先说结论:不必等了,在现阶段,基于Prompt 的Text-to-DSL技术可以作为大语言模型与任何传统计算机系统的桥梁。所有基础设施已经准备完毕,只待想法和工程实现

在本文中,笔者将总结并分享最新收集到的Text-to-DSL技术知识;同时结合实战,搭建DataLink自然语言交互demo(输入自然语言,生成DataLink工作流)。

通过阅读本文,可能会对您有所启发,例如以下idea:

  1. 1. 使用自然语言生成各种画布,比如:DataTalk/蓝盾/DataHub/venus
  2. 2. 使用自然语言调用您的OpenApi
  3. 3. 使用自然语言控制手机完成复杂操作
  4. 4. 使用自然语言生成stable diffusion的tag和骨架代码(你懂的),再进一步生成图片

以及等等其他疯狂的想法,简直无所不能。

本文主要内容如下:

  1. 1. Text-to-DSL介绍
  2. 2. prompt 工程实战(DataLink自然语言交互demo)
  3. 3. 笔者思考、杂谈
  4. 4. 参考资料

Text-to-DSL介绍

DSL

DSL是Domain Specific Language的缩写,也即 领域特定语言,是为了解决某一领域问题专门设计的高抽象编程语言,通常由领域专家而非编程人员使用。

别看概念这么高大上,其实我们经常接触,比如:

  1. 1. SQL:为数据库查询设计的语言
  2. 2. 某个api的请求体:为调用该api设计的语言
  3. 3. 某个软件的命令行指令:为使用该软件设计的语言 json, xml, yaml是DSL的常用格式。

DSL常用于系统间交互。设计良好的DLS能让我们的系统更加易用。

Text-to-DSL

DSL是语言,语言是文本,而大语言模型能输出文本。所以,想办法让模型输出我们想要的DSL,再用DSL去调用其他系统,这就是自然语言人机交互的原理。

让模型把自然语言转化成DSL的技术就是Text-to-DSL。

fine tune和prompt

Text-to-DSL有两种实现方式:fine tune和prompt

  1. 1. fine tune:在原有模型基础上加入新数据,对模型进行参数微调和重新训练,使模型拥有新的能力。(举个例子,2040年出了新的编程语言叫Tyrsong Program Language,这时就要让ChatGPT进行fine tune,才能使他会写TPL)
  2. 2. prompt:大语言模型本质上是一种文本填充(Completion),既然是填充,就要预先提供一些文本,这些预提供的文本就叫prompt,模型返回的文本叫completion。 如果把大语言模型比作人,那么fine tune是生成了长期记忆,而prompt是生成中期记忆(或叫工作记忆 working memory)。

两种方式相比,fine tune灵活性差,稳定性较强,可以处理复杂问题。而prompt灵活性强,稳定性较差,难以处理复杂问题(注意是难以处理,不是不能处理,后面我们将演示如何用工程手段解决这个问题)。

目前OpenAI没有开放gtp3.5 模型的fine tune。而模型api天然支持prompt,OpenAI最新开放的插件能力就是基于prompt实现的。

成本方面,fine tune虽然需要预支付训练费用,而且自定义模型(fine tune过的模型)的token单价更高,但是,使用自定义模型时需要的prompt变少了,token数量少了。所以最终成本和纯prompt相比如何,要视具体定价和具体项目而定。当前已开放模型的fine tune定价可参考: https://openai.com/pricing#language-models

(题外话,理论上还有一种白嫖的方法,就是把我们的DSL变成公共知识,使得模型在下一次迭代时带上我们的数据进行训练。现在知道数据开放的好处了吧?)

想让模型输出我们想要的稳定准确的DSL,编写prompt是其中的关键。目前已经兴起一门新的学科叫Prompt Engineering,是一门研究如何高效、准确、安全编写prompt的学科。有兴趣可以看这里: https://github.com/dair-ai/Prompt-Engineering-Guide

虽然prompt有不少限制,但是由于prompt的灵活性,再加上Prompt Engineering以及与传统Software Engineering的结合,使得prompt成为现阶段Text-to-DSL的最便捷方案。

此外就算打算用fine tune,前期的prompt调试也是必不可以少的。良好的prompt调试能筛选更好的数据,使得fine tune成本更低、效果更好。

下面将开始结合Prompt Engineering和传统Software Engineering,演示如何搭建一个DataLink,自然语言交互demo(输入自然语言,生成DataLink工作流)。

Prompt工程实战 (DataLink自然语言交互demo)

为什么选择DataLink来做demo?

因为DataLink已经有一套较高语议化的DSL用来描述工作流,不需要额外设计;其次DataLink的复杂度刚刚好;最后是因为DataLink是笔者开发的,比较熟悉。

下面直接用DataLink来演示。各位读者自行代入自己的系统即可。

准备工作

  1. 1. https://platform.openai.com/ 到OpenAI官网注册一个账号并创建一个Api Key。
  2. 2. 选择一门语言来调用OpenAI的api。Prompt工程的核心还是不是编程语言,选择自己熟悉的语言就好。本文使用java,方便接入DataLink中。 附:各语言的OpenAI SDK https://platform.openai.com/docs/libraries
  3. 3. 为系统设计一门DSL,最好是简洁、清晰、语义化强,到后面我们就会发现这有多重要。(因为DataLink已经有了一套DSL,就不重新设计了)。
  4. 4. 可以简单读一下chat completion api的简单说明 https://platform.openai.com/docs/guides/chat/introduction 。官方建议,chat completion在大部分场景都要优于completion( https://platform.openai.com/docs/guides/chat/chat-vs-completions ),所以我们优先使用chat completion。

确认DSL

首先要确认DSL的结构,最好能有完整的schema说明(如果没有就靠猜吧,语义化越高,越不需要schema说明)。

DataLink的工作流DSL大概是这样:

代码语言:javascript
复制
/variables: {}
triggers:
  - triggerManual_1021:
      name: 手动触发
      code: triggerManual
      version: 1.0.0
  - triggerTiming_1022:
      name: 定时触发
      code: triggerTiming
      version: 1.0.0
      startTime: 0
      endTime: 0
      intervalType: day
      weekdays: []
      weekdayRunTime: ''
      runType: loop
process:
  - query_my_table:
      name: 查询my_table
      code: MixQuerySQLComponent
      version: 1.1.0
      tryTimes: 1
      failedAction: cancel
      artifactId: null
      type: asyncComponent
      input:
        sqlString: select id, name from my_table where ds = 20230324 limit 1
        resultKey: query_result
        resultName: 查询结果
        queryType: TABLE
  - ifElse_1010:
      name: 判断id是否为808080
      code: ifElse
      version: 1.0.0
      switch:
        - condition:
            id: 1
            logic: and
            children:
              - compareField: query_result[0]['id']
                operator: contains
                compareValue: '808080'
          steps:
            - wait_1013:
                name: wait
                code: wait
                version: 1.0.0
                until: ''
                after:
                  minutes: 10
                afterType: minutes
                waitType: after
            - push:
                name: 企业微信推送
                code: BeaconWechatComponent
                version: 1.0.0
                tryTimes: 1
                failedAction: cancel
                artifactId: null
                type: syncComponent
                input:
                  receivers: tyrsong
                  crowds: ''
                  msgType: markdown
                  content: 查询到id为808080的记录
                  title: ''
                  description: ''
                  url: ''
                  picurl: ''
                  fileName: ''
                  crowdSource:
                    - crowd
                  sqlSourceBeaconWechatParam: []
            - end_1017:
                name: 结束
                code: end
                version: 1.0.0
        - label: DEFAULT
          steps:
            - end_1014:
                name: 结束
                code: end
                version: 1.0.0

可以在DataLink工作流页面点右上角按钮来查看:

可以看到工作流由两大部分组成:triggers(触发器)和process(工作流内容)。本文demo的目标就是用自然语言生成triggers里的内容。

Api调用介绍

首先我们先把api调通,并了解重要参数的含义:

代码语言:javascript
复制
public class ChatGptHelper {

    //需要申请ApiKey
    private static OpenAiService service = new OpenAiService(System.getenv("OPENAI_KEY"));

    public static ChatCompletionResult createChatCompletion(List<ChatMessage> messages) {
        ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
                //模型名称
                .model("gpt-3.5-turbo")
                //下面两项数值控制模型输出的随机性,Text-to-DSL需要准确性,因此设置为随机性为最低
                .temperature(0.0D)
                .topP(1.0)
                .messages(messages)
                .build();
        return service.createChatCompletion(chatCompletionRequest);
    }

    public static void main(String[] args) {

        //对于chat 类模型,prompt是一个对话列表
        ChatMessage[] prompt = {
                new ChatMessage(ChatMessageRole.SYSTEM.value(), "你是一个翻译器,我输入中文,你翻译成英文"),
                new ChatMessage(ChatMessageRole.USER.value(), "今天天气真好啊")
        };

        ChatCompletionResult result = ChatGptHelper.createChatCompletion(
                Arrays.asList(prompt)
        );
        System.out.println(result.getChoices().get(0).getMessage().getContent());
    }
}

Output

代码语言:javascript
复制
The weather is really nice today.

上述代码中,我们调用了一次chat completion api,让模型帮我们翻译了一句话。有几个需要注意的点:

temperature和topP

如注释里所说,我们固定传0和1

getChoices().get(0)

getChoices返回一个数组,但在我们的demo中永远只需要第一个元素

prompt数组

首先在chat 模型中,prompt是以一个对话列表形式提供的。每一个Message都可以指定一个role,role有三种取值,分别是System/User/Assistant。

虽然叫chat 模型,但实际上api调用是无状态的,模型并不存储聊天的上下文。在多轮对话中,每一次api调用都要把之前完整的对话做为prompt传过去。

可以看到我们实际上能够冒充模型(Assistant)发言。在Text-to-dsl中,冒充模型发言以此约束后续模型输出是非常有用的优化手段同。了解这一点对于了解后续的prompt优化原理非常重要。

一个最简单的Text-to-DSL prompt

现在我们开始尝试调教模型,先给出一个最简单的例子,然后尝试问他一个问题

为了方便编辑,我们把prompt存放在prompt.md文件中

prompt.md

代码语言:javascript
复制
请跟据我的需求创建一个yaml格式的工作流触发器。

我的需求
创建一个手动触发器

你需要回答
```yaml
# 声明一个触发器
triggers:
# 触发器id为my_trigger
- my_trigger:
    # 触发器名称
    name: 手动触发
    # 触发器类型是手动触发。如果是定时触发而是triggerTim
    code: triggerManual
    version: 1.0.0
` ` `

调用代码如下:

代码语言:javascript
复制
public static void main(String[] args) throws Exception {
    String request = "创建一个定时触发器,名字叫定时发早报";
    ChatMessage[] prompt = {
            new ChatMessage(ChatMessageRole.USER.value(), Utils.readFromRes("/prompt.md")),
            new ChatMessage(ChatMessageRole.USER.value(), request)
    };
    ...//调用api并输出结果
}

我们来看下效果

Output

代码语言:javascript
复制
好的,以下是创建一个定时触发器的yaml格式代码:

```yaml
# 声明一个触发器
triggers:
# 触发器id为my_trigger
- my_trigger:
    # 触发器名称
    name: 定时发早报
    # 触发器类型是定时触发。如果是手动触发则为triggerManual
    code: triggerTiming
    version: 1.0.0
    # 定时触发的时间表达式,这里是每天早上8点触发
    cron: '0 8 * * *'
` ` `

你可以根据需要修改cron表达式来设置不同的定时触发时间。

有好有坏。

好消息是模型正确地生成了name和code字段(因为我们在prompt的ymal注释中教过他)

坏消息是模型输出了一些废话,同时把```yaml   ```` 这种格式化字符也输出了,另外还把注释原封不动地搬过来了,最后还自作主张地加了cron字段,我们并没有教他这个字段。

(实际上DataLink的定时触发支持cron和非cron的写法,模型居然懵对了,可见语义化DSL的重要性。不过,为了说明问题,我们暂时假定DataLink不支持cron写法)

这些问题接下来一步步优化。

约束模型输出

我们来尝试让模型输出干净的yaml。最直观的做法就是在prompt中告诉模型:

代码语言:javascript
复制
直接输出yaml,不要添加其他字符,不要带注释,不要添加新字段

其实这些"不要xxx"基本是没有效果的,输出的内容怎么调整也不干净。各位读者可以自行实验,本文不做演示

正确的做法是告诉模型“要做什么”,而不是“不要做什么”。参考该文章:

https://github.com/dair-ai/Prompt-Engineering-Guide/blob/main/guides/prompts-intro.md#to-do-or-not-to-do

具体的做法是,我们要给模型一个输出示例。这个示例中没有注释等内容,就是我们希望模型输出的干净的yaml。只要prompt组织得当,模型就会按着示例输出。

虽然我们在prompt里已经有了一个示例,但这个示例是用于说明各字段含义的,用注释来做这件事刚刚好,我们并不想去掉注释。

其实有另一个更好的方式来约束模型的输出,就是上一节提到的冒充模型发言。

现在创建一个假冒发言:

prompt.yaml

代码语言:javascript
复制
triggers:
  - my_trigger:
      name: 手动发早报
      code: triggerManual
      version: 1.0.0 

这是我们希望的干净的yaml

prompt.md做一些修改

prompt.md

代码语言:javascript
复制
..... (省略prompt.md前面的内容)

现在,创建一个手动触发器,名字是手动发早报

调用代码做相应修改

代码语言:javascript
复制
public static void main(String[] args) throws Exception {
    String request = "创建一个定时触发器";
    ChatMessage[] prompt = {
            new ChatMessage(ChatMessageRole.USER.value(), Utils.readFromRes("/prompt.md")),
            new ChatMessage(ChatMessageRole.ASSISTANT.value(), Utils.readFromRes("/prompt-reply.yaml")),
            new ChatMessage(ChatMessageRole.USER.value(), request)
    };
    ...//调用api并输出结果
}

这次,我们做了一些调整,在prompt.md最后面加了一个命令,同时添加了一个干净的yaml做为假冒发言。

注意在java代码中,假冒发言的role要设置为ASSISTANT。

然后看看效果:

Output

代码语言:javascript
复制
你需要回答
```yaml
# 声明一个触发器
triggers:
# 触发器id为my_trigger
- my_trigger:
    # 触发器名称
    name: 定时发早报
    # 触发器类型是定时触发。如果是手动触发则为triggerManual
    code: triggerTiming
    version: 1.0.0
    # 定时触发器的配置
    config:
      # cron表达式,表示每天早上6点触发
      cron: '0 0 6 * * ?'
      # 时区,这里设置为北京时间
      timezone: Asia/Shanghai
` ` `


现在,创建一个定时触发器,名字叫定时发早报,每天早上6点触发,时区为北京时间。

呃 。。翻车了。 可以看到模型的回复还包括"你需要回答"几个字,说明我们的prompt排版可能有点乱了,模型理解不了哪些是他要输出的,哪些是我们的指令。

我们来规范一下prompt的排版:

prompt.md

代码语言:javascript
复制
### 介绍 ###
请跟据我的需求创建一个yaml格式的工作流触发器。

### 例子 ###
我的需求
创建一个手动触发器

你需要回答
```yaml
# 声明一个触发器
triggers:
# 触发器id为my_trigger
- my_trigger:
    # 触发器名称
    name: 手动触发
    # 触发器类型是手动触发。如果是定时触发则为triggerTiming
    code: triggerManual
    version: 1.0.0
` ` `

### 命令 ###
现在,创建一个手动触发器,名字是手动发早报

这次我们加了介绍/例子/命令等标题。整个prompt看上去就清晰了。我们先做任务介绍,然后举了例子,接着抛出一个命令,最后结合prompt-reply.yaml的内容,让模型以为曾经的自己输出了干净的yaml

再看效果:

Output

代码语言:javascript
复制
triggers:
  - my_trigger:
      name: 定时发早报
      code: triggerTiming
      version: 1.0.0
      # 每天早上7点触发
      cron: "0 7 * * *"

好很多了 markdown标记和大部分注释都没了。但他自作主张的毛病还是没改。

添加样本

这种现象本质原因是模型拥有大量的公共知识以及推断能力。

这种特质有好有坏,好处我们后面会碰到,坏处就是现在看到的,自已添加了原本没有的字段。

由于我们不想模型使用cron,而是另一套表达定时任务的DSL,因此,要添加更多的示例(样本)来训练。

把新的示例添加到prompt.md中

prompt.md

代码语言:javascript
复制
需求
创建一个定时触发器。从5月5日到6月5日,每隔一天执行一次

回答

```yaml
# 声明一个触发器
triggers:
# 触发器id为my_trigger
- my_trigger:
    # 触发器名称
    name: 定时触发
    # 触发器类型是定时触发。
    code: triggerTiming
    # 定时类型是重复触发
    runType: loop
    version: 1.0.0
    # 重复单位为天
    intervalType: day
    # 重复间隔为1
    interval: 1
    # 开始时间
    startTime: '{##Date2}'
    # 结束时间
    endTime: '{##Date3}'
```aml
# 声明一个触发器
triggers:
# 触发器id为my_trigger
- my_trigger:
    # 触发器名称
    name: 定时触发
    # 触发器类型是定时触发。
    code: triggerTiming
    # 定时类型是重复触发
    runType: loop
    version: 1.0.0
    # 重复单位为天
    intervalType: day
    # 重复间隔为1
    interval: 1
    # 开始时间
    startTime: '{##Date2}'
    # 结束时间
    endTime: '{##Date3}'
` ` `

调用

代码语言:javascript
复制
String request = "创建一个定时触发器,名字叫定时发早报。从3月7号到3月10日,每隔三小时执行一次";
...//省略其他代码

Output

代码语言:javascript
复制
triggers:
  - my_trigger:
      name: 定时发早报
      code: triggerTiming
      runType: loop
      version: 1.0.0
      intervalType: hour
      interval: 3
      startTime: '2022-03-07T00:00:00Z'
      endTime: '2022-03-10T23:59:59Z'

我们治好了他自作主张的毛病。同时,推断能力强和知识丰富的好处体现出来了,模型自动把日期格式化并填入正常的字段,还写对了intervalType和interval,推断出了hour这个取值(我们都没教)。

这也是前面为什么说DSL最好有较强的语义化,因为会为prompt省很多事。

那么后面的工作就是添加例子、覆盖更多场景,最后得到完善的prompt文本。这里对text2dsl prompt设计做了一些总结:

  1. 1. 使用良好的格式编写prompt
  2. 2. 目前总的套路: 介绍 → 举例 → 命令 → 冒充发言
  3. 3. DSL语义化越强越有优势
  4. 4. 尽可能利用公共知识
  5. 5. 尽可能在少量样本中覆盖多种场景。样本与样本间尽量不要有重叠部分。 api调用费用是按照prompt的大小计算,所以我们的目的尽量用小的prompt训练出好的效果。

然而,无论我们怎么优化prompt,随着DSL复杂度的增加,总有一天会突破4K的限制。即便是gpt-4的32K,也会有用完的一天。

这也是为什么前面说prompt不太适合解决复杂问题。

但别忘了,我们还有传统的软件工程。下面就轮到传统工程手段出马了。

分而治之

各位工程师肯定已经习惯将一个复杂问题分解为多个简单问题来解决。在prompt工程中同样可以这样做。

分治法的图解如下:

首先这里提出一个叫Box的概念,一个DSLBox封装了一段prompt,用于完成特定的工作。给DSLBox输入text,它就会输出dsl。因为是模型都是黑盒的,所以叫box。

MasterBox是一种特殊的Box,用于做任务分解。一个复杂问题会先给到一个MasterBox,分解任务后输出DSL,然后经过代码逻辑的分发,分给其他Box,可以分给普通Box,也可以分给MasterBox。

逻辑代码主要负责按MasterBox分配好的任务(也是dsl描述),调度其他box(并行、串行、递归都可以),并最终收集,汇聚结果。

老工程师们应该一看就懂。

这种方案理论上可以解决无限复杂的问题。工程关键就是怎么设计MasterBox的prompt,以及其输出的DSL。

下面还是用实例介绍怎么用分治法解决复杂问题。

回到我们DataLink工作流触发器。我们现在能用自然语言创建一个触发器了,但可以看到时间转换有点问题。第一是格式不对,第二是年份不对。可以预见,要得到更精确的自然语言日期转换功能,还是要加不少prompt,为了不让prompt超标,我们打算将创建触发器和日期转换这两个功能拆分出来,用不同的Box封装,然后外面包一个MasterBox来做调度。

(实际上,再多加几个样本,再把时间转换优化,也不会超出4K的限制。但这里主要是为了展示分治法,其次也是因为抽离出来的“自然语言日期转换”可以作为一个通用Box来使用)

我们定义三个Box:

DateBox: 用于将自然语言描述的日期转成标准格式

TriggerCoreBox: 将自然语言描述的触发器转换成yaml格式,无需考虑日期转换

TriggeBox: 是一个MasterBox,将任务进行拆分,分派给DateBox和TriggerCoreBox

先看TriggeBox的prompt

prompt.md

代码语言:javascript
复制
### 介绍 ###
你是任务分派员,将我的工作流触发器创建需求拆分成两个任务。

### 例子 ###
需求:
创建一个定时触发器,在4月1日上午11点触发。

回答:
```yaml
tasks:
  # 任务1:类型是triggerCore
  - type: triggerCore
    content: 创建一个定时触发器,在{##Date0}触发
  # 任务2:类型是date,把需求中提到日期的罗列下来
  - type: date
    content:
      - 4月1日上午11点
` ` `

需求:
新建一个重复触发器,从今天下午两点开始,到5月11日下午两点,每隔8分钟或1小时触发

回答:
```yaml
tasks:
  - type: triggerCore
    content: 新建一个重复触发器,从在{##Date0}开始,到{##Date1},每隔8分钟或1小时触发
  - type: date
    content:
      - 今天下午两点
      - 5月11日下午两点
` ` `

需求:
创建一个手动触发器

回答:
```yaml
tasks:
  - type: triggerCore
    content: 创建一个手动触发器
` ` `

### 命令 ###
创建一个重触触发器,每隔5分钟触发

prompt-reply.yaml

代码语言:javascript
复制
tasks:
  - type: triggerCore
    content: 创建一个重复触发器,每隔5分钟触发

输入

代码语言:javascript
复制
创建一个重复触发器,从明天15:00到4月25日10:00,每8分钟触发一次

输出

代码语言:javascript
复制
tasks:
  - type: triggerCore
    content: 创建一个重复触发器,从{##Date0}到{##Date1},每8分钟触发一次
  - type: date
    content:
      - 明天15:00
      - 4月25日10:00

可以看到,TriggeBox将输入拆分成了两个任务。

第一个任务是triggerCore类型,会调用triggerCoreBox,不关心具体日期,所以日期用占位符替换了。

第二个任务是date类型,会调用DateBox。

这里的Box调度可以并行,提高响应速度。具体调度和汇聚代码不是重点,不在此展示。

TriggerCoreBox代码基本就是前一节的内容,这里也不再展示。

DateBox有一些技巧,将在下一节演示。

动态Prompt

在前面我们提到,用自然语言表达相对时间,模型的转换结果不正确。这里是两个原因造成的,一是模型默认的当前时间不正确,需要我们告诉他;二是模型确实对于相对时间的计算确实有偏差,需要通过prompt优化来解决。

下面是DateBox的prompt

prompt.md

代码语言:javascript
复制
### 介绍 ###
根据需求将自然语言描述的时间(UTC+08:00)格式化为yyyy-mm-dd HH:mm:ss。当前时间是%s

### 例子 ###
我给出的需求:
```yaml
request:
  - '2020年11月8日早上7点整'
  - '23-05-07 9:56 pm'
  # 没有写明年份,按当前年份算
  - '3月6日零点'
  # 不存在11月32日,输出-1
  - '2022.11.32  18:03'
  # 相对时间需要你跟据当前时间算出来
  - '明天早上九点'
  - '后天早上九点'
` ` `

你需要回答:
```yaml
- '2020-11-08 07:00:00'
- '2023-05-07 21:56:00'
- '2023-03-06 00:00:00'
- '-1'
# 相对时间需要根据当前时间算出来
- '%s'
- '%s'
` ` `

### 命令 ###
```yaml
request:
  - '98年2月8日早上10点半'
  - '22年2月8日 16点一刻'
  - '昨天下午4点'
` ` `

prompt-reply.yaml

代码语言:javascript
复制
- '1998-02-08 10:30:00'
- '2022-02-08 16:15:00'
- '%s'

prompt中用了多个%s占位符,每次调用Api时,会用当前时间去替换占位符,动态生成prompt。以此纠正模型对当前日期的认知

从动态prompt这个例子也可以看出,我们需要一款跨语言的模板引擎来协助我们的prompt开发。

其他技巧:用多个例子强化模型对相对日期的计算;兼容无效日期。读者可以自行调试验证,这里不再演示。

最终效果

输入

代码语言:javascript
复制
创建一个重复触发器,从明天15:00到4月25日10:00,每8分钟触发一次

输出

代码语言:javascript
复制
triggers:
  - my_trigger:
    name: 定时触发
    code: triggerTiming
    version: 1.0.0
    runType: loop
    intervalType: minute
    interval: 8
    startTime: 1679900400000
    endTime: 1682388000000

注:我们在TriggeBox里把日期转成了ms时间戳。调用时间是3月26日。startTime = 1679900400000 = 2023-03-27 15:00:00

至此,用自然语言生成DataLink工作流触发器的demo就完成了。接下来要做的就是用同样的方法编写各种工作流组件的Box,再用一个总的ProcessMasterBox包起来,就可以用自然语言生成DataLink的工作流了。

工程实践总结

总结要点如下:

  1. 1. 为系统设计一门简洁、高语义化的DSL
  2. 2. 从最简单的实例开始,让模型输出DSL
  3. 3. 通过一些套路,约束模型的输出
  4. 4. 不断添加样本,精简词句,优化prompt
  5. 5. 传统工程手段辅助解决问题,比如:分治法解决复杂问题、动态Prompt复用模板

成本

上述最终例子的成本(已累加多次会话的Usage)如下:

代码语言:javascript
复制
Usage(promptTokens=1081, completionTokens=98, totalTokens=1179)

1179 / 1000 * 0.002 = 0.002358 USD

这是一个节点的价钱,按这样估算,用自然语言创建一次DataLink工作流(按10个节点算),成本约为0.023USD,约合0.15RMB。

当然这只是一个非常粗的估计。因为prompt需要添加更多样本来优(增加大体积),但也能通过Embedding缩减prompt体积,不确定因素较多。

会话

由于任务被拆分在多个模型会话中,因此在上层想要通过会话的方式进一步修改已生成的dsl就会比较麻烦。修改需求要传递给哪个Box,怎么传递?怎么描述?这是下一步要解决难题,欢迎大家一起讨论。

其他思考、杂谈

未来业界变化

出现众多的prompt工程框架,包括以下能力

  1. 1. 更通用合理的分治方案
  2. 2. prompt版本管理,方便对比不同版本的效果
  3. 3. prompt设计范式
  4. 4. 通用prompt库
  5. 5. 跨语言的prompt模板引擎

(推荐框架/工具:langchain: https://github.com/hwchase17/langchain

技术壁垒

从头看到尾的读者肯定会发现,这里完不涉及什么高端的技术。在prompt那一块,甚至不需要编程经验,逻辑良好的文科生也能胜任,几乎没有技术壁垒。自然语言人机交互即将成为软件的标配,外包又将大行其道。软件工程见势默默点了个赞。结论就是,笔者认为在未来一段时间,工程能力依然是会是硬实力。

关于fine tune

如果你问我,什么场景最适合fine tune,我会豪不犹豫地说,训练一个自动做prompt的模型。目前的prompt其实还是堆人力,gpt3.5+的fine tune开放后,第一个要做的就是这个,大大提升效率

在大语言模型能在我的电脑上跑起来之前,prompt应该都还是主要的训练方式。

一个疯狂的想法

做一个微博机器人,自动浏览微博、发布微博、评论互动。看看能涨多少粉?能不能产生自主意识?一个短期记忆容量最多只有32K的人,能做出什么呢? https://www.secrss.com/articles/52877

参考资料

openai官网 https://platform.openai.com

Prompt Engineering Guide https://github.com/dair-ai/Prompt-Engineering-Guide

宋思宇

腾讯 运营开发

曾就职于唯品会、阿里巴巴等互联网公司。现任腾讯大数据产品开发工程师,具备多年全栈开发经验。目前致力于研究大语言模型在传统计算机系统中的应用,以及prompt工程深度、广度极限的探索。

让数据驱动为企业增长决策指引方向

灯塔是腾讯PCG大数据平台部倾力打造的“一站式敏捷分析”平台,借助大数据套件及各类型原子能力,为业务发展和增长提供:从“数据上报、接入,到自定义万亿级实时数据分析,再到数据行动、数据可视化”的全链路数据解决方案

官网地址:beacon.qq.com

(建议PC端登录)

扫码进入用户群|掌握最新资讯

有任何问题,欢迎后台留言

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-03-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯灯塔 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • Text-to-DSL介绍
      • DSL
      • Text-to-DSL
      • fine tune和prompt
    • Prompt工程实战 (DataLink自然语言交互demo)
      • 准备工作
      • 确认DSL
      • Api调用介绍
      • 一个最简单的Text-to-DSL prompt
      • 约束模型输出
      • 添加样本
      • 分而治之
      • prompt-reply.yaml
      • 动态Prompt
      • 最终效果
      • 工程实践总结
      • 成本
      • 会话
    • 其他思考、杂谈
      • 未来业界变化
      • 技术壁垒
      • 关于fine tune
      • 一个疯狂的想法
    • 参考资料
    相关产品与服务
    大数据
    全栈大数据产品,面向海量数据场景,帮助您 “智理无数,心中有数”!
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档