1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
| def parse_code_blobs(text: str) -> str: """从 LLM 输出中提取可执行的代码块""" code_matches = [] search_pos = 0 while True: start = text.find("<code>", search_pos) if start == -1: break content_start = start + len("<code>") end = text.find("</code>", content_start) if end == -1: break code_matches.append(text[content_start:end]) search_pos = end + len("</code>")
if code_matches: return "\n\n".join(match.strip() for match in code_matches)
run_matches = [] search_pos = 0 run_tag = "```<RUN>" while True: start = text.find(run_tag, search_pos) if start == -1: break content_start = start + len(run_tag) end = text.find("```", content_start) if end == -1: break run_matches.append(text[content_start:end]) search_pos = end + len("```")
if run_matches: return "
".join(match.strip() for match in run_matches)
```text
这种设计的好处是: - **安全**:代码在沙箱中执行,不会污染主进程 - **可观测**:每一步执行都有清晰的输出 - **可控**:框架控制最大步数(`max_steps`),防止无限循环
```python
class NexentAgent: def __init__(self, observer: MessageObserver, model_config_list: List[ModelConfig], stop_event: Event, mcp_tool_collection=None): self.observer = observer self.model_config_list = model_config_list self.stop_event = stop_event self.mcp_tool_collection = mcp_tool_collection self.agent = None
def create_model(self, model_cite_name: str): """根据别名创建模型实例""" model_config = next( (mc for mc in self.model_config_list if mc is not None and mc.cite_name == model_cite_name), None ) if model_config is None: raise ValueError(f"Model {model_cite_name} not found")
model = OpenAIModel( observer=self.observer, model_id=model_config.model_name, api_key=model_config.api_key, api_base=model_config.url, temperature=model_config.temperature, top_p=model_config.top_p, ssl_verify=model_config.ssl_verify or True, model_factory=model_config.model_factory ) model.stop_event = self.stop_event return model
def create_local_tool(self, tool_config: ToolConfig): """从 ToolConfig 创建本地工具实例""" class_name = tool_config.class_name params = tool_config.params tool_class = globals().get(class_name) if tool_class is None: raise ValueError(f"{class_name} not found in local") ```text
关键设计:**工具通过字符串类名动态实例化**,这是零代码平台的核心——配置驱动,而非硬编码。
Nexent 的记忆系统是它区别于其他 Agent 平台的重要特色。它基于 **mem0** 构建,提供**四级记忆隔离**:
```python
""" SDK-level wrapper around mem0 Memory that keeps an in-process cache. """ from mem0.memory.main import AsyncMemory from .embedder_adaptor import EmbedderAdaptor
_MEMORY_CACHE: dict[str, AsyncMemory] = {} _CACHE_LOCKS: dict[int, asyncio.Lock] = {}
async def get_memory_instance(memory_config: Dict[str, Any]) -> AsyncMemory: """返回(并缓存)一个 mem0 Memory 实例""" _validate_config(memory_config) cache_key = _hash_config(memory_config)
async with _get_cache_lock(): if cache_key in _MEMORY_CACHE: logger.debug("Memory cache hit.") return _MEMORY_CACHE[cache_key]
memory_obj = await AsyncMemory.from_config(memory_config) memory_obj.embedding_model = EmbedderAdaptor(memory_config["embedder"]["config"]) _MEMORY_CACHE[cache_key] = memory_obj return memory_obj ```text
```python
def _filter_by_memory_level(memory_level: str, raw_results: List[Dict]) -> List[Dict]: """按记忆级别过滤结果""" if memory_level in {"tenant", "user"}: return [r for r in raw_results if not r.get("agent_id")] elif memory_level in {"agent", "user_agent"}: return [r for r in raw_results if r.get("agent_id")] ```text
| 记忆级别 | 说明 | 典型场景 | |---------|------|---------| | `tenant` | 租户共享知识 | 公司规章制度、产品文档 | | `user` | 用户私有知识 | 个人笔记、偏好设置 | | `agent` | Agent 专用知识 | Agent 的专业技能 | | `user_agent` | 用户+Agent 组合 | 用户的 Agent 个性化配置 |
这种设计的精妙之处在于:**通过不同粒度的记忆隔离,实现"知识即服务"的多租户架构**。
```python async def add_memory( messages: List[Dict] | str, memory_level: str, memory_config: Dict, tenant_id: str, user_id: str, agent_id: Optional[str] = None, infer: bool = True ) -> Any: """添加记忆""" mem_user_id = build_memory_identifiers( memory_level=memory_level, user_id=user_id, tenant_id=tenant_id ) memory = await get_memory_instance(memory_config)
if memory_level in {"tenant", "user"}: return await memory.add(messages, user_id=mem_user_id, infer=infer) elif memory_level in {"agent", "user_agent"}: return await memory.add(messages, agent_id=agent_id, user_id=mem_user_id, infer=infer) ```text
`infer=True` 时,mem0 会自动从消息中提取关键信息并结构化存储,而不仅仅是原始文本存储。
Nexent 的工具系统分为三层:
```mermaid graph LR subgraph 工具注册层 A[工具配置存储<br/>tool_db] B[MCP Server 注册<br/>remote_mcp_db] end
subgraph 工具执行层 C[本地工具<br/>20+ 内置工具] D[MCP 工具<br/>容器化执行] E[外部 API 工具] end
subgraph 工具抽象层 F[ToolConfig 统一封装] G[smolagents Tool 接口] end
A --> F B --> D C --> F E --> F F --> G ```text
Nexent SDK 内置了 **20+ 工具**,覆盖文件操作、搜索、邮件、多模态等场景:
```python
__all__ = [ "ExaSearchTool", "KnowledgeBaseSearchTool", "DifySearchTool", "DataMateSearchTool", "IdataSearchTool", "TavilySearchTool", "LinkupSearchTool", "CreateFileTool", "ReadFileTool", "DeleteFileTool", "CreateDirectoryTool", "DeleteDirectoryTool", "MoveItemTool", "ListDirectoryTool", "SendEmailTool", "GetEmailTool", "TerminalTool", "AnalyzeTextFileTool", "AnalyzeImageTool", "run_skill_script", "read_skill_md", "read_skill_config" ] ```text
MCP(Model Context Protocol)是 Anthropic 提出的工具标准化协议。Nexent 的 MCP 集成架构:
```python
```text
Nexent 支持将 MCP 工具**无缝桥接**到 Agent 的工具集中,这意味着: - Agent 可以使用任何符合 MCP 规范的第三方工具 - 工具替换不需要修改核心代码 - 安全隔离:MCP Server 运行在独立容器中
Nexent 支持通过 **A2A(Agent-to-Agent)协议**实现多 Agent 协作:
```python
@dataclass class A2AAgentInfo: """外部 A2A Agent 配置""" agent_id: str name: str url: str api_key: Optional[str] = None transport_type: str = "http-streaming" protocol_version: str = "1.0" protocol_type: str = PROTOCOL_JSONRPC timeout: float = 300.0
class ExternalA2AAgentProxy: """调用外部 A2A Agent 的代理类""" def get_skills_description(self) -> str: """从 Agent Card 中提取能力描述""" skills = [] if self.raw_card: skills = self.raw_card.get("skills", []) capability_names = [s.get("name", "") for s in skills if s.get("name")] return f"External A2A agent: {self.name} [Capabilities: {', '.join(capability_names)}]" ```text
A2A 协议的三种传输类型: - **JSONRPC**:标准 JSON-RPC 2.0 - **HTTP+JSON**:RESTful JSON - **GRPC**:高性能 gRPC(未来支持)
---
```python
class AgentRunManager: _instance = None _lock = threading.Lock()
def __new__(cls): if cls._instance is None: with cls._lock: if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance
def register_agent_run(self, conversation_id: int, agent_run_info, user_id: str): """注册 Agent 运行实例""" run_key = self._get_run_key(conversation_id, user_id) self.agent_runs[run_key] = agent_run_info
def stop_agent_run(self, conversation_id: int, user_id: str) -> bool: """停止指定 Agent 运行""" agent_run_info = self.get_agent_run_info(conversation_id, user_id) if agent_run_info is not None: agent_run_info.stop_event.set() return True return False ```text
关键设计:**单例模式 + 线程安全**的运行管理器,确保同一 conversation_id 在同一时刻只有一个活跃 Agent 实例。
Nexent 提供了完整的 Agent 版本管理:
- **草稿版本(version_no=0)**:开发中,可随时修改 - **已发布版本**:不可修改,用于生产 - **版本对比**:Compare API 可以对比两个版本的差异 - **版本回滚**:将当前版本指针指向历史版本(不创建新版本)
---
| 维度 | 说明 | |------|------| | **架构简洁性** | 基于 smolagents 封装,避免重复造轮子;SDK 与后端解耦,可独立使用 | | **扩展性** | MCP 协议兼容,工具生态丰富;A2A 协议支持多 Agent 协作 | | **易用性** | 零代码 Web UI,自然语言即可生成 Agent;Docker 一键部署 | | **多租户** | 四级记忆隔离,Tenant/User/Agent/User-Agent 粒度分明 | | **可观测性** | 内置 MessageObserver,每个步骤均可观测 |
| 维度 | 说明 | |------|------| | **性能** | smolagents 的代码执行机制在高并发场景下可能有瓶颈 | | **复杂度** | 多层抽象(SDK→smolagents→mem0→向量库)导致调试困难 | | **维护性** | 依赖外部库版本(mem0、smolagents),这些库本身迭代频繁 | | **学习曲线** | Harness Engineering 概念较新,需要理解其设计哲学 | | **生产验证** | Star 4,361(截至 2026-04),生产案例相对较少 |
---
| 维度 | Nexent | LangChain | CrewAI | AutoGen | |------|--------|-----------|--------|---------| | **核心定位** | 零代码 Agent 生成平台 | Agent 编排框架 | 多 Agent 角色扮演 | 多 Agent 对话框架 | | **记忆方案** | mem0(四级隔离) | 内存/向量存储 | 短时 + 自定义 | 会话历史 | | **工具协议** | MCP 兼容 + 内置 20+ | LangChain Tools | 自定义 Tools | 自定义 Tools | | **多 Agent** | A2A 协议 | 链式调用 | Agent 团队协作 | Agent 间对话 | | **代码执行** | smolagents 引擎 | Python 解释器 | Python REPL | 代码执行 | | **部署方式** | Docker Compose | 任意 | 任意 | 任意 | | **上手门槛** | 低(零代码 UI) | 高(需要编码) | 中(YAML 配置) | 中(需要编码) |
1. **Nexent vs LangChain**: - LangChain 是"框架式",Nexent 是"平台式" - Nexent 通过零代码 UI 降低门槛,LangChain 需要编码 - LangChain 的抽象更细粒度,定制能力更强
2. **Nexent vs CrewAI**: - CrewAI 强调"角色扮演",Nexent 强调"任务完成" - CrewAI 的 Agent 协作是预设的团队结构,Nexent 是动态的 A2A 协议 - Nexent 的四级记忆隔离比 CrewAI 的记忆方案更系统化
3. **Harness Engineering 理念**: - 核心思想是"约束即自由"——通过标准化约束,让 LLM 的输出更可控 - 对比 LangChain 的"everything is configurable",Nexent 更强调"opinionated defaults"
---
```bash git clone https://github.com/ModelEngine-Group/nexent.git cd nexent/docker cp .env.example .env bash deploy.sh ```text
访问 `http://localhost:3000` 即可使用 Web UI。
```python
pip install nexent
from nexent.core.agents.nexent_agent import NexentAgent from nexent.core.models.openai_llm import OpenAIModel from nexent.core.utils.observer import MessageObserver, ProcessType from nexent.memory.memory_core import get_memory_instance from threading import Event
observer = MessageObserver()
model_configs = [...] stop_event = Event()
agent_factory = NexentAgent( observer=observer, model_config_list=model_configs, stop_event=stop_event )
model = agent_factory.create_model("gpt-4")
|