speedy-utils 1.1.27__py3-none-any.whl → 1.1.29__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 (54) hide show
  1. llm_utils/__init__.py +16 -4
  2. llm_utils/chat_format/__init__.py +10 -10
  3. llm_utils/chat_format/display.py +33 -21
  4. llm_utils/chat_format/transform.py +17 -19
  5. llm_utils/chat_format/utils.py +6 -4
  6. llm_utils/group_messages.py +17 -14
  7. llm_utils/lm/__init__.py +6 -5
  8. llm_utils/lm/async_lm/__init__.py +1 -0
  9. llm_utils/lm/async_lm/_utils.py +10 -9
  10. llm_utils/lm/async_lm/async_llm_task.py +141 -137
  11. llm_utils/lm/async_lm/async_lm.py +48 -42
  12. llm_utils/lm/async_lm/async_lm_base.py +59 -60
  13. llm_utils/lm/async_lm/lm_specific.py +4 -3
  14. llm_utils/lm/base_prompt_builder.py +93 -70
  15. llm_utils/lm/llm.py +126 -108
  16. llm_utils/lm/llm_signature.py +4 -2
  17. llm_utils/lm/lm_base.py +72 -73
  18. llm_utils/lm/mixins.py +102 -62
  19. llm_utils/lm/openai_memoize.py +124 -87
  20. llm_utils/lm/signature.py +105 -92
  21. llm_utils/lm/utils.py +42 -23
  22. llm_utils/scripts/vllm_load_balancer.py +23 -30
  23. llm_utils/scripts/vllm_serve.py +8 -7
  24. llm_utils/vector_cache/__init__.py +9 -3
  25. llm_utils/vector_cache/cli.py +1 -1
  26. llm_utils/vector_cache/core.py +59 -63
  27. llm_utils/vector_cache/types.py +7 -5
  28. llm_utils/vector_cache/utils.py +12 -8
  29. speedy_utils/__imports.py +244 -0
  30. speedy_utils/__init__.py +90 -194
  31. speedy_utils/all.py +125 -227
  32. speedy_utils/common/clock.py +37 -42
  33. speedy_utils/common/function_decorator.py +6 -12
  34. speedy_utils/common/logger.py +43 -52
  35. speedy_utils/common/notebook_utils.py +13 -21
  36. speedy_utils/common/patcher.py +21 -17
  37. speedy_utils/common/report_manager.py +42 -44
  38. speedy_utils/common/utils_cache.py +152 -169
  39. speedy_utils/common/utils_io.py +137 -103
  40. speedy_utils/common/utils_misc.py +15 -21
  41. speedy_utils/common/utils_print.py +22 -28
  42. speedy_utils/multi_worker/process.py +66 -79
  43. speedy_utils/multi_worker/thread.py +78 -155
  44. speedy_utils/scripts/mpython.py +38 -36
  45. speedy_utils/scripts/openapi_client_codegen.py +10 -10
  46. {speedy_utils-1.1.27.dist-info → speedy_utils-1.1.29.dist-info}/METADATA +1 -1
  47. speedy_utils-1.1.29.dist-info/RECORD +57 -0
  48. vision_utils/README.md +202 -0
  49. vision_utils/__init__.py +4 -0
  50. vision_utils/io_utils.py +735 -0
  51. vision_utils/plot.py +345 -0
  52. speedy_utils-1.1.27.dist-info/RECORD +0 -52
  53. {speedy_utils-1.1.27.dist-info → speedy_utils-1.1.29.dist-info}/WHEEL +0 -0
  54. {speedy_utils-1.1.27.dist-info → speedy_utils-1.1.29.dist-info}/entry_points.txt +0 -0
@@ -1,91 +1,128 @@
1
- from openai import OpenAI, AsyncOpenAI
2
- from typing import Any, Callable
1
+ from collections.abc import Callable
2
+ from typing import TYPE_CHECKING, Any
3
3
 
4
4
  from speedy_utils.common.utils_cache import memoize
5
5
 
6
6
 
7
- class MOpenAI(OpenAI):
8
- """
9
- MOpenAI(*args, **kwargs)
10
-
11
- Subclass of OpenAI that transparently memoizes the instance's `post` method.
12
-
13
- This class forwards all constructor arguments to the OpenAI base class and then
14
- replaces the instance's `post` method with a memoized wrapper:
15
-
16
- Behavior
17
- - The memoized `post` caches responses based on the arguments with which it is
18
- invoked, preventing repeated identical requests from invoking the underlying
19
- OpenAI API repeatedly.
20
- - Because `post` is replaced on the instance, the cache is by-default tied to
21
- the MOpenAI instance (per-instance cache).
22
- - Any initialization arguments are passed unchanged to OpenAI.__init__.
23
-
24
- Notes and cautions
25
- - The exact semantics of caching (cache key construction, expiry, max size,
26
- persistence) depend on the implementation of `memoize`. Ensure that the
27
- provided `memoize` supports the desired behavior (e.g., hashing of mutable
28
- inputs, thread-safety, TTL, cache invalidation).
29
- - If the original `post` method has important side effects or relies on
30
- non-deterministic behavior, memoization may change program behavior.
31
- - If you need a shared cache across instances, or more advanced cache controls,
32
- modify `memoize` or wrap at a class/static level instead of assigning to the
33
- bound method.
34
- - Type information is now fully preserved by the memoize decorator, eliminating
35
- the need for type casting.
36
-
37
- Example
38
- m = MOpenAI(api_key="...", model="gpt-4")
39
- r1 = m.post("Hello") # executes API call and caches result
40
- r2 = m.post("Hello") # returns cached result (no API call)
41
- """
42
-
43
- def __init__(self, *args, cache=True, **kwargs):
44
- super().__init__(*args, **kwargs)
45
- self._orig_post = self.post
46
- if cache:
47
- self.set_cache(cache)
48
-
49
- def set_cache(self, cache: bool) -> None:
50
- """Enable or disable caching of the post method."""
51
- if cache and self.post == self._orig_post:
52
- self.post = memoize(self._orig_post) # type: ignore
53
- elif not cache and self.post != self._orig_post:
54
- self.post = self._orig_post
55
-
56
-
57
- class MAsyncOpenAI(AsyncOpenAI):
58
- """
59
- MAsyncOpenAI(*args, **kwargs)
60
-
61
- Async subclass of AsyncOpenAI that transparently memoizes the instance's `post` method.
62
-
63
- This class forwards all constructor arguments to the AsyncOpenAI base class and then
64
- replaces the instance's `post` method with a memoized wrapper:
65
-
66
- Behavior
67
- - The memoized `post` caches responses based on the arguments with which it is
68
- invoked, preventing repeated identical requests from invoking the underlying
69
- OpenAI API repeatedly.
70
- - Because `post` is replaced on the instance, the cache is by-default tied to
71
- the MAsyncOpenAI instance (per-instance cache).
72
- - Any initialization arguments are passed unchanged to AsyncOpenAI.__init__.
73
-
74
- Example
75
- m = MAsyncOpenAI(api_key="...", model="gpt-4")
76
- r1 = await m.post("Hello") # executes API call and caches result
77
- r2 = await m.post("Hello") # returns cached result (no API call)
78
- """
79
-
80
- def __init__(self, *args, cache=True, **kwargs):
81
- super().__init__(*args, **kwargs)
82
- self._orig_post = self.post
83
- if cache:
84
- self.set_cache(cache)
85
-
86
- def set_cache(self, cache: bool) -> None:
87
- """Enable or disable caching of the post method."""
88
- if cache and self.post == self._orig_post:
89
- self.post = memoize(self._orig_post) # type: ignore
90
- elif not cache and self.post != self._orig_post:
91
- self.post = self._orig_post
7
+ if TYPE_CHECKING:
8
+ from openai import AsyncOpenAI, OpenAI
9
+
10
+
11
+ def _get_mopenai_class():
12
+ """Lazily create MOpenAI class to avoid importing openai at module load."""
13
+ from openai import OpenAI
14
+
15
+ class MOpenAI(OpenAI):
16
+ """
17
+ MOpenAI(*args, **kwargs)
18
+
19
+ Subclass of OpenAI that transparently memoizes the instance's `post` method.
20
+
21
+ This class forwards all constructor arguments to the OpenAI base class and then
22
+ replaces the instance's `post` method with a memoized wrapper:
23
+
24
+ Behavior
25
+ - The memoized `post` caches responses based on the arguments with which it is
26
+ invoked, preventing repeated identical requests from invoking the underlying
27
+ OpenAI API repeatedly.
28
+ - Because `post` is replaced on the instance, the cache is by-default tied to
29
+ the MOpenAI instance (per-instance cache).
30
+ - Any initialization arguments are passed unchanged to OpenAI.__init__.
31
+
32
+ Notes and cautions
33
+ - The exact semantics of caching (cache key construction, expiry, max size,
34
+ persistence) depend on the implementation of `memoize`. Ensure that the
35
+ provided `memoize` supports the desired behavior (e.g., hashing of mutable
36
+ inputs, thread-safety, TTL, cache invalidation).
37
+ - If the original `post` method has important side effects or relies on
38
+ non-deterministic behavior, memoization may change program behavior.
39
+ - If you need a shared cache across instances, or more advanced cache controls,
40
+ modify `memoize` or wrap at a class/static level instead of assigning to the
41
+ bound method.
42
+ - Type information is now fully preserved by the memoize decorator, eliminating
43
+ the need for type casting.
44
+
45
+ Example
46
+ m = MOpenAI(api_key="...", model="gpt-4")
47
+ r1 = m.post("Hello") # executes API call and caches result
48
+ r2 = m.post("Hello") # returns cached result (no API call)
49
+ """
50
+
51
+ def __init__(self, *args, cache=True, **kwargs):
52
+ super().__init__(*args, **kwargs)
53
+ self._orig_post = self.post
54
+ if cache:
55
+ self.set_cache(cache)
56
+
57
+ def set_cache(self, cache: bool) -> None:
58
+ """Enable or disable caching of the post method."""
59
+ if cache and self.post == self._orig_post:
60
+ self.post = memoize(self._orig_post) # type: ignore
61
+ elif not cache and self.post != self._orig_post:
62
+ self.post = self._orig_post
63
+
64
+ return MOpenAI
65
+
66
+
67
+ def _get_masyncopenai_class():
68
+ """Lazily create MAsyncOpenAI class to avoid importing openai at module load."""
69
+ from openai import AsyncOpenAI
70
+
71
+ class MAsyncOpenAI(AsyncOpenAI):
72
+ """
73
+ MAsyncOpenAI(*args, **kwargs)
74
+
75
+ Async subclass of AsyncOpenAI that transparently memoizes the instance's `post` method.
76
+
77
+ This class forwards all constructor arguments to the AsyncOpenAI base class and then
78
+ replaces the instance's `post` method with a memoized wrapper:
79
+
80
+ Behavior
81
+ - The memoized `post` caches responses based on the arguments with which it is
82
+ invoked, preventing repeated identical requests from invoking the underlying
83
+ OpenAI API repeatedly.
84
+ - Because `post` is replaced on the instance, the cache is by-default tied to
85
+ the MAsyncOpenAI instance (per-instance cache).
86
+ - Any initialization arguments are passed unchanged to AsyncOpenAI.__init__.
87
+
88
+ Example
89
+ m = MAsyncOpenAI(api_key="...", model="gpt-4")
90
+ r1 = await m.post("Hello") # executes API call and caches result
91
+ r2 = await m.post("Hello") # returns cached result (no API call)
92
+ """
93
+
94
+ def __init__(self, *args, cache=True, **kwargs):
95
+ super().__init__(*args, **kwargs)
96
+ self._orig_post = self.post
97
+ if cache:
98
+ self.set_cache(cache)
99
+
100
+ def set_cache(self, cache: bool) -> None:
101
+ """Enable or disable caching of the post method."""
102
+ if cache and self.post == self._orig_post:
103
+ self.post = memoize(self._orig_post) # type: ignore
104
+ elif not cache and self.post != self._orig_post:
105
+ self.post = self._orig_post
106
+
107
+ return MAsyncOpenAI
108
+
109
+
110
+ # Cache the classes so they're only created once
111
+ _MOpenAI_class = None
112
+ _MAsyncOpenAI_class = None
113
+
114
+
115
+ def MOpenAI(*args, **kwargs):
116
+ """Factory function for MOpenAI that lazily loads openai module."""
117
+ global _MOpenAI_class
118
+ if _MOpenAI_class is None:
119
+ _MOpenAI_class = _get_mopenai_class()
120
+ return _MOpenAI_class(*args, **kwargs)
121
+
122
+
123
+ def MAsyncOpenAI(*args, **kwargs):
124
+ """Factory function for MAsyncOpenAI that lazily loads openai module."""
125
+ global _MAsyncOpenAI_class
126
+ if _MAsyncOpenAI_class is None:
127
+ _MAsyncOpenAI_class = _get_masyncopenai_class()
128
+ return _MAsyncOpenAI_class(*args, **kwargs)
llm_utils/lm/signature.py CHANGED
@@ -5,14 +5,25 @@ This module provides a declarative way to define LLM input/output schemas
5
5
  with field descriptions and type annotations.
6
6
  """
7
7
 
8
- from typing import Any, Dict, List, Type, get_type_hints, Annotated, get_origin, get_args, cast
9
- from pydantic import BaseModel, Field
10
8
  import inspect
9
+ from typing import (
10
+ Annotated,
11
+ Any,
12
+ Dict,
13
+ List,
14
+ Type,
15
+ cast,
16
+ get_args,
17
+ get_origin,
18
+ get_type_hints,
19
+ )
20
+
21
+ from pydantic import BaseModel, Field
11
22
 
12
23
 
13
24
  class _FieldProxy:
14
25
  """Proxy that stores field information while appearing type-compatible."""
15
-
26
+
16
27
  def __init__(self, field_type: str, desc: str = "", **kwargs):
17
28
  self.field_type = field_type # 'input' or 'output'
18
29
  self.desc = desc
@@ -21,12 +32,12 @@ class _FieldProxy:
21
32
 
22
33
  def InputField(desc: str = "", **kwargs) -> Any:
23
34
  """Create an input field descriptor."""
24
- return cast(Any, _FieldProxy('input', desc=desc, **kwargs))
35
+ return cast(Any, _FieldProxy("input", desc=desc, **kwargs))
25
36
 
26
37
 
27
38
  def OutputField(desc: str = "", **kwargs) -> Any:
28
- """Create an output field descriptor."""
29
- return cast(Any, _FieldProxy('output', desc=desc, **kwargs))
39
+ """Create an output field descriptor."""
40
+ return cast(Any, _FieldProxy("output", desc=desc, **kwargs))
30
41
 
31
42
 
32
43
  # Type aliases for cleaner syntax
@@ -42,19 +53,19 @@ def Output(desc: str = "", **kwargs) -> Any:
42
53
 
43
54
  class SignatureMeta(type):
44
55
  """Metaclass for Signature that processes field annotations."""
45
-
56
+
46
57
  def __new__(cls, name, bases, namespace, **kwargs):
47
58
  # Get type hints for this class
48
- annotations = namespace.get('__annotations__', {})
49
-
59
+ annotations = namespace.get("__annotations__", {})
60
+
50
61
  # Store field information
51
62
  input_fields = {}
52
63
  output_fields = {}
53
-
64
+
54
65
  for field_name, field_type in annotations.items():
55
66
  field_value = namespace.get(field_name)
56
67
  field_desc = None
57
-
68
+
58
69
  # Handle Annotated[Type, Field(...)] syntax using get_origin/get_args
59
70
  if get_origin(field_type) is Annotated:
60
71
  # Extract args from Annotated type
@@ -67,163 +78,163 @@ class SignatureMeta(type):
67
78
  if isinstance(metadata, _FieldProxy):
68
79
  field_desc = metadata
69
80
  break
70
-
81
+
71
82
  # Handle old syntax with direct assignment
72
83
  if field_desc is None and isinstance(field_value, _FieldProxy):
73
84
  field_desc = field_value
74
-
85
+
75
86
  # Store field information
76
- if field_desc and field_desc.field_type == 'input':
87
+ if field_desc and field_desc.field_type == "input":
77
88
  input_fields[field_name] = {
78
- 'type': field_type,
79
- 'desc': field_desc.desc,
80
- **field_desc.kwargs
89
+ "type": field_type,
90
+ "desc": field_desc.desc,
91
+ **field_desc.kwargs,
81
92
  }
82
- elif field_desc and field_desc.field_type == 'output':
93
+ elif field_desc and field_desc.field_type == "output":
83
94
  output_fields[field_name] = {
84
- 'type': field_type,
85
- 'desc': field_desc.desc,
86
- **field_desc.kwargs
95
+ "type": field_type,
96
+ "desc": field_desc.desc,
97
+ **field_desc.kwargs,
87
98
  }
88
-
99
+
89
100
  # Store in class attributes
90
- namespace['_input_fields'] = input_fields
91
- namespace['_output_fields'] = output_fields
92
-
101
+ namespace["_input_fields"] = input_fields
102
+ namespace["_output_fields"] = output_fields
103
+
93
104
  return super().__new__(cls, name, bases, namespace)
94
105
 
95
106
 
96
107
  class Signature(metaclass=SignatureMeta):
97
108
  """Base class for defining LLM signatures with input and output fields."""
98
-
99
- _input_fields: Dict[str, Dict[str, Any]] = {}
100
- _output_fields: Dict[str, Dict[str, Any]] = {}
101
-
109
+
110
+ _input_fields: dict[str, dict[str, Any]] = {}
111
+ _output_fields: dict[str, dict[str, Any]] = {}
112
+
102
113
  def __init__(self, **kwargs):
103
114
  """Initialize signature with field values."""
104
115
  for field_name, value in kwargs.items():
105
116
  setattr(self, field_name, value)
106
-
117
+
107
118
  @classmethod
108
119
  def get_instruction(cls) -> str:
109
120
  """Generate instruction text from docstring and field descriptions."""
110
121
  instruction = cls.__doc__ or "Complete the following task."
111
122
  instruction = instruction.strip()
112
-
123
+
113
124
  # Add input field descriptions
114
125
  if cls._input_fields:
115
126
  instruction += "\n\n**Input Fields:**\n"
116
127
  for field_name, field_info in cls._input_fields.items():
117
- desc = field_info.get('desc', '')
118
- field_type = field_info['type']
119
- type_str = getattr(field_type, '__name__', str(field_type))
128
+ desc = field_info.get("desc", "")
129
+ field_type = field_info["type"]
130
+ type_str = getattr(field_type, "__name__", str(field_type))
120
131
  instruction += f"- {field_name} ({type_str}): {desc}\n"
121
-
132
+
122
133
  # Add output field descriptions
123
134
  if cls._output_fields:
124
135
  instruction += "\n**Output Fields:**\n"
125
136
  for field_name, field_info in cls._output_fields.items():
126
- desc = field_info.get('desc', '')
127
- field_type = field_info['type']
128
- type_str = getattr(field_type, '__name__', str(field_type))
137
+ desc = field_info.get("desc", "")
138
+ field_type = field_info["type"]
139
+ type_str = getattr(field_type, "__name__", str(field_type))
129
140
  instruction += f"- {field_name} ({type_str}): {desc}\n"
130
-
141
+
131
142
  return instruction
132
-
143
+
133
144
  @classmethod
134
- def get_input_model(cls) -> Type[BaseModel]:
145
+ def get_input_model(cls) -> type[BaseModel]:
135
146
  """Generate Pydantic input model from input fields."""
136
147
  if not cls._input_fields:
137
- raise ValueError(f"Signature {cls.__name__} must have at least one input field")
138
-
148
+ raise ValueError(
149
+ f"Signature {cls.__name__} must have at least one input field"
150
+ )
151
+
139
152
  fields = {}
140
153
  annotations = {}
141
-
154
+
142
155
  for field_name, field_info in cls._input_fields.items():
143
- field_type = field_info['type']
144
- desc = field_info.get('desc', '')
145
-
156
+ field_type = field_info["type"]
157
+ desc = field_info.get("desc", "")
158
+
146
159
  # Create Pydantic field
147
- field_kwargs = {k: v for k, v in field_info.items()
148
- if k not in ['type', 'desc']}
160
+ field_kwargs = {
161
+ k: v for k, v in field_info.items() if k not in ["type", "desc"]
162
+ }
149
163
  if desc:
150
- field_kwargs['description'] = desc
151
-
164
+ field_kwargs["description"] = desc
165
+
152
166
  fields[field_name] = Field(**field_kwargs) if field_kwargs else Field()
153
167
  annotations[field_name] = field_type
154
-
168
+
155
169
  # Create dynamic Pydantic model
156
170
  input_model = type(
157
171
  f"{cls.__name__}Input",
158
172
  (BaseModel,),
159
- {
160
- '__annotations__': annotations,
161
- **fields
162
- }
173
+ {"__annotations__": annotations, **fields},
163
174
  )
164
-
175
+
165
176
  return input_model
166
-
177
+
167
178
  @classmethod
168
- def get_output_model(cls) -> Type[BaseModel]:
179
+ def get_output_model(cls) -> type[BaseModel]:
169
180
  """Generate Pydantic output model from output fields."""
170
181
  if not cls._output_fields:
171
- raise ValueError(f"Signature {cls.__name__} must have at least one output field")
172
-
182
+ raise ValueError(
183
+ f"Signature {cls.__name__} must have at least one output field"
184
+ )
185
+
173
186
  fields = {}
174
187
  annotations = {}
175
-
188
+
176
189
  for field_name, field_info in cls._output_fields.items():
177
- field_type = field_info['type']
178
- desc = field_info.get('desc', '')
179
-
190
+ field_type = field_info["type"]
191
+ desc = field_info.get("desc", "")
192
+
180
193
  # Create Pydantic field
181
- field_kwargs = {k: v for k, v in field_info.items()
182
- if k not in ['type', 'desc']}
194
+ field_kwargs = {
195
+ k: v for k, v in field_info.items() if k not in ["type", "desc"]
196
+ }
183
197
  if desc:
184
- field_kwargs['description'] = desc
185
-
198
+ field_kwargs["description"] = desc
199
+
186
200
  fields[field_name] = Field(**field_kwargs) if field_kwargs else Field()
187
201
  annotations[field_name] = field_type
188
-
202
+
189
203
  # Create dynamic Pydantic model
190
204
  output_model = type(
191
205
  f"{cls.__name__}Output",
192
206
  (BaseModel,),
193
- {
194
- '__annotations__': annotations,
195
- **fields
196
- }
207
+ {"__annotations__": annotations, **fields},
197
208
  )
198
-
209
+
199
210
  return output_model
200
-
211
+
201
212
  def format_input(self, **kwargs) -> str:
202
213
  """Format input fields as a string."""
203
214
  input_data = {}
204
-
215
+
205
216
  # Collect input field values
206
217
  for field_name in self._input_fields:
207
218
  if field_name in kwargs:
208
219
  input_data[field_name] = kwargs[field_name]
209
220
  elif hasattr(self, field_name):
210
221
  input_data[field_name] = getattr(self, field_name)
211
-
222
+
212
223
  # Format as key-value pairs
213
224
  formatted_lines = []
214
225
  for field_name, value in input_data.items():
215
226
  field_info = self._input_fields[field_name]
216
- desc = field_info.get('desc', '')
227
+ desc = field_info.get("desc", "")
217
228
  if desc:
218
229
  formatted_lines.append(f"{field_name} ({desc}): {value}")
219
230
  else:
220
231
  formatted_lines.append(f"{field_name}: {value}")
221
-
222
- return '\n'.join(formatted_lines)
232
+
233
+ return "\n".join(formatted_lines)
223
234
 
224
235
 
225
236
  # Export functions for easier importing
226
- __all__ = ['Signature', 'InputField', 'OutputField', 'Input', 'Output']
237
+ __all__ = ["Signature", "InputField", "OutputField", "Input", "Output"]
227
238
 
228
239
 
229
240
  # Example usage for testing
@@ -231,41 +242,43 @@ if __name__ == "__main__":
231
242
  # Define a signature like DSPy - using Annotated approach
232
243
  class FactJudge(Signature):
233
244
  """Judge if the answer is factually correct based on the context."""
234
-
245
+
235
246
  context: Annotated[str, Input("Context for the prediction")]
236
247
  question: Annotated[str, Input("Question to be answered")]
237
248
  answer: Annotated[str, Input("Answer for the question")]
238
- factually_correct: Annotated[bool, Output("Is the answer factually correct based on the context?")]
239
-
249
+ factually_correct: Annotated[
250
+ bool, Output("Is the answer factually correct based on the context?")
251
+ ]
252
+
240
253
  # Alternative syntax still works but will show type warnings
241
254
  class FactJudgeOldSyntax(Signature):
242
255
  """Judge if the answer is factually correct based on the context."""
243
-
256
+
244
257
  context: str = InputField(desc="Context for the prediction") # type: ignore
245
258
  question: str = InputField(desc="Question to be answered") # type: ignore
246
259
  answer: str = InputField(desc="Answer for the question") # type: ignore
247
260
  factually_correct: bool = OutputField(desc="Is the answer factually correct based on the context?") # type: ignore
248
-
261
+
249
262
  # Test both signatures
250
263
  for judge_class in [FactJudge, FactJudgeOldSyntax]:
251
264
  print(f"\n=== Testing {judge_class.__name__} ===")
252
265
  print("Instruction:")
253
266
  print(judge_class.get_instruction())
254
-
267
+
255
268
  print("\nInput Model:")
256
269
  input_model = judge_class.get_input_model()
257
270
  print(input_model.model_json_schema())
258
-
271
+
259
272
  print("\nOutput Model:")
260
273
  output_model = judge_class.get_output_model()
261
274
  print(output_model.model_json_schema())
262
-
275
+
263
276
  # Test instance usage
264
277
  judge = judge_class()
265
278
  input_text = judge.format_input(
266
279
  context="The sky is blue during daytime.",
267
280
  question="What color is the sky?",
268
- answer="Blue"
281
+ answer="Blue",
269
282
  )
270
283
  print("\nFormatted Input:")
271
- print(input_text)
284
+ print(input_text)