From 413e327a19a5fdb20a545df8fddd8c0254200a76 Mon Sep 17 00:00:00 2001 From: zzlgreat Date: Wed, 17 Dec 2025 15:12:26 +0800 Subject: [PATCH] update pay ui --- __pycache__/mcp_server.cpython-310.pyc | Bin 90229 -> 93319 bytes mcp_server.py | 208 ++++++++++++++++++++++++- 2 files changed, 202 insertions(+), 6 deletions(-) diff --git a/__pycache__/mcp_server.cpython-310.pyc b/__pycache__/mcp_server.cpython-310.pyc index d8e93f255b2f0c52a8f6d47f23132d15d623c604..e06d1636aec27e5a68560a948c48edc17cede0a7 100644 GIT binary patch delta 8427 zcmcgRX?RpsmhZlL^-`%+k|GIYCzU`*DunD1Ov1h?`zA<83~6qGiI9X>6(I3G3q;hO z5sh5r2?R{qw#DwY)sEe9poDvs%?>nN1l2zLXu9d5}#ha z?y13DTldb6Aoucj_Gj82CX)0p(Vpn~asQAo!ugQLrjti_^<&xO_x!fUy2uV*_IPr{ zp8!kC2Hkpw@OvH{(zX8aa@qMCTzm+@egqHm7rL5aa1r>;1KiMZJgp-qT-2j{^>o@SP${l6OU9SI#rV#A0pj z<0}pvvnbk&eBxiW`AO5F)smhL-wc-CM!Z{@t3IKPXyR8-pcO1(y#(uxbX;&W-W+LL zeNqRZ6FLcz#AqT*pqX^!R`BE=X|IgyOzKEv37tf6w`h|D-Xx#Q(zlR35}oYdYY}L1 z9pTI+MkzG^Mi~@1OQ$db?53LRXzpGMowiz{)7vfjFqS@5s?ODiw>sJI`lR|~z(3$i z=}0waouq-(NqXi|sX-+Y{^4wy#D>usi4sUcnxQO{Ud{Ysh}zO?+J!1@WZfg6brhY+ zhIPtIfLP3i(phwNQ13aQOnSQ<*av0WKG23NBJ@X!k4pL zHp)+{wyAm^ON3L&6X&1Hl4vzcp(XJUMD%)LqXMjx0mIbUR5@^5!k!vrNd-f?qTL!W zWdzIhW%;sK$y?Bj*M-8lrZ*ZaJ~zwVLUeyVgV3Y3ESu#+u>Asqjjl8!K{%DNQPi_X zp{vY5g;SfSNWPdhOUq~?>1jl2o%|CE-7F1Q7X&R`%?g0a4VM{;F;-J1G&d3I`Ze!Y ziPp0MJhuS~#nKziwNAqS@KSGc58Hd0-z4`;(^RH6je zV>oSsUpHOHQbK2}XA6ZjR7s33kMAqQvp;YCy9yQdv&#e8jOQ%s#fw9IBC4AM8CmH+apB&dM)m>`*-Fdr0+BuSe~s*A|3hg1 zY-DHA^#SEDx(6zC#pvE}NpxG*{ZeSZ5m(!kbuWUL)Eluv*?l=zjNcX!zsY9&ZnNlx zfqdRdZwlpb$^=#-V(CRRM=xeYZGNjaCU7~dS`2fwSv6IZ$G|Tj;Z@M-&8)bNm|3Uc z9_qj1`c`0Q^`_HJSKM9SdKGijN7p;+2eC*NM@v~0-R!5gY_sYmtOSbPFnu%|T^|Q3 zEW}@$pD5Ljgi3wcTpz=V*%-(JA=Sq8tJY>+S0H@11$_sZtU{c(wPh!qcPV7DeAtbp zx3bbevG7ZDYez1XrS6Mp+QP&FA0cmPsVVg0BrP_=13jElAqpG^OqHu@p&h!fdDSRx=k3g)HIu(Ct`3OIhi?JBN9=# zR(Y+lpm8B1thiGS6t;5V%I&N?;4E}#u5hT@G72180S-+Q4&|((9|x=>kVbdhh-#*b z{$__LIl1vr;Th4(mrBB46*pHjSpDEI7@)&^MfJr@VOFNLsZC?(y(|oV%V-W{@?uuT zu43hM=eF=b;xw4jFOZdkpe4fH_t9OfygwE|+0=ltzQApF`lR|;x|^jTE!|A_Y~zo- zH1wKwx|b<*U%!Kw!f7s&0;G=yNYe%B{ZLb(@CC``KyvP&ZrbT{^SGDYZe2&8b%)6}YE4cgSTf{^T2JQ9}UDb^OWSp1AA&lRNI`pS(fIH+<@w6XKlx z8v~f~Ti(3e4VOpK3dLNEYXty^M*6-_`GutIBxCsBOFMN}G|InoG;4|;pX zl^#~{vNeKle*0?jJO29H$?9Pgp28i6lDPHI1L9>BFBeDfzaCl$FShi1!s!53P*ItgBbnir80QKlEZ+Fuao7>=IBk$9;^e-87>i2NL^wU+Vp{jmWbraGTT zv=?YaZJG*<<;KeE$>%EbfCC7sB|}f{{8rj9xL^% z@zrWoxIPQlU&hf)9L+}X2EX`z3K_?f59dX^iCeV@4)Vo^tKn+@@$jT+m4g~;eRb#H7G*#U{i)yCZR&s(`}c}XCQ*Q&Vzq6OPuNo(#I7hr+AM9h ze=hmEW*k{bQjee&^8n1%t_A3}XmfDo41y{A(6LzcKVf)N1pl-r$B$>c5@WFcgI5kO z?-clY@fazg@)}8(;el_V7F}U-2Z2IDm|QP+T68P3LUS>?O4e093|`4Ls_5YYC!B=^ zaO}(uI7xsLp*xtP!_q0~j@EFW(&4u#6pI1@S$cG<3z%A2G>U*0!|4lSPM>WHyf!I})=N;?k)M->LH2WY zg!`$jPU@8VnS%<8O%*`&Vj8Xw5`qRX=*{#P76U>Oz=~Kst~FW+iUT1w!7?_aB5=7a z$mKR{fvMf+U>5O+ieMH_pJj{pgoA%yW`Q` zJ2%6&nea~^*d5#|%ySK>hOUZL%?&Gkb&U<+D&(l=A!D>Ga0Q}m`I9!jQ z8^K}VqkWCwG=eh-M!<%9u$e_LUub6$^EiM?TT`tM!l0eq7-!rT&}8I)5LlvC7W6;v7~s^ETcaKBho zf*u>V)|8K(%YaVh1gP(isD&hV9cNZSsWV@9@LR8+{& zFph$C1$OL^hm}n#Fy++baX+! z`R}sm;cHRjzv1W&1k-UEjo>s+F^Ov@2tSk(&wqb3ocxym`RGHc>6^Iw;yvk5lg!Yxne`3P3N0k3Gx4c;lWEuokaUb z*Z0S^67p`>=1)45_zQqD_hDHCOOJI;;|7lgv+wxWI zL%Uu(K8BD_xAz{E^BnwG?>@Q6!S$0#!@{wPp z5C0sw{}%$R_LY&E$J^A{01XbUTj?|7Ba%0L5eF={e(@Pe>RSEfY>WCG5`4#3^d|64 zXRLYO> z6nNYmIX!A{1TKGy?8UQSHI5uS`OH1?vkv};GbKsUh~Pm3S0QlXbTy6!@gL5tii^a# z2>M`W{sCWqwuD6S-<{1QhxnnhCF(zct2=_b4!bYu#wL8d3q9NOb;`IHWD|=Z4tdAn z2or+#4Nj*b=)-9(jt(IZ$%Nu)FfTrrH9v=JyHt}FZ7*9qz5<(y5-_v8|gLfQ%QyT1=^R_-J_EG!%2R3tCRFalY;J*E;7VQ3cFh~$u3*6IQ$xP zf{1_>Fq&&RB8Y>3A4i9cw9#Z_%uyVRu6!aOxvkn!qiQr6GbXgY z0Ey=#ScqT(0@1|w0O?|&2ys7bygHgJAjgfQF=TTYmLN?i*oz|-fv9H_fkltvt6S^Q zgiT9u=OhAgbh)zeTmgyCPSGla#R$eD5Xw`)tF*6egqMt{MB1B1Tq(&Qr;O54GQ9Xp zWb=0f=2HVlC{KG3fv`oGA=2qo4+eInKvJCRh&4@DcsL{j6Qh#MiVp-k{ii15-aZ4#ni>RLFcE1_SC zq)kSRECVow&!*rd(}X?3?RfJq!NLXYQDgTck}|RgaZ8X-_yD7Vt6(1N3%19N&nA%x zVXvVV3x&~R!+=pNGMkN9a}dl$@CiyJfCb}A^V%_}irPFwn@n=uAs=2*oH#sWKHqp{ zGRY+Kjjtw?p{kkXj6qXKMw<8!P<;7lsS7BSXnzqi-7dc&fMu<*2N%z8KN*988ZV z;ReRI+ot_IreU8!O5|v#F>wZ&8(s#~b3yZIhB1Q_#oL{bNT94q@+MhoP9t$9g!)or_Du2{a-FeY9*H&7StMSbvfZO$p+CjgMh$vy>k|L{+zMHrYhdszX?Ic#X?)6uhLPzyt6g{!zQW zJ9aK)M^cS^Gt%k?B1c`0yrO&iJTjP&8OHwkBsX;oSX!wzHhCIq>ogI7BB3lm_r(}z z=aVLqVftaNOH(yV;{wK5Y#KGIS=83G=iC8>*20hfoO{o5|M%Q; zm*K|)hD&c4d>egy%o6?-FPL3-=+XVY`&*!!ztNHcsr;Lk|3TcfZV{v*hH&rJa)YT- zvZnX7x5|@!>R$Geq;o68&u9E+uZ-{e)vgjR_&LA0H`cTlB?sj6Zrq`nb7z1{duxw z4|P6G@C?CTUV9*jU+!DtPYN8Vj683vYJm&_`i>t+2jgyXc%{$hr6e7^$a7vi>UU@|&;x^3dA{{|aSEa#+lLb6=eXI(^$LzW%k-I-L^6ANp-)yH4?QNMFSll-JokDzI2KbCVn*p=I(e9h;lRxdO_o#dKSB{dj9qTf2H-8E^6q(-yZArcNo zPT}e#R_K;?O1oq>r(2Neg639L>hWdws?ojjVssX*iL8j-Hvkn_l$#|| z1GGpYhsD$u(mXYGL@(O+t8ru6B^WzOjc3{IY<{Oaq7pMIRm0U-R28~Iu(EEwdp1sU zk5{kcN@)A38q3OYIb@|!jqfqA3YOR_yR52}Em$j2uLvH>vRNft*r~%<75S8{Xo{=q zdUeGTi=8XE5;0;shEx-RrJfiJo#dP}Joq9tQB7itJB_YnH3SzXSyVxy8p^8FaF!87 zYyW^CsK?M@Xfc}d@REt?ETM?Z9k06EFg%sXYN9jN8Mi`i@1S`+B^W0nW6VZ_&Ka*J zc7SUN6(D<1jZ>#!$t|UlW6RvtAd>nJQmomjXAir}6&I}x7tOy**Qx`_6$w(?YoL=b?KCca9dhOKGBG#wwg7l>2=^Qp=jCb_1m)9isb^mFvdQbQzr zqM$OS?aENoRm#*IY8fNcFk4$h?i`dmT;TlS1^yZrI1?8*6}17(sb-FBA-1L`shzDA zsY%1CX`!m26!n5@I$NhsC&N)g-Od6Aie#0Hj9}O=^I}XGr9rUNxx3E`8Hhwn_EC&+ROcC14V>)hsoKZSM3O zJ}-Z)>PH4tb8w(vi*#>cKT&f=Y5~bc43j-PyuB?>$sWM|LyaU~EWZEjl&MPie;*7i z6#;(AI#oT}I+E%t$j|W1!INQhAQ7R9S=lycJb&yRD_;07ymKCY z!=L!WDLC5K{BE)VyMFQB7XHlpktPdXv{9eF{2c%2eFkfI&IdDj=LcJjPtbMzB#-_u z*h?evDS{n*&WEj*{~~cGfw<Ho@^c?;^gm90O$b(5IY}>oo%Am7X~!px1Fv!^ zPHM~Ja3G4`{8Je3K6c+-pAqtAGxt6|H{v;rv(Rc#QYg@BirheuO3h=u`S>C6s0fk| znECt@3xM;U6A|u!uae#E$48Ly5RR`nP#?%|p76B1M(WcP=C>q?r-|QLZ2z9@UdO(+ z++mjfeAP)W%kRkS4T6INhxq1`HO7xgY^M1c50jrH;)f4-`VXSU3W`XE0AVG6Wai}q zaqup08BpO2|6!oT_~Pwe z95r@FV}tS-Ih^5jr*mN{-+g+A)uamsv(BvZ*U2D*@rKzTLlhYC*MxsEc!T#1Utpu>$Qr_< z&sA96FH5ptL>%8D>5l|O)bu0xf|`HkJ?Em~X@2P3GjeD+Z#@6Ips%psfDdSg39ZbM zo``|+6>mNt!5_I$49EBj7osd=rQ}eYVuLez>pri~Hv#zdZTq4} zAM^v-ls+pf-=XiIp}w)jreNz^KKEiOB=L0@GkwWIxrDH0BHHj}*CzH2T$~PYnp?g* zCELCDSKs|YZt&ur--p8ceS5$601MTba*E(iEqZT9CJMeWr6T17Kl?)re8LTvQnB7L zFInX)z#qOe4?gA3Uy6aZ_|Z!-0U|$3a7e2~`GRJ46}awl798Pum!l`2q3E9z(8^Z( z;P-;2#s-J2hSe<}{(7M28!iXJWd6kEAsE-!a-~G)@hv%Am3Z%oa0|XcCFOg9%LIR; z?*U#{QGAVvUIQL@ZK}~dd0uiYZTxkzx8Q4g-g202CD@ihO?;9%u_0$&MZs?1RGJp`kH{Zwr z1Ap~KEWFQ$P`=0~+#I`o%5LtU9DaT?e1<1QFcSn)1TT`}h{h#q&LQ}enzbZ-K(LmY zj3jSfcq@LUD8+BFjhXi+cai=yvZDMbKGf$+;74$T`j(Ts*vl6F>@BN%Z;N^8`RQ9> z^8b19tGA9q=!<`Vcfb&ie%acSGDJhT_Oc8!Ax!(53>WmoR-23cno$pv{HR&(jgcd0 zAg}&xJ-iFhqm9!;uyG>xJ?gDR8*$%G?4NCfy|Q&sR&0Yh$C^h5jdRKt7L=3>dMLK0 z`r73-MMaucqd29MC|Mil2^A4LFt)=Vshe;({M847NoVr#1(U4A_P2Y21t6}!%M9a9 za8`TTAM%Z#(QM=U&-=qKJt3igOE{eO18aYCGE6W)V*i$O*kKA2RZv4I5vA}jN~P`; zy{+Zv!VGv@Ys-aXLmEy&uRWg&A&{oMl?zTw98ENyAc4TD73aacalXFD(?~pJGxAh2 z`giBSY=BJdTt4K%cx}=Qh&E0qkM#a|Ghm|*KGP1*g1Ee+M7NokyhoC_qmrokCu|le z+YzlEqi!-$1|N~>F>Oi#B>4}JEKc58=!Uo6fMzd%=`f)67C=Ps2~wX#7<(Ze(~cKF z1q^8kvteWI5E%(3XGy|us*Xu0>pP;d%bSoKOKxd=A&F90P)#qx&S&BFi8q@@=vauZj#B z>K=j*$fba!bnW3XNDM7Tx!PxWV}sMS&RNq`t2iCX26D{Oc9%g`IMJv!D~uu+ymuW^-*2XOZs~rgz*5gw3n7bazrZ5 zz1rB=WNWCcQ$*Q{GiM$p)>FH+6q+DJYgz`^+r`6)9!g3J!8o!AC8(s-(>>+BLFuwl zrV`8~h()M2FDNUyw`O5kSxKe4An7<)M16|)u{hF1eaZfkPZH8yBp sJOrMR3rQoc=S8G8H4e{NsUpP^EuaQ6AiIBV4OqQp' in content or '```tool_call' in content or '"tool":' in content: + logger.info(f"[Agent Stream] 尝试从文本内容解析工具调用") + logger.info(f"[Agent Stream] 内容预览: {content[:500]}") + text_tool_calls = self._parse_text_tool_calls(content) + + # 检查是否有工具调用(原生或文本格式) + if native_tool_calls: + logger.info(f"[Agent Stream] 检测到 {len(native_tool_calls)} 个原生工具调用") # 将 assistant 消息添加到历史(包含 tool_calls) messages.append(assistant_message) @@ -2557,7 +2570,7 @@ A股交易时间: 上午 9:30-11:30,下午 13:00-15:00 "reasoning": "使用工具获取相关数据进行分析", "steps": [] } - for tc in assistant_message.tool_calls: + for tc in native_tool_calls: try: args = json.loads(tc.function.arguments) if tc.function.arguments else {} except: @@ -2569,10 +2582,10 @@ A股交易时间: 上午 9:30-11:30,下午 13:00-15:00 }) yield self._format_sse("plan", plan_data) - yield self._format_sse("status", {"stage": "executing", "message": f"开始执行 {len(assistant_message.tool_calls)} 个工具调用"}) + yield self._format_sse("status", {"stage": "executing", "message": f"开始执行 {len(native_tool_calls)} 个工具调用"}) # 执行每个工具调用 - for tool_call in assistant_message.tool_calls: + for tool_call in native_tool_calls: tool_name = tool_call.function.name tool_call_id = tool_call.id @@ -2675,6 +2688,120 @@ A股交易时间: 上午 9:30-11:30,下午 13:00-15:00 logger.info(f"[Tool Call] ========== 工具调用结束 ==========") step_index += 1 + elif text_tool_calls: + # 处理文本格式的工具调用 + logger.info(f"[Agent Stream] 检测到 {len(text_tool_calls)} 个文本格式工具调用") + + # 将 assistant 消息添加到历史 + messages.append({"role": "assistant", "content": assistant_message.content}) + + # 如果是第一次工具调用,发送计划事件 + if step_index == 0: + plan_data = { + "goal": f"分析用户问题:{user_query[:50]}...", + "reasoning": "使用工具获取相关数据进行分析", + "steps": [ + {"tool": tc["name"], "arguments": tc["arguments"], "reason": f"调用 {tc['name']}"} + for tc in text_tool_calls + ] + } + yield self._format_sse("plan", plan_data) + yield self._format_sse("status", {"stage": "executing", "message": f"开始执行 {len(text_tool_calls)} 个工具调用"}) + + # 执行每个工具调用 + for tc in text_tool_calls: + tool_name = tc["name"] + arguments = tc["arguments"] + tool_call_id = f"text_call_{step_index}_{tool_name}" + + logger.info(f"[Tool Call] ========== 文本工具调用开始 ==========") + logger.info(f"[Tool Call] 工具名: {tool_name}") + logger.info(f"[Tool Call] 参数内容: {json.dumps(arguments, ensure_ascii=False)}") + + # 发送步骤开始事件 + yield self._format_sse("step_start", { + "step_index": step_index, + "tool": tool_name, + "arguments": arguments, + "reason": f"调用 {tool_name}", + }) + + start_time = datetime.now() + + try: + # 特殊处理 summarize_news + if tool_name == "summarize_news": + data_arg = arguments.get("data", "") + if data_arg in ["前面的新闻数据", "前面收集的所有数据", ""]: + arguments["data"] = json.dumps(collected_data, ensure_ascii=False, indent=2) + + # 执行工具 + result = await self.execute_tool(tool_name, arguments, tool_handlers) + execution_time = (datetime.now() - start_time).total_seconds() + + # 记录结果 + step_result = StepResult( + step_index=step_index, + tool=tool_name, + arguments=arguments, + status="success", + result=result, + execution_time=execution_time, + ) + step_results.append(step_result) + collected_data[f"step_{step_index+1}_{tool_name}"] = result + plan_steps.append({"tool": tool_name, "arguments": arguments, "reason": f"调用 {tool_name}"}) + + # 发送步骤完成事件 + yield self._format_sse("step_complete", { + "step_index": step_index, + "tool": tool_name, + "status": "success", + "result": result, + "execution_time": execution_time, + }) + + # 将工具结果添加到消息历史(简化格式,因为模型可能不支持标准 tool 消息) + result_str = json.dumps(result, ensure_ascii=False) if isinstance(result, (dict, list)) else str(result) + messages.append({ + "role": "user", + "content": f"[工具调用结果] {tool_name}: {result_str[:3000]}" + }) + + logger.info(f"[Tool Call] 执行成功,耗时 {execution_time:.2f}s") + + except Exception as e: + execution_time = (datetime.now() - start_time).total_seconds() + error_msg = str(e) + + step_result = StepResult( + step_index=step_index, + tool=tool_name, + arguments=arguments, + status="failed", + error=error_msg, + execution_time=execution_time, + ) + step_results.append(step_result) + + yield self._format_sse("step_complete", { + "step_index": step_index, + "tool": tool_name, + "status": "failed", + "error": error_msg, + "execution_time": execution_time, + }) + + messages.append({ + "role": "user", + "content": f"[工具调用失败] {tool_name}: {error_msg}" + }) + + logger.error(f"[Tool Call] 执行失败: {error_msg}") + + logger.info(f"[Tool Call] ========== 文本工具调用结束 ==========") + step_index += 1 + else: # 没有工具调用,模型生成了最终回复 logger.info(f"[Agent Stream] 模型生成最终回复") @@ -2813,6 +2940,75 @@ A股交易时间: 上午 9:30-11:30,下午 13:00-15:00 """格式化 SSE 消息""" return f"event: {event}\ndata: {json.dumps(data, ensure_ascii=False)}\n\n" + def _parse_text_tool_calls(self, content: str) -> List[Dict[str, Any]]: + """ + 解析文本格式的工具调用 + + 支持的格式: + 1. value + 2. ```tool_call\n{"name": "xxx", "arguments": {...}}\n``` + + 返回: [{"name": "tool_name", "arguments": {...}}, ...] + """ + import re + + tool_calls = [] + + # 格式1: 标签格式 + # 例如: 300274 + pattern1 = r'\s*(.*?)\s*' + matches1 = re.findall(pattern1, content, re.DOTALL) + + for func_name, params_str in matches1: + arguments = {} + # 解析参数: value + param_pattern = r'\s*(.*?)\s*' + param_matches = re.findall(param_pattern, params_str, re.DOTALL) + for param_name, param_value in param_matches: + # 尝试解析 JSON 值,否则作为字符串 + param_value = param_value.strip() + try: + arguments[param_name] = json.loads(param_value) + except: + arguments[param_name] = param_value + + tool_calls.append({ + "name": func_name, + "arguments": arguments + }) + + # 格式2: ```tool_call 代码块格式 + pattern2 = r'```tool_call\s*\n?(.*?)\n?```' + matches2 = re.findall(pattern2, content, re.DOTALL) + + for match in matches2: + try: + data = json.loads(match.strip()) + if isinstance(data, dict) and "name" in data: + tool_calls.append({ + "name": data["name"], + "arguments": data.get("arguments", {}) + }) + except: + pass + + # 格式3: 直接 JSON 格式 {"tool": "xxx", "arguments": {...}} + pattern3 = r'\{\s*"tool"\s*:\s*"(\w+)"\s*,\s*"arguments"\s*:\s*(\{[^}]*\})\s*\}' + matches3 = re.findall(pattern3, content) + + for tool_name, args_str in matches3: + try: + arguments = json.loads(args_str) + tool_calls.append({ + "name": tool_name, + "arguments": arguments + }) + except: + pass + + logger.info(f"[Text Tool Call] 解析到 {len(tool_calls)} 个工具调用: {tool_calls}") + return tool_calls + # 创建 Agent 实例(全局) agent = MCPAgentIntegrated()