| """Function-calling tool helpers (parsing / validating user-supplied JSON).""" |
|
|
| from __future__ import annotations |
|
|
| import json |
| import logging |
| from typing import Any, Optional |
|
|
| import gradio as gr |
|
|
| from i18n import t |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| def _normalize_tool_item(item: Any) -> Optional[dict]: |
| """Accept either {type, function} or a bare {name, parameters} entry.""" |
| if not isinstance(item, dict): |
| return None |
| if item.get("type") == "function" and "function" in item: |
| fn = item["function"] |
| if isinstance(fn, dict) and fn.get("name"): |
| return fn |
| return None |
| if item.get("name"): |
| return item |
| return None |
|
|
|
|
| def parse_functions_json(functions_json_str: Any) -> list[dict]: |
| if functions_json_str is None: |
| return [] |
| if not isinstance(functions_json_str, str): |
| functions_json_str = str(functions_json_str) |
| if not functions_json_str.strip(): |
| return [] |
| try: |
| data = json.loads(functions_json_str) |
| except json.JSONDecodeError: |
| return [] |
| if isinstance(data, dict): |
| data = [data] |
| if not isinstance(data, list): |
| return [] |
| result: list[dict] = [] |
| for item in data: |
| fn = _normalize_tool_item(item) |
| if fn: |
| result.append(fn) |
| return result |
|
|
|
|
| def build_tools_list(functions_json_str: Any) -> Optional[list[dict]]: |
| functions = parse_functions_json(functions_json_str) |
| logger.debug("parsed functions count=%d", len(functions)) |
| if not functions: |
| return None |
| tools: list[dict] = [] |
| for fn in functions: |
| params = fn.get("parameters", {}) |
| if isinstance(params, str): |
| try: |
| params = json.loads(params) |
| except (json.JSONDecodeError, TypeError): |
| continue |
| tools.append({ |
| "type": "function", |
| "function": { |
| "name": fn["name"], |
| "description": fn.get("description", ""), |
| "parameters": params, |
| }, |
| }) |
| return tools or None |
|
|
|
|
| def validate_functions_json(functions_json_str: str): |
| """Gradio handler: validate + pretty-print the function definitions textbox.""" |
| if not functions_json_str or not functions_json_str.strip(): |
| gr.Warning(t("warn.fn.enter_json")) |
| return gr.update() |
| try: |
| data = json.loads(functions_json_str) |
| except json.JSONDecodeError as e: |
| gr.Warning(t("warn.fn.invalid_format", err=str(e))) |
| return gr.update() |
| if isinstance(data, dict): |
| data = [data] |
| if not isinstance(data, list): |
| gr.Warning(t("warn.fn.must_be_array")) |
| return gr.update() |
| names: list[str] = [] |
| for i, item in enumerate(data): |
| if not isinstance(item, dict): |
| gr.Warning(t("warn.fn.item_not_object", i=i + 1)) |
| return gr.update() |
| fn = _normalize_tool_item(item) |
| if not fn: |
| gr.Warning(t("warn.fn.item_invalid", i=i + 1)) |
| return gr.update() |
| if fn["name"] in names: |
| gr.Warning(t("warn.fn.duplicate_name", name=fn["name"])) |
| return gr.update() |
| names.append(fn["name"]) |
| formatted = json.dumps(data, indent=2, ensure_ascii=False) |
| gr.Info(t("info.fn.validation_passed", n=len(names), names=", ".join(names))) |
| return gr.update(value=formatted) |
|
|