From 4d6152a9fee5678d4b4a7e530faed79cb842afd404808ce998fcbf62bdf3a174 Mon Sep 17 00:00:00 2001 From: Francisco Pineda Date: Sun, 15 Mar 2026 08:44:25 +0100 Subject: [PATCH] feat: Agrega primera version de template (revisar archivo de documentacion - docs.md) --- .gitignore | 1 + app/__init__.py | 1 + app/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 167 bytes app/__pycache__/main.cpython-312.pyc | Bin 0 -> 2373 bytes app/api/v1/__init__.py | 1 + .../v1/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 203 bytes .../__pycache__/router.cpython-312.pyc | Bin 0 -> 1977 bytes .../__pycache__/transcription.cpython-312.pyc | Bin 0 -> 2089 bytes app/api/v1/endpoints/audio/transcription.py | 38 ++++ .../rubricated_analysis.cpython-312.pyc | Bin 0 -> 2118 bytes .../v1/endpoints/image/rubricated_analysis.py | 39 +++++ app/api/v1/endpoints/router.py | 61 +++++++ .../texto/__pycache__/resume.cpython-312.pyc | Bin 0 -> 2047 bytes .../rubricated_analysis.cpython-312.pyc | Bin 0 -> 2146 bytes app/api/v1/endpoints/texto/resume.py | 39 +++++ .../v1/endpoints/texto/rubricated_analysis.py | 39 +++++ .../__pycache__/transcription.cpython-312.pyc | Bin 0 -> 1984 bytes app/api/v1/endpoints/video/transcription.py | 36 ++++ app/core/__pycache__/config.cpython-312.pyc | Bin 0 -> 784 bytes app/core/config.py | 26 +++ app/main.py | 60 +++++++ .../__pycache__/audio_standar.cpython-312.pyc | Bin 0 -> 1751 bytes .../audio_standard.cpython-312.pyc | Bin 0 -> 4631 bytes .../image_standard.cpython-312.pyc | Bin 0 -> 2518 bytes .../__pycache__/text_standard.cpython-312.pyc | Bin 0 -> 2495 bytes .../video_standard.cpython-312.pyc | Bin 0 -> 3446 bytes app/schemas/audio_standard.py | 92 ++++++++++ app/schemas/image_standard.py | 44 +++++ app/schemas/text_standard.py | 44 +++++ app/schemas/video_standard.py | 65 +++++++ .../transcription_adapters.cpython-312.pyc | Bin 0 -> 6883 bytes .../transcription_adapters.cpython-312.pyc | Bin 0 -> 7778 bytes app/services/audio/transcription_adapters.py | 163 ++++++++++++++++++ .../evaluations_adapters.cpython-312.pyc | Bin 0 -> 2674 bytes app/services/image/evaluations_adapters.py | 68 ++++++++ .../evaluations_adapters.cpython-312.pyc | Bin 0 -> 2117 bytes .../resume_adapters.cpython-312.pyc | Bin 0 -> 2115 bytes app/services/text/evaluations_adapters.py | 50 ++++++ app/services/text/resume_adapters.py | 50 ++++++ .../transcription_adapters.cpython-312.pyc | Bin 0 -> 2429 bytes app/services/video/transcription_adapters.py | 67 +++++++ .../audio_utilities.cpython-312.pyc | Bin 0 -> 2468 bytes app/utilities/audio_utilities.py | 62 +++++++ docs.md | 77 +++++++++ requirements.txt | 28 +++ 45 files changed, 1151 insertions(+) create mode 100644 .gitignore create mode 100644 app/__init__.py create mode 100644 app/__pycache__/__init__.cpython-312.pyc create mode 100644 app/__pycache__/main.cpython-312.pyc create mode 100644 app/api/v1/__init__.py create mode 100644 app/api/v1/__pycache__/__init__.cpython-312.pyc create mode 100644 app/api/v1/endpoints/__pycache__/router.cpython-312.pyc create mode 100644 app/api/v1/endpoints/audio/__pycache__/transcription.cpython-312.pyc create mode 100644 app/api/v1/endpoints/audio/transcription.py create mode 100644 app/api/v1/endpoints/image/__pycache__/rubricated_analysis.cpython-312.pyc create mode 100644 app/api/v1/endpoints/image/rubricated_analysis.py create mode 100644 app/api/v1/endpoints/router.py create mode 100644 app/api/v1/endpoints/texto/__pycache__/resume.cpython-312.pyc create mode 100644 app/api/v1/endpoints/texto/__pycache__/rubricated_analysis.cpython-312.pyc create mode 100644 app/api/v1/endpoints/texto/resume.py create mode 100644 app/api/v1/endpoints/texto/rubricated_analysis.py create mode 100644 app/api/v1/endpoints/video/__pycache__/transcription.cpython-312.pyc create mode 100644 app/api/v1/endpoints/video/transcription.py create mode 100644 app/core/__pycache__/config.cpython-312.pyc create mode 100644 app/core/config.py create mode 100644 app/main.py create mode 100644 app/schemas/__pycache__/audio_standar.cpython-312.pyc create mode 100644 app/schemas/__pycache__/audio_standard.cpython-312.pyc create mode 100644 app/schemas/__pycache__/image_standard.cpython-312.pyc create mode 100644 app/schemas/__pycache__/text_standard.cpython-312.pyc create mode 100644 app/schemas/__pycache__/video_standard.cpython-312.pyc create mode 100644 app/schemas/audio_standard.py create mode 100644 app/schemas/image_standard.py create mode 100644 app/schemas/text_standard.py create mode 100644 app/schemas/video_standard.py create mode 100644 app/services/__pycache__/transcription_adapters.cpython-312.pyc create mode 100644 app/services/audio/__pycache__/transcription_adapters.cpython-312.pyc create mode 100644 app/services/audio/transcription_adapters.py create mode 100644 app/services/image/__pycache__/evaluations_adapters.cpython-312.pyc create mode 100644 app/services/image/evaluations_adapters.py create mode 100644 app/services/text/__pycache__/evaluations_adapters.cpython-312.pyc create mode 100644 app/services/text/__pycache__/resume_adapters.cpython-312.pyc create mode 100644 app/services/text/evaluations_adapters.py create mode 100644 app/services/text/resume_adapters.py create mode 100644 app/services/video/__pycache__/transcription_adapters.cpython-312.pyc create mode 100644 app/services/video/transcription_adapters.py create mode 100644 app/utilities/__pycache__/audio_utilities.cpython-312.pyc create mode 100644 app/utilities/audio_utilities.py create mode 100644 docs.md create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a1e5ac6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..4c0d52d --- /dev/null +++ b/app/__init__.py @@ -0,0 +1 @@ + diff --git a/app/__pycache__/__init__.cpython-312.pyc b/app/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..f3f153baebcbaf38af77335cfa537aeb77c2a8150d780b0f1a1c1f257284e50b GIT binary patch literal 167 zcmX@j%ge<81Shv`&13@7k3k%C@Rn&Ma3}% ziOJddG47cq9;Hb!g{6r(nJM`tx&@WGDf!98F=>g#C5{D|A*s0qIf*5yF^L5QG4b)4 pd6^~g@p=W7w>WHa^HWN5QtgUZfyOcdaWRPTk(rT^v4|PS0svQDE4}~# literal 0 HcmV?d00001 diff --git a/app/__pycache__/main.cpython-312.pyc b/app/__pycache__/main.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..37ab9066f92da7b6fff4f75b4beb02f166491b1379b114703e804ad4daa74206 GIT binary patch literal 2373 zcmcIm&2Jk;6rc6(+KKaJCuyQK1xC=)8e+SFUMQhJD+omhRkUe^F+}5?i92P#IC6K)e~-`D!Ee04sTRX5ROEzxUSP zjg4gyJYTMS%d5i({jLs!$L?=7rwxR@Kon8KMV?VH47E?WDbK8!o>j3tyJCCkO4>jv z#0+Xui`sY7cg&uACBrgT^8=;ZZ)VaA9jY65hX&`+Q2#TNStZl9DRUu zYTLiTj*i`d_|-$lKSg(XTU5!eO%!r}s8hj6GOrUEy?Xv!!Af!@Ycazv(RE@n zYOP}@5CY0X1H7h65eCA6ya6ZoR|DY@hqu1*ajms3K_1JXCO|qb2z~?oW z$&&U!FYmA@7QQ^C56Umfo#+%^_A!)FlB*gnHL%=ZKBok%$37NJs!A&e?P~)p_ao?8 zQ@XX0cnMOl7h?N?5$}!O34H9UfL-v0HtjN?PR6sKG7`ks$m*qHfY9clKD5dju;neb zNS2$k1Da~1G6dxj$TAQP?jo=TabuzoE6ascGq)ptB^dmrx}5l}b(c#nv50E|sz3uV z0r%Su=vFE3RJM=fy^h^-TpxEi4r9UY%>r9H5W}cNe1P)A?kULE!>D*NC=NodvF7kc zR{#b~6c?HkvCmce5x4k!5g^qZ*}iyA^;-}!pYSA0q-0*rT_Zfc0A}Eps)3=ALF$6l z0fivnU<@K=5+LFQlxbjqDW`Q0APq=-_!Q~6fB8SxyOoUp!DpRaC_%N61XLAz8*7*;*P{ZhD=SRx1_aTwGon8};4+(6xFH2uEO$<21tXzc0sv9nz>H8b8F zLPt+`j-A=CO*mvxzE3%%DEVjF&n{`UjoI;;Z1)H{aJ)19MAtUwCV`9>?ibsSzp^os zo|)|CP=3`IsLWqg+n$d8?Z#tyoZUEjWHjAHN9?pv`ghbKh}e%K9I}#H_Pk_&TrJl$cZ8LyM69*kz~1 zlW69zld}`k zQ~fjI0mFBKgKnm7xvLoDrVq@FjEuJ!M2c8|8~_4%IEMfL literal 0 HcmV?d00001 diff --git a/app/api/v1/endpoints/__pycache__/router.cpython-312.pyc b/app/api/v1/endpoints/__pycache__/router.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..f2703429e29e1b9365b0d82e50d9409cb52f93eb8a60a6dbff90f7d6c69adaed GIT binary patch literal 1977 zcma)6&u<$=6rOc9cKj=L;{2ecBwLUGi^Nr~rHT}x(nJUdkZ@R8vZgx|YhY(*J2M;N z91#CQ4;)dsRpl0m16Lopq~g$YrHBJ3ZmCKxJ@IC2CvoZsR>^v1-uJ%u&G%;K_j0*_ z;Q6`rE4wp+&|hXUc*gsKm)|=G{fG$i5phT+ba=+kIA%N+j&av_`M5vMvwoK6{G45P z!wH`E^L9KQ7I@Jw+Hp24@v>jG<6Jn&D}Kd}C&DV9@~7-LA5Qa{Uvp3fQTIt-fgfOw>G2VUDrV&+3mV}5P z;YO`m@^%@afS@`6>FXdX6m8(`9z;F$U$F?%V<9z`-h-t3*z9+gQX-@&0B$MjC7OjS zpg@>o&;^rdS_+ZfC(-l=v;-~iKT?5Y19VN(Jv|7jgmWxAkTi2~8AmwmD5eaVwQ+#IOg76nZqq?@ zkhY(_WhLy-!HGFPLB`cJ)3$mh&4PV9Y%g^+Zma$ATc-L5_!cY+ptB-I#J@H@hPaiH zTn95>tGF>ZA)nQ+rMVWE9JE?#w&%4pUv`??!Aj$pNf&0)Tw*e7UO5x2_Ja*YgG4gj zk#Osw^nD!14ajNit~Ab$Zo_W7G31iW8~DwUaIX|B~2C`Zutamjtj0{EUu zYv8mjUaiG?c1cU=>KfLTt*JEkb|9+i@lVU!fJ20hL^U|UdGCJSu5-1 ztels(^7>ucEqFz%sP`Fn#2d9n^*-yCys}mH#;h@Y&bi~>8fy(kDMZuP2O+JsbnU6_ zgTsA`lc(hm3fEJ85m;mri#B$Obj%r{<#yT`y)=|t6Wvm^9PPmf6{m=#i==Bh)I8Ks z^D_}~nG;BJr+H*~$%))RNssZsrh<5k`cexTk;8&w5#-_Wl3+5Zn`#u*bU;XIQr`&! z=1Y@Po6S=*@=c;_%&rM8?ZEFa%0Z8qlAZydks!Vkm|#I&wm^NG5tDGq+6;DXtyr86 zyub|>h|QLle2At)%`)F)UKnsmxOqBSzGj9z=uqked~hWJp(S&fO?Q~og|88jD+xpr zjCvubCMdwAI9(vHDix0jFEHBy_XvC!6>|Bi?|}P|a579!t@x`-cooMZZa|zCWQ_Wq zY9=l;jvbl_B1w6i+fPGCgNTdAL#1?(xin7eximdDckI9!n}(7FK6n`gv%|DIO^HJ} zoU>f}sHWn|tc1)E?#yxG3!5`-GNbPikLxGdY^lUB zaDKM5D^Uog*aZeAC2Tk*bE_!KH5?%M#D;mE=6B0Q_F!gLs6;6ee( zp~g9MeXzd>oOsbd=uP9Y@k%Cx&f{}<7CnJp%Wgq(fLVmLB3?P4l9|N6laeZe9_Lbt zuMdm_K0u)W)VLJj)@B?) z4S0acKZG6aNKXyEAnJOjd_Rz$y7~0{DGES&xDo(Is&FpBJwVI_G}|XxN3%zJ*wKfu z@_2u24O3kR%@rcdAhZD_R47C{jBs|gPl6q$2R=aoQ1!rlkUq&HALR z4L@IRW^_&OyVU=ntZw$vrdR`^T%c?MMNLM$ylU_)cDwuz99kj+8HTxfqy5{B@wW@mRZ=~|+xOVoTIWG(E* z>-zx@){}GRarwh1|!k5Yw^g7;@~hLKzOveF3z=}1=+0@Bvf}Iu96bx)K%eXJPjwV=*86^j4N{W z6Fff9s0nIEoXIXPz`J+?a#91sT4#4{m=mu4i}6PFO4uiUX`?1DbI<3;+fy&)3i@%!sd7A@Y;JlE|e` z@ojKa{YE%2K@Ks#o66(#?-K}bzKy27LsPd<^%mNG8_nH9b9b|7)AlRR_Rz+kaOEa8 xzrp4^n|qCB51Tjeb3Z(}|FaW!(Kw#$Jyu`Q>*4l2wXPZ{^*nfnE#C@({smm#c@Y2r literal 0 HcmV?d00001 diff --git a/app/api/v1/endpoints/audio/transcription.py b/app/api/v1/endpoints/audio/transcription.py new file mode 100644 index 0000000..572c84e --- /dev/null +++ b/app/api/v1/endpoints/audio/transcription.py @@ -0,0 +1,38 @@ +""" +Gateway de IA de Qualidot - Módulo de Procesamiento de Audio + +Propósito: + Este endpoint recibe un audio y lo convierte a texto usando inteligencia artificial. + +Homologación: + Sin importar qué proveedor de IA se utilice, el resultado siempre se entrega en el mismo formato estándar para Qualidot. +""" + +from dotenv import load_dotenv +from fastapi import APIRouter, Depends, UploadFile, File, HTTPException +from fastapi.security import APIKeyHeader +from app.api.v1.endpoints import router +from app.schemas.audio_standard import StandardTranscriptionResult, AudioRequestFile +from app.services.audio.transcription_adapters import transcribe_audio_with_provider + +# Inicializar el router de FastAPI para este módulo +audio_router_transcription = APIRouter() + +@audio_router_transcription.post("/transcripts/", response_model=StandardTranscriptionResult) +async def transcribe_audio(audio_request: AudioRequestFile = Depends()) -> StandardTranscriptionResult: + """ + Endpoint para transcribir audio simple infiriendo el proveedor de IA + + Args: + audio_request: Objeto AudioRequestFile que contiene el archivo de audio, + el proveedor, el modelo y las opciones de diarización, marcas de tiempo y análisis de sentimiento. + + Returns: + StandardTranscriptionResult: Resultado de la transcripción en formato estándar de Qualidot + """ + try: + # Transcribir usando el adaptador de transcripción que infiere el proveedor + transcription_result = await transcribe_audio_with_provider(audio_request) + return transcription_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/image/__pycache__/rubricated_analysis.cpython-312.pyc b/app/api/v1/endpoints/image/__pycache__/rubricated_analysis.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..db6c217f61f246d078e9c82d8d249e73b7b5f148d373cf4471f4492869cc2e22 GIT binary patch literal 2118 zcma)7U2IfE6rS0;|J&Uyq!3^Y-Y5n)!V;d;(nu;lZBvC>N|eo-&Gg=B8N7eY%xzf& zLx30_e9}ZPJYjs0fKN4spotIr=t~U}^hSb-nCM%fn)vtNnYp`5!NfSpojWu4%y-Uy z=R0S=Di-qy#)r+%*v<KdMc4z+R8#FW_|^*{F_eY$qJ-Ch7^S)`nxa$$HXF)l+V| zo^~_!jGL`z^|z#xbMy7QUZ$FpJOZAdlu9x*bZ>qHAey|=aamtY zS0CBj*{pckSz5Z9yOM~H;6|!@V+PxKTDA+cG?TRZ&i2mh1Fd4U6duP2d1^Q6hC#ipsoU@iervt>F2Kp>Fg#0&-T zY(I9)>O{r(7LPF@5Gq6NS)jGv$iV6hJj>@`8b=rkDTidw;smlcLMjh>oJe%Bt{UAEIgM?6GVxYo9?zp= z#vwG1KQZQUH5r~#1)=rXm8Q;FM~EDh($)O`&?NI_7>9BJsemXRk(KtMr(r%T2K7og zeu%q4bN}=a3L1JeNuX4!%1#12Ri&b@d0S#lE1Z=cwQhB8d|g<=^aBgB2c^5RRBOE; z^kPz{ZAEDRA)`SvmXj(AkidiTR}LPK2IcwhssM{_$I*WGv0otzpxc6wM24bifvTdk zO(kJYHOY&R)+pDLKF_MXAtbLFysXw`l`bXmW?ZWgrUE2{fR#TF?;30z6qJibK&&_X z#t4(6;dJ9@r{!%8T3c=35{(%mk8Ieu%y__^}8<&BHkjY|fqyoCR_ zTNt>HaBArLQsrag(_Po_!rpI6I~KD${#r8N_;2wH0G-Ce34FFxLGR-A$@fwfbU___ zfGg-?Zm03l+Ck%bYNzp~0f+mlYobgO`i#|V0-d_uaK%fMQNI>`9Z$lJt19B^UyiF9 zis|j|SkwfyLe6B1=ipm30JPNLVr_1Ftw#%2|DDkW^+{NVsY^sH7B%?LZHN~XZlAK+ ztgR@YgYMcftF7oRP3Y+XG(r(4eNBuLH?|(xQ8Mr$`-v|K-|cwu^XKoOemu1J^m9x4J!aom?_~oe4goSO`6h7m4;t--6951J literal 0 HcmV?d00001 diff --git a/app/api/v1/endpoints/image/rubricated_analysis.py b/app/api/v1/endpoints/image/rubricated_analysis.py new file mode 100644 index 0000000..cafc0ab --- /dev/null +++ b/app/api/v1/endpoints/image/rubricated_analysis.py @@ -0,0 +1,39 @@ +""" +Gateway de IA de Qualidot - Módulo de Procesamiento de Imágenes con una rubrica de análisis + +Propósito: + Este endpoint recibe una imagen y la analiza usando una rúbrica de evaluación basada en inteligencia artificial. + +Homologación: + Sin importar qué proveedor de IA se utilice, el resultado siempre se entrega en el mismo formato estándar para Qualidot. +""" + +from dotenv import load_dotenv +from fastapi import APIRouter, Depends, UploadFile, File, HTTPException +from fastapi.security import APIKeyHeader +from app.api.v1.endpoints import router +from app.schemas.image_standard import StandardImageAnalysisResult, ImageRequestFile + +from app.services.image.evaluations_adapters import evaluate_image_with_provider + +# Inicializar el router de FastAPI para este módulo +image_router_analysis = APIRouter() + +@image_router_analysis.post("/evaluations/", response_model=StandardImageAnalysisResult) +async def evaluate_image(image_request: ImageRequestFile = Depends()) -> StandardImageAnalysisResult: + """ + Endpoint para analizar imágenes usando una rúbrica de evaluación infiriendo el proveedor de IA + + Args: + image_request: Objeto ImageRequestFile que contiene la imagen, + el proveedor, el modelo y las opciones de análisis. + + Returns: + StandardImageAnalysisResult: Resultado del análisis de imágenes en formato estándar de Qualidot + """ + try: + # Analizar imagen usando el adaptador de análisis de imagen que infiere el proveedor + analysis_result = await evaluate_image_with_provider(image_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 new file mode 100644 index 0000000..a1163f8 --- /dev/null +++ b/app/api/v1/endpoints/router.py @@ -0,0 +1,61 @@ +""" +Gateway de IA de Qualidot - Módulo maestro de endpoints de procesamiento. + +Propósito: + Este módulo actúa como el punto central de enrutamiento para todos los + endpoints relacionados con el procesamiento de audio, imágenes, + documentos, video, etc. + +Homologación: + Sin importar qué proveedor de IA se utilice, el resultado siempre se + entrega en el mismo formato estándar para Qualidot. + +""" + +from dotenv import load_dotenv +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 + +# 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_audio.include_router( + audio_router_transcription, + prefix="/audio", + tags=["Procesamiento de Audio"] +) + +api_router_text.include_router( + text_router_summary, + prefix="/text", + tags=["Procesamiento de Texto"] +) + +api_router_text.include_router( + text_router_analysis, + prefix="/text", + tags=["Procesamiento de Texto"] +) + +api_router_video.include_router( + video_router_transcription, + prefix="/video", + tags=["Procesamiento de Video"] +) + +api_router_image.include_router( + image_router_analysis, + prefix="/image", + tags=["Procesamiento de Imágenes"] +) \ 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 new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..2329166c4516402d6b151002760b9351311c3d41dcc386c5dea1140215d20781 GIT binary patch literal 2047 zcmZ`)O^g&p6t3!?|Jj}4XGMlJ*hUPQ$PVG8E~~iVXBUVFyUW3Ll1|ZGyA<>vRn@x; zf*K%(gC|G?<3)@I2zXj!h?;oVqn9;k(2fKWG11$?YU1C)S2Z)61!E;$^{V>S_qx9K zz3Q)fdrJt~N6u&LZVsWJ)TG;BuG`|5FkD0)vXO^9!^eSP8(7(2JU1(tj0P>$ zN~+r_B(-Sh#Ssgo#i`2+( zad`jku_%$0r}-T;hO>*bbTC#YYjP83!CnomqJnqt1vM2f;x(1XNJkcjv|Et z@QL!~&TA?*gL4JQkE2jfCx|@ir#*TLZv(Z?Erq+BnL+E-Lq{lx;}QBnfz)!NPy#rp$_D|vhuO3)8MQy!)K;y+ zTOL^}Q)eKupeBg0qSyspP@zQZF_q4??l)PGTNfN)9io8pVFL;0F8d5rp;iTmnYAmm zvGnoU7}MEYcBKCo)}SuycAbDO9*&?-qH5*8B?9rg1)@XlW4}ROK*VJsiA;p!g3QvK zMVGLD#?|c0l(F`r~c1ur`|=x@QgDXx_B;+*-HiT=o0v`uW281rzn}#ed!__dP&3 zJM?3v|BCtP_8WM1*SD3e^M$Q{FPO0Wk9ZbzS10nP+#LEh{M>##EohYA% z%eC#*XcOoVkHl~TigtomYk>BJ=CpZjrI|Q_JS$qSqDANtU#l<+Lr@2aPhaBCz*OBO z!i5ev!}z{Yz?t9s5PtFw8u<>5+(z}=XyYAp;AgbwehxiuT{t?AR^P>aw{YznT)Q+d r|NPiIuHD4rKfLhr7q$Cn1s?kFtpzpAkB|pyTr`m}0l;AL4G`yFjGSl} literal 0 HcmV?d00001 diff --git a/app/api/v1/endpoints/texto/__pycache__/rubricated_analysis.cpython-312.pyc b/app/api/v1/endpoints/texto/__pycache__/rubricated_analysis.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..8564fc5be5ec55cca540108b273dd773f77e32c40bed9a964d0fb32e14920dd0 GIT binary patch literal 2146 zcma)7U2IfE6rS0;|LyJqDFoUC$B2@Ru!JYIZEPBT+KM8SqH(jyI=y$=4&Faz=C&+9 z#sD!q_#}y7d=cY=1dIZ&de3&(t9^|^xfq2Qq5K$CY=Jh2 z!^p&_9uG#;56 z0htMeM*$ZmRr8YXKzM^t2h&RaXI-T-j(gmII(2}V`SayeoEtqbF&RXPi8!;Hg#eL^ z^M^ydX)kwKoHS!$d}`{zo?|u(6%Txf(hF|;SYw<~hY6Ucg^5v4#Qld9;75g{>of|O zHlRL}<{EKHuT8R}5u8%T1;>FFu8P%lDe4Pc&DAv~9O51?>}Y?5q;d!BE&?7Q*sSga z4s+vP<0M)U;Sfwk!nc84-3MfZ&cJWw91Qyq2I=IG>h3s+uC(nlAfXc$LT_0Ytk+X1 zbPAuuhtLz~jr1c(wJDC!79<8vB~+>t-$^K)!8|7uomf|`jwnuJ2cJs3npnUK=$Q2q zTEHJ#3%Hz&KGG5}%QG(I)2q;3D^DxBzhgMSu(s(8**l!>E{*gT~THSHm_) zo}WI#fI1Hp2l&Z|9ry}@1Ls_zw>`(_jRv)Uu&u>u9dC7J!&_6HfPk>5Btd9H0h!bk zcQ_TGGNYqy#Dkcy0oEZ4=mH#QSJSX6f$F>^{Yv-n#+WpvUUjAarrjWE(NC)gNOOM% za}%xPe+vlmcjRMI?&rTi(SYJTQc)Gjx(ytR(+*RVyXB-P01M(=S0y5==ay8WY>AS# zOS-yR4)vC-W6UHl4gr9F%OswP@JQ8B09Nn8mVap6A2iA`{T4e*n@VfYFC_8MB= zm`V-bAw$=my+I~!_D`HEeN)=Jl-;~+p@C=c?{|9o?jf8S{;oLif%WmOYj|<*SH+>F z?9iXf7A*fIABRAv@n`~{Ee@bJ@uuYY)Bw7u7v9DL=$+h%_5OxI>w0R$`rLxW{pIy> zrVbs+uGaxg)0l*YCC=!}!sSE~W71u&{O?yX0GCLAnHAD`+#eINE1sp5$ zJ1bpOg#K@hH|bB(o?{-B6^&CJQkZ@vdUV)cz3DNtiHUh=!>wJFRgI_)y+4G8DO(lq zGf`UouM2dX3rff0#~I&yXxlo7e?Y4Ipbb?cEf41gTQcN1Bh{Iw6^hi;>> zuhG~oRKA6_-bM$1MEmci(Z+|*pIAbD-{bW+asQXN|KiBf(BUQAe*?ep?UN&)=I)|3 cc=+95S-+O{zI;!=S1gox5dwoLwm_c00r6Rn*8l(j literal 0 HcmV?d00001 diff --git a/app/api/v1/endpoints/texto/resume.py b/app/api/v1/endpoints/texto/resume.py new file mode 100644 index 0000000..8b7f4c7 --- /dev/null +++ b/app/api/v1/endpoints/texto/resume.py @@ -0,0 +1,39 @@ +""" +Gateway de IA de Qualidot - Módulo de Procesamiento de Texto para Resumen + +Propósito: + Este endpoint recibe un texto y lo resume usando inteligencia artificial. + +Homologación: + Sin importar qué proveedor de IA se utilice, el resultado siempre se entrega en el mismo formato estándar para Qualidot. +""" + +from dotenv import load_dotenv +from fastapi import APIRouter, Depends, UploadFile, File, HTTPException +from fastapi.security import APIKeyHeader +from app.api.v1.endpoints import router +from app.schemas.text_standard import StandardTextAnalysisResult, TextRequestFile +#from app.services.transcription_adapters import transcribe_audio_with_provider +from app.services.text.resume_adapters import summarize_text_with_provider + +# Inicializar el router de FastAPI para este módulo +text_router_summary = APIRouter() + +@text_router_summary.post("/summaries/", response_model=StandardTextAnalysisResult) +async def summarize_text(text_request: TextRequestFile = Depends()) -> StandardTextAnalysisResult: + """ + Endpoint para resumir texto simple infiriendo el proveedor de IA + + Args: + text_request: Objeto TextRequestFile que contiene el archivo de texto, + el proveedor, el modelo y las opciones de diarización, marcas de tiempo y análisis de sentimiento. + + Returns: + StandardTextAnalysisResult: Resultado del análisis de texto en formato estándar de Qualidot + """ + try: + # Resumir texto usando el adaptador de resumen de texto que infiere el proveedor + analysis_result = await summarize_text_with_provider(text_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/texto/rubricated_analysis.py b/app/api/v1/endpoints/texto/rubricated_analysis.py new file mode 100644 index 0000000..a0200ef --- /dev/null +++ b/app/api/v1/endpoints/texto/rubricated_analysis.py @@ -0,0 +1,39 @@ +""" +Gateway de IA de Qualidot - Módulo de Procesamiento de Texto para Análisis Rubricado + +Propósito: + Este endpoint recibe un texto y lo analiza usando una rúbrica de evaluación basada en inteligencia artificial. + +Homologación: + Sin importar qué proveedor de IA se utilice, el resultado siempre se entrega en el mismo formato estándar para Qualidot. +""" + +from dotenv import load_dotenv +from fastapi import APIRouter, Depends, UploadFile, File, HTTPException +from fastapi.security import APIKeyHeader +from app.api.v1.endpoints import router +from app.schemas.text_standard import StandardTextAnalysisResult, TextRequestFile +#from app.services.transcription_adapters import transcribe_audio_with_provider +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) +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 + + Args: + text_request: Objeto TextRequestFile que contiene el archivo de texto, + el proveedor, el modelo y las opciones de diarización, marcas de tiempo y análisis de sentimiento. + + Returns: + StandardTextAnalysisResult: Resultado del análisis de texto en formato estándar de Qualidot + """ + try: + # Analizar texto usando el adaptador de análisis de texto que infiere el proveedor + analysis_result = await evaluate_text_with_provider(text_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/video/__pycache__/transcription.cpython-312.pyc b/app/api/v1/endpoints/video/__pycache__/transcription.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..1c008f0a49ac42814321d377df0d6edbe107466ea0b691c279581b838fc3d6a8 GIT binary patch literal 1984 zcmaJ?O^g&p6t0?{-yUXNBSB>hTLy_4g(03yhE=lRXCVZ{-Gzj4WK!E*vlR3nRn@aB zf-yjh2Tz)ahKm>u65zDP7(HmRM=xX0pdAe+a?sntYU1C)S2fdb>{9 z*I!mDHiGd%`(u70kI+y0kPbMTv2q2Li^xSy`vCTFAV9;(S>qtO+1bfvL~rx^K^l@jO?Az^NTU{xEm_6h3sEibYm~n z)Po{)n4~^ufii@{+-0G(ZMcY*7A03%;y zC=`kcaxz}JN+J<<8FNE1@JWJc#Xat@&4hXI8X0?vf+fkB9|=Z)0lX4yj>4*0d@lWv zbVA`%co&mu>2lzL`iKfTj8EPEMMu1bld>05w+$gy*PH#lxVMNvhKd{HMa55j2pF;O1h=yfzs%;hthAV|b00 z2eTX0fN_?!O<>#eTpep`_aV&UbY*MG5GlD81V+I1C=4WP`=QIcq|%oXeXU7eFcpiy z0aOe}0e0u%TX_SfeFy`zOGphl&Y)|9{ds`+D;7fUSQo9=b2)StpTX1US@dRpBT@rs zBJ>0j)w3CuOZB@Mr7f6oCX?#AZlz#8hh2O&^J-=u&!bb;5j2l)So64+jW25G8l0Q-<6ToEM#JVlP_@R@whb6fB1}b4Ny5khC@`sU>vAgi=|0pq6CZQ} z1JJ<=fE`MR21}PcF1evfz%TFDW_|d4vzanEUFFpOpsYdqXw$U;raUOj6pE(gbani< zD1b9<8OGuP{wpL009}@fs#vxi09%rGnWEgQWkngXos@>i7e#%xq!KktRP=uB(5kMC zcE4T|%p|}IK@k6(-O*?rl1#`}M4c01Ym%!y@oeklpmA-97Ph#dBU>FR)kMT+AYvYL zxfYEg_;{=POe^h8vSm`#y3b@iT1ZAyN?KgvVX*!Xd~z2|KcnAYL(7lka^rV4?f7E< z%}rCc#-@73$G&}FW3RYz*+OI*|8b|h_8!8y@$W~fA6g&n_yjNR{(59vuej~6WedoE z%cnupIXscU7e=b+E&OQq?OYYTs|oMnD!NqKZe3Z|us+Rgw?4Okc%b%RQfNb?aoTMN zsOdLC2PG+JSHU%rg_F?bCG-zT=;G=Jcs)2@m(DTfQ(5l=8zfA>5hcx~e{D(4a52$^ zW;tN1r-3l-s7+lOL1&ZwG_LP!5~ka=@8e9eQ3y%ZQM-y(U`Aq{Hc=c0%*P&kUOWy* z-96+{m>?n;-^~rJ$J nnB2rKfA`$Z&yL(hqj StandardTranscriptionResult: + """ + Endpoint para transcribir video simple infiriendo el proveedor de IA + + Args: + video_request: Objeto VideoRequestFile que contiene el archivo de video, + el proveedor, el modelo y las opciones de diarización, marcas de tiempo y análisis de sentimiento. + + Returns: + StandardTranscriptionResult: Resultado de la transcripción en formato estándar de Qualidot + """ + try: + # Transcribir usando el adaptador de transcripción que infiere el proveedor + transcription_result = await transcribe_video_with_provider(video_request) + return transcription_result + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) \ No newline at end of file diff --git a/app/core/__pycache__/config.cpython-312.pyc b/app/core/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..19234a75c0afaa973386add96f1afd3be2aae5621798d70f182ac9cb1f56b193 GIT binary patch literal 784 zcmaKqy>HV%7{=eT9pfZTfl_tgqe!Ky;-O3ov`QnR=qM(LNSBrgs?sY2<#_9# zygbEgLtg$buT1gDA+HF&mC)5#tI#NNym}bVJ}Ku*MiCq3B7T%Vj3J&gDwX`+*5kve z>b0GGzFgQh_9k3OPsJL>p534r$0>{(K54olp27G<({{%l$q!?-PQ|*I%H3l*j97dX zW83rm$c`M}3o&M5KP<`RJzQ{wKgI2J>mZ~ov;w>K+_wr&wA-v&VzR@xzZ$ewiC+t? zBRh++OPH9lf{w;6t^4 zbL~~>WV^4d4OF&{Aht6S_8dn*smm-@FiddmDs z?TfO(<|Zb3f@OFdtD`r<_yZle$67evIlC@u!#{2aRLhN)x5*ZGiuf1a<1&yDLSG^I c4Hmyc`Z)7j$)L?&AS4Sj^KaK*Yr`Rb0i08}Pyhe` literal 0 HcmV?d00001 diff --git a/app/core/config.py b/app/core/config.py new file mode 100644 index 0000000..1f7d4c1 --- /dev/null +++ b/app/core/config.py @@ -0,0 +1,26 @@ +import os +from dotenv import load_dotenv + +load_dotenv() + +class Settings: + # Configuración para las claves de API de los proveedores de IA + + # --------------------------------------------------------------- + # Proveedores de Audio + # --------------------------------------------------------------- + # OpenAI + OPENAI_API_KEY: str = os.getenv("OPENAI_API_KEY", "") + + #AssemblyAI + ASSEMBLYAI_API_KEY: str = os.getenv("ASSEMBLYAI_API_KEY", "") + + # Deepgram + DEEPGRAM_API_KEY: str = os.getenv("DEEPGRAM_API_KEY", "") + + # --------------------------------------------------------------- + # Proveedores de Imagen + # --------------------------------------------------------------- + + +settings = Settings() \ No newline at end of file diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..ba7c11d --- /dev/null +++ b/app/main.py @@ -0,0 +1,60 @@ +from fastapi import FastAPI +from app.api.v1.endpoints.router import api_router_audio, api_router_text, api_router_image, api_router_video + +app = FastAPI( + title="Template de API de Procesamiento general", + description="Template de API para procesamiento de audio, imagenes, documento, video, etc", + version="1.0.0" +) + +app.include_router(api_router_audio, prefix="/api/v1", tags=["Procesamiento de Audio"]) +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.get("/") +def root(): + """ + Endpoint raíz para verificar que la API está corriendo y proporcionar información básica sobre los endpoints disponibles. + + Returns: + dict: Un mensaje de bienvenida y un resumen de los endpoints disponibles en la API. + + """ + return { + "message": "Template de API de Procesamiento general está corriendo con normalidad", + "docs": "/docs", + "endpoints": { + "audio": { + "transcripción de audio": "/api/v1/audio/transcripts/", + }, + "texto": { + "resumen de texto": "/api/v1/text/summaries/", + "análisis rubricado": "/api/v1/text/evaluations/" + }, + "imágenes": { + "análisis rubricado": "/api/v1/image/evaluations/", + }, + "video": { + "transcripción de video": "/api/v1/video/transcripts/" + } + }, + "modelos_disponibles": { + "audio": { + "openai": [ + "gpt-4o-transcribe", + "whisper-1" + ], + "assemblyai": ["universal-3-pro", "universal-2"] + }, + "texto": { + "pendiente de agregar proveedores y modelos específicos para procesamiento de texto" + }, + "imágenes": { + "pendiente de agregar proveedores y modelos específicos para procesamiento de imágenes" + }, + "video": { + "pendiente de agregar proveedores y modelos específicos para procesamiento de video" + } + } + } \ No newline at end of file diff --git a/app/schemas/__pycache__/audio_standar.cpython-312.pyc b/app/schemas/__pycache__/audio_standar.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..81a07eeb13a8875ee9b57f88702123cda02394f13621e926d952c030d187cc54 GIT binary patch literal 1751 zcmZ`(&1+pn6rcP3-c8b^rg`S0)(cYcLh^Pl3`K}vsi=uI4d~M)rxfdH&=N?DXjgt_n&83>*Q{P022wCzttnjJonJWrQx2gZ9WjjH)FlMQ!oG;8(MTZD}L3z69@Px9AC6u!yXE6#07CD(nuQNs= z7%cs8#VK+g@f&asDD`q0r%}4)SEcu>UG?ESIV^CX1z7Z`wyIeFne#f5vp5sXk+3+) z1>3@132uzJjMJ*d=g*x%7U#3Vk2p>9ru`3IfUA`DW)`JB{eVZzRZAUbkkB>o)!*?Y z`vm(AN^L#Md7Ai9fIEl78i3x%0QLwYEd^Mks)T7&Ie?4B0h<0IE!3#W45~4c>dXvH zYU~;5*f9E7Wvx@1xKj9Fqzq}qQQ?=H7DB@?op`N217 zdzI&xi?7?;gHA7Hoh6zEvK{&|U&;7YxVy;DS=-ODwhXQT=ViO1lP7Z@8QRP`WzF+= z!gJ5NG51^(hOTL-K+b`{%~e)|Fs{!IH}i+g}=_s9vd(pn`F9iY$2+Vsp-e)>xadNPcaI!cc{UY%iv#sNq#yRHoETEjD2CLp#ozUFcdqRhchf)g#ePJL zv*wPMmgfO$fg~34*3WpK6=w!o6MvIfo{|RMWQXD}76=nUlxE(^c(Th}U literal 0 HcmV?d00001 diff --git a/app/schemas/__pycache__/audio_standard.cpython-312.pyc b/app/schemas/__pycache__/audio_standard.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..b95addac46dd0f79e64520dec3d9667b8a2f7a982b83b38cee2e54e6297e587a GIT binary patch literal 4631 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 S@Y*OHNT^@v-zj)WK>shT%_J=V literal 0 HcmV?d00001 diff --git a/app/schemas/__pycache__/image_standard.cpython-312.pyc b/app/schemas/__pycache__/image_standard.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..ffcf4c364a95d8e8b07438a8efdf3fdb648219b6ebaadc29867e0fabd160bf05 GIT binary patch literal 2518 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 literal 0 HcmV?d00001 diff --git a/app/schemas/__pycache__/video_standard.cpython-312.pyc b/app/schemas/__pycache__/video_standard.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..b814004a920cc90952a28d09a9ba8a27b6f03ee6183d4e86c0120488c1194c8b GIT binary patch literal 3446 zcmaJ@O>Eu95#IO5_a{C5S+*q0He*>oOyn24ZZA&kLjG6KNKPyV!GjWFMedWBl3Zq& zRHFwK)xiaHXaV_TT?!;UIYFwjG?#s_3=26{dx?nPLLuzxK$$$wQz^cb>=5mR{P*7i5!U-&AWcxn;AuI-5DCYNm zRB2B>Jgz&w%>w4z4)a6iApvEs9tgh3m<`H6baFzELdSI)a3C=C5%;*u=czhF)7pm= z4i1p6{YCmvXW~AdRzJY?*U{<_CC3_z)AkIB-m}{fNPaGM>h3O!4xKCZHd8JKS zX-)S30D~8Vky;93Ds87~nKWTpJ8frvmrY^=R&ubCJGG*nSjpRkQ!B+&E2X2Ax@MP| zX1B4t-46c>E3jfeSY&q`jyjW3z0K}=maCPPx{aQ=^COI>4;fs84AB$Ob=amHpkh&S z*Rh;1vUTOm?~BK3JTJX>$d|Ep>1RQn7bt)zE=G-Of7>&^*8zJ0I?)ce)g9&x@y73_TF&t>Z3Y|r& zyA&b_57JC0ro-VO4C{{7T7ls8HvZ&>PT(@BFVHy`!j#FztwRM;_o%RFGdnRVA<@2| zRtPJYN*1;88}pbMys5zE(cL2xAY`e(@s;aHN9mAgfQQ+{^|8m(PbOz3Kbn4f>i)wC z92iPzqA$+YF;)whY+$*Fb1G}Wn+_F_Oz6$H1nS@}sTat&*whZntvr~JJP1`8`0sxV zxbZ0H>PIat3BCXd`4f3p3oQp5+|OvWGJbW(mBKB}lX>Llc-Eq60}M zk}f3ONP2)6`8X??TmS!35=}>lgXdo}cW=%Rd|}!^=gQd2-d#d&-AMy)_-=Zyqmmo?TcvL|2k%{t^qk$z zj8m(Qtdg$_!!oBG%yb)kU4PPnsc)oXCL z>(y&U+zCglW1X~^DJ?FT(Je4o#Q>7?NCuH0N{h=#E&z!;Oq2RPhfo09B_P@mvaTQ* zMlyl~5lxIDDFBJHwqu2242QWov}mEYguEk^djqFPP>=i;$S(oq?49m&+lBJ>nMVh? zOpmtmVD;`k$>g-X0=YP(825m2QQOS+uE~Tma7l4K0M3B6^8VV5gmG?AG4254oVN1b zS|(wPytQ3u+deZ1j1g^RdhO$N>-i@qn9EJ(qt%7A5WueSm&)Zt!ls3>p7c`RTwaGLO6Y0at_pq@Oc4$uoM+1N>2LWAw<5@Ib}3LJl4 z`LyX`RUMS`UUz6*c(+-IZi{QMJq3SR1+tq-rBZw8QY!o1S(3W+S2C=AZ~QacnHt(7 SKn_MOq_i)M?+Cn<`Tqr!g4iwq literal 0 HcmV?d00001 diff --git a/app/schemas/audio_standard.py b/app/schemas/audio_standard.py new file mode 100644 index 0000000..9e45a62 --- /dev/null +++ b/app/schemas/audio_standard.py @@ -0,0 +1,92 @@ +""" +Esquema de resultado esperado de modelos de transcripción. + +Propósito: + Define el formato estándar de los resultados devueltos por los modelos de transcripción + de audio, independientemente del proveedor de IA utilizado. + +Homologación: + Garantiza que el resultado de la transcripción siempre se entregue en el mismo + formato estándar para Qualidot. + +Pendiente (recomendación personal): + - Definir mejor la estructura de cada segmento según las opciones avanzadas (diarization, timestamps, sentiment) + - Delimitar los proveedores de IA soportados inicialmente +""" + +from fastapi import UploadFile +from fastapi.params import File, Form +from pydantic import BaseModel, Field +from typing import List, Optional + +class AudioRequestFile: + """Modelo de solicitud para transcripción de audio.""" + def __init__( + self, + file: UploadFile = File(..., description="Archivo de audio a procesar (ej. mp3, wav)"), + provider: str = Form(..., description="Proveedor de IA a utilizar (ej. openai, assemblyai)"), + model: str = Form(..., description="Modelo de IA a utilizar (ej. whisper-1)"), + diarization: Optional[bool] = Form(False, description="Activa la separación e identificación de múltiples hablantes"), + timestamps: Optional[bool] = Form(False, description="Activa las marcas de tiempo exactas por cada segmento hablado"), + sentiment: Optional[bool] = Form(False, description="Activa el análisis de sentimiento (POSITIVO/NEGATIVO) por segmento") + ): + self.file = file + self.provider = provider + self.model = model + self.diarization = diarization + self.timestamps = timestamps + self.sentiment = sentiment + +class TranscriptionSegment(BaseModel): + """Modelo que representa un segmento de transcripción de audio detallado.""" + text: str = Field( + ..., + description="Texto transcrito en este fragmento específico" + ) + speaker: Optional[str] = Field( + None, + description="Identificador del hablante (ej. 'Speaker A') si diarization está activo" + ) + start_time: Optional[float] = Field( + None, + description="Marca de tiempo en segundos donde inicia el segmento" + ) + end_time: Optional[float] = Field( + None, + description="Marca de tiempo en segundos donde termina el segmento" + ) + sentiment: Optional[str] = Field( + None, + description="Sentimiento detectado en el segmento (ej. 'POSITIVE', 'NEGATIVE')" + ) + +class StandardTranscriptionResult(BaseModel): + """Modelo que representa el resultado estándar de una transcripción de audio para Qualidot.""" + status: str = Field( + ..., + description="Estado final de la petición ('success' o 'error')" + ) + original_filename: str = Field( + ..., + description="Nombre original del archivo de audio procesado" + ) + provider_used: str = Field( + ..., + description="Proveedor de IA que ejecutó la transcripción (ej. OpenAI)" + ) + model_used: str = Field( + ..., + description="Modelo específico utilizado para el proceso" + ) + full_transcript: Optional[str] = Field( + None, + description="Texto completo y continuo de toda la transcripción" + ) + segments: Optional[List[TranscriptionSegment]] = Field( + None, + description="Lista detallada de segmentos si se solicitaron opciones avanzadas (diarization, timestamps, sentiment)" + ) + confidence_score: Optional[float] = Field( + None, + description="Nivel de certeza global del modelo (valor entre 0.0 y 1.0)" + ) \ No newline at end of file diff --git a/app/schemas/image_standard.py b/app/schemas/image_standard.py new file mode 100644 index 0000000..bfc529d --- /dev/null +++ b/app/schemas/image_standard.py @@ -0,0 +1,44 @@ +""" +Esquema de resultado esperado de modelos de análisis de imágenes. + +Propósito: + Define el formato estándar de los resultados devueltos por los modelos de análisis + de imágenes, independientemente del proveedor de IA utilizado. + +Homologación: + Garantiza que el resultado del análisis de imágenes siempre se entregue en el mismo + formato estándar para Qualidot. +""" +from fastapi import UploadFile +from fastapi.params import File, Form +from pydantic import BaseModel, Field +from typing import List, Optional + +class ImageRequestFile: + """Modelo de solicitud para análisis de imágenes.""" + def __init__( + self, + file: UploadFile = File(..., description="Archivo de imagen a procesar (ej. jpg, png)"), + 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: Optional[str] = Form(None, description="Rúbrica de evaluación personalizada para el análisis") + ): + self.file = file + self.provider = provider + self.model = model + self.rubric = rubric + +class StandardImageAnalysisResult(BaseModel): + """Modelo que representa el resultado estándar de un análisis de imágenes para Qualidot.""" + status: str = Field( + ..., + description="Estado del análisis (ej. 'success', 'error')" + ) + analysis: Optional[dict] = Field( + None, + description="Resultados detallados del análisis de la imagen, estructurados según la rúbrica si se proporcionó" + ) + error: Optional[str] = Field( + None, + description="Mensaje de error en caso de que el análisis haya fallado" + ) \ No newline at end of file diff --git a/app/schemas/text_standard.py b/app/schemas/text_standard.py new file mode 100644 index 0000000..131fe5a --- /dev/null +++ b/app/schemas/text_standard.py @@ -0,0 +1,44 @@ +""" +Esquema de resultado esperado de modelos de análisis de texto (JSON). + +Propósito: + Define el formato estándar de los resultados devueltos por los modelos de análisis + de texto, independientemente del proveedor de IA utilizado. + +Homologación: + Garantiza que el resultado del análisis de texto siempre se entregue en el mismo + formato estándar para Qualidot. +""" +from fastapi import UploadFile +from fastapi.params import File, Form +from pydantic import BaseModel, Field +from typing import List, Optional + +class TextRequestFile: + """Modelo de solicitud para análisis de texto.""" + def __init__( + self, + file: UploadFile = File(..., description="Archivo de texto a procesar (ej. txt, json)"), + provider: str = Form(..., description="Proveedor de IA a utilizar (ej. openai, google)"), + model: str = Form(..., description="Modelo de IA a utilizar (ej. text-1)"), + rubric: Optional[str] = Form(None, description="Rúbrica de evaluación personalizada para el análisis") + ): + self.file = file + self.provider = provider + self.model = model + self.rubric = rubric + +class StandardTextAnalysisResult(BaseModel): + """Modelo que representa el resultado estándar de un análisis de texto para Qualidot.""" + status: str = Field( + ..., + description="Estado del análisis (ej. 'success', 'error')" + ) + analysis: Optional[dict] = Field( + None, + description="Resultados detallados del análisis de texto, estructurados según la rúbrica si se proporcionó" + ) + error: Optional[str] = Field( + None, + description="Mensaje de error en caso de que el análisis haya fallado" + ) \ No newline at end of file diff --git a/app/schemas/video_standard.py b/app/schemas/video_standard.py new file mode 100644 index 0000000..65c31fd --- /dev/null +++ b/app/schemas/video_standard.py @@ -0,0 +1,65 @@ +""" +Esquema de resultado esperado de modelos de transcripción de video. + +Propósito: + Define el formato estándar de los resultados devueltos por los modelos de transcripción + de video, independientemente del proveedor de IA utilizado. + +Homologación: + Garantiza que el resultado de la transcripción siempre se entregue en el mismo + formato estándar para Qualidot. +""" +from fastapi import UploadFile +from fastapi.params import File, Form +from pydantic import BaseModel, Field +from typing import List, Optional + +class VideoRequestFile: + """Modelo de solicitud para transcripción de video.""" + def __init__( + self, + file: UploadFile = File(..., description="Archivo de video a procesar (ej. mp4, mov)"), + provider: str = Form(..., description="Proveedor de IA a utilizar (ej. openai, assemblyai)"), + model: str = Form(..., description="Modelo de IA a utilizar (ej. whisper-1)"), + diarization: Optional[bool] = Form(False, description="Activa la separación e identificación de múltiples hablantes"), + timestamps: Optional[bool] = Form(False, description="Activa las marcas de tiempo exactas por cada segmento hablado"), + sentiment: Optional[bool] = Form(False, description="Activa el análisis de sentimiento (POSITIVO/NEGATIVO) por segmento") + ): + self.file = file + self.provider = provider + self.model = model + self.diarization = diarization + self.timestamps = timestamps + self.sentiment = sentiment + + +class StandardTranscriptionResult(BaseModel): + """Modelo que representa el resultado estándar de una transcripción de video para Qualidot.""" + status: str = Field( + ..., + description="Estado final de la petición ('success' o 'error')" + ) + original_filename: str = Field( + ..., + description="Nombre original del archivo de video procesado" + ) + provider_used: str = Field( + ..., + description="Proveedor de IA que ejecutó la transcripción (ej. OpenAI)" + ) + model_used: str = Field( + ..., + description="Modelo específico utilizado para el proceso" + ) + full_transcript: Optional[str] = Field( + None, + description="Texto completo y continuo de toda la transcripción" + ) + segments: Optional[List[dict]] = Field( ##Queda pendiente definir mejor la estructura de cada segmento según las opciones avanzadas + None, + description="Lista detallada de segmentos si se solicitaron opciones avanzadas (diarization, timestamps, sentiment)" + ) + confidence_score: Optional[float] = Field( + None, + description="Nivel de certeza global del modelo (valor entre 0.0 y 1.0)" + ) \ No newline at end of file diff --git a/app/services/__pycache__/transcription_adapters.cpython-312.pyc b/app/services/__pycache__/transcription_adapters.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..85ad205e00282c148b0e96ae84a8cf5b8295cf18c8484d93e71f3e21eb3758c8 GIT binary patch literal 6883 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 K#gu*>2K_xoKsyfr literal 0 HcmV?d00001 diff --git a/app/services/audio/__pycache__/transcription_adapters.cpython-312.pyc b/app/services/audio/__pycache__/transcription_adapters.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..999534bc0345513c1128ceacd1c25a2566f40099fd2a3d80f9ae5c987c8962d2 GIT binary patch literal 7778 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 PAR4h-SkMTXfN}o^V~POd literal 0 HcmV?d00001 diff --git a/app/services/audio/transcription_adapters.py b/app/services/audio/transcription_adapters.py new file mode 100644 index 0000000..1455a5d --- /dev/null +++ b/app/services/audio/transcription_adapters.py @@ -0,0 +1,163 @@ +""" +Gateway de IA de Qualidot - Módulo de Adaptadores de Transcripción + +Propósito: + Este módulo contiene funciones de adaptadores que permiten transcribir audio usando diferentes proveedores de IA. + Cada función de adaptador se encarga de interactuar con un proveedor específico (como OpenAI, AssemblyAI, Deepgram, etc.) + y de convertir la respuesta del proveedor al formato estándar de transcripción de Qualidot. + +""" + +import tempfile +import os +from fastapi import HTTPException +from openai import OpenAI, AsyncOpenAI +import assemblyai as aai +from app.core.config import settings +from app.schemas.audio_standard import AudioRequestFile +from app.schemas.audio_standard import StandardTranscriptionResult +from app.utilities.audio_utilities import validate_audio_file, validate_audio_size, validate_audio_request +from app.core import config + +# Función de adaptador principal que infiere el proveedor y llama al adaptador específico +async def transcribe_audio_with_provider(audio_request: AudioRequestFile) -> StandardTranscriptionResult: + """ + Función de adaptador para transcribir audio usando el proveedor de IA configurado. + """ + provider = audio_request.provider.lower() + + match provider: + case "openai": + return await transcribe_with_openai(audio_request) + case "assemblyai": + return await transcribe_with_assemblyai(audio_request) + case _: + raise ValueError(f"Proveedor de IA no soportado: {audio_request.provider}") + +# Función de adaptador para transcribir audio usando OpenAI +async def transcribe_with_openai(audio_request: AudioRequestFile) -> StandardTranscriptionResult: + """ + Función de adaptador para transcribir audio usando OpenAI. + """ + client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY) + + audio_content = await audio_request.file.read() + + temp_audio_path = None # Inicializamos la variable fuera del try + + # Validar el audio antes de continuar + validate_audio_request(audio_request, audio_content) + + try: + # Crear archivo temporal para el audio + with tempfile.NamedTemporaryFile( + delete=False, + suffix=os.path.splitext(audio_request.file.filename)[1] + ) as temp_audio: + temp_audio.write(audio_content) + temp_audio_path = temp_audio.name + + with open(temp_audio_path, "rb") as audio_file_obj: + transcription = await client.audio.transcriptions.create( + model=audio_request.model, + file=audio_file_obj, + response_format="text" + ) + + result = StandardTranscriptionResult( + status="success", + original_filename=audio_request.file.filename, + full_transcript=transcription, + model_used=audio_request.model, + provider_used="OpenAI", + confidence_score=None + ) + + return result + + except Exception as e: + # Capturamos cualquier error de OpenAI o de lectura de archivos + raise HTTPException( + status_code=500, + detail=f"Error transcribiendo el audio: {str(e)}" + ) + + finally: + if temp_audio_path and os.path.exists(temp_audio_path): + try: + os.unlink(temp_audio_path) + except Exception: + pass + +# Función de adaptador para transcribir audio usando AssemblyAI +async def transcribe_with_assemblyai(audio_request: AudioRequestFile) -> StandardTranscriptionResult: + """ + Función de adaptador para transcribir audio usando AssemblyAI. + """ + aai.settings.api_key = settings.ASSEMBLYAI_API_KEY + + audio_content = await audio_request.file.read() + temp_audio_path = None + + # Validar el audio antes de continuar + validate_audio_request(audio_request, audio_content) + + try: + # Crear archivo temporal para el audio + with tempfile.NamedTemporaryFile( + delete=False, + suffix=os.path.splitext(audio_request.file.filename)[1] + ) as temp_audio: + temp_audio.write(audio_content) + temp_audio_path = temp_audio.name + + #Definimos el modelo a usar + config = aai.TranscriptionConfig(language_code="es", speaker_labels=audio_request.diarization, + sentiment_analysis=audio_request.sentiment) + + transcription_obj = aai.Transcriber(config=config).transcribe(temp_audio_path) + + iterable = transcription_obj.sentiment_analysis if audio_request.sentiment else transcription_obj.utterances + + + if transcription_obj.status == aai.TranscriptStatus.error: + raise Exception(f"AssemblyAI Error: {transcription_obj.error}") + + assemblyaiSegment = "utterances" if not audio_request.sentiment else "sentiment_analysis" + + result = StandardTranscriptionResult( + status="success", + original_filename=audio_request.file.filename, + full_transcript=transcription_obj.text, + model_used=audio_request.model, + provider_used="AssemblyAI", + confidence_score=None, + segments=[ + { + "text": segment.text, + "speaker": segment.speaker if audio_request.diarization else None, + "start_time": segment.start if audio_request.timestamps else None, + "end_time": segment.end if audio_request.timestamps else None, + "sentiment": segment.sentiment if audio_request.sentiment else None + } + for segment in iterable + ] if (audio_request.diarization or audio_request.timestamps or audio_request.sentiment) else None + ) + + return result + + except Exception as e: + # Capturamos cualquier error de OpenAI o de lectura de archivos + raise HTTPException( + status_code=500, + detail=f"Error transcribiendo el audio: {str(e)}" + ) + + finally: + if temp_audio_path and os.path.exists(temp_audio_path): + try: + os.unlink(temp_audio_path) + except Exception: + pass + # Aquí iría la implementación específica para AssemblyAI, similar a la de OpenAI pero usando su SDK y formato de respuesta + pass \ 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 new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..2971a1352221941e974daee6e0cdc82530df01a2eedb9d10b0c664aa91fe7171 GIT binary patch literal 2674 zcmbVOU2IfE6rQ_(-EQ}%v_)Hia+~nCfi`FiA}MWdu$2Za6$%*<_B zgoG61laEFoj1L$eG)Q7>VvKL3@yVBMYSQFF1BoyCwx&EFPo9~(yIoY&aFadv%$=EY z&YbgoXZEK|CWYYny!4&vrV;u@3>r_YuB?9y%1vaT0x}dMVkrehz9WSQyrWjsjum2x zn2T9)J5fm3ZG|>Dj#$Y;JA98@DZ8W4A-^T8w4Etr>}(+`$8A>5?kseIo+KHgon*@~ zBlSV7u&HI-0pqTgaT>1vEfp}#Bgj5TRzs!rA0^Xw3lwp5{IxGiFSKXt#wyPfZh@(yH zQ#HnjJ!4gb{stl5EXDRdm2iD1Fa67eg1tFHxk*(EtB^U*CyWcPR*Mg8sb!bin7iPP zFWqtshy(%)58q7jMh;-EQKRszsowP0MbFfumCObixZ*kb1fMb=-*&t(z zjU^iH3=))ZpE^2#s`&^O+<@PD0OA-@fC*R8h352zav{2*H?-Sz<(hI;xeTnixCbHH zqaYq{thkD9*S+09M&y04@k!)n^uu@@okT`duJ7h;|7UeO;_Z!fM(m$s{1kl_Y4}@E zUPB+f3cu}lqVq~V?w^I-vhd>q*?+^FhbewGoS(u2loyB?=Gdn|xvb}e-Ekoc#ssNF%3e4xaH2n zt0x2-C#89lpD%@;f)}6uip`Sk0(M^sQm29aWQdluKEV5HrGdq3$_B=b7KBPLKLXU`%wbYr*=L`x4FAT)|L?3tyDq~Z@l2yeBt<7V+%(5B+0 z9~=Qy*#ujR8h9ulqk{7^0V2pApBkIgCQ91WMCsVrdqMZ4%O@Hk5Tg~9(5fuBZg?G- zv|AYIY>3^Uuw5V|LLQ}1w(pDH)q#V{0|$T3yncQBQ77u?ul5&LQfI5NvwtvAgoRyu zo>4&+X8&42qgkT`MOGu?b6ECY)c`U(OsD1yTpWU?ZsXdMG}k7cryRZpj$3QcbW-Ph zlimMX2!-~7oxCDR&jeJ}FsqwDr9vEl9}@%&v)U_jh*wfG)!0luhv}@6?+MyUCFlaB zQjp}(0LtRxiMT9i6I}s-Lj^ZzMhKC334kDl>(?$egD3`(gPt*@!@m*`;!+AB@{;(< z^z+2VY$$9HU{hXcR9pu%2_i@~_EORMWzz`3A&&7CS_g^b;ezBaG(O)VBUHdeoSIo6 zYEeZ|evc%T*m@6AI`5(OH8k)5?Yf70*U*j!XnGB8sU>pCyGr%IcnwMASb1G4wH+Dd ioKhVcsUfKxIkzsAT7Mb StandardImageAnalysisResult: + """ + Función de adaptador para evaluar imágenes usando el proveedor de IA configurado. + """ + provider = image_request.provider.lower() + + match provider: + case "openai": + return await evaluate_with_openai(image_request) + case "inserte nombre de otra ia aqui": + return await evaluate_with_ai_model2(image_request) + + # AGREGAR OTROS CASOS PARA DIFERENTES PROVEEDORES DE IA AQUÍ + + case _: + raise ValueError(f"Proveedor de IA no soportado: {image_request.provider}") + +# Función de adaptador para evaluar imágenes usando OpenAI +async def evaluate_with_openai(image_request: ImageRequestFile) -> StandardImageAnalysisResult: + """ + Función de adaptador para evaluar imágenes usando OpenAI. + (Plantilla para futuras implementaciones) + """ + client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY) + + # PASOS A SEGUIR PARA IMPLEMENTAR LA LÓGICA DE EVALUACIÓN CON OPENAI: + # 1. Validar la imagen de entrada (tamaño, formato, etc.) + # 2. Configurar el cliente de OpenAI con la clave API + # 3. Llamar a la API de OpenAI para evaluar la imagen + # 4. Convertir la respuesta de OpenAI al formato estándar de evaluación de imágenes de Qualidot + # 5. Manejar errores y excepciones adecuadamente + raise NotImplementedError("La función evaluate_with_openai aún no está implementada.") + +async def evaluate_with_ai_model2(image_request: ImageRequestFile) -> StandardImageAnalysisResult: + """ + Función de adaptador para transcribir video usando otra AI. + (Plantilla para futuras implementaciones) + """ + + # PASOS A SEGUIR PARA IMPLEMENTAR LA LÓGICA DE TRANSCRIPCIÓN CON IA (ELEGIR MODELO): + # 1. Validar el video de entrada (tamaño, formato, etc.) + # 2. Configurar el cliente de OpenAI con la clave API + # 3. Llamar a la API de OpenAI para transcribir el video + # 4. Convertir la respuesta de OpenAI al formato estándar de resumen de texto de Qualidot + # 5. Manejar errores y excepciones adecuadamente + raise NotImplementedError("La función transcribe_with_ai_model2 aún no está implementada.") + +# Otros modelos de IA \ No newline at end of file diff --git a/app/services/text/__pycache__/evaluations_adapters.cpython-312.pyc b/app/services/text/__pycache__/evaluations_adapters.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..c4b6d914ae05caf6df24ac7d95945bb70a047b8ab6f86ba3bfff1caab2ac4d8c GIT binary patch literal 2117 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 ScWE2RzV%`KrBaxD$$tRE?_>i2 literal 0 HcmV?d00001 diff --git a/app/services/text/evaluations_adapters.py b/app/services/text/evaluations_adapters.py new file mode 100644 index 0000000..b136d8c --- /dev/null +++ b/app/services/text/evaluations_adapters.py @@ -0,0 +1,50 @@ +""" +Gateway de IA de Qualidot - Módulo de Adaptadores de Resumen de Texto + +Propósito: + Este módulo contiene funciones de adaptadores que permiten resumir texto usando diferentes proveedores de IA. + Cada función de adaptador se encarga de interactuar con un proveedor específico (como OpenAI, AssemblyAI, Deepgram, etc.) + y de convertir la respuesta del proveedor al formato estándar de resumen de texto de Qualidot. + +""" + +import tempfile +import os +from fastapi import HTTPException +from openai import OpenAI, AsyncOpenAI +import assemblyai as aai +from app.core.config import settings +from app.schemas.text_standard import TextRequestFile, StandardTextAnalysisResult +from app.core import config + +# Función de adaptador principal que infiere el proveedor y llama al adaptador específico +async def evaluate_text_with_provider(text_request: TextRequestFile) -> StandardTextAnalysisResult: + """ + Función de adaptador para evaluar texto usando el proveedor de IA configurado. + """ + provider = text_request.provider.lower() + + match provider: + case "openai": + return await evaluate_with_openai(text_request) + # AGREGAR OTROS CASOS PARA DIFERENTES PROVEEDORES DE IA AQUÍ + case _: + raise ValueError(f"Proveedor de IA no soportado: {text_request.provider}") + +# Función de adaptador para evaluar texto usando OpenAI +async def evaluate_with_openai(text_request: TextRequestFile) -> StandardTextAnalysisResult: + """ + Función de adaptador para evaluar texto usando OpenAI. + (Plantilla para futuras implementaciones) + """ + client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY) + + # PASOS A SEGUIR PARA IMPLEMENTAR LA LÓGICA DE EVALUACIÓN CON OPENAI: + # 1. Validar el texto de entrada (tamaño, formato, etc.) + # 2. Configurar el cliente de OpenAI con la clave API + # 3. Llamar a la API de OpenAI para evaluar el texto + # 4. Convertir la respuesta de OpenAI al formato estándar de evaluación de texto de Qualidot + # 5. Manejar errores y excepciones adecuadamente + raise NotImplementedError("La función evaluate_with_openai aún no está implementada.") + +# Otros modelos de IA \ No newline at end of file diff --git a/app/services/text/resume_adapters.py b/app/services/text/resume_adapters.py new file mode 100644 index 0000000..31def8b --- /dev/null +++ b/app/services/text/resume_adapters.py @@ -0,0 +1,50 @@ +""" +Gateway de IA de Qualidot - Módulo de Adaptadores de Resumen de Texto + +Propósito: + Este módulo contiene funciones de adaptadores que permiten resumir texto usando diferentes proveedores de IA. + Cada función de adaptador se encarga de interactuar con un proveedor específico (como OpenAI, AssemblyAI, Deepgram, etc.) + y de convertir la respuesta del proveedor al formato estándar de resumen de texto de Qualidot. + +""" + +import tempfile +import os +from fastapi import HTTPException +from openai import OpenAI, AsyncOpenAI +import assemblyai as aai +from app.core.config import settings +from app.schemas.text_standard import TextRequestFile, StandardTextAnalysisResult +from app.core import config + +# Función de adaptador principal que infiere el proveedor y llama al adaptador específico +async def summarize_text_with_provider(text_request: TextRequestFile) -> StandardTextAnalysisResult: + """ + Función de adaptador para resumir texto usando el proveedor de IA configurado. + """ + provider = text_request.provider.lower() + + match provider: + case "openai": + return await summarize_with_openai(text_request) + # AGREGAR OTROS CASOS PARA DIFERENTES PROVEEDORES DE IA AQUÍ + case _: + raise ValueError(f"Proveedor de IA no soportado: {text_request.provider}") + +# Función de adaptador para resumir texto usando OpenAI +async def summarize_with_openai(text_request: TextRequestFile) -> StandardTextAnalysisResult: + """ + Función de adaptador para resumir texto usando OpenAI. + (Plantilla para futuras implementaciones) + """ + client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY) + + # PASOS A SEGUIR PARA IMPLEMENTAR LA LÓGICA DE RESUMEN CON OPENAI: + # 1. Validar el texto de entrada (tamaño, formato, etc.) + # 2. Configurar el cliente de OpenAI con la clave API + # 3. Llamar a la API de OpenAI para resumir el texto + # 4. Convertir la respuesta de OpenAI al formato estándar de resumen de texto de Qualidot + # 5. Manejar errores y excepciones adecuadamente + raise NotImplementedError("La función summarize_with_openai aún no está implementada.") + +# Otros modelos de IA \ No newline at end of file diff --git a/app/services/video/__pycache__/transcription_adapters.cpython-312.pyc b/app/services/video/__pycache__/transcription_adapters.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..ca3e8be18667d70ab22649e6f45f06e6db21be7488195361d255eaac8293f067 GIT binary patch literal 2429 zcmbtVU1$_n6uvY2lg(x~n!MlTC1y;Wy^AR=58`Nf8Kj1 zG1h`;=~Ev|eJFis-$aDcK#{&l9{aMAhrno1=u7*yWi42rdhVUw&8iio9hfuc{+*wD zzVFPh`Fs|^^F#G#dnSj_Z*tIil5M5+H7M7Sh04g%EZxz{n)>Qx9li-C;U>#TO|B)K zl$$Q6-M(_48tYD`+z<09C+iNB2h?2J$+`J*-W@Cts&SuFaEHo6pl4{_>ZgPCq?P?5 zS$?HwJOJb2o^cMwBa3=5AKumw${r= zeJ{2}dQ4vkDGn%eZ9zQ@zCzb#SkQ~Y#~~-41+mz5%BUycY{2|^O1m-57*opSAut$w zXouEyfH}p~GYOj|GGrU9Fk*_3FbtvL&|5c&DG#W*e5-DoKHhEmu8%(osAtUV!3O8l ztvQQQf1lD|mJxRkrox;mD*x&(!QMP&0-|?_Oe6>?7t*WKO+-Omc(s7*8fWa3%GakUc45vca z-YkbLL#kLODcm*}M{I{in@dDUq&hn;qnmAS z*N~-u1~$IYuP44trO+v4CDi^A(f2aD`;qAH?6Z>p9^<#@JH6xYs`fVe>P>jJ-AXKI z#Z>qyoK^)tBGLRD)&jx^7Hyu?00`>tIxv0;2T)$%UVO(P1D3@_zO=#f-5R432Y@1* z;Juh!2<_v~FYHqbm&Sp9F(u>~u<7}j`+?6Sb@$_9D#}RGKoMD#a{LAOjCDh#)dgG3 zRf%17eZcA77ccFLvS)}B(!-4Ttf({D23Z~i*Fj+X#5}aWa+*`dD*-Xj`<0`%m=0@| z3+>i287xj(zR4?f!i5poCF%wa^q~p~f(jg)w@u0`>b@$S@D#kOippSjDp-uhJ4-cM zjd7{s?IQE$mz^Ox!I@tQ01%Oh3#HujO~%AKUli zft4|1b;M}ojC;>cz|q0N{oyT*Z3k|0 z6TThXU47k{(0^Fj@Y^ zli*Wrt;d{2rfz7_$Z=oHbX-tN!RUsjV8mtKm<@wug^t(-N^2B^s^X)KXkffCPFAwD zMzZ#IOLm;AxxxQ+rAz|lrSk)}y*NL%0Zg%tAbN8dl9N)rC6zHLHi0OPMt#*PaG_d_ zG6HB&m)%(RdDJJlK?0jea71Yzem=xzGFK*>VU#89-w?5*1OZV1@=TfVg9ag+HA~BF zo8&Av=cr5gRLl}Et6DviZIWf=u1x1FlPys(C%%0d@eR}hiBwIZ%)8JWLWdq?(3+vU zLzWJRWD+Wr8&b`Nb_LpvUz(i+;_Oc%6Iw8lHrO{A1#b1kJb ccjTaUjW-T8ky573mQtEqo}GWB=9z@UKM-QSrT_o{ literal 0 HcmV?d00001 diff --git a/app/services/video/transcription_adapters.py b/app/services/video/transcription_adapters.py new file mode 100644 index 0000000..aa8fb41 --- /dev/null +++ b/app/services/video/transcription_adapters.py @@ -0,0 +1,67 @@ +""" +Gateway de IA de Qualidot - Módulo de Adaptadores de Transcripción de Video + +Propósito: + Este módulo contiene funciones de adaptadores que permiten resumir texto usando diferentes proveedores de IA. + Cada función de adaptador se encarga de interactuar con un proveedor específico (como OpenAI, AssemblyAI, Deepgram, etc.) + y de convertir la respuesta del proveedor al formato estándar de resumen de texto de Qualidot. + +""" + +import tempfile +import os +from fastapi import HTTPException +from openai import OpenAI, AsyncOpenAI +import assemblyai as aai +from app.core.config import settings +from app.schemas.video_standard import VideoRequestFile, StandardTranscriptionResult +from app.core import config + +# Función de adaptador principal que infiere el proveedor y llama al adaptador específico +async def transcribe_video_with_provider(video_request: VideoRequestFile) -> StandardTranscriptionResult: + """ + Función de adaptador para transcribir video usando el proveedor de IA configurado. + """ + provider = video_request.provider.lower() + + match provider: + case "nombre de la ia 1 aqui": + return await transcribe_with_ai_model1(video_request) + case "nombre de la ia 2 aqui": + return await transcribe_with_ai_model2(video_request) + + # AGREGAR OTROS CASOS PARA DIFERENTES PROVEEDORES DE IA AQUÍ + + case _: + raise ValueError(f"Proveedor de IA no soportado: {video_request.provider}") + +# Función de adaptador para transcribir video usando OpenAI +async def transcribe_with_ai_model1(video_request: VideoRequestFile) -> StandardTranscriptionResult: + """ + Función de adaptador para transcribir video usando OpenAI. + (Plantilla para futuras implementaciones) + """ + + # PASOS A SEGUIR PARA IMPLEMENTAR LA LÓGICA DE TRANSCRIPCIÓN CON IA (ELEGIR MODELO): + # 1. Validar el video de entrada (tamaño, formato, etc.) + # 2. Configurar el cliente de OpenAI con la clave API + # 3. Llamar a la API de OpenAI para transcribir el video + # 4. Convertir la respuesta de OpenAI al formato estándar de resumen de texto de Qualidot + # 5. Manejar errores y excepciones adecuadamente + raise NotImplementedError("La función transcribe_with_ai_model1 aún no está implementada.") + +async def transcribe_with_ai_model2(video_request: VideoRequestFile) -> StandardTranscriptionResult: + """ + Función de adaptador para transcribir video usando OpenAI. + (Plantilla para futuras implementaciones) + """ + + # PASOS A SEGUIR PARA IMPLEMENTAR LA LÓGICA DE TRANSCRIPCIÓN CON IA (ELEGIR MODELO): + # 1. Validar el video de entrada (tamaño, formato, etc.) + # 2. Configurar el cliente de OpenAI con la clave API + # 3. Llamar a la API de OpenAI para transcribir el video + # 4. Convertir la respuesta de OpenAI al formato estándar de resumen de texto de Qualidot + # 5. Manejar errores y excepciones adecuadamente + raise NotImplementedError("La función transcribe_with_ai_model2 aún no está implementada.") + +# Otros modelos de IA \ No newline at end of file diff --git a/app/utilities/__pycache__/audio_utilities.cpython-312.pyc b/app/utilities/__pycache__/audio_utilities.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000000000000000000000000000..fa521a5c9b63e98eeccc9f2449562ccb77e8a590d9c5306abd929bf6959e8e74 GIT binary patch literal 2468 zcmb7FO>7%Q6rT02f8xYRf8r!09+s458oIpx%U_v$+JFWu%}yQaB}~ad{3juZ46z&+9^7;MlyN zh4n~2!lBzp2|wcUQP2_4F(sOBBW+4-A&ihO^Kqq(@Jd_>%?L{S$3nhc=^!1!I055? z(n%t04%WM1+*uBZyB1EwugjXM$cjOUg^Pynlt7y@m9b`6n3NRLP)oLj6@q0)QH`Pa z21_9)hTQlyQ!`}cf~pZWacOGmYIeRzOj|WdBIoYPFzX3&&mor0OvLa)+}mMTH_7dV zHot#go?B@5yYqu=5X|dlsyVNxWRYJGhII2JNM;57l)SMI0#OlMsq617&9G1+l-Ld} z6*(4X(Ac%}@NNDEVi_%>SCv^pt-RtbWAVk^0Ikl()6bwztk8 zc3#JevrmEu6@eu(+w~HQVCq9|k+XNz*I$S(ygj;dM2tEWu#IDAeswX?@Qguq**0() z+p;dNd~HBUH7wKO9d(ZN>*Zx$o@<8Qw$fgYO?fr)E;x-dElya*a~_Il!`$mk$f`xQ z?Xd$RX*{X2xatv<3|w?{QzL-Fk_SEt;888rHY{(qjZfjeoU@SGg&>uB&=`E@RvrS5 zjUhZ~Xa=?jHVM^Lo0QUc?CQy*csg_aC>~2?9*5j^%a(1&l8S}`Xizjj3#I^743Y zQVh{3^Y3J~5_qw=jgrNHit#_wBk965i%_dz%Eh}z;i76^a&8swIjmHMJ!qB(6{Bbs zW@O9GnCcXP%a?6ZkWI7T*eV2~606|Xy)hm#%kEyk*fx=xpr8!G458LN5KE}mgJOHu zq6e$dgRA{#s?jsQbRJmmJFuR7e=E#)#cPOfi*I$Hp1$Sj&!*RshpWlM4`$c;Pgna- zKToE=J6=tmTT6~qlOxZQ*`?fiG`affa5Xwy-{od=ml}^^$2Q?mnbUBCfN?tB26r2hgb{FnSwq4^+`!(znw88-HC+N9ieqWM9= z#rw4LpHfCF&*x=a3(`pA5f4ZW*H-e8624WoiBFQoj{l2$1Ric66R@!!vcB?gUeygR zILdx!&Iu6NCSCv2B_N(3E_Op>=P7#;bE4q#0BN^-EOR4Z!Q|z9Ryu!kDjN`Ccgxb) zd6AugW3d9oH>PE5lPtHA$?puB3*pJa;b=X{7y>zp3 zF<;3SDtkZJ38CY3KifTk5PN#yzg2Af5nzqM$Ab`{qXj~<+wEYLwK}Xg)CAHZ*PJaQ zk;{p^M;upR6!x+JQojM-e%d~h{k(fxSQmsy4}*0YK8xMX68b~v{XDVOH&E>xSQU?RgkBC_OplRws2_antZpcdM&6}Iz?N0hvO&~%j^q9cg*ag|g1Gpv zXz)+;&fgKVPvR;l=_Us&XwQ0gPX+B>?|tj5RJC{b*`aFhsS4_@MS8jTgZF9()U(tk iQ?*nlOgS|K>RI None: + """ + Valida el formato y tamaño del archivo de audio. + + Args: + audio_file: Archivo de audio a validar + + Raises: + HTTPException: Si el archivo no cumple con los requisitos + """ + # Validar formato (content-type) + if audio_file.content_type not in ALLOWED_AUDIO_FORMATS: + raise HTTPException( + status_code=400, + detail=f"Formato de audio no válido. Solo se permiten: MP3, WAV, M4A" + ) + + # Validar extensión del archivo + file_extension = os.path.splitext(audio_file.filename)[1].lower() + if file_extension not in ALLOWED_EXTENSIONS: + raise HTTPException( + status_code=400, + detail=f"Extensión de archivo no válida. Solo se permiten: .mp3, .wav, .m4a" + ) + + +def validate_audio_size(audio_content: bytes) -> None: + """ + Valida el tamaño del contenido del audio. + + Args: + audio_content: Contenido del archivo de audio en bytes + + Raises: + HTTPException: Si el archivo excede el tamaño máximo + """ + if len(audio_content) > MAX_AUDIO_SIZE_BYTES: + raise HTTPException( + status_code=400, + detail=f"El archivo excede el tamaño máximo permitido de {MAX_AUDIO_SIZE_MB}MB" + ) + +def validate_audio_request(audio_request: AudioRequestFile, audio_content: bytes): + # Validar el archivo de audio + validate_audio_file(audio_request.file) + # Validar tamaño del archivo + validate_audio_size(audio_content) \ No newline at end of file diff --git a/docs.md b/docs.md new file mode 100644 index 0000000..ff67d25 --- /dev/null +++ b/docs.md @@ -0,0 +1,77 @@ +# Qualidot AI Gateway - FastAPI Template + +## Metadatos + +- **Autor:** Alan Ivan Sánchez Gómez +- **Revisor:** Francisco Pineda +- **Proyecto:** Qualidot +- **Versión:** 1.0 +- **Fecha de creación:** 12/03/2026 +- **Última actualización:** 14/03/2026 +- **Lenguaje:** Python 3.12+ +- **Tipo:** Microservicio +- **Dominio:** IA / NLP / Audio / Visión / Multimodal +- **Estado:** Desarrollo (MVP Funcional) + +--- + +## Descripción General + +Este microservicio es una **Template de FastAPI** diseñada para el consumo de modelos de IA (OpenAI, AssemblyAI, etc.). Su característica principal es la **homologación de respuestas**: sin importar el proveedor utilizado, el cliente siempre recibe los resultados en un formato estándar de Qualidot. + +--- + +## Objetivo + +- **Estandarización:** Proveer una base sólida para integrar nuevos modelos de IA rápidamente. +- **Evaluación:** Facilitar el testing de modelos mediante Postman con una estructura de datos predecible. +- **Escalabilidad:** Separar la lógica de negocio de los proveedores externos mediante el uso de adaptadores. + +--- + +## Cambios a Implementar + +- Agregar endpoints para procesamiento de texto, imagen y video. +- Mejorar la gestión de errores y mensajes de validación. +- Implementar autenticación y autorización (JWT o API Key). +- Añadir pruebas unitarias y de integración. +- Optimizar el manejo de archivos temporales y recursos. +- Agregar ejemplos de uso y scripts de automatización para testing. + +--- + +## Arquitectura / Flujo + +Entrada (Postman/Client) + ↓ +Validación (Check de extensión y peso del archivo) + ↓ +Preprocesamiento (Creación de archivos temporales / Seek(0)) + ↓ +Modelo(s) IA (Adaptadores de OpenAI, AssemblyAI, etc.) + ↓ +Postprocesamiento (Mapeo a esquema homologado StandardTranscriptionResult) + ↓ +Salida (Respuesta JSON estandarizada) + +## Estructura del proyecto +Para mantener el orden y evitar errores de importación circular, el proyecto se organiza de la siguiente manera: + +fastApiTemplate/ +├── app/ +│ ├── api/ +│ │ └── v1/ +│ │ └── endpoints/ # Definición de rutas y routers +│ │ ├── audio/ # Endpoints específicos de procesamiento de audio +│ │ ├── image/ # (Pendiente) Procesamiento de visión +│ │ ├── video/ # (Pendiente) Procesamiento de video +│ │ └── router.py # Router maestro que unifica los módulos +│ ├── core/ +│ │ └── config.py # Configuración central (Settings y Pydantic-settings) +│ ├── schemas/ # Contratos de datos (Pydantic Models) +│ ├── services/ # Adaptadores y lógica con proveedores de IA +│ ├── utilities/ # Helpers (Validaciones, conversiones de audio) +│ └── main.py # Punto de entrada de la aplicación +├── .env # Credenciales y API Keys (No trackeado en Git) +├── requirements.txt # Dependencias del proyecto +└── docs.md # Documentación técnica \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ecc246d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,28 @@ +# 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 + +# Procesamiento de audio +noisereduce +librosa +soundfile +praat-parselmouth +numpy + +# Modelos de IA e integraciones +openai +langchain +langchain-openai +assemblyai \ No newline at end of file