dumplingsAI 0.1.1__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.
@@ -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