From e882442f8b7a605d3711375e5f63fc16f43827c1d529ed239baa3e2d2d327f89 Mon Sep 17 00:00:00 2001 From: lansan69 Date: Tue, 31 Mar 2026 20:00:04 -0600 Subject: [PATCH] =?UTF-8?q?Versi=C3=B3n=201.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- app/__pycache__/main.cpython-312.pyc | Bin 2358 -> 2571 bytes .../__pycache__/router.cpython-312.pyc | Bin 1962 -> 2217 bytes .../__pycache__/transcription.cpython-312.pyc | Bin 2074 -> 2073 bytes app/api/v1/endpoints/audio/transcription.py | 2 +- .../rubricated_analysis.cpython-312.pyc | Bin 0 -> 2007 bytes .../endpoints/document/rubricated_analysis.py | 37 ++++ app/api/v1/endpoints/router.py | 21 ++- .../texto/__pycache__/resume.cpython-312.pyc | Bin 2032 -> 2031 bytes .../rubricated_analysis.cpython-312.pyc | Bin 2131 -> 2130 bytes app/api/v1/endpoints/texto/resume.py | 2 +- .../v1/endpoints/texto/rubricated_analysis.py | 2 +- .../__pycache__/transcription.cpython-312.pyc | Bin 1969 -> 1968 bytes app/api/v1/endpoints/video/transcription.py | 2 +- app/main.py | 18 +- .../document_standard.cpython-312.pyc | Bin 0 -> 5006 bytes app/schemas/document_standard.py | 60 ++++++ .../evaluations_adapters.cpython-312.pyc | Bin 0 -> 6651 bytes .../prompt_builder.cpython-312.pyc | Bin 0 -> 3648 bytes app/services/document/evaluations_adapters.py | 162 +++++++++++++++++ app/services/document/prompt_builder.py | 58 ++++++ .../evaluations_adapters.cpython-312.pyc | Bin 8266 -> 9103 bytes .../prompt_builder.cpython-312.pyc | Bin 3478 -> 3630 bytes app/services/image/evaluations_adapters.py | 105 ++++++----- app/services/image/prompt_builder.py | 5 +- app/tests/rubricas/documentos/text.json | 171 ++++++++++++++++++ app/tests/{ => rubricas/imagenes}/id.json | 0 .../document_utilities.cpython-312.pyc | Bin 0 -> 2780 bytes app/utilities/document_utilities.py | 51 ++++++ requirements.txt | 168 ++++++++++++++--- 30 files changed, 778 insertions(+), 89 deletions(-) create mode 100644 app/api/v1/endpoints/document/__pycache__/rubricated_analysis.cpython-312.pyc create mode 100644 app/api/v1/endpoints/document/rubricated_analysis.py create mode 100644 app/schemas/__pycache__/document_standard.cpython-312.pyc create mode 100644 app/schemas/document_standard.py create mode 100644 app/services/document/__pycache__/evaluations_adapters.cpython-312.pyc create mode 100644 app/services/document/__pycache__/prompt_builder.cpython-312.pyc create mode 100644 app/services/document/evaluations_adapters.py create mode 100644 app/services/document/prompt_builder.py create mode 100644 app/tests/rubricas/documentos/text.json rename app/tests/{ => rubricas/imagenes}/id.json (100%) create mode 100644 app/utilities/__pycache__/document_utilities.cpython-312.pyc create mode 100644 app/utilities/document_utilities.py diff --git a/.gitignore b/.gitignore index a1e5ac6..7d04269 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.env \ No newline at end of file +.env +.venv \ No newline at end of file diff --git a/app/__pycache__/main.cpython-312.pyc b/app/__pycache__/main.cpython-312.pyc index 40141ef99a28b3890817cc970a1f63b069ce8be06c161152f2bdb8f86a52be04..f9d93936ac0a9b3fcb2dfc2d824d4cdf4e4ea2b0681b94e304444172192f3304 100644 GIT binary patch delta 819 zcmY*X&1(}u9G%(CX1B4~CYw#tHtEN~HjP@=g6P4I+7BxpT7zDsP}3zd#4SxW%x;1W zJtztadI@_H2Cx-f6w+uxht?+vp%$_rKg z5{*UxeJ-wjHA0b=+z)44xm>6sE{mw`bMOLtIkcDd72 z66!S|inawLqrNhSVkk~gX>%y?03wyHBweX?2&LPhyCC1cJ<3}!JD|}tT4PGn>&P%Q zo9I=mM2wnkHB-*VP-?K*A2@9M(AIE6U$5)7VOA~YXJ~RHMiVsuH8CoB8UN?egdsTK zED8g+o7tdIaOji9yx<9)2%f;^n1?;Iu~GT-wv4OT@`Q$gu<1pDt){j8Qdos5iBlU< z7$g{|6rA7T3oz@9i1Vu>R5FM4hQcG@t3X|Wd$F`12GMahH|z6UO7@&pA8dOnh>pH|&E6Axwtol%;&HBT3Um!tTf8 z^;*r7jcRGVj&S$VJ-&=>!lIEZ1Gb9{Q#g{YR7_M~$JfXhmF&l|K2z{{2;mQq`NIJ? z#~sqj9Ot~2HZSi1{yR{1f#N2{-*33rSKR6QE>OOKB0V4{JWD@LKTkZ#?sMm1_<#qT J++m%8e*q||*f{_I delta 591 zcmX|8O=uHA7@cqb6Pr!8U9-)PzocE(T8DZN1gX@&q6aC|Q=#C}S>j4JDZ87X#Y4rS zf(VAWc*sTY7QsWH=c0J(rQoFmMX!Q~4ZY>$Oj0}Wy*D54&3lJ=WA2RWdznlcvGZ+v z&p(%H>62i#Y`Y9dKpqg0NJRdmbVP1JXhrqZ$40AWi=5t%=*x)y+Mu>}%}XTHfsvc& z+o&_TqLuX1tdsl0HwSv^9XE;>mhL(d-GasL4>%3Q0pPVG^N=)#y!P-Oof9iFV{M6K zh+Y*)mgE>3ZGntG0y4qIWW)87ZD|=f`R+?Gf-6PrZuq!4husG8>o^YGTC^7W8*zj; zt^Zo=J&AGD2m&|sJth?BtUL*r-j-+2W?A{>af%K5#UA^?*AG>%=0!cZ=@YNswUmcI->R3PKzG$!%Vp-_CDA(uTPQJ+ zxe5n4D^XBpdf&d8h(caZG&D7TVBhG=B3shPI8usSF)r-SEDmjbOV6j13MJZtF;y8; z5>%0uu*ib%J16LlcAXBi(pBCV7C3MvEHM~p*Md4}tb6m}dA9Pei#{?)L;(1SO21I> h9R>73>e-p!Aic7lTdyZxl>5R(P!2^T=)>DE?jPxJl+geH diff --git a/app/api/v1/endpoints/__pycache__/router.cpython-312.pyc b/app/api/v1/endpoints/__pycache__/router.cpython-312.pyc index ad1858ee9e27caab476a1b2793a8e5608c12f6790efd3170c2b71cb7d4fb1ba0..14f1453cde056c94c6ecdc03f3f3c1bfa651b65be1b6398e838fd4f437a1d070 100644 GIT binary patch delta 552 zcmZ3*zfzFzG%qg~0}v>toXHI0oX97^cxa+}DNhPx3RjLuu4t4fBg4cUGWARhsVu8O z>VP0h0);1u!jnoB%QAMv>$z&0hW!hDo`Y?W#bU;yla%yp6ZYEGRUm+z`!3C-`zt~SxX7gSaYeq)H$qB5Y zaxgu5MWsnanaPPIsVVV^d5Jld#hJxLe3QShdW#4r7G%a3<(HPE7RAGqPqt&r;#L8M zEF%yXOEFH~%%;P9mmzHOeKtkzn_}{lvoA0RKVT4;oWZUzS(Uw7`wIgLBhv>S21b$V i3`!Rnly0!d+~5|w!KHqIQ{w|W1Eca+kb)v5pm6}+{&Q9U delta 327 zcmZ1}xQd_eG%qg~0}vc-Ii1PCF_BM#v2LPzsSFcCD$8n+I1ogMpzuT|u8^ru70EJ$ z$Rd-e+$mfs-06&Kcvdq*#2FbnP2o-9BT6Md7M09mS%#Af7)9;*Qv}cq5yxts zaEf4x5UR>bVNH?E?-;9?CbzImWA@XO-0aF~%{X}noBL!D_DpU?pa&R%xcCFZ StandardTranscriptionResult: """ Endpoint para transcribir audio simple infiriendo el proveedor de IA diff --git a/app/api/v1/endpoints/document/__pycache__/rubricated_analysis.cpython-312.pyc b/app/api/v1/endpoints/document/__pycache__/rubricated_analysis.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..cde56cd14a3be25ecf1cab7c8a4a64c67b42f96412554998dca884033a2a52fd GIT binary patch literal 2007 zcmah~O^g&p6t3!?o}V6efk+TsUCOA3QJ9Grjmrkyxcr5Py9=5)0VYLv?QW6&qpEs; z5QqU{IC#=TFq}v@NWjx_FkC#$5f5X~pdAe+a?sntYNF`DS2fe)CL~%(SG}r!_1^36 zeed<{fdLo6xb1(%lOjSt>rFmjZ^p`1SS}(THIa{fE5M=Ew6K=#zzz$|Lg+M|u-Ghy zrDiECH_PT-A*h6I(+#W5s@XfinsA^wfRTk*;d;+>bC3-l-_qNxTKpob-Y8wSx<_!s z$!|jj|u3qfX}eV+X%iIEZJuiV&WO?I4c`50h9ZD#(eneT^g{o@309MGpuGu__*L zk8LF^00%M+6a~MMvoH~ifC7BIU^5g}trBt>#>4~#2tgF>E0GW86DnvQ)VlktPE!qM z)gY#R3vg!9T+PWUV-x$P;#4t_mBv{DpC~i;?L;f><^jtJW**pk=+MNj1&<|)$5GA7 zp2*WM#ZIK)(`XGJRDdNFe*SU{FkX~gPMNO&=UV`+Acf3B0NpgU=DC_}X%x86#K|>{ zo(8y)aA6PtNI+z*F!ouHt?S{6h4SvMIRQQL#jt?gq}xY=(MGr+`exq?ZJ#wR&MKtotMH{ z?Bmnc8EYQTqXqk2G><>G=W(r&E@Y+12-a2-i0WHRRmRF?<_{WGBM+!;{I28cvy;(kIiY&Ji_xP`EgGQgMhMOiMv92ac zMElk;!9A*3+|8gVka|BdBTSi8t%~=*2{S^v#L@JJ6%;4Nc)c?^icGMA#YqVQ}a(`{o;8;HBN)3~uj~xBsx162!mo^+8sIAXREnUa07QkrwiO_gtrD+NfCtMU@ zCv?>a{g)EDX1cIG12EL3H_JjQ>-pEW;F9ScqM{Y`FE4x1C>k)?znb+`J)#BOHi7OX zy9wOXw*JUek{FVyal3|AU`ApiOpnT=K;txEJH)fF)ono@g$Xdh_<>cyh2J+K zyyYI+^DWwQ4;}dtz4M@mo_^u-_72+g6W)9m4}Xn^FP-V^INZU*ckq$#$M?7QJwWU5 ZXs155Y}Tpdp StandardDocumentAnalysisResult: + """ + Endpoint para analizar documentos usando inteligencia artificial + + Args: + document_request: Objeto DocumentRequestFile que contiene el archivo de documento, + el proveedor, el modelo y la rúbrica de evaluación. + + Returns: + StandardDocumentAnalysisResult: Resultado del análisis de documentos en formato estándar de Qualidot + """ + try: + # Analizar el documento usando el adaptador de análisis que infiere el proveedor + analysis_result = await evaluate_document_with_provider(document_request) + return analysis_result + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) \ No newline at end of file diff --git a/app/api/v1/endpoints/router.py b/app/api/v1/endpoints/router.py index a1163f8..09c0875 100644 --- a/app/api/v1/endpoints/router.py +++ b/app/api/v1/endpoints/router.py @@ -17,18 +17,19 @@ from fastapi import APIRouter, UploadFile, File, HTTPException from fastapi.security import APIKeyHeader # Importar los routers específicos de cada módulo -from app.api.v1.endpoints.audio.transcription import audio_router_transcription # Endpoint de transcripción de audio -from app.api.v1.endpoints.texto.resume import text_router_summary # Endpoint de resumen de texto -from app.api.v1.endpoints.texto.rubricated_analysis import text_router_analysis # Endpoint de análisis rubricado de texto -from app.api.v1.endpoints.video.transcription import video_router_transcription # Endpoint de transcripción de video -from app.api.v1.endpoints.image.rubricated_analysis import image_router_analysis # Endpoint de análisis rubricado de imágenes +from app.api.v1.endpoints.audio.transcription import audio_router_transcription # Endpoint de transcripción de audio +from app.api.v1.endpoints.texto.resume import text_router_summary # Endpoint de resumen de texto +from app.api.v1.endpoints.texto.rubricated_analysis import text_router_analysis # Endpoint de análisis rubricado de texto +from app.api.v1.endpoints.video.transcription import video_router_transcription # Endpoint de transcripción de video +from app.api.v1.endpoints.image.rubricated_analysis import image_router_analysis # Endpoint de análisis rubricado de imágenes +from app.api.v1.endpoints.document.rubricated_analysis import document_router_analysis # Endpoint de análisis rubricado de documentos # Inicializar el router de FastAPI para los módulos de procesamiento api_router_audio = APIRouter() api_router_text = APIRouter() api_router_video = APIRouter() api_router_image = APIRouter() - +api_router_document = APIRouter() api_router_audio.include_router( audio_router_transcription, @@ -58,4 +59,10 @@ api_router_image.include_router( image_router_analysis, prefix="/image", tags=["Procesamiento de Imágenes"] -) \ No newline at end of file +) + +api_router_document.include_router( + document_router_analysis, + prefix="/document", + tags=["Procesamiento de Documentos"] +) \ No newline at end of file diff --git a/app/api/v1/endpoints/texto/__pycache__/resume.cpython-312.pyc b/app/api/v1/endpoints/texto/__pycache__/resume.cpython-312.pyc index fa6687b524980da89af09a7900e1338a3891e53502e15489fae6a40f8acdc9a9..bb26e2dd0ad24c115f310e5d43c7b57579c586e59908004dd167ed8ae418b1da 100644 GIT binary patch delta 67 zcmeys|DK=sG%qg~0}#ZgoXOm|k@pf4HM*}QD$oK=GRP@Sh#d9mdYIAnRk7(X%zFxnKU0QCU?qeT)n delta 70 zcmca4a9M!&G%qg~0}vc-Ih`rIk(Y&;k5@mnEHS4vu_QA;uULPx95W*;m;D9n& StandardTextAnalysisResult: """ Endpoint para resumir texto simple infiriendo el proveedor de IA diff --git a/app/api/v1/endpoints/texto/rubricated_analysis.py b/app/api/v1/endpoints/texto/rubricated_analysis.py index a0200ef..9af8bd5 100644 --- a/app/api/v1/endpoints/texto/rubricated_analysis.py +++ b/app/api/v1/endpoints/texto/rubricated_analysis.py @@ -19,7 +19,7 @@ from app.services.text.evaluations_adapters import evaluate_text_with_provider # Inicializar el router de FastAPI para este módulo text_router_analysis = APIRouter() -@text_router_analysis.post("/evaluations/", response_model=StandardTextAnalysisResult) +@text_router_analysis.post("/evaluations", response_model=StandardTextAnalysisResult) async def evaluate_text(text_request: TextRequestFile = Depends()) -> StandardTextAnalysisResult: """ Endpoint para analizar texto usando una rúbrica de evaluación infiriendo el proveedor de IA diff --git a/app/api/v1/endpoints/video/__pycache__/transcription.cpython-312.pyc b/app/api/v1/endpoints/video/__pycache__/transcription.cpython-312.pyc index 129eba5bdcd68cfaa8ec8070f51fd0d82fabe2f015654370155f7eff2190a551..8e80452b85317eb7584b15067bf26d3cb662edb76af419372cf9baaf3dcf4d8e 100644 GIT binary patch delta 69 zcmdnUzk#3kG%qg~0}y1ToXK?G$h(+{mq)*(C^4@%xhS)sq StandardTranscriptionResult: """ Endpoint para transcribir video simple infiriendo el proveedor de IA diff --git a/app/main.py b/app/main.py index ba7c11d..7d7c2f3 100644 --- a/app/main.py +++ b/app/main.py @@ -1,5 +1,5 @@ from fastapi import FastAPI -from app.api.v1.endpoints.router import api_router_audio, api_router_text, api_router_image, api_router_video +from app.api.v1.endpoints.router import api_router_audio, api_router_text, api_router_image, api_router_video, api_router_document app = FastAPI( title="Template de API de Procesamiento general", @@ -11,6 +11,8 @@ app.include_router(api_router_audio, prefix="/api/v1", tags=["Procesamiento de A app.include_router(api_router_text, prefix="/api/v1", tags=["Procesamiento de Texto"]) app.include_router(api_router_image, prefix="/api/v1", tags=["Procesamiento de Imágenes"]) app.include_router(api_router_video, prefix="/api/v1", tags=["Procesamiento de Video"]) +app.include_router(api_router_document, prefix="/api/v1", tags=["Procesamiento de Documentos"]) + @app.get("/") def root(): @@ -26,18 +28,22 @@ def root(): "docs": "/docs", "endpoints": { "audio": { - "transcripción de audio": "/api/v1/audio/transcripts/", + "transcripción de audio": "/api/v1/audio/transcripts", }, "texto": { - "resumen de texto": "/api/v1/text/summaries/", - "análisis rubricado": "/api/v1/text/evaluations/" + "resumen de texto": "/api/v1/text/summaries", + "análisis rubricado": "/api/v1/text/evaluations" }, "imágenes": { - "análisis rubricado": "/api/v1/image/evaluations/", + "análisis rubricado": "/api/v1/image/evaluations", }, "video": { - "transcripción de video": "/api/v1/video/transcripts/" + "transcripción de video": "/api/v1/video/transcripts" + }, + "documentos": { + "análisis rubricado": "/api/v1/document/evaluations" } + }, "modelos_disponibles": { "audio": { diff --git a/app/schemas/__pycache__/document_standard.cpython-312.pyc b/app/schemas/__pycache__/document_standard.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..723c60eb51eb58681197dcd100b761c02af21b8363ed17134c119b581390b26a GIT binary patch literal 5006 zcmc&&O>7&-72YM66iJc#w?o0=PZt(nd%7QowKn6v(Zo00Mex4>Yuga4$ts^w6FhSVn=I`rho4TG?XV z00Byfk2CXT-n`lQ-h1Eb&xJxxf)Bm+7xQ-mlJs}%bUvx5@#s&`_)IdSs$|GU!jkPo zH6dd=X(er?s))8?rR;PyZTD6CL_cL^>})k_=c+l;Ph0(VzM8iS)q*S~q_-ub?-R+$ zknH_rxKDLJjAmgp7aJWU2{GOe@b9)>MP2jBw9#HG`^zHweQ9jM~&77UkH~oYlLQ$xYEQsP5UsaVan5au*nF ztbV~wmmbck@Ht85Ooyn%Qs*eMH5W~~t9KnkV`vENb$3H+3m&n+lG>n5D7V^F7>30g z8&9jIV~_@M3=!-BRwK zcz;DX$Qqif>6XShbaIy)7S)VXrbUX0K*3*-@Fby^eM{qH7B3}8oid4K;EZ!7cZ1CN zhHFwsv-AiP;zx-(kA4e{&mRLQU~Mj@?T=~uNuCr8X%Z~s7iQ*hW;0zZ1UpVfaGF`rEKkF8oM5I) zn5lUVOcDpC;(i!`htA=NYpUm{dN@l!m58IHD>Ap z@|&*Kv{&!ei9>j)-$#6Jy#vra{ zS(<@+ZWv>bW+d+rhzp+MdLbfJjhl4`r)nEBVHt0DtM@Qetc@@XQe3BucrStpM1VLF z_pWuk6>%DSC_(>*QY#JwnOY4UuGNBEtp*|IS=i3kYS#gtk*2R!gMh2mSOzv>gCK$= zcUdr4t7(oyU2#<$^mrIHg!m@oXb){$ z)>~8W_(SKGPTtT%C7 zsrrUGV|cI#5)ihXIuyKlXuWl?7-S-2O|Jx-b|bD=xW#i2hs9AoD-N&`Sk4f(Yz)OV z6y5k@+p#;2Ld4WAY>7D9jV%nO5c_!5AvB-E*3OPVwDJ!?U_8{Lc$oEvDq%cSqImeJ zKmJ}A5AU^i?foi~|7z^!dMdd)y>$Ba{4)QQ{YXlt(qCt!gNOd$w$3#D1B;+BTBG|P z43vCjG29w=-8w3M4VrIVZ!%Dg(y^KmR+PS|qM$Yt)AoyclkL`qcmNwB7C}bf)iI!2IG@3cB)+wz60|+w6XH0{z#qVvn3b1{0 z-2!kk>g+#I3{sHIiRB?KI*y*bbiM-RLyg69kY*wiREmWyA&In?VI^cpB0@-}v9%Y4 zfGL(9h7rr2K;sKIh5~2sSrDH}e^ZXM$Ddm{(c1rkKVAbg?psr8?XiiK(bm47`D5<` zy!18Y{r0fP;OG6}3y_^(TT?EyCwH#st@1g4a(3y|?X$iz8-fq*ZwP?Y9J zB5;oeyDmkgu>>`*pjy&%+Ek| z7Viu(iW0`b6!&xp3qCciP7%f^n~DM=4NF{)FCL7F1Z8I31jj?3vsH*i0cOx(xHGwi zQH6BHI}Bbt8C+fNANm~UPmy!yA>59inJNZ_PMp;|P7LqOjYlS`M#LtcZLkxEU@|8H zQP`_IK8Ehas2-9{28WGPzKvwEHRlr)%ew%UP5_Sr63k3^Z=!I8Kyjh0iLsD%hUT$I z!;27{Teu=J7UT|2k=w#(cf%kv2k%VRH2oKkkr_O@ymeSD?5{($^?=)M=wq?GinC;13 zE4=lp<5VFn$OIQalmnH}rGs{Ki1@9ptwxWT9U9Ek__rMR`VK zxn`o63nqHy8TK-`RAHD1LNGgB!2ew6tPi?^LI!EK*)W}Ycs@ZJ@f|5-hoUHp z2oQ(dT(ZB(9EA)4?|^TxAHoV5zMO)1n3QGt>qJgg9&eZAJ^zq){Z)EN{JpTQq~v3X zJCo}Y{;m&XVovS#LC)T$sAXl1? JCHxi0{|~pqgA4!w literal 0 HcmV?d00001 diff --git a/app/schemas/document_standard.py b/app/schemas/document_standard.py new file mode 100644 index 0000000..94cfb53 --- /dev/null +++ b/app/schemas/document_standard.py @@ -0,0 +1,60 @@ +""" +Esquema de resultado esperado de modelos de análisis de documentos. + +Propósito: + Define el formato estándar de los resultados devueltos por los modelos de análisis + de documentos, independientemente del proveedor de IA utilizado. + +Homologación: + Garantiza que el resultado del análisis de documentos siempre se entregue en el mismo + formato estándar para Qualidot. +""" +from dataclasses import dataclass + +from fastapi import UploadFile +from fastapi.params import File, Form +from pydantic import BaseModel, Field +from typing import List, Optional + +class DocumentEvaluationCriteria(BaseModel): + """Modelo que representa un criterio de evaluación específico dentro de una rúbrica de análisis de imágenes.""" + name: str = Field(..., description="Nombre del criterio de evaluación") + description: str = Field(None, description="Descripción detallada del criterio") + score: float = Field(None, description="Puntuación asignada al criterio después del análisis") + subcriteria: Optional[List["DocumentEvaluationCriteria"]] = Field(None, description="Lista de subcriterios de evaluación") + +class DocumentEvaluationRubric(BaseModel): + """Modelo que representa una rúbrica de evaluación personalizada para análisis de documentos.""" + name: str = Field(..., description="Nombre de la rúbrica de evaluación") + description: str = Field(None, description="Descripción detallada de la rúbrica") + category: Optional[str] = Field( + None, + description="Contexto jerárquico de la categoría para especializar a la IA (ej. Audio > Education > English > B2)" + ) + criteria: List[DocumentEvaluationCriteria] = Field(None, description="Lista de criterios de evaluación específicos") + +@dataclass +class DocumentRequestFile: + """Modelo de solicitud para análisis de documentos.""" + file: UploadFile = File(..., description="Archivo de documento a procesar (ej. pdf, docx)") + provider: str = Form(..., description="Proveedor de IA a utilizar (ej. openai, google)") + model: str = Form(..., description="Modelo de IA a utilizar (ej. vision-1)") + rubric: UploadFile = File(..., description="Archivo JSON de evaluación") + +class StandardDocumentAnalysisResult(BaseModel): + """Modelo que representa el resultado estándar de un análisis de documentos para Qualidot.""" + status: str = Field(..., description="Estado del análisis (ej. 'success', 'error')") + original_filename: str = Field(..., description="Nombre original del archivo de documento procesado") + provider_used: str = Field(..., description="Proveedor de IA utilizado para el análisis (ej. 'OpenAI')") + model_used: str = Field(..., description="Modelo específico utilizado para el análisis (ej. 'vision-1')") + score: float = Field(None, description="Puntuación general asignada al documento después del análisis") + feedback: str = Field(None, description="Comentarios o retroalimentación generada por el modelo de IA sobre el documento") + detailed_criteria: List[DocumentEvaluationCriteria] = Field(None, description="Lista de criterios de evaluación detallados con sus respectivas puntuaciones y descripciones") + + +class StandardDocumentAnalysis(BaseModel): + """Modelo que representa el resultado estándar de un análisis de documentos para Qualidot.""" + score: float = Field(None, description="Puntuación general asignada al documento después del análisis") + feedback: str = Field(None, description="Comentarios o retroalimentación generada por el modelo de IA sobre la imagen") + detailed_criteria: List[DocumentEvaluationCriteria] = Field(None, description="Lista de criterios de evaluación detallados con sus respectivas puntuaciones y descripciones") + \ No newline at end of file diff --git a/app/services/document/__pycache__/evaluations_adapters.cpython-312.pyc b/app/services/document/__pycache__/evaluations_adapters.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..840c5c0b13a8f8757bc22fd50146a1fe1492be99cd3ece9ae68f68dd9d8eddb3 GIT binary patch literal 6651 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 aVkX4R3gcsfR!xX86O$K>M;KELM*M&Jl9WgQ literal 0 HcmV?d00001 diff --git a/app/services/document/__pycache__/prompt_builder.cpython-312.pyc b/app/services/document/__pycache__/prompt_builder.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..59fe08457ab02b27d3b688fe8a7cbcec20e3b10b2b71e8400df13b5a5f30eeae GIT binary patch literal 3648 zcmbVPO>87b74DuHkC*m(5stDyKv0QD!8W^N0!ai5LO9-8Vq$w%o^iCX2s1TZWlwo~ zx`(Q6+hchpAGmTQ{0K!kArd5za^r;L3lbb0B_h&XK;o3!ppAsI;=uQ+x@Q291B~SE z>h4$X@B7|+X&!R z{mO2|_e4dO#f+TUtBC3kD!bKTX=ye)^F1%vJby=IQNm;#a=p(*!h~dD5}QM%}o}lcql{KlaQc3|7};R zkl!Sg(4WfV)Y_6}AVb9?CAeTi`YO%$Guh|tJI%%x9tKjwVvPlLlSU5RQR$3S7Yq+(QUMtITAy z&kQokfMG^5evpw2gYCUU_c;R55_YvJGGj6}NFbwCqFI0phSKo9g5CsLd3^q6q+qBP zd`K8>EtN*s?<8T?hd|wCG=*o|8D(sAz3CD!3W?R2M?>r$Rkf3-YMEv_4uR+7H-}Xj ze~`#5fqObzJRDpqR@3|2`4So{6OAR< zwFI8Z(Mn(b@9$$@%secM>S_6Bo|v`QOQ$~8pRnuTw1oBC&*vUiPAg4s>2h`kWdXx) z%`tW;KbEl{M@PH;|Kz0I+xn352M90A<1osEY{y{LsH$QCIG21)2{^g&ZIp!uV+s^m z0prO*@HE?QY}A8SlZWe4g6pVThVa<;|(+ zO|Vzw7H@?p+b#(Rymh2x$)2?h&L1j)4=NsHDeIy>1#7mp*VkGbud*a&L-Jq}ASc3G z)#UhOvuiK*o8x*dK(bn^&6k69Bp|ZgiR_R%ml0dtWRm>Mn}`b(MLIws0~DK*n05ZM z);j1iHZv&n2YFI#U?Qa=<@JEo=qf7QtWXkZCdU$HdoiT*ji(&RlnZ~ znt5M6ak;(6)81`%GDivA-gyx~1SdbFhx#k48#}A5bu`=UosD`kc(w+n+;N;*%Mm7Y z7T31dTkC798%wvBms#<}rXGU=^hCEOsL(kvJye*Yw_;7{ns`ph*AmnQwGu4|2oR4j z2RZ+arUT9Sbf}?JSe#I5X~v-UXNKfyzMzm46-7Rig!ww-g8|f%CQGV46;8kA-eChz zAp*9`V;-g~0&0nE1=H9QSl+Nz+!pC`jYHm41C2JMMe-H*;+M5?Sn)t#!P?+>sZ2dC|Qz+y~1=} zGgI41Tw^WDf#s(qmX>=Fpkck_h#g6k47u)@+?O&%<>OgMgJ=e}(rg7IQIkA6H*3Lj z-vF6j$+o0lJJ3Hi=9sAz$Eb!a6TSH7!P^R12K7w z4upd(OS06^R3eMkj&se1T)%&>zSgSWaUYs%FV#0!gU5cbR2_Y?p9mSXMb;m*$;PN+ zuQT+*XgZROKIKH{ecM>)N0*A`wKSt=NM_D;9H-){>&#NL;S}a#vFnL{Cw&`5)f-ja zVc(D%I##jAjg?-~mn(?V@c4zVtx&-!oSRn^ztvZvPTUK*g8rL-lx9WbhuIaUuied5 zB&4nlh9hRz?LwkG#H!1|8+d`3tY~;@fGih8y_qzzPqsa8}YkmFBTTxTd1GC z{882W+~?oe{mJh8i{JWSar@oH?e`X27q5Nq{ioU&l~0c^d9~+$-F~b6<7eJj`RU5V zi?3Z&u8pguh0y;?czDr08hf+H{#EF{wtVsA^4KeX(H~d6()`&x)>nq~aN=*4#-2Cy zw{V-=D*5B9Ug`RUcYW;nH~jJQpSe;wyF31>cX{D#^ATM2mS#t@ZHlbj))c_#DI}%l zP%^bi#Kq9kWym;G%b=@`` StandardDocumentAnalysisResult: + """ + Función de adaptador para evaluar documentos usando el proveedor de IA configurado. + """ + provider = document_request.provider.lower() + + content = await document_request.rubric.read() + rubric_dict = json.loads(content) + rubric = json_to_rubric(rubric_dict) + prompt = build_document_evaluation_prompt(rubric) + + match provider: + case "openai": + return await evaluate_with_openai(document_request, prompt) + case "claude": + return await evaluate_with_claude(document_request, prompt) + case "gemini": + return await evaluate_with_gemini(document_request, prompt) + case _: + raise ValueError(f"Proveedor de IA no soportado: {document_request.provider}") + +# Función de adaptador para evaluar documentos usando OpenAI +async def evaluate_with_openai(document_request: DocumentRequestFile, prompt: str) -> StandardDocumentAnalysisResult: + """ + Función de adaptador para evaluar documentos usando OpenAI. + """ + + client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY) + + document_bytes = await document_request.file.read() + + base64_document = encode_document_from_bytes(document_bytes) + media_type = document_request.file.content_type + + try: + response = await client.chat.completions.create( + model=document_request.model, + messages=[ + {"role": "user", "content": [ + {"type": "text", "text": prompt}, + { + "type": "file", + "file": { + "file_data": f"data:{media_type};base64,{base64_document}", + "filename": document_request.file.filename + } + } + ]} + ], + response_format={"type": "json_object"} + ) + + resultado = json.loads(response.choices[0].message.content) + + return StandardDocumentAnalysisResult( + status="success", + original_filename=document_request.file.filename, + provider_used="openai", + model_used=document_request.model, + **resultado + ) + + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"Error evaluando el documento: {str(e)}" + ) + +# Función de adaptador para evaluar documentos usando Claude +async def evaluate_with_claude(document_request: DocumentRequestFile, prompt: str) -> StandardDocumentAnalysisResult: + """ + Función de adaptador para evaluar documentos usando Claude. + """ + client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY")) + + document_bytes = await document_request.file.read() + + base64_document = encode_document_from_bytes(document_bytes) + media_type = document_request.file.content_type + + if media_type not in ["application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"]: + raise ValueError(f"Tipo de documento no soportado por Anthropic: {media_type}") + + try: + messages = [ + { + "role": "user", + "content": [ + { + "type": "document", + "source": { + "type": "base64", + "media_type": media_type, + "data": base64_document + }, + }, + {"type": "text", "text": prompt} + ], + } + ] + + response = client.messages.create( + model=document_request.model, + max_tokens=4096, + messages=messages, + ) + + json_string = response.content[0].text + parsed_data = json.loads(json_string) + + return StandardDocumentAnalysisResult( + status="success", + original_filename=document_request.file.filename, + provider_used="Claude", + model_used=document_request.model, + **parsed_data + ) + + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"Error evaluando el documento: {str(e)}" + ) + +# Función de adaptador para evaluar documentos usando Gemini +async def evaluate_with_gemini(document_request: DocumentRequestFile, prompt: str) -> StandardDocumentAnalysisResult: + """ + Función de adaptador para evaluar documentos usando Gemini. + (Plantilla para futuras implementaciones) + """ + # Aquí iría la implementación específica para Gemini + pass \ No newline at end of file diff --git a/app/services/document/prompt_builder.py b/app/services/document/prompt_builder.py new file mode 100644 index 0000000..7771593 --- /dev/null +++ b/app/services/document/prompt_builder.py @@ -0,0 +1,58 @@ +""" +Módulo encargado de construir el prompt estandarizado para la evaluación de imágenes, +inyectando la rúbrica, el contexto de especialidad y el esquema JSON esperado. +Este módulo es crucial para asegurar que los evaluadores expertos reciban instrucciones +claras y consistentes, y que sus respuestas se ajusten al formato requerido para su posterior +procesamiento y análisis. +""" + + +import json +from app.schemas.document_standard import DocumentEvaluationRubric, StandardDocumentAnalysis + +def build_document_evaluation_prompt(rubric: DocumentEvaluationRubric) -> str: + """ + Construye el prompt estandarizado inyectando la rúbrica, + el contexto de especialidad y el esquema JSON esperado. + """ + + # Extraemos el JSON de la rúbrica de forma limpia (ignorando nulos) + rubric_json = rubric.model_dump_json(exclude_none=True, indent=2) + + # Extraemos el esquema dinámico de salida basado en Pydantic + expected_output_schema = json.dumps(StandardDocumentAnalysis.model_json_schema(), indent=2) + + # Obtenemos el path de especialización + # Si por alguna razón viene vacío, le damos un rol genérico por defecto + specialization_path = rubric.category if rubric.category else "General Document Analysis" + + # Ensamblamos el prompt con f-strings + prompt = f""" +# ROLE +You are a highly specialized Expert Evaluator with deep domain expertise in the following area: **{specialization_path}**. Your objective is to perform a highly accurate, objective, and domain-calibrated analysis of the provided input based STRICTLY on your specialization and the provided evaluation rubric. + +# TASK +I will provide you with an input and a JSON object representing an `EvaluationRubric`. +Your task is to analyze the input and score it against every criterion and subcriterion defined in the rubric. You must justify your scores with objective feedback based on evidence. + +# DOMAIN CONTEXT +Specialization Path: {specialization_path} + +# RUBRIC +{rubric_json} + +# EVALUATION RULES +1. **Domain Calibration (CRITICAL):** Calibrate your expectations, strictness, and feedback entirely according to the **{specialization_path}** context. Do not evaluate using generalized standards; apply the specific standards expected at this exact level and category. +2. **Strict Adherence:** Evaluate ONLY the criteria and subcriteria listed in the rubric. Do not invent new metrics. +3. **Scoring:** Assign a numeric `score` to each criterion and subcriterion. The score must reflect how well the input meets the description of that specific metric. +4. **Objective Feedback:** Generate constructive, evidence-grounded `feedback` for the overall evaluation. Mention specific elements or patterns observed in the input that justify the scores within the context of the specialization. +5. **Subcriteria Handling:** If a criterion has `subcriteria`, evaluate and score each subcriterion individually. The parent criterion's `score` MUST be the exact mathematical **sum** of its subcriteria scores. +6. **Final Score Calculation:** The overall final `score` of the evaluation MUST be the exact mathematical **sum** of all the main criteria scores. + +# OUTPUT FORMAT +You MUST return your response EXCLUSIVELY as a raw, valid JSON object that strictly adheres to the following JSON Schema definition. Do NOT include markdown blocks (```json), explanations, or any text outside the JSON object. + +# EXPECTED JSON SCHEMA +{expected_output_schema} +""" + return prompt \ No newline at end of file diff --git a/app/services/image/__pycache__/evaluations_adapters.cpython-312.pyc b/app/services/image/__pycache__/evaluations_adapters.cpython-312.pyc index 1572b1c39d72006e4bb244e44bf82f93e9e72438f5ac23797734cc3e863b174f..fd9d06955f135282ef4c5bf251eb1e87e824ac32d55537abce5751b4c7b916eb 100644 GIT binary patch delta 3319 zcmZuzYfK#16~41O`(WSfvMdYxg2k{4n5T^ib^@5U!N!I-t*NEWurt7}_mw-d!6v)e zG<|4YRZXH%>r_cqtFlx_CT$`r)m1CCssA9iKX!_xp;MwZYNYm0Ra8@{N?NJ+&N2(O za-})wE_CR?&{8sC43k0o7_!s>& zL7NkSWaDBZ0g!G0`prYt79~r!Ww} zq7ey67hO%G0kM)U$*?T7f+F}?b6feBAwFlGwT7{ClOmqk6wPlF{-_E8N--hjYv_Bt zP699k2yl!4t~C%=i?-6AyucLLeKBrno=CW#;$m=rxQfru0R0^BbA~iOgQU$L-KnyOB@)1pW=t zk7eV45_DMSP26e4il4!8efFG*8?0c@DcWIOnXEZyhOIesd<4gxuT~Z$Z{bE2@A#Oa z9o9KZ#nYN1X-Cc!Bl1>mQt_bc=o)++Rq-j@vBrrv8*7EycgdXf4)Gmg0h|JF65oCp z1YD;Q)(oI8Z+qgbySN#pHD^?VRse}#}Ghe zBAbfF9&DxpG=JOPTj-QQIE%=aJC}y3G(3wsfOwzQtDJ&WohcFEX0}wODy7uZTovm$G^O6buC4y)* zlTM+r;KL&6l@N%}(|U;iiOF3EphXEN4XBrKJfaq;#{ssd7YgDWf_T{>x^Mujmc3RO zPA29ES|3d45EF-_eW7=O%Pv7XHcXe%AVxBo>9GZdgP39@*Tpo;oiE@y&Sg0~zj@}R zEW>gTbFZYC#&ed)F*I3ZBoXII{3i;O@1P)Rf>_Ylu?`)J zzk~n02KG&$E+D$P1y^^8tofs{Vb{{IQ`fNLxU{cPTMc_aU1iua0ITb@$sM!rwTV6Q zl*X{*35%Xi!P6;v`UFqkw&$c+s6AOU?rVXgNwoJ0_TFv#iK1@T>KCmog03ktTN?B6c9+jo8KqOV8r_1tfG%5 zZQoPtYApLYqr*_t?bU;tI?)jl9HG0e`vWCMXxlMX)c?ikcw^}1kZAM^M*p25F*qm$ z2mfFk+_g89e4Tf%|FrO4LF_swbe$`8&Xhc}+xGJ%)A>CusP&1?V}kS8ZAx%PLZb7y z;5;rmdj)5&={8c=W5yQlipTYVa=52#UV<;}8r$MruI(ZT6Y`XX++gvC4-U&Ufz2unFpjdF4#(u$>4;Hwyx(-XhQj-+Ne zF`IES7VmFf^L#+G9G7&F2%U~bBJ!Z5s$>k&J6cOt(sN8Qvm8S&({J!2z98Y|8Q%w47#|o3FbNYF2e9blYPJ!{b%ie0#*Ok0%DZ-Gnf;= q+{fS<{-OU=<0L^CO5MYI04rO;a$TBFK9+&Kc@^ItXdvG~{rn#xA}~+@ delta 2634 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 M=Pw|tNF3-a0R5~omjD0& diff --git a/app/services/image/evaluations_adapters.py b/app/services/image/evaluations_adapters.py index 8abdcbf..d754f13 100644 --- a/app/services/image/evaluations_adapters.py +++ b/app/services/image/evaluations_adapters.py @@ -24,6 +24,12 @@ from app.core import config from app.utilities.image_utilities import json_to_rubric, encode_image_from_bytes from app.services.image.prompt_builder import build_image_evaluation_prompt from anthropic import Anthropic +import re + +# Importaciones de Clarifai +from clarifai_grpc.channel.clarifai_channel import ClarifaiChannel +from clarifai_grpc.grpc.api import resources_pb2, service_pb2, service_pb2_grpc +from clarifai_grpc.grpc.api.status import status_code_pb2 # Función de adaptador principal que infiere el proveedor y llama al adaptador específico async def evaluate_image_with_provider(image_request: ImageRequestFile) -> StandardImageAnalysisResult: @@ -91,62 +97,77 @@ async def evaluate_with_openai(image_request: ImageRequestFile, prompt: str) -> detail=f"Error evaluando la imagen: {str(e)}" ) -async def evaluate_with_clarifai(image_request: ImageRequestFile, rubric: ImageEvaluationRubric, prompt: str) -> StandardImageAnalysisResult: +async def evaluate_with_clarifai(image_request: ImageRequestFile, prompt: str) -> StandardImageAnalysisResult: """ - Función de adaptador para evaluar imágenes usando Clarifai con un modelo Multimodal. + Función de adaptador para evaluar imágenes usando Clarifai. """ - try: - # 1. Obtener el token de configuración (PAT) - pat = settings.CLARIFAI_API_KEY - if not pat: - raise ValueError("La clave CLARIFAI_API_KEY no está configurada en el entorno.") - # 2. Obtener la URL del modelo enviada en la petición - model_url = image_request.model + + CLARIFAI_API_KEY = settings.CLARIFAI_API_KEY + if not CLARIFAI_API_KEY: + raise HTTPException( + status_code=500, + detail="No se encontró CLARIFAI_API_KEY en las variables de entorno" + ) - # Inicializar el modelo de Clarifai - model = Model(url=model_url, pat=pat) - - # 3. Leer los bytes de la imagen subida + USER_ID = "openai" + APP_ID = "chat-completion" + + try: image_bytes = await image_request.file.read() - if not image_bytes: - raise ValueError("El archivo de imagen recibido está vacío.") + # 2. Preparamos y ejecutamos la llamada a Clarifai (gRPC) + channel = ClarifaiChannel.get_grpc_channel() + stub = service_pb2_grpc.V2Stub(channel) + metadata = (('authorization', 'Key ' + CLARIFAI_API_KEY),) + userDataObject = resources_pb2.UserAppIDSet(user_id=USER_ID, app_id=APP_ID) - # 4. Preparar el input multimodal para Clarifai combinando la imagen y el prompt de evaluación - multimodal_input = Inputs.get_multimodal_input( - input_id="image_evaluation", - image_bytes=image_bytes, - raw_text=prompt + request = service_pb2.PostModelOutputsRequest( + user_app_id=userDataObject, + model_id=image_request.model, + inputs=[ + resources_pb2.Input( + data=resources_pb2.Data( + image=resources_pb2.Image(base64=image_bytes), + text=resources_pb2.Text(raw=prompt) + ) + ) + ] ) - - # 5. Llamar a la API de Clarifai para evaluar la imagen - predict_response = model.predict([multimodal_input]) - # Extraer el texto crudo de la respuesta del modelo - raw_output = predict_response.outputs[0].data.text.raw - - # 6. Limpiar la respuesta y convertirla a JSON - # Los LLMs suelen devolver el JSON envuelto en bloques de markdown (```json ... ```) - clean_json = raw_output.replace("```json", "").replace("```", "").strip() + response = stub.PostModelOutputs(request, metadata=metadata) - # Convertir el string limpio a un diccionario de Python - parsed_data = json.loads(clean_json) + if response.status.code != status_code_pb2.SUCCESS: + raise Exception(f"Clarifai Error: {response.status.description}") + + # 3. Extraemos la respuesta cruda + raw_output = response.outputs[0].data.text.raw - # 7. Retornar el resultado validado contra tu esquema estándar de Pydantic - return StandardImageAnalysisResult(**parsed_data) - - except json.JSONDecodeError as e: - # Error específico si el modelo alucinó texto extra y no devolvió un JSON válido - raise HTTPException( - status_code=500, - detail=f"El modelo de Clarifai no devolvió un JSON válido. Error: {str(e)} | Respuesta cruda: {raw_output if 'raw_output' in locals() else 'N/A'}" + + + # 4. Limpiamos y parseamos el JSON devuelto + json_match = re.search(r'\{.*\}', raw_output, re.DOTALL) + + if json_match: + clean_json = json_match.group(0) + parsed_data = json.loads(clean_json) + else: + # Respaldo en caso de que el modelo devuelva texto plano en lugar de JSON + parsed_data = {"raw_response": raw_output} + + # 5. Retornamos el modelo estandarizado + return StandardImageAnalysisResult( + status="success", + original_filename=image_request.file.filename, + provider_used="Clarifai", + model_used=image_request.model, + **parsed_data ) + except Exception as e: - # Captura cualquier otro error (problemas de red, token inválido, URL incorrecta, etc.) raise HTTPException( - status_code=500, - detail=f"Error interno al evaluar con Clarifai: {str(e)}" + status_code=500, + detail=f"Error evaluando la imagen con Clarifai: {str(e)}" ) async def evaluate_with_claude(image_request: ImageRequestFile, prompt: str) -> StandardImageAnalysisResult: diff --git a/app/services/image/prompt_builder.py b/app/services/image/prompt_builder.py index cddac68..1b62641 100644 --- a/app/services/image/prompt_builder.py +++ b/app/services/image/prompt_builder.py @@ -44,9 +44,10 @@ Specialization Path: {specialization_path} # EVALUATION RULES 1. **Domain Calibration (CRITICAL):** Calibrate your expectations, strictness, and feedback entirely according to the **{specialization_path}** context. Do not evaluate using generalized standards; apply the specific standards expected at this exact level and category. 2. **Strict Adherence:** Evaluate ONLY the criteria and subcriteria listed in the rubric. Do not invent new metrics. -3. **Scoring:** Assign a numeric `score` to each criterion and subcriterion. The score must reflect how well the input meets the description of that specific metric. +3. **Scoring:** Assign a numeric `score` to each criterion and subcriterion. The score must reflect how well the input meets the description of that specific metric. 4. **Objective Feedback:** Generate constructive, evidence-grounded `feedback` for the overall evaluation. Mention specific elements or patterns observed in the input that justify the scores within the context of the specialization. -5. **Subcriteria Handling:** If a criterion has `subcriteria`, evaluate each subcriterion individually. The parent criterion's score should be a logical aggregate (e.g., average) of its subcriteria scores. +5. **Subcriteria Handling:** If a criterion has `subcriteria`, evaluate and score each subcriterion individually. The parent criterion's `score` MUST be the exact mathematical **sum** of its subcriteria scores. +6. **Final Score Calculation:** The overall final `score` of the evaluation MUST be the exact mathematical **sum** of all the main criteria scores. # OUTPUT FORMAT You MUST return your response EXCLUSIVELY as a raw, valid JSON object that strictly adheres to the following JSON Schema definition. Do NOT include markdown blocks (```json), explanations, or any text outside the JSON object. diff --git a/app/tests/rubricas/documentos/text.json b/app/tests/rubricas/documentos/text.json new file mode 100644 index 0000000..4b46b3d --- /dev/null +++ b/app/tests/rubricas/documentos/text.json @@ -0,0 +1,171 @@ +{ + "rubric": { + "id": "e7b9c1d2-4f5a-6b7c-8d9e-0a1b2c3d4e5f", + "name": "Academic Essay Evaluation Rubric", + "description": "Rúbrica detallada para evaluar la redacción de ensayos. Mide la calidad de la argumentación, la cohesión estructural y el dominio de la gramática y el vocabulario.", + "user_id": "112b3fda-e380-4919-9f9d-ff941a3b1938", + "status": true, + "visibility": "private", + "verified": true + }, + "category": { + "id": 14, + "name": "Essay Evaluation", + "status": true, + "hierarchy": [ + { + "id": 11, + "name": "Education", + "level": 0, + "parent_id": null + }, + { + "id": 12, + "name": "Language Arts", + "level": 1, + "parent_id": 11 + }, + { + "id": 13, + "name": "Academic Writing", + "level": 2, + "parent_id": 12 + }, + { + "id": 14, + "name": "Essay Evaluation", + "level": 3, + "parent_id": 13 + } + ] + }, + "criteria": [ + { + "id": 100, + "name": "Content & Argumentation", + "description": "Evalúa la profundidad del contenido, la claridad de la tesis y la solidez de los argumentos presentados.", + "type_criteria": "primary", + "type_value": "numeric", + "value": "10", + "status": true, + "criteria_id": null, + "sub_criteria": [ + { + "id": 101, + "name": "Thesis Clarity", + "description": "El ensayo presenta una tesis central clara, específica y debatible en la introducción.", + "type_criteria": "primary", + "type_value": "numeric", + "value": "10", + "status": true, + "criteria_id": 100 + }, + { + "id": 102, + "name": "Evidence & Support", + "description": "Los argumentos están respaldados por evidencia sólida, ejemplos relevantes o citas bien integradas.", + "type_criteria": "primary", + "type_value": "numeric", + "value": "10", + "status": true, + "criteria_id": 100 + }, + { + "id": 103, + "name": "Critical Thinking", + "description": "El texto demuestra un análisis profundo del tema, evitando generalidades superficiales o afirmaciones sin fundamento.", + "type_criteria": "primary", + "type_value": "numeric", + "value": "10", + "status": true, + "criteria_id": 100 + } + ] + }, + { + "id": 200, + "name": "Structure & Organization", + "description": "Evalúa el flujo lógico del texto, la estructuración de párrafos y el uso de conectores.", + "type_criteria": "primary", + "type_value": "numeric", + "value": "10", + "status": true, + "criteria_id": null, + "sub_criteria": [ + { + "id": 201, + "name": "Logical Flow", + "description": "Las ideas progresan de manera lógica. La introducción, el desarrollo y la conclusión están claramente definidos y equilibrados.", + "type_criteria": "primary", + "type_value": "numeric", + "value": "10", + "status": true, + "criteria_id": 200 + }, + { + "id": 202, + "name": "Paragraph Cohesion", + "description": "Cada párrafo se centra en una idea principal única. Las transiciones entre párrafos y oraciones son fluidas y naturales.", + "type_criteria": "primary", + "type_value": "numeric", + "value": "10", + "status": true, + "criteria_id": 200 + } + ] + }, + { + "id": 300, + "name": "Language Mechanics & Style", + "description": "Evalúa las convenciones ortográficas, gramaticales y la riqueza del vocabulario.", + "type_criteria": "primary", + "type_value": "numeric", + "value": "10", + "status": true, + "criteria_id": null, + "sub_criteria": [ + { + "id": 301, + "name": "Grammar & Syntax", + "description": "Las oraciones están construidas correctamente. No hay errores de concordancia, tiempos verbales o sintaxis.", + "type_criteria": "primary", + "type_value": "numeric", + "value": "10", + "status": true, + "criteria_id": 300 + }, + { + "id": 302, + "name": "Spelling & Punctuation", + "description": "Uso impecable de la ortografía y los signos de puntuación (comas, puntos, comillas, etc.).", + "type_criteria": "primary", + "type_value": "numeric", + "value": "10", + "status": true, + "criteria_id": 300 + }, + { + "id": 303, + "name": "Vocabulary & Tone", + "description": "El vocabulario es variado, preciso y adecuado para un contexto académico. El tono se mantiene formal y objetivo.", + "type_criteria": "primary", + "type_value": "numeric", + "value": "10", + "status": true, + "criteria_id": 300 + } + ] + } + ], + "references": [ + { + "id": 1, + "title": "APA Style Guidelines for Academic Writing", + "url": "https://apastyle.apa.org/style-grammar-guidelines", + "reference_type": "official_doc", + "is_primary": true, + "description": "Guía oficial de estilo APA para redacción, gramática y estructuración de textos académicos.", + "status": true + } + ] +} \ No newline at end of file diff --git a/app/tests/id.json b/app/tests/rubricas/imagenes/id.json similarity index 100% rename from app/tests/id.json rename to app/tests/rubricas/imagenes/id.json diff --git a/app/utilities/__pycache__/document_utilities.cpython-312.pyc b/app/utilities/__pycache__/document_utilities.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..9aa3a3159db0e0784c97018d18d26d5a1963bbca8631a9c14d94ff9aeeb5a45d GIT binary patch literal 2780 zcmZ`*O>7&-6`uViDgMZ^BwLnTc_T-%=|EJ?8d6fraudlYU_){eAueJhAeK8rab zm|Z%SN;PU>ph^)S5RgI)5THy_pepPZ_Q~q0?yW$2ph}^p=h6V}r8hQmfHtSTS#oL0 zwFcPveedm??|t)TzKljY5R_{Re=&a-MCc!U;1{{s*?So}n+PK;A&|TBE-Ylf4 zX|TR|SGTZEu`lterRSC%(_#Jo%u}6dYPy7)I*Z|^y;OSHB@R<5aSPNkgr?iPk(%G$ zW$3^b1T5yzs!$Xb`N}h;i0(9X7nNt0V2CAubsoj#m5Uu3lxZ8A8Pm|Uf~EQH*qW}T zb%%_fgU*spU6X2qp`=uOiw?J-jy&6@_b8xv$qUs@U~xMt3DP2!CC zE~Z*jiKS)M-}qmuwVI)Jz5yzY!v8o_&Pk}&&|VNJhil=kN8y2;@IYy<8h+_fIJpx} z-kYd~&sXI0dmx5bXwk8)6xLmRUo^t7#*OX$9&rA7fXBn-@?OE>(|$%?4`c)^-H!Ys zP(;s*6_!nOhl2s-pm<_s;i-)1wqjZoft=`WprtPrB|3zIi-Cr{_PQv|ppRrjTtJ5S zF08N0MHz>VfyjpVVMvDARoUl-CE$)#T`D&!V%xQ10ILdwV*SJSTMMNJQgktO|LcY%3&O6P6KM%VTydA!W;)hn!)3$ZfB-AAy+f4)b zX6e+lwafEY=QLvRX*<10Tw9wt;HYbPeTl$Xx3!$@Xoj9&;*CuyFk<-SF-81A~4kY@!7oeKdpK{TakYEQ*Wmrym4)!Ggaxz>4$FE5HKb8LZH% zbbtl1%|bhm#Q(D(A7G)CJ%Aw!4SF$Ae5(*Aw5mc@dJY5IxFTYi|F0+)rJ{OQ@#ExM z5zvRXWmFU(KdEy};aQfjFeVOwy2R5hi#UaJ%7>WAmYyeZnH~X~v{sVW%lRCt|E!@} z|MB(CTF(n#qgbeOedep=_=D5;Pj63Dlhc*t)lY}Za`{J}MR)rXmABqrzfv3j{)6oO z?DlGPe6}+F_NOOHxO`z}@a%4XvXU~^uhdm^bYL^PkuCkCdL*&_!^h{&e=@ig*gf{$ zhdt|;zlsbziVW>UhIUV0sP!HzjcmMAJASHma^6)F z7bxdWTwua2hbwCyuHObHb9z3F^{Ex@AaH$&VbF4Tu-DM`tAD)ohf5Dn{rPNla&`@Q zy8zSD4YIru@gPXG( zv*j~e>1xl}wb@$l(4*dwo!*i1kMF%!?VVVgd!nMg!IHI;oZUS-SCM=Et)go}Mko#c z>dYr+Di^Q*ZTR(jZW(Wlf06s^@awz%Z&YsB75OL-vitbv=*DO{xHVKgJi2z}pW&0W zXx~Sv4^pLT<#(#lWNqTL2k+m1|KVzN;;qVr{_%7vT%O(OOYZi3ud;BX*7f4!t`p_Z zZ=#*sj7HuN9R39P)6vNE;ow|6#J&R=AmJEUlGmL? z>)qfqpK*;Can5=vf+^m+#|xVGpkOjK4c9~Nw+-(2T+G|JkRwy{70~4*b&^okB|#AW oj&ALsTXk6xCbtUUnJ~HCy~mq+?6jbk0B>k2r!GF>t;fgz0m*u^&j0`b literal 0 HcmV?d00001 diff --git a/app/utilities/document_utilities.py b/app/utilities/document_utilities.py new file mode 100644 index 0000000..b1d61ec --- /dev/null +++ b/app/utilities/document_utilities.py @@ -0,0 +1,51 @@ +from app.schemas.document_standard import DocumentEvaluationRubric, StandardDocumentAnalysis, DocumentEvaluationCriteria + +def encode_document_from_bytes(document_bytes: bytes) -> str: + """ + Codifica un documento a base64 a partir de sus bytes. + """ + import base64 + return base64.b64encode(document_bytes).decode('utf-8') + +def json_to_rubric(json_data: dict) -> DocumentEvaluationRubric: + """Convierte un diccionario JSON en un objeto DocumentEvaluationRubric mapeando los campos correctos.""" + + def parse_criteria(criteria_list: list) -> list: + parsed_criteria = [] + for item in criteria_list: + # Convertir el valor a float si existe + score_value = float(item["value"]) if item.get("value") is not None else None + description = str(item["description"]) if item.get("description") is not None else None + + # Procesar subcriterios recursivamente si existen + sub_raw = item.get("sub_criteria") + sub_parsed = parse_criteria(sub_raw) if sub_raw else None + + parsed_criteria.append(DocumentEvaluationCriteria( + name=item["name"], + description=description, + score=score_value, + subcriteria=sub_parsed + )) + return parsed_criteria + + # Jerarquía de la categoría + category_info = json_data.get("category", {}) + hierarchy_list = category_info.get("hierarchy", []) + + # Ordenamos por nivel para asegurar la lógica: Nivel 0 -> Nivel 1 -> Nivel 2 + sorted_hierarchy = sorted(hierarchy_list, key=lambda x: x.get("level", 0)) + + # Unimos los nombres con ' > ' + specialization_path = " > ".join([item["name"] for item in sorted_hierarchy]) if sorted_hierarchy else None + + # Extraer la información principal de la rúbrica + rubric_info = json_data.get("rubric", {}) + + return DocumentEvaluationRubric( + name=rubric_info.get("name", "Sin nombre"), + description=str(rubric_info["description"]) if rubric_info.get("description") is not None else None, + category=specialization_path, + criteria=parse_criteria(json_data.get("criteria", [])) + ) + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e220608..6bd94e1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,29 +1,143 @@ -# Dependencias principales -annotated-doc==0.0.4 -annotated-types==0.7.0 -anyio==4.11.0 -fastapi[standard]==0.121.3 -idna==3.11 -pydantic==2.12.4 -pydantic_core==2.41.5 -sniffio==1.3.1 -starlette==0.50.0 -typing-inspection==0.4.2 -typing_extensions==4.15.0 -uvicorn[standard] -python-multipart -python-dotenv +# ========================================== +# Web & API Framework (FastAPI) +# ========================================== +fastapi==0.121.3 # Framework principal para crear la API +fastapi-cli==0.0.24 # Interfaz de línea de comandos para FastAPI +fastapi-cloud-cli==0.15.0 # Herramientas de despliegue para FastAPI +starlette==0.50.0 # Toolkit ASGI base de FastAPI +uvicorn==0.41.0 # Servidor ASGI para ejecutar la aplicación +uvloop==0.22.1 # Ciclo de eventos rápido para Uvicorn +anyio==4.11.0 # Soporte asíncrono para concurrencia +sniffio==1.3.1 # Detección de la librería asíncrona en uso +websockets==16.0 # Soporte para conexiones WebSockets +watchfiles==1.1.1 # Recarga automática del servidor al detectar cambios +python-multipart==0.0.22 # Manejo de datos de formularios y subida de archivos -# Procesamiento de audio -noisereduce -librosa -soundfile -praat-parselmouth -numpy +# ========================================== +# Validación de Datos & Tipado +# ========================================== +pydantic==2.12.4 # Validación de datos y gestión de esquemas +pydantic_core==2.41.5 # Núcleo en Rust para Pydantic (rendimiento) +pydantic-extra-types==2.11.0 # Tipos adicionales para Pydantic +pydantic-settings==2.13.1 # Manejo avanzado de variables de entorno +annotated-types==0.7.0 # Metadatos para el tipado de variables +typing_extensions==4.15.0 # Funciones de tipado para versiones antiguas de Python +typing-inspection==0.4.2 # Inspección de tipos en tiempo de ejecución +email-validator==2.3.0 # Validación de correos electrónicos -# Modelos de IA e integraciones -openai -langchain -langchain-openai -assemblyai -anthropic \ No newline at end of file +# ========================================== +# Modelos de IA, LLMs & Agentes (LangChain) +# ========================================== +openai==2.28.0 # Cliente oficial para la API de OpenAI +langchain==1.2.12 # Framework para aplicaciones con LLMs +langchain-core==1.2.19 # Componentes y abstracciones base de LangChain +langchain-openai==1.1.11 # Integración específica de OpenAI para LangChain +langgraph==1.1.2 # Creación de agentes y flujos cíclicos con LLMs +langgraph-checkpoint==4.0.1 # Gestión de estados y memoria para LangGraph +langgraph-prebuilt==1.0.8 # Componentes preconstruidos para LangGraph +langgraph-sdk==0.3.11 # SDK oficial de LangGraph +langsmith==0.7.17 # Monitoreo, trazas y depuración para LangChain +tiktoken==0.12.0 # Tokenizador rápido usado por OpenAI + +# ========================================== +# Procesamiento de Audio & Voz (Speech-to-Text) +# ========================================== +assemblyai==0.56.0 # SDK de AssemblyAI para transcripción de audio +deepgram-sdk==6.0.1 # SDK de Deepgram para transcripción de audio +librosa==0.11.0 # Análisis de música y señales de audio +noisereduce==3.0.3 # Algoritmos para reducción de ruido en audio +praat-parselmouth==0.4.7 # Interfaz de Python para Praat (análisis fonético) +soundfile==0.13.1 # Lectura y escritura de archivos de audio +audioread==3.1.0 # Decodificación de audio multiplataforma +soxr==1.0.0 # Conversión de frecuencia de muestreo (resampling) de alta calidad + +# ========================================== +# Ciencia de Datos, Matemáticas & Machine Learning +# ========================================== +numpy==2.4.3 # Computación numérica y manejo de arreglos (matrices) +scipy==1.17.1 # Funciones matemáticas, científicas y de ingeniería +scikit-learn==1.8.0 # Herramientas de Machine Learning y análisis de datos +numba==0.64.0 # Compilador JIT (Just-In-Time) para acelerar código matemático +llvmlite==0.46.0 # Motor subyacente para compilar con Numba +joblib==1.5.3 # Procesamiento en paralelo y caché (usado por scikit-learn) +threadpoolctl==3.6.0 # Control de hilos en librerías nativas (C/C++) + +# ========================================== +# Redes & Clientes HTTP +# ========================================== +requests==2.32.5 # Cliente HTTP síncrono estándar +requests-toolbelt==1.0.0 # Utilidades adicionales para la librería requests +httpx==0.28.1 # Cliente HTTP asíncrono (alternativa moderna a requests) +httpcore==1.0.9 # Motor subyacente de red para HTTPX +httptools==0.7.1 # Analizador (parser) de peticiones HTTP ultrarrápido +h11==0.16.0 # Implementación pura de HTTP/1.1 +urllib3==2.6.3 # Cliente HTTP base con gestión de conexiones y reintentos +certifi==2026.2.25 # Colección de certificados SSL/TLS raíz +idna==3.11 # Soporte para nombres de dominio internacionalizados +dnspython==2.8.0 # Herramientas para consultas y manipulación de DNS + +# ========================================== +# Gráficos & Visualización +# ========================================== +matplotlib==3.10.8 # Creación de gráficas y visualizaciones de datos +contourpy==1.3.3 # Cálculo de contornos 2D (dependencia de matplotlib) +cycler==0.12.1 # Creación de iteradores complejos (dependencia de matplotlib) +fonttools==4.62.1 # Manipulación de fuentes tipográficas +kiwisolver==1.5.0 # Solucionador matemático rápido (dependencia de matplotlib) +pillow==12.1.1 # Procesamiento y manipulación de imágenes (PIL) +pyparsing==3.3.2 # Herramienta para crear analizadores de texto sintácticos + +# ========================================== +# CLI (Terminal) & Utilidades de Salida +# ========================================== +click==8.3.1 # Creación rápida de interfaces de línea de comandos (CLI) +typer==0.24.1 # Creación de CLIs basado en Pydantic y tipado +rich==14.3.3 # Texto enriquecido, tablas y colores en la terminal +rich-toolkit==0.19.7 # Componentes adicionales para Rich +tqdm==4.67.3 # Barras de progreso visuales en consola +shellingham==1.5.4 # Herramienta para detectar qué shell se está utilizando + +# ========================================== +# Serialización, Parsing & Utilidades Generales +# ========================================== +python-dotenv==1.2.2 # Carga de variables de entorno desde archivos .env +orjson==3.11.7 # Analizador (parser) de JSON ultrarrápido +jiter==0.13.0 # Parser de JSON eficiente (usado internamente por Pydantic) +ormsgpack==1.12.2 # Serialización de datos en formato MessagePack (rápida) +msgpack==1.1.2 # Serialización de datos en formato MessagePack (estándar) +PyYAML==6.0.3 # Procesamiento de archivos YAML +jsonpatch==1.33 # Aplicación de parches a documentos JSON +jsonpointer==3.0.0 # Identificación de nodos dentro de un JSON +python-dateutil==2.9.0.post0 # Extensiones robustas para el manejo de fechas (datetime) +regex==2026.2.28 # Motor de expresiones regulares alternativo y más potente +uuid_utils==0.14.1 # Utilidades para la generación rápida de UUIDs +charset-normalizer==3.4.5 # Detección automática de codificación de texto +six==1.17.0 # Librería de compatibilidad entre Python 2 y 3 +tenacity==9.1.4 # Reintentos automáticos para código propenso a fallos +decorator==5.2.1 # Simplificación en la creación de decoradores +xxhash==3.6.0 # Algoritmo de hash no criptográfico extremadamente rápido +zstandard==0.25.0 # Compresión de datos rápida (algoritmo zstd) +packaging==26.0 # Manejo y parseo de versiones de paquetes de Python +platformdirs==4.9.4 # Identificación de rutas de directorios estándar del SO +pooch==1.9.0 # Descarga y almacenamiento en caché de archivos de datos +lazy-loader==0.5 # Carga perezosa (lazy) de módulos pesados +cffi==2.0.0 # Interfaz para llamar código en C desde Python (FFI) +pycparser==3.0 # Analizador sintáctico de lenguaje C en Python +rignore==0.7.6 # Herramienta para analizar archivos ignorados (ej. .gitignore) +annotated-doc==0.0.4 # Utilidades para extraer documentación de tipos anotados +fastar==0.8.0 # Utilidad secundaria (generalmente vinculada al framework web) + +# ========================================== +# Plantillas & Procesamiento de Markdown +# ========================================== +Jinja2==3.1.6 # Motor de plantillas (usado comúnmente para renderizar HTML) +MarkupSafe==3.0.3 # Escapado seguro de strings para evitar inyecciones en HTML +markdown-it-py==4.0.0 # Analizador y renderizador de Markdown extensible +mdurl==0.1.2 # Utilidad para parsear URLs dentro de Markdown +Pygments==2.19.2 # Resaltador de sintaxis genérico para código fuente + +# ========================================== +# Monitoreo & Diagnóstico del Sistema +# ========================================== +sentry-sdk==2.54.0 # Integración con Sentry para monitoreo y rastreo de errores +distro==1.9.0 # Extracción de información específica del sistema operativo Linux \ No newline at end of file