myagent-ai 1.47.21 → 1.47.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -499,27 +499,6 @@ class MainAgent(BaseAgent):
499
499
  except Exception as e:
500
500
  logger.debug(f"V2 SSE 事件发送失败 ({event_type}): {e}")
501
501
 
502
- def _try_extract_partial_response(self, llm_raw: str) -> str:
503
- """[v1.47.21] 从不完整的 LLM 输出中提取纯文本回复。
504
-
505
- 完全依赖原生 tool_calling,不再解析 XML 格式。
506
- 仅做简单的 XML 标签清理(兜底,防止模型意外输出 XML)。
507
- """
508
- if not llm_raw:
509
- return ""
510
-
511
- import re
512
- # 去除所有 XML 标签
513
- cleaned = re.sub(r"<[^>]+>", "", llm_raw).strip()
514
- cleaned = re.sub(r"^(reasoning|assistant)\s*", "", cleaned, flags=re.IGNORECASE).strip()
515
- # 跳过工具执行状态文本
516
- if cleaned and len(cleaned) > 5 and not re.match(
517
- r"^(执行工具|调用工具|Running tool|Calling tool)", cleaned, re.IGNORECASE
518
- ):
519
- return cleaned
520
-
521
- return ""
522
-
523
502
  async def _merge_duplicate_memory(
524
503
  self,
525
504
  old_memory,
@@ -964,16 +943,77 @@ class MainAgent(BaseAgent):
964
943
  _reasoning_parts.append(delta_text)
965
944
  await self._emit_v2_event("v2_reasoning", {"content": delta_text}, stream_callback)
966
945
 
967
- if stream_response and self.llm:
968
- response = await self._call_llm_stream(
969
- messages,
970
- tools=tools if tools else None,
971
- text_delta_callback=text_delta_callback,
972
- reasoning_delta_callback=_reasoning_delta_cb,
973
- stream_response=stream_response,
974
- )
975
- else:
976
- response = await self._call_llm(messages, tools=tools if tools else None)
946
+ # 可重试的 LLM 调用(最多 5 次)
947
+ _max_llm_retries = 5
948
+ _llm_retry_count = 0
949
+ response = None
950
+
951
+ while _llm_retry_count < _max_llm_retries:
952
+ try:
953
+ if stream_response and self.llm:
954
+ response = await self._call_llm_stream(
955
+ messages,
956
+ tools=tools if tools else None,
957
+ text_delta_callback=text_delta_callback,
958
+ reasoning_delta_callback=_reasoning_delta_cb,
959
+ stream_response=stream_response,
960
+ )
961
+ else:
962
+ response = await self._call_llm(messages, tools=tools if tools else None)
963
+ except Exception as _llm_exc:
964
+ # 异常类错误 → 判断是否可重试
965
+ _llm_exc_str = str(_llm_exc).lower()
966
+ _is_retryable = any(kw in _llm_exc_str for kw in (
967
+ "connection", "timeout", "timed out",
968
+ "429", "500", "502", "503", "504",
969
+ "rate_limit", "rate limit", "overloaded", "capacity",
970
+ "network", "eof",
971
+ ))
972
+ _llm_retry_count += 1
973
+ if _is_retryable and _llm_retry_count < _max_llm_retries:
974
+ _delay = 2.0 * (2 ** (_llm_retry_count - 1)) # 2s, 4s, 8s, 16s
975
+ logger.warning(
976
+ f"[{task_id}] LLM 调用异常 (第 {_llm_retry_count}/{_max_llm_retries} 次),"
977
+ f"{_delay:.0f}s 后重试: {_llm_exc}"
978
+ )
979
+ await self._emit_v2_event("v2_reasoning", {
980
+ "content": f"⏳ 网络不稳定,正在重试 ({_llm_retry_count}/{_max_llm_retries})..."
981
+ }, stream_callback)
982
+ await asyncio.sleep(_delay)
983
+ continue
984
+ else:
985
+ # 不可重试 或 已达上限 → 包装为失败 response
986
+ logger.error(f"[{task_id}] LLM 调用异常 (已重试 {_llm_retry_count} 次): {_llm_exc}")
987
+ response = type("LLMResponse", (), {"success": False, "error": str(_llm_exc)})()
988
+ break
989
+
990
+ # 没有异常 → 检查 response.success
991
+ if response.success:
992
+ break # 成功 → 跳出重试循环
993
+
994
+ # response.success == False 但没抛异常 → 判断错误是否可重试
995
+ _llm_error = (response.error or "").lower()
996
+ _is_retryable = any(kw in _llm_error for kw in (
997
+ "connection", "timeout", "timed out",
998
+ "429", "500", "502", "503", "504",
999
+ "rate_limit", "rate limit", "overloaded", "capacity",
1000
+ "network", "eof",
1001
+ ))
1002
+ _llm_retry_count += 1
1003
+ if _is_retryable and _llm_retry_count < _max_llm_retries:
1004
+ _delay = 2.0 * (2 ** (_llm_retry_count - 1))
1005
+ logger.warning(
1006
+ f"[{task_id}] LLM 返回失败 (第 {_llm_retry_count}/{_max_llm_retries} 次),"
1007
+ f"{_delay:.0f}s 后重试: {response.error}"
1008
+ )
1009
+ await self._emit_v2_event("v2_reasoning", {
1010
+ "content": f"⏳ 网络不稳定,正在重试 ({_llm_retry_count}/{_max_llm_retries})..."
1011
+ }, stream_callback)
1012
+ await asyncio.sleep(_delay)
1013
+ continue
1014
+ else:
1015
+ # 不可重试 或 已达上限 → 保持失败 response,退出循环
1016
+ break
977
1017
 
978
1018
  # LLM 调用失败处理
979
1019
  if not response.success:
@@ -1003,8 +1043,8 @@ class MainAgent(BaseAgent):
1003
1043
  await self._emit_v2_event("v2_reasoning", {"content": _vision_skip_msg}, stream_callback)
1004
1044
  break
1005
1045
 
1006
- # 其他错误
1007
- error_msg = f"LLM 调用失败: {response.error}"
1046
+ # 其他错误(含已耗尽重试次数的连接错误)
1047
+ error_msg = f"LLM 调用失败 (已重试 {_llm_retry_count} 次): {_llm_error}"
1008
1048
  context.working_memory["final_response"] = error_msg
1009
1049
  await self._emit_v2_event("v2_reasoning", {"content": error_msg}, stream_callback)
1010
1050
  break
@@ -1180,20 +1220,8 @@ class MainAgent(BaseAgent):
1180
1220
  continue
1181
1221
 
1182
1222
  else:
1183
- # [v1.47.21] 没有原生工具调用 → 纯文本回复
1184
- # 完全依赖 tool_calling,不再解析 <output> XML
1185
- raw_content = (response.content or "").strip()
1186
-
1187
- # 如果模型意外输出了 XML 标签,清理掉
1188
- import re as _re_clean
1189
- if raw_content.startswith("<") and "</" in raw_content:
1190
- # 清除 XML 标签,提取纯文本
1191
- cleaned = _re_clean.sub(r'<[^>]+>', '', raw_content).strip()
1192
- if cleaned:
1193
- raw_content = cleaned
1194
- logger.info(f"[{task_id}] 清理了 LLM 输出中的 XML 标签")
1195
-
1196
- reply_text = raw_content
1223
+ # 没有原生工具调用 → 纯文本回复,完全依赖 tool_calling
1224
+ reply_text = (response.content or "").strip()
1197
1225
  logger.info(f"[{task_id}] 无工具调用,任务完成 (reply长度={len(reply_text)})")
1198
1226
 
1199
1227
  if not reply_text:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.47.21",
3
+ "version": "1.47.23",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
@@ -2999,11 +2999,7 @@ async function selectSession(id) {
2999
2999
  return m && (m.role === 'user' || m.role === 'assistant' || m.role === 'tool');
3000
3000
  }).map(function(m) {
3001
3001
  var content = (m.content != null) ? String(m.content) : '';
3002
- // [v1.47.21] 清理意外输出的 XML 标签(完全依赖 tool_calling)
3003
3002
  var mkey = (m.key || '').toLowerCase();
3004
- if (m.role === 'assistant' && content && content.trim().startsWith('<')) {
3005
- content = content.replace(/<[^>]+>/g, ' ').replace(/\s{2,}/g, ' ').trim();
3006
- }
3007
3003
  var mapped = {
3008
3004
  role: m.role || 'assistant',
3009
3005
  content: content,
@@ -3108,10 +3104,6 @@ async function loadMoreMessages() {
3108
3104
  }).map(function(m) {
3109
3105
  var content = (m.content != null) ? String(m.content) : '';
3110
3106
  var mkey = (m.key || '').toLowerCase();
3111
- // [v1.47.21] 清理意外输出的 XML 标签
3112
- if (m.role === 'assistant' && content && content.trim().startsWith('<')) {
3113
- content = content.replace(/<[^>]+>/g, ' ').replace(/\s{2,}/g, ' ').trim();
3114
- }
3115
3107
  var mapped = {
3116
3108
  role: m.role || 'assistant',
3117
3109
  content: content,
@@ -398,10 +398,6 @@ async function pollChatHistory() {
398
398
  }).map(function(m) {
399
399
  var content = (m.content != null) ? String(m.content) : '';
400
400
  var mkey = (m.key || '').toLowerCase();
401
- // [v1.47.21] 清理意外输出的 XML 标签(完全依赖 tool_calling,不再解析 XML)
402
- if (m.role === 'assistant' && content && content.trim().startsWith('<')) {
403
- content = content.replace(/<[^>]+>/g, ' ').replace(/\s{2,}/g, ' ').trim();
404
- }
405
401
  var mapped = {
406
402
  role: m.role || 'assistant',
407
403
  content: content,
@@ -416,7 +412,7 @@ async function pollChatHistory() {
416
412
  if (m._media && m._media.length > 0) mapped._media = m._media;
417
413
  return mapped;
418
414
  });
419
-
415
+
420
416
  // 检测是否有新消息
421
417
  var newCount = loaded.length;
422
418
  if (newCount === _lastKnownMessageCount && !state.isGenerating) return; // 无变化且非生成中,跳过
@@ -472,10 +468,6 @@ async function forceRefreshHistory() {
472
468
  }).map(function(m) {
473
469
  var content = (m.content != null) ? String(m.content) : '';
474
470
  var mkey = (m.key || '').toLowerCase();
475
- // [v1.47.21] 清理意外输出的 XML 标签
476
- if (m.role === 'assistant' && content && content.trim().startsWith('<')) {
477
- content = content.replace(/<[^>]+>/g, ' ').replace(/\s{2,}/g, ' ').trim();
478
- }
479
471
  var mapped = {
480
472
  role: m.role || 'assistant',
481
473
  content: content,
@@ -1114,16 +1106,6 @@ function _showFinishNotification(text) {
1114
1106
  setTimeout(function() { if (overlay.parentNode) overlay.remove(); }, 5000);
1115
1107
  }
1116
1108
 
1117
- /**
1118
- * [v1.47.21] Strip XML tags from text — simple regex cleanup for accidental XML output.
1119
- * No longer parses <output>/<reply>/<toolstocal> — fully relies on native tool_calling.
1120
- */
1121
- function _stripXmlTags(xml) {
1122
- if (!xml) return '';
1123
- return xml.replace(/<[^>]+>/g, ' ').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
1124
- .replace(/&amp;/g, '&').replace(/\s{2,}/g, ' ').trim();
1125
- }
1126
-
1127
1109
  // ══════════════════════════════════════════════════════
1128
1110
  // ── V2 Content Assembler (V2 内容组装) ──
1129
1111
  // ══════════════════════════════════════════════════════
@@ -1148,14 +1130,7 @@ function _assembleV2Content(msg, msgParts) {
1148
1130
  if (msg._askUser && msg._askUser.trim()) {
1149
1131
  return msg._askUser.trim();
1150
1132
  }
1151
- // Priority 4: V2 raw XML stripped of tags (fallback when v2_reasoning not sent)
1152
- if (msg._v2RawXml && msg._v2RawXml.trim()) {
1153
- var strippedText = _stripXmlTags(msg._v2RawXml);
1154
- if (strippedText && strippedText.trim()) {
1155
- return strippedText.trim();
1156
- }
1157
- }
1158
- // Priority 5: raw content from message (server-stored response)
1133
+ // Priority 4: raw content from message (server-stored response)
1159
1134
  if (msg.content && msg.content.trim() && msg.content !== '(无回复)') {
1160
1135
  return msg.content.trim();
1161
1136
  }