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

@@ -13,6 +13,7 @@ from langchain_core.messages import (
13
13
  from langchain_core.tools import ToolException
14
14
  from langgraph.store.base import BaseStore
15
15
  from langchain_openai import OpenAIEmbeddings, ChatOpenAI
16
+ from langchain_anthropic import ChatAnthropic
16
17
 
17
18
  from ..langchain.assistant import Assistant as LangChainAssistant
18
19
  # from ..llamaindex.assistant import Assistant as LLamaAssistant
@@ -43,6 +44,7 @@ class AlitaClient:
43
44
  self.base_url = base_url.rstrip('/')
44
45
  self.api_path = '/api/v1'
45
46
  self.llm_path = '/llm/v1'
47
+ self.allm_path = '/llm'
46
48
  self.project_id = project_id
47
49
  self.auth_token = auth_token
48
50
  self.headers = {
@@ -74,6 +76,10 @@ class AlitaClient:
74
76
  self.configurations: list = configurations or []
75
77
  self.model_timeout = kwargs.get('model_timeout', 120)
76
78
  self.model_image_generation = kwargs.get('model_image_generation')
79
+
80
+ # Cache for generated images to avoid token consumption
81
+ # This is used by image_generation and artifact toolkits to pass data via reference
82
+ self._generated_images_cache: Dict[str, Dict[str, Any]] = {}
77
83
 
78
84
  def get_mcp_toolkits(self):
79
85
  if user_id := self._get_real_user_id():
@@ -219,21 +225,25 @@ class AlitaClient:
219
225
  request_timeout=self.model_timeout
220
226
  )
221
227
 
222
- def get_llm(self, model_name: str, model_config: dict) -> ChatOpenAI:
228
+ def get_llm(self, model_name: str, model_config: dict):
223
229
  """
224
- Get a ChatOpenAI model instance based on the model name and configuration.
230
+ Get a ChatOpenAI or ChatAnthropic model instance based on the model name and configuration.
225
231
 
226
232
  Args:
227
233
  model_name: Name of the model to retrieve
228
234
  model_config: Configuration parameters for the model
229
235
 
230
236
  Returns:
231
- An instance of ChatOpenAI configured with the provided parameters.
237
+ An instance of ChatOpenAI or ChatAnthropic configured with the provided parameters.
232
238
  """
233
239
  if not model_name:
234
240
  raise ValueError("Model name must be provided")
235
241
 
236
- logger.info(f"Creating ChatOpenAI model: {model_name} with config: {model_config}")
242
+ # Determine if this is an Anthropic model
243
+ model_name_lower = model_name.lower()
244
+ is_anthropic = "anthropic" in model_name_lower or "claude" in model_name_lower
245
+
246
+ logger.info(f"Creating {'ChatAnthropic' if is_anthropic else 'ChatOpenAI'} model: {model_name} with config: {model_config}")
237
247
 
238
248
  try:
239
249
  from tools import this # pylint: disable=E0401,C0415
@@ -256,25 +266,62 @@ class AlitaClient:
256
266
  # default nuber for a case when auto is selected for an agent
257
267
  llm_max_tokens = 4000
258
268
 
259
- target_kwargs = {
260
- "base_url": f"{self.base_url}{self.llm_path}",
261
- "model": model_name,
262
- "api_key": self.auth_token,
263
- "streaming": model_config.get("streaming", True),
264
- "stream_usage": model_config.get("stream_usage", True),
265
- "max_tokens": llm_max_tokens,
266
- "temperature": model_config.get("temperature"),
267
- "reasoning_effort": model_config.get("reasoning_effort"),
268
- "max_retries": model_config.get("max_retries", 3),
269
- "seed": model_config.get("seed", None),
270
- "openai_organization": str(self.project_id),
271
- }
272
-
273
- if use_responses_api:
274
- target_kwargs["use_responses_api"] = True
275
-
276
- return ChatOpenAI(**target_kwargs)
269
+ if is_anthropic:
270
+ # ChatAnthropic configuration
271
+ target_kwargs = {
272
+ "base_url": f"{self.base_url}{self.allm_path}",
273
+ "model": model_name,
274
+ "api_key": self.auth_token,
275
+ "streaming": model_config.get("streaming", True),
276
+ "max_tokens": llm_max_tokens,
277
+ "temperature": model_config.get("temperature"),
278
+ "max_retries": model_config.get("max_retries", 3),
279
+ "default_headers": {"openai-organization": str(self.project_id),
280
+ "Authorization": f"Bearer {self.auth_token}"},
281
+ }
282
+
283
+ # TODO": Check on ChatAnthropic client when they get "effort" support back
284
+ if model_config.get("reasoning_effort"):
285
+ if model_config["reasoning_effort"].lower() == "low":
286
+ target_kwargs['thinking'] = {"type": "enabled", "budget_tokens": 2048}
287
+ target_kwargs['temperature'] = 1
288
+ target_kwargs["max_tokens"] = 2048 + target_kwargs["max_tokens"]
289
+ elif model_config["reasoning_effort"].lower() == "medium":
290
+ target_kwargs['thinking'] = {"type": "enabled", "budget_tokens": 4096}
291
+ target_kwargs['temperature'] = 1
292
+ target_kwargs["max_tokens"] = 4096 + target_kwargs["max_tokens"]
293
+ elif model_config["reasoning_effort"].lower() == "high":
294
+ target_kwargs['thinking'] = {"type": "enabled", "budget_tokens": 9092}
295
+ target_kwargs['temperature'] = 1
296
+ target_kwargs["max_tokens"] = 9092 + target_kwargs["max_tokens"]
297
+
298
+ # Add http_client if provided
299
+ if "http_client" in model_config:
300
+ target_kwargs["http_client"] = model_config["http_client"]
301
+
302
+ llm = ChatAnthropic(**target_kwargs)
303
+ else:
304
+ # ChatOpenAI configuration
305
+ target_kwargs = {
306
+ "base_url": f"{self.base_url}{self.llm_path}",
307
+ "model": model_name,
308
+ "api_key": self.auth_token,
309
+ "streaming": model_config.get("streaming", True),
310
+ "stream_usage": model_config.get("stream_usage", True),
311
+ "max_tokens": llm_max_tokens,
312
+ "temperature": model_config.get("temperature"),
313
+ "reasoning_effort": model_config.get("reasoning_effort"),
314
+ "max_retries": model_config.get("max_retries", 3),
315
+ "seed": model_config.get("seed", None),
316
+ "openai_organization": str(self.project_id),
317
+ }
277
318
 
319
+ if use_responses_api:
320
+ target_kwargs["use_responses_api"] = True
321
+
322
+ llm = ChatOpenAI(**target_kwargs)
323
+ return llm
324
+
278
325
  def generate_image(self,
279
326
  prompt: str,
280
327
  n: int = 1,
@@ -6,7 +6,6 @@ import requests
6
6
  from typing import Any
7
7
  from json import dumps
8
8
  import chardet
9
- from ...tools import instantiate_toolkit
10
9
 
11
10
  logger = logging.getLogger(__name__)
12
11
 
@@ -49,27 +48,6 @@ class SandboxArtifact:
49
48
  return f'{data['error']}. {data['content'] if data['content'] else ''}'
50
49
  detected = chardet.detect(data)
51
50
  return data
52
- # TODO: add proper handling for binary files (images, pdf, etc.) for sandbox
53
- # if detected['encoding'] is not None:
54
- # try:
55
- # return data.decode(detected['encoding'])
56
- # except Exception:
57
- # logger.error('Error while default encoding')
58
- # return parse_file_content(file_name=artifact_name,
59
- # file_content=data,
60
- # is_capture_image=is_capture_image,
61
- # page_number=page_number,
62
- # sheet_name=sheet_name,
63
- # excel_by_sheets=excel_by_sheets,
64
- # llm=llm)
65
- # else:
66
- # return parse_file_content(file_name=artifact_name,
67
- # file_content=data,
68
- # is_capture_image=is_capture_image,
69
- # page_number=page_number,
70
- # sheet_name=sheet_name,
71
- # excel_by_sheets=excel_by_sheets,
72
- # llm=llm)
73
51
 
74
52
  def delete(self, artifact_name: str, bucket_name=None):
75
53
  if not bucket_name:
@@ -185,19 +163,6 @@ class SandboxClient:
185
163
  data = requests.get(url, headers=self.headers, verify=False).json()
186
164
  return data
187
165
 
188
- def toolkit(self, toolkit_id: int):
189
- url = f"{self.base_url}{self.api_path}/tool/prompt_lib/{self.project_id}/{toolkit_id}"
190
- response = requests.get(url, headers=self.headers, verify=False)
191
- if not response.ok:
192
- raise ValueError(f"Failed to fetch toolkit {toolkit_id}: {response.text}")
193
-
194
- tool_data = response.json()
195
- if 'settings' not in tool_data:
196
- tool_data['settings'] = {}
197
- tool_data['settings']['alita'] = self
198
-
199
- return instantiate_toolkit(tool_data)
200
-
201
166
  def get_list_of_apps(self):
202
167
  apps = []
203
168
  limit = 10
@@ -937,7 +937,7 @@ class LangGraphAgentRunnable(CompiledStateGraph):
937
937
  "with no accompanying text."
938
938
  )
939
939
 
940
- logging.info(f"Input: {thread_id} - {input}")
940
+ logger.info(f"Input: {thread_id} - {input}")
941
941
  try:
942
942
  if self.checkpointer and self.checkpointer.get_tuple(config):
943
943
  if config.pop("should_continue", False):
@@ -1,3 +1,4 @@
1
+ import base64
1
2
  import hashlib
2
3
  import io
3
4
  import json
@@ -14,7 +15,7 @@ from pydantic import create_model, Field, model_validator
14
15
  from ...tools.non_code_indexer_toolkit import NonCodeIndexerToolkit
15
16
  from ...tools.utils.available_tools_decorator import extend_with_parent_available_tools
16
17
  from ...tools.elitea_base import extend_with_file_operations, BaseCodeToolApiWrapper
17
- from ...runtime.utils.utils import IndexerKeywords
18
+ from ...runtime.utils.utils import IndexerKeywords, resolve_image_from_cache
18
19
 
19
20
 
20
21
  class ArtifactWrapper(NonCodeIndexerToolkit):
@@ -63,23 +64,30 @@ class ArtifactWrapper(NonCodeIndexerToolkit):
63
64
  if was_modified:
64
65
  logging.warning(f"Filename sanitized: '{filename}' -> '{sanitized_filename}'")
65
66
 
67
+ # Auto-detect and extract base64 from image_url structures (from image_generation tool)
68
+ # Returns tuple: (processed_data, is_from_image_generation)
69
+ filedata, is_from_image_generation = self._extract_base64_if_needed(filedata)
70
+
66
71
  if sanitized_filename.endswith(".xlsx"):
67
72
  data = json.loads(filedata)
68
73
  filedata = self.create_xlsx_filedata(data)
69
74
 
70
75
  result = self.artifact.create(sanitized_filename, filedata, bucket_name)
71
76
 
72
- # Dispatch custom event for file creation
73
- dispatch_custom_event("file_modified", {
74
- "message": f"File '{filename}' created successfully",
75
- "filename": filename,
76
- "tool_name": "createFile",
77
- "toolkit": "artifact",
78
- "operation_type": "create",
79
- "meta": {
80
- "bucket": bucket_name or self.bucket
81
- }
82
- })
77
+ # Skip file_modified event for images from image_generation tool
78
+ # These are already tracked in the tool output and don't need duplicate events
79
+ if not is_from_image_generation:
80
+ # Dispatch custom event for file creation
81
+ dispatch_custom_event("file_modified", {
82
+ "message": f"File '{filename}' created successfully",
83
+ "filename": filename,
84
+ "tool_name": "createFile",
85
+ "toolkit": "artifact",
86
+ "operation_type": "create",
87
+ "meta": {
88
+ "bucket": bucket_name or self.bucket
89
+ }
90
+ })
83
91
 
84
92
  return result
85
93
 
@@ -109,6 +117,43 @@ class ArtifactWrapper(NonCodeIndexerToolkit):
109
117
 
110
118
  sanitized = sanitized_name + extension
111
119
  return sanitized, (sanitized != original)
120
+
121
+ def _extract_base64_if_needed(self, filedata: str) -> tuple[str | bytes, bool]:
122
+ """
123
+ Resolve cached_image_id references from cache and decode to binary data.
124
+
125
+ Requires JSON format with cached_image_id field: {"cached_image_id": "img_xxx"}
126
+ LLM must extract specific cached_image_id from generate_image response.
127
+
128
+ Returns:
129
+ tuple: (processed_data, is_from_image_generation)
130
+ - processed_data: Original filedata or resolved binary image data
131
+ - is_from_image_generation: True if data came from image_generation cache
132
+ """
133
+ if not filedata or not isinstance(filedata, str):
134
+ return filedata, False
135
+
136
+ # Require JSON format - fail fast if not JSON
137
+ if '{' not in filedata:
138
+ return filedata, False
139
+
140
+ try:
141
+ data = json.loads(filedata)
142
+ except json.JSONDecodeError:
143
+ # Not valid JSON, return as-is (regular file content)
144
+ return filedata, False
145
+
146
+ if not isinstance(data, dict):
147
+ return filedata, False
148
+
149
+ # Only accept direct cached_image_id format: {"cached_image_id": "img_xxx"}
150
+ # LLM must parse generate_image response and extract specific cached_image_id
151
+ if 'cached_image_id' in data:
152
+ binary_data = resolve_image_from_cache(self.alita, data['cached_image_id'])
153
+ return binary_data, True # Mark as from image_generation
154
+
155
+ # If JSON doesn't have cached_image_id, treat as regular file content
156
+ return filedata, False
112
157
 
113
158
  def create_xlsx_filedata(self, data: dict[str, list[list]]) -> bytes:
114
159
  try:
@@ -377,15 +422,19 @@ class ArtifactWrapper(NonCodeIndexerToolkit):
377
422
  "createFile",
378
423
  filename=(str, Field(description="Filename")),
379
424
  filedata=(str, Field(description="""Stringified content of the file.
380
- Example for .xlsx filedata format:
381
- {
382
- "Sheet1":[
383
- ["Name", "Age", "City"],
384
- ["Alice", 25, "New York"],
385
- ["Bob", 30, "San Francisco"],
386
- ["Charlie", 35, "Los Angeles"]
387
- ]
388
- }
425
+
426
+ Supports three input formats:
427
+
428
+ 1. CACHED IMAGE REFERENCE (for generated/cached images):
429
+ Pass JSON with cached_image_id field: {"cached_image_id": "img_xxx"}
430
+ The tool will automatically resolve and decode the image from cache.
431
+ This is typically used when another tool returns an image reference.
432
+
433
+ 2. EXCEL FILES (.xlsx extension):
434
+ Pass JSON with sheet structure: {"Sheet1": [["Name", "Age"], ["Alice", 25], ["Bob", 30]]}
435
+
436
+ 3. TEXT/OTHER FILES:
437
+ Pass the plain text string directly.
389
438
  """)),
390
439
  bucket_name=bucket_name
391
440
  )
@@ -1,7 +1,9 @@
1
1
  """
2
2
  Image generation tool for Alita SDK.
3
3
  """
4
+ import json
4
5
  import logging
6
+ import uuid
5
7
  from typing import Optional, Type, Any, List, Literal
6
8
  from langchain_core.tools import BaseTool, BaseToolkit
7
9
  from pydantic import BaseModel, Field, create_model, ConfigDict
@@ -76,7 +78,12 @@ class ImageGenerationTool(BaseTool):
76
78
  """Tool for generating images using the Alita client."""
77
79
 
78
80
  name: str = "generate_image"
79
- description: str = "Generate images from text prompts using AI models"
81
+ description: str = (
82
+ "Generate images from text prompts using AI models. "
83
+ "Returns a JSON object with 'cached_image_id' field containing a reference to the generated image data. "
84
+ "The cached_image_id can be used to save or process the image. "
85
+ "The actual image data is stored temporarily and can be retrieved using the cached_image_id reference."
86
+ )
80
87
  args_schema: Type[BaseModel] = ImageGenerationInput
81
88
  alita_client: Any = None
82
89
 
@@ -85,10 +92,10 @@ class ImageGenerationTool(BaseTool):
85
92
  self.alita_client = client
86
93
 
87
94
  def _run(self, prompt: str, n: int = 1, size: str = "auto",
88
- quality: str = "auto", style: Optional[str] = None) -> list:
95
+ quality: str = "auto", style: Optional[str] = None) -> str:
89
96
  """Generate an image based on the provided parameters."""
90
97
  try:
91
- logger.info(f"Generating image with prompt: {prompt[:50]}...")
98
+ logger.debug(f"Generating image with prompt: {prompt[:50]}...")
92
99
 
93
100
  result = self.alita_client.generate_image(
94
101
  prompt=prompt,
@@ -98,57 +105,56 @@ class ImageGenerationTool(BaseTool):
98
105
  style=style
99
106
  )
100
107
 
101
- # Return multimodal content format for LLM consumption
108
+ # Return simple JSON structure with reference ID instead of full base64
102
109
  if 'data' in result:
103
110
  images = result['data']
104
- content_chunks = []
105
111
 
106
- # Add a text description of what was generated
107
- if len(images) == 1:
108
- content_chunks.append({
109
- "type": "text",
110
- "text": f"Generated image for prompt: '{prompt}'"
111
- })
112
- else:
113
- content_chunks.append({
114
- "type": "text",
115
- "text": f"Generated {len(images)} images for "
116
- f"prompt: '{prompt}'"
112
+ # Process all images with unified structure
113
+ images_list = []
114
+ for idx, image_data in enumerate(images, 1):
115
+ if not image_data.get('b64_json'):
116
+ continue
117
+
118
+ cached_image_id = f"img_{uuid.uuid4().hex[:12]}"
119
+
120
+ # Store in cache
121
+ if hasattr(self.alita_client, '_generated_images_cache'):
122
+ self.alita_client._generated_images_cache[cached_image_id] = {
123
+ 'base64_data': image_data['b64_json']
124
+ }
125
+ logger.debug(f"Stored generated image in cache with ID: {cached_image_id}")
126
+
127
+ images_list.append({
128
+ "image_number": idx,
129
+ "image_type": "png",
130
+ "cached_image_id": cached_image_id
117
131
  })
118
132
 
119
- # Add image content for each generated image
120
- for image_data in images:
121
- if image_data.get('url'):
122
- content_chunks.append({
123
- "type": "image_url",
124
- "image_url": {
125
- "url": image_data['url']
126
- }
127
- })
128
- elif image_data.get('b64_json'):
129
- content_chunks.append({
130
- "type": "image_url",
131
- "image_url": {
132
- "url": f"data:image/png;base64,"
133
- f"{image_data['b64_json']}"
134
- }
135
- })
133
+ if not images_list:
134
+ return json.dumps({
135
+ "status": "error",
136
+ "message": "No base64 image data found"
137
+ })
136
138
 
137
- return content_chunks
139
+ return json.dumps({
140
+ "status": "success",
141
+ "prompt": prompt,
142
+ "total_images": len(images_list),
143
+ "images": images_list
144
+ })
138
145
 
139
- # Fallback to text response if no images in result
140
- return [{
141
- "type": "text",
142
- "text": f"Image generation completed but no images "
143
- f"returned: {result}"
144
- }]
146
+ # Fallback to error response if no images in result
147
+ return json.dumps({
148
+ "status": "error",
149
+ "message": f"Image generation completed but no images returned: {result}"
150
+ })
145
151
 
146
152
  except Exception as e:
147
153
  logger.error(f"Error generating image: {e}")
148
- return [{
149
- "type": "text",
150
- "text": f"Error generating image: {str(e)}"
151
- }]
154
+ return json.dumps({
155
+ "status": "error",
156
+ "message": f"Error generating image: {str(e)}"
157
+ })
152
158
 
153
159
  async def _arun(self, prompt: str, n: int = 1, size: str = "256x256",
154
160
  quality: str = "auto",
@@ -6,6 +6,8 @@ from typing import Any, Optional, List, Union, Literal
6
6
  from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
7
7
  from langchain_core.runnables import RunnableConfig
8
8
  from langchain_core.tools import BaseTool, ToolException
9
+ from langchain_core.exceptions import OutputParserException
10
+ from langchain_core.callbacks import dispatch_custom_event
9
11
  from pydantic import Field
10
12
 
11
13
  from ..langchain.constants import ELITEA_RS
@@ -14,6 +16,35 @@ from ..langchain.utils import create_pydantic_model, propagate_the_input_mapping
14
16
  logger = logging.getLogger(__name__)
15
17
 
16
18
 
19
+ # def _is_thinking_model(llm_client: Any) -> bool:
20
+ # """
21
+ # Check if a model uses extended thinking capability by reading cached metadata.
22
+
23
+ # Thinking models require special message formatting where assistant messages
24
+ # must start with thinking blocks before tool_use blocks.
25
+
26
+ # This function reads the `_supports_reasoning` attribute that should be set
27
+ # when the LLM client is created (by checking the model's supports_reasoning field).
28
+
29
+ # Args:
30
+ # llm_client: LLM client instance with optional _supports_reasoning attribute
31
+
32
+ # Returns:
33
+ # True if the model is a thinking model, False otherwise
34
+ # """
35
+ # if not llm_client:
36
+ # return False
37
+
38
+ # # Check if supports_reasoning was cached on the client
39
+ # supports_reasoning = getattr(llm_client, '_supports_reasoning', False)
40
+
41
+ # if supports_reasoning:
42
+ # model_name = getattr(llm_client, 'model_name', None) or getattr(llm_client, 'model', 'unknown')
43
+ # logger.debug(f"Model '{model_name}' is a thinking/reasoning model (cached from API metadata)")
44
+
45
+ # return supports_reasoning
46
+
47
+
17
48
  class LLMNode(BaseTool):
18
49
  """Enhanced LLM node with chat history and tool binding support"""
19
50
 
@@ -190,20 +221,54 @@ class LLMNode(BaseTool):
190
221
  try:
191
222
  llm = self.__get_struct_output_model(llm_client, struct_model)
192
223
  completion = llm.invoke(messages, config=config)
193
- except ValueError as e:
224
+ except (ValueError, OutputParserException) as e:
194
225
  logger.error(f"Error invoking structured output model: {format_exc()}")
195
- logger.info("Attemping to fall back to json mode")
196
- # Fallback to regular LLM with JSON extraction
197
- completion = self.__get_struct_output_model(llm_client, struct_model,
198
- method="json_mode").invoke(messages, config=config)
226
+ logger.info("Attempting to fall back to json mode")
227
+ try:
228
+ # Fallback to regular LLM with JSON extraction
229
+ completion = self.__get_struct_output_model(llm_client, struct_model,
230
+ method="json_mode").invoke(messages, config=config)
231
+ except (ValueError, OutputParserException) as e2:
232
+ logger.error(f"json_mode fallback also failed: {format_exc()}")
233
+ logger.info("Attempting to fall back to function_calling")
234
+ # Final fallback to function_calling method
235
+ completion = self.__get_struct_output_model(llm_client, struct_model,
236
+ method="json_schema").invoke(messages, config=config)
199
237
  result = completion.model_dump()
200
238
 
201
239
  # Ensure messages are properly formatted
202
240
  if result.get('messages') and isinstance(result['messages'], list):
203
241
  result['messages'] = [{'role': 'assistant', 'content': '\n'.join(result['messages'])}]
204
242
  else:
205
- result['messages'] = messages + [
206
- AIMessage(content=result.get(ELITEA_RS, '') or initial_completion.content)]
243
+ # Extract content from initial_completion, handling thinking blocks
244
+ fallback_content = result.get(ELITEA_RS, '')
245
+ if not fallback_content and initial_completion:
246
+ content_parts = self._extract_content_from_completion(initial_completion)
247
+ fallback_content = content_parts.get('text') or ''
248
+ thinking = content_parts.get('thinking')
249
+
250
+ # Dispatch thinking event if present
251
+ if thinking:
252
+ try:
253
+ model_name = getattr(llm_client, 'model_name', None) or getattr(llm_client, 'model', 'LLM')
254
+ dispatch_custom_event(
255
+ name="thinking_step",
256
+ data={
257
+ "message": thinking,
258
+ "tool_name": f"LLM ({model_name})",
259
+ "toolkit": "reasoning",
260
+ },
261
+ config=config,
262
+ )
263
+ except Exception as e:
264
+ logger.warning(f"Failed to dispatch thinking event: {e}")
265
+
266
+ if not fallback_content:
267
+ # Final fallback to raw content
268
+ content = initial_completion.content
269
+ fallback_content = content if isinstance(content, str) else str(content)
270
+
271
+ result['messages'] = messages + [AIMessage(content=fallback_content)]
207
272
 
208
273
  return result
209
274
  else:
@@ -221,28 +286,98 @@ class LLMNode(BaseTool):
221
286
  if self.output_variables:
222
287
  if self.output_variables[0] == 'messages':
223
288
  return output_msgs
224
- output_msgs[self.output_variables[0]] = current_completion.content if current_completion else None
289
+ # Extract content properly from thinking-enabled responses
290
+ if current_completion:
291
+ content_parts = self._extract_content_from_completion(current_completion)
292
+ text_content = content_parts.get('text')
293
+ thinking = content_parts.get('thinking')
294
+
295
+ # Dispatch thinking event if present
296
+ if thinking:
297
+ try:
298
+ model_name = getattr(llm_client, 'model_name', None) or getattr(llm_client, 'model', 'LLM')
299
+ dispatch_custom_event(
300
+ name="thinking_step",
301
+ data={
302
+ "message": thinking,
303
+ "tool_name": f"LLM ({model_name})",
304
+ "toolkit": "reasoning",
305
+ },
306
+ config=config,
307
+ )
308
+ except Exception as e:
309
+ logger.warning(f"Failed to dispatch thinking event: {e}")
310
+
311
+ if text_content:
312
+ output_msgs[self.output_variables[0]] = text_content
313
+ else:
314
+ # Fallback to raw content
315
+ content = current_completion.content
316
+ output_msgs[self.output_variables[0]] = content if isinstance(content, str) else str(content)
317
+ else:
318
+ output_msgs[self.output_variables[0]] = None
225
319
 
226
320
  return output_msgs
227
321
  else:
228
- # Regular text response
229
- content = completion.content.strip() if hasattr(completion, 'content') else str(completion)
322
+ # Regular text response - handle both simple strings and thinking-enabled responses
323
+ content_parts = self._extract_content_from_completion(completion)
324
+ thinking = content_parts.get('thinking')
325
+ text_content = content_parts.get('text') or ''
326
+
327
+ # Fallback to string representation if no content extracted
328
+ if not text_content:
329
+ if hasattr(completion, 'content'):
330
+ content = completion.content
331
+ text_content = content.strip() if isinstance(content, str) else str(content)
332
+ else:
333
+ text_content = str(completion)
334
+
335
+ # Dispatch thinking step event to chat if present
336
+ if thinking:
337
+ logger.info(f"Model thinking: {thinking[:200]}..." if len(thinking) > 200 else f"Model thinking: {thinking}")
338
+
339
+ # Dispatch custom event for thinking step to be displayed in chat
340
+ try:
341
+ model_name = getattr(llm_client, 'model_name', None) or getattr(llm_client, 'model', 'LLM')
342
+ dispatch_custom_event(
343
+ name="thinking_step",
344
+ data={
345
+ "message": thinking,
346
+ "tool_name": f"LLM ({model_name})",
347
+ "toolkit": "reasoning",
348
+ },
349
+ config=config,
350
+ )
351
+ except Exception as e:
352
+ logger.warning(f"Failed to dispatch thinking event: {e}")
353
+
354
+ # Build the AI message with both thinking and text
355
+ # Store thinking in additional_kwargs for potential future use
356
+ ai_message_kwargs = {'content': text_content}
357
+ if thinking:
358
+ ai_message_kwargs['additional_kwargs'] = {'thinking': thinking}
359
+ ai_message = AIMessage(**ai_message_kwargs)
230
360
 
231
361
  # Try to extract JSON if output variables are specified (but exclude 'messages' which is handled separately)
232
362
  json_output_vars = [var for var in (self.output_variables or []) if var != 'messages']
233
363
  if json_output_vars:
234
364
  # set response to be the first output variable for non-structured output
235
- response_data = {json_output_vars[0]: content}
236
- new_messages = messages + [AIMessage(content=content)]
365
+ response_data = {json_output_vars[0]: text_content}
366
+ new_messages = messages + [ai_message]
237
367
  response_data['messages'] = new_messages
238
368
  return response_data
239
369
 
240
370
  # Simple text response (either no output variables or JSON parsing failed)
241
- new_messages = messages + [AIMessage(content=content)]
371
+ new_messages = messages + [ai_message]
242
372
  return {"messages": new_messages}
243
373
 
244
374
  except Exception as e:
375
+ # Enhanced error logging with model diagnostics
376
+ model_info = getattr(llm_client, 'model_name', None) or getattr(llm_client, 'model', 'unknown')
245
377
  logger.error(f"Error in LLM Node: {format_exc()}")
378
+ logger.error(f"Model being used: {model_info}")
379
+ logger.error(f"Error type: {type(e).__name__}")
380
+
246
381
  error_msg = f"Error: {e}"
247
382
  new_messages = messages + [AIMessage(content=error_msg)]
248
383
  return {"messages": new_messages}
@@ -251,6 +386,56 @@ class LLMNode(BaseTool):
251
386
  # Legacy support for old interface
252
387
  return self.invoke(kwargs, **kwargs)
253
388
 
389
+ @staticmethod
390
+ def _extract_content_from_completion(completion) -> dict:
391
+ """Extract thinking and text content from LLM completion.
392
+
393
+ Handles Anthropic's extended thinking format where content is a list
394
+ of blocks with types: 'thinking' and 'text'.
395
+
396
+ Args:
397
+ completion: LLM completion object with content attribute
398
+
399
+ Returns:
400
+ dict with 'thinking' and 'text' keys
401
+ """
402
+ result = {'thinking': None, 'text': None}
403
+
404
+ if not hasattr(completion, 'content'):
405
+ return result
406
+
407
+ content = completion.content
408
+
409
+ # Handle list of content blocks (Anthropic extended thinking format)
410
+ if isinstance(content, list):
411
+ thinking_blocks = []
412
+ text_blocks = []
413
+
414
+ for block in content:
415
+ if isinstance(block, dict):
416
+ block_type = block.get('type', '')
417
+ if block_type == 'thinking':
418
+ thinking_blocks.append(block.get('thinking', ''))
419
+ elif block_type == 'text':
420
+ text_blocks.append(block.get('text', ''))
421
+ elif hasattr(block, 'type'):
422
+ # Handle object format
423
+ if block.type == 'thinking':
424
+ thinking_blocks.append(getattr(block, 'thinking', ''))
425
+ elif block.type == 'text':
426
+ text_blocks.append(getattr(block, 'text', ''))
427
+
428
+ if thinking_blocks:
429
+ result['thinking'] = '\n\n'.join(thinking_blocks)
430
+ if text_blocks:
431
+ result['text'] = '\n\n'.join(text_blocks)
432
+
433
+ # Handle simple string content
434
+ elif isinstance(content, str):
435
+ result['text'] = content
436
+
437
+ return result
438
+
254
439
  def _run_async_in_sync_context(self, coro):
255
440
  """Run async coroutine from sync context.
256
441
 
@@ -403,6 +588,20 @@ class LLMNode(BaseTool):
403
588
  async def __perform_tool_calling(self, completion, messages, llm_client, config):
404
589
  # Handle iterative tool-calling and execution
405
590
  logger.info(f"__perform_tool_calling called with {len(completion.tool_calls) if hasattr(completion, 'tool_calls') else 0} tool calls")
591
+
592
+ # Check if this is a thinking model - they require special message handling
593
+ # model_name = getattr(llm_client, 'model_name', None) or getattr(llm_client, 'model', '')
594
+ # if _is_thinking_model(llm_client):
595
+ # logger.warning(
596
+ # f"⚠️ THINKING/REASONING MODEL DETECTED: '{model_name}'\n"
597
+ # f"Tool execution with thinking models may fail due to message format requirements.\n"
598
+ # f"Thinking models require 'thinking_blocks' to be preserved between turns, which this "
599
+ # f"framework cannot do.\n"
600
+ # f"Recommendation: Use standard model variants (e.g., claude-3-5-sonnet-20241022-v2:0) "
601
+ # f"instead of thinking/reasoning variants for tool calling.\n"
602
+ # f"See: https://docs.litellm.ai/docs/reasoning_content"
603
+ # )
604
+
406
605
  new_messages = messages + [completion]
407
606
  iteration = 0
408
607
 
@@ -511,6 +710,29 @@ class LLMNode(BaseTool):
511
710
  except Exception as e:
512
711
  error_str = str(e).lower()
513
712
 
713
+ # Check for thinking model message format errors
714
+ is_thinking_format_error = any(indicator in error_str for indicator in [
715
+ 'expected `thinking`',
716
+ 'expected `redacted_thinking`',
717
+ 'thinking block',
718
+ 'must start with a thinking block',
719
+ 'when `thinking` is enabled'
720
+ ])
721
+
722
+ # Check for non-recoverable errors that should fail immediately
723
+ # These indicate configuration or permission issues, not content size issues
724
+ is_non_recoverable = any(indicator in error_str for indicator in [
725
+ 'model identifier is invalid',
726
+ 'authentication',
727
+ 'unauthorized',
728
+ 'access denied',
729
+ 'permission denied',
730
+ 'invalid credentials',
731
+ 'api key',
732
+ 'quota exceeded',
733
+ 'rate limit'
734
+ ])
735
+
514
736
  # Check for context window / token limit errors
515
737
  is_context_error = any(indicator in error_str for indicator in [
516
738
  'context window', 'context_window', 'token limit', 'too long',
@@ -518,17 +740,76 @@ class LLMNode(BaseTool):
518
740
  'contextwindowexceedederror', 'max_tokens', 'content too large'
519
741
  ])
520
742
 
521
- # Check for Bedrock/Claude output limit errors
522
- # These often manifest as "model identifier is invalid" when output exceeds limits
743
+ # Check for Bedrock/Claude output limit errors (recoverable by truncation)
523
744
  is_output_limit_error = any(indicator in error_str for indicator in [
524
- 'model identifier is invalid',
525
- 'bedrockexception',
526
745
  'output token',
527
746
  'response too large',
528
747
  'max_tokens_to_sample',
529
- 'output_token_limit'
748
+ 'output_token_limit',
749
+ 'output exceeds'
530
750
  ])
531
751
 
752
+ # Handle thinking model format errors
753
+ if is_thinking_format_error:
754
+ model_info = getattr(llm_client, 'model_name', None) or getattr(llm_client, 'model', 'unknown')
755
+ logger.error(f"Thinking model message format error during tool execution iteration {iteration}")
756
+ logger.error(f"Model: {model_info}")
757
+ logger.error(f"Error details: {e}")
758
+
759
+ error_msg = (
760
+ f"⚠️ THINKING MODEL FORMAT ERROR\n\n"
761
+ f"The model '{model_info}' uses extended thinking and requires specific message formatting.\n\n"
762
+ f"**Issue**: When 'thinking' is enabled, assistant messages must start with thinking blocks "
763
+ f"before any tool_use blocks. This framework cannot preserve thinking_blocks during iterative "
764
+ f"tool execution.\n\n"
765
+ f"**Root Cause**: Anthropic's Messages API is stateless - clients must manually preserve and "
766
+ f"resend thinking_blocks with every tool response. LangChain's message abstraction doesn't "
767
+ f"include thinking_blocks, so they are lost between turns.\n\n"
768
+ f"**Solutions**:\n"
769
+ f"1. **Recommended**: Use non-thinking model variants:\n"
770
+ f" - claude-3-5-sonnet-20241022-v2:0 (instead of thinking variants)\n"
771
+ f" - anthropic.claude-3-5-sonnet-20241022-v2:0 (Bedrock)\n"
772
+ f"2. Disable extended thinking: Set reasoning_effort=None or remove thinking config\n"
773
+ f"3. Use LiteLLM directly with modify_params=True (handles thinking_blocks automatically)\n"
774
+ f"4. Avoid tool calling with thinking models (use for reasoning tasks only)\n\n"
775
+ f"**Technical Context**: {str(e)}\n\n"
776
+ f"References:\n"
777
+ f"- https://docs.claude.com/en/docs/build-with-claude/extended-thinking\n"
778
+ f"- https://docs.litellm.ai/docs/reasoning_content (See 'Tool Calling with thinking' section)"
779
+ )
780
+ new_messages.append(AIMessage(content=error_msg))
781
+ raise ValueError(error_msg)
782
+
783
+ # Handle non-recoverable errors immediately
784
+ if is_non_recoverable:
785
+ # Enhanced error logging with model information for better diagnostics
786
+ model_info = getattr(llm_client, 'model_name', None) or getattr(llm_client, 'model', 'unknown')
787
+ logger.error(f"Non-recoverable error during tool execution iteration {iteration}")
788
+ logger.error(f"Model: {model_info}")
789
+ logger.error(f"Error details: {e}")
790
+ logger.error(f"Error type: {type(e).__name__}")
791
+
792
+ # Provide detailed error message for debugging
793
+ error_details = []
794
+ error_details.append(f"Model configuration error: {str(e)}")
795
+ error_details.append(f"Model identifier: {model_info}")
796
+
797
+ # Check for common Bedrock model ID issues
798
+ if 'model identifier is invalid' in error_str:
799
+ error_details.append("\nPossible causes:")
800
+ error_details.append("1. Model not available in the configured AWS region")
801
+ error_details.append("2. Model not enabled in your AWS Bedrock account")
802
+ error_details.append("3. LiteLLM model group prefix not stripped (check for prefixes like '1_')")
803
+ error_details.append("4. Incorrect model version or typo in model name")
804
+ error_details.append("\nPlease verify:")
805
+ error_details.append("- AWS Bedrock console shows this model as available")
806
+ error_details.append("- LiteLLM router configuration is correct")
807
+ error_details.append("- Model ID doesn't contain unexpected prefixes")
808
+
809
+ error_msg = "\n".join(error_details)
810
+ new_messages.append(AIMessage(content=error_msg))
811
+ break
812
+
532
813
  if is_context_error or is_output_limit_error:
533
814
  error_type = "output limit" if is_output_limit_error else "context window"
534
815
  logger.warning(f"{error_type.title()} exceeded during tool execution iteration {iteration}")
@@ -595,9 +876,27 @@ class LLMNode(BaseTool):
595
876
  )
596
877
  new_messages[last_tool_msg_idx] = truncated_msg
597
878
 
598
- logger.info(f"Truncated large tool result from '{last_tool_name}' and continuing")
599
- # Continue to next iteration - the model will see the truncation message
600
- continue
879
+ logger.info(f"Truncated large tool result from '{last_tool_name}' and retrying LLM call")
880
+
881
+ # CRITICAL FIX: Call LLM again with truncated message to get fresh completion
882
+ # This prevents duplicate tool_call_ids that occur when we continue with
883
+ # the same current_completion that still has the original tool_calls
884
+ try:
885
+ current_completion = llm_client.invoke(new_messages, config=config)
886
+ new_messages.append(current_completion)
887
+
888
+ # Continue to process any new tool calls in the fresh completion
889
+ if hasattr(current_completion, 'tool_calls') and current_completion.tool_calls:
890
+ logger.info(f"LLM requested {len(current_completion.tool_calls)} more tool calls after truncation")
891
+ continue
892
+ else:
893
+ logger.info("LLM completed after truncation without requesting more tools")
894
+ break
895
+ except Exception as retry_error:
896
+ logger.error(f"Error retrying LLM after truncation: {retry_error}")
897
+ error_msg = f"Failed to retry after truncation: {str(retry_error)}"
898
+ new_messages.append(AIMessage(content=error_msg))
899
+ break
601
900
  else:
602
901
  # Couldn't find tool message, add error and break
603
902
  if is_output_limit_error:
@@ -313,7 +313,8 @@ class AlitaStreamlitCallback(BaseCallbackHandler):
313
313
  if self.debug:
314
314
  log.debug("on_llm_end(%s, %s)", response, kwargs)
315
315
  llm_run_id = str(run_id)
316
- if self.callback_state.get(llm_run_id):
316
+ # Check if callback_state exists and is not None before accessing
317
+ if self.callback_state is not None and self.callback_state.get(llm_run_id):
317
318
  status_widget = self.callback_state[llm_run_id]
318
319
  self._safe_streamlit_call(
319
320
  status_widget.update,
@@ -1,5 +1,8 @@
1
+ import base64
2
+ import logging
1
3
  import re
2
4
  from enum import Enum
5
+ from typing import Any
3
6
 
4
7
  # DEPRECATED: Tool names no longer use prefixes
5
8
  # Kept for backward compatibility only
@@ -32,3 +35,34 @@ def clean_node_str(s: str) -> str:
32
35
  """Cleans a node string by removing all non-alphanumeric characters except underscores and spaces."""
33
36
  cleaned_string = re.sub(r'[^\w\s]', '', s)
34
37
  return cleaned_string
38
+
39
+
40
+ def resolve_image_from_cache(client: Any, cached_image_id: str) -> bytes:
41
+ """
42
+ Resolve cached_image_id from client's image cache and return decoded binary data.
43
+
44
+ Args:
45
+ client: AlitaClient instance with _generated_images_cache attribute
46
+ cached_image_id: The cached image ID to resolve
47
+
48
+ Returns:
49
+ bytes: Decoded binary image data
50
+
51
+ Raises:
52
+ ValueError: If cached_image_id not found or decoding fails
53
+ """
54
+ cache = getattr(client, '_generated_images_cache', {})
55
+
56
+ if cached_image_id not in cache:
57
+ raise ValueError(f"Image reference '{cached_image_id}' not found. The image may have expired.")
58
+
59
+ cached_data = cache[cached_image_id]
60
+ base64_data = cached_data.get('base64_data', '')
61
+ logging.debug(f"Resolved cached_image_id '{cached_image_id}' from cache (length: {len(base64_data)} chars)")
62
+ # Decode base64 to binary data for image files
63
+ try:
64
+ binary_data = base64.b64decode(base64_data)
65
+ logging.debug(f"Decoded base64 to binary data ({len(binary_data)} bytes)")
66
+ return binary_data
67
+ except Exception as e:
68
+ raise ValueError(f"Failed to decode image data for '{cached_image_id}': {e}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alita_sdk
3
- Version: 0.3.522
3
+ Version: 0.3.532
4
4
  Summary: SDK for building langchain agents using resources from Alita
5
5
  Author-email: Artem Rozumenko <artyom.rozumenko@gmail.com>, Mikalai Biazruchka <mikalai_biazruchka@epam.com>, Roman Mitusov <roman_mitusov@epam.com>, Ivan Krakhmaliuk <lifedj27@gmail.com>, Artem Dubrovskiy <ad13box@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -31,7 +31,7 @@ Requires-Dist: langchain_core<0.4.0,>=0.3.76; extra == "runtime"
31
31
  Requires-Dist: langchain<0.4.0,>=0.3.22; extra == "runtime"
32
32
  Requires-Dist: langchain_community<0.4.0,>=0.3.7; extra == "runtime"
33
33
  Requires-Dist: langchain-openai<0.4.0,>=0.3.0; extra == "runtime"
34
- Requires-Dist: langchain-anthropic<0.4.0,>=0.3.10; extra == "runtime"
34
+ Requires-Dist: langchain-anthropic<0.4.0,>=0.3.20; extra == "runtime"
35
35
  Requires-Dist: anthropic>=0.57.0; extra == "runtime"
36
36
  Requires-Dist: langgraph<0.5,>=0.4.8; extra == "runtime"
37
37
  Requires-Dist: langgraph-prebuilt==0.5.2; extra == "runtime"
@@ -93,18 +93,18 @@ alita_sdk/configurations/zephyr_essential.py,sha256=TiZedsBlfIDroflipvoqxjJeEWPo
93
93
  alita_sdk/runtime/__init__.py,sha256=4W0UF-nl3QF2bvET5lnah4o24CoTwSoKXhuN0YnwvEE,828
94
94
  alita_sdk/runtime/clients/__init__.py,sha256=BdehU5GBztN1Qi1Wul0cqlU46FxUfMnI6Vq2Zd_oq1M,296
95
95
  alita_sdk/runtime/clients/artifact.py,sha256=7C1e9RtftqOJd3Mo5gNDnBuYg1Z9xTqjxmfdWeJH5Cc,4014
96
- alita_sdk/runtime/clients/client.py,sha256=8DGrto3RcURZQE2qUTFJ3boDat_YvedqmbTTHpRXyDg,52069
96
+ alita_sdk/runtime/clients/client.py,sha256=qsXM5wfQ6BaT44sH9wW3M4YkbFUK7WGLcWT8U0SHWGA,54748
97
97
  alita_sdk/runtime/clients/datasource.py,sha256=HAZovoQN9jBg0_-lIlGBQzb4FJdczPhkHehAiVG3Wx0,1020
98
98
  alita_sdk/runtime/clients/mcp_discovery.py,sha256=aFJ0wYQ8EAmXa9qLUusHZfQXkNec1wbgkqHdVeSFX-g,11697
99
99
  alita_sdk/runtime/clients/mcp_manager.py,sha256=DRbqiO761l7UgOdv_keHbD2g0oZodtPHejpArXYZIoE,9050
100
100
  alita_sdk/runtime/clients/prompt.py,sha256=li1RG9eBwgNK_Qf0qUaZ8QNTmsncFrAL2pv3kbxZRZg,1447
101
- alita_sdk/runtime/clients/sandbox_client.py,sha256=4GLoCFZXtTYKM3SFMJAfFO7QNE38c1V7DI1b88uOySY,17227
101
+ alita_sdk/runtime/clients/sandbox_client.py,sha256=z8emjrMe9hkEKOnr19qDxRzXG1ug-XJXBKQzGtjXEAs,15390
102
102
  alita_sdk/runtime/langchain/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
103
103
  alita_sdk/runtime/langchain/assistant.py,sha256=yVTosONjQYUHbzhtTWG53odpXbWCQLLe18oaqniqvx8,18447
104
104
  alita_sdk/runtime/langchain/chat_message_template.py,sha256=kPz8W2BG6IMyITFDA5oeb5BxVRkHEVZhuiGl4MBZKdc,2176
105
105
  alita_sdk/runtime/langchain/constants.py,sha256=tbVA-OPRDzEMspO9raOj_jb57Yt-TUYulG6FOXCmu78,17150
106
106
  alita_sdk/runtime/langchain/indexer.py,sha256=0ENHy5EOhThnAiYFc7QAsaTNp9rr8hDV_hTK8ahbatk,37592
107
- alita_sdk/runtime/langchain/langraph_agent.py,sha256=4rWJ6tQXIzVHgF9zzDL3kiR67rvBAxrJxpglJ6Z_2w0,59364
107
+ alita_sdk/runtime/langchain/langraph_agent.py,sha256=vQ5HPzgngNgZ6amco7PPx0Gn0TdaW8dvYVexPT68bB8,59363
108
108
  alita_sdk/runtime/langchain/mixedAgentParser.py,sha256=M256lvtsL3YtYflBCEp-rWKrKtcY1dJIyRGVv7KW9ME,2611
109
109
  alita_sdk/runtime/langchain/mixedAgentRenderes.py,sha256=asBtKqm88QhZRILditjYICwFVKF5KfO38hu2O-WrSWE,5964
110
110
  alita_sdk/runtime/langchain/store_manager.py,sha256=i8Fl11IXJhrBXq1F1ukEVln57B1IBe-tqSUvfUmBV4A,2218
@@ -170,14 +170,14 @@ alita_sdk/runtime/toolkits/vectorstore.py,sha256=H-HQsHhLm-vQWS3kvwkh-OHrOWKuylB
170
170
  alita_sdk/runtime/tools/__init__.py,sha256=Fx7iHqkzA90-KfjdcUUzMUI_7kDarjuTsSpSzOW2pN0,568
171
171
  alita_sdk/runtime/tools/agent.py,sha256=m98QxOHwnCRTT9j18Olbb5UPS8-ZGeQaGiUyZJSyFck,3162
172
172
  alita_sdk/runtime/tools/application.py,sha256=RCGe-mRfj8372gTFkEX2xBvcYhw7IKdU1t50lXaBPOY,3701
173
- alita_sdk/runtime/tools/artifact.py,sha256=1ZeqgHwDToFr98SX4aUWwvr5iVltdpWjooRd0euO7pg,21081
173
+ alita_sdk/runtime/tools/artifact.py,sha256=wZ6nPracF-SWkH52YtZWs2pePQavIDutUZ6BQpr5THU,23625
174
174
  alita_sdk/runtime/tools/datasource.py,sha256=pvbaSfI-ThQQnjHG-QhYNSTYRnZB0rYtZFpjCfpzxYI,2443
175
175
  alita_sdk/runtime/tools/echo.py,sha256=spw9eCweXzixJqHnZofHE1yWiSUa04L4VKycf3KCEaM,486
176
176
  alita_sdk/runtime/tools/function.py,sha256=HSMO1nBTRKMvWC_m0M8TOLGaZ2k_7ksPgLqzuRh6kV4,7083
177
177
  alita_sdk/runtime/tools/graph.py,sha256=7jImBBSEdP5Mjnn2keOiyUwdGDFhEXLUrgUiugO3mgA,3503
178
- alita_sdk/runtime/tools/image_generation.py,sha256=Kls9D_ke_SK7xmVr7I9SlQcAEBJc86gf66haN0qIj9k,7469
178
+ alita_sdk/runtime/tools/image_generation.py,sha256=waxxFIAgmh9-COcljL9uZ7e_s7EL9OWveUxYk0ulEUM,7855
179
179
  alita_sdk/runtime/tools/indexer_tool.py,sha256=whSLPevB4WD6dhh2JDXEivDmTvbjiMV1MrPl9cz5eLA,4375
180
- alita_sdk/runtime/tools/llm.py,sha256=jcOwqdYH8VIlnxn775bxYJ2haFUB5GbySUYD4WPlA5o,35700
180
+ alita_sdk/runtime/tools/llm.py,sha256=j2d7Pd0TUMYCGNSD7B440N7gpbzuJVVshftRlqe8Jgw,52911
181
181
  alita_sdk/runtime/tools/loop.py,sha256=uds0WhZvwMxDVFI6MZHrcmMle637cQfBNg682iLxoJA,8335
182
182
  alita_sdk/runtime/tools/loop_output.py,sha256=U4hO9PCQgWlXwOq6jdmCGbegtAxGAPXObSxZQ3z38uk,8069
183
183
  alita_sdk/runtime/tools/mcp_inspect_tool.py,sha256=38X8euaxDbEGjcfp6ElvExZalpZun6QEr6ZEW4nU5pQ,11496
@@ -193,7 +193,7 @@ alita_sdk/runtime/tools/vectorstore_base.py,sha256=GUO7Gxgy4GKTttsOrsPQTUb_I5EDe
193
193
  alita_sdk/runtime/tools/planning/__init__.py,sha256=15eWTtz4oMB5vnKsLEFPW7lVY7y1Fxk3edo2bNf0ooE,821
194
194
  alita_sdk/runtime/tools/planning/models.py,sha256=bcwfjEnDTqirTT9bjHEDF8o3UYIAD8IqiqrZsca8gfw,8816
195
195
  alita_sdk/runtime/tools/planning/wrapper.py,sha256=om-4f3qMzkqBcBmINQ469IykBubm_UwJ-WZsEchehto,22412
196
- alita_sdk/runtime/utils/AlitaCallback.py,sha256=t2KYBrLLjjpw79KWJJo9PLjwRj4PZWCc3PbQ-6FXfLk,12020
196
+ alita_sdk/runtime/utils/AlitaCallback.py,sha256=G-UU30P_Q9jiCr7eBZUDVRZ7Z6qlBPX5f0Cvt5tx528,12130
197
197
  alita_sdk/runtime/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
198
198
  alita_sdk/runtime/utils/constants.py,sha256=Xntx1b_uxUzT4clwqHA_U6K8y5bBqf_4lSQwXdcWrp4,13586
199
199
  alita_sdk/runtime/utils/evaluate.py,sha256=iM1P8gzBLHTuSCe85_Ng_h30m52hFuGuhNXJ7kB1tgI,1872
@@ -206,7 +206,7 @@ alita_sdk/runtime/utils/save_dataframe.py,sha256=i-E1wp-t4wb17Zq3nA3xYwgSILjoXNi
206
206
  alita_sdk/runtime/utils/streamlit.py,sha256=0TotNKnvMPHuwBdhMEpM5DhIedQQa1AUz9BlmXFBhAU,107179
207
207
  alita_sdk/runtime/utils/toolkit_runtime.py,sha256=MU63Fpxj0b5_r1IUUc0Q3-PN9VwL7rUxp2MRR4tmYR8,5136
208
208
  alita_sdk/runtime/utils/toolkit_utils.py,sha256=g1Au_nzJgde2NW732GACZGSIQOt7o0mjAbrRxG6GVwA,6579
209
- alita_sdk/runtime/utils/utils.py,sha256=6XkmoWjG_ZCIycU4qPhUSr9EdTrknhWbedbqE1fk1iU,1128
209
+ alita_sdk/runtime/utils/utils.py,sha256=d0RLiKfBnobC3PrEFPvZt3uUx3Jie2rR32Fp-3hkWCU,2380
210
210
  alita_sdk/tools/__init__.py,sha256=jzj502O3yO40cjs37Uzqcbd6fG3pFmoU1TLw1-j4_3M,13011
211
211
  alita_sdk/tools/base_indexer_toolkit.py,sha256=AXygnaQZFEEeq6kkJbWIzUF1i31HoWdL7yidcDy_iKk,34305
212
212
  alita_sdk/tools/code_indexer_toolkit.py,sha256=4uQHnv7sHzECmOWbeqoVPT4prt_hv91gYxWxvvRdOjg,9219
@@ -427,9 +427,9 @@ alita_sdk/tools/zephyr_scale/api_wrapper.py,sha256=kT0TbmMvuKhDUZc0i7KO18O38JM9S
427
427
  alita_sdk/tools/zephyr_squad/__init__.py,sha256=gZTEanHf9pRCiZaKobF4Wbm33wUxxXoIjOr544TcXas,2903
428
428
  alita_sdk/tools/zephyr_squad/api_wrapper.py,sha256=kmw_xol8YIYFplBLWTqP_VKPRhL_1ItDD0_vXTe_UuI,14906
429
429
  alita_sdk/tools/zephyr_squad/zephyr_squad_cloud_client.py,sha256=R371waHsms4sllHCbijKYs90C-9Yu0sSR3N4SUfQOgU,5066
430
- alita_sdk-0.3.522.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
431
- alita_sdk-0.3.522.dist-info/METADATA,sha256=wfMuqtLZNiFVBOEI0aUg4OV61OhqNGmnpp5j4LNKxo8,24266
432
- alita_sdk-0.3.522.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
433
- alita_sdk-0.3.522.dist-info/entry_points.txt,sha256=VijN0h4alp1WXm8tfS3P7vuGxN4a5RZqHjXAoEIBZnI,49
434
- alita_sdk-0.3.522.dist-info/top_level.txt,sha256=0vJYy5p_jK6AwVb1aqXr7Kgqgk3WDtQ6t5C-XI9zkmg,10
435
- alita_sdk-0.3.522.dist-info/RECORD,,
430
+ alita_sdk-0.3.532.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
431
+ alita_sdk-0.3.532.dist-info/METADATA,sha256=bAsh58nb6kyLbv_sI2BbOdxFiYfqGcmyhJrOpnlXo70,24266
432
+ alita_sdk-0.3.532.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
433
+ alita_sdk-0.3.532.dist-info/entry_points.txt,sha256=VijN0h4alp1WXm8tfS3P7vuGxN4a5RZqHjXAoEIBZnI,49
434
+ alita_sdk-0.3.532.dist-info/top_level.txt,sha256=0vJYy5p_jK6AwVb1aqXr7Kgqgk3WDtQ6t5C-XI9zkmg,10
435
+ alita_sdk-0.3.532.dist-info/RECORD,,