tamar-model-client 0.1.27__py3-none-any.whl → 0.1.30__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.
@@ -1,21 +1,21 @@
1
1
  tamar_model_client/__init__.py,sha256=4DEIUGlLTeiaECjJQbGYik7C0JO6hHwwfbLYpYpMdzg,444
2
- tamar_model_client/async_client.py,sha256=FYZdo7ltypC4IEq2eUevcxxjTqoKXjZaCv_jpKle7tA,42788
2
+ tamar_model_client/async_client.py,sha256=J3787otYGW2ycizLC5HDPibm_USNy4oKI5QQUo3L-aE,45328
3
3
  tamar_model_client/auth.py,sha256=gbwW5Aakeb49PMbmYvrYlVx1mfyn1LEDJ4qQVs-9DA4,438
4
- tamar_model_client/circuit_breaker.py,sha256=0XHJXBYA4O8vwsDGwqNrae9zxNJphY5Rfucc9ytVFGA,5419
5
- tamar_model_client/error_handler.py,sha256=oI_jUTjnq4OXu8fwJoGXNmQpddEgOFF9ZUhbytq7H6c,12384
4
+ tamar_model_client/circuit_breaker.py,sha256=Y3AVp7WzVYU-ubcmovKsJ8DRJbbO4G7vdZgSjnwcWJQ,5550
5
+ tamar_model_client/error_handler.py,sha256=y7EipcqkXbCecSAOsnoSP3SH7hvZSNF_NUHooTi3hP0,18364
6
6
  tamar_model_client/exceptions.py,sha256=EOr4JMYI7hVszRvNYJ1JqsUNpVmd16T2KpJ0MkFTsUE,13073
7
- tamar_model_client/json_formatter.py,sha256=IyBv_pEEzjF-KaMF-7rxRpNc_fxRYK2A-pu_2n4Liow,1990
7
+ tamar_model_client/json_formatter.py,sha256=XT8XPMKKM2M22tuYR2e1rvWHcpz3UD9iLLgGPsGOjCI,2410
8
8
  tamar_model_client/logging_icons.py,sha256=MRTZ1Xvkep9ce_jdltj54_XZUXvIpQ95soRNmLdJ4qw,1837
9
- tamar_model_client/sync_client.py,sha256=BRra6GyTwVA4Sm8tjYnwkJg-iJZ_y1fbXuk0-EsJvGo,44396
9
+ tamar_model_client/sync_client.py,sha256=vBxVvDFeY_Sd7JRLJwkdOcm6sCxmGaDW0tyCspp-n7E,52671
10
10
  tamar_model_client/utils.py,sha256=Kn6pFz9GEC96H4eejEax66AkzvsrXI3WCSDtgDjnVTI,5238
11
- tamar_model_client/core/__init__.py,sha256=rHwVOs4h4eHKy6FCgIv9PJKYKUZHyPjPkdYbbxraLNc,938
12
- tamar_model_client/core/base_client.py,sha256=nSQTvW8oz4wLpBKArgiNjuzpEyv0X5jALwrNmkcUzTw,9267
13
- tamar_model_client/core/http_fallback.py,sha256=1OuSMxzhDyxy07JZa5artMTNdPNMyAhI7By3RUCSPDw,9872
14
- tamar_model_client/core/logging_setup.py,sha256=h1aky1uslIQnx4NxMqjoDMxwlc4Vg46KYTjW9yPu2xQ,6032
11
+ tamar_model_client/core/__init__.py,sha256=RMiZjV1S4csWPLxB_JfdOea8fYPz97Oj3humQSBw1OI,1054
12
+ tamar_model_client/core/base_client.py,sha256=XUbMDM6B3ZtAnAdgSDF-bdLgACY46igCcMVoiMC3faQ,13056
13
+ tamar_model_client/core/http_fallback.py,sha256=ULmHXfKPwP4T32xo7yQV_z2bGaI_L71BQIcylFs8dTM,21243
14
+ tamar_model_client/core/logging_setup.py,sha256=-MXzTR4Ax50H16cbq1jCXbxgayf5fZ0U3o0--fMmxD8,6692
15
15
  tamar_model_client/core/request_builder.py,sha256=yi8iy2Ps2m4d1YwIFiQLRxTvxQxgEGV576aXnNYRl7E,8507
16
16
  tamar_model_client/core/request_id_manager.py,sha256=S-Mliaby9zN_bx-B85FvVnttal-w0skkjy2ZvWoQ5vw,3689
17
17
  tamar_model_client/core/response_handler.py,sha256=_q5galAT0_RaUT5C_yZsjg-9VnT9CBjmIASOt28BUmQ,4616
18
- tamar_model_client/core/utils.py,sha256=8jSx8UOE6ukbiIgruCX7SXN8J5FyuGbqENOmJDsxaSM,5084
18
+ tamar_model_client/core/utils.py,sha256=AcbsGfNQEaZLYI4OZJs-BdmJgxAoLUC5LFoiYmji820,5875
19
19
  tamar_model_client/enums/__init__.py,sha256=3cYYn8ztNGBa_pI_5JGRVYf2QX8fkBVWdjID1PLvoBQ,182
20
20
  tamar_model_client/enums/channel.py,sha256=wCzX579nNpTtwzGeS6S3Ls0UzVAgsOlfy4fXMzQTCAw,199
21
21
  tamar_model_client/enums/invoke.py,sha256=Up87myAg4-0SDJV5a82ggPDpYHSLEtIco8BF_5Ph1nY,322
@@ -28,10 +28,11 @@ tamar_model_client/schemas/inputs.py,sha256=dz1m8NbUIxA99JXZc8WlyzbKpDuz1lEzx3Vg
28
28
  tamar_model_client/schemas/outputs.py,sha256=M_fcqUtXPJnfiLabHlyA8BorlC5pYkf5KLjXO1ysKIQ,1031
29
29
  tests/__init__.py,sha256=kbmImddLDwdqlkkmkyKtl4bQy_ipe-R8eskpaBylU9w,38
30
30
  tests/stream_hanging_analysis.py,sha256=W3W48IhQbNAR6-xvMpoWZvnWOnr56CTaH4-aORNBuD4,14807
31
- tests/test_google_azure_final.py,sha256=NozBjNvbPm5vRw_GdEGMcdB4L-xtuMI3VQHObaccVr8,26425
31
+ tests/test_circuit_breaker.py,sha256=nhEBnyXFjIYjRWlUdu7Z9PnPq48ypbBK6fxN6deHedw,12172
32
+ tests/test_google_azure_final.py,sha256=Cx2lfnoj48_7pUjpCYbrx6OLJF4cI79McV24_EYt_8s,55093
32
33
  tests/test_logging_issue.py,sha256=JTMbotfHpAEPMBj73pOwxPn-Zn4QVQJX6scMz48FRDQ,2427
33
34
  tests/test_simple.py,sha256=Xf0U-J9_xn_LzUsmYu06suK0_7DrPeko8OHoHldsNxE,7169
34
- tamar_model_client-0.1.27.dist-info/METADATA,sha256=YsBltIhH5QAfn43-DpMOQm4KnsWpUtdX6Jbg0UU1NVM,23453
35
- tamar_model_client-0.1.27.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
36
- tamar_model_client-0.1.27.dist-info/top_level.txt,sha256=f1I-S8iWN-cgv4gB8gxRg9jJOTJMumvm4oGKVPfGg6A,25
37
- tamar_model_client-0.1.27.dist-info/RECORD,,
35
+ tamar_model_client-0.1.30.dist-info/METADATA,sha256=QrJLOVUNNXKGNv4ZJRPKDFLP9d9JUlfrqQNN-FQuAvA,41310
36
+ tamar_model_client-0.1.30.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
+ tamar_model_client-0.1.30.dist-info/top_level.txt,sha256=f1I-S8iWN-cgv4gB8gxRg9jJOTJMumvm4oGKVPfGg6A,25
38
+ tamar_model_client-0.1.30.dist-info/RECORD,,
@@ -0,0 +1,269 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ 熔断器测试脚本
4
+ 专门用于测试熔断器和HTTP fallback功能
5
+ """
6
+
7
+ import asyncio
8
+ import logging
9
+ import os
10
+ import sys
11
+ import time
12
+ from typing import List, Dict, Tuple
13
+
14
+ # 配置测试脚本专用的日志
15
+ test_logger = logging.getLogger('test_circuit_breaker')
16
+ test_logger.setLevel(logging.INFO)
17
+ test_logger.propagate = False
18
+
19
+ # 创建测试脚本专用的handler
20
+ test_handler = logging.StreamHandler()
21
+ test_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
22
+ test_logger.addHandler(test_handler)
23
+
24
+ logger = test_logger
25
+
26
+ # 导入客户端模块
27
+ try:
28
+ from tamar_model_client import AsyncTamarModelClient
29
+ from tamar_model_client.schemas import ModelRequest, UserContext
30
+ from tamar_model_client.enums import ProviderType, InvokeType
31
+ except ImportError as e:
32
+ logger.error(f"导入模块失败: {e}")
33
+ sys.exit(1)
34
+
35
+
36
+ async def test_circuit_breaker_with_single_requests(num_requests: int = 10):
37
+ """
38
+ 测试熔断器功能 - 使用单个请求触发熔断
39
+
40
+ Args:
41
+ num_requests: 要发送的请求数,默认10个
42
+ """
43
+ print(f"\n🔥 测试熔断器功能 - 单请求模式 ({num_requests} 个请求)...")
44
+
45
+ # 保存原始环境变量
46
+ original_env = {}
47
+ env_vars = ['MODEL_CLIENT_RESILIENT_ENABLED', 'MODEL_CLIENT_HTTP_FALLBACK_URL',
48
+ 'MODEL_CLIENT_CIRCUIT_BREAKER_THRESHOLD', 'MODEL_CLIENT_CIRCUIT_BREAKER_TIMEOUT',
49
+ 'MODEL_MANAGER_SERVER_ADDRESS', 'MODEL_MANAGER_SERVER_GRPC_USE_TLS']
50
+ for var in env_vars:
51
+ original_env[var] = os.environ.get(var)
52
+
53
+ # 设置环境变量以启用熔断器和HTTP fallback
54
+ os.environ['MODEL_CLIENT_RESILIENT_ENABLED'] = 'true'
55
+ os.environ['MODEL_CLIENT_HTTP_FALLBACK_URL'] = 'http://localhost:8000' # 假设HTTP服务在8000端口
56
+ os.environ['MODEL_CLIENT_CIRCUIT_BREAKER_THRESHOLD'] = '3' # 3次失败后触发熔断
57
+ os.environ['MODEL_CLIENT_CIRCUIT_BREAKER_TIMEOUT'] = '30' # 熔断器30秒后恢复
58
+
59
+ # 使用一个不存在的服务器地址来触发连接错误
60
+ os.environ['MODEL_MANAGER_SERVER_ADDRESS'] = 'localhost:99999' # 无效端口
61
+ os.environ['MODEL_MANAGER_SERVER_GRPC_USE_TLS'] = 'false'
62
+
63
+ # 调试:打印环境变量确认设置成功
64
+ print(f" 环境变量设置:")
65
+ print(f" - MODEL_CLIENT_RESILIENT_ENABLED: {os.environ.get('MODEL_CLIENT_RESILIENT_ENABLED')}")
66
+ print(f" - MODEL_CLIENT_HTTP_FALLBACK_URL: {os.environ.get('MODEL_CLIENT_HTTP_FALLBACK_URL')}")
67
+ print(f" - 熔断阈值: {os.environ.get('MODEL_CLIENT_CIRCUIT_BREAKER_THRESHOLD')} 次失败")
68
+ print(f" - 熔断恢复时间: {os.environ.get('MODEL_CLIENT_CIRCUIT_BREAKER_TIMEOUT')} 秒")
69
+ print(f" - gRPC服务器: {os.environ.get('MODEL_MANAGER_SERVER_ADDRESS')} (故意使用无效地址)")
70
+
71
+ # 统计变量
72
+ total_requests = 0
73
+ successful_requests = 0
74
+ failed_requests = 0
75
+ http_fallback_requests = 0
76
+ circuit_breaker_opened = False
77
+ request_times: List[float] = []
78
+ errors: Dict[str, int] = {}
79
+
80
+ try:
81
+ # 创建一个共享的异步客户端(启用熔断器)
82
+ async with AsyncTamarModelClient() as client:
83
+ print(f"\n 熔断器初始配置:")
84
+ print(f" - 启用状态: {getattr(client, 'resilient_enabled', False)}")
85
+ print(f" - HTTP Fallback URL: {getattr(client, 'http_fallback_url', 'None')}")
86
+
87
+ # 获取初始熔断器状态
88
+ if hasattr(client, 'resilient_enabled') and client.resilient_enabled:
89
+ try:
90
+ metrics = client.get_resilient_metrics()
91
+ if metrics and 'circuit_breaker' in metrics:
92
+ print(f" - 初始状态: {metrics['circuit_breaker']['state']}")
93
+ print(f" - 失败阈值: {metrics['circuit_breaker']['failure_threshold']}")
94
+ print(f" - 恢复超时: {metrics['circuit_breaker']['recovery_timeout']}秒")
95
+ except Exception as e:
96
+ print(f" - 获取初始状态失败: {e}")
97
+
98
+ print(f"\n 开始发送请求...")
99
+
100
+ for i in range(num_requests):
101
+ start_time = time.time()
102
+ request_num = i + 1
103
+
104
+ try:
105
+ # 构建请求
106
+ request = ModelRequest(
107
+ provider=ProviderType.GOOGLE,
108
+ invoke_type=InvokeType.GENERATION,
109
+ model="tamar-google-gemini-flash-lite",
110
+ contents=f"测试请求 {request_num}: 1+1等于几?",
111
+ user_context=UserContext(
112
+ user_id=f"circuit_test_user_{i}",
113
+ org_id="circuit_test_org",
114
+ client_type="circuit_breaker_test"
115
+ ),
116
+ config={"temperature": 0.1, "maxOutputTokens": 10}
117
+ )
118
+
119
+ print(f"\n 📤 请求 {request_num}/{num_requests}...")
120
+
121
+ # 发送请求
122
+ response = await client.invoke(
123
+ request,
124
+ timeout=5000, # 5秒超时
125
+ request_id=f"circuit_test_{i}"
126
+ )
127
+
128
+ duration = time.time() - start_time
129
+ request_times.append(duration)
130
+ total_requests += 1
131
+ successful_requests += 1
132
+
133
+ # 如果成功了,检查是否是通过HTTP fallback
134
+ if hasattr(client, 'resilient_enabled') and client.resilient_enabled:
135
+ metrics = client.get_resilient_metrics()
136
+ if metrics and metrics['circuit_breaker']['state'] == 'open':
137
+ http_fallback_requests += 1
138
+ print(f" ✅ 请求 {request_num} 成功 (通过HTTP fallback) - 耗时: {duration:.2f}秒")
139
+ else:
140
+ print(f" ✅ 请求 {request_num} 成功 (gRPC) - 耗时: {duration:.2f}秒")
141
+ else:
142
+ print(f" ✅ 请求 {request_num} 成功 - 耗时: {duration:.2f}秒")
143
+
144
+ # 打印响应内容的前100个字符
145
+ if response.content:
146
+ print(f" 响应: {response.content[:100]}...")
147
+
148
+ except Exception as e:
149
+ duration = time.time() - start_time
150
+ request_times.append(duration)
151
+ total_requests += 1
152
+ failed_requests += 1
153
+
154
+ error_type = type(e).__name__
155
+ error_msg = str(e)[:100]
156
+ errors[error_type] = errors.get(error_type, 0) + 1
157
+
158
+ print(f" ❌ 请求 {request_num} 失败 - {error_type}: {error_msg}")
159
+ print(f" 耗时: {duration:.2f}秒")
160
+
161
+ # 检查熔断器状态
162
+ if hasattr(client, 'resilient_enabled') and client.resilient_enabled:
163
+ try:
164
+ metrics = client.get_resilient_metrics()
165
+ if metrics and 'circuit_breaker' in metrics:
166
+ cb_state = metrics['circuit_breaker']['state']
167
+ cb_failures = metrics['circuit_breaker']['failure_count']
168
+
169
+ print(f" 熔断器状态: {cb_state}, 失败计数: {cb_failures}")
170
+
171
+ if cb_state == 'open' and not circuit_breaker_opened:
172
+ circuit_breaker_opened = True
173
+ print(f" 🔻 熔断器已打开!后续请求将使用HTTP fallback")
174
+ except Exception as e:
175
+ print(f" 获取熔断器状态失败: {e}")
176
+
177
+ # 请求之间短暂等待
178
+ if i < num_requests - 1:
179
+ await asyncio.sleep(0.5)
180
+
181
+ # 最终统计
182
+ print(f"\n📊 熔断器测试结果:")
183
+ print(f" 总请求数: {total_requests}")
184
+ print(f" 成功请求: {successful_requests} ({successful_requests / total_requests * 100:.1f}%)")
185
+ print(f" 失败请求: {failed_requests} ({failed_requests / total_requests * 100:.1f}%)")
186
+
187
+ if request_times:
188
+ avg_time = sum(request_times) / len(request_times)
189
+ print(f"\n 请求耗时统计:")
190
+ print(f" - 平均: {avg_time:.3f} 秒")
191
+ print(f" - 最小: {min(request_times):.3f} 秒")
192
+ print(f" - 最大: {max(request_times):.3f} 秒")
193
+
194
+ print(f"\n 🔥 熔断器统计:")
195
+ print(f" - 熔断器是否触发: {'是' if circuit_breaker_opened else '否'}")
196
+ print(f" - HTTP fallback请求数: {http_fallback_requests}")
197
+
198
+ # 获取最终的熔断器状态
199
+ if hasattr(client, 'resilient_enabled') and client.resilient_enabled:
200
+ try:
201
+ final_metrics = client.get_resilient_metrics()
202
+ if final_metrics and 'circuit_breaker' in final_metrics:
203
+ print(f" - 最终状态: {final_metrics['circuit_breaker']['state']}")
204
+ print(f" - 总失败次数: {final_metrics['circuit_breaker']['failure_count']}")
205
+ print(f" - 失败阈值: {final_metrics['circuit_breaker']['failure_threshold']}")
206
+ print(f" - 恢复超时: {final_metrics['circuit_breaker']['recovery_timeout']}秒")
207
+ except Exception as e:
208
+ print(f" - 获取最终状态失败: {e}")
209
+
210
+ if errors:
211
+ print(f"\n 错误类型统计:")
212
+ for error_type, count in sorted(errors.items(), key=lambda x: x[1], reverse=True):
213
+ print(f" - {error_type}: {count} 次")
214
+
215
+ except Exception as e:
216
+ print(f"\n❌ 测试失败: {str(e)}")
217
+ import traceback
218
+ traceback.print_exc()
219
+
220
+ finally:
221
+ # 恢复原始环境变量
222
+ print(f"\n 恢复环境变量...")
223
+ for var, value in original_env.items():
224
+ if value is None:
225
+ os.environ.pop(var, None)
226
+ else:
227
+ os.environ[var] = value
228
+
229
+
230
+ async def test_circuit_breaker_recovery():
231
+ """测试熔断器恢复功能"""
232
+ print(f"\n🔄 测试熔断器恢复功能...")
233
+
234
+ # 这里可以先触发熔断,然后恢复正常服务,观察熔断器是否能自动恢复
235
+ # 实现略...
236
+ pass
237
+
238
+
239
+ async def main():
240
+ """主函数"""
241
+ print("🚀 熔断器功能测试")
242
+ print("=" * 50)
243
+
244
+ try:
245
+ # 测试熔断器触发
246
+ await test_circuit_breaker_with_single_requests(10)
247
+
248
+ # 可选:测试熔断器恢复
249
+ # await test_circuit_breaker_recovery()
250
+
251
+ print("\n✅ 测试完成")
252
+
253
+ except KeyboardInterrupt:
254
+ print("\n⚠️ 用户中断测试")
255
+ except Exception as e:
256
+ print(f"\n❌ 测试执行出错: {e}")
257
+ import traceback
258
+ traceback.print_exc()
259
+
260
+
261
+ if __name__ == "__main__":
262
+ try:
263
+ asyncio.run(main())
264
+ except KeyboardInterrupt:
265
+ print("\n⚠️ 程序被用户中断")
266
+ except Exception as e:
267
+ print(f"\n❌ 程序执行出错: {e}")
268
+ finally:
269
+ print("🏁 程序已退出")