"""工具表:从 tools/list 动态构建,新增工具无需改代码。""" from .mcp_client import McpClient # 只读工具白名单:审查阶段的受控子循环只允许调用这些(不做任何写库操作) READ_ONLY_TOOLS = frozenset( { "query_entities", "get_text_content", "get_dimensions", "get_block_references", "get_polylines_info", "get_entity_detail", "get_layers", "get_layouts", "get_drawing_info", "detect_drawing_frames", "find_duplicate_entities", "find_zero_length", "zoom_to_window", "zoom_to_entity", "get_viewport_screenshot", } ) # 写类工具前缀/名称:调用时不重试,且默认禁止 LLM 在审查阶段触发 _WRITE_PREFIXES = ("add_", "delete_", "draw_", "set_") _WRITE_EXACT = {"set_entity_properties", "highlight_entities"} def is_write_tool(name: str) -> bool: return name.startswith(_WRITE_PREFIXES) or name in _WRITE_EXACT class CadTools: """工具注册表 + 调用代理。""" def __init__(self, client=None): self.client = client or McpClient() self._by_name = {} self.refresh() def refresh(self): tools = self.client.list_tools(use_cache=False) self._by_name = {t["name"]: t for t in tools} return tools @property def names(self): return sorted(self._by_name) def has(self, name): return name in self._by_name def describe(self, name): return self._by_name.get(name) def openai_tools(self, names=None): """转换为 OpenAI function 工具格式;names 为可选的名称白名单集合。""" out = [] for name, tool in self._by_name.items(): if names is not None and name not in names: continue out.append( { "type": "function", "function": { "name": name, "description": tool.get("description", ""), "parameters": tool.get("inputSchema") or {"type": "object", "properties": {}}, }, } ) return out def call(self, name, arguments=None, *, is_write=None): if is_write is None: is_write = is_write_tool(name) return self.client.call_tool(name, arguments, is_write=is_write)