tooluniverse-custom-tool

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Adding Custom Tools to ToolUniverse

向ToolUniverse添加自定义工具

Three ways to add tools — pick the one that fits your needs:
ApproachWhen to use
JSON configREST API with standard request/response — no coding needed
Python class (workspace)Custom logic for local/private use only
Plugin packageReusable tools you want to share or install via pip

添加工具的三种方式——选择最适合你的一种:
实现方式适用场景
JSON配置适用于标准请求/响应的REST API —— 无需编写代码
Python类(工作区)仅用于本地/私有的自定义逻辑
插件包可复用、可分享、可通过pip安装的工具集

Option A — Workspace tools (local use)

选项A —— 工作区工具(本地使用)

Tools in
.tooluniverse/tools/
are auto-discovered at startup. No installation needed.
bash
mkdir -p .tooluniverse/tools
.tooluniverse/tools/
目录下的工具会在启动时自动被识别,无需额外安装。
bash
mkdir -p .tooluniverse/tools

JSON config

JSON配置

Create
.tooluniverse/tools/my_tools.json
:
json
[
  {
    "name": "MyAPI_search",
    "description": "Search my internal database. Returns matching records with id, title, and score.",
    "type": "BaseRESTTool",
    "fields": {
      "endpoint": "https://my-api.example.com/search"
    },
    "parameter": {
      "type": "object",
      "properties": {
        "q": {
          "type": "string",
          "description": "Search query"
        },
        "limit": {
          "type": ["integer", "null"],
          "description": "Max results to return (default 10)"
        }
      },
      "required": ["q"]
    }
  }
]
One JSON file can define multiple tools — just add more objects to the array.
For the full JSON field reference, see references/json-tool.md.
创建
.tooluniverse/tools/my_tools.json
json
[
  {
    "name": "MyAPI_search",
    "description": "Search my internal database. Returns matching records with id, title, and score.",
    "type": "BaseRESTTool",
    "fields": {
      "endpoint": "https://my-api.example.com/search"
    },
    "parameter": {
      "type": "object",
      "properties": {
        "q": {
          "type": "string",
          "description": "Search query"
        },
        "limit": {
          "type": ["integer", "null"],
          "description": "Max results to return (default 10)"
        }
      },
      "required": ["q"]
    }
  }
]
单个JSON文件可定义多个工具——只需向数组中添加更多对象即可。
完整的JSON字段参考,请查看references/json-tool.md

Python class

Python类

Create
.tooluniverse/tools/my_tool.py
:
python
from tooluniverse.tool_registry import register_tool

@register_tool
class MyAPI_search:
    name = "MyAPI_search"
    description = "Search my internal database. Returns matching records with id, title, and score."
    input_schema = {
        "type": "object",
        "properties": {
            "q": {"type": "string", "description": "Search query"},
            "limit": {"type": "integer", "description": "Max results (default 10)"}
        },
        "required": ["q"]
    }

    def run(self, q: str, limit: int = 10) -> dict:
        import requests
        resp = requests.get(
            "https://my-api.example.com/search",
            params={"q": q, "limit": limit},
            timeout=30,
        )
        resp.raise_for_status()
        return {"status": "success", "data": resp.json()}
Note: workspace Python tools use
run(self, **named_params)
— arguments are unpacked as keyword arguments matching the
input_schema
properties.
For the full Python class reference, see references/python-tool.md.
创建
.tooluniverse/tools/my_tool.py
python
from tooluniverse.tool_registry import register_tool

@register_tool
class MyAPI_search:
    name = "MyAPI_search"
    description = "Search my internal database. Returns matching records with id, title, and score."
    input_schema = {
        "type": "object",
        "properties": {
            "q": {"type": "string", "description": "Search query"},
            "limit": {"type": "integer", "description": "Max results (default 10)"}
        },
        "required": ["q"]
    }

    def run(self, q: str, limit: int = 10) -> dict:
        import requests
        resp = requests.get(
            "https://my-api.example.com/search",
            params={"q": q, "limit": limit},
            timeout=30,
        )
        resp.raise_for_status()
        return {"status": "success", "data": resp.json()}
注意:工作区Python工具使用
run(self, **named_params)
——参数会被解包为与
input_schema
属性匹配的关键字参数。
完整的Python类参考,请查看references/python-tool.md

Test workspace tools

测试工作区工具

bash
undefined
bash
undefined

Uses test_examples from the tool's JSON config — zero config needed

使用工具JSON配置中的test_examples —— 无需额外配置

tu test MyAPI_search
tu test MyAPI_search

Single ad-hoc call

单次临时调用

tu test MyAPI_search '{"q": "test"}'
tu test MyAPI_search '{"q": "test"}'

Full config with assertions

带断言的完整配置测试

tu test --config my_tool_tests.json

`tu test` automatically runs these checks on every call:
- Result is not None or empty
- `return_schema` validation — validates `result["data"]` against the JSON Schema defined in `return_schema` (if present)
- `expect_status` and `expect_keys` — only if set in the config file

**Gotcha:** `tu test` does NOT verify that results are non-empty. An empty array `[]` satisfies
`"type": "array"` and passes all checks. Make sure your `test_examples` use args that actually
return results — otherwise a completely broken tool can pass all tests silently.


**Verify test_examples manually before finalizing.** Run a quick Python snippet against
the real API with your chosen args BEFORE writing them into `test_examples`. Some APIs require
all query words to appear literally in a title field (`intitle`-style); overly specific queries
like "I2C pull-up resistor value" will return 0 results even though the tool works. Use 2-4 key
words that are reliably present in real content.

Use `urllib` rather than `curl` for API verification — `curl` requires shell quoting tricks and
may not follow redirects correctly, while `urllib` matches what the tool will actually do:

```python
import urllib.request, json
with urllib.request.urlopen("https://api.example.com/search?q=test") as r:
    print(json.dumps(json.loads(r.read()), indent=2))
Also check that the URL is still a real JSON API before writing any tool code. Some candidate URLs (e.g.
certification.oshwa.org/api/projects
) may redirect to a GitHub Pages static site that returns HTML, not JSON. A quick urllib fetch will reveal this immediately.
my_tool_tests.json
(optional extra assertions):
json
{
  "tool_name": "MyAPI_search",
  "tests": [
    {
      "name": "basic search",
      "args": {"q": "climate change"},
      "expect_status": "success",
      "expect_keys": ["data"]
    }
  ]
}
Add
test_examples
and
return_schema
to your JSON config for best coverage:
json
{
  "name": "MyAPI_search",
  ...
  "test_examples": [
    {"q": "climate change"},
    {"q": "CRISPR", "limit": 3}
  ],
  "return_schema": {
    "type": "array",
    "items": {
      "type": "object",
      "properties": {
        "id":    { "type": "string" },
        "title": { "type": "string" },
        "score": { "type": "number" }
      }
    }
  }
}
tu test
validates
result["data"]
against
return_schema
. Match the schema type to what your
run()
returns under the
"data"
key:
  • If
    data
    is a list:
    "type": "array"
  • If
    data
    is a dict:
    "type": "object"
tu test --config my_tool_tests.json

`tu test`会自动对每次调用执行以下检查:
- 结果不为None或空值
- `return_schema`验证——验证`result["data"]`是否符合`return_schema`中定义的JSON Schema(如果存在)
- `expect_status`和`expect_keys`——仅在配置文件中设置时生效

**注意事项**:`tu test`不会验证结果是否非空。空数组`[]`满足`"type": "array"`并通过所有检查。确保你的`test_examples`使用能实际返回结果的参数——否则完全失效的工具也能静默通过所有测试。


**在最终确定前手动验证test_examples**。在将选定的参数写入`test_examples`之前,先运行一段简单的Python代码调用真实API进行验证。有些API要求所有查询词必须字面出现在标题字段中(类似`intitle`风格);过于具体的查询如"I2C pull-up resistor value"会返回0条结果,即使工具本身是正常工作的。使用2-4个在真实内容中可靠存在的关键词。

使用`urllib`而非`curl`进行API验证——`curl`需要shell转义技巧,且可能无法正确跟随重定向,而`urllib`与工具实际执行的操作一致:

```python
import urllib.request, json
with urllib.request.urlopen("https://api.example.com/search?q=test") as r:
    print(json.dumps(json.loads(r.read()), indent=2))
在编写任何工具代码前,还要检查URL是否为有效的JSON API。一些候选URL(如
certification.oshwa.org/api/projects
)可能会重定向到GitHub Pages静态站点,返回HTML而非JSON。快速使用urllib请求就能立即发现这个问题。
my_tool_tests.json
(可选的额外断言):
json
{
  "tool_name": "MyAPI_search",
  "tests": [
    {
      "name": "basic search",
      "args": {"q": "climate change"},
      "expect_status": "success",
      "expect_keys": ["data"]
    }
  ]
}
为了获得最佳测试覆盖率,向JSON配置中添加
test_examples
return_schema
json
{
  "name": "MyAPI_search",
  ...
  "test_examples": [
    {"q": "climate change"},
    {"q": "CRISPR", "limit": 3}
  ],
  "return_schema": {
    "type": "array",
    "items": {
      "type": "object",
      "properties": {
        "id":    { "type": "string" },
        "title": { "type": "string" },
        "score": { "type": "number" }
      }
    }
  }
}
tu test
会根据
return_schema
验证
result["data"]
。确保schema类型与
run()
"data"
键下返回的类型匹配:
  • 如果
    data
    是列表:
    "type": "array"
  • 如果
    data
    是字典:
    "type": "object"

Use with MCP server

与MCP服务器配合使用

Tools in
.tooluniverse/tools/
are automatically available when you run:
bash
tu serve          # MCP stdio server (Claude Desktop, etc.)
tooluniverse      # same
The workspace directory is auto-detected in this priority order:
--workspace
flag →
TOOLUNIVERSE_HOME
env var →
./.tooluniverse/
(current dir) →
~/.tooluniverse/
(global)
当你运行以下命令时,
.tooluniverse/tools/
目录下的工具会自动可用:
bash
tu serve          # MCP标准输入输出服务器(适用于Claude Desktop等)
tooluniverse      # 同上
工作区目录的自动检测优先级为:
--workspace
参数 →
TOOLUNIVERSE_HOME
环境变量 → 当前目录下的
./.tooluniverse/
→ 全局目录
~/.tooluniverse/

Point to a different tools directory

指定其他工具目录

Add a
sources
entry in
.tooluniverse/profile.yaml
:
yaml
name: my-profile
sources:
  - ./my-custom-tools/    # relative to profile.yaml location
  - /absolute/path/tools/
Then start with:
bash
tu serve --load .tooluniverse/profile.yaml

.tooluniverse/profile.yaml
中添加
sources
条目:
yaml
name: my-profile
sources:
  - ./my-custom-tools/    # 相对于profile.yaml的路径
  - /absolute/path/tools/
然后启动:
bash
tu serve --load .tooluniverse/profile.yaml

Option B — Plugin package (shareable, pip-installable)

选项B —— 插件包(可分享、可通过pip安装)

Use this when you want to distribute tools as a reusable Python package that other users can install with
pip install
. The plugin package has the same directory layout as a workspace, plus a
pyproject.toml
that declares the entry point.
当你希望将工具作为可复用的Python包分发,让其他用户可以通过
pip install
安装时,可使用此方式。插件包的目录结构与工作区相同,额外增加一个
pyproject.toml
文件用于声明入口点。

Package layout

包目录结构

my_project_root/           # directory containing pyproject.toml
    pyproject.toml
    my_tools_package/      # importable Python package (matches entry-point value)
        __init__.py        # minimal — one-line docstring, no registration code
        my_api_tool.py     # tool class(es) with @register_tool
        data/
            my_api_tools.json  # JSON tool configs (type must match registered class name)
        profile.yaml       # optional: name, description, required_env
JSON config files are discovered from both
data/
and the package root directory. The convention is
data/
.
my_project_root/           # 包含pyproject.toml的目录
    pyproject.toml
    my_tools_package/      # 可导入的Python包(需与入口点值匹配)
        __init__.py        # 保持简洁——仅一行文档字符串,无需注册代码
        my_api_tool.py     # 带有@register_tool装饰器的工具类
        data/
            my_api_tools.json  # JSON工具配置(type必须与注册的类名匹配)
        profile.yaml       # 可选:名称、描述、所需环境变量
JSON配置文件会在
data/
目录和包根目录中被自动识别,通常建议放在
data/
目录下。

pyproject.toml
entry point

pyproject.toml
入口点

toml
[project.entry-points."tooluniverse.plugins"]
my-tools = "my_tools_package"
The value (
my_tools_package
) must be the importable Python package name.
toml
[project.entry-points."tooluniverse.plugins"]
my-tools = "my_tools_package"
值(
my_tools_package
)必须是可导入的Python包名称。

Python class in a plugin package

插件包中的Python类

Plugin package tools use
BaseTool
and receive all arguments as a single
Dict
:
python
import requests
from typing import Dict, Any
from tooluniverse.base_tool import BaseTool
from tooluniverse.tool_registry import register_tool

@register_tool("MyAPITool")
class MyAPITool(BaseTool):
    """Tool description here."""

    def __init__(self, tool_config: Dict[str, Any]):
        super().__init__(tool_config)
        self.timeout = tool_config.get("timeout", 30)
        fields = tool_config.get("fields", {})
        self.operation = fields.get("operation", "search")

    def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
        query = arguments.get("query", "")
        if not query:
            return {"error": "query parameter is required"}
        try:
            resp = requests.get(
                "https://my-api.example.com/search",
                params={"q": query},
                timeout=self.timeout,
            )
            resp.raise_for_status()
            return {"status": "success", "data": resp.json()}
        except requests.exceptions.RequestException as e:
            return {"error": str(e)}
Key differences from the workspace pattern:
  • Inherit from
    BaseTool
    (from
    tooluniverse.base_tool
    )
  • @register_tool("ClassName")
    takes the class name as a string argument
  • run(self, arguments: Dict)
    receives all arguments in a single dict — extract them with
    .get()
  • __init__
    receives
    tool_config
    dict; call
    super().__init__(tool_config)
    first
插件包工具使用
BaseTool
,并接收所有参数作为单个
Dict
python
import requests
from typing import Dict, Any
from tooluniverse.base_tool import BaseTool
from tooluniverse.tool_registry import register_tool

@register_tool("MyAPITool")
class MyAPITool(BaseTool):
    """Tool description here."""

    def __init__(self, tool_config: Dict[str, Any]):
        super().__init__(tool_config)
        self.timeout = tool_config.get("timeout", 30)
        fields = tool_config.get("fields", {})
        self.operation = fields.get("operation", "search")

    def run(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
        query = arguments.get("query", "")
        if not query:
            return {"error": "query parameter is required"}
        try:
            resp = requests.get(
                "https://my-api.example.com/search",
                params={"q": query},
                timeout=self.timeout,
            )
            resp.raise_for_status()
            return {"status": "success", "data": resp.json()}
        except requests.exceptions.RequestException as e:
            return {"error": str(e)}
与工作区模式的主要区别:
  • 继承自
    BaseTool
    (来自
    tooluniverse.base_tool
  • @register_tool("ClassName")
    接收类名字符串作为参数
  • run(self, arguments: Dict)
    接收所有参数作为单个字典——使用
    .get()
    提取参数
  • __init__
    接收
    tool_config
    字典;需先调用
    super().__init__(tool_config)

JSON config in a plugin package

插件包中的JSON配置

Place configs in
data/my_api_tools.json
. The
"type"
field must match the string passed to
@register_tool(...)
:
json
[
  {
    "name": "MyAPI_search",
    "description": "Search my API. Returns matching records.",
    "type": "MyAPITool",
    "fields": { "operation": "search" },
    "parameter": {
      "type": "object",
      "properties": {
        "query": { "type": "string", "description": "Search query" },
        "limit": { "type": ["integer", "null"], "description": "Max results" }
      },
      "required": ["query"]
    }
  }
]
将配置文件放在
data/my_api_tools.json
中。
"type"
字段必须与
@register_tool(...)
中传入的字符串匹配:
json
[
  {
    "name": "MyAPI_search",
    "description": "Search my API. Returns matching records.",
    "type": "MyAPITool",
    "fields": { "operation": "search" },
    "parameter": {
      "type": "object",
      "properties": {
        "query": { "type": "string", "description": "Search query" },
        "limit": { "type": ["integer", "null"], "description": "Max results" }
      },
      "required": ["query"]
    }
  }
]

__init__.py

__init__.py

Keep it minimal — no registration code needed. The plugin system imports every
.py
file in the package directory automatically (via
_discover_entry_point_plugins()
), so
@register_tool
decorators fire on their own:
python
"""My tools plugin for ToolUniverse."""
If you want IDE autocompletion or to make it easy to import specific classes directly, you can add explicit imports — they are harmless because
@register_tool
is idempotent (registering the same class twice is a no-op):
python
"""My tools plugin for ToolUniverse."""

from . import my_api_tool       # optional — for IDE support
from . import my_other_tool     # optional
Do not add registration logic, JSON loading, or
register_tool_configs()
calls here. Those run automatically at plugin discovery time.
保持简洁——无需注册代码。插件系统会自动导入包目录下的所有
.py
文件(通过
_discover_entry_point_plugins()
),因此
@register_tool
装饰器会自动生效:
python
"""My tools plugin for ToolUniverse."""
如果你需要IDE自动补全或方便直接导入特定类,可以添加显式导入——这不会有问题,因为
@register_tool
是幂等的(重复注册同一个类不会产生影响):
python
"""My tools plugin for ToolUniverse."""

from . import my_api_tool       # 可选——用于IDE支持
from . import my_other_tool     # 可选
请勿在此处添加注册逻辑、JSON加载或
register_tool_configs()
调用。这些操作会在插件发现时自动执行。

Install and verify

安装与验证

bash
undefined
bash
undefined

Install in editable mode — path must point to the directory containing pyproject.toml

以可编辑模式安装——路径必须指向包含pyproject.toml的目录

pip install -e /path/to/my_project_root
pip install -e /path/to/my_project_root

Verify the entry point is registered

验证入口点是否已注册

python -c " from importlib.metadata import entry_points eps = entry_points(group='tooluniverse.plugins') print([ep.name for ep in eps]) "
python -c " from importlib.metadata import entry_points eps = entry_points(group='tooluniverse.plugins') print([ep.name for ep in eps]) "

Test the tool — MUST run from the plugin repo directory

测试工具——必须从插件仓库目录运行

cd /path/to/my_project_root tu test MyAPI_search '{"query": "test"}'

`tu test` finds plugin tools via the installed entry point — the package must be
`pip install -e`'d first. Always run `tu test` from the plugin repo directory (not
from an arbitrary location): ToolUniverse's workspace auto-detection looks for
`.tooluniverse/` in the current directory, which is where the plugin's `profile.yaml`
and any workspace-level config lives.

Add `test_examples` to your JSON config for zero-config testing:
```json
{ "name": "MyAPI_search", ..., "test_examples": [{"query": "test"}] }
Then:
tu test MyAPI_search
Note:
tu list
shows tool counts grouped by category, not individual tool names. To confirm your specific tool loaded, use
tu info MyAPI_search
or run
tu test MyAPI_search
directly. If "Tool not found", see the gotcha above about the lazy registry refresh.

cd /path/to/my_project_root tu test MyAPI_search '{"query": "test"}'

`tu test`通过已安装的入口点查找插件工具——包必须先通过`pip install -e`安装。请始终从插件仓库目录运行`tu test`(而非任意目录):ToolUniverse的工作区自动检测会在当前目录下查找`.tooluniverse/`,这是插件的`profile.yaml`和任何工作区级配置所在的位置。

向JSON配置中添加`test_examples`以实现零配置测试:
```json
{ "name": "MyAPI_search", ..., "test_examples": [{"query": "test"}] }
然后运行:
tu test MyAPI_search
注意
tu list
按类别显示工具数量,而非单个工具名称。要确认特定工具是否已加载,请使用
tu info MyAPI_search
或直接运行
tu test MyAPI_search
。如果提示“Tool not found”,请参考上述关于延迟注册表刷新的注意事项。

Offline / pure-computation tools

离线/纯计算工具

Calculator tools that perform local math (no HTTP) follow the plugin-package pattern but skip the HTTP layer entirely. Common designs:
执行本地数学运算(无需HTTP请求)的计算器工具遵循插件包模式,但跳过HTTP层。常见设计如下:

Preset lookup tables

预设查找表

Define named presets at module level as a
Dict[str, float]
, then resolve the parameter with a priority chain: explicit user value → preset name → default. Always include the preset table in
metadata
so callers can discover valid names without reading source code:
python
_PACKAGE_THETA_JA = {"sot-23": 200.0, "to-220": 50.0, "bga-256": 20.0}

def run(self, arguments):
    theta = arguments.get("theta_ja")
    if theta is None and arguments.get("package"):
        key = arguments["package"].lower()
        if key not in _PACKAGE_THETA_JA:
            return {"status": "error",
                    "message": f"Unknown package. Known: {list(_PACKAGE_THETA_JA)}"}
        theta = _PACKAGE_THETA_JA[key]
    return {
        "status": "success",
        "data": {"theta_ja": theta, ...},
        "metadata": {"package_presets": _PACKAGE_THETA_JA},
    }
模块级别将预设值定义为
Dict[str, float]
,然后按优先级解析参数:显式用户值 → 预设名称 → 默认值。请务必将预设表包含在
metadata
中,以便调用者无需阅读源代码即可发现有效的预设名称:
python
_PACKAGE_THETA_JA = {"sot-23": 200.0, "to-220": 50.0, "bga-256": 20.0}

def run(self, arguments):
    theta = arguments.get("theta_ja")
    if theta is None and arguments.get("package"):
        key = arguments["package"].lower()
        if key not in _PACKAGE_THETA_JA:
            return {"status": "error",
                    "message": f"Unknown package. Known: {list(_PACKAGE_THETA_JA)}"}
        theta = _PACKAGE_THETA_JA[key]
    return {
        "status": "success",
        "data": {"theta_ja": theta, ...},
        "metadata": {"package_presets": _PACKAGE_THETA_JA},
    }

Solving the same equation in both directions

双向求解同一公式

When the same formula can be rearranged to solve for different unknowns, expose them as separate
operation
values with a single runtime-dispatch tool:
python
undefined
当同一个公式可被重排以求解不同未知数时,可将其作为单独的
operation
值暴露出来,使用单个运行时分发工具:
python
undefined

C_min = I × Δt / ΔV → also: ΔV = I × Δt / C

C_min = I × Δt / ΔV → 也可变形为: ΔV = I × Δt / C

op = arguments.get("operation") or self.operation if op == "solve_capacitance": dV = _req_float(arguments, "voltage_droop_V") C_min = I * dt / dV ... elif op == "solve_droop": C = _req_float(arguments, "capacitance_F") dV = I * dt / C ...

The two directions share a single JSON config entry. Use `"fields": {"operation": "default_op"}`
in the JSON to set the default, and document both modes clearly in the description.
op = arguments.get("operation") or self.operation if op == "solve_capacitance": dV = _req_float(arguments, "voltage_droop_V") C_min = I * dt / dV ... elif op == "solve_droop": C = _req_float(arguments, "capacitance_F") dV = I * dt / C ...

两种求解方式共享单个JSON配置条目。在JSON中使用`"fields": {"operation": "default_op"}`设置默认操作,并在描述中清晰说明两种模式。

Physical constants at module level

模块级物理常量

Define fundamental constants once, near the top of the file, so they appear in code review and are easy to update:
python
import math

_MU0   = 4.0 * math.pi * 1e-7   # H/m — permeability of free space
_KB_EV = 8.617333e-5             # eV/K — Boltzmann constant
在文件顶部定义基础常量一次,以便于代码审查和更新:
python
import math

_MU0   = 4.0 * math.pi * 1e-7   # H/m — 真空磁导率
_KB_EV = 8.617333e-5             # eV/K — 玻尔兹曼常数

Material-specific values as a named dict

材料特定值定义为命名字典

_MATERIAL_EA = {"al": 0.7, "cu": 0.9, "w": 1.0} # activation energy in eV
undefined
_MATERIAL_EA = {"al": 0.7, "cu": 0.9, "w": 1.0} # 激活能,单位eV
undefined

Multi-output operations

多输出操作

When a single computation naturally yields multiple related results (e.g., Tj AND headroom AND pass/fail), return them all in
data
rather than forcing a second call:
python
data = {
    "junction_temp_C":   tj,
    "headroom_C":        tj_max - tj,
    "passes_thermal":    (tj_max - tj) >= 0,
    ...
}
For the complete patterns (significant-figure rounding,
_req_float
helper, preset resolution), see references/python-tool.md.
当单次计算自然产生多个相关结果时(如Tj、裕量和通过/失败状态),请将所有结果放在
data
中返回,而非强制用户进行第二次调用:
python
data = {
    "junction_temp_C":   tj,
    "headroom_C":        tj_max - tj,
    "passes_thermal":    (tj_max - tj) >= 0,
    ...
}
完整的实现模式(有效数字舍入、
_req_float
辅助函数、预设解析),请查看references/python-tool.md