fiuai-sdk-python 0.4.2__tar.gz → 0.4.4__tar.gz

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 (26) hide show
  1. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/PKG-INFO +1 -1
  2. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/pyproject.toml +1 -1
  3. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/auth/header.py +3 -1
  4. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/auth/helper.py +18 -0
  5. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/auth/type.py +2 -0
  6. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/client.py +2 -0
  7. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/context.py +2 -2
  8. fiuai_sdk_python-0.4.4/src/fiuai_sdk_python/resp.py +241 -0
  9. fiuai_sdk_python-0.4.2/src/fiuai_sdk_python/resp.py +0 -198
  10. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/.gitignore +0 -0
  11. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/CHANGELOG.md +0 -0
  12. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/LICENSE +0 -0
  13. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/README.md +0 -0
  14. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/__init__.py +0 -0
  15. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/auth/__init__.py +0 -0
  16. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/bank.py +0 -0
  17. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/company.py +0 -0
  18. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/const.py +0 -0
  19. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/error.py +0 -0
  20. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/examples/fastapi_integration.py +0 -0
  21. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/item.py +0 -0
  22. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/perm.py +0 -0
  23. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/profile.py +0 -0
  24. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/setup.py +0 -0
  25. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/type.py +0 -0
  26. {fiuai_sdk_python-0.4.2 → fiuai_sdk_python-0.4.4}/src/fiuai_sdk_python/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fiuai_sdk_python
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: FiuAI Python SDK - 企业级AI服务集成开发工具包
5
5
  Project-URL: Homepage, https://github.com/fiuai/fiuai-sdk-python
6
6
  Project-URL: Documentation, https://github.com/fiuai/fiuai-sdk-python#readme
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fiuai_sdk_python"
3
- version = "0.4.2"
3
+ version = "0.4.4"
4
4
  description = "FiuAI Python SDK - 企业级AI服务集成开发工具包"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -31,6 +31,7 @@ def parse_auth_headers(headers: Dict[str, str]) -> Optional[AuthData]:
31
31
  impersonation = headers.get("x-fiuai-impersonation", "")
32
32
  unique_no = headers.get("x-fiuai-unique-no", "")
33
33
  trace_id = headers.get("x-fiuai-trace-id", "")
34
+ client = headers.get("x-fiuai-client", "unknown")
34
35
 
35
36
  # 验证必需字段
36
37
  if not user_id:
@@ -57,7 +58,8 @@ def parse_auth_headers(headers: Dict[str, str]) -> Optional[AuthData]:
57
58
  current_company=current_company[0],
58
59
  impersonation=impersonation,
59
60
  company_unique_no=unique_no,
60
- trace_id=trace_id
61
+ trace_id=trace_id,
62
+ client=client
61
63
  )
62
64
 
63
65
  except Exception as e:
@@ -143,6 +143,24 @@ def get_impersonation(
143
143
  return auth_data.impersonation
144
144
 
145
145
 
146
+ def get_current_client(
147
+ request: Union[Request, Dict[str, str]],
148
+ engine: Literal["fastapi", "dict"] = "fastapi"
149
+ ) -> str:
150
+ """
151
+ 获取当前客户端标识
152
+
153
+ Args:
154
+ request: FastAPI Request 对象或包含认证数据的字典
155
+ engine: 请求引擎类型,默认为 "fastapi"
156
+
157
+ Returns:
158
+ str: 客户端标识
159
+ """
160
+ auth_data = get_auth_data(request, engine)
161
+ return auth_data.client
162
+
163
+
146
164
  def is_impersonating(
147
165
  request: Union[Request, Dict[str, str]],
148
166
  engine: Literal["fastapi", "dict"] = "fastapi"
@@ -16,6 +16,7 @@ class AuthData(BaseModel):
16
16
  impersonation: str = Field(description="当前代表的租户ID", default="")
17
17
  company_unique_no: str = Field(description="当前公司唯一编号,正常情况等于current_company")
18
18
  trace_id: str = Field(description="追踪ID", default="")
19
+ client: str = Field(description="客户端标识", default="unknown")
19
20
 
20
21
 
21
22
 
@@ -30,6 +31,7 @@ class AuthHeader(BaseModel):
30
31
  x_fiuai_impersonation: str = Field(alias="x-fiuai-impersonation", default="", description="当前代表的租户ID")
31
32
  x_fiuai_lang: str = Field(alias="x-fiuai-lang", default="zh", description="用户当前语言")
32
33
  x_fiuai_trace_id: str = Field(alias="x-fiuai-trace-id", default="", description="追踪ID")
34
+ x_fiuai_client: str = Field(alias="x-fiuai-client", default="unknown", description="客户端标识")
33
35
  accept_language: str = Field(alias="accept-language", default="zh", description="语言")
34
36
 
35
37
  class Config:
@@ -94,6 +94,7 @@ class FiuaiSDK(object):
94
94
  "x-fiuai-impersonation": context_headers.get("x-fiuai-impersonation", self.headers.x_fiuai_impersonation),
95
95
  "x-fiuai-unique-no": company_unique_no_value,
96
96
  "x-fiuai-trace-id": context_headers.get("x-fiuai-trace-id", self.headers.x_fiuai_trace_id),
97
+ "x-fiuai-client": context_headers.get("x-fiuai-client", self.headers.x_fiuai_client),
97
98
  "x-fiuai-lang": context_headers.get("x-fiuai-lang", self.headers.x_fiuai_lang),
98
99
  "accept-language": context_headers.get("accept-language", self.headers.accept_language),
99
100
  })
@@ -112,6 +113,7 @@ class FiuaiSDK(object):
112
113
  "x-fiuai-impersonation": self.headers.x_fiuai_impersonation,
113
114
  "x-fiuai-unique-no": company_unique_no_value,
114
115
  "x-fiuai-trace-id": self.headers.x_fiuai_trace_id,
116
+ "x-fiuai-client": self.headers.x_fiuai_client,
115
117
  "x-fiuai-lang": self.headers.x_fiuai_lang,
116
118
  "accept-language": self.headers.accept_language,
117
119
  }
@@ -100,9 +100,9 @@ class RequestContext:
100
100
  # 生成新的 trace id
101
101
  processed_headers["x-fiuai-trace-id"] = str(uuid.uuid4())
102
102
 
103
- # 确保 X-Fiuai-Client 字段存在(如果不存在则设为空字符串)
103
+ # 确保 X-Fiuai-Client 字段存在(如果不存在则设为 "unknown")
104
104
  if "x-fiuai-client" not in processed_headers:
105
- processed_headers["x-fiuai-client"] = ""
105
+ processed_headers["x-fiuai-client"] = "unknown"
106
106
 
107
107
  return processed_headers
108
108
 
@@ -0,0 +1,241 @@
1
+ # -- coding: utf-8 --
2
+ # Project: fiuai_sdk_python
3
+ # Created Date: 2025 10 Mo
4
+ # Author: liming
5
+ # Email: lmlala@aliyun.com
6
+ # Copyright (c) 2025 FiuAI
7
+
8
+ import logging
9
+ from typing import List, Dict, Any, Optional, Union
10
+ from pydantic import BaseModel, Field
11
+
12
+ # 配置日志
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ # {'errors': [{'type': 'ValidationError', 'exception': 'Traceback (most recent call last):\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/fiuai_utils/auth.py", line 143, in wrapper\n r = func(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/fiuai_utils/api/docs/docs.py", line 163, in create_internal_doc\n r = frappe.new_doc(**d).save()\n ^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/model/document.py", line 416, in save\n return self._save(*args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/model/document.py", line 438, in _save\n return self.insert()\n ^^^^^^^^^^^^^\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/model/document.py", line 346, in insert\n self._validate()\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/model/document.py", line 660, in _validate\n self._validate_mandatory()\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/model/document.py", line 994, in _validate_mandatory\n raise frappe.MandatoryError(\nfrappe.exceptions.MandatoryError: [Item, 7384919604195962880]: stock_uom\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/app.py", line 131, in application\n response = frappe.api.handle(request)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/api/__init__.py", line 51, in handle\n data = endpoint(**arguments)\n ^^^^^^^^^^^^^^^^^^^^^\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/fiuai_utils/auth.py", line 147, in wrapper\n frappe.throw(title=_("INTERNAL_API_ERROR"),msg=_(f"{e}"))\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/__init__.py", line 651, in throw\n msgprint(\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/__init__.py", line 616, in msgprint\n _raise_exception()\n File "fiuai/frappe-fiuai/local/apps/frappe/frappe/__init__.py", line 567, in _raise_exception\n raise exc\nfrappe.exceptions.ValidationError: [Item, 7384919604195962880]: stock_uom\n', 'message': '[Item, 7384919604195962880]: stock_uom', 'title': 'INTERNAL_API_ERROR', 'indicator': 'red'}], 'messages': [{'message': '错误:商品缺少值:默认计量单位', 'title': '消息'}]}
17
+
18
+ class ApiResponse(BaseModel):
19
+ """API响应结构体"""
20
+ http_success: bool = Field(description="HTTP是否成功")
21
+ api_success: bool = Field(description="API业务是否成功")
22
+ status_code: int = Field(description="HTTP状态码")
23
+ data: Optional[Any] = Field(description="响应数据", default=None)
24
+ error_code: Optional[List[str]] = Field(description="错误码列表,对应error的title", default=None)
25
+ error_message: Optional[List[str]] = Field(description="错误消息列表,对应error的message", default=None)
26
+ messages: Optional[List[Any]] = Field(description="消息,对应messages字段", default=None)
27
+
28
+ def is_success(self) -> bool:
29
+ """判断是否完全成功"""
30
+ return self.http_success and self.api_success
31
+
32
+
33
+ def parse_response(response) -> ApiResponse:
34
+ """
35
+ 解析HTTP响应,返回结构化的API响应
36
+
37
+ Args:
38
+ response: httpx响应对象
39
+
40
+ Returns:
41
+ ApiResponse: 结构化的API响应
42
+ """
43
+ # 检查响应是否存在
44
+ if not response:
45
+ return ApiResponse(
46
+ http_success=False,
47
+ api_success=False,
48
+ status_code=504,
49
+ error_message=["Api no response"],
50
+ error_code=["API_NO_RESPONSE"],
51
+ )
52
+
53
+
54
+ # 解析JSON响应
55
+ try:
56
+ # 检查HTTP状态码
57
+ http_success = 200 <= response.status_code < 300
58
+ except Exception as e:
59
+ return ApiResponse(
60
+ http_success=False,
61
+ api_success=False,
62
+ status_code=response.status_code,
63
+ error_message=[f"Invalid JSON response: {e}"],
64
+ error_code=["API_INVALID_JSON"]
65
+ )
66
+
67
+ try:
68
+ response_data = response.json()
69
+ except Exception as e:
70
+ return ApiResponse(
71
+ http_success=True,
72
+ api_success=False,
73
+ status_code=response.status_code,
74
+ error_message=[f"Invalid JSON response: {e}"],
75
+ error_code=["API_INVALID_JSON"]
76
+ )
77
+
78
+ # 检查API业务是否成功
79
+ api_success = _is_api_success(response_data)
80
+
81
+ if api_success:
82
+ # 成功响应,提取数据
83
+ data = _extract_success_data(response_data)
84
+ return ApiResponse(
85
+ http_success=http_success,
86
+ api_success=True,
87
+ status_code=response.status_code,
88
+ error_message=None,
89
+ error_code=None,
90
+ data=data,
91
+ messages=None
92
+ )
93
+ else:
94
+ # 失败响应,提取错误信息
95
+ error_info = _extract_error_info(response_data)
96
+ return ApiResponse(
97
+ http_success=http_success,
98
+ api_success=False,
99
+ status_code=response.status_code,
100
+ error_message=error_info.get("messages", []),
101
+ error_code=error_info.get("codes", []),
102
+ data=None,
103
+ messages=None
104
+ )
105
+
106
+
107
+ def _is_api_success(response_data: Dict[str, Any]) -> bool:
108
+ """
109
+ 判断API业务是否成功
110
+
111
+ Args:
112
+ response_data: API响应数据
113
+
114
+ Returns:
115
+ bool: 是否成功
116
+ """
117
+ # 检查是否有错误
118
+ errors = response_data.get("errors", None)
119
+ if errors:
120
+ return False
121
+
122
+ # 检查是否有异常
123
+ exc = response_data.get("exc", None)
124
+ if exc:
125
+ return False
126
+
127
+ # 检查HTTP状态码
128
+ http_status_code = response_data.get("http_status_code", None)
129
+ if http_status_code is not None:
130
+ return 200 <= http_status_code < 300
131
+
132
+ # 默认认为有message或data字段就是成功
133
+ return response_data.get("message", None) is not None or response_data.get("data", None) is not None
134
+
135
+
136
+ def _extract_success_data(response_data: Dict[str, Any]) -> Any:
137
+ """
138
+ 从成功响应中提取数据
139
+ 优先从 data 字段获取,如果没有则从 messages 字段获取
140
+
141
+ Args:
142
+ response_data: API响应数据
143
+
144
+ Returns:
145
+ Any: 提取的数据,如果都没有则返回 None
146
+ """
147
+ # 优先从 data 字段获取
148
+ data = response_data.get("data", None)
149
+ if data is not None:
150
+ return data
151
+
152
+ # 如果 data 字段为空,尝试从 messages 字段获取
153
+ messages = response_data.get("messages", None)
154
+ if messages is not None:
155
+ return messages
156
+
157
+ # 都没有则返回 None
158
+ return None
159
+
160
+
161
+ def _extract_error_info(response_data: Dict[str, Any]) -> Dict[str, List[str]]:
162
+ """
163
+ 从响应中提取错误信息
164
+ 返回所有错误信息的列表
165
+
166
+ Args:
167
+ response_data: API响应数据
168
+
169
+ Returns:
170
+ Dict[str, List[str]]: {"messages": 错误消息列表, "codes": 错误码列表}
171
+ """
172
+ error_messages = []
173
+ error_codes = []
174
+
175
+ # 处理errors字段
176
+ errors = response_data.get("errors", None)
177
+ if errors and isinstance(errors, list) and errors:
178
+ for error in errors:
179
+ if isinstance(error, dict):
180
+ error_msg = error.get("message", "Unknown error")
181
+ error_title = error.get("title", "UnknownError")
182
+
183
+ error_messages.append(error_msg)
184
+ error_codes.append(error_title)
185
+
186
+ # 记录异常详情到日志
187
+ exception = error.get("exception", None)
188
+ if exception:
189
+ logger.error(f"API Error Exception: {error_title} - {exception}")
190
+
191
+ # 记录其他错误信息到日志
192
+ indicator = error.get("indicator", "")
193
+ if indicator:
194
+ logger.error(f"API Error Details: {error_title} - Indicator: {indicator}")
195
+
196
+ # 处理exc字段(V1 API格式)
197
+ exc = response_data.get("exc", None)
198
+ if exc:
199
+ logger.error(f"API Exception: {exc}")
200
+ error_messages.append(str(exc))
201
+ error_codes.append("API_EXCEPTION")
202
+
203
+ # 处理message字段中的错误
204
+ messages = response_data.get("messages", None)
205
+ logger.error(f"API Messages: {messages}")
206
+ if messages and isinstance(messages, list):
207
+ for msg in messages:
208
+ if isinstance(msg, dict):
209
+ error_msg = msg.get("message", "Unknown error")
210
+ # error_title = msg.get("title", "UnknownError")
211
+ error_messages.append(error_msg)
212
+ # error_codes.append(error_title)
213
+ if messages and isinstance(messages, str):
214
+ # 检查是否是错误消息
215
+ if any(keyword in messages.lower() for keyword in ["error", "failed", "invalid", "unauthorized"]):
216
+ logger.error(f"API Error Message: {messages}")
217
+ error_messages.append(str(messages))
218
+ error_codes.append("API_ERROR")
219
+
220
+ # 如果没有找到任何错误,返回默认错误
221
+ if not error_messages:
222
+ error_messages.append("Unknown error occurred")
223
+ error_codes.append("UnknownError")
224
+
225
+ return {"messages": error_messages, "codes": error_codes}
226
+
227
+
228
+ def is_auth_error(error_type: str) -> bool:
229
+ """
230
+ 判断是否为认证相关错误
231
+
232
+ Args:
233
+ error_type: 错误类型
234
+
235
+ Returns:
236
+ bool: 是否为认证错误
237
+ """
238
+ auth_types = ["AuthenticationError", "PermissionError", "Unauthorized", "Forbidden"]
239
+ return any(auth_type in error_type for auth_type in auth_types)
240
+
241
+
@@ -1,198 +0,0 @@
1
- # -- coding: utf-8 --
2
- # Project: fiuai_sdk_python
3
- # Created Date: 2025 10 Mo
4
- # Author: liming
5
- # Email: lmlala@aliyun.com
6
- # Copyright (c) 2025 FiuAI
7
-
8
- from typing import List, Dict, Any, Optional, Union
9
- from pydantic import BaseModel, Field
10
-
11
-
12
- class ApiResponse(BaseModel):
13
- """API响应结构体"""
14
- http_success: bool = Field(description="HTTP是否成功")
15
- api_success: bool = Field(description="API业务是否成功")
16
- status_code: int = Field(description="HTTP状态码")
17
- data: Optional[Any] = Field(description="响应数据", default=None)
18
- error_code: Optional[str] = Field(description="精简的错误码", default=None)
19
- error: Optional[str] = Field(description="错误消息", default=None)
20
-
21
- def is_success(self) -> bool:
22
- """判断是否完全成功"""
23
- return self.http_success and self.api_success
24
-
25
-
26
- def parse_response(response) -> ApiResponse:
27
- """
28
- 解析HTTP响应,返回结构化的API响应
29
-
30
- Args:
31
- response: httpx响应对象
32
-
33
- Returns:
34
- ApiResponse: 结构化的API响应
35
- """
36
- # 检查响应是否存在
37
- if not response:
38
- return ApiResponse(
39
- http_success=False,
40
- api_success=False,
41
- status_code=504,
42
- error="Api no response",
43
- error_code="API_NO_RESPONSE",
44
- )
45
-
46
-
47
- # 解析JSON响应
48
- try:
49
- # 检查HTTP状态码
50
- http_success = 200 <= response.status_code < 300
51
- except Exception as e:
52
- return ApiResponse(
53
- http_success=False,
54
- api_success=False,
55
- status_code=response.status_code,
56
- error=f"Invalid JSON response: {e}",
57
- error_code="API_INVALID_JSON"
58
- )
59
-
60
- try:
61
- response_data = response.json()
62
- except Exception as e:
63
- return ApiResponse(
64
- http_success=True,
65
- api_success=False,
66
- status_code=response.status_code,
67
- error=f"Invalid JSON response: {e}",
68
- error_code="API_INVALID_JSON"
69
- )
70
-
71
- # 检查API业务是否成功
72
- api_success = _is_api_success(response_data)
73
-
74
- if api_success:
75
- # 成功响应,提取数据
76
- data = _extract_success_data(response_data)
77
- return ApiResponse(
78
- http_success=http_success,
79
- api_success=True,
80
- status_code=response.status_code,
81
- error=None,
82
- error_code=None,
83
- data=data
84
- )
85
- else:
86
- # 失败响应,提取错误信息
87
- error_msg, error_type = _extract_error_info(response_data)
88
- return ApiResponse(
89
- http_success=http_success,
90
- api_success=False,
91
- status_code=response.status_code,
92
- error=error_msg,
93
- error_code=error_type,
94
- data=None
95
- )
96
-
97
-
98
- def _is_api_success(response_data: Dict[str, Any]) -> bool:
99
- """
100
- 判断API业务是否成功
101
-
102
- Args:
103
- response_data: API响应数据
104
-
105
- Returns:
106
- bool: 是否成功
107
- """
108
- # 检查是否有错误
109
- errors = response_data.get("errors", None)
110
- if errors:
111
- return False
112
-
113
- # 检查是否有异常
114
- exc = response_data.get("exc", None)
115
- if exc:
116
- return False
117
-
118
- # 检查HTTP状态码
119
- http_status_code = response_data.get("http_status_code", None)
120
- if http_status_code is not None:
121
- return 200 <= http_status_code < 300
122
-
123
- # 默认认为有message或data字段就是成功
124
- return response_data.get("message", None) is not None or response_data.get("data", None) is not None
125
-
126
-
127
- def _extract_success_data(response_data: Dict[str, Any]) -> Any:
128
- """
129
- 从成功响应中提取数据
130
-
131
- Args:
132
- response_data: API响应数据
133
-
134
- Returns:
135
- Any: 提取的数据
136
- """
137
- return response_data.get("data", None)
138
-
139
-
140
- def _extract_error_info(response_data: Dict[str, Any]) -> tuple[str, str]:
141
- """
142
- 从响应中提取错误信息
143
-
144
- Args:
145
- response_data: API响应数据
146
-
147
- Returns:
148
- tuple[str, str]: (错误消息, 错误类型)
149
- """
150
- # 处理errors字段
151
- errors = response_data.get("errors", None)
152
- if errors and isinstance(errors, list):
153
- if errors:
154
- # 获取所有错误消息,用换行符分割
155
- error_messages = []
156
- error_codes = []
157
- for error in errors:
158
- if isinstance(error, dict):
159
- error_msg = error.get("message", "Unknown error")
160
- error_code = error.get("type", "UnknownError")
161
- error_messages.append(error_msg)
162
- error_codes.append(error_code)
163
-
164
- # 用换行符连接所有错误消息
165
- combined_error_msg = "\n".join(error_messages)
166
- # 使用第一个错误码作为主要错误码
167
- primary_error_code = error_codes[0] if error_codes else "UnknownError"
168
- return combined_error_msg, primary_error_code
169
-
170
- # 处理exc字段(V1 API格式)
171
- exc = response_data.get("exc", None)
172
- if exc:
173
- return str(exc), "API_EXCEPTION"
174
-
175
- # 处理message字段中的错误
176
- message = response_data.get("message", None)
177
- if message and isinstance(message, str):
178
- # 检查是否是错误消息
179
- if any(keyword in message.lower() for keyword in ["error", "failed", "invalid", "unauthorized"]):
180
- return message, "API_ERROR"
181
-
182
- return "Unknown error occurred", "UnknownError"
183
-
184
-
185
- def is_auth_error(error_type: str) -> bool:
186
- """
187
- 判断是否为认证相关错误
188
-
189
- Args:
190
- error_type: 错误类型
191
-
192
- Returns:
193
- bool: 是否为认证错误
194
- """
195
- auth_types = ["AuthenticationError", "PermissionError", "Unauthorized", "Forbidden"]
196
- return any(auth_type in error_type for auth_type in auth_types)
197
-
198
-