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.
- {lightagent-0.3.2 → lightagent-0.4.0}/LightAgent/la_core.py +476 -467
- {lightagent-0.3.2 → lightagent-0.4.0}/PKG-INFO +310 -107
- {lightagent-0.3.2 → lightagent-0.4.0}/README.de.md +20 -9
- {lightagent-0.3.2 → lightagent-0.4.0}/README.es.md +21 -12
- {lightagent-0.3.2 → lightagent-0.4.0}/README.fr.md +20 -10
- {lightagent-0.3.2 → lightagent-0.4.0}/README.ja.md +21 -11
- {lightagent-0.3.2 → lightagent-0.4.0}/README.ko.md +18 -11
- {lightagent-0.3.2 → lightagent-0.4.0}/README.md +68 -18
- {lightagent-0.3.2 → lightagent-0.4.0}/README.pt.md +18 -9
- {lightagent-0.3.2 → lightagent-0.4.0}/README.ru.md +40 -11
- {lightagent-0.3.2 → lightagent-0.4.0}/README.zh-CN.md +79 -13
- {lightagent-0.3.2 → lightagent-0.4.0}/pyproject.toml +6 -4
- {lightagent-0.3.2 → lightagent-0.4.0}/LICENSE +0 -0
- {lightagent-0.3.2 → lightagent-0.4.0}/LightAgent/__init__.py +0 -0
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
"""
|
|
5
5
|
作者: [weego/WXAI-Team]
|
|
6
|
-
版本: 0.
|
|
7
|
-
最后更新: 2025-
|
|
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.
|
|
34
|
+
__version__ = "0.4.0" # 你可以根据需要设置版本号
|
|
40
35
|
|
|
41
36
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
136
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
210
|
-
|
|
232
|
+
file_handler = logging.FileHandler(log_file)
|
|
233
|
+
file_handler.setFormatter(formatter)
|
|
234
|
+
logger.addHandler(file_handler)
|
|
211
235
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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.
|
|
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
|
-
|
|
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(
|
|
246
|
-
|
|
247
|
-
self.session = await self.exit_stack.enter_async_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
|
-
|
|
258
|
-
self.session = await self.exit_stack.enter_async_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.
|
|
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
|
-
|
|
315
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
#
|
|
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
|
-
#
|
|
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 =
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
-
#
|
|
669
|
+
# 思维链处理
|
|
717
670
|
active_tools = []
|
|
718
671
|
if self.tree_of_thought:
|
|
719
|
-
tot_response, active_tools = self.run_thought(query
|
|
720
|
-
system_prompt
|
|
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
|
-
#
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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
|
-
#
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
909
|
-
|
|
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
|
|
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
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
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", {"
|
|
910
|
+
self.logger.log("INFO", "stream function_call", {"tool_call_start": tool_call_info})
|
|
926
911
|
# 将工具的调用信息推送给开发者
|
|
927
|
-
yield
|
|
912
|
+
yield tool_call_info
|
|
928
913
|
|
|
929
914
|
# 解析参数并调用工具
|
|
930
915
|
try:
|
|
931
916
|
# 使用正则表达式将多个 JSON 对象拆分开
|
|
932
|
-
json_objects = re.findall(r'\{.*?\}',
|
|
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
|
-
|
|
947
|
-
|
|
948
|
-
|
|
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":
|
|
962
|
-
"title":
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
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
|
-
|
|
1169
|
-
self.log("DEBUG", "
|
|
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":
|
|
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", "
|
|
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
|
-
|
|
1208
|
-
|
|
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
|
"""
|