myagent-ai 1.15.45 → 1.15.47

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.
@@ -47,7 +47,7 @@ class MainAgent(BaseAgent):
47
47
  <task_plan>任务计划(仅复杂任务使用):如"context"包含非空"task_plan",则更新它。否则,先评估任务复杂度——如果预计操作步骤不超过3步(如:单次查询、简单问答、格式转换、单文件修改、简单计算等简单任务),则<task_plan>输出为空,不要创建任务列表;只有当任务较复杂(预计超过3步操作,如:多文件修改、需要调研+实现+测试、涉及多个模块联动等),才以Markdown列表格式制定新任务列表。格式:每项用 "- [ ] 任务描述" 或 "- [x] 已完成任务",含完成状态标记。</task_plan>
48
48
 
49
49
  <toolstocal>
50
- <tool><beforecalltext>连接词,介绍调用什么工具,达到什么目的。</beforecalltext><toolname>工具名</toolname><parms>JSON格式的参数对象,例如: {"query": "搜索关键词", "num": 5}</parms><timeout>预估超时时限(秒)</timeout><callback>true/false,要求解析器在该工具执行完后是否要回调llm大模型,将所有工具输出结果+新构造的"context"输入给llm</callback></tool>
50
+ <tool><beforecalltext>连接词,介绍调用什么工具,达到什么目的。</beforecalltext><toolname>工具名</toolname><parms>JSON格式的参数对象,例如: {"query": "搜索关键词", "num": 5}</parms><timeout>预估超时时限(秒)</timeout></tool>
51
51
  </toolstocal>
52
52
  <remember><type>global或session</type><content>仅从最新用户输入(userprint 或 usersays_correct)中提炼值得记忆的信息(如用户偏好、重要结论、错误经验等)。type=global表示跨会话全局记忆,type=session表示仅当前会话可用的记忆。如果本轮没有新信息需要记忆,则<content>为空、<type>不填。</content></remember>
53
53
  <recall>下一轮需要主动召回的记忆描述。填写需要从记忆库中检索的关键字或描述。如果不填写则为空。如果需要更多记忆支持当前任务,填写相关关键词(可包含时间参考,如"2025年1月的项目"),系统将在下一轮搜索top5相关记忆并通过<recall_memory>注入上下文。你也可以直接调用recall_memory工具即时搜索。</recall>
@@ -69,17 +69,16 @@ class MainAgent(BaseAgent):
69
69
  5. <toolstocal>: 列出所有需要执行的工具调用,每个工具包含完整的参数说明
70
70
  6. <parms>: **必须使用严格合法的JSON格式**,例如 {"query": "关键词", "num": 10},不要使用其他格式
71
71
  7. <timeout>: 预估超时秒数(简单操作10-30s,文件操作30-60s,网络请求60-120s,数据处理120-300s)
72
- 8. <callback>: 如果该工具的执行结果对后续决策有影响,设为 true;否则设为 false
73
- 9. <remember>: 包含 <type> <content> 子标签。type "global"(跨会话全局记忆)或 "session"(仅当前会话)。content 填从最新用户输入中提炼的值得记忆的关键信息。如果本轮无需记忆,content 为空且不填 type。注意:用户个人偏好、重要结论、通用经验用 global;当前任务的临时上下文、过程信息用 session
74
- 10. <recall>: 填写下一轮需要从记忆库中主动召回的内容描述和关键字(可包含时间参考)。系统将根据这些信息搜索top5相关记忆,在下一轮通过 <recall_memory> 标签注入上下文。如果当前 <automemory> 中的记忆已足够完成任务,<recall> 为空;如果需要更多历史记忆支撑,则填写。你也可以直接使用 recall_memory 工具在当前轮即时搜索
75
- 11. <knowledge>: 从本轮对话或工具执行结果中提炼值得长期保存的专业知识、事实、经验法则、技术要点等。这些知识会被持久化到知识库文件,未来可通过 get_knowledge 检索复用。如果没有需要保存的知识,则为空。格式:简洁明确,每条知识一行
76
- 12. <get_knowledge>: 如果当前 <knowledge> 内容不足以完成任务,填写需要从知识库搜索的关键词;否则为空
77
- 13. <askuser>: 当信息不足需要用户补充时,在此填写要问的问题
78
- 14. <finish>: 当任务已完成或需要等待用户回应时为 true;否则为 false 继续执行
79
- 15. <finish_reason>: **finish=true 时必须填写**,详细说明结束原因(任务完成/等待用户/信息不足/无法处理等)
80
- 16. <next_step>: **finish=false 时必须填写**,描述下一步计划做什么,要求简洁明确(1-2句话)
81
- 17. <mainsubject>: 为当前对话生成6字以内的简短标题,概括对话主题。仅在对话刚开始的前几轮需要输出,已有标题后留空
82
- 18. 使用中文输出所有内容
72
+ 8. <remember>: 包含 <type> 和 <content> 子标签。type 填 "global"(跨会话全局记忆)或 "session"(仅当前会话)。content 填从最新用户输入中提炼的值得记忆的关键信息。如果本轮无需记忆,content 为空且不填 type。注意:用户个人偏好、重要结论、通用经验用 global;当前任务的临时上下文、过程信息用 session
73
+ 9. <recall>: 填写下一轮需要从记忆库中主动召回的内容描述和关键字(可包含时间参考)。系统将根据这些信息搜索top5相关记忆,在下一轮通过 <recall_memory> 标签注入上下文。如果当前 <automemory> 中的记忆已足够完成任务,<recall> 为空;如果需要更多历史记忆支撑,则填写。你也可以直接使用 recall_memory 工具在当前轮即时搜索
74
+ 10. <knowledge>: 从本轮对话或工具执行结果中提炼值得长期保存的专业知识、事实、经验法则、技术要点等。这些知识会被持久化到知识库文件,未来可通过 get_knowledge 检索复用。如果没有需要保存的知识,则为空。格式:简洁明确,每条知识一行
75
+ 11. <get_knowledge>: 如果当前 <knowledge> 内容不足以完成任务,填写需要从知识库搜索的关键词;否则为空
76
+ 12. <askuser>: 当信息不足需要用户补充时,在此填写要问的问题
77
+ 13. <finish>: 当任务已完成或需要等待用户回应时为 true;否则为 false 继续执行
78
+ 14. <finish_reason>: **finish=true 时必须填写**,详细说明结束原因(任务完成/等待用户/信息不足/无法处理等)
79
+ 15. <next_step>: **finish=false 时必须填写**,描述下一步计划做什么,要求简洁明确(1-2句话)
80
+ 16. <mainsubject>: 为当前对话生成6字以内的简短标题,概括对话主题。仅在对话刚开始的前几轮需要输出,已有标题后留空
81
+ 17. 使用中文输出所有内容
83
82
 
84
83
  ## 上下文中的记忆系统说明
85
84
  - <automemory>: 系统自动根据你通过 <remember> 保存的记忆和当前用户输入,搜索出的 top10 相关记忆。这些是你过去主动记住的内容(包含时间信息),可供参考。
@@ -462,8 +461,6 @@ class MainAgent(BaseAgent):
462
461
  _v2_reasoning_collected: List[str] = []
463
462
  # XML 解析失败时的 LLM 修正重试计数
464
463
  _xml_correction_retries: int = 0
465
- # 连续无工具调用计数(防止模型一直不调用工具导致死循环)
466
- _consecutive_no_tool_count: int = 0
467
464
 
468
465
  conversation_history = list(context.conversation_history or [])
469
466
 
@@ -907,9 +904,8 @@ class MainAgent(BaseAgent):
907
904
  )
908
905
  break
909
906
 
910
- # Step 10: 执行工具调用
907
+ # Step 10: 无工具调用
911
908
  if not parsed.tools_to_call:
912
- _consecutive_no_tool_count += 1
913
909
  # 如果 finish=true 且无工具,任务完成
914
910
  if parsed.finish:
915
911
  logger.info(f"[{task_id}] finish=true 且无工具调用,任务完成")
@@ -928,35 +924,10 @@ class MainAgent(BaseAgent):
928
924
  content=final_text,
929
925
  )
930
926
  break
931
- elif _consecutive_no_tool_count >= 2:
932
- # 连续 2 轮无工具调用,视为模型无意继续执行任务
933
- logger.warning(
934
- f"[{task_id}] 连续 {_consecutive_no_tool_count} 轮无工具调用且未 finish,"
935
- f"视为任务结束"
936
- )
937
- if _v2_reasoning_collected:
938
- final_text = "\n".join(_v2_reasoning_collected)
939
- else:
940
- before, after = extract_surrounding_text(llm_raw)
941
- final_text = (before + "\n" + after).strip() if (before.strip() or after.strip()) else "任务已完成。"
942
- context.working_memory["final_response"] = final_text
943
- if not _emitted_reasoning_this_iter:
944
- await self._emit_v2_event("v2_reasoning", {"content": truncate_str(final_text, 3000)}, stream_callback)
945
- if self.memory:
946
- self.memory.add_session(
947
- session_id=context.session_id,
948
- role="assistant",
949
- content=final_text,
950
- )
951
- break
952
927
  else:
953
- # finish 且未达到连续无工具阈值,继续回调 LLM
954
- # 模型可能只是在中间步骤输出说明文字,下一轮才调用工具
955
- logger.info(
956
- f"[{task_id}] 无工具调用且未 finish(连续第 {_consecutive_no_tool_count} 轮),"
957
- f"继续回调 LLM"
958
- )
959
- # 将当前输出作为对话历史,回调 LLM 继续执行
928
+ # finish=false,不限次数继续回调 LLM
929
+ # 模型可能在中间步骤输出说明文字,下一轮才调用工具
930
+ logger.info(f"[{task_id}] 无工具调用且 finish=false,继续回调 LLM")
960
931
  if llm_raw.strip():
961
932
  conversation_history.append(Message(role="assistant", content=llm_raw))
962
933
  conversation_history.append(Message(
@@ -967,23 +938,22 @@ class MainAgent(BaseAgent):
967
938
  ))
968
939
  continue
969
940
 
970
- # Step 11: 有工具调用 — 先执行所有工具,再根据 finish 决定回调
971
- _consecutive_no_tool_count = 0 # 有工具调用,重置计数器
972
- need_callback = False
941
+ # Step 11: 有工具调用 — 顺序执行所有工具
942
+ # 统一回调策略: 所有工具执行完毕,或任一工具超时 → 立即用已收集的结果回调 LLM
973
943
  tool_outputs_parts = []
974
- _reasoning_len_before_round = len(_v2_reasoning_collected) # 记录本轮开始时的长度
944
+ _reasoning_len_before_round = len(_v2_reasoning_collected)
945
+ _has_timeout = False
975
946
 
976
947
  for tool_info in parsed.tools_to_call:
977
948
  tool_name = tool_info.get("toolname", "").strip()
978
949
  before_call = tool_info.get("beforecalltext", "")
979
950
  parms = tool_info.get("parms", "")
980
951
  timeout = tool_info.get("timeout", 120)
981
- should_callback = tool_info.get("callback", True)
982
952
 
983
953
  if not tool_name:
984
954
  continue
985
955
 
986
- logger.info(f"[{task_id}] 执行工具: {tool_name} (timeout={timeout}s, callback={should_callback})")
956
+ logger.info(f"[{task_id}] 执行工具: {tool_name} (timeout={timeout}s)")
987
957
 
988
958
  # 发送 beforecalltext 作为显示文本
989
959
  if before_call:
@@ -1003,7 +973,6 @@ class MainAgent(BaseAgent):
1003
973
  "beforecalltext": before_call,
1004
974
  "parms": truncate_str(parms, 500),
1005
975
  "timeout": timeout,
1006
- "callback": should_callback,
1007
976
  }},
1008
977
  stream_callback,
1009
978
  )
@@ -1167,10 +1136,8 @@ class MainAgent(BaseAgent):
1167
1136
 
1168
1137
  is_timeout = tool_result.get("timed_out", False)
1169
1138
  if is_timeout:
1170
- need_callback = True
1171
- logger.warning(f"[{task_id}] 工具 {tool_name} 超时 ({timeout}s)")
1172
- elif should_callback:
1173
- need_callback = True
1139
+ _has_timeout = True
1140
+ logger.warning(f"[{task_id}] 工具 {tool_name} 超时 ({timeout}s),停止执行剩余工具")
1174
1141
 
1175
1142
  output_str = tool_output_text
1176
1143
  # 数据密集型工具允许更长的输出
@@ -1180,10 +1147,12 @@ class MainAgent(BaseAgent):
1180
1147
  _max_output = 6000
1181
1148
  else:
1182
1149
  _max_output = 3000
1150
+ # 超时工具明确标注状态,让 LLM 清楚知道哪个工具超时了
1151
+ _status = "超时" if is_timeout else ('成功' if tool_result.get('success') else '失败')
1183
1152
  tool_outputs_parts.append(
1184
1153
  f"### {before_call}\n"
1185
1154
  f"**工具**: {tool_name}\n"
1186
- f"**结果**: {'成功' if tool_result.get('success') else '失败'}\n"
1155
+ f"**结果**: {_status}\n"
1187
1156
  f"{truncate_str(output_str, _max_output)}\n"
1188
1157
  )
1189
1158
 
@@ -1193,7 +1162,7 @@ class MainAgent(BaseAgent):
1193
1162
  ))
1194
1163
  conversation_history.append(Message(
1195
1164
  role="user",
1196
- content=f"[工具 {tool_name} 执行完成] {'成功' if tool_result.get('success') else '失败'}",
1165
+ content=f"[工具 {tool_name} {'超时' if is_timeout else '执行完成'}] {_status}",
1197
1166
  ))
1198
1167
 
1199
1168
  # 保存工具调用到会话记忆
@@ -1210,15 +1179,32 @@ class MainAgent(BaseAgent):
1210
1179
  self.memory.add_session(
1211
1180
  session_id=context.session_id,
1212
1181
  role="tool",
1213
- content=f"[{tool_name}] {'成功' if tool_result.get('success') else '失败'}\n{truncate_str(output_str, 5000)}",
1182
+ content=f"[{tool_name}] {_status}\n{truncate_str(output_str, 5000)}",
1214
1183
  key="tool_result",
1215
1184
  importance=0.4,
1216
1185
  )
1217
1186
 
1187
+ # 任一工具超时 → 立即停止执行剩余工具
1188
+ if _has_timeout:
1189
+ # 收集当前工具之后未执行的工具,一并写入回调数据
1190
+ _current_idx = parsed.tools_to_call.index(tool_info)
1191
+ _skipped_tools = []
1192
+ for _remaining in parsed.tools_to_call[_current_idx + 1:]:
1193
+ _rname = _remaining.get("toolname", "").strip()
1194
+ if _rname:
1195
+ _skipped_tools.append(_rname)
1196
+ if _skipped_tools:
1197
+ tool_outputs_parts.append(
1198
+ f"**注意**: 工具 {tool_name} 执行超时 ({timeout}s),"
1199
+ f"以下工具未执行: {', '.join(_skipped_tools)}"
1200
+ )
1201
+ break
1202
+
1218
1203
  all_tool_outputs = "\n".join(tool_outputs_parts)
1219
1204
 
1220
- # Step 12: 工具执行完毕后,根据 finish 标志决定是否回调 LLM
1221
- # 核心逻辑: finish=true 表示任务已完成/不需要再调用LLM,即使工具设置了callback=true
1205
+ # Step 12: 统一回调决策
1206
+ # 所有工具结果已收集到 all_tool_outputs(或任一超时提前收集)
1207
+ # finish=true → 任务完成,不回调;finish=false → 将结果喂回 LLM 继续执行
1222
1208
  if parsed.finish:
1223
1209
  logger.info(f"[{task_id}] finish=true,任务已完成,不回调 LLM")
1224
1210
  # 构建有意义的最终回复:使用当前轮次的 reasoning text + 任务计划摘要
@@ -1249,10 +1235,9 @@ class MainAgent(BaseAgent):
1249
1235
  )
1250
1236
  break
1251
1237
 
1252
- # finish=false: 必须继续回调 LLM,无论工具 callback 设置如何
1253
- # callback 只控制工具结果是否喂回 LLM,不控制循环是否继续
1254
- # 只有 finish=true 才应该终止循环
1255
- logger.info(f"[{task_id}] finish=false,继续回调 LLM...")
1238
+ # finish=false: 统一回调 LLM(所有工具结果已收集到 all_tool_outputs)
1239
+ _timeout_info = "(有工具超时,已停止剩余工具)" if _has_timeout else f"({len(tool_outputs_parts)} 个工具结果已收集)"
1240
+ logger.info(f"[{task_id}] finish=false,继续回调 LLM {_timeout_info}")
1256
1241
 
1257
1242
  # 回调前,保存当前轮次的 LLM 输出到会话记忆
1258
1243
  # 这样每轮工具调用都有对应的 assistant 消息记录
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.15.45",
3
+ "version": "1.15.47",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
@@ -65,4 +65,4 @@
65
65
  "departments/",
66
66
  "web/"
67
67
  ]
68
- }
68
+ }