hammad-python 0.0.19__py3-none-any.whl → 0.0.21__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 +1298 -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 +122 -0
  34. hammad/genai/agents/types/agent_stream.py +318 -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} +496 -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 +60 -67
  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} +157 -140
  66. hammad/logging/logger.py +9 -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.21.dist-info}/METADATA +10 -2
  74. hammad_python-0.0.21.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.21.dist-info}/WHEEL +0 -0
  83. {hammad_python-0.0.19.dist-info → hammad_python-0.0.21.dist-info}/licenses/LICENSE +0 -0
@@ -1,25 +1,38 @@
1
1
  """hammad.genai.language_models.language_model"""
2
2
 
3
3
  from typing import (
4
- Any,
4
+ Any,
5
5
  Callable,
6
- List,
7
- TypeVar,
8
- Generic,
9
- Union,
10
- Optional,
11
- Type,
12
- overload,
13
- Dict,
6
+ List,
7
+ TypeVar,
8
+ Generic,
9
+ Union,
10
+ Optional,
11
+ Type,
12
+ overload,
13
+ Dict,
14
14
  TYPE_CHECKING,
15
15
  )
16
+ import functools
17
+ import inspect
18
+ import asyncio
16
19
  from typing_extensions import Literal
17
20
 
18
21
  if TYPE_CHECKING:
19
22
  from httpx import Timeout
20
23
 
21
- from ._types import LanguageModelName, LanguageModelInstructorMode
22
- from ._utils import (
24
+ from ....logging.logger import _get_internal_logger
25
+ from ..model_provider import litellm, instructor
26
+
27
+ from ...types.base import BaseGenAIModel
28
+ from .types.language_model_instructor_mode import LanguageModelInstructorMode
29
+ from .types.language_model_name import LanguageModelName
30
+ from .types.language_model_messages import LanguageModelMessages
31
+ from .types.language_model_response import LanguageModelResponse
32
+ from .types.language_model_settings import LanguageModelSettings
33
+ from .types.language_model_stream import LanguageModelStream
34
+
35
+ from .utils import (
23
36
  parse_messages_input,
24
37
  handle_completion_request_params,
25
38
  handle_completion_response,
@@ -29,9 +42,6 @@ from ._utils import (
29
42
  format_tool_calls,
30
43
  LanguageModelRequestBuilder,
31
44
  )
32
- from .language_model_request import LanguageModelRequest, LanguageModelMessagesParam
33
- from .language_model_response import LanguageModelResponse
34
- from ._streaming import Stream, AsyncStream
35
45
 
36
46
  __all__ = [
37
47
  "LanguageModel",
@@ -41,9 +51,12 @@ __all__ = [
41
51
  T = TypeVar("T")
42
52
 
43
53
 
54
+ logger = _get_internal_logger(__name__)
55
+
56
+
44
57
  class LanguageModelError(Exception):
45
58
  """Error raised when an error occurs during a language model operation."""
46
-
59
+
47
60
  def __init__(self, message: str, *args: Any, **kwargs: Any):
48
61
  super().__init__(message, *args, **kwargs)
49
62
  self.message = message
@@ -51,117 +64,111 @@ class LanguageModelError(Exception):
51
64
  self.kwargs = kwargs
52
65
 
53
66
 
54
- class _AIProvider:
55
- """Provider for accessing litellm and instructor instances."""
56
-
57
- _LITELLM = None
58
- _INSTRUCTOR = None
59
-
60
- @staticmethod
61
- def get_litellm():
62
- """Returns the `litellm` module."""
63
- if _AIProvider._LITELLM is None:
64
- try:
65
- import litellm
66
- litellm.drop_params = True
67
- litellm.modify_params = True
68
- _AIProvider._LITELLM = litellm
69
-
70
- # Rebuild LanguageModelResponse model now that litellm is available
71
- LanguageModelResponse.model_rebuild()
72
- except ImportError as e:
73
- raise ImportError(
74
- "Using the `hammad.ai.llms` extension requires the `litellm` package to be installed.\n"
75
- "Please either install the `litellm` package, or install the `hammad.ai` extension with:\n"
76
- "`pip install 'hammad-python[ai]'`"
77
- ) from e
78
- return _AIProvider._LITELLM
79
-
80
- @staticmethod
81
- def get_instructor():
82
- """Returns the `instructor` module."""
83
- if _AIProvider._INSTRUCTOR is None:
84
- try:
85
- import instructor
86
- _AIProvider._INSTRUCTOR = instructor
87
- except ImportError as e:
88
- raise ImportError(
89
- "Using the `hammad.ai.llms` extension requires the `instructor` package to be installed.\n"
90
- "Please either install the `instructor` package, or install the `hammad.ai` extension with:\n"
91
- "`pip install 'hammad-python[ai]'`"
92
- ) from e
93
- return _AIProvider._INSTRUCTOR
94
-
95
-
96
- class LanguageModel(Generic[T]):
97
- """A clean language model interface for generating responses with comprehensive
98
- parameter handling and type safety."""
99
-
67
+ class LanguageModel(BaseGenAIModel, Generic[T]):
68
+ """
69
+ A Generative AI model that can be used to generate text, chat completions,
70
+ structured outputs, call tools and more.
71
+ """
72
+
73
+ model: LanguageModelName | str = "openai/gpt-4o-mini"
74
+ """The model to use for requests."""
75
+
76
+ type: Type[T] = str
77
+ """The type of the output of the language model."""
78
+
79
+ instructions: Optional[str] = None
80
+ """Instructions for the language model."""
81
+
82
+ base_url: Optional[str] = None
83
+ api_key: Optional[str] = None
84
+ api_version: Optional[str] = None
85
+ organization: Optional[str] = None
86
+ deployment_id: Optional[str] = None
87
+ model_list: Optional[List[Any]] = None
88
+ extra_headers: Optional[Dict[str, str]] = None
89
+
90
+ settings: LanguageModelSettings = LanguageModelSettings()
91
+ """Settings for the language model."""
92
+
93
+ instructor_mode: LanguageModelInstructorMode = "tool_call"
94
+ """Default instructor mode for structured outputs."""
95
+
100
96
  def __init__(
101
97
  self,
102
98
  model: LanguageModelName = "openai/gpt-4o-mini",
103
99
  base_url: Optional[str] = None,
104
100
  api_key: Optional[str] = None,
105
101
  instructor_mode: LanguageModelInstructorMode = "tool_call",
102
+ **kwargs: Any,
106
103
  ):
107
104
  """Initialize the language model.
108
-
105
+
109
106
  Args:
110
107
  model: The model to use for requests
108
+ base_url: Custom base URL for the API
109
+ api_key: API key for authentication
111
110
  instructor_mode: Default instructor mode for structured outputs
111
+ **kwargs: Additional arguments passed to BaseGenAIModel
112
112
  """
113
- self.model = model
114
- self.base_url = base_url
115
- self.api_key = api_key
116
- self.instructor_mode = instructor_mode
113
+ # Initialize BaseGenAIModel via super()
114
+ super().__init__(model=model, base_url=base_url, api_key=api_key, **kwargs)
115
+
116
+ # Initialize LanguageModel-specific attributes
117
117
  self._instructor_client = None
118
-
119
- def _get_instructor_client(self, mode: Optional[LanguageModelInstructorMode] = None):
118
+
119
+ logger.info(f"Initialized LanguageModel w/ model: {self.model}")
120
+ logger.debug(f"LanguageModel settings: {self.settings}")
121
+
122
+ def _get_instructor_client(
123
+ self, mode: Optional[LanguageModelInstructorMode] = None
124
+ ):
120
125
  """Get or create an instructor client with the specified mode."""
121
126
  effective_mode = mode or self.instructor_mode
122
-
127
+
123
128
  # Create a new client if mode changed or client doesn't exist
124
- if (self._instructor_client is None or
125
- getattr(self._instructor_client, '_mode', None) != effective_mode):
126
-
127
- instructor = _AIProvider.get_instructor()
129
+ if (
130
+ self._instructor_client is None
131
+ or getattr(self._instructor_client, "_mode", None) != effective_mode
132
+ ):
133
+ logger.debug(f"Creating new instructor client for mode: {effective_mode} from old mode: {getattr(self._instructor_client, '_mode', None)}")
134
+
128
135
  self._instructor_client = instructor.from_litellm(
129
- completion=_AIProvider.get_litellm().completion,
130
- mode=instructor.Mode(effective_mode)
136
+ completion=litellm.completion, mode=instructor.Mode(effective_mode)
131
137
  )
132
138
  self._instructor_client._mode = effective_mode
133
-
139
+
134
140
  return self._instructor_client
135
-
136
- def _get_async_instructor_client(self, mode: Optional[LanguageModelInstructorMode] = None):
141
+
142
+ def _get_async_instructor_client(
143
+ self, mode: Optional[LanguageModelInstructorMode] = None
144
+ ):
137
145
  """Get or create an async instructor client with the specified mode."""
138
146
  effective_mode = mode or self.instructor_mode
139
-
140
- instructor = _AIProvider.get_instructor()
147
+
141
148
  return instructor.from_litellm(
142
- completion=_AIProvider.get_litellm().acompletion,
143
- mode=instructor.Mode(effective_mode)
149
+ completion=litellm.acompletion, mode=instructor.Mode(effective_mode)
144
150
  )
145
-
151
+
146
152
  # Overloaded run methods for different return types
147
-
153
+
148
154
  @overload
149
155
  def run(
150
156
  self,
151
- messages: LanguageModelMessagesParam,
157
+ messages: LanguageModelMessages,
152
158
  instructions: Optional[str] = None,
153
159
  *,
154
160
  stream: Literal[False] = False,
155
161
  model: Optional[LanguageModelName | str] = None,
156
162
  base_url: Optional[str] = None,
157
163
  api_key: Optional[str] = None,
164
+ mock_response: Optional[str] = None,
158
165
  **kwargs: Any,
159
166
  ) -> LanguageModelResponse[str]: ...
160
-
167
+
161
168
  @overload
162
169
  def run(
163
170
  self,
164
- messages: LanguageModelMessagesParam,
171
+ messages: LanguageModelMessages,
165
172
  instructions: Optional[str] = None,
166
173
  *,
167
174
  stream: Literal[False] = False,
@@ -178,26 +185,28 @@ class LanguageModel(Generic[T]):
178
185
  frequency_penalty: Optional[float] = None,
179
186
  seed: Optional[int] = None,
180
187
  user: Optional[str] = None,
188
+ mock_response: Optional[str] = None,
181
189
  **kwargs: Any,
182
190
  ) -> LanguageModelResponse[str]: ...
183
-
191
+
184
192
  @overload
185
193
  def run(
186
194
  self,
187
- messages: LanguageModelMessagesParam,
195
+ messages: LanguageModelMessages,
188
196
  instructions: Optional[str] = None,
189
197
  *,
190
198
  stream: Literal[True],
191
199
  model: Optional[LanguageModelName | str] = None,
192
200
  base_url: Optional[str] = None,
193
201
  api_key: Optional[str] = None,
202
+ mock_response: Optional[str] = None,
194
203
  **kwargs: Any,
195
- ) -> Stream[str]: ...
196
-
204
+ ) -> LanguageModelStream[str]: ...
205
+
197
206
  @overload
198
207
  def run(
199
208
  self,
200
- messages: LanguageModelMessagesParam,
209
+ messages: LanguageModelMessages,
201
210
  instructions: Optional[str] = None,
202
211
  *,
203
212
  stream: Literal[True],
@@ -214,13 +223,14 @@ class LanguageModel(Generic[T]):
214
223
  frequency_penalty: Optional[float] = None,
215
224
  seed: Optional[int] = None,
216
225
  user: Optional[str] = None,
226
+ mock_response: Optional[str] = None,
217
227
  **kwargs: Any,
218
- ) -> Stream[str]: ...
219
-
228
+ ) -> LanguageModelStream[str]: ...
229
+
220
230
  @overload
221
231
  def run(
222
232
  self,
223
- messages: LanguageModelMessagesParam,
233
+ messages: LanguageModelMessages,
224
234
  instructions: Optional[str] = None,
225
235
  *,
226
236
  type: Type[T],
@@ -228,13 +238,14 @@ class LanguageModel(Generic[T]):
228
238
  model: Optional[LanguageModelName | str] = None,
229
239
  base_url: Optional[str] = None,
230
240
  api_key: Optional[str] = None,
241
+ mock_response: Optional[str] = None,
231
242
  **kwargs: Any,
232
243
  ) -> LanguageModelResponse[T]: ...
233
-
244
+
234
245
  @overload
235
246
  def run(
236
247
  self,
237
- messages: LanguageModelMessagesParam,
248
+ messages: LanguageModelMessages,
238
249
  instructions: Optional[str] = None,
239
250
  *,
240
251
  type: Type[T],
@@ -263,13 +274,14 @@ class LanguageModel(Generic[T]):
263
274
  frequency_penalty: Optional[float] = None,
264
275
  seed: Optional[int] = None,
265
276
  user: Optional[str] = None,
277
+ mock_response: Optional[str] = None,
266
278
  **kwargs: Any,
267
279
  ) -> LanguageModelResponse[T]: ...
268
-
280
+
269
281
  @overload
270
282
  def run(
271
283
  self,
272
- messages: LanguageModelMessagesParam,
284
+ messages: LanguageModelMessages,
273
285
  instructions: Optional[str] = None,
274
286
  *,
275
287
  type: Type[T],
@@ -277,13 +289,14 @@ class LanguageModel(Generic[T]):
277
289
  model: Optional[LanguageModelName | str] = None,
278
290
  base_url: Optional[str] = None,
279
291
  api_key: Optional[str] = None,
292
+ mock_response: Optional[str] = None,
280
293
  **kwargs: Any,
281
- ) -> Stream[T]: ...
282
-
294
+ ) -> LanguageModelStream[T]: ...
295
+
283
296
  @overload
284
297
  def run(
285
298
  self,
286
- messages: LanguageModelMessagesParam,
299
+ messages: LanguageModelMessages,
287
300
  instructions: Optional[str] = None,
288
301
  *,
289
302
  type: Type[T],
@@ -312,77 +325,87 @@ class LanguageModel(Generic[T]):
312
325
  frequency_penalty: Optional[float] = None,
313
326
  seed: Optional[int] = None,
314
327
  user: Optional[str] = None,
328
+ mock_response: Optional[str] = None,
315
329
  **kwargs: Any,
316
- ) -> Stream[T]: ...
317
-
330
+ ) -> LanguageModelStream[T]: ...
331
+
318
332
  def run(
319
333
  self,
320
- messages: LanguageModelMessagesParam,
334
+ messages: LanguageModelMessages,
321
335
  instructions: Optional[str] = None,
336
+ mock_response: Optional[str] = None,
322
337
  **kwargs: Any,
323
- ) -> Union[LanguageModelResponse[Any], Stream[Any]]:
338
+ ) -> Union[LanguageModelResponse[Any], LanguageModelStream[Any]]:
324
339
  """Run a language model request.
325
-
340
+
326
341
  Args:
327
342
  messages: The input messages/content for the request
328
343
  instructions: Optional system instructions to prepend
344
+ mock_response: Mock response string for testing (saves API costs)
329
345
  **kwargs: Additional request parameters
330
-
346
+
331
347
  Returns:
332
348
  LanguageModelResponse or LanguageModelStream depending on parameters
333
349
  """
350
+ logger.info(f"Running LanguageModel request with model: {self.model}")
351
+ logger.debug(f"LanguageModel request kwargs: {kwargs}")
352
+
334
353
  try:
335
- # Extract model, base_url, and api_key from kwargs, using instance defaults
354
+ # Extract model, base_url, api_key, and mock_response from kwargs, using instance defaults
336
355
  model = kwargs.pop("model", None) or self.model
337
356
  base_url = kwargs.pop("base_url", None) or self.base_url
338
357
  api_key = kwargs.pop("api_key", None) or self.api_key
339
-
340
- # Add base_url and api_key to kwargs if they are set
358
+ mock_response_param = kwargs.pop("mock_response", None) or mock_response
359
+
360
+ # Add base_url, api_key, and mock_response to kwargs if they are set
341
361
  if base_url is not None:
342
362
  kwargs["base_url"] = base_url
343
363
  if api_key is not None:
344
364
  kwargs["api_key"] = api_key
345
-
365
+ if mock_response_param is not None:
366
+ kwargs["mock_response"] = mock_response_param
367
+
346
368
  # Create the request
347
369
  request = LanguageModelRequestBuilder(
348
- messages=messages,
349
- instructions=instructions,
350
- model=model,
351
- **kwargs
370
+ messages=messages, instructions=instructions, model=model, **kwargs
352
371
  )
353
-
372
+
354
373
  # Parse messages
355
- parsed_messages = parse_messages_input(request.messages, request.instructions)
356
- parsed_messages = format_tool_calls(parsed_messages)
357
-
374
+ parsed_messages = parse_messages_input(
375
+ request.messages, request.instructions
376
+ )
377
+ if request.is_structured_output():
378
+ parsed_messages = format_tool_calls(parsed_messages)
379
+
358
380
  # Handle different request types
359
381
  if request.is_structured_output():
360
382
  return self._handle_structured_output_request(request, parsed_messages)
361
383
  else:
362
384
  return self._handle_completion_request(request, parsed_messages)
363
-
385
+
364
386
  except Exception as e:
365
387
  raise LanguageModelError(f"Error in language model request: {e}") from e
366
-
388
+
367
389
  # Overloaded async_run methods for different return types
368
-
390
+
369
391
  @overload
370
392
  async def async_run(
371
393
  self,
372
- messages: LanguageModelMessagesParam,
394
+ messages: LanguageModelMessages,
373
395
  instructions: Optional[str] = None,
374
396
  *,
375
397
  stream: Literal[False] = False,
376
398
  model: Optional[LanguageModelName | str] = None,
377
399
  base_url: Optional[str] = None,
378
400
  api_key: Optional[str] = None,
401
+ mock_response: Optional[str] = None,
379
402
  **kwargs: Any,
380
403
  ) -> LanguageModelResponse[str]: ...
381
-
404
+
382
405
  @overload
383
406
  async def async_run(
384
407
  self,
385
- messages: LanguageModelMessagesParam,
408
+ messages: LanguageModelMessages,
386
409
  instructions: Optional[str] = None,
387
410
  *,
388
411
  stream: Literal[False] = False,
@@ -399,26 +422,28 @@ class LanguageModel(Generic[T]):
399
422
  frequency_penalty: Optional[float] = None,
400
423
  seed: Optional[int] = None,
401
424
  user: Optional[str] = None,
425
+ mock_response: Optional[str] = None,
402
426
  **kwargs: Any,
403
427
  ) -> LanguageModelResponse[str]: ...
404
-
428
+
405
429
  @overload
406
430
  async def async_run(
407
431
  self,
408
- messages: LanguageModelMessagesParam,
432
+ messages: LanguageModelMessages,
409
433
  instructions: Optional[str] = None,
410
434
  *,
411
435
  stream: Literal[True],
412
436
  model: Optional[LanguageModelName | str] = None,
413
437
  base_url: Optional[str] = None,
414
438
  api_key: Optional[str] = None,
439
+ mock_response: Optional[str] = None,
415
440
  **kwargs: Any,
416
- ) -> AsyncStream[str]: ...
417
-
441
+ ) -> LanguageModelStream[str]: ...
442
+
418
443
  @overload
419
444
  async def async_run(
420
445
  self,
421
- messages: LanguageModelMessagesParam,
446
+ messages: LanguageModelMessages,
422
447
  instructions: Optional[str] = None,
423
448
  *,
424
449
  stream: Literal[True],
@@ -435,13 +460,14 @@ class LanguageModel(Generic[T]):
435
460
  frequency_penalty: Optional[float] = None,
436
461
  seed: Optional[int] = None,
437
462
  user: Optional[str] = None,
463
+ mock_response: Optional[str] = None,
438
464
  **kwargs: Any,
439
- ) -> AsyncStream[str]: ...
440
-
465
+ ) -> LanguageModelStream[str]: ...
466
+
441
467
  @overload
442
468
  async def async_run(
443
469
  self,
444
- messages: LanguageModelMessagesParam,
470
+ messages: LanguageModelMessages,
445
471
  instructions: Optional[str] = None,
446
472
  *,
447
473
  type: Type[T],
@@ -449,13 +475,14 @@ class LanguageModel(Generic[T]):
449
475
  model: Optional[LanguageModelName | str] = None,
450
476
  base_url: Optional[str] = None,
451
477
  api_key: Optional[str] = None,
478
+ mock_response: Optional[str] = None,
452
479
  **kwargs: Any,
453
480
  ) -> LanguageModelResponse[T]: ...
454
-
481
+
455
482
  @overload
456
483
  async def async_run(
457
484
  self,
458
- messages: LanguageModelMessagesParam,
485
+ messages: LanguageModelMessages,
459
486
  instructions: Optional[str] = None,
460
487
  *,
461
488
  type: Type[T],
@@ -484,13 +511,14 @@ class LanguageModel(Generic[T]):
484
511
  frequency_penalty: Optional[float] = None,
485
512
  seed: Optional[int] = None,
486
513
  user: Optional[str] = None,
514
+ mock_response: Optional[str] = None,
487
515
  **kwargs: Any,
488
516
  ) -> LanguageModelResponse[T]: ...
489
-
517
+
490
518
  @overload
491
519
  async def async_run(
492
520
  self,
493
- messages: LanguageModelMessagesParam,
521
+ messages: LanguageModelMessages,
494
522
  instructions: Optional[str] = None,
495
523
  *,
496
524
  type: Type[T],
@@ -498,13 +526,14 @@ class LanguageModel(Generic[T]):
498
526
  model: Optional[LanguageModelName | str] = None,
499
527
  base_url: Optional[str] = None,
500
528
  api_key: Optional[str] = None,
529
+ mock_response: Optional[str] = None,
501
530
  **kwargs: Any,
502
- ) -> AsyncStream[T]: ...
503
-
531
+ ) -> LanguageModelStream[T]: ...
532
+
504
533
  @overload
505
534
  async def async_run(
506
535
  self,
507
- messages: LanguageModelMessagesParam,
536
+ messages: LanguageModelMessages,
508
537
  instructions: Optional[str] = None,
509
538
  *,
510
539
  type: Type[T],
@@ -533,114 +562,129 @@ class LanguageModel(Generic[T]):
533
562
  frequency_penalty: Optional[float] = None,
534
563
  seed: Optional[int] = None,
535
564
  user: Optional[str] = None,
565
+ mock_response: Optional[str] = None,
536
566
  **kwargs: Any,
537
- ) -> AsyncStream[T]: ...
538
-
567
+ ) -> LanguageModelStream[T]: ...
568
+
539
569
  async def async_run(
540
570
  self,
541
- messages: LanguageModelMessagesParam,
571
+ messages: LanguageModelMessages,
542
572
  instructions: Optional[str] = None,
573
+ mock_response: Optional[str] = None,
543
574
  **kwargs: Any,
544
- ) -> Union[LanguageModelResponse[Any], AsyncStream[Any]]:
575
+ ) -> Union[LanguageModelResponse[Any], LanguageModelStream[Any]]:
545
576
  """Run an async language model request.
546
-
577
+
547
578
  Args:
548
579
  messages: The input messages/content for the request
549
580
  instructions: Optional system instructions to prepend
581
+ mock_response: Mock response string for testing (saves API costs)
550
582
  **kwargs: Additional request parameters
551
-
583
+
552
584
  Returns:
553
585
  LanguageModelResponse or LanguageModelAsyncStream depending on parameters
554
586
  """
587
+ logger.info(f"Running async LanguageModel request with model: {self.model}")
588
+ logger.debug(f"LanguageModel request kwargs: {kwargs}")
589
+
555
590
  try:
556
- # Extract model, base_url, and api_key from kwargs, using instance defaults
591
+ # Extract model, base_url, api_key, and mock_response from kwargs, using instance defaults
557
592
  model = kwargs.pop("model", None) or self.model
558
593
  base_url = kwargs.pop("base_url", None) or self.base_url
559
594
  api_key = kwargs.pop("api_key", None) or self.api_key
560
-
561
- # Add base_url and api_key to kwargs if they are set
595
+ mock_response_param = kwargs.pop("mock_response", None) or mock_response
596
+
597
+ # Add base_url, api_key, and mock_response to kwargs if they are set
562
598
  if base_url is not None:
563
599
  kwargs["base_url"] = base_url
564
600
  if api_key is not None:
565
601
  kwargs["api_key"] = api_key
566
-
602
+ if mock_response_param is not None:
603
+ kwargs["mock_response"] = mock_response_param
604
+
567
605
  # Create the request
568
606
  request = LanguageModelRequestBuilder(
569
- messages=messages,
570
- instructions=instructions,
571
- model=model,
572
- **kwargs
607
+ messages=messages, instructions=instructions, model=model, **kwargs
573
608
  )
574
-
609
+
575
610
  # Parse messages
576
- parsed_messages = parse_messages_input(request.messages, request.instructions)
577
- parsed_messages = format_tool_calls(parsed_messages)
578
-
611
+ parsed_messages = parse_messages_input(
612
+ request.messages, request.instructions
613
+ )
614
+ if request.is_structured_output():
615
+ parsed_messages = format_tool_calls(parsed_messages)
616
+
579
617
  # Handle different request types
580
618
  if request.is_structured_output():
581
- return await self._handle_async_structured_output_request(request, parsed_messages)
619
+ return await self._handle_async_structured_output_request(
620
+ request, parsed_messages
621
+ )
582
622
  else:
583
- return await self._handle_async_completion_request(request, parsed_messages)
584
-
623
+ return await self._handle_async_completion_request(
624
+ request, parsed_messages
625
+ )
626
+
585
627
  except Exception as e:
586
- raise LanguageModelError(f"Error in async language model request: {e}") from e
587
-
628
+ raise LanguageModelError(
629
+ f"Error in async language model request: {e}"
630
+ ) from e
631
+
588
632
  def _handle_completion_request(
589
- self,
590
- request: LanguageModelRequestBuilder,
591
- parsed_messages: List[Any]
592
- ) -> Union[LanguageModelResponse[str], Stream[str]]:
633
+ self, request: LanguageModelRequestBuilder, parsed_messages: List[Any]
634
+ ) -> Union[LanguageModelResponse[str], LanguageModelStream[str]]:
593
635
  """Handle a standard completion request."""
594
636
  # Get filtered parameters
595
637
  params = handle_completion_request_params(request.get_completion_settings())
596
638
  params["messages"] = parsed_messages
597
-
598
- litellm = _AIProvider.get_litellm()
599
-
639
+
600
640
  if request.is_streaming():
601
641
  # Handle streaming - stream parameter is already in params
602
642
  if "stream_options" not in params and "stream_options" in request.settings:
603
643
  params["stream_options"] = request.settings["stream_options"]
604
644
  stream = litellm.completion(**params)
605
- return Stream(stream, output_type=str, model=request.model)
645
+ return LanguageModelStream(
646
+ model=request.model,
647
+ stream=stream,
648
+ output_type=str,
649
+ )
606
650
  else:
607
651
  # Handle non-streaming
608
652
  response = litellm.completion(**params)
609
653
  return handle_completion_response(response, request.model)
610
-
654
+
611
655
  async def _handle_async_completion_request(
612
- self,
613
- request: LanguageModelRequestBuilder,
614
- parsed_messages: List[Any]
615
- ) -> Union[LanguageModelResponse[str], AsyncStream[str]]:
656
+ self, request: LanguageModelRequestBuilder, parsed_messages: List[Any]
657
+ ) -> Union[LanguageModelResponse[str], LanguageModelStream[str]]:
616
658
  """Handle an async standard completion request."""
617
659
  # Get filtered parameters
618
660
  params = handle_completion_request_params(request.get_completion_settings())
619
661
  params["messages"] = parsed_messages
620
-
621
- litellm = _AIProvider.get_litellm()
622
-
662
+
623
663
  if request.is_streaming():
624
664
  # Handle streaming - stream parameter is already in params
625
665
  if "stream_options" not in params and "stream_options" in request.settings:
626
666
  params["stream_options"] = request.settings["stream_options"]
627
667
  stream = await litellm.acompletion(**params)
628
- return AsyncStream(stream, output_type=str, model=request.model)
668
+ return LanguageModelStream(
669
+ model=request.model,
670
+ stream=stream,
671
+ output_type=str,
672
+ )
629
673
  else:
630
674
  # Handle non-streaming
631
675
  response = await litellm.acompletion(**params)
632
676
  return handle_completion_response(response, request.model)
633
-
677
+
634
678
  def _handle_structured_output_request(
635
- self,
636
- request: LanguageModelRequestBuilder,
637
- parsed_messages: List[Any]
638
- ) -> Union[LanguageModelResponse[Any], Stream[Any]]:
679
+ self, request: LanguageModelRequestBuilder, parsed_messages: List[Any]
680
+ ) -> Union[LanguageModelResponse[Any], LanguageModelStream[Any]]:
639
681
  """Handle a structured output request."""
640
682
  # Get filtered parameters
641
- params = handle_structured_output_request_params(request.get_structured_output_settings())
683
+ params = handle_structured_output_request_params(
684
+ request.get_structured_output_settings()
685
+ )
642
686
  params["messages"] = parsed_messages
643
-
687
+
644
688
  # Prepare response model
645
689
  response_model = prepare_response_model(
646
690
  request.get_output_type(),
@@ -648,10 +692,10 @@ class LanguageModel(Generic[T]):
648
692
  request.get_response_field_instruction(),
649
693
  request.get_response_model_name(),
650
694
  )
651
-
695
+
652
696
  # Get instructor client
653
697
  client = self._get_instructor_client(request.get_instructor_mode())
654
-
698
+
655
699
  if request.is_streaming():
656
700
  if isinstance(request.get_output_type(), list):
657
701
  # Handle streaming - stream parameter is already in params
@@ -669,7 +713,12 @@ class LanguageModel(Generic[T]):
669
713
  strict=request.get_strict_mode(),
670
714
  **params,
671
715
  )
672
- return Stream(stream, output_type=request.get_output_type(), model=request.model, response_field_name=request.get_response_field_name())
716
+ return LanguageModelStream(
717
+ model=request.model,
718
+ stream=stream,
719
+ output_type=request.get_output_type(),
720
+ response_field_name=request.get_response_field_name(),
721
+ )
673
722
  else:
674
723
  # Handle non-streaming
675
724
  response, completion = client.chat.completions.create_with_completion(
@@ -679,19 +728,23 @@ class LanguageModel(Generic[T]):
679
728
  **params,
680
729
  )
681
730
  return handle_structured_output_response(
682
- response, completion, request.model, request.get_output_type(), request.get_response_field_name()
731
+ response,
732
+ completion,
733
+ request.model,
734
+ request.get_output_type(),
735
+ request.get_response_field_name(),
683
736
  )
684
-
737
+
685
738
  async def _handle_async_structured_output_request(
686
- self,
687
- request: LanguageModelRequestBuilder,
688
- parsed_messages: List[Any]
689
- ) -> Union[LanguageModelResponse[Any], AsyncStream[Any]]:
739
+ self, request: LanguageModelRequestBuilder, parsed_messages: List[Any]
740
+ ) -> Union[LanguageModelResponse[Any], LanguageModelStream[Any]]:
690
741
  """Handle an async structured output request."""
691
742
  # Get filtered parameters
692
- params = handle_structured_output_request_params(request.get_structured_output_settings())
743
+ params = handle_structured_output_request_params(
744
+ request.get_structured_output_settings()
745
+ )
693
746
  params["messages"] = parsed_messages
694
-
747
+
695
748
  # Prepare response model
696
749
  response_model = prepare_response_model(
697
750
  request.get_output_type(),
@@ -699,10 +752,10 @@ class LanguageModel(Generic[T]):
699
752
  request.get_response_field_instruction(),
700
753
  request.get_response_model_name(),
701
754
  )
702
-
755
+
703
756
  # Get async instructor client
704
757
  client = self._get_async_instructor_client(request.get_instructor_mode())
705
-
758
+
706
759
  if request.is_streaming():
707
760
  if isinstance(request.get_output_type(), list):
708
761
  # Handle streaming - stream parameter is already in params
@@ -720,7 +773,12 @@ class LanguageModel(Generic[T]):
720
773
  strict=request.get_strict_mode(),
721
774
  **params,
722
775
  )
723
- return AsyncStream(stream, output_type=request.get_output_type(), model=request.model, response_field_name=request.get_response_field_name())
776
+ return LanguageModelStream(
777
+ model=request.model,
778
+ stream=stream,
779
+ output_type=request.get_output_type(),
780
+ response_field_name=request.get_response_field_name(),
781
+ )
724
782
  else:
725
783
  # Handle non-streaming
726
784
  response, completion = await client.chat.completions.create_with_completion(
@@ -730,5 +788,239 @@ class LanguageModel(Generic[T]):
730
788
  **params,
731
789
  )
732
790
  return handle_structured_output_response(
733
- response, completion, request.model, request.get_output_type(), request.get_response_field_name()
734
- )
791
+ response,
792
+ completion,
793
+ request.model,
794
+ request.get_output_type(),
795
+ request.get_response_field_name(),
796
+ )
797
+
798
+ def as_tool(
799
+ self,
800
+ func: Optional[Callable] = None,
801
+ *,
802
+ name: Optional[str] = None,
803
+ description: Optional[str] = None,
804
+ instructions: Optional[str] = None,
805
+ **kwargs: Any,
806
+ ) -> Union[Callable, Any]:
807
+ """Convert this language model to a tool that can be used by agents.
808
+
809
+ Can be used as a decorator or as a function:
810
+
811
+ As a decorator:
812
+ @model.as_tool()
813
+ def my_function(param1: str, param2: int) -> MyType:
814
+ '''Function description'''
815
+ pass
816
+
817
+ As a function:
818
+ tool = model.as_tool(
819
+ name="my_tool",
820
+ description="Tool description",
821
+ instructions="Custom instructions for the LLM"
822
+ )
823
+
824
+ Args:
825
+ func: The function to wrap (when used as decorator)
826
+ name: The name of the tool
827
+ description: Description of what the tool does
828
+ instructions: Custom instructions for the LLM generation
829
+ **kwargs: Additional arguments for tool creation
830
+
831
+ Returns:
832
+ BaseTool or decorated function
833
+ """
834
+ from ...types.base import BaseTool
835
+ from ....formatting.text.converters import convert_docstring_to_text
836
+
837
+ def create_tool_wrapper(target_func: Optional[Callable] = None) -> Any:
838
+ """Create a tool wrapper for the language model."""
839
+
840
+ if target_func is not None:
841
+ # Decorator usage - use function signature and docstring
842
+ sig = inspect.signature(target_func)
843
+ func_name = name or target_func.__name__
844
+
845
+ # Get return type from function signature
846
+ return_type = (
847
+ sig.return_annotation
848
+ if sig.return_annotation != inspect.Signature.empty
849
+ else str
850
+ )
851
+
852
+ # Extract docstring as system instructions
853
+ system_instructions = instructions or convert_docstring_to_text(
854
+ target_func
855
+ )
856
+
857
+ # Create parameter schema from function signature
858
+ parameters_schema = {"type": "object", "properties": {}, "required": []}
859
+
860
+ for param_name, param in sig.parameters.items():
861
+ param_type = (
862
+ param.annotation
863
+ if param.annotation != inspect.Parameter.empty
864
+ else str
865
+ )
866
+
867
+ # Convert type to JSON schema type
868
+ if param_type == str:
869
+ json_type = "string"
870
+ elif param_type == int:
871
+ json_type = "integer"
872
+ elif param_type == float:
873
+ json_type = "number"
874
+ elif param_type == bool:
875
+ json_type = "boolean"
876
+ elif param_type == list:
877
+ json_type = "array"
878
+ elif param_type == dict:
879
+ json_type = "object"
880
+ else:
881
+ json_type = "string" # Default fallback
882
+
883
+ parameters_schema["properties"][param_name] = {
884
+ "type": json_type,
885
+ "description": f"Parameter {param_name} of type {param_type.__name__ if hasattr(param_type, '__name__') else str(param_type)}",
886
+ }
887
+
888
+ if param.default == inspect.Parameter.empty:
889
+ parameters_schema["required"].append(param_name)
890
+
891
+ # Create partial function with model settings
892
+ partial_func = functools.partial(
893
+ self._execute_tool_function,
894
+ target_func=target_func,
895
+ return_type=return_type,
896
+ system_instructions=system_instructions,
897
+ )
898
+
899
+ # Handle async functions
900
+ if asyncio.iscoroutinefunction(target_func):
901
+
902
+ async def async_tool_function(**tool_kwargs: Any) -> Any:
903
+ return await partial_func(**tool_kwargs)
904
+
905
+ return BaseTool(
906
+ name=func_name,
907
+ description=description
908
+ or system_instructions
909
+ or f"Tool for {func_name}",
910
+ function=async_tool_function,
911
+ parameters_json_schema=parameters_schema,
912
+ **kwargs,
913
+ )
914
+ else:
915
+
916
+ def sync_tool_function(**tool_kwargs: Any) -> Any:
917
+ return partial_func(**tool_kwargs)
918
+
919
+ return BaseTool(
920
+ name=func_name,
921
+ description=description
922
+ or system_instructions
923
+ or f"Tool for {func_name}",
924
+ function=sync_tool_function,
925
+ parameters_json_schema=parameters_schema,
926
+ **kwargs,
927
+ )
928
+ else:
929
+ # Function usage - create generic tool
930
+ tool_name = name or f"language_model_{self.model.replace('/', '_')}"
931
+ tool_description = (
932
+ description or f"Language model tool using {self.model}"
933
+ )
934
+
935
+ # Create partial function with model settings
936
+ partial_func = functools.partial(
937
+ self._execute_generic_tool, system_instructions=instructions
938
+ )
939
+
940
+ def generic_tool_function(
941
+ input: str, type: Optional[Type[T]] = None, **tool_kwargs: Any
942
+ ) -> Any:
943
+ """Generic tool function that runs the language model."""
944
+ return partial_func(input=input, output_type=type, **tool_kwargs)
945
+
946
+ # Generic parameter schema
947
+ parameters_schema = {
948
+ "type": "object",
949
+ "properties": {
950
+ "input": {
951
+ "type": "string",
952
+ "description": "The input text for the language model",
953
+ },
954
+ "type": {
955
+ "type": "string",
956
+ "description": "Optional output type specification",
957
+ },
958
+ },
959
+ "required": ["input"],
960
+ }
961
+
962
+ return BaseTool(
963
+ name=tool_name,
964
+ description=tool_description,
965
+ function=generic_tool_function,
966
+ parameters_json_schema=parameters_schema,
967
+ **kwargs,
968
+ )
969
+
970
+ if func is None:
971
+ # Called as @model.as_tool() or model.as_tool()
972
+ return create_tool_wrapper
973
+ else:
974
+ # Called as @model.as_tool (without parentheses)
975
+ return create_tool_wrapper(func)
976
+
977
+ def _execute_tool_function(
978
+ self,
979
+ target_func: Callable,
980
+ return_type: Type,
981
+ system_instructions: str,
982
+ **kwargs: Any,
983
+ ) -> Any:
984
+ """Execute a function-based tool using the language model."""
985
+ # Format the function call parameters
986
+ param_text = ", ".join([f"{k}={v}" for k, v in kwargs.items()])
987
+ input_text = f"Function: {target_func.__name__}({param_text})"
988
+
989
+ # Use the language model to generate structured output
990
+ if return_type != str:
991
+ response = self.run(
992
+ messages=[{"role": "user", "content": input_text}],
993
+ instructions=system_instructions,
994
+ type=return_type,
995
+ )
996
+ else:
997
+ response = self.run(
998
+ messages=[{"role": "user", "content": input_text}],
999
+ instructions=system_instructions,
1000
+ )
1001
+
1002
+ return response.output
1003
+
1004
+ def _execute_generic_tool(
1005
+ self,
1006
+ input: str,
1007
+ output_type: Optional[Type] = None,
1008
+ system_instructions: Optional[str] = None,
1009
+ **kwargs: Any,
1010
+ ) -> Any:
1011
+ """Execute a generic tool using the language model."""
1012
+ if output_type and output_type != str:
1013
+ response = self.run(
1014
+ messages=[{"role": "user", "content": input}],
1015
+ instructions=system_instructions,
1016
+ type=output_type,
1017
+ **kwargs,
1018
+ )
1019
+ else:
1020
+ response = self.run(
1021
+ messages=[{"role": "user", "content": input}],
1022
+ instructions=system_instructions,
1023
+ **kwargs,
1024
+ )
1025
+
1026
+ return response.output