From d84d8336943777d939d427f0102bdf5538bd2b0d Mon Sep 17 00:00:00 2001 From: "cristiano.hoshikawa" Date: Wed, 2 Sep 2026 13:04:56 -0300 Subject: [PATCH] nova funcionalidade: reconciliacao temporal --- .../__pycache__/__init__.cpython-313.pyc | Bin 1502 -> 1501 bytes .../__pycache__/base.cpython-313.pyc | Bin 1376 -> 1375 bytes .../__pycache__/config_loader.cpython-313.pyc | Bin 9161 -> 9160 bytes .../__pycache__/custom_rails.cpython-313.pyc | Bin 4405 -> 4404 bytes .../framework_llm_client.cpython-313.pyc | Bin 19674 -> 19673 bytes .../__pycache__/llm_rails.cpython-313.pyc | Bin 5924 -> 5923 bytes .../output_supervisor.cpython-313.pyc | Bin 24307 -> 24306 bytes .../parallel_executor.cpython-313.pyc | Bin 21863 -> 21862 bytes .../__pycache__/pipeline.cpython-313.pyc | Bin 10890 -> 10889 bytes .../__pycache__/rail_action.cpython-313.pyc | Bin 655 -> 654 bytes .../__pycache__/rail_decision.cpython-313.pyc | Bin 1606 -> 1605 bytes .../__pycache__/rail_result.cpython-313.pyc | Bin 1005 -> 1004 bytes .../__pycache__/rails.cpython-313.pyc | Bin 42854 -> 43324 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 2995 -> 2994 bytes .../__pycache__/_compat.cpython-313.pyc | Bin 2446 -> 2445 bytes .../contestation_validation.cpython-313.pyc | Bin 2178 -> 2177 bytes .../__pycache__/contracts.cpython-313.pyc | Bin 6902 -> 6901 bytes .../__pycache__/input_size.cpython-313.pyc | Bin 3650 -> 3649 bytes .../__pycache__/llm_adapter.cpython-313.pyc | Bin 3800 -> 3799 bytes .../__pycache__/llm_client.cpython-313.pyc | Bin 7455 -> 7454 bytes .../__pycache__/llm_rails.cpython-313.pyc | Bin 8895 -> 8894 bytes .../output_sanitization.cpython-313.pyc | Bin 13911 -> 13910 bytes .../__pycache__/pipeline.cpython-313.pyc | Bin 12662 -> 12661 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 474 -> 473 bytes .../__pycache__/_context.cpython-313.pyc | Bin 5872 -> 5871 bytes .../ausencia_oferta_proativa.cpython-313.pyc | Bin 12206 -> 12205 bytes .../__pycache__/coerencia.cpython-313.pyc | Bin 4158 -> 4157 bytes .../__pycache__/dlex_in.cpython-313.pyc | Bin 1305 -> 1304 bytes .../__pycache__/dlex_out.cpython-313.pyc | Bin 1816 -> 1815 bytes .../__pycache__/fallback.cpython-313.pyc | Bin 17653 -> 17652 bytes .../__pycache__/fraseologia.cpython-313.pyc | Bin 7634 -> 7633 bytes .../__pycache__/out_of_scope.cpython-313.pyc | Bin 17494 -> 17493 bytes .../prompts/__pycache__/pinj.cpython-313.pyc | Bin 8425 -> 8424 bytes .../__pycache__/ragsec.cpython-313.pyc | Bin 1121 -> 1120 bytes .../__pycache__/revprec.cpython-313.pyc | Bin 3151 -> 3150 bytes .../prompts/__pycache__/tox.cpython-313.pyc | Bin 645 -> 644 bytes .../toxicidade_output.cpython-313.pyc | Bin 929 -> 928 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 1664 -> 1663 bytes .../rails/__pycache__/alcada.cpython-313.pyc | Bin 5000 -> 4999 bytes .../rails/__pycache__/anatel.cpython-313.pyc | Bin 9149 -> 9148 bytes .../__pycache__/confirmation.cpython-313.pyc | Bin 11043 -> 11042 bytes .../rails/__pycache__/dlex_in.cpython-313.pyc | Bin 3280 -> 3279 bytes .../__pycache__/dlex_out.cpython-313.pyc | Bin 3332 -> 3331 bytes .../rails/__pycache__/ragsec.cpython-313.pyc | Bin 5463 -> 5462 bytes .../rails/__pycache__/revprec.cpython-313.pyc | Bin 5471 -> 5470 bytes .../rails/__pycache__/tox.cpython-313.pyc | Bin 8294 -> 8293 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 534 -> 533 bytes .../rules/__pycache__/alcada.cpython-313.pyc | Bin 1882 -> 1881 bytes .../__pycache__/pinj_patterns.cpython-313.pyc | Bin 3731 -> 3730 bytes .../__pycache__/tox_blocklist.cpython-313.pyc | Bin 1507 -> 1506 bytes .../src/agent_framework/guardrails/rails.py | 26 +- .../__pycache__/__init__.cpython-313.pyc | Bin 349 -> 348 bytes .../__pycache__/agent_runtime.cpython-313.pyc | Bin 185641 -> 200597 bytes .../transaction_parameters.cpython-313.pyc | Bin 11118 -> 14476 bytes .../agent_framework/runtime/agent_runtime.py | 298 +++++++++++++++++- .../runtime/transaction_parameters.py | 182 +++++++---- 56 files changed, 425 insertions(+), 81 deletions(-) diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/__init__.cpython-313.pyc index d0b2b90738a03416fa836880cb94d155e77983fe..bc07267eb827d1aee88907ef46a6e8f889f788d2 100644 GIT binary patch delta 36 qcmcb|eV3d2GcPX}0}w>I%-G2Nk%hZbKR2&LKSkd+*mE)m>oowzMhg7^ delta 37 rcmcc1eUF>_GcPX}0}zCtowkwtBMVQJer{fgezLw}acS{ncGhbE-I%-G1y%fwx(pPN^rpQ7&@?73Nm$%YvKz!M3# delta 38 scmcc5^?-}}GcPX}0}zCtowkvimx-rLKR2&LKUv?gxU_h)GLsE60NJ(*?*IS* diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/config_loader.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/config_loader.cpython-313.pyc index 750a7f1f5e827af5e957ed077ceffa4cd70512da..868b18d1c68ef28ae7b7788843ff58f67e7b3512 100644 GIT binary patch delta 37 rcmX@I%-G27z{FjrpPN^rpQ7&@?72CdsYnq3&-e<) delta 38 scmX@%e$t)$GcPX}0}zCtowkwNfr+PHKR2&LKUv?gxU_h47*mlV0O}(P{r~^~ diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/custom_rails.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/custom_rails.cpython-313.pyc index abe2d9d7931fd9faec209e02a7bc0e2959a47dea..5297e5ad4953625c7f5d11fa71b9483e3c353f83 100644 GIT binary patch delta 37 rcmdn0v_*;gGcPX}0}w>I%-G1C#=>2zpPN^rpQ7&@?76v?Wd{!c#5D@k delta 38 scmdm@v{i}wGcPX}0}zCtowkuXjfJO9KR2&LKUv?gxU_h44a*K50Nxc03;+NC diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/framework_llm_client.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/framework_llm_client.cpython-313.pyc index 1c44d7be708b26345ab95b519bf56568302baeda..6a077d2e3b0467ea95eedd50ac46848b3dc5d928 100644 GIT binary patch delta 39 tcmcaLlkw(EM()qNyj%=G5a}{wBX=JscdLGGUWtB+zHhMS=B1pb&H(p%3?u*m delta 40 ucmcaPlkwI}M()qNyj%=G5Po*rM(#dNo;Lm5yb}FneaGU`;>}AqO`QP}*9}|% diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/llm_rails.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/llm_rails.cpython-313.pyc index 3e23168589e04dd8d4421607ae797f85065730f9..70803d59f104dd23c8029ee18eda570ab42132de 100644 GIT binary patch delta 37 rcmZ3Yw^)z+GcPX}0}w>I%-G01nVGvvKR2&LKSkd+*mLtL<|<(T#s&)L delta 38 scmZ3iw?vQoGcPX}0}zCtowkvCGBZ!Ler{fgezLw}acS}9mCRMb0N@1-AOHXW diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/output_supervisor.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/output_supervisor.cpython-313.pyc index 863d19cd864fc7ea77c66ed77dcb98566b17ec1d..c62f7b427e4f20701c0f23fb7bf4fe6c18a25b1c 100644 GIT binary patch delta 39 tcmeyom+{kHM()qNyj%=G5a}{wBlmYM?k4@*yb}Esecxcu&3xQXg8>ic4KM%z delta 40 vcmeygm+|vnM()qNyj%=G5Po*rM(*!iJk9#Kc_sSE`i{k=#hZD#p9TW}EEW!E diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/parallel_executor.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/parallel_executor.cpython-313.pyc index e4bd26ef64c4347c1838419e5787ea09aa1aee86..dd9c3d8e84ab119f8abf8d43b3830397bd3431fa 100644 GIT binary patch delta 39 tcmaF9it*VhM()qNyj%=G5a}{wBexqXcawf@UWtB+zHhMS=2%v)008*i3#R}8 delta 40 ucmaF1it+g>M()qNyj%=G5Po*rMs7D&o@V{ryb}FneaGU`;>|IvTmb+SCk)>J diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/pipeline.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/pipeline.cpython-313.pyc index 063409316710bad40f31a1e3fff6ba1e1cfb7d1d..49c70be651ff6bd9e8d04c9676ae6ef38066c70e 100644 GIT binary patch delta 37 rcmeAQ?F{Aq%*)Hg00faPGd6NRX6CNc&&?~*Pto@c_T2o7d4d`M(7y}H delta 38 scmeAS?F!}o%*)Hg00iM@r)}hZ%*<1zpPN^rpRDg#Tw1*OC-Vd~0P80W0ssI2 diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rail_action.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rail_action.cpython-313.pyc index 3dc9f8ef64a804c207612a492fa40ba2b16434ad..93052a6e8103fc2547c304dcdfaf3d2198f8ddd3 100644 GIT binary patch delta 37 rcmeBY?PKNs%*)Hg00faPGd6PPGjiAH=jN5@r|A0zdv0!HI%-G0X&dgn>pPN^rpQ7&@?76v*c?S~!#BmDY delta 38 scmX@gbBu@kGcPX}0}zCtowkv?oSCOyKR2&LKUv?gxU_h4FY^v20Nz*(82|tP diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rail_result.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rail_result.cpython-313.pyc index 4a31c5dc9ca03a0f9f006535d53c6db046e9321e..67dcd0c3d0071a67e1fdedad6ccec30942b24954 100644 GIT binary patch delta 37 rcmaFM{)V0VGcPX}0}w>I%-G1C%*0)zpPN^rpQ7&@?76v$shAM}%YO>9 delta 38 scmaFE{+6BlGcPX}0}zCtowkuXnTe-XKR2&LKUv?gxU_h4B~vjY0OgYl>i_@% diff --git a/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rails.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/__pycache__/rails.cpython-313.pyc index 8cb01ecc497c43b50d6f0d970b21568197e6da01..0dbd89ea93dcfd053c5674d3e89c87f550cb12b9 100644 GIT binary patch delta 2549 zcmZWrdsI~C5x;ZyA?${Q2q@wzmlX+1Q$Rry(5MK=LlE2rE77vK?p|nQB0Mj zP%E(~Q70;qR#20k;;F|b_oVGb@whmXz<*6;{TnR@UbZ-l=FZ%9e<;_JTkEV+eMVcBIgZN2Oa z04R+hG=ej|F~(_d+H!Qx*djxQ5ELc;_zf*WjIi#h#X_Y;)BnKoG=7x49n&S1hFgX}vVRtCZ6jzJv^|=Bair+iI zcgL=0wLBrK;}A6|ES?3JE7aAYL=6h#G8PtQz=OE9urehYTfW7ubcDPCe^gHt=H*Uy zGs+Jqim!rSTtkBmXg7q;YB%jJ}O-1ulVWaIRrrtWQ` zFuN6Qdq9_Hxt+t|?nI1`Fk@Uuh_y_Zgd{1FJmWyqo~B5OH8R~3Nt<&mePP6wdD)gZ z6g+CbWGi^zR&aTiG?X~7X2>%X>~FpuYe=1X+iXs<+!4%imU~~Ad8GS6n&Wp%9;#jh zZ`y5D>H6Qs#}hWrF3sS&(;`%5>QC!R4191VHnS>QIFk!iS^6`1dXfuD^10z$7VyEj zY*TfS?p#4}b%Aa)2e>?%OKzhD29k?()r;_rqB2lWU+j-_^UaMKR@c=f5xU1N{&pM2MY zKg0CpsrdKC^QL>mCb)y6t8H*k{dDyj5G|rwv1TX06@0JhCzd?!!q#w36gIQd>W;_% z2Egy)x-o!9v95VLY```(dHl+oBhF%)Si+$=@Pg5y3(N zZ5FjyH!#a=pfLeolfqC!)EDT&Qb`ft2S~?1#Pc7vLPf{Y2hx7!!IJhZO^iY~|e1J`dJ!%GfAI$AU z@DqHtHzda}F7f&p06GzUWX$56eklbfScH^@F;*zNVp)Kpx|8*F1? zZfLNJdItDfz7VA@A<2!`=cw*>ptH@e@{ zK^Z>XV>6a>bu=Hp-6P?-oUru0;)@aEDK^8(9{e#~_i7?Q*b9@kC=f zSRfQ|2YgXoo6^M&q1>Bf(mV-XRl9rdiN+R+^rHII_m{!sN%xRq6%Y3Hlm5fb#W04m z`@+^&Ipu3sa{4)kF+9;H!7uQ=zGT>nH~U%*zvkL^(DBR_xTfaqI%G)D>i2bWn7s7r z)!n}YaN>cz>);el>}{N<%`uVIvyfme0fk|Q38?=?jo}WD98%avd|-c&e@K$>Sxlb1 zLNDSlE;pVf?kT*3Zupbh^!#F-=_F|guHoSWl6Vp@zBmQP4mjf-yr`p79OVrzM|lm~ zgOBxB@V@!I{$g0I{-(ba%u#Z^NDx8G!Rg$Xc`(hWX)Ez?V+y(tO3;M82X|UO{XSa4D^hbD<9b}RDHASfx8ec?BXD5nz+CadWa(TVNCKDoj{&_7TJip&An&#v zgA39lv`Q1pqm5WV&_JNI*eJoNUpv6;^Re|KSs1&ToV*^&WcNdgQG=(wH zQjhpRe4yr|X;k_eHOD_x7?n%{G9Rm%rCCinji$|%*7TjrG*jJ~{c-Pkedm1N`3|QC zg!{dM=dUlms8jLBdU?y62e8vKOB_}gS>

7Dpz@E}2z`#&Me>h6-0T2yX}D^oU{X zoVi?$Sy5J8{gM;(=#%G0QPQ!+vQTWlck(u)aRd5+R zS`2Dm&qov0Ub^4qI3DqxqzH=%&YFxCgXWx<7cn>`l#9*_U9mJ$VuI;hAJJ6tZ8ESAfu?Lhp(|T{jsK=3(oMDCpi4G8=tT{a_7? zNDUC+YfPDSRBYyZEMLPTRy__+d#bxdK7=*sV==-loMNemJ?yCEI>6^_?VLpdoWY;d zUxF(boS}z{7?<(3*hw+Eu_k^pdyuhJn9$4pdG6^H>o_5kkVW{OFq*K2@C_cBmjMs4 zvmpreSru@OHD%?(h@ZI5?NMB5ljW)sXZe!F(!;@$?vWE=5BlY#!96PJP>v7WWv6nk zf%k8u{&)0_yav&&aW1Pa!7%G;_z@3VAL^cx72z)K$v47N)|Hw#s(y5Sw^Gi_?N}2e$bdGl>c_^Et?9TwEmqiDD%hxn%CGwU2XK)91 zm4!kYc9m@b6I)QO5qQkPH+T4Dk?|D5RDzOD8hTX(d8p`{z^jX?_yGhJAcTy@*s56| zv6WR_0^gGv)ysks$nY9%jGjYQa~KROr2vv7qPsc@>Ty`jfaoMU8Q-td<6kv;sAK+o zVW_3WbcLi-fG#;)C>&hs=cSYIJ9!i|us`H}@F*uEJK;yX>aq=~;;+0{5-_1Q5E8MV zb}BR>){ccGc%t?-V+~ikhnL$bD_zBQhs|kU;wY5ta)ngQ6*(q>=VGce3ogJTHO3KA zBbj@+9K|J)(`I)#mnpf%!D{TOyREC|T8Yqrol6Zu{XYrD!+JJ;89)0LWc3?iBTIeD z19WsVB*J=Ju-qUt|6@G~&n_PcTiNyHr2=fkgoaD7gL$kh1lGoA-Ph2|#Pz$h-b!bFN&yCMF#BZFNq`Kj z+FZa#!)KdwCn~x6Qi$ngC|oKRGeE|TDipLl=|`cju7|?1KVGPS-7Bmt$!ak&oDt*+je?xB=GK1 zB3#91?Isvddi*klBqZY8ongY|!KP^0nFQ(V@=lE!7U94ina|dl9nsund52zXBA2V! z-eKnAOC9<#Rua&GyS?m=T3e-kaiPOiRU+AorMslMkK*2B{vW}z_e0R#APFHI|J-MQ z8yLL5d(>m9X^50dHE9W{gva>%{!p=v9OkpJADo8dZW1daT^XD-^OR{YcuSS*pme#? pxXQ69=cH`8Qt1sGbFhGqMr=5E$15Lri(8(k=U diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/__init__.cpython-313.pyc index 57f8c4542d823419bc78cb51c051417de7b08ab0..715230e57f7d0b450a19bedc8256c74252cba0d3 100644 GIT binary patch delta 37 rcmdlizDb<>GcPX}0}w>I%-F~sz{%aBpPN^rpQ7&@?72Ce(}xKFzMu*z delta 38 scmdlazFD06GcPX}0}zCtowkuXfRm?HKR2&LKUv?gxU_h48mA8v0N7j$VE_OC diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/_compat.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/_compat.cpython-313.pyc index 20682da7bb6f8e411924e7e17ad7331b6034f6c6..4d7ec5293811e4addbe3a4d1a072f1b14b42769c 100644 GIT binary patch delta 37 rcmeAZ?iJ?#%*)Hg00faPGd6P1VdHMr&&?~*Pto@c_T0Rg?KTqty2A?h delta 38 scmeAb?i1$z%*)Hg00iM@r)}h(!^YF1pPN^rpRDg#Tw1(&6WeVj0MvC1F8}}l diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/contestation_validation.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/contestation_validation.cpython-313.pyc index e88ed6e5bd2da5259d7b84913ba03e1d69d22ebd..ac2b9d0892605011c7935ffe13ab9719ff671ada 100644 GIT binary patch delta 37 rcmZn?Y!u}F%*)Hg00faPGd6Oov2st=&&?~*Pto@c_S|gC>dFiNtNjUR delta 38 scmZn^Y!c-D%*)Hg00iM@r)}g`W96BmpPN^rpRDg#Tw1)@hSilB0L3f{pa1{> diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/contracts.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/contracts.cpython-313.pyc index 2e692edbd55785466e6c15d1c72d616381a30563..c5bcaafc4319105428670a5510c3000483d114ec 100644 GIT binary patch delta 37 rcmexn`qh;CGcPX}0}w>I%-G0%ijBKfKR2&LKSkd+*mLtkHc=4(kb5Mr+GcPX}0}w>I%-G2Nmxa4cKR2&LKSkd+*mJW8s~#r+&qfN3 delta 38 scmX>ob4Z5!GcPX}0}zCtowkwtFAGn*er{fgezLw}acS{pVOBj(0O`~V#Q*>R diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_adapter.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_adapter.cpython-313.pyc index 9772a2ded6f2e8ceba2122fc7f6a53225d75cfca..df23f355650fb7952bf91ec532fba9bf41687cf6 100644 GIT binary patch delta 37 rcmca1dtH|MGcPX}0}w>I%-G0Xz`@I%-F~s#lhXCpPN^rpQ7&@?76v+W1R>9#90c@ delta 38 scmbPdHQ$Q+GcPX}0}zCtowkuXii4+JKR2&LKUv?gxU_h40mnKK0Ny?f2LJ#7 diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_rails.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/llm_rails.cpython-313.pyc index fcdc277ac7b95a90672b7e4ad851d479d2cb1294..8b9f7599029680bc86942b628d4438211f0474dd 100644 GIT binary patch delta 37 rcmdn*y3dvSGcPX}0}w>I%-G0n$jsfUpPN^rpQ7&@?77*4xm+Fq%g_p( delta 38 scmdnzy5E)iGcPX}0}zCtowkwNkeR1VKR2&LKUv?gxU_h)J9D`_0Oi~Z)Bpeg diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/output_sanitization.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/output_sanitization.cpython-313.pyc index a36bce592cbe6c0296e9575facd16d35449f8e81..fb48f8ca3aeb0f4fff944978aafa48b6fcc70cee 100644 GIT binary patch delta 37 rcmcbfb1jGaGcPX}0}w>I%-F~s&&l1dpPN^rpQ7&@?76v&^Pvs^<68^^ delta 38 scmcbXb3KRqGcPX}0}zCtowkuXo|9*Ser{fgezLw}acS}9QqG4u0RAHlI{*Lx diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/pipeline.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/__pycache__/pipeline.cpython-313.pyc index daa206a97acdf592ee3d7a8fa1bf052e5395541c..1b50e7c17d260347ee59d8e7711dd5928d87d54b 100644 GIT binary patch delta 37 rcmeyC^fih5GcPX}0}w>I%-G1SD!|>MpPN^rpQ7&@?77)Sz)BGS;;Rb^ delta 38 scmeyG^eu_|GcPX}0}zCtowku%Re+~eKR2&LKUv?gxU_h)wSbi(0R3MKLI3~& diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/__init__.cpython-313.pyc index 24c0300fb4635484c3c12be9d42734fc314a319d..a1c0ae305a27df0d2bc176fe80f54529231552cb 100644 GIT binary patch delta 36 qcmcb`e3O~`GcPX}0}w>I%-G1y&&b`QpPN^rpQ7&@>^WJDaSH&nL<$Q4 delta 37 rcmcb~e2ba;GcPX}0}zCtowkvipOL3mKR2&LKUv?gxU_h(D&rOa%&iMU diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/_context.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/_context.cpython-313.pyc index 84a4672d73191518a3a87cdcee988b34cb326b0c..85e68340b1d72c1cd90dea97e82cc26e173bac58 100644 GIT binary patch delta 37 rcmeyM`(Bs(GcPX}0}w>I%-G1ig@wCEKR2&LKSkd+*mLt~7F8ht-k}SA delta 38 scmaE_`$3ocGcPX}0}zCtowkvC3ky%Ler{fgezLw}acS}9Q!J`N0QqVRwg3PC diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/ausencia_oferta_proativa.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/ausencia_oferta_proativa.cpython-313.pyc index d2948c87f21870463b042c70c4d2f9c96c5ef30c..014d27e96a36f95ec1c0fd0c4e9266dd33eab2a8 100644 GIT binary patch delta 37 rcmZ1%zc!xxGcPX}0}w>I%-G27tH(V@KR2&LKSkd+*mHA=o);qk)h7!G delta 38 scmZ1*zb>BpGcPX}0}zCtowkwNSC40|er{fgezLw}acS}9WIZoN0Pmv=KmY&$ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/coerencia.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/coerencia.cpython-313.pyc index f78d1ba0acc9454e37fa4d15068e5014008b1e41..1adbe28eb841de9616eebac4d71ef9f39cefebd4 100644 GIT binary patch delta 37 rcmdm|uvdZmGcPX}0}w>I%-G0%nvc6zKR2&LKSkd+*mLtEz8FRT&{GR( delta 38 scmdn1uup;eGcPX}0}zCtowkwtG#^i&er{fgezLw}acS}9hkP-N0P4UDp#T5? diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/dlex_in.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/dlex_in.cpython-313.pyc index ce28e29f60ed86300bcf649969a149c91206e998..4c25391337980b36a9fe703021c28963b20fc7d5 100644 GIT binary patch delta 37 rcmbQqHG_-$GcPX}0}w>I%-G2Nmzld;KR2&LKSkd+*mJW83o9c4zOxDG delta 38 scmbQiHIs|`GcPX}0}zCtowkwtFEdY%er{fgezLw}acS{pVHQ?K0NBq9ApigX diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/dlex_out.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/dlex_out.cpython-313.pyc index d7b0e3d32902954deba9eba31579334ebd8549ae..4c834efd5965242d74ec0a4ffec468de942f8bec 100644 GIT binary patch delta 37 rcmbQiH=U3BGcPX}0}w>I%-G0%o|U^tKR2&LKSkd+*mLtURwG6Lzw!#i delta 38 scmbQvH-nG+GcPX}0}zCtowkwtJS$JHer{fgezLw}acS}9r>sVd0NLpa`~Uy| diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/fallback.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/fallback.cpython-313.pyc index f18b89eb7b5bdbf9ac3c4e0c90cc9f7be03e6a1c..ae92b20e17b3fe65ed64b052b5263daebf64da16 100644 GIT binary patch delta 39 tcmey`$@ry{k^3_*FBbz4M7qq_$XzAR-J_qISE8Sy?;Gs7d7^xZDggV13|0UD delta 40 ucmey;$@sOCk^3_*FBbz4grA+Zk-JKsr&m8WuS7pt-?6x~c=H7L6jcBh0S%4- diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/fraseologia.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/fraseologia.cpython-313.pyc index 87681d3431fd35e19b7ec29bcc3f19b5f35f0546..e757574e3fd728b50846b30f19e5361352d4ec2a 100644 GIT binary patch delta 37 rcmca)ebJixGcPX}0}w>I%-F~sEW_QepPN^rpQ7&@?72BhCW8?G)9VWX delta 38 scmca;eaV{pGcPX}0}zCtowkuXScYeUer{fgezLw}acS}9OqmQu0Pao};25*Yy*-wrtd diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/pinj.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/pinj.cpython-313.pyc index 528d2668fa31b5b03e8f9cdf8c38d2f465f9639b..d785a3a97bbd4faf9de0b58fee6ae3858a830843 100644 GIT binary patch delta 37 rcmaFq_`;FI%-G0XCC}ZVpPN^rpQ7&@?74ZOd;}u^-!}_* delta 38 scmaFi_|lR4GcPX}0}zCtowkv?N}i`vKR2&LKUv?gxU_il1o;R?0QvU}umAu6 diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/ragsec.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/ragsec.cpython-313.pyc index 384cd688b7994f0acadc083fe5dcfcec89cec7f0..c586b640d91df9e5c49d5e988e69e9320d33275f 100644 GIT binary patch delta 37 rcmaFJ@qmN-GcPX}0}w>I%-F~+!_3{KpPN^rpQ7&@?77*PS&tC_!fy%M delta 38 scmaFB@sNZ2GcPX}0}zCtowku%hMA{ZKR2&LKUv?gxU_h)5wjj60NgbT5&!@I diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/revprec.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/revprec.cpython-313.pyc index dde7d4644fb02e949d73b3d6b564440b6123a745..6b2041d107c31b9079ddeab16eabbac66e8c7731 100644 GIT binary patch delta 37 rcmX>vaZZB!GcPX}0}w>I%-G0%n~S?!KR2&LKSkd+*mLtIu2@C@)9DM6 delta 38 scmX>nabAM^GcPX}0}zCtowkwtHWyEier{fgezLw}acS}9k6f{g0Pb=O$^ZZW diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/tox.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/tox.cpython-313.pyc index 84f78f39621fe372343b03b9b3f0a6dce95186ba..50fcb15315db1bede1c74cb5e84546b43cf02b93 100644 GIT binary patch delta 36 qcmZo=ZDHm9%*)Hg00faPGd6M;GIF=;=jN5@r|A0zdrt0P{0ji3mI`eE delta 37 rcmZo+ZDr;D%*)Hg00iM@r)}geWaR14&&?~*Pu6!VE-jwi&iEGqy|xRb diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/toxicidade_output.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/prompts/__pycache__/toxicidade_output.cpython-313.pyc index 9600db02d704117cf52dd9f357d843c4f6acbcff..ec8f20bfffdc05d4eb959676e0e5b4481cf8ffad 100644 GIT binary patch delta 37 rcmZ3;zJQ(kGcPX}0}w>I%-G1C$izKWKR2&LKSkd+*mH9QlO`how(SY# delta 38 scmZ3$zL1^!GcPX}0}zCtowkuXk%?!Ter{fgezLw}acS}9awbhi0MLL79smFU diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/__init__.cpython-313.pyc index 679d83d10e29c60713d7d83b41dbf7039be865d1..8f3fac0e59edec9b26db2429aa7ccabf2a329098 100644 GIT binary patch delta 36 qcmZqR{m;YwnU|M~0SF>pW^CkM$jaTNpPN^rpQ7&@>^XS{>j?n6YYQj< delta 37 rcmey*)4!fKR2&LKUv?gxU_ikcGeRB)kO?o diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/alcada.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/alcada.cpython-313.pyc index 5001ab461a8e646449038af40d777a3bbbaa09e6..e93d71eecabdb08a030bd5d6888654fc87e23c2c 100644 GIT binary patch delta 37 rcmeBBZ&&C3%*)Hg00faPGd6O+X5;SA&&?~*Pto@c_T0?G-o^s}!B+}w delta 38 scmZoy?@;Id%*)Hg00iM@r)}hZ&BoKIpPN^rpRDg#Tw1)Dk-d!v0NZ&BqyPW_ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/anatel.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/anatel.cpython-313.pyc index 93af9183a3a89a4dd1f21e88e95ec25e8f6823b0..659cca02653235768827f185384e795c29ffaf11 100644 GIT binary patch delta 37 rcmdn%zQ>*WGcPX}0}w>I%-G2Nm7BXmKR2&LKSkd+*mE-v&qi?o+OG?0 delta 38 scmdnvzSo`mGcPX}0}zCtowkwtD>qN4er{fgezLw}acS{pZk~I%-G0nEY97hpPN^rpQ7&@?77)X{H8Df%diU( delta 38 scmZ1!wm6LYGcPX}0}zCtowkwNSe&O{KR2&LKUv?gxU_h)r}#}_0OiFDNdN!< diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/dlex_in.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/dlex_in.cpython-313.pyc index 2d893db11a0d4a7f7744c5eaae535ef28c406c98..f157348833b606390bde56eab02e3b9cc89f11d1 100644 GIT binary patch delta 37 rcmca0d0vwHGcPX}0}w>I%-G0%kd?brKR2&LKSkd+*mLs@)*oyD($fp} delta 38 scmX>vc|nr#RT60PQ~wE&u=k diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/dlex_out.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/dlex_out.cpython-313.pyc index 7152ce1a948001f029cf67778af66eeb84ed61e2..e437bea977eb6981c862977590bbd233fde71cd5 100644 GIT binary patch delta 37 rcmZpXYL?>u%*)Hg00faPGd6M`Vdd`9&&?~*Pto@c_S}4%Rg)b6ym1Pd delta 38 scmZpcYLVjp%*)Hg00iM@r)}gu!phUFpPN^rpRDg#Tw1*O7ON&Z0M<(j(EtDd diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/ragsec.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/ragsec.cpython-313.pyc index 26340d559a39bb93ce8dd1620f3a304b420c1776..b62a84fc863e7fdf412599be1a47d8ef8bef78f3 100644 GIT binary patch delta 37 rcmcbvbxn)=GcPX}0}w>I%-G0%jh(wgKR2&LKSkd+*mLt+_AFii+F1+C delta 38 scmcbnbzO`5GcPX}0}zCtowkwt8aq#?er{fgezLw}acS}9H|$xw0QCzE1ONa4 diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/revprec.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/revprec.cpython-313.pyc index 19b310af4e6cda8495aed73a38e00d177ae1cc97..aa2be86e684c2786e50c074b35df14c8e86ac486 100644 GIT binary patch delta 36 qcmcbwbx(`?GcPX}0}w>I%-G2NlAXI#KR2&LKSkd+*mDy{JTCyx!V35R delta 38 scmcbobzh77GcPX}0}zCtowkwtB|A@-er{fgezLw}acS}9|LpO+0QnvbFaQ7m diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/tox.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rails/__pycache__/tox.cpython-313.pyc index d82a04f723afe6eeaca9d3c9aded8def5539e6ce..15dcb19ffffced5f7b0297c4055f470672870e6a 100644 GIT binary patch delta 37 rcmaFn@YI3(GcPX}0}w>I%-G2NTY$S&KR2&LKSkd+*mJXxUI%-F~s!N}dEpPN^rpQ7&@>^V7~aXA2|!U_lg delta 37 rcmbQrGL41%GcPX}0}zCtowkuXf{~|NKR2&LKUv?gxU_h39^-NVzw8S@ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/alcada.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/alcada.cpython-313.pyc index e72c6d270291065598c520a4157c26e6520e2b6e..86c6d7e1a5dfc97a944f3d63945d56ce79d1629c 100644 GIT binary patch delta 37 rcmcb`cax9%GcPX}0}w>I%-G1C#LC^FpPN^rpQ7&@?76v;^&ukw$<+%P delta 38 scmcb~cZ-kvGcPX}0}zCtowkuXiIt~QKR2&LKUv?gxU_h41?xjb0OQ{bQ2+n{ diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/pinj_patterns.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/pinj_patterns.cpython-313.pyc index 65efbd1a120ab8277effbfec8e31f1a8ae2c8f73..72d4bc1b534679d1811d220b0cdeab3dd64ef363 100644 GIT binary patch delta 37 rcmbO%J4u%NGcPX}0}w>I%-G1if{VLfKR2&LKSkd+*mLuKt}Dy{!2%0A delta 38 tcmbOvJ6V?dGcPX}0}zCtowkvC1sBf*{oK3~{bYT|;?m;H`?#(!0|46Y408Yg diff --git a/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/tox_blocklist.cpython-313.pyc b/libs/agent_framework/src/agent_framework/guardrails/calibrated/rules/__pycache__/tox_blocklist.cpython-313.pyc index 32b0d827c0580565b245d1189928abefdba482d2..85ecb207a8e7a9a6ebfa42f633054a272febda51 100644 GIT binary patch delta 37 rcmaFN{fL|UGcPX}0}w>I%-F~+$HLvOpPN^rpQ7&@?77*LWgQ~`#bgR^ delta 38 scmaFF{g|8kGcPX}0}zCtowku%j)iA}er{fgezLw}acS{p6P9(10N*?drvLx| diff --git a/libs/agent_framework/src/agent_framework/guardrails/rails.py b/libs/agent_framework/src/agent_framework/guardrails/rails.py index fcfb31b..9a32e5b 100644 --- a/libs/agent_framework/src/agent_framework/guardrails/rails.py +++ b/libs/agent_framework/src/agent_framework/guardrails/rails.py @@ -323,10 +323,28 @@ class CoherenceRail(Guardrail): "data": out, }, ) - out = await classify_with_framework_llm( - _llm(ctx), "COER", {"text": text or "", "context": ctx}, - profile_name="guardrail", component_name="guardrail.coer", generation_name="guardrail.coer", - ) + try: + out = await classify_with_framework_llm( + _llm(ctx), "COER", {"text": text or "", "context": ctx}, + profile_name="guardrail", component_name="guardrail.coer", generation_name="guardrail.coer", + ) + except Exception as exc: + # COER is a conversational-coherence rail, not a security boundary. + # If its semantic classifier/provider is unavailable, the graph must + # continue and let the normal router/workflow clarification logic own + # the turn instead of surfacing an infrastructure exception. + return RailDecision( + code=self.code, + allowed=True, + reason="Classificador de coerência indisponível; continuidade delegada ao runtime", + sanitized_text=text, + metadata={ + "mechanism": "infrastructure_fail_open", + "calibrated": True, + "error_type": type(exc).__name__, + "error": str(exc)[:500], + }, + ) return RailDecision( code=self.code, allowed=bool(out.get("allowed", True)), reason=str(out.get("reason") or out.get("label") or "COER avaliado"), diff --git a/libs/agent_framework/src/agent_framework/runtime/__pycache__/__init__.cpython-313.pyc b/libs/agent_framework/src/agent_framework/runtime/__pycache__/__init__.cpython-313.pyc index da962e4492ef173d64d376c604a32f33860fecab..5312258964a239b5ffa243eb9639dbd4d27013db 100644 GIT binary patch delta 34 ocmcc1bcc!iGcPX}0}w>G%$Uf%mb*+pH?KrLMc+5rbK;Si0Im26^#A|> delta 35 pcmcb^beDqsEl;_AZeEFgvc6++Y4OCvGXcdU3@!iw diff --git a/libs/agent_framework/src/agent_framework/runtime/__pycache__/agent_runtime.cpython-313.pyc b/libs/agent_framework/src/agent_framework/runtime/__pycache__/agent_runtime.cpython-313.pyc index a519316898d1bbae99e8403724ebc4a22602130c..9603442b9cea1a901949c97fc5d7f07aa0c4d8e4 100644 GIT binary patch delta 40778 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 U1e#MlYIS7Ij9~a$0FZY74`4qFBLDyZ delta 30589 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 Vc0D}Rd1mSv<3z$I4nUUf{{djDU=9EP diff --git a/libs/agent_framework/src/agent_framework/runtime/__pycache__/transaction_parameters.cpython-313.pyc b/libs/agent_framework/src/agent_framework/runtime/__pycache__/transaction_parameters.cpython-313.pyc index 48591afd12b0d3dd9bfeb1128e813e2ccdbc2a2c..b797d4f4500c47dbb651d181a8aca1e61c8e2d14 100644 GIT binary patch delta 7787 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 T%G{DDb3>wNyI<6SVgURPa9DUz delta 4645 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 str: + """Normalize bounded conversation text into newest->oldest message blocks. + + Role-labelled multi-line messages are kept intact. This avoids reversing + individual lines inside an assistant explanation while still honoring the + temporal search order required by parameter reconciliation. + """ + raw = str(context or "").strip() + if not raw: + return "" + blocks: list[str] = [] + current: list[str] = [] + for line in raw.splitlines(): + stripped = line.strip() + if not stripped: + continue + low = stripped.lower() + starts_role = low.startswith(("user:", "assistant:", "system:", "cliente:", "agente:")) + if starts_role and current: + blocks.append(" ".join(current)) + current = [stripped] + else: + current.append(stripped) + if current: + blocks.append(" ".join(current)) + if not blocks: + blocks = [line.strip() for line in raw.splitlines() if line.strip()] + newest = list(reversed(blocks)) + return "\n".join(f"history:{i+1}: {block}" for i, block in enumerate(newest)) + + async def _reconcile_transaction_parameters( + self, + state: dict[str, Any], + *, + tool_name: str, + parameter_names: list[str], + known_arguments: dict[str, Any] | None = None, + ) -> dict[str, Any]: + """Reconcile a coherent required-parameter set from text and tool schema.""" + # Compatibility/extensibility: if a domain/runtime subclass explicitly + # overrides the legacy extractor, honor that override and translate its + # candidates into reconciliation decisions. The base framework path below + # remains the coherent temporal reconciler. + override = getattr(type(self), "_extract_transaction_parameters", None) + if override is not None and override is not AgentRuntimeMixin._extract_transaction_parameters: + route_meta_compat = ((state.get("route_decision") or {}).get("metadata") or {}) if isinstance(state.get("route_decision"), dict) else {} + compat_fields = list(parameter_names) if route_meta_compat.get("transaction_parameter_values") else [ + name for name in parameter_names if (known_arguments or {}).get(name) in _EMPTY_VALUES + ] + extracted = await override( + self, state, tool_name=tool_name, missing_parameters=compat_fields, + known_arguments=dict(known_arguments or {}), + ) + values = dict(extracted or {}) + decisions = {name: ("resolved" if name in values else ("preserve" if (known_arguments or {}).get(name) not in _EMPTY_VALUES else "unresolved")) for name in parameter_names} + provenance = {name: ("current" if name in values else "state") for name in parameter_names if decisions[name] != "unresolved"} + return {"values": values, "decisions": decisions, "provenance": provenance, "clear_fields": []} + + active = self._active_transaction(state) or {} + schema = active.get("parameter_schema") if isinstance(active.get("parameter_schema"), dict) else None + if not schema: + policy = self._resolve_tool_execution_policy(tool_name, known_arguments or {}) + schema = self._transaction_parameter_schema(tool_name, policy) + description = str(active.get("tool_description") or self._transaction_tool_description(tool_name) or "") + route_meta = ((state.get("route_decision") or {}).get("metadata") or {}) if isinstance(state.get("route_decision"), dict) else {} + contextual_reentry = bool(route_meta.get("contextual_reentry")) + text = (route_meta.get("original_input") if contextual_reentry else None) or state.get("sanitized_input") or state.get("user_text") or "" + conversational_context = route_meta.get("relevant_conversation_context") if contextual_reentry else None + if not str(conversational_context or "").strip(): + conversational_context = active.get("parameter_conversational_context") + if contextual_reentry and not str(conversational_context or "").strip(): + effective = str(route_meta.get("contextual_reentry_input") or "") + prefix = "CONTEXTO DA SOLICITAÇÃO IMEDIATAMENTE ANTERIOR:\n" + suffix = "\n\nCONTINUAÇÃO ATUAL DO CLIENTE:\n" + if prefix in effective and suffix in effective: + conversational_context = effective.split(prefix, 1)[1].split(suffix, 1)[0].strip() + ordered_context = self._transaction_context_newest_first(conversational_context) + cached = route_meta.get("transaction_parameter_values") + effective_known = dict(known_arguments or {}) + if isinstance(cached, dict): + for key, value in cached.items(): + if str(key) in set(str(x) for x in parameter_names) and value not in _EMPTY_VALUES: + effective_known[str(key)] = value + return await reconcile_transaction_parameters( + getattr(self, "llm", None), + text=str(text), + tool_name=tool_name, + parameter_names=[str(x) for x in parameter_names], + known_arguments=effective_known, + parameter_schema=schema, + tool_description=description, + conversational_context=ordered_context, + ) + + async def _extract_transaction_parameters_current_only( + self, + state: dict[str, Any], + *, + tool_name: str, + missing_parameters: list[str], + known_arguments: dict[str, Any] | None = None, + ) -> dict[str, Any]: + """Collect pending parameters from the current utterance only. + + This is the normal COLLECTING_PARAMETERS path. Temporal conversation + history is deliberately excluded here; `_reconcile_transaction_parameters` + is the fallback when this direct collection cannot satisfy or validate the + declarative parameter contract. + """ + if not missing_parameters: + return {} + + # Preserve the framework extension contract: domain/test runtimes that + # explicitly override the legacy collector remain the authoritative + # implementation of the *traditional* first pass. The temporal fallback + # is still owned by the base runtime and is invoked only after failure. + override = getattr(type(self), "_extract_transaction_parameters", None) + if override is not None and override is not AgentRuntimeMixin._extract_transaction_parameters: + return await override( + self, + state, + tool_name=tool_name, + missing_parameters=list(missing_parameters), + known_arguments=dict(known_arguments or {}), + ) + + active = self._active_transaction(state) or {} + schema = active.get("parameter_schema") if isinstance(active.get("parameter_schema"), dict) else None + if not schema: + policy = self._resolve_tool_execution_policy(tool_name, known_arguments or {}) + schema = self._transaction_parameter_schema(tool_name, policy) + description = str(active.get("tool_description") or self._transaction_tool_description(tool_name) or "") + route_meta = ((state.get("route_decision") or {}).get("metadata") or {}) if isinstance(state.get("route_decision"), dict) else {} + text = route_meta.get("original_input") if route_meta.get("contextual_reentry") else None + text = text or state.get("sanitized_input") or state.get("user_text") or "" + return await extract_transaction_parameters( + getattr(self, "llm", None), + text=str(text), + tool_name=tool_name, + missing_parameters=[str(x) for x in missing_parameters], + known_arguments=dict(known_arguments or {}), + parameter_schema=schema, + tool_description=description, + conversational_context="", + ) + async def _extract_transaction_parameters( self, state: dict[str, Any], @@ -1535,6 +1682,7 @@ class AgentRuntimeMixin: suffix = "\n\nCONTINUAÇÃO ATUAL DO CLIENTE:\n" if prefix in effective and suffix in effective: conversational_context = effective.split(prefix, 1)[1].split(suffix, 1)[0].strip() + ordered_context = self._transaction_context_newest_first(conversational_context) extracted = await extract_transaction_parameters( getattr(self, "llm", None), text=str(text), @@ -1543,9 +1691,42 @@ class AgentRuntimeMixin: known_arguments={**dict(known_arguments or {}), **reused}, parameter_schema=schema, tool_description=description, - conversational_context=str(conversational_context or ""), + conversational_context=ordered_context, ) - return {**reused, **extracted} + + # Compatibility/resilience fallback: if a provider returns only a partial + # flat extraction, scan bounded prior text source-by-source from newest to + # oldest for still-unresolved fields. This preserves temporal order and + # remains schema-driven; the main COLLECTING_PARAMETERS path uses the + # coherent set reconciler above. + combined = {**reused, **extracted} + unresolved = [name for name in missing_parameters if name not in combined] + if unresolved and str(conversational_context or "").strip(): + source_lines = self._transaction_context_newest_first(conversational_context).splitlines() + for source_line in source_lines: + if not unresolved: + break + source_text = source_line.split(": ", 1)[1] if ": " in source_line else source_line + low_source = source_text.lower() + for role_prefix in ("user: ", "assistant: ", "cliente: ", "agente: ", "system: "): + if low_source.startswith(role_prefix): + source_text = source_text[len(role_prefix):] + break + recovered = await extract_transaction_parameters( + getattr(self, "llm", None), + text=source_text, + tool_name=tool_name, + missing_parameters=list(unresolved), + known_arguments={**dict(known_arguments or {}), **combined}, + parameter_schema=schema, + tool_description=description, + conversational_context="", + ) + for key, value in recovered.items(): + if key in unresolved and value not in _EMPTY_VALUES: + combined[key] = value + unresolved = [name for name in unresolved if name not in combined] + return combined def _transactional_action_match(self, text: str, tools: list[str] | None = None) -> str | None: """Detecta solicitação transacional usando metadados de tools.yaml. @@ -2827,23 +3008,48 @@ class AgentRuntimeMixin: policy = self._resolve_tool_execution_policy(tool_name, previous_args) missing_before = self._missing_required_arguments(policy, previous_args) - # Parâmetros TRANSACIONAIS são interpretados exclusivamente pelo - # extrator LLM genérico. Durante COLLECTING_PARAMETERS, a fala atual - # também pode CORRIGIR um required field já coletado em turno anterior - # (ex.: valor=19,99 e o cliente diz "desculpa, é 14,99" enquanto - # subject ainda está pendente). Por isso o contrato editável do turno - # é o conjunto completo de ``requires``; somente as chaves realmente - # extraídas pela LLM sobrescrevem ``previous_args``. Campos não citados - # permanecem intactos. Isso preserva parameter-before-intent-shift sem - # tornar valores antigos imutáveis por acidente. - editable_required = [str(name) for name in (policy.get("requires") or [])] - extracted = await self._extract_transaction_parameters( + # Primeira tentativa: coleta tradicional usando SOMENTE a fala + # atual. O histórico não deve participar enquanto a mensagem corrente + # conseguir preencher o contrato normalmente. + arguments = dict(previous_args) + extracted_current = await self._extract_transaction_parameters_current_only( state, tool_name=tool_name, - missing_parameters=editable_required, + missing_parameters=missing_before, known_arguments=previous_args, ) - arguments = {**previous_args, **extracted} + arguments.update(dict(extracted_current or {})) + state["transaction_parameter_collection"] = { + "tool_name": tool_name, + "mode": "current_turn", + "resolved_fields": sorted(str(k) for k in (extracted_current or {}).keys()), + } + + # Segunda opção: somente se a coleta tradicional ainda deixar + # required fields em aberto, reconcilie temporalmente usando o + # schema/descrições declarados em tools.yaml e o contexto bounded + # newest->oldest preservado na transação. + missing_after_current = self._missing_required_arguments(policy, arguments) + if missing_after_current: + required_fields = [str(name) for name in (policy.get("requires") or [])] + reconciliation = await self._reconcile_transaction_parameters( + state, + tool_name=tool_name, + parameter_names=required_fields, + known_arguments=arguments, + ) + for field in reconciliation.get("clear_fields") or []: + arguments.pop(str(field), None) + arguments.update(dict(reconciliation.get("values") or {})) + state["transaction_parameter_reconciliation"] = { + "tool_name": tool_name, + "trigger": "traditional_collection_unresolved", + "decisions": dict(reconciliation.get("decisions") or {}), + "provenance": dict(reconciliation.get("provenance") or {}), + "clear_fields": list(reconciliation.get("clear_fields") or []), + } + else: + state.pop("transaction_parameter_reconciliation", None) # Argumentos estruturados já presentes no contexto são aceitos de # forma genérica (não são parsing textual). Para required fields, @@ -2890,7 +3096,65 @@ class AgentRuntimeMixin: state, tool_name=tool_name, arguments=arguments, policy=policy, emit_events=emit_events ) if pre_validation_result is not None: - return [pre_validation_result] + # A pre-validation é a fronteira autoritativa da coleta + # tradicional. Se ela rejeitar um candidato de forma recuperável + # (NEEDS_PARAMETER), ainda não repromptamos o usuário: fazemos UMA + # tentativa de reconciliação temporal com o histórico bounded e + # com a semântica declarada em tools.yaml. Isso cobre referências + # como "é a de R$ 19,99" sem tornar o reconciliador o caminho + # primário de COLLECTING_PARAMETERS. + pre_meta = state.get("transaction_pre_validation") + pre_meta = pre_meta if isinstance(pre_meta, dict) else {} + retry_parameter = str(pre_meta.get("parameter") or "").strip() + recoverable = ( + str(pre_meta.get("status") or "").upper() == "NEEDS_PARAMETER" + and bool(retry_parameter) + and state.get("transaction_status") == "COLLECTING_PARAMETERS" + ) + if recoverable: + recovered_selected = dict(self._active_transaction(state) or state.get("selected_tool_call") or {}) + recovered_arguments = dict(recovered_selected.get("arguments") or {}) + required_fields = [str(name) for name in (policy.get("requires") or [])] + reconciliation = await self._reconcile_transaction_parameters( + state, + tool_name=tool_name, + parameter_names=required_fields, + known_arguments=recovered_arguments, + ) + for field in reconciliation.get("clear_fields") or []: + recovered_arguments.pop(str(field), None) + recovered_arguments.update(dict(reconciliation.get("values") or {})) + state["transaction_parameter_reconciliation"] = { + "tool_name": tool_name, + "trigger": "prevalidation_needs_parameter", + "rejected_parameter": retry_parameter, + "decisions": dict(reconciliation.get("decisions") or {}), + "provenance": dict(reconciliation.get("provenance") or {}), + "clear_fields": list(reconciliation.get("clear_fields") or []), + } + retry_policy = self._resolve_tool_execution_policy(tool_name, recovered_arguments) + retry_missing = self._missing_required_arguments(retry_policy, recovered_arguments) + if retry_parameter not in retry_missing: + state["selected_tool_call"] = {"tool_name": tool_name, "arguments": recovered_arguments} + self._set_active_transaction( + state, tool_name=tool_name, arguments=recovered_arguments, status="COLLECTING_PARAMETERS" + ) + state["missing_parameters"] = retry_missing + pre_validation_retry = await self._run_transaction_pre_validation( + state, + tool_name=tool_name, + arguments=recovered_arguments, + policy=retry_policy, + emit_events=emit_events, + ) + if pre_validation_retry is None: + arguments = recovered_arguments + policy = retry_policy + pre_validation_result = None + else: + return [pre_validation_retry] + if pre_validation_result is not None: + return [pre_validation_result] tool_name, policy, force_confirmation = self._apply_prevalidated_transaction_decision( state, tool_name=tool_name, arguments=arguments, policy=policy diff --git a/libs/agent_framework/src/agent_framework/runtime/transaction_parameters.py b/libs/agent_framework/src/agent_framework/runtime/transaction_parameters.py index 1a394f7..88cfbeb 100644 --- a/libs/agent_framework/src/agent_framework/runtime/transaction_parameters.py +++ b/libs/agent_framework/src/agent_framework/runtime/transaction_parameters.py @@ -69,69 +69,77 @@ def parse_transaction_confirmation(text: str) -> str | None: return None -async def extract_transaction_parameters( +async def reconcile_transaction_parameters( llm: Any, *, text: str, tool_name: str, - missing_parameters: list[str], + parameter_names: list[str], known_arguments: Mapping[str, Any] | None = None, parameter_schema: Mapping[str, Any] | None = None, tool_description: str | None = None, conversational_context: str | None = None, ) -> dict[str, Any]: - """Extract values for pending transactional parameters using the LLM only. + """Rebuild a coherent parameter set from text, newest to oldest. - This component intentionally contains no domain/entity regexes and no - knowledge of parameter names such as ``order_id`` or ``reason``. The - transaction runtime supplies the pending parameter names and optional schema; - the LLM only interprets the current user turn. State/control-flow decisions - remain deterministic outside this function. + The framework is intentionally field/domain neutral. Meaning comes from the + declarative tool schema (normally tools.yaml) plus the conversation text. + The LLM may resolve a field, preserve a previously known value, explicitly + clear a stale value whose context was superseded, or leave a field unresolved. + Returned provenance is interpretive metadata only; authoritative business + validation still belongs to the configured pre-validation/tool layer. """ - pending = [str(name) for name in (missing_parameters or []) if str(name).strip()] + names = [str(name) for name in (parameter_names or []) if str(name).strip()] message = str(text or "").strip() - if not pending or not message or llm is None: - return {} + if not names or not message or llm is None: + return {"values": {}, "decisions": {}, "provenance": {}, "clear_fields": []} schema = dict(parameter_schema or {}) known = { str(key): value for key, value in dict(known_arguments or {}).items() - if value not in _EMPTY_VALUES and str(key) not in pending + if value not in _EMPTY_VALUES } field_spec = { name: { "type": schema.get(name, "string") if not isinstance(schema.get(name), dict) else schema.get(name, {}).get("type", "string"), "description": None if not isinstance(schema.get(name), dict) else schema.get(name, {}).get("description"), } - for name in pending + for name in names + } + output_shape = { + "fields": { + name: {"decision": "resolved|preserve|clear|unresolved", "value": None, "source": "current|history:N|state"} + for name in names + } } - output_shape = {name: None for name in pending} prompt = ( - "Você extrai parâmetros PENDENTES de uma transação ativa. " - "Sua única tarefa é interpretar a mensagem atual e devolver valores para os parâmetros pendentes. " - "Não decida roteamento, intenção, confirmação ou execução da transação.\n\n" - "REGRAS OBRIGATÓRIAS:\n" - "1. Extraia SOMENTE parâmetros listados em pending_parameters.\n" - "2. Não invente valores e não transforme uma nova solicitação/intenção do usuário em valor de parâmetro.\n" - "3. Se nenhum parâmetro pendente foi realmente informado, devolva null para todos.\n" - "4. Se houver apenas um parâmetro pendente, uma resposta contendo apenas um valor pode ser associada a ele quando isso for semanticamente inequívoco.\n" - "5. Se houver vários parâmetros pendentes, extraia todos os que estiverem presentes no mesmo turno.\n" - "6. O nome do parâmetro não precisa aparecer literalmente na fala. Associe semanticamente o valor usando o nome da transação e os metadados disponíveis para cada campo.\n" - "7. Para cada parâmetro, considere o nome técnico, o tipo quando disponível e principalmente a descrição semântica quando disponível. A ausência de tipo ou descrição NÃO impede a extração.\n" - "8. Se a mensagem deixar clara a correspondência entre um trecho e um parâmetro, preencha-o mesmo que o usuário não cite o nome técnico do campo.\n" - "9. Não use conhecimento externo para completar valores ausentes e não transforme aproximações ou suposições em fatos.\n" - "10. conversational_context, quando presente, serve SOMENTE para resolver referências da mensagem atual (por exemplo: 'a de 14,99' apontando para um item citado imediatamente antes). Não trate texto do contexto como uma nova afirmação do cliente nem como evidência de negócio.\n" - "11. Quando a mensagem atual identifica um valor OU nome e o contexto imediatamente anterior contém uma única entidade compatível, você pode preencher essa entidade e os atributos pendentes inequivocamente associados a ela como CANDIDATOS. Exemplo genérico: se a fala identifica uma entidade e o contexto associa unicamente essa entidade a um valor requerido, o valor pode ser retornado como candidato. A validação autoritativa ocorrerá depois; não invente se houver ambiguidade.\n" - "12. Em caso de dúvida razoável sobre a correspondência ou o valor, prefira null.\n" - "13. Responda SOMENTE JSON válido, sem markdown, sem explicação e sem chaves extras.\n\n" + "Você é o conciliador temporal de parâmetros de uma transação ativa. " + "Reconstrua UM CONJUNTO COERENTE de parâmetros; não extraia cada campo de forma isolada. " + "Não decida roteamento, confirmação nem execução.\n\n" + "CONTRATO OBRIGATÓRIO:\n" + "1. O significado de cada campo vem EXCLUSIVAMENTE de parameter_schema e transaction_description, considerando principalmente a descrição semântica quando disponível. O framework não conhece conceitos de domínio por nome de campo.\n" + "2. A ausência de tipo ou descrição NÃO impede a extração quando o restante do contrato e o texto forem semanticamente suficientes.\n" + "3. A busca textual é temporal, em ordem decrescente: user_message é a fonte mais nova; depois conversational_context já vem do texto mais novo para o mais antigo.\n" + "3. Textos do usuário e textos explicativos do assistente podem ser usados para interpretar referências e relações entre campos. Não trate texto do contexto como uma nova afirmação do cliente; eles são contexto, não evidência autoritativa de negócio.\n" + "4. Para cada campo escolha: resolved = um texto determina novo valor; preserve = nenhum texto mais novo invalida o valor conhecido; clear = existe mudança textual mais nova que torna o valor conhecido incompatível/sem vínculo seguro com o novo contexto e ainda não há substituto; unresolved = não há valor conhecido nem candidato textual seguro.\n" + "5. Se uma fonte mais nova muda uma entidade, objeto, escopo ou outro contexto que dava sentido a campos extraídos anteriormente, REAVALIE os demais campos como conjunto. Não combine automaticamente um atributo antigo com um contexto novo só porque ambos existem.\n" + "6. Um valor de texto anterior pode continuar válido após uma mudança somente quando os textos, considerados em conjunto, sustentarem de forma inequívoca que ele ainda pertence ao contexto mais recente. Caso contrário use clear para o campo dependente.\n" + "7. Se a nova menção é inválida no mundo real, NÃO volte silenciosamente ao valor antigo. Ainda assim devolva o candidato textual mais recente como resolved; a pre-validation autoritativa é responsável por rejeitá-lo.\n" + "8. known_arguments é fallback de estado. Use preserve somente quando nenhum texto mais novo o contradiz ou rompe sua associação contextual.\n" + "9. Uma expressão só pode preencher um campo se satisfizer semanticamente type/description desse campo. Não transfira um trecho para outro campo apenas por proximidade lexical.\n" + "10. Se um texto recente corrige explicitamente informação anterior, a correção prevalece para os campos que o schema permite inferir; os demais devem ser reavaliados quanto à coerência com a correção.\n" + "11. Em ambiguidade razoável, prefira clear/unresolved a inventar uma associação. Em caso de dúvida razoável sobre a correspondência ou o valor, prefira null.\n" + "12. Responda SOMENTE JSON válido no formato pedido, sem markdown e sem chaves extras.\n\n" f"transaction_tool: {tool_name}\n" f"transaction_description: {tool_description or ''}\n" - f"pending_parameters: {json.dumps(pending, ensure_ascii=False)}\n" + f"parameter_names: {json.dumps(names, ensure_ascii=False)}\n" + f"pending_parameters: {json.dumps(names, ensure_ascii=False)}\n" f"parameter_schema: {json.dumps(field_spec, ensure_ascii=False, default=str)}\n" f"known_arguments: {json.dumps(known, ensure_ascii=False, default=str)}\n" - f"conversational_context: {str(conversational_context or '').strip()}\n" + "conversation_sources_newest_to_oldest:\n" f"user_message: {message}\n" + f"conversational_context: {str(conversational_context or '').strip()}\n" f"Formato obrigatório: {json.dumps(output_shape, ensure_ascii=False)}" ) @@ -144,44 +152,98 @@ async def extract_transaction_parameters( temperature=0.0, ) except TypeError: - # Compatibilidade com doubles/testes e providers mínimos que aceitam - # apenas messages. response = await llm.ainvoke([{"role": "user", "content": prompt}]) except Exception as exc: - logger.warning( - "transaction.parameter.llm_extract_failed tool=%s pending=%s error=%s", - tool_name, - pending, - exc, - ) - return {} + logger.warning("transaction.parameter.reconcile_failed tool=%s fields=%s error=%s", tool_name, names, exc) + return {"values": {}, "decisions": {}, "provenance": {}, "clear_fields": []} raw = _response_text(response).strip() try: payload = parse_json_object(raw) except (TypeError, ValueError): - logger.warning( - "transaction.parameter.llm_invalid_structured_output tool=%s pending=%s raw=%r", - tool_name, - pending, - raw[:240], - ) - return {} + logger.warning("transaction.parameter.reconcile_invalid_output tool=%s raw=%r", tool_name, raw[:240]) + return {"values": {}, "decisions": {}, "provenance": {}, "clear_fields": []} if not isinstance(payload, dict): - return {} + return {"values": {}, "decisions": {}, "provenance": {}, "clear_fields": []} - extracted: dict[str, Any] = {} - for name in pending: - value = payload.get(name) + # Backward compatibility with existing providers/test doubles that return a + # flat {field: value} object. Non-null flat values mean ``resolved``. + fields = payload.get("fields") if isinstance(payload.get("fields"), dict) else None + if fields is None: + fields = {} + for name in names: + flat_value = payload.get(name) + if flat_value in _EMPTY_VALUES: + decision = "preserve" if name in known else "unresolved" + source = "state" if decision == "preserve" else "" + else: + decision = "resolved" + source = "current" + fields[name] = {"decision": decision, "value": flat_value, "source": source} + + values: dict[str, Any] = {} + decisions: dict[str, str] = {} + provenance: dict[str, str] = {} + clear_fields: list[str] = [] + for name in names: + item = fields.get(name) if isinstance(fields, dict) else None + if not isinstance(item, dict): + item = {"decision": "unresolved", "value": None, "source": ""} + decision = str(item.get("decision") or "unresolved").strip().lower() + if decision not in {"resolved", "preserve", "clear", "unresolved"}: + decision = "unresolved" + decisions[name] = decision + source = str(item.get("source") or "").strip() + if source: + provenance[name] = source + if decision == "clear": + clear_fields.append(name) + continue + if decision == "preserve": + if name in known: + values[name] = known[name] + continue + if decision != "resolved": + continue declared = field_spec.get(name, {}).get("type", "string") - coerced = _coerce(value, declared) + coerced = _coerce(item.get("value"), declared) if coerced not in _EMPTY_VALUES: - extracted[name] = coerced + values[name] = coerced + else: + decisions[name] = "unresolved" logger.info( - "transaction.parameter.llm_extracted tool=%s pending=%s consumed=%s", - tool_name, - pending, - sorted(extracted), + "transaction.parameter.reconciled tool=%s decisions=%s resolved=%s clear=%s provenance=%s", + tool_name, decisions, sorted(values), clear_fields, provenance, ) - return extracted + return {"values": values, "decisions": decisions, "provenance": provenance, "clear_fields": clear_fields} + + +async def extract_transaction_parameters( + llm: Any, + *, + text: str, + tool_name: str, + missing_parameters: list[str], + known_arguments: Mapping[str, Any] | None = None, + parameter_schema: Mapping[str, Any] | None = None, + tool_description: str | None = None, + conversational_context: str | None = None, +) -> dict[str, Any]: + """Compatibility facade returning only resolved candidates. + + New runtime code should use :func:`reconcile_transaction_parameters` when it + needs preserve/clear/provenance decisions. Keeping this facade avoids + breaking routers and existing extensions that only need candidate extraction. + """ + result = await reconcile_transaction_parameters( + llm, + text=text, + tool_name=tool_name, + parameter_names=missing_parameters, + known_arguments=known_arguments, + parameter_schema=parameter_schema, + tool_description=tool_description, + conversational_context=conversational_context, + ) + return dict(result.get("values") or {})