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.
Files changed (73) hide show
  1. {signalwire_agents-0.1.18/signalwire_agents.egg-info → signalwire_agents-0.1.20}/PKG-INFO +17 -2
  2. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/README.md +11 -1
  3. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/pyproject.toml +11 -2
  4. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/__init__.py +1 -1
  5. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/cli/test_swaig.py +285 -142
  6. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/search/index_builder.py +48 -7
  7. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/search/query_processor.py +52 -11
  8. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/datasphere_serverless/skill.py +0 -1
  9. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/native_vector_search/skill.py +75 -38
  10. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20/signalwire_agents.egg-info}/PKG-INFO +17 -2
  11. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents.egg-info/entry_points.txt +1 -1
  12. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents.egg-info/requires.txt +6 -0
  13. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/LICENSE +0 -0
  14. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/schema.json +0 -0
  15. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/setup.cfg +0 -0
  16. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/setup.py +0 -0
  17. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/agent_server.py +0 -0
  18. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/cli/__init__.py +0 -0
  19. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/cli/build_search.py +0 -0
  20. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/__init__.py +0 -0
  21. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/agent_base.py +0 -0
  22. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/contexts.py +0 -0
  23. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/data_map.py +0 -0
  24. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/function_result.py +0 -0
  25. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/logging_config.py +0 -0
  26. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/pom_builder.py +0 -0
  27. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/security/__init__.py +0 -0
  28. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/security/session_manager.py +0 -0
  29. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/skill_base.py +0 -0
  30. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/skill_manager.py +0 -0
  31. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/state/__init__.py +0 -0
  32. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/state/file_state_manager.py +0 -0
  33. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/state/state_manager.py +0 -0
  34. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/swaig_function.py +0 -0
  35. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/swml_builder.py +0 -0
  36. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/swml_handler.py +0 -0
  37. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/swml_renderer.py +0 -0
  38. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/core/swml_service.py +0 -0
  39. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/prefabs/__init__.py +0 -0
  40. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/prefabs/concierge.py +0 -0
  41. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/prefabs/faq_bot.py +0 -0
  42. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/prefabs/info_gatherer.py +0 -0
  43. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/prefabs/receptionist.py +0 -0
  44. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/prefabs/survey.py +0 -0
  45. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/schema.json +0 -0
  46. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/search/__init__.py +0 -0
  47. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/search/document_processor.py +0 -0
  48. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/search/search_engine.py +0 -0
  49. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/search/search_service.py +0 -0
  50. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/__init__.py +0 -0
  51. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/datasphere/__init__.py +0 -0
  52. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/datasphere/skill.py +0 -0
  53. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/datasphere_serverless/__init__.py +0 -0
  54. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/datetime/__init__.py +0 -0
  55. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/datetime/skill.py +0 -0
  56. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/joke/__init__.py +0 -0
  57. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/joke/skill.py +0 -0
  58. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/math/__init__.py +0 -0
  59. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/math/skill.py +0 -0
  60. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/native_vector_search/__init__.py +0 -0
  61. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/registry.py +0 -0
  62. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/web_search/__init__.py +0 -0
  63. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/web_search/skill.py +0 -0
  64. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/wikipedia_search/__init__.py +0 -0
  65. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/skills/wikipedia_search/skill.py +0 -0
  66. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/utils/__init__.py +0 -0
  67. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/utils/pom_utils.py +0 -0
  68. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/utils/schema_utils.py +0 -0
  69. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/utils/token_generators.py +0 -0
  70. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents/utils/validators.py +0 -0
  71. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents.egg-info/SOURCES.txt +0 -0
  72. {signalwire_agents-0.1.18 → signalwire_agents-0.1.20}/signalwire_agents.egg-info/dependency_links.txt +0 -0
  73. {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.18
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
- # Basic search (vector search + keyword search)
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
- # Basic search (vector search + keyword search)
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.18"
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.sw_search_fast:main"
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.18"
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 by processing its configuration and making HTTP requests
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: The complete DataMap function configuration
1090
+ datamap_config: DataMap configuration dictionary
1086
1091
  args: Function arguments
1087
- verbose: Whether to show verbose output
1092
+ verbose: Enable verbose output
1088
1093
 
1089
1094
  Returns:
1090
- Final function result
1095
+ Function result (should be string or dict with 'response' key)
1091
1096
  """
1092
- # Extract data_map configuration
1093
- data_map = datamap_config.get('data_map', {})
1094
-
1095
- # Check if this is a simple DataMap (webhooks directly) or complex (with expressions)
1096
- if 'webhooks' in data_map and 'expressions' not in data_map:
1097
- # Simple DataMap structure - webhooks directly under data_map
1098
- webhooks = data_map.get('webhooks', [])
1099
- fallback_output = data_map.get('output', 'Function completed')
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(f"Simple DataMap structure with {len(webhooks)} webhook(s)")
1102
- else:
1103
- # Complex DataMap structure - with expressions containing webhooks
1104
- expressions = data_map.get('expressions', [])
1105
- matched_expression = None
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(f"Complex DataMap structure with {len(expressions)} expression(s)...")
1136
+ print("\n--- Processing Webhooks ---")
1109
1137
 
1110
- for expr in expressions:
1111
- pattern = expr.get('pattern', '.*') # Default to match everything
1112
- string_value = expr.get('string', '')
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
- # Create a test string from the arguments
1115
- test_string = json.dumps(args)
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" Testing pattern '{pattern}' against '{test_string}'")
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
- if re.search(pattern, test_string):
1123
- matched_expression = expr
1124
- if verbose:
1125
- print(f" ✓ Pattern matched!")
1126
- break
1127
- elif re.search(pattern, string_value):
1128
- matched_expression = expr
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" Pattern matched against string value!")
1131
- break
1132
- except re.error as e:
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" Invalid regex pattern: {e}")
1135
-
1136
- if not matched_expression:
1137
- if verbose:
1138
- print(" No expressions matched, using first expression if available")
1139
- matched_expression = expressions[0] if expressions else {}
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
- if response.status_code == 200:
1176
- try:
1177
- webhook_data = response.json()
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" Webhook succeeded: {response.status_code}")
1180
- print(f" Response: {json.dumps(webhook_data, indent=4)}")
1286
+ print(f"\n--- Processing Webhook Foreach ---")
1287
+ print(f"Foreach config: {json.dumps(foreach_config, indent=2)}")
1181
1288
 
1182
- # Process output template if defined in this webhook
1183
- webhook_output = webhook.get('output')
1184
- if webhook_output:
1185
- # Create context for template expansion
1186
- template_context = {
1187
- "args": args,
1188
- "array": webhook_data if isinstance(webhook_data, list) else [webhook_data]
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
- # Add webhook_data contents to context if it's a dict
1192
- if isinstance(webhook_data, dict):
1193
- template_context.update(webhook_data)
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
- if isinstance(webhook_output, dict):
1196
- # Process dict output template (e.g., {"response": "template", "action": [...]} )
1197
- webhook_result = {}
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" Processed output: {webhook_result}")
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
- webhook_result = webhook_data if isinstance(webhook_data, dict) else {"response": str(webhook_data)}
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" Webhook succeeded (text): {response.status_code}")
1220
- break
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"Webhook failed: {response.status_code}")
1224
- except requests.RequestException as e:
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
- # If no webhook succeeded, use fallback
1229
- if webhook_result is None:
1366
+ # Step 5: All webhooks failed, use fallback output if available
1367
+ if "output" in actual_datamap:
1230
1368
  if verbose:
1231
- print("All webhooks failed, using fallback output...")
1232
-
1233
- # Use the fallback output determined earlier
1234
- output_template = fallback_output
1235
-
1236
- # Handle both string and dict output templates
1237
- if isinstance(output_template, dict):
1238
- # Process dict output template (e.g., {"response": "template"})
1239
- webhook_result = {}
1240
- for key, template in output_template.items():
1241
- if isinstance(template, str):
1242
- webhook_result[key] = simple_template_expand(template, {"args": args})
1243
- else:
1244
- webhook_result[key] = template
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
- # Other types (shouldn't happen but be safe)
1250
- webhook_result = {"response": str(output_template)}
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 result = {webhook_result}")
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
- # Process foreach (not implemented in this simple version)
1256
- # This would iterate over array results and apply templates
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 webhook_result
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],