gllm-core-binary 0.3.23b3__py3-none-any.whl → 0.4.4__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.
- gllm_core/adapters/__init__.py +5 -0
- gllm_core/adapters/__init__.pyi +3 -0
- gllm_core/adapters/tool/__init__.py +6 -0
- gllm_core/adapters/tool/__init__.pyi +4 -0
- gllm_core/adapters/tool/google_adk.py +91 -0
- gllm_core/adapters/tool/google_adk.pyi +23 -0
- gllm_core/adapters/tool/langchain.py +130 -0
- gllm_core/adapters/tool/langchain.pyi +31 -0
- gllm_core/constants.py +0 -1
- gllm_core/constants.pyi +0 -1
- gllm_core/event/event_emitter.py +8 -44
- gllm_core/event/event_emitter.pyi +9 -21
- gllm_core/event/handler/console_event_handler.py +1 -12
- gllm_core/event/handler/console_event_handler.pyi +0 -1
- gllm_core/event/handler/print_event_handler.py +15 -59
- gllm_core/event/handler/print_event_handler.pyi +1 -2
- gllm_core/schema/__init__.py +2 -2
- gllm_core/schema/__init__.pyi +2 -2
- gllm_core/schema/component.py +236 -27
- gllm_core/schema/component.pyi +164 -17
- gllm_core/schema/schema_generator.py +150 -0
- gllm_core/schema/schema_generator.pyi +35 -0
- gllm_core/schema/tool.py +31 -1
- gllm_core/schema/tool.pyi +21 -0
- gllm_core/utils/__init__.py +2 -0
- gllm_core/utils/__init__.pyi +2 -1
- gllm_core/utils/analyzer.py +24 -1
- gllm_core/utils/analyzer.pyi +15 -1
- gllm_core/utils/concurrency.py +2 -2
- gllm_core/utils/logger_manager.py +17 -7
- gllm_core/utils/logger_manager.pyi +3 -0
- gllm_core/utils/main_method_resolver.py +185 -0
- gllm_core/utils/main_method_resolver.pyi +54 -0
- gllm_core/utils/retry.py +130 -21
- gllm_core/utils/retry.pyi +6 -29
- gllm_core_binary-0.4.4.dist-info/METADATA +177 -0
- {gllm_core_binary-0.3.23b3.dist-info → gllm_core_binary-0.4.4.dist-info}/RECORD +39 -27
- gllm_core_binary-0.3.23b3.dist-info/METADATA +0 -108
- {gllm_core_binary-0.3.23b3.dist-info → gllm_core_binary-0.4.4.dist-info}/WHEEL +0 -0
- {gllm_core_binary-0.3.23b3.dist-info → gllm_core_binary-0.4.4.dist-info}/top_level.txt +0 -0
gllm_core/schema/component.py
CHANGED
|
@@ -10,12 +10,9 @@ References:
|
|
|
10
10
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
|
-
import ast
|
|
14
13
|
import inspect
|
|
15
14
|
import logging
|
|
16
|
-
import textwrap
|
|
17
15
|
import warnings
|
|
18
|
-
from abc import ABC, abstractmethod
|
|
19
16
|
from functools import lru_cache
|
|
20
17
|
from traceback import format_exc
|
|
21
18
|
from typing import TYPE_CHECKING, Any, Callable
|
|
@@ -23,25 +20,83 @@ from typing import TYPE_CHECKING, Any, Callable
|
|
|
23
20
|
if TYPE_CHECKING:
|
|
24
21
|
from gllm_core.event.event_emitter import EventEmitter
|
|
25
22
|
|
|
26
|
-
|
|
23
|
+
|
|
24
|
+
from pydantic import BaseModel
|
|
25
|
+
|
|
26
|
+
from gllm_core.schema.schema_generator import generate_params_model
|
|
27
|
+
from gllm_core.schema.tool import Tool, tool
|
|
28
|
+
from gllm_core.utils import BinaryHandlingStrategy, binary_handler_factory
|
|
29
|
+
from gllm_core.utils.analyzer import MethodSignature, ParameterInfo, ParameterKind, RunProfile, analyze_method
|
|
27
30
|
from gllm_core.utils.logger_manager import LoggerManager
|
|
31
|
+
from gllm_core.utils.main_method_resolver import MainMethodResolver
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def main(method: Callable) -> Callable:
|
|
35
|
+
"""Decorate a Component method as the async main entrypoint.
|
|
36
|
+
|
|
37
|
+
Usage:
|
|
38
|
+
Declare the coroutine that should act as the primary execution path
|
|
39
|
+
for a `Component` subclass. The decorated coroutine will be resolved by
|
|
40
|
+
`Component.run()` unless another subclass overrides the decoration.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
method (Callable): Coroutine to mark as the main entrypoint.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Callable: The same coroutine that is passed to the decorator. The decorator only marks the method as the main
|
|
47
|
+
entrypoint. It does not wrap or change its behavior or signature.
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
TypeError: If the decorated callable is not asynchronous.
|
|
51
|
+
"""
|
|
52
|
+
actual_method = method.__func__ if isinstance(method, (classmethod, staticmethod)) else method
|
|
28
53
|
|
|
54
|
+
if not inspect.iscoroutinefunction(actual_method):
|
|
55
|
+
method_name = getattr(actual_method, "__name__", "unknown")
|
|
56
|
+
raise TypeError(f"Main method {method_name!r} must be asynchronous")
|
|
57
|
+
method.__is_main__ = True
|
|
58
|
+
return method
|
|
29
59
|
|
|
30
|
-
|
|
60
|
+
|
|
61
|
+
class Component:
|
|
31
62
|
"""An abstract base class for all components used throughout the Gen AI applications.
|
|
32
63
|
|
|
33
64
|
Every instance of Component has access to class-level `_default_log_level` and `_logger`, as detailed below.
|
|
34
65
|
For components that require high observability, it is recommended to set `_default_log_level` to `logging.INFO`
|
|
35
66
|
or higher.
|
|
36
67
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
68
|
+
Defining Custom Components:
|
|
69
|
+
There are two ways to define the main execution logic for a component:
|
|
70
|
+
|
|
71
|
+
1. **Using the @main decorator (Recommended)**:
|
|
72
|
+
Decorate an async method with `@main` to mark it as the primary entrypoint.
|
|
73
|
+
This is the preferred approach as it provides explicit control over the main method.
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
class MyComponent(Component):
|
|
77
|
+
_default_log_level = logging.INFO
|
|
78
|
+
|
|
79
|
+
@main
|
|
80
|
+
async def execute(self, **kwargs: Any) -> Any:
|
|
81
|
+
return "Hello from @main!"
|
|
82
|
+
```
|
|
41
83
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
84
|
+
2. **Implementing _run method (Deprecated)**:
|
|
85
|
+
Override the abstract `_run` method. This is the traditional approach and still supported.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
class MyComponent(Component):
|
|
89
|
+
_default_log_level = logging.INFO
|
|
90
|
+
|
|
91
|
+
async def _run(self, **kwargs: Any) -> Any:
|
|
92
|
+
return "Hello, World!"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The `run()` method resolves the main entrypoint using the following precedence:
|
|
96
|
+
1. Method decorated with @main in the current class.
|
|
97
|
+
2. Method decorated with @main in the nearest ancestor class.
|
|
98
|
+
3. Method named in __main_method__ property.
|
|
99
|
+
4. The _run method (with deprecation warning).
|
|
45
100
|
|
|
46
101
|
Attributes:
|
|
47
102
|
run_profile (RunProfile): The profile of the `_run` method.
|
|
@@ -52,12 +107,100 @@ class Component(ABC):
|
|
|
52
107
|
**Do not override this property in your subclass.**
|
|
53
108
|
|
|
54
109
|
You also do not need to write this attribute in your component's docstring.
|
|
55
|
-
_default_log_level (int): The default log level for the component. Defaults to DEBUG.
|
|
56
|
-
_logger (logging.Logger): The logger instance for the component.
|
|
57
110
|
"""
|
|
58
111
|
|
|
59
112
|
_default_log_level = logging.DEBUG
|
|
60
113
|
|
|
114
|
+
def __init_subclass__(cls, **kwargs):
|
|
115
|
+
"""Hook called when a subclass is created.
|
|
116
|
+
|
|
117
|
+
This validates the __main_method__ property and checks for multiple @main decorators
|
|
118
|
+
within the current class definition. Uses MainMethodResolver for consistent validation logic.
|
|
119
|
+
|
|
120
|
+
Note: Multiple inheritance conflicts are intentionally deferred to runtime (get_main())
|
|
121
|
+
to allow class definition to succeed.
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
AttributeError: If __main_method__ refers to a non-existent method.
|
|
125
|
+
TypeError: If multiple methods are decorated with @main in the same class.
|
|
126
|
+
"""
|
|
127
|
+
super().__init_subclass__(**kwargs)
|
|
128
|
+
|
|
129
|
+
MainMethodResolver.validate_class(cls)
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
@lru_cache(maxsize=None)
|
|
133
|
+
def get_main(cls) -> Callable | None:
|
|
134
|
+
"""Return the resolved main coroutine for this Component class.
|
|
135
|
+
|
|
136
|
+
This method resolves the main method for the Component class following
|
|
137
|
+
the precedence rules:
|
|
138
|
+
1. Most derived coroutine decorated with `@main`.
|
|
139
|
+
2. Method named by `__main_method__`.
|
|
140
|
+
3. `_run` coroutine as a deprecated fallback.
|
|
141
|
+
|
|
142
|
+
Results are cached for performance.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Callable | None: The coroutine that will be executed by `run()` or
|
|
146
|
+
`None` when no entrypoint can be determined.
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
TypeError: If conflicting main methods are inherited from multiple ancestors.
|
|
150
|
+
"""
|
|
151
|
+
return MainMethodResolver(cls).resolve()
|
|
152
|
+
|
|
153
|
+
@classmethod
|
|
154
|
+
@lru_cache(maxsize=None)
|
|
155
|
+
def _get_input_params_model(cls) -> type[BaseModel] | None:
|
|
156
|
+
"""Generate and cache the input-parameter model for this component class.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
type[BaseModel] | None: A Pydantic model describing the component's
|
|
160
|
+
input parameters, or `None` when no main method can be resolved or the
|
|
161
|
+
model generation fails.
|
|
162
|
+
"""
|
|
163
|
+
main_method = cls.get_main() or cls._run
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
return generate_params_model(main_method, cls.__name__)
|
|
167
|
+
except Exception: # pragma: no cover - defensive fallback
|
|
168
|
+
logging.getLogger(__name__).exception("Failed to generate input params model for %s", cls.__name__)
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def input_params(self) -> type[BaseModel] | None:
|
|
172
|
+
"""Return the Pydantic model describing this component's main method input parameters.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
type[BaseModel] | None: The cached model that mirrors the signature of
|
|
176
|
+
the resolved main method, or `None` if no main method can be
|
|
177
|
+
determined.
|
|
178
|
+
|
|
179
|
+
Examples:
|
|
180
|
+
```python
|
|
181
|
+
from pydantic import ValidationError
|
|
182
|
+
|
|
183
|
+
component = SomeComponent()
|
|
184
|
+
ParamsModel = component.input_params
|
|
185
|
+
assert ParamsModel.__name__ == "SomeComponentParams"
|
|
186
|
+
fields = list(ParamsModel.model_fields)
|
|
187
|
+
|
|
188
|
+
# Validation with valid params
|
|
189
|
+
params = ParamsModel(text="hello")
|
|
190
|
+
|
|
191
|
+
# Validation catches missing required fields
|
|
192
|
+
try:
|
|
193
|
+
invalid_params = ParamsModel() # Missing required 'text' field
|
|
194
|
+
except ValidationError as e:
|
|
195
|
+
print(f"Validation failed: {e.error_count()} errors")
|
|
196
|
+
|
|
197
|
+
# Argument construction
|
|
198
|
+
payload = params.model_dump()
|
|
199
|
+
result = await component.run(**payload)
|
|
200
|
+
```
|
|
201
|
+
"""
|
|
202
|
+
return self.__class__._get_input_params_model()
|
|
203
|
+
|
|
61
204
|
@property
|
|
62
205
|
def _logger(self) -> logging.Logger:
|
|
63
206
|
"""Get a logger instance configured for this component class.
|
|
@@ -94,28 +237,92 @@ class Component(ABC):
|
|
|
94
237
|
async def run(self, **kwargs: Any) -> Any:
|
|
95
238
|
"""Runs the operations defined for the component.
|
|
96
239
|
|
|
97
|
-
This method emits the provided input arguments using an EventEmitter instance if available, executes
|
|
98
|
-
|
|
240
|
+
This method emits the provided input arguments using an EventEmitter instance if available, executes the
|
|
241
|
+
resolved main method, and emits the resulting output if the EventEmitter is provided.
|
|
242
|
+
|
|
243
|
+
The main method is resolved using the following precedence:
|
|
244
|
+
1. Method decorated with @main in the current class.
|
|
245
|
+
2. Method decorated with @main in the nearest ancestor class.
|
|
246
|
+
3. Method named in __main_method__ property.
|
|
247
|
+
4. The _run method (with deprecation warning).
|
|
99
248
|
|
|
100
249
|
Args:
|
|
101
250
|
**kwargs (Any): A dictionary of arguments to be processed. May include an `event_emitter`
|
|
102
251
|
key with an EventEmitter instance.
|
|
103
252
|
|
|
104
253
|
Returns:
|
|
105
|
-
Any: The result of the
|
|
254
|
+
Any: The result of the resolved main method.
|
|
255
|
+
|
|
256
|
+
Raises:
|
|
257
|
+
TypeError: If conflicting main methods are inherited from multiple ancestors.
|
|
258
|
+
AttributeError: If __main_method__ refers to a non-existent method.
|
|
106
259
|
"""
|
|
107
260
|
event_emitter = kwargs.get("event_emitter")
|
|
108
261
|
|
|
109
262
|
input_event = self._format_input_event(kwargs)
|
|
110
263
|
await self._log_io_event(input_event, event_emitter)
|
|
111
264
|
|
|
112
|
-
|
|
265
|
+
main_method = self.get_main()
|
|
266
|
+
if main_method:
|
|
267
|
+
output = await main_method(self, **kwargs)
|
|
268
|
+
else:
|
|
269
|
+
self._logger.warning(
|
|
270
|
+
"_run method is deprecated and will be removed in a future release.\n"
|
|
271
|
+
"Use the `@main` decorator instead."
|
|
272
|
+
)
|
|
273
|
+
output = await self._run(**kwargs)
|
|
113
274
|
|
|
114
275
|
output_event = self._format_output_event(output)
|
|
115
276
|
await self._log_io_event(output_event, event_emitter)
|
|
116
277
|
|
|
117
278
|
return output
|
|
118
279
|
|
|
280
|
+
def as_tool(self, name: str | None = None, description: str | None = None, title: str | None = None) -> Tool:
|
|
281
|
+
"""Convert the component's main method into a `Tool` instance.
|
|
282
|
+
|
|
283
|
+
Example:
|
|
284
|
+
```python
|
|
285
|
+
from gllm_core.schema import Component, main
|
|
286
|
+
|
|
287
|
+
class MyComponent(Component):
|
|
288
|
+
@main
|
|
289
|
+
async def my_method(self, param: str) -> str:
|
|
290
|
+
return param
|
|
291
|
+
|
|
292
|
+
component = MyComponent()
|
|
293
|
+
tool = component.as_tool()
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
name (str | None, optional): Identifier for the resulting tool. Defaults to the component class name.
|
|
298
|
+
description (str | None, optional): Summary of the tool's behavior. Defaults to None, in which case the
|
|
299
|
+
main method's docstring is used.
|
|
300
|
+
title (str | None, optional): Optional display title for the tool. Defaults to None, in which case the
|
|
301
|
+
component's class name is used.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Tool: The tool wrapping the component's main method.
|
|
305
|
+
|
|
306
|
+
Raises:
|
|
307
|
+
RuntimeError: If the component does not declare a main method using @main or __main_method__.
|
|
308
|
+
"""
|
|
309
|
+
main_method = self.get_main()
|
|
310
|
+
if main_method is None or main_method is self.__class__._run:
|
|
311
|
+
raise RuntimeError(
|
|
312
|
+
"Component main method is not declared. Use @main or __main_method__ before converting to a tool."
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
bound_main = main_method.__get__(self, self.__class__)
|
|
316
|
+
|
|
317
|
+
tool_name = name or self.__class__.__name__
|
|
318
|
+
tool_decorator = tool(name=tool_name, description=description, title=title)
|
|
319
|
+
component_tool = tool_decorator(bound_main)
|
|
320
|
+
|
|
321
|
+
if not isinstance(component_tool, Tool): # pragma: no cover - Defensive: decorator should always return Tool
|
|
322
|
+
raise TypeError("Failed to convert component main method into a Tool instance.")
|
|
323
|
+
|
|
324
|
+
return component_tool
|
|
325
|
+
|
|
119
326
|
async def _log_io_event(self, event_message: str, event_emitter: EventEmitter | None = None) -> None:
|
|
120
327
|
"""Logs an event message and emits it via the event emitter if available.
|
|
121
328
|
|
|
@@ -147,8 +354,10 @@ class Component(ABC):
|
|
|
147
354
|
|
|
148
355
|
if input_dict:
|
|
149
356
|
message += " Processing input: "
|
|
357
|
+
handler = binary_handler_factory(BinaryHandlingStrategy.SHOW_SIZE)
|
|
150
358
|
for key, value in input_dict.items():
|
|
151
|
-
|
|
359
|
+
formatted_value = handler(value) if isinstance(value, bytes) else value
|
|
360
|
+
message += f"\n - {key}: {formatted_value!r}"
|
|
152
361
|
|
|
153
362
|
return message
|
|
154
363
|
|
|
@@ -164,17 +373,21 @@ class Component(ABC):
|
|
|
164
373
|
Returns:
|
|
165
374
|
str: A formatted event message string representing the output data.
|
|
166
375
|
"""
|
|
167
|
-
|
|
376
|
+
handler = binary_handler_factory(BinaryHandlingStrategy.SHOW_SIZE)
|
|
377
|
+
formatted_output = handler(output) if isinstance(output, bytes) else output
|
|
378
|
+
message = f"[Finished {self.__class__.__name__!r}] Successfully produced output:\n{formatted_output!r}"
|
|
168
379
|
return message
|
|
169
380
|
|
|
170
|
-
@abstractmethod
|
|
171
381
|
async def _run(self, **kwargs: Any) -> Any:
|
|
172
382
|
"""Defines the core process logic to be implemented by subclasses.
|
|
173
383
|
|
|
174
|
-
This
|
|
384
|
+
This method can be implemented in a subclass to define the specific process that will be executed.
|
|
175
385
|
It is called by the `run` method and is responsible for handling the provided input arguments and producing
|
|
176
386
|
an output.
|
|
177
387
|
|
|
388
|
+
Deprecation:
|
|
389
|
+
This method is deprecated and will be removed in a future release. Use the `@main` decorator instead.
|
|
390
|
+
|
|
178
391
|
Args:
|
|
179
392
|
**kwargs (Any): A dictionary of arguments required for the process.
|
|
180
393
|
|
|
@@ -227,11 +440,7 @@ class Component(ABC):
|
|
|
227
440
|
"""
|
|
228
441
|
try:
|
|
229
442
|
method = cls._run
|
|
230
|
-
|
|
231
|
-
tree = ast.parse(textwrap.dedent(source))
|
|
232
|
-
analyzer = RunAnalyzer(cls)
|
|
233
|
-
analyzer.visit(tree)
|
|
234
|
-
return analyzer.profile
|
|
443
|
+
return analyze_method(cls, method)
|
|
235
444
|
except Exception as e:
|
|
236
445
|
warnings.warn(
|
|
237
446
|
f"Failed to analyze the _run method: {e}.\n{format_exc()}",
|
gllm_core/schema/component.pyi
CHANGED
|
@@ -1,25 +1,71 @@
|
|
|
1
|
-
import abc
|
|
2
|
-
from abc import ABC
|
|
3
1
|
from gllm_core.event.event_emitter import EventEmitter as EventEmitter
|
|
4
|
-
from gllm_core.
|
|
2
|
+
from gllm_core.schema.schema_generator import generate_params_model as generate_params_model
|
|
3
|
+
from gllm_core.schema.tool import Tool as Tool, tool as tool
|
|
4
|
+
from gllm_core.utils import BinaryHandlingStrategy as BinaryHandlingStrategy, binary_handler_factory as binary_handler_factory
|
|
5
|
+
from gllm_core.utils.analyzer import MethodSignature as MethodSignature, ParameterInfo as ParameterInfo, ParameterKind as ParameterKind, RunProfile as RunProfile, analyze_method as analyze_method
|
|
5
6
|
from gllm_core.utils.logger_manager import LoggerManager as LoggerManager
|
|
6
|
-
from
|
|
7
|
+
from gllm_core.utils.main_method_resolver import MainMethodResolver as MainMethodResolver
|
|
8
|
+
from pydantic import BaseModel as BaseModel
|
|
9
|
+
from typing import Any, Callable
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
def main(method: Callable) -> Callable:
|
|
12
|
+
"""Decorate a Component method as the async main entrypoint.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
Declare the coroutine that should act as the primary execution path
|
|
16
|
+
for a `Component` subclass. The decorated coroutine will be resolved by
|
|
17
|
+
`Component.run()` unless another subclass overrides the decoration.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
method (Callable): Coroutine to mark as the main entrypoint.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Callable: The same coroutine that is passed to the decorator. The decorator only marks the method as the main
|
|
24
|
+
entrypoint. It does not wrap or change its behavior or signature.
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
TypeError: If the decorated callable is not asynchronous.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
class Component:
|
|
9
31
|
'''An abstract base class for all components used throughout the Gen AI applications.
|
|
10
32
|
|
|
11
33
|
Every instance of Component has access to class-level `_default_log_level` and `_logger`, as detailed below.
|
|
12
34
|
For components that require high observability, it is recommended to set `_default_log_level` to `logging.INFO`
|
|
13
35
|
or higher.
|
|
14
36
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
37
|
+
Defining Custom Components:
|
|
38
|
+
There are two ways to define the main execution logic for a component:
|
|
39
|
+
|
|
40
|
+
1. **Using the @main decorator (Recommended)**:
|
|
41
|
+
Decorate an async method with `@main` to mark it as the primary entrypoint.
|
|
42
|
+
This is the preferred approach as it provides explicit control over the main method.
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
class MyComponent(Component):
|
|
46
|
+
_default_log_level = logging.INFO
|
|
47
|
+
|
|
48
|
+
@main
|
|
49
|
+
async def execute(self, **kwargs: Any) -> Any:
|
|
50
|
+
return "Hello from @main!"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
2. **Implementing _run method (Deprecated)**:
|
|
54
|
+
Override the abstract `_run` method. This is the traditional approach and still supported.
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
class MyComponent(Component):
|
|
58
|
+
_default_log_level = logging.INFO
|
|
19
59
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
60
|
+
async def _run(self, **kwargs: Any) -> Any:
|
|
61
|
+
return "Hello, World!"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The `run()` method resolves the main entrypoint using the following precedence:
|
|
65
|
+
1. Method decorated with @main in the current class.
|
|
66
|
+
2. Method decorated with @main in the nearest ancestor class.
|
|
67
|
+
3. Method named in __main_method__ property.
|
|
68
|
+
4. The _run method (with deprecation warning).
|
|
23
69
|
|
|
24
70
|
Attributes:
|
|
25
71
|
run_profile (RunProfile): The profile of the `_run` method.
|
|
@@ -30,21 +76,122 @@ class Component(ABC, metaclass=abc.ABCMeta):
|
|
|
30
76
|
**Do not override this property in your subclass.**
|
|
31
77
|
|
|
32
78
|
You also do not need to write this attribute in your component\'s docstring.
|
|
33
|
-
_default_log_level (int): The default log level for the component. Defaults to DEBUG.
|
|
34
|
-
_logger (logging.Logger): The logger instance for the component.
|
|
35
79
|
'''
|
|
80
|
+
def __init_subclass__(cls, **kwargs) -> None:
|
|
81
|
+
"""Hook called when a subclass is created.
|
|
82
|
+
|
|
83
|
+
This validates the __main_method__ property and checks for multiple @main decorators
|
|
84
|
+
within the current class definition. Uses MainMethodResolver for consistent validation logic.
|
|
85
|
+
|
|
86
|
+
Note: Multiple inheritance conflicts are intentionally deferred to runtime (get_main())
|
|
87
|
+
to allow class definition to succeed.
|
|
88
|
+
|
|
89
|
+
Raises:
|
|
90
|
+
AttributeError: If __main_method__ refers to a non-existent method.
|
|
91
|
+
TypeError: If multiple methods are decorated with @main in the same class.
|
|
92
|
+
"""
|
|
93
|
+
@classmethod
|
|
94
|
+
def get_main(cls) -> Callable | None:
|
|
95
|
+
"""Return the resolved main coroutine for this Component class.
|
|
96
|
+
|
|
97
|
+
This method resolves the main method for the Component class following
|
|
98
|
+
the precedence rules:
|
|
99
|
+
1. Most derived coroutine decorated with `@main`.
|
|
100
|
+
2. Method named by `__main_method__`.
|
|
101
|
+
3. `_run` coroutine as a deprecated fallback.
|
|
102
|
+
|
|
103
|
+
Results are cached for performance.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Callable | None: The coroutine that will be executed by `run()` or
|
|
107
|
+
`None` when no entrypoint can be determined.
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
TypeError: If conflicting main methods are inherited from multiple ancestors.
|
|
111
|
+
"""
|
|
112
|
+
@property
|
|
113
|
+
def input_params(self) -> type[BaseModel] | None:
|
|
114
|
+
'''Return the Pydantic model describing this component\'s main method input parameters.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
type[BaseModel] | None: The cached model that mirrors the signature of
|
|
118
|
+
the resolved main method, or `None` if no main method can be
|
|
119
|
+
determined.
|
|
120
|
+
|
|
121
|
+
Examples:
|
|
122
|
+
```python
|
|
123
|
+
from pydantic import ValidationError
|
|
124
|
+
|
|
125
|
+
component = SomeComponent()
|
|
126
|
+
ParamsModel = component.input_params
|
|
127
|
+
assert ParamsModel.__name__ == "SomeComponentParams"
|
|
128
|
+
fields = list(ParamsModel.model_fields)
|
|
129
|
+
|
|
130
|
+
# Validation with valid params
|
|
131
|
+
params = ParamsModel(text="hello")
|
|
132
|
+
|
|
133
|
+
# Validation catches missing required fields
|
|
134
|
+
try:
|
|
135
|
+
invalid_params = ParamsModel() # Missing required \'text\' field
|
|
136
|
+
except ValidationError as e:
|
|
137
|
+
print(f"Validation failed: {e.error_count()} errors")
|
|
138
|
+
|
|
139
|
+
# Argument construction
|
|
140
|
+
payload = params.model_dump()
|
|
141
|
+
result = await component.run(**payload)
|
|
142
|
+
```
|
|
143
|
+
'''
|
|
36
144
|
async def run(self, **kwargs: Any) -> Any:
|
|
37
145
|
"""Runs the operations defined for the component.
|
|
38
146
|
|
|
39
|
-
This method emits the provided input arguments using an EventEmitter instance if available, executes
|
|
40
|
-
|
|
147
|
+
This method emits the provided input arguments using an EventEmitter instance if available, executes the
|
|
148
|
+
resolved main method, and emits the resulting output if the EventEmitter is provided.
|
|
149
|
+
|
|
150
|
+
The main method is resolved using the following precedence:
|
|
151
|
+
1. Method decorated with @main in the current class.
|
|
152
|
+
2. Method decorated with @main in the nearest ancestor class.
|
|
153
|
+
3. Method named in __main_method__ property.
|
|
154
|
+
4. The _run method (with deprecation warning).
|
|
41
155
|
|
|
42
156
|
Args:
|
|
43
157
|
**kwargs (Any): A dictionary of arguments to be processed. May include an `event_emitter`
|
|
44
158
|
key with an EventEmitter instance.
|
|
45
159
|
|
|
46
160
|
Returns:
|
|
47
|
-
Any: The result of the
|
|
161
|
+
Any: The result of the resolved main method.
|
|
162
|
+
|
|
163
|
+
Raises:
|
|
164
|
+
TypeError: If conflicting main methods are inherited from multiple ancestors.
|
|
165
|
+
AttributeError: If __main_method__ refers to a non-existent method.
|
|
166
|
+
"""
|
|
167
|
+
def as_tool(self, name: str | None = None, description: str | None = None, title: str | None = None) -> Tool:
|
|
168
|
+
"""Convert the component's main method into a `Tool` instance.
|
|
169
|
+
|
|
170
|
+
Example:
|
|
171
|
+
```python
|
|
172
|
+
from gllm_core.schema import Component, main
|
|
173
|
+
|
|
174
|
+
class MyComponent(Component):
|
|
175
|
+
@main
|
|
176
|
+
async def my_method(self, param: str) -> str:
|
|
177
|
+
return param
|
|
178
|
+
|
|
179
|
+
component = MyComponent()
|
|
180
|
+
tool = component.as_tool()
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
name (str | None, optional): Identifier for the resulting tool. Defaults to the component class name.
|
|
185
|
+
description (str | None, optional): Summary of the tool's behavior. Defaults to None, in which case the
|
|
186
|
+
main method's docstring is used.
|
|
187
|
+
title (str | None, optional): Optional display title for the tool. Defaults to None, in which case the
|
|
188
|
+
component's class name is used.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
Tool: The tool wrapping the component's main method.
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
RuntimeError: If the component does not declare a main method using @main or __main_method__.
|
|
48
195
|
"""
|
|
49
196
|
@property
|
|
50
197
|
def run_profile(self) -> RunProfile:
|