JC321 commited on
Commit
5702960
·
verified ·
1 Parent(s): 9159817

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +108 -306
app.py CHANGED
@@ -4,122 +4,29 @@ import json
4
  import os
5
  from huggingface_hub import InferenceClient
6
 
7
- # ========== 配置两个 MCP 服务 ==========
8
- MCP_SERVICES = {
9
- "financial": {
10
- "name": "SEC Financial Reports",
11
- "url": "https://jc321-easyreportdatemcp.hf.space/mcp",
12
- "type": "fastmcp" # 标准 FastMCP (HTTP JSON-RPC)
13
- },
14
- "market": {
15
- "name": "Market & Stock Data (Finnhub)",
16
- "url": "https://jc321-marketandstockmcp.hf.space",
17
- "type": "gradio" # Gradio API
18
- }
19
- }
20
-
21
- # MCP 工具定义(两个服务的工具合并)
22
  MCP_TOOLS = [
23
- # Financial Reports Tools
24
- {
25
- "type": "function",
26
- "function": {
27
- "name": "advanced_search_company",
28
- "description": "Search for US listed companies by name or ticker to get CIK and basic info",
29
- "parameters": {
30
- "type": "object",
31
- "properties": {
32
- "company_input": {"type": "string", "description": "Company name or ticker (e.g., 'Apple', 'AAPL')"}
33
- },
34
- "required": ["company_input"]
35
- }
36
- }
37
- },
38
- {
39
- "type": "function",
40
- "function": {
41
- "name": "get_latest_financial_data",
42
- "description": "Get latest SEC financial data (revenue, net income, EPS, cash flow, etc.)",
43
- "parameters": {
44
- "type": "object",
45
- "properties": {
46
- "cik": {"type": "string", "description": "10-digit CIK number"}
47
- },
48
- "required": ["cik"]
49
- }
50
- }
51
- },
52
- {
53
- "type": "function",
54
- "function": {
55
- "name": "extract_financial_metrics",
56
- "description": "Get multi-year financial trends (3 or 5 years)",
57
- "parameters": {
58
- "type": "object",
59
- "properties": {
60
- "cik": {"type": "string", "description": "10-digit CIK number"},
61
- "years": {"type": "integer", "enum": [3, 5]}
62
- },
63
- "required": ["cik", "years"]
64
- }
65
- }
66
- },
67
- # Market & Stock Tools (Finnhub API)
68
- {
69
- "type": "function",
70
- "function": {
71
- "name": "get_quote",
72
- "description": "Get real-time stock quote (price, volume, change, etc.) for a ticker symbol",
73
- "parameters": {
74
- "type": "object",
75
- "properties": {
76
- "symbol": {"type": "string", "description": "Stock ticker symbol (e.g., 'AAPL')"}
77
- },
78
- "required": ["symbol"]
79
- }
80
- }
81
- },
82
- {
83
- "type": "function",
84
- "function": {
85
- "name": "get_market_news",
86
- "description": "Get latest market news by category (general, forex, crypto, merger)",
87
- "parameters": {
88
- "type": "object",
89
- "properties": {
90
- "category": {"type": "string", "enum": ["general", "forex", "crypto", "merger"], "description": "News category"},
91
- "min_id": {"type": "integer", "description": "Minimum news ID (optional)"}
92
- },
93
- "required": ["category"]
94
- }
95
- }
96
- },
97
- {
98
- "type": "function",
99
- "function": {
100
- "name": "get_company_news",
101
- "description": "Get company-specific news for a stock symbol within a date range",
102
- "parameters": {
103
- "type": "object",
104
- "properties": {
105
- "symbol": {"type": "string", "description": "Stock ticker symbol (e.g., 'AAPL')"},
106
- "from_date": {"type": "string", "description": "Start date (YYYY-MM-DD, default: 7 days ago)"},
107
- "to_date": {"type": "string", "description": "End date (YYYY-MM-DD, default: today)"}
108
- },
109
- "required": ["symbol"]
110
- }
111
- }
112
- }
113
  ]
114
 
115
- # 工具路由:工具名 -> 服务配置
 
 
 
 
 
116
  TOOL_ROUTING = {
117
  "advanced_search_company": MCP_SERVICES["financial"],
118
  "get_latest_financial_data": MCP_SERVICES["financial"],
119
  "extract_financial_metrics": MCP_SERVICES["financial"],
120
  "get_quote": MCP_SERVICES["market"],
121
  "get_market_news": MCP_SERVICES["market"],
122
- "get_company_news": MCP_SERVICES["market"],
123
  }
124
 
125
  # ========== 初始化 LLM 客户端 ==========
@@ -127,245 +34,151 @@ hf_token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN"
127
  client = InferenceClient(api_key=hf_token) if hf_token else InferenceClient()
128
  print(f"✅ LLM initialized: Qwen/Qwen2.5-72B-Instruct:novita")
129
  print(f"📊 MCP Services: {len(MCP_SERVICES)} services, {len(MCP_TOOLS)} tools")
130
- print(f" - Financial: advanced_search_company, get_latest_financial_data, extract_financial_metrics")
131
- print(f" - Market: get_quote, get_market_news, get_company_news")
132
-
133
- # ========== 系统提示词 ==========
134
- SYSTEM_PROMPT = """You are an intelligent financial and market analysis assistant.
135
-
136
- You have access to TWO data sources:
137
 
138
- 1. **SEC Financial Reports** (Official filings)
139
- - advanced_search_company: Find US companies
140
- - get_latest_financial_data: Get latest 10-K/10-Q data
141
- - extract_financial_metrics: Get multi-year trends
142
 
143
- 2. **Market & Stock Data** (Finnhub real-time)
144
- - get_quote: Real-time stock price, volume, change
145
- - get_market_news: Latest market news (general/forex/crypto/merger)
146
- - get_company_news: Company-specific news
147
 
148
- Automatically use the right tools and provide clear, data-driven insights."""
149
-
150
- # ========== 核心函数:调用 MCP 工具 ==========
151
  def call_mcp_tool(tool_name, arguments):
152
- """调用 MCP 工具(支持 FastMCP 和 Gradio 两种协议)"""
153
  service_config = TOOL_ROUTING.get(tool_name)
154
  if not service_config:
155
  return {"error": f"Unknown tool: {tool_name}"}
156
 
157
- service_type = service_config["type"]
158
- service_url = service_config["url"]
159
-
160
- print(f"\n[DEBUG] Calling tool: {tool_name}")
161
- print(f"[DEBUG] Service type: {service_type}, URL: {service_url}")
162
-
163
  try:
164
- if service_type == "fastmcp":
165
- # FastMCP: 标准 MCP JSON-RPC 协议
166
- return _call_fastmcp(service_url, tool_name, arguments)
167
- elif service_type == "gradio":
168
- # Gradio: Gradio API 协议
169
- return _call_gradio_api(service_url, tool_name, arguments)
170
  else:
171
- return {"error": f"Unknown service type: {service_type}"}
172
-
173
  except Exception as e:
174
- error_msg = f"Exception: {str(e)}"
175
- print(f"[DEBUG] {error_msg}")
176
- return {"error": error_msg}
177
 
178
 
179
  def _call_fastmcp(service_url, tool_name, arguments):
180
- """\u8c03\u7528 FastMCP \u670d\u52a1 (\u6807\u51c6 MCP JSON-RPC \u534f\u8bae)"""
181
  response = requests.post(
182
  service_url,
183
- json={
184
- "jsonrpc": "2.0",
185
- "method": "tools/call",
186
- "params": {"name": tool_name, "arguments": arguments},
187
- "id": 1
188
- },
189
  headers={"Content-Type": "application/json"},
190
- timeout=60
191
  )
192
 
193
- if response.status_code == 200:
194
- data = response.json()
195
- print(f"[DEBUG] FastMCP raw response: {json.dumps(data, ensure_ascii=False)[:500]}")
196
-
197
- # 解包 JSON-RPC 响应
198
- if isinstance(data, dict) and "result" in data:
199
- result = data["result"]
200
-
201
- # MCP 协议格式: {"content": [{"type": "text", "text": "..."}]}
202
- if isinstance(result, dict) and "content" in result:
203
- content = result["content"]
204
-
205
- # 提取第一个 content item text
206
- if isinstance(content, list) and len(content) > 0:
207
- first_item = content[0]
208
- if isinstance(first_item, dict) and "text" in first_item:
209
- text_data = first_item["text"]
210
-
211
- # text 可能是 JSON 字符串,尝试解析
212
- try:
213
- parsed_data = json.loads(text_data)
214
- print(f"[DEBUG] Parsed data: {json.dumps(parsed_data, ensure_ascii=False)[:300]}")
215
- return parsed_data
216
- except (json.JSONDecodeError, TypeError):
217
- # 如果不是 JSON,直接返回文本
218
- print(f"[DEBUG] Returning text as-is")
219
- return {"text": text_data}
220
-
221
- # 如果不是 content 格式,直接返回 result
222
- print(f"[DEBUG] Returning result directly")
223
- return result
224
-
225
- # 如果没有 result 字段,返回整个响应
226
- print(f"[DEBUG] No result field, returning full response")
227
- return data
228
- else:
229
- error_msg = f"HTTP {response.status_code}: {response.text[:200]}"
230
- print(f"[DEBUG] {error_msg}")
231
- return {"error": error_msg}
232
 
233
 
234
  def _call_gradio_api(service_url, tool_name, arguments):
235
- """\u8c03\u7528 Gradio API (Gradio \u5185\u7f6e MCP \u670d\u52a1)"""
236
- # Gradio API 工具名映射
237
- gradio_tool_map = {
238
- "get_quote": "test_quote_tool",
239
- "get_market_news": "test_market_news_tool",
240
- "get_company_news": "test_company_news_tool"
241
- }
242
-
243
- gradio_fn_name = gradio_tool_map.get(tool_name)
244
- if not gradio_fn_name:
245
- return {"error": f"No Gradio mapping for tool: {tool_name}"}
246
 
247
- # 步骤1: 提交调用请求
248
- call_url = f"{service_url}/call/{gradio_fn_name}"
249
-
250
- # 构造 Gradio API 参数格式
251
  if tool_name == "get_quote":
252
- data_params = [arguments.get("symbol", "")]
253
  elif tool_name == "get_market_news":
254
- data_params = [arguments.get("category", "general")]
255
  elif tool_name == "get_company_news":
256
- data_params = [
257
- arguments.get("symbol", ""),
258
- arguments.get("from_date", ""),
259
- arguments.get("to_date", "")
260
- ]
261
  else:
262
- data_params = []
263
-
264
- print(f"[DEBUG] Gradio call URL: {call_url}")
265
- print(f"[DEBUG] Gradio data params: {data_params}")
266
 
267
  # 提交请求
268
- response = requests.post(
269
- call_url,
270
- json={"data": data_params},
271
- headers={"Content-Type": "application/json"},
272
- timeout=10
273
- )
274
-
275
- if response.status_code != 200:
276
- return {"error": f"Gradio call failed: HTTP {response.status_code}"}
277
-
278
- call_data = response.json()
279
- event_id = call_data.get("event_id")
280
 
 
281
  if not event_id:
282
- return {"error": "No event_id returned from Gradio"}
283
-
284
- print(f"[DEBUG] Got event_id: {event_id}")
285
-
286
- # 步骤2: 轮询获取结果 (SSE ��)
287
- result_url = f"{call_url}/{event_id}"
288
- result_response = requests.get(result_url, stream=True, timeout=30)
289
-
290
- if result_response.status_code != 200:
291
- return {"error": f"Failed to get result: HTTP {result_response.status_code}"}
292
 
293
- # 解析 SSE
294
- result_text = ""
295
- for line in result_response.iter_lines():
296
- if line:
297
- line_str = line.decode('utf-8')
298
- if line_str.startswith('data: '):
299
- data_part = line_str[6:] # 移除 'data: ' 前缀
300
- try:
301
- result_data = json.loads(data_part)
302
- if isinstance(result_data, list) and len(result_data) > 0:
303
- result_text = result_data[0]
304
- print(f"[DEBUG] Gradio result: {result_text[:200]}")
305
- break
306
- except json.JSONDecodeError:
307
- continue
308
 
309
- if not result_text:
310
- return {"error": "No result received from Gradio"}
 
 
 
 
 
 
 
311
 
312
- # 返回统一格式
313
- return {"text": result_text, "_source": "gradio_api"}
 
 
 
314
 
315
- # ========== 核心函数:AI 助手 ==========
316
  def chatbot_response(message, history):
317
- """AI 助手主函数(流式输出)"""
318
  try:
319
- # 构建消息历史
320
  messages = [{"role": "system", "content": SYSTEM_PROMPT}]
321
 
322
- # 添加对话历史(最近5轮)
323
  if history:
324
- for item in history[-5:]:
325
- if isinstance(item, dict):
326
- messages.append(item)
327
- elif isinstance(item, (list, tuple)) and len(item) == 2:
328
- user_msg, assistant_msg = item
329
- messages.append({"role": "user", "content": user_msg})
330
- messages.append({"role": "assistant", "content": assistant_msg})
331
 
332
  messages.append({"role": "user", "content": message})
333
 
334
- # 工具调用日志和响应前缀
335
  tool_calls_log = []
336
- response_prefix = ""
337
- max_iterations = 5
338
 
339
- # LLM 调用循环(支持多轮工具调用)
340
- for iteration in range(max_iterations):
341
- # 调用 LLM(非流式,用于工具调用判断)
342
  response = client.chat_completion(
343
  messages=messages,
344
  model="Qwen/Qwen2.5-72B-Instruct:novita",
345
  tools=MCP_TOOLS,
346
- max_tokens=3000,
347
- temperature=0.7,
348
  tool_choice="auto",
349
  stream=False
350
  )
351
 
352
  choice = response.choices[0]
353
 
354
- # 检查是否有工具调用
355
  if choice.message.tool_calls:
356
  messages.append(choice.message)
357
 
358
  for tool_call in choice.message.tool_calls:
359
  tool_name = tool_call.function.name
360
  tool_args = json.loads(tool_call.function.arguments)
361
-
362
- # 记录工具调用
363
  tool_calls_log.append({"name": tool_name, "arguments": tool_args})
364
 
365
  # 调用 MCP 工具
366
  tool_result = call_mcp_tool(tool_name, tool_args)
367
 
368
- # 添加工具结果到消息
369
  messages.append({
370
  "role": "tool",
371
  "name": tool_name,
@@ -373,56 +186,45 @@ def chatbot_response(message, history):
373
  "tool_call_id": tool_call.id
374
  })
375
 
376
- continue # 继续下一轮
377
  else:
378
- # 无工具调用,准备���式输出最终答案
379
  break
380
 
381
- # 构建响应前缀(模型信息+工具调用)
382
- response_prefix += "<div style='padding: 10px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 8px; margin-bottom: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);'>\n"
383
- response_prefix += "<div style='color: white; font-size: 0.95em; font-weight: 500;'>🤖 <strong>Model:</strong> Qwen/Qwen2.5-72B-Instruct:novita</div>\n"
384
  response_prefix += "</div>\n\n"
385
 
386
- # 显示工具调用日志(精致卡片样式)
387
  if tool_calls_log:
388
- response_prefix += "<div style='background: #f8f9fa; border-radius: 8px; padding: 12px; margin-bottom: 16px; border: 1px solid #e9ecef;'>\n"
389
- response_prefix += "<div style='font-weight: 600; color: #495057; margin-bottom: 10px; font-size: 1em;'>🛠️ MCP Tools Used</div>\n"
390
 
391
- for i, tool_call in enumerate(tool_calls_log, 1):
392
- # 工具图标映射
393
- tool_icons = {
394
- "advanced_search_company": "🔍",
395
- "get_latest_financial_data": "📊",
396
- "extract_financial_metrics": "📈",
397
- "get_quote": "💹",
398
- "get_market_news": "📰",
399
- "get_company_news": "📢"
400
- }
401
  icon = tool_icons.get(tool_call['name'], "⚙️")
402
-
403
- response_prefix += f"<div style='background: white; padding: 8px 12px; margin: 6px 0; border-radius: 6px; border-left: 3px solid #28a745; font-size: 0.9em;'>\n"
404
  response_prefix += f"<span style='color: #28a745; font-weight: 600;'>{icon} {tool_call['name']}</span>\n"
405
- response_prefix += f"<div style='color: #6c757d; margin-top: 4px; font-family: monospace; font-size: 0.85em;'>{json.dumps(tool_call['arguments'], ensure_ascii=False)}</div>\n"
406
  response_prefix += "</div>\n"
407
 
408
  response_prefix += "</div>\n\n"
409
- response_prefix += "<div style='border-top: 2px solid #dee2e6; margin: 16px 0;'></div>\n\n"
410
 
411
  # 流式输出最终答案
412
  yield response_prefix
413
 
414
- # 流式调用 LLM
415
  stream = client.chat_completion(
416
  messages=messages,
417
  model="Qwen/Qwen2.5-72B-Instruct:novita",
418
- max_tokens=3000,
419
- temperature=0.7,
420
  stream=True
421
  )
422
 
423
  accumulated_text = ""
424
  for chunk in stream:
425
- if chunk.choices[0].delta.content:
426
  accumulated_text += chunk.choices[0].delta.content
427
  yield response_prefix + accumulated_text
428
 
 
4
  import os
5
  from huggingface_hub import InferenceClient
6
 
7
+ # ========== MCP 工具简化定义(符合MCP协议标准) ==========
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  MCP_TOOLS = [
9
+ {"type": "function", "function": {"name": "advanced_search_company", "description": "Search US companies", "parameters": {"type": "object", "properties": {"company_input": {"type": "string"}}, "required": ["company_input"]}}},
10
+ {"type": "function", "function": {"name": "get_latest_financial_data", "description": "Get latest financial data", "parameters": {"type": "object", "properties": {"cik": {"type": "string"}}, "required": ["cik"]}}},
11
+ {"type": "function", "function": {"name": "extract_financial_metrics", "description": "Get multi-year trends", "parameters": {"type": "object", "properties": {"cik": {"type": "string"}, "years": {"type": "integer"}}, "required": ["cik", "years"]}}},
12
+ {"type": "function", "function": {"name": "get_quote", "description": "Get stock quote", "parameters": {"type": "object", "properties": {"symbol": {"type": "string"}}, "required": ["symbol"]}}},
13
+ {"type": "function", "function": {"name": "get_market_news", "description": "Get market news", "parameters": {"type": "object", "properties": {"category": {"type": "string"}}, "required": ["category"]}}},
14
+ {"type": "function", "function": {"name": "get_company_news", "description": "Get company news", "parameters": {"type": "object", "properties": {"symbol": {"type": "string"}, "from_date": {"type": "string"}, "to_date": {"type": "string"}}, "required": ["symbol"]}}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  ]
16
 
17
+ # ========== MCP 服务配置 ==========
18
+ MCP_SERVICES = {
19
+ "financial": {"url": "https://jc321-easyreportdatemcp.hf.space/mcp", "type": "fastmcp"},
20
+ "market": {"url": "https://jc321-marketandstockmcp.hf.space", "type": "gradio"}
21
+ }
22
+
23
  TOOL_ROUTING = {
24
  "advanced_search_company": MCP_SERVICES["financial"],
25
  "get_latest_financial_data": MCP_SERVICES["financial"],
26
  "extract_financial_metrics": MCP_SERVICES["financial"],
27
  "get_quote": MCP_SERVICES["market"],
28
  "get_market_news": MCP_SERVICES["market"],
29
+ "get_company_news": MCP_SERVICES["market"]
30
  }
31
 
32
  # ========== 初始化 LLM 客户端 ==========
 
34
  client = InferenceClient(api_key=hf_token) if hf_token else InferenceClient()
35
  print(f"✅ LLM initialized: Qwen/Qwen2.5-72B-Instruct:novita")
36
  print(f"📊 MCP Services: {len(MCP_SERVICES)} services, {len(MCP_TOOLS)} tools")
 
 
 
 
 
 
 
37
 
38
+ # ========== 系统提示词(简化) ==========
39
+ SYSTEM_PROMPT = """You are a financial analysis assistant. Use tools to get data on company financials (past 5-year reports), current stock prices, market news, and company news. Provide data-driven insights."""
 
 
40
 
41
+ # ============================================================
42
+ # MCP 服务调用核心代码区
43
+ # 支持 FastMCP (JSON-RPC) Gradio (SSE) 两种协议
44
+ # ============================================================
45
 
 
 
 
46
  def call_mcp_tool(tool_name, arguments):
47
+ """调用 MCP 工具"""
48
  service_config = TOOL_ROUTING.get(tool_name)
49
  if not service_config:
50
  return {"error": f"Unknown tool: {tool_name}"}
51
 
 
 
 
 
 
 
52
  try:
53
+ if service_config["type"] == "fastmcp":
54
+ return _call_fastmcp(service_config["url"], tool_name, arguments)
55
+ elif service_config["type"] == "gradio":
56
+ return _call_gradio_api(service_config["url"], tool_name, arguments)
 
 
57
  else:
58
+ return {"error": "Unknown service type"}
 
59
  except Exception as e:
60
+ return {"error": str(e)}
 
 
61
 
62
 
63
  def _call_fastmcp(service_url, tool_name, arguments):
64
+ """FastMCP: 标准 MCP JSON-RPC"""
65
  response = requests.post(
66
  service_url,
67
+ json={"jsonrpc": "2.0", "method": "tools/call", "params": {"name": tool_name, "arguments": arguments}, "id": 1},
 
 
 
 
 
68
  headers={"Content-Type": "application/json"},
69
+ timeout=30
70
  )
71
 
72
+ if response.status_code != 200:
73
+ return {"error": f"HTTP {response.status_code}"}
74
+
75
+ data = response.json()
76
+
77
+ # 解包 MCP 协议: jsonrpc -> result -> content[0].text -> JSON
78
+ if isinstance(data, dict) and "result" in data:
79
+ result = data["result"]
80
+ if isinstance(result, dict) and "content" in result:
81
+ content = result["content"]
82
+ if isinstance(content, list) and len(content) > 0:
83
+ first_item = content[0]
84
+ if isinstance(first_item, dict) and "text" in first_item:
85
+ try:
86
+ return json.loads(first_item["text"])
87
+ except (json.JSONDecodeError, TypeError):
88
+ return {"text": first_item["text"]}
89
+ return result
90
+ return data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
 
93
  def _call_gradio_api(service_url, tool_name, arguments):
94
+ """Gradio: SSE 流式协议"""
95
+ tool_map = {"get_quote": "test_quote_tool", "get_market_news": "test_market_news_tool", "get_company_news": "test_company_news_tool"}
96
+ gradio_fn = tool_map.get(tool_name)
97
+ if not gradio_fn:
98
+ return {"error": "No mapping"}
 
 
 
 
 
 
99
 
100
+ # 构造参数
 
 
 
101
  if tool_name == "get_quote":
102
+ params = [arguments.get("symbol", "")]
103
  elif tool_name == "get_market_news":
104
+ params = [arguments.get("category", "general")]
105
  elif tool_name == "get_company_news":
106
+ params = [arguments.get("symbol", ""), arguments.get("from_date", ""), arguments.get("to_date", "")]
 
 
 
 
107
  else:
108
+ params = []
 
 
 
109
 
110
  # 提交请求
111
+ call_url = f"{service_url}/call/{gradio_fn}"
112
+ resp = requests.post(call_url, json={"data": params}, timeout=10)
113
+ if resp.status_code != 200:
114
+ return {"error": f"HTTP {resp.status_code}"}
 
 
 
 
 
 
 
 
115
 
116
+ event_id = resp.json().get("event_id")
117
  if not event_id:
118
+ return {"error": "No event_id"}
 
 
 
 
 
 
 
 
 
119
 
120
+ # 获取结果 (SSE)
121
+ result_resp = requests.get(f"{call_url}/{event_id}", stream=True, timeout=20)
122
+ if result_resp.status_code != 200:
123
+ return {"error": f"HTTP {result_resp.status_code}"}
 
 
 
 
 
 
 
 
 
 
 
124
 
125
+ # 解析 SSE
126
+ for line in result_resp.iter_lines():
127
+ if line and line.decode('utf-8').startswith('data: '):
128
+ try:
129
+ result_data = json.loads(line.decode('utf-8')[6:])
130
+ if isinstance(result_data, list) and len(result_data) > 0:
131
+ return {"text": result_data[0]}
132
+ except json.JSONDecodeError:
133
+ continue
134
 
135
+ return {"error": "No result"}
136
+
137
+ # ============================================================
138
+ # End of MCP 服务调用代码区
139
+ # ============================================================
140
 
 
141
  def chatbot_response(message, history):
142
+ """AI 助手主函数(流式输出,性能优化)"""
143
  try:
 
144
  messages = [{"role": "system", "content": SYSTEM_PROMPT}]
145
 
146
+ # 添加历史(最近3轮)
147
  if history:
148
+ for item in history[-3:]:
149
+ if isinstance(item, (list, tuple)) and len(item) == 2:
150
+ messages.append({"role": "user", "content": item[0]})
151
+ messages.append({"role": "assistant", "content": item[1]})
 
 
 
152
 
153
  messages.append({"role": "user", "content": message})
154
 
 
155
  tool_calls_log = []
 
 
156
 
157
+ # LLM 调用循环(最多3轮工具调用)
158
+ for iteration in range(3):
 
159
  response = client.chat_completion(
160
  messages=messages,
161
  model="Qwen/Qwen2.5-72B-Instruct:novita",
162
  tools=MCP_TOOLS,
163
+ max_tokens=2000,
164
+ temperature=0.5,
165
  tool_choice="auto",
166
  stream=False
167
  )
168
 
169
  choice = response.choices[0]
170
 
 
171
  if choice.message.tool_calls:
172
  messages.append(choice.message)
173
 
174
  for tool_call in choice.message.tool_calls:
175
  tool_name = tool_call.function.name
176
  tool_args = json.loads(tool_call.function.arguments)
 
 
177
  tool_calls_log.append({"name": tool_name, "arguments": tool_args})
178
 
179
  # 调用 MCP 工具
180
  tool_result = call_mcp_tool(tool_name, tool_args)
181
 
 
182
  messages.append({
183
  "role": "tool",
184
  "name": tool_name,
 
186
  "tool_call_id": tool_call.id
187
  })
188
 
189
+ continue
190
  else:
 
191
  break
192
 
193
+ # 构建响应前缀
194
+ response_prefix = "<div style='padding: 8px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 6px; margin-bottom: 10px;'>\n"
195
+ response_prefix += "<span style='color: white; font-size: 0.9em;'>🤖 <strong>Qwen2.5-72B</strong></span>\n"
196
  response_prefix += "</div>\n\n"
197
 
198
+ # 显示工具调用
199
  if tool_calls_log:
200
+ response_prefix += "<div style='background: #f8f9fa; border-radius: 6px; padding: 10px; margin-bottom: 12px;'>\n"
201
+ response_prefix += "<div style='font-weight: 600; color: #495057; margin-bottom: 8px;'>🛠️ Tools Used</div>\n"
202
 
203
+ tool_icons = {"advanced_search_company": "🔍", "get_latest_financial_data": "📊", "extract_financial_metrics": "📈",
204
+ "get_quote": "💹", "get_market_news": "📰", "get_company_news": "📢"}
205
+
206
+ for tool_call in tool_calls_log:
 
 
 
 
 
 
207
  icon = tool_icons.get(tool_call['name'], "⚙️")
208
+ response_prefix += f"<div style='background: white; padding: 6px 10px; margin: 4px 0; border-radius: 4px; border-left: 3px solid #28a745;'>\n"
 
209
  response_prefix += f"<span style='color: #28a745; font-weight: 600;'>{icon} {tool_call['name']}</span>\n"
 
210
  response_prefix += "</div>\n"
211
 
212
  response_prefix += "</div>\n\n"
 
213
 
214
  # 流式输出最终答案
215
  yield response_prefix
216
 
 
217
  stream = client.chat_completion(
218
  messages=messages,
219
  model="Qwen/Qwen2.5-72B-Instruct:novita",
220
+ max_tokens=2000,
221
+ temperature=0.5,
222
  stream=True
223
  )
224
 
225
  accumulated_text = ""
226
  for chunk in stream:
227
+ if chunk.choices and len(chunk.choices) > 0 and chunk.choices[0].delta.content:
228
  accumulated_text += chunk.choices[0].delta.content
229
  yield response_prefix + accumulated_text
230