signalwire-agents 0.1.18__py3-none-any.whl → 0.1.20__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.
- signalwire_agents/__init__.py +1 -1
- signalwire_agents/cli/test_swaig.py +285 -142
- signalwire_agents/search/index_builder.py +48 -7
- signalwire_agents/search/query_processor.py +52 -11
- signalwire_agents/skills/datasphere_serverless/skill.py +0 -1
- signalwire_agents/skills/native_vector_search/skill.py +75 -38
- {signalwire_agents-0.1.18.dist-info → signalwire_agents-0.1.20.dist-info}/METADATA +17 -2
- {signalwire_agents-0.1.18.dist-info → signalwire_agents-0.1.20.dist-info}/RECORD +13 -13
- {signalwire_agents-0.1.18.dist-info → signalwire_agents-0.1.20.dist-info}/entry_points.txt +1 -1
- {signalwire_agents-0.1.18.data → signalwire_agents-0.1.20.data}/data/schema.json +0 -0
- {signalwire_agents-0.1.18.dist-info → signalwire_agents-0.1.20.dist-info}/WHEEL +0 -0
- {signalwire_agents-0.1.18.dist-info → signalwire_agents-0.1.20.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.18.dist-info → signalwire_agents-0.1.20.dist-info}/top_level.txt +0 -0
signalwire_agents/__init__.py
CHANGED
@@ -18,7 +18,7 @@ A package for building AI agents using SignalWire's AI and SWML capabilities.
|
|
18
18
|
from .core.logging_config import configure_logging
|
19
19
|
configure_logging()
|
20
20
|
|
21
|
-
__version__ = "0.1.
|
21
|
+
__version__ = "0.1.20"
|
22
22
|
|
23
23
|
# Import core classes for easier access
|
24
24
|
from .core.agent_base import AgentBase
|
@@ -1079,183 +1079,326 @@ def simple_template_expand(template: str, data: Dict[str, Any]) -> str:
|
|
1079
1079
|
def execute_datamap_function(datamap_config: Dict[str, Any], args: Dict[str, Any],
|
1080
1080
|
verbose: bool = False) -> Dict[str, Any]:
|
1081
1081
|
"""
|
1082
|
-
Execute a DataMap function
|
1082
|
+
Execute a DataMap function following the actual DataMap processing pipeline:
|
1083
|
+
1. Expressions (pattern matching)
|
1084
|
+
2. Webhooks (try each sequentially until one succeeds)
|
1085
|
+
3. Foreach (within successful webhook)
|
1086
|
+
4. Output (from successful webhook)
|
1087
|
+
5. Fallback output (if all webhooks fail)
|
1083
1088
|
|
1084
1089
|
Args:
|
1085
|
-
datamap_config:
|
1090
|
+
datamap_config: DataMap configuration dictionary
|
1086
1091
|
args: Function arguments
|
1087
|
-
verbose:
|
1092
|
+
verbose: Enable verbose output
|
1088
1093
|
|
1089
1094
|
Returns:
|
1090
|
-
|
1095
|
+
Function result (should be string or dict with 'response' key)
|
1091
1096
|
"""
|
1092
|
-
|
1093
|
-
|
1094
|
-
|
1095
|
-
|
1096
|
-
|
1097
|
-
|
1098
|
-
|
1099
|
-
|
1097
|
+
if verbose:
|
1098
|
+
print("=== DataMap Function Execution ===")
|
1099
|
+
print(f"Config: {json.dumps(datamap_config, indent=2)}")
|
1100
|
+
print(f"Args: {json.dumps(args, indent=2)}")
|
1101
|
+
|
1102
|
+
# Extract the actual data_map configuration
|
1103
|
+
# DataMap configs have the structure: {"function": "...", "data_map": {...}}
|
1104
|
+
actual_datamap = datamap_config.get("data_map", datamap_config)
|
1105
|
+
|
1106
|
+
if verbose:
|
1107
|
+
print(f"Extracted data_map: {json.dumps(actual_datamap, indent=2)}")
|
1108
|
+
|
1109
|
+
# Initialize context with function arguments
|
1110
|
+
context = {"args": args}
|
1111
|
+
context.update(args) # Also make args available at top level for backward compatibility
|
1112
|
+
|
1113
|
+
if verbose:
|
1114
|
+
print(f"Initial context: {json.dumps(context, indent=2)}")
|
1115
|
+
|
1116
|
+
# Step 1: Process expressions first (pattern matching)
|
1117
|
+
if "expressions" in actual_datamap:
|
1100
1118
|
if verbose:
|
1101
|
-
print(
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1119
|
+
print("\n--- Processing Expressions ---")
|
1120
|
+
for expr in actual_datamap["expressions"]:
|
1121
|
+
# Simple expression evaluation - in real implementation this would be more sophisticated
|
1122
|
+
if "pattern" in expr and "output" in expr:
|
1123
|
+
# For testing, we'll just match simple strings
|
1124
|
+
pattern = expr["pattern"]
|
1125
|
+
if pattern in str(args):
|
1126
|
+
if verbose:
|
1127
|
+
print(f"Expression matched: {pattern}")
|
1128
|
+
result = simple_template_expand(str(expr["output"]), context)
|
1129
|
+
if verbose:
|
1130
|
+
print(f"Expression result: {result}")
|
1131
|
+
return result
|
1132
|
+
|
1133
|
+
# Step 2: Process webhooks sequentially
|
1134
|
+
if "webhooks" in actual_datamap:
|
1107
1135
|
if verbose:
|
1108
|
-
print(
|
1136
|
+
print("\n--- Processing Webhooks ---")
|
1109
1137
|
|
1110
|
-
for
|
1111
|
-
|
1112
|
-
|
1138
|
+
for i, webhook in enumerate(actual_datamap["webhooks"]):
|
1139
|
+
if verbose:
|
1140
|
+
print(f"\n=== Webhook {i+1}/{len(actual_datamap['webhooks'])} ===")
|
1113
1141
|
|
1114
|
-
|
1115
|
-
|
1142
|
+
url = webhook.get("url", "")
|
1143
|
+
method = webhook.get("method", "POST").upper()
|
1144
|
+
headers = webhook.get("headers", {})
|
1145
|
+
|
1146
|
+
# Expand template variables in URL and headers
|
1147
|
+
url = simple_template_expand(url, context)
|
1148
|
+
expanded_headers = {}
|
1149
|
+
for key, value in headers.items():
|
1150
|
+
expanded_headers[key] = simple_template_expand(str(value), context)
|
1116
1151
|
|
1117
1152
|
if verbose:
|
1118
|
-
print(f"
|
1153
|
+
print(f"Making {method} request to: {url}")
|
1154
|
+
print(f"Headers: {json.dumps(expanded_headers, indent=2)}")
|
1155
|
+
|
1156
|
+
# Prepare request data
|
1157
|
+
request_data = None
|
1158
|
+
if method in ["POST", "PUT", "PATCH"]:
|
1159
|
+
# Check for 'params' (SignalWire style) or 'data' (generic style) or 'body'
|
1160
|
+
if "params" in webhook:
|
1161
|
+
# Expand template variables in params
|
1162
|
+
expanded_params = {}
|
1163
|
+
for key, value in webhook["params"].items():
|
1164
|
+
expanded_params[key] = simple_template_expand(str(value), context)
|
1165
|
+
request_data = json.dumps(expanded_params)
|
1166
|
+
elif "body" in webhook:
|
1167
|
+
# Expand template variables in body
|
1168
|
+
if isinstance(webhook["body"], str):
|
1169
|
+
request_data = simple_template_expand(webhook["body"], context)
|
1170
|
+
else:
|
1171
|
+
expanded_body = {}
|
1172
|
+
for key, value in webhook["body"].items():
|
1173
|
+
expanded_body[key] = simple_template_expand(str(value), context)
|
1174
|
+
request_data = json.dumps(expanded_body)
|
1175
|
+
elif "data" in webhook:
|
1176
|
+
# Expand template variables in data
|
1177
|
+
if isinstance(webhook["data"], str):
|
1178
|
+
request_data = simple_template_expand(webhook["data"], context)
|
1179
|
+
else:
|
1180
|
+
request_data = json.dumps(webhook["data"])
|
1181
|
+
|
1182
|
+
if verbose and request_data:
|
1183
|
+
print(f"Request data: {request_data}")
|
1184
|
+
|
1185
|
+
webhook_failed = False
|
1186
|
+
response_data = None
|
1119
1187
|
|
1120
|
-
# Use regex to match
|
1121
1188
|
try:
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
elif
|
1128
|
-
|
1189
|
+
# Make the HTTP request
|
1190
|
+
if method == "GET":
|
1191
|
+
response = requests.get(url, headers=expanded_headers, timeout=30)
|
1192
|
+
elif method == "POST":
|
1193
|
+
response = requests.post(url, data=request_data, headers=expanded_headers, timeout=30)
|
1194
|
+
elif method == "PUT":
|
1195
|
+
response = requests.put(url, data=request_data, headers=expanded_headers, timeout=30)
|
1196
|
+
elif method == "PATCH":
|
1197
|
+
response = requests.patch(url, data=request_data, headers=expanded_headers, timeout=30)
|
1198
|
+
elif method == "DELETE":
|
1199
|
+
response = requests.delete(url, headers=expanded_headers, timeout=30)
|
1200
|
+
else:
|
1201
|
+
raise ValueError(f"Unsupported HTTP method: {method}")
|
1202
|
+
|
1203
|
+
if verbose:
|
1204
|
+
print(f"Response status: {response.status_code}")
|
1205
|
+
print(f"Response headers: {dict(response.headers)}")
|
1206
|
+
|
1207
|
+
# Parse response
|
1208
|
+
try:
|
1209
|
+
response_data = response.json()
|
1210
|
+
except json.JSONDecodeError:
|
1211
|
+
response_data = {"text": response.text, "status_code": response.status_code}
|
1212
|
+
# Add parse_error like server does
|
1213
|
+
response_data["parse_error"] = True
|
1214
|
+
response_data["raw_response"] = response.text
|
1215
|
+
|
1216
|
+
if verbose:
|
1217
|
+
print(f"Response data: {json.dumps(response_data, indent=2)}")
|
1218
|
+
|
1219
|
+
# Check for webhook failure following server logic
|
1220
|
+
|
1221
|
+
# 1. Check HTTP status code (fix the server bug - should be OR not AND)
|
1222
|
+
if response.status_code < 200 or response.status_code > 299:
|
1223
|
+
webhook_failed = True
|
1129
1224
|
if verbose:
|
1130
|
-
print(f"
|
1131
|
-
|
1132
|
-
|
1225
|
+
print(f"Webhook failed: HTTP status {response.status_code} outside 200-299 range")
|
1226
|
+
|
1227
|
+
# 2. Check for explicit error keys (parse_error, protocol_error)
|
1228
|
+
if not webhook_failed:
|
1229
|
+
explicit_error_keys = ["parse_error", "protocol_error"]
|
1230
|
+
for error_key in explicit_error_keys:
|
1231
|
+
if error_key in response_data and response_data[error_key]:
|
1232
|
+
webhook_failed = True
|
1233
|
+
if verbose:
|
1234
|
+
print(f"Webhook failed: Found explicit error key '{error_key}' = {response_data[error_key]}")
|
1235
|
+
break
|
1236
|
+
|
1237
|
+
# 3. Check for custom error_keys from webhook config
|
1238
|
+
if not webhook_failed and "error_keys" in webhook:
|
1239
|
+
error_keys = webhook["error_keys"]
|
1240
|
+
if isinstance(error_keys, str):
|
1241
|
+
error_keys = [error_keys] # Convert single string to list
|
1242
|
+
elif not isinstance(error_keys, list):
|
1243
|
+
error_keys = []
|
1244
|
+
|
1245
|
+
for error_key in error_keys:
|
1246
|
+
if error_key in response_data and response_data[error_key]:
|
1247
|
+
webhook_failed = True
|
1248
|
+
if verbose:
|
1249
|
+
print(f"Webhook failed: Found custom error key '{error_key}' = {response_data[error_key]}")
|
1250
|
+
break
|
1251
|
+
|
1252
|
+
except Exception as e:
|
1253
|
+
webhook_failed = True
|
1133
1254
|
if verbose:
|
1134
|
-
print(f"
|
1135
|
-
|
1136
|
-
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
# Get webhooks from matched expression
|
1142
|
-
webhooks = matched_expression.get('webhooks', [])
|
1143
|
-
fallback_output = matched_expression.get('output', 'Function completed')
|
1144
|
-
webhook_result = None
|
1145
|
-
|
1146
|
-
if verbose:
|
1147
|
-
print(f"Processing {len(webhooks)} webhook(s)...")
|
1148
|
-
|
1149
|
-
for i, webhook in enumerate(webhooks):
|
1150
|
-
url = webhook.get('url', '')
|
1151
|
-
method = webhook.get('method', 'POST').upper()
|
1152
|
-
headers = webhook.get('headers', {})
|
1153
|
-
|
1154
|
-
if verbose:
|
1155
|
-
print(f" Webhook {i+1}: {method} {url}")
|
1156
|
-
|
1157
|
-
# Prepare request data
|
1158
|
-
request_data = args.copy()
|
1159
|
-
|
1160
|
-
# Expand URL template with arguments
|
1161
|
-
template_context = {"args": args, "array": [], **args}
|
1162
|
-
expanded_url = simple_template_expand(url, template_context)
|
1163
|
-
|
1164
|
-
if verbose:
|
1165
|
-
print(f" Original URL: {url}")
|
1166
|
-
print(f" Template context: {template_context}")
|
1167
|
-
print(f" Expanded URL: {expanded_url}")
|
1168
|
-
|
1169
|
-
try:
|
1170
|
-
if method == 'GET':
|
1171
|
-
response = requests.get(expanded_url, params=request_data, headers=headers, timeout=10)
|
1172
|
-
else:
|
1173
|
-
response = requests.post(expanded_url, json=request_data, headers=headers, timeout=10)
|
1255
|
+
print(f"Webhook failed: HTTP request exception: {e}")
|
1256
|
+
# Create error response like server does
|
1257
|
+
response_data = {
|
1258
|
+
"protocol_error": True,
|
1259
|
+
"error": str(e)
|
1260
|
+
}
|
1174
1261
|
|
1175
|
-
|
1176
|
-
|
1177
|
-
|
1262
|
+
# If webhook succeeded, process its output
|
1263
|
+
if not webhook_failed:
|
1264
|
+
if verbose:
|
1265
|
+
print(f"Webhook {i+1} succeeded!")
|
1266
|
+
|
1267
|
+
# Add response data to context
|
1268
|
+
webhook_context = context.copy()
|
1269
|
+
|
1270
|
+
# Handle different response types
|
1271
|
+
if isinstance(response_data, list):
|
1272
|
+
# For array responses, use ${array[0].field} syntax
|
1273
|
+
webhook_context["array"] = response_data
|
1274
|
+
if verbose:
|
1275
|
+
print(f"Array response: {len(response_data)} items")
|
1276
|
+
else:
|
1277
|
+
# For object responses, use ${response.field} syntax
|
1278
|
+
webhook_context["response"] = response_data
|
1279
|
+
if verbose:
|
1280
|
+
print("Object response")
|
1281
|
+
|
1282
|
+
# Step 3: Process webhook-level foreach (if present)
|
1283
|
+
if "foreach" in webhook:
|
1284
|
+
foreach_config = webhook["foreach"]
|
1178
1285
|
if verbose:
|
1179
|
-
print(f"
|
1180
|
-
print(f"
|
1286
|
+
print(f"\n--- Processing Webhook Foreach ---")
|
1287
|
+
print(f"Foreach config: {json.dumps(foreach_config, indent=2)}")
|
1181
1288
|
|
1182
|
-
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1289
|
+
input_key = foreach_config.get("input_key", "data")
|
1290
|
+
output_key = foreach_config.get("output_key", "result")
|
1291
|
+
max_items = foreach_config.get("max", 100)
|
1292
|
+
append_template = foreach_config.get("append", "${this.value}")
|
1293
|
+
|
1294
|
+
# Look for the input data in the response
|
1295
|
+
input_data = None
|
1296
|
+
if input_key in response_data and isinstance(response_data[input_key], list):
|
1297
|
+
input_data = response_data[input_key]
|
1298
|
+
if verbose:
|
1299
|
+
print(f"Found array data in response.{input_key}: {len(input_data)} items")
|
1300
|
+
|
1301
|
+
if input_data:
|
1302
|
+
result_parts = []
|
1303
|
+
items_to_process = input_data[:max_items]
|
1190
1304
|
|
1191
|
-
|
1192
|
-
|
1193
|
-
|
1305
|
+
for item in items_to_process:
|
1306
|
+
if isinstance(item, dict):
|
1307
|
+
# For objects, make properties available as ${this.property}
|
1308
|
+
item_context = {"this": item}
|
1309
|
+
expanded = simple_template_expand(append_template, item_context)
|
1310
|
+
else:
|
1311
|
+
# For non-dict items, make them available as ${this.value}
|
1312
|
+
item_context = {"this": {"value": item}}
|
1313
|
+
expanded = simple_template_expand(append_template, item_context)
|
1314
|
+
result_parts.append(expanded)
|
1194
1315
|
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
for key, template in webhook_output.items():
|
1199
|
-
if isinstance(template, str):
|
1200
|
-
webhook_result[key] = simple_template_expand(template, template_context)
|
1201
|
-
else:
|
1202
|
-
webhook_result[key] = template
|
1203
|
-
elif isinstance(webhook_output, str):
|
1204
|
-
# Simple string template
|
1205
|
-
webhook_result = {"response": simple_template_expand(webhook_output, template_context)}
|
1206
|
-
else:
|
1207
|
-
# Other types
|
1208
|
-
webhook_result = {"response": str(webhook_output)}
|
1316
|
+
# Store the concatenated result
|
1317
|
+
foreach_result = "".join(result_parts)
|
1318
|
+
webhook_context[output_key] = foreach_result
|
1209
1319
|
|
1210
1320
|
if verbose:
|
1211
|
-
print(f"
|
1321
|
+
print(f"Processed {len(items_to_process)} items")
|
1322
|
+
print(f"Foreach result ({output_key}): {foreach_result[:200]}{'...' if len(foreach_result) > 200 else ''}")
|
1212
1323
|
else:
|
1213
|
-
|
1324
|
+
if verbose:
|
1325
|
+
print(f"No array data found for foreach input_key: {input_key}")
|
1326
|
+
|
1327
|
+
# Step 4: Process webhook-level output (this is the final result)
|
1328
|
+
if "output" in webhook:
|
1329
|
+
webhook_output = webhook["output"]
|
1330
|
+
if verbose:
|
1331
|
+
print(f"\n--- Processing Webhook Output ---")
|
1332
|
+
print(f"Output template: {json.dumps(webhook_output, indent=2)}")
|
1333
|
+
|
1334
|
+
if isinstance(webhook_output, dict):
|
1335
|
+
# Process each key-value pair in the output
|
1336
|
+
final_result = {}
|
1337
|
+
for key, template in webhook_output.items():
|
1338
|
+
expanded_value = simple_template_expand(str(template), webhook_context)
|
1339
|
+
final_result[key] = expanded_value
|
1340
|
+
if verbose:
|
1341
|
+
print(f"Set {key} = {expanded_value}")
|
1342
|
+
else:
|
1343
|
+
# Single output value (string template)
|
1344
|
+
final_result = simple_template_expand(str(webhook_output), webhook_context)
|
1345
|
+
if verbose:
|
1346
|
+
print(f"Final result = {final_result}")
|
1214
1347
|
|
1215
|
-
break
|
1216
|
-
except json.JSONDecodeError:
|
1217
|
-
webhook_result = {"response": response.text}
|
1218
1348
|
if verbose:
|
1219
|
-
print(f"
|
1220
|
-
|
1349
|
+
print(f"\n--- Webhook {i+1} Final Result ---")
|
1350
|
+
print(f"Result: {json.dumps(final_result, indent=2) if isinstance(final_result, dict) else final_result}")
|
1351
|
+
|
1352
|
+
return final_result
|
1353
|
+
|
1354
|
+
else:
|
1355
|
+
# No output template defined, return the response data
|
1356
|
+
if verbose:
|
1357
|
+
print("No output template defined, returning response data")
|
1358
|
+
return response_data
|
1359
|
+
|
1221
1360
|
else:
|
1361
|
+
# This webhook failed, try next webhook
|
1222
1362
|
if verbose:
|
1223
|
-
print(f"
|
1224
|
-
|
1225
|
-
if verbose:
|
1226
|
-
print(f" ✗ Webhook request failed: {e}")
|
1363
|
+
print(f"Webhook {i+1} failed, trying next webhook...")
|
1364
|
+
continue
|
1227
1365
|
|
1228
|
-
#
|
1229
|
-
if
|
1366
|
+
# Step 5: All webhooks failed, use fallback output if available
|
1367
|
+
if "output" in actual_datamap:
|
1230
1368
|
if verbose:
|
1231
|
-
print("
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
1235
|
-
|
1236
|
-
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
elif isinstance(output_template, str):
|
1246
|
-
# Simple string template
|
1247
|
-
webhook_result = {"response": simple_template_expand(output_template, {"args": args})}
|
1369
|
+
print(f"\n--- Using DataMap Fallback Output ---")
|
1370
|
+
datamap_output = actual_datamap["output"]
|
1371
|
+
if verbose:
|
1372
|
+
print(f"Fallback output template: {json.dumps(datamap_output, indent=2)}")
|
1373
|
+
|
1374
|
+
if isinstance(datamap_output, dict):
|
1375
|
+
# Process each key-value pair in the fallback output
|
1376
|
+
final_result = {}
|
1377
|
+
for key, template in datamap_output.items():
|
1378
|
+
expanded_value = simple_template_expand(str(template), context)
|
1379
|
+
final_result[key] = expanded_value
|
1380
|
+
if verbose:
|
1381
|
+
print(f"Fallback: Set {key} = {expanded_value}")
|
1382
|
+
result = final_result
|
1248
1383
|
else:
|
1249
|
-
#
|
1250
|
-
|
1384
|
+
# Single fallback output value
|
1385
|
+
result = simple_template_expand(str(datamap_output), context)
|
1386
|
+
if verbose:
|
1387
|
+
print(f"Fallback result = {result}")
|
1251
1388
|
|
1252
1389
|
if verbose:
|
1253
|
-
print(f"Fallback
|
1390
|
+
print(f"\n--- DataMap Fallback Final Result ---")
|
1391
|
+
print(f"Result: {json.dumps(result, indent=2) if isinstance(result, dict) else result}")
|
1392
|
+
|
1393
|
+
return result
|
1254
1394
|
|
1255
|
-
#
|
1256
|
-
|
1395
|
+
# No fallback defined, return generic error
|
1396
|
+
error_result = {"error": "All webhooks failed and no fallback output defined", "status": "failed"}
|
1397
|
+
if verbose:
|
1398
|
+
print(f"\n--- DataMap Error Result ---")
|
1399
|
+
print(f"Result: {json.dumps(error_result, indent=2)}")
|
1257
1400
|
|
1258
|
-
return
|
1401
|
+
return error_result
|
1259
1402
|
|
1260
1403
|
|
1261
1404
|
def execute_external_webhook_function(func: 'SWAIGFunction', function_name: str, function_args: Dict[str, Any],
|
@@ -367,16 +367,57 @@ class IndexBuilder:
|
|
367
367
|
global_tags: Optional[List[str]] = None) -> List[Dict[str, Any]]:
|
368
368
|
"""Process single file into chunks"""
|
369
369
|
try:
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
370
|
+
relative_path = str(file_path.relative_to(source_dir))
|
371
|
+
file_extension = file_path.suffix.lower()
|
372
|
+
|
373
|
+
# Handle different file types appropriately
|
374
|
+
if file_extension == '.pdf':
|
375
|
+
# Use document processor for PDF extraction
|
376
|
+
content_result = self.doc_processor._extract_text_from_file(str(file_path))
|
377
|
+
if isinstance(content_result, str) and content_result.startswith('{"error"'):
|
378
|
+
if self.verbose:
|
379
|
+
print(f"Skipping PDF file (extraction failed): {file_path}")
|
380
|
+
return []
|
381
|
+
content = content_result
|
382
|
+
elif file_extension in ['.docx', '.xlsx', '.pptx']:
|
383
|
+
# Use document processor for Office documents
|
384
|
+
content_result = self.doc_processor._extract_text_from_file(str(file_path))
|
385
|
+
if isinstance(content_result, str) and content_result.startswith('{"error"'):
|
386
|
+
if self.verbose:
|
387
|
+
print(f"Skipping office document (extraction failed): {file_path}")
|
388
|
+
return []
|
389
|
+
content = content_result
|
390
|
+
elif file_extension == '.html':
|
391
|
+
# Use document processor for HTML
|
392
|
+
content_result = self.doc_processor._extract_text_from_file(str(file_path))
|
393
|
+
if isinstance(content_result, str) and content_result.startswith('{"error"'):
|
394
|
+
if self.verbose:
|
395
|
+
print(f"Skipping HTML file (extraction failed): {file_path}")
|
396
|
+
return []
|
397
|
+
content = content_result
|
398
|
+
elif file_extension == '.rtf':
|
399
|
+
# Use document processor for RTF
|
400
|
+
content_result = self.doc_processor._extract_text_from_file(str(file_path))
|
401
|
+
if isinstance(content_result, str) and content_result.startswith('{"error"'):
|
402
|
+
if self.verbose:
|
403
|
+
print(f"Skipping RTF file (extraction failed): {file_path}")
|
404
|
+
return []
|
405
|
+
content = content_result
|
406
|
+
else:
|
407
|
+
# Try to read as text file (markdown, txt, code, etc.)
|
408
|
+
try:
|
409
|
+
content = file_path.read_text(encoding='utf-8')
|
410
|
+
except UnicodeDecodeError:
|
411
|
+
if self.verbose:
|
412
|
+
print(f"Skipping binary file: {file_path}")
|
413
|
+
return []
|
414
|
+
|
415
|
+
# Validate content
|
416
|
+
if not content or (isinstance(content, str) and len(content.strip()) == 0):
|
374
417
|
if self.verbose:
|
375
|
-
print(f"Skipping
|
418
|
+
print(f"Skipping empty file: {file_path}")
|
376
419
|
return []
|
377
420
|
|
378
|
-
relative_path = str(file_path.relative_to(source_dir))
|
379
|
-
|
380
421
|
# Create chunks using document processor - pass content directly, not file path
|
381
422
|
chunks = self.doc_processor.create_chunks(
|
382
423
|
content=content, # Pass the actual content, not the file path
|
@@ -118,15 +118,28 @@ stopwords_language_map = {
|
|
118
118
|
# Function to ensure NLTK resources are downloaded
|
119
119
|
def ensure_nltk_resources():
|
120
120
|
"""Download required NLTK resources if not already present"""
|
121
|
-
resources = ['punkt', 'wordnet', 'averaged_perceptron_tagger', 'stopwords']
|
121
|
+
resources = ['punkt', 'punkt_tab', 'wordnet', 'averaged_perceptron_tagger', 'stopwords']
|
122
122
|
for resource in resources:
|
123
123
|
try:
|
124
|
-
|
124
|
+
# Try different paths for different resource types
|
125
|
+
if resource in ['punkt', 'punkt_tab']:
|
126
|
+
nltk.data.find(f'tokenizers/{resource}')
|
127
|
+
elif resource in ['wordnet']:
|
128
|
+
nltk.data.find(f'corpora/{resource}')
|
129
|
+
elif resource in ['averaged_perceptron_tagger']:
|
130
|
+
nltk.data.find(f'taggers/{resource}')
|
131
|
+
elif resource in ['stopwords']:
|
132
|
+
nltk.data.find(f'corpora/{resource}')
|
133
|
+
else:
|
134
|
+
nltk.data.find(f'corpora/{resource}')
|
125
135
|
except LookupError:
|
126
136
|
try:
|
137
|
+
logger.info(f"Downloading NLTK resource '{resource}'...")
|
127
138
|
nltk.download(resource, quiet=True)
|
139
|
+
logger.info(f"Successfully downloaded NLTK resource '{resource}'")
|
128
140
|
except Exception as e:
|
129
141
|
logger.warning(f"Failed to download NLTK resource '{resource}': {e}")
|
142
|
+
# Continue without this resource - some functionality may be degraded
|
130
143
|
|
131
144
|
# Initialize NLTK resources
|
132
145
|
ensure_nltk_resources()
|
@@ -246,7 +259,20 @@ def preprocess_query(query: str, language: str = 'en', pos_to_expand: Optional[L
|
|
246
259
|
query_nlp_backend = 'nltk'
|
247
260
|
|
248
261
|
# Tokenization and stop word removal
|
249
|
-
|
262
|
+
try:
|
263
|
+
tokens = nltk.word_tokenize(query)
|
264
|
+
except LookupError as e:
|
265
|
+
# If tokenization fails, try to download punkt resources
|
266
|
+
logger.warning(f"NLTK tokenization failed: {e}")
|
267
|
+
try:
|
268
|
+
nltk.download('punkt', quiet=True)
|
269
|
+
nltk.download('punkt_tab', quiet=True)
|
270
|
+
tokens = nltk.word_tokenize(query)
|
271
|
+
except Exception as fallback_error:
|
272
|
+
# If all else fails, use simple split as fallback
|
273
|
+
logger.warning(f"NLTK tokenization fallback failed: {fallback_error}. Using simple word splitting.")
|
274
|
+
tokens = query.split()
|
275
|
+
|
250
276
|
nltk_language = stopwords_language_map.get(language, 'english')
|
251
277
|
|
252
278
|
try:
|
@@ -279,14 +305,29 @@ def preprocess_query(query: str, language: str = 'en', pos_to_expand: Optional[L
|
|
279
305
|
logger.info(f"POS Tagging Results (spaCy): {pos_tags}")
|
280
306
|
else:
|
281
307
|
# Use NLTK (default or fallback)
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
308
|
+
try:
|
309
|
+
nltk_pos_tags = nltk.pos_tag(tokens)
|
310
|
+
for token, pos_tag in nltk_pos_tags:
|
311
|
+
try:
|
312
|
+
lemma = lemmatizer.lemmatize(token, get_wordnet_pos(pos_tag)).lower()
|
313
|
+
except Exception:
|
314
|
+
# Fallback if lemmatization fails
|
315
|
+
lemma = token.lower()
|
316
|
+
stemmed = stemmer.stem(lemma)
|
317
|
+
lemmas.append((token.lower(), stemmed))
|
318
|
+
pos_tags[token.lower()] = pos_tag
|
319
|
+
if debug:
|
320
|
+
logger.info(f"POS Tagging Results (NLTK): {pos_tags}")
|
321
|
+
except Exception as pos_error:
|
322
|
+
# Fallback if POS tagging fails completely
|
323
|
+
logger.warning(f"NLTK POS tagging failed: {pos_error}. Using basic token processing.")
|
324
|
+
for token in tokens:
|
325
|
+
lemma = token.lower()
|
326
|
+
stemmed = stemmer.stem(lemma)
|
327
|
+
lemmas.append((token.lower(), stemmed))
|
328
|
+
pos_tags[token.lower()] = 'NN' # Default to noun
|
329
|
+
if debug:
|
330
|
+
logger.info(f"Using fallback token processing for: {tokens}")
|
290
331
|
|
291
332
|
# Expanding query with synonyms
|
292
333
|
expanded_query_set = set()
|
@@ -41,18 +41,7 @@ class NativeVectorSearchSkill(SkillBase):
|
|
41
41
|
def setup(self) -> bool:
|
42
42
|
"""Setup the native vector search skill"""
|
43
43
|
|
44
|
-
#
|
45
|
-
try:
|
46
|
-
from signalwire_agents.search import IndexBuilder, SearchEngine
|
47
|
-
from signalwire_agents.search.query_processor import preprocess_query
|
48
|
-
self.search_available = True
|
49
|
-
except ImportError as e:
|
50
|
-
self.search_available = False
|
51
|
-
self.import_error = str(e)
|
52
|
-
self.logger.warning(f"Search dependencies not available: {e}")
|
53
|
-
# Don't fail setup - we'll provide helpful error messages at runtime
|
54
|
-
|
55
|
-
# Get configuration
|
44
|
+
# Get configuration first
|
56
45
|
self.tool_name = self.params.get('tool_name', 'search_knowledge')
|
57
46
|
self.index_file = self.params.get('index_file')
|
58
47
|
self.build_index = self.params.get('build_index', False)
|
@@ -74,7 +63,34 @@ class NativeVectorSearchSkill(SkillBase):
|
|
74
63
|
# SWAIG fields for function fillers
|
75
64
|
self.swaig_fields = self.params.get('swaig_fields', {})
|
76
65
|
|
77
|
-
#
|
66
|
+
# **EARLY REMOTE CHECK - Option 1**
|
67
|
+
# If remote URL is configured, skip all heavy local imports and just validate remote connectivity
|
68
|
+
if self.remote_url:
|
69
|
+
self.use_remote = True
|
70
|
+
self.search_engine = None # No local search engine needed
|
71
|
+
self.logger.info(f"Using remote search server: {self.remote_url}")
|
72
|
+
|
73
|
+
# Test remote connection (lightweight check)
|
74
|
+
try:
|
75
|
+
import requests
|
76
|
+
response = requests.get(f"{self.remote_url}/health", timeout=5)
|
77
|
+
if response.status_code == 200:
|
78
|
+
self.logger.info("Remote search server is available")
|
79
|
+
self.search_available = True
|
80
|
+
return True # Success - skip all local setup
|
81
|
+
else:
|
82
|
+
self.logger.error(f"Remote search server returned status {response.status_code}")
|
83
|
+
self.search_available = False
|
84
|
+
return False
|
85
|
+
except Exception as e:
|
86
|
+
self.logger.error(f"Failed to connect to remote search server: {e}")
|
87
|
+
self.search_available = False
|
88
|
+
return False
|
89
|
+
|
90
|
+
# **LOCAL MODE SETUP - Only when no remote URL**
|
91
|
+
self.use_remote = False
|
92
|
+
|
93
|
+
# NLP backend configuration (only needed for local mode)
|
78
94
|
self.nlp_backend = self.params.get('nlp_backend') # Backward compatibility
|
79
95
|
self.index_nlp_backend = self.params.get('index_nlp_backend', 'nltk') # Default to fast NLTK for indexing
|
80
96
|
self.query_nlp_backend = self.params.get('query_nlp_backend', 'nltk') # Default to fast NLTK for search
|
@@ -95,6 +111,17 @@ class NativeVectorSearchSkill(SkillBase):
|
|
95
111
|
self.logger.warning(f"Invalid query_nlp_backend '{self.query_nlp_backend}', using 'nltk'")
|
96
112
|
self.query_nlp_backend = 'nltk'
|
97
113
|
|
114
|
+
# Check if local search functionality is available (heavy imports only for local mode)
|
115
|
+
try:
|
116
|
+
from signalwire_agents.search import IndexBuilder, SearchEngine
|
117
|
+
from signalwire_agents.search.query_processor import preprocess_query
|
118
|
+
self.search_available = True
|
119
|
+
except ImportError as e:
|
120
|
+
self.search_available = False
|
121
|
+
self.import_error = str(e)
|
122
|
+
self.logger.warning(f"Search dependencies not available: {e}")
|
123
|
+
# Don't fail setup - we'll provide helpful error messages at runtime
|
124
|
+
|
98
125
|
# Auto-build index if requested and search is available
|
99
126
|
if self.build_index and self.source_dir and self.search_available:
|
100
127
|
if not self.index_file:
|
@@ -124,7 +151,7 @@ class NativeVectorSearchSkill(SkillBase):
|
|
124
151
|
self.logger.error(f"Failed to build search index: {e}")
|
125
152
|
self.search_available = False
|
126
153
|
|
127
|
-
# Initialize search engine
|
154
|
+
# Initialize local search engine
|
128
155
|
self.search_engine = None
|
129
156
|
if self.search_available and self.index_file and os.path.exists(self.index_file):
|
130
157
|
try:
|
@@ -134,24 +161,6 @@ class NativeVectorSearchSkill(SkillBase):
|
|
134
161
|
self.logger.error(f"Failed to load search index {self.index_file}: {e}")
|
135
162
|
self.search_available = False
|
136
163
|
|
137
|
-
# Check if we should use remote search mode
|
138
|
-
self.use_remote = bool(self.remote_url)
|
139
|
-
if self.use_remote:
|
140
|
-
self.logger.info(f"Using remote search server: {self.remote_url}")
|
141
|
-
# Test remote connection
|
142
|
-
try:
|
143
|
-
import requests
|
144
|
-
response = requests.get(f"{self.remote_url}/health", timeout=5)
|
145
|
-
if response.status_code == 200:
|
146
|
-
self.logger.info("Remote search server is available")
|
147
|
-
self.search_available = True
|
148
|
-
else:
|
149
|
-
self.logger.error(f"Remote search server returned status {response.status_code}")
|
150
|
-
self.search_available = False
|
151
|
-
except Exception as e:
|
152
|
-
self.logger.error(f"Failed to connect to remote search server: {e}")
|
153
|
-
self.search_available = False
|
154
|
-
|
155
164
|
return True
|
156
165
|
|
157
166
|
def register_tools(self) -> None:
|
@@ -184,6 +193,11 @@ class NativeVectorSearchSkill(SkillBase):
|
|
184
193
|
def _search_handler(self, args, raw_data):
|
185
194
|
"""Handle search requests"""
|
186
195
|
|
196
|
+
# Debug logging to see what arguments are being passed
|
197
|
+
self.logger.info(f"Search handler called with args: {args}")
|
198
|
+
self.logger.info(f"Args type: {type(args)}")
|
199
|
+
self.logger.info(f"Raw data: {raw_data}")
|
200
|
+
|
187
201
|
if not self.search_available:
|
188
202
|
return SwaigFunctionResult(
|
189
203
|
f"Search functionality is not available. {getattr(self, 'import_error', '')}\n"
|
@@ -196,21 +210,27 @@ class NativeVectorSearchSkill(SkillBase):
|
|
196
210
|
f"{'Index file not found: ' + (self.index_file or 'not specified') if self.index_file else 'No index file configured'}"
|
197
211
|
)
|
198
212
|
|
213
|
+
# Get arguments - the framework handles parsing correctly
|
199
214
|
query = args.get('query', '').strip()
|
215
|
+
self.logger.error(f"DEBUG: Extracted query: '{query}' (length: {len(query)})")
|
216
|
+
self.logger.info(f"Query bool value: {bool(query)}")
|
217
|
+
|
200
218
|
if not query:
|
219
|
+
self.logger.error(f"Query validation failed - returning error message")
|
201
220
|
return SwaigFunctionResult("Please provide a search query.")
|
202
221
|
|
222
|
+
self.logger.info(f"Query validation passed - proceeding with search")
|
203
223
|
count = args.get('count', self.count)
|
204
224
|
|
205
225
|
try:
|
206
|
-
# Preprocess the query
|
207
|
-
from signalwire_agents.search.query_processor import preprocess_query
|
208
|
-
enhanced = preprocess_query(query, language='en', vector=True, query_nlp_backend=self.query_nlp_backend)
|
209
|
-
|
210
226
|
# Perform search (local or remote)
|
211
227
|
if self.use_remote:
|
212
|
-
|
228
|
+
# For remote searches, let the server handle query preprocessing
|
229
|
+
results = self._search_remote(query, None, count)
|
213
230
|
else:
|
231
|
+
# For local searches, preprocess the query locally
|
232
|
+
from signalwire_agents.search.query_processor import preprocess_query
|
233
|
+
enhanced = preprocess_query(query, language='en', vector=True, query_nlp_backend=self.query_nlp_backend)
|
214
234
|
results = self.search_engine.search(
|
215
235
|
query_vector=enhanced.get('vector', []),
|
216
236
|
enhanced_text=enhanced['enhanced_text'],
|
@@ -256,7 +276,24 @@ class NativeVectorSearchSkill(SkillBase):
|
|
256
276
|
return SwaigFunctionResult("\n".join(response_parts))
|
257
277
|
|
258
278
|
except Exception as e:
|
259
|
-
|
279
|
+
# Log the full error details for debugging
|
280
|
+
self.logger.error(f"Search error for query '{query}': {str(e)}", exc_info=True)
|
281
|
+
|
282
|
+
# Return user-friendly error message
|
283
|
+
user_msg = "I'm sorry, I encountered an issue while searching. "
|
284
|
+
|
285
|
+
# Check for specific error types and provide helpful guidance
|
286
|
+
error_str = str(e).lower()
|
287
|
+
if 'punkt' in error_str or 'nltk' in error_str:
|
288
|
+
user_msg += "It looks like some language processing resources are missing. Please try again in a moment."
|
289
|
+
elif 'vector' in error_str or 'embedding' in error_str:
|
290
|
+
user_msg += "There was an issue with the search indexing. Please try rephrasing your question."
|
291
|
+
elif 'timeout' in error_str or 'connection' in error_str:
|
292
|
+
user_msg += "The search service is temporarily unavailable. Please try again later."
|
293
|
+
else:
|
294
|
+
user_msg += "Please try rephrasing your question or contact support if the issue persists."
|
295
|
+
|
296
|
+
return SwaigFunctionResult(user_msg)
|
260
297
|
|
261
298
|
def _search_remote(self, query: str, enhanced: dict, count: int) -> list:
|
262
299
|
"""Perform search using remote search server"""
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: signalwire_agents
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.20
|
4
4
|
Summary: SignalWire AI Agents SDK
|
5
5
|
Author-email: SignalWire Team <info@signalwire.com>
|
6
6
|
Project-URL: Homepage, https://github.com/signalwire/signalwire-ai-agents
|
@@ -26,6 +26,11 @@ Requires-Dist: structlog==25.3.0
|
|
26
26
|
Requires-Dist: uvicorn==0.34.2
|
27
27
|
Requires-Dist: beautifulsoup4==4.12.3
|
28
28
|
Requires-Dist: pytz==2023.3
|
29
|
+
Provides-Extra: search-queryonly
|
30
|
+
Requires-Dist: numpy>=1.24.0; extra == "search-queryonly"
|
31
|
+
Requires-Dist: scikit-learn>=1.3.0; extra == "search-queryonly"
|
32
|
+
Requires-Dist: sentence-transformers>=2.2.0; extra == "search-queryonly"
|
33
|
+
Requires-Dist: nltk>=3.8; extra == "search-queryonly"
|
29
34
|
Provides-Extra: search
|
30
35
|
Requires-Dist: sentence-transformers>=2.2.0; extra == "search"
|
31
36
|
Requires-Dist: scikit-learn>=1.3.0; extra == "search"
|
@@ -488,7 +493,10 @@ The SDK includes optional local search capabilities that can be installed separa
|
|
488
493
|
#### Search Installation Options
|
489
494
|
|
490
495
|
```bash
|
491
|
-
#
|
496
|
+
# Query existing .swsearch files only (smallest footprint)
|
497
|
+
pip install signalwire-agents[search-queryonly]
|
498
|
+
|
499
|
+
# Basic search (vector search + keyword search + building indexes)
|
492
500
|
pip install signalwire-agents[search]
|
493
501
|
|
494
502
|
# Full search with document processing (PDF, DOCX, etc.)
|
@@ -505,11 +513,18 @@ pip install signalwire-agents[search-all]
|
|
505
513
|
|
506
514
|
| Option | Size | Features |
|
507
515
|
|--------|------|----------|
|
516
|
+
| `search-queryonly` | ~400MB | Query existing .swsearch files only (no building/processing) |
|
508
517
|
| `search` | ~500MB | Vector embeddings, keyword search, basic text processing |
|
509
518
|
| `search-full` | ~600MB | + PDF, DOCX, Excel, PowerPoint, HTML, Markdown processing |
|
510
519
|
| `search-nlp` | ~600MB | + Advanced spaCy NLP features |
|
511
520
|
| `search-all` | ~700MB | All search features combined |
|
512
521
|
|
522
|
+
**When to use `search-queryonly`:**
|
523
|
+
- Production containers with pre-built `.swsearch` files
|
524
|
+
- Lambda/serverless deployments
|
525
|
+
- Agents that only need to query knowledge bases (not build them)
|
526
|
+
- Smaller deployment footprint requirements
|
527
|
+
|
513
528
|
#### Search Features
|
514
529
|
|
515
530
|
- **Local/Offline Search**: No external API dependencies
|
@@ -1,9 +1,9 @@
|
|
1
|
-
signalwire_agents/__init__.py,sha256=
|
1
|
+
signalwire_agents/__init__.py,sha256=Y9kuYxQVXPYUbHXioNln_a1vqo6wUCdzdJWt3Hd_BPs,2707
|
2
2
|
signalwire_agents/agent_server.py,sha256=3Or8rIMAqW750V-XitBUMgOpW9BAIXmKXoGq7LkejAA,24988
|
3
3
|
signalwire_agents/schema.json,sha256=M8Mn6pQda2P9jhbmkALrLr1wt-fRuhYRqdmEi9Rbhqk,178075
|
4
4
|
signalwire_agents/cli/__init__.py,sha256=Iy2BfWDWBEZoA1cyHTDsooBSVMx4vH5Ddhr3sEuFe8c,197
|
5
5
|
signalwire_agents/cli/build_search.py,sha256=KLQJBqVSADFyGcKAi0KLwU_UoUd5rtRoKAdfcH91u70,28652
|
6
|
-
signalwire_agents/cli/test_swaig.py,sha256=
|
6
|
+
signalwire_agents/cli/test_swaig.py,sha256=IWjFULRM-KhYanwdVfVZalnLBkwxHTLcffggYNkfATI,109758
|
7
7
|
signalwire_agents/core/__init__.py,sha256=mVDLbpq1pg_WwiqsQR28NNZwJ6-VUXFIfg-vN7pk0ew,806
|
8
8
|
signalwire_agents/core/agent_base.py,sha256=O-sQ9k6L8_qE94Xzpds3bSS52onxqVs8rYU9_1B0LV8,159349
|
9
9
|
signalwire_agents/core/contexts.py,sha256=RQGIZGR92AC90nv8_hGa8S4YGY-SmMa1giR4g7jLgQI,21283
|
@@ -31,8 +31,8 @@ signalwire_agents/prefabs/receptionist.py,sha256=8EyQ6M0Egs3g7KKWukHFiO9UPoVUxT4
|
|
31
31
|
signalwire_agents/prefabs/survey.py,sha256=IIIQfgvMlfVNjEEEdWUn4lAJqVsCDlBsIAkOJ1ckyAE,14796
|
32
32
|
signalwire_agents/search/__init__.py,sha256=x7saU_MDbhoOIzcvCT1-gnqyH2rrMpzB4ZUqk-av-lI,3958
|
33
33
|
signalwire_agents/search/document_processor.py,sha256=J4OG640qbqGslbVevvD4J2cbTmFCZiGJ1bLX2yDayaE,43699
|
34
|
-
signalwire_agents/search/index_builder.py,sha256=
|
35
|
-
signalwire_agents/search/query_processor.py,sha256=
|
34
|
+
signalwire_agents/search/index_builder.py,sha256=kPnM62qBEV6gunBr50EFEBrTM8Oy0-L3hYAEScb63bM,25190
|
35
|
+
signalwire_agents/search/query_processor.py,sha256=WMm_jjArQ6-Jpy0Cc0sUI4saidOtDRKx_XLv0qi3N3k,16739
|
36
36
|
signalwire_agents/search/search_engine.py,sha256=KFF33aPsBdwb2N1aTJmAA0xo3KPANoXjwV53uSxih9o,14798
|
37
37
|
signalwire_agents/search/search_service.py,sha256=Src_REgjkcddHBDCaLv2BMJnDSxcE4XV_1U8YStJOh8,8719
|
38
38
|
signalwire_agents/skills/__init__.py,sha256=j0HambEKTkrFNP2vsaSmfrMJNDabq4EuvYJqRKZXfxU,342
|
@@ -40,7 +40,7 @@ signalwire_agents/skills/registry.py,sha256=lnr0XFOQ5YC_WgsAT6Id3RMAJ2-nf2ZCdqB7
|
|
40
40
|
signalwire_agents/skills/datasphere/__init__.py,sha256=SJJlmeMSeezjINPgkuWN1XzDPN_Z3GzZ_StzO1BtxQs,257
|
41
41
|
signalwire_agents/skills/datasphere/skill.py,sha256=L6GrGwej3sKPcHljKBNf4it5g4DaGzR18KlQx65_XKg,9598
|
42
42
|
signalwire_agents/skills/datasphere_serverless/__init__.py,sha256=65hu8_0eqiczLSZ-aJgASpMQqTUjzTQUI1fC8GI7qTI,70
|
43
|
-
signalwire_agents/skills/datasphere_serverless/skill.py,sha256=
|
43
|
+
signalwire_agents/skills/datasphere_serverless/skill.py,sha256=zgEoTY8Jm6YKJBzM1kn3j7tf492K-NiKG7DbE3GReeE,6787
|
44
44
|
signalwire_agents/skills/datetime/__init__.py,sha256=coPaY-k2EyZWuYckGunhSJ65Y1Jwz66h-iOo0QWb5WY,43
|
45
45
|
signalwire_agents/skills/datetime/skill.py,sha256=aBBdcPV5xWX6uWi9W0VqZ0kKOqC3JHrAhG1bZn58ro8,3900
|
46
46
|
signalwire_agents/skills/joke/__init__.py,sha256=R-iS9UMMvOdpkxL9aooVik16eCddJw14Rz4PmFqCdsM,53
|
@@ -48,7 +48,7 @@ signalwire_agents/skills/joke/skill.py,sha256=AFaf6fMy0sxUPJHvcnf3CWMuPqpJP4ODsc
|
|
48
48
|
signalwire_agents/skills/math/__init__.py,sha256=lGAFWEmJH2fuwkuZUdDTY5dmucrIwtjfNT8bE2hOSP8,39
|
49
49
|
signalwire_agents/skills/math/skill.py,sha256=5sErd5x1rFHJg2GlmdJB3LvrmvTNOrZsA2jRnG67Zw8,3342
|
50
50
|
signalwire_agents/skills/native_vector_search/__init__.py,sha256=buvncVoH5u8MJA0SLlz1JQgIuyBTQW5aql-ydnc7Wh8,29
|
51
|
-
signalwire_agents/skills/native_vector_search/skill.py,sha256=
|
51
|
+
signalwire_agents/skills/native_vector_search/skill.py,sha256=6rIsyoXPAuzl_C550On2Hp0mQm5a5c8q7yiePjowefQ,18161
|
52
52
|
signalwire_agents/skills/web_search/__init__.py,sha256=wJlptYDExYw-nxZJVzlTLOgkKkDOLUUt1ZdoLt44ixs,45
|
53
53
|
signalwire_agents/skills/web_search/skill.py,sha256=6EwoNABxEH5UkEdXsPT72PQzoVlFUbWsFJR6NuyhglI,10363
|
54
54
|
signalwire_agents/skills/wikipedia_search/__init__.py,sha256=8Db_aE0ly7QoXg7n2RDvCqKupkyR-UYlK9uFUnGNCE8,184
|
@@ -58,10 +58,10 @@ signalwire_agents/utils/pom_utils.py,sha256=4Mr7baQ_xR_hfJ72YxQRAT_GFa663YjFX_Pu
|
|
58
58
|
signalwire_agents/utils/schema_utils.py,sha256=i4okv_O9bUApwT_jJf4Yoij3bLCrGrW3DC-vzSy2RuY,16392
|
59
59
|
signalwire_agents/utils/token_generators.py,sha256=4Mr7baQ_xR_hfJ72YxQRAT_GFa663YjFX_PumJ35Xds,191
|
60
60
|
signalwire_agents/utils/validators.py,sha256=4Mr7baQ_xR_hfJ72YxQRAT_GFa663YjFX_PumJ35Xds,191
|
61
|
-
signalwire_agents-0.1.
|
62
|
-
signalwire_agents-0.1.
|
63
|
-
signalwire_agents-0.1.
|
64
|
-
signalwire_agents-0.1.
|
65
|
-
signalwire_agents-0.1.
|
66
|
-
signalwire_agents-0.1.
|
67
|
-
signalwire_agents-0.1.
|
61
|
+
signalwire_agents-0.1.20.data/data/schema.json,sha256=M8Mn6pQda2P9jhbmkALrLr1wt-fRuhYRqdmEi9Rbhqk,178075
|
62
|
+
signalwire_agents-0.1.20.dist-info/licenses/LICENSE,sha256=NYvAsB-rTcSvG9cqHt9EUHAWLiA9YzM4Qfz-mPdvDR0,1067
|
63
|
+
signalwire_agents-0.1.20.dist-info/METADATA,sha256=7EJOLAnXfsbnlBjo_2iP3Hzt_7Lg0RyRU4_8QawL2yM,36104
|
64
|
+
signalwire_agents-0.1.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
65
|
+
signalwire_agents-0.1.20.dist-info/entry_points.txt,sha256=7RBxC9wwFdsqP7g1F4JzsJ3AIHsjGtb3h0mxEGmANlI,151
|
66
|
+
signalwire_agents-0.1.20.dist-info/top_level.txt,sha256=kDGS6ZYv84K9P5Kyg9_S8P_pbUXoHkso0On_DB5bbWc,18
|
67
|
+
signalwire_agents-0.1.20.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|