feat: organize tools and enable qwen realtime calls
This commit is contained in:
@@ -11,7 +11,7 @@ from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
from loguru import logger
|
||||
from models import AssistantConfig, RuntimeTool
|
||||
from models import AssistantConfig, RuntimeTool, RuntimeToolBinding
|
||||
from db.session import SessionLocal
|
||||
from pipecat.flows import (
|
||||
ContextStrategy,
|
||||
@@ -61,7 +61,8 @@ from services.message_stage import (
|
||||
confirmation_context_message,
|
||||
)
|
||||
from services.runtime_variables import DynamicVariableError, DynamicVariableStore
|
||||
from services.system_tools import state_update_properties, system_tool_kind
|
||||
from services.system_tools import runtime_system_tools, state_update_properties, system_tool_kind
|
||||
from services.state_expressions import evaluate_update_rules
|
||||
from services.tool_executor import ToolExecutionError, ToolExecutor
|
||||
from services.tool_policy import policy_for_tool
|
||||
from services.workflow.agent import WorkflowAgentStage
|
||||
@@ -654,20 +655,26 @@ class WorkflowBrain(BaseBrain):
|
||||
registered_names.add(function_name)
|
||||
functions.append(function)
|
||||
|
||||
for tool_id in stage.tool_ids:
|
||||
tool = self._tool_by_id.get(str(tool_id))
|
||||
for raw_binding in stage.tool_bindings:
|
||||
binding = RuntimeToolBinding(**raw_binding)
|
||||
tool = self._tool_by_id.get(binding.tool_id)
|
||||
if not tool:
|
||||
continue
|
||||
if tool.type == "system":
|
||||
append_function(
|
||||
self._workflow_system_tool(
|
||||
tool,
|
||||
node_id=node_id,
|
||||
state_variable_names=stage.state_variable_names,
|
||||
)
|
||||
)
|
||||
elif tool.type in {"http", "mcp", "client"}:
|
||||
append_function(self._flow_tool(tool, node_id))
|
||||
if (
|
||||
tool.type == "client"
|
||||
and self._require_runtime().client_tools is not None
|
||||
and getattr(
|
||||
self._require_runtime().client_tools,
|
||||
"supports",
|
||||
lambda _name: True,
|
||||
)(tool.function_name) is False
|
||||
):
|
||||
logger.warning(f"跳过客户端未上报的工具: {tool.function_name}")
|
||||
continue
|
||||
if tool.type in {"http", "mcp", "client"}:
|
||||
append_function(self._flow_tool(tool, node_id, binding))
|
||||
for tool in runtime_system_tools(stage.built_in_tools):
|
||||
append_function(self._workflow_system_tool(tool, node_id=node_id))
|
||||
append_function(self._knowledge_function(node_id))
|
||||
if stage.vision_enabled and self._require_runtime().vision_function:
|
||||
vision_function = self._require_runtime().vision_function
|
||||
@@ -765,9 +772,13 @@ class WorkflowBrain(BaseBrain):
|
||||
"respond_immediately": False,
|
||||
}
|
||||
|
||||
def _flow_tool(self, tool: RuntimeTool, node_id: str) -> FlowsFunctionSchema:
|
||||
properties, required = self._tools.schema_parts(tool)
|
||||
self._tools.register_secrets(tool)
|
||||
def _flow_tool(
|
||||
self,
|
||||
tool: RuntimeTool,
|
||||
node_id: str,
|
||||
binding: RuntimeToolBinding,
|
||||
) -> FlowsFunctionSchema:
|
||||
properties, required = self._tools.schema_parts(tool, binding)
|
||||
policy = policy_for_tool(tool)
|
||||
|
||||
async def handler(args, _flow_manager):
|
||||
@@ -784,7 +795,11 @@ class WorkflowBrain(BaseBrain):
|
||||
toolType=tool.type,
|
||||
)
|
||||
try:
|
||||
result = await self._tools.execute(tool, dict(args or {}))
|
||||
result = await self._tools.execute(
|
||||
tool,
|
||||
dict(args or {}),
|
||||
binding=binding,
|
||||
)
|
||||
except ToolExecutionError as exc:
|
||||
await self._emit_trace(
|
||||
"tool_failed",
|
||||
@@ -935,7 +950,6 @@ class WorkflowBrain(BaseBrain):
|
||||
tool: RuntimeTool,
|
||||
*,
|
||||
node_id: str,
|
||||
state_variable_names: tuple[str, ...],
|
||||
) -> FlowsFunctionSchema:
|
||||
"""Build one platform-owned tool scoped to the active Agent node."""
|
||||
kind = system_tool_kind(tool.definition or {})
|
||||
@@ -943,7 +957,6 @@ class WorkflowBrain(BaseBrain):
|
||||
schema = self._workflow_update_state_tool(
|
||||
tool,
|
||||
node_id,
|
||||
state_variable_names=state_variable_names,
|
||||
)
|
||||
elif kind == "skip_turn":
|
||||
schema = self._workflow_skip_turn_tool(tool)
|
||||
@@ -962,22 +975,19 @@ class WorkflowBrain(BaseBrain):
|
||||
self,
|
||||
tool: RuntimeTool,
|
||||
node_id: str,
|
||||
*,
|
||||
state_variable_names: tuple[str, ...],
|
||||
) -> FlowsFunctionSchema:
|
||||
allowed = frozenset(state_variable_names)
|
||||
config = (tool.definition or {}).get("config") or {}
|
||||
rules = list(config.get("updates") or [])
|
||||
|
||||
async def handler(args, _flow_manager):
|
||||
values = dict(args or {})
|
||||
unauthorized = sorted(set(values) - allowed)
|
||||
if unauthorized:
|
||||
return {
|
||||
"status": "error",
|
||||
"message": "状态变量未获当前节点授权: " + ",".join(unauthorized),
|
||||
}
|
||||
try:
|
||||
values = evaluate_update_rules(
|
||||
rules,
|
||||
dict(self._store.values),
|
||||
dict(args or {}),
|
||||
)
|
||||
changed = self._store.assign_declared_many(values)
|
||||
except DynamicVariableError as exc:
|
||||
except (DynamicVariableError, ValueError) as exc:
|
||||
return {"status": "error", "message": f"状态更新失败: {exc}"}
|
||||
result = {
|
||||
"status": "success",
|
||||
@@ -1009,7 +1019,7 @@ class WorkflowBrain(BaseBrain):
|
||||
),
|
||||
properties=state_update_properties(
|
||||
self._cfg.dynamic_variable_definitions if self._cfg else {},
|
||||
allowed_names=state_variable_names,
|
||||
rules=rules,
|
||||
),
|
||||
required=[],
|
||||
handler=handler,
|
||||
@@ -1311,11 +1321,9 @@ class WorkflowBrain(BaseBrain):
|
||||
"""Apply one deterministic, atomic dynamic-variable update."""
|
||||
self._state.enter(node_id, WorkflowStatus.RUNNING_ACTION)
|
||||
await self._emit_node_active(node_id)
|
||||
raw_assignments = self._engine.data(node_id).get("assignments") or {}
|
||||
rendered = self._store.render_data(raw_assignments)
|
||||
if not isinstance(rendered, dict):
|
||||
raise DynamicVariableError("Update State 节点赋值必须是对象")
|
||||
changed = self._store.assign_declared_many(rendered)
|
||||
updates = self._engine.data(node_id).get("updates") or []
|
||||
values = evaluate_update_rules(updates, dict(self._store.values))
|
||||
changed = self._store.assign_declared_many(values)
|
||||
if changed:
|
||||
await self._emit_variables(
|
||||
reason="update_state",
|
||||
@@ -1461,8 +1469,10 @@ class WorkflowBrain(BaseBrain):
|
||||
data = self._engine.data(node_id)
|
||||
runtime = self._require_runtime()
|
||||
invocation_id = self._actions.new_invocation_id()
|
||||
tool_id = str(data.get("toolId") or "")
|
||||
tool = self._tool_by_id.get(tool_id)
|
||||
raw_binding = data.get("toolBinding") or {}
|
||||
binding = RuntimeToolBinding(**raw_binding)
|
||||
tool_id = binding.tool_id
|
||||
tool = self._tool_by_id.get(binding.tool_id)
|
||||
try:
|
||||
stage_result = await self._action_stages.run(
|
||||
ActionStageSpec(
|
||||
@@ -1470,8 +1480,7 @@ class WorkflowBrain(BaseBrain):
|
||||
StageAction(
|
||||
id=node_id,
|
||||
tool=tool,
|
||||
arguments=data.get("arguments") or {},
|
||||
result_assignments=self._action_result_assignments(data),
|
||||
binding=binding,
|
||||
invocation_id=invocation_id,
|
||||
),
|
||||
),
|
||||
@@ -1633,23 +1642,6 @@ class WorkflowBrain(BaseBrain):
|
||||
}[outcome.status]
|
||||
await self._emit_trace(event, nodeId=node_id, outcome=outcome.trace_payload())
|
||||
|
||||
@staticmethod
|
||||
def _action_result_assignments(
|
||||
data: dict[str, Any],
|
||||
) -> dict[str, str] | None:
|
||||
"""Resolve node mapping semantics for ToolExecutor.
|
||||
|
||||
``None`` means inherit the reusable tool's mapping, while an empty
|
||||
dictionary explicitly disables all result assignments.
|
||||
"""
|
||||
mode = str(data.get("resultAssignmentMode") or "none")
|
||||
if mode == "inherit":
|
||||
return None
|
||||
if mode == "override":
|
||||
assignments = data.get("resultAssignments")
|
||||
return dict(assignments) if isinstance(assignments, dict) else {}
|
||||
return {}
|
||||
|
||||
async def _enter_handoff(self, node_id: str) -> None:
|
||||
self._state.enter(node_id, WorkflowStatus.HANDOFF)
|
||||
await self._emit_node_active(node_id)
|
||||
|
||||
Reference in New Issue
Block a user