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