From 7dfec6f5decf0a188034ab5b0a1f88b00f4bb09c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 07:30:09 +0000 Subject: [PATCH 1/4] Initial plan From 0c4332f704a03994a24d76efe835115c831a6755 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 07:32:42 +0000 Subject: [PATCH 2/4] Initial analysis of repository issues Co-authored-by: amitdevx <110670491+amitdevx@users.noreply.github.com> --- google/__pycache__/__init__.cpython-312.pyc | Bin 261 -> 286 bytes .../adk/__pycache__/__init__.cpython-312.pyc | Bin 150 -> 175 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 195 -> 220 bytes .../agents/__pycache__/agent.cpython-312.pyc | Bin 10327 -> 11958 bytes .../callback_context.cpython-312.pyc | Bin 1515 -> 1540 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 198 -> 223 bytes .../__pycache__/runner.cpython-312.pyc | Bin 6415 -> 6440 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 234 -> 259 bytes .../in_memory_session_service.cpython-312.pyc | Bin 8677 -> 8702 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 209 -> 234 bytes .../__pycache__/function_tool.cpython-312.pyc | Bin 5070 -> 5095 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 239 -> 264 bytes .../__pycache__/agent.cpython-312.pyc | Bin 1963 -> 1988 bytes .../__pycache__/agent_utils.cpython-312.pyc | Bin 480 -> 505 bytes .../__pycache__/config.cpython-312.pyc | Bin 1254 -> 1287 bytes .../__pycache__/observability.cpython-312.pyc | Bin 14033 -> 14087 bytes .../__pycache__/tools.cpython-312.pyc | Bin 10304 -> 10329 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 311 -> 336 bytes .../__pycache__/strategist.cpython-312.pyc | Bin 584 -> 609 bytes .../__pycache__/taxonomist.cpython-312.pyc | Bin 741 -> 766 bytes .../__pycache__/trend_spotter.cpython-312.pyc | Bin 678 -> 703 bytes 21 files changed, 0 insertions(+), 0 deletions(-) diff --git a/google/__pycache__/__init__.cpython-312.pyc b/google/__pycache__/__init__.cpython-312.pyc index 7ec3593cb7c9d4f1f02e1bcbe37d22cb66818726..1607efafa9e5785d35faf51e3922e0ef8eecf70a 100644 GIT binary patch delta 60 zcmZo=n#aU_nwOW00SIQUubs%o?n!$A5fH^mRek#Ulb2w MW#*(7Ol$*$H%wwuwo?nz*T#%TYT0Aj8WMa7m0Kp0iKmY&$ diff --git a/google/adk/agents/__pycache__/__init__.cpython-312.pyc b/google/adk/agents/__pycache__/__init__.cpython-312.pyc index 5dd09dc8083a485bbf6644b38d27c15155fbcf21..c35a53ea141117b4fd3a95b0dd2607db55014dd1 100644 GIT binary patch delta 60 zcmX@ic!!buG%qg~0}#wyUptXIOf^(LBR@A)zo;}XFSSU&JijPgKcFZ-Ew#8fzbGEW M%FIbEnwT2~08jQ6NB{r; delta 35 pcmcb^c$ksX`cGfR%4UMZu-)5 z?yx^rJP+sIxqs)}@0|0UyEgv2v;L2KKCb}Z^4k|>bNefPJuA6HK^RI=)gaO&Gju6o z$eK!KWg|_F(rH6ENeMkJzBlSz{>=^$VpJC$Wt^imjKJsns%O% zDNQG^&4_38IxRsNLj~{TE{a)+02*LnJfqE0ohAuC%s~U~0tMVkBB@EDWl%3zQPN5@ zFpMIKn?MoxhSW_!behpAL*oD})CmUZRAQP6{Q(L9Wleq)QT{2LKLjDi zQXrla?6A-pT6ABkE3}58@!Hiwq<7K%X>D7feH(9KWJ~+2PcKd4bRiu3B>eEI3;K;P z(cXidLToRt?1F)<9oC=S&+eQbI8CJ_555x0+y~s(Njhn!QUEe{o1&#yzqQBn-G&2b zRwp-W-CFis&AvTk7*Zl#hB=|gm>GbTfg$fi1Nt7FQF1$jlt3rp=cEQT(pr*Co6I1S zl!FJcG)W=wC^-j!ZSOmWhfyE zF7Q;S7(Y`?ZD1T8$B=s!(>!uZ`#9wOa=@asxsU6vi-F?^)}w%n16I^psV5!?q_$4$ zPxS|4?yfrjdPmnj18>RdiIRJq#CE(oG$Y9hPbN+z6`RXWkY*uDfcMM$tSgQG2>cY`{Wk~*#Acx> zn7bVCiLQHs^+wb8#ADXoCemQk!ZiP;T-KiEvFxD*SJJgm^}3L(T5yA`e#0~G6NHPd zODEjIg1F$Cht+3Em4%vaVjVkde9kM^E%pr+POOOf(4}R8%%`x$7>p#tlfj7Z6b{Pa**ee8D ztQUd%GMJAH-Vv&rPKhrc`Fv}1$yj;j*4A%di>>V&y}566y{B)j=Rm&a zz_p3BgD3L`Pu}bqU+WzIq;1@;!98|^wIjNJxI<`}5br_GkW=Eh})~`QhgSpE%O^^It|?*~?`oaFAvGIDq0a#~B9L zwF2)k$-wstpDD$kDosO*X*?h9dr@GSC}E{*$Rr zW>B0!F^Xc=+R@c1n$~F7zf1VK2j%Oa@93t+@J=Sm#R>99ASKR73{~h6q4?!urQ+ zBV5u;_VUyxKspro)7*RweDT~eBbsSsOjyc@4DTg}l`0^pi|H-MVV~7(KB;s9fBKyD zQtWUrg4bSw|Khc`Y|VWUds@sQUMvl>F9^3?HJ(~{IYpv{_KreNU!i|jA-26RxaW4T z%M)JrH`yL&q3i^Lb`ADwg)Jew4trkg)nl(g2(&G^m(DEL}(VMHVd7-D;M(7Z*8EaJKqsqp2Omu+FH*5m=oW5yXJ9^e;u@X zpauQS;icFTTNz%uu-u<->a%egOz`>_6E6=grhgpWsK=4Ru8^mDb#Oy~#@^lNiLC~0 z0h+794Q%Z0W{)?!l3q2g4qppi9r?wN^4kt=2ry`G-|smpa)sCdjUB|U;a#|LbSb$q zxLUQccUjH1#_c*j4%&Si^}IVTHhV_IYoG*MdwamseGL>tb3OPaHhMey#S5a(=hC0I z-tCnidlCM?2?FZ{VONQw2;LCg8v#LV{Hsv^cVYJ}VfWvJ_}%KWZn680fRdl{e`QrM A8vp|uG95YDoJw$hPbr%-$iFiE53G}`gM+>o7 zgr`H%L7GqOZ48lQygywhpN8ren?_>X>wG*EW_a`u73@>t()a-k$AD-B<+gG8sghi| z9h=fzMa{VUMzQsoEw{h7E45devU{)FeN!^*>vDFlWW1}%+m$)91AVGE0Joxma)-K_ z(7Pr29Qs^2UG3c*Wd$}AVUr9kRrcJz!x0u2>qm-`KEpDyv=zf2;L$OVH#^e9CBMr2 z$%c)iOgJGtw|s~_$VBNl7aNWX(#FFv+`Uo3pAKp+LA{?)HNZtGQH!iPx+rgPp>?{o zZiT%>n{!rXo&Jo|KXv%p+O5}mcFj0mG01gEo<_3B-jW( zltaA04CC|V1uhYm&Yq2iBZ)yaI<(jmE*4?@=sV+G?H*G4Jq)-7`m*!|lLpGv5S_PH zpxN^Euos!kG<`vaU%(Hq7pQ^gP=5827@0e}%+vQ*kj>(dEsu$ELllDSQ zi>;G1xYRqe-r|LGC~O(m3WWR&23&>Ch@M)SG^QK6nlF9R+kXh$@U^F#t#(*j)59_G z5XVRYq%qAtb|_I$u%yd^xc%s=?VSBMVPuvV-%Y-q%o#1B0yy1h)P4xor04B>WYqU# z#jknL0fUkHM)L0JP3VNv3D2TWob~Vxj6Qh)^mm}A&N1~7)tD+QgLq$ZKxWVvE~e?P zR&Z+DS4AVR&YJWrDd*X(`JZB>OXE+^x!;FSjkwA!#yJ&<4R8#fhzPtI*Gi+GD(m!T zN$E37YI>+mKdD>=x8@zySw~~W(U^6#WE?G68HW1Pf!h{WN|+p&wXD48&9-&Tv~}eh zmS-C}GYy@W0@=WhOkl@M!_I8Y&Xg+e_FcF7L=|2q>e1n8sKFY3~J<;-R;eIaHUL5(Cs3)4hD6KE9=|!cyLt*gNDx@SIJz?rG1B_lWE$Q zh!$iQ4n-nQue&oAX3=kdh|w7 zNxWiUwuvRgDC5aXZ;GGnOpQ+XGln{m zOydelwd3K_E#v#&axJJxWDu57o~f1vfQ`6JO?jt`BEV+axIm2P&{L(8`=`*kMy(yY zqIWg52})^*L+%PKDp#W)8!a280M4LUP)V);byN^u#6-l39yR5;D3Nv8tw!#`Pr2x@c=g&@KqSZA_(D4(0U7KZvoXEuxb{p`VBPxtq2!E&paR~QTz+q C#~P*p diff --git a/google/adk/agents/__pycache__/callback_context.cpython-312.pyc b/google/adk/agents/__pycache__/callback_context.cpython-312.pyc index 07317f10b0fc65cbc48da06c6bfef72da5766cb5..3ff057c155cb31b83cca7c028ebfbcd3a1aecb4f 100644 GIT binary patch delta 63 zcmaFO-NM6tnwOW00SIQUuiePa#H1RlpOK%Ns$W!^mzP?kU!Gr-tshX7pO#u&oL>|V PVrAx}7HyVfy2K0sa)K37 delta 38 scmZqSdCkpznwOW00SM%z6gF}*G4Z(Qm**E{7Z)TZrxtHEV!Ff(0IU!S761SM diff --git a/google/adk/runners/__pycache__/__init__.cpython-312.pyc b/google/adk/runners/__pycache__/__init__.cpython-312.pyc index ce7d0402dfeef051a351c24aa592483430545758..f86b0c5644a94cae31d6dd9d8f03caebdc8fc5f2 100644 GIT binary patch delta 60 zcmX@cc%PB`G%qg~0}#wyUptXILN!c3BR@A)zo;}XFSSU&JijPgKcFZ-Ew#8fzbGEW M%FIbEnwTFB08!Z$Q~&?~ delta 35 pcmcc5c#M(zG%qg~0}y22l$*#M!DFjmo?nz*T#%TYT0F5M900P-3nBmj diff --git a/google/adk/runners/__pycache__/runner.cpython-312.pyc b/google/adk/runners/__pycache__/runner.cpython-312.pyc index b218eeb1cb38b78ceb39a8c9c01116f0911b05e4..614e13684560d4b9708e35f73d6c5c715df457a1 100644 GIT binary patch delta 63 zcmeA-T4BU}nwOW00SIQUuiePKn@Kf9KO;XkRlle-FE6!7zdXMvTR)&EKP|PmIKL delta 38 scmZ2s)NjOnnwOW00SM%z6gG12X5z8dFV8Q^E-pw+PA%U2n#ooS0KW4J5&!@I diff --git a/google/adk/sessions/__pycache__/__init__.cpython-312.pyc b/google/adk/sessions/__pycache__/__init__.cpython-312.pyc index 6327005ccfd86d805834f5ddbde8ac81000c9de1..d715b72bb1ea56dc69b7e5a5a01efab2daeb9e0b 100644 GIT binary patch delta 60 zcmaFG*v!OznwOW00SIQUubs$Uts1VMk)NBYUsRfxms+G>o?n!$A5fH^mRek#Ulb2w MW#*(7O`H?~087^uWdHyG delta 35 pcmZo>dd0|nnwOW00SL-(%1z|1=CRW+&o9a@E=WvHEuOeK0syYC3qJq= diff --git a/google/adk/sessions/__pycache__/in_memory_session_service.cpython-312.pyc b/google/adk/sessions/__pycache__/in_memory_session_service.cpython-312.pyc index 5dd0f082483506a53606a3aa54ef2fe1866eb9db..b6da9a384b69afb2ea0ed1b281d6eddf3fc50273 100644 GIT binary patch delta 63 zcmaFr{Lh*DG%qg~0}#wyU%Qd}GP7!ienx(7s(w*vUS4XEetCXTwthfSep+gAaeh%e Ph?SX>TD18U^KV%I()buB delta 42 wcmez8{M4EIG%qg~0}z~$QrO6SnVH95zdXMvySN}RIkkB52WFAY;w;Z)0Vd!MFaQ7m diff --git a/google/adk/tools/__pycache__/__init__.cpython-312.pyc b/google/adk/tools/__pycache__/__init__.cpython-312.pyc index ab6dfad3323d97eaa9ec9f57b9c233e7d55af249..ec93170f2e64de074b7f81e8cdf84092d276e821 100644 GIT binary patch delta 60 zcmcb}_==JHG%qg~0}#wyUptXIRW(FEBR@A)zo;}XFSSU&JijPgKcFZ-Ew#8fzbGEW M%FIbEnphJG09e%(fB*mh delta 35 pcmaFGc#)C&G%qg~0}#aDl$*$%%44lxo?nz*T#%TYT0C)TC;+vH3qk+@ diff --git a/google/adk/tools/__pycache__/function_tool.cpython-312.pyc b/google/adk/tools/__pycache__/function_tool.cpython-312.pyc index 584b6e0cdb6763ffb7be2ecdb385723afda8251c..dfe12d38b0f6ecc0d94c6c6fcd5570ccfc6ba31c 100644 GIT binary patch delta 63 zcmX@7{#>2=G%qg~0}#wyU%Qc;mqj&FKO;XkRlle-FE6!7zdXMvTR)&EKP|PmIKLo?n!$A5fH^mRek#Ulb2w MW#*(7O`IA908Qf+asU7T delta 35 pcmeBRde6vxnwOW00SH{L$W7#~S~Pht+dBYygBFwk delta 37 rcmX@YznY)>G%qg~0}#kbDQx6k#>Qi&U!Gr-U0jfuoLW5j8rwSnu#yW{ diff --git a/profiler_agent/__pycache__/agent_utils.cpython-312.pyc b/profiler_agent/__pycache__/agent_utils.cpython-312.pyc index e60a4c7530cdf1aa74bd45e80c8c0efa76799ce4..7ad02a39bdf86bce98b7761601eba7305b4e9803 100644 GIT binary patch delta 60 zcmaFB{F9mcG%qg~0}#wyUptZef@-LKMt*Lpeo<*&UTTqkd45s0en3%vT554|eo;J# Mm6?-TH1YXQ0CU$D9RL6T delta 35 pcmey#{D7JJG%qg~0}$wnD@^3Rz+yln+uhc{NkKYJh%5er~FMQE6UYYLR|qfq{up|1&cK6ZZ!u KAZzj*7B2w!Lpibl delta 141 zcmZqYddA6nnwOW00SI)(6*3t&@@6v`3#6y!X69w;8tIwprsX6SXWSA<22#bDX_=`- z@wxdasX0}ANNNg-@+WU*isv!cFV8Q^E-pw+PA#5n&g?9#cY#G@Lh@x6y&FDQx84$;zatxA_k%w;=#|$p`ZQ diff --git a/profiler_agent/__pycache__/tools.cpython-312.pyc b/profiler_agent/__pycache__/tools.cpython-312.pyc index 5bc58126d5d295e438f95ae315859ab754fe70b7..45890b8cafcc119696d55b956cfb8b9d8ef51145 100644 GIT binary patch delta 63 zcmX>Qa5I4WG%qg~0}#wyU%QdpkzLhaKO;XkRlle-FE6!7zdXMvTR)&EKP|PmIKLw4I6jG%qg~0}!}gk(S~U4EV=DlKjTT`5 delta 37 rcmaFJa)O2XG%qg~0}!}gk=w| Date: Sat, 31 Jan 2026 07:37:43 +0000 Subject: [PATCH 3/4] Fix security vulnerability, test config, and code quality issues Co-authored-by: amitdevx <110670491+amitdevx@users.noreply.github.com> --- create_sample_exams.py | 36 ++-- demo.py | 189 +++++++++--------- google/__pycache__/__init__.cpython-312.pyc | Bin 286 -> 0 bytes .../adk/__pycache__/__init__.cpython-312.pyc | Bin 175 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 220 -> 0 bytes .../agents/__pycache__/agent.cpython-312.pyc | Bin 11958 -> 0 bytes .../callback_context.cpython-312.pyc | Bin 1540 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 223 -> 0 bytes .../__pycache__/runner.cpython-312.pyc | Bin 6440 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 259 -> 0 bytes .../in_memory_session_service.cpython-312.pyc | Bin 8702 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 234 -> 0 bytes .../__pycache__/function_tool.cpython-312.pyc | Bin 5095 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 264 -> 0 bytes .../__pycache__/agent.cpython-312.pyc | Bin 1988 -> 0 bytes .../__pycache__/agent_utils.cpython-312.pyc | Bin 505 -> 0 bytes .../__pycache__/config.cpython-312.pyc | Bin 1287 -> 0 bytes .../__pycache__/memory.cpython-312.pyc | Bin 11912 -> 0 bytes .../__pycache__/observability.cpython-312.pyc | Bin 14087 -> 0 bytes .../__pycache__/tools.cpython-312.pyc | Bin 10329 -> 0 bytes profiler_agent/agent_utils.py | 1 + profiler_agent/config.py | 4 +- profiler_agent/memory.py | 2 +- profiler_agent/observability.py | 2 +- profiler_agent/paths.py | 1 - .../__pycache__/__init__.cpython-312.pyc | Bin 336 -> 0 bytes .../__pycache__/strategist.cpython-312.pyc | Bin 609 -> 0 bytes .../__pycache__/taxonomist.cpython-312.pyc | Bin 766 -> 0 bytes .../__pycache__/trend_spotter.cpython-312.pyc | Bin 703 -> 0 bytes profiler_agent/sub_agents/strategist.py | 5 +- profiler_agent/sub_agents/taxonomist.py | 7 +- profiler_agent/tools.py | 8 +- tests/test_agent.py | 103 +++++----- 33 files changed, 186 insertions(+), 172 deletions(-) delete mode 100644 google/__pycache__/__init__.cpython-312.pyc delete mode 100644 google/adk/__pycache__/__init__.cpython-312.pyc delete mode 100644 google/adk/agents/__pycache__/__init__.cpython-312.pyc delete mode 100644 google/adk/agents/__pycache__/agent.cpython-312.pyc delete mode 100644 google/adk/agents/__pycache__/callback_context.cpython-312.pyc delete mode 100644 google/adk/runners/__pycache__/__init__.cpython-312.pyc delete mode 100644 google/adk/runners/__pycache__/runner.cpython-312.pyc delete mode 100644 google/adk/sessions/__pycache__/__init__.cpython-312.pyc delete mode 100644 google/adk/sessions/__pycache__/in_memory_session_service.cpython-312.pyc delete mode 100644 google/adk/tools/__pycache__/__init__.cpython-312.pyc delete mode 100644 google/adk/tools/__pycache__/function_tool.cpython-312.pyc delete mode 100644 profiler_agent/__pycache__/__init__.cpython-312.pyc delete mode 100644 profiler_agent/__pycache__/agent.cpython-312.pyc delete mode 100644 profiler_agent/__pycache__/agent_utils.cpython-312.pyc delete mode 100644 profiler_agent/__pycache__/config.cpython-312.pyc delete mode 100644 profiler_agent/__pycache__/memory.cpython-312.pyc delete mode 100644 profiler_agent/__pycache__/observability.cpython-312.pyc delete mode 100644 profiler_agent/__pycache__/tools.cpython-312.pyc delete mode 100644 profiler_agent/sub_agents/__pycache__/__init__.cpython-312.pyc delete mode 100644 profiler_agent/sub_agents/__pycache__/strategist.cpython-312.pyc delete mode 100644 profiler_agent/sub_agents/__pycache__/taxonomist.cpython-312.pyc delete mode 100644 profiler_agent/sub_agents/__pycache__/trend_spotter.cpython-312.pyc diff --git a/create_sample_exams.py b/create_sample_exams.py index 089d8b9..ecb33e2 100755 --- a/create_sample_exams.py +++ b/create_sample_exams.py @@ -9,9 +9,8 @@ from reportlab.lib.pagesizes import letter from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.units import inch -from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak -from reportlab.lib.enums import TA_CENTER, TA_LEFT -import os +from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer +from reportlab.lib.enums import TA_CENTER from pathlib import Path # Import path utilities @@ -124,45 +123,38 @@ def create_sample_pdf(filename: str, exam_data: dict): question = Paragraph(question_text, question_style) elements.append(question) elements.append(Spacer(1, 0.15*inch)) - + # Add footer elements.append(Spacer(1, 0.5*inch)) footer = Paragraph( - f"This is a sample exam generated for testing purposes. " - f"Bloom's levels included for demonstration.", + "This is a sample exam generated for testing purposes. " + "Bloom's levels included for demonstration.", styles['Italic'] ) elements.append(footer) - + # Build PDF doc.build(elements) - print(f"✓ Created {filename}") + print("✓ Created {filename}".format(filename=filename)) def main(): """Generate all sample exam PDFs.""" print("Generating sample exam PDFs...\n") - + # Ensure input directory exists ensure_directories() - - # Check if reportlab is available - try: - import reportlab - except ImportError: - print("ERROR: reportlab is required to generate sample PDFs") - print("Install it with: pip install reportlab") - return 1 - + + # Check if reportlab is available (already imported at module level) # Generate each exam for filename, exam_data in SAMPLE_EXAMS.items(): try: create_sample_pdf(filename, exam_data) except Exception as e: - print(f"✗ Failed to create {filename}: {e}") - - print(f"\n✓ Successfully generated {len(SAMPLE_EXAMS)} sample exam PDFs") - print(f"✓ Files saved to: {get_input_path('')}") + print("✗ Failed to create {filename}: {error}".format(filename=filename, error=e)) + + print("\n✓ Successfully generated {count} sample exam PDFs".format(count=len(SAMPLE_EXAMS))) + print("✓ Files saved to: {path}".format(path=get_input_path(''))) print("\nYou can now run: python demo.py") return 0 diff --git a/demo.py b/demo.py index a16b0af..7b5706b 100644 --- a/demo.py +++ b/demo.py @@ -35,7 +35,7 @@ from profiler_agent.agent import root_agent from profiler_agent.observability import setup_logging, metrics, tracer, log_agent_event from profiler_agent.memory import MemoryBank -from profiler_agent.paths import get_input_path, get_output_path, list_input_files, ensure_directories +from profiler_agent.paths import get_input_path, list_input_files, ensure_directories from google.genai import types as genai_types @@ -104,14 +104,14 @@ async def demo_memory_bank(): print("\n" + "="*80) print("DEMO 2: Memory Bank & Long-term Context") print("="*80) - + # Memory bank will automatically save to output/memory_bank.json memory_bank = MemoryBank() user_id = "demo_user" - + # Add memories print("\n💾 Adding memories to memory bank...") - + memory_bank.add_memory( user_id=user_id, memory_type="exam_analysis", @@ -123,7 +123,7 @@ async def demo_memory_bank(): }, tags=["physics", "2024", "midterm"] ) - + memory_bank.add_memory( user_id=user_id, memory_type="study_plan", @@ -135,7 +135,7 @@ async def demo_memory_bank(): }, tags=["physics", "study_plan"] ) - + memory_bank.add_memory( user_id=user_id, memory_type="preference", @@ -146,29 +146,36 @@ async def demo_memory_bank(): }, tags=["preferences", "learning_style"] ) - + # Retrieve memories print("\n📚 Retrieving memories...") memories = memory_bank.get_memories(user_id, limit=10) for mem in memories: - print(f" - [{mem['type']}] {json.dumps(mem['content'], indent=2)}") - + print(" - [{type}] {content}".format( + type=mem['type'], + content=json.dumps(mem['content'], indent=2) + )) + # Search memories print("\n🔍 Searching for 'quantum'...") results = memory_bank.search_memories(user_id, "quantum") for result in results: - print(f" - Found: {result['type']} - {result['content']}") - + print(" - Found: {type} - {content}".format( + type=result['type'], + content=result['content'] + )) + # Get summary summary = memory_bank.get_summary(user_id) - print(f"\n📋 Memory Summary: {json.dumps(summary, indent=2)}") - + print("\n📋 Memory Summary: {summary}".format(summary=json.dumps(summary, indent=2))) + # Compact context for LLM context = memory_bank.compact_context(user_id, max_tokens=500) - print(f"\n📄 Compacted Context (for LLM):\n{context}") - - # Cleanup - os.remove("demo_memory.json") + print("\n📄 Compacted Context (for LLM):\n{context}".format(context=context)) + + # Cleanup - clear user memories instead of removing file + # (file is shared in output/memory_bank.json) + memory_bank.clear_user_memories(user_id) async def demo_observability(): @@ -176,43 +183,43 @@ async def demo_observability(): print("\n" + "="*80) print("DEMO 3: Observability (Logging, Tracing, Metrics)") print("="*80) - - # Setup structured logging - logger = setup_logging(level="INFO", structured=True) - + + # Setup structured logging (logger returned for potential future use) + setup_logging(level="INFO", structured=True) + # Start trace print("\n🔍 Starting trace for agent operation...") trace_id = tracer.start_trace("demo_agent_execution", metadata={"user": "demo"}) - + # Simulate agent operations import time - + print(" ⏱️ Simulating PDF ingestion...") time.sleep(0.1) tracer.add_span(trace_id, "pdf_ingestion", 100.5, {"file": "sample.pdf"}) metrics.increment("pdf.ingested") metrics.histogram("pdf.pages", 12) - + print(" ⏱️ Simulating question classification...") time.sleep(0.15) tracer.add_span(trace_id, "question_classification", 150.2, {"count": 25}) metrics.increment("questions.classified", 25) metrics.histogram("classification.duration_ms", 150.2) - + print(" ⏱️ Simulating trend analysis...") time.sleep(0.2) tracer.add_span(trace_id, "trend_analysis", 200.7, {"trends_found": 3}) metrics.increment("trends.analyzed") metrics.histogram("analysis.duration_ms", 200.7) - + # End trace trace_data = tracer.end_trace(trace_id) - print(f"\n📊 Trace Data:\n{json.dumps(trace_data, indent=2)}") - + print("\n📊 Trace Data:\n{trace}".format(trace=json.dumps(trace_data, indent=2))) + # Get metrics metrics_data = metrics.get_metrics() - print(f"\n📈 Metrics:\n{json.dumps(metrics_data, indent=2)}") - + print("\n📈 Metrics:\n{metrics}".format(metrics=json.dumps(metrics_data, indent=2))) + # Reset for clean slate metrics.reset() @@ -222,37 +229,37 @@ async def demo_tools(): print("\n" + "="*80) print("DEMO 4: Custom Tools (PDF, Statistics, Visualization)") print("="*80) - + from profiler_agent.tools import ( read_pdf_content, analyze_statistics, visualize_trends, list_available_exams ) - + # List available exams - print(f"\n📂 Listing available exams in input/ folder...") + print("\n📂 Listing available exams in input/ folder...") available = list_available_exams() if available.get("count", 0) > 0: - print(f" ✅ Found {available['count']} exam(s):") + print(" ✅ Found {count} exam(s):".format(count=available['count'])) for filename in available.get("files", []): - print(f" - {filename}") + print(" - {filename}".format(filename=filename)) else: - print(f" ⚠️ No exams found in input/ folder") - + print(" ⚠️ No exams found in input/ folder") + # Create mock PDF in input folder test_pdf = get_input_path("demo_exam.pdf") if not test_pdf.exists(): - print(f"\n📄 Creating mock PDF at {test_pdf}...") + print("\n📄 Creating mock PDF at {path}...".format(path=test_pdf)) with open(test_pdf, "w") as f: f.write("Mock exam content") - - print(f"\n📄 Testing read_pdf_content tool...") + + print("\n📄 Testing read_pdf_content tool...") result = read_pdf_content("demo_exam.pdf") # Just use filename - print(f" ✅ Extracted content from: {result.get('filename', 'unknown')}") - + print(" ✅ Extracted content from: {filename}".format(filename=result.get('filename', 'unknown'))) + # Test statistics tool - print(f"\n📊 Testing analyze_statistics tool...") + print("\n📊 Testing analyze_statistics tool...") mock_questions = { "questions": [ {"topic": "Quantum Mechanics", "bloom_level": "Analyze"}, @@ -262,19 +269,19 @@ async def demo_tools(): {"topic": "Quantum Mechanics", "bloom_level": "Analyze"}, ] } - + stats = analyze_statistics(json.dumps(mock_questions)) - print(f" ✅ Statistics:\n{json.dumps(stats, indent=4)}") - + print(" ✅ Statistics:\n{stats}".format(stats=json.dumps(stats, indent=4))) + # Test visualization tool (output will go to output/charts/) - print(f"\n📈 Testing visualize_trends tool...") + print("\n📈 Testing visualize_trends tool...") chart_path = "demo_chart.png" # Will be saved to output/charts/ viz_result = visualize_trends(json.dumps(stats), chart_path) - + if viz_result.get("success"): - print(f" ✅ Chart created: {viz_result['chart_path']}") + print(" ✅ Chart created: {path}".format(path=viz_result['chart_path'])) else: - print(f" ⚠️ {viz_result.get('error', 'Unknown error')}") + print(" ⚠️ {error}".format(error=viz_result.get('error', 'Unknown error'))) async def demo_multi_agent(): @@ -282,32 +289,32 @@ async def demo_multi_agent(): print("\n" + "="*80) print("DEMO 5: Multi-Agent System (Hub-and-Spoke)") print("="*80) - + from profiler_agent.sub_agents import taxonomist, trend_spotter, strategist - - print(f"\n🤖 Root Agent: {root_agent.name}") - print(f" Model: {root_agent.model}") - print(f" Description: {root_agent.description}") - print(f" Tools: {[tool.name for tool in root_agent.tools]}") - print(f" Sub-agents: {[agent.name for agent in root_agent.sub_agents]}") - - print(f"\n🔹 Sub-agent 1: {taxonomist.name}") - print(f" Model: {taxonomist.model}") - print(f" Role: {taxonomist.description}") - print(f" Output Key: {taxonomist.output_key}") - - print(f"\n🔹 Sub-agent 2: {trend_spotter.name}") - print(f" Model: {trend_spotter.model}") - print(f" Role: {trend_spotter.description}") - print(f" Output Key: {trend_spotter.output_key}") - - print(f"\n🔹 Sub-agent 3: {strategist.name}") - print(f" Model: {strategist.model}") - print(f" Role: {strategist.description}") - print(f" Output Key: {strategist.output_key}") - - print(f"\n📊 Architecture Pattern: Hub-and-Spoke (Sequential Execution)") - print(f" Flow: Root → Taxonomist → Trend Spotter → Strategist") + + print("\n🤖 Root Agent: {name}".format(name=root_agent.name)) + print(" Model: {model}".format(model=root_agent.model)) + print(" Description: {description}".format(description=root_agent.description)) + print(" Tools: {tools}".format(tools=[tool.name for tool in root_agent.tools])) + print(" Sub-agents: {agents}".format(agents=[agent.name for agent in root_agent.sub_agents])) + + print("\n🔹 Sub-agent 1: {name}".format(name=taxonomist.name)) + print(" Model: {model}".format(model=taxonomist.model)) + print(" Role: {description}".format(description=taxonomist.description)) + print(" Output Key: {output_key}".format(output_key=taxonomist.output_key)) + + print("\n🔹 Sub-agent 2: {name}".format(name=trend_spotter.name)) + print(" Model: {model}".format(model=trend_spotter.model)) + print(" Role: {description}".format(description=trend_spotter.description)) + print(" Output Key: {output_key}".format(output_key=trend_spotter.output_key)) + + print("\n🔹 Sub-agent 3: {name}".format(name=strategist.name)) + print(" Model: {model}".format(model=strategist.model)) + print(" Role: {description}".format(description=strategist.description)) + print(" Output Key: {output_key}".format(output_key=strategist.output_key)) + + print("\n📊 Architecture Pattern: Hub-and-Spoke (Sequential Execution)") + print(" Flow: Root → Taxonomist → Trend Spotter → Strategist") async def main(): @@ -321,7 +328,7 @@ async def main(): print(" ✅ Sessions & Memory (InMemorySessionService + MemoryBank)") print(" ✅ Observability (Logging, Tracing, Metrics)") print(" ✅ Gemini API integration (if API key provided)") - + # Show folder structure print("\n📁 Project Structure:") print(" 📂 input/ - Place your exam PDFs here") @@ -329,27 +336,27 @@ async def main(): print(" ├── charts/ - Visualization charts") print(" ├── logs/ - Log files") print(" └── reports/ - Analysis reports") - + # Ensure directories exist ensure_directories() - + # Check for input files input_files = list_input_files() - print(f"\n📄 Found {len(input_files)} PDF file(s) in input/ folder") + print("\n📄 Found {count} PDF file(s) in input/ folder".format(count=len(input_files))) if input_files: for f in input_files[:3]: # Show first 3 - print(f" - {f.name}") + print(" - {name}".format(name=f.name)) if len(input_files) > 3: - print(f" ... and {len(input_files) - 3} more") - + print(" ... and {count} more".format(count=len(input_files) - 3)) + # Check for API key api_key = os.getenv("GOOGLE_API_KEY") if not api_key: print("\n⚠️ WARNING: GOOGLE_API_KEY not set. Agent will use mock responses.") print(" To use real Gemini API, set: export GOOGLE_API_KEY=your_key") else: - print(f"\n✅ GOOGLE_API_KEY found (length: {len(api_key)})") - + print("\n✅ GOOGLE_API_KEY found (length: {length})".format(length=len(api_key))) + try: # Run demos await demo_multi_agent() @@ -357,19 +364,19 @@ async def main(): await demo_observability() await demo_memory_bank() await demo_basic_workflow() - + print("\n" + "="*80) print("✅ ALL DEMOS COMPLETED SUCCESSFULLY!") print("="*80) - print(f"\n📊 Check the output/ folder for:") - print(f" - Charts: output/charts/") - print(f" - Logs: output/logs/demo_run.log") - print(f" - Memory: output/memory_bank.json") - + print("\n📊 Check the output/ folder for:") + print(" - Charts: output/charts/") + print(" - Logs: output/logs/demo_run.log") + print(" - Memory: output/memory_bank.json") + except KeyboardInterrupt: print("\n\n⚠️ Demo interrupted by user") except Exception as e: - print(f"\n\n❌ Error during demo: {e}") + print("\n\n❌ Error during demo: {error}".format(error=e)) import traceback traceback.print_exc() diff --git a/google/__pycache__/__init__.cpython-312.pyc b/google/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 1607efafa9e5785d35faf51e3922e0ef8eecf70a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 286 zcmX@j%ge<81T)vyW?lr+k3k$5V1Y6|8vz;98B!Qh7;_k+7?>DR8M2sQiV$=vQ!4Xn zsCX1pC8H+OOOT|WCd)1Mg6#CtlFXc2+^H2Msd*{!1&JjYw>aX#bbLIBm6w>C8Xv!s z;WJ3jFE9O!{M=OiqSCy))FS=z{Gx3AfTH}g)Z*g&qIeK1GY2S+MJPQ#KRqW^KR!M) zFS8^*Uaz3?7l%!5eoARhs$CJttBgQgEC(b$Ff%eT-e%zYz`?*H-(h~8Tkaya+>DA9 O5|_D+8aawMfJy*wWlW?1 diff --git a/google/adk/__pycache__/__init__.cpython-312.pyc b/google/adk/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 5ca9d72c9cc3a9b05173e530de3437f130cb262c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 175 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 diff --git a/google/adk/agents/__pycache__/callback_context.cpython-312.pyc b/google/adk/agents/__pycache__/callback_context.cpython-312.pyc deleted file mode 100644 index 3ff057c155cb31b83cca7c028ebfbcd3a1aecb4f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1540 zcmZux&u<$=6n^{D>rGrYPHIYHqz#~;<%g|?&ve-+B!Bbfy?LJZee-62s#HwC z@%YEDf|3I8yI2%Mof?yyXpF!I2YktwL#Zn}a#wMbuIi|g@F}6z)g4^|8MeV!55U(> z*Ye_s(>-prV`jM>8gZ)^C;Yu0i#?hoLDXsfDKc#(RX&Oa zsqsmV2XW+vsd^{yxGkln+u|Q0?&SyMn6W@}au;C)6dVcbm3_%k%HW&6LRDX-ny*ov zmZ(P6w)`sY6u-9a>j#=+_~>uTzHy*BWwcGS%V;kQWP2g47WHR`@A2XFsbTfpB%!{= zW6N!G%1FkEcm>^dKk@{Dq`8x7Su8aeO?v2|snMm}^n}*XH?E3#STB-(NcV_>uZeWr7!mWEfhDYuH7vE0NGUD~weR7p6?R6Lg0CCp4jmc^=w z=~9PsQX~O2h+P9mdTi@M!Qe8VW)|l}Ai;<3wjL{Yv7a56{ zh^$0xZk$Zlfz*+HP>Oem9M$0)*tB0zc7Z3hZPv*64CDI`sK=XW85=rCxEt|Q6^u{< zmBk@VwVum(V#`bv#p(jCAY9bLQj?Hw?Ds=qFB0;3-wmgc5+Q!< z5yFH#Y(;?Ra;6EmEFh~s-GO;!=ciX=%- X;rg$zeg^Bm!1{B&E}4G=LYDjwf>?IA diff --git a/google/adk/runners/__pycache__/__init__.cpython-312.pyc b/google/adk/runners/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index f86b0c5644a94cae31d6dd9d8f03caebdc8fc5f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 223 zcmX@j%ge<81T)vyW=aF;#~=<2FhLog1%Qm{3@HpLj5!Rsj8Tk?43$ip%r6;%!kUb? z*n&#)@=}ZZG?{L(6@i&W%s`oy44*;ze}(C1n)_~kn3}Sp> PW@Kc%#~@Y22IK$$FR48C diff --git a/google/adk/runners/__pycache__/runner.cpython-312.pyc b/google/adk/runners/__pycache__/runner.cpython-312.pyc deleted file mode 100644 index 614e13684560d4b9708e35f73d6c5c715df457a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6440 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 diff --git a/google/adk/sessions/__pycache__/in_memory_session_service.cpython-312.pyc b/google/adk/sessions/__pycache__/in_memory_session_service.cpython-312.pyc deleted file mode 100644 index b6da9a384b69afb2ea0ed1b281d6eddf3fc50273..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8702 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 CetyXS diff --git a/profiler_agent/__pycache__/__init__.cpython-312.pyc b/profiler_agent/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index dd4e1093c771afe256661a1c7b3581cd1af07e88..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 264 zcmX@j%ge<81T)vyX1W6D#~=<2FhLog#ej_I3@HpLj5!Rsj8Tk?3@J?Mj8ROL%$h7O zL5egPZ*djn=aoy_C% z9x$gKbB@lL7kXN)@?0%Ht!Z&~y+Z>DO7V6SSkh&|=ZtyJ6%;V$EEYI!r3<&@a%(01CeSHjux90`cm+of$Gi3BIt5^?pR zD_=VLNMM|n(I;CCJO5AS9gdTV8*R3O#(CCz@G#i zSO|P~<*9u7Schz9uuQvZ_i z2adveDEkm- zZwwP}_TWS4S_#0L9%KHS0D8cE&$u>#bDmJxGytSnj3BDRDdZklJ!}Kz+}(^48jSa* zke#jMBskXwwq2(O^bE^JJS@7dBZcw)0LOF@$0!nJvDGCUysuBj9JkA4K&UQNjWnzW zr;v*ejRw)7fQE4^;(%6SFwhp>)tB$rm;L&(O@4-w8OD7U;VyYdF|jOY9L2AM7EuLY zo@g2Mb5(5Nk9MGSEr})Fkw6*qRr#P(JSxEdJk2e9Ysl|eQbMBc0YI$dX+~Q3x1igdN=TjO(g*e>;VD76>8JCYU7+Ht`1%SCZSSf6LOd;UJYhR#(@D- z%f*?1_%zNYXySrRg~i=aZHx04FNPPCC@P`;D{*;Pecm1*O$Tf{ z)&ZZR;`6fdF*sv#0P=VA0xam|XU#72X_H3*G;?zs21)boXp$*SO|n;+CZ}98$*B?c zUg|d>e&o>)IfnDqTo%8A=%+PJ`v+B?prt3M_5@veiq;O$+EaAz5Z!xr8K2~rPK@fY zv3OuCo>W;srC+S{tEjqgT)_twd|bJ5P`Pqgxq4h_9#oot8i$n+_RBZ=sIa!K^=B{4 zZfN}#G<*5co!?sfmFv&+`Ta|89qDiXt(4Uxy`CsT%cJ^{zS7TU3%UQUExwl8M+^O% N8Y*A?*5K-w`9C)Oa;*RW diff --git a/profiler_agent/__pycache__/agent_utils.cpython-312.pyc b/profiler_agent/__pycache__/agent_utils.cpython-312.pyc deleted file mode 100644 index 7ad02a39bdf86bce98b7761601eba7305b4e9803..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 505 zcmZutF-yZh6uxWHRxPDcRGdT{gn%TY2-2l{5yYiSh{?5vUUK2?f_4xc-24H>e_(O& zFE|P=&cRMjZiPB^^1YZi`QW|pd-vVD_ujoVn`;E@;rTjz1^mRMG_xR!10WL;kbrU$ z(LSZfE4&gJeFM0`Xkap9UfPmrUXLZ6<*SuHAS01B_{F3+3`5iv)mam=K1 zDdfmGkz&A<64FH$ayb9zC@D188?snCd2~}9a%CsuY+I#CBHm$l^&NZd4Q7Z;(u7utH9FeR%X5f% diff --git a/profiler_agent/__pycache__/config.cpython-312.pyc b/profiler_agent/__pycache__/config.cpython-312.pyc deleted file mode 100644 index 72ae7620efc16069b8d6b5c477c02b94352eba18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1287 zcmbVM%WD%s82@H>^KR7EjgR7^q975QXb<9}h%tsztx2h^=pqbjc9L$~?AF9#g8i}ui&f%Ny_dRBQ-}lY#n|M45d_8`C z+YHG79|dTwz=pDR2LaT<00UW|iCwZ#RnjEXw8@$h0ism=lUm8my%PT!3-oNmbWH-# zLRy$WMg2Xhvh#PFA(QYko+TE7f5;0M!T-t&8SVE;5)6P5xd%qHby}+yEiM6x|3I6j z(S9J213>rwmFMb&zBGC-5Y_>0hFqL6?g^>dFQYe63=0!;^!L6#V% zPRX zqc-WbZcWd$V)Drl3uA2SRe~{#Vv$+1gx-Pi&6;jCJ+y;UVzTQ|wi#pHwjIx>;$lq2 zBYg^MT*CTrdfcg!G_Bb-q3KD7-bjlQi0e8Ow~G0biPQ57#d?|8Ub^Vlo0^!WF@CHs zws6H2W4{lpyZROtb-uWy^sRKKX7lseWhF($XW&OBH#5m`S?tHCiA%){Sghw+tC}fb5Ub8%gco#UC3MqQnG*~1zRU{Ll>nuddeqrIlJN4jO z_2814oVz%inVo5L0Foa>=c4Z*@fH$`+YUdeEW?QvklKyCPo|dP_%FtzFODt4$yF5n zDoH5a04~;KkYWu1KZhZ@cPY{{7yQ=P4dKo&pl}Y2Fm$Q)?uG99o`pS&J;z>!mO9Ve dJ>Q53=epa;sn72|y8~YD*mt&HetimY`5n6`G&2AI diff --git a/profiler_agent/__pycache__/memory.cpython-312.pyc b/profiler_agent/__pycache__/memory.cpython-312.pyc deleted file mode 100644 index 631a370964a037757f1ca8d57d96e29ce4231fee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11912 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�K diff --git a/profiler_agent/agent_utils.py b/profiler_agent/agent_utils.py index dcc85e3..4c374f2 100644 --- a/profiler_agent/agent_utils.py +++ b/profiler_agent/agent_utils.py @@ -1,5 +1,6 @@ from google.adk.agents.callback_context import CallbackContext from google.genai.types import Content + def suppress_output_callback(callback_context: CallbackContext) -> Content: return Content() diff --git a/profiler_agent/config.py b/profiler_agent/config.py index a3bf186..885e6c9 100644 --- a/profiler_agent/config.py +++ b/profiler_agent/config.py @@ -5,7 +5,7 @@ try: _, project_id = google.auth.default() except Exception: - # If Application Default Credentials are not available (e.g. in CI/local test) + # If Application Default Credentials are not available (e.g. in CI/local) # fall back to the env var or a sensible default so importing this module # doesn't raise. Tests can override `GOOGLE_CLOUD_PROJECT` if needed project_id = os.environ.get("GOOGLE_CLOUD_PROJECT", "local") @@ -13,9 +13,11 @@ os.environ.setdefault("GOOGLE_CLOUD_LOCATION", "global") os.environ.setdefault("GOOGLE_GENAI_USE_VERTEXAI", "True") + @dataclass class ProfilerConfiguration: classifier_model: str = "gemini-2.0-flash-exp" analyzer_model: str = "gemini-2.0-pro-exp" + config = ProfilerConfiguration() diff --git a/profiler_agent/memory.py b/profiler_agent/memory.py index dd0176f..af2bdf3 100644 --- a/profiler_agent/memory.py +++ b/profiler_agent/memory.py @@ -255,7 +255,7 @@ def compact_context( def _generate_id(self, user_id: str, memory_type: str, content: Dict) -> str: """Generate unique memory ID.""" data = f"{user_id}:{memory_type}:{json.dumps(content)}:{datetime.now().isoformat()}" - return hashlib.md5(data.encode()).hexdigest()[:16] + return hashlib.md5(data.encode(), usedforsecurity=False).hexdigest()[:16] def clear_user_memories(self, user_id: str) -> int: """Clear all memories for a user.""" diff --git a/profiler_agent/observability.py b/profiler_agent/observability.py index 4974886..bc55356 100644 --- a/profiler_agent/observability.py +++ b/profiler_agent/observability.py @@ -8,7 +8,7 @@ from datetime import datetime from collections import defaultdict import threading -from .paths import get_output_path, LOGS_DIR +from .paths import get_output_path # Configure structured logging diff --git a/profiler_agent/paths.py b/profiler_agent/paths.py index d719041..591cf92 100644 --- a/profiler_agent/paths.py +++ b/profiler_agent/paths.py @@ -1,5 +1,4 @@ """Path configuration for input and output directories.""" -import os from pathlib import Path diff --git a/profiler_agent/sub_agents/__pycache__/__init__.cpython-312.pyc b/profiler_agent/sub_agents/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 61def91d8669e5e2bc52cc3248745b70dff06794..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 336 zcmZvYJx;_h5QS%wEW3)7iW^Xo2Gh|Xj)15mAuGrxMEEBfJAy4JI0d4mK~x-s8${ZQ ziVkVJtr)}7Af|Zp&3m50^Hx_0Qbj)9_o_7S@Lh51>Gy0SgUa;>@7M2EREs zna!FEp#xPsVUsUs_09+KT8&c~+gtCv?L*|Lilm*AVem0(N^ywPM%|@2r<`}FNHSwN zt&2a-G?%h9M#>jh^8?tNiz{!npwT%^;>OdpI3s_lL+~UM^@ja_tYMqUw$m<(Fkbvn s5K{K8k5V2C%gy50dNmq-Lg`NFM`4%cGe^V~?5$v*bt;eU=d@t|3tlT)!2kdN diff --git a/profiler_agent/sub_agents/__pycache__/strategist.cpython-312.pyc b/profiler_agent/sub_agents/__pycache__/strategist.cpython-312.pyc deleted file mode 100644 index b23d6cbe3fb7a7bc989c779dba970f6f7f12d170..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 609 zcmZuvF>ezw6ti z@e>f0`a_tQh)(K&n7UOeQzz_8t1ftWe&2g~@B948KYG0w`SbPclMoo8KLOmHwVUMX zl~2APjyRTRjz<_HPIya_IT;a*2m%b`W9N44?&fVC@eW@*UH=}fs-t$bF^De$7~neE z%R#vdvO=A)Ruhrev2}(y$c1%_-4~!BAWPZU39Z<%1ZrK$D_TgVY_ib%%mSxcQ8xv8 zWI%EH77A_LkUEgEDTfJ9MW4UlJ2*Q0Ku@O3QKwg%5_(|3b5KTK3P%q;@sPe_6QCol zU`UzY_{`{{=_GZ3A{3KZBO(hT22mZ&H3wO@6`Modol8gVS-upCi5)S0!-HIHo@O-=*mZQouyMfht!nHyOW-1 zGfUqXJppUA$wDTium7i1-1M@BF16+HDzoXG=}A#tuRrmmrG%$u+w=L4f$Sq6E?XGm sbM){WJ^0<*UdH`j@y^e9XSs2I*?)XV(AGX#-rN1#?QD|IvFZDXf58U1x&QzG diff --git a/profiler_agent/sub_agents/__pycache__/taxonomist.cpython-312.pyc b/profiler_agent/sub_agents/__pycache__/taxonomist.cpython-312.pyc deleted file mode 100644 index 751017444be5f9c6e32d7ea3f6078e607f5a905d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 766 zcmZuu&u`N(6n4^|OI7HAN+2$-IFw3LqQMm*jUi5GAhb!Sm&lD@GE*lGw$m+@IPf2E zL;MK{B>oUioTwu8fH>_oX`FV#N!N10lApi#p8dV&?|tuf9c0$0Z|_*&M(B6xtXKOA zT)Z~mBcg~}9L3hyvJkatjoYz3wkurcwYWa6TgXOWf&Hf9T+^&h+MrF^qHU;u-TKm8 z#m3Ft8FkJ|XJi$Py$BM0X1XZ7W+0M~MF!ngS(-{v$`_eVGwlb2^9c#2g`>%-NJPw( zp6~B-q7(}mC=4_Uh-M-o9KX##l~CbHj;1yf`!r>I5X#$FQmE7Cloa0_tzRF=`f#SWw_aJs~j^ z9SSOtEG_?7bXcOa%!0~suH>3RUfd#~2I-Sh;f;O=qgJsU2@!E{37xuCR>_(ffNU63 zbXNB>&A5`~%!*#HR$2O07qVr-pV5V}=+gI&MGT(Ik_4o8BIMM2DMe^TC!}8@#!dV` zAtP^@wgY={eXzK*`={HwX1{mjP4n(AW32Px diff --git a/profiler_agent/sub_agents/__pycache__/trend_spotter.cpython-312.pyc b/profiler_agent/sub_agents/__pycache__/trend_spotter.cpython-312.pyc deleted file mode 100644 index 81c799994b06c212ba6b26fa2a06969d7a251584..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 703 zcmZuvO^ehp7*0B$+i`RjU04y>fQMx+tshqr7SW?FdXc%5O!H1BPMdU-baVzh_$$Pp zAPW8vPo6A+Imn*&CakADnYOcnU=DBI=gIq(C;2uU`pB)1U*GW(BXnAU%hlgH=dYae z0WrjIiZVRG7%`8vQZMr+UhTKjR@R=hG4c>#@Ltz7FUvGL>##2Cu|BlF?0oJv+@!nm z$Ahy9jIr$;B_Pb1*Pw1J#f&G8-`7Q+E6_TUg~5y^@Afp z)72U@(S{nX4R?`5&-u(~!Uci1G$V`}O5_q0F+77{&9+Iz%z+r1B*4f?0WMhzO^hTn zE*N>Lz>y+ek(W(5>d4135!~=4Y{`H;uLmTQ9M)mv1>{nh!@yo?CRH^acWoz=3{u+` zGy^+epku}Js-+!pp^Yly8u;5AFJNVF(3!hrL@Sa0O7Lvlv%86uNeTgFi=aN$s^v6P z9cS#Ii7yOKwW?&dS81b?is~6u&yk dict: "count": len(pdf_files), "files": [f.name for f in pdf_files], "paths": [str(f) for f in pdf_files], - "message": f"Found {len(pdf_files)} PDF file(s) in input/ directory" + "message": "Found {count} PDF file(s) in input/ directory".format(count=len(pdf_files)) } except Exception as e: - return {"error": f"Failed to list files: {str(e)}"} \ No newline at end of file + return {"error": "Failed to list files: {error}".format(error=str(e))} diff --git a/tests/test_agent.py b/tests/test_agent.py index f2d18e4..7badce4 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -6,6 +6,8 @@ import sys from pathlib import Path +import pytest + # Ensure repo root is on sys.path so running this script directly # (python tests/test_agent.py) can import local packages like `google.adk`. repo_root = Path(__file__).resolve().parent.parent @@ -19,6 +21,7 @@ from google.genai import types as genai_types +@pytest.mark.asyncio async def test_agent_initialization(): """Test agent initialization.""" print("TEST 1: Agent Initialization") @@ -29,28 +32,29 @@ async def test_agent_initialization(): assert len(root_agent.tools) > 0 print(f"✅ Root agent: {root_agent.name}") - print(f"✅ Sub-agents: {[a.name for a in root_agent.sub_agents]}") - print(f"✅ Tools: {[t.name for t in root_agent.tools]}") + print("✅ Sub-agents: {sub_agents}".format(sub_agents=[a.name for a in root_agent.sub_agents])) + print("✅ Tools: {tools}".format(tools=[t.name for t in root_agent.tools])) print() +@pytest.mark.asyncio async def test_session_service(): """Test session service.""" print("TEST 2: Session Service") print("-" * 60) - + session_service = InMemorySessionService() - + # Create session session = await session_service.create_session( app_name="test_app", user_id="test_user", session_id="test_session" ) - + assert session["session_id"] == "test_session" - print(f"✅ Created session: {session['session_id']}") - + print("✅ Created session: {session_id}".format(session_id=session['session_id'])) + # Add messages await session_service.add_message( app_name="test_app", @@ -59,16 +63,16 @@ async def test_session_service(): role="user", content="Test message" ) - + messages = await session_service.get_messages( app_name="test_app", user_id="test_user", session_id="test_session" ) - + assert len(messages) == 1 - print(f"✅ Added and retrieved message") - + print("✅ Added and retrieved message") + # Update context await session_service.update_context( app_name="test_app", @@ -76,37 +80,38 @@ async def test_session_service(): session_id="test_session", context_updates={"test_key": "test_value"} ) - + context = await session_service.get_context( app_name="test_app", user_id="test_user", session_id="test_session" ) - + assert context["test_key"] == "test_value" - print(f"✅ Updated and retrieved context") + print("✅ Updated and retrieved context") print() +@pytest.mark.asyncio async def test_tools(): """Test custom tools.""" print("TEST 3: Custom Tools") print("-" * 60) - + from profiler_agent.tools import read_pdf_content, analyze_statistics import json - + # Ensure mock file exists os.makedirs("tests/sample_data", exist_ok=True) test_file = "tests/sample_data/test.pdf" with open(test_file, "w") as f: f.write("mock pdf content") - + # Test PDF tool result = read_pdf_content(test_file) assert "filename" in result or "error" in result - print(f"✅ PDF tool executed: {result.get('filename', 'error')}") - + print("✅ PDF tool executed: {filename}".format(filename=result.get('filename', 'error'))) + # Test statistics tool mock_data = { "questions": [ @@ -114,19 +119,20 @@ async def test_tools(): {"topic": "Math", "bloom_level": "Analyze"} ] } - + stats = analyze_statistics(json.dumps(mock_data)) assert "total_questions" in stats assert stats["total_questions"] == 2 - print(f"✅ Statistics tool executed: {stats['total_questions']} questions") + print("✅ Statistics tool executed: {count} questions".format(count=stats['total_questions'])) print() +@pytest.mark.asyncio async def test_runner_execution(): """Test runner with agent execution.""" print("TEST 4: Runner Execution") print("-" * 60) - + # Setup setup_logging(level="INFO") session_service = InMemorySessionService() @@ -135,21 +141,21 @@ async def test_runner_execution(): user_id="test_user", session_id="test_sess" ) - + runner = Runner( agent=root_agent, app_name="test_app", session_service=session_service ) - + # Ensure mock file exists os.makedirs("tests/sample_data", exist_ok=True) with open("tests/sample_data/physics_2024.pdf", "w") as f: f.write("mock content") - + query = "Analyze tests/sample_data/physics_2024.pdf" - print(f"Query: {query}") - + print("Query: {query}".format(query=query)) + final_response = None async for event in runner.run_async( user_id="test_user", @@ -161,22 +167,23 @@ async def test_runner_execution(): ): if event.is_final_response(): final_response = event.content.parts[0].text - print(f"Response received: {final_response[:100]}...") - + print("Response received: {response}...".format(response=final_response[:100])) + assert final_response is not None - print(f"✅ Runner executed successfully") + print("✅ Runner executed successfully") print() +@pytest.mark.asyncio async def test_memory_bank(): """Test memory bank functionality.""" print("TEST 5: Memory Bank") print("-" * 60) - + from profiler_agent.memory import MemoryBank - + memory_bank = MemoryBank(storage_path="test_memory.json") - + # Add memory memory_id = memory_bank.add_memory( user_id="test_user", @@ -184,19 +191,19 @@ async def test_memory_bank(): content={"key": "value"}, tags=["test"] ) - - print(f"✅ Added memory: {memory_id}") - + + print("✅ Added memory: {memory_id}".format(memory_id=memory_id)) + # Retrieve memory memories = memory_bank.get_memories("test_user") assert len(memories) > 0 - print(f"✅ Retrieved {len(memories)} memories") - + print("✅ Retrieved {count} memories".format(count=len(memories))) + # Search results = memory_bank.search_memories("test_user", "value") assert len(results) > 0 - print(f"✅ Search found {len(results)} results") - + print("✅ Search found {count} results".format(count=len(results))) + # Cleanup if os.path.exists("test_memory.json"): os.remove("test_memory.json") @@ -209,31 +216,31 @@ async def main(): print("PROFESSOR PROFILER - INTEGRATION TESTS") print("="*60) print() - + api_key = os.getenv("GOOGLE_API_KEY") if not api_key: print("⚠️ GOOGLE_API_KEY not set - using mock responses") else: - print(f"✅ GOOGLE_API_KEY configured") - + print("✅ GOOGLE_API_KEY configured") + print() - + try: await test_agent_initialization() await test_session_service() await test_tools() await test_memory_bank() await test_runner_execution() - + print("="*60) print("✅ ALL TESTS PASSED") print("="*60) - + except AssertionError as e: - print(f"\n❌ TEST FAILED: {e}") + print("\n❌ TEST FAILED: {error}".format(error=e)) raise except Exception as e: - print(f"\n❌ ERROR: {e}") + print("\n❌ ERROR: {error}".format(error=e)) import traceback traceback.print_exc() raise From 55134ce52f12a8119fb4aac7395cd49248b1d99c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 31 Jan 2026 07:40:58 +0000 Subject: [PATCH 4/4] Address code review feedback and clean up whitespace Co-authored-by: amitdevx <110670491+amitdevx@users.noreply.github.com> --- create_sample_exams.py | 42 ++++++--- demo.py | 20 ++-- google/adk/agents/agent.py | 94 +++++++++---------- google/adk/agents/callback_context.py | 6 +- google/adk/runners/runner.py | 30 +++--- .../adk/sessions/in_memory_session_service.py | 60 ++++++------ google/adk/tools/function_tool.py | 34 +++---- profiler_agent/memory.py | 94 +++++++++---------- profiler_agent/observability.py | 80 ++++++++-------- profiler_agent/paths.py | 20 ++-- profiler_agent/tools.py | 75 +++++++-------- tests/test_agent.py | 4 +- 12 files changed, 283 insertions(+), 276 deletions(-) diff --git a/create_sample_exams.py b/create_sample_exams.py index ecb33e2..5bd87ed 100755 --- a/create_sample_exams.py +++ b/create_sample_exams.py @@ -6,11 +6,16 @@ in the input/ folder, so you can test the system without needing real exam papers. """ -from reportlab.lib.pagesizes import letter -from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle -from reportlab.lib.units import inch -from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer -from reportlab.lib.enums import TA_CENTER +try: + from reportlab.lib.pagesizes import letter + from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle + from reportlab.lib.units import inch + from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer + from reportlab.lib.enums import TA_CENTER + REPORTLAB_AVAILABLE = True +except ImportError: + REPORTLAB_AVAILABLE = False + from pathlib import Path # Import path utilities @@ -71,7 +76,7 @@ def create_sample_pdf(filename: str, exam_data: dict): """Create a sample exam PDF with the given questions.""" filepath = str(get_input_path(filename)) # Convert Path to string - + # Create PDF document doc = SimpleDocTemplate( filepath, @@ -81,10 +86,10 @@ def create_sample_pdf(filename: str, exam_data: dict): topMargin=72, bottomMargin=18 ) - + # Container for the 'Flowable' objects elements = [] - + # Define styles styles = getSampleStyleSheet() title_style = ParagraphStyle( @@ -95,7 +100,7 @@ def create_sample_pdf(filename: str, exam_data: dict): spaceAfter=30, alignment=TA_CENTER ) - + question_style = ParagraphStyle( 'Question', parent=styles['BodyText'], @@ -103,12 +108,12 @@ def create_sample_pdf(filename: str, exam_data: dict): spaceAfter=12, leftIndent=20 ) - + # Add title title = Paragraph(exam_data["title"], title_style) elements.append(title) elements.append(Spacer(1, 0.2*inch)) - + # Add instructions instructions = Paragraph( "Instructions: Answer all questions. Show all work for calculation problems. " @@ -117,7 +122,7 @@ def create_sample_pdf(filename: str, exam_data: dict): ) elements.append(instructions) elements.append(Spacer(1, 0.3*inch)) - + # Add questions for question_text, bloom_level in exam_data["questions"]: question = Paragraph(question_text, question_style) @@ -142,18 +147,25 @@ def main(): """Generate all sample exam PDFs.""" print("Generating sample exam PDFs...\n") + # Check if reportlab is available + if not REPORTLAB_AVAILABLE: + print("ERROR: reportlab is required to generate sample PDFs") + print("Install it with: pip install reportlab") + return 1 + # Ensure input directory exists ensure_directories() - # Check if reportlab is available (already imported at module level) # Generate each exam for filename, exam_data in SAMPLE_EXAMS.items(): try: create_sample_pdf(filename, exam_data) except Exception as e: - print("✗ Failed to create {filename}: {error}".format(filename=filename, error=e)) + print("✗ Failed to create {filename}: {error}".format( + filename=filename, error=e)) - print("\n✓ Successfully generated {count} sample exam PDFs".format(count=len(SAMPLE_EXAMS))) + print("\n✓ Successfully generated {count} sample exam PDFs".format( + count=len(SAMPLE_EXAMS))) print("✓ Files saved to: {path}".format(path=get_input_path(''))) print("\nYou can now run: python demo.py") return 0 diff --git a/demo.py b/demo.py index 7b5706b..9b97b5b 100644 --- a/demo.py +++ b/demo.py @@ -12,7 +12,7 @@ # Place your exam PDFs in the input/ folder export GOOGLE_API_KEY=your_api_key_here python demo.py - + # Results will be saved to output/ folder: # - output/charts/ - Visualization charts # - output/logs/ - Log files @@ -44,13 +44,13 @@ async def demo_basic_workflow(): print("\n" + "="*80) print("DEMO 1: Basic Agent Workflow") print("="*80) - + # Ensure directories exist ensure_directories() - + # Setup logging with file output logger = setup_logging(level="INFO", structured=False, log_file="demo_run.log") - + # Initialize session service session_service = InMemorySessionService() await session_service.create_session( @@ -58,29 +58,29 @@ async def demo_basic_workflow(): user_id="demo_user", session_id="demo_session_1" ) - + # Initialize runner runner = Runner( agent=root_agent, app_name="professor_profiler", session_service=session_service ) - + # Create sample PDF in input folder if doesn't exist sample_pdf = get_input_path("physics_2024.pdf") if not sample_pdf.exists(): print(f"\n⚠️ Creating mock PDF at {sample_pdf}") with open(sample_pdf, "w") as f: f.write("Mock PDF content for testing") - + # Run agent with query (use just the filename, tool will look in input/) query = "Analyze the exam paper physics_2024.pdf and tell me what topics to focus on." print(f"\n📝 Query: {query}") print("\n🤖 Agent Response:") print("-" * 80) - + log_agent_event(logger, "query_start", "professor_profiler_agent", query=query) - + async for event in runner.run_async( user_id="demo_user", session_id="demo_session_1", @@ -93,7 +93,7 @@ async def demo_basic_workflow(): response_text = event.content.parts[0].text print(f"\n{response_text}") log_agent_event(logger, "query_complete", "professor_profiler_agent") - + # Show session stats stats = session_service.get_stats() print(f"\n📊 Session Stats: {json.dumps(stats, indent=2)}") diff --git a/google/adk/agents/agent.py b/google/adk/agents/agent.py index cd9062d..2f416a1 100644 --- a/google/adk/agents/agent.py +++ b/google/adk/agents/agent.py @@ -21,7 +21,7 @@ class Agent: - Delegate work to sub-agents - Post-process results via callbacks """ - + def __init__( self, name: str, @@ -37,30 +37,30 @@ def __init__( # Agent identity and model configuration self.name = name self.model = model - + # High-level role and behavioral instructions self.description = description self.instruction = instruction - + # Execution extensions self.tools = tools or [] self.sub_agents = sub_agents or [] - + # Output routing / post-processing metadata self.output_key = output_key self.after_agent_callback = after_agent_callback - + # Model generation parameters (temperature, top_p, etc.) self.kwargs = kwargs - + # Runtime state (initialized later) self.client = None self.context = {} - + def __repr__(self): """Readable representation for debugging and logs.""" return f"Agent(name='{self.name}', model='{self.model}')" - + async def initialize(self, client): """Attach a Gemini client to this agent and all sub-agents. @@ -70,7 +70,7 @@ async def initialize(self, client): self.client = client for sub_agent in self.sub_agents: await sub_agent.initialize(client) - + async def run( self, prompt: str, @@ -88,19 +88,19 @@ async def run( """ if not self.client: raise RuntimeError(f"Agent {self.name} not initialized with client") - + # Store execution-scoped context self.context = context or {} - + # Construct system-level instructions (role, constraints, metadata) system_instruction = self._build_system_instruction() - + # Combine user prompt with structured context full_prompt = self._build_full_prompt(prompt) - + # Prepare Gemini-compatible tool declarations tool_config = self._prepare_tool_config() - + try: # Execute the primary LLM call response = await self._execute_llm( @@ -108,11 +108,11 @@ async def run( system_instruction, tool_config ) - + # Sequentially run sub-agents on the parent response if self.sub_agents: response = await self._execute_sub_agents(response) - + # Optional post-processing hook if self.after_agent_callback: from .callback_context import CallbackContext @@ -120,14 +120,14 @@ async def run( callback_result = self.after_agent_callback(ctx) if callback_result: response = callback_result - + # Standardized agent output envelope return { "agent": self.name, "response": response, "output_key": self.output_key } - + except Exception as e: # Fail safely without crashing the agent runtime return { @@ -135,7 +135,7 @@ async def run( "error": str(e), "output_key": self.output_key } - + def _build_system_instruction(self) -> str: """Assemble the system instruction passed to the LLM. @@ -146,23 +146,23 @@ def _build_system_instruction(self) -> str: - Sub-agent awareness """ parts = [] - + if self.description: parts.append(f"Role: {self.description}") - + if self.instruction: parts.append(f"Instructions: {self.instruction}") - + if self.tools: tool_names = [getattr(t, 'name', 'tool') for t in self.tools] parts.append(f"Available tools: {', '.join(tool_names)}") - + if self.sub_agents: agent_names = [a.name for a in self.sub_agents] parts.append(f"Sub-agents: {', '.join(agent_names)}") - + return "\n\n".join(parts) - + def _build_full_prompt(self, prompt: str) -> str: """Merge the user prompt with structured execution context. @@ -170,16 +170,16 @@ def _build_full_prompt(self, prompt: str) -> str: """ if not self.context: return prompt - + context_str = "\n\nContext:\n" for key, value in self.context.items(): if isinstance(value, (dict, list)): context_str += f"- {key}: {json.dumps(value, indent=2)}\n" else: context_str += f"- {key}: {value}\n" - + return prompt + context_str - + def _prepare_tool_config(self) -> Optional[Dict[str, Any]]: """Convert registered tools into Gemini function declarations. @@ -187,19 +187,19 @@ def _prepare_tool_config(self) -> Optional[Dict[str, Any]]: """ if not self.tools: return None - + tool_declarations = [] for tool in self.tools: if hasattr(tool, 'to_gemini_declaration'): tool_declarations.append(tool.to_gemini_declaration()) - + if not tool_declarations: return None - + return { "function_declarations": tool_declarations } - + async def _execute_llm( self, prompt: str, @@ -214,7 +214,7 @@ async def _execute_llm( - Tool call detection and execution """ from google import genai - + # Model generation parameters config = { "temperature": self.kwargs.get("temperature", 0.7), @@ -222,7 +222,7 @@ async def _execute_llm( "top_k": self.kwargs.get("top_k", 40), "max_output_tokens": self.kwargs.get("max_output_tokens", 2048), } - + # User content payload contents = [ genai_types.Content( @@ -230,23 +230,23 @@ async def _execute_llm( parts=[genai_types.Part.from_text(text=prompt)] ) ] - + # Request configuration generate_kwargs = { "model": self.model, "contents": contents, "config": genai_types.GenerateContentConfig(**config) } - + if system_instruction: generate_kwargs["config"].system_instruction = system_instruction - + if tool_config: generate_kwargs["config"].tools = [tool_config] - + # Invoke Gemini response = await self.client.aio.models.generate_content(**generate_kwargs) - + # Inspect model output for tool calls or text responses if hasattr(response, 'candidates') and response.candidates: candidate = response.candidates[0] @@ -255,12 +255,12 @@ async def _execute_llm( if hasattr(part, 'function_call') and part.function_call: # Execute the requested tool return await self._execute_tool_call(part.function_call) - + # Default to returning textual output return candidate.content.parts[0].text - + return str(response.text) if hasattr(response, 'text') else "" - + async def _execute_tool_call(self, function_call) -> str: """Execute a tool invoked by the LLM. @@ -269,7 +269,7 @@ async def _execute_tool_call(self, function_call) -> str: """ function_name = function_call.name args = dict(function_call.args) if hasattr(function_call, 'args') else {} - + for tool in self.tools: if hasattr(tool, 'name') and tool.name == function_name: if hasattr(tool, 'execute'): @@ -278,9 +278,9 @@ async def _execute_tool_call(self, function_call) -> str: elif hasattr(tool, 'func'): result = tool.func(**args) return json.dumps(result) - + return json.dumps({"error": f"Tool {function_name} not found"}) - + async def _execute_sub_agents(self, parent_response: str) -> str: """Run sub-agents sequentially using the parent agent's response. @@ -288,7 +288,7 @@ async def _execute_sub_agents(self, parent_response: str) -> str: previous agent’s output as its prompt. """ results = [f"[{self.name} Initial Response]\n{parent_response}"] - + for sub_agent in self.sub_agents: result = await sub_agent.run( prompt=parent_response, @@ -296,5 +296,5 @@ async def _execute_sub_agents(self, parent_response: str) -> str: ) response_text = result.get("response", "") results.append(f"\n[{sub_agent.name} Response]\n{response_text}") - + return "\n".join(results) diff --git a/google/adk/agents/callback_context.py b/google/adk/agents/callback_context.py index aa15f05..17b6705 100644 --- a/google/adk/agents/callback_context.py +++ b/google/adk/agents/callback_context.py @@ -5,7 +5,7 @@ class CallbackContext: """Context passed to after_agent_callback functions.""" - + def __init__( self, agent: Any, @@ -15,11 +15,11 @@ def __init__( self.agent = agent self.response = response self.metadata = metadata or {} - + def get_response_text(self) -> str: """Get response as text.""" return str(self.response) - + def to_content(self) -> Content: """Convert response to Content object.""" return Content( diff --git a/google/adk/runners/runner.py b/google/adk/runners/runner.py index 5831a88..ddb16c5 100644 --- a/google/adk/runners/runner.py +++ b/google/adk/runners/runner.py @@ -12,7 +12,7 @@ class RunnerEvent: """Event emitted during agent execution.""" - + def __init__( self, content: Optional[genai_types.Content] = None, @@ -24,18 +24,18 @@ def __init__( self.agent_name = agent_name self._is_final = is_final self.metadata = metadata or {} - + def is_final_response(self) -> bool: """Check if this is the final response.""" return self._is_final - + def __repr__(self): return f"RunnerEvent(agent='{self.agent_name}', is_final={self._is_final})" class Runner: """Execute agents with session management and streaming.""" - + def __init__( self, agent: Any, @@ -49,14 +49,14 @@ def __init__( self.session_service = session_service self.api_key = api_key or os.getenv("GOOGLE_API_KEY") self.kwargs = kwargs - + # Initialize Gemini client if not self.api_key: logger.warning("No GOOGLE_API_KEY found, agent will use mock responses") self.client = None else: self.client = genai.Client(api_key=self.api_key) - + async def run_async( self, user_id: str, @@ -65,7 +65,7 @@ async def run_async( **kwargs ) -> AsyncIterator[RunnerEvent]: """Execute agent and stream results.""" - + # Extract message text message_text = "" if new_message and hasattr(new_message, "parts") and len(new_message.parts) > 0: @@ -74,21 +74,21 @@ async def run_async( message_text = part.text elif hasattr(part, "from_text"): message_text = str(part) - + logger.info(f"Running agent {self.agent.name} for session {session_id}") logger.debug(f"Message: {message_text[:100]}...") - + # Get or create session session = await self.session_service.get_session( app_name=self.app_name, user_id=user_id, session_id=session_id ) - + # Initialize agent with client if self.client: await self.agent.initialize(self.client) - + # Execute agent try: # Yield intermediate events @@ -100,7 +100,7 @@ async def run_async( agent_name=self.agent.name, is_final=False ) - + # Run agent if self.client: result = await self.agent.run( @@ -111,7 +111,7 @@ async def run_async( else: # Mock response if no API key response_text = f"[Mock Response] {self.agent.name} processed: {message_text[:100]}" - + # Update session with result if session: await self.session_service.add_message( @@ -128,7 +128,7 @@ async def run_async( role="assistant", content=response_text ) - + # Yield final response yield RunnerEvent( content=genai_types.Content( @@ -139,7 +139,7 @@ async def run_async( is_final=True, metadata={"session_id": session_id} ) - + except Exception as e: logger.error(f"Error running agent: {e}", exc_info=True) yield RunnerEvent( diff --git a/google/adk/sessions/in_memory_session_service.py b/google/adk/sessions/in_memory_session_service.py index 35a0884..3ca2e95 100644 --- a/google/adk/sessions/in_memory_session_service.py +++ b/google/adk/sessions/in_memory_session_service.py @@ -11,13 +11,13 @@ class InMemorySessionService: """Manage sessions and conversation history in memory.""" - + def __init__(self): # sessions: {app_name: {user_id: {session_id: session_data}}} self._sessions: Dict[str, Dict[str, Dict[str, Dict[str, Any]]]] = defaultdict( lambda: defaultdict(dict) ) - + async def create_session( self, app_name: str, @@ -36,12 +36,12 @@ async def create_session( "context": metadata or {}, "metadata": metadata or {} } - + self._sessions[app_name][user_id][session_id] = session_data logger.info(f"Created session {session_id} for user {user_id}") - + return session_data - + async def get_session( self, app_name: str, @@ -52,9 +52,9 @@ async def get_session( if session_id not in self._sessions[app_name][user_id]: logger.info(f"Session {session_id} not found, creating new one") return await self.create_session(app_name, user_id, session_id) - + return self._sessions[app_name][user_id][session_id] - + async def update_session( self, app_name: str, @@ -65,13 +65,13 @@ async def update_session( """Update session data.""" if session_id not in self._sessions[app_name][user_id]: raise ValueError(f"Session {session_id} not found") - + session = self._sessions[app_name][user_id][session_id] session.update(updates) session["updated_at"] = datetime.now().isoformat() - + return session - + async def add_message( self, app_name: str, @@ -83,21 +83,21 @@ async def add_message( ) -> Dict[str, Any]: """Add a message to session history.""" session = await self.get_session(app_name, user_id, session_id) - + message = { "role": role, "content": content, "timestamp": datetime.now().isoformat(), "metadata": metadata or {} } - + session["messages"].append(message) session["updated_at"] = datetime.now().isoformat() - + logger.debug(f"Added {role} message to session {session_id}") - + return message - + async def get_messages( self, app_name: str, @@ -107,17 +107,17 @@ async def get_messages( ) -> List[Dict[str, Any]]: """Get conversation history for a session.""" session = await self.get_session(app_name, user_id, session_id) - + if not session: return [] - + messages = session.get("messages", []) - + if limit: return messages[-limit:] - + return messages - + async def delete_session( self, app_name: str, @@ -129,9 +129,9 @@ async def delete_session( del self._sessions[app_name][user_id][session_id] logger.info(f"Deleted session {session_id}") return True - + return False - + async def list_sessions( self, app_name: str, @@ -140,7 +140,7 @@ async def list_sessions( """List all sessions for a user.""" sessions = list(self._sessions[app_name][user_id].values()) return sorted(sessions, key=lambda s: s["updated_at"], reverse=True) - + async def update_context( self, app_name: str, @@ -150,15 +150,15 @@ async def update_context( ) -> Dict[str, Any]: """Update session context (for memory/state management).""" session = await self.get_session(app_name, user_id, session_id) - + if "context" not in session: session["context"] = {} - + session["context"].update(context_updates) session["updated_at"] = datetime.now().isoformat() - + return session["context"] - + async def get_context( self, app_name: str, @@ -168,7 +168,7 @@ async def get_context( """Get session context.""" session = await self.get_session(app_name, user_id, session_id) return session.get("context", {}) if session else {} - + def get_stats(self) -> Dict[str, Any]: """Get service statistics.""" total_sessions = sum( @@ -176,14 +176,14 @@ def get_stats(self) -> Dict[str, Any]: for app in self._sessions.values() for users_sessions in app.values() ) - + total_messages = sum( len(session.get("messages", [])) for app in self._sessions.values() for users_sessions in app.values() for session in users_sessions.values() ) - + return { "total_sessions": total_sessions, "total_messages": total_messages, diff --git a/google/adk/tools/function_tool.py b/google/adk/tools/function_tool.py index 6c532d4..5a5b756 100644 --- a/google/adk/tools/function_tool.py +++ b/google/adk/tools/function_tool.py @@ -5,7 +5,7 @@ class FunctionTool: """Wrapper for Python functions to be used as LLM tools.""" - + def __init__( self, func: Optional[Callable] = None, @@ -17,7 +17,7 @@ def __init__( self.name = name or (func.__name__ if func else kwargs.get('name', 'tool')) self.description = description or (func.__doc__ if func else kwargs.get('description', '')) self.kwargs = kwargs - + # Parse function signature if available if self.func: self.signature = inspect.signature(self.func) @@ -25,17 +25,17 @@ def __init__( else: self.signature = None self.parameters = {} - + def _extract_parameters(self) -> Dict[str, Any]: """Extract parameter schema from function signature.""" params = {} - + for param_name, param in self.signature.parameters.items(): param_info = { "type": "string", # Default to string "description": f"Parameter {param_name}" } - + # Try to infer type from annotation if param.annotation != inspect.Parameter.empty: annotation = param.annotation @@ -51,18 +51,18 @@ def _extract_parameters(self) -> Dict[str, Any]: param_info["type"] = "object" elif annotation == list: param_info["type"] = "array" - + # Check if required if param.default == inspect.Parameter.empty: param_info["required"] = True else: param_info["required"] = False param_info["default"] = param.default - + params[param_name] = param_info - + return params - + def to_gemini_declaration(self) -> Dict[str, Any]: """Convert to Gemini function declaration format.""" # Extract required parameters @@ -70,7 +70,7 @@ def to_gemini_declaration(self) -> Dict[str, Any]: name for name, info in self.parameters.items() if info.get("required", False) ] - + # Build parameter schema properties = {} for name, info in self.parameters.items(): @@ -78,7 +78,7 @@ def to_gemini_declaration(self) -> Dict[str, Any]: "type": info["type"], "description": info["description"] } - + declaration = { "name": self.name, "description": self.description or f"Execute {self.name} function", @@ -87,29 +87,29 @@ def to_gemini_declaration(self) -> Dict[str, Any]: "properties": properties, } } - + if required_params: declaration["parameters"]["required"] = required_params - + return declaration - + async def execute(self, **kwargs) -> Any: """Execute the wrapped function.""" if not self.func: raise RuntimeError(f"No function defined for tool {self.name}") - + # Check if function is async if inspect.iscoroutinefunction(self.func): return await self.func(**kwargs) else: return self.func(**kwargs) - + def __call__(self, **kwargs) -> Any: """Allow tool to be called directly.""" if inspect.iscoroutinefunction(self.func): import asyncio return asyncio.create_task(self.execute(**kwargs)) return self.func(**kwargs) - + def __repr__(self): return f"FunctionTool(name='{self.name}')" diff --git a/profiler_agent/memory.py b/profiler_agent/memory.py index af2bdf3..6538cfd 100644 --- a/profiler_agent/memory.py +++ b/profiler_agent/memory.py @@ -10,7 +10,7 @@ class MemoryBank: """Long-term memory storage for agent context.""" - + def __init__(self, storage_path: Optional[str] = None): if storage_path is None: # Default to output directory @@ -18,7 +18,7 @@ def __init__(self, storage_path: Optional[str] = None): self.storage_path = storage_path self.memories: Dict[str, List[Dict[str, Any]]] = defaultdict(list) self.load() - + def load(self): """Load memories from disk.""" if os.path.exists(self.storage_path): @@ -28,7 +28,7 @@ def load(self): self.memories = defaultdict(list, data) except Exception as e: print(f"Warning: Failed to load memory bank: {e}") - + def save(self): """Persist memories to disk.""" try: @@ -36,7 +36,7 @@ def save(self): json.dump(dict(self.memories), f, indent=2) except Exception as e: print(f"Warning: Failed to save memory bank: {e}") - + def add_memory( self, user_id: str, @@ -46,18 +46,18 @@ def add_memory( ) -> str: """ Add a new memory. - + Args: user_id: User identifier memory_type: Type of memory (e.g., 'exam_analysis', 'study_plan', 'preference') content: Memory content tags: Optional tags for categorization - + Returns: Memory ID """ memory_id = self._generate_id(user_id, memory_type, content) - + memory = { "id": memory_id, "user_id": user_id, @@ -68,12 +68,12 @@ def add_memory( "access_count": 0, "last_accessed": None } - + self.memories[user_id].append(memory) self.save() - + return memory_id - + def get_memories( self, user_id: str, @@ -83,41 +83,41 @@ def get_memories( ) -> List[Dict[str, Any]]: """ Retrieve memories for a user. - + Args: user_id: User identifier memory_type: Filter by memory type tags: Filter by tags (must have all tags) limit: Maximum number of memories to return - + Returns: List of matching memories """ user_memories = self.memories.get(user_id, []) - + # Filter by type if memory_type: user_memories = [m for m in user_memories if m["type"] == memory_type] - + # Filter by tags if tags: user_memories = [ m for m in user_memories if all(tag in m.get("tags", []) for tag in tags) ] - + # Sort by most recent and update access count user_memories.sort(key=lambda m: m["created_at"], reverse=True) - + # Update access count for returned memories for memory in user_memories[:limit]: memory["access_count"] += 1 memory["last_accessed"] = datetime.now().isoformat() - + self.save() - + return user_memories[:limit] - + def search_memories( self, user_id: str, @@ -126,34 +126,34 @@ def search_memories( ) -> List[Dict[str, Any]]: """ Search memories by text content. - + Args: user_id: User identifier query: Search query limit: Maximum number of results - + Returns: List of matching memories """ user_memories = self.memories.get(user_id, []) query_lower = query.lower() - + # Simple text-based search matches = [] for memory in user_memories: content_str = json.dumps(memory["content"]).lower() tags_str = " ".join(memory.get("tags", [])).lower() - + if query_lower in content_str or query_lower in tags_str: # Calculate simple relevance score score = content_str.count(query_lower) + tags_str.count(query_lower) * 2 matches.append((score, memory)) - + # Sort by relevance matches.sort(key=lambda x: x[0], reverse=True) - + return [m for _, m in matches[:limit]] - + def update_memory( self, user_id: str, @@ -162,12 +162,12 @@ def update_memory( ) -> bool: """ Update an existing memory. - + Args: user_id: User identifier memory_id: Memory to update updates: Fields to update - + Returns: True if memory was found and updated """ @@ -179,30 +179,30 @@ def update_memory( memory["updated_at"] = datetime.now().isoformat() self.save() return True - + return False - + def delete_memory(self, user_id: str, memory_id: str) -> bool: """Delete a memory.""" user_memories = self.memories.get(user_id, []) initial_count = len(user_memories) - + self.memories[user_id] = [m for m in user_memories if m["id"] != memory_id] - + if len(self.memories[user_id]) < initial_count: self.save() return True - + return False - + def get_summary(self, user_id: str) -> Dict[str, Any]: """Get summary statistics for user's memories.""" user_memories = self.memories.get(user_id, []) - + type_counts = defaultdict(int) for memory in user_memories: type_counts[memory["type"]] += 1 - + return { "total_memories": len(user_memories), "by_type": dict(type_counts), @@ -212,7 +212,7 @@ def get_summary(self, user_id: str) -> Dict[str, Any]: reverse=True )[:5] if user_memories else [] } - + def compact_context( self, user_id: str, @@ -221,42 +221,42 @@ def compact_context( ) -> str: """ Compact memories into a context string for LLM. - + Args: user_id: User identifier memory_types: Types of memories to include max_tokens: Approximate max tokens (rough estimate: 1 token ~= 4 chars) - + Returns: Compacted context string """ user_memories = self.get_memories(user_id, limit=20) - + # Filter by type if specified if memory_types: user_memories = [m for m in user_memories if m["type"] in memory_types] - + # Build context string context_parts = ["Historical Context:"] current_length = len(context_parts[0]) max_chars = max_tokens * 4 # Rough estimate - + for memory in user_memories: memory_str = f"\n- [{memory['type']}] {json.dumps(memory['content'])}" - + if current_length + len(memory_str) > max_chars: break - + context_parts.append(memory_str) current_length += len(memory_str) - + return "\n".join(context_parts) - + def _generate_id(self, user_id: str, memory_type: str, content: Dict) -> str: """Generate unique memory ID.""" data = f"{user_id}:{memory_type}:{json.dumps(content)}:{datetime.now().isoformat()}" return hashlib.md5(data.encode(), usedforsecurity=False).hexdigest()[:16] - + def clear_user_memories(self, user_id: str) -> int: """Clear all memories for a user.""" count = len(self.memories.get(user_id, [])) diff --git a/profiler_agent/observability.py b/profiler_agent/observability.py index bc55356..f27ae75 100644 --- a/profiler_agent/observability.py +++ b/profiler_agent/observability.py @@ -14,7 +14,7 @@ # Configure structured logging class StructuredFormatter(logging.Formatter): """JSON formatter for structured logs.""" - + def format(self, record: logging.LogRecord) -> str: log_data = { "timestamp": datetime.utcnow().isoformat(), @@ -25,7 +25,7 @@ def format(self, record: logging.LogRecord) -> str: "function": record.funcName, "line": record.lineno } - + # Add extra fields if present if hasattr(record, "trace_id"): log_data["trace_id"] = record.trace_id @@ -33,37 +33,37 @@ def format(self, record: logging.LogRecord) -> str: log_data["agent_name"] = record.agent_name if hasattr(record, "duration_ms"): log_data["duration_ms"] = record.duration_ms - + # Add exception info if present if record.exc_info: log_data["exception"] = self.formatException(record.exc_info) - + return json.dumps(log_data) def setup_logging(level: str = "INFO", structured: bool = False, log_file: Optional[str] = None): """ Setup logging configuration. - + Args: level: Log level (INFO, DEBUG, WARNING, ERROR) structured: Use JSON structured logging log_file: Optional log file name (saved to output/logs/) """ log_level = getattr(logging, level.upper(), logging.INFO) - + # Create logger logger = logging.getLogger() logger.setLevel(log_level) - + # Remove existing handlers for handler in logger.handlers[:]: logger.removeHandler(handler) - + # Create console handler console_handler = logging.StreamHandler() console_handler.setLevel(log_level) - + # Set formatter if structured: formatter = StructuredFormatter() @@ -72,10 +72,10 @@ def setup_logging(level: str = "INFO", structured: bool = False, log_file: Optio '%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) - + console_handler.setFormatter(formatter) logger.addHandler(console_handler) - + # Add file handler if requested if log_file: log_path = get_output_path(log_file, "logs") @@ -84,21 +84,21 @@ def setup_logging(level: str = "INFO", structured: bool = False, log_file: Optio file_handler.setFormatter(formatter) logger.addHandler(file_handler) logger.info(f"Logging to file: {log_path}") - + return logger class Tracer: """Distributed tracing for agent operations.""" - + def __init__(self): self._traces: Dict[str, Dict[str, Any]] = {} self._lock = threading.Lock() - + def start_trace(self, operation: str, metadata: Optional[Dict] = None) -> str: """Start a new trace.""" trace_id = str(uuid.uuid4()) - + with self._lock: self._traces[trace_id] = { "trace_id": trace_id, @@ -107,9 +107,9 @@ def start_trace(self, operation: str, metadata: Optional[Dict] = None) -> str: "metadata": metadata or {}, "spans": [] } - + return trace_id - + def add_span( self, trace_id: str, @@ -126,19 +126,19 @@ def add_span( "timestamp": time.time(), "metadata": metadata or {} }) - + def end_trace(self, trace_id: str) -> Dict[str, Any]: """End a trace and return the trace data.""" with self._lock: if trace_id not in self._traces: return {} - + trace = self._traces[trace_id] trace["end_time"] = time.time() trace["total_duration_ms"] = (trace["end_time"] - trace["start_time"]) * 1000 - + return trace - + def get_trace(self, trace_id: str) -> Optional[Dict[str, Any]]: """Get trace data by ID.""" with self._lock: @@ -147,39 +147,39 @@ def get_trace(self, trace_id: str) -> Optional[Dict[str, Any]]: class MetricsCollector: """Collect and aggregate metrics.""" - + def __init__(self): self._counters: Dict[str, int] = defaultdict(int) self._gauges: Dict[str, float] = {} self._histograms: Dict[str, list] = defaultdict(list) self._lock = threading.Lock() - + def increment(self, metric: str, value: int = 1, tags: Optional[Dict] = None): """Increment a counter.""" key = self._make_key(metric, tags) with self._lock: self._counters[key] += value - + def gauge(self, metric: str, value: float, tags: Optional[Dict] = None): """Set a gauge value.""" key = self._make_key(metric, tags) with self._lock: self._gauges[key] = value - + def histogram(self, metric: str, value: float, tags: Optional[Dict] = None): """Add a value to histogram.""" key = self._make_key(metric, tags) with self._lock: self._histograms[key].append(value) - + def _make_key(self, metric: str, tags: Optional[Dict] = None) -> str: """Create metric key with tags.""" if not tags: return metric - + tag_str = ",".join(f"{k}={v}" for k, v in sorted(tags.items())) return f"{metric}{{{tag_str}}}" - + def get_metrics(self) -> Dict[str, Any]: """Get all metrics.""" with self._lock: @@ -194,14 +194,14 @@ def get_metrics(self) -> Dict[str, Any]: "min": min(values), "max": max(values) } - + return { "counters": dict(self._counters), "gauges": dict(self._gauges), "histograms": histogram_stats, "timestamp": datetime.utcnow().isoformat() } - + def reset(self): """Reset all metrics.""" with self._lock: @@ -222,15 +222,15 @@ def decorator(func): async def async_wrapper(*args, **kwargs): trace_id = tracer.start_trace(operation_name) start_time = time.time() - + try: result = await func(*args, **kwargs) duration_ms = (time.time() - start_time) * 1000 - + tracer.add_span(trace_id, operation_name, duration_ms, {"status": "success"}) metrics.histogram(f"{operation_name}.duration_ms", duration_ms) metrics.increment(f"{operation_name}.success") - + return result except Exception as e: duration_ms = (time.time() - start_time) * 1000 @@ -239,20 +239,20 @@ async def async_wrapper(*args, **kwargs): raise finally: tracer.end_trace(trace_id) - + @wraps(func) def sync_wrapper(*args, **kwargs): trace_id = tracer.start_trace(operation_name) start_time = time.time() - + try: result = func(*args, **kwargs) duration_ms = (time.time() - start_time) * 1000 - + tracer.add_span(trace_id, operation_name, duration_ms, {"status": "success"}) metrics.histogram(f"{operation_name}.duration_ms", duration_ms) metrics.increment(f"{operation_name}.success") - + return result except Exception as e: duration_ms = (time.time() - start_time) * 1000 @@ -261,14 +261,14 @@ def sync_wrapper(*args, **kwargs): raise finally: tracer.end_trace(trace_id) - + # Return appropriate wrapper based on function type import asyncio if asyncio.iscoroutinefunction(func): return async_wrapper else: return sync_wrapper - + return decorator @@ -279,6 +279,6 @@ def log_agent_event(logger: logging.Logger, event_type: str, agent_name: str, ** "event_type": event_type, **kwargs } - + message = f"Agent event: {event_type} - {agent_name}" logger.info(message, extra=extra) diff --git a/profiler_agent/paths.py b/profiler_agent/paths.py index 591cf92..021215b 100644 --- a/profiler_agent/paths.py +++ b/profiler_agent/paths.py @@ -27,10 +27,10 @@ def ensure_directories(): def get_input_path(filename: str) -> Path: """ Get full path for an input file. - + Args: filename: Name of the input file - + Returns: Path object for the input file """ @@ -41,39 +41,39 @@ def get_input_path(filename: str) -> Path: def get_output_path(filename: str, subfolder: str = "") -> Path: """ Get full path for an output file. - + Args: filename: Name of the output file subfolder: Optional subfolder (charts, logs, reports) - + Returns: Path object for the output file """ ensure_directories() - + if subfolder: base_dir = OUTPUT_DIR / subfolder base_dir.mkdir(parents=True, exist_ok=True) return base_dir / filename - + return OUTPUT_DIR / filename def list_input_files(extension: str = ".pdf") -> list: """ List all files in the input directory with given extension. - + Args: extension: File extension to filter (default: .pdf) - + Returns: List of Path objects for matching files """ ensure_directories() - + if not extension.startswith("."): extension = f".{extension}" - + return list(INPUT_DIR.glob(f"*{extension}")) diff --git a/profiler_agent/tools.py b/profiler_agent/tools.py index 9317c31..218b83f 100644 --- a/profiler_agent/tools.py +++ b/profiler_agent/tools.py @@ -18,25 +18,20 @@ except ImportError: plt = None -try: - import pandas as pd # noqa: F401 - imported for optional functionality -except ImportError: - pd = None - def read_pdf_content(file_path: str) -> dict: """ Extract text content from a PDF file. - + Args: file_path: Path to the PDF file (relative to input/ folder or absolute path) - + Returns: Dictionary with filename and content, or error message """ if PdfReader is None: return {"error": "pypdf library is not installed."} - + # If path is relative (no directory separator), look in input/ folder path = Path(file_path) if not path.is_absolute() and not os.path.exists(file_path): @@ -48,19 +43,19 @@ def read_pdf_content(file_path: str) -> dict: return { "error": f"File not found: {file_path}. Please place exam PDFs in the 'input/' folder." } - + if not os.path.exists(file_path): return {"error": f"File not found: {file_path}"} - + try: reader = PdfReader(file_path) text = "" page_count = len(reader.pages) - + for page_num, page in enumerate(reader.pages, 1): page_text = page.extract_text() text += f"\n--- Page {page_num} ---\n{page_text}" - + return { "filename": os.path.basename(file_path), "content": text, @@ -74,10 +69,10 @@ def read_pdf_content(file_path: str) -> dict: def analyze_statistics(questions_data: str) -> dict: """ Analyze statistical patterns in exam questions. - + Args: questions_data: JSON string or dict containing tagged questions - + Returns: Statistical analysis including frequency distributions """ @@ -87,27 +82,27 @@ def analyze_statistics(questions_data: str) -> dict: data = json.loads(questions_data) else: data = questions_data - + # Extract topics and bloom levels topics = [] bloom_levels = [] - + if isinstance(data, dict): questions = data.get("questions", []) elif isinstance(data, list): questions = data else: return {"error": "Invalid input format"} - + for q in questions: if isinstance(q, dict): topics.append(q.get("topic", "Unknown")) bloom_levels.append(q.get("bloom_level", "Unknown")) - + # Calculate statistics topic_freq = Counter(topics) bloom_freq = Counter(bloom_levels) - + return { "total_questions": len(questions), "topic_distribution": dict(topic_freq), @@ -135,37 +130,37 @@ def visualize_trends( ) -> dict: """ Create visualizations for exam trends. - + Args: statistics: JSON string containing statistical data output_path: Path to save the chart (saved to output/charts/ by default) chart_type: Type of chart ('bar', 'pie', 'line') - + Returns: Dictionary with chart path and metadata """ if plt is None: return {"error": "matplotlib library is not installed"} - + try: # Use output/charts directory if only filename provided if not os.path.dirname(output_path): output_path = str(get_output_path(output_path, "charts")) - + # Parse statistics if isinstance(statistics, str): stats = json.loads(statistics) else: stats = statistics - + # Create figure with subplots fig, axes = plt.subplots(1, 2, figsize=(14, 6)) - + # Plot 1: Topic Distribution if "topic_distribution" in stats: topics = list(stats["topic_distribution"].keys()) counts = list(stats["topic_distribution"].values()) - + if chart_type == "bar": axes[0].bar(topics, counts, color='skyblue') axes[0].set_xlabel('Topics') @@ -175,34 +170,34 @@ def visualize_trends( elif chart_type == "pie": axes[0].pie(counts, labels=topics, autopct='%1.1f%%') axes[0].set_title('Topic Distribution') - + # Plot 2: Bloom's Taxonomy Distribution if "bloom_distribution" in stats: blooms = list(stats["bloom_distribution"].keys()) bloom_counts = list(stats["bloom_distribution"].values()) - + axes[1].bar(blooms, bloom_counts, color='lightcoral') axes[1].set_xlabel('Bloom\'s Level') axes[1].set_ylabel('Frequency') axes[1].set_title('Cognitive Complexity Distribution') axes[1].tick_params(axis='x', rotation=45) - + plt.tight_layout() - + # Ensure output directory exists os.makedirs(os.path.dirname(output_path) if os.path.dirname(output_path) else ".", exist_ok=True) - + # Save figure plt.savefig(output_path, dpi=150, bbox_inches='tight') plt.close() - + return { "chart_path": output_path, "chart_type": chart_type, "success": True, "message": f"Chart saved to {output_path}" } - + except Exception as e: return {"error": f"Failed to create visualization: {str(e)}"} @@ -210,18 +205,18 @@ def visualize_trends( def compare_exams(exam_files: List[str]) -> dict: """ Compare multiple exam papers to identify trends over time. - + Args: exam_files: List of PDF file paths to compare - + Returns: Comparison analysis with trends """ if not exam_files: return {"error": "No exam files provided"} - + results = [] - + for file_path in exam_files: content = read_pdf_content(file_path) if "error" not in content: @@ -230,7 +225,7 @@ def compare_exams(exam_files: List[str]) -> dict: "page_count": content.get("page_count", 0), "content_length": len(content.get("content", "")) }) - + return { "total_exams": len(results), "exams_analyzed": results, @@ -241,13 +236,13 @@ def compare_exams(exam_files: List[str]) -> dict: def list_available_exams() -> dict: """ List all PDF files available in the input directory. - + Returns: Dictionary with list of available exam files """ try: pdf_files = list_input_files(".pdf") - + return { "count": len(pdf_files), "files": [f.name for f in pdf_files], diff --git a/tests/test_agent.py b/tests/test_agent.py index 7badce4..097b0db 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -26,11 +26,11 @@ async def test_agent_initialization(): """Test agent initialization.""" print("TEST 1: Agent Initialization") print("-" * 60) - + assert root_agent.name == "professor_profiler_agent" assert len(root_agent.sub_agents) == 3 assert len(root_agent.tools) > 0 - + print(f"✅ Root agent: {root_agent.name}") print("✅ Sub-agents: {sub_agents}".format(sub_agents=[a.name for a in root_agent.sub_agents])) print("✅ Tools: {tools}".format(tools=[t.name for t in root_agent.tools]))