aixtools 0.0.0__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/.chainlit/config.toml +113 -0
- aixtools/.chainlit/translations/bn.json +214 -0
- aixtools/.chainlit/translations/en-US.json +214 -0
- aixtools/.chainlit/translations/gu.json +214 -0
- aixtools/.chainlit/translations/he-IL.json +214 -0
- aixtools/.chainlit/translations/hi.json +214 -0
- aixtools/.chainlit/translations/ja.json +214 -0
- aixtools/.chainlit/translations/kn.json +214 -0
- aixtools/.chainlit/translations/ml.json +214 -0
- aixtools/.chainlit/translations/mr.json +214 -0
- aixtools/.chainlit/translations/nl.json +214 -0
- aixtools/.chainlit/translations/ta.json +214 -0
- aixtools/.chainlit/translations/te.json +214 -0
- aixtools/.chainlit/translations/zh-CN.json +214 -0
- aixtools/__init__.py +11 -0
- aixtools/_version.py +34 -0
- aixtools/a2a/app.py +126 -0
- aixtools/a2a/google_sdk/__init__.py +0 -0
- aixtools/a2a/google_sdk/card.py +27 -0
- aixtools/a2a/google_sdk/pydantic_ai_adapter/agent_executor.py +199 -0
- aixtools/a2a/google_sdk/pydantic_ai_adapter/storage.py +26 -0
- aixtools/a2a/google_sdk/remote_agent_connection.py +88 -0
- aixtools/a2a/google_sdk/utils.py +59 -0
- aixtools/a2a/utils.py +115 -0
- aixtools/agents/__init__.py +12 -0
- aixtools/agents/agent.py +164 -0
- aixtools/agents/agent_batch.py +71 -0
- aixtools/agents/prompt.py +97 -0
- aixtools/app.py +143 -0
- aixtools/chainlit.md +14 -0
- aixtools/compliance/__init__.py +9 -0
- aixtools/compliance/private_data.py +138 -0
- aixtools/context.py +17 -0
- aixtools/db/__init__.py +17 -0
- aixtools/db/database.py +110 -0
- aixtools/db/vector_db.py +115 -0
- aixtools/google/client.py +25 -0
- aixtools/log_view/__init__.py +17 -0
- aixtools/log_view/app.py +195 -0
- aixtools/log_view/display.py +285 -0
- aixtools/log_view/export.py +51 -0
- aixtools/log_view/filters.py +41 -0
- aixtools/log_view/log_utils.py +26 -0
- aixtools/log_view/node_summary.py +229 -0
- aixtools/logfilters/__init__.py +7 -0
- aixtools/logfilters/context_filter.py +67 -0
- aixtools/logging/__init__.py +30 -0
- aixtools/logging/log_objects.py +227 -0
- aixtools/logging/logging_config.py +161 -0
- aixtools/logging/mcp_log_models.py +102 -0
- aixtools/logging/mcp_logger.py +172 -0
- aixtools/logging/model_patch_logging.py +87 -0
- aixtools/logging/open_telemetry.py +36 -0
- aixtools/mcp/__init__.py +9 -0
- aixtools/mcp/client.py +375 -0
- aixtools/mcp/example_client.py +30 -0
- aixtools/mcp/example_server.py +22 -0
- aixtools/mcp/fast_mcp_log.py +31 -0
- aixtools/mcp/faulty_mcp.py +319 -0
- aixtools/model_patch/model_patch.py +63 -0
- aixtools/server/__init__.py +29 -0
- aixtools/server/app_mounter.py +90 -0
- aixtools/server/path.py +72 -0
- aixtools/server/utils.py +70 -0
- aixtools/server/workspace_privacy.py +65 -0
- aixtools/testing/__init__.py +9 -0
- aixtools/testing/aix_test_model.py +149 -0
- aixtools/testing/mock_tool.py +66 -0
- aixtools/testing/model_patch_cache.py +279 -0
- aixtools/tools/doctor/__init__.py +3 -0
- aixtools/tools/doctor/tool_doctor.py +61 -0
- aixtools/tools/doctor/tool_recommendation.py +44 -0
- aixtools/utils/__init__.py +35 -0
- aixtools/utils/chainlit/cl_agent_show.py +82 -0
- aixtools/utils/chainlit/cl_utils.py +168 -0
- aixtools/utils/config.py +131 -0
- aixtools/utils/config_util.py +69 -0
- aixtools/utils/enum_with_description.py +37 -0
- aixtools/utils/files.py +17 -0
- aixtools/utils/persisted_dict.py +99 -0
- aixtools/utils/utils.py +167 -0
- aixtools/vault/__init__.py +7 -0
- aixtools/vault/vault.py +137 -0
- aixtools-0.0.0.dist-info/METADATA +669 -0
- aixtools-0.0.0.dist-info/RECORD +88 -0
- aixtools-0.0.0.dist-info/WHEEL +5 -0
- aixtools-0.0.0.dist-info/entry_points.txt +2 -0
- aixtools-0.0.0.dist-info/top_level.txt +1 -0
aixtools/utils/utils.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
General utility functions for string manipulation, logging, and data handling.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import uuid
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
DF_SHOW_MAX_ROWS = 20
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def escape_newline(s, max_length: int = 300) -> str:
|
|
16
|
+
"""Escape newlines in a string."""
|
|
17
|
+
s = str(s)
|
|
18
|
+
ss = "\\n".join(s.split("\n"))
|
|
19
|
+
if len(ss) <= max_length:
|
|
20
|
+
return ss
|
|
21
|
+
return "".join(ss[:max_length]) + "..."
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def escape_backticks(s) -> str:
|
|
25
|
+
"""Escape backticks in a string."""
|
|
26
|
+
s = f"{s}"
|
|
27
|
+
return s.replace("`", "\\`")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def find_file(path: Path, glob="*.pdf"):
|
|
31
|
+
"""Recursively find all files matching the glob pattern in a directory"""
|
|
32
|
+
yield from path.rglob(glob)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def prepend_all_lines(msg, prepend="\t", skip_first_line: bool = False) -> str:
|
|
36
|
+
"""Prepend all lines of a message with a prepend."""
|
|
37
|
+
out = ""
|
|
38
|
+
for i, line in enumerate(str(msg).split("\n")):
|
|
39
|
+
if i == 0 and skip_first_line:
|
|
40
|
+
out += f"{line}\n"
|
|
41
|
+
else:
|
|
42
|
+
out += f"{prepend}{line}\n"
|
|
43
|
+
return out
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def remove_quotes(s):
|
|
47
|
+
"""
|
|
48
|
+
Remove all quotes (including triple backticks with language specifications) surrounding a string.
|
|
49
|
+
|
|
50
|
+
This function strips the input string of all surrounding quotes, including single quotes, double quotes,
|
|
51
|
+
backticks, and triple backticks with or without language specifications. It continues to remove quotes
|
|
52
|
+
until none are left.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
s (str): The input string potentially surrounded by quotes.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
str: The string with all surrounding quotes removed. If the input is None, returns None.
|
|
59
|
+
"""
|
|
60
|
+
if s is None:
|
|
61
|
+
return None
|
|
62
|
+
s = s.strip()
|
|
63
|
+
while (
|
|
64
|
+
(s.startswith('"') and s.endswith('"'))
|
|
65
|
+
or (s.startswith("'") and s.endswith("'"))
|
|
66
|
+
or (s.startswith("`") and s.endswith("`"))
|
|
67
|
+
or ("```" in s)
|
|
68
|
+
):
|
|
69
|
+
if "```" in s:
|
|
70
|
+
s = tripple_quote_strip(s)
|
|
71
|
+
else:
|
|
72
|
+
# Single quotes
|
|
73
|
+
s = s[1:-1].strip()
|
|
74
|
+
# Remove spaces
|
|
75
|
+
s = s.strip()
|
|
76
|
+
return s
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def tabit(s: str, prefix="\t|") -> str:
|
|
80
|
+
"""Add a prefix to each line of a string for improved readability."""
|
|
81
|
+
return prefix + str(s).replace("\n", f"\n{prefix}")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def to_str(data) -> str:
|
|
85
|
+
"""Convert any data type to a readable string representation."""
|
|
86
|
+
# Primitive values, just use str()
|
|
87
|
+
if isinstance(data, str):
|
|
88
|
+
return f"'{str}'"
|
|
89
|
+
if isinstance(data, (bool, bytes, float, int)):
|
|
90
|
+
return str(data)
|
|
91
|
+
# Dataframes
|
|
92
|
+
if isinstance(data, pd.DataFrame):
|
|
93
|
+
if data.shape[0] > DF_SHOW_MAX_ROWS:
|
|
94
|
+
return f"Showing only the first {DF_SHOW_MAX_ROWS} rows out of {data.shape[0]}:\n" + data.head(
|
|
95
|
+
DF_SHOW_MAX_ROWS
|
|
96
|
+
).to_markdown(index=False)
|
|
97
|
+
return data.to_markdown(index=False)
|
|
98
|
+
# Use json for list, dict, etc.
|
|
99
|
+
if isinstance(data, (dict, list, tuple)):
|
|
100
|
+
return json.dumps(data, indent=2, default=str)
|
|
101
|
+
return str(data)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def truncate(s: str, max_len=76, ellipsis="...") -> str:
|
|
105
|
+
"""Truncate a string to a maximum length, adding ellipsis if needed."""
|
|
106
|
+
s = str(s)
|
|
107
|
+
if len(s) > max_len:
|
|
108
|
+
return s[: max_len - len(ellipsis)] + ellipsis
|
|
109
|
+
return s
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def tripple_quote_strip(s):
|
|
113
|
+
"""
|
|
114
|
+
Remove triple quotes from a string, including those with language specifications.
|
|
115
|
+
|
|
116
|
+
Eexamples:
|
|
117
|
+
```sql SELECT * from table;```
|
|
118
|
+
|
|
119
|
+
```Here is your code ```python c = a + b ``` This code will perform addition```
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
s (str): The input string potentially containing triple quotes.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
str: The string with triple quotes removed, if present.
|
|
126
|
+
"""
|
|
127
|
+
if "```" not in s:
|
|
128
|
+
return s
|
|
129
|
+
left_pos, right_pos = len(s), s.rfind("```")
|
|
130
|
+
s_lower = s.lower()
|
|
131
|
+
pre_matched = ""
|
|
132
|
+
for lang in ["python3", "python2", "python", "bash", "json", "sql", ""]:
|
|
133
|
+
pre = f"```{lang}"
|
|
134
|
+
idx = s_lower.find(pre)
|
|
135
|
+
if idx != -1 and idx < left_pos:
|
|
136
|
+
left_pos = idx
|
|
137
|
+
pre_matched = pre
|
|
138
|
+
if left_pos < right_pos:
|
|
139
|
+
s = s[left_pos + len(pre_matched) : right_pos].strip()
|
|
140
|
+
return s
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def timestamp_with_uuid() -> str:
|
|
144
|
+
"""Get a timestamp string + a UUID string (first 8 chars)."""
|
|
145
|
+
(yyy, hh, uu) = timestamp_uuid_tuple()
|
|
146
|
+
return f"{yyy}.{hh}.{uu[:8]}"
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def timestamp_uuid_tuple() -> tuple[str, str, str]:
|
|
150
|
+
"""
|
|
151
|
+
Get a tuple of timestamp + a UUID: (YYYY-MM-DD, HH:MM:SS, UUID)
|
|
152
|
+
"""
|
|
153
|
+
now = datetime.now()
|
|
154
|
+
return (now.strftime("%Y-%m-%d"), now.strftime("%H:%M:%S"), str(uuid.uuid4()))
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def str2bool(v: str | None) -> bool:
|
|
158
|
+
"""Convert a string to a boolean value."""
|
|
159
|
+
if not v:
|
|
160
|
+
return False
|
|
161
|
+
return str(v).lower() in ("yes", "true", "on", "1")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
async def async_iter(items):
|
|
165
|
+
"""Asynchronously iterate over items."""
|
|
166
|
+
for item in items:
|
|
167
|
+
yield item
|
aixtools/vault/vault.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# ruff: noqa: PLR0913
|
|
2
|
+
"""
|
|
3
|
+
Provides a Vault client for storing and retrieving user service api keys.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Dict, Optional
|
|
8
|
+
|
|
9
|
+
import hvac
|
|
10
|
+
from hvac.exceptions import InvalidPath
|
|
11
|
+
|
|
12
|
+
from aixtools.utils import config
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class VaultAuthError(Exception):
|
|
18
|
+
"""Exception raised for vault authentication errors."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class VaultClient:
|
|
22
|
+
"""Vault client for storing and retrieving user service api keys."""
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self.client = hvac.Client(url=config.VAULT_ADDRESS, token=config.VAULT_TOKEN)
|
|
26
|
+
|
|
27
|
+
if not self.client.is_authenticated():
|
|
28
|
+
raise VaultAuthError("Vault client authentication failed. Check vault_token.")
|
|
29
|
+
|
|
30
|
+
def _get_secret_path(self, user_id: str, service_name: Optional[str] = None) -> str:
|
|
31
|
+
"""Generate the vault secret path for a user and optionally a service."""
|
|
32
|
+
if service_name:
|
|
33
|
+
return f"{config.VAULT_PATH_PREFIX}/{config.VAULT_ENV}/{user_id}/{service_name}"
|
|
34
|
+
return f"{config.VAULT_PATH_PREFIX}/{config.VAULT_ENV}/{user_id}"
|
|
35
|
+
|
|
36
|
+
def store_user_service_api_key(self, *, user_id: str, service_name: str, user_api_key: str):
|
|
37
|
+
"""
|
|
38
|
+
Store user's service api key in the Vault at the specified vault mount
|
|
39
|
+
point, where the path is <path_prefix>/<env>/<user_id>/<service_name>.
|
|
40
|
+
|
|
41
|
+
This is a convenience method for storing a single API key.
|
|
42
|
+
For storing multiple secrets, use store_user_service_secret().
|
|
43
|
+
"""
|
|
44
|
+
secret_dict = {"user-api-key": user_api_key}
|
|
45
|
+
self.store_user_service_secret(user_id=user_id, service_name=service_name, secret_data=secret_dict)
|
|
46
|
+
|
|
47
|
+
def read_user_service_api_key(self, *, user_id: str, service_name: str) -> Optional[str]:
|
|
48
|
+
"""
|
|
49
|
+
Read user's service api key in from vault at the specified mount point,
|
|
50
|
+
where the path is <path_prefix>/<env>/<user_id>/<service_name>.
|
|
51
|
+
|
|
52
|
+
This is a convenience method for reading a single API key.
|
|
53
|
+
For reading multiple secrets, use read_user_service_secret().
|
|
54
|
+
"""
|
|
55
|
+
secret_data = self.read_user_service_secret(user_id=user_id, service_name=service_name)
|
|
56
|
+
if secret_data is None:
|
|
57
|
+
return None
|
|
58
|
+
return secret_data.get("user-api-key")
|
|
59
|
+
|
|
60
|
+
def store_user_service_secret(self, *, user_id: str, service_name: str, secret_data: Dict[str, str]):
|
|
61
|
+
"""
|
|
62
|
+
Store complete user service secret with multiple key-value pairs in the Vault
|
|
63
|
+
at the specified vault mount point, where the path is <path_prefix>/<env>/<user_id>/<service_name>.
|
|
64
|
+
"""
|
|
65
|
+
secret_path = None
|
|
66
|
+
try:
|
|
67
|
+
secret_path = self._get_secret_path(user_id, service_name)
|
|
68
|
+
logger.info("Writing complete secret to path %s", secret_path)
|
|
69
|
+
self.client.secrets.kv.v2.create_or_update_secret(
|
|
70
|
+
secret_path, secret=secret_data, mount_point=config.VAULT_MOUNT_POINT
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
logger.info("Complete secret written to path %s", secret_path)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.error("Failed to write complete secret to path %s: %s", secret_path, str(e))
|
|
76
|
+
raise VaultAuthError(e) from e
|
|
77
|
+
|
|
78
|
+
def read_user_service_secret(self, *, user_id: str, service_name: str) -> Optional[Dict[str, str]]:
|
|
79
|
+
"""
|
|
80
|
+
Read complete user service secret from vault at the specified mount point,
|
|
81
|
+
where the path is <path_prefix>/<env>/<user_id>/<service_name>.
|
|
82
|
+
Returns all key-value pairs in the secret or None if the secret doesn't exist.
|
|
83
|
+
"""
|
|
84
|
+
secret_path = None
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
secret_path = self._get_secret_path(user_id, service_name)
|
|
88
|
+
logger.info("Reading complete secret from path %s", secret_path)
|
|
89
|
+
response = self.client.secrets.kv.v2.read_secret_version(
|
|
90
|
+
secret_path, mount_point=config.VAULT_MOUNT_POINT, raise_on_deleted_version=True
|
|
91
|
+
)
|
|
92
|
+
secret_data = response["data"]["data"]
|
|
93
|
+
logger.info("Complete secret read from path %s", secret_path)
|
|
94
|
+
return secret_data
|
|
95
|
+
except InvalidPath:
|
|
96
|
+
# Secret path does not exist
|
|
97
|
+
logger.warning("Secret path does not exist %s", secret_path)
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logger.error("Failed to read complete secret from path %s: %s", secret_path, str(e))
|
|
102
|
+
raise VaultAuthError(e) from e
|
|
103
|
+
|
|
104
|
+
def list_user_secret_keys(self, *, user_id: str) -> list[str]:
|
|
105
|
+
"""
|
|
106
|
+
List all secret keys (service names) for a user, optionally filtered by service name.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
user_id: The user ID to list secrets for
|
|
110
|
+
service_name: Optional service name to filter results. If provided, returns only this service if it exists.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
List of service names (secret keys) for the user. Empty list if no secrets exist.
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
# List all services for user
|
|
117
|
+
user_path = self._get_secret_path(user_id)
|
|
118
|
+
logger.info("Listing secret keys for user at path %s", user_path)
|
|
119
|
+
|
|
120
|
+
response = self.client.secrets.kv.v2.list_secrets(path=user_path, mount_point=config.VAULT_MOUNT_POINT)
|
|
121
|
+
|
|
122
|
+
if response and "data" in response and "keys" in response["data"]:
|
|
123
|
+
secret_keys = response["data"]["keys"]
|
|
124
|
+
# Remove trailing slashes from directory names if any
|
|
125
|
+
secret_keys = [key.rstrip("/") for key in secret_keys]
|
|
126
|
+
logger.info("Found %d secret keys for user %s", len(secret_keys), user_id)
|
|
127
|
+
return secret_keys
|
|
128
|
+
logger.info("No secret keys found for user %s", user_id)
|
|
129
|
+
return []
|
|
130
|
+
|
|
131
|
+
except InvalidPath:
|
|
132
|
+
# User path does not exist
|
|
133
|
+
logger.warning("User path does not exist for user %s", user_id)
|
|
134
|
+
return []
|
|
135
|
+
except Exception as e:
|
|
136
|
+
logger.error("Failed to list secret keys for user %s: %s", user_id, str(e))
|
|
137
|
+
raise VaultAuthError(e) from e
|