183 lines
5.6 KiB
Python
183 lines
5.6 KiB
Python
"""
|
|
Tool trace models for Mid Platform.
|
|
[AC-IDMP-15] 工具调用结构化记录
|
|
|
|
Reference: spec/intent-driven-mid-platform/openapi.provider.yaml - ToolCallTrace
|
|
"""
|
|
|
|
import hashlib
|
|
import json
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime
|
|
from enum import Enum
|
|
from typing import Any
|
|
|
|
|
|
class ToolCallStatus(str, Enum):
|
|
"""工具调用状态"""
|
|
OK = "ok"
|
|
TIMEOUT = "timeout"
|
|
ERROR = "error"
|
|
REJECTED = "rejected"
|
|
|
|
|
|
class ToolType(str, Enum):
|
|
"""工具类型"""
|
|
INTERNAL = "internal"
|
|
MCP = "mcp"
|
|
|
|
|
|
@dataclass
|
|
class ToolCallTrace:
|
|
"""
|
|
[AC-IDMP-15] 工具调用追踪记录
|
|
Reference: openapi.provider.yaml - ToolCallTrace
|
|
|
|
记录字段:
|
|
- tool_name: 工具名称
|
|
- tool_type: 工具类型 (internal | mcp)
|
|
- registry_version: 注册表版本
|
|
- auth_applied: 是否应用鉴权
|
|
- duration_ms: 调用耗时(毫秒)
|
|
- status: 调用状态 (ok | timeout | error | rejected)
|
|
- error_code: 错误码
|
|
- args_digest: 参数摘要(脱敏)
|
|
- result_digest: 结果摘要
|
|
- arguments: 完整参数
|
|
- result: 完整结果
|
|
"""
|
|
tool_name: str
|
|
duration_ms: int
|
|
status: ToolCallStatus
|
|
tool_type: ToolType = ToolType.INTERNAL
|
|
registry_version: str | None = None
|
|
auth_applied: bool = False
|
|
error_code: str | None = None
|
|
args_digest: str | None = None
|
|
result_digest: str | None = None
|
|
arguments: dict[str, Any] | None = None
|
|
result: Any = None
|
|
started_at: datetime = field(default_factory=datetime.utcnow)
|
|
completed_at: datetime | None = None
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
result = {
|
|
"tool_name": self.tool_name,
|
|
"duration_ms": self.duration_ms,
|
|
"status": self.status.value,
|
|
}
|
|
if self.tool_type != ToolType.INTERNAL:
|
|
result["tool_type"] = self.tool_type.value
|
|
if self.registry_version:
|
|
result["registry_version"] = self.registry_version
|
|
if self.auth_applied:
|
|
result["auth_applied"] = self.auth_applied
|
|
if self.error_code:
|
|
result["error_code"] = self.error_code
|
|
if self.args_digest:
|
|
result["args_digest"] = self.args_digest
|
|
if self.result_digest:
|
|
result["result_digest"] = self.result_digest
|
|
if self.arguments:
|
|
result["arguments"] = self.arguments
|
|
if self.result is not None:
|
|
result["result"] = self.result
|
|
return result
|
|
|
|
@staticmethod
|
|
def compute_digest(data: Any, max_length: int = 64) -> str:
|
|
"""
|
|
计算数据摘要(用于脱敏记录)
|
|
|
|
Args:
|
|
data: 原始数据
|
|
max_length: 最大长度限制
|
|
|
|
Returns:
|
|
摘要字符串
|
|
"""
|
|
if data is None:
|
|
return ""
|
|
|
|
if isinstance(data, (dict, list)):
|
|
data_str = json.dumps(data, ensure_ascii=False, sort_keys=True)
|
|
else:
|
|
data_str = str(data)
|
|
|
|
if len(data_str) <= max_length:
|
|
return data_str
|
|
|
|
hash_value = hashlib.sha256(data_str.encode("utf-8")).hexdigest()[:16]
|
|
preview = data_str[:32]
|
|
return f"{preview}...[hash:{hash_value}]"
|
|
|
|
|
|
@dataclass
|
|
class ToolCallBuilder:
|
|
"""
|
|
[AC-IDMP-15] 工具调用记录构建器
|
|
用于在工具执行过程中逐步构建追踪记录
|
|
"""
|
|
tool_name: str
|
|
tool_type: ToolType = ToolType.INTERNAL
|
|
registry_version: str | None = None
|
|
auth_applied: bool = False
|
|
_started_at: datetime = field(default_factory=datetime.utcnow)
|
|
_args: Any = None
|
|
_result: Any = None
|
|
_error: Exception | None = None
|
|
_status: ToolCallStatus = ToolCallStatus.OK
|
|
_error_code: str | None = None
|
|
|
|
def with_args(self, args: Any) -> "ToolCallBuilder":
|
|
"""设置调用参数"""
|
|
self._args = args
|
|
return self
|
|
|
|
def with_registry_info(self, version: str, auth_applied: bool) -> "ToolCallBuilder":
|
|
"""设置注册表信息"""
|
|
self.registry_version = version
|
|
self.auth_applied = auth_applied
|
|
return self
|
|
|
|
def with_result(self, result: Any) -> "ToolCallBuilder":
|
|
"""设置调用结果"""
|
|
self._result = result
|
|
self._status = ToolCallStatus.OK
|
|
return self
|
|
|
|
def with_error(self, error: Exception, error_code: str | None = None) -> "ToolCallBuilder":
|
|
"""设置错误信息"""
|
|
self._error = error
|
|
self._error_code = error_code
|
|
if isinstance(error, TimeoutError):
|
|
self._status = ToolCallStatus.TIMEOUT
|
|
else:
|
|
self._status = ToolCallStatus.ERROR
|
|
return self
|
|
|
|
def with_rejected(self, reason: str) -> "ToolCallBuilder":
|
|
"""设置拒绝状态"""
|
|
self._status = ToolCallStatus.REJECTED
|
|
self._error_code = reason
|
|
return self
|
|
|
|
def build(self) -> ToolCallTrace:
|
|
"""构建追踪记录"""
|
|
completed_at = datetime.utcnow()
|
|
duration_ms = int((completed_at - self._started_at).total_seconds() * 1000)
|
|
|
|
return ToolCallTrace(
|
|
tool_name=self.tool_name,
|
|
tool_type=self.tool_type,
|
|
registry_version=self.registry_version,
|
|
auth_applied=self.auth_applied,
|
|
duration_ms=duration_ms,
|
|
status=self._status,
|
|
error_code=self._error_code,
|
|
args_digest=ToolCallTrace.compute_digest(self._args) if self._args else None,
|
|
result_digest=ToolCallTrace.compute_digest(self._result) if self._result else None,
|
|
started_at=self._started_at,
|
|
completed_at=completed_at,
|
|
)
|