feat: add prompt startup actions and shared vision config
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import uuid
|
||||
|
||||
from models import RuntimeTool
|
||||
from db.models import (
|
||||
Assistant,
|
||||
AssistantModelBinding,
|
||||
@@ -16,6 +17,7 @@ from schemas import AssistantOut, AssistantUpsert
|
||||
from services.auth import require_admin
|
||||
from services.masking import mask, resolve_incoming_key
|
||||
from services.node_specs import graph_references, normalize_graph, validate_graph
|
||||
from services.tool_policy import policy_for_tool
|
||||
from services.workflow_engine import WorkflowEngine
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@@ -110,6 +112,57 @@ async def _validate_workflow_references(
|
||||
raise HTTPException(400, f"Workflow 引用了无效知识库:{knowledge_id}")
|
||||
|
||||
|
||||
async def _validate_startup_actions(
|
||||
session: AsyncSession,
|
||||
body: AssistantUpsert,
|
||||
) -> None:
|
||||
"""Keep startup deterministic and reject unsupported lifecycle/tool pairs."""
|
||||
|
||||
actions = body.startup.actions
|
||||
if not actions:
|
||||
return
|
||||
bound_tool_ids = set(body.tool_ids)
|
||||
for action in actions:
|
||||
if action.tool_id not in bound_tool_ids:
|
||||
raise HTTPException(400, f"启动 Action 必须先绑定工具:{action.tool_id}")
|
||||
tool = await session.get(Tool, action.tool_id)
|
||||
if not tool or tool.status != "active":
|
||||
raise HTTPException(400, f"启动 Action 引用了无效工具:{action.tool_id}")
|
||||
if action.phase == "preflight" and tool.type not in {"http", "mcp"}:
|
||||
raise HTTPException(400, "preflight Action 仅支持 HTTP 或 MCP 工具")
|
||||
if action.phase == "opening" and tool.type not in {"http", "mcp", "client"}:
|
||||
raise HTTPException(400, "opening Action 不支持该工具类型")
|
||||
if tool.type != "client":
|
||||
continue
|
||||
runtime_tool = RuntimeTool(
|
||||
id=tool.id,
|
||||
name=tool.name,
|
||||
function_name=tool.function_name,
|
||||
type=tool.type,
|
||||
definition=tool.definition or {},
|
||||
)
|
||||
policy = policy_for_tool(runtime_tool)
|
||||
if action.required and not policy.wait_for_response:
|
||||
raise HTTPException(400, "必需的 Client 启动 Action 必须等待客户端响应")
|
||||
if tool.function_name != "show_message":
|
||||
continue
|
||||
if policy.response_wait_mode != "session":
|
||||
raise HTTPException(400, "show_message 启动 Action 必须使用会话内等待")
|
||||
message = str(action.arguments.get("message") or "").strip()
|
||||
buttons = action.arguments.get("actions")
|
||||
if not message or len(message) > 2000:
|
||||
raise HTTPException(400, "show_message 重要信息必须为 1-2000 个字符")
|
||||
if not isinstance(buttons, list) or not buttons:
|
||||
raise HTTPException(400, "show_message 至少需要一个确认按钮")
|
||||
first_button = buttons[0] if isinstance(buttons[0], dict) else {}
|
||||
if not str(first_button.get("id") or "").strip() or not str(
|
||||
first_button.get("label") or ""
|
||||
).strip():
|
||||
raise HTTPException(400, "show_message 确认按钮必须配置 id 和文字")
|
||||
if action.required and action.arguments.get("dismissible") is not False:
|
||||
raise HTTPException(400, "必需的 show_message 启动 Action 不允许跳过确认")
|
||||
|
||||
|
||||
async def _validate_vision_model(
|
||||
session: AsyncSession, body: AssistantUpsert
|
||||
) -> None:
|
||||
@@ -228,6 +281,7 @@ async def _to_out(session: AsyncSession, assistant: Assistant) -> AssistantOut:
|
||||
greeting=assistant.greeting,
|
||||
enable_interrupt=assistant.enable_interrupt,
|
||||
turn_config=assistant.turn_config or {},
|
||||
startup=assistant.startup or {},
|
||||
vision_enabled=assistant.vision_enabled,
|
||||
vision_model_resource_id=assistant.vision_model_resource_id,
|
||||
model_resource_ids=await _resource_ids(session, assistant.id),
|
||||
@@ -262,6 +316,7 @@ async def create_assistant(
|
||||
):
|
||||
_validate_workflow(body)
|
||||
await _validate_workflow_references(session, body)
|
||||
await _validate_startup_actions(session, body)
|
||||
await _validate_vision_model(session, body)
|
||||
await _validate_knowledge_base(session, body)
|
||||
data = body.model_dump()
|
||||
@@ -302,6 +357,7 @@ async def duplicate_assistant(
|
||||
greeting=source.greeting,
|
||||
enable_interrupt=source.enable_interrupt,
|
||||
turn_config=dict(source.turn_config or {}),
|
||||
startup=dict(source.startup or {}),
|
||||
vision_enabled=source.vision_enabled,
|
||||
vision_model_resource_id=source.vision_model_resource_id,
|
||||
knowledge_base_id=source.knowledge_base_id,
|
||||
@@ -335,6 +391,7 @@ async def update_assistant(
|
||||
raise HTTPException(404, "助手不存在")
|
||||
_validate_workflow(body)
|
||||
await _validate_workflow_references(session, body)
|
||||
await _validate_startup_actions(session, body)
|
||||
await _validate_vision_model(session, body)
|
||||
await _validate_knowledge_base(session, body)
|
||||
data = body.model_dump()
|
||||
|
||||
Reference in New Issue
Block a user