Gallind/OSINT-Military-Bases-Analyzer
GitHub: Gallind/OSINT-Military-Bases-Analyzer
一个基于LangGraph多智能体协作的军事基地卫星图像OSINT分析流水线,通过八个视觉LLM分析师、指挥官和战略家三级架构实现自动化威胁评估与跨基地模式识别,并以实时交互地图呈现结果。
Stars: 0 | Forks: 0
# OSINT 军事基地分析器
一个多智能体 OSINT 流水线,用于获取军事基地的卫星图像,对每个基地运行 **8 个视觉 LLM 分析师**,通过 **指挥官** 智能体综合他们的发现,通过 **战略家** 智能体生成跨基地模式,并在实时 **Streamlit + Folium** 仪表板中将所有内容可视化。
为 *From Idea to App*(赖希曼大学,第 4 学期,作业 2)构建。
## 目录
- [功能介绍](#what-it-does)
- [架构](#architecture)
- [技术栈](#tech-stack)
- [项目布局](#project-layout)
- [设置](#setup)
- [运行流水线](#running-the-pipeline)
- [运行仪表板](#running-the-dashboard)
- [配置](#configuration)
- [输出 schema (`data.json`)](#output-schema-datajson)
- [测试](#tests)
- [注意事项](#gotchas)
## 功能介绍
给定一个包含军事基地的 CSV 文件,流水线将执行以下操作:
1. **获取图像**:从 Google Maps Static API(主要)和 Sentinel Hub(Sentinel-2 真彩色,深度视图)获取每个基地的图像。
2. **运行 8 个分析师智能体**:在 LangGraph 循环中运行。每个分析师检查当前画面,提取发现及其置信度,并选择一个动作(`zoom-in`、`zoom-out`、`move-left`、`move-right` 或 `finish`)。系统会据此重新获取下一帧画面。
3. **调用指挥官智能体**:将 8 份分析师报告整合为一份 `threat_assessment`(HIGH / MEDIUM / LOW / UNKNOWN),包括高共识发现(3 名以上分析师同意,平均置信度 ≥ 7)、有争议的发现以及建议的后续行动。
4. **调用战略家智能体**:在运行结束时调用,以揭示跨基地模式、优先基地及整体评估。
5. **持久化所有数据**:将所有数据保存到 `data.json`(原子写入,仅追加的运行历史记录)并在仪表板中呈现。
仪表板将分析器作为子进程启动,在运行期间每 2 秒轮询一次 `data.json`,并包含一个“Ask AI”选项卡,该选项卡以图像和报告作为上下文,回答关于所选基地的自由格式问题。
## 架构
```
military_bases.csv
│
▼
┌───────────────────────────────────────┐
│ base_analyzer.py │
│ (orchestrator: per-base loop, run │
│ versioning, commander, strategist) │
└───────────────────────────────────────┘
│
▼
┌───────────────────────────────────────┐
│ pipeline.py │
│ LangGraph StateGraph │
│ │
│ fetch_image ──► run_analyst ──► │
│ ▲ │ │
│ │ ▼ │
│ └──── decide_next ──► END │
└───────────────────────────────────────┘
│ │
┌───────────┘ └──────────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ imagery.py │ │ llm_client.py│
│ │ │ │
│ Google Maps │ │ gemini / │
│ Sentinel Hub │ │ openai / │
│ Moondream │ │ qwen factory │
└──────────────┘ └──────────────┘
│
▼
┌────────────────┐
│ storage.py │
│ atomic JSON │
│ run versioning │
└────────────────┘
│
▼
data.json
│
▼
┌────────────────┐
│ app.py │
│ Streamlit + │
│ Folium map + │
│ Ask AI tab │
└────────────────┘
```
每个分析师的写入都是通过 `graph.stream()`(而不是 `.invoke()`)进行流式传输的,以便仪表板可以实时看到每个分析师的进度。
## 技术栈
| 层级 | 库 / 服务 | 备注 |
| --- | --- | --- |
| 图像 — 主要 | Google Maps Static API | REST → JPEG;需要 `GOOGLE_MAPS_KEY` |
| 图像 — 深度视图 | `sentinelhub-py` 3.11.5 | 通过 Copernicus Data Space 获取 Sentinel-2 L2A |
| 图像 — 描述生成 | Moondream 3 cloud (`moondream`) | 可选的快速预传递;需要 `MOONDREAM_API_KEY` |
| 分析师 LLM | `google.genai` (Gemini) | 使用 `response_schema` 进行结构化输出的 `gemini-3.1-pro-preview` |
| 指挥官 / 战略家 | `openai` SDK | 通过 `beta.chat.completions.parse` 调用 `gpt-5.5` |
| 免费视觉替代方案 | OpenRouter (`qwen3-vl-32b`) | 通过 `PROVIDER="qwen"` 直接替换 |
| 多智能体循环 | `langgraph` 1.1.10 | StateGraph;流式传输 |
| 仪表板 | `streamlit` 1.57 + `streamlit-folium` | 使用 Folium MarkerCluster 处理重叠的基地 |
| 验证 | `pydantic` 2.x | 原生结构化输出(无 `instructor` —— 参见注意事项) |
## 项目布局
```
.
├── base_analyzer.py # main orchestrator — entry point
├── pipeline.py # LangGraph nodes + commander/strategist callers
├── llm_client.py # provider factory: gemini | openai | qwen
├── imagery.py # Google Maps, Sentinel Hub, Moondream fetchers
├── storage.py # atomic data.json reads/writes + run versioning
├── models.py # AnalystReport, CommanderReport, StrategistReport
├── app.py # Streamlit dashboard
├── tests/ # 13 unit tests (models, storage, consensus)
├── military_bases.csv # input dataset
├── data.json # output (gitignored, append-only run history)
├── screenshots/ # JPEGs per base (gitignored)
├── requirements.txt
├── .env.example # template — copy to .env and fill in keys
└── CLAUDE.md # project instructions for Claude Code
```
## 设置
```
# 克隆并进入项目
cd OSINT-Military-Bases-Analyzer
# 创建 virtualenv(推荐 Python 3.12+)
python3 -m venv .venv
source .venv/bin/activate
# 安装依赖
pip install -r requirements.txt
# 配置 secrets
cp .env.example .env
# 然后编辑 .env 并填入你的 API keys(见下文)
```
### 必需的环境变量
```
GOOGLE_MAPS_KEY= # required — primary imagery
GEMINI_API_KEY= # required — analyst + commander vision LLM
OPENAI_API_KEY= # required — commander + strategist (gpt-5.5)
OPENROUTER_API_KEY= # optional — only if PROVIDER="qwen"
MOONDREAM_API_KEY= # optional — caption pre-pass
SENTINELHUB_CLIENT_ID2= # optional — deep Sentinel-2 imagery
SENTINELHUB_CLIENT_SECRET2= # optional — paired with the above
```
## 运行流水线
```
# 完整运行(使用 base_analyzer.py 中的 ROWS_TO_PROCESS)
.venv/bin/python base_analyzer.py
# 全新运行 — 先清除之前的输出
rm data.json && .venv/bin/python base_analyzer.py
```
流水线在单次运行中是**幂等的**:最新运行中标记为 `status="complete"` 的基地将被跳过,因此您可以安全地中断并重新运行。
## 运行仪表板
```
.venv/bin/streamlit run app.py
# → 打开 http://localhost:8501
```
功能:
- **Folium 地图**:每个基地一个标记,缩小视图时自动聚合(`disableClusteringAtZoom=8`),因此同处一地的基地(例如 0.17° 内的四个埃及 SA-2 阵地)仍可单独选择。
- **运行选择器**:浏览 `data.json` 中的历史运行。运行进行时锁定为最新一次运行。
- **详情面板**:屏幕截图、所有 8 份分析师报告、指挥官摘要、威胁级别。
- **Ask AI 选项卡**:关于所选基地的自由格式问答(使用 Gemini,将图像和报告作为上下文注入)。
- **实时轮询**:在分析器子进程存活期间,执行 2 秒间隔的 `st.rerun()` 循环。
您也可以直接从仪表板的“Run analysis”按钮启动分析器。
## 配置
`base_analyzer.py` 的顶部:
```
ROWS_TO_PROCESS = 20 # 1 for testing, full CSV otherwise
NUM_ANALYSTS = 8
PROVIDER = "gemini" # "gemini" | "openai" | "qwen"
ANALYST_MODEL = "gemini-3.1-pro-preview"
COMMANDER_PROVIDER = "openai"
COMMANDER_MODEL = "gpt-5.5"
STRATEGIST_PROVIDER = "openai"
STRATEGIST_MODEL = "gpt-5.5"
DEFAULT_ZOOM = 17
ZOOM_DELTA = 1
LAT_LNG_DELTA = 0.01
```
调整 `ROWS_TO_PROCESS` 以控制单次运行中处理多少行 CSV 数据。
## 输出 schema (`data.json`)
```
{
"runs": [
{
"run_id": "2026-05-09T13:10:42",
"started_at": "...",
"completed_at": "...",
"strategist": {
"cross_base_patterns": ["..."],
"priority_bases": [147, 1059],
"overall_assessment": "..."
},
"bases": [
{
"id": 147,
"country": "Egypt",
"latitude": 23.954,
"longitude": 32.995,
"status": "complete", // "pending" | "in_progress" | "complete"
"moondream_caption": "...",
"screenshots": ["screenshots/147/static_z17.jpg", "screenshots/147/sentinel.jpg"],
"analysts": [ /* 8 × AnalystReport */ ],
"commander": { /* CommanderReport */ }
}
]
}
]
}
```
运行记录是**追加的,永远不会被覆盖**。仪表板默认读取 `runs[-1]`。
### Pydantic 模型 (`models.py`)
```
class AnalystReport(BaseModel):
findings: list[str]
finding_confidences: list[int] # same length as findings
confidence: int # overall 1–10
analysis: str
things_to_continue_analyzing: list[str]
action: Literal["zoom-in", "zoom-out", "move-left", "move-right", "finish"]
status: Literal["done", "failed"] = "done"
error: str | None = None
class CommanderReport(BaseModel):
summary: str
threat_assessment: Literal["HIGH", "MEDIUM", "LOW", "UNKNOWN"]
high_consensus_findings: list[str] # 3+ analysts agree, avg conf ≥ 7
contested_findings: list[str]
recommended_next_actions: list[str]
class StrategistReport(BaseModel):
cross_base_patterns: list[str]
priority_bases: list[int]
overall_assessment: str
```
## 测试
```
.venv/bin/python -m pytest tests/ -v
# 13/13 通过
```
- `tests/test_models.py` —— Pydantic 模型验证(4 个测试)
- `tests/test_storage.py` —— 原子化 JSON 读写,运行版本控制(6 个测试)
- `tests/test_consensus.py` —— 指挥官共识评分(3 个测试)
## 注意事项
在修改任何内容之前,值得了解的一些棘手问题。
### LLM 客户端
- **`instructor` 在此环境中已损坏** —— 由于 `mistralai` 版本冲突导致导入失败。请改用原生结构化输出 API(`google.genai` 的 `response_schema` 和 `openai` 的 `beta.chat.completions.parse`)。
- **Gemini**:从 `google.genai` 导入,而不是已弃用的 `google.generativeai`。
- **`gemini-3.1-flash-image-preview`** 是一个*图像生成*模型。对于视觉*分析*,请使用 `gemini-3.1-pro-preview`。
- **OpenAI**:指挥官/战略家只使用 `gpt-5.x` 模型 —— 绝不要使用 `gpt-4.x`。
### Sentinel Hub (Copernicus Data Space)
- 使用 `SENTINELHUB_CLIENT_ID2` 和 `SENTINELHUB_CLIENT_SECRET2` —— 原始的(`CLIENT_ID` / `CLIENT_SECRET`)对 CDSE 无效。
- 必须设置全部三个 `SHConfig` URL 字段:`sh_auth_base_url`、`sh_token_url`、`sh_base_url`。
- 仅设置 `config.sh_base_url` **不能**覆盖请求 URL。您还必须重新定义数据集合:
cdse_s2 = DataCollection.SENTINEL2_L2A.define_from(
"SENTINEL2_L2A_CDSE",
service_url="https://sh.dataspace.copernicus.eu",
)
### Streamlit 仪表板
- `DATA_PATH` 必须是绝对路径:`Path(__file__).parent / "data.json"`。
- “Run analysis”按钮必须使用绝对 Python 路径**以及**显式的 `cwd` 来启动分析器:
subprocess.Popen(
[str(PROJECT_ROOT / ".venv/bin/python"), "base_analyzer.py"],
cwd=str(PROJECT_ROOT),
)
- Folium 地图使用 `MarkerCluster(disableClusteringAtZoom=8)`,这样四个埃及 SA-2 阵地(在约 0.17° 范围内)就不会在视觉上合并。
### `data.json` 版本控制
运行记录是追加的。不要原地替换 `runs[-1]` —— `start_run()` 会使用 CSV 派生的 `latitude`/`longitude` 字段创建一个新条目,并且每个基地的跳过逻辑依赖于该最新运行中的 `status`。
标签:Black Hat, DLL 劫持, ESC4, Folium, GIS, Google Maps API, Kubernetes, LangGraph, LLM, Multi-Agent, OSINT, PyRIT, Sentinel Hub, Streamlit, Unmanaged PE, Vision-LLM, 人工智能, 军事侦察, 军事基地, 卫星图像分析, 地理信息系统, 多智能体系统, 大语言模型, 威胁评估, 实时仪表盘, 情报分析, 数据聚合, 用户模式Hook绕过, 网络诊断, 自动化情报管线, 视觉大模型, 访问控制, 逆向工具, 遥感分析