company-classification.md 6.4 KB

公司信息多标签分类接口(F062)

POST /api/company/classify,请求和响应均为 application/json,返回普通JSON,不使用SSE。 直接将 /api/chatresult 事件中 data.company_info 对象作为整个请求体,不增加外层 company_info,不传 thread_id

const response = await fetch('/api/company/classify', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify(chatResult.data.company_info)
});
const classification = await response.json();

请求沿用 company / profile / honors 全量原始结构,见 公司信息契约。下面仅为合成片段,不限制工商原始字段:

{
  "company": {"KeyNo": "synthetic-key", "Name": "合成公司"},
  "profile": {
    "data": {"KeyNo": "synthetic-key", "Name": "合成公司", "OperName": "合成法人", "主营": "农产品生产及加工"},
    "source": {"provider": "synthetic", "fetched_at": "2026-09-18T15:00:00+08:00"}
  },
  "honors": {"records": [], "sources": [], "complete": true, "error": null}
}

返回主要字段示意(detailsas_ofclassification_version 省略):

{
  "company_name": "合成公司",
  "credit_code": null,
  "legal_representative": "合成法人",
  "categories": {
    "基本信息": [],
    "资质荣誉": [],
    "产业信息": ["第一产业", "第二产业", "现代农业"],
    "经营活动": [],
    "行业信息": ["涉农(广义)", "农产品生产", "农产品加工"],
    "许可认证": []
  },
  "status": "completed",
  "errors": {},
  "input_warnings": []
}

示意标签不是所有输入的固定输出,实际依据LLM判断。每个角度总是数组,可以多选,信息不足直接为 [],不返回“未知”或“其他”等表外兜底标签。未选中不等于判定“不符合”。

公司名称、统一社会信用代码、法人(法定代表人)分别取 profile.data.Name/CreditCode/OperName,缺失才回退 company 同名字段,再缺失返回 null,不交由LLM生成。KeyNo/Name/CreditCode 或来源主体键明确冲突时拒绝,HTTP422;不会合并两家公司。此接口信任调用方提供的资料,不重新查询或认证资料真实性。

允许请求 {}(chat未取得公司资料),身份均为null、六角度均为[]、不调用LLM,input_warnings=["company_info_empty"]。允许 honors=null,提示 honors_unavailable。荣誉 complete=false 时仍使用已取得记录,提示 honors_incomplete,不把查询失败或缺少记录作为否定事实。

details按相同六角度返回命中对象数组:{tag, reason, citations:[{path, quote}]}。path为请求原始对象的JSON Pointer,quote必须在该字段连续逐字出现;保留reason中的年份和有效期限制。来源可用该路径回溯原请求的profile.source或对应honors.records项的source。程序检查引用真实存在,不能证明LLM语义判断一定正确。

as_of为服务器上海日期,供成立年限、上月税务、许可有效期判断;来源获取时间保留在模型输入。classification_version覆盖六套提示、公共规则及标签列表的哈希,便于后续评测比较。没有缓存、落库或跨请求画像复用。

状态/错误 行为
HTTP200 / completed 六个角度分析完成,允许全部为空数组
HTTP200 / partial 部分角度失败,已成功角度保留;失败角度仍为空数组,必须结合errors判断
HTTP502 / failed 全部角度调用或输出校验失败,不能当成没有标签
errors中的model_call_failed 对应角度LLM调用失败;不输出异常原文
errors中的invalid_model_result 对应角度两次结构/标签/引用验证仍未通过
HTTP415 / application_json_required Content-Type错误
HTTP422 / invalid_request 请求结构、类型、嵌套深度或公司身份冲突
HTTP413 / request_too_large 请求体超过65536字节,不截断资料
HTTP408 / request_timeout 请求体读取超过15秒
HTTP429 / server_busy 分类工作池已满,Retry-After为2秒
HTTP504 / classification_timeout HTTP等待超时,后台尚未退出时继续占用名额
HTTP503 / service_unavailable 分类服务不可用
HTTP502 / classification_failed 接口内部失败,返回安全错误码

六个角度当前依次独立调用LLM,每个角度仅接收对应白名单,其他角度结果不作为事实输入。每角度结构错误最多修正一次,服务错误不自动重试。非空请求正常6次调用,最多12次;空对象0次。按六角度拆分便于以后独立评测、选择模型或有界并行,目前未宣称并行提速或生产吞吐。

分类工作池容量沿用 API_MAX_CONCURRENT_REQUESTS 对应的 settings.max_concurrent,独立于问答工作池;底层LLM共享现有 settings.llm_concurrent 信号量,不扩大同时调用模型上限。HTTP等待沿用 settings.stream_timeout_seconds。停止服务时等待分类工作结束再关闭共享运行时。请求正文、模型结果及异常原文不写日志,响应含X-Request-ID。

标签来自 docs/法人标签表.xlsx:166行、165个唯一分类标签,基本信息22、资质荣誉83、产业信息26、经营活动11、行业信息10、许可认证13。“产业信息 / 是否落户在综合保税区”重复一次,稳定去重,其余原名保留。运行时使用 src/company_classification/taxonomy.json 快照,无新增Excel依赖;测试核对源文件SHA256及全部行,更新xlsx后须同步快照并重新验证。

提示词位于 src/company_classification/prompts/,common.md为共享证据与输出规则,另有basic、honors、industry_sector、operations、industry、licenses六份独立判断规则。成立年限区间采用左闭右开,3/6月内和1年内允许重叠;规模、税务、认证等不能仅凭注册资本、注册地址、经营范围推断。

验证:

.\.venv\Scripts\python.exe -m unittest discover -s src/company_classification/tests -v
.\.venv\Scripts\python.exe -m src.company_classification.smoke_check

第二条会调用已配置的真实模型,仅发送脚本内合成资料,不查询真实公司、不写DMS。启动仍为 python -m src.step4_api,需重启加载新接口;整个服务启动仍依赖原有Redis、Embedding等。生产鉴权、真实公司分类质量、大量并发和延迟尚未验收。