diff --git a/docs/problem.md b/docs/problem.md new file mode 100644 index 0000000..346518e --- /dev/null +++ b/docs/problem.md @@ -0,0 +1,9 @@ +待解决问题 +--- + +#### 2026/4/12 +- [ ] /pretor/tool_plugin/approval/approval.py的approval函数event改为依赖注入 +- [ ] /pretor/core/individual每个template进行优化 +- [ ] /pretor/worker_individual待完善复合子个体和基础子个体 +- [ ] /pretor/api待完善 +- [ ] /dockerfile待完善 \ No newline at end of file diff --git a/pretor/api/agent.py b/pretor/api/agent.py new file mode 100644 index 0000000..d2504ee --- /dev/null +++ b/pretor/api/agent.py @@ -0,0 +1,52 @@ +# 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 typing import Union + +from fastapi import APIRouter, Request, Depends +from pydantic import BaseModel +from pretor.utils.access import Accessor, TokenData + +agent_router = APIRouter(prefix="/api/v1/agent", tags=["agent"]) + +class AgentRegister(BaseModel): + provider_title: str + model_id: str + individual_name: str + +class AgentLocalRegister(BaseModel): + path: str + individual_name: str + +@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 + 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.create_agent.remote(global_state_machine,agent_register.provider_title,agent_register.model_id) + case "consciousness_node": + node = request.app.state.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.create_agent.remote(global_state_machine,agent_register.provider_title,agent_register.model_id) + case _: + pass + return {"message": "创建成功"} \ No newline at end of file diff --git a/pretor/api/platform/frontend.py b/pretor/api/platform/frontend.py index d920418..8d222ee 100644 --- a/pretor/api/platform/frontend.py +++ b/pretor/api/platform/frontend.py @@ -27,12 +27,12 @@ class Message(BaseModel): @client_router.post("") async def create_message(message: Message, request: Request, - token_date: TokenData = Depends(Accessor.get_current_user)): + token_data: TokenData = Depends(Accessor.get_current_user)): logger.info("收到消息,来源:客户端") logger.debug(f"消息内容:{message.message}") event = PretorEvent(platform="client", - user_id=str(token_date.user_id), - user_name=token_date.user_name, + user_id=str(token_data.user_id), + user_name=token_data.user_name, message=message.message) supervisory_node = request.app.state.supervisory_node message = await supervisory_node.working.remote(event) diff --git a/pretor/core/api/__init__.py b/pretor/core/api/__init__.py index 9c93638..f307d33 100644 --- a/pretor/core/api/__init__.py +++ b/pretor/core/api/__init__.py @@ -19,7 +19,8 @@ from fastapi import FastAPI,WebSocket from pretor.core.database.postgres import PostgresDatabase from pretor.core.global_state_machine.global_state_machine import GlobalStateMachine 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 from pretor.api.platform.frontend import client_router from pretor.api.auth import auth_router from pretor.api.provider import provider_router @@ -30,14 +31,17 @@ class PretorGateway: def __init__(self, postgres_database: PostgresDatabase, global_state_machine: GlobalStateMachine, - supervisory_node: SupervisoryNode,): + supervisory_node: SupervisoryNode, + consciousness_node: ConsciousnessNode, + control_node: ControlNode,): 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 = supervisory_node 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) diff --git a/pretor/core/global_state_machine/global_state_machine.py b/pretor/core/global_state_machine/global_state_machine.py index 3c292e4..9d1b4ac 100644 --- a/pretor/core/global_state_machine/global_state_machine.py +++ b/pretor/core/global_state_machine/global_state_machine.py @@ -57,7 +57,7 @@ class GlobalStateMachine: async def put_received(self, event_id, item) -> None: await self.event_dict[event_id].receive_queue.put(item) - async def get_receive_queue(self, event_id) -> str: + async def get_received(self, event_id) -> str: return await self.event_dict[event_id].receive_queue.get() diff --git a/pretor/tool_plugin/docker_sandbox/__init__.py b/pretor/core/global_state_machine/tool_manager.py similarity index 73% rename from pretor/tool_plugin/docker_sandbox/__init__.py rename to pretor/core/global_state_machine/tool_manager.py index 5fa7362..dd52fa1 100644 --- a/pretor/tool_plugin/docker_sandbox/__init__.py +++ b/pretor/core/global_state_machine/tool_manager.py @@ -12,3 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pathlib +from pretor.tool_plugin.base_tool import BaseToolData +from typing import Dict, Type + +class GlobalToolManager: + tool_mapper = Dict[str, Type[BaseToolData]] + + def __init__(self): + pass \ No newline at end of file diff --git a/pretor/tool_plugin/__init__.py b/pretor/tool_plugin/__init__.py index 5fa7362..a997743 100644 --- a/pretor/tool_plugin/__init__.py +++ b/pretor/tool_plugin/__init__.py @@ -10,5 +10,4 @@ # 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. - +# limitations under the License. \ No newline at end of file diff --git a/pretor/tool_plugin/approval/__init__.py b/pretor/tool_plugin/approval/__init__.py new file mode 100644 index 0000000..d1abb8a --- /dev/null +++ b/pretor/tool_plugin/approval/__init__.py @@ -0,0 +1 @@ +from .approval import ApprovalToolData, approval \ No newline at end of file diff --git a/pretor/tool_plugin/approval/approval.py b/pretor/tool_plugin/approval/approval.py new file mode 100644 index 0000000..9f1433e --- /dev/null +++ b/pretor/tool_plugin/approval/approval.py @@ -0,0 +1,39 @@ +# 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 pydantic import Field + +from pretor.tool_plugin.base_tool import BaseToolData +from pretor.utils.ray_hook import ray_actor_hook + + +class ApprovalToolData(BaseToolData): + is_system = True + action_scope = ["control_node", "consciousness_node",] + config_args = {} + + +async def approval(message: str, event_id: str) -> str: + """ + 当任务存在某些高风险操作或者计划需要让用户审批,发送请求给用户等待用户审批 + Args: + message: 发送给用户的请求 + event_id: + + Returns: + 用户的审批结果 + """ + actor_list = ray_actor_hook("global_state_machine") + await actor_list.global_state_machine.put_pending.remote(event_id, message) + reply = await actor_list.global_state_machine.get_received.remote(event_id) + return reply \ No newline at end of file diff --git a/pretor/tool_plugin/approval/config.json b/pretor/tool_plugin/approval/config.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/pretor/tool_plugin/approval/config.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/pretor/tool_plugin/web_crawler/__init__.py b/pretor/tool_plugin/base_tool.py similarity index 67% rename from pretor/tool_plugin/web_crawler/__init__.py rename to pretor/tool_plugin/base_tool.py index 5fa7362..3f961fb 100644 --- a/pretor/tool_plugin/web_crawler/__init__.py +++ b/pretor/tool_plugin/base_tool.py @@ -12,3 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +from pydantic import BaseModel +from typing import List, Literal, Dict + +class BaseToolData(BaseModel): + is_system: bool + action_scope: List[Literal["control_node", "consciousness_node", "supervisory_node", "growth_node", "", ""]] = [] + config_args: Dict[str, str] = {} \ No newline at end of file diff --git a/pretor/tool_plugin/docker_sandbox/docker_sandbox.py b/pretor/tool_plugin/docker_sandbox/docker_sandbox.py deleted file mode 100644 index 6db2bc7..0000000 --- a/pretor/tool_plugin/docker_sandbox/docker_sandbox.py +++ /dev/null @@ -1,46 +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. - -import docker -from typing import Dict, Any - -class DockerSandboxTool: - def __init__(self): - try: - self.client = docker.from_env() - except Exception as e: - self.client = None - print(f"Failed to initialize Docker client: {e}") - - def run_code(self, code: str, image: str = "python:3.9-slim") -> Dict[str, Any]: - if not self.client: - return {"error": "Docker client not initialized"} - - try: - # Simple python code runner in a container - container = self.client.containers.run( - image, - command=["python", "-c", code], - remove=True, - detach=False, - stdout=True, - stderr=True - ) - # Depending on python version, container returns bytes directly - output = container.decode("utf-8") if isinstance(container, bytes) else container - return {"status": "success", "output": output} - except docker.errors.ContainerError as e: - return {"status": "error", "output": e.stderr.decode("utf-8")} - except Exception as e: - return {"status": "error", "error": str(e)} diff --git a/pretor/tool_plugin/rag/__init__.py b/pretor/tool_plugin/rag/__init__.py deleted file mode 100644 index 5fa7362..0000000 --- a/pretor/tool_plugin/rag/__init__.py +++ /dev/null @@ -1,14 +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. - diff --git a/pretor/tool_plugin/rag/rag.py b/pretor/tool_plugin/rag/rag.py deleted file mode 100644 index be99c8d..0000000 --- a/pretor/tool_plugin/rag/rag.py +++ /dev/null @@ -1,31 +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 typing import List, Dict, Any -from sqlmodel import select -# Assuming MemoryRecord is accessible or passed, simulating direct pgvector call - -class RAGTool: - def __init__(self, async_session_maker): - self.async_session_maker = async_session_maker - - async def get_embedding(self, query: str) -> List[float]: - # Simulated embedding logic; in reality, this would call an embedding API - return [0.1] * 1536 - - async def retrieve(self, query: str, limit: int = 5) -> List[Dict[str, Any]]: - embedding = await self.get_embedding(query) - # We simulate the retrieve_memory call logic from MemoryRAG here - # Normally you would inject MemoryRAG or a repository, doing a simplistic return here - return [{"query": query, "simulated_results": f"Found results for {query} with vector {embedding[:2]}..."}] diff --git a/pretor/tool_plugin/web_crawler/web_crawler.py b/pretor/tool_plugin/web_crawler/web_crawler.py deleted file mode 100644 index f11a601..0000000 --- a/pretor/tool_plugin/web_crawler/web_crawler.py +++ /dev/null @@ -1,34 +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. - -import httpx -from typing import Dict, Any - -class WebCrawlerTool: - def __init__(self, timeout: int = 10): - self.timeout = timeout - - async def crawl(self, url: str) -> Dict[str, Any]: - try: - async with httpx.AsyncClient(timeout=self.timeout) as client: - response = await client.get(url) - response.raise_for_status() - # Basic text extraction can happen here (e.g., stripping HTML tags manually or with a library later) - return { - "url": url, - "status_code": response.status_code, - "content_preview": response.text[:500] - } - except Exception as e: - return {"url": url, "error": str(e)} diff --git a/pretor/utils/ray_hook.py b/pretor/utils/ray_hook.py new file mode 100644 index 0000000..cd1583f --- /dev/null +++ b/pretor/utils/ray_hook.py @@ -0,0 +1,41 @@ +# 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 ray +from typing import List + +class ActorList: + def __init__(self): + super().__setattr__('dict', {}) + + def __setattr__(self, key, value): + self.dict[key] = value + + def __getattr__(self, key): + if key in self.dict: + return self.dict[key] + raise AttributeError(f"ActorList 对象没有属性 '{key}'") + + def __delattr__(self, key): + if key in self.dict: + del self.dict[key] + else: + raise AttributeError(f"ActorList对象没有属性 '{key}'") + +def ray_actor_hook(*actor_names: str): + actor_list = ActorList() + for actor_name in actor_names: + handle = ray.get_actor(actor_name) + setattr(actor_list, actor_name, handle) + return actor_list +