ai-microcore 4.0.0.dev3__tar.gz → 4.0.0.dev5__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 (41) hide show
  1. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/PKG-INFO +1 -1
  2. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/__init__.py +1 -1
  3. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/_env.py +8 -4
  4. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/_llm_functions.py +66 -5
  5. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/ai_func/__init__.py +1 -0
  6. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/configuration.py +13 -0
  7. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/embedding_db/__init__.py +1 -0
  8. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/file_storage.py +1 -0
  9. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/json_parsing.py +1 -1
  10. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/llm/_openai_llm_v0.py +1 -0
  11. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/llm/anthropic.py +9 -1
  12. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/llm/local_transformers.py +1 -1
  13. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/llm/shared.py +1 -0
  14. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/text2speech/elevenlabs.py +30 -0
  15. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/tokenizing.py +2 -1
  16. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/types.py +3 -2
  17. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/utils.py +4 -4
  18. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/wrappers/llm_response_wrapper.py +20 -2
  19. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/LICENSE +0 -0
  20. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/README.md +0 -0
  21. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/_prepare_llm_args.py +0 -0
  22. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/ai_func/ai-func.json.j2 +0 -0
  23. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/ai_func/ai-func.pythonic.j2 +0 -0
  24. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/ai_modules.py +0 -0
  25. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/embedding_db/chromadb.py +0 -0
  26. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/llm/__init__.py +0 -0
  27. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/llm/_openai_llm_v1.py +0 -0
  28. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/llm/google_genai.py +0 -0
  29. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/llm/google_vertex_ai.py +0 -0
  30. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/llm/local_llm.py +0 -0
  31. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/llm/openai_llm.py +0 -0
  32. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/logging.py +0 -0
  33. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/message_types.py +0 -0
  34. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/metrics.py +0 -0
  35. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/python.py +0 -0
  36. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/templating/__init__.py +0 -0
  37. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/templating/jinja2.py +0 -0
  38. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/ui.py +0 -0
  39. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/wrappers/__init__.py +0 -0
  40. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/microcore/wrappers/prompt_wrapper.py +0 -0
  41. {ai_microcore-4.0.0.dev3 → ai_microcore-4.0.0.dev5}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ai-microcore
3
- Version: 4.0.0.dev3
3
+ Version: 4.0.0.dev5
4
4
  Summary: # Minimalistic Foundation for AI Applications
5
5
  Keywords: llm,large language models,ai,similarity search,ai search,gpt,openai
6
6
  Author-email: Vitalii Stepanenko <mail@vitalii.in>
@@ -161,4 +161,4 @@ __all__ = [
161
161
  # "wrappers",
162
162
  ]
163
163
 
164
- __version__ = "4.0.0-dev3"
164
+ __version__ = "4.0.0-dev5"
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
6
6
  import jinja2
7
7
 
8
8
  from .embedding_db import AbstractEmbeddingDB
9
- from .configuration import Config, ApiType, LLMConfigError
9
+ from .configuration import Config, ApiType, LLMConfigError, EmbeddingDbType
10
10
  from .types import TplFunctionType, LLMAsyncFunctionType, LLMFunctionType
11
11
  from .templating.jinja2 import make_jinja2_env, make_tpl_function
12
12
  from .llm.openai_llm import make_llm_functions as make_openai_llm_functions
@@ -14,6 +14,7 @@ from .llm.local_llm import make_llm_functions as make_local_llm_functions
14
14
 
15
15
  if TYPE_CHECKING:
16
16
  from .wrappers.llm_response_wrapper import LLMResponse # noqa: F401
17
+ from transformers import PreTrainedModel, PreTrainedTokenizer # noqa: F401
17
18
 
18
19
 
19
20
  @dataclass
@@ -26,10 +27,10 @@ class Env:
26
27
  llm_before_handlers: list[callable] = field(default_factory=list)
27
28
  llm_after_handlers: list[callable] = field(default_factory=list)
28
29
  texts: AbstractEmbeddingDB = None
29
- model: "transformers.PreTrainedModel" = field(
30
+ model: "PreTrainedModel" = field(
30
31
  default=None, init=False, repr=False
31
32
  ) # noqa
32
- tokenizer: "transformers.PreTrainedTokenizer" = field( # noqa
33
+ tokenizer: "PreTrainedTokenizer" = field( # noqa
33
34
  default=None, init=False, repr=False
34
35
  )
35
36
 
@@ -134,7 +135,10 @@ class Env:
134
135
  )
135
136
 
136
137
  def init_similarity_search(self):
137
- if find_spec("chromadb") is not None:
138
+ if (
139
+ self.config.EMBEDDING_DB_TYPE == EmbeddingDbType.CHROMA
140
+ and find_spec("chromadb") is not None
141
+ ):
138
142
  from .embedding_db.chromadb import ChromaEmbeddingDB
139
143
 
140
144
  self.texts = ChromaEmbeddingDB(self.config)
@@ -1,17 +1,27 @@
1
+ import logging
1
2
  from datetime import datetime
2
3
 
3
4
  from .utils import run_parallel
4
- from .wrappers.llm_response_wrapper import LLMResponse
5
+ from .wrappers.llm_response_wrapper import LLMResponse, DictFromLLMResponse
5
6
  from .types import TPrompt
6
7
  from ._env import env
7
8
 
8
9
 
9
- def llm(prompt: TPrompt, **kwargs) -> str | LLMResponse:
10
+ def llm(
11
+ prompt: TPrompt,
12
+ retries: int = 0,
13
+ parse_json: bool | dict = False,
14
+ **kwargs
15
+ ) -> str | LLMResponse:
10
16
  """
11
17
  Request Large Language Model synchronously
12
18
 
13
19
  Args:
14
20
  prompt (str | Msg | dict | list[str | Msg | dict]): Text to send to LLM
21
+ retries (int): Number of retries in case of error
22
+ parse_json (bool|dict):
23
+ If True, parses response as JSON,
24
+ alternatively non-empty dict can be used as parse_json arguments
15
25
  **kwargs (dict): Parameters supported by the LLM API
16
26
 
17
27
  See parameters supported by the OpenAI:
@@ -40,7 +50,18 @@ def llm(prompt: TPrompt, **kwargs) -> str | LLMResponse:
40
50
  """
41
51
  [h(prompt, **kwargs) for h in env().llm_before_handlers]
42
52
  start = datetime.now()
43
- response = env().llm_function(prompt, **kwargs)
53
+ tries = retries + 1
54
+ while tries > 0:
55
+ try:
56
+ tries -= 1
57
+ response = env().llm_function(prompt, **kwargs)
58
+ break
59
+ except Exception as e:
60
+ if tries == 0:
61
+ raise e
62
+ logging.error(f"LLM error: {e}")
63
+ logging.info(f"Retrying... {tries} retries left")
64
+ continue
44
65
  try:
45
66
  response.gen_duration = (datetime.now() - start).total_seconds()
46
67
  if not env().config.SAVE_MEMORY:
@@ -48,15 +69,35 @@ def llm(prompt: TPrompt, **kwargs) -> str | LLMResponse:
48
69
  except AttributeError:
49
70
  ...
50
71
  [h(response) for h in env().llm_after_handlers]
72
+ if tries > 0:
73
+ retry_params = dict(**kwargs)
74
+ retry_params["retries"] = tries - 1
75
+ setattr(
76
+ response,
77
+ "_retry_callback",
78
+ lambda: llm(prompt, **retry_params)
79
+ )
80
+ if parse_json:
81
+ parsing_params = parse_json if isinstance(parse_json, dict) else {}
82
+ return response.parse_json(**parsing_params)
51
83
  return response
52
84
 
53
85
 
54
- async def allm(prompt: TPrompt, **kwargs) -> str | LLMResponse:
86
+ async def allm(
87
+ prompt: TPrompt,
88
+ retries: int = 0,
89
+ parse_json: bool | dict = False,
90
+ **kwargs
91
+ ) -> str | LLMResponse | DictFromLLMResponse:
55
92
  """
56
93
  Request Large Language Model asynchronously
57
94
 
58
95
  Args:
59
96
  prompt (str | Msg | dict | list[str | Msg | dict]): Text to send to LLM
97
+ retries (int): Number of retries in case of error
98
+ parse_json (bool|dict):
99
+ If True, parses response as JSON,
100
+ alternatively non-empty dict can be used as parse_json arguments
60
101
  **kwargs (dict): Parameters supported by the LLM API
61
102
 
62
103
  See parameters supported by the OpenAI:
@@ -87,7 +128,18 @@ async def allm(prompt: TPrompt, **kwargs) -> str | LLMResponse:
87
128
  """
88
129
  [h(prompt, **kwargs) for h in env().llm_before_handlers]
89
130
  start = datetime.now()
90
- response = await env().llm_async_function(prompt, **kwargs)
131
+ tries = retries + 1
132
+ while tries > 0:
133
+ try:
134
+ tries -= 1
135
+ response = await env().llm_async_function(prompt, **kwargs)
136
+ break
137
+ except Exception as e:
138
+ if tries == 0:
139
+ raise e
140
+ logging.error(f"LLM error: {e}")
141
+ logging.info(f"Retrying... {tries} retries left")
142
+ continue
91
143
  try:
92
144
  response.gen_duration = (datetime.now() - start).total_seconds()
93
145
  if not env().config.SAVE_MEMORY:
@@ -95,6 +147,15 @@ async def allm(prompt: TPrompt, **kwargs) -> str | LLMResponse:
95
147
  except AttributeError:
96
148
  ...
97
149
  [h(response) for h in env().llm_after_handlers]
150
+ if parse_json:
151
+ try:
152
+ parsing_params = parse_json if isinstance(parse_json, dict) else {}
153
+ return response.parse_json(**parsing_params)
154
+ except Exception as e:
155
+ if tries > 0:
156
+ logging.error(f"LLM error: {e}")
157
+ logging.info(f"Retrying... {tries} retries left")
158
+ return await allm(prompt, retries=tries - 1, parse_json=parse_json, **kwargs)
98
159
  return response
99
160
 
100
161
 
@@ -20,6 +20,7 @@ class AiFuncSyntax(str, Enum):
20
20
  def __str__(self):
21
21
  return self.value
22
22
 
23
+
23
24
  def func_arg_comments(func):
24
25
  func_source = dedent(inspect.getsource(func))
25
26
  module = ast.parse(func_source)
@@ -78,6 +78,17 @@ class ApiType(str, Enum):
78
78
  def is_local(api_type: str) -> bool:
79
79
  return api_type in (ApiType.FUNCTION, ApiType.TRANSFORMERS, ApiType.NONE)
80
80
 
81
+ def __str__(self):
82
+ return self.value
83
+
84
+
85
+ class EmbeddingDbType(str, Enum):
86
+ CHROMA = "chroma"
87
+ NONE = ""
88
+
89
+ def __str__(self):
90
+ return self.value
91
+
81
92
 
82
93
  _default_dotenv_loaded = False
83
94
 
@@ -373,6 +384,8 @@ class Config(LLMConfig):
373
384
 
374
385
  EMBEDDING_DB_PORT: str = from_env(default=None)
375
386
 
387
+ EMBEDDING_DB_TYPE: str = from_env(EmbeddingDbType.CHROMA)
388
+
376
389
  DEFAULT_ENCODING: str = from_env("utf-8")
377
390
  """Used in file system operations, utf-8 by default"""
378
391
 
@@ -9,6 +9,7 @@ from ..utils import ExtendedString
9
9
 
10
10
  INT32_MAX = 2**31 - 1 # 2147483647
11
11
 
12
+
12
13
  class SearchResults(list):
13
14
  def fit_to_token_size(
14
15
  self,
@@ -15,6 +15,7 @@ from .utils import file_link, list_files
15
15
 
16
16
  _missing = object()
17
17
 
18
+
18
19
  @dataclass
19
20
  class Storage:
20
21
 
@@ -102,7 +102,7 @@ def unwrap_json_substring(
102
102
  ...
103
103
 
104
104
  return (
105
- input_string[start : end + 1]
105
+ input_string[start: end + 1]
106
106
  if brace
107
107
  else input_string if return_original_on_fail else ""
108
108
  )
@@ -9,6 +9,7 @@ from ..wrappers.llm_response_wrapper import LLMResponse
9
9
  from ..utils import is_chat_model
10
10
  from .shared import prepare_callbacks
11
11
 
12
+
12
13
  def _get_chunk_text(chunk, mode_chat_model: bool):
13
14
  # Azure API gives first chunk with empty choices
14
15
  choice = chunk.choices[0] if len(chunk.choices) else {}
@@ -9,6 +9,7 @@ from ..types import LLMAsyncFunctionType, LLMFunctionType
9
9
  from ..wrappers.llm_response_wrapper import LLMResponse
10
10
  from .shared import prepare_callbacks
11
11
 
12
+
12
13
  def _get_chunk_text(chunk):
13
14
  return isinstance(chunk, ContentBlockDeltaEvent) and chunk.delta.text or ""
14
15
 
@@ -36,8 +37,15 @@ def _process_streamed_response(response, callbacks: list[callable]):
36
37
 
37
38
 
38
39
  def _prepare_llm_arguments(config: Config, kwargs: dict):
39
- args = {"max_tokens": 1024, **config.LLM_DEFAULT_ARGS, **kwargs}
40
+ args = {**config.LLM_DEFAULT_ARGS, **kwargs}
40
41
  args["model"] = args.get("model", config.MODEL)
42
+ if "max_tokens" not in args:
43
+ if "claude-3-5-sonnet" in args["model"]:
44
+ args["max_tokens"] = 8192
45
+ elif "claude-3-7-sonnet" in args["model"]:
46
+ args["max_tokens"] = 16384
47
+ else:
48
+ args["max_tokens"] = 4096
41
49
  args.pop("seed", None) # Not supported by Anthropic
42
50
  callbacks = prepare_callbacks(config, args)
43
51
  return args, {"callbacks": callbacks}
@@ -16,7 +16,7 @@ def inference(prompt: str, model, tokenizer, **kwargs):
16
16
  inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
17
17
  outputs = model.generate(**inputs, **kwargs)
18
18
  outputs = [
19
- tokenizer.decode(i[len(inputs[0]) :], skip_special_tokens=skip_special_tokens)
19
+ tokenizer.decode(i[len(inputs[0]):], skip_special_tokens=skip_special_tokens)
20
20
  for i in outputs
21
21
  ]
22
22
  return LLMResponse(outputs[0], dict(all=outputs))
@@ -13,6 +13,7 @@ def make_remove_hidden_output(config: Config) -> callable:
13
13
 
14
14
  return remove_hidden_output
15
15
 
16
+
16
17
  def prepare_callbacks(config: Config, args, set_stream: bool = True) -> list[callable]:
17
18
  callbacks = args.pop("callbacks", []) or [] + config.CALLBACKS or []
18
19
  if "callback" in args:
@@ -1,9 +1,28 @@
1
1
  import os
2
+ from dataclasses import dataclass, asdict
2
3
  from datetime import datetime
3
4
  import aiohttp
4
5
  from .._env import env
5
6
 
6
7
 
8
+ @dataclass
9
+ class TTSArgs:
10
+ text: str
11
+ out_file: str = None
12
+ voice: str = "D38z5RcWu1voky8WS1ja"
13
+ stability: float = 0.29
14
+ similarity_boost: float = 0.5
15
+ style: float = 0.0
16
+ chunk_size: int = 1024
17
+ speed: float = 1.0
18
+ use_speaker_boost: bool = False
19
+ previous_text: str = None
20
+ next_text: str = None
21
+
22
+ def to_dict(self) -> dict:
23
+ return asdict(self)
24
+
25
+
7
26
  async def text_to_speech(
8
27
  text: str,
9
28
  out_file: str = None,
@@ -12,6 +31,10 @@ async def text_to_speech(
12
31
  similarity_boost=0.5,
13
32
  style=0.0,
14
33
  chunk_size=1024,
34
+ speed=1.0,
35
+ use_speaker_boost: bool = False,
36
+ previous_text: str = None,
37
+ next_text: str = None,
15
38
  ) -> str:
16
39
  url = f"https://api.elevenlabs.io/v1/text-to-speech/{voice}"
17
40
  if not out_file:
@@ -25,8 +48,15 @@ async def text_to_speech(
25
48
  "stability": stability,
26
49
  "similarity_boost": similarity_boost,
27
50
  "style": style,
51
+ "speed": speed,
28
52
  },
29
53
  }
54
+ if use_speaker_boost:
55
+ data["voice_settings"]["use_speaker_boost"] = use_speaker_boost
56
+ if previous_text:
57
+ data["previous_text"] = previous_text
58
+ if next_text:
59
+ data["next_text"] = next_text
30
60
  headers = {
31
61
  "Accept": "audio/mpeg",
32
62
  "Content-Type": "application/json",
@@ -5,7 +5,8 @@ import requests.exceptions
5
5
  from ._env import env
6
6
 
7
7
 
8
- class CantLoadTikTokenEncoding(RuntimeError): ...
8
+ class CantLoadTikTokenEncoding(RuntimeError):
9
+ ...
9
10
 
10
11
 
11
12
  def _resolve_tiktoken_encoding(
@@ -5,6 +5,7 @@ from .message_types import Msg
5
5
 
6
6
  if TYPE_CHECKING:
7
7
  from .wrappers.prompt_wrapper import PromptWrapper # noqa: F401
8
+ from .wrappers.llm_response_wrapper import LLMResponse # noqa: F401
8
9
 
9
10
  TPrompt = Union[
10
11
  dict, Msg, str, "PromptWrapper", List[Union[dict, Msg, str, "PromptWrapper"]]
@@ -12,9 +13,9 @@ TPrompt = Union[
12
13
  """Type for prompt argument in LLM requests"""
13
14
  TplFunctionType = Callable[[Union[PathLike[str], str], Any], str]
14
15
  """Function type for rendering prompt templates"""
15
- LLMFunctionType = Callable[[TPrompt, Any], str]
16
+ LLMFunctionType = Callable[[TPrompt, Any], "LLMResponse"]
16
17
  """Function type for requesting LLM synchronously"""
17
- LLMAsyncFunctionType = Callable[[TPrompt, Any], Awaitable[str]]
18
+ LLMAsyncFunctionType = Callable[[TPrompt, Any], Awaitable["LLMResponse"]]
18
19
  """Function type for requesting LLM asynchronously"""
19
20
 
20
21
 
@@ -412,9 +412,9 @@ def levenshtein(a: str, b: str) -> int:
412
412
  cost = 0 if ch_a == ch_b else 1
413
413
  current.append(
414
414
  min(
415
- current[-1] + 1, # insertion
416
- previous[j] + 1, # deletion
417
- previous[j - 1] + cost # substitution
415
+ current[-1] + 1, # insertion
416
+ previous[j] + 1, # deletion
417
+ previous[j - 1] + cost # substitution
418
418
  )
419
419
  )
420
420
  previous = current
@@ -458,4 +458,4 @@ def most_similar(
458
458
  min_dist = dist
459
459
  most_similar_word = word
460
460
 
461
- return most_similar_word, min_dist
461
+ return most_similar_word, min_dist
@@ -46,9 +46,27 @@ class LLMResponse(ExtendedString, ConvertableToMessage):
46
46
  return obj
47
47
 
48
48
  def parse_json(
49
- self, raise_errors: bool = True, required_fields: list[str] = None
49
+ self,
50
+ raise_errors: bool = True,
51
+ required_fields: list[str] = None,
52
+ validator: callable = None,
50
53
  ) -> list | dict | float | int | str | DictFromLLMResponse:
51
- res = parse_json(self.content, raise_errors, required_fields)
54
+ try:
55
+ res = parse_json(self.content, True, required_fields)
56
+ if validator:
57
+ try:
58
+ validator(res)
59
+ except Exception as e:
60
+ raise BadAIAnswer(f"Language model response validation failed: {e}") from None
61
+ except Exception as e:
62
+ if hasattr(self, "_retry_callback"):
63
+ res = self._retry_callback()
64
+ if isinstance(res, DictFromLLMResponse):
65
+ return res
66
+ return res.parse_json(raise_errors, required_fields, validator)
67
+ if raise_errors:
68
+ raise e
69
+ res = False
52
70
  if isinstance(res, dict):
53
71
  res = DictFromLLMResponse(res)
54
72
  res.llm_response = self