|
71 | 71 | WebhookListResult, |
72 | 72 | ReportResult, |
73 | 73 | Usage, |
| 74 | + DetectionInput, |
| 75 | + DetectionResult, |
| 76 | + AnalyseMultiInput, |
| 77 | + AnalyseMultiResult, |
| 78 | + VideoAnalysisResult, |
74 | 79 | ) |
75 | 80 |
|
76 | 81 |
|
@@ -106,6 +111,42 @@ def _resolve_platform(platform: Optional[str] = None) -> str: |
106 | 111 | return f"{platform} - {Tuteliq._SDK_IDENTIFIER}" |
107 | 112 | return Tuteliq._SDK_IDENTIFIER |
108 | 113 |
|
| 114 | + @staticmethod |
| 115 | + def _resolve_detection_input( |
| 116 | + content_or_input: Union[str, DetectionInput], |
| 117 | + context: Optional[AnalysisContext], |
| 118 | + include_evidence: bool, |
| 119 | + external_id: Optional[str], |
| 120 | + customer_id: Optional[str], |
| 121 | + metadata: Optional[dict[str, Any]], |
| 122 | + ) -> DetectionInput: |
| 123 | + """Resolve string or DetectionInput to DetectionInput.""" |
| 124 | + if isinstance(content_or_input, str): |
| 125 | + return DetectionInput( |
| 126 | + content=content_or_input, |
| 127 | + context=context, |
| 128 | + include_evidence=include_evidence, |
| 129 | + external_id=external_id, |
| 130 | + customer_id=customer_id, |
| 131 | + metadata=metadata, |
| 132 | + ) |
| 133 | + return content_or_input |
| 134 | + |
| 135 | + def _build_detection_body(self, input_data: DetectionInput) -> dict[str, Any]: |
| 136 | + """Build request body for unified detection endpoints.""" |
| 137 | + ctx = input_data.context.to_dict() if input_data.context else {} |
| 138 | + ctx["platform"] = self._resolve_platform(ctx.get("platform")) |
| 139 | + body: dict[str, Any] = {"text": input_data.content, "context": ctx} |
| 140 | + if input_data.include_evidence: |
| 141 | + body["include_evidence"] = True |
| 142 | + if input_data.external_id: |
| 143 | + body["external_id"] = input_data.external_id |
| 144 | + if input_data.customer_id: |
| 145 | + body["customer_id"] = input_data.customer_id |
| 146 | + if input_data.metadata: |
| 147 | + body["metadata"] = input_data.metadata |
| 148 | + return body |
| 149 | + |
109 | 150 | def __init__( |
110 | 151 | self, |
111 | 152 | api_key: str, |
@@ -824,6 +865,233 @@ async def analyze_image( |
824 | 865 | data = await self._multipart_request("/api/v1/safety/image", fields, files) |
825 | 866 | return ImageAnalysisResult.from_dict(data) |
826 | 867 |
|
| 868 | + # ========================================================================= |
| 869 | + # Fraud Detection |
| 870 | + # ========================================================================= |
| 871 | + |
| 872 | + async def detect_social_engineering( |
| 873 | + self, |
| 874 | + content_or_input: Union[str, DetectionInput], |
| 875 | + *, |
| 876 | + context: Optional[AnalysisContext] = None, |
| 877 | + include_evidence: bool = False, |
| 878 | + external_id: Optional[str] = None, |
| 879 | + customer_id: Optional[str] = None, |
| 880 | + metadata: Optional[dict[str, Any]] = None, |
| 881 | + ) -> DetectionResult: |
| 882 | + """Detect social engineering tactics.""" |
| 883 | + input_data = self._resolve_detection_input( |
| 884 | + content_or_input, context, include_evidence, external_id, customer_id, metadata |
| 885 | + ) |
| 886 | + data = await self._request("POST", "/api/v1/fraud/social-engineering", self._build_detection_body(input_data)) |
| 887 | + return DetectionResult.from_dict(data) |
| 888 | + |
| 889 | + async def detect_app_fraud( |
| 890 | + self, |
| 891 | + content_or_input: Union[str, DetectionInput], |
| 892 | + *, |
| 893 | + context: Optional[AnalysisContext] = None, |
| 894 | + include_evidence: bool = False, |
| 895 | + external_id: Optional[str] = None, |
| 896 | + customer_id: Optional[str] = None, |
| 897 | + metadata: Optional[dict[str, Any]] = None, |
| 898 | + ) -> DetectionResult: |
| 899 | + """Detect app-based fraud patterns.""" |
| 900 | + input_data = self._resolve_detection_input( |
| 901 | + content_or_input, context, include_evidence, external_id, customer_id, metadata |
| 902 | + ) |
| 903 | + data = await self._request("POST", "/api/v1/fraud/app-fraud", self._build_detection_body(input_data)) |
| 904 | + return DetectionResult.from_dict(data) |
| 905 | + |
| 906 | + async def detect_romance_scam( |
| 907 | + self, |
| 908 | + content_or_input: Union[str, DetectionInput], |
| 909 | + *, |
| 910 | + context: Optional[AnalysisContext] = None, |
| 911 | + include_evidence: bool = False, |
| 912 | + external_id: Optional[str] = None, |
| 913 | + customer_id: Optional[str] = None, |
| 914 | + metadata: Optional[dict[str, Any]] = None, |
| 915 | + ) -> DetectionResult: |
| 916 | + """Detect romance scam patterns.""" |
| 917 | + input_data = self._resolve_detection_input( |
| 918 | + content_or_input, context, include_evidence, external_id, customer_id, metadata |
| 919 | + ) |
| 920 | + data = await self._request("POST", "/api/v1/fraud/romance-scam", self._build_detection_body(input_data)) |
| 921 | + return DetectionResult.from_dict(data) |
| 922 | + |
| 923 | + async def detect_mule_recruitment( |
| 924 | + self, |
| 925 | + content_or_input: Union[str, DetectionInput], |
| 926 | + *, |
| 927 | + context: Optional[AnalysisContext] = None, |
| 928 | + include_evidence: bool = False, |
| 929 | + external_id: Optional[str] = None, |
| 930 | + customer_id: Optional[str] = None, |
| 931 | + metadata: Optional[dict[str, Any]] = None, |
| 932 | + ) -> DetectionResult: |
| 933 | + """Detect money mule recruitment.""" |
| 934 | + input_data = self._resolve_detection_input( |
| 935 | + content_or_input, context, include_evidence, external_id, customer_id, metadata |
| 936 | + ) |
| 937 | + data = await self._request("POST", "/api/v1/fraud/mule-recruitment", self._build_detection_body(input_data)) |
| 938 | + return DetectionResult.from_dict(data) |
| 939 | + |
| 940 | + # ========================================================================= |
| 941 | + # Safety Extended |
| 942 | + # ========================================================================= |
| 943 | + |
| 944 | + async def detect_gambling_harm( |
| 945 | + self, |
| 946 | + content_or_input: Union[str, DetectionInput], |
| 947 | + *, |
| 948 | + context: Optional[AnalysisContext] = None, |
| 949 | + include_evidence: bool = False, |
| 950 | + external_id: Optional[str] = None, |
| 951 | + customer_id: Optional[str] = None, |
| 952 | + metadata: Optional[dict[str, Any]] = None, |
| 953 | + ) -> DetectionResult: |
| 954 | + """Detect gambling harm indicators.""" |
| 955 | + input_data = self._resolve_detection_input( |
| 956 | + content_or_input, context, include_evidence, external_id, customer_id, metadata |
| 957 | + ) |
| 958 | + data = await self._request("POST", "/api/v1/safety/gambling-harm", self._build_detection_body(input_data)) |
| 959 | + return DetectionResult.from_dict(data) |
| 960 | + |
| 961 | + async def detect_coercive_control( |
| 962 | + self, |
| 963 | + content_or_input: Union[str, DetectionInput], |
| 964 | + *, |
| 965 | + context: Optional[AnalysisContext] = None, |
| 966 | + include_evidence: bool = False, |
| 967 | + external_id: Optional[str] = None, |
| 968 | + customer_id: Optional[str] = None, |
| 969 | + metadata: Optional[dict[str, Any]] = None, |
| 970 | + ) -> DetectionResult: |
| 971 | + """Detect coercive control patterns.""" |
| 972 | + input_data = self._resolve_detection_input( |
| 973 | + content_or_input, context, include_evidence, external_id, customer_id, metadata |
| 974 | + ) |
| 975 | + data = await self._request("POST", "/api/v1/safety/coercive-control", self._build_detection_body(input_data)) |
| 976 | + return DetectionResult.from_dict(data) |
| 977 | + |
| 978 | + async def detect_vulnerability_exploitation( |
| 979 | + self, |
| 980 | + content_or_input: Union[str, DetectionInput], |
| 981 | + *, |
| 982 | + context: Optional[AnalysisContext] = None, |
| 983 | + include_evidence: bool = False, |
| 984 | + external_id: Optional[str] = None, |
| 985 | + customer_id: Optional[str] = None, |
| 986 | + metadata: Optional[dict[str, Any]] = None, |
| 987 | + ) -> DetectionResult: |
| 988 | + """Detect vulnerability exploitation with cross-endpoint modifier.""" |
| 989 | + input_data = self._resolve_detection_input( |
| 990 | + content_or_input, context, include_evidence, external_id, customer_id, metadata |
| 991 | + ) |
| 992 | + data = await self._request("POST", "/api/v1/safety/vulnerability-exploitation", self._build_detection_body(input_data)) |
| 993 | + return DetectionResult.from_dict(data) |
| 994 | + |
| 995 | + async def detect_radicalisation( |
| 996 | + self, |
| 997 | + content_or_input: Union[str, DetectionInput], |
| 998 | + *, |
| 999 | + context: Optional[AnalysisContext] = None, |
| 1000 | + include_evidence: bool = False, |
| 1001 | + external_id: Optional[str] = None, |
| 1002 | + customer_id: Optional[str] = None, |
| 1003 | + metadata: Optional[dict[str, Any]] = None, |
| 1004 | + ) -> DetectionResult: |
| 1005 | + """Detect radicalisation indicators.""" |
| 1006 | + input_data = self._resolve_detection_input( |
| 1007 | + content_or_input, context, include_evidence, external_id, customer_id, metadata |
| 1008 | + ) |
| 1009 | + data = await self._request("POST", "/api/v1/safety/radicalisation", self._build_detection_body(input_data)) |
| 1010 | + return DetectionResult.from_dict(data) |
| 1011 | + |
| 1012 | + # ========================================================================= |
| 1013 | + # Multi-Endpoint Analysis |
| 1014 | + # ========================================================================= |
| 1015 | + |
| 1016 | + async def analyse_multi( |
| 1017 | + self, input_data: AnalyseMultiInput |
| 1018 | + ) -> AnalyseMultiResult: |
| 1019 | + """Run multiple detection endpoints on a single piece of content. |
| 1020 | +
|
| 1021 | + Args: |
| 1022 | + input_data: AnalyseMultiInput with content and detections list. |
| 1023 | +
|
| 1024 | + Returns: |
| 1025 | + AnalyseMultiResult with per-endpoint results and summary. |
| 1026 | + """ |
| 1027 | + ctx = input_data.context.to_dict() if input_data.context else {} |
| 1028 | + ctx["platform"] = self._resolve_platform(ctx.get("platform")) |
| 1029 | + body: dict[str, Any] = { |
| 1030 | + "text": input_data.content, |
| 1031 | + "endpoints": input_data.detections, |
| 1032 | + "context": ctx, |
| 1033 | + } |
| 1034 | + if input_data.include_evidence: |
| 1035 | + body["options"] = {"include_evidence": True} |
| 1036 | + if input_data.external_id: |
| 1037 | + body["external_id"] = input_data.external_id |
| 1038 | + if input_data.customer_id: |
| 1039 | + body["customer_id"] = input_data.customer_id |
| 1040 | + if input_data.metadata: |
| 1041 | + body["metadata"] = input_data.metadata |
| 1042 | + |
| 1043 | + data = await self._request("POST", "/api/v1/analyse/multi", body) |
| 1044 | + return AnalyseMultiResult.from_dict(data) |
| 1045 | + |
| 1046 | + # ========================================================================= |
| 1047 | + # Video Analysis |
| 1048 | + # ========================================================================= |
| 1049 | + |
| 1050 | + async def analyze_video( |
| 1051 | + self, |
| 1052 | + file: bytes, |
| 1053 | + filename: str, |
| 1054 | + *, |
| 1055 | + file_id: Optional[str] = None, |
| 1056 | + external_id: Optional[str] = None, |
| 1057 | + customer_id: Optional[str] = None, |
| 1058 | + metadata: Optional[dict[str, Any]] = None, |
| 1059 | + age_group: Optional[str] = None, |
| 1060 | + platform: Optional[str] = None, |
| 1061 | + ) -> VideoAnalysisResult: |
| 1062 | + """Analyze video content for safety concerns. |
| 1063 | +
|
| 1064 | + Args: |
| 1065 | + file: Raw video file bytes. |
| 1066 | + filename: Original filename (e.g. "clip.mp4"). |
| 1067 | + file_id: Optional file identifier. |
| 1068 | + external_id: Your identifier for correlation. |
| 1069 | + customer_id: Customer identifier. |
| 1070 | + metadata: Custom metadata. |
| 1071 | + age_group: Age group context. |
| 1072 | + platform: Platform identifier. |
| 1073 | +
|
| 1074 | + Returns: |
| 1075 | + VideoAnalysisResult with frame findings and overall risk. |
| 1076 | + """ |
| 1077 | + fields: dict[str, Any] = { |
| 1078 | + "platform": self._resolve_platform(platform), |
| 1079 | + } |
| 1080 | + if file_id: |
| 1081 | + fields["file_id"] = file_id |
| 1082 | + if external_id: |
| 1083 | + fields["external_id"] = external_id |
| 1084 | + if customer_id: |
| 1085 | + fields["customer_id"] = customer_id |
| 1086 | + if metadata: |
| 1087 | + fields["metadata"] = json.dumps(metadata) |
| 1088 | + if age_group: |
| 1089 | + fields["age_group"] = age_group |
| 1090 | + |
| 1091 | + files = {"file": (filename, file, "application/octet-stream")} |
| 1092 | + data = await self._multipart_request("/api/v1/safety/video", fields, files) |
| 1093 | + return VideoAnalysisResult.from_dict(data) |
| 1094 | + |
827 | 1095 | # ========================================================================= |
828 | 1096 | # Voice Streaming |
829 | 1097 | # ========================================================================= |
|
0 commit comments