ai-microcore 4.0.0.dev18__tar.gz → 4.0.0.dev20__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.dev18 → ai_microcore-4.0.0.dev20}/PKG-INFO +4 -3
  2. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/README.md +3 -2
  3. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/__init__.py +1 -1
  4. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/mcp.py +38 -98
  5. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/tokenizing.py +2 -0
  6. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/LICENSE +0 -0
  7. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/_env.py +0 -0
  8. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/_llm_functions.py +0 -0
  9. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/_prepare_llm_args.py +0 -0
  10. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/ai_func/__init__.py +0 -0
  11. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/ai_func/ai-func.json.j2 +0 -0
  12. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/ai_func/ai-func.pythonic.j2 +0 -0
  13. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/ai_modules.py +0 -0
  14. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/configuration.py +0 -0
  15. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/embedding_db/__init__.py +0 -0
  16. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/embedding_db/chromadb.py +0 -0
  17. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/file_storage.py +0 -0
  18. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/interactive_setup.py +0 -0
  19. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/json_parsing.py +0 -0
  20. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/llm/__init__.py +0 -0
  21. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/llm/_openai_llm_v0.py +0 -0
  22. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/llm/_openai_llm_v1.py +0 -0
  23. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/llm/anthropic.py +0 -0
  24. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/llm/google_genai.py +0 -0
  25. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/llm/google_vertex_ai.py +0 -0
  26. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/llm/local_llm.py +0 -0
  27. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/llm/local_transformers.py +0 -0
  28. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/llm/openai_llm.py +0 -0
  29. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/llm/shared.py +0 -0
  30. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/logging.py +0 -0
  31. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/message_types.py +0 -0
  32. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/metrics.py +0 -0
  33. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/python.py +0 -0
  34. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/templating/__init__.py +0 -0
  35. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/templating/jinja2.py +0 -0
  36. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/text2speech/elevenlabs.py +0 -0
  37. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/types.py +0 -0
  38. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/ui.py +0 -0
  39. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/utils.py +0 -0
  40. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/wrappers/__init__.py +0 -0
  41. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/wrappers/llm_response_wrapper.py +0 -0
  42. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/microcore/wrappers/prompt_wrapper.py +0 -0
  43. {ai_microcore-4.0.0.dev18 → ai_microcore-4.0.0.dev20}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ai-microcore
3
- Version: 4.0.0.dev18
3
+ Version: 4.0.0.dev20
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>
@@ -27,10 +27,11 @@ Requires-Dist: mcp~=1.9.1
27
27
  Requires-Dist: docstring_parser~=0.16.0
28
28
  Project-URL: Source Code, https://github.com/Nayjest/ai-microcore
29
29
 
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>
30
+ <p align="right">
31
+ <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
32
  <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
33
  <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>
34
+ <img src="coverage.svg" alt="Code Coverage">
34
35
  <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
36
  <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
37
  </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-dev18"
189
+ __version__ = "4.0.0-dev20"
@@ -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,21 @@ 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
79
  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
80
+ con.transport = transport
81
+ con._client = Client(url, timeout=connect_timeout) # pylint: disable=W0212
82
+ await con._client.__aenter__() # pylint: disable=E1101,W0212,C2801
83
+ if fetch_tools:
84
+ await con.fetch_tools(use_cache=use_cache)
132
85
  return con
133
86
 
134
87
  async def close(self):
135
- self._del_event.set()
136
- if self._lifecycle_task:
137
- await self._lifecycle_task
88
+ if self._client:
89
+ await self._client.__aexit__(None, None, None)
90
+ self._client = None
138
91
  else:
139
92
  logging.error(f"Trying to close MCP connection that is not opened ({self.url})")
140
93
 
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
94
  async def fetch_tools(self, use_cache: bool = True) -> "Tools":
166
95
  if self.tools is not None:
167
96
  return self.tools
@@ -172,8 +101,8 @@ class MCPConnection:
172
101
  return self.tools
173
102
 
174
103
  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])
104
+ mcp_tools = await self._client.list_tools()
105
+ self.tools = Tools.from_list([Tool.from_mcp(tool) for tool in mcp_tools])
177
106
  if use_cache:
178
107
  self.update_tools_cache()
179
108
  return self.tools
@@ -183,17 +112,24 @@ class MCPConnection:
183
112
  raise RuntimeError("Tools are not fetched yet. Call fetch_tools() first.")
184
113
  ToolsCache.write(self.url, self.tools)
185
114
 
186
- def __del__(self):
187
- self._del_event.set()
188
-
189
- async def call(self, name: str, **kwargs):
115
+ async def call(
116
+ self,
117
+ name: str,
118
+ timeout: datetime.timedelta | float | int | None = None,
119
+ progress_handler: ProgressHandler | None = None,
120
+ **kwargs
121
+ ):
190
122
  assert env().config.AI_SYNTAX_FUNCTION_NAME_FIELD not in kwargs
191
123
  params = dict(kwargs)
192
124
  params[env().config.AI_SYNTAX_FUNCTION_NAME_FIELD] = name
193
- return await self.exec(params)
194
-
125
+ return await self.exec(params, timeout=timeout, progress_handler=progress_handler)
195
126
 
196
- async def exec(self, params: dict | LLMResponse):
127
+ async def exec(
128
+ self,
129
+ params: dict | LLMResponse,
130
+ timeout: datetime.timedelta | float | int | None = None,
131
+ progress_handler: ProgressHandler | None = None,
132
+ ):
197
133
  if isinstance(params, LLMResponse):
198
134
  try:
199
135
  params = params.parse_json(
@@ -209,11 +145,15 @@ class MCPConnection:
209
145
  f"Tool name should be passed in {env().config.AI_SYNTAX_FUNCTION_NAME_FIELD} field"
210
146
  )
211
147
  logging.info(f"Calling MCP tool {ui.green(name)} with {params}...")
212
- result = await self.session.call_tool(name, params)
213
- content = result.content
148
+ content = await self._client.call_tool(
149
+ name=name,
150
+ arguments=params,
151
+ timeout=timeout,
152
+ progress_handler=progress_handler
153
+ )
214
154
  if content and len(content) == 1 and content[0].type == "text":
215
- return MCPAnswer(content[0].text, result.__dict__)
216
- return result
155
+ return MCPAnswer(content[0].text, dict(response=content))
156
+ return content
217
157
 
218
158
 
219
159
  @dataclass
@@ -242,7 +182,7 @@ class Tool:
242
182
  self.args[key] = Tool.Arg(**self.args[key])
243
183
 
244
184
  @staticmethod
245
- def from_mcp(tool: types.Tool) -> "Tool":
185
+ def from_mcp(tool: mcp.types.Tool) -> "Tool":
246
186
  t = Tool(
247
187
  name=tool.name,
248
188
  description=tool.description,
@@ -25,6 +25,8 @@ def _resolve_tiktoken_encoding(
25
25
  )
26
26
  if for_model:
27
27
  try:
28
+ if for_model.startswith("gpt-4.1") or for_model.startswith("gpt-4.5"):
29
+ return tiktoken.get_encoding("o200k_base")
28
30
  return tiktoken.encoding_for_model(for_model)
29
31
  except (KeyError, requests.exceptions.ConnectionError):
30
32
  logging.warning(