LightAgent 0.2.85__tar.gz → 0.2.95__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.2.85 → lightagent-0.2.95}/LightAgent/la_core.py +304 -64
- {lightagent-0.2.85 → lightagent-0.2.95}/PKG-INFO +13 -5
- {lightagent-0.2.85 → lightagent-0.2.95}/README.md +4 -1
- {lightagent-0.2.85 → lightagent-0.2.95}/README.zh-CN.md +3 -2
- {lightagent-0.2.85 → lightagent-0.2.95}/pyproject.toml +6 -2
- {lightagent-0.2.85 → lightagent-0.2.95}/LICENSE +0 -0
- {lightagent-0.2.85 → lightagent-0.2.95}/LightAgent/__init__.py +0 -0
- {lightagent-0.2.85 → lightagent-0.2.95}/README.de.md +0 -0
- {lightagent-0.2.85 → lightagent-0.2.95}/README.es.md +0 -0
- {lightagent-0.2.85 → lightagent-0.2.95}/README.fr.md +0 -0
- {lightagent-0.2.85 → lightagent-0.2.95}/README.ja.md +0 -0
- {lightagent-0.2.85 → lightagent-0.2.95}/README.ko.md +0 -0
- {lightagent-0.2.85 → lightagent-0.2.95}/README.pt.md +0 -0
- {lightagent-0.2.85 → lightagent-0.2.95}/README.ru.md +0 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import re
|
|
2
|
-
import
|
|
3
|
-
from typing import List, Dict, Any, Callable, Union, Iterable, Optional, Generator
|
|
2
|
+
from typing import List, Dict, Any, Callable, Union, Iterable, Optional, Generator, AsyncGenerator
|
|
4
3
|
from copy import deepcopy
|
|
5
4
|
import importlib.util
|
|
6
5
|
import random
|
|
@@ -12,14 +11,24 @@ import os
|
|
|
12
11
|
import httpx
|
|
13
12
|
import importlib
|
|
14
13
|
from openai.types.chat import ChatCompletionChunk
|
|
14
|
+
import inspect
|
|
15
|
+
import traceback
|
|
16
|
+
from mcp import ClientSession, StdioServerParameters, types
|
|
17
|
+
from contextlib import AsyncExitStack
|
|
18
|
+
|
|
19
|
+
from mcp.client.sse import sse_client
|
|
20
|
+
from mcp.client.stdio import stdio_client
|
|
21
|
+
import asyncio
|
|
22
|
+
from functools import partial
|
|
23
|
+
|
|
15
24
|
|
|
16
25
|
# 全局工具注册表
|
|
17
26
|
_FUNCTION_MAPPINGS = {} # 工具名称 -> 工具函数
|
|
18
|
-
_FUNCTION_INFO = {}
|
|
27
|
+
_FUNCTION_INFO = {} # 工具名称 -> 工具info信息
|
|
19
28
|
_OPENAI_FUNCTION_SCHEMAS = [] # OpenAI 格式的工具描述
|
|
20
29
|
_PROMPT_FUNCTION_SCHEMAS = [] # prompt 格式的工具描述
|
|
21
30
|
|
|
22
|
-
__version__ = "0.
|
|
31
|
+
__version__ = "0.3.0" # 你可以根据需要设置版本号
|
|
23
32
|
|
|
24
33
|
|
|
25
34
|
def register_tool_manually(tools: List[Union[str, Callable]]) -> bool:
|
|
@@ -88,35 +97,44 @@ def load_tool(tool_name: str, tools_directory: str = "tools"):
|
|
|
88
97
|
raise AttributeError(f"Tool '{tool_name}' is not properly defined in {tool_path}")
|
|
89
98
|
|
|
90
99
|
|
|
91
|
-
def dispatch_tool(tool_name: str, tool_params: Dict[str, Any]) -> Union[
|
|
100
|
+
async def dispatch_tool(tool_name: str, tool_params: Dict[str, Any]) -> Union[
|
|
101
|
+
str, Generator[str, None, None], AsyncGenerator[str, None]]:
|
|
92
102
|
"""
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
:param tool_name: 工具名称
|
|
96
|
-
:param tool_params: 工具参数
|
|
97
|
-
:return: 如果工具是流式输出,返回生成器;否则返回字符串结果。
|
|
103
|
+
调用工具执行,支持同步/异步工具及流式输出。
|
|
98
104
|
"""
|
|
99
105
|
if tool_name not in _FUNCTION_MAPPINGS:
|
|
100
106
|
return f"Tool `{tool_name}` not found."
|
|
101
107
|
|
|
102
108
|
tool_call = _FUNCTION_MAPPINGS[tool_name]
|
|
103
109
|
try:
|
|
104
|
-
#
|
|
105
|
-
|
|
110
|
+
# 处理不同类型的流式输出
|
|
111
|
+
# 区分同步/异步工具
|
|
112
|
+
if inspect.iscoroutinefunction(tool_call):
|
|
113
|
+
# result = await tool_call(**tool_params)
|
|
114
|
+
# 将参数以字典形式传递给包装器
|
|
115
|
+
result = await tool_call(**tool_params) if inspect.iscoroutinefunction(tool_call) else tool_call(
|
|
116
|
+
**tool_params)
|
|
117
|
+
else:
|
|
118
|
+
result = tool_call(**tool_params)
|
|
106
119
|
|
|
107
|
-
#
|
|
108
|
-
if
|
|
120
|
+
# 处理不同类型的流式输出
|
|
121
|
+
if inspect.isasyncgen(result):
|
|
122
|
+
return async_stream_generator(result)
|
|
123
|
+
elif inspect.isgenerator(result):
|
|
109
124
|
return stream_generator(result)
|
|
110
|
-
# 否则,返回字符串结果
|
|
111
125
|
else:
|
|
112
126
|
return str(result)
|
|
113
127
|
except Exception as e:
|
|
114
|
-
# print(f"Tool call failed: {e}") # 调试信息
|
|
115
128
|
return traceback.format_exc()
|
|
116
129
|
|
|
117
130
|
|
|
118
|
-
def
|
|
119
|
-
for chunk in
|
|
131
|
+
async def async_stream_generator(async_gen: AsyncGenerator) -> AsyncGenerator[str, None]:
|
|
132
|
+
async for chunk in async_gen:
|
|
133
|
+
yield chunk
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def stream_generator(sync_gen: Generator) -> Generator[str, None, None]:
|
|
137
|
+
for chunk in sync_gen:
|
|
120
138
|
yield chunk
|
|
121
139
|
|
|
122
140
|
|
|
@@ -154,26 +172,223 @@ def get_tools_str() -> str:
|
|
|
154
172
|
return tools_str
|
|
155
173
|
|
|
156
174
|
|
|
175
|
+
class MCPClientManager:
|
|
176
|
+
"""增强版MCP客户端管理器"""
|
|
177
|
+
|
|
178
|
+
def __init__(self, config: dict):
|
|
179
|
+
self.config = config
|
|
180
|
+
self.session: Optional[ClientSession] = None
|
|
181
|
+
self.exit_stack = AsyncExitStack()
|
|
182
|
+
self._streams_context = None
|
|
183
|
+
self._session_context = None
|
|
184
|
+
self.server_sessions = {} # 存储不同服务器的会话
|
|
185
|
+
|
|
186
|
+
async def _call_tool_wrapper(self, tool_name: str, target_server: str, **kwargs):
|
|
187
|
+
"""参数转换适配器"""
|
|
188
|
+
return await self.call_tool(
|
|
189
|
+
tool_name=tool_name,
|
|
190
|
+
arguments=kwargs,
|
|
191
|
+
target_server=target_server
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
async def _create_session(self, server_name: str, config: dict):
|
|
195
|
+
"""创建并管理会话上下文"""
|
|
196
|
+
if 'url' in config:
|
|
197
|
+
# SSE 服务器连接
|
|
198
|
+
self._streams_context = sse_client(
|
|
199
|
+
url=config['url'],
|
|
200
|
+
headers=config.get('headers', {})
|
|
201
|
+
)
|
|
202
|
+
streams = await self.exit_stack.enter_async_context(self._streams_context)
|
|
203
|
+
self._session_context = ClientSession(*streams)
|
|
204
|
+
self.session = await self.exit_stack.enter_async_context(self._session_context)
|
|
205
|
+
else:
|
|
206
|
+
# 标准输入输出服务器连接
|
|
207
|
+
server_params = StdioServerParameters(
|
|
208
|
+
command=config["command"],
|
|
209
|
+
args=config["args"],
|
|
210
|
+
env=config.get("env")
|
|
211
|
+
)
|
|
212
|
+
transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
|
|
213
|
+
stdio, write = transport
|
|
214
|
+
self._session_context = ClientSession(stdio, write)
|
|
215
|
+
self.session = await self.exit_stack.enter_async_context(self._session_context)
|
|
216
|
+
|
|
217
|
+
await self.session.initialize()
|
|
218
|
+
self.server_sessions[server_name] = self.session
|
|
219
|
+
|
|
220
|
+
async def cleanup(self):
|
|
221
|
+
"""清理所有会话资源"""
|
|
222
|
+
await self.exit_stack.__aexit__(None, None, None)
|
|
223
|
+
self.server_sessions.clear()
|
|
224
|
+
|
|
225
|
+
async def register_mcp_tool(self) -> bool:
|
|
226
|
+
"""
|
|
227
|
+
自动注册所有MCP服务的工具到全局字典
|
|
228
|
+
:param config: MCP服务配置(与call_tool使用的相同配置)
|
|
229
|
+
:return: 是否至少成功注册一个工具
|
|
230
|
+
"""
|
|
231
|
+
registered_count = 0
|
|
232
|
+
enabled_servers = [
|
|
233
|
+
(name, config)
|
|
234
|
+
for name, config in self.config["mcpServers"].items()
|
|
235
|
+
if not config["disabled"]
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
for server_name, config in enabled_servers:
|
|
239
|
+
try:
|
|
240
|
+
# 创建会话连接
|
|
241
|
+
# print(server_name,config)
|
|
242
|
+
await self._create_session(server_name, config)
|
|
243
|
+
|
|
244
|
+
# 获取工具列表
|
|
245
|
+
tools_response = await self.session.list_tools()
|
|
246
|
+
print(f"🔍 Registering tools for server : {server_name} ...")
|
|
247
|
+
|
|
248
|
+
# 注册工具处理逻辑
|
|
249
|
+
for tool in tools_response.tools:
|
|
250
|
+
try:
|
|
251
|
+
# 构建工具元数据
|
|
252
|
+
tool_info = {
|
|
253
|
+
"tool_name": tool.name,
|
|
254
|
+
"tool_description": tool.description,
|
|
255
|
+
"tool_params": []
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
# 解析参数模式
|
|
259
|
+
properties = tool.inputSchema.get("properties", {})
|
|
260
|
+
required_fields = tool.inputSchema.get("required", [])
|
|
261
|
+
|
|
262
|
+
for param_name, param_schema in properties.items():
|
|
263
|
+
tool_info["tool_params"].append({
|
|
264
|
+
"name": param_name,
|
|
265
|
+
"type": param_schema.get("type", "string"),
|
|
266
|
+
"description": param_schema.get("title", ""),
|
|
267
|
+
"required": param_name in required_fields
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
# 注册到全局字典
|
|
271
|
+
_FUNCTION_INFO[tool.name] = tool_info
|
|
272
|
+
_FUNCTION_MAPPINGS[tool.name] = partial(
|
|
273
|
+
self._call_tool_wrapper,
|
|
274
|
+
tool_name=tool.name,
|
|
275
|
+
target_server=server_name
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# 构建OpenAI格式
|
|
279
|
+
openai_schema = {
|
|
280
|
+
"type": "function",
|
|
281
|
+
"function": {
|
|
282
|
+
"name": tool.name,
|
|
283
|
+
"description": tool.description,
|
|
284
|
+
"parameters": {
|
|
285
|
+
"type": "object",
|
|
286
|
+
"properties": {
|
|
287
|
+
k: {"type": v["type"], "description": v.get("title", "")}
|
|
288
|
+
for k, v in properties.items()
|
|
289
|
+
},
|
|
290
|
+
"required": required_fields
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
_OPENAI_FUNCTION_SCHEMAS.append(openai_schema)
|
|
295
|
+
|
|
296
|
+
registered_count += 1
|
|
297
|
+
print(f"✅ The registered tool : {tool.name}")
|
|
298
|
+
|
|
299
|
+
except Exception as e:
|
|
300
|
+
print(f"⚠️ 工具 {tool.name} 注册失败: {str(e)}")
|
|
301
|
+
continue
|
|
302
|
+
print(f"🟢 {registered_count} tools have been registered")
|
|
303
|
+
|
|
304
|
+
except Exception as e:
|
|
305
|
+
print(f"🔴 服务器 {server_name} 连接失败: {str(e)}")
|
|
306
|
+
continue
|
|
307
|
+
# 清理
|
|
308
|
+
await self.cleanup()
|
|
309
|
+
return registered_count > 0
|
|
310
|
+
|
|
311
|
+
async def call_tool(self, tool_name: str, arguments: dict, target_server: str = None):
|
|
312
|
+
"""
|
|
313
|
+
通用工具调用方法
|
|
314
|
+
:param tool_name: 要调用的工具名称
|
|
315
|
+
:param arguments: 工具参数字典
|
|
316
|
+
:param target_server: 指定服务器名称(可选)
|
|
317
|
+
:return: 工具调用结果
|
|
318
|
+
"""
|
|
319
|
+
enabled_servers = [
|
|
320
|
+
(name, config)
|
|
321
|
+
for name, config in self.config["mcpServers"].items()
|
|
322
|
+
if not config["disabled"]
|
|
323
|
+
]
|
|
324
|
+
|
|
325
|
+
if target_server:
|
|
326
|
+
enabled_servers = [s for s in enabled_servers if s[0] == target_server]
|
|
327
|
+
|
|
328
|
+
for server_name, config in enabled_servers:
|
|
329
|
+
try:
|
|
330
|
+
# 复用已建立的会话
|
|
331
|
+
session = self.server_sessions.get(server_name)
|
|
332
|
+
# print(111,server_name,session)
|
|
333
|
+
# print(222,server_name,config)
|
|
334
|
+
if not session:
|
|
335
|
+
await self._create_session(server_name, config)
|
|
336
|
+
session = self.session
|
|
337
|
+
|
|
338
|
+
# 获取工具列表
|
|
339
|
+
tools = await session.list_tools()
|
|
340
|
+
available_tools = {t.name: t for t in tools.tools}
|
|
341
|
+
|
|
342
|
+
if tool_name in available_tools:
|
|
343
|
+
# 验证参数类型
|
|
344
|
+
schema = available_tools[tool_name].inputSchema
|
|
345
|
+
self._validate_arguments(arguments, schema)
|
|
346
|
+
|
|
347
|
+
# 执行调用
|
|
348
|
+
result = await session.call_tool(tool_name, arguments)
|
|
349
|
+
# print(f"mcp工具运行结果: {result.content[0].text}")
|
|
350
|
+
# 调用完成清理session
|
|
351
|
+
await self.cleanup()
|
|
352
|
+
return {
|
|
353
|
+
"server": server_name,
|
|
354
|
+
"tool": tool_name,
|
|
355
|
+
"result": result.content[0].text
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
except Exception as e:
|
|
359
|
+
print(f"调用服务器 {server_name} 失败: {str(e)}")
|
|
360
|
+
continue
|
|
361
|
+
|
|
362
|
+
raise ValueError(f"工具 {tool_name} 在可用服务器中未找到")
|
|
363
|
+
|
|
364
|
+
def _validate_arguments(self, arguments: dict, schema: dict):
|
|
365
|
+
"""简单参数校验(可选)"""
|
|
366
|
+
required_fields = schema.get("required", [])
|
|
367
|
+
for field in required_fields:
|
|
368
|
+
if field not in arguments:
|
|
369
|
+
raise ValueError(f"缺少必要参数: {field}")
|
|
370
|
+
|
|
371
|
+
|
|
157
372
|
class LightAgent:
|
|
158
|
-
__version__ = "0.
|
|
373
|
+
__version__ = "0.3.0" # 将版本号放在类中
|
|
159
374
|
|
|
160
375
|
def __init__(
|
|
161
376
|
self,
|
|
162
377
|
*,
|
|
163
378
|
name: Optional[str] = None, # 代理名称
|
|
164
379
|
instructions: Optional[str] = None, # 代理指令
|
|
165
|
-
role: Optional[str] = None,
|
|
166
|
-
model: str,
|
|
167
|
-
api_key: str | None = None,
|
|
168
|
-
base_url: str | httpx.URL | None = None,
|
|
169
|
-
websocket_base_url: str | httpx.URL | None = None,
|
|
380
|
+
role: Optional[str] = None, # 代理角色
|
|
381
|
+
model: str, # agent模型名称
|
|
382
|
+
api_key: str | None = None, # 模型 api key
|
|
383
|
+
base_url: str | httpx.URL | None = None, # 模型 base url
|
|
384
|
+
websocket_base_url: str | httpx.URL | None = None, # 模型 websocket base url
|
|
170
385
|
memory=None, # 支持外部传入记忆模块
|
|
171
386
|
tree_of_thought: bool = False, # 是否启用链式思考
|
|
172
|
-
tot_model: str | None = None,
|
|
173
|
-
tot_api_key: str | None = None,
|
|
174
|
-
tot_base_url: str | httpx.URL | None = None,
|
|
387
|
+
tot_model: str | None = None, # 链式思考模型
|
|
388
|
+
tot_api_key: str | None = None, # 链式思考模型API密钥
|
|
389
|
+
tot_base_url: str | httpx.URL | None = None, # 链式思考模型base_url
|
|
175
390
|
self_learning: bool = False, # 是否启用agent自我学习
|
|
176
|
-
tools: List[Union[str, Callable]] = None, #
|
|
391
|
+
tools: List[Union[str, Callable]] = None, # 支持工具混合输入
|
|
177
392
|
debug: bool = False, # 是否启用调试模式
|
|
178
393
|
log_level: str = "INFO", # 日志级别(INFO, DEBUG, ERROR)
|
|
179
394
|
log_file: Optional[str] = None # 日志文件路径
|
|
@@ -198,6 +413,8 @@ class LightAgent:
|
|
|
198
413
|
:param log_level: 日志级别(INFO, DEBUG, ERROR)。
|
|
199
414
|
:param log_file: 日志文件路径。
|
|
200
415
|
"""
|
|
416
|
+
self.mcp_setting = None
|
|
417
|
+
self.mcp_client = None
|
|
201
418
|
if not model:
|
|
202
419
|
model = "gpt-4o-mini" # 默认模型
|
|
203
420
|
if not api_key:
|
|
@@ -206,7 +423,7 @@ class LightAgent:
|
|
|
206
423
|
base_url = os.environ.get("OPENAI_BASE_URL")
|
|
207
424
|
self.loaded_tools = {} # 用于存储已加载的工具函数
|
|
208
425
|
if not name:
|
|
209
|
-
random_suffix = random.randint(10000000, 99999999) # 生成一个8
|
|
426
|
+
random_suffix = random.randint(10000000, 99999999) # 生成一个8位随机数作为agent编号
|
|
210
427
|
name = f"LightAgent{random_suffix}"
|
|
211
428
|
self.name = name
|
|
212
429
|
if not instructions:
|
|
@@ -236,6 +453,8 @@ class LightAgent:
|
|
|
236
453
|
# 初始化工具列表
|
|
237
454
|
self.load_tools(tools)
|
|
238
455
|
# register_tool_manually(tools)
|
|
456
|
+
|
|
457
|
+
|
|
239
458
|
if api_key is None:
|
|
240
459
|
raise ValueError(
|
|
241
460
|
"The api_key client option must be set either by passing api_key to the client or by setting the OPENAI_API_KEY environment variable"
|
|
@@ -247,8 +466,8 @@ class LightAgent:
|
|
|
247
466
|
base_url = f"https://api.openai.com/v1"
|
|
248
467
|
|
|
249
468
|
self.client = OpenAI(
|
|
250
|
-
base_url
|
|
251
|
-
api_key
|
|
469
|
+
base_url=base_url,
|
|
470
|
+
api_key=self.api_key
|
|
252
471
|
)
|
|
253
472
|
if self.tree_of_thought:
|
|
254
473
|
if tot_api_key is None:
|
|
@@ -259,8 +478,8 @@ class LightAgent:
|
|
|
259
478
|
tot_model = "deepseek-r1" # 默认思维推理模型为deepseek-r1
|
|
260
479
|
self.tot_model = tot_model
|
|
261
480
|
self.tot_client = OpenAI(
|
|
262
|
-
base_url
|
|
263
|
-
api_key
|
|
481
|
+
base_url=tot_base_url,
|
|
482
|
+
api_key=tot_api_key
|
|
264
483
|
)
|
|
265
484
|
|
|
266
485
|
def get_tool(self, tool_name: str) -> Callable:
|
|
@@ -278,7 +497,6 @@ class LightAgent:
|
|
|
278
497
|
用于外部可以获取已加载的工具函数列表
|
|
279
498
|
:return: 工具函数
|
|
280
499
|
"""
|
|
281
|
-
|
|
282
500
|
return list(_FUNCTION_MAPPINGS.keys())
|
|
283
501
|
|
|
284
502
|
def load_tools(self, tool_names: List[Union[str, Callable]], tools_directory: str = "tools"):
|
|
@@ -377,6 +595,18 @@ class LightAgent:
|
|
|
377
595
|
elif level == "ERROR":
|
|
378
596
|
self.logger.error(log_message)
|
|
379
597
|
|
|
598
|
+
async def setup_mcp(
|
|
599
|
+
self,
|
|
600
|
+
mcp_setting: dict | None = None, # mcp 设置
|
|
601
|
+
):
|
|
602
|
+
if mcp_setting:
|
|
603
|
+
self.mcp_setting = mcp_setting
|
|
604
|
+
"""单独初始化 MCP 模块"""
|
|
605
|
+
if self.mcp_setting and not self.mcp_client:
|
|
606
|
+
self.mcp_client = MCPClientManager(self.mcp_setting)
|
|
607
|
+
await self.mcp_client.register_mcp_tool()
|
|
608
|
+
self.log("INFO", "setup_mcp", "MCP 模块初始化成功")
|
|
609
|
+
|
|
380
610
|
def run(
|
|
381
611
|
self,
|
|
382
612
|
query: str,
|
|
@@ -489,18 +719,18 @@ class LightAgent:
|
|
|
489
719
|
fixed_args = function_call.arguments.replace('\\"', '"').replace('\\\\', '\\')
|
|
490
720
|
self.log("DEBUG", "non_stream function_call", {"function_call": fixed_args})
|
|
491
721
|
|
|
492
|
-
function_args = json.loads(fixed_args)
|
|
493
722
|
# 解析函数参数
|
|
494
|
-
|
|
723
|
+
function_args = json.loads(fixed_args)
|
|
495
724
|
|
|
496
725
|
# 调用工具并获取响应
|
|
497
|
-
tool_response = dispatch_tool(function_call.name, function_args)
|
|
726
|
+
tool_response = asyncio.run(dispatch_tool(function_call.name, function_args))
|
|
498
727
|
function_call_name = function_call.name
|
|
499
728
|
combined_response = ""
|
|
729
|
+
single_tool_response = ""
|
|
500
730
|
|
|
501
731
|
# 如果工具返回的是生成器(流式输出),则将所有 chunk 叠加
|
|
502
732
|
if isinstance(tool_response, Generator):
|
|
503
|
-
print(f"Streaming response from tool: {function_call.name}")
|
|
733
|
+
# print(f"Streaming response from tool: {function_call.name}")
|
|
504
734
|
for chunk in tool_response:
|
|
505
735
|
# print("Received chunk:", chunk) # 打印每个 chunk
|
|
506
736
|
if function_call_name == 'finish':
|
|
@@ -520,8 +750,8 @@ class LightAgent:
|
|
|
520
750
|
# 将 JSON 对象中的 Unicode 编码转换为中文字符
|
|
521
751
|
if isinstance(combined_response, dict):
|
|
522
752
|
combined_response = json.dumps(combined_response, ensure_ascii=False) # 确保中文字符不转义
|
|
753
|
+
single_tool_response = combined_response # 处理单个工具的方法
|
|
523
754
|
|
|
524
|
-
tool_responses.append(combined_response) # 将叠加后的完整响应添加到列表
|
|
525
755
|
else:
|
|
526
756
|
# print(f"Non-streaming response from tool: {function_call.name}")
|
|
527
757
|
combined_response = tool_response
|
|
@@ -534,22 +764,23 @@ class LightAgent:
|
|
|
534
764
|
except json.JSONDecodeError:
|
|
535
765
|
combined_response = tool_response
|
|
536
766
|
pass # 如果不是 JSON 字符串,保持原样
|
|
537
|
-
|
|
767
|
+
single_tool_response = combined_response # 处理单个工具的方法
|
|
538
768
|
|
|
539
|
-
self.log("INFO", "non_stream
|
|
769
|
+
self.log("INFO", "non_stream single_tool_response", {"single_tool_response": single_tool_response})
|
|
540
770
|
|
|
541
|
-
#
|
|
542
|
-
tool_responses.append(
|
|
771
|
+
# 将单个工具的响应结果添加到列表中
|
|
772
|
+
tool_responses.append(single_tool_response)
|
|
543
773
|
|
|
544
774
|
# 将所有工具调用的结果合并为一个字符串
|
|
775
|
+
self.log("DEBUG", "non_stream tool_responses", {"tool_responses": tool_responses})
|
|
776
|
+
|
|
545
777
|
combined_tool_response = "\n".join(tool_responses)
|
|
546
778
|
|
|
547
779
|
# 将工具调用和响应添加到消息列表中
|
|
548
780
|
params["messages"].append(
|
|
549
781
|
{
|
|
550
782
|
"role": "assistant",
|
|
551
|
-
"content": json.dumps(
|
|
552
|
-
[tool_call.function.model_dump() for tool_call in tool_calls]),
|
|
783
|
+
"content": f"使用工具: \n {json.dumps([tool_call.function.model_dump() for tool_call in tool_calls], ensure_ascii=False)}\n",
|
|
553
784
|
}
|
|
554
785
|
)
|
|
555
786
|
params["messages"].append(
|
|
@@ -561,14 +792,14 @@ class LightAgent:
|
|
|
561
792
|
else:
|
|
562
793
|
# 返回最终回复
|
|
563
794
|
reply = response.choices[0].message.content
|
|
564
|
-
self.log("INFO", "final_reply", {"reply": reply})
|
|
795
|
+
self.log("INFO", "non_stream final_reply", {"reply": reply})
|
|
565
796
|
return reply
|
|
566
797
|
|
|
567
798
|
# 更新响应
|
|
568
799
|
if function_call_name == 'finish':
|
|
569
800
|
return # 如果最后调用了finish工具,则结束生成器
|
|
570
801
|
# print("params:",params)
|
|
571
|
-
self.log("DEBUG", "chat-completions params", {"params": params})
|
|
802
|
+
self.log("DEBUG", "non_stream chat-completions params", {"params": params})
|
|
572
803
|
|
|
573
804
|
try:
|
|
574
805
|
response = self.client.chat.completions.create(**params)
|
|
@@ -631,7 +862,7 @@ class LightAgent:
|
|
|
631
862
|
chunk.choices[0].finish_reason == "stop" and any(
|
|
632
863
|
tool_call["name"] for tool_call in tool_calls)):
|
|
633
864
|
# 遍历所有工具调用
|
|
634
|
-
self.log("DEBUG", "tool_calls", {"tool_calls": tool_calls})
|
|
865
|
+
self.log("DEBUG", "stream tool_calls", {"tool_calls": tool_calls})
|
|
635
866
|
for tool_call in tool_calls:
|
|
636
867
|
if tool_call["name"]: # 确保工具调用有名称
|
|
637
868
|
function_call = {
|
|
@@ -639,7 +870,7 @@ class LightAgent:
|
|
|
639
870
|
"title": _FUNCTION_INFO.get(tool_call["name"], {}).get('tool_title') or '',
|
|
640
871
|
"arguments": tool_call["arguments"],
|
|
641
872
|
}
|
|
642
|
-
self.log("INFO", "
|
|
873
|
+
self.log("INFO", "stream function_call", {"function_call": function_call})
|
|
643
874
|
# 将工具的调用信息推送给开发者
|
|
644
875
|
yield function_call
|
|
645
876
|
|
|
@@ -655,14 +886,20 @@ class LightAgent:
|
|
|
655
886
|
# tool_responses.append(tool_response)
|
|
656
887
|
|
|
657
888
|
for json_obj in json_objects:
|
|
658
|
-
|
|
659
|
-
|
|
889
|
+
# 尝试自动修复常见转义问题
|
|
890
|
+
fixed_args = json_obj.replace('\\"', '"').replace('\\\\', '\\')
|
|
891
|
+
self.log("DEBUG", "stream fixed_args", {"fixed_args": fixed_args})
|
|
892
|
+
|
|
893
|
+
function_args = json.loads(fixed_args)
|
|
894
|
+
# tool_response = dispatch_tool(function_call["name"], function_args)
|
|
895
|
+
tool_response = asyncio.run(dispatch_tool(function_call["name"], function_args))
|
|
660
896
|
function_call_name = function_call["name"]
|
|
897
|
+
combined_response = ""
|
|
898
|
+
single_tool_response = ""
|
|
661
899
|
|
|
662
900
|
# 如果工具返回的是生成器(流式输出),则将所有 chunk 叠加
|
|
663
901
|
if isinstance(tool_response, Generator):
|
|
664
902
|
# print(f"Streaming response from tool: {function_call['name']}")
|
|
665
|
-
combined_response = ""
|
|
666
903
|
for chunk in tool_response:
|
|
667
904
|
# 将工具返回的数据继续流出
|
|
668
905
|
if isinstance(chunk, ChatCompletionChunk):
|
|
@@ -674,7 +911,7 @@ class LightAgent:
|
|
|
674
911
|
'tool_title') or '',
|
|
675
912
|
"output": chunk,
|
|
676
913
|
}
|
|
677
|
-
self.log("
|
|
914
|
+
self.log("DEBUG", "stream tool_output", {"tool_output": tool_output})
|
|
678
915
|
yield tool_output
|
|
679
916
|
# 将工具的调用信息推送给开发者
|
|
680
917
|
if function_call_name == 'finish':
|
|
@@ -682,13 +919,15 @@ class LightAgent:
|
|
|
682
919
|
combined_response += content # 将每个 chunk 叠加
|
|
683
920
|
else:
|
|
684
921
|
combined_response += chunk # 将每个 chunk 叠加
|
|
685
|
-
|
|
922
|
+
single_tool_response = combined_response # 处理单个工具的方法
|
|
686
923
|
else:
|
|
687
924
|
# print(f"Non-streaming response from tool: {tool_response}")
|
|
688
925
|
combined_response = tool_response
|
|
689
|
-
|
|
690
|
-
self.log("INFO", "stream
|
|
691
|
-
|
|
926
|
+
single_tool_response = combined_response # 处理单个工具的方法
|
|
927
|
+
self.log("INFO", "stream single_tool_response",
|
|
928
|
+
{"single_tool_response": single_tool_response})
|
|
929
|
+
# 将单个工具的响应结果添加到列表中
|
|
930
|
+
tool_responses.append(single_tool_response)
|
|
692
931
|
|
|
693
932
|
except json.JSONDecodeError as e:
|
|
694
933
|
self.log("ERROR", "json_decode_error",
|
|
@@ -697,15 +936,15 @@ class LightAgent:
|
|
|
697
936
|
|
|
698
937
|
# 将所有工具调用的结果合并为一个字符串
|
|
699
938
|
combined_tool_response = "\n".join(tool_responses)
|
|
939
|
+
tool_str = json.dumps(
|
|
940
|
+
[{"name": tool_call["name"], "arguments": tool_call["arguments"]} for tool_call in tool_calls],
|
|
941
|
+
ensure_ascii=False)
|
|
700
942
|
|
|
701
943
|
# 将工具调用和响应添加到消息列表中
|
|
702
944
|
params["messages"].append(
|
|
703
945
|
{
|
|
704
946
|
"role": "assistant",
|
|
705
|
-
"content":
|
|
706
|
-
[{"name": tool_call["name"], "arguments": tool_call["arguments"]} for tool_call in
|
|
707
|
-
tool_calls]
|
|
708
|
-
),
|
|
947
|
+
"content": f"使用工具: \n {tool_str}\n"
|
|
709
948
|
}
|
|
710
949
|
)
|
|
711
950
|
params["messages"].append(
|
|
@@ -720,7 +959,7 @@ class LightAgent:
|
|
|
720
959
|
# 更新响应
|
|
721
960
|
if function_call_name == 'finish':
|
|
722
961
|
return # 如果最后调用了finish工具,则结束生成器
|
|
723
|
-
self.log("DEBUG", "chat-completions params", {"params": params})
|
|
962
|
+
self.log("DEBUG", "stream chat-completions params", {"params": params})
|
|
724
963
|
response = self.client.chat.completions.create(**params)
|
|
725
964
|
|
|
726
965
|
# 重试次数用尽
|
|
@@ -1024,6 +1263,7 @@ class LightAgent:
|
|
|
1024
1263
|
# 在函数内部定义工具信息
|
|
1025
1264
|
get_weather.tool_info = {
|
|
1026
1265
|
"tool_name": "get_weather",
|
|
1266
|
+
"tool_title": "天气查询",
|
|
1027
1267
|
"tool_description": "获取指定城市的当前天气信息",
|
|
1028
1268
|
"tool_params": [
|
|
1029
1269
|
{"name": "city_name", "description": "要查询的城市名称", "type": "string", "required": True},
|
|
@@ -1065,7 +1305,7 @@ get_weather.tool_info = {
|
|
|
1065
1305
|
f.write(tool_code)
|
|
1066
1306
|
self.log("INFO", "tool_created", {"tool_name": tool_name, "tool_path": tool_path})
|
|
1067
1307
|
|
|
1068
|
-
#
|
|
1308
|
+
# 自动加载新创建的工具
|
|
1069
1309
|
self.load_tools([tool_name], tools_directory)
|
|
1070
1310
|
except Exception as e:
|
|
1071
1311
|
self.log("ERROR", "tool_creation_failed", {"error": str(e)})
|
|
@@ -1106,4 +1346,4 @@ class LightSwarm:
|
|
|
1106
1346
|
if __name__ == "__main__":
|
|
1107
1347
|
# Example of registering and using a tool
|
|
1108
1348
|
print("This is LightAgent")
|
|
1109
|
-
# print(dispatch_tool("example_tool", {"param1": "test"}))
|
|
1349
|
+
# print(dispatch_tool("example_tool", {"param1": "test"}))
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: LightAgent
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.95
|
|
4
4
|
Summary: **LightAgent** is an extremely lightweight active Agentic Framework with memory (`mem0`), tools (`Tools`), and a Tree of Thought (`ToT`). It supports swarm-like multi-agent collaboration, automated tool generation, and agent assessment, with underlying model support for OpenAI, Zhipu ChatGLM, Baichuan Large Model, DeepSeek R1, Qwen series large models, and more. At the same time, LightAgent supports OpenAI streaming format API service output, seamlessly integrating with major mainstream chat frameworks. 🌟
|
|
5
5
|
Home-page: https://github.com/wxai-space/LightAgent
|
|
6
6
|
License: Apache 2.0
|
|
7
7
|
Author: caiweige
|
|
8
8
|
Author-email: caiweige@qq.com
|
|
9
|
-
Requires-Python: >=3.10,<3.
|
|
9
|
+
Requires-Python: >=3.10,<3.13
|
|
10
10
|
Classifier: License :: Other/Proprietary License
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
15
|
Requires-Dist: colorama (==0.4.6)
|
|
15
16
|
Requires-Dist: httpx (==0.28.1)
|
|
17
|
+
Requires-Dist: httpx-sse (==0.4.0)
|
|
16
18
|
Requires-Dist: loguru (==0.7.3)
|
|
19
|
+
Requires-Dist: mcp (==1.5.0)
|
|
17
20
|
Requires-Dist: openai (==1.59.3)
|
|
21
|
+
Requires-Dist: pydantic-settings (==2.8.1)
|
|
18
22
|
Requires-Dist: requests (==2.32.3)
|
|
19
23
|
Project-URL: Repository, https://github.com/wxai-space/LightAgent
|
|
20
24
|
Description-Content-Type: text/markdown
|
|
@@ -58,6 +62,8 @@ Description-Content-Type: text/markdown
|
|
|
58
62
|
|
|
59
63
|
---
|
|
60
64
|
|
|
65
|
+

|
|
66
|
+
|
|
61
67
|
## ✨ Features
|
|
62
68
|
|
|
63
69
|
- **Lightweight and Efficient** 🚀: Minimalist design, quick deployment, suitable for various application scenarios. (No LangChain, No LlamaIndex) 100% Python implementation, no additional dependencies, core code is only 1000 lines, fully open source.
|
|
@@ -74,7 +80,8 @@ Description-Content-Type: text/markdown
|
|
|
74
80
|
|
|
75
81
|
---
|
|
76
82
|
## News
|
|
77
|
-
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>**[2025-02-19]** LightAgent v0.
|
|
83
|
+
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>**[2025-02-19]** LightAgent v0.3.0 fully supports the MCP protocol, enabling collaborative work with multiple models and tools to achieve more efficient handling of complex tasks.<a href="mcp_release.zh-CN.md">View MCP release introduction.>></a>
|
|
84
|
+
- **[2025-02-19]** LightAgent v0.2.7 supports deepseek-r1 model for tot now.Significantly enhances the multi-tool planning capability for complex tasks.
|
|
78
85
|
- **[2025-02-06]** LightAgent version 0.2.5 is released now.
|
|
79
86
|
- **[2025-01-20]** LightAgent version 0.2.0 is released now.
|
|
80
87
|
- **[2025-01-05]** LightAgent version 0.1.0 is released now.
|
|
@@ -776,7 +783,7 @@ We look forward to your feedback and work together to make LightAgent even stron
|
|
|
776
783
|
**LightAgent** 是一个极其轻量的带记忆(`mem0`)、工具(`Tools`)、思维树(`ToT`)的主动式 Agentic Framework(自主性框架)。它支持比Openai Swarm更简单的多智能体协同,构建具备自我学习能力的agent,并支持Agent测评,底层模型支持 OpenAI、智谱 ChatGLM、DeepSeek、阶跃星辰、Qwen通义千问大模型等。同时,LightAgent 支持 OpenAI 流格式 API 服务输出,无缝接入各大主流 Chat 框架。🌟
|
|
777
784
|
|
|
778
785
|
---
|
|
779
|
-
|
|
786
|
+

|
|
780
787
|
## ✨ 特性
|
|
781
788
|
|
|
782
789
|
- **轻量高效** 🚀:极简设计,快速部署,适合各种规模的应用场景。(No LangChain, No LlamaIndex)100% Python 实现,无需额外依赖,核心代码仅1000行,完全开源。
|
|
@@ -793,7 +800,8 @@ We look forward to your feedback and work together to make LightAgent even stron
|
|
|
793
800
|
|
|
794
801
|
---
|
|
795
802
|
## 新闻
|
|
796
|
-
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>**[2025-02-19]** LightAgent v0.
|
|
803
|
+
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>**[2025-02-19]** LightAgent v0.3.0 全面支持MCP协议,支持多模型多工具的协同工作,实现更高效的复杂任务处理。<a href="mcp_release.zh-CN.md">查看MCP发布简介>></a>
|
|
804
|
+
- **[2025-02-19]** LightAgent v0.2.7 支持单独采用 deepseek-r1 作为的agent推理规划ToT引擎,大幅度提升复杂任务的多工具Plan能力.
|
|
797
805
|
- **[2025-02-06]** LightAgent version 0.2.5 is released now.
|
|
798
806
|
- **[2025-01-20]** LightAgent version 0.2.0 is released now.
|
|
799
807
|
- **[2025-01-05]** LightAgent version 0.1.0 is released now.
|
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|
|
40
|
+

|
|
41
|
+
|
|
40
42
|
## ✨ Features
|
|
41
43
|
|
|
42
44
|
- **Lightweight and Efficient** 🚀: Minimalist design, quick deployment, suitable for various application scenarios. (No LangChain, No LlamaIndex) 100% Python implementation, no additional dependencies, core code is only 1000 lines, fully open source.
|
|
@@ -53,7 +55,8 @@
|
|
|
53
55
|
|
|
54
56
|
---
|
|
55
57
|
## News
|
|
56
|
-
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>**[2025-02-19]** LightAgent v0.
|
|
58
|
+
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>**[2025-02-19]** LightAgent v0.3.0 fully supports the MCP protocol, enabling collaborative work with multiple models and tools to achieve more efficient handling of complex tasks.<a href="mcp_release.zh-CN.md">View MCP release introduction.>></a>
|
|
59
|
+
- **[2025-02-19]** LightAgent v0.2.7 supports deepseek-r1 model for tot now.Significantly enhances the multi-tool planning capability for complex tasks.
|
|
57
60
|
- **[2025-02-06]** LightAgent version 0.2.5 is released now.
|
|
58
61
|
- **[2025-01-20]** LightAgent version 0.2.0 is released now.
|
|
59
62
|
- **[2025-01-05]** LightAgent version 0.1.0 is released now.
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
**LightAgent** 是一个极其轻量的带记忆(`mem0`)、工具(`Tools`)、思维树(`ToT`)的主动式 Agentic Framework(自主性框架)。它支持比Openai Swarm更简单的多智能体协同,构建具备自我学习能力的agent,并支持Agent测评,底层模型支持 OpenAI、智谱 ChatGLM、DeepSeek、阶跃星辰、Qwen通义千问大模型等。同时,LightAgent 支持 OpenAI 流格式 API 服务输出,无缝接入各大主流 Chat 框架。🌟
|
|
37
37
|
|
|
38
38
|
---
|
|
39
|
-
|
|
39
|
+

|
|
40
40
|
## ✨ 特性
|
|
41
41
|
|
|
42
42
|
- **轻量高效** 🚀:极简设计,快速部署,适合各种规模的应用场景。(No LangChain, No LlamaIndex)100% Python 实现,无需额外依赖,核心代码仅1000行,完全开源。
|
|
@@ -53,7 +53,8 @@
|
|
|
53
53
|
|
|
54
54
|
---
|
|
55
55
|
## 新闻
|
|
56
|
-
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>**[2025-02-19]** LightAgent v0.
|
|
56
|
+
- <img src="https://img.alicdn.com/imgextra/i3/O1CN01SFL0Gu26nrQBFKXFR_!!6000000007707-2-tps-500-500.png" alt="new" width="30" height="30"/>**[2025-02-19]** LightAgent v0.3.0 全面支持MCP协议,支持多模型多工具的协同工作,实现更高效的复杂任务处理。<a href="mcp_release.zh-CN.md">查看MCP发布简介>></a>
|
|
57
|
+
- **[2025-02-19]** LightAgent v0.2.7 支持单独采用 deepseek-r1 作为的agent推理规划ToT引擎,大幅度提升复杂任务的多工具Plan能力.
|
|
57
58
|
- **[2025-02-06]** LightAgent version 0.2.5 is released now.
|
|
58
59
|
- **[2025-01-20]** LightAgent version 0.2.0 is released now.
|
|
59
60
|
- **[2025-01-05]** LightAgent version 0.1.0 is released now.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "LightAgent"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.95"
|
|
4
4
|
description = "**LightAgent** is an extremely lightweight active Agentic Framework with memory (`mem0`), tools (`Tools`), and a Tree of Thought (`ToT`). It supports swarm-like multi-agent collaboration, automated tool generation, and agent assessment, with underlying model support for OpenAI, Zhipu ChatGLM, Baichuan Large Model, DeepSeek R1, Qwen series large models, and more. At the same time, LightAgent supports OpenAI streaming format API service output, seamlessly integrating with major mainstream chat frameworks. 🌟"
|
|
5
5
|
authors = ["caiweige <caiweige@qq.com>"]
|
|
6
6
|
license = "Apache 2.0"
|
|
@@ -22,12 +22,16 @@ packages = [
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
[tool.poetry.dependencies]
|
|
25
|
-
python = ">=3.10,<3.
|
|
25
|
+
python = ">=3.10,<3.13,!=3.9.7"
|
|
26
26
|
loguru = "0.7.3"
|
|
27
27
|
requests = "2.32.3"
|
|
28
28
|
openai = "1.59.3"
|
|
29
29
|
colorama = "0.4.6"
|
|
30
30
|
httpx = "0.28.1"
|
|
31
|
+
httpx-sse = "0.4.0"
|
|
32
|
+
mcp = "1.5.0"
|
|
33
|
+
pydantic-settings = "2.8.1"
|
|
34
|
+
|
|
31
35
|
|
|
32
36
|
[tool.ruff]
|
|
33
37
|
extend-include = ["*.ipynb"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|