From 11529b44b29b481fdffb1a1851c3f37d94ade6ab Mon Sep 17 00:00:00 2001 From: Sven Alaric Brandt Date: Wed, 22 Oct 2025 18:06:55 +0000 Subject: [PATCH 1/2] feat: Add endpoint to retrieve report processing status --- backend/__pycache__/__init__.cpython-313.pyc | Bin 169 -> 169 bytes backend/app/api/v1/routes.py | 11 +-- .../__pycache__/orchestrator.cpython-313.pyc | Bin 3685 -> 4297 bytes backend/app/core/orchestrator.py | 70 +++++++++++++----- .../report_processor.cpython-313.pyc | Bin 0 -> 3335 bytes .../report_service.cpython-313.pyc | Bin 1420 -> 1644 bytes backend/app/services/report_service.py | 3 + ..._orchestrator.cpython-313-pytest-8.4.2.pyc | Bin 7197 -> 7197 bytes ...ort_processor.cpython-313-pytest-8.4.2.pyc | Bin 0 -> 12353 bytes 9 files changed, 62 insertions(+), 22 deletions(-) create mode 100644 backend/app/services/__pycache__/report_processor.cpython-313.pyc create mode 100644 backend/tests/__pycache__/test_report_processor.cpython-313-pytest-8.4.2.pyc diff --git a/backend/__pycache__/__init__.cpython-313.pyc b/backend/__pycache__/__init__.cpython-313.pyc index e11fd64482f1d30c90491e9d03490dbc593135be..0a56e1a7b48db6cfa2ee1cf5efafe67a6fdd558c 100644 GIT binary patch delta 18 YcmZ3Hq)$ delta 18 YcmZ3-WtCx=on23|o^j5Z zB{7jInn;zSsEVrwHGOGNrAp+DhgPC$A55O;Q+F|nt!b)8YF~I;*GMU9ANv2Zv%V2U zJ=#5&|J?rbpYMGCKP$;(j6nM2ohOr@LhT>e=p-?ytb7WUn?xllcb3q0$x#m4p|hbg zJmt>_R5&A2@r*C zTbnYc96F;>bK0RUGlEt8A`&!Ku0iD{DG@vy4XGUE)esd_K0~Oe3MEMuOW_eg1-;%9 zXasbV)Cl~erD#bi#YRGCun|d(`TcPEHotd&x(;1U14wVhZp=Ha~UCXY@Q}vo_P1j60cUsn~rtPw9Y}{m4)i%qr z>&TP3U54Iioq}=9nyj-K+nA(|?bKO&f^0w!%rb)8@GXJ2fHq&jtGh{tNs-k24$P`V z;qqT_p48qDtSd0HR$;h+>=lP5ohfq&t;{Sqg=Mm#*XmQ2?V7cmt=DE;%V0wSYtGe;FX_tre>FFWEq2`dY&Jfw@884z~mTkG3=Jjl}Qnu5Pg6&xpvKwUS z;Ne-}XYmzi5Kpl6DbtJj%4(ImZ5RQXu-8AOe^MD5H_0WrfYME=$n6ISpe=O$cC27l z0aGRg``N2zm*2JAN!gt=d-DIy%Y@&qxrME z-J5K?vAY}Rry;vRS}C%7|0nT};&bB+D|KxTl*VD}>VF1r`j-xN&1)Or0|$wmI6!1!UpLR-nHBq4P`?exf{k-g;7mJW|- zp>uH1IP77IHg5~V#FkXf4j0AmU;9)~u-=J2QjDAiMwO8Y*I6>tQC3I}+c$ZN6t3b=Pc9Vo$+(V|NCYV84g#5mLv5`j_|r z{Qly^mFC2ihZ9#Hri}*^SDVkDy*o0uZSMLlWufQzgYM(aiL0|>Gi@ww*)|(q+9@|8 zz29W|KR@`ZgTFrf(V5xtR!B+?F7*$5o|%)D4j!MEn)0i2;a?<|!(``RD^1cpv#BR5 zJ4kn#TTYP7zJLF7>#h~TCkMYyZ(mHy&9vOuGd^E#ym9Ga`iDygkN(M+-}A>xQ+|0a z{PX0}!2Yk&@_$-k82?{(7Bv5$Z|n$nYg=wm`rR(5-Rl-}!=ZcIxm;Gd*AvJ3-UQ0C z9M*?pV_EUus4}LAUk*qh|56b_@@1BXIz>Pmfg6S@A*;>+;{*r-xFR>q?IS>jzm4Up zRp1ESHJQ&n!NtiH>P-29z|!Saq6AN~#)+NbgJp2oz;ZbmD*zsy&d9)xo&Kkn^XUrK zD){UYP%P4_-chI=%+AKVq(2%QzmKXfK=U>P@uUn-*P+Ai)!CiF9w@%vCug4;q2MA& zra+2m(k=)#h~jgi&%t>mh3*RpAKX_=)25tqy@vIKroB_wYe7$!rd2G;+?r*Z zwgcT^O)EQwrcvZv`YdKhh;%z+ z|8-EL96!VOf|VirDOnEjV&q>jPRzEXu(~i}}~@ z58P+>2k*W47^SVgh!|3V0agt7*;eJmoQX@Njr}n^KYQ@VPC3UlK3l zUVc(T8B*j6;ZA$4T=RgPT4#;HV2}==R(V@2a+f>yCC@{jE)%jQDRQ`3Vb!-lBS=ND zD5wJVs$w?OSzRVYVIR4SVTGRxi26-Qm5N-)_qNA_54_=fpm3h2dVCw&Q}+ zt=nbUvf;PsYeg>CDF%3J(IwzGt3=j4eqM*~9qF20tDB4>Y&@wx4bI)91K0?^t5X}6 z0Eg{Igll+5#k{UnVk>JH5qK%*nu#9`KBLqb!;q!zW!pY>=l9so8~lH*{2Z*xegm2B zd;9h-_8n>V9l4_{^u4m!`^s!`Dbu%@8E$5V@9bX4{9v}LC6Uatv#GD+sb=cJ-NTEc z`Q~W;;pq8=)P=?Pg=YN1((deR9!4dedDyEg#FeiTyFasccLt#3-VeLU2bYK1zBVGdx_Iwh3U^=`K06OQzQ}Fo`sYqQCQvL7lk~2SfBch#fA;?Y&G;SI literal 3685 zcma)9U2GKB6~41Gv%j zbug6@N}|d%520<`lDve6K7_YCRaX0Aq*S76D(zy5x3`Xp)V}a$*GM#KAA0W0j{i!d zUhSN7?*BdKp7Wi%tKo1EK}k+O9p4u~=x?mz7NIPxUIk$mDM;bYBJ6Mub5Qr3^_<}` ze@4K<84-&z>*LRQ&-k!UMk#Kj%3W%mx1v;_n{(f#u3~*Mhb?tfv$S-15u4d+ zg0%V(kXe*MOabgsILs@a2*QHGr$j|ac~fFa?C_*~DZdCKyF4}(6fuI{7H~j;e#Ms( zy9C8=w*9NoC>Bkq0^v!R&^|IrqfJEw=mh( zSq5PiCFBveX9JIOd!d#=I0Ot!K%zXZU~(-Z+kgSrBb0}gCNIQ=5$20qN?dfKNk(!0nIq7MZ_6a8CN3mux&^0w zeT8!`AQXZ#Cz&(aWyB2}w*h!7?4)pt=OEX^orahn2MwmUS$)T~yyDP18g-`ru(^_?IpSzYK zydVHRiOB}HCmwW9rg&qX#K+91&M62fzIagaueGGg7HM?nDS-%z`$wAG`Z`~~HCJ+X zFr{RRJ1*{DYYivcLE5D%e2GkjO2}+YiCFdIX3QA8`b}QH@ZpT0zV&6pmSC6ukZ%_>tn7LbIZb&OvZ9 z-^}o~CW5^IZiexX+4{qG5FE%8J4iW97<9V9ALs+!^edCNpB~*5_xcq*owxKdY^v#; zna*PjcF0c0`m(vSmL>hMwaKlpB(Zk`WGF-~-fthcT|Iy0ZnAJbRUAxx(fL^?G<`={ zAdH!q z(C~D$8nd9HqG9P1gn9v)0ibEMiu0zUf=aY8Jv&W(8ksiJ2HaCdv&MBy1G<@?&>_nJ z?i$1};nK`BWY0m@1E@Ef%VgjVWwt~3C5`CoeLN&6%EKwBcS_6Vbwc^9ZelMCjaE9a zTMsg3fki`(29`$NA#Q~MK3Rum>MQFz*0-ds8R@(;&gAzdejm1I##_zdky6^vW3AQ|mgQq=anW3(JCS;RGa@(TZ zQHk9u+WlvC|z6;(XQi9 z{HQ_xqT=(4FGGd;p?RxVKeSjgG~;{J)Kc(AmKs`bAAZ=d|HkQ=fl@=;XU#KbmMR-= zwf`!*Sb3mWd0?rk?w8U}r9#v3`N)H}#tTO7!MpD-y!F9NX$7I`0Z*y#`XY-l`x7Ncv6Mx8;g;Ph4$XX_T$C&;}6;g79tlHc3hm1 zma1wC+u9eZ_Rff<(2j+WTx!~LW4IJr9T`d`r4#(+`S zg_1DCDL$kD$eUu4MXg<)l};5dv0d>fJWD4!BtKIe5XZPNbc}C7W1Is`!&i3>GUSvY zHci9K$aYlN@XEmSJl+Y*!<(fC7={Fo?7)k9w>)C-dR%cjVFBkjx9!%r6Y8{ill%S` z7O*koMIg*+mpwwD=FbhgZ=YUh=v}PsEl9n^+TMRIRo1T}PoQQ=s`+H-#!#W*@Z7e0 zT{A;P>9vwn{jX&a`uQq_0!TDII%sj6cJCRWxkbu*``m^}k9yhGqk z&Ir3^%#sFXIUT>uh^_i|)sB24t!kEqjmrS#1g0}bvY%>>m6&H>M%YJw0_4Z&A0AG; zz%6?@@%h__mJzV8UYSeJADVmfUPD27{r4?}V~Jwd8&6ozvR4*Eb1(|nz1>e3TRzE& zV$GK)mJzVI#5}ooV*c&INV1T+R2Vgp%1v1Jg$5S{x&^jbDd+d zgGIymd0^DXa2Onhdadb6;Ev*JcA&e<@A4R+e~jpO%7BCtD?drgbsL#!AfsDD4yX^o z%8r+@t*DgS@RZ+TF6!@duBjhifUzvEl3pOoJjZc=K^5@(D>_g_2fjrwJ_=MV1|s(Z fkz2hBfwpVjr$UUYSwW0k;!xGjkKg~1WNY{zbNX^8 diff --git a/backend/app/core/orchestrator.py b/backend/app/core/orchestrator.py index 13365fe..fd683be 100644 --- a/backend/app/core/orchestrator.py +++ b/backend/app/core/orchestrator.py @@ -1,4 +1,9 @@ import asyncio +import logging +from typing import Callable, Dict, Any, List +from backend.app.services.report_service import in_memory_reports + +logger = logging.getLogger(__name__) class AIOrchestrator: """ @@ -7,33 +12,64 @@ class AIOrchestrator: """ def __init__(self): - self.agents = [] + self.agents: Dict[str, Callable] = {} - def register_agent(self, agent): + def register_agent(self, name: str, agent_func: Callable): """ Registers an AI agent with the orchestrator. Args: - agent: An instance of an AI agent. + name (str): The name of the agent. + agent_func (Callable): The asynchronous function representing the agent. """ - raise NotImplementedError + self.agents[name] = agent_func - async def execute_agents(self, *args, **kwargs): - """ - Executes all registered AI agents in parallel asynchronously. - Args: - *args: Variable length argument list for agent execution. - **kwargs: Arbitrary keyword arguments for agent execution. - Returns: - A list of results from each agent. - """ - raise NotImplementedError + async def execute_agents(self, report_id: str, token_id: str) -> Dict[str, Any]: + tasks = {name: agent_func(report_id, token_id) for name, agent_func in self.agents.items()} + results = {} + has_failed_agent = False + + for name, task in tasks.items(): + try: + result = await task + results[name] = {"status": "completed", "data": result} + except Exception as e: + logger.exception("Agent %s failed for report %s: %s", name, report_id, e) + results[name] = {"status": "failed", "error": str(e)} + has_failed_agent = True + return results - def aggregate_results(self, results): + def aggregate_results(self, results: Dict[str, Any]) -> Dict[str, Any]: """ Aggregates the results from the executed AI agents. Args: - results (list): A list of results from the executed agents. + results (dict): A dictionary of results from the executed agents. Returns: The aggregated result. """ - raise NotImplementedError \ No newline at end of file + return {"agent_results": results} + +class Orchestrator(AIOrchestrator): + """ + Concrete implementation of AIOrchestrator. + """ + async def execute_agents_concurrently(self, report_id: str, token_id: str) -> Dict[str, Any]: + agent_results = await self.execute_agents(report_id, token_id) + aggregated_data = self.aggregate_results(agent_results) + + # Determine overall status + overall_status = "completed" + if any(result["status"] == "failed" for result in agent_results.values()): + overall_status = "partial_success" + + # Update in_memory_reports + if report_id in in_memory_reports: + in_memory_reports[report_id].update({ + "status": overall_status, + "agent_results": aggregated_data["agent_results"] + }) + else: + logger.warning("Report ID %s not found in in_memory_reports during orchestration.", report_id) + + return aggregated_data + +orchestrator = Orchestrator() \ No newline at end of file diff --git a/backend/app/services/__pycache__/report_processor.cpython-313.pyc b/backend/app/services/__pycache__/report_processor.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..56b9ee3e7e8514e08e23b41f02625caac26ab51c GIT binary patch literal 3335 zcmb_eUrZdw8K1em-P^stz~LBU413rIoNM1DHPkM)(;^@UnOqV!w5}jQ>$|yQ&E4&F z_RbARm0ahcYScDO+bDj>15%{QmHJ?{4~?ok6l0)|cZM3ewbV*|@*5S7P@FvEo88+x z+i{~1b+j|{?acS*_kA<-&FlsOUIb<6ho8*-LqzC5?8hlWjo7^g#JfmA3ci4--D8Sp z@TA*eZS>t}h8w{4KXpHgC_jTH{f&8t>P7=Kl6n-^jHqx>-7`|Zpz!VJvYUF@XunGl ztUq5C$2|9$MADQfDeAN|tEEk`q|cKAqXbGyWm-tN90SmUL+DMYy9j_DMKU-sU7yBi z94mN^L+A>AmrL?~n4dz^b@{WvsfG}m<{c5mHHCsm;ZD^ZBv^4zVTF&o8gf&p7X%d0 zt>A&0wsF^!mZqKHqDjfo-{=+LA|3!_tjMSnO^`!1CPOL zyFBU@3&V7iGvjy$>{d}>i52llent8}#KJp;9BVSl`b3nx3-yBtI)SXX!rIy^C?Y>{ z@T6f2W?VArg80cO`Qdr43uQ3Ky#@8hP|xp6e$tkYJh~DsPAnR|2dN5Mu`k5@F^o^y zXDoXDuN=K6v=Ou)UkI{&nQnD(1M16P8+G98qJF7;Kd4{gYN#9M=AeVW(JR46_X^vY1a zr{>CJlv14z%Hx?D07zxB2GDYpsObgy5&_)~5|WNvf}?MwNRdGtl!ponGN30kx4;;Q zGXokZ0hK2yGnkguOo>oAuhMzOW_u9#PFg9m2Io~;CXazlE_x=dL@O^I{t`^4w-JSy z%M@(o6!s`ujJ#~ekQuE7IBLilokEZxZ_82RK1(a6RF4;BIeW3SW*C_3Gx1k}O)aEI zE=M5UXH*UP#pVX5Jra-k%@*6%tPN#0r>Ex4z++>kw-J3)$m!WEp{83aWOUP`8ViM# zrklKxBcy10HA5?aCa`R}(^{%z28SIO*6Nv3eL+mAz5+29WeMDhgTbx=SvFrw%;|ZO zU`PzDq*IL;iHUMvE0joXsG#N+N?OWD49}@r!FKE9(97cqwn<4LoluL#ghA*P4fc|y z1FpbY=ye(|E||gE->mH_Z3nACsKy(x@9(3lXs-=5b>9s|wtcPd2R3}sdo3N;f3&o) zG+%jcqH^TLjh1A^pWKcQ{!OeNedXtYUujiQ-t&OWRw(koi+JBpFZ|>}rRmhoA8fkE zcihc<@Lur1+xfTh*M~NOom*mOMeO`nIC?i6-43Wjb?&j;;E$M*lQR0&6Sz*rC*vn>C;_W$sn zuXPvkU(|B`X?SfA(1N;$7Wgf(ttz%v4xPB6ZHi~M#lVgalznELgy3KEjXjTlfrpO? zzvO^=r)Rj2`!gF`cH!Y3_cGVc=*~kU-DvqV9&x#s&jf+KCEyXaaLdCn+Iwn5;&}Ku|4PEo=(8RezwN@ndE0HZpc80O80qrd z4&sq!|Lu^U(T6>3yt{MkEVqjAC?~FBH>3H3qj7$?;tSiU0c$6hsIOtd4zJc4JmU?A91GkTTOyk~UjH3a-NZDaExhyHgu$4Rv773Xi zV#CcaTszpcyhe|}ct2Dl0gZLOMf-f;3w3OTI3YseQI;RShetZ&wR{!N z<@IzqN1mrWFvfylOhL25VT}KYg7E!YYTA<8??~;}&umD?t_nLr)EvGV*gnwl-psFO pe)D<-wd}Z$;o#En4ubCHDXaUi{{j2knZP_gv1ELVprcXH{5f=U*#ZCn literal 0 HcmV?d00001 diff --git a/backend/app/services/__pycache__/report_service.cpython-313.pyc b/backend/app/services/__pycache__/report_service.cpython-313.pyc index 82433c12b2318d5d3a4bd41dd15b74d9464ed9eb..2f175bc6f7eb575c5ca310c10d7d86384d01b698 100644 GIT binary patch delta 338 zcmeC-e#4{wnU|M~0SE#Le`Zv$Ffcp@abSQI%J{r)qWW~1V9pqxVD=dHU`Abb1_l$B zU>+%kES4gHbVg0yjfbW)*C#W=Gy-X6DE_PiWK3m$Q wiYpmDgJg>YfW$2ho80`A(wtPgA|)V~5r~VECckEl=l;sT%IM2jqzDuN09vmYe*gdg diff --git a/backend/app/services/report_service.py b/backend/app/services/report_service.py index ba6617e..72feffc 100644 --- a/backend/app/services/report_service.py +++ b/backend/app/services/report_service.py @@ -25,3 +25,6 @@ async def save_report_data(report_id: str, data: Dict): else: # Handle case where report_id does not exist, or log a warning logger.warning("Report ID %s not found for saving data.", report_id) + +def get_report_status_from_memory(report_id: str) -> Dict | None: + return in_memory_reports.get(report_id) diff --git a/backend/tests/__pycache__/test_orchestrator.cpython-313-pytest-8.4.2.pyc b/backend/tests/__pycache__/test_orchestrator.cpython-313-pytest-8.4.2.pyc index d8e1144b94c85f92839e238f722a6794dda49bfa..0ecf0650207cb16eaf0c2cc3c40d93a64ef9e9d1 100644 GIT binary patch delta 19 ZcmbPhG1r3YGcPX}0}!NN-pC~^0{}MH1n>X= delta 19 ZcmbPhG1r3YGcPX}0}!NDZsZb{0RS}*1eE{) diff --git a/backend/tests/__pycache__/test_report_processor.cpython-313-pytest-8.4.2.pyc b/backend/tests/__pycache__/test_report_processor.cpython-313-pytest-8.4.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b64968ce46cb1a7d8e517049ef5f914d2618431 GIT binary patch literal 12353 zcmeHNTWlQHd7iT~J2QJHxgup=L@h;$(#jSo-gKvUH+3^xaI^;PO}br^OKPFzE*sL&vNDPwk(T5Cn&-~|{|37o)%sJnGIp?9z=VsvQfBVZ*U4Dl756l=BrxOq73Bz1r zBt{~~8JW<#;~1+rLd-Evc80VXUgl|yAPaDJ92bwdWY;mb>^|m^J;%JVH^h8{AFDGO zx9(dQiIuqhL~_d$}{r6_}I%S5KT~L}qjX{(Vi%D-7)s zyo2^`BI9Lt=x;I!V=)CQW;48Rh#8Iau$!4F0&+*-zimCT(~Ks&mz^SEG8$%OCp<&O zkyD)$sYG19#yrHQs?oMnnVCczeoZBr&B)1w(sp8QCYjDAQvK<8>RdKCp|lO2iYL=! zbRz8P|Kp==Z^b80C(@H`*@Ti+Xli_Seb!{;*4cAvkUq+&(q@kAgU4cTly5-td*(dz zyFk-#15NkD>SeKhNvyvuZhFKr6*wMdR`Qve7+KE=Y#yfPGP>53^aJ>>%2&}2vA-$@kQtp z_Uqx#;bUkMUm|G$!!(J)=hv=rVoqO}%Jzkjc1UcdBf_3wGDGIH`3bY;@{qU)*9Gu7 z-rVKr#%TbT#7B9JVwf@PSIK29s)JB7Szq)NsuGF?wlEP#xBd=B-{LhzQr=<-%-N;> zh(MpLgYpcR2v+nR@-e}9XCjfYQ$n0RW z%HS&tMjMRU2%XB38z*#HkUW`75%ItghRJjqBQTjM8UcOm4SBqSa`Q%5_&UDvv)0$K z^}YN!2u7dRj+OsdA;ex5OrD!=x*O^i1-zU(v2ZBq*7cBK@2VKY_%gX*bj50%h}V{{9ogeoJ? zb1Re$qpA;)-vIV@)l0M43@C+I`^jrWbtIK*wtf;)4Q07wE zYYvr#I!rnr`{Rm|kh7Gh8+f3wTjh02!!?nanN20KiOFl^v7@KQXty2O?dhR1U13FQ zuN_6ZirU+wwYNtVweBUM8LeZs2B2ft9C8RItGp4Cut#R$rn>uP&(SxF%W7q8BA!a& zE6doSxSUYEP#t?~E}6hAeRCRz;xU<2M!q+pmg?f%d%EcRfmz#X#S(sQ;G4zI>ov%igxp z1>`olOahAU8I8IZ1D$!X_xjnzK<~1s|CYqwe4tay-nP*u0~B6AYvvYsHa^D{$K;GC$jvU}`yu>11KkPZu%YNG8 zfy}4v|eyOc5kRx@m=r? zUPmA|lv0Tg@uulRY+0SF$XgIfz`dgg+&g~v*FVJTm%Z-_nm-VFw;tg=o{|&s$#bE% z63O&*NJj;PY}E;{HgPKK0!L6em!3#wRL=zPW)rb&Tsf@)ty4)Q60<6=!7b>Bcv49y zs{8eLYA&H6BtH>PPb5;Re~{i&iAh=xu1y;ZD+NWB!xj~UZU8F{rCQ3Nl-}8x-c9JS zd<2AQ;jjD?B*lRBKRn|NE_)i6JdJ$B{pEbmcs~3_!S&`nKY&od4;TXw)DF&#J4#k~#_76r{5!`l zzIIO+duMs$uEmYJ?zmd-`l~*0Uv?vBTi(_Bz{fZ@J>VHvhbDW+_2%OTZpIa)48Z@D zG6}uD&@;G;{F)3pxg663WUeVN=wfrb$)LdJ+Bu}V4?+2SJ3rXK&hI6Ib^QE34(UT~ zD8C^P;JM*)4{j801O+UqBT##z!9BQBxY2?oMX-Pikix=USo;58U;!4X3;I=Z89^gh z`X*S6nE+ui17Mk%a)G4vF4CyE(98M}rkA1n5gg)Dnw6ZH!3alf=7_nl(T_0g>oM@F zN-yvu_=pqy2w}X-W;0tdFO^DA@5_W(TkGXHIpggmQMY4x$tAfXyzWN`QScG9%hpzd zQD)u2^D?~`qfOn9V8M?#Sq!{ShHxnKJp$lmKF95g>NT zhv2&e-gCQWpuKGHSHNZ$3O9CrO!%@)&oKJ*|{tRCALeKC{ zz|o<-fTR0>%*au+XRDxmzM2es`S}_a>DmF`a0R>2;(^RUI~n$K z3mrVt`w0jv9CQ!6gio9zlzieRm{#z^+l5bR$Z)OqlZ{@an*}JJfDV|K39y&f39uL@ zsskX2uoj%9z;@HFs2PMPZ*^IAjKoP!-C{x?fL@jzx3-O9*)j?u?;QgUW41=iuDVML-mnLsBQ z_7YsfXD1S~=p2U~Di@ch!R4O547cCszAbSt~4}S|&VKv*j9H>fc zgh*Vg!-@(=7&IW(0u{?8jxYu&l>$7f6|HsLgP;IiybEDE@#XguBq!a4;EQ%4gtaVk z!*)LiZ$Wn*4R&7b^x!-!iBFmbg9)Dq96W-zh3v zv~o}Z;w9XW1(y54t@&*S3c-WRzJo6;dJPfqM*(vy`wr}Ef59{U-4FrtANo>>!#G_C zjD*QYWQ67ak_GCDZ^Xs^od>9&k`bQ!hbo|Q3>n$VWsx^4_fQBHe(jH@9PLkuEabkZ;2jDb__23p#;I+uV%j2m^DLld`6Cv#~R7 z;9=TnlQuCA#z?=mFyq69OsPJz2a(I^QcE-G4vLdHD7?gH&O`*N|1KLkhVJv45v6jm zBfwlCA_C@$#5~+(rWv>JUA$q zY~$j<5t05dE~Vq5oeS{d=wzn!{cmfo`~+fo))y#i#prXzHy(eUxgzt zRb}<3>;b%p>8u$uo?2CwRQcI!N`Y17mje2{^hgy_WyGU9&fe&=&r7ed?v1;CV&l{k zbJ7@l%}#y3v7dOZv9J2t$KJFy*ERO06pVeP>vK(ESA$Frv(kb#i!^ti5EYj^@hlfsKFKi_p@-&t>ar3KPyLGJU_uH&9 z+@Cii07z&yS`)R=Y{NO3oWfHJrunGIGa!k(&&q!r=33SE^T+R2ZaaVMu1Vd8O*8N2 zCGX}R`>%eh5E?0XkDMRASFZ3r?4-56-}-TN!QHaVwdA=LYx6$37zkh8LHF4Qu74{p zLb4bbSQho)k~ly&+cA6FMi-FLHYjo#g0D?k;zo=Ub3@(fMZ%G`?2fDTF zZ5wSeK;bXy%-mv8&MYp7gJ$1f>|-9e%#isPPR6+%z6PAIgdaV2facbP{?LgYlG{Q~ z)N{FJKho{wL?fT;V3F=VR(+zGz2){m=2kU1(Zt=V;gN16m^RV0nLlwrxD_TRUh&@A z=|y^v0JY&7RW#1j(1oDZ#3UTFN7TO^wvIGm*MA Date: Wed, 22 Oct 2025 18:21:37 +0000 Subject: [PATCH 2/2] Fix API route and improve orchestrator concurrency and logging --- backend/app/api/v1/routes.py | 2 +- backend/app/core/orchestrator.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/app/api/v1/routes.py b/backend/app/api/v1/routes.py index 5afc260..b7b204c 100644 --- a/backend/app/api/v1/routes.py +++ b/backend/app/api/v1/routes.py @@ -43,7 +43,7 @@ def _on_done(t: asyncio.Task): task.add_done_callback(_on_done) return report_response -@router.get("/report/{report_id}/status") +@router.get("/reports/{report_id}/status") async def get_report_status(report_id: str): report = get_report_status_from_memory(report_id) if not report: diff --git a/backend/app/core/orchestrator.py b/backend/app/core/orchestrator.py index fd683be..e9f650f 100644 --- a/backend/app/core/orchestrator.py +++ b/backend/app/core/orchestrator.py @@ -24,18 +24,19 @@ def register_agent(self, name: str, agent_func: Callable): self.agents[name] = agent_func async def execute_agents(self, report_id: str, token_id: str) -> Dict[str, Any]: - tasks = {name: agent_func(report_id, token_id) for name, agent_func in self.agents.items()} + tasks = {name: asyncio.create_task(agent_func(report_id, token_id)) for name, agent_func in self.agents.items()} results = {} - has_failed_agent = False for name, task in tasks.items(): try: - result = await task + result = await asyncio.wait_for(task, timeout=10) # Added timeout results[name] = {"status": "completed", "data": result} + except asyncio.TimeoutError: # Handle timeout specifically + logger.exception("Agent %s timed out for report %s", name, report_id) + results[name] = {"status": "failed", "error": "Agent timed out"} except Exception as e: - logger.exception("Agent %s failed for report %s: %s", name, report_id, e) + logger.exception("Agent %s failed for report %s", name, report_id) results[name] = {"status": "failed", "error": str(e)} - has_failed_agent = True return results def aggregate_results(self, results: Dict[str, Any]) -> Dict[str, Any]: