属灵问答构建流程

一 前端的设计

【入口:用户输入】(点击发送 或 回车 或 语音识别填入 input)

是否通过语音识别?
├── 是 →
│     ├── 调用 SpeechRecognition 开始识别
│     ├── 监听 result → 获取 transcript
│     ├── 是否繁体环境?
│     │     └── 是 → convertToTraditional(transcript)
│     └── 将 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(rawInput)

遍历 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(client_prompt)

Step 1:检查 token 长度
├── 若超过 INPUT_TOKEN_LIMIT → 返回错误提示
└── 否 → 继续处理


Step 2:构造 Prompt 文本
├── 插入用户问题,并加说明:
│   ├── 不可改写原文
│   ├── 回答应包括经节出处、正文、参考信息
│   └── 返回格式例子(用于提示格式)


Step 3:构建 Bedrock API 请求体(request_data)
├── input:
│   └── {“text”: prompt}
├── retrieveAndGenerateConfiguration:
│   └── 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_data)
├── 捕获耗时日志
├── 若成功 → 获取 response 对象
│   ├── 提取 response[“output”][“text”]
│   ├── 获取 response[“citations”](文档引用结构)
│   │   └── 每条 citation 包含:
│   │       ├── generatedResponsePart.textResponsePart.text
│   │       └── retrievedReferences[].location.s3Location.uri
│   └── 若无 citation → 返回“未找到匹配文档”


Step 5:整理回答段落(按来源分组)
├── 用 grouped_text_by_source[s3_uri] 存储每段文字
├── 每个文档一段,多段用 “###” 分隔
└── 合成 output_text


Step 6:整理引用信息
├── 遍历所有 citation.retrievedReferences
│   ├── 取 metadata[“x-amz-bedrock-kb-source-uri”]
│   ├── 使用 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(用户问题 + 格式说明)
└─ retrieveAndGenerateConfiguration:
├─ knowledgeBaseId: KB_GENERAL_ID
├─ modelArn: Claude 模型 ARN
├─ vectorSearchConfiguration: Hybrid(混合检索)
└─ generationConfiguration: maxTokens, temperature 等

→ 调用 Claude
client_bedrock_knowledgebase.retrieve_and_generate(request_data)
════════════════════════════════════════════════════════════════
🧠 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”>[属灵实际]</strong>

⑥ 插入经节内容
└─ 匹配 “重要经节出处:[弗四22、23;林前三1~3]”
└─ 映射 window.bibleVerse → 拼成:
<p><strong>弗四22</strong> 内容</p>

⑦ 诗歌标题结构化
└─ [大本诗歌第123首 某标题 查看全文]
└─ 替换为:
<span class=”hymn-title”>标题</span>
<button class=”view-original”>查看全文</button>

⑧ 标题关键词高亮
└─ 读经:/参考信息:/壹 → 加 <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) → 系统字体缩放支持

🔍 最终完整视觉展示
✅ 格式整齐
✅ 引用准确
✅ 按钮可用
✅ 跨平台兼容