chore: 迁移服务器前同步本地修改

This commit is contained in:
2026-08-05 21:26:13 +08:00
parent aa47a19e98
commit 1a914a2517
73 changed files with 908 additions and 1961 deletions
+1 -14
View File
@@ -13,19 +13,14 @@
# limitations under the License.
import os
from typing import Dict
from fastapi import FastAPI, WebSocket, Request
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from kilostar.utils.ray_compat import _STANDALONE
from kilostar.utils.settings import get_settings
if not _STANDALONE:
from ray import serve
from .agent import agent_router
from .auth import auth_router
from .system import system_router, system_api_router
@@ -221,11 +216,3 @@ else:
)
if not _STANDALONE:
@serve.deployment
@serve.ingress(app)
class KiloStarGateway:
gateway: Dict[str, WebSocket]
def __init__(self):
self.gateway = {}
+42 -42
View File
@@ -14,7 +14,7 @@
from typing import Union
from kilostar.utils.ray_hook import ray_actor_hook
from kilostar.utils.actor import get_actor
from fastapi import APIRouter, Depends, Request
from pydantic import BaseModel, field_validator
from kilostar.utils.access import Accessor, TokenData, RoleChecker
@@ -52,8 +52,8 @@ async def get_system_nodes(
_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
):
"""返回两大系统节点(regulatory/consciousness)当前的持久化配置。"""
postgres_database = ray_actor_hook("postgres_database").postgres_database
configs = await postgres_database.get_all_system_node_configs.remote()
postgres_database = get_actor("postgres_database")
configs = await postgres_database.get_all_system_node_configs()
return {"system_nodes": configs}
@@ -64,8 +64,8 @@ async def load_agent(
_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.SUPER_ADMINISTRATOR)),
):
"""加载/重载某个系统节点的 Agent:先持久化配置,再调用对应节点 Actor 的 ``create_agent``。"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
postgres_database = ray_actor_hook("postgres_database").postgres_database
global_state_machine = get_actor("global_state_machine")
postgres_database = get_actor("postgres_database")
accept_lang = request.headers.get("accept-language", "")
if isinstance(agent_register, AgentLocalRegister):
@@ -73,7 +73,7 @@ async def load_agent(
elif isinstance(agent_register, AgentRegister):
try:
await postgres_database.upsert_system_node_config.remote(
await postgres_database.upsert_system_node_config(
agent_register.individual_name,
agent_register.provider_title,
agent_register.model_id,
@@ -90,14 +90,14 @@ async def load_agent(
# Resolve persona system_prompt from DB
persona_prompt = None
if agent_register.persona_id:
tpl = await postgres_database.get_template.remote(agent_register.persona_id)
tpl = await postgres_database.get_template(agent_register.persona_id)
if tpl:
persona_prompt = tpl.system_prompt
match scope:
case "regulatory_node":
node = ray_actor_hook("regulatory_node").regulatory_node
await node.create_agent.remote(
node = get_actor("regulatory_node")
await node.create_agent(
global_state_machine,
agent_register.provider_title,
agent_register.model_id,
@@ -107,8 +107,8 @@ async def load_agent(
persona_prompt,
)
case "consciousness_node":
node = ray_actor_hook("consciousness_node").consciousness_node
await node.create_agent.remote(
node = get_actor("consciousness_node")
await node.create_agent(
global_state_machine,
agent_register.provider_title,
agent_register.model_id,
@@ -181,10 +181,10 @@ async def create_worker_individual(
token_data: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
):
"""创建一个 Worker Agent,``owner_id`` 自动绑定为当前登录用户。"""
postgres_database = ray_actor_hook("postgres_database").postgres_database
postgres_database = get_actor("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)
worker = await postgres_database.add_worker_individual(**data_dict)
return {"message": "success", "agent_id": worker.agent_id}
@@ -197,11 +197,11 @@ async def get_worker_individual_list(
plugin_owned slot 是插件登记的"占位 agent",所有用户共享同一份配置,
在前端展示时会用徽标标记,并允许任何登录用户装配 provider/model。
"""
postgres_database = ray_actor_hook("postgres_database").postgres_database
workers = await postgres_database.get_worker_individual_list.remote(
postgres_database = get_actor("postgres_database")
workers = await postgres_database.get_worker_individual_list(
owner_id=token_data.user_id
) or []
all_workers = await postgres_database.get_all_worker_individual.remote() or []
all_workers = await postgres_database.get_all_worker_individual() or []
seen_ids = {w.agent_id for w in workers}
plugin_slots = [
w for w in all_workers
@@ -215,8 +215,8 @@ async def get_worker_individual(
agent_id: str, token_data: TokenData = Depends(Accessor.get_current_user)
):
"""按 ``agent_id`` 查询 Worker Agent;非本人的 Agent 返回 403(plugin_owned slot 例外)。"""
postgres_database = ray_actor_hook("postgres_database").postgres_database
worker = await postgres_database.get_worker_individual.remote(agent_id=agent_id)
postgres_database = get_actor("postgres_database")
worker = await postgres_database.get_worker_individual(agent_id=agent_id)
if not worker:
raise HTTPException(status_code=404, detail="Agent not found")
if not getattr(worker, "plugin_owned", None) and worker.owner_id != token_data.user_id:
@@ -233,8 +233,8 @@ async def update_worker_individual(
token_data: TokenData = Depends(Accessor.get_current_user),
):
"""局部更新 Worker Agent 配置;同时把状态机里的旧实例移除等待懒加载。"""
postgres_database = ray_actor_hook("postgres_database").postgres_database
worker = await postgres_database.get_worker_individual.remote(agent_id=agent_id)
postgres_database = get_actor("postgres_database")
worker = await postgres_database.get_worker_individual(agent_id=agent_id)
if not worker:
raise HTTPException(status_code=404, detail="Agent not found")
# plugin_owned slot:任何登录用户都能装配 provider/model;普通 worker 仅 owner 可改
@@ -244,21 +244,21 @@ async def update_worker_individual(
)
update_data = worker_data.model_dump(exclude_unset=True)
updated_worker = await postgres_database.update_worker_individual.remote(
updated_worker = await postgres_database.update_worker_individual(
agent_id=agent_id, **update_data
)
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
global_state_machine = get_actor("global_state_machine")
try:
await global_state_machine.remove_individual.remote(agent_id)
await global_state_machine.remove_individual(agent_id)
except Exception:
pass
# plugin_owned 时顺带触发对应插件的 reload,让新 provider/model 立刻生效
if getattr(worker, "plugin_owned", None):
try:
pm = ray_actor_hook("global_plugin_manager").global_plugin_manager
await pm.reload.remote(worker.plugin_owned)
pm = get_actor("global_plugin_manager")
await pm.reload(worker.plugin_owned)
except Exception:
pass
@@ -270,8 +270,8 @@ async def reload_worker_individual(
agent_id: str, token_data: TokenData = Depends(Accessor.get_current_user)
):
"""强制把 Worker 从内存池中卸载,下次调用时按最新配置重新加载。"""
postgres_database = ray_actor_hook("postgres_database").postgres_database
worker = await postgres_database.get_worker_individual.remote(agent_id=agent_id)
postgres_database = get_actor("postgres_database")
worker = await postgres_database.get_worker_individual(agent_id=agent_id)
if not worker:
raise HTTPException(status_code=404, detail="Agent not found")
if worker.owner_id != token_data.user_id:
@@ -279,8 +279,8 @@ async def reload_worker_individual(
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)
global_state_machine = get_actor("global_state_machine")
await global_state_machine.remove_individual(agent_id)
return {"message": "Worker will be reloaded on next use"}
@@ -290,15 +290,15 @@ async def delete_worker_individual(
agent_id: str, token_data: TokenData = Depends(Accessor.get_current_user)
):
"""删除 Worker Agent;非本人 Agent 返回 403。"""
postgres_database = ray_actor_hook("postgres_database").postgres_database
worker = await postgres_database.get_worker_individual.remote(agent_id=agent_id)
postgres_database = get_actor("postgres_database")
worker = await postgres_database.get_worker_individual(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)
await postgres_database.delete_worker_individual(agent_id=agent_id)
return {"message": "success"}
@@ -318,8 +318,8 @@ class PersonaTemplateUpdate(BaseModel):
async def list_templates(
token_data: TokenData = Depends(Accessor.get_current_user),
):
postgres_database = ray_actor_hook("postgres_database").postgres_database
templates = await postgres_database.list_templates.remote(
postgres_database = get_actor("postgres_database")
templates = await postgres_database.list_templates(
owner_id=token_data.user_id
)
return {"templates": templates}
@@ -330,8 +330,8 @@ async def create_template(
data: PersonaTemplateCreate,
token_data: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
):
postgres_database = ray_actor_hook("postgres_database").postgres_database
tpl = await postgres_database.add_template.remote(
postgres_database = get_actor("postgres_database")
tpl = await postgres_database.add_template(
name=data.name,
system_prompt=data.system_prompt,
owner_id=token_data.user_id,
@@ -345,13 +345,13 @@ async def update_template(
data: PersonaTemplateUpdate,
token_data: TokenData = Depends(Accessor.get_current_user),
):
postgres_database = ray_actor_hook("postgres_database").postgres_database
tpl = await postgres_database.get_template.remote(template_id)
postgres_database = get_actor("postgres_database")
tpl = await postgres_database.get_template(template_id)
if not tpl:
raise HTTPException(status_code=404, detail="Template not found")
if tpl.owner_id != token_data.user_id:
raise HTTPException(status_code=403, detail="Forbidden")
updated = await postgres_database.update_template.remote(
updated = await postgres_database.update_template(
template_id, **data.model_dump(exclude_unset=True)
)
return {"message": "success", "template": updated}
@@ -362,11 +362,11 @@ async def delete_template(
template_id: str,
token_data: TokenData = Depends(Accessor.get_current_user),
):
postgres_database = ray_actor_hook("postgres_database").postgres_database
tpl = await postgres_database.get_template.remote(template_id)
postgres_database = get_actor("postgres_database")
tpl = await postgres_database.get_template(template_id)
if not tpl:
raise HTTPException(status_code=404, detail="Template not found")
if tpl.owner_id != token_data.user_id:
raise HTTPException(status_code=403, detail="Forbidden")
await postgres_database.delete_template.remote(template_id)
await postgres_database.delete_template(template_id)
return {"message": "success"}
+11 -11
View File
@@ -17,7 +17,7 @@ from fastapi import Depends
from pydantic import BaseModel
from kilostar.utils.access import Accessor, TokenData, RoleChecker
from fastapi.concurrency import run_in_threadpool
from kilostar.utils.ray_hook import ray_actor_hook
from kilostar.utils.actor import get_actor
from kilostar.core.postgres_database.model import UserAuthority
from kilostar.utils.error import UserNotExistError
from kilostar.utils.rate_limit import register_limiter, login_limiter
@@ -36,7 +36,7 @@ class UserRegister(BaseModel):
async def create_user(user_register: UserRegister, request: Request):
"""注册新用户:异步线程池里做 argon2 哈希,再交由 PostgresDatabase Actor 落库。"""
register_limiter.check(request)
postgres_database = ray_actor_hook("postgres_database").postgres_database
postgres_database = get_actor("postgres_database")
try:
hashed_password = await run_in_threadpool(
Accessor.hash_password, user_register.password
@@ -47,7 +47,7 @@ async def create_user(user_register: UserRegister, request: Request):
status_code=400,
content={"code": "password_invalid", "message": str(e)},
)
user = await postgres_database.add_user.remote(
user = await postgres_database.add_user(
user_register.user_name, hashed_password
)
return {"message": "success", "user_id": user.user_id}
@@ -64,8 +64,8 @@ class UserLogin(BaseModel):
async def login_user(user_login: UserLogin, request: Request):
"""用户登录:查询用户后在线程池中校验口令,校验成功则签发 JWT。"""
login_limiter.check(request)
postgres_database = ray_actor_hook("postgres_database").postgres_database
user = await postgres_database.login_user.remote(user_login.user_name)
postgres_database = get_actor("postgres_database")
user = await postgres_database.login_user(user_login.user_name)
if not user:
raise UserNotExistError()
tokens = await run_in_threadpool(
@@ -113,8 +113,8 @@ async def change_authority(
"""
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(
postgres_database = get_actor("postgres_database")
user = await postgres_database.change_user_authority(
user_id=request.user_id, new_authority=request.new_authority
)
return {
@@ -133,8 +133,8 @@ async def get_user_list(
"""
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()
postgres_database = get_actor("postgres_database")
users = await postgres_database.get_all_users()
return {
"users": [
{"user_id": u.user_id, "user_name": u.user_name, "role": u.user_authority}
@@ -153,6 +153,6 @@ async def delete_user(
"""
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)
postgres_database = get_actor("postgres_database")
await postgres_database.delete_user_by_id(user_id=user_id)
return {"message": "success"}
+27 -40
View File
@@ -17,7 +17,7 @@ import asyncio
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from kilostar.utils.ray_hook import ray_actor_hook
from kilostar.utils.actor import get_actor
from kilostar.utils.access import Accessor, TokenData
from kilostar.core.individual.regulatory_node.template import (
MessageRequest,
@@ -56,8 +56,8 @@ def _build_message_history(rows) -> list:
async def _load_message_history(chat_id: str) -> list:
postgres_database = ray_actor_hook("postgres_database").postgres_database
rows = await postgres_database.list_chat_messages.remote(chat_id=chat_id)
postgres_database = get_actor("postgres_database")
rows = await postgres_database.list_chat_messages(chat_id=chat_id)
return _build_message_history(rows or [])
@@ -76,14 +76,14 @@ async def _ask_regulatory(
*, user_id: str, chat_id: str, message: str, message_history: list | None = None
) -> str | None:
"""统一封装 chat 入口对 RegulatoryNode 的调用。"""
regulatory_node = ray_actor_hook("regulatory_node").regulatory_node
regulatory_node = get_actor("regulatory_node")
payload = MessageRequest(
platform="client",
user_name=user_id,
platform_id=chat_id,
message=message,
)
resp: MessageResponse | None = await regulatory_node.working.remote(
resp: MessageResponse | None = await regulatory_node.working(
payload, message_history
)
return _extract_reply(resp)
@@ -103,13 +103,13 @@ async def create_chat_session(
request: CreateChatRequest,
token_data: TokenData = Depends(Accessor.get_current_user),
):
postgres_database = ray_actor_hook("postgres_database").postgres_database
chat = await postgres_database.create_chat_session.remote(
postgres_database = get_actor("postgres_database")
chat = await postgres_database.create_chat_session(
user_id=token_data.user_id, title=request.title
)
# 存入用户消息
await postgres_database.add_chat_message.remote(
await postgres_database.add_chat_message(
chat_id=chat.chat_id, message=request.initial_message, message_owner="user"
)
@@ -122,7 +122,7 @@ async def create_chat_session(
# 存入回复消息
if response_msg:
await postgres_database.add_chat_message.remote(
await postgres_database.add_chat_message(
chat_id=chat.chat_id, message=response_msg, message_owner="regulatory_node"
)
@@ -133,8 +133,8 @@ async def create_chat_session(
async def list_chat_sessions(
token_data: TokenData = Depends(Accessor.get_current_user),
):
postgres_database = ray_actor_hook("postgres_database").postgres_database
sessions = await postgres_database.list_chat_sessions.remote(
postgres_database = get_actor("postgres_database")
sessions = await postgres_database.list_chat_sessions(
user_id=token_data.user_id
)
return {"sessions": sessions}
@@ -144,8 +144,8 @@ async def list_chat_sessions(
async def get_chat_history(
chat_id: str, token_data: TokenData = Depends(Accessor.get_current_user)
):
postgres_database = ray_actor_hook("postgres_database").postgres_database
messages = await postgres_database.list_chat_messages.remote(chat_id=chat_id)
postgres_database = get_actor("postgres_database")
messages = await postgres_database.list_chat_messages(chat_id=chat_id)
return {"messages": messages}
@@ -155,10 +155,10 @@ async def send_chat_message(
request: SendMessageRequest,
token_data: TokenData = Depends(Accessor.get_current_user),
):
postgres_database = ray_actor_hook("postgres_database").postgres_database
postgres_database = get_actor("postgres_database")
# 先取历史(不含当前输入),再写入用户消息,避免历史里出现重复
message_history = await _load_message_history(chat_id)
await postgres_database.add_chat_message.remote(
await postgres_database.add_chat_message(
chat_id=chat_id, message=request.message, message_owner="user"
)
@@ -172,7 +172,7 @@ async def send_chat_message(
# 存回复
if response_msg:
await postgres_database.add_chat_message.remote(
await postgres_database.add_chat_message(
chat_id=chat_id, message=response_msg, message_owner="regulatory_node"
)
@@ -184,13 +184,13 @@ async def delete_chat_session(
chat_id: str,
token_data: TokenData = Depends(Accessor.get_current_user),
):
postgres_database = ray_actor_hook("postgres_database").postgres_database
session = await postgres_database.get_chat_session.remote(chat_id=chat_id)
postgres_database = get_actor("postgres_database")
session = await postgres_database.get_chat_session(chat_id=chat_id)
if not session:
raise HTTPException(status_code=404, detail="Chat session not found")
if session.user_id != token_data.user_id:
raise HTTPException(status_code=403, detail="Forbidden")
await postgres_database.delete_chat_session.remote(chat_id=chat_id)
await postgres_database.delete_chat_session(chat_id=chat_id)
return {"message": "success"}
@@ -201,14 +201,12 @@ async def stream_chat_message(
request: Request,
token_data: TokenData = Depends(Accessor.get_current_user),
):
"""SSE 流式聊天端点:standalone 模式下逐 token 流式输出;distributed 模式 fallback 到整段回复。"""
from kilostar.utils.ray_compat import _STANDALONE
postgres_database = ray_actor_hook("postgres_database").postgres_database
"""SSE 流式聊天端点:逐 token 流式输出。"""
postgres_database = get_actor("postgres_database")
message_history = await _load_message_history(chat_id)
await postgres_database.add_chat_message.remote(
await postgres_database.add_chat_message(
chat_id=chat_id, message=request_body.message, message_owner="user"
)
@@ -219,23 +217,12 @@ async def stream_chat_message(
message=request_body.message,
)
regulatory_node = ray_actor_hook("regulatory_node").regulatory_node
if not _STANDALONE:
async def fallback_generator():
resp = await regulatory_node.working.remote(payload, message_history)
full_response = resp.reply_message if resp else ""
if full_response:
await postgres_database.add_chat_message.remote(
chat_id=chat_id, message=full_response, message_owner="regulatory_node"
)
yield f"data: {json.dumps({'token': full_response})}\n\n"
yield f"data: {json.dumps({'done': True, 'full_message': full_response})}\n\n"
return StreamingResponse(fallback_generator(), media_type="text/event-stream")
regulatory_node = get_actor("regulatory_node")
token_queue = asyncio.Queue()
stream_task = regulatory_node.stream_working.remote(payload, token_queue, message_history)
stream_task = asyncio.create_task(
regulatory_node.stream_working(payload, token_queue, message_history)
)
async def event_generator():
full_response = ""
@@ -260,7 +247,7 @@ async def stream_chat_message(
yield f"data: {json.dumps({'token': full_response})}\n\n"
if full_response:
await postgres_database.add_chat_message.remote(
await postgres_database.add_chat_message(
chat_id=chat_id,
message=full_response,
message_owner="regulatory_node",
+3 -3
View File
@@ -15,7 +15,7 @@
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
from pydantic import BaseModel
from kilostar.utils.access import Accessor, TokenData
from kilostar.utils.ray_hook import ray_actor_hook
from kilostar.utils.actor import get_actor
from kilostar.core.individual.regulatory_node.template import (
MessageRequest,
MessageResponse,
@@ -42,14 +42,14 @@ async def create_message(
"""把前端消息转交给 RegulatoryNode 处理,并把回复透传给前端。"""
logger.info("收到消息,来源:客户端")
logger.debug(f"消息内容:{message.message}")
regulatory_node = ray_actor_hook("regulatory_node").regulatory_node
regulatory_node = get_actor("regulatory_node")
msg_request = MessageRequest(
platform="client",
user_name=token_data.username,
platform_id=token_data.user_id,
message=message.message,
)
result = await regulatory_node.working.remote(msg_request)
result = await regulatory_node.working(msg_request)
if isinstance(result, MessageResponse):
return {"message": result.reply_message}
if isinstance(result, str):
+3 -3
View File
@@ -39,7 +39,7 @@ from kilostar.core.individual.regulatory_node.template import (
MessageResponse,
)
from kilostar.utils.logger import get_logger
from kilostar.utils.ray_hook import ray_actor_hook
from kilostar.utils.actor import get_actor
logger = get_logger("onebot")
@@ -129,8 +129,8 @@ async def _dispatch_event(payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
)
try:
regulatory_node = ray_actor_hook("regulatory_node").regulatory_node
result = await regulatory_node.working.remote(msg_request)
regulatory_node = get_actor("regulatory_node")
result = await regulatory_node.working(msg_request)
except Exception as e:
logger.exception(f"[OneBot] RegulatoryNode 调用失败: {e}")
return None
+21 -23
View File
@@ -8,7 +8,7 @@ from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from kilostar.utils.access import Accessor, TokenData
from kilostar.utils.ray_hook import ray_actor_hook
from kilostar.utils.actor import get_actor
from kilostar.utils.settings import get_plugin_dir
plugin_router = APIRouter(prefix="/api/v1/plugin", tags=["plugin"])
@@ -25,15 +25,16 @@ async def submit_task(
req: SubmitRequest,
token_data: TokenData = Depends(Accessor.get_current_user),
):
pm = ray_actor_hook("global_plugin_manager").global_plugin_manager
plugins = await pm.list_plugins.remote()
if req.org_name not in plugins:
pm = get_actor("global_plugin_manager")
plugins = await pm.list_plugins()
# list_plugins 返回 [{"name": ..., ...}],按 name 判断插件是否已加载
if req.org_name not in {p["name"] for p in plugins}:
raise HTTPException(404, f"Plugin '{req.org_name}' not found")
org = ray_actor_hook(f"org_{req.org_name}").get(f"org_{req.org_name}")
org = get_actor(f"org_{req.org_name}")
ctx = req.context or {}
ctx["user"] = token_data.username
task_id = await org.submit.remote(req.task_description, ctx)
task_id = await org.submit(req.task_description, ctx)
return {"task_id": task_id}
@@ -42,8 +43,8 @@ async def get_task_status(
task_id: str,
token_data: TokenData = Depends(Accessor.get_current_user),
):
db = ray_actor_hook("postgres_database").postgres_database
task = await db.get_org_task.remote(task_id)
db = get_actor("postgres_database")
task = await db.get_org_task(task_id)
if not task:
raise HTTPException(404, "Task not found")
return task
@@ -54,8 +55,8 @@ async def get_task_events(
task_id: str,
token_data: TokenData = Depends(Accessor.get_current_user),
):
db = ray_actor_hook("postgres_database").postgres_database
events = await db.query_org_events.remote(task_id)
db = get_actor("postgres_database")
events = await db.query_org_events(task_id)
return {"events": events}
@@ -64,19 +65,16 @@ async def stream_task(
task_id: str,
token_data: TokenData = Depends(Accessor.get_current_user),
):
import asyncio
org_name = None
db = ray_actor_hook("postgres_database").postgres_database
task = await db.get_org_task.remote(task_id)
db = get_actor("postgres_database")
task = await db.get_org_task(task_id)
if not task:
raise HTTPException(404, "Task not found")
org_name = task["org_name"]
org = ray_actor_hook(f"org_{org_name}").get(f"org_{org_name}")
org = get_actor(f"org_{org_name}")
async def _generate():
async for event in await org.stream.remote(task_id):
async for event in await org.stream(task_id):
yield f"data: {event}\n\n"
return StreamingResponse(_generate(), media_type="text/event-stream")
@@ -86,8 +84,8 @@ async def stream_task(
async def list_plugins(
token_data: TokenData = Depends(Accessor.get_current_user),
):
pm = ray_actor_hook("global_plugin_manager").global_plugin_manager
plugins = await pm.list_plugins.remote()
pm = get_actor("global_plugin_manager")
plugins = await pm.list_plugins()
return {"plugins": plugins}
@@ -96,8 +94,8 @@ async def install_plugin(
name: str,
token_data: TokenData = Depends(Accessor.get_current_user),
):
pm = ray_actor_hook("global_plugin_manager").global_plugin_manager
await pm.install.remote(name)
pm = get_actor("global_plugin_manager")
await pm.install(name)
return {"status": "ok", "name": name}
@@ -106,8 +104,8 @@ async def reload_plugin(
name: str,
token_data: TokenData = Depends(Accessor.get_current_user),
):
pm = ray_actor_hook("global_plugin_manager").global_plugin_manager
await pm.reload.remote(name)
pm = get_actor("global_plugin_manager")
await pm.reload(name)
return {"status": "ok", "name": name}
+7 -7
View File
@@ -18,7 +18,7 @@ from typing import Any, Dict, Literal, Optional
from kilostar.utils.access import TokenData, Accessor, RoleChecker
from kilostar.core.postgres_database.model import UserAuthority
from kilostar.core.global_state_machine.model_provider.base_provider import Provider
from kilostar.utils.ray_hook import ray_actor_hook
from kilostar.utils.actor import get_actor
provider_router = APIRouter(prefix="/api/v1/provider", tags=["provider"])
@@ -39,8 +39,8 @@ async def create_provider(
token_data: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
) -> None:
"""注册一个 Provider;owner 为当前登录用户的 ``user_id``。"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
await global_state_machine.add_provider_wrap.remote(
global_state_machine = get_actor("global_state_machine")
await global_state_machine.add_provider_wrap(
provider_type=provider_register.provider_type,
provider_title=provider_register.provider_title,
provider_url=provider_register.provider_url,
@@ -61,10 +61,10 @@ async def get_provider_list(
_: TokenData = Depends(Accessor.get_current_user),
) -> Dict[str, Any]:
"""返回当前所有已注册的 Provider,前端用以展示模型清单。apikey 脱敏。"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
global_state_machine = get_actor("global_state_machine")
provider_list: Dict[
str, Provider
] = await global_state_machine.get_provider_list.remote()
] = await global_state_machine.get_provider_list()
masked = {}
for title, p in provider_list.items():
d = p.model_dump() if hasattr(p, "model_dump") else dict(p)
@@ -138,6 +138,6 @@ async def delete_provider(
),
) -> dict:
"""删除指定 ``provider_title`` 的 Provider;仅超管可调用。"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
await global_state_machine.delete_provider.remote(provider_title=provider_title)
global_state_machine = get_actor("global_state_machine")
await global_state_machine.delete_provider(provider_title=provider_title)
return {"message": "success"}
+42 -42
View File
@@ -15,7 +15,7 @@
from typing import Any, Dict, List, Optional
from pydantic import BaseModel
import viceroy
from kilostar.utils.ray_hook import ray_actor_hook
from kilostar.utils.actor import get_actor
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import FileResponse
from kilostar.utils.access import TokenData, RoleChecker, Accessor
@@ -50,7 +50,7 @@ async def install_skill(
skill: Skill, _: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER))
):
"""通过 viceroy 把 skill 仓库克隆到 ``data/plugin/skill``,并在状态机中登记。"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
global_state_machine = get_actor("global_state_machine")
import os
from kilostar.utils.settings import get_plugin_dir
@@ -63,7 +63,7 @@ async def install_skill(
skill_name = skill.path.split("/")[-1]
else:
skill_name = skill.repo_url.split("/")[-1]
await global_state_machine.add_skill.remote(skill_name)
await global_state_machine.add_skill(skill_name)
return {"message": "创建成功"}
@@ -72,8 +72,8 @@ async def get_skills(
_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
):
"""返回当前状态机中已登记的所有 skill 名称列表。"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
skills = await global_state_machine.get_skill_list.remote()
global_state_machine = get_actor("global_state_machine")
skills = await global_state_machine.get_skill_list()
return {"skills": skills}
@@ -85,8 +85,8 @@ async def delete_skill(
),
):
"""从状态机中移除 skill 注册项;不会删除磁盘上的代码文件。"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
await global_state_machine.remove_skill.remote(skill_name)
global_state_machine = get_actor("global_state_machine")
await global_state_machine.remove_skill(skill_name)
return {"message": "success"}
@@ -98,12 +98,12 @@ async def add_mcp_server(
_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.SUPER_ADMINISTRATOR)),
):
"""注册一个 MCP 服务器到全局状态机。"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
global_state_machine = get_actor("global_state_machine")
import uuid
server_id = str(uuid.uuid4())[:8]
cfg_dict = config.model_dump(exclude_none=True)
await global_state_machine.add_mcp_server.remote(server_id, cfg_dict)
await global_state_machine.add_mcp_server(server_id, cfg_dict)
return {"server_id": server_id, "message": "MCP server registered"}
@@ -112,8 +112,8 @@ async def list_mcp_servers(
_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
):
"""返回已注册的全部 MCP 服务器配置;env 中的敏感字段脱敏。"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
servers = await global_state_machine.list_mcp_servers.remote()
global_state_machine = get_actor("global_state_machine")
servers = await global_state_machine.list_mcp_servers()
for s in servers:
if "env" in s and isinstance(s["env"], dict):
s["env"] = _mask_config(s["env"])
@@ -126,8 +126,8 @@ async def delete_mcp_server(
_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.SUPER_ADMINISTRATOR)),
):
"""从状态机中移除一个 MCP 服务器配置。"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
ok = await global_state_machine.delete_mcp_server.remote(server_id)
global_state_machine = get_actor("global_state_machine")
ok = await global_state_machine.delete_mcp_server(server_id)
if not ok:
raise HTTPException(status_code=404, detail="MCP server not found")
return {"message": "success"}
@@ -152,8 +152,8 @@ async def download_artifact(
if not artifact_id.isalnum() or len(artifact_id) > 32:
raise HTTPException(status_code=400, detail="invalid artifact id")
postgres_database = ray_actor_hook("postgres_database").postgres_database
wf = await postgres_database.get_workflow.remote(trace_id)
postgres_database = get_actor("postgres_database")
wf = await postgres_database.get_workflow(trace_id)
if not wf:
raise HTTPException(status_code=404, detail="Workflow not found")
if getattr(wf, "user_id", None) != token_data.user_id:
@@ -187,8 +187,8 @@ async def list_toolset_packages(
_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
):
"""列出所有磁盘上的工具包(``data/toolset/<name>/`` 单元)。"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
packages = await global_state_machine.list_toolset_packages.remote()
global_state_machine = get_actor("global_state_machine")
packages = await global_state_machine.list_toolset_packages()
return {"packages": packages}
@@ -198,8 +198,8 @@ async def get_toolset_package_readme(
_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
):
"""返回指定工具包的 README.md 内容(markdown 文本)。"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
content = await global_state_machine.get_toolset_package_readme.remote(name)
global_state_machine = get_actor("global_state_machine")
content = await global_state_machine.get_toolset_package_readme(name)
if content is None:
raise HTTPException(status_code=404, detail="README not found")
return {"name": name, "content": content}
@@ -216,9 +216,9 @@ async def get_tools(
其中 ``mcp_servers`` 会现场尝试连接每个已注册的 MCP 服务器并列出它们暴露的
工具名,便于前端展示;任意一台 MCP server 不可达不影响其他工具的返回。
"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
tool_mapper = await global_state_machine.get_tool_mapper.remote()
categories = await global_state_machine.get_tool_categories.remote()
global_state_machine = get_actor("global_state_machine")
tool_mapper = await global_state_machine.get_tool_mapper()
categories = await global_state_machine.get_tool_categories()
all_tool_names = set()
for scope_tools in tool_mapper.values():
@@ -266,8 +266,8 @@ async def list_tool_configs(
_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.SUPER_ADMINISTRATOR)),
):
"""列出所有工具运行期配置;敏感字段会被脱敏。"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
raw = await global_state_machine.list_tool_configs.remote()
global_state_machine = get_actor("global_state_machine")
raw = await global_state_machine.list_tool_configs()
return {
"configs": {name: _mask_config(cfg) for name, cfg in raw.items()},
}
@@ -279,8 +279,8 @@ async def get_tool_config(
_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.SUPER_ADMINISTRATOR)),
):
"""按工具名取出脱敏后的配置。"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
raw = await global_state_machine.get_tool_config.remote(tool_name)
global_state_machine = get_actor("global_state_machine")
raw = await global_state_machine.get_tool_config(tool_name)
return {"tool_name": tool_name, "config": _mask_config(raw)}
@@ -291,8 +291,8 @@ async def set_tool_config(
_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.SUPER_ADMINISTRATOR)),
):
"""写入/覆盖某工具的运行期配置(如 ``tavily_search`` 的 ``api_key``)。"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
await global_state_machine.set_tool_config.remote(tool_name, body.config)
global_state_machine = get_actor("global_state_machine")
await global_state_machine.set_tool_config(tool_name, body.config)
return {"message": "success"}
@@ -302,8 +302,8 @@ async def delete_tool_config(
_: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.SUPER_ADMINISTRATOR)),
):
"""删除某工具的运行期配置。"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
ok = await global_state_machine.delete_tool_config.remote(tool_name)
global_state_machine = get_actor("global_state_machine")
ok = await global_state_machine.delete_tool_config(tool_name)
if not ok:
raise HTTPException(status_code=404, detail="Tool config not found")
return {"message": "success"}
@@ -343,12 +343,12 @@ async def create_custom_toolset(
body: CustomToolsetCreate,
token_data: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
):
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
global_state_machine = get_actor("global_state_machine")
import uuid
toolset_id = str(uuid.uuid4())[:8]
try:
saved = await global_state_machine.add_custom_toolset.remote(
saved = await global_state_machine.add_custom_toolset(
toolset_id=toolset_id,
name=body.name,
tools=body.tools,
@@ -368,8 +368,8 @@ async def list_custom_toolsets(
"""列出工具组:支持按 category 过滤。USER 只能看到自己的+系统的;ADMIN 看全部。"""
from kilostar.utils.access import get_authority
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
toolsets = await global_state_machine.list_custom_toolsets.remote()
global_state_machine = get_actor("global_state_machine")
toolsets = await global_state_machine.list_custom_toolsets()
authority = await get_authority(token_data.user_id)
if authority < UserAuthority.ADMINISTRATOR:
toolsets = [
@@ -386,8 +386,8 @@ async def get_custom_toolset(
toolset_id: str,
token_data: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
):
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
ts = await global_state_machine.get_custom_toolset.remote(toolset_id)
global_state_machine = get_actor("global_state_machine")
ts = await global_state_machine.get_custom_toolset(toolset_id)
if not ts:
raise HTTPException(status_code=404, detail="Custom toolset not found")
await _assert_toolset_owner_or_admin(ts, token_data)
@@ -400,8 +400,8 @@ async def update_custom_toolset(
body: CustomToolsetUpdate,
token_data: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
):
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
existing = await global_state_machine.get_custom_toolset.remote(toolset_id)
global_state_machine = get_actor("global_state_machine")
existing = await global_state_machine.get_custom_toolset(toolset_id)
if not existing:
raise HTTPException(status_code=404, detail="Custom toolset not found")
if existing.get("is_system"):
@@ -411,7 +411,7 @@ async def update_custom_toolset(
tools = body.tools if body.tools is not None else existing["tools"]
description = body.description if body.description is not None else existing.get("description")
try:
saved = await global_state_machine.add_custom_toolset.remote(
saved = await global_state_machine.add_custom_toolset(
toolset_id=toolset_id,
name=name,
tools=tools,
@@ -429,14 +429,14 @@ async def delete_custom_toolset(
token_data: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER)),
):
"""删除工具组:系统预置不可删;USER 只能删自己的;ADMIN 及以上可删任意用户的。"""
global_state_machine = ray_actor_hook("global_state_machine").global_state_machine
existing = await global_state_machine.get_custom_toolset.remote(toolset_id)
global_state_machine = get_actor("global_state_machine")
existing = await global_state_machine.get_custom_toolset(toolset_id)
if not existing:
raise HTTPException(status_code=404, detail="Custom toolset not found")
if existing.get("is_system"):
raise HTTPException(status_code=403, detail="系统预置工具集不可删除")
await _assert_toolset_owner_or_admin(existing, token_data)
ok = await global_state_machine.delete_custom_toolset.remote(toolset_id)
ok = await global_state_machine.delete_custom_toolset(toolset_id)
if not ok:
raise HTTPException(status_code=404, detail="Custom toolset not found")
return {"message": "success"}
+7 -7
View File
@@ -24,7 +24,7 @@ from __future__ import annotations
from fastapi import APIRouter, Depends
from fastapi.responses import JSONResponse
from kilostar.utils.ray_hook import ray_actor_hook
from kilostar.utils.actor import get_actor
from kilostar.utils.access import Accessor, TokenData, RoleChecker
from kilostar.core.postgres_database.model import UserAuthority
from kilostar.utils.config_loader import (
@@ -49,15 +49,15 @@ async def readiness():
checks = {"postgres": False, "global_state_machine": False}
try:
postgres_database = ray_actor_hook("postgres_database").postgres_database
await postgres_database.ping.remote()
postgres_database = get_actor("postgres_database")
await postgres_database.ping()
checks["postgres"] = True
except Exception:
pass
try:
gsm = ray_actor_hook("global_state_machine").global_state_machine
await gsm.get_skill_list.remote()
gsm = get_actor("global_state_machine")
await gsm.get_skill_list()
checks["global_state_machine"] = True
except Exception:
pass
@@ -95,8 +95,8 @@ async def query_system_logs(
offset: int = 0,
_: TokenData = Depends(Accessor.get_current_user),
):
pg = ray_actor_hook("postgres_database").postgres_database
logs = await pg.query_event_logs.remote(
pg = get_actor("postgres_database")
logs = await pg.query_event_logs(
trace_id=trace_id,
event_type=event_type,
level=level,
+5 -5
View File
@@ -17,7 +17,7 @@ from typing import Optional
from fastapi import APIRouter, Depends, HTTPException
from kilostar.utils.access import Accessor, TokenData
from kilostar.utils.ray_hook import ray_actor_hook
from kilostar.utils.actor import get_actor
task_router = APIRouter(prefix="/api/v1/task", tags=["task"])
@@ -30,8 +30,8 @@ async def list_tasks(
token_data: TokenData = Depends(Accessor.get_current_user),
):
"""列出当前用户的所有短任务,按时间倒序。"""
postgres_database = ray_actor_hook("postgres_database").postgres_database
tasks = await postgres_database.list_tasks_by_user.remote(
postgres_database = get_actor("postgres_database")
tasks = await postgres_database.list_tasks_by_user(
user_id=token_data.user_id,
status=status,
limit=limit,
@@ -46,8 +46,8 @@ async def get_task(
token_data: TokenData = Depends(Accessor.get_current_user),
):
"""按 task_id 读取一条 task 详情。仅 owner 可访问。"""
postgres_database = ray_actor_hook("postgres_database").postgres_database
task = await postgres_database.get_task.remote(task_id)
postgres_database = get_actor("postgres_database")
task = await postgres_database.get_task(task_id)
if not task:
raise HTTPException(status_code=404, detail="task not found")
if task.get("user_id") != token_data.user_id:
+33 -38
View File
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from kilostar.utils.ray_hook import ray_actor_hook
from kilostar.utils.actor import get_actor, spawn_background
from fastapi import APIRouter, Request, HTTPException, Depends
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
@@ -34,22 +34,23 @@ async def create_workflow(
request: CreateWorkflowRequest,
token_data: TokenData = Depends(Accessor.get_current_user),
):
postgres_database = ray_actor_hook("postgres_database").postgres_database
postgres_database = get_actor("postgres_database")
trace_id = str(ULID())
await postgres_database.create_workflow.remote(
await postgres_database.create_workflow(
trace_id=trace_id,
user_id=token_data.user_id,
title=request.title,
command=request.command,
)
global_workflow_manager = ray_actor_hook(
"global_workflow_manager"
).global_workflow_manager
await global_workflow_manager.create_trace.remote(trace_id)
global_workflow_manager = get_actor("global_workflow_manager")
await global_workflow_manager.create_trace(trace_id)
consciousness_node = ray_actor_hook("consciousness_node").consciousness_node
consciousness_node.start_workflow_design.remote(trace_id, request.command)
consciousness_node = get_actor("consciousness_node")
# fire-and-forget:设计过程可能跑很久,不能阻塞 HTTP 响应
spawn_background(
consciousness_node.start_workflow_design(trace_id, request.command)
)
return {"trace_id": trace_id, "status": "creating"}
@@ -57,8 +58,8 @@ async def create_workflow(
@workflow_router.get("/list")
async def get_workflow_list(
token_data: TokenData = Depends(RoleChecker(allowed_roles=UserAuthority.USER))):
postgres_database = ray_actor_hook("postgres_database").postgres_database
workflows = await postgres_database.list_workflows.remote(
postgres_database = get_actor("postgres_database")
workflows = await postgres_database.list_workflows(
user_id=token_data.user_id
)
return {"workflows": workflows}
@@ -75,23 +76,21 @@ async def get_workflow_sse(
鉴权走标准 ``Authorization: Bearer`` 头(前端用 fetch-based SSE,
token 不进 URL)。校验该 trace_id 属于当前用户。
"""
postgres_database = ray_actor_hook("postgres_database").postgres_database
wf = await postgres_database.get_workflow.remote(trace_id)
postgres_database = get_actor("postgres_database")
wf = await postgres_database.get_workflow(trace_id)
if not wf:
raise HTTPException(status_code=404, detail="Workflow not found")
if getattr(wf, "user_id", None) != token_data.user_id:
raise HTTPException(status_code=403, detail="Forbidden")
global_workflow_manager = ray_actor_hook(
"global_workflow_manager"
).global_workflow_manager
global_workflow_manager = get_actor("global_workflow_manager")
async def event_generator():
try:
while True:
if await request.is_disconnected():
break
message = await global_workflow_manager.get_pending.remote(trace_id)
message = await global_workflow_manager.get_pending(trace_id)
if message:
yield f"data: {message}\n\n"
else:
@@ -108,8 +107,8 @@ async def post_workflow_reply(
request: Request,
token_data: TokenData = Depends(Accessor.get_current_user),
):
postgres_database = ray_actor_hook("postgres_database").postgres_database
wf = await postgres_database.get_workflow.remote(trace_id)
postgres_database = get_actor("postgres_database")
wf = await postgres_database.get_workflow(trace_id)
if not wf:
raise HTTPException(status_code=404, detail="Workflow not found")
if getattr(wf, "user_id", None) != token_data.user_id:
@@ -117,10 +116,8 @@ async def post_workflow_reply(
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)
global_workflow_manager = get_actor("global_workflow_manager")
await global_workflow_manager.put_received(trace_id, reply_msg)
return {"status": "ok"}
@@ -128,14 +125,14 @@ async def post_workflow_reply(
async def get_workflow_detail(
trace_id: str, token_data: TokenData = Depends(Accessor.get_current_user)
):
postgres_database = ray_actor_hook("postgres_database").postgres_database
wf = await postgres_database.get_workflow.remote(trace_id)
postgres_database = get_actor("postgres_database")
wf = await postgres_database.get_workflow(trace_id)
if not wf:
raise HTTPException(status_code=404, detail="Workflow not found")
if getattr(wf, "user_id", None) != token_data.user_id:
raise HTTPException(status_code=403, detail="Forbidden")
context = await postgres_database.get_workflow_context.remote(trace_id)
context = await postgres_database.get_workflow_context(trace_id)
work_link = (
context.work_link if context and hasattr(context, "work_link") else []
@@ -209,32 +206,30 @@ async def resume_workflow(
):
"""从 ``workflow_graph_state`` 持久化恢复一个被中断/挂起的工作流。
新 fire 一个 ray task,task 入口的 ``hydrate`` 检查会自动走 resume 路径
新 fire 一个后台任务,入口的 ``hydrate`` 检查会自动走 resume 路径
把剩余节点跑完。
"""
postgres_database = ray_actor_hook("postgres_database").postgres_database
wf = await postgres_database.get_workflow.remote(trace_id)
postgres_database = get_actor("postgres_database")
wf = await postgres_database.get_workflow(trace_id)
if not wf:
raise HTTPException(status_code=404, detail="Workflow not found")
if getattr(wf, "user_id", None) != token_data.user_id:
raise HTTPException(status_code=403, detail="Forbidden")
record = await postgres_database.get_workflow_graph_state.remote(trace_id)
record = await postgres_database.get_workflow_graph_state(trace_id)
if record is None:
raise HTTPException(
status_code=409, detail="该工作流没有可恢复的图持久化记录"
)
global_workflow_manager = ray_actor_hook(
"global_workflow_manager"
).global_workflow_manager
await global_workflow_manager.create_trace.remote(trace_id)
global_workflow_manager = get_actor("global_workflow_manager")
await global_workflow_manager.create_trace(trace_id)
from kilostar.core.work.workflow.workflow_engine import run_workflow_task
# resume_only=True:task 入口 hydrate 失败会 fail-fast,绝不 fall through
# 到"全新模式空跑"。workflow_data 在 resume 路径上不会被使用,传空 dict 占位。
run_workflow_task.remote({}, trace_id, resume_only=True)
spawn_background(run_workflow_task({}, trace_id, resume_only=True))
return {"trace_id": trace_id, "status": "resuming"}
@@ -251,15 +246,15 @@ async def get_workflow_graph_mermaid(
"""
from kilostar.core.work.workflow.workflow_engine import workflow_graph
postgres_database = ray_actor_hook("postgres_database").postgres_database
wf = await postgres_database.get_workflow.remote(trace_id)
postgres_database = get_actor("postgres_database")
wf = await postgres_database.get_workflow(trace_id)
if not wf:
raise HTTPException(status_code=404, detail="Workflow not found")
if getattr(wf, "user_id", None) != token_data.user_id:
raise HTTPException(status_code=403, detail="Forbidden")
visited: list[str] = []
record = await postgres_database.get_workflow_graph_state.remote(trace_id)
record = await postgres_database.get_workflow_graph_state(trace_id)
if record is not None:
history = getattr(record, "history", None) or []
# history 里每条 NodeSnapshot.id 形如 "ClassName:hash",截前缀作为 NodeIdent