signalwire-agents 0.1.23__py3-none-any.whl → 0.1.25__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/agent_server.py +2 -1
- signalwire_agents/cli/config.py +61 -0
- signalwire_agents/cli/core/__init__.py +1 -0
- signalwire_agents/cli/core/agent_loader.py +254 -0
- signalwire_agents/cli/core/argparse_helpers.py +164 -0
- signalwire_agents/cli/core/dynamic_config.py +62 -0
- signalwire_agents/cli/execution/__init__.py +1 -0
- signalwire_agents/cli/execution/datamap_exec.py +437 -0
- signalwire_agents/cli/execution/webhook_exec.py +125 -0
- signalwire_agents/cli/output/__init__.py +1 -0
- signalwire_agents/cli/output/output_formatter.py +132 -0
- signalwire_agents/cli/output/swml_dump.py +177 -0
- signalwire_agents/cli/simulation/__init__.py +1 -0
- signalwire_agents/cli/simulation/data_generation.py +365 -0
- signalwire_agents/cli/simulation/data_overrides.py +187 -0
- signalwire_agents/cli/simulation/mock_env.py +271 -0
- signalwire_agents/cli/test_swaig.py +522 -2539
- signalwire_agents/cli/types.py +72 -0
- signalwire_agents/core/agent/__init__.py +1 -3
- signalwire_agents/core/agent/config/__init__.py +1 -3
- signalwire_agents/core/agent/prompt/manager.py +25 -7
- signalwire_agents/core/agent/tools/decorator.py +2 -0
- signalwire_agents/core/agent/tools/registry.py +8 -0
- signalwire_agents/core/agent_base.py +492 -3053
- signalwire_agents/core/function_result.py +31 -42
- signalwire_agents/core/mixins/__init__.py +28 -0
- signalwire_agents/core/mixins/ai_config_mixin.py +373 -0
- signalwire_agents/core/mixins/auth_mixin.py +287 -0
- signalwire_agents/core/mixins/prompt_mixin.py +345 -0
- signalwire_agents/core/mixins/serverless_mixin.py +368 -0
- signalwire_agents/core/mixins/skill_mixin.py +55 -0
- signalwire_agents/core/mixins/state_mixin.py +219 -0
- signalwire_agents/core/mixins/tool_mixin.py +295 -0
- signalwire_agents/core/mixins/web_mixin.py +1130 -0
- signalwire_agents/core/skill_manager.py +3 -1
- signalwire_agents/core/swaig_function.py +10 -1
- signalwire_agents/core/swml_service.py +140 -58
- signalwire_agents/skills/README.md +452 -0
- signalwire_agents/skills/api_ninjas_trivia/README.md +215 -0
- signalwire_agents/skills/datasphere/README.md +210 -0
- signalwire_agents/skills/datasphere_serverless/README.md +258 -0
- signalwire_agents/skills/datetime/README.md +132 -0
- signalwire_agents/skills/joke/README.md +149 -0
- signalwire_agents/skills/math/README.md +161 -0
- signalwire_agents/skills/native_vector_search/skill.py +33 -13
- signalwire_agents/skills/play_background_file/README.md +218 -0
- signalwire_agents/skills/spider/README.md +236 -0
- signalwire_agents/skills/spider/__init__.py +4 -0
- signalwire_agents/skills/spider/skill.py +479 -0
- signalwire_agents/skills/swml_transfer/README.md +395 -0
- signalwire_agents/skills/swml_transfer/__init__.py +1 -0
- signalwire_agents/skills/swml_transfer/skill.py +257 -0
- signalwire_agents/skills/weather_api/README.md +178 -0
- signalwire_agents/skills/web_search/README.md +163 -0
- signalwire_agents/skills/wikipedia_search/README.md +228 -0
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/METADATA +47 -2
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/RECORD +62 -22
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/entry_points.txt +1 -1
- signalwire_agents/core/agent/config/ephemeral.py +0 -176
- signalwire_agents-0.1.23.data/data/schema.json +0 -5611
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/WHEEL +0 -0
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/licenses/LICENSE +0 -0
- {signalwire_agents-0.1.23.dist-info → signalwire_agents-0.1.25.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Display agent/tools and format results
|
4
|
+
"""
|
5
|
+
|
6
|
+
import json
|
7
|
+
from typing import Any, TYPE_CHECKING
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from signalwire_agents.core.agent_base import AgentBase
|
11
|
+
from signalwire_agents.core.function_result import SwaigFunctionResult
|
12
|
+
|
13
|
+
|
14
|
+
def display_agent_tools(agent: 'AgentBase', verbose: bool = False) -> None:
|
15
|
+
"""
|
16
|
+
Display the available SWAIG functions for an agent
|
17
|
+
|
18
|
+
Args:
|
19
|
+
agent: The agent instance
|
20
|
+
verbose: Whether to show verbose details
|
21
|
+
"""
|
22
|
+
print("\nAvailable SWAIG functions:")
|
23
|
+
# Try to access functions from the tool registry
|
24
|
+
functions = {}
|
25
|
+
if hasattr(agent, '_tool_registry') and hasattr(agent._tool_registry, '_swaig_functions'):
|
26
|
+
functions = agent._tool_registry._swaig_functions
|
27
|
+
elif hasattr(agent, '_swaig_functions'):
|
28
|
+
functions = agent._swaig_functions
|
29
|
+
|
30
|
+
if functions:
|
31
|
+
for name, func in functions.items():
|
32
|
+
if isinstance(func, dict):
|
33
|
+
# DataMap function
|
34
|
+
description = func.get('description', 'DataMap function (serverless)')
|
35
|
+
print(f" {name} - {description}")
|
36
|
+
|
37
|
+
# Show parameters for DataMap functions
|
38
|
+
if 'parameters' in func and func['parameters']:
|
39
|
+
params = func['parameters']
|
40
|
+
# Handle both formats: direct properties dict or full schema
|
41
|
+
if 'properties' in params:
|
42
|
+
properties = params['properties']
|
43
|
+
required_fields = params.get('required', [])
|
44
|
+
else:
|
45
|
+
properties = params
|
46
|
+
required_fields = []
|
47
|
+
|
48
|
+
if properties:
|
49
|
+
print(f" Parameters:")
|
50
|
+
for param_name, param_def in properties.items():
|
51
|
+
param_type = param_def.get('type', 'unknown')
|
52
|
+
param_desc = param_def.get('description', 'No description')
|
53
|
+
is_required = param_name in required_fields
|
54
|
+
required_marker = " (required)" if is_required else ""
|
55
|
+
print(f" {param_name} ({param_type}){required_marker}: {param_desc}")
|
56
|
+
else:
|
57
|
+
print(f" Parameters: None")
|
58
|
+
else:
|
59
|
+
print(f" Parameters: None")
|
60
|
+
|
61
|
+
if verbose:
|
62
|
+
print(f" Config: {json.dumps(func, indent=6)}")
|
63
|
+
else:
|
64
|
+
# Regular SWAIG function
|
65
|
+
func_type = ""
|
66
|
+
if hasattr(func, 'webhook_url') and func.webhook_url and func.is_external:
|
67
|
+
func_type = " (EXTERNAL webhook)"
|
68
|
+
elif hasattr(func, 'webhook_url') and func.webhook_url:
|
69
|
+
func_type = " (webhook)"
|
70
|
+
else:
|
71
|
+
func_type = " (LOCAL webhook)"
|
72
|
+
|
73
|
+
print(f" {name} - {func.description}{func_type}")
|
74
|
+
|
75
|
+
# Show external URL if applicable
|
76
|
+
if hasattr(func, 'webhook_url') and func.webhook_url and func.is_external:
|
77
|
+
print(f" External URL: {func.webhook_url}")
|
78
|
+
|
79
|
+
# Show parameters
|
80
|
+
if hasattr(func, 'parameters') and func.parameters:
|
81
|
+
params = func.parameters
|
82
|
+
# Handle both formats: direct properties dict or full schema
|
83
|
+
if 'properties' in params:
|
84
|
+
properties = params['properties']
|
85
|
+
required_fields = params.get('required', [])
|
86
|
+
else:
|
87
|
+
properties = params
|
88
|
+
required_fields = []
|
89
|
+
|
90
|
+
if properties:
|
91
|
+
print(f" Parameters:")
|
92
|
+
for param_name, param_def in properties.items():
|
93
|
+
param_type = param_def.get('type', 'unknown')
|
94
|
+
param_desc = param_def.get('description', 'No description')
|
95
|
+
is_required = param_name in required_fields
|
96
|
+
required_marker = " (required)" if is_required else ""
|
97
|
+
print(f" {param_name} ({param_type}){required_marker}: {param_desc}")
|
98
|
+
else:
|
99
|
+
print(f" Parameters: None")
|
100
|
+
else:
|
101
|
+
print(f" Parameters: None")
|
102
|
+
|
103
|
+
if verbose:
|
104
|
+
print(f" Function object: {func}")
|
105
|
+
else:
|
106
|
+
print(" No SWAIG functions registered")
|
107
|
+
|
108
|
+
|
109
|
+
def format_result(result: Any) -> str:
|
110
|
+
"""
|
111
|
+
Format the result of a SWAIG function call for display
|
112
|
+
|
113
|
+
Args:
|
114
|
+
result: The result from the SWAIG function
|
115
|
+
|
116
|
+
Returns:
|
117
|
+
Formatted string representation
|
118
|
+
"""
|
119
|
+
# Import here to avoid circular imports
|
120
|
+
from signalwire_agents.core.function_result import SwaigFunctionResult
|
121
|
+
|
122
|
+
if isinstance(result, SwaigFunctionResult):
|
123
|
+
return f"SwaigFunctionResult: {result.response}"
|
124
|
+
elif isinstance(result, dict):
|
125
|
+
if 'response' in result:
|
126
|
+
return f"Response: {result['response']}"
|
127
|
+
else:
|
128
|
+
return f"Dict: {json.dumps(result, indent=2)}"
|
129
|
+
elif isinstance(result, str):
|
130
|
+
return f"String: {result}"
|
131
|
+
else:
|
132
|
+
return f"Other ({type(result).__name__}): {result}"
|
@@ -0,0 +1,177 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Handle SWML document dumping
|
4
|
+
"""
|
5
|
+
|
6
|
+
import sys
|
7
|
+
import json
|
8
|
+
import warnings
|
9
|
+
import argparse
|
10
|
+
from typing import TYPE_CHECKING
|
11
|
+
from ..simulation.data_generation import generate_fake_swml_post_data
|
12
|
+
from ..simulation.data_overrides import apply_convenience_mappings, apply_overrides
|
13
|
+
from ..simulation.mock_env import create_mock_request
|
14
|
+
from ..core.dynamic_config import apply_dynamic_config
|
15
|
+
|
16
|
+
if TYPE_CHECKING:
|
17
|
+
from signalwire_agents.core.agent_base import AgentBase
|
18
|
+
|
19
|
+
|
20
|
+
# Store the original print function for raw mode
|
21
|
+
original_print = print
|
22
|
+
|
23
|
+
|
24
|
+
def setup_raw_mode_suppression():
|
25
|
+
"""Set up output suppression for raw mode using central logging system"""
|
26
|
+
# The central logging system is already configured via environment variable
|
27
|
+
# Just suppress any remaining warnings
|
28
|
+
warnings.filterwarnings("ignore")
|
29
|
+
|
30
|
+
# Capture and suppress print statements in raw mode if needed
|
31
|
+
def suppressed_print(*args, **kwargs):
|
32
|
+
pass
|
33
|
+
|
34
|
+
# Replace print function globally for raw mode
|
35
|
+
import builtins
|
36
|
+
builtins.print = suppressed_print
|
37
|
+
|
38
|
+
|
39
|
+
def handle_dump_swml(agent: 'AgentBase', args: argparse.Namespace) -> int:
|
40
|
+
"""
|
41
|
+
Handle SWML dumping with fake post_data and mock request support
|
42
|
+
|
43
|
+
Args:
|
44
|
+
agent: The loaded agent instance
|
45
|
+
args: Parsed CLI arguments
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
Exit code (0 for success, 1 for error)
|
49
|
+
"""
|
50
|
+
if not args.raw:
|
51
|
+
if args.verbose:
|
52
|
+
print(f"Agent: {agent.get_name()}")
|
53
|
+
print(f"Route: {agent.route}")
|
54
|
+
|
55
|
+
# Show loaded skills
|
56
|
+
skills = agent.list_skills()
|
57
|
+
if skills:
|
58
|
+
print(f"Skills: {', '.join(skills)}")
|
59
|
+
|
60
|
+
# Show available functions
|
61
|
+
functions = agent._tool_registry.get_all_functions() if hasattr(agent, '_tool_registry') else {}
|
62
|
+
if functions:
|
63
|
+
print(f"Functions: {', '.join(functions.keys())}")
|
64
|
+
|
65
|
+
print("-" * 60)
|
66
|
+
|
67
|
+
try:
|
68
|
+
# Generate fake SWML post_data
|
69
|
+
post_data = generate_fake_swml_post_data(
|
70
|
+
call_type=args.call_type,
|
71
|
+
call_direction=args.call_direction,
|
72
|
+
call_state=args.call_state
|
73
|
+
)
|
74
|
+
|
75
|
+
# Apply convenience mappings from CLI args
|
76
|
+
post_data = apply_convenience_mappings(post_data, args)
|
77
|
+
|
78
|
+
# Apply explicit overrides
|
79
|
+
post_data = apply_overrides(post_data, args.override, args.override_json)
|
80
|
+
|
81
|
+
# Parse headers for mock request
|
82
|
+
headers = {}
|
83
|
+
for header in args.header:
|
84
|
+
if '=' in header:
|
85
|
+
key, value = header.split('=', 1)
|
86
|
+
headers[key] = value
|
87
|
+
|
88
|
+
# Parse query params for mock request (separate from userVariables)
|
89
|
+
query_params = {}
|
90
|
+
if args.query_params:
|
91
|
+
try:
|
92
|
+
query_params = json.loads(args.query_params)
|
93
|
+
except json.JSONDecodeError as e:
|
94
|
+
if not args.raw:
|
95
|
+
print(f"Warning: Invalid JSON in --query-params: {e}")
|
96
|
+
|
97
|
+
# Parse request body
|
98
|
+
request_body = {}
|
99
|
+
if args.body:
|
100
|
+
try:
|
101
|
+
request_body = json.loads(args.body)
|
102
|
+
except json.JSONDecodeError as e:
|
103
|
+
if not args.raw:
|
104
|
+
print(f"Warning: Invalid JSON in --body: {e}")
|
105
|
+
|
106
|
+
# Create mock request object
|
107
|
+
mock_request = create_mock_request(
|
108
|
+
method=args.method,
|
109
|
+
headers=headers,
|
110
|
+
query_params=query_params,
|
111
|
+
body=request_body
|
112
|
+
)
|
113
|
+
|
114
|
+
if args.verbose and not args.raw:
|
115
|
+
print(f"Using fake SWML post_data:")
|
116
|
+
print(json.dumps(post_data, indent=2))
|
117
|
+
print(f"\nMock request headers: {dict(mock_request.headers.items())}")
|
118
|
+
print(f"Mock request query params: {dict(mock_request.query_params.items())}")
|
119
|
+
print(f"Mock request method: {mock_request.method}")
|
120
|
+
print("-" * 60)
|
121
|
+
|
122
|
+
# Apply dynamic configuration with the mock request
|
123
|
+
apply_dynamic_config(agent, mock_request, verbose=args.verbose and not args.raw)
|
124
|
+
|
125
|
+
# For dynamic agents, call on_swml_request if available
|
126
|
+
if hasattr(agent, 'on_swml_request'):
|
127
|
+
try:
|
128
|
+
# Dynamic agents expect (request_data, callback_path, request)
|
129
|
+
call_id = post_data.get('call', {}).get('call_id', 'test-call-id')
|
130
|
+
modifications = agent.on_swml_request(post_data, "/swml", mock_request)
|
131
|
+
|
132
|
+
if args.verbose and not args.raw:
|
133
|
+
print(f"Dynamic agent modifications: {modifications}")
|
134
|
+
|
135
|
+
# Generate SWML with modifications
|
136
|
+
swml_doc = agent._render_swml(call_id, modifications)
|
137
|
+
except Exception as e:
|
138
|
+
if args.verbose and not args.raw:
|
139
|
+
print(f"Dynamic agent callback failed, falling back to static SWML: {e}")
|
140
|
+
# Fall back to static SWML generation
|
141
|
+
swml_doc = agent._render_swml()
|
142
|
+
else:
|
143
|
+
# Static agent - generate SWML normally
|
144
|
+
swml_doc = agent._render_swml()
|
145
|
+
|
146
|
+
if args.raw:
|
147
|
+
# Temporarily restore print for JSON output
|
148
|
+
if '--raw' in sys.argv and 'original_print' in globals():
|
149
|
+
import builtins
|
150
|
+
builtins.print = original_print
|
151
|
+
|
152
|
+
# Output only the raw JSON for piping to jq/yq
|
153
|
+
print(swml_doc)
|
154
|
+
else:
|
155
|
+
# Output formatted JSON (like raw but pretty-printed)
|
156
|
+
try:
|
157
|
+
swml_parsed = json.loads(swml_doc)
|
158
|
+
print(json.dumps(swml_parsed, indent=2))
|
159
|
+
except json.JSONDecodeError:
|
160
|
+
# If not valid JSON, show raw
|
161
|
+
print(swml_doc)
|
162
|
+
|
163
|
+
return 0
|
164
|
+
|
165
|
+
except Exception as e:
|
166
|
+
if args.raw:
|
167
|
+
# For raw mode, output error to stderr to not interfere with JSON output
|
168
|
+
original_print(f"Error generating SWML: {e}", file=sys.stderr)
|
169
|
+
if args.verbose:
|
170
|
+
import traceback
|
171
|
+
traceback.print_exc(file=sys.stderr)
|
172
|
+
else:
|
173
|
+
print(f"Error generating SWML: {e}")
|
174
|
+
if args.verbose:
|
175
|
+
import traceback
|
176
|
+
traceback.print_exc()
|
177
|
+
return 1
|
@@ -0,0 +1 @@
|
|
1
|
+
"""Simulation and mock environment modules"""
|
@@ -0,0 +1,365 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Generate fake SWML post_data and related helpers
|
4
|
+
"""
|
5
|
+
|
6
|
+
import uuid
|
7
|
+
import json
|
8
|
+
from datetime import datetime
|
9
|
+
from typing import Dict, Any, Optional
|
10
|
+
from ..types import CallData, VarsData, PostData
|
11
|
+
|
12
|
+
|
13
|
+
def generate_fake_uuid() -> str:
|
14
|
+
"""Generate a fake UUID for testing"""
|
15
|
+
return str(uuid.uuid4())
|
16
|
+
|
17
|
+
|
18
|
+
def generate_fake_node_id() -> str:
|
19
|
+
"""Generate a fake node ID for testing"""
|
20
|
+
return f"test-node-{uuid.uuid4().hex[:8]}"
|
21
|
+
|
22
|
+
|
23
|
+
def generate_fake_sip_from(call_type: str) -> str:
|
24
|
+
"""Generate a fake 'from' address based on call type"""
|
25
|
+
if call_type == "sip":
|
26
|
+
return f"+1555{uuid.uuid4().hex[:7]}" # Fake phone number
|
27
|
+
else: # webrtc
|
28
|
+
return f"user-{uuid.uuid4().hex[:8]}@test.domain"
|
29
|
+
|
30
|
+
|
31
|
+
def generate_fake_sip_to(call_type: str) -> str:
|
32
|
+
"""Generate a fake 'to' address based on call type"""
|
33
|
+
if call_type == "sip":
|
34
|
+
return f"+1444{uuid.uuid4().hex[:7]}" # Fake phone number
|
35
|
+
else: # webrtc
|
36
|
+
return f"agent-{uuid.uuid4().hex[:8]}@test.domain"
|
37
|
+
|
38
|
+
|
39
|
+
def adapt_for_call_type(call_data: Dict[str, Any], call_type: str) -> Dict[str, Any]:
|
40
|
+
"""
|
41
|
+
Adapt call data structure based on call type (sip vs webrtc)
|
42
|
+
|
43
|
+
Args:
|
44
|
+
call_data: Base call data structure
|
45
|
+
call_type: "sip" or "webrtc"
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
Adapted call data with appropriate addresses and metadata
|
49
|
+
"""
|
50
|
+
call_data = call_data.copy()
|
51
|
+
|
52
|
+
# Update addresses based on call type
|
53
|
+
call_data["from"] = generate_fake_sip_from(call_type)
|
54
|
+
call_data["to"] = generate_fake_sip_to(call_type)
|
55
|
+
|
56
|
+
# Add call type specific metadata
|
57
|
+
if call_type == "sip":
|
58
|
+
call_data["type"] = "phone"
|
59
|
+
call_data["headers"] = {
|
60
|
+
"User-Agent": f"Test-SIP-Client/1.0.0",
|
61
|
+
"From": f"<sip:{call_data['from']}@test.sip.provider>",
|
62
|
+
"To": f"<sip:{call_data['to']}@test.sip.provider>",
|
63
|
+
"Call-ID": call_data["call_id"]
|
64
|
+
}
|
65
|
+
else: # webrtc
|
66
|
+
call_data["type"] = "webrtc"
|
67
|
+
call_data["headers"] = {
|
68
|
+
"User-Agent": "Test-WebRTC-Client/1.0.0",
|
69
|
+
"Origin": "https://test.webrtc.app",
|
70
|
+
"Sec-WebSocket-Protocol": "sip"
|
71
|
+
}
|
72
|
+
|
73
|
+
return call_data
|
74
|
+
|
75
|
+
|
76
|
+
def generate_fake_swml_post_data(call_type: str = "webrtc",
|
77
|
+
call_direction: str = "inbound",
|
78
|
+
call_state: str = "created") -> Dict[str, Any]:
|
79
|
+
"""
|
80
|
+
Generate fake SWML post_data that matches real SignalWire structure
|
81
|
+
|
82
|
+
Args:
|
83
|
+
call_type: "sip" or "webrtc" (default: webrtc)
|
84
|
+
call_direction: "inbound" or "outbound" (default: inbound)
|
85
|
+
call_state: Call state (default: created)
|
86
|
+
|
87
|
+
Returns:
|
88
|
+
Fake post_data dict with call, vars, and envs structure
|
89
|
+
"""
|
90
|
+
call_id = generate_fake_uuid()
|
91
|
+
project_id = generate_fake_uuid()
|
92
|
+
space_id = generate_fake_uuid()
|
93
|
+
current_time = datetime.now().isoformat()
|
94
|
+
|
95
|
+
# Base call structure
|
96
|
+
call_data = {
|
97
|
+
"call_id": call_id,
|
98
|
+
"node_id": generate_fake_node_id(),
|
99
|
+
"segment_id": generate_fake_uuid(),
|
100
|
+
"call_session_id": generate_fake_uuid(),
|
101
|
+
"tag": call_id,
|
102
|
+
"state": call_state,
|
103
|
+
"direction": call_direction,
|
104
|
+
"type": call_type,
|
105
|
+
"from": generate_fake_sip_from(call_type),
|
106
|
+
"to": generate_fake_sip_to(call_type),
|
107
|
+
"timeout": 30,
|
108
|
+
"max_duration": 14400,
|
109
|
+
"answer_on_bridge": False,
|
110
|
+
"hangup_after_bridge": True,
|
111
|
+
"ringback": [],
|
112
|
+
"record": {},
|
113
|
+
"project_id": project_id,
|
114
|
+
"space_id": space_id,
|
115
|
+
"created_at": current_time,
|
116
|
+
"updated_at": current_time
|
117
|
+
}
|
118
|
+
|
119
|
+
# Adapt for specific call type
|
120
|
+
call_data = adapt_for_call_type(call_data, call_type)
|
121
|
+
|
122
|
+
# Complete post_data structure
|
123
|
+
post_data = {
|
124
|
+
"call": call_data,
|
125
|
+
"vars": {
|
126
|
+
"userVariables": {} # Empty by default, can be filled via overrides
|
127
|
+
},
|
128
|
+
"envs": {} # Empty by default, can be filled via overrides
|
129
|
+
}
|
130
|
+
|
131
|
+
return post_data
|
132
|
+
|
133
|
+
|
134
|
+
def generate_comprehensive_post_data(function_name: str, args: Dict[str, Any],
|
135
|
+
custom_data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
136
|
+
"""
|
137
|
+
Generate comprehensive post_data that matches what SignalWire would send
|
138
|
+
|
139
|
+
Args:
|
140
|
+
function_name: Name of the SWAIG function being called
|
141
|
+
args: Function arguments
|
142
|
+
custom_data: Optional custom data to override defaults
|
143
|
+
|
144
|
+
Returns:
|
145
|
+
Complete post_data dict with all possible keys
|
146
|
+
"""
|
147
|
+
call_id = str(uuid.uuid4())
|
148
|
+
session_id = str(uuid.uuid4())
|
149
|
+
project_id = str(uuid.uuid4())
|
150
|
+
space_id = str(uuid.uuid4())
|
151
|
+
space_name = "test-space"
|
152
|
+
environment = "production"
|
153
|
+
|
154
|
+
current_time = datetime.now().isoformat()
|
155
|
+
|
156
|
+
# Base structure with all keys
|
157
|
+
post_data = {
|
158
|
+
"call_id": call_id,
|
159
|
+
"call": {
|
160
|
+
"call_id": call_id,
|
161
|
+
"node_id": f"test-node-{uuid.uuid4().hex[:8]}",
|
162
|
+
"segment_id": str(uuid.uuid4()),
|
163
|
+
"call_session_id": str(uuid.uuid4()),
|
164
|
+
"to": "+15551234567",
|
165
|
+
"from": "+15559876543",
|
166
|
+
"direction": "inbound",
|
167
|
+
"state": "answered",
|
168
|
+
"tag": call_id,
|
169
|
+
"project_id": project_id,
|
170
|
+
"space_id": space_id,
|
171
|
+
"headers": {"User-Agent": "SignalWire/1.0"},
|
172
|
+
"type": "phone",
|
173
|
+
"timeout": 30,
|
174
|
+
"answer_on_bridge": False,
|
175
|
+
"created_at": current_time,
|
176
|
+
"updated_at": current_time
|
177
|
+
},
|
178
|
+
"vars": {
|
179
|
+
"environment": environment,
|
180
|
+
"space_id": space_id,
|
181
|
+
"userVariables": {},
|
182
|
+
"call_data": {
|
183
|
+
"id": call_id,
|
184
|
+
"state": "answered",
|
185
|
+
"type": "phone",
|
186
|
+
"from": "+15559876543",
|
187
|
+
"to": "+15551234567",
|
188
|
+
"project_id": project_id,
|
189
|
+
"created_at": current_time
|
190
|
+
}
|
191
|
+
},
|
192
|
+
"params": args,
|
193
|
+
"space_id": space_id,
|
194
|
+
"project_id": project_id,
|
195
|
+
"meta_data": {
|
196
|
+
"application": {
|
197
|
+
"name": "SignalWire AI Agent",
|
198
|
+
"version": "1.0.0"
|
199
|
+
},
|
200
|
+
"swml": {
|
201
|
+
"version": "1.0.0",
|
202
|
+
"session_id": session_id
|
203
|
+
},
|
204
|
+
"ai": {
|
205
|
+
"call_id": call_id,
|
206
|
+
"session_id": session_id,
|
207
|
+
"conversation_id": session_id
|
208
|
+
},
|
209
|
+
"request": {
|
210
|
+
"method": "POST",
|
211
|
+
"source_ip": "192.168.1.1",
|
212
|
+
"user_agent": "SignalWire-AI-Agent/1.0"
|
213
|
+
},
|
214
|
+
"timing": {
|
215
|
+
"request_start": current_time,
|
216
|
+
"function_start": current_time
|
217
|
+
},
|
218
|
+
"user": {
|
219
|
+
"id": f"user-{uuid.uuid4().hex[:8]}",
|
220
|
+
"session_start": current_time,
|
221
|
+
"last_updated": current_time
|
222
|
+
}
|
223
|
+
},
|
224
|
+
|
225
|
+
# Global application data
|
226
|
+
"global_data": {
|
227
|
+
"app_name": "test_application",
|
228
|
+
"environment": "test",
|
229
|
+
"user_preferences": {"language": "en"},
|
230
|
+
"session_data": {"start_time": current_time}
|
231
|
+
},
|
232
|
+
|
233
|
+
# Conversation context
|
234
|
+
"call_log": [
|
235
|
+
{
|
236
|
+
"role": "system",
|
237
|
+
"content": "You are a helpful AI assistant created with SignalWire AI Agents."
|
238
|
+
},
|
239
|
+
{
|
240
|
+
"role": "user",
|
241
|
+
"content": f"Please call the {function_name} function"
|
242
|
+
},
|
243
|
+
{
|
244
|
+
"role": "assistant",
|
245
|
+
"content": f"I'll call the {function_name} function for you.",
|
246
|
+
"tool_calls": [
|
247
|
+
{
|
248
|
+
"id": f"call_{call_id[:8]}",
|
249
|
+
"type": "function",
|
250
|
+
"function": {
|
251
|
+
"name": function_name,
|
252
|
+
"arguments": json.dumps(args)
|
253
|
+
}
|
254
|
+
}
|
255
|
+
]
|
256
|
+
}
|
257
|
+
],
|
258
|
+
"raw_call_log": [
|
259
|
+
{
|
260
|
+
"role": "system",
|
261
|
+
"content": "You are a helpful AI assistant created with SignalWire AI Agents."
|
262
|
+
},
|
263
|
+
{
|
264
|
+
"role": "user",
|
265
|
+
"content": "Hello"
|
266
|
+
},
|
267
|
+
{
|
268
|
+
"role": "assistant",
|
269
|
+
"content": "Hello! How can I help you today?"
|
270
|
+
},
|
271
|
+
{
|
272
|
+
"role": "user",
|
273
|
+
"content": f"Please call the {function_name} function"
|
274
|
+
},
|
275
|
+
{
|
276
|
+
"role": "assistant",
|
277
|
+
"content": f"I'll call the {function_name} function for you.",
|
278
|
+
"tool_calls": [
|
279
|
+
{
|
280
|
+
"id": f"call_{call_id[:8]}",
|
281
|
+
"type": "function",
|
282
|
+
"function": {
|
283
|
+
"name": function_name,
|
284
|
+
"arguments": json.dumps(args)
|
285
|
+
}
|
286
|
+
}
|
287
|
+
]
|
288
|
+
}
|
289
|
+
],
|
290
|
+
|
291
|
+
# SWML and prompt variables
|
292
|
+
"prompt_vars": {
|
293
|
+
# From SWML prompt variables
|
294
|
+
"ai_instructions": "You are a helpful assistant",
|
295
|
+
"temperature": 0.7,
|
296
|
+
"max_tokens": 1000,
|
297
|
+
# From global_data
|
298
|
+
"app_name": "test_application",
|
299
|
+
"environment": "test",
|
300
|
+
"user_preferences": {"language": "en"},
|
301
|
+
"session_data": {"start_time": current_time},
|
302
|
+
# SWML system variables
|
303
|
+
"current_timestamp": current_time,
|
304
|
+
"call_duration": "00:02:15",
|
305
|
+
"caller_number": "+15551234567",
|
306
|
+
"to_number": "+15559876543"
|
307
|
+
},
|
308
|
+
|
309
|
+
# Permission flags (from SWML parameters)
|
310
|
+
"swaig_allow_swml": True,
|
311
|
+
"swaig_post_conversation": True,
|
312
|
+
"swaig_post_swml_vars": True,
|
313
|
+
|
314
|
+
# Additional context
|
315
|
+
"http_method": "POST",
|
316
|
+
"webhook_url": f"https://test.example.com/webhook/{function_name}",
|
317
|
+
"user_agent": "SignalWire-AI-Agent/1.0",
|
318
|
+
"request_headers": {
|
319
|
+
"Content-Type": "application/json",
|
320
|
+
"User-Agent": "SignalWire-AI-Agent/1.0",
|
321
|
+
"X-Signalwire-Call-Id": call_id,
|
322
|
+
"X-Signalwire-Session-Id": session_id
|
323
|
+
},
|
324
|
+
|
325
|
+
# SignalWire environment data
|
326
|
+
"swml_env": {
|
327
|
+
"space_id": space_id,
|
328
|
+
"project_id": project_id,
|
329
|
+
"environment": environment,
|
330
|
+
"space_name": space_name,
|
331
|
+
"api_version": "1.0.0"
|
332
|
+
}
|
333
|
+
}
|
334
|
+
|
335
|
+
# Apply custom data overrides if provided
|
336
|
+
if custom_data:
|
337
|
+
# Deep merge custom_data into post_data
|
338
|
+
for key, value in custom_data.items():
|
339
|
+
if key in post_data and isinstance(post_data[key], dict) and isinstance(value, dict):
|
340
|
+
# Merge dictionaries
|
341
|
+
post_data[key].update(value)
|
342
|
+
else:
|
343
|
+
# Replace value
|
344
|
+
post_data[key] = value
|
345
|
+
|
346
|
+
return post_data
|
347
|
+
|
348
|
+
|
349
|
+
def generate_minimal_post_data(function_name: str, args: Dict[str, Any]) -> Dict[str, Any]:
|
350
|
+
"""
|
351
|
+
Generate minimal post_data with only essential keys
|
352
|
+
|
353
|
+
Args:
|
354
|
+
function_name: Name of the SWAIG function being called
|
355
|
+
args: Function arguments
|
356
|
+
|
357
|
+
Returns:
|
358
|
+
Minimal post_data dict
|
359
|
+
"""
|
360
|
+
call_id = str(uuid.uuid4())
|
361
|
+
|
362
|
+
return {
|
363
|
+
"call_id": call_id,
|
364
|
+
"params": args
|
365
|
+
}
|