agent-builder-gateway-sdk 0.7.1__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.
- agent_builder_gateway_sdk-0.7.1.dist-info/METADATA +540 -0
- agent_builder_gateway_sdk-0.7.1.dist-info/RECORD +10 -0
- agent_builder_gateway_sdk-0.7.1.dist-info/WHEEL +4 -0
- agent_builder_gateway_sdk-0.7.1.dist-info/licenses/LICENSE +22 -0
- gateway_sdk/__init__.py +34 -0
- gateway_sdk/auth.py +49 -0
- gateway_sdk/client.py +726 -0
- gateway_sdk/exceptions.py +87 -0
- gateway_sdk/models.py +213 -0
- gateway_sdk/streaming.py +41 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""异常定义"""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GatewayError(Exception):
|
|
7
|
+
"""Gateway SDK 基础异常"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
|
|
10
|
+
self.message = message
|
|
11
|
+
self.details = details or {}
|
|
12
|
+
super().__init__(message)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AuthenticationError(GatewayError):
|
|
16
|
+
"""认证失败"""
|
|
17
|
+
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PrefabNotFoundError(GatewayError):
|
|
22
|
+
"""预制件不存在"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, prefab_id: str, version: str, message: Optional[str] = None):
|
|
25
|
+
self.prefab_id = prefab_id
|
|
26
|
+
self.version = version
|
|
27
|
+
super().__init__(
|
|
28
|
+
message or f"Prefab {prefab_id}@{version} not found",
|
|
29
|
+
{"prefab_id": prefab_id, "version": version},
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ValidationError(GatewayError):
|
|
34
|
+
"""参数验证失败"""
|
|
35
|
+
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class QuotaExceededError(GatewayError):
|
|
40
|
+
"""配额超限"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, message: str, limit: int, used: int, quota_type: str):
|
|
43
|
+
self.limit = limit
|
|
44
|
+
self.used = used
|
|
45
|
+
self.quota_type = quota_type
|
|
46
|
+
super().__init__(
|
|
47
|
+
message,
|
|
48
|
+
{"limit": limit, "used": used, "quota_type": quota_type},
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ServiceUnavailableError(GatewayError):
|
|
53
|
+
"""服务不可用"""
|
|
54
|
+
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class MissingSecretError(GatewayError):
|
|
59
|
+
"""缺少必需的密钥"""
|
|
60
|
+
|
|
61
|
+
def __init__(self, prefab_id: str, secret_name: str, instructions: Optional[str] = None):
|
|
62
|
+
self.prefab_id = prefab_id
|
|
63
|
+
self.secret_name = secret_name
|
|
64
|
+
self.instructions = instructions
|
|
65
|
+
message = f"Missing required secret '{secret_name}' for prefab '{prefab_id}'"
|
|
66
|
+
if instructions:
|
|
67
|
+
message += f"\n配置说明: {instructions}"
|
|
68
|
+
super().__init__(
|
|
69
|
+
message,
|
|
70
|
+
{"prefab_id": prefab_id, "secret_name": secret_name, "instructions": instructions},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class AgentContextRequiredError(GatewayError):
|
|
75
|
+
"""缺少 Agent 上下文(文件操作需要在 Agent 环境中运行)"""
|
|
76
|
+
|
|
77
|
+
def __init__(self, message: Optional[str] = None):
|
|
78
|
+
default_message = (
|
|
79
|
+
"File operations require agent context.\n"
|
|
80
|
+
"This feature is only available when running in production (called via Gateway Agent invoke).\n"
|
|
81
|
+
"For third-party integrations, please use upload_input_file() to upload input files."
|
|
82
|
+
)
|
|
83
|
+
super().__init__(
|
|
84
|
+
message or default_message,
|
|
85
|
+
{"error_code": "MISSING_AGENT_CONTEXT"},
|
|
86
|
+
)
|
|
87
|
+
|
gateway_sdk/models.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""数据模型"""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CallStatus(str, Enum):
|
|
9
|
+
"""调用状态"""
|
|
10
|
+
|
|
11
|
+
SUCCESS = "SUCCESS"
|
|
12
|
+
FAILED = "FAILED"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class StreamEventType(str, Enum):
|
|
16
|
+
"""流式事件类型"""
|
|
17
|
+
|
|
18
|
+
START = "start"
|
|
19
|
+
CONTENT = "content"
|
|
20
|
+
PROGRESS = "progress"
|
|
21
|
+
DONE = "done"
|
|
22
|
+
ERROR = "error"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class PrefabCall:
|
|
27
|
+
"""预制件调用请求"""
|
|
28
|
+
|
|
29
|
+
prefab_id: str
|
|
30
|
+
version: str
|
|
31
|
+
function_name: str
|
|
32
|
+
parameters: Dict[str, Any]
|
|
33
|
+
files: Optional[Dict[str, List[str]]] = None
|
|
34
|
+
|
|
35
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
36
|
+
"""转换为字典格式"""
|
|
37
|
+
inputs: Dict[str, Any] = {"parameters": self.parameters}
|
|
38
|
+
if self.files:
|
|
39
|
+
inputs["files"] = self.files
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
"prefab_id": self.prefab_id,
|
|
43
|
+
"version": self.version,
|
|
44
|
+
"function_name": self.function_name,
|
|
45
|
+
"inputs": inputs,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class PrefabResult:
|
|
51
|
+
"""预制件执行结果"""
|
|
52
|
+
|
|
53
|
+
status: CallStatus
|
|
54
|
+
output: Optional[Dict[str, Any]] = None
|
|
55
|
+
error: Optional[Dict[str, Any]] = None
|
|
56
|
+
job_id: Optional[str] = None
|
|
57
|
+
|
|
58
|
+
def is_success(self) -> bool:
|
|
59
|
+
"""判断是否成功"""
|
|
60
|
+
return self.status == CallStatus.SUCCESS
|
|
61
|
+
|
|
62
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
63
|
+
"""便捷获取输出字段"""
|
|
64
|
+
if self.output:
|
|
65
|
+
return self.output.get(key, default)
|
|
66
|
+
return default
|
|
67
|
+
|
|
68
|
+
def get_result(self) -> Any:
|
|
69
|
+
"""
|
|
70
|
+
获取完整的 output 字典(保持向后兼容)
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
完整的 output 字典,包含 status, output, files 等
|
|
74
|
+
"""
|
|
75
|
+
return self.output
|
|
76
|
+
|
|
77
|
+
def get_function_result(self) -> Dict[str, Any]:
|
|
78
|
+
"""
|
|
79
|
+
获取预制件函数的返回值(对应 manifest.returns)
|
|
80
|
+
|
|
81
|
+
这是响应的嵌套结构:
|
|
82
|
+
- result.output 是 Gateway 的响应
|
|
83
|
+
- result.output['output'] 是预制件函数的实际返回值
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
预制件函数的返回值字典
|
|
87
|
+
|
|
88
|
+
Example:
|
|
89
|
+
>>> result = client.run(...)
|
|
90
|
+
>>> function_result = result.get_function_result()
|
|
91
|
+
>>> if function_result.get('success'):
|
|
92
|
+
... print(function_result.get('message'))
|
|
93
|
+
"""
|
|
94
|
+
if self.output and isinstance(self.output, dict):
|
|
95
|
+
return self.output.get('output', {})
|
|
96
|
+
return {}
|
|
97
|
+
|
|
98
|
+
def get_business_success(self) -> bool:
|
|
99
|
+
"""
|
|
100
|
+
判断业务是否成功(检查函数返回值中的 success 字段)
|
|
101
|
+
|
|
102
|
+
注意:
|
|
103
|
+
- result.is_success() 表示 SDK 调用成功
|
|
104
|
+
- result.get_business_success() 表示业务逻辑成功
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
业务是否成功
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
>>> result = client.run(...)
|
|
111
|
+
>>> if result.is_success() and result.get_business_success():
|
|
112
|
+
... print("调用成功且业务成功")
|
|
113
|
+
"""
|
|
114
|
+
function_result = self.get_function_result()
|
|
115
|
+
return function_result.get('success', False)
|
|
116
|
+
|
|
117
|
+
def get_business_message(self) -> str:
|
|
118
|
+
"""
|
|
119
|
+
获取业务消息
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
业务消息字符串,如果没有消息则返回空字符串
|
|
123
|
+
|
|
124
|
+
Example:
|
|
125
|
+
>>> result = client.run(...)
|
|
126
|
+
>>> print(f"消息: {result.get_business_message()}")
|
|
127
|
+
"""
|
|
128
|
+
function_result = self.get_function_result()
|
|
129
|
+
return function_result.get('message', '')
|
|
130
|
+
|
|
131
|
+
def get_business_error(self) -> Optional[str]:
|
|
132
|
+
"""
|
|
133
|
+
获取业务错误信息
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
错误信息字符串,如果没有错误则返回 None
|
|
137
|
+
|
|
138
|
+
Example:
|
|
139
|
+
>>> result = client.run(...)
|
|
140
|
+
>>> if not result.get_business_success():
|
|
141
|
+
... print(f"错误: {result.get_business_error()}")
|
|
142
|
+
"""
|
|
143
|
+
function_result = self.get_function_result()
|
|
144
|
+
return function_result.get('error')
|
|
145
|
+
|
|
146
|
+
def get_business_error_code(self) -> Optional[str]:
|
|
147
|
+
"""
|
|
148
|
+
获取业务错误代码
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
错误代码字符串,如果没有错误码则返回 None
|
|
152
|
+
|
|
153
|
+
Example:
|
|
154
|
+
>>> result = client.run(...)
|
|
155
|
+
>>> if not result.get_business_success():
|
|
156
|
+
... print(f"错误码: {result.get_business_error_code()}")
|
|
157
|
+
"""
|
|
158
|
+
function_result = self.get_function_result()
|
|
159
|
+
return function_result.get('error_code')
|
|
160
|
+
|
|
161
|
+
def get_files(self) -> Dict[str, List[str]]:
|
|
162
|
+
"""
|
|
163
|
+
获取输出文件(S3 URL 列表)
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
文件字典,key 对应 manifest.files 中定义的 key
|
|
167
|
+
|
|
168
|
+
Example:
|
|
169
|
+
>>> result = client.run(...)
|
|
170
|
+
>>> output_files = result.get_files()
|
|
171
|
+
>>> # 获取特定 key 的输出文件
|
|
172
|
+
>>> if 'output' in output_files:
|
|
173
|
+
... s3_url = output_files['output'][0]
|
|
174
|
+
"""
|
|
175
|
+
if self.output:
|
|
176
|
+
return self.output.get("files", {})
|
|
177
|
+
return {}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@dataclass
|
|
181
|
+
class BatchResult:
|
|
182
|
+
"""批量执行结果"""
|
|
183
|
+
|
|
184
|
+
job_id: str
|
|
185
|
+
status: str
|
|
186
|
+
results: List[PrefabResult]
|
|
187
|
+
|
|
188
|
+
def all_success(self) -> bool:
|
|
189
|
+
"""判断是否全部成功"""
|
|
190
|
+
return all(r.is_success() for r in self.results)
|
|
191
|
+
|
|
192
|
+
def get_failed(self) -> List[PrefabResult]:
|
|
193
|
+
"""获取失败的结果"""
|
|
194
|
+
return [r for r in self.results if not r.is_success()]
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass
|
|
198
|
+
class StreamEvent:
|
|
199
|
+
"""流式事件"""
|
|
200
|
+
|
|
201
|
+
type: StreamEventType
|
|
202
|
+
data: Any
|
|
203
|
+
|
|
204
|
+
@classmethod
|
|
205
|
+
def from_dict(cls, data: Dict[str, Any]) -> "StreamEvent":
|
|
206
|
+
"""从字典创建"""
|
|
207
|
+
event_type = data.get("type", "content")
|
|
208
|
+
try:
|
|
209
|
+
event_type_enum = StreamEventType(event_type)
|
|
210
|
+
except ValueError:
|
|
211
|
+
event_type_enum = StreamEventType.CONTENT
|
|
212
|
+
|
|
213
|
+
return cls(type=event_type_enum, data=data.get("data"))
|
gateway_sdk/streaming.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""流式响应处理"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Iterator
|
|
5
|
+
from .models import StreamEvent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def parse_sse_stream(response_iter: Iterator[bytes]) -> Iterator[StreamEvent]:
|
|
9
|
+
"""
|
|
10
|
+
解析 SSE 流式响应
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
response_iter: HTTP 响应字节流迭代器
|
|
14
|
+
|
|
15
|
+
Yields:
|
|
16
|
+
StreamEvent: 解析后的流式事件
|
|
17
|
+
"""
|
|
18
|
+
buffer = b""
|
|
19
|
+
|
|
20
|
+
for chunk in response_iter:
|
|
21
|
+
buffer += chunk
|
|
22
|
+
|
|
23
|
+
# 按行分割
|
|
24
|
+
while b"\n" in buffer:
|
|
25
|
+
line_bytes, buffer = buffer.split(b"\n", 1)
|
|
26
|
+
line = line_bytes.decode("utf-8").strip()
|
|
27
|
+
|
|
28
|
+
# 跳过空行和注释
|
|
29
|
+
if not line or line.startswith(":"):
|
|
30
|
+
continue
|
|
31
|
+
|
|
32
|
+
# 解析 SSE 格式:data: {...}
|
|
33
|
+
if line.startswith("data: "):
|
|
34
|
+
data_str = line[6:] # 去掉 "data: " 前缀
|
|
35
|
+
try:
|
|
36
|
+
data = json.loads(data_str)
|
|
37
|
+
yield StreamEvent.from_dict(data)
|
|
38
|
+
except json.JSONDecodeError:
|
|
39
|
+
# 忽略无法解析的行
|
|
40
|
+
continue
|
|
41
|
+
|