
最初决定把 RAG 用在做服装推荐上是因为我实在受够了传统电商搜索的物理学家式回答。你输入冬天上班穿的不要太正式但也有质感的外套系统只会做关键词拆解把冬天上班外套三个词拿去做字面匹配然后在标题里同时含这三个词的商品中按销量排序给你。至于不要太正式有质感这种真正的意图传统推荐系统完全无感。而 RAG 的核心思路其实很适合这个场景把商品知识拆成可检索的文本块先召回再让大模型综合理解最后生成一个既符合用户模糊描述又能解释为什么的推荐结果。这篇文章就是我基于这个想法从零搭起一个服装推荐 RAG 项目的完整记录包含数据切分、本体设计、Agentic 化改造、本地部署和一堆实测踩坑适合想入门 RAG 又不想只做文档问答 Demo的人参考。1. 为什么服装推荐需要 RAG传统推荐系统的三个死穴1.1 关键词匹配处理不了组合语义服装推荐里最难的不是数据少而是用户表达的需求天然是组合式、模糊式的。同样是通勤穿在互联网公司上班和在外企坐前台对衣服的要求完全不同同样是显瘦小个子和大骨架人群理解得也不一样。传统搜索用倒排索引做字面匹配能处理白色 衬衫这种词但处理不了适合面试的白色衬衫和适合约会的白色衬衫之间的细微差别——这两个查询在字面上高度重合但检索意图南辕北辙。RAG 的逻辑不一样。它先把每个商品、每篇穿搭文章变成向量化表示用户查询的时候也是把整句自然语言变成向量在语义空间里找邻居。这样适合面试的白色衬衫会被优先召回那些提到面试正式场合通勤商务的文本块而不是简单撞词的商品。我在项目里测过同样一组商品数据传统 BM25 搜索和向量检索对同一条模糊查询的结果重合度不到 40%向量检索对意图的把握明显更好。1.2 推荐系统的核心其实是知识资产很多团队做推荐只盯着点击率、转化率却忽略了一个事实服装推荐本质上是一个知识密集型任务。什么场合配什么风格、什么版型适合什么身材、什么面料适合什么季节这些知识散落在商品详情页、穿搭文章、用户评价里。传统推荐系统要么把这些知识人工抽成标签要么干脆不管靠协同过滤让用户行为代替知识。RAG 项目让我重新理解了这件事推荐系统的竞争力很大程度取决于你把这些知识组织成了什么形态。同样一批商品如果只是结构化字段那查询就只能走属性过滤用户说有点正式又不想太呆板就完全没辙如果把穿搭师写的内容、商品详情里的描述细节、甚至面料科普都拆成文本块放进向量库大模型就有足够素材来理解模糊措辞。我搭这个项目时特意把 200 篇穿搭文章和 1000 件商品描述混在一个知识库里效果比只放商品结构化数据好得多。1.3 服装 RAG 推荐助手的最小可运行形态如果你也想复现这个项目先建立正确的心智模型。整个系统的核心只有四个部分知识库承载服装知识的文本块集合来源包括商品描述、穿搭文章、面料百科、用户常见问答嵌入模型把文本变成向量中文场景推荐 bge-m3、m3e 这类模型向量检索根据用户查询召回最相关的 TopK 知识块可以混入BM25关键词召回大模型生成把召回的知识块和用户原始问题拼进 Prompt让模型生成推荐意见和解释理由。我最初用 Python LangChain 写后来为了给团队 Java 栈复用又用 LangChain4j 重写了一遍核心链路。LangChain4j 的 easy-rag 模块对初学者特别友好几条配置就能跑通。别纠结框架选型先把这条链路上的每一环跑明白后面再谈优化。2. 服装知识库搭建切分、本体与向量化的完整链路2.1 服装数据的特殊难点属性密度高、语义粒度细服装文本和通用文档最大的区别是属性密度极高。一件衬衫的描述里可能同时包含面料成分版型廓形领口设计适合场合搭配建议尺码说明六类信息而且这些信息是揉在一段话里的。如果用通用拆解工具按固定字数切很容易把版型修身和适合人群切到两个 Chunk 里去检索时永远拿不到完整上下文。我的做法是分两层处理。第一层按商品粒度强制切分每个商品一个独立单元不跨商品合并文本第二层在商品单元内部按语义块做二次分段比如把设计亮点面料与工艺穿搭场景尺码与版型拆成字段级子块。这样做的好处是用户问这件衣服会不会显胖时检索能精确命中版型与穿着效果这个子块而不是整篇详情页一股脑塞给大模型。2.2 三个切分粒度单品、场景搭配、知识问答我建议任何服装 RAG 项目至少维护三个切分粒度的知识库它们服务的查询类型完全不同。第一个粒度是单品知识。每个商品一条记录包含标题、属性、详情描述。服务的是我要一件什么风格的外套这类直接需求。第二个粒度是场景搭配知识。一段文字描述一个完整的穿搭方案比如初秋通勤西装外套 直筒西裤 乐福鞋。服务的是下周要去新公司报到穿什么这种需要组合推理的需求。第三个粒度是知识问答。把小个子怎么选大衣长度羊毛衫怎么洗不变形这类 FAQ 拆成问答对。用户问羊毛大衣和羊绒大衣什么区别时千万不要去检索商品详情页这类知识必须单独成库。三个库共用同一个向量索引但每个文本块开头都加了一个类型元数据标签场景/单品/问答检索之后先按标签过滤再决定要不要混合排序。这是让服装 RAG 从能跑到好用的关键一步。2.3 本体 RAG把风格、场合、版型变成显式关系纯向量检索的问题在于它只懂语义相似不懂逻辑关系。用户说想要通勤风模型能查到通勤相关的文章但如果遇到一条文本里通篇只写适合都市白领却从没出现通勤这个词向量检索就可能漏检。这时就需要引入本体Ontology思路。我在项目里手工定义了一个轻量级的服装本体模型核心是四组关系风格类目通勤风 - 休闲风 - 街头风 - 甜美风 - 极简风不同风格之间定义相关性场合映射办公、约会、运动、旅行、婚礼每个场合关联可接受的风格列表版型与身材适配小个子、梨形、苹果型分别适配什么版型单品互斥与互补比如厚底鞋和宽松拖地裤是高风险搭配而修身西装和阔腿裤是互补关系。具体实现上我在每个文本块的元数据里维护一组 RDF-style 三元组比如(单品ID, 适合场合, 正式商务)。检索时先用向量召回一个候选集再做一个本体关系扩展——把候选集里单品的关联单品、适配身材、互斥关系一并抓出来重新排序后交给大模型。这个过程不需要额外部署图谱数据库用内存图结构或者 NetworkX 就行。2.4 Embedding 选型与向量库选择的实测记录中文服装文本的 Embedding我会直接给出结论先用bge-m3或者m3e-base不要一上来就追最新的英文模型。英文模型在中文服装俗语上表现很飘尤其是显瘦遮胯直角肩这类词很多模型完全抓不住语义。我用bge-m3在自建的 500 条服装查询集上测过语义命中率明显比text-embedding-ada-002高。向量库方面本地项目直接上 Chroma 就够。Chroma 支持简单的元数据过滤对上面说的标签预筛选完全够用。如果数据量到十万级以上再考虑 Qdrant 或 Milvus。我是先用 Chroma 跑通后面发现搭配类查询越来越多才迁到 Qdrant 上加了 payload 索引。迁移成本不高因为向量库的抽象层用 LangChain 的vector store接口隔离了换了实现也不改上层代码。3. 从普通 RAG 到 Agentic RAG让服装推荐会追问3.1 单轮 RAG 在服装推荐里的局限第一版系统我做的很天真用户输入一句话检索 TopK 文本块拼 Prompt生成推荐。上线测了两天发现一个问题——用户根本不会把需求一次性说清楚。真实对话是这样的帮我推荐个外套什么场合穿上班穿公司有 dress code 吗不太严格但要体面预算呢一千五以内吧。如果系统只在第一句话做完检索和生成后面的追问信息就全浪费了。这就是 RAG 项目里越来越被强调的 Agentic 化改造检索不是一次性动作而是可以多轮、可编程、可决策的。热词里那个agentscope 2.0的RAG as Service思路也是这个方向让检索从固定步骤变成模型可调用的工具。3.2 意图识别与属性补全先把用户的话翻译成检索条件我在第二版系统里引入了一个轻量级意图识别层不是分类模型而是用 LLM 做一次查询改写。用户输入进来先让模型抽取出结构化的检索条件场合办公/约会/运动/日常风格通勤/休闲/街头/极简版型偏好宽松/修身/oversize价格区间数值范围特殊修饰显瘦、遮胯、高级感、不显老这个阶段不给模型任何检索结果只让他做翻译。翻译出来的结构化字段同时用于两件事一是生成检索式比如通勤 外套 显瘦 1500以内再做向量检索二是调用规则过滤接口把不符合属性的商品直接排除。实测下来多这个一步推荐结果在用户会不会点进去看这个指标上提升了 30% 以上。原因很简单向量检索容易把风格接近但场合不符的商品召回来属性补全把这类噪声在源头卡掉了。3.3 工具调用架构把向量检索、规则过滤、搭配库串起来Agentic RAG 落地时我不是把所有检索逻辑塞进一个 ReAct Agent 里而是拆成几个独立工具。核心工具只有三个。search_knowledge_base(query, filters)向量检索 元数据过滤返回知识块列表带来源标签。这个工具负责理解语义。filter_catalog(attributes)查询结构化商品库按场合/价格/风格/版型做精确过滤。这个工具负责保证准确。get_outfit_combo(occasion)调场景搭配知识库返回预设的成套穿搭方案。这个工具负责提供组合思路。Agent 的决策流程很自然先用filter_catalog圈定候选池再用search_knowledge_base补知识细节如果用户明确说到场合直接拉get_outfit_combo给出成套方案。这套设计比让 Agent 自由调用十几二十个工具稳定得多。工具越多模型选错工具的概率越高我宁愿用三个高内聚工具加一个查询改写层也不愿堆功能面面俱到的十个小工具。如果你在 Java 技术栈LangChain4j 的ToolService支持注解定义工具和方法签名调用链路非常清晰同样的三工具架构很容易复刻这也是热词里langchain4j easy rag能火的原因——它把 RAG 的复杂链路框架化你只需要往框架里填数据源和工具逻辑。4. 零基础本地 RAG 复现Ollama 与简易知识库全流程4.1 为什么要先跑本地而不是直接调云端 API服装推荐这个场景里商品信息、用户需求描述都算得上敏感业务数据直接送第三方大模型 API很多公司法务这关就过不去另外本地跑还意味着你可以随便调 Prompt、随便改切分策略不用心疼 token 费用。热词里ollama 简易本地 rag 知识库【零基础可复制教程】搜索量那么高说明大家都有同样的诉求。我给自己的要求是整套环境在 16G 内存的普通笔记本上能跑。实践下来完全没问题就是响应速度会慢一点做原型验证足够了。4.2 本地部署的具体步骤与配置清单我按自己的实际操作整理了一份可直接执行的清单环境是 macOSWindows 的差异我会单独标注。第一步安装 Ollama并拉取两个模型一个是生成模型我用的qwen2.5:7b-instruct中文理解力在 7B 级别里表现很稳另一个是嵌入模型用bge-m3。命令分别执行ollama pull qwen2.5:7b-instruct ollama pull bge-m3bge-m3支持 8192 的上下文长度用来做长商品描述的向量化很合适。如果你的机器显存吃紧嵌入模型也可以换更小的nomic-embed-text但中文效果会打折扣。第二步准备 Knowledge Base。我直接把 1000 件商品的详情文本和 200 篇穿搭文章丢了进去切分用 LangChain 的RecursiveCharacterTextSplitter参数是chunk_size400、chunk_overlap80。注意这个切分是粗略的真正上线前一定要按我在第 2 小节说的语义块逻辑再过一遍。第三步初始化向量库并写入数据from langchain_community.vectorstores import Chroma from langchain_community.embeddings import OllamaEmbeddings embeddings OllamaEmbeddings(modelbge-m3) vectorstore Chroma.from_documents( documentssplitted_docs, embeddingembeddings, persist_directory./clothing_rag_db, )第四步写检索加生成的代码。这一步我用 LangChain4j 的 Java 版本更顺手但 Python 版本逻辑完全一样from langchain_community.chat_models import ChatOllama from langchain_core.prompts import ChatPromptTemplate llm ChatOllama(modelqwen2.5:7b-instruct) retriever vectorstore.as_retriever(search_kwargs{k: 5, filter: {type: item}}) prompt ChatPromptTemplate.from_template( 你是服装搭配顾问。基于以下知识库内容回答用户问题。如果知识库不足请直接说不知道。\n知识{context}\n用户{question} )第五步把对话接口包成一个简单的 Python 服务我用的 FastAPI一个/recommend端点接收用户描述内部走查询改写-过滤-检索-生成这条链。到这里一个本地服装 RAG 的 MVP 就跑通了。4.3 检索命中率Hit Rate怎么测、怎么调很多初学者跑通了 Demo 就开始沾沾自喜但一遇到真实查询就露馅。我强烈建议你在优化之前先给你的检索写一个带标准答案的评测集。热词里rag hit rate冲上热搜说明大家都在这里栽过跟头。Hit Rate 的定义很简单在 N 条测试查询里标准答案对应的文档是否出现在检索结果的前 K 条中统计命中比例。我准备了 100 条用户需求描述 期望召回商品 ID的测试集每一条都对应一件确切的商品。比如适合小个子的通勤大衣对应商品 02417跑完检索看这条商品在不在 top-5 结果里。我的调优顺序是固定的先调 K从 3 递增到 10观察 Hit Rate 曲线K5 和 K8 的差距如果超过 10%说明召回质量不够先回去调切分再调切分把 chunk_size 从 400 调到 800 或 200看哪组命中率更高服装文本我最后定格在 500 左右最后调混合检索用 BM25 和向量检索各召回一批做 RRF 融合排序对于型款这类属性词BM25 命中率经常比向量检索高。我在这个项目里纯向量检索的 Hit Rate5 大约 68%改成 BM25 向量 本体扩展三路融合之后Hit Rate5 到了 84%。这个数据说明服装 RAG 里单纯堆向量不如把几路召回混合起来。5. 服装 RAG 的典型瓶颈假命中、图片商品与知识割裂5.1 TopK 召回里的假命中语义相似不等于推荐合适服装领域最容易踩的坑是语义相似但场景不搭。用户说想买件显瘦的裙子向量检索召回了显瘦的穿搭技巧、如何穿出显瘦效果、显瘦连衣裙推荐三组文本从语义上完全合理但前两条根本不是商品知识。大模型拿到这种上下文生成出来的答案自然是看起来专业实际上无法执行。解决假命中有两个有效手段。第一个是元数据隔离我在第 2 小节提到的单品/场景/问答三分类在检索后增加一道硬过滤用户要商品推荐就只允许typeitem的文本块进入上下文。第二个手段是加 Reranker 重排用bge-reranker-base对向量召回的 Top20 做精排重排模型能看到查询和候选文本的完整语义对伪相关的惩罚非常明显。我接入 Reranker 之后最终推荐结果里不可用推荐的比例砍掉了近一半。5.2 图片商品能不能进知识库多模态与纯文本两条路线很多做服装的都会问一句RAG 知识库能存图片吗答案是能但要看怎么存。存图片本身没有任何技术障碍把图片文件路径塞进数据库就行。真正的问题是检索。你拿一段用户文本去匹配图片传统向量模型做不了跨模态的语义对齐。要让图片可检索至少要在多模态 Embedding 模型比如 CLIP把图片转成特征向量然后和文本向量的语义空间对齐才能实现外貌-衣服的跨模态召回。我在项目里的实际方案是先转文本再入库因为 1000 件商品本来就有详情页描述文本图片只做展示不做检索源。如果有的商品只有图片没有文案就用一个本地视觉模型我用的 Qwen2-VL 系列先把图片生成一段描述文字再走常规文本入库。这笔账算下来比全局部署一套多模态向量库省很多显卡资源。5.3 文本拆解工具的选择自带分割器还是自定义很多人搜过有没有本地的 RAG 文本拆解工具说明大家都对 LangChain 默认的字符切分效果不满。字符切分在服装文本上的问题很典型一段 800 字的商品描述里前两段讲设计灵感后两段讲面料参数字符切分很可能在第三段中间断开导致两个 Chunk 里都没有完整的面料信息。我最终的拆解方案是半自动的通用文本穿搭文章、面料百科用 LangChain 的RecursiveCharacterTextSplitter按标题层级切商品详情页走了BeautifulSoup按 HTML 结构块解析逐个h2/p标签切保证信息完整性高频问答整理成固定格式的 QA 对不切分每条记录一个单元。如果你连正则都不想写LangChain4j easy-rag里也封装了基于文档结构的拆解器Java 生态可以直接拿来用。但我的体会是拆解这种活儿没有银弹最粗暴高效的永远是读一遍自己的数据再决定怎么切。每类数据的结构不同强行统一切分参数最后一定有一批 Chunk 是碎的。5.4 知识割裂问题GraphRAG 与本体关系的补充思路解决了知识割裂 rag能成为热词说明大家在实际项目中都撞过这堵墙。纯向量检索的每一个文本块都是独立的它看不到这件西装外套和去年那篇教你怎么搭西装的推文之间其实是同一套装扮里的配套关系。数据规模小的时候靠 Reranker 能兜底规模一大割裂感会吞掉推荐的可信度。我在项目里做的折中方案不复杂。用第 2 小节定义的本体关系搭了一个轻量级的图结构节点是单品和知识文章边是搭配互补同风格互斥四类关系。检索流程变成先向量召回候选集再对候选集里的每个节点做一跳或两跳的图扩展把关联节点一起塞进 Prompt。效果最明显的是搭配类查询——用户问买了件卡其色风衣怎么搭纯向量可能只召回风衣详情页加图扩展之后和它共现过三次的白色衬衫、直筒牛仔裤都会被带出来推荐完整度高了不止一个档次。这就是 GraphRAG 思路的极简落地版。不需要搞复杂的图索引算法直接把关系网叠在向量检索之上工程成本不高但对知识割裂的缓解立竿见影。最后再分享一个我自己的感受RAG 项目的难度从来不在框架和技术选型而在数据组织。你花一个晚上能跑通 Ollama Chroma LangChain 的 Demo但要让推荐结果让真人觉得懂我前面 70% 的精力都要砸在切分、本体、评测集这些看着不起眼的活儿上。我踩过最深的坑就是过早优化 Prompt后来发现 Prompt 调得再好检索回来的上下文是垃圾生成结果一样是垃圾。先把 Hit Rate 指标测明白把知识库的组织结构打磨清楚再去研究花哨的 Agent 编排路会顺很多。