alita-sdk 0.3.206__py3-none-any.whl → 0.3.208__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.
Files changed (26) hide show
  1. alita_sdk/runtime/clients/client.py +369 -6
  2. alita_sdk/runtime/langchain/langraph_agent.py +6 -1
  3. alita_sdk/runtime/langchain/store_manager.py +4 -4
  4. alita_sdk/runtime/toolkits/tools.py +11 -20
  5. alita_sdk/runtime/utils/streamlit.py +472 -192
  6. alita_sdk/runtime/utils/toolkit_runtime.py +147 -0
  7. alita_sdk/runtime/utils/toolkit_utils.py +157 -0
  8. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +82 -11
  9. alita_sdk/tools/ado/wiki/ado_wrapper.py +62 -2
  10. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +2 -1
  11. alita_sdk/tools/memory/__init__.py +54 -10
  12. alita_sdk/tools/sharepoint/api_wrapper.py +13 -4
  13. {alita_sdk-0.3.206.dist-info → alita_sdk-0.3.208.dist-info}/METADATA +1 -1
  14. {alita_sdk-0.3.206.dist-info → alita_sdk-0.3.208.dist-info}/RECORD +17 -24
  15. alita_sdk/community/analysis/__init__.py +0 -0
  16. alita_sdk/community/analysis/ado_analyse/__init__.py +0 -103
  17. alita_sdk/community/analysis/ado_analyse/api_wrapper.py +0 -261
  18. alita_sdk/community/analysis/github_analyse/__init__.py +0 -98
  19. alita_sdk/community/analysis/github_analyse/api_wrapper.py +0 -166
  20. alita_sdk/community/analysis/gitlab_analyse/__init__.py +0 -110
  21. alita_sdk/community/analysis/gitlab_analyse/api_wrapper.py +0 -172
  22. alita_sdk/community/analysis/jira_analyse/__init__.py +0 -141
  23. alita_sdk/community/analysis/jira_analyse/api_wrapper.py +0 -252
  24. {alita_sdk-0.3.206.dist-info → alita_sdk-0.3.208.dist-info}/WHEEL +0 -0
  25. {alita_sdk-0.3.206.dist-info → alita_sdk-0.3.208.dist-info}/licenses/LICENSE +0 -0
  26. {alita_sdk-0.3.206.dist-info → alita_sdk-0.3.208.dist-info}/top_level.txt +0 -0
@@ -473,15 +473,15 @@ class AlitaClient:
473
473
  logger.warning(f"Error: Could not determine user ID for MCP tool: {e}")
474
474
  return None
475
475
 
476
- def predict_agent(self, client: Any, instructions: str = "You are a helpful assistant.",
476
+ def predict_agent(self, llm: ChatOpenAI, instructions: str = "You are a helpful assistant.",
477
477
  tools: Optional[list] = None, chat_history: Optional[List[Any]] = None,
478
478
  memory=None, runtime='langchain', variables: Optional[list] = None,
479
479
  store: Optional[BaseStore] = None):
480
480
  """
481
481
  Create a predict-type agent with minimal configuration.
482
-
482
+
483
483
  Args:
484
- client: The LLM client to use
484
+ llm: The LLM to use
485
485
  instructions: System instructions for the agent
486
486
  tools: Optional list of tools to provide to the agent
487
487
  chat_history: Optional chat history
@@ -489,7 +489,7 @@ class AlitaClient:
489
489
  runtime: Runtime type (default: 'langchain')
490
490
  variables: Optional list of variables for the agent
491
491
  store: Optional store for memory
492
-
492
+
493
493
  Returns:
494
494
  Runnable agent ready for execution
495
495
  """
@@ -499,7 +499,7 @@ class AlitaClient:
499
499
  chat_history = []
500
500
  if variables is None:
501
501
  variables = []
502
-
502
+
503
503
  # Create a minimal data structure for predict agent
504
504
  # All LLM settings are taken from the passed client instance
505
505
  agent_data = {
@@ -507,6 +507,369 @@ class AlitaClient:
507
507
  'tools': tools, # Tools are handled separately in predict agents
508
508
  'variables': variables
509
509
  }
510
- return LangChainAssistant(self, agent_data, client,
510
+ return LangChainAssistant(self, agent_data, llm,
511
511
  chat_history, "predict", memory=memory, store=store).runnable()
512
+
513
+ def test_toolkit_tool(self, toolkit_config: dict, tool_name: str, tool_params: dict = None,
514
+ runtime_config: dict = None, llm_model: str = None,
515
+ llm_config: dict = None) -> dict:
516
+ """
517
+ Test a single tool from a toolkit with given parameters and runtime callbacks.
518
+
519
+ This method initializes a toolkit, calls a specific tool, and supports runtime
520
+ callbacks for event dispatching, enabling tools to send custom events back to
521
+ the platform during execution.
522
+
523
+ Args:
524
+ toolkit_config: Configuration dictionary for the toolkit containing:
525
+ - toolkit_name: Name of the toolkit (e.g., 'github', 'jira')
526
+ - settings: Dictionary containing toolkit-specific settings
527
+ tool_name: Name of the specific tool to call
528
+ tool_params: Parameters to pass to the tool (default: empty dict)
529
+ runtime_config: Runtime configuration with callbacks for events, containing:
530
+ - callbacks: List of callback handlers for event processing
531
+ - configurable: Additional configuration parameters
532
+ - tags: Tags for the execution
533
+ llm_model: Name of the LLM model to use (default: 'gpt-4o-mini')
534
+ llm_config: Configuration for the LLM containing:
535
+ - max_tokens: Maximum tokens for response (default: 1000)
536
+ - temperature: Temperature for response generation (default: 0.1)
537
+ - top_p: Top-p value for response generation (default: 1.0)
538
+
539
+ Returns:
540
+ Dictionary containing:
541
+ - success: Boolean indicating if the operation was successful
542
+ - result: The actual result from the tool (if successful)
543
+ - error: Error message (if unsuccessful)
544
+ - tool_name: Name of the executed tool
545
+ - toolkit_config: Original toolkit configuration
546
+ - events_dispatched: List of custom events dispatched during execution
547
+ - llm_model: LLM model used for the test
548
+ - execution_time_seconds: Time taken to execute the tool in seconds
549
+
550
+ Example:
551
+ >>> from langchain_core.callbacks import BaseCallbackHandler
552
+ >>>
553
+ >>> class TestCallback(BaseCallbackHandler):
554
+ ... def __init__(self):
555
+ ... self.events = []
556
+ ... def on_custom_event(self, name, data, **kwargs):
557
+ ... self.events.append({'name': name, 'data': data})
558
+ >>>
559
+ >>> callback = TestCallback()
560
+ >>> runtime_config = {'callbacks': [callback]}
561
+ >>>
562
+ >>> config = {
563
+ ... 'toolkit_name': 'github',
564
+ ... 'settings': {'github_token': 'your_token'}
565
+ ... }
566
+ >>> result = client.test_toolkit_tool(
567
+ ... config, 'get_repository_info',
568
+ ... {'repo_name': 'alita'}, runtime_config,
569
+ ... llm_model='gpt-4o-mini',
570
+ ... llm_config={'temperature': 0.1}
571
+ ... )
572
+ """
573
+ if tool_params is None:
574
+ tool_params = {}
575
+ if llm_model is None:
576
+ llm_model = 'gpt-4o-mini'
577
+ if llm_config is None:
578
+ llm_config = {
579
+ 'max_tokens': 1024,
580
+ 'temperature': 0.1,
581
+ 'top_p': 1.0
582
+ }
583
+
584
+ try:
585
+ from ..utils.toolkit_utils import instantiate_toolkit_with_client
586
+ from langchain_core.runnables import RunnableConfig
587
+ import logging
588
+ import time
589
+
590
+ logger = logging.getLogger(__name__)
591
+ logger.info(f"Testing tool '{tool_name}' from toolkit '{toolkit_config.get('toolkit_name')}' with LLM '{llm_model}'")
592
+
593
+ # Create RunnableConfig for callback support
594
+ config = None
595
+ callbacks = []
596
+ events_dispatched = []
597
+
598
+ if runtime_config:
599
+ callbacks = runtime_config.get('callbacks', [])
600
+ if callbacks:
601
+ config = RunnableConfig(
602
+ callbacks=callbacks,
603
+ configurable=runtime_config.get('configurable', {}),
604
+ tags=runtime_config.get('tags', [])
605
+ )
606
+
607
+ # Create LLM instance using the client's get_llm method
608
+ try:
609
+ llm = self.get_llm(llm_model, llm_config)
610
+ logger.info(f"Created LLM instance: {llm_model} with config: {llm_config}")
611
+ except Exception as llm_error:
612
+ logger.error(f"Failed to create LLM instance: {str(llm_error)}")
613
+ return {
614
+ "success": False,
615
+ "error": f"Failed to create LLM instance '{llm_model}': {str(llm_error)}",
616
+ "tool_name": tool_name,
617
+ "toolkit_config": toolkit_config,
618
+ "llm_model": llm_model,
619
+ "events_dispatched": events_dispatched,
620
+ "execution_time_seconds": 0.0
621
+ }
622
+
623
+ # Instantiate the toolkit with client and LLM support
624
+ tools = instantiate_toolkit_with_client(toolkit_config, llm, self)
625
+
626
+ if not tools:
627
+ return {
628
+ "success": False,
629
+ "error": f"Failed to instantiate toolkit '{toolkit_config.get('toolkit_name')}' or no tools found",
630
+ "tool_name": tool_name,
631
+ "toolkit_config": toolkit_config,
632
+ "llm_model": llm_model,
633
+ "events_dispatched": events_dispatched,
634
+ "execution_time_seconds": 0.0
635
+ }
636
+
637
+ # Find the specific tool with smart name matching
638
+ target_tool = None
639
+ toolkit_name = toolkit_config.get('toolkit_name', '').lower()
640
+
641
+ # Helper function to extract base tool name from full name
642
+ def extract_base_tool_name(full_name: str) -> str:
643
+ """Extract base tool name from toolkit___toolname format."""
644
+ if '___' in full_name:
645
+ return full_name.split('___', 1)[1]
646
+ return full_name
647
+
648
+ # Helper function to create full tool name
649
+ def create_full_tool_name(base_name: str, toolkit_name: str) -> str:
650
+ """Create full tool name in toolkit___toolname format."""
651
+ return f"{toolkit_name}___{base_name}"
652
+
653
+ # Normalize tool_name to handle both formats
654
+ # If user provides toolkit___toolname, extract just the tool name
655
+ # If user provides just toolname, keep as is
656
+ if '___' in tool_name:
657
+ normalized_tool_name = extract_base_tool_name(tool_name)
658
+ logger.info(f"Extracted base tool name '{normalized_tool_name}' from full name '{tool_name}'")
659
+ else:
660
+ normalized_tool_name = tool_name
661
+
662
+ # Try multiple matching strategies
663
+ for tool in tools:
664
+ tool_name_attr = None
665
+ if hasattr(tool, 'name'):
666
+ tool_name_attr = tool.name
667
+ elif hasattr(tool, 'func') and hasattr(tool.func, '__name__'):
668
+ tool_name_attr = tool.func.__name__
669
+
670
+ if tool_name_attr:
671
+ # Strategy 1: Exact match with provided name (handles both formats)
672
+ if tool_name_attr == tool_name:
673
+ target_tool = tool
674
+ logger.info(f"Found tool using exact match: '{tool_name_attr}'")
675
+ break
676
+
677
+ # Strategy 2: Match normalized name with toolkit prefix
678
+ expected_full_name = create_full_tool_name(normalized_tool_name, toolkit_name)
679
+ if tool_name_attr == expected_full_name:
680
+ target_tool = tool
681
+ logger.info(f"Found tool using toolkit prefix mapping: '{tool_name_attr}' for normalized name '{normalized_tool_name}'")
682
+ break
683
+
684
+ # Strategy 3: Match base names (extract from both sides)
685
+ base_tool_name = extract_base_tool_name(tool_name_attr)
686
+ if base_tool_name == normalized_tool_name:
687
+ target_tool = tool
688
+ logger.info(f"Found tool using base name mapping: '{tool_name_attr}' -> '{base_tool_name}' matches '{normalized_tool_name}'")
689
+ break
690
+
691
+ # Strategy 4: Match provided name with base tool name (reverse lookup)
692
+ if tool_name_attr == normalized_tool_name:
693
+ target_tool = tool
694
+ logger.info(f"Found tool using direct name match: '{tool_name_attr}' matches normalized '{normalized_tool_name}'")
695
+ break
696
+
697
+ if target_tool is None:
698
+ available_tools = []
699
+ base_available_tools = []
700
+ full_available_tools = []
701
+
702
+ for tool in tools:
703
+ tool_name_attr = None
704
+ if hasattr(tool, 'name'):
705
+ tool_name_attr = tool.name
706
+ elif hasattr(tool, 'func') and hasattr(tool.func, '__name__'):
707
+ tool_name_attr = tool.func.__name__
708
+
709
+ if tool_name_attr:
710
+ available_tools.append(tool_name_attr)
711
+
712
+ # Extract base name for user-friendly error
713
+ base_name = extract_base_tool_name(tool_name_attr)
714
+ if base_name not in base_available_tools:
715
+ base_available_tools.append(base_name)
716
+
717
+ # Track full names separately
718
+ if '___' in tool_name_attr:
719
+ full_available_tools.append(tool_name_attr)
720
+
721
+ # Create comprehensive error message
722
+ error_msg = f"Tool '{tool_name}' not found in toolkit '{toolkit_config.get('toolkit_name')}'."
723
+
724
+ if base_available_tools and full_available_tools:
725
+ error_msg += f" Available tools: {base_available_tools} (base names) or {full_available_tools} (full names)"
726
+ elif base_available_tools:
727
+ error_msg += f" Available tools: {base_available_tools}"
728
+ elif available_tools:
729
+ error_msg += f" Available tools: {available_tools}"
730
+ else:
731
+ error_msg += " No tools found in the toolkit."
732
+
733
+ # Add helpful hint about naming conventions
734
+ if '___' in tool_name:
735
+ error_msg += f" Note: You provided a full name '{tool_name}'. Try using just the base name '{extract_base_tool_name(tool_name)}'."
736
+ elif full_available_tools:
737
+ possible_full_name = create_full_tool_name(tool_name, toolkit_name)
738
+ error_msg += f" Note: You provided a base name '{tool_name}'. The full name might be '{possible_full_name}'."
739
+
740
+ return {
741
+ "success": False,
742
+ "error": error_msg,
743
+ "tool_name": tool_name,
744
+ "toolkit_config": toolkit_config,
745
+ "llm_model": llm_model,
746
+ "events_dispatched": events_dispatched,
747
+ "execution_time_seconds": 0.0
748
+ }
749
+
750
+ # Execute the tool with callback support
751
+ try:
752
+ # Log which tool was found and how
753
+ actual_tool_name = getattr(target_tool, 'name', None) or getattr(target_tool.func, '__name__', 'unknown')
754
+
755
+ # Determine which matching strategy was used
756
+ if actual_tool_name == tool_name:
757
+ logger.info(f"Found tool '{tool_name}' using exact match")
758
+ elif actual_tool_name == create_full_tool_name(normalized_tool_name, toolkit_name):
759
+ logger.info(f"Found tool '{tool_name}' using toolkit prefix mapping ('{actual_tool_name}' for normalized '{normalized_tool_name}')")
760
+ elif extract_base_tool_name(actual_tool_name) == normalized_tool_name:
761
+ logger.info(f"Found tool '{tool_name}' using base name mapping ('{actual_tool_name}' -> '{extract_base_tool_name(actual_tool_name)}')")
762
+ elif actual_tool_name == normalized_tool_name:
763
+ logger.info(f"Found tool '{tool_name}' using direct normalized name match ('{actual_tool_name}')")
764
+ else:
765
+ logger.info(f"Found tool '{tool_name}' using fallback matching ('{actual_tool_name}')")
766
+
767
+ logger.info(f"Executing tool '{tool_name}' (internal name: '{actual_tool_name}') with parameters: {tool_params}")
768
+
769
+ # Start timing the tool execution
770
+ start_time = time.time()
771
+
772
+ # Different tools might have different invocation patterns
773
+ if hasattr(target_tool, 'invoke'):
774
+ # Use config for tools that support RunnableConfig
775
+ if config is not None:
776
+ result = target_tool.invoke(tool_params, config=config)
777
+ else:
778
+ result = target_tool.invoke(tool_params)
779
+ elif hasattr(target_tool, 'run'):
780
+ result = target_tool.run(tool_params)
781
+ elif callable(target_tool):
782
+ result = target_tool(**tool_params)
783
+ else:
784
+ execution_time = time.time() - start_time
785
+ return {
786
+ "success": False,
787
+ "error": f"Tool '{tool_name}' is not callable",
788
+ "tool_name": tool_name,
789
+ "toolkit_config": toolkit_config,
790
+ "llm_model": llm_model,
791
+ "events_dispatched": events_dispatched,
792
+ "execution_time_seconds": execution_time
793
+ }
794
+
795
+ # Calculate execution time
796
+ execution_time = time.time() - start_time
797
+
798
+ # Extract events from callbacks if they support it
799
+ for callback in callbacks:
800
+ if hasattr(callback, 'events'):
801
+ events_dispatched.extend(callback.events)
802
+ elif hasattr(callback, 'get_events'):
803
+ events_dispatched.extend(callback.get_events())
804
+ elif hasattr(callback, 'dispatched_events'):
805
+ events_dispatched.extend(callback.dispatched_events)
806
+
807
+ logger.info(f"Tool '{tool_name}' executed successfully in {execution_time:.3f} seconds")
808
+
809
+ return {
810
+ "success": True,
811
+ "result": result,
812
+ "tool_name": tool_name,
813
+ "toolkit_config": toolkit_config,
814
+ "llm_model": llm_model,
815
+ "events_dispatched": events_dispatched,
816
+ "execution_time_seconds": execution_time
817
+ }
818
+
819
+ except Exception as tool_error:
820
+ # Calculate execution time even for failed executions
821
+ execution_time = time.time() - start_time
822
+ logger.error(f"Error executing tool '{tool_name}' after {execution_time:.3f} seconds: {str(tool_error)}")
823
+
824
+ # Still collect events even if tool execution failed
825
+ for callback in callbacks:
826
+ if hasattr(callback, 'events'):
827
+ events_dispatched.extend(callback.events)
828
+ elif hasattr(callback, 'get_events'):
829
+ events_dispatched.extend(callback.get_events())
830
+ elif hasattr(callback, 'dispatched_events'):
831
+ events_dispatched.extend(callback.dispatched_events)
832
+
833
+ return {
834
+ "success": False,
835
+ "error": f"Tool execution failed: {str(tool_error)}",
836
+ "tool_name": tool_name,
837
+ "toolkit_config": toolkit_config,
838
+ "llm_model": llm_model,
839
+ "events_dispatched": events_dispatched,
840
+ "execution_time_seconds": execution_time
841
+ }
842
+
843
+ except Exception as e:
844
+ logger = logging.getLogger(__name__)
845
+ logger.error(f"Error in test_toolkit_tool: {str(e)}")
846
+ return {
847
+ "success": False,
848
+ "error": f"Method execution failed: {str(e)}",
849
+ "tool_name": tool_name,
850
+ "toolkit_config": toolkit_config,
851
+ "llm_model": llm_model if 'llm_model' in locals() else None,
852
+ "events_dispatched": [],
853
+ "execution_time_seconds": 0.0
854
+ }
855
+
856
+ def _get_real_user_id(self) -> str:
857
+ """Extract the real user ID from the auth token for MCP tool calls."""
858
+ try:
859
+ import base64
860
+ import json
861
+ # Assuming JWT token, extract user ID from payload
862
+ # This is a basic implementation - adjust based on your token format
863
+ token_parts = self.auth_token.split('.')
864
+ if len(token_parts) >= 2:
865
+ payload_part = token_parts[1]
866
+ # Add padding if needed
867
+ padding = len(payload_part) % 4
868
+ if padding:
869
+ payload_part += '=' * (4 - padding)
870
+ payload = json.loads(base64.b64decode(payload_part))
871
+ return payload.get('user_id') or payload.get('sub') or payload.get('uid')
872
+ except Exception as e:
873
+ logger.error(f"Error extracting user ID from token: {e}")
874
+ return None
512
875
 
@@ -500,7 +500,12 @@ def create_graph(
500
500
  }
501
501
 
502
502
  # Check if tools should be bound to this LLM node
503
- tool_names = node.get('tool_names', []) if isinstance(node.get('tool_names'), list) else []
503
+ connected_tools = node.get('tool_names', {})
504
+ tool_names = []
505
+ if isinstance(connected_tools, dict):
506
+ for toolkit, selected_tools in connected_tools.items():
507
+ for tool in selected_tools:
508
+ tool_names.append(f"{toolkit}___{tool}")
504
509
 
505
510
  # Filter tools if specific tool names are provided
506
511
  available_tools = []
@@ -3,9 +3,6 @@ import atexit
3
3
  import logging
4
4
  from urllib.parse import urlparse, unquote
5
5
 
6
- from psycopg import Connection
7
- from langgraph.store.postgres import PostgresStore
8
-
9
6
  logger = logging.getLogger(__name__)
10
7
 
11
8
  class StoreManager:
@@ -37,7 +34,10 @@ class StoreManager:
37
34
  "dbname": parsed.path.lstrip("/") if parsed.path else None
38
35
  }
39
36
 
40
- def get_store(self, conn_str: str) -> PostgresStore:
37
+ def get_store(self, conn_str: str):
38
+ from psycopg import Connection
39
+ from langgraph.store.postgres import PostgresStore
40
+
41
41
  store = self._stores.get(conn_str)
42
42
  if store is None:
43
43
  logger.info(f"Creating new PostgresStore for connection: {conn_str}")
@@ -14,18 +14,15 @@ from .vectorstore import VectorStoreToolkit
14
14
  from ..tools.mcp_server_tool import McpServerTool
15
15
  # Import community tools
16
16
  from ...community import get_toolkits as community_toolkits, get_tools as community_tools
17
- # from ...tools.memory import MemoryToolkit
17
+ from ...tools.memory import MemoryToolkit
18
18
 
19
19
  logger = logging.getLogger(__name__)
20
20
 
21
21
 
22
22
  def get_toolkits():
23
23
  core_toolkits = [
24
- # PromptToolkit.toolkit_config_schema(),
25
- # DatasourcesToolkit.toolkit_config_schema(),
26
- # ApplicationToolkit.toolkit_config_schema(),
27
24
  ArtifactToolkit.toolkit_config_schema(),
28
- # MemoryToolkit.toolkit_config_schema(),
25
+ MemoryToolkit.toolkit_config_schema(),
29
26
  VectorStoreToolkit.toolkit_config_schema()
30
27
  ]
31
28
 
@@ -37,12 +34,7 @@ def get_tools(tools_list: list, alita_client, llm, memory_store: BaseStore = Non
37
34
  tools = []
38
35
 
39
36
  for tool in tools_list:
40
- if tool['type'] == 'prompt':
41
- prompts.append([
42
- int(tool['settings']['prompt_id']),
43
- int(tool['settings']['prompt_version_id'])
44
- ])
45
- elif tool['type'] == 'datasource':
37
+ if tool['type'] == 'datasource':
46
38
  tools.extend(DatasourcesToolkit.get_toolkit(
47
39
  alita_client,
48
40
  datasource_ids=[int(tool['settings']['datasource_id'])],
@@ -66,15 +58,14 @@ def get_tools(tools_list: list, alita_client, llm, memory_store: BaseStore = Non
66
58
  selected_tools=[],
67
59
  llm=llm
68
60
  ))
69
- # move on tools level
70
- # elif tool['type'] == 'memory':
71
- # if memory_store is None:
72
- # raise ToolException(f"Memory store is not provided for memory tool: {tool['name']}")
73
- # tools += MemoryToolkit.get_toolkit(
74
- # namespace=tool['settings'].get('namespace', str(tool['id'])),
75
- # store=memory_store,
76
- # toolkit_name=tool.get('toolkit_name', '')
77
- # ).get_tools()
61
+ elif tool['type'] == 'memory':
62
+ if memory_store is None:
63
+ raise ToolException(f"Memory store is not provided for memory tool: {tool.get('name', tool.get('toolkit_name', 'unknown'))}")
64
+ tools += MemoryToolkit.get_toolkit(
65
+ namespace=tool['settings'].get('namespace', str(tool['id'])),
66
+ store=memory_store,
67
+ toolkit_name=tool.get('toolkit_name', '')
68
+ ).get_tools()
78
69
  elif tool['type'] == 'artifact':
79
70
  tools.extend(ArtifactToolkit.get_toolkit(
80
71
  client=alita_client,