feat(ai): add tenant-safe hierarchical expense learning

This commit is contained in:
caoxiaozhu
2026-07-16 14:30:41 +08:00
parent 6bdf65bc24
commit ee88a36baf
65 changed files with 6909 additions and 232 deletions

View File

@@ -14,7 +14,10 @@ from app.services.agent_asset_rule_library import AgentAssetRuleLibraryManager
from app.services.agent_asset_spreadsheet import RISK_RULES_LIBRARY
from app.services.audit import AuditLogService
from app.services.expense_claim_risk_stage import infer_risk_domain
from app.services.risk_rule_dsl_validator import validate_risk_rule_draft
from app.services.risk_rule_explainability import build_risk_rule_explainability_artifacts
from app.services.risk_rule_generation_interpreter import COMPOSITE_RULE_TEMPLATE_KEY
from app.services.risk_rule_generation_markdown import build_risk_rule_version_markdown
from app.services.risk_rule_generation_ontology import (
BUSINESS_DOMAIN_LABELS,
DOMAIN_FIELD_PREFIXES,
@@ -26,16 +29,13 @@ from app.services.risk_rule_generation_ontology import (
RiskRuleField,
)
from app.services.risk_rule_generation_prompt import build_risk_rule_compiler_messages
from app.services.risk_rule_generation_interpreter import COMPOSITE_RULE_TEMPLATE_KEY
from app.services.risk_rule_generation_markdown import build_risk_rule_version_markdown
from app.services.risk_rule_generation_semantic_plan import unwrap_semantic_plan_payload
from app.services.risk_rule_generation_semantics import (
CITY_CONSISTENCY_SEMANTIC_TYPE,
CITY_CONSISTENCY_SEMANTIC_TYPES,
build_city_consistency_draft,
build_city_consistency_params,
)
from app.services.risk_rule_generation_semantic_plan import unwrap_semantic_plan_payload
from app.services.risk_rule_dsl_validator import validate_risk_rule_draft
from app.services.risk_rule_scoring import apply_risk_score_to_draft, calculate_risk_rule_score
from app.services.runtime_chat import RuntimeChatService
@@ -57,6 +57,7 @@ class RiskRuleGenerationService:
self,
body: AgentAssetRiskRuleGenerateRequest,
*,
tenant_id: str | None = None,
actor: str,
request_id: str | None = None,
) -> str:
@@ -81,6 +82,7 @@ class RiskRuleGenerationService:
created_at = datetime.now(UTC)
fields = self._resolve_fields(natural_language, domain=domain)
draft = self._compile_with_model(
tenant_id=tenant_id,
natural_language=natural_language,
domain=domain,
business_stage=business_stage,
@@ -174,6 +176,7 @@ class RiskRuleGenerationService:
"ontology_signal": payload.get("ontology_signal"),
"evaluator": payload.get("evaluator"),
"generated_by": "natural_language",
"tenant_id": str(tenant_id or "").strip(),
"source_ref": "自然语言风险规则",
"last_operation": {
"action": "create",
@@ -217,6 +220,7 @@ class RiskRuleGenerationService:
def _compile_with_model(
self,
*,
tenant_id: str | None,
natural_language: str,
domain: str,
business_stage: str,
@@ -235,6 +239,7 @@ class RiskRuleGenerationService:
for item in fields
]
few_shot_samples = self._retrieve_few_shot_samples(
tenant_id=tenant_id,
domain=domain,
natural_language=natural_language,
)
@@ -271,6 +276,7 @@ class RiskRuleGenerationService:
def _retrieve_few_shot_samples(
self,
*,
tenant_id: str | None,
domain: str,
natural_language: str,
) -> list[dict[str, Any]]:
@@ -280,11 +286,15 @@ class RiskRuleGenerationService:
if os.environ.get("FEW_SHOT_INJECTION_ENABLED", "true").strip().lower() in {"0", "false", "no"}:
return []
normalized_tenant_id = str(tenant_id or "").strip()
if not normalized_tenant_id:
return []
try:
from app.services.few_shot_retrieval import FewShotRetriever
retriever = FewShotRetriever.from_session(self.db)
return retriever.retrieve_for_risk_rule_generation(
tenant_id=normalized_tenant_id,
domain=domain,
natural_language=natural_language,
)