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.
@@ -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"))
@@ -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
+