| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147 |
- """
- 公司信息提取器
- 从通用标书生成资料中提取结构化的公司基本信息。
- 使用 LLM 一次性提取,结果缓存。
- """
- from __future__ import annotations
- import logging
- from typing import Dict, List, Optional
- from llm_client import LLMClient
- from models import CompanyInfo, ParsedDocument
- logger = logging.getLogger(__name__)
- def extract_company_info(
- general_materials: List[ParsedDocument],
- ) -> CompanyInfo:
- """从通用标书资料中提取结构化公司信息
- 优先从"企业基本情况简介"类 MD 文件中提取。
- Args:
- general_materials: Step 1 加载的通用资料文档列表
- Returns:
- CompanyInfo — 结构化公司信息
- """
- # 1. 优先找公司简介类文档
- intro_text = ""
- for doc in general_materials:
- if not doc.content:
- continue
- name = doc.file_name.lower()
- if any(kw in name for kw in ["企业基本情况", "公司简介", "企业介绍", "公司概况"]):
- intro_text = doc.content
- break
- # 2. 降级:合并所有短文档
- if not intro_text:
- combined = []
- for doc in general_materials:
- if doc.content:
- combined.append(f"--- {doc.file_name} ---\n{doc.content[:3000]}")
- intro_text = "\n\n".join(combined)
- if not intro_text:
- logger.warning("未找到公司信息资料,返回空 CompanyInfo")
- return CompanyInfo()
- # 3. LLM 提取
- llm = LLMClient()
- prompt = """请从以下公司资料中提取公司基本信息,以 JSON 格式输出。
- 如果某项信息未找到,请填空字符串""。
- 输出格式:
- {
- "full_name": "公司全称",
- "short_name": "公司简称",
- "address": "注册地址或办公地址",
- "phone": "联系电话",
- "fax": "传真",
- "zip_code": "邮编",
- "bank_name": "开户银行",
- "bank_account": "银行账号",
- "legal_rep": "法定代表人姓名",
- "legal_rep_id": "法定代表人身份证号",
- "registration_capital": "注册资本",
- "unified_code": "统一社会信用代码",
- "website": "公司官网"
- }
- 注意:
- - 公司全称必须与营业执照一致,不要删减或添加文字
- - 地址必须完整,包含省市(直辖)区
- - 电话格式保留原样"""
- try:
- result = llm.extract_json(
- system_prompt="你是企业信息提取专家。请从文本中准确提取公司基本信息。",
- user_prompt=f"{prompt}\n\n---\n{intro_text[:12000]}",
- max_tokens=32768,
- )
- if isinstance(result, dict):
- # LLM 可能返回 null(而非空字符串):str(None) 会生成 "None"
- # 污染占位符替换结果,统一转回空字符串
- def _safe_str(v) -> str:
- if v is None:
- return ""
- s = str(v).strip()
- return "" if s.lower() in ("none", "null") else s
- info = CompanyInfo(
- full_name=_safe_str(result.get("full_name")),
- short_name=_safe_str(result.get("short_name")),
- address=_safe_str(result.get("address")),
- phone=_safe_str(result.get("phone")),
- fax=_safe_str(result.get("fax")),
- zip_code=_safe_str(result.get("zip_code")),
- bank_name=_safe_str(result.get("bank_name")),
- bank_account=_safe_str(result.get("bank_account")),
- legal_rep=_safe_str(result.get("legal_rep")),
- legal_rep_id=_safe_str(result.get("legal_rep_id")),
- registration_capital=_safe_str(result.get("registration_capital")),
- unified_code=_safe_str(result.get("unified_code")),
- website=_safe_str(result.get("website")),
- )
- logger.info(f"公司信息提取完成: {info.full_name}")
- return info
- else:
- logger.warning(f"公司信息提取返回非 dict: {type(result)}")
- return CompanyInfo()
- except Exception as e:
- logger.warning(f"公司信息提取失败: {e}")
- # 降级:正则提取
- return _fallback_extract(intro_text)
- def _fallback_extract(text: str) -> CompanyInfo:
- """降级方案:正则表达式提取公司名"""
- import re
- info = CompanyInfo()
- # 尝试匹配公司名(中文名+有限公司)
- m = re.search(r"((?:上海|北京|深圳|广州|杭州|南京|成都|武汉|重庆|天津)[一-龥()\(\)]{2,20}(?:有限公司|股份有限公司))", text)
- if m:
- info.full_name = m.group(1)
- # 地址
- m = re.search(r"(?:地址|注册地址|办公地址)[::]\s*(.+?)(?:[\n,。]|$)", text)
- if m:
- info.address = m.group(1).strip()
- # 电话
- m = re.search(r"(?:电话|联系电话|Tel)[::]\s*([\d\-]+)", text)
- if m:
- info.phone = m.group(1).strip()
- return info
- __all__ = ["extract_company_info"]
|