From 266eb89c5a7cc625f26cf662b5b26a6249155a8f Mon Sep 17 00:00:00 2001 From: zhaoyingbo Date: Thu, 6 Feb 2025 13:06:27 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=BD=91=E9=A1=B5?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=84=8F=E5=9B=BE=E8=AF=86=E5=88=AB=E6=A8=A1=E5=BC=8F=EF=BC=8C?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BE=9D=E8=B5=96=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 2 + bun.lockb | Bin 166839 -> 165495 bytes constant/function.ts | 5 ++ controller/intentAgent/index.ts | 50 +++++++++++++--- controller/reportAgent/index.ts | 2 +- controller/searchAgent/index.ts | 70 ++++++++++++++++++++++ package.json | 16 +++--- routes/bot/eventMsg.ts | 99 ++++++++++++++++---------------- services/attach/index.ts | 31 ++++++++++ test/llm/intentRecognition.ts | 39 +++++++++++-- test/server/getBatchUserInfo.ts | 11 ++++ test/server/webSearch.ts | 8 +++ 12 files changed, 261 insertions(+), 72 deletions(-) create mode 100644 controller/searchAgent/index.ts create mode 100644 test/server/getBatchUserInfo.ts create mode 100644 test/server/webSearch.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 8252710..ce81483 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,7 @@ { "cSpell.words": [ + "BOCHA", + "bochaai", "bunx", "CEINTL", "Chakroun", diff --git a/bun.lockb b/bun.lockb index 244d5e1e21b946c1014e468d16cd56dbe0bf249f..e0497f381be2be8499dab9934dd0bb1629b148ff 100755 GIT binary patch delta 26545 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 delta 26827 zcmeHwcT`nJ_x711SGg!EDoRs8#7-A33UaX^%C(^)mRJx$Q3Ppr1?*k%sAG98M2$VR zD4J+&QBkpAFR@@njRkv)e$PHdNxmfO{eA0O>-SIY%EO*#&zzard-m)zT#hGird#B2 zi`njV;+9SSUZ0-n=jPq3V&j){hJ3V3?l#*qBd5)?&xhX?9r{pfnkdj`T~?%{ICj;R zVGBgdvP=-l2||V}CCd`_>4)qE!3vrmAxlF}gQON)9R#5)D-Uxl=R>;y@k>6bXTFO72qypk^z z?=d5?JROo|ScppM_(L5*u!S5NmynX0m>^`0usx zR;c>fs$2v~^XnTEGuSOIMF;>d2kx!vmv||hc`Na3@riwe z6d$==-Hg}n%6#JbB_t-t2*M+k$Hpb}bxRv8tZSguFO&A`9Mi9&mZs_IrwlVNX7~`A z?qYDVHa0#gH5Kcfhjui*n3S~CxcC$yCM78?CV9A!XHc$-Ng13RgVA=O9l1BuUokH# zIx#r`{b_O|>S_Af4dr?d?1q{2n^00| zrbSWI0C}>)+Qv%s%##XAR!Pr9XA~NPAS*$(gRBPGRMp=z{;}Q@dJ2I`kTkcZm@mbu z6S%yDO%$7YLsGpR8j!Svq;b|j(o)hoM8&713w>kyr1c{&q63=1l4eRQ-*;0&=O`pi zC?+L7E+JJAG`%)q?K-U=&8g-g%7kL0Qc{!9eR51%N?h!4VOG7gRa*_s{m-B$%kQbuB23|$N3@9uFu)1z?$=igvT7{|HNd->1i=xT zH{%@&w7a|@$yI-jY6%yR%RJjE0}O#A-X}RKItDxbv8wlJr%ZGLB-Ia!N$wYe9iavF zaP&_TX$*-JmvIUcN~eKqLqQiHDpOPAVv-YwDXz}$pe)HONLurukkmd3vNB{CWEDtX zNJ^m9At|1%ASoMchTBU+4v!iXkD|~kLg9ns`liwbfI;c93*+NbQj@&}!OUC@lFo`J zf+H3AXBTtmDORRK(g1#tv=mReC==2`e<}JQ{X9rA@XzfEy2Ag|;6*pZbs^oA4w339 z(+j_Vmqq>guF6C@f1}h-S369O?Hf&&b%td$K+Le1=qT`2JrqwYfTXCO4oOQpI4ZTj z8`4IG%7>VvKob}%4Yx4X(KeSBLfR%n*vl(75R!)ZRw}S?OtV?uRfEhG830yEsVN5IrEN zUu_jM@2Xy}fGrsv6(7|%COSSYEe;*bld?)zv9iNuw2ubGCBzNF!cLcVm3DLz@Qnf616bishQRI*sOhza4Y{3Xpj z#8Q#*7a?gY98o!ZLCu8~XpiGahL((v4N=<7YfR%J8&SG)OG}R1i-xq<-p48(_8q2J zdKr>Dau|{Z_yMv4ERxj-!U>4z>4KA$`OOqJW(mv3&#oTMy=dQz~x z`L^7uWr|VByk41Nl`ZR?7FivM+1H}BQcL&p%$eAn#(HUr`zdBAS7msVu?)n33uubu*w%2`MYyUuxxP)H^u{ zLXdWqz9$&&6{ydzW?=gyy_ZpEh9GM$&2R|NcSMQW>Ljb029_lq^)j-flHS`WT2+(6 zyo1E(YSL8iAU01r>TP6KB)yMOtn4U-`2^`B90kE&9>%JMK|ce`PhlHUoC zqtwdJs54<71W6~V28dItOJTl2?2x2481da`u`judAU?n9hM+3Vi9rZWr!(0Uc_hPI?cs&D~CADg3)IW!=0dz3J z%b>4WQxK5ys6|bKzCW0<3S{ngVB`?Yr-ngylbGy(yIPt)+{vIH4%Sxb!Ox(30oFyb z^a{|MY70VZSx?U5V4g}VXM^qtSbM3BZ-7{(juaLcq>rkjm@b>CTLRWovh)eiRjMlp z-6YEz0c^U|sACNn{aTb)oD0b5Vn*GA9Wq%dO;n=4H*8pTc?(lKL@evgMBv_wNJt-nF9!^COs z0!6gW7sWuyvQB_5Nh#F~&~HRZ@l<#NgZ@4k1%@Dn`x^9By_8sHl2sjpJ{pW#mLPw# z4U)d8QC}KS5~#FyHi)6#l7&~0ZW@%q(uryT`r{}iqZ&(uWp3=FbZ$nAq8|-Lmtrv; z4EkMQi z7<9|Pf+S0q0R1(T$U)}v)gEXwAMyoe0bP&4^G4=r5o|o$HX;4EhS#y%;;gLT2s3DE7?c-LVvme5;f4s~Xq`N#DvS zx;K=(0wE8uv&UYG$y;0uuTMOAf3R(q|%PoFaeBajF@X+U|Xdr?TzB2 zU}-9LkrP^x(~-qtOgb2?mEw{UU+Ti^`58T=T-)N2qxMAb;{aq1w+_p=%suY zgJ=^Xg>^!pAYCfPWi|4bj&?HYA3>umP`)>IGge%QI0}}5VDJ+;$<3hO1BM+)6Ypfu zKLew12BSP1s$^m~y}m&=2`pHe5fo^S0@|Tb7BZMaOUWWKNZ%bhOYs}oyckT0cw(o) zFmL#SwtpqqO)aqvF-9jatR}*f>gIqUVrco{{gWyyNl8Rk2_C||rDmWx3SWGn9}lK1 zQMk9k@(fsK>RiyWW$;*i8RSn)p}5lC+|n0`zGrbQjuB;`>r`AP1=p9AWN)!P?alRm zZ4@(0xNZ>RWACnwXHV02k2IgRCl(Pdb%QF23aCK}c>ST@2&`janU_H1ZrhyB4C z%bQUB(Z+o(_2%KqNb+4k92_oL^a^6XO09Ysb=wh=t)w=+0(75H3Xx0AItoI#T$+ng z8@W_~Qaici3BQ`;(nyp-<2T_vYdof)C~ahluoz==oXSp zebdLFcYtB!ALNzV27MPWa)LSSXZ;Ugv=9I%;Seqi$Yi)Xq^7CnsS-)Lsh4vzw(_ETlFvfzDIb<)ei~4`n_GHryhbgHg7C`*6G435MvS?1kHE$)0egd|Pda5;;=2 zr%eX?n&$VZb;@QCdMWlP{RM)N?Q(zmAz(Cn_yzOW0QR+)J7BoaWLU~}I`&p(g*zki zW_WMOB0fkz4odt7S5T(3jZ)GKcAcj|-xiG0400-x-kd+w9RZ_(5It25`p;l-U}fk| zr|aJb6O=4#2C(&#KEX(*=meuaB3jANFe%=kUkV1lXH=A@?EpG3+Ag~zO>l;xbpI~4ZRkxoY zXKO1d%hjNt0)~7?o#UFbU@aue$N;@}y0U0HbLu8RrZbMG_3e))NP4j?L=pZw`XnIa!41Emo_P8_iiT$}q}i zu^p)RML7hu0mB(nSr7dfFlBHG_sw9q;i!GS0s9jCx<>JGa3W8PLJ6lloD_bf651;} z{4E%nrG%U3AmwUh4zXa$Y~fm6!vyN#glmAFzVW6EiMin*eFO{vqHg3miAwK?IIQ3Z zut3xyMC}c_S72dMn}z{;<6z|)EKGBQJ{yeYfJ}ney9`G2LnPKTh*gp#ixEM(v?SS( z5dpeeD8)*aBLeg-laX5IL5naROj%c& z@NO^~91(yV^&X7+L@!{?Q1&Fg9Vd&b&b++FO`q@V^N|!p=71g zq?Z}x5StWqY8SUjmshEb<%A-X9%hAu>Fq4yq+o68rL2G-;YD>XvdIhY?7C>%_gI~|`Fer1?@ z!5>(iky=aaQhgGbx`AB{x(8tR1rQEkqW*}D^=pP}>!kq@M*nRn1oGN$SBD zGDJ6hP>@YstTrG?#s8?hxFppQ{2-H;tM$bt4frke)ggaT>x)ae?g;c$m8(T}E*M>S zOszOhwfK-E{RvfnQq>oiWXVP7spBhZJxRLmS5<#Q)sv*+O_kr2{a;*x3QAkQL6(Ai z0Z9XVgrp8XLDGjL6+f$zDv2{l+6^Tksl7SO)S6lv-q$+o{#|COUl2r7=5AtP0NSa9_QvQvk zsz9~Am`s;_&;%9KQFBOY(*i%J!%$VWRP|w0i4RGtH>sQ?@zyH;CrN7*p|*<<)tpHR z>Y%e)@lTT4MXK%oNm7%p_~8OM3X-lHtzP#a?;B#EC_d2vbm^eX8o{^Y`+BwhHcdLc9gF^)3|I5d~Pvz9#lDAD;HljrE*kAk1?_Jl)e$Y9(ESLT(R?8aCXk^ zhUF6)f7lkX;;mKi>LZ(joLvo0UB)@iNNPMP{9&t&`Mv``oLV3A;c(4GyDs{AyB|m$ z^g1%L@05Yh+=F9&eG`ZHo*~LH&RQzu-p(=&0$uc@w?oK zR%yAb9;|Y>emQ+{hluRVlU1&i3^{IKOxoGK*SA@zmv*O3JwATRg|OZ(LwDVZYc$+?T+4|^@@fa# zmtQrhUe~-iv!@Nsd*HO)RIkB^HP?~XnV>icUlxD)m}N}!iK{Sw!SA6JN@UmA+ReL0 zp5q}y_e(7v?wCK-p^o0~rQgZI1C8$44p_U~Yg5#<1mjt|Tm4r*8r8nX!;5R{9x3X( zt<8^4EOn^vBwL9@)U%}Nm z$LH%i)*WpeQH329&aAQaXcpbMPSK(*3+zr^y0FG8XUCD~La*xc^NziCSi3TC-*w4y zkByYJ+r%QJ{ktQj+&z|(>mC#9DkbfSl!pCmDV+!FE;;`kDb?Of z2O_1G2Q8&l2TUwZDgt{87IDzT21?lnBc(OJSW3EIOl*)8{!66P<&dSc9V}5|hax5O z!AIQgS{D`;NiBqb4?5$_Kjy=6lS<(xuE}uEn@*mvB-#z_TW zgh?6EZP0BtA`%b~WQzkY;3O@z= zPQyO1SrR)9`|@GmX%n7B+yb^U-?DY|fZJDl=(^H3KKC9LDBO28jf57-l$)-!@51z5rac}#T0mb4{lA`kArZaF; zz6lRCTAhKL&caP+OpHtW!E(V|&zjgmDd{ZSbPjF;TP!)BgPYF7P3KH(iIfj^3C#Dr z34h4WJP$WrfSbUwC9ex`(?z)Hf{Cq=3cw1%LN1!vDk@8TtWfT75n|&E>x&k*{F|mzO_!YS6D%=FNNn%&wreEQvt0uNZ+5)x{%=%Xo z+a^W*3j40XKCtbQ)iv069rj%_u^eeXST30BbrahqC0&PoH((#wPm=Qu*mo25-7v8| zQa;!vFyEUdwpYr$3Hu6QAJ~4$s{r=hf_()hc2Ft+D+CL?MN_JN(3tO{Y@1K3w+Vi%?TV7Xwf4@~T`l=J}hJ%oK=S0(3%u*bOQ35$yX7_JI{hUcbS<$FT1=6T2-HfE9v;JT|erQr2VG_XPHV z-Itm?fqhS5-xCviAQgeV1&erUVvnTkr?BrC?0aTnkEQTuu&)UAfjyO25$t;o`-)7g zNZJCn6U_R#2~UMZJ%@cSU?12k$?65{dkOnqnD7kZez06H*Ow;tPD*+S`(D95un&^+ zE73cS(wK{P9$G6kX9Pc$en{}uCtDiTl zs8BTC%ii?9m*;zZeZ!%^^Wj5wuj|^>`g7F6JnxWx(uvnL>E7#O2c2y`WL#3~j}N-J zu3mHDKldk(s^M8PYhbI2Z~`d^-OrFVJPL;1=BBw~{jsH?x_T{CevA z>`dD^v16h;RGm_L&!q7U+<)$Q{N~!1&hvd|9r`Sdeq)o)26SP=&y9V4B5sRxd}QXa zu1>L2>+jigZ}P=7mwhSMY8US4TW(Od?0}5_oSY?WS?b>-yk7Z=k3QDukh$jL=7gJP zi_MOqtbNS#-OCj0m~nFH_N#$)%1tag<@TG-)5|g&i%YF6KlbkTWoW9Duuc)aQVLc!h=ipjhy(C_2lS=Cm&pIXjRp- zM~gdK_r^t??;}on(B7rhkq^OnS67aGy=Y3YsnOpgwU3#>%h12tj!|BAc77RAS^o9M zW%y0Hk%v8)_*+DGkIq7)uEUA zm8-k+*Rs>fg*}V4_PDh?WkB=R=Z-fjHajb6%g0DoLDK&o$tp@w_*qHXg`d`v)hFz* z&)8v~OstBu|5GHhk!(LlGFvGLKkcNW_-Q}SnMLy2j9Jc`z)Y;_ynN70pxl>%JMv5h z#bgnRyQFaDULq9!C7_rmLgB&-NKr_NkP=Y1@~je2EHH!O6)9@*CT38y)IqVz42n9u zh!k&05ut;k9?#Z6v8E&xx{^@1@$iyRbSVYJc2an7RtgGpb0}g;LE*)>kYXn(tj(eD z;Zf#L^w&dih!nouN)LsN1r%v|C=7f*DRN2SY5_$Vu+mVRCq)2vE)7NPGEht? z4Mk&~Pl`*V@GS#{k!O~HVzMO^cS+HNds#x^UlxjamQXa~1*9k>MMzmFLU>kLC>E51 z;uR@Ed6RNbv@8$Bs&Y`Y;zgu*ONxl{P?&gjc_`Lcu`=AuinVUjrg#Ld-f^!-$afoF zZJBWRQ0KOV;VBKLvyE>iSPht%v-VTXQ#0}^R19A6-tEG}7bRD_js39hsYjQ?rWyCU z|NLp~&XU#ZJMuOaSQY*Q`ep4ntAK&bD`MoB3e40dy!b$6=Dt1SQmf6hCfh@wy|n1Q zc}Gar9d*3)&zZ|T>UO%b{)N@^vrR8gSk&!DrMTjjFF+9r_iUoF1ydp(E-oy@ymiADrvV$Uy7m?yE zDI)Bl7|65jp;+Sph0XzrK|I_6iY`^5*iMQ>&ZQ8z(s^ceC?>l=ahDXC+{*uLNSeJ*MwqCEhu!gpqRnKYeCVaHWb@QF^jX>P?*<& zBBnMJS$qpAc9O!n4iplPssly;x=R96zLks*@ zFMsx|R>ct|%1&IhbbF_dA>C>ZVO__*D%&ai>EnIfM(ZcrjeAjj=IBQezho_`a3OU^ zu^8ffKXsdnZnp`fhQENNU@qX@nn%**A(BhjXr%_ zvP`SsPNV0H`+lX*kv;p*y$+r5S zIKOFReJpZUDQ+3Ij?Y=ag88`>ERap-Rx4S>#xrTQ_(aQ#s{HA7wyNu^>gZdihN@@j zuQ^ntKS~GiDC(lITGdajqQBS&t2%n5hl=zU-xjJ)d!UCNN^<2}&_x|@wQ8W++!9sC z0s6$Ny0R#r;Ez|bcGmPaWcsA2RTWWQ!NXT!GIQ0cG_}eaRZCH(`3_Nal~K+@*&LFd z48ni(PlJVdJe#`cpms4_t)i!qyj9%@Rc8mCm#P~H9Tn{XPhNyBte;Vr_++Tf>941k zx$SqXU7c%aS^;!|T2&oo`r|s;Kz}Eqo?L)sJnK75hODQLatf2*3?MY1Hu%ufL{zK^ z_@Y1)ov!L?p&Y~us0$;iXukAt5cO6EXbK(8m)t;?GaVw%SyRp*T|Jq|)2?V%$dl#6&ib#Yv+QqH~dgVX{& zIe=#~<>jJo{eTw$UA9c^)_`&zcV5FPc7BZ_eO9Pd4N*RgE~v>$Ro4jRd{sxA0RPbg z&GO&;sr`4VZp}KX!2>N@_|!FQQ93<#F$198F$NS76=7e0;XH#4}JIo4FDg&o5ACbD4c~y z4g*JkT;M2h44|Ev2b=&-0;hp|9f=mqoy+G602KpP+&pybdRXb*G(C^2{dK7f}P ztvr0?2~r<$0}9c}J>Wi20Ner|1C$~zz;X20>_y-*a0R#u{0dwHt^+p!C$x12-l6=S zzgiDR7oqFt06jEvpN0W00+)cxz!hL0a1fwJZ0LEOwZJ-HDX382T! zsso>Z9l%szIrMZ#qtrbO$OPI!e~I__9!{kD%X(lL@E=qWC@DvP(@i1>Fak}0ra&{` z4eB3aUU>YU{-Fu|D>UII^c#SUz;b|+ej~^Lzz^^Tngby~Lx6OU-Gve;=z!0-AORiq z0ycpsfK$VkzzgW|AU6Z_!1Za!GXUl4H{jp*=IY@R%9QpfJ<_J3 zRA~cA`ExF0Ye>qmEdUBK@?`~J4(etDvw$f8{h5Ib?U-|P6O%;xN8>^mPuk2X@(o*< z11nZer}n8pXP`gO2j~aH0zHAgKv#flMGg?U0FeOIk)C)DfSlV6_y(XpqJZ9j8$j1m zyBJ^~KmkaBIEW%M9))Cp97?N{2n+@gw!&n9qIME64A7#FwsR(s0gMDjsO8a+=>Q(^ zr+-K!5FZa@sAXz97MKWR0HdijDJKBq0BTH}pj}9HGzppjg+6&|Dlh|>4v=N!k!isH z#Dg=@ehST&I?MvfV<|sD{tnP`Q#epgxdYq+E&-HBDKstu=YX@o86Y2M575nvZfg61 zy}-}FZeS;n18fI=0JZ_&1LR#{unJfKd<$d)OMw3X^MKFzEQDMPECLn)9GDM~ zo;b;+z;a+2uo9sD)&XmQ^}q&TBd`hBN~^UQg)P7i;3r@gK(qZ3*aMKBun#x{`~n;R z4g!aPV?Zu&6rkHT#n35$EIbLE08RrJfb+m5fF`bZ3;r(vA=lgl$P2#$fdE;56}S#u z18x9rz-@r8q56-&J%HR!os#Px0xtpTOLI4QnLI()d;rL!?|`=$Urr0O1mynL0J;7Z zKo*euUjV-Wv|f*Z2S6b}YkMEK3!DXL*=TY!0UC%V`xJN#JOQ2q&wwI;CQR+pX{I-U zzp|W`fR=*>-2>1vXzr(_cn{EZR!q4;&}1oyDPZOAQ&FZH!CY_&_yp_)=!QZ!7SyH7 zwvp*xQ`KzAQKKzF6m0QIZ& zMPrG~Q9p(&I0248HGqx)+9do@ zHUMrweZU2%4%7i^0JVWyfGbcFs0Y*q=yQEWq z1{9dP0a_~BS=uhxhB5{5|0L(ouA&|01+>Kk+5z4eUoOz^3)=4yol(|)m!RJt==TU} zycVD{H64%p0d!3)&>e^YqJcg@FMy84bX5KZpzRq2*&Cob;(Z}0-=u4bK~PYKaX^27 zI*tbh0t0{qAPE=@Bm#7HO99N#$!thE@J|J(51KHY<%a@8fNZp(^ZXQmtfx8(`pLBZ zzoHNb(7~V9Z4BfnU?eaC7!70s>A*N(tjaSWCjk?H@xVkN8U4^?Xc9Al8NhU48ZZkW zeHK8=Hy2W(e*rTGg*Cu(fCJQs4r6rMUZ8T)Ed!PUOMw3XiviNF2C{*bz_)HU zima!PyANt|p8m`po0mX|N9D50Ij@eecO@9q$5~@B$L1*WXE|N+@RN&sn4MVFp0_;7 zyv1mHKI$a15mW8?d~i0FA3e$1*lVx0JpNeMrAf`be)2F4+>uNcaj#R%mTlrD5@$x$&Dt<^V7VtBF~y0O%x4++!Zk`1E1UUXO?Y-by1m1Hf^c}^jQj>am8#UM z(#Qu*H^Bf;bc?A3SLe_2nT_uqw6H*n{4VQGw|cqkuE@OHz2ISllnb&uYOa*1X>k6| z<^-;7;3dy#CU1NOW4z~)XPA#zx(1(q20qK@d!hByUS?_R`FzOxX3I9{(1kdx2)i@TQA?+8<&mxA5|D_xgux^SRYIW^1p# zQ?shW@9A0V?jKc$#fG}iXPsp}m>#tgJJsUXF_it^PeyI1&dFYTDd)4C$+H)l-}RR} z_QsTiBfJ-l{NMES{W&=3DlaHD15q7-r003Wo;owJrYrAz9?cam^Udd3TlRr}KF@4@ z6Y9#}vSoyo+Oy-1NA^U_#nau7=8}n;(papi$piNE2zziCHSjF?R*sZPB^yvHIY4cB z#s!4_|F4i=c9CVWQ{4FytAyv=FyQ_A%8M6cFB`W;xcMWZ{N26f8ak;sVUoFdCm-6i z@9D18aQEBT-cD|O-xFrz;)OO8vD$kmQ+7wJ|6VLt1cMqV8{$6Sa|xlXy@>L7 z#$`p>7hVl9gG%naegPcnO32`jB#kpSz#X&YjP^ zhbsm_q9y()vidUXhZQJy1(CCm8}A|9H*)8@tWrtYl3hIMH)hLcU%}es@f%lAeU-nX z#*et$RfyL-3c`VT$lrHnbhc}m?^v|0SX9>Z;G3^vNN*4Bc?*-%-mz+3e#=iIW+g1Z zv}UMRP0d_xoc*lBKhfDP*!!1vu?t;KHwdD)v7YZ`BI6OHch?l*XRp0&lpDyo=N`8zz%WYC zoD_RE;U5bSH0rwe#Wzv5T)U0=LmywX^~HtAN7S=rQ{^7C-nq-S?| ztYaPwP3~H-S~VQ2Zyv&bzKay9y`R@Re%p&oUd#x zKkX&61(UC)ZfSeW96e$2aN7|6J`JdQ*8cCe*dP7Q%ihObcO9>DA2)XG<+IcJ&fT*6 z%F!Dlqb-6xB#h))_Yq!4`1<>7@t>nXjBmo{7a}^o1~VCoKlc@jU+(z;1If|rBU*&< zj0eoyL3`)xFI_#~SWMqB3l^%|+snjzKVjC@;Ml)k3?;uRx#vE&f5>dZwAadJdsQBx|Q46N{Bz&#WUP+r!?Zw{nmufWpiv(=1CE!1&@$$%nM;|h0 z_KsUUVU^m+Vfa5CitZ2E3whU8y_S1^Ysl-rn1tx%#xowVP7d0;c)g4(T}O?xvXg_* zht4n9P0t=71*z^cL&wU*?Ehh6eEDyzvR<=L$waC>NNK37hRwPcnceU2bKn0RI&yl& zl&~+#KE}3x#A`jqe)nw4zj=&U8Pb-|hT2zqZ*b!m`?ee_@7o`?7?ez+y-C=ti+86J zCD+l7$rs^>95aDmLs#k)|IxDGC#+g=XDAV)T=HLd{EJf*Cn}M4lz(`_Y(4)vYW~Zm zw9ve5)q#(B%540!w-);~FV|;cDXEJYgQfD4YVS36{#?Cd`}jqBzt)6x;CZ;#L3iS1b#T7i72gqT^XTlb5~;jlw?aiBlNW ztl^J~aNpP7eB7j$G1P4R;-~70qCs#ZpZNka)!wE2Ti8PZ)jS^nH1?x6VT>}+{pxx-(0kC&KX=WqD1 zm$)&Hp{QMS{b@>-XE|t3cLfCVOnw6meE+^f?KREE%^%bZYkMpYmr`2B z+^2K5SLj=Nzw@g3OMlZj^=^U&v{BGt6_0ww+Eo1W39}Nr&G+L<7kw1I0QxVEm45$p zti+d2=)$&SWDWb;L*`;|Pj@sB-#=&uy<@Tbz+uF~ zw|vz*Jcsuy%Dl{bDBkhR-?0SRTd)W1G8Gi2UYG+f7_bCLf17#ZlA8Gre7APQl-J5$z4}n~SHQy@I;^k-)j%JzqpwQLgb4?E3NEACOL! zf%%*dY^;O!ZtUOV+2+C3n_od|`mF%xnI8SP=SO5&?QPnd;?|$ftN1g%H;`ipw|4D) z+jp*oABlThwWnMopXAij)?aK%^*r;0Z~2Hw$DYZ<80zT^JL&UBY>>a4le*(@imOa_ zcI|cC(G7MCYji5Iq+%sv7dI_(D^kwl-`RE?q{Baft>XhBM7MZ8@e_;1!$Xfj?X`DQ zFF90mb=@g>_{EA`MV(ya&Yy9a>Zwkjv0Tr2B2<3bo4k9U_&mtTrCSSJqh>1YUEj;| ze;k}&Hen`eD52oj1npN7_XK|FGtTkP=edeL4%(}@7q!oS-tFXk3$(`<3AC?!H<_0z zdW(Y-=dBUF*^YTvMIW4>$}!Q_PkUkbh^vm}^P=5`)0B~oFi3E9C9S+pm_N3X#dj{K zp>HP8UH2s3lZiHBOcEc4<1x$O>(P~{s@Xn%gNZ)&+8e_|4n{|xsP!Tl*Za!pQ&`B| zM9e_`O2k(6o$4A)Ym!!hZu-58oJZo>s@1W_q`qV z+{2r>wH=|oGCcG8qnLJQ8#*X8w9I{nDK8ygI`iG|W1jnPa+0qx`w~X>+Uv=8{cu0N z+%NCx3j#Gn4)b8ySqO1M2ff|U4u-D#So_Fw8_MPJ!uA*H7#l44l zK00C-_#^7?-f$(a`bDfiwW**=sN7Eewe>Z3)S;c)pY}TQNyq929QZWWfOeEN;G&a! z5ZXCt??E3tzg%IN`0?v#Vi*wnHhdKSRwuT`$?U04tmooBTDiYyFHFzqlVb`iwQh`j zfqeE;6Op3`%XLrUIjdD2Y%nI|8vcZGD<&=(-dhDnC2hk zyDX<`b#aqbDT+6A$HO~{6?3XL5RKK)PoECr>EbmuR6PCDez#E6S)D{D?phT;e)bnD=iKik-V(oDP=$+K#42BCMc%fHXeHB}%r4?L zR?I8sdYssaVd7hoFr5|yMMRcgPW1$_lT|6)4&<2*o`8;}bkVFN+Oasy<@89gG(R*! H-1>h2+;Xv~ diff --git a/constant/function.ts b/constant/function.ts index 5d852a7..8649484 100644 --- a/constant/function.ts +++ b/constant/function.ts @@ -29,6 +29,11 @@ const functionMap = { xAuthor: "wangyifei15 🕹️ Yingbo", xIcon: "🕹️", }, + searchAgent: { + xName: "小煎蛋 Search Agent", + xAuthor: "Yingbo", + xIcon: "🔍", + }, } export default functionMap diff --git a/controller/intentAgent/index.ts b/controller/intentAgent/index.ts index 2c7c5d0..65ca3fc 100644 --- a/controller/intentAgent/index.ts +++ b/controller/intentAgent/index.ts @@ -22,6 +22,9 @@ const BriefingSchema = BaseIntentSchema.extend({ userDescription: z.string().min(1), }) +/** + * 简报存储链接模式 + */ const BriefingLinkSchema = z.object({ intent: z.literal(14), link: z.string().url(), @@ -35,6 +38,14 @@ const CommonResponseSchema = BaseIntentSchema.extend({ message: z.string().min(1), }) +/** + * 联网检索模式 + */ +const NetSearchSchema = BaseIntentSchema.extend({ + intent: z.literal(15), + query: z.string().min(1), +}) + /** * 意图模式 */ @@ -43,12 +54,13 @@ const IntentSchema = z.union([ CommonResponseSchema, BaseIntentSchema, BriefingLinkSchema, + NetSearchSchema, ]) -type BaseIntent = z.infer type Briefing = z.infer type BriefingLink = z.infer type CommonResponse = z.infer +type NetSearch = z.infer export type Intent = z.infer /** @@ -58,8 +70,8 @@ const jsonSchema = zodToJsonSchema(IntentSchema) /** * 代理函数 - * @param {Context} ctx - 上下文对象 - * @returns {Promise} - 返回意图对象 + * @param ctx - 上下文对象 + * @returns 返回意图对象 */ const agent = async (ctx: Context): Promise => { const { @@ -71,7 +83,7 @@ const agent = async (ctx: Context): Promise => { let attempts = 0 while (attempts < 3) { const res = await llm.invoke( - "intentRecognitionNext", + "intentRecognition", { userInput: msgText, time: new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }), @@ -105,31 +117,51 @@ const agent = async (ctx: Context): Promise => { } } -const isBaseIntent = (intent: Intent): intent is BaseIntent => { - return intent.intent !== 5 && intent.intent !== 2 -} - +/** + * 判断是否为简报意图 + * @param intent - 意图对象 + * @returns 如果是简报意图则返回true,否则返回false + */ const isBriefing = (intent: Intent): intent is Briefing => { return intent.intent === 5 } +/** + * 判断是否为简报链接意图 + * @param intent - 意图对象 + * @returns 如果是简报链接意图则返回true,否则返回false + */ const isBriefingLink = (intent: Intent): intent is BriefingLink => { return intent.intent === 14 } +/** + * 判断是否为通用响应意图 + * @param intent - 意图对象 + * @returns 如果是通用响应意图则返回true,否则返回false + */ const isCommonResponse = (intent: Intent): intent is CommonResponse => { return intent.intent === 2 } +/** + * 判断是否为联网检索意图 + * @param intent - 意图对象 + * @returns 如果是联网检索意图则返回true,否则返回false + */ +const isNetSearch = (intent: Intent): intent is NetSearch => { + return intent.intent === 15 +} + /** * 意图代理对象 */ const intentAgent = { agent, - isBaseIntent, isBriefing, isCommonResponse, isBriefingLink, + isNetSearch, } export default intentAgent diff --git a/controller/reportAgent/index.ts b/controller/reportAgent/index.ts index dd44497..afd6127 100644 --- a/controller/reportAgent/index.ts +++ b/controller/reportAgent/index.ts @@ -154,7 +154,7 @@ const agent = async (ctx: Context, link: string, userDescription: string) => { validateLink(link) // 发送一个loading卡片 await message.updateOrReply( - cardGender.genSuccessCard("正在为您收集简报,请稍等片刻~") + cardGender.genPendingCard("正在为您收集简报,请稍等片刻~") ) // // 抓取网页 // const crawRes = await crawlWebPage(ctx, link) diff --git a/controller/searchAgent/index.ts b/controller/searchAgent/index.ts new file mode 100644 index 0000000..ca17816 --- /dev/null +++ b/controller/searchAgent/index.ts @@ -0,0 +1,70 @@ +import { Context } from "../../types" +import llm from "../../utils/llm" + +/** + * 生成网页简报 + * @param {Context} ctx - 上下文对象 + * @param {string} userDescription - 用户描述 + * @param {any} content - 网页内容 + * @returns {Promise} - 返回简报内容 + * @throws {Error} - 当生成简报失败时抛出错误 + */ +export const generateAnswer = async ( + ctx: Context, + webSearchResults: + | { + siteName: string + summary: string + }[] + | null +): Promise => { + const { + requestId, + larkBody: { msgText }, + } = ctx + const llmRes = (await llm.invoke( + "searchAgent", + { + webSearchResults, + time: new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }), + userInput: msgText, + }, + requestId, + 1 + )) as string + if (!llmRes) throw new Error("模型侧错误") + return llmRes +} + +const agent = async (ctx: Context, query: string) => { + const { + larkService: { message }, + attachService, + larkCard, + logger, + } = ctx + const cardGender = larkCard.child("searchAgent") + try { + // 发送一个loading卡片 + await message.updateOrReply( + cardGender.genPendingCard("正在检索网络信息,请稍等片刻~") + ) + const searchRes = await attachService.webSearch(query) + await message.updateOrReply( + cardGender.genPendingCard("LLM输出中,请稍等...") + ) + const answer = await generateAnswer(ctx, searchRes) + await message.updateOrReply(cardGender.genSuccessCard(answer)) + } catch (error: any) { + logger.error(`searchAgent error: ${error}`) + await message.updateOrReply( + cardGender.genErrorCard(`检索失败: ${error.message}`) + ) + } +} + +const searchAgent = { + agent, +} + +export default searchAgent diff --git a/package.json b/package.json index ac06a14..0b009dd 100644 --- a/package.json +++ b/package.json @@ -17,12 +17,12 @@ ] }, "devDependencies": { - "@commitlint/cli": "^19.6.1", - "@commitlint/config-conventional": "^19.6.0", + "@commitlint/cli": "^19.7.1", + "@commitlint/config-conventional": "^19.7.1", "@eslint/js": "^9.19.0", "@types/node-schedule": "^2.1.7", "@types/uuid": "^10.0.0", - "bun-types": "^1.2.1", + "bun-types": "^1.2.2", "eslint": "^9.19.0", "eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-unused-imports": "^4.1.4", @@ -30,7 +30,7 @@ "lint-staged": "^15.4.3", "oxlint": "^0.13.2", "prettier": "^3.4.2", - "typescript-eslint": "^8.22.0" + "typescript-eslint": "^8.23.0" }, "peerDependencies": { "typescript": "^5.5.4" @@ -39,13 +39,13 @@ "@egg/hooks": "^1.2.0", "@egg/lark-msg-tool": "^1.21.0", "@egg/logger": "^1.6.0", - "@egg/net-tool": "^1.31.1", + "@egg/net-tool": "^1.31.2", "@egg/path-tool": "^1.4.1", - "@langchain/core": "^0.3.36", - "@langchain/langgraph": "^0.2.41", + "@langchain/core": "^0.3.38", + "@langchain/langgraph": "^0.2.44", "@langchain/openai": "^0.3.17", "joi": "^17.13.3", - "langfuse-langchain": "^3.32.3", + "langfuse-langchain": "^3.35.1", "node-schedule": "^2.1.1", "p-limit": "^6.2.0", "pocketbase": "^0.23.0", diff --git a/routes/bot/eventMsg.ts b/routes/bot/eventMsg.ts index 1b18901..ea681b0 100644 --- a/routes/bot/eventMsg.ts +++ b/routes/bot/eventMsg.ts @@ -1,6 +1,7 @@ import groupAgent from "../../controller/groupAgent" import intentAgent from "../../controller/intentAgent" import reportAgent from "../../controller/reportAgent" +import searchAgent from "../../controller/searchAgent" import soupAgent from "../../controller/soupAgent" import { Context } from "../../types" import { isNotP2POrAtBot } from "../../utils/message" @@ -68,69 +69,69 @@ const manageIntent = async (ctx: Context) => { try { const intentRes = await intentAgent.agent(ctx) logger.info(`intentRes: ${JSON.stringify(intentRes)}`) + // 链接总结 if (intentAgent.isBriefing(intentRes)) { reportAgent .agent(ctx, intentRes.link, intentRes.userDescription) .catch(() => null) return } + // 设置链接总结的结果存储表格 if (intentAgent.isBriefingLink(intentRes)) { reportAgent.setSheet(ctx, intentRes.link).catch(() => null) return } + // 网络检索 + if (intentAgent.isNetSearch(intentRes)) { + searchAgent.agent(ctx, intentRes.query).catch(() => null) + return + } + // 通用回复 if (intentAgent.isCommonResponse(intentRes)) { await message.updateOrReply(larkCard.genSuccessCard(intentRes.message)) return } - if (intentAgent.isBaseIntent(intentRes)) { - switch (intentRes.intent) { - case 3: - await message.updateOrReply( - larkCard.genTempCard("chatId", { chat_id: chatId }) as string - ) - break - case 4: - await attachService.ciMonitor(chatId) - break - case 6: - await message.updateOrReply( - larkCard.genTempCard("eggGuide", { chat_id: chatId }) as string - ) - break - case 7: - groupAgent.report - .setSubscription(ctx, "daily", true) - .catch(() => null) - break - case 8: - groupAgent.report - .setSubscription(ctx, "weekly", true) - .catch(() => null) - break - case 9: - groupAgent.report - .setSubscription(ctx, "daily", false) - .catch(() => null) - break - case 10: - groupAgent.report - .setSubscription(ctx, "weekly", false) - .catch(() => null) - break - case 11: - groupAgent.report.gen4Test(ctx, "daily").catch(() => null) - break - case 12: - groupAgent.report.gen4Test(ctx, "weekly").catch(() => null) - break - case 13: - soupAgent.startOrStopGame(ctx, true, "manual").catch(() => null) - break - case 1: - default: - groupAgent.agent(ctx).catch(() => null) - break - } + switch (intentRes.intent) { + case 3: + await message.updateOrReply( + larkCard.genTempCard("chatId", { chat_id: chatId }) as string + ) + break + case 4: + await attachService.ciMonitor(chatId) + break + case 6: + await message.updateOrReply( + larkCard.genTempCard("eggGuide", { chat_id: chatId }) as string + ) + break + case 7: + groupAgent.report.setSubscription(ctx, "daily", true).catch(() => null) + break + case 8: + groupAgent.report.setSubscription(ctx, "weekly", true).catch(() => null) + break + case 9: + groupAgent.report.setSubscription(ctx, "daily", false).catch(() => null) + break + case 10: + groupAgent.report + .setSubscription(ctx, "weekly", false) + .catch(() => null) + break + case 11: + groupAgent.report.gen4Test(ctx, "daily").catch(() => null) + break + case 12: + groupAgent.report.gen4Test(ctx, "weekly").catch(() => null) + break + case 13: + soupAgent.startOrStopGame(ctx, true, "manual").catch(() => null) + break + case 1: + default: + groupAgent.agent(ctx).catch(() => null) + break } } catch (error) { logger.error(`manageIntent error: ${error}`) diff --git a/services/attach/index.ts b/services/attach/index.ts index 74864d8..4665b4b 100644 --- a/services/attach/index.ts +++ b/services/attach/index.ts @@ -142,6 +142,37 @@ class AttachService extends NetToolBase { throw new Error("MIFY爬虫请求失败") }) } + + /** + * 使用bochaai搜索网页 + * @param {string} query - 搜索关键词 + * @returns 返回搜索结果 + */ + async webSearch(query: string) { + const URL = "https://api.bochaai.com/v1/web-search" + return this.post( + URL, + { + query, + summary: true, + }, + {}, + { + Authorization: `Bearer ${APP_CONFIG.BOCHA_SK}`, + } + ) + .then((res) => { + const { value } = res.data.webPages + return value.map(({ siteName, summary }: any) => ({ + siteName, + summary, + })) as { + siteName: string + summary: string + }[] + }) + .catch(() => null) + } } export default AttachService diff --git a/test/llm/intentRecognition.ts b/test/llm/intentRecognition.ts index 1a72294..bc524d9 100644 --- a/test/llm/intentRecognition.ts +++ b/test/llm/intentRecognition.ts @@ -8,34 +8,63 @@ import { cleanLLMRes } from "../../utils/llm/base" await initAppConfig() +/** + * 基础意图模式 + */ const BaseIntentSchema = z.object({ intent: z.number().min(1).max(13), }) +/** + * 简报模式 + */ const BriefingSchema = BaseIntentSchema.extend({ - intent: z.literal(3), + intent: z.literal(5), link: z.string().url(), userDescription: z.string().min(1), }) +/** + * 简报存储链接模式 + */ +const BriefingLinkSchema = z.object({ + intent: z.literal(14), + link: z.string().url(), +}) + +/** + * 通用响应模式 + */ const CommonResponseSchema = BaseIntentSchema.extend({ - intent: z.literal(12), + intent: z.literal(2), message: z.string().min(1), }) +/** + * 联网检索模式 + */ +const NetSearchSchema = BaseIntentSchema.extend({ + intent: z.literal(15), + query: z.string().min(1), +}) + +/** + * 意图模式 + */ const IntentSchema = z.union([ BriefingSchema, CommonResponseSchema, BaseIntentSchema, + BriefingLinkSchema, + NetSearchSchema, ]) const jsonSchema = zodToJsonSchema(IntentSchema) const res = await llm.invoke( - "intentRecognitionNext", + "intentRecognition", { - userInput: - "https://mp.weixin.qq.com/s/-0J8XbXJU6Bu-UihRtgGAQ Airbnb死磕React Native惨败,微软却玩出花!Office、Outlook全线接入,Copilot成最大赢家 推荐大家看一下,rn助力微软copilot 跨平台实现", + userInput: "今天是哪天", time: new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }), jsonSchema, }, diff --git a/test/server/getBatchUserInfo.ts b/test/server/getBatchUserInfo.ts new file mode 100644 index 0000000..95356f6 --- /dev/null +++ b/test/server/getBatchUserInfo.ts @@ -0,0 +1,11 @@ +import initAppConfig from "../../constant/config" +import genLarkService from "../../utils/genLarkService" + +await initAppConfig() + +const larkService = genLarkService("egg", "test") + +larkService.user + .batchGet(["ou_5d9c2da2870802fc47fc2066f28b1b49"], "open_id") + .then(console.log) + .catch(console.error) diff --git a/test/server/webSearch.ts b/test/server/webSearch.ts new file mode 100644 index 0000000..e1fcb6a --- /dev/null +++ b/test/server/webSearch.ts @@ -0,0 +1,8 @@ +import initAppConfig from "../../constant/config" +import { AttachService } from "../../services" + +await initAppConfig() + +const service = new AttachService() + +service.webSearch("北京今天天气").then(console.log).catch(console.error)