Langchain-Chatchat搜索关键词高亮显示实现

在企业知识管理日益智能化的今天,一个常见的挑战是:用户问了一个问题,系统返回了一段看似合理的答案,但没人知道这个答案是从哪来的。这种“黑箱式”输出虽然技术上可行,却难以建立信任——尤其是在法律、医疗或金融这类对信息溯源要求极高的场景中。

Langchain-Chatchat 正是在这样的背景下应运而生。它不仅能让大模型基于私有文档生成回答,还能把依据清晰地呈现出来。而其中一项看似微小却极为关键的功能,就是关键词高亮显示。这项功能就像给文本打上荧光笔标记,让用户一眼就能看出系统是否真正理解了问题,并精准定位到了相关信息。

这背后的技术链条其实相当完整:从文档解析、向量检索到前端渲染,每一个环节都在为最终的可读性服务。我们不妨从一次典型的查询开始,拆解整个流程是如何协同工作的。

当用户输入“员工请假需要哪些审批?”时,系统并不会直接把这个句子丢给大模型去猜答案。而是先通过 LangChain 构建的 RetrievalQA 链,在本地知识库中进行语义搜索,找出最相关的几段原文。这些原文可能是来自《人事管理制度.docx》的一句话:“普通员工请假1天以内由部门主管审批,超过3天需提交至人力资源部备案。”

接下来的问题是:如何让用户快速确认这段话确实与“请假”和“审批”相关?这时,关键词高亮机制就派上了用场。系统会提取原始问题中的核心词(如“请假”、“审批”),然后在检索出的文本中自动标亮它们。最终呈现的效果就像是人工做过标注一样自然。

要实现这一点,首先得依赖 LangChain 提供的强大抽象能力。作为一套模块化框架,LangChain 允许我们将整个问答过程分解为多个可组合的组件:

  • 文档加载器(DocumentLoader)负责读取 PDF、Word 等格式;
  • 文本分割器(TextSplitter)将长文档切分为适合嵌入的小块;
  • 嵌入模型(Embeddings)将文本转化为向量;
  • 向量数据库(如 FAISS)支持高效相似度检索;
  • 最终由 LLM 根据检索结果生成自然语言回答。

以下是一个典型的构建流程示例:

from langchain.chains import RetrievalQA
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.vectorstores import FAISS
from langchain.llms import HuggingFaceHub

# 初始化嵌入模型
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")

# 加载本地向量库
vectorstore = FAISS.load_local("knowledge_base", embeddings, allow_dangerous_deserialization=True)

# 配置语言模型
llm = HuggingFaceHub(repo_id="google/flan-t5-large", model_kwargs={"temperature": 0.7})

# 创建检索问答链
qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
    return_source_documents=True
)

值得注意的是,return_source_documents=True 这个参数非常关键。它确保了系统不仅能返回答案,还能带回原始文本片段,为后续的高亮处理提供了数据基础。没有这一步,所谓的“可解释性”就会成为空谈。

而在中文环境下,Chatchat 的作用尤为突出。它是基于 LangChain 深度定制的一套本地化部署方案,专为中文文档优化。无论是使用 PyPDF2 解析 PDF,还是借助 Unstructured 处理复杂排版文件,Chatchat 都封装好了完整的流水线。更重要的是,它默认集成了中文友好的嵌入模型和大模型(如 ChatGLM、Qwen),避免了因语言差异导致的语义偏差。

不过,即使后端准备充分,如果前端展示不够直观,用户体验依然会打折扣。这就引出了我们关注的核心——关键词高亮显示

实现这一功能的关键在于时机和方式的选择。理想的做法是在服务调度层完成文本匹配与标签注入,而不是让前端去做字符串替换。原因很简单:前端不具备完整的上下文,也无法安全地执行正则操作。

下面是一个经过生产环境验证的高亮函数:

import re
from typing import List

def highlight_keywords(text: str, keywords: List[str], tag: str = "mark") -> str:
    """
    对文本中的关键词进行HTML高亮处理
    :param text: 原始文本
    :param keywords: 关键词列表
    :param tag: 包裹标签名,默认为 <mark>
    :return: 带高亮标签的HTML字符串
    """
    highlighted = text
    # 按关键词长度降序排列,防止短词先被替换导致长词无法匹配
    sorted_keywords = sorted(keywords, key=len, reverse=True)

    for keyword in sorted_keywords:
        if not keyword:
            continue
        # 使用正则表达式进行全局、不区分大小写的替换
        pattern = re.escape(keyword)
        replacement = f"<{tag} style='background-color: yellow; font-weight: bold;'>{keyword}</{tag}>"
        highlighted = re.sub(pattern, replacement, highlighted, flags=re.IGNORECASE)

    return highlighted

# 示例调用
query = "合同签署流程"
source_text = "公司规定所有合同必须经过法务审核后方可签署,签署完成后需归档。"

keywords = ["签署", "合同", "流程"]
result_html = highlight_keywords(source_text, keywords)
print(result_html)

这个函数的设计有几个工程上的巧思:

  1. 关键词排序:按长度从长到短处理,避免“合同”被提前替换了,导致“合同签署”再也匹配不到。
  2. 转义处理:使用 re.escape() 防止关键词中含有特殊字符(如 .*)引发正则错误。
  3. 样式内联:直接写入 style 属性,减少对外部 CSS 的依赖,提升兼容性。

当然,实际应用中还需要考虑更多边界情况。比如原始文本可能已经包含 HTML 实体(如 &lt;),如果不先解码就直接插入 <mark> 标签,可能会破坏结构。建议在处理前统一进行 HTML 解码,处理后再编码输出。

前端接收后,可以通过 innerHTML 或 React 的 dangerouslySetInnerHTML 渲染结果:

// React 示例:渲染高亮文本
function DisplayResult({ htmlContent }) {
  return (
    <div className="result">
      <p dangerouslySetInnerHTML={{ __html: htmlContent }} />
    </div>
  );
}

这里有个重要的安全提醒:任何动态插入 HTML 的行为都存在 XSS 风险。因此,强烈建议配合 DOMPurify 这类库对内容进行净化处理,只允许 <mark><span> 等必要标签通过。

回到用户体验层面,你会发现,仅仅加个黄底并不能解决所有问题。真正的挑战在于如何平衡信息密度与视觉干扰。如果每段都密密麻麻标满了关键词,反而会让用户眼花缭乱。因此,在设计时可以引入一些智能策略:

  • 同义词扩展:利用 Jieba 分词 + 同义词库,把“审批”也匹配“批准”“审核”等近义词,提高召回率;
  • 权重控制:根据 TF-IDF 或 BM25 给关键词打分,优先高亮更重要的词汇;
  • 颜色分级:高频词用浅黄色,核心词用橙色,形成视觉层次;
  • 移动端适配:限制单段最多高亮次数,避免页面布局错乱。

更进一步,还可以尝试“反向高亮”机制:当用户点击某个高亮词时,自动滚动到其他含有该词的段落,形成关联浏览体验。这种交互模式在查阅长篇制度文件或技术手册时特别有用。

整个系统的架构也因此变得更加立体。从前端 Web UI 到 FastAPI 接口,再到 LangChain 应用逻辑层、数据处理层和底层模型资源,关键词高亮虽只是一个小小的中间环节,但它串联起了前后端的信任桥梁。

尤其在企业级应用场景中,这种细粒度的信息呈现方式意义重大。试想一位 HR 在查找薪酬政策时,看到“年终奖发放时间”这几个字被高亮出来,他会立刻意识到系统确实找到了相关内容,从而增强对系统的信赖。而在法律或审计场景中,这种透明性甚至是合规性的基本要求。

未来的发展方向也很明确:我们可以不再局限于静态关键词匹配,而是结合 LLM 自身的注意力机制,提取出真正影响答案生成的关键句子或短语。换句话说,不只是标出“用户提过的词”,更要标出“模型认为重要的部分”。这正是迈向“可解释 AI”的重要一步。

目前已有研究尝试将 Transformer 的注意力权重可视化为热力图,叠加在原文之上。虽然在本地部署环境中实现代价较高,但对于高价值场景来说,这种深度解释能力值得投入。

总结来看,Langchain-Chatchat 中的关键词高亮功能,远不止是一次简单的字符串替换。它是连接技术实现与用户体验的关键节点,体现了智能系统从“能答”到“易懂”的演进路径。在一个越来越重视透明性和可控性的时代,这类细节往往决定了一个工具能否真正落地并被广泛接受。

这种高度集成且注重可用性的设计思路,正在引领本地化知识库系统向更可靠、更高效的方向发展。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐