LightAgent 0.3.2__tar.gz → 0.4.0__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.
@@ -3,8 +3,8 @@
3
3
 
4
4
  """
5
5
  作者: [weego/WXAI-Team]
6
- 版本: 0.3.2
7
- 最后更新: 2025-03-31
6
+ 版本: 0.4.0
7
+ 最后更新: 2025-06-12
8
8
  """
9
9
 
10
10
  import asyncio
@@ -21,40 +21,48 @@ from contextlib import AsyncExitStack
21
21
  from copy import deepcopy
22
22
  from datetime import datetime
23
23
  from functools import partial
24
- from typing import List, Dict, Any, Callable, Union, Optional, Generator, AsyncGenerator
24
+ from typing import List, Dict, Any, Callable, Union, Optional, Generator, AsyncGenerator, Protocol
25
+ from uuid import uuid4
25
26
 
26
27
  import httpx
27
28
  from mcp import ClientSession, StdioServerParameters
28
29
  from mcp.client.sse import sse_client
29
30
  from mcp.client.stdio import stdio_client
30
- from openai import OpenAI
31
31
  from openai.types.chat import ChatCompletionChunk
32
32
 
33
- # 全局工具注册表
34
- _FUNCTION_MAPPINGS = {} # 工具名称 -> 工具函数
35
- _FUNCTION_INFO = {} # 工具名称 -> 工具info信息
36
- _OPENAI_FUNCTION_SCHEMAS = [] # OpenAI 格式的工具描述
37
- _PROMPT_FUNCTION_SCHEMAS = [] # prompt 格式的工具描述
38
33
 
39
- __version__ = "0.3.2" # 你可以根据需要设置版本号
34
+ __version__ = "0.4.0" # 你可以根据需要设置版本号
40
35
 
41
36
 
42
- def register_tool_manually(tools: List[Union[str, Callable]]) -> bool:
43
- """
44
- 手动注册多个工具,从函数属性中提取工具信息
45
- :param tools: 工具函数列表
46
- """
47
- for func in tools:
37
+ # openai.langfuse_auth_check()
38
+
39
+ # 1. 定义内存接口协议
40
+ class MemoryProtocol(Protocol):
41
+ def store(self, data: str, user_id: str) -> Any:
42
+ ...
43
+
44
+ def retrieve(self, query: str, user_id: str) -> List[Any]:
45
+ ...
46
+
47
+ class ToolRegistry:
48
+ """集中管理工具注册表,避免全局变量"""
49
+
50
+ def __init__(self):
51
+ self.function_mappings = {} # 工具名称 -> 工具函数
52
+ self.function_info = {} # 工具名称 -> 工具info信息
53
+ self.openai_function_schemas = [] # OpenAI 格式的工具描述
54
+
55
+ def register_tool(self, func: Callable) -> bool:
56
+ """注册单个工具"""
48
57
  if not hasattr(func, "tool_info"):
49
- # raise ValueError(f"Function `{func.__name__}` does not have tool_info attribute.")
50
- continue
58
+ return False
51
59
 
52
60
  tool_info = func.tool_info
53
61
  tool_name = tool_info["tool_name"]
54
62
 
55
- # 注册到全局字典
56
- _FUNCTION_INFO[tool_name] = tool_info
57
- _FUNCTION_MAPPINGS[tool_name] = func # 注册工具
63
+ # 注册到字典
64
+ self.function_info[tool_name] = tool_info
65
+ self.function_mappings[tool_name] = func
58
66
 
59
67
  # 构建 OpenAI 格式的工具描述
60
68
  tool_params_openai = {}
@@ -80,171 +88,194 @@ def register_tool_manually(tools: List[Union[str, Callable]]) -> bool:
80
88
  }
81
89
  }
82
90
 
83
- _OPENAI_FUNCTION_SCHEMAS.append(tool_def_openai)
84
- return True
91
+ self.openai_function_schemas.append(tool_def_openai)
92
+ return True
85
93
 
94
+ def register_tools(self, tools: List[Callable]) -> bool:
95
+ """批量注册工具"""
96
+ success = True
97
+ for func in tools:
98
+ if not self.register_tool(func):
99
+ success = False
100
+ return success
86
101
 
87
- def load_tool(tool_name: str, tools_directory: str = "tools"):
88
- """
89
- 根据工具名称从 tools 目录中加载对应的工具函数
90
- """
91
- tool_path = os.path.join(tools_directory, f"{tool_name}.py")
92
- if not os.path.exists(tool_path):
93
- raise FileNotFoundError(f"Tool '{tool_name}' not found in {tools_directory}")
102
+ def get_tools(self) -> List[Dict[str, Any]]:
103
+ """获取所有工具的描述(OpenAI 格式)"""
104
+ return deepcopy(self.openai_function_schemas)
94
105
 
95
- # 动态加载模块
96
- spec = importlib.util.spec_from_file_location(tool_name, tool_path)
97
- module = importlib.util.module_from_spec(spec)
98
- spec.loader.exec_module(module)
106
+ def get_tools_str(self) -> str:
107
+ """将工具描述转换为格式化的 JSON 字符串"""
108
+ return json.dumps(self.openai_function_schemas, indent=4, ensure_ascii=False)
99
109
 
100
- # 获取工具函数
101
- if hasattr(module, tool_name):
102
- tool_func = getattr(module, tool_name)
103
- if callable(tool_func) and hasattr(tool_func, "tool_info"):
104
- return tool_func
105
- raise AttributeError(f"Tool '{tool_name}' is not properly defined in {tool_path}")
110
+ def filter_tools(self, tool_reflection_result: str) -> List[Dict]:
111
+ """根据内容过滤工具"""
112
+ try:
113
+ # 安全解析可能包含 Markdown 代码块的 JSON
114
+ refined_content = tool_reflection_result.strip()
115
+ if refined_content.startswith('```json') and refined_content.endswith('```'):
116
+ refined_content = refined_content[7:-3].strip()
106
117
 
118
+ parsed_data = json.loads(refined_content)
119
+ valid_tools = {tool["name"].strip().lower() for tool in parsed_data.get("tools", [])}
107
120
 
108
- async def dispatch_tool(tool_name: str, tool_params: Dict[str, Any]) -> Union[
109
- str, Generator[str, None, None], AsyncGenerator[str, None]]:
110
- """
111
- 调用工具执行,支持同步/异步工具及流式输出。
112
- """
113
- if tool_name not in _FUNCTION_MAPPINGS:
114
- return f"Tool `{tool_name}` not found."
121
+ return [
122
+ schema for schema in self.openai_function_schemas
123
+ if isinstance(schema, dict) and
124
+ schema.get("function", {}).get("name", "").strip().lower() in valid_tools
125
+ ]
126
+ except (json.JSONDecodeError, KeyError, AttributeError) as e:
127
+ raise ValueError(f"工具过滤失败: {str(e)}") from e
115
128
 
116
- tool_call = _FUNCTION_MAPPINGS[tool_name]
117
- try:
118
- # 处理不同类型的流式输出
119
- # 区分同步/异步工具
120
- if inspect.iscoroutinefunction(tool_call):
121
- # result = await tool_call(**tool_params)
122
- # 将参数以字典形式传递给包装器
123
- result = await tool_call(**tool_params) if inspect.iscoroutinefunction(tool_call) else tool_call(
124
- **tool_params)
125
- else:
126
- result = tool_call(**tool_params)
127
129
 
128
- # 处理不同类型的流式输出
129
- if inspect.isasyncgen(result):
130
- return async_stream_generator(result)
131
- elif inspect.isgenerator(result):
132
- return stream_generator(result)
133
- else:
130
+ class ToolLoader:
131
+ """工具加载器,支持动态加载和缓存"""
132
+
133
+ def __init__(self, tools_directory: str = "tools"):
134
+ self.tools_directory = tools_directory
135
+ self.loaded_tools = {}
136
+
137
+ def load_tool(self, tool_name: str) -> Callable:
138
+ """加载单个工具"""
139
+ if tool_name in self.loaded_tools:
140
+ return self.loaded_tools[tool_name]
141
+
142
+ tool_path = os.path.join(self.tools_directory, f"{tool_name}.py")
143
+ if not os.path.exists(tool_path):
144
+ raise FileNotFoundError(f"Tool '{tool_name}' not found in {tool_path}")
145
+
146
+ # 动态加载模块
147
+ spec = importlib.util.spec_from_file_location(tool_name, tool_path)
148
+ module = importlib.util.module_from_spec(spec)
149
+ spec.loader.exec_module(module)
150
+
151
+ # 获取工具函数
152
+ if hasattr(module, tool_name):
153
+ tool_func = getattr(module, tool_name)
154
+ if callable(tool_func) and hasattr(tool_func, "tool_info"):
155
+ self.loaded_tools[tool_name] = tool_func
156
+ return tool_func
157
+
158
+ raise AttributeError(f"Tool '{tool_name}' is not properly defined in {tool_path}")
159
+
160
+ def load_tools(self, tool_names: List[str]) -> Dict[str, Callable]:
161
+ """批量加载工具"""
162
+ for tool_name in tool_names:
163
+ if tool_name not in self.loaded_tools:
164
+ self.load_tool(tool_name)
165
+ return self.loaded_tools
166
+
167
+
168
+ class AsyncToolDispatcher:
169
+ """异步工具调度器"""
170
+
171
+ async def dispatch(self, tool_name: str, tool_params: Dict[str, Any]) -> Union[
172
+ str, Generator[str, None, None], AsyncGenerator[str, None]]:
173
+ """调用工具执行,支持同步/异步工具及流式输出"""
174
+ if tool_name not in self.function_mappings:
175
+ return f"Tool `{tool_name}` not found."
176
+
177
+ tool_call = self.function_mappings[tool_name]
178
+ try:
179
+ # 处理不同类型的工具
180
+ if inspect.iscoroutinefunction(tool_call):
181
+ result = await tool_call(**tool_params)
182
+ elif inspect.isasyncgenfunction(tool_call):
183
+ result = tool_call(**tool_params)
184
+ else:
185
+ result = tool_call(**tool_params)
186
+
187
+ # 处理流式输出
188
+ if inspect.isasyncgen(result):
189
+ return self.async_stream_generator(result)
190
+ elif inspect.isgenerator(result):
191
+ return self.stream_generator(result)
134
192
  return str(result)
135
- except Exception as e:
136
- return traceback.format_exc()
193
+ except Exception as e:
194
+ return f"Tool call error: {str(e)}\n{traceback.format_exc()}"
137
195
 
196
+ async def async_stream_generator(self, async_gen: AsyncGenerator) -> AsyncGenerator[str, None]:
197
+ async for chunk in async_gen:
198
+ yield chunk
138
199
 
139
- async def async_stream_generator(async_gen: AsyncGenerator) -> AsyncGenerator[str, None]:
140
- async for chunk in async_gen:
141
- yield chunk
200
+ def stream_generator(self, sync_gen: Generator) -> Generator[str, None, None]:
201
+ for chunk in sync_gen:
202
+ yield chunk
142
203
 
143
204
 
144
- def stream_generator(sync_gen: Generator) -> Generator[str, None, None]:
145
- for chunk in sync_gen:
146
- yield chunk
205
+ class LoggerManager:
206
+ """集中管理日志系统"""
147
207
 
208
+ def __init__(self, name: str, debug: bool, log_level: str, log_file: Optional[str] = None):
209
+ self.name = name
210
+ self.debug = debug
211
+ self.logger = self._setup_logger(log_level, log_file)
212
+ self.traceid = ""
148
213
 
149
- def dispatch_tool_old(tool_name: str, tool_params: Dict[str, Any]) -> str:
150
- """
151
- 调用工具执行
152
- """
153
- if tool_name not in _FUNCTION_MAPPINGS:
154
- return f"Tool `{tool_name}` not found."
214
+ def _setup_logger(self, log_level: str, log_file: Optional[str] = None) -> logging.Logger:
215
+ logger = logging.getLogger(self.name)
216
+ logger.setLevel(log_level.upper())
217
+ logger.propagate = False
155
218
 
156
- tool_call = _FUNCTION_MAPPINGS[tool_name]
157
- try:
158
- # print(f"Calling tool: {tool_name} with params: {tool_params}") # 调试信息
159
- return str(tool_call(**tool_params))
160
- except Exception as e:
161
- # print(f"Tool call failed: {e}") # 调试信息
162
- return traceback.format_exc()
163
-
164
-
165
- def get_tools() -> List[Dict[str, Any]]:
166
- """
167
- 获取所有工具的描述(OpenAI 格式)
168
- """
169
- return deepcopy(_OPENAI_FUNCTION_SCHEMAS)
170
-
171
-
172
- def get_tools_str() -> str:
173
- """
174
- 将 _OPENAI_FUNCTION_SCHEMAS 转换为格式化的 JSON 字符串。
175
- Returns:
176
- str: 格式化的 JSON 字符串。
177
- """
178
- # 使用 json.dumps 将字典转换为格式化的 JSON 字符串
179
- tools_str = json.dumps(_OPENAI_FUNCTION_SCHEMAS, indent=4, ensure_ascii=False)
180
- return tools_str
181
-
182
-
183
- def filter_tools_schemas(refined_content: str) -> json:
184
- """
185
- 根据refined_content中的工具列表过滤全局_OPENAI_FUNCTION_SCHEMAS
186
- :param refined_content: 包含工具列表的JSON字符串
187
- """
188
- # global _OPENAI_FUNCTION_SCHEMAS # 声明操作全局变量
219
+ formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
189
220
 
190
- try:
191
- # 解析工具列表
192
- parsed_data: Dict[str, List[Dict]] = json.loads(refined_content)
193
- valid_tools = {tool["name"].strip().lower() for tool in parsed_data.get("tools", [])}
194
-
195
- # 原地过滤操作
196
- filtered_schemas: List[Dict] = []
197
- for schema in _OPENAI_FUNCTION_SCHEMAS:
198
- if not isinstance(schema, dict):
199
- continue
221
+ if self.debug:
222
+ console_handler = logging.StreamHandler()
223
+ console_handler.setFormatter(formatter)
224
+ logger.addHandler(console_handler)
200
225
 
201
- # 深度检查结构
202
- function_info = schema.get("function", {})
203
- if isinstance(function_info, dict):
204
- schema_name = function_info.get("name", "").strip().lower()
205
- if schema_name in valid_tools:
206
- filtered_schemas.append(schema)
226
+ if log_file:
227
+ # 确保 log 目录存在
228
+ log_dir = os.path.dirname(log_file)
229
+ if log_dir and not os.path.exists(log_dir):
230
+ os.makedirs(log_dir)
207
231
 
208
- # 直接替换全局变量内容
209
- # _OPENAI_FUNCTION_SCHEMAS[:] = filtered_schemas
210
- return filtered_schemas
232
+ file_handler = logging.FileHandler(log_file)
233
+ file_handler.setFormatter(formatter)
234
+ logger.addHandler(file_handler)
211
235
 
212
- except (json.JSONDecodeError, KeyError, AttributeError) as e:
213
- # 错误处理:清空工具列表并记录日志
214
- # _OPENAI_FUNCTION_SCHEMAS.clear()
215
- raise ValueError(f"工具过滤失败: {str(e)}") from e
236
+ return logger
237
+
238
+ def log(self, level: str, action: str, data: Any):
239
+ """记录日志"""
240
+ if not self.debug:
241
+ return
242
+
243
+ trace_info = f"[TraceID: {self.traceid}] " if self.traceid else ""
244
+ log_message = f"{trace_info}{action}: {data}"
245
+
246
+ if level == "DEBUG":
247
+ self.logger.debug(log_message)
248
+ elif level == "INFO":
249
+ self.logger.info(log_message)
250
+ elif level == "ERROR":
251
+ self.logger.error(log_message)
252
+
253
+ def set_traceid(self, traceid: str):
254
+ """设置当前跟踪ID"""
255
+ self.traceid = traceid
216
256
 
217
257
 
218
258
  class MCPClientManager:
219
259
  """增强版MCP客户端管理器"""
220
260
 
221
- def __init__(self, config: dict):
261
+ def __init__(self, config: dict, tool_registry: ToolRegistry):
222
262
  self.config = config
263
+ self.tool_registry = tool_registry
223
264
  self.session: Optional[ClientSession] = None
224
265
  self.exit_stack = AsyncExitStack()
225
- self._streams_context = None
226
- self._session_context = None
227
- self.server_sessions = {} # 存储不同服务器的会话
228
-
229
- async def _call_tool_wrapper(self, tool_name: str, target_server: str, **kwargs):
230
- """参数转换适配器"""
231
- return await self.call_tool(
232
- tool_name=tool_name,
233
- arguments=kwargs,
234
- target_server=target_server
235
- )
266
+ self.server_sessions = {}
236
267
 
237
268
  async def _create_session(self, server_name: str, config: dict):
238
269
  """创建并管理会话上下文"""
239
270
  if 'url' in config:
240
271
  # SSE 服务器连接
241
- self._streams_context = sse_client(
272
+ streams_context = sse_client(
242
273
  url=config['url'],
243
274
  headers=config.get('headers', {})
244
275
  )
245
- streams = await self.exit_stack.enter_async_context(self._streams_context)
246
- self._session_context = ClientSession(*streams)
247
- self.session = await self.exit_stack.enter_async_context(self._session_context)
276
+ streams = await self.exit_stack.enter_async_context(streams_context)
277
+ session_context = ClientSession(*streams)
278
+ self.session = await self.exit_stack.enter_async_context(session_context)
248
279
  else:
249
280
  # 标准输入输出服务器连接
250
281
  server_params = StdioServerParameters(
@@ -254,23 +285,19 @@ class MCPClientManager:
254
285
  )
255
286
  transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
256
287
  stdio, write = transport
257
- self._session_context = ClientSession(stdio, write)
258
- self.session = await self.exit_stack.enter_async_context(self._session_context)
288
+ session_context = ClientSession(stdio, write)
289
+ self.session = await self.exit_stack.enter_async_context(session_context)
259
290
 
260
291
  await self.session.initialize()
261
292
  self.server_sessions[server_name] = self.session
262
293
 
263
294
  async def cleanup(self):
264
295
  """清理所有会话资源"""
265
- await self.exit_stack.__aexit__(None, None, None)
296
+ await self.exit_stack.aclose()
266
297
  self.server_sessions.clear()
267
298
 
268
299
  async def register_mcp_tool(self) -> bool:
269
- """
270
- 自动注册所有MCP服务的工具到全局字典
271
- :param config: MCP服务配置(与call_tool使用的相同配置)
272
- :return: 是否至少成功注册一个工具
273
- """
300
+ """自动注册所有MCP服务的工具"""
274
301
  registered_count = 0
275
302
  enabled_servers = [
276
303
  (name, config)
@@ -280,15 +307,10 @@ class MCPClientManager:
280
307
 
281
308
  for server_name, config in enabled_servers:
282
309
  try:
283
- # 创建会话连接
284
- # print(server_name,config)
285
310
  await self._create_session(server_name, config)
286
-
287
- # 获取工具列表
288
311
  tools_response = await self.session.list_tools()
289
- print(f"🔍 Registering tools for server : {server_name} ...")
312
+ print(f"🔍 Registering MCP tools for server : {server_name} ...")
290
313
 
291
- # 注册工具处理逻辑
292
314
  for tool in tools_response.tools:
293
315
  try:
294
316
  # 构建工具元数据
@@ -310,9 +332,9 @@ class MCPClientManager:
310
332
  "required": param_name in required_fields
311
333
  })
312
334
 
313
- # 注册到全局字典
314
- _FUNCTION_INFO[tool.name] = tool_info
315
- _FUNCTION_MAPPINGS[tool.name] = partial(
335
+ # 注册到工具注册表
336
+ self.tool_registry.function_info[tool.name] = tool_info
337
+ self.tool_registry.function_mappings[tool.name] = partial(
316
338
  self._call_tool_wrapper,
317
339
  tool_name=tool.name,
318
340
  target_server=server_name
@@ -334,31 +356,27 @@ class MCPClientManager:
334
356
  }
335
357
  }
336
358
  }
337
- _OPENAI_FUNCTION_SCHEMAS.append(openai_schema)
338
-
359
+ self.tool_registry.openai_function_schemas.append(openai_schema)
339
360
  registered_count += 1
340
- print(f"✅ The registered tool : {tool.name}")
341
-
361
+ print(f"✅ The registered MCP tool : {tool.name}")
342
362
  except Exception as e:
343
- print(f"⚠️ 工具 {tool.name} 注册失败: {str(e)}")
344
363
  continue
345
- print(f"🟢 {registered_count} tools have been registered")
346
-
347
364
  except Exception as e:
348
- print(f"🔴 服务器 {server_name} 连接失败: {str(e)}")
349
365
  continue
350
- # 清理
366
+
351
367
  await self.cleanup()
352
368
  return registered_count > 0
353
369
 
370
+ async def _call_tool_wrapper(self, tool_name: str, target_server: str, **kwargs):
371
+ """参数转换适配器"""
372
+ return await self.call_tool(
373
+ tool_name=tool_name,
374
+ arguments=kwargs,
375
+ target_server=target_server
376
+ )
377
+
354
378
  async def call_tool(self, tool_name: str, arguments: dict, target_server: str = None):
355
- """
356
- 通用工具调用方法
357
- :param tool_name: 要调用的工具名称
358
- :param arguments: 工具参数字典
359
- :param target_server: 指定服务器名称(可选)
360
- :return: 工具调用结果
361
- """
379
+ """通用工具调用方法"""
362
380
  enabled_servers = [
363
381
  (name, config)
364
382
  for name, config in self.config["mcpServers"].items()
@@ -370,15 +388,11 @@ class MCPClientManager:
370
388
 
371
389
  for server_name, config in enabled_servers:
372
390
  try:
373
- # 复用已建立的会话
374
391
  session = self.server_sessions.get(server_name)
375
- # print(111,server_name,session)
376
- # print(222,server_name,config)
377
392
  if not session:
378
393
  await self._create_session(server_name, config)
379
394
  session = self.session
380
395
 
381
- # 获取工具列表
382
396
  tools = await session.list_tools()
383
397
  available_tools = {t.name: t for t in tools.tools}
384
398
 
@@ -389,31 +403,26 @@ class MCPClientManager:
389
403
 
390
404
  # 执行调用
391
405
  result = await session.call_tool(tool_name, arguments)
392
- # print(f"mcp工具运行结果: {result.content[0].text}")
393
- # 调用完成清理session
394
406
  await self.cleanup()
395
407
  return {
396
408
  "server": server_name,
397
409
  "tool": tool_name,
398
410
  "result": result.content[0].text
399
411
  }
400
-
401
412
  except Exception as e:
402
- print(f"调用服务器 {server_name} 失败: {str(e)}")
403
413
  continue
404
414
 
405
415
  raise ValueError(f"工具 {tool_name} 在可用服务器中未找到")
406
416
 
407
417
  def _validate_arguments(self, arguments: dict, schema: dict):
408
- """简单参数校验(可选)"""
418
+ """简单参数校验"""
409
419
  required_fields = schema.get("required", [])
410
420
  for field in required_fields:
411
421
  if field not in arguments:
412
422
  raise ValueError(f"缺少必要参数: {field}")
413
423
 
414
-
415
424
  class LightAgent:
416
- __version__ = "0.3.2" # 将版本号放在类中
425
+ __version__ = "0.4.0" # 将版本号放在类中
417
426
 
418
427
  def __init__(
419
428
  self,
@@ -425,16 +434,18 @@ class LightAgent:
425
434
  api_key: str | None = None, # 模型 api key
426
435
  base_url: str | httpx.URL | None = None, # 模型 base url
427
436
  websocket_base_url: str | httpx.URL | None = None, # 模型 websocket base url
428
- memory=None, # 支持外部传入记忆模块
437
+ memory: Optional[MemoryProtocol] = None, # 支持外部传入记忆模块
429
438
  tree_of_thought: bool = False, # 是否启用链式思考
430
439
  tot_model: str | None = None, # 链式思考模型
431
440
  tot_api_key: str | None = None, # 链式思考模型API密钥
432
441
  tot_base_url: str | httpx.URL | None = None, # 链式思考模型base_url
442
+ filter_tools: bool = True, # 是否启用工具过滤
433
443
  self_learning: bool = False, # 是否启用agent自我学习
434
444
  tools: List[Union[str, Callable]] = None, # 支持工具混合输入
435
445
  debug: bool = False, # 是否启用调试模式
436
446
  log_level: str = "INFO", # 日志级别(INFO, DEBUG, ERROR)
437
- log_file: Optional[str] = None # 日志文件路径
447
+ log_file: Optional[str] = None, # 日志文件路径
448
+ tracetools: Optional[dict] = None, # log跟踪工具
438
449
  ) -> None:
439
450
  """
440
451
  初始化 LightAgent。
@@ -451,11 +462,20 @@ class LightAgent:
451
462
  :param tot_model: 使用的模型名称。
452
463
  :param tot_api_key: API 密钥。
453
464
  :param tot_base_url: API 的基础 URL。
465
+ :param filter_tools: 是否启用工具过滤。
454
466
  :param tools: 工具列表,支持函数名称(字符串)或函数对象。
455
467
  :param debug: 是否启用调试模式。
456
468
  :param log_level: 日志级别(INFO, DEBUG, ERROR)。
457
469
  :param log_file: 日志文件路径。
470
+ :param tracetools: log跟踪工具。
458
471
  """
472
+
473
+ # 初始化核心组件
474
+ self.tool_registry = ToolRegistry()
475
+ self.tool_loader = ToolLoader()
476
+ self.tool_dispatcher = AsyncToolDispatcher()
477
+ self.tool_dispatcher.function_mappings = self.tool_registry.function_mappings
478
+
459
479
  self.mcp_setting = None
460
480
  self.mcp_client = None
461
481
  if not model:
@@ -477,9 +497,11 @@ class LightAgent:
477
497
  self.memory = memory
478
498
  self.tree_of_thought = tree_of_thought
479
499
  self.self_learning = self_learning
500
+ self.filter_tools = filter_tools
480
501
 
481
502
  self.debug = debug
482
503
  self.log_level = log_level.upper()
504
+ self.traceid = "" # 用于存储 traceid
483
505
  # 确保 log 目录存在
484
506
  log_dir = 'logs'
485
507
  if not os.path.exists(log_dir):
@@ -488,7 +510,14 @@ class LightAgent:
488
510
  if debug:
489
511
  self.log_file = os.path.join(log_dir, log_file)
490
512
  # Set up the logger
491
- self.logger = self._setup_logger(log_level, self.log_file)
513
+ # 初始化日志系统
514
+ self.logger = LoggerManager(
515
+ name=self.name,
516
+ debug=debug,
517
+ log_level=log_level,
518
+ log_file=log_file
519
+ )
520
+
492
521
  if tools is None:
493
522
  self.tools = []
494
523
  if tools:
@@ -503,26 +532,53 @@ class LightAgent:
503
532
  )
504
533
  self.api_key = api_key
505
534
  self.websocket_base_url = websocket_base_url
535
+ self.base_url = base_url or os.environ.get("OPENAI_BASE_URL") or "https://api.openai.com/v1"
506
536
 
507
- if base_url is None:
508
- base_url = f"https://api.openai.com/v1"
509
-
510
- self.client = OpenAI(
511
- base_url=base_url,
512
- api_key=self.api_key
513
- )
514
537
  if self.tree_of_thought:
515
538
  if tot_api_key is None:
516
- tot_api_key = api_key
539
+ tot_api_key = self.api_key
517
540
  if tot_base_url is None:
518
- tot_base_url = base_url
541
+ tot_base_url = self.base_url
519
542
  if not tot_model:
520
543
  tot_model = "deepseek-r1" # 默认思维推理模型为deepseek-r1
521
544
  self.tot_model = tot_model
522
- self.tot_client = OpenAI(
523
- base_url=tot_base_url,
524
- api_key=tot_api_key
545
+
546
+ # 初始化客户端
547
+ self._initialize_clients(tracetools, tot_api_key, tot_base_url, tot_model)
548
+
549
+ def _initialize_clients(self, tracetools, tot_api_key, tot_base_url, tot_model):
550
+ """初始化 OpenAI 客户端"""
551
+ if tracetools:
552
+ from langfuse.openai import openai as la_openai
553
+ la_openai.langfuse_public_key = tracetools['TraceToolConfig']['langfuse_public_key']
554
+ la_openai.langfuse_secret_key = tracetools['TraceToolConfig']['langfuse_secret_key']
555
+ la_openai.langfuse_enabled = tracetools['TraceToolConfig']['langfuse_enabled']
556
+ la_openai.langfuse_host = tracetools['TraceToolConfig']['langfuse_host']
557
+ la_openai.base_url = self.base_url
558
+ la_openai.api_key = self.api_key
559
+ self.client = la_openai
560
+
561
+ if self.tree_of_thought:
562
+ la_openai.base_url = tot_base_url or self.base_url
563
+ la_openai.api_key = tot_api_key or self.api_key
564
+ self.tot_client = la_openai
565
+ else:
566
+ from openai import OpenAI as la_openai
567
+ self.client = la_openai(
568
+ base_url=self.base_url,
569
+ api_key=self.api_key
525
570
  )
571
+ if self.tree_of_thought:
572
+ self.tot_client = la_openai(
573
+ base_url=tot_base_url or self.base_url,
574
+ api_key=tot_api_key or self.api_key
575
+ )
576
+
577
+ def get_tools(self) -> List[Dict[str, Any]]:
578
+ """
579
+ 获取所有工具的描述(OpenAI 格式)
580
+ """
581
+ return deepcopy(self.tool_registry.get_tools())
526
582
 
527
583
  def get_tool(self, tool_name: str) -> Callable:
528
584
  """
@@ -534,108 +590,19 @@ class LightAgent:
534
590
  return self.loaded_tools[tool_name]
535
591
  raise ValueError(f"Tool `{tool_name}` is not loaded.")
536
592
 
537
- def get_tools(self) -> List[str]:
538
- """
539
- 用于外部可以获取已加载的工具函数列表
540
- :return: 工具函数
541
- """
542
- return list(_FUNCTION_MAPPINGS.keys())
543
-
544
593
  def load_tools(self, tool_names: List[Union[str, Callable]], tools_directory: str = "tools"):
545
- """
546
- 根据工具名称列表加载对应的工具函数,并注册到全局工具注册表中
547
- """
548
- for tool_name in tool_names:
549
- try:
550
- tool_func = load_tool(tool_name, tools_directory)
551
- # globals()[tool_name] = tool_func # 添加到全局命名空间
552
- self.loaded_tools[tool_name] = tool_func # 存储工具函数
553
- # print(f"Tool `{tool_name}` loaded successfully and added to _loaded_tools.") # 调试信息
554
-
555
- # 注册工具函数
556
- if hasattr(tool_func, "tool_info"):
557
- tool_info = tool_func.tool_info
558
- _FUNCTION_INFO[tool_name] = tool_info # 注册工具info信息
559
- _FUNCTION_MAPPINGS[tool_name] = tool_func
560
-
561
- # 构建 OpenAI 格式的工具描述
562
- tool_params_openai = {}
563
- tool_required = []
564
- for param in tool_info["tool_params"]:
565
- tool_params_openai[param["name"]] = {
566
- "type": param["type"],
567
- "description": param["description"],
568
- }
569
- if param["required"]:
570
- tool_required.append(param["name"])
571
-
572
- tool_def_openai = {
573
- "type": "function",
574
- "function": {
575
- "name": tool_name,
576
- "description": tool_info["tool_description"],
577
- "parameters": {
578
- "type": "object",
579
- "properties": tool_params_openai,
580
- "required": tool_required,
581
- },
582
- }
583
- }
584
- _OPENAI_FUNCTION_SCHEMAS.append(tool_def_openai)
585
-
586
- self.log("DEBUG", "load_tools success", {"tools": tool_name})
587
- except Exception as e:
588
- if register_tool_manually([tool_name]):
589
- self.log("DEBUG", "register_tool_manually success", {"tools": tool_name})
590
- else:
591
- self.log("DEBUG", "load_tools error", {"e": e})
592
-
593
- def _setup_logger(self, log_level: str, log_file: Optional[str] = None) -> logging.Logger:
594
- """
595
- 设置日志记录器。
596
-
597
- :param log_level: 日志级别(INFO, DEBUG, ERROR)。
598
- :param log_file: 日志文件路径。
599
- :return: 配置好的日志记录器。
600
- """
601
- logger = logging.getLogger(self.name)
602
- logger.setLevel(log_level.upper())
603
- logger.propagate = False # 禁用传播到根日志记录器
604
-
605
- formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
606
-
607
- # 输出到控制台
608
- # 仅在调试模式下输出到控制台
609
- if self.debug:
610
- console_handler = logging.StreamHandler()
611
- console_handler.setFormatter(formatter)
612
- logger.addHandler(console_handler)
613
-
614
- # 输出到文件(如果指定了文件路径)
615
- if log_file:
616
- file_handler = logging.FileHandler(log_file)
617
- file_handler.setFormatter(formatter)
618
- logger.addHandler(file_handler)
619
-
620
- return logger
621
-
622
- def log(self, level, action, data):
623
- """
624
- 记录日志。
625
-
626
- :param level: 日志级别(INFO, DEBUG, ERROR)。
627
- :param action: 日志动作(如 chat, call_tool, retrieve_memory)。
628
- :param data: 日志数据。
629
- """
630
- if not self.debug:
631
- return
632
- log_message = f"{action}: {data}"
633
- if level == "DEBUG":
634
- self.logger.debug(log_message)
635
- elif level == "INFO":
636
- self.logger.info(log_message)
637
- elif level == "ERROR":
638
- self.logger.error(log_message)
594
+ """加载并注册工具"""
595
+ for tool in tool_names:
596
+ if isinstance(tool, str):
597
+ try:
598
+ tool_func = self.tool_loader.load_tool(tool)
599
+ self.tool_registry.register_tool(tool_func)
600
+ self.logger.log("DEBUG", "load_tools", {"tool": tool, "status": "success"})
601
+ except Exception as e:
602
+ self.logger.log("ERROR", "load_tools", {"tool": tool, "error": str(e)})
603
+ elif callable(tool) and hasattr(tool, "tool_info"):
604
+ if self.tool_registry.register_tool(tool):
605
+ self.logger.log("DEBUG", "register_tool", {"tool": tool.__name__, "status": "success"})
639
606
 
640
607
  async def setup_mcp(
641
608
  self,
@@ -645,9 +612,9 @@ class LightAgent:
645
612
  self.mcp_setting = mcp_setting
646
613
  """单独初始化 MCP 模块"""
647
614
  if self.mcp_setting and not self.mcp_client:
648
- self.mcp_client = MCPClientManager(self.mcp_setting)
615
+ self.mcp_client = MCPClientManager(self.mcp_setting, self.tool_registry)
649
616
  await self.mcp_client.register_mcp_tool()
650
- self.log("INFO", "setup_mcp", "MCP 模块初始化成功")
617
+ self.logger.log("INFO", "setup_mcp", "MCP 模块初始化成功")
651
618
 
652
619
  def run(
653
620
  self,
@@ -671,85 +638,93 @@ class LightAgent:
671
638
  :param metadata: 元数据。
672
639
  :return: 代理的回复。
673
640
  """
674
- self.log("INFO", "run", {"query": query, "user_id": user_id, "light_swarm": light_swarm, "stream": stream})
675
- if history is None:
676
- history = []
677
- # 构建消息列表,先添加系统提示信息
678
- params = {}
679
-
680
- # 1. 判断是否需要转移任务
681
- # if light_swarm:
682
- # intent = self._detect_intent(query, light_swarm)
683
- # if intent and intent.get("transfer_to"):
684
- # target_agent_name = intent["transfer_to"]
685
- # self.log("INFO", "detect_intent", {"intent": intent})
686
- # print(light_swarm.agents[target_agent_name])
687
- # self._transfer_to_agent(light_swarm.agents[target_agent_name], query, stream=stream)
688
- # return # 立即结束当前生成器
689
-
690
- # 1. 判断是否需要转移任务
641
+ # 设置跟踪ID
642
+ traceid = uuid4().hex
643
+ self.logger.set_traceid(traceid)
644
+ self.logger.log("INFO", "run_start", {"query": query, "user_id": user_id, "stream": stream})
645
+
646
+ # 初始化历史记录
647
+ history = history or []
648
+
649
+ # 0. 判断是否需要转移任务
691
650
  if light_swarm:
692
651
  result = self._handle_task_transfer(query, light_swarm, stream)
693
652
  if result is not None:
694
653
  return result
695
654
 
696
- # 2. 正常处理任务
655
+ # 1. 正常处理任务
697
656
  now = datetime.now()
698
657
  current_date = now.strftime("%Y-%m-%d")
699
658
  current_time = now.strftime("%H:%M:%S")
700
- system_prompt = f"##代理名称:{self.name} ##代理指令 /n{self.instructions} ##身份 /n {self.role} /n 请一步一步思考来完成用户的要求。尽可能完成用户的回答,如果有补充信息,请参考补充信息来调用工具,直到获取所有满足用户的提问所需的答案。 /n 今日的日期: {current_date} 当前时间: {current_time}"
701
- params = dict(model=self.model, stream=stream)
702
- memory = ''
703
- # 3. 从记忆中检索相关内容&保存记忆
704
- if self.memory:
705
- related_memories = self.memory.retrieve(query=query, user_id=user_id)
706
- memory = memory + self._build_context(related_memories)
707
- self.memory.store(data=query, user_id=user_id)
708
- if self.self_learning:
709
- agent_memories = self.memory.retrieve(query=query, user_id=self.name)
710
- memory = memory + self._build_agent_memory(agent_memories)
711
- self.memory.store(data=query, user_id=self.name)
712
-
713
- query = f"{memory}\n##用户提问:\n{query}"
714
- # print(query)
659
+ system_prompt = (
660
+ f"##代理名称:{self.name}\n"
661
+ f"##代理指令:{self.instructions}\n"
662
+ f"##身份:{self.role}\n"
663
+ f"请一步一步思考来完成用户的要求。尽可能完成用户的回答,如果有补充信息,请参考补充信息来调用工具,直到获取所有满足用户的提问所需的答案。\n"
664
+ f"今日的日期: {current_date} 当前时间: {current_time}"
665
+ )
666
+ # 添加记忆上下文
667
+ query = self._add_memory_context(query, user_id)
715
668
 
716
- # 4. 思维链
669
+ # 思维链处理
717
670
  active_tools = []
718
671
  if self.tree_of_thought:
719
- tot_response, active_tools = self.run_thought(query=query)
720
- system_prompt = system_prompt + f" /n ##以下是问题的补充说明 /n {tot_response}"
721
- self.log("DEBUG", "tree_of_thought", {"response": tot_response, "active_tools": active_tools})
672
+ tot_response, active_tools = self.run_thought(query)
673
+ system_prompt += f"\n##以下是问题的补充说明\n{tot_response}"
674
+ self.logger.log("DEBUG", "tree_of_thought", {"response": tot_response, "active_tools": active_tools})
722
675
 
723
- # 5. 拼接tools工具
724
- # 带类型校验 自适应工具机制
725
- try:
726
- tools = active_tools if (
727
- len(active_tools) > 0
728
- ) else get_tools()
729
- except TypeError:
730
- tools = get_tools()
731
- # 带类型校验 自适应工具机制
732
- # tools = get_tools() # v0.2.X的工具选取机制
676
+ # 准备API参数
677
+ params = {
678
+ "model": self.model,
679
+ "messages": [{"role": "system", "content": system_prompt}] + history + [{"role": "user", "content": query}],
680
+ "stream": stream
681
+ }
682
+
683
+ # 添加工具
684
+ tools = active_tools or self.tool_registry.get_tools()
733
685
  if tools:
734
- self.log("DEBUG", "register_tools", {"tools": list(_FUNCTION_MAPPINGS.keys())})
735
- self.log("DEBUG", "active_tools", {"tools": tools})
736
686
  params["tools"] = tools
737
687
  params["tool_choice"] = "auto"
738
688
 
739
- # 6. 调用核心运行逻辑
740
- params["messages"] = [{"role": "system", "content": system_prompt}]
741
- # 将历史对话添加到消息列表中
742
- for item in history:
743
- params["messages"].append({"role": item["role"], "content": item["content"]})
744
- # 最后添加当前用户的查询信息
745
- params["messages"].append({"role": "user", "content": query})
689
+ # 添加跟踪会话
690
+ if hasattr(self, 'tracetools') and self.tracetools:
691
+ params["session_id"] = traceid
692
+
693
+ # 调用模型
746
694
  response = self.client.chat.completions.create(**params)
695
+ return self._core_run_logic(response, params, stream, max_retry)
696
+
697
+ def _add_memory_context(self, query: str, user_id: str) -> str:
698
+ """添加记忆上下文"""
699
+ if not self.memory:
700
+ return query
701
+
702
+ context = ""
703
+ related_memories = self.memory.retrieve(query=query, user_id=user_id)
704
+ if related_memories and related_memories.get("results"):
705
+ context += "\n##用户偏好\n用户之前提到了:\n" + "\n".join(
706
+ [m["memory"] for m in related_memories["results"]]
707
+ )
708
+ self.memory.store(data=query, user_id=user_id)
747
709
 
748
- result = self._core_run_logic(response, params, stream, max_retry)
710
+ if self.self_learning:
711
+ agent_memories = self.memory.retrieve(query=query, user_id=self.name)
712
+ if agent_memories and agent_memories.get("results"):
713
+ context += "\n##问题相关补充信息:\n" + "\n".join(
714
+ [m["memory"] for m in agent_memories["results"]]
715
+ )
716
+ self.memory.store(data=query, user_id=self.name)
749
717
 
750
- return result
718
+ return f"{context}\n##用户提问:\n{query}" if context else query
719
+
720
+ def _core_run_logic(self, response, params, stream, max_retry) -> Union[Generator[str, None, None], str]:
721
+ """核心运行逻辑"""
722
+ if stream:
723
+ return self._run_stream_logic(response, params, max_retry)
724
+ else:
725
+ return self._run_non_stream_logic(response, params, max_retry)
751
726
 
752
- def _run_logic_non_stream(self, response, params, max_retry) -> Union[str, None]:
727
+ def _run_non_stream_logic(self, response, params, max_retry) -> Union[str, None]:
753
728
  """
754
729
  非流式处理逻辑。
755
730
  """
@@ -761,7 +736,7 @@ class LightAgent:
761
736
  output = ""
762
737
  function_call_name = ""
763
738
  tool_calls = response.choices[0].message.tool_calls
764
- self.log("DEBUG", "non_stream tool_calls", {"tool_calls": tool_calls})
739
+ self.logger.log("DEBUG", "non_stream tool_calls", {"tool_calls": tool_calls})
765
740
 
766
741
  # 遍历所有工具调用
767
742
  for tool_call in tool_calls:
@@ -769,13 +744,13 @@ class LightAgent:
769
744
 
770
745
  # 尝试自动修复常见转义问题
771
746
  fixed_args = function_call.arguments.replace('\\"', '"').replace('\\\\', '\\')
772
- self.log("DEBUG", "non_stream function_call", {"function_call": fixed_args})
747
+ self.logger.log("DEBUG", "non_stream function_call", {"function_call": fixed_args})
773
748
 
774
749
  # 解析函数参数
775
750
  function_args = json.loads(fixed_args)
776
751
 
777
752
  # 调用工具并获取响应
778
- tool_response = asyncio.run(dispatch_tool(function_call.name, function_args))
753
+ tool_response = asyncio.run(self.tool_dispatcher.dispatch(function_call.name, function_args))
779
754
  function_call_name = function_call.name
780
755
  combined_response = ""
781
756
  single_tool_response = ""
@@ -818,13 +793,13 @@ class LightAgent:
818
793
  pass # 如果不是 JSON 字符串,保持原样
819
794
  single_tool_response = combined_response # 处理单个工具的方法
820
795
 
821
- self.log("INFO", "non_stream single_tool_response", {"single_tool_response": single_tool_response})
796
+ self.logger.log("INFO", "non_stream single_tool_response", {"single_tool_response": single_tool_response})
822
797
 
823
798
  # 将单个工具的响应结果添加到列表中
824
799
  tool_responses.append(single_tool_response)
825
800
 
826
801
  # 将所有工具调用的结果合并为一个字符串
827
- self.log("DEBUG", "non_stream tool_responses", {"tool_responses": tool_responses})
802
+ self.logger.log("DEBUG", "non_stream tool_responses", {"tool_responses": tool_responses})
828
803
 
829
804
  combined_tool_response = "\n".join(tool_responses)
830
805
 
@@ -844,14 +819,14 @@ class LightAgent:
844
819
  else:
845
820
  # 返回最终回复
846
821
  reply = response.choices[0].message.content
847
- self.log("INFO", "non_stream final_reply", {"reply": reply})
822
+ self.logger.log("INFO", "non_stream final_reply", {"reply": reply})
848
823
  return reply
849
824
 
850
825
  # 更新响应
851
826
  if function_call_name == 'finish':
852
827
  return # 如果最后调用了finish工具,则结束生成器
853
828
  # print("params:",params)
854
- self.log("DEBUG", "non_stream chat-completions params", {"params": params})
829
+ self.logger.log("DEBUG", "non_stream chat-completions params", {"params": params})
855
830
 
856
831
  try:
857
832
  response = self.client.chat.completions.create(**params)
@@ -859,20 +834,17 @@ class LightAgent:
859
834
  print(f"An error occurred: {e}")
860
835
 
861
836
  # 重试次数用尽
862
- self.log("ERROR", "max_retry_reached", {"message": "Failed to generate a valid response."})
837
+ self.logger.log("ERROR", "max_retry_reached", {"message": "Failed to generate a valid response."})
863
838
  return "Failed to generate a valid response."
864
839
 
865
- def _run_logic_stream(self, response, params, max_retry) -> Generator[str, None, None]:
866
- """
867
- 流式处理逻辑。
868
- """
840
+ def _run_stream_logic(self, response, params, max_retry) -> Generator[str, None, None]:
841
+ """流式处理逻辑"""
869
842
  for _ in range(max_retry):
870
843
  # 初始化变量
871
844
  output = ""
872
- function_call_name = ""
873
- function_call_arguments = ""
874
845
  tool_calls = [] # 用于存储所有工具调用的信息
875
846
  tool_responses = [] # 用于存储所有工具调用的结果
847
+ finish_called = False # 标记是否调用了finish工具
876
848
 
877
849
  for chunk in response:
878
850
  content = chunk.choices[0].delta.content or ""
@@ -891,7 +863,7 @@ class LightAgent:
891
863
 
892
864
  # 如果工具调用信息尚未记录,初始化一个空字典
893
865
  if len(tool_calls) <= tool_call_index:
894
- tool_calls.append({"name": "", "arguments": "", "index": tool_call_index})
866
+ tool_calls.append({"name": "", "arguments": "", "index": tool_call_index, "title": ""})
895
867
 
896
868
  # 更新工具调用的名称
897
869
  if hasattr(tool_call_delta.function, "name") and tool_call_delta.function.name:
@@ -901,35 +873,48 @@ class LightAgent:
901
873
  if hasattr(tool_call_delta.function, "arguments") and tool_call_delta.function.arguments:
902
874
  tool_calls[tool_call_index]["arguments"] += tool_call_delta.function.arguments
903
875
 
904
- except (IndexError, AttributeError, KeyError):
905
- pass
876
+ except (IndexError, AttributeError, KeyError) as e:
877
+ self.logger.log("ERROR", "tool_call_error", {
878
+ "error": str(e),
879
+ "traceback": traceback.format_exc()
880
+ })
906
881
 
907
882
  # 如果流式输出结束
908
- if chunk.choices[0].finish_reason == "stop" and not any(tool_call["name"] for tool_call in tool_calls):
909
- self.log("INFO", "stream_response", {"output": output})
883
+ finish_reason = chunk.choices[0].finish_reason if chunk.choices else None
884
+ if finish_reason == "stop" and not any(tc["name"] for tc in tool_calls):
885
+ self.logger.log("INFO", "stream_response", {"output": output})
910
886
  return # 结束生成器
911
887
 
912
888
  # 如果工具调用结束
913
- elif chunk.choices[0].finish_reason == "tool_calls" or (
914
- chunk.choices[0].finish_reason == "stop" and any(
915
- tool_call["name"] for tool_call in tool_calls)):
889
+ elif finish_reason in ("tool_calls", "stop") and any(tc["name"] for tc in tool_calls):
916
890
  # 遍历所有工具调用
917
- self.log("DEBUG", "stream tool_calls", {"tool_calls": tool_calls})
891
+ self.logger.log("DEBUG", "stream tool_calls", {"tool_calls": tool_calls})
918
892
  for tool_call in tool_calls:
919
893
  if tool_call["name"]: # 确保工具调用有名称
920
- function_call = {
921
- "name": tool_call["name"],
922
- "title": _FUNCTION_INFO.get(tool_call["name"], {}).get('tool_title') or '',
923
- "arguments": tool_call["arguments"],
894
+ tool_name = tool_call["name"]
895
+ arguments = tool_call["arguments"]
896
+
897
+ # 从注册表中获取工具标题
898
+ tool_info = self.tool_registry.function_info.get(tool_name, {})
899
+ tool_title = tool_info.get("tool_title") or ""
900
+
901
+ # 更新工具调用信息
902
+ tool_call["title"] = tool_title
903
+
904
+ # 记录调用工具
905
+ tool_call_info = {
906
+ "name": tool_name,
907
+ "title": tool_title,
908
+ "arguments": arguments,
924
909
  }
925
- self.log("INFO", "stream function_call", {"function_call": function_call})
910
+ self.logger.log("INFO", "stream function_call", {"tool_call_start": tool_call_info})
926
911
  # 将工具的调用信息推送给开发者
927
- yield function_call
912
+ yield tool_call_info
928
913
 
929
914
  # 解析参数并调用工具
930
915
  try:
931
916
  # 使用正则表达式将多个 JSON 对象拆分开
932
- json_objects = re.findall(r'\{.*?\}', function_call["arguments"])
917
+ json_objects = re.findall(r'\{.*?\}', tool_call_info["arguments"])
933
918
 
934
919
  # 解析每个 JSON 对象并调用工具
935
920
  # for json_obj in json_objects:
@@ -940,12 +925,15 @@ class LightAgent:
940
925
  for json_obj in json_objects:
941
926
  # 尝试自动修复常见转义问题
942
927
  fixed_args = json_obj.replace('\\"', '"').replace('\\\\', '\\')
943
- self.log("DEBUG", "stream fixed_args", {"fixed_args": fixed_args})
928
+ self.logger.log("DEBUG", "stream fixed_args", {"fixed_args": fixed_args})
944
929
 
930
+ # 解析参数
945
931
  function_args = json.loads(fixed_args)
946
- # tool_response = dispatch_tool(function_call["name"], function_args)
947
- tool_response = asyncio.run(dispatch_tool(function_call["name"], function_args))
948
- function_call_name = function_call["name"]
932
+
933
+ # 调用工具
934
+ tool_response = asyncio.run(self.tool_dispatcher.dispatch(tool_name, function_args))
935
+
936
+ # 处理不同类型的工具响应
949
937
  combined_response = ""
950
938
  single_tool_response = ""
951
939
 
@@ -958,15 +946,14 @@ class LightAgent:
958
946
  yield chunk
959
947
  else:
960
948
  tool_output = {
961
- "name": tool_call["name"],
962
- "title": _FUNCTION_INFO.get(tool_call["name"], {}).get(
963
- 'tool_title') or '',
949
+ "name": tool_name,
950
+ "title": tool_title,
964
951
  "output": chunk,
965
952
  }
966
- self.log("DEBUG", "stream tool_output", {"tool_output": tool_output})
953
+ self.logger.log("DEBUG", "stream tool_output", {"tool_output": tool_output})
967
954
  yield tool_output
968
955
  # 将工具的调用信息推送给开发者
969
- if function_call_name == 'finish':
956
+ if tool_name == 'finish':
970
957
  content = chunk.choices[0].delta.content or ""
971
958
  combined_response += content # 将每个 chunk 叠加
972
959
  else:
@@ -974,25 +961,54 @@ class LightAgent:
974
961
  single_tool_response = combined_response # 处理单个工具的方法
975
962
  else:
976
963
  # print(f"Non-streaming response from tool: {tool_response}")
977
- combined_response = tool_response
964
+ combined_response = str(tool_response)
978
965
  single_tool_response = combined_response # 处理单个工具的方法
979
- self.log("INFO", "stream single_tool_response",
966
+ tool_output = {
967
+ "name": tool_name,
968
+ "title": tool_title,
969
+ "output": combined_response
970
+ }
971
+ yield tool_output
972
+
973
+ # 记录工具响应
974
+ self.logger.log("INFO", "stream single_tool_response",
980
975
  {"single_tool_response": single_tool_response})
981
- # 将单个工具的响应结果添加到列表中
976
+
977
+ # 将单个工具的响应结果保存到列表中
982
978
  tool_responses.append(single_tool_response)
983
979
 
984
- except json.JSONDecodeError as e:
985
- self.log("ERROR", "json_decode_error",
986
- {"error": str(e), "arguments": function_call["arguments"]})
987
- continue
980
+ # 检查是否调用了finish工具
981
+ if tool_name == 'finish':
982
+ finish_called = True
983
+ self.logger.log("INFO", "finish_tool_called", {"response": combined_response})
988
984
 
989
- # 将所有工具调用的结果合并为一个字符串
985
+ except json.JSONDecodeError as e:
986
+ error_msg = f"JSON解析错误: {str(e)}\n参数: {arguments}"
987
+ self.logger.log("ERROR", "json_decode_error", {"tool": tool_name, "title": tool_title, "error": error_msg})
988
+ tool_responses.append(error_msg)
989
+ yield {"name": tool_name, "title": tool_title, "error": error_msg}
990
+
991
+ except Exception as e:
992
+ error_msg = f"工具调用错误: {str(e)}\n{traceback.format_exc()}"
993
+ self.logger.log("ERROR", "tool_execution_error", {
994
+ "tool": tool_name,
995
+ "title": tool_title,
996
+ "error": error_msg
997
+ })
998
+ tool_responses.append(error_msg)
999
+ yield {"name": tool_name, "title": tool_title, "error": error_msg}
1000
+
1001
+ # 如果调用了finish工具,则结束处理
1002
+ if finish_called:
1003
+ return
1004
+
1005
+ # 准备下一轮请求
990
1006
  combined_tool_response = "\n".join(tool_responses)
991
1007
  tool_str = json.dumps(
992
1008
  [{"name": tool_call["name"], "arguments": tool_call["arguments"]} for tool_call in tool_calls],
993
1009
  ensure_ascii=False)
994
1010
 
995
- # 将工具调用和响应添加到消息列表中
1011
+ # 添加工具调用和响应到消息历史
996
1012
  params["messages"].append(
997
1013
  {
998
1014
  "role": "assistant",
@@ -1006,26 +1022,15 @@ class LightAgent:
1006
1022
  }
1007
1023
  )
1008
1024
 
1025
+ # 创建新的响应流
1026
+ self.logger.log("DEBUG", "stream next_request_params", {"params": params})
1027
+ response = self.client.chat.completions.create(**params)
1009
1028
  break
1010
1029
 
1011
- # 更新响应
1012
- if function_call_name == 'finish':
1013
- return # 如果最后调用了finish工具,则结束生成器
1014
- self.log("DEBUG", "stream chat-completions params", {"params": params})
1015
- response = self.client.chat.completions.create(**params)
1016
-
1017
1030
  # 重试次数用尽
1018
- self.log("ERROR", "max_retry_reached", {"message": "Failed to generate a valid response."})
1031
+ self.logger.log("ERROR", "max_retry_reached", {"message": "Failed to generate a valid response."})
1019
1032
  yield "Failed to generate a valid response."
1020
1033
 
1021
- def _core_run_logic(self, response, params, stream, max_retry) -> Union[Generator[str, None, None], str]:
1022
- """
1023
- 核心运行逻辑。
1024
- """
1025
- if stream:
1026
- return self._run_logic_stream(response, params, max_retry)
1027
- else:
1028
- return self._run_logic_non_stream(response, params, max_retry)
1029
1034
 
1030
1035
  def _handle_task_transfer(
1031
1036
  self,
@@ -1044,9 +1049,9 @@ class LightAgent:
1044
1049
  intent = self._detect_intent(query, light_swarm)
1045
1050
  if intent and intent.get("transfer_to"):
1046
1051
  target_agent_name = intent["transfer_to"]
1047
- self.log("INFO", "detect_intent", {"intent": intent})
1052
+ self.logger.log("INFO", "detect_intent", {"intent": intent})
1048
1053
  if target_agent_name == self.name:
1049
- self.log("INFO", "self_transfer_detected", {"target_agent": target_agent_name})
1054
+ self.logger.log("INFO", "self_transfer_detected", {"target_agent": target_agent_name})
1050
1055
  return None # 如果是自身,直接返回 None
1051
1056
  if stream:
1052
1057
  return self._handle_task_transfer_stream(light_swarm.agents[target_agent_name], query, light_swarm)
@@ -1068,18 +1073,18 @@ class LightAgent:
1068
1073
  :param light_swarm: LightSwarm 实例。
1069
1074
  :return: 生成器,用于流式输出。
1070
1075
  """
1071
- self.log("INFO", "transfer_to_agent", {"from": self.name, "to": target_agent.name, "context": context})
1076
+ self.logger.log("INFO", "transfer_to_agent", {"from": self.name, "to": target_agent.name, "context": context})
1072
1077
 
1073
1078
  # 检查目标代理是否有效
1074
1079
  if not hasattr(target_agent, 'run'):
1075
- self.log("ERROR", "invalid_target_agent", {"target_agent": target_agent})
1080
+ self.logger.log("ERROR", "invalid_target_agent", {"target_agent": target_agent})
1076
1081
  yield "Failed to transfer task: invalid target agent"
1077
1082
  return
1078
1083
 
1079
1084
  try:
1080
1085
  yield from target_agent.run(context, light_swarm=light_swarm, stream=True)
1081
1086
  except Exception as e:
1082
- self.log("ERROR", "run_failed", {"error": str(e)})
1087
+ self.logger.log("ERROR", "run_failed", {"error": str(e)})
1083
1088
  raise # 重新抛出异常以便调试
1084
1089
 
1085
1090
  def _handle_task_transfer_non_stream(
@@ -1096,11 +1101,11 @@ class LightAgent:
1096
1101
  :param light_swarm: LightSwarm 实例。
1097
1102
  :return: 字符串,表示非流式输出结果。
1098
1103
  """
1099
- self.log("INFO", "transfer_to_agent", {"from": self.name, "to": target_agent.name, "context": context})
1104
+ self.logger.log("INFO", "transfer_to_agent", {"from": self.name, "to": target_agent.name, "context": context})
1100
1105
 
1101
1106
  # 检查目标代理是否有效
1102
1107
  if not hasattr(target_agent, 'run'):
1103
- self.log("ERROR", "invalid_target_agent", {"target_agent": target_agent})
1108
+ self.logger.log("ERROR", "invalid_target_agent", {"target_agent": target_agent})
1104
1109
  return "Failed to transfer task: invalid target agent"
1105
1110
 
1106
1111
  try:
@@ -1109,7 +1114,7 @@ class LightAgent:
1109
1114
  return "".join(result) # 将生成器转换为字符串
1110
1115
  return result
1111
1116
  except Exception as e:
1112
- self.log("ERROR", "run_failed", {"error": str(e)})
1117
+ self.logger.log("ERROR", "run_failed", {"error": str(e)})
1113
1118
  raise # 重新抛出异常以便调试
1114
1119
 
1115
1120
  def _build_context(self, related_memories):
@@ -1126,7 +1131,7 @@ class LightAgent:
1126
1131
  return ""
1127
1132
 
1128
1133
  prompt = f"\n##用户偏好 \n用户之前提到了\n{memory_context}。"
1129
- self.log("DEBUG", "related_memories", {"memory_context": memory_context})
1134
+ self.logger.log("DEBUG", "related_memories", {"memory_context": memory_context})
1130
1135
  return prompt
1131
1136
 
1132
1137
  def _build_agent_memory(self, agent_memories):
@@ -1144,41 +1149,41 @@ class LightAgent:
1144
1149
  return ""
1145
1150
 
1146
1151
  prompt = f"\n##以下是解决该问题的相关补充信息:\n{memory_context}。"
1147
- self.log("DEBUG", "agent_memories", {"memory_context": memory_context})
1152
+ self.logger.log("DEBUG", "agent_memories", {"memory_context": memory_context})
1148
1153
  return prompt
1149
1154
 
1150
1155
  def run_thought(self, query: str) -> tuple:
1151
1156
  """使用思维树的方式 让大模型先根据get_tools_str生成一个解答用户query的工具使用计划"""
1152
1157
  tot_model = self.tot_model # self.model
1153
- tools = get_tools_str()
1158
+ tools = self.tool_registry.get_tools_str()
1154
1159
  if not isinstance(tools, str):
1155
1160
  tools = str(tools) # 确保 tools 是字符串
1156
1161
  now = datetime.now()
1157
1162
  current_date = now.strftime("%Y-%m-%d")
1158
1163
  current_time = now.strftime("%H:%M:%S")
1159
1164
  system_prompt = f"""你是一个智能助手,请根据用户输入的问题,结合工具使用计划,生成一个思维树,并按照思维树依次调用工具步骤,最终生成一个最终回答。\n 今日的日期: {current_date} 当前时间: {current_time} \n 工具列表: {tools}"""
1160
- self.log("DEBUG", "run_thought", {"system_prompt": system_prompt})
1165
+ self.logger.log("DEBUG", "run_thought", {"system_prompt": system_prompt})
1161
1166
 
1162
1167
  try:
1163
- # 第一次请求,生成初始的工具使用计划
1168
+ # 1. 第一次请求,生成初始的工具使用计划
1164
1169
  params = dict(model=tot_model,
1165
1170
  messages=[{"role": "system", "content": system_prompt}, {"role": "user", "content": query}],
1166
1171
  stream=False)
1167
1172
  response = self.tot_client.chat.completions.create(**params)
1168
- initial_content = response.choices[0].message.content
1169
- self.log("DEBUG", "initial_response", {"response": initial_content})
1173
+ thought_response = response.choices[0].message.content
1174
+ self.logger.log("DEBUG", "thought_response", {"response": thought_response})
1170
1175
 
1171
- # 第二次请求,请求大模型反思并生成新的工具使用规划
1176
+ # 2. 第二次请求,请求大模型反思并生成新的工具使用规划
1172
1177
  reflection_prompt = "请反思你的回答,请严格按照<工具列表>中的工具来规划,不可以创造其他新的工具。请输出新的任务规划,不要输出其他分析和回答。"
1173
1178
  reflection_params = dict(model=tot_model, messages=[
1174
1179
  {"role": "user", "content": f"{system_prompt} /n 开始思考问题: {query}"},
1175
- {"role": "assistant", "content": initial_content},
1180
+ {"role": "assistant", "content": thought_response},
1176
1181
  {"role": "user", "content": reflection_prompt}
1177
1182
  ], stream=False)
1178
- self.log("DEBUG", "reflection_params", {"params": reflection_params})
1183
+ self.logger.log("DEBUG", "reflection_params", {"params": reflection_params})
1179
1184
  reflection_response = self.tot_client.chat.completions.create(**reflection_params)
1180
1185
  refined_content = reflection_response.choices[0].message.content
1181
- self.log("DEBUG", "refined_response", {"response": refined_content})
1186
+ self.logger.log("DEBUG", "reflection_response", {"response": refined_content})
1182
1187
 
1183
1188
  # 获取工具的使用集合
1184
1189
  tool_reflection_prompt = """请严格按以下要求执行:
@@ -1200,17 +1205,21 @@ class LightAgent:
1200
1205
  stream=False
1201
1206
  )
1202
1207
 
1203
- self.log("DEBUG", "tool_reflection_params", {"params": tool_reflection_params})
1208
+ self.logger.log("DEBUG", "tool_reflection_params", {"params": tool_reflection_params})
1204
1209
  tool_reflection_response = self.tot_client.chat.completions.create(**tool_reflection_params)
1205
1210
  tool_reflection_result = tool_reflection_response.choices[0].message.content
1206
- self.log("DEBUG", "tool_reflection_result", {"result": tool_reflection_result})
1207
- current_tools = filter_tools_schemas(tool_reflection_result)
1208
- self.log("DEBUG", "current_tools", {"get_tools": current_tools})
1211
+ self.logger.log("DEBUG", "tool_reflection_result", {"result": tool_reflection_result})
1212
+
1213
+ # 3.执行自适应工具过滤
1214
+ current_tools = []
1215
+ if self.filter_tools:
1216
+ current_tools = self.tool_registry.filter_tools(tool_reflection_result)
1217
+ self.logger.log("DEBUG", "current_tools", {"get_tools": current_tools})
1209
1218
 
1210
1219
  return refined_content, current_tools
1211
1220
 
1212
1221
  except Exception as e:
1213
- self.log("ERROR", "run_thought_failure", {"error": str(e)})
1222
+ self.logger.log("ERROR", "run_thought_failure", {"error": str(e)})
1214
1223
  raise RuntimeError(f"思维链执行失败: {str(e)}") from e
1215
1224
 
1216
1225
  def _detect_intent(self, query: str, light_swarm=None) -> Optional[Dict]:
@@ -1247,7 +1256,7 @@ class LightAgent:
1247
1256
  messages=[{"role": "system", "content": prompt}]
1248
1257
  )
1249
1258
  intent = response.choices[0].message.content
1250
- self.log("DEBUG", "detect_intent", {"intent": intent})
1259
+ self.logger.log("DEBUG", "detect_intent", {"intent": intent})
1251
1260
 
1252
1261
  # # 使用正则表达式解析意图
1253
1262
  # match = re.search(r"transfer to (\w+)", intent, re.IGNORECASE)
@@ -1279,11 +1288,11 @@ class LightAgent:
1279
1288
  :param stream: 是否启用流式输出。
1280
1289
  :return: 如果 stream=True,返回生成器;否则返回完整结果字符串。
1281
1290
  """
1282
- self.log("INFO", "transfer_to_agent", {"from": self.name, "to": target_agent.name, "context": context})
1291
+ self.logger.log("INFO", "transfer_to_agent", {"from": self.name, "to": target_agent.name, "context": context})
1283
1292
 
1284
1293
  # 检查目标代理是否有效
1285
1294
  if not hasattr(target_agent, 'run'):
1286
- self.log("ERROR", "invalid_target_agent", {"target_agent": target_agent})
1295
+ self.logger.log("ERROR", "invalid_target_agent", {"target_agent": target_agent})
1287
1296
  return "Failed to transfer task: invalid target agent"
1288
1297
  #
1289
1298
  # # 调用目标代理的 run 方法
@@ -1303,7 +1312,7 @@ class LightAgent:
1303
1312
  return "".join(result) # 将生成器转换为字符串
1304
1313
  return result
1305
1314
  except Exception as e:
1306
- self.log("ERROR", "run_failed", {"error": str(e)})
1315
+ self.logger.log("ERROR", "run_failed", {"error": str(e)})
1307
1316
  raise # 重新抛出异常以便调试
1308
1317
 
1309
1318
  def create_tool(self, user_input: str, tools_directory: str = "tools"):
@@ -1380,19 +1389,19 @@ get_weather.tool_info = {
1380
1389
  tool_code = tool_data.get("tool_code")
1381
1390
 
1382
1391
  if not tool_name or not tool_code:
1383
- self.log("ERROR", "invalid_tool_data", {"tool_data": tool_data})
1392
+ self.logger.log("ERROR", "invalid_tool_data", {"tool_data": tool_data})
1384
1393
  continue
1385
1394
 
1386
1395
  # 保存生成的代码到 tools 目录
1387
1396
  tool_path = os.path.join(tools_directory, f"{tool_name}.py")
1388
1397
  with open(tool_path, "w", encoding="utf-8") as f:
1389
1398
  f.write(tool_code)
1390
- self.log("INFO", "tool_created", {"tool_name": tool_name, "tool_path": tool_path})
1399
+ self.logger.log("INFO", "tool_created", {"tool_name": tool_name, "tool_path": tool_path})
1391
1400
 
1392
1401
  # 自动加载新创建的工具
1393
1402
  self.load_tools([tool_name], tools_directory)
1394
1403
  except Exception as e:
1395
- self.log("ERROR", "tool_creation_failed", {"error": str(e)})
1404
+ self.logger.log("ERROR", "tool_creation_failed", {"error": str(e)})
1396
1405
 
1397
1406
 
1398
1407
  class LightSwarm:
@@ -1408,11 +1417,11 @@ class LightSwarm:
1408
1417
  for agent in agents:
1409
1418
  if agent.name in self.agents:
1410
1419
  # print(f"Agent '{agent.name}' is already registered.")
1411
- agent.log("INFO", "register_agent", {"agent_name": agent.name, "status": "already_registered"})
1420
+ agent.logger.log("INFO", "register_agent", {"agent_name": agent.name, "status": "already_registered"})
1412
1421
  else:
1413
1422
  self.agents[agent.name] = agent
1414
1423
  # print(f"Agent '{agent.name}' registered.")
1415
- agent.log("INFO", "register_agent", {"agent_name": agent.name, "status": "registered"})
1424
+ agent.logger.log("INFO", "register_agent", {"agent_name": agent.name, "status": "registered"})
1416
1425
 
1417
1426
  def run(self, agent: LightAgent, query: str, stream=False):
1418
1427
  """