From d30c7e37a6e84a47d1f1f135578659c6701706d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9D=E5=A4=95?= Date: Mon, 4 May 2026 16:38:21 +0800 Subject: [PATCH] =?UTF-8?q?chore(release)=EF=BC=9A=20v0.1.1-alpha?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ##前端美化和bug修复 #### 💄 美化 - **前端美化**:对于整个前端效果进行了重新设计,现在的前端看起来会更立体。 #### 🐛 修复 - **前端演示**:修复了前端展示workflow列表的bug,但是workflow的具体条目显示由于序列化导致仍然有问题。 - **密钥修复**:对于secret_key现在在使用默认情况时,会强制生成一个安全的密钥。 --- .dockerignore | 2 +- .env => .env.template | 0 changelogs/CHANGELOG.md | 12 +- frontend/package-lock.json | 234 +++++++++++++++- frontend/package.json | 1 + frontend/src/App.tsx | 149 +++++++--- frontend/src/components/Agent/AgentLayout.tsx | 54 ++-- .../Agent/WorkerIndividualSettings.tsx | 28 +- frontend/src/components/Chat/ChatPanel.tsx | 255 +++++++++++------- frontend/src/components/Chat/LeftPanel.tsx | 213 ++++++++------- frontend/src/components/Chat/RightPanel.tsx | 247 ++++++++--------- .../src/components/Chat/WorkflowDiagram.tsx | 118 ++++++++ .../src/components/Chat/WorkflowListView.tsx | 128 +++++++++ .../components/Layout/CollapsibleSidebar.tsx | 100 +++++++ frontend/src/components/Layout/Sidebar.tsx | 51 ---- frontend/src/components/Layout/TopBar.tsx | 59 ++++ .../src/components/Plugin/PluginLayout.tsx | 49 ++++ .../{Resource => Plugin}/SkillSettings.tsx | 8 +- .../{Resource => Plugin}/ToolSettings.tsx | 0 .../WorkflowTemplateSettings.tsx | 6 +- .../components/Resource/ResourceLayout.tsx | 52 ---- frontend/src/types/index.ts | 2 + main.py | 26 +- pretor/adapter/model_adapter/agent_factory.py | 2 + .../model_adapter/deepseek_reasoner.py | 35 ++- pretor/api/agent.py | 40 +++ pretor/api/auth.py | 14 + pretor/api/platform/event.py | 2 + pretor/api/platform/frontend.py | 24 +- pretor/api/provider.py | 14 + pretor/api/resource.py | 30 +++ pretor/api/workflow.py | 33 ++- pretor/core/api/__init__.py | 2 + pretor/core/database/database_exception.py | 7 + pretor/core/database/module/event.py | 45 ++++ pretor/core/database/module/individual.py | 24 ++ pretor/core/database/module/memory.py | 68 ----- pretor/core/database/module/provider.py | 16 ++ pretor/core/database/module/system_node.py | 13 + pretor/core/database/module/user.py | 29 ++ pretor/core/database/postgres.py | 99 ++++++- pretor/core/database/table/event.py | 5 + pretor/core/database/table/individual.py | 4 + pretor/core/database/table/provider.py | 2 + pretor/core/database/table/system_node.py | 2 + pretor/core/database/table/user.py | 4 + .../global_state_machine.py | 118 ++++---- .../global_workflow_manager.py | 187 +++++++++++++ .../individual_manager.py | 13 + .../model_provider/base_provider.py | 8 + .../model_provider/claude_provider.py | 17 +- .../model_provider/deepseek_provider.py | 14 + .../model_provider/openai_provider.py | 14 + .../global_state_machine/provider_manager.py | 19 ++ .../global_state_machine/skill_manager.py | 2 + .../core/global_state_machine/tool_manager.py | 2 + .../consciousness_node/consciousness_node.py | 14 + .../individual/consciousness_node/template.py | 10 + .../individual/control_node/control_node.py | 14 + .../core/individual/control_node/template.py | 8 + .../supervisory_node/supervisory_node.py | 10 +- .../individual/supervisory_node/template.py | 10 + pretor/core/workflow/workflow.py | 13 + pretor/core/workflow/workflow_runner.py | 35 ++- .../workflow_template.py | 7 + .../workflow_template_generator.py | 6 + .../workflow/workflow_template_manager.py | 20 ++ pretor/plugin/__init__.py | 13 + .../plugin/tool_plugin/approval/__init__.py | 14 + .../plugin/tool_plugin/approval/approval.py | 2 + pretor/plugin/tool_plugin/base_tool.py | 2 + .../tool_plugin/file_reader/__init__.py | 14 + .../tool_plugin/file_reader/file_reader.py | 2 + pretor/utils/access.py | 31 +++ pretor/utils/agent_model.py | 6 + pretor/utils/banner.py | 17 ++ pretor/utils/check_user/role_check.py | 10 + pretor/utils/error.py | 18 ++ pretor/utils/get_tool.py | 14 +- pretor/utils/logger.py | 11 + pretor/utils/pickle.py | 3 + pretor/utils/ray_hook.py | 15 ++ pretor/utils/retry.py | 28 ++ pretor/worker_individual/__init__.py | 14 + pretor/worker_individual/base_individual.py | 18 ++ .../worker_individual/ordinary_individual.py | 4 + pretor/worker_individual/skill_individual.py | 4 + .../worker_individual/special_individual.py | 4 + pretor/worker_individual/worker_cluster.py | 12 + tests/core/database/module/memory_test.py | 145 ---------- .../global_state_machine_test.py | 51 ---- tests/core/workflow/workflow_runner_test.py | 11 +- 92 files changed, 2449 insertions(+), 863 deletions(-) rename .env => .env.template (100%) create mode 100644 frontend/src/components/Chat/WorkflowDiagram.tsx create mode 100644 frontend/src/components/Chat/WorkflowListView.tsx create mode 100644 frontend/src/components/Layout/CollapsibleSidebar.tsx delete mode 100644 frontend/src/components/Layout/Sidebar.tsx create mode 100644 frontend/src/components/Layout/TopBar.tsx create mode 100644 frontend/src/components/Plugin/PluginLayout.tsx rename frontend/src/components/{Resource => Plugin}/SkillSettings.tsx (93%) rename frontend/src/components/{Resource => Plugin}/ToolSettings.tsx (100%) rename frontend/src/components/{Resource => Plugin}/WorkflowTemplateSettings.tsx (94%) delete mode 100644 frontend/src/components/Resource/ResourceLayout.tsx create mode 100644 pretor/core/database/module/event.py delete mode 100644 pretor/core/database/module/memory.py create mode 100644 pretor/core/database/table/event.py create mode 100644 pretor/core/global_state_machine/global_workflow_manager.py delete mode 100644 tests/core/database/module/memory_test.py diff --git a/.dockerignore b/.dockerignore index e11a476..acf91de 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,6 +6,6 @@ __pycache__ frontend/node_modules frontend/dist docker-compose.yml -.env +.env.template .env.example .idea \ No newline at end of file diff --git a/.env b/.env.template similarity index 100% rename from .env rename to .env.template diff --git a/changelogs/CHANGELOG.md b/changelogs/CHANGELOG.md index 676607f..5376efe 100644 --- a/changelogs/CHANGELOG.md +++ b/changelogs/CHANGELOG.md @@ -1,9 +1,19 @@ # ChangeLog +--- +## [v0.1.1Alpha] - 2026/5/4 +### 更新: +#### 💄 美化 +- **前端美化**:对于整个前端效果进行了重新设计,现在的前端看起来会更立体。 + +#### 🐛 修复 +- **前端演示**:修复了前端展示workflow列表的bug,但是workflow的具体条目显示由于序列化导致仍然有问题。 +- **密钥修复**:对于secret_key现在在使用默认情况时,会强制生成一个安全的密钥。 + --- ## [v0.1.0Alpha] - 2026/4/28 ### 更新: -#### 🚀 新增功能 (Added) +#### 🚀 新增功能 - **分布式 Actor 骨架**:基于 Ray 框架构建了多智能体协作底座,支持节点跨进程通讯与资源调度。 - **全局状态机 (GSM)**:实现了 `GlobalStateMachine` 模块,作为系统的“唯一真相来源”,管理所有 Individual、Skill 和 Provider 的注册信息。 - **核心认知节点**: diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5165204..7afc37f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "pretor-dashboard", "version": "0.0.0", "dependencies": { + "@xyflow/react": "^12.10.2", "axios": "^1.15.1", "lucide-react": "^1.8.0", "react": "^19.2.4", @@ -1153,6 +1154,55 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1181,7 +1231,7 @@ "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -1518,6 +1568,38 @@ } } }, + "node_modules/@xyflow/react": { + "version": "12.10.2", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.2.tgz", + "integrity": "sha512-CgIi6HwlcHXwlkTpr0fxLv/0sRVNZ8IdwKLzzeCscaYBwpvfcH1QFOCeaTCuEn1FQEs/B8CjnTSjhs8udgmBgQ==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.76", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.76", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.76.tgz", + "integrity": "sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -1724,6 +1806,12 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1789,9 +1877,114 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -3461,6 +3654,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vite": { "version": "8.0.8", "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", @@ -3607,6 +3809,34 @@ "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } + }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } } } } diff --git a/frontend/package.json b/frontend/package.json index 58eb229..b33947f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "@xyflow/react": "^12.10.2", "axios": "^1.15.1", "lucide-react": "^1.8.0", "react": "^19.2.4", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index db4bb5c..ff03083 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,25 +1,58 @@ import { useState, useEffect } from 'react'; -import { Sidebar } from './components/Layout/Sidebar'; +import { TopBar } from './components/Layout/TopBar'; +import { CollapsibleSidebar } from './components/Layout/CollapsibleSidebar'; import { SettingsLayout } from './components/Settings/SettingsLayout'; import { AgentLayout } from './components/Agent/AgentLayout'; -import { ResourceLayout } from './components/Resource/ResourceLayout'; +import { PluginLayout } from './components/Plugin/PluginLayout'; // Will rename to PluginLayout soon import { LeftPanel } from './components/Chat/LeftPanel'; import { ChatPanel } from './components/Chat/ChatPanel'; import { RightPanel } from './components/Chat/RightPanel'; +import { WorkflowListView } from './components/Chat/WorkflowListView'; import { AuthPage } from './components/Auth/AuthPage'; +// For Chat Module State Persistence +export interface Message { + id: string; + role: 'user' | 'assistant' | 'system'; + content: string; + timestamp: number; +} + +export interface ChatSession { + id: string; + title: string; + messages: Message[]; + updatedAt: number; +} + function App() { const [isAuthenticated, setIsAuthenticated] = useState(false); - const [activeTab, setActiveTab] = useState('chats'); // For LeftPanel - const [currentView, setCurrentView] = useState('dashboard'); // 'dashboard', 'settings', 'agent', 'resource' - const [settingsTab, setSettingsTab] = useState('users'); // For SettingsLayout - const [agentTab, setAgentTab] = useState('worker'); // For AgentLayout - const [resourceTab, setResourceTab] = useState('skill'); // For ResourceLayout + // Layout State + const [mode, setMode] = useState<'work' | 'agent'>('work'); + const [showSettings, setShowSettings] = useState(false); + const [isSidebarOpen, setIsSidebarOpen] = useState(true); + + // Module Sub-navigation States + // Work Mode + const [workTab, setWorkTab] = useState<'chat' | 'workflow'>('chat'); const [selectedWorkflow, setSelectedWorkflow] = useState(null); + // Agent Mode + const [agentTab, setAgentTab] = useState<'plugin' | 'agents'>('plugin'); + + // Settings Sub-tab + const [settingsTab, setSettingsTab] = useState('users'); + + // Inner Agent Tab (temporary until full Agent layout rewrite) + const [innerAgentTab, setInnerAgentTab] = useState('worker'); + const [resourceTab, setResourceTab] = useState('skill'); + + // Chat State Hoisted for Persistence + const [chatSessions, setChatSessions] = useState([]); + const [activeSessionId, setActiveSessionId] = useState(null); + useEffect(() => { - // Check if token exists in localStorage on mount const token = localStorage.getItem('token'); if (token) { setIsAuthenticated(true); @@ -31,37 +64,85 @@ function App() { } return ( -
+
+ {/* 1. Top Bar */} + - {/* 1. Sidebar (Leftmost) */} - + {/* 2. Main Content Area */} +
+ {showSettings ? ( + + ) : ( + <> + {/* Collapsible Main Sidebar */} + - {/* Main Content Area depending on view */} - {currentView === 'agent' ? ( - - ) : currentView === 'resource' ? ( - - ) : currentView === 'dashboard' ? ( - <> - {/* 2. Left Panel - Cluster Status & Workflows/Chats */} - + {/* Dynamic View based on Mode and Tab */} +
+ {mode === 'work' && workTab === 'chat' && ( +
+
+ {}} + // Pass hoisted state down + chatSessions={chatSessions} + setChatSessions={setChatSessions} + activeSessionId={activeSessionId} + setActiveSessionId={setActiveSessionId} + /> + +
+
+ )} - {/* 3. Middle Panel - AI Chat */} - + {mode === 'work' && workTab === 'workflow' && ( + <> + {selectedWorkflow ? ( + <> + + + + ) : ( + + )} + + )} - {/* 4. Right Panel - Workflow Execution Status (Only show when viewing workflows) */} - {activeTab === 'workflows' && } - - ) : ( - /* Settings View */ - - )} + {mode === 'agent' && agentTab === 'agents' && ( + + )} + {mode === 'agent' && agentTab === 'plugin' && ( + + )} +
+ + )} +
); } diff --git a/frontend/src/components/Agent/AgentLayout.tsx b/frontend/src/components/Agent/AgentLayout.tsx index ce21534..5c3f842 100644 --- a/frontend/src/components/Agent/AgentLayout.tsx +++ b/frontend/src/components/Agent/AgentLayout.tsx @@ -1,4 +1,3 @@ -import { Bot, Key } from 'lucide-react'; import { ProvidersSettings } from './ProvidersSettings'; import { WorkerIndividualSettings } from './WorkerIndividualSettings'; @@ -9,35 +8,32 @@ interface AgentLayoutProps { export function AgentLayout({ agentTab, setAgentTab }: AgentLayoutProps) { return ( -
- {/* Agent Inner Sidebar */} -
-
-

Agents

-
-
- - -
-
+
+ {/* Top Tabs for Agent Module */} +
+ + +
- {/* Agent Main Content */} -
- {agentTab === 'worker' && } - {agentTab === 'providers' && } -
+ {/* Main Content */} +
+ {agentTab === 'worker' && } + {agentTab === 'providers' && } +
); } diff --git a/frontend/src/components/Agent/WorkerIndividualSettings.tsx b/frontend/src/components/Agent/WorkerIndividualSettings.tsx index de4d9a5..c0481aa 100644 --- a/frontend/src/components/Agent/WorkerIndividualSettings.tsx +++ b/frontend/src/components/Agent/WorkerIndividualSettings.tsx @@ -167,7 +167,7 @@ export function WorkerIndividualSettings() {
@@ -219,7 +219,7 @@ export function WorkerIndividualSettings() { {w.provider_title} / {w.model_id} -
@@ -265,7 +265,7 @@ export function WorkerIndividualSettings() { + diff --git a/frontend/src/components/Chat/LeftPanel.tsx b/frontend/src/components/Chat/LeftPanel.tsx index 391bc3c..cf6e493 100644 --- a/frontend/src/components/Chat/LeftPanel.tsx +++ b/frontend/src/components/Chat/LeftPanel.tsx @@ -1,44 +1,32 @@ import { useState, useEffect } from 'react'; -import { Server, Box, Cpu, HardDrive, List, MessageCircle } from 'lucide-react'; -import { useClusterState } from '../../hooks/useClusterState'; +import { Plus, Trash2 } from 'lucide-react'; import apiClient from '../../api/client'; import type { Workflow } from '../../types'; +import type { ChatSession } from '../../App'; interface LeftPanelProps { activeTab: string; - setActiveTab: (tab: string) => void; selectedWorkflow: string | null; setSelectedWorkflow: (id: string | null) => void; + // Hoisted state props (optional, since this panel is used for workflows too) + chatSessions?: ChatSession[]; + setChatSessions?: React.Dispatch>; + activeSessionId?: string | null; + setActiveSessionId?: React.Dispatch>; } -export function LeftPanel({ activeTab, setActiveTab, selectedWorkflow, setSelectedWorkflow }: LeftPanelProps) { - const { nodes } = useClusterState(); +export function LeftPanel({ + activeTab, + selectedWorkflow, + setSelectedWorkflow, + chatSessions, + setChatSessions, + activeSessionId, + setActiveSessionId, +}: LeftPanelProps) { const [workflows, setWorkflows] = useState([]); const [loadingWorkflows, setLoadingWorkflows] = useState(false); - const totalNodes = nodes.length; - const aliveNodes = nodes.filter(n => n.alive).length; - - let totalCpu = 0; - let usedCpu = 0; - let totalMemory = 0; - let usedMemory = 0; - - nodes.forEach(node => { - const nodeTotalCpu = node.resources?.CPU || 0; - const nodeRemainingCpu = node.remaining?.CPU || 0; - totalCpu += nodeTotalCpu; - usedCpu += (nodeTotalCpu - nodeRemainingCpu); - - const nodeTotalMem = node.resources?.memory || 0; - const nodeRemainingMem = node.remaining?.memory || 0; - totalMemory += nodeTotalMem; - usedMemory += (nodeTotalMem - nodeRemainingMem); - }); - - const cpuPercent = totalCpu > 0 ? (usedCpu / totalCpu) * 100 : 0; - const memPercent = totalMemory > 0 ? (usedMemory / totalMemory) * 100 : 0; - useEffect(() => { let intervalId: ReturnType; @@ -46,18 +34,16 @@ export function LeftPanel({ activeTab, setActiveTab, selectedWorkflow, setSelect if (isInitial) setLoadingWorkflows(true); try { const response = await apiClient.get('/api/v1/workflow/list'); - // Fallback parsing just in case it returns an object or array const data = response.data; let parsedWorkflows: Workflow[] = []; if (Array.isArray(data)) { parsedWorkflows = data; } else if (data && typeof data === 'object') { - // Suppose backend sends { workflows: [...] } parsedWorkflows = data.workflows || Object.values(data); } setWorkflows(parsedWorkflows); } catch (error) { - console.error("Failed to fetch workflows", error); + console.error('Failed to fetch workflows', error); setWorkflows([]); } finally { if (isInitial) setLoadingWorkflows(false); @@ -74,69 +60,56 @@ export function LeftPanel({ activeTab, setActiveTab, selectedWorkflow, setSelect }; }, [activeTab]); + const handleNewChat = () => { + if (!setChatSessions || !setActiveSessionId) return; + const newSession: ChatSession = { + id: Date.now().toString(), + title: 'New Chat', + messages: [ + { + id: Date.now().toString(), + role: 'assistant', + content: 'Hello! I am Pretor Assistant. How can I help you today?', + timestamp: Date.now(), + }, + ], + updatedAt: Date.now(), + }; + setChatSessions((prev) => [newSession, ...prev]); + setActiveSessionId(newSession.id); + }; + + const handleDeleteChat = (e: React.MouseEvent, id: string) => { + e.stopPropagation(); + if (!setChatSessions || !setActiveSessionId || !chatSessions) return; + const updated = chatSessions.filter((s) => s.id !== id); + setChatSessions(updated); + if (activeSessionId === id) { + setActiveSessionId(updated.length > 0 ? updated[0].id : null); + } + }; + return ( -
- {/* Top: Cluster Status */} -
-

- - Cluster Status -

-
-
-
- - Active Nodes -
- {aliveNodes} / {totalNodes || 0} -
- -
-
- - Cluster CPU -
- {cpuPercent.toFixed(1)}% -
-
-
-
- -
-
- - Cluster RAM -
- - {(totalMemory > 0 ? usedMemory / (1024 ** 3) : 0).toFixed(1)} GB - -
-
-
-
-
-
- - {/* Bottom: Tabs for Workflows & Basic Chats */} +
+ {/* Bottom: Tab Selection */}
-
- - + +
+ + {activeTab === 'chats' ? 'Chat History' : 'Workflows'} + + {activeTab === 'chats' && ( + + )}
-
+
{activeTab === 'workflows' && (
{loadingWorkflows ? ( @@ -148,11 +121,29 @@ export function LeftPanel({ activeTab, setActiveTab, selectedWorkflow, setSelect
setSelectedWorkflow(wf.event_id)} - className={`p-3 rounded-lg border cursor-pointer transition-all ${selectedWorkflow === wf.event_id ? 'border-blue-200 bg-blue-50 shadow-sm' : 'border-slate-100 hover:border-blue-200 hover:bg-slate-50'}`} + className={`p-3 rounded-lg border cursor-pointer transition-all ${ + selectedWorkflow === wf.event_id + ? 'border-blue-300 bg-blue-50 shadow-sm' + : 'border-slate-100 hover:border-blue-200 hover:bg-slate-50' + }`} >
- {wf.workflow_title || 'Unnamed Workflow'} - + + {wf.workflow_title || 'Unnamed Workflow'} + +

ID: {wf.event_id}

@@ -160,8 +151,42 @@ export function LeftPanel({ activeTab, setActiveTab, selectedWorkflow, setSelect )}
)} - {activeTab === 'chats' && ( + {activeTab === 'chats' && chatSessions && (
+ {chatSessions.length === 0 ? ( +
+ No chat history.
Click + to start a new chat. +
+ ) : ( + chatSessions.map((session) => ( +
setActiveSessionId?.(session.id)} + className={`group flex items-center justify-between p-3 rounded-lg border cursor-pointer transition-all ${ + activeSessionId === session.id + ? 'border-blue-300 bg-blue-50 shadow-sm' + : 'border-slate-100 hover:border-blue-200 hover:bg-slate-50' + }`} + > +
+

+ {session.title} +

+

+ {new Date(session.updatedAt).toLocaleDateString()} +

+
+ +
+ )) + )}
)}
diff --git a/frontend/src/components/Chat/RightPanel.tsx b/frontend/src/components/Chat/RightPanel.tsx index a13546f..bb9ae8a 100644 --- a/frontend/src/components/Chat/RightPanel.tsx +++ b/frontend/src/components/Chat/RightPanel.tsx @@ -1,35 +1,25 @@ import { useState, useEffect, useRef } from 'react'; -import { Terminal, Activity, RefreshCw, CheckCircle2, Circle, XCircle, Clock, Loader2 } from 'lucide-react'; +import { Terminal, RefreshCw, SendHorizontal, LayoutList, GitFork } from 'lucide-react'; import apiClient from '../../api/client'; -import type { WorkflowDetail, WorkflowStep } from '../../types'; +import type { WorkflowDetail } from '../../types'; +import { WorkflowDiagram } from './WorkflowDiagram'; interface RightPanelProps { selectedWorkflow: string | null; } -function stepStatusIcon(status: string) { - switch (status) { - case 'completed': - return ; - case 'running': - return ; - case 'failed': - return ; - default: - return ; - } -} - export function RightPanel({ selectedWorkflow }: RightPanelProps) { const [detail, setDetail] = useState(null); const [loading, setLoading] = useState(false); const [logs, setLogs] = useState([]); const [sseConnected, setSseConnected] = useState(false); + const [replyText, setReplyText] = useState(''); + const [activeTab, setActiveTab] = useState<'chat' | 'diagram'>('chat'); const eventSourceRef = useRef(null); + const logsEndRef = useRef(null); const fetchDetail = async (traceId: string) => { setLoading(true); - setLogs([]); try { const response = await apiClient.get(`/api/v1/workflow/${traceId}`); setDetail(response.data); @@ -48,6 +38,7 @@ export function RightPanel({ selectedWorkflow }: RightPanelProps) { } fetchDetail(selectedWorkflow); + setLogs([]); // Reset logs when changing workflow const protocol = window.location.protocol; const host = window.location.host; @@ -55,17 +46,13 @@ export function RightPanel({ selectedWorkflow }: RightPanelProps) { const es = new EventSource(`${apiBase}/api/v1/workflow/sse/${selectedWorkflow}`); eventSourceRef.current = es; - es.onopen = () => { - setSseConnected(true); - }; + es.onopen = () => setSseConnected(true); es.onmessage = (event) => { setLogs(prev => [...prev, event.data]); }; - es.onerror = () => { - setSseConnected(false); - }; + es.onerror = () => setSseConnected(false); const interval = setInterval(() => { fetchDetail(selectedWorkflow); @@ -78,127 +65,127 @@ export function RightPanel({ selectedWorkflow }: RightPanelProps) { }; }, [selectedWorkflow]); - const isActive = detail?.status === 'llm_working' || detail?.status === 'tool_working'; + useEffect(() => { + if (activeTab === 'chat' && logsEndRef.current) { + logsEndRef.current.scrollIntoView({ behavior: 'smooth' }); + } + }, [logs, activeTab]); - if (!selectedWorkflow) { - return ( -
- -

No Workflow Selected

-

Select a workflow from the left panel to view its details.

-
- ); - } + const handleReplySubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!replyText.trim() || !selectedWorkflow) return; + + const message = replyText.trim(); + setReplyText(''); + setLogs(prev => [...prev, `[You]: ${message}`]); + + try { + await apiClient.post(`/api/v1/workflow/reply/${selectedWorkflow}`, { message }); + } catch (err) { + console.error("Failed to send reply", err); + setLogs(prev => [...prev, `[System Error]: Failed to send reply.`]); + } + }; + + if (!selectedWorkflow) return null; return ( -
-
-

- - Workflow Detail -

-
- - {sseConnected ? 'Live' : '--'} +
+
+
+

+ + + {detail?.workflow_title || 'Workflow Details'} + +

+ + {sseConnected ? 'Live' : 'Disconnected'} -
+ + {/* Navigation Tabs */} +
+ + +
+ +
-
- {loading && !detail ? ( -
- - Loading... +
+ {activeTab === 'diagram' ? ( +
+ {detail?.steps && detail.steps.length > 0 ? ( + + ) : ( +
+ Workflow steps are not yet generated. +
+ )}
- ) : !detail ? ( -
Failed to load workflow details
) : ( - <> - {/* Header */} -
-

- {detail.workflow_title || 'Workflow'} -

-

ID: {detail.event_id}

- {detail.command && ( -

Command: {detail.command}

- )} -
- - {detail.status} - - - Step {detail.current_step}/{detail.steps.length} - -
-
- - {/* Steps */} - {detail.steps.length > 0 && ( -
-

Steps

-
- {detail.steps.map((step: WorkflowStep) => ( -
- {stepStatusIcon(step.status)} - {step.step} - {step.name} - {step.node} -
- ))} +
+ {/* Command Header */} + {detail?.command && ( +
+

Original Command

+

{detail.command}

-
- )} + )} - {detail.steps.length === 0 && ( -
- -

Workflow is being generated...

-
- )} - - {/* SSE Logs */} - {logs.length > 0 && ( -
-

Live Logs

-
- {logs.map((msg, idx) => ( -
-
-

{msg}

+ {/* Live Chat / Logs Area */} +
+ {logs.length === 0 ? ( +
+ Waiting for events... +
+ ) : ( + logs.map((log, index) => ( +
+ {log}
- ))} -
-
- )} + )) + )} +
+
- {logs.length === 0 && sseConnected && isActive && ( -
Waiting for live events...
- )} - + {/* Input Area */} +
+ setReplyText(e.target.value)} + placeholder="Reply to the workflow..." + className="w-full bg-white border border-slate-200 rounded-xl pl-4 pr-12 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent shadow-sm" + /> + +
+
)}
diff --git a/frontend/src/components/Chat/WorkflowDiagram.tsx b/frontend/src/components/Chat/WorkflowDiagram.tsx new file mode 100644 index 0000000..3e151a5 --- /dev/null +++ b/frontend/src/components/Chat/WorkflowDiagram.tsx @@ -0,0 +1,118 @@ +import { useEffect, useMemo } from 'react'; +import { + ReactFlow, + MiniMap, + Controls, + Background, + useNodesState, + useEdgesState, + MarkerType, + BackgroundVariant +} from '@xyflow/react'; +import '@xyflow/react/dist/style.css'; +import type { WorkflowStep } from '../../types'; + +interface WorkflowDiagramProps { + steps: WorkflowStep[]; + currentStep: number; + status: string; +} + +export function WorkflowDiagram({ steps, currentStep, status }: WorkflowDiagramProps) { + const isWorkflowActive = status === 'llm_working' || status === 'tool_working'; + + const initialNodes = useMemo(() => { + return steps.map((step, index) => { + const isCurrent = step.step === currentStep && isWorkflowActive; + const isCompleted = step.status === 'completed'; + const isFailed = step.status === 'failed'; + + let bgColor = '#ffffff'; + let borderColor = '#e2e8f0'; // slate-200 + let textColor = '#334155'; // slate-700 + + if (isCurrent) { + bgColor = '#eff6ff'; // blue-50 + borderColor = '#3b82f6'; // blue-500 + textColor = '#1e40af'; // blue-800 + } else if (isFailed) { + bgColor = '#fef2f2'; // red-50 + borderColor = '#ef4444'; // red-500 + textColor = '#991b1b'; // red-800 + } else if (isCompleted) { + bgColor = '#f0fdf4'; // green-50 + borderColor = '#22c55e'; // green-500 + textColor = '#166534'; // green-800 + } + + return { + id: step.step.toString(), + position: { x: 250, y: index * 120 + 50 }, + data: { + label: ( +
+
{step.node}
+
{step.name}
+
+ ) + }, + style: { + background: bgColor, + border: `2px solid ${borderColor}`, + borderRadius: '8px', + color: textColor, + boxShadow: isCurrent ? '0 4px 6px -1px rgba(59, 130, 246, 0.2)' : '0 1px 2px 0 rgba(0, 0, 0, 0.05)', + padding: '4px', + } + }; + }); + }, [steps, currentStep, isWorkflowActive]); + + const initialEdges = useMemo(() => { + const edges = []; + for (let i = 0; i < steps.length - 1; i++) { + edges.push({ + id: `e${steps[i].step}-${steps[i + 1].step}`, + source: steps[i].step.toString(), + target: steps[i + 1].step.toString(), + animated: steps[i].step === currentStep && isWorkflowActive, + style: { stroke: '#cbd5e1', strokeWidth: 2 }, + markerEnd: { + type: MarkerType.ArrowClosed, + color: '#cbd5e1', + }, + }); + } + return edges; + }, [steps, currentStep, isWorkflowActive]); + + const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + + // Update nodes and edges if props change + useEffect(() => { + setNodes(initialNodes); + setEdges(initialEdges); + }, [initialNodes, initialEdges, setNodes, setEdges]); + + return ( + + + + { + if (n.style?.background) return n.style.background as string; + return '#e2e8f0'; + }} + maskColor="rgba(248, 250, 252, 0.7)" + /> + + ); +} diff --git a/frontend/src/components/Chat/WorkflowListView.tsx b/frontend/src/components/Chat/WorkflowListView.tsx new file mode 100644 index 0000000..47c2393 --- /dev/null +++ b/frontend/src/components/Chat/WorkflowListView.tsx @@ -0,0 +1,128 @@ +import { useState, useEffect } from 'react'; +import apiClient from '../../api/client'; +import type { Workflow } from '../../types'; +import { PlayCircle, CheckCircle, XCircle, Clock } from 'lucide-react'; + +interface WorkflowListViewProps { + onSelectWorkflow: (id: string) => void; +} + +export function WorkflowListView({ onSelectWorkflow }: WorkflowListViewProps) { + const [workflows, setWorkflows] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchWorkflows = async () => { + try { + const response = await apiClient.get('/api/v1/workflow/list'); + const data = response.data; + let parsedWorkflows: Workflow[] = []; + if (Array.isArray(data)) { + parsedWorkflows = data; + } else if (data && typeof data === 'object') { + parsedWorkflows = data.workflows || Object.values(data); + } + setWorkflows(parsedWorkflows); + } catch (error) { + console.error('Failed to fetch workflows', error); + } finally { + setLoading(false); + } + }; + + fetchWorkflows(); + const intervalId = setInterval(fetchWorkflows, 5000); + return () => clearInterval(intervalId); + }, []); + + const getStatusIcon = (status?: string) => { + if (status === 'completed') return ; + if (status === 'failed') return ; + if (status === 'llm_working' || status === 'tool_working' || status?.includes('working')) + return ; + return ; + }; + + const getStatusBadge = (status?: string) => { + let colorClass = "bg-slate-100 text-slate-600"; + let label = "Waiting"; + + if (status === 'completed') { colorClass = "bg-green-100 text-green-700"; label = "Completed"; } + else if (status === 'failed') { colorClass = "bg-red-100 text-red-700"; label = "Failed"; } + else if (status?.includes('working')) { colorClass = "bg-blue-100 text-blue-700 animate-pulse"; label = "Running"; } + + return ( + + {label} + + ); + }; + + if (loading) { + return ( +
+
Loading Workflows...
+
+ ); + } + + return ( +
+
+

Workflows

+

Manage and monitor your automated processes.

+
+ + {workflows.length === 0 ? ( +
+
+ +
+

No Workflows Found

+

+ Workflows created from your chats will appear here automatically. +

+
+ ) : ( +
+ {workflows.map((wf) => ( +
onSelectWorkflow(wf.event_id)} + className="bg-white rounded-2xl p-6 border border-slate-200 shadow-sm hover:shadow-md hover:border-blue-300 transition-all cursor-pointer group flex flex-col h-full" + > +
+
+ {getStatusIcon(wf.status)} +
+ {getStatusBadge(wf.status)} +
+ +

+ {wf.workflow_title || 'Unnamed Workflow'} +

+ +
+ {wf.message && ( +
+ Command: + "{wf.message}" +
+ )} + +
+ + {wf.event_id} + + {wf.create_time && ( + {new Date(wf.create_time).toLocaleDateString()} + )} +
+
+
+ ))} +
+ )} +
+ ); +} diff --git a/frontend/src/components/Layout/CollapsibleSidebar.tsx b/frontend/src/components/Layout/CollapsibleSidebar.tsx new file mode 100644 index 0000000..450fcd9 --- /dev/null +++ b/frontend/src/components/Layout/CollapsibleSidebar.tsx @@ -0,0 +1,100 @@ +import { ChevronLeft, ChevronRight, MessageSquare, Workflow, Box, Bot } from 'lucide-react'; + +interface CollapsibleSidebarProps { + mode: 'work' | 'agent'; + isOpen: boolean; + setIsOpen: (isOpen: boolean) => void; + workTab: 'chat' | 'workflow'; + setWorkTab: (tab: 'chat' | 'workflow') => void; + agentTab: 'plugin' | 'agents'; + setAgentTab: (tab: 'plugin' | 'agents') => void; +} + +export function CollapsibleSidebar({ + mode, + isOpen, + setIsOpen, + workTab, + setWorkTab, + agentTab, + setAgentTab, +}: CollapsibleSidebarProps) { + + const getWorkNav = () => ( + <> + + + + ); + + const getAgentNav = () => ( + <> + + + + ); + + // Background is slightly darker than the page background but lighter than the topbar + // Let's make it the "main nav light blue" level. We will use bg-blue-50 for it, + // and make the page background essentially white or extremely light slate, + // but since the instruction says "page background is the next lightest", + // let's use bg-blue-100 for Sidebar and bg-blue-50 for page background. + return ( +
+
+ {mode === 'work' ? getWorkNav() : getAgentNav()} +
+ + +
+ ); +} diff --git a/frontend/src/components/Layout/Sidebar.tsx b/frontend/src/components/Layout/Sidebar.tsx deleted file mode 100644 index 28d0bdc..0000000 --- a/frontend/src/components/Layout/Sidebar.tsx +++ /dev/null @@ -1,51 +0,0 @@ - -import { Activity, MessageSquare, Settings, Bot, Box } from 'lucide-react'; - -interface SidebarProps { - currentView: string; - setCurrentView: (view: string) => void; -} - -export function Sidebar({ currentView, setCurrentView }: SidebarProps) { - return ( -
-
setCurrentView('dashboard')} - > - -
- -
- - - - -
-
- ); -} diff --git a/frontend/src/components/Layout/TopBar.tsx b/frontend/src/components/Layout/TopBar.tsx new file mode 100644 index 0000000..b560050 --- /dev/null +++ b/frontend/src/components/Layout/TopBar.tsx @@ -0,0 +1,59 @@ +import { Settings, BrainCircuit } from 'lucide-react'; + +interface TopBarProps { + mode: 'work' | 'agent'; + setMode: (mode: 'work' | 'agent') => void; + showSettings: boolean; + setShowSettings: (show: boolean) => void; +} + +export function TopBar({ mode, setMode, showSettings, setShowSettings }: TopBarProps) { + return ( +
+ {/* Left: Logo */} +
+ + Pretor +
+ + {/* Right Container: Mode Toggle Switch + Settings */} +
+ + {/* Mode Toggle Switch */} +
+ + +
+ + {/* Settings */} + +
+
+ ); +} diff --git a/frontend/src/components/Plugin/PluginLayout.tsx b/frontend/src/components/Plugin/PluginLayout.tsx new file mode 100644 index 0000000..afcb8fe --- /dev/null +++ b/frontend/src/components/Plugin/PluginLayout.tsx @@ -0,0 +1,49 @@ +import { SkillSettings } from './SkillSettings'; +import { ToolSettings } from './ToolSettings'; +import { WorkflowTemplateSettings } from './WorkflowTemplateSettings'; + +interface PluginLayoutProps { + resourceTab: string; + setResourceTab: (tab: string) => void; +} + +export function PluginLayout({ resourceTab, setResourceTab }: PluginLayoutProps) { + return ( +
+ {/* Top Tabs for Plugin Module */} +
+ + + +
+ + {/* Main Content */} +
+ {resourceTab === 'skill' && } + {resourceTab === 'workflow_template' && } + {resourceTab === 'tool' && } +
+
+ ); +} diff --git a/frontend/src/components/Resource/SkillSettings.tsx b/frontend/src/components/Plugin/SkillSettings.tsx similarity index 93% rename from frontend/src/components/Resource/SkillSettings.tsx rename to frontend/src/components/Plugin/SkillSettings.tsx index bb11fca..a6b8f8f 100644 --- a/frontend/src/components/Resource/SkillSettings.tsx +++ b/frontend/src/components/Plugin/SkillSettings.tsx @@ -79,7 +79,7 @@ export function SkillSettings() {
-
+
@@ -98,7 +98,7 @@ export function SkillSettings() { value={repoUrl} onChange={(e) => setRepoUrl(e.target.value)} placeholder="https://github.com/user/repo" - className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" + className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" />
@@ -108,7 +108,7 @@ export function SkillSettings() { value={path} onChange={(e) => setPath(e.target.value)} placeholder="e.g. subfolder/path" - className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" + className="w-full px-4 py-2 border border-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" />
@@ -120,7 +120,7 @@ export function SkillSettings() { - - -
-
- - {/* Resource Main Content */} -
- {resourceTab === 'skill' && } - {resourceTab === 'workflow_template' && } - {resourceTab === 'tool' && } -
-
- ); -} diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index cbd2e8c..2515f07 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -65,6 +65,8 @@ export interface Workflow { event_id: string; workflow_title: string; status?: string; + message?: string; + create_time?: string; } export interface WorkflowStep { diff --git a/main.py b/main.py index 8494a91..fbaa6d8 100644 --- a/main.py +++ b/main.py @@ -4,6 +4,7 @@ from pretor.worker_individual.worker_cluster import WorkerCluster from pretor.utils.banner import print_banner from pretor.core.database.postgres import PostgresDatabase from pretor.core.global_state_machine.global_state_machine import GlobalStateMachine +from pretor.core.global_state_machine.global_workflow_manager import GlobalWorkflowManager from pretor.core.individual.supervisory_node.supervisory_node import SupervisoryNode from pretor.core.individual.consciousness_node.consciousness_node import ConsciousnessNode from pretor.core.individual.control_node.control_node import ControlNode @@ -11,8 +12,13 @@ from pretor.core.workflow.workflow_runner import WorkflowRunningEngine from pretor.core.api import PretorGateway from ray import serve import os +import secrets - +_secret_key = os.getenv("SECRET_KEY") +if not _secret_key or _secret_key in {"secret", "114514"}: + _secret_key = secrets.token_urlsafe(32) + os.environ["SECRET_KEY"] = _secret_key + print("⚠️ 警告: 未提供有效的 SECRET_KEY 或使用了不安全的默认值,已生成并设置随机密钥。") async def start_system(): env_vars = { @@ -21,7 +27,7 @@ async def start_system(): "POSTGRES_HOST": os.getenv("POSTGRES_HOST", "db"), "POSTGRES_PORT": os.getenv("POSTGRES_PORT", "5432"), "POSTGRES_DB": os.getenv("POSTGRES_DB", "postgres"), - "SECRET_KEY": os.getenv("SECRET_KEY", "secret"), + "SECRET_KEY": os.getenv("SECRET_KEY"), } ray.init(ignore_reinit_error=True, @@ -51,13 +57,19 @@ async def start_system(): print(f"\n[致命错误] GlobalStateMachine 启动失败!真实报错如下:\n{e}\n") return + global_workflow_manager = GlobalWorkflowManager.options( + name='global_workflow_manager', + namespace='pretor', + lifetime='detached' + ).remote() + # 4. 启动核心节点 supervisory_node = SupervisoryNode.options(name='supervisory_node').remote() consciousness_node = ConsciousnessNode.options(name='consciousness_node').remote() control_node = ControlNode.options(name='control_node').remote() try: - worker_cluster_actor = WorkerCluster.options( + WorkerCluster.options( name="worker_cluster", lifetime="detached" # 保证它在后台一直运行 ).remote() @@ -73,6 +85,14 @@ async def start_system(): # 异步拉起 runner 协程群 workflow_engine.run.remote() + print("正在等待 GlobalWorkflowManager 初始化与恢复工作流...") + try: + await global_workflow_manager.init_manager.remote() + print("GlobalWorkflowManager 初始化成功!") + except Exception as e: + print(f"\n[致命错误] GlobalWorkflowManager 启动失败!真实报错如下:\n{e}\n") + return + # 6. 启动 FastAPI 网关 (使用 Ray Serve) serve.start(http_options={"host": "0.0.0.0", "port": 8000}) serve.run(PretorGateway.bind()) diff --git a/pretor/adapter/model_adapter/agent_factory.py b/pretor/adapter/model_adapter/agent_factory.py index 725769f..ca3c9d4 100644 --- a/pretor/adapter/model_adapter/agent_factory.py +++ b/pretor/adapter/model_adapter/agent_factory.py @@ -23,6 +23,8 @@ from pretor.utils.agent_model import ResponseModel, DepsModel from pretor.utils.error import ModelNotExistError class AgentFactory: + """AgentFactory 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 AgentFactory 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ def __init__(self): self._models_mapping = {"openai": (OpenAIChatModel, OpenAIProvider), "claude": (AnthropicModel, AnthropicProvider), diff --git a/pretor/adapter/model_adapter/deepseek_reasoner.py b/pretor/adapter/model_adapter/deepseek_reasoner.py index 131b0f8..520f566 100644 --- a/pretor/adapter/model_adapter/deepseek_reasoner.py +++ b/pretor/adapter/model_adapter/deepseek_reasoner.py @@ -1,17 +1,36 @@ +# Copyright 2026 zhaoxi826 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import re import json from typing import Type, TypeVar, Any, Generic from pydantic import BaseModel, ValidationError -from pydantic_ai import Agent, RunContext -from pydantic_ai.run import AgentRunResult +from pydantic_ai import Agent T = TypeVar('T', bound=BaseModel) class AgentRunResultProxy: + """AgentRunResultProxy 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 AgentRunResultProxy 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ def __init__(self, original, parsed): self._original = original self._parsed = parsed def __getattr__(self, name): + """检索并获取特定的 getattr 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: name: 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ if name == 'data': return self._parsed if name == 'output': @@ -78,6 +97,10 @@ class DeepSeekReasonerAgent(Generic[T]): ) def _parse_output(self, text: str) -> Any: + """执行与 parse output 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: text (str): 控制逻辑流向的具体字符串参数,指定了期望的 text 内容。 + Returns: (Any): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ if not self.has_custom_output: return text @@ -114,10 +137,18 @@ class DeepSeekReasonerAgent(Generic[T]): def __getattr__(self, item): # Delegate any unknown attributes (like .system_prompt, .tool) to the underlying pydantic_ai Agent + """检索并获取特定的 getattr 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: item: 参与 getattr 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return getattr(self.agent, item) async def run(self, user_prompt: str, deps: Any = None, message_history: list = None, **kwargs) -> Any: # Custom retry loop + """执行与 run 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: user_prompt (str): 控制逻辑流向的具体字符串参数,指定了期望的 user prompt 内容。 deps (Any): 参与 run 逻辑运算或数据构建的上下文依赖对象。 message_history (list): 批量操作所需的列表集合,囊括了需要统一处理的多个 message history 元素。 + Returns: (Any): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ current_history = message_history or [] last_exception = None diff --git a/pretor/api/agent.py b/pretor/api/agent.py index befdd53..72fd164 100644 --- a/pretor/api/agent.py +++ b/pretor/api/agent.py @@ -27,18 +27,26 @@ from pretor.core.database.table.user import UserAuthority agent_router = APIRouter(prefix="/api/v1/agent", tags=["agent"]) class AgentRegister(BaseModel): + """AgentRegister 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 AgentRegister 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ provider_title: str model_id: str individual_name: str tools: Optional[List[str]] = None class AgentLocalRegister(BaseModel): + """AgentLocalRegister 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 AgentLocalRegister 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ path: str individual_name: str tools: Optional[List[str]] = None @agent_router.get("") async def get_system_nodes(_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER))): + """处理针对 get system nodes 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: _ (TokenData): 参与 get system nodes 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ postgres_database = ray_actor_hook("postgres_database").postgres_database configs = await postgres_database.get_all_system_node_configs.remote() return {"system_nodes": configs} @@ -46,6 +54,10 @@ async def get_system_nodes(_: TokenData = Depends(RoleChecker(allowed_roles=User @agent_router.post("") async def load_agent(agent_register: Union[AgentRegister, AgentLocalRegister], _: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER))): + """处理针对 load agent 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: agent_register (Union[AgentRegister, AgentLocalRegister]): 参与 load agent 逻辑运算或数据构建的上下文依赖对象。 _ (TokenData): 参与 load agent 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ global_state_machine = ray_actor_hook("global_state_machine").global_state_machine postgres_database = ray_actor_hook("postgres_database").postgres_database @@ -81,6 +93,8 @@ async def load_agent(agent_register: Union[AgentRegister, AgentLocalRegister], class WorkerIndividualCreate(BaseModel): + """WorkerIndividualCreate 核心组件类。 + 这是一个具体的 Worker 智能体实体类,代表着具备特定人设、领域技能或长文本处理能力的数字员工。它可以被控制器动态拉起,并在安全沙箱内执行复杂的工作流指令与多步骤推理任务。 """ agent_name: str agent_type: AgentType description: str @@ -94,6 +108,8 @@ class WorkerIndividualCreate(BaseModel): class WorkerIndividualUpdate(BaseModel): + """WorkerIndividualUpdate 核心组件类。 + 这是一个具体的 Worker 智能体实体类,代表着具备特定人设、领域技能或长文本处理能力的数字员工。它可以被控制器动态拉起,并在安全沙箱内执行复杂的工作流指令与多步骤推理任务。 """ agent_name: Optional[str] = None agent_type: Optional[AgentType] = None description: Optional[str] = None @@ -109,6 +125,10 @@ class WorkerIndividualUpdate(BaseModel): @agent_router.post("/worker") async def create_worker_individual(worker_data: WorkerIndividualCreate, token_data: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER))): + """处理针对 create worker individual 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: worker_data (WorkerIndividualCreate): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 token_data (TokenData): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ postgres_database = ray_actor_hook("postgres_database").postgres_database data_dict = worker_data.model_dump() data_dict["owner_id"] = token_data.user_id @@ -118,6 +138,10 @@ async def create_worker_individual(worker_data: WorkerIndividualCreate, @agent_router.get("/worker") async def get_worker_individual_list(token_data: TokenData = Depends(Accessor.get_current_user)): + """处理针对 get worker individual list 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: token_data (TokenData): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ postgres_database = ray_actor_hook("postgres_database").postgres_database workers = await postgres_database.get_worker_individual_list.remote( owner_id=token_data.user_id) return {"workers": workers} @@ -126,6 +150,10 @@ async def get_worker_individual_list(token_data: TokenData = Depends(Accessor.ge @agent_router.get("/worker/{agent_id}") async def get_worker_individual(agent_id: str, token_data: TokenData = Depends(Accessor.get_current_user)): + """处理针对 get worker individual 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: agent_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 agent 实例。 token_data (TokenData): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ postgres_database = ray_actor_hook("postgres_database").postgres_database worker = await postgres_database.get_worker_individual.remote( agent_id=agent_id) if not worker: @@ -139,6 +167,10 @@ async def get_worker_individual(agent_id: str, async def update_worker_individual(agent_id: str, worker_data: WorkerIndividualUpdate, token_data: TokenData = Depends(Accessor.get_current_user)): + """处理针对 update worker individual 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: agent_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 agent 实例。 worker_data (WorkerIndividualUpdate): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 token_data (TokenData): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ postgres_database = ray_actor_hook("postgres_database").postgres_database worker = await postgres_database.get_worker_individual.remote( agent_id=agent_id) if not worker: @@ -159,6 +191,10 @@ async def update_worker_individual(agent_id: str, @agent_router.post("/worker/{agent_id}/reload") async def reload_worker_individual(agent_id: str, token_data: TokenData = Depends(Accessor.get_current_user)): + """处理针对 reload worker individual 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: agent_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 agent 实例。 token_data (TokenData): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ postgres_database = ray_actor_hook("postgres_database").postgres_database worker = await postgres_database.get_worker_individual.remote(agent_id=agent_id) if not worker: @@ -175,6 +211,10 @@ async def reload_worker_individual(agent_id: str, token_data: TokenData = Depend @agent_router.delete("/worker/{agent_id}") async def delete_worker_individual(agent_id: str, token_data: TokenData = Depends(Accessor.get_current_user)): + """处理针对 delete worker individual 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: agent_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 agent 实例。 token_data (TokenData): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ postgres_database = ray_actor_hook("postgres_database").postgres_database worker = await postgres_database.get_worker_individual.remote( agent_id=agent_id) if not worker: diff --git a/pretor/api/auth.py b/pretor/api/auth.py index 311bc88..d084233 100644 --- a/pretor/api/auth.py +++ b/pretor/api/auth.py @@ -25,22 +25,34 @@ from pretor.utils.error import UserNotExistError auth_router = APIRouter(prefix="/api/v1/auth", tags=["auth"]) class UserRegister(BaseModel): + """UserRegister 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 UserRegister 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ user_name: str password: str @auth_router.post("/register") async def create_user(user_register: UserRegister): + """处理针对 create user 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: user_register (UserRegister): 参与 create user 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ postgres_database = ray_actor_hook("postgres_database").postgres_database hashed_password = await run_in_threadpool(Accessor.hash_password, user_register.password) user = await postgres_database.add_user.remote( user_register.user_name, hashed_password) return {"message": "success", "user_id": user.user_id} class UserLogin(BaseModel): + """UserLogin 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 UserLogin 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ user_name: str password: str @auth_router.post("/login") async def login_user(user_login: UserLogin): + """处理针对 login user 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: user_login (UserLogin): 参与 login user 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ postgres_database = ray_actor_hook("postgres_database").postgres_database user = await postgres_database.login_user.remote( user_login.user_name) if not user: @@ -49,6 +61,8 @@ async def login_user(user_login: UserLogin): return {"message":"success", "token":token} class ChangeAuthorityRequest(BaseModel): + """ChangeAuthorityRequest 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 ChangeAuthorityRequest 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ user_id: str new_authority: UserAuthority diff --git a/pretor/api/platform/event.py b/pretor/api/platform/event.py index 847343f..8b0a68e 100644 --- a/pretor/api/platform/event.py +++ b/pretor/api/platform/event.py @@ -20,6 +20,8 @@ from pretor.core.workflow.workflow import PretorWorkflow import asyncio class PretorEvent(BaseModel): + """PretorEvent 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 PretorEvent 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ model_config = ConfigDict(arbitrary_types_allowed=True) trace_id: str = Field(default_factory=lambda: str(ULID()), description="事件的唯一标识符") platform: str = Field(description="消息来源的平台") diff --git a/pretor/api/platform/frontend.py b/pretor/api/platform/frontend.py index 21a2be2..329f82b 100644 --- a/pretor/api/platform/frontend.py +++ b/pretor/api/platform/frontend.py @@ -18,18 +18,25 @@ from pretor.utils.access import Accessor, TokenData from pretor.api.platform.event import PretorEvent from pretor.utils.ray_hook import ray_actor_hook import os -import shutil - +import anyio from pretor.utils.logger import get_logger + + logger = get_logger('frontend') client_router = APIRouter(prefix="/api/v1/adapter/client", tags=["client"]) class Message(BaseModel): + """Message 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 Message 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ message: str @client_router.post("") async def create_message(message: Message, token_data: TokenData = Depends(Accessor.get_current_user)): + """处理针对 create message 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: message (Message): 参与 create message 逻辑运算或数据构建的上下文依赖对象。 token_data (TokenData): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ logger.info("收到消息,来源:客户端") logger.debug(f"消息内容:{message.message}") event = PretorEvent(platform="client", @@ -38,8 +45,8 @@ async def create_message(message: Message, message=message.message) supervisory_node = ray_actor_hook("supervisory_node").supervisory_node message = await supervisory_node.working.remote(event) - if message == "任务已创建": - return {"message": event.trace_id} + if message.startswith("任务已创建"): + return {"message": f"{event.trace_id}\n\n{message}"} elif message == "未知相应类型": raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, @@ -50,12 +57,17 @@ async def create_message(message: Message, @client_router.post("/upload") async def upload_file(file: UploadFile = File(...), token_data: TokenData = Depends(Accessor.get_current_user)): + """处理针对 upload file 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: file (UploadFile): 参与 upload file 逻辑运算或数据构建的上下文依赖对象。 token_data (TokenData): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ try: upload_dir = "uploads" os.makedirs(upload_dir, exist_ok=True) file_path = os.path.join(upload_dir, file.filename) - with open(file_path, "wb") as buffer: - shutil.copyfileobj(file.file, buffer) + async with await anyio.open_file(file_path, "wb") as buffer: + while chunk := await file.read(64 * 1024): # 64KB chunks + await buffer.write(chunk) logger.info(f"用户 {token_data.username} 上传了文件: {file.filename}") return {"filename": file.filename, "message": f"File {file.filename} uploaded successfully"} except Exception as e: diff --git a/pretor/api/provider.py b/pretor/api/provider.py index 3ebfba5..6ec4087 100644 --- a/pretor/api/provider.py +++ b/pretor/api/provider.py @@ -25,6 +25,8 @@ from pretor.utils.ray_hook import ray_actor_hook provider_router = APIRouter(prefix="/api/v1/provider", tags=["provider"]) class ProviderRegister(BaseModel): + """ProviderRegister 核心组件类。 + 这是一个模型/服务提供商适配器类,屏蔽了外部不同供应商(如 OpenAI、Anthropic 等)的底层 API 差异。它负责标准化参数组装、网络请求发送、鉴权处理以及响应结构的反序列化。 """ provider_type: Literal["openai", "claude", "deepseek"] provider_title: str provider_url: str @@ -33,6 +35,10 @@ class ProviderRegister(BaseModel): @provider_router.post("") async def create_provider(provider_register: ProviderRegister, token_data: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER))) -> None: + """处理针对 create provider 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: provider_register (ProviderRegister): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_register 实例。 token_data (TokenData): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 + Returns: (None): 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ global_state_machine = ray_actor_hook("global_state_machine").global_state_machine await global_state_machine.add_provider_wrap.remote(provider_type=provider_register.provider_type, provider_title=provider_register.provider_title, @@ -43,12 +49,20 @@ async def create_provider(provider_register: ProviderRegister, @provider_router.get("/list") async def get_provider_list(_: TokenData = Depends(Accessor.get_current_user)) -> Dict[str, Dict[str, Provider]]: + """处理针对 get provider list 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: _ (TokenData): 参与 get provider list 逻辑运算或数据构建的上下文依赖对象。 + Returns: (Dict[str, Dict[str, Provider]]): 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ global_state_machine = ray_actor_hook("global_state_machine").global_state_machine provider_list: Dict[str, Provider] = await global_state_machine.get_provider_list.remote() return {"provider_list": provider_list} @provider_router.delete("/{provider_title}") async def delete_provider(provider_title: str, _: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.SUPER_ADMINISTRATOR))) -> dict: + """处理针对 delete provider 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: provider_title (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_title 实例。 _ (TokenData): 参与 delete provider 逻辑运算或数据构建的上下文依赖对象。 + Returns: (dict): 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ global_state_machine = ray_actor_hook("global_state_machine").global_state_machine await global_state_machine.delete_provider.remote(provider_title=provider_title) return {"message": "success"} \ No newline at end of file diff --git a/pretor/api/resource.py b/pretor/api/resource.py index af28e8a..08bd491 100644 --- a/pretor/api/resource.py +++ b/pretor/api/resource.py @@ -26,18 +26,30 @@ resource_router = APIRouter(prefix="/api/v1/resource") @resource_router.post("/workflow_template") async def create_workflow_template(workflow_template: WorkflowTemplate, _: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER))): + """处理针对 create workflow template 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: workflow_template (WorkflowTemplate): 参与 create workflow template 逻辑运算或数据构建的上下文依赖对象。 _ (TokenData): 参与 create workflow template 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ global_state_machine = ray_actor_hook("global_state_machine").global_state_machine await global_state_machine.add_workflow_template.remote( workflow_template.name, workflow_template) return {"message": "创建成功"} @resource_router.get("/workflow_template") async def get_workflow_templates(_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER))): + """处理针对 get workflow templates 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: _ (TokenData): 参与 get workflow templates 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ global_state_machine = ray_actor_hook("global_state_machine").global_state_machine templates = await global_state_machine.get_all_workflow_templates.remote() return {"templates": templates} @resource_router.delete("/workflow_template/{template_name}") async def delete_workflow_template(template_name: str, _: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.SUPER_ADMINISTRATOR))): + """处理针对 delete workflow template 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: template_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 _ (TokenData): 参与 delete workflow template 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ global_state_machine = ray_actor_hook("global_state_machine").global_state_machine await global_state_machine.delete_workflow_template.remote( template_name) return {"message": "success"} @@ -45,12 +57,18 @@ async def delete_workflow_template(template_name: str, _: TokenData = Depends(Ro class Skill(BaseModel): + """Skill 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 Skill 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ repo_url: str path: str | None @resource_router.post("/skill") async def install_skill(skill: Skill, _: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER))): + """处理针对 install skill 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: skill (Skill): 参与 install skill 逻辑运算或数据构建的上下文依赖对象。 _ (TokenData): 参与 install skill 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ global_state_machine = ray_actor_hook("global_state_machine").global_state_machine # noinspection PyUnresolvedReferences import os @@ -68,12 +86,20 @@ async def install_skill(skill: Skill, @resource_router.get("/skill") async def get_skills(_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER))): + """处理针对 get skills 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: _ (TokenData): 参与 get skills 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ global_state_machine = ray_actor_hook("global_state_machine").global_state_machine skills = await global_state_machine.get_skill_list.remote() return {"skills": skills} @resource_router.delete("/skill/{skill_name}") async def delete_skill(skill_name: str, _: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.SUPER_ADMINISTRATOR))): + """处理针对 delete skill 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: skill_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 _ (TokenData): 参与 delete skill 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ global_state_machine = ray_actor_hook("global_state_machine").global_state_machine # Note: this only removes it from the state machine manager. await global_state_machine.remove_skill.remote( skill_name) @@ -81,6 +107,10 @@ async def delete_skill(skill_name: str, _: TokenData = Depends(RoleChecker(allow @resource_router.get("/tool") async def get_tools(_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER))): + """处理针对 get tools 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: _ (TokenData): 参与 get tools 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ global_state_machine = ray_actor_hook("global_state_machine").global_state_machine tool_mapper = await global_state_machine.get_tool_mapper.remote() all_tool_names = set() diff --git a/pretor/api/workflow.py b/pretor/api/workflow.py index e2d6cab..8e2012a 100644 --- a/pretor/api/workflow.py +++ b/pretor/api/workflow.py @@ -22,15 +22,22 @@ workflow_router = APIRouter(prefix="/api/v1/workflow", tags=["workflow"]) @workflow_router.get("/list") async def get_workflow_list(): - global_state_machine = ray_actor_hook("global_state_machine").global_state_machine - events = await global_state_machine.list_events.remote() + """处理针对 get workflow list 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ + global_workflow_manager = ray_actor_hook("global_workflow_manager").global_workflow_manager + events = await global_workflow_manager.list_events.remote() return events @workflow_router.get("/{trace_id}") async def get_workflow_detail(trace_id: str): - global_state_machine = ray_actor_hook("global_state_machine").global_state_machine - event = await global_state_machine.get_event.remote(trace_id) + """处理针对 get workflow detail 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: trace_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 trace 实例。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ + global_workflow_manager = ray_actor_hook("global_workflow_manager").global_workflow_manager + event = await global_workflow_manager.get_event.remote(trace_id) if not event: raise HTTPException(status_code=404, detail="Workflow not found") @@ -71,9 +78,15 @@ async def get_workflow_detail(trace_id: str): @workflow_router.get("/sse/{trace_id}") async def get_workflow_sse(trace_id: str, request: Request): - global_state_machine = ray_actor_hook("global_state_machine").global_state_machine + """处理针对 get workflow sse 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: trace_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 trace 实例。 request (Request): FastAPI 框架注入的原生 HTTP 请求对象,包含了完整的 Header 标头、查询参数和正文流。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ + global_workflow_manager = ray_actor_hook("global_workflow_manager").global_workflow_manager async def event_generator(): + """执行与 event generator 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 """ try: while True: if await request.is_disconnected(): @@ -81,7 +94,7 @@ async def get_workflow_sse(trace_id: str, request: Request): # You might also want to send the workflow state periodically or when updated # Here we just wait for pending messages and send them - message = await global_state_machine.get_pending.remote(trace_id) + message = await global_workflow_manager.get_pending.remote(trace_id) # Ensure the message is formatted as SSE yield f"data: {message}\n\n" except asyncio.CancelledError: @@ -91,9 +104,13 @@ async def get_workflow_sse(trace_id: str, request: Request): @workflow_router.post("/reply/{trace_id}") async def post_workflow_reply(trace_id: str, request: Request): + """处理针对 post workflow reply 相关的 HTTP API 请求。 + 该接口负责解析前端传入的载荷数据,调用底层核心业务逻辑进行处理,并组装标准化的 JSON 响应。 + Args: trace_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 trace 实例。 request (Request): FastAPI 框架注入的原生 HTTP 请求对象,包含了完整的 Header 标头、查询参数和正文流。 + Returns: : 序列化后的标准网络响应模型(如包含业务状态码、成功标志及对应的数据载荷 Data)。 """ data = await request.json() reply_msg = data.get("message", "") - global_state_machine = ray_actor_hook("global_state_machine").global_state_machine - await global_state_machine.put_received.remote(trace_id, reply_msg) + global_workflow_manager = ray_actor_hook("global_workflow_manager").global_workflow_manager + await global_workflow_manager.put_received.remote(trace_id, reply_msg) return {"status": "ok"} diff --git a/pretor/core/api/__init__.py b/pretor/core/api/__init__.py index 7f27692..7434659 100644 --- a/pretor/core/api/__init__.py +++ b/pretor/core/api/__init__.py @@ -24,6 +24,7 @@ from pretor.api.provider import provider_router from pretor.api.resource import resource_router from pretor.api.cluster import cluster_router from pretor.api.agent import agent_router +from pretor.api.workflow import workflow_router from pretor.utils.error import ( DemandError, ModelNotExistError, UserError, UserNotExistError, UserPasswordError, ProviderError, @@ -40,6 +41,7 @@ app.include_router(provider_router) # 供应商路径 app.include_router(resource_router) # 资源路径 app.include_router(cluster_router) # 集群信息路径 app.include_router(agent_router) # agent路径 +app.include_router(workflow_router) # workflow路径 @app.exception_handler(UserNotExistError) async def user_not_exist_handler(request: Request, exc: UserNotExistError): diff --git a/pretor/core/database/database_exception.py b/pretor/core/database/database_exception.py index 9359b42..9fd8322 100644 --- a/pretor/core/database/database_exception.py +++ b/pretor/core/database/database_exception.py @@ -19,7 +19,14 @@ from pretor.utils.error import UserNotExistError from pretor.utils.logger import get_logger logger = get_logger('database_exception') def database_exception(func): + """执行与 database exception 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: func: 参与 database exception 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ async def wrapper(*args, **kwargs): + """执行与 wrapper 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ try: return await func(*args, **kwargs) except ValidationError as e: diff --git a/pretor/core/database/module/event.py b/pretor/core/database/module/event.py new file mode 100644 index 0000000..ced4f8d --- /dev/null +++ b/pretor/core/database/module/event.py @@ -0,0 +1,45 @@ +from sqlmodel import select +from typing import List, Optional +from pretor.core.database.table.event import EventRecord +from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession + +class EventDatabase: + def __init__(self, async_session_maker: async_sessionmaker[AsyncSession]): + self.async_session_maker = async_session_maker + + async def upsert_event(self, trace_id: str, event_data_json: str) -> EventRecord: + async with self.async_session_maker() as session: + statement = select(EventRecord).where(EventRecord.trace_id == trace_id) + results = await session.execute(statement) + record = results.scalar_one_or_none() + if record: + record.event_data_json = event_data_json + else: + record = EventRecord(trace_id=trace_id, event_data_json=event_data_json) + session.add(record) + await session.commit() + await session.refresh(record) + return record + + async def get_event(self, trace_id: str) -> Optional[EventRecord]: + async with self.async_session_maker() as session: + statement = select(EventRecord).where(EventRecord.trace_id == trace_id) + results = await session.execute(statement) + return results.scalar_one_or_none() + + async def get_all_events(self) -> List[EventRecord]: + async with self.async_session_maker() as session: + statement = select(EventRecord) + results = await session.execute(statement) + return results.scalars().all() + + async def delete_event(self, trace_id: str) -> bool: + async with self.async_session_maker() as session: + statement = select(EventRecord).where(EventRecord.trace_id == trace_id) + results = await session.execute(statement) + record = results.scalar_one_or_none() + if record: + await session.delete(record) + await session.commit() + return True + return False diff --git a/pretor/core/database/module/individual.py b/pretor/core/database/module/individual.py index fa8e7b6..1fcb03a 100644 --- a/pretor/core/database/module/individual.py +++ b/pretor/core/database/module/individual.py @@ -20,11 +20,16 @@ from pretor.core.database.database_exception import database_exception from ulid import ULID class IndividualDatabase: + """IndividualDatabase 核心组件类。 + 这是一个数据库操作层 (DAO/Repository) 封装类,专注于处理实体模型与关系型数据库表之间的映射。它将复杂的 SQL 查询、跨表 Join 和事务回滚逻辑进行了高级抽象,向上层服务暴露简洁的数据读写接口。 """ def __init__(self, async_session_maker): self.async_session_maker = async_session_maker @database_exception async def add_worker_individual(self, **kwargs) -> WorkerIndividual: + """创建并持久化新的 worker individual 实体。 + 接收构建参数,执行必要的数据校验与默认值填充后,将新记录安全地写入底层存储或系统注册表中。 + Returns: (WorkerIndividual): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ async with self.async_session_maker() as session: agent_id = str(ULID()) individual = WorkerIndividual(agent_id=agent_id, **kwargs) @@ -35,6 +40,10 @@ class IndividualDatabase: @database_exception async def get_worker_individual(self, agent_id: str) -> Optional[WorkerIndividual]: + """检索并获取特定的 worker individual 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: agent_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 agent 实例。 + Returns: (Optional[WorkerIndividual]): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ async with self.async_session_maker() as session: statement = select(WorkerIndividual).where(WorkerIndividual.agent_id == agent_id) results = await session.execute(statement) @@ -42,6 +51,10 @@ class IndividualDatabase: @database_exception async def get_worker_individual_list(self, owner_id: str) -> List[WorkerIndividual]: + """检索并获取特定的 worker individual list 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: owner_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 owner 实例。 + Returns: (List[WorkerIndividual]): 经过筛选、排序或分页处理后的实体对象列表集合。 """ async with self.async_session_maker() as session: statement = select(WorkerIndividual).where(WorkerIndividual.owner_id == owner_id) results = await session.execute(statement) @@ -49,6 +62,10 @@ class IndividualDatabase: @database_exception async def update_worker_individual(self, agent_id: str, **kwargs) -> Optional[WorkerIndividual]: + """对现有的 worker individual 进行状态更新或属性覆盖。 + 基于增量变更原则,合并最新的配置或数据,并触发相关依赖组件的缓存刷新或事件通知。 + Args: agent_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 agent 实例。 + Returns: (Optional[WorkerIndividual]): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ async with self.async_session_maker() as session: statement = select(WorkerIndividual).where(WorkerIndividual.agent_id == agent_id) results = await session.execute(statement) @@ -65,6 +82,10 @@ class IndividualDatabase: @database_exception async def delete_worker_individual(self, agent_id: str) -> bool: + """安全地移除或注销 worker individual。 + 执行物理删除或逻辑删除操作,并妥善清理相关的关联数据及占用资源。 + Args: agent_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 agent 实例。 + Returns: (bool): 一个布尔型结果标志,明确返回 True 表示该操作成功应用或条件达成,False 则表示失败或被拒绝。 """ async with self.async_session_maker() as session: statement = select(WorkerIndividual).where(WorkerIndividual.agent_id == agent_id) results = await session.execute(statement) @@ -77,6 +98,9 @@ class IndividualDatabase: @database_exception async def get_all_worker_individual(self) -> List[WorkerIndividual]: + """检索并获取特定的 all worker individual 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Returns: (List[WorkerIndividual]): 经过筛选、排序或分页处理后的实体对象列表集合。 """ async with self.async_session_maker() as session: statement = select(WorkerIndividual) results = await session.execute(statement) diff --git a/pretor/core/database/module/memory.py b/pretor/core/database/module/memory.py deleted file mode 100644 index 96180f5..0000000 --- a/pretor/core/database/module/memory.py +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright 2026 zhaoxi826 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from sqlmodel import SQLModel, Field, select -from typing import Optional, List -import json - -class WorkflowRecord(SQLModel, table=True): - id: Optional[int] = Field(default=None, primary_key=True) - workflow_id: str = Field(index=True) - workflow_data_json: str - -class MemoryRecord(SQLModel, table=True): - id: Optional[int] = Field(default=None, primary_key=True) - memory_text: str - embedding: List[float] = Field(sa_column_kwargs={"type_": "VECTOR"}) # Requires pgvector extension setup in DB - -class MemoryRAG: - def __init__(self, async_session_maker): - self.async_session_maker = async_session_maker - - async def save_workflow(self, workflow_id: str, workflow_data: dict): - async with self.async_session_maker() as session: - record = WorkflowRecord( - workflow_id=workflow_id, - workflow_data_json=json.dumps(workflow_data) - ) - session.add(record) - await session.commit() - await session.refresh(record) - return record - - async def get_workflow(self, workflow_id: str): - async with self.async_session_maker() as session: - statement = select(WorkflowRecord).where(WorkflowRecord.workflow_id == workflow_id) - results = await session.execute(statement) - record = results.scalar_one_or_none() - if record: - return json.loads(record.workflow_data_json) - return None - - async def add_memory(self, memory_text: str, embedding: List[float]): - async with self.async_session_maker() as session: - record = MemoryRecord(memory_text=memory_text, embedding=embedding) - session.add(record) - await session.commit() - await session.refresh(record) - return record - - async def retrieve_memory(self, query_embedding: List[float], limit: int = 5): - # Requires pgvector specific operations; simplified retrieval simulation here - async with self.async_session_maker() as session: - # A true pgvector query would use an ORDER BY using `<->` operator - # e.g. statement = select(MemoryRecord).order_by(MemoryRecord.embedding.l2_distance(query_embedding)).limit(limit) - statement = select(MemoryRecord).limit(limit) - results = await session.execute(statement) - return results.all() diff --git a/pretor/core/database/module/provider.py b/pretor/core/database/module/provider.py index 13af372..6bb3297 100644 --- a/pretor/core/database/module/provider.py +++ b/pretor/core/database/module/provider.py @@ -19,11 +19,16 @@ from sqlmodel import select from pretor.core.database.database_exception import database_exception class ProviderDatabase: + """ProviderDatabase 核心组件类。 + 这是一个模型/服务提供商适配器类,屏蔽了外部不同供应商(如 OpenAI、Anthropic 等)的底层 API 差异。它负责标准化参数组装、网络请求发送、鉴权处理以及响应结构的反序列化。 """ def __init__(self, async_session_maker): self.async_session_maker = async_session_maker @database_exception async def get_provider(self) -> List[Provider]: + """检索并获取特定的 provider 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Returns: (List[Provider]): 经过筛选、排序或分页处理后的实体对象列表集合。 """ async with self.async_session_maker() as session: statement = select(Provider) results = await session.execute(statement) @@ -37,6 +42,9 @@ class ProviderDatabase: @database_exception async def add_provider(self, **kwargs) -> None: + """创建并持久化新的 provider 实体。 + 接收构建参数,执行必要的数据校验与默认值填充后,将新记录安全地写入底层存储或系统注册表中。 + Returns: (None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ async with self.async_session_maker() as session: provider = Provider(**kwargs) session.add(provider) @@ -44,6 +52,10 @@ class ProviderDatabase: @database_exception async def delete_provider(self, provider_id: str) -> None: + """安全地移除或注销 provider。 + 执行物理删除或逻辑删除操作,并妥善清理相关的关联数据及占用资源。 + Args: provider_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider 实例。 + Returns: (None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ async with self.async_session_maker() as session: provider = await session.get(Provider, provider_id) if provider is not None: @@ -52,6 +64,10 @@ class ProviderDatabase: @database_exception async def update_provider(self, provider_id: str, **kwargs) -> Provider: + """对现有的 provider 进行状态更新或属性覆盖。 + 基于增量变更原则,合并最新的配置或数据,并触发相关依赖组件的缓存刷新或事件通知。 + Args: provider_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider 实例。 + Returns: (Provider): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ async with self.async_session_maker() as session: provider = await session.get(Provider, provider_id) if provider is not None: diff --git a/pretor/core/database/module/system_node.py b/pretor/core/database/module/system_node.py index 4b8fe26..4a7a872 100644 --- a/pretor/core/database/module/system_node.py +++ b/pretor/core/database/module/system_node.py @@ -18,11 +18,17 @@ from typing import List, Optional from pretor.core.database.database_exception import database_exception class SystemNodeDatabase: + """SystemNodeDatabase 核心组件类。 + 这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。 """ def __init__(self, async_session_maker): self.async_session_maker = async_session_maker @database_exception async def upsert_system_node_config(self, node_name: str, provider_title: str, model_id: str, tools: Optional[List[str]] = None) -> SystemNodeConfig: + """执行与 upsert system node config 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: node_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 provider_title (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_title 实例。 model_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 model 实例。 tools (Optional[List[str]]): 控制逻辑流向的具体字符串参数,指定了期望的 tools 内容。 + Returns: (SystemNodeConfig): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ async with self.async_session_maker() as session: statement = select(SystemNodeConfig).where(SystemNodeConfig.node_name == node_name) results = await session.execute(statement) @@ -41,6 +47,9 @@ class SystemNodeDatabase: @database_exception async def get_all_system_node_configs(self) -> List[SystemNodeConfig]: + """检索并获取特定的 all system node configs 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Returns: (List[SystemNodeConfig]): 经过筛选、排序或分页处理后的实体对象列表集合。 """ async with self.async_session_maker() as session: statement = select(SystemNodeConfig) results = await session.execute(statement) @@ -48,6 +57,10 @@ class SystemNodeDatabase: @database_exception async def get_system_node_config(self, node_name: str) -> Optional[SystemNodeConfig]: + """检索并获取特定的 system node config 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: node_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 + Returns: (Optional[SystemNodeConfig]): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ async with self.async_session_maker() as session: statement = select(SystemNodeConfig).where(SystemNodeConfig.node_name == node_name) results = await session.execute(statement) diff --git a/pretor/core/database/module/user.py b/pretor/core/database/module/user.py index 35eb25a..06463ed 100644 --- a/pretor/core/database/module/user.py +++ b/pretor/core/database/module/user.py @@ -20,11 +20,17 @@ from pretor.core.database.table.user import UserAuthority from pretor.utils.access import Accessor class AuthDatabase: + """AuthDatabase 核心组件类。 + 这是一个数据库操作层 (DAO/Repository) 封装类,专注于处理实体模型与关系型数据库表之间的映射。它将复杂的 SQL 查询、跨表 Join 和事务回滚逻辑进行了高级抽象,向上层服务暴露简洁的数据读写接口。 """ def __init__(self, async_session_maker): self.async_session_maker = async_session_maker @database_exception async def add_user(self, user_name: str, hashed_password: str) -> User: + """创建并持久化新的 user 实体。 + 接收构建参数,执行必要的数据校验与默认值填充后,将新记录安全地写入底层存储或系统注册表中。 + Args: user_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 hashed_password (str): 控制逻辑流向的具体字符串参数,指定了期望的 hashed password 内容。 + Returns: (User): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ from ulid import ULID async with self.async_session_maker() as session: # Check if any users exist @@ -49,6 +55,10 @@ class AuthDatabase: @database_exception async def change_password(self, user_name, old_password, new_password) -> User: + """执行与 change password 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: user_name: 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 old_password: 参与 change password 逻辑运算或数据构建的上下文依赖对象。 new_password: 参与 change password 逻辑运算或数据构建的上下文依赖对象。 + Returns: (User): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ async with self.async_session_maker() as session: statement = select(User).where(User.user_name == user_name) results = await session.execute(statement) @@ -65,6 +75,10 @@ class AuthDatabase: @database_exception async def delete_user(self, user_name: str) -> None: + """安全地移除或注销 user。 + 执行物理删除或逻辑删除操作,并妥善清理相关的关联数据及占用资源。 + Args: user_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 + Returns: (None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ async with self.async_session_maker() as session: statement = select(User).where(User.user_name == user_name) results = await session.execute(statement) @@ -76,6 +90,10 @@ class AuthDatabase: @database_exception async def delete_user_by_id(self, user_id: str) -> None: + """安全地移除或注销 user by id。 + 执行物理删除或逻辑删除操作,并妥善清理相关的关联数据及占用资源。 + Args: user_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 user 实例。 + Returns: (None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ async with self.async_session_maker() as session: user = await session.get(User, user_id) if user is None: @@ -85,6 +103,10 @@ class AuthDatabase: @database_exception async def login_user(self, user_name: str) -> str: + """执行与 login user 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: user_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 + Returns: (str): 处理流程所输出的具体字符串产物,可能是新生成的 ID 序列、格式化好的文本片段或 LLM 推理的回答内容。 """ async with self.async_session_maker() as session: statement = select(User).where(User.user_name == user_name) results = await session.execute(statement) @@ -95,6 +117,9 @@ class AuthDatabase: @database_exception async def get_all_users(self) -> list[User]: + """检索并获取特定的 all users 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Returns: (list[User]): 经过筛选、排序或分页处理后的实体对象列表集合。 """ async with self.async_session_maker() as session: statement = select(User) results = await session.execute(statement) @@ -103,6 +128,10 @@ class AuthDatabase: @database_exception async def get_user_authority(self, user_id: str) -> UserAuthority: + """检索并获取特定的 user authority 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: user_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 user 实例。 + Returns: (UserAuthority): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ async with self.async_session_maker() as session: user = await session.get(User, user_id) if user is None: diff --git a/pretor/core/database/postgres.py b/pretor/core/database/postgres.py index 8349e59..d1b978a 100644 --- a/pretor/core/database/postgres.py +++ b/pretor/core/database/postgres.py @@ -21,12 +21,15 @@ from sqlalchemy.orm import sessionmaker from sqlmodel import SQLModel from pretor.core.database.module.individual import IndividualDatabase +from pretor.core.database.module.event import EventDatabase from pretor.core.database.module.user import AuthDatabase from pretor.core.database.module.provider import ProviderDatabase from pretor.core.database.module.system_node import SystemNodeDatabase @ray.remote class PostgresDatabase: + """PostgresDatabase 核心组件类。 + 这是一个数据库操作层 (DAO/Repository) 封装类,专注于处理实体模型与关系型数据库表之间的映射。它将复杂的 SQL 查询、跨表 Join 和事务回滚逻辑进行了高级抽象,向上层服务暴露简洁的数据读写接口。 """ def __init__(self): user = os.environ.get('POSTGRES_USER') password = os.environ.get('POSTGRES_PASSWORD') @@ -40,11 +43,15 @@ class PostgresDatabase: self._auth_database = AuthDatabase(self.async_session_maker) self._provider_database = ProviderDatabase(self.async_session_maker) self._individual_database = IndividualDatabase(self.async_session_maker) + self._event_database = EventDatabase(self.async_session_maker) self._system_node_database = SystemNodeDatabase(self.async_session_maker) self.ready_event = asyncio.Event() async def init_db(self) -> None: + """完成 db 模块的启动与依赖初始化。 + 在系统引导或服务拉起阶段被调用,负责建立网络连接、分配基础内存资源及注册核心服务组件。 + Returns: (None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ try: async with self.async_engine.begin() as conn: await conn.run_sync(SQLModel.metadata.create_all) @@ -57,84 +64,174 @@ class PostgresDatabase: # Auth Database Methods async def add_user(self, user_name: str, hashed_password: str): + """创建并持久化新的 user 实体。 + 接收构建参数,执行必要的数据校验与默认值填充后,将新记录安全地写入底层存储或系统注册表中。 + Args: user_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 hashed_password (str): 控制逻辑流向的具体字符串参数,指定了期望的 hashed password 内容。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._auth_database.add_user(user_name, hashed_password) async def change_password(self, user_name, old_password, new_password): + """执行与 change password 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: user_name: 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 old_password: 参与 change password 逻辑运算或数据构建的上下文依赖对象。 new_password: 参与 change password 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._auth_database.change_password(user_name, old_password, new_password) async def delete_user(self, user_name: str): + """安全地移除或注销 user。 + 执行物理删除或逻辑删除操作,并妥善清理相关的关联数据及占用资源。 + Args: user_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._auth_database.delete_user(user_name) async def delete_user_by_id(self, user_id: str): + """安全地移除或注销 user by id。 + 执行物理删除或逻辑删除操作,并妥善清理相关的关联数据及占用资源。 + Args: user_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 user 实例。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._auth_database.delete_user_by_id(user_id) async def login_user(self, user_name: str): + """执行与 login user 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: user_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._auth_database.login_user(user_name) async def get_all_users(self): + """检索并获取特定的 all users 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._auth_database.get_all_users() async def get_user_authority(self, user_id: str): + """检索并获取特定的 user authority 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: user_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 user 实例。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._auth_database.get_user_authority(user_id) async def change_user_authority(self, user_id: str, new_authority): + """执行与 change user authority 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: user_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 user 实例。 new_authority: 参与 change user authority 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._auth_database.change_user_authority(user_id, new_authority) # Provider Database Methods async def get_provider(self): + """检索并获取特定的 provider 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._provider_database.get_provider() async def add_provider_db(self, **kwargs): + """创建并持久化新的 provider db 实体。 + 接收构建参数,执行必要的数据校验与默认值填充后,将新记录安全地写入底层存储或系统注册表中。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._provider_database.add_provider(**kwargs) async def delete_provider_db(self, provider_id: str): + """安全地移除或注销 provider db。 + 执行物理删除或逻辑删除操作,并妥善清理相关的关联数据及占用资源。 + Args: provider_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider 实例。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._provider_database.delete_provider(provider_id) async def update_provider_db(self, provider_id: str, **kwargs): + """对现有的 provider db 进行状态更新或属性覆盖。 + 基于增量变更原则,合并最新的配置或数据,并触发相关依赖组件的缓存刷新或事件通知。 + Args: provider_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider 实例。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._provider_database.update_provider(provider_id, **kwargs) # System Node Database Methods async def upsert_system_node_config(self, node_name: str, provider_title: str, model_id: str, tools: list[str] = None): + """执行与 upsert system node config 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: node_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 provider_title (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_title 实例。 model_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 model 实例。 tools (list[str]): 控制逻辑流向的具体字符串参数,指定了期望的 tools 内容。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._system_node_database.upsert_system_node_config(node_name, provider_title, model_id, tools) async def get_all_system_node_configs(self): + """检索并获取特定的 all system node configs 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._system_node_database.get_all_system_node_configs() # Individual Database Methods async def add_worker_individual(self, **kwargs): + """创建并持久化新的 worker individual 实体。 + 接收构建参数,执行必要的数据校验与默认值填充后,将新记录安全地写入底层存储或系统注册表中。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._individual_database.add_worker_individual(**kwargs) async def get_worker_individual(self, agent_id: str): + """检索并获取特定的 worker individual 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: agent_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 agent 实例。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._individual_database.get_worker_individual(agent_id) async def get_worker_individual_list(self, owner_id: str): + """检索并获取特定的 worker individual list 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: owner_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 owner 实例。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._individual_database.get_worker_individual_list(owner_id) async def update_worker_individual(self, agent_id: str, **kwargs): + """对现有的 worker individual 进行状态更新或属性覆盖。 + 基于增量变更原则,合并最新的配置或数据,并触发相关依赖组件的缓存刷新或事件通知。 + Args: agent_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 agent 实例。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._individual_database.update_worker_individual(agent_id, **kwargs) async def delete_worker_individual(self, agent_id: str): + """安全地移除或注销 worker individual。 + 执行物理删除或逻辑删除操作,并妥善清理相关的关联数据及占用资源。 + Args: agent_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 agent 实例。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() return await self._individual_database.delete_worker_individual(agent_id) async def get_all_worker_individual(self): + """检索并获取特定的 all worker individual 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.ready_event.wait() - return await self._individual_database.get_all_worker_individual() \ No newline at end of file + return await self._individual_database.get_all_worker_individual() + # Event Database Methods + async def upsert_event(self, trace_id: str, event_data_json: str): + await self.ready_event.wait() + return await self._event_database.upsert_event(trace_id, event_data_json) + + async def get_event(self, trace_id: str): + await self.ready_event.wait() + return await self._event_database.get_event(trace_id) + + async def get_all_events(self): + await self.ready_event.wait() + return await self._event_database.get_all_events() + + async def delete_event(self, trace_id: str): + await self.ready_event.wait() + return await self._event_database.delete_event(trace_id) diff --git a/pretor/core/database/table/event.py b/pretor/core/database/table/event.py new file mode 100644 index 0000000..2c5fdd9 --- /dev/null +++ b/pretor/core/database/table/event.py @@ -0,0 +1,5 @@ +from sqlmodel import SQLModel, Field + +class EventRecord(SQLModel, table=True): + trace_id: str = Field(primary_key=True, description="The unique trace ID of the PretorEvent") + event_data_json: str = Field(description="The JSON serialized PretorEvent data") diff --git a/pretor/core/database/table/individual.py b/pretor/core/database/table/individual.py index ad8c7e8..10f97c9 100644 --- a/pretor/core/database/table/individual.py +++ b/pretor/core/database/table/individual.py @@ -18,11 +18,15 @@ from sqlalchemy import Column, JSON from enum import Enum class AgentType(str, Enum): + """AgentType 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 AgentType 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ SKILL_INDIVIDUAL = "skill_individual" ORDINARY_INDIVIDUAL = "ordinary_individual" SPECIAL_INDIVIDUAL = "special_individual" class WorkerIndividual(SQLModel, table=True): + """WorkerIndividual 核心组件类。 + 这是一个具体的 Worker 智能体实体类,代表着具备特定人设、领域技能或长文本处理能力的数字员工。它可以被控制器动态拉起,并在安全沙箱内执行复杂的工作流指令与多步骤推理任务。 """ __tablename__ = "worker_individual" agent_id: str = Field(primary_key=True) agent_name: str = Field(index=True) diff --git a/pretor/core/database/table/provider.py b/pretor/core/database/table/provider.py index cafeece..9f9ccc6 100644 --- a/pretor/core/database/table/provider.py +++ b/pretor/core/database/table/provider.py @@ -18,6 +18,8 @@ from sqlalchemy import Column, JSON from typing import Optional class Provider(SQLModel, table=True): + """Provider 核心组件类。 + 这是一个模型/服务提供商适配器类,屏蔽了外部不同供应商(如 OpenAI、Anthropic 等)的底层 API 差异。它负责标准化参数组装、网络请求发送、鉴权处理以及响应结构的反序列化。 """ __tablename__ = "provider" provider_id: str = Field(primary_key=True) provider_title: str = Field(index=True) diff --git a/pretor/core/database/table/system_node.py b/pretor/core/database/table/system_node.py index a34684b..45789df 100644 --- a/pretor/core/database/table/system_node.py +++ b/pretor/core/database/table/system_node.py @@ -18,6 +18,8 @@ from typing import List, Optional from sqlalchemy import Column, JSON class SystemNodeConfig(SQLModel, table=True): + """SystemNodeConfig 核心组件类。 + 这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。 """ __tablename__ = "system_node_config" node_name: str = Field(primary_key=True) provider_title: str diff --git a/pretor/core/database/table/user.py b/pretor/core/database/table/user.py index df5afed..3a23c57 100644 --- a/pretor/core/database/table/user.py +++ b/pretor/core/database/table/user.py @@ -16,6 +16,8 @@ from sqlmodel import SQLModel, Field from enum import IntEnum class UserAuthority(IntEnum): + """UserAuthority 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 UserAuthority 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ SUPER_ADMINISTRATOR = 100 ADMINISTRATOR = 50 USER = 20 @@ -23,6 +25,8 @@ class UserAuthority(IntEnum): GUEST = 0 class User(SQLModel, table=True): + """User 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 User 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ __tablename__ = 'user' user_id: str = Field(primary_key=True) user_name: str = Field(index=True) diff --git a/pretor/core/global_state_machine/global_state_machine.py b/pretor/core/global_state_machine/global_state_machine.py index acb8479..aa89df6 100644 --- a/pretor/core/global_state_machine/global_state_machine.py +++ b/pretor/core/global_state_machine/global_state_machine.py @@ -15,11 +15,7 @@ import ray from pretor.core.global_state_machine.provider_manager import ProviderManager from pretor.core.global_state_machine.tool_manager import GlobalToolManager -from typing import Dict from pretor.core.database.postgres import PostgresDatabase -from pretor.api.platform.event import PretorEvent -import asyncio -from pretor.core.workflow.workflow import PretorWorkflow from pretor.core.workflow.workflow_template_manager import WorkflowManager from pretor.core.global_state_machine.skill_manager import GlobalSkillManager from pretor.core.global_state_machine.individual_manager import GlobalIndividualManager @@ -27,10 +23,11 @@ from pretor.core.global_state_machine.individual_manager import GlobalIndividual @ray.remote class GlobalStateMachine: + """GlobalStateMachine 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 GlobalStateMachine 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ def __init__(self, postgres_database: PostgresDatabase): import sys print("GSM __init__ START", file=sys.stderr, flush=True) - self.event_dict: Dict[str, PretorEvent] = {} print(" event_dict done", file=sys.stderr, flush=True) self._global_provider_manager = ProviderManager(postgres_database) print(" provider_manager done", file=sys.stderr, flush=True) @@ -46,10 +43,16 @@ class GlobalStateMachine: print("GSM __init__ DONE", file=sys.stderr, flush=True) async def init_state_machine(self): + """完成 state machine 模块的启动与依赖初始化。 + 在系统引导或服务拉起阶段被调用,负责建立网络连接、分配基础内存资源及注册核心服务组件。 """ await self._global_provider_manager.init_provider_register(self.postgres_database) await self._global_individual_manager.init_individual_register(self.postgres_database) async def add_provider_wrap(self, provider_type, provider_title, provider_url, provider_apikey, provider_owner): + """创建并持久化新的 provider wrap 实体。 + 接收构建参数,执行必要的数据校验与默认值填充后,将新记录安全地写入底层存储或系统注册表中。 + Args: provider_type: 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_type 实例。 provider_title: 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_title 实例。 provider_url: 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_url 实例。 provider_apikey: 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_apikey 实例。 provider_owner: 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_owner 实例。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return await self._global_provider_manager.add_provider( provider_type=provider_type, provider_title=provider_title, @@ -61,21 +64,39 @@ class GlobalStateMachine: # Provider Manager Methods def get_provider_list(self): + """检索并获取特定的 provider list 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return self._global_provider_manager.get_provider_list() def get_provider(self, provider_title): + """检索并获取特定的 provider 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: provider_title: 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_title 实例。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return self._global_provider_manager.get_provider(provider_title) async def delete_provider(self, provider_title: str): + """安全地移除或注销 provider。 + 执行物理删除或逻辑删除操作,并妥善清理相关的关联数据及占用资源。 + Args: provider_title (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_title 实例。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return await self._global_provider_manager.delete_provider(provider_title, self.postgres_database) # Tool Manager Methods def get_tool_mapper(self): + """检索并获取特定的 tool mapper 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return self._global_tool_manager.tool_mapper def get_tool_list(self, agent_name: str): # get_tool_list didn't actually exist on tool_manager, let's implement it to return the tools # for a specific agent name (or scope) + """检索并获取特定的 tool list 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: agent_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ tools = self._global_tool_manager.tool_mapper.get(agent_name, {}) # also include default tools default_tools = self._global_tool_manager.tool_mapper.get("default", {}) @@ -84,83 +105,78 @@ class GlobalStateMachine: # Workflow Template Manager Methods def get_all_workflow_templates(self): + """检索并获取特定的 all workflow templates 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return self._global_workflow_template_manager.get_all_workflow_templates() def add_workflow_template(self, template_name: str, workflow_template): + """创建并持久化新的 workflow template 实体。 + 接收构建参数,执行必要的数据校验与默认值填充后,将新记录安全地写入底层存储或系统注册表中。 + Args: template_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 workflow_template: 参与 add workflow template 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return self._global_workflow_template_manager.add_workflow_template(template_name, workflow_template) def delete_workflow_template(self, template_name: str): + """安全地移除或注销 workflow template。 + 执行物理删除或逻辑删除操作,并妥善清理相关的关联数据及占用资源。 + Args: template_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return self._global_workflow_template_manager.delete_workflow_template(template_name) def generate_workflow_template(self, workflow_template): + """执行与 generate workflow template 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: workflow_template: 参与 generate workflow template 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return self._global_workflow_template_manager.generate_workflow_template(workflow_template) # Skill Manager Methods def add_skill(self, skill_name: str): + """创建并持久化新的 skill 实体。 + 接收构建参数,执行必要的数据校验与默认值填充后,将新记录安全地写入底层存储或系统注册表中。 + Args: skill_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return self._global_skill_manager.add_skill(skill_name) def get_skill_list(self): + """检索并获取特定的 skill list 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return self._global_skill_manager.get_skill_list() def remove_skill(self, skill_name: str): + """安全地移除或注销 skill。 + 执行物理删除或逻辑删除操作,并妥善清理相关的关联数据及占用资源。 + Args: skill_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return self._global_skill_manager.remove_skill(skill_name) # Individual Manager Methods def add_individual(self, agent_id: str, config): + """创建并持久化新的 individual 实体。 + 接收构建参数,执行必要的数据校验与默认值填充后,将新记录安全地写入底层存储或系统注册表中。 + Args: agent_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 agent 实例。 config: 驱动该模块运行的核心配置字典或 Pydantic 数据模型,定义了重试策略、超时时间及模型参数等选项。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return self._global_individual_manager.add_individual(agent_id, config) def get_individual(self, agent_id: str): + """检索并获取特定的 individual 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: agent_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 agent 实例。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return self._global_individual_manager.get_individual(agent_id) def remove_individual(self, agent_id: str): + """安全地移除或注销 individual。 + 执行物理删除或逻辑删除操作,并妥善清理相关的关联数据及占用资源。 + Args: agent_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 agent 实例。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return self._global_individual_manager.remove_individual(agent_id) def list_individuals(self): + """执行与 list individuals 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return self._global_individual_manager.list_individuals() - ###以下方法为event_dict方法 - def add_event(self, event: PretorEvent) -> None: - event.pending_queue = asyncio.Queue() - event.receive_queue = asyncio.Queue() - self.event_dict[event.trace_id] = event - - def delete_event(self, trace_id: str) -> None: - del self.event_dict[trace_id] - - def get_event(self, trace_id: str) -> PretorEvent: - return self.event_dict.get(trace_id, None) - - def update_attachment(self, trace_id: str, attachment: Dict[str, str]) -> None: - self.event_dict[trace_id].attachment = attachment - - def update_workflow(self, trace_id: str, workflow: PretorWorkflow) -> None: - self.event_dict[trace_id].workflow = workflow - - def get_workflow(self, trace_id: str) -> PretorWorkflow: - return self.event_dict[trace_id].workflow - - def list_events(self) -> list[dict]: - result = [] - for trace_id, event in self.event_dict.items(): - workflow_title = event.workflow.title if event.workflow else None - workflow_status = event.workflow.status.status if event.workflow and event.workflow.status else None - result.append({ - "event_id": trace_id, - "workflow_title": workflow_title, - "status": workflow_status, - "user_name": event.user_name, - "message": event.message, - }) - return result - - async def put_pending(self, trace_id, item) -> None: - await self.event_dict[trace_id].pending_queue.put(item) - - async def get_pending(self, trace_id) -> str: - return await self.event_dict[trace_id].pending_queue.get() - - async def put_received(self, trace_id, item) -> None: - await self.event_dict[trace_id].receive_queue.put(item) - - async def get_received(self, trace_id) -> str: - return await self.event_dict[trace_id].receive_queue.get() diff --git a/pretor/core/global_state_machine/global_workflow_manager.py b/pretor/core/global_state_machine/global_workflow_manager.py new file mode 100644 index 0000000..38fb881 --- /dev/null +++ b/pretor/core/global_state_machine/global_workflow_manager.py @@ -0,0 +1,187 @@ +import ray +import asyncio +from typing import Dict +from pretor.api.platform.event import PretorEvent +from pretor.core.workflow.workflow import PretorWorkflow +from pretor.utils.ray_hook import ray_actor_hook +from pretor.utils.logger import get_logger + +@ray.remote +class GlobalWorkflowManager: + def __init__(self): + self.event_dict: Dict[str, PretorEvent] = {} + self.event_object_refs: Dict[str, ray.ObjectRef] = {} + self.postgres_database = None + self.logger = get_logger("GlobalWorkflowManager") + + async def init_manager(self): + self.postgres_database = ray_actor_hook("postgres_database").postgres_database + + # Load all events from database to memory + try: + records = await self.postgres_database.get_all_events.remote() + for record in records: + try: + event = PretorEvent.model_validate_json(record.event_data_json) + event.pending_queue = asyncio.Queue() + event.receive_queue = asyncio.Queue() + self.event_dict[event.trace_id] = event + + # Store in ray object store for cache + event_copy = event.model_copy() + event_copy.pending_queue = None + event_copy.receive_queue = None + self.event_object_refs[event.trace_id] = ray.put(event_copy.model_dump_json()) + + except Exception as e: + self.logger.error(f"Failed to load event {record.trace_id}: {e}") + self.logger.info(f"Loaded {len(self.event_dict)} events from database") + + # Trigger resumption of incomplete workflows + workflow_running_engine = None + for trace_id, event in self.event_dict.items(): + if event.workflow and event.workflow.status.status in ["waiting_llm_working", "waiting_tool_working", "llm_working", "tool_working"]: + self.logger.info(f"Resuming incomplete workflow {trace_id}") + if not workflow_running_engine: + try: + workflow_running_engine = ray_actor_hook("workflow_running_engine").workflow_running_engine + except AttributeError: + self.logger.warning("workflow_running_engine not found, cannot resume workflow") + break + await workflow_running_engine.resume_workflow.remote(event) + + except Exception as e: + self.logger.error(f"Failed to fetch events from database: {e}") + + async def _upsert_event_to_db(self, event: PretorEvent): + try: + # Create a copy and remove non-serializable queues + event_copy = event.model_copy() + event_copy.pending_queue = None + event_copy.receive_queue = None + + event_json = event_copy.model_dump_json() + # Update cache + self.event_object_refs[event.trace_id] = ray.put(event_json) + + await self.postgres_database.upsert_event.remote( + event.trace_id, + event_json + ) + except Exception as e: + self.logger.error(f"Failed to upsert event {event.trace_id} to database: {e}") + + async def add_event(self, event: PretorEvent) -> None: + event.pending_queue = asyncio.Queue() + event.receive_queue = asyncio.Queue() + self.event_dict[event.trace_id] = event + await self._upsert_event_to_db(event) + + async def delete_event(self, trace_id: str) -> None: + if trace_id in self.event_dict: + del self.event_dict[trace_id] + if trace_id in self.event_object_refs: + del self.event_object_refs[trace_id] + try: + await self.postgres_database.delete_event.remote(trace_id) + except Exception as e: + self.logger.error(f"Failed to delete event {trace_id} from database: {e}") + + async def get_event(self, trace_id: str) -> PretorEvent | None: + # First check memory dict + if trace_id in self.event_dict: + return self.event_dict[trace_id] + + # Then check Ray object store cache + if trace_id in self.event_object_refs: + try: + event_json = ray.get(self.event_object_refs[trace_id]) + return PretorEvent.model_validate_json(event_json) + except Exception as e: + self.logger.warning(f"Failed to fetch event from cache for trace {trace_id}: {e}") + + # Fallback to database + try: + record = await self.postgres_database.get_event.remote(trace_id) + if record: + event = PretorEvent.model_validate_json(record.event_data_json) + + # Restore to memory dict with missing transient queues + event.pending_queue = asyncio.Queue() + event.receive_queue = asyncio.Queue() + self.event_dict[trace_id] = event + + # Restore to cache + event_copy = event.model_copy() + event_copy.pending_queue = None + event_copy.receive_queue = None + self.event_object_refs[trace_id] = ray.put(event_copy.model_dump_json()) + + return event + except Exception as e: + self.logger.error(f"Failed to fetch event {trace_id} from database fallback: {e}") + + return None + + async def update_attachment(self, trace_id: str, attachment: Dict[str, str]) -> None: + if trace_id in self.event_dict: + self.event_dict[trace_id].attachment = attachment + await self._upsert_event_to_db(self.event_dict[trace_id]) + + async def update_workflow(self, trace_id: str, workflow: PretorWorkflow) -> None: + if trace_id in self.event_dict: + self.event_dict[trace_id].workflow = workflow + await self._upsert_event_to_db(self.event_dict[trace_id]) + + async def get_workflow(self, trace_id: str) -> PretorWorkflow | None: + event = await self.get_event(trace_id) + return event.workflow if event else None + + async def list_events(self) -> list[dict]: + result = [] + + # Read strictly from the database to ensure we get all events, + # and ignore the cache to prevent frontend missing items. + try: + records = await self.postgres_database.get_all_events.remote() + for record in records: + try: + event = PretorEvent.model_validate_json(record.event_data_json) + workflow_title = event.workflow.title if event.workflow else None + workflow_status = event.workflow.status.status if event.workflow and event.workflow.status else None + result.append({ + "event_id": event.trace_id, + "workflow_title": workflow_title, + "status": workflow_status, + "user_name": event.user_name, + "message": event.message, + "create_time": event.create_time, + }) + # Best-effort cache population + self.event_object_refs[event.trace_id] = ray.put(record.event_data_json) + except Exception: + continue + except Exception as e: + self.logger.error(f"Failed to list_events from DB: {e}") + + return result + + async def put_pending(self, trace_id, item) -> None: + if trace_id in self.event_dict and self.event_dict[trace_id].pending_queue: + await self.event_dict[trace_id].pending_queue.put(item) + + async def get_pending(self, trace_id) -> str: + if trace_id in self.event_dict and self.event_dict[trace_id].pending_queue: + return await self.event_dict[trace_id].pending_queue.get() + await asyncio.sleep(1) # Prevent CPU spinning if not found + return "" + + async def put_received(self, trace_id, item) -> None: + if trace_id in self.event_dict and self.event_dict[trace_id].receive_queue: + await self.event_dict[trace_id].receive_queue.put(item) + + async def get_received(self, trace_id) -> str: + if trace_id in self.event_dict and self.event_dict[trace_id].receive_queue: + return await self.event_dict[trace_id].receive_queue.get() + await asyncio.sleep(1) # Prevent CPU spinning if not found + return "" diff --git a/pretor/core/global_state_machine/individual_manager.py b/pretor/core/global_state_machine/individual_manager.py index 568c8c0..94f9a9f 100644 --- a/pretor/core/global_state_machine/individual_manager.py +++ b/pretor/core/global_state_machine/individual_manager.py @@ -17,10 +17,16 @@ from pretor.utils.logger import get_logger logger = get_logger('individual_manager') class GlobalIndividualManager: + """GlobalIndividualManager 核心组件类。 + 这是一个管理器类,职责集中在维护整个系统内有关 GlobalIndividual 资源的全局生命周期。它提供了注册机制、状态同步以及跨组件的统一查询入口,确保系统中该类型资源的实例一致性与可控性。 """ def __init__(self): self._individuals: Dict[str, Dict[str, Any]] = {} async def init_individual_register(self, postgres) -> None: + """完成 individual register 模块的启动与依赖初始化。 + 在系统引导或服务拉起阶段被调用,负责建立网络连接、分配基础内存资源及注册核心服务组件。 + Args: postgres: 参与 init individual register 逻辑运算或数据构建的上下文依赖对象。 + Returns: (None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ try: try: individuals = await postgres.get_all_worker_individual.remote() @@ -55,8 +61,15 @@ class GlobalIndividualManager: return self._individuals.get(agent_id, None) def remove_individual(self, agent_id: str) -> None: + """安全地移除或注销 individual。 + 执行物理删除或逻辑删除操作,并妥善清理相关的关联数据及占用资源。 + Args: agent_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 agent 实例。 + Returns: (None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ if agent_id in self._individuals: del self._individuals[agent_id] def list_individuals(self) -> Dict[str, Dict[str, Any]]: + """执行与 list individuals 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Returns: (Dict[str, Dict[str, Any]]): 高度聚合的字典结构数据,将多维度的属性特征或统计指标组合后一并返回。 """ return self._individuals diff --git a/pretor/core/global_state_machine/model_provider/base_provider.py b/pretor/core/global_state_machine/model_provider/base_provider.py index 5e0653a..4383522 100644 --- a/pretor/core/global_state_machine/model_provider/base_provider.py +++ b/pretor/core/global_state_machine/model_provider/base_provider.py @@ -18,10 +18,14 @@ from typing import List from enum import Enum class ProviderStatus(str, Enum): + """ProviderStatus 核心组件类。 + 这是一个模型/服务提供商适配器类,屏蔽了外部不同供应商(如 OpenAI、Anthropic 等)的底层 API 差异。它负责标准化参数组装、网络请求发送、鉴权处理以及响应结构的反序列化。 """ UP = "up" DOWN = "down" class Provider(BaseModel): + """Provider 核心组件类。 + 这是一个模型/服务提供商适配器类,屏蔽了外部不同供应商(如 OpenAI、Anthropic 等)的底层 API 差异。它负责标准化参数组装、网络请求发送、鉴权处理以及响应结构的反序列化。 """ provider_title: str provider_url: str provider_apikey: str @@ -31,12 +35,16 @@ class Provider(BaseModel): provider_status: ProviderStatus = ProviderStatus.UP class ProviderArgs(BaseModel): + """ProviderArgs 核心组件类。 + 这是一个模型/服务提供商适配器类,屏蔽了外部不同供应商(如 OpenAI、Anthropic 等)的底层 API 差异。它负责标准化参数组装、网络请求发送、鉴权处理以及响应结构的反序列化。 """ provider_title: str provider_url: str provider_apikey: str provider_owner: str class BaseProvider(ABC): + """BaseProvider 核心组件类。 + 这是一个模型/服务提供商适配器类,屏蔽了外部不同供应商(如 OpenAI、Anthropic 等)的底层 API 差异。它负责标准化参数组装、网络请求发送、鉴权处理以及响应结构的反序列化。 """ @staticmethod @abstractmethod async def create_provider(provider_args: ProviderArgs) -> Provider: diff --git a/pretor/core/global_state_machine/model_provider/claude_provider.py b/pretor/core/global_state_machine/model_provider/claude_provider.py index 94c689c..77159e4 100644 --- a/pretor/core/global_state_machine/model_provider/claude_provider.py +++ b/pretor/core/global_state_machine/model_provider/claude_provider.py @@ -1,4 +1,3 @@ -from pretor.utils.retry import retry_on_retryable_error # Copyright 2026 zhaoxi826 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,13 +12,21 @@ from pretor.utils.retry import retry_on_retryable_error # See the License for the specific language governing permissions and # limitations under the License. +from pretor.utils.retry import retry_on_retryable_error + from pretor.core.global_state_machine.model_provider.base_provider import BaseProvider, Provider, ProviderArgs import httpx from typing import List class ClaudeProvider(BaseProvider): + """ClaudeProvider 核心组件类。 + 这是一个模型/服务提供商适配器类,屏蔽了外部不同供应商(如 OpenAI、Anthropic 等)的底层 API 差异。它负责标准化参数组装、网络请求发送、鉴权处理以及响应结构的反序列化。 """ @staticmethod async def create_provider(provider_args: ProviderArgs) -> Provider: + """创建并持久化新的 provider 实体。 + 接收构建参数,执行必要的数据校验与默认值填充后,将新记录安全地写入底层存储或系统注册表中。 + Args: provider_args (ProviderArgs): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_args 实例。 + Returns: (Provider): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ provider_models: List[str] = await ClaudeProvider._load_models(provider_args) provider: Provider = ClaudeProvider._return_provider(provider_args, provider_models) return provider @@ -28,6 +35,10 @@ class ClaudeProvider(BaseProvider): @retry_on_retryable_error() async def _load_models(provider_args: ProviderArgs) -> List[str]: # Anthropic 官方需要 version 头 + """执行与 load models 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: provider_args (ProviderArgs): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_args 实例。 + Returns: (List[str]): 经过筛选、排序或分页处理后的实体对象列表集合。 """ headers = { "x-api-key": provider_args.provider_apikey, "anthropic-version": "2023-06-01", @@ -53,6 +64,10 @@ class ClaudeProvider(BaseProvider): @staticmethod def _return_provider(provider_args: ProviderArgs, provider_models: List[str]) -> Provider: + """执行与 return provider 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: provider_args (ProviderArgs): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_args 实例。 provider_models (List[str]): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_models 实例。 + Returns: (Provider): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return Provider(provider_title=provider_args.provider_title, provider_apikey=provider_args.provider_apikey, provider_url=provider_args.provider_url, diff --git a/pretor/core/global_state_machine/model_provider/deepseek_provider.py b/pretor/core/global_state_machine/model_provider/deepseek_provider.py index 0ca8a6f..3285809 100644 --- a/pretor/core/global_state_machine/model_provider/deepseek_provider.py +++ b/pretor/core/global_state_machine/model_provider/deepseek_provider.py @@ -18,8 +18,14 @@ import httpx from typing import List class DeepseekProvider(BaseProvider): + """DeepseekProvider 核心组件类。 + 这是一个模型/服务提供商适配器类,屏蔽了外部不同供应商(如 OpenAI、Anthropic 等)的底层 API 差异。它负责标准化参数组装、网络请求发送、鉴权处理以及响应结构的反序列化。 """ @staticmethod async def create_provider(provider_args: ProviderArgs) -> Provider: + """创建并持久化新的 provider 实体。 + 接收构建参数,执行必要的数据校验与默认值填充后,将新记录安全地写入底层存储或系统注册表中。 + Args: provider_args (ProviderArgs): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_args 实例。 + Returns: (Provider): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ provider_models: List[str] = await DeepseekProvider._load_models(provider_args) provider: Provider = DeepseekProvider._return_provider(provider_args, provider_models) return provider @@ -27,6 +33,10 @@ class DeepseekProvider(BaseProvider): @staticmethod @retry_on_retryable_error() async def _load_models(provider_args: ProviderArgs) -> List[str]: + """执行与 load models 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: provider_args (ProviderArgs): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_args 实例。 + Returns: (List[str]): 经过筛选、排序或分页处理后的实体对象列表集合。 """ headers = { "Authorization": f"Bearer {provider_args.provider_apikey}", "Content-Type": "application/json" @@ -52,6 +62,10 @@ class DeepseekProvider(BaseProvider): @staticmethod def _return_provider(provider_args: ProviderArgs, provider_models: List[str]) -> Provider: + """执行与 return provider 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: provider_args (ProviderArgs): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_args 实例。 provider_models (List[str]): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_models 实例。 + Returns: (Provider): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return Provider(provider_title=provider_args.provider_title, provider_apikey=provider_args.provider_apikey, provider_url=provider_args.provider_url, diff --git a/pretor/core/global_state_machine/model_provider/openai_provider.py b/pretor/core/global_state_machine/model_provider/openai_provider.py index 26ac3c4..a38a77f 100644 --- a/pretor/core/global_state_machine/model_provider/openai_provider.py +++ b/pretor/core/global_state_machine/model_provider/openai_provider.py @@ -18,8 +18,14 @@ import httpx from typing import List class OpenAIProvider(BaseProvider): + """OpenAIProvider 核心组件类。 + 这是一个模型/服务提供商适配器类,屏蔽了外部不同供应商(如 OpenAI、Anthropic 等)的底层 API 差异。它负责标准化参数组装、网络请求发送、鉴权处理以及响应结构的反序列化。 """ @staticmethod async def create_provider(provider_args: ProviderArgs) -> Provider: + """创建并持久化新的 provider 实体。 + 接收构建参数,执行必要的数据校验与默认值填充后,将新记录安全地写入底层存储或系统注册表中。 + Args: provider_args (ProviderArgs): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_args 实例。 + Returns: (Provider): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ provider_models: List[str] = await OpenAIProvider._load_models(provider_args) provider: Provider = OpenAIProvider._return_provider(provider_args, provider_models) return provider @@ -27,6 +33,10 @@ class OpenAIProvider(BaseProvider): @staticmethod @retry_on_retryable_error() async def _load_models(provider_args: ProviderArgs) -> List[str]: + """执行与 load models 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: provider_args (ProviderArgs): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_args 实例。 + Returns: (List[str]): 经过筛选、排序或分页处理后的实体对象列表集合。 """ headers = { "Authorization": f"Bearer {provider_args.provider_apikey}", "Content-Type": "application/json" @@ -52,6 +62,10 @@ class OpenAIProvider(BaseProvider): @staticmethod def _return_provider(provider_args: ProviderArgs, provider_models: List[str]) -> Provider: + """执行与 return provider 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: provider_args (ProviderArgs): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_args 实例。 provider_models (List[str]): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_models 实例。 + Returns: (Provider): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return Provider(provider_title=provider_args.provider_title, provider_apikey=provider_args.provider_apikey, provider_url=provider_args.provider_url, diff --git a/pretor/core/global_state_machine/provider_manager.py b/pretor/core/global_state_machine/provider_manager.py index b714700..b262f14 100644 --- a/pretor/core/global_state_machine/provider_manager.py +++ b/pretor/core/global_state_machine/provider_manager.py @@ -33,11 +33,19 @@ class ProviderManager: self.provider_register = {} async def init_provider_register(self, postgres) -> None: + """完成 provider register 模块的启动与依赖初始化。 + 在系统引导或服务拉起阶段被调用,负责建立网络连接、分配基础内存资源及注册核心服务组件。 + Args: postgres: 参与 init provider register 逻辑运算或数据构建的上下文依赖对象。 + Returns: (None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ providers = await postgres.get_provider.remote() for provider in providers: self.provider_register[provider.provider_title] = provider async def add_provider(self, provider_type, provider_title, provider_url, provider_apikey, provider_owner, postgres_database) -> None: + """创建并持久化新的 provider 实体。 + 接收构建参数,执行必要的数据校验与默认值填充后,将新记录安全地写入底层存储或系统注册表中。 + Args: provider_type: 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_type 实例。 provider_title: 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_title 实例。 provider_url: 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_url 实例。 provider_apikey: 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_apikey 实例。 provider_owner: 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_owner 实例。 postgres_database: 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 + Returns: (None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ from pretor.core.global_state_machine.model_provider import ProviderArgs from pretor.utils.logger import get_logger logger = get_logger('provider_manager') @@ -74,12 +82,23 @@ class ProviderManager: logger.warning(f"[{provider_args.provider_title}] 解析模型列表时发生错误: {e}") def get_provider_list(self): + """检索并获取特定的 provider list 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return self.provider_register def get_provider(self, provider_title): + """检索并获取特定的 provider 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: provider_title: 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_title 实例。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return self.provider_register.get(provider_title) async def delete_provider(self, provider_title: str, postgres_database) -> None: + """安全地移除或注销 provider。 + 执行物理删除或逻辑删除操作,并妥善清理相关的关联数据及占用资源。 + Args: provider_title (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 provider_title 实例。 postgres_database: 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 + Returns: (None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ if provider_title in self.provider_register: provider = self.provider_register[provider_title] await postgres_database.delete_provider_db.remote( provider_id=provider.provider_id) diff --git a/pretor/core/global_state_machine/skill_manager.py b/pretor/core/global_state_machine/skill_manager.py index 6021a83..85a556c 100644 --- a/pretor/core/global_state_machine/skill_manager.py +++ b/pretor/core/global_state_machine/skill_manager.py @@ -18,6 +18,8 @@ import pathlib import json class GlobalSkillManager: + """GlobalSkillManager 核心组件类。 + 这是一个管理器类,职责集中在维护整个系统内有关 GlobalSkill 资源的全局生命周期。它提供了注册机制、状态同步以及跨组件的统一查询入口,确保系统中该类型资源的实例一致性与可控性。 """ skill_mapper = Dict[str,Tuple[str]] """skill的存储表""" diff --git a/pretor/core/global_state_machine/tool_manager.py b/pretor/core/global_state_machine/tool_manager.py index ec41a1e..4c7b82e 100644 --- a/pretor/core/global_state_machine/tool_manager.py +++ b/pretor/core/global_state_machine/tool_manager.py @@ -22,6 +22,8 @@ from pretor.utils.logger import get_logger logger = get_logger('tool_manager') class GlobalToolManager: + """GlobalToolManager 核心组件类。 + 这是一个管理器类,职责集中在维护整个系统内有关 GlobalTool 资源的全局生命周期。它提供了注册机制、状态同步以及跨组件的统一查询入口,确保系统中该类型资源的实例一致性与可控性。 """ tool_mapper: Dict[str, Dict[str, Type[BaseToolData]]] def __init__(self): diff --git a/pretor/core/individual/consciousness_node/consciousness_node.py b/pretor/core/individual/consciousness_node/consciousness_node.py index 2f5fa5d..6ee07e0 100644 --- a/pretor/core/individual/consciousness_node/consciousness_node.py +++ b/pretor/core/individual/consciousness_node/consciousness_node.py @@ -25,6 +25,8 @@ from pretor.adapter.model_adapter.agent_factory import AgentFactory @ray.remote class ConsciousnessNode: + """ConsciousnessNode 核心组件类。 + 这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。 """ def __init__(self) -> None: from pretor.utils.logger import get_logger self.logger = get_logger('consciousness_node') @@ -70,6 +72,10 @@ class ConsciousnessNode: @self.agent.system_prompt async def dynamic_prompt(ctx: RunContext[ConsciousnessNodeDeps]): + """执行与 dynamic prompt 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: ctx (RunContext[ConsciousnessNodeDeps]): 参与 dynamic prompt 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ prompt = system_prompt + "\n\n" prompt += ( f"=== 当前任务上下文 ===\n" @@ -87,6 +93,10 @@ class ConsciousnessNode: return prompt async def working(self, payload: Union[ForWorkflowEngineInput, ForWorkflowInput, ForSupervisoryInput]) -> Union[ForWorkflowEngine, ForWorkflow, ForSupervisoryNode, None]: + """执行与 working 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: payload (Union[ForWorkflowEngineInput, ForWorkflowInput, ForSupervisoryInput]): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 + Returns: (Union[ForWorkflowEngine, ForWorkflow, ForSupervisoryNode, None]): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ try: result = await self._run(payload) if isinstance(result, (ForWorkflowEngine, ForWorkflow, ForSupervisoryNode)): @@ -139,6 +149,10 @@ class ConsciousnessNode: pass async def _run(self, payload: Union[ForSupervisoryInput, ForWorkflowInput, ForWorkflowEngineInput]) -> Union[ForSupervisoryNode, ForWorkflow, ForWorkflowEngine]: + """执行与 run 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: payload (Union[ForSupervisoryInput, ForWorkflowInput, ForWorkflowEngineInput]): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 + Returns: (Union[ForSupervisoryNode, ForWorkflow, ForWorkflowEngine]): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ try: self.agent.retries = 3 if isinstance(payload, ForWorkflowEngineInput): diff --git a/pretor/core/individual/consciousness_node/template.py b/pretor/core/individual/consciousness_node/template.py index 161f483..6141764 100644 --- a/pretor/core/individual/consciousness_node/template.py +++ b/pretor/core/individual/consciousness_node/template.py @@ -41,6 +41,8 @@ class ForSupervisoryNode(ConsciousnessNodeResponse): class ConsciousnessNodeDeps(DepsModel): + """ConsciousnessNodeDeps 核心组件类。 + 这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。 """ original_command: str workflow_template: str | None = None command: str @@ -48,20 +50,28 @@ class ConsciousnessNodeDeps(DepsModel): class ConsciousnessNodeInput(InputModel): + """ConsciousnessNodeInput 核心组件类。 + 这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。 """ pass class ForWorkflowEngineInput(ConsciousnessNodeInput): + """ForWorkflowEngineInput 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 ForWorkflowEngineInput 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ workflow_template: str | None = None original_command: str available_skills: list[dict] | None = None class ForWorkflowInput(ConsciousnessNodeInput): + """ForWorkflowInput 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 ForWorkflowInput 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ workflow_step: WorkStep original_command: str class ForSupervisoryInput(ConsciousnessNodeInput): + """ForSupervisoryInput 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 ForSupervisoryInput 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ workflow: PretorWorkflow original_command: str diff --git a/pretor/core/individual/control_node/control_node.py b/pretor/core/individual/control_node/control_node.py index 5e1ed50..468befd 100644 --- a/pretor/core/individual/control_node/control_node.py +++ b/pretor/core/individual/control_node/control_node.py @@ -23,6 +23,8 @@ from pretor.core.individual.control_node.template import ForWorkflow, ForWorkflo @ray.remote class ControlNode: + """ControlNode 核心组件类。 + 这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。 """ def __init__(self): from pretor.utils.logger import get_logger self.logger = get_logger('control_node') @@ -67,6 +69,10 @@ class ControlNode: tools=callables) @self.agent.system_prompt async def dynamic_prompt(ctx: RunContext[ControlNodeDeps]): + """执行与 dynamic prompt 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: ctx (RunContext[ControlNodeDeps]): 参与 dynamic prompt 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ prompt = system_prompt + "\n\n" prompt += ( f"=== 当前任务步骤上下文 ===\n" @@ -77,6 +83,10 @@ class ControlNode: return prompt async def working(self, payload: ForWorkflowInput) -> str: + """执行与 working 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: payload (ForWorkflowInput): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 + Returns: (str): 处理流程所输出的具体字符串产物,可能是新生成的 ID 序列、格式化好的文本片段或 LLM 推理的回答内容。 """ try: result: ForWorkflow = await self._run(payload) return result @@ -85,6 +95,10 @@ class ControlNode: return None async def _run(self, payload: ForWorkflowInput) -> ForWorkflow: + """执行与 run 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: payload (ForWorkflowInput): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 + Returns: (ForWorkflow): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ try: self.agent.retries = 3 deps = ControlNodeDeps( diff --git a/pretor/core/individual/control_node/template.py b/pretor/core/individual/control_node/template.py index 6fb21b8..0ccf3a4 100644 --- a/pretor/core/individual/control_node/template.py +++ b/pretor/core/individual/control_node/template.py @@ -23,17 +23,25 @@ class ControlNodeResponse(ResponseModel): class ControlNodeInput(InputModel): + """ControlNodeInput 核心组件类。 + 这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。 """ pass class ControlNodeDeps(DepsModel): + """ControlNodeDeps 核心组件类。 + 这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。 """ workflow_step: WorkStep # In the future, this can be dynamically populated with tools specific to the current task execution class ForWorkflow(ControlNodeResponse): + """ForWorkflow 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 ForWorkflow 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ output: str = Field(..., description="控制节点执行特定工作流步骤的结果。包含执行细节和输出数据。") class ForWorkflowInput(ControlNodeInput): + """ForWorkflowInput 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 ForWorkflowInput 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ workflow_step: WorkStep diff --git a/pretor/core/individual/supervisory_node/supervisory_node.py b/pretor/core/individual/supervisory_node/supervisory_node.py index ec453ba..2f46c67 100644 --- a/pretor/core/individual/supervisory_node/supervisory_node.py +++ b/pretor/core/individual/supervisory_node/supervisory_node.py @@ -26,6 +26,8 @@ from pretor.utils.ray_hook import ray_actor_hook @ray.remote class SupervisoryNode: + """SupervisoryNode 核心组件类。 + 这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。 """ def __init__(self) -> None: from pretor.utils.logger import get_logger self.logger = get_logger('supervisory_node') @@ -71,6 +73,10 @@ class SupervisoryNode: @self.agent.system_prompt async def dynamic_prompt(ctx: RunContext[SupervisoryNodeDeps]): + """执行与 dynamic prompt 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: ctx (RunContext[SupervisoryNodeDeps]): 参与 dynamic prompt 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ prompt = system_prompt + "\n\n" prompt += ( f"=== 当前上下文 ===\n" @@ -111,8 +117,8 @@ class SupervisoryNode: if isinstance(payload, PretorEvent): payload.context["workflow_template"] = result.workflow_template try: - global_state_machine = ray_actor_hook("global_state_machine").global_state_machine - await global_state_machine.add_event.remote(payload) + global_workflow_manager = ray_actor_hook("global_workflow_manager").global_workflow_manager + await global_workflow_manager.add_event.remote(payload) workflow_running_engine = ray_actor_hook("workflow_running_engine").workflow_running_engine await workflow_running_engine.put_event.remote(payload) except Exception as e: diff --git a/pretor/core/individual/supervisory_node/template.py b/pretor/core/individual/supervisory_node/template.py index b7eb59d..e72c9da 100644 --- a/pretor/core/individual/supervisory_node/template.py +++ b/pretor/core/individual/supervisory_node/template.py @@ -17,21 +17,31 @@ from pretor.utils.agent_model import ResponseModel, DepsModel from pydantic import BaseModel class SupervisoryNodeResponse(ResponseModel): + """SupervisoryNodeResponse 核心组件类。 + 这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。 """ pass class ForUser(SupervisoryNodeResponse): + """ForUser 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 ForUser 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ context: str = Field(..., description="对用户的回复,应当使用和蔼的语气进行回复。用于直接解答简单问题或返回最终报告。") class ForConsciousnessNode(SupervisoryNodeResponse): + """ForConsciousnessNode 核心组件类。 + 这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。 """ workflow_template: str | None = Field(default=None, description="选择的工作流模板的名称,用于处理复杂任务。若无需模板则为 None。") reasoning: str = Field(..., description="选择将任务移交意识节点并选用该模板的简短原因。") class TerminationMessage(BaseModel): + """TerminationMessage 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 TerminationMessage 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ platform: str user_name: str message: str class SupervisoryNodeDeps(DepsModel): + """SupervisoryNodeDeps 核心组件类。 + 这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。 """ platform: str user_name: str time: str diff --git a/pretor/core/workflow/workflow.py b/pretor/core/workflow/workflow.py index cfcb476..2283430 100644 --- a/pretor/core/workflow/workflow.py +++ b/pretor/core/workflow/workflow.py @@ -22,14 +22,20 @@ NodeType = Literal[ ] class EventInfo(BaseModel): + """EventInfo 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 EventInfo 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ platform: str user_name: str class LogicGate(BaseModel): + """LogicGate 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 LogicGate 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ if_fail: str = Field(..., description="失败跳转目标,如 'jump_to_step_1'") if_pass: Literal["continue", "exit"] = Field(default="continue", description="成功后的动作") class WorkStep(BaseModel): + """WorkStep 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 WorkStep 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ step: int = Field(..., gt=0, description="步骤序号,严格自增") name: str = Field(..., description="步骤名称") node: NodeType = Field(..., description="负责执行的节点类型") @@ -46,6 +52,8 @@ class WorkStep(BaseModel): class WorkflowStatus(BaseModel): + """WorkflowStatus 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 WorkflowStatus 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ step: int = Field(default=1, gt=0, description="当前运行到的工作流步数") status: Literal["waiting_llm_working", "waiting_tool_working", "llm_working", "tool_working"] = Field( default="waiting_llm_working", @@ -53,6 +61,8 @@ class WorkflowStatus(BaseModel): ) class PretorWorkflow(BaseModel): + """PretorWorkflow 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 PretorWorkflow 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ title: str = Field(..., description="工作流的标题") work_link: List[WorkStep] = Field(..., description="工作链逻辑定义") # ---------------- 以下为系统级管控字段,LLM 无需关心 ---------------- # @@ -66,6 +76,9 @@ class PretorWorkflow(BaseModel): @model_validator(mode='after') def validate_workflow_integrity(self) -> 'PretorWorkflow': + """执行与 validate workflow integrity 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Returns: ('PretorWorkflow'): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ steps = [s.step for s in self.work_link] expected = list(range(1, len(steps) + 1)) if steps != expected: diff --git a/pretor/core/workflow/workflow_runner.py b/pretor/core/workflow/workflow_runner.py index afba38a..19edd61 100644 --- a/pretor/core/workflow/workflow_runner.py +++ b/pretor/core/workflow/workflow_runner.py @@ -34,6 +34,10 @@ import pathlib def get_workflow_template(workflow_name: str) -> str: + """检索并获取特定的 workflow template 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: workflow_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 + Returns: (str): 处理流程所输出的具体字符串产物,可能是新生成的 ID 序列、格式化好的文本片段或 LLM 推理的回答内容。 """ workflow_template = pathlib.Path(__file__).parent.parent.parent / "workflow_template" / (workflow_name + "_workflow_template.json") with open(workflow_template, "r", encoding="utf-8") as workflow_template_file: workflow_template = workflow_template_file.read() @@ -41,6 +45,8 @@ def get_workflow_template(workflow_name: str) -> str: class WorkflowEngine: + """WorkflowEngine 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 WorkflowEngine 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ def __init__(self, workflow: PretorWorkflow, consciousness_node=None, @@ -58,11 +64,15 @@ class WorkflowEngine: """控制节点""" self.supervisory_node = supervisory_node """监督节点""" - self._gsm = ray_actor_hook("global_state_machine").global_state_machine + self._gwm = ray_actor_hook("global_workflow_manager").global_workflow_manager async def _push_sse(self, msg: str) -> None: + """执行与 push sse 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: msg (str): 控制逻辑流向的具体字符串参数,指定了期望的 msg 内容。 + Returns: (None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ try: - await self._gsm.put_pending.remote(self.workflow.trace_id, msg) + await self._gwm.put_pending.remote(self.workflow.trace_id, msg) except Exception: pass @@ -273,6 +283,8 @@ class WorkflowEngine: @ray.remote class WorkflowRunningEngine: + """WorkflowRunningEngine 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 WorkflowRunningEngine 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ def __init__(self, consciousness_node=None, control_node=None, supervisory_node=None): from pretor.utils.logger import get_logger self.logger = get_logger('workflow_runner') @@ -285,6 +297,8 @@ class WorkflowRunningEngine: async def run(self): # Move actor hook to async start so we don't race during __init__ across cluster + """执行与 run 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 """ self.global_state_machine = ray_actor_hook("global_state_machine").global_state_machine self.workflow_queue = asyncio.Queue() self.runner_engine = { @@ -293,8 +307,22 @@ class WorkflowRunningEngine: } async def put_event(self, event: PretorEvent) -> None: + """执行与 put event 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: event (PretorEvent): 由事件总线或工作流引擎分发过来的事件载荷,封装了触发此次调用的上下文快照与任务目标指令。 + Returns: (None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ await self.workflow_queue.put(event) + async def resume_workflow(self, event: PretorEvent) -> None: + """Resume an incomplete workflow that was loaded from the database.""" + self.logger.info(f"Resuming workflow {event.trace_id}") + workflow_engine = WorkflowEngine(event.workflow, + self.consciousness_node, + self.control_node, + self.supervisory_node) + # Assuming you want to schedule it via a task + asyncio.create_task(workflow_engine.run()) + async def runner(self, i: int) -> None: """ runner方法,从self.workflow_queue中不断取出任务并执行 @@ -347,7 +375,8 @@ class WorkflowRunningEngine: self.logger.info( f"WorkflowRunningEngine: runner_{i} 成功生成工作流 {workflow.trace_id}:{workflow.title}") - await self.global_state_machine.update_workflow.remote(event.trace_id, workflow) + global_workflow_manager = ray_actor_hook("global_workflow_manager").global_workflow_manager + await global_workflow_manager.update_workflow.remote(event.trace_id, workflow) workflow_engine = WorkflowEngine(workflow, self.consciousness_node, diff --git a/pretor/core/workflow/workflow_template_generator/workflow_template.py b/pretor/core/workflow/workflow_template_generator/workflow_template.py index 3686d8d..f7f8e3c 100644 --- a/pretor/core/workflow/workflow_template_generator/workflow_template.py +++ b/pretor/core/workflow/workflow_template_generator/workflow_template.py @@ -16,6 +16,8 @@ from pydantic import BaseModel, model_validator from typing import Dict,List class WorkflowTemplateStep(BaseModel): + """WorkflowTemplateStep 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 WorkflowTemplateStep 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ step: int node: str action: str @@ -25,12 +27,17 @@ class WorkflowTemplateStep(BaseModel): logic_gate: Dict[str, str] class WorkflowTemplate(BaseModel): + """WorkflowTemplate 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 WorkflowTemplate 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ name: str desc: str work_link: list[WorkflowTemplateStep] @model_validator(mode='after') def validate_steps(self) -> 'WorkflowTemplate': + """执行与 validate steps 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Returns: ('WorkflowTemplate'): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ steps = [s.step for s in self.work_link] if len(steps) != len(set(steps)): raise ValueError("Step numbers in work_link must be unique") diff --git a/pretor/core/workflow/workflow_template_generator/workflow_template_generator.py b/pretor/core/workflow/workflow_template_generator/workflow_template_generator.py index 4bb821e..e249e5d 100644 --- a/pretor/core/workflow/workflow_template_generator/workflow_template_generator.py +++ b/pretor/core/workflow/workflow_template_generator/workflow_template_generator.py @@ -16,8 +16,14 @@ from pathlib import Path from pretor.core.workflow.workflow_template_generator.workflow_template import WorkflowTemplate class WorkflowTemplateGenerator: + """WorkflowTemplateGenerator 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 WorkflowTemplateGenerator 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ @staticmethod def generate_workflow_template(workflow_template: WorkflowTemplate) -> WorkflowTemplate: + """执行与 generate workflow template 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: workflow_template (WorkflowTemplate): 参与 generate workflow template 逻辑运算或数据构建的上下文依赖对象。 + Returns: (WorkflowTemplate): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ output_dir = Path("pretor") / "workflow_template" if not output_dir.exists(): output_dir.mkdir(parents=True) diff --git a/pretor/core/workflow/workflow_template_manager.py b/pretor/core/workflow/workflow_template_manager.py index 109a67d..3ad0d8e 100644 --- a/pretor/core/workflow/workflow_template_manager.py +++ b/pretor/core/workflow/workflow_template_manager.py @@ -21,6 +21,8 @@ from pretor.utils.logger import get_logger logger = get_logger('workflow_template_manager') class WorkflowManager: + """WorkflowManager 核心组件类。 + 这是一个管理器类,职责集中在维护整个系统内有关 Workflow 资源的全局生命周期。它提供了注册机制、状态同步以及跨组件的统一查询入口,确保系统中该类型资源的实例一致性与可控性。 """ def __init__(self): self.workflow_template_generator = WorkflowTemplateGenerator() self.workflow_templates_registry = {} @@ -28,6 +30,9 @@ class WorkflowManager: self._load_workflow_template() def _load_workflow_template(self) -> None: + """执行与 load workflow template 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Returns: (None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ for workflow_template_file in self.template_path.glob("*_workflow_template.json"): with workflow_template_file.open("r",encoding="utf-8") as f: try: @@ -39,6 +44,10 @@ class WorkflowManager: logger.warning(f"{workflow_template_file}不符合workflow_template格式") def generate_workflow_template(self, workflow_template: WorkflowTemplate) -> None: + """执行与 generate workflow template 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: workflow_template (WorkflowTemplate): 参与 generate workflow template 逻辑运算或数据构建的上下文依赖对象。 + Returns: (None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ try: workflow_template = self.workflow_template_generator.generate_workflow_template(workflow_template=workflow_template) self.workflow_templates_registry[workflow_template.name] = workflow_template.desc @@ -46,11 +55,22 @@ class WorkflowManager: logger.exception("Failed to generate workflow template") def add_workflow_template(self, template_name: str, workflow_template: WorkflowTemplate) -> None: + """创建并持久化新的 workflow template 实体。 + 接收构建参数,执行必要的数据校验与默认值填充后,将新记录安全地写入底层存储或系统注册表中。 + Args: template_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 workflow_template (WorkflowTemplate): 参与 add workflow template 逻辑运算或数据构建的上下文依赖对象。 + Returns: (None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ self.generate_workflow_template(workflow_template) def get_all_workflow_templates(self) -> dict: + """检索并获取特定的 all workflow templates 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Returns: (dict): 高度聚合的字典结构数据,将多维度的属性特征或统计指标组合后一并返回。 """ return self.workflow_templates_registry def delete_workflow_template(self, template_name: str) -> None: + """安全地移除或注销 workflow template。 + 执行物理删除或逻辑删除操作,并妥善清理相关的关联数据及占用资源。 + Args: template_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 + Returns: (None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ if template_name in self.workflow_templates_registry: del self.workflow_templates_registry[template_name] \ No newline at end of file diff --git a/pretor/plugin/__init__.py b/pretor/plugin/__init__.py index e69de29..319b42c 100644 --- a/pretor/plugin/__init__.py +++ b/pretor/plugin/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2026 zhaoxi826 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/pretor/plugin/tool_plugin/approval/__init__.py b/pretor/plugin/tool_plugin/approval/__init__.py index 6a66610..b53f657 100644 --- a/pretor/plugin/tool_plugin/approval/__init__.py +++ b/pretor/plugin/tool_plugin/approval/__init__.py @@ -1,2 +1,16 @@ +# Copyright 2026 zhaoxi826 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from .approval import ApprovalToolData, approval __all__ = ["ApprovalToolData", "approval"] diff --git a/pretor/plugin/tool_plugin/approval/approval.py b/pretor/plugin/tool_plugin/approval/approval.py index e834bef..1eeba85 100644 --- a/pretor/plugin/tool_plugin/approval/approval.py +++ b/pretor/plugin/tool_plugin/approval/approval.py @@ -17,6 +17,8 @@ from pretor.utils.ray_hook import ray_actor_hook from typing import List, Literal, Dict class ApprovalToolData(BaseToolData): + """ApprovalToolData 核心组件类。 + 这是一个可被智能体动态调用的外部工具组件类。它定义了清晰的输入参数 Schema 与执行契约,赋予智能体与外界真实系统(如文件、网页、API)进行交互的能力。 """ is_system: bool = True action_scope: List[Literal["control_node", "consciousness_node", "supervisory_node", "growth_node", "", ""]] = [ "control_node", "consciousness_node"] diff --git a/pretor/plugin/tool_plugin/base_tool.py b/pretor/plugin/tool_plugin/base_tool.py index f97f959..92cae0e 100644 --- a/pretor/plugin/tool_plugin/base_tool.py +++ b/pretor/plugin/tool_plugin/base_tool.py @@ -17,6 +17,8 @@ from typing import List, Literal, Dict from pydantic import ConfigDict class BaseToolData(BaseModel): + """BaseToolData 核心组件类。 + 这是一个可被智能体动态调用的外部工具组件类。它定义了清晰的输入参数 Schema 与执行契约,赋予智能体与外界真实系统(如文件、网页、API)进行交互的能力。 """ model_config = ConfigDict(extra="allow") is_system: bool action_scope: List[Literal["control_node", "consciousness_node", "supervisory_node", "growth_node", "", ""]] = [] diff --git a/pretor/plugin/tool_plugin/file_reader/__init__.py b/pretor/plugin/tool_plugin/file_reader/__init__.py index 30143e6..2311299 100644 --- a/pretor/plugin/tool_plugin/file_reader/__init__.py +++ b/pretor/plugin/tool_plugin/file_reader/__init__.py @@ -1,3 +1,17 @@ +# Copyright 2026 zhaoxi826 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from .file_reader import FileReaderData, file_reader __all__ = ["FileReaderData", "file_reader"] diff --git a/pretor/plugin/tool_plugin/file_reader/file_reader.py b/pretor/plugin/tool_plugin/file_reader/file_reader.py index 10d1711..71aec3a 100644 --- a/pretor/plugin/tool_plugin/file_reader/file_reader.py +++ b/pretor/plugin/tool_plugin/file_reader/file_reader.py @@ -17,6 +17,8 @@ from pretor.plugin.tool_plugin.base_tool import BaseToolData import os class FileReaderData(BaseToolData): + """FileReaderData 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 FileReaderData 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ is_system: bool = True name: str = "file_reader" description: str = "读取本地文件的内容" diff --git a/pretor/utils/access.py b/pretor/utils/access.py index bd5cfef..e9548e2 100644 --- a/pretor/utils/access.py +++ b/pretor/utils/access.py @@ -23,6 +23,8 @@ from pwdlib import PasswordHash class TokenData(BaseModel): + """TokenData 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 TokenData 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ user_id: str username: Optional[str] = None exp: Optional[int] = None @@ -31,12 +33,21 @@ SECRET_KEY = os.getenv("SECRET_KEY") ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 +if not SECRET_KEY or SECRET_KEY in {"secret", "114514"}: + raise RuntimeError("未提供有效的 SECRET_KEY 或使用了不安全的默认值") + password_hasher = PasswordHash.recommended() class Accessor: + """Accessor 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 Accessor 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ @staticmethod def _decode_token(token: str) -> TokenData: + """执行与 decode token 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: token (str): 由认证中心颁发的 JWT 或长期访问令牌,用于跨服务调用时的身份自证与权限校验。 + Returns: (TokenData): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ try: payload = jwt.decode( token, @@ -57,6 +68,10 @@ class Accessor: @staticmethod def _create_access_token(data: dict) -> str: + """创建并持久化新的 access token 实体。 + 接收构建参数,执行必要的数据校验与默认值填充后,将新记录安全地写入底层存储或系统注册表中。 + Args: data (dict): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 + Returns: (str): 处理流程所输出的具体字符串产物,可能是新生成的 ID 序列、格式化好的文本片段或 LLM 推理的回答内容。 """ to_encode = data.copy() expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) to_encode.update({"exp": int(expire.timestamp())}) @@ -64,10 +79,18 @@ class Accessor: @staticmethod def verify_password(plain_password: str, hashed_password: str) -> bool: + """执行与 verify password 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: plain_password (str): 控制逻辑流向的具体字符串参数,指定了期望的 plain password 内容。 hashed_password (str): 控制逻辑流向的具体字符串参数,指定了期望的 hashed password 内容。 + Returns: (bool): 一个布尔型结果标志,明确返回 True 表示该操作成功应用或条件达成,False 则表示失败或被拒绝。 """ return password_hasher.verify(plain_password, hashed_password) @staticmethod def get_current_user(request: Request) -> TokenData: + """检索并获取特定的 current user 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: request (Request): FastAPI 框架注入的原生 HTTP 请求对象,包含了完整的 Header 标头、查询参数和正文流。 + Returns: (TokenData): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ auth_header = request.headers.get("Authorization") if not auth_header or not auth_header.startswith("Bearer "): raise HTTPException( @@ -79,6 +102,10 @@ class Accessor: @staticmethod def login_hashed_password(user: User, password: str) -> str: + """执行与 login hashed password 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: user (User): 当前已通过鉴权流程的访问者实体对象,内部包含用户角色、权限层级及租户归属等核心元信息。 password (str): 控制逻辑流向的具体字符串参数,指定了期望的 password 内容。 + Returns: (str): 处理流程所输出的具体字符串产物,可能是新生成的 ID 序列、格式化好的文本片段或 LLM 推理的回答内容。 """ if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -97,6 +124,10 @@ class Accessor: @staticmethod def hash_password(password: str) -> str: + """执行与 hash password 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: password (str): 控制逻辑流向的具体字符串参数,指定了期望的 password 内容。 + Returns: (str): 处理流程所输出的具体字符串产物,可能是新生成的 ID 序列、格式化好的文本片段或 LLM 推理的回答内容。 """ if not password: raise ValueError("密码不能为空") if len(password) < 6: diff --git a/pretor/utils/agent_model.py b/pretor/utils/agent_model.py index ef9e0e3..048c97e 100644 --- a/pretor/utils/agent_model.py +++ b/pretor/utils/agent_model.py @@ -16,10 +16,16 @@ from pydantic import BaseModel class ResponseModel(BaseModel): + """ResponseModel 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 ResponseModel 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ pass class DepsModel(BaseModel): + """DepsModel 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 DepsModel 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ pass class InputModel(BaseModel): + """InputModel 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 InputModel 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ pass \ No newline at end of file diff --git a/pretor/utils/banner.py b/pretor/utils/banner.py index 56e3b66..8908c08 100644 --- a/pretor/utils/banner.py +++ b/pretor/utils/banner.py @@ -1,7 +1,24 @@ +# Copyright 2026 zhaoxi826 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from rich.console import Console from rich.text import Text import yaml def print_banner() -> None: + """执行与 print banner 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Returns: (None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ with open("config/config.yml","r") as config: config = yaml.load(config, Loader=yaml.FullLoader) version = config.get("version", "unknown") diff --git a/pretor/utils/check_user/role_check.py b/pretor/utils/check_user/role_check.py index a589a62..c04a217 100644 --- a/pretor/utils/check_user/role_check.py +++ b/pretor/utils/check_user/role_check.py @@ -18,6 +18,10 @@ from pretor.core.database.table.user import UserAuthority from pretor.utils.ray_hook import ray_actor_hook async def get_authority(user_id: str) -> UserAuthority: + """检索并获取特定的 authority 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: user_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 user 实例。 + Returns: (UserAuthority): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ from pretor.utils.error import UserNotExistError postgres_database = ray_actor_hook("postgres_database").postgres_database try: @@ -38,11 +42,17 @@ async def get_authority(user_id: str) -> UserAuthority: raise class RoleChecker: + """RoleChecker 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 RoleChecker 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ def __init__(self, **kwargs): self.allowed_roles = kwargs.get("allowed_roles", ) async def __call__(self, token_data: Annotated[TokenData, Depends(Accessor.get_current_user)]): + """执行与 call 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: token_data (Annotated[TokenData, Depends(Accessor.get_current_user)]): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ user_authority = await get_authority(token_data.user_id) if user_authority < self.allowed_roles: raise HTTPException( diff --git a/pretor/utils/error.py b/pretor/utils/error.py index db536a1..ebe1654 100644 --- a/pretor/utils/error.py +++ b/pretor/utils/error.py @@ -21,30 +21,48 @@ class NonRetryableError(Exception): pass class DemandError(NonRetryableError): + """DemandError 核心组件类。 + 这是一个自定义异常类,专门用于在 Demand 相关业务流程中触发中断。它携带了精确的错误上下文与追溯代码,帮助最外层网关能够统一捕获并返回友好的前端错误提示。 """ pass class ModelNotExistError(Exception): + """ModelNotExistError 核心组件类。 + 这是一个自定义异常类,专门用于在 ModelNotExist 相关业务流程中触发中断。它携带了精确的错误上下文与追溯代码,帮助最外层网关能够统一捕获并返回友好的前端错误提示。 """ pass class UserError(Exception): + """UserError 核心组件类。 + 这是一个自定义异常类,专门用于在 User 相关业务流程中触发中断。它携带了精确的错误上下文与追溯代码,帮助最外层网关能够统一捕获并返回友好的前端错误提示。 """ pass class UserNotExistError(UserError): + """UserNotExistError 核心组件类。 + 这是一个自定义异常类,专门用于在 UserNotExist 相关业务流程中触发中断。它携带了精确的错误上下文与追溯代码,帮助最外层网关能够统一捕获并返回友好的前端错误提示。 """ pass class UserPasswordError(UserError): + """UserPasswordError 核心组件类。 + 这是一个自定义异常类,专门用于在 UserPassword 相关业务流程中触发中断。它携带了精确的错误上下文与追溯代码,帮助最外层网关能够统一捕获并返回友好的前端错误提示。 """ pass class ProviderError(Exception): + """ProviderError 核心组件类。 + 这是一个模型/服务提供商适配器类,屏蔽了外部不同供应商(如 OpenAI、Anthropic 等)的底层 API 差异。它负责标准化参数组装、网络请求发送、鉴权处理以及响应结构的反序列化。 """ pass class ProviderNotExistError(ProviderError): + """ProviderNotExistError 核心组件类。 + 这是一个模型/服务提供商适配器类,屏蔽了外部不同供应商(如 OpenAI、Anthropic 等)的底层 API 差异。它负责标准化参数组装、网络请求发送、鉴权处理以及响应结构的反序列化。 """ pass class WorkflowError(Exception): + """WorkflowError 核心组件类。 + 这是一个自定义异常类,专门用于在 Workflow 相关业务流程中触发中断。它携带了精确的错误上下文与追溯代码,帮助最外层网关能够统一捕获并返回友好的前端错误提示。 """ pass class WorkflowExit(WorkflowError): + """WorkflowExit 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 WorkflowExit 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ pass diff --git a/pretor/utils/get_tool.py b/pretor/utils/get_tool.py index f6f8346..13fa59b 100644 --- a/pretor/utils/get_tool.py +++ b/pretor/utils/get_tool.py @@ -16,8 +16,6 @@ import importlib.util import os import sys from typing import Callable, Dict, List -import pathlib -from pretor.utils.ray_hook import ray_actor_hook from pretor.utils.logger import get_logger logger = get_logger('get_tool') @@ -25,6 +23,10 @@ _tool_cache: Dict[str, Callable] = {} def _get_tool_func(tool_name: str) -> Callable | None: + """检索并获取特定的 tool func 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: tool_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 + Returns: (Callable | None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ func = _tool_cache.get(tool_name, None) if func: return func @@ -64,10 +66,18 @@ def _get_tool_func(tool_name: str) -> Callable | None: return None def del_tool_cache(tool_name: str) -> None: + """执行与 del tool cache 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: tool_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 + Returns: (None): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ if tool_name in _tool_cache: del _tool_cache[tool_name] def load_tools_from_list(tool_names: List[str] | None) -> List[Callable]: + """执行与 load tools from list 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: tool_names (List[str] | None): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 + Returns: (List[Callable]): 经过筛选、排序或分页处理后的实体对象列表集合。 """ if not tool_names: return [] diff --git a/pretor/utils/logger.py b/pretor/utils/logger.py index 925f3af..0c6ad98 100644 --- a/pretor/utils/logger.py +++ b/pretor/utils/logger.py @@ -17,10 +17,17 @@ from rich.logging import RichHandler from loguru._logger import Logger def setup_logger() -> Logger: + """对现有的 setup logger 进行状态更新或属性覆盖。 + 基于增量变更原则,合并最新的配置或数据,并触发相关依赖组件的缓存刷新或事件通知。 + Returns: (Logger): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ logger.remove() def format_record(record): # Format string for rich handler + """执行与 format record 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: record: 参与 format record 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ actor = record["extra"].get("actor_name", "System") trace_id = record["extra"].get("trace_id", "") @@ -41,4 +48,8 @@ def setup_logger() -> Logger: global_logger = setup_logger() def get_logger(actor_name: str, trace_id: str = "") -> Logger: + """检索并获取特定的 logger 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: actor_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 trace_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 trace 实例。 + Returns: (Logger): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return global_logger.bind(actor_name=actor_name, trace_id=trace_id) diff --git a/pretor/utils/pickle.py b/pretor/utils/pickle.py index d9db22c..d96c83f 100644 --- a/pretor/utils/pickle.py +++ b/pretor/utils/pickle.py @@ -29,6 +29,9 @@ def pickle(cls: T) -> T: """ def __reduce__(self): # 1. 序列化:触发 Pydantic-core (Rust) 的极速序列化 + """执行与 reduce 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ data = self.model_dump_json() # 2. 反序列化:告诉 Pickle 重建时调用 cls.model_validate_json return cls.model_validate_json, (data,) diff --git a/pretor/utils/ray_hook.py b/pretor/utils/ray_hook.py index 90cdac9..606e396 100644 --- a/pretor/utils/ray_hook.py +++ b/pretor/utils/ray_hook.py @@ -15,18 +15,30 @@ import ray from functools import lru_cache class ActorList: + """ActorList 核心组件类。 + 这是一个领域数据模型或功能封装类,承载了 ActorList 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。 """ def __init__(self): super().__setattr__('dict', {}) def __setattr__(self, key, value): + """对现有的 setattr 进行状态更新或属性覆盖。 + 基于增量变更原则,合并最新的配置或数据,并触发相关依赖组件的缓存刷新或事件通知。 + Args: key: 参与 setattr 逻辑运算或数据构建的上下文依赖对象。 value: 参与 setattr 逻辑运算或数据构建的上下文依赖对象。 """ self.dict[key] = value def __getattr__(self, key): + """检索并获取特定的 getattr 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Args: key: 参与 getattr 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ if key in self.dict: return self.dict[key] raise AttributeError(f"ActorList 对象没有属性 '{key}'") def __delattr__(self, key): + """执行与 delattr 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: key: 参与 delattr 逻辑运算或数据构建的上下文依赖对象。 """ if key in self.dict: del self.dict[key] else: @@ -42,6 +54,9 @@ def clear_actor_cache(): _get_cached_actor_handle.cache_clear() def ray_actor_hook(*actor_names: str): + """执行与 ray actor hook 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ actor_list = ActorList() for actor_name in actor_names: handle = _get_cached_actor_handle(actor_name) diff --git a/pretor/utils/retry.py b/pretor/utils/retry.py index 38f8278..ed28ed4 100644 --- a/pretor/utils/retry.py +++ b/pretor/utils/retry.py @@ -1,13 +1,38 @@ +# Copyright 2026 zhaoxi826 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import asyncio from functools import wraps from pretor.utils.error import RetryableError def retry_on_retryable_error(max_retries=3, base_delay=1): + """执行与 retry on retryable error 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: max_retries: 参与 retry on retryable error 逻辑运算或数据构建的上下文依赖对象。 base_delay: 参与 retry on retryable error 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ def decorator(func): + """执行与 decorator 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: func: 参与 decorator 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ if asyncio.iscoroutinefunction(func): @wraps(func) async def async_wrapper(*args, **kwargs): + """执行与 async wrapper 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ for attempt in range(max_retries): try: return await func(*args, **kwargs) @@ -19,6 +44,9 @@ def retry_on_retryable_error(max_retries=3, base_delay=1): else: @wraps(func) def sync_wrapper(*args, **kwargs): + """执行与 sync wrapper 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ import time for attempt in range(max_retries): try: diff --git a/pretor/worker_individual/__init__.py b/pretor/worker_individual/__init__.py index 565a31a..f9afbd8 100644 --- a/pretor/worker_individual/__init__.py +++ b/pretor/worker_individual/__init__.py @@ -1,3 +1,17 @@ +# Copyright 2026 zhaoxi826 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + from pretor.worker_individual.base_individual import BaseIndividual from pretor.worker_individual.skill_individual import SkillIndividual from pretor.worker_individual.ordinary_individual import OrdinaryIndividual diff --git a/pretor/worker_individual/base_individual.py b/pretor/worker_individual/base_individual.py index 16f4338..6283c9e 100644 --- a/pretor/worker_individual/base_individual.py +++ b/pretor/worker_individual/base_individual.py @@ -23,12 +23,18 @@ from pretor.utils.logger import get_logger logger = get_logger('worker_individual') class WorkerIndividualResponse(ResponseModel): + """WorkerIndividualResponse 核心组件类。 + 这是一个具体的 Worker 智能体实体类,代表着具备特定人设、领域技能或长文本处理能力的数字员工。它可以被控制器动态拉起,并在安全沙箱内执行复杂的工作流指令与多步骤推理任务。 """ output: str = Field(..., description="Worker执行任务的输出结果") class WorkerIndividualDeps(DepsModel): + """WorkerIndividualDeps 核心组件类。 + 这是一个具体的 Worker 智能体实体类,代表着具备特定人设、领域技能或长文本处理能力的数字员工。它可以被控制器动态拉起,并在安全沙箱内执行复杂的工作流指令与多步骤推理任务。 """ task_event: dict class WorkerIndividualInput(InputModel): + """WorkerIndividualInput 核心组件类。 + 这是一个具体的 Worker 智能体实体类,代表着具备特定人设、领域技能或长文本处理能力的数字员工。它可以被控制器动态拉起,并在安全沙箱内执行复杂的工作流指令与多步骤推理任务。 """ task_event: dict class BaseIndividual: @@ -42,6 +48,10 @@ class BaseIndividual: self.agent: Agent | None = None async def _init_agent(self, agent_name: str, system_prompt: str): + """完成 agent 模块的启动与依赖初始化。 + 在系统引导或服务拉起阶段被调用,负责建立网络连接、分配基础内存资源及注册核心服务组件。 + Args: agent_name (str): 赋予该实体的人类可读名称或标题字符串,主要用于前端 UI 展示、日志记录或模糊检索。 system_prompt (str): 控制逻辑流向的具体字符串参数,指定了期望的 system prompt 内容。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ from pretor.utils.get_tool import load_tools_from_list global_state_machine = ray_actor_hook("global_state_machine").global_state_machine provider_title = self.agent_config.get("provider_title", "openai") # default fallback @@ -65,6 +75,10 @@ class BaseIndividual: @self.agent.system_prompt async def dynamic_prompt(ctx: RunContext[WorkerIndividualDeps]): + """执行与 dynamic prompt 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: ctx (RunContext[WorkerIndividualDeps]): 参与 dynamic prompt 逻辑运算或数据构建的上下文依赖对象。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ prompt = system_prompt + "\n\n" prompt += ( f"=== 当前任务上下文 ===\n" @@ -73,4 +87,8 @@ class BaseIndividual: return prompt async def run(self, task_event: dict) -> dict: + """执行与 run 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: task_event (dict): 由事件总线或工作流引擎分发过来的事件载荷,封装了触发此次调用的上下文快照与任务目标指令。 + Returns: (dict): 高度聚合的字典结构数据,将多维度的属性特征或统计指标组合后一并返回。 """ raise NotImplementedError("子类必须实现 run 方法") diff --git a/pretor/worker_individual/ordinary_individual.py b/pretor/worker_individual/ordinary_individual.py index 2386ac9..3055fba 100644 --- a/pretor/worker_individual/ordinary_individual.py +++ b/pretor/worker_individual/ordinary_individual.py @@ -26,6 +26,10 @@ class OrdinaryIndividual(BaseIndividual): super().__init__(agent_config) async def run(self, task_event: dict) -> dict: + """执行与 run 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: task_event (dict): 由事件总线或工作流引擎分发过来的事件载荷,封装了触发此次调用的上下文快照与任务目标指令。 + Returns: (dict): 高度聚合的字典结构数据,将多维度的属性特征或统计指标组合后一并返回。 """ if self.agent is None: system_prompt = self.agent_config.get("prompt", "你是一个普通的AI助手,请尽力完成给定的任务。") await self._init_agent("ordinary_individual", system_prompt) diff --git a/pretor/worker_individual/skill_individual.py b/pretor/worker_individual/skill_individual.py index cde9776..b5751b9 100644 --- a/pretor/worker_individual/skill_individual.py +++ b/pretor/worker_individual/skill_individual.py @@ -88,6 +88,10 @@ class SkillIndividual(BaseIndividual): return tools async def run(self, task_event: dict) -> dict: + """执行与 run 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: task_event (dict): 由事件总线或工作流引擎分发过来的事件载荷,封装了触发此次调用的上下文快照与任务目标指令。 + Returns: (dict): 高度聚合的字典结构数据,将多维度的属性特征或统计指标组合后一并返回。 """ if self.agent is None: system_prompt = self.agent_config.get("prompt", "你是一个拥有专业技能的专家级AI助手,请利用你的专业知识完成给定的任务。") diff --git a/pretor/worker_individual/special_individual.py b/pretor/worker_individual/special_individual.py index 27ee49d..b92bdc7 100644 --- a/pretor/worker_individual/special_individual.py +++ b/pretor/worker_individual/special_individual.py @@ -26,6 +26,10 @@ class SpecialIndividual(BaseIndividual): super().__init__(agent_config) async def run(self, task_event: dict) -> dict: + """执行与 run 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: task_event (dict): 由事件总线或工作流引擎分发过来的事件载荷,封装了触发此次调用的上下文快照与任务目标指令。 + Returns: (dict): 高度聚合的字典结构数据,将多维度的属性特征或统计指标组合后一并返回。 """ if self.agent is None: system_prompt = self.agent_config.get("prompt", "你是一个特殊的AI助手,负责处理特殊类型的任务。") await self._init_agent("special_individual", system_prompt) diff --git a/pretor/worker_individual/worker_cluster.py b/pretor/worker_individual/worker_cluster.py index 813dbd8..fc4af71 100644 --- a/pretor/worker_individual/worker_cluster.py +++ b/pretor/worker_individual/worker_cluster.py @@ -45,6 +45,8 @@ class WorkerCluster: self.logger = get_logger('worker_cluster') async def start(self): + """执行与 start 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 """ if self.task_queue is None: self.task_queue = Queue() self.runners = [asyncio.create_task(self._runner(i)) for i in range(self.num_runners)] @@ -78,6 +80,9 @@ class WorkerCluster: return worker async def _runner(self, runner_id: int): + """执行与 runner 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: runner_id (int): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 runner 实例。 """ while True: try: if self.task_queue is None: @@ -119,6 +124,10 @@ class WorkerCluster: await asyncio.sleep(1) async def submit_task(self, task_id: str, agent_id: str, task_event: dict): + """执行与 submit task 相关的核心业务流转操作。 + 该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。 + Args: task_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 task 实例。 agent_id (str): 目标对象的唯一全局标识符 (UUID/ULID),用于在数据库表或缓存结构中精准匹配该 agent 实例。 task_event (dict): 由事件总线或工作流引擎分发过来的事件载荷,封装了触发此次调用的上下文快照与任务目标指令。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ if not self.runners: await self.start() @@ -140,6 +149,9 @@ class WorkerCluster: self.results_futures.pop(task_id, None) def get_cluster_metrics(self): + """检索并获取特定的 cluster metrics 数据集合或实例对象。 + 根据提供的查询条件或上下文凭证,从数据库、缓存或第三方服务中读取对应的资源状态。 + Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。 """ return { "active_worker_count": len(self._active_workers), "max_capacity": self.max_capacity, diff --git a/tests/core/database/module/memory_test.py b/tests/core/database/module/memory_test.py deleted file mode 100644 index 37cfdcb..0000000 --- a/tests/core/database/module/memory_test.py +++ /dev/null @@ -1,145 +0,0 @@ -import pytest -from unittest.mock import MagicMock, AsyncMock, patch -import json -import sys -import builtins - -real_import = builtins.__import__ - - -def mock_import(name, globals=None, locals=None, fromlist=(), level=0): - if name == 'sqlmodel': - mock_sqlmodel = MagicMock() - - class DummySQLModel: - def __init_subclass__(cls, **kwargs): - pass - - mock_sqlmodel.SQLModel = DummySQLModel - mock_sqlmodel.Field = MagicMock(return_value=None) - mock_sqlmodel.select = MagicMock() - return mock_sqlmodel - return real_import(name, globals, locals, fromlist, level) - - -builtins.__import__ = mock_import -for mod in list(sys.modules.keys()): - if 'pretor.core.database.module.memory' in mod or 'sqlmodel' in mod: - del sys.modules[mod] -from pretor.core.database.module.memory import MemoryRAG - -builtins.__import__ = real_import - - -@pytest.fixture(autouse=True) -def mock_dependencies(): - with patch("pretor.core.database.module.memory.WorkflowRecord") as mock_workflow_record: - with patch("pretor.core.database.module.memory.MemoryRecord") as mock_memory_record: - with patch("pretor.core.database.module.memory.select") as mock_select: - yield mock_workflow_record, mock_memory_record, mock_select - - -@pytest.fixture -def mock_session_maker(): - maker = MagicMock() - session = AsyncMock() - session.add = MagicMock() - maker.return_value.__aenter__.return_value = session - maker.__aenter__.return_value = session - maker.__aexit__ = AsyncMock() - return maker, session - - -@pytest.mark.asyncio -async def test_save_workflow(mock_session_maker, mock_dependencies): - mock_workflow_record, _, _ = mock_dependencies - maker, session = mock_session_maker - rag = MemoryRAG(maker) - - mock_record = MagicMock() - mock_workflow_record.return_value = mock_record - - workflow_data = {"key": "value"} - record = await rag.save_workflow("wf_123", workflow_data) - - mock_workflow_record.assert_called_once_with( - workflow_id="wf_123", - workflow_data_json=json.dumps(workflow_data) - ) - session.add.assert_called_once_with(mock_record) - session.commit.assert_called_once() - session.refresh.assert_called_once_with(mock_record) - assert record == mock_record - - -@pytest.mark.asyncio -async def test_get_workflow_success(mock_session_maker, mock_dependencies): - _, _, mock_select = mock_dependencies - maker, session = mock_session_maker - rag = MemoryRAG(maker) - - mock_statement = MagicMock() - mock_select.return_value.where.return_value = mock_statement - - mock_record = MagicMock() - mock_record.workflow_data_json = '{"key": "value"}' - - mock_exec_result = MagicMock() - mock_exec_result.scalar_one_or_none.return_value = mock_record - session.execute = AsyncMock(return_value=mock_exec_result) - - data = await rag.get_workflow("wf_123") - - session.execute.assert_called_once_with(mock_statement) - assert data == {"key": "value"} - - -@pytest.mark.asyncio -async def test_get_workflow_not_found(mock_session_maker, mock_dependencies): - _, _, mock_select = mock_dependencies - maker, session = mock_session_maker - rag = MemoryRAG(maker) - - mock_exec_result = MagicMock() - mock_exec_result.scalar_one_or_none.return_value = None - session.execute = AsyncMock(return_value=mock_exec_result) - - data = await rag.get_workflow("wf_123") - assert data is None - - -@pytest.mark.asyncio -async def test_add_memory(mock_session_maker, mock_dependencies): - _, mock_memory_record, _ = mock_dependencies - maker, session = mock_session_maker - rag = MemoryRAG(maker) - - mock_record = MagicMock() - mock_memory_record.return_value = mock_record - - record = await rag.add_memory("text", [0.1, 0.2]) - - mock_memory_record.assert_called_once_with(memory_text="text", embedding=[0.1, 0.2]) - session.add.assert_called_once_with(mock_record) - session.commit.assert_called_once() - session.refresh.assert_called_once_with(mock_record) - assert record == mock_record - - -@pytest.mark.asyncio -async def test_retrieve_memory(mock_session_maker, mock_dependencies): - _, _, mock_select = mock_dependencies - maker, session = mock_session_maker - rag = MemoryRAG(maker) - - mock_statement = MagicMock() - mock_select.return_value.limit.return_value = mock_statement - - mock_exec_result = MagicMock() - mock_exec_result.all.return_value = ["res1", "res2"] - session.execute = AsyncMock(return_value=mock_exec_result) - - results = await rag.retrieve_memory([0.1, 0.2], 5) - - session.execute.assert_called_once_with(mock_statement) - assert results == ["res1", "res2"] diff --git a/tests/core/global_state_machine/global_state_machine_test.py b/tests/core/global_state_machine/global_state_machine_test.py index f894199..a06ebce 100644 --- a/tests/core/global_state_machine/global_state_machine_test.py +++ b/tests/core/global_state_machine/global_state_machine_test.py @@ -1,8 +1,6 @@ import pytest -import asyncio from unittest.mock import MagicMock, AsyncMock, patch import sys - import builtins real_import = builtins.__import__ @@ -31,8 +29,6 @@ for mod in list(sys.modules.keys()): del sys.modules[mod] from pretor.core.global_state_machine.global_state_machine import GlobalStateMachine -from pretor.api.platform.event import PretorEvent -from pretor.core.workflow.workflow import PretorWorkflow builtins.__import__ = real_import @@ -48,53 +44,6 @@ def gsm(mock_postgres): return manager -def test_add_delete_get_event(gsm): - event = MagicMock(spec=PretorEvent) - event.trace_id = "123" - - gsm.add_event(event) - - assert getattr(event, 'pending_queue', None) is not None - assert getattr(event, 'receive_queue', None) is not None - - retrieved = gsm.get_event("123") - assert retrieved == event - - gsm.delete_event("123") - assert gsm.get_event("123") is None - - -def test_update_attachment_and_workflow(gsm): - event = MagicMock(spec=PretorEvent) - event.trace_id = "abc" - gsm.add_event(event) - - gsm.update_attachment("abc", {"k": "v"}) - assert event.attachment == {"k": "v"} - - wf = MagicMock(spec=PretorWorkflow) - gsm.update_workflow("abc", wf) - assert event.workflow == wf - - -@pytest.mark.asyncio -async def test_queues(gsm): - event = MagicMock(spec=PretorEvent) - event.trace_id = "q_event" - # To use await put/get, we must actually use real asyncio queues for the mock event - event.pending_queue = asyncio.Queue() - event.receive_queue = asyncio.Queue() - gsm.event_dict["q_event"] = event - - await gsm.put_pending("q_event", "item1") - res1 = await gsm.get_pending("q_event") - assert res1 == "item1" - - await gsm.put_received("q_event", "item2") - res2 = await gsm.get_received("q_event") - assert res2 == "item2" - - @pytest.mark.asyncio async def test_add_provider_success(gsm, mock_postgres): mock_provider_class = AsyncMock() diff --git a/tests/core/workflow/workflow_runner_test.py b/tests/core/workflow/workflow_runner_test.py index d0d99d3..927344c 100644 --- a/tests/core/workflow/workflow_runner_test.py +++ b/tests/core/workflow/workflow_runner_test.py @@ -147,16 +147,21 @@ async def test_workflow_running_engine_runner(): # Mock the global_state_machine get_skill_list.remote method properly mock_gsm = MagicMock() - mock_gsm.get_skill_list.remote = AsyncMock(return_value={"test_skill": ("description", "instructions")}) + mock_gsm.list_individuals.remote = AsyncMock(return_value={"test_skill": {"agent_type": "skill_individual", "agent_name": "TestSkill", "description": "desc"}}) engine.global_state_machine = mock_gsm - with patch("pretor.core.workflow.workflow_runner.WorkflowEngine") as mock_wf_engine_cls, patch("builtins.open", new_callable=MagicMock) as mock_open: + with patch("pretor.core.workflow.workflow_runner.WorkflowEngine") as mock_wf_engine_cls, patch("builtins.open", new_callable=MagicMock) as mock_open, \ + patch("pretor.core.workflow.workflow_runner.ray_actor_hook") as mock_hook: # Instead of patching hook, we inject it directly - engine.global_state_machine = AsyncMock() + # engine.global_state_machine = AsyncMock() mock_open.return_value.__enter__.return_value.read.return_value = '{}' + mock_gwm = MagicMock() + mock_gwm.update_workflow.remote = AsyncMock() + mock_hook.return_value.global_workflow_manager = mock_gwm + mock_engine_instance = MagicMock() mock_engine_instance.run = AsyncMock() mock_wf_engine_cls.return_value = mock_engine_instance