426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
228 lines
7.6 KiB
Python
228 lines
7.6 KiB
Python
"""BFCL tool schema helpers.
|
|
|
|
This module provides the small adapter surface used by the BFCL runner and
|
|
the shared eliza benchmark bridge. It intentionally avoids depending on an
|
|
elizaOS runtime so mock and smoke-test paths can import cleanly.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
from benchmarks.bfcl.types import FunctionCall, FunctionDefinition
|
|
|
|
|
|
_JSON_SCHEMA_TYPE_ALIASES = {
|
|
"boolean": "boolean",
|
|
"bool": "boolean",
|
|
"dict": "object",
|
|
"double": "number",
|
|
"float": "number",
|
|
"integer": "integer",
|
|
"int": "integer",
|
|
"list": "array",
|
|
"none": "null",
|
|
"number": "number",
|
|
"object": "object",
|
|
"str": "string",
|
|
"string": "string",
|
|
"tuple": "array",
|
|
}
|
|
|
|
_UNCONSTRAINED_SCHEMA_TYPES = {"any"}
|
|
|
|
|
|
def _normalize_schema(schema: Any) -> Any:
|
|
"""Convert BFCL/Python-ish schema fragments to JSON Schema types."""
|
|
if isinstance(schema, list):
|
|
return [_normalize_schema(item) for item in schema]
|
|
if not isinstance(schema, dict):
|
|
return schema
|
|
|
|
normalized: dict[str, Any] = {}
|
|
normalized_type: str | list[str] | None = None
|
|
for key, value in schema.items():
|
|
if key == "type":
|
|
if isinstance(value, str):
|
|
type_name = value.lower()
|
|
if type_name in _UNCONSTRAINED_SCHEMA_TYPES:
|
|
continue
|
|
normalized_type = _JSON_SCHEMA_TYPE_ALIASES.get(type_name, "string")
|
|
normalized[key] = normalized_type
|
|
elif isinstance(value, list):
|
|
normalized_types = [
|
|
_JSON_SCHEMA_TYPE_ALIASES.get(str(item).lower(), "string")
|
|
for item in value
|
|
if str(item).lower() not in _UNCONSTRAINED_SCHEMA_TYPES
|
|
]
|
|
if normalized_types:
|
|
normalized_type = normalized_types
|
|
normalized[key] = normalized_type
|
|
else:
|
|
normalized[key] = value
|
|
elif key in {"items", "properties", "additionalProperties"}:
|
|
normalized[key] = _normalize_schema(value)
|
|
else:
|
|
normalized[key] = _normalize_schema(value)
|
|
|
|
# BFCL contains Python-ish fragments such as {"type": "string",
|
|
# "items": {"type": "string"}} for list fields. Strict OpenAI-compatible
|
|
# providers reject that invalid JSON Schema. Prefer the structural hints
|
|
# over the stale scalar type so the schema remains scoreable.
|
|
if "properties" in normalized:
|
|
normalized["type"] = "object"
|
|
normalized_type = "object"
|
|
elif "items" in normalized:
|
|
normalized["type"] = "array"
|
|
normalized_type = "array"
|
|
|
|
if normalized_type != "array":
|
|
normalized.pop("items", None)
|
|
elif "items" not in normalized:
|
|
normalized["items"] = {}
|
|
|
|
if normalized_type != "object":
|
|
normalized.pop("properties", None)
|
|
normalized.pop("additionalProperties", None)
|
|
return normalized
|
|
|
|
|
|
def _coerce_default(schema_type: str | None, default: Any) -> Any:
|
|
"""Return a default only when it is valid for the JSON Schema type."""
|
|
if default is None:
|
|
return None
|
|
if schema_type == "boolean":
|
|
if isinstance(default, bool):
|
|
return default
|
|
if isinstance(default, str):
|
|
lowered = default.strip().lower()
|
|
if lowered in {"true", "false"}:
|
|
return lowered == "true"
|
|
return None
|
|
if schema_type == "integer":
|
|
if isinstance(default, bool):
|
|
return None
|
|
if isinstance(default, int):
|
|
return default
|
|
if isinstance(default, str):
|
|
try:
|
|
return int(default)
|
|
except ValueError:
|
|
return None
|
|
return None
|
|
if schema_type == "number":
|
|
if isinstance(default, bool):
|
|
return None
|
|
if isinstance(default, (int, float)):
|
|
return default
|
|
if isinstance(default, str):
|
|
try:
|
|
return float(default)
|
|
except ValueError:
|
|
return None
|
|
return None
|
|
if schema_type == "array":
|
|
return default if isinstance(default, list) else None
|
|
if schema_type == "object":
|
|
return default if isinstance(default, dict) else None
|
|
if schema_type == "string":
|
|
return default if isinstance(default, str) else str(default)
|
|
return default
|
|
|
|
|
|
def _json_schema_for_function(function: FunctionDefinition) -> dict[str, Any]:
|
|
properties: dict[str, Any] = {}
|
|
for name, parameter in function.parameters.items():
|
|
schema: dict[str, Any] = {
|
|
"description": parameter.description,
|
|
}
|
|
param_type = (parameter.param_type or "string").lower()
|
|
schema_type: str | None = None
|
|
if parameter.properties is not None:
|
|
schema_type = "object"
|
|
elif parameter.items is not None:
|
|
schema_type = "array"
|
|
elif param_type not in _UNCONSTRAINED_SCHEMA_TYPES:
|
|
schema_type = _JSON_SCHEMA_TYPE_ALIASES.get(param_type, "string")
|
|
if schema_type is not None:
|
|
schema["type"] = schema_type
|
|
if parameter.enum is not None:
|
|
schema["enum"] = parameter.enum
|
|
default = _coerce_default(schema_type, parameter.default)
|
|
if default is not None:
|
|
schema["default"] = default
|
|
if schema_type == "array" and parameter.items is not None:
|
|
schema["items"] = _normalize_schema(parameter.items)
|
|
if schema_type == "object" and parameter.properties is not None:
|
|
schema["properties"] = _normalize_schema(parameter.properties)
|
|
properties[name] = schema
|
|
|
|
return {
|
|
"type": "object",
|
|
"properties": properties,
|
|
"required": list(function.required_params),
|
|
}
|
|
|
|
|
|
def generate_function_schema(function: FunctionDefinition) -> dict[str, Any]:
|
|
"""Return an OpenAI-compatible function schema for one BFCL function."""
|
|
return {
|
|
"name": function.name,
|
|
"description": function.description,
|
|
"parameters": _json_schema_for_function(function),
|
|
}
|
|
|
|
|
|
def generate_openai_tools_format(functions: list[FunctionDefinition]) -> list[dict[str, Any]]:
|
|
"""Return function definitions in OpenAI ``tools`` format."""
|
|
return [
|
|
{
|
|
"type": "function",
|
|
"function": generate_function_schema(function),
|
|
}
|
|
for function in functions
|
|
]
|
|
|
|
|
|
@dataclass
|
|
class FunctionCallCapture:
|
|
"""Simple in-memory capture used by tests and lightweight integrations."""
|
|
|
|
calls: list[FunctionCall] = field(default_factory=list)
|
|
|
|
def record(self, call: FunctionCall) -> None:
|
|
self.calls.append(call)
|
|
|
|
def clear(self) -> None:
|
|
self.calls.clear()
|
|
|
|
def get_calls(self) -> list[FunctionCall]:
|
|
return list(self.calls)
|
|
|
|
|
|
_GLOBAL_CAPTURE = FunctionCallCapture()
|
|
|
|
|
|
def get_call_capture() -> FunctionCallCapture:
|
|
return _GLOBAL_CAPTURE
|
|
|
|
|
|
def create_function_action(function: FunctionDefinition) -> dict[str, Any]:
|
|
"""Create a runtime-neutral action descriptor for a BFCL function."""
|
|
return {
|
|
"name": function.name,
|
|
"description": function.description,
|
|
"schema": generate_function_schema(function),
|
|
}
|
|
|
|
|
|
class BFCLPluginFactory:
|
|
"""Runtime-neutral factory for BFCL function action descriptors."""
|
|
|
|
def create_actions(self, functions: list[FunctionDefinition]) -> list[dict[str, Any]]:
|
|
return [create_function_action(function) for function in functions]
|
|
|
|
def create_tools(self, functions: list[FunctionDefinition]) -> list[dict[str, Any]]:
|
|
return generate_openai_tools_format(functions)
|