Files

27 KiB
Raw Permalink Blame History

天璇 (TianXuan) 分析工作流

从原始 TLS 流 CSV 到实体画像、聚类结果、区分特征的完整流水线文档。


目录

  1. 数据准备
  2. 数据加载与配置
  3. 数据校验
  4. 数据清洗
  5. 实体识别
  6. 类型感知聚合
  7. 聚类分析
  8. 特征提取
  9. 可视化
  10. LLM 集成
  11. 跨机部署

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_ipsource_ipsrc_addr
目的 IP dst_ipdest_ipdestination_ipdst_addrip_dst
字节数(上行) bytes_sentsrc_bytesbytes_outup_bytesupload
字节数(下行) bytes_revbytes_receiveddst_bytesdown_bytesdownload
持续时间 durationdurelapsedtime_deltaflow_duration
包数 packetspktspacket_counttotal_packets
TLS 版本 tls_versionversionssl_versiontlsver
加密套件 cipher_suiteciphertls_cipherssl_cipher
SNI sniserver_nametls_sni
协议 protoprotocoll4_protoip_proto
目的端口 dst_portdestination_portdest_portport_dst
目标 URL dst_urlurluri
时间戳 timestamptstimedatetimefirst_seen
纬度 latlatitudelatitudy
经度 lonlnglongitudelongitudx

列名通过下划线分词匹配关键词(如 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 counthex 对数统计)
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↔FLOATENUM↔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% 检查数据源是否漏采集;考虑在预处理阶段 fillnadrop
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()

  1. 扫描 schema 通过关键词匹配找到可用列
  2. 通过 type_classifier 获取每列的 DataType 分类
  3. 按数据类型选择对应的聚合函数

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_counttotal_bytes_senttotal_bytes_revavg_durationtotal_packets 实体流量基本量
目标多样性 unique_dst_ipsunique_dst_portsunique_dst_networks 目的地址分布广度
TLS 特征 unique_cipherstls_version_modeunique_versionssni_count 加密和证书多样性
协议特征 unique_protocolstcp_flag_modes 协议和标志分布
地理位置 avg_latitudeavg_longitude 经纬度均值
时间模式 first_seenlast_seenactive_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 预处理步骤

聚类执行前的自动预处理流程:

  1. 列过滤:只保留请求的特征列
  2. 全 NaN 列丢弃:无用列自动移除
  3. NaN 填充:数值 NaN 用该列均值填充
  4. StandardScaler:所有特征标准化(零均值、单位方差)
  5. 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 合并为一个实体节点。 支持 /240-255)和 /28240-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_latitudeavg_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 获取跨机问题深度排查指南。