alita-sdk 0.3.528__py3-none-any.whl → 0.3.554__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.

Potentially problematic release.


This version of alita-sdk might be problematic. Click here for more details.

Files changed (46) hide show
  1. alita_sdk/community/__init__.py +8 -4
  2. alita_sdk/configurations/__init__.py +1 -0
  3. alita_sdk/configurations/openapi.py +111 -0
  4. alita_sdk/runtime/clients/client.py +185 -10
  5. alita_sdk/runtime/langchain/langraph_agent.py +2 -2
  6. alita_sdk/runtime/langchain/utils.py +46 -0
  7. alita_sdk/runtime/skills/__init__.py +91 -0
  8. alita_sdk/runtime/skills/callbacks.py +498 -0
  9. alita_sdk/runtime/skills/discovery.py +540 -0
  10. alita_sdk/runtime/skills/executor.py +610 -0
  11. alita_sdk/runtime/skills/input_builder.py +371 -0
  12. alita_sdk/runtime/skills/models.py +330 -0
  13. alita_sdk/runtime/skills/registry.py +355 -0
  14. alita_sdk/runtime/skills/skill_runner.py +330 -0
  15. alita_sdk/runtime/toolkits/__init__.py +2 -0
  16. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  17. alita_sdk/runtime/toolkits/tools.py +76 -9
  18. alita_sdk/runtime/tools/__init__.py +3 -1
  19. alita_sdk/runtime/tools/artifact.py +70 -21
  20. alita_sdk/runtime/tools/image_generation.py +50 -44
  21. alita_sdk/runtime/tools/llm.py +363 -44
  22. alita_sdk/runtime/tools/loop.py +3 -1
  23. alita_sdk/runtime/tools/loop_output.py +3 -1
  24. alita_sdk/runtime/tools/skill_router.py +776 -0
  25. alita_sdk/runtime/tools/tool.py +3 -1
  26. alita_sdk/runtime/tools/vectorstore.py +7 -2
  27. alita_sdk/runtime/tools/vectorstore_base.py +7 -2
  28. alita_sdk/runtime/utils/AlitaCallback.py +2 -1
  29. alita_sdk/runtime/utils/utils.py +34 -0
  30. alita_sdk/tools/__init__.py +41 -1
  31. alita_sdk/tools/ado/work_item/ado_wrapper.py +33 -2
  32. alita_sdk/tools/base_indexer_toolkit.py +36 -24
  33. alita_sdk/tools/confluence/api_wrapper.py +5 -6
  34. alita_sdk/tools/confluence/loader.py +4 -2
  35. alita_sdk/tools/openapi/__init__.py +280 -120
  36. alita_sdk/tools/openapi/api_wrapper.py +883 -0
  37. alita_sdk/tools/openapi/tool.py +20 -0
  38. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  39. alita_sdk/tools/servicenow/__init__.py +9 -9
  40. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  41. {alita_sdk-0.3.528.dist-info → alita_sdk-0.3.554.dist-info}/METADATA +2 -2
  42. {alita_sdk-0.3.528.dist-info → alita_sdk-0.3.554.dist-info}/RECORD +46 -33
  43. {alita_sdk-0.3.528.dist-info → alita_sdk-0.3.554.dist-info}/WHEEL +0 -0
  44. {alita_sdk-0.3.528.dist-info → alita_sdk-0.3.554.dist-info}/entry_points.txt +0 -0
  45. {alita_sdk-0.3.528.dist-info → alita_sdk-0.3.554.dist-info}/licenses/LICENSE +0 -0
  46. {alita_sdk-0.3.528.dist-info → alita_sdk-0.3.554.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,498 @@
1
+ """
2
+ Callback system for skill execution transparency.
3
+
4
+ This module provides a comprehensive callback system that allows real-time
5
+ monitoring of skill execution events, including tool usage, LLM calls,
6
+ and node transitions.
7
+ """
8
+
9
+ import json
10
+ import logging
11
+ import threading
12
+ import time
13
+ from abc import ABC, abstractmethod
14
+ from pathlib import Path
15
+ from typing import Any, Dict, List, Optional
16
+
17
+ from langchain_core.callbacks import BaseCallbackHandler
18
+ from langchain_core.messages import BaseMessage
19
+
20
+ from .models import SkillEvent, SkillEventType
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class SkillCallback(ABC):
26
+ """
27
+ Abstract base class for skill execution callbacks.
28
+ """
29
+
30
+ @abstractmethod
31
+ def on_skill_event(
32
+ self,
33
+ event_type: SkillEventType,
34
+ data: Dict[str, Any],
35
+ skill_name: str,
36
+ execution_id: str
37
+ ) -> None:
38
+ """
39
+ Handle a skill execution event.
40
+
41
+ Args:
42
+ event_type: Type of the event.
43
+ data: Event data dictionary.
44
+ skill_name: Name of the skill generating the event.
45
+ execution_id: Unique execution identifier.
46
+ """
47
+ pass
48
+
49
+
50
+ class CallbackManager:
51
+ """
52
+ Manager for multiple skill callbacks that forwards events to all registered callbacks.
53
+ """
54
+
55
+ def __init__(self, callbacks: Optional[List[SkillCallback]] = None):
56
+ """
57
+ Initialize callback manager.
58
+
59
+ Args:
60
+ callbacks: List of initial callbacks to register.
61
+ """
62
+ self.callbacks = callbacks or []
63
+ self._lock = threading.Lock()
64
+
65
+ def add_callback(self, callback: SkillCallback) -> None:
66
+ """
67
+ Add a callback to the manager.
68
+
69
+ Args:
70
+ callback: Callback to add.
71
+ """
72
+ with self._lock:
73
+ self.callbacks.append(callback)
74
+
75
+ def remove_callback(self, callback: SkillCallback) -> None:
76
+ """
77
+ Remove a callback from the manager.
78
+
79
+ Args:
80
+ callback: Callback to remove.
81
+ """
82
+ with self._lock:
83
+ if callback in self.callbacks:
84
+ self.callbacks.remove(callback)
85
+
86
+ def emit_event(
87
+ self,
88
+ event_type: SkillEventType,
89
+ data: Dict[str, Any],
90
+ skill_name: str,
91
+ execution_id: str
92
+ ) -> None:
93
+ """
94
+ Emit an event to all registered callbacks.
95
+
96
+ Args:
97
+ event_type: Type of the event.
98
+ data: Event data dictionary.
99
+ skill_name: Name of the skill generating the event.
100
+ execution_id: Unique execution identifier.
101
+ """
102
+ # Create event object
103
+ event = SkillEvent(
104
+ event_type=event_type,
105
+ data=data.copy(),
106
+ skill_name=skill_name,
107
+ execution_id=execution_id,
108
+ timestamp=time.time()
109
+ )
110
+
111
+ # Forward to all callbacks (thread-safe)
112
+ callbacks_snapshot = []
113
+ with self._lock:
114
+ callbacks_snapshot = self.callbacks.copy()
115
+
116
+ for callback in callbacks_snapshot:
117
+ try:
118
+ callback.on_skill_event(event_type, data, skill_name, execution_id)
119
+ except Exception as e:
120
+ # Don't let callback errors break skill execution
121
+ logger.warning(
122
+ f"Callback {callback.__class__.__name__} failed for event "
123
+ f"{event_type.value}: {e}"
124
+ )
125
+
126
+ def clear(self) -> None:
127
+ """Clear all registered callbacks."""
128
+ with self._lock:
129
+ self.callbacks.clear()
130
+
131
+ def __len__(self) -> int:
132
+ """Return number of registered callbacks."""
133
+ with self._lock:
134
+ return len(self.callbacks)
135
+
136
+
137
+ class LoggingCallback(SkillCallback):
138
+ """
139
+ Simple callback that logs all events.
140
+ """
141
+
142
+ def __init__(self, level: int = logging.INFO):
143
+ """
144
+ Initialize logging callback.
145
+
146
+ Args:
147
+ level: Logging level to use for events.
148
+ """
149
+ self.level = level
150
+ self.logger = logging.getLogger(f"{__name__}.LoggingCallback")
151
+
152
+ def on_skill_event(
153
+ self,
154
+ event_type: SkillEventType,
155
+ data: Dict[str, Any],
156
+ skill_name: str,
157
+ execution_id: str
158
+ ) -> None:
159
+ """Log the skill event."""
160
+ message = f"[{skill_name}:{execution_id[-8:]}] {event_type.value}"
161
+ if data:
162
+ message += f": {data}"
163
+
164
+ self.logger.log(self.level, message)
165
+
166
+
167
+ class ProgressCallback(SkillCallback):
168
+ """
169
+ Callback that tracks and displays execution progress.
170
+ """
171
+
172
+ def __init__(self):
173
+ """Initialize progress callback."""
174
+ self.start_times: Dict[str, float] = {}
175
+ self.node_counts: Dict[str, int] = {}
176
+
177
+ def on_skill_event(
178
+ self,
179
+ event_type: SkillEventType,
180
+ data: Dict[str, Any],
181
+ skill_name: str,
182
+ execution_id: str
183
+ ) -> None:
184
+ """Track progress events."""
185
+ if event_type == SkillEventType.SKILL_START:
186
+ self.start_times[execution_id] = time.time()
187
+ self.node_counts[execution_id] = 0
188
+ print(f"🚀 Starting skill: {skill_name}")
189
+
190
+ elif event_type == SkillEventType.SKILL_END:
191
+ if execution_id in self.start_times:
192
+ duration = time.time() - self.start_times[execution_id]
193
+ nodes = self.node_counts.get(execution_id, 0)
194
+ print(f"✅ Completed skill: {skill_name} ({duration:.1f}s, {nodes} nodes)")
195
+
196
+ elif event_type == SkillEventType.NODE_START:
197
+ node_name = data.get('node', 'unknown')
198
+ print(f" 🔄 Executing node: {node_name}")
199
+ self.node_counts[execution_id] = self.node_counts.get(execution_id, 0) + 1
200
+
201
+ elif event_type == SkillEventType.TOOL_START:
202
+ tool_name = data.get('tool', 'unknown')
203
+ print(f" 🔧 Using tool: {tool_name}")
204
+
205
+ elif event_type == SkillEventType.LLM_START:
206
+ model = data.get('model', 'unknown')
207
+ print(f" 🤖 Calling LLM: {model}")
208
+
209
+ elif event_type == SkillEventType.ERROR:
210
+ error = data.get('error', 'unknown error')
211
+ print(f"❌ Error in skill {skill_name}: {error}")
212
+
213
+
214
+ class FileCallback(SkillCallback):
215
+ """
216
+ Callback that writes events to a file for analysis or debugging.
217
+ """
218
+
219
+ def __init__(self, file_path: Path, json_format: bool = True):
220
+ """
221
+ Initialize file callback.
222
+
223
+ Args:
224
+ file_path: Path to write events to.
225
+ json_format: Whether to write events as JSON (True) or text (False).
226
+ """
227
+ self.file_path = file_path
228
+ self.json_format = json_format
229
+ self._lock = threading.Lock()
230
+
231
+ # Ensure directory exists
232
+ self.file_path.parent.mkdir(parents=True, exist_ok=True)
233
+
234
+ def on_skill_event(
235
+ self,
236
+ event_type: SkillEventType,
237
+ data: Dict[str, Any],
238
+ skill_name: str,
239
+ execution_id: str
240
+ ) -> None:
241
+ """Write event to file."""
242
+ event = SkillEvent(
243
+ event_type=event_type,
244
+ data=data,
245
+ skill_name=skill_name,
246
+ execution_id=execution_id,
247
+ timestamp=time.time()
248
+ )
249
+
250
+ with self._lock:
251
+ try:
252
+ with open(self.file_path, 'a', encoding='utf-8') as f:
253
+ if self.json_format:
254
+ f.write(json.dumps(event.to_dict()) + '\n')
255
+ else:
256
+ f.write(f"{event.timestamp} [{skill_name}:{execution_id}] "
257
+ f"{event_type.value}: {data}\n")
258
+ except Exception as e:
259
+ logger.warning(f"Failed to write event to file {self.file_path}: {e}")
260
+
261
+
262
+ class SkillLangChainCallback(BaseCallbackHandler):
263
+ """
264
+ LangChain callback handler that forwards events to the skill callback system.
265
+ """
266
+
267
+ def __init__(self, callback_manager: CallbackManager, skill_name: str, execution_id: str):
268
+ """
269
+ Initialize LangChain callback handler.
270
+
271
+ Args:
272
+ callback_manager: Callback manager to forward events to.
273
+ skill_name: Name of the skill being executed.
274
+ execution_id: Execution identifier.
275
+ """
276
+ super().__init__()
277
+ self.callback_manager = callback_manager
278
+ self.skill_name = skill_name
279
+ self.execution_id = execution_id
280
+
281
+ def on_tool_start(
282
+ self,
283
+ serialized: Dict[str, Any],
284
+ input_str: str,
285
+ **kwargs: Any
286
+ ) -> None:
287
+ """Handle tool start event."""
288
+ self.callback_manager.emit_event(
289
+ SkillEventType.TOOL_START,
290
+ {
291
+ "tool": serialized.get("name", "unknown"),
292
+ "input": input_str,
293
+ "serialized": serialized
294
+ },
295
+ self.skill_name,
296
+ self.execution_id
297
+ )
298
+
299
+ def on_tool_end(
300
+ self,
301
+ output: str,
302
+ **kwargs: Any
303
+ ) -> None:
304
+ """Handle tool end event."""
305
+ self.callback_manager.emit_event(
306
+ SkillEventType.TOOL_END,
307
+ {
308
+ "output": output
309
+ },
310
+ self.skill_name,
311
+ self.execution_id
312
+ )
313
+
314
+ def on_tool_error(
315
+ self,
316
+ error: Exception,
317
+ **kwargs: Any
318
+ ) -> None:
319
+ """Handle tool error event."""
320
+ self.callback_manager.emit_event(
321
+ SkillEventType.ERROR,
322
+ {
323
+ "error": str(error),
324
+ "error_type": "tool_error"
325
+ },
326
+ self.skill_name,
327
+ self.execution_id
328
+ )
329
+
330
+ def on_llm_start(
331
+ self,
332
+ serialized: Dict[str, Any],
333
+ prompts: List[str],
334
+ **kwargs: Any
335
+ ) -> None:
336
+ """Handle LLM start event."""
337
+ self.callback_manager.emit_event(
338
+ SkillEventType.LLM_START,
339
+ {
340
+ "model": serialized.get("model_name", "unknown"),
341
+ "prompts": prompts,
342
+ "serialized": serialized
343
+ },
344
+ self.skill_name,
345
+ self.execution_id
346
+ )
347
+
348
+ def on_llm_end(
349
+ self,
350
+ response: Any,
351
+ **kwargs: Any
352
+ ) -> None:
353
+ """Handle LLM end event."""
354
+ self.callback_manager.emit_event(
355
+ SkillEventType.LLM_END,
356
+ {
357
+ "response": str(response)
358
+ },
359
+ self.skill_name,
360
+ self.execution_id
361
+ )
362
+
363
+ def on_llm_error(
364
+ self,
365
+ error: Exception,
366
+ **kwargs: Any
367
+ ) -> None:
368
+ """Handle LLM error event."""
369
+ self.callback_manager.emit_event(
370
+ SkillEventType.ERROR,
371
+ {
372
+ "error": str(error),
373
+ "error_type": "llm_error"
374
+ },
375
+ self.skill_name,
376
+ self.execution_id
377
+ )
378
+
379
+ def on_chain_start(
380
+ self,
381
+ serialized: Dict[str, Any],
382
+ inputs: Dict[str, Any],
383
+ **kwargs: Any
384
+ ) -> None:
385
+ """Handle chain start event."""
386
+ self.callback_manager.emit_event(
387
+ SkillEventType.CUSTOM_EVENT,
388
+ {
389
+ "event": "chain_start",
390
+ "chain": serialized.get("name", "unknown"),
391
+ "inputs": inputs
392
+ },
393
+ self.skill_name,
394
+ self.execution_id
395
+ )
396
+
397
+ def on_chain_end(
398
+ self,
399
+ outputs: Dict[str, Any],
400
+ **kwargs: Any
401
+ ) -> None:
402
+ """Handle chain end event."""
403
+ self.callback_manager.emit_event(
404
+ SkillEventType.CUSTOM_EVENT,
405
+ {
406
+ "event": "chain_end",
407
+ "outputs": outputs
408
+ },
409
+ self.skill_name,
410
+ self.execution_id
411
+ )
412
+
413
+
414
+ class CallbackEmitter:
415
+ """
416
+ Helper class for emitting callbacks from subprocess execution.
417
+
418
+ This class writes events to a pipe file that can be monitored by the parent process.
419
+ """
420
+
421
+ def __init__(self, pipe_path: Optional[str], execution_id: str):
422
+ """
423
+ Initialize callback emitter.
424
+
425
+ Args:
426
+ pipe_path: Path to callback pipe file.
427
+ execution_id: Execution identifier.
428
+ """
429
+ self.pipe_path = Path(pipe_path) if pipe_path else None
430
+ self.execution_id = execution_id
431
+ self._lock = threading.Lock()
432
+
433
+ def emit(
434
+ self,
435
+ event_type: SkillEventType,
436
+ data: Dict[str, Any],
437
+ skill_name: str
438
+ ) -> None:
439
+ """
440
+ Emit an event to the pipe file.
441
+
442
+ Args:
443
+ event_type: Type of event.
444
+ data: Event data.
445
+ skill_name: Name of skill generating event.
446
+ """
447
+ if not self.pipe_path:
448
+ return
449
+
450
+ event = SkillEvent(
451
+ event_type=event_type,
452
+ data=data,
453
+ skill_name=skill_name,
454
+ execution_id=self.execution_id,
455
+ timestamp=time.time()
456
+ )
457
+
458
+ with self._lock:
459
+ try:
460
+ with open(self.pipe_path, 'a', encoding='utf-8') as f:
461
+ f.write(json.dumps(event.to_dict()) + '\n')
462
+ f.flush()
463
+ except Exception as e:
464
+ logger.warning(f"Failed to emit callback event: {e}")
465
+
466
+
467
+ def create_default_callbacks() -> List[SkillCallback]:
468
+ """
469
+ Create a default set of callbacks for skill execution.
470
+
471
+ Returns:
472
+ List of default callback instances.
473
+ """
474
+ return [
475
+ LoggingCallback(level=logging.INFO),
476
+ ProgressCallback()
477
+ ]
478
+
479
+
480
+ def create_debug_callbacks(log_file: Optional[Path] = None) -> List[SkillCallback]:
481
+ """
482
+ Create callbacks suitable for debugging skill execution.
483
+
484
+ Args:
485
+ log_file: Optional path to write detailed event log.
486
+
487
+ Returns:
488
+ List of debug callback instances.
489
+ """
490
+ callbacks = [
491
+ LoggingCallback(level=logging.DEBUG),
492
+ ProgressCallback()
493
+ ]
494
+
495
+ if log_file:
496
+ callbacks.append(FileCallback(log_file, json_format=True))
497
+
498
+ return callbacks