From 3cf2411c4abd283ee762df2ba60988913447e051 Mon Sep 17 00:00:00 2001 From: zhaoxi Date: Fri, 17 Apr 2026 22:07:49 +0800 Subject: [PATCH] =?UTF-8?q?wip:=20=E4=BF=AE=E6=94=B9=E4=BA=86=E5=A4=A7?= =?UTF-8?q?=E9=87=8F=E6=BC=8F=E6=B4=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/problem.md | 26 +++++++++++++-- pretor/api/agent.py | 11 +++---- pretor/api/auth.py | 15 +++++---- pretor/api/platform/frontend.py | 9 +++--- pretor/api/provider.py | 10 +++--- pretor/api/resource.py | 3 ++ pretor/core/api/__init__.py | 6 ---- pretor/core/database/module/user.py | 2 +- pretor/core/database/postgres.py | 20 ++++++++++-- .../global_state_machine.py | 2 +- pretor/core/workflow/workflow_runner.py | 4 ++- pretor/utils/access.py | 23 +++++++------ pyproject.toml | 2 +- uv.lock | 32 +++++++++---------- 14 files changed, 98 insertions(+), 67 deletions(-) diff --git a/docs/problem.md b/docs/problem.md index 346518e..5b58296 100644 --- a/docs/problem.md +++ b/docs/problem.md @@ -1,9 +1,31 @@ 待解决问题 --- +## 问题栏 +#### 🔴 核心缺陷与修复 (Bug Fixes & Stability) +- [ ] /pretor/core/individual每个template进行优化 +- [ ] /pretor/worker_individual待完善复合子个体和基础子个体 +#### 🛡️ 安全与合规 (Security & Auth) + + +#### ⚡ 性能与资源优化 (Performance & Scalability) +- [ ] 增加对应全workflow的情况追踪,使得在任务运行中人机交互更加自然方便 + +#### 🏗️ 架构演进 (Architecture & Refactoring) +- [ ] 使用fastapi-users完善用户系统 +- [x] /pretor/api的接口函数进行重构 +- [ ] /dockerfile待完善 + +--- +## 日志 #### 2026/4/12 -- [ ] /pretor/tool_plugin/approval/approval.py的approval函数event改为依赖注入 +- [x] /pretor/api的接口函数进行重构 - [ ] /pretor/core/individual每个template进行优化 - [ ] /pretor/worker_individual待完善复合子个体和基础子个体 - [ ] /pretor/api待完善 -- [ ] /dockerfile待完善 \ No newline at end of file +- [ ] /dockerfile待完善 + +#### 2026/4/16 +- [ ] 发布v0.1.0正式版 +- [ ] 增加对应全workflow的情况追踪,使得在任务运行中人机交互更加自然方便 +- [ ] 使用fastapi-users完善用户系统 \ No newline at end of file diff --git a/pretor/api/agent.py b/pretor/api/agent.py index d2504ee..45e38ef 100644 --- a/pretor/api/agent.py +++ b/pretor/api/agent.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. from typing import Union - +from pretor.utils.ray_hook import ray_actor_hook from fastapi import APIRouter, Request, Depends from pydantic import BaseModel from pretor.utils.access import Accessor, TokenData @@ -30,22 +30,21 @@ class AgentLocalRegister(BaseModel): @agent_router.post("") async def load_agent(agent_register: Union[AgentRegister, AgentLocalRegister], - request: Request, _: TokenData = Depends(Accessor.get_current_user)): - global_state_machine = request.app.state.global_state_machine + global_state_machine = ray_actor_hook("global_state_machine") if isinstance(agent_register, AgentLocalRegister): pass elif isinstance(agent_register, AgentRegister): match agent_register.individual_title: case "supervisory_node": - node = request.app.state.supervisory_node + node = ray_actor_hook("supervisory_node") node.create_agent.remote(global_state_machine,agent_register.provider_title,agent_register.model_id) case "consciousness_node": - node = request.app.state.consciousness_node + node = ray_actor_hook("consciousness_node") node.create_agent.remote(global_state_machine,agent_register.provider_title,agent_register.model_id) case "control_node": - node = request.app.state.control_node + node = ray_actor_hook("control_node") node.create_agent.remote(global_state_machine,agent_register.provider_title,agent_register.model_id) case _: pass diff --git a/pretor/api/auth.py b/pretor/api/auth.py index 1e4560f..773c25b 100644 --- a/pretor/api/auth.py +++ b/pretor/api/auth.py @@ -12,10 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from fastapi import APIRouter, Request +from fastapi import APIRouter from pydantic import BaseModel from pretor.utils.access import Accessor from fastapi.concurrency import run_in_threadpool +from pretor.utils.ray_hook import ray_actor_hook auth_router = APIRouter(prefix="/api/v1/auth", tags=["auth"]) @@ -24,10 +25,10 @@ class UserRegister(BaseModel): password: str @auth_router.post("/register") -async def create_user(user_register: UserRegister, request: Request): - postgres_database = request.app.state.postgres_database +async def create_user(user_register: UserRegister): + postgres_database = ray_actor_hook("postgres_database") hashed_password = await run_in_threadpool(Accessor.hash_password, user_register.password) - user = await postgres_database.auth_database.add_user.remote(user_register.user_name, hashed_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): @@ -35,9 +36,9 @@ class UserLogin(BaseModel): password: str @auth_router.post("/login") -async def login_user(user_login: UserLogin, request: Request): - postgres_database = request.app.state.postgres_database - user = await postgres_database.auth_database.login_user.remote(user_login.user_name) +async def login_user(user_login: UserLogin): + postgres_database = ray_actor_hook("postgres_database") + user = await postgres_database.login_user.remote(user_login.user_name) if user.user_name != user_login.user_name: pass token = await run_in_threadpool(Accessor.login_hashed_password, user, user_login.password) diff --git a/pretor/api/platform/frontend.py b/pretor/api/platform/frontend.py index 8d222ee..a8bd1c1 100644 --- a/pretor/api/platform/frontend.py +++ b/pretor/api/platform/frontend.py @@ -14,10 +14,10 @@ from fastapi import APIRouter, Request, Depends, HTTPException, status, WebSocket, WebSocketDisconnect from pydantic import BaseModel - from pretor.utils.access import Accessor, TokenData from pretor.api.platform.event import PretorEvent from loguru import logger +from pretor.utils.ray_hook import ray_actor_hook client_router = APIRouter(prefix="/api/v1/adapter/client", tags=["client"]) @@ -26,7 +26,6 @@ class Message(BaseModel): @client_router.post("") async def create_message(message: Message, - request: Request, token_data: TokenData = Depends(Accessor.get_current_user)): logger.info("收到消息,来源:客户端") logger.debug(f"消息内容:{message.message}") @@ -34,11 +33,11 @@ async def create_message(message: Message, user_id=str(token_data.user_id), user_name=token_data.user_name, message=message.message) - supervisory_node = request.app.state.supervisory_node + supervisory_node = ray_actor_hook("supervisor_node") message = await supervisory_node.working.remote(event) if message == "任务已创建": - global_state_machine = request.app.state.global_state_machine - global_state_machine.add.remote(event) + global_state_machine = ray_actor_hook("global_state_machine") + global_state_machine.add_event.remote(event) return {"message": event.event_id} elif message == "未知相应类型": raise HTTPException( diff --git a/pretor/api/provider.py b/pretor/api/provider.py index 6df8452..11e28c5 100644 --- a/pretor/api/provider.py +++ b/pretor/api/provider.py @@ -18,7 +18,7 @@ from typing import Literal from pretor.utils.access import TokenData, Accessor from typing import Dict from pretor.core.global_state_machine.model_provider.base_provider import Provider - +from pretor.utils.ray_hook import ray_actor_hook provider_router = APIRouter(prefix="/api/v1/provider", tags=["provider"]) @@ -30,9 +30,8 @@ class ProviderRegister(BaseModel): @provider_router.post("") async def create_provider(provider_register: ProviderRegister, - request: Request, token_data: TokenData = Depends(Accessor.get_current_user)) -> None: - global_state_machine = request.app.state.global_state_machine + global_state_machine = ray_actor_hook("global_state_machine") await global_state_machine.add_provider.remote(provider_type=provider_register.provider_type, provider_title=provider_register.provider_title, provider_url=provider_register.provider_url, @@ -41,8 +40,7 @@ async def create_provider(provider_register: ProviderRegister, @provider_router.get("/list") -async def get_provider_list(request: Request, - _: TokenData = Depends(Accessor.get_current_user)) -> Dict[str, Provider]: - global_state_machine = request.app.state.global_state_machine +async def get_provider_list(_: TokenData = Depends(Accessor.get_current_user)) -> Dict[str, Provider]: + global_state_machine = ray_actor_hook("global_state_machine") provider_list: Dict[str, Provider] = await global_state_machine.get_provider_list.remote() return {"provider_list": provider_list} \ No newline at end of file diff --git a/pretor/api/resource.py b/pretor/api/resource.py index 3c9ad6d..0754cdf 100644 --- a/pretor/api/resource.py +++ b/pretor/api/resource.py @@ -28,6 +28,8 @@ async def create_workflow_template(workflow_template: WorkflowTemplate, await global_state_machine.workflow_template_generate.remote(workflow_template) return {"message": "创建成功"} + + class Skill(BaseModel): repo_url: str path: str | None @@ -36,6 +38,7 @@ class Skill(BaseModel): async def install_skill(skill: Skill, _: TokenData = Depends(Accessor.get_current_user)): global_state_machine = ray_actor_hook("global_state_machine") + # noinspection PyUnresolvedReferences await viceroy.install_skill_async(url = skill.repo_url, path = skill.path, output = "./pretor/plugin/tool_plugin") diff --git a/pretor/core/api/__init__.py b/pretor/core/api/__init__.py index cd4acfe..7989836 100644 --- a/pretor/core/api/__init__.py +++ b/pretor/core/api/__init__.py @@ -38,12 +38,6 @@ class PretorGateway: self.app = FastAPI() self.gateway = {} - self.app.state.postgres_database = postgres_database - self.app.state.global_state_machine = global_state_machine - self.app.state.supervisory_node = supervisory_node - self.app.state.consciousness_node = consciousness_node - self.app.state.control_node = control_node - self.app.include_router(client_router) self.app.include_router(auth_router) self.app.include_router(provider_router) diff --git a/pretor/core/database/module/user.py b/pretor/core/database/module/user.py index dcbfe05..fa5d6d7 100644 --- a/pretor/core/database/module/user.py +++ b/pretor/core/database/module/user.py @@ -24,7 +24,7 @@ class AuthDatabase: @database_exception async def add_user(self, user_name: str, hashed_password: str) -> User: user = User(user_name=user_name, hashed_password=hashed_password) - async with self.async_session_maker as session: + async with self.async_session_maker() as session: session.add(user) await session.commit() await session.refresh(user) diff --git a/pretor/core/database/postgres.py b/pretor/core/database/postgres.py index 199899c..5cd88a2 100644 --- a/pretor/core/database/postgres.py +++ b/pretor/core/database/postgres.py @@ -37,12 +37,26 @@ class PostgresDatabase: self.auth_database = AuthDatabase(self.async_session_maker) self.provider_database = ProviderDatabase(self.async_session_maker) + async def init_db(self) -> None: + async with self.async_engine.begin() as conn: + await conn.run_sync(SQLModel.metadata.create_all) + + # provider_database操作 async def get_providers(self): return await self.provider_database.get_provider() async def add_provider(self, **kwargs): return await self.provider_database.add_provider(**kwargs) - async def init_db(self) -> None: - async with self.async_engine.begin() as conn: - await conn.run_sync(SQLModel.metadata.create_all) \ No newline at end of file + # auth_database操作 + async def add_user(self, **kwargs): + return await self.auth_database.add_user(**kwargs) + + async def change_password(self, **kwargs): + return await self.auth_database.change_password(**kwargs) + + async def delete_user(self, **kwargs): + return await self.auth_database.delete_user(**kwargs) + + async def login_user(self, **kwargs): + return await self.auth_database.login_user(**kwargs) \ No newline at end of file diff --git a/pretor/core/global_state_machine/global_state_machine.py b/pretor/core/global_state_machine/global_state_machine.py index 4af7169..9b66803 100644 --- a/pretor/core/global_state_machine/global_state_machine.py +++ b/pretor/core/global_state_machine/global_state_machine.py @@ -158,7 +158,7 @@ class GlobalStateMachine: ###以下为workflow_template_manager方法 - def workflow_template_generator(self, workflow_template: WorkflowTemplate) -> None: + def workflow_template_generate(self, workflow_template: WorkflowTemplate) -> None: self.global_workflow_template_manager.generate_workflow_template(workflow_template) ###以下为skill_manager方法 diff --git a/pretor/core/workflow/workflow_runner.py b/pretor/core/workflow/workflow_runner.py index f0bc8d0..96edb9d 100644 --- a/pretor/core/workflow/workflow_runner.py +++ b/pretor/core/workflow/workflow_runner.py @@ -35,7 +35,6 @@ class WorkflowEngine: """工作流:当前WorkflowEngine待执行的workflow""" self._steps_by_id: Dict[int, WorkStep] = {step.step: step for step in self.workflow.work_link} """步骤表:将当前workflow的步骤序号和步骤内容存放""" - self.consciousness_node = consciousness_node """意识节点""" self.control_node = control_node @@ -238,6 +237,9 @@ class WorkflowRunningEngine: } self.workflow_queue = asyncio.Queue() + async def put_workflow(self, workflow: PretorWorkflow) -> None: + await self.workflow_queue.put(workflow) + async def runner(self, i: int) -> None: """ runner方法,从self.workflow_queue中不断取出任务并执行 diff --git a/pretor/utils/access.py b/pretor/utils/access.py index f31d432..28e3690 100644 --- a/pretor/utils/access.py +++ b/pretor/utils/access.py @@ -19,18 +19,20 @@ from typing import Optional from fastapi import HTTPException, status, Request from pydantic import BaseModel, ValidationError from pretor.core.database.table.user import User -from passlib.context import CryptContext +from pwdlib import PasswordHash +from pwdlib.hashers.bcrypt import BcryptHasher + class TokenData(BaseModel): user_id: str username: Optional[str] = None exp: Optional[int] = None - SECRET_KEY = os.getenv("SECRET_KEY") ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 # 默认有效期 1 天 -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 + +password_hasher = PasswordHash.recommended() class Accessor: @@ -54,19 +56,16 @@ class Accessor: detail="无效的认证凭证", ) - - @staticmethod def _create_access_token(data: dict) -> str: to_encode = data.copy() expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) to_encode.update({"exp": int(expire.timestamp())}) - encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) - return encoded_jwt + return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) @staticmethod def _verify_password(plain_password: str, hashed_password: str) -> bool: - return pwd_context.verify(plain_password, hashed_password) + return password_hasher.verify(plain_password, hashed_password) @staticmethod def get_current_user(request: Request) -> TokenData: @@ -92,8 +91,8 @@ class Accessor: detail="用户名或密码错误", ) token_payload = { - "user_id": str(user.id), # 确保是字符串格式 - "username": user.username + "user_id": str(user.user_id), + "username": user.user_name } return Accessor._create_access_token(data=token_payload) @@ -103,4 +102,4 @@ class Accessor: raise ValueError("密码不能为空") if len(password) < 6: raise ValueError("密码长度不能小于 6 位") - return pwd_context.hash(password) + return password_hasher.hash(password) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index fd1b24e..a089c09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,8 +11,8 @@ dependencies = [ "httpx>=0.28.1", "jinja2>=3.1.6", "loguru>=0.7.3", - "passlib[bcrypt]>=1.7.4", "pretor-viceroy>=0.2.0", + "pwdlib[bcrypt]>=0.3.0", "pydantic-ai>=1.73.0", "pyfiglet>=1.0.4", "python-ulid>=3.1.0", diff --git a/uv.lock b/uv.lock index 8dfb64c..70480a4 100644 --- a/uv.lock +++ b/uv.lock @@ -2915,20 +2915,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/32/658973117bf0fd82a24abbfb94fe73a5e86216e49342985e10acce54775a/partial_json_parser-0.2.1.1.post7-py3-none-any.whl", hash = "sha256:145119e5eabcf80cbb13844a6b50a85c68bf99d376f8ed771e2a3c3b03e653ae", size = 10877, upload-time = "2025-11-17T07:27:40.457Z" }, ] -[[package]] -name = "passlib" -version = "1.7.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/06/9da9ee59a67fae7761aab3ccc84fa4f3f33f125b370f1ccdb915bf967c11/passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04", size = 689844, upload-time = "2020-10-08T19:00:52.121Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/a4/ab6b7589382ca3df236e03faa71deac88cae040af60c071a78d254a62172/passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", size = 525554, upload-time = "2020-10-08T19:00:49.856Z" }, -] - -[package.optional-dependencies] -bcrypt = [ - { name = "bcrypt" }, -] - [[package]] name = "pathable" version = "0.5.0" @@ -3025,8 +3011,8 @@ dependencies = [ { name = "httpx" }, { name = "jinja2" }, { name = "loguru" }, - { name = "passlib", extra = ["bcrypt"] }, { name = "pretor-viceroy" }, + { name = "pwdlib", extra = ["bcrypt"] }, { name = "pydantic-ai", version = "1.75.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, { name = "pydantic-ai", version = "1.84.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, { name = "pyfiglet" }, @@ -3055,8 +3041,8 @@ requires-dist = [ { name = "httpx", specifier = ">=0.28.1" }, { name = "jinja2", specifier = ">=3.1.6" }, { name = "loguru", specifier = ">=0.7.3" }, - { name = "passlib", extras = ["bcrypt"], specifier = ">=1.7.4" }, { name = "pretor-viceroy", specifier = ">=0.2.0" }, + { name = "pwdlib", extras = ["bcrypt"], specifier = ">=0.3.0" }, { name = "pydantic-ai", specifier = ">=1.73.0" }, { name = "pyfiglet", specifier = ">=1.0.4" }, { name = "python-ulid", specifier = ">=3.1.0" }, @@ -3245,6 +3231,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] +[[package]] +name = "pwdlib" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/41/a7c0d8a003c36ce3828ae3ed0391fe6a15aad65f082dbd6bec817ea95c0b/pwdlib-0.3.0.tar.gz", hash = "sha256:6ca30f9642a1467d4f5d0a4d18619de1c77f17dfccb42dd200b144127d3c83fc", size = 215810, upload-time = "2025-10-25T12:44:24.395Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/0c/9086a357d02a050fbb3270bf5043ac284dbfb845670e16c9389a41defc9e/pwdlib-0.3.0-py3-none-any.whl", hash = "sha256:f86c15c138858c09f3bba0a10984d4f9178158c55deaa72eac0210849b1a140d", size = 8633, upload-time = "2025-10-25T12:44:23.406Z" }, +] + +[package.optional-dependencies] +bcrypt = [ + { name = "bcrypt" }, +] + [[package]] name = "py-cpuinfo" version = "9.0.0"