webagents 0.2.2__py3-none-any.whl → 0.2.3__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.
- webagents/__init__.py +9 -0
- webagents/agents/core/base_agent.py +865 -69
- webagents/agents/core/handoffs.py +14 -6
- webagents/agents/skills/base.py +33 -2
- webagents/agents/skills/core/llm/litellm/skill.py +906 -27
- webagents/agents/skills/core/memory/vector_memory/skill.py +8 -16
- webagents/agents/skills/ecosystem/openai/__init__.py +6 -0
- webagents/agents/skills/ecosystem/openai/skill.py +867 -0
- webagents/agents/skills/ecosystem/replicate/README.md +440 -0
- webagents/agents/skills/ecosystem/replicate/__init__.py +10 -0
- webagents/agents/skills/ecosystem/replicate/skill.py +517 -0
- webagents/agents/skills/examples/__init__.py +6 -0
- webagents/agents/skills/examples/music_player.py +329 -0
- webagents/agents/skills/robutler/handoff/__init__.py +6 -0
- webagents/agents/skills/robutler/handoff/skill.py +191 -0
- webagents/agents/skills/robutler/nli/skill.py +180 -24
- webagents/agents/skills/robutler/payments/exceptions.py +27 -7
- webagents/agents/skills/robutler/payments/skill.py +64 -14
- webagents/agents/skills/robutler/storage/files/skill.py +2 -2
- webagents/agents/tools/decorators.py +243 -47
- webagents/agents/widgets/__init__.py +6 -0
- webagents/agents/widgets/renderer.py +150 -0
- webagents/server/core/app.py +130 -15
- webagents/server/core/models.py +1 -1
- webagents/utils/logging.py +13 -1
- {webagents-0.2.2.dist-info → webagents-0.2.3.dist-info}/METADATA +8 -25
- {webagents-0.2.2.dist-info → webagents-0.2.3.dist-info}/RECORD +30 -20
- webagents/agents/skills/ecosystem/openai_agents/__init__.py +0 -0
- {webagents-0.2.2.dist-info → webagents-0.2.3.dist-info}/WHEEL +0 -0
- {webagents-0.2.2.dist-info → webagents-0.2.3.dist-info}/entry_points.txt +0 -0
- {webagents-0.2.2.dist-info → webagents-0.2.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,517 @@
|
|
1
|
+
"""
|
2
|
+
Replicate Skill for WebAgents
|
3
|
+
|
4
|
+
This skill allows users to:
|
5
|
+
- Set up Replicate API key securely (via auth/kv skills)
|
6
|
+
- List available models on Replicate
|
7
|
+
- Run predictions with models
|
8
|
+
- Get prediction status and results
|
9
|
+
- Cancel running predictions
|
10
|
+
|
11
|
+
Uses auth skill for user context and kv skill for secure API key storage.
|
12
|
+
"""
|
13
|
+
|
14
|
+
import os
|
15
|
+
import httpx
|
16
|
+
import json
|
17
|
+
import asyncio
|
18
|
+
from typing import Dict, Any, Optional, List, Union
|
19
|
+
from datetime import datetime
|
20
|
+
|
21
|
+
from webagents.agents.skills.base import Skill
|
22
|
+
from webagents.agents.tools.decorators import tool, prompt
|
23
|
+
from webagents.server.context.context_vars import get_context
|
24
|
+
|
25
|
+
|
26
|
+
class ReplicateSkill(Skill):
|
27
|
+
"""Replicate skill for running machine learning models"""
|
28
|
+
|
29
|
+
def __init__(self):
|
30
|
+
super().__init__()
|
31
|
+
self.api_base = "https://api.replicate.com/v1"
|
32
|
+
|
33
|
+
def get_dependencies(self) -> List[str]:
|
34
|
+
"""Skill dependencies"""
|
35
|
+
return ['auth', 'kv']
|
36
|
+
|
37
|
+
@prompt(priority=40, scope=["owner", "all"])
|
38
|
+
def replicate_prompt(self) -> str:
|
39
|
+
"""Prompt describing Replicate capabilities"""
|
40
|
+
return """
|
41
|
+
Replicate integration for running machine learning models. Available tools:
|
42
|
+
|
43
|
+
• replicate_setup(api_token) - Set up Replicate API credentials securely
|
44
|
+
• replicate_list_models(owner) - List models from a specific owner or popular models
|
45
|
+
• replicate_run_prediction(model, input_data) - Run a prediction with a model
|
46
|
+
• replicate_get_prediction(prediction_id) - Get prediction status and results
|
47
|
+
• replicate_cancel_prediction(prediction_id) - Cancel a running prediction
|
48
|
+
• replicate_get_model_info(model) - Get detailed information about a model
|
49
|
+
|
50
|
+
Features:
|
51
|
+
- Secure API token storage via KV skill
|
52
|
+
- Per-user credential isolation via Auth skill
|
53
|
+
- Run any public model on Replicate
|
54
|
+
- Monitor prediction progress and results
|
55
|
+
- Handle both sync and async predictions
|
56
|
+
- Support for text, image, audio, and video models
|
57
|
+
|
58
|
+
Setup: First run replicate_setup() with your Replicate API token from your account settings.
|
59
|
+
"""
|
60
|
+
|
61
|
+
# Helper methods for auth and kv skills
|
62
|
+
async def _get_auth_skill(self):
|
63
|
+
"""Get auth skill for user context"""
|
64
|
+
return self.agent.skills.get('auth')
|
65
|
+
|
66
|
+
async def _get_kv_skill(self):
|
67
|
+
"""Get KV skill for secure storage"""
|
68
|
+
return self.agent.skills.get('kv')
|
69
|
+
|
70
|
+
async def _get_authenticated_user_id(self) -> Optional[str]:
|
71
|
+
"""Get authenticated user ID from context"""
|
72
|
+
try:
|
73
|
+
context = get_context()
|
74
|
+
if context and context.auth and context.auth.authenticated:
|
75
|
+
return context.auth.user_id
|
76
|
+
return None
|
77
|
+
except Exception as e:
|
78
|
+
if hasattr(self, 'logger'):
|
79
|
+
self.logger.error(f"Failed to get user context: {e}")
|
80
|
+
return None
|
81
|
+
|
82
|
+
async def _save_replicate_credentials(self, user_id: str, api_token: str) -> bool:
|
83
|
+
"""Save Replicate credentials securely using KV skill"""
|
84
|
+
try:
|
85
|
+
kv_skill = await self._get_kv_skill()
|
86
|
+
if kv_skill:
|
87
|
+
credentials = {
|
88
|
+
'api_token': api_token,
|
89
|
+
'created_at': datetime.now().isoformat()
|
90
|
+
}
|
91
|
+
try:
|
92
|
+
await kv_skill.kv_set(
|
93
|
+
key='credentials',
|
94
|
+
value=json.dumps(credentials),
|
95
|
+
namespace=f'replicate:{user_id}'
|
96
|
+
)
|
97
|
+
return True
|
98
|
+
except Exception as kv_error:
|
99
|
+
if hasattr(self, 'logger'):
|
100
|
+
self.logger.error(f"KV storage failed, falling back to memory: {kv_error}")
|
101
|
+
# Fall through to memory storage
|
102
|
+
|
103
|
+
# Fallback to in-memory storage (either no KV skill or KV failed)
|
104
|
+
if not hasattr(self.agent, '_replicate_credentials'):
|
105
|
+
self.agent._replicate_credentials = {}
|
106
|
+
self.agent._replicate_credentials[user_id] = {
|
107
|
+
'api_token': api_token,
|
108
|
+
'created_at': datetime.now().isoformat()
|
109
|
+
}
|
110
|
+
return True
|
111
|
+
except Exception as e:
|
112
|
+
if hasattr(self, 'logger'):
|
113
|
+
self.logger.error(f"Failed to save Replicate credentials: {e}")
|
114
|
+
return False
|
115
|
+
|
116
|
+
async def _load_replicate_credentials(self, user_id: str) -> Optional[Dict[str, str]]:
|
117
|
+
"""Load Replicate credentials from KV skill"""
|
118
|
+
try:
|
119
|
+
kv_skill = await self._get_kv_skill()
|
120
|
+
if kv_skill:
|
121
|
+
try:
|
122
|
+
credentials_json = await kv_skill.kv_get(
|
123
|
+
key='credentials',
|
124
|
+
namespace=f'replicate:{user_id}'
|
125
|
+
)
|
126
|
+
if credentials_json:
|
127
|
+
return json.loads(credentials_json)
|
128
|
+
except Exception as kv_error:
|
129
|
+
if hasattr(self, 'logger'):
|
130
|
+
self.logger.error(f"KV retrieval failed, falling back to memory: {kv_error}")
|
131
|
+
# Fall through to memory storage
|
132
|
+
|
133
|
+
# Fallback to in-memory storage (either no KV skill or KV failed)
|
134
|
+
if hasattr(self.agent, '_replicate_credentials'):
|
135
|
+
return self.agent._replicate_credentials.get(user_id)
|
136
|
+
return None
|
137
|
+
except Exception as e:
|
138
|
+
if hasattr(self, 'logger'):
|
139
|
+
self.logger.error(f"Failed to load Replicate credentials: {e}")
|
140
|
+
return None
|
141
|
+
|
142
|
+
async def _make_replicate_request(self, method: str, endpoint: str, data: Optional[Dict] = None, user_id: str = None) -> Dict[str, Any]:
|
143
|
+
"""Make authenticated request to Replicate API"""
|
144
|
+
if not user_id:
|
145
|
+
user_id = await self._get_authenticated_user_id()
|
146
|
+
if not user_id:
|
147
|
+
raise Exception("Authentication required")
|
148
|
+
|
149
|
+
credentials = await self._load_replicate_credentials(user_id)
|
150
|
+
if not credentials:
|
151
|
+
raise Exception("Replicate credentials not found. Please run replicate_setup() first.")
|
152
|
+
|
153
|
+
api_token = credentials['api_token']
|
154
|
+
|
155
|
+
headers = {
|
156
|
+
'Authorization': f'Token {api_token}',
|
157
|
+
'Content-Type': 'application/json'
|
158
|
+
}
|
159
|
+
|
160
|
+
url = f"{self.api_base}{endpoint}"
|
161
|
+
|
162
|
+
async with httpx.AsyncClient(timeout=30.0) as client:
|
163
|
+
if method.upper() == 'GET':
|
164
|
+
response = await client.get(url, headers=headers)
|
165
|
+
elif method.upper() == 'POST':
|
166
|
+
response = await client.post(url, headers=headers, json=data)
|
167
|
+
elif method.upper() == 'DELETE':
|
168
|
+
response = await client.delete(url, headers=headers)
|
169
|
+
else:
|
170
|
+
raise Exception(f"Unsupported HTTP method: {method}")
|
171
|
+
|
172
|
+
response.raise_for_status()
|
173
|
+
return response.json() if response.content else {}
|
174
|
+
|
175
|
+
# Public tools
|
176
|
+
@tool(description="Set up Replicate API token securely. Get your token from Replicate account settings.", scope="owner")
|
177
|
+
async def replicate_setup(self, api_token: str) -> str:
|
178
|
+
"""Set up Replicate API credentials for secure access"""
|
179
|
+
user_id = await self._get_authenticated_user_id()
|
180
|
+
if not user_id:
|
181
|
+
return "❌ Authentication required"
|
182
|
+
|
183
|
+
if not api_token or not api_token.strip():
|
184
|
+
return "❌ API token is required. Get one from your Replicate account settings."
|
185
|
+
|
186
|
+
try:
|
187
|
+
# Test the API token by making a simple request
|
188
|
+
test_response = await self._make_replicate_request('GET', '/models', user_id=user_id)
|
189
|
+
|
190
|
+
# If test succeeds, save credentials
|
191
|
+
success = await self._save_replicate_credentials(user_id, api_token.strip())
|
192
|
+
|
193
|
+
if success:
|
194
|
+
return f"✅ Replicate credentials saved successfully!\n🔑 API token configured\n📊 Connection to Replicate API verified"
|
195
|
+
else:
|
196
|
+
return "❌ Failed to save credentials"
|
197
|
+
|
198
|
+
except httpx.HTTPStatusError as e:
|
199
|
+
if e.response.status_code == 401:
|
200
|
+
return "❌ Invalid API token. Please check your Replicate API token."
|
201
|
+
elif e.response.status_code == 403:
|
202
|
+
return "❌ API token doesn't have required permissions."
|
203
|
+
else:
|
204
|
+
return f"❌ API test failed: HTTP {e.response.status_code}"
|
205
|
+
except Exception as e:
|
206
|
+
return f"❌ Setup failed: {str(e)}"
|
207
|
+
|
208
|
+
@tool(description="List models from a specific owner or popular models")
|
209
|
+
async def replicate_list_models(self, owner: str = None) -> str:
|
210
|
+
"""List available models on Replicate"""
|
211
|
+
user_id = await self._get_authenticated_user_id()
|
212
|
+
if not user_id:
|
213
|
+
return "❌ Authentication required"
|
214
|
+
|
215
|
+
try:
|
216
|
+
# Build endpoint
|
217
|
+
if owner:
|
218
|
+
endpoint = f"/models?owner={owner}"
|
219
|
+
else:
|
220
|
+
endpoint = "/models"
|
221
|
+
|
222
|
+
response = await self._make_replicate_request('GET', endpoint, user_id=user_id)
|
223
|
+
|
224
|
+
models = response.get('results', [])
|
225
|
+
|
226
|
+
if not models:
|
227
|
+
return f"📭 No models found{f' for owner {owner}' if owner else ''}"
|
228
|
+
|
229
|
+
result = [f"🤖 Available Models{f' from {owner}' if owner else ''}:\n"]
|
230
|
+
|
231
|
+
for model in models[:10]: # Limit to first 10 models
|
232
|
+
name = model.get('name', 'Unknown')
|
233
|
+
full_name = f"{model.get('owner', 'unknown')}/{name}"
|
234
|
+
description = model.get('description', 'No description')
|
235
|
+
visibility = model.get('visibility', 'unknown')
|
236
|
+
|
237
|
+
# Visibility icon
|
238
|
+
vis_icon = "🔒" if visibility == 'private' else "🌍"
|
239
|
+
|
240
|
+
result.append(f"{vis_icon} **{full_name}**")
|
241
|
+
result.append(f" 📝 {description[:100]}{'...' if len(description) > 100 else ''}")
|
242
|
+
result.append("") # Empty line for spacing
|
243
|
+
|
244
|
+
if len(models) > 10:
|
245
|
+
result.append(f"... and {len(models) - 10} more models")
|
246
|
+
result.append("")
|
247
|
+
|
248
|
+
result.append("💡 Use replicate_run_prediction(model, input_data) to run a model")
|
249
|
+
|
250
|
+
return "\n".join(result)
|
251
|
+
|
252
|
+
except httpx.HTTPStatusError as e:
|
253
|
+
if e.response.status_code == 401:
|
254
|
+
return "❌ Authentication failed. Please run replicate_setup() again."
|
255
|
+
else:
|
256
|
+
return f"❌ Failed to list models: HTTP {e.response.status_code}"
|
257
|
+
except Exception as e:
|
258
|
+
return f"❌ Error listing models: {str(e)}"
|
259
|
+
|
260
|
+
@tool(description="Get detailed information about a specific model")
|
261
|
+
async def replicate_get_model_info(self, model: str) -> str:
|
262
|
+
"""Get detailed information about a model"""
|
263
|
+
user_id = await self._get_authenticated_user_id()
|
264
|
+
if not user_id:
|
265
|
+
return "❌ Authentication required"
|
266
|
+
|
267
|
+
if not model or not model.strip():
|
268
|
+
return "❌ Model name is required (format: owner/model-name)"
|
269
|
+
|
270
|
+
try:
|
271
|
+
# Clean model name
|
272
|
+
model_name = model.strip()
|
273
|
+
if '/' not in model_name:
|
274
|
+
return "❌ Model name must be in format: owner/model-name"
|
275
|
+
|
276
|
+
response = await self._make_replicate_request('GET', f'/models/{model_name}', user_id=user_id)
|
277
|
+
|
278
|
+
name = response.get('name', 'Unknown')
|
279
|
+
owner = response.get('owner', 'unknown')
|
280
|
+
description = response.get('description', 'No description')
|
281
|
+
visibility = response.get('visibility', 'unknown')
|
282
|
+
github_url = response.get('github_url', '')
|
283
|
+
paper_url = response.get('paper_url', '')
|
284
|
+
license_url = response.get('license_url', '')
|
285
|
+
|
286
|
+
# Get latest version info
|
287
|
+
latest_version = response.get('latest_version', {})
|
288
|
+
version_id = latest_version.get('id', 'unknown')
|
289
|
+
created_at = latest_version.get('created_at', 'unknown')
|
290
|
+
|
291
|
+
# Schema info
|
292
|
+
schema = latest_version.get('openapi_schema', {})
|
293
|
+
input_schema = schema.get('components', {}).get('schemas', {}).get('Input', {}).get('properties', {})
|
294
|
+
|
295
|
+
vis_icon = "🔒" if visibility == 'private' else "🌍"
|
296
|
+
|
297
|
+
result = [
|
298
|
+
f"🤖 Model Information: {owner}/{name}",
|
299
|
+
f"{vis_icon} Visibility: {visibility}",
|
300
|
+
f"📝 Description: {description}",
|
301
|
+
f"🆔 Latest Version: {version_id}",
|
302
|
+
f"📅 Created: {created_at}",
|
303
|
+
""
|
304
|
+
]
|
305
|
+
|
306
|
+
if github_url:
|
307
|
+
result.append(f"🔗 GitHub: {github_url}")
|
308
|
+
if paper_url:
|
309
|
+
result.append(f"📄 Paper: {paper_url}")
|
310
|
+
if license_url:
|
311
|
+
result.append(f"📜 License: {license_url}")
|
312
|
+
|
313
|
+
if input_schema:
|
314
|
+
result.append("\n📋 Input Parameters:")
|
315
|
+
for param_name, param_info in input_schema.items():
|
316
|
+
param_type = param_info.get('type', 'unknown')
|
317
|
+
param_desc = param_info.get('description', 'No description')
|
318
|
+
required = param_name in schema.get('components', {}).get('schemas', {}).get('Input', {}).get('required', [])
|
319
|
+
req_icon = "⚠️" if required else "📝"
|
320
|
+
result.append(f" {req_icon} {param_name} ({param_type}): {param_desc}")
|
321
|
+
|
322
|
+
result.append("\n💡 Use replicate_run_prediction() to run this model")
|
323
|
+
|
324
|
+
return "\n".join(result)
|
325
|
+
|
326
|
+
except httpx.HTTPStatusError as e:
|
327
|
+
if e.response.status_code == 401:
|
328
|
+
return "❌ Authentication failed. Please run replicate_setup() again."
|
329
|
+
elif e.response.status_code == 404:
|
330
|
+
return f"❌ Model '{model}' not found"
|
331
|
+
else:
|
332
|
+
return f"❌ Failed to get model info: HTTP {e.response.status_code}"
|
333
|
+
except Exception as e:
|
334
|
+
return f"❌ Error getting model info: {str(e)}"
|
335
|
+
|
336
|
+
@tool(description="Run a prediction with a Replicate model")
|
337
|
+
async def replicate_run_prediction(self, model: str, input_data: Dict[str, Any]) -> str:
|
338
|
+
"""Run a prediction with a model"""
|
339
|
+
user_id = await self._get_authenticated_user_id()
|
340
|
+
if not user_id:
|
341
|
+
return "❌ Authentication required"
|
342
|
+
|
343
|
+
if not model or not model.strip():
|
344
|
+
return "❌ Model name is required (format: owner/model-name)"
|
345
|
+
|
346
|
+
if not input_data:
|
347
|
+
return "❌ Input data is required"
|
348
|
+
|
349
|
+
try:
|
350
|
+
# Clean model name
|
351
|
+
model_name = model.strip()
|
352
|
+
if '/' not in model_name:
|
353
|
+
return "❌ Model name must be in format: owner/model-name"
|
354
|
+
|
355
|
+
# Prepare prediction data
|
356
|
+
prediction_data = {
|
357
|
+
"version": f"{model_name}:latest",
|
358
|
+
"input": input_data
|
359
|
+
}
|
360
|
+
|
361
|
+
# Create prediction
|
362
|
+
response = await self._make_replicate_request(
|
363
|
+
'POST',
|
364
|
+
'/predictions',
|
365
|
+
prediction_data,
|
366
|
+
user_id
|
367
|
+
)
|
368
|
+
|
369
|
+
prediction_id = response.get('id', 'unknown')
|
370
|
+
status = response.get('status', 'starting')
|
371
|
+
|
372
|
+
result = [
|
373
|
+
f"🚀 Prediction started successfully!",
|
374
|
+
f"🆔 Prediction ID: {prediction_id}",
|
375
|
+
f"🤖 Model: {model_name}",
|
376
|
+
f"📊 Status: {status}"
|
377
|
+
]
|
378
|
+
|
379
|
+
# If prediction completed immediately, show results
|
380
|
+
if status == 'succeeded':
|
381
|
+
output = response.get('output')
|
382
|
+
if output:
|
383
|
+
result.append(f"✅ Output: {str(output)[:200]}{'...' if len(str(output)) > 200 else ''}")
|
384
|
+
elif status == 'failed':
|
385
|
+
error = response.get('error', 'Unknown error')
|
386
|
+
result.append(f"❌ Error: {error}")
|
387
|
+
else:
|
388
|
+
result.append("⏳ Use replicate_get_prediction() to check status and get results")
|
389
|
+
|
390
|
+
return "\n".join(result)
|
391
|
+
|
392
|
+
except httpx.HTTPStatusError as e:
|
393
|
+
if e.response.status_code == 401:
|
394
|
+
return "❌ Authentication failed. Please run replicate_setup() again."
|
395
|
+
elif e.response.status_code == 404:
|
396
|
+
return f"❌ Model '{model}' not found"
|
397
|
+
elif e.response.status_code == 422:
|
398
|
+
return "❌ Invalid input data. Check model requirements with replicate_get_model_info()"
|
399
|
+
else:
|
400
|
+
return f"❌ Prediction failed: HTTP {e.response.status_code}"
|
401
|
+
except Exception as e:
|
402
|
+
return f"❌ Error running prediction: {str(e)}"
|
403
|
+
|
404
|
+
@tool(description="Get prediction status and results")
|
405
|
+
async def replicate_get_prediction(self, prediction_id: str) -> str:
|
406
|
+
"""Get prediction status and results"""
|
407
|
+
user_id = await self._get_authenticated_user_id()
|
408
|
+
if not user_id:
|
409
|
+
return "❌ Authentication required"
|
410
|
+
|
411
|
+
if not prediction_id or not prediction_id.strip():
|
412
|
+
return "❌ Prediction ID is required"
|
413
|
+
|
414
|
+
try:
|
415
|
+
response = await self._make_replicate_request(
|
416
|
+
'GET',
|
417
|
+
f'/predictions/{prediction_id.strip()}',
|
418
|
+
user_id=user_id
|
419
|
+
)
|
420
|
+
|
421
|
+
status = response.get('status', 'unknown')
|
422
|
+
model = response.get('model', 'unknown')
|
423
|
+
created_at = response.get('created_at', 'unknown')
|
424
|
+
started_at = response.get('started_at', '')
|
425
|
+
completed_at = response.get('completed_at', '')
|
426
|
+
|
427
|
+
# Status icons
|
428
|
+
status_icons = {
|
429
|
+
'starting': '🔄',
|
430
|
+
'processing': '⚙️',
|
431
|
+
'succeeded': '✅',
|
432
|
+
'failed': '❌',
|
433
|
+
'canceled': '🚫'
|
434
|
+
}
|
435
|
+
|
436
|
+
status_icon = status_icons.get(status, '❓')
|
437
|
+
|
438
|
+
result = [
|
439
|
+
f"📊 Prediction Status Report",
|
440
|
+
f"🆔 Prediction ID: {prediction_id}",
|
441
|
+
f"🤖 Model: {model}",
|
442
|
+
f"{status_icon} Status: {status}",
|
443
|
+
f"🕐 Created: {created_at}"
|
444
|
+
]
|
445
|
+
|
446
|
+
if started_at:
|
447
|
+
result.append(f"▶️ Started: {started_at}")
|
448
|
+
if completed_at:
|
449
|
+
result.append(f"🏁 Completed: {completed_at}")
|
450
|
+
|
451
|
+
# Add results if succeeded
|
452
|
+
if status == 'succeeded':
|
453
|
+
output = response.get('output')
|
454
|
+
if output:
|
455
|
+
output_str = str(output)
|
456
|
+
if len(output_str) > 500:
|
457
|
+
result.append(f"📋 Output: {output_str[:500]}...")
|
458
|
+
result.append("💡 Output truncated. Full results available via API.")
|
459
|
+
else:
|
460
|
+
result.append(f"📋 Output: {output_str}")
|
461
|
+
|
462
|
+
# Add error details if failed
|
463
|
+
elif status == 'failed':
|
464
|
+
error = response.get('error', 'Unknown error')
|
465
|
+
logs = response.get('logs', '')
|
466
|
+
result.append(f"❌ Error: {error}")
|
467
|
+
if logs:
|
468
|
+
result.append(f"📝 Logs: {logs[-200:]}...") # Show last 200 chars of logs
|
469
|
+
|
470
|
+
return "\n".join(result)
|
471
|
+
|
472
|
+
except httpx.HTTPStatusError as e:
|
473
|
+
if e.response.status_code == 401:
|
474
|
+
return "❌ Authentication failed. Please run replicate_setup() again."
|
475
|
+
elif e.response.status_code == 404:
|
476
|
+
return f"❌ Prediction '{prediction_id}' not found"
|
477
|
+
else:
|
478
|
+
return f"❌ Status check failed: HTTP {e.response.status_code}"
|
479
|
+
except Exception as e:
|
480
|
+
return f"❌ Error checking prediction: {str(e)}"
|
481
|
+
|
482
|
+
@tool(description="Cancel a running prediction")
|
483
|
+
async def replicate_cancel_prediction(self, prediction_id: str) -> str:
|
484
|
+
"""Cancel a running prediction"""
|
485
|
+
user_id = await self._get_authenticated_user_id()
|
486
|
+
if not user_id:
|
487
|
+
return "❌ Authentication required"
|
488
|
+
|
489
|
+
if not prediction_id or not prediction_id.strip():
|
490
|
+
return "❌ Prediction ID is required"
|
491
|
+
|
492
|
+
try:
|
493
|
+
response = await self._make_replicate_request(
|
494
|
+
'POST',
|
495
|
+
f'/predictions/{prediction_id.strip()}/cancel',
|
496
|
+
user_id=user_id
|
497
|
+
)
|
498
|
+
|
499
|
+
status = response.get('status', 'unknown')
|
500
|
+
|
501
|
+
if status == 'canceled':
|
502
|
+
return f"✅ Prediction {prediction_id} canceled successfully"
|
503
|
+
else:
|
504
|
+
return f"⚠️ Prediction status: {status} (may not be cancelable)"
|
505
|
+
|
506
|
+
except httpx.HTTPStatusError as e:
|
507
|
+
if e.response.status_code == 401:
|
508
|
+
return "❌ Authentication failed. Please run replicate_setup() again."
|
509
|
+
elif e.response.status_code == 404:
|
510
|
+
return f"❌ Prediction '{prediction_id}' not found"
|
511
|
+
elif e.response.status_code == 422:
|
512
|
+
return f"❌ Cannot cancel prediction (may be completed or already canceled)"
|
513
|
+
else:
|
514
|
+
return f"❌ Cancel failed: HTTP {e.response.status_code}"
|
515
|
+
except Exception as e:
|
516
|
+
return f"❌ Error canceling prediction: {str(e)}"
|
517
|
+
|