likes
comments
collection
share

一次AI辅助编程实践和分析

作者站长头像
站长
· 阅读数 3

这里也来蹭一个AGI的热点。

最近笔者在编写一些跟文件,网络服务相关的程序,中间涉及到一些关于数据流编程和操作的方式。出于参考、学习和调优的想法,笔者使用了AI技术来辅助完成这个需求,但实际操作下来,包括使用搜索看到好像网络上相关(靠谱)的内容都比较少,包括claude和ChatGPT提供的代码,都有一些问题,遂著文记录之,也当一个开发笔记使用吧。

笔者使用的是nodejs系统,涉及到的相关类,方法和技术限于这个技术体系。

基本需求

基本需求非常简单,就是有很多文件,需要将这些文件按照纯二进制的方式进行合并,最终输出单一的二进制文件。所以,输入就是一个文件名称列表和对应的文件,输出就是一个单一文件。文件的格式和内容不重要,在这个场景中都使用纯二进制的方式来处理。

解决方案构想

下面是一些可以很快想得到的解决方案。

shell方式

如果不考虑应用的集成,只考虑文件操作的话,最简单和直观的方式,其实就是操作系统自己提供的文件合并命令。比如cat和重定向符号,它的示例如下。

cat *.txt > combined.txt

上面的cat命令,可以遍历并合并当前文件夹中所有的.txt的内容,并将合并后的内容,输出到另一个txt文件中。当然我们一般的应用场景是在程序中执行这些操作,另外也需要考虑不同的操作系统环境的兼容性。所以这个方法不是本文讨论的主要内容。

编程

可以使用编程的方式来进行处理。不考虑具体的实现和代码,基本的操作流程就是,先按照文件列表,依次使用二进制的方式读取文件的内容,并将这些二进制数据进行合并,最终用二进制的方式,在写入到目标文件中。

基本思路非常简单和清晰,但实际在工程上的实现,却可能会遇到很多问题。比如中间的文件访问的错误处理、计算资源占用和消耗等等。不考虑其他的问题,最主要的问题,就是如果需要一次性处理的文件数量很多或者数据量比较大,很有可能超过计算机系统的容量,使程序无法正常运行,通常就会报出如OutOfMemory或者StackOverflow这种错误。

对于这类的问题,在计算机技术上有一个通用的处理思路,就是将复杂问题或者大问题拆分成小问题来处理,最后再将结果合并起来,就可以用相对比较少的计算资源,来处理更复杂庞大的问题。具体到本文的需求中,就可以考虑使用数据流的方式,将文件拆分成为小型的数据包,来进行处理。其实所有编程语言中,相关于数据流的功能和实现,遵循的都是这个基本思路。

实现

下面让我们来看看具体的实现过程。这里笔者有了一些思路,但不是很确定可行的方案和代码。就带着相关的问题,先去请教了一些AI系统。在这个过程中,也发现了一些有趣的内容,值得分享和讨论。

针对这个需求,结合笔者的基本开发环境,所使用的提示词如下:

如何使用nodejs stream 合并多个文件到一个文件

下面就是chatGPT3.5和Claude3提供的响应结果。

chatGPT的方案

chatGPT的响应大致如下:


你可以使用 Node.js 中的可读流(Readable Stream)和可写流(Writable Stream)来合并多个文件到一个文件。下面是一个简单的例子:

const { Transform } = require('stream');

// 要合并的文件列表
const filesToMerge = ['file1.txt', 'file2.txt', 'file3.txt'];

// 创建可写流,指定要写入的文件路径
const outputStream = fs.createWriteStream('mergedFile.txt');

// 遍历文件列表
filesToMerge.forEach(fileName => {
  // 创建一个可读流
  const readStream = fs.createReadStream(fileName);

  // 使用管道将可读流连接到可写流中
  readStream.pipe(outputStream, { end: false });

  // 在每个文件的末尾添加换行符
  readStream.on('end', () => {
    outputStream.write('\n');
  });
});

// 在所有文件合并完毕后,结束可写流
outputStream.end();

可以看到,chatGPT确实能够正确的理解这个问题。它在解决问题方案方面的基本思路是,使用for each来遍历文件数组,然后为每个文件创建一个读取流,然后使用pipe方法,将这个流导输出到目标写入流中。

这段代码看起来不错,但实际上是不能运行的。这中间的主要问题应该是,pipe是一个同步执行机制,所以实际上这些读取流是并行运行的,显然和业务需求冲突,并且在逻辑上,也会造成混乱。

此外,有趣的是,虽然代码声明了tranform流,但实际并没有使用它,所以,chatGTP只是不知道从哪里找来了这段代码,但实际上它并不能更深刻的理解代码的执行方式,并确保能够重执行原理推导出可以真实正确运行的程序。

claude的方案

下面是claude提供的方案的核心代码:

const fs = require('fs');
const path = require('path');

const outputFilePath = path.join(__dirname, 'merged.txt');
const outputFileStream = fs.createWriteStream(outputFilePath);

const filePathsToMerge = [
  path.join(__dirname, 'file1.txt'),
  path.join(__dirname, 'file2.txt'),
  path.join(__dirname, 'file3.txt')
];

const readStreams = filePathsToMerge.map(filePath => fs.createReadStream(filePath));

const { pipeline } = require('stream');

pipeline(
  ...readStreams,
  outputFileStream,
  (err) => {
    if (err) {
      console.error('Error merging files:', err);
    } else {
      console.log('Files merged successfully.');
    }
  }
);

笔者感觉,claude提供的代码和思路,看起来比chatGPT更好一点。简单而言,就是基于文件列表,创建多个相关的文件读取流,然后使用pipeline方法,将这些流连接起来。这个方案非常简洁优雅,但可惜是,它也是不能运行的,因为pipeline方法,不是这样用的。

笔者的理解,pipeline是一个流水线式的处理方式,其精髓就是,上一个步骤的输出,就是下一个步骤的输入。所以,它参数中的每个元素,作为流的类型,其实是有限制的。简单而言就是第一个流必须是一个读取流,中间的流必须是转换流(有输入和输出),最后一个流是一个写入流,来承载最后的处理结果。所以claude这样用,中间都是读取流,这个流水线,是无法工作的。

其实,虽然claude虽然提供了错误的代码和方法,但启发了笔者思考文件数组和流水线处理的方案。下面就是基于它的启发,笔者构想并且实现的,可以真正运行并且比较完美的满足业务需求的解决方案。

人工的解决方案

上面我们已经提到,claude提供的pipeline方案,理论上可以进行一些改造,将中间的读取流,全部改造成为传输流的方式,就可以正常工作了。但在实践上,笔者并没有实施这个改造pipeline的方法,因为觉得过于复杂。笔者的解决方案,借鉴了数组处理的思路,简单而言就是将读取流数组使用Promise同步并且队列化,一次只处理一个流。核心示例代码如下:


// write file stream
const wstream = fs.createWriteStream(RECV.PATH + finfo.fileName);

for (const block of blocks) {
    await new Promise((rv,rj)=>{
        const rstream = fs.createReadStream(RECV.PATH + block);
        console.log("merge:", block);

        rstream
        .on("data", chunk=> wstream.write(chunk))
        .on("end", ()=>{  rv(); rstream.close(); });
    });
};
// close stream
wstream.emit("end");

这里的要点包括:

  • 使用结果文件名创建一个写入流
  • 使用for of 同步遍历文件名数组
  • 为每个文件创建对应的读取流
  • 便利时,使用promise进行同步化
  • 侦听读取流的data方法,并将数据写入写入流
  • 在end事件中返回和结束promise,进入下一循环
  • 全部文件处理完成后,关闭写入流

这个方法,可以将文件处理队列化,一次只专注于处理一个文件的读取,并且还进一步使用读取流的方式,实际处理的数据更小(数据流读取操作的范围),再加上写入流方式,使整个处理流程,在理论上,可以处理任意规模的信息,而不会造成系统资源的过分占用,而且相关的处理时间只会随信息量线性增长,不会造成恶化的情况。

小结

简单的总结一下。起码在现在这个阶段,针对于一些不是特别常规,但有代表性的,需要一些简单的创意和综合能力的编程文件,AGI系统还是无法提供一个比较理想的方案。包括对于原理的理解性、提供代码的可执行性方面,还是有一定不足的。这需要开发者在其基础上进行调整和改进,才能最终实现可执行的代码和功能。