Files
group_xinghuo_jinrong/app/service/compliance_semantic_service.py
T
zhanghongyu_0626 70aa861983 feat(advisor-agent): Introduce advisor agent functionalities with compliance, KYC, and script templates
- Added new modules for advisor compliance, KYC sessions, and script templates, enhancing the advisor agent's capabilities.
- Implemented a comprehensive API structure under the `/api/advisor-agent` prefix, ensuring clear organization and access to new features.
- Established database models and repositories for compliance rules and KYC sessions, facilitating robust data management.
- Integrated exception handling and response models to improve error management and user feedback.
- Updated settings to include new configurations for compliance and KYC features, ensuring flexibility and adaptability.

This update significantly expands the advisor agent's functionality, providing essential tools for compliance and customer interaction while maintaining a structured API design.
2026-09-12 16:33:07 +08:00

151 lines
5.0 KiB
Python

"""AI semantic compliance detection with safe degradation."""
from __future__ import annotations
import json
from typing import Protocol
import httpx
from pydantic import BaseModel, ValidationError
from app.config.settings import settings
from app.model.advisor_schemas import ComplianceAiAnalysis
PROMPT_VERSION = "compliance-semantic-v1"
DEGRADED_SUGGESTION = "AI 语义检测暂不可用,请人工确认后再继续复制或导出。"
class LLMClient(Protocol):
def complete(self, prompt: str, *, timeout_seconds: float) -> str:
pass
class SemanticLLMUnavailableError(RuntimeError):
def __init__(self, reason: str) -> None:
self.reason = reason
super().__init__(reason)
class SemanticLLMResult(BaseModel):
risk_level: str
reason: str
suggestion: str | None = None
class DeepSeekLLMClient:
def complete(self, prompt: str, *, timeout_seconds: float) -> str:
if not settings.deepseek_api_key:
raise SemanticLLMUnavailableError("missing_api_key")
response = httpx.post(
f"{settings.deepseek_base_url.rstrip('/')}/chat/completions",
headers={"Authorization": f"Bearer {settings.deepseek_api_key}"},
json={
"model": settings.deepseek_model,
"messages": [
{
"role": "system",
"content": "你是金融投顾话术合规审核助手,只返回 JSON。",
},
{"role": "user", "content": prompt},
],
"temperature": 0,
"response_format": {"type": "json_object"},
},
timeout=timeout_seconds,
)
response.raise_for_status()
body = response.json()
return body["choices"][0]["message"]["content"]
class ComplianceSemanticService:
def __init__(
self,
llm_client: LLMClient | None = None,
*,
enabled: bool | None = None,
timeout_seconds: float | None = None,
) -> None:
self.llm_client = llm_client or DeepSeekLLMClient()
self.enabled = settings.compliance_ai_enabled if enabled is None and llm_client is None else enabled is not False
self.timeout_seconds = timeout_seconds or settings.compliance_ai_timeout_seconds
def analyze(
self,
*,
text: str,
scene: str | None = None,
customer_risk_level: str | None = None,
) -> ComplianceAiAnalysis | None:
if not self.enabled:
return None
prompt = self._build_prompt(text=text, scene=scene, customer_risk_level=customer_risk_level)
try:
raw_output = self.llm_client.complete(prompt, timeout_seconds=self.timeout_seconds)
return self._parse_output(raw_output)
except TimeoutError:
return self._degraded("timeout")
except SemanticLLMUnavailableError as exc:
return self._degraded(exc.reason)
except (httpx.HTTPError, KeyError, IndexError, TypeError, ValueError):
return self._degraded("malformed_output")
def _parse_output(self, raw_output: str) -> ComplianceAiAnalysis:
if not raw_output.strip():
return self._degraded("empty_output")
try:
parsed = json.loads(raw_output)
except json.JSONDecodeError:
return self._degraded("malformed_output")
if parsed.get("refusal"):
return self._degraded("refusal")
try:
result = SemanticLLMResult.model_validate(parsed)
except ValidationError:
return self._degraded("malformed_output")
risk_level = result.risk_level.upper()
if risk_level not in {"BLOCK", "WARN", "INFO"}:
return self._degraded("malformed_output")
return ComplianceAiAnalysis(
risk_level=risk_level,
reason=result.reason,
suggestion=result.suggestion,
degraded=False,
prompt_version=PROMPT_VERSION,
model=settings.deepseek_model,
)
@staticmethod
def _build_prompt(
*,
text: str,
scene: str | None = None,
customer_risk_level: str | None = None,
) -> str:
return (
f"prompt_version={PROMPT_VERSION}\n"
"请判断以下金融投顾话术是否存在隐性违规风险,只返回 JSON:"
"{\"risk_level\":\"INFO|WARN|BLOCK\",\"reason\":\"...\",\"suggestion\":\"...\"}\n"
f"scene={scene or 'general'}\n"
f"customer_risk_level={customer_risk_level or 'unknown'}\n"
f"text={text}"
)
@staticmethod
def _degraded(reason: str) -> ComplianceAiAnalysis:
return ComplianceAiAnalysis(
risk_level="WARN",
reason=f"AI semantic detection degraded: {reason}",
suggestion=DEGRADED_SUGGESTION,
degraded=True,
prompt_version=PROMPT_VERSION,
model=settings.deepseek_model,
)