# 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 kilostar.utils.ray_hook import ray_actor_hook from fastapi import APIRouter, Depends, Request from pydantic import BaseModel from kilostar.utils.access import Accessor, TokenData from kilostar.core.postgres_database.model import AgentType from fastapi import HTTPException from typing import Optional, List, Dict from kilostar.utils.check_user.role_check import RoleChecker from kilostar.core.postgres_database.model import UserAuthority from kilostar.utils.mcp_helper import get_all_toolsets_for_scope from kilostar.utils.i18n import t agent_router = APIRouter(prefix="/api/v1/agent", tags=["agent"]) class AgentRegister(BaseModel): """``POST /agent`` 入参(远程模型):通过 provider + model_id 加载系统节点。""" provider_title: str model_id: str individual_name: str tools: Optional[List[str]] = None class AgentLocalRegister(BaseModel): """``POST /agent`` 入参(本地模型):通过本地路径加载系统节点。""" 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)), ): """返回三大系统节点(regulatory/consciousness/control)当前的持久化配置。""" postgres_database = ray_actor_hook("postgres_database").postgres_database configs = await postgres_database.get_all_system_node_configs.remote() return {"system_nodes": configs} @agent_router.post("") async def load_agent( agent_register: Union[AgentRegister, AgentLocalRegister], request: Request, _: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)), ): """加载/重载某个系统节点的 Agent:先持久化配置,再调用对应节点 Actor 的 ``create_agent``。""" global_state_machine = ray_actor_hook("global_state_machine").global_state_machine postgres_database = ray_actor_hook("postgres_database").postgres_database accept_lang = request.headers.get("accept-language", "") if isinstance(agent_register, AgentLocalRegister): pass elif isinstance(agent_register, AgentRegister): try: await postgres_database.upsert_system_node_config.remote( agent_register.individual_name, agent_register.provider_title, agent_register.model_id, agent_register.tools, ) scope = agent_register.individual_name toolsets = await get_all_toolsets_for_scope(scope) match scope: case "regulatory_node": node = ray_actor_hook("regulatory_node").regulatory_node await node.create_agent.remote( global_state_machine, agent_register.provider_title, agent_register.model_id, agent_register.tools, toolsets, accept_lang, ) case "consciousness_node": node = ray_actor_hook("consciousness_node").consciousness_node await node.create_agent.remote( global_state_machine, agent_register.provider_title, agent_register.model_id, agent_register.tools, toolsets, accept_lang, ) case "control_node": node = ray_actor_hook("control_node").control_node await node.create_agent.remote( global_state_machine, agent_register.provider_title, agent_register.model_id, agent_register.tools, toolsets, accept_lang, ) case _: pass except Exception as e: from kilostar.utils.logger import get_logger get_logger("agent_api").exception(f"加载节点失败: {e}") raise HTTPException(status_code=500, detail="加载节点失败,请查看服务端日志") return {"message": "创建成功"} class WorkerIndividualCreate(BaseModel): """``POST /worker`` 入参:创建一个 Worker Agent 所需的完整配置。""" agent_name: str agent_type: AgentType description: str provider_title: str model_id: str system_prompt: str output_template: dict bound_skill: Dict[str, List[str]] workspace: List[str] tools: Optional[List[str]] = None class WorkerIndividualUpdate(BaseModel): """``PUT /worker/{agent_id}`` 入参:可选字段构成的局部更新载荷。""" agent_name: Optional[str] = None agent_type: Optional[AgentType] = None description: Optional[str] = None provider_title: Optional[str] = None model_id: Optional[str] = None system_prompt: Optional[str] = None output_template: Optional[dict] = None bound_skill: Optional[Dict[str, List[str]]] = None workspace: Optional[List[str]] = None tools: Optional[List[str]] = None @agent_router.post("/worker") async def create_worker_individual( worker_data: WorkerIndividualCreate, token_data: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)), ): """创建一个 Worker Agent,``owner_id`` 自动绑定为当前登录用户。""" postgres_database = ray_actor_hook("postgres_database").postgres_database data_dict = worker_data.model_dump() data_dict["owner_id"] = token_data.user_id worker = await postgres_database.add_worker_individual.remote(**data_dict) return {"message": "success", "agent_id": worker.agent_id} @agent_router.get("/worker") async def get_worker_individual_list( token_data: TokenData = Depends(Accessor.get_current_user), ): """列出当前登录用户名下的全部 Worker Agent。""" 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} @agent_router.get("/worker/{agent_id}") async def get_worker_individual( agent_id: str, token_data: TokenData = Depends(Accessor.get_current_user) ): """按 ``agent_id`` 查询 Worker Agent;非本人的 Agent 返回 403。""" postgres_database = ray_actor_hook("postgres_database").postgres_database worker = await postgres_database.get_worker_individual.remote(agent_id=agent_id) if not worker: raise HTTPException(status_code=404, detail="Agent not found") if worker.owner_id != token_data.user_id: raise HTTPException( status_code=403, detail="Forbidden: You do not own this agent" ) return worker @agent_router.put("/worker/{agent_id}") async def update_worker_individual( agent_id: str, worker_data: WorkerIndividualUpdate, token_data: TokenData = Depends(Accessor.get_current_user), ): """局部更新 Worker Agent 配置;同时把状态机里的旧实例移除等待懒加载。""" postgres_database = ray_actor_hook("postgres_database").postgres_database worker = await postgres_database.get_worker_individual.remote(agent_id=agent_id) if not worker: raise HTTPException(status_code=404, detail="Agent not found") if worker.owner_id != token_data.user_id: raise HTTPException( status_code=403, detail="Forbidden: You do not own this agent" ) update_data = worker_data.model_dump(exclude_unset=True) updated_worker = await postgres_database.update_worker_individual.remote( agent_id=agent_id, **update_data ) global_state_machine = ray_actor_hook("global_state_machine").global_state_machine try: await global_state_machine.remove_individual.remote(agent_id) except Exception: pass return {"message": "success", "worker": updated_worker} @agent_router.post("/worker/{agent_id}/reload") async def reload_worker_individual( agent_id: str, token_data: TokenData = Depends(Accessor.get_current_user) ): """强制把 Worker 从内存池中卸载,下次调用时按最新配置重新加载。""" postgres_database = ray_actor_hook("postgres_database").postgres_database worker = await postgres_database.get_worker_individual.remote(agent_id=agent_id) if not worker: raise HTTPException(status_code=404, detail="Agent not found") if worker.owner_id != token_data.user_id: raise HTTPException( status_code=403, detail="Forbidden: You do not own this agent" ) global_state_machine = ray_actor_hook("global_state_machine").global_state_machine await global_state_machine.remove_individual.remote(agent_id) return {"message": "Worker will be reloaded on next use"} @agent_router.delete("/worker/{agent_id}") async def delete_worker_individual( agent_id: str, token_data: TokenData = Depends(Accessor.get_current_user) ): """删除 Worker Agent;非本人 Agent 返回 403。""" postgres_database = ray_actor_hook("postgres_database").postgres_database worker = await postgres_database.get_worker_individual.remote(agent_id=agent_id) if not worker: raise HTTPException(status_code=404, detail="Agent not found") if worker.owner_id != token_data.user_id: raise HTTPException( status_code=403, detail="Forbidden: You do not own this agent" ) await postgres_database.delete_worker_individual.remote(agent_id=agent_id) return {"message": "success"}