ai-microcore 4.0.0.dev19__tar.gz → 4.0.0.dev21__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.dev19 → ai_microcore-4.0.0.dev21}/PKG-INFO +7 -5
  2. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/README.md +3 -2
  3. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/__init__.py +1 -1
  4. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/mcp.py +38 -99
  5. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/pyproject.toml +3 -2
  6. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/LICENSE +0 -0
  7. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/_env.py +0 -0
  8. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/_llm_functions.py +0 -0
  9. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/_prepare_llm_args.py +0 -0
  10. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/ai_func/__init__.py +0 -0
  11. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/ai_func/ai-func.json.j2 +0 -0
  12. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/ai_func/ai-func.pythonic.j2 +0 -0
  13. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/ai_modules.py +0 -0
  14. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/configuration.py +0 -0
  15. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/embedding_db/__init__.py +0 -0
  16. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/embedding_db/chromadb.py +0 -0
  17. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/file_storage.py +0 -0
  18. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/interactive_setup.py +0 -0
  19. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/json_parsing.py +0 -0
  20. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/llm/__init__.py +0 -0
  21. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/llm/_openai_llm_v0.py +0 -0
  22. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/llm/_openai_llm_v1.py +0 -0
  23. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/llm/anthropic.py +0 -0
  24. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/llm/google_genai.py +0 -0
  25. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/llm/google_vertex_ai.py +0 -0
  26. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/llm/local_llm.py +0 -0
  27. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/llm/local_transformers.py +0 -0
  28. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/llm/openai_llm.py +0 -0
  29. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/llm/shared.py +0 -0
  30. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/logging.py +0 -0
  31. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/message_types.py +0 -0
  32. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/metrics.py +0 -0
  33. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/python.py +0 -0
  34. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/templating/__init__.py +0 -0
  35. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/templating/jinja2.py +0 -0
  36. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/text2speech/elevenlabs.py +0 -0
  37. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/tokenizing.py +0 -0
  38. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/types.py +0 -0
  39. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/ui.py +0 -0
  40. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/utils.py +0 -0
  41. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/wrappers/__init__.py +0 -0
  42. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/wrappers/llm_response_wrapper.py +0 -0
  43. {ai_microcore-4.0.0.dev19 → ai_microcore-4.0.0.dev21}/microcore/wrappers/prompt_wrapper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ai-microcore
3
- Version: 4.0.0.dev19
3
+ Version: 4.0.0.dev21
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>
@@ -17,20 +17,22 @@ Classifier: Intended Audience :: Developers
17
17
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
18
18
  License-File: LICENSE
19
19
  Requires-Dist: openai>=0.28.1,<2
20
- Requires-Dist: python-dotenv~=1.0.0
20
+ Requires-Dist: python-dotenv~=1.1.0
21
21
  Requires-Dist: Jinja2~=3.1.2
22
22
  Requires-Dist: colorama~=0.4.6
23
23
  Requires-Dist: PyYAML~=6.0
24
24
  Requires-Dist: chardet~=5.2.0
25
25
  Requires-Dist: tiktoken>=0.7.0,<1.0
26
- Requires-Dist: mcp~=1.9.1
26
+ Requires-Dist: mcp~=1.9.2
27
+ Requires-Dist: fastmcp~=2.8.0
27
28
  Requires-Dist: docstring_parser~=0.16.0
28
29
  Project-URL: Source Code, https://github.com/Nayjest/ai-microcore
29
30
 
30
- <p align="right">
31
- <a href="https://github.com/Nayjest/ai-microcore/releases" target="_blank"><img src="https://img.shields.io/github/release/ai-microcore/microcore" alt="Release Notes"></a>
31
+ <p align="right">
32
+ <a href="https://github.com/Nayjest/ai-microcore/releases" target="_blank"><img src="https://img.shields.io/github/v/release/Nayjest/ai-microcore.svg" alt="Release Notes"></a>
32
33
  <a href="https://app.codacy.com/gh/Nayjest/ai-microcore/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade" target="_blank"><img src="https://app.codacy.com/project/badge/Grade/441d03416bc048828c649129530dcbc3" alt="Code Quality"></a>
33
34
  <a href="https://github.com/Nayjest/ai-microcore/actions/workflows/pylint.yml" target="_blank"><img src="https://github.com/Nayjest/ai-microcore/actions/workflows/pylint.yml/badge.svg" alt="Pylint"></a>
35
+ <img src="coverage.svg" alt="Code Coverage">
34
36
  <a href="https://github.com/Nayjest/ai-microcore/actions/workflows/tests.yml" target="_blank"><img src="https://github.com/Nayjest/ai-microcore/actions/workflows/tests.yml/badge.svg" alt="Tests"></a>
35
37
  <a href="https://github.com/Nayjest/ai-microcore/blob/main/LICENSE" target="_blank"><img src="https://img.shields.io/static/v1?label=license&message=MIT&color=d08aff" alt="License"></a>
36
38
  </p>
@@ -1,7 +1,8 @@
1
- <p align="right">
2
- <a href="https://github.com/Nayjest/ai-microcore/releases" target="_blank"><img src="https://img.shields.io/github/release/ai-microcore/microcore" alt="Release Notes"></a>
1
+ <p align="right">
2
+ <a href="https://github.com/Nayjest/ai-microcore/releases" target="_blank"><img src="https://img.shields.io/github/v/release/Nayjest/ai-microcore.svg" alt="Release Notes"></a>
3
3
  <a href="https://app.codacy.com/gh/Nayjest/ai-microcore/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade" target="_blank"><img src="https://app.codacy.com/project/badge/Grade/441d03416bc048828c649129530dcbc3" alt="Code Quality"></a>
4
4
  <a href="https://github.com/Nayjest/ai-microcore/actions/workflows/pylint.yml" target="_blank"><img src="https://github.com/Nayjest/ai-microcore/actions/workflows/pylint.yml/badge.svg" alt="Pylint"></a>
5
+ <img src="coverage.svg" alt="Code Coverage">
5
6
  <a href="https://github.com/Nayjest/ai-microcore/actions/workflows/tests.yml" target="_blank"><img src="https://github.com/Nayjest/ai-microcore/actions/workflows/tests.yml/badge.svg" alt="Tests"></a>
6
7
  <a href="https://github.com/Nayjest/ai-microcore/blob/main/LICENSE" target="_blank"><img src="https://img.shields.io/static/v1?label=license&message=MIT&color=d08aff" alt="License"></a>
7
8
  </p>
@@ -186,4 +186,4 @@ __all__ = [
186
186
  # "wrappers",
187
187
  ]
188
188
 
189
- __version__ = "4.0.0-dev19"
189
+ __version__ = "4.0.0-dev21"
@@ -1,13 +1,14 @@
1
1
  import asyncio
2
2
  import logging
3
+ import datetime
3
4
  from typing import Optional
4
5
  from dataclasses import dataclass, field
5
6
  from enum import Enum
6
7
 
7
8
  import requests
8
- from mcp.client.streamable_http import streamablehttp_client
9
- from mcp.client.sse import sse_client
10
- from mcp import ClientSession, types
9
+ from fastmcp import Client
10
+ from fastmcp.client.progress import ProgressHandler
11
+ import mcp.types
11
12
 
12
13
  from .utils import ExtendedString, ConvertableToMessage
13
14
  from .ai_func import AiFuncSyntax
@@ -64,13 +65,8 @@ class McpTransport(str, Enum):
64
65
  class MCPConnection:
65
66
  url: str = None
66
67
  transport: McpTransport = field(default=McpTransport.STREAMABLE_HTTP)
67
- read_stream: any = None
68
- write_stream: any = None
69
- context_manager: any = None
70
- session: ClientSession = field(default=None, init=False)
71
68
  tools: Optional["Tools"] = field(default=None, init=False)
72
- _lifecycle_task: asyncio.Task = field(default=None, init=False)
73
- _del_event: asyncio.Event = field(default=None, init=False)
69
+ _client: Client | None = field(default=None, init=False)
74
70
 
75
71
  @staticmethod
76
72
  async def init(
@@ -80,88 +76,20 @@ class MCPConnection:
80
76
  use_cache: bool = True,
81
77
  connect_timeout: float = 10,
82
78
  ) -> "MCPConnection":
83
-
84
- del_event = asyncio.Event()
85
- opened_event = asyncio.Event()
86
- con: MCPConnection = MCPConnection()
87
- con._del_event = del_event # pylint: disable=W0212
88
-
89
- # That's a bit of a hack for closing the async context managers
90
- async def lifecycle():
91
- try:
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}")
108
- con.url = url
109
- con.transport = transport
110
- con.read_stream = read_stream
111
- con.write_stream = write_stream
112
- con.context_manager = context_manager
113
- await con.init_session()
114
- if fetch_tools:
115
- await con.fetch_tools(use_cache=use_cache)
116
- opened_event.set()
117
-
118
- finally:
119
- await del_event.wait()
120
- await con._close() # pylint: disable=W0212
121
-
122
- con._lifecycle_task = asyncio.create_task(lifecycle()) # pylint: disable=W0212
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
79
+ con: MCPConnection = MCPConnection(url=url, transport=transport)
80
+ con._client = Client(url, timeout=connect_timeout) # pylint: disable=W0212
81
+ await con._client.__aenter__() # pylint: disable=E1101,W0212,C2801
82
+ if fetch_tools:
83
+ await con.fetch_tools(use_cache=use_cache)
132
84
  return con
133
85
 
134
86
  async def close(self):
135
- self._del_event.set()
136
- if self._lifecycle_task:
137
- await self._lifecycle_task
87
+ if self._client:
88
+ await self._client.__aexit__(None, None, None)
89
+ self._client = None
138
90
  else:
139
91
  logging.error(f"Trying to close MCP connection that is not opened ({self.url})")
140
92
 
141
- async def _close(self):
142
- logging.info(f"Closing MCP session ({self.url})...")
143
- try:
144
- if self.session:
145
- await self.session.__aexit__(None, None, None)
146
- del self.session
147
- self.session = None
148
- finally:
149
- logging.info(f"Closing MCP connection ({self.url})...")
150
- try:
151
- if self.context_manager:
152
- await self.context_manager.__aexit__(None, None, None)
153
- del self.context_manager
154
- self.context_manager = None
155
- finally:
156
- logging.info(f"Closed CTX ({self.url})")
157
-
158
- async def init_session(self):
159
- logging.info(f"Initializing MCP session ({self.url})")
160
- self.session = ClientSession(self.read_stream, self.write_stream)
161
- await self.session.__aenter__() # pylint: disable=unnecessary-dunder-call
162
- await self.session.initialize()
163
- return self.session
164
-
165
93
  async def fetch_tools(self, use_cache: bool = True) -> "Tools":
166
94
  if self.tools is not None:
167
95
  return self.tools
@@ -172,8 +100,8 @@ class MCPConnection:
172
100
  return self.tools
173
101
 
174
102
  logging.info("Fetching tools from MCP %s", ui.green(self.url))
175
- mcp_tools = await self.session.list_tools()
176
- self.tools = Tools.from_list([Tool.from_mcp(tool) for tool in mcp_tools.tools])
103
+ mcp_tools = await self._client.list_tools()
104
+ self.tools = Tools.from_list([Tool.from_mcp(tool) for tool in mcp_tools])
177
105
  if use_cache:
178
106
  self.update_tools_cache()
179
107
  return self.tools
@@ -183,17 +111,24 @@ class MCPConnection:
183
111
  raise RuntimeError("Tools are not fetched yet. Call fetch_tools() first.")
184
112
  ToolsCache.write(self.url, self.tools)
185
113
 
186
- def __del__(self):
187
- self._del_event.set()
188
-
189
- async def call(self, name: str, **kwargs):
114
+ async def call(
115
+ self,
116
+ name: str,
117
+ timeout: datetime.timedelta | float | int | None = None,
118
+ progress_handler: ProgressHandler | None = None,
119
+ **kwargs
120
+ ):
190
121
  assert env().config.AI_SYNTAX_FUNCTION_NAME_FIELD not in kwargs
191
122
  params = dict(kwargs)
192
123
  params[env().config.AI_SYNTAX_FUNCTION_NAME_FIELD] = name
193
- return await self.exec(params)
194
-
124
+ return await self.exec(params, timeout=timeout, progress_handler=progress_handler)
195
125
 
196
- async def exec(self, params: dict | LLMResponse):
126
+ async def exec(
127
+ self,
128
+ params: dict | LLMResponse,
129
+ timeout: datetime.timedelta | float | int | None = None,
130
+ progress_handler: ProgressHandler | None = None,
131
+ ):
197
132
  if isinstance(params, LLMResponse):
198
133
  try:
199
134
  params = params.parse_json(
@@ -209,11 +144,15 @@ class MCPConnection:
209
144
  f"Tool name should be passed in {env().config.AI_SYNTAX_FUNCTION_NAME_FIELD} field"
210
145
  )
211
146
  logging.info(f"Calling MCP tool {ui.green(name)} with {params}...")
212
- result = await self.session.call_tool(name, params)
213
- content = result.content
147
+ content = await self._client.call_tool(
148
+ name=name,
149
+ arguments=params,
150
+ timeout=timeout,
151
+ progress_handler=progress_handler
152
+ )
214
153
  if content and len(content) == 1 and content[0].type == "text":
215
- return MCPAnswer(content[0].text, result.__dict__)
216
- return result
154
+ return MCPAnswer(content[0].text, dict(response=content))
155
+ return content
217
156
 
218
157
 
219
158
  @dataclass
@@ -242,7 +181,7 @@ class Tool:
242
181
  self.args[key] = Tool.Arg(**self.args[key])
243
182
 
244
183
  @staticmethod
245
- def from_mcp(tool: types.Tool) -> "Tool":
184
+ def from_mcp(tool: mcp.types.Tool) -> "Tool":
246
185
  t = Tool(
247
186
  name=tool.name,
248
187
  description=tool.description,
@@ -20,13 +20,14 @@ classifiers = [
20
20
 
21
21
  dependencies = [
22
22
  "openai>=0.28.1,<2",
23
- "python-dotenv~=1.0.0",
23
+ "python-dotenv~=1.1.0",
24
24
  "Jinja2~=3.1.2",
25
25
  "colorama~=0.4.6",
26
26
  "PyYAML~=6.0",
27
27
  "chardet~=5.2.0",
28
28
  "tiktoken>=0.7.0,<1.0",
29
- "mcp~=1.9.1",
29
+ "mcp~=1.9.2",
30
+ "fastmcp~=2.8.0",
30
31
  "docstring_parser~=0.16.0",
31
32
  ]
32
33
  requires-python = ">=3.10"