signalwire-agents 0.1.18__tar.gz → 0.1.20__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.
- {signalwire_agents-0.1.18/signalwire_agents.egg-info → signalwire_agents-0.1.20}/PKG-INFO +17 -2
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/README.md +11 -1
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/pyproject.toml +11 -2
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/__init__.py +1 -1
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/cli/test_swaig.py +285 -142
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/search/index_builder.py +48 -7
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/search/query_processor.py +52 -11
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/datasphere_serverless/skill.py +0 -1
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/native_vector_search/skill.py +75 -38
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20/signalwire_agents.egg-info}/PKG-INFO +17 -2
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents.egg-info/entry_points.txt +1 -1
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents.egg-info/requires.txt +6 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/LICENSE +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/schema.json +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/setup.cfg +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/setup.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/agent_server.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/cli/__init__.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/cli/build_search.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/__init__.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/agent_base.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/contexts.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/data_map.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/function_result.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/logging_config.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/pom_builder.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/security/__init__.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/security/session_manager.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/skill_base.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/skill_manager.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/state/__init__.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/state/file_state_manager.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/state/state_manager.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/swaig_function.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/swml_builder.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/swml_handler.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/swml_renderer.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/swml_service.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/prefabs/__init__.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/prefabs/concierge.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/prefabs/faq_bot.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/prefabs/info_gatherer.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/prefabs/receptionist.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/prefabs/survey.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/schema.json +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/search/__init__.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/search/document_processor.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/search/search_engine.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/search/search_service.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/__init__.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/datasphere/__init__.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/datasphere/skill.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/datasphere_serverless/__init__.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/datetime/__init__.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/datetime/skill.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/joke/__init__.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/joke/skill.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/math/__init__.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/math/skill.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/native_vector_search/__init__.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/registry.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/web_search/__init__.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/web_search/skill.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/wikipedia_search/__init__.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/wikipedia_search/skill.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/utils/__init__.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/utils/pom_utils.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/utils/schema_utils.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/utils/token_generators.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/utils/validators.py +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents.egg-info/SOURCES.txt +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents.egg-info/dependency_links.txt +0 -0
- {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents.egg-info/top_level.txt +0 -0
@@ -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
|
@@ -422,7 +422,10 @@ The SDK includes optional local search capabilities that can be installed separa
|
|
422
422
|
#### Search Installation Options
|
423
423
|
|
424
424
|
```bash
|
425
|
-
#
|
425
|
+
# Query existing .swsearch files only (smallest footprint)
|
426
|
+
pip install signalwire-agents[search-queryonly]
|
427
|
+
|
428
|
+
# Basic search (vector search + keyword search + building indexes)
|
426
429
|
pip install signalwire-agents[search]
|
427
430
|
|
428
431
|
# Full search with document processing (PDF, DOCX, etc.)
|
@@ -439,11 +442,18 @@ pip install signalwire-agents[search-all]
|
|
439
442
|
|
440
443
|
| Option | Size | Features |
|
441
444
|
|--------|------|----------|
|
445
|
+
| `search-queryonly` | ~400MB | Query existing .swsearch files only (no building/processing) |
|
442
446
|
| `search` | ~500MB | Vector embeddings, keyword search, basic text processing |
|
443
447
|
| `search-full` | ~600MB | + PDF, DOCX, Excel, PowerPoint, HTML, Markdown processing |
|
444
448
|
| `search-nlp` | ~600MB | + Advanced spaCy NLP features |
|
445
449
|
| `search-all` | ~700MB | All search features combined |
|
446
450
|
|
451
|
+
**When to use `search-queryonly`:**
|
452
|
+
- Production containers with pre-built `.swsearch` files
|
453
|
+
- Lambda/serverless deployments
|
454
|
+
- Agents that only need to query knowledge bases (not build them)
|
455
|
+
- Smaller deployment footprint requirements
|
456
|
+
|
447
457
|
#### Search Features
|
448
458
|
|
449
459
|
- **Local/Offline Search**: No external API dependencies
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "signalwire_agents"
|
7
|
-
version = "0.1.
|
7
|
+
version = "0.1.20"
|
8
8
|
description = "SignalWire AI Agents SDK"
|
9
9
|
authors = [
|
10
10
|
{name = "SignalWire Team", email = "info@signalwire.com"}
|
@@ -37,6 +37,15 @@ dependencies = [
|
|
37
37
|
|
38
38
|
# Optional dependencies for search functionality
|
39
39
|
[project.optional-dependencies]
|
40
|
+
# Query existing .swsearch files only (no document processing/building)
|
41
|
+
search-queryonly = [
|
42
|
+
"numpy>=1.24.0",
|
43
|
+
"scikit-learn>=1.3.0",
|
44
|
+
"sentence-transformers>=2.2.0",
|
45
|
+
"nltk>=3.8",
|
46
|
+
]
|
47
|
+
|
48
|
+
# Full search functionality (includes document processing and building)
|
40
49
|
search = [
|
41
50
|
"sentence-transformers>=2.2.0",
|
42
51
|
"scikit-learn>=1.3.0",
|
@@ -90,7 +99,7 @@ Homepage = "https://github.com/signalwire/signalwire-ai-agents"
|
|
90
99
|
|
91
100
|
[project.scripts]
|
92
101
|
swaig-test = "signalwire_agents.cli.test_swaig:console_entry_point"
|
93
|
-
sw-search = "signalwire_agents.cli.
|
102
|
+
sw-search = "signalwire_agents.cli.build_search:console_entry_point"
|
94
103
|
|
95
104
|
[tool.setuptools]
|
96
105
|
packages = ["signalwire_agents", "signalwire_agents.prefabs", "signalwire_agents.utils", "signalwire_agents.core", "signalwire_agents.core.state", "signalwire_agents.core.security", "signalwire_agents.skills", "signalwire_agents.skills.web_search", "signalwire_agents.skills.datetime", "signalwire_agents.skills.math", "signalwire_agents.skills.joke", "signalwire_agents.skills.datasphere", "signalwire_agents.skills.datasphere_serverless", "signalwire_agents.skills.wikipedia_search", "signalwire_agents.skills.native_vector_search", "signalwire_agents.cli", "signalwire_agents.search"]
|
@@ -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],
|