hammad-python 0.0.19__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.
Files changed (83) hide show
  1. hammad/__init__.py +7 -137
  2. hammad/_internal.py +1 -0
  3. hammad/cli/_runner.py +8 -8
  4. hammad/cli/plugins.py +55 -26
  5. hammad/cli/styles/utils.py +16 -8
  6. hammad/data/__init__.py +1 -5
  7. hammad/data/collections/__init__.py +2 -3
  8. hammad/data/collections/collection.py +41 -22
  9. hammad/data/collections/indexes/__init__.py +1 -1
  10. hammad/data/collections/indexes/qdrant/__init__.py +1 -1
  11. hammad/data/collections/indexes/qdrant/index.py +106 -118
  12. hammad/data/collections/indexes/qdrant/settings.py +14 -14
  13. hammad/data/collections/indexes/qdrant/utils.py +28 -38
  14. hammad/data/collections/indexes/tantivy/__init__.py +1 -1
  15. hammad/data/collections/indexes/tantivy/index.py +57 -59
  16. hammad/data/collections/indexes/tantivy/settings.py +8 -19
  17. hammad/data/collections/indexes/tantivy/utils.py +28 -52
  18. hammad/data/models/__init__.py +2 -7
  19. hammad/data/sql/__init__.py +1 -1
  20. hammad/data/sql/database.py +71 -73
  21. hammad/data/sql/types.py +37 -51
  22. hammad/formatting/__init__.py +2 -1
  23. hammad/formatting/json/converters.py +2 -2
  24. hammad/genai/__init__.py +96 -36
  25. hammad/genai/agents/__init__.py +47 -1
  26. hammad/genai/agents/agent.py +1022 -0
  27. hammad/genai/agents/run.py +615 -0
  28. hammad/genai/agents/types/__init__.py +29 -22
  29. hammad/genai/agents/types/agent_context.py +13 -0
  30. hammad/genai/agents/types/agent_event.py +128 -0
  31. hammad/genai/agents/types/agent_hooks.py +220 -0
  32. hammad/genai/agents/types/agent_messages.py +31 -0
  33. hammad/genai/agents/types/agent_response.py +90 -0
  34. hammad/genai/agents/types/agent_stream.py +242 -0
  35. hammad/genai/models/__init__.py +1 -0
  36. hammad/genai/models/embeddings/__init__.py +39 -0
  37. hammad/genai/{embedding_models/embedding_model.py → models/embeddings/model.py} +45 -41
  38. hammad/genai/{embedding_models → models/embeddings}/run.py +10 -8
  39. hammad/genai/models/embeddings/types/__init__.py +37 -0
  40. hammad/genai/{embedding_models → models/embeddings/types}/embedding_model_name.py +2 -4
  41. hammad/genai/{embedding_models → models/embeddings/types}/embedding_model_response.py +11 -4
  42. hammad/genai/{embedding_models/embedding_model_request.py → models/embeddings/types/embedding_model_run_params.py} +4 -3
  43. hammad/genai/models/embeddings/types/embedding_model_settings.py +47 -0
  44. hammad/genai/models/language/__init__.py +48 -0
  45. hammad/genai/{language_models/language_model.py → models/language/model.py} +481 -204
  46. hammad/genai/{language_models → models/language}/run.py +80 -57
  47. hammad/genai/models/language/types/__init__.py +40 -0
  48. hammad/genai/models/language/types/language_model_instructor_mode.py +47 -0
  49. hammad/genai/models/language/types/language_model_messages.py +28 -0
  50. hammad/genai/{language_models/_types.py → models/language/types/language_model_name.py} +3 -40
  51. hammad/genai/{language_models → models/language/types}/language_model_request.py +17 -25
  52. hammad/genai/{language_models → models/language/types}/language_model_response.py +61 -68
  53. hammad/genai/{language_models → models/language/types}/language_model_response_chunk.py +8 -5
  54. hammad/genai/models/language/types/language_model_settings.py +89 -0
  55. hammad/genai/{language_models/_streaming.py → models/language/types/language_model_stream.py} +221 -243
  56. hammad/genai/{language_models/_utils → models/language/utils}/__init__.py +8 -11
  57. hammad/genai/models/language/utils/requests.py +421 -0
  58. hammad/genai/{language_models/_utils/_structured_outputs.py → models/language/utils/structured_outputs.py} +31 -20
  59. hammad/genai/models/model_provider.py +4 -0
  60. hammad/genai/{multimodal_models.py → models/multimodal.py} +4 -5
  61. hammad/genai/models/reranking.py +26 -0
  62. hammad/genai/types/__init__.py +1 -0
  63. hammad/genai/types/base.py +215 -0
  64. hammad/genai/{agents/types → types}/history.py +101 -88
  65. hammad/genai/{agents/types/tool.py → types/tools.py} +156 -141
  66. hammad/logging/logger.py +1 -1
  67. hammad/mcp/client/__init__.py +2 -3
  68. hammad/mcp/client/client.py +10 -10
  69. hammad/mcp/servers/__init__.py +2 -1
  70. hammad/service/decorators.py +1 -3
  71. hammad/web/models.py +1 -3
  72. hammad/web/search/client.py +10 -22
  73. {hammad_python-0.0.19.dist-info → hammad_python-0.0.20.dist-info}/METADATA +10 -2
  74. hammad_python-0.0.20.dist-info/RECORD +127 -0
  75. hammad/genai/embedding_models/__init__.py +0 -41
  76. hammad/genai/language_models/__init__.py +0 -35
  77. hammad/genai/language_models/_utils/_completions.py +0 -131
  78. hammad/genai/language_models/_utils/_messages.py +0 -89
  79. hammad/genai/language_models/_utils/_requests.py +0 -202
  80. hammad/genai/rerank_models.py +0 -26
  81. hammad_python-0.0.19.dist-info/RECORD +0 -111
  82. {hammad_python-0.0.19.dist-info → hammad_python-0.0.20.dist-info}/WHEEL +0 -0
  83. {hammad_python-0.0.19.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 Any, Callable, Dict, List, Optional, Union, get_type_hints, TYPE_CHECKING, Generic, TypeVar, ParamSpec, overload
11
- from dataclasses import dataclass
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 ....formatting.json.converters import convert_to_json_schema
15
- from ....data.models.extensions.pydantic.converters import (
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
- "function_tool",
40
+ "define_tool",
31
41
  "ToolResponseMessage",
32
42
  "execute_tool_calls_parallel",
33
- "execute_tools_from_response",
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
- "name": self.name,
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["LanguageModelResponse", "Stream", "AsyncStream"]
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[P, R]",
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(max_workers=min(len(tool_calls), 4)) as executor:
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(ToolResponseMessage(
119
- tool_call_id="unknown",
120
- name=tool.name,
121
- content=f"Tool execution failed: {str(e)}"
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
- @dataclass
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
- name: str
136
- """The name of the tool."""
137
-
138
- description: str
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 = True
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 __post_init__(self):
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 = {k: v for k, v in sig.parameters.items()
210
- if k not in ('self', 'cls', 'context', 'ctx')}
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("tool_call_id", "unknown")
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 = str(result) if result is not None else "Tool executed successfully"
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["LanguageModelResponse", "Stream", "AsyncStream"],
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 = getattr(tool_call.function, "name", None) if hasattr(tool_call, "function") else None
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 function_tool(
340
+ def define_tool(
327
341
  function: Callable[P, R],
328
- ) -> Tool[P, R]:
329
- """Overload for direct decorator usage: @function_tool"""
342
+ ) -> Tool:
343
+ """Overload for direct decorator usage: @define_tool"""
330
344
  ...
331
345
 
332
346
 
333
347
  @overload
334
- def function_tool(
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[P, R]]:
341
- """Overload for decorator with parameters: @function_tool(...)"""
354
+ ) -> Callable[[Callable[P, R]], Tool]:
355
+ """Overload for decorator with parameters: @define_tool(...)"""
342
356
  ...
343
357
 
344
358
 
345
- def function_tool(
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[P, R], Callable[[Callable[P, R]], Tool[P, R]]]:
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 @function_tool)
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
- @function_tool
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
- @function_tool(name="custom_name", takes_context=True)
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[P, R]:
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[P, R](
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 @function_tool(...)
433
+ # Used as @define_tool(...)
421
434
  return _create_tool
422
435
  else:
423
- # Used as @function_tool
436
+ # Used as @define_tool
424
437
  return _create_tool(function)
425
438
 
426
439
 
427
- def _generate_schema_from_signature(func: Callable, strict: bool = True) -> Dict[str, Any]:
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 execute_tools_from_response(
484
+ def execute_tools_from_language_model_response(
470
485
  tools: List[Tool],
471
- response: Union["LanguageModelResponse", "Stream", "AsyncStream"],
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
@@ -242,7 +242,7 @@ class RichLoggerFormatter(_logging.Formatter):
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
+ return formatted if formatted != "None" else ""
246
246
 
247
247
  def _build_renderable_style_string(self, style_dict: dict) -> str:
248
248
  """Build a rich markup style string from a CLIStyleRenderableSettings dictionary."""
@@ -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__)