refactor(tools): unify tool-detail normalization in ToolManager

Drop the PluginToolLoader.get_tool() override that returned a raw
ComponentManifest, so every loader's get_tool() now returns a uniform
resource_tool.LLMTool (PluginToolLoader.get_tools() already did this
conversion). This removes the only source of tool-shape heterogeneity.

- ToolManager.get_tool_schema(): drop the ComponentManifest-vs-LLMTool branch
- ToolManager.get_tool_detail(): new host-level shape {name, description,
  human_desc, parameters}
- handler.py GET_TOOL_DETAIL: call tool_mgr.get_tool_detail(); delete the
  handler-local _build_tool_detail + _i18n_to_dict/_i18n_to_text adapters and
  the litellm TODO
- ToolLookupResult is now just LLMTool

The dropped label/spec fields were not consumed by any runner (local-agent
build_llm_tool and external harnesses use only name/description/parameters).
This commit is contained in:
huanghuoguoguo
2026-06-22 13:39:45 +08:00
parent ae0faa425a
commit a64fb976c3
5 changed files with 56 additions and 106 deletions
+2 -2
View File
@@ -4,14 +4,14 @@ import abc
import typing
from typing import TYPE_CHECKING
from langbot_plugin.api.definition.components.manifest import ComponentManifest
from langbot_plugin.api.entities.events import pipeline_query
import langbot_plugin.api.entities.builtin.resource.tool as resource_tool
if TYPE_CHECKING:
from ...core import app
ToolLookupResult = resource_tool.LLMTool | ComponentManifest
# All loaders normalize their tools to resource_tool.LLMTool.
ToolLookupResult = resource_tool.LLMTool
preregistered_loaders: list[typing.Type[ToolLoader]] = []
@@ -3,7 +3,6 @@ from __future__ import annotations
import typing
import traceback
from langbot_plugin.api.definition.components.manifest import ComponentManifest
from langbot_plugin.api.entities.events import pipeline_query
from .. import loader
@@ -58,12 +57,6 @@ class PluginToolLoader(loader.ToolLoader):
return True
return False
async def get_tool(self, name: str) -> ComponentManifest | None:
for tool in await self.ap.plugin_connector.list_tools():
if tool.metadata.name == name:
return tool
return None
async def invoke_tool(self, name: str, parameters: dict, query: pipeline_query.Query) -> typing.Any:
try:
return await self.ap.plugin_connector.call_tool(
+21 -8
View File
@@ -133,18 +133,31 @@ class ToolManager:
"""Return (description, parameters JSON schema) for a tool by name.
Used by the host to prefill ToolResource so a runner can build LLM tool
definitions without a separate get_tool_detail round-trip. Handles both
LLMTool (native/mcp/skill) and plugin ComponentManifest shapes. Returns
(None, None) when the tool is not found.
definitions without a separate get_tool_detail round-trip. All loaders
return resource_tool.LLMTool, so no per-shape branching is needed.
Returns (None, None) when the tool is not found.
"""
tool = await self.get_tool_by_name(name)
if tool is None:
return None, None
if hasattr(tool, 'spec') and hasattr(tool, 'metadata'):
spec = getattr(tool, 'spec', None) or {}
return spec.get('llm_prompt'), (spec.get('parameters') or None)
description = getattr(tool, 'description', None) or getattr(tool, 'human_desc', None)
return description, (getattr(tool, 'parameters', None) or None)
return tool.description, (tool.parameters or None)
async def get_tool_detail(self, name: str) -> dict | None:
"""Return the host-level tool detail shape for a tool by name.
All loaders return resource_tool.LLMTool, so the shape is uniform:
{name, description, human_desc, parameters}. Returns None when the tool
is not found.
"""
tool = await self.get_tool_by_name(name)
if tool is None:
return None
return {
'name': tool.name,
'description': tool.description,
'human_desc': tool.human_desc,
'parameters': tool.parameters or {},
}
async def generate_tools_for_openai(self, use_funcs: list[resource_tool.LLMTool]) -> list:
tools = []