以终为始

按照上一篇的架构,咱们整个程序终究写完的运转视图大概是下面这个样子的:

ChatGPT编程秀-4:与ChatGPT结对编写实现代码

能够看出咱们的实践成果比咱们上一篇文章考虑的还要杂乱:

  • 由于AI生成的API不接受批量prompt处理,所以咱们需求打开担任表达简略排列组合的DSL-b生成一批DSL-b’,再由DSL-b’1对1转成DSL-c
  • 由于API的限制,不是服务端生成文件让咱们去下载而,而是直接把base64给咱们发回来,所以不得不自己存文件还得考虑太多了怎样分分文件夹
  • 由于咱们的目标是了解哪些关键字和其图片好用,还得写点信息到png里,不然光看个图片能看出什么来?(其实AI也写了一些进去,可是究竟是混编后的,不太好读)。而由于nodejs技能欠缺,不得不还要调用python

但总的来说,它仍是上文那个架构,只是每一层都只写了一个节点,究竟个人用,也不需求那么多扩展。而即便如此也是个挺杂乱的结构,这么杂乱的结构,天然不能一下生成出来,咱们需求分块完成,那么咱们的每一块要怎样完成呢?有什么套路呢?这将是咱们接下来的内容,内容比较长,这一篇咱们先讲生成代码这一步的套路。

怎样编写一个节点

编写主干逻辑

咱们以dsl_translator这个节点为例,一开始,我只是这么发问(并没有,我只是演示一个错误演示):

我想用nodeJS用下面的yaml描绘的数据结构得到一个新的数组:

base:
  steps: 10
  batch_size: 1
poly:
  - template_prompt:
      template: >
          a cat,
          ${ chara }
          ${ facial_expressions }
      meta:
        view_angle: 
          - side_view,
          - front_view,
        faal_expressions:
          - (smile:1.5),  
          - (smile:1.2),  
          - smile,  

果然,ChatGPT也不惯着我,给出了我下面的代码:

//......
const newArray = [];
data.poly.forEach(item => {
const obj = {
    template: item.template_prompt.template,
    meta: item.template_prompt.meta
};
newArray.push(obj);
});
console.log(newArray);

我似乎听到AI在说:“你就说是不是数组吧,你要数组,我给你数组,没毛病~”

遇到这种问题,只能反思是自己没说清楚,那么该怎样说清楚呢?

咱们这个程序是服务于AI画图的,其实咱们也能够从AI画图中学习不少经验,比方说在AI画图中,有一个control net里的open pose技能,我能够画个骨架,就像下面这样:

ChatGPT编程秀-4:与ChatGPT结对编写实现代码

我就能够用这个骨架图去画个穿靴子的猫,拿着个刺剑什么的。这个技能告知咱们什么呢?咱们能够通过描绘一个骨架来操控AI生成的内容。那么图片能够,编程是不是也能够?

所以通过一段时间的摸索,终究我写出了下面的描绘:

我想用nodeJS用下面的yaml描绘的数据结构得到一个新的数组:

base:
  steps: 10
  batch_size: 1
poly:
  - template_prompt:
      template: >
          a cat,
          ${ chara }
          ${ facial_expressions }
      meta:
        chara: 
          - Abyssinian,
          - cat_in_boots,
        facial_expressions:
          - (smile:1.5),  
          - (smile:1.2),  
          - smile,  

要求:

  1. 假定上面的yaml转成json的转化代码我现已写完了
  2. 我需求遍历poly下的一切的顶层元素
  3. 遍历过程中,要处理template_prompt元素的子元素:
    1. 从template中读取作为模版。
    2. 读取meta中的特点,由于特点或许每次都不相同,是不确定的,所以不能硬编码
    3. 然后根据meta中的特点,把template作为 string literal 解析,这个解析代码我现已有了,假定名为render_string_template,能够不完成,留一个函数接口即可。
    4. 要遍历组合meta中的特点构成一个数组,比方chara 有两个值,facial_expressions 有三个值,那么应该生成2*3也便是六组特点放入这个数组中,这个数组和template会被传入render_string_template函数,终究会取得两个prompt字符串
  4. 将生成的prompt字符串数组和template_prompt元素之外的其他元素兼并成一个目标,要求在同一等级。prompt字符串数组有几个元素,就会兼并成几个目标,并放入一个新数组中,咱们称之为ploys。
  5. 继续遍历,直到遍历完一切顶层元素,一切元素都放入了polys中。polys是一个一维数组。
  6. 将ploys中的每一个元素与base中的特点组成一个新的目标,base的特点打开与prompt特点同级,当ploys中的每一个元素的特点名与base中的特点名相一起,掩盖base中的特点。这些新目标组合出的数组便是我要的数组

能够看到,我在之前的prompt的后面加了许多的描绘,其中的2-6便是用天然语言讲了我期望这个函数完成整个过程的大概逻辑。这个方法就很像Open Pose。通过这个操作,我得到了我想要的代码,究竟这个逻辑不杂乱。

鸿沟划分

上面prompt的要求里除了表达了主干逻辑,其实也用了一些其他的技巧。比方“1. 假定上面的yaml转成json的转化代码我现已写完了”,“把template作为 string literal 解析,这个解析代码我现已有了,假定名为render_string_template,能够不完成,留一个函数接口即可。”

这个呢有点像control net里的seg技能,他便是把图片上的东西都分区域,就像下面这样:

ChatGPT编程秀-4:与ChatGPT结对编写实现代码

然后再生成的就靠谱许多,乃至你还能够对其中特定部分进行针对性的描绘。这个技能就很合适用来操控AI生成的注意力焦点。

那么在咱们这个比方,上面提到的那些描绘就起到了seg的效果。他能够让咱们不用在一个prompt描绘里编写一切的细节,这关于杂乱的逻辑很有帮助。由于即便是一个小节点,其实逻辑也或许有点杂乱的,就像这里边,咱们能够把meta数据与tempalte兼并的功用推迟完成,只描绘与他的交互,使生成的程序先把参数扔给它。有的时分,咱们想要运用特定的库,也能够用这个方法,比方我在另外一个场景下是这么完成的这个效果:

1. 读取文件的fs,要运用const fs = require('fs/promise')引进。
2. 用js-yaml库解析yaml。

通过这样的技巧,咱们就能够把代码进行进一步的分化,让咱们更简单描绘清楚咱们想要部分的事务逻辑,而且也能够给咱们节约点算力,究竟ChatGPT生成的时分如果太长也会中断,你让他继续的话,有时分也续不上。这个技巧就能够让他专心于你期望他专心的地方,然后进步体现力。

功用迭代

上面的写完之后呢,我发现一个问题,这个DSL还不是我想要的终究形态,关于同一套模板,我或许需求多种meta,由于有些特点的组合是没有意义的,我也不想浪费我宝贵的GPU。那么咱们就不能太暴力的让它穷举一切的组合,我要针对同一个tempalte给他不同的特点组合,所以meta的值就必须是个列表,大概如下所示:

base:
  steps: 10
  batch_size: 1
poly:
  - template_prompt:
      template: >
          a cat,
          ${ chara }
          ${ facial_expressions }
      meta:
        - chara: #  这里改成了数组
            - Abyssinian,
            - cat_in_boots,
          facial_expressions:
            - (smile:1.5),  
            - (smile:1.2),  
            - smile,  

所以现在咱们就面临一个问题:改代码。而这个时分就体现出咱们之前拆任务的价值了,由于各个模块都是阻隔的,那就没有什么改代码,从头生成一份就好了,所以我改了改要求:

要求:

  1. 假定上面的yaml转成json的转化代码我现已写完了
  2. 我需求遍历poly下的一切的顶层元素
  3. 遍历过程中,要处理template_prompt元素的子元素:
    1. 从template中读取作为模版。
    2. 读取meta中的特点,由于特点或许每次都不相同,是不确定的,所以不能硬编码。
    3. 然后根据meta中的特点,把template作为 string literal 解析,这个解析代码我现已有了,假定名为render_string_template,能够不完成,留一个函数接口即可。
    4. 要遍历组合meta中的每一个特点组构成一个数组,
      1. 每一个特点组或许只需求看做一个目标,当且仅当每一个特点值都为单值
      2. 每一个特点组或许也需求打开,当且仅当任何一个特点值有多值,比方 chara 有两个值,facial_expressions 有三个值,那么应该生成2*3也便是六组特点放入这个数组中,这个数组和template会被传入render_string_template函数,终究会取得两个prompt字符串
  4. 将生成的prompt字符串数组和template_prompt元素之外的其他元素兼并成一个目标,要求在同一等级。prompt字符串数组有几个元素,就会兼并成几个目标,并放入一个新数组中,咱们称之为ploys。
  5. 继续遍历,直到遍历完一切顶层元素,一切元素都放入了polys中。polys是一个一维数组。
  6. 将ploys中的每一个元素与base中的特点组成一个新的目标,base的特点打开与prompt特点同级,当ploys中的每一个元素的特点名与base中的特点名相一起,掩盖base中的特点。这些新目标组合出的数组便是我要的数组

改完要求,配上上面的DSL直接扔给了它,得到了咱们想要的代码。

所以在今天这个时刻,有些重构作业突然变得没有那么大价值了,由于在划定好的鸿沟里,重写比重构更快,尽管两次输出的代码并不相同,可是他们的功用是相同的。

总结一下

咱们首先概述了整个程序的终究运转视图,以及在完成过程中遇到的应战。然后咱们以dsl_translator节点为例,讲解了一些与ChatGPT结对编程的一些套路:

首先是编写主干逻辑,通过描绘咱们期望函数完成的逻辑,类似于Open Pose中的骨架图,以操控AI生成的内容。我发现许多人让ChatGPT作业的时分总想给一句话需求,软件仍是很杂乱的,必要的话,咱们仍是要把主干逻辑写出来。程序员有多讨厌一句话需求,我想我不用多说了,到咱们自己提需求的时分,可不能忘本。

接着咱们介绍了通过类似于Control Net中的seg技能,咱们将代码进行更详尽的分化,这样的技巧能够使AI专心于咱们期望专心的地方,一起让咱们自己也更简单描绘咱们想要的事务逻辑,既节约算力,也能提升AI的体现。究竟GPT-4还没有广泛运用,3000字仍是个门槛。其实即便是广泛运用了,咱们不要忘了,AI这个东西生成总是不太安稳的,如果你切得满足小,成功率总是要高得多,而且你能够靠反复生成来达成某种自动化,这个呢,咱们下一篇文章评论。

接着咱们面对了一个需求改代码以加入新功用的场景,由于咱们之前现已进行了规划,已然现已拆分好了模块,从头生成一份代码来完成新的功用,比改代码更快。第一次运用这个技巧的时分,我想起了流量地球里550C接入的时分,发出的提示音:正在生成底层操作系统。是的,关于AI来说,重写比重构更快。这个点极大地动摇了软件工程中很多具体实践的根基,从这一刻起,八面玲珑的文档胜过了能够作业的代码。具体到行为上的改变便是,我现在写代码都会有意识的保存自己对AI发问的prompt,而且我会在每个节点的根路径上记录下完善的技能文档。

到此为止,咱们把与ChatGPT结对写代码中,怎样第一次把代码写出来这一步的首要技巧给讲的差不多了。但其实这还远远不够,正如没有ChatGPT的年代咱们就知道的,第一次把代码写出来所占软件开发的本钱少得不幸,软件开发本钱的绝大部分都是在后续迭代中花掉的。那么关于一个节点也是相同的,尽管咱们展现了一种重写优于重构的新思路,但这一篇也只是讲了个思路,AI生成的不安稳性依然是悬在咱们心头上的一把刀,所以咱们接下来会讲讲更多的工程实践来确保咱们继续写出可信的代码。