Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 133 additions & 6 deletions keeper/agent/hybrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def __init__(self, config: AppConfig):
self._agent_loop: Optional[AgentLoop] = None
self._stream_callback: Optional[Callable] = None
self.memory = AgentMemory()
self._first_turn = True # 首次对话标志,用于注入记忆摘要

@property
def agent_loop(self) -> AgentLoop:
Expand Down Expand Up @@ -117,9 +118,23 @@ def process(self, user_input: str) -> str:
augmented_input = f"{user_input}\n\n[排查路线: {steps_desc}]"

# 注入历史记忆上下文
history_context = self.memory.get_context_for_prompt(user_input)
if history_context:
augmented_input = f"{history_context}\n[当前问题]\n{augmented_input}"
# 首次对话:主动注入最近记忆摘要(无论是否匹配关键词)
# 后续对话:仅在关键词匹配时注入相关记忆
if self._first_turn:
self._first_turn = False
recent = self.memory.get_recent(3)
if recent:
lines = ["[上次工作回顾]"]
for entry in recent:
time_str = entry.timestamp[:16].replace("T", " ")
lines.append(f" • [{time_str}] {entry.user_input}")
lines.append(f" 结论: {entry.conclusion[:100]}")
lines.append("")
augmented_input = "\n".join(lines) + f"[当前问题]\n{augmented_input}"
else:
history_context = self.memory.get_context_for_prompt(user_input)
if history_context:
augmented_input = f"{history_context}\n[当前问题]\n{augmented_input}"

response = self.agent_loop.run(augmented_input, stream_callback=self._stream_callback)

Expand Down Expand Up @@ -156,7 +171,14 @@ def process(self, user_input: str) -> str:

def _handle_slash_command(self, cmd: str) -> str:
"""处理斜杠命令"""
cmd = cmd.strip().lower()
cmd_raw = cmd.strip()
cmd_lower = cmd_raw.lower()

# /memory 支持参数,不能完全 lowercase(主机名/关键词可能大小写敏感)
if cmd_lower == "/memory" or cmd_lower == "/记忆" or cmd_lower.startswith("/memory "):
return self._handle_memory_command(cmd_raw)

cmd = cmd_lower

if cmd in ("/clear", "/reset"):
if self._agent_loop:
Expand All @@ -177,10 +199,107 @@ def _handle_slash_command(self, cmd: str) -> str:
mode = self._agent_loop.active_mode if self._agent_loop else "未初始化"
return f"[系统] 当前模式: Agent Loop ({mode})"

if cmd in ("/memory", "/记忆"):
if cmd in ("/memory", "/记忆") or cmd.startswith("/memory "):
return self._handle_memory_command(cmd)

if cmd in ("/plugins", "/插件"):
from .plugins import format_plugins_info
return format_plugins_info()

return f"[系统] 未知命令: {cmd}\n可用: /clear /history /tools /mode /memory /plugins"

def _handle_memory_command(self, cmd: str) -> str:
"""处理 /memory 命令(支持筛选参数)

用法:
/memory — 显示最近 5 条
/memory 10 — 显示最近 10 条
/memory --host xxx — 按主机筛选
/memory --cat xxx — 按类别筛选 (inspect/network/k8s/security/docker/fix)
/memory --search xxx — 按关键词搜索
/memory --date 2026-05-15 — 按日期筛选
"""
parts = cmd.strip().split()
# 去掉 /memory 本身
args = parts[1:] if len(parts) > 1 else []

if not args:
return self.memory.format_recent(5)

return f"[系统] 未知命令: {cmd}\n可用: /clear /history /tools /mode /memory"
# 解析参数
host_filter = None
cat_filter = None
search_kw = None
date_filter = None
count = 10

i = 0
while i < len(args):
arg = args[i]
if arg in ("--host", "-h") and i + 1 < len(args):
host_filter = args[i + 1]
i += 2
elif arg in ("--cat", "--category", "-c") and i + 1 < len(args):
cat_filter = args[i + 1]
i += 2
elif arg in ("--search", "--keyword", "-s", "-k") and i + 1 < len(args):
search_kw = args[i + 1]
i += 2
elif arg in ("--date", "-d") and i + 1 < len(args):
date_filter = args[i + 1]
i += 2
elif arg.isdigit():
count = int(arg)
i += 1
else:
# 当作搜索关键词
search_kw = arg
i += 1

# 执行筛选
if search_kw:
entries = self.memory.search(search_kw, limit=count)
elif host_filter:
entries = self.memory.get_host_history(host_filter, limit=count)
else:
entries = self.memory.get_recent(count)

# 二次过滤:按类别
if cat_filter:
entries = [e for e in entries if e.category == cat_filter]

# 二次过滤:按日期
if date_filter:
entries = [e for e in entries if e.timestamp.startswith(date_filter)]

if not entries:
hints = []
if host_filter:
hints.append(f"主机={host_filter}")
if cat_filter:
hints.append(f"类别={cat_filter}")
if search_kw:
hints.append(f"关键词={search_kw}")
if date_filter:
hints.append(f"日期={date_filter}")
filter_desc = ", ".join(hints) if hints else "无"
return f"[Agent 记忆] 未找到匹配记录 (筛选: {filter_desc})"

# 格式化输出
lines = [f"[Agent 记忆] 匹配 {len(entries)} 条记录:"]
lines.append("━" * 50)
for i_idx, entry in enumerate(entries, 1):
time_str = entry.timestamp[:16].replace("T", " ")
tools_str = ", ".join(entry.tools_used[:3])
cat_str = f" [{entry.category}]" if entry.category else ""
host_str = f" @{entry.host}" if entry.host else ""
lines.append(f" {i_idx}. [{time_str}]{cat_str}{host_str} {entry.user_input[:50]}")
lines.append(f" 工具: {tools_str}")
lines.append(f" 结论: {entry.conclusion[:80]}")
lines.append("━" * 50)
lines.append(f"共 {self.memory.count} 条记忆 | 显示 {len(entries)} 条")
lines.append("筛选: /memory --host <ip> | --cat <类别> | --search <关键词> | --date <YYYY-MM-DD>")
return "\n".join(lines)

def _handle_fast_path(self, intent: IntentType, entities: dict) -> str:
"""处理 Fast Path 意图"""
Expand Down Expand Up @@ -238,6 +357,14 @@ def _handle_agent_error(self, user_input: str, fast_result, error: Exception, st

def _get_help_text(self) -> str:
"""帮助信息"""
try:
from keeper.i18n import get_help_text
help_text = get_help_text()
if help_text and help_text != "agent.help":
return help_text
except Exception:
pass

from .free_tools import get_free_tools_description
return f"""[Keeper Agent 模式 — 自由模式]

Expand Down
15 changes: 14 additions & 1 deletion keeper/agent/loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,17 @@ def _emit(callback, event):
callback(msg)


AGENT_SYSTEM_PROMPT = """你是 Keeper,一个智能运维 Agent(当前版本 v1.0.0)。你拥有和资深 Linux 运维工程师一样的能力。
# ─── System Prompt(支持 i18n 动态加载)────────────────────────
def _get_system_prompt() -> str:
"""获取系统 Prompt(优先使用 i18n 模块)"""
try:
from keeper.i18n import get_system_prompt
return get_system_prompt()
except Exception:
return _FALLBACK_SYSTEM_PROMPT


_FALLBACK_SYSTEM_PROMPT = """你是 Keeper,一个智能运维 Agent(当前版本 v1.0.0)。你拥有和资深 Linux 运维工程师一样的能力。

## 关于你自己(Keeper 是什么)
Keeper 是一个类 Claude Code 的对话式 CLI 运维工具,运行在终端中。用户通过自然语言与你对话来管理服务器。
Expand Down Expand Up @@ -132,6 +142,9 @@ def _emit(callback, event):
- 最后给出 [总结] 和 [建议]
"""

# 动态获取(优先 i18n,fallback 到上面的中文)
AGENT_SYSTEM_PROMPT = _get_system_prompt()


@dataclass
class ToolCall:
Expand Down
Loading
Loading