JC321 commited on
Commit
aa9d94f
·
verified ·
1 Parent(s): 3c65a0d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +87 -46
app.py CHANGED
@@ -44,15 +44,32 @@ client = InferenceClient(api_key=hf_token) if hf_token else InferenceClient()
44
  print(f"✅ LLM initialized: Qwen/Qwen2.5-72B-Instruct:novita")
45
  print(f"📊 MCP Services: {len(MCP_SERVICES)} services, {len(MCP_TOOLS)} tools")
46
 
47
- # ========== 系统提示词(简化) ==========
48
- from datetime import datetime
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
  def get_system_prompt():
51
- """生成包含当前日期的系统提示词"""
 
52
  current_date = datetime.now().strftime("%Y-%m-%d")
53
- return f"""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.
54
-
55
- IMPORTANT: Today's date is {current_date}. When querying news or time-sensitive data, use recent dates relative to today."""
56
 
57
  # ============================================================
58
  # MCP 服务调用核心代码区
@@ -159,28 +176,30 @@ def chatbot_response(message, history):
159
  try:
160
  messages = [{"role": "system", "content": get_system_prompt()}]
161
 
162
- # 添加历史(最近3轭) - 减少上下文长度
163
  if history:
164
- for item in history[-3:]: # 从5轮改为3轮
165
  if isinstance(item, (list, tuple)) and len(item) == 2:
 
166
  messages.append({"role": "user", "content": item[0]})
167
- # 截断过长的历史回复
168
- assistant_msg = item[1]
169
- if len(assistant_msg) > 1000:
170
- assistant_msg = assistant_msg[:1000] + "...[truncated]"
 
171
  messages.append({"role": "assistant", "content": assistant_msg})
172
 
173
  messages.append({"role": "user", "content": message})
174
 
175
  tool_calls_log = []
176
 
177
- # LLM 调用循环(最多3轮工具调用) - 减少迭代次数
178
- for iteration in range(3): # 从5轮改为3轮
179
  response = client.chat_completion(
180
  messages=messages,
181
  model="Qwen/Qwen2.5-72B-Instruct:novita",
182
  tools=MCP_TOOLS,
183
- max_tokens=1500, # 从2000降到1500
184
  temperature=0.5,
185
  tool_choice="auto",
186
  stream=False
@@ -198,16 +217,29 @@ def chatbot_response(message, history):
198
  # 调用 MCP 工具
199
  tool_result = call_mcp_tool(tool_name, tool_args)
200
 
201
- # 大幅限制返回结果大小,避免超长内容导致500错误
202
  result_str = json.dumps(tool_result, ensure_ascii=False)
203
- if len(result_str) > 2000: # 从4000降到2000
204
- # 截断过长的结果,只保留关键信息
 
205
  if isinstance(tool_result, dict) and "text" in tool_result:
206
- # 如果是文本格式,截取前1500字符
207
- tool_result_truncated = {"text": tool_result["text"][:1500] + "...[truncated]", "_truncated": True}
 
 
 
 
 
 
 
 
 
 
 
 
208
  else:
209
- tool_result_truncated = {"_truncated": True, "preview": result_str[:1500] + "...[truncated]"}
210
- result_for_llm = json.dumps(tool_result_truncated)
211
  else:
212
  result_for_llm = result_str
213
 
@@ -230,6 +262,27 @@ def chatbot_response(message, history):
230
 
231
  # 显示工具调用(带展开/折叠按钮)
232
  if tool_calls_log:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  response_prefix += """<div style='margin-bottom: 15px;'>
234
  <div style='background: #f0f0f0; padding: 8px 12px; border-radius: 6px; font-weight: 600; color: #333;'>
235
  🛠️ Tools Used ({} calls)
@@ -237,41 +290,29 @@ def chatbot_response(message, history):
237
  """.format(len(tool_calls_log))
238
 
239
  for idx, tool_call in enumerate(tool_calls_log):
240
- tool_id = f"tool_{idx}_{hash(str(tool_call))}"
241
 
242
  # 工具卡片
243
  response_prefix += f"""<div style='margin: 8px 0; border: 1px solid #ddd; border-radius: 6px; overflow: hidden;'>
244
- <div style='background: #fff; padding: 10px; cursor: pointer; display: flex; justify-content: space-between; align-items: center;' onclick='toggleTool("{tool_id}")'>
245
- <div>
246
- <strong style='color: #2c5aa0;'>📌 {idx+1}. {tool_call['name']}</strong>
247
- <div style='font-size: 0.85em; color: #666; margin-top: 4px;'>📥 Input: <code>{json.dumps(tool_call['arguments'], ensure_ascii=False)}</code></div>
 
 
 
248
  </div>
249
- <span id='arrow_{tool_id}' style='font-size: 1.2em; color: #999;'>▶</span>
250
  </div>
251
- <div id='{tool_id}' style='display: none; background: #f9f9f9; padding: 12px; border-top: 1px solid #eee;'>
252
  <div style='font-size: 0.9em; color: #333;'>
253
  <strong>📤 Output:</strong>
254
- <pre style='background: #fff; padding: 10px; border-radius: 4px; overflow-x: auto; margin-top: 6px; font-size: 0.85em; border: 1px solid #e0e0e0;'>{json.dumps(tool_call.get('result', {}), ensure_ascii=False, indent=2)[:1000]}{'...' if len(json.dumps(tool_call.get('result', {}), ensure_ascii=False)) > 1000 else ''}</pre>
255
  </div>
256
  </div>
257
  </div>
258
  """
259
 
260
- # JavaScript 函数
261
- response_prefix += """<script>
262
- function toggleTool(id) {
263
- var content = document.getElementById(id);
264
- var arrow = document.getElementById('arrow_' + id);
265
- if (content.style.display === 'none') {
266
- content.style.display = 'block';
267
- arrow.innerHTML = '▼';
268
- } else {
269
- content.style.display = 'none';
270
- arrow.innerHTML = '▶';
271
- }
272
- }
273
- </script>
274
- </div>
275
 
276
  ---
277
 
@@ -285,7 +326,7 @@ function toggleTool(id) {
285
  messages=messages,
286
  model="Qwen/Qwen2.5-72B-Instruct:novita",
287
  tools=MCP_TOOLS,
288
- max_tokens=1500, # 从2000降到1500
289
  temperature=0.5,
290
  stream=True
291
  )
 
44
  print(f"✅ LLM initialized: Qwen/Qwen2.5-72B-Instruct:novita")
45
  print(f"📊 MCP Services: {len(MCP_SERVICES)} services, {len(MCP_TOOLS)} tools")
46
 
47
+ # ========== Token 限制配置 ==========
48
+ # HuggingFace Inference API 实际限制约 8000-16000 tokens
49
+ # 为了安全,设置更低的限制
50
+ MAX_TOTAL_TOKENS = 6000 # 总上下文限制
51
+ MAX_TOOL_RESULT_CHARS = 800 # 工具返回最大字符数 (约400 tokens)
52
+ MAX_HISTORY_CHARS = 500 # 单条历史消息最大字符数
53
+ MAX_HISTORY_TURNS = 2 # 最大历史轮数
54
+ MAX_TOOL_ITERATIONS = 2 # 最大工具调用轮数
55
+ MAX_OUTPUT_TOKENS = 1000 # 最大输出 tokens
56
+
57
+ def estimate_tokens(text):
58
+ """估算文本 token 数量(粗略:1 token ≈ 2 字符)"""
59
+ return len(str(text)) // 2
60
+
61
+ def truncate_text(text, max_chars, suffix="...[truncated]"):
62
+ """截断文本到指定长度"""
63
+ text = str(text)
64
+ if len(text) <= max_chars:
65
+ return text
66
+ return text[:max_chars] + suffix
67
 
68
  def get_system_prompt():
69
+ """生成包含当前日期的系统提示词(精简版)"""
70
+ from datetime import datetime
71
  current_date = datetime.now().strftime("%Y-%m-%d")
72
+ return f"""Financial analyst. Today: {current_date}. Use tools for company data, stock prices, news. Be concise."""
 
 
73
 
74
  # ============================================================
75
  # MCP 服务调用核心代码区
 
176
  try:
177
  messages = [{"role": "system", "content": get_system_prompt()}]
178
 
179
+ # 添加历史(最近2轮) - 严格限制上下文长度
180
  if history:
181
+ for item in history[-MAX_HISTORY_TURNS:]:
182
  if isinstance(item, (list, tuple)) and len(item) == 2:
183
+ # 用户消息(不截断)
184
  messages.append({"role": "user", "content": item[0]})
185
+
186
+ # 助手回复(严格截断)
187
+ assistant_msg = str(item[1])
188
+ if len(assistant_msg) > MAX_HISTORY_CHARS:
189
+ assistant_msg = truncate_text(assistant_msg, MAX_HISTORY_CHARS)
190
  messages.append({"role": "assistant", "content": assistant_msg})
191
 
192
  messages.append({"role": "user", "content": message})
193
 
194
  tool_calls_log = []
195
 
196
+ # LLM 调用循环(最多2轮工具调用) - 严格控制迭代次数
197
+ for iteration in range(MAX_TOOL_ITERATIONS):
198
  response = client.chat_completion(
199
  messages=messages,
200
  model="Qwen/Qwen2.5-72B-Instruct:novita",
201
  tools=MCP_TOOLS,
202
+ max_tokens=MAX_OUTPUT_TOKENS,
203
  temperature=0.5,
204
  tool_choice="auto",
205
  stream=False
 
217
  # 调用 MCP 工具
218
  tool_result = call_mcp_tool(tool_name, tool_args)
219
 
220
+ # 严格限制返回结果大小,避免超长内容导致500错误
221
  result_str = json.dumps(tool_result, ensure_ascii=False)
222
+
223
+ # 截断到安全长度 (800字符 ≈ 400 tokens)
224
+ if len(result_str) > MAX_TOOL_RESULT_CHARS:
225
  if isinstance(tool_result, dict) and "text" in tool_result:
226
+ # 如果是文本格式
227
+ truncated_text = truncate_text(tool_result["text"], MAX_TOOL_RESULT_CHARS - 50)
228
+ tool_result_truncated = {"text": truncated_text, "_truncated": True}
229
+ elif isinstance(tool_result, dict):
230
+ # JSON 格式,保留关键字段
231
+ truncated = {}
232
+ char_count = 0
233
+ for k, v in list(tool_result.items())[:5]: # 只保留前5个字段
234
+ v_str = str(v)[:200] # 每个值最多200字符
235
+ truncated[k] = v_str
236
+ char_count += len(k) + len(v_str)
237
+ if char_count > MAX_TOOL_RESULT_CHARS:
238
+ break
239
+ tool_result_truncated = {**truncated, "_truncated": True}
240
  else:
241
+ tool_result_truncated = {"preview": truncate_text(result_str, MAX_TOOL_RESULT_CHARS), "_truncated": True}
242
+ result_for_llm = json.dumps(tool_result_truncated, ensure_ascii=False)
243
  else:
244
  result_for_llm = result_str
245
 
 
262
 
263
  # 显示工具调用(带展开/折叠按钮)
264
  if tool_calls_log:
265
+ # 先添加 JavaScript 函数(确保在 HTML 之前加载)
266
+ response_prefix += """<script>
267
+ if (typeof window.toggleToolResult === 'undefined') {
268
+ window.toggleToolResult = function(id) {
269
+ var content = document.getElementById('content_' + id);
270
+ var arrow = document.getElementById('arrow_' + id);
271
+ if (content && arrow) {
272
+ if (content.style.display === 'none' || content.style.display === '') {
273
+ content.style.display = 'block';
274
+ arrow.textContent = '▼';
275
+ } else {
276
+ content.style.display = 'none';
277
+ arrow.textContent = '▶';
278
+ }
279
+ }
280
+ };
281
+ }
282
+ </script>
283
+
284
+ """
285
+
286
  response_prefix += """<div style='margin-bottom: 15px;'>
287
  <div style='background: #f0f0f0; padding: 8px 12px; border-radius: 6px; font-weight: 600; color: #333;'>
288
  🛠️ Tools Used ({} calls)
 
290
  """.format(len(tool_calls_log))
291
 
292
  for idx, tool_call in enumerate(tool_calls_log):
293
+ tool_id = f"tool{idx}"
294
 
295
  # 工具卡片
296
  response_prefix += f"""<div style='margin: 8px 0; border: 1px solid #ddd; border-radius: 6px; overflow: hidden;'>
297
+ <div style='background: #fff; padding: 10px; cursor: pointer; user-select: none;' onclick='window.toggleToolResult("{tool_id}")'>
298
+ <div style='display: flex; justify-content: space-between; align-items: center;'>
299
+ <div style='flex: 1;'>
300
+ <strong style='color: #2c5aa0;'>📌 {idx+1}. {tool_call['name']}</strong>
301
+ <div style='font-size: 0.85em; color: #666; margin-top: 4px;'>📥 Input: <code style='background: #f5f5f5; padding: 2px 6px; border-radius: 3px;'>{json.dumps(tool_call['arguments'], ensure_ascii=False)}</code></div>
302
+ </div>
303
+ <span id='arrow_{tool_id}' style='font-size: 1.2em; color: #999; margin-left: 10px;'>▶</span>
304
  </div>
 
305
  </div>
306
+ <div id='content_{tool_id}' style='display: none; background: #f9f9f9; padding: 12px; border-top: 1px solid #eee;'>
307
  <div style='font-size: 0.9em; color: #333;'>
308
  <strong>📤 Output:</strong>
309
+ <pre style='background: #fff; padding: 10px; border-radius: 4px; overflow-x: auto; margin-top: 6px; font-size: 0.85em; border: 1px solid #e0e0e0; max-height: 400px;'>{json.dumps(tool_call.get('result', {}), ensure_ascii=False, indent=2)[:1500]}{'...' if len(json.dumps(tool_call.get('result', {}), ensure_ascii=False)) > 1500 else ''}</pre>
310
  </div>
311
  </div>
312
  </div>
313
  """
314
 
315
+ response_prefix += """</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
 
317
  ---
318
 
 
326
  messages=messages,
327
  model="Qwen/Qwen2.5-72B-Instruct:novita",
328
  tools=MCP_TOOLS,
329
+ max_tokens=MAX_OUTPUT_TOKENS,
330
  temperature=0.5,
331
  stream=True
332
  )