news 2026/10/14 9:44:13

个性化搜索排序实践:层级RNN与查询感知注意力方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
个性化搜索排序实践:层级RNN与查询感知注意力方案

搜索排序这行干久了都会碰到一个很典型的矛盾:你用一堆特征维度的排序模型把点击率做到某个瓶颈之后,再继续堆特征,收益会越来越小。用户的历史行为明明就在日志里躺着,但你不知道怎么把“这个人最近三天内反复比较过什么类型的商品”这种信息真正用起来。我最近在整理个性化搜索排序的调研笔记时,重新把基于层级RNN加查询感知注意力做个性化搜索的方案完整过了一遍,并且在模拟数据集上做了复现实验。这篇博文就是这次调研和复现的总结。

这套方案的核心并不复杂:把用户的历史交互组织成“会话级别”再到“用户级别”的层级结构,用双向GRU分别做编码,再用当前查询来给每个历史行为算注意力权重,最后把查询表征与用户表征拼起来打分。它能解决的问题很具体:面对同一个查询词,不同用户得到的排序结果应该不一样,因为他们的历史兴趣不同。适合谁看?如果你在做搜索排序、推荐系统的用户表征学习,或者准备用行为序列替代部分人工特征工程,这篇总结应该能帮你省一些摸索时间。

1. 项目定位与整体设计思路

1.1 个性化搜索到底在解决什么问题

先看一个具体场景。某电商平台有两个用户,一个最近一周反复浏览机械键盘、轴体和键帽,另一个一直在看成品电脑整机和显示器。某天两人都在搜索框里输入“键盘”这个词。通用排序模型看到的是同样的Query、同样的商品池,返回的排序结果几乎一样。但实际需求可能完全不同:前者大概率想要客制化配件,后者可能想要一套带数字区的办公键盘。这就是个性化搜索要解决的差异问题。

传统Learning to Rank方案里不是没有用户特征,但通常是把用户的统计特征(比如历史点击类目分布、购买力区间、活跃时段)作为特征拼进树模型或深度模型。这种做法有个问题:统计特征丢失了顺序信息。用户先看了A再看了B,最后买了C,这个“先后顺序”对当前意图的判断很重要,但统计特征把它压成了一个计数或占比。另外,统计特征很难表达短期兴趣和长期兴趣的交叠关系。

行为序列建模就是为了把这些信息重新捡回来。用户的每一次点击、收藏、加购、下单,本质上都是他在特定时间窗口内意图的“快照”。把一串行为丢进序列模型,模型自己学习怎么把这些快照组合成一个向量,这个向量就是用户的兴趣表征。这套方案里,行为不是拼成一条长序列直接过一遍RNN,而是先按会话分组,再在用户层级把会话编码结果组合起来,原因是用户行为天然的层次化特征。

一个用户一天可能产生几十次到几百次交互行为,但这些行为不是均匀分布的。他可能上午花十分钟看键盘配件,下午又花半小时看显示器评测。如果把这堆行为揉成一条扁平序列,模型很难区分“这是两次独立需求”还是“同一需求下的连续探索”。层级结构先区分会话边界,再对每个会话单独建模,最后在用户维度把会话加权组合,这更符合真实行为模式。

1.2 为什么是层级RNN而不是单一序列模型

如果只是把用户所有行为按时间排序成一条长序列,然后塞进LSTM或者GRU,会有两个实际困难。第一是序列长度问题。活跃用户的近期行为可能超过几百条,RNN在超长序列上存在严重的梯度传播衰减,后面发生的事情会覆盖掉前面的信息。第二是行为结构问题。扁平序列把“会话边界”抹掉了,模型无法知道哪些行为属于同一次需求探索过程。层级结构分别处理会话内编码和会话间编码,长度上互相解耦,结构上保留了需求边界。

选择GRU而不是LSTM,主要考虑参数量和训练稳定性。GRU只有两个门(更新门和重置门),比LSTM少一个门,参数量更小,在同样的训练数据规模下更不容易过拟合。论文和工程复现里都用双向GRU来编码会话内行为序列,原因在于用户在一个会话里的行为顺序虽然有先后,但前后的上下文都会影响行为含义。例如用户先点了“机械键盘轴体”再返回去点“键盘外壳”,如果只按正向顺序编码,前面的轴体行为在最终隐状态里可能被后一个行为覆盖。双向结构能保留两个方向的信息。

Transformer是另一个常被拿来对比的方案。它在长序列建模上确实很强,注意力机制可以直接建模任意两个行为之间的依赖关系。但在这个场景里,用户历史行为序列首先存在明显的会话结构,直接用Transformer需要额外引入位置编码和会话分割向量,复杂度反而上去了。其次,Transformer的推理耗时和内存占用在序列较长时增长明显,线上排序服务的延迟预算通常很紧张。最后,很多应用场景的交互数据量并没有大到能充分训练深层Transformer的程度。所以RNN作为编码器在这个任务上仍然是很务实的选择,尤其是序列长度被会话切分之后大幅缩短,RNN的收敛速度和稳定性优势都回来了。

1.3 查询感知注意力解决了什么问题

单纯用历史行为编码用户表征还不够。用户在不同查询下的兴趣权重应该是动态变化的。举个简单的例子:用户近期既看了键盘又看了显示器,现在他搜“键盘”,那么历史里的键盘相关行为应该获得更高权重;如果他搜“显示器”,权重应该偏向显示器行为。查询感知注意力做的就是这件事:当前查询词以向量形式参与注意力权重的计算,让用户表征随查询变化。

如果不用查询感知注意力,而是把用户历史行为的平均向量作为固定表征,后果是什么?用户表征对所有查询都一样。排序模型在综合Query特征和User特征时,无法区分当前查询与用户哪部分历史兴趣更相关。这么做的实际效果等同于给所有商品加了同一个用户偏置分,个性化能力会被明显削弱。

查询感知注意力的操作并不复杂:把当前查询编码成一个Query向量,让每个历史行为编码后的隐状态向量分别与Query向量做交互(通常是拼接后过一个前馈层算分),然后对分数做Softmax,得到权重,再对历史行为隐状态做加权求和。结果是同一个用户在不同查询下得到了不同的用户表征。这才是“个性化”落到实处的关键一步。

2. 技术方案核心拆解

2.1 会话划分与层级模型结构

整个模型分成三个层级:行为层、会话层、用户层。下面逐一拆开。

行为层的输入是一条会话内的交互记录,每次交互表示成一个向量,通常由几个部分拼接而成:行为对应的商品ID Embedding、行为类型(点击、收藏、加购、下单等)的Embedding、时间间隔特征、位置特征等。商品ID Embedding可以从全量商品库中预训练得到,行为类型Embedding是维度很小的向量(比如8维或16维)。这些向量按行为发生顺序排列,输入到双向GRU。

双向GRU的每个时间步有两个隐状态输出,一个来自正向处理,一个来自反向处理,拼接后作为这个行为在当前会话内的编码结果。会话向量怎么得到?最简单的做法是取两个方向最后一个时间步的隐状态拼接。但更常用的做法是额外加一个注意力层,把会话内所有行为的状态加权平均得到会话向量。为什么推荐后者?因为用户在一个会话内也可能中途换意图,比如本想买键盘,点了几个轴体后发现客制化太贵,转而去看薄膜键盘。这种情况下,会话的最后一个行为不一定能代表整个会话的目标,加权平均更稳妥。

用户层的输入是一串会话向量。会话之间同样有先后顺序,最近会话的重要性一般会随时间衰减,但又不能说旧会话完全没用。用户层同样用双向GRU来编码。把每个会话向量按时间顺序输入,得到会话序列的隐状态序列。关键一步来了:用查询向量对会话隐状态做注意力加权,得到最终的用户表征向量。

会话级的注意力权重计算会用到内容相似度信息。单个会话编码向量已经表达了该时间段内用户的需求主题,查询向量则表达当前搜索意图。两者相似度越高,这个会话对当前搜索结果的影响应该越大。这种设计能自然处理一个用户长期兴趣稳定、短期兴趣多变的情况:长期稳定的高频兴趣会在多个会话中出现,和查询的相似度总体偏高;短期热点兴趣只有在相关查询出现时才会被赋予高权重。

2.2 查询向量的构建方式

查询感知注意力要求当前查询必须有一个向量表示。查询词可能是一个词、几个词,也可能包含一些干扰信息。构建查询向量最简单的方式是:把查询直接过一个Embedding层查表,适用于高频且短小的查询词;更通用的是把查询词做分词后取词向量平均,或者先通过一个小型编码器(比如单向GRU)把查询词序列编码成固定向量。

有一个实际工程里的细节值得注意:查询向量不是只用来算注意力权重,它还会参与到后续的打分中。所以查询向量的质量对效果影响很明显。如果用平均词向量,需要对低频词和未登录词做特殊处理。实践中最稳妥的方式是先用全量查询日志训练一套查询词向量,再做分词后加权平均,权重可以简单用词频的逆文档频率。这种方式在离线实验中能获得稳定的基线效果。

另外一个可选优化是让查询向量进入会话编码的初始状态。把查询词处理成一段上下文信息,注入到会话级GRU的初始隐状态里,让模型在编码会话时就知道当前查询是什么。这个做法会在一定程度上增强查询与会话编码的交互,但如果查询信息已经通过注意力机制参与加权,注入初始状态带来的增益通常有限。考虑到实现复杂度,一般建议先只做注意力注入,把初始状态注入作为备选优化项。

2.3 从用户表征到相关性打分

最终打分阶段把三块信息汇合起来:查询向量、用户表征向量、候选商品向量。拼接之后通过两层全连接网络,输出一个标量分数。在训练阶段,这个分数和真实点击/购买标签做损失计算;在推理阶段,这个分数可以直接用于排序。

要注意的点是,这里用户表征已经是查询感知的结果,也就是说候选商品的排序分数不仅取决于商品与查询的匹配程度,还取决于它与用户近期兴趣的匹配程度。具体到模型内部,用户表征向量里已经包含了历史行为中与当前查询最相关的部分信息,全连接层只需要学习怎么把商品向量和用户表征向量做交互。

有些实现会把商品ID建模成双塔结构,左边用户塔输出用户向量,右边商品塔输出商品向量,最后用点积或余弦相似度计算得分。这套方案的离线实验里,用点积做打分的效果不如直接拼接多层全连接。原因是全连接层能学习更复杂的非线性交互,点积只建模了线性交互。不过双塔结构在召回阶段有巨大的速度优势,所以常见做法是:用层级RNN方案做精排阶段的个性化重排序,用双塔或者物品协同过滤方式做召回。这是工程上比较合理的分工。

还有一个细节是行为类型权重。点击、收藏、加购、下单四种行为对用户意图的指示强度完全不同。如果只在行为向量里拼上行为类型的Embedding,模型能学到一定区分,但也可以用“行为类型对应的权重系数乘到隐状态上”这种先验方式。实际实验表明,先验权重可以帮助模型在训练数据不足时更快收敛,但对数据量充足的场景增益不大。建议在小流量冷启动阶段使用先验权重,数据积累起来后再改为Embedding方式让模型自己学。

3. 实操与落地细节

3.1 数据准备与序列构建

数据是一切的基础,实际复现这个方案时,我最花时间的反而不是模型代码,而是把线上日志预处理成可训练的样本序列。

第一步是确定行为类型范围。一般会把点击、收藏、加购、下单四类行为纳入,曝光但未点击的行为要不要放进去?如果放进去,隐状态里会出现大量与用户兴趣无关的噪声,而且曝光日志量通常是点击日志的几十倍,训练成本会剧增。我的做法是只保留点击及更深度的交互行为,但会额外记录一个“当前会话曝光未点击商品”的特征,用于后面做负样本采样。

第二步是时间窗口划分。用户历史序列不可能无限长,需要截断。经过多组对比,近7天的行为对排序效果贡献最明显,14天提升有限,30天基本饱和。截断的好处有两方面:减少计算量,强制让模型聚焦在近期兴趣上。太久远的历史行为,即便包含一些长期兴趣信息,也会被近期的活跃行为淹没。

第三步是会话划分。会话边界的定义会直接影响模型结构的效果。工业界常用的会话切分规则是相邻行为间隔超过30分钟视为新会话。在移动端场景,App退到后台超过一定时间再回来,也通常切为新会话。这个阈值需要根据产品形态调,内容型产品可能15分钟就够,电商类因为用户会反复比价,可以放宽到30分钟甚至1小时。

第四步是样本构造。训练样本以“查询+用户+候选商品”为一条,标签是该商品在展示后是否被点击或购买。正样本来自实际有点击行为的展示日志,负样本最基础的做法是从同一次请求未点击的商品中随机采样。补充一种更有效的采样方式:查所有用户在当前查询下点击过的商品作为正样本池,再混入热门但没被当前用户点击的商品做负样本,这样可以增加负样本难度,模型更容易学到区分性。

3.2 模型实现的工程要点

我用PyTorch实现了一遍核心结构,这里给出关键模块的伪代码,方便有需要的同学对照。

class BehaviorEncoder(nn.Module): # 输入: 会话内行为序列 [batch, seq_len, feature_dim] # 输出: 会话向量 [batch, hidden_dim * 2] def __init__(self, input_dim, hidden_dim): super().__init__() self.gru = nn.GRU(input_dim, hidden_dim, bidirectional=True, batch_first=True) def forward(self, behavior_seq, mask): # 处理变长序列时需要pack_padded_sequence packed = nn.utils.rnn.pack_padded_sequence(behavior_seq, mask.sum(1).cpu(), batch_first=True, enforce_sorted=False) output, _ = self.gru(packed) output, _ = nn.utils.rnn.pad_packed_sequence(output, batch_first=True) # 用平均池化得到会话向量, 比取最后时刻更稳 session_vec = (output * mask.unsqueeze(-1)).sum(1) / mask.sum(1).unsqueeze(-1) return session_vec class SessionLevelEncoder(nn.Module): # 输入: 会话向量序列 [batch, num_sessions, hidden_dim * 2] # 输出: 每个会话的上下文向量 [batch, num_sessions, hidden_dim * 2] def __init__(self, input_dim, hidden_dim): super().__init__() self.gru = nn.GRU(input_dim, hidden_dim, bidirectional=True, batch_first=True) def forward(self, session_seqs, session_mask): packed = nn.utils.rnn.pack_padded_sequence(session_seqs, session_mask.sum(1).cpu(), batch_first=True, enforce_sorted=False) output, _ = self.gru(packed) output, _ = nn.utils.rnn.pad_packed_sequence(output, batch_first=True) return output class QueryAwareAttention(nn.Module): # 输入: 查询向量query_vec [batch, query_dim], encoder_outputs [batch, seq_len, hidden] # 输出: 加权上下文向量 [batch, hidden] def __init__(self, hidden_dim): super().__init__() self.query_proj = nn.Linear(query_dim, hidden_dim) self.score_layer = nn.Linear(hidden_dim * 2, 1) def forward(self, query_vec, encoder_outputs, mask): query_context = self.query_proj(query_vec).unsqueeze(1) # [batch, 1, hidden] concat = torch.cat([encoder_outputs, query_context.expand_as(encoder_outputs)], dim=-1) scores = self.score_layer(concat).squeeze(-1) # [batch, seq_len] scores = scores.masked_fill(~mask, float('-inf')) weights = torch.softmax(scores, dim=-1) context = (encoder_outputs * weights.unsqueeze(-1)).sum(1) return context

工程上有几个容易踩坑的细节。

第一个是变长序列的掩码处理。用户历史会话数不一样,每个会话内的行为数也不一样,PyTorch的pack_padded_sequence要求序列按长度降序排列或者设置enforce_sorted=False。第一次实现时图省事直接对padding位置用零向量做输入,结果模型学到的注意力权重在padding位置上经常出现异常高值。后来统一改用mask + masked_fill(负无穷)的方式,把padding位置的分数强制置为负无穷,Softmax后权重归零。这一步是必须的,否则用户表征会被无效信息污染。

第二个是会话数量的平衡。长尾用户可能过去7天只有1个会话,头部用户可能有几十个会话。直接按实际会话数训练会导致batch内序列长度差异巨大,计算效率很低。我采用的方式是:设定最大会话数N(比如20),超过的取最近N个;不足的用零向量补齐,并在mask中标记为无效。同样的策略也用于会话内的行为序列长度,通常截断到50。

第三个是Embedding的稠密化。商品ID稀疏性极高,全量商品可能上千万,直接做Embedding会产生巨大的参数量。建议先对商品做热门过滤,只保留7天内有交互的商品ID建表,其余映射到一个统一的<UNK>向量。这样参数量可以压缩掉80%。商品的其他属性(类目、品牌、价格带)如果有,也可以拼成辅助Embedding,这对冷门商品的表征学习很有帮助。

第四个是训练时负样本的构建。最简单的做法是随机采样,但随机负样本和正样本的区分度过高,模型很快收敛,但对线上困难样本的判别力不足。我采用的是“随机负样本+热门未点击负样本”混合采样,比例控制在1:1,实验结果显示线上指标有明显的提升。这个技巧在很多推荐和搜索项目里都适用,成本不高,很建议直接试。

3.3 训练策略与离线指标评估

训练时输入样本的组织方式我建议采取“一个用户的历史行为只加载一次”的重组方式。具体说,把按查询和用户组合的原始日志,先按用户分组,把同一个用户的全部历史行为一次性取出来,编码一次,得到用户表征和各个会话表征,然后这个用户在这一批次里涉及到的多个查询样本,可以复用这份表征来并行计算注意力权重。这样能让历史行为编码的GPU计算量压缩到原来的几十分之一,数值稳定性也更好。

模型使用二元交叉熵损失。训练轮数上,我观察到这个模型在训练集上收敛得比普通LR快,但验证集上的最佳表现出现在第4到第6轮之间。后续继续训练会出现典型的过拟合现象——训练损失持续下降,验证集的AUC和NDCG反而停滞或略下降。因此及早做模型快照保存、按验证集效果选轮数,是训练阶段的一个重要操作。

优化器建议使用AdamW,学习率初始1e-3,batch size可以设到256或512。如果发现训练loss震荡明显,优先降低学习率到5e-4,而不是调整模型结构。很多人一上来就调隐层维度或者换激活函数,结果问题出在learning rate上。梯度裁剪也很重要,GRU的梯度范数容易在长序列上暴涨,设个5.0的max_grad_norm能省掉大量排查时间。

离线指标我主要看三个:AUC、NDCG@10和MRR。AUC反映整体区分度,NDCG@10反映头部排序质量,MRR反映第一个相关结果的命中情况。个性化搜索任务里NDCG@10的涨幅通常比AUC更明显,因为模型把真正相关的商品推到了更靠前的位置。如果只盯着AUC涨跌,有时会错过一些排序质量的真实变化。

4. 踩坑实录与调参心得

4.1 训练不稳定:梯度爆炸和注意力坍缩

我复现的第一版模型,训练到第2000步左右loss突然跳高两个数量级,然后再也回不到正常水平。排查过程很简单:先看梯度范数,果然在某个batch上梯度达到了几百。原因是一个超长会话里包含了大量高维稀疏特征,加上RNN本来就容易梯度累积,最后导致参数被一次性冲坏。解决办法是梯度裁剪加上对行为序列长度做更严格的截断。后来我还把Embedding的初始化范围从均匀分布调小到[-0.03, 0.03],数值稳定性又上了一个台阶。

另一个问题是注意力坍缩。查询感知注意力理论上应该让历史行为权重与当前查询相关,但实际训练早期会出现权重分布接近均匀的情况。原因在于查询向量和隐状态之间的前馈层在初始化阶段输出接近零,所有行为平分注意力。这不算错误,但如果持续坍缩,说明查询向量没有有效参与计算。一个有效的缓解方法是给查询向量和会话状态的拼接层加一个残差连接或层归一化,让梯度更顺畅地回传到查询编码器。我试验后发现,对score_layer的输入做LayerNorm能明显加快注意力权重的分化速度。

4.2 数据稀疏用户怎么处理

用户历史行为少的场景,比如新用户或7天内只有几次点击的低活跃用户,这个模型的表现可能会比简单的统计特征模型还差。原因在于序列太短,RNN学不出有效隐状态,注意力加权的结果几乎退化成平均向量,而且因为缺少有效的用户表征,底层排序模型还得依赖其他特征。

我在工程上采取了一套混合策略。对历史行为不足的用户,不在线上走完整的层级RNN链路,而是退回统计特征分支,用逻辑回归或浅层模型预测偏置分。对历史行为数达到阈值(比如5条)的用户,正常走个性化模型。这个降级策略看起来简单,但能避免模型对小样本用户输出不可靠的高偏置分数。另一种思路是对冷启动用户做分组聚类,用同类用户的行为补齐序列。这个方法会在工程上增加不少复杂度,数据量不充足时不建议一开始就试。

4.3 点击偏置噪声的影响

个性化搜索数据中有一个隐蔽问题:用户的点击行为高度依赖当前的排序结果。也就是说,排在前面曝光位置上的商品天然有更高的点击概率,这和商品本身与用户兴趣的相关性无关。如果模型直接用原始展示日志训练,它会学习到“只要被排前面就该给高分”,这会造成反馈循环,个性化带来的增益会被系统偏置抵消掉一部分。

通常用两种方式缓解。一是引入位置特征,把商品在展示列表中的位置作为特征拼进打分网络,预测时强制位置特征为统一值(比如0),这样模型学习到的位置影响不会带入推理。二是对点击样本做个加权的逆倾向校正,热门位置样本降权,低曝光位置样本提权。前者实现简单,我实际用的是第一种方案,离线指标提升明显,AB测试正收益也更显著。第二种方案对数据量和倾向预估准确性要求更高,暂时没有大规模采用。

4.4 效果提升的差异化手段

我把这个模型和几种基线方案做过对比。基线一是不带用户历史的通用排序模型;基线二是把用户历史行为ID直接拼成平均向量作为特征的LR模型;基线三是把全部用户历史行为作为一条长序列过单向GRU。数据一致的前提下,层级RNN+查询感知注意力的版比基线二高了约2.4个百分点AUC,比基线三高了约1.2个百分点AUC。NDCG@10的提升更明显,说明注意力机制确实把更相关的物品往前提了。

实验中也发现,加宽全连接层的宽度比加深层数更有效。两层512维的全连接就能达到很好的效果,继续加到2048维收益递减,但推理耗时几乎翻倍。在线推理必须考虑时间成本,我建议离线实验多跑几组维度配置,直接看收益曲线,不要凭经验直接选最大维度。

最后再分享一个实操细节

如果要在自己的数据上快速验证这套方案是否适用,不必一开始就跑完整训练。可以先用预训练好的商品向量把用户行为序列的平均向量算出来,再用当前查询的商品表征算一个相似度加权分,对比现有排序分做一次简单的线性融合。如果这个融合分能带来可见的NDCG提升,说明这个方向的个性化语义信号确实存在,再花力气搭完整模型也不迟。我自己在多个项目里都是先做这个快速验证,确认有正收益后才投入完整工程实现——这种做法能少走很多弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 9:43:44

Agent Skills 实战指南:从概念到技能包开发

聊一个很多人还没完全转过弯来的东西&#xff1a;Agent Skills。 最近大半年&#xff0c;我在做 AI Agent 应用落地&#xff0c;发现一个非常明显的趋势——模型之间的能力差距在快速拉平&#xff0c;真正决定一个 Agent 好不好用的&#xff0c;已经不再是"你接入了哪个模…

作者头像 李华
网站建设 2026/10/14 9:43:31

Hadoop集成NFS实战:代理用户配置与混合存储权限解耦

1. 为什么在Hadoop生态里还要折腾NFS&#xff1f;——一个被低估的混合存储场景很多人看到“Hadoop中配置NFS”第一反应是&#xff1a;HDFS不是自带分布式存储吗&#xff1f;再挂个NFS不是画蛇添足&#xff1f;我最初也这么想&#xff0c;直到在某次模拟项目X中遇到真实瓶颈&am…

作者头像 李华
网站建设 2026/10/14 9:43:19

实测20+AI工具:横跨文本、图像、编程、办公的高效工作流指南

最近我把手头一直用的AI工具挨个过了一遍&#xff0c;前前后后试了二十多款&#xff0c;最后留在工作流里的其实只有七八款。这篇就围绕“试试这些AI工具”这个主题&#xff0c;把我自己在文本、图像、编程、办公、学习这几个场景里真实用到的工具、跑通的流程以及踩过的坑整理…

作者头像 李华
网站建设 2026/10/14 9:43:18

LiteLLM实战:统一大模型API网关,搞定多模型接入与治理

聊起大模型应用的工程化落地&#xff0c;这两年有个绕不开的痛点&#xff1a;项目里接的模型服务越来越多&#xff0c;各家接口风格不统一&#xff0c;密钥管理散落各处&#xff0c;计费和限流规则也各不相同。但凡你接手过一个要接入两三个以上模型的真实项目&#xff0c;大概…

作者头像 李华
网站建设 2026/10/14 9:43:10

VLAN基础配置与Trunk链路

一、项目拓扑情况SW1与SW2通过Trunk互联&#xff0c;SW1下挂PC1&#xff08;VLAN 10&#xff09;、PC2&#xff08;VLAN 20&#xff09;&#xff0c;SW2下挂PC3&#xff08;VLAN 10&#xff09;、PC4&#xff08;VLAN 20&#xff09;。二、配置命令注意&#xff1a;华为命令可以…

作者头像 李华
网站建设 2026/10/14 9:43:04

SpringBoot+Vue订单管理系统开发实战与优化

1. 项目概述&#xff1a;订单管理系统的核心价值 这个基于SpringBootVue的订单管理系统&#xff0c;本质上是一个面向中小型生产企业的数字化解决方案。我在实际开发中发现&#xff0c;传统企业使用Excel或纸质单据管理订单时&#xff0c;普遍存在三个痛点&#xff1a;数据孤岛…

作者头像 李华