# Emergency Command — 应急指挥事件处理流水线

基于 Nanobot Agent Loop 框架的 AI 应急指挥事件处理系统。处理**被叫号码为指挥中心电话**的来电录音，经多步流水线自动判定事件性质、提取关键信息、分类事件类型、评估响应等级，最终生成结构化应急事件报告。

> 本项目与 duty_processor 是两个独立的并行流程，分别由不同的被叫号码触发，不存在路由关系。

## V8.2 当前基线

- 客户侧部署候选：`Qwen2.5-32B-Instruct`；`GLM-5.1-FP8` 仅作大模型效果参照。
- 第9批正式HTTP直驱：285例，Qwen综合73%、平均26.498秒、接口失败0；GLM参照综合71%、平均42.575秒、接口失败14。
- Qwen初步验证可用于客户环境部署；后续重点优化伤亡情况与严格等级。
- 业务接口版本仍为v3.0；V8.2是当前算法优化与评测基线，两者不是同一版本号。

## 处理流程

```
录音 URL（或 asr_text 直传文本）
    │
    ▼
[Step 1] ASR 转写              ──► 转写文本
    │                               (传了 asr_text 则跳过此步)
    ▼
[Step 2] 应急判定               ──► 是否为真实应急事件
    │                               (同一事件≥10人伤亡 / 需医学救援)
    │                               依据：《日常应急值班规则》
    ▼
[Step 3] 结构化信息抽取         ──► 12 项关键字段
    │                               事件类型/地点/伤亡/报告人等
    ▼
[Step 4] 补充判定（语音未明述时）
    ├── 事件类型分类 ──► BGE 向量 + FAISS 匹配《事件类型目录》
    ├── 响应等级评估 ──► LLM + 《响应等级判断标准》(docx)
    └── 敏感事件判定 ──► 4 维关键词匹配《敏感事件判断标准》(md)
    │
    ▼
[Step 5] 组装最终结果            ──► 应急事件信息 JSON + 全字段溯源
```

## API 接口

| 方法 | 路径 | 说明 |
|------|------|------|
| `POST` | `/api/v3/emergency/pipeline` | 编排模式（nanobot agentloop 调度） |
| `POST` | `/api/v3/emergency/pipeline_direct` | 直驱模式（确定性按序调工具，推荐） |
| `POST` | `/api/v3/emergency/pipeline_direct/stream` | 直驱SSE流式，与非流式直驱同源 |
| `POST` | `/api/v3/emergency/pipeline/stream` | 编排SSE流式，与非流式编排同源 |
| `GET` | `/health` | 健康检查 |

### 请求参数

> **工程接入约定**：平台应随每通来电传完整通话记录字段：`id`、`businessId`、`caller`/`callerName`、`called`/`calledName`、`callType`、`tmAnswer`/`tmHangup`、`callLength`。为支持文本冒烟与离线评测，API模型层允许这些字段为空；非空字段原样透传到 `emergency_event_info.metadata.call_info`。

**方式一：传录音文件（完整流水线）**
```json
{
  "recording_url": "https://example.com/recording.mp4",
  "recording_time": "2025-01-01T08:00:00+08:00",
  "status_code": "EMERGENCY_CALL",
  "asr_provider": "qwen3_asr",
  "id": 10086,
  "businessId": "BIZ-10086",
  "caller": "13892076554",
  "callerName": "张主任",
  "called": "01012345678",
  "calledName": "指挥中心",
  "callType": "callin",
  "tmAnswer": "2025-01-01 08:00:00",
  "tmHangup": "2025-01-01 08:03:00",
  "callLength": 180
}
```

**方式二：传 ASR 文本（跳过录音/ASR，通话记录仍需带全；推荐测试用）**
```json
{
  "asr_text": "化工厂毒气泄漏，2人重伤30人轻伤，需支援，联系人张主任13892076554",
  "id": 10086,
  "businessId": "BIZ-10086",
  "caller": "13892076554",
  "callerName": "张主任",
  "called": "01012345678",
  "calledName": "指挥中心",
  "callType": "callin",
  "tmAnswer": "2025-01-01 08:00:00",
  "tmHangup": "2025-01-01 08:03:00",
  "callLength": 180
}
```

### 响应示例（直驱模式）

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "is_emergency": true,
    "pipeline_duration_ms": 12000,
    "execution_mode": "direct",
    "emergency_event_info": {
      "event_type_major": "事故灾难",
      "event_level": "IV",
      "is_sensitive_event": "否",
      "event_location": "牛栏山京密公路",
      "reporter_contact": "13892076554",
      "casualty_info": "重伤2人，轻伤约30人",
      "metadata": {
        "execution_mode": "direct",
        "pipeline_duration_ms": 12000,
        "field_sources": {
          "event_type_major": "classify_event_type·依据《事件类型目录》",
          "event_level": "estimate_event_level·依据《响应等级判断标准》",
          "is_sensitive_event": "judge_sensitive_event·依据《敏感事件判断标准》"
        }
      }
    }
  }
}
```

### 错误码

| 错误码 | 说明 |
|--------|------|
| 40001 | 录音内容为空（asr_text 与 recording_url 均为空/纯空白时也返回此码） |
| 40002 | 录音无法识别（置信度过低） |
| 40003 | ASR服务调用超时 |
| 40004 | ASR 服务内部错误 / 未接入或未实现的 provider / 未配置对应 ASR 密钥 |
| 50001 | 流水线总超时（当前服务配置默认60秒） |
| 50002 | 内部处理或部分工具错误 |

## 源表锚定

所有判定严格依据原始标准表，禁止凭直觉：

| 工具 | 依据的源表 | 说明 |
|------|-----------|------|
| judge_emergency | 《日常应急值班规则》 | ≥10人伤亡 / 需医疗卫生救援 |
| classify_event_type | 《事件类型目录》(257行CSV) | BGE-M3 向量 + FAISS 语义召回，再结合模型比较 |
| estimate_event_level | 《响应等级判断标准》(docx) | I≥100人 / II 50-100 / III 30-50 / IV 10-30 |
| judge_sensitive_event | 《敏感事件判断标准》(md) + 节假日日历 | 4维度OR：时间/地点/群体/影响 |
| extract_event_info | 《AI应急事件信息表》(csv) | 12字段 + 数据来源规则 |

## 模型配置

工具模型在 `config.yaml` 配置（与 nanobot 编排模型 `config.json` 独立）：

```yaml
llm:
  base_url: "http://192.168.68.92:8986/v1"
  api_key: "Empty"
  models:
    judge: "Qwen2.5-32B-Instruct"
    extract: "Qwen2.5-32B-Instruct"
    level: "Qwen2.5-32B-Instruct"
    calibrate: "Qwen2.5-32B-Instruct"   # 缺省跟随 judge
  max_tokens:
    judge: 2048
    extract: 4096
    level: 2048
    calibrate: 4096
```

> `max_tokens` 由 `config.yaml` 注入各工具。默认 profile 与第9批 V8.2 Qwen评测一致。

正式切换自建模型端点时，工具模型使用原子配置：

```bash
export EXP_TOOL_BASE_URL=http://192.168.68.92:8986/v1
export EXP_TOOL_API_KEY=Empty
export EXP_TOOL_MODEL=Qwen2.5-32B-Instruct
```

三个变量必须同时提供，显式环境变量优先于默认 profile。后续切换模型时同时替换模型名、URL 和 Key，并重启服务。编排模型另用 `AGENTLOOP_BASE_URL`、`AGENTLOOP_API_KEY`、`AGENTLOOP_MODEL`；直驱不会读取编排模型配置。Qwen与GLM调用均关闭思考模式。

事件类型语义召回默认使用 `config.yaml` 中的本地 BGE-M3：

```yaml
data:
  embedding:
    provider: "local"
    model: "BAAI/bge-m3"
    timeout_s: 30
    batch_size: 32
```

需要切换为远程部署的 OpenAI 兼容 `/embeddings` 接口时，配置：

```bash
export EMBEDDING_BASE_URL=http://embedding-host:port/v1
export EMBEDDING_API_KEY=your-key
export EMBEDDING_MODEL=BAAI/bge-m3
```

三个变量必须同时设置；全部不设置时仍使用本地 BGE-M3。服务启动时会校验
provider、模型、Base URL 指纹、维度、目录条数和目录 SHA256；任一身份发生变化，
旧 FAISS 缓存都会被拒绝并自动重建。

这里不需要提前保存用户事件向量。系统只预向量化并本地保存 255 条稳定事件类型目录；
用户当前上报事件在每次请求时实时生成一条查询向量，不持久化。当前规模无需另行部署
Milvus、Qdrant、pgvector 等向量数据库。

仅使用 `asr_text` 的直驱服务启动时不会创建 Nanobot 或 ASR 客户端，只需要完整
`EXP_TOOL_*` profile。首次调用编排接口时才加载 Nanobot 并校验 `AGENTLOOP_*`；
首次传入 `recording_url` 时才创建所选 ASR provider 并校验其凭据。

Demo 使用正式 SSE 流式端点，评测使用正式非流式端点；除此之外两者使用相同
profile、预处理和业务处理链。

## 对比评测摘要

完整结果见 `experiments/REPORT.md`，数据在 `experiments/runs/`。

第9批V8.2直驱全量结果（`cases_emergency_v2.json`，285例）：

| 模型 | 综合 | 平均耗时 | 接口失败 |
|---|---:|---:|---:|
| Qwen2.5-32B-Instruct | 73% | 26.498s | 0/285 |
| GLM-5.1-FP8（效果参照） | 71% | 42.575s | 14/285 |

Qwen的客户侧部署结论不是“全面优于GLM”，而是综合效果与参照模型接近，同时耗时和稳定性满足继续部署验证的条件。

## 快速开始

```bash
pip install -e .
python -m uvicorn emergency_command.app:create_fastapi_app --factory --host 0.0.0.0 --port 8000

# 最简调用：只传 asr_text
curl -X POST http://localhost:8000/api/v3/emergency/pipeline_direct \
  -H "Content-Type: application/json" \
  -d '{"asr_text":"化工厂毒气泄漏，2人重伤30人轻伤，需支援，联系人张主任13892076554"}'
```

## 环境变量

| 变量 | 说明 | 必需 |
|------|------|------|
| `DASHSCOPE_API_KEY` | 百炼 ASR 等 DashScope 能力的 API 密钥 | 按 ASR provider |
| `EXP_TOOL_BASE_URL` / `EXP_TOOL_API_KEY` / `EXP_TOOL_MODEL` | 直驱工具模型完整端点配置，必须同时设置 | 可选（默认使用 Qwen2.5 profile） |
| `AGENTLOOP_BASE_URL` / `AGENTLOOP_API_KEY` / `AGENTLOOP_MODEL` | 编排模型完整端点配置 | 可选 |
| `EMBEDDING_BASE_URL` / `EMBEDDING_API_KEY` / `EMBEDDING_MODEL` | 远程 Embedding 完整端点配置，必须同时设置 | 可选（默认使用本地 BGE-M3） |
| `EXP_MOCK_ASR_TEXT` | Mock ASR 返回文本（评测用） | 可选 |

## 技术栈

- **Agent Loop**: Nanobot (Skill-Tool-Hook 架构)
- **LLM**: OpenAI兼容模型端点；V8.2验证 `Qwen2.5-32B-Instruct` 与 `GLM-5.1-FP8`，思考模式关闭
- **ASR**: 百炼 qwen3-asr-flash（默认）/ 火山引擎
- **语义匹配**: FAISS + BGE-M3
- **Web**: FastAPI + Uvicorn
- **Python**: 3.11
