大部分国内的模型, 在结构化输出和工具调用方面都做得不如 OpenAI 和 Anthropic, 两者无法兼顾。这种情况下我们应该优先选择工具调用能力强的模型, 因为在当前背景下, 没有工具调用能力的模型没有太大用处。况且, 我们也可以伪造一个输出工具, 让模型把你需要的结构化输出结果通过工具调用的形式填入, 变相解决了结构化输出的问题
今年有挺多事情想做的,想做游戏,想学习 blender 想学习 aseprite
所以我应该不止是让 AI 替我做,而是通过它学习,辅助我掌握。我知道现在很多人提倡的是习惯 AI 自动化,但我觉得即使我克服这种不适,也会变废
今天终于确认了我对 AI 的使用态度。我还是应该克制。设想 AI 在最聪明的状态,它可以取代我,无所不能,那么我更应该做的是学习,掌握它能给予我的知识。我所恐惧的其实是把一切交给 AI 然后失控的感觉。自动化最重要的就是人类可以介入。 AI 可以帮我干活,但我应该理解它在干什么,至少我要懂一些基础,才能控制方向,成为它的 leader.
从这份 lsblk 输出可以确认:

- nvme0n1p1 1G → **EFI 系统分区**(`/boot/efi`)
- nvme0n1p2 2G → /boot 分区
- nvme0n1p3 1.8T → 物理卷,其下逻辑卷 ubuntu--vg-ubuntu--lv 才是 真正的根(/)

下面给出**完全离线**的挂载 + GRUB 重装命令,直接复制即可。

---

### 1. 启动 LiveCD → 打开终端
“Try Ubuntu” 进入桌面后 `Ctrl+Alt+T`。

---

### 2. 激活 LVM 并挂载各分区
sudo vgchange -ay          # 激活卷组(否则逻辑卷不可见)
sudo mkdir -p /mnt/ubuntu
sudo mount /dev/mapper/ubuntu--vg-ubuntu--lv /mnt/ubuntu   # 挂载根(LVM)
sudo mount /dev/nvme0n1p2 /mnt/ubuntu/boot                 # 挂载 /boot
sudo mount /dev/nvme0n1p1 /mnt/ubuntu/boot/efi              # 挂载 EFI
# 绑定虚拟文件系统
for i in dev proc sys; do sudo mount --bind /$i /mnt/ubuntu/$i; done

---

### 3. 进入原系统环境
sudo chroot /mnt/ubuntu /bin/bash

---

### 4. 重装 GRUB(UEFI 模式,离线)
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=ubuntu --removable
update-grub

---

### 5. 退出并重启
exit
sudo reboot
拔掉 LiveCD,GRUB 菜单应已恢复,可正常进入 Ubuntu。
因为 soluna 支持写 app 应用,而我最近正好打算为我父母和妻子编写一个抖音直播录制工具 app,想要试试。不过这有个问题,就是录制部分需要使用 ffmpeg 来进行,而 ltask 虽然可以 N:M 调度 lua 虚拟机,但是如果像 ffmpeg 这种阻塞调用,就会导致线程阻塞了。
当然,解决方法也是有的,比如引入事件循环,将 fork 的进程挂载到事件循环上,比如使用 bee.lua 就是个不错的选择。不过这意味着需要修改 soluna 的源码,在虚拟机注入 bee.lua。但我又不太想修改源码。首先这个功能对于 soluna 本身没有什么必要,因此我倾向于不到上游提需求;其次,我也不想修改源码,方便后续同步上游最新更新。
这时候我突然理解了 C 语言的一个优点了。
ltask 预留了一个 -DLTASK_EXTERNAL_OPENLIBS 选项,soluna 本身的库就是通过 -DLTASK_EXTERNAL_OPENLIBS=soluna_openlibs 在编译时注入符号的。
这意味着,我只需要把 soluna 设置为我的子仓库依赖,然后创建一个例如 soluna_app.c 的文件。在这个文件里面我创建一个 soluna_app_openlibs 函数,它内部除了调用 soluna_openlibs 还注入 bee.lua。最后在编译时,我把定义改成 -DLTASK_EXTERNAL_OPENLIBS=soluna_app_openlibs 从而就可以实现不更改源码,增量编译增加功能
yoga 在排版中的具体作用是什么呢?举个简单的例子,就拿 soluna/test/layout.lua 来举例:
我们创建如下一个 hud 排版, 它是一个简单的左右结构。其中左边我们定义了宽度为 400, 右边则是填满剩下的宽度。两者都填充满高度。另外右边是上下结构,其中第一个 node 占了70%的宽度,第二个占了30%的宽度。

local hud = [[
id : screen
padding : 10
direction : row
gap : 10
left :
 width : 400
 background : 0x40000000
right :
 flex : 1
 gap : 10
 node :
  flex : 0.7
  background : 0x40ffffff
 node :
  flex : 0.3
  background : 0x40ffffff
]]

此时单单使用这个 hud 我们还做不了什么。
紧接着我们在脚本入参中,可以获得启动时窗口的宽高, 我们可以将其设置到 screen 上:

local dom = layout.load(datalist.parse_list(hud))
local screen = dom.screen

local function calc_hub()
 screen.width = args.width
 screen.height = args.height
 return layout.calc(dom)
end

上面这段代码做了三件事: 加载布局到 yoga 中, 然后为 screen 设置宽高, 接着调用 calc 计算布局。
最后,假设宽是800,高是600,那么 yoga 会根据 left 的宽,计算出 right 的宽度为 400, 两者的高度都是 600; 而在 right 中第一个 node 的高 420, 第二个则是 180.
最后我们将计算结果使用 matquad 材质构建出实体:

local function draw_hud()
 for _, obj in ipairs(draw_list) do
  args.batch:add(matquad.quad(obj.w, obj.h, obj.background), obj.x, obj.y)
 end
end

并使用 batch:add 提交绘制命令,告诉框架,我要创建一个四边形(quad), 它宽w高h, 使用背景色 background, 绘制时的坐标是 x 和 y。
最后在 frame 中调用该函数,也就是每一帧都绘制。
当窗口发生变化时,我们又更新宽高,然后重新计算 node 布局信息,这样就可以在下一帧及时更新整体布局。
Back to Top