atp-protocol 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- atp/__init__.py +11 -0
- atp/config.py +77 -0
- atp/middleware.py +343 -0
- atp/schemas.py +171 -0
- atp/settlement_client.py +197 -0
- atp/solana_utils.py +1001 -0
- atp/token_prices.py +97 -0
- atp/utils.py +174 -0
- atp/vault.py +75 -0
- atp_protocol-1.0.0.dist-info/LICENSE +201 -0
- atp_protocol-1.0.0.dist-info/METADATA +492 -0
- atp_protocol-1.0.0.dist-info/RECORD +13 -0
- atp_protocol-1.0.0.dist-info/WHEEL +4 -0
atp/__init__.py
ADDED
atp/config.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Central configuration for the ATP Gateway.
|
|
3
|
+
|
|
4
|
+
Keep all env parsing + constants here so the rest of the code can be imported cleanly.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from dotenv import load_dotenv
|
|
13
|
+
|
|
14
|
+
load_dotenv()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _bool_env(name: str, default: bool = False) -> bool:
|
|
18
|
+
v = os.getenv(name)
|
|
19
|
+
if v is None:
|
|
20
|
+
return default
|
|
21
|
+
return v.strip().lower() in {"1", "true", "yes", "y", "on"}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _float_env(name: str) -> Optional[float]:
|
|
25
|
+
v = os.getenv(name)
|
|
26
|
+
if v is None:
|
|
27
|
+
return None
|
|
28
|
+
v = v.strip()
|
|
29
|
+
if not v:
|
|
30
|
+
return None
|
|
31
|
+
return float(v)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
SWARMS_API_KEY = os.getenv("SWARMS_API_KEY")
|
|
35
|
+
SWARMS_API_URL = os.getenv(
|
|
36
|
+
"SWARMS_API_URL", "https://api.swarms.world/v1/agent/completions"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
SOLANA_RPC_URL = os.getenv(
|
|
40
|
+
"SOLANA_RPC_URL", "https://api.mainnet-beta.solana.com"
|
|
41
|
+
)
|
|
42
|
+
AGENT_TREASURY_PUBKEY = os.getenv("AGENT_TREASURY_PUBKEY")
|
|
43
|
+
|
|
44
|
+
JOB_TTL_SECONDS = int(os.getenv("JOB_TTL_SECONDS", "600"))
|
|
45
|
+
|
|
46
|
+
# Swarms Treasury for settlement fees
|
|
47
|
+
SWARMS_TREASURY_PUBKEY = os.getenv(
|
|
48
|
+
"SWARMS_TREASURY_PUBKEY",
|
|
49
|
+
"7MaX4muAn8ZQREJxnupm8sgokwFHujgrGfH9Qn81BuEV",
|
|
50
|
+
)
|
|
51
|
+
SETTLEMENT_FEE_PERCENT = float(
|
|
52
|
+
os.getenv("SETTLEMENT_FEE_PERCENT", "0.05")
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# USDC Token Configuration (Solana Mainnet)
|
|
56
|
+
USDC_MINT_ADDRESS = os.getenv(
|
|
57
|
+
"USDC_MINT_ADDRESS",
|
|
58
|
+
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
59
|
+
)
|
|
60
|
+
USDC_DECIMALS = int(os.getenv("USDC_DECIMALS", "6"))
|
|
61
|
+
|
|
62
|
+
SUPPORTED_PAYMENT_TOKENS = ["SOL", "USDC"]
|
|
63
|
+
|
|
64
|
+
# Agent pricing configuration (host-provided)
|
|
65
|
+
INPUT_COST_PER_MILLION_USD = _float_env("INPUT_COST_PER_MILLION_USD")
|
|
66
|
+
OUTPUT_COST_PER_MILLION_USD = _float_env(
|
|
67
|
+
"OUTPUT_COST_PER_MILLION_USD"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Verbose Solana debug logging (server-side). Enable temporarily for diagnosing RPC type mismatches.
|
|
71
|
+
# WARNING: do not enable permanently in high-traffic production environments.
|
|
72
|
+
ATP_SOLANA_DEBUG = _bool_env("ATP_SOLANA_DEBUG", default=False)
|
|
73
|
+
|
|
74
|
+
# Settlement Service URL
|
|
75
|
+
ATP_SETTLEMENT_URL = os.getenv(
|
|
76
|
+
"ATP_SETTLEMENT_URL", "http://localhost:8001"
|
|
77
|
+
)
|
atp/middleware.py
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI middleware for ATP settlement on any endpoint.
|
|
3
|
+
|
|
4
|
+
This middleware enables automatic payment deduction from Solana wallets
|
|
5
|
+
based on token usage (input/output tokens) for any configured endpoint.
|
|
6
|
+
|
|
7
|
+
The middleware delegates all settlement logic to the ATP Settlement Service,
|
|
8
|
+
ensuring immutable and centralized settlement operations.
|
|
9
|
+
|
|
10
|
+
The middleware accepts wallet private keys directly via headers, making it
|
|
11
|
+
simple to use without requiring API key management. Users can add their
|
|
12
|
+
own API key handling layer if needed.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
from typing import Any, Callable, Dict, List, Optional, Set
|
|
19
|
+
|
|
20
|
+
from fastapi import HTTPException, Request, Response
|
|
21
|
+
from loguru import logger
|
|
22
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
23
|
+
from starlette.types import ASGIApp
|
|
24
|
+
|
|
25
|
+
from atp import config
|
|
26
|
+
from atp.schemas import PaymentToken
|
|
27
|
+
from atp.settlement_client import SettlementServiceClient
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ATPSettlementMiddleware(BaseHTTPMiddleware):
|
|
31
|
+
"""
|
|
32
|
+
FastAPI middleware that automatically deducts payment from Solana wallets
|
|
33
|
+
based on token usage for configured endpoints.
|
|
34
|
+
|
|
35
|
+
The middleware delegates all settlement logic to the ATP Settlement Service,
|
|
36
|
+
ensuring immutable and centralized settlement operations.
|
|
37
|
+
|
|
38
|
+
The middleware accepts wallet private keys directly via headers, making it
|
|
39
|
+
simple to use. Users can add their own API key handling layer if needed.
|
|
40
|
+
|
|
41
|
+
Payments are split automatically:
|
|
42
|
+
- Treasury (SWARMS_TREASURY_PUBKEY) receives the processing fee
|
|
43
|
+
- Recipient (endpoint host) receives the remainder
|
|
44
|
+
|
|
45
|
+
Usage:
|
|
46
|
+
app.add_middleware(
|
|
47
|
+
ATPSettlementMiddleware,
|
|
48
|
+
allowed_endpoints=["/v1/chat", "/v1/completions"],
|
|
49
|
+
input_cost_per_million_usd=10.0,
|
|
50
|
+
output_cost_per_million_usd=30.0,
|
|
51
|
+
wallet_private_key_header="x-wallet-private-key",
|
|
52
|
+
payment_token=PaymentToken.SOL,
|
|
53
|
+
recipient_pubkey="YourPublicKeyHere", # Required: endpoint host receives main payment
|
|
54
|
+
# settlement_service_url is optional - uses ATP_SETTLEMENT_URL env var by default
|
|
55
|
+
)
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
app: ASGIApp,
|
|
61
|
+
*,
|
|
62
|
+
allowed_endpoints: List[str],
|
|
63
|
+
input_cost_per_million_usd: float,
|
|
64
|
+
output_cost_per_million_usd: float,
|
|
65
|
+
wallet_private_key_header: str = "x-wallet-private-key",
|
|
66
|
+
payment_token: PaymentToken = PaymentToken.SOL,
|
|
67
|
+
recipient_pubkey: Optional[str] = None,
|
|
68
|
+
skip_preflight: bool = False,
|
|
69
|
+
commitment: str = "confirmed",
|
|
70
|
+
usage_response_key: str = "usage",
|
|
71
|
+
include_usage_in_response: bool = True,
|
|
72
|
+
require_wallet: bool = True,
|
|
73
|
+
settlement_service_url: Optional[str] = None,
|
|
74
|
+
):
|
|
75
|
+
"""
|
|
76
|
+
Initialize the ATP settlement middleware.
|
|
77
|
+
|
|
78
|
+
The middleware delegates all settlement logic to the ATP Settlement Service.
|
|
79
|
+
All settlement operations are handled by the immutable settlement service.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
app: The ASGI application.
|
|
83
|
+
allowed_endpoints: List of endpoint paths to apply settlement to (e.g., ["/v1/chat"]).
|
|
84
|
+
Supports path patterns - exact matches only.
|
|
85
|
+
input_cost_per_million_usd: Cost per million input tokens in USD.
|
|
86
|
+
output_cost_per_million_usd: Cost per million output tokens in USD.
|
|
87
|
+
wallet_private_key_header: HTTP header name containing the wallet private key
|
|
88
|
+
(default: "x-wallet-private-key"). The private key should be in JSON array
|
|
89
|
+
format (e.g., "[1,2,3,...]") or base58 string format.
|
|
90
|
+
payment_token: Token to use for payment (SOL or USDC).
|
|
91
|
+
recipient_pubkey: Solana public key of the recipient wallet (the endpoint host).
|
|
92
|
+
This wallet receives the main payment (after processing fee). Required.
|
|
93
|
+
skip_preflight: Whether to skip preflight simulation for Solana transactions.
|
|
94
|
+
commitment: Solana commitment level (processed|confirmed|finalized).
|
|
95
|
+
usage_response_key: Key in response JSON where usage data is located (default: "usage").
|
|
96
|
+
include_usage_in_response: Whether to add usage/cost info to the response.
|
|
97
|
+
require_wallet: Whether to require wallet private key (if False, skips settlement when missing).
|
|
98
|
+
settlement_service_url: Base URL of the settlement service. If not provided, uses
|
|
99
|
+
ATP_SETTLEMENT_URL environment variable (default: http://localhost:8001).
|
|
100
|
+
The middleware always uses the settlement service for all settlement operations.
|
|
101
|
+
"""
|
|
102
|
+
super().__init__(app)
|
|
103
|
+
self.allowed_endpoints: Set[str] = set(allowed_endpoints)
|
|
104
|
+
self.input_cost_per_million_usd = input_cost_per_million_usd
|
|
105
|
+
self.output_cost_per_million_usd = output_cost_per_million_usd
|
|
106
|
+
self.wallet_private_key_header = (
|
|
107
|
+
wallet_private_key_header.lower()
|
|
108
|
+
)
|
|
109
|
+
self.payment_token = payment_token
|
|
110
|
+
# Recipient pubkey - configurable, the endpoint host receives the main payment
|
|
111
|
+
self._recipient_pubkey = recipient_pubkey
|
|
112
|
+
if not self._recipient_pubkey:
|
|
113
|
+
raise ValueError("recipient_pubkey must be provided")
|
|
114
|
+
# Treasury pubkey - always uses SWARMS_TREASURY_PUBKEY for processing fees
|
|
115
|
+
self._treasury_pubkey = config.SWARMS_TREASURY_PUBKEY
|
|
116
|
+
if not self._treasury_pubkey:
|
|
117
|
+
raise ValueError(
|
|
118
|
+
"SWARMS_TREASURY_PUBKEY must be set in configuration"
|
|
119
|
+
)
|
|
120
|
+
self.skip_preflight = skip_preflight
|
|
121
|
+
self.commitment = commitment
|
|
122
|
+
self.usage_response_key = usage_response_key
|
|
123
|
+
self.include_usage_in_response = include_usage_in_response
|
|
124
|
+
self.require_wallet = require_wallet
|
|
125
|
+
# Always use settlement service - initialize client with config value or provided URL
|
|
126
|
+
service_url = (
|
|
127
|
+
settlement_service_url or config.ATP_SETTLEMENT_URL
|
|
128
|
+
)
|
|
129
|
+
self.settlement_service_client = SettlementServiceClient(
|
|
130
|
+
base_url=service_url
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def _should_process(self, path: str) -> bool:
|
|
134
|
+
"""Check if the request path should be processed by this middleware."""
|
|
135
|
+
return path in self.allowed_endpoints
|
|
136
|
+
|
|
137
|
+
def _extract_wallet_private_key(
|
|
138
|
+
self, request: Request
|
|
139
|
+
) -> Optional[str]:
|
|
140
|
+
"""Extract wallet private key from request headers."""
|
|
141
|
+
return request.headers.get(self.wallet_private_key_header)
|
|
142
|
+
|
|
143
|
+
async def _extract_usage_from_response(
|
|
144
|
+
self, response_body: bytes
|
|
145
|
+
) -> Optional[Dict[str, Any]]:
|
|
146
|
+
"""
|
|
147
|
+
Extract usage information from response body.
|
|
148
|
+
|
|
149
|
+
Tries multiple strategies:
|
|
150
|
+
1. Look for usage data at the configured usage_response_key
|
|
151
|
+
2. Check if the entire response contains usage-like keys
|
|
152
|
+
3. Try nested structures (usage.usage, meta.usage, etc.)
|
|
153
|
+
|
|
154
|
+
The usage data is then sent to the settlement service for parsing,
|
|
155
|
+
so we just need to extract the raw usage object.
|
|
156
|
+
"""
|
|
157
|
+
try:
|
|
158
|
+
body_str = response_body.decode("utf-8")
|
|
159
|
+
if not body_str.strip():
|
|
160
|
+
return None
|
|
161
|
+
data = json.loads(body_str)
|
|
162
|
+
|
|
163
|
+
# Strategy 1: Try the configured usage key first
|
|
164
|
+
usage = data.get(self.usage_response_key)
|
|
165
|
+
if usage and isinstance(usage, dict):
|
|
166
|
+
return usage
|
|
167
|
+
|
|
168
|
+
# Strategy 2: Check if the entire response is usage-like
|
|
169
|
+
if isinstance(data, dict):
|
|
170
|
+
# Check for common usage keys at top level
|
|
171
|
+
usage_keys = [
|
|
172
|
+
"input_tokens",
|
|
173
|
+
"output_tokens",
|
|
174
|
+
"prompt_tokens",
|
|
175
|
+
"completion_tokens",
|
|
176
|
+
"total_tokens",
|
|
177
|
+
"tokens",
|
|
178
|
+
"promptTokenCount",
|
|
179
|
+
"candidatesTokenCount",
|
|
180
|
+
"totalTokenCount",
|
|
181
|
+
]
|
|
182
|
+
if any(key in data for key in usage_keys):
|
|
183
|
+
return data
|
|
184
|
+
|
|
185
|
+
# Strategy 3: Try nested structures
|
|
186
|
+
# Check for usage nested in common locations
|
|
187
|
+
for nested_key in [
|
|
188
|
+
"usage",
|
|
189
|
+
"token_usage",
|
|
190
|
+
"tokens",
|
|
191
|
+
"statistics",
|
|
192
|
+
"meta",
|
|
193
|
+
]:
|
|
194
|
+
if nested_key in data and isinstance(
|
|
195
|
+
data[nested_key], dict
|
|
196
|
+
):
|
|
197
|
+
nested_usage = data[nested_key]
|
|
198
|
+
# Check if it looks like usage data
|
|
199
|
+
if any(
|
|
200
|
+
key in nested_usage
|
|
201
|
+
for key in [
|
|
202
|
+
"input_tokens",
|
|
203
|
+
"output_tokens",
|
|
204
|
+
"prompt_tokens",
|
|
205
|
+
"completion_tokens",
|
|
206
|
+
"tokens",
|
|
207
|
+
]
|
|
208
|
+
):
|
|
209
|
+
return nested_usage
|
|
210
|
+
|
|
211
|
+
return None
|
|
212
|
+
except (json.JSONDecodeError, UnicodeDecodeError) as e:
|
|
213
|
+
logger.debug(
|
|
214
|
+
f"Failed to parse response body for usage: {e}"
|
|
215
|
+
)
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
async def dispatch(
|
|
219
|
+
self, request: Request, call_next: Callable
|
|
220
|
+
) -> Response:
|
|
221
|
+
"""Process the request and apply settlement if applicable."""
|
|
222
|
+
path = request.url.path
|
|
223
|
+
|
|
224
|
+
# Skip if not in allowed endpoints
|
|
225
|
+
if not self._should_process(path):
|
|
226
|
+
return await call_next(request)
|
|
227
|
+
|
|
228
|
+
# Extract wallet private key
|
|
229
|
+
private_key = self._extract_wallet_private_key(request)
|
|
230
|
+
if not private_key:
|
|
231
|
+
if self.require_wallet:
|
|
232
|
+
raise HTTPException(
|
|
233
|
+
status_code=401,
|
|
234
|
+
detail=f"Missing wallet private key in header: {self.wallet_private_key_header}",
|
|
235
|
+
)
|
|
236
|
+
# If wallet not required, skip settlement
|
|
237
|
+
return await call_next(request)
|
|
238
|
+
|
|
239
|
+
# Execute the endpoint
|
|
240
|
+
response = await call_next(request)
|
|
241
|
+
|
|
242
|
+
# Only process successful responses
|
|
243
|
+
if response.status_code >= 400:
|
|
244
|
+
return response
|
|
245
|
+
|
|
246
|
+
# Extract usage from response
|
|
247
|
+
response_body = b""
|
|
248
|
+
async for chunk in response.body_iterator:
|
|
249
|
+
response_body += chunk
|
|
250
|
+
|
|
251
|
+
usage = await self._extract_usage_from_response(response_body)
|
|
252
|
+
|
|
253
|
+
if not usage:
|
|
254
|
+
logger.warning(
|
|
255
|
+
f"No usage data found in response for {path}. Response keys: {list(json.loads(response_body.decode('utf-8')).keys()) if response_body else 'empty'}"
|
|
256
|
+
)
|
|
257
|
+
# Return original response if no usage found
|
|
258
|
+
return Response(
|
|
259
|
+
content=response_body,
|
|
260
|
+
status_code=response.status_code,
|
|
261
|
+
headers=dict(response.headers),
|
|
262
|
+
media_type=response.media_type,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
# Calculate and deduct payment via settlement service
|
|
266
|
+
try:
|
|
267
|
+
payment_result = await self.settlement_service_client.settle(
|
|
268
|
+
private_key=private_key,
|
|
269
|
+
usage=usage,
|
|
270
|
+
input_cost_per_million_usd=self.input_cost_per_million_usd,
|
|
271
|
+
output_cost_per_million_usd=self.output_cost_per_million_usd,
|
|
272
|
+
recipient_pubkey=self._recipient_pubkey,
|
|
273
|
+
payment_token=self.payment_token.value,
|
|
274
|
+
treasury_pubkey=self._treasury_pubkey,
|
|
275
|
+
skip_preflight=self.skip_preflight,
|
|
276
|
+
commitment=self.commitment,
|
|
277
|
+
)
|
|
278
|
+
except HTTPException:
|
|
279
|
+
raise
|
|
280
|
+
except Exception as e:
|
|
281
|
+
logger.error(f"Settlement error: {e}", exc_info=True)
|
|
282
|
+
raise HTTPException(
|
|
283
|
+
status_code=500,
|
|
284
|
+
detail=f"Settlement failed: {str(e)}",
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# Modify response to include usage/payment info if requested
|
|
288
|
+
if self.include_usage_in_response:
|
|
289
|
+
try:
|
|
290
|
+
response_data = json.loads(
|
|
291
|
+
response_body.decode("utf-8")
|
|
292
|
+
)
|
|
293
|
+
response_data["atp_settlement"] = payment_result
|
|
294
|
+
response_data["atp_usage"] = usage
|
|
295
|
+
response_body = json.dumps(response_data).encode(
|
|
296
|
+
"utf-8"
|
|
297
|
+
)
|
|
298
|
+
except Exception as e:
|
|
299
|
+
logger.warning(
|
|
300
|
+
f"Failed to add settlement info to response: {e}"
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
return Response(
|
|
304
|
+
content=response_body,
|
|
305
|
+
status_code=response.status_code,
|
|
306
|
+
headers=dict(response.headers),
|
|
307
|
+
media_type=response.media_type,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def create_settlement_middleware(
|
|
312
|
+
allowed_endpoints: List[str],
|
|
313
|
+
input_cost_per_million_usd: float,
|
|
314
|
+
output_cost_per_million_usd: float,
|
|
315
|
+
**kwargs: Any,
|
|
316
|
+
) -> type[ATPSettlementMiddleware]:
|
|
317
|
+
"""
|
|
318
|
+
Factory function to create a configured ATP settlement middleware.
|
|
319
|
+
|
|
320
|
+
Example:
|
|
321
|
+
middleware = create_settlement_middleware(
|
|
322
|
+
allowed_endpoints=["/v1/chat", "/v1/completions"],
|
|
323
|
+
input_cost_per_million_usd=10.0,
|
|
324
|
+
output_cost_per_million_usd=30.0,
|
|
325
|
+
wallet_private_key_header="x-wallet-private-key",
|
|
326
|
+
recipient_pubkey="YourPublicKeyHere", # Optional: defaults to SWARMS_TREASURY_PUBKEY
|
|
327
|
+
)
|
|
328
|
+
app.add_middleware(middleware)
|
|
329
|
+
"""
|
|
330
|
+
return type(
|
|
331
|
+
"ConfiguredATPSettlementMiddleware",
|
|
332
|
+
(ATPSettlementMiddleware,),
|
|
333
|
+
{
|
|
334
|
+
"__init__": lambda self, app: ATPSettlementMiddleware.__init__(
|
|
335
|
+
self,
|
|
336
|
+
app,
|
|
337
|
+
allowed_endpoints=allowed_endpoints,
|
|
338
|
+
input_cost_per_million_usd=input_cost_per_million_usd,
|
|
339
|
+
output_cost_per_million_usd=output_cost_per_million_usd,
|
|
340
|
+
**kwargs,
|
|
341
|
+
)
|
|
342
|
+
},
|
|
343
|
+
)
|
atp/schemas.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Any, Dict, List, Optional, Union
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
from swarms.schemas.mcp_schemas import (
|
|
6
|
+
MCPConnection,
|
|
7
|
+
MultipleMCPConnections,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AgentSpec(BaseModel):
|
|
12
|
+
agent_name: Optional[str] = Field(
|
|
13
|
+
# default=None,
|
|
14
|
+
description="The unique name assigned to the agent, which identifies its role and functionality within the swarm.",
|
|
15
|
+
)
|
|
16
|
+
description: Optional[str] = Field(
|
|
17
|
+
default=None,
|
|
18
|
+
description="A detailed explanation of the agent's purpose, capabilities, and any specific tasks it is designed to perform.",
|
|
19
|
+
)
|
|
20
|
+
system_prompt: Optional[str] = Field(
|
|
21
|
+
default=None,
|
|
22
|
+
description="The initial instruction or context provided to the agent, guiding its behavior and responses during execution.",
|
|
23
|
+
)
|
|
24
|
+
model_name: Optional[str] = Field(
|
|
25
|
+
default="gpt-4.1",
|
|
26
|
+
description="The name of the AI model that the agent will utilize for processing tasks and generating outputs. For example: gpt-4o, gpt-4o-mini, openai/o3-mini",
|
|
27
|
+
)
|
|
28
|
+
auto_generate_prompt: Optional[bool] = Field(
|
|
29
|
+
default=False,
|
|
30
|
+
description="A flag indicating whether the agent should automatically create prompts based on the task requirements.",
|
|
31
|
+
)
|
|
32
|
+
max_tokens: Optional[int] = Field(
|
|
33
|
+
default=8192,
|
|
34
|
+
description="The maximum number of tokens that the agent is allowed to generate in its responses, limiting output length.",
|
|
35
|
+
)
|
|
36
|
+
temperature: Optional[float] = Field(
|
|
37
|
+
default=0.5,
|
|
38
|
+
description="A parameter that controls the randomness of the agent's output; lower values result in more deterministic responses.",
|
|
39
|
+
)
|
|
40
|
+
role: Optional[str] = Field(
|
|
41
|
+
default="worker",
|
|
42
|
+
description="The designated role of the agent within the swarm, which influences its behavior and interaction with other agents.",
|
|
43
|
+
)
|
|
44
|
+
max_loops: Optional[int] = Field(
|
|
45
|
+
default=1,
|
|
46
|
+
description="The maximum number of times the agent is allowed to repeat its task, enabling iterative processing if necessary.",
|
|
47
|
+
)
|
|
48
|
+
tools_list_dictionary: Optional[List[Dict[Any, Any]]] = Field(
|
|
49
|
+
default=None,
|
|
50
|
+
description="A dictionary of tools that the agent can use to complete its task.",
|
|
51
|
+
)
|
|
52
|
+
mcp_url: Optional[str] = Field(
|
|
53
|
+
default=None,
|
|
54
|
+
description="The URL of the MCP server that the agent can use to complete its task.",
|
|
55
|
+
)
|
|
56
|
+
streaming_on: Optional[bool] = Field(
|
|
57
|
+
default=False,
|
|
58
|
+
description="A flag indicating whether the agent should stream its output.",
|
|
59
|
+
)
|
|
60
|
+
llm_args: Optional[Dict[str, Any]] = Field(
|
|
61
|
+
default=None,
|
|
62
|
+
description="Additional arguments to pass to the LLM such as top_p, frequency_penalty, presence_penalty, etc.",
|
|
63
|
+
)
|
|
64
|
+
dynamic_temperature_enabled: Optional[bool] = Field(
|
|
65
|
+
default=True,
|
|
66
|
+
description="A flag indicating whether the agent should dynamically adjust its temperature based on the task.",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
mcp_config: Optional[MCPConnection] = Field(
|
|
70
|
+
default=None,
|
|
71
|
+
description="The MCP connection to use for the agent.",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
mcp_configs: Optional[MultipleMCPConnections] = Field(
|
|
75
|
+
default=None,
|
|
76
|
+
description="The MCP connections to use for the agent. This is a list of MCP connections. Includes multiple MCP connections.",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
tool_call_summary: Optional[bool] = Field(
|
|
80
|
+
default=True,
|
|
81
|
+
description="A parameter enabling an agent to summarize tool calls.",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
reasoning_effort: Optional[str] = Field(
|
|
85
|
+
default=None,
|
|
86
|
+
description="The effort to put into reasoning.",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
thinking_tokens: Optional[int] = Field(
|
|
90
|
+
default=None,
|
|
91
|
+
description="The number of tokens to use for thinking.",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
reasoning_enabled: Optional[bool] = Field(
|
|
95
|
+
default=False,
|
|
96
|
+
description="A parameter enabling an agent to use reasoning.",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
class Config:
|
|
100
|
+
arbitrary_types_allowed = True
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class PaymentToken(str, Enum):
|
|
104
|
+
"""Supported payment tokens on Solana."""
|
|
105
|
+
|
|
106
|
+
SOL = "SOL"
|
|
107
|
+
USDC = "USDC"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class AgentTask(BaseModel):
|
|
111
|
+
"""Complete agent task request requiring full agent specification."""
|
|
112
|
+
|
|
113
|
+
agent_config: AgentSpec = Field(
|
|
114
|
+
...,
|
|
115
|
+
description="Complete agent configuration specification matching the Swarms API AgentSpec schema",
|
|
116
|
+
)
|
|
117
|
+
task: str = Field(
|
|
118
|
+
...,
|
|
119
|
+
description="The task or query to execute",
|
|
120
|
+
example="Analyze the latest SOL/USDC liquidity pool data and provide trading recommendations.",
|
|
121
|
+
)
|
|
122
|
+
user_wallet: str = Field(
|
|
123
|
+
...,
|
|
124
|
+
description="The Solana public key of the sender for payment verification",
|
|
125
|
+
)
|
|
126
|
+
payment_token: PaymentToken = Field(
|
|
127
|
+
default=PaymentToken.SOL,
|
|
128
|
+
description="Payment token to use for settlement (SOL or USDC)",
|
|
129
|
+
)
|
|
130
|
+
history: Optional[Union[Dict[Any, Any], List[Dict[str, str]]]] = (
|
|
131
|
+
Field(
|
|
132
|
+
default=None,
|
|
133
|
+
description="Optional conversation history for context",
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
img: Optional[str] = Field(
|
|
137
|
+
default=None,
|
|
138
|
+
description="Optional image URL for vision tasks",
|
|
139
|
+
)
|
|
140
|
+
imgs: Optional[List[str]] = Field(
|
|
141
|
+
default=None,
|
|
142
|
+
description="Optional list of image URLs for vision tasks",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class SettleTrade(BaseModel):
|
|
147
|
+
"""Settlement request that asks the facilitator to sign+send the payment tx.
|
|
148
|
+
|
|
149
|
+
WARNING: This is custodial-like behavior. The private key is used in-memory only
|
|
150
|
+
for the duration of this request and is not persisted.
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
job_id: str = Field(
|
|
154
|
+
..., description="Job ID from the trade creation response"
|
|
155
|
+
)
|
|
156
|
+
private_key: str = Field(
|
|
157
|
+
...,
|
|
158
|
+
description=(
|
|
159
|
+
"Payer private key encoded as a string. Supported formats:\n"
|
|
160
|
+
"- Base58 keypair (common Solana secret key string)\n"
|
|
161
|
+
"- JSON array of ints (e.g. '[12,34,...]')"
|
|
162
|
+
),
|
|
163
|
+
)
|
|
164
|
+
skip_preflight: bool = Field(
|
|
165
|
+
default=False,
|
|
166
|
+
description="Whether to skip preflight simulation",
|
|
167
|
+
)
|
|
168
|
+
commitment: str = Field(
|
|
169
|
+
default="confirmed",
|
|
170
|
+
description="Confirmation level to wait for (processed|confirmed|finalized)",
|
|
171
|
+
)
|