GravityAgent(五):用 LangGraph 编排 26 个测绘工具

GravityAgent(五):用 LangGraph 编排 26 个测绘工具


IPv6 网络测绘 GravityAgent Agent LLM
本文属于系列

用 LangGraph 编排 26 个测绘工具

前面几篇讲了算法层、扫描引擎和 OS 识别。这一篇我们来看把它们串起来的Agent 编排层——agent/agent.py。

为什么测绘平台需要一个 Agent

传统的测绘工具是”参数式”的:你得记住一堆命令行参数,知道每个算法的输入格式,还要手动把输出接到下一步。这对研究者来说没问题,但对平台使用者来说门槛太高。

Agent 的价值在于:用户只需要说人话,系统自动选择工具、串联流程、汇总结果。

比如:

识别 202.194.68.10 的操作系统
对 UJN 的存活地址用 6Forest 做模式挖掘
为 CERN 生成 IPv6 候选地址并提交扫描
用 /path/to/seeds.txt 生成候选地址并扫描

这些请求会被 Agent 翻译成一系列工具调用。

技术选型:LangGraph ReAct

agent.py 基于 LangGraph 的 create_react_agent 构建,配合 LangChain 的 @tool 装饰器把 Python 函数注册成工具。

# 框架:LangGraph create_react_agent + LangChain @tool
# LLM:ChatOpenAI(model="deepseek-chat", temperature=0)

为什么用 ReAct 模式?因为测绘任务天然是多步的:

  1. 先查组织信息,拿到前缀;
  2. 再拉存活地址作为种子;
  3. 然后调用算法挖掘模式;
  4. 基于模式生成候选;
  5. 最后提交扫描。

ReAct 的”思考—行动—观察”循环正好适合这种链式任务。temperature=0 保证同样的输入尽量得到同样的工具调用路径。

网络层的细节

agent.py 显式构造了 httpx.Client/AsyncClient,并配置了代理和超时:

# 代理 http://127.0.0.1:7897
# 读写超时 LLM_HTTP_TIMEOUT_SEC(默认 7200s)
# 绕过环境中的 socks5h 代理

超时设置为 7200 秒(2 小时)是必要的——6Sense 训练一次可能要几十分钟,如果 HTTP 客户端提前超时,整个流程就断了。

26 个工具,六大类

Agent 绑定了 26 个工具,可以分为六类:

类别工具
组织情报list_organizations、query_org_stats、query_org_alive_ips
模式挖掘mine_org_patterns_6graph、mine_org_patterns_6forest、mine_org_patterns_entropy_ip
候选生成predict_org_candidates_6graph/6forest/entropy_ip/6sense、predict_candidates_from_file
扫描提交/查询submit_scan、submit_scan_prefix、get_batch_status、get_alive_ips
DNS 发现start_dns_server_discovery、list_dns_server_discovery_runs、get_dns_server_discovery_status、get_dns_server_discovery_results
OS 识别identify_os_single、identify_os_batch、identify_os_from_scan_batch、list_os_identify_runs、get_os_identify_status、get_os_identify_results、generate_os_confidence_report

系统提示词的约束

工具能不能被正确调用,很大程度上取决于系统提示词。SYSTEM_PROMPT 里明确规定了:

  1. 用户要求”操作系统识别 / OS 指纹”时,调用 identify_os_single 或 identify_os_batch;
  2. 用户要求”生成可信度报告”时,调用 generate_os_confidence_report;
  3. 默认预测用 6Graph;
  4. 用户明确指定算法时切换到对应工具;
  5. 用户直接提供本地种子文件时优先调用 predict_candidates_from_file;
  6. 不允许在未真实调用工具时伪造文件路径或 batch id。

最后一条特别重要——LLM 很容易”编造”一个看起来合理的 batch_id 或文件路径,必须用提示词明确禁止。

候选生成算法

预测类工具的核心是”从模式生成地址”。这里有两套实现。

通配符模式采样(6Graph / 6Forest)

模式里 * 表示自由半字节。生成时按 seed_count 加权选择模式,再把 * 随机填 0–f:

# 模式池按 seed_count 加权(copies = min(32, seed_count))
# random.Random(20260309) 固定种子保证可复现
# 循环:从池中轮询模式 → 填 * → 去重 → 前缀内校验
# 直到凑满 budget(最大尝试 max(10000, budget*300))

用固定随机种子 20260309 是一个刻意的设计:同样的输入应该得到同样的输出,方便复现和调试。

Entropy/IP 规则采样

Entropy/IP 输出的是分段规则。生成时按段分组,每段保留覆盖率最高的规则:

# 规则按 (segment, bit_start, bit_stop) 分组
# bit 区间换算半字节区间
# 每段保留覆盖率最高的 max_rules_per_segment(默认 8)条
# 每次尝试:初始化 32 个 "0",逐段按覆盖率加权 rng.choices 选规则
#   range 型:在 [value_start, value_stop] 随机取十六进制值
#   const 型:取固定值
# 拼成 128 位地址 → 去重 + 前缀过滤

预测 → 自动提交扫描的闭环

这是整个 Agent 层最有价值的设计。所有 predict_* 工具在返回给 LLM 的同一轮调用内完成闭环:

1. 生成候选(通配符采样 / Entropy 规则 / 6Sense 输出文件)
        │
        ▼
2. 超过阈值落盘到 download/<uid>/candidates/
        │
        ▼
3. _auto_submit_prediction_candidates
   批名 {算法}_{yyyyMMdd_HHMMSS}
   用专用账号 realtime/realtime 调 _submit_batch_file
   上传 runs/agent/tmp/predict_*.csv(首行 ip)
        │
        ▼
4. _submit_batch_file
   httpx multipart 优先(60s 超时)
   → curl 回退(--retry 2 --retry-connrefused)
   → 解析 batch_id
        │
        ▼
5. _append_prediction_submit_result
   把"提交者/批名/batch_id"追加进返回文本

这意味着用户说”为 CERN 生成候选地址并扫描”,Agent 不是只生成一个文件就完事,而是真的把候选地址提交进了扫描队列,并返回一个可以追踪的 batch_id。

用户随后可以说”查一下这个批次的状态”,Agent 会调用 get_batch_status(batch_id)。

为什么用 curl 回退

_submit_batch_file 优先用 httpx,失败时回退到 curl。原因是某些环境下 Python 的 HTTP 库和系统代理配置不兼容,而 curl 的行为更可预测。--retry 2 --retry-connrefused 则能容忍扫描服务的短暂重启。

数据源回退链

Agent 需要读取扫描数据,但数据可能在多个地方。系统设计了一条回退链:

批次列表

scanner API /api/batch
    → postgres_fallback.pg_list_batches(psql 直查)
    → 本地 master.db

存活地址

API /api/batch/{id}/txt
    → 本地 data/batch_<id>.db
        (scan_results / service_observations 表 DISTINCT ip)

按组织取存活地址

_get_alive_ips_by_org 遍历所有批次 SQLite 按 org 列精确匹配,本地无结果时拉取最多 200 个批次的 export 过滤。

这条回退链的意义是:即使 scanner 服务暂时不可用,Agent 依然能从本地数据工作。

组织前缀二次过滤

扫描结果里的 org 字段质量并不总是可靠,所以 _get_alive_ips_for_org 会结合 org_ipv6_mapping.json 做二次清洗:

def _filter_ips_by_prefixes(ips, prefixes):
    # 用 ipaddress.IPv6Network 判断 IP 是否落在组织前缀内

org_ipv6_mapping.json 里维护了 9 个组织的 ASN 和前缀映射,包括 CERNET、UJN、QLU、Google、Cloudflare、Amazon、Alibaba、China Telecom、China Unicom。

长任务与等待体验

测绘任务动辄几分钟到几十分钟。如果 Web 端同步等待,用户会以为页面卡死了。

解决方案是后台 job + 细分等待文案:

地址生成预测任务执行中
操作系统识别任务执行中
模式挖掘任务执行中
批次扫描提交任务执行中
扫描结果查询任务执行中
后台任务执行中

后端根据用户意图推断 pending_label,前端轮询时同时展示”已等待 xx 秒”。

防幻觉设计

LLM 最危险的行为是”假装调用了工具”。针对这个问题,系统做了两层防护。

第一层:提示词约束

系统提示词明确禁止伪造文件路径和 batch_id。

第二层:后端兜底

当用户上传附件并要求预测时,如果模型没有真正发起预测类 tool call,后端会直接执行:

# 识别"用户上传附件并要求做预测"的请求
# 如果模型没有真正发起预测类 tool call
# 则后端直接执行 predict_candidates_from_file
# 使用真实执行结果覆盖原本可能幻觉的自然语言回复

这解决了一个具体问题:模型可能会编造”已成功生成 1000 个候选地址”,而实际上什么都没做,Web 又去归档一个根本不存在的文件。后端兜底确保回复里提到的文件一定真实存在。

文件归档

当 Agent 回复中包含绝对路径时,Web 会:

  1. 等待文件真正落盘;
  2. 复制到 download/<uid>/;
  3. 将回复文本中的原始路径替换成用户可见的归档项。

如果文件不存在或复制失败,就显示为:

未归档文件(<文件名>)

这个”未归档”标记本身就是一种可观测性——它明确告诉用户”这个文件有问题”,而不是假装成功。

运行期路径规范

Agent 的所有产物路径统一由 runtime_paths.py 管理,并且实现了用户隔离:

download/<uid>/
├── os_identify/          # OS 识别报告(JSON/Markdown)
├── candidates/           # 候选地址生成结果
├── patterns/             # 模式挖掘结果
├── alive_ips/            # 存活地址导出
├── batch_results/        # 批次扫描结果
└── service_probe/        # 服务探测结果

用户上下文通过 set_current_user(uid) / get_current_user() 管理,Web 请求期间会被设置为实际用户。

一个完整的调用链

把上面所有东西串起来,用户说”为 UJN 生成候选地址并扫描”时,实际发生的是:

用户输入
  │
  ▼
LLM 判断意图 → 选择 predict_org_candidates_6graph
  │
  ▼
_get_alive_ips_for_org("UJN")
  ├─ 遍历批次 SQLite 按 org 匹配
  ├─ 用 org_ipv6_mapping.json 前缀二次过滤
  └─ 得到种子地址列表
  │
  ▼
SixGraphMiner.mine_patterns(seeds)
  ├─ 动态加载 6Graph 模块(清理同名缓存)
  ├─ DHC 区域划分 + OutlierDetect
  └─ 输出 pattern dict 列表
  │
  ▼
_generate_candidates_from_wildcard_patterns
  └─ 按 seed_count 加权采样,生成候选地址
  │
  ▼
落盘 download/<uid>/candidates/6graph_UJN_<ts>.txt
  │
  ▼
_auto_submit_prediction_candidates
  └─ POST /api/batch → 拿到 batch_id
  │
  ▼
返回文本:候选数量 + 文件路径 + batch_id

小结

Agent 编排层的设计要点:

设计解决的问题
LangGraph ReAct多步测绘任务自动串联
temperature=0工具调用路径稳定可复现
固定随机种子候选生成结果可复现
26 个工具分类意图到能力的映射清晰
自动提交闭环预测不止于文件,而是真的扫描
数据源回退链scanner 挂了也能工作
前缀二次过滤修正不可靠的 org 标签
后台 job + 文案长任务不再像卡死
双层防幻觉回复里的文件一定真实存在

下一篇,我们来看 Web 多用户平台,看看这套能力是怎么被安全地开放给多个用户的。