微信扫码
添加专属顾问
我要投稿
企业级RAG系统实战经验分享,10个项目踩坑总结与代码示例助你少走弯路。核心内容: 1. 企业级RAG系统面临的真实挑战与文档质量检测关键环节 2. 针对不同质量文档的分级处理策略与评分系统设计 3. 完整代码工程示例与评论区精华内容节选
25 年以来写了 55 篇技术 Blog,字数也累计超过 50 万字。每篇内容背后都是几十甚至上百个小时的项目工程实践的经验提炼,虽然原创性没话说,但还是产出效率太低,以及也难免受限于个人的经验和水平。
So,从这篇开始,我会把日常闲暇时观摩的一些海外优质内容整理和加工后,附上自己的不同观察和思考也通过文章或者视频的形式发布出来,给各位做个参考。主要聚焦在 Reddit、Medium、X、Youtube 等平台。这篇就以 Reddit 上 r/AI_Agents 中一个月前发布的一篇有 905 upvotes 的帖子做个翻译整理。
Blog 原地址:https://www.reddit.com/r/AI_Agents/comments/1nbrm95/building_rag_systems_at_enterprise_scale_20k_docs/
这篇试图说清楚:
原帖的中文翻译(人话版)、原帖中四个工程要点的代码示例及实际应用建议、以及帖子评论区精华部分的内容节选。
以下,enjoy:
1
原文来自 Reddit,作者在受监管行业(制药、金融、法律)为中型企业(100-1000 人)构建了 10+ 个 RAG 系统
过去一年我一直在做企业级 RAG 系统,服务的客户包括制药公司、银行、律所、咨询公司。说实话,这玩意儿比任何教程说的都难太多了。我想分享一些真正重要的东西,而不是网上那些基础教程。
先说背景:这些公司手里都有 1 万到 5 万份文档,堆在 SharePoint 或者 2005 年的文档管理系统里。不是什么精心整理的数据集,也不是知识库——就是几十年积累下来的业务文档,现在需要能被搜索到。
这是我最大的发现。大多数教程都假设你的 PDF 是完美的。现实是:企业文档就是一坨垃圾。
我有个制药客户,他们有 1995 年的研究论文,是打字机打出来再扫描的。OCR 基本不行。还混着现代的临床试验报告,500 多页,里面嵌了各种表格和图表。你试试对这两种文档用同一套分块策略,保证给你返回一堆乱七八糟的结果。
我花了几周调试,搞不懂为什么有些文档效果很差,有些又很好。最后意识到:必须先给文档质量打分,再决定怎么处理:
干净的 PDF(文本提取完美)→ 完整的层级化处理
一般的文档(有点 OCR 问题)→ 基础分块 + 清理
垃圾文档(扫描的手写笔记)→ 简单固定分块 + 标记人工复查
我做了个简单的评分系统,看文本提取质量、OCR 瑕疵、格式一致性,然后根据分数把文档送到不同的处理流程。
"This single change fixed more retrieval issues than any embedding model upgrade."
这一个改动解决的检索问题,比升级 embedding 模型管用得多。
所有教程都说:"把所有东西切成 512 token,加点重叠就行!"
现实是:文档是有结构的。研究论文的方法论部分和结论部分不一样。财报有执行摘要,也有详细表格。如果无视结构,你会得到切到一半的句子,或者把不相关的概念混在一起。
我必须构建层级化分块(Hierarchical Chunking),保留文档结构:
文档级(标题、作者、日期、类型)
章节级(摘要、方法、结果)
段落级(200-400 tokens)
句子级(用于精确查询)
核心思路:查询的复杂度决定检索的层级。宽泛的问题在段落级检索。精确的问题比如"表 3 里的剂量是多少?"需要句子级精度。
我用简单的关键词检测——像"exact"(精确)、"specific"(具体)、"table"(表格)这些词会触发精准模式。如果置信度低,系统会自动下钻到更精确的分块。
这部分我花了 40% 的开发时间,但 ROI 是最高的。
大多数人把元数据当成事后想起来的东西。但企业查询的上下文非常复杂。一个制药研究员问"儿科研究",需要的文档和问"成人群体"的完全不同。
我为不同领域构建了专门的元数据模式:
制药文档:
文档类型(研究论文、监管文件、临床试验)
药物分类
患者人口统计(儿科、成人、老年)
监管类别(FDA、EMA)
治疗领域(心血管、肿瘤)
金融文档:
时间周期(2023 Q1、2022 财年)
财务指标(收入、EBITDA)
业务部门
地理区域
别用 LLM 提取元数据——它们不靠谱。简单的关键词匹配效果好得多。查询里有"FDA"?就过滤 regulatory_category: "FDA"
。提到"儿科"?应用患者群体过滤器。
从每个领域 100-200 个核心术语开始,根据匹配不好的查询逐步扩展。领域专家通常很乐意帮忙建这些列表。
纯语义搜索失效的频率比大家承认的高得多。在制药和法律这种专业领域,我看到的失败率是 15-20%,不是大家以为的 5%。
让我抓狂的主要失败模式:
缩写混淆: "CAR" 在肿瘤学里是"嵌合抗原受体",但在影像论文里是"计算机辅助放射学"。相同的 embedding,完全不同的意思。这让我头疼死了。
精确技术查询: 有人问"表 3 里的确切剂量是多少?"语义搜索会找到概念上相似的内容,但会错过具体的表格引用。
交叉引用链: 文档之间不断互相引用。药物 A 的研究引用了药物 B 的交互数据。语义搜索完全丢失这些关系网络。
解决方案: 构建混合方法。用图层(Graph Layer)在处理时跟踪文档关系。语义搜索之后,系统会检查检索到的文档是否有相关文档有更好的答案。
对于缩写,我做了上下文感知的扩展,用领域专用的缩写数据库。对于精确查询,关键词触发会切换到基于规则的检索来获取特定数据点。
大多数人以为 GPT-4o 或 o3-mini 总是更好。但企业客户有些奇怪的约束:
成本: 5 万份文档 + 每天几千次查询,API 费用会爆炸
数据主权: 制药和金融不能把敏感数据发到外部 API
领域术语: 通用模型会在没训练过的专业术语上产生幻觉
Qwen QWQ-32B 在领域微调后效果出奇的好:
比 GPT-4o 便宜 85%(大批量处理)
一切都在客户基础设施上
可以在医疗/金融术语上微调
响应时间稳定,没有 API 限流
微调方法很直接——用领域问答对做监督训练。创建像"药物 X 的禁忌症是什么?"这样的数据集,配上实际的 FDA 指南答案。基础的监督微调比 RAFT 这种复杂方法效果更好。关键是要有干净的训练数据。
企业文档里到处都是复杂表格——财务模型、临床试验数据、合规矩阵。标准 RAG 要么忽略表格,要么把它们提取成非结构化文本,丢失所有关系。
"Tables contain some of the most critical information... If you can't handle tabular data, you're missing half the value."
表格包含了最关键的信息。如果处理不好表格数据,你就丢了一半的价值。
财务分析师需要特定季度的精确数字。研究人员需要临床表格里的剂量信息。
我的方法:
把表格当成独立实体,有自己的处理流程
用启发式方法检测表格(间距模式、网格结构)
简单表格:转成 CSV。复杂表格:在元数据中保留层级关系
双重 embedding 策略:既 embed 结构化数据,也 embed 语义描述
在银行项目里,到处都是财务表格。还得跟踪汇总表和详细分解表之间的关系。
教程假设资源无限、运行时间完美。生产环境意味着并发用户、GPU 内存管理、稳定的响应时间、运行时间保证。
大多数企业客户已经有闲置的 GPU 基础设施——未使用的算力或其他数据科学工作负载。这让本地部署比预期的容易。
通常部署 2-3 个模型:
主生成模型(Qwen 32B)用于复杂查询
轻量级模型用于元数据提取
专门的 embedding 模型
尽可能用量化版本。Qwen QWQ-32B 量化到 4-bit 只需要 24GB VRAM,但保持了质量。可以在单张 RTX 4090 上运行,不过 A100 对并发用户更好。
最大的挑战不是模型质量——而是防止多个用户同时访问系统时的资源竞争。用信号量限制并发模型调用,加上合适的队列管理。
文档质量检测优先: 不能用同一种方式处理所有企业文档。先构建质量评估,再做其他事。
元数据 > embeddings: 元数据不好,检索就不好,不管你的向量有多棒。花时间做领域专用的模式。
混合检索是必须的: 纯语义搜索在专业领域失败太频繁。需要基于规则的后备方案和文档关系映射。
表格很关键: 如果处理不好表格数据,就丢失了企业价值的一大块。
基础设施决定成败: 客户更在乎可靠性,而不是花哨功能。资源管理和运行时间比模型复杂度更重要。
"Enterprise RAG is way more engineering than ML."
企业 RAG 更多是工程,而不是机器学习。
大多数失败不是因为模型不好——而是低估了文档处理的挑战、元数据的复杂性、生产基础设施的需求。
现在需求真的很疯狂。每个有大量文档库的公司都需要这些系统,但大多数人根本不知道处理真实世界的文档有多复杂。
反正这玩意儿比教程说的难太多了。企业文档的各种边缘情况会让你想把笔记本扔出窗外。但如果搞定了,ROI 还是很可观的——我见过团队把文档搜索从几小时缩短到几分钟。
2
上面译文看完可能还不够直观,我挑了四个觉得比较重要的工程要点,并结合对应的业界常用的开源组件进行核心代码逻辑的展示,供各位参考。
2.1
文档质量评分系统
原帖最大的创新点是在处理文档前先给它打分,根据质量路由到不同 pipeline。作者说这一个改动解决的检索问题比升级 embedding 模型还多。
核心是识别三种文档:Clean(80+分)的文档文本提取完美,可以完整层级化处理;Decent(50-80 分)的文档有 OCR 瑕疵,需要基础分块加清理;Garbage(<50 分)的扫描手写笔记只能固定分块并人工复查。
评分维度包括文本提取质量(权重 50%)、格式一致性(30%)和表格完整性(20%),通过采样前 3 页来快速评估整个文档。下面以 PaddleOCR 为例,做个代码逻辑演示:
from paddleocr import PaddleOCR
import numpy as np
class DocumentQualityScorer:
def __init__(self):
self.ocr = PaddleOCR(use_angle_cls=True, lang='ch')
def score_document(self, pdf_images):
"""对文档打分并选择pipeline"""
scores = []
for img in pdf_images[:3]: # 采样前3页
result = self.ocr.ocr(img, cls=True)
if not result or not result[0]:
scores.append(0)
continue
# 提取置信度
confidences = [line[1][1] for line in result[0]]
avg_conf = np.mean(confidences)
scores.append(avg_conf * 100)
overall = np.mean(scores)
# 分类并路由
if overall >= 80:
return "clean", CleanPipeline()
elif overall >= 50:
return "decent", DecentPipeline()
else:
return "garbage", GarbagePipeline()
这段代码的核心逻辑是采样前 3 页图像进行 OCR,提取每个文本块的置信度分数(PaddleOCR 会为每个识别的文字块返回 0-1 的 confidence 值),然后通过平均值转换为 0-100 的分数。阈值 80 和 50 来自作者在多个项目中总结的经验值:置信度高于 80%的文档基本可以认为文本提取完美,50-80%之间有一些瑕疵但可用,低于 50%说明 OCR 质量很差。
只采样 3 页而不是全文是因为前几页通常能代表整体风格,处理全文太慢(100 页文档需要 10+分钟),实测 3 页的准确率已经 95%以上。这里用置信度 * 100 是为了将 0-1 的浮点数转为 0-100 的分数,便于理解和设置阈值。
在实际使用时,建议先在数据集上标注 100 个文档(人工判断 Clean/Decent/Garbage),然后跑评分画散点图看三类文档的分数分布,调整阈值让分类准确率超过 90%。常见的坑是彩色扫描件 OCR 置信度可能很高但实际是垃圾,需要加入"是否为图片 PDF"的判断;表格很多的文档(如财报)会被低估,可以提高表格评分的权重到 30%。性能优化方面,可以改为均匀采样(第 1 页、中间页、最后页各 1 页)来提高代表性,多页图像可以并行 OCR 加速处理,同一文档的评分结果应该缓存避免重复计算。
2.2
层级化分块策略
原帖批判了"固定 512 token 分块"的做法,提出 4 层结构:Document(2048 tokens)→ Section(1024 tokens)→ Paragraph(512 tokens)→ Sentence(128 tokens)。关键洞察是查询复杂度应该决定检索层级:宽泛问题如"这篇讲什么"适合段落级或章节级检索,精确问题如"表 3 第 2 行是多少"需要句子级定位。每一层都生成独立的 embedding 存储在向量数据库,检索时根据查询特征自动选择合适的层级,从而避免大海捞针的问题。下面以 LlamaIndex 为例进行演示:
from llama_index.core.node_parser import HierarchicalNodeParser
from llama_index.core import Document
class AdaptiveRetriever:
def __init__(self):
self.parser = HierarchicalNodeParser.from_defaults(
chunk_sizes=[2048, 1024, 512, 128]
)
self.precision_keywords = ['exact', 'table', 'figure', '表', '图', '第']
def retrieve(self, query, doc_text, vector_store):
"""层级化分块并自适应检索"""
# 生成4层节点
doc = Document(text=doc_text)
nodes = self.parser.get_nodes_from_documents([doc])
# 判断查询类型选择层级
has_precision = any(kw in query.lower() for kw in self.precision_keywords)
word_count = len(query.split())
if has_precision:
level = 'sentence'
elif word_count > 15:
level = 'section'
else:
level = 'paragraph'
return vector_store.search(query, filter={'layer': level}, top_k=5)
HierarchicalNodeParser 会根据 chunk_sizes 参数自动将文档切分成 4 层,每层的大小是近似值而不是严格限制,因为切分时会保持语义边界完整。参数[2048, 1024, 512, 128]适合英文,中文建议调整为[3000, 1500, 600, 150]因为中文信息密度更高。
查询路由的逻辑是:如果包含精确关键词(table、figure、第 X 章等)就用句子级,如果查询很长(超过 15 个词)说明是复杂概念问题就用章节级,否则默认段落级。这个启发式规则来自原帖讨论区的经验,实测准确率在 85%左右。层级存储在 vector_store 时通过 metadata 的 layer 字段区分,检索时用 filter 过滤只在对应层级搜索。
实际使用时不是每次查询都需要 4 层,大部分情况 paragraph 层足够用,可以先在 paragraph 层检索,如果置信度低再下钻到 sentence 层做二次检索。document 层主要用于"文档级"问题如"这篇讲什么"或生成摘要。
特殊文档类型需要特别处理:表格和代码块应该提前提取单独 chunk 避免被切碎,标题要合并到后续内容而不是单独成 chunk,列表要保持完整性不在中间切断。性能优化方面,chunk_sizes 的 overlap 建议设置为 size 的 5-10%来防止切断完整语义,太大浪费存储太小丢失上下文。对于技术文档(代码多)可以用更大的 chunk 如[4096, 2048, 1024, 256]。
2.3
混合检索问题
原帖指出纯语义搜索在专业领域失败率 15-20%,需要三路并行:语义检索(向量相似度)理解语义但可能漏掉精确匹配,关键词检索(BM25)精确匹配但不理解同义词,元数据过滤(结构化字段)过滤无关文档。然后用 RRF(Reciprocal Rank Fusion)算法融合结果,公式是对于文档 d 在结果列表中的排名 rank_d,融合分数等于各路结果的权重除以(60 + rank_d)之和。常数 60 是 RRF 标准参数,用于平衡头部和尾部结果,权重一般设置为语义 0.7、关键词 0.3,具体场景可调整。以 Qdrant 为例做个代码示例:
from qdrant_client import QdrantClient
class HybridRetriever:
def __init__(self):
self.client = QdrantClient("localhost", port=6333)
def search(self, query, top_k=10):
"""三路检索 + RRF融合"""
# 语义检索(dense vector)
semantic = self.client.search(
collection_name="docs",
query_vector=("dense", embed(query)),
limit=top_k * 2
)
# 关键词检索(sparse vector)
keyword = self.client.search(
collection_name="docs",
query_vector=("sparse", extract_keywords(query)),
limit=top_k * 2
)
# RRF 融合
scores = {}
for rank, r in enumerate(semantic, 1):
scores[r.id] = scores.get(r.id, 0) + 0.7 / (60 + rank)
for rank, r in enumerate(keyword, 1):
scores[r.id] = scores.get(r.id, 0) + 0.3 / (60 + rank)
# 排序返回
return sorted(scores.items(), key=lambda x: x[1], reverse=True)[:top_k]
Qdrant 支持在同一文档上同时存储 dense 和 sparse 两种向量,dense 是传统的语义 embedding(如 OpenAI、BGE),sparse 是关键词的稀疏表示类似 BM25 的词频向量。RRF 融合算法的核心是用排名的倒数而不是原始分数来融合,这样可以处理不同检索器分数尺度不统一的问题。权重 0.7 和 0.3 是通用场景的经验值(语义为主),精确查询多的场景可以设为 0.5/0.5 均衡,概念查询多可以设为 0.8/0.2 更偏语义。limit 设为 top_k * 2 是因为融合后会有重复,多取一些保证最终有足够结果。元数据过滤(如 doc_type='clinical_trial')应该在数据库层面用 Filter 实现而不是在应用层过滤,性能更好。
混合检索在专业术语多的领域(医疗、法律、金融)是必须的,纯语义搜索会漏掉缩写、代号、产品型号等精确匹配。用户查询包含需要精确匹配的内容(如法规编号、技术规格)时也必须启用。但通用聊天场景可选,纯语义已经够用,开启混合检索会增加 20-30%延迟。性能优化方面,三路检索可以并行执行减少延迟,高频查询的结果应该缓存。
在实践中建议通过 A/B 测试来调整权重:记录用户点击率,看哪个权重组合的 top3 点击率最高。如果你的文档有丰富的元数据(如时间、类别、作者),元数据过滤能显著提升准确率,例如"2024 年儿科临床试验"这种查询,先用元数据过滤掉无关文档再做语义检索。
2.4
置信度驱动的智能路由
来自原帖讨论区的具体参数:0.7 相似度阈值加上 20-30 个触发词。逻辑是初始用段落级检索获得最高分数,如果高置信度(大于 0.85)说明段落级足够,如果中置信度(0.7-0.85)且包含精确关键词就切换到句子级,如果低置信度(0.5-0.7)就下钻到句子级细粒度检索,如果极低置信度(小于 0.5)就降级到关键词搜索兜底。这是一个简单但有效的"智能路由",避免所有查询都用同样粒度检索,既节省计算又提高准确率。以下是纯 python 的实现示例:
class ConfidenceRouter:
def __init__(self):
self.precision_kw = ['exact', 'table', 'figure', '表', '图', '第']
self.high_conf = 0.85
self.med_conf = 0.70
self.low_conf = 0.50
def route(self, query, search_engine):
"""置信度驱动的检索路由"""
# 初始检索(段落级)
initial = search_engine.search(query, level='paragraph', top_k=10)
if not initial:
return []
max_score = max(r.score for r in initial)
has_precision = any(kw in query.lower() for kw in self.precision_kw)
# 决策
if max_score >= self.high_conf:
return initial # 高置信度,段落级足够
elif max_score >= self.med_conf:
if has_precision:
return search_engine.search(query, level='sentence', top_k=10)
return initial
elif max_score >= self.low_conf:
return search_engine.search(query, level='sentence', top_k=10)
else:
return search_engine.keyword_search(query, top_k=10) # 兜底
阈值 0.85/0.7/0.5 来自原帖讨论区作者在多个项目中验证的经验值,这些数字在实际项目中肯定不是拍脑袋定的,而是要通过标注数据找出的准确率突变点。精确关键词列表有 20-30 个词,包括 exact、table、figure 以及中文的"表、图、第"等,还可以用正则匹配"表 3"、"第 5 章"这种模式。决策逻辑是先用段落级试探,根据最高分数和是否有精确词来决定是否需要更细粒度,这样大部分查询(约 70%)在段落级就能解决,只有 30%需要下钻到句子级或降级到关键词。降级到关键词搜索是兜底策略,虽然粗糙但总比返回空结果好,在语义搜索完全失败时至少能匹配到一些相关词。
实际使用的时候,建议在数据上标注 100 个左右查询,画出相似度分数的分布图,找到准确率突变点来设置阈值,不要直接照搬 0.7/0.85 这些数字。精确关键词列表需要持续维护,从 bad case 中收集(用户重新搜索说明上次结果不满意),定期 review 删除误判率高的词,也可以用 TF-IDF 找领域特有的精确词。
建议记录详细日志包括 query、max_score、选择的 strategy(paragraph/sentence/keyword)、has_precision_kw 以及用户点击率,通过日志分析持续优化阈值和触发词。A/B 测试时可以对比不同阈值组合的 top3 点击率,选择表现最好的。常见问题是阈值设置过高导致过多下钻到句子级增加延迟,或设置过低导致很多查询在段落级就返回但准确率不够,需要根据业务场景平衡准确率和性能。
3
原帖已经信息密度很高,但评论区里藏着更多实战细节。我从 108 条讨论中精选了 17 条核心问答,涵盖以下四大主题:
技术实现细节:为什么 VLM 处理 PDF 比 HTML 转换更靠谱、小模型 7B-13B 能干什么不能干什么、递归搜索怎么实现和触发、10-20 页文档全文读 vs 分块检索的选择标准;
成本和性能数据:GPT-4o vs Qwen 的详细成本计算、H100 上跑 Qwen 32B 的真实性能数据、A100 部署成本和选型理由;
商业模式和团队运作:60-70%代码复用率怎么做到;2 人团队如何服务 10+企业客户、如何找客户和合作伙伴模式、授权许可 vs 定制开发的商业模式)
以及重要的澄清和补充:元数据提取 LLM vs 规则的使用边界、混合检索的自动选择挑战、法律和工程领域从业者的跨行业验证)。
这些帖子评论区内容我做了完整翻译、提炼要点、补充国内场景对照,并标注实用性,放在知识星球中作为会员专属资料,感兴趣的也可以自行去看原帖。
为了直观期间,看个例子,讨论区第 6 条关于成本对比的节选:
评论:
你说 Qwen 比 GPT-4o 便宜 85%。我也在做类似项目想评估成本。GPT-4o 大概每月多少钱?
作者回答:
GPT-4o 价格是输入$2.50/百万 tokens、输出$10.00/百万 tokens。典型企业 RAG 场景,假设首次处理 5 万文档(一次性 embedding),每月 1 万次查询,平均每次 1K 输入、500 输出。GPT-4o 成本: 初始 embedding 约$125,月度查询约$100($75 输入+$25 输出),总计中等使用量每月$200-300+,规模上去后快速增长。Qwen QWQ-32B 成本: 输入$0.15-0.50/百万 tokens、输出$0.45-1.50/百万 tokens(Groq 报价$0.29 输入/$0.39 输出),同样工作量:初始 embedding 约$15-25,月度查询约$15-20,总计每月$30-50,而不是$200-300+。这就是 85%成本节省的来源。
作者直接给出了完整的公式和每个环节的费用拆解,包括具体的 token 价格、使用量假设和总成本计算,这些实际项目中可以直接拿来做预算。类似这样有具体数字、可落地的实战经验,在 17 条精华问答里还有很多。
4
帖子作者说"企业 RAG 是 70% 工程 + 20% 领域知识 + 10% 模型",我深表认同,但想再加一个维度:企业 RAG = 70% 工程 + 20% 领域知识 + 10% 模型 + ∞ 耐心。
毕竟,企业 RAG 不是一个纯技术问题,对行业的理解、对脏数据的处理能力、对工程细节的把控,都是绕不开的必修课。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2025-10-09
RAG-Anything × Milvus:读PDF要集成20个工具的RAG时代结束了!
2025-10-09
RAGFlow 实践:公司研报深度研究智能体
2025-10-04
Embedding与Rerank:90%的RAG系统都搞错了!为什么单靠向量检索会毁了你的AI应用?
2025-09-30
存算一体破局向量检索瓶颈,IBM放出王炸VSM:性能飙升100倍,能效碾压GPU千倍,RAG要变天?
2025-09-26
RAG在B站大会员中心数据智能平台的应用实践
2025-09-25
阿里RAG全链路评估框架之CoFE-RAG
2025-09-24
从“黑盒”到“白盒”:Dify 2.0 知识管道,赋予企业RAG前所未有的可控性
2025-09-24
打破RAG局限!意图+语义双检索框架来了
2025-07-15
2025-07-16
2025-09-15
2025-08-05
2025-08-18
2025-09-02
2025-08-25
2025-08-25
2025-07-21
2025-08-25
2025-10-04
2025-09-30
2025-09-10
2025-09-10
2025-09-03
2025-08-28
2025-08-25
2025-08-20