LightAgent 0.2.85__tar.gz → 0.3.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.2.85 → lightagent-0.3.0}/LightAgent/la_core.py +313 -64
- {lightagent-0.2.85 → lightagent-0.3.0}/PKG-INFO +13 -5
- {lightagent-0.2.85 → lightagent-0.3.0}/README.md +4 -1
- {lightagent-0.2.85 → lightagent-0.3.0}/README.zh-CN.md +3 -2
- {lightagent-0.2.85 → lightagent-0.3.0}/pyproject.toml +6 -2
- {lightagent-0.2.85 → lightagent-0.3.0}/LICENSE +0 -0
- {lightagent-0.2.85 → lightagent-0.3.0}/LightAgent/__init__.py +0 -0
- {lightagent-0.2.85 → lightagent-0.3.0}/README.de.md +0 -0
- {lightagent-0.2.85 → lightagent-0.3.0}/README.es.md +0 -0
- {lightagent-0.2.85 → lightagent-0.3.0}/README.fr.md +0 -0
- {lightagent-0.2.85 → lightagent-0.3.0}/README.ja.md +0 -0
- {lightagent-0.2.85 → lightagent-0.3.0}/README.ko.md +0 -0
- {lightagent-0.2.85 → lightagent-0.3.0}/README.pt.md +0 -0
- {lightagent-0.2.85 → lightagent-0.3.0}/README.ru.md +0 -0
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
作者: [weego/WXAI-Team]
|
|
6
|
+
版本: 0.3.0
|
|
7
|
+
最后更新: 2025-03-31
|
|
8
|
+
"""
|
|
9
|
+
|
|
1
10
|
import re
|
|
2
|
-
import
|
|
3
|
-
from typing import List, Dict, Any, Callable, Union, Iterable, Optional, Generator
|
|
11
|
+
from typing import List, Dict, Any, Callable, Union, Iterable, Optional, Generator, AsyncGenerator
|
|
4
12
|
from copy import deepcopy
|
|
5
13
|
import importlib.util
|
|
6
14
|
import random
|
|
@@ -12,14 +20,24 @@ import os
|
|
|
12
20
|
import httpx
|
|
13
21
|
import importlib
|
|
14
22
|
from openai.types.chat import ChatCompletionChunk
|
|
23
|
+
import inspect
|
|
24
|
+
import traceback
|
|
25
|
+
from mcp import ClientSession, StdioServerParameters, types
|
|
26
|
+
from contextlib import AsyncExitStack
|
|
27
|
+
|
|
28
|
+
from mcp.client.sse import sse_client
|
|
29
|
+
from mcp.client.stdio import stdio_client
|
|
30
|
+
import asyncio
|
|
31
|
+
from functools import partial
|
|
32
|
+
|
|
15
33
|
|
|
16
34
|
# 全局工具注册表
|
|
17
35
|
_FUNCTION_MAPPINGS = {} # 工具名称 -> 工具函数
|
|
18
|
-
_FUNCTION_INFO = {}
|
|
36
|
+
_FUNCTION_INFO = {} # 工具名称 -> 工具info信息
|
|
19
37
|
_OPENAI_FUNCTION_SCHEMAS = [] # OpenAI 格式的工具描述
|
|
20
38
|
_PROMPT_FUNCTION_SCHEMAS = [] # prompt 格式的工具描述
|
|
21
39
|
|
|
22
|
-
__version__ = "0.
|
|
40
|
+
__version__ = "0.3.0" # 你可以根据需要设置版本号
|
|
23
41
|
|
|
24
42
|
|
|
25
43
|
def register_tool_manually(tools: List[Union[str, Callable]]) -> bool:
|
|
@@ -88,35 +106,44 @@ def load_tool(tool_name: str, tools_directory: str = "tools"):
|
|
|
88
106
|
raise AttributeError(f"Tool '{tool_name}' is not properly defined in {tool_path}")
|
|
89
107
|
|
|
90
108
|
|
|
91
|
-
def dispatch_tool(tool_name: str, tool_params: Dict[str, Any]) -> Union[
|
|
109
|
+
async def dispatch_tool(tool_name: str, tool_params: Dict[str, Any]) -> Union[
|
|
110
|
+
str, Generator[str, None, None], AsyncGenerator[str, None]]:
|
|
92
111
|
"""
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
:param tool_name: 工具名称
|
|
96
|
-
:param tool_params: 工具参数
|
|
97
|
-
:return: 如果工具是流式输出,返回生成器;否则返回字符串结果。
|
|
112
|
+
调用工具执行,支持同步/异步工具及流式输出。
|
|
98
113
|
"""
|
|
99
114
|
if tool_name not in _FUNCTION_MAPPINGS:
|
|
100
115
|
return f"Tool `{tool_name}` not found."
|
|
101
116
|
|
|
102
117
|
tool_call = _FUNCTION_MAPPINGS[tool_name]
|
|
103
118
|
try:
|
|
104
|
-
#
|
|
105
|
-
|
|
119
|
+
# 处理不同类型的流式输出
|
|
120
|
+
# 区分同步/异步工具
|
|
121
|
+
if inspect.iscoroutinefunction(tool_call):
|
|
122
|
+
# result = await tool_call(**tool_params)
|
|
123
|
+
# 将参数以字典形式传递给包装器
|
|
124
|
+
result = await tool_call(**tool_params) if inspect.iscoroutinefunction(tool_call) else tool_call(
|
|
125
|
+
**tool_params)
|
|
126
|
+
else:
|
|
127
|
+
result = tool_call(**tool_params)
|
|
106
128
|
|
|
107
|
-
#
|
|
108
|
-
if
|
|
129
|
+
# 处理不同类型的流式输出
|
|
130
|
+
if inspect.isasyncgen(result):
|
|
131
|
+
return async_stream_generator(result)
|
|
132
|
+
elif inspect.isgenerator(result):
|
|
109
133
|
return stream_generator(result)
|
|
110
|
-
# 否则,返回字符串结果
|
|
111
134
|
else:
|
|
112
135
|
return str(result)
|
|
113
136
|
except Exception as e:
|
|
114
|
-
# print(f"Tool call failed: {e}") # 调试信息
|
|
115
137
|
return traceback.format_exc()
|
|
116
138
|
|
|
117
139
|
|
|
118
|
-
def
|
|
119
|
-
for chunk in
|
|
140
|
+
async def async_stream_generator(async_gen: AsyncGenerator) -> AsyncGenerator[str, None]:
|
|
141
|
+
async for chunk in async_gen:
|
|
142
|
+
yield chunk
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def stream_generator(sync_gen: Generator) -> Generator[str, None, None]:
|
|
146
|
+
for chunk in sync_gen:
|
|
120
147
|
yield chunk
|
|
121
148
|
|
|
122
149
|
|
|
@@ -154,26 +181,223 @@ def get_tools_str() -> str:
|
|
|
154
181
|
return tools_str
|
|
155
182
|
|
|
156
183
|
|
|
184
|
+
class MCPClientManager:
|
|
185
|
+
"""增强版MCP客户端管理器"""
|
|
186
|
+
|
|
187
|
+
def __init__(self, config: dict):
|
|
188
|
+
self.config = config
|
|
189
|
+
self.session: Optional[ClientSession] = None
|
|
190
|
+
self.exit_stack = AsyncExitStack()
|
|
191
|
+
self._streams_context = None
|
|
192
|
+
self._session_context = None
|
|
193
|
+
self.server_sessions = {} # 存储不同服务器的会话
|
|
194
|
+
|
|
195
|
+
async def _call_tool_wrapper(self, tool_name: str, target_server: str, **kwargs):
|
|
196
|
+
"""参数转换适配器"""
|
|
197
|
+
return await self.call_tool(
|
|
198
|
+
tool_name=tool_name,
|
|
199
|
+
arguments=kwargs,
|
|
200
|
+
target_server=target_server
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
async def _create_session(self, server_name: str, config: dict):
|
|
204
|
+
"""创建并管理会话上下文"""
|
|
205
|
+
if 'url' in config:
|
|
206
|
+
# SSE 服务器连接
|
|
207
|
+
self._streams_context = sse_client(
|
|
208
|
+
url=config['url'],
|
|
209
|
+
headers=config.get('headers', {})
|
|
210
|
+
)
|
|
211
|
+
streams = await self.exit_stack.enter_async_context(self._streams_context)
|
|
212
|
+
self._session_context = ClientSession(*streams)
|
|
213
|
+
self.session = await self.exit_stack.enter_async_context(self._session_context)
|
|
214
|
+
else:
|
|
215
|
+
# 标准输入输出服务器连接
|
|
216
|
+
server_params = StdioServerParameters(
|
|
217
|
+
command=config["command"],
|
|
218
|
+
args=config["args"],
|
|
219
|
+
env=config.get("env")
|
|
220
|
+
)
|
|
221
|
+
transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
|
|
222
|
+
stdio, write = transport
|
|
223
|
+
self._session_context = ClientSession(stdio, write)
|
|
224
|
+
self.session = await self.exit_stack.enter_async_context(self._session_context)
|
|
225
|
+
|
|
226
|
+
await self.session.initialize()
|
|
227
|
+
self.server_sessions[server_name] = self.session
|
|
228
|
+
|
|
229
|
+
async def cleanup(self):
|
|
230
|
+
"""清理所有会话资源"""
|
|
231
|
+
await self.exit_stack.__aexit__(None, None, None)
|
|
232
|
+
self.server_sessions.clear()
|
|
233
|
+
|
|
234
|
+
async def register_mcp_tool(self) -> bool:
|
|
235
|
+
"""
|
|
236
|
+
自动注册所有MCP服务的工具到全局字典
|
|
237
|
+
:param config: MCP服务配置(与call_tool使用的相同配置)
|
|
238
|
+
:return: 是否至少成功注册一个工具
|
|
239
|
+
"""
|
|
240
|
+
registered_count = 0
|
|
241
|
+
enabled_servers = [
|
|
242
|
+
(name, config)
|
|
243
|
+
for name, config in self.config["mcpServers"].items()
|
|
244
|
+
if not config["disabled"]
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
for server_name, config in enabled_servers:
|
|
248
|
+
try:
|
|
249
|
+
# 创建会话连接
|
|
250
|
+
# print(server_name,config)
|
|
251
|
+
await self._create_session(server_name, config)
|
|
252
|
+
|
|
253
|
+
# 获取工具列表
|
|
254
|
+
tools_response = await self.session.list_tools()
|
|
255
|
+
print(f"🔍 Registering tools for server : {server_name} ...")
|
|
256
|
+
|
|
257
|
+
# 注册工具处理逻辑
|
|
258
|
+
for tool in tools_response.tools:
|
|
259
|
+
try:
|
|
260
|
+
# 构建工具元数据
|
|
261
|
+
tool_info = {
|
|
262
|
+
"tool_name": tool.name,
|
|
263
|
+
"tool_description": tool.description,
|
|
264
|
+
"tool_params": []
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
# 解析参数模式
|
|
268
|
+
properties = tool.inputSchema.get("properties", {})
|
|
269
|
+
required_fields = tool.inputSchema.get("required", [])
|
|
270
|
+
|
|
271
|
+
for param_name, param_schema in properties.items():
|
|
272
|
+
tool_info["tool_params"].append({
|
|
273
|
+
"name": param_name,
|
|
274
|
+
"type": param_schema.get("type", "string"),
|
|
275
|
+
"description": param_schema.get("title", ""),
|
|
276
|
+
"required": param_name in required_fields
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
# 注册到全局字典
|
|
280
|
+
_FUNCTION_INFO[tool.name] = tool_info
|
|
281
|
+
_FUNCTION_MAPPINGS[tool.name] = partial(
|
|
282
|
+
self._call_tool_wrapper,
|
|
283
|
+
tool_name=tool.name,
|
|
284
|
+
target_server=server_name
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# 构建OpenAI格式
|
|
288
|
+
openai_schema = {
|
|
289
|
+
"type": "function",
|
|
290
|
+
"function": {
|
|
291
|
+
"name": tool.name,
|
|
292
|
+
"description": tool.description,
|
|
293
|
+
"parameters": {
|
|
294
|
+
"type": "object",
|
|
295
|
+
"properties": {
|
|
296
|
+
k: {"type": v["type"], "description": v.get("title", "")}
|
|
297
|
+
for k, v in properties.items()
|
|
298
|
+
},
|
|
299
|
+
"required": required_fields
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
_OPENAI_FUNCTION_SCHEMAS.append(openai_schema)
|
|
304
|
+
|
|
305
|
+
registered_count += 1
|
|
306
|
+
print(f"✅ The registered tool : {tool.name}")
|
|
307
|
+
|
|
308
|
+
except Exception as e:
|
|
309
|
+
print(f"⚠️ 工具 {tool.name} 注册失败: {str(e)}")
|
|
310
|
+
continue
|
|
311
|
+
print(f"🟢 {registered_count} tools have been registered")
|
|
312
|
+
|
|
313
|
+
except Exception as e:
|
|
314
|
+
print(f"🔴 服务器 {server_name} 连接失败: {str(e)}")
|
|
315
|
+
continue
|
|
316
|
+
# 清理
|
|
317
|
+
await self.cleanup()
|
|
318
|
+
return registered_count > 0
|
|
319
|
+
|
|
320
|
+
async def call_tool(self, tool_name: str, arguments: dict, target_server: str = None):
|
|
321
|
+
"""
|
|
322
|
+
通用工具调用方法
|
|
323
|
+
:param tool_name: 要调用的工具名称
|
|
324
|
+
:param arguments: 工具参数字典
|
|
325
|
+
:param target_server: 指定服务器名称(可选)
|
|
326
|
+
:return: 工具调用结果
|
|
327
|
+
"""
|
|
328
|
+
enabled_servers = [
|
|
329
|
+
(name, config)
|
|
330
|
+
for name, config in self.config["mcpServers"].items()
|
|
331
|
+
if not config["disabled"]
|
|
332
|
+
]
|
|
333
|
+
|
|
334
|
+
if target_server:
|
|
335
|
+
enabled_servers = [s for s in enabled_servers if s[0] == target_server]
|
|
336
|
+
|
|
337
|
+
for server_name, config in enabled_servers:
|
|
338
|
+
try:
|
|
339
|
+
# 复用已建立的会话
|
|
340
|
+
session = self.server_sessions.get(server_name)
|
|
341
|
+
# print(111,server_name,session)
|
|
342
|
+
# print(222,server_name,config)
|
|
343
|
+
if not session:
|
|
344
|
+
await self._create_session(server_name, config)
|
|
345
|
+
session = self.session
|
|
346
|
+
|
|
347
|
+
# 获取工具列表
|
|
348
|
+
tools = await session.list_tools()
|
|
349
|
+
available_tools = {t.name: t for t in tools.tools}
|
|
350
|
+
|
|
351
|
+
if tool_name in available_tools:
|
|
352
|
+
# 验证参数类型
|
|
353
|
+
schema = available_tools[tool_name].inputSchema
|
|
354
|
+
self._validate_arguments(arguments, schema)
|
|
355
|
+
|
|
356
|
+
# 执行调用
|
|
357
|
+
result = await session.call_tool(tool_name, arguments)
|
|
358
|
+
# print(f"mcp工具运行结果: {result.content[0].text}")
|
|
359
|
+
# 调用完成清理session
|
|
360
|
+
await self.cleanup()
|
|
361
|
+
return {
|
|
362
|
+
"server": server_name,
|
|
363
|
+
"tool": tool_name,
|
|
364
|
+
"result": result.content[0].text
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
except Exception as e:
|
|
368
|
+
print(f"调用服务器 {server_name} 失败: {str(e)}")
|
|
369
|
+
continue
|
|
370
|
+
|
|
371
|
+
raise ValueError(f"工具 {tool_name} 在可用服务器中未找到")
|
|
372
|
+
|
|
373
|
+
def _validate_arguments(self, arguments: dict, schema: dict):
|
|
374
|
+
"""简单参数校验(可选)"""
|
|
375
|
+
required_fields = schema.get("required", [])
|
|
376
|
+
for field in required_fields:
|
|
377
|
+
if field not in arguments:
|
|
378
|
+
raise ValueError(f"缺少必要参数: {field}")
|
|
379
|
+
|
|
380
|
+
|
|
157
381
|
class LightAgent:
|
|
158
|
-
__version__ = "0.
|
|
382
|
+
__version__ = "0.3.0" # 将版本号放在类中
|
|
159
383
|
|
|
160
384
|
def __init__(
|
|
161
385
|
self,
|
|
162
386
|
*,
|
|
163
387
|
name: Optional[str] = None, # 代理名称
|
|
164
388
|
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,
|
|
389
|
+
role: Optional[str] = None, # 代理角色
|
|
390
|
+
model: str, # agent模型名称
|
|
391
|
+
api_key: str | None = None, # 模型 api key
|
|
392
|
+
base_url: str | httpx.URL | None = None, # 模型 base url
|
|
393
|
+
websocket_base_url: str | httpx.URL | None = None, # 模型 websocket base url
|
|
170
394
|
memory=None, # 支持外部传入记忆模块
|
|
171
395
|
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,
|
|
396
|
+
tot_model: str | None = None, # 链式思考模型
|
|
397
|
+
tot_api_key: str | None = None, # 链式思考模型API密钥
|
|
398
|
+
tot_base_url: str | httpx.URL | None = None, # 链式思考模型base_url
|
|
175
399
|
self_learning: bool = False, # 是否启用agent自我学习
|
|
176
|
-
tools: List[Union[str, Callable]] = None, #
|
|
400
|
+
tools: List[Union[str, Callable]] = None, # 支持工具混合输入
|
|
177
401
|
debug: bool = False, # 是否启用调试模式
|
|
178
402
|
log_level: str = "INFO", # 日志级别(INFO, DEBUG, ERROR)
|
|
179
403
|
log_file: Optional[str] = None # 日志文件路径
|
|
@@ -198,6 +422,8 @@ class LightAgent:
|
|
|
198
422
|
:param log_level: 日志级别(INFO, DEBUG, ERROR)。
|
|
199
423
|
:param log_file: 日志文件路径。
|
|
200
424
|
"""
|
|
425
|
+
self.mcp_setting = None
|
|
426
|
+
self.mcp_client = None
|
|
201
427
|
if not model:
|
|
202
428
|
model = "gpt-4o-mini" # 默认模型
|
|
203
429
|
if not api_key:
|
|
@@ -206,7 +432,7 @@ class LightAgent:
|
|
|
206
432
|
base_url = os.environ.get("OPENAI_BASE_URL")
|
|
207
433
|
self.loaded_tools = {} # 用于存储已加载的工具函数
|
|
208
434
|
if not name:
|
|
209
|
-
random_suffix = random.randint(10000000, 99999999) # 生成一个8
|
|
435
|
+
random_suffix = random.randint(10000000, 99999999) # 生成一个8位随机数作为agent编号
|
|
210
436
|
name = f"LightAgent{random_suffix}"
|
|
211
437
|
self.name = name
|
|
212
438
|
if not instructions:
|
|
@@ -236,6 +462,8 @@ class LightAgent:
|
|
|
236
462
|
# 初始化工具列表
|
|
237
463
|
self.load_tools(tools)
|
|
238
464
|
# register_tool_manually(tools)
|
|
465
|
+
|
|
466
|
+
|
|
239
467
|
if api_key is None:
|
|
240
468
|
raise ValueError(
|
|
241
469
|
"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 +475,8 @@ class LightAgent:
|
|
|
247
475
|
base_url = f"https://api.openai.com/v1"
|
|
248
476
|
|
|
249
477
|
self.client = OpenAI(
|
|
250
|
-
base_url
|
|
251
|
-
api_key
|
|
478
|
+
base_url=base_url,
|
|
479
|
+
api_key=self.api_key
|
|
252
480
|
)
|
|
253
481
|
if self.tree_of_thought:
|
|
254
482
|
if tot_api_key is None:
|
|
@@ -259,8 +487,8 @@ class LightAgent:
|
|
|
259
487
|
tot_model = "deepseek-r1" # 默认思维推理模型为deepseek-r1
|
|
260
488
|
self.tot_model = tot_model
|
|
261
489
|
self.tot_client = OpenAI(
|
|
262
|
-
base_url
|
|
263
|
-
api_key
|
|
490
|
+
base_url=tot_base_url,
|
|
491
|
+
api_key=tot_api_key
|
|
264
492
|
)
|
|
265
493
|
|
|
266
494
|
def get_tool(self, tool_name: str) -> Callable:
|
|
@@ -278,7 +506,6 @@ class LightAgent:
|
|
|
278
506
|
用于外部可以获取已加载的工具函数列表
|
|
279
507
|
:return: 工具函数
|
|
280
508
|
"""
|
|
281
|
-
|
|
282
509
|
return list(_FUNCTION_MAPPINGS.keys())
|
|
283
510
|
|
|
284
511
|
def load_tools(self, tool_names: List[Union[str, Callable]], tools_directory: str = "tools"):
|
|
@@ -377,6 +604,18 @@ class LightAgent:
|
|
|
377
604
|
elif level == "ERROR":
|
|
378
605
|
self.logger.error(log_message)
|
|
379
606
|
|
|
607
|
+
async def setup_mcp(
|
|
608
|
+
self,
|
|
609
|
+
mcp_setting: dict | None = None, # mcp 设置
|
|
610
|
+
):
|
|
611
|
+
if mcp_setting:
|
|
612
|
+
self.mcp_setting = mcp_setting
|
|
613
|
+
"""单独初始化 MCP 模块"""
|
|
614
|
+
if self.mcp_setting and not self.mcp_client:
|
|
615
|
+
self.mcp_client = MCPClientManager(self.mcp_setting)
|
|
616
|
+
await self.mcp_client.register_mcp_tool()
|
|
617
|
+
self.log("INFO", "setup_mcp", "MCP 模块初始化成功")
|
|
618
|
+
|
|
380
619
|
def run(
|
|
381
620
|
self,
|
|
382
621
|
query: str,
|
|
@@ -489,18 +728,18 @@ class LightAgent:
|
|
|
489
728
|
fixed_args = function_call.arguments.replace('\\"', '"').replace('\\\\', '\\')
|
|
490
729
|
self.log("DEBUG", "non_stream function_call", {"function_call": fixed_args})
|
|
491
730
|
|
|
492
|
-
function_args = json.loads(fixed_args)
|
|
493
731
|
# 解析函数参数
|
|
494
|
-
|
|
732
|
+
function_args = json.loads(fixed_args)
|
|
495
733
|
|
|
496
734
|
# 调用工具并获取响应
|
|
497
|
-
tool_response = dispatch_tool(function_call.name, function_args)
|
|
735
|
+
tool_response = asyncio.run(dispatch_tool(function_call.name, function_args))
|
|
498
736
|
function_call_name = function_call.name
|
|
499
737
|
combined_response = ""
|
|
738
|
+
single_tool_response = ""
|
|
500
739
|
|
|
501
740
|
# 如果工具返回的是生成器(流式输出),则将所有 chunk 叠加
|
|
502
741
|
if isinstance(tool_response, Generator):
|
|
503
|
-
print(f"Streaming response from tool: {function_call.name}")
|
|
742
|
+
# print(f"Streaming response from tool: {function_call.name}")
|
|
504
743
|
for chunk in tool_response:
|
|
505
744
|
# print("Received chunk:", chunk) # 打印每个 chunk
|
|
506
745
|
if function_call_name == 'finish':
|
|
@@ -520,8 +759,8 @@ class LightAgent:
|
|
|
520
759
|
# 将 JSON 对象中的 Unicode 编码转换为中文字符
|
|
521
760
|
if isinstance(combined_response, dict):
|
|
522
761
|
combined_response = json.dumps(combined_response, ensure_ascii=False) # 确保中文字符不转义
|
|
762
|
+
single_tool_response = combined_response # 处理单个工具的方法
|
|
523
763
|
|
|
524
|
-
tool_responses.append(combined_response) # 将叠加后的完整响应添加到列表
|
|
525
764
|
else:
|
|
526
765
|
# print(f"Non-streaming response from tool: {function_call.name}")
|
|
527
766
|
combined_response = tool_response
|
|
@@ -534,22 +773,23 @@ class LightAgent:
|
|
|
534
773
|
except json.JSONDecodeError:
|
|
535
774
|
combined_response = tool_response
|
|
536
775
|
pass # 如果不是 JSON 字符串,保持原样
|
|
537
|
-
|
|
776
|
+
single_tool_response = combined_response # 处理单个工具的方法
|
|
538
777
|
|
|
539
|
-
self.log("INFO", "non_stream
|
|
778
|
+
self.log("INFO", "non_stream single_tool_response", {"single_tool_response": single_tool_response})
|
|
540
779
|
|
|
541
|
-
#
|
|
542
|
-
tool_responses.append(
|
|
780
|
+
# 将单个工具的响应结果添加到列表中
|
|
781
|
+
tool_responses.append(single_tool_response)
|
|
543
782
|
|
|
544
783
|
# 将所有工具调用的结果合并为一个字符串
|
|
784
|
+
self.log("DEBUG", "non_stream tool_responses", {"tool_responses": tool_responses})
|
|
785
|
+
|
|
545
786
|
combined_tool_response = "\n".join(tool_responses)
|
|
546
787
|
|
|
547
788
|
# 将工具调用和响应添加到消息列表中
|
|
548
789
|
params["messages"].append(
|
|
549
790
|
{
|
|
550
791
|
"role": "assistant",
|
|
551
|
-
"content": json.dumps(
|
|
552
|
-
[tool_call.function.model_dump() for tool_call in tool_calls]),
|
|
792
|
+
"content": f"使用工具: \n {json.dumps([tool_call.function.model_dump() for tool_call in tool_calls], ensure_ascii=False)}\n",
|
|
553
793
|
}
|
|
554
794
|
)
|
|
555
795
|
params["messages"].append(
|
|
@@ -561,14 +801,14 @@ class LightAgent:
|
|
|
561
801
|
else:
|
|
562
802
|
# 返回最终回复
|
|
563
803
|
reply = response.choices[0].message.content
|
|
564
|
-
self.log("INFO", "final_reply", {"reply": reply})
|
|
804
|
+
self.log("INFO", "non_stream final_reply", {"reply": reply})
|
|
565
805
|
return reply
|
|
566
806
|
|
|
567
807
|
# 更新响应
|
|
568
808
|
if function_call_name == 'finish':
|
|
569
809
|
return # 如果最后调用了finish工具,则结束生成器
|
|
570
810
|
# print("params:",params)
|
|
571
|
-
self.log("DEBUG", "chat-completions params", {"params": params})
|
|
811
|
+
self.log("DEBUG", "non_stream chat-completions params", {"params": params})
|
|
572
812
|
|
|
573
813
|
try:
|
|
574
814
|
response = self.client.chat.completions.create(**params)
|
|
@@ -631,7 +871,7 @@ class LightAgent:
|
|
|
631
871
|
chunk.choices[0].finish_reason == "stop" and any(
|
|
632
872
|
tool_call["name"] for tool_call in tool_calls)):
|
|
633
873
|
# 遍历所有工具调用
|
|
634
|
-
self.log("DEBUG", "tool_calls", {"tool_calls": tool_calls})
|
|
874
|
+
self.log("DEBUG", "stream tool_calls", {"tool_calls": tool_calls})
|
|
635
875
|
for tool_call in tool_calls:
|
|
636
876
|
if tool_call["name"]: # 确保工具调用有名称
|
|
637
877
|
function_call = {
|
|
@@ -639,7 +879,7 @@ class LightAgent:
|
|
|
639
879
|
"title": _FUNCTION_INFO.get(tool_call["name"], {}).get('tool_title') or '',
|
|
640
880
|
"arguments": tool_call["arguments"],
|
|
641
881
|
}
|
|
642
|
-
self.log("INFO", "
|
|
882
|
+
self.log("INFO", "stream function_call", {"function_call": function_call})
|
|
643
883
|
# 将工具的调用信息推送给开发者
|
|
644
884
|
yield function_call
|
|
645
885
|
|
|
@@ -655,14 +895,20 @@ class LightAgent:
|
|
|
655
895
|
# tool_responses.append(tool_response)
|
|
656
896
|
|
|
657
897
|
for json_obj in json_objects:
|
|
658
|
-
|
|
659
|
-
|
|
898
|
+
# 尝试自动修复常见转义问题
|
|
899
|
+
fixed_args = json_obj.replace('\\"', '"').replace('\\\\', '\\')
|
|
900
|
+
self.log("DEBUG", "stream fixed_args", {"fixed_args": fixed_args})
|
|
901
|
+
|
|
902
|
+
function_args = json.loads(fixed_args)
|
|
903
|
+
# tool_response = dispatch_tool(function_call["name"], function_args)
|
|
904
|
+
tool_response = asyncio.run(dispatch_tool(function_call["name"], function_args))
|
|
660
905
|
function_call_name = function_call["name"]
|
|
906
|
+
combined_response = ""
|
|
907
|
+
single_tool_response = ""
|
|
661
908
|
|
|
662
909
|
# 如果工具返回的是生成器(流式输出),则将所有 chunk 叠加
|
|
663
910
|
if isinstance(tool_response, Generator):
|
|
664
911
|
# print(f"Streaming response from tool: {function_call['name']}")
|
|
665
|
-
combined_response = ""
|
|
666
912
|
for chunk in tool_response:
|
|
667
913
|
# 将工具返回的数据继续流出
|
|
668
914
|
if isinstance(chunk, ChatCompletionChunk):
|
|
@@ -674,7 +920,7 @@ class LightAgent:
|
|
|
674
920
|
'tool_title') or '',
|
|
675
921
|
"output": chunk,
|
|
676
922
|
}
|
|
677
|
-
self.log("
|
|
923
|
+
self.log("DEBUG", "stream tool_output", {"tool_output": tool_output})
|
|
678
924
|
yield tool_output
|
|
679
925
|
# 将工具的调用信息推送给开发者
|
|
680
926
|
if function_call_name == 'finish':
|
|
@@ -682,13 +928,15 @@ class LightAgent:
|
|
|
682
928
|
combined_response += content # 将每个 chunk 叠加
|
|
683
929
|
else:
|
|
684
930
|
combined_response += chunk # 将每个 chunk 叠加
|
|
685
|
-
|
|
931
|
+
single_tool_response = combined_response # 处理单个工具的方法
|
|
686
932
|
else:
|
|
687
933
|
# print(f"Non-streaming response from tool: {tool_response}")
|
|
688
934
|
combined_response = tool_response
|
|
689
|
-
|
|
690
|
-
self.log("INFO", "stream
|
|
691
|
-
|
|
935
|
+
single_tool_response = combined_response # 处理单个工具的方法
|
|
936
|
+
self.log("INFO", "stream single_tool_response",
|
|
937
|
+
{"single_tool_response": single_tool_response})
|
|
938
|
+
# 将单个工具的响应结果添加到列表中
|
|
939
|
+
tool_responses.append(single_tool_response)
|
|
692
940
|
|
|
693
941
|
except json.JSONDecodeError as e:
|
|
694
942
|
self.log("ERROR", "json_decode_error",
|
|
@@ -697,15 +945,15 @@ class LightAgent:
|
|
|
697
945
|
|
|
698
946
|
# 将所有工具调用的结果合并为一个字符串
|
|
699
947
|
combined_tool_response = "\n".join(tool_responses)
|
|
948
|
+
tool_str = json.dumps(
|
|
949
|
+
[{"name": tool_call["name"], "arguments": tool_call["arguments"]} for tool_call in tool_calls],
|
|
950
|
+
ensure_ascii=False)
|
|
700
951
|
|
|
701
952
|
# 将工具调用和响应添加到消息列表中
|
|
702
953
|
params["messages"].append(
|
|
703
954
|
{
|
|
704
955
|
"role": "assistant",
|
|
705
|
-
"content":
|
|
706
|
-
[{"name": tool_call["name"], "arguments": tool_call["arguments"]} for tool_call in
|
|
707
|
-
tool_calls]
|
|
708
|
-
),
|
|
956
|
+
"content": f"使用工具: \n {tool_str}\n"
|
|
709
957
|
}
|
|
710
958
|
)
|
|
711
959
|
params["messages"].append(
|
|
@@ -720,7 +968,7 @@ class LightAgent:
|
|
|
720
968
|
# 更新响应
|
|
721
969
|
if function_call_name == 'finish':
|
|
722
970
|
return # 如果最后调用了finish工具,则结束生成器
|
|
723
|
-
self.log("DEBUG", "chat-completions params", {"params": params})
|
|
971
|
+
self.log("DEBUG", "stream chat-completions params", {"params": params})
|
|
724
972
|
response = self.client.chat.completions.create(**params)
|
|
725
973
|
|
|
726
974
|
# 重试次数用尽
|
|
@@ -1024,6 +1272,7 @@ class LightAgent:
|
|
|
1024
1272
|
# 在函数内部定义工具信息
|
|
1025
1273
|
get_weather.tool_info = {
|
|
1026
1274
|
"tool_name": "get_weather",
|
|
1275
|
+
"tool_title": "天气查询",
|
|
1027
1276
|
"tool_description": "获取指定城市的当前天气信息",
|
|
1028
1277
|
"tool_params": [
|
|
1029
1278
|
{"name": "city_name", "description": "要查询的城市名称", "type": "string", "required": True},
|
|
@@ -1065,7 +1314,7 @@ get_weather.tool_info = {
|
|
|
1065
1314
|
f.write(tool_code)
|
|
1066
1315
|
self.log("INFO", "tool_created", {"tool_name": tool_name, "tool_path": tool_path})
|
|
1067
1316
|
|
|
1068
|
-
#
|
|
1317
|
+
# 自动加载新创建的工具
|
|
1069
1318
|
self.load_tools([tool_name], tools_directory)
|
|
1070
1319
|
except Exception as e:
|
|
1071
1320
|
self.log("ERROR", "tool_creation_failed", {"error": str(e)})
|
|
@@ -1106,4 +1355,4 @@ class LightSwarm:
|
|
|
1106
1355
|
if __name__ == "__main__":
|
|
1107
1356
|
# Example of registering and using a tool
|
|
1108
1357
|
print("This is LightAgent")
|
|
1109
|
-
# print(dispatch_tool("example_tool", {"param1": "test"}))
|
|
1358
|
+
# print(dispatch_tool("example_tool", {"param1": "test"}))
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: LightAgent
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
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.
|
|
3
|
+
version = "0.3.0"
|
|
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
|