myagent-ai 1.9.9 → 1.10.0
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.
- package/agents/base.py +11 -1
- package/agents/main_agent.py +39 -17
- package/config.py +8 -0
- package/core/logger.py +16 -1
- package/core/output_parser.py +141 -5
- package/main.py +5 -4
- package/package.json +3 -3
- package/web/api_server.py +191 -24
- package/web/ui/chat/chat.css +154 -0
- package/web/ui/chat/chat_container.html +2 -2
- package/web/ui/chat/chat_main.js +259 -2
- package/web/ui/chat/flow_engine.js +78 -2
- package/web/ui/chat/middle_chat.html +21 -0
- package/web/ui/index.html +172 -24
package/agents/base.py
CHANGED
|
@@ -142,6 +142,12 @@ class BaseAgent(ABC):
|
|
|
142
142
|
if tools:
|
|
143
143
|
request_kwargs["tools"] = tools
|
|
144
144
|
request_kwargs["tool_choice"] = "auto"
|
|
145
|
+
# ── 推理模式 (Reasoning) ──
|
|
146
|
+
if self.llm.reasoning:
|
|
147
|
+
if self.llm.provider in self.llm._OPENAI_COMPATIBLE_PROVIDERS or self.llm.provider == "zhipu":
|
|
148
|
+
request_kwargs["reasoning_effort"] = self.llm.reasoning_effort
|
|
149
|
+
if self.llm.max_tokens < 8192:
|
|
150
|
+
request_kwargs["max_tokens"] = 8192
|
|
145
151
|
request_kwargs.update(kwargs)
|
|
146
152
|
|
|
147
153
|
full_text = ""
|
|
@@ -351,9 +357,13 @@ class BaseAgent(ABC):
|
|
|
351
357
|
"arguments": args,
|
|
352
358
|
})
|
|
353
359
|
|
|
360
|
+
# 对于推理模型(如 o1/DeepSeek-R1),如果 content 为空但有 reasoning 内容,
|
|
361
|
+
# 使用 reasoning 内容作为最终回复
|
|
362
|
+
final_content = full_text if full_text.strip() else (full_reasoning if full_reasoning.strip() else full_text)
|
|
363
|
+
|
|
354
364
|
return LLMResponse(
|
|
355
365
|
success=True,
|
|
356
|
-
content=
|
|
366
|
+
content=final_content,
|
|
357
367
|
tool_calls=final_tool_calls,
|
|
358
368
|
finish_reason=finish_reason,
|
|
359
369
|
model=request_kwargs.get("model", self.llm.model),
|
package/agents/main_agent.py
CHANGED
|
@@ -39,8 +39,9 @@ class MainAgent(BaseAgent):
|
|
|
39
39
|
# =========================================================================
|
|
40
40
|
SYSTEM_PROMPT = """你是一个强内容分析格式转化引擎,要深入分析以下上下文内容:
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
严格以XML格式化输出以下内容:
|
|
43
43
|
<output>
|
|
44
|
+
<response>直接回复用户的内容。这是一段友好、自然的话语,用于向用户说明你正在做什么,或者回应用户的问题/问候。要求简洁、有礼貌、符合对话场景。如果用户只是问候,简单回应即可;如果用户有具体任务,要说明你的计划。</response>
|
|
44
45
|
<usersays_correct>根据用户输入的"usersays"内容,结合上下文优化为新的用户输入,如果"usersays"为空,这里输出为空。</usersays_correct>
|
|
45
46
|
<task_plan>如"context"包含非空"task_plan",则更新它,变为当前输出。否则,根据"context", 以MD 的格式,制定新任务列表。</task_plan>
|
|
46
47
|
|
|
@@ -59,18 +60,19 @@ class MainAgent(BaseAgent):
|
|
|
59
60
|
|
|
60
61
|
## 核心规则
|
|
61
62
|
1. 你必须且只能输出 <output> XML 结构,不要输出任何其他文本
|
|
62
|
-
2. <
|
|
63
|
-
3. <
|
|
64
|
-
4. <
|
|
65
|
-
5. <
|
|
66
|
-
6. <
|
|
67
|
-
7. <
|
|
68
|
-
8. <
|
|
69
|
-
9. <
|
|
70
|
-
10. <
|
|
71
|
-
11. <
|
|
72
|
-
12.
|
|
73
|
-
13.
|
|
63
|
+
2. <response>: 必须输出一段直接回复用户的话语(这是用户实际看到的回复),要求简洁友好、自然流畅。不要只输出任务计划而不说话!
|
|
64
|
+
3. <usersays_correct>: 如果 context 中 usersays 非空,则根据对话语境优化为更准确的用户意图表达
|
|
65
|
+
4. <task_plan>: 使用 Markdown 列表格式,每项包含任务描述和完成状态标记 [x]/[ ]
|
|
66
|
+
5. <toolstocal>: 列出所有需要执行的工具调用,每个工具包含完整的参数说明
|
|
67
|
+
6. <timeout>: 预估超时秒数(简单操作10-30s,文件操作30-60s,网络请求60-120s,数据处理120-300s)
|
|
68
|
+
7. <callback>: 如果该工具的执行结果对后续决策有影响,设为 true;否则设为 false
|
|
69
|
+
8. <remember>: 仅从最新用户输入(userprint 或 usersays_correct)中提炼值得长期记忆的关键信息,不要重复提炼历史对话中已有的记忆。如果本轮没有新信息需要记忆,则为空
|
|
70
|
+
9. <recall>: 描述下一轮执行时需要从记忆库中检索的内容关键词
|
|
71
|
+
10. <get_knowledge>: 如果当前 <knowledge> 内容不足以完成任务,填写需要从知识库搜索的关键词;否则为空
|
|
72
|
+
11. <askuser>: 当信息不足需要用户补充时,在此填写要问的问题
|
|
73
|
+
12. <finish>: 当任务已完成或需要等待用户回应时为 true;否则为 false 继续执行
|
|
74
|
+
13. 使用中文输出所有内容
|
|
75
|
+
14. 优先使用技能系统(skill)而非直接写代码执行
|
|
74
76
|
"""
|
|
75
77
|
|
|
76
78
|
def __init__(self, tool_agent=None, memory_agent=None, **kwargs):
|
|
@@ -173,8 +175,6 @@ class MainAgent(BaseAgent):
|
|
|
173
175
|
stream_callback(event)
|
|
174
176
|
except Exception as e:
|
|
175
177
|
logger.debug(f"V2 SSE 事件发送失败 ({event_type}): {e}")
|
|
176
|
-
else:
|
|
177
|
-
logger.debug(f"[v2-event] {event_type}: {data}")
|
|
178
178
|
|
|
179
179
|
async def _merge_duplicate_memory(
|
|
180
180
|
self,
|
|
@@ -391,7 +391,7 @@ class MainAgent(BaseAgent):
|
|
|
391
391
|
+ self.SYSTEM_PROMPT.split("\n", 1)[1]
|
|
392
392
|
)
|
|
393
393
|
system_content = _prompt_with_placeholder.replace(_CONTEXT_PLACEHOLDER, context_xml)
|
|
394
|
-
|
|
394
|
+
print(system_content)
|
|
395
395
|
# Step 3: 调用 LLM
|
|
396
396
|
messages = [Message(role="system", content=system_content)]
|
|
397
397
|
|
|
@@ -452,7 +452,17 @@ class MainAgent(BaseAgent):
|
|
|
452
452
|
)
|
|
453
453
|
break
|
|
454
454
|
else:
|
|
455
|
-
|
|
455
|
+
# XML 解析失败且无法提取文本,发送原始输出作为备选
|
|
456
|
+
logger.warning(f"[{task_id}] 无法提取文本,发送原始 LLM 输出")
|
|
457
|
+
final_text = llm_raw.strip() if llm_raw.strip() else "处理完毕。"
|
|
458
|
+
context.working_memory["final_response"] = final_text
|
|
459
|
+
await self._emit_v2_event("v2_reasoning", {"content": final_text}, stream_callback)
|
|
460
|
+
if self.memory:
|
|
461
|
+
self.memory.add_short_term(
|
|
462
|
+
session_id=context.session_id,
|
|
463
|
+
role="assistant",
|
|
464
|
+
content=final_text,
|
|
465
|
+
)
|
|
456
466
|
break
|
|
457
467
|
|
|
458
468
|
warnings = validate_output(parsed)
|
|
@@ -463,6 +473,18 @@ class MainAgent(BaseAgent):
|
|
|
463
473
|
if parsed.usersays_correct:
|
|
464
474
|
context.working_memory["usersays_correct"] = parsed.usersays_correct
|
|
465
475
|
|
|
476
|
+
# Step 5.5: 处理 response — 直接回复用户的内容
|
|
477
|
+
if parsed.response:
|
|
478
|
+
response_text = parsed.response.strip()
|
|
479
|
+
if response_text:
|
|
480
|
+
logger.debug(f"[{task_id}] 模型回复用户: {response_text[:100]}")
|
|
481
|
+
context.working_memory["model_response"] = response_text
|
|
482
|
+
await self._emit_v2_event(
|
|
483
|
+
"v2_reasoning",
|
|
484
|
+
{"content": response_text},
|
|
485
|
+
stream_callback,
|
|
486
|
+
)
|
|
487
|
+
|
|
466
488
|
# Step 6: 处理 remember — 查重+LLM合并后存入长期记忆
|
|
467
489
|
if parsed.remember:
|
|
468
490
|
try:
|
package/config.py
CHANGED
|
@@ -23,11 +23,15 @@ from typing import Optional, Dict, Any, List
|
|
|
23
23
|
class LLMConfig:
|
|
24
24
|
"""LLM 大模型配置"""
|
|
25
25
|
provider: str = "openai" # openai | anthropic | ollama | custom
|
|
26
|
+
api_type: str = "openai-completions" # API 类型
|
|
26
27
|
api_key: str = ""
|
|
27
28
|
base_url: str = "https://api.openai.com/v1"
|
|
28
29
|
model: str = "gpt-4"
|
|
29
30
|
temperature: float = 0.1
|
|
30
31
|
max_tokens: int = 4096
|
|
32
|
+
context_window: int = 128000 # 上下文窗口大小
|
|
33
|
+
input_modes: List[str] = field(default_factory=lambda: ["text"]) # 支持的输入模式
|
|
34
|
+
reasoning: bool = False # 是否支持推理
|
|
31
35
|
timeout: int = 120 # 请求超时(秒)
|
|
32
36
|
max_retries: int = 3 # 最大重试次数
|
|
33
37
|
# Anthropic 专用
|
|
@@ -86,11 +90,15 @@ class ModelEntry:
|
|
|
86
90
|
id: str = "" # 唯一标识符,如 "gpt-4o", "claude-3.5-sonnet"
|
|
87
91
|
name: str = "" # 显示名称
|
|
88
92
|
provider: str = "" # openai | anthropic | ollama | zhipu | custom
|
|
93
|
+
api_type: str = "openai-completions" # API 类型:openai-completions | openai-chat | anthropic | ollama | custom
|
|
89
94
|
model: str = "" # API 调用使用的实际模型字符串
|
|
90
95
|
base_url: str = "" # 自定义 Base URL(空=使用 provider 默认值)
|
|
91
96
|
api_key: str = "" # 专用 API Key(空=使用全局默认值)
|
|
92
97
|
max_tokens: int = 4096
|
|
93
98
|
temperature: float = 0.1
|
|
99
|
+
context_window: int = 128000 # 上下文窗口大小(token)
|
|
100
|
+
input_modes: List[str] = field(default_factory=lambda: ["text"]) # 支持的输入模式: text, image, video, audio
|
|
101
|
+
reasoning: bool = True # 是否支持推理(如 o1 系列)
|
|
94
102
|
enabled: bool = True
|
|
95
103
|
|
|
96
104
|
|
package/core/logger.py
CHANGED
|
@@ -148,6 +148,12 @@ def setup_logger(
|
|
|
148
148
|
# 控制台输出
|
|
149
149
|
if console:
|
|
150
150
|
ch = logging.StreamHandler(sys.stdout)
|
|
151
|
+
# Windows 兼容 UTF-8
|
|
152
|
+
if sys.platform == "win32":
|
|
153
|
+
try:
|
|
154
|
+
sys.stdout.reconfigure(encoding="utf-8")
|
|
155
|
+
except Exception:
|
|
156
|
+
pass
|
|
151
157
|
ch.setFormatter(ColorFormatter(fmt, datefmt=datefmt))
|
|
152
158
|
ch.setLevel(logging.DEBUG) # 控制台始终显示所有级别
|
|
153
159
|
logger.addHandler(ch)
|
|
@@ -216,7 +222,16 @@ def get_logger(name: str = "myagent") -> logging.Logger:
|
|
|
216
222
|
"""获取已存在的 Logger,如果不存在则创建默认的"""
|
|
217
223
|
logger = logging.getLogger(name)
|
|
218
224
|
if not logger.handlers:
|
|
219
|
-
|
|
225
|
+
# 子 logger 继承父 logger 的 handlers(如文件 handler)
|
|
226
|
+
parent_logger = logging.getLogger()
|
|
227
|
+
if parent_logger.handlers:
|
|
228
|
+
for handler in parent_logger.handlers:
|
|
229
|
+
logger.addHandler(handler)
|
|
230
|
+
logger.setLevel(parent_logger.level)
|
|
231
|
+
logger.propagate = parent_logger.propagate
|
|
232
|
+
else:
|
|
233
|
+
# 没有父 logger,创建默认配置
|
|
234
|
+
return setup_logger(name)
|
|
220
235
|
return logger
|
|
221
236
|
|
|
222
237
|
|
package/core/output_parser.py
CHANGED
|
@@ -43,6 +43,10 @@ import xml.etree.ElementTree as ET
|
|
|
43
43
|
from dataclasses import dataclass, field
|
|
44
44
|
from typing import Any, Dict, List
|
|
45
45
|
|
|
46
|
+
from core.logger import get_logger
|
|
47
|
+
|
|
48
|
+
logger = get_logger("myagent.output_parser")
|
|
49
|
+
|
|
46
50
|
# ---------------------------------------------------------------------------
|
|
47
51
|
# Constants
|
|
48
52
|
# ---------------------------------------------------------------------------
|
|
@@ -95,6 +99,7 @@ class ParsedOutput:
|
|
|
95
99
|
ask_user: str = ""
|
|
96
100
|
get_knowledge: str = ""
|
|
97
101
|
finish: bool = False
|
|
102
|
+
response: str = "" # 模型对用户的直接回复(友好自然的话语)
|
|
98
103
|
raw_text: str = ""
|
|
99
104
|
parse_success: bool = False
|
|
100
105
|
|
|
@@ -207,21 +212,149 @@ def _parse_tools_element(tools_element: ET.Element | None) -> List[Dict[str, Any
|
|
|
207
212
|
return tools
|
|
208
213
|
|
|
209
214
|
|
|
215
|
+
def _fix_incomplete_xml(xml_content: str) -> str:
|
|
216
|
+
"""修复不完整的 XML,使用正则表达式补齐格式。
|
|
217
|
+
|
|
218
|
+
支持修复的问题类型:
|
|
219
|
+
1. 自闭合标签误用:<tag /> → <tag></tag>
|
|
220
|
+
2. 缺少闭合标签:<tag>value → <tag>value</tag>
|
|
221
|
+
3. 标签大小写混乱:<TAG>value</TAG> → <tag>value</tag>
|
|
222
|
+
4. 空白字符问题:< tag >value</ tag >
|
|
223
|
+
5. 嵌套标签未闭合
|
|
224
|
+
6. 特殊字符转义:& → & (在属性值中)
|
|
225
|
+
"""
|
|
226
|
+
if not xml_content:
|
|
227
|
+
return xml_content
|
|
228
|
+
|
|
229
|
+
# 标准标签列表(用于修复缺少闭合标签)
|
|
230
|
+
STANDARD_TAGS = [
|
|
231
|
+
"usersays_correct", "task_plan", "toolstocal", "remember",
|
|
232
|
+
"recall", "askuser", "get_knowledge", "finish", "response",
|
|
233
|
+
"tool", "beforecalltext", "toolname", "parms", "timeout", "callback"
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
# 1. 修复自闭合标签为普通标签
|
|
237
|
+
# <tag /> → <tag></tag>
|
|
238
|
+
# <tag/> → <tag></tag>
|
|
239
|
+
xml_content = re.sub(r'<(\w+)\s*/\s*>', r'<\1></\1>', xml_content)
|
|
240
|
+
|
|
241
|
+
# 2. 修复空白字符在标签内的问题:< tag > → <tag>
|
|
242
|
+
# 同时规范化大小写
|
|
243
|
+
def normalize_tag(match):
|
|
244
|
+
inner = match.group(1).strip()
|
|
245
|
+
tag_name = inner.split()[0].lower()
|
|
246
|
+
rest = ' '.join(inner.split()[1:]) # 保留可能的属性
|
|
247
|
+
if rest:
|
|
248
|
+
return f'<{tag_name} {rest}>'
|
|
249
|
+
return f'<{tag_name}>'
|
|
250
|
+
xml_content = re.sub(r'<([a-z_][a-z_0-9]*)[^>]*>', normalize_tag, xml_content, flags=re.IGNORECASE)
|
|
251
|
+
|
|
252
|
+
# 3. 修复闭合标签的大小写和空白:</ TAG > → </tag>
|
|
253
|
+
def normalize_close_tag(match):
|
|
254
|
+
tag_name = match.group(1).strip().lower()
|
|
255
|
+
return f'</{tag_name}>'
|
|
256
|
+
xml_content = re.sub(r'</\s*([a-z_][a-z_0-9]*)\s*>', normalize_close_tag, xml_content, flags=re.IGNORECASE)
|
|
257
|
+
|
|
258
|
+
# 4. 修复缺少闭合标签的问题
|
|
259
|
+
# 策略:对于标准标签,如果后面跟着另一个标签或 </output>,则添加闭合标签
|
|
260
|
+
for tag in STANDARD_TAGS:
|
|
261
|
+
# 修复 <tag>value<下一个标签> 格式(缺少 </tag>)
|
|
262
|
+
# 例如:<finish>true<task_plan> → <finish>true</finish><task_plan>
|
|
263
|
+
next_tag_pattern = '|'.join([re.escape(t) for t in STANDARD_TAGS if t != tag])
|
|
264
|
+
if next_tag_pattern:
|
|
265
|
+
# 匹配 <tag>...后面跟着其他标签或 </output>
|
|
266
|
+
pattern = rf'(<{tag}>)([^<]*?)(?=(?:<{next_tag_pattern}|</output>))'
|
|
267
|
+
replacement = rf'\1\2</{tag}>'
|
|
268
|
+
xml_content = re.sub(pattern, replacement, xml_content, flags=re.IGNORECASE | re.DOTALL)
|
|
269
|
+
|
|
270
|
+
# 5. 修复 <toolstocal> 和 </toolstocal> 标签
|
|
271
|
+
xml_content = re.sub(r'<toolstocal\s*>', '<toolstocal>', xml_content, flags=re.IGNORECASE)
|
|
272
|
+
xml_content = re.sub(r'</toolstocal\s*>', '</toolstocal>', xml_content, flags=re.IGNORECASE)
|
|
273
|
+
|
|
274
|
+
# 6. 修复 askuser 标签(系统提示中用的是 askuser,但有时可能写成 ask_user)
|
|
275
|
+
xml_content = re.sub(r'<ask_user\s*>', '<askuser>', xml_content, flags=re.IGNORECASE)
|
|
276
|
+
xml_content = re.sub(r'</ask_user\s*>', '</askuser>', xml_content, flags=re.IGNORECASE)
|
|
277
|
+
|
|
278
|
+
# 7. 修复单独的 <tool> 块中的标签
|
|
279
|
+
# 匹配 <tool>... 中缺少闭合标签的情况
|
|
280
|
+
tool_tags = ["beforecalltext", "toolname", "parms", "timeout", "callback"]
|
|
281
|
+
tool_next_pattern = '|'.join([re.escape(t) for t in tool_tags])
|
|
282
|
+
|
|
283
|
+
for tag in tool_tags:
|
|
284
|
+
pattern = rf'(<{tag}>)([^<]*?)(?=(?:<{tool_next_pattern}|</tool>|</toolstocal>|</output>))'
|
|
285
|
+
replacement = rf'\1\2</{tag}>'
|
|
286
|
+
xml_content = re.sub(pattern, replacement, xml_content, flags=re.IGNORECASE | re.DOTALL)
|
|
287
|
+
|
|
288
|
+
# 8. 处理文本内容中的特殊 XML 字符(简单处理 &)
|
|
289
|
+
# 只在标签外的内容中处理
|
|
290
|
+
# 这个比较复杂,简单处理:在文本内容中 & 后没有 ; 的转为 &
|
|
291
|
+
# 但更安全的做法是只在必要时处理
|
|
292
|
+
|
|
293
|
+
# 9. 确保 <output> 标签周围没有多余空白
|
|
294
|
+
xml_content = xml_content.strip()
|
|
295
|
+
|
|
296
|
+
return xml_content
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _aggressive_clean_xml(xml_content: str) -> str:
|
|
300
|
+
"""激进清理 XML 内容,移除可能导致解析失败的字符。"""
|
|
301
|
+
# 移除控制字符(除了换行和 tab)
|
|
302
|
+
xml_content = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '', xml_content)
|
|
303
|
+
|
|
304
|
+
# 修复 & 字符(确保它是有效的 XML 实体)
|
|
305
|
+
# 匹配 & 不在有效实体前的情况
|
|
306
|
+
xml_content = re.sub(r'&(?!amp;|lt;|gt;|quot;|apos;|#\d+;|#x[0-9a-fA-F]+;)', '&', xml_content)
|
|
307
|
+
|
|
308
|
+
# 移除多余的空白(连续多个空白合并为一个)
|
|
309
|
+
xml_content = re.sub(r'>\s+<', '><', xml_content)
|
|
310
|
+
xml_content = re.sub(r'\s{2,}', ' ', xml_content)
|
|
311
|
+
|
|
312
|
+
return xml_content
|
|
313
|
+
|
|
314
|
+
|
|
210
315
|
def _parse_xml_content(xml_content: str) -> ParsedOutput:
|
|
211
316
|
"""Attempt to parse *xml_content* (the inner body of ``<output>``) as XML.
|
|
212
317
|
|
|
213
318
|
Assumes *xml_content* has already been extracted from the surrounding
|
|
214
319
|
``<output>`` tags. If parsing fails a :class:`ParsedOutput` with
|
|
215
320
|
``parse_success=False`` is returned.
|
|
321
|
+
|
|
322
|
+
解析策略:
|
|
323
|
+
1. 首先尝试直接解析
|
|
324
|
+
2. 如果失败,使用正则表达式修复后再解析
|
|
325
|
+
3. 如果仍然失败,尝试激进清理
|
|
216
326
|
"""
|
|
217
327
|
parsed = ParsedOutput(parse_success=False)
|
|
218
328
|
|
|
329
|
+
# Strategy 1: 尝试直接解析原始 XML
|
|
219
330
|
try:
|
|
220
|
-
root = ET.fromstring(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
331
|
+
root = ET.fromstring("<output>" + xml_content + "</output>")
|
|
332
|
+
parsed.parse_success = True
|
|
333
|
+
logger.debug(f"XML 直接解析成功")
|
|
334
|
+
except ET.ParseError as e1:
|
|
335
|
+
logger.debug(f"XML 直接解析失败: {e1},尝试修复...")
|
|
336
|
+
|
|
337
|
+
# Strategy 2: 修复不完整的 XML 后再解析
|
|
338
|
+
fixed_xml = _fix_incomplete_xml(xml_content)
|
|
339
|
+
logger.debug(f"修复后 XML 前200字符: {fixed_xml[:200]}...")
|
|
340
|
+
|
|
341
|
+
try:
|
|
342
|
+
root = ET.fromstring("<output>" + fixed_xml + "</output>")
|
|
343
|
+
parsed.parse_success = True
|
|
344
|
+
logger.debug(f"XML 修复后解析成功")
|
|
345
|
+
except ET.ParseError as e2:
|
|
346
|
+
logger.warning(f"XML 修复后仍然解析失败: {e2},尝试激进清理...")
|
|
347
|
+
# Strategy 3: 激进清理
|
|
348
|
+
cleaned = _aggressive_clean_xml(fixed_xml)
|
|
349
|
+
try:
|
|
350
|
+
root = ET.fromstring("<output>" + cleaned + "</output>")
|
|
351
|
+
parsed.parse_success = True
|
|
352
|
+
logger.debug(f"XML 激进清理后解析成功")
|
|
353
|
+
except ET.ParseError as e3:
|
|
354
|
+
logger.warning(f"XML 解析最终失败: {e3}")
|
|
355
|
+
return parsed
|
|
356
|
+
|
|
357
|
+
# 提取各字段
|
|
225
358
|
parsed.usersays_correct = _safe_strip(root.findtext("usersays_correct"))
|
|
226
359
|
parsed.task_plan = _safe_strip(root.findtext("task_plan"))
|
|
227
360
|
parsed.tools_to_call = _parse_tools_element(root.find("toolstocal"))
|
|
@@ -230,6 +363,7 @@ def _parse_xml_content(xml_content: str) -> ParsedOutput:
|
|
|
230
363
|
parsed.ask_user = _safe_strip(root.findtext("askuser"))
|
|
231
364
|
parsed.get_knowledge = _safe_strip(root.findtext("get_knowledge"))
|
|
232
365
|
parsed.finish = _parse_bool(root.findtext("finish"), False)
|
|
366
|
+
parsed.response = _safe_strip(root.findtext("response"))
|
|
233
367
|
|
|
234
368
|
return parsed
|
|
235
369
|
|
|
@@ -259,6 +393,7 @@ def _fallback_regex_parse(raw_text: str) -> ParsedOutput:
|
|
|
259
393
|
parsed.ask_user = _safe_strip(tag_map.get("askuser"))
|
|
260
394
|
parsed.get_knowledge = _safe_strip(tag_map.get("get_knowledge"))
|
|
261
395
|
parsed.finish = _parse_bool(tag_map.get("finish"), False)
|
|
396
|
+
parsed.response = _safe_strip(tag_map.get("response"))
|
|
262
397
|
|
|
263
398
|
# For toolstocal we attempt to find individual <tool> blocks.
|
|
264
399
|
tools_raw = tag_map.get("toolstocal", "")
|
|
@@ -347,6 +482,7 @@ def parse_output(raw_text: str) -> ParsedOutput:
|
|
|
347
482
|
parsed.ask_user = _safe_strip(root.findtext("askuser"))
|
|
348
483
|
parsed.get_knowledge = _safe_strip(root.findtext("get_knowledge"))
|
|
349
484
|
parsed.finish = _parse_bool(root.findtext("finish"), False)
|
|
485
|
+
parsed.response = _safe_strip(root.findtext("response"))
|
|
350
486
|
return parsed
|
|
351
487
|
except ET.ParseError:
|
|
352
488
|
pass
|
package/main.py
CHANGED
|
@@ -199,6 +199,7 @@ class MyAgentApp:
|
|
|
199
199
|
max_tokens=llm_cfg.max_tokens,
|
|
200
200
|
timeout=llm_cfg.timeout,
|
|
201
201
|
max_retries=llm_cfg.max_retries,
|
|
202
|
+
reasoning=llm_cfg.reasoning,
|
|
202
203
|
anthropic_api_key=llm_cfg.anthropic_api_key,
|
|
203
204
|
)
|
|
204
205
|
self.logger.info(f"LLM: {llm_cfg.provider}/{llm_cfg.model}")
|
|
@@ -487,7 +488,7 @@ class MyAgentApp:
|
|
|
487
488
|
|
|
488
489
|
print()
|
|
489
490
|
print("=" * 60)
|
|
490
|
-
print("
|
|
491
|
+
print(" MyAgent - 本地桌面端执行型AI助手")
|
|
491
492
|
print(f" 版本: v{get_version()}")
|
|
492
493
|
print(f" LLM: {self.config.llm.provider}/{self.config.llm.model}")
|
|
493
494
|
print(f" 技能: {len(self.skill_registry.list_skills())} 个已注册")
|
|
@@ -499,14 +500,14 @@ class MyAgentApp:
|
|
|
499
500
|
while self._running:
|
|
500
501
|
try:
|
|
501
502
|
# 读取用户输入
|
|
502
|
-
user_input = input("
|
|
503
|
+
user_input = input("> ").strip()
|
|
503
504
|
|
|
504
505
|
if not user_input:
|
|
505
506
|
continue
|
|
506
507
|
|
|
507
508
|
# 内置命令
|
|
508
509
|
if user_input.lower() in ("quit", "exit", "q"):
|
|
509
|
-
print("
|
|
510
|
+
print("再见!")
|
|
510
511
|
break
|
|
511
512
|
elif user_input.lower() == "help":
|
|
512
513
|
self._print_help()
|
|
@@ -1499,7 +1500,7 @@ def main():
|
|
|
1499
1500
|
args.web = args.port
|
|
1500
1501
|
if args.server and not args.host:
|
|
1501
1502
|
args.host = "0.0.0.0"
|
|
1502
|
-
web_port = args.web if args.web else args.port
|
|
1503
|
+
web_port = args.web if args.web else args.port
|
|
1503
1504
|
web_host = args.host or "127.0.0.1"
|
|
1504
1505
|
if not web_port:
|
|
1505
1506
|
# 仅 CLI 模式才用命令行配置向导
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myagent-ai",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.10.0",
|
|
4
|
+
"description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
|
|
5
5
|
"main": "main.py",
|
|
6
6
|
"bin": {
|
|
7
7
|
"myagent-ai": "start.js"
|
|
@@ -65,4 +65,4 @@
|
|
|
65
65
|
"departments/",
|
|
66
66
|
"web/"
|
|
67
67
|
]
|
|
68
|
-
}
|
|
68
|
+
}
|