Models-学习指南_小白版
Base/Models 层详解——小白也能看懂
📍 原文位置:
D:\AI0409\Day22\Models-学习指南.md本文档是这个包的系统学习指南,目标:让完全不懂数据库的小白也能理解 Models 层是什么、怎么用、为什么这样设计。
前置知识:在看这个之前,你需要知道……
🗄️ 数据库是什么
数据库 = 数据的仓库。就像 Excel 表格,但这个"表格"不是给人看的,是给程序看的。
Excel 表格(给人看): 数据库表(给程序看): ┌──────┬──────┐ ┌──────┬──────┐ │ 姓名 │ 年龄 │ │ name │ age │ ├──────┼──────┤ ├──────┼──────┤ │ 张三 │ 25 │ │"张三" │ 25 │ │ 李四 │ 30 │ │"李四" │ 30 │ └──────┴──────┘ └──────┴──────┘🔢 两种完全不同的"查东西"方式
普通查询:SELECT * FROM users WHERE name = "张三"
→ 精确匹配,张三就是张三,不认识"姓张的人"。
向量查询:找出和"张三"最像的名字
→ 语义理解,李四、王五、赵六……都能找到,甚至英文名"John"也能找到。
类比:
| 查询方式 | 比喻 |
|---|---|
| 普通查询(WHERE) | 📇 查字典——"张三"就只找"张三"这个词 |
| 向量查询(语义搜索) | 🧠 问人——"谁和孙悟空最像?"你会想到猪八戒、沙僧,都对 |
📐 整体定位:Models 层在系统中站在哪儿
先搞清楚一件事——Models 在整个系统里是什么角色。
想象你要盖一栋楼:
完整系统架构(分层): ┌─────────────────────────────┐ │ 用户 / 前端 / App │ ← 最外层,用户能看到的 └────────────┬────────────────┘ ▼ 发送请求 ┌─────────────────────────────┐ │ Base/Api(接口层) │ ← 接收请求,决定交给谁处理 └────────────┬────────────────┘ ▼ 使用 ┌─────────────────────────────┐ │ Base/Models(数据模型层) │ ← ★ 本文档讲的就是这里 │ "数据长什么样" │ │ "有哪些查询方法" │ └────────────┬────────────────┘ ▼ 继承 ┌─────────────────────────────┐ │ Base/Repository(数据库层) │ ← 真正执行 SQL 的地方 │ "SQL 怎么写" │ │ "数据库怎么连" │ └─────────────────────────────┘Models 层就是"数据模具":告诉程序"我这张表有几个字段、每个字段是什么类型、可以做什么查询"。
第1步:Models 层的两种模型——这是理解一切的钥匙
🔑 核心概念:两种数据库,两种模型
Models 层包含两类模型: ┌──────────────────────────────┐ │ 关系型模型(MySQL) │ │ │ │ 继承:BaseModuleDBModel │ │ 特点:精确查询(WHERE column = 值)│ │ 例子:找到 session_id="abc" 的会话│ └──────────────────────────────┘ ┌──────────────────────────────┐ │ 向量模型(Milvus) │ │ │ │ 继承:BaseVDBModel │ │ 特点:语义搜索(找最相似的) │ │ 例子:找到和"电池续航"最像的词 │ └──────────────────────────────┘🏠 生活类比
MySQL(关系型)= 图书馆的索书号系统:
- 每个书架都有编号(A区-1号架-B面-第3层)
- 想要《西游记》?记住编号
A-1-B-3,按编号去找,一定能找到 - 精确、稳定,但只知道"编号=什么"
Milvus(向量型)= 图书馆的智能推荐系统:
- 你说"我喜欢看西游记"
- 系统自动推荐:《三国演义》《水浒传》《红楼梦》……
- 它找的不是"名字一样",是"味道差不多"
- 语义理解,但有一点点模糊性
第2步:关系型模型详解(MySQL)
2.1 标准结构——所有模型都长这样
每个模型都遵循"四件套"格式:
classMyModel(BaseModuleDBModel):# 第1件:表名(告诉程序这个模型对应哪张表)table_alias:ClassVar[str]="my_table"# 第2件:建表 SQL(如果表不存在,框架自动按这个建)create_table_sql:ClassVar[str]=f"""CREATE TABLE `{table_alias}` ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100), age INT, ... )"""# 第3件:字段定义(Pydantic 的 Field,每个字段对应一列)id:Optional[int]=Field(None,description="主键ID")name:str=Field(...,description="名称")# ... = 必填age:int=Field(18,description="年龄")# 18 = 默认值# 第4件:自定义查询方法(按业务需要添加)@classmethoddefget_by_name(cls,name:str):"""按名字查找"""results=cls.find_by(name=name,limit=1)returnresults[0]ifresultselseNone为什么要用 ClassVar?
# 普通字段 = 每个实例有一份# ClassVar 字段 = 整个类只有一份(不实例化也有的字段)classStudent:name:str="李四"# 每次创建学生对象,这个名字都是"李四"school:ClassVar[str]="宜春学院"# 所有学生共享一个学校名# 测试s1=Student()s2=Student()s1.name="张三"print(s1.name)# "张三"print(s2.name)# "李四"(不变)print(Student.school)# "宜春学院"(所有学生共享)table_alias 和 create_table_sql 必须是 ClassVar,因为它们描述的是"表"的共同属性,而不是"某一行数据"的属性。
2.2 BaseLLMSession —— 用户会话模型
类比:微信里的"聊天窗口"。
你在微信里打开一个新聊天 → 就是新建一个 Session 这个聊天窗口里有: - 窗口的唯一ID(session_uuid) - 这个窗口属于哪个用户(user_id) - 窗口的标题(title) - 用的是哪个 AI 模型(model_name) - AI 自动总结的对话摘要(ai_summary) - 内容标签(tags) - 上次看到哪条消息了(last_handle_id) - 创建时间 / 更新时间字段详解:
session_uuid:str# "abc-123-def..." → 窗口的唯一身份证号user_id:str# "user_001" → 属于哪个用户title:str# "新会话" → 窗口标题(用户可改)model_name:str# "qwen-plus" → 用哪个 AI 模型回答ai_summary:str# "用户问了Python的问题" → AI 自动写的摘要tags:str# "编程,Python" → 内容标签last_handle_id:int# 42 → 上次处理到第42条消息created_at:datetime# 2026-01-01 12:00 → 窗口什么时候创建的update_at:datetime# 2026-01-01 14:00 → 最后一次有消息的时间核心方法图解:
┌─────────────────────────────────────────────────┐ │ get_or_create_session(user_id) │ │ │ │ 输入:user_id = "user_001" │ │ │ │ 逻辑: │ │ 这个用户有会话? │ │ ├─ 有 → 返回已有会话 │ │ └─ 没有 → 创建新会话,返回 │ │ │ │ 用途:用户发消息时,确保有一个地方可以存放消息 │ └─────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────┐ │ get_user_last_session(user_id) │ │ │ │ 智能容错逻辑: │ │ │ │ Step1: 传了 session_id 吗? │ │ └─ 传了 → 找这个 ID │ │ └─ 找到了 → 返回 │ │ └─ 没找到 → 继续 Step2 │ │ └─ 没传 或 没找到 → 继续 Step2 │ │ │ │ Step2: 找该用户最近一次会话 │ │ └─ 找到了 → 返回 │ │ └─ 也没有 → 创建新会话,返回 │ │ │ │ 用途:容错——用户填错了 session_id 也尽量给结果 │ └─────────────────────────────────────────────────┘2.3 BaseLLMConversationModel —— 对话记录模型
类比:聊天窗口里的"每一条消息"。
一个微信聊天窗口(Session) │ ├── 第1条消息(Conversation): 用户:"今天天气如何?" AI:"晴天,25度" ├── 第2条消息(Conversation): 用户:"适合出门吗?" AI:"适合,注意防晒" ├── 第3条消息(Conversation): 用户:"推荐个旅游景点?" AI:"建议去西湖..." └── 第4条消息(Conversation): ...字段详解:
id:int# 这条对话记录的身份证号session_id:str# 属于哪个会话窗口user_id:str# 哪个用户问的question:str# 用户原始问题rewrite_question:str# AI 改写后的问题(更清晰、更规范)answer:str# AI 的回答context:str# 上下文(前几轮对话内容,拼在一起)ai_model:str# 用的是哪个 AI 模型stream_mode:str# "0"=普通回答 "1"=流式输出 "2"=带思考过程status:str# "success"=成功 "failed"=失败 "timeout"=超时error_msg:str# 如果失败了,存什么错source:str# 从哪个接口来的("base_chat_api" 等)duration_ms:int# 回答用了多少毫秒(2500 = 2.5秒)created_at:datetime# 什么时候问的特别设计:思考模式(stream_mode=2)
当 AI 需要"先想再答"时:
# 数据库存的 answer 字段实际上存的是这样一段 JSON:answer_raw=""" { "reasoning": "让我分析一下:用户问的是特斯拉续航问题...", "content": "特斯拉 Model Y 标准续航约 525km,长续航版约 550km" } """# 普通方式取答案:answer=conversation.answer# 返回完整 JSON,包含推理过程# 安全方式取答案(推荐):answer=conversation.get_answer# 自动解析 JSON,只返回"content"部分为什么这样设计?
- 推理过程(reasoning)太长、太啰嗦,展示给用户会吓跑人
get_answer属性自动把推理过程藏起来,只显示最终答案- 但数据库里保留了完整信息——如果要审计或调试,还能查到
2.4 BaseKeywordModel —— 关键词模型
类比:专业的业务术语词典。
比如"续航"这个词: 标准名称: 电池续航能力 ← 正式叫法 编码: BATTERY_RANGE ← 程序里用这个(不会写错字) 同义词: 续航,电池,电量,里程 ← 用户可能用这些词 语义描述: 电动车一次充电能跑多远 ← AI 理解用的解释 状态: 1(可用) ← 还没审核的不能参与搜索字段详解:
keyword_name:str# "电池续航能力" ← 标准名称(数据库存的)keyword_code:str# "BATTERY_RANGE" ← 英文编码(程序用这个,不会写错)keyword_desc:str# "车辆一次充电的行驶里程" ← 给人的解释semantic_desc:str# "电动车续航相关概念" ← 给 AI 看的解释keyword_synonyms:str# "续航,电池,电量,里程" ← 这些词都应该映射到标准名称status:int# 1=可用 2=可用但不能同步向量库 0=禁用type:str# "TECH"(技术类)/ "POLICY"(政策类)等source:str# "MANUAL"(人工)/ "AI"(AI生成)/ "IMPORT"(批量导入)为什么关键词要分三种来源?
MANUAL(人工):人工审核过的,绝对准确,但建库慢 AI(AI生成):AI 自动提取的,速度快,但可能有错误,需要人工复核 IMPORT(批量导入):从外部系统批量导进来的,比如从监管文件批量生成 Workflow: 监管文件 → 批量 IMPORT → AI 生成候选关键词 → 人工审核(MANUAL)→ 上线2.5 BaseParamsModel —— 系统参数模型
类比:手机里的"设置"菜单。
手机设置: Wi-Fi 设置 → 开/关 通知设置 → 开/关 亮度设置 → 1~100 的数字 系统参数: 邮件发件人地址 → 配置好的邮箱地址 AI 模型选择 → qwen-plus / deepseek-chat Session 超时时间 → 3600 秒字段详解:
code:str# "email_sender" ← 程序查询时用的唯一标识value:str# "noreply@xx.com" ← 参数的实际值(存成字符串)desc:str# "发件人邮箱地址" ← 给管理员看的说明parent_code:str# "email_config" ← 属于哪个分组type:str# "Email" ← 参数的类型/分组status:int# 1=启用 0=禁用sort_order:int# 1 ← 界面展示时的排序特殊设计:LRU 缓存
参数是"读多写少"的典型场景:
# 没有缓存时:# 用户每问一次 → 查数据库一次 → 数据库压力很大# 有 LRU 缓存后:@lru_cache(maxsize=128)def_cached_get_param(code):returndb.query(code)# 第1次调用:查数据库,把结果记住# 第2次调用(同样的code):直接返回记住的结果,不查数据库!# 第3次、第4次……都是直接返回!# 最大记住 128 个参数# 如果查了 129 个不同的参数,最早记住的那个就被踢出去缓存什么时候会失效?
# 管理员改了一个参数的值BaseParamsModel.update_by_code("email_sender","new@email.com")# 缓存还是旧的!# → 必须手动清除缓存BaseParamsModel.clear_param_cache()# 清除后,下次查询会重新从数据库读,读到新值2.6 BaseEmailModels —— 邮件记录模型
类比:邮件系统的"已发送"文件夹。
你发了一封邮件 → 存在"已发送"里 → 记录着: - 谁发的 - 发给谁 - 什么时候发的 - 发送是否成功 - 如果失败了,重试了几次字段详解:
sender_email:str# "noreply@company.com"receiver_emails:str# "user1@xx,user2@yy,user3@zz" ← 多人用逗号分隔email_type:str# "system"(系统邮件)/ "normal"(普通邮件)subject:str# 邮件标题body:str# 邮件正文(HTML格式)is_html:bool# True=HTML格式 False=纯文本status:str# "pending"(等待发送)/ "success"(成功)/ "failed"(失败)retry_count:int# 失败了重试了几次priority:int# 优先级(数值大的先发)created_at:datetime# 什么时候创建的为什么邮件也要记录?
- 发失败了 → 用户投诉"没收到" → 查邮件记录,确认是否发送过、失败原因
- 审计需求 → 金融系统要求记录所有发出的通知
- 重试机制 → 失败了可以按 retry_count 决定要不要再发
第3步:向量模型详解(Milvus)
3.1 普通查询 vs 向量查询——对比理解
场景:用户问"特斯拉的电池能用多久?" 普通查询(WHERE):精准匹配 SELECT * FROM conversations WHERE question = "特斯拉的电池能用多久?" → 只有完全一样的句子才找到 → 用户换个说法就查不到 ❌ 向量查询(语义搜索):意思相近就算匹配 把问题转成向量 → 找"最相似的"历史对话 → "特斯拉续航如何?" ✅ 找到 → "Model Y 充一次电能跑多远?" ✅ 找到 → "电池寿命多久?" ✅ 找到3.2 向量是什么
把文字变成一串数字:
"特斯拉续航如何" → [0.72, 0.15, 0.88, 0.34, ...] ← 1024维向量 "Model Y 跑多远" → [0.70, 0.18, 0.85, 0.30, ...] ← 1024维向量 这两句话意思相近 → 数字也很接近 ✅ (0.72 vs 0.70,0.15 vs 0.18……) "今天天气不错" → [0.05, 0.91, 0.22, 0.11, ...] ← 1024维向量 话题不同 → 数字差距很大 ❌3.3 VdbLLMConversation —— 对话向量模型
作用:把历史对话存成向量,支持语义检索。
当用户问了一个新问题: "特斯拉电池续航怎么样?" 1. 把这个问题转成向量:Q_vector = [0.72, 0.15, ...] 2. 在 Milvus 里找向量最接近的历史对话: ┌──────────────────────────────────────────┐ │ 历史对话向量库 │ │ │ │ [0.72, 0.15, 0.88, ...] → "特斯拉续航约550km"│ ← 最接近! │ [0.68, 0.20, 0.80, ...] → "Model Y 续航强" │ │ [0.30, 0.85, 0.40, ...] → "今天天气真好" │ ← 差距大 │ │ └──────────────────────────────────────────┘ 3. 返回最相似的几条历史对话 → 作为上下文传给 AI → AI 基于这些上下文回答 → 更精准、更有依据字段详解:
classVdbLLMConversation(BaseVDBModel):collection_alias="llm_conversation"# Milvus 里这个集合的名字# 标量字段(普通字段,存具体值)db_id:str# 对应 MySQL 里这条记录的 idsession_id:str# 属于哪个会话user_id:str# 属于哪个用户question:str# 用户问题rewrite_question:stranswer:str# 密集向量字段(embedding = 语义向量)embedding:list[float]=Field(json_schema_extra={'dim':1024}# 每个向量 1024 个数字)# 稀疏向量字段(BM25 = 关键词向量)content_sparse:list[float]=Field(json_schema_extra={'is_sparse_vector':True,# 这是稀疏向量'bm25_source_field':'question'# 从 question 字段生成})为什么有两种向量?
密集向量(embedding)= 语义理解 → "特斯拉续航如何" ≈ "Model Y 能跑多远" ✅ → 全靠 AI 模型的语义理解能力 稀疏向量(BM25)= 关键词匹配 → "特斯拉" 命中 "特斯拉" ✅ → "续航" 命中 "续航" ✅ → 不管语义,只管关键词是否出现 两者混合搜索(RRF)= 语义 + 关键词全都要 ✅混合搜索的代码逻辑:
results=VdbLLMConversation.get_n_high_similarity_item(question="特斯拉电池续航如何",# 用户的问题user_id="user_001",# 只在该用户的历史里找n=5# 返回最相似的 5 条)# 内部流程:# 1. 把问题转成 1024 维向量(embedding)# 2. 同时生成 BM25 稀疏向量(从关键词生成)# 3. 密集向量搜索(权重 0.7)→ 得到一批结果# 4. 稀疏向量搜索(权重 0.3)→ 得到一批结果# 5. RRF 融合排名 → 综合得分最高的 5 条返回3.4 VDBLLMKeyword —— 关键词向量模型
作用:关键词的向量化存储,支持自然语言找相似关键词。
用户问:"什么是续航?" 1. 把问题转成向量 2. 找最接近的关键词向量 结果可能返回: - "电池续航能力"(标准名称) - "行驶里程"(同义词) - "充电一次能跑多远"(语义相近) → AI 可以根据返回的关键词,更准确地理解用户想问什么两种搜索方式的权重差异:
| 模型 | 密集权重 | 稀疏权重 | 为什么 |
|---|---|---|---|
| VdbLLMConversation | 0.7 | 0.3 | 对话更看重语义理解(说法多样) |
| VDBLLMKeyword | 0.3 | 0.7 | 关键词更看重精确匹配(“续航"就是要找"续航”) |
为什么关键词用更高的 BM25 权重?
关键词 = 专业术语 = 必须精确匹配 比如:监管文件里有"抵质押率"这个词 → 用户问"抵押率"(口语化) → BM25 可能找到"抵质押率"(因为有重叠的字) → 但 embedding 可能认为"抵押率"和"抵质押率"差距较大 所以关键词表要用高 BM25 权重:确保专有名词能被发现第4步:模型之间的关系——完整数据流
4.1 实体关系图
用户(user_001) │ ├──▶ BaseLLMSession(会话窗口) │ │ │ ├── session_uuid = "abc123" │ ├── title = "特斯拉问题讨论" │ └── model_name = "qwen-plus" │ │ │ └──▶ 多条 BaseLLMConversationModel(对话记录) │ │ │ ├── question = "Model Y 续航多少?" │ ├── answer = "约 550km" │ └── status = "success" │ │ │ └── 同时存入两个地方: │ │ │ ├──▶ Milvus: VdbLLMConversation │ │ embedding = [0.72, 0.15, ...] │ │ 用于:下次语义相似度检索 │ │ │ └──▶ MySQL: BaseLLMConversationModel │ 用于:分页查询、历史记录展示 │ └──▶ BaseKeywordModel(关键词) │ ├── keyword_name = "电池续航能力" ├── keyword_code = "BATTERY_RANGE" └── keyword_synonyms = "续航,电池,电量" │ └── 同时存入两个地方: │ ├──▶ Milvus: VDBLLMKeyword │ embedding = [0.80, 0.12, ...] │ 用于:自然语言找相关关键词 │ └──▶ MySQL: BaseKeywordModel 用于:关键词精确查找、管理页面4.2 完整数据流转(从用户发问到得到答案)
Step 1: 用户发消息 "特斯拉电池续航怎么样?" │ ▼ Step 2: 找/建会话 BaseLLMSession.get_or_create_session(user_id="user_001") │ ▼ Step 3: 创建对话记录 BaseLLMConversationModel.create( session_id="abc123", question="特斯拉电池续航怎么样?" ) │ ▼ Step 4: 判断是否需要上下文(语义相似度搜索) ┌─────────────────────────────────┐ │ 如果这是第1次问(没有历史) │ │ → 跳过搜索,直接问 AI │ │ │ │ 如果不是第1次问 │ │ → VdbLLMConversation. │ │ get_n_high_similarity_item() │ │ → 找到最相似的3条历史对话 │ │ → 拼成上下文一起发给 AI │ └─────────────────────────────────┘ │ ▼ Step 5: 调 AI llm.invoke(context + "特斯拉电池续航怎么样?") │ ▼ Step 6: 保存回答 conversation.answer = AI 的回答 conversation.status = "success" conversation.save() ← MySQL │ ▼ Step 7: 同步到向量库(异步) VdbLLMConversation.embedding( text=question + answer ← 生成向量 ).batch_insert() ← 存入 Milvus │ ▼ Step 8: 返回回答给用户第5步:Pydantic Field 详解
为什么要用 Pydantic
普通 Python 字典 vs Pydantic 模型:
# ❌ 普通字典——字段名可能写错、类型可能填错user={"name":"张三","age":"25岁","emial":"zhang@email.com"}# emial 拼错了!age 是字符串不是整数!但 Python 不报错!# ✅ Pydantic 模型——自动校验classUser(BaseModel):name:strage:intemail:struser=User(name="张三",age="25岁",email="zhang@email.com")# ❌ age="25岁" → 不是 int 类型 → Pydantic 自动报错!# ❌ emial → 字段名不对 → Pydantic 自动报错!Field 的三种写法
frompydanticimportFieldclassStudent(BaseModel):# 方式1: Field(None) = 可选字段,可以是 Noneid:Optional[int]=Field(None,description="主键ID")# 方式2: Field(...) = 必填字段,不能为空name:str=Field(...,description="学生姓名")# 方式3: Field(默认值) = 有默认值,不传就用默认值age:int=Field(18,description="学生年龄")gender:str=Field("未知",description="性别")| 写法 | 代码示例 | 传参测试 |
|---|---|---|
| Field(None) | id: Optional[int] = Field(None) | Student()✅Student(id=None)✅ |
| Field(…) | name: str = Field(...) | Student()❌ 必须传 name |
| Field(默认值) | age: int = Field(18) | Student(name="张三")✅ age 自动=18 |
第6步:状态属性设计模式
问题:什么是"魔法数字"?
# ❌ 魔法数字——status == 1 是什么意思?ifsession.status==1:print("会话可用")# 1 = 什么? 2 = 什么? 时间久了谁记得住?解决方案:用属性包装
classBaseKeywordModel:status:int# 数据库里存的:0=禁用 1=可用 2=可用但不同步# ✅ 用属性把数字变成语义@propertydefis_active(self)->bool:"""是否可用?True = status 为 1 或 2"""returnself.statusin[1,2]@propertydefcan_sync_to_vdb(self)->bool:"""能否同步到向量库?True = 只有 status 为 1 时"""returnself.status==1使用对比:
# ❌ 魔法数字写法ifkeyword.status==1orkeyword.status==2:print("可用")# ✅ 属性写法ifkeyword.is_active:print("可用")# 更清晰,且如果业务规则变了(比如变成 status==1 才能用)# 只需要改属性内部逻辑,不用改所有调用方第7步:FAQ 深度解答
Q1:MySQL 和 Milvus 同时存数据,数据会不一致吗?
答:会有时间差,但不是"不一致"。
Save 操作时序: MySQL.save() ──────▶ 立即返回(同步) │ │ 同时 ▼ Milvus.batch_insert() ──▶ 异步执行(可能有几毫秒延迟) MySQL 是"正本",Milvus 是"索引": - MySQL 存的是原始数据 → 必须准确 - Milvus 存的是向量 → 丢了可以从 MySQL 重建 如果 Milvus 插入失败了: → MySQL 已有数据 → 可以手动触发一次"全量同步"从 MySQL 重建 Milvus 索引Q2:向量维度(dim=1024)是怎么来的?
答:由 AI 模型决定。
不同的 embedding 模型输出不同维度的向量: 阿里云百炼(text-embedding-v3): 1024 维 ✅ 本项目用这个 OpenAI text-embedding-3-small: 1536 维 OpenAI text-embedding-3-large: 3072 维 MiniMax: 1536 维 Milvus 的向量字段必须声明维度: embedding: list[float] = Field(json_schema_extra={'dim': 1024}) → 如果存进去的向量不是 1024 维 → 报错 → 搜索时传的向量不是 1024 维 → 报错Q3:LRU 缓存在多进程环境下会怎样?
单进程(只有一个 Python 程序在跑): Process A └── _cached_get_param() 缓存存在 A 的内存里 ✅ 正常 多进程(Nginx 起了 4 个 worker): Process A ←── 有自己的缓存 ←── Process B ←── 有自己的缓存 └── _cached_get_param() └── _cached_get_param() ❌ 各自为政,缓存不共享! 场景:管理员改了参数 → 管理员更新了数据库里的值 → 清缓存只清了 Process A 的 → Process B/C/D 还是旧的缓存 ❌ → 不同用户访问,看到的值不一样! 解决方案:换 Redis 缓存(多进程共享) ┌─────────┐ │ Redis │ ← 所有进程共享同一个缓存 │ 缓存层 │ └────┬────┘ │ ┌───┴───┬────┐ ▼ ▼ ▼ ProcessA ProcessB ProcessCQ4:向量搜索的权重(0.7/0.3)怎么调出来的?
答:经验 + 测试,没有标准答案。
调参过程: Step 1: 定初始值 对话场景 → 参考论文和业界经验 → [0.7, 0.3] Step 2: 建测试集 收集 200 条真实用户问题 + 人工标注的"正确答案" Step 3: 批量测试 用不同权重组合搜索 → 看 Recall@K、MRR 等指标 Step 4: 选最优 [0.7, 0.3] → Recall@5 = 0.85 ← 当前选这个 [0.5, 0.5] → Recall@5 = 0.82 [0.3, 0.7] → Recall@5 = 0.78 Step 5: 上线观察 持续收集用户反馈,命中率低了就再调Q5:为什么不把所有查询都换成向量搜索?
向量搜索很强,但不是万能的: 场景 A:我知道 session_id="abc123",想查这条记录 → WHERE session_id = 'abc123' ✅ 精确查 → 快准狠 → 向量搜索 ❌ "最像 abc123 的记录"——答非所问 场景 B:想查 2026年7月1日 创建的所有会话 → WHERE created_at >= '2026-07-01' ✅ 范围查 → 数据库索引支持 → 向量搜索 ❌ 按时间范围检索,数据库 B-Tree 最拿手 结论: 精确匹配/范围查询 → MySQL ✅ 语义相似度搜索 → Milvus ✅ 两者配合使用,各取所长一句话总结
Models 层 = 系统的"数据模具" ┌─────────────────────────────────────────┐ │ │ │ 关系型模型(BaseModuleDBModel → MySQL) │ │ → 精确查询:WHERE / 分页 / 统计 │ │ → 类比:图书馆的索书号 │ │ │ │ 向量模型(BaseVDBModel → Milvus) │ │ → 语义搜索:找"最相似的" │ │ → 类比:图书馆的智能推荐 │ │ │ │ 两者配合 = 查得快 + 理解得准 │ │ │ └─────────────────────────────────────────┘