1. 从一个面试翻车现场说起前阵子帮朋友复盘一场Java AI岗的面试他简历上写了“熟悉Spring AI做过RAG知识库”结果面试官上来就问了一句“你说说Embedding和LLM输出的向量到底是不是一回事”他当场卡壳憋了半天回了句“都是向量吧应该差不多”然后就没有然后了。这个问题看着像八股其实特别能筛人。因为只要你真正动手搭过RAG、做过语义检索、调过Spring AI的EmbeddingModel和ChatModel你就会知道这两个东西从来源、维度、用途到工程处理方式完全是两条线上的产物。把它们混为一谈代码能跑但一上生产就出问题——检索召回率低、相似度算错、向量库写入报错、成本算不明白全是坑。这篇内容我打算把这件事彻底讲透。核心围绕四个关键词展开Java、AI、Embedding、LLM、Spring AI。我会先说清楚Embedding向量和LLM输出向量的本质区别再往下拆它们在Spring AI里的API长什么样、生产代码怎么写、向量库怎么选、常见故障怎么排查。适合正在做Java AI应用开发的、准备面试的、以及刚接触Spring AI想少踩坑的读者。不管你是刚学完Java基础想往AI方向转还是已经写过几个Demo但没上过生产这篇都能给你一些能直接抄作业的东西。我自己的判断是未来两年Java后端工程师的核心竞争力之一就是能不能把大模型能力稳稳当当地集成进业务系统。而Embedding和LLM的关系是这条路上第一道必须迈过去的坎。2. 先把概念钉死Embedding向量和LLM输出向量到底差在哪2.1 一句话说清两者的本质区别Embedding向量是文本的“语义坐标”LLM输出向量是模型内部状态的“中间产物”。打个比方。Embedding就像给每句话在一个巨大的地图上标一个坐标点语义相近的句子坐标就挨得近。“今天天气不错”和“今天阳光很好”这两个句子在Embedding空间里的距离会非常小哪怕它们没有一个字相同。这个坐标是专门为了“比较相似度”而训练出来的。而LLM输出的向量是模型在生成token过程中最后一层隐藏状态经过投影后得到的logits向量维度等于词表大小比如Qwen是15万Llama 3是12万。它的作用是决定下一个token是什么不是用来算语义相似度的。你拿两个LLM输出向量去算余弦相似度得到的数字基本没有业务意义。注意很多初学者看到float[]就以为是同一种东西这是最大的认知陷阱。向量的“形状”一样不代表“语义”一样。2.2 从训练目标看两者的根本分歧Embedding模型的训练目标是对比学习。典型做法是把一个正样本对语义相近的两句话拉近把负样本对语义无关的两句话推远。损失函数通常是InfoNCE或者Triplet Loss。训练完之后模型输出的向量天然具备“距离即语义”的性质。LLM的训练目标是自回归语言建模也就是给定前文预测下一个token。它的损失函数是交叉熵优化的是“下一个词预测得准不准”。在这个过程中模型确实会产生大量中间向量但这些向量的使命是服务于生成不是服务于比较。这就解释了为什么你不能拿LLM的hidden state当Embedding用。虽然学术界有人尝试过比如用LLM最后一层hidden state做检索但效果普遍不如专门的Embedding模型因为训练目标压根不一样。2.3 维度、归一化、存储成本的现实差异对比项Embedding向量LLM输出向量典型维度384 / 768 / 1024 / 1536词表大小通常10万是否归一化通常L2归一化后存一般不归一化单条存储成本1.5KB~6KBfloat32几百KB到MB级主要用途语义检索、聚类、去重生成下一个token能否直接比相似度能且是设计目的不能无业务意义生产是否持久化必须存向量库一般不存用完即弃这张表建议你直接记住。面试的时候如果能把这几个维度说清楚面试官基本就知道你是真做过项目的。我见过一个真实的翻车案例有个团队为了“省事”直接把LLM最后一层hidden state存进Milvus当检索向量结果维度对不上Milvus collection建的是1536维hidden state是4096维写入直接报schema mismatch。改完之后又发现检索效果极差因为hidden state根本没经过对比学习训练。这就是没搞清楚两者区别的代价。3. Spring AI里的两条API线EmbeddingModel和ChatModel3.1 EmbeddingModel的接口设计与调用方式Spring AI把Embedding能力抽象成了EmbeddingModel接口核心方法就两个public interface EmbeddingModel extends ModelEmbeddingRequest, EmbeddingResponse { EmbeddingResponse call(EmbeddingRequest request); float[] embed(String text); Listfloat[] embed(ListString texts); EmbeddingResponse embedForResponse(ListString texts); int dimensions(); }最常用的是embed(String text)和embed(ListString texts)。前者返回单条文本的向量后者批量返回。批量接口在生产里非常重要因为逐条调用会产生大量网络往返吞吐量上不去。以OpenAI的Embedding为例Spring AI的配置大概是这样spring: ai: openai: api-key: ${OPENAI_API_KEY} embedding: options: model: text-embedding-3-small dimensions: 1536text-embedding-3-small默认1536维也支持通过dimensions参数降维到512或256降维后存储成本能降不少但召回率会略有下降。这个取舍后面会细说。3.2 ChatModel的输出结构为什么它不给你“一个向量”ChatModel的接口是这样的public interface ChatModel extends ModelPrompt, ChatResponse, StreamingChatModel { ChatResponse call(Prompt prompt); FluxChatResponse stream(Prompt prompt); }注意它返回的是ChatResponse里面是Generation列表每个Generation包含的是文本内容和元数据token使用量、finish reason等而不是一个向量。这是Spring AI有意为之的设计——它把LLM的“生成能力”和“向量能力”彻底分开了。那LLM的向量去哪了在底层调用HTTP API的时候如果你显式请求logprobs或者某些模型的embedding endpoint你才能拿到token级别的向量信息。但Spring AI的ChatModel抽象层默认不暴露这些因为99%的业务场景不需要。提示如果你真的需要LLM的token embedding比如做token级相似度分析得绕过Spring AI的高层抽象直接用WebClient调底层API。但这属于非常小众的需求生产里极少见。3.3 两条线在RAG流程里怎么配合RAG的典型流程是这样的离线阶段文档切块 → 用EmbeddingModel把每个chunk转成向量 → 存入向量库在线阶段用户query → 用EmbeddingModel转成向量 → 向量库检索Top-K → 把检索到的chunk拼进Prompt → 交给ChatModel生成回答你看EmbeddingModel负责“找”ChatModel负责“说”。两者各司其职中间通过向量库和Prompt模板连接。这就是为什么面试官爱问这个问题——它直接考察你有没有真正理解RAG的架构。我见过有人把这两个角色搞混试图用ChatModel去做检索或者用EmbeddingModel去生成回答结果当然是跑不通。理解这条分工线是写好RAG代码的前提。4. 生产级代码实战从文档入库到语义检索4.1 项目依赖与基础配置先上Maven依赖。Spring AI的版本迭代很快我这里用的是1.0.0 GA之后的稳定版本配合Spring Boot 3.3dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version1.0.0/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-pgvector-store-spring-boot-starter/artifactId version1.0.0/version /dependency向量库我选PgVector理由是团队已经在用PostgreSQL不用额外运维一套Milvus数据量在百万级以下时PgVector的IVFFlat或HNSW索引性能完全够用而且事务、备份、监控这些基础设施都是现成的。配置文件spring: ai: openai: api-key: ${OPENAI_API_KEY} embedding: options: model: text-embedding-3-small dimensions: 1536 chat: options: model: gpt-4o-mini temperature: 0.2 datasource: url: jdbc:postgresql://localhost:5432/ragdb username: rag password: ${DB_PASSWORD}temperature设成0.2是因为RAG场景要的是稳定、忠于原文的回答不需要太多创造性。4.2 文档切块别小看这一步文档切块Chunking是RAG里最容易被低估的环节。切得太粗检索精度差切得太细上下文丢失。我的经验值是中文文档每块300~500字重叠50~80字英文文档每块500~800 token重叠100~150 token代码文档按函数或类切不要按行数硬切Spring AI提供了TokenTextSplitterTokenTextSplitter splitter new TokenTextSplitter(500, 100, 5, 10000, true); ListDocument chunks splitter.apply(documents);参数含义依次是目标chunk大小、重叠大小、最小chunk字符数、最大chunk字符数、是否保留分隔符。最后那个true很重要它会让切分尽量在句子边界发生而不是把一句话拦腰截断。实操心得切块参数没有万能值一定要拿你的真实文档做召回测试。我一般会准备20个典型query人工标注哪些chunk应该被召回然后调参数看召回率。这个过程花半天能省掉后面几周的调优。4.3 向量入库批量写入与索引优化入库代码用Spring AI的VectorStore抽象Autowired private VectorStore vectorStore; public void ingest(ListDocument documents) { ListDocument chunks splitter.apply(documents); // 批量写入Spring AI内部会分批调用Embedding API vectorStore.add(chunks); }这里有个关键点vectorStore.add()内部会自动调用EmbeddingModel把文本转成向量然后写入PgVector。你不需要手动调embed()。但如果你想控制批量大小可以自己分批Lists.partition(chunks, 100).forEach(batch - vectorStore.add(batch));每批100条是个比较稳的值。太大容易触发API的rate limit太小则网络开销占比高。PgVector的索引创建SQLCREATE INDEX ON vector_store USING hnsw (embedding vector_cosine_ops) WITH (m 16, ef_construction 64);m16和ef_construction64是HNSW的经典参数。m越大索引越精确但内存占用越高ef_construction越大构建越慢但查询质量越好。百万级数据用这组参数基本够用。4.4 检索与Prompt组装把向量用起来检索代码public String ragQuery(String question) { SearchRequest request SearchRequest.builder() .query(question) .topK(5) .similarityThreshold(0.7) .build(); ListDocument docs vectorStore.similaritySearch(request); String context docs.stream() .map(Document::getText) .collect(Collectors.joining(\n\n---\n\n)); String prompt 你是一个严谨的助手。请仅根据以下上下文回答问题。 如果上下文中没有相关信息请直接说我不知道不要编造。 上下文 %s 问题%s .formatted(context, question); return chatModel.call(prompt); }similarityThreshold(0.7)是个经验值。低于这个阈值的chunk即使被召回也大概率是噪声。但阈值设太高会导致召回不足需要根据你的Embedding模型和业务数据调。text-embedding-3-small的余弦相似度分布比较集中0.7是个不错的起点。注意Prompt里一定要加“仅根据上下文回答”和“不知道就说不知道”这两句。不加的话LLM会用它的预训练知识胡编RAG就白做了。5. 向量库选型与Embedding模型选型的取舍5.1 向量库PgVector、Milvus、Redis怎么选向量库适用场景优势劣势PgVector百万级以下已有PG运维简单事务支持超大规模性能下降Milvus千万级以上专为向量设计性能强运维复杂组件多Redis Stack中小规模低延迟内存级速度部署简单成本高持久化弱Elasticsearch已有ES混合检索支持BM25向量混合向量性能一般我的建议是如果你团队没有专职的向量库运维优先PgVector。它的性能在百万级数据下完全够用而且你能用SQL直接查数据、做join、做聚合调试起来非常方便。等数据量真的上千万了再考虑迁移Milvus那时候你的业务也已经跑通了。5.2 Embedding模型开源还是闭源2024年之后Embedding模型的选择多了很多。几个主流选项OpenAI text-embedding-3-small/large闭源效果好按token收费1536/3072维BGE-M3开源中文效果好支持多语言1024维GTE-Qwen2开源中文榜单靠前维度可选Cohere embed-v3闭源多语言强支持压缩维度选型的核心考量是你的数据是否敏感、你的预算是多少、你的中文占比有多高。如果数据敏感不能出内网那就只能选开源模型自己部署。BGE-M3是目前中文场景下比较稳的选择用Ollama或者vLLM都能跑。如果预算充足且数据不敏感OpenAI的text-embedding-3-small性价比很高1536维每百万token只要几分钱。实操心得换Embedding模型意味着所有存量向量都要重新生成。因为不同模型的向量空间完全不兼容你没法拿A模型生成的向量去和B模型生成的query向量算相似度。所以选型要慎重最好在项目初期就定下来。如果非要换得做全量重刷百万级数据大概要跑几个小时。5.3 维度压缩省钱还是省性能OpenAI的text-embedding-3系列支持通过dimensions参数降维。比如1536维降到512维存储成本降三分之二检索速度也更快。但代价是召回率下降。我做过一组对比测试在同一个中文问答数据集上维度召回率5单条存储检索延迟153692%6KB45ms102490%4KB32ms51284%2KB20ms25676%1KB14ms可以看到从1536降到1024召回率只掉2个点但存储和延迟都明显改善。降到512就开始有明显损失了。所以我的建议是如果存储成本敏感降到1024是划算的512以下要谨慎除非你的业务对精度要求不高。6. 常见问题与排查技巧实录6.1 检索结果不相关从五个方向排查这是RAG最常见的问题。按我的经验排查顺序应该是切块是否合理把召回的chunk打印出来看是不是被切得七零八落。如果是调TokenTextSplitter的参数。Embedding模型是否匹配语言用英文模型处理中文效果会很差。确认模型的语言支持。相似度阈值是否过高阈值0.8以上可能把相关chunk也过滤掉了。降到0.6试试。Top-K是否太小Top-3可能不够试试Top-10然后让LLM自己筛选。query是否需要改写用户的口语化query和文档的书面语差距大时可以先让LLM改写query再检索。6.2 向量维度不匹配最常见的报错报错信息通常长这样org.springframework.ai.vectorstore.VectorStoreException: Dimension mismatch: expected 1536, got 1024原因无非两个要么你换了Embedding模型但没重建collection要么配置文件里的dimensions和实际模型输出不一致。解决办法是删掉collection重建然后全量重刷数据。注意PgVector的维度是在建表时固定的改不了。换维度必须drop table重建。所以生产环境换模型是个大工程要提前规划。6.3 API限流与重试生产必备的容错Embedding API调用量大时很容易触发rate limit。Spring AI内置了重试机制但默认配置比较保守。建议显式配置spring: ai: retry: max-attempts: 5 backoff: initial-interval: 1000 multiplier: 2 max-interval: 10000这是指数退避第一次等1秒第二次2秒第三次4秒以此类推。配合批量写入基本能扛住大部分限流场景。如果数据量特别大建议在入库任务里加个队列把Embedding调用和写入解耦避免一个批次失败导致整个任务中断。6.4 成本失控三个容易忽略的坑第一个坑是重复Embedding。同一段文本被反复embed钱就白花了。解决办法是在入库前做去重用文本的hash做key。第二个坑是query侧没缓存。热门query的Embedding结果可以缓存起来用Caffeine或者Redis都行。命中率高的场景能省不少钱。第三个坑是chunk过大。chunk越大token越多Embedding成本越高。500字的chunk和1000字的chunk成本差一倍但召回效果未必差一倍。所以切块要精细。6.5 常见问题速查表现象可能原因排查动作检索结果完全不相关模型语言不匹配换中文Embedding模型报维度不匹配换模型未重建索引drop重建全量重刷写入超时批量太大或限流减小batch加重试召回率突然下降数据分布变化重新评估切块参数成本异常升高重复embed或chunk过大去重调小chunkLLM回答编造Prompt未约束加仅根据上下文指令7. 面试怎么答这个问题才能加分回到开头那个面试场景。如果面试官问你“Embedding和LLM输出向量啥关系”一个加分的回答结构是这样的先定性两者都是向量但来源和用途完全不同。Embedding向量是文本的语义表示专门为相似度比较训练LLM输出向量是生成过程的中间产物维度等于词表大小用于预测下一个token。再讲训练目标Embedding用对比学习LLM用自回归语言建模。训练目标不同导致向量的性质不同。然后落到工程在RAG里Embedding负责检索LLM负责生成两者通过向量库和Prompt连接。生产里Embedding向量要持久化LLM输出向量用完即弃。最后补一句实践我做过一个项目用Spring AI PgVector搭RAGEmbedding用text-embedding-3-smallLLM用gpt-4o-mini百万级文档检索延迟在50ms以内。这样面试官就知道你是真做过。这个回答的逻辑是概念 → 原理 → 工程 → 实践层层递进。比单纯背八股强得多。8. 一些踩坑之后的个人体会做Java AI应用这一年多我最大的体会是大模型集成不难难的是把工程细节做扎实。Embedding和LLM的区别本质上是个概念问题但概念不清会导致一连串工程错误——向量存错、相似度算错、成本失控、检索不准。另一个体会是Spring AI的抽象层设计得不错但它掩盖了很多底层细节。如果你只会在ChatClient上调call()遇到问题就抓瞎。所以我的建议是用Spring AI的同时花点时间看看它底层调的HTTP API长什么样理解EmbeddingRequest和ChatRequest的实际payload。这样排查问题时心里有底。最后分享一个小技巧在开发阶段把每次Embedding调用的token数和每次Chat调用的token数都打日志。跑一周之后你会对成本结构有非常清晰的认识优化起来也有方向。这个习惯帮我省了不少钱也让我在跟老板汇报时能拿出具体数字。向量库的选型和Embedding模型的选型没有标准答案只有适合你当前业务阶段的答案。先跑通再优化别一上来就追求完美架构。
企业数字化 ERP 产品动态
相关推荐
Embedding与LLM输出向量的本质区别及RAG实战 1. 面试官为什么总爱问 Embedding 和 LLM 输出的关系1.1 这道题到底在考什么面 Java AI 岗位的时候,很多人一听到“Embedding 和 LLM 输出向量到底啥关系”就懵了。有人觉得这俩是一回事,都是“把东西变成一串数字”;有人觉得 Embedding 是 E… · 2026/11/3 8:44:30
UE5 JSON解析与序列化:FJsonObject与FJsonValue详解 1. UE5 JSON解析核心类解析在Unreal Engine 5的C开发中,处理JSON数据是常见的需求。UE5提供了一套完整的JSON处理类库,其中最核心的就是FJsonObject和FJsonValue这两个类。理解它们的结构和关系,对于高效处理JSON数据至关重要。1.1 FJsonObje… · 2026/11/3 8:43:30
Word文档跨平台排版差异解析与解决方案 1. 为什么同一个Word文件在不同编辑器里排版会不一样? 这个问题困扰着无数办公人士,尤其是那些需要跨平台协作的团队。作为一名经历过无数次文档格式灾难的老编辑,我清楚地记得上周五下午5点半,当我把精心排版的报告发给客户后&am… · 2026/11/3 8:43:30
美业小店数字化转型:轻量化解决方案与应用实践 1. 美业小店数字化转型的必然趋势 2026年的美业市场已经发生了翻天覆地的变化。作为一名在美发行业摸爬滚打8年的从业者,我亲眼见证了从"流量为王"到"效率制胜"的转变。现在的消费者不仅对服务质量要求更高,对消费体验的便捷性也愈发… · 2026/11/3 16:37:59
自动化工程师职业突破与质量保障体系构建 1. 自动化工程师的职业困境与价值重估在软件测试领域,我见过太多优秀的自动化工程师陷入职业发展瓶颈。他们日复一日地编写着重复的测试用例,像流水线上的工人一样机械地完成着测试任务。这种工作状态不仅消磨了他们的技术热情,更严重限制了职… · 2026/11/3 16:37:59
LMS算法详解:从梯度下降原理到自适应滤波实战 1. LMS算法的本质:从“找最低点”说起很多人第一次接触LMS(Least Mean Squares,最小均方)算法,是在自适应滤波器的章节里。教材通常一上来就给出一堆公式,然后告诉你“用梯度下降法更新权向量”。但如果你只… · 2026/11/3 16:37:59
LMS自适应滤波算法全解析:从梯度下降原理到NLMS、APA工程实践 1. 从一个“看起来很简单”的算法说起LMS,全称 Least Mean Squares,中文一般叫“最小均方算法”。如果你翻过任何一本讲自适应滤波或者信号处理的书,大概率会在前几章就撞见它。公式短得可怜,就两行:算误差,… · 2026/11/3 16:37:59
AddressSanitizer原理与实战:内存错误检测工具详解 1. AddressSanitizer 核心原理剖析1.1 内存错误检测的基本原理AddressSanitizer(简称ASan)本质上是一个基于编译时插桩和运行时检查的内存错误检测工具。它的核心思想是通过在程序内存周围创建"隔离区"(redzone)和使用&… · 2026/11/3 16:37:58
Opus 5.5、Grok 4.7与GPT-5.6 Sol实测对比:谁是最强AI模型? 从昨天开始,AI圈的朋友应该都没睡好。Opus 5.5、Grok 4.7、GPT-5.6 Sol,三家重量级选手在两天内先后放榜,节奏紧凑得像是商量好的。我刷完三场发布的技术报告和实测数据,第一反应是:这哪是发布,分明是三场复… · 2026/11/3 16:36:58
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46