hammad-python 0.0.18__py3-none-any.whl → 0.0.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hammad/__init__.py +7 -137
- hammad/_internal.py +1 -0
- hammad/cli/_runner.py +8 -8
- hammad/cli/plugins.py +55 -26
- hammad/cli/styles/utils.py +16 -8
- hammad/data/__init__.py +1 -5
- hammad/data/collections/__init__.py +2 -3
- hammad/data/collections/collection.py +41 -22
- hammad/data/collections/indexes/__init__.py +1 -1
- hammad/data/collections/indexes/qdrant/__init__.py +1 -1
- hammad/data/collections/indexes/qdrant/index.py +106 -118
- hammad/data/collections/indexes/qdrant/settings.py +14 -14
- hammad/data/collections/indexes/qdrant/utils.py +28 -38
- hammad/data/collections/indexes/tantivy/__init__.py +1 -1
- hammad/data/collections/indexes/tantivy/index.py +57 -59
- hammad/data/collections/indexes/tantivy/settings.py +8 -19
- hammad/data/collections/indexes/tantivy/utils.py +28 -52
- hammad/data/models/__init__.py +2 -7
- hammad/data/sql/__init__.py +1 -1
- hammad/data/sql/database.py +71 -73
- hammad/data/sql/types.py +37 -51
- hammad/formatting/__init__.py +2 -1
- hammad/formatting/json/converters.py +2 -2
- hammad/genai/__init__.py +96 -36
- hammad/genai/agents/__init__.py +47 -1
- hammad/genai/agents/agent.py +1022 -0
- hammad/genai/agents/run.py +615 -0
- hammad/genai/agents/types/__init__.py +29 -22
- hammad/genai/agents/types/agent_context.py +13 -0
- hammad/genai/agents/types/agent_event.py +128 -0
- hammad/genai/agents/types/agent_hooks.py +220 -0
- hammad/genai/agents/types/agent_messages.py +31 -0
- hammad/genai/agents/types/agent_response.py +90 -0
- hammad/genai/agents/types/agent_stream.py +242 -0
- hammad/genai/models/__init__.py +1 -0
- hammad/genai/models/embeddings/__init__.py +39 -0
- hammad/genai/{embedding_models/embedding_model.py → models/embeddings/model.py} +45 -41
- hammad/genai/{embedding_models → models/embeddings}/run.py +10 -8
- hammad/genai/models/embeddings/types/__init__.py +37 -0
- hammad/genai/{embedding_models → models/embeddings/types}/embedding_model_name.py +2 -4
- hammad/genai/{embedding_models → models/embeddings/types}/embedding_model_response.py +11 -4
- hammad/genai/{embedding_models/embedding_model_request.py → models/embeddings/types/embedding_model_run_params.py} +4 -3
- hammad/genai/models/embeddings/types/embedding_model_settings.py +47 -0
- hammad/genai/models/language/__init__.py +48 -0
- hammad/genai/{language_models/language_model.py → models/language/model.py} +481 -204
- hammad/genai/{language_models → models/language}/run.py +80 -57
- hammad/genai/models/language/types/__init__.py +40 -0
- hammad/genai/models/language/types/language_model_instructor_mode.py +47 -0
- hammad/genai/models/language/types/language_model_messages.py +28 -0
- hammad/genai/{language_models/_types.py → models/language/types/language_model_name.py} +3 -40
- hammad/genai/{language_models → models/language/types}/language_model_request.py +17 -25
- hammad/genai/{language_models → models/language/types}/language_model_response.py +61 -68
- hammad/genai/{language_models → models/language/types}/language_model_response_chunk.py +8 -5
- hammad/genai/models/language/types/language_model_settings.py +89 -0
- hammad/genai/{language_models/_streaming.py → models/language/types/language_model_stream.py} +221 -243
- hammad/genai/{language_models/_utils → models/language/utils}/__init__.py +8 -11
- hammad/genai/models/language/utils/requests.py +421 -0
- hammad/genai/{language_models/_utils/_structured_outputs.py → models/language/utils/structured_outputs.py} +31 -20
- hammad/genai/models/model_provider.py +4 -0
- hammad/genai/{multimodal_models.py → models/multimodal.py} +4 -5
- hammad/genai/models/reranking.py +26 -0
- hammad/genai/types/__init__.py +1 -0
- hammad/genai/types/base.py +215 -0
- hammad/genai/{agents/types → types}/history.py +101 -88
- hammad/genai/{agents/types/tool.py → types/tools.py} +156 -141
- hammad/logging/logger.py +2 -1
- hammad/mcp/client/__init__.py +2 -3
- hammad/mcp/client/client.py +10 -10
- hammad/mcp/servers/__init__.py +2 -1
- hammad/service/decorators.py +1 -3
- hammad/web/models.py +1 -3
- hammad/web/search/client.py +10 -22
- {hammad_python-0.0.18.dist-info → hammad_python-0.0.20.dist-info}/METADATA +10 -2
- hammad_python-0.0.20.dist-info/RECORD +127 -0
- hammad/genai/embedding_models/__init__.py +0 -41
- hammad/genai/language_models/__init__.py +0 -35
- hammad/genai/language_models/_utils/_completions.py +0 -131
- hammad/genai/language_models/_utils/_messages.py +0 -89
- hammad/genai/language_models/_utils/_requests.py +0 -202
- hammad/genai/rerank_models.py +0 -26
- hammad_python-0.0.18.dist-info/RECORD +0 -111
- {hammad_python-0.0.18.dist-info → hammad_python-0.0.20.dist-info}/WHEEL +0 -0
- {hammad_python-0.0.18.dist-info → hammad_python-0.0.20.dist-info}/licenses/LICENSE +0 -0
@@ -7,19 +7,29 @@ import asyncio
|
|
7
7
|
import concurrent.futures
|
8
8
|
import inspect
|
9
9
|
import json
|
10
|
-
from typing import
|
11
|
-
|
10
|
+
from typing import (
|
11
|
+
Any,
|
12
|
+
Callable,
|
13
|
+
Dict,
|
14
|
+
List,
|
15
|
+
Optional,
|
16
|
+
Union,
|
17
|
+
get_type_hints,
|
18
|
+
TYPE_CHECKING,
|
19
|
+
Generic,
|
20
|
+
TypeVar,
|
21
|
+
ParamSpec,
|
22
|
+
overload,
|
23
|
+
)
|
12
24
|
from pydantic import BaseModel, Field, ValidationError
|
13
25
|
|
14
|
-
from
|
15
|
-
from
|
26
|
+
from ...formatting.json.converters import convert_to_json_schema
|
27
|
+
from ...data.models.extensions.pydantic.converters import (
|
16
28
|
get_pydantic_fields_from_function,
|
17
|
-
convert_to_pydantic_model
|
29
|
+
convert_to_pydantic_model,
|
18
30
|
)
|
31
|
+
from .base import BaseGenAIModelStream, BaseGenAIModelResponse, BaseTool
|
19
32
|
|
20
|
-
if TYPE_CHECKING:
|
21
|
-
from ...language_models.language_model_response import LanguageModelResponse
|
22
|
-
from ...language_models._streaming import Stream, AsyncStream
|
23
33
|
|
24
34
|
# Type variables for generic tool typing
|
25
35
|
P = ParamSpec("P")
|
@@ -27,48 +37,50 @@ R = TypeVar("R")
|
|
27
37
|
|
28
38
|
__all__ = (
|
29
39
|
"Tool",
|
30
|
-
"
|
40
|
+
"define_tool",
|
31
41
|
"ToolResponseMessage",
|
32
42
|
"execute_tool_calls_parallel",
|
33
|
-
"
|
43
|
+
"execute_tools_from_language_model_response",
|
34
44
|
)
|
35
45
|
|
36
46
|
|
37
|
-
@dataclass
|
38
47
|
class ToolResponseMessage:
|
39
48
|
"""Represents a tool response message for chat completion."""
|
40
|
-
|
49
|
+
|
41
50
|
tool_call_id: str
|
42
51
|
"""ID of the tool call this response corresponds to."""
|
43
|
-
|
52
|
+
|
44
53
|
name: str
|
45
54
|
"""Name of the tool that was called."""
|
46
|
-
|
55
|
+
|
47
56
|
content: str
|
48
57
|
"""The result/output of the tool execution."""
|
49
58
|
|
50
59
|
role: str = "tool"
|
51
60
|
"""Message role, always 'tool'."""
|
52
|
-
|
61
|
+
|
53
62
|
def to_dict(self) -> Dict[str, Any]:
|
54
63
|
"""Convert to dictionary format for API calls."""
|
55
64
|
return {
|
56
65
|
"role": self.role,
|
57
66
|
"tool_call_id": self.tool_call_id,
|
58
|
-
"
|
59
|
-
"content": self.content
|
67
|
+
"content": self.content,
|
60
68
|
}
|
61
69
|
|
62
70
|
|
63
71
|
def extract_tool_calls_from_response(
|
64
|
-
response: Union["
|
72
|
+
response: Union["BaseGenAIModelResponse", "BaseGenAIModelStream"],
|
65
73
|
) -> List[Any]:
|
66
74
|
"""Extract tool calls from various response types."""
|
75
|
+
# ensure type is of agent or language model
|
76
|
+
if response.type not in ["language_model", "agent"]:
|
77
|
+
raise ValueError(f"Response type {response.type} is not supported")
|
78
|
+
|
67
79
|
# Handle LanguageModelResponse
|
68
80
|
if hasattr(response, "get_tool_calls"):
|
69
81
|
tool_calls = response.get_tool_calls()
|
70
82
|
return tool_calls or []
|
71
|
-
|
83
|
+
|
72
84
|
# Handle Stream/AsyncStream - need to collect first
|
73
85
|
if hasattr(response, "collect"):
|
74
86
|
try:
|
@@ -81,7 +93,7 @@ def extract_tool_calls_from_response(
|
|
81
93
|
else:
|
82
94
|
# Sync stream
|
83
95
|
collected_response = response.collect()
|
84
|
-
|
96
|
+
|
85
97
|
if hasattr(collected_response, "get_tool_calls"):
|
86
98
|
return collected_response.get_tool_calls() or []
|
87
99
|
else:
|
@@ -90,67 +102,62 @@ def extract_tool_calls_from_response(
|
|
90
102
|
return response.get_tool_calls() or []
|
91
103
|
except Exception:
|
92
104
|
pass
|
93
|
-
|
105
|
+
|
94
106
|
# Check if response has tool_calls attribute directly
|
95
107
|
if hasattr(response, "tool_calls"):
|
96
108
|
return response.tool_calls or []
|
97
|
-
|
109
|
+
|
98
110
|
return []
|
99
111
|
|
112
|
+
|
100
113
|
def execute_tool_calls_parallel(
|
101
|
-
tool: "Tool
|
102
|
-
tool_calls: List[Any],
|
103
|
-
context: Any = None
|
114
|
+
tool: "Tool", tool_calls: List[Any], context: Any = None
|
104
115
|
) -> List[ToolResponseMessage]:
|
105
116
|
"""Execute multiple tool calls in parallel using ThreadPoolExecutor."""
|
106
|
-
with concurrent.futures.ThreadPoolExecutor(
|
117
|
+
with concurrent.futures.ThreadPoolExecutor(
|
118
|
+
max_workers=min(len(tool_calls), 4)
|
119
|
+
) as executor:
|
107
120
|
futures = [
|
108
121
|
executor.submit(tool.call_from_tool_call, call, context)
|
109
122
|
for call in tool_calls
|
110
123
|
]
|
111
|
-
|
124
|
+
|
112
125
|
results = []
|
113
126
|
for future in concurrent.futures.as_completed(futures):
|
114
127
|
try:
|
115
128
|
results.append(future.result())
|
116
129
|
except Exception as e:
|
117
130
|
# Create error response
|
118
|
-
results.append(
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
131
|
+
results.append(
|
132
|
+
ToolResponseMessage(
|
133
|
+
tool_call_id="unknown",
|
134
|
+
name=tool.name,
|
135
|
+
content=f"Tool execution failed: {str(e)}",
|
136
|
+
)
|
137
|
+
)
|
138
|
+
|
124
139
|
return results
|
125
140
|
|
126
141
|
|
127
|
-
|
128
|
-
class Tool(Generic[P, R]):
|
142
|
+
class Tool(BaseTool[P, R]):
|
129
143
|
"""A tool that wraps a function for agent execution.
|
130
|
-
|
144
|
+
|
131
145
|
Combines concepts from both PydanticAI and OpenAI tool specifications
|
132
146
|
to provide a simple, internalized tool system.
|
133
147
|
"""
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
"""Description of what the tool does."""
|
140
|
-
|
141
|
-
function: Callable[P, R]
|
142
|
-
"""The Python function to execute."""
|
143
|
-
|
144
|
-
parameters_json_schema: Dict[str, Any]
|
145
|
-
"""JSON schema for the tool's parameters."""
|
146
|
-
|
147
|
-
takes_context: bool = False
|
148
|
+
|
149
|
+
takes_context: bool = Field(
|
150
|
+
default=False,
|
151
|
+
description="Whether the function expects a context as first parameter.",
|
152
|
+
)
|
148
153
|
"""Whether the function expects a context as first parameter."""
|
149
|
-
|
150
|
-
strict: bool =
|
154
|
+
|
155
|
+
strict: bool = Field(
|
156
|
+
default=True, description="Whether to enforce strict JSON schema validation."
|
157
|
+
)
|
151
158
|
"""Whether to enforce strict JSON schema validation."""
|
152
|
-
|
153
|
-
def
|
159
|
+
|
160
|
+
def model_post_init(self, __context: Any) -> None:
|
154
161
|
"""Validate the tool after initialization."""
|
155
162
|
if not callable(self.function):
|
156
163
|
raise ValueError("Tool function must be callable")
|
@@ -158,36 +165,36 @@ class Tool(Generic[P, R]):
|
|
158
165
|
raise ValueError("Tool name cannot be empty")
|
159
166
|
if not self.parameters_json_schema:
|
160
167
|
raise ValueError("Tool must have parameters JSON schema")
|
161
|
-
|
168
|
+
|
162
169
|
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
163
170
|
"""Call the tool's function directly with the given arguments.
|
164
|
-
|
171
|
+
|
165
172
|
This allows using the tool as if it were the original function.
|
166
|
-
|
173
|
+
|
167
174
|
Args:
|
168
175
|
*args: Positional arguments to pass to the function
|
169
176
|
**kwargs: Keyword arguments to pass to the function
|
170
|
-
|
177
|
+
|
171
178
|
Returns:
|
172
179
|
The result of the function call
|
173
180
|
"""
|
174
181
|
return self.function(*args, **kwargs)
|
175
|
-
|
182
|
+
|
176
183
|
def call(
|
177
|
-
self,
|
178
|
-
arguments: Union[str, Dict[str, Any]],
|
184
|
+
self,
|
185
|
+
arguments: Union[str, Dict[str, Any]],
|
179
186
|
context: Any = None,
|
180
187
|
) -> Any:
|
181
188
|
"""Execute the tool with given arguments.
|
182
|
-
|
189
|
+
|
183
190
|
Args:
|
184
191
|
arguments: Tool arguments as JSON string or dict
|
185
192
|
context: Optional context to pass as first argument if takes_context=True
|
186
193
|
as_message: Whether to return the result as a ToolResponseMessage
|
187
|
-
|
194
|
+
|
188
195
|
Returns:
|
189
196
|
The result of the function call
|
190
|
-
|
197
|
+
|
191
198
|
Raises:
|
192
199
|
ValidationError: If arguments don't match schema
|
193
200
|
ValueError: If function execution fails
|
@@ -200,24 +207,27 @@ class Tool(Generic[P, R]):
|
|
200
207
|
raise ValidationError(f"Invalid JSON arguments: {e}")
|
201
208
|
else:
|
202
209
|
args_dict = arguments or {}
|
203
|
-
|
210
|
+
|
204
211
|
# Get function signature and validate arguments
|
205
212
|
sig = inspect.signature(self.function)
|
206
|
-
|
213
|
+
|
207
214
|
# Filter out context parameter if needed
|
208
215
|
if self.takes_context:
|
209
|
-
params = {
|
210
|
-
|
216
|
+
params = {
|
217
|
+
k: v
|
218
|
+
for k, v in sig.parameters.items()
|
219
|
+
if k not in ("self", "cls", "context", "ctx")
|
220
|
+
}
|
211
221
|
filtered_sig = sig.replace(parameters=list(params.values()))
|
212
222
|
bound_args = filtered_sig.bind_partial(**args_dict)
|
213
223
|
else:
|
214
224
|
bound_args = sig.bind_partial(**args_dict)
|
215
|
-
|
225
|
+
|
216
226
|
try:
|
217
227
|
bound_args.apply_defaults()
|
218
228
|
except TypeError as e:
|
219
229
|
raise ValidationError(f"Arguments don't match function signature: {e}")
|
220
|
-
|
230
|
+
|
221
231
|
# Execute function with or without context
|
222
232
|
try:
|
223
233
|
if self.takes_context:
|
@@ -226,24 +236,24 @@ class Tool(Generic[P, R]):
|
|
226
236
|
return self.function(**bound_args.arguments)
|
227
237
|
except Exception as e:
|
228
238
|
raise ValueError(f"Tool execution failed: {e}")
|
229
|
-
|
239
|
+
|
230
240
|
def call_from_tool_call(
|
231
|
-
self,
|
232
|
-
tool_call: Union[Dict[str, Any], Any],
|
233
|
-
context: Any = None
|
241
|
+
self, tool_call: Union[Dict[str, Any], Any], context: Any = None
|
234
242
|
) -> ToolResponseMessage:
|
235
243
|
"""Execute tool from a tool call and return a tool response message.
|
236
|
-
|
244
|
+
|
237
245
|
Args:
|
238
246
|
tool_call: Tool call dict or object with function.arguments and id
|
239
247
|
context: Optional context to pass to function
|
240
|
-
|
248
|
+
|
241
249
|
Returns:
|
242
250
|
ToolResponseMessage with tool call ID and result
|
243
251
|
"""
|
244
252
|
# Extract tool call information
|
245
253
|
if isinstance(tool_call, dict):
|
246
|
-
tool_call_id = tool_call.get("id") or tool_call.get(
|
254
|
+
tool_call_id = tool_call.get("id") or tool_call.get(
|
255
|
+
"tool_call_id", "unknown"
|
256
|
+
)
|
247
257
|
if "function" in tool_call:
|
248
258
|
arguments = tool_call["function"].get("arguments", "{}")
|
249
259
|
else:
|
@@ -255,60 +265,64 @@ class Tool(Generic[P, R]):
|
|
255
265
|
arguments = getattr(tool_call.function, "arguments", "{}")
|
256
266
|
else:
|
257
267
|
arguments = getattr(tool_call, "arguments", "{}")
|
258
|
-
|
268
|
+
|
259
269
|
# Execute the tool
|
260
270
|
try:
|
261
271
|
result = self.call(arguments, context)
|
262
|
-
content =
|
272
|
+
content = (
|
273
|
+
str(result) if result is not None else "Tool executed successfully"
|
274
|
+
)
|
263
275
|
except Exception as e:
|
264
276
|
content = f"Tool execution failed: {str(e)}"
|
265
|
-
|
277
|
+
|
266
278
|
return ToolResponseMessage(
|
267
|
-
tool_call_id=tool_call_id,
|
268
|
-
name=self.name,
|
269
|
-
content=content
|
279
|
+
tool_call_id=tool_call_id, name=self.name, content=content
|
270
280
|
)
|
271
|
-
|
281
|
+
|
272
282
|
def call_from_response(
|
273
|
-
self,
|
274
|
-
response: Union["
|
283
|
+
self,
|
284
|
+
response: Union["BaseGenAIModelResponse", "BaseGenAIModelStream"],
|
275
285
|
context: Any = None,
|
276
|
-
parallel: bool = True
|
286
|
+
parallel: bool = True,
|
277
287
|
) -> List[ToolResponseMessage]:
|
278
288
|
"""Execute tool calls found in a language model response or stream.
|
279
|
-
|
289
|
+
|
280
290
|
Args:
|
281
291
|
response: LanguageModelResponse, Stream, or AsyncStream
|
282
292
|
context: Optional context to pass to functions
|
283
293
|
parallel: Whether to execute tool calls in parallel
|
284
|
-
|
294
|
+
|
285
295
|
Returns:
|
286
296
|
List of ToolResponseMessage objects
|
287
297
|
"""
|
288
298
|
tool_calls = extract_tool_calls_from_response(response)
|
289
|
-
|
299
|
+
|
290
300
|
if not tool_calls:
|
291
301
|
return []
|
292
|
-
|
302
|
+
|
293
303
|
# Filter tool calls that match this tool's name
|
294
304
|
matching_calls = []
|
295
305
|
for tool_call in tool_calls:
|
296
306
|
if isinstance(tool_call, dict):
|
297
307
|
func_name = tool_call.get("function", {}).get("name")
|
298
308
|
else:
|
299
|
-
func_name =
|
300
|
-
|
309
|
+
func_name = (
|
310
|
+
getattr(tool_call.function, "name", None)
|
311
|
+
if hasattr(tool_call, "function")
|
312
|
+
else None
|
313
|
+
)
|
314
|
+
|
301
315
|
if func_name == self.name:
|
302
316
|
matching_calls.append(tool_call)
|
303
|
-
|
317
|
+
|
304
318
|
if not matching_calls:
|
305
319
|
return []
|
306
|
-
|
320
|
+
|
307
321
|
if parallel and len(matching_calls) > 1:
|
308
322
|
return execute_tool_calls_parallel(self, matching_calls, context)
|
309
323
|
else:
|
310
324
|
return [self.call_from_tool_call(call, context) for call in matching_calls]
|
311
|
-
|
325
|
+
|
312
326
|
def to_dict(self) -> Dict[str, Any]:
|
313
327
|
"""Convert tool to dictionary format suitable for API calls."""
|
314
328
|
return {
|
@@ -317,68 +331,68 @@ class Tool(Generic[P, R]):
|
|
317
331
|
"name": self.name,
|
318
332
|
"description": self.description,
|
319
333
|
"parameters": self.parameters_json_schema,
|
320
|
-
"strict": self.strict
|
321
|
-
}
|
334
|
+
"strict": self.strict,
|
335
|
+
},
|
322
336
|
}
|
323
337
|
|
324
338
|
|
325
339
|
@overload
|
326
|
-
def
|
340
|
+
def define_tool(
|
327
341
|
function: Callable[P, R],
|
328
|
-
) -> Tool
|
329
|
-
"""Overload for direct decorator usage: @
|
342
|
+
) -> Tool:
|
343
|
+
"""Overload for direct decorator usage: @define_tool"""
|
330
344
|
...
|
331
345
|
|
332
346
|
|
333
347
|
@overload
|
334
|
-
def
|
348
|
+
def define_tool(
|
335
349
|
*,
|
336
350
|
name: Optional[str] = None,
|
337
351
|
description: Optional[str] = None,
|
338
352
|
takes_context: bool = False,
|
339
353
|
strict: bool = True,
|
340
|
-
) -> Callable[[Callable[P, R]], Tool
|
341
|
-
"""Overload for decorator with parameters: @
|
354
|
+
) -> Callable[[Callable[P, R]], Tool]:
|
355
|
+
"""Overload for decorator with parameters: @define_tool(...)"""
|
342
356
|
...
|
343
357
|
|
344
358
|
|
345
|
-
def
|
359
|
+
def define_tool(
|
346
360
|
function: Optional[Callable[P, R]] = None,
|
347
361
|
*,
|
348
362
|
name: Optional[str] = None,
|
349
363
|
description: Optional[str] = None,
|
350
364
|
takes_context: bool = False,
|
351
365
|
strict: bool = True,
|
352
|
-
) -> Union[Tool
|
366
|
+
) -> Union[Tool, Callable[[Callable[P, R]], Tool]]:
|
353
367
|
"""Decorator to create a Tool from a function.
|
354
|
-
|
368
|
+
|
355
369
|
Args:
|
356
|
-
func: Function to wrap (when used as @
|
370
|
+
func: Function to wrap (when used as @define_tool)
|
357
371
|
name: Override tool name (defaults to function name)
|
358
372
|
description: Override tool description (defaults to function docstring)
|
359
373
|
takes_context: Whether function expects context as first parameter
|
360
374
|
strict: Whether to enforce strict JSON schema validation
|
361
|
-
|
375
|
+
|
362
376
|
Returns:
|
363
377
|
Tool instance or decorator function
|
364
|
-
|
378
|
+
|
365
379
|
Example:
|
366
|
-
@
|
380
|
+
@define_tool
|
367
381
|
def my_tool(x: int, y: str = "default") -> str:
|
368
382
|
\"\"\"Does something useful.\"\"\"
|
369
383
|
return f"{x}: {y}"
|
370
|
-
|
384
|
+
|
371
385
|
# Or with parameters:
|
372
|
-
@
|
386
|
+
@define_tool(name="custom_name", takes_context=True)
|
373
387
|
def context_tool(ctx, value: int) -> int:
|
374
388
|
return value * 2
|
375
389
|
"""
|
376
|
-
|
377
|
-
def _create_tool(target_func: Callable[P, R]) -> Tool
|
390
|
+
|
391
|
+
def _create_tool(target_func: Callable[P, R]) -> Tool:
|
378
392
|
# Extract function metadata
|
379
393
|
func_name = name or target_func.__name__
|
380
394
|
func_description = description or (target_func.__doc__ or "").strip()
|
381
|
-
|
395
|
+
|
382
396
|
# Generate JSON schema from function signature
|
383
397
|
try:
|
384
398
|
# Try using Pydantic converter first for better schema generation
|
@@ -386,8 +400,7 @@ def function_tool(
|
|
386
400
|
if pydantic_fields:
|
387
401
|
# Create temporary Pydantic model to get schema
|
388
402
|
temp_model = convert_to_pydantic_model(
|
389
|
-
target_func,
|
390
|
-
name=f"{func_name}_params"
|
403
|
+
target_func, name=f"{func_name}_params"
|
391
404
|
)
|
392
405
|
schema = temp_model.model_json_schema()
|
393
406
|
# Extract just the properties and required fields
|
@@ -405,81 +418,83 @@ def function_tool(
|
|
405
418
|
except Exception:
|
406
419
|
# Ultimate fallback
|
407
420
|
parameters_schema = _generate_schema_from_signature(target_func, strict)
|
408
|
-
|
409
|
-
return Tool
|
421
|
+
|
422
|
+
return Tool(
|
410
423
|
name=func_name,
|
411
424
|
description=func_description,
|
412
425
|
function=target_func,
|
413
426
|
parameters_json_schema=parameters_schema,
|
414
427
|
takes_context=takes_context,
|
415
|
-
strict=strict
|
428
|
+
strict=strict,
|
416
429
|
)
|
417
|
-
|
430
|
+
|
418
431
|
# Handle decorator usage patterns
|
419
432
|
if function is None:
|
420
|
-
# Used as @
|
433
|
+
# Used as @define_tool(...)
|
421
434
|
return _create_tool
|
422
435
|
else:
|
423
|
-
# Used as @
|
436
|
+
# Used as @define_tool
|
424
437
|
return _create_tool(function)
|
425
438
|
|
426
439
|
|
427
|
-
def _generate_schema_from_signature(
|
440
|
+
def _generate_schema_from_signature(
|
441
|
+
func: Callable, strict: bool = True
|
442
|
+
) -> Dict[str, Any]:
|
428
443
|
"""Generate JSON schema from function signature as fallback."""
|
429
444
|
sig = inspect.signature(func)
|
430
445
|
type_hints = get_type_hints(func)
|
431
|
-
|
446
|
+
|
432
447
|
properties = {}
|
433
448
|
required = []
|
434
|
-
|
449
|
+
|
435
450
|
for param_name, param in sig.parameters.items():
|
436
451
|
if param_name == "self" or param_name == "cls":
|
437
452
|
continue
|
438
|
-
|
453
|
+
|
439
454
|
param_type = type_hints.get(param_name, str)
|
440
|
-
|
455
|
+
|
441
456
|
try:
|
442
457
|
# Use JSON converter for type
|
443
458
|
param_schema = convert_to_json_schema(param_type)
|
444
459
|
except Exception:
|
445
460
|
# Ultimate fallback
|
446
461
|
param_schema = {"type": "string"}
|
447
|
-
|
462
|
+
|
448
463
|
properties[param_name] = param_schema
|
449
|
-
|
464
|
+
|
450
465
|
# Add to required if no default value
|
451
466
|
if param.default is inspect.Parameter.empty:
|
452
467
|
required.append(param_name)
|
453
|
-
|
468
|
+
|
454
469
|
schema = {
|
455
470
|
"type": "object",
|
456
471
|
"properties": properties,
|
457
472
|
}
|
458
|
-
|
473
|
+
|
459
474
|
if required:
|
460
475
|
schema["required"] = required
|
461
|
-
|
476
|
+
|
462
477
|
if strict:
|
463
478
|
schema["additionalProperties"] = False
|
464
|
-
|
479
|
+
|
465
480
|
return schema
|
466
481
|
|
467
482
|
|
468
483
|
# Utility functions for batch tool execution
|
469
|
-
def
|
484
|
+
def execute_tools_from_language_model_response(
|
470
485
|
tools: List[Tool],
|
471
|
-
response: Union["
|
486
|
+
response: Union["BaseGenAIModelResponse", "BaseGenAIModelStream"],
|
472
487
|
context: Any = None,
|
473
|
-
parallel: bool = True
|
488
|
+
parallel: bool = True,
|
474
489
|
) -> List[ToolResponseMessage]:
|
475
490
|
"""Execute all matching tools from a response.
|
476
|
-
|
491
|
+
|
477
492
|
Args:
|
478
493
|
tools: List of tools to check for matches
|
479
494
|
response: LanguageModelResponse, Stream, or AsyncStream
|
480
495
|
context: Optional context to pass to functions
|
481
496
|
parallel: Whether to execute tool calls in parallel
|
482
|
-
|
497
|
+
|
483
498
|
Returns:
|
484
499
|
List of ToolResponseMessage objects from all executed tools
|
485
500
|
"""
|
@@ -487,4 +502,4 @@ def execute_tools_from_response(
|
|
487
502
|
for tool in tools:
|
488
503
|
results = tool.call_from_response(response, context, parallel)
|
489
504
|
all_results.extend(results)
|
490
|
-
return all_results
|
505
|
+
return all_results
|
hammad/logging/logger.py
CHANGED
@@ -241,7 +241,8 @@ class RichLoggerFormatter(_logging.Formatter):
|
|
241
241
|
record.message = record.getMessage()
|
242
242
|
|
243
243
|
# Now format with the styled values
|
244
|
-
|
244
|
+
formatted = self._style._fmt.format(**record.__dict__)
|
245
|
+
return formatted if formatted != "None" else ""
|
245
246
|
|
246
247
|
def _build_renderable_style_string(self, style_dict: dict) -> str:
|
247
248
|
"""Build a rich markup style string from a CLIStyleRenderableSettings dictionary."""
|
hammad/mcp/client/__init__.py
CHANGED
@@ -12,16 +12,14 @@ if TYPE_CHECKING:
|
|
12
12
|
MCPClientSettings,
|
13
13
|
MCPClientSseSettings,
|
14
14
|
MCPClientStreamableHttpSettings,
|
15
|
-
MCPClientStdioSettings
|
15
|
+
MCPClientStdioSettings,
|
16
16
|
)
|
17
17
|
|
18
18
|
__all__ = (
|
19
19
|
# hammad.mcp.client
|
20
20
|
"MCPClient",
|
21
|
-
|
22
21
|
# hammad.mcp.client.client_service
|
23
22
|
"MCPClientService",
|
24
|
-
|
25
23
|
# hammad.mcp.client.settings
|
26
24
|
"MCPClientSettings",
|
27
25
|
"MCPClientSseSettings",
|
@@ -31,6 +29,7 @@ __all__ = (
|
|
31
29
|
|
32
30
|
__getattr__ = create_getattr_importer(__all__)
|
33
31
|
|
32
|
+
|
34
33
|
def __dir__() -> list[str]:
|
35
34
|
"""Get the attributes of the client module."""
|
36
35
|
return list(__all__)
|