aixtools 0.1.10__py3-none-any.whl → 0.1.11__py3-none-any.whl
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.
Potentially problematic release.
This version of aixtools might be problematic. Click here for more details.
- aixtools/_version.py +2 -2
- aixtools/mcp/client.py +102 -1
- aixtools/testing/aix_test_model.py +2 -0
- {aixtools-0.1.10.dist-info → aixtools-0.1.11.dist-info}/METADATA +2 -1
- {aixtools-0.1.10.dist-info → aixtools-0.1.11.dist-info}/RECORD +8 -45
- aixtools-0.1.11.dist-info/top_level.txt +1 -0
- aixtools-0.1.10.dist-info/top_level.txt +0 -5
- docker/mcp-base/Dockerfile +0 -33
- docker/mcp-base/zscaler.crt +0 -28
- notebooks/example_faulty_mcp_server.ipynb +0 -74
- notebooks/example_mcp_server_stdio.ipynb +0 -76
- notebooks/example_raw_mcp_client.ipynb +0 -84
- notebooks/example_tool_doctor.ipynb +0 -65
- scripts/config.sh +0 -28
- scripts/lint.sh +0 -32
- scripts/log_view.sh +0 -18
- scripts/run_example_mcp_server.sh +0 -14
- scripts/run_faulty_mcp_server.sh +0 -13
- scripts/run_server.sh +0 -29
- scripts/test.sh +0 -30
- tests/__init__.py +0 -0
- tests/unit/__init__.py +0 -0
- tests/unit/a2a/__init__.py +0 -0
- tests/unit/a2a/google_sdk/__init__.py +0 -0
- tests/unit/a2a/google_sdk/pydantic_ai_adapter/__init__.py +0 -0
- tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_agent_executor.py +0 -188
- tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_storage.py +0 -156
- tests/unit/a2a/google_sdk/test_card.py +0 -114
- tests/unit/a2a/google_sdk/test_remote_agent_connection.py +0 -413
- tests/unit/a2a/google_sdk/test_utils.py +0 -208
- tests/unit/agents/__init__.py +0 -0
- tests/unit/agents/test_prompt.py +0 -363
- tests/unit/compliance/test_private_data.py +0 -329
- tests/unit/google/__init__.py +0 -1
- tests/unit/google/test_client.py +0 -233
- tests/unit/mcp/__init__.py +0 -0
- tests/unit/mcp/test_client.py +0 -242
- tests/unit/server/__init__.py +0 -0
- tests/unit/server/test_path.py +0 -225
- tests/unit/server/test_utils.py +0 -362
- tests/unit/utils/__init__.py +0 -0
- tests/unit/utils/test_files.py +0 -146
- tests/unit/vault/__init__.py +0 -0
- tests/unit/vault/test_vault.py +0 -246
- {aixtools-0.1.10.dist-info → aixtools-0.1.11.dist-info}/WHEEL +0 -0
- {aixtools-0.1.10.dist-info → aixtools-0.1.11.dist-info}/entry_points.txt +0 -0
aixtools/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.1.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
31
|
+
__version__ = version = '0.1.11'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 11)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
aixtools/mcp/client.py
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
"""MCP server utilities with caching and robust error handling."""
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
|
-
from
|
|
4
|
+
from contextlib import asynccontextmanager
|
|
5
|
+
from datetime import timedelta
|
|
6
|
+
from typing import Any, AsyncGenerator
|
|
5
7
|
|
|
6
8
|
import anyio
|
|
9
|
+
import httpx
|
|
10
|
+
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
|
7
11
|
from cachebox import TTLCache
|
|
8
12
|
from mcp import types as mcp_types
|
|
13
|
+
from mcp.client import streamable_http
|
|
9
14
|
from mcp.shared.exceptions import McpError
|
|
15
|
+
from mcp.shared.message import SessionMessage
|
|
10
16
|
from pydantic_ai import RunContext, exceptions
|
|
11
17
|
from pydantic_ai.mcp import MCPServerStreamableHTTP, ToolResult
|
|
12
18
|
from pydantic_ai.toolsets.abstract import ToolsetTool
|
|
@@ -16,6 +22,7 @@ from aixtools.logging.logging_config import get_logger
|
|
|
16
22
|
|
|
17
23
|
MCP_TOOL_CACHE_TTL = 300 # 5 minutes
|
|
18
24
|
DEFAULT_MCP_CONNECTION_TIMEOUT = 30
|
|
25
|
+
DEFAULT_MCP_READ_TIMEOUT = float(60 * 5) # 5 minutes
|
|
19
26
|
CACHE_KEY = "TOOL_LIST"
|
|
20
27
|
|
|
21
28
|
logger = get_logger(__name__)
|
|
@@ -145,6 +152,23 @@ class CachedMCPServerStreamableHTTP(MCPServerStreamableHTTP):
|
|
|
145
152
|
logger.warning("MCP %s: %s exception %s: %s", self.url, func.__name__, type(exc), exc)
|
|
146
153
|
return fallback(exc)
|
|
147
154
|
|
|
155
|
+
@property
|
|
156
|
+
def _transport_client(self):
|
|
157
|
+
"""Override base transport client with wrapper logging and suppressing exceptions"""
|
|
158
|
+
return patched_streamablehttp_client
|
|
159
|
+
|
|
160
|
+
@asynccontextmanager
|
|
161
|
+
async def client_streams(self):
|
|
162
|
+
"""Override base client_streams with wrapper logging and suppressing exceptions"""
|
|
163
|
+
try:
|
|
164
|
+
async with super().client_streams() as streams: # pylint: disable=contextmanager-generator-missing-cleanup
|
|
165
|
+
try:
|
|
166
|
+
yield streams
|
|
167
|
+
except Exception as exc: # pylint: disable=broad-except
|
|
168
|
+
logger.error("MCP %s: client_streams; %s: %s", self.url, type(exc).__name__, exc)
|
|
169
|
+
except Exception as exc: # pylint: disable=broad-except
|
|
170
|
+
logger.error("MCP %s: client_streams: %s: %s", self.url, type(exc).__name__, exc)
|
|
171
|
+
|
|
148
172
|
async def __aenter__(self):
|
|
149
173
|
"""Enter the context of the cached MCP server with complete cancellation isolation."""
|
|
150
174
|
async with self._isolation_lock:
|
|
@@ -272,3 +296,80 @@ class CachedMCPServerStreamableHTTP(MCPServerStreamableHTTP):
|
|
|
272
296
|
raise exceptions.ModelRetry(text)
|
|
273
297
|
|
|
274
298
|
return content[0] if len(content) == 1 else content
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class PatchedStreamableHTTPTransport(streamable_http.StreamableHTTPTransport):
|
|
302
|
+
"""Patched StreamableHTTPTransport with exception suppression for _handle_post_request."""
|
|
303
|
+
|
|
304
|
+
async def _handle_post_request(self, ctx: streamable_http.RequestContext) -> None:
|
|
305
|
+
"""Patched _handle_post_request with proper error handling."""
|
|
306
|
+
try:
|
|
307
|
+
await super()._handle_post_request(ctx)
|
|
308
|
+
except Exception as exc: # pylint: disable=broad-except
|
|
309
|
+
logger.error("MCP %s: _handle_post_request %s: %s", self.url, type(exc).__name__, exc)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@asynccontextmanager
|
|
313
|
+
async def patched_streamablehttp_client( # noqa: PLR0913, pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-locals
|
|
314
|
+
url: str,
|
|
315
|
+
headers: dict[str, str] | None = None,
|
|
316
|
+
timeout: float | timedelta = 30,
|
|
317
|
+
sse_read_timeout: float | timedelta = DEFAULT_MCP_READ_TIMEOUT,
|
|
318
|
+
terminate_on_close: bool = True,
|
|
319
|
+
httpx_client_factory: streamable_http.McpHttpClientFactory = streamable_http.create_mcp_http_client,
|
|
320
|
+
auth: httpx.Auth | None = None,
|
|
321
|
+
) -> AsyncGenerator[
|
|
322
|
+
tuple[
|
|
323
|
+
MemoryObjectReceiveStream[SessionMessage | Exception],
|
|
324
|
+
MemoryObjectSendStream[SessionMessage],
|
|
325
|
+
streamable_http.GetSessionIdCallback,
|
|
326
|
+
],
|
|
327
|
+
None,
|
|
328
|
+
]:
|
|
329
|
+
"""Patched version of `streamablehttp_client` with exception suppression."""
|
|
330
|
+
try:
|
|
331
|
+
transport = PatchedStreamableHTTPTransport(url, headers, timeout, sse_read_timeout, auth)
|
|
332
|
+
|
|
333
|
+
read_stream_writer, read_stream = anyio.create_memory_object_stream[SessionMessage | Exception](0)
|
|
334
|
+
write_stream, write_stream_reader = anyio.create_memory_object_stream[SessionMessage](0)
|
|
335
|
+
async with anyio.create_task_group() as tg:
|
|
336
|
+
try:
|
|
337
|
+
async with httpx_client_factory(
|
|
338
|
+
headers=transport.request_headers,
|
|
339
|
+
timeout=httpx.Timeout(transport.timeout, read=transport.sse_read_timeout),
|
|
340
|
+
auth=transport.auth,
|
|
341
|
+
) as client:
|
|
342
|
+
# Define callbacks that need access to tg
|
|
343
|
+
def start_get_stream() -> None:
|
|
344
|
+
tg.start_soon(transport.handle_get_stream, client, read_stream_writer)
|
|
345
|
+
|
|
346
|
+
tg.start_soon(
|
|
347
|
+
transport.post_writer,
|
|
348
|
+
client,
|
|
349
|
+
write_stream_reader,
|
|
350
|
+
read_stream_writer,
|
|
351
|
+
write_stream,
|
|
352
|
+
start_get_stream,
|
|
353
|
+
tg,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
try:
|
|
357
|
+
yield (
|
|
358
|
+
read_stream,
|
|
359
|
+
write_stream,
|
|
360
|
+
transport.get_session_id,
|
|
361
|
+
)
|
|
362
|
+
except GeneratorExit:
|
|
363
|
+
logger.warning("patched_streamablehttp_client: GeneratorExit caught, closing streams.")
|
|
364
|
+
finally:
|
|
365
|
+
if transport.session_id and terminate_on_close:
|
|
366
|
+
await transport.terminate_session(client)
|
|
367
|
+
tg.cancel_scope.cancel()
|
|
368
|
+
finally:
|
|
369
|
+
await read_stream_writer.aclose()
|
|
370
|
+
await write_stream.aclose()
|
|
371
|
+
except Exception as exc: # pylint: disable=broad-except
|
|
372
|
+
if str(exc) == "Attempted to exit cancel scope in a different task than it was entered in":
|
|
373
|
+
logger.warning("MCP %s: patched_streamablehttp_client: enter/exit cancel scope task mismatch.", url)
|
|
374
|
+
else:
|
|
375
|
+
logger.error("MCP %s: patched_streamablehttp_client: %s: %s", url, type(exc).__name__, exc)
|
|
@@ -108,6 +108,8 @@ class AixTestModel(Model):
|
|
|
108
108
|
messages: list[ModelMessage],
|
|
109
109
|
model_settings: ModelSettings | None,
|
|
110
110
|
model_request_parameters: ModelRequestParameters,
|
|
111
|
+
*args, # pylint: disable=unused-argument # Accept additional arguments for compatibility with pydantic-ai 1.0.9
|
|
112
|
+
**kwargs, # pylint: disable=unused-argument
|
|
111
113
|
) -> AsyncIterator[StreamedResponse]:
|
|
112
114
|
model_response = await self._request(messages, model_settings, model_request_parameters)
|
|
113
115
|
yield TestStreamedResponse(_model_name=self.model_name, _structured_response=model_response, _messages=messages)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aixtools
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.11
|
|
4
4
|
Summary: Tools for AI exploration and debugging
|
|
5
5
|
Requires-Python: >=3.11.2
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -16,6 +16,7 @@ Requires-Dist: langchain-chroma>=0.2.3
|
|
|
16
16
|
Requires-Dist: langchain-ollama>=0.3.2
|
|
17
17
|
Requires-Dist: langchain-openai>=0.3.14
|
|
18
18
|
Requires-Dist: mcp>=1.11.0
|
|
19
|
+
Requires-Dist: mypy>=1.18.2
|
|
19
20
|
Requires-Dist: pandas>=2.2.3
|
|
20
21
|
Requires-Dist: pydantic-ai>=0.4.10
|
|
21
22
|
Requires-Dist: pylint>=3.3.7
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
aixtools/__init__.py,sha256=9NGHm7LjsQmsvjTZvw6QFJexSvAU4bCoN_KBk9SCa00,260
|
|
2
|
-
aixtools/_version.py,sha256=
|
|
2
|
+
aixtools/_version.py,sha256=0-Ruc52ECccw_8Ef0d7jMkzrb8fkobUkZLqGGvcm1ik,706
|
|
3
3
|
aixtools/app.py,sha256=JzQ0nrv_bjDQokllIlGHOV0HEb-V8N6k_nGQH-TEsVU,5227
|
|
4
4
|
aixtools/chainlit.md,sha256=yC37Ly57vjKyiIvK4oUvf4DYxZCwH7iocTlx7bLeGLU,761
|
|
5
5
|
aixtools/context.py,sha256=I_MD40ZnvRm5WPKAKqBUAdXIf8YaurkYUUHSVVy-QvU,598
|
|
@@ -52,7 +52,7 @@ aixtools/logging/mcp_logger.py,sha256=d2I5l4t0d6rQH17w23FpE1IUD8Ax-mSaKfByCH86q4
|
|
|
52
52
|
aixtools/logging/model_patch_logging.py,sha256=MY2EvR7ZSctC4hJxNMe8iACeVayUJ2V5In2GAnKdgOo,2880
|
|
53
53
|
aixtools/logging/open_telemetry.py,sha256=fJjF1ou_8GyfNfbyWDQPGK6JAUrUaPwURYPHhXEtDBE,1121
|
|
54
54
|
aixtools/mcp/__init__.py,sha256=tLo2KZ1Ojo-rgEEJBGtZfUw-iOoopWoHDnYQTq3IzfE,163
|
|
55
|
-
aixtools/mcp/client.py,sha256=
|
|
55
|
+
aixtools/mcp/client.py,sha256=zN5Na3Vgub2-BoJeldtFrpciBW-TrLjZUpjcQm-biqU,16661
|
|
56
56
|
aixtools/mcp/example_client.py,sha256=QCFGP3NCNJMOKWjUOnFwjnbJhUSb879IA1ZYmwjRnmc,889
|
|
57
57
|
aixtools/mcp/example_server.py,sha256=1SWCyrLWsAnOa81HC4QbPJo_lBVu0b3SZBWI-qDh1vQ,458
|
|
58
58
|
aixtools/mcp/fast_mcp_log.py,sha256=XYOS406dVjn5YTHyGRsRvVNQ0SKlRObfrKj6EeLFjHg,1057
|
|
@@ -64,7 +64,7 @@ aixtools/server/path.py,sha256=SaIJxvmhJy3kzx5zJ6d4cKP6kKu2wFFciQkOLGTA4gg,3056
|
|
|
64
64
|
aixtools/server/utils.py,sha256=tZWITIx6M-luV9yve4j3rPtYGSSA6zWS0JWEAySne_M,2276
|
|
65
65
|
aixtools/server/workspace_privacy.py,sha256=grcj82eHSd7gFbb5f_w9nv4TWp50QyU952l0iIPoChM,2375
|
|
66
66
|
aixtools/testing/__init__.py,sha256=mlmaAR2gmS4SbsYNCxnIprmFpFp-syjgVUkpUszo3mE,166
|
|
67
|
-
aixtools/testing/aix_test_model.py,sha256=
|
|
67
|
+
aixtools/testing/aix_test_model.py,sha256=KDSPwvOOzBVFym14hkXw7BWSLqkFH9nu5OVdfNZoUeQ,6099
|
|
68
68
|
aixtools/testing/mock_tool.py,sha256=4I0LxxSkLhGIKM2YxCP3cnYI8IYJjdKhfwGZ3dioXsM,2465
|
|
69
69
|
aixtools/testing/model_patch_cache.py,sha256=238gKC_gSpR3BkeejhetObOkpOR1l2Iz3A6B_eUTRNc,10158
|
|
70
70
|
aixtools/tools/doctor/__init__.py,sha256=FPwYzC1eJyw8IH0-BP0wgxSprLy6Y_4yXCek7496f2k,64
|
|
@@ -81,45 +81,8 @@ aixtools/utils/chainlit/cl_agent_show.py,sha256=vaRuowp4BRvhxEr5hw0zHEJ7iaSF_5bo
|
|
|
81
81
|
aixtools/utils/chainlit/cl_utils.py,sha256=fxaxdkcZg6uHdM8uztxdPowg3a2f7VR7B26VPY4t-3c,5738
|
|
82
82
|
aixtools/vault/__init__.py,sha256=fsr_NuX3GZ9WZ7dGfe0gp_5-z3URxAfwVRXw7Xyc0dU,141
|
|
83
83
|
aixtools/vault/vault.py,sha256=9dZLWdZQk9qN_Q9Djkofw9LUKnJqnrX5H0fGusVLBhA,6037
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
notebooks/example_tool_doctor.ipynb,sha256=bWTlPNI1ZQStwMfr-KSkTGYckJuJmRG_e112Gr4KZ0I,1339
|
|
90
|
-
scripts/config.sh,sha256=xnA_S4p2w8fuIEPB4MiTWZdyIlCh5m4XuHY_fhE68kg,820
|
|
91
|
-
scripts/lint.sh,sha256=YmPcjfFVe2s-xSaddgSxOsSm9dmHnsmbiLZnuaPgXmY,744
|
|
92
|
-
scripts/log_view.sh,sha256=bp8oXFRRbbHpyvHAN85wfDHTVK7vMJOYsBx_-bgECQc,511
|
|
93
|
-
scripts/run_example_mcp_server.sh,sha256=f7m7h7O_wo6-nAsYlOXVWIASCOh3Qbuu0XWizlxMhl8,355
|
|
94
|
-
scripts/run_faulty_mcp_server.sh,sha256=u_-8NbPDnJQt6IinNSjh8tc2ed-_MjGyipJXrUXaGR8,291
|
|
95
|
-
scripts/run_server.sh,sha256=5iiB9bB5M2MuOgxVQqu7Oa_tBVtJpt0uB4z9uLu2J50,720
|
|
96
|
-
scripts/test.sh,sha256=KxXWkVqctFRNP8hItJr8K27nDHEkfwNWb1UFhpBQDOk,865
|
|
97
|
-
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
98
|
-
tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
99
|
-
tests/unit/a2a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
100
|
-
tests/unit/a2a/google_sdk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
101
|
-
tests/unit/a2a/google_sdk/test_card.py,sha256=g8OUIX9BCtApM9y8l_nL1Q7sm3yezxu5yBxrpy76mo4,4359
|
|
102
|
-
tests/unit/a2a/google_sdk/test_remote_agent_connection.py,sha256=nIY8eg32w96BAddaQ25mT-lr0ozPb6UrG-_Vpqx5RMY,17492
|
|
103
|
-
tests/unit/a2a/google_sdk/test_utils.py,sha256=-eHmIk2GJH57W2bAdTzfRrUUb5jnd9Pf-QSXJogN3g8,8312
|
|
104
|
-
tests/unit/a2a/google_sdk/pydantic_ai_adapter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
105
|
-
tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_agent_executor.py,sha256=PcyCw0N3y-txu2KJzufzbCjs7ZfoBBCVjpZuRBqTmOw,7722
|
|
106
|
-
tests/unit/a2a/google_sdk/pydantic_ai_adapter/test_storage.py,sha256=tb67pFfvyWSaDfKaiPDNBQfl6-o17WtCMZh3lQHrYxY,5468
|
|
107
|
-
tests/unit/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
108
|
-
tests/unit/agents/test_prompt.py,sha256=YWFZdH_F774hxw79gsWoTWBPVs8UjOAtJOgNXJ8N9gs,15384
|
|
109
|
-
tests/unit/compliance/test_private_data.py,sha256=GjH7NCp54Bz1S-CmH_mUe53lb53kllOOJEm448OniRI,13693
|
|
110
|
-
tests/unit/google/__init__.py,sha256=eRYHldBi5cFWL7oo2_t5TErI8ESmIjNvBZIcp-w8hSA,45
|
|
111
|
-
tests/unit/google/test_client.py,sha256=fXR4Cozea7bdL2prM-1s9IqUQ9AheklQnHpN-4YM3gg,11005
|
|
112
|
-
tests/unit/mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
113
|
-
tests/unit/mcp/test_client.py,sha256=n9sZvmzNzJfozvxoHweAg4M5ZLNhEizq16IjcZHGdj0,8838
|
|
114
|
-
tests/unit/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
115
|
-
tests/unit/server/test_path.py,sha256=1QKiKLLRga9GNxmaUEt_wEZ9U14yzB-7PIhAOgB4wwo,9523
|
|
116
|
-
tests/unit/server/test_utils.py,sha256=kvhzdgNfsJl5tqcRBWg2yTR5GPpyrFCOmEIOuHb3904,14848
|
|
117
|
-
tests/unit/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
118
|
-
tests/unit/utils/test_files.py,sha256=AKFmXQqXstyKd2PreE4EmQyhQYeqOmu1Sp80MwHrf_Q,5782
|
|
119
|
-
tests/unit/vault/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
120
|
-
tests/unit/vault/test_vault.py,sha256=T9V2Opxl3N5sJPftw0Q4lnVOs6urGpAmffe0cz6PUfw,10445
|
|
121
|
-
aixtools-0.1.10.dist-info/METADATA,sha256=BvV1AzDpgiy7rjt6AFyUdAiDOwt_mN8CmmEo0JkM4C4,18570
|
|
122
|
-
aixtools-0.1.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
123
|
-
aixtools-0.1.10.dist-info/entry_points.txt,sha256=dHoutULEZx7xXSqJrZdViSVjfInJibfLibi2nRXL3SE,56
|
|
124
|
-
aixtools-0.1.10.dist-info/top_level.txt,sha256=ee4eF-0pqu45zCUVml0mWIhnXQgqMQper2-49BBVHLY,40
|
|
125
|
-
aixtools-0.1.10.dist-info/RECORD,,
|
|
84
|
+
aixtools-0.1.11.dist-info/METADATA,sha256=4mR141qz8BCJbuQy5iTwN0yikrPEKeUb8XPInS5CC6k,18598
|
|
85
|
+
aixtools-0.1.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
86
|
+
aixtools-0.1.11.dist-info/entry_points.txt,sha256=dHoutULEZx7xXSqJrZdViSVjfInJibfLibi2nRXL3SE,56
|
|
87
|
+
aixtools-0.1.11.dist-info/top_level.txt,sha256=wBn-rw9bCtxrR4AYEYgjilNCUVmKY0LWby9Zan2PRJM,9
|
|
88
|
+
aixtools-0.1.11.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
aixtools
|
docker/mcp-base/Dockerfile
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
FROM ubuntu:22.04
|
|
2
|
-
|
|
3
|
-
RUN apt-get -y update && \
|
|
4
|
-
apt-get -y install ca-certificates curl gcc git libcap2-bin sudo
|
|
5
|
-
RUN mv /usr/bin/sudo /usr/sbin
|
|
6
|
-
|
|
7
|
-
# Add Zscaler CA certificate
|
|
8
|
-
COPY ./zscaler.crt /usr/local/share/ca-certificates/
|
|
9
|
-
RUN update-ca-certificates
|
|
10
|
-
ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
|
|
11
|
-
ENV CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
|
|
12
|
-
|
|
13
|
-
# Install `uv` Python package manager
|
|
14
|
-
RUN bash -o pipefail -c "curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=/usr/local/bin sh"
|
|
15
|
-
|
|
16
|
-
# Add a user matching the sandbox user so that the files and folders created by the MCP server
|
|
17
|
-
# are writable by the user in the sandbox containers (UID=1000 must match!)
|
|
18
|
-
ENV USER=mcp_user
|
|
19
|
-
RUN useradd -m -s /bin/bash -u 1000 ${USER} && \
|
|
20
|
-
echo "${USER} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/${USER} && chmod 0440 /etc/sudoers.d/${USER}
|
|
21
|
-
USER ${USER}
|
|
22
|
-
|
|
23
|
-
# Build argument: GITHUB_PAT (optional)
|
|
24
|
-
# If provided, this GitHub Personal Access Token will be used to authenticate git
|
|
25
|
-
# operations against github.com during the build. This is useful for accessing private repositories such as `aixtools`.
|
|
26
|
-
# If not set, git will use unauthenticated access (public repositories only).
|
|
27
|
-
ARG GITHUB_PAT
|
|
28
|
-
RUN if [ -n "$GITHUB_PAT" ]; then \
|
|
29
|
-
git config --global url."https://x-access-token:${GITHUB_PAT}@github.com/".insteadOf "https://github.com/"; \
|
|
30
|
-
fi
|
|
31
|
-
|
|
32
|
-
WORKDIR /app
|
|
33
|
-
RUN mkdir data
|
docker/mcp-base/zscaler.crt
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
-----BEGIN CERTIFICATE-----
|
|
2
|
-
MIIE0zCCA7ugAwIBAgIJANu+mC2Jt3uTMA0GCSqGSIb3DQEBCwUAMIGhMQswCQYD
|
|
3
|
-
VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTERMA8GA1UEBxMIU2FuIEpvc2Ux
|
|
4
|
-
FTATBgNVBAoTDFpzY2FsZXIgSW5jLjEVMBMGA1UECxMMWnNjYWxlciBJbmMuMRgw
|
|
5
|
-
FgYDVQQDEw9ac2NhbGVyIFJvb3QgQ0ExIjAgBgkqhkiG9w0BCQEWE3N1cHBvcnRA
|
|
6
|
-
enNjYWxlci5jb20wHhcNMTQxMjE5MDAyNzU1WhcNNDIwNTA2MDAyNzU1WjCBoTEL
|
|
7
|
-
MAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExETAPBgNVBAcTCFNhbiBK
|
|
8
|
-
b3NlMRUwEwYDVQQKEwxac2NhbGVyIEluYy4xFTATBgNVBAsTDFpzY2FsZXIgSW5j
|
|
9
|
-
LjEYMBYGA1UEAxMPWnNjYWxlciBSb290IENBMSIwIAYJKoZIhvcNAQkBFhNzdXBw
|
|
10
|
-
b3J0QHpzY2FsZXIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
|
|
11
|
-
qT7STSxZRTgEFFf6doHajSc1vk5jmzmM6BWuOo044EsaTc9eVEV/HjH/1DWzZtcr
|
|
12
|
-
fTj+ni205apMTlKBW3UYR+lyLHQ9FoZiDXYXK8poKSV5+Tm0Vls/5Kb8mkhVVqv7
|
|
13
|
-
LgYEmvEY7HPY+i1nEGZCa46ZXCOohJ0mBEtB9JVlpDIO+nN0hUMAYYdZ1KZWCMNf
|
|
14
|
-
5J/aTZiShsorN2A38iSOhdd+mcRM4iNL3gsLu99XhKnRqKoHeH83lVdfu1XBeoQz
|
|
15
|
-
z5V6gA3kbRvhDwoIlTBeMa5l4yRdJAfdpkbFzqiwSgNdhbxTHnYYorDzKfr2rEFM
|
|
16
|
-
dsMU0DHdeAZf711+1CunuQIDAQABo4IBCjCCAQYwHQYDVR0OBBYEFLm33UrNww4M
|
|
17
|
-
hp1d3+wcBGnFTpjfMIHWBgNVHSMEgc4wgcuAFLm33UrNww4Mhp1d3+wcBGnFTpjf
|
|
18
|
-
oYGnpIGkMIGhMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTERMA8G
|
|
19
|
-
A1UEBxMIU2FuIEpvc2UxFTATBgNVBAoTDFpzY2FsZXIgSW5jLjEVMBMGA1UECxMM
|
|
20
|
-
WnNjYWxlciBJbmMuMRgwFgYDVQQDEw9ac2NhbGVyIFJvb3QgQ0ExIjAgBgkqhkiG
|
|
21
|
-
9w0BCQEWE3N1cHBvcnRAenNjYWxlci5jb22CCQDbvpgtibd7kzAMBgNVHRMEBTAD
|
|
22
|
-
AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAw0NdJh8w3NsJu4KHuVZUrmZgIohnTm0j+
|
|
23
|
-
RTmYQ9IKA/pvxAcA6K1i/LO+Bt+tCX+C0yxqB8qzuo+4vAzoY5JEBhyhBhf1uK+P
|
|
24
|
-
/WVWFZN/+hTgpSbZgzUEnWQG2gOVd24msex+0Sr7hyr9vn6OueH+jj+vCMiAm5+u
|
|
25
|
-
kd7lLvJsBu3AO3jGWVLyPkS3i6Gf+rwAp1OsRrv3WnbkYcFf9xjuaf4z0hRCrLN2
|
|
26
|
-
xFNjavxrHmsH8jPHVvgc1VD0Opja0l/BRVauTrUaoW6tE+wFG5rEcPGS80jjHK4S
|
|
27
|
-
pB5iDj2mUZH1T8lzYtuZy0ZPirxmtsk3135+CKNa2OCAhhFjE0xd
|
|
28
|
-
-----END CERTIFICATE-----
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"cells": [
|
|
3
|
-
{
|
|
4
|
-
"cell_type": "markdown",
|
|
5
|
-
"id": "57d36f62",
|
|
6
|
-
"metadata": {},
|
|
7
|
-
"source": [
|
|
8
|
-
"# Example \"Faulty MCP Server\"\n",
|
|
9
|
-
"\n",
|
|
10
|
-
"Run the server using the script:\n",
|
|
11
|
-
"\n",
|
|
12
|
-
"```bash\n",
|
|
13
|
-
"./scripts/run_faulty_mcp_server.sh\n",
|
|
14
|
-
"``` "
|
|
15
|
-
]
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"cell_type": "code",
|
|
19
|
-
"execution_count": null,
|
|
20
|
-
"id": "ebf6d915",
|
|
21
|
-
"metadata": {},
|
|
22
|
-
"outputs": [],
|
|
23
|
-
"source": [
|
|
24
|
-
"from aixtools.agents.agent import get_agent, run_agent\n",
|
|
25
|
-
"from pydantic_ai.mcp import MCPServerStreamableHTTP"
|
|
26
|
-
]
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"cell_type": "code",
|
|
30
|
-
"execution_count": null,
|
|
31
|
-
"id": "97d1e607",
|
|
32
|
-
"metadata": {},
|
|
33
|
-
"outputs": [],
|
|
34
|
-
"source": [
|
|
35
|
-
"server = MCPServerStreamableHTTP(\"http://localhost:9999/mcp/\")\n",
|
|
36
|
-
"agent = get_agent(mcp_servers=[server])"
|
|
37
|
-
]
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"cell_type": "code",
|
|
41
|
-
"execution_count": null,
|
|
42
|
-
"id": "fc560d25",
|
|
43
|
-
"metadata": {},
|
|
44
|
-
"outputs": [],
|
|
45
|
-
"source": [
|
|
46
|
-
"async with agent:\n",
|
|
47
|
-
" # ret = await run_agent(agent, \"What is the add of 40123456789 and 2123456789?\", verbose=True, debug=True)\n",
|
|
48
|
-
" # ret = await run_agent(agent, \"Invoke the always_error tool\", verbose=True, debug=True)\n",
|
|
49
|
-
" ret = await run_agent(agent, \"Invoke the throw_404_exception tool\", verbose=True, debug=True)"
|
|
50
|
-
]
|
|
51
|
-
}
|
|
52
|
-
],
|
|
53
|
-
"metadata": {
|
|
54
|
-
"kernelspec": {
|
|
55
|
-
"display_name": ".venv",
|
|
56
|
-
"language": "python",
|
|
57
|
-
"name": "python3"
|
|
58
|
-
},
|
|
59
|
-
"language_info": {
|
|
60
|
-
"codemirror_mode": {
|
|
61
|
-
"name": "ipython",
|
|
62
|
-
"version": 3
|
|
63
|
-
},
|
|
64
|
-
"file_extension": ".py",
|
|
65
|
-
"mimetype": "text/x-python",
|
|
66
|
-
"name": "python",
|
|
67
|
-
"nbconvert_exporter": "python",
|
|
68
|
-
"pygments_lexer": "ipython3",
|
|
69
|
-
"version": "3.12.2"
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
"nbformat": 4,
|
|
73
|
-
"nbformat_minor": 5
|
|
74
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"cells": [
|
|
3
|
-
{
|
|
4
|
-
"cell_type": "markdown",
|
|
5
|
-
"id": "57d36f62",
|
|
6
|
-
"metadata": {},
|
|
7
|
-
"source": [
|
|
8
|
-
"# Example MCP Server STDIO"
|
|
9
|
-
]
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
"cell_type": "code",
|
|
13
|
-
"execution_count": null,
|
|
14
|
-
"id": "ebf6d915",
|
|
15
|
-
"metadata": {},
|
|
16
|
-
"outputs": [],
|
|
17
|
-
"source": [
|
|
18
|
-
"from aixtools.agents.agent import get_agent, run_agent\n",
|
|
19
|
-
"from aixtools.utils.config import PROJECT_DIR\n",
|
|
20
|
-
"from pydantic_ai.mcp import MCPServerStdio"
|
|
21
|
-
]
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"cell_type": "code",
|
|
25
|
-
"execution_count": null,
|
|
26
|
-
"id": "97d1e607",
|
|
27
|
-
"metadata": {},
|
|
28
|
-
"outputs": [],
|
|
29
|
-
"source": [
|
|
30
|
-
"mcp_path = PROJECT_DIR /'aixtools' / 'mcp' / 'example_server.py'\n",
|
|
31
|
-
"server = MCPServerStdio(command='fastmcp', args=['run', str(mcp_path)])\n",
|
|
32
|
-
"agent = get_agent(mcp_servers=[server])"
|
|
33
|
-
]
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"cell_type": "code",
|
|
37
|
-
"execution_count": null,
|
|
38
|
-
"id": "fc560d25",
|
|
39
|
-
"metadata": {},
|
|
40
|
-
"outputs": [],
|
|
41
|
-
"source": [
|
|
42
|
-
"async with agent:\n",
|
|
43
|
-
" ret = await run_agent(agent, \"What is the add of 40123456789 and 2123456789?\", verbose=True, debug=True)"
|
|
44
|
-
]
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
"cell_type": "code",
|
|
48
|
-
"execution_count": null,
|
|
49
|
-
"id": "cad32e85",
|
|
50
|
-
"metadata": {},
|
|
51
|
-
"outputs": [],
|
|
52
|
-
"source": []
|
|
53
|
-
}
|
|
54
|
-
],
|
|
55
|
-
"metadata": {
|
|
56
|
-
"kernelspec": {
|
|
57
|
-
"display_name": ".venv",
|
|
58
|
-
"language": "python",
|
|
59
|
-
"name": "python3"
|
|
60
|
-
},
|
|
61
|
-
"language_info": {
|
|
62
|
-
"codemirror_mode": {
|
|
63
|
-
"name": "ipython",
|
|
64
|
-
"version": 3
|
|
65
|
-
},
|
|
66
|
-
"file_extension": ".py",
|
|
67
|
-
"mimetype": "text/x-python",
|
|
68
|
-
"name": "python",
|
|
69
|
-
"nbconvert_exporter": "python",
|
|
70
|
-
"pygments_lexer": "ipython3",
|
|
71
|
-
"version": "3.12.2"
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
"nbformat": 4,
|
|
75
|
-
"nbformat_minor": 5
|
|
76
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"cells": [
|
|
3
|
-
{
|
|
4
|
-
"cell_type": "markdown",
|
|
5
|
-
"id": "57d36f62",
|
|
6
|
-
"metadata": {},
|
|
7
|
-
"source": [
|
|
8
|
-
"# Example \"Raw MCP Client\"\n",
|
|
9
|
-
"\n",
|
|
10
|
-
"Run the server using the script:\n",
|
|
11
|
-
"\n",
|
|
12
|
-
"```bash\n",
|
|
13
|
-
"./scripts/run_faulty_mcp_server.sh\n",
|
|
14
|
-
"``` "
|
|
15
|
-
]
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"cell_type": "code",
|
|
19
|
-
"execution_count": null,
|
|
20
|
-
"id": "cad32e85",
|
|
21
|
-
"metadata": {},
|
|
22
|
-
"outputs": [],
|
|
23
|
-
"source": [
|
|
24
|
-
"from fastmcp import Client\n",
|
|
25
|
-
"import rich\n",
|
|
26
|
-
"\n",
|
|
27
|
-
"client = Client(\"http://localhost:9999/mcp/\")"
|
|
28
|
-
]
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"cell_type": "code",
|
|
32
|
-
"execution_count": null,
|
|
33
|
-
"id": "e372fc6a",
|
|
34
|
-
"metadata": {},
|
|
35
|
-
"outputs": [],
|
|
36
|
-
"source": [
|
|
37
|
-
"async with client:\n",
|
|
38
|
-
" # Basic server interaction\n",
|
|
39
|
-
" await client.ping()\n",
|
|
40
|
-
" \n",
|
|
41
|
-
" # # List available operations\n",
|
|
42
|
-
" # tools = await client.list_tools()\n",
|
|
43
|
-
" # rich.print(tools)\n",
|
|
44
|
-
" # \n",
|
|
45
|
-
" # # Execute 'add' tool\n",
|
|
46
|
-
" # result = await client.call_tool(\"add\", {\"a\": 7, \"b\": 3})\n",
|
|
47
|
-
" # rich.print(result)\n",
|
|
48
|
-
"\n",
|
|
49
|
-
" # Execute 'add' tool\n",
|
|
50
|
-
" result = await client.call_tool(\"throw_404_exception\", {})\n",
|
|
51
|
-
" rich.print(result)\n"
|
|
52
|
-
]
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
"cell_type": "code",
|
|
56
|
-
"execution_count": null,
|
|
57
|
-
"id": "a8a743e2",
|
|
58
|
-
"metadata": {},
|
|
59
|
-
"outputs": [],
|
|
60
|
-
"source": []
|
|
61
|
-
}
|
|
62
|
-
],
|
|
63
|
-
"metadata": {
|
|
64
|
-
"kernelspec": {
|
|
65
|
-
"display_name": ".venv",
|
|
66
|
-
"language": "python",
|
|
67
|
-
"name": "python3"
|
|
68
|
-
},
|
|
69
|
-
"language_info": {
|
|
70
|
-
"codemirror_mode": {
|
|
71
|
-
"name": "ipython",
|
|
72
|
-
"version": 3
|
|
73
|
-
},
|
|
74
|
-
"file_extension": ".py",
|
|
75
|
-
"mimetype": "text/x-python",
|
|
76
|
-
"name": "python",
|
|
77
|
-
"nbconvert_exporter": "python",
|
|
78
|
-
"pygments_lexer": "ipython3",
|
|
79
|
-
"version": "3.12.2"
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
"nbformat": 4,
|
|
83
|
-
"nbformat_minor": 5
|
|
84
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"cells": [
|
|
3
|
-
{
|
|
4
|
-
"cell_type": "markdown",
|
|
5
|
-
"id": "dab47237",
|
|
6
|
-
"metadata": {},
|
|
7
|
-
"source": [
|
|
8
|
-
"# Tool doctor example"
|
|
9
|
-
]
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
"cell_type": "code",
|
|
13
|
-
"execution_count": null,
|
|
14
|
-
"id": "9735e38c",
|
|
15
|
-
"metadata": {},
|
|
16
|
-
"outputs": [],
|
|
17
|
-
"source": [
|
|
18
|
-
"# Example 1: A reasonably well-defined tool\n",
|
|
19
|
-
"from aixtools.tools.doctor import tool_doctor\n",
|
|
20
|
-
"\n",
|
|
21
|
-
"\n",
|
|
22
|
-
"def add(a: int, b: int) -> int:\n",
|
|
23
|
-
" \"\"\" Add two numbers \"\"\"\n",
|
|
24
|
-
" return a + b\n",
|
|
25
|
-
"\n",
|
|
26
|
-
"# Example 2: A poorly defined tool\n",
|
|
27
|
-
"def z(a: int, b: int) -> int:\n",
|
|
28
|
-
" \"\"\" performs some arithmentic with two parameters \"\"\"\n",
|
|
29
|
-
" return a + b\n"
|
|
30
|
-
]
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
"cell_type": "code",
|
|
34
|
-
"execution_count": null,
|
|
35
|
-
"id": "e50d48d6",
|
|
36
|
-
"metadata": {},
|
|
37
|
-
"outputs": [],
|
|
38
|
-
"source": [
|
|
39
|
-
"# Call tool doctor\n",
|
|
40
|
-
"ret = await tool_doctor([add, z])"
|
|
41
|
-
]
|
|
42
|
-
}
|
|
43
|
-
],
|
|
44
|
-
"metadata": {
|
|
45
|
-
"kernelspec": {
|
|
46
|
-
"display_name": ".venv",
|
|
47
|
-
"language": "python",
|
|
48
|
-
"name": "python3"
|
|
49
|
-
},
|
|
50
|
-
"language_info": {
|
|
51
|
-
"codemirror_mode": {
|
|
52
|
-
"name": "ipython",
|
|
53
|
-
"version": 3
|
|
54
|
-
},
|
|
55
|
-
"file_extension": ".py",
|
|
56
|
-
"mimetype": "text/x-python",
|
|
57
|
-
"name": "python",
|
|
58
|
-
"nbconvert_exporter": "python",
|
|
59
|
-
"pygments_lexer": "ipython3",
|
|
60
|
-
"version": "3.12.2"
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
"nbformat": 4,
|
|
64
|
-
"nbformat_minor": 5
|
|
65
|
-
}
|
scripts/config.sh
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
#-----------------------------------------------------------------------------
|
|
2
|
-
#
|
|
3
|
-
# This template script sets up the environment for the project by defining the
|
|
4
|
-
# project directory and activating the virtual environment.
|
|
5
|
-
#
|
|
6
|
-
#-----------------------------------------------------------------------------
|
|
7
|
-
|
|
8
|
-
export SCRIPTS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
9
|
-
|
|
10
|
-
# Project directory
|
|
11
|
-
export PROJECT_DIR="$( cd $SCRIPTS_DIR/.. && pwd -P )"
|
|
12
|
-
export PROJECT_NAME="$(basename $PROJECT_DIR)"
|
|
13
|
-
|
|
14
|
-
# Data directories
|
|
15
|
-
export DATA_DIR="$PROJECT_DIR/data"
|
|
16
|
-
export LOGS_DIR="$PROJECT_DIR/logs"
|
|
17
|
-
export PG_DATA_DIR="$DATA_DIR/data/db/postgres"
|
|
18
|
-
|
|
19
|
-
# Server configuration
|
|
20
|
-
export PORT=8081
|
|
21
|
-
|
|
22
|
-
# Activate virtual environment
|
|
23
|
-
if [ "${OS-}" == "Windows_NT" ]; then
|
|
24
|
-
source .venv/Scripts/activate
|
|
25
|
-
else
|
|
26
|
-
source .venv/bin/activate
|
|
27
|
-
fi
|
|
28
|
-
|