dashscope 1.8.0__py3-none-any.whl → 1.25.6__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.
Files changed (110) hide show
  1. dashscope/__init__.py +61 -14
  2. dashscope/aigc/__init__.py +10 -3
  3. dashscope/aigc/chat_completion.py +282 -0
  4. dashscope/aigc/code_generation.py +145 -0
  5. dashscope/aigc/conversation.py +71 -12
  6. dashscope/aigc/generation.py +288 -16
  7. dashscope/aigc/image_synthesis.py +473 -31
  8. dashscope/aigc/multimodal_conversation.py +299 -14
  9. dashscope/aigc/video_synthesis.py +610 -0
  10. dashscope/api_entities/aiohttp_request.py +8 -5
  11. dashscope/api_entities/api_request_data.py +4 -2
  12. dashscope/api_entities/api_request_factory.py +68 -20
  13. dashscope/api_entities/base_request.py +20 -3
  14. dashscope/api_entities/chat_completion_types.py +344 -0
  15. dashscope/api_entities/dashscope_response.py +243 -15
  16. dashscope/api_entities/encryption.py +179 -0
  17. dashscope/api_entities/http_request.py +216 -62
  18. dashscope/api_entities/websocket_request.py +43 -34
  19. dashscope/app/__init__.py +5 -0
  20. dashscope/app/application.py +203 -0
  21. dashscope/app/application_response.py +246 -0
  22. dashscope/assistants/__init__.py +16 -0
  23. dashscope/assistants/assistant_types.py +175 -0
  24. dashscope/assistants/assistants.py +311 -0
  25. dashscope/assistants/files.py +197 -0
  26. dashscope/audio/__init__.py +4 -2
  27. dashscope/audio/asr/__init__.py +17 -1
  28. dashscope/audio/asr/asr_phrase_manager.py +203 -0
  29. dashscope/audio/asr/recognition.py +167 -27
  30. dashscope/audio/asr/transcription.py +107 -14
  31. dashscope/audio/asr/translation_recognizer.py +1006 -0
  32. dashscope/audio/asr/vocabulary.py +177 -0
  33. dashscope/audio/qwen_asr/__init__.py +7 -0
  34. dashscope/audio/qwen_asr/qwen_transcription.py +189 -0
  35. dashscope/audio/qwen_omni/__init__.py +11 -0
  36. dashscope/audio/qwen_omni/omni_realtime.py +524 -0
  37. dashscope/audio/qwen_tts/__init__.py +5 -0
  38. dashscope/audio/qwen_tts/speech_synthesizer.py +77 -0
  39. dashscope/audio/qwen_tts_realtime/__init__.py +10 -0
  40. dashscope/audio/qwen_tts_realtime/qwen_tts_realtime.py +355 -0
  41. dashscope/audio/tts/__init__.py +2 -0
  42. dashscope/audio/tts/speech_synthesizer.py +5 -0
  43. dashscope/audio/tts_v2/__init__.py +12 -0
  44. dashscope/audio/tts_v2/enrollment.py +179 -0
  45. dashscope/audio/tts_v2/speech_synthesizer.py +886 -0
  46. dashscope/cli.py +157 -37
  47. dashscope/client/base_api.py +652 -87
  48. dashscope/common/api_key.py +2 -0
  49. dashscope/common/base_type.py +135 -0
  50. dashscope/common/constants.py +13 -16
  51. dashscope/common/env.py +2 -0
  52. dashscope/common/error.py +58 -22
  53. dashscope/common/logging.py +2 -0
  54. dashscope/common/message_manager.py +2 -0
  55. dashscope/common/utils.py +276 -46
  56. dashscope/customize/__init__.py +0 -0
  57. dashscope/customize/customize_types.py +192 -0
  58. dashscope/customize/deployments.py +146 -0
  59. dashscope/customize/finetunes.py +234 -0
  60. dashscope/embeddings/__init__.py +5 -1
  61. dashscope/embeddings/batch_text_embedding.py +208 -0
  62. dashscope/embeddings/batch_text_embedding_response.py +65 -0
  63. dashscope/embeddings/multimodal_embedding.py +118 -10
  64. dashscope/embeddings/text_embedding.py +13 -1
  65. dashscope/{file.py → files.py} +19 -4
  66. dashscope/io/input_output.py +2 -0
  67. dashscope/model.py +11 -2
  68. dashscope/models.py +43 -0
  69. dashscope/multimodal/__init__.py +20 -0
  70. dashscope/multimodal/dialog_state.py +56 -0
  71. dashscope/multimodal/multimodal_constants.py +28 -0
  72. dashscope/multimodal/multimodal_dialog.py +648 -0
  73. dashscope/multimodal/multimodal_request_params.py +313 -0
  74. dashscope/multimodal/tingwu/__init__.py +10 -0
  75. dashscope/multimodal/tingwu/tingwu.py +80 -0
  76. dashscope/multimodal/tingwu/tingwu_realtime.py +579 -0
  77. dashscope/nlp/__init__.py +0 -0
  78. dashscope/nlp/understanding.py +64 -0
  79. dashscope/protocol/websocket.py +3 -0
  80. dashscope/rerank/__init__.py +0 -0
  81. dashscope/rerank/text_rerank.py +69 -0
  82. dashscope/resources/qwen.tiktoken +151643 -0
  83. dashscope/threads/__init__.py +26 -0
  84. dashscope/threads/messages/__init__.py +0 -0
  85. dashscope/threads/messages/files.py +113 -0
  86. dashscope/threads/messages/messages.py +220 -0
  87. dashscope/threads/runs/__init__.py +0 -0
  88. dashscope/threads/runs/runs.py +501 -0
  89. dashscope/threads/runs/steps.py +112 -0
  90. dashscope/threads/thread_types.py +665 -0
  91. dashscope/threads/threads.py +212 -0
  92. dashscope/tokenizers/__init__.py +7 -0
  93. dashscope/tokenizers/qwen_tokenizer.py +111 -0
  94. dashscope/tokenizers/tokenization.py +125 -0
  95. dashscope/tokenizers/tokenizer.py +45 -0
  96. dashscope/tokenizers/tokenizer_base.py +32 -0
  97. dashscope/utils/__init__.py +0 -0
  98. dashscope/utils/message_utils.py +838 -0
  99. dashscope/utils/oss_utils.py +243 -0
  100. dashscope/utils/param_utils.py +29 -0
  101. dashscope/version.py +3 -1
  102. {dashscope-1.8.0.dist-info → dashscope-1.25.6.dist-info}/METADATA +53 -50
  103. dashscope-1.25.6.dist-info/RECORD +112 -0
  104. {dashscope-1.8.0.dist-info → dashscope-1.25.6.dist-info}/WHEEL +1 -1
  105. {dashscope-1.8.0.dist-info → dashscope-1.25.6.dist-info}/entry_points.txt +0 -1
  106. {dashscope-1.8.0.dist-info → dashscope-1.25.6.dist-info/licenses}/LICENSE +2 -4
  107. dashscope/deployment.py +0 -129
  108. dashscope/finetune.py +0 -149
  109. dashscope-1.8.0.dist-info/RECORD +0 -49
  110. {dashscope-1.8.0.dist-info → dashscope-1.25.6.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,5 @@
1
+ # Copyright (c) Alibaba, Inc. and its affiliates.
2
+
1
3
  import json
2
4
  from dataclasses import dataclass
3
5
  from http import HTTPStatus
@@ -110,13 +112,14 @@ class Role:
110
112
  SYSTEM = 'system'
111
113
  BOT = 'bot'
112
114
  ASSISTANT = 'assistant'
115
+ ATTACHMENT = 'attachment'
113
116
 
114
117
 
115
118
  class Message(DictMixin):
116
119
  role: str
117
120
  content: Union[str, List]
118
121
 
119
- def __init__(self, role: str, content: str, **kwargs):
122
+ def __init__(self, role: str, content: str = None, **kwargs):
120
123
  super().__init__(role=role, content=content, **kwargs)
121
124
 
122
125
  @classmethod
@@ -136,12 +139,37 @@ class Message(DictMixin):
136
139
  class Choice(DictMixin):
137
140
  finish_reason: str
138
141
  message: Message
139
- def __init__(self, finish_reason: str = None, message: Message = None, **kwargs):
142
+
143
+ def __init__(self,
144
+ finish_reason: str = None,
145
+ message: Message = None,
146
+ **kwargs):
140
147
  msgObject = None
141
- if message is not None:
148
+ if message is not None and message:
142
149
  msgObject = Message(**message)
143
- super().__init__(finish_reason=finish_reason,
144
- message=msgObject, **kwargs)
150
+ super().__init__(finish_reason=finish_reason,
151
+ message=msgObject,
152
+ **kwargs)
153
+
154
+
155
+ @dataclass(init=False)
156
+ class Audio(DictMixin):
157
+ data: str
158
+ url: str
159
+ id: str
160
+ expires_at: int
161
+
162
+ def __init__(self,
163
+ data: str = None,
164
+ url: str = None,
165
+ id: str = None,
166
+ expires_at: int = None,
167
+ **kwargs):
168
+ super().__init__(data=data,
169
+ url=url,
170
+ id=id,
171
+ expires_at=expires_at,
172
+ **kwargs)
145
173
 
146
174
 
147
175
  @dataclass(init=False)
@@ -159,7 +187,7 @@ class GenerationOutput(DictMixin):
159
187
  if choices is not None:
160
188
  chs = []
161
189
  for choice in choices:
162
- chs.append(Choice(**choice))
190
+ chs.append(Choice(**choice))
163
191
  super().__init__(text=text,
164
192
  finish_reason=finish_reason,
165
193
  choices=chs,
@@ -205,23 +233,29 @@ class GenerationResponse(DashScopeAPIResponse):
205
233
  code=api_response.code,
206
234
  message=api_response.message)
207
235
 
236
+
208
237
  @dataclass(init=False)
209
238
  class MultiModalConversationOutput(DictMixin):
210
239
  choices: List[Choice]
240
+ audio: Audio
211
241
 
212
242
  def __init__(self,
213
243
  text: str = None,
214
244
  finish_reason: str = None,
215
245
  choices: List[Choice] = None,
246
+ audio: Audio = None,
216
247
  **kwargs):
217
248
  chs = None
218
249
  if choices is not None:
219
250
  chs = []
220
251
  for choice in choices:
221
- chs.append(Choice(**choice))
252
+ chs.append(Choice(**choice))
253
+ if audio is not None:
254
+ audio = Audio(**audio)
222
255
  super().__init__(text=text,
223
256
  finish_reason=finish_reason,
224
257
  choices=chs,
258
+ audio=audio,
225
259
  **kwargs)
226
260
 
227
261
 
@@ -229,14 +263,18 @@ class MultiModalConversationOutput(DictMixin):
229
263
  class MultiModalConversationUsage(DictMixin):
230
264
  input_tokens: int
231
265
  output_tokens: int
266
+ characters: int
267
+
232
268
  # TODO add image usage info.
233
269
 
234
270
  def __init__(self,
235
271
  input_tokens: int = 0,
236
272
  output_tokens: int = 0,
273
+ characters: int = 0,
237
274
  **kwargs):
238
275
  super().__init__(input_tokens=input_tokens,
239
276
  output_tokens=output_tokens,
277
+ characters=characters,
240
278
  **kwargs)
241
279
 
242
280
 
@@ -260,10 +298,12 @@ class MultiModalConversationResponse(DashScopeAPIResponse):
260
298
  output=MultiModalConversationOutput(**api_response.output),
261
299
  usage=MultiModalConversationUsage(**usage))
262
300
  else:
263
- return MultiModalConversationResponse(status_code=api_response.status_code,
264
- request_id=api_response.request_id,
265
- code=api_response.code,
266
- message=api_response.message)
301
+ return MultiModalConversationResponse(
302
+ status_code=api_response.status_code,
303
+ request_id=api_response.request_id,
304
+ code=api_response.code,
305
+ message=api_response.message)
306
+
267
307
 
268
308
  @dataclass(init=False)
269
309
  class ConversationResponse(GenerationResponse):
@@ -366,7 +406,7 @@ class RecognitionResponse(DashScopeAPIResponse):
366
406
  """
367
407
  result = False
368
408
  if sentence is not None and 'end_time' in sentence and sentence[
369
- 'end_time'] is not None:
409
+ 'end_time'] is not None:
370
410
  result = True
371
411
  return result
372
412
 
@@ -433,8 +473,8 @@ class ImageSynthesisOutput(DictMixin):
433
473
  results: List[ImageSynthesisResult]
434
474
 
435
475
  def __init__(self,
436
- task_id: str,
437
- task_status: str,
476
+ task_id: str = None,
477
+ task_status: str = None,
438
478
  results: List[ImageSynthesisResult] = [],
439
479
  **kwargs):
440
480
  res = []
@@ -448,14 +488,49 @@ class ImageSynthesisOutput(DictMixin):
448
488
  **kwargs)
449
489
 
450
490
 
491
+ @dataclass(init=False)
492
+ class VideoSynthesisOutput(DictMixin):
493
+ task_id: str
494
+ task_status: str
495
+ video_url: str
496
+
497
+ def __init__(self,
498
+ task_id: str,
499
+ task_status: str,
500
+ video_url: str = '',
501
+ **kwargs):
502
+ super().__init__(self,
503
+ task_id=task_id,
504
+ task_status=task_status,
505
+ video_url=video_url,
506
+ **kwargs)
507
+
508
+
451
509
  @dataclass(init=False)
452
510
  class ImageSynthesisUsage(DictMixin):
453
511
  image_count: int
454
512
 
455
- def __init__(self, image_count: int, **kwargs):
513
+ def __init__(self, image_count: int = None, **kwargs):
456
514
  super().__init__(image_count=image_count, **kwargs)
457
515
 
458
516
 
517
+ @dataclass(init=False)
518
+ class VideoSynthesisUsage(DictMixin):
519
+ video_count: int
520
+ video_duration: int
521
+ video_ratio: str
522
+
523
+ def __init__(self,
524
+ video_count: int = 1,
525
+ video_duration: int = 0,
526
+ video_ratio: str = '',
527
+ **kwargs):
528
+ super().__init__(video_count=video_count,
529
+ video_duration=video_duration,
530
+ video_ratio=video_ratio,
531
+ **kwargs)
532
+
533
+
459
534
  @dataclass(init=False)
460
535
  class ImageSynthesisResponse(DashScopeAPIResponse):
461
536
  output: ImageSynthesisOutput
@@ -483,3 +558,156 @@ class ImageSynthesisResponse(DashScopeAPIResponse):
483
558
  request_id=api_response.request_id,
484
559
  code=api_response.code,
485
560
  message=api_response.message)
561
+
562
+
563
+ @dataclass(init=False)
564
+ class VideoSynthesisResponse(DashScopeAPIResponse):
565
+ output: VideoSynthesisOutput
566
+ usage: VideoSynthesisUsage
567
+
568
+ @staticmethod
569
+ def from_api_response(api_response: DashScopeAPIResponse):
570
+ if api_response.status_code == HTTPStatus.OK:
571
+ output = None
572
+ usage = None
573
+ if api_response.output is not None:
574
+ output = VideoSynthesisOutput(**api_response.output)
575
+ if api_response.usage is not None:
576
+ usage = VideoSynthesisUsage(**api_response.usage)
577
+
578
+ return VideoSynthesisResponse(status_code=api_response.status_code,
579
+ request_id=api_response.request_id,
580
+ code=api_response.code,
581
+ message=api_response.message,
582
+ output=output,
583
+ usage=usage)
584
+
585
+ else:
586
+ return VideoSynthesisResponse(status_code=api_response.status_code,
587
+ request_id=api_response.request_id,
588
+ code=api_response.code,
589
+ message=api_response.message)
590
+
591
+
592
+ @dataclass(init=False)
593
+ class ReRankResult(DictMixin):
594
+ index: int
595
+ relevance_score: float
596
+ document: Dict = None
597
+
598
+ def __init__(self,
599
+ index: int,
600
+ relevance_score: float,
601
+ document: Dict = None,
602
+ **kwargs):
603
+ super().__init__(index=index,
604
+ relevance_score=relevance_score,
605
+ document=document,
606
+ **kwargs)
607
+
608
+
609
+ @dataclass(init=False)
610
+ class ReRankOutput(DictMixin):
611
+ results: List[ReRankResult]
612
+
613
+ def __init__(self, results: List[ReRankResult] = None, **kwargs):
614
+ ress = None
615
+ if results is not None:
616
+ ress = []
617
+ for res in results:
618
+ ress.append(ReRankResult(**res))
619
+ super().__init__(results=ress, **kwargs)
620
+
621
+
622
+ @dataclass(init=False)
623
+ class ReRankUsage(DictMixin):
624
+ total_tokens: int
625
+
626
+ def __init__(self, total_tokens=None, **kwargs):
627
+ super().__init__(total_tokens=total_tokens, **kwargs)
628
+
629
+
630
+ @dataclass(init=False)
631
+ class ReRankResponse(DashScopeAPIResponse):
632
+ output: ReRankOutput
633
+ usage: GenerationUsage
634
+
635
+ @staticmethod
636
+ def from_api_response(api_response: DashScopeAPIResponse):
637
+ if api_response.status_code == HTTPStatus.OK:
638
+ usage = {}
639
+ if api_response.usage:
640
+ usage = api_response.usage
641
+
642
+ return ReRankResponse(status_code=api_response.status_code,
643
+ request_id=api_response.request_id,
644
+ code=api_response.code,
645
+ message=api_response.message,
646
+ output=ReRankOutput(**api_response.output),
647
+ usage=ReRankUsage(**usage))
648
+ else:
649
+ return ReRankResponse(status_code=api_response.status_code,
650
+ request_id=api_response.request_id,
651
+ code=api_response.code,
652
+ message=api_response.message)
653
+
654
+
655
+ @dataclass(init=False)
656
+ class TextToSpeechAudio(DictMixin):
657
+ expires_at: int
658
+ id: str
659
+ data: str
660
+ url: str
661
+
662
+ def __init__(self,
663
+ expires_at: int,
664
+ id: str,
665
+ data: str = None,
666
+ url: str = None,
667
+ **kwargs):
668
+ super().__init__(expires_at=expires_at,
669
+ id=id,
670
+ data=data,
671
+ url=url,
672
+ **kwargs)
673
+
674
+
675
+ @dataclass(init=False)
676
+ class TextToSpeechOutput(DictMixin):
677
+ finish_reason: str
678
+ audio: TextToSpeechAudio
679
+
680
+ def __init__(self,
681
+ finish_reason: str = None,
682
+ audio: TextToSpeechAudio = None,
683
+ **kwargs):
684
+ super().__init__(finish_reason=finish_reason,
685
+ audio=audio,
686
+ **kwargs)
687
+
688
+
689
+ @dataclass(init=False)
690
+ class TextToSpeechResponse(DashScopeAPIResponse):
691
+ output: TextToSpeechOutput
692
+ usage: MultiModalConversationUsage
693
+
694
+ @staticmethod
695
+ def from_api_response(api_response: DashScopeAPIResponse):
696
+ if api_response.status_code == HTTPStatus.OK:
697
+ usage = {}
698
+ if api_response.usage:
699
+ usage = api_response.usage
700
+
701
+ return MultiModalConversationResponse(
702
+ status_code=api_response.status_code,
703
+ request_id=api_response.request_id,
704
+ code=api_response.code,
705
+ message=api_response.message,
706
+ output=TextToSpeechOutput(**api_response.output),
707
+ usage=MultiModalConversationUsage(**usage))
708
+ else:
709
+ return TextToSpeechResponse(
710
+ status_code=api_response.status_code,
711
+ request_id=api_response.request_id,
712
+ code=api_response.code,
713
+ message=api_response.message)
@@ -0,0 +1,179 @@
1
+ # Copyright (c) Alibaba, Inc. and its affiliates.
2
+ import base64
3
+ import json
4
+ from dataclasses import dataclass
5
+ import os
6
+ from typing import Optional
7
+
8
+ import requests
9
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
10
+ from cryptography.hazmat.primitives import serialization, hashes
11
+ from cryptography.hazmat.primitives.asymmetric import padding
12
+ from cryptography.hazmat.backends import default_backend
13
+
14
+ import dashscope
15
+ from dashscope.common.constants import ENCRYPTION_AES_SECRET_KEY_BYTES, ENCRYPTION_AES_IV_LENGTH
16
+ from dashscope.common.logging import logger
17
+
18
+
19
+ class Encryption:
20
+ def __init__(self):
21
+ self.pub_key_id: str = ''
22
+ self.pub_key_str: str = ''
23
+ self.aes_key_bytes: bytes = b''
24
+ self.encrypted_aes_key_str: str = ''
25
+ self.iv_bytes: bytes = b''
26
+ self.base64_iv_str: str = ''
27
+ self.valid: bool = False
28
+
29
+ def initialize(self):
30
+ public_keys = self._get_public_keys()
31
+ if not public_keys:
32
+ return
33
+
34
+ public_key_str = public_keys.get('public_key')
35
+ public_key_id = public_keys.get('public_key_id')
36
+ if not public_key_str or not public_key_id:
37
+ logger.error("public keys data not valid")
38
+ return
39
+
40
+ aes_key_bytes = self._generate_aes_secret_key()
41
+ iv_bytes = self._generate_iv()
42
+
43
+ encrypted_aes_key_str = self._encrypt_aes_key_with_rsa(aes_key_bytes, public_key_str)
44
+ base64_iv_str = base64.b64encode(iv_bytes).decode('utf-8')
45
+
46
+ self.pub_key_id = public_key_id
47
+ self.pub_key_str = public_key_str
48
+ self.aes_key_bytes = aes_key_bytes
49
+ self.encrypted_aes_key_str = encrypted_aes_key_str
50
+ self.iv_bytes = iv_bytes
51
+ self.base64_iv_str = base64_iv_str
52
+
53
+ self.valid = True
54
+
55
+ def encrypt(self, dict_plaintext):
56
+ return self._encrypt_text_with_aes(json.dumps(dict_plaintext, ensure_ascii=False),
57
+ self.aes_key_bytes, self.iv_bytes)
58
+
59
+ def decrypt(self, base64_ciphertext):
60
+ return self._decrypt_text_with_aes(base64_ciphertext, self.aes_key_bytes, self.iv_bytes)
61
+
62
+ def is_valid(self):
63
+ return self.valid
64
+
65
+ def get_pub_key_id(self):
66
+ return self.pub_key_id
67
+
68
+ def get_encrypted_aes_key_str(self):
69
+ return self.encrypted_aes_key_str
70
+
71
+ def get_base64_iv_str(self):
72
+ return self.base64_iv_str
73
+
74
+ @staticmethod
75
+ def _get_public_keys():
76
+ url = dashscope.base_http_api_url + '/public-keys/latest'
77
+ headers = {
78
+ "Authorization": f"Bearer {dashscope.api_key}"
79
+ }
80
+
81
+ response = requests.get(url, headers=headers)
82
+ if response.status_code != 200:
83
+ logger.error("exceptional public key response: %s" % response)
84
+ return None
85
+
86
+ json_resp = response.json()
87
+ response_data = json_resp.get('data')
88
+
89
+ if not response_data:
90
+ logger.error("no valid data in public key response")
91
+ return None
92
+
93
+ return response_data
94
+
95
+ @staticmethod
96
+ def _generate_aes_secret_key():
97
+ return os.urandom(ENCRYPTION_AES_SECRET_KEY_BYTES)
98
+
99
+ @staticmethod
100
+ def _generate_iv():
101
+ return os.urandom(ENCRYPTION_AES_IV_LENGTH)
102
+
103
+ @staticmethod
104
+ def _encrypt_text_with_aes(plaintext, key, iv):
105
+ """使用AES-GCM加密数据"""
106
+
107
+ # 创建AES-GCM加密器
108
+ aes_gcm = Cipher(
109
+ algorithms.AES(key),
110
+ modes.GCM(iv, tag=None),
111
+ backend=default_backend()
112
+ ).encryptor()
113
+
114
+ # 关联数据设为空(根据需求可调整)
115
+ aes_gcm.authenticate_additional_data(b'')
116
+
117
+ # 加密数据
118
+ ciphertext = aes_gcm.update(plaintext.encode('utf-8')) + aes_gcm.finalize()
119
+
120
+ # 获取认证标签
121
+ tag = aes_gcm.tag
122
+
123
+ # 组合密文和标签
124
+ encrypted_data = ciphertext + tag
125
+
126
+ # 返回Base64编码结果
127
+ return base64.b64encode(encrypted_data).decode('utf-8')
128
+
129
+ @staticmethod
130
+ def _decrypt_text_with_aes(base64_ciphertext, aes_key, iv):
131
+ """使用AES-GCM解密响应"""
132
+
133
+ # 解码Base64数据
134
+ encrypted_data = base64.b64decode(base64_ciphertext)
135
+
136
+ # 分离密文和标签(标签长度16字节)
137
+ ciphertext = encrypted_data[:-16]
138
+ tag = encrypted_data[-16:]
139
+
140
+ # 创建AES-GCM解密器
141
+ aes_gcm = Cipher(
142
+ algorithms.AES(aes_key),
143
+ modes.GCM(iv, tag),
144
+ backend=default_backend()
145
+ ).decryptor()
146
+
147
+ # 验证关联数据(与加密时一致)
148
+ aes_gcm.authenticate_additional_data(b'')
149
+
150
+ # 解密数据
151
+ decrypted_bytes = aes_gcm.update(ciphertext) + aes_gcm.finalize()
152
+
153
+ # 明文
154
+ plaintext = decrypted_bytes.decode('utf-8')
155
+
156
+ return json.loads(plaintext)
157
+
158
+ @staticmethod
159
+ def _encrypt_aes_key_with_rsa(aes_key, public_key_str):
160
+ """使用RSA公钥加密AES密钥"""
161
+
162
+ # 解码Base64格式的公钥
163
+ public_key_bytes = base64.b64decode(public_key_str)
164
+
165
+ # 加载公钥
166
+ public_key = serialization.load_der_public_key(
167
+ public_key_bytes,
168
+ backend=default_backend()
169
+ )
170
+
171
+ base64_aes_key = base64.b64encode(aes_key).decode('utf-8')
172
+
173
+ # 使用RSA加密
174
+ encrypted_bytes = public_key.encrypt(
175
+ base64_aes_key.encode('utf-8'),
176
+ padding.PKCS1v15()
177
+ )
178
+
179
+ return base64.b64encode(encrypted_bytes).decode('utf-8')