ChatGPT5–学习加速器

https://chatgpt.com/share/68bb0c7a-35e8-8003-8b0f-414a2e3f33ed

平时工作用大部分时间都是qwen/deepseek/豆包, 今天准备睡觉突然来兴趣上ChatGPT问一个之前在deepseek已经over的问题,结果回答得让我精神一振毫无睡意。

回答得实在太好,比deepseek强不知道多少倍!也不知道是不是因为ChatGPT可以实时翻阅GitHub导致,质量优秀得无话可说。估计后面好一段时间又得跑GPT了。

浅尝(Dify)AI流程编排

其实也做了有好一段时间, 一直没时间归档, 今天决定mark一下.

由于工作需要, 最终效果需要具备移植性, 所以我这里Dify平台也尝试了本地部署, 以保证最终可交付.

准备就绪, 接下来就是创建ChatFlow

先捋一下编排构思:

  • 目标是实现即时的数据查询统计分析
  • RAG提供知识库, 由LLM自行推断数据查找办法
  • 提供数据查询接口
  • 结果渲染(列表or图表)

Dify当前是原生支持http请求节点, 安装工具也能够支持database. 所以取数问题不大. 刚开始先直连数据库做个demo, 大概流程如上图所示

实际调试下来, 需要增加很多的优化节点才能保证有一个比较高的准确性, 上图是花了近一天时间调试下来的效果, 而且还没有足够完善, 比如数据库查询这一个环节, 有几率会出现生成的SQL无法执行, 需要增加重试机制(不是简单的重新执行, 而是把错误结果作为BuildSQL的一部分进行重试)

贴几处提高准确率办法:

1. 提取关键字再进行知识检索能提高检索准确率

2. 自动编写SQL, 需要对做好限定条件, 且保留sql到最终回复(便于持续优化)

3. 固定逻辑写Python处理, 而不是全靠LLM

4. 保留好异常结果(不要过于依赖后台预览, 发布后仍然需要持续优化)

整体用下来基本都是拖拉拽, 测试, 调整prompt, 测试, 增删节点,调整promtp, 测试……

目前有发现影响比较大的问题: 预览测试失败时, 失败节点如果在循环体内, 无法显示是循环体内的哪个节点有问题, 这个超级不方便(我用的1.3.0版本)

多个Echarts结果无法连结同一个变量聚合器, 导致柱状图/饼图/线型图都需要分别做结果处理

DSL导出导入经常不可用,怀疑是版本匹配问题

十年学会编程

原文

研究表明 (HayesBloom)在任何一种领域内,象下棋、作曲、绘画、钢琴演奏、游泳、网球、以及原子物理学和拓扑学,等等,要达到专家水平大约都要花十年时间。没有真正的捷径:即使是莫扎特,4岁时就是音乐神童,13年后才开始写出世界级的作品。在另一方面,披头士似乎在1964年的EdSullivan表演上一炮走红。但他们从1957年就开始表演,在获得大众青睐后,他们的第一个重大成功,Sgt.Peppers,是1967年发行的。Samuel Johnson (塞缪尔·约翰逊,英国辞典编纂家及作家)认为要花比十年更长的时间:“在任何领域中出类拔萃都要用毕生的劳作来取得;它不可能用较低的代价获得。” 而Chaucer(乔叟,英国诗人)感叹到:“人生短暂,学海无涯。”

脚本小子

还记得初中那会,大家上网都很直接(各种意义上的直接),我家的电脑就经常中毒,QQ就不知道被盗走了多少遍,也就这种环境接触到了各种奇奇怪怪东西,这其中就有关于黑客技术圈子,现在还记得网上找QQ病毒的教材, 然后和制作视频的人加了上了好友(那会大家都觉得自己好牛X,发了视频都会留QQ)。后来自己东抄抄西抄抄,生搬硬套的也开始自己录视频,QQ加了几千个人,想起来真是好玩。

印象中花时间最多的是病毒免杀,简单说就是通过一些手段让病毒不再被杀毒软件检查得到,那时候根本不懂编程,只会学着视频教材对病毒进行各种加工,这个过程有点像养蛊,由于不懂原理,这过程对于我来说就是一个黑盒子。 要说收获也有,就是懂得了重装系统,然后比一般人更会操作电脑,如此而已。

(其实还有,2022年公司被勒索病毒攻击,因为有这方面的积累,成为第一批开始行动的人,虽然事后也没有统计帮上了多少忙,但没关系)

然后上了高中后就开始少有接触,直至上了大学才又是捡回来,开始接触Delphi,VB,C++,汇编… 然后不知道怎么着的,在网易云课堂找到了C语言课程,总算是正儿八经的开始入门了。

转存一篇2019年前写的《流水账一下对二进制理解》

图文无关

昨天研究HashMap(版本8)底层。
发现jdk的工程师对于HashMap定义容量的处理超级聪明。
对于手动输入容量值,底层会计算出一个比她大,而且最近的一个 二次幂的数
比如输入7,会得到8,输入9,会得到16,输入17会32。
底层用的是位运算。因为2次幂数二进制都是1开头后面全是0。
2==>10
4==>100
8==>1000
16==>10000
二进制规范(int)一下如下

0000 0000 0000 0000 0000 0000 0000 0010
0000 0000 0000 0000 0000 0000 0000 0100
0000 0000 0000 0000 0000 0000 0000 1000
0000 0000 0000 0000 0000 0000 0001 0000


所以只要知道输入的值的二进制,前面加个1,后面清零即可得到最邻近的二次幂数

/**
 * Returns a power of two size for the given target capacity.
 */
static final int tableSizeFor(int cap) {
	int n = cap - 1;
	n |= n >>> 1;
	n |= n >>> 2;
	n |= n >>> 4;
	n |= n >>> 8;
	n |= n >>> 16;
	return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}


上面是JDK工程师是这样写的,通过右移并对自己进行或运算,“把自己全部变成1”,然后再+1得到结果。
看完先默默膜拜个一秒钟。
自个儿的回顾了一下二进制,大概就是自问自答,自己考自己吧。 然后发现自己对于“ 怎么用位运算快速得到 int 区间的最大值和最小值 ”竟然回答不出来!

事发在昨天晚上,今天早上自己跟自己较劲了一整个上午时间,流水账一下总结:
因为 int 类 占4个字节,每个字节占8个bit位置。所以每个int都需要占用32个bit的空间。
最小当然是全部都是0,最大是全部都是1。
微笑脸,这是错的。
因为一开始的那一位是用来代表正负的(1代表负,0代表正数),
所以实际上可以用来存值只有31个bit的位置。
而每个bit有两个状态:1和0。所以 int 可以代表的范围:
开头第一个bit是0时(正数) 有 2个2个2…一共有31个2。即2的31次方。
开头第一个bit是1时(负数) 有 2个2个2…一共有31个2…
所以一共有2的31次方再乘2 也就是2的32次方。

这样从后往前推的理解跟一开始学计算机时候理解的:“32个bit 每个bit代表1和0 所以int的取值范围是2的32次方”,丰富多了。
顺带还解决了我以前的一个疑惑:
为什么科教数上对于 int 的定义都是
-2147483648~ -1,0 ~2147483647
而不是
-2147483647~ 0, 1~2147483648
因为0 二进制的32个0,它的第一位也是0。

这过程还复习了一些很陌生的运算符:“~” 和 “>>>”
“~”是二进制的取反,比如
0的二进制:0000 0000 0000 0000 0000 0000 0000 0000
~0的二进制:1111 1111 1111 1111 1111 1111 1111 1111
那~0后再全部用 >>1 移一个bit的位置不就是
0111 1111 1111 1111 1111 1111 1111 1111
了吗,这不就是正数时候 int 的最大值
理论没错,但是使用 >>1 时候会根据最开始的位数进行补充。
换句话说,因为~0的第一位是1 所以补充的也是1,嗯,帮倒忙变成了:
1111 1111 1111 1111 1111 1111 1111 1111
//扶额。并没有任何变化! 我希望补充的是0而不是1可以吗
可以!
使用 “>>>1” 就可以了
所以 int 的最大值可以是 ~0>>>1
通过这样得到 int 的极限值 比以前的那种通过 int 溢出得到边缘数好玩多了!
/*
多手尝试了一下<<1和<<<1会不会也是这种套路
结果是输入第三个<后直接就冒红警告了
*/
还有一些跟常识有点冲突的理解,
比如上面的 ~0 二进制是全部都是1。用常规的理解就是,负数情况的极限值
即int 的负边缘数: -2147483648 ?
按这个套路,二进制是
1000 0000 0000 0000 0000 0000 0000 0001
的时候,就是负数时候第一个数,即右边缘数, -1 ?
答案是错的。这就是跟常规的冲突所在了。
我们普通的理解下 前面加个 “-” 后面的数字越大,其实质上是越小,因为她是朝着负的方向头走的,所以走的越远,越小。
但是这里不能这样套路,int 的第一个 bit 不可以直接用 “1是 – ”,“0是+” 进行直接翻译。

想透彻了解有点辛苦,大概就是 二进制10为什么是int的2 这样的问题。理解10变成2的话就自然懂了。
从简单推导也可以辩证出。 比如简单的二进制加减 这个入手。
1+1 二进制
0000 0000 0000 0000 0000 0000 0000 0001
+
0000 0000 0000 0000 0000 0000 0000 0001
逢2进1。变成
0000 0000 0000 0000 0000 0000 0000 0010

自然也不难理解 为什么int的最大值 2147483647 即二进制
0111 1111 1111 1111 1111 1111 1111 1111
为什么加一变成负数。因为加1后变成了
1000 0000 0000 0000 0000 0000 0000 0000
也就是符号被填充了1,成了负数。

回到刚才,0-1大家都知道等于-1。
即全部bit都是零的二进制减1,需要一直问前面的借借数,一直到最左边顶端的0也成为了1。
即全部1
1111 1111 1111 1111 1111 1111 1111 1111
代表的是 0-1= -1
这就是负数情况下的 “最大” 值。

计算机是对的。觉得冲突其实很简单,因为我们口头上说最大负数时候有时候会理解成那个“最左边”的数
比如
-999,-998…-1,0,1…998,999
的-999
然而“最大的负数”其实一直都不是 -999 而是 -1 啊。
所以 int 的最小负数 也就是开头是1 后面全是0
1000 0000 0000 0000 0000 0000 0000 0000
代表的是
-2147483648
一点毛病都没有!

-2147483648 + 1 = -2147483647 (1000 0000 0000 0000 0000 0000 0000 0001)
-2147483647 + 1 = -2147483646 (1000 0000 0000 0000 0000 0000 0000 0010)

一直加上去 直到到了 int 的最大负数 -1 (1111 1111 1111 1111 1111 1111 1111 1111)
-1+1 又变成了全部都是0
嗯,-1 + 1 当然等于0
没毛病!

source https://blog.csdn.net/yongroot/article/details/89431136

CSDN是越来越讨厌了,大概这辈子都不会再用。。

让我寝食难安的“雪花算法”

先科普:

雪花算法是一种由Twitter(现在是X)推广的分布式全局唯一ID的算法。关于雪花这个名称的解释,我自以为是因为雪花是随机飘落然后越堆越高的,前天同事问我雪花的由来是不是因为每片雪花都不一样,我觉得这个解释更浪漫。

回归主题,简单说这是一种生成不重复ID的算法,有别于UUID, 雪花具备自增性的,方便数据库存储和使用(可排序,体积也不大)

原理是一个63位(二进制)的正整数Long值,前41位保存毫秒级时间戳,中间10位存放机器唯一标识,剩下12位是用于1毫秒内序列递增, 如果1毫秒内递增序号值满12位,那就等待直至时间毫秒数+1再继续。

我手上有老一个项目,使用的是一套自编的ID算法:

  • ID也是纯数字
  • 使用数据库保存最新id
  • 应用启动时从数据库获取5000个id备用 (then update id+=5000 to DB), 不够再拿。

这套算法有一个问题, 就是每个环境id进度不一样, 导致dev的配置数据无法直接迁移到sit,同理sit无法迁移到uat,导致配置成本很大。 所以我提议并主导引入雪花算法,经过一轮技术预研+组会讨论影响+1周dev+1月sit….. 雪花算法被推送到了生产。

然!

生产环境竟然出现ID冲突,初期以为因为机器码冲突

ps:dev发现Long值太长导致前端模块出现了异常,所以雪花缩水成53位,主要是机器码由10位缩成4位,序号从12位缩成8位,机器码基于hutool实现(使用ip)

然后就是夜里挑灯看…bug。经过了两日两夜的emjo:

先试调整算法,机器码增到到8,序号缩到4,问题没解决(其实这时候有种不好的预感,8位机器码即可容纳256个pod 竟然这么容易冲突?)

通宵添加了一个唯一机器码计算程序(给serviceName增加一个数字,每次应用上线时从服务注册中心下载清单校验唯一性),问题没解决

第二天白天研读代码发现Snowflake类存在多例情况(Snowflake创建在一个数据逻辑层底层对象类, 基于spring框架先入为主以为是单例,检查发现竟然不是,导致Snowflake类实际被创建多次),逐把Snowflake类属性增加static,信心满满以为解决,发版测试发现还是没解决

到了这个时候, 从代码已经检查不出问题了,后面模拟线上环境编写单元测试没问题, 通过日志埋点(仍怀疑workerId冲突),数据分析等等等一系列调试, 都没有发现问题。

直至现在

嗯, 现在也还没解决, 目前是代码逻辑增加一个外部引入的boolean做算法路由,先暂时切回到了传统算法, 这个五一假期继续研究吧。


回来补一下后续

后面再过滤几轮压测, 都没有再复现了, 有几个可能性:

  1. 当时有其他开发人员连接上了SIT环境, 由于每套环境有独立的注册中心, 所以我做的基于注册中心实现的唯一workerId验证并不可靠(其实前面有考虑过用数据库实现, 但是成本太高且手上还忙着其他事情,就搁置了。
  2. 压测并不到位(其实问题还在), 除非我能亲眼看到问题产生的过程且能够按照缺陷原理复现,否则这种靠外部测试来论证问题结果的有概率性的办法永远让我不得心安。