SYSTEM OK | ACTION CONTRACTS 247 DOMAINS 35 DEMO ENVIRONMENT 无需申请 | 服务热线 18926139835
首页/洞察/技术系列/基于 pgvector 的故障知识库与 RAG

基于 pgvector 的故障知识库与 RAG

设备维修的经验多数留存于资深工程师的记忆之中,未进入任何可被检索的载体。EAMX 的故障知识库以两条自动路径把维修经验沉淀为可语义检索的向量知识: 工单关闭时自动提取知识对并写入 pgvector,故障提交时异步检索相似历史方案并附注于故障单。 本文拆解该 RAG 管线的完整实现:向量生成、写入与检索、异步调度、知识提取的两条路径,以及管线自身的边界。

作者 EAMX 产品团队 发布 2026-09-28 阅读 约 11 分钟 专题 技术系列

01故障知识为何留不住

设备维修完成之后,工单上通常留下两栏内容:故障现象与处理结果。至于如何逐项排查、最终定位到哪一个部件、更换之后又调整了哪些参数, 这些内容多数保留在经办人的记忆之中,并未进入可被检索的记录。

由此形成三项实际后果。同一类故障在半年后由另一位工程师接手时,排查路径需要从头再来;资深工程师离岗或调岗之后, 其判断习惯没有可继承的载体;工单上「已修复」一类的结论式记载,无法作为下一次维修的依据。 这三项后果彼此关联:记录的内容不足以支撑复用,检索方式也无法弥补内容本身的缺失。

检索方式构成第二处限制。按关键词匹配的搜索要求提问方与记录方使用同一组词:输入「温度偏高」无法命中记录中的「温控异常」, 即便两者描述的是同一种现象。

本文的核心判断

故障知识库的可用程度取决于两件事:维修过程是否被转写为结构化的知识对,检索是否按语义相似而非字面匹配进行。 EAMX 以两条自动路径落实这两件事,知识库的增量来自正常的工单流转,每一次维修成为下一次维修的输入。

02架构:两条自动路径构成的闭环

故障知识库由两条自动路径组成,分别位于维修过程的前后两端,两条路径之间不设人工录入环节。

事前路径在故障提交时触发:系统对故障描述执行一次异步相似检索,把命中的历史方案作为维修参考附注在故障单上。 事后路径在工单关闭时触发:大模型从完整的维修记录中提取结构化的知识对,生成向量并写入 pgvector。 下一次遇到同类故障时,事前路径即可命中这一条新增的知识。

故障报修
  ↓
异步相似检索(事前)→ 维修参考附注
  ↓
工程师维修(参考历史经验)
  ↓
工单关闭
  ↓
大模型提取知识对(事后)→ { faultPattern, rootCause, solution, keyParts }
  ↓
embedText() → pgvector 写入
  ↓
下次类似故障 → 检索命中

口径:该流程图取自源文。维修参考附注在源文中的呈现形式为一句自然语言,例如「3 号注塑机去年 10 月同样故障,更换接触器后修复」;其中设备名称与位置为源文举例所用。

两条路径共用同一张向量表:写入的时机由业务事件决定,读取的时机由使用者的提问决定,两者之间只通过 pgvector 上的向量距离发生联系。

03向量生成:模型与维度取舍

文本转向量使用 OpenAI 的 text-embedding-3-small,并在调用时显式指定 1536 维。

// agents/rag.ts — embedText()
export async function embedText(text: string): Promise<number[] | null> {
  if (!text || text.trim().length < 3) return null;
  const resp = await openai.embeddings.create({
    model: "text-embedding-3-small",
    input: text.slice(0, 8000),  // 模型输入上限保护
    dimensions: 1536,       // 显式指定维数
  });
  return resp.data[0]?.embedding ?? null;
}

该函数有三处设定值得说明。其一,文本为空或去空格后长度不足三个字符时直接返回 null,调用方据此跳过写入, 使空白记录不会进入向量表。其二,输入按 8000 字符截断,作为模型输入上限的保护。 其三,维数显式传入,不依赖模型的默认取值,配置因此可在代码中直接读到并核对。

关于维度的选择,源文给出的取舍是:1536 维在精准度与存储、查询性能之间较为均衡;3072 维带来的提升有限, 而存储与查询开销约为前者的两倍。上述对比说明的是工程代价的差异,源文未附基准测试记录。

口径:模型名称、维数、截断长度与空值条件取自 agents/rag.ts 中 embedText() 的实现配置。本文不据上述取舍给出召回率或准确率数字。

04存储与检索:余弦距离、阈值与异步调度

写入:以来源标识保证幂等

写入以来源记录标识为幂等键:同一来源只写入一次向量,重复关闭同一张工单不会产生重复知识。

// agents/rag.ts — saveEmbedding()
// 幂等:同一 source_id 不重复写入
const exists = await pool.query(
  `SELECT id FROM knowledge_embeddings WHERE source_id = $1 LIMIT 1`,
  [opts.sourceId],
);
if ((exists.rowCount ?? 0) > 0) return;

const vector = await embedText(opts.content);
if (!vector) return;

await pool.query(
  `INSERT INTO knowledge_embeddings
   (tenant_id, source_type, source_id, content, embedding)
   VALUES ($1, $2, $3, $4, $5::vector)`,
  [opts.tenantId, opts.sourceType, opts.sourceId,
   opts.content.slice(0, 2000), `[${vector.join(",")}]`],
);

向量与租户标识、来源类型、来源记录标识同表保存。来源记录标识指向知识对所在的业务表,使每一条向量都能回溯到产生它的知识条目; 租户标识同时出现在写入与检索两侧,检索范围因此被限制在同一租户之内。

此处有两处截断长度不同:用于生成向量的文本在 embedText() 内部按 8000 字符截断, 而落库保存的 content 字段按 2000 字符截断,供展示与人工核对使用。

就切分与排序而言,该实现不设两层处理:一个知识对整体生成一条向量,超出输入上限的部分按截断处理,不做二次切分; 检索结果直接按向量距离排序返回,不设独立的重排环节。源文中亦无切分长度与重排模型的参数记录。

检索:余弦距离与阈值过滤

// agents/rag.ts — searchSimilarFaultSolutions()
SELECT
  ak.id AS knowledge_id,
  1 - (ke.embedding <=> $1::vector) AS similarity,
  ak.equipment_name, ak.fault_symptom, ak.root_cause, ak.solution
FROM knowledge_embeddings ke
JOIN agent_knowledge ak ON ak.id = ke.source_id
WHERE ke.tenant_id = $2
  AND ke.source_type IN ('fault_solution', 'external_knowledge')
  AND 1 - (ke.embedding <=> $1::vector) >= $3  -- 阈值过滤
ORDER BY ke.embedding <=> $1::vector  -- 按距离排序
LIMIT $4

该查询包含四处关键设定。

  • 余弦距离运算符。<=> 是 pgvector 提供的距离运算符,1 - (embedding <=> query) 把距离转换为 0 至 1 之间的相似度。
  • 类型显式转换。查询参数写作 $1::vector,避免 pgvector 在类型推断上产生歧义。
  • 来源类型过滤。只检索来源类型为 fault_solution 与 external_knowledge 的向量,其余来源类型的向量不进入故障检索的结果集。
  • 阈值过滤与排序。相似度低于 0.5 的结果被过滤,结果按距离升序排列并限制返回条数。阈值的作用是排除相关度不足、仅因余弦距离最近而被选中的噪声记录。

口径:上述四处设定取自 agents/rag.ts 中 searchSimilarFaultSolutions() 的查询语句与阈值参数(阈值 0.5)。

异步调度:不阻塞主业务流程

写向量涉及一次外部接口调用与一次数据库写入,其耗时不宜进入报修与工单关闭的主流程。EAMX 把写入交给异步任务调度,调用方不等待结果。

// fire-and-forget,不阻塞主业务流程
export function scheduleEmbedding(opts: SaveEmbeddingOpts): void {
  setImmediate(() => {
    saveEmbedding(opts).catch(err =>
      aiLog.warn(`[RAG] scheduleEmbedding error: ${err.message}`));
  });
}

这一安排同时确定了失败的处理方式:向量写入失败不会中断业务动作,错误由 aiLog 以告警级别记录。 工程上需要相应关注该日志——写入失败在业务流程中的表现是后续检索结果变少,而这一变化不会产生报错。

本节所述内容可以当场核验

与工单、故障、点检相关的动作数量可由演示环境实时导出,无需申请。查看核验方式 →

05知识生成的两条路径

向量表的内容来自两条相互独立的生成路径,两者以来源类型区分。

路径一:外部知识查询触发

使用者在行业知识问答中提出问题时,系统以问题与设备上下文组装提示词,调用大模型生成回答。 结果标记为需要入库时,回答写入 agent_knowledge 表,并以 external_knowledge 作为来源类型调度向量写入。

// agents/external-knowledge.agent.ts — queryKnowledge()
const { text } = await generateText({
  system: "你是一位工业设备领域的高级专家…",
  prompt: context,  // 用户问题 + 设备上下文
  maxOutputTokens: 1500,
});
scheduleEmbedding({ tenantId, sourceType: "external_knowledge",
  sourceId: knowledgeId, content: `${context} ${text}` });

提示词由使用者的问题与设备上下文共同构成,最大输出长度限定为 1500 token。写入向量时所拼接的内容为「上下文 + 回答」, 使该条知识在检索时既能被问题侧的文字命中,也能被结论侧的文字命中。

路径二:工单关闭时自动提取

工单关闭时,大模型从维修过程之中提取结构化的知识对,提取结果包含故障模式、根因、处置方案、关键备件与维修时长五项。

工单内容:
  故障:料筒温度偏高,PID 调节无效
  维修:检查传感器正常 → 接触器触点烧蚀(根因)→ 更换 CJX2-2510 → 校准 PID

→ 大模型提取:
{
  "faultPattern": "料筒温度偏高,PID 调节无效",
  "rootCause": "加热器接触器触点烧蚀",
  "solution": "更换接触器(CJX2-2510),校准 PID 参数",
  "keyParts": ["CJX2-2510 接触器"],
  "repairDuration": "45 分钟"
}

→ embedText(faultPattern + rootCause + solution) → pgvector 写入

生成向量所用的文本为故障模式、根因与处置方案三项的拼接,维修时长等字段作为元数据保存。 该拼接方式使检索面向「现象—成因—处置」这一组内容,工单的行政信息不进入向量, 检索结果因此更贴近维修场景的实际提问方式。

口径:知识对的字段构成、示例内容与向量拼接方式取自 agents/fault.agent.ts 与 agents/rag.ts 的实现记录;示例中的备件型号与维修时长为源文举例所用。

06语义检索与关键词检索的差别

按关键词匹配的搜索要求提问与记录使用相同的词形,语义检索则按向量距离判断相关程度。 提问方可以直接使用现场的表述方式,无须先确定当初记录者用了哪一个词。

用户提问:「料筒温度老是偏高,自动调不过来怎么办?」

→ 向量化查询 → 余弦相似度搜索
→ 命中:
  0.93  料筒温度偏高,PID 调节无效 — 接触器触点烧蚀
  0.87  加热区温度异常偏高 — 加热器功率控制器故障
  0.72  温控系统不稳定 — 传感器信号受干扰
表 1 · 检索结果示例(源文示例,非离线评测结果)
相似度命中的知识对说明
0.93 料筒温度偏高,PID 调节无效 — 接触器触点烧蚀 现象与成因均与提问接近,排序居首
0.87 加热区温度异常偏高 — 加热器功率控制器故障 故障部件不同,现象描述相近
0.72 温控系统不稳定 — 传感器信号受干扰 相关但成因方向不同,列第三顺位

口径:该组相似度数值为源文给出的检索示例,用于说明排序形态与阈值过滤的作用;其不构成离线评测,亦不作为召回率或准确率的口径。本方不据此作出准确率承诺。

三者的相似度均在 0.5 的阈值之上,因此同时进入附注;阈值以下的记录不予返回,该设定的取舍在第 08 节说明。 命中的三条记录使用了三种不同的措辞——语义检索所度量的是现象与成因之间的距离,而不是词形本身。

07实现文件与数据表

该管线涉及的实现文件与数据表如下,其中行数为源文记录的文件规模口径。

表 2 · 故障知识库的实现文件与数据表
文件 / 数据表行数职责
agents/rag.ts 184 向量生成、写入与相似检索三项能力的实现
agents/external-knowledge.agent.ts 477 行业知识的生成,以及知识写入与向量写入的调度
agents/fault.agent.ts — 工单关闭时触发知识提取
knowledge_embeddings 表 — 向量存储,含租户标识、来源类型、来源记录标识与原文片段
agent_knowledge 表 — 知识对元数据,可回溯到来源工单

口径:行数为源文记录的口径;「—」表示源文未记录该项行数,不代表该文件或数据表不存在。

从分工上看,agents/rag.ts 把生成、写入与检索三项集中在一个文件之内,使检索参数与写入参数在同一处可见。

08边界与不承诺

该管线的边界集中在四处,均与实现方式直接相关。

  1. 阈值过滤会漏掉相关记录。相似度低于 0.5 的历史方案不返回。该设定以漏检换取结果的可读性: 附注条数有限,相关度不足的记录会挤占工程师的注意力。
  2. 不承诺召回率与准确率。本文与产品侧页面均不列示检索召回率、准确率或诊断正确率一类指标; 本文可核对的内容为阈值设定、维数配置与检索示例三项。
  3. 知识提取以工单内容完整为前提。工单需要记录故障现象、处置过程与所更换的部件,提取环节才有可用的输入。 仅记录「已修复」的工单无法形成知识对。
  4. 异步写入需要日志监控。写入失败不影响业务动作,其代价是失败不会被业务流程察觉,需要由日志侧承担监控职责。

另有一项工程约束与模型配置有关:更换向量模型或改变维数会使既有向量与新查询不再处于同一向量空间,两者无法直接比较, 需要按新的配置重建存量向量。因此模型名称与维数在实现中显式固定,并在代码注释中记录其取值。

上述边界与实现方式直接对应:该管线的效果取决于工单内容的完整程度与检索阈值的设定。

09作者与依据

作者
EAMX 产品团队 · 产品与解决方案
依据
EAMX 2.0 中故障知识库与 RAG 管线的实现记录:agents/rag.ts、agents/external-knowledge.agent.ts、agents/fault.agent.ts,以及 knowledge_embeddings 与 agent_knowledge 两张数据表的定义
数据来源
向量模型与维数取自 embedText() 的实现配置;检索阈值取自 searchSimilarFaultSolutions() 的查询参数;文件行数取自源文记录;相似度数值取自源文示例,非离线评测结果
引用标准
ISO 55000 系列仅为方法论依据,不构成认证;认证对象是组织,而非软件
更新日期
2026-09-28
01相关产品能力

本文所述的管线,在产品中落在哪个环节

02相关文章

同专题与跨专题的延伸

上一级专题:技术系列——该专题总纲给出本专题十一篇长文的阅读顺序。

本文的实现口径可以逐条核对

向量模型与维数、输入截断长度、检索阈值、文件行数——本文所引各项均可对照实现记录与源文逐条核对; 与工单、故障、点检相关的动作数量可在演示环境当场核实,无需采信本方任何陈述。

信息中心 / 技术评估
要核对实现方式与接入细节
运维执行
关心故障知识如何落到工单上