feat: organize tools and enable qwen realtime calls
This commit is contained in:
@@ -0,0 +1,329 @@
|
||||
"""Typed, side-effect-free expressions used by built-in state updates.
|
||||
|
||||
Expressions always read one immutable snapshot. Callers validate first, then
|
||||
evaluate every rule, and only commit after all values succeed.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Mapping
|
||||
|
||||
|
||||
MAX_EXPRESSION_DEPTH = 8
|
||||
MAX_UPDATE_RULES = 10
|
||||
MAX_LLM_PARAMETERS = 10
|
||||
|
||||
|
||||
class StateExpressionError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def _field(value: Mapping[str, Any], snake: str, camel: str | None = None) -> Any:
|
||||
if snake in value:
|
||||
return value[snake]
|
||||
return value.get(camel or snake)
|
||||
|
||||
|
||||
def _value_type(value: Any) -> str:
|
||||
if isinstance(value, bool):
|
||||
return "boolean"
|
||||
if isinstance(value, (int, float)):
|
||||
return "number"
|
||||
if isinstance(value, str):
|
||||
return "string"
|
||||
raise StateExpressionError("状态表达式只支持字符串、数字或布尔值")
|
||||
|
||||
|
||||
def _definition_types(
|
||||
definitions: Mapping[str, Mapping[str, Any]] | None,
|
||||
) -> dict[str, str]:
|
||||
result: dict[str, str] = {}
|
||||
for name, definition in (definitions or {}).items():
|
||||
value_type = (
|
||||
(definition or {}).get("type")
|
||||
if isinstance(definition, Mapping)
|
||||
else getattr(definition, "type", None)
|
||||
)
|
||||
result[str(name)] = str(value_type or "string")
|
||||
return result
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LlmParameter:
|
||||
name: str
|
||||
value_type: str
|
||||
description: str
|
||||
|
||||
|
||||
def _inspect_expression(
|
||||
expression: Mapping[str, Any],
|
||||
variable_types: Mapping[str, str],
|
||||
*,
|
||||
allow_llm: bool,
|
||||
depth: int,
|
||||
parameters: dict[str, LlmParameter],
|
||||
) -> str:
|
||||
if depth > MAX_EXPRESSION_DEPTH:
|
||||
raise StateExpressionError(
|
||||
f"状态表达式深度不能超过 {MAX_EXPRESSION_DEPTH}"
|
||||
)
|
||||
kind = str(expression.get("type") or "")
|
||||
if kind == "constant":
|
||||
return _value_type(expression.get("value"))
|
||||
if kind == "variable":
|
||||
name = str(_field(expression, "variable_name", "variableName") or "")
|
||||
if name not in variable_types:
|
||||
raise StateExpressionError(f"状态表达式引用了未声明变量: {name}")
|
||||
return variable_types[name]
|
||||
if kind == "llm":
|
||||
if not allow_llm:
|
||||
raise StateExpressionError("确定性 Update State 节点不能使用 LLM 提取值")
|
||||
name = str(_field(expression, "parameter_name", "parameterName") or "")
|
||||
value_type = str(_field(expression, "value_type", "valueType") or "string")
|
||||
description = str(expression.get("description") or "").strip()
|
||||
if not name or value_type not in {"string", "number", "boolean"}:
|
||||
raise StateExpressionError("LLM 提取参数名称或类型无效")
|
||||
if not description:
|
||||
raise StateExpressionError(f"LLM 提取参数 {name} 必须填写说明")
|
||||
current = LlmParameter(name, value_type, description)
|
||||
previous = parameters.get(name)
|
||||
if previous is not None and previous != current:
|
||||
raise StateExpressionError(f"LLM 参数 {name} 存在冲突定义")
|
||||
parameters[name] = current
|
||||
if len(parameters) > MAX_LLM_PARAMETERS:
|
||||
raise StateExpressionError(
|
||||
f"LLM 提取参数不能超过 {MAX_LLM_PARAMETERS} 个"
|
||||
)
|
||||
return value_type
|
||||
if kind == "if":
|
||||
condition = expression.get("condition")
|
||||
then = expression.get("then")
|
||||
otherwise = _field(expression, "otherwise")
|
||||
if not all(isinstance(item, Mapping) for item in (condition, then, otherwise)):
|
||||
raise StateExpressionError("if 表达式缺少 condition/then/otherwise")
|
||||
condition_type = _inspect_expression(
|
||||
condition,
|
||||
variable_types,
|
||||
allow_llm=allow_llm,
|
||||
depth=depth + 1,
|
||||
parameters=parameters,
|
||||
)
|
||||
if condition_type != "boolean":
|
||||
raise StateExpressionError("if 条件必须是布尔值")
|
||||
then_type = _inspect_expression(
|
||||
then,
|
||||
variable_types,
|
||||
allow_llm=allow_llm,
|
||||
depth=depth + 1,
|
||||
parameters=parameters,
|
||||
)
|
||||
otherwise_type = _inspect_expression(
|
||||
otherwise,
|
||||
variable_types,
|
||||
allow_llm=allow_llm,
|
||||
depth=depth + 1,
|
||||
parameters=parameters,
|
||||
)
|
||||
if then_type != otherwise_type:
|
||||
raise StateExpressionError("if 的两个结果分支类型必须一致")
|
||||
return then_type
|
||||
|
||||
operands = expression.get("operands")
|
||||
if kind not in {
|
||||
"and", "or", "eq", "neq", "gt", "gte", "lt", "lte",
|
||||
"add", "subtract", "multiply", "divide",
|
||||
} or not isinstance(operands, list) or len(operands) < 2:
|
||||
raise StateExpressionError(f"状态表达式运算无效: {kind or 'empty'}")
|
||||
if not all(isinstance(item, Mapping) for item in operands):
|
||||
raise StateExpressionError(f"{kind} 的操作数必须是表达式")
|
||||
operand_types = [
|
||||
_inspect_expression(
|
||||
item,
|
||||
variable_types,
|
||||
allow_llm=allow_llm,
|
||||
depth=depth + 1,
|
||||
parameters=parameters,
|
||||
)
|
||||
for item in operands
|
||||
]
|
||||
if kind in {"and", "or"}:
|
||||
if any(item != "boolean" for item in operand_types):
|
||||
raise StateExpressionError(f"{kind} 只能组合布尔值")
|
||||
return "boolean"
|
||||
if kind in {"eq", "neq"}:
|
||||
if len(set(operand_types)) != 1:
|
||||
raise StateExpressionError(f"{kind} 的操作数类型必须一致")
|
||||
return "boolean"
|
||||
if kind in {"gt", "gte", "lt", "lte"}:
|
||||
if any(item != "number" for item in operand_types):
|
||||
raise StateExpressionError(f"{kind} 只能比较数字")
|
||||
return "boolean"
|
||||
if kind == "add" and len(set(operand_types)) == 1:
|
||||
result_type = operand_types[0]
|
||||
if result_type in {"number", "string"}:
|
||||
return result_type
|
||||
if any(item != "number" for item in operand_types):
|
||||
raise StateExpressionError(f"{kind} 只能用于数字")
|
||||
return "number"
|
||||
|
||||
|
||||
def validate_update_rules(
|
||||
rules: list[Mapping[str, Any]] | None,
|
||||
definitions: Mapping[str, Mapping[str, Any]] | None,
|
||||
*,
|
||||
allow_llm: bool,
|
||||
) -> dict[str, dict[str, str]]:
|
||||
"""Validate rules and return the LLM-visible JSON schema properties."""
|
||||
|
||||
rules = rules or []
|
||||
if len(rules) > MAX_UPDATE_RULES:
|
||||
raise StateExpressionError(
|
||||
f"状态更新规则不能超过 {MAX_UPDATE_RULES} 条"
|
||||
)
|
||||
variable_types = _definition_types(definitions)
|
||||
parameters: dict[str, LlmParameter] = {}
|
||||
targets: set[str] = set()
|
||||
llm_target_parameters: dict[str, str] = {}
|
||||
for rule in rules:
|
||||
target = str(_field(rule, "variable_name", "variableName") or "")
|
||||
expression = rule.get("expression")
|
||||
if target not in variable_types:
|
||||
raise StateExpressionError(f"状态更新目标未声明: {target}")
|
||||
if target in targets:
|
||||
raise StateExpressionError(f"状态更新目标不能重复: {target}")
|
||||
targets.add(target)
|
||||
if not isinstance(expression, Mapping):
|
||||
raise StateExpressionError(f"状态更新 {target} 缺少表达式")
|
||||
result_type = _inspect_expression(
|
||||
expression,
|
||||
variable_types,
|
||||
allow_llm=allow_llm,
|
||||
depth=1,
|
||||
parameters=parameters,
|
||||
)
|
||||
if result_type != variable_types[target]:
|
||||
raise StateExpressionError(
|
||||
f"状态更新 {target} 的结果类型应为 {variable_types[target]}"
|
||||
)
|
||||
if allow_llm:
|
||||
llm_names: set[str] = set()
|
||||
_collect_llm_names(expression, llm_names)
|
||||
for parameter_name in llm_names:
|
||||
previous_target = llm_target_parameters.get(parameter_name)
|
||||
if previous_target is not None and previous_target != target:
|
||||
raise StateExpressionError(
|
||||
f"LLM 参数 {parameter_name} 不能同时更新多个变量"
|
||||
)
|
||||
llm_target_parameters[parameter_name] = target
|
||||
return {
|
||||
parameter.name: {
|
||||
"type": parameter.value_type,
|
||||
"description": parameter.description,
|
||||
}
|
||||
for parameter in parameters.values()
|
||||
}
|
||||
|
||||
|
||||
def _collect_llm_names(expression: Mapping[str, Any], result: set[str]) -> None:
|
||||
kind = str(expression.get("type") or "")
|
||||
if kind == "llm":
|
||||
result.add(
|
||||
str(_field(expression, "parameter_name", "parameterName") or "")
|
||||
)
|
||||
return
|
||||
if kind == "if":
|
||||
children = [
|
||||
expression.get("condition"),
|
||||
expression.get("then"),
|
||||
expression.get("otherwise"),
|
||||
]
|
||||
else:
|
||||
children = expression.get("operands") or []
|
||||
for child in children:
|
||||
if isinstance(child, Mapping):
|
||||
_collect_llm_names(child, result)
|
||||
|
||||
|
||||
def _evaluate(
|
||||
expression: Mapping[str, Any],
|
||||
snapshot: Mapping[str, Any],
|
||||
arguments: Mapping[str, Any],
|
||||
) -> Any:
|
||||
kind = str(expression.get("type") or "")
|
||||
if kind == "constant":
|
||||
return expression.get("value")
|
||||
if kind == "variable":
|
||||
name = str(_field(expression, "variable_name", "variableName") or "")
|
||||
if name not in snapshot:
|
||||
raise StateExpressionError(f"状态变量尚未赋值: {name}")
|
||||
return snapshot[name]
|
||||
if kind == "llm":
|
||||
name = str(_field(expression, "parameter_name", "parameterName") or "")
|
||||
if name not in arguments:
|
||||
raise StateExpressionError(f"缺少 LLM 提取参数: {name}")
|
||||
return arguments[name]
|
||||
if kind == "if":
|
||||
branch = "then" if bool(
|
||||
_evaluate(expression["condition"], snapshot, arguments)
|
||||
) else "otherwise"
|
||||
return _evaluate(expression[branch], snapshot, arguments)
|
||||
|
||||
values = [
|
||||
_evaluate(item, snapshot, arguments)
|
||||
for item in expression.get("operands") or []
|
||||
]
|
||||
if kind == "and":
|
||||
return all(values)
|
||||
if kind == "or":
|
||||
return any(values)
|
||||
if kind == "eq":
|
||||
return all(value == values[0] for value in values[1:])
|
||||
if kind == "neq":
|
||||
return all(value != values[0] for value in values[1:])
|
||||
if kind == "gt":
|
||||
return all(left > right for left, right in zip(values, values[1:]))
|
||||
if kind == "gte":
|
||||
return all(left >= right for left, right in zip(values, values[1:]))
|
||||
if kind == "lt":
|
||||
return all(left < right for left, right in zip(values, values[1:]))
|
||||
if kind == "lte":
|
||||
return all(left <= right for left, right in zip(values, values[1:]))
|
||||
if kind == "add":
|
||||
result = values[0]
|
||||
for value in values[1:]:
|
||||
result += value
|
||||
return result
|
||||
if kind == "subtract":
|
||||
result = values[0]
|
||||
for value in values[1:]:
|
||||
result -= value
|
||||
return result
|
||||
if kind == "multiply":
|
||||
result = values[0]
|
||||
for value in values[1:]:
|
||||
result *= value
|
||||
return result
|
||||
if kind == "divide":
|
||||
result = values[0]
|
||||
for value in values[1:]:
|
||||
if value == 0:
|
||||
raise StateExpressionError("状态表达式不能除以零")
|
||||
result /= value
|
||||
return result
|
||||
raise StateExpressionError(f"不支持的状态表达式: {kind}")
|
||||
|
||||
|
||||
def evaluate_update_rules(
|
||||
rules: list[Mapping[str, Any]] | None,
|
||||
snapshot: Mapping[str, Any],
|
||||
arguments: Mapping[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Evaluate every rule from the same snapshot without mutating state."""
|
||||
|
||||
return {
|
||||
str(_field(rule, "variable_name", "variableName")): _evaluate(
|
||||
rule["expression"], snapshot, arguments or {}
|
||||
)
|
||||
for rule in rules or []
|
||||
}
|
||||
Reference in New Issue
Block a user