wip: 增加插件

This commit is contained in:
朝夕 2026-04-12 14:18:03 +08:00
parent 929cf0e2f2
commit a04fc08735
16 changed files with 172 additions and 134 deletions

9
docs/problem.md Normal file
View File

@ -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待完善

52
pretor/api/agent.py Normal file
View File

@ -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": "创建成功"}

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -11,4 +11,3 @@
# 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.

View File

@ -0,0 +1 @@
from .approval import ApprovalToolData, approval

View File

@ -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

View File

@ -0,0 +1,2 @@
{
}

View File

@ -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] = {}

View File

@ -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)}

View File

@ -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.

View File

@ -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]}..."}]

View File

@ -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)}

41
pretor/utils/ray_hook.py Normal file
View File

@ -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