Files
w 08e66ccc02 发布(0.7.0): 修复问题并增强技能与工具体验
1. 解决多个 issue 反馈问题,修复已知 Bug
2. 优化前端界面与交互体验
3. 优化 tool 调用链路,较此前版本节省约 70% token 用量
4. 增加模型思考控制开关,提升整体响应体感
5. 新增 find-skills,全面接入腾讯云 SkillsHub,支持通过对话进行 skills 管理
6. 新增 ima-knowledge-base、ima-notes,全面接入 IMA 知识库和笔记,支持知识库与笔记内容管理和写入
7. 优化 README,并补充 0.7.0 发布说明
8. 发布说明:https://654321.ai/docs/releases/v0.7.0
2026-03-29 22:37:11 +08:00

211 lines
6.7 KiB
Python

"""Tool Base Class - 工具基类"""
from abc import ABC, abstractmethod
from typing import Any, Dict, List
from loguru import logger
class Tool(ABC):
"""工具抽象基类
所有工具必须继承此类并实现抽象方法。
工具是 AI Agent 可以调用的可执行函数,用于执行特定操作,
如文件操作、Shell 命令或 Web 搜索等。
"""
# Type mapping for parameter validation
_TYPE_MAP = {
"string": str,
"integer": int,
"number": (int, float),
"boolean": bool,
"array": list,
"object": dict,
}
@property
@abstractmethod
def name(self) -> str:
"""
返回工具的唯一名称
Returns:
str: 用于识别和调用的工具名称
"""
pass
@property
@abstractmethod
def description(self) -> str:
"""
返回工具的描述
Returns:
str: 供 LLM 理解的人类可读描述
"""
pass
@property
@abstractmethod
def parameters(self) -> Dict[str, Any]:
"""
返回工具参数的 JSON Schema
Returns:
dict: 定义必需和可选参数的 JSON Schema
"""
pass
@abstractmethod
async def execute(self, **kwargs: Any) -> str:
"""
执行工具
Args:
**kwargs: 符合参数 schema 的工具特定参数
Returns:
str: 工具执行结果
Raises:
Exception: 工具执行失败时抛出
"""
pass
def validate_params(self, params: Dict[str, Any]) -> List[str]:
"""验证工具参数是否符合 JSON Schema
Args:
params: 要验证的参数字典
Returns:
List[str]: 错误列表(空列表表示验证通过)
"""
schema = self.parameters or {}
if schema.get("type", "object") != "object":
raise ValueError(f"Schema must be object type, got {schema.get('type')!r}")
return self._validate(params, {**schema, "type": "object"}, "")
def _validate(self, val: Any, schema: Dict[str, Any], path: str) -> List[str]:
"""
递归验证值是否符合 schema
Args:
val: 要验证的值
schema: JSON Schema
path: 当前路径(用于错误消息)
Returns:
List[str]: 错误列表
"""
t, label = schema.get("type"), path or "parameter"
# Type check
if t in self._TYPE_MAP and not isinstance(val, self._TYPE_MAP[t]):
return [f"{label} should be {t}"]
errors = []
# Enum check
if "enum" in schema and val not in schema["enum"]:
errors.append(f"{label} must be one of {schema['enum']}")
# Number constraints
if t in ("integer", "number"):
if "minimum" in schema and val < schema["minimum"]:
errors.append(f"{label} must be >= {schema['minimum']}")
if "maximum" in schema and val > schema["maximum"]:
errors.append(f"{label} must be <= {schema['maximum']}")
# String constraints
if t == "string":
if "minLength" in schema and len(val) < schema["minLength"]:
errors.append(f"{label} must be at least {schema['minLength']} chars")
if "maxLength" in schema and len(val) > schema["maxLength"]:
errors.append(f"{label} must be at most {schema['maxLength']} chars")
# Object validation
if t == "object":
props = schema.get("properties", {})
additional_properties = schema.get("additionalProperties", True)
# Check required fields
for k in schema.get("required", []):
if k not in val:
errors.append(f"missing required {path + '.' + k if path else k}")
# Check unknown fields when additionalProperties is disabled
if additional_properties is False:
for k in val:
if k not in props:
errors.append(
f"unknown parameter {path + '.' + k if path else k}"
)
# Validate each property
for k, v in val.items():
if k in props:
errors.extend(
self._validate(v, props[k], path + "." + k if path else k)
)
# Support simple JSON Schema oneOf usage for object params
if "oneOf" in schema:
match_count = 0
child_errors: List[List[str]] = []
base_schema = dict(schema)
base_schema.pop("oneOf", None)
base_required = list(base_schema.get("required", []))
for option in schema["oneOf"]:
merged_schema = dict(base_schema)
merged_required = base_required + list(option.get("required", []))
if merged_required:
merged_schema["required"] = list(dict.fromkeys(merged_required))
option_errors = self._validate(val, merged_schema, path)
if not option_errors:
match_count += 1
else:
child_errors.append(option_errors)
if match_count != 1:
if match_count == 0:
errors.append(f"{label} must match exactly one schema option")
else:
errors.append(f"{label} matches multiple schema options")
# Array validation
if t == "array" and "items" in schema:
for i, item in enumerate(val):
errors.extend(
self._validate(
item, schema["items"], f"{path}[{i}]" if path else f"[{i}]"
)
)
return errors
def get_definition(self) -> Dict[str, Any]:
"""
获取完整的工具定义,用于 LLM 函数调用
Returns:
dict: 包含 name、description 和 parameters 的工具定义
"""
definition = {
"type": "function",
"function": {
"name": self.name,
"description": self.description,
"parameters": self.parameters,
},
}
logger.debug(f"Generated definition for tool: {self.name}")
return definition
def to_schema(self) -> Dict[str, Any]:
"""转换为 OpenAI 函数 schema 格式(别名方法)"""
return self.get_definition()