ai-microcore 4.0.0.dev14__tar.gz → 4.0.0.dev15__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 (43) hide show
  1. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/PKG-INFO +1 -1
  2. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/__init__.py +1 -1
  3. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/configuration.py +8 -1
  4. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/mcp.py +37 -10
  5. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/wrappers/llm_response_wrapper.py +9 -2
  6. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/LICENSE +0 -0
  7. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/README.md +0 -0
  8. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/_env.py +0 -0
  9. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/_llm_functions.py +0 -0
  10. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/_prepare_llm_args.py +0 -0
  11. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/ai_func/__init__.py +0 -0
  12. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/ai_func/ai-func.json.j2 +0 -0
  13. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/ai_func/ai-func.pythonic.j2 +0 -0
  14. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/ai_modules.py +0 -0
  15. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/embedding_db/__init__.py +0 -0
  16. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/embedding_db/chromadb.py +0 -0
  17. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/file_storage.py +0 -0
  18. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/interactive_setup.py +0 -0
  19. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/json_parsing.py +0 -0
  20. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/llm/__init__.py +0 -0
  21. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/llm/_openai_llm_v0.py +0 -0
  22. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/llm/_openai_llm_v1.py +0 -0
  23. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/llm/anthropic.py +0 -0
  24. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/llm/google_genai.py +0 -0
  25. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/llm/google_vertex_ai.py +0 -0
  26. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/llm/local_llm.py +0 -0
  27. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/llm/local_transformers.py +0 -0
  28. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/llm/openai_llm.py +0 -0
  29. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/llm/shared.py +0 -0
  30. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/logging.py +0 -0
  31. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/message_types.py +0 -0
  32. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/metrics.py +0 -0
  33. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/python.py +0 -0
  34. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/templating/__init__.py +0 -0
  35. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/templating/jinja2.py +0 -0
  36. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/text2speech/elevenlabs.py +0 -0
  37. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/tokenizing.py +0 -0
  38. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/types.py +0 -0
  39. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/ui.py +0 -0
  40. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/utils.py +0 -0
  41. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/wrappers/__init__.py +0 -0
  42. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/microcore/wrappers/prompt_wrapper.py +0 -0
  43. {ai_microcore-4.0.0.dev14 → ai_microcore-4.0.0.dev15}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ai-microcore
3
- Version: 4.0.0.dev14
3
+ Version: 4.0.0.dev15
4
4
  Summary: # Minimalistic Foundation for AI Applications
5
5
  Keywords: llm,large language models,ai,similarity search,ai search,gpt,openai,framework,adapter
6
6
  Author-email: Vitalii Stepanenko <mail@vitalii.in>
@@ -184,4 +184,4 @@ __all__ = [
184
184
  # "wrappers",
185
185
  ]
186
186
 
187
- __version__ = "4.0.0-dev14"
187
+ __version__ = "4.0.0-dev15"
@@ -112,7 +112,13 @@ class BaseConfig:
112
112
 
113
113
  if self.USE_DOT_ENV:
114
114
  if self.DOT_ENV_FILE or not _default_dotenv_loaded:
115
- dotenv.load_dotenv(override=True, dotenv_path=self.DOT_ENV_FILE)
115
+ fp = self.DOT_ENV_FILE
116
+ if fp and "~" in fp:
117
+ fp = Path(fp).expanduser()
118
+ dotenv.load_dotenv(
119
+ override=True,
120
+ dotenv_path=fp
121
+ )
116
122
  if not self.DOT_ENV_FILE:
117
123
  _default_dotenv_loaded = True
118
124
 
@@ -411,6 +417,7 @@ class Config(LLMConfig):
411
417
  MCP_SERVERS: list = from_env(dtype=list)
412
418
 
413
419
  INTERACTIVE_SETUP: bool = field(default=False)
420
+ """Whether to run interactive setup if configuration is not valid."""
414
421
 
415
422
  def __post_init__(self):
416
423
  try:
@@ -6,7 +6,7 @@ from dataclasses import dataclass, field
6
6
  from mcp.client.streamable_http import streamablehttp_client
7
7
  from mcp import ClientSession, types
8
8
 
9
- from .utils import ExtendedString
9
+ from .utils import ExtendedString, ConvertableToMessage
10
10
  from .ai_func import AiFuncSyntax
11
11
  from . import ui
12
12
  from .types import BadAIAnswer, BadAIJsonAnswer
@@ -38,6 +38,10 @@ class ToolsCache:
38
38
  storage.write_json(ToolsCache.FILE, cached_tools)
39
39
 
40
40
 
41
+ class MCPAnswer(ExtendedString, ConvertableToMessage):
42
+ ...
43
+
44
+
41
45
  @dataclass
42
46
  class MCPConnection:
43
47
  url: str = None
@@ -46,8 +50,9 @@ class MCPConnection:
46
50
  connection: any = None
47
51
  context_manager: any = None
48
52
  session: ClientSession = field(default=None, init=False)
49
- del_event: asyncio.Event = field(default=None, init=False)
50
53
  tools: Optional["Tools"] = field(default=None, init=False)
54
+ _lifecycle_task: asyncio.Task = field(default=None, init=False)
55
+ _del_event: asyncio.Event = field(default=None, init=False)
51
56
 
52
57
  @staticmethod
53
58
  async def init(
@@ -59,7 +64,7 @@ class MCPConnection:
59
64
  del_event = asyncio.Event()
60
65
  opened_event = asyncio.Event()
61
66
  con: MCPConnection = MCPConnection()
62
- con.del_event = del_event
67
+ con._del_event = del_event # pylint: disable=W0212
63
68
 
64
69
  # That's a bit of a hack for closing the async context managers
65
70
  async def lifecycle():
@@ -80,15 +85,23 @@ class MCPConnection:
80
85
  if fetch_tools:
81
86
  await con.fetch_tools(use_cache=use_cache)
82
87
  opened_event.set()
83
- await del_event.wait()
88
+
84
89
  finally:
85
- await con.close()
90
+ await del_event.wait()
91
+ await con._close() # pylint: disable=W0212
86
92
 
87
- asyncio.create_task(lifecycle())
93
+ con._lifecycle_task = asyncio.create_task(lifecycle()) # pylint: disable=W0212
88
94
  await opened_event.wait()
89
95
  return con
90
96
 
91
97
  async def close(self):
98
+ self._del_event.set()
99
+ if self._lifecycle_task:
100
+ await self._lifecycle_task
101
+ else:
102
+ logging.error(f"Trying to close MCP connection that is not opened ({self.url})")
103
+
104
+ async def _close(self):
92
105
  logging.info(f"Closing MCP session ({self.url})...")
93
106
  try:
94
107
  if self.session:
@@ -129,7 +142,7 @@ class MCPConnection:
129
142
  return self.tools
130
143
 
131
144
  def __del__(self):
132
- self.del_event.set()
145
+ self._del_event.set()
133
146
 
134
147
  async def exec(self, params: dict | LLMResponse):
135
148
  if isinstance(params, LLMResponse):
@@ -150,7 +163,7 @@ class MCPConnection:
150
163
  result = await self.session.call_tool(name, params)
151
164
  content = result.content
152
165
  if content and len(content) == 1 and content[0].type == "text":
153
- return ExtendedString(content[0].text, result.__dict__)
166
+ return MCPAnswer(content[0].text, result.__dict__)
154
167
  return result
155
168
 
156
169
 
@@ -231,10 +244,19 @@ class Tools(dict[str, Tool]):
231
244
 
232
245
  @dataclass
233
246
  class MCPServer:
234
- name: str
235
247
  url: str
248
+ name: str = field(default="")
236
249
  tools: Tools = field(default_factory=Tools)
237
250
 
251
+ @staticmethod
252
+ def name_from_url(url: str) -> str:
253
+ """Domain name from URL."""
254
+ return url.split("//")[-1].split("/")[0]
255
+
256
+ def __post_init__(self):
257
+ if not self.name:
258
+ self.name = MCPServer.name_from_url(self.url)
259
+
238
260
  async def connect(
239
261
  self,
240
262
  fetch_tools: bool = True,
@@ -244,9 +266,14 @@ class MCPServer:
244
266
 
245
267
 
246
268
  class MCPRegistry(dict[str, MCPServer]):
247
- def __init__(self, server_configs: list[dict]):
269
+ def __init__(self, server_configs: list[dict | str]):
248
270
  super().__init__()
249
271
  for server_config in server_configs:
272
+ if isinstance(server_config, str):
273
+ server_config = {
274
+ "name": MCPServer.name_from_url(server_config),
275
+ "url": server_config
276
+ }
250
277
  self[server_config["name"]] = MCPServer(**server_config)
251
278
 
252
279
  def get(self, server_name: str) -> MCPServer:
@@ -6,7 +6,7 @@ from ..utils import ExtendedString, ConvertableToMessage, extract_number
6
6
  from ..message_types import Role, AssistantMsg
7
7
 
8
8
  if TYPE_CHECKING:
9
- from ..mcp import MCPConnection
9
+ from ..mcp import MCPConnection, MCPAnswer
10
10
 
11
11
 
12
12
  class DictFromLLMResponse(dict):
@@ -90,5 +90,12 @@ class LLMResponse(ExtendedString, ConvertableToMessage):
90
90
  def as_message(self) -> AssistantMsg:
91
91
  return self.as_assistant
92
92
 
93
- async def to_mcp(self, mcp: "MCPConnection"):
93
+ async def to_mcp(self, mcp: "MCPConnection") -> "MCPAnswer":
94
94
  return await mcp.exec(self)
95
+
96
+ def is_tool_call(self):
97
+ from .._env import env
98
+ return self.parse_json(
99
+ raise_errors=False,
100
+ required_fields=[env().config.AI_SYNTAX_FUNCTION_NAME_FIELD],
101
+ ) is not False