| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- """
- Hybrid extractor with mode switching.
- Supports: rules, bert, ollama, hybrid (rules + BERT + Ollama with field-type routing).
- """
- from __future__ import annotations
- from typing import Any
- import requests
- from app.config import settings
- from app.extractors.rule_extractor import RuleBasedExtractor
- class HybridExtractor:
- """
- Multi-mode extractor for labor arbitration case elements.
- Modes:
- - "rules": Rule-based regex extraction only (fast, no GPU needed)
- - "bert": BERT multi-task model via nlp-service (deep learning)
- - "ollama": Qwen 2.5 via Ollama (LLM zero-shot)
- - "hybrid": Rules for structured fields + BERT for entities + Ollama for free text
- """
- def __init__(self, mode: str | None = None):
- self._mode = mode or settings.extractor_mode or "rules"
- self._rule = RuleBasedExtractor()
- self._ollama = None
- # Init Ollama if needed
- if self._mode in ("ollama", "hybrid") and settings.use_ollama:
- try:
- from app.anj import OllamaClaimsExtractor
- self._ollama = OllamaClaimsExtractor(
- settings.ollama_base_url,
- settings.ollama_model_name,
- )
- except Exception:
- self._ollama = None
- def extract(self, text: str) -> dict[str, Any]:
- if self._mode == "rules":
- return self._extract_rules(text)
- elif self._mode == "bert":
- return self._extract_bert(text)
- elif self._mode == "ollama":
- return self._extract_ollama(text)
- elif self._mode == "hybrid":
- return self._extract_hybrid(text)
- else:
- return self._extract_rules(text)
- def _extract_rules(self, text: str) -> dict[str, Any]:
- return self._rule.extract_all(text)
- def _extract_bert(self, text: str) -> dict[str, Any]:
- """Call the nlp-service BERT model for extraction."""
- try:
- resp = requests.post(
- settings.bert_model_service_url,
- json={"text": text},
- timeout=60,
- )
- resp.raise_for_status()
- raw = resp.json()
- # Flatten NLP nested response to backend flat format
- flat = {}
- parties = raw.get("parties", {})
- for k, v in parties.items():
- if v is not None:
- flat[k] = v
- facts = raw.get("facts", {})
- for k, v in facts.items():
- if v is not None:
- flat[k] = v
- if raw.get("case_cause", {}).get("type"):
- flat["case_cause"] = raw["case_cause"]["type"]
- if raw.get("tmpl_primary_cause"):
- flat["primary_cause_type"] = raw.get("tmpl_primary_cause")
- if raw.get("claims", {}).get("amount_total") is not None:
- flat["claims"] = raw["claims"]
- if raw.get("contract_type"):
- flat["contract_type"] = raw.get("contract_type")
- if raw.get("law_refs"):
- flat["law_refs"] = raw["law_refs"]
- return flat
- except Exception:
- # Fallback to rules
- return self._extract_rules(text)
- def _extract_ollama(self, text: str) -> dict[str, Any]:
- """Use Ollama LLM for extraction, with rule fallback for structured fields."""
- base = self._rule.extract_all(text)
- if self._ollama is None:
- return base
- try:
- # Enhance claims with Ollama
- base["claims"] = self._ollama.extract_claims(text or "")
- except Exception:
- pass
- try:
- # Enhance template fields with Ollama
- template_fields = self._ollama.extract_dispute_template_fields(text or "")
- for k, v in template_fields.items():
- if v is not None and v != "":
- base[k] = v
- except Exception:
- pass
- return base
- def _extract_hybrid(self, text: str) -> dict[str, Any]:
- """
- Hybrid extraction with field-type routing:
- - Structured fields (case_number, dates, amounts): rules
- - Entity fields (names, positions): BERT model
- - Free text fields (facts, claims): Ollama LLM
- """
- # Start with rules for structured fields
- base = self._rule.extract_all(text)
- # Try BERT for entity fields
- try:
- resp = requests.post(
- settings.bert_model_service_url,
- json={"text": text},
- timeout=60,
- )
- if resp.status_code == 200:
- bert_result = resp.json()
- # Merge entity fields from BERT
- entity_fields = [
- "applicant_name", "respondent_name", "worker_position",
- "entry_date", "leave_date", "filing_date",
- "termination_reason", "arbitration_org",
- ]
- bert_parties = bert_result.get("parties", {})
- for field in entity_fields:
- if field in bert_parties and bert_parties[field]:
- base[field] = bert_parties[field]
- bert_facts = bert_result.get("facts", {})
- for field in entity_fields:
- if field in bert_facts and bert_facts[field]:
- base[field] = bert_facts[field]
- # Use BERT case cause if confident
- bert_cause = bert_result.get("case_cause", {}).get("type")
- if bert_cause and bert_cause != "劳动争议":
- base["case_cause"] = bert_cause
- except Exception:
- pass
- # Try Ollama for free text fields
- if self._ollama is not None:
- try:
- base["claims"] = self._ollama.extract_claims(text or "")
- except Exception:
- pass
- try:
- template_fields = self._ollama.extract_dispute_template_fields(text or "")
- for k, v in template_fields.items():
- if v is not None and v != "":
- base[k] = v
- except Exception:
- pass
- return base
- @property
- def mode(self) -> str:
- return self._mode
- @mode.setter
- def mode(self, value: str):
- self._mode = value
- if value in ("ollama", "hybrid") and self._ollama is None and settings.use_ollama:
- try:
- from app.anj import OllamaClaimsExtractor
- self._ollama = OllamaClaimsExtractor(
- settings.ollama_base_url,
- settings.ollama_model_name,
- )
- except Exception:
- pass
|