助手 · 一句话如何变成查真实业务(翻译层 ≠ 查询层 ≠ 知识层)
日期: 2026-08-31 | 作者: sunxin(+ Cursor AI) 关联决策: ADR-057 · ADR-047 关联代码:
ILlmPort.extractSearchIntent·ActivitySearchIntent·AssistantApplicationService.toCriteria·searchActivitiesAdvanced·VectorKnowledgeRetrievalAdapter姊妹篇: 意图识别是怎么做到的(Prompt / JSON 细节)· RAG 向量检索新手科普 · Java 编排 vs 模型编排 定位:把「大模型是不是直接查我的库」拆开。读完应能独立画出:人话 → JSON 槽位 → 你原来的 SQL。
TL;DR
- 智能助手没有让大模型连上你的 MySQL。模型不会写
SELECT * FROM activity。 - 它只做一件 Java 字符串匹配做不好的事:把口语收成固定字段(意图 + 地点 + 时间 + 预算)。
- 查数的还是你写过的方法,和 App 搜索框最终打到同一类查询。
- 向量模型是另一条腿:用来在攻略/规则文档里找相似段落,不是用来搜活动表。
1. 你卡住的那句话
「我想去赛里木湖,准备玩 3 天,费用 2000 以内。」
纯 Java / 字符串:识别不到用户想干嘛,也没法稳定变成「标题含赛里木湖、时间窗、价格 ≤ 2000」。 但大模型也不是直接把数据查出来,代码里肯定还有 SQL。这块糊涂。
糊涂的点其实是把三层看成了一层。
用户一句话
│
├─① 翻译层(大模型)→ JSON 槽位 「听懂」
├─② 查询层(Java + 原有 SQL)→ 行 「查真数」
└─③ 知识层(向量检索 + 再生成)→ 引用 「查文档」① 和 ③ 都用到「模型」,但不是同一个模型、也不是同一件事。② 全程没有「用模型查表」。
2. 为什么纯 Java 字符串做不好 ①
同一需求,人会说成完全不同的字:
| 说法 | 字符串规则容易挂在哪 |
|---|---|
| 赛里木湖 / 赛湖 / 赛里木 | 别名表可以补,永远补不完口语 |
| 玩 3 天 / 这周末出发待三天 / 7 天内 | 「3 天」是行程长度还是时间窗?要结合整句 |
| 2000 以内 / 别超过两千 / 预算两千块 | 单位、中文数字 |
| 最近想去那边看看,别太贵 | 「那边」要靠上轮会话,不是本句字面 |
规则引擎能覆盖演示句,一上真用户就炸。大模型适合的正是这一步:非结构化 → 结构化。
本项目里这一步的契约是 ActivitySearchIntent,大致长这样(真模型走 ILlmPort.extractSearchIntent):
{
"intent": "SEARCH_ACTIVITIES",
"destinationKeywords": ["赛里木湖"],
"dateStart": "2026-08-31",
"dateEnd": "2026-09-03",
"maxPrice": 2000,
"days": 3
}「玩 3 天」有时会被抽成 days(做计划用),有时抽成时间窗。抽错了是意图评测的事(eval-cases.json),不是 SQL 写错。Java 接到 JSON 之后,不再猜这句话的意思。
离线/无 Key 时 MockLlmAdapter 用关键词硬判,那是用代码冒充翻译层,方便测后面的查询层;生产真模型才扛得住口语。
3. 查询层:你熟悉的 Java,一次都没换成模型
AssistantApplicationService.toCriteria 做的就是把 JSON 填进早已存在的搜索条件:
| 槽位 | 进哪个查询字段 |
|---|---|
destinationKeywords[0] | keyword(活动搜索关键词) |
dateStart / dateEnd | startTimeFrom / startTimeTo |
maxPrice | maxCost |
city / province | 城市 / 省 |
然后调用 searchActivitiesAdvanced——和 App 里筛活动是同一条业务腿,底层还是 JPA/SQL,活动 id、价格、时间都以库为准。模型看不到表,也不许编一条活动。
所以:
没有助手:用户点筛选,你的方法查库。
有助手:用户说话 → 模型填筛选表单 → 还是你的方法查库。
「一个问题写一个方法」并没有消失。消失的是「为每一种口语再写一个 Controller」。方法仍然按结构化条件来,只是表单改由模型填。
退款金额、积分是否到账,同理:将来也是「模型听懂这是查我的订单」→ Java 调既有退款查询 → 把数字说给人听。数字不来自知识库、不来自模型记忆。
4. 向量模型是 ③,不是给活动表用的
Embedding(本项目通义 text-embedding-v4)干的是:把一段文字变成一串数字,意思近的段落数字也近。
用来:用户问「赛里木湖海拔多少 / 注意事项」,在 assistant_kb_doc 切好的段落里找最像的几块,喂给另一个生成模型,要求带「来源:」。找不到或分数太低就拒答。
不用来:扫描 activity 表。活动是结构化库存,用 SQL 的 keyword + 时间 + 价格 更准、可审计。把活动简介灌进向量当搜索引擎,会丢掉价格时间过滤,也更容易搜到过期局。
对照:
| 找活动(①→②) | 问攻略/规则(①→③) | |
|---|---|---|
| 模型 1 | 意图模型:吐 JSON | 同一意图模型:判成 KNOWLEDGE_QA |
| 检索 | 无向量,SQL | Embedding 余弦 + 词面分 |
| 模型 2 | 通常不用;Java 拼「找到 N 个」 | 生成模型:只根据片段说话 |
| 真相来源 | 活动表 | 你维护的 md/知识库行 |
「智能」在 ① 和 ③ 的语言能力;业务正确在 ② 和知识库正文。
5. 同一句「赛里木湖」会走哪条腿
| 用户原话 | intent | 之后 |
|---|---|---|
| 想去赛里木湖玩 3 天,2000 以内 | SEARCH_ACTIVITIES | toCriteria → 查活动 |
| 赛里木湖海拔多少 / 怎么去 | KNOWLEDGE_QA | 向量检索攻略;成功可挂活动卡(W6) |
| 帮我规划赛里木湖 3 天 | MAKE_TRAVEL_PLAN | Java 先查真活动+天气,再让模型排日程(默认关) |
| 我这次报名能退多少 | 现在 OUT_OF_SCOPE | 将来应是查订单工具,不是攻略 RAG |
| 活动取消怎么退(问政策) | 现在常被拒;有 kb:policy 后应 KNOWLEDGE_QA | 只引用条文,不算你的金额 |
分诊错了(把找活动判成攻略,或反过来),后面再准也是错腿。所以评测有两轨:eval-cases 管意图,rag-cases 管检索。
6. 和企业知识库的关系(电子秤例子只为建立直觉)
企业库难,是因为文档类型多、谁能看哪份、PDF/表格怎么切、过期、法务引用。但每一刀仍是这三层:
- 听懂员工问的是「查制度」还是「查我的工单」。
- 工单/库存/退款走 ERP、电商后台、你们自己的 Java。
- 制度/规格书/App 玩法说明走知识库 + 向量。
出海电子秤公司可以按部门切:产品结构文档、芯片规格书、OEM 工艺、App 玩法、平台售后政策——每一刀先 Markdown + 引用 + 拒答,不要第一天就上全公司权限中台。TourMate 助手就是其中一刀:出行活动 + 目的地/规则说明书。细则见 ADR-057。
7. 教学收获
- 模型 = 翻译官 +(可选)文书;Java = 仓库管理员。翻译官不能进库房自己点货。
- 向量相似 ≠ SQL
WHERE。一个比段落意思,一个比列条件。 - 「一个问题一个方法」还在,只是方法入口从「筛选表单」变成了「槽位 JSON」。
- 觉得助手神奇时,打开审计:先看
intentType,再看是活动hitCount还是rag_top1_source——立刻知道走的是 ② 还是 ③。