From bc513ad2c4eda0cfc4c5dc0cdb0b9829fad47f3291c17c4e098712942f7aa45d Mon Sep 17 00:00:00 2001 From: lansan69 Date: Tue, 31 Mar 2026 03:54:46 -0600 Subject: [PATCH] =?UTF-8?q?Im=C3=A1genes=20OpenAI=20y=20Claude?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rubricated_analysis.cpython-312.pyc | Bin 2138 -> 2137 bytes .../v1/endpoints/image/rubricated_analysis.py | 2 +- app/core/__pycache__/config.cpython-312.pyc | Bin 852 -> 934 bytes app/core/config.py | 1 + .../image_standard.cpython-312.pyc | Bin 4460 -> 4894 bytes app/schemas/image_standard.py | 10 ++- .../evaluations_adapters.cpython-312.pyc | Bin 6684 -> 8266 bytes .../prompt_builder.cpython-312.pyc | Bin 3484 -> 3478 bytes app/services/image/evaluations_adapters.py | 77 ++++++++++++++---- app/services/image/prompt_builder.py | 4 +- requirements.txt | 3 +- 11 files changed, 75 insertions(+), 22 deletions(-) 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 index f811b386fc133901ab5304f63d98c24509d67d512136d370d5eaabd8c08b4f91..67e4025f4e07e3b08210e3bb79ed154bb1e0b87bd92f44127102617461cab811 100644 GIT binary patch delta 69 zcmca5a8rQyG%qg~0}$vckq!Y?pN YUuKM$tj{6G6Uq3INr2I&NDZhF03y;9_5c6? diff --git a/app/api/v1/endpoints/image/rubricated_analysis.py b/app/api/v1/endpoints/image/rubricated_analysis.py index 7a0086e..8fe1d8c 100644 --- a/app/api/v1/endpoints/image/rubricated_analysis.py +++ b/app/api/v1/endpoints/image/rubricated_analysis.py @@ -21,7 +21,7 @@ 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) +@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 diff --git a/app/core/__pycache__/config.cpython-312.pyc b/app/core/__pycache__/config.cpython-312.pyc index 8bd36b5dd63c72ac3eafcbf8ff1b24f97602d02f3e1ee906bd0bb25c36b83a0f..4a5b6fe05e4c6894fb30cd2ad7f3d52c5ad31267ed81fd609af0a10995c21784 100644 GIT binary patch delta 187 zcmcb@wv3(kG%qg~0}%LioX$*~$UBMg)x-ryC$5(iWJ_UM%> zc1zIFFT^9rKfu#D-Z8*4-rF_OPm^cz0mdk%BJRoJOreZ5lXICeR4=fI+>lV5!8D(9 zCg%ku(+x$JC9E#8h!%+ejVodW5}K?<{F9$CDM$%}M8LwLK-Mn~o80`A(wtPgB8kZg Y%yMGFjMg6+K!g?pk4U#wBL`S5002rbn*aa+ delta 154 zcmZ3+eua(qG%qg~0}vc-Ii0CCk#`c~k%u|3zG$Yev6Fc?R44!C%hJ;X>L?Ne5#k_1 z3q)vx2pte103swngg%HcoII8P+~huPF~*|F2An37Z*mJUa!!83EgjC4%ALw12~?BH zp2BGZq$`;;xo(LDmn7z;Bo?K3<|d}6I_4$jR2F9z7ij?H{fcx!Ch~#^uq}EZ7T6X8 z5X%rqX!1_><(Kk9I0EcMuo|$7L28OPK?Dzo09juo1tO$DgfWaT2QrF%fJ7_9O_rp| zLIS?*AUQux;mK(NRgA`y?+D0pSpr2EK??IHGYiH^eqrEXEMWQ~#sVZ~cz$I7(O|Oy DDYR1s delta 155 zcmbQI_C|^CG%qg~0}vc-Ii2avGm%e%al=IQjVzV?ngSa?UFBvh-F$+lo{_P1vIn0B zketXD4J6<4P0-T-YAO-~5#k_16GUi%2yGA{03swngbs+%om?v@JNX3vMrJ=vp~7&-6`t8$lDo@)F8?Kwlqg%KWXr1SAIO$}WJ!*H6kGm*>+oQL=B^b=yCgGQ zS&l%Nh8wgF5EL~Qg&RbL4YY@*c7Xuup>-~iBLhVO1Y%rN#MUi*=pnsPl@3l?6rCA~ zvSFZyK5)PJ-kUc&vor6#;oR;oj<~*bIz@ox>Ce8*e-pdo3Ns#fXIop9Ni#B(8I?`5 zwB^zqZdui$S<_YsFy~a8#;1A8Evlf2X;HJM?Nnz}N7{*ftg1_Mr`^0lTPb&_ZCWfH!`!JzvP5d6_|Pg0TADq?}85)X9}9a zU_pNy6-)Wt{MW3TZTI56LLm&MEmth)NZSPw{x^^f$Zq35d?=g1w%0`VRKSGqH6vDK?;KaL{tEsCo(KED z2MhR_NCLg1>DdWTY}TfeRZx+AvZQwz_r(zO8^0IhFlfNgAtPwt4?i}}+C$>UjLGTm zmK4-&Ap6enc03C0z(DM%s4A$PShgE~u=kIew(?Xir&M8ecm`{hee$TNA#INqN}VsJj;GUJ;dik&cE6~smlkaoRj@Gigj?OLCa zaP2wP4Ijaw0q#@xa5r4A5AwqRJeJ_lA?~q%unQ}nA7F6L=Z7HXi$ECeVi$$Ta0k2C zVIjPW#l0vp|B>qn3ww>M?-Cp}9{G~+fbloqBs^%Gly<-)#&xMJlptS2cEhy1Nwy0e zC5EW+P}-m9CT138r%fADN@_Nzn3l4R@}=g^S^e?;lf=oY7IRs(j80%Bv!wzL9hQzCa=yu1*8wgyU|H98RB9AEz=nsqq z{(HuG&L5TG1AM$h9x_cf%iv(1B&24ZhTxOm8q`7dnd|5rHo$;nWmxi9KnOl^(EnM4Y#T()@oCep~x4<;~Ejw+2sqoZ@@u_9NI2>|RP4D383&+o$ z89OnQ85}#2IW_VYBGa5saWi6G%Z@8MCrZlrjIhp2g-tr1zuNSeR<4w0dM@Nk#e}RR zsAnNjC??9qQW5D{xtPFhVzcP(Pav{(!~synL474#R`%{rv4~VCTuu}@pUu$1br~B{ z%(~rLE>58wE@ot84}qN+@&LaC52dLkYT4@4S$m1) z0D%Dl2de~+U`Pd!2R9~DESol^a4nCD1(O|D^o>j**7X5v790>O5jZbDgpZP)0mm|iE^=!A(f#pj@~AH zG@Xrq^Wn_B8z0_S>a`6%jUWA!bI3U9 zOje(JW6R!T&6`~I_SL+7H%~sdd6yz*YPK`aT;ZjbW1r1@rY=R!KXqML;x9C~E{oW( zf=C=g`?lwu+w0xC>z#coHZB=mvDgB^h6ltte|@p$-`$YJR^N)n>2x(%w_m)?H#&en zdiU_1!^={tCZ+1W__A+HwdUJW54F?-Z4Ddncdr7Dx;j88z8u_I3vRvtPA!;P4))f9 zy|?Ypg3-GZcP8$)JPq!+ZU4Kw`~SHXqOV*FZ$Nx6e7klLR)Hh%PrzBkXReld7_X!6 zchV~b5gcZbpHCXKP@A5vpv$qCqBOF{1;Bc5*5cgsJm^m64j)KJy zgO$aoFap`dWMp`NT^z7r{sgkvp6WDhnT%Y_WiqtJXW}*hXr2 z`4NT&3n5%#c*w2#0d)KYc<|5AB3=D|1^7@UcnL1;J=Oq}oVvP7Nn?8ox|VtmH2`k^ j$kJwMY;u*Tjf=)tk)HS-C{hR95(lD=O|Fp^ksIZ|8F+y5 delta 1273 zcmZ8gO>7%Q7@gT&d)NPtca!+1wwENd-bSRfN@TT46QU$kh)^nV39AIl@=mCgSv$Dym>DM$Vx)O~-}}D# zJo9Gu-cujV#_z{sVZg@SEBEU^=dQ)m(g0j5_A7xiW{&45HPObpsU)d-~p_; zAv@%T?XZX?9I>OUCWvEh+>Q%P#R)fQCk5B=h?}xgu5RnDVH-jV;Zb+Y9uqu_$KAA@ zb~AQH#E}i0b#r!(1yPi8Vo2XoocI;RJ|yA<$_p*%B$-wa+6Xe716^}cOfUYgR%e=d zQL>EoJp58>|05^lV3Hl3jM1c;g=spiz7v>YLbiQN9fNR!cC`#lwg1pQhjXbqxD@;p zUWG4!GI;M}HqT4~-(=NdOaW4OL-i*QVkhAw{l{q{G7d-Rkw{V2TOPfdnx}6^PC<)) z8yOE#Wx#dP#E6(&v1loJGWh!d0`NM$8C{1N8jMYC&hT88 zLykdDt(|YyYsl-VF7mwU%lnJ@&Gtg8uhr0wUvD;u%wyT}Nl!o9^q+BeFmh4DN6soC zO=4+8*fBvb#nudd6msYQo@B&J(+9EB%%^PpdgjMO`Lh=pbhUDS8t5k;_O-YXel(b( zf5qpXR^e^9oKe0`tf=t2$db042X}P1oKx-?O9~4*X^CYzImmfFw4%tJ!t^F z0<A?igXp`F*~_hYR=8F*w_1%_z0+=(=+!E2Ree;sQ1{PQYPiZO&1&a%!)!E7 zN47%dQ&Y1|I)7K6SFA^cO)pt5HG7^OFgN9es`);tPdg58;g lKH%gBdHCBsx4kEX{`v>z^knp)h7(>Rgmc>iK?r8;{{?oYF8BZd diff --git a/app/services/image/__pycache__/prompt_builder.cpython-312.pyc b/app/services/image/__pycache__/prompt_builder.cpython-312.pyc index 96cb7aa48576f49ca20348cbcfe9a17361dee6497e05602f30e0932433b20895..e2f87e0ff783cc5af7771926342b1e627cb3d104973a527c67ce73549159b8c0 100644 GIT binary patch delta 99 zcmbOuJx!YLG%qg~0}!xGIi2~HXCvPoMlsRglEl1}#G(|>+{E-$$GpUx%Hqu8&1_8d yTmrV&HT*AX_+Qotybu+AMK)&gRGzm|!5>(37=ql|QLNn&0~Vo{1`Zen_>V_srTWpQS4P-<~$ mPRS;wdM*Ly>l&dKH9{|IgkMNVyds-4xu54PW8~yoUP%ClXdSKq diff --git a/app/services/image/evaluations_adapters.py b/app/services/image/evaluations_adapters.py index 0ddc153..8abdcbf 100644 --- a/app/services/image/evaluations_adapters.py +++ b/app/services/image/evaluations_adapters.py @@ -9,6 +9,8 @@ Propósito: """ import json +import mimetypes +import mimetypes import tempfile import os from fastapi import HTTPException @@ -21,6 +23,7 @@ from app.schemas.image_standard import ImageRequestFile, StandardImageAnalysisRe 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 # 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: @@ -33,7 +36,7 @@ async def evaluate_image_with_provider(image_request: ImageRequestFile) -> Stand rubric_dict = json.loads(content) rubric = json_to_rubric(rubric_dict) prompt = build_image_evaluation_prompt(rubric) - + match provider: case "openai": return await evaluate_with_openai(image_request, prompt) @@ -74,20 +77,19 @@ async def evaluate_with_openai(image_request: ImageRequestFile, prompt: str) -> ) resultado = json.loads(response.choices[0].message.content) - return StandardImageAnalysisResult(**resultado) + return StandardImageAnalysisResult( + status="success", + original_filename=image_request.file.filename, + provider_used="OpenAI", + model_used=image_request.model, + **resultado + ) except Exception as e: # Capturamos cualquier error de OpenAI o de lectura de archivos raise HTTPException( status_code=500, detail=f"Error evaluando la imagen: {str(e)}" ) - # 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_clarifai(image_request: ImageRequestFile, rubric: ImageEvaluationRubric, prompt: str) -> StandardImageAnalysisResult: """ @@ -150,13 +152,54 @@ async def evaluate_with_clarifai(image_request: ImageRequestFile, rubric: ImageE async def evaluate_with_claude(image_request: ImageRequestFile, prompt: str) -> StandardImageAnalysisResult: """ Función de adaptador para evaluar imágenes usando Claude. - (Plantilla para futuras implementaciones) """ + client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY")) + + image_bytes = await image_request.file.read() + base64_image = encode_image_from_bytes(image_bytes) + + media_type = image_request.file.content_type + + if media_type not in ["image/jpeg", "image/png", "image/gif", "image/webp"]: + raise ValueError(f"Tipo de imagen no soportado por Anthropic: {media_type}") + + try: + messages = [ + { + "role": "user", + "content": [ + { + "type": "image", + "source": { + "type": "base64", + "media_type": media_type, + "data": base64_image + }, + }, + {"type": "text", "text": prompt} + ], + } + ] + + response = client.messages.create( + model=image_request.model, + max_tokens=1024, + messages=messages, + ) + + json_string = response.content[0].text + parsed_data = json.loads(json_string) + + return StandardImageAnalysisResult( + status="success", + original_filename=image_request.file.filename, + provider_used="Claude", + model_used=image_request.model, + **parsed_data + ) - # PASOS A SEGUIR PARA IMPLEMENTAR LA LÓGICA DE EVALUACIÓN CON CLAUDE: - # 1. Validar la imagen de entrada (tamaño, formato, etc.) - # 2. Configurar el cliente de Claude con la clave API - # 3. Llamar a la API de Claude para evaluar la imagen - # 4. Convertir la respuesta de Claude 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_claude aún no está implementada.") + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"Error evaluando la imagen: {str(e)}" + ) \ No newline at end of file diff --git a/app/services/image/prompt_builder.py b/app/services/image/prompt_builder.py index 547785b..cddac68 100644 --- a/app/services/image/prompt_builder.py +++ b/app/services/image/prompt_builder.py @@ -8,7 +8,7 @@ procesamiento y análisis. import json -from app.schemas.image_standard import StandardImageAnalysisResult, ImageEvaluationRubric +from app.schemas.image_standard import StandardImageAnalysis, ImageEvaluationRubric def build_image_evaluation_prompt(rubric: ImageEvaluationRubric) -> str: """ @@ -20,7 +20,7 @@ def build_image_evaluation_prompt(rubric: ImageEvaluationRubric) -> str: rubric_json = rubric.model_dump_json(exclude_none=True, indent=2) # 2. Extraemos el esquema dinámico de salida basado en Pydantic - expected_output_schema = json.dumps(StandardImageAnalysisResult.model_json_schema(), indent=2) + expected_output_schema = json.dumps(StandardImageAnalysis.model_json_schema(), indent=2) # 3. Obtenemos el path de especialización # Si por alguna razón viene vacío, le damos un rol genérico por defecto diff --git a/requirements.txt b/requirements.txt index ecc246d..e220608 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,4 +25,5 @@ numpy openai langchain langchain-openai -assemblyai \ No newline at end of file +assemblyai +anthropic \ No newline at end of file