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.
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 +2 -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.18.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.18.dist-info/RECORD +0 -111
  82. {hammad_python-0.0.18.dist-info → hammad_python-0.0.20.dist-info}/WHEEL +0 -0
  83. {hammad_python-0.0.18.dist-info → hammad_python-0.0.20.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,128 @@
1
+ """hammad.genai.agents.types.agent_event"""
2
+
3
+ from typing import Any, Dict, Optional, Literal
4
+ from openai.types.chat import ChatCompletionMessageParam, ChatCompletionContentPartParam
5
+
6
+ from ...types.base import BaseGenAIModelEvent
7
+
8
+
9
+ __all__ = [
10
+ "AgentEvent",
11
+ ]
12
+
13
+
14
+ class AgentEvent(BaseGenAIModelEvent[Any]):
15
+ """Base class for all agent events with universal event checking.
16
+
17
+ This class extends BaseGenAIModelEvent to provide agent-specific
18
+ event handling capabilities.
19
+ """
20
+
21
+ type: Literal["agent"] = "agent"
22
+ """The type of the model. Always 'agent'."""
23
+
24
+ model: str = "agent"
25
+ """The model that generated this event."""
26
+
27
+ output: Any = None
28
+ """The event data/output."""
29
+
30
+ event_type: str
31
+ """The specific type of event (e.g., 'context_update', 'tool_call', etc.)."""
32
+
33
+ metadata: Dict[str, Any] = {}
34
+ """Additional metadata for the event."""
35
+
36
+ def __init__(
37
+ self,
38
+ event_type: str,
39
+ data: Any = None,
40
+ metadata: Optional[Dict[str, Any]] = None,
41
+ **kwargs: Any,
42
+ ):
43
+ """Initialize an AgentEvent.
44
+
45
+ Args:
46
+ event_type: The type of event
47
+ data: The event data
48
+ metadata: Additional metadata
49
+ **kwargs: Additional keyword arguments
50
+ """
51
+ super().__init__(
52
+ output=data, event_type=event_type, metadata=metadata or {}, **kwargs
53
+ )
54
+
55
+ def is_event(self, event_type: str) -> bool:
56
+ """Universal event type checker."""
57
+ return self.event_type == event_type
58
+
59
+ def is_event_category(self, category: str) -> bool:
60
+ """Check if event belongs to a category (e.g., 'tool' matches 'tool_call', 'tool_execution')."""
61
+ return self.event_type.startswith(category)
62
+
63
+ def has_metadata(self, key: str) -> bool:
64
+ """Check if event has specific metadata."""
65
+ return key in self.metadata
66
+
67
+ def get_metadata(self, key: str, default: Any = None) -> Any:
68
+ """Get metadata value."""
69
+ return self.metadata.get(key, default)
70
+
71
+ # Common event type checks (for convenience)
72
+ def is_context_update(self) -> bool:
73
+ return self.is_event("context_update")
74
+
75
+ def is_tool_call(self) -> bool:
76
+ return self.is_event("tool_call")
77
+
78
+ def is_tool_execution(self) -> bool:
79
+ return self.is_event("tool_execution")
80
+
81
+ def is_tool_response(self) -> bool:
82
+ return self.is_event("tool_response")
83
+
84
+ def is_final_response(self) -> bool:
85
+ return self.is_event("final_response")
86
+
87
+ def is_step_start(self) -> bool:
88
+ return self.is_event("step_start")
89
+
90
+ def is_step_end(self) -> bool:
91
+ return self.is_event("step_end")
92
+
93
+ def is_error(self) -> bool:
94
+ return self.is_event("error")
95
+
96
+ def is_stream_chunk(self) -> bool:
97
+ return self.is_event("stream_chunk")
98
+
99
+ # Category checks
100
+ def is_tool_event(self) -> bool:
101
+ return self.is_event_category("tool")
102
+
103
+ def is_context_event(self) -> bool:
104
+ return self.is_event_category("context")
105
+
106
+ def is_stream_event(self) -> bool:
107
+ return self.is_event_category("stream")
108
+
109
+ def to_message(self) -> ChatCompletionMessageParam:
110
+ """Convert the event to a chat completion message."""
111
+ content = f"Event: {self.event_type}"
112
+ if self.output:
113
+ content += f"\nData: {self.output}"
114
+ if self.metadata:
115
+ content += f"\nMetadata: {self.metadata}"
116
+
117
+ return {"role": "assistant", "content": content}
118
+
119
+ def to_content_part(self) -> ChatCompletionContentPartParam:
120
+ """Convert the event to a chat completion content part."""
121
+ content = f"Event: {self.event_type}"
122
+ if self.output:
123
+ content += f" - {self.output}"
124
+
125
+ return {"type": "text", "text": content}
126
+
127
+ def __repr__(self) -> str:
128
+ return f"AgentEvent(type='{self.event_type}', data={self.output}, metadata={self.metadata})"
@@ -0,0 +1,220 @@
1
+ """hammad.genai.agents.types.agent_hooks"""
2
+
3
+ from typing import Any, Dict, List, Callable, Optional
4
+ from .agent_event import AgentEvent
5
+
6
+
7
+ __all__ = [
8
+ "HookManager",
9
+ "HookDecorator",
10
+ ]
11
+
12
+
13
+ class HookManager:
14
+ """Manages hooks for agent events."""
15
+
16
+ def __init__(self):
17
+ self.hooks: Dict[str, List[Callable]] = {}
18
+ self.specific_hooks: Dict[str, Dict[str, List[Callable]]] = {}
19
+
20
+ def register_hook(
21
+ self, event_type: str, callback: Callable, specific_name: Optional[str] = None
22
+ ):
23
+ """Register a hook for an event type.
24
+
25
+ Args:
26
+ event_type: The type of event to hook into
27
+ callback: The callback function to execute
28
+ specific_name: Optional specific name for targeted hooks
29
+ """
30
+ if specific_name:
31
+ if event_type not in self.specific_hooks:
32
+ self.specific_hooks[event_type] = {}
33
+ if specific_name not in self.specific_hooks[event_type]:
34
+ self.specific_hooks[event_type][specific_name] = []
35
+ self.specific_hooks[event_type][specific_name].append(callback)
36
+ else:
37
+ if event_type not in self.hooks:
38
+ self.hooks[event_type] = []
39
+ self.hooks[event_type].append(callback)
40
+
41
+ def trigger_hooks(
42
+ self, event_type: str, data: Any, specific_name: Optional[str] = None
43
+ ) -> Any:
44
+ """Trigger hooks for an event type.
45
+
46
+ Args:
47
+ event_type: The type of event
48
+ data: The event data
49
+ specific_name: Optional specific name for targeted hooks
50
+
51
+ Returns:
52
+ The potentially modified data after running through hooks
53
+ """
54
+ result = data
55
+
56
+ # Trigger general hooks
57
+ if event_type in self.hooks:
58
+ for hook in self.hooks[event_type]:
59
+ hook_result = hook(result)
60
+ if hook_result is not None:
61
+ result = hook_result
62
+
63
+ # Trigger specific hooks
64
+ if specific_name and event_type in self.specific_hooks:
65
+ if specific_name in self.specific_hooks[event_type]:
66
+ for hook in self.specific_hooks[event_type][specific_name]:
67
+ hook_result = hook(result)
68
+ if hook_result is not None:
69
+ result = hook_result
70
+
71
+ return result
72
+
73
+ def trigger_event(
74
+ self, event: AgentEvent, specific_name: Optional[str] = None
75
+ ) -> Any:
76
+ """Trigger hooks for an AgentEvent.
77
+
78
+ Args:
79
+ event: The AgentEvent instance
80
+ specific_name: Optional specific name for targeted hooks
81
+
82
+ Returns:
83
+ The potentially modified event data after running through hooks
84
+ """
85
+ return self.trigger_hooks(event.event_type, event.output, specific_name)
86
+
87
+ def clear_hooks(
88
+ self, event_type: Optional[str] = None, specific_name: Optional[str] = None
89
+ ):
90
+ """Clear hooks for a specific event type or all hooks.
91
+
92
+ Args:
93
+ event_type: Optional event type to clear (clears all if None)
94
+ specific_name: Optional specific name to clear
95
+ """
96
+ if event_type is None:
97
+ self.hooks.clear()
98
+ self.specific_hooks.clear()
99
+ else:
100
+ if event_type in self.hooks:
101
+ if specific_name is None:
102
+ del self.hooks[event_type]
103
+
104
+ if event_type in self.specific_hooks:
105
+ if specific_name is None:
106
+ del self.specific_hooks[event_type]
107
+ elif specific_name in self.specific_hooks[event_type]:
108
+ del self.specific_hooks[event_type][specific_name]
109
+
110
+ def list_hooks(self) -> Dict[str, Any]:
111
+ """List all registered hooks.
112
+
113
+ Returns:
114
+ Dictionary containing all hooks information
115
+ """
116
+ return {
117
+ "general_hooks": {k: len(v) for k, v in self.hooks.items()},
118
+ "specific_hooks": {
119
+ k: {sk: len(sv) for sk, sv in v.items()}
120
+ for k, v in self.specific_hooks.items()
121
+ },
122
+ }
123
+
124
+
125
+ class HookDecorator:
126
+ """Provides type-hinted hook decorators with extensible event system."""
127
+
128
+ def __init__(self, hook_manager: HookManager):
129
+ self.hook_manager = hook_manager
130
+ self._event_types = set()
131
+
132
+ def __call__(self, event_type: str, specific_name: Optional[str] = None):
133
+ """Register a hook for any event type.
134
+
135
+ Args:
136
+ event_type: The type of event to hook into
137
+ specific_name: Optional specific name for targeted hooks
138
+
139
+ Returns:
140
+ Decorator function
141
+ """
142
+
143
+ def decorator(func: Callable):
144
+ self.hook_manager.register_hook(event_type, func, specific_name)
145
+ self._event_types.add(event_type)
146
+ return func
147
+
148
+ return decorator
149
+
150
+ def get_registered_event_types(self) -> set:
151
+ """Get all registered event types."""
152
+ return self._event_types.copy()
153
+
154
+ def supports_event(self, event_type: str) -> bool:
155
+ """Check if an event type is supported."""
156
+ return event_type in self._event_types
157
+
158
+ # Convenience decorators for common events
159
+ def on_context_update(self, specific_name: Optional[str] = None):
160
+ """Decorator for context update events."""
161
+ return self.__call__("context_update", specific_name)
162
+
163
+ def on_tool_call(self, specific_name: Optional[str] = None):
164
+ """Decorator for tool call events."""
165
+ return self.__call__("tool_call", specific_name)
166
+
167
+ def on_tool_execution(self, specific_name: Optional[str] = None):
168
+ """Decorator for tool execution events."""
169
+ return self.__call__("tool_execution", specific_name)
170
+
171
+ def on_tool_response(self, specific_name: Optional[str] = None):
172
+ """Decorator for tool response events."""
173
+ return self.__call__("tool_response", specific_name)
174
+
175
+ def on_final_response(self, specific_name: Optional[str] = None):
176
+ """Decorator for final response events."""
177
+ return self.__call__("final_response", specific_name)
178
+
179
+ def on_step_start(self, specific_name: Optional[str] = None):
180
+ """Decorator for step start events."""
181
+ return self.__call__("step_start", specific_name)
182
+
183
+ def on_step_end(self, specific_name: Optional[str] = None):
184
+ """Decorator for step end events."""
185
+ return self.__call__("step_end", specific_name)
186
+
187
+ def on_error(self, specific_name: Optional[str] = None):
188
+ """Decorator for error events."""
189
+ return self.__call__("error", specific_name)
190
+
191
+ def on_stream_chunk(self, specific_name: Optional[str] = None):
192
+ """Decorator for stream chunk events."""
193
+ return self.__call__("stream_chunk", specific_name)
194
+
195
+ # Dynamic event type classes - these are created dynamically
196
+ # based on the actual events that occur in the system
197
+ def __getattr__(self, name: str):
198
+ """Dynamically create event type classes for better type hinting."""
199
+ if name.startswith("_"):
200
+ raise AttributeError(
201
+ f"'{self.__class__.__name__}' object has no attribute '{name}'"
202
+ )
203
+
204
+ # Create a dynamic class for this event type
205
+ class DynamicEventType:
206
+ def __init__(
207
+ self, event_data: Any = None, metadata: Optional[Dict[str, Any]] = None
208
+ ):
209
+ self.event_data = event_data
210
+ self.metadata = metadata or {}
211
+
212
+ def __repr__(self):
213
+ return f"{name}(data={self.event_data}, metadata={self.metadata})"
214
+
215
+ DynamicEventType.__name__ = name
216
+ DynamicEventType.__qualname__ = f"{self.__class__.__name__}.{name}"
217
+
218
+ # Cache the class
219
+ setattr(self, name, DynamicEventType)
220
+ return DynamicEventType
@@ -0,0 +1,31 @@
1
+ """hammad.genai.agents.types.agent_messages"""
2
+
3
+ from typing import TypeAlias, Union, List, Any, TYPE_CHECKING
4
+
5
+ from ...models.language.types import LanguageModelMessages
6
+
7
+ if TYPE_CHECKING:
8
+ from openai.types.chat import ChatCompletionMessageParam
9
+ from ...types.history import History
10
+
11
+
12
+ __all__ = [
13
+ "AgentMessages",
14
+ ]
15
+
16
+
17
+ AgentMessages: TypeAlias = Union[
18
+ str,
19
+ "ChatCompletionMessageParam",
20
+ "List[ChatCompletionMessageParam]",
21
+ "History",
22
+ LanguageModelMessages,
23
+ Any,
24
+ ]
25
+ """Type alias for agent message inputs that extends LanguageModelMessages to include History objects.
26
+
27
+ This type alias allows agents to accept:
28
+ - All standard LanguageModelMessages types (str, ChatCompletionMessageParam, List[ChatCompletionMessageParam])
29
+ - History objects for conversation history management
30
+ - Any other type for flexibility
31
+ """
@@ -0,0 +1,90 @@
1
+ """hammad.genai.agents.types.agent_response"""
2
+
3
+ from typing import List, Any, TypeVar, Literal
4
+
5
+ from ....cache import cached
6
+ from ...models.language.types import (
7
+ LanguageModelResponse,
8
+ )
9
+
10
+
11
+ __all__ = [
12
+ "AgentResponse",
13
+ "_create_agent_response_from_language_model_response",
14
+ ]
15
+
16
+
17
+ T = TypeVar("T")
18
+
19
+
20
+ def _create_agent_response_from_language_model_response(
21
+ response: LanguageModelResponse[T],
22
+ steps: List[LanguageModelResponse[str]] | None = None,
23
+ context: Any = None,
24
+ ) -> "AgentResponse[T]":
25
+ """Create a AgentResponse from a LanguageModelResponse."""
26
+ try:
27
+ return AgentResponse(
28
+ type="agent",
29
+ model=response.model,
30
+ output=response.output,
31
+ content=response.content,
32
+ completion=response.completion,
33
+ refusal=response.refusal,
34
+ steps=steps or [],
35
+ context=context,
36
+ )
37
+ except Exception as e:
38
+ raise ValueError(
39
+ "Failed to create AgentResponse from LanguageModelResponse."
40
+ ) from e
41
+
42
+
43
+ class AgentResponse(LanguageModelResponse[T]):
44
+ """A response generated by an agent, that includes the steps and final
45
+ output during the agent's execution."""
46
+
47
+ type: Literal["agent"] = "agent"
48
+ """The type of the response. Always `agent`."""
49
+
50
+ steps: List[LanguageModelResponse[str]]
51
+ """
52
+ A list of steps taken by the agent **BEFORE** its final output.
53
+
54
+ NOTE: If the final output was also the first step, this will be
55
+ empty.
56
+ """
57
+
58
+ context: Any = None
59
+ """
60
+ The final context object after agent execution.
61
+
62
+ This is always the final version of the context after any updates
63
+ have been applied during the agent's execution.
64
+ """
65
+
66
+ @cached
67
+ def __str__(self) -> str:
68
+ """Pretty prints the response object."""
69
+ output = "AgentResponse:"
70
+
71
+ if self.output or self.content:
72
+ output += f"\n{self.output if self.output else self.content}"
73
+ else:
74
+ output += f"\n{self.completion}"
75
+
76
+ output += f"\n\n>>> Model: {self.model}"
77
+ # NOTE:
78
+ # added +1 to include final step in the output
79
+ output += f"\n>>> Steps: {len(self.steps) + 1}"
80
+ output += f"\n>>> Tool Calls: {len(self.tool_calls) if self.tool_calls else 0}"
81
+
82
+ # Calculate total tool calls across all steps
83
+ total_tool_calls = 0
84
+ for step in self.steps:
85
+ if step.has_tool_calls():
86
+ total_tool_calls += len(step.tool_calls)
87
+
88
+ output += f"\n>>> Total Tool Calls: {total_tool_calls}"
89
+
90
+ return output