qtype 0.0.5__py3-none-any.whl → 0.0.7__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 (67) hide show
  1. qtype/commands/convert.py +18 -5
  2. qtype/commands/generate.py +16 -8
  3. qtype/commands/run.py +6 -83
  4. qtype/commands/serve.py +73 -0
  5. qtype/commands/validate.py +18 -8
  6. qtype/commands/visualize.py +87 -0
  7. qtype/commons/generate.py +9 -4
  8. qtype/converters/tools_from_module.py +69 -134
  9. qtype/converters/types.py +47 -1
  10. qtype/dsl/base_types.py +0 -1
  11. qtype/dsl/custom_types.py +73 -0
  12. qtype/dsl/document.py +27 -3
  13. qtype/dsl/domain_types.py +3 -0
  14. qtype/dsl/model.py +60 -73
  15. qtype/dsl/validator.py +20 -0
  16. qtype/interpreter/api.py +45 -12
  17. qtype/interpreter/chat/chat_api.py +237 -0
  18. qtype/interpreter/chat/file_conversions.py +57 -0
  19. qtype/interpreter/chat/vercel.py +314 -0
  20. qtype/interpreter/conversions.py +2 -0
  21. qtype/interpreter/steps/llm_inference.py +44 -19
  22. qtype/interpreter/streaming_helpers.py +123 -0
  23. qtype/interpreter/typing.py +29 -10
  24. qtype/interpreter/ui/404/index.html +1 -0
  25. qtype/interpreter/ui/404.html +1 -0
  26. qtype/interpreter/ui/_next/static/chunks/4bd1b696-cf72ae8a39fa05aa.js +1 -0
  27. qtype/interpreter/ui/_next/static/chunks/736-7fc606e244fedcb1.js +36 -0
  28. qtype/interpreter/ui/_next/static/chunks/964-ed4ab073db645007.js +1 -0
  29. qtype/interpreter/ui/_next/static/chunks/app/_not-found/page-e110d2a9d0a83d82.js +1 -0
  30. qtype/interpreter/ui/_next/static/chunks/app/layout-f8f02d19bf177f87.js +1 -0
  31. qtype/interpreter/ui/_next/static/chunks/app/page-c72e847e888e549d.js +1 -0
  32. qtype/interpreter/ui/_next/static/chunks/ba12c10f-22556063851a6df2.js +1 -0
  33. qtype/interpreter/ui/_next/static/chunks/framework-7c95b8e5103c9e90.js +1 -0
  34. qtype/interpreter/ui/_next/static/chunks/main-6d261b6c5d6fb6c2.js +1 -0
  35. qtype/interpreter/ui/_next/static/chunks/main-app-6fc6346bc8f7f163.js +1 -0
  36. qtype/interpreter/ui/_next/static/chunks/pages/_app-0a0020ddd67f79cf.js +1 -0
  37. qtype/interpreter/ui/_next/static/chunks/pages/_error-03529f2c21436739.js +1 -0
  38. qtype/interpreter/ui/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  39. qtype/interpreter/ui/_next/static/chunks/webpack-ebad980006f664ca.js +1 -0
  40. qtype/interpreter/ui/_next/static/css/cd7a636f069c2cbd.css +3 -0
  41. qtype/interpreter/ui/_next/static/media/569ce4b8f30dc480-s.p.woff2 +0 -0
  42. qtype/interpreter/ui/_next/static/media/747892c23ea88013-s.woff2 +0 -0
  43. qtype/interpreter/ui/_next/static/media/8d697b304b401681-s.woff2 +0 -0
  44. qtype/interpreter/ui/_next/static/media/93f479601ee12b01-s.p.woff2 +0 -0
  45. qtype/interpreter/ui/_next/static/media/9610d9e46709d722-s.woff2 +0 -0
  46. qtype/interpreter/ui/_next/static/media/ba015fad6dcf6784-s.woff2 +0 -0
  47. qtype/interpreter/ui/_next/static/qlZrsNT9fTEe92CsYpHBB/_buildManifest.js +1 -0
  48. qtype/interpreter/ui/_next/static/qlZrsNT9fTEe92CsYpHBB/_ssgManifest.js +1 -0
  49. qtype/interpreter/ui/favicon.ico +0 -0
  50. qtype/interpreter/ui/file.svg +1 -0
  51. qtype/interpreter/ui/globe.svg +1 -0
  52. qtype/interpreter/ui/index.html +1 -0
  53. qtype/interpreter/ui/index.txt +22 -0
  54. qtype/interpreter/ui/next.svg +1 -0
  55. qtype/interpreter/ui/vercel.svg +1 -0
  56. qtype/interpreter/ui/window.svg +1 -0
  57. qtype/loader.py +57 -8
  58. qtype/semantic/generate.py +17 -5
  59. qtype/semantic/model.py +16 -24
  60. qtype/semantic/visualize.py +485 -0
  61. {qtype-0.0.5.dist-info → qtype-0.0.7.dist-info}/METADATA +28 -20
  62. qtype-0.0.7.dist-info/RECORD +91 -0
  63. qtype-0.0.5.dist-info/RECORD +0 -50
  64. {qtype-0.0.5.dist-info → qtype-0.0.7.dist-info}/WHEEL +0 -0
  65. {qtype-0.0.5.dist-info → qtype-0.0.7.dist-info}/entry_points.txt +0 -0
  66. {qtype-0.0.5.dist-info → qtype-0.0.7.dist-info}/licenses/LICENSE +0 -0
  67. {qtype-0.0.5.dist-info → qtype-0.0.7.dist-info}/top_level.txt +0 -0
@@ -1,44 +1,22 @@
1
1
  import importlib
2
2
  import inspect
3
- from datetime import date, datetime, time
4
3
  from typing import Any, Type, Union, get_args, get_origin
5
4
 
6
5
  from pydantic import BaseModel
7
6
 
7
+ from qtype.converters.types import PYTHON_TYPE_TO_PRIMITIVE_TYPE
8
+ from qtype.dsl.base_types import PrimitiveTypeEnum
8
9
  from qtype.dsl.model import (
9
- ArrayTypeDefinition,
10
- ObjectTypeDefinition,
11
- PrimitiveTypeEnum,
10
+ CustomType,
12
11
  PythonFunctionTool,
13
12
  Variable,
14
13
  VariableType,
15
14
  )
16
15
 
17
- VARIABLE_TO_TYPE = {
18
- # VariableTypeEnum.audio: bytes, # TODO: Define a proper audio type
19
- PrimitiveTypeEnum.boolean: bool, # No boolean type in enum, using Python's
20
- PrimitiveTypeEnum.bytes: bytes,
21
- PrimitiveTypeEnum.date: date,
22
- PrimitiveTypeEnum.datetime: datetime,
23
- PrimitiveTypeEnum.int: int,
24
- # VariableTypeEnum.file: bytes, # TODO: Define a proper file type
25
- # VariableTypeEnum.image: bytes, # TODO: Define a proper image type
26
- PrimitiveTypeEnum.float: float,
27
- PrimitiveTypeEnum.text: str,
28
- PrimitiveTypeEnum.time: time,
29
- # VariableTypeEnum.video: bytes, # TODO: Define a proper video type
30
- }
31
-
32
- TYPE_TO_VARIABLE = {v: k for k, v in VARIABLE_TO_TYPE.items()}
33
-
34
- assert len(VARIABLE_TO_TYPE) == len(
35
- TYPE_TO_VARIABLE
36
- ), "Variable to type mapping is not one-to-one"
37
-
38
16
 
39
17
  def tools_from_module(
40
18
  module_path: str,
41
- ) -> list[PythonFunctionTool]:
19
+ ) -> tuple[list[PythonFunctionTool], list[CustomType]]:
42
20
  """
43
21
  Load tools from a Python module by introspecting its functions.
44
22
 
@@ -64,11 +42,14 @@ def tools_from_module(
64
42
  f"No public functions found in module '{module_path}'"
65
43
  )
66
44
 
45
+ custom_types: dict[str, CustomType] = {}
46
+
67
47
  # Create Tool instances from functions
68
- return [
69
- _create_tool_from_function(func_name, func_info)
48
+ tools = [
49
+ _create_tool_from_function(func_name, func_info, custom_types)
70
50
  for func_name, func_info in functions.items()
71
51
  ]
52
+ return (tools, list(custom_types.values()))
72
53
  except ImportError as e:
73
54
  raise ImportError(f"Cannot import module '{module_path}': {e}") from e
74
55
 
@@ -132,7 +113,9 @@ def _get_module_functions(
132
113
 
133
114
 
134
115
  def _create_tool_from_function(
135
- func_name: str, func_info: dict[str, Any]
116
+ func_name: str,
117
+ func_info: dict[str, Any],
118
+ custom_types: dict[str, CustomType],
136
119
  ) -> PythonFunctionTool:
137
120
  """
138
121
  Convert function metadata into a Tool instance.
@@ -154,8 +137,8 @@ def _create_tool_from_function(
154
137
  # Create input variables from function parameters
155
138
  input_variables = [
156
139
  Variable(
157
- id=p["name"],
158
- type=_map_python_type_to_variable_type(p["type"]),
140
+ id=func_name + "." + p["name"],
141
+ type=_map_python_type_to_variable_type(p["type"], custom_types),
159
142
  )
160
143
  for p in func_info["parameters"]
161
144
  ]
@@ -163,7 +146,9 @@ def _create_tool_from_function(
163
146
  # Create output variable based on return type
164
147
  tool_id = func_info["module"] + "." + func_name
165
148
 
166
- output_type = _map_python_type_to_variable_type(func_info["return_type"])
149
+ output_type = _map_python_type_to_variable_type(
150
+ func_info["return_type"], custom_types
151
+ )
167
152
 
168
153
  output_variable = Variable(
169
154
  id=f"{tool_id}.result",
@@ -181,89 +166,28 @@ def _create_tool_from_function(
181
166
  )
182
167
 
183
168
 
184
- def _map_type_to_dsl(
185
- pydantic_type: Type[Any], model_name: str
186
- ) -> VariableType | str:
187
- """
188
- Recursively maps a Python/Pydantic type to a DSL Type Definition.
189
-
190
- Args:
191
- pydantic_type: The type hint to map (e.g., str, list[int], MyPydanticModel).
192
- model_name: The name of the model being processed, for generating unique IDs.
193
-
194
- Returns:
195
- A PrimitiveTypeEnum member, an ObjectTypeDefinition, an ArrayTypeDefinition,
196
- or a string reference to another type.
197
- """
198
- origin = get_origin(pydantic_type)
199
- args = get_args(pydantic_type)
200
-
201
- # --- Handle Lists ---
202
- if origin in (list, list):
203
- if not args:
204
- raise TypeError(
205
- "List types must be parameterized, e.g., list[str]."
206
- )
207
-
208
- # Recursively map the inner type of the list
209
- inner_type = _map_type_to_dsl(args[0], model_name)
210
-
211
- # Create a unique ID for this specific array definition
212
- array_id = (
213
- f"{model_name}.{getattr(inner_type, 'id', str(inner_type))}_Array"
214
- )
215
-
216
- return ArrayTypeDefinition(
217
- id=array_id,
218
- type=inner_type,
219
- description=f"An array of {getattr(inner_type, 'id', inner_type)}.",
220
- )
221
-
222
- # --- Handle Unions (specifically for Optional[T]) ---
223
- if origin is Union:
224
- # Filter out NoneType to handle Optional[T]
225
- non_none_args = [arg for arg in args if arg is not type(None)]
226
- if len(non_none_args) == 1:
227
- return _map_type_to_dsl(non_none_args[0], model_name)
228
- else:
229
- # For more complex unions, you might decide on a specific handling strategy.
230
- # For now, we'll raise an error as it's ambiguous.
231
- raise TypeError(
232
- "Complex Union types are not supported for automatic conversion."
233
- )
234
-
235
- # --- Handle Nested Pydantic Models ---
236
- if inspect.isclass(pydantic_type) and issubclass(pydantic_type, BaseModel):
237
- # If it's a nested model, recursively call the main function.
238
- # This returns a full definition for the nested object.
239
- return pydantic_to_object_definition(pydantic_type)
240
-
241
- # --- Handle Primitive Types ---
242
- # This could be expanded with more sophisticated mapping.
243
-
244
- if pydantic_type in TYPE_TO_VARIABLE:
245
- return TYPE_TO_VARIABLE[pydantic_type]
246
-
247
- raise TypeError(f"Unsupported type for DSL conversion: {pydantic_type}")
248
-
249
-
250
- def pydantic_to_object_definition(
169
+ def _pydantic_to_custom_types(
251
170
  model_cls: Type[BaseModel],
252
- ) -> ObjectTypeDefinition:
171
+ custom_types: dict[str, CustomType],
172
+ ) -> str:
253
173
  """
254
- Converts a Pydantic BaseModel class into a QType ObjectTypeDefinition.
174
+ Converts a Pydantic BaseModel class into a QType CustomType.
175
+ This function extracts the model's fields and their types, converting them
176
+ into a CustomType definition.
255
177
 
256
- This function introspects the model's fields, recursively converting them
257
- into the appropriate DSL type definitions (primitive, object, or array).
178
+ If multiple nested types are found, they are recursively converted
179
+ into CustomType definitions.
258
180
 
259
181
  Args:
260
182
  model_cls: The Pydantic model class to convert.
261
183
 
262
184
  Returns:
263
- An ObjectTypeDefinition representing the Pydantic model.
185
+ A dictionary mapping field names to their corresponding CustomType definitions.
264
186
  """
265
187
  properties = {}
266
188
  model_name = model_cls.__name__
189
+ if model_name in custom_types:
190
+ return model_name # Already processed
267
191
 
268
192
  for field_name, field_info in model_cls.model_fields.items():
269
193
  # Use the annotation (the type hint) for the field
@@ -272,19 +196,35 @@ def pydantic_to_object_definition(
272
196
  raise TypeError(
273
197
  f"Field '{field_name}' in '{model_name}' must have a type hint."
274
198
  )
199
+ elif get_origin(field_type) is Union:
200
+ # Assume the union means it's optional
201
+ field_type = [
202
+ t for t in get_args(field_type) if t is not type(None)
203
+ ][0]
204
+ rv = _map_python_type_to_type_str(field_type, custom_types)
205
+ properties[field_name] = f"{rv}?"
206
+ elif get_origin(field_type) is list:
207
+ inner_type = get_args(field_type)[0]
208
+ rv = _map_python_type_to_type_str(inner_type, custom_types)
209
+ properties[field_name] = f"list[{rv}]"
210
+ else:
211
+ properties[field_name] = _map_python_type_to_type_str(
212
+ field_type, custom_types
213
+ )
275
214
 
276
- properties[field_name] = _map_type_to_dsl(field_type, model_name)
277
-
278
- return ObjectTypeDefinition(
215
+ # Add the custom type to the list
216
+ custom_types[model_name] = CustomType(
279
217
  id=model_name,
280
- description=model_cls.__doc__ or f"A definition for {model_name}.",
281
218
  properties=properties,
219
+ description=model_cls.__doc__ or f"Custom type for {model_name}",
282
220
  )
221
+ return model_name
283
222
 
284
223
 
285
224
  def _map_python_type_to_variable_type(
286
- python_type: type | None,
287
- ) -> VariableType:
225
+ python_type: Any,
226
+ custom_types: dict[str, CustomType],
227
+ ) -> str | VariableType:
288
228
  """
289
229
  Map Python type annotations to QType VariableType.
290
230
 
@@ -295,32 +235,27 @@ def _map_python_type_to_variable_type(
295
235
  VariableType compatible value.
296
236
  """
297
237
 
298
- if python_type is not None:
299
- if python_type in TYPE_TO_VARIABLE:
300
- return TYPE_TO_VARIABLE[python_type]
301
- else:
302
- # If the type is a Pydantic model, use its model_dump method
303
- # to convert it to a dictionary representation
304
- try:
305
- return pydantic_to_object_definition(python_type)
306
- except AttributeError:
307
- pass
238
+ if python_type in PYTHON_TYPE_TO_PRIMITIVE_TYPE:
239
+ return PYTHON_TYPE_TO_PRIMITIVE_TYPE[python_type]
240
+ elif python_type in get_args(VariableType):
241
+ # If it's a domain type, return its name
242
+ return python_type
243
+ elif inspect.isclass(python_type) and issubclass(python_type, BaseModel):
244
+ # If it's a Pydantic model, create or retrieve its CustomType definition
245
+ return _pydantic_to_custom_types(python_type, custom_types)
308
246
  raise ValueError(
309
247
  f"Unsupported Python type '{python_type}' for VariableType mapping"
310
248
  )
311
249
 
312
250
 
313
- VARIABLE_TO_TYPE = {
314
- # VariableTypeEnum.audio: bytes, # TODO: Define a proper audio type
315
- PrimitiveTypeEnum.boolean: bool, # No boolean type in enum, using Python's
316
- PrimitiveTypeEnum.bytes: bytes,
317
- PrimitiveTypeEnum.date: date,
318
- PrimitiveTypeEnum.datetime: datetime,
319
- PrimitiveTypeEnum.int: int,
320
- # VariableTypeEnum.file: bytes, # TODO: Define a proper file type
321
- # VariableTypeEnum.image: bytes, # TODO: Define a proper image type
322
- PrimitiveTypeEnum.float: float,
323
- PrimitiveTypeEnum.text: str,
324
- PrimitiveTypeEnum.time: time,
325
- # VariableTypeEnum.video: bytes, # TODO: Define a proper video type
326
- }
251
+ def _map_python_type_to_type_str(
252
+ python_type: Any,
253
+ custom_types: dict[str, CustomType],
254
+ ) -> str:
255
+ var_type = _map_python_type_to_variable_type(python_type, custom_types)
256
+ if isinstance(var_type, PrimitiveTypeEnum):
257
+ return var_type.value
258
+ elif inspect.isclass(python_type):
259
+ return python_type.__name__
260
+ else:
261
+ return str(python_type)
qtype/converters/types.py CHANGED
@@ -1,3 +1,5 @@
1
+ from datetime import date, datetime, time
2
+
1
3
  from qtype.dsl.base_types import PrimitiveTypeEnum
2
4
 
3
5
  """
@@ -13,8 +15,52 @@ PRIMITIVE_TO_PYTHON_TYPE = {
13
15
  PrimitiveTypeEnum.file: bytes, # Use bytes for file content
14
16
  PrimitiveTypeEnum.float: float,
15
17
  PrimitiveTypeEnum.image: bytes, # Use bytes for image data
16
- PrimitiveTypeEnum.number: float, # Use float for number representation
17
18
  PrimitiveTypeEnum.text: str,
18
19
  PrimitiveTypeEnum.time: str, # Use str for time representation
19
20
  PrimitiveTypeEnum.video: bytes, # Use bytes for video data
20
21
  }
22
+
23
+ PYTHON_TYPE_TO_PRIMITIVE_TYPE = {
24
+ bytes: PrimitiveTypeEnum.file,
25
+ bool: PrimitiveTypeEnum.boolean,
26
+ str: PrimitiveTypeEnum.text,
27
+ int: PrimitiveTypeEnum.int,
28
+ float: PrimitiveTypeEnum.float,
29
+ date: PrimitiveTypeEnum.date,
30
+ datetime: PrimitiveTypeEnum.datetime,
31
+ time: PrimitiveTypeEnum.time,
32
+ # TODO: decide on internal representation for images, video, and audio, or use annotation/hinting
33
+ }
34
+
35
+ # def create_custom_type(model_cls: Type[BaseModel],) -> CustomType:
36
+ # """
37
+ # Create a CustomType from a Pydantic BaseModel.
38
+
39
+ # Args:
40
+ # type: The Pydantic BaseModel class.
41
+
42
+ # Returns:
43
+ # A CustomType instance representing the model.
44
+ # """
45
+
46
+ # properties = {}
47
+ # for field_name, field_info in model_cls.model_fields.items():
48
+ # # Use the annotation (the type hint) for the field
49
+ # field_type = field_info.annotation
50
+ # if field_type is None:
51
+ # raise TypeError(
52
+ # f"Field '{field_name}' in '{model_name}' must have a type hint."
53
+ # )
54
+ # origin = get_origin(field_type)
55
+
56
+ # if origin is Union:
57
+ # # Assume the union means it's optional
58
+
59
+
60
+ # return CustomType(
61
+ # id=type.__name__,
62
+ # properties={
63
+ # name: python_type_to_variable_type(field.type_)
64
+ # for name, field in type.__fields__.items()
65
+ # },
66
+ # )
qtype/dsl/base_types.py CHANGED
@@ -19,7 +19,6 @@ class PrimitiveTypeEnum(str, Enum):
19
19
  file = "file"
20
20
  float = "float"
21
21
  image = "image"
22
- number = "number"
23
22
  text = "text"
24
23
  time = "time"
25
24
  video = "video"
@@ -0,0 +1,73 @@
1
+ from typing import Any, ForwardRef, Type, Union
2
+
3
+ from pydantic import BaseModel, create_model
4
+
5
+ from qtype.converters.types import PRIMITIVE_TO_PYTHON_TYPE
6
+
7
+ # --- This would be in your interpreter's logic ---
8
+
9
+
10
+ def build_dynamic_types(
11
+ type_definitions: list[dict]
12
+ ) -> dict[str, Type[BaseModel]]:
13
+ """
14
+ Parses a list of simplified type definitions and dynamically creates
15
+ Pydantic BaseModel classes. It handles out-of-order definitions using
16
+ a two-pass approach with ForwardRef.
17
+ """
18
+ created_models: dict[str, Type[BaseModel]] = {}
19
+
20
+ PRIMITIVE_MAP = {k.value: v for k, v in PRIMITIVE_TO_PYTHON_TYPE.items()}
21
+
22
+ def _parse_type_string(type_str: str) -> tuple[Any, bool]:
23
+ """
24
+ Parses a type string and returns the resolved type and a boolean
25
+ indicating if the type is optional.
26
+ """
27
+ is_optional = False
28
+ if type_str.endswith("?"):
29
+ is_optional = True
30
+ type_str = type_str[:-1]
31
+
32
+ if type_str.startswith("list[") and type_str.endswith("]"):
33
+ inner_type_name = type_str[5:-1]
34
+ inner_type, _ = _parse_type_string(inner_type_name)
35
+ resolved_type = list[inner_type]
36
+ elif type_str in PRIMITIVE_MAP:
37
+ resolved_type = PRIMITIVE_MAP[type_str]
38
+ elif type_str in created_models:
39
+ resolved_type = created_models[type_str]
40
+ else:
41
+ # If the type isn't defined yet, create a ForwardRef placeholder.
42
+ # This is the key to handling out-of-order definitions.
43
+ resolved_type = ForwardRef(type_str)
44
+
45
+ if is_optional:
46
+ return Union[resolved_type, None], True
47
+
48
+ return resolved_type, False
49
+
50
+ # --- Pass 1: Create all models, using ForwardRef for unresolved types ---
51
+ for type_def in type_definitions:
52
+ model_name = type_def["id"]
53
+ field_definitions = {}
54
+
55
+ if "properties" in type_def:
56
+ for field_name, type_str in type_def["properties"].items():
57
+ resolved_type, is_optional = _parse_type_string(type_str)
58
+ default_value = None if is_optional else ...
59
+ field_definitions[field_name] = (resolved_type, default_value)
60
+
61
+ # Pass the created_models dict as the local namespace for resolution
62
+ DynamicModel = create_model(
63
+ model_name, **field_definitions, __localns=created_models
64
+ )
65
+ created_models[model_name] = DynamicModel
66
+
67
+ # --- Pass 2: Resolve all ForwardRef placeholders ---
68
+ # This rebuilds the models, linking the placeholders to the actual classes.
69
+ # We pass `created_models` as the types_namespace for resolution in Pydantic V2.
70
+ for model in created_models.values():
71
+ model.model_rebuild(_types_namespace=created_models)
72
+
73
+ return created_models
qtype/dsl/document.py CHANGED
@@ -3,6 +3,7 @@ from pathlib import Path
3
3
  from typing import Any, Type, Union, get_args, get_origin
4
4
 
5
5
  import qtype.dsl.model as dsl
6
+ from qtype.dsl.base_types import PrimitiveTypeEnum
6
7
 
7
8
 
8
9
  def _format_type_name(field_type: Any) -> str:
@@ -56,8 +57,13 @@ def generate_class_docstring(output_file: Path, cls: Type[Any]) -> None:
56
57
  class_name = cls.__name__
57
58
  docstring = cls.__doc__ or "No documentation available."
58
59
 
60
+ # new lines in the docstring often have indentation, which we want to remove
61
+ docstring = "\n".join(
62
+ line.strip() for line in docstring.splitlines() if line.strip()
63
+ )
64
+
59
65
  with output_file.open("w", encoding="utf-8") as file:
60
- file.write(f"# {class_name}\n\n{docstring}\n\n## Members\n")
66
+ file.write(f"### {class_name}\n\n{docstring}\n\n")
61
67
 
62
68
  # Handle Pydantic models by checking for model_fields
63
69
  if hasattr(cls, "model_fields") and hasattr(cls, "__annotations__"):
@@ -93,8 +99,6 @@ def generate_class_docstring(output_file: Path, cls: Type[Any]) -> None:
93
99
  )
94
100
  file.write(f"- **{name}**: {member_doc}\n")
95
101
 
96
- file.write("\n---\n")
97
-
98
102
 
99
103
  def generate_documentation(output_prefix: Path) -> None:
100
104
  """Generates markdown documentation for all DSL classes.
@@ -102,7 +106,27 @@ def generate_documentation(output_prefix: Path) -> None:
102
106
  Args:
103
107
  output_prefix (Path): The directory where the documentation files will be saved.
104
108
  """
109
+ # erase everything in the output directory
110
+ output_prefix.mkdir(parents=True, exist_ok=True)
111
+ for item in output_prefix.iterdir():
112
+ if item.is_dir():
113
+ for subitem in item.iterdir():
114
+ if subitem.is_file():
115
+ subitem.unlink()
116
+ item.rmdir()
117
+ elif item.is_file():
118
+ item.unlink()
119
+
105
120
  # Get all classes from the DSL model module
106
121
  for name, cls in inspect.getmembers(dsl, inspect.isclass): # noqa: F821
107
122
  if cls.__module__ == dsl.__name__ and not name.startswith("_"):
108
123
  generate_class_docstring(output_prefix / f"{name}.md", cls)
124
+ generate_class_docstring(
125
+ output_prefix / "PrimitiveTypeEnum.md", PrimitiveTypeEnum
126
+ )
127
+
128
+ for name, cls in inspect.getmembers(dsl.domain_types, inspect.isclass): # noqa: F821
129
+ if cls.__module__ == dsl.domain_types.__name__ and not name.startswith(
130
+ "_"
131
+ ):
132
+ generate_class_docstring(output_prefix / f"{name}.md", cls)
qtype/dsl/domain_types.py CHANGED
@@ -41,6 +41,9 @@ class ChatContent(StrictBaseModel):
41
41
  ...,
42
42
  description="The actual content, which can be a string, image data, etc.",
43
43
  )
44
+ mime_type: str | None = Field(
45
+ default=None, description="The MIME type of the content, if known."
46
+ )
44
47
 
45
48
 
46
49
  class ChatMessage(StrictBaseModel):