VLA 系统学习第 12 课:为什么机器人不能只看一帧?——时间序列、Observation History 与 Action Chunk

第十一课标准答案

上一课真正要建立的是一个判断习惯:

看到 Robot Learning / VLA 的测试结果,先不要急着看 Success Rate,先看它到底怎么切数据、测试了什么"没见过的东西"。


1. 为什么 Robot Dataset 随机按 Frame 划分特别容易造成 Data Leakage?

因为同一个 Episode 中相邻 Frame 高度相关。

例如:

复制代码
Episode 7

Frame 100 → Train
Frame 101 → Test
Frame 102 → Train

假设控制频率很高,那么 Frame 100 和 Frame 101 可能只相隔几十毫秒。

它们的:

  • 图像;
  • Robot State;
  • Action;

都可能几乎一样。

于是虽然:

\ Frame_{101} \\

名义上属于 Test,但模型训练时已经看过几乎完全相同的:

\ Frame_{100},Frame_{102} \\

这样得到的 Test Performance 会被高估。

这就是一种典型的:

\ \\boxed{\\text{Data Leakage}} \\


2. Episode-level split 解决了什么?

Episode-level split 会把一整次任务轨迹作为最小划分单位。

例如:

\ Episode_{0\\sim79}\\rightarrow Train \\\ Episode_{80\\sim89}\\rightarrow Validation \\\ Episode_{90\\sim99}\\rightarrow Test \\

这样:

一个 Episode 中的相邻 Frame 不会一部分进入 Train、一部分进入 Test。

因此至少解决了:

\ \\boxed{\\text{同一条轨迹内部的时间泄漏}} \\

问题。


3. 为什么按 Episode 划分后仍不能自动说"泛化很好"?

因为不同 Episode 虽然是不同的轨迹,但实验条件可能几乎完全一样。

例如所有 Episode 都是:

复制代码
同一个物体
同一个背景
同一个任务
同一个相机
相近的初始位置

那么 Test 只是:

在相似条件下重新做一遍任务。

它并没有证明模型能够处理:

  • 新物体;
  • 新场景;
  • 新任务;
  • 新语言;
  • 新位置分布。

所以必须继续问:

\ \\boxed{\\text{Generalize to what?}} \\


4. Seen Object 和 Unseen Object 有什么区别?

最直接理解:

如果某个物体在训练数据中出现过,那么测试时再次出现,可以称为:

Seen Object

如果训练阶段完全没有出现,而只在测试阶段第一次出现,则可以称为:

Unseen Object

但以后看到论文写 unseen,不能自动理解成"整个任务都没见过"。

必须看作者具体定义的是:

  • unseen object;
  • unseen task;
  • unseen scene;
  • unseen instruction;
  • 还是其他东西。

5. IID Test 和 OOD Test 怎么直观理解?

IID 可以先理解成:

考试题是新题,但仍然来自和平时练习差不多的出题规律。

例如训练:

\ x\\sim\[-0.1,0.1 \]

测试也从差不多的范围随机采样。

而 OOD 可以理解成:

考试时出现了训练数据没有覆盖好的情况。

例如训练:

\ x\\in\[-0.1,0.1 \]

测试突然变成:

\ x\\in\[0.3,0.4 \]

数据条件发生明显变化。


6. Train 中

\ x\\in\[-0.1,0.1 \]

Test 中:

\ x\\in\[0.35,0.45 \]

更接近什么?

更接近:

\ \\boxed{\\text{OOD}} \\

因为测试位置明显超出了训练主要覆盖范围。

模型需要对训练分布之外的位置进行泛化。


7. 为什么 95% Success Rate 不一定强于 85%?

因为两篇论文可能根本不是在考同一种考试。

例如论文 A:

复制代码
Seen Object
Seen Scene
Seen Task

成功率:

\ 95\\% \\

论文 B:

复制代码
Unseen Object
Unseen Scene

成功率:

\ 85\\% \\

B 的测试可能明显更困难。

所以模型比较不能只看:

\ Success\\ Rate \\

还必须一起看:

\ \\boxed{ Dataset + Split + Task + Evaluation\\ Protocol + Metric } \\


8. 阅读 Robot Learning Repository,除了 Dataset Class 还必须检查什么?

至少继续找:

\ \\boxed{\\text{Dataset Split Protocol}} \\

也就是:

Train / Validation / Test 到底按照什么单位划分?

重点看:

  • Frame-level?
  • Episode-level?
  • Object-level?
  • Task-level?
  • Scene-level?

以及 Test 中哪些条件属于:

  • Seen;
  • Unseen;
  • IID;
  • OOD。

这直接决定最终评估结果到底在证明什么。


VLA 系统学习第 12 课:为什么机器人不能只看一帧?------时间序列、Observation History 与 Action Chunk

到目前为止,我们为了把 Behavior Cloning 讲清楚,一直把 Policy 写成:

\ a_t=\\pi(o_t) \\

意思是:

给我当前时刻 \(t\) 的 Observation,我预测当前时刻应该执行的一个 Action。

这个形式很重要,但它把真实机器人中的两个时间问题隐藏掉了。

第一个问题:

只看当前这一帧,真的足够判断机器人现在在干什么吗?

第二个问题:

Policy 为什么一定要每次只预测一个 Action?能不能一次预测未来一串动作?

这两个问题分别会引出:

\ \\boxed{\\text{Observation History}} \\

以及:

\ \\boxed{\\text{Action Chunk}} \\

而 Action Chunk 正是后面 ACT 的核心入口之一。


一、先理解:一张静态图片经常缺少"运动信息"

假设你只看到一张照片:

复制代码
一个机械臂夹爪在物体左侧

你知道夹爪现在在哪里。

但你未必知道:

它正在往右靠近物体?

还是:

它刚刚从右边往左退回来?

单独看:

\ I_t \\

有时候两种情况看起来几乎一样。

但如果同时看到:

\ I_{t-2},I_{t-1},I_t \\

你就可能发现:

复制代码
前两帧夹爪更靠左
现在逐渐向右

于是知道:

它正在向右运动。

这就是时间序列信息的价值。


二、Robot State 也有同样的问题

假设当前关节位置:

\ q_t=0.5 \\

只看:

\ q_t \\

你只知道:

当前关节在 0.5。

但不知道:

它正在向正方向运动,还是负方向运动。

如果前一时刻:

\ q_{t-1}=0.4 \\

那么:

\ 0.4\\rightarrow0.5 \\

说明它最近在增大。

如果:

\ q_{t-1}=0.6 \\

那么:

\ 0.6\\rightarrow0.5 \\

说明它最近在减小。

所以:

\ q_t \\

只是位置,

而:

\ (q_{t-1},q_t) \\

还隐含了运动趋势。


三、这就是 Observation History

以前我们使用:

\ o_t \\

现在把过去若干个 Observation 一起给模型:

\ \[o_{t-k+1},\\ldots,o_{t-1},o_t \]

这就是:

Observation History

也可以理解成:

当前时刻之前的一小段历史窗口。

例如 History Length:

\ T_o=3 \\

那么一个输入可能是:

\ \[o_{t-2},o_{t-1},o_t \]

Policy 就变成:

\ a_t = \\pi( o_{t-2}, o_{t-1}, o_t ) \\

而不是:

\ a_t=\\pi(o_t) \\


四、这时候 Tensor 为什么突然多了一个维度?

以前一个 Robot State:

\ s_t:\[D_s \]

例如:

\ D_s=10 \\

那么:

\ s_t:\[10 \]

如果现在给过去 4 个时刻:

\ s_{t-3},s_{t-2},s_{t-1},s_t \\

把它们堆在一起:

\ \[4,10 \]

第一个维度:

\ 4 \\

就是:

Time / Sequence Length

通常用:

\ T \\

表示。

于是:

\ \[T,D_s \]

加入 Batch:

\ \\boxed{ \[B,T,D_s } \]

例如:

\ \[32,4,10 \]

表示:

  • 32 个 Sample;
  • 每个 Sample 有连续 4 个时间点;
  • 每个时间点的 Robot State 是 10 维。

五、一定要区分 \(B\)、\(T\)、\(D\)

以后 Transformer、ACT 中会频繁看到:

\ \[B,T,D \]

这三个维度必须形成条件反射。

\ B \\

是:

Batch Size

表示:

一次训练多少个样本。


\ T \\

是:

Sequence Length / Time Length

表示:

每个样本内部有多少个时间位置。


\ D \\

是:

Feature Dimension

表示:

每一个时间位置用多少个数字表示。

所以:

\ \[32,4,10 \]

绝对不能读成:

32×4×10 的三维数组。

虽然数学上没错,但这没有理解数据语义。

你应该读成:

32 个训练样本,每个样本包含 4 个连续时间步,每个时间步有 10 维特征。

这就是后面读 Transformer Tensor 的基本方式。


六、图像有时间维以后 Shape 怎么变化?

原来一张 RGB 图:

\ \[3,H,W \]

一个 Batch:

\ \[B,3,H,W \]

如果每个 Sample 现在包含 \(T\) 张连续图片:

\ \\boxed{ \[B,T,3,H,W } \]

例如:

\ \[32,4,3,224,224 \]

解释:

复制代码
32个Sample
↓
每个Sample 4个时间点
↓
每个时间点1张RGB图片
↓
3个颜色通道
↓
224×224

这就是 Observation History 的图像版本。


七、但"历史帧"不是越多越好吗?

不是。

假设使用:

\ T=2 \\

模型只看很短历史。

优点:

  • 数据量小;
  • 计算量低;
  • 延迟小。

但可能缺少更长时间信息。

如果:

\ T=100 \\

模型看到的历史很多,

但:

  • Tensor 更大;
  • 显存更高;
  • 计算更慢;
  • 很久以前的信息可能没有价值。

所以:

\ T \\

是一个设计参数。

不是:

越大越先进。


八、到这里我们解决的是"输入看多久"

也就是:

\ \\boxed{\\text{Observation Horizon}} \\

可以粗略理解:

Policy 决策时考虑多少历史 Observation。

例如:

\ T_o=4 \\

表示使用:

\ o_{t-3:t} \\

也就是最近 4 个 Observation。

但是这里又出现第二个时间问题:

输出为什么只能是一个 Action?


九、以前我们一次只预测一个 Action

最基础 BC:

\ o_t \\rightarrow a_t \\

模型输入当前 Observation:

\ o_t \\

输出:

\ a_t \\

然后下一时刻:

\ o_{t+1} \\rightarrow a_{t+1} \\

所以运行:

复制代码
Observe
↓
Predict one Action
↓
Execute
↓
Observe
↓
Predict one Action
↓
Execute
...

这叫:

single-step action prediction

单步动作预测。


十、单步动作预测有什么问题?

假设一个抓取动作需要连续:

复制代码
向前
向前
向下
向下
关闭夹爪
向上

如果模型每一步都完全重新预测:

复制代码
第1帧 → Action1
第2帧 → Action2
第3帧 → Action3
...

那么相邻 Action 之间可能不够一致。

例如:

复制代码
Action t:
向右 1 cm

Action t+1:
突然向左 0.8 cm

Action t+2:
又向右 1.2 cm

就可能表现为:

动作抖动、不连贯。

尤其机器人控制本身具有明显的时间连续性。

因此一个自然想法是:

能不能一次预测未来一小段连续动作?

答案是可以。


十一、于是从一个 Action 变成一串 Action

原来:

\ a_t \\

现在预测:

\ A_t= \[ a_t, a_{t+1}, a_{t+2}, \\ldots, a_{t+K-1} \]

这里:

\ K \\

表示一次预测多少个 Action。

这串 Action 就可以称为:

Action Chunk

动作块。

如果:

\ K=4 \\

那么模型一次预测:

\ \[a_t,a_{t+1},a_{t+2},a_{t+3} \]

而不是只预测:

\ a_t \\


十二、Action Chunk 的 Shape 怎么来?

假设单个 Action:

\ a_t:\[D_a \]

例如:

\ D_a=7 \\

以前一个 Sample 输出:

\ \[7 \]

现在预测未来:

\ K=16 \\

个 Action。

那么:

\ A_t:\[16,7 \]

加入 Batch:

\ \\boxed{ A:\[B,K,D_a } \]

例如:

\ \[32,16,7 \]

应该读成:

32 个 Sample,每个 Sample 预测未来 16 个 Action,每个 Action 有 7 个维度。


十三、这里的 \(T\) 和 \(K\) 不一定一样

这是一个很重要的点。

输入可能看:

\ T_o=2 \\

个 Observation。

输出却可能预测:

\ K=16 \\

个 Action。

于是:

\ Observation: \[B,2,D_o \]

而:

\ Action: \[B,16,D_a \]

所以:

输入历史长度和输出动作长度是两个独立概念。

不要看到两个时间维就默认它们一样。


十四、为什么 Action Chunk 可能让动作更连贯?

因为模型不是把每一步当成完全孤立的问题。

它直接学习一段:

\ \[a_t,a_{t+1},\\ldots,a_{t+K-1} \]

这意味着:

模型可以同时考虑这一小段动作之间的整体关系。

例如一次抓取动作中:

复制代码
靠近
↓
下降
↓
闭合
↓
抬起

这些动作在 Demonstration 中本来就是连续发生的。

如果模型一起预测,它有机会学习:

这一小段动作应该作为一个整体保持协调。

这就是 Action Chunking 非常重要的直觉。


十五、但预测 16 个 Action,是不是一定全部执行?

不一定。

这里必须区分两个概念:

\ \\boxed{\\text{Prediction Horizon}} \\

和:

\ \\boxed{\\text{Execution Horizon}} \\

假设模型一次预测:

\ 16 \\

步:

\ \[a_t,\\ldots,a_{t+15} \]

并不意味着机器人必须一口气把 16 步全部执行完。

它可以只执行前:

\ 4 \\

步。

然后重新获取 Observation,再预测新的 Action Chunk。

于是:

复制代码
Observation at t
↓
Predict 16 actions
↓
Execute first 4
↓
New Observation
↓
Predict another 16
↓
Execute first 4
↓
...

这个思想以后在:

  • Diffusion Policy;
  • Receding Horizon Control;

中都会非常重要。


十六、为什么不直接把整个 Chunk 全执行完?

假设:

\ K=100 \\

模型预测未来 100 个 Action。

如果全部盲目执行:

复制代码
Action 1
Action 2
...
Action 100

中间环境稍微变化,

后面的动作可能已经不适合当前状态。

所以一个自然策略是:

预测得长一点,但只执行其中一部分,然后重新观察。

这样兼顾:

  • 动作连续性;
  • 闭环反馈。

十七、现在出现三个非常容易混淆的 Horizon

以后尤其学 Diffusion Policy 时必须分清。

Observation Horizon

\ T_o \\

表示:

输入看多少历史 Observation。


Prediction Horizon

\ T_p \\

表示:

模型一次预测多长的未来 Action Sequence。


Action / Execution Horizon

\ T_a \\

表示:

预测出来以后,真正执行其中多少步再重新规划。

例如:

\ T_o=2 \\\ T_p=16 \\\ T_a=8 \\

表示:

复制代码
看过去2个Observation
↓
预测未来16个Action
↓
实际执行前8个
↓
重新观察
↓
再次预测

不过不同论文对命名可能略有不同。

所以以后不能只看到 horizon 就自己默认含义,必须检查作者定义和代码。


十八、ACT 的 Action Chunking 为什么叫"Chunking"?

现在终于可以第一次真正理解 ACT 这个名字中的一个核心部分。

ACT:

Action Chunking with Transformers

先只看:

Action Chunking

它强调的就是:

Policy 不再只预测单个 Action,而是一次预测一段未来动作。

形式上:

\ o_t \\rightarrow \[a_t,a_{t+1},\\ldots,a_{t+K-1} \]

所以 ACT 并不是突然发明一个玄学概念。

它来自一个非常自然的问题:

单步预测机器人动作容易受到时序不稳定和高频决策等问题影响,那么能不能一次学习一段动作?

答案就是:

Action Chunking


十九、但是这里有一个很重要的问题:训练数据怎么得到未来 Action Chunk?

假设 Dataset 中原本存的是:

\ a_0,a_1,a_2,\\ldots,a_T \\

现在训练样本在:

\ t=20 \\

我们希望预测:

\ K=4 \\

个 Action。

那么 Target 就可以从 Dataset 中切:

\ \[a_{20},a_{21},a_{22},a_{23} \]

所以原本:

\ (o_t,a_t) \\

这个 Sample 定义变成:

\ \\boxed{ (o_t, \[a_t,a_{t+1},\\ldots,a_{t+K-1}) } \]

这就是为什么我们前面一直强调:

Dataset 中的时间结构非常重要。

Action Chunk 不是模型凭空创造未来监督信号。

这些 Future Actions 原本就在 Demonstration Trajectory 里。


二十、这时 __getitem__() 的职责也变了

最简单 BC Dataset:

复制代码
def __getitem__(self, index):    obs = ...    action = ...    return obs, action

Target 只有:

\ a_t \\

但 Action Chunk Dataset 的逻辑可能更接近:

复制代码
def __getitem__(self, index):    obs = ...    action_chunk = ...    return obs, action_chunk

其中:

\ action\\_chunk = \[a_t,\\ldots,a_{t+K-1} \]

所以现在你应该重新理解:

__getitem__() 不只是"读取第几个文件"。

它还可能决定:

一个训练 Sample 的时间窗口到底怎么切。

这就是代码阅读为什么必须看 Dataset 实现。


二十一、Episode 结尾会发生什么?

假设 Episode 总共只有:

\ 100 \\

步。

现在:

\ t=98 \\

而我们要求:

\ K=8 \\

未来需要:

\ a_{98},a_{99},a_{100},\\ldots \\

但 Episode 已经结束。

数据不够。

于是这里才自然出现我们之前暂时没展开的:

Padding

例如有效 Action 只有:

\ a_{98},a_{99} \\

剩下位置需要补齐到固定长度:

\ K=8 \\

可能得到:

\ \[a_{98},a_{99},PAD,PAD,PAD,PAD,PAD,PAD \]


二十二、为什么 Padding 后还需要 Mask?

模型必须知道:

哪些是真实 Action?
哪些只是为了凑 Shape 补出来的?

于是可能有:

\ mask= \[1,1,0,0,0,0,0,0 \]

其中可以约定:

\ 1 \\

表示有效,

\ 0 \\

表示 Padding。

那么计算 Loss 时:

Padding 的那些位置不应该当成真实 Action 参与训练。

否则模型会被迫学习:

"Episode 结束以后应该预测 PAD 伪数据。"

这是错误的。

所以:

\ \\boxed{ Padding \\rightarrow 通常需要配套Mask } \\

这里 Padding 和 Mask 出现就很自然了------因为现在确实遇到了变长时间序列问题。


二十三、现在看一次完整 Tensor

假设:

\ B=32 \\

Observation History:

\ T_o=3 \\

Robot State Dimension:

\ D_s=10 \\

那么:

\ state:\[32,3,10 \]

如果图像也保留时间:

\ image:\[32,3,3,H,W \]

注意这里出现两个 3:

第一个:

\ 3=T_o \\

表示 3 个时间点。

第二个:

\ 3=C \\

表示 RGB 三个 Channel。

所以必须根据位置和语义区分。

Action Chunk:

\ K=16 \\

Action Dimension:

\ D_a=7 \\

于是:

\ action:\[32,16,7 \]

这就是一个典型时间序列 Robot Learning Sample 在 Batch 后可能出现的形态。


二十四、为什么这个 [B,T,D] 会直接把我们带向 Transformer?

现在出现了一个新问题。

以前:

\ \[B,D \]

每个 Sample 只是一个向量。

现在:

\ \[B,T,D \]

每个 Sample 内部变成了一串:

\ x_1,x_2,\\ldots,x_T \\

也就是:

Sequence

序列。

例如:

\ \[x_{t-2},x_{t-1},x_t \]

或者 Action Query:

\ \[q_1,q_2,\\ldots,q_K \]

那么模型需要理解:

序列中不同位置之间是什么关系?

这正是 Attention / Transformer 擅长处理的问题。

所以 Transformer 并不是突然切换到另一个主题。

它是从:

\ \\boxed{ Robot\\ Data开始具有Sequence结构 } \\

自然出现的。


二十五、ACT 的问题已经逐渐显现出来了

现在我们已经能提出 ACT 要解决的一部分问题:

问题 1

Robot Manipulation 是时间连续的。

问题 2

单步 Action Prediction 可能缺少一整段动作协调性。

问题 3

Demonstration 本身就是:

\ a_0,a_1,\\ldots,a_T \\

这样的 Sequence。

于是可以考虑:

\ Observation \\rightarrow Action\\ Sequence \\

也就是:

\ o_t \\rightarrow A_t \\

其中:

\ A_t:\[K,D_a \]

然后用能够处理 Sequence 的 Transformer 建模。

现在"Action Chunking with Transformers"这几个词已经第一次真正连起来了:

\ \\boxed{ Action\\ Chunk + Sequence\\ Modeling \\rightarrow Transformer } \\

但现在还不能直接进入 ACT 源码。

因为我们还缺一个关键基础:

Transformer 到底如何理解 Sequence 中不同位置之间的关系?

这就是 Attention。


第二十六、整章链路回看

这一课从最简单 BC:

\ o_t\\rightarrow a_t \\

进行了两个方向的扩展。

输入端:

\ o_t \\

扩展为:

\ \[o_{t-k+1},\\ldots,o_t \]

也就是:

\ \\boxed{\\text{Observation History}} \\

因此 Tensor 出现:

\ \[B,T,D \]

输出端:

\ a_t \\

扩展为:

\ \[a_t,a_{t+1},\\ldots,a_{t+K-1} \]

也就是:

\ \\boxed{\\text{Action Chunk}} \\

因此 Action Tensor 变成:

\ \[B,K,D_a \]

最终:

\ \\boxed{ Sequence\\ Input \\rightarrow Sequence\\ Modeling \\rightarrow Sequence\\ Output } \\

这就是我们接下来进入 Attention / Transformer 的直接原因。


第十二课自测

  1. 为什么只看当前一帧 \(o_t\) 有时候无法判断机器人当前的运动趋势?

  2. 什么叫 Observation History?

  3. Tensor:

\ \[32,4,10 \]

中的 32、4、10 分别表示什么?

  1. 图像 Tensor:

\ \[16,3,3,224,224 \]

如果第一个 3 表示时间长度,那么第二个 3 表示什么?

  1. 什么叫 Single-step Action Prediction?

  2. 什么叫 Action Chunk?

  3. 如果:

\ B=32,\\quad K=20,\\quad D_a=7 \\

那么 Action Chunk Batch 的 Shape 是什么?

  1. Observation Horizon 和 Prediction Horizon 为什么不是同一个东西?

  2. 为什么"预测 16 个 Action"不一定意味着"一口气执行完 16 个 Action"?

  3. 如果 Episode 只剩 3 个 Action,但模型要求固定预测 10 个 Action,为什么可能需要 Padding?

  4. Padding 后为什么通常还需要 Mask?

  5. 为什么 Robot Data 一旦变成:

\ \[B,T,D \]

这种 Sequence 结构,就会自然把我们带向 Attention / Transformer?


下一课:第 13 课------Transformer 前最后一块真正必要的基础:Sequence、Token 和 Embedding

下一课我们暂时还不会直接写 Q/K/V。

因为如果现在直接写:

\ Q=XW_Q,\\qquad K=XW_K,\\qquad V=XW_V \\

你很可能会知道怎么算,却不知道这里的:

\ X \\

到底是什么。

所以第 13 课只解决这一件事:

Transformer 看到的到底不是"图片""语言""关节状态",而是一串 Token 表示。

我们会把:

\ \[B,T,D \]

进一步解释成:

\ \\boxed{ Batch \\times Token/Sequence \\times Embedding\\ Dimension } \\

并讲清:

  • Sequence 和 Token 到底是什么关系;
  • 一个数字为什么不能直接代表 Token;
  • Embedding 到底是什么;
  • Language Token 怎么变成向量;
  • Robot State 怎么变成 Token;
  • Image 为什么也能变成 Visual Token;
  • 为什么 Transformer 最终可以统一处理语言、视觉、机器人状态;
  • Position Information 为什么必须补进去。

这一步完成以后,第 14 课就可以正式进入:

\ \\boxed{ Q,\\ K,\\ V,\\ Attention } \\

然后再进入完整 Transformer Block。

相关推荐
朝朝辞暮i1 小时前
VLA 系统学习第 8 课:Optimizer 到底在干什么?——从 Learning Rate 到 SGD、Momentum 和 Adam
人工智能·python·深度学习·神经网络·vla
镜子AI1 小时前
教培机构多模态内容跨模态检索系统:基于CLIP的统一表征与图文混合召回算法
人工智能·python·算法·机器学习
belldeep1 小时前
AI-3D 真人剧如何制作
人工智能·3d·ai
落魄大学生之流水线上谋生计1 小时前
Selenium 入门到实战:用 Python 写出稳定的浏览器自动化
python·selenium·自动化
会编程的吕洞宾1 小时前
Spring AI 2.0 会话记忆实战,让 AI 助手记住每一次对话
java·人工智能·spring
浩风祭月1 小时前
ChatGPT Work和Codex为什么共用额度?在哪里看剩余量
人工智能·chatgpt·plus·codex·chatgpt work·chatgpt额度·用量查询
阡陌数智1 小时前
RAG 系统的召回退化:向量库、分块、重排全链路问题排查与优化实践
开发语言·人工智能·语言模型·自然语言处理·推荐算法
嘉立创FPC苗工1 小时前
柔性电路板(FPC)补强:材料选型、工艺与设计全解
人工智能
小宋10211 小时前
Agent 工具升级如何不破坏线上:Tool Schema 版本兼容与契约测试
java·人工智能·后端·spring