ES 9.x 中文 NER 推理 API + 管道配置方案

适配 ES 9.x 的中文 NER(命名实体识别)推理 API + 管道配置完整方案

本文档包含:中文 NER 外部模型服务(FastAPI)、ES 9.x 推理 API 配置、Ingest 管道、索引 Mapping、数据索引与搜索验证,全程适配 ES 9.x 特性,支持识别人名(PER)、机构名(ORG)、地名(LOC) 等核心实体。


一、前置准备

  1. 环境要求

    • Python 3.10+、Elasticsearch 9.x(启用 ML 模块)、网络连通;

    • 硬件:模型服务至少 4GB 内存(首次下载模型需额外 2-3GB 空间,轻量模型无需 GPU)。

  2. 安装 Python 依赖

Bash 复制代码
pip3 install fastapi uvicorn torch transformers

二、步骤1:部署中文 NER 外部模型服务

完整代码(优化注释,适配 ES 9.x 响应格式)

保存为 chinese_ner_service.py

Python 复制代码
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Union, Dict
from transformers import AutoTokenizer, AutoModelForTokenClassification, pipeline
from contextlib import asynccontextmanager
import torch

# 全局模型变量(启动时仅加载一次)
ner_pipeline = None


@asynccontextmanager
async def lifespan(app: FastAPI):
    """ES 9.x 推荐:服务启动时加载模型,关闭时释放资源"""
    global ner_pipeline

    print("正在加载中文 NER 模型(hfl/chinese-bert-wwm-ext)...")
    # 加载开源中文 NER 模型,支持人名、机构名、地名识别
    model_name = "hfl/chinese-bert-wwm-ext"
    tokenizer = AutoTokenizer.from_pretrained(model_name)
    model = AutoModelForTokenClassification.from_pretrained(model_name)
    model.eval()

    # 构建 NER 流水线,聚合相邻实体标签
    ner_pipeline = pipeline(
        "ner",
        model=model,
        tokenizer=tokenizer,
        aggregation_strategy="simple"  # 聚合相邻标签,避免实体拆分
    )

    # 启用 GPU 加速(如有)
    if torch.cuda.is_available():
        model.to("cuda")
        print("GPU 加速已启用")

    print("中文 NER 模型加载完成!")
    yield
    print("正在关闭服务...")
    ner_pipeline = None


app = FastAPI(
    title="中文 NER 神经分析器",
    description="中文命名实体识别服务(适配 ES 9.x 推理 API)",
    version="1.0.0",
    lifespan=lifespan
)


class InferenceRequest(BaseModel):
    """ES 9.x 推理 API 标准请求格式"""
    input: Union[str, List[str]]


def format_response(
    tagged_text: str,
    entities: List[Dict[str, str]]
) -> dict:
    """
    格式化响应,适配 ES 9.x 解析规则
    返回:带实体标记的文本 + 结构化实体列表
    """
    return {
        "choices": [
            {
                "message": {
                    "content": tagged_text,  # 带标记的文本(如 [PER]张三[/PER]...)
                    "entities": entities      # 结构化实体列表
                }
            }
        ]
    }


def extract_entities(text: str) -> tuple[str, List[Dict[str, str]]]:
    """
    核心 NER 逻辑:识别实体并标记
    支持实体类型:PER(人名)、ORG(机构名)、LOC(地名)
    """
    if not text or not text.strip():
        return "", []

    # 调用 NER 流水线
    results = ner_pipeline(text)

    # 解析结果:构建带标记的文本 + 实体列表
    tagged_text = ""
    entities = []
    last_end = 0

    for entity in results:
        # 提取实体信息
        start = entity["start"]
        end = entity["end"]
        entity_text = entity["word"]
        entity_type = entity["entity_group"]  # PER/ORG/LOC

        # 拼接带标记的文本
        tagged_text += text[last_end:start]
        tagged_text += f"[{entity_type}]{entity_text}[/{entity_type}]"
        last_end = end

        # 收集结构化实体
        entities.append({
            "text": entity_text,
            "type": entity_type,
            "start": start,
            "end": end
        })

    # 拼接剩余文本
    tagged_text += text[last_end:]

    return tagged_text, entities


@app.post("/analyze/chinese-ner")
async def analyze_chinese_ner(request: InferenceRequest):
    """中文 NER 接口,适配 ES 9.x 推理 API"""
    global ner_pipeline

    if ner_pipeline is None:
        raise HTTPException(status_code=503, detail="中文 NER 模型未加载")

    # 处理输入(支持单文本/多文本)
    if isinstance(request.input, str):
        texts = [request.input]
    else:
        texts = request.input

    # 批量处理(取第一个文本,可扩展为批量返回)
    tagged_text, entities = extract_entities(texts[0])

    return format_response(tagged_text, entities)


@app.get("/health")
async def health():
    """ES 9.x 健康检查接口"""
    return {
        "status": "healthy",
        "message": "中文 NER 模型已就绪",
        "support_entities": ["PER(人名)", "ORG(机构名)", "LOC(地名)"]
    }

启动服务

Bash 复制代码
python3 -m uvicorn chinese_ner_service:app --port 8001
# 后台运行:nohup python3 -m uvicorn chinese_ner_service:app --port 8001 &

等待出现「中文 NER 模型加载完成!」,本地测试:

Bash 复制代码
curl -X POST http://localhost:8001/analyze/chinese-ner \
 -H "Content-Type: application/json" \
 -d '{"input": "张三在阿里巴巴杭州总部参加会议"}'

三、步骤2:配置 ES 9.x 推理 API

前提:暴露本地服务(测试用 ngrok)

Bash 复制代码
ngrok http 8001
# 复制 HTTPS 地址(如 https://def456.ngrok.io)

配置推理端点(测试环境,可直接复制)

JSON 复制代码
PUT _inference/completion/chinese-ner-analyzer
{
  "service": "custom",
  "service_settings": {
    "url": "https://def456.ngrok.io/analyze/chinese-ner",  // 替换为你的 ngrok 地址
    "headers": {
      "Content-Type": "application/json"
    },
    "request": "{\"input\": ${input}}",
    "response": {
      "json_parser": {
        "completion_result": "$.choices[*].message.content",  // 提取带标记的文本
        "entities": "$.choices[*].message.entities"            // 提取结构化实体列表
      }
    }
  }
}

验证配置

JSON 复制代码
POST _inference/completion/chinese-ner-analyzer
{
  "input": "张三在阿里巴巴杭州总部参加会议"
}

四、步骤3:创建 ES 9.x Ingest 管道

管道功能

  • 调用 NER 推理 API;

  • 提取带标记的文本到 content_ner

  • 用 Script 处理器将实体拆分到 personorgloc 字段。

JSON 复制代码
PUT _ingest/pipeline/chinese_ner_pipeline
{
  "description": "中文 NER 分析管道(适配 ES 9.x)",
  "processors": [
    {
      "inference": {
        "model_id": "chinese-ner-analyzer",
        "input_output": {
          "input_field": "content",
          "output_field": "content_ner"
        },
        "on_failure": [
          {
            "set": {
              "field": "content_ner",
              "value": "{{content}}"
            }
          }
        ]
      }
    },
    {
      "script": {
        "description": "提取实体到单独字段",
        "lang": "painless",
        "source": """
          // 初始化实体字段
          ctx.person = new ArrayList();
          ctx.org = new ArrayList();
          ctx.loc = new ArrayList();

          // 从推理结果中提取实体(若存在)
          if (ctx.containsKey('inference') && ctx.inference.containsKey('entities')) {
            for (def entity : ctx.inference.entities) {
              def entityText = entity.text;
              def entityType = entity.type;
              if (entityType == 'PER') {
                ctx.person.add(entityText);
              } else if (entityType == 'ORG') {
                ctx.org.add(entityText);
              } else if (entityType == 'LOC') {
                ctx.loc.add(entityText);
              }
            }
          }
        """
      }
    }
  ]
}

五、步骤4:创建 ES 9.x 索引 Mapping

JSON 复制代码
PUT /my-chinese-ner-index
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 0
  },
  "mappings": {
    "properties": {
      "content": {  // 原始文本
        "type": "text",
        "analyzer": "standard"
      },
      "content_ner": {  // 带实体标记的文本(用 whitespace 分析器)
        "type": "text",
        "analyzer": "whitespace",
        "fields": {
          "keyword": {
            "type": "keyword"
          }
        }
      },
      "person": {  // 人名列表
        "type": "keyword"
      },
      "org": {  // 机构名列表
        "type": "keyword"
      },
      "loc": {  // 地名列表
        "type": "keyword"
      }
    }
  }
}

六、步骤5:索引数据与搜索验证

1. 索引测试数据

JSON 复制代码
POST /my-chinese-ner-index/_doc?pipeline=chinese_ner_pipeline
{
  "content": "张三在阿里巴巴杭州总部参加会议,李四去北京腾讯出差"
}

2. 验证索引结果

JSON 复制代码
GET /my-chinese-ner-index/_doc/文档ID

预期返回:person: ["张三", "李四"]org: ["阿里巴巴", "腾讯"]loc: ["杭州", "北京"]

3. 按实体搜索(可直接复制)

搜索"人名是张三"的文档
JSON 复制代码
GET /my-chinese-ner-index/_search
{
  "query": {
    "term": {
      "person": "张三"
    }
  },
  "highlight": {
    "fields": {
      "content_ner": {}
    }
  }
}
搜索"机构名包含阿里巴巴"的文档
JSON 复制代码
GET /my-chinese-ner-index/_search
{
  "query": {
    "terms": {
      "org": ["阿里巴巴", "腾讯"]
    }
  }
}

七、生产环境优化配置

1. 推理 API 安全配置

JSON 复制代码
PUT _inference/completion/chinese-ner-analyzer
{
  "service": "custom",
  "service_settings": {
    "url": "https://你的API网关地址/prod/analyze/chinese-ner",
    "headers": {
      "x-api-key": "${api_key}",
      "Content-Type": "application/json"
    },
    "secret_parameters": {
      "api_key": "你的API密钥"
    },
    "request": "{\"input\": ${input}}",
    "response": {
      "json_parser": {
        "completion_result": "$.choices[*].message.content",
        "entities": "$.choices[*].message.entities"
      }
    }
  }
}

2. 设置管道为索引默认

JSON 复制代码
PUT /my-chinese-ner-index/_settings
{
  "index.default_pipeline": "chinese_ner_pipeline"
}

八、故障排查

  1. 模型下载失败 :手动下载模型到 ~/.cache/huggingface/transformers

  2. ES 推理 API 调用失败:检查 ngrok 地址是否正确、服务是否启动;

  3. 实体未提取 :检查 Script 处理器语法、推理结果是否包含 entities 字段。

相关推荐
赵谨言2 小时前
基于Python的汽车CAN总线报文格式转换系统的设计与实现
大数据·开发语言·经验分享·笔记·python
海兰2 小时前
Elasticsearch Java 客户端(9.x)
java·elasticsearch·jenkins
沪漂阿龙2 小时前
大模型推理成本与优化技术全景解析:从显存估算到Continuous Batching
大数据·人工智能
W133309089073 小时前
高职大数据技术专业,CDA和Python认证优先考哪个?
大数据·开发语言·python
海兰3 小时前
ES_QL 稠密向量检索:本地部署实操
大数据·elasticsearch·搜索引擎
TDengine (老段)3 小时前
TDengine IDMP 高级功能——计量单位
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
侧耳倾听1113 小时前
kibana-基本使用
elasticsearch
海兰3 小时前
Elasticsearch 9.x Java 异步客户端
java·elasticsearch·jenkins
Elastic 中国社区官方博客3 小时前
推出 Elastic Serverless Plus 附加组件,支持 AWS PrivateLink 功能
大数据·elasticsearch·搜索引擎·云原生·serverless·全文检索·aws