ai-microcore 4.0.0.dev16__tar.gz → 4.0.0.dev18__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.
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/PKG-INFO +1 -1
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/__init__.py +3 -1
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/mcp.py +127 -12
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/LICENSE +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/README.md +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/_env.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/_llm_functions.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/_prepare_llm_args.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/ai_func/__init__.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/ai_func/ai-func.json.j2 +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/ai_func/ai-func.pythonic.j2 +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/ai_modules.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/configuration.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/embedding_db/__init__.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/embedding_db/chromadb.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/file_storage.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/interactive_setup.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/json_parsing.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/llm/__init__.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/llm/_openai_llm_v0.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/llm/_openai_llm_v1.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/llm/anthropic.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/llm/google_genai.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/llm/google_vertex_ai.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/llm/local_llm.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/llm/local_transformers.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/llm/openai_llm.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/llm/shared.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/logging.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/message_types.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/metrics.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/python.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/templating/__init__.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/templating/jinja2.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/text2speech/elevenlabs.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/tokenizing.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/types.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/ui.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/utils.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/wrappers/__init__.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/wrappers/llm_response_wrapper.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/wrappers/prompt_wrapper.py +0 -0
- {ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ai-microcore
|
|
3
|
-
Version: 4.0.0.
|
|
3
|
+
Version: 4.0.0.dev18
|
|
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>
|
|
@@ -23,6 +23,7 @@ from .wrappers.llm_response_wrapper import LLMResponse
|
|
|
23
23
|
from ._llm_functions import llm, allm, llm_parallel
|
|
24
24
|
from .utils import parse, dedent
|
|
25
25
|
from .metrics import Metrics
|
|
26
|
+
from .interactive_setup import interactive_setup
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
def tpl(file: os.PathLike[str] | str, **kwargs) -> str | PromptWrapper:
|
|
@@ -181,7 +182,8 @@ __all__ = [
|
|
|
181
182
|
"mcp_server",
|
|
182
183
|
"tokenizing",
|
|
183
184
|
"Metrics",
|
|
185
|
+
"interactive_setup",
|
|
184
186
|
# "wrappers",
|
|
185
187
|
]
|
|
186
188
|
|
|
187
|
-
__version__ = "4.0.0-
|
|
189
|
+
__version__ = "4.0.0-dev18"
|
|
@@ -2,8 +2,11 @@ import asyncio
|
|
|
2
2
|
import logging
|
|
3
3
|
from typing import Optional
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
|
+
from enum import Enum
|
|
5
6
|
|
|
7
|
+
import requests
|
|
6
8
|
from mcp.client.streamable_http import streamablehttp_client
|
|
9
|
+
from mcp.client.sse import sse_client
|
|
7
10
|
from mcp import ClientSession, types
|
|
8
11
|
|
|
9
12
|
from .utils import ExtendedString, ConvertableToMessage
|
|
@@ -37,17 +40,32 @@ class ToolsCache:
|
|
|
37
40
|
cached_tools[mcp_url] = tools
|
|
38
41
|
storage.write_json(ToolsCache.FILE, cached_tools)
|
|
39
42
|
|
|
43
|
+
@staticmethod
|
|
44
|
+
def clear():
|
|
45
|
+
logging.info("Clearing MCP tools cache...")
|
|
46
|
+
storage.delete(ToolsCache.FILE)
|
|
47
|
+
|
|
40
48
|
|
|
41
49
|
class MCPAnswer(ExtendedString, ConvertableToMessage):
|
|
42
50
|
...
|
|
43
51
|
|
|
44
52
|
|
|
53
|
+
class McpTransport(str, Enum):
|
|
54
|
+
SSE: str = "sse"
|
|
55
|
+
STREAMABLE_HTTP: str = "streamable_http"
|
|
56
|
+
WS: str = "ws"
|
|
57
|
+
STDIO: str = "stdio"
|
|
58
|
+
|
|
59
|
+
def __str__(self):
|
|
60
|
+
return self.value
|
|
61
|
+
|
|
62
|
+
|
|
45
63
|
@dataclass
|
|
46
64
|
class MCPConnection:
|
|
47
65
|
url: str = None
|
|
66
|
+
transport: McpTransport = field(default=McpTransport.STREAMABLE_HTTP)
|
|
48
67
|
read_stream: any = None
|
|
49
68
|
write_stream: any = None
|
|
50
|
-
connection: any = None
|
|
51
69
|
context_manager: any = None
|
|
52
70
|
session: ClientSession = field(default=None, init=False)
|
|
53
71
|
tools: Optional["Tools"] = field(default=None, init=False)
|
|
@@ -57,8 +75,10 @@ class MCPConnection:
|
|
|
57
75
|
@staticmethod
|
|
58
76
|
async def init(
|
|
59
77
|
url: str,
|
|
78
|
+
transport: McpTransport,
|
|
60
79
|
fetch_tools: bool = True,
|
|
61
80
|
use_cache: bool = True,
|
|
81
|
+
connect_timeout: float = 10,
|
|
62
82
|
) -> "MCPConnection":
|
|
63
83
|
|
|
64
84
|
del_event = asyncio.Event()
|
|
@@ -69,17 +89,26 @@ class MCPConnection:
|
|
|
69
89
|
# That's a bit of a hack for closing the async context managers
|
|
70
90
|
async def lifecycle():
|
|
71
91
|
try:
|
|
72
|
-
logging.info(f"Connecting to MCP {url}...")
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
92
|
+
logging.info(f"Connecting to {transport} MCP {url}...")
|
|
93
|
+
if transport == McpTransport.STREAMABLE_HTTP:
|
|
94
|
+
context_manager = streamablehttp_client(url)
|
|
95
|
+
(
|
|
96
|
+
read_stream,
|
|
97
|
+
write_stream,
|
|
98
|
+
_
|
|
99
|
+
) = await context_manager.__aenter__() # pylint: disable=E1101
|
|
100
|
+
elif transport == McpTransport.SSE:
|
|
101
|
+
context_manager = sse_client(url)
|
|
102
|
+
(
|
|
103
|
+
read_stream,
|
|
104
|
+
write_stream
|
|
105
|
+
) = await context_manager.__aenter__() # pylint: disable=E1101
|
|
106
|
+
else:
|
|
107
|
+
raise ValueError(f"Unsupported transport type: {transport}")
|
|
79
108
|
con.url = url
|
|
109
|
+
con.transport = transport
|
|
80
110
|
con.read_stream = read_stream
|
|
81
111
|
con.write_stream = write_stream
|
|
82
|
-
con.connection = connection
|
|
83
112
|
con.context_manager = context_manager
|
|
84
113
|
await con.init_session()
|
|
85
114
|
if fetch_tools:
|
|
@@ -91,7 +120,15 @@ class MCPConnection:
|
|
|
91
120
|
await con._close() # pylint: disable=W0212
|
|
92
121
|
|
|
93
122
|
con._lifecycle_task = asyncio.create_task(lifecycle()) # pylint: disable=W0212
|
|
94
|
-
|
|
123
|
+
try:
|
|
124
|
+
await asyncio.wait_for(opened_event.wait(), timeout=connect_timeout)
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logging.warning(f"Failed to connect to MCP {url}: {e}")
|
|
127
|
+
try:
|
|
128
|
+
await con._close() # pylint: disable=W0212
|
|
129
|
+
except: # noqa: E722 # pylint: disable=W0702
|
|
130
|
+
pass
|
|
131
|
+
raise
|
|
95
132
|
return con
|
|
96
133
|
|
|
97
134
|
async def close(self):
|
|
@@ -138,12 +175,24 @@ class MCPConnection:
|
|
|
138
175
|
mcp_tools = await self.session.list_tools()
|
|
139
176
|
self.tools = Tools.from_list([Tool.from_mcp(tool) for tool in mcp_tools.tools])
|
|
140
177
|
if use_cache:
|
|
141
|
-
|
|
178
|
+
self.update_tools_cache()
|
|
142
179
|
return self.tools
|
|
143
180
|
|
|
181
|
+
def update_tools_cache(self):
|
|
182
|
+
if self.tools is None:
|
|
183
|
+
raise RuntimeError("Tools are not fetched yet. Call fetch_tools() first.")
|
|
184
|
+
ToolsCache.write(self.url, self.tools)
|
|
185
|
+
|
|
144
186
|
def __del__(self):
|
|
145
187
|
self._del_event.set()
|
|
146
188
|
|
|
189
|
+
async def call(self, name: str, **kwargs):
|
|
190
|
+
assert env().config.AI_SYNTAX_FUNCTION_NAME_FIELD not in kwargs
|
|
191
|
+
params = dict(kwargs)
|
|
192
|
+
params[env().config.AI_SYNTAX_FUNCTION_NAME_FIELD] = name
|
|
193
|
+
return await self.exec(params)
|
|
194
|
+
|
|
195
|
+
|
|
147
196
|
async def exec(self, params: dict | LLMResponse):
|
|
148
197
|
if isinstance(params, LLMResponse):
|
|
149
198
|
try:
|
|
@@ -247,6 +296,32 @@ class MCPServer:
|
|
|
247
296
|
url: str
|
|
248
297
|
name: str = field(default="")
|
|
249
298
|
tools: Tools = field(default_factory=Tools)
|
|
299
|
+
transport: McpTransport = field(default=None)
|
|
300
|
+
|
|
301
|
+
@staticmethod
|
|
302
|
+
def _try_sse_or_streamable_http(url) -> tuple[McpTransport, str]:
|
|
303
|
+
if url.endswith("/"):
|
|
304
|
+
test_sse_url = f"{url}sse"
|
|
305
|
+
else:
|
|
306
|
+
test_sse_url = f"{url}/sse"
|
|
307
|
+
try:
|
|
308
|
+
response = requests.request(method="HEAD", url=test_sse_url, timeout=5)
|
|
309
|
+
if response.status_code == 200:
|
|
310
|
+
return McpTransport.SSE, test_sse_url
|
|
311
|
+
except requests.RequestException:
|
|
312
|
+
# not a SSE endpoint or not reachable
|
|
313
|
+
pass
|
|
314
|
+
return McpTransport.STREAMABLE_HTTP, f"{url}/mcp"
|
|
315
|
+
|
|
316
|
+
@staticmethod
|
|
317
|
+
def _guess_transport_type_by_url(url: str) -> Optional[McpTransport]:
|
|
318
|
+
if url.startswith("ws://") or url.startswith("wss://"):
|
|
319
|
+
return McpTransport.WS
|
|
320
|
+
if url.endswith("/mcp"):
|
|
321
|
+
return McpTransport.STREAMABLE_HTTP
|
|
322
|
+
if url.endswith("/sse"):
|
|
323
|
+
return McpTransport.SSE
|
|
324
|
+
return None
|
|
250
325
|
|
|
251
326
|
@staticmethod
|
|
252
327
|
def name_from_url(url: str) -> str:
|
|
@@ -256,13 +331,29 @@ class MCPServer:
|
|
|
256
331
|
def __post_init__(self):
|
|
257
332
|
if not self.name:
|
|
258
333
|
self.name = MCPServer.name_from_url(self.url)
|
|
334
|
+
if not self.transport:
|
|
335
|
+
self.transport = self._guess_transport_type_by_url(self.url)
|
|
259
336
|
|
|
260
337
|
async def connect(
|
|
261
338
|
self,
|
|
262
339
|
fetch_tools: bool = True,
|
|
263
340
|
use_cache: bool = True,
|
|
341
|
+
connect_timeout: float = 10,
|
|
264
342
|
) -> MCPConnection:
|
|
265
|
-
|
|
343
|
+
if self.transport:
|
|
344
|
+
transport, url = self.transport, self.url
|
|
345
|
+
else:
|
|
346
|
+
transport, url = self._try_sse_or_streamable_http(self.url)
|
|
347
|
+
return await MCPConnection.init(
|
|
348
|
+
url=url,
|
|
349
|
+
transport=transport,
|
|
350
|
+
fetch_tools=fetch_tools,
|
|
351
|
+
use_cache=use_cache,
|
|
352
|
+
connect_timeout=connect_timeout,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
def get_tools_cache(self) -> Tools | None:
|
|
356
|
+
return ToolsCache.read(self.url)
|
|
266
357
|
|
|
267
358
|
|
|
268
359
|
class MCPRegistry(dict[str, MCPServer]):
|
|
@@ -281,6 +372,30 @@ class MCPRegistry(dict[str, MCPServer]):
|
|
|
281
372
|
raise ValueError(f"MCP server '{server_name}' not found in registry")
|
|
282
373
|
return self[server_name]
|
|
283
374
|
|
|
375
|
+
async def precache_tools(
|
|
376
|
+
self,
|
|
377
|
+
raise_errors: bool = False,
|
|
378
|
+
connect_timeout: int = 10,
|
|
379
|
+
):
|
|
380
|
+
async def precache_server_tools(server_name):
|
|
381
|
+
conn = None
|
|
382
|
+
try:
|
|
383
|
+
conn = await self.get(server_name).connect(
|
|
384
|
+
fetch_tools=True,
|
|
385
|
+
use_cache=False,
|
|
386
|
+
connect_timeout=connect_timeout,
|
|
387
|
+
)
|
|
388
|
+
conn.update_tools_cache()
|
|
389
|
+
except Exception as e: # pylint: disable=W0718
|
|
390
|
+
logging.error("Failed to precache tools for MCP server %s: %s", server_name, e)
|
|
391
|
+
if raise_errors:
|
|
392
|
+
raise
|
|
393
|
+
finally:
|
|
394
|
+
if conn is not None:
|
|
395
|
+
await conn.close()
|
|
396
|
+
|
|
397
|
+
await asyncio.gather(*[precache_server_tools(srv) for srv in self.keys()])
|
|
398
|
+
|
|
284
399
|
async def connect_to(
|
|
285
400
|
self,
|
|
286
401
|
server_name: str,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ai_microcore-4.0.0.dev16 → ai_microcore-4.0.0.dev18}/microcore/wrappers/llm_response_wrapper.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|