From deeeb5924eb09f69bcca9b052ef23ce94ad3510e Mon Sep 17 00:00:00 2001 From: Abdul Basit Anees Date: Mon, 16 Jun 2025 13:13:22 +0000 Subject: [PATCH 1/8] add file parsing --- aixplain/modules/model/index_model.py | 39 ++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/aixplain/modules/model/index_model.py b/aixplain/modules/model/index_model.py index 04ed3df1..ce3acd35 100644 --- a/aixplain/modules/model/index_model.py +++ b/aixplain/modules/model/index_model.py @@ -1,3 +1,5 @@ +import os +from uuid import uuid4 from aixplain.enums import EmbeddingModel, Function, Supplier, ResponseStatus, StorageType, FunctionType from aixplain.modules.model import Model from aixplain.utils import config @@ -152,11 +154,11 @@ def search(self, query: str, top_k: int = 10, filters: List[IndexFilter] = []) - } return self.run(data=data) - def upsert(self, documents: List[Record], splitter: Optional[Splitter] = None) -> ModelResponse: + def upsert(self, documents: List[Record] | str, splitter: Optional[Splitter] = None) -> ModelResponse: """Upsert documents into the index Args: - documents (List[Record]): List of documents to be upserted + documents (List[Record] | str): List of documents to be upserted or a file path splitter (Splitter, optional): Splitter to be applied. Defaults to None. Returns: @@ -165,8 +167,12 @@ def upsert(self, documents: List[Record], splitter: Optional[Splitter] = None) - Examples: index_model.upsert([Record(value="Hello, world!", value_type="text", uri="", id="1", attributes={})]) index_model.upsert([Record(value="Hello, world!", value_type="text", uri="", id="1", attributes={})], splitter=Splitter(split=True, split_by=SplittingOptions.WORD, split_length=1, split_overlap=0)) + index_model.upsert("my_file.pdf") + index_model.upsert("my_file.pdf", splitter=Splitter(split=True, split_by=SplittingOptions.WORD, split_length=400, split_overlap=50)) Splitter in the above example is optional and can be used to split the documents into smaller chunks. """ + if isinstance(documents, str): + documents = [self.prepare_record_from_file(documents)] # Validate documents for doc in documents: doc.validate() @@ -193,7 +199,7 @@ def upsert(self, documents: List[Record], splitter: Optional[Splitter] = None) - return response raise Exception(f"Failed to upsert documents: {response.error_message}") - def count(self) -> float: + def count(self) -> int: data = {"action": "count", "data": ""} response = self.run(data=data) if response.status == "SUCCESS": @@ -243,3 +249,30 @@ def delete_record(self, record_id: Text) -> ModelResponse: if response.status == "SUCCESS": return response raise Exception(f"Failed to delete record: {response.error_message}") + + def prepare_record_from_file(self, file_path: str, file_id: str = None) -> Record: + """ + Prepare a record from a file. + """ + response = self.parse_file(file_path) + file_name = file_path.split("/")[-1] + if not file_id: + file_id = file_name + "_" + str(uuid4()) + return Record(value=response.data, value_type="text", id=file_id, attributes={"file_name": file_name}) + + @staticmethod + def parse_file(file_path: str) -> ModelResponse: + """ + Parse a file using the Docling model. + """ + if not os.path.exists(file_path): + raise Exception(f"File {file_path} does not exist") + try: + from aixplain.factories import ModelFactory + + docling_model_id = "677bee6c6eb56331f9192a91" + model = ModelFactory.get(docling_model_id) + response = model.run(file_path) + return response + except Exception as e: + raise Exception(f"Failed to parse file: {e}") From 35f885f314b6c8263c8a89d18cbac6e840472ba3 Mon Sep 17 00:00:00 2001 From: Abdul Basit Anees Date: Mon, 16 Jun 2025 13:18:37 +0000 Subject: [PATCH 2/8] add unit tests --- tests/unit/index_model_test.py | 57 ++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/unit/index_model_test.py b/tests/unit/index_model_test.py index 8d5c3a74..5f5e13eb 100644 --- a/tests/unit/index_model_test.py +++ b/tests/unit/index_model_test.py @@ -252,3 +252,60 @@ def test_index_model_splitter(): assert splitter.split_by == "sentence" assert splitter.split_length == 100 assert splitter.split_overlap == 0 + + +def test_parse_file_success(mocker): + mock_response = {"status": "SUCCESS", "data": "parsed content"} + mock_model = mocker.Mock() + mock_model.run.return_value = ModelResponse(status=ResponseStatus.SUCCESS, data="parsed content") + + mocker.patch("aixplain.factories.ModelFactory.get", return_value=mock_model) + mocker.patch("os.path.exists", return_value=True) + + response = IndexModel.parse_file("test.pdf") + + assert isinstance(response, ModelResponse) + assert response.status == ResponseStatus.SUCCESS + assert response.data == "parsed content" + mock_model.run.assert_called_once_with("test.pdf") + + +def test_parse_file_not_found(): + with pytest.raises(Exception) as e: + IndexModel.parse_file("nonexistent.pdf") + assert str(e.value) == "File nonexistent.pdf does not exist" + + +def test_parse_file_error(mocker): + mocker.patch("os.path.exists", return_value=True) + mocker.patch("aixplain.factories.ModelFactory.get", side_effect=Exception("Model error")) + + with pytest.raises(Exception) as e: + IndexModel.parse_file("test.pdf") + assert str(e.value) == "Failed to parse file: Model error" + + +def test_upsert_with_file_path(mocker): + mock_parse_response = ModelResponse(status=ResponseStatus.SUCCESS, data="parsed content") + mock_upsert_response = {"status": "SUCCESS"} + + mocker.patch("aixplain.modules.model.index_model.IndexModel.parse_file", return_value=mock_parse_response) + mocker.patch("aixplain.factories.FileFactory.check_storage_type", return_value=StorageType.TEXT) + + with requests_mock.Mocker() as mock: + mock.post(execute_url, json=mock_upsert_response, status_code=200) + index_model = IndexModel(id=index_id, data=data, name="name", function=Function.SEARCH) + response = index_model.upsert("test.pdf") + + assert isinstance(response, ModelResponse) + assert response.status == ResponseStatus.SUCCESS + + +def test_upsert_with_invalid_file_path(mocker): + mocker.patch("aixplain.modules.model.index_model.IndexModel.parse_file", side_effect=Exception("File not found")) + + index_model = IndexModel(id=index_id, data=data, name="name", function=Function.SEARCH) + + with pytest.raises(Exception) as e: + index_model.upsert("nonexistent.pdf") + assert str(e.value) == "File not found" From 6c6068eae7d44552957c4a31f865f03abe5f5371 Mon Sep 17 00:00:00 2001 From: Abdul Basit Anees Date: Mon, 16 Jun 2025 13:27:09 +0000 Subject: [PATCH 3/8] add functional tests --- tests/functional/model/run_model_test.py | 97 ++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/tests/functional/model/run_model_test.py b/tests/functional/model/run_model_test.py index 71838034..1e385869 100644 --- a/tests/functional/model/run_model_test.py +++ b/tests/functional/model/run_model_test.py @@ -324,3 +324,100 @@ def test_index_model_air_with_splitter(embedding_model, supplier_params): assert str(response.status) == "SUCCESS" assert "berlin" in response.data.lower() index_model.delete() + + +def test_index_model_with_file(): + """Testing Index Model with local file input""" + from aixplain.factories import IndexFactory + from uuid import uuid4 + from aixplain.factories.index_factory.utils import AirParams + from pathlib import Path + + # Create test file path + test_file_path = Path(__file__).parent / "data" / "test_input.txt" + + # Create index with OpenAI Ada 002 for text processing + params = AirParams( + name=f"File Index {uuid4()}", description="Index for file processing", embedding_model=EmbeddingModel.OPENAI_ADA002 + ) + index_model = IndexFactory.create(params=params) + + try: + # Upsert the file + response = index_model.upsert(str(test_file_path)) + assert str(response.status) == "SUCCESS" + + # Verify the content was indexed + response = index_model.search("demo") + assert str(response.status) == "SUCCESS" + assert "🤖" in response.data, "Robot emoji should be present in the response" + + # Verify count + assert index_model.count() > 0 + + finally: + # Cleanup + index_model.delete() + + +def test_index_model_with_pdf_file(): + """Testing Index Model with PDF file input""" + from aixplain.factories import IndexFactory + from uuid import uuid4 + from aixplain.factories.index_factory.utils import AirParams + from pathlib import Path + + # Create test file path + test_file_path = Path(__file__).parent / "data" / "test_file_parser_input.pdf" + + # Create index with OpenAI Ada 002 for text processing + params = AirParams( + name=f"PDF Index {uuid4()}", description="Index for PDF processing", embedding_model=EmbeddingModel.OPENAI_ADA002 + ) + index_model = IndexFactory.create(params=params) + + try: + # Upsert the PDF file + response = index_model.upsert(str(test_file_path)) + assert str(response.status) == "SUCCESS" + + # Verify the content was indexed + response = index_model.search("document") + assert str(response.status) == "SUCCESS" + assert len(response.data) > 0 + + # Verify count + assert index_model.count() > 0 + + finally: + # Cleanup + index_model.delete() + + +def test_index_model_with_invalid_file(): + """Testing Index Model with invalid file input""" + from aixplain.factories import IndexFactory + from uuid import uuid4 + from aixplain.factories.index_factory.utils import AirParams + from pathlib import Path + + # Create non-existent file path + test_file_path = Path(__file__).parent / "data" / "nonexistent.pdf" + + # Create index with OpenAI Ada 002 for text processing + params = AirParams( + name=f"Invalid File Index {uuid4()}", + description="Index for invalid file testing", + embedding_model=EmbeddingModel.OPENAI_ADA002, + ) + index_model = IndexFactory.create(params=params) + + try: + # Attempt to upsert non-existent file + with pytest.raises(Exception) as e: + index_model.upsert(str(test_file_path)) + assert "does not exist" in str(e.value) + + finally: + # Cleanup + index_model.delete() From 51aaa7c5ec0aa5e2b6c9a68afb8be462226236f4 Mon Sep 17 00:00:00 2001 From: Abdul Basit Anees Date: Mon, 16 Jun 2025 13:29:58 +0000 Subject: [PATCH 4/8] txt file alternate reading --- aixplain/modules/model/index_model.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/aixplain/modules/model/index_model.py b/aixplain/modules/model/index_model.py index ce3acd35..7a20900f 100644 --- a/aixplain/modules/model/index_model.py +++ b/aixplain/modules/model/index_model.py @@ -267,6 +267,10 @@ def parse_file(file_path: str) -> ModelResponse: """ if not os.path.exists(file_path): raise Exception(f"File {file_path} does not exist") + if file_path.endswith(".txt"): + with open(file_path, "r") as file: + data = file.read() + return ModelResponse(status=ResponseStatus.SUCCESS, data=data, completed=True) try: from aixplain.factories import ModelFactory From 25cfec055c4e7ed1ae8f4fdb6aafbd39805cdb07 Mon Sep 17 00:00:00 2001 From: Abdul Basit Anees Date: Mon, 16 Jun 2025 13:48:56 +0000 Subject: [PATCH 5/8] add test pdf file --- .../model/data/test_file_parser_input.pdf | Bin 0 -> 12664 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/functional/model/data/test_file_parser_input.pdf diff --git a/tests/functional/model/data/test_file_parser_input.pdf b/tests/functional/model/data/test_file_parser_input.pdf new file mode 100644 index 0000000000000000000000000000000000000000..03e8289821bd70f1c1f056f1914c89915e478daa GIT binary patch literal 12664 zcmb_@cOcwfvv)|8Xd#HsBC?3GtM?LJ^e*bMs|Bm9PW0XhRu?6rL?^oFy^9i^=q-9g z$h-3UJ}u=)cBrPLtc(WgRSIs0TCg^=wzWohD8XD|wxTEi0K4Pgd@8DH zlG>UwD9#s>T2k@=c4uiB9R(>FQ3t4no0ByHhN3tlpl}z2s5ok<>Vj}^LC9L$!tSR3 z8%^RkyZ{gYZf1icBEqQzv$sT80r*f94Nepa0_FtZl(mH*VA3#iI20x(hU1KIfSPfAx7xvhfnT8^3{P7X z)OUaTh#sAJei#3JeSK8EbuQj^d#THCvElpXx6e&4YPReftLu4lT5jUWH&_1X_wUhV zliwFVFMV%+m;e5?HRF53THQLRSgtSfw2Oo!YNO>4ht!TD^)y?J_*xj!FLbQFPhkjaHrX+~_`pOwnmwceU3%(m|M7>)oEK2Sxrx&%#6Cql_3_o0N2*&qVJZl1|>stv&Nscm{S4-Rb%E zoQVum?1yx`B99l_6*UgV;J5a!JkjBBz38yu#A!06)XlZ9bCoIcPNZJVS$#4xD3#LR zCNl`^V(<$So4XqHEFtbRIdrA4O=3W2I>b)8E?DeTB!05#>Rw`AGVU#*?b9fhFNU|x z;r4?TiUVUAioj+oX1ri-KTrXBNXb=&!kFuWa^=hNSw(#cWmCT z1SsnY6a-evJZi1NZ*6Zi{2`MHtG+HerdrRWW5bQGY#tmwuS|z@q2xtxKZkX~VfVn#Vy`_&Cyq_t8$B+S$B#H-Jr$Zo!Ta4Y8~N z1xgqYgH@YAkBc!*B7C7Qzo4JF&6Lj%{YegcQ={6TqOO7!0jKGj_K$W2A$Bjg3z{vo z&RPtKd(3J*&#qQbem_uUQ;->S{9qBIeY%g5sNGn;n{BT9;S$LLAJ$Y#>GMsk7c90a zky>BLo$*s6tjHbrOp6T+OQ-5#CJM2_8Oegt84KyWBaE6RR>my(J>2cGY5l=<`2G#1 zm+gL_{6Kv8+Q($;BQhps7de-&oE0_=yB)wSsy6`k+!j!$Sp0`yYZiq($o_dre_w`l z>-#%$U+&E|=Ys;AsgIs?ykG~J15#WpxS*#3_ayLa7?~*qtZ5buv4G4xR2%v*ZTmo5UL5 zFnxShql*SFqE;so`}Md@Ol$h*sS$WdudFq_p%S=jXbM(l6=r3*oC6~R`ehF;m_s+` zKQ*x-z21*ospuA`6#hJewXH5Ig?!jreVfiwsw7oE6Znv<8Ouj%AH10_YUYw+E359f z>RN_HOz

XN(|6L%K<-rM53V`4&_Eo0M`quI8OQ(@!qcmI1C^$3z@PPhUF5&ocfWqVuTzG;$a7M zay?v3<0pNKHC*|T1Qdw)f_SOJO;QcB4^~ijBeXI2XVr-PICkeTQqKCjBY>nT$`Y#hJ)J)XsT(L4c~BGQ@kOl$<|ce z%O3u0rzYr-({!T|mk#fMV}EAZrfdJAVTBo012oR*<9Y&p-cEnY%R>tyUfhOn1Vlil zZwbO{&tFwI^b`8Uzp97IN1l*3K>HG3)E^|!`;BfSC|fZ(9wt7bR`D?Zmc&xM;|>eU zzc#!aSX>LT!~1NyO}kEaWk~q`?ft;|Ia(28imoziFPKAbW?8cne`Ck}Q5;N_2lLXF={1Sf-Pmp~JH3WV=Z$HA4zuxFp} zM#4*1kzS6^nc5a!3V^RRa9!I_4ox{mx32!h)ddsVaT>Z2FcY!}%V@|sKsGf%f)T4o z7O@t1uXpKNYVPUGj*YW#$yIV`0+cKsh4CiMl-{SHTTgp+)8t!Yq*(j0cnG*4O_Mxs z=QyLk&zcpYxB{tdubM5nR=Unn=Kq+mVvro`k*X6eggGgH)?nxo#nf|Vzs9z%M-N*h zu_Ccl1IAiLL+QuWhu5wY4fqLPIe^wu@V6eTHEw&i=~!_vj4Ka+4>Z&?i6vu;@=ErE z$S^J}AzLkoZ}!4T{bzH|#QGEYJPMZEBzg+4A>jHDZVM}zao>;YB;Gd8>8TJmKmE?6 zVUTBVU{ZUH;|LasV+LK4)JT3sw@6^BnN0IX>j}#AxXPc57b*~mMvT>>$4xjB2)E!c zWtI?TV(^q?^SnIUa7Q;GZ=6#&Bou70BjQ(VXdo8@QGFSdLv5POC=-Qq<-TApp{=yi zLr@mcv?`gc|#L{!xROkPrS zs~?5MnJt&R$C>Rf)bjdyNnp<(4X?3j=u~^YB~z1$B4=|Bh^hiyuppgIVa3I{?$Tm< zaT6jYJ!V;I=s$K}w&GFWNVQ_N^k-9b`VpPHXSR6O73lZLAmecbEp5@nVLJA}vv*Hy zOrtw2rG|ALr|LIWowvJ)_FLMqhM~nSgi6-_EEeJAB`x^DLeK9IfwSl5E+*H!`bdPM z%^@}Ty@l=@U6=V>hC~RVZ_YplLzBu|j_h~rA*E3PyhOenLE)_}^Po~gIqfg+qZ{E} z0@0WsJTSB&Cqds-A%JmE>Lf1yV2YrLE_Tp9eKwYS5BWXtd{8e?+P&)o?St_pB>sDa zS{5^#u=Dfie6C;e-7+LRJda;B3kvU@XfmcrKWR3i9?RP|TFDJ*c$0)bv`>rOsBncV zo$oI9tJNHe9$$)|Xi^^HS;xm##k_+=ju)21RMkQhnNJVqzJi$ST$&jSsMsSA1Jklp z#^1wi4@?>Osl4XbOyfR!d0P0go%pJtxgfVRSZVtyE#hA@kKG+ zSn=#&lg<~~L%OYeL&;%#@fGnrS?#Ok6u8#e6P5m;BL%7+KO#t&IMMj0$90fq#PZI= zdC=V9u$%-5q(3>uMO`h6x3t>Qw)253CDWFGDNYycz3u%bw$4etcpM_`*ItTq_cRZZ zDrv@u>jF(%F~?rd56yg+3?_~*#}66Aqf9M_sHf$g@Ww%enElD)XIYKzFFuB;?NH`x zr8?!mzCUlG4#t=tp7^vus=Yn#ZVc4Ef7;TjxMv5?B$|5iFeLyqI>qeG6E!de$a>5V zvmx70{o2l~nfG{1mPIpAz$Q4p2;aM@;USvffu=`RCSXjuj(2FvZy9N6i49#Moq5(% z`&^T17Ld!vqDeJquF7O%9xN-J>k5ib#&2fz1?XLc^9(;;s4!ES1mG$Th`j8iCbfF5 z+)1g0tMNEQyI6_Fn$jb)_gvc5LnjThHcEQzp|$7rlrGd7P}-#M1x~Q9^R`v>L8j zNkPP9rxJd)R;ElUrSynKpDxr!>hSwh8xd0%UE<+zz)SJjkDs1 zNgmyxCGI7pWa#Bq56L#O=>c2%b}9wte)>I47DAA*`8x)syq3E4xW`HzRYjAD@2y#9 z^TgoI z{gT3aDe7ijXWAWdIRE^89eOoCapiz9JyJD_>ppQ!JMd}MhcsaOVo?{l$A#TBOGHq* zu47L<1UJYj%Iea-ad>s`_Gi28Elt!9%L-SuF0l{a>KiP>I%>W)%$RMChz6-01bSJW z$9*Kd9P$;e?QWSie8G1)w2|`J&*uqI)a=g=ARUWk#UrBLg)sfRm{=8p^!S}-E*mSy zb-&pesE&;)fIUWsAkQwf(%_PrRtTxA@HV!06#22(YsyXO%xPu*%RF;bDKc%=Ux&b4 z&Td|xt2)(XpQn?-evH9rK7CT@o3ov70MaO$?fCPFVp{t{r=;8zbfFi59k&T?iL-rk z>iYEOWTl(ZGR#77t8Cze(Z`4hE;(|!dIU>cAw338FcJ4Y^;b_NM$xllg#gYSjz?16 zOghJ1h$@8;Q%$p2S0_?Fk0aHhw=aJ|i2ZL&8DQ%+(Fwfz%?$k6HOCjrDhf=gemeIKDW8-@tqPl!k#j*iP-(C6C{(M9c+fTr=LyDfr^Slt z@fmL5@$GpGAC8U#e@9eR@W@J`B7Pv9o+;w|u%Vl*tFG32>c!=&xo3`~a7Ty82F0o6 z8_TJ}6Ib`WGg_W(z{PqpTrxj>PM&0|d;jXgm_wznKc875nIW*0ZCS8IVjfFo2*Zkb zDVVSrt7lpmJnHTGESi<6F$sIkV=`9b;;5ovZm$-@fyZ6~{zA3J8YqK^^yOUT8JPjWuJhMw89k?kto>*e$nOg>YHcQNz1GDGhph2kP` zqj^VRI60454DP*13_? zD?PWp-SBCmm8wWN@ieuY#ePkZdI|sF9ZW>{c%~#AOC;VvX&gVxn9{H~IKR;Jw_$Zn z0Xd%eb}nv3STJ|Vt|^jrw4DJv%Sy$s(m%iSvt-_rwbk0(fMfh|WJNh?g6T1?<#okz z{K<5Uh}Nk&oR-da+8$Q&l<;vheqzvi=IKqBr<_^fhD8mjqGc96PV(jYt108B+h>`G zm#qrptPWbDqjb3{<`uiVgzp~AV(+gYa+zJe6-$3V#Hd1OQcI>MtLbs#4`t&9Y8XC# z78xA^ez+=^CS0*m{i-!y$7!g^y`enx14(-1@%w4}*H=bi2&4PZesx7p$LXb@ zB%ZB}R5{1nqUMMGbP6ROvYPQ!JX;ruKzu8#6D%$cq_IbO{BhkH9n<_nM5DCrz%dMR zuZ6d#w;aY5Cu$dF=oCsY;8KuNm6p~IpWqH z!swSAneOfP^6W|MiBK_%iuYDDURi3=n@j8hEZAUrHz=5>!avR2<27@qb90K!i#b!V zF`mz78%6vJN#4|WR+=ook{fEX35iZKz{{W|XDqy36>-1_G6+97n{caB>>Cw2LFF>` zDf9gdjM;Az!uhK0mBn?2?0YgAZz8JsOtsE}&yWFyIDC8A1USzQ2n@Sjet|bXG#6dbL|)86g;Wg^*27$N zyy8hI%YC$B(teUkf@qk9+A4l%heN`x_ZZ5FO&q?uV3C{J@45N;yG*&Drz#*>bl~Mj`@u`XDYBx6##CZt&OPrQqkcZ$#6wUHInyE< zW)z-v5q4l2&kO+=yU;P1Jfw(A3kQrX4(t+WY>+94TA|16dw-^3F8{%lL=AaY#us!MKyOc-;&^tte#5=tz zDb}*=o3?@2{4f-eG|D(AIDBv@F`SVmJcOOkWng4NBo8|!o^Zm@z>1NuF?X#=$YwRT zpmCli%WH+Ge0w`SzTn$om=}@a-fBKFV?O0JMDT&HhmrL&)7OLqQ(h8x>7<*SP%JG$ z@xyFVHUIC!my4uou#5awSBWI4bdj8ahQ`=1Ki0gf>6QnDJWJzbx>x9rMn4hvZ+OJI zib8e_p+)Zg6hYfZIwDCgNl<0ad3oIW3a@?9dAhi;SBz0zT)w5V$ndL}fgj<$>?p=k z61v%m8B%4)z_|2>2tvR_al4z2c>6hOE2e)!gCI$=3bVQ`=0{*e7Eys?+~D}wuG|lG zyGt!J3XKOZUVhnD9>5hjC;FKn8o!Jtr=VHsCKA73lJkjVGV5qJVnm0rQ!6Pu?H4PV z;wdjn0M#_Zo8LmncC{qi2^~+YuIW#5)~^OIW8{x&#K?)sd2F`RUshtsYlOV?n68V16!(g6)AbN5JpA~R zW;8dKaRSuu(wi;6PD6wAma;kYxZrnq?3SUB!|RYmf$9FF7JMdW>202aXdTlVTn$@I z4a%((m2<=KBAvH$$kaWQ6q0^!=%TnoY!~!HrF=&Go3Wa)`!G~(%q0~2S z{+~S2>9OCLr%L%hWFyai%_`34FyTnzT7@fBkgg6a4+G(IoCF65G92ihgo8yHdmi9v zNl{^_ASk*^y!9DEG0m2Z{5#n!NQ+#}Lwp_%)sM~I8Du9YPi<^jKsIq*!)crdYxzeMgTj6ix%@4(wPd$6JtzH{mSWNVZ;Q z8D4KCHvI#%fm_Fiy=Qe%vJCjK5fML4G;1OpRU5*;JNnth6B%vzA0P?eZF!208xE#0 z_|`OCh1bUw(0Ve?QH^I46o-`Nz78A}egPiHW;|Xd6vy^9_dn3Vah-aJ6(aonOD(AU zgXK)AHD{J|we^Tc?15jr}?Ya`VhW+EnRKcDp8xUu3WXvLM@y5n61}U!$KfNj6I*=PtTRFdWQt?kAB@_or zE&91N;xtn&VJtJj}-;lV216@hoi9TGGe2zSO>O1ioIfs_Jj6d0@u&G#dPB?KrPIG ze=_!*oxIPHuPG*2z6aDyUUJ1zA6tZ@fW!7a(yGnWM|9ve0}#KNzppKYIN~%n#;@1n zHm9F8eIA@w6+>;R8n>B!A&kuZIDWI$&6={jq%~4-2K7h#V@d0v;i1bHw@VC47eyG|r72N=(j;RIaDo zzj%mh2{Gc5ajQl!o|b z{MX4&ET2O-*=}rIkyoA?k37};ed9H}re!g7_~YV?5UT{PLB1~H#n=5d@qF%KhT{x& zPTyoaYUQa)hb3I(skRNc$LZ~mhkLmcxt=29vzs_PzlfC+(}ZF-XePA!_oi8G_ATGx zzFInheZQwuBR?5}2W*S*ub;{5C8exWS=du=c63Ek!a2*65jvl}RF6b-bklXiMmo3y zTUyvro{NVid|j`5d_K#fB~bB6o^FUJU6uG{_5Af*qDH@+dGnL69*<9JU%#WVj=bEl zx-tqqaRb-PtUhMDT-d87HgB+8_^wud2v}HFYwh}*Nf~!r^YiJ;cK2T8RbC5 zuk*OxQdwSDvu*KBT0_8ipUE$UoFR7cKHg0WK{xI&2nQj>I z6bH9xD=>}6FenC%Fm}~@KrZO5121r(ZQld**H5ih#CiN^`1d_{Y_*(sZx`DAH@Z!I0;Z-pr_xw>wloTrO4J}w6o37$ADXqJ z^}BJ!gBSxAti#mTGxwQmJqCtUr^425g3u_mj9B6gZ83MF+I62e2hGr}y=`xIPlZZb z#>P;Pi!@JtG{+3F5>{i*3DQrm-=>G8o<>N@V=c?aELRsU;84je=+Bx(j5Fn!V}`B@ zSvXYo#8MbCtn}jH5es*Z6!$)wyrd_JjO=|Y;OF6(I`@rwaOoOTAvbzj>> z?Re5SLkAX^`5kVWLbkg4shcS@RX0ymX7D>Z7PSQltnriG*ff2>TjbMWo2TWNT#2dz z3qFgusSaNxqVw*vAi>S(`c1R7O>C@l4QNjD@S2!x$9JKRwy;)!Mv1gOrXy~KbAF99 zG0Nif^Et2Ob7I7ATA@zFPA^Z~hDb+Nqb!z$&3yftg?*mhSRzP&^3f0|%7&8l@xFGy zo-OR`A0TS7?|JtSxWu+&qWbJcMO58eGl}$4KY_>hXmYi0>%>Us!3%CT z)xla-{?63{pT_7po1xQ%Q@J5}ebi5YbJqy&N6R5oVvAA9)XRmPW>rUztn%!iEccy7 z3lTo0X#t=;l8<=SkZ~g+c0X-}_F)$0Tc1{}9>>En1G_1^x})euRmnJ8=~`$nE7f`> z&WLMIB}YJPHchx1Mxk7}JZ$$c4P-`~!kHN$m_@h4F~meWdI8L7PIRklE|b6rwdf@+ zQd%HiOGRTy3ZE9Uah7JKB98BuP5}A7pf2l0eW68DvNFgT#I4i5V?<-8RT@wrvh*9| zQyH7TU{0E0-^OuIL4Y7zrB%WyIg{=*d}lG4wsa`Tkc3gM-Em%ELRF!G)pR|?t%7U) zNjyy{>f`Ej)8f4+;y9fW{l|JRm7}P0J&eL%s=>HQvAe#HDNYV6KOcREW_9pK_*p6o z>!1zhZ716&dzCurp}smL3JB8%JyYCES~zj@tVfrOY%-{BGav9ojSn*!V-01_4CO(N z+>=6W>p8WB#8Z7clntGvyAg%N*t!IaBxT+Zyw0RA6T*j*nvI{pS<$D=FvB^?`kx{U zR3_)@-Tl>$8HfA2Ret6z>h>wg99>Q`_ltgHRx0(7!H2z#=6-no%1N|b$Hn(vudP1D zKMI1Ii%|7aC3yMmFNY~fzNpk#+zm!hI~(477%=YEdEYYP&al0*^W*)LN-saVD9v)s z1>zSjzd#56`Kqev+r72CQ)oxYkWsl-@et!g8A13$zwku%WuvQ5%>B=o_pR-H)^ID} z@#13fh9o`&{qTm9zuw~y^jx}+_FR&=^0jWQ%(*L#YaXm@u+_CyM;dS%>=;&5_vp%XGR8&+qYvK&hKUZmA#d0}jQwXJc=6 z4!HwI^|g4F*?y(dt1nu;vNC+Ga}|ER^gAK26+9tU_F|Q8DVUQN2wpcyaj0!$%gI7s zW8)2fK69pWyh??b$<@4MvOMOHGk#;?u-Mz_O*45(invN`uY6g;wRI9nbh%+^y<;QY zb{%DUewI^571OtEI?LXpJK}spQ8>oPY(q%|te!m_fEDG%&SX>2PYaioyS`gFS1Ygo zJ!A^+n!d5b^y(qH%%*r2iCp|hT7^7|xE}43FlyLz%TagLW`m}SKh@RE9dFb4fY^K! z>~Cx1eDQLOxo~M;iXL~YTw)%3(eIgd*zf?uJFSQ{{v37e!d`tSr}RGS^!L)+qLRd_ z%hAbBx$^4r5+CJ(2HHnC#SaM6Q(9r|6zsZ@_4f`;*IK#F%)GckS+t@gA+@S4pV(Hv z+pF;kNCh>K**wh|U<%HhWPS~fm5Q% zv<-!YKuc>M89b~4U7kO<4PTXOL!eOt3li*(6>J6>55@Byyx#j__oNQT#~H3Q<3C&K zqJ8mH_cB}kHDW0AGCO{e$I0<`D&#$xL-N9aVr~XXQ+-&S<8S?rZu~}%IFy(6w^Uuw zzG7VCTamr*^Wj)loglUHUO((7r?N0CGrThkK!dX4Ux>eb&ji@ znJTIfpmPQi&akO{?{;lwW$%ReW5DVj*GNeUaSzOpVf?0Awl=DB^R#ijgRr&4JelS6 z>D6=3BtG(wid5|?1yRo1&xOj*cQ>0BUd95&sY_G}hN$kT&}sD0pLhiDMlp~2v}p8v z!9>$lBJ5>RjTs_ZGAUCj_@z~DS)|!-DlYRRQ!ym`IofOy)M0tv_QwMpA=;0mbfC*= zk5yvLYqediM$;oLA#%9VfWWCL>Kn5Unk=Vje$I0;J-q0hk6iiLdAh~(qzBU-6WhQ1 z&h$v_6)ZgH!gCu1junt~>#{X7kH@MoNp-c`Az0&UhC9OxBWs#MbZS*pl{b+17c|P} ztnpJDt%n_;0~$B?u(Q>|N#$!RZ3c^sYtCCkq?PuXL1U=-lg;l`-MsLt(hxGXh&JA9 zBDTYyFRhHnOQE>p2g_myR%+Ux9<$Uod56kVw8)Dculao4)fuP{V5B?0DQ={$JS2|u z4W_WK+&b~_FnRBjiTBE_DWRr)@}h^RlS*@}tjk)i{0$M-ORwI8Nk(gWK@}q2ex=h3 zDIH&C?b9E81B*L4xhg5w0<0J=Z$#qFB+z$Gs=XjK4;5l@?-$`LE~0+6d3FR0vuEm* z{*9N3doZ%D)h9DRN;9O5n9wiS)?vu_wQ#h8tvkA{^K&3moTsgR@w_#FOMG#1m|2`w z=^L6Q>O+3f>g`PV@B7Oj{aKhj^zVi~)ab4W@UHdm?iCCI3vlE7P2~AkhoF{+1B_D* zVhQ6^hC!_%FW~L~0}yJN56lJN7vwU+;naX5Q2l}c5Q@tQW{&`HqgFLgWSBGD#mOAz zjA}DfbAp>|!Vm_WsD4QRrxwf|fs&JjqnHK$Fr(TGIi*ljl(;kCu02r$2SxhZm^-{X z3q?dwq9|(sf6Q?PaK5yLIs*)F?u^yIF#@0vTu_MM{~!wfr|933QV;~h7H;_uh`&u$ zh9MwO)CxfG-&EgO_m^r;4SpQ{yVZZ$bvONc@dd;gcDEf)d6=y$3}I~!`J?a87I8Q= zUCa=7Uc56N{4e=CWq-^6aq>T`_?Lj5veG{~G{Ib8J~6SsLnk8c?f@}=4MPCTV3yYQ zqQIl3HXy(nDhkx)RR$?LNWrYEb}(B`eg$ z9Ofhnlv7gz$XUUi5r4q~z#t9~7Y7)~`S-^D2Yo1K{@40Y5%}9~5fqP*G8}4c;USGW zL}FYZE?#yJA3K;&3nU=K1s384vx9hrKp+v$f8#_&3neOr>di&<>i#c8{{!cL5QRHw zp<*cJ0C8{zxWb&A?>qI-(D28xH zaG?ZE_yl-CU`*JXve&qaZ5cUKW=^q;E`*T3$-EK=KnS-|4;>YV*Z~`2#c85-v!Iv31)!dZvs_6el69A)%<3DH!CkX0444nRyU`=aJ7;0|-RNaT8s_k7(1aKnCHY@~#r|gwqq{=x XjDR>H{x}rO#f|bP6O)XpEYAM{99-3q literal 0 HcmV?d00001 From 1938f47b02064330005d0108cc94bce16bea842f Mon Sep 17 00:00:00 2001 From: Abdul Basit Anees Date: Tue, 17 Jun 2025 13:45:23 +0000 Subject: [PATCH 6/8] update test file --- .../model/data/test_file_parser_input.pdf | Bin 12664 -> 16202 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/functional/model/data/test_file_parser_input.pdf b/tests/functional/model/data/test_file_parser_input.pdf index 03e8289821bd70f1c1f056f1914c89915e478daa..5882d1bcfe60dcf21b6436f78e916071a8c91d87 100644 GIT binary patch literal 16202 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 literal 12664 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 XjDR>H{x}rO#f|bP6O)XpEYAM{99-3q From 9c4cdb16f145c8969ef07d1385f4b4c224436dc0 Mon Sep 17 00:00:00 2001 From: Abdul Basit Anees Date: Tue, 17 Jun 2025 13:58:47 +0000 Subject: [PATCH 7/8] add warning --- aixplain/modules/model/index_model.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/aixplain/modules/model/index_model.py b/aixplain/modules/model/index_model.py index 591b782b..33549948 100644 --- a/aixplain/modules/model/index_model.py +++ b/aixplain/modules/model/index_model.py @@ -1,4 +1,5 @@ import os +import warnings from uuid import uuid4 from aixplain.enums import EmbeddingModel, Function, Supplier, ResponseStatus, StorageType, FunctionType from aixplain.modules.model import Model @@ -179,8 +180,6 @@ def __init__( model = ModelFactory.get(embedding_model) self.embedding_size = model.additional_info["embedding_size"] except Exception as e: - import warnings - warnings.warn(f"Failed to get embedding size for embedding model {embedding_model}: {e}") self.embedding_size = None @@ -361,6 +360,8 @@ def parse_file(file_path: str) -> ModelResponse: if file_path.endswith(".txt"): with open(file_path, "r") as file: data = file.read() + if not data: + warnings.warn(f"File {file_path} is empty") return ModelResponse(status=ResponseStatus.SUCCESS, data=data, completed=True) try: from aixplain.factories import ModelFactory @@ -368,6 +369,8 @@ def parse_file(file_path: str) -> ModelResponse: docling_model_id = "677bee6c6eb56331f9192a91" model = ModelFactory.get(docling_model_id) response = model.run(file_path) + if not response.data: + warnings.warn(f"File {file_path} is empty") return response except Exception as e: raise Exception(f"Failed to parse file: {e}") From 74497ccc995016e3c5febc175b45b77b547f0941 Mon Sep 17 00:00:00 2001 From: Abdul Basit Anees Date: Thu, 28 Aug 2025 11:26:28 +0000 Subject: [PATCH 8/8] update docs and function def --- aixplain/modules/model/index_model.py | 38 ++++++++++++++++++------ tests/functional/model/run_model_test.py | 4 +-- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/aixplain/modules/model/index_model.py b/aixplain/modules/model/index_model.py index 33549948..c4343a4c 100644 --- a/aixplain/modules/model/index_model.py +++ b/aixplain/modules/model/index_model.py @@ -12,9 +12,7 @@ from aixplain.enums.splitting_options import SplittingOptions import os -from urllib.parse import urljoin -from aixplain.utils.file_utils import _request_with_retry - +DOCLING_MODEL_ID = "677bee6c6eb56331f9192a91" class IndexFilterOperator(Enum): """Enumeration of operators available for filtering index records. @@ -341,8 +339,20 @@ def delete_record(self, record_id: Text) -> ModelResponse: raise Exception(f"Failed to delete record: {response.error_message}") def prepare_record_from_file(self, file_path: str, file_id: str = None) -> Record: - """ - Prepare a record from a file. + """Prepare a record from a file. + + Args: + file_path (str): The path to the file to be processed. + file_id (str, optional): The ID to assign to the record. If not provided, a unique ID is generated. + + Returns: + Record: A Record object containing the file's content and metadata. + + Raises: + Exception: If the file cannot be parsed. + + Example: + >>> record = index_model.prepare_record_from_file("/path/to/file.txt") """ response = self.parse_file(file_path) file_name = file_path.split("/")[-1] @@ -352,8 +362,19 @@ def prepare_record_from_file(self, file_path: str, file_id: str = None) -> Recor @staticmethod def parse_file(file_path: str) -> ModelResponse: - """ - Parse a file using the Docling model. + """Parse a file using the Docling model. + + Args: + file_path (str): The path to the file to be parsed. + + Returns: + ModelResponse: The response containing the parsed file content. + + Raises: + Exception: If the file does not exist or cannot be parsed. + + Example: + >>> response = IndexModel.parse_file("/path/to/file.pdf") """ if not os.path.exists(file_path): raise Exception(f"File {file_path} does not exist") @@ -366,8 +387,7 @@ def parse_file(file_path: str) -> ModelResponse: try: from aixplain.factories import ModelFactory - docling_model_id = "677bee6c6eb56331f9192a91" - model = ModelFactory.get(docling_model_id) + model = ModelFactory.get(DOCLING_MODEL_ID) response = model.run(file_path) if not response.data: warnings.warn(f"File {file_path} is empty") diff --git a/tests/functional/model/run_model_test.py b/tests/functional/model/run_model_test.py index c657b13e..68380d91 100644 --- a/tests/functional/model/run_model_test.py +++ b/tests/functional/model/run_model_test.py @@ -324,8 +324,8 @@ def test_index_model_air_with_splitter(embedding_model, supplier_params): index_model.delete() -def test_index_model_with_file(): - """Testing Index Model with local file input""" +def test_index_model_with_txt_file(): + """Testing Index Model with local txt file input""" from aixplain.factories import IndexFactory from uuid import uuid4 from aixplain.factories.index_factory.utils import AirParams