Add workflow support and enhance runtime configuration in models and services
- Introduce RuntimeModelResource and RuntimeKnowledgeBase classes to manage workflow resources. - Update AssistantConfig to include workflow_model_resources and workflow_knowledge_bases for better integration. - Refactor validation and processing logic in routes and services to accommodate workflow types. - Implement dynamic variable support for workflow assistants and enhance graph normalization. - Add ToolExecutor for reusable tool execution across different assistant types. - Update various services to ensure compatibility with new workflow features and improve error handling.
This commit is contained in:
@@ -15,7 +15,7 @@ from fastapi import APIRouter, Depends, HTTPException
|
||||
from schemas import AssistantOut, AssistantUpsert
|
||||
from services.auth import require_admin
|
||||
from services.masking import mask, resolve_incoming_key
|
||||
from services.node_specs import validate_graph
|
||||
from services.node_specs import graph_references, normalize_graph, validate_graph
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
@@ -31,9 +31,45 @@ def _validate_workflow(body: AssistantUpsert) -> None:
|
||||
"""workflow 类型:保存前校验图结构,不通过则 400。其他类型跳过。"""
|
||||
if body.type != "workflow":
|
||||
return
|
||||
errors = validate_graph(body.graph or {})
|
||||
body.graph = normalize_graph(body.graph or {})
|
||||
errors = validate_graph(body.graph)
|
||||
if errors:
|
||||
raise HTTPException(400, "工作流校验失败:" + ";".join(errors))
|
||||
refs = graph_references(body.graph)
|
||||
body.tool_ids = list(dict.fromkeys([*body.tool_ids, *sorted(refs["tools"])]))
|
||||
|
||||
|
||||
async def _validate_workflow_references(
|
||||
session: AsyncSession, body: AssistantUpsert
|
||||
) -> None:
|
||||
if body.type != "workflow" or not body.graph.get("nodes"):
|
||||
return
|
||||
graph = body.graph
|
||||
settings = graph.get("settings") or {}
|
||||
resource_expectations: dict[str, str] = {}
|
||||
for key, capability in (
|
||||
("defaultAsrResourceId", "ASR"),
|
||||
("defaultTtsResourceId", "TTS"),
|
||||
):
|
||||
if settings.get(key):
|
||||
resource_expectations[str(settings[key])] = capability
|
||||
knowledge_ids: set[str] = set()
|
||||
for node in graph.get("nodes") or []:
|
||||
data = node.get("data") or {}
|
||||
if data.get("asrResourceId"):
|
||||
resource_expectations[str(data["asrResourceId"])] = "ASR"
|
||||
if data.get("ttsResourceId"):
|
||||
resource_expectations[str(data["ttsResourceId"])] = "TTS"
|
||||
if data.get("knowledgeBaseId"):
|
||||
knowledge_ids.add(str(data["knowledgeBaseId"]))
|
||||
for resource_id, capability in resource_expectations.items():
|
||||
resource = await session.get(ModelResource, resource_id)
|
||||
if not resource or not resource.enabled or resource.capability != capability:
|
||||
raise HTTPException(400, f"Workflow 引用了无效的 {capability} 资源:{resource_id}")
|
||||
for knowledge_id in knowledge_ids:
|
||||
knowledge = await session.get(KnowledgeBase, knowledge_id)
|
||||
if not knowledge or knowledge.status != "active":
|
||||
raise HTTPException(400, f"Workflow 引用了无效知识库:{knowledge_id}")
|
||||
|
||||
|
||||
async def _validate_vision_model(
|
||||
@@ -101,7 +137,11 @@ async def _resource_ids(session: AsyncSession, assistant_id: str) -> dict[str, s
|
||||
async def _sync_tool_bindings(
|
||||
session: AsyncSession, assistant_id: str, assistant_type: str, tool_ids: list[str]
|
||||
) -> None:
|
||||
requested = list(dict.fromkeys(tool_ids)) if assistant_type == "prompt" else []
|
||||
requested = (
|
||||
list(dict.fromkeys(tool_ids))
|
||||
if assistant_type in {"prompt", "workflow"}
|
||||
else []
|
||||
)
|
||||
if requested:
|
||||
tools = (
|
||||
await session.execute(select(Tool).where(Tool.id.in_(requested)))
|
||||
@@ -158,7 +198,11 @@ async def _to_out(session: AsyncSession, assistant: Assistant) -> AssistantOut:
|
||||
api_url=assistant.api_url,
|
||||
api_key=mask(assistant.api_key),
|
||||
app_id=assistant.app_id,
|
||||
graph=assistant.graph or {},
|
||||
graph=(
|
||||
normalize_graph(assistant.graph or {})
|
||||
if assistant.type == "workflow"
|
||||
else {}
|
||||
),
|
||||
updated_at=assistant.updated_at.isoformat() if assistant.updated_at else None,
|
||||
)
|
||||
|
||||
@@ -176,6 +220,7 @@ async def create_assistant(
|
||||
body: AssistantUpsert, session: AsyncSession = Depends(get_session)
|
||||
):
|
||||
_validate_workflow(body)
|
||||
await _validate_workflow_references(session, body)
|
||||
await _validate_vision_model(session, body)
|
||||
await _validate_knowledge_base(session, body)
|
||||
data = body.model_dump()
|
||||
@@ -248,6 +293,7 @@ async def update_assistant(
|
||||
if not assistant:
|
||||
raise HTTPException(404, "助手不存在")
|
||||
_validate_workflow(body)
|
||||
await _validate_workflow_references(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