Converts a standard Python function into a Tool object.
Wraps the function to handle JSON inputs/outputs automatically.
Source code in wintermute/ai/utils/tool_factory.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128 | def function_to_tool(func: Callable[..., Any]) -> Tool:
"""
Converts a standard Python function into a Tool object.
Wraps the function to handle JSON inputs/outputs automatically.
"""
func_name = func.__name__
func_doc = func.__doc__ or "No description provided."
# 1. GENERATE INPUT SCHEMA
# Inspect arguments to build the Pydantic model
type_hints = get_type_hints(func)
input_fields = {}
signature = inspect.signature(func)
for param_name, param in signature.parameters.items():
if param_name == "self":
continue
annotation = type_hints.get(param_name, Any)
default = param.default
if default == inspect.Parameter.empty:
input_fields[param_name] = (annotation, ...)
else:
input_fields[param_name] = (annotation, default)
InputModel = create_model(f"{func_name}_Input", **input_fields) # type: ignore
input_schema = cast(JSONObject, InputModel.model_json_schema())
# 2. GENERATE OUTPUT SCHEMA
# We inspect the return type.
# If the function returns `int`, the tool output will be `{"result": int}`.
return_annotation = type_hints.get("return", Any)
# Create a dynamic model for the output to get a valid JSON Schema
OutputModel = create_model(f"{func_name}_Output", result=(return_annotation, ...))
output_schema = cast(JSONObject, OutputModel.model_json_schema())
# 3. CREATE THE WRAPPER (The Handler)
# This transforms the Tool's JSON input into the function's args,
# and transforms the function's return value back into JSON.
def adapter_handler(params: JSONObject) -> JSONObject:
# Validate input using our generated model (optional but safe)
validated_params = InputModel(**params)
# Call the actual function with unpacked arguments
result = func(**validated_params.model_dump())
# Intercept oversized payloads (raw bytes, multi-KB strings) before
# they reach the LLM. Tools writing firmware dumps can therefore just
# `return raw_flash_bytes` and the descriptor is what the model sees.
offloaded = _maybe_offload_payload(result, get_default_workspace())
if offloaded is not None:
return offloaded
# Pack the result into a JSON object matching our output schema
# Note: If your function already returns a dict, you might want logic here
# to decide whether to wrap it in "result" or return as is.
# For consistency, we always wrap strictly typed returns in "result".
return {"result": result}
# 4. INSTANTIATE TOOL
return Tool(
name=func_name,
description=func_doc,
input_schema=input_schema,
output_schema=output_schema,
handler=adapter_handler,
)
|