bizyengine 1.2.50__py3-none-any.whl → 1.2.51__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,344 @@
1
+ """
2
+ Data models for conversation management
3
+ """
4
+
5
+ import json
6
+ import uuid
7
+ from dataclasses import dataclass, field
8
+ from datetime import datetime
9
+ from typing import Any, Dict, List, Optional
10
+
11
+
12
+ class ConversationValidationError(Exception):
13
+ """对话验证错误"""
14
+
15
+ pass
16
+
17
+
18
+ @dataclass
19
+ class ToolCall:
20
+ """Represents a tool call from LLM"""
21
+
22
+ id: str
23
+ type: str # Currently only "function"
24
+ function: "ToolFunction"
25
+ result: Optional[Dict[str, Any]] = None
26
+ error: Optional[str] = None
27
+
28
+ def to_dict(self) -> Dict[str, Any]:
29
+ """Convert to dictionary format"""
30
+ return {
31
+ "id": self.id,
32
+ "type": self.type,
33
+ "function": {
34
+ "name": self.function.name,
35
+ "arguments": self.function.arguments,
36
+ },
37
+ }
38
+
39
+
40
+ @dataclass
41
+ class ToolFunction:
42
+ """Represents a function call within a tool call"""
43
+
44
+ name: str
45
+ arguments: str # JSON string format
46
+
47
+
48
+ @dataclass
49
+ class Message:
50
+ """Represents a conversation message"""
51
+
52
+ role: str # "user", "assistant", "system", "tool"
53
+ content: Optional[str] = None
54
+ tool_calls: Optional[List[ToolCall]] = None
55
+ tool_call_id: Optional[str] = None
56
+ timestamp: datetime = field(default_factory=datetime.now)
57
+ reasoning_content: Optional[str] = None # For reasoning models like deepseek-R1
58
+
59
+ def __post_init__(self):
60
+ """Validate message after initialization"""
61
+ if self.role not in ["user", "assistant", "system", "tool"]:
62
+ raise ValueError(f"Invalid role: {self.role}")
63
+
64
+ if self.role == "tool" and not self.tool_call_id:
65
+ raise ValueError("Tool messages must have a tool_call_id")
66
+
67
+ if self.role == "tool" and not self.content:
68
+ raise ValueError("Tool messages must have content")
69
+
70
+ def to_openai_format(self) -> Dict[str, Any]:
71
+ """Convert to OpenAI API format"""
72
+ msg = {"role": self.role}
73
+
74
+ # Only include content if it's not None
75
+ if self.content is not None:
76
+ msg["content"] = self.content
77
+
78
+ if self.tool_calls:
79
+ msg["tool_calls"] = [tc.to_dict() for tc in self.tool_calls]
80
+
81
+ if self.tool_call_id:
82
+ msg["tool_call_id"] = self.tool_call_id
83
+
84
+ # Note: reasoning_content is not included in OpenAI format
85
+ # as it's handled separately in the conversation flow
86
+
87
+ return msg
88
+
89
+
90
+ @dataclass
91
+ class Conversation:
92
+ """Represents a conversation with message history"""
93
+
94
+ id: str
95
+ messages: List[Message]
96
+ created_at: datetime
97
+ updated_at: datetime
98
+
99
+ def add_user_message(self, content: str) -> None:
100
+ """Add a user message to the conversation"""
101
+ message = Message(role="user", content=content)
102
+ self.messages.append(message)
103
+ self.updated_at = datetime.now()
104
+
105
+ def add_assistant_message(
106
+ self, content: str, tool_calls: Optional[List[ToolCall]] = None
107
+ ) -> None:
108
+ """Add an assistant message to the conversation"""
109
+ message = Message(role="assistant", content=content, tool_calls=tool_calls)
110
+ self.messages.append(message)
111
+ self.updated_at = datetime.now()
112
+
113
+ def add_tool_result(self, tool_call_id: str, result: Any) -> None:
114
+ """Add a tool result message to the conversation"""
115
+ # Handle different result types
116
+ if isinstance(result, str):
117
+ content = result
118
+ elif isinstance(result, dict):
119
+ # 如果是字典,尝试提取有用信息
120
+ content = str(result)
121
+ else:
122
+ content = str(result)
123
+
124
+ message = Message(role="tool", content=content, tool_call_id=tool_call_id)
125
+ self.messages.append(message)
126
+ self.updated_at = datetime.now()
127
+
128
+ def add_system_message(self, content: str) -> None:
129
+ """Add a system message to the conversation"""
130
+ message = Message(role="system", content=content)
131
+ self.messages.append(message)
132
+ self.updated_at = datetime.now()
133
+
134
+ def add_message(self, message: Message) -> None:
135
+ """Add a message to the conversation"""
136
+ self.messages.append(message)
137
+ self.updated_at = datetime.now()
138
+
139
+ def to_openai_format(self) -> List[Dict[str, Any]]:
140
+ """Convert conversation to OpenAI API format"""
141
+ return [msg.to_openai_format() for msg in self.messages]
142
+
143
+ def get_message_count(self) -> int:
144
+ """Get total number of messages"""
145
+ return len(self.messages)
146
+
147
+ def get_last_message(self) -> Optional[Message]:
148
+ """Get the last message in the conversation"""
149
+ return self.messages[-1] if self.messages else None
150
+
151
+ def get_messages_by_role(self, role: str) -> List[Message]:
152
+ """Get all messages with a specific role"""
153
+ return [msg for msg in self.messages if msg.role == role]
154
+
155
+ def get_recent_messages(self, count: int) -> List[Message]:
156
+ """Get the most recent N messages"""
157
+ return self.messages[-count:] if count > 0 else []
158
+
159
+ def clear_messages(self) -> None:
160
+ """Clear all messages from the conversation"""
161
+ self.messages.clear()
162
+ self.updated_at = datetime.now()
163
+
164
+ def has_tool_calls(self) -> bool:
165
+ """Check if the conversation has any pending tool calls"""
166
+ last_message = self.get_last_message()
167
+ return (
168
+ last_message
169
+ and last_message.role == "assistant"
170
+ and last_message.tool_calls is not None
171
+ and len(last_message.tool_calls) > 0
172
+ )
173
+
174
+ @classmethod
175
+ def from_openai_format(
176
+ cls,
177
+ conversation_history: List[Dict[str, Any]],
178
+ conversation_id: Optional[str] = None,
179
+ ) -> "Conversation":
180
+ """
181
+ 从OpenAI格式的对话历史创建Conversation实例
182
+
183
+ Args:
184
+ conversation_history: OpenAI格式的消息列表
185
+ conversation_id: 可选的对话ID,如果不提供则生成新的
186
+
187
+ Returns:
188
+ Conversation实例
189
+
190
+ Raises:
191
+ ConversationValidationError: 如果消息格式无效
192
+ """
193
+ if conversation_id is None:
194
+ conversation_id = str(uuid.uuid4())
195
+
196
+ messages = []
197
+ current_time = datetime.now()
198
+
199
+ for i, msg_data in enumerate(conversation_history):
200
+ try:
201
+ message = cls._parse_message_from_dict(msg_data)
202
+ messages.append(message)
203
+ except Exception as e:
204
+ raise ConversationValidationError(
205
+ f"Invalid message format at index {i}: {e}"
206
+ ) from e
207
+
208
+ return cls(
209
+ id=conversation_id,
210
+ messages=messages,
211
+ created_at=current_time,
212
+ updated_at=current_time,
213
+ )
214
+
215
+ @staticmethod
216
+ def _parse_message_from_dict(msg_data: Dict[str, Any]) -> Message:
217
+ """解析单个消息字典为Message对象"""
218
+
219
+ # 验证必需字段
220
+ if "role" not in msg_data:
221
+ raise ConversationValidationError("Message must have 'role' field")
222
+
223
+ role = msg_data["role"]
224
+ if role not in ["user", "assistant", "system", "tool"]:
225
+ raise ConversationValidationError(f"Invalid role: {role}")
226
+
227
+ content = msg_data.get("content")
228
+
229
+ # 解析工具调用(如果存在)
230
+ tool_calls = None
231
+ if "tool_calls" in msg_data and msg_data["tool_calls"]:
232
+ tool_calls = []
233
+ for tc_data in msg_data["tool_calls"]:
234
+ if not isinstance(tc_data, dict):
235
+ raise ConversationValidationError(
236
+ "Each tool_call must be a dictionary"
237
+ )
238
+
239
+ if "id" not in tc_data or "function" not in tc_data:
240
+ raise ConversationValidationError(
241
+ "tool_call must have 'id' and 'function' fields"
242
+ )
243
+
244
+ function = tc_data["function"]
245
+ if "name" not in function or "arguments" not in function:
246
+ raise ConversationValidationError(
247
+ "function must have 'name' and 'arguments' fields"
248
+ )
249
+
250
+ # 验证arguments是否为有效JSON
251
+ try:
252
+ json.loads(function["arguments"])
253
+ except json.JSONDecodeError as e:
254
+ raise ConversationValidationError(
255
+ f"Invalid JSON in function arguments: {e}"
256
+ )
257
+
258
+ tool_call = ToolCall(
259
+ id=str(tc_data["id"]),
260
+ type=tc_data.get("type", "function"),
261
+ function=ToolFunction(
262
+ name=str(function["name"]), arguments=str(function["arguments"])
263
+ ),
264
+ )
265
+ tool_calls.append(tool_call)
266
+
267
+ # 获取工具调用ID(用于tool角色消息)
268
+ tool_call_id = msg_data.get("tool_call_id")
269
+ if role == "tool" and not tool_call_id:
270
+ raise ConversationValidationError("Tool messages must have a tool_call_id")
271
+
272
+ # 处理推理内容(如果存在)
273
+ reasoning_content = msg_data.get("reasoning_content")
274
+
275
+ return Message(
276
+ role=role,
277
+ content=content,
278
+ tool_calls=tool_calls,
279
+ tool_call_id=str(tool_call_id) if tool_call_id else None,
280
+ reasoning_content=reasoning_content,
281
+ )
282
+
283
+ @staticmethod
284
+ def validate_conversation_history(
285
+ conversation_history: List[Dict[str, Any]],
286
+ ) -> bool:
287
+ """验证对话历史格式是否正确"""
288
+ try:
289
+ if not isinstance(conversation_history, list):
290
+ return False
291
+
292
+ for msg_data in conversation_history:
293
+ # 基本字段验证
294
+ if not isinstance(msg_data, dict):
295
+ return False
296
+
297
+ if "role" not in msg_data:
298
+ return False
299
+
300
+ role = msg_data["role"]
301
+ if role not in ["user", "assistant", "system", "tool"]:
302
+ return False
303
+
304
+ # 角色特定验证
305
+ if role == "tool":
306
+ if "tool_call_id" not in msg_data or not msg_data["tool_call_id"]:
307
+ return False
308
+ if "content" not in msg_data:
309
+ return False
310
+
311
+ # 工具调用格式验证
312
+ if "tool_calls" in msg_data and msg_data["tool_calls"]:
313
+ tool_calls = msg_data["tool_calls"]
314
+ if not isinstance(tool_calls, list):
315
+ return False
316
+
317
+ for tc in tool_calls:
318
+ if not isinstance(tc, dict):
319
+ return False
320
+ if "id" not in tc or "function" not in tc:
321
+ return False
322
+ if (
323
+ "name" not in tc["function"]
324
+ or "arguments" not in tc["function"]
325
+ ):
326
+ return False
327
+
328
+ # 验证arguments是否为有效JSON
329
+ try:
330
+ json.loads(tc["function"]["arguments"])
331
+ except json.JSONDecodeError:
332
+ return False
333
+
334
+ return True
335
+ except Exception:
336
+ return False
337
+
338
+ def add_user_message_from_request(self, message: str) -> None:
339
+ """从请求中添加用户消息"""
340
+ self.add_user_message(message)
341
+
342
+ def get_openai_messages_for_llm(self) -> List[Dict[str, Any]]:
343
+ """获取用于LLM调用的OpenAI格式消息"""
344
+ return self.to_openai_format()
@@ -107,7 +107,6 @@ def get_api_key() -> str:
107
107
  if validate_api_key(BIZYAIR_API_KEY):
108
108
  api_key_state.is_valid = True
109
109
  api_key_state.current_api_key = BIZYAIR_API_KEY
110
- logging.info("API key set successfully")
111
110
  except Exception as e:
112
111
  logging.error(str(e))
113
112
  raise ValueError(str(e))
bizyengine/version.txt CHANGED
@@ -1 +1 @@
1
- 1.2.50
1
+ 1.2.51
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bizyengine
3
- Version: 1.2.50
3
+ Version: 1.2.51
4
4
  Summary: [a/BizyAir](https://github.com/siliconflow/BizyAir) Comfy Nodes that can run in any environment.
5
5
  Author-email: SiliconFlow <yaochi@siliconflow.cn>
6
6
  Project-URL: Repository, https://github.com/siliconflow/BizyAir
@@ -13,6 +13,7 @@ Requires-Dist: requests
13
13
  Requires-Dist: inputimeout
14
14
  Requires-Dist: openai>=1.77.0
15
15
  Requires-Dist: pycryptodome
16
+ Requires-Dist: mcp>=1.8.0
16
17
  Requires-Dist: bizyairsdk>=0.0.4
17
18
 
18
19
  ## BizyEngine
@@ -1,13 +1,13 @@
1
1
  bizyengine/__init__.py,sha256=GP9V-JM07fz7uv_qTB43QEA2rKdrVJxi5I7LRnn_3ZQ,914
2
- bizyengine/version.txt,sha256=zGrmrSrzHGOEzO23IqIfRL3venxRA-IUVh6N6ZhB4X8,7
2
+ bizyengine/version.txt,sha256=AvxtajeVm2SVm3pKmMIouv9qTZ-GSKH6a35TmY6KQVo,7
3
3
  bizyengine/bizy_server/__init__.py,sha256=SP9oSblnPo4KQyh7yOGD26YCskFAcQHAZy04nQBNRIw,200
4
4
  bizyengine/bizy_server/api_client.py,sha256=Z7G5IjaEqSJkF6nLLw2R3bpgBAOi5ClQiUbel6NMXmE,43932
5
- bizyengine/bizy_server/errno.py,sha256=1UiFmE2U7r7hCHgsw4-p_YL0VCmTJc9NyYDEbhkanaY,16336
5
+ bizyengine/bizy_server/errno.py,sha256=RIyvegX3lzpx_1L1q2XVvu3on0kvYgKiUQ8U3ZtyF68,16823
6
6
  bizyengine/bizy_server/error_handler.py,sha256=MGrfO1AEqbfEgMWPL8B6Ypew_zHiQAdYGlhN9bZohrY,167
7
7
  bizyengine/bizy_server/execution.py,sha256=ayaEf6eGJKQsVZV-1_UlGlvwwmlH7FEek31Uq-MbUjA,1644
8
8
  bizyengine/bizy_server/profile.py,sha256=f4juAzJ73gCm0AhagYpt9WnG8HEI6xze_U96-omBLqU,3044
9
9
  bizyengine/bizy_server/resp.py,sha256=iOFT5Ud7VJBP2uqkojJIgc3y2ifMjjEXoj0ewneL9lc,710
10
- bizyengine/bizy_server/server.py,sha256=7OWEA8GMrqpg4jxCvakX5zR2JukEOhKbqPRefYo6t8c,59707
10
+ bizyengine/bizy_server/server.py,sha256=isOzHDk2kD6WjdlemeOA7j_xLnZ2vat_NvE1I0bsOFw,57490
11
11
  bizyengine/bizy_server/stream_response.py,sha256=H2XHqlVRtQMhgdztAuG7l8-iV_Pm42u2x6WJ0gNVIW0,9654
12
12
  bizyengine/bizy_server/utils.py,sha256=Kkn-AATZcdaDhg8Rg_EJW6aKqkyiSE2EYmuyOhUwXso,3863
13
13
  bizyengine/bizyair_extras/__init__.py,sha256=lsGuqMnFX-WSOUszSQ5NB6FnKcHlhhO1WG9iiywWQug,1092
@@ -46,6 +46,17 @@ bizyengine/bizyair_extras/nodes_ipadapter_plus/__init__.py,sha256=ECKATm_EKi_4G4
46
46
  bizyengine/bizyair_extras/nodes_ipadapter_plus/nodes_ipadapter_plus.py,sha256=lOKRem7oiPs8ZkA_p68HxagAgiCSvn3Rk-L4fSXIjyE,54846
47
47
  bizyengine/bizyair_extras/nodes_kolors_mz/__init__.py,sha256=HsCCCphW8q0SrWEiFlZKK_W2lQr1T0UJIJL7gEn37ME,3729
48
48
  bizyengine/bizyair_extras/oauth_callback/main.py,sha256=KQOZWor3kyNx8xvUNHYNMoHfCF9g_ht13_iPk4K_5YM,3633
49
+ bizyengine/bizybot/__init__.py,sha256=NINN_7QECKQwtAwKPBTrrSiAK6KbxaZCkIvJ-e1J1xk,262
50
+ bizyengine/bizybot/client.py,sha256=PWdcjslMaW4xmNaAq3TwRGV8twg9yPEfDNyfuZzpCyY,26029
51
+ bizyengine/bizybot/config.py,sha256=F2uN-2z-VGDtwe0aK8mDQ6-2wzRzy6EI0gH7i8UkoPU,4398
52
+ bizyengine/bizybot/coordinator.py,sha256=qFqZIwthJo1EZLgelMrW5PuwuPqCx2eIPxcQxLmW3NU,22038
53
+ bizyengine/bizybot/exceptions.py,sha256=8Mw7Y10LWdjImSkPXvCgGJqKFinCCYFgj7oRO72cNb0,4067
54
+ bizyengine/bizybot/models.py,sha256=oHfdvqcmjF2WsqbttyqHVarndrNBoPylENirBC7u6zY,11760
55
+ bizyengine/bizybot/mcp/__init__.py,sha256=qWrcVAgX6B88qqRwVXQoTcybuE1lcBN9mURmPyL0o1U,27
56
+ bizyengine/bizybot/mcp/manager.py,sha256=uPpqtJpCbr5u9Ey5qtDHKX2mt_ifNzD50kczITCE4GE,19205
57
+ bizyengine/bizybot/mcp/models.py,sha256=Ybo7QK4T32YpYwcUs88d5Hi39pz7yEu7qIeaQ5BkX4M,998
58
+ bizyengine/bizybot/mcp/registry.py,sha256=jUqny2Km9EcvHmpBZl06HqNxWm0PQT6TZS8EOiiBVAw,4855
59
+ bizyengine/bizybot/mcp/routing.py,sha256=COgeao02y-oIiHpcXEZGl2cccgcR1u343BEcJ95sCJw,13780
49
60
  bizyengine/core/__init__.py,sha256=EV9ZtTwOHC0S_eNvCu-tltIydfxfMsH59LbgVX4e_1c,359
50
61
  bizyengine/core/data_types.py,sha256=2f7QqqZvhKmXw3kZV1AvXuPTda34b4wXQE9tyO8nUSM,1595
51
62
  bizyengine/core/image_utils.py,sha256=vJt42FcEDD8-fQumuogZM1XXwgYseSQ79_9Qzu9YTQk,409
@@ -60,7 +71,7 @@ bizyengine/core/commands/servers/model_server.py,sha256=47DEQpj8HBSa-_TImW-5JCeu
60
71
  bizyengine/core/commands/servers/prompt_server.py,sha256=e8JhtKRM8nw0kQwe2Ofl-zQtiVqQdbbWRxOqkFmSclM,10873
61
72
  bizyengine/core/common/__init__.py,sha256=GicZw6YeAZk1PsKmFDt9dm1F75zPUlpia9Q_ki5vW1Y,179
62
73
  bizyengine/core/common/caching.py,sha256=hRNsSrfNxgc1zzvBzLVjMY0iMkKqA0TBCr-iYhEpzik,6946
63
- bizyengine/core/common/client.py,sha256=KzKEzsiiY4RXT3kFZ84oBbdpLJopmV_BjHAaRe03qlo,10682
74
+ bizyengine/core/common/client.py,sha256=gk8hfw8sjbJJM7zgz5-54YiC25PHibuT1UtgKFTAhO4,10625
64
75
  bizyengine/core/common/env_var.py,sha256=1EAW3gOXY2bKouCqrGa583vTJRdDasQ1IsFTnzDg7Dk,3450
65
76
  bizyengine/core/common/utils.py,sha256=bm-XmSPy83AyjD0v5EfWp6jiO6_5p7rkZ_HQAuVmgmo,3086
66
77
  bizyengine/core/configs/conf.py,sha256=D_UWG9SSJnK5EhbrfNFryJQ8hUwwdvhOGlq1TielwpI,3830
@@ -80,7 +91,7 @@ bizyengine/misc/route_sam.py,sha256=-bMIR2QalfnszipGxSxvDAHGJa5gPSrjkYPb5baaRg4,
80
91
  bizyengine/misc/segment_anything.py,sha256=wNKYwlYPMszfwj23524geFZJjZaG4eye65SGaUnh77I,8941
81
92
  bizyengine/misc/supernode.py,sha256=STN9gaxfTSErH8OiHeZa47d8z-G9S0I7fXuJvHQOBFM,4532
82
93
  bizyengine/misc/utils.py,sha256=CKduySGSMNGlJMImHyZmN-giABY5VUaB88f6Kq-HAV0,6831
83
- bizyengine-1.2.50.dist-info/METADATA,sha256=oDy6bxpEMjEUlrLgBDEdsI-Rz1pW7SM_lDl3b5HbNJk,708
84
- bizyengine-1.2.50.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
85
- bizyengine-1.2.50.dist-info/top_level.txt,sha256=2zapzqxX-we5cRyJkGf9bd5JinRtXp3-_uDI-xCAnc0,11
86
- bizyengine-1.2.50.dist-info/RECORD,,
94
+ bizyengine-1.2.51.dist-info/METADATA,sha256=ubIks74xsnM0PG1697BjTcGvw-LxY5gaG-btFI8-cUk,734
95
+ bizyengine-1.2.51.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
96
+ bizyengine-1.2.51.dist-info/top_level.txt,sha256=2zapzqxX-we5cRyJkGf9bd5JinRtXp3-_uDI-xCAnc0,11
97
+ bizyengine-1.2.51.dist-info/RECORD,,