volcengine-python-sdk 4.0.16__py2.py3-none-any.whl → 4.0.17__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of volcengine-python-sdk might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: volcengine-python-sdk
3
- Version: 4.0.16
3
+ Version: 4.0.17
4
4
  Summary: Volcengine SDK for Python
5
5
  Home-page: https://github.com/volcengine/volcengine-python-sdk
6
6
  License: Apache License 2.0
@@ -3213,8 +3213,8 @@ volcenginesdkcloudtrail20180101/models/trail_for_describe_trails_output.py,sha25
3213
3213
  volcenginesdkcloudtrail20180101/models/update_trail_request.py,sha256=B3ekQIluA7_EJS-lDVFfS9opp_Q0AA85o7feAxhRCuQ,12596
3214
3214
  volcenginesdkcloudtrail20180101/models/update_trail_response.py,sha256=Gw7FcSWqHtttRxkuWdFuh9A69ZHcwHWEf2K8S9AfWQQ,10744
3215
3215
  volcenginesdkcore/__init__.py,sha256=hXXJZvkXO1--TZxyZTuFowfJp0BoBo6c3KYbGNuVc9k,239
3216
- volcenginesdkcore/api_client.py,sha256=t9XrPgW0WDXXlXMXMjQCVjK_Q7rfjhhK-TaYoDF74d0,15114
3217
- volcenginesdkcore/configuration.py,sha256=oz7aMZf8pxT6kPVbPcKEOd5Hm7tVSSz4aC0LeE-6nyI,10769
3216
+ volcenginesdkcore/api_client.py,sha256=isLSgMUf3919HNcAi2kFmuZO11w7ygoTygukB7STP9c,15114
3217
+ volcenginesdkcore/configuration.py,sha256=CnGmr3CkQ_gp1l-m130mQqzd7pRqA1clUmWG8ENl2I0,10769
3218
3218
  volcenginesdkcore/flatten.py,sha256=g3r61JS_AO7WV6ClRDkXgtnVXcw3c7tbZjeLAJxkSLc,3811
3219
3219
  volcenginesdkcore/metadata.py,sha256=uEwumzeIwgfu3_QBUk6BU_MQg_snF92Kke1J2L5TcGc,3419
3220
3220
  volcenginesdkcore/model.py,sha256=f3cvQ6QiQfecClucu-_-l5xVI3W5_vF-EKkjwZu7Vjc,177
@@ -3226,7 +3226,7 @@ volcenginesdkcore/auth/credential.py,sha256=LhD66-T1w_zhCXMht_JpMXO2pX05_r5dwmyq
3226
3226
  volcenginesdkcore/auth/providers/__init__.py,sha256=ezMR8ym3WLs0LdXFioeWaU1gLljgaH9_OCevWDWx89E,102
3227
3227
  volcenginesdkcore/auth/providers/provider.py,sha256=tkcsu66gX3F8KXcKxLLQm27mOhaAcnqnXGUmgp0Mseo,533
3228
3228
  volcenginesdkcore/auth/providers/static_provider.py,sha256=OGbYQ80a8T9FdR7EjpZ6s8h7_xzYO7OstWizFOiFeuA,548
3229
- volcenginesdkcore/auth/providers/sts_provider.py,sha256=d5v0zLc-ZH1AX0f_B4zuqaJYAKgkuND-_nSLOe-v7aI,3229
3229
+ volcenginesdkcore/auth/providers/sts_provider.py,sha256=rgGxyBk3W0dbesShpDSKq1xwrRD8aOdDXN9Junc19Nc,3242
3230
3230
  volcenginesdkcore/endpoint/__init__.py,sha256=K7dXFGGh0QYiALOLgtPxboGYzwCJB5IHseGSQPObZdQ,117
3231
3231
  volcenginesdkcore/endpoint/endpoint_provider.py,sha256=9ajUjf2N2NmThuWrMm5QgLtmh8YbPas5TdvkAZYQ9lM,330
3232
3232
  volcenginesdkcore/endpoint/providers/__init__.py,sha256=FmxBi_siUNTZod9GFOk5VG-UVYT7daNt4hZ2zifqYY8,76
@@ -6725,6 +6725,11 @@ volcenginesdklivesaas20230801/models/whitelist_viewing_restriction_for_update_vi
6725
6725
  volcenginesdklivesaas20230801/models/whitelist_viewing_restriction_for_update_viewing_restriction_output.py,sha256=QuKWHvZ-vHo0-MxiPWyhOVW29N7rVpTW-rbsCG3vyGE,6689
6726
6726
  volcenginesdklivesaas20230801/models/zoom_config_for_get_vod_player_config_output.py,sha256=H9oQplsZBAB68cH70Ja84dq0oV8vT6K3zZbqSSyT55M,3655
6727
6727
  volcenginesdklivesaas20230801/models/zoom_config_for_update_vod_player_config_input.py,sha256=-TMwP9NipvXdwgo1JNDDyq_HsVNNEnN3DFGq-v7iwUQ,3673
6728
+ volcenginesdkllmshield/__init__.py,sha256=0tXID7HFo_matygEeWBXQUeB-vc1BBuW36ImIiwqceE,1738
6729
+ volcenginesdkllmshield/api/__init__.py,sha256=di_9IIKQ0HCuzkGrTyZ6h38iP7y1xelH8_IJbKi4aWA,71
6730
+ volcenginesdkllmshield/api/llm_shield_sdk_v2.py,sha256=t3unTS8U8I5J_L09jVX4ESX8wQNinXSZ-XriJ2VFl_Q,16661
6731
+ volcenginesdkllmshield/models/__init__.py,sha256=OpEdwHNrJ0rcWEdVt0Km58pboIBWP4WYA8zv2LhzzWc,259
6732
+ volcenginesdkllmshield/models/llm_shield_sign.py,sha256=JmmaxTpGJR-5QltTHFffe8CXUv7rrCiWlx8jzt78r80,5640
6728
6733
  volcenginesdkmcdn/__init__.py,sha256=T6n7RhmCZfockyBT_7tbkB5Zb7moirVG3hC_l6lgUbw,19051
6729
6734
  volcenginesdkmcdn/api/__init__.py,sha256=1vA5hPYIN4Hv1_IMCWN9GHHaSWQrgd6tSGDmcUDeyXg,138
6730
6735
  volcenginesdkmcdn/api/mcdn_api.py,sha256=LbNhFv3JE0DcJFGtIx_79JYWyA1p53n7-F2ozW6YUVc,110963
@@ -13663,14 +13668,14 @@ volcenginesdkwaf/models/vul_for_create_domain_output.py,sha256=2iStwOpXqnVreaLjo
13663
13668
  volcenginesdkwaf/models/waf_action_list_for_get_tls_config_output.py,sha256=cdZlna1vQ3QG-MUGyOyjaycO0WHAT-gXpydZ_PJ3MhI,11680
13664
13669
  volcenginesdkwaf/models/waf_action_list_for_modify_tls_config_input.py,sha256=DMZcjsl_w6q9n0laMPBaLojH8jxCQb3V0dd6md4n9Mg,11786
13665
13670
  volcenginesdkwaf/models/web_backdoor_for_get_vulnerability_config_output.py,sha256=QiIjPj4k2ILlHPlP1aXEhv-aGzFngcp6-vhwwHzPiXc,7549
13666
- volcenginesdkwafruntime/__init__.py,sha256=oRCJ_LTUCaMoR7HlJqM6xnXbr4rb5_luzeM0wsjf63k,517
13671
+ volcenginesdkwafruntime/__init__.py,sha256=NqvhBz6iQAKGphWWkJVQ4eVwsfeyY7cf9wNrV1hJaOA,793
13667
13672
  volcenginesdkwafruntime/api/__init__.py,sha256=di_9IIKQ0HCuzkGrTyZ6h38iP7y1xelH8_IJbKi4aWA,71
13668
- volcenginesdkwafruntime/api/waf_runtime_api.py,sha256=Ya_maRHEfUUpkIVY4aU09PsVuOyewvAraYGEMPxqUCc,3776
13673
+ volcenginesdkwafruntime/api/waf_runtime_api.py,sha256=eWgdzFcwflEkhkNrPzZL1mqSid7O1OWiwHGUyWJvZNA,4035
13669
13674
  volcenginesdkwafruntime/models/__init__.py,sha256=di_9IIKQ0HCuzkGrTyZ6h38iP7y1xelH8_IJbKi4aWA,71
13670
- volcenginesdkwafruntime/models/llm_stream_session.py,sha256=bYCzl2GYXv2hS6J-yiEYjYHQpq4f7Vs-GslyuqL4bW4,1516
13671
- volcengine_python_sdk-4.0.16.dist-info/LICENSE.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
13672
- volcengine_python_sdk-4.0.16.dist-info/METADATA,sha256=wyVRZgGMzgzfrZtFrz5i52GcnxS26rzldOu8RQx6QK4,674
13673
- volcengine_python_sdk-4.0.16.dist-info/NOTICE.md,sha256=dqWX0O4-gFqGLdHJsXAiF6Q8JHlu_3nFaQSmrMHzujM,254
13674
- volcengine_python_sdk-4.0.16.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
13675
- volcengine_python_sdk-4.0.16.dist-info/top_level.txt,sha256=ehvVeTUQW6mfpJtv8XAvo_bnmVbptrq2eqL1Iub3TBo,2195
13676
- volcengine_python_sdk-4.0.16.dist-info/RECORD,,
13675
+ volcenginesdkwafruntime/models/llm_stream_session.py,sha256=U9cig3fuZRBT4Ov8_cUsWDKXcMLNjBDxKt-7IoewoUw,1589
13676
+ volcengine_python_sdk-4.0.17.dist-info/LICENSE.txt,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
13677
+ volcengine_python_sdk-4.0.17.dist-info/METADATA,sha256=3LBnEVQcUSuvePfehA-e2yKQmYpb3cf8vYRA3SO707g,674
13678
+ volcengine_python_sdk-4.0.17.dist-info/NOTICE.md,sha256=dqWX0O4-gFqGLdHJsXAiF6Q8JHlu_3nFaQSmrMHzujM,254
13679
+ volcengine_python_sdk-4.0.17.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
13680
+ volcengine_python_sdk-4.0.17.dist-info/top_level.txt,sha256=ynjiHmtvNtMbJ9sneg1RSWBhKzwNfX5yGiVKTa6AXYA,2218
13681
+ volcengine_python_sdk-4.0.17.dist-info/RECORD,,
@@ -50,6 +50,7 @@ volcenginesdkkafka
50
50
  volcenginesdkkms
51
51
  volcenginesdklivesaas
52
52
  volcenginesdklivesaas20230801
53
+ volcenginesdkllmshield
53
54
  volcenginesdkmcdn
54
55
  volcenginesdkmcs
55
56
  volcenginesdkmlplatform20240701
@@ -67,7 +67,7 @@ class ApiClient(object):
67
67
  self.default_headers[header_name] = header_value
68
68
  self.cookie = cookie
69
69
  # Set default User-Agent.
70
- self.user_agent = 'volcstack-python-sdk/4.0.16'
70
+ self.user_agent = 'volcstack-python-sdk/4.0.17'
71
71
  self.client_side_validation = configuration.client_side_validation
72
72
 
73
73
  self.interceptor_chain = InterceptorChain()
@@ -3,7 +3,7 @@ import time
3
3
  import uuid
4
4
  from datetime import datetime
5
5
 
6
- import dateutil.parser
6
+ import dateutil.parser, dateutil.tz
7
7
 
8
8
  from volcenginesdkcore import UniversalApi, UniversalInfo, ApiClient, Configuration
9
9
  from .provider import Provider, CredentialValue
@@ -267,7 +267,7 @@ class Configuration(six.with_metaclass(TypeWithDefault, object)):
267
267
  "OS: {env}\n" \
268
268
  "Python Version: {pyversion}\n" \
269
269
  "Version of the API: 0.1.0\n" \
270
- "SDK Package Version: 4.0.16".\
270
+ "SDK Package Version: 4.0.17".\
271
271
  format(env=sys.platform, pyversion=sys.version)
272
272
 
273
273
  @property
@@ -0,0 +1,82 @@
1
+ # coding: utf-8
2
+
3
+ # flake8: noqa
4
+
5
+ """
6
+ llmshield
7
+
8
+ No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) # noqa: E501
9
+
10
+ OpenAPI spec version: common-version
11
+
12
+ Generated by: https://github.com/swagger-api/swagger-codegen.git
13
+ """
14
+
15
+ from __future__ import absolute_import
16
+
17
+ # 导入API模块
18
+ from volcenginesdkllmshield.api.llm_shield_sdk_v2 import ClientV2
19
+ from volcenginesdkllmshield.api.llm_shield_sdk_v2 import (
20
+ ContentTypeV2,
21
+ DecisionTypeV2,
22
+ UserAction,
23
+ MatchSource,
24
+ MessageV2,
25
+ ModerateV2Request,
26
+ RiskMatchV2,
27
+ PermitMatchV2,
28
+ RiskV2,
29
+ RiskInfoV2,
30
+ PermitV2,
31
+ PermitInfoV2,
32
+ BlockDetailV2,
33
+ ReplaceDetailV2,
34
+ DecisionDetailV2,
35
+ DecisionV2,
36
+ ModerateV2Result,
37
+ ErrorInfo,
38
+ ResponseMetadata,
39
+ ModerateV2Response,
40
+ ModerateV2StreamSession,
41
+ GenerateStreamV2Request,
42
+ GenerateStreamV2Response,
43
+ GenerateSummarizeV2,
44
+ GenerateStreamResult,
45
+ GenerateStreamV2ResponseData,
46
+ CustomJSONEncoder
47
+ )
48
+
49
+
50
+ __all__ = [
51
+ # API客户端
52
+ 'ClientV2',
53
+ # 常量类
54
+ 'ContentTypeV2',
55
+ 'DecisionTypeV2',
56
+ 'UserAction',
57
+ 'MatchSource',
58
+ # 数据模型
59
+ 'MessageV2',
60
+ 'ModerateV2Request',
61
+ 'RiskMatchV2',
62
+ 'PermitMatchV2',
63
+ 'RiskV2',
64
+ 'RiskInfoV2',
65
+ 'PermitV2',
66
+ 'PermitInfoV2',
67
+ 'BlockDetailV2',
68
+ 'ReplaceDetailV2',
69
+ 'DecisionDetailV2',
70
+ 'DecisionV2',
71
+ 'ModerateV2Result',
72
+ 'ErrorInfo',
73
+ 'ResponseMetadata',
74
+ 'ModerateV2Response',
75
+ 'ModerateV2StreamSession',
76
+ 'GenerateStreamV2Request',
77
+ 'GenerateStreamV2Response',
78
+ 'GenerateSummarizeV2',
79
+ 'GenerateStreamResult',
80
+ 'GenerateStreamV2ResponseData',
81
+ 'CustomJSONEncoder',
82
+ ]
@@ -0,0 +1,4 @@
1
+ # coding: utf-8
2
+ # flake8: noqa
3
+
4
+ from __future__ import absolute_import
@@ -0,0 +1,470 @@
1
+ from pydantic import BaseModel, field_validator, Field
2
+ from typing import List, Optional, Any, Union
3
+ from datetime import datetime, date
4
+ from uuid import UUID
5
+ import requests
6
+ import json
7
+
8
+ from ..models.llm_shield_sign import request_sign, Version
9
+
10
+ LLM_STREAM_SEND_BASE_WINDOW_V2 = 10
11
+ LLM_STREAM_SEND_EXPONENT_V2 = 2
12
+
13
+
14
+ # 定义内容类型常量
15
+ class ContentTypeV2:
16
+ TEXT = 1
17
+ AUDIO = 2
18
+ IMAGE = 3
19
+ VIDEO = 4
20
+
21
+
22
+ # 定义决策类型常量
23
+ class DecisionTypeV2:
24
+ PASS = 1
25
+ BLOCK = 2
26
+ MARK = 3
27
+ REPLACE = 4
28
+ OPTIMIZE = 5
29
+
30
+
31
+ # 定义用户操作常量
32
+ class UserAction:
33
+ PASS = 1
34
+ BLOCK = 2
35
+ MARK = 3
36
+ REPLACE = 4
37
+
38
+
39
+ # 定义匹配来源常量
40
+ class MatchSource:
41
+ UNKNOWN = 0
42
+ GLOBAL_CONTENTLIB = 1
43
+ ADMIN_CONTENTLIB = 2
44
+ USER_CONTENTLIB = 3
45
+
46
+
47
+ # 定义消息结构体
48
+ class MessageV2(BaseModel):
49
+ role: str = Field("", alias="Role")
50
+ content: str = Field("", alias="Content")
51
+ content_type: int = Field(ContentTypeV2.TEXT, alias="ContentType")
52
+
53
+ class Config:
54
+ populate_by_name = True
55
+
56
+
57
+ # 定义审核请求结构体
58
+ class ModerateV2Request(BaseModel):
59
+ message: MessageV2 = Field(None, alias="Message")
60
+ msg_id: str = Field("", alias="MsgID")
61
+ use_stream: int = Field(0, alias="UseStream")
62
+ scene: str = Field("", alias="Scene")
63
+ history: List[MessageV2] = Field([], alias="History")
64
+
65
+ class Config:
66
+ populate_by_name = True
67
+
68
+ # 深拷贝构造方法:通过 Pydantic 序列化/反序列化实现
69
+ def __init__(self, other=None, **data):
70
+ # 如果传入了其他 ModerateV2Request 实例,则进行深拷贝
71
+ if other is not None and isinstance(other, ModerateV2Request):
72
+ # 1. 将其他实例序列化为字典(包含嵌套对象)
73
+ other_dict = other.model_dump(by_alias=True) # 使用 alias 键名
74
+ # 2. 用序列化后的字典初始化当前实例(实现深拷贝)
75
+ super().__init__(**other_dict)
76
+ else:
77
+ # 正常初始化逻辑
78
+ super().__init__(**data)
79
+
80
+
81
+ # 定义风险匹配结构体
82
+ class RiskMatchV2(BaseModel):
83
+ word: str = Field("", alias="Word")
84
+ action: Optional[int] = Field(None, alias="Action")
85
+ source: Optional[int] = Field(None, alias="Source")
86
+ rule_id: Optional[Any] = Field(None, alias="RuleID")
87
+
88
+ class Config:
89
+ populate_by_name = True
90
+
91
+
92
+ # 定义放行匹配结构体
93
+ class PermitMatchV2(BaseModel):
94
+ word: str = Field("", alias="Word")
95
+ action: Optional[int] = Field(None, alias="Action")
96
+ source: Optional[int] = Field(None, alias="Source")
97
+ rule_id: Optional[Any] = Field(None, alias="RuleID")
98
+
99
+ class Config:
100
+ populate_by_name = True
101
+
102
+
103
+ # 定义风险结构体
104
+ class RiskV2(BaseModel):
105
+ category: str = Field("", alias="Category")
106
+ label: str = Field("", alias="Label")
107
+ prob: Optional[float] = Field(None, alias="Prob")
108
+ matches: List[RiskMatchV2] = Field([], alias="Matches")
109
+
110
+ @field_validator('matches', mode="before")
111
+ def convert_risk_matches_none_to_list(cls, value):
112
+ return [] if value is None else value
113
+
114
+ class Config:
115
+ populate_by_name = True
116
+
117
+
118
+ # 定义风险信息结构体 - 添加 None 转换
119
+ class RiskInfoV2(BaseModel):
120
+ risks: List[RiskV2] = Field([], alias="Risks")
121
+
122
+ @field_validator('risks', mode="before")
123
+ def convert_none_to_list(cls, value):
124
+ return [] if value is None else value
125
+
126
+ class Config:
127
+ populate_by_name = True
128
+
129
+
130
+ # 定义放行结构体
131
+ class PermitV2(BaseModel):
132
+ category: str = Field("", alias="Category")
133
+ label: str = Field("", alias="Label")
134
+ prob: Optional[float] = Field(None, alias="Prob")
135
+ matches: List[PermitMatchV2] = Field([], alias="Matches")
136
+
137
+ @field_validator('matches', mode="before")
138
+ def convert_permit_matches_none_to_list(cls, value):
139
+ return [] if value is None else value
140
+
141
+ class Config:
142
+ populate_by_name = True
143
+
144
+
145
+ # 定义放行信息结构体 - 添加 None 转换
146
+ class PermitInfoV2(BaseModel):
147
+ permits: List[PermitV2] = Field([], alias="Permits")
148
+
149
+ @field_validator('permits', mode="before")
150
+ def convert_none_to_list(cls, value):
151
+ return [] if value is None else value
152
+
153
+ class Config:
154
+ populate_by_name = True
155
+
156
+
157
+ # 定义拦截详情结构体
158
+ class BlockDetailV2(BaseModel):
159
+ class Config:
160
+ extra = "forbid"
161
+
162
+
163
+ # 定义替换详情结构体
164
+ class ReplaceDetailV2(BaseModel):
165
+ replacement: Optional[MessageV2] = Field(None, alias="Replacement")
166
+
167
+ class Config:
168
+ populate_by_name = True
169
+
170
+
171
+ # 定义决策详情结构体
172
+ class DecisionDetailV2(BaseModel):
173
+ block_detail: Optional[BlockDetailV2] = Field(None, alias="BlockDetail")
174
+ replace_detail: Optional[ReplaceDetailV2] = Field(None, alias="ReplaceDetail")
175
+
176
+ class Config:
177
+ populate_by_name = True
178
+
179
+
180
+ # 定义决策结构体
181
+ class DecisionV2(BaseModel):
182
+ decision_type: int = Field(0, alias="DecisionType")
183
+ decision_detail: DecisionDetailV2 = Field(default_factory=DecisionDetailV2, alias="DecisionDetail")
184
+ decision_strategy_id: Optional[str] = Field(None, alias="DecisionStrategyID")
185
+ hit_strategy_ids: List[str] = Field([], alias="HitStrategyIDs")
186
+
187
+ @field_validator('hit_strategy_ids', mode="before")
188
+ def convert_hit_strategies_none_to_list(cls, value):
189
+ return [] if value is None else value
190
+
191
+ class Config:
192
+ populate_by_name = True
193
+
194
+
195
+ # 定义审核结果结构体
196
+ class ModerateV2Result(BaseModel):
197
+ msg_id: str = Field("", alias="MsgID")
198
+ risk_info: RiskInfoV2 = Field(default_factory=RiskInfoV2, alias="RiskInfo")
199
+ decision: DecisionV2 = Field(default_factory=DecisionV2, alias="Decision")
200
+ permit_info: PermitInfoV2 = Field(default_factory=PermitInfoV2, alias="PermitInfo")
201
+ degraded: bool = Field(False, alias="Degraded")
202
+ degrade_reason: str = Field("", alias="DegradeReason")
203
+
204
+ class Config:
205
+ populate_by_name = True
206
+
207
+
208
+ # 定义错误信息结构体
209
+ class ErrorInfo(BaseModel):
210
+ code: str = Field("", alias="Code")
211
+ codeN: Union[int, str] = Field("", alias="CodeN")
212
+ message: str = Field("", alias="Message")
213
+
214
+ class Config:
215
+ populate_by_name = True
216
+
217
+
218
+ # 定义响应元数据结构体
219
+ class ResponseMetadata(BaseModel):
220
+ error: Union[ErrorInfo, None] = Field(default_factory=ErrorInfo, alias="Error")
221
+ requestId: str = Field(..., alias="RequestId") # 添加requestId字段,映射自RequestId
222
+ service: Union[str, None] = Field(None, alias="Service")
223
+ action: Union[str, None] = Field(None, alias="Action")
224
+ version: Union[str, None] = Field(None, alias="Version")
225
+ region: Union[str, None] = Field(None, alias="Region")
226
+
227
+ class Config:
228
+ populate_by_name = True
229
+ validate_by_name = True
230
+
231
+
232
+ # 定义审核响应结构体
233
+ class ModerateV2Response(BaseModel):
234
+ response_metadata: ResponseMetadata = Field(default_factory=ResponseMetadata, alias="ResponseMetadata")
235
+ result: Union[ModerateV2Result, None] = Field(default_factory=ModerateV2Result, alias="Result")
236
+
237
+ class Config:
238
+ populate_by_name = True
239
+
240
+
241
+ class ModerateV2StreamSession:
242
+ """流式会话结构体,用于积累流式请求、存储未发送长度和默认响应体"""
243
+
244
+ def __init__(self):
245
+ # 用于积累流式的请求(初始为 None,对应 Go 中的指针)
246
+ self.request: Optional[ModerateV2Request] = None
247
+ # 未发送的长度(对应 Go 中的 StreamSendLen)
248
+ self.stream_send_len: int = 0
249
+ self.CurrentSendWindow = LLM_STREAM_SEND_BASE_WINDOW_V2
250
+ # 存储默认响应体(初始为 None,对应 Go 中的指针)
251
+ self.default_body: Optional[ModerateV2Response] = None
252
+
253
+
254
+ class GenerateStreamV2Request(BaseModel):
255
+ """生成流V2版本的请求模型"""
256
+ msg_id: str = Field(..., alias="MsgID", description="消息ID,表示请求的唯一标识")
257
+
258
+ class Config:
259
+ populate_by_name = True
260
+ json_schema_extra = {
261
+ "validate": {"required": True} # 对应Go中的validate:"required"
262
+ }
263
+
264
+
265
+ class GenerateStreamV2Response(BaseModel):
266
+ def __init__(self, reader=None):
267
+ self.Reader = reader
268
+
269
+
270
+ class GenerateSummarizeV2(BaseModel):
271
+ """生成过程的总结信息模型"""
272
+ token_cost: int = Field(0, alias="TokenCost", description="消耗的token数量")
273
+ time_cost_ms: int = Field(0, alias="TimeCostMS", description="消耗的时长(毫秒)")
274
+
275
+ class Config:
276
+ populate_by_name = True
277
+
278
+
279
+ class GenerateStreamResult(BaseModel):
280
+ """生成流V2版本的结果模型"""
281
+ message: Optional[MessageV2] = Field(None, alias="Message", description="优化内容,isFinished为true时为空")
282
+ is_finished: bool = Field(False, alias="IsFinished", description="标识是否结束")
283
+
284
+ # summarize: Optional[GenerateSummarizeV2] = Field(None, alias="Summarize", description="总结信息,isFinished为true时有值")
285
+
286
+ class Config:
287
+ populate_by_name = True
288
+
289
+
290
+ class GenerateStreamV2ResponseData(BaseModel):
291
+ """生成流V2版本的响应数据模型"""
292
+ response_metadata: ResponseMetadata = Field(..., alias="ResponseMetadata", description="响应元数据")
293
+ result: GenerateStreamResult = Field(..., alias="Result", description="生成流结果")
294
+
295
+ class Config:
296
+ populate_by_name = True
297
+
298
+
299
+ # 定义客户端类
300
+ class ClientV2:
301
+ def __init__(self, url: str, ak: str, sk: str, region: str, timeout: float):
302
+ self.url = url
303
+ self.ak = ak
304
+ self.sk = sk
305
+ self.region = region
306
+ self.http_client = requests.Session()
307
+ self.http_client.timeout = timeout
308
+
309
+ def Moderate(self, request: Optional[ModerateV2Request] = None) -> ModerateV2Response:
310
+ path = "/v2/moderate"
311
+ action = "Moderate"
312
+
313
+ if request is None:
314
+ request = ModerateV2Request()
315
+
316
+ request_body = request.model_dump_json(by_alias=True).encode("utf-8")
317
+
318
+ header = {
319
+ }
320
+
321
+ sign_header = request_sign(header, self.ak, self.sk, self.region, self.url, path, action, request_body)
322
+
323
+ try:
324
+ resp = self.http_client.post(
325
+ url=self.url + path + "?Action=" + action + "&Version=" + Version,
326
+ data=request_body,
327
+ headers=sign_header
328
+ )
329
+
330
+ response = ModerateV2Response.model_validate(resp.json())
331
+ return response
332
+
333
+ except requests.RequestException as e:
334
+ raise Exception(f"请求失败: {e}")
335
+ except Exception as e:
336
+ raise Exception(f"处理响应失败: {e}")
337
+
338
+ def ModerateStream(
339
+ self, request: ModerateV2Request, session: ModerateV2StreamSession
340
+ ) -> Optional[ModerateV2Response]:
341
+ """
342
+ 处理流式审核请求
343
+ :param request: 当前流式请求片段(ModerateV2Request 类型)
344
+ :param session: 流式会话对象(ModerateV2StreamSession 类型)
345
+ :return: 审核响应(ModerateV2Response 类型)
346
+ """
347
+ # 1. 校验参数合法性
348
+ path = "/v2/moderate"
349
+ action = "Moderate"
350
+ if request is None:
351
+ request = ModerateV2Request() # 初始化空请求
352
+
353
+ # 本接口仅支持流式调用(use_stream 不能为 0,且 session 不能为空)
354
+ if request.use_stream == 0 or session is None:
355
+ raise ValueError("use_stream cannot be 0, and session cannot be None")
356
+
357
+ is_first_request = (session.request is None) # 判断是否为首次请求
358
+ is_last_request = (request.use_stream == 2) # 判断是否为最后一次请求
359
+
360
+ # 2. 初始化或追加会话请求(深拷贝确保隔离)
361
+ if session.request is None:
362
+ # 首次请求:深拷贝初始请求到 session
363
+ session.request = ModerateV2Request(request)
364
+ else:
365
+ # 后续请求:追加当前请求内容到 session 积累的请求中
366
+ # 示例:追加 message.content(根据实际业务调整)
367
+ if request.message and request.message.content:
368
+ if session.request.message is None:
369
+ session.request.message = MessageV2()
370
+ session.request.message.content += request.message.content
371
+ session.request.use_stream = request.use_stream
372
+ session.stream_send_len += len(request.message.content)
373
+
374
+ # 3. 判断是否需要发送请求到后端
375
+ # 只有当未检测长度 >= 10 或者是第一次或者是最后一次请求时,才发送请求
376
+ need_send_request = is_last_request or is_first_request or (
377
+ session.stream_send_len >= session.CurrentSendWindow)
378
+
379
+ # 如果不需要发送请求,直接返回上次的默认响应(如果有)
380
+ if not need_send_request:
381
+ return session.default_body
382
+ else:
383
+ session.CurrentSendWindow = session.CurrentSendWindow * LLM_STREAM_SEND_EXPONENT_V2
384
+
385
+ # 3. 序列化请求(使用 Pydantic 的 model_dump 方法)
386
+ try:
387
+ request_body = session.request.model_dump_json(by_alias=True).encode("utf-8")
388
+ # request_str = session.request.model_dump_json(by_alias=True)
389
+ except Exception as e:
390
+ raise IOError(f"Failed to serialize request: {str(e)}")
391
+ headers = {
392
+ # "Content-Type": "application/json",
393
+ }
394
+ sign_header = request_sign(headers, self.ak, self.sk, self.region, self.url, path, action, request_body)
395
+ try:
396
+ response = requests.post(
397
+ url=self.url + path + "?Action=" + action + "&Version=" + Version,
398
+ data=request_body,
399
+ headers=sign_header
400
+ )
401
+ except requests.exceptions.RequestException as e:
402
+ raise IOError(f"HTTP request failed: {str(e)}")
403
+
404
+ # 5. 解析响应
405
+ try:
406
+ response_data = json.loads(response.text)
407
+ moderate_response = ModerateV2Response(**response_data)
408
+ except Exception as e:
409
+ raise IOError(f"Failed to parse response: {str(e)}")
410
+
411
+ # 6. 更新会话状态
412
+ session.default_body = moderate_response # 存储响应体
413
+ session.stream_send_len = 0 # 重置未发送长度(根据实际业务调整)
414
+ session.request.msg_id = moderate_response.result.msg_id
415
+
416
+ # 7. 若为最后一次流式请求(use_stream == 2),打印最终内容
417
+ if session.request.use_stream == 2:
418
+ final_content = session.request.message.content if (
419
+ session.request.message and session.request.message.content) else ""
420
+ print(f"最终检测内容: {final_content}")
421
+
422
+ return moderate_response
423
+
424
+ def GenerateV2Stream(self, request):
425
+ path = "/v2/generate"
426
+ action = "Generate"
427
+ if request is None:
428
+ request = GenerateStreamV2Request()
429
+
430
+ # 将请求结构体序列化为 JSON
431
+ requestBody = request.model_dump_json(by_alias=True).encode("utf-8")
432
+
433
+ headers = {
434
+ # "Content-Type": "application/json",
435
+ }
436
+ try:
437
+ sign_header = request_sign(headers, self.ak, self.sk, self.region, self.url, path, action, requestBody)
438
+ # 发送 HTTP 请求
439
+ resp = self.http_client.post(url=self.url + path + "?Action=" + action + "&Version=" + Version,
440
+ data=requestBody, headers=sign_header, stream=True)
441
+ if resp.status_code != 200:
442
+ raise Exception("bad response code: %d" % resp.status_code)
443
+
444
+ for line in resp.iter_lines():
445
+ if line:
446
+ line = line.decode('utf-8')
447
+ if line.lstrip().startswith('data:'):
448
+ sse_data = line[line.index(':') + 1:].strip()
449
+ yield sse_data
450
+ except Exception as e:
451
+ return None, Exception("failed to send request: %s" % str(e))
452
+
453
+
454
+ # 自定义 JSON 编码器
455
+ class CustomJSONEncoder(json.JSONEncoder):
456
+ def default(self, obj):
457
+ # 处理datetime类型(如2023-10-01T12:00:00)
458
+ if isinstance(obj, datetime):
459
+ return obj.isoformat()
460
+ # 处理date类型(如2023-10-01)
461
+ elif isinstance(obj, date):
462
+ return obj.isoformat()
463
+ # 处理UUID类型
464
+ elif isinstance(obj, UUID):
465
+ return str(obj)
466
+ # 处理其他未知的自定义类型(返回类型信息便于调试)
467
+ elif hasattr(obj, '__dict__'):
468
+ return obj.__dict__ # 返回对象的属性字典
469
+ # 调用默认处理(会抛出TypeError)
470
+ return super().default(obj)
@@ -0,0 +1,20 @@
1
+ # coding:utf-8
2
+
3
+ from __future__ import absolute_import
4
+
5
+ from .llm_shield_sign import (
6
+ request_sign,
7
+ Service,
8
+ Version,
9
+ ContentType,
10
+ Method
11
+ )
12
+
13
+ __all__ = [
14
+ 'request_sign',
15
+ 'Service',
16
+ 'Version',
17
+ 'ContentType',
18
+ 'Method'
19
+ ]
20
+
@@ -0,0 +1,159 @@
1
+ # coding:utf-8
2
+ """
3
+ Copyright (year) Beijing Volcano Engine Technology Ltd.
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+ """
17
+
18
+ import datetime
19
+ import hashlib
20
+ import hmac
21
+ from urllib.parse import quote, urlparse
22
+ import requests
23
+
24
+ # 以下参数视服务不同而不同,一个服务内通常是一致的
25
+ Service = "llmshield"
26
+ Version = "2025-08-31"
27
+ ContentType = "application/json"
28
+ Method = "POST"
29
+
30
+
31
+ def norm_query(params):
32
+ query = ""
33
+ for key in sorted(params.keys()):
34
+ if type(params[key]) == list:
35
+ for k in params[key]:
36
+ query = (
37
+ query + quote(key, safe="-_.~") + "=" + quote(k, safe="-_.~") + "&"
38
+ )
39
+ else:
40
+ query = (query + quote(key, safe="-_.~") + "=" + quote(params[key], safe="-_.~") + "&")
41
+ query = query[:-1]
42
+ return query.replace("+", "%20")
43
+
44
+
45
+ # 第一步:准备辅助函数。
46
+ # sha256 非对称加密
47
+ def hmac_sha256(key: bytes, content: str):
48
+ return hmac.new(key, content.encode("utf-8"), hashlib.sha256).digest()
49
+
50
+
51
+ # sha256 hash算法
52
+ def hash_sha256(content: bytes):
53
+ return hashlib.sha256(content).hexdigest()
54
+
55
+
56
+ # 第二步:签名请求函数
57
+ def request_sign(header, ak, sk, region, url, path, action, body):
58
+ host = urlparse(url).netloc
59
+ date = utc_now()
60
+ # 第三步:创建身份证明。其中的 Service 和 Region 字段是固定的。ak 和 sk 分别代表
61
+ # AccessKeyID 和 SecretAccessKey。同时需要初始化签名结构体。一些签名计算时需要的属性也在这里处理。
62
+ # 初始化身份证明结构体
63
+ credential = {
64
+ "access_key_id": ak,
65
+ "secret_access_key": sk,
66
+ "service": Service,
67
+ "region": region,
68
+ }
69
+ # 初始化签名结构体
70
+ request_param = {
71
+ "body": body,
72
+ "host": host,
73
+ "path": path,
74
+ "method": Method,
75
+ "content_type": ContentType,
76
+ "date": date,
77
+ "query": {"Action": action, "Version": Version},
78
+ }
79
+ if body is None:
80
+ request_param["body"] = ""
81
+ # 第四步:接下来开始计算签名。在计算签名前,先准备好用于接收签算结果的 signResult 变量,并设置一些参数。
82
+ # 初始化签名结果的结构体
83
+ x_date = request_param["date"].strftime("%Y%m%dT%H%M%SZ")
84
+ short_x_date = x_date[:8]
85
+ x_content_sha256 = hash_sha256(request_param["body"])
86
+ sign_result = {
87
+ "Host": request_param["host"],
88
+ "X-Content-Sha256": x_content_sha256,
89
+ "X-Date": x_date,
90
+ "Content-Type": request_param["content_type"],
91
+ }
92
+ # 第五步:计算 Signature 签名。
93
+ signed_headers_str = ";".join(
94
+ ["content-type", "host", "x-content-sha256", "x-date"]
95
+ )
96
+ # signed_headers_str = signed_headers_str + ";x-security-token"
97
+ canonical_request_str = "\n".join(
98
+ [request_param["method"].upper(),
99
+ request_param["path"],
100
+ norm_query(request_param["query"]),
101
+ "\n".join(
102
+ [
103
+ "content-type:" + request_param["content_type"],
104
+ "host:" + request_param["host"],
105
+ "x-content-sha256:" + x_content_sha256,
106
+ "x-date:" + x_date,
107
+ ]
108
+ ),
109
+ "",
110
+ signed_headers_str,
111
+ x_content_sha256,
112
+ ]
113
+ )
114
+
115
+ # 打印正规化的请求用于调试比对
116
+ # print(canonical_request_str)
117
+ hashed_canonical_request = hash_sha256(canonical_request_str.encode("utf-8"))
118
+
119
+ # 打印hash值用于调试比对
120
+ # print(hashed_canonical_request)
121
+ credential_scope = "/".join([short_x_date, credential["region"], credential["service"], "request"])
122
+ string_to_sign = "\n".join(["HMAC-SHA256", x_date, credential_scope, hashed_canonical_request])
123
+
124
+ # 打印最终计算的签名字符串用于调试比对
125
+ # print(string_to_sign)
126
+ k_date = hmac_sha256(credential["secret_access_key"].encode("utf-8"), short_x_date)
127
+ k_region = hmac_sha256(k_date, credential["region"])
128
+ k_service = hmac_sha256(k_region, credential["service"])
129
+ k_signing = hmac_sha256(k_service, "request")
130
+ signature = hmac_sha256(k_signing, string_to_sign).hex()
131
+
132
+ sign_result["Authorization"] = "HMAC-SHA256 Credential={}, SignedHeaders={}, Signature={}".format(
133
+ credential["access_key_id"] + "/" + credential_scope,
134
+ signed_headers_str,
135
+ signature,
136
+ )
137
+ header = {**header, **sign_result, "X-Top-Service": Service, "X-Top-Region": region}
138
+ # header = {**header, **{"X-Security-Token": SessionToken}}
139
+ # 第六步:将 Signature 签名写入 HTTP Header 中,并发送 HTTP 请求。
140
+ return header
141
+
142
+
143
+ # datetime.utcnow() 在 3.12+ 已经过期,使用如下方法兼容
144
+ def utc_now():
145
+ try:
146
+ from datetime import timezone
147
+ return datetime.datetime.now(timezone.utc)
148
+ except ImportError:
149
+ class UTC(datetime.tzinfo):
150
+ def utcoffset(self, dt):
151
+ return datetime.timedelta(0)
152
+
153
+ def tzname(self, dt):
154
+ return "UTC"
155
+
156
+ def dst(self, dt):
157
+ return datetime.timedelta(0)
158
+
159
+ return datetime.datetime.now(UTC())
@@ -15,5 +15,7 @@
15
15
  from volcenginesdkwaf import *
16
16
  from volcenginesdkwafruntime.api.waf_runtime_api import WAFRuntimeApi
17
17
  from volcenginesdkwafruntime.models.llm_stream_session import LLMStreamSession
18
-
19
- __all__ = ["WAFRuntimeApi", "LLMStreamSession"]
18
+ from volcenginesdkwafruntime.models.llm_stream_session import LLM_STREAM_SEND_EXPONENT
19
+ from volcenginesdkwafruntime.models.llm_stream_session import LLM_STREAM_SEND_BASE_WINDOW
20
+ __all__ = ["WAFRuntimeApi", "LLMStreamSession" ,
21
+ "LLM_STREAM_SEND_EXPONENT" , "LLM_STREAM_SEND_BASE_WINDOW"] # @opensource-lint-ignore
@@ -1,11 +1,13 @@
1
-
2
1
  from volcenginesdkwaf import WAFApi, CheckLLMResponseStreamRequest
3
- from volcenginesdkwafruntime.models.llm_stream_session import LLMStreamSession
2
+ from volcenginesdkwafruntime.models.llm_stream_session import LLMStreamSession, LLM_STREAM_SEND_EXPONENT, \
3
+ LLM_STREAM_SEND_BASE_WINDOW
4
4
 
5
5
  global_llm_send_len = 10
6
6
 
7
+
7
8
  class WAFRuntimeApi(WAFApi):
8
9
  """继承自 WAFApi 并重写 check_llm_response_stream 方法"""
10
+
9
11
  def check_llm_response_stream(
10
12
  self,
11
13
  body: CheckLLMResponseStreamRequest,
@@ -52,13 +54,15 @@ class WAFRuntimeApi(WAFApi):
52
54
 
53
55
  # 重置流缓冲区和发送长度
54
56
  session.set_stream_send_len(0)
57
+ session.CurrentSendWindow = session.CurrentSendWindow * LLM_STREAM_SEND_EXPONENT
55
58
 
56
59
  return response
57
60
 
58
61
  # 3. 处理 use_stream 为其他值的情况(累计长度,超过阈值才发送)
59
62
  else:
60
63
  # 如果未发送长度超过 10 个字符,调用 API
61
- if session.get_stream_send_len() > global_llm_send_len:
64
+ if session.get_stream_send_len() >= session.CurrentSendWindow:
65
+ session.CurrentSendWindow = session.CurrentSendWindow * LLM_STREAM_SEND_EXPONENT
62
66
  # 准备请求体,使用 session 中的完整流内容
63
67
  body.content = session.get_stream_buf()
64
68
  body.msg_id = session.get_msg_id()
@@ -2,10 +2,11 @@ from typing import Optional
2
2
 
3
3
  from volcenginesdkwaf import CheckLLMResponseStreamResponse
4
4
 
5
+ LLM_STREAM_SEND_BASE_WINDOW = 10
6
+ LLM_STREAM_SEND_EXPONENT = 2
5
7
 
6
- class LLMStreamSession:
7
- """对应 Java 中的 LLMStreamSession 类"""
8
8
 
9
+ class LLMStreamSession:
9
10
  def __init__(
10
11
  self,
11
12
  stream_buf: str = "",
@@ -17,6 +18,7 @@ class LLMStreamSession:
17
18
  self.stream_send_len = stream_send_len
18
19
  self.msg_id = msg_id
19
20
  self.default_body = default_body
21
+ self.CurrentSendWindow = LLM_STREAM_SEND_BASE_WINDOW
20
22
 
21
23
  def get_stream_buf(self) -> str:
22
24
  return self.stream_buf