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.
- alita_sdk/runtime/clients/client.py +314 -11
- alita_sdk/runtime/langchain/assistant.py +22 -21
- alita_sdk/runtime/langchain/interfaces/llm_processor.py +1 -4
- alita_sdk/runtime/langchain/langraph_agent.py +6 -1
- alita_sdk/runtime/langchain/store_manager.py +4 -4
- alita_sdk/runtime/toolkits/application.py +5 -10
- alita_sdk/runtime/toolkits/tools.py +11 -21
- alita_sdk/runtime/tools/vectorstore.py +25 -11
- alita_sdk/runtime/utils/streamlit.py +505 -222
- alita_sdk/runtime/utils/toolkit_runtime.py +147 -0
- alita_sdk/runtime/utils/toolkit_utils.py +157 -0
- alita_sdk/runtime/utils/utils.py +5 -0
- alita_sdk/tools/__init__.py +2 -0
- alita_sdk/tools/ado/repos/repos_wrapper.py +20 -13
- alita_sdk/tools/bitbucket/api_wrapper.py +5 -5
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +54 -29
- alita_sdk/tools/elitea_base.py +9 -4
- alita_sdk/tools/gitlab/__init__.py +22 -10
- alita_sdk/tools/gitlab/api_wrapper.py +278 -253
- alita_sdk/tools/gitlab/tools.py +354 -376
- alita_sdk/tools/llm/llm_utils.py +0 -6
- alita_sdk/tools/memory/__init__.py +54 -10
- alita_sdk/tools/openapi/__init__.py +14 -3
- alita_sdk/tools/sharepoint/__init__.py +2 -1
- alita_sdk/tools/sharepoint/api_wrapper.py +11 -3
- alita_sdk/tools/testrail/api_wrapper.py +39 -16
- alita_sdk/tools/utils/content_parser.py +77 -13
- {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/METADATA +1 -1
- {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/RECORD +32 -40
- alita_sdk/community/analysis/__init__.py +0 -0
- alita_sdk/community/analysis/ado_analyse/__init__.py +0 -103
- alita_sdk/community/analysis/ado_analyse/api_wrapper.py +0 -261
- alita_sdk/community/analysis/github_analyse/__init__.py +0 -98
- alita_sdk/community/analysis/github_analyse/api_wrapper.py +0 -166
- alita_sdk/community/analysis/gitlab_analyse/__init__.py +0 -110
- alita_sdk/community/analysis/gitlab_analyse/api_wrapper.py +0 -172
- alita_sdk/community/analysis/jira_analyse/__init__.py +0 -141
- alita_sdk/community/analysis/jira_analyse/api_wrapper.py +0 -252
- alita_sdk/runtime/llms/alita.py +0 -259
- {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.205.dist-info → alita_sdk-0.3.207.dist-info}/top_level.txt +0 -0
alita_sdk/tools/llm/llm_utils.py
CHANGED
@@ -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
|
-
|
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(
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
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
|
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
|
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,
|
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
|
-
) ->
|
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
|
-
|
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
|
-
|
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:
|
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
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
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
|
-
|
660
|
+
page_content = self._client.get(endpoint=f"get_attachment/{attachment['id']}")
|
639
661
|
# TODO: add support for other file types
|
640
|
-
|
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
|
-
|
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 +=
|
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 =
|
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.
|
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
|