style: 项目重构
1.项目改名为kilostar(千星) 2.后端部分进行大规模重构 3.node功能进行大规模重新设计
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
# 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.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# 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.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# 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.
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# 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_ai import Agent
|
||||
from pydantic_ai.models.openai import OpenAIChatModel
|
||||
from pydantic_ai.models.anthropic import AnthropicModel
|
||||
from pydantic_ai.providers.openai import OpenAIProvider
|
||||
from pydantic_ai.providers.anthropic import AnthropicProvider
|
||||
from kilostar.adapter.model_adapter.deepseek_reasoner import DeepSeekReasonerAgent
|
||||
from kilostar.core.global_state_machine.model_provider import Provider
|
||||
from kilostar.utils.agent_model import ResponseModel, DepsModel
|
||||
from kilostar.utils.error import ModelNotExistError
|
||||
|
||||
|
||||
class AgentFactory:
|
||||
"""AgentFactory 核心组件类。
|
||||
这是一个领域数据模型或功能封装类,承载了 AgentFactory 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。"""
|
||||
|
||||
def __init__(self):
|
||||
self._models_mapping = {
|
||||
"openai": (OpenAIChatModel, OpenAIProvider),
|
||||
"claude": (AnthropicModel, AnthropicProvider),
|
||||
"deepseek": (OpenAIChatModel, OpenAIProvider),
|
||||
}
|
||||
|
||||
def create_agent(
|
||||
self,
|
||||
provider: Provider,
|
||||
model_id: str,
|
||||
output_type: ResponseModel,
|
||||
system_prompt: str,
|
||||
deps_type: DepsModel,
|
||||
agent_name: str,
|
||||
tools: list = None,
|
||||
) -> Agent:
|
||||
"""
|
||||
create_agent方法,将输入的provider对象实例化为一个pydantic-ai的agent对象
|
||||
|
||||
Args:
|
||||
provider: Provider对象,从global_state_machine中获取
|
||||
model_id: 模型名
|
||||
output_type: 输出格式
|
||||
system_prompt: 系统提示词
|
||||
deps_type: 依赖类型,在agent运行时动态输入的格式化消息
|
||||
agent_name: agent的名字
|
||||
tools: 工具列表
|
||||
|
||||
Returns:
|
||||
返回被实例化的pydantic-ai的Agent对象
|
||||
"""
|
||||
if model_id not in provider.provider_models:
|
||||
raise ModelNotExistError("模型不存在")
|
||||
if provider.provider_type not in self._models_mapping:
|
||||
raise ValueError(f"不支持的协议类型: {provider.provider_type}")
|
||||
model_class, provider_class = self._models_mapping[provider.provider_type]
|
||||
model = model_class(
|
||||
model_id,
|
||||
provider=provider_class(
|
||||
api_key=provider.provider_apikey, base_url=provider.provider_url
|
||||
),
|
||||
)
|
||||
match provider.provider_type:
|
||||
case "deepseek":
|
||||
agent = DeepSeekReasonerAgent(
|
||||
model=model,
|
||||
name=agent_name,
|
||||
output_type=output_type,
|
||||
deps_type=deps_type,
|
||||
system_prompt=system_prompt,
|
||||
tools=tools,
|
||||
retries=3,
|
||||
)
|
||||
case _:
|
||||
agent = Agent(
|
||||
model=model,
|
||||
name=agent_name,
|
||||
system_prompt=system_prompt,
|
||||
output_type=output_type,
|
||||
deps_type=deps_type,
|
||||
tools=tools,
|
||||
)
|
||||
return agent
|
||||
@@ -0,0 +1,193 @@
|
||||
# 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
|
||||
|
||||
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":
|
||||
return self._parsed
|
||||
return getattr(self._original, name)
|
||||
|
||||
|
||||
class DeepSeekReasonerAgent(Generic[T]):
|
||||
"""
|
||||
专为 DeepSeek-V4/R1 设计的适配器。
|
||||
将结构化输出降级为文本解析模式,并支持重试逻辑以确保系统兼容性。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model,
|
||||
name,
|
||||
output_type: Any = str,
|
||||
system_prompt: str = "",
|
||||
deps_type: Type[Any] = None,
|
||||
tools: list = None,
|
||||
retries: int = 3,
|
||||
**kwargs,
|
||||
):
|
||||
self.output_schema = output_type
|
||||
self.has_custom_output = output_type is not str and output_type is not None
|
||||
self.tools = tools or []
|
||||
self.retries = retries
|
||||
|
||||
format_instruction = ""
|
||||
if self.has_custom_output:
|
||||
try:
|
||||
from pydantic import TypeAdapter
|
||||
|
||||
schema_dict = TypeAdapter(self.output_schema).json_schema()
|
||||
schema_str = json.dumps(schema_dict, ensure_ascii=False)
|
||||
format_instruction = (
|
||||
f"\n\nCRITICAL: 你必须输出且只能输出一段纯 JSON 格式的数据,"
|
||||
f"并包裹在 ```json 和 ``` 之间。格式必须符合以下 JSON Schema 结构(或对应数据类型):\n"
|
||||
f"{schema_str}"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
tool_instruction = ""
|
||||
if self.tools:
|
||||
tool_descs = []
|
||||
for t in self.tools:
|
||||
desc = getattr(t, "__name__", str(t))
|
||||
if hasattr(t, "__doc__") and t.__doc__:
|
||||
desc += f": {t.__doc__.strip()}"
|
||||
tool_descs.append(f"- {desc}")
|
||||
tool_instruction = (
|
||||
"\n\n系统为您提供了以下工具。由于当前处于结构化降级模式,无法原生调用。"
|
||||
"但如果您在思考过程中判断必须使用这些工具,请在返回的结构中(或如果是自由文本)注明意图,由外层逻辑进行调度:\n"
|
||||
+ "\n".join(tool_descs)
|
||||
)
|
||||
|
||||
self.agent = Agent(
|
||||
model=model,
|
||||
name=name,
|
||||
output_type=str, # Force native agent to return str to disable function calling
|
||||
system_prompt=system_prompt + format_instruction + tool_instruction,
|
||||
deps_type=deps_type,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def _parse_output(self, text: str) -> Any:
|
||||
"""执行与 parse output 相关的核心业务流转操作。
|
||||
该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。
|
||||
Args: text (str): 控制逻辑流向的具体字符串参数,指定了期望的 text 内容。
|
||||
Returns: (Any): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。"""
|
||||
if not self.has_custom_output:
|
||||
return text
|
||||
|
||||
match = re.search(r"```json\s*(.*?)\s*```", text, re.DOTALL)
|
||||
json_str = match.group(1).strip() if match else text
|
||||
|
||||
if not json_str.startswith("{") and not json_str.startswith("["):
|
||||
start_obj = json_str.find("{")
|
||||
start_arr = json_str.find("[")
|
||||
start = -1
|
||||
end = -1
|
||||
if start_obj != -1 and (start_arr == -1 or start_obj < start_arr):
|
||||
start = start_obj
|
||||
end = json_str.rfind("}")
|
||||
elif start_arr != -1:
|
||||
start = start_arr
|
||||
end = json_str.rfind("]")
|
||||
|
||||
if start != -1 and end != -1 and end > start:
|
||||
json_str = json_str[start : end + 1]
|
||||
|
||||
if not json_str:
|
||||
raise ValueError("未找到有效的 JSON 块。请将结果包装在 ```json 中。")
|
||||
|
||||
try:
|
||||
from pydantic import TypeAdapter
|
||||
|
||||
adapter = TypeAdapter(self.output_schema)
|
||||
return adapter.validate_json(json_str)
|
||||
except ValidationError as e:
|
||||
raise ValueError(f"返回的 JSON 无法匹配所需结构:{e}")
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"返回的不是合法的 JSON:{e}")
|
||||
|
||||
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
|
||||
|
||||
for attempt in range(self.retries + 1):
|
||||
result = await self.agent.run(
|
||||
user_prompt, deps=deps, message_history=current_history, **kwargs
|
||||
)
|
||||
|
||||
raw_text = (
|
||||
result.data
|
||||
if hasattr(result, "data")
|
||||
else getattr(result, "output", str(result))
|
||||
)
|
||||
|
||||
try:
|
||||
parsed_data = self._parse_output(raw_text)
|
||||
|
||||
# Proxy the result to inject the parsed data seamlessly
|
||||
return AgentRunResultProxy(result, parsed_data)
|
||||
|
||||
except ValueError as e:
|
||||
last_exception = e
|
||||
# Prepare retry prompt
|
||||
user_prompt = (
|
||||
f"你的上一次输出解析失败,错误原因是: {e}\n请修正格式后重新输出。"
|
||||
)
|
||||
|
||||
# We need to maintain history manually so the model sees what it did wrong
|
||||
# Actually, pydantic-ai manages history inside the result. Let's use the all_messages from result
|
||||
if hasattr(result, "all_messages"):
|
||||
current_history = result.all_messages()
|
||||
|
||||
raise ValueError(
|
||||
f"Exceeded maximum retries ({self.retries}) for output validation. Last error: {last_exception}"
|
||||
)
|
||||
@@ -0,0 +1,146 @@
|
||||
# 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 os
|
||||
from typing import Dict
|
||||
|
||||
from fastapi import FastAPI, WebSocket, Request
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from ray import serve
|
||||
|
||||
from .agent import agent_router
|
||||
from .auth import auth_router
|
||||
from .cluster import cluster_router
|
||||
from .platform.frontend import client_router
|
||||
from .provider import provider_router
|
||||
from .resource import resource_router
|
||||
from .workflow import workflow_router
|
||||
from kilostar.utils.error import (
|
||||
DemandError,
|
||||
ModelNotExistError,
|
||||
UserError,
|
||||
UserNotExistError,
|
||||
UserPasswordError,
|
||||
ProviderError,
|
||||
ProviderNotExistError,
|
||||
WorkflowError,
|
||||
WorkflowExit,
|
||||
)
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
app.include_router(client_router) # 客户端路径
|
||||
app.include_router(auth_router) # 用户路径
|
||||
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):
|
||||
return JSONResponse(status_code=404, content={"message": "用户不存在"})
|
||||
|
||||
|
||||
@app.exception_handler(UserPasswordError)
|
||||
async def user_password_handler(request: Request, exc: UserPasswordError):
|
||||
return JSONResponse(status_code=401, content={"message": "密码错误"})
|
||||
|
||||
|
||||
@app.exception_handler(UserError)
|
||||
async def user_error_handler(request: Request, exc: UserError):
|
||||
return JSONResponse(status_code=400, content={"message": "用户相关错误"})
|
||||
|
||||
|
||||
@app.exception_handler(ProviderNotExistError)
|
||||
async def provider_not_exist_handler(request: Request, exc: ProviderNotExistError):
|
||||
return JSONResponse(status_code=404, content={"message": "服务提供商不存在"})
|
||||
|
||||
|
||||
@app.exception_handler(ProviderError)
|
||||
async def provider_error_handler(request: Request, exc: ProviderError):
|
||||
return JSONResponse(status_code=400, content={"message": "服务提供商错误"})
|
||||
|
||||
|
||||
@app.exception_handler(ModelNotExistError)
|
||||
async def model_not_exist_handler(request: Request, exc: ModelNotExistError):
|
||||
return JSONResponse(status_code=404, content={"message": "模型不存在"})
|
||||
|
||||
|
||||
@app.exception_handler(DemandError)
|
||||
async def demand_error_handler(request: Request, exc: DemandError):
|
||||
return JSONResponse(status_code=400, content={"message": "需求格式错误或不满足"})
|
||||
|
||||
|
||||
@app.exception_handler(WorkflowExit)
|
||||
async def workflow_exit_handler(request: Request, exc: WorkflowExit):
|
||||
return JSONResponse(status_code=400, content={"message": "工作流已退出"})
|
||||
|
||||
|
||||
@app.exception_handler(WorkflowError)
|
||||
async def workflow_error_handler(request: Request, exc: WorkflowError):
|
||||
return JSONResponse(status_code=500, content={"message": "工作流执行错误"})
|
||||
|
||||
|
||||
base_dir = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
)
|
||||
frontend_dir = os.path.join(base_dir, "frontend", "dist")
|
||||
|
||||
if os.path.exists(frontend_dir):
|
||||
app.mount(
|
||||
"/assets",
|
||||
StaticFiles(directory=os.path.join(frontend_dir, "assets")),
|
||||
name="assets",
|
||||
)
|
||||
|
||||
@app.get("/favicon.svg", include_in_schema=False)
|
||||
async def serve_favicon():
|
||||
return FileResponse(os.path.join(frontend_dir, "favicon.svg"))
|
||||
|
||||
@app.get("/icons.svg", include_in_schema=False)
|
||||
async def serve_icons():
|
||||
return FileResponse(os.path.join(frontend_dir, "icons.svg"))
|
||||
|
||||
@app.get("/{full_path:path}", include_in_schema=False)
|
||||
async def serve_frontend(full_path: str):
|
||||
# 【重要安全修复】避免拦截不存在的 API 路由。如果是调用了不存在的 /api/ 接口,直接返回 404,不返回前端页面
|
||||
if full_path.startswith("api/"):
|
||||
return JSONResponse(
|
||||
status_code=404, content={"detail": "API endpoint not found"}
|
||||
)
|
||||
|
||||
index_path = os.path.join(frontend_dir, "index.html")
|
||||
if os.path.exists(index_path):
|
||||
return FileResponse(index_path)
|
||||
return JSONResponse(
|
||||
status_code=404, content={"detail": "Frontend build not found"}
|
||||
)
|
||||
else:
|
||||
import logging
|
||||
|
||||
logging.getLogger("kilostar").warning(
|
||||
f"Frontend dist folder not found at {frontend_dir}. Skipping frontend mount."
|
||||
)
|
||||
|
||||
|
||||
@serve.deployment
|
||||
@serve.ingress(app)
|
||||
class kilostarGateway:
|
||||
gateway: Dict[str, WebSocket]
|
||||
|
||||
def __init__(self):
|
||||
self.gateway = {}
|
||||
@@ -0,0 +1,275 @@
|
||||
# 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
|
||||
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
|
||||
|
||||
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}
|
||||
|
||||
|
||||
@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
|
||||
|
||||
if isinstance(agent_register, AgentLocalRegister):
|
||||
pass
|
||||
|
||||
elif isinstance(agent_register, AgentRegister):
|
||||
try:
|
||||
# Persist configuration
|
||||
await postgres_database.upsert_system_node_config.remote(
|
||||
agent_register.individual_name,
|
||||
agent_register.provider_title,
|
||||
agent_register.model_id,
|
||||
agent_register.tools,
|
||||
)
|
||||
|
||||
# Load agent into state machine
|
||||
match agent_register.individual_name:
|
||||
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,
|
||||
)
|
||||
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,
|
||||
)
|
||||
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,
|
||||
)
|
||||
case _:
|
||||
pass
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"加载节点失败: {str(e)}")
|
||||
return {"message": "创建成功"}
|
||||
|
||||
|
||||
class WorkerIndividualCreate(BaseModel):
|
||||
"""WorkerIndividualCreate 核心组件类。
|
||||
这是一个具体的 Worker 智能体实体类,代表着具备特定人设、领域技能或长文本处理能力的数字员工。它可以被控制器动态拉起,并在安全沙箱内执行复杂的工作流指令与多步骤推理任务。"""
|
||||
|
||||
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):
|
||||
"""WorkerIndividualUpdate 核心组件类。
|
||||
这是一个具体的 Worker 智能体实体类,代表着具备特定人设、领域技能或长文本处理能力的数字员工。它可以被控制器动态拉起,并在安全沙箱内执行复杂的工作流指令与多步骤推理任务。"""
|
||||
|
||||
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)),
|
||||
):
|
||||
"""处理针对 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
|
||||
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),
|
||||
):
|
||||
"""处理针对 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}
|
||||
|
||||
|
||||
@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:
|
||||
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),
|
||||
):
|
||||
"""处理针对 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:
|
||||
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)
|
||||
):
|
||||
"""处理针对 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:
|
||||
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)
|
||||
):
|
||||
"""处理针对 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:
|
||||
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"}
|
||||
@@ -0,0 +1,136 @@
|
||||
# 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 fastapi import APIRouter
|
||||
from fastapi import Depends
|
||||
from pydantic import BaseModel
|
||||
from kilostar.utils.access import Accessor, TokenData
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
from kilostar.utils.ray_hook import ray_actor_hook
|
||||
from kilostar.utils.check_user.role_check import RoleChecker
|
||||
from kilostar.core.postgres_database.model import UserAuthority
|
||||
from kilostar.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:
|
||||
raise UserNotExistError()
|
||||
token = await run_in_threadpool(
|
||||
Accessor.login_hashed_password, user, user_login.password
|
||||
)
|
||||
return {"message": "success", "token": token}
|
||||
|
||||
|
||||
class ChangeAuthorityRequest(BaseModel):
|
||||
"""ChangeAuthorityRequest 核心组件类。
|
||||
这是一个领域数据模型或功能封装类,承载了 ChangeAuthorityRequest 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。"""
|
||||
|
||||
user_id: str
|
||||
new_authority: UserAuthority
|
||||
|
||||
|
||||
@auth_router.put("/authority")
|
||||
async def change_authority(
|
||||
request: ChangeAuthorityRequest,
|
||||
_: TokenData = Depends(
|
||||
RoleChecker(allowed_roles=UserAuthority.SUPER_ADMINISTRATOR)
|
||||
),
|
||||
):
|
||||
"""
|
||||
Update a user's authority level. Only accessible by SUPER_ADMINISTRATOR.
|
||||
"""
|
||||
postgres_database = ray_actor_hook("postgres_database").postgres_database
|
||||
user = await postgres_database.change_user_authority.remote(
|
||||
user_id=request.user_id, new_authority=request.new_authority
|
||||
)
|
||||
return {
|
||||
"message": "success",
|
||||
"user_id": user.user_id,
|
||||
"new_authority": user.user_authority,
|
||||
}
|
||||
|
||||
|
||||
@auth_router.get("/list")
|
||||
async def get_user_list(
|
||||
_: TokenData = Depends(
|
||||
RoleChecker(allowed_roles=UserAuthority.SUPER_ADMINISTRATOR)
|
||||
),
|
||||
):
|
||||
"""
|
||||
Get a list of all users. Only accessible by SUPER_ADMINISTRATOR.
|
||||
"""
|
||||
postgres_database = ray_actor_hook("postgres_database").postgres_database
|
||||
users = await postgres_database.get_all_users.remote()
|
||||
return {
|
||||
"users": [
|
||||
{"user_id": u.user_id, "user_name": u.user_name, "role": u.user_authority}
|
||||
for u in users
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@auth_router.delete("/{user_id}")
|
||||
async def delete_user(
|
||||
user_id: str,
|
||||
_: TokenData = Depends(
|
||||
RoleChecker(allowed_roles=UserAuthority.SUPER_ADMINISTRATOR)
|
||||
),
|
||||
):
|
||||
"""
|
||||
Delete a user. Only accessible by SUPER_ADMINISTRATOR.
|
||||
"""
|
||||
postgres_database = ray_actor_hook("postgres_database").postgres_database
|
||||
await postgres_database.delete_user_by_id.remote(user_id=user_id)
|
||||
return {"message": "success"}
|
||||
@@ -0,0 +1,19 @@
|
||||
# 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 fastapi import APIRouter
|
||||
|
||||
cluster_router = APIRouter(prefix="/api/v1/cluster", tags=["cluster"])
|
||||
|
||||
# Monitor websocket API temporarily removed
|
||||
@@ -0,0 +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 .frontend import client_router
|
||||
|
||||
__all__ = ["client_router"]
|
||||
@@ -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.
|
||||
|
||||
import datetime
|
||||
from pydantic import BaseModel, Field, ConfigDict
|
||||
from ulid import ULID
|
||||
from typing import Any, Dict
|
||||
from kilostar.core.workflow_running_engine.workflow import kilostarWorkflow
|
||||
import asyncio
|
||||
|
||||
|
||||
class kilostarEvent(BaseModel):
|
||||
"""kilostarEvent 核心组件类。
|
||||
这是一个领域数据模型或功能封装类,承载了 kilostarEvent 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。"""
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
trace_id: str = Field(
|
||||
default_factory=lambda: str(ULID()), description="事件的唯一标识符"
|
||||
)
|
||||
platform: str = Field(description="消息来源的平台")
|
||||
user_id: str = Field(description="用户id")
|
||||
user_name: str = Field(description="用户名")
|
||||
create_time: str = Field(
|
||||
default_factory=lambda: str(
|
||||
datetime.datetime.now(datetime.timezone.utc).isoformat()
|
||||
),
|
||||
description="事件创建时间",
|
||||
)
|
||||
message: str = Field(description="用户发来的消息")
|
||||
attachment: Dict[str, str] | None = Field(default=None, description="附件")
|
||||
# --------------------------------------------------------------------------------------------------------------
|
||||
context: Dict[str, Any] = Field(
|
||||
default_factory=dict, description="事件上下文内容,可包含工作流模板等信息"
|
||||
)
|
||||
workflow: kilostarWorkflow | None = Field(default=None, description="工作流")
|
||||
pending_queue: asyncio.Queue[str] | None = Field(
|
||||
default=None, description="待处理队列"
|
||||
)
|
||||
receive_queue: asyncio.Queue[str] | None = Field(
|
||||
default=None, description="待接收队列"
|
||||
)
|
||||
@@ -0,0 +1,87 @@
|
||||
# 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 fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File
|
||||
from pydantic import BaseModel
|
||||
from kilostar.utils.access import Accessor, TokenData
|
||||
from kilostar.api.platform.event import kilostarEvent
|
||||
from kilostar.utils.ray_hook import ray_actor_hook
|
||||
import os
|
||||
import anyio
|
||||
from kilostar.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 = kilostarEvent(
|
||||
platform="client",
|
||||
user_id=str(token_data.user_id),
|
||||
user_name=token_data.username,
|
||||
message=message.message,
|
||||
)
|
||||
regulatory_node = ray_actor_hook("regulatory_node").regulatory_node
|
||||
message = await regulatory_node.working.remote(event)
|
||||
if message.startswith("任务已创建"):
|
||||
return {"message": f"{event.trace_id}\n\n{message}"}
|
||||
elif message == "未知相应类型":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="模型回复错误"
|
||||
)
|
||||
else:
|
||||
return {"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)
|
||||
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:
|
||||
logger.error(f"文件上传失败: {e}")
|
||||
raise HTTPException(status_code=500, detail="文件上传失败")
|
||||
@@ -0,0 +1,85 @@
|
||||
# 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 fastapi import APIRouter, Depends
|
||||
from pydantic import BaseModel
|
||||
from typing import Literal
|
||||
from kilostar.utils.access import TokenData, Accessor
|
||||
from kilostar.utils.check_user.role_check import RoleChecker
|
||||
from kilostar.core.postgres_database.model import UserAuthority
|
||||
from typing import Dict
|
||||
from kilostar.core.global_state_machine.model_provider.base_provider import Provider
|
||||
from kilostar.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
|
||||
provider_apikey: str
|
||||
|
||||
|
||||
@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,
|
||||
provider_url=provider_register.provider_url,
|
||||
provider_apikey=provider_register.provider_apikey,
|
||||
provider_owner=token_data.user_id,
|
||||
)
|
||||
|
||||
|
||||
@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"}
|
||||
@@ -0,0 +1,104 @@
|
||||
# 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 BaseModel
|
||||
import viceroy
|
||||
from kilostar.utils.ray_hook import ray_actor_hook
|
||||
from fastapi import APIRouter, Depends
|
||||
from kilostar.utils.access import TokenData
|
||||
from kilostar.utils.check_user.role_check import RoleChecker
|
||||
from kilostar.core.postgres_database.model import UserAuthority
|
||||
|
||||
resource_router = APIRouter(prefix="/api/v1/resource")
|
||||
|
||||
|
||||
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
|
||||
|
||||
skill_output_dir = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..", "plugin", "skill")
|
||||
)
|
||||
os.makedirs(skill_output_dir, exist_ok=True)
|
||||
await viceroy.install_skill_async(
|
||||
url=skill.repo_url, path=skill.path, output=skill_output_dir
|
||||
)
|
||||
if skill.path:
|
||||
skill_name = skill.path.split("/")[-1]
|
||||
else:
|
||||
skill_name = skill.repo_url.split("/")[-1]
|
||||
await global_state_machine.add_skill.remote(skill_name)
|
||||
return {"message": "创建成功"}
|
||||
|
||||
|
||||
@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)
|
||||
return {"message": "success"}
|
||||
|
||||
|
||||
@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()
|
||||
for scope_tools in tool_mapper.values():
|
||||
all_tool_names.update(scope_tools.keys())
|
||||
return {"tools": list(all_tool_names)}
|
||||
@@ -0,0 +1,128 @@
|
||||
# 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 kilostar.utils.ray_hook import ray_actor_hook
|
||||
from fastapi import APIRouter, Request, HTTPException
|
||||
from fastapi.responses import StreamingResponse
|
||||
import asyncio
|
||||
|
||||
workflow_router = APIRouter(prefix="/api/v1/workflow", tags=["workflow"])
|
||||
|
||||
|
||||
@workflow_router.get("/list")
|
||||
async def get_workflow_list():
|
||||
"""处理针对 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):
|
||||
"""处理针对 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")
|
||||
|
||||
workflow = event.workflow
|
||||
if not workflow:
|
||||
return {
|
||||
"event_id": trace_id,
|
||||
"workflow_title": None,
|
||||
"status": "waiting",
|
||||
"user_name": event.user_name,
|
||||
"message": event.message,
|
||||
"create_time": event.create_time,
|
||||
"steps": [],
|
||||
}
|
||||
|
||||
steps = []
|
||||
for step in workflow.work_link:
|
||||
steps.append(
|
||||
{
|
||||
"step": step.step,
|
||||
"name": step.name,
|
||||
"node": step.node,
|
||||
"action": step.action,
|
||||
"desc": step.desc,
|
||||
"status": step.status,
|
||||
"agent_id": step.agent_id,
|
||||
}
|
||||
)
|
||||
return {
|
||||
"event_id": trace_id,
|
||||
"workflow_title": workflow.title,
|
||||
"status": workflow.status.status,
|
||||
"command": workflow.command,
|
||||
"current_step": workflow.status.step,
|
||||
"user_name": event.user_name,
|
||||
"message": event.message,
|
||||
"create_time": event.create_time,
|
||||
"steps": steps,
|
||||
}
|
||||
|
||||
|
||||
@workflow_router.get("/sse/{trace_id}")
|
||||
async def get_workflow_sse(trace_id: str, request: Request):
|
||||
"""处理针对 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():
|
||||
break
|
||||
|
||||
# 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_workflow_manager.get_pending.remote(trace_id)
|
||||
# Ensure the message is formatted as SSE
|
||||
yield f"data: {message}\n\n"
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
return StreamingResponse(event_generator(), media_type="text/event-stream")
|
||||
|
||||
|
||||
@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_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"}
|
||||
@@ -0,0 +1,14 @@
|
||||
# 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.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
from kilostar.core.global_state_machine.global_state_machine import GlobalStateMachine
|
||||
|
||||
__all__ = ["GlobalStateMachine"]
|
||||
@@ -0,0 +1,165 @@
|
||||
# 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 kilostar.core.global_state_machine.provider_manager import ProviderManager
|
||||
from kilostar.core.global_state_machine.tool_manager import GlobalToolManager
|
||||
from kilostar.core.postgres_database import PostgresDatabase
|
||||
from kilostar.core.global_state_machine.skill_manager import GlobalSkillManager
|
||||
from kilostar.core.global_state_machine.individual_manager import GlobalIndividualManager
|
||||
|
||||
|
||||
@ray.remote
|
||||
class GlobalStateMachine:
|
||||
"""GlobalStateMachine 核心组件类。
|
||||
这是一个领域数据模型或功能封装类,承载了 GlobalStateMachine 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。"""
|
||||
|
||||
def __init__(self, postgres_database: PostgresDatabase):
|
||||
import sys
|
||||
|
||||
print("GSM __init__ START", file=sys.stderr, flush=True)
|
||||
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)
|
||||
self._global_tool_manager = GlobalToolManager()
|
||||
print(" tool_manager done", file=sys.stderr, flush=True)
|
||||
self._global_skill_manager = GlobalSkillManager()
|
||||
print(" skill_manager done", file=sys.stderr, flush=True)
|
||||
self._global_individual_manager = GlobalIndividualManager()
|
||||
print(" individual_manager done", file=sys.stderr, flush=True)
|
||||
self.postgres_database = postgres_database
|
||||
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,
|
||||
provider_url=provider_url,
|
||||
provider_apikey=provider_apikey,
|
||||
provider_owner=provider_owner,
|
||||
postgres_database=self.postgres_database,
|
||||
)
|
||||
|
||||
# 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", {})
|
||||
merged_tools = {**default_tools, **tools}
|
||||
return merged_tools
|
||||
|
||||
# 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()
|
||||
@@ -0,0 +1,88 @@
|
||||
# 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 Dict, Any
|
||||
from kilostar.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()
|
||||
for ind in individuals:
|
||||
agent_id = getattr(ind, "agent_id", None)
|
||||
if agent_id:
|
||||
self._individuals[agent_id] = (
|
||||
ind.model_dump()
|
||||
if hasattr(ind, "model_dump")
|
||||
else dict(ind)
|
||||
)
|
||||
logger.info(
|
||||
f"成功从数据库拉取了 {len(self._individuals)} 个 Worker Individual 配置。"
|
||||
)
|
||||
except AttributeError:
|
||||
logger.warning(
|
||||
"数据库中 get_all_worker_individual 方法未实现,跳过全量加载。可以在将来完善该接口。"
|
||||
)
|
||||
except Exception as e:
|
||||
# 捕获因 Ray 调用目标方法不存在引发的异常
|
||||
if "has no attribute 'get_all_worker_individual'" in str(e):
|
||||
logger.warning(
|
||||
"数据库 individual_database 中缺少 get_all_worker_individual 方法,无法全量拉取。"
|
||||
)
|
||||
else:
|
||||
raise e
|
||||
except Exception as e:
|
||||
logger.error(f"从数据库拉取 Worker Individual 配置失败: {e}")
|
||||
|
||||
def add_individual(self, agent_id: str, config: Dict[str, Any]) -> None:
|
||||
"""
|
||||
注册一个 worker individual
|
||||
config 可以包含 type, prompt, provider_title, model_id 等
|
||||
"""
|
||||
config["agent_id"] = agent_id
|
||||
self._individuals[agent_id] = config
|
||||
|
||||
def get_individual(self, agent_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
获取一个 worker individual 的配置
|
||||
"""
|
||||
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
|
||||
@@ -0,0 +1,35 @@
|
||||
# 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 kilostar.core.global_state_machine.model_provider.base_provider import (
|
||||
Provider,
|
||||
ProviderArgs,
|
||||
)
|
||||
from kilostar.core.global_state_machine.model_provider.openai_provider import (
|
||||
OpenAIProvider,
|
||||
)
|
||||
from kilostar.core.global_state_machine.model_provider.claude_provider import (
|
||||
ClaudeProvider,
|
||||
)
|
||||
from kilostar.core.global_state_machine.model_provider.deepseek_provider import (
|
||||
DeepseekProvider,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Provider",
|
||||
"ProviderArgs",
|
||||
"OpenAIProvider",
|
||||
"ClaudeProvider",
|
||||
"DeepseekProvider",
|
||||
]
|
||||
@@ -0,0 +1,112 @@
|
||||
# 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 abc import ABC, abstractmethod
|
||||
from pydantic import BaseModel
|
||||
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
|
||||
provider_models: List[str]
|
||||
provider_type: str
|
||||
provider_owner: str | None = None
|
||||
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:
|
||||
"""
|
||||
创建一个供应商,传入provider_args参数,打包为一个Provider对象
|
||||
|
||||
Args:
|
||||
provider_args: 参数包,包含以下几个参数
|
||||
provider_title: 供应商的别名
|
||||
provider_url: 供应商的url
|
||||
provider_apikey:供应商的apikey
|
||||
|
||||
Returns:
|
||||
返回一个Provider对象,由provider_manager管理
|
||||
"""
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
async def _load_models(provider_args: ProviderArgs) -> List[str]:
|
||||
"""
|
||||
加载模型列表
|
||||
base_provider的字类应当按照供应商的api标准,向供应商的接口发送http请求从而或者供应商所提供的模型列表
|
||||
|
||||
Args:
|
||||
provider_args: 参数包,包含以下几个参数
|
||||
provider_title: 供应商的别名
|
||||
provider_url: 供应商的url
|
||||
provider_apikey:供应商的apikey
|
||||
|
||||
Returns:
|
||||
返回一个列表,为http请求获取的模型列表
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def _return_provider(
|
||||
provider_args: ProviderArgs, provider_models: List[str]
|
||||
) -> Provider:
|
||||
"""
|
||||
包装Provider对象并返回
|
||||
将provider_args和_load_models获取的方法包装为provider对象
|
||||
|
||||
Args:
|
||||
provider_args: 参数包,包含以下几个参数
|
||||
provider_title: 供应商的别名
|
||||
provider_url: 供应商的url
|
||||
provider_apikey:供应商的apikey
|
||||
|
||||
provider_models: 模型列表,为该供应商包含的模型列表
|
||||
|
||||
Returns:
|
||||
返回一个Provider对象
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,91 @@
|
||||
# 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 kilostar.utils.retry import retry_on_retryable_error
|
||||
|
||||
from kilostar.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
|
||||
|
||||
@staticmethod
|
||||
@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",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
# 如果是官方 API,通常使用 /v1/models (如果支持)
|
||||
# 注意:很多时候 Anthropic 并不返回完整列表,如果请求失败,建议返回硬编码的常用模型
|
||||
url = f"{provider_args.provider_url.rstrip('/')}/v1/models"
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
response = await client.get(url, headers=headers)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
model_ids = [m["id"] for m in data.get("data", [])]
|
||||
return sorted(model_ids)
|
||||
else:
|
||||
# 如果官方列表接口不可用,fallback 到已知常用模型
|
||||
return [
|
||||
"claude-3-5-sonnet-20240620",
|
||||
"claude-3-opus-20240229",
|
||||
"claude-3-haiku-20240307",
|
||||
]
|
||||
except Exception as e:
|
||||
print(f"[{provider_args.provider_title}] 获取 Claude 模型列表错误: {e}")
|
||||
return []
|
||||
|
||||
@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,
|
||||
provider_models=provider_models,
|
||||
provider_type="claude",
|
||||
)
|
||||
@@ -0,0 +1,94 @@
|
||||
# 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 kilostar.utils.retry import retry_on_retryable_error
|
||||
from kilostar.core.global_state_machine.model_provider.base_provider import (
|
||||
BaseProvider,
|
||||
Provider,
|
||||
ProviderArgs,
|
||||
)
|
||||
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
|
||||
|
||||
@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",
|
||||
}
|
||||
url = (
|
||||
f"{provider_args.provider_url}/models"
|
||||
if "/v1" in provider_args.provider_url
|
||||
else f"{provider_args.provider_url}/v1/models"
|
||||
)
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
response = await client.get(url, headers=headers)
|
||||
if response.status_code != 200:
|
||||
print(
|
||||
f"[{provider_args.provider_title}] 获取模型失败: {response.status_code}"
|
||||
)
|
||||
return []
|
||||
data = response.json()
|
||||
raw_models = data.get("data", [])
|
||||
model_ids = [m["id"] for m in raw_models]
|
||||
return sorted(model_ids)
|
||||
except httpx.RequestError as e:
|
||||
from kilostar.utils.error import RetryableError
|
||||
|
||||
print(f"[{provider_args.provider_title}] 网络请求异常: {e}")
|
||||
raise RetryableError(
|
||||
f"[{provider_args.provider_title}] 网络请求异常: {e}"
|
||||
) from e
|
||||
except Exception as e:
|
||||
print(f"[{provider_args.provider_title}] 解析模型列表时发生错误: {e}")
|
||||
return []
|
||||
|
||||
@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,
|
||||
provider_models=provider_models,
|
||||
provider_type="deepseek",
|
||||
)
|
||||
@@ -0,0 +1,94 @@
|
||||
# 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 kilostar.utils.retry import retry_on_retryable_error
|
||||
from kilostar.core.global_state_machine.model_provider.base_provider import (
|
||||
BaseProvider,
|
||||
Provider,
|
||||
ProviderArgs,
|
||||
)
|
||||
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
|
||||
|
||||
@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",
|
||||
}
|
||||
url = (
|
||||
f"{provider_args.provider_url}/models"
|
||||
if "/v1" in provider_args.provider_url
|
||||
else f"{provider_args.provider_url}/v1/models"
|
||||
)
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
response = await client.get(url, headers=headers)
|
||||
if response.status_code != 200:
|
||||
print(
|
||||
f"[{provider_args.provider_title}] 获取模型失败: {response.status_code}"
|
||||
)
|
||||
return []
|
||||
data = response.json()
|
||||
raw_models = data.get("data", [])
|
||||
model_ids = [m["id"] for m in raw_models]
|
||||
return sorted(model_ids)
|
||||
except httpx.RequestError as e:
|
||||
from kilostar.utils.error import RetryableError
|
||||
|
||||
print(f"[{provider_args.provider_title}] 网络请求异常: {e}")
|
||||
raise RetryableError(
|
||||
f"[{provider_args.provider_title}] 网络请求异常: {e}"
|
||||
) from e
|
||||
except Exception as e:
|
||||
print(f"[{provider_args.provider_title}] 解析模型列表时发生错误: {e}")
|
||||
return []
|
||||
|
||||
@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,
|
||||
provider_models=provider_models,
|
||||
provider_type="openai",
|
||||
)
|
||||
@@ -0,0 +1,135 @@
|
||||
# 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 kilostar.core.global_state_machine.model_provider import (
|
||||
Provider,
|
||||
OpenAIProvider,
|
||||
ClaudeProvider,
|
||||
DeepseekProvider,
|
||||
)
|
||||
from typing import Dict, Type
|
||||
|
||||
|
||||
class ProviderManager:
|
||||
"""
|
||||
模型供应商管理器 (ProviderManager)。
|
||||
负责维护不同的 LLM 协议适配器,提供从配置注册到 Agent 实例化的全生命周期管理。
|
||||
"""
|
||||
|
||||
# --- 类属性显式标注 (IDE 友好) ---
|
||||
provider_mapper: Dict[str, Type[Provider]]
|
||||
"""协议映射表:键为协议名(如 'openai'),值为对应的 Provider 类。"""
|
||||
|
||||
provider_register: Dict[str, Provider]
|
||||
"""供应商注册表:键为用户自定义别名,值为已实例化的 Provider 对象。"""
|
||||
|
||||
def __init__(self, postgres):
|
||||
self.provider_mapper = {
|
||||
"openai": OpenAIProvider,
|
||||
"claude": ClaudeProvider,
|
||||
"deepseek": DeepseekProvider,
|
||||
}
|
||||
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 kilostar.core.global_state_machine.model_provider import ProviderArgs
|
||||
from kilostar.utils.logger import get_logger
|
||||
|
||||
logger = get_logger("provider_manager")
|
||||
import httpx
|
||||
|
||||
provider_args: ProviderArgs = ProviderArgs(
|
||||
provider_title=provider_title,
|
||||
provider_url=provider_url,
|
||||
provider_apikey=provider_apikey,
|
||||
provider_owner=provider_owner,
|
||||
)
|
||||
try:
|
||||
import ulid
|
||||
|
||||
provider_class = self.provider_mapper.get(provider_type, None)
|
||||
if provider_class is None:
|
||||
logger.warning(f"Provider type {provider_type} is not supported.")
|
||||
return None
|
||||
provider: Provider = await provider_class.create_provider(provider_args)
|
||||
provider.provider_owner = provider_owner
|
||||
self.provider_register[provider_title] = provider
|
||||
await postgres_database.add_provider_db.remote(
|
||||
provider_id=str(ulid.ULID()),
|
||||
provider_title=provider.provider_title,
|
||||
provider_url=provider.provider_url,
|
||||
provider_apikey=provider.provider_apikey,
|
||||
provider_models=provider.provider_models,
|
||||
provider_type=provider.provider_type,
|
||||
provider_owner=provider.provider_owner,
|
||||
)
|
||||
|
||||
logger.info(f"已添加适配器{provider_title}")
|
||||
except httpx.RequestError as e:
|
||||
from kilostar.utils.error import RetryableError
|
||||
|
||||
logger.warning(f"[{provider_args.provider_title}] 网络请求异常: {e}")
|
||||
raise RetryableError(
|
||||
f"[{provider_args.provider_title}] 网络请求异常: {e}"
|
||||
) from e
|
||||
except Exception as e:
|
||||
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
|
||||
)
|
||||
del self.provider_register[provider_title]
|
||||
@@ -0,0 +1,89 @@
|
||||
# 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 Tuple, Dict
|
||||
from collections import defaultdict
|
||||
import pathlib
|
||||
import json
|
||||
|
||||
|
||||
class GlobalSkillManager:
|
||||
"""GlobalSkillManager 核心组件类。
|
||||
这是一个管理器类,职责集中在维护整个系统内有关 GlobalSkill 资源的全局生命周期。它提供了注册机制、状态同步以及跨组件的统一查询入口,确保系统中该类型资源的实例一致性与可控性。"""
|
||||
|
||||
skill_mapper = Dict[str, Tuple[str]]
|
||||
"""skill的存储表"""
|
||||
|
||||
def __init__(self):
|
||||
self.skill_mapper = defaultdict(tuple)
|
||||
|
||||
import os
|
||||
|
||||
skill_plugin_dir = pathlib.Path(
|
||||
os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..", "..", "plugin", "skill")
|
||||
)
|
||||
)
|
||||
if not skill_plugin_dir.exists() or not skill_plugin_dir.is_dir():
|
||||
return
|
||||
for item in skill_plugin_dir.iterdir():
|
||||
if item.is_dir() and not item.name.startswith((".", "__")):
|
||||
json_path = item / "skill.json" # 拼接文件路径
|
||||
if json_path.exists():
|
||||
try:
|
||||
with open(json_path, "r", encoding="utf-8") as f:
|
||||
skill = json.load(f)
|
||||
# 提取并映射
|
||||
name = skill.get("name")
|
||||
if name:
|
||||
self.skill_mapper[name] = (
|
||||
skill.get("description", ""),
|
||||
skill.get("instructions", ""),
|
||||
)
|
||||
except (json.JSONDecodeError, OSError) as e:
|
||||
print(f"警告: 加载插件 {item.name} 失败: {e}")
|
||||
|
||||
def add_skill(self, skill_name: str) -> None:
|
||||
"""Add a skill to the manager by reading its skill.json from the path"""
|
||||
import os
|
||||
|
||||
skill_plugin_dir = pathlib.Path(
|
||||
os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..", "..", "plugin", "skill")
|
||||
)
|
||||
)
|
||||
item = skill_plugin_dir / skill_name
|
||||
if item.is_dir() and not item.name.startswith((".", "__")):
|
||||
json_path = item / "skill.json"
|
||||
if json_path.exists():
|
||||
try:
|
||||
with open(json_path, "r", encoding="utf-8") as f:
|
||||
skill = json.load(f)
|
||||
name = skill.get("name")
|
||||
if name:
|
||||
self.skill_mapper[name] = (
|
||||
skill.get("description", ""),
|
||||
skill.get("instructions", ""),
|
||||
)
|
||||
except (json.JSONDecodeError, OSError) as e:
|
||||
print(f"警告: 加载插件 {item.name} 失败: {e}")
|
||||
|
||||
def get_skill_list(self) -> dict:
|
||||
"""Return all skills currently loaded."""
|
||||
return self.skill_mapper
|
||||
|
||||
def remove_skill(self, skill_name: str) -> None:
|
||||
"""Remove a skill from the manager mapping."""
|
||||
if skill_name in self.skill_mapper:
|
||||
del self.skill_mapper[skill_name]
|
||||
@@ -0,0 +1,59 @@
|
||||
# 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 pathlib
|
||||
import importlib
|
||||
import inspect
|
||||
from collections import defaultdict
|
||||
from kilostar.plugin.tool_plugin.base_tool import BaseToolData
|
||||
from typing import Dict, Type
|
||||
from kilostar.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):
|
||||
self.tool_mapper = defaultdict(dict)
|
||||
|
||||
tool_plugin_dir = (
|
||||
pathlib.Path(__file__).parent.parent.parent / "plugin" / "tool_plugin"
|
||||
)
|
||||
if not tool_plugin_dir.exists() or not tool_plugin_dir.is_dir():
|
||||
return
|
||||
|
||||
for item in tool_plugin_dir.iterdir():
|
||||
if item.is_dir() and not item.name.startswith("__"):
|
||||
plugin_name = item.name
|
||||
module_name = f"kilostar.plugin.tool_plugin.{plugin_name}"
|
||||
|
||||
try:
|
||||
module = importlib.import_module(module_name)
|
||||
for name, obj in inspect.getmembers(module, inspect.isclass):
|
||||
if issubclass(obj, BaseToolData) and obj is not BaseToolData:
|
||||
# It's a valid tool class
|
||||
action_scopes = obj.model_fields.get("action_scope").default
|
||||
|
||||
if not action_scopes:
|
||||
self.tool_mapper["default"][plugin_name] = obj
|
||||
else:
|
||||
for scope in action_scopes:
|
||||
self.tool_mapper[scope][plugin_name] = obj
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to load tool plugin {plugin_name}: {e}")
|
||||
@@ -0,0 +1,5 @@
|
||||
from kilostar.core.global_workflow_manager.global_workflow_manager import (
|
||||
GlobalWorkflowManager,
|
||||
)
|
||||
|
||||
__all__ = ["GlobalWorkflowManager"]
|
||||
@@ -0,0 +1,212 @@
|
||||
import ray
|
||||
import asyncio
|
||||
from typing import Dict
|
||||
from kilostar.api.platform.event import kilostarEvent
|
||||
from kilostar.core.workflow_running_engine.workflow import kilostarWorkflow
|
||||
from kilostar.utils.ray_hook import ray_actor_hook
|
||||
from kilostar.utils.logger import get_logger
|
||||
|
||||
|
||||
@ray.remote
|
||||
class GlobalWorkflowManager:
|
||||
def __init__(self):
|
||||
self.event_dict: Dict[str, kilostarEvent] = {}
|
||||
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 = kilostarEvent.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: kilostarEvent):
|
||||
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: kilostarEvent) -> 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) -> kilostarEvent | 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 kilostarEvent.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 = kilostarEvent.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: kilostarWorkflow) -> 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) -> kilostarWorkflow | 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 = kilostarEvent.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 ""
|
||||
@@ -0,0 +1,14 @@
|
||||
# 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.
|
||||
|
||||
@@ -0,0 +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 .consciousness_node import ConsciousnessNode
|
||||
|
||||
__all__ = ["ConsciousnessNode"]
|
||||
@@ -0,0 +1,222 @@
|
||||
# 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 Union, overload
|
||||
from kilostar.core.individual.consciousness_node.template import (
|
||||
ConsciousnessNodeDeps,
|
||||
ForregulatoryNode,
|
||||
ForWorkflow,
|
||||
ForWorkflowEngine,
|
||||
ForWorkflowInput,
|
||||
ForregulatoryInput,
|
||||
ForWorkflowEngineInput,
|
||||
)
|
||||
from pydantic_ai import Agent, RunContext
|
||||
from kilostar.core.global_state_machine.global_state_machine import GlobalStateMachine
|
||||
from kilostar.core.global_state_machine.model_provider.base_provider import Provider
|
||||
from kilostar.adapter.model_adapter.agent_factory import AgentFactory
|
||||
|
||||
|
||||
@ray.remote
|
||||
class ConsciousnessNode:
|
||||
"""ConsciousnessNode 核心组件类。
|
||||
这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
from kilostar.utils.logger import get_logger
|
||||
|
||||
self.logger = get_logger("consciousness_node")
|
||||
self.agent: None | Agent = None
|
||||
|
||||
async def create_agent(
|
||||
self,
|
||||
global_state_machine: GlobalStateMachine,
|
||||
provider_title: str,
|
||||
model_id: str,
|
||||
tools_list: list[str] = None,
|
||||
) -> None:
|
||||
"""
|
||||
create_agent方法,将agent对象装配到ConsciousnessNode的属性内
|
||||
该方法通过provider_title从global_state_machine中获取provider对象,然后从provider对象中取出供应商形象,装配为pydantic_ai的
|
||||
Agent实例,
|
||||
并挂载到self.agent属性
|
||||
Args:
|
||||
global_state_machine: 全局状态机
|
||||
provider_title: 供应商名
|
||||
model_id: 模型id
|
||||
|
||||
Returns:
|
||||
无返回
|
||||
"""
|
||||
system_prompt: str = (
|
||||
"你叫kilostar,是一个多智能体AI助手系统中的【意识节点 (Consciousness Node)】。\n"
|
||||
"你是系统的'高级规划师'和'架构师',负责处理监控节点分配过来的复杂任务。\n"
|
||||
"你的主要工作场景包括:\n"
|
||||
"1. 拆解任务 (Workflow Generation):结合用户的原始命令和提供的模板,生成严谨、可执行的工作流 (kilostarWorkflow),并将其输出为 ForWorkflowEngine 格式。拆解时步骤应清晰连贯。\n"
|
||||
"2. 中途指导 (Workflow Execution):在工作流执行中,如果某一步骤指派给你,你需要对控制节点的结果进行分析或提供下一步的指导,输出 ForWorkflow 格式。\n"
|
||||
"3. 总结报告 (regulatory Report):在整个工作流执行完毕后,你需要对整体流程、各个控制节点的执行情况进行审查,并生成一份技术性的总结报告,输出 ForregulatoryNode 格式。\n"
|
||||
"请确保所有的思考和生成过程符合逻辑,严密且高质量。"
|
||||
)
|
||||
output_type = Union[ForregulatoryNode, ForWorkflow, ForWorkflowEngine]
|
||||
from kilostar.utils.get_tool import load_tools_from_list
|
||||
|
||||
provider: Provider = await global_state_machine.get_provider.remote(
|
||||
provider_title
|
||||
)
|
||||
agent_factory = AgentFactory()
|
||||
|
||||
callables = load_tools_from_list(tools_list)
|
||||
self.agent = agent_factory.create_agent(
|
||||
provider=provider,
|
||||
model_id=model_id,
|
||||
output_type=output_type,
|
||||
system_prompt=system_prompt,
|
||||
deps_type=ConsciousnessNodeDeps,
|
||||
agent_name="consciousness_node",
|
||||
tools=callables,
|
||||
)
|
||||
|
||||
@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"
|
||||
f"- 当前指令 (Command): {ctx.deps.command}\n"
|
||||
f"- 原始用户命令 (Original Command): {ctx.deps.original_command}\n"
|
||||
)
|
||||
if ctx.deps.available_skills:
|
||||
prompt += "\n=== 当前可用 Skill Individual ===\n"
|
||||
prompt += "你可以直接将以下 Skill Individual 安排进工作流的步骤中(设置 node 为 skill_individual,并将 agent_id 设置为对应 Skill Individual 的真实 agent_id,不要用名称!),作为可调用的工具。\n"
|
||||
for skill in ctx.deps.available_skills:
|
||||
prompt += f"- 真实 agent_id: {skill.get('agent_id')}\n 名称: {skill['name']}\n 描述: {skill['description']}\n"
|
||||
|
||||
return prompt
|
||||
|
||||
async def working(
|
||||
self,
|
||||
payload: Union[ForWorkflowEngineInput, ForWorkflowInput, ForregulatoryInput],
|
||||
) -> Union[ForWorkflowEngine, ForWorkflow, ForregulatoryNode, None]:
|
||||
"""执行与 working 相关的核心业务流转操作。
|
||||
该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。
|
||||
Args: payload (Union[ForWorkflowEngineInput, ForWorkflowInput, ForregulatoryInput]): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。
|
||||
Returns: (Union[ForWorkflowEngine, ForWorkflow, ForregulatoryNode, None]): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。"""
|
||||
try:
|
||||
result = await self._run(payload)
|
||||
if isinstance(result, (ForWorkflowEngine, ForWorkflow, ForregulatoryNode)):
|
||||
return result
|
||||
else:
|
||||
self.logger.error(
|
||||
f"ConsciousnessNode: 未知或不匹配的返回类型: {type(result)}"
|
||||
)
|
||||
return None
|
||||
except Exception:
|
||||
self.logger.exception("ConsciousnessNode在执行working时发生严重错误")
|
||||
return None
|
||||
|
||||
@overload
|
||||
async def _run(self, payload: ForWorkflowEngineInput) -> ForWorkflowEngine:
|
||||
"""
|
||||
_run方法
|
||||
该分支应当在regulatory_node简单处理用户命令后,工作流创建前调用!
|
||||
Args:
|
||||
payload: 应当包含原始命令和可用技能等信息
|
||||
|
||||
Returns:
|
||||
ForWorkflowEngine对象,将被放到全局状态机后丢入WorkflowEngine的异步队列
|
||||
"""
|
||||
pass
|
||||
|
||||
@overload
|
||||
async def _run(self, payload: ForWorkflow) -> ForWorkflow:
|
||||
"""
|
||||
_run方法
|
||||
该分支应当在workflow运行时,由WorkflowEngine进行调用!
|
||||
Args:
|
||||
payload: 应当包含workflow中的WorkStep对象
|
||||
|
||||
Returns:
|
||||
ForWorkflow对象,作为ConsciousnessNode执行Workflow中的WorkStep的结果
|
||||
"""
|
||||
pass
|
||||
|
||||
@overload
|
||||
async def _run(self, payload: ForregulatoryInput) -> ForregulatoryNode:
|
||||
"""
|
||||
_run方法
|
||||
该分支应当在workflow运行完全结束后,由WorkflowEngine进行调用!
|
||||
Args:
|
||||
payload: 应当包含整个Workflow的情况
|
||||
|
||||
Returns:
|
||||
Forregulatory对象,作为ConsciousnessNode对于全工作流的技术性总结,返回给regulatoryNode
|
||||
"""
|
||||
pass
|
||||
|
||||
async def _run(
|
||||
self,
|
||||
payload: Union[ForregulatoryInput, ForWorkflowInput, ForWorkflowEngineInput],
|
||||
) -> Union[ForregulatoryNode, ForWorkflow, ForWorkflowEngine]:
|
||||
"""执行与 run 相关的核心业务流转操作。
|
||||
该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。
|
||||
Args: payload (Union[ForregulatoryInput, ForWorkflowInput, ForWorkflowEngineInput]): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。
|
||||
Returns: (Union[ForregulatoryNode, ForWorkflow, ForWorkflowEngine]): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。"""
|
||||
try:
|
||||
self.agent.retries = 3
|
||||
if isinstance(payload, ForWorkflowEngineInput):
|
||||
deps = ConsciousnessNodeDeps(
|
||||
original_command=payload.original_command,
|
||||
command="自主分析并拆解原始命令,生成严密可执行的工作流",
|
||||
available_skills=payload.available_skills,
|
||||
)
|
||||
self.logger.debug("ConsciousnessNode: 开始生成工作流 (原生重试开启)")
|
||||
prompt = "根据original_command制定严密的可执行workflow"
|
||||
result = await self.agent.run(prompt, deps=deps)
|
||||
return result.output
|
||||
|
||||
elif isinstance(payload, ForWorkflowInput):
|
||||
deps = ConsciousnessNodeDeps(
|
||||
original_command=payload.original_command,
|
||||
command="完成workflow step中分配给意识节点的特定任务或指导",
|
||||
)
|
||||
self.logger.debug(
|
||||
"ConsciousnessNode: 开始处理工作流节点任务 (原生重试开启)"
|
||||
)
|
||||
result = await self.agent.run(
|
||||
f"处理此工作流步骤信息:\n{payload.workflow_step.model_dump_json()}",
|
||||
deps=deps,
|
||||
)
|
||||
return result.output
|
||||
|
||||
elif isinstance(payload, ForregulatoryInput):
|
||||
deps = ConsciousnessNodeDeps(
|
||||
original_command=payload.original_command,
|
||||
command="对于工作流整体执行结果进行检查,并且生成一份专业的技术性总结报告",
|
||||
)
|
||||
self.logger.debug(
|
||||
"ConsciousnessNode: 开始生成技术总结报告 (原生重试开启)"
|
||||
)
|
||||
result = await self.agent.run(
|
||||
f"基于以下工作流的执行记录,生成技术报告:\n{payload.workflow.model_dump_json()}",
|
||||
deps=deps,
|
||||
)
|
||||
return result.output
|
||||
except Exception as e:
|
||||
self.logger.exception(f"ConsciousnessNode 模型生成最终失败: {str(e)}")
|
||||
raise RuntimeError(f"ConsciousnessNode 无法完成任务: {str(e)}") from e
|
||||
@@ -0,0 +1,88 @@
|
||||
# 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 kilostar.core.workflow_running_engine.workflow import kilostarWorkflow, WorkStep
|
||||
from kilostar.utils.agent_model import ResponseModel, DepsModel, InputModel
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
# 意识节点回复类
|
||||
class ConsciousnessNodeResponse(ResponseModel):
|
||||
"""Consciousness response model,是意识节点所有回复类型的父类"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ForWorkflowEngine(ConsciousnessNodeResponse):
|
||||
"""生成workflow并放入WorkflowEngine"""
|
||||
|
||||
workflow: kilostarWorkflow = Field(
|
||||
..., description="生成好的符合规范的完整工作流对象。"
|
||||
)
|
||||
reasoning: str = Field(..., description="生成此工作流的原因和思路简述。")
|
||||
|
||||
|
||||
class ForWorkflow(ConsciousnessNodeResponse):
|
||||
"""处理workflow中需要ConsciousnessNode的工作"""
|
||||
|
||||
output: str = Field(..., description="对当前工作流步骤的具体处理结果或指导意见。")
|
||||
|
||||
|
||||
class ForregulatoryNode(ConsciousnessNodeResponse):
|
||||
"""工作流完成后进行校验并返回给regulatoryNode"""
|
||||
|
||||
output: str = Field(
|
||||
..., description="为监控节点提供的全工作流执行情况的技术性总结报告。"
|
||||
)
|
||||
|
||||
|
||||
class ConsciousnessNodeDeps(DepsModel):
|
||||
"""ConsciousnessNodeDeps 核心组件类。
|
||||
这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。"""
|
||||
|
||||
original_command: str
|
||||
command: str
|
||||
available_skills: list[dict] | None = None
|
||||
|
||||
|
||||
class ConsciousnessNodeInput(InputModel):
|
||||
"""ConsciousnessNodeInput 核心组件类。
|
||||
这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ForWorkflowEngineInput(ConsciousnessNodeInput):
|
||||
"""ForWorkflowEngineInput 核心组件类。
|
||||
这是一个领域数据模型或功能封装类,承载了 ForWorkflowEngineInput 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。"""
|
||||
|
||||
original_command: str
|
||||
available_skills: list[dict] | None = None
|
||||
|
||||
|
||||
class ForWorkflowInput(ConsciousnessNodeInput):
|
||||
"""ForWorkflowInput 核心组件类。
|
||||
这是一个领域数据模型或功能封装类,承载了 ForWorkflowInput 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。"""
|
||||
|
||||
workflow_step: WorkStep
|
||||
original_command: str
|
||||
|
||||
|
||||
class ForregulatoryInput(ConsciousnessNodeInput):
|
||||
"""ForregulatoryInput 核心组件类。
|
||||
这是一个领域数据模型或功能封装类,承载了 ForregulatoryInput 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。"""
|
||||
|
||||
workflow: kilostarWorkflow
|
||||
original_command: str
|
||||
@@ -0,0 +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 .control_node import ControlNode
|
||||
|
||||
__all__ = ["ControlNode"]
|
||||
@@ -0,0 +1,134 @@
|
||||
# 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 pydantic_ai import Agent, RunContext
|
||||
from kilostar.core.global_state_machine.global_state_machine import GlobalStateMachine
|
||||
from kilostar.core.global_state_machine.model_provider.base_provider import Provider
|
||||
from kilostar.adapter.model_adapter.agent_factory import AgentFactory
|
||||
from kilostar.core.individual.control_node.template import (
|
||||
ForWorkflow,
|
||||
ForWorkflowInput,
|
||||
ControlNodeDeps,
|
||||
)
|
||||
|
||||
|
||||
@ray.remote
|
||||
class ControlNode:
|
||||
"""ControlNode 核心组件类。
|
||||
这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。"""
|
||||
|
||||
def __init__(self):
|
||||
from kilostar.utils.logger import get_logger
|
||||
|
||||
self.logger = get_logger("control_node")
|
||||
self.agent: Agent | None = None
|
||||
|
||||
async def create_agent(
|
||||
self,
|
||||
global_state_machine: GlobalStateMachine,
|
||||
provider_title: str,
|
||||
model_id: str,
|
||||
tools_list: list[str] = None,
|
||||
) -> None:
|
||||
"""
|
||||
create_agent方法,将agent对象装配到Control的属性内
|
||||
该方法通过provider_title从global_state_machine中获取provider对象,然后从provider对象中取出供应商形象,装配为pydantic_ai的
|
||||
Agent实例,
|
||||
并挂载到self.agent属性
|
||||
Args:
|
||||
global_state_machine: 全局状态机
|
||||
provider_title: 供应商名
|
||||
model_id: 模型id
|
||||
|
||||
Returns:
|
||||
无返回
|
||||
"""
|
||||
system_prompt: str = (
|
||||
"你叫kilostar,是一个多智能体AI助手系统中的【控制节点 (Control Node)】。\n"
|
||||
"你是系统的'执行者'和'车间主任',专门负责执行工作流中分配给你的具体子任务。\n"
|
||||
"你的工作职责是:\n"
|
||||
"1. 仔细分析分配给你的工作流步骤 (workflow_step) 的目标和要求。\n"
|
||||
"2. 运用你被分配的工具 (如有) 或者依靠自身的知识和推理能力,精准、高效地完成该任务。\n"
|
||||
"3. 将执行的结果、产生的数据或者具体的输出,严格按照 ForWorkflow 格式返回。\n"
|
||||
"请注意:你的输出应当具体、实用,直接提供任务所要求的结果,不要做过多无关的寒暄。"
|
||||
)
|
||||
output_type = ForWorkflow
|
||||
from kilostar.utils.get_tool import load_tools_from_list
|
||||
|
||||
provider: Provider = await global_state_machine.get_provider.remote(
|
||||
provider_title
|
||||
)
|
||||
agent_factory = AgentFactory()
|
||||
|
||||
callables = load_tools_from_list(tools_list)
|
||||
self.agent = agent_factory.create_agent(
|
||||
provider=provider,
|
||||
model_id=model_id,
|
||||
output_type=output_type,
|
||||
system_prompt=system_prompt,
|
||||
deps_type=ControlNodeDeps,
|
||||
agent_name="control_node",
|
||||
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"
|
||||
f"- 步骤名称 (Name): {ctx.deps.workflow_step.name}\n"
|
||||
f"- 步骤目标/描述 (Description): {ctx.deps.workflow_step.desc}\n"
|
||||
f"- 前置输入(input): {ctx.deps.workflow_step.inputs}\n"
|
||||
)
|
||||
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
|
||||
except Exception:
|
||||
self.logger.exception("ControlNode在执行working时发生严重错误")
|
||||
return None
|
||||
|
||||
async def _run(self, payload: ForWorkflowInput) -> ForWorkflow:
|
||||
"""执行与 run 相关的核心业务流转操作。
|
||||
该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。
|
||||
Args: payload (ForWorkflowInput): 从客户端传递过来或由上游组件生成的核心业务数据体,通常需要进一步的清洗和结构化解析。
|
||||
Returns: (ForWorkflow): 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。"""
|
||||
try:
|
||||
self.agent.retries = 3
|
||||
deps = ControlNodeDeps(workflow_step=payload.workflow_step)
|
||||
self.logger.debug(
|
||||
f"ControlNode: 开始执行工作流节点 [{payload.workflow_step.name}] (原生重试开启)"
|
||||
)
|
||||
|
||||
result = await self.agent.run(
|
||||
f"请根据提供的 workflow_step 上下文,执行此步骤并输出结果。\n详细指令或附加数据:{payload.workflow_step.model_dump_json()}",
|
||||
deps=deps,
|
||||
)
|
||||
return result.output
|
||||
except Exception as e:
|
||||
self.logger.exception(
|
||||
f"ControlNode 在执行步骤 [{payload.workflow_step.name}] 时最终失败: {str(e)}"
|
||||
)
|
||||
raise RuntimeError(f"ControlNode 执行步骤失败: {str(e)}") from e
|
||||
@@ -0,0 +1,55 @@
|
||||
# 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 kilostar.core.workflow_running_engine.workflow import WorkStep
|
||||
from kilostar.utils.agent_model import ResponseModel, InputModel, DepsModel
|
||||
|
||||
|
||||
class ControlNodeResponse(ResponseModel):
|
||||
"""控制节点回复的基类"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
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
|
||||
@@ -0,0 +1,14 @@
|
||||
# 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.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# 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.
|
||||
|
||||
@@ -0,0 +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 .regulatory_node import regulatoryNode
|
||||
|
||||
__all__ = ["regulatoryNode"]
|
||||
@@ -0,0 +1,213 @@
|
||||
# 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 datetime
|
||||
import ray
|
||||
from typing import Union, overload
|
||||
from kilostar.api.platform.event import kilostarEvent
|
||||
from kilostar.adapter.model_adapter.agent_factory import AgentFactory
|
||||
from kilostar.core.global_state_machine.global_state_machine import GlobalStateMachine
|
||||
from kilostar.core.global_state_machine.model_provider import Provider
|
||||
from kilostar.core.individual.regulatory_node.template import (
|
||||
ForConsciousnessNode,
|
||||
ForUser,
|
||||
regulatoryNodeDeps,
|
||||
TerminationMessage,
|
||||
)
|
||||
from pydantic_ai import RunContext, Agent
|
||||
from kilostar.utils.ray_hook import ray_actor_hook
|
||||
|
||||
|
||||
@ray.remote
|
||||
class RegulatoryNode:
|
||||
"""regulatoryNode 核心组件类。
|
||||
这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
from kilostar.utils.logger import get_logger
|
||||
|
||||
self.logger = get_logger("regulatory_node")
|
||||
self.agent: None | Agent = None
|
||||
|
||||
async def create_agent(
|
||||
self,
|
||||
global_state_machine: GlobalStateMachine,
|
||||
provider_title: str,
|
||||
model_id: str,
|
||||
tools_list: list[str] = None,
|
||||
) -> None:
|
||||
"""
|
||||
create_agent方法,将agent对象装配到regulatoryNode的属性内
|
||||
该方法通过provider_title从global_state_machine中获取provider对象,然后从provider对象中取出供应商形象,装配为pydantic_ai的Agent实例,
|
||||
并挂载到self.agent属性
|
||||
Args:
|
||||
global_state_machine: 全局状态机
|
||||
provider_title: 供应商名
|
||||
model_id: 模型id
|
||||
|
||||
Returns:
|
||||
无返回
|
||||
"""
|
||||
system_prompt: str = (
|
||||
"你叫kilostar,是一个多智能体AI助手系统中的【监控节点 (regulatory Node)】。\n"
|
||||
"你是系统的'前台接待'和'大脑皮层',负责接收用户的初始请求或工作流的最终报告。\n"
|
||||
"你的核心职责是进行【意图识别与路由】。请仔细阅读用户的请求:\n"
|
||||
"1. 如果用户只是进行简单的问候、闲聊或查询非常基础的信息,请直接生成友好的回复,使用 ForUser 格式。\n"
|
||||
"2. 如果用户提出的是复杂任务(如需要编写代码、多步骤规划、数据处理等),请务必将其判定为需要工作流处理的任务,"
|
||||
" 并使用 ForConsciousnessNode 格式将其移交意识节点处理。\n"
|
||||
"3. 如果你收到的是 TerminationMessage(代表工作流已完成并生成了报告),请将报告内容转化为友好的面向用户的回复,使用 ForUser 格式。\n"
|
||||
"请保持冷静、专业,并严格遵循上述路由规则。"
|
||||
)
|
||||
output_type = Union[ForConsciousnessNode, ForUser]
|
||||
from kilostar.utils.get_tool import load_tools_from_list
|
||||
|
||||
provider: Provider = await global_state_machine.get_provider.remote(
|
||||
provider_title
|
||||
)
|
||||
agent_factory = AgentFactory()
|
||||
|
||||
callables = load_tools_from_list(tools_list)
|
||||
self.agent = agent_factory.create_agent(
|
||||
provider=provider,
|
||||
model_id=model_id,
|
||||
output_type=output_type,
|
||||
system_prompt=system_prompt,
|
||||
deps_type=regulatoryNodeDeps,
|
||||
agent_name="regulatory_node",
|
||||
tools=callables,
|
||||
)
|
||||
|
||||
@self.agent.system_prompt
|
||||
async def dynamic_prompt(ctx: RunContext[regulatoryNodeDeps]):
|
||||
"""执行与 dynamic prompt 相关的核心业务流转操作。
|
||||
该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。
|
||||
Args: ctx (RunContext[regulatoryNodeDeps]): 参与 dynamic prompt 逻辑运算或数据构建的上下文依赖对象。
|
||||
Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。"""
|
||||
prompt = system_prompt + "\n\n"
|
||||
prompt += (
|
||||
f"=== 当前上下文 ===\n"
|
||||
f"- 平台 (Platform): {ctx.deps.platform}\n"
|
||||
f"- 用户名 (User): {ctx.deps.user_name}\n"
|
||||
f"- 当前时间 (Time): {ctx.deps.time}\n"
|
||||
)
|
||||
# 修改 system_prompt 变量
|
||||
prompt += (
|
||||
"\n\n注意:你必须调用且只能调用一个函数(工具)来输出结果。"
|
||||
"如果你想直接回复用户,请调用 ForUser;"
|
||||
"如果你想移交给工作流,请调用 ForConsciousnessNode。"
|
||||
"严禁返回纯文本,必须使用工具格式!"
|
||||
)
|
||||
if ctx.deps.error_history:
|
||||
prompt += (
|
||||
f"\n=== 错误重试指示 ===\n"
|
||||
f"警告:前一次尝试失败,错误信息如下:\n{ctx.deps.error_history}\n"
|
||||
f"请务必修正该错误并按照要求的 Pydantic 格式输出。"
|
||||
)
|
||||
return prompt
|
||||
|
||||
###工作函数
|
||||
async def working(self, payload: Union[kilostarEvent, TerminationMessage]) -> str:
|
||||
"""
|
||||
working方法,是节点唯一的调用方法,对于_run函数的结果进行判断并实现最终回复
|
||||
Args:
|
||||
payload: 消息载荷,包含所有信息
|
||||
|
||||
Returns:
|
||||
str,监控节点对于用户的回复
|
||||
"""
|
||||
try:
|
||||
result = await self._run(payload)
|
||||
if isinstance(result, ForConsciousnessNode):
|
||||
self.logger.info("regulatoryNode: 任务已分配给工作流引擎处理")
|
||||
if isinstance(payload, kilostarEvent):
|
||||
try:
|
||||
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:
|
||||
self.logger.error(
|
||||
f"regulatoryNode: 无法将事件放入 WorkflowRunningEngine: {e}"
|
||||
)
|
||||
return "抱歉,任务提交失败,系统内部错误。"
|
||||
return f"任务已创建,准备创建工作流。原因:{result.reasoning}"
|
||||
elif isinstance(result, ForUser):
|
||||
self.logger.info("regulatoryNode: 直接向用户返回简单回复。")
|
||||
return result.context
|
||||
else:
|
||||
self.logger.error(f"regulatoryNode: 未知响应类型: {type(result)}")
|
||||
return "抱歉,系统内部遇到未知错误,无法正确处理您的请求。"
|
||||
except Exception:
|
||||
self.logger.exception("regulatoryNode在处理请求时发生未捕获的严重错误")
|
||||
return "抱歉,监控节点处理请求时发生严重错误,请联系管理员。"
|
||||
|
||||
@overload
|
||||
async def _run(self, payload: kilostarEvent) -> Union[ForConsciousnessNode, ForUser]:
|
||||
"""
|
||||
_run方法
|
||||
Args:
|
||||
payload: kilostarEvent的实例,是用户输入时对于消息的封装
|
||||
|
||||
Returns:
|
||||
ForUser对象,监控节点对于用户进行的简单回答
|
||||
ForConsciousnessNode对象,监控节点将用户的请求判断为复杂任务,将kilostarEvent传递给意识节点
|
||||
"""
|
||||
...
|
||||
|
||||
@overload
|
||||
async def _run(self, payload: TerminationMessage) -> ForUser:
|
||||
"""
|
||||
_run方法
|
||||
Args:
|
||||
payload: Termination的实例,是工作流结束后到达监控节点的最后结果
|
||||
|
||||
Returns:
|
||||
ForUser对象,工作流结束后给用户的返回
|
||||
"""
|
||||
...
|
||||
|
||||
async def _run(
|
||||
self, payload: Union[kilostarEvent, TerminationMessage]
|
||||
) -> Union[ForConsciousnessNode, ForUser]:
|
||||
"""
|
||||
_run方法,将payload转化为对llm发送的消息并发送
|
||||
Args:
|
||||
payload: 消息载荷
|
||||
|
||||
Returns:
|
||||
ForConsciousnessNode对象,对意识节点发送的消息
|
||||
ForUser对象,对用户发送到消息
|
||||
"""
|
||||
platform = payload.platform
|
||||
user_name = payload.user_name
|
||||
message = payload.message
|
||||
time_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
try:
|
||||
deps = regulatoryNodeDeps(
|
||||
platform=platform, user_name=user_name, time=time_str
|
||||
)
|
||||
self.logger.debug("regulatoryNode 开始生成 (启用原生 Pydantic-AI 重试)")
|
||||
prompt_message = message
|
||||
if isinstance(payload, TerminationMessage):
|
||||
prompt_message = f"【工作流执行结束报告】\n请将以下技术报告转化为对用户的友好回复:\n{message}"
|
||||
self.agent.retries = 3
|
||||
result = await self.agent.run(prompt_message, deps=deps)
|
||||
return result.output
|
||||
except Exception as e:
|
||||
self.logger.exception(f"regulatoryNode 模型生成或解析最终失败: {str(e)}")
|
||||
return ForUser(context="系统当前负载过高或遇到复杂内部错误,请稍后再试。")
|
||||
@@ -0,0 +1,61 @@
|
||||
# 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 kilostar.utils.agent_model import ResponseModel, DepsModel
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class regulatoryNodeResponse(ResponseModel):
|
||||
"""regulatoryNodeResponse 核心组件类。
|
||||
这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class ForUser(regulatoryNodeResponse):
|
||||
"""ForUser 核心组件类。
|
||||
这是一个领域数据模型或功能封装类,承载了 ForUser 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。"""
|
||||
|
||||
context: str = Field(
|
||||
...,
|
||||
description="对用户的回复,应当使用和蔼的语气进行回复。用于直接解答简单问题或返回最终报告。",
|
||||
)
|
||||
|
||||
|
||||
class ForConsciousnessNode(regulatoryNodeResponse):
|
||||
"""ForConsciousnessNode 核心组件类。
|
||||
这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。"""
|
||||
|
||||
reasoning: str = Field(..., description="选择将任务移交意识节点的简短原因。")
|
||||
|
||||
|
||||
class TerminationMessage(BaseModel):
|
||||
"""TerminationMessage 核心组件类。
|
||||
这是一个领域数据模型或功能封装类,承载了 TerminationMessage 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。"""
|
||||
|
||||
platform: str
|
||||
user_name: str
|
||||
message: str
|
||||
|
||||
|
||||
class regulatoryNodeDeps(DepsModel):
|
||||
"""regulatoryNodeDeps 核心组件类。
|
||||
这是一个系统执行节点类,作为多智能体架构中的独立处理单元。它能够接收工作流上下文,根据内置的大模型策略进行意图理解和自主决策,从而驱动特定阶段的任务闭环。"""
|
||||
|
||||
platform: str
|
||||
user_name: str
|
||||
time: str
|
||||
retry_count: int = 0
|
||||
error_history: str = ""
|
||||
@@ -0,0 +1,3 @@
|
||||
from kilostar.core.postgres_database.postgres import PostgresDatabase
|
||||
|
||||
__all__ = ["PostgresDatabase"]
|
||||
@@ -0,0 +1,51 @@
|
||||
# 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 sqlalchemy.exc import IntegrityError, OperationalError
|
||||
from pydantic import ValidationError
|
||||
from kilostar.utils.error import UserNotExistError
|
||||
|
||||
from kilostar.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:
|
||||
logger.error(f"对象校验失败:{e}")
|
||||
raise e
|
||||
except IntegrityError as e:
|
||||
logger.error(f"数据库完整性错误 (如重复记录): {e}")
|
||||
raise e
|
||||
except OperationalError as e:
|
||||
logger.error(f"数据库连接异常: {e}")
|
||||
raise e
|
||||
except UserNotExistError as e:
|
||||
logger.error(f"更改密码失败,用户不存在:{e}")
|
||||
except Exception as e:
|
||||
logger.exception(f"未预期的数据库错误: {e}")
|
||||
raise e
|
||||
|
||||
return wrapper
|
||||
@@ -0,0 +1,19 @@
|
||||
# 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 kilostar.core.postgres_database.model.user import User
|
||||
from kilostar.core.postgres_database.model.provider import Provider
|
||||
from kilostar.core.postgres_database.model.individual import WorkerIndividual
|
||||
|
||||
__all__ = ["User", "Provider", "WorkerIndividual"]
|
||||
@@ -0,0 +1,19 @@
|
||||
# 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 sqlalchemy.ext.asyncio import AsyncAttrs
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
|
||||
class BaseDataModel(DeclarativeBase, AsyncAttrs):
|
||||
pass
|
||||
@@ -0,0 +1,29 @@
|
||||
# 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 Literal
|
||||
|
||||
from .base import BaseDataModel
|
||||
from sqlalchemy.orm import Mapped
|
||||
|
||||
class ChatHistoryMessage(BaseDataModel):
|
||||
__tablename__ = "chat_history_massage"
|
||||
message_id: Mapped[str]
|
||||
message: Mapped[str]
|
||||
message_owner: Literal["user","regulatory_node"]
|
||||
|
||||
class ChatHistoryRegister(BaseDataModel):
|
||||
__tablename__ = "chat_history_register"
|
||||
chat_id: Mapped[str]
|
||||
user_id: Mapped[str]
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
# 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 enum import Enum
|
||||
from typing import List, Optional, Dict, Any
|
||||
from sqlalchemy import String, Text, text, ForeignKey
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from .base import BaseDataModel
|
||||
|
||||
|
||||
class ModalityType(str, Enum):
|
||||
TEXT = "text"
|
||||
VISION = "vision"
|
||||
AUDIO = "audio"
|
||||
MULTIMODAL = "multimodal"
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 1. 通用基类表 (身份中心)
|
||||
# ==========================================
|
||||
class BaseIndividualModel(BaseDataModel):
|
||||
__tablename__ = "base_individual"
|
||||
|
||||
agent_id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
||||
agent_name: Mapped[str] = mapped_column(String(100), index=True, nullable=False)
|
||||
description: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
system_prompt: Mapped[Optional[str]] = mapped_column(Text)
|
||||
provider_title: Mapped[str] = mapped_column(String(50))
|
||||
model_id: Mapped[str] = mapped_column(String(100))
|
||||
owner_id: Mapped[str] = mapped_column(String(64), index=True)
|
||||
|
||||
agent_type: Mapped[str] = mapped_column(String(32))
|
||||
|
||||
__mapper_args__ = {
|
||||
"polymorphic_on": "agent_type",
|
||||
"polymorphic_identity": "base"
|
||||
}
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 2. 专家子个体 (技能与复杂工作流)
|
||||
# ==========================================
|
||||
class SpecialistIndividualModel(BaseIndividualModel):
|
||||
__tablename__ = "specialist_individual"
|
||||
|
||||
agent_id: Mapped[str] = mapped_column(
|
||||
ForeignKey("base_individual.agent_id", ondelete="CASCADE"),
|
||||
primary_key=True
|
||||
)
|
||||
bound_skill: Mapped[Optional[Dict[str, Any]]] = mapped_column(JSONB)
|
||||
workspace: Mapped[Optional[List[str]]] = mapped_column(JSONB)
|
||||
tools: Mapped[Optional[List[str]]] = mapped_column(
|
||||
JSONB, default=list, server_default=text("'[]'::jsonb")
|
||||
)
|
||||
|
||||
# 逻辑关联:作为管理者,管理下属个体
|
||||
sub_ordinary_agents: Mapped[List["OrdinaryIndividualModel"]] = relationship(
|
||||
back_populates="manager",
|
||||
cascade="all, delete-orphan",
|
||||
foreign_keys="[OrdinaryIndividualModel.manager_id]"
|
||||
)
|
||||
sub_special_agents: Mapped[List["SpecialIndividualModel"]] = relationship(
|
||||
back_populates="manager",
|
||||
cascade="all, delete-orphan",
|
||||
foreign_keys="[SpecialIndividualModel.manager_id]"
|
||||
)
|
||||
|
||||
__mapper_args__ = {
|
||||
"polymorphic_identity": "specialist",
|
||||
}
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 3. 基础子个体 (普通微调模型)
|
||||
# ==========================================
|
||||
class OrdinaryIndividualModel(BaseIndividualModel):
|
||||
__tablename__ = "ordinary_individual"
|
||||
|
||||
agent_id: Mapped[str] = mapped_column(
|
||||
ForeignKey("base_individual.agent_id", ondelete="CASCADE"),
|
||||
primary_key=True
|
||||
)
|
||||
finetuned_from: Mapped[Optional[str]] = mapped_column(String(100))
|
||||
tools: Mapped[Optional[List[str]]] = mapped_column(
|
||||
JSONB, default=list, server_default=text("'[]'::jsonb")
|
||||
)
|
||||
|
||||
# 【修复1】:必须显式定义物理外键
|
||||
manager_id: Mapped[Optional[str]] = mapped_column(
|
||||
ForeignKey("specialist_individual.agent_id", ondelete="SET NULL")
|
||||
)
|
||||
|
||||
# 逻辑关联:指向上级专家
|
||||
manager: Mapped[Optional["SpecialistIndividualModel"]] = relationship(
|
||||
back_populates="sub_ordinary_agents",
|
||||
foreign_keys=[manager_id] # 显式指定使用 manager_id 解析关系
|
||||
)
|
||||
|
||||
__mapper_args__ = {
|
||||
"polymorphic_identity": "ordinary",
|
||||
}
|
||||
|
||||
|
||||
# ==========================================
|
||||
# 4. 特殊子个体 (多模态)
|
||||
# ==========================================
|
||||
class SpecialIndividualModel(BaseIndividualModel):
|
||||
__tablename__ = "special_individual"
|
||||
|
||||
agent_id: Mapped[str] = mapped_column(
|
||||
ForeignKey("base_individual.agent_id", ondelete="CASCADE"),
|
||||
primary_key=True
|
||||
)
|
||||
modality_type: Mapped[ModalityType] = mapped_column(
|
||||
default=ModalityType.MULTIMODAL,
|
||||
server_default=text("'multimodal'")
|
||||
)
|
||||
multimodal_config: Mapped[Optional[Dict[str, Any]]] = mapped_column(JSONB)
|
||||
|
||||
# 【修复1】:添加缺失的物理外键
|
||||
manager_id: Mapped[Optional[str]] = mapped_column(
|
||||
ForeignKey("specialist_individual.agent_id", ondelete="SET NULL")
|
||||
)
|
||||
|
||||
# 【修复2】:修正 back_populates 指向正确的变量名
|
||||
manager: Mapped[Optional["SpecialistIndividualModel"]] = relationship(
|
||||
back_populates="sub_special_agents",
|
||||
foreign_keys=[manager_id]
|
||||
)
|
||||
|
||||
__mapper_args__ = {
|
||||
"polymorphic_identity": "special",
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
# 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, Optional
|
||||
from sqlalchemy import String, Text, Boolean, text
|
||||
from sqlalchemy.dialects.postgresql import JSONB # 针对供应商模型列表优化
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from .base import BaseDataModel
|
||||
|
||||
|
||||
class ProviderModel(BaseDataModel):
|
||||
"""
|
||||
Provider 物理模型。
|
||||
作为模型/服务提供商适配器,标准化不同供应商(OpenAI, Anthropic 等)的配置。
|
||||
"""
|
||||
__tablename__ = "provider"
|
||||
provider_id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
||||
provider_title: Mapped[str] = mapped_column(String(100), index=True, nullable=False)
|
||||
provider_type: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
provider_url: Mapped[Optional[str]] = mapped_column(Text)
|
||||
provider_apikey: Mapped[Optional[str]] = mapped_column(Text)
|
||||
provider_models: Mapped[List[str]] = mapped_column(
|
||||
JSONB,
|
||||
default=list,
|
||||
server_default=text("'[]'::jsonb")
|
||||
)
|
||||
provider_owner: Mapped[str] = mapped_column(String(64), index=True)
|
||||
is_active: Mapped[bool] = mapped_column(
|
||||
Boolean,
|
||||
default=True,
|
||||
server_default=text("true"),
|
||||
comment="该服务商节点是否在线/启用"
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
# 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, Optional
|
||||
from sqlalchemy import String, Text
|
||||
from sqlalchemy.dialects.postgresql import JSONB # 针对 Postgres 优化,支持索引和高性能解析
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from .base import BaseDataModel
|
||||
|
||||
|
||||
class SystemNodeConfigModel(BaseDataModel):
|
||||
"""
|
||||
SystemNodeConfig 物理模型。
|
||||
作为 kilostar 架构中的独立处理单元,负责存储 LLM 节点的执行策略与工具配置。
|
||||
"""
|
||||
__tablename__ = "system_node_config"
|
||||
node_name: Mapped[str] = mapped_column(String(100), primary_key=True)
|
||||
provider_title: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
model_id: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
tools: Mapped[Optional[List[str]]] = mapped_column(
|
||||
JSONB,
|
||||
default=list,
|
||||
comment="节点可调用的工具标识列表"
|
||||
)
|
||||
@@ -0,0 +1,47 @@
|
||||
# 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 enum import IntEnum
|
||||
|
||||
from sqlalchemy import String, Integer, text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from .base import BaseDataModel
|
||||
|
||||
|
||||
class UserAuthority(IntEnum):
|
||||
"""
|
||||
权限枚举类
|
||||
"""
|
||||
SUPER_ADMINISTRATOR = 100
|
||||
ADMINISTRATOR = 50
|
||||
USER = 20
|
||||
UNAUTHORIZED_USER = 10
|
||||
GUEST = 0
|
||||
|
||||
|
||||
class User(BaseDataModel):
|
||||
"""
|
||||
数据库user表模型
|
||||
"""
|
||||
__tablename__ = "user"
|
||||
user_id: Mapped[str] = mapped_column(String(64), primary_key=True)
|
||||
user_name: Mapped[str] = mapped_column(String(100), index=True, nullable=False)
|
||||
hashed_password: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
user_authority: Mapped[UserAuthority] = mapped_column(
|
||||
Integer,
|
||||
default=UserAuthority.USER,
|
||||
server_default=text("20")
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
# 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
|
||||
|
||||
|
||||
class EventRecord(SQLModel, table=True):
|
||||
trace_id: str = Field(
|
||||
primary_key=True, description="The unique trace ID of the kilostarEvent"
|
||||
)
|
||||
event_data_json: str = Field(description="The JSON serialized kilostarEvent data")
|
||||
@@ -0,0 +1,14 @@
|
||||
# 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.
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
from sqlmodel import select
|
||||
from typing import List, Optional
|
||||
from kilostar.core.postgres_database.model.workflow 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
|
||||
@@ -0,0 +1,119 @@
|
||||
# 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 kilostar.core.postgres_database.model.individual import WorkerIndividual
|
||||
from sqlmodel import select
|
||||
from typing import List, Optional
|
||||
from kilostar.core.postgres_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)
|
||||
session.add(individual)
|
||||
await session.commit()
|
||||
await session.refresh(individual)
|
||||
return individual
|
||||
|
||||
@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)
|
||||
return results.scalar_one_or_none()
|
||||
|
||||
@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)
|
||||
return list(results.scalars().all())
|
||||
|
||||
@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)
|
||||
individual = results.scalar_one_or_none()
|
||||
if not individual:
|
||||
return None
|
||||
for key, value in kwargs.items():
|
||||
if value is not None:
|
||||
setattr(individual, key, value)
|
||||
session.add(individual)
|
||||
await session.commit()
|
||||
await session.refresh(individual)
|
||||
return individual
|
||||
|
||||
@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)
|
||||
individual = results.scalar_one_or_none()
|
||||
if not individual:
|
||||
return False
|
||||
session.delete(individual)
|
||||
await session.commit()
|
||||
return True
|
||||
|
||||
@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)
|
||||
return list(results.scalars().all())
|
||||
@@ -0,0 +1,87 @@
|
||||
# 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
|
||||
|
||||
from kilostar.core.postgres_database.model.provider import Provider
|
||||
from sqlmodel import select
|
||||
from kilostar.core.postgres_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)
|
||||
results = results.scalars().all()
|
||||
providers = [
|
||||
Provider(
|
||||
provider_title=provider.provider_title,
|
||||
provider_url=provider.provider_url,
|
||||
provider_apikey=provider.provider_apikey,
|
||||
provider_models=provider.provider_models,
|
||||
provider_type=provider.provider_type,
|
||||
)
|
||||
for provider in results
|
||||
]
|
||||
return providers
|
||||
|
||||
@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)
|
||||
await session.commit()
|
||||
|
||||
@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:
|
||||
session.delete(provider)
|
||||
await session.commit()
|
||||
|
||||
@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:
|
||||
for key, value in kwargs.items():
|
||||
setattr(provider, key, value)
|
||||
session.add(provider)
|
||||
await session.commit()
|
||||
await session.refresh(provider)
|
||||
return provider
|
||||
return None
|
||||
@@ -0,0 +1,86 @@
|
||||
# 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 kilostar.core.postgres_database.model.system_node import SystemNodeConfig
|
||||
from sqlmodel import select
|
||||
from typing import List, Optional
|
||||
from kilostar.core.postgres_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)
|
||||
config = results.scalar_one_or_none()
|
||||
if config:
|
||||
config.provider_title = provider_title
|
||||
config.model_id = model_id
|
||||
if tools is not None:
|
||||
config.tools = tools
|
||||
else:
|
||||
config = SystemNodeConfig(
|
||||
node_name=node_name,
|
||||
provider_title=provider_title,
|
||||
model_id=model_id,
|
||||
tools=tools,
|
||||
)
|
||||
session.add(config)
|
||||
await session.commit()
|
||||
await session.refresh(config)
|
||||
return config
|
||||
|
||||
@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)
|
||||
return list(results.scalars().all())
|
||||
|
||||
@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)
|
||||
return results.scalar_one_or_none()
|
||||
@@ -0,0 +1,169 @@
|
||||
# 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 kilostar.core.postgres_database.model.user import User
|
||||
from sqlmodel import select
|
||||
from kilostar.utils.error import UserNotExistError, UserPasswordError
|
||||
from kilostar.core.postgres_database.database_exception import database_exception
|
||||
from kilostar.core.postgres_database.model.user import UserAuthority
|
||||
from kilostar.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
|
||||
statement = select(User).limit(1)
|
||||
results = await session.execute(statement)
|
||||
existing_user = results.first()
|
||||
|
||||
authority = UserAuthority.USER
|
||||
if existing_user is None:
|
||||
authority = UserAuthority.SUPER_ADMINISTRATOR
|
||||
|
||||
user = User(
|
||||
user_id=str(ULID()),
|
||||
user_name=user_name,
|
||||
hashed_password=hashed_password,
|
||||
user_authority=authority,
|
||||
)
|
||||
session.add(user)
|
||||
await session.commit()
|
||||
await session.refresh(user)
|
||||
return user
|
||||
|
||||
@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)
|
||||
user = results.scalar_one_or_none()
|
||||
if user is None:
|
||||
raise UserNotExistError()
|
||||
if not Accessor.verify_password(old_password, user.hashed_password):
|
||||
raise UserPasswordError()
|
||||
user.hashed_password = new_password
|
||||
session.add(user)
|
||||
await session.commit()
|
||||
await session.refresh(user)
|
||||
return user
|
||||
|
||||
@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)
|
||||
user = results.scalar_one_or_none()
|
||||
if user is None:
|
||||
raise UserNotExistError()
|
||||
session.delete(user)
|
||||
await session.commit()
|
||||
|
||||
@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:
|
||||
raise UserNotExistError()
|
||||
session.delete(user)
|
||||
await session.commit()
|
||||
|
||||
@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)
|
||||
user = results.scalar_one_or_none()
|
||||
if user is None:
|
||||
raise UserNotExistError()
|
||||
return user
|
||||
|
||||
@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)
|
||||
users = results.scalars().all()
|
||||
return list(users)
|
||||
|
||||
@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:
|
||||
raise UserNotExistError()
|
||||
return user.user_authority
|
||||
|
||||
@database_exception
|
||||
async def change_user_authority(
|
||||
self, user_id: str, new_authority: UserAuthority
|
||||
) -> User:
|
||||
"""
|
||||
Changes the authority level of a specific user.
|
||||
|
||||
Args:
|
||||
user_id: The ID of the user whose authority is to be changed.
|
||||
new_authority: The new authority level to assign to the user.
|
||||
|
||||
Returns:
|
||||
User: The updated user object.
|
||||
|
||||
Raises:
|
||||
UserNotExistError: If the specified user does not exist.
|
||||
"""
|
||||
async with self.async_session_maker() as session:
|
||||
user = await session.get(User, user_id)
|
||||
if user is None:
|
||||
raise UserNotExistError()
|
||||
user.user_authority = new_authority
|
||||
session.add(user)
|
||||
await session.commit()
|
||||
await session.refresh(user)
|
||||
return user
|
||||
@@ -0,0 +1,256 @@
|
||||
# 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 os
|
||||
import asyncio
|
||||
|
||||
import ray
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlmodel import SQLModel
|
||||
|
||||
from .module.individual import IndividualDatabase
|
||||
from .module.event import EventDatabase
|
||||
from .module.user import AuthDatabase
|
||||
from .module.provider import ProviderDatabase
|
||||
from .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")
|
||||
host = os.environ.get("POSTGRES_HOST")
|
||||
port = os.environ.get("POSTGRES_PORT")
|
||||
database = os.environ.get("POSTGRES_DB")
|
||||
database_url = (
|
||||
f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{database}"
|
||||
)
|
||||
self.async_engine = create_async_engine(database_url, echo=True)
|
||||
self.async_session_maker = sessionmaker(
|
||||
self.async_engine, class_=AsyncSession, expire_on_commit=False
|
||||
)
|
||||
|
||||
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)
|
||||
except Exception as e:
|
||||
# Provide a warning if the database is not accessible, allowing
|
||||
# the app to start up for development/UI tests without crashing immediately.
|
||||
print(f"Warning: Failed to initialize PostgreSQL database: {e}")
|
||||
finally:
|
||||
self.ready_event.set()
|
||||
|
||||
# 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()
|
||||
|
||||
# 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)
|
||||
@@ -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.
|
||||
@@ -0,0 +1,69 @@
|
||||
# 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 BaseModel, Field
|
||||
from typing import Literal, Optional
|
||||
from enum import Enum
|
||||
|
||||
class LogicGate(BaseModel):
|
||||
"""
|
||||
LogicGate 类。
|
||||
跳转逻辑,标记该步骤运行成功或失败的动作
|
||||
"""
|
||||
if_fail: str = Field(..., description="失败跳转目标,如 'jump_to_step_1'")
|
||||
if_pass: Literal["continue", "exit"] = Field(default="continue", description="成功后的动作")
|
||||
|
||||
|
||||
class WorkflowMetadata(BaseModel):
|
||||
"""
|
||||
WorkflowMetadata类
|
||||
workflow的元数据类,保存与用户有关的数据
|
||||
"""
|
||||
user_id: Optional[str] = Field(default=None, description="创建工作流的用户的ulid")
|
||||
command: Optional[str] = Field(default=None, description="创建工作流的原始命令")
|
||||
|
||||
|
||||
class WorkStepStatus(str, Enum):
|
||||
"""
|
||||
WorkflowStepStatus 枚举类
|
||||
包含workflow step运行时的状态:
|
||||
PENDING: 等待工作
|
||||
WORKING: 工作中
|
||||
HANGUP: 挂起
|
||||
COMPLETED: 完成
|
||||
FAILED = 失败
|
||||
"""
|
||||
PENDING = "pending"
|
||||
WORKING = "working"
|
||||
HANGUP = "hang_up"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
|
||||
|
||||
class WorkflowStatus(str, Enum):
|
||||
"""
|
||||
WorkflowStatus 枚举类
|
||||
包含workflow运行时的状态:
|
||||
RUNNING = 运行中
|
||||
HANGUP = 挂起
|
||||
COMPLETED = 完成
|
||||
CREATING = 创建中
|
||||
PENDING = 等待中
|
||||
"""
|
||||
RUNNING = "running"
|
||||
HANGUP = "hang_up"
|
||||
COMPLETED = "completed"
|
||||
FAILED = "failed"
|
||||
CREATING = "creating"
|
||||
PENDING = "pending"
|
||||
@@ -0,0 +1,89 @@
|
||||
# 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 BaseModel, Field, model_validator
|
||||
from typing import Optional, Union, List, Dict, Any
|
||||
from .model import LogicGate, WorkflowMetadata, WorkStepStatus, WorkflowStatus
|
||||
from ulid import ULID
|
||||
from datetime import datetime
|
||||
|
||||
class WorkflowContext(BaseModel):
|
||||
"""
|
||||
WorkflowContext 类
|
||||
作为workflow运行时的数据部分,使得数据和计算分离
|
||||
"""
|
||||
trace_id: str = Field(description="工作流的trace_id")
|
||||
workflow_status: Dict[str, WorkflowStatus] = Field(default_factory=lambda: {datetime.now().strftime("%Y-%m-%d %H:%M:%S"):WorkflowStatus.CREATING} ,description="工作流状态")
|
||||
blackboard: Dict[str, Any] = Field(description="大模型输出的存储区")
|
||||
work_step_status: Optional[Dict[int, tuple[str, WorkStepStatus]]] = Field(default= None,description="工作流运行状态")
|
||||
"""work_step_status:字典,键为整个工作流的运行步骤,值为元组,包含两个字段:
|
||||
1.字符串,更新时间的字符串;2.WorkflowStatus枚举类,当前步骤的运行情况"""
|
||||
workflow_pointer: Optional[int] = Field(description="工作流指针,指向具体的workflow位置")
|
||||
workflow_log: List[Dict[int, tuple[str, WorkflowStatus, str]]] = Field(default=[], description="工作流运行日志")
|
||||
"""workflow_log:一个列表,内部元素为一个字典,键为步骤序号,值为一个元组,包含三个字段:
|
||||
1.字符串,更新时间的字符串;2.WorkflowStatus枚举类,当前步骤的运行情况;3.字符串,当前步骤运行完后的输出总结或失败原因"""
|
||||
|
||||
|
||||
class WorkflowStep(BaseModel):
|
||||
"""
|
||||
WorkflowStep 类
|
||||
workflow每一个步骤的模型,为workflow的最小执行单位
|
||||
"""
|
||||
step: int = Field(..., gt=0, description="步骤序号,严格自增")
|
||||
name: str = Field(..., description="步骤名称")
|
||||
action: str = Field(..., description="执行的原子动作")
|
||||
inputs: Optional[Union[str, List[str]]] = Field(default=None, description="前置依赖输出")
|
||||
outputs: Optional[str] = Field(default=None, description="当前步骤产出物变量名")
|
||||
agent_id: Optional[str] = Field(default=None,description="分配给 skill_individual 的 Skill Individual 真实 agent_id,不可用名称代替",)
|
||||
logic_gate: Optional[LogicGate] = Field(default=None, description="逻辑跳转控制")
|
||||
|
||||
|
||||
class KiloStarWorkflow(BaseModel):
|
||||
"""
|
||||
KiloStarWorkflow 类
|
||||
kilostar的workflow核心类,由consciousness_node创建
|
||||
"""
|
||||
trace_id: str = Field(default_factory=lambda: str(ULID()), description="系统自动生成的追溯ID")
|
||||
version: str = Field(default="v1.0", description="系统协议版本号")
|
||||
#-------------------
|
||||
title: str = Field(..., description="工作流标题")
|
||||
work_link: List[WorkflowStep] = Field(..., description="工作链")
|
||||
workflow_metadata: WorkflowMetadata
|
||||
|
||||
@model_validator(mode="after")
|
||||
def validate_workflow_integrity(self) -> "KiloStarWorkflow":
|
||||
"""
|
||||
执行与 validate workflow integrity 相关的核心业务流转操作。
|
||||
该方法保证了workflow中的work_step的序号为递增且跳转逻辑不会发生越界
|
||||
Returns:
|
||||
('KiloStarWorkflow'): 经过校验后的KiloStarWorkflow对象。"""
|
||||
steps = [s.step for s in self.work_link]
|
||||
expected = list(range(1, len(steps) + 1))
|
||||
if steps != expected:
|
||||
raise ValueError(f"工作链步数不连续!期望 {expected},实际 {steps}")
|
||||
|
||||
max_step = len(steps)
|
||||
for s in self.work_link:
|
||||
if s.logic_gate and "jump_to_step_" in s.logic_gate.if_fail:
|
||||
try:
|
||||
target = int(s.logic_gate.if_fail.split("_")[-1])
|
||||
if target > max_step or target < 1:
|
||||
raise ValueError(
|
||||
f"Step {s.step} 的跳转目标 Step {target} 越界了!"
|
||||
)
|
||||
except ValueError as e:
|
||||
if "越界" in str(e):
|
||||
raise e
|
||||
raise ValueError(f"LogicGate 格式错误: {s.logic_gate.if_fail}")
|
||||
return self
|
||||
@@ -0,0 +1,23 @@
|
||||
# workflow文档
|
||||
---
|
||||
- workflow(工作流)是作为kilostar中运行任务的基本单位,workflow_manager管理整个workflow模块,包括生成workflow_template(工作流模板),生成workflow对象,和保存整个workflow_template表。
|
||||
- workflow_template是一个工作流模板,旨在由专业人士教导LLM如何编写工作流并进行任务,每个workflow_template都应该保存在 **kilostar/workflow_pugin/** 文件夹下,保存格式为~_workflow_template.json,json格式为:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "",
|
||||
"desc": "",
|
||||
"work_link": [
|
||||
{
|
||||
"step": "",
|
||||
"node": "",
|
||||
"action": "",
|
||||
"desc": "",
|
||||
"input": [],
|
||||
"output": [],
|
||||
"logic_gate": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
- workflow_template将由监管节点挑选交给意识节点,意识节点按照参考模板生成标准的workflow对象,转交给pipeline开始执行任务链。
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +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 .approval import ApprovalToolData, approval
|
||||
|
||||
__all__ = ["ApprovalToolData", "approval"]
|
||||
@@ -0,0 +1,51 @@
|
||||
# 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 kilostar.plugin.tool_plugin.base_tool import BaseToolData
|
||||
from kilostar.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",
|
||||
"regulatory_node",
|
||||
"growth_node",
|
||||
"",
|
||||
"",
|
||||
]
|
||||
] = ["control_node", "consciousness_node"]
|
||||
config_args: Dict[str, str] = {}
|
||||
|
||||
|
||||
async def approval(message: str, trace_id: str) -> str:
|
||||
"""
|
||||
当任务存在某些高风险操作或者计划需要让用户审批,发送请求给用户等待用户审批
|
||||
Args:
|
||||
message: 发送给用户的请求
|
||||
trace_id:
|
||||
|
||||
Returns:
|
||||
用户的审批结果
|
||||
"""
|
||||
actor_list = ray_actor_hook("global_state_machine")
|
||||
await actor_list.global_state_machine.put_pending.remote(trace_id, message)
|
||||
reply = await actor_list.global_state_machine.get_received.remote(trace_id)
|
||||
return reply
|
||||
@@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
||||
@@ -0,0 +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.
|
||||
|
||||
from pydantic import BaseModel
|
||||
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",
|
||||
"regulatory_node",
|
||||
"growth_node",
|
||||
"",
|
||||
"",
|
||||
]
|
||||
] = []
|
||||
config_args: Dict[str, str] = {}
|
||||
@@ -0,0 +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"]
|
||||
@@ -0,0 +1,48 @@
|
||||
# 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_ai import RunContext
|
||||
from kilostar.plugin.tool_plugin.base_tool import BaseToolData
|
||||
import os
|
||||
|
||||
|
||||
class FileReaderData(BaseToolData):
|
||||
"""FileReaderData 核心组件类。
|
||||
这是一个领域数据模型或功能封装类,承载了 FileReaderData 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。"""
|
||||
|
||||
is_system: bool = True
|
||||
name: str = "file_reader"
|
||||
description: str = "读取本地文件的内容"
|
||||
|
||||
|
||||
def file_reader(ctx: RunContext, filepath: str) -> str:
|
||||
"""读取本地文件内容的工具。
|
||||
|
||||
Args:
|
||||
filepath: 目标文件的绝对路径或相对路径。
|
||||
|
||||
Returns:
|
||||
如果文件存在并可读,返回文件内容;否则返回错误信息。
|
||||
"""
|
||||
if not os.path.exists(filepath):
|
||||
return f"Error: 文件 {filepath} 不存在。"
|
||||
if not os.path.isfile(filepath):
|
||||
return f"Error: {filepath} 不是一个文件。"
|
||||
|
||||
try:
|
||||
with open(filepath, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
return content
|
||||
except Exception as e:
|
||||
return f"Error: 读取文件失败,原因:{str(e)}"
|
||||
@@ -0,0 +1,14 @@
|
||||
# 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.
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
# 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 jwt
|
||||
import os
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional
|
||||
from fastapi import HTTPException, status, Request
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from kilostar.core.postgres_database.model import User
|
||||
from pwdlib import PasswordHash
|
||||
|
||||
|
||||
class TokenData(BaseModel):
|
||||
"""TokenData 核心组件类。
|
||||
这是一个领域数据模型或功能封装类,承载了 TokenData 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。"""
|
||||
|
||||
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
|
||||
|
||||
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, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
return TokenData(**payload)
|
||||
except jwt.ExpiredSignatureError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Token 已过期",
|
||||
)
|
||||
except (jwt.InvalidTokenError, ValidationError):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="无效的认证凭证",
|
||||
)
|
||||
|
||||
@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())})
|
||||
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
|
||||
@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(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="未提供认证头部",
|
||||
)
|
||||
token = auth_header.split(" ")[1]
|
||||
return Accessor._decode_token(token)
|
||||
|
||||
@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,
|
||||
detail="用户不存在",
|
||||
)
|
||||
if not Accessor.verify_password(password, user.hashed_password):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="用户名或密码错误",
|
||||
)
|
||||
token_payload = {"user_id": str(user.user_id), "username": user.user_name}
|
||||
return Accessor._create_access_token(data=token_payload)
|
||||
|
||||
@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:
|
||||
raise ValueError("密码长度不能小于 6 位")
|
||||
return password_hasher.hash(password)
|
||||
@@ -0,0 +1,37 @@
|
||||
# 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 BaseModel
|
||||
|
||||
|
||||
class ResponseModel(BaseModel):
|
||||
"""ResponseModel 核心组件类。
|
||||
这是一个领域数据模型或功能封装类,承载了 ResponseModel 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class DepsModel(BaseModel):
|
||||
"""DepsModel 核心组件类。
|
||||
这是一个领域数据模型或功能封装类,承载了 DepsModel 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class InputModel(BaseModel):
|
||||
"""InputModel 核心组件类。
|
||||
这是一个领域数据模型或功能封装类,承载了 InputModel 相关的内聚属性定义与状态维护。它的存在隔离了局部的业务复杂性,并对外提供了类型安全的访问接口。"""
|
||||
|
||||
pass
|
||||
@@ -0,0 +1,44 @@
|
||||
# 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")
|
||||
kilostar_banner = """
|
||||
██████╗ ██████╗ ███████╗████████╗ ██████╗ ██████╗
|
||||
██╔══██╗██╔══██╗██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗
|
||||
██████╔╝██████╔╝█████╗ ██║ ██║ ██║██████╔╝
|
||||
██╔═══╝ ██╔══██╗██╔══╝ ██║ ██║ ██║██╔══██╗
|
||||
██║ ██║ ██║███████╗ ██║ ╚██████╔╝██║ ██║
|
||||
╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
|
||||
"""
|
||||
console = Console()
|
||||
banner_colored = Text(kilostar_banner, style="gold3 bold")
|
||||
console.print(banner_colored)
|
||||
console.print("=" * 40, style="dim") # dim=灰色,低调
|
||||
console.print("🚀 Multi-Agent Orchestration Platform", style="blue")
|
||||
console.print(f"📦 Version: {version}", style="green")
|
||||
console.print("👤 Author: zhaoxi826", style="yellow")
|
||||
console.print("📜 License: Apache 2.0", style="magenta")
|
||||
console.print("🐙 github: https://github.com/zhaoxi826/kilostar", style="yellow")
|
||||
console.print("=" * 40, style="dim")
|
||||
@@ -0,0 +1,69 @@
|
||||
# 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 Annotated
|
||||
from fastapi import Depends, HTTPException
|
||||
from kilostar.utils.access import Accessor, TokenData
|
||||
from kilostar.core.postgres_database.model import UserAuthority
|
||||
from kilostar.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 kilostar.utils.error import UserNotExistError
|
||||
|
||||
postgres_database = ray_actor_hook("postgres_database").postgres_database
|
||||
try:
|
||||
user_authority = await postgres_database.get_user_authority.remote(
|
||||
user_id=user_id
|
||||
)
|
||||
return user_authority
|
||||
except UserNotExistError:
|
||||
raise HTTPException(status_code=401, detail="用户不存在或已被删除,请重新登录")
|
||||
except Exception as e:
|
||||
# Check if it's a RayTaskError wrapping UserNotExistError
|
||||
if "UserNotExistError" in str(e):
|
||||
raise HTTPException(
|
||||
status_code=401, detail="用户不存在或已被删除,请重新登录"
|
||||
)
|
||||
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(
|
||||
status_code=403,
|
||||
detail={
|
||||
"message": f"User {token_data.user_id} does not have allowed roles"
|
||||
},
|
||||
)
|
||||
return token_data
|
||||
@@ -0,0 +1,88 @@
|
||||
# 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.
|
||||
|
||||
|
||||
class RetryableError(Exception):
|
||||
"""基类:所有可重试错误(如网络断开、抖动等临时性故障)"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
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
|
||||
@@ -0,0 +1,97 @@
|
||||
# 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 importlib.util
|
||||
import os
|
||||
import sys
|
||||
from typing import Callable, Dict, List
|
||||
|
||||
from kilostar.utils.logger import get_logger
|
||||
|
||||
logger = get_logger("get_tool")
|
||||
_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
|
||||
|
||||
app_root = "/app"
|
||||
tool_plugin_dir = os.path.join(
|
||||
app_root, "kilostar", "plugin", "tool_plugin", tool_name
|
||||
)
|
||||
|
||||
if not os.path.exists(tool_plugin_dir) or not os.path.isdir(tool_plugin_dir):
|
||||
logger.error(f"Tool directory not found: {tool_plugin_dir}")
|
||||
return None
|
||||
|
||||
init_file = os.path.join(tool_plugin_dir, "__init__.py")
|
||||
if not os.path.exists(init_file):
|
||||
logger.error(f"Tool init file not found: {init_file}")
|
||||
return None
|
||||
|
||||
try:
|
||||
module_name = f"kilostar.plugin.tool_plugin.{tool_name}"
|
||||
spec = importlib.util.spec_from_file_location(module_name, init_file)
|
||||
if spec is None or spec.loader is None:
|
||||
logger.error(f"Failed to create spec for {module_name}")
|
||||
return None
|
||||
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[module_name] = module
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
func = getattr(module, tool_name, None)
|
||||
|
||||
if not callable(func):
|
||||
logger.error(
|
||||
f"Tool function '{tool_name}' not found or not callable in {module_name}"
|
||||
)
|
||||
return None
|
||||
_tool_cache[tool_name] = func
|
||||
return func
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load module {module_name}: {e}")
|
||||
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 []
|
||||
|
||||
tool_list = []
|
||||
for tool_name in tool_names:
|
||||
tool_func = _get_tool_func(tool_name)
|
||||
if tool_func:
|
||||
tool_list.append(tool_func)
|
||||
|
||||
return tool_list
|
||||
@@ -0,0 +1,64 @@
|
||||
# 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 loguru import logger
|
||||
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", "")
|
||||
|
||||
trace_str = f" | trace_id:({trace_id})" if trace_id else ""
|
||||
return f"actor:({actor}){trace_str} : {record['message']}"
|
||||
|
||||
logger.configure(extra={"actor_name": "System", "trace_id": ""})
|
||||
|
||||
logger.add(
|
||||
RichHandler(
|
||||
rich_tracebacks=True,
|
||||
markup=True,
|
||||
show_time=False,
|
||||
show_level=False,
|
||||
show_path=False,
|
||||
),
|
||||
format=format_record,
|
||||
level="DEBUG",
|
||||
enqueue=True, # 异步记录
|
||||
)
|
||||
|
||||
return 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)
|
||||
@@ -0,0 +1,42 @@
|
||||
# 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 Type, TypeVar
|
||||
from pydantic import BaseModel
|
||||
|
||||
T = TypeVar("T", bound=Type[BaseModel])
|
||||
|
||||
|
||||
def pickle(cls: T) -> T:
|
||||
"""
|
||||
类装饰器pickle
|
||||
通过装饰继承了BaseModel的类,用pydantic的高效序列化替代python原生__reduce__魔术方法,实现ray在通讯时的高效序列化
|
||||
Args:
|
||||
cls: 继承了BaseModel类的类,需要被装饰的对象
|
||||
|
||||
Returns:
|
||||
返回被重写了__reduce__魔术方法的cls类
|
||||
"""
|
||||
|
||||
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,)
|
||||
|
||||
cls.__reduce__ = __reduce__
|
||||
return cls
|
||||
@@ -0,0 +1,69 @@
|
||||
# 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 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:
|
||||
raise AttributeError(f"ActorList对象没有属性 '{key}'")
|
||||
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def _get_cached_actor_handle(actor_name: str):
|
||||
"""缓存接口"""
|
||||
return ray.get_actor(actor_name, namespace="kilostar")
|
||||
|
||||
|
||||
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)
|
||||
setattr(actor_list, actor_name, handle)
|
||||
return actor_list
|
||||
@@ -0,0 +1,67 @@
|
||||
# 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 kilostar.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)
|
||||
except RetryableError:
|
||||
if attempt == max_retries - 1:
|
||||
raise
|
||||
await asyncio.sleep(base_delay * (2**attempt))
|
||||
|
||||
return async_wrapper
|
||||
else:
|
||||
|
||||
@wraps(func)
|
||||
def sync_wrapper(*args, **kwargs):
|
||||
"""执行与 sync wrapper 相关的核心业务流转操作。
|
||||
该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。
|
||||
Returns: : 经由当前业务模型加工处理后所输出的具体数据实例或领域模型对象。"""
|
||||
import time
|
||||
|
||||
for attempt in range(max_retries):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except RetryableError:
|
||||
if attempt == max_retries - 1:
|
||||
raise
|
||||
time.sleep(base_delay * (2**attempt))
|
||||
|
||||
return sync_wrapper
|
||||
|
||||
return decorator
|
||||
@@ -0,0 +1,3 @@
|
||||
from kilostar.worker_cluster.worker_cluster import WorkerCluster
|
||||
|
||||
__all__ = ["WorkerCluster"]
|
||||
@@ -0,0 +1,162 @@
|
||||
# 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
|
||||
import time
|
||||
import asyncio
|
||||
from collections import OrderedDict
|
||||
from ray.util.queue import Queue
|
||||
from kilostar.utils.ray_hook import ray_actor_hook
|
||||
from kilostar.worker_individual.base_individual import BaseIndividual
|
||||
from kilostar.worker_individual.skill_individual import SkillIndividual
|
||||
from kilostar.worker_individual.ordinary_individual import OrdinaryIndividual
|
||||
from kilostar.worker_individual.special_individual import SpecialIndividual
|
||||
|
||||
|
||||
from kilostar.utils.logger import get_logger
|
||||
|
||||
|
||||
@ray.remote
|
||||
class WorkerCluster:
|
||||
"""
|
||||
工作集群 Actor:管理和调度所有的 worker_individual
|
||||
设计理念:按需加载,内存 LRU 淘汰,避免 Actor 爆炸
|
||||
"""
|
||||
|
||||
def __init__(self, max_capacity: int = 200, num_runners: int = 10):
|
||||
self.max_capacity = max_capacity
|
||||
self._active_workers: OrderedDict[str, BaseIndividual] = OrderedDict()
|
||||
self.status = "running"
|
||||
self.task_queue = None
|
||||
self.results_futures = {}
|
||||
self.runners = []
|
||||
self.num_runners = num_runners
|
||||
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)
|
||||
]
|
||||
self.logger.info(f"WorkerCluster 已启动 {self.num_runners} 个 runner 协程。")
|
||||
|
||||
async def _recruit_worker(self, agent_id: str) -> BaseIndividual:
|
||||
"""内部方法:招聘/唤醒一个具体的 Agent 对象"""
|
||||
if agent_id in self._active_workers:
|
||||
self._active_workers.move_to_end(agent_id)
|
||||
return self._active_workers[agent_id]
|
||||
|
||||
global_state_machine = ray_actor_hook(
|
||||
"global_state_machine"
|
||||
).global_state_machine
|
||||
agent_config = await global_state_machine.get_individual.remote(agent_id)
|
||||
|
||||
if not agent_config:
|
||||
raise ValueError(f"无法唤醒 Agent {agent_id}:数据库中不存在该档案")
|
||||
|
||||
worker_type = agent_config.get("type", "ordinary")
|
||||
if worker_type == "skill":
|
||||
worker = SkillIndividual(agent_config)
|
||||
elif worker_type == "special":
|
||||
worker = SpecialIndividual(agent_config)
|
||||
else:
|
||||
worker = OrdinaryIndividual(agent_config)
|
||||
|
||||
self._active_workers[agent_id] = worker
|
||||
if len(self._active_workers) > self.max_capacity:
|
||||
evicted_id, _ = self._active_workers.popitem(last=False)
|
||||
self.logger.info(f"[WorkerCluster] 内存池满,休眠老化 Agent: {evicted_id}")
|
||||
|
||||
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:
|
||||
await asyncio.sleep(0.1)
|
||||
continue
|
||||
task = await self.task_queue.get_async()
|
||||
task_id = task.get("task_id")
|
||||
agent_id = task.get("agent_id")
|
||||
task_event = task.get("task_event")
|
||||
|
||||
self.logger.debug(
|
||||
f"[WorkerCluster Runner {runner_id}] 开始处理任务 {task_id} 给 Agent {agent_id}"
|
||||
)
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
worker = await self._recruit_worker(agent_id)
|
||||
result = await worker.run(task_event)
|
||||
cost_time = time.time() - start_time
|
||||
|
||||
response = {
|
||||
"success": True,
|
||||
"agent_id": agent_id,
|
||||
"data": result,
|
||||
"metrics": {"cost_time_sec": round(cost_time, 2)},
|
||||
}
|
||||
except Exception as e:
|
||||
self.logger.exception(
|
||||
f"[WorkerCluster Runner {runner_id}] 执行任务 {task_id} 时发生错误: {e}"
|
||||
)
|
||||
response = {"success": False, "agent_id": agent_id, "error": str(e)}
|
||||
if task_id in self.results_futures:
|
||||
future = self.results_futures[task_id]
|
||||
if not future.done():
|
||||
future.set_result(response)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"[WorkerCluster Runner {runner_id}] 循环发生异常: {e}"
|
||||
)
|
||||
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()
|
||||
|
||||
future = asyncio.Future()
|
||||
self.results_futures[task_id] = future
|
||||
|
||||
task = {"task_id": task_id, "agent_id": agent_id, "task_event": task_event}
|
||||
await self.task_queue.put_async(task)
|
||||
self.logger.debug(f"[WorkerCluster] 任务 {task_id} 已加入队列。")
|
||||
|
||||
try:
|
||||
result = await future
|
||||
return result
|
||||
finally:
|
||||
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,
|
||||
"cached_agent_ids": list(self._active_workers.keys()),
|
||||
"queue_size": self.task_queue.size(),
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# 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 kilostar.worker_individual.base_individual import BaseIndividual
|
||||
from kilostar.worker_individual.skill_individual import SkillIndividual
|
||||
from kilostar.worker_individual.ordinary_individual import OrdinaryIndividual
|
||||
from kilostar.worker_individual.special_individual import SpecialIndividual
|
||||
|
||||
__all__ = [
|
||||
"BaseIndividual",
|
||||
"SkillIndividual",
|
||||
"OrdinaryIndividual",
|
||||
"SpecialIndividual",
|
||||
]
|
||||
@@ -0,0 +1,106 @@
|
||||
# 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_ai import Agent, RunContext
|
||||
from pydantic import Field
|
||||
from kilostar.adapter.model_adapter.agent_factory import AgentFactory
|
||||
from kilostar.core.global_state_machine.model_provider.base_provider import Provider
|
||||
from kilostar.utils.agent_model import ResponseModel, InputModel, DepsModel
|
||||
from kilostar.utils.ray_hook import ray_actor_hook
|
||||
|
||||
from kilostar.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:
|
||||
"""
|
||||
Worker Individual 的基类
|
||||
"""
|
||||
|
||||
def __init__(self, agent_config: dict):
|
||||
self.agent_config = agent_config
|
||||
self.agent_id = agent_config.get("agent_id")
|
||||
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 kilostar.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
|
||||
model_id = self.agent_config.get("model_id", "gpt-4o") # default fallback
|
||||
tools_list = self.agent_config.get("tools", None)
|
||||
|
||||
provider: Provider = await global_state_machine.get_provider.remote(
|
||||
provider_title
|
||||
)
|
||||
agent_factory = AgentFactory()
|
||||
|
||||
callables = load_tools_from_list(tools_list)
|
||||
|
||||
self.agent = agent_factory.create_agent(
|
||||
provider=provider,
|
||||
model_id=model_id,
|
||||
output_type=WorkerIndividualResponse,
|
||||
system_prompt=system_prompt,
|
||||
deps_type=WorkerIndividualDeps,
|
||||
agent_name=agent_name,
|
||||
tools=callables,
|
||||
)
|
||||
|
||||
@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{ctx.deps.task_event}\n"
|
||||
return prompt
|
||||
|
||||
async def run(self, task_event: dict) -> dict:
|
||||
"""执行与 run 相关的核心业务流转操作。
|
||||
该方法封装了具体的算法策略或状态控制逻辑,确保操作能够在事务上下文中被原子且一致地执行。
|
||||
Args: task_event (dict): 由事件总线或工作流引擎分发过来的事件载荷,封装了触发此次调用的上下文快照与任务目标指令。
|
||||
Returns: (dict): 高度聚合的字典结构数据,将多维度的属性特征或统计指标组合后一并返回。"""
|
||||
raise NotImplementedError("子类必须实现 run 方法")
|
||||
@@ -0,0 +1,14 @@
|
||||
worker_individual
|
||||
---
|
||||
**worker_individual**是kilostar中的基础工作对象,主要分为三类:**skill_individual**,**ordinary_individual**和**special_individual**,庞大的**worker_individual**将负责具体的生产工作。
|
||||
|
||||
---
|
||||
## worker_individual分类
|
||||
### skill_individual(专家子个体)
|
||||
**skill_individual(专家子个体)** 是拥有专业**skill**的agent,通常使用MoE(混合专家模型)或者大参数的专家模型来作为agent的模型。通过装配专业化的知识从而实现完成复杂任务。
|
||||
|
||||
### ordinary_individual(普通子个体)
|
||||
**ordinary_individual(普通子个体)** 是普通的agent,通常使用小参数微调专家模型来作为agent的模型。通过专业化数据的微调,在一定程度上实现比大参数MoE模型在单一方面上的能力。
|
||||
|
||||
### special_individual(特殊子个体)
|
||||
**special_individual(特殊子个体)** 是特殊的agent,这类agent一般不承担普通的生成任务,更多是实现一些特殊的任务,比如生成语音生成视频等。
|
||||
@@ -0,0 +1,50 @@
|
||||
# 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 kilostar.worker_individual.base_individual import (
|
||||
BaseIndividual,
|
||||
WorkerIndividualDeps,
|
||||
)
|
||||
from kilostar.utils.logger import get_logger
|
||||
|
||||
logger = get_logger("ordinary_individual")
|
||||
|
||||
|
||||
class OrdinaryIndividual(BaseIndividual):
|
||||
"""
|
||||
普通子个体:普通的 agent。
|
||||
"""
|
||||
|
||||
def __init__(self, agent_config: dict):
|
||||
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)
|
||||
|
||||
deps = WorkerIndividualDeps(task_event=task_event)
|
||||
self.agent.retries = 3
|
||||
try:
|
||||
result = await self.agent.run(f"请执行以下任务:\n{task_event}", deps=deps)
|
||||
return {"output": result.data.output}
|
||||
except Exception as e:
|
||||
logger.exception(f"OrdinaryIndividual {self.agent_id} 执行失败: {e}")
|
||||
raise
|
||||
@@ -0,0 +1,132 @@
|
||||
# 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 kilostar.worker_individual.base_individual import (
|
||||
BaseIndividual,
|
||||
WorkerIndividualDeps,
|
||||
)
|
||||
from kilostar.utils.logger import get_logger
|
||||
import os
|
||||
import json
|
||||
from pydantic_ai import Tool
|
||||
import importlib.util
|
||||
|
||||
logger = get_logger("skill_individual")
|
||||
|
||||
|
||||
class SkillIndividual(BaseIndividual):
|
||||
"""
|
||||
专家子个体:拥有专业 skill 的 agent。
|
||||
"""
|
||||
|
||||
def __init__(self, agent_config: dict):
|
||||
super().__init__(agent_config)
|
||||
|
||||
async def _load_skill_tools(self):
|
||||
"""动态加载已绑定的 skill 工具。"""
|
||||
tools = []
|
||||
bound_skill = self.agent_config.get("bound_skill", "")
|
||||
# bound_skill can be string or dict {"skill_name": ["file1", "file2"]}
|
||||
skill_mapper = {}
|
||||
if isinstance(bound_skill, str) and bound_skill:
|
||||
try:
|
||||
skill_mapper = json.loads(bound_skill)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
elif isinstance(bound_skill, dict):
|
||||
skill_mapper = bound_skill
|
||||
|
||||
skill_base_dir = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..", "plugin", "skill")
|
||||
)
|
||||
|
||||
for skill_name, _ in skill_mapper.items():
|
||||
skill_path = os.path.join(skill_base_dir, skill_name)
|
||||
metadata_path = os.path.join(skill_path, "metadata.json")
|
||||
if not os.path.exists(metadata_path):
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(metadata_path, "r", encoding="utf-8") as f:
|
||||
metadata = json.load(f)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load metadata for skill {skill_name}: {e}")
|
||||
continue
|
||||
|
||||
if "functions" in metadata:
|
||||
for func_info in metadata["functions"]:
|
||||
# Ensure path is absolute
|
||||
script_path = func_info.get("file_path", "")
|
||||
if not os.path.isabs(script_path):
|
||||
script_path = os.path.join(skill_path, script_path)
|
||||
|
||||
if not os.path.exists(script_path):
|
||||
logger.warning(f"Skill script not found: {script_path}")
|
||||
continue
|
||||
|
||||
func_name = func_info.get("name")
|
||||
try:
|
||||
# Dynamically load the python module
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
func_name, script_path
|
||||
)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
func = getattr(module, func_name)
|
||||
if callable(func):
|
||||
# Convert to PydanticAI Tool
|
||||
tool = Tool(
|
||||
func,
|
||||
name=func_name,
|
||||
description=func_info.get("docstring", ""),
|
||||
)
|
||||
tools.append(tool)
|
||||
logger.info(
|
||||
f"Loaded skill tool: {func_name} from {skill_name}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Failed to load function {func_name} from {script_path}: {e}"
|
||||
)
|
||||
|
||||
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助手,请利用你的专业知识完成给定的任务。",
|
||||
)
|
||||
await self._init_agent("skill_individual", system_prompt)
|
||||
|
||||
deps = WorkerIndividualDeps(task_event=task_event)
|
||||
self.agent.retries = 3
|
||||
|
||||
tools = await self._load_skill_tools()
|
||||
|
||||
try:
|
||||
result = await self.agent.run(
|
||||
f"请执行以下任务:\n{task_event}",
|
||||
deps=deps,
|
||||
tools=tools if tools else None,
|
||||
)
|
||||
return {"output": result.data.output}
|
||||
except Exception as e:
|
||||
logger.exception(f"SkillIndividual {self.agent_id} 执行失败: {e}")
|
||||
raise
|
||||
@@ -0,0 +1,50 @@
|
||||
# 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 kilostar.worker_individual.base_individual import (
|
||||
BaseIndividual,
|
||||
WorkerIndividualDeps,
|
||||
)
|
||||
from kilostar.utils.logger import get_logger
|
||||
|
||||
logger = get_logger("special_individual")
|
||||
|
||||
|
||||
class SpecialIndividual(BaseIndividual):
|
||||
"""
|
||||
特殊子个体:执行特殊任务的 agent,如生成语音、视频等。
|
||||
"""
|
||||
|
||||
def __init__(self, agent_config: dict):
|
||||
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)
|
||||
|
||||
deps = WorkerIndividualDeps(task_event=task_event)
|
||||
self.agent.retries = 3
|
||||
try:
|
||||
result = await self.agent.run(f"请执行以下任务:\n{task_event}", deps=deps)
|
||||
return {"output": result.data.output}
|
||||
except Exception as e:
|
||||
logger.exception(f"SpecialIndividual {self.agent_id} 执行失败: {e}")
|
||||
raise
|
||||
Reference in New Issue
Block a user