This commit is contained in:
@@ -13,10 +13,18 @@ class ToolCallUnavailable(Exception):
|
||||
"""Internal signal for expected tool-calling transport/response failures."""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ToolCallUsage:
|
||||
input_tokens: int | None = None
|
||||
output_tokens: int | None = None
|
||||
total_tokens: int | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ToolCallDecision:
|
||||
tool_name: str
|
||||
arguments: dict[str, Any]
|
||||
usage: ToolCallUsage | None = None
|
||||
|
||||
|
||||
class ToolCallingClient(Protocol):
|
||||
@@ -234,7 +242,11 @@ def _decision_from_anthropic_response(response: Any) -> ToolCallDecision:
|
||||
name = _value(block, "name")
|
||||
arguments = _value(block, "input")
|
||||
if isinstance(name, str) and isinstance(arguments, dict):
|
||||
return ToolCallDecision(tool_name=name, arguments=arguments)
|
||||
return ToolCallDecision(
|
||||
tool_name=name,
|
||||
arguments=arguments,
|
||||
usage=_anthropic_usage(response),
|
||||
)
|
||||
raise ValueError("anthropic response did not include a tool_use block")
|
||||
|
||||
|
||||
@@ -278,7 +290,40 @@ def _decision_from_openai_response(response: Any) -> ToolCallDecision:
|
||||
if not isinstance(name, str):
|
||||
raise ValueError("openai tool call missing a function name")
|
||||
arguments = _decode_openai_arguments(_value(function, "arguments"))
|
||||
return ToolCallDecision(tool_name=name, arguments=arguments)
|
||||
return ToolCallDecision(
|
||||
tool_name=name,
|
||||
arguments=arguments,
|
||||
usage=_openai_usage(response),
|
||||
)
|
||||
|
||||
|
||||
def _anthropic_usage(response: Any) -> ToolCallUsage | None:
|
||||
usage = _value(response, "usage")
|
||||
input_tokens = _integer_value(usage, "input_tokens")
|
||||
output_tokens = _integer_value(usage, "output_tokens")
|
||||
if input_tokens is None and output_tokens is None:
|
||||
return None
|
||||
return ToolCallUsage(
|
||||
input_tokens=input_tokens,
|
||||
output_tokens=output_tokens,
|
||||
total_tokens=(input_tokens or 0) + (output_tokens or 0),
|
||||
)
|
||||
|
||||
|
||||
def _openai_usage(response: Any) -> ToolCallUsage | None:
|
||||
usage = _value(response, "usage")
|
||||
input_tokens = _integer_value(usage, "prompt_tokens")
|
||||
output_tokens = _integer_value(usage, "completion_tokens")
|
||||
total_tokens = _integer_value(usage, "total_tokens")
|
||||
if input_tokens is None and output_tokens is None and total_tokens is None:
|
||||
return None
|
||||
return ToolCallUsage(
|
||||
input_tokens=input_tokens,
|
||||
output_tokens=output_tokens,
|
||||
total_tokens=total_tokens
|
||||
if total_tokens is not None
|
||||
else (input_tokens or 0) + (output_tokens or 0),
|
||||
)
|
||||
|
||||
|
||||
def _decode_openai_arguments(raw_arguments: Any) -> dict[str, Any]:
|
||||
@@ -296,3 +341,8 @@ def _value(source: Any, key: str) -> Any:
|
||||
return source.get(key)
|
||||
value = getattr(source, key, None)
|
||||
return None if callable(value) else value
|
||||
|
||||
|
||||
def _integer_value(source: Any, key: str) -> int | None:
|
||||
value = _value(source, key)
|
||||
return value if isinstance(value, int) and value >= 0 else None
|
||||
|
||||
Reference in New Issue
Block a user