alita-sdk 0.3.486__py3-none-any.whl → 0.3.497__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 (34) hide show
  1. alita_sdk/cli/agent_loader.py +27 -6
  2. alita_sdk/cli/agents.py +10 -1
  3. alita_sdk/cli/tools/filesystem.py +95 -9
  4. alita_sdk/runtime/clients/client.py +40 -21
  5. alita_sdk/runtime/langchain/constants.py +3 -1
  6. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
  7. alita_sdk/runtime/langchain/document_loaders/constants.py +10 -6
  8. alita_sdk/runtime/langchain/langraph_agent.py +2 -1
  9. alita_sdk/runtime/toolkits/mcp.py +68 -62
  10. alita_sdk/runtime/toolkits/planning.py +3 -1
  11. alita_sdk/runtime/toolkits/tools.py +37 -18
  12. alita_sdk/runtime/tools/artifact.py +46 -17
  13. alita_sdk/runtime/tools/function.py +2 -1
  14. alita_sdk/runtime/tools/llm.py +135 -24
  15. alita_sdk/runtime/tools/mcp_remote_tool.py +23 -7
  16. alita_sdk/runtime/tools/vectorstore_base.py +3 -3
  17. alita_sdk/runtime/utils/AlitaCallback.py +106 -20
  18. alita_sdk/runtime/utils/mcp_client.py +465 -0
  19. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  20. alita_sdk/runtime/utils/toolkit_utils.py +7 -13
  21. alita_sdk/tools/base_indexer_toolkit.py +1 -1
  22. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  23. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +2 -0
  24. alita_sdk/tools/chunkers/universal_chunker.py +1 -0
  25. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  26. alita_sdk/tools/confluence/api_wrapper.py +63 -14
  27. alita_sdk/tools/sharepoint/api_wrapper.py +2 -2
  28. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +16 -18
  29. {alita_sdk-0.3.486.dist-info → alita_sdk-0.3.497.dist-info}/METADATA +1 -1
  30. {alita_sdk-0.3.486.dist-info → alita_sdk-0.3.497.dist-info}/RECORD +34 -32
  31. {alita_sdk-0.3.486.dist-info → alita_sdk-0.3.497.dist-info}/WHEEL +0 -0
  32. {alita_sdk-0.3.486.dist-info → alita_sdk-0.3.497.dist-info}/entry_points.txt +0 -0
  33. {alita_sdk-0.3.486.dist-info → alita_sdk-0.3.497.dist-info}/licenses/LICENSE +0 -0
  34. {alita_sdk-0.3.486.dist-info → alita_sdk-0.3.497.dist-info}/top_level.txt +0 -0
@@ -20,10 +20,14 @@ from ..utils.mcp_oauth import (
20
20
  fetch_resource_metadata_async,
21
21
  infer_authorization_servers_from_realm,
22
22
  )
23
- from ..utils.mcp_sse_client import McpSseClient
23
+ from ..utils.mcp_client import McpClient
24
24
 
25
25
  logger = logging.getLogger(__name__)
26
26
 
27
+ # Global registry to store MCP tool session metadata by tool name
28
+ # This is used to pass session info to callbacks since LangChain's serialization doesn't include all fields
29
+ MCP_TOOL_SESSION_REGISTRY: Dict[str, Dict[str, Any]] = {}
30
+
27
31
 
28
32
  class McpRemoteTool(McpServerTool):
29
33
  """
@@ -43,6 +47,7 @@ class McpRemoteTool(McpServerTool):
43
47
  """Update metadata with session info after model initialization."""
44
48
  super().model_post_init(__context)
45
49
  self._update_metadata_with_session()
50
+ self._register_session_metadata()
46
51
 
47
52
  def _update_metadata_with_session(self):
48
53
  """Update the metadata dict with current session information."""
@@ -54,6 +59,15 @@ class McpRemoteTool(McpServerTool):
54
59
  'mcp_server_url': canonical_resource(self.server_url)
55
60
  })
56
61
 
62
+ def _register_session_metadata(self):
63
+ """Register session metadata in global registry for callback access."""
64
+ if self.session_id and self.server_url:
65
+ MCP_TOOL_SESSION_REGISTRY[self.name] = {
66
+ 'mcp_session_id': self.session_id,
67
+ 'mcp_server_url': canonical_resource(self.server_url)
68
+ }
69
+ logger.debug(f"[MCP] Registered session metadata for tool '{self.name}': session={self.session_id}")
70
+
57
71
  def __getstate__(self):
58
72
  """Custom serialization for pickle compatibility."""
59
73
  state = super().__getstate__()
@@ -98,7 +112,7 @@ class McpRemoteTool(McpServerTool):
98
112
  tool_name_for_server = self.name.rsplit(TOOLKIT_SPLITTER, 1)[-1] if TOOLKIT_SPLITTER in self.name else self.name
99
113
  logger.warning(f"original_tool_name not set for '{self.name}', using extracted: {tool_name_for_server}")
100
114
 
101
- logger.info(f"[MCP SSE] Executing tool '{tool_name_for_server}' with session {self.session_id}")
115
+ logger.info(f"[MCP] Executing tool '{tool_name_for_server}' with session {self.session_id}")
102
116
 
103
117
  try:
104
118
  # Prepare headers
@@ -106,16 +120,18 @@ class McpRemoteTool(McpServerTool):
106
120
  if self.server_headers:
107
121
  headers.update(self.server_headers)
108
122
 
109
- # Create SSE client
110
- client = McpSseClient(
123
+ # Create unified MCP client (auto-detects transport)
124
+ client = McpClient(
111
125
  url=self.server_url,
112
126
  session_id=self.session_id,
113
127
  headers=headers,
114
128
  timeout=self.tool_timeout_sec
115
129
  )
116
130
 
117
- # Execute tool call via SSE
118
- result = await client.call_tool(tool_name_for_server, kwargs)
131
+ # Execute tool call (client auto-detects SSE vs Streamable HTTP)
132
+ async with client:
133
+ await client.initialize()
134
+ result = await client.call_tool(tool_name_for_server, kwargs)
119
135
 
120
136
  # Format the result
121
137
  if isinstance(result, dict):
@@ -144,7 +160,7 @@ class McpRemoteTool(McpServerTool):
144
160
  return str(result)
145
161
 
146
162
  except Exception as e:
147
- logger.error(f"[MCP SSE] Tool execution failed: {e}", exc_info=True)
163
+ logger.error(f"[MCP] Tool execution failed: {e}", exc_info=True)
148
164
  raise
149
165
 
150
166
  def _parse_sse(self, text: str) -> Dict[str, Any]:
@@ -270,7 +270,7 @@ class VectorStoreWrapperBase(BaseToolApiWrapper):
270
270
  )
271
271
  ).count()
272
272
 
273
- def _clean_collection(self, index_name: str = ''):
273
+ def _clean_collection(self, index_name: str = '', including_index_meta: bool = False):
274
274
  """
275
275
  Clean the vectorstore collection by deleting all indexed data.
276
276
  """
@@ -279,7 +279,7 @@ class VectorStoreWrapperBase(BaseToolApiWrapper):
279
279
  f"Cleaning collection '{self.dataset}'",
280
280
  tool_name="_clean_collection"
281
281
  )
282
- self.vector_adapter.clean_collection(self, index_name)
282
+ self.vector_adapter.clean_collection(self, index_name, including_index_meta)
283
283
  self._log_tool_event(
284
284
  f"Collection '{self.dataset}' has been cleaned. ",
285
285
  tool_name="_clean_collection"
@@ -303,7 +303,7 @@ class VectorStoreWrapperBase(BaseToolApiWrapper):
303
303
  logger.info("Cleaning index before re-indexing all documents.")
304
304
  self._log_tool_event("Cleaning index before re-indexing all documents. Previous index will be removed", tool_name="index_documents")
305
305
  try:
306
- self._clean_collection(index_name)
306
+ self._clean_collection(index_name, including_index_meta=False)
307
307
  self._log_tool_event("Previous index has been removed",
308
308
  tool_name="index_documents")
309
309
  except Exception as e:
@@ -23,9 +23,45 @@ class AlitaStreamlitCallback(BaseCallbackHandler):
23
23
  self.tokens_out = 0
24
24
  self.pending_llm_requests = defaultdict(int)
25
25
  self.current_model_name = 'gpt-4'
26
+ self._event_queue = [] # Queue for events when context is unavailable
26
27
  #
27
28
  super().__init__()
28
29
 
30
+ def _has_streamlit_context(self) -> bool:
31
+ """Check if Streamlit context is available in the current thread."""
32
+ try:
33
+ # Try to import streamlit runtime context checker
34
+ from streamlit.runtime.scriptrunner import get_script_run_ctx
35
+ ctx = get_script_run_ctx()
36
+ return ctx is not None
37
+ except (ImportError, Exception) as e:
38
+ if self.debug:
39
+ log.debug(f"Streamlit context check failed: {e}")
40
+ return False
41
+
42
+ def _safe_streamlit_call(self, func, *args, **kwargs):
43
+ """Safely execute a Streamlit UI operation, handling missing context gracefully."""
44
+ if not self._has_streamlit_context():
45
+ func_name = getattr(func, '__name__', str(func))
46
+ if self.debug:
47
+ log.warning(f"Streamlit context not available for {func_name}, queueing event")
48
+ # Store the event for potential replay when context is available
49
+ self._event_queue.append({
50
+ 'func': func_name,
51
+ 'args': args,
52
+ 'kwargs': kwargs,
53
+ 'timestamp': datetime.now(tz=timezone.utc)
54
+ })
55
+ return None
56
+
57
+ try:
58
+ return func(*args, **kwargs)
59
+ except Exception as e:
60
+ func_name = getattr(func, '__name__', str(func))
61
+ # Handle any Streamlit-specific exceptions gracefully
62
+ log.warning(f"Streamlit operation {func_name} failed: {e}")
63
+ return None
64
+
29
65
  #
30
66
  # Chain
31
67
  #
@@ -76,10 +112,14 @@ class AlitaStreamlitCallback(BaseCallbackHandler):
76
112
  json.dumps(payload, ensure_ascii=False, default=lambda o: str(o))
77
113
  )
78
114
 
79
- self.callback_state[str(run_id)] = self.st.status(
80
- f"Running {payload.get('tool_name')}...", expanded=True
115
+ status_widget = self._safe_streamlit_call(
116
+ self.st.status,
117
+ f"Running {payload.get('tool_name')}...",
118
+ expanded=True
81
119
  )
82
- self.callback_state[str(run_id)].write(f"Tool inputs: {payload}")
120
+ if status_widget:
121
+ self.callback_state[str(run_id)] = status_widget
122
+ self._safe_streamlit_call(status_widget.write, f"Tool inputs: {payload}")
83
123
 
84
124
  def on_tool_start(self, *args, run_id: UUID, **kwargs):
85
125
  """ Callback """
@@ -95,8 +135,15 @@ class AlitaStreamlitCallback(BaseCallbackHandler):
95
135
  "tool_inputs": kwargs.get('inputs')
96
136
  }
97
137
  payload = json.loads(json.dumps(payload, ensure_ascii=False, default=lambda o: str(o)))
98
- self.callback_state[tool_run_id] = self.st.status(f"Running {tool_name}...", expanded=True)
99
- self.callback_state[tool_run_id].write(f"Tool inputs: {kwargs.get('inputs')}")
138
+
139
+ status_widget = self._safe_streamlit_call(
140
+ self.st.status,
141
+ f"Running {tool_name}...",
142
+ expanded=True
143
+ )
144
+ if status_widget:
145
+ self.callback_state[tool_run_id] = status_widget
146
+ self._safe_streamlit_call(status_widget.write, f"Tool inputs: {kwargs.get('inputs')}")
100
147
 
101
148
  def on_tool_end(self, *args, run_id: UUID, **kwargs):
102
149
  """ Callback """
@@ -104,11 +151,16 @@ class AlitaStreamlitCallback(BaseCallbackHandler):
104
151
  log.info("on_tool_end(%s, %s)", args, kwargs)
105
152
  tool_run_id = str(run_id)
106
153
  tool_output = args[0]
107
- if self.callback_state[tool_run_id]:
108
- self.callback_state[tool_run_id].write(f"Tool output: {tool_output}")
109
- self.callback_state[tool_run_id].update(label=f"Completed {kwargs.get('name')}", state="complete", expanded=False)
154
+ if self.callback_state.get(tool_run_id):
155
+ status_widget = self.callback_state[tool_run_id]
156
+ self._safe_streamlit_call(status_widget.write, f"Tool output: {tool_output}")
157
+ self._safe_streamlit_call(
158
+ status_widget.update,
159
+ label=f"Completed {kwargs.get('name')}",
160
+ state="complete",
161
+ expanded=False
162
+ )
110
163
  self.callback_state.pop(tool_run_id, None)
111
- del self.callback_state[run_id]
112
164
 
113
165
  def on_tool_error(self, *args, run_id: UUID, **kwargs):
114
166
  """ Callback """
@@ -116,9 +168,19 @@ class AlitaStreamlitCallback(BaseCallbackHandler):
116
168
  log.info("on_tool_error(%s, %s)", args, kwargs)
117
169
  tool_run_id = str(run_id)
118
170
  tool_exception = args[0]
119
- self.callback_state[tool_run_id].write(f"{traceback.format_exception(tool_exception)}")
120
- self.callback_state[tool_run_id].update(label=f"Error {kwargs.get('name')}", state="error", expanded=False)
121
- self.callback_state.pop(tool_run_id, None)
171
+ if self.callback_state.get(tool_run_id):
172
+ status_widget = self.callback_state[tool_run_id]
173
+ self._safe_streamlit_call(
174
+ status_widget.write,
175
+ f"{traceback.format_exception(tool_exception)}"
176
+ )
177
+ self._safe_streamlit_call(
178
+ status_widget.update,
179
+ label=f"Error {kwargs.get('name')}",
180
+ state="error",
181
+ expanded=False
182
+ )
183
+ self.callback_state.pop(tool_run_id, None)
122
184
 
123
185
  #
124
186
  # Agent
@@ -156,8 +218,14 @@ class AlitaStreamlitCallback(BaseCallbackHandler):
156
218
  self.current_model_name = metadata.get('ls_model_name', self.current_model_name)
157
219
  llm_run_id = str(run_id)
158
220
 
159
- self.callback_state[llm_run_id] = self.st.status(f"Running LLM ...", expanded=True)
160
- self.callback_state[llm_run_id].write(f"LLM inputs: {messages}")
221
+ status_widget = self._safe_streamlit_call(
222
+ self.st.status,
223
+ f"Running LLM ...",
224
+ expanded=True
225
+ )
226
+ if status_widget:
227
+ self.callback_state[llm_run_id] = status_widget
228
+ self._safe_streamlit_call(status_widget.write, f"LLM inputs: {messages}")
161
229
 
162
230
  def on_llm_start(self, *args, **kwargs):
163
231
  """ Callback """
@@ -178,16 +246,27 @@ class AlitaStreamlitCallback(BaseCallbackHandler):
178
246
  content = None
179
247
  if chunk:
180
248
  content = chunk.text
181
- self.callback_state[str(run_id)].write(content)
249
+
250
+ llm_run_id = str(run_id)
251
+ if self.callback_state.get(llm_run_id):
252
+ status_widget = self.callback_state[llm_run_id]
253
+ self._safe_streamlit_call(status_widget.write, content)
182
254
 
183
255
  def on_llm_error(self, *args, run_id: UUID, **kwargs):
184
256
  """ Callback """
185
257
  if self.debug:
186
258
  log.error("on_llm_error(%s, %s)", args, kwargs)
187
259
  llm_run_id = str(run_id)
188
- self.callback_state[llm_run_id].write(f"on_llm_error({args}, {kwargs})")
189
- self.callback_state[llm_run_id].update(label=f"Error {kwargs.get('name')}", state="error", expanded=False)
190
- self.callback_state.pop(llm_run_id, None)
260
+ if self.callback_state.get(llm_run_id):
261
+ status_widget = self.callback_state[llm_run_id]
262
+ self._safe_streamlit_call(status_widget.write, f"on_llm_error({args}, {kwargs})")
263
+ self._safe_streamlit_call(
264
+ status_widget.update,
265
+ label=f"Error {kwargs.get('name')}",
266
+ state="error",
267
+ expanded=False
268
+ )
269
+ self.callback_state.pop(llm_run_id, None)
191
270
  #
192
271
  # exception = args[0]
193
272
  # FIXME: should we emit an error here too?
@@ -205,5 +284,12 @@ class AlitaStreamlitCallback(BaseCallbackHandler):
205
284
  if self.debug:
206
285
  log.debug("on_llm_end(%s, %s)", response, kwargs)
207
286
  llm_run_id = str(run_id)
208
- self.callback_state[llm_run_id].update(label=f"Completed LLM call", state="complete", expanded=False)
209
- self.callback_state.pop(llm_run_id, None)
287
+ if self.callback_state.get(llm_run_id):
288
+ status_widget = self.callback_state[llm_run_id]
289
+ self._safe_streamlit_call(
290
+ status_widget.update,
291
+ label=f"Completed LLM call",
292
+ state="complete",
293
+ expanded=False
294
+ )
295
+ self.callback_state.pop(llm_run_id, None)