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 标准结构------所有模型都长这样

每个模型都遵循"四件套"格式:

python 复制代码
class MyModel(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件:自定义查询方法(按业务需要添加)
    @classmethod
    def get_by_name(cls, name: str):
        """按名字查找"""
        results = cls.find_by(name=name, limit=1)
        return results[0] if results else None

为什么要用 ClassVar?

python 复制代码
# 普通字段 = 每个实例有一份
# ClassVar 字段 = 整个类只有一份(不实例化也有的字段)

class Student:
    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)
    - 创建时间 / 更新时间

字段详解

python 复制代码
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): ...

字段详解

python 复制代码
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 需要"先想再答"时:

python 复制代码
# 数据库存的 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(可用)              ← 还没审核的不能参与搜索

字段详解

python 复制代码
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 秒

字段详解

python 复制代码
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 缓存

参数是"读多写少"的典型场景:

python 复制代码
# 没有缓存时:
# 用户每问一次 → 查数据库一次 → 数据库压力很大

# 有 LRU 缓存后:
@lru_cache(maxsize=128)
def _cached_get_param(code):
    return db.query(code)   # 第1次调用:查数据库,把结果记住

# 第2次调用(同样的code):直接返回记住的结果,不查数据库!
# 第3次、第4次......都是直接返回!

# 最大记住 128 个参数
# 如果查了 129 个不同的参数,最早记住的那个就被踢出去

缓存什么时候会失效?

python 复制代码
# 管理员改了一个参数的值
BaseParamsModel.update_by_code("email_sender", "new@email.com")

# 缓存还是旧的!
# → 必须手动清除缓存
BaseParamsModel.clear_param_cache()

# 清除后,下次查询会重新从数据库读,读到新值

2.6 BaseEmailModels ------ 邮件记录模型

类比:邮件系统的"已发送"文件夹。

复制代码
你发了一封邮件 → 存在"已发送"里 → 记录着:
  - 谁发的
  - 发给谁
  - 什么时候发的
  - 发送是否成功
  - 如果失败了,重试了几次

字段详解

python 复制代码
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 基于这些上下文回答 → 更精准、更有依据

字段详解

python 复制代码
class VdbLLMConversation(BaseVDBModel):
    collection_alias = "llm_conversation"  # Milvus 里这个集合的名字

    # 标量字段(普通字段,存具体值)
    db_id: str           # 对应 MySQL 里这条记录的 id
    session_id: str      # 属于哪个会话
    user_id: str         # 属于哪个用户
    question: str        # 用户问题
    rewrite_question: str
    answer: 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)= 语义 + 关键词全都要 ✅

混合搜索的代码逻辑

python 复制代码
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 模型:

python 复制代码
# ❌ 普通字典------字段名可能写错、类型可能填错
user = {"name": "张三", "age": "25岁", "emial": "zhang@email.com"}
# emial 拼错了!age 是字符串不是整数!但 Python 不报错!

# ✅ Pydantic 模型------自动校验
class User(BaseModel):
    name: str
    age: int
    email: str

user = User(name="张三", age="25岁", email="zhang@email.com")
# ❌ age="25岁" → 不是 int 类型 → Pydantic 自动报错!
# ❌ emial → 字段名不对 → Pydantic 自动报错!

Field 的三种写法

python 复制代码
from pydantic import Field

class Student(BaseModel):
    # 方式1: Field(None) = 可选字段,可以是 None
    id: 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步:状态属性设计模式

问题:什么是"魔法数字"?

python 复制代码
# ❌ 魔法数字------status == 1 是什么意思?
if session.status == 1:
    print("会话可用")

# 1 = 什么? 2 = 什么? 时间久了谁记得住?

解决方案:用属性包装

python 复制代码
class BaseKeywordModel:
    status: int  # 数据库里存的:0=禁用 1=可用 2=可用但不同步

    # ✅ 用属性把数字变成语义
    @property
    def is_active(self) -> bool:
        """是否可用?True = status 为 1 或 2"""
        return self.status in [1, 2]

    @property
    def can_sync_to_vdb(self) -> bool:
        """能否同步到向量库?True = 只有 status 为 1 时"""
        return self.status == 1

使用对比

python 复制代码
# ❌ 魔法数字写法
if keyword.status == 1 or keyword.status == 2:
    print("可用")

# ✅ 属性写法
if keyword.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 ProcessC

Q4:向量搜索的权重(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)        │
│    → 语义搜索:找"最相似的"              │
│    → 类比:图书馆的智能推荐               │
│                                         │
│  两者配合 = 查得快 + 理解得准             │
│                                         │
└─────────────────────────────────────────┘
相关推荐
Java小白笔记1 小时前
Codex 桌面应用设置功能完全指南
人工智能·opencv·目标检测·机器学习·计算机视觉·数据挖掘·语音识别
EterNity_TiMe_1 小时前
别让告警变成噪音:用Prometheus和Alertmanager搭一套可远程查看的监控系统
数据库·人工智能·ai·postgresql·prometheus·cpolar
世界很奇妙塔1 小时前
大模型轻量化技术攻坚:从千亿参数到端侧部署,重构AI落地底层路径
人工智能·重构
z小猫不吃鱼1 小时前
模型剪枝经典论文精读:Rethinking the Value of Network Pruning
人工智能·深度学习·计算机视觉
阿尔泰科技官方2 小时前
阿尔泰科技——NVH/振动/声学测试专用解决方案
人工智能·信号采集·仪器仪表·数据采集卡·采样率
147API2 小时前
Claude global workspace 研究给智能体测试提了一个醒
人工智能·算法·机器学习
闲猫2 小时前
深入理解 Skills:从 API 调用到智能体行为封装
人工智能·python·langchain
葫三生2 小时前
《论三生原理》与模糊数学的关联、异同、互补关系?
人工智能·科技·算法·机器学习·开源
刘一说2 小时前
AI科技热点日报 | 2026年7月7日
人工智能·科技