dumplingsAI 0.1.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.
- dumplingsAI/Agent_Base_.py +659 -0
- dumplingsAI/Agent_list.py +37 -0
- dumplingsAI/__init__.py +220 -0
- dumplingsAI/agent_tool.py +390 -0
- dumplingsAI/anthropic_agent.py +603 -0
- dumplingsAI/logging_config.py +136 -0
- dumplingsAI/mcp_bridge.py +583 -0
- dumplingsAI/skill.py +537 -0
- dumplingsAI/skill_bridge.py +102 -0
- dumplingsai-0.1.1.dist-info/METADATA +491 -0
- dumplingsai-0.1.1.dist-info/RECORD +14 -0
- dumplingsai-0.1.1.dist-info/WHEEL +5 -0
- dumplingsai-0.1.1.dist-info/licenses/LICENSE +202 -0
- dumplingsai-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import platform
|
|
4
|
+
import re
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
import uuid
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
from bs4 import BeautifulSoup
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
from .agent_tool import _builtin_promote_overrides, builtin_tool, tool_registry
|
|
15
|
+
from .logging_config import logger # 配置日志
|
|
16
|
+
except Exception:
|
|
17
|
+
raise ImportError("不可单独执行")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Agent():
|
|
21
|
+
"""
|
|
22
|
+
所有具体 Dumplings 必须实现四个属性:
|
|
23
|
+
api_key
|
|
24
|
+
api_provider
|
|
25
|
+
model_name
|
|
26
|
+
prompt
|
|
27
|
+
"""
|
|
28
|
+
prompt = None
|
|
29
|
+
api_provider = None
|
|
30
|
+
model_name = None
|
|
31
|
+
api_key = None
|
|
32
|
+
fc_model = True # 现在xml工具调用改为下位支持,如有bug修复优先级降低
|
|
33
|
+
stream = True
|
|
34
|
+
description = None
|
|
35
|
+
|
|
36
|
+
# ---------------- 类层级钩子:子类覆盖 @builtin_tool 方法时自动复用父类的 meta ----------------
|
|
37
|
+
def __init_subclass__(cls, **kwargs):
|
|
38
|
+
super().__init_subclass__(**kwargs)
|
|
39
|
+
_builtin_promote_overrides(cls)
|
|
40
|
+
# ---------------- 通用构造 ----------------
|
|
41
|
+
def __init__(self,new_load=True):
|
|
42
|
+
self.uuid=self.__class__.uuid
|
|
43
|
+
self.name=self.__class__.name
|
|
44
|
+
self.stream_run=False
|
|
45
|
+
self.current_task_id = None # 当前任务 ID
|
|
46
|
+
self.tool_call_hooks = [] # 工具调用钩子列表
|
|
47
|
+
agent_name = getattr(self.__class__, 'name', None) or getattr(self.__class__, '__name__', None)
|
|
48
|
+
if agent_name and self.uuid:
|
|
49
|
+
tool_registry.register_agent_uuid(self.uuid, agent_name)
|
|
50
|
+
|
|
51
|
+
# 获取该 agent 有权限的所有工具信息
|
|
52
|
+
tools_info = tool_registry.get_all_tools_info(self.uuid)
|
|
53
|
+
tools_prompt = ""
|
|
54
|
+
tools_list = list(tools_info.keys())
|
|
55
|
+
|
|
56
|
+
# 通过自动收集器获取 @builtin_tool 装饰的内置工具 schema
|
|
57
|
+
builtin_schemas = tool_registry.collect_builtin_tools(self)
|
|
58
|
+
builtin_names = {s["function"]["name"] for s in builtin_schemas}
|
|
59
|
+
for name in builtin_names:
|
|
60
|
+
if name not in tools_list:
|
|
61
|
+
tools_list.append(name)
|
|
62
|
+
|
|
63
|
+
# 生成工具提示
|
|
64
|
+
if tools_list:
|
|
65
|
+
tools_prompt = "\n\n你可以使用以下工具:\n"
|
|
66
|
+
# 注册的工具
|
|
67
|
+
for tool_name, tool_info in tools_info.items():
|
|
68
|
+
tools_prompt += f"- {tool_name}: {tool_info['description']}\n"
|
|
69
|
+
# 内置工具(自动来自 @builtin_tool 装饰器)
|
|
70
|
+
for s in builtin_schemas:
|
|
71
|
+
n = s["function"]["name"]
|
|
72
|
+
if n in tools_list and n not in tools_info:
|
|
73
|
+
tools_prompt += f"- {n}: {s['function']['description']}\n"
|
|
74
|
+
if not self.fc_model:
|
|
75
|
+
tools_prompt += "在使用xml格式的工具时应采用(无参数调用)<工具名></工具名>(含参数调用)<工具名><参数1>放入你想传入的内容</参数1>...</工具名>"
|
|
76
|
+
|
|
77
|
+
# 注入 Skills 信息
|
|
78
|
+
try:
|
|
79
|
+
from .skill import skill_registry
|
|
80
|
+
tools_prompt += skill_registry.get_skills_prompt_text(self.uuid)
|
|
81
|
+
except ImportError:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
prompt = self.prompt + tools_prompt + ", 你的uuid " + str(self.uuid)
|
|
86
|
+
logger.debug(f"Agent {self.name} 初始化,系统提示词长度:{len(prompt)}")
|
|
87
|
+
# print(prompt)
|
|
88
|
+
if new_load:
|
|
89
|
+
self.history = [{"role": "system", "content": prompt}]
|
|
90
|
+
else:
|
|
91
|
+
self.history[0] = {"role": "system", "content": prompt}
|
|
92
|
+
self.headers = {
|
|
93
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
94
|
+
"Content-Type": "application/json"
|
|
95
|
+
}
|
|
96
|
+
self.os_name = platform.system()
|
|
97
|
+
self.conversations_folder = os.getcwd()
|
|
98
|
+
if self.os_name == "Windows":
|
|
99
|
+
self.os_main_folder = os.getenv("USERPROFILE")
|
|
100
|
+
elif self.os_name == "Linux":
|
|
101
|
+
self.os_main_folder = os.path.expanduser("~")
|
|
102
|
+
elif self.os_name == "Darwin":
|
|
103
|
+
self.os_main_folder = os.getenv("HOME")
|
|
104
|
+
|
|
105
|
+
threading.Thread(target=self.Connectivity, daemon=True).start()
|
|
106
|
+
|
|
107
|
+
# ---------------- 辅助方法 ----------------
|
|
108
|
+
def _generate_task_id(self):
|
|
109
|
+
"""生成唯一任务 ID"""
|
|
110
|
+
return str(uuid.uuid4())
|
|
111
|
+
|
|
112
|
+
def _get_timestamp(self):
|
|
113
|
+
"""获取当前时间戳(毫秒级)"""
|
|
114
|
+
return int(time.time() * 1000)
|
|
115
|
+
|
|
116
|
+
def register_tool_hook(self, hook_func):
|
|
117
|
+
"""
|
|
118
|
+
注册工具调用钩子
|
|
119
|
+
hook_func 签名:hook_func(event_type, tool_name, tool_args, tool_result, task_id)
|
|
120
|
+
event_type: 'before' | 'after' | 'error'
|
|
121
|
+
"""
|
|
122
|
+
self.tool_call_hooks.append(hook_func)
|
|
123
|
+
|
|
124
|
+
def _execute_hooks(self, event_type, tool_name, tool_args, tool_result=None):
|
|
125
|
+
"""执行所有注册的钩子"""
|
|
126
|
+
for hook in self.tool_call_hooks:
|
|
127
|
+
try:
|
|
128
|
+
hook(
|
|
129
|
+
event_type=event_type,
|
|
130
|
+
tool_name=tool_name,
|
|
131
|
+
tool_args=tool_args,
|
|
132
|
+
tool_result=tool_result,
|
|
133
|
+
task_id=self.current_task_id
|
|
134
|
+
)
|
|
135
|
+
except Exception as e:
|
|
136
|
+
logger.error(f"钩子执行失败:{e}")
|
|
137
|
+
|
|
138
|
+
# ---------------- 连通性测试 ----------------
|
|
139
|
+
def Connectivity(self):
|
|
140
|
+
payload = {
|
|
141
|
+
"model": self.model_name,
|
|
142
|
+
"messages": [{"role": "user", "content": "你好"}],
|
|
143
|
+
"stream": self.stream,
|
|
144
|
+
"stream_options": {"include_usage": True},
|
|
145
|
+
"max_tokens": 1
|
|
146
|
+
}
|
|
147
|
+
rsp = requests.post(self.api_provider,
|
|
148
|
+
headers=self.headers,
|
|
149
|
+
json=payload)
|
|
150
|
+
if rsp.status_code == 200:
|
|
151
|
+
logger.info(f"{self.name} 连接正常")
|
|
152
|
+
else:
|
|
153
|
+
logger.error(f"{self.name} 连接测试未通过,可能存在配置错误")
|
|
154
|
+
return rsp.status_code == 200
|
|
155
|
+
|
|
156
|
+
# ---------------- 主对话函数 ----------------
|
|
157
|
+
def conversation_with_tool(self, messages=None, tool=False, images=None):
|
|
158
|
+
"""
|
|
159
|
+
进行对话,支持多模态输入(文本 + 图片)
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
messages: 文本消息
|
|
163
|
+
tool: 是否是工具调用后的继续对话
|
|
164
|
+
images: 图片列表,可以是 base64 字符串或图片 URL
|
|
165
|
+
"""
|
|
166
|
+
work_history = self.history
|
|
167
|
+
|
|
168
|
+
if messages:
|
|
169
|
+
# 如果有图片,构建多模态内容
|
|
170
|
+
if images:
|
|
171
|
+
content_list = [{"type": "text", "text": messages}]
|
|
172
|
+
for img in images:
|
|
173
|
+
if img.startswith("http"):
|
|
174
|
+
content_list.append({
|
|
175
|
+
"type": "image_url",
|
|
176
|
+
"image_url": {"url": img}
|
|
177
|
+
})
|
|
178
|
+
else:
|
|
179
|
+
# 假设是 base64
|
|
180
|
+
content_list.append({
|
|
181
|
+
"type": "image_url",
|
|
182
|
+
"image_url": {"url": f"data:image/png;base64,{img}"}
|
|
183
|
+
})
|
|
184
|
+
work_history.append({"role": "user", "content": content_list})
|
|
185
|
+
else:
|
|
186
|
+
work_history.append({"role": "user", "content": messages})
|
|
187
|
+
|
|
188
|
+
if self.fc_model:
|
|
189
|
+
# Function Calling 模式
|
|
190
|
+
tools_schema = tool_registry.get_all_tools_schema(self.uuid)
|
|
191
|
+
|
|
192
|
+
# 通过自动收集器添加内置工具(@builtin_tool 装饰)到 schema
|
|
193
|
+
for s in tool_registry.collect_builtin_tools(self):
|
|
194
|
+
tools_schema.append(s)
|
|
195
|
+
|
|
196
|
+
# 添加 Skills 到 Function Calling schema
|
|
197
|
+
try:
|
|
198
|
+
from .skill import skill_registry
|
|
199
|
+
tools_schema.extend(skill_registry.get_all_tool_schemas())
|
|
200
|
+
except ImportError:
|
|
201
|
+
pass
|
|
202
|
+
|
|
203
|
+
payload = {
|
|
204
|
+
"model": self.model_name,
|
|
205
|
+
"messages": work_history,
|
|
206
|
+
"stream": self.stream,
|
|
207
|
+
"tools": tools_schema,
|
|
208
|
+
"tool_choice": "auto"
|
|
209
|
+
}
|
|
210
|
+
if self.stream:
|
|
211
|
+
payload["stream_options"] = {"include_usage": True},
|
|
212
|
+
else:
|
|
213
|
+
# XML 模式(原有逻辑)
|
|
214
|
+
payload = {
|
|
215
|
+
"model": self.model_name,
|
|
216
|
+
"messages": work_history,
|
|
217
|
+
"stream": self.stream,
|
|
218
|
+
"stream_options": {"include_usage": True}
|
|
219
|
+
}
|
|
220
|
+
if self.stream:
|
|
221
|
+
payload["stream_options"] = {"include_usage": True},
|
|
222
|
+
|
|
223
|
+
rsp = requests.post(
|
|
224
|
+
self.api_provider,
|
|
225
|
+
headers={**self.headers,
|
|
226
|
+
"Accept-Charset": "utf-8",
|
|
227
|
+
"Accept": "text/event-stream"},
|
|
228
|
+
json=payload,
|
|
229
|
+
stream=self.stream
|
|
230
|
+
)
|
|
231
|
+
rsp.encoding = 'utf-8'
|
|
232
|
+
|
|
233
|
+
full_content = ""
|
|
234
|
+
self.stream_run = True
|
|
235
|
+
tool_calls_list = [] # 存储所有 tool_calls
|
|
236
|
+
|
|
237
|
+
if self.stream:
|
|
238
|
+
# 流式响应处理
|
|
239
|
+
for line in rsp.iter_lines(decode_unicode=True):
|
|
240
|
+
logger.trace(line)
|
|
241
|
+
if not line or not line.startswith('data: '):
|
|
242
|
+
continue
|
|
243
|
+
data = line[6:]
|
|
244
|
+
if data == '[DONE]':
|
|
245
|
+
break
|
|
246
|
+
try:
|
|
247
|
+
chunk = json.loads(data)
|
|
248
|
+
except json.JSONDecodeError:
|
|
249
|
+
continue
|
|
250
|
+
|
|
251
|
+
delta = (chunk.get('choices') or [{}])[0].get('delta') or {}
|
|
252
|
+
|
|
253
|
+
# Function Calling: 处理 tool_calls
|
|
254
|
+
if self.fc_model:
|
|
255
|
+
tool_calls = delta.get('tool_calls')
|
|
256
|
+
if tool_calls:
|
|
257
|
+
for call in tool_calls:
|
|
258
|
+
# 初始化或更新 tool_calls_list
|
|
259
|
+
if call.get('index') is not None:
|
|
260
|
+
idx = call['index']
|
|
261
|
+
if idx >= len(tool_calls_list):
|
|
262
|
+
tool_calls_list.append({
|
|
263
|
+
'id': call.get('id'),
|
|
264
|
+
'function': {
|
|
265
|
+
'name': call['function']['name'],
|
|
266
|
+
'arguments': call['function'].get('arguments') or ''
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
else:
|
|
270
|
+
if 'arguments' in call['function'] and call['function']['arguments'] is not None:
|
|
271
|
+
tool_calls_list[idx]['function']['arguments'] += call['function']['arguments']
|
|
272
|
+
continue
|
|
273
|
+
|
|
274
|
+
# 普通 content
|
|
275
|
+
content = delta.get('content', '')
|
|
276
|
+
if content:
|
|
277
|
+
full_content += content
|
|
278
|
+
self.pack(content, finish_task=False)
|
|
279
|
+
|
|
280
|
+
usage = chunk.get('usage')
|
|
281
|
+
if usage:
|
|
282
|
+
self.stream_run = False
|
|
283
|
+
self.pack(finish_task=True)
|
|
284
|
+
self.pack(f"\n本次请求用量:提示 {usage['prompt_tokens']} tokens,"
|
|
285
|
+
f"生成 {usage['completion_tokens']} tokens,"
|
|
286
|
+
f"总计 {usage['total_tokens']} tokens。", other=True)
|
|
287
|
+
else:
|
|
288
|
+
# 非流式响应处理
|
|
289
|
+
self.stream_run = False
|
|
290
|
+
try:
|
|
291
|
+
response_json = rsp.json()
|
|
292
|
+
logger.trace(response_json)
|
|
293
|
+
message = response_json['choices'][0]['message']
|
|
294
|
+
full_content = message.get('content', '')
|
|
295
|
+
|
|
296
|
+
# Function Calling: 处理 tool_calls
|
|
297
|
+
if self.fc_model:
|
|
298
|
+
tool_calls_list = message.get('tool_calls', [])
|
|
299
|
+
|
|
300
|
+
if full_content:
|
|
301
|
+
self.pack(full_content, finish_task=False)
|
|
302
|
+
usage = response_json.get('usage', {})
|
|
303
|
+
if usage:
|
|
304
|
+
self.pack(finish_task=True)
|
|
305
|
+
self.pack(f"\n本次请求用量:提示 {usage['prompt_tokens']} tokens,"
|
|
306
|
+
f"生成 {usage['completion_tokens']} tokens,"
|
|
307
|
+
f"总计 {usage['total_tokens']} tokens。", other=True)
|
|
308
|
+
except Exception as e:
|
|
309
|
+
logger.error(f"非流式响应处理错误: {e}")
|
|
310
|
+
full_content = rsp.text
|
|
311
|
+
|
|
312
|
+
logger.trace(f"AI 回复内容长度:{len(full_content)}")
|
|
313
|
+
|
|
314
|
+
# Function Calling: 执行工具调用
|
|
315
|
+
if self.fc_model and tool_calls_list:
|
|
316
|
+
logger.debug(f"发现 Function Calling 工具调用: {tool_calls_list}")
|
|
317
|
+
|
|
318
|
+
# 添加 assistant message with tool_calls
|
|
319
|
+
work_history.append({
|
|
320
|
+
"role": "assistant",
|
|
321
|
+
"content": None,
|
|
322
|
+
"tool_calls": tool_calls_list
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
tool_results = []
|
|
326
|
+
for tool_call in tool_calls_list:
|
|
327
|
+
tool_name = tool_call['function']['name']
|
|
328
|
+
tool_id = tool_call['id']
|
|
329
|
+
|
|
330
|
+
# 查找工具
|
|
331
|
+
tool_func = None
|
|
332
|
+
if tool_registry.check_permission(self.uuid, tool_name):
|
|
333
|
+
tool_info = tool_registry.get_tool_info(tool_name)
|
|
334
|
+
if tool_info is not None:
|
|
335
|
+
tool_func = tool_info['function']
|
|
336
|
+
|
|
337
|
+
if tool_func is None and hasattr(self, tool_name):
|
|
338
|
+
method = getattr(self, tool_name)
|
|
339
|
+
if callable(method):
|
|
340
|
+
tool_func = method
|
|
341
|
+
|
|
342
|
+
if tool_func is None:
|
|
343
|
+
error_msg = f"找不到工具 '{tool_name}'"
|
|
344
|
+
logger.warning(error_msg)
|
|
345
|
+
tool_results.append({
|
|
346
|
+
'tool_call_id': tool_id,
|
|
347
|
+
'name': tool_name,
|
|
348
|
+
'content': error_msg
|
|
349
|
+
})
|
|
350
|
+
continue
|
|
351
|
+
|
|
352
|
+
# 调用工具(解析 arguments 为 dict)
|
|
353
|
+
try:
|
|
354
|
+
args = json.loads(tool_call['function']['arguments'])
|
|
355
|
+
# 生成任务 ID
|
|
356
|
+
self.current_task_id = self._generate_task_id()
|
|
357
|
+
# 执行 before 钩子
|
|
358
|
+
self._execute_hooks('before', tool_name, args)
|
|
359
|
+
logger.debug(f"调用工具 {tool_name},参数: {args}")
|
|
360
|
+
self.pack(tool_name=tool_name, tool_parameter=args)
|
|
361
|
+
result = tool_func(**args)
|
|
362
|
+
|
|
363
|
+
# 执行 after 钩子
|
|
364
|
+
self._execute_hooks('after', tool_name, args, result)
|
|
365
|
+
# 打包工具返回值
|
|
366
|
+
self.pack(tool_result=result, tool_name=tool_name)
|
|
367
|
+
tool_results.append({
|
|
368
|
+
'tool_call_id': tool_id,
|
|
369
|
+
'name': tool_name,
|
|
370
|
+
'content': result
|
|
371
|
+
})
|
|
372
|
+
except Exception as e:
|
|
373
|
+
error_msg = f"执行工具 {tool_name} 时出错: {str(e)}"
|
|
374
|
+
logger.error(error_msg)
|
|
375
|
+
|
|
376
|
+
# 执行 error 钩子(如果 args 已定义)
|
|
377
|
+
if 'args' in locals():
|
|
378
|
+
self._execute_hooks('error', tool_name, args, error_msg)
|
|
379
|
+
tool_results.append({
|
|
380
|
+
'tool_call_id': tool_id,
|
|
381
|
+
'name': tool_name,
|
|
382
|
+
'content': error_msg
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
# 添加 tool responses 到历史
|
|
386
|
+
for result in tool_results:
|
|
387
|
+
work_history.append({
|
|
388
|
+
"role": "tool",
|
|
389
|
+
"tool_call_id": result['tool_call_id'],
|
|
390
|
+
"name": result['name'],
|
|
391
|
+
"content": result['content']
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
# 继续对话
|
|
395
|
+
logger.debug("工具执行完成,继续对话")
|
|
396
|
+
return self.conversation_with_tool(tool=True)
|
|
397
|
+
|
|
398
|
+
# XML 模式:提取并执行工具
|
|
399
|
+
xml_pattern = re.compile(r'<(\w+)>.*?</\1>', flags=re.S)
|
|
400
|
+
clean_pattern = re.compile(r'</?(out_text|thinking)>', flags=re.S)
|
|
401
|
+
clean_content = clean_pattern.sub('', full_content)
|
|
402
|
+
xml_blocks = [m.group(0) for m in xml_pattern.finditer(clean_content)]
|
|
403
|
+
|
|
404
|
+
tool_results = []
|
|
405
|
+
tool_names = []
|
|
406
|
+
for block in xml_blocks:
|
|
407
|
+
logger.debug(f"发现工具块:{block[:50]}...")
|
|
408
|
+
soup = BeautifulSoup(block, "xml")
|
|
409
|
+
root = soup.find()
|
|
410
|
+
if root is None:
|
|
411
|
+
raise ValueError("空 XML")
|
|
412
|
+
tool_name = root.name
|
|
413
|
+
|
|
414
|
+
# 优先级查找工具:1. 工具注册器 2. 类方法 3. 返回无工具
|
|
415
|
+
tool_func = None
|
|
416
|
+
tool_source = None
|
|
417
|
+
|
|
418
|
+
# 优先级1: 在工具注册器中查找
|
|
419
|
+
if tool_registry.check_permission(self.uuid, tool_name):
|
|
420
|
+
tool_info = tool_registry.get_tool_info(tool_name)
|
|
421
|
+
if tool_info is not None:
|
|
422
|
+
tool_func = tool_info['function']
|
|
423
|
+
tool_source = "工具注册器"
|
|
424
|
+
|
|
425
|
+
# 优先级2: 在类方法中查找
|
|
426
|
+
if tool_func is None and hasattr(self, tool_name):
|
|
427
|
+
method = getattr(self, tool_name)
|
|
428
|
+
if callable(method):
|
|
429
|
+
tool_func = method
|
|
430
|
+
tool_source = "类方法"
|
|
431
|
+
|
|
432
|
+
# 优先级3: 都没有找到
|
|
433
|
+
if tool_func is None:
|
|
434
|
+
available_tools = self.get_all_available_tools()
|
|
435
|
+
tool_error = f"工具错误:找不到工具 '{tool_name}'。"
|
|
436
|
+
if available_tools:
|
|
437
|
+
tool_error += f" 你可以使用以下工具:{', '.join(available_tools)}"
|
|
438
|
+
|
|
439
|
+
work_history.append({"role": "system", "content": tool_error})
|
|
440
|
+
tool_results.append({"error": tool_error})
|
|
441
|
+
logger.warning(f"工具 {tool_name} 未找到,可用工具: {available_tools}")
|
|
442
|
+
continue
|
|
443
|
+
|
|
444
|
+
logger.debug(f"从 {tool_source} 找到工具 {tool_name}")
|
|
445
|
+
print(block)
|
|
446
|
+
|
|
447
|
+
# 解析 XML 参数,与 Function Calling 模式统一
|
|
448
|
+
params = {}
|
|
449
|
+
for child in root.children:
|
|
450
|
+
if hasattr(child, 'name') and child.name:
|
|
451
|
+
params[child.name] = child.text
|
|
452
|
+
|
|
453
|
+
# 生成任务 ID
|
|
454
|
+
self.current_task_id = self._generate_task_id()
|
|
455
|
+
|
|
456
|
+
# 执行 before 钩子
|
|
457
|
+
self._execute_hooks('before', tool_name, params)
|
|
458
|
+
|
|
459
|
+
self.pack(tool_name=tool_name, tool_parameter=params)
|
|
460
|
+
|
|
461
|
+
# 执行工具:根据函数签名决定传递 dict 还是解包参数
|
|
462
|
+
import inspect
|
|
463
|
+
sig = inspect.signature(tool_func)
|
|
464
|
+
param_count = len([p for p in sig.parameters.values()
|
|
465
|
+
if p.default == inspect.Parameter.empty and p.kind not in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)])
|
|
466
|
+
|
|
467
|
+
# 如果函数接受 **kwargs 或单个参数,传递整个 dict
|
|
468
|
+
# 否则解包参数
|
|
469
|
+
has_kwargs = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values())
|
|
470
|
+
if has_kwargs or param_count == 0:
|
|
471
|
+
result = tool_func(**params) if params else tool_func()
|
|
472
|
+
elif param_count == 1 and len(params) == 1:
|
|
473
|
+
# 单个参数函数,传递单个值
|
|
474
|
+
result = tool_func(list(params.values())[0])
|
|
475
|
+
else:
|
|
476
|
+
# 多参数函数,尝试解包
|
|
477
|
+
try:
|
|
478
|
+
result = tool_func(**params)
|
|
479
|
+
except TypeError:
|
|
480
|
+
# 如果解包失败,回退到传递整个 XML 块(向后兼容)
|
|
481
|
+
result = tool_func(block)
|
|
482
|
+
|
|
483
|
+
# 执行 after 钩子(传入结果)
|
|
484
|
+
self._execute_hooks('after', tool_name, params, result)
|
|
485
|
+
|
|
486
|
+
# 打包工具返回值
|
|
487
|
+
self.pack(tool_result=result, tool_name=tool_name)
|
|
488
|
+
|
|
489
|
+
if not result:
|
|
490
|
+
result = f"no return for the tool{tool_name}"
|
|
491
|
+
|
|
492
|
+
tool_results.append(result)
|
|
493
|
+
tool_names.append(tool_name)
|
|
494
|
+
|
|
495
|
+
#如配置错误强制跳出避免堵塞
|
|
496
|
+
if "<attempt_completion>" in full_content:
|
|
497
|
+
self.pack("\n[系统] AI 已标记任务完成,程序退出。", tool_name="attempt_completion")
|
|
498
|
+
# sys.exit(0)
|
|
499
|
+
|
|
500
|
+
# 3. 若工具产生结果,继续对话
|
|
501
|
+
if tool_results:
|
|
502
|
+
logger.debug(f"工具执行成功,结果数:{len(tool_results)}")
|
|
503
|
+
n = 0
|
|
504
|
+
for i in tool_results:
|
|
505
|
+
try:
|
|
506
|
+
work_history.append({"role": "system", "content": f"{tool_names[n]} results: {i}"})
|
|
507
|
+
n += 1
|
|
508
|
+
except Exception:
|
|
509
|
+
break
|
|
510
|
+
logger.debug(f"对话历史长度:{len(self.history)}")
|
|
511
|
+
return self.conversation_with_tool(tool=True)
|
|
512
|
+
if tool:
|
|
513
|
+
logger.debug(f"返回对话历史最后一条,长度:{len(work_history[-1].get('content')) if work_history[-1].get('content') else 0}")
|
|
514
|
+
return work_history[-1].get("content")
|
|
515
|
+
return full_content
|
|
516
|
+
|
|
517
|
+
def pack(self, message=None, tool_model=False, tool_name=None, tool_parameter=None,
|
|
518
|
+
finish_task=False, other=False, tool_result=None):
|
|
519
|
+
"""
|
|
520
|
+
打包输出内容,包含 AI UUID、任务戳和时间戳
|
|
521
|
+
|
|
522
|
+
参数:
|
|
523
|
+
message: 普通消息内容
|
|
524
|
+
tool_model: 是否是工具模型调用
|
|
525
|
+
tool_name: 工具名称
|
|
526
|
+
tool_parameter: 工具参数
|
|
527
|
+
finish_task: 是否任务完成
|
|
528
|
+
other: 其他标记
|
|
529
|
+
tool_result: 工具执行结果
|
|
530
|
+
"""
|
|
531
|
+
content = {}
|
|
532
|
+
task_id = self.current_task_id or self._generate_task_id()
|
|
533
|
+
timestamp = self._get_timestamp()
|
|
534
|
+
|
|
535
|
+
if finish_task:
|
|
536
|
+
content = {
|
|
537
|
+
"task": True,
|
|
538
|
+
"task_id": task_id,
|
|
539
|
+
"timestamp": timestamp,
|
|
540
|
+
"ai_uuid": self.uuid,
|
|
541
|
+
"ai_name": self.name
|
|
542
|
+
}
|
|
543
|
+
elif tool_model:
|
|
544
|
+
content = {
|
|
545
|
+
"tool_name": tool_name,
|
|
546
|
+
"tool_parameter": tool_parameter,
|
|
547
|
+
"ai_uuid": self.uuid,
|
|
548
|
+
"ai_name": self.name,
|
|
549
|
+
"task_id": task_id,
|
|
550
|
+
"timestamp": timestamp,
|
|
551
|
+
"task": False
|
|
552
|
+
}
|
|
553
|
+
elif tool_result is not None:
|
|
554
|
+
# 工具返回值
|
|
555
|
+
content = {
|
|
556
|
+
"tool_result": tool_result,
|
|
557
|
+
"tool_name": tool_name,
|
|
558
|
+
"ai_uuid": self.uuid,
|
|
559
|
+
"ai_name": self.name,
|
|
560
|
+
"task_id": task_id,
|
|
561
|
+
"timestamp": timestamp,
|
|
562
|
+
"task": False
|
|
563
|
+
}
|
|
564
|
+
else:
|
|
565
|
+
content = {
|
|
566
|
+
"message": message,
|
|
567
|
+
"ai_uuid": self.uuid,
|
|
568
|
+
"ai_name": self.name,
|
|
569
|
+
"other": other,
|
|
570
|
+
"task_id": task_id,
|
|
571
|
+
"timestamp": timestamp,
|
|
572
|
+
"task": False
|
|
573
|
+
}
|
|
574
|
+
self.out(content)
|
|
575
|
+
|
|
576
|
+
def out(self, content):
|
|
577
|
+
if content.get("tool_name"):
|
|
578
|
+
print("调用工具:", content.get("tool_name"), "参数", content.get("tool_parameter"))
|
|
579
|
+
return
|
|
580
|
+
if not content.get("task"):
|
|
581
|
+
message = content.get("message")
|
|
582
|
+
if message is not None:
|
|
583
|
+
print(message, end="")
|
|
584
|
+
else:
|
|
585
|
+
print()
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
# ---------------- 内置工具(@builtin_tool 装饰器自动从签名推导 schema)----------------
|
|
589
|
+
|
|
590
|
+
@builtin_tool(
|
|
591
|
+
description="请求其他Agent帮助",
|
|
592
|
+
params={
|
|
593
|
+
"agent_id": "目标Agent的UUID或名称",
|
|
594
|
+
"message": "请求内容",
|
|
595
|
+
},
|
|
596
|
+
)
|
|
597
|
+
def ask_for_help(self, agent_id: str, message: str) -> str:
|
|
598
|
+
"""请求另一个 Agent 协助完成子任务,并把对方的回复作为工具返回值返回。"""
|
|
599
|
+
try:
|
|
600
|
+
from Dumplings import agent_list
|
|
601
|
+
target = agent_list.get(agent_id)
|
|
602
|
+
if target is None:
|
|
603
|
+
return f"未找到 Agent:{agent_id}"
|
|
604
|
+
reply = target.conversation_with_tool(message)
|
|
605
|
+
return str(reply)
|
|
606
|
+
except Exception as e:
|
|
607
|
+
logger.error(f"ask_for_help 失败:{e}")
|
|
608
|
+
return f"协助请求失败:{e}"
|
|
609
|
+
|
|
610
|
+
@builtin_tool(
|
|
611
|
+
description="列出所有可用的Agent及其UUID和名称",
|
|
612
|
+
)
|
|
613
|
+
def list_agents(self) -> str:
|
|
614
|
+
"""返回当前系统中所有已注册 Agent 的清单,便于发现协作对象。"""
|
|
615
|
+
from Dumplings import agent_list
|
|
616
|
+
|
|
617
|
+
unique_agents: dict = {}
|
|
618
|
+
for key, agent in agent_list.items():
|
|
619
|
+
uuid = getattr(agent, "uuid", None)
|
|
620
|
+
name = getattr(agent, "name", None)
|
|
621
|
+
desc = getattr(agent, "description", None)
|
|
622
|
+
if uuid and name and uuid not in unique_agents:
|
|
623
|
+
unique_agents[uuid] = {"uuid": uuid, "name": name, "description": desc}
|
|
624
|
+
|
|
625
|
+
if not unique_agents:
|
|
626
|
+
return "可用的Agent列表:(暂无)"
|
|
627
|
+
|
|
628
|
+
lines = []
|
|
629
|
+
for info in unique_agents.values():
|
|
630
|
+
lines.append(
|
|
631
|
+
f"- {info['name']} (UUID: {info['uuid']}) description: {info['description']}"
|
|
632
|
+
)
|
|
633
|
+
return "可用的Agent列表:" + "".join(lines)
|
|
634
|
+
|
|
635
|
+
@builtin_tool(
|
|
636
|
+
description="标记任务完成并退出",
|
|
637
|
+
params={"report_content": "汇报内容(可空)"},
|
|
638
|
+
)
|
|
639
|
+
def attempt_completion(self, report_content: str = "") -> str:
|
|
640
|
+
"""告知框架当前任务已结束,传入汇报内容;该工具返回值即为整个任务的对外输出。"""
|
|
641
|
+
return report_content if report_content else "任务已完成"
|
|
642
|
+
|
|
643
|
+
@builtin_tool(
|
|
644
|
+
description="重新加载你自己:重新拉取当前可用的工具列表和Skills,重置系统提示词。",
|
|
645
|
+
)
|
|
646
|
+
def reload(self) -> str:
|
|
647
|
+
"""重新构建内部状态(系统提示词 / 工具列表 / Skills),保留对话历史。"""
|
|
648
|
+
self.__init__(new_load=False)
|
|
649
|
+
return "successfully reloaded"
|
|
650
|
+
|
|
651
|
+
def get_all_available_tools(self) -> list:
|
|
652
|
+
tools: list[Any] = []
|
|
653
|
+
tools_info = tool_registry.get_all_tools_info(self.uuid)
|
|
654
|
+
if tools_info:
|
|
655
|
+
tools.extend(tools_info.keys())
|
|
656
|
+
for s in tool_registry.collect_builtin_tools(self):
|
|
657
|
+
tools.append(s["function"]["name"])
|
|
658
|
+
return tools
|
|
659
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# 1. 准备一个“双键”容器
|
|
2
|
+
agent_list = {} # {key1: cls, key2: cls}
|
|
3
|
+
|
|
4
|
+
def register_agent(uuid, name, description=None):
|
|
5
|
+
"""
|
|
6
|
+
类装饰器:把被装饰的类同时注册到两个字典键下。
|
|
7
|
+
两个键指向**同一个类对象**,因此内存中只有一份。
|
|
8
|
+
"""
|
|
9
|
+
def _decorator(cls):
|
|
10
|
+
cls.uuid = uuid
|
|
11
|
+
cls.name = name
|
|
12
|
+
cls.description = description
|
|
13
|
+
cls_instantiation = cls()
|
|
14
|
+
agent_list[uuid] = cls_instantiation
|
|
15
|
+
agent_list[name] = cls_instantiation
|
|
16
|
+
return cls # 原样返回,不影响类本身
|
|
17
|
+
return _decorator
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == '__main__':
|
|
21
|
+
# 2. 用法示例
|
|
22
|
+
@register_agent('foo', 'bar')
|
|
23
|
+
class Demo:
|
|
24
|
+
def __init__(self, name):
|
|
25
|
+
self.name = name
|
|
26
|
+
|
|
27
|
+
def greet(self):
|
|
28
|
+
return f'Hello {self.name}'
|
|
29
|
+
|
|
30
|
+
# 3. 通过任意键都能拿到类
|
|
31
|
+
Cls1 = agent_list['foo'] # 拿到 Demo
|
|
32
|
+
Cls2 = agent_list['bar'] # 拿到同一个 Demo
|
|
33
|
+
assert Cls1 is Cls2 # True
|
|
34
|
+
|
|
35
|
+
# 4. 像正常类一样用
|
|
36
|
+
obj = Cls1('world')
|
|
37
|
+
print(obj.greet()) # Hello world
|