应急处理知识库:关键词匹配与智能问答
·
本文基于「灶台导航」小程序的应急处理功能,分享知识库的设计、关键词匹配算法、上下文传递等实现思路。
一、功能需求
在烹饪过程中,用户可能遇到紧急情况(如烫伤、油锅起火),需要快速获取处理方法。
用户提问:"油锅起火了怎么办?"
│
▼
关键词匹配 / AI 理解
│
▼
返回应急处理步骤
核心需求:
- 快速响应(秒级)
- 准确匹配用户意图
- 结构化展示处理步骤
- 支持追问(上下文)
二、数据结构设计
2.1 emergencies 集合
// database/collections/emergencies.json
{
"_id": "emerg_001",
"category": "烫伤", // 分类
"keywords": ["烫伤", "烫到", "被烫", "热水烫"], // 关键词
"title": "烫伤应急处理", // 标题
"severity": "warning", // 严重程度:warning/danger
"steps": [ // 处理步骤
{
"order": 1,
"content": "立即用流动的冷水冲洗烫伤部位",
"duration": "10-15分钟",
"important": true
},
{
"order": 2,
"content": "不要涂抹牙膏、酱油等偏方",
"important": true
},
{
"order": 3,
"content": "如有水泡,不要自行刺破",
"important": false
},
{
"order": 4,
"content": "严重烫伤请立即就医",
"important": true
}
],
"warnings": [ // 注意事项
"不要直接用冰块冷敷",
"不要撕扯粘在伤口的衣物"
],
"relatedQuestions": [ // 相关问题
"烫伤后可以涂什么药膏?",
"如何判断烫伤严重程度?"
],
"createTime": "2026-03-01"
}
2.2 分类设计
| 分类 | 示例关键词 | 严重程度 |
|---|---|---|
| 烫伤 | 烫伤、被烫、热水 | warning |
| 烧伤 | 烧伤、火烫 | danger |
| 油锅起火 | 油锅、起火、着火 | danger |
| 刀伤 | 切到手、刀伤、流血 | warning |
| 食物中毒 | 中毒、呕吐、腹泻 | danger |
| 异物卡喉 | 卡住、噎住、喉咙 | danger |
三、关键词匹配实现
3.1 简单匹配
/**
* 简单关键词匹配
*/
async function simpleMatch(message) {
const db = cloud.database()
// 获取所有应急知识
const res = await db.collection('emergencies').get()
for (const item of res.data) {
for (const keyword of item.keywords) {
if (message.includes(keyword)) {
return item
}
}
}
return null
}
3.2 加权匹配
/**
* 加权关键词匹配
*/
async function weightedMatch(message) {
const db = cloud.database()
const res = await db.collection('emergencies').get()
let bestMatch = null
let bestScore = 0
for (const item of res.data) {
let score = 0
// 关键词匹配
for (const keyword of item.keywords) {
if (message.includes(keyword)) {
// 完全匹配加高分
if (message === keyword) {
score += 10
} else {
score += keyword.length // 按关键词长度加分
}
}
}
// 分类匹配(额外加分)
if (message.includes(item.category)) {
score += 5
}
if (score > bestScore) {
bestScore = score
bestMatch = item
}
}
// 设置阈值,避免误匹配
if (bestScore < 3) {
return null
}
return bestMatch
}
3.3 正则表达式匹配
/**
* 正则匹配
*/
async function regexMatch(message) {
const db = cloud.database()
const res = await db.collection('emergencies').get()
for (const item of res.data) {
// 构建正则模式
const patterns = item.keywords.map(k => {
// 处理特殊字符
const escaped = k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
return escaped
})
const regex = new RegExp(patterns.join('|'), 'i')
if (regex.test(message)) {
return item
}
}
return null
}
3.4 多条件匹配
/**
* 多条件综合匹配
*/
async function comprehensiveMatch(message) {
const db = cloud.database()
const res = await db.collection('emergencies').get()
const results = []
for (const item of res.data) {
let score = 0
const matchedKeywords = []
// 1. 关键词匹配
for (const keyword of item.keywords) {
if (message.includes(keyword)) {
score += keyword.length * 2
matchedKeywords.push(keyword)
}
}
// 2. 分类匹配
if (message.includes(item.category)) {
score += 10
}
// 3. 标题匹配
if (message.includes(item.title)) {
score += 15
}
// 4. 同义词扩展
const synonyms = getSynonyms(message)
for (const syn of synonyms) {
if (item.keywords.includes(syn)) {
score += syn.length
}
}
if (score > 0) {
results.push({
item,
score,
matchedKeywords
})
}
}
// 按分数排序
results.sort((a, b) => b.score - a.score)
// 返回最高分的结果
if (results.length > 0 && results[0].score >= 3) {
return results[0].item
}
return null
}
/**
* 获取同义词
*/
function getSynonyms(word) {
const synonymMap = {
'烫': ['烫伤', '被烫', '热'],
'火': ['起火', '着火', '燃烧'],
'刀': ['切', '割', '划'],
'中毒': ['食物中毒', '拉肚子', '呕吐']
}
const result = []
for (const [key, values] of Object.entries(synonymMap)) {
if (word.includes(key)) {
result.push(...values)
}
}
return result
}
四、云函数实现
4.1 emergencyHandler 云函数
由于紧急情况在项目初期用到极少,而且大部分情况都大同小异比如盐放多了类似的,所有我们采用云数据库做小数据量的语义匹配。
// cloudfunctions/emergencyHandler/index.js
const cloud = require('wx-server-sdk')
const { success, serverError } = require('../utils/response')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
exports.main = async (event, context) => {
const { action, message, emergencyId } = event
try {
switch (action) {
case 'match':
return await handleMatch(message)
case 'detail':
return await handleDetail(emergencyId)
case 'search':
return await handleSearch(message)
case 'related':
return await handleRelated(emergencyId)
default:
return await handleMatch(message)
}
} catch (err) {
console.error('应急处理失败:', err)
return serverError(err.message)
}
}
/**
* 匹配应急知识
*/
async function handleMatch(message) {
if (!message) {
return success({ match: null })
}
// 1. 尝试关键词匹配
const match = await comprehensiveMatch(message)
if (match) {
return success({
match: match,
confidence: 'high',
source: 'keyword'
})
}
// 2. 降级:返回常见问题列表
const hotRes = await db.collection('emergencies')
.limit(5)
.get()
return success({
match: null,
suggestions: hotRes.data.map(item => ({
id: item._id,
title: item.title,
category: item.category
})),
message: '未找到匹配的应急知识,以下是常见问题'
})
}
/**
* 获取详情
*/
async function handleDetail(emergencyId) {
if (!emergencyId) {
return success({ detail: null })
}
const res = await db.collection('emergencies')
.doc(emergencyId)
.get()
return success({
detail: res.data
})
}
/**
* 搜索
*/
async function handleSearch(keyword) {
if (!keyword) {
const res = await db.collection('emergencies')
.limit(20)
.get()
return success({ list: res.data })
}
const res = await db.collection('emergencies')
.where({
$or: [
{ title: db.RegExp({ regexp: keyword, options: 'i' }) },
{ category: db.RegExp({ regexp: keyword, options: 'i' }) },
{ keywords: keyword }
]
})
.get()
return success({
list: res.data,
keyword: keyword
})
}
/**
* 获取相关应急知识
*/
async function handleRelated(emergencyId) {
// 获取当前项的分类
const currentRes = await db.collection('emergencies')
.doc(emergencyId)
.get()
if (!currentRes.data) {
return success({ related: [] })
}
const category = currentRes.data.category
// 获取同分类的其他项
const res = await db.collection('emergencies')
.where({
category: category,
_id: db.command.neq(emergencyId)
})
.limit(5)
.get()
return success({
related: res.data
})
}
/**
* 综合匹配
*/
async function comprehensiveMatch(message) {
const res = await db.collection('emergencies').get()
let bestMatch = null
let bestScore = 0
for (const item of res.data) {
let score = 0
// 关键词匹配
for (const keyword of item.keywords) {
if (message.includes(keyword)) {
score += keyword.length * 2
}
}
// 分类匹配
if (message.includes(item.category)) {
score += 10
}
if (score > bestScore) {
bestScore = score
bestMatch = item
}
}
return bestScore >= 3 ? bestMatch : null
}
五、上下文传递
5.1 对话上下文
当用户追问时,需要保持上下文:
用户:油锅起火怎么办?
AI:油锅起火处理方法:1. 立即关闭火源...
用户:可以用水浇吗?
AI:不可以!用水浇油火会导致油溅射... ← 需要理解上下文
5.2 上下文实现
// cloudfunctions/emergencyHandler/index.js
// 存储对话上下文
const sessionCache = new Map()
exports.main = async (event, context) => {
const { action, message, sessionId } = event
// 获取上下文
let sessionContext = null
if (sessionId) {
sessionContext = sessionCache.get(sessionId)
}
try {
switch (action) {
case 'match':
return await handleMatchWithContext(message, sessionContext)
// ...
}
} catch (err) {
// ...
}
}
/**
* 带上下文的匹配
*/
async function handleMatchWithContext(message, context) {
// 如果有上下文,先判断是否为追问
if (context && context.lastMatch) {
const isFollowUp = checkFollowUp(message, context.lastMatch)
if (isFollowUp) {
// 追问:基于上次匹配的内容回答
const followUpAnswer = await handleFollowUp(message, context.lastMatch)
return success(followUpAnswer)
}
}
// 新问题:进行匹配
const match = await comprehensiveMatch(message)
if (match) {
// 保存上下文
const newSessionId = `session_${Date.now()}`
sessionCache.set(newSessionId, {
lastMatch: match,
lastMessage: message,
timestamp: Date.now()
})
return success({
match: match,
sessionId: newSessionId,
confidence: 'high'
})
}
// 未匹配
return success({
match: null,
suggestions: await getHotEmergencies()
})
}
/**
* 检查是否为追问
*/
function checkFollowUp(message, lastMatch) {
// 追问关键词
const followUpIndicators = [
'可以', '能', '要', '需要', '然后', '之后',
'呢', '吗', '什么', '怎么', '为什么'
]
for (const indicator of followUpIndicators) {
if (message.includes(indicator)) {
return true
}
}
return false
}
/**
* 处理追问
*/
async function handleFollowUp(message, lastMatch) {
// 预设追问回复
const followUpAnswers = {
'水': {
type: 'warning',
content: '不可以用水浇灭油火!水会使油溅射,火势更大。'
},
'盖': {
type: 'success',
content: '可以用锅盖盖住,隔绝氧气灭火。'
},
'灭火器': {
type: 'success',
content: '可以使用干粉灭火器,但不能用水基灭火器。'
}
}
// 检查追问内容
for (const [key, answer] of Object.entries(followUpAnswers)) {
if (message.includes(key)) {
return {
type: 'follow_up',
relatedTo: lastMatch.title,
answer: answer
}
}
}
// 无法回答的追问
return {
type: 'unknown',
relatedTo: lastMatch.title,
answer: {
type: 'info',
content: '这个问题建议您查看详细说明或咨询专业人士。'
}
}
}
六、前端实现
6.1 应急页面
<!-- pages/emergency/emergency.wxml -->
<page-header title="应急处理" showBack="{{true}}" />
<view class="emergency-container">
<!-- 搜索区 -->
<view class="search-box">
<input
class="search-input"
placeholder="描述您遇到的问题..."
value="{{inputValue}}"
bindinput="onInput"
bindconfirm="onSearch"
/>
<button class="search-btn" bindtap="onSearch">搜索</button>
</view>
<!-- 匹配结果 -->
<view class="match-result" wx:if="{{matchResult}}">
<view class="result-header {{matchResult.severity}}">
<text class="result-title">{{matchResult.title}}</text>
<text class="result-category">{{matchResult.category}}</text>
</view>
<view class="result-steps">
<view class="step-item" wx:for="{{matchResult.steps}}" wx:key="order">
<view class="step-number">{{item.order}}</view>
<view class="step-content">
<text class="{{item.important ? 'important' : ''}}">{{item.content}}</text>
<text wx:if="{{item.duration}}" class="step-duration">约 {{item.duration}}</text>
</view>
</view>
</view>
<view class="result-warnings" wx:if="{{matchResult.warnings.length > 0}}">
<text class="warning-title">⚠️ 注意事项</text>
<view class="warning-item" wx:for="{{matchResult.warnings}}" wx:key="index">
<text>• {{item}}</text>
</view>
</view>
<!-- 相关问题 -->
<view class="related-questions" wx:if="{{matchResult.relatedQuestions.length > 0}}">
<text class="related-title">相关问题</text>
<view
class="related-item"
wx:for="{{matchResult.relatedQuestions}}"
wx:key="index"
bindtap="onRelatedTap"
data-question="{{item}}"
>
{{item}}
</view>
</view>
</view>
<!-- 建议 -->
<view class="suggestions" wx:if="{{suggestions.length > 0 && !matchResult}}">
<text class="suggest-title">常见问题</text>
<view
class="suggest-item"
wx:for="{{suggestions}}"
wx:key="_id"
bindtap="onSuggestTap"
data-id="{{item.id}}"
>
<text class="suggest-category">{{item.category}}</text>
<text class="suggest-title">{{item.title}}</text>
</view>
</view>
</view>
6.2 页面逻辑
// pages/emergency/emergency.js
const { callFunction } = require('../../utils/cloud')
Page({
data: {
inputValue: '',
matchResult: null,
suggestions: [],
sessionId: null
},
onLoad() {
this.loadSuggestions()
},
onInput(e) {
this.setData({ inputValue: e.detail.value })
},
async onSearch() {
const message = this.data.inputValue.trim()
if (!message) return
wx.showLoading({ title: '搜索中...' })
try {
const data = await callFunction('emergencyHandler', {
action: 'match',
message: message,
sessionId: this.data.sessionId
})
this.setData({
matchResult: data.match,
suggestions: data.suggestions || [],
sessionId: data.sessionId
})
} catch (err) {
wx.showToast({ title: '搜索失败', icon: 'none' })
} finally {
wx.hideLoading()
}
},
async loadSuggestions() {
try {
const data = await callFunction('emergencyHandler', {
action: 'search'
})
this.setData({
suggestions: data.list.map(item => ({
id: item._id,
category: item.category,
title: item.title
}))
})
} catch (err) {
console.error('加载建议失败:', err)
}
},
onSuggestTap(e) {
const id = e.currentTarget.dataset.id
this.loadDetail(id)
},
async loadDetail(id) {
try {
wx.showLoading({ title: '加载中...' })
const data = await callFunction('emergencyHandler', {
action: 'detail',
emergencyId: id
})
this.setData({
matchResult: data.detail,
inputValue: data.detail.title
})
} finally {
wx.hideLoading()
}
},
onRelatedTap(e) {
const question = e.currentTarget.dataset.question
this.setData({ inputValue: question })
this.onSearch()
}
})
七、结合 AI 增强
当关键词匹配不到时,可以调用 AI 进行理解:
async function handleMatch(message) {
// 1. 先尝试关键词匹配
const keywordMatch = await comprehensiveMatch(message)
if (keywordMatch) {
return {
match: keywordMatch,
source: 'keyword',
confidence: 'high'
}
}
// 2. 关键词匹配失败,调用 AI 理解
try {
const aiResult = await callAI(message)
if (aiResult.matched) {
// AI 识别到相关应急知识
const detail = await db.collection('emergencies')
.doc(aiResult.emergencyId)
.get()
return {
match: detail.data,
source: 'ai',
confidence: 'medium'
}
}
} catch (err) {
console.error('AI 理解失败:', err)
}
// 3. 都失败,返回建议
return {
match: null,
suggestions: await getHotEmergencies()
}
}
async function callAI(message) {
// 调用 DeepSeek API
const response = await got.post('https://api.deepseek.com/chat/completions', {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.DEEPSEEK_API_KEY}`
},
json: {
model: 'deepseek-chat',
messages: [
{
role: 'system',
content: '你是厨房安全助手。根据用户描述,判断是否与厨房安全相关,返回 JSON 格式。'
},
{ role: 'user', content: message }
],
response_format: { type: 'json_object' }
},
responseType: 'json'
})
return JSON.parse(response.body.choices[0].message.content)
}
八、总结
应急知识库设计要点:
| 要点 | 说明 |
|---|---|
| 数据结构 | 分类 + 关键词 + 步骤 + 注意事项 |
| 关键词匹配 | 加权匹配 + 阈值判断 |
| 上下文传递 | sessionId + 追问识别 |
| 降级方案 | 热门问题推荐 |
| AI 增强 | 关键词失败时调用 AI |
通过关键词匹配 + AI 理解的组合方案,可以快速响应用户的应急查询需求。
作者:「倒灶了队」
项目:灶台导航 - 微信小程序
更新时间:2026-05-25
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)