alita-sdk 0.3.205__py3-none-any.whl → 0.3.207__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 (42) hide show
  1. alita_sdk/runtime/clients/client.py +314 -11
  2. alita_sdk/runtime/langchain/assistant.py +22 -21
  3. alita_sdk/runtime/langchain/interfaces/llm_processor.py +1 -4
  4. alita_sdk/runtime/langchain/langraph_agent.py +6 -1
  5. alita_sdk/runtime/langchain/store_manager.py +4 -4
  6. alita_sdk/runtime/toolkits/application.py +5 -10
  7. alita_sdk/runtime/toolkits/tools.py +11 -21
  8. alita_sdk/runtime/tools/vectorstore.py +25 -11
  9. alita_sdk/runtime/utils/streamlit.py +505 -222
  10. alita_sdk/runtime/utils/toolkit_runtime.py +147 -0
  11. alita_sdk/runtime/utils/toolkit_utils.py +157 -0
  12. alita_sdk/runtime/utils/utils.py +5 -0
  13. alita_sdk/tools/__init__.py +2 -0
  14. alita_sdk/tools/ado/repos/repos_wrapper.py +20 -13
  15. alita_sdk/tools/bitbucket/api_wrapper.py +5 -5
  16. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +54 -29
  17. alita_sdk/tools/elitea_base.py +9 -4
  18. alita_sdk/tools/gitlab/__init__.py +22 -10
  19. alita_sdk/tools/gitlab/api_wrapper.py +278 -253
  20. alita_sdk/tools/gitlab/tools.py +354 -376
  21. alita_sdk/tools/llm/llm_utils.py +0 -6
  22. alita_sdk/tools/memory/__init__.py +54 -10
  23. alita_sdk/tools/openapi/__init__.py +14 -3
  24. alita_sdk/tools/sharepoint/__init__.py +2 -1
  25. alita_sdk/tools/sharepoint/api_wrapper.py +11 -3
  26. alita_sdk/tools/testrail/api_wrapper.py +39 -16
  27. alita_sdk/tools/utils/content_parser.py +77 -13
  28. {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/METADATA +1 -1
  29. {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/RECORD +32 -40
  30. alita_sdk/community/analysis/__init__.py +0 -0
  31. alita_sdk/community/analysis/ado_analyse/__init__.py +0 -103
  32. alita_sdk/community/analysis/ado_analyse/api_wrapper.py +0 -261
  33. alita_sdk/community/analysis/github_analyse/__init__.py +0 -98
  34. alita_sdk/community/analysis/github_analyse/api_wrapper.py +0 -166
  35. alita_sdk/community/analysis/gitlab_analyse/__init__.py +0 -110
  36. alita_sdk/community/analysis/gitlab_analyse/api_wrapper.py +0 -172
  37. alita_sdk/community/analysis/jira_analyse/__init__.py +0 -141
  38. alita_sdk/community/analysis/jira_analyse/api_wrapper.py +0 -252
  39. alita_sdk/runtime/llms/alita.py +0 -259
  40. {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/WHEEL +0 -0
  41. {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/licenses/LICENSE +0 -0
  42. {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/top_level.txt +0 -0
@@ -10,12 +10,6 @@ def get_model(model_type: str, model_params: dict):
10
10
  return None
11
11
  if model_type in llms:
12
12
  return get_llm(model_type)(**model_params)
13
- elif model_type == "Alita":
14
- try:
15
- from alita_sdk.llms.alita import AlitaChatModel
16
- except ImportError:
17
- raise RuntimeError("Alita model not found")
18
- return AlitaChatModel(**model_params)
19
13
  elif model_type in chat_models:
20
14
  model = getattr(__import__("langchain_community.chat_models", fromlist=[model_type]), model_type)
21
15
  return model(**model_params)
@@ -1,7 +1,7 @@
1
1
  from typing import Optional, List
2
2
 
3
3
  from langchain_core.tools import BaseToolkit, BaseTool
4
- from langgraph.store.postgres import PostgresStore
4
+
5
5
  try:
6
6
  from langmem import create_manage_memory_tool, create_search_memory_tool
7
7
  except ImportError:
@@ -15,13 +15,37 @@ from pydantic import create_model, BaseModel, ConfigDict, Field, SecretStr
15
15
 
16
16
  name = "memory"
17
17
 
18
- def get_tools(tool):
19
- return MemoryToolkit().get_toolkit(
20
- namespace=tool['settings'].get('namespace', str(tool['id'])),
21
- username=tool['settings'].get('username', ''),
22
- store=tool['settings'].get('store', None),
23
- toolkit_name=tool.get('toolkit_name', '')
24
- ).get_tools()
18
+ def get_tools(tools_list: list, alita_client, llm, memory_store=None):
19
+ """
20
+ Get memory tools for the provided tool configurations.
21
+
22
+ Args:
23
+ tools_list: List of tool configurations
24
+ alita_client: Alita client instance
25
+ llm: LLM client instance
26
+ memory_store: Optional memory store instance
27
+
28
+ Returns:
29
+ List of memory tools
30
+ """
31
+ all_tools = []
32
+
33
+ for tool in tools_list:
34
+ if tool.get('type') == 'memory' or tool.get('toolkit_name') == 'memory':
35
+ try:
36
+ toolkit_instance = MemoryToolkit().get_toolkit(
37
+ namespace=tool['settings'].get('namespace', str(tool['id'])),
38
+ username=tool['settings'].get('username', ''),
39
+ store=tool['settings'].get('store', memory_store),
40
+ toolkit_name=tool.get('toolkit_name', '')
41
+ )
42
+ all_tools.extend(toolkit_instance.get_tools())
43
+ except Exception as e:
44
+ print(f"DEBUG: Error in memory toolkit get_tools: {e}")
45
+ print(f"DEBUG: Tool config: {tool}")
46
+ raise
47
+
48
+ return all_tools
25
49
 
26
50
  class MemoryToolkit(BaseToolkit):
27
51
  tools: List[BaseTool] = []
@@ -30,7 +54,7 @@ class MemoryToolkit(BaseToolkit):
30
54
  @staticmethod
31
55
  def toolkit_config_schema() -> BaseModel:
32
56
  return create_model(
33
- name,
57
+ 'MemoryConfig',
34
58
  namespace=(str, Field(description="Memory namespace", json_schema_extra={'toolkit_name': True})),
35
59
  username=(Optional[str], Field(description="Username", default='Tester', json_schema_extra={'hidden': True})),
36
60
  connection_string=(Optional[SecretStr], Field(description="Connection string for vectorstore",
@@ -48,7 +72,27 @@ class MemoryToolkit(BaseToolkit):
48
72
  )
49
73
 
50
74
  @classmethod
51
- def get_toolkit(cls, namespace: str, store: PostgresStore, **kwargs):
75
+ def get_toolkit(cls, namespace: str, store=None, **kwargs):
76
+ """
77
+ Get toolkit with memory tools.
78
+
79
+ Args:
80
+ namespace: Memory namespace
81
+ store: PostgresStore instance (imported dynamically)
82
+ **kwargs: Additional arguments
83
+ """
84
+ try:
85
+ from langgraph.store.postgres import PostgresStore
86
+ except ImportError:
87
+ raise ImportError(
88
+ "PostgreSQL dependencies (psycopg) are required for MemoryToolkit. "
89
+ "Install with: pip install psycopg[binary]"
90
+ )
91
+
92
+ # Validate store type
93
+ if store is not None and not isinstance(store, PostgresStore):
94
+ raise TypeError(f"Expected PostgresStore, got {type(store)}")
95
+
52
96
  return cls(tools=[
53
97
  create_manage_memory_tool(namespace=namespace, store=store),
54
98
  create_search_memory_tool(namespace=namespace, store=store)
@@ -1,12 +1,15 @@
1
1
  import json
2
2
  import re
3
+ import logging
3
4
  from typing import List, Any, Optional, Dict
4
- from langchain_core.tools import BaseTool, BaseToolkit
5
+ from langchain_core.tools import BaseTool, BaseToolkit, ToolException
5
6
  from requests_openapi import Operation, Client, Server
6
7
 
7
8
  from pydantic import create_model, Field
8
9
  from functools import partial
9
10
 
11
+ logger = logging.getLogger(__name__)
12
+
10
13
  name = "openapi"
11
14
 
12
15
  def get_tools(tool):
@@ -105,11 +108,19 @@ class AlitaOpenAPIToolkit(BaseToolkit):
105
108
  c.requestor.headers.update(headers)
106
109
  tools = []
107
110
  for i in tools_set:
111
+
108
112
  try:
113
+ if not i:
114
+ raise ToolException("Operation id is missing for some of declared operations.")
109
115
  tool = c.operations[i]
116
+ if not isinstance(tool, Operation):
117
+ raise ToolException(f"Operation {i} is not an instance of Operation class.")
110
118
  tools.append(create_api_tool(i, tool))
111
- except KeyError:
112
- ...
119
+ except ToolException:
120
+ raise
121
+ except Exception as e:
122
+ logger.warning(f"Tool {i} not found in OpenAPI spec.")
123
+ raise ToolException(f"Cannot create API tool ({i}): \n{e}.")
113
124
  return cls(request_session=c, tools=tools)
114
125
 
115
126
  def get_tools(self):
@@ -14,7 +14,8 @@ def get_tools(tool):
14
14
  site_url=tool['settings'].get('site_url', None),
15
15
  client_id=tool['settings'].get('client_id', None),
16
16
  client_secret=tool['settings'].get('client_secret', None),
17
- toolkit_name=tool.get('toolkit_name'))
17
+ toolkit_name=tool.get('toolkit_name'),
18
+ llm=tool['settings'].get('llm'))
18
19
  .get_tools())
19
20
 
20
21
 
@@ -32,7 +32,10 @@ ReadDocument = create_model(
32
32
  "ReadDocument",
33
33
  path=(str, Field(description="Contains the server-relative path of a document for reading.")),
34
34
  is_capture_image=(Optional[bool], Field(description="Determines is pictures in the document should be recognized.", default=False)),
35
- page_number=(Optional[int], Field(description="Specifies which page to read. If it is None, then full document will be read.", default=None))
35
+ page_number=(Optional[int], Field(description="Specifies which page to read. If it is None, then full document will be read.", default=None)),
36
+ sheet_name=(Optional[str], Field(
37
+ description="Specifies which sheet to read. If it is None, then full document will be read.",
38
+ default=None))
36
39
  )
37
40
 
38
41
  indexData = create_model(
@@ -139,7 +142,7 @@ class SharepointApiWrapper(BaseVectorStoreToolApiWrapper):
139
142
  logging.error(f"Failed to load files from sharepoint: {e}")
140
143
  return ToolException("Can not get files. Please, double check folder name and read permissions.")
141
144
 
142
- def read_file(self, path, is_capture_image: bool = False, page_number: int = None):
145
+ def read_file(self, path, is_capture_image: bool = False, page_number: int = None, sheet_name: str=None):
143
146
  """ Reads file located at the specified server-relative path. """
144
147
  try:
145
148
  file = self._client.web.get_file_by_server_relative_path(path)
@@ -150,7 +153,12 @@ class SharepointApiWrapper(BaseVectorStoreToolApiWrapper):
150
153
  except Exception as e:
151
154
  logging.error(f"Failed to load file from SharePoint: {e}. Path: {path}. Please, double check file name and path.")
152
155
  return ToolException("File not found. Please, check file name and path.")
153
- return parse_file_content(file.name, file_content, is_capture_image, page_number)
156
+ return parse_file_content(file_name=file.name,
157
+ file_content=file_content,
158
+ is_capture_image=is_capture_image,
159
+ page_number=page_number,
160
+ sheet_name=sheet_name,
161
+ llm=self.llm)
154
162
 
155
163
  def _base_loader(self) -> List[Document]:
156
164
  try:
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  import logging
3
- from typing import Dict, List, Optional, Union, Any
3
+ from typing import Dict, List, Optional, Union, Any, Generator
4
4
 
5
5
  import pandas as pd
6
6
  from langchain_core.tools import ToolException
@@ -9,6 +9,9 @@ from pydantic.fields import Field, PrivateAttr
9
9
  from testrail_api import StatusCodeError, TestRailAPI
10
10
  from ..elitea_base import BaseVectorStoreToolApiWrapper, BaseIndexParams
11
11
  from langchain_core.documents import Document
12
+
13
+ from ...runtime.utils.utils import IndexerKeywords
14
+
12
15
  try:
13
16
  from alita_sdk.runtime.langchain.interfaces.llm_processor import get_embeddings
14
17
  except ImportError:
@@ -551,7 +554,7 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
551
554
  suite_id: Optional[str] = None,
552
555
  section_id: Optional[int] = None,
553
556
  title_keyword: Optional[str] = None
554
- ) -> List[Document]:
557
+ ) -> Generator[Document, None, None]:
555
558
  try:
556
559
  if suite_id:
557
560
  resp = self._client.cases.get_cases(project_id=project_id, suite_id=int(suite_id))
@@ -567,16 +570,22 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
567
570
  if title_keyword is not None:
568
571
  cases = [case for case in cases if title_keyword.lower() in case.get('title', '').lower()]
569
572
 
570
- docs: List[Document] = []
571
573
  for case in cases:
572
- docs.append(Document(page_content=json.dumps(case), metadata={
574
+ yield Document(page_content=json.dumps(case), metadata={
573
575
  'project_id': project_id,
574
576
  'title': case.get('title', ''),
575
577
  'suite_id': suite_id or case.get('suite_id', ''),
576
578
  'id': str(case.get('id', '')),
577
- 'updated_on': case.get('updated_on', ''),
578
- }))
579
- return docs
579
+ 'updated_on': case.get('updated_on') or -1,
580
+ 'labels': [lbl['title'] for lbl in case.get('labels', [])],
581
+ 'type': case.get('type_id') or -1,
582
+ 'priority': case.get('priority_id') or -1,
583
+ 'milestone': case.get('milestone_id') or -1,
584
+ 'estimate': case.get('estimate') or '',
585
+ 'automation_type': case.get('custom_automation_type') or -1,
586
+ 'section_id': case.get('section_id') or -1,
587
+ 'entity_type': 'test_case',
588
+ })
580
589
 
581
590
  def index_data(
582
591
  self,
@@ -594,7 +603,7 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
594
603
  vs = self._init_vector_store(collection_suffix, embeddings=embedding)
595
604
  return vs.index_documents(docs, progress_step=progress_step, clean_index=clean_index)
596
605
 
597
- def _process_document(self, document: Document) -> Document:
606
+ def _process_document(self, document: Document) -> Generator[Document, None, None]:
598
607
  """
599
608
  Process an existing base document to extract relevant metadata for full document preparation.
600
609
  Used for late processing of documents after we ensure that the document has to be indexed to avoid
@@ -604,7 +613,7 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
604
613
  document (Document): The base document to process.
605
614
 
606
615
  Returns:
607
- Document: The processed document with metadata.
616
+ Generator[Document, None, None]: A generator yielding processed Document objects with metadata.
608
617
  """
609
618
  try:
610
619
  # get base data from the document required to extract attachments and other metadata
@@ -613,14 +622,25 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
613
622
 
614
623
  # get a list of attachments for the case
615
624
  attachments = self._client.attachments.get_attachments_for_case_bulk(case_id=case_id)
616
- attachments_data = {}
617
625
 
618
626
  # process each attachment to extract its content
619
627
  for attachment in attachments:
620
- attachments_data[attachment['filename']] = self._process_attachment(attachment)
621
- base_data['attachments'] = attachments_data
622
- document.page_content = json.dumps(base_data)
623
- return document
628
+ attachment_id = attachment['id']
629
+ # add attachment id to metadata of parent
630
+ document.metadata.setdefault(IndexerKeywords.DEPENDENT_DOCS.value, []).append(attachment_id)
631
+
632
+ # TODO: pass it to chunkers
633
+ yield Document(page_content=self._process_attachment(attachment),
634
+ metadata={
635
+ 'project_id': base_data.get('project_id', ''),
636
+ IndexerKeywords.PARENT.value: case_id,
637
+ 'id': attachment_id,
638
+ 'filename': attachment['filename'],
639
+ 'filetype': attachment['filetype'],
640
+ 'created_on': attachment['created_on'],
641
+ 'entity_type': 'test_case_attachment',
642
+ 'is_image': attachment['is_image'],
643
+ })
624
644
  except json.JSONDecodeError as e:
625
645
  raise ToolException(f"Failed to decode JSON from document: {e}")
626
646
 
@@ -634,10 +654,13 @@ class TestrailAPIWrapper(BaseVectorStoreToolApiWrapper):
634
654
  Returns:
635
655
  str: string description of the attachment.
636
656
  """
657
+
658
+ page_content = "This filetype is not supported."
637
659
  if attachment['filetype'] == 'txt' :
638
- return self._client.get(endpoint=f"get_attachment/{attachment['id']}")
660
+ page_content = self._client.get(endpoint=f"get_attachment/{attachment['id']}")
639
661
  # TODO: add support for other file types
640
- return "This filetype is not supported."
662
+ # use utility to handle different types (tools/utils)
663
+ return page_content
641
664
 
642
665
  def _to_markup(self, data: List[Dict], output_format: str) -> str:
643
666
  """
@@ -1,3 +1,5 @@
1
+ import re
2
+
1
3
  from docx import Document
2
4
  from io import BytesIO
3
5
  import pandas as pd
@@ -8,8 +10,53 @@ import io
8
10
  import pymupdf
9
11
  from langchain_core.tools import ToolException
10
12
  from transformers import BlipProcessor, BlipForConditionalGeneration
13
+ from langchain_core.messages import HumanMessage
14
+
15
+ from ...runtime.langchain.tools.utils import bytes_to_base64
16
+
17
+ image_processing_prompt='''
18
+ You are an AI model designed for analyzing images. Your task is to accurately describe the content of the given image. Depending on the type of image, follow these specific instructions:
19
+
20
+ If the image is a diagram (e.g., chart, table, pie chart, bar graph, etc.):
21
+
22
+ Identify the type of diagram.
23
+ Extract all numerical values, labels, axis titles, headings, legends, and any other textual elements.
24
+ Describe the relationships or trends between the data, if visible.
25
+ If the image is a screenshot:
26
+
27
+ Describe what is shown in the screenshot.
28
+ If it is a software interface, identify the program or website name (if visible).
29
+ List the key interface elements (e.g., buttons, menus, text fields, images, headers).
30
+ If there is text, extract it.
31
+ If the screenshot shows a conversation, describe the participants, the content of the messages, and timestamps (if visible).
32
+ If the image is a photograph:
33
+
34
+ Describe the main objects, people, animals, or elements visible in the photo.
35
+ Specify the setting (e.g., indoors, outdoors, nature, urban area).
36
+ If possible, identify the actions being performed by people or objects in the photo.
37
+ If the image is an illustration or drawing:
11
38
 
12
- def parse_file_content(file_name, file_content, is_capture_image: bool = False, page_number: int = None, sheet_name: str = None):
39
+ Describe the style of the illustration (e.g., realistic, cartoonish, abstract).
40
+ Identify the main elements, their colors, and the composition of the image.
41
+ If there is text, extract it.
42
+ If the image contains text:
43
+
44
+ Extract all text from the image.
45
+ Specify the format of the text (e.g., heading, paragraph, list).
46
+ If the image is a mixed type (e.g., a diagram within a screenshot):
47
+
48
+ Identify all types of content present in the image.
49
+ Perform an analysis for each type of content separately, following the relevant instructions above.
50
+ If the image does not fit into any of the above categories:
51
+
52
+ Provide a detailed description of what is shown in the image.
53
+ Highlight any visible details that could help in understanding the image.
54
+ Be as precise and thorough as possible in your responses. If something is unclear or illegible, state that explicitly.
55
+ '''
56
+
57
+ IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'webp', 'svg']
58
+
59
+ def parse_file_content(file_name, file_content, is_capture_image: bool = False, page_number: int = None, sheet_name: str = None, llm=None):
13
60
  if file_name.endswith('.txt'):
14
61
  return parse_txt(file_content)
15
62
  elif file_name.endswith('.docx'):
@@ -17,9 +64,12 @@ def parse_file_content(file_name, file_content, is_capture_image: bool = False,
17
64
  elif file_name.endswith('.xlsx') or file_name.endswith('.xls'):
18
65
  return parse_excel(file_content, sheet_name)
19
66
  elif file_name.endswith('.pdf'):
20
- return parse_pdf(file_content, page_number, is_capture_image)
67
+ return parse_pdf(file_content, page_number, is_capture_image, llm)
21
68
  elif file_name.endswith('.pptx'):
22
- return parse_pptx(file_content, page_number, is_capture_image)
69
+ return parse_pptx(file_content, page_number, is_capture_image, llm)
70
+ elif any(file_name.lower().endswith(f".{ext}") for ext in IMAGE_EXTENSIONS):
71
+ match = re.search(r'\.([a-zA-Z0-9]+)$', file_name)
72
+ return __perform_llm_prediction_for_image(llm, file_content, match.group(1), image_processing_prompt)
23
73
  else:
24
74
  return ToolException(
25
75
  "Not supported type of files entered. Supported types are TXT, DOCX, PDF, PPTX, XLSX and XLS only.")
@@ -49,28 +99,28 @@ def parse_sheet(excel_file, sheet_name):
49
99
  df.fillna('', inplace=True)
50
100
  return df.to_string()
51
101
 
52
- def parse_pdf(file_content, page_number, is_capture_image):
102
+ def parse_pdf(file_content, page_number, is_capture_image, llm):
53
103
  with pymupdf.open(stream=file_content, filetype="pdf") as report:
54
104
  text_content = ''
55
105
  if page_number is not None:
56
106
  page = report.load_page(page_number - 1)
57
- text_content += read_pdf_page(report, page, page_number, is_capture_image)
107
+ text_content += read_pdf_page(report, page, page_number, is_capture_image, llm)
58
108
  else:
59
109
  for index, page in enumerate(report, start=1):
60
- text_content += read_pdf_page(report, page, index, is_capture_image)
110
+ text_content += read_pdf_page(report, page, index, is_capture_image, llm)
61
111
  return text_content
62
112
 
63
- def parse_pptx(file_content, page_number, is_capture_image):
113
+ def parse_pptx(file_content, page_number, is_capture_image, llm=None):
64
114
  prs = Presentation(io.BytesIO(file_content))
65
115
  text_content = ''
66
116
  if page_number is not None:
67
- text_content += read_pptx_slide(prs.slides[page_number - 1], page_number, is_capture_image)
117
+ text_content += read_pptx_slide(prs.slides[page_number - 1], page_number, is_capture_image, llm)
68
118
  else:
69
119
  for index, slide in enumerate(prs.slides, start=1):
70
- text_content += read_pptx_slide(slide, index, is_capture_image)
120
+ text_content += read_pptx_slide(slide, index, is_capture_image, llm)
71
121
  return text_content
72
122
 
73
- def read_pdf_page(report, page, index, is_capture_images):
123
+ def read_pdf_page(report, page, index, is_capture_images, llm=None):
74
124
  text_content = f'Page: {index}\n'
75
125
  text_content += page.get_text()
76
126
  if is_capture_images:
@@ -79,7 +129,7 @@ def read_pdf_page(report, page, index, is_capture_images):
79
129
  xref = img[0]
80
130
  base_image = report.extract_image(xref)
81
131
  img_bytes = base_image["image"]
82
- text_content += describe_image(Image.open(io.BytesIO(img_bytes)).convert("RGB"))
132
+ text_content += __perform_llm_prediction_for_image(llm, img_bytes)
83
133
  return text_content
84
134
 
85
135
  def read_docx_from_bytes(file_content):
@@ -94,14 +144,14 @@ def read_docx_from_bytes(file_content):
94
144
  print(f"Error reading .docx from bytes: {e}")
95
145
  return ""
96
146
 
97
- def read_pptx_slide(slide, index, is_capture_image):
147
+ def read_pptx_slide(slide, index, is_capture_image, llm):
98
148
  text_content = f'Slide: {index}\n'
99
149
  for shape in slide.shapes:
100
150
  if hasattr(shape, "text"):
101
151
  text_content += shape.text + "\n"
102
152
  elif is_capture_image and shape.shape_type == MSO_SHAPE_TYPE.PICTURE:
103
153
  try:
104
- caption = describe_image(Image.open(io.BytesIO(shape.image.blob)).convert("RGB"))
154
+ caption = __perform_llm_prediction_for_image(llm, shape.image.blob)
105
155
  except:
106
156
  caption = "\n[Picture: unknown]\n"
107
157
  text_content += caption
@@ -113,3 +163,17 @@ def describe_image(image):
113
163
  inputs = processor(image, return_tensors="pt")
114
164
  out = model.generate(**inputs)
115
165
  return "\n[Picture: " + processor.decode(out[0], skip_special_tokens=True) + "]\n"
166
+
167
+ def __perform_llm_prediction_for_image(llm, image: bytes, image_format='png', prompt=image_processing_prompt) -> str:
168
+ base64_string = bytes_to_base64(image)
169
+ result = llm.invoke([
170
+ HumanMessage(
171
+ content=[
172
+ {"type": "text", "text": prompt},
173
+ {
174
+ "type": "image_url",
175
+ "image_url": {"url": f"data:image/{image_format};base64,{base64_string}"},
176
+ },
177
+ ])
178
+ ])
179
+ return f"\n[Image description: {result.content}]\n"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alita_sdk
3
- Version: 0.3.205
3
+ Version: 0.3.207
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 <lifedjik@gmail.com>, Artem Dubrovskiy <ad13box@gmail.com>
6
6
  License-Expression: Apache-2.0