# 天璇 (TianXuan) 分析工作流 > 从原始 TLS 流 CSV 到实体画像、聚类结果、区分特征的完整流水线文档。 --- ## 目录 1. [数据准备](#1-数据准备) 2. [数据加载与配置](#2-数据加载与配置) 3. [数据校验](#3-数据校验) 4. [数据清洗](#4-数据清洗) 5. [实体识别](#5-实体识别) 6. [类型感知聚合](#6-类型感知聚合) 7. [聚类分析](#7-聚类分析) 8. [特征提取](#8-特征提取) 9. [可视化](#9-可视化) 10. [LLM 集成](#10-llm-集成) 11. [跨机部署](#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_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()` ```python 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 管道**(一键运行): ```bash runtime\python\python.exe manage.py run_pipeline data/*.csv ``` ### 2.2 列类型配置 在 `config/config.yaml` 中通过 `columns` 映射手动指定列类型。配置优先级高于自动推断: ```yaml 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 配置段控制自动经纬度填充和旧列丢弃: ```yaml 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 二进制查询格式)。 查询接口: ```python 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()` ```python 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 报告结构: ```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 配置中指定的旧列: ```python # 默认丢弃列 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` 辅助列: ```python # 计算每个值中空格分隔的 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 工具参数**: ```python build_entity_profiles( dataset_id="ds_...", entity_columns=["src_ip", "dst_ip"], # 多列复合键 auto_detect=false ) ``` **CLI 管道**目前仅支持单列实体键。 ### 5.3 IP 掩码聚类 在聚合阶段中,对 `DataType.IPv4` 类型的列自动生成 `/24` 子网信息: ```python # 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_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: ```python # 在聚合非数值列前 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 零样本降级 当样本数过少或全为缺失值时,系统自动降级: ```python 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 运行示例 ```bash # 完整管道(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中配置: ```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 列应用子网掩码。 ```python # 示例: 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| / (σ_cluster + σ_global)` | 考虑簇内方差,更保守 | ### 8.2 区分特征解读 返回的每个特征包含: ```json { "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 依赖): ```python # 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//` 使用纯 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//` 同时显示 PCA 散点图和每簇的 Top 区分特征。 ## 10. LLM 集成 ### 9.1 API 配置 通过 `config/config.yaml` 或 Web 界面「配置」页面设置: ```yaml 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]` 日志看哪个工具调用失败 | | 分析结果为空 | 实体检测失败 | 检查实体列是否正确,尝试手动指定 | **连通性测试**: ```bash # CLI 测试 runtime\python\python.exe scripts\test_llm_full.py # Web 测试 # 导航到「配置」页面 → 填写 LLM 配置 → 点击「测试连通性」 ``` --- ## 11. 跨机部署 ### 10.1 运行时隔离验证 **脚本**:`scripts/verify_runtime.py` 在不同机器上运行该脚本,输出应完全一致: ```bash # 每台机器各自执行 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` 对比两台机器的运行日志,定位第一个差异点: ```bash # 每台机器运行管道,生成日志 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 跨机一致性检查清单 当两台机器表现不一致时,逐项排查: ```bash # 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` 获取跨机问题深度排查指南。