属灵问答构建流程
一 前端的设计
【入口:用户输入】(点击发送 或 回车 或 语音识别填入 input)
↓
是否通过语音识别?
├── 是 →
│ ├── 调用 SpeechRecognition 开始识别
│ ├── 监听 result → 获取 transcript
│ ├── 是否繁体环境?
│ │ └── 是 → convertToTraditional(
│ └── 将 transcript 写入输入框 + 自动启用发送按钮
└── 否 →
直接从 userInput.value 获取输入
↓
【统一进入 sendMessage 流程】
↓
Step 1. convertTraditionalToSimplified
↓
Step 2. cleanPoliteStructure()
├─
遍历 politePrefixes,判断 text.startsWith(prefix)
│ └── 命中则截掉前缀 + trim()
└─
进入 removeSuffixes(text, politeSuffixes)
└── 连续去除语气后缀(如“吗”“吧”“~”等)
↓
Step 3. standardizeQuestion()
├─
检查是否是例外句(如“我们是什么”)→ 是则直接返回
├─
遍历 replacements:
│ ├── 若 keep: 仅记录 matchedType,不清洗
│ ├── 否则:
│ │ └── 只替换第一次命中的 from → to
│ └── 清洗完成 → 得到 rawInput
↓
Step 4. 是否匹配“诗歌编号格式”?(正则)
└── /大本|补充本|小本|儿童[\D]*(\d{1,4})/
├─
小本 → 替换为补充本
├─
构造 prefix: 如“大本诗歌第123首”
└─
从 window.hymns 找 key.startsWith(prefix)
├── 找到:
│ └── appendMessage 渲染 + formatMessage 诗歌内容
└── 没找到 → 回复“编号错误或未收录”
↓
Step 5. 本地关键词匹配 handleLocalDictionaryMatch(
↓
遍历 configList 中各类:
┌────────────────────────────
│ 词典种类 | 字典名 | 限定条件 │
├────────────────────────────
│ 诗歌 | window.shi_ge | 必须包含”诗歌” │
│ 经节问答 | window.jing_jie | 必须包含”经节” │
│ 注解问答 | window.zhu_jie | 必须包含”注解” │
└────────────────────────────
↓
【每种词典内部匹配流程】
├── 判断 rawInput 是否含关键词?(如 “诗歌”)
│ └── 否 → 跳过当前类型
├── 是否含排除词?(仅诗歌:点诗歌 / 唱诗歌等)→ 是则跳过
├── 去除关键词后 → 得到 query(模糊主词)
├── 从 dict 中查找:
│ ├── 精确匹配 key === query
│ ├── 模糊匹配(Fuse.js)
│ │ ├── 阈值 score < 0.25(75% 相似度以上)
│ │ └── 合并精确匹配 + 模糊前5项(不重复)
├── 若有匹配结果:
│ ├── 渲染格式为:标题 + 查看全文按钮 + 内容前120字摘要
│ └── appendMessage + 插入“复制”按钮 + “更多”按钮
└── 匹配成功 return true → 停止后续流程
↓
Step 6. 小百科兜底 window.xiao_bai_ke(不限制关键词)
├── rawInput → query
├── 精确匹配 / 模糊匹配(Fuse)
├── 返回前5条:
│ └── 渲染格式为:标题 + 查看全文按钮 + 内容前240字摘要
├── appendMessage + 插入按钮
└── 匹配成功 return true
↓
Step 7. fallback → 后端 API 请求(五类皆未命中)
├── showLoading()
├── fetch(fixedApiUrl, { message: userMessage })
│ ├── 成功:
│ │ └── data.data.response → appendMessage
│ │ └── 插入按钮:更多 / 复制
│ └── 失败:
│ └── appendMessage(“请求失败,请稍后再试”)
└── removeLoading()
↓
【完成】
二 后端流程,以一般分类举例
(后端分为四类:经节、注解、诗歌、一般问答)
【入口】调用 process_general_request(
↓
Step 1:检查 token 长度
├── 若超过 INPUT_TOKEN_LIMIT → 返回错误提示
└── 否 → 继续处理
↓
Step 2:构造 Prompt 文本
├── 插入用户问题,并加说明:
│ ├── 不可改写原文
│ ├── 回答应包括经节出处、正文、参考信息
│ └── 返回格式例子(用于提示格式)
↓
Step 3:构建 Bedrock API 请求体(request_data)
├── input:
│ └── {“text”: prompt}
├── retrieveAndGenerateConfigurati
│ └── knowledgeBaseConfiguration:
│ ├── knowledgeBaseId = KB_GENERAL_ID
│ ├── modelArn = MODEL_ARN
│ ├── retrievalConfiguration:
│ │ └── vectorSearchConfiguration:
│ │ ├── overrideSearchType = “HYBRID”
混合搜索
│ │ └── numberOfResults = 5
检索最多 5 篇文档
│ └── generationConfiguration:
│ └── inferenceConfig:
│ └── textInferenceConfig:
│ ├── temperature = 0.0
│ ├── topP = 1.0
│ └── maxTokens = 1024
↓
Step 4:调用 API → retrieve_and_generate(request_
├── 捕获耗时日志
├── 若成功 → 获取 response 对象
│ ├── 提取 response[“output”][“text”]
│ ├── 获取 response[“citations”](文档引用结构)
│ │ └── 每条 citation 包含:
│ │ ├── generatedResponsePart.
│ │ └── retrievedReferences[].
│ └── 若无 citation → 返回“未找到匹配文档”
↓
Step 5:整理回答段落(按来源分组)
├── 用 grouped_text_by_source[s3_uri] 存储每段文字
├── 每个文档一段,多段用 “###” 分隔
└── 合成 output_text
↓
Step 6:整理引用信息
├── 遍历所有 citation.retrievedReferences
│ ├── 取 metadata[“x-amz-bedrock-kb-
│ ├── 使用 clean_filename() 提取文件名(去前缀/后缀)
│ ├── 编号为 [1]、[2]…
│ └── 拼接为参考信息文本块 references_text
↓
Step 7:合并输出内容 + 引用
├── output_text + “\n\n参考信息:\n” + sources.join(“\n”)
└── 构造响应:
{
“response”: 合并后文本,
“sources”: [清洗后文件名列表]
}
↓
【返回】
→ main → lambda_handler
→ 最终通过 standard_response 统一封装返回
三 进入Claude 的前后流程
→ 构造 Prompt:
– 含格式要求(如经节 + 注解 + 参考来源)
– 插入用户原始问题
↓
→ 构造 request_data:
├─ input: prompt(用户问题 + 格式说明)
└─ retrieveAndGenerateConfigurati
├─ knowledgeBaseId: KB_GENERAL_ID
├─ modelArn: Claude 模型 ARN
├─ vectorSearchConfiguration: Hybrid(混合检索)
└─ generationConfiguration: maxTokens, temperature 等
↓
→ 调用 Claude:
client_bedrock_knowledgebase.
══════════════════════════════
Claude 执行中(Bedrock 内部行为)
══════════════════════════════
↓
→
检索阶段(Retrieval):
– Claude 自动使用 embedding 向量 + keyword hybrid search
– 从 KB 中取 Top N(如 5 条)相关文段
↓
→
上下文拼接(Context 注入):
– Bedrock 自动将 retrieved 文段注入 Claude 的系统上下文中
– 不需要你手动拼接,Claude 会以此为生成参考依据
↓
→
生成阶段(Generation):
– Claude 阅读 prompt + 上下文
– 严格按格式说明,引用 KB 内容生成回答
– 输出字段包括:
├─ output.text:最终生成的完整内容
└─ citations:retrievedReferences(
══════════════════════════════
Claude 执行完毕,返回结果到 Lambda
══════════════════════════════
↓
→ 提取 output.text 作为回答正文
↓
→ 提取 citations → 提取 retrievedReferences → 得到 S3 URI
↓
→ clean_filename():清理文件名,提取来源名称
↓
→ 整理参考来源列表(编号 + 文件名)
↓
→ 拼接回答正文 + “参考信息”文字段
↓
→ 封装为 JSON 响应(standard_response),带 CORS headers
四 前端格式化,展示答案的流程
后台返回 JSON 响应
└─ { success: true, data: { response: “AI生成的内容…” } }
↓
提取 message = data.data.response
↓
appendMessage(‘AI’, message)
↓
调用 formatMessage(message)
↓
formatMessage(message) 执行逻辑:
① 段落标记处理
└─ “###” → 替换为 \n
② 段落拆分
└─ message.split(‘\n’) → 每段包裹 <p>
└─ 有加粗标记 → margin-bottom: 5px
└─ 普通段落 → margin-bottom: 12px
③ 标点格式优化
└─ , . ! ? : ; → 替换为 ,。!?:;
└─ 避免误替换缩写/网址(正则限制)
④ 数字引用处理
└─ [123] → <a class=”reference”>[123]</a>
⑤ 普通加粗文本
└─ [属灵实际] → <strong data-tag=”inline-bold”>[属灵实际]<
⑥ 插入经节内容
└─ 匹配 “重要经节出处:[弗四22、23;林前三1~3]”
└─ 映射 window.bibleVerse → 拼成:
<p><strong>弗四22</strong> 内容</p>
⑦ 诗歌标题结构化
└─ [大本诗歌第123首 某标题 查看全文]
└─ 替换为:
<span class=”hymn-title”>标题</span>
<button class=”view-original”>查看全文</
⑧ 标题关键词高亮
└─ 读经:/参考信息:/壹 → 加 <strong> 包裹
⑨ 正文 / 参考信息 分区
└─ 以 <strong>参考信息:</strong> 分割两段
└─ 分别 <p> 包裹正文与参考内容
⑩ 繁体转换(可选)
└─ 若 navigator.language 是 zh-TW/HK/MO
└─ 调用 convertToTraditional() 转换文本
↓
返回格式化 HTML 结构字符串
↓
插入 DOM → appendMessage 渲染:
<div class=”message bot”>
<div class=”avatar”>B</div>
<div class=”content”>[格式化内容]</div>
</div>
↓
CSS 样式作用点:
【.message.bot】
└─ 灰色背景、圆角、最大宽度 90%
└─ padding: 10px 15px
└─ word-break: break-word
【.history】
└─ flex 垂直排列、scroll-behavior: smooth
└─ 滚动隐藏 scrollbar-width: none
【.data-title】
└─ 灰背景块、字间距大、圆角样式
【.reference】
└─ 蓝色粗体链接,hover 高亮
【.view-original】
└─ 蓝底白字按钮,点击弹出原文 modal
↓
自动挂载附加功能:
查看全文按钮
└─ showHymnModal(title, content)
└─ 渲染模态弹窗 + formatMessage(content)
更多按钮
└─ 重新发送原始用户问题 → 请求新回复
复制按钮
└─ 提取上下文问题 + 答案 → 写入剪贴板
↓
手机端适配细节:
.chat-container
└─ `max-width: 800px` → 响应式压缩为 100%
.message.bot .content
└─ `max-width: 85%` on small screens
.modal-content
└─ `height: 90dvh`、iOS `fixed + bottom` 弹窗
textarea
└─ 自动高度、隐藏滚动条、保留可选中行为
iOS Safari
└─ @supports (font: -apple-system-body) → 系统字体缩放支持
↓
最终完整视觉展示
格式整齐
引用准确
按钮可用
跨平台兼容