kgmodule-utils 0.4.2__tar.gz → 0.4.3__tar.gz

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 (27) hide show
  1. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/PKG-INFO +1 -1
  2. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/pyproject.toml +1 -1
  3. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/__init__.py +1 -1
  4. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/synthesis/_image.py +38 -3
  5. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/worker/client.py +18 -10
  6. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/worker/ops.py +22 -7
  7. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/LICENSE +0 -0
  8. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/README.md +0 -0
  9. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/embed.py +0 -0
  10. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/embedder.py +0 -0
  11. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/extractor.py +0 -0
  12. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/module.py +0 -0
  13. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/pipeline.py +0 -0
  14. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/py.typed +0 -0
  15. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/retrieval/__init__.py +0 -0
  16. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/retrieval/hits.py +0 -0
  17. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/semantic.py +0 -0
  18. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/snapshots/__init__.py +0 -0
  19. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/snapshots/manager.py +0 -0
  20. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/snapshots/models.py +0 -0
  21. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/specs.py +0 -0
  22. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/store.py +0 -0
  23. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/synthesis/__init__.py +0 -0
  24. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/synthesis/_config.py +0 -0
  25. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/synthesis/_text.py +0 -0
  26. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/synthesis/factory.py +0 -0
  27. {kgmodule_utils-0.4.2 → kgmodule_utils-0.4.3}/src/kg_utils/worker/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kgmodule-utils
3
- Version: 0.4.2
3
+ Version: 0.4.3
4
4
  Summary: Shared types, graph store, semantic index, and pipeline base for the KGModule SDK
5
5
  License: Elastic-2.0
6
6
  License-File: LICENSE
@@ -10,7 +10,7 @@ build-backend = "poetry.core.masonry.api"
10
10
 
11
11
  [project]
12
12
  name = "kgmodule-utils"
13
- version = "0.4.2"
13
+ version = "0.4.3"
14
14
  description = "Shared types, graph store, semantic index, and pipeline base for the KGModule SDK"
15
15
  readme = "README.md"
16
16
  license = { text = "Elastic-2.0" }
@@ -24,4 +24,4 @@ Optional extras
24
24
  pip install 'kgmodule-utils[synthesis-mflux]' # + mflux (Apple Silicon local gen)
25
25
  """
26
26
 
27
- __version__ = "0.4.2"
27
+ __version__ = "0.4.3"
@@ -51,6 +51,29 @@ _GPT_IMAGE_SIZES: dict[str, str] = {
51
51
  }
52
52
 
53
53
 
54
+ # ---------------------------------------------------------------------------
55
+ # Size parsing
56
+ # ---------------------------------------------------------------------------
57
+
58
+
59
+ def _parse_size(size: str | None) -> tuple[int, int] | None:
60
+ """Parse an explicit ``"WIDTHxHEIGHT"`` string into an ``(width, height)`` pair.
61
+
62
+ :param size: Size string such as ``"768x512"`` (case-insensitive ``x``), or None.
63
+ :returns: ``(width, height)`` when *size* parses to two positive ints, else None.
64
+ """
65
+ if not size:
66
+ return None
67
+ try:
68
+ w_str, h_str = size.lower().split("x", 1)
69
+ width, height = int(w_str), int(h_str)
70
+ except (ValueError, AttributeError):
71
+ return None
72
+ if width <= 0 or height <= 0:
73
+ return None
74
+ return width, height
75
+
76
+
54
77
  # ---------------------------------------------------------------------------
55
78
  # Synthesizer
56
79
  # ---------------------------------------------------------------------------
@@ -97,6 +120,7 @@ class ImageSynthesizer:
97
120
  seed: int | None = None,
98
121
  steps: int | None = None,
99
122
  model: str | None = None,
123
+ size: str | None = None,
100
124
  ) -> PILImage:
101
125
  """Generate an image and return a PIL Image.
102
126
 
@@ -105,6 +129,9 @@ class ImageSynthesizer:
105
129
  :param seed: Random seed for reproducibility (random int if omitted).
106
130
  :param steps: Override inference steps (mflux backends only; ignored for OpenAI).
107
131
  :param model: Override the configured model for this single call.
132
+ :param size: Explicit ``"WIDTHxHEIGHT"`` override (mflux backends only). When given
133
+ and valid, it takes priority over the *aspect_ratio* size table. OpenAI
134
+ backends ignore it because they accept only a fixed set of sizes.
108
135
  :returns: PIL Image.
109
136
  """
110
137
  cfg = self._cfg
@@ -118,6 +145,7 @@ class ImageSynthesizer:
118
145
  seed=seed,
119
146
  steps=effective_steps,
120
147
  aspect_ratio=aspect_ratio,
148
+ size=size,
121
149
  )
122
150
  elif cfg.backend == ImageBackend.MFLUX_SERVE:
123
151
  return self._generate_via_server(
@@ -126,6 +154,7 @@ class ImageSynthesizer:
126
154
  seed=seed,
127
155
  steps=effective_steps,
128
156
  aspect_ratio=aspect_ratio,
157
+ size=size,
129
158
  )
130
159
  else:
131
160
  return self._generate_openai(
@@ -142,6 +171,7 @@ class ImageSynthesizer:
142
171
  seed: int | None = None,
143
172
  steps: int | None = None,
144
173
  model: str | None = None,
174
+ size: str | None = None,
145
175
  ) -> str:
146
176
  """Generate an image and return it as a base64-encoded PNG string.
147
177
 
@@ -152,9 +182,12 @@ class ImageSynthesizer:
152
182
  :param seed: Random seed for reproducibility.
153
183
  :param steps: Override inference steps (mflux backends only).
154
184
  :param model: Override the configured model for this single call.
185
+ :param size: Explicit ``"WIDTHxHEIGHT"`` override (mflux backends only).
155
186
  :returns: Base64-encoded PNG string.
156
187
  """
157
- img = self.generate(prompt, aspect_ratio=aspect_ratio, seed=seed, steps=steps, model=model)
188
+ img = self.generate(
189
+ prompt, aspect_ratio=aspect_ratio, seed=seed, steps=steps, model=model, size=size
190
+ )
158
191
  buf = BytesIO()
159
192
  img.save(buf, format="PNG")
160
193
  return base64.b64encode(buf.getvalue()).decode()
@@ -171,8 +204,9 @@ class ImageSynthesizer:
171
204
  seed: int | None,
172
205
  steps: int,
173
206
  aspect_ratio: str,
207
+ size: str | None = None,
174
208
  ) -> PILImage:
175
- width, height = _MFLUX_SIZES.get(aspect_ratio, _MFLUX_SIZES["3:2"])
209
+ width, height = _parse_size(size) or _MFLUX_SIZES.get(aspect_ratio, _MFLUX_SIZES["3:2"])
176
210
  effective_seed = seed if seed is not None else random.randint(0, 2**31 - 1)
177
211
  flux = self._load_mflux(model)
178
212
  result = flux.generate_image(
@@ -194,11 +228,12 @@ class ImageSynthesizer:
194
228
  seed: int | None,
195
229
  steps: int,
196
230
  aspect_ratio: str,
231
+ size: str | None = None,
197
232
  ) -> PILImage:
198
233
  import httpx
199
234
  from PIL import Image # type: ignore[import-unresolved]
200
235
 
201
- width, height = _MFLUX_SIZES.get(aspect_ratio, _MFLUX_SIZES["3:2"])
236
+ width, height = _parse_size(size) or _MFLUX_SIZES.get(aspect_ratio, _MFLUX_SIZES["3:2"])
202
237
  payload: dict[str, Any] = {
203
238
  "prompt": prompt,
204
239
  "n": 1,
@@ -8,6 +8,7 @@ This module centralizes payload construction and response/error decoding for
8
8
  from __future__ import annotations
9
9
 
10
10
  import json
11
+ from typing import Any, cast
11
12
 
12
13
  import httpx
13
14
 
@@ -29,8 +30,9 @@ def _format_error_data(error_data: object) -> str:
29
30
  return str(decoded)
30
31
 
31
32
  if isinstance(error_data, dict):
32
- err_type = error_data.get("error_type", "Unknown")
33
- err_msg = error_data.get("error_message", str(error_data))
33
+ d = cast("dict[str, Any]", error_data)
34
+ err_type = d.get("error_type", "Unknown")
35
+ err_msg = d.get("error_message", str(error_data))
34
36
  return f"{err_type}: {err_msg}"
35
37
 
36
38
  return str(error_data)
@@ -41,10 +43,11 @@ def extract_worker_error(data: object) -> str | None:
41
43
  if not isinstance(data, dict):
42
44
  return str(data)
43
45
 
44
- if data.get("status") == "FAILED" or "error_type" in data:
45
- return _format_error_data(data.get("error", data))
46
+ d = cast("dict[str, Any]", data)
47
+ if d.get("status") == "FAILED" or "error_type" in d:
48
+ return _format_error_data(d.get("error", d))
46
49
 
47
- out = data.get("output")
50
+ out = d.get("output")
48
51
  if isinstance(out, dict) and isinstance(out.get("error"), str):
49
52
  return out["error"]
50
53
 
@@ -60,7 +63,8 @@ def decode_worker_response(data: object) -> dict:
60
63
  if not isinstance(data, dict):
61
64
  raise WorkerError(f"unexpected worker response type: {type(data).__name__}")
62
65
 
63
- out = data.get("output", data)
66
+ d2 = cast("dict[str, Any]", data)
67
+ out = d2.get("output", d2)
64
68
  if not isinstance(out, dict):
65
69
  raise WorkerError(f"unexpected worker output type: {type(out).__name__}")
66
70
  return out
@@ -133,14 +137,18 @@ class WorkerClient:
133
137
  image_backend: str = "",
134
138
  aspect_ratio: str = "3:2",
135
139
  steps: int | None = None,
140
+ size: str | None = None,
136
141
  ) -> tuple[str | None, str | None, str | None, str | None]:
137
- payload: dict = {"input": {"op": "imagine", "prompt": prompt, "aspect_ratio": aspect_ratio}}
142
+ inp: dict[str, Any] = {"op": "imagine", "prompt": prompt, "aspect_ratio": aspect_ratio}
138
143
  if image_backend:
139
- payload["input"]["image_backend"] = image_backend
144
+ inp["image_backend"] = image_backend
140
145
  if steps is not None:
141
- payload["input"]["steps"] = steps
146
+ inp["steps"] = steps
147
+ if size:
148
+ inp["size"] = size
142
149
  if self._secret:
143
- payload["input"]["secret"] = self._secret
150
+ inp["secret"] = self._secret
151
+ payload: dict[str, Any] = {"input": inp}
144
152
 
145
153
  try:
146
154
  data = self._post(
@@ -48,24 +48,39 @@ def handle_aux_ops(
48
48
  return {"error": "imagine requires a non-empty 'prompt'"}
49
49
 
50
50
  aspect = inp.get("aspect_ratio", "3:2")
51
+ size = inp.get("size")
51
52
  seed = inp.get("seed")
52
53
  steps = inp.get("steps")
53
54
  img_synth = image_synth_factory(inp.get("image_backend", ""))
55
+ seed_int = int(seed) if seed is not None else None
56
+ steps_int = int(steps) if steps is not None else None
54
57
 
55
58
  try:
56
- b64 = img_synth.generate_b64(
57
- prompt,
58
- aspect_ratio=aspect,
59
- seed=int(seed) if seed is not None else None,
60
- steps=int(steps) if steps is not None else None,
61
- )
62
- return {
59
+ if size is not None:
60
+ b64 = img_synth.generate_b64(
61
+ prompt,
62
+ aspect_ratio=aspect,
63
+ seed=seed_int,
64
+ steps=steps_int,
65
+ size=size,
66
+ )
67
+ else:
68
+ b64 = img_synth.generate_b64(
69
+ prompt,
70
+ aspect_ratio=aspect,
71
+ seed=seed_int,
72
+ steps=steps_int,
73
+ )
74
+ result = {
63
75
  "image_b64": b64,
64
76
  "prompt": prompt,
65
77
  "aspect_ratio": aspect,
66
78
  "image_model": img_synth._cfg.resolved_model(), # pylint: disable=protected-access
67
79
  "image_backend": img_synth._cfg.backend.value, # pylint: disable=protected-access
68
80
  }
81
+ if size is not None:
82
+ result["size"] = size
83
+ return result
69
84
  except Exception as exc: # noqa: BLE001 # pylint: disable=broad-exception-caught
70
85
  return {"error": f"image generation failed: {exc}"}
71
86
 
File without changes
File without changes