
简介基于LLM将自然语言转化为图数据并进行异常检测与结果可视化的完整项目源码现已打包为zip发布。面向对图异常检测、自然语言处理及数据可视化感兴趣的开发者或研究人员项目中的完整流程适合用于学习LLM在非结构化文本转结构化图数据方面的落地实现。压缩包共35个文件大小仅122KB包含Python后端脚本如py及编译后的pyc、前端交互资源js、html、css、项目配置与说明文件json、xml、txt、md等目录结构清晰便于快速定位核心逻辑。项目以AnomalyGPT-main为基础后端包括模型调用、提示词处理、API核心逻辑等模块前端通过可视化界面展示异常检测结果。值得关注的是该项目覆盖了从自然语言理解、图数据构建到图异常检测和结果可视化的完整流程对希望系统性理解该类系统设计和代码实现的读者具有直接参考价值。目前已有41人学习浏览资源体积小、结构完整适合快速入手和二次开发。 这个项目标题拆开看其实是一条很完整的研究/工程链路先让LLM把自然语言变成结构化的图数据再在图上做异常检测最后把检测结果可视化出来。我前阵子正好把一个类似的端到端方案跑通并落了地所以这篇直接把整条链路的设计思路、核心实现、选型理由和踩坑记录写完给正在做LLM应用、知识图谱或图分析方向的朋友做参考。先说明一下这个项目适合什么人看如果你手里有一堆文本类的业务记录工单、日志、事件描述、舆情文本等希望找出其中“不对劲”的实体关系结构又不想手工建图谱那么这套“LLM建图 图异常检测 可视化”就是一条很实用的路线。下面我按实际落地顺序逐段拆解。1. 项目整体设计为什么必须走“LLM建图 图检测 可视化”这条路1.1 先拆解完整链路最开始我看到“基于LLM将自然语言转化为图数据”这个描述第一反应是这不就是把文本抽成三元组吗但真正做起来会发现难的不是“抽取”而是抽取完以后怎么让图“有用”。所以我在设计阶段把链路拆成了四步输入原始自然语言文本例如一段安全日志、一段业务事件描述。通过LLM以结构化输出方式抽取实体和实体之间的关系得到三元组集合。将三元组去重、合并、映射成图结构节点 边 属性。在图结构上做异常检测筛出可疑节点、可疑边或可疑子图。将带异常标记的图渲染成交互可视化页面。这个链路里最容易被低估的是第2步和第4步。很多人以为LLM输出JSON就算完事后面直接用NetworkX画个图就结束了但真实业务中LLM输出会有实体不一致、关系类型冗余、属性缺失这些问题而图异常检测算法又会反过来要求图结构清洗到足够的质量否则算出来的指标全是噪声。1.2 为什么不让LLM直接判断异常而要先建图这是个很关键的设计决策。项目名里之所以强调“转化图数据后做检测”而不是“让LLM直接回答哪些地方可疑”是因为LLM虽然能理解单条文本语义但在大规模、多实体、长时序的关联场景里它的上下文窗口和推理稳定性都不足以支撑全局判断。比如面对几十万条日志LLM不可能把它们全部塞进上下文再给出一个全局异常结论。但图结构天然适合表达这种全局关联。把实体变成节点、把业务行为变成边之后我们就能用度数分布、社区结构、网络中心性等指标从全局视角找出那些“位置奇怪”的节点或边。这也是图异常检测相对纯文本判断的核心优势它能捕捉到局部语义之外的拓扑异常。比如某个账户在转账网络里连接了30个原本毫无关联的节点在文本语义层面你难以察觉但在图结构上会非常扎眼。1.3 整体技术栈选型这里给出我当时实际用的技术栈适配中小规模项目大家可以按自己的场景替换环节选型理由LLM推理Qwen系列开源模型也可用其他支持JSON Mode的模型支持结构化输出、本地部署成本可控文本处理LangChain或纯requests调用HTTP接口链路简单调试方便图存储与计算NetworkX PyG小图直接用NetworkX大规模图再上PyG异常检测统计特征 孤立森林 / 图自编码器兼顾可解释性和精度可视化Pyvis / Plotly可交互、免部署、直接生成HTML选择开源LLM而不是纯API方案主要因为业务文本往往涉及内部数据不太方便直接外发。如果你的场景不敏感直接用任意支持JSON输出的商用API都可以。2. LLM将自然语言转图数据提示词与结构化输出是核心2.1 明确“实体”和“关系”的定义在写提示词之前一定要先定义清楚本项目的本体Schema也就是图上允许出现哪些类型的节点、哪些类型的边。如果这一步不做LLM会自由发挥输出五花八门的实体类型和关系类型后期根本没法建模。拿我当时的风控场景举例文本里涉及的核心节点类型是用户user、账户account、IP地址ip、设备device、订单order。核心边类型是登录login、转账transfer、访问access、下单order_create。这个Schema需要提前写进提示词里并且明确告诉LLM“只能输出这些类型不要自己发明”。2.2 提示词模板与JSON输出设计LLM输出的稳定性是整个项目成败的关键。我最终用的方案是“系统提示词约束Schema 用户提示词给具体文本 JSON Schema校验”。下面是一段经过多轮调优的提示词模板片段你是一个信息抽取引擎。请从给定的文本中抽取实体和关系并以JSON格式输出。 实体类型仅限user, account, ip, device, order 关系类型仅限login, transfer, access, order_create 输出格式 { entities: [ {id: E001, type: user, name: 用户A, attributes: {}}, {id: E002, type: account, name: 账户B, attributes: {}} ], relations: [ {source_id: E001, target_id: E002, type: transfer, attributes: {amount: 20000}} ] } 要求 1. 每个实体必须有唯一的id用E001、E002这种格式。 2. 如果文本中有多个相同实体只输出一次并尽量合并属性。 3. 如果文本中没有明确行为关系不要强行猜测。 4. attributes字段可以为空对象。这句要求“如果有多个相同实体只输出一次”很关键。由于LLM每次处理的是单条或小批量文本同一个实体很可能在多条文本里重复出现如果一条文本里就输出重复实体后续合并成本会很高。为了提升解析稳定性我在拿到LLM返回后还会做一步“强制JSON修复”。做法是如果json.loads()失败就先用正则把最外层大括号裁剪出来再尝试解析如果还失败就用一个很小的修复模型或预设规则把常见的缺逗号、多余逗号问题修掉。这一步看似笨拙但在批量处理数千条文本时能把整体成功率从90%拉高到99%以上。2.3 实体对齐与去重真正的隐性工作量很多第一次做这类项目的人以为LLM输出JSON后图就自动建好了结果跑出来的图一团糟。原因就在实体对齐环节。举个例子第一条文本里出现“用户A”第二条文本里出现“A用户”第三条里是“user:Alice”。这些到底是不是同一个实体如果直接按字符串匹配你会得到三个节点但实际业务中很可能是同一个对象。我当时的处理策略分几层同一类型且同名的实体直接合并。类型相同但名字相似如“用户A”和“A用户”先用归一化处理再把归一化后仍相同的合并。属性里有明确ID如账号ID、手机号、IP的优先属性匹配。这一步决定了后续图质量的基础。如果节点数量异常膨胀先回头检查实体对齐而不是怀疑检测算法。3. 图异常检测从统计特征到图神经网络的两套落地路线3.1 先定义“什么是异常”图异常检测不是一句“找出可疑节点”就完了必须先把异常模式定义清楚。我一般把异常分成三类节点级异常某个节点的度数、中心性、聚类系数显著偏离同类节点。比如一个普通账户关联了30个IP。边级异常某条边承载的行为强度异常比如短时间内高频转账。子图级异常一小群节点形成异常密集的可疑子图比如团伙式操作。定义清楚异常类型后再去选算法就不会乱。对多数业务场景我建议先做节点级和边级检测子图级需要更大规模的数据和更强的计算资源可以作为后续扩展。3.2 方案A图统计特征 孤立森林快速且可解释这套方案不需要GPU适合快速搭建和验证。具体做法是对图里的每个节点计算一批结构特征包括但不限于度degree和出入度差PageRank值介数中心性betweenness centrality局部聚类系数local clustering coefficient邻居节点数egonet内部边数把这些特征拼成一个特征矩阵然后喂给孤立森林Isolation Forest或者局部离群因子LOF做无监督异常打分。每个节点得到一个异常分数数值越高越可疑。import networkx as nx import pandas as pd from sklearn.ensemble import IsolationForest # G 是由三元组构建好的有向图 feat { degree: dict(G.degree()), pagerank: nx.pagerank(G), betweenness: nx.betweenness_centrality(G), clustering: nx.clustering(G.to_undirected()), } df pd.DataFrame(feat).fillna(0) model IsolationForest(contamination0.05, random_state42) df[anomaly_score] model.fit_predict(df) df[score] model.score_samples(df)实际运行中这类方法能把结构上“孤立但高度聚合”的节点快速筛出来。比如我在日志场景里就发现一个正常用户节点的PageRank分布和聚类系数分布通常落在某个区间而一个有问题的内部账号会在“低聚类系数 高介数中心性”的组合上显得异常突出。这套方案的好处是特征可解释你可以在可视化页面里明确告诉业务方“这个节点因为PageRank过高被标记”而不是丢出一个黑盒分数。如果后面要接业务规则特征值也可以直接用于规则引擎。3.3 方案B图自编码器重构误差需要GPU的进阶路线如果节点还带丰富的属性特征比如账号的注册时长、登录频次、设备指纹那么纯结构特征就浪费了属性信息。这时可以考虑图自编码器GAE这类深度方法核心思路是用图编码器把节点压缩成向量再尝试重构邻接矩阵和节点属性重构误差大的节点就是异常。在PyG里搭建一个简单的GAE代码骨架如下import torch from torch_geometric.nn import GCNConv from torch_geometric.utils import to_undirected class GAE(torch.nn.Module): def __init__(self, in_dim, hidden_dim): super().__init__() self.conv1 GCNConv(in_dim, hidden_dim) self.conv2 GCNConv(hidden_dim, hidden_dim) def encode(self, x, edge_index): x self.conv1(x, edge_index).relu() return self.conv2(x, edge_index) def decode(self, z, edge_index): return (z[edge_index[0]] * z[edge_index[1]]).sum(dim-1) def recon_loss(self, z, pos_edge_index, neg_edge_index): pos_score self.decode(z, pos_edge_index) neg_score self.decode(z, neg_edge_index) loss -torch.log(torch.sigmoid(pos_score) 1e-8).mean() loss -torch.log(1 - torch.sigmoid(neg_score) 1e-8).mean() return loss训练结束后把每个节点的邻居重构误差算出来误差越大说明该节点周围结构与模型学到的“正常模式”偏差越大。相比统计特征方案这个方案在带有属性信息的数据集上效果通常更好但调试成本和算力成本也更高。我的建议是第一次跑通项目先用方案A确定链路没问题再上方案B。4. 可视化落地把检测结果变成一张能交互的图4.1 选型对比别一上来就上重型前端可视化这一步常见的坑是“想得太复杂”。有人一开始就规划用Gephi导出、用Neo4j Browser展示、或者自研前端调用图数据库结果项目还没走完就卡在环境搭建上。我实际验证下来中小规模图数据可视化最顺手的是Pyvis和Plotly两个都是Python库直接把结果输出成HTML浏览器打开就能交互。工具优点缺点适合场景NetworkX Matplotlib简单静态、不可交互、节点一多就糊调试阶段看结构Pyvis交互能力强、支持拖拽/缩放/颜色映射节点过多会卡顿数千节点以内的交互图Plotly视觉精细、支持颜色轴和图例配置略复杂、边一多渲染变慢需要发布成报表或嵌入网页Gephi布局算法强大需要单独安装客户端、不利于自动化流程一次性深度分析Neo4j Bloom企业级图数据库配套依赖图数据库服务已有Neo4j存储的场景我在这个项目里选择的是效果图用Plotly快速交付和内部演示用Pyvis两条路都保留。4.2 从图数据到交互页面的实现流程以Pyvis为例整个发布流程只需要几步from pyvis.network import Network net Network(height800px, width100%, directedTrue) # 添加节点node_size 根据异常分数映射 for node_id, data in node_dict.items(): net.add_node( node_id, labeldata[name], colordata[color], sizedata[size], titlef异常分数: {data[score]}, ) # 添加边 for src, dst, etype in edge_list: net.add_edge(src, dst, labeletype) # 生成HTML net.show(anomaly_graph.html)这里最核心的是把“异常分数”映射到视觉通道。我的做法是颜色正常节点用浅蓝色异常分数排名前5%的节点用深红色。大小节点大小与异常分数或PageRank挂钩分数越高节点越大。鼠标悬停信息把该节点的特征向量和异常分数放在title里方便排查。边的透明度或颜色用来区分关系类型比如转账用红色、登录用灰色一眼能看到危险路径。4.3 让结果“会说话”的视觉细节有几个细节能显著提升可视化效果属于“不做也行、做了立刻高级”的小技巧第一个是布局算法。默认的力学布局force-directed在节点少时很好用但节点过千后容易变成一团乱麻。我建议先用NetworkX的spring_layout或kamada_kawai_layout算好坐标再传给前端渲染布局稳定性会比运行时实时计算好很多。第二个是子图隔离。如果异常节点聚集在一个局部区域可以在前端加一个“只看异常子图”的过滤开关。我当时通过图的连通分量分析把包含异常节点的连通子图单独导出成页面业务方反馈排查效率大幅提升。第三个是导出快照数据。可视化页面最终要给业务看而业务不一定能跑Python。把节点和边导出成GraphML或CSV再用Gephi做一次深度分析也是很好的补充路径。5. 过程中踩过的坑与排查记录5.1 LLM输出不稳定导致图谱断裂这是所有问题里出现频率最高的。表现是批量文本经过LLM抽取后有些关系缺失有些实体ID不连贯导致同一实体在图里变成多个节点或者同一批逻辑关系被拆成零散的小图。排查思路是先做统计抽取前后实体数、关系数、连通分量数对比预期。我当时写了一个简单的“三段质检”逻辑第一段检查JSON是否能解析第二段检查实体类型、关系类型是否在Schema范围内第三段检查每条关系两端的实体id是否存在。只要有一项不通过就重新抽一次或打上人工复核标记。这一步能筛掉大部分脏数据。5.2 图数据膨胀与实体对齐不充分在实际处理几万条日志时实体对齐的坑最大。因为LLM有时会把同一个人在不同上下文里描述成“张三”“张先生”“zhang_san”如果不做别名归一化一个真实用户就可能变成三四个节点整个图结构就会被严重稀释。我的解法是单条文本抽取时保留原始的实体名称和类型构建全局图时再做多级对齐。第一级用规范化后的字符串精确匹配第二级用同义词典比如“张三”和“zhang_san”映射到同一个ID第三级用属性ID匹配比如账号ID相同则必然是同一实体。这一套做下来图节点数能下降30%左右异常检测的精确率反而明显提升。5.3 异常检测结果“看起来像随机”怎么办如果你跑完算法发现异常节点分布非常均匀看不出明显聚集大概率不是算法问题而是“图建得不好”或“特征选得不对”。我总结出两个经验一是检查图的连通性。如果图被拆成大量孤立小图那么小图里的小度数节点很容易被误判为异常因为它们和整体分布差异太大。这种情况要先把孤立的“单点图”过滤掉再在核心大图上做检测。二是检查特征分布。如果所有节点的度都集中在个位数那么“度”这个特征几乎没有判别力。此时建议换用带属性的深度方法或者加入时间维度特征比如边的最近活跃时间、频次变化率让异常从“结构差异”变成“行为差异”。5.4 常见问题速查现象可能原因处理建议抽取的JSON频繁解析失败提示词约束不足启用JSON Mode增加格式Few-shot示例修复逻辑兜底图的节点数远超实体数实体对齐缺失增加字符串归一化和属性ID匹配图太散、连通分量过多关系抽取过少降低关系抽取阈值检查提示词是否过度保守异常节点全是小度数节点图结构偏差过滤孤立小图换特征或算法可视化网页节点一多就卡顿渲染节点过多先按异常分数截断Top N或改用采样后渲染最后再说一个我在多次实操里验证过的体会这类“LLM 图 可视化”的项目工期分布往往不是想象中“LLM占一半、检测占一半、可视化占一点”而是“数据清洗和实体对齐占四成、检测调参占三成、LLM和可视化各占一成半”。如果你正准备启动类似项目建议把精力重点压在实体对齐和异常评估这两块它们才是决定最终效果的天花板。至于可视化能支持交互、能表达分数、能高亮异常子图就已经赢了。本文还有配套的精品资源点击获取