ai-microcore 5.0.0.dev7__tar.gz → 5.0.0.dev8__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 (47) hide show
  1. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/PKG-INFO +1 -1
  2. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/__init__.py +2 -2
  3. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/_llm_functions.py +14 -4
  4. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/ai_func/__init__.py +2 -1
  5. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/file_storage.py +17 -14
  6. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/images.py +1 -1
  7. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/llm/google_genai.py +3 -2
  8. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/llm/openai.py +50 -15
  9. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/LICENSE +0 -0
  10. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/README.md +0 -0
  11. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/_env.py +0 -0
  12. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/_prepare_llm_args.py +0 -0
  13. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/ai_func/ai-func.json.j2 +0 -0
  14. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/ai_func/ai-func.pythonic.j2 +0 -0
  15. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/ai_func/ai-func.tag.j2 +0 -0
  16. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/ai_modules.py +0 -0
  17. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/configuration.py +0 -0
  18. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/embedding_db/__init__.py +0 -0
  19. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/embedding_db/chromadb.py +0 -0
  20. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/embedding_db/qdrant.py +0 -0
  21. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/file_cache.py +0 -0
  22. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/interactive_setup.py +0 -0
  23. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/json_parsing.py +0 -0
  24. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/llm/__init__.py +0 -0
  25. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/llm/anthropic.py +0 -0
  26. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/llm/local_llm.py +0 -0
  27. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/llm/local_transformers.py +0 -0
  28. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/llm/shared.py +0 -0
  29. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/llm_backends.py +0 -0
  30. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/lm_client.py +0 -0
  31. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/logging.py +0 -0
  32. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/mcp.py +0 -0
  33. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/message_types.py +0 -0
  34. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/metrics.py +0 -0
  35. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/presets.py +0 -0
  36. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/python.py +0 -0
  37. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/templating/__init__.py +0 -0
  38. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/templating/jinja2.py +0 -0
  39. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/text2speech/elevenlabs.py +0 -0
  40. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/tokenizing.py +0 -0
  41. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/types.py +0 -0
  42. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/ui.py +0 -0
  43. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/utils.py +0 -0
  44. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/wrappers/__init__.py +0 -0
  45. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/wrappers/llm_response_wrapper.py +0 -0
  46. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/microcore/wrappers/prompt_wrapper.py +0 -0
  47. {ai_microcore-5.0.0.dev7 → ai_microcore-5.0.0.dev8}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ai-microcore
3
- Version: 5.0.0.dev7
3
+ Version: 5.0.0.dev8
4
4
  Summary: # Minimalistic Foundation for AI Applications
5
5
  Keywords: llm,large language models,ai,similarity search,ai search,gpt,openai,framework,adapter,anthropic,google gemini,google vertex ai
6
6
  Author-email: Vitalii Stepanenko <mail@vitaliy.in>
@@ -79,7 +79,7 @@ def model_names() -> list[str]:
79
79
  Return a list of available model names from the default LLM client.
80
80
  """
81
81
  if env().default_client is None:
82
- raise ValueError("No default LLM client supporting models list configured.")
82
+ raise ValueError("No default LLM client is configured.")
83
83
  return env().default_client.model_names()
84
84
 
85
85
 
@@ -231,4 +231,4 @@ __all__ = [
231
231
  # "wrappers",
232
232
  ]
233
233
 
234
- __version__ = "5.0.0.dev7"
234
+ __version__ = "5.0.0dev8"
@@ -267,9 +267,7 @@ def llm(
267
267
  save_cache(cache_name, response)
268
268
  [h(response) for h in env().llm_after_handlers]
269
269
  if tries > 0:
270
- retry_params = dict(**kwargs)
271
- retry_params["retries"] = tries - 1
272
- setattr(response, "_retry_callback", lambda: llm(prompt, **retry_params))
270
+ setattr(response, "_retry_callback", lambda: llm(prompt, retries=tries - 1, **kwargs))
273
271
  if parse_json:
274
272
  parsing_params = parse_json if isinstance(parse_json, dict) else {}
275
273
  return response.parse_json(**parsing_params)
@@ -294,6 +292,13 @@ async def allm(
294
292
  parse_json (bool|dict):
295
293
  If True, parses response as JSON,
296
294
  alternatively non-empty dict can be used as parse_json arguments.
295
+ Default is False (no parsing).
296
+ file_cache (bool | str):
297
+ If True or non-empty string, enables file caching of LLM responses.
298
+ If string, it will be used as cache prefix.
299
+ When enabled, identical requests with identical parameters
300
+ will return cached responses instead of making new API calls.
301
+ Default is False (no caching).
297
302
  **kwargs: Parameters supported by the LLM API.
298
303
 
299
304
  See parameters supported by the OpenAI:
@@ -374,8 +379,13 @@ async def allm(
374
379
  if file_cache:
375
380
  delete_cache(cache_name)
376
381
  return await allm(
377
- prompt, retries=tries - 1, parse_json=parse_json, **kwargs
382
+ prompt,
383
+ retries=tries - 1,
384
+ parse_json=parse_json,
385
+ file_cache=file_cache,
386
+ **kwargs
378
387
  )
388
+ raise e
379
389
  return response
380
390
 
381
391
 
@@ -80,7 +80,7 @@ def func_metadata(func, name=None) -> Dict[str, Any]:
80
80
 
81
81
  # Add descriptions from parsed docstring to parameters
82
82
  for param in parsed_docstring.params:
83
- if param.arg_name in metadata.get("args", []):
83
+ if param.arg_name in metadata.get("args", {}):
84
84
  metadata["args"][param.arg_name]["docstr"] = param.description
85
85
 
86
86
  return metadata
@@ -190,6 +190,7 @@ def extract_tag_tool_params(
190
190
  if len(tags) > 1:
191
191
  if raise_errors:
192
192
  raise ValueError("Response contains multiple tags when only one expected")
193
+ logging.warning("Response contains multiple tags, but only the first one will be used.")
193
194
  tag, attrs, content = tags[0]
194
195
  return tag, [content], attrs
195
196
 
@@ -1,5 +1,15 @@
1
1
  """
2
- File storage functions
2
+ File storage functionality.
3
+
4
+
5
+ Provides a Storage class for file operations within a configured storage directory.
6
+ Supports automatic file numbering, backups, JSON serialization, encoding detection,
7
+ file copying, directory listing, etc.
8
+
9
+ Usage:
10
+ from microcore import storage
11
+ storage.write("data.txt", "content")
12
+ content = storage.read("data.txt")
3
13
  """
4
14
 
5
15
  import fnmatch
@@ -15,6 +25,7 @@ from ._env import config
15
25
  from .utils import file_link, list_files
16
26
 
17
27
  _missing = object()
28
+ """Sentinel value to distinguish between None and 'not provided'."""
18
29
 
19
30
 
20
31
  @dataclass
@@ -224,8 +235,6 @@ class Storage:
224
235
  if isinstance(content, bytes):
225
236
  if encoding is not None:
226
237
  logging.warning("Encoding is ignored when writing bytes content")
227
- if append:
228
- raise ValueError("Cannot append bytes content")
229
238
 
230
239
  if rewrite_existing is None:
231
240
  rewrite_existing = True
@@ -268,18 +277,12 @@ class Storage:
268
277
  if file_name != fn_incremented:
269
278
  os.rename(self.path / file_name, self.path / fn_incremented)
270
279
  (self.path / file_name).parent.mkdir(parents=True, exist_ok=True)
271
- if append:
272
- with (self.path / file_name).open(
273
- mode="a",
274
- encoding=encoding if not isinstance(content, bytes) else None,
275
- ) as file:
276
- file.write(content)
280
+ if isinstance(content, bytes):
281
+ with (self.path / file_name).open(mode="ab" if append else "wb") as f:
282
+ f.write(content)
277
283
  else:
278
- if isinstance(content, bytes):
279
- with (self.path / file_name).open(mode="wb") as file:
280
- file.write(content)
281
- else:
282
- (self.path / file_name).write_text(content, encoding=encoding)
284
+ with (self.path / file_name).open(mode="a" if append else "w", encoding=encoding) as f:
285
+ f.write(content)
283
286
  return file_name
284
287
 
285
288
  def clean(self, path: str | Path):
@@ -87,7 +87,7 @@ class FileImage(ImageInterface):
87
87
 
88
88
 
89
89
  class FileImageList(ImageListInterface):
90
- files: list[str] | None
90
+ files: list[str] | None = None
91
91
 
92
92
  def images(self) -> list[FileImage]:
93
93
  if not self.files:
@@ -163,8 +163,9 @@ class AsyncGoogleClient(BaseAsyncAIClient):
163
163
  def __init__(self, client: GoogleClient):
164
164
  self.sync_client = client
165
165
 
166
- async def load_models(self) -> dict:
167
- raise NotImplementedError
166
+ async def load_models(self, **kwargs) -> dict:
167
+ models = await self.sync_client.genai_client.aio.models.list(**kwargs)
168
+ return {model.name: model for model in models}
168
169
 
169
170
  async def generate(
170
171
  self,
@@ -42,8 +42,7 @@ class AsyncOpenAIClient(BaseAsyncAIClient):
42
42
  config = self.sync_client.config
43
43
  args, options = _prepare_llm_arguments(config, kwargs)
44
44
  if is_image_model(args["model"]):
45
- # Note: image generation is synchronous
46
- return _generate_image(
45
+ return await _generate_image_async(
47
46
  prompt,
48
47
  args,
49
48
  self.oai_client,
@@ -294,12 +293,8 @@ def _oai_image_response_to_images(response: ImagesResponse) -> list[Image]:
294
293
  return images
295
294
 
296
295
 
297
- def _generate_image(
298
- prompt,
299
- args,
300
- connection: openai.OpenAI | openai.AsyncOpenAI,
301
- options
302
- ) -> ImageGenerationResponse | None:
296
+ def _prepare_image_generation(prompt, args):
297
+ """Prepare prompt and images for image generation (shared logic)."""
303
298
  def convert_input_image(image: ImageInterface):
304
299
  if isinstance(image, FileImage):
305
300
  return open(image.file, "rb")
@@ -333,6 +328,32 @@ def _generate_image(
333
328
  if save and args.get("response_format", "b64_json") != "b64_json":
334
329
  raise ValueError("Only 'b64_json' response format is supported.")
335
330
 
331
+ return prompt, images, save
332
+
333
+
334
+ def _image_generation_response(
335
+ response: ImagesResponse,
336
+ save: bool,
337
+ options: dict,
338
+ ) -> ImageGenerationResponse | None:
339
+ check_for_errors(response)
340
+ images = _oai_image_response_to_images(response)
341
+ response_attrs = response.__dict__.copy()
342
+ result = make_image_generation_response(images, save, response_attrs)
343
+ for cb in options["callbacks"]:
344
+ cb(result)
345
+ return result
346
+
347
+
348
+ def _generate_image(
349
+ prompt,
350
+ args,
351
+ connection: openai.OpenAI,
352
+ options
353
+ ) -> ImageGenerationResponse | None:
354
+ """Synchronous version of image generation."""
355
+ prompt, images, save = _prepare_image_generation(prompt, args)
356
+
336
357
  if not images:
337
358
  response: ImagesResponse = connection.images.generate(prompt=prompt, **args)
338
359
  else:
@@ -341,10 +362,24 @@ def _generate_image(
341
362
  prompt=prompt,
342
363
  **args
343
364
  )
344
- check_for_errors(response)
345
- images = _oai_image_response_to_images(response)
346
- response_attrs = response.__dict__.copy()
347
- result = make_image_generation_response(images, save, response_attrs)
348
- for cb in options["callbacks"]:
349
- cb(result)
350
- return result
365
+ return _image_generation_response(response, save, options)
366
+
367
+
368
+ async def _generate_image_async(
369
+ prompt,
370
+ args,
371
+ connection: openai.AsyncOpenAI,
372
+ options
373
+ ) -> ImageGenerationResponse | None:
374
+ """Asynchronous version of image generation."""
375
+ prompt, images, save = _prepare_image_generation(prompt, args)
376
+
377
+ if not images:
378
+ response: ImagesResponse = await connection.images.generate(prompt=prompt, **args)
379
+ else:
380
+ response: ImagesResponse = await connection.images.edit(
381
+ image=images,
382
+ prompt=prompt,
383
+ **args
384
+ )
385
+ return _image_generation_response(response, save, options)