20230429 IaC之IDE,也许是下一个ChatIDE?

0.背景

继基于插件做到代码部分语义可视化、以及反向控制IDE浏览代码跳转之后,我想尝试进一步获得IDE的控制权,来完成其他想法中的数据收集和增强自动化办公的程度,小小的推进一下IaC的进程,顺带着给AI自动化控制IDE开启一个新的道路,给AI“看”和“动”的基础能力。

这个想法启发于黄老师在Archguard中使用的createRepl方法,可以在自己的应用程序中内嵌一个jupyter的后端来完成自定义DSL的REPL(参考 https://github.com/archguard/archguard/blob/master/architecture-as-code/repl-api/src/main/kotlin/org/archguard/aaac/repl/compiler/KotlinReplWrapper.kt )。要是模仿他的方法在插件中启动一个embedded-jupyter-repl后端,能否通过repl来越过jupyter来执行插件中的方法呢?

1.需求分析

实际上IDEA本身也是一个java进程,有着自己的JVM和类加载器,其实相同的功能还在官方的IDE Scripting Console中有所体现、还有名为liveplugin的IDEAplugin,都可以做到动态在已经运行了的IDE的JVM进程中执行额外的代码

众所周知Java的运行是需要先编译成字节码再加载到JVM中的,那么也就意味着如果没有字节码增强和动态类加载技术,仅凭借正常的Java反射是很难做到的。理论上大部分程序在启动之后其行为就已经固化,不太可能轻易的通过外部进行干预。因此jupyter-repl在正常情况下是没法控制它的父进程的才对。但createRepl方法中的embed参数又让我起了怀疑,特此继续调研之前设想的IDE中REPL的可行性。

实现这个功能的思路上大致可以分为暴力穷举方案、RPC方案、字节码增强&动态类加载方案、移花接木方案

暴力穷举:人工或通过某种形式提取IDEA目前已知所有公开的API,生成delegate代码写入一个插件中,通过RPC等形式暴露给外部使用,这个方法难度最低但工作量最大,并且随着IDEA自身的升级需要持续维护

RPC方案:相比暴力穷举做一层抽象,使用反射进行动态调用再暴露结果给外部,这个方案难度比前一个高,工作量少了很多。潜在的坑是数据类型不太好处理,容易出运行时的问题

字节码增强&动态类加载方案:使用asm等字节码增强技术实时修改JVM运行时的字节码来达到,这个方案难度继续提高,工作量相比前一个差不多。潜在的坑是字节码增强技术配合任意代码使用,不异于重写一个编译器,可能会重新发明一次JShell和部分的javac。

因为我这么懒必然不会优先考虑前面的,因此一直在关注有什么已经做的差不多的功能,稍加拼接就能复用的方案。

移花接木方案1:因为按之前的思路是想继续使用Java的生态直接复用,特意看了下java在9之后出的JShell,理论上这个能作为一个后端使用,让这个后端和已有的JVM共享类加载器,麻烦的是需要自己解决类加载器的问题

移花接木方案2:IDE自带的部分功能和历史上有人实现的插件做到了类似的能力,参考他们的原理和代码稍加修改也许能和jupyter结合起来。

2.前端方案

jupyter、shell //any way,只要方便交互就好,按历史经验和套路,只要能用shell或文本交互的最后都比较容易再做二次开发
https://github.com/jupyterlab/jupyter-ai
https://github.com/Kotlin/kotlin-jupyter
https://github.com/dev-assistant/skykoma-plugin-idea/blob/bundle-scripting-console/src/main/kotlin/cn/hylstudio/skykoma/plugin/idea/KotlinJsr223JvmScriptEngine4Idea.kt
https://github.com/unit-mesh/auto-dev

3.执行引擎方案

3.1.embedded kotlin compiler //demo测试成功

实际的依赖是kotlin-scripting-compiler-embeddable

参考IDE Scripting Console的实现方式,兼容JSR-223的脚本引擎都可以调用IDE自身公开的接口,通过追踪IDEA源码可以看到入口,参考附录。

可以从源码看到IDEA主平台只声明了接口,由各自的插件自行实现Factory接口,根据追踪可以看到名为KotlinJsr223StandardScriptEngineFactory4Idea的类,这里已经处理好了IDEA主JVM的类加载器,因此模仿IDEA的核心代码调用即可。

官方的功能相对比较简单且不是核心功能,只能选取部分代码传给script engine执行动作。因和不同的实现相关,控制台并不都会显示输出。因此相关的资料也及其稀少,通过阅读大量源码和官方的bug反馈,对比git log、release note等方式才能找到一点有用的信息作为参考,另外还需要防备官方后续废弃这个功能。

注意:在某些版本上无法使用这个功能,需要开启internal actions菜单才能看到入口,但我打算后续拆出来放插件里通过别的形式触发。
PS: 冷门知识还是得自己动手啊,chatgpt一直在胡编一些不存在的路径,指望AI偷懒只会浪费时间。这部分代码开始在主仓库后来分到了kotlin的仓库,然后又从kotlin拆走回到主仓库。
PPS: 在没有编译环境的情况下,追这么大型的源代码还是需要一些技巧的,这个等后面有闲心再写

测试脚本

环境测试

3.2.kotlin-repl //待详细调研

从kotlin-repl的启动命令可以分析原理,理论上也需要解决类加载器的问题

3.3.jshell

jshell跨越宿主jvm执行动态字节码理论上不可行,推迟

3.4.jupyter 集成 //成功

REPL除了动态执行代码以外,还要包括上下文记录、UI渲染等功能。上下文记录也就是前面定义的变量后面可以继续引用。根据之前测试JSR-223的Java默认实现来看效果不是很好,根本做不到历史的记录。
根据script engine成功的现象debug可以发现,是kotlin的插件自己实现了历史记录的记录,利用这个也许可以和jupyter做对接
那么jupyter-kotlin 的api能否复用呢?理论上可行,尝试安装jupyterlab如下

记录下当前的依赖版本

 

安装完jupyterlab和kotlin-kernel后可以在python包的路径中看到相关的文件夹,kernel的描述文件主要入口是kernel.json,内容如下

其中核心内容是argv,可以看到当kernel启动时候实际执行的是run_kotlin_kernel,其中connection_file每次动态生成,内容如下

文件中描述了本次内核启动需要的端口、标识、协议等信息传递给python脚本。python脚本主要用于拼接启动命令

主要是获取环境变量、寻找java可执行文件路径、获取当前文件夹等信息,拼接的命令如下。

修改run_kernel.py如下

目前add_kernel支持设置环境变量之类的参数,理论上可以把这个过程从原流程中剥离出来独立出一个jar来执行,这样可以不破坏原有的python文件同时完成自定义kernel的接入,目前仅是demo直接暴力修改了python脚本不是很优雅

0.0.9初步实现了用自动注册kernel,可手动指定python的目录,支持动态修改kernel.json。

剥离对官方run_kernel.py的依赖,暂时使用shell替换整个文件

20250201更新:更换为独立模块摆脱对官方的依赖 https://github.com/956237586/run_kotlin_kernel_idea

尝试手动指定、远程分配监听端口:根据调用栈可追踪,需要修改逻辑

https://github.com/jupyter-server/jupyter_server

https://github.com/jupyter-server/jupyter_client

fork官方库后修改对应的代码强制使用环境变量中的端口,打tag v8.4.3后可从github安装指定版本

之后可支持在kernel.json的环境变量指定端口

继续分析脚本逻辑,为了方便理解简化启动命令为  java -jar xxx.jar -classpath=xxx xxx.json -home=xxx -debugPort=xxx,可以看到入口是kotlin-jupyter-kernel-0.11.0-385.jar,这个jar是kotlin编写的

根据日志和源码的情况,如果我们想模拟这个过程,只需要让server在idea插件的上下文启动。

因为端口描述的文件每次都是动态生成的,因此修改run_kernel.py把命令行参数传给idea插件,idea插件再启动replserver

在idea插件里启动一个httpserver监听请求,接读取原来的参数调用embedKernel,同时替换classpath为当前线程上下文的classpath,示例请求如下

这样启动之后就可以尝试jupyter中执行kotlin了,遇到的错误如下

避开之前测试引入的库影响同时保证编译通过,仅保留相关的依赖

再次继续测试错误如下

按独立demo调整后错误消失,但我忘了为啥了,下一个错误是构造器初始化失败提示类型不匹配,还去github开了issue问

通过反复和demo对比,发现classLoader的继承关系不一样,虽然是相同的类但被不通的类加载器加载后实际是无法通用的

构造测试脚本

最后根据调整ide scripting console时候的经验,修改最初启动server入口独立Thread的classLoader为idea加载插件所用的classLoader即可解决问题

效果如下

在java11和下面的环境组合下测试,代码分支https://github.com/dev-assistant/skykoma-plugin-idea/tree/test-jupyter-java11

Unable to instantiate class Line_4_jupyter

目前在java17和下面环境组合下测试成功,代码分支https://github.com/dev-assistant/skykoma-plugin-idea/tree/test-jupyter-java17

 

3.5.livePlugin的实现方案//待调研

TODO

附录

JSR-223

https://www.jcp.org/en/jsr/detail?id=223

livePlugin

https://plugins.jetbrains.com/plugin/7282-liveplugin

IDE Scripting Console 功能文档
https://www.jetbrains.com/help/idea/ide-scripting-console.html
IDE Scripting Console 源码
https://github.com/JetBrains/intellij-community/blob/fc580582f211e5f8866462d8c596aaf8e36760ac/platform/lang-impl/src/com/intellij/ide/script/RunIdeConsoleAction.java#L88
内置的Kotlin脚本引擎 源码
https://github.com/JetBrains/intellij-community/blob/master/plugins/kotlin/repl/src/org/jetbrains/kotlin/jsr223/KotlinJsr223StandardScriptEngineFactory4Idea.kt
官方issues
https://youtrack.jetbrains.com/issues?q=%22IDE%20Scripting%20Console%22%20project:%20%7BIntelliJ%20IDEA%7D
https://visualstudio.microsoft.com/zh-hans/visual-cpp-build-tools/
https://mirrors.tuna.tsinghua.edu.cn/help/pypi/

https://saturncloud.io/blog/how-to-get-autocomplete-in-jupyter-notebook-without-using-tab/

https://browse.arxiv.org/pdf/2309.12499.pdf

20230423大模型上手笔记

背景

AI最近又开始火了尝试上手玩玩,我水平还停留在MNIST入门阶段,哪写的不对欢迎大佬指正。

基本概念

模型是一个或一组文件,里面存储了权值的集合,理论上就是一堆浮点数的集合。

模型的生成过程叫做训练,执行过程叫做推理。训练过程中和训练好后的模型可以持久化到硬盘中,结合结构信息可以反向序列化加载到硬件中。

默认的浮点数精度一般是32bit浮点(32fp,32bit,4byte),  半精度.half()就是16bit(16fp,16bit,2byte)

量化说的是把浮点数据继续缩减空间,4bit定点(int4,0.5byte),8bit定点(int8,1byte)

文本是可以转换成高维向量表示的,向量的每个维度都可以用浮点数表达,transformer模型计算(推理)的过程实际是对输入的向量进行补全。

和AI对话只是表现形式,本质上是根据上文猜测下文在概率上最大可能性的结果并进行补全,重复进行这个过程直到长度上限或终止符,然后再以对话形式展现。

huggingface提供了大量模型和数据可供下载,常见的模型是pytorch生成的,格式上大多是一个或多个bin文件,加上一个index.json。

至于模型是怎么训练的暂时还不是很清楚,这种格式能否以更可视化的方式获取信息也不知道。(有没有一种方式能自动计算当前硬件资源,给出合理的模型加载方式呢?)

huggingface同时提供了常用的python库可以方便的自定义模型结构、加载预训练的模型,利用accelerate库可以把模型拆开加载到不同的设备。

nvidia-smi可以看到显卡的显存占用,使用watch -n 1 -c nvidia-smi可以实时监控显存。

工程结构

目前见到的几个模型的项目工程结构从上到下一般为

web-ui(可选):自己手写或者从其他地方抄,常见的是gradio,server_name可以指定监听IP(和其他的软件一样,设置成127.0.0.1仅限本地访问,设置成0.0.0.0监听所有网卡地址), server_port可以指定监听端口,一般不要开启share=True否则会发布到公网

示例代码demo: 一般从github下,里面经常包含一个model(s)的目录

模型本身+模型配置(可选) : 一般从huggingface下,内容会和github有部分重叠。我暂时粗暴的把模型以外的东西复制到model(s)下一份方便python中引用和修改,模型本体存别处

conda虚拟环境:使用conda可以做到和venv、nvm类似的方式管理虚拟环境并相互隔离,包含pytorch底层和其他的python依赖

模型加载技巧记录

huggingface提供了众多抽象好的预训练模型结构,可以通过mixin等方式复用

from_pretrained是个使用python动态实现的模型加载器,内部会动态实例化预训练的模型,直接追踪我没找到链路,但可以通过type(model)的方式获取运行时类名从而debug到真实的模型实现类

from_pretrained参数支持设置load_in_8bit=True,可以在加载时候进行量化。

这个方法底层实际调用的还是pytorch,pytorch中加载模型可以指定map_location。

加载模型的行为默认是加载到内存,然后再转移到显存中。

如果模型太大无法完全加载到内存会报错,根据报错信息可以定位到/conda/envs/py38/lib/python3.8/site-packages/transformers/modeling_utils.py文件,把map_location=”cpu” 全部替换为map_location=”cuda” 就能直接加载到显卡而不经过内存

 

如果正常方式无法加载模型,可以借助accelerate库分开加载一个模型的不同部分到不同的设备

主要的变更是用load_checkpoint_and_dispatch替代from_pretrained,关键参数如下

device_map参数可以指定加载策略,  auto可以自动分割模型来最大化利用硬件,infer_auto_device_map也可以生成device_map

也可以传入dict结构指定每个部分使用什么设备加载cpu是使用RAM加载 disk 是使用磁盘加载,数字编号是显卡的编号

打印model.hf_device_map可以看到自动生成的device_map结构

max_momory可以限制每个设备初始加载最多可用的资源,dict的key定义同device_map的value,例如:

注意这个参数只是设置加载时候的限制,实际推理过程还需要显存,因此使用此种方式需要注意给显卡预留足够的空间用于推理,剩下的部分用CPU RAM凑。

(做个梦:啥时候显存价格能降低到和硬盘一样啊)

如果device_map中出现了disk,则需要指定offload_folder来指定目录,我一般用f”{model_path}/tmp”

记录

chatglm-6b 成功,但有点弱智还需要调教

stablelm-7b 成功,比chatglm强点有限

codegen-6b 16b 失败,加载不了,推迟

MOSS-plugin 成功,看文档是基于codegen的,理论上代码能力应该还可以才对。但单张显卡无法直接加载,利用cpu分段加载后速度太慢,等待量化模型再测试。

参考

https://gradio.app/docs/

https://github.com/IST-DASLab/gptq

https://github.com/qwopqwop200/GPTQ-for-LLaMa

https://github.com/openai/triton

https://github.com/TimDettmers/bitsandbytes

https://github.com/OpenLMLab/MOSS/issues/35

https://huggingface.co/docs/transformers/main/en/autoclass_tutorial

https://www.zhihu.com/question/362131975/answer/2182682685

https://arxiv.org/pdf/1607.04683.pdf

https://zhuanlan.zhihu.com/p/38328685

https://blog.csdn.net/lai_cheng/article/details/118961420

https://huggingface.co/docs/optimum/concept_guides/quantization

https://huggingface.co/docs/transformers/main/en/main_classes/quantization

https://pytorch.org/docs/stable/quantization.html

https://deepspeed.readthedocs.io/en/latest/memory.html

https://pub.towardsai.net/run-very-large-language-models-on-your-computer-390dd33838bb

https://huggingface.co/docs/accelerate/v0.18.0/en/package_reference/big_modeling

https://huggingface.co/docs/accelerate/v0.18.0/en/usage_guides/big_modeling#designing-a-device-map

https://huggingface.co/docs/accelerate/v0.18.0/en/usage_guides/big_modeling#loading-weights

https://github.com/OpenLMLab/MOSS/issues/38

20230407kubekey搭建k8s

视频版

https://www.bilibili.com/video/BV1gL411Y75L

 

背景

20230204尝试搭建k8s青春版

上次尝试了k3s,本来计划是尝试原版k8s,但看了下文档感觉略麻烦,我决定放弃。。。。

按上次eric的建议这次尝试玩下kubekey

安装准备

环境要求

Ubuntu 16.04,18.04,20.04CPU:2 核,内存:4 G,硬盘:40 G

机器节点2c4g * 3

  • 所有节点必须都能通过 SSH 访问。
  • 所有节点时间同步。
  • 所有节点都应使用 sudo/curl/openssl/tar
域名分配
k8s1.k8s.local
k8s2.k8s.local
k8s3.k8s.local
kubesphere.k8s.local

生成config

修改config

每个节点上安装依赖项

任务机上执行这个安装集群

安装过程

这时候可以进到命令行查看安装过程,ks-installer-86ddb55c5b-gwlhh就是安装的pod

可以进入pod容器查看目录

也可以直接用logs查看日志

观察到这两个pod一直起不来

都是源于configmap kubesphere-config不存在

多等了一会发现第二个pod ks-controller-manager-8569fb495c-b9qwv自己成功了,可能有降级?(来自20230701的更新:只是单纯的慢,实际是monitor还没装完,所以不动它也能自己好)

尝试手动修改kubesphere-config 里的jwtToken

kubectl get cm kubesphere-config -n kubesphere-system -o yaml

删除pod ks-apiserver后不再报错

参考

https://kubesphere.io/zh/docs/v3.3/installing-on-linux/introduction/multioverview/

https://github.com/kubesphere/kubesphere/issues/4729

http://www.minio.org.cn/docs/minio/kubernetes/upstream/

https://docs.ceph.com/en/quincy/cephfs/index.html

 

20230311neo4j测试

0.背景

因json文件、mysql、mongo存储都不能方便的满足我的要求,计划后续使用图数据库来存储复杂的的图状关系

1.安装

看了一眼dockerhub有现成的镜像,直接选了最新的

7474是web端浏览器的端口,7687是数据库本身的端口。启动后默认用户名密码都是neo4j,记得修改密码。这里测试,因此用弱密码12345678

2.使用

2.1.基本语法

node节点

relationship关系

properties属性

好了,你已经学会基本的语法了,是时候设计一个复杂的项目了(

3.集成到java中

尝试 spring-boot 3 + neo4j ORM

3.1.修改pom.xml

3.2.entity类

3.3 repo类

注意:关系字段中,如果实体不存在则会自动创建Node,但如果修改实体导致数据减少,OGM不会删除关联的实体,只会删除关系

 

参考

https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#preface

https://neo4j.com/docs/getting-started/current/get-started-with-neo4j/

https://neo4j.com/docs/browser-manual/current/

https://www.w3cschool.cn/neo4j/neo4j_cql_introduction.html

20230203IDEA插件开发-Action的实现

背景

开发的插件点击后UI卡住,怀疑是阻塞了UI线程。。。。仔细看下文档

文档翻译-Action的实现

自定义的action类是派生自抽象类 AnAction的子类。当用户操作菜单项或工具栏按钮时,IDEA会调用action的方法

基于 AnAction 的类不能包含任何属性,因为 AnAction 的实例会存在 application整个生命周期。如果 AnAction 类使用了属性存储了短声明周期的数据但没有合理的清除,会造成内存泄漏。例如如果你把Project对象存到了这里,当用户关闭了这个Project之后就会发生内存泄漏

方法Override覆盖原则

每个action可以覆盖 AnAction.update(),必须覆盖AnAction.actionPerformed().

AnAction.update() 用来被IDEA调用更新action的状态,action是否可用/是否可见取决于action在当前UI上是否可用.AnActionEvent 对象会传递给这个方法并且包含当前action上下文的信息。可以通过修改和事件上下文关联的Presentation对象状态来改变action是否可用。像Overriding the AnAction.update() Method说的一样, update() 方法的执行速度非常重要

2022.3+版本后AnAction.getActionUpdateThread() 返回 ActionUpdateThread 线程决定了 update() 方法是否在 background thread (BGT) or the event-dispatching thread (EDT)上被调用。首选的方式是在BGT上执行update方法, 这样可以保证application侧读取 PSIthe virtual file system (VFS), 或 project models的有利条件。在BGT上运行update的Actions不应该直接访问Swing 组件的结构。 反之EDT上运行的update的Action必须禁止访问PSI, VFS, 或 project的数据,但可以访问Swing组件和其他UI模型。所有由DataContext 提供的可访问数据在这里有说明 Determining the Action Context. 当必须从BGT切换回EDT时,需要使用 AnActionEvent.getUpdateSession()方法获得 UpdateSession 并调用 UpdateSession.compute() 在EDT上运行方法。从2022.3开始,插件的DevKit将会有检查 Plugin DevKit | Code | ActionUpdateThread 是否丢失,来提醒插件作者实现这个方法

AnAction.actionPerformed() 将会在可用之后当用户选择action时被IDEA调用。这个方法是实现action最麻烦的任务,它包含了当action被调用时要执行的代码。 actionPerformed()方法也接收一个 AnActionEvent作为参数,它用来访问任何上下文相关的信息例如 projects, files, selection。。。参考Overriding the AnAction.actionPerformed() Method 获取更多信息

这里还有其他的方法可以在 AnAction 类中被覆盖,例如改变action默认的 Presentation。还有一个使用场景可以通过覆盖构造器来达成,当动态注册到分组的时候demo可以在这里看到Grouping Actions

AnAction.update 

IDEA会周期性的调用AnAction.update() 方法来响应用户的操作。 update() 方法用于动态判断action在当前上下文是否应该禁用或启用。方法的实现必须确保可用性状态和可见性状态 ,否则指定的action将会”卡住”。(译者注:这里的意思应该是从用户看来是卡住的现象,实际就是没响应任何动作)

AnAction.update()方法可能在UI线程上频繁的调用。所以这个方法必须非常快的执行,没有必须执行的实际动作。例如检查选中的对象是否可用,但不做任何文件系统相关的动作。如果新的状态无法被快速的确定是否可用,判定过程应该在AnAction.actionPerformed()中执行,并通知notify用户这个动作在当前上下文不能被执行

线程

//TODO

参考

https://plugins.jetbrains.com/docs/intellij/basic-action-system.html

https://plugins.jetbrains.com/docs/intellij/general-threading-rules.html#modality-and-invokelater

20230224dev-assistant idea plugin开发

既然ChatGPT能理解自然语言,为什么不让它来帮我写代码完成这个开发任务呢?

基于这个猜想,我尝试让它编写IDEA插件逻辑,以一个LOGGER插入为例。

我给的命令是

利用IntelliJ Platform Plugin SDK获取当前光标所在类的成员变量,检测是否存在LOGGER,如果不存在,在类声明的第一行插入LOGGER的声明,并import对应的slf4j依赖。用java实现

它给出的代码如下

对不起,是我高估了AI的实力,它部分代码在胡写。。。。得自己验证是否可行,它的代码看着没毛病,但实际很多地方都有问题= =

还是自己动手写吧,参考 https://www.bilibili.com/video/BV15D4y137Sp/

20230218长期计划:dev-assistant

背景

最近试用ChatGPT给我带来了新的想法,如果你觉得我后面写的比较离谱,那确实有可能是我错误的高估了现有AI的能力,就当我是在做梦吧hhhh。总的来说这种对话的方式极大的降低了人机交互的门槛。相比于传统的交互方式,以自然语言和计算机进行有效的交互是听上去理所当然,但实际上一直都不太成熟的方式。而ChatGPT给我们带来了新的希望,我认为之后这个技术足够成熟之后将会改变很多现有的交互逻辑。可以设想,假如以后AI能正确理解绝大部分的自然语言,那么是不是很多任务都不需要用鼠标和键盘来操作了?而根据网友给出的参考资料,有人正在尝试将自然语言转化为一系列的API调用(参考最后的附录),这意味着AI生成的内容将具有可执行的特性,不同的API完全可以代替现有的人工操作,完成一场人机交互的变革。

继上一次我根据AST上下文来做可控的LiveTemplate之后,我继续尝试了提取代码的AST节点并结构化其中的语义信息配合可视化来提高日常工作效率。但短期内无论从安全角度还是对成果负责的角度来说,程序生成的结果,或者说AI的动作都必须经过人工的确认以避免AI失控造成不好的后果。也就是说在相当长的一段时间内,AI的操作必须要可观测、可干预。以此为前提让我有了一个科幻的想法:我对电脑用自然语言下达任务后,电脑在我的监视下逐步完成任务。选择离我最近的场景来说就是日常的编码工作在有详细设计的情况下是否有希望完全自动化?

可行性分析

现有的代码生成AI大多是基于工程内已有的文件、注释、当前文件光标的上下文来猜测并生成代码,但这里面缺少了一个重要的信息就是目的。AI无法准确猜测人的意图,比如完成某个API的编码通常会分成接入层、业务层、数据层,多层次+多个方法的组合。AI每次只能预测我选定光标位置的代码内容,而无法连续跳跃多个层次来完成这个任务,依然需要人工大量操作IDE。如果能以自然语言提供给AI这个目的,是否有可能让AI自行完成这一系列的动作呢?由此会带来的一系列问题。开发人员对IDE的操作是否能通过文本精确的描述?开发人员操作IDE时依赖的信息有没有可能被收集起来并打上标记?如果将我日常操作IDE的记录和操作IDE时候的信息完整的记录下来并打上标记,能否训练出一个AI来理解并代替我进行IDE的操作?

以我目前的AI水平还停留在手写数字识别的入门级别,虽然有计划学习相关知识,不过并不是抱很大的希望能独立搞定模型的设计、训练之类如此专业的任务。但以我目前的能力,处理点数据、集成已有的功能还是不成什么问题的。 因此我有一个长期计划的想法,设计并收集开发操作IDE的上下文信息,包括但不限于工程类型、文件组织结构、版本控制信息、产物信息、光标上下文信息等。依赖这些详细的信息,自动或手动的分析任务场景打上标记作为下一步AI训练的基础数据。抽取并实现一组API来获取、操作IDE内的动作并实时的反馈到屏幕上供人工干预这个过程。至于这个AI怎么训练,那可以将这个项目开源交给AI的专业人士搞定,我可以力所能及的收集数据或组合已有功能。退一步来说,就算AI搞不出来,收集的这些信息可以继续试用传统方式来处理,也能推动现一点点xxxAsCode的进程。

方案设计

项目名代号暂定为skykoma,分别取自skynet和tachikoma的一半,如果有好的名字欢迎反馈给我。整个项目拆分成若干模块分别独立实现,如果有生之年每个部分最后都能完全实现,则组合到一起。

信息收集模块:收集当前工程结构、版本控制、产物、光标上下文、IDE等信息,这些信息等价于开发员看到和了解到的信息。收集开发人员对IDE的操作序列数据。

信息处理模块:对收集到的信息做预处理,标记子序列对应的场景并用自然语言描述,在必要的位置插入人工干预的节点,以此作为训练的基础数据。

指令生成模块:接受自然语言描述的任务,生成一系列结构化指令

指令调度模块:接收一系列结构化指令并转化成具体的动作,动作的序列必须满足人工可观测、可干预、可理解,符合正常人操作的习惯,且动作序列有对应的API。理论上训练数据如果足够好可以达到部分效果,但为了可控和安全还是需要一个调度模块,为的是有机会通过程序强制干预AI的结果,加一道保险。

动作执行模块:给定具体动作并执行的同时收集结果、可视化反馈

以一个常见的场景为例:

自然语言描述:查找ClassA的所有实现类。

指令生成模块:打开搜索功能,输入关键字ClassA,从结果中定位ClassA,打开ClassA对应的文件编辑器,光标定位到ClassA的声明位置,搜索实现类(Ctrl+Alt+B)

指令调度模块插入必要指令:打开搜索功能,输入关键字ClassA,从结果中定位ClassA,判断是否能定位到ClassA(满足人工干预的要求),尝试打开ClassA对应的文件编辑器,光标定位到ClassA的声明位置,搜索实现类(Ctrl+Alt+B),收集搜索结果同时展示到屏幕上(满足可观测的要求)

指令调度模块转化指令到具体的API:打开搜索功能,输入关键字ClassA,从结果中定位ClassA,判断是否能定位到ClassA(满足人工干预的要求),尝试打开ClassA对应的文件编辑器,搜索当前文件下所有的class声明(指令调度需要生成API可支持的操作),寻找ClassA的声明位置(指令调度需要生成API可支持的操作),搜索实现类(Ctrl+Alt+B),收集搜索结果同时展示到屏幕上(满足可观测的要求)

执行模块:依赖指定调度的API结果控制IDE依次执行对应的指令

以下的ChatGPT给出的示例代码

 

概要设计

以上模块将会划分成多个开源项目,为了便于实现部分逻辑上的模块会放到同一个实际的项目中

交互层 skykoma-plugin-idea(1), skykoma-plugin-vscode(2), skykoma-chatbot(3) , skykoma-agent(待定),  skykoma-workspace(待定) 顾名思义交互层定位于用户的输入和输出,同一管理最外层的交互

数据层 skykoma-data-collector(4), skykoma-data-handler(5), 收集和处理所有相关的数据并存储、上报

逻辑层 skykoma-core(6), skykoma-cmd-generator(7), skykoma-api-transformer(8), skykoma-api-worker(9), core就是核心的模型负责自然语言处理并生成第一版指令序列,cmd-generator负责指令的调度、插值、过滤以符合前文所说的各种条件,api-transformer负责把指令序列做翻译成现存工具的API调用, worker负责调用具体的API

执行层 skykoma-api: skykoma-api-idea,skykoma-api-vscode,skykoma-api-git(10),skykoma-api-mvn(11),skykoma-api-gitlab(12),skykoma-api-github(13), 顾名思义,实现整合各个现有工具的功能,以localhost HTTP、cmdline、rpc等方式对外提供服务

里程碑规划

v0.1 搭框架

手动完成realworld或者其他什么的工程编写作为测试项目,要求满足常见的企业级开发特性。

初始化idea插件工程(https://github.com/dev-assistant/skykoma-plugin-idea),支持私有化部署,收集idea当前的项目信息。

初始化数据收集工程(https://github.com/dev-assistant/skykoma-data-collector),支持私有化部署,数据存储位置可自行选择。

skykoma-plugin-idea概要设计

架构上因为不想出现多个idea插件,因此设计一个skykoma-api-idea负责和idea的api具体交互,kykoma-plugin-idea依赖skykoma-api-idea来获得对IDE完整的感知和控制能力。plugin支持常用的LiveTemplate,设计生成规则,在固定场景下支持人工触发生成。skykoma-api-idea收集idea的状态和操作事件并上报给skykoma-data-collector达成数据收集目的。同时也可以通过websocket、http等协议监听本地端口或从外部的控制模块获取指令。

模板代码自动生成功能

模板代码生成主要依赖于LiveTemplate实现,分为静态生成和动态生成,静态生成是上下文无关的,动态生成可感知代码上下文。

静态生成

如@Autowire @Column 之类的,试用idea自带API完成命名风格的转换。

动态生成

日志:寻找上下文中可用的LOGGER,若LOGGER不存在则尝试先生成LOGGER字段,并根据当前上下文插入LOGGER语句。

SQL:寻找上下文中的ORM注解信息,根据字段类型生成默认的建表语句。

数据复制:根据上下文获取指定类的所有属性,复制到当前上下文中或指定的对象中,代替动态的反射copy避免运行时异常。

Service:增强自带的创建类过程,根据类名猜测正确的包名,一键生成带注解的Service类和接口并实现对应接口满足主流项目的风格要求,减少人工操作。

代码见 https://github.com/dev-assistant/skykoma-plugin-idea

数据上报功能

TODO 设计数据结构表示上下文,包括当前窗口信息、窗口标题、项目信息、git信息、全量AST信息等

代码见 https://github.com/dev-assistant/skykoma-data-collector

skykoma-data-collector概要设计

负责skykoma需要的数据收集工作,用多种合适的存储方案持久化用户每个操作的上下文信息和事件。语义图暂定neo4j图数据库,其他结构化信息继续使用mysql。

设计数据结构存储相关信息

有了全量AST和语义,才有可能做到
1.给定任意项目任意API、自动提取可运行的最小依赖沙盒环境
2.任意复杂业务逻辑分析,不重不漏
3.沙盒环境的修改可回填到原始项目:可debug、做测试用例、分析逻辑、修改
4.因沙河内依赖最小化了,理论上可以用自然语言总结所有的上下文和代码意图信息、为炼丹做准备。退一步即使炼丹失败,也能快速抽象出大量模板代码来使用
5.反向给定自然语言生成沙盒环境、可完成对任意项目的patch

 

参考

https://arxiv.org/pdf/2302.04761.pdf

https://arxiv.org/pdf/2211.10435.pdf