1. 项目概述:这不是一份清单,而是一张LLM应用开发的实战地图
“awesome-llm-apps”——看到这个名字,第一反应不是点开收藏,而是下意识打开终端准备 clone。它早已不是GitHub上又一个冷冰冰的star收割机,而是我过去两年在真实业务线里反复回溯、验证、踩坑后,最常翻阅的导航索引。它不教你怎么调用OpenAI API,也不讲Transformer的数学推导,它干的事很实在:把散落在全球开源社区里、能真正跑起来、能改、能扩、能上线的LLM应用案例,按技术栈、按问题域、按成熟度,像修车手册一样摊开给你看。关键词里的RAG、AI Agents、open-source,不是标签,是三条贯穿所有项目的主干脉络:RAG解决“我知道什么”的可信知识注入问题,AI Agents解决“我该做什么”的任务拆解与执行闭环问题,而open-source则是所有可复现、可审计、可定制的前提。我带团队做过三个落地项目——智能合同审查助手、垂类技术文档问答系统、内部IT服务自助机器人,每一个的起点,都是从这个仓库里挑出一个匹配度最高的参考项目,然后花3天时间把它本地跑通,再根据业务数据和流程开始迭代。它适合谁?不是纯理论研究者,也不是只想调API玩demo的初学者,而是正在被“怎么把大模型用到实际业务里”这个问题卡住的工程师、技术负责人、产品架构师。你不需要从零造轮子,但必须清楚每个轮子的轴承型号、承重极限和适配胎压——而这,正是这份清单存在的全部意义。
2. 核心设计逻辑:为什么是“应用”而非“模型”?一张分层解耦的技术图谱
2.1 从模型能力到业务价值的三层断层,以及如何填平
很多人误以为拿到一个7B参数的开源LLM就万事大吉,结果部署后发现:回答牛头不对马嘴、检索不到关键条款、多步任务直接崩盘。根本原因在于,模型能力 ≠ 应用能力。这中间横亘着三道鸿沟:
第一层:知识鸿沟。通用大模型的知识截止于训练数据,而你的合同模板、内部SOP、最新API文档,它一无所知。RAG不是简单加个向量库,而是构建一套“知识保鲜机制”:文档解析(PDF/Word/Markdown结构化提取)、语义分块(不是按固定字数切,而是按逻辑段落+标题层级+代码块边界)、嵌入质量校验(用query embedding与chunk embedding的余弦相似度分布图判断切块合理性)、重排序(Cross-Encoder对Top-K结果做精排)。我在做合同审查时,发现原始RAG返回的条款引用位置错乱,最后定位到是PDF解析时表格识别失败,改用
unstructured库的partition_pdf并开启strategy="hi_res"才解决。第二层:行为鸿沟。模型能生成文本,但不能自主决策下一步该查数据库、该调用哪个API、该向用户确认模糊需求。AI Agents的核心不是“更聪明”,而是“更守规矩”。典型框架如LangChain的AgentExecutor、LlamaIndex的ReActAgent,本质是定义了一套工具调用协议:每个工具必须有明确的description(供LLM理解用途)、input_schema(约束输入格式)、output_parser(结构化返回结果)。我们曾用一个天气查询Agent上线,结果LLM在用户问“明天北京穿什么”时,错误调用了“获取空气质量指数”工具,只因description写成了“提供环境数据”,太宽泛。后来改成“返回北京市明日最高/最低气温、体感温度及穿衣建议”,问题消失。
第三层:工程鸿沟。开源项目跑得通demo,不等于能进生产。这里藏着大量“非功能性需求”:响应延迟(RAG中向量检索+LLM生成的P95必须<3s)、缓存策略(相同query的embedding复用、检索结果缓存)、降级方案(向量库宕机时自动fallback到关键词检索)、可观测性(记录每一步token消耗、工具调用耗时、失败原因)。一个叫
llama-index-rag的项目,本地测试流畅,但压测时QPS刚过50,向量库内存就爆了——根源在于它用SimpleVectorStore全量加载到内存,换成Milvus或Qdrant的分布式部署才扛住。
“awesome-llm-apps”的价值,正在于它天然按这三层断层组织项目。你看它分类:RAG目录下全是知识注入方案,Agents目录聚焦任务编排,Frameworks里列着LangChain、LlamaIndex、Semantic Kernel等胶水层选型对比。它不告诉你“RAG很好”,而是用private-gpt告诉你怎么离线运行,用ragatouille展示如何用ColBERT做高效重排,用docling解决复杂PDF表格提取——每一项都直指某一层的具体痛点。
2.2 开源生态的真实分工:谁在造引擎,谁在搭汽车,谁在规划公路
把“awesome-llm-apps”当菜谱看会走偏,它本质是一份开源协作分工图。理解这个,才能避免重复造轮子:
底层引擎层(Infrastructure):专注模型与算力。Hugging Face Transformers、vLLM、Ollama、llama.cpp属于这一层。它们解决“怎么高效加载、推理、量化模型”。比如
vLLM的PagedAttention,让7B模型在单卡A10G上吞吐翻3倍;llama.cpp的GGUF量化,让3B模型能在MacBook M2上跑起来。这些项目不直接做应用,但决定了上层应用的性能天花板。中间框架层(Orchestration):专注连接与编排。LangChain、LlamaIndex、Semantic Kernel是典型。它们不训练模型,也不存知识,而是提供一套DSL(领域特定语言),让你用Python代码描述“先检索知识库,再用结果填充prompt,最后调用工具修正输出”。LangChain的
Runnable抽象,LlamaIndex的QueryEngine,本质都是把LLM、向量库、工具、记忆模块,像乐高一样插在一起。选框架不是比功能多,而是看它是否匹配你的团队技能树——如果团队熟悉Pydantic,LangChain的BaseModel定义工具就很顺手;如果更习惯函数式编程,LlamaIndex的NodeParser链式调用可能更直观。上层应用层(Application):专注场景与交付。
PrivateGPT、Docq、Flowise、Dify属于这一层。它们是开箱即用的解决方案,目标是让用户“上传文档→点击部署→获得问答界面”。这类项目的价值在于验证了某个模式的可行性,比如Docq证明了RAG在企业文档管理中的UI/UX范式,Flowise展示了低代码编排Agent的交互逻辑。但它们往往需要深度定制才能融入现有系统——我们曾基于Dify二次开发,替换了它的默认向量库为自研的混合检索(关键词+向量+规则),并接入内部SSO认证。
“awesome-llm-apps”的分类,恰恰映射了这个分层。它不鼓励你从引擎层开始重写vLLM,而是引导你:先确认业务需要什么应用形态(问答?Agent?工作流?),再选匹配的框架层工具,最后用引擎层项目解决性能瓶颈。这种分层思维,比任何具体技术细节都重要。
2.3 “Awesome”背后的残酷筛选标准:为什么有些项目永远进不了清单
很多人好奇,一个项目凭什么被收录进awesome-llm-apps?不是靠star数,也不是靠作者名气,而是几条硬核的“生存检验”:
可复现性(Reproducibility):必须提供完整的
requirements.txt或Dockerfile,且依赖版本锁定。我曾试过一个标榜“支持中文RAG”的项目,clone后发现pip install -r requirements.txt直接报错,因为langchain==0.1.0与chromadb==0.4.24存在已知兼容问题,而作者在issue里回复“请自行解决”。这种项目永远不会被收录——它违背了开源协作的基本契约:降低他人使用门槛。最小可行路径(MVP Path):必须有清晰的“5分钟上手指南”。理想状态是:
git clone→cd project→pip install -e .→python app.py→ 浏览器打开http://localhost:8000看到界面。PrivateGPT做得极好:它用uv替代pip加速安装,docker-compose.yml一键拉起PostgreSQL+Qdrant+Web UI,甚至预置了sample-docs让你立刻体验。反例是某些学术项目,README里写满论文公式,却找不到一行启动命令。生产就绪信号(Production Signals):虽是开源,但要有工程化痕迹。比如:配置文件分离(
.env管理密钥)、日志分级(INFO/DEBUG/WARN)、健康检查端点(/healthz)、指标暴露(Prometheus格式)。Flowise的docker-compose.prod.yml里包含Nginx反向代理、Let's Encrypt证书自动续期、Redis缓存配置,这就是生产就绪的明证。活跃维护证据(Maintenance Proof):不是看commit频率,而是看issue响应质量。一个健康的项目,其issue区应该有:用户提问得到详细解答、bug报告附带复现步骤和环境信息、PR被及时review并给出建设性意见。我曾给一个项目提过一个内存泄漏bug,作者三天内回复“已定位,将在v2.3.0修复”,并附上临时patch——这种维护节奏,才是“awesome”的底气。
理解这些筛选标准,你就明白:这份清单不是技术趋势风向标,而是经过千锤百炼的工程实践结晶。它过滤掉了90%的玩具项目,剩下的每一个,都值得你投入时间去深挖。
3. 核心技术点深度拆解:RAG、Agents、框架选型的实操陷阱与避坑指南
3.1 RAG不是“检索+生成”,而是知识生命周期管理
RAG常被简化为“向量检索+LLM生成”,但真实项目里,80%的精力花在检索之前和生成之后。我把RAG流程拆解为六个不可跳过的环节,并标注每个环节的致命陷阱:
文档摄入(Ingestion):
- 陷阱:PDF解析丢失表格、公式、页眉页脚;Markdown中代码块被当作普通文本切分。
- 实操:用
unstructured处理PDF,关键参数strategy="hi_res"(启用OCR)+infer_table_structure=True;处理Markdown用markdown-it-py解析AST,保留代码块节点,切块时将代码块整体作为独立chunk。我们曾因忽略表格解析,导致合同金额条款被拆成碎片,RAG返回“¥”和“1,000,000”两个孤立词。
语义分块(Chunking):
- 陷阱:固定长度切块(如512字符)破坏语义连贯性;标题层级丢失导致上下文断裂。
- 实操:采用
HierarchicalNodeParser(LlamaIndex):先按#、##标题分割大块,再在每个大块内用SentenceSplitter按句号/分号切分,最后对代码块、表格单独处理。参数设置:chunk_size=512, chunk_overlap=128,但必须配合metadata记录source_doc_id和hierarchy_level,供后续重排序使用。
向量化(Embedding):
- 陷阱:通用embedding模型(如text-embedding-ada-002)在专业领域表现差;本地模型(如bge-small-zh)未针对业务术语微调。
- 实操:优先选领域适配模型。中文法律场景用
bge-reranker-base做重排,embedding用bge-m3(支持多粒度检索);技术文档用text2vec-large-chinese。本地部署时,用llama-cpp-python加载GGUF格式模型,比transformers快3倍,显存占用低40%。
向量存储(Vector Store):
- 陷阱:ChromaDB单机版不支持并发写入,高QPS下崩溃;FAISS内存暴涨无法释放。
- 实操:生产环境必选分布式向量库。
Qdrant(Rust编写,内存友好)+Redis(缓存query embedding)组合:Qdrant配置optimization_threshold=1000(自动合并小段),Redis设置TTL=1h缓存高频query。我们压测时,Qdrant集群3节点支撑200 QPS稳定,Chroma单机在50 QPS即OOM。
检索与重排(Retrieval & Reranking):
- 陷阱:单纯Top-K检索返回噪声;未用重排导致相关性排序错误。
- 实操:两阶段检索:第一阶段用Qdrant的
hybrid search(关键词+向量),召回Top-50;第二阶段用bge-reranker-base对Top-50重排,取Top-5。重排模型必须用业务数据微调——我们用1000条人工标注的“query-paragraph-relevance”样本,在LoRA上微调2小时,相关性提升37%。
提示工程与生成(Prompting & Generation):
- 陷阱:Prompt写成“请根据以下内容回答”,导致LLM自由发挥;未约束输出格式,前端解析失败。
- 实操:采用
ReAct风格Prompt:
并用你是一个严谨的合同审查助手。请严格按以下步骤操作: 1. 分析用户问题,识别关键实体(如甲方、乙方、金额、日期); 2. 检索知识库,找到最相关的3个条款片段; 3. 基于条款,用JSON格式输出:{"risk_level": "high/medium/low", "explanation": "不超过50字", "suggestion": "具体修改建议"}。pydantic定义OutputSchema,LLM输出后自动校验,失败则重试。
提示:RAG效果70%取决于数据质量,而非模型。我们曾用同一套LLM,仅更换知识库——旧库用OCR扫描件,新库用人工校对的Markdown,准确率从62%跃升至89%。别迷信模型,先搞定数据。
3.2 AI Agents不是“更聪明的聊天机器人”,而是受控的任务执行器
Agent的本质是状态机+工具集+记忆体。很多项目失败,是因为把Agent当成“能聊会写的LLM”,忽略了其核心约束:
状态机(State Machine):Agent必须有明确的状态流转。典型ReAct模式:
Thought→Action→Observation→Answer。每个Action必须对应一个注册的工具,且Observation必须是工具执行后的结构化结果。我们曾用LangChain的SelfAskWithSearch,结果LLM在Thought阶段就编造了不存在的搜索结果,导致整个流程失控。后来强制要求:所有Action必须是预定义工具名,Observation必须是工具返回的dict,否则中断流程。工具集(Tool Registry):工具不是越多越好,而是越精准越可靠。每个工具必须满足:
description:用动宾短语,如“查询用户在CRM中的最新订单状态”,而非“提供订单信息”;args_schema:用Pydantic BaseModel严格定义,如class OrderQuery(BaseModel): user_id: str = Field(..., description="用户唯一标识");return_direct:设为True时,工具结果直接作为最终答案,绕过LLM生成(适用于精确查询)。
记忆体(Memory):不是简单存聊天记录,而是管理对话状态。
ConversationBufferMemory只存最近N轮,易丢失上下文;ConversationSummaryMemory用LLM压缩摘要,但可能丢失关键细节。我们采用ConversationEntityMemory:自动提取人名、公司名、金额等实体,存入Redis,每次调用前注入current_entities到prompt,确保LLM始终知道“张三”是谁、“XX科技”是哪家客户。
一个典型Agent故障排查表:
| 现象 | 可能原因 | 排查步骤 |
|---|---|---|
| Agent反复调用同一工具无进展 | Thought逻辑循环 | 检查prompt中是否缺少“若多次尝试失败,请换策略”约束;查看max_iterations是否设为1 |
| 工具调用参数错误 | args_schema校验失败 | 在tool wrapper中添加print(f"Calling {tool.name} with {kwargs}"),确认传入值类型 |
| Observation返回空或乱码 | 工具实现未处理异常 | 在tool函数内加try-except,捕获异常并返回{"error": "xxx"},避免LLM解析失败 |
| 多轮对话后忘记初始需求 | Memory未持久化 | 检查memory backend(Redis/PostgreSQL)连接是否正常,key命名是否含session_id |
实操心得:Agent的稳定性,80%靠约束,20%靠LLM。我们上线前,用100个真实case做回归测试,重点验证:工具调用次数≤3次、最终答案含明确action verb(如“已为您创建工单”)、无模糊表述(如“可能”“大概”)。任何一项不达标,就重构prompt或调整tool schema。
3.3 框架选型:LangChain vs LlamaIndex vs Semantic Kernel,一场关于抽象层级的抉择
选框架不是比功能多,而是比抽象层级是否匹配你的问题复杂度。我用三个真实项目对比:
| 维度 | LangChain | LlamaIndex | Semantic Kernel |
|---|---|---|---|
| 核心哲学 | “胶水框架”:连接一切,灵活性极高 | “RAG专用框架”:为检索优化,开箱即用RAG | “微软生态框架”:深度集成Azure AI,强企业级支持 |
| 学习曲线 | 陡峭:需理解Runnable、Chain、AgentExecutor等抽象 | 平缓:VectorStoreIndex、QueryEngine概念直观 | 中等:需熟悉Kernel、Plugin、FunctionCall概念 |
| RAG上手速度 | 慢:需手动组装Retriever+LLM+PromptTemplate | 快:index.as_query_engine()一行启动 | 中:需配置AzureTextCompletion和Memory |
| Agent编排能力 | 最强:Plan-and-Execute、ReAct、Self-Refine全支持 | 较弱:主要聚焦RAG,Agent需额外扩展 | 强:SequentialPlanner、StepwisePlanner成熟 |
| 生产监控 | 需自行集成OpenTelemetry | 内置CallbackManager支持日志/指标 | Azure Monitor原生集成 |
| 我们的选择 | 内部IT服务机器人(复杂多工具调用) | 技术文档问答系统(纯RAG,高精度) | 客户-facing智能客服(需Azure合规审计) |
关键决策点:
选LangChain当你要“造车”:比如需要Agent动态决定调用CRM还是ERP,且每个系统API差异巨大。它的
Tool抽象让你能为每个API写定制wrapper,AgentExecutor的max_execution_time参数能防止单次调用超时。选LlamaIndex当你要“开高速”:比如知识库是10万页技术文档,要求毫秒级响应。它的
HybridRetriever(关键词+向量)+SubQuestionQueryEngine(自动拆解复合问题)+RecursiveRetriever(跨文档关联)组合,比LangChain手写快3倍。选Semantic Kernel当你要“进国企”:比如项目必须通过ISO 27001审计,所有日志需存Azure Log Analytics。它的
Kernel内置Telemetry模块,自动上报token用量、latency、error rate,无需额外开发。
注意:框架不是终身契约。我们第一个项目用LangChain,半年后因RAG性能瓶颈,将检索模块替换为LlamaIndex的
VectorStoreIndex,其余Agent逻辑不变——这得益于它们都遵循LLM、Retriever等标准接口。框架间互操作,比绑定单一框架更重要。
4. 实操全流程:从clone一个项目到上线一个RAG+Agent混合系统的完整路径
4.1 第一步:选定基准项目——以docq为例的深度剖析
为什么选docq?不是因为它star最多,而是它完美覆盖RAG+Agent混合场景,且代码干净、文档扎实。它定位为“企业级文档问答”,核心能力:上传PDF/DOCX → 自动解析 → 构建知识库 → 支持自然语言问答 → 对复杂问题自动拆解为子问题(Agent行为)。
代码结构解读:
docq/根目录下:core/:核心逻辑,ingestion.py处理文档摄入,retrieval.py封装Qdrant检索,llm.py管理模型调用;web/:Streamlit前端,app.py是入口,components/里chat_ui.py实现消息流;config/:settings.py定义所有可配置项,secrets.toml管理密钥(Git忽略);tests/:覆盖关键路径,如test_ingestion.py验证PDF解析准确性。
启动前必改的三处配置:
config/settings.py中VECTOR_STORE_TYPE = "qdrant"(默认Chroma,生产必须换);docker-compose.yml中Qdrant服务增加environment: QDRANT__OPTIMIZATION_THRESHOLD: "1000";.env中LLM_MODEL_NAME = "qwen2:7b"(Ollama模型名),EMBEDDING_MODEL_NAME = "bge-m3"。
首次运行验证:
# 启动服务 docker-compose up -d qdrant postgres # 安装依赖(注意:docq用poetry) poetry install # 启动Web UI poetry run streamlit run web/app.py访问
http://localhost:8501,上传sample-docs/contract.pdf,输入“甲方违约责任是什么?”,应返回精准条款。若失败,立即看docker logs docq-web,90%问题在QDRANT_URL未指向docker网络内地址(应为http://qdrant:6333,非localhost)。
4.2 第二步:数据管道加固——让知识库从“能用”到“可信”
docq默认用pymupdf解析PDF,但在合同场景下,表格和签名区域识别率不足。我们加固流程:
解析层升级:
替换core/ingestion.py中的_parse_pdf函数:from unstructured.partition.pdf import partition_pdf def _parse_pdf(file_path: str) -> List[Document]: elements = partition_pdf( filename=file_path, strategy="hi_res", # 启用OCR infer_table_structure=True, # 表格结构识别 languages=["chi"], # 中文 chunking_strategy="by_title", # 按标题分块 ) # 过滤掉页眉页脚(基于坐标) filtered_elements = [e for e in elements if e.metadata.page_number and e.metadata.coordinates] return [Document(text=e.text, metadata=e.metadata.to_dict()) for e in filtered_elements]分块策略优化:
在core/retrieval.py中,修改_get_nodes_from_documents:from llama_index.core.node_parser import HierarchicalNodeParser parser = HierarchicalNodeParser.from_defaults( chunk_sizes=[2048, 512, 128], # 大块→中块→小块 chunk_overlap=128, include_metadata=True, ) nodes = parser.get_nodes_from_documents(documents) # 为代码块添加特殊tag for node in nodes: if "```" in node.text: node.metadata["node_type"] = "code_block"向量库初始化脚本:
新增scripts/init_vector_store.py,确保Qdrant collection创建时启用混合搜索:from qdrant_client import QdrantClient client = QdrantClient(url="http://qdrant:6333") client.create_collection( collection_name="docq_docs", vectors_config={ "default": models.VectorParams( size=1024, # bge-m3输出维度 distance=models.Distance.COSINE ) }, # 启用全文搜索 hnsw_config=models.HnswConfigDiff( on_disk=True, ), # 添加payload index(用于filter) payload_schema={"source_doc_id": models.PayloadSchemaType.KEYWORD}, )
实操心得:知识库质量验证,不能只看单次问答。我们建立自动化验证集:100个预设问题(如“保密期限多久?”),用
pytest跑回归测试,统计answer_correctness(人工标注标准答案)和response_latency(P95 < 1.2s)。每次代码变更,必须通过此测试集才允许合并。
4.3 第三步:Agent能力注入——让问答系统学会“思考”
docq原生只支持单轮问答。我们要让它对“对比A/B两个合同版本的付款条款差异”这类问题,自动拆解为:
- 检索合同A的付款条款;
- 检索合同B的付款条款;
- 调用
diff_tool计算差异; - 生成总结。
定义Diff工具:
在core/tools.py新增:from pydantic import BaseModel, Field class DiffInput(BaseModel): text_a: str = Field(..., description="合同A的付款条款文本") text_b: str = Field(..., description="合同B的付款条款文本") def diff_tool(input: DiffInput) -> dict: """比较两段文本差异,返回结构化结果""" # 使用difflib.SequenceMatcher from difflib import SequenceMatcher matcher = SequenceMatcher(None, input.text_a, input.text_b) opcodes = matcher.get_opcodes() changes = [] for tag, i1, i2, j1, j2 in opcodes: if tag == 'replace': changes.append({"type": "replace", "a": input.text_a[i1:i2], "b": input.text_b[j1:j2]}) return {"changes": changes, "summary": f"共{len(changes)}处差异"}注册工具到Agent:
修改web/app.py,在initialize_app中:from langchain.agents import Tool from core.tools import diff_tool, DiffInput tools = [ Tool( name="diff_contracts", func=diff_tool, description="比较两个合同付款条款的差异,输入为text_a和text_b", args_schema=DiffInput, ), # 保留原有检索工具 Tool( name="search_knowledge_base", func=retriever.query, description="在知识库中检索合同条款,输入为自然语言问题", ), ]设计Agent Prompt:
创建prompts/agent_prompt.txt:你是一个专业的合同对比分析师。请严格按以下步骤操作: 1. 解析用户问题,识别两个合同ID(如"合同A"、"合同B"); 2. 用search_knowledge_base工具分别检索两个合同的付款条款; 3. 将检索结果传给diff_contracts工具; 4. 用JSON格式输出:{"contract_a_id": "...", "contract_b_id": "...", "differences": [...], "recommendation": "..."} 注意:若任一合同ID未识别,立即停止并询问用户。集成Agent到UI:
在web/components/chat_ui.py中,当检测到问题含“对比”“差异”“不同”时,切换为Agent模式:if any(word in user_input for word in ["对比", "差异", "不同"]): agent = initialize_agent(tools, llm, prompt_template="agent_prompt.txt") response = agent.invoke({"input": user_input}) st.write(response["output"]) else: # 原RAG流程 response = query_engine.query(user_input)
注意:Agent模式必须有超时保护。我们在
initialize_agent中设置max_execution_time=30,并捕获TimeoutError,返回“分析超时,请简化问题”。
4.4 第四步:生产化部署——从Demo到SLA保障的七道关卡
一个能跑通的Demo,距离生产环境还有七道墙:
容器化加固:
Dockerfile中禁用root用户,用USER 1001;COPY指令后加RUN chown -R 1001:1001 /app;HEALTHCHECK指令检查/healthz端点。配置中心化:
移除所有硬编码配置,用pydantic-settings读取环境变量:from pydantic_settings import BaseSettings class Settings(BaseSettings): QDRANT_URL: str LLM_MODEL_NAME: str EMBEDDING_MODEL_NAME: str class Config: env_file = ".env" settings = Settings()密钥安全:
.env文件不提交,用vault或AWS Secrets Manager注入。Docker启动时:docker run --env-file <(aws secretsmanager get-secret-value --secret-id docq-secrets --query 'SecretString' --output text) docq-app日志标准化:
用structlog替代print,输出JSON日志:import structlog logger = structlog.get_logger() logger.info("query_processed", query=user_input, latency_ms=latency, status="success")监控埋点:
集成prometheus-client,暴露指标:docq_query_total{status="success"}docq_latency_seconds{quantile="0.95"}qdrant_query_count
灰度发布:
Nginx配置按请求头X-User-Group分流:map $http_x_user_group $backend { "beta" "backend-beta"; default "backend-stable"; } upstream backend-stable { server docq-stable:8000; } upstream backend-beta { server docq-beta:8000; }灾备降级:
当Qdrant不可用时,自动fallback到Elasticsearch关键词检索:try: results = qdrant_retriever.retrieve(query) except Exception as e: logger.warning("Qdrant failed, fallback to ES", error=str(e)) results = es_retriever.retrieve(query)
实操心得:上线前必须做混沌工程测试。我们用
chaos-mesh随机kill Qdrant pod,验证降级是否生效;用artillery模拟500并发,观察CPU/内存/延迟曲线。一次上线前测试发现,ES降级时响应时间从1.2s飙升至8s,立即优化ES查询DSL,加入_source_includes减少网络传输。
5. 常见问题与独家排查技巧:那些文档里不会写的血泪教训
5.1 RAG相关问题速查表
| 问题现象 | 根本原因 | 排查技巧 | 解决方案 |
|---|---|---|---|
| 检索结果完全不相关 | Embedding模型与业务术语不匹配 | 用curl直接调用embedding API,输入“违约金”和“滞纳金”,看向量余弦相似度是否>0.8 | 微调embedding模型:用业务术语对(如“违约金”↔“滞纳金”)构造对比学习样本,LoRA微调2小时 |
| 同一问题多次检索结果不同 | 向量库未设置consistency参数 | 查看Qdrant collection info,确认consistency是否为all | 在Qdrant client初始化时加consistency=models.ReadConsistencyType.ALL |
| PDF表格内容缺失 | pymupdf未启用OCR | 用fitz.open(pdf_path)[0].get_text()打印第一页文本,看表格区域是否为空 | 改用unstructured+strategy="hi_res",或pdfplumber解析表格后转Markdown |
| 长文档检索慢 | 向量库未优化 | qdrant describe collection docq_docs看segments数量,若>1000则需优化 | 设置QDRANT__OPTIMIZATION_THRESHOLD=1000,或手动触发client.optimize(collection_name) |
| LLM生成答案偏离检索内容 | Prompt未强制引用 | 检查prompt中是否有“必须基于以下检索结果回答”字样 | 加入约束:“若检索结果为空,回答‘未找到相关信息’,禁止编造” |
5.2 Agents相关问题速查表
| 问题现象 | 根本原因 | 排查技巧 | 解决方案 |
|---|---|---|---|
| Agent无限循环调用同一工具 | Thought未更新 |