27 KiB
天璇 (TianXuan) 分析工作流
从原始 TLS 流 CSV 到实体画像、聚类结果、区分特征的完整流水线文档。
目录
1. 数据准备
1.1 CSV 格式要求
天璇接收以 CSV 格式存放的 TLS 流记录文件。每行代表一条网络流。
| 要求 | 说明 |
|---|---|
| 分隔符 | 逗号 ,(默认),支持自定义分隔符 |
| 文件扩展名 | .csv |
| 多文件 | 支持 glob 通配符、**/*.csv 递归扫描、自动合并 |
| 压缩包 | .zip 文件自动解压(通过上传界面) |
| 文件数量 | 无硬性限制(合并时使用 Polars diagonal_relaxed 模式) |
1.2 编码建议
| 编码 | BOM 标记 | 自动检测 |
|---|---|---|
| UTF-8(推荐) | 无 / EF BB BF |
✅ 默认 |
| UTF-8 with BOM | EF BB BF |
✅ 自动检测 |
| UTF-16LE | FF FE |
✅ 自动检测 |
| UTF-16BE | FE FF |
✅ 自动检测 |
| Latin-1 | 无 | ✅ 手动指定 |
⚠️ 生产建议:始终使用 UTF-8 无 BOM 编码。跨机器部署时强烈建议通过
set PYTHONUTF8=1强制 Python 使用 UTF-8 模式(run.bat已包含该设置)。
1.3 推荐列
系统基于列名关键词自动识别以下维度:
| 维度 | 推荐列名(不区分大小写) |
|---|---|
| 源 IP | src_ip、source_ip、src_addr |
| 目的 IP | dst_ip、dest_ip、destination_ip、dst_addr、ip_dst |
| 字节数(上行) | bytes_sent、src_bytes、bytes_out、up_bytes、upload |
| 字节数(下行) | bytes_rev、bytes_received、dst_bytes、down_bytes、download |
| 持续时间 | duration、dur、elapsed、time_delta、flow_duration |
| 包数 | packets、pkts、packet_count、total_packets |
| TLS 版本 | tls_version、version、ssl_version、tlsver |
| 加密套件 | cipher_suite、cipher、tls_cipher、ssl_cipher |
| SNI | sni、server_name、tls_sni |
| 协议 | proto、protocol、l4_proto、ip_proto |
| 目的端口 | dst_port、destination_port、dest_port、port_dst |
| 目标 URL | dst_url、url、uri |
| 时间戳 | timestamp、ts、time、datetime、first_seen |
| 纬度 | lat、latitude、latitud、y |
| 经度 | lon、lng、longitude、longitud、x |
列名通过下划线分词匹配关键词(如
flow_duration匹配duration)。匹配算法见analysis/entity_aggregator.py中的_find_column()。
1.4 命名规范
- 不要求列名在所有文件中完全一致(默认
schema_strict=false) - 不同名的列自动取并集,缺失列填
null - 建议团队内统一列名,以获得最佳的自动识别效果
2. 数据加载与配置
2.1 加载入口
主函数:analysis/data_loader.py::load_csv_directory()
lf, schema, row_count, file_count, memory_mb = load_csv_directory(
glob_pattern="data/*.csv",
encoding="utf-8",
delimiter=",",
schema_strict=False,
recursive=False,
)
MCP 工具(通过 tool_registry.py 暴露):
load_data(csv_glob="data/*.csv", schema_strict=False, recursive=False)
CLI 管道(一键运行):
runtime\python\python.exe manage.py run_pipeline data/*.csv
2.2 列类型配置
在 config/config.yaml 中通过 columns 映射手动指定列类型。配置优先级高于自动推断:
columns:
src_ip: ipv4 # 强制识别为 IPv4
dst_ip: ipv4
bytes_sent: int # 强制为整数类型
bytes_rev: int
duration: float # 强制为浮点类型
tls_version: enum # 枚举类型(低基数分类变量)
cipher_suite: hex # 十六进制数据
dst_url: url # URL 字符串
lat: lat_lon # 地理纬度
lon: lat_lon # 地理经度
timestamp: timestamp # 时间戳
支持的类型字符串及映射:
| 配置值 | DataType |
聚合方式 |
|---|---|---|
int |
INT |
mean / sum |
float |
FLOAT |
mean / sum |
enum |
ENUM |
mode(众数) |
hex |
HEX |
count(hex 对数统计) |
url |
URL |
domain(提取域名) |
ipv4 |
IPv4 |
subnet(/24 网络) |
lat_lon |
LAT_LON |
mean |
timestamp |
TIMESTAMP |
min / max |
bool_enum |
BOOL_ENUM |
mode |
string |
STRING |
count |
2.3 Schema 容错
| 模式 | 行为 | 适用场景 |
|---|---|---|
schema_strict: true |
列名不一致时报 ValueError |
测试/质量门禁 |
schema_strict: false(默认) |
自动并集合并,缺失列填 null | 日常使用 |
建议:生产环境建议 schema_strict: true,提前发现列名漂移。
2.4 GeoIP 配置
config.yaml 中的 GeoIP 配置段控制自动经纬度填充和旧列丢弃:
data:
geoip:
enabled: true
ip_columns: ["src_ip", "dst_ip"] # 需查询的 IP 列
drop_columns: ["src_latitude", "src_longitude", # 加载后丢弃的旧列
"dst_latitude", "dst_longitude"]
GeoIP 数据文件:data/geoip_data.txt,内嵌约 23 个主要城市 IP 段(200 KB 二进制查询格式)。
查询接口:
from analysis.geoip import lookup
result = lookup("8.8.8.8") # → {lat: 37.386, lon: -122.083, city: "Mountain View", country: "US"}
⚠️ 旧列丢弃:如果 CSV 本身已包含
src_latitude/src_longitude等列(来自旧有系统的 GeoIP 结果),系统在加载后自动丢弃这些列,避免与新的 GeoIP 查询冲突。
3. 数据校验
3.1 校验入口
主函数:analysis/data_validator.py::validate()
from analysis.data_validator import validate, report
# 返回 JSON 报告
validation_result = validate("dataset_id", strict=False)
# 返回格式化字符串报告
print(report("dataset_id"))
校验项通过 [VALIDATE] 日志标记输出。
3.2 校验项说明
| 校验项 | 检测内容 | 判定标准 |
|---|---|---|
| 列类型矛盾 | schema 推断类型 vs classify_column 结果 | INT↔FLOAT、ENUM↔BOOL_ENUM 为良性差异;STRING 类型包容一切 |
| 缺失率 | 每列 null_count / total_rows |
>50% → 高风险;>30% → 警告 |
| 异常值 | 数值列 Z-score > 5 的比例 | >5% 触发警告 |
| 枚举分布 | ENUM/BOOL_ENUM 列的值频率 | 标记空白、+、- 等特殊符号 |
| IPv4 有效性 | IP 列每个值的合法性 | 无效 IP 比例 >50% → 高风险 |
3.3 校验报告解读
JSON 报告结构:
{
"valid": true, // 无高风险项时为 true
"dataset_id": "ds_abc123",
"total_rows": 5000,
"total_columns": 24,
"columns": {
"src_ip": {
"dtype": "String",
"inferred_type": "IPv4",
"null_rate": 0.02,
"warnings": []
},
"duration": {
"dtype": "Float64",
"inferred_type": "FLOAT",
"null_rate": 0.55,
"z_score_gt_5_ratio": 0.03,
"warnings": ["null rate 55.0% exceeds 50% (high risk)"]
}
},
"warnings": [...],
"risks": ["Column 'duration': null rate 55.0% exceeds 50% (high risk)"]
}
3.4 处理建议
| 发现问题 | 建议处理 |
|---|---|
| 缺失率 > 50% | 检查数据源是否漏采集;考虑在预处理阶段 fillna 或 drop |
| Z-score > 5 比例过高 | 检查是否有异常流量(DDoS、扫描等) |
| 无效 IPv4 比例高 | 检查列名是否错误(非 IP 列被匹配为 IPv4) |
枚举值异常(空白/+/-) |
这些值会在清洗阶段被规范化(见第 4 节) |
4. 数据清洗
数据清洗在 load_csv_directory() 中自动执行,分三个阶段。
4.1 旧经纬度丢弃
加载完成后,立即丢弃 GeoIP 配置中指定的旧列:
# 默认丢弃列
drop_columns = ["src_latitude", "src_longitude", "dst_latitude", "dst_longitude"]
# 只丢弃 schema 中实际存在的列
merged_lf = merged_lf.drop(existing_drop)
日志标记:[CLEAN] dropped columns: [...]
4.2 BOOL_ENUM 标准化
对 classify_schema() 标记为 DataType.BOOL_ENUM 的字符串列,执行以下变换:
| 原始值 | 标准化后 |
|---|---|
""(空字符串) |
null |
"+" |
"True" |
"-" |
"False" |
"t", "T", "true", "True" |
保持不变 |
"f", "F", "false", "False" |
保持不变 |
原生 Boolean 类型的列跳过此步骤(已经是标准格式)。
日志标记:[CLEAN] BOOL_ENUM columns normalised: [...]
4.3 HEX 预处理
对标记为 DataType.HEX 的列(如 cipher_suite),添加 {col}_hex_pairs_count 辅助列:
# 计算每个值中空格分隔的 hex 对数量
pl.col("cipher_suite").str.split(" ").list.len().alias("cipher_suite_hex_pairs_count")
该计数列在后续聚合中以 avg_hex_pairs_count 参与聚类特征。
日志标记:[CLEAN] HEX columns added hex_pairs_count: [...]
4.4 通用数值清洗
对于任何未被分类为 IPv4/URL/HEX/ENUM/LAT_LON 的字符串列,系统自动尝试转为 Float64:
| 原始值 | 清洗后 |
|---|---|
"123.45" |
123.45 |
"+" / "-" |
null |
| 空白/空字符串 | null |
"N/A" / "null" / "None" |
null |
| 其他不可解析字符串 | null |
这防止了"mean on str column"崩溃(当字符串列因数据伪影未能被早期类型检测捕获时), 无论后续数据中出现何种新伪影。
函数:analysis/entity_aggregator.py → _coerce_to_float()
日志标记:[CLEAN] numeric coercion applied to: [...]
5. 实体识别
5.1 自动检测原理
主函数:analysis/entity_detector.py::detect_entity_column()
采用三维打分算法,对所有 String/Categorical 列评分:
| 维度 | 分值 | 说明 |
|---|---|---|
| 名称匹配 | +3.0 | 列名(归一化后)精确匹配关键词或 _ 分隔匹配 |
| 唯一值比率 | 0~10 | unique_ratio = unique_count / non_null_count,在 [0.01, 0.50] 区间内得分最高 |
| 字符串类型 | +2.0 | Utf8 / String / Categorical 类型加分 |
| 高唯一性惩罚 | -5.0 | unique_ratio > 0.50 时扣分(可能是随机 ID) |
| 高空值惩罚 | ×0.1 | null 率 > 50% 时分数严重降低 |
关键词集(ENTITY_KEYWORDS):
src_ip, dst_ip, source_ip, destination_ip, ip,
sni, server_name, host, domain, user, client,
username, hostname, mac, email, session_id,
uid, id, src_addr, dst_addr
5.2 多列复合键
支持场景:当需要按 (src_ip, dst_ip) 或 (src_ip, sni) 等多列组合作为实体标识时。
MCP 工具参数:
build_entity_profiles(
dataset_id="ds_...",
entity_columns=["src_ip", "dst_ip"], # 多列复合键
auto_detect=false
)
CLI 管道目前仅支持单列实体键。
5.3 IP 掩码聚类
在聚合阶段中,对 DataType.IPv4 类型的列自动生成 /24 子网信息:
# entity_aggregator.py 中:
subnet_expr = (
pl.col(ip_col).str.split('.').list.slice(0, 3).list.join('.') + pl.lit('.0/24')
)
# → "192.168.1.0/24"
生成的 unique_24_networks 在聚类时作为特征输入,反映实体的网络分布广度。
6. 类型感知聚合
6.1 聚合原理
主函数:analysis/entity_aggregator.py::aggregate_by_entity()
- 扫描 schema 通过关键词匹配找到可用列
- 通过
type_classifier获取每列的DataType分类 - 按数据类型选择对应的聚合函数
6.2 数据类型→聚合函数映射
DataType |
聚合方式 | 特征命名 | 代码位置 |
|---|---|---|---|
| INT (整数) | mean() 均值 |
total_bytes_sent, total_packets |
build_aggregation_params() |
| FLOAT (浮点) | mean() 均值 |
avg_duration, avg_latitude |
build_aggregation_params() |
| ENUM (枚举) | mode().first() 众数 |
tls_version_mode, tcp_flag_modes |
build_aggregation_params() |
| HEX (十六进制) | str.extract_all().list.len().mean() 统计 hex 对数量 |
avg_hex_pairs_count |
build_aggregation_params() |
| URL (链接) | str.extract(r'https?://([^/]+)').n_unique() 统计唯一域名 |
unique_domains |
build_aggregation_params() |
| IPv4 (地址) | str.split('.').list.slice(0,3).join('.') + '.0/24'.n_unique() 子网多样性 |
unique_24_networks |
build_aggregation_params() |
6.3 聚合特征分类
所有生成的聚合特征分为 6 大类:
| 类别 | 特征 | 说明 |
|---|---|---|
| 流统计 | flow_count、total_bytes_sent、total_bytes_rev、avg_duration、total_packets |
实体流量基本量 |
| 目标多样性 | unique_dst_ips、unique_dst_ports、unique_dst_networks |
目的地址分布广度 |
| TLS 特征 | unique_ciphers、tls_version_mode、unique_versions、sni_count |
加密和证书多样性 |
| 协议特征 | unique_protocols、tcp_flag_modes |
协议和标志分布 |
| 地理位置 | avg_latitude、avg_longitude |
经纬度均值 |
| 时间模式 | first_seen、last_seen、active_hours_count |
活动时间段 |
6.4 类型安全防范
build_aggregation_params 在聚合前检查列的实际 Polars dtype:
# 在聚合非数值列前 cast
if schema.get(col_name) in ('Utf8', 'String'):
pl.col(col_name).cast(pl.Float64)
这解决了跨机"mean on str"错误(当同一数据在不同机器上被 Polars 推断为不同 dtypes 时)。
7. 聚类分析
7.1 算法选择
| 算法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| HDBSCAN(默认) | 未知簇数、存在噪声点 | 自动确定簇数;噪声点处理;无需预设 k | 对 min_cluster_size 敏感;大数据集计算慢 |
| KMeans | 已知簇数、球形分布 | 速度快;可预设 k | 需指定 n_clusters;对离群点敏感 |
7.2 参数建议
HDBSCAN 参数:
| 参数 | 默认值 | 说明 | 调整建议 |
|---|---|---|---|
min_cluster_size |
5 | 最小簇样本数 | 数据量大时增大(10~20);数据少时自动降级为 max(len//2, 2) |
min_samples |
None | 核心点邻域 | 默认与 min_cluster_size 相同;增大产生更保守的聚类 |
metric |
euclidean | 距离度量 | 高维特征时考虑 cosine |
cluster_selection_epsilon |
0.0 | 簇选择阈值 | 越大簇越少 |
KMeans 参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
n_clusters |
3 | 簇数 |
random_state |
42 | 随机种子(保证可复现性) |
7.3 零样本降级
当样本数过少或全为缺失值时,系统自动降级:
if len(data) < 3:
return {"n_clusters": 0, "n_noise": 0, "quality_metrics": {"note": "too few samples"}}
同样,全部为 NaN 的列在聚类前自动丢弃。
7.4 预处理步骤
聚类执行前的自动预处理流程:
- 列过滤:只保留请求的特征列
- 全 NaN 列丢弃:无用列自动移除
- NaN 填充:数值 NaN 用该列均值填充
- StandardScaler:所有特征标准化(零均值、单位方差)
- min_cluster_size 自适应:不超过样本数的 50%
7.5 质量评估
| 指标 | 范围 | 说明 |
|---|---|---|
| Silhouette Score | [-1, 1] |
>0.5 表示良好聚类;>0.7 表示优秀 |
| Davies-Bouldin Index | [0, +∞) |
越低越好(簇内紧凑、簇间分离) |
| Calinski-Harabasz Score | [0, +∞) |
越高越好 |
| Noise Ratio | [0, 1] |
HDBSCAN 特有的噪声点比例 |
7.6 CLI 运行示例
# 完整管道(HDBSCAN 默认)
runtime\python\python.exe manage.py run_pipeline data/complex_test.csv
# 指定 KMeans
runtime\python\python.exe manage.py run_pipeline data/*.csv --algo kmeans
# 手动指定实体列
runtime\python\python.exe manage.py run_pipeline data/*.csv --entity-col src_ip
8. 子网实体聚合
config.yaml中配置:
entity:
subnet_masks: [24, 28] # 子网掩码列表
ip_columns: ["src_ip", "dst_ip"] # 需要聚合的IP列
启用后,同一子网(如 10.0.1.0/24)下的所有 IP 合并为一个实体节点。 支持 /24(0-255)和 /28(240-255, 16个IP分组)两种掩码。
entity_aggregator.ip_to_subnet() 负责 IP 到 CIDR 子网前缀的转换,
apply_subnet_aggregation() 在 group_by 前对 IP 列应用子网掩码。
# 示例: IP → /24 子网
ip_to_subnet("192.168.1.42", 24) # → "192.168.1.0/24"
# 示例: IP → /28 子网
ip_to_subnet("192.168.1.42", 28) # → "192.168.1.32/28"
8. 特征提取
8.1 方法对比
主函数:analysis/tool_registry.py → _handle_extract_features()
| 方法 | 公式 | 适用场景 |
|---|---|---|
| Z-Score(默认) | (μ_cluster - μ_global) / σ_global |
簇中心 vs 全局均值的偏离程度 |
| ANOVA | ` | μ_cluster - μ_global |
8.2 区分特征解读
返回的每个特征包含:
{
"feature_name": "total_bytes_sent",
"cluster_label": 0,
"mean": 1234567.89, // 该簇均值
"std": 234567.89, // 该簇标准差
"global_mean": 987654.32, // 全局均值
"global_std": 765432.10, // 全局标准差
"distinguishing_score": 2.34, // Z-Score 或 ANOVA 分数
"distinguishing_method": "zscore"
}
解读方法:
| 分数范围 | 含义 |
|---|---|
| |Z| < 1.0 | 无明显区分力 |
| 1.0 ≤ |Z| < 2.0 | 中等区分力 |
| |Z| ≥ 2.0 | 强区分特征(在 Web 界面中以颜色高亮) |
8.3 地理分布可视化
如果聚合特征包含 avg_latitude 和 avg_longitude,聚类概览页会同时显示:
- PCA 散点图:实体在二维主成分空间的分布,颜色区分聚类
- 地理分布图:实体在地理坐标上的分布
地理位置在 Web 界面中通过 Canvas 2D API 原生绘制(无外部 JS 依赖):
# views.py 中提取逻辑
lat = _extract_lat(feature_json.get('avg_latitude'))
lon = _extract_lon(feature_json.get('avg_longitude'))
# 过滤无效值:纬度 -90~90,经度 -180~180
9. 可视化
9.1 PCA 散点图
路径: /clusters/<run_id>/
使用纯 Canvas 2D 绘制的散点图,展示实体在二维主成分空间中的分布。
点颜色按聚类标签区分,鼠标悬停显示实体名称。
依赖: run_pipeline 步骤 6 自动计算 PCA-2D 坐标并写入 EntityProfile 表。
9.2 3D 地球流量弧线
路径: /globe/
使用 Three.js (603KB, 全离线) 绘制的三维地球,显示源 IP 到目标 IP 的 TLS 流量弧线。
- 颜色: TLSv1.3 (蓝) / TLSv1.2 (粉) / 其他 (青)
- 弧线高度根据距离自动调整
- 鼠标拖拽旋转地球
- 地球贴图: 1.4MB (离线内嵌)
- 多数据源叠加: 页面复选框选择多个运行,弧线叠加在同一地球上
- 经纬度来源: 优先使用数据中
src_latitude/src_longitude/dst_latitude/dst_longitude列,无则回退GeoIP查询 - GeoIP: 从
data/geoip_city.csv查询 IP 对应经纬度 - 滚轮缩放: 鼠标滚轮控制相机距离(3-30)
- 经纬线: 10度间隔的白色半透明辅助线
- 国境线: world_borders.js 10国精简轮廓,含台湾和南海九段线示意
- 背面剔除:
updatePulses中使用localToWorld替代applyQuaternion,确保弧线脉冲只在面向相机的一侧显示,不穿透地球 - 脉冲动画: 弧线上移动的贝塞尔曲线脉冲光点,
arcGroup.localToWorld(midPt)计算世界坐标,midPt.dot(camDir) < -0.08判段正面可见性 - 多数据选择: 复选框选择多个运行叠加弧线
- 经纬度来源: 优先使用数据列 src_latitude/src_longitude 等,无则GeoIP回退
9.3 聚类概览页
路径: /clusters/<run_id>/
同时显示 PCA 散点图和每簇的 Top 区分特征。
10. LLM 集成
9.1 API 配置
通过 config/config.yaml 或 Web 界面「配置」页面设置:
llm:
enabled: true # 启用 LLM 功能
base_url: "https://api.openai.com/v1" # OpenAI 兼容 API 地址
api_key: "sk-..." # API Key
model: "deepseek-v4-flash" # 模型名称
max_tokens: 2048
temperature: 0.1
支持的 LLM 服务:
- OpenAI GPT-4 / GPT-3.5
- Azure OpenAI Service
- 本地部署的 Ollama / vLLM / LM Studio
- 其他 OpenAI 兼容 API
9.2 32B 模型优化
系统针对 32B 等较大模型的推理特性做了以下优化(tianxuan/llm_orchestrator.py):
| 优化项 | 实现 | 说明 |
|---|---|---|
| Step Guidance | 每步告诉模型下一步做什么 | 减少 32B 模型在复杂推理中的发散 |
| 工具结果截断 | 截断至 1200 chars | 避免上下文窗口溢出 |
| 超时 | 90s | 32B 推理更慢,比默认超时长 |
| 最大步数 | 15 步 | 限制过长的工具调用链 |
| 工具描述 | 一行简洁描述 | 减少冗长描述对 token 的消耗 |
9.3 LLM 分析流程
自动化编排调用链:
Step 1: profile_data("ds_...") → 了解数据集的列统计
Step 2: build_entity_profiles("ds_...") → 实体聚合
Step 3: run_clustering("entity_...") → 聚类分析
Step 4: extract_features("entity_...") → 特征提取
Step 5: 总结分析结果
9.4 日志链解读
LLM 交互日志以 [LLM] 和 [TOOL] 标记输出到 logs/tianxuan.log:
[LLM] model=deepseek-v4-flash # 模型名
[LLM] latency_ms=3245 # LLM 响应延迟
[LLM REQUEST] ... # 请求内容(缩减后)
[LLM RESPONSE] ... # 响应状态
[TOOL] profile_data # LLM 调用的工具
[TOOL] build_entity_profiles
[TOOL] run_clustering
[TOOL] extract_features
9.5 故障排查
| 问题 | 常见原因 | 排查方法 |
|---|---|---|
| 400 错误 | API 地址错误 / Key 无效 | 配置页「测试连通性」 |
| 超时 | 32B 推理慢 / 网络延迟 | 检查 latency_ms;增大 max_tokens |
| 工具调用失败 | LLM 返回格式错误 | 检查 [TOOL] 日志看哪个工具调用失败 |
| 分析结果为空 | 实体检测失败 | 检查实体列是否正确,尝试手动指定 |
连通性测试:
# CLI 测试
runtime\python\python.exe scripts\test_llm_full.py
# Web 测试
# 导航到「配置」页面 → 填写 LLM 配置 → 点击「测试连通性」
11. 跨机部署
10.1 运行时隔离验证
脚本:scripts/verify_runtime.py
在不同机器上运行该脚本,输出应完全一致:
# 每台机器各自执行
runtime\python\python.exe scripts\verify_runtime.py
验证内容:
| 检查项 | 期望值 |
|---|---|
| Python 版本 | 3.12.x |
| ENABLE_USER_SITE | False(隔离) |
| django | 4.2.x |
| polars | 1.42.x |
| sklearn | 1.5.x |
| numpy | 1.26.x |
| defaultencoding | utf-8 |
| filesystemencoding | utf-8 |
10.2 diagnose_compare 使用
脚本:scripts/diagnose_compare.py
对比两台机器的运行日志,定位第一个差异点:
# 每台机器运行管道,生成日志
del logs\tianxuan.log
runtime\python\python.exe manage.py run_pipeline data\complex_test.csv
copy logs\tianxuan.log log_机器A.txt
# 在开发机上对比
runtime\python\python.exe scripts\diagnose_compare.py log_开发机.txt log_机器A.txt
输出示例:
[DIFF] 首个差异在行 123:
--- 机器A ---
[LOAD_SCHEMA] col=duration dtype=Utf8 sample=1.2s,0.5s,3.1s
--- 机器B ---
[LOAD_SCHEMA] col=duration dtype=Float64 sample=1.2,0.5,3.1
首个 [DIFF] 行就是问题根源。
10.3 关键日志标记
| 标记 | 对应文件 | 含义 |
|---|---|---|
[LOAD] |
data_loader.py |
CSV 加载完成 |
[LOAD_SCHEMA] |
data_loader.py |
每列的推断类型(最重要!) |
[CLEAN] |
data_loader.py |
清洗操作记录 |
[VALIDATE] |
data_validator.py |
数据校验结果 |
[FIND_COL] |
entity_aggregator.py |
关键词匹配结果 |
[CLUSTER_INPUT] |
tool_registry.py |
聚类输入形状 |
[GEOIP] |
geoip.py |
GeoIP 数据加载 |
[LLM] |
llm_orchestrator.py |
LLM 调用状态 |
[TOOL] |
llm_orchestrator.py |
LLM 调用的工具 |
[DB_SAVE_ERROR] |
tool_registry.py |
数据库持久化错误 |
10.4 跨机一致性检查清单
当两台机器表现不一致时,逐项排查:
# 1. Python 版本
runtime\python\python.exe --version
# 2. 编码设置
runtime\python\python.exe -c "
import sys; print(f'defaultenc={sys.getdefaultencoding()}')
print(f'fsenc={sys.getfilesystemencoding()}')
print(f'utf8_mode={sys.flags.utf8_mode}')
"
# 3. Polars 版本
runtime\python\python.exe -c "import polars; print(polars.__version__)"
# 4. 依赖版本
runtime\python\python.exe -c "import numpy; print(numpy.__version__)"
runtime\python\python.exe -c "import sklearn; print(sklearn.__version__)"
10.5 故障诊断流程
出现跨机差异
│
├─ Step 1: 收集两台机器的 logs/tianxuan.log
│
├─ Step 2: 运行 diagnose_compare.py 对比日志
│ └─ 找到首个 [DIFF]
│
├─ Step 3: 判断差异类型
│ ├─ [LOAD_SCHEMA] 列类型不同
│ │ └─ 检查 Polars 版本一致、PYTHONUTF8 生效
│ │
│ ├─ [FIND_COL] 关键词匹配不同
│ │ └─ 确认 frozenset→tuple 修复已应用(消除哈希随机性)
│ │
│ └─ [CLUSTER_INPUT] 聚类结果不同
│ └─ 检查 random_state=42、StandardScaler 一致性
│
├─ Step 4: 应急修复
│ ├─ 手动指定实体列:--entity-col src_ip
│ ├─ 强制编码:set PYTHONUTF8=1
│ └─ 使用 tuple 替代 frozenset(已在 entity_aggregator.py 中修复)
│
└─ Step 5: 归档诊断信息
( echo === 天璇故障诊断报告 ===
date /t & time /t
runtime\python\python.exe --version
type logs\tianxuan.log
) > 诊断报告_%COMPUTERNAME%.txt
10.6 已知跨机问题(已修复)
| 问题 | 根因 | 修复 |
|---|---|---|
| "mean on str" 错误 | frozenset 哈希随机性 | frozenset → tuple 确定性迭代 |
| "ZZZ不在{XXX,YYY}中" 错误 | Python 编码不一致 | PYTHONUTF8=1 强制 UTF-8 |
| DB 特征不保存 | Django ORM 在 async 上下文中的限制 | sync_to_async 包装 |
| 图表不显示 | Chart.js 从未下载 | 改用纯 Canvas 2D API |
更多技术细节:请参阅
docs/故障诊断手册.md获取跨机问题深度排查指南。