mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-06-02 00:57:41 +08:00
feat: add knowledge switch and cli
This commit is contained in:
@@ -318,6 +318,8 @@ class MemoryManager:
|
|||||||
await self._sync_file(file_path, "memory", scope, user_id)
|
await self._sync_file(file_path, "memory", scope, user_id)
|
||||||
|
|
||||||
# Scan knowledge directory (structured knowledge wiki)
|
# Scan knowledge directory (structured knowledge wiki)
|
||||||
|
from config import conf
|
||||||
|
if conf().get("knowledge", True):
|
||||||
knowledge_dir = Path(workspace_dir) / "knowledge"
|
knowledge_dir = Path(workspace_dir) / "knowledge"
|
||||||
if knowledge_dir.exists():
|
if knowledge_dir.exists():
|
||||||
for file_path in knowledge_dir.rglob("*.md"):
|
for file_path in knowledge_dir.rglob("*.md"):
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from typing import List, Dict, Optional, Any
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from common.log import logger
|
from common.log import logger
|
||||||
|
from config import conf
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -129,6 +130,7 @@ def build_agent_system_prompt(
|
|||||||
sections.extend(_build_memory_section(memory_manager, tools, language))
|
sections.extend(_build_memory_section(memory_manager, tools, language))
|
||||||
|
|
||||||
# 3.5 知识系统(结构化知识库)
|
# 3.5 知识系统(结构化知识库)
|
||||||
|
if conf().get("knowledge", True):
|
||||||
sections.extend(_build_knowledge_section(workspace_dir, language))
|
sections.extend(_build_knowledge_section(workspace_dir, language))
|
||||||
|
|
||||||
# 4. 工作空间(工作环境说明)
|
# 4. 工作空间(工作环境说明)
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ def ensure_workspace(workspace_dir: str, create_templates: bool = True) -> Works
|
|||||||
websites_dir = os.path.join(workspace_dir, "websites")
|
websites_dir = os.path.join(workspace_dir, "websites")
|
||||||
os.makedirs(websites_dir, exist_ok=True)
|
os.makedirs(websites_dir, exist_ok=True)
|
||||||
|
|
||||||
|
from config import conf
|
||||||
|
knowledge_enabled = conf().get("knowledge", True)
|
||||||
|
if knowledge_enabled:
|
||||||
knowledge_dir = os.path.join(workspace_dir, "knowledge")
|
knowledge_dir = os.path.join(workspace_dir, "knowledge")
|
||||||
os.makedirs(knowledge_dir, exist_ok=True)
|
os.makedirs(knowledge_dir, exist_ok=True)
|
||||||
|
|
||||||
@@ -77,6 +80,7 @@ def ensure_workspace(workspace_dir: str, create_templates: bool = True) -> Works
|
|||||||
_create_template_if_missing(user_path, _get_user_template())
|
_create_template_if_missing(user_path, _get_user_template())
|
||||||
_create_template_if_missing(rule_path, _get_rule_template())
|
_create_template_if_missing(rule_path, _get_rule_template())
|
||||||
_create_template_if_missing(memory_path, _get_memory_template())
|
_create_template_if_missing(memory_path, _get_memory_template())
|
||||||
|
if knowledge_enabled:
|
||||||
_create_template_if_missing(
|
_create_template_if_missing(
|
||||||
os.path.join(knowledge_dir, "index.md"),
|
os.path.join(knowledge_dir, "index.md"),
|
||||||
_get_knowledge_index_template()
|
_get_knowledge_index_template()
|
||||||
|
|||||||
@@ -210,6 +210,10 @@ class SkillManager:
|
|||||||
if not include_disabled:
|
if not include_disabled:
|
||||||
entries = [e for e in entries if self.is_skill_enabled(e.skill.name)]
|
entries = [e for e in entries if self.is_skill_enabled(e.skill.name)]
|
||||||
|
|
||||||
|
from config import conf
|
||||||
|
if not conf().get("knowledge", True):
|
||||||
|
entries = [e for e in entries if e.skill.name != "knowledge-wiki"]
|
||||||
|
|
||||||
return entries
|
return entries
|
||||||
|
|
||||||
def filter_unavailable_skills(
|
def filter_unavailable_skills(
|
||||||
|
|||||||
@@ -45,7 +45,8 @@
|
|||||||
.msg-content h1 { font-size: 1.4em; }
|
.msg-content h1 { font-size: 1.4em; }
|
||||||
.msg-content h2 { font-size: 1.25em; }
|
.msg-content h2 { font-size: 1.25em; }
|
||||||
.msg-content h3 { font-size: 1.1em; }
|
.msg-content h3 { font-size: 1.1em; }
|
||||||
.msg-content ul, .msg-content ol { margin: 0.5em 0; padding-left: 1.8em; }
|
.msg-content ul { margin: 0.5em 0; padding-left: 1.8em; list-style: disc; }
|
||||||
|
.msg-content ol { margin: 0.5em 0; padding-left: 1.8em; list-style: decimal; }
|
||||||
.msg-content li { margin: 0.25em 0; }
|
.msg-content li { margin: 0.25em 0; }
|
||||||
.msg-content pre {
|
.msg-content pre {
|
||||||
border-radius: 8px; overflow-x: auto; margin: 0.8em 0;
|
border-radius: 8px; overflow-x: auto; margin: 0.8em 0;
|
||||||
|
|||||||
@@ -496,6 +496,10 @@ const SLASH_COMMANDS = [
|
|||||||
{ cmd: '/skill info ', desc: '查看技能详情' },
|
{ cmd: '/skill info ', desc: '查看技能详情' },
|
||||||
{ cmd: '/skill enable ', desc: '启用技能' },
|
{ cmd: '/skill enable ', desc: '启用技能' },
|
||||||
{ cmd: '/skill disable ', desc: '禁用技能' },
|
{ cmd: '/skill disable ', desc: '禁用技能' },
|
||||||
|
{ cmd: '/knowledge', desc: '查看知识库统计' },
|
||||||
|
{ cmd: '/knowledge list', desc: '查看知识库文件树' },
|
||||||
|
{ cmd: '/knowledge on', desc: '开启知识库' },
|
||||||
|
{ cmd: '/knowledge off', desc: '关闭知识库' },
|
||||||
{ cmd: '/config', desc: '查看当前配置' },
|
{ cmd: '/config', desc: '查看当前配置' },
|
||||||
{ cmd: '/logs', desc: '查看最近日志' },
|
{ cmd: '/logs', desc: '查看最近日志' },
|
||||||
{ cmd: '/version', desc: '查看版本' },
|
{ cmd: '/version', desc: '查看版本' },
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ available_setting = {
|
|||||||
"agent_max_context_tokens": 50000, # Agent模式下最大上下文tokens
|
"agent_max_context_tokens": 50000, # Agent模式下最大上下文tokens
|
||||||
"agent_max_context_turns": 30, # Agent模式下最大上下文记忆轮次
|
"agent_max_context_turns": 30, # Agent模式下最大上下文记忆轮次
|
||||||
"agent_max_steps": 15, # Agent模式下单次运行最大决策步数
|
"agent_max_steps": 15, # Agent模式下单次运行最大决策步数
|
||||||
|
"knowledge": True, # 是否开启知识库功能
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ KNOWN_COMMANDS = {
|
|||||||
"help", "version", "status", "logs",
|
"help", "version", "status", "logs",
|
||||||
"start", "stop", "restart",
|
"start", "stop", "restart",
|
||||||
"skill", "context", "config",
|
"skill", "context", "config",
|
||||||
|
"knowledge",
|
||||||
"install-browser",
|
"install-browser",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,6 +158,9 @@ class CowCliPlugin(Plugin):
|
|||||||
" /config 查看当前配置",
|
" /config 查看当前配置",
|
||||||
" /config <key> 查看某项配置",
|
" /config <key> 查看某项配置",
|
||||||
" /config <key> <val> 修改配置",
|
" /config <key> <val> 修改配置",
|
||||||
|
" /knowledge 查看知识库统计",
|
||||||
|
" /knowledge list 查看知识库文件树",
|
||||||
|
" /knowledge on|off 开启/关闭知识库",
|
||||||
"",
|
"",
|
||||||
"💡 也可以用 cow <command> 代替 /<command>",
|
"💡 也可以用 cow <command> 代替 /<command>",
|
||||||
]
|
]
|
||||||
@@ -310,6 +314,7 @@ class CowCliPlugin(Plugin):
|
|||||||
"agent_max_context_tokens",
|
"agent_max_context_tokens",
|
||||||
"agent_max_context_turns",
|
"agent_max_context_turns",
|
||||||
"agent_max_steps",
|
"agent_max_steps",
|
||||||
|
"knowledge",
|
||||||
}
|
}
|
||||||
|
|
||||||
_CONFIG_READABLE = _CONFIG_WRITABLE | {"channel_type"}
|
_CONFIG_READABLE = _CONFIG_WRITABLE | {"channel_type"}
|
||||||
@@ -851,6 +856,133 @@ class CowCliPlugin(Plugin):
|
|||||||
icon = "✅" if enabled else "⬚"
|
icon = "✅" if enabled else "⬚"
|
||||||
return f"{icon} 技能 '{name}' 已{action}"
|
return f"{icon} 技能 '{name}' 已{action}"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# knowledge
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _cmd_knowledge(self, args: str, e_context, **_) -> str:
|
||||||
|
sub = args.strip().lower().split(None, 1)[0] if args.strip() else ""
|
||||||
|
|
||||||
|
if sub == "on":
|
||||||
|
return self._knowledge_toggle(True)
|
||||||
|
elif sub == "off":
|
||||||
|
return self._knowledge_toggle(False)
|
||||||
|
elif sub in ("list", "tree"):
|
||||||
|
return self._knowledge_tree()
|
||||||
|
else:
|
||||||
|
return self._knowledge_stats()
|
||||||
|
|
||||||
|
def _knowledge_toggle(self, enabled: bool) -> str:
|
||||||
|
from config import conf
|
||||||
|
import json as _json
|
||||||
|
|
||||||
|
conf()["knowledge"] = enabled
|
||||||
|
|
||||||
|
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
config_path = os.path.join(project_root, "config.json")
|
||||||
|
try:
|
||||||
|
with open(config_path, "r", encoding="utf-8") as f:
|
||||||
|
file_config = _json.load(f)
|
||||||
|
file_config["knowledge"] = enabled
|
||||||
|
with open(config_path, "w", encoding="utf-8") as f:
|
||||||
|
_json.dump(file_config, f, indent=4, ensure_ascii=False)
|
||||||
|
except Exception as e:
|
||||||
|
return f"⚠️ 内存中已切换,但写入 config.json 失败: {e}"
|
||||||
|
|
||||||
|
status = "开启 ✅" if enabled else "关闭 ❌"
|
||||||
|
note = "知识库将在下次对话中生效" if enabled else "知识库系统已停用,不再注入提示词和索引知识文件"
|
||||||
|
return f"📚 知识库已{status}\n\n{note}"
|
||||||
|
|
||||||
|
def _knowledge_stats(self) -> str:
|
||||||
|
from config import conf
|
||||||
|
from common.utils import expand_path
|
||||||
|
knowledge_dir = os.path.join(
|
||||||
|
expand_path(conf().get("agent_workspace", "~/cow")),
|
||||||
|
"knowledge"
|
||||||
|
)
|
||||||
|
if not os.path.isdir(knowledge_dir):
|
||||||
|
return "📚 知识库目录不存在\n\n💡 开启知识库: /knowledge on"
|
||||||
|
|
||||||
|
enabled = conf().get("knowledge", True)
|
||||||
|
total_files = 0
|
||||||
|
total_bytes = 0
|
||||||
|
cat_count = {}
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(knowledge_dir):
|
||||||
|
dirs[:] = [d for d in dirs if not d.startswith(".")]
|
||||||
|
rel_root = os.path.relpath(root, knowledge_dir)
|
||||||
|
category = rel_root.split(os.sep)[0] if rel_root != "." else "root"
|
||||||
|
for f in files:
|
||||||
|
if f.endswith(".md") and f not in ("index.md", "log.md"):
|
||||||
|
total_files += 1
|
||||||
|
total_bytes += os.path.getsize(os.path.join(root, f))
|
||||||
|
cat_count[category] = cat_count.get(category, 0) + 1
|
||||||
|
|
||||||
|
status = "✅ 已开启" if enabled else "❌ 已关闭"
|
||||||
|
lines = [
|
||||||
|
"📚 知识库统计",
|
||||||
|
"",
|
||||||
|
f"状态: {status}",
|
||||||
|
f"页面: {total_files} 篇",
|
||||||
|
f"大小: {total_bytes / 1024:.1f} KB",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
if cat_count:
|
||||||
|
for cat in sorted(cat_count.keys()):
|
||||||
|
lines.append(f"- {cat}/ ({cat_count[cat]} pages)")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
lines.append(f"路径: {knowledge_dir}")
|
||||||
|
lines.extend([
|
||||||
|
"",
|
||||||
|
"━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
||||||
|
"💡 /knowledge list 查看文件树",
|
||||||
|
"💡 /knowledge on|off 开关知识库",
|
||||||
|
])
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def _knowledge_tree(self) -> str:
|
||||||
|
from config import conf
|
||||||
|
from common.utils import expand_path
|
||||||
|
knowledge_dir = os.path.join(
|
||||||
|
expand_path(conf().get("agent_workspace", "~/cow")),
|
||||||
|
"knowledge"
|
||||||
|
)
|
||||||
|
if not os.path.isdir(knowledge_dir):
|
||||||
|
return "📚 知识库目录不存在\n\n💡 开启知识库: /knowledge on"
|
||||||
|
|
||||||
|
tree = ["knowledge/"]
|
||||||
|
|
||||||
|
subdirs = sorted([
|
||||||
|
d for d in os.listdir(knowledge_dir)
|
||||||
|
if os.path.isdir(os.path.join(knowledge_dir, d)) and not d.startswith(".")
|
||||||
|
])
|
||||||
|
|
||||||
|
for i, subdir in enumerate(subdirs):
|
||||||
|
is_last_dir = (i == len(subdirs) - 1)
|
||||||
|
branch = "└── " if is_last_dir else "├── "
|
||||||
|
subdir_path = os.path.join(knowledge_dir, subdir)
|
||||||
|
md_files = sorted([
|
||||||
|
f for f in os.listdir(subdir_path)
|
||||||
|
if f.endswith(".md") and not f.startswith(".")
|
||||||
|
])
|
||||||
|
tree.append(f"{branch}{subdir}/ ({len(md_files)})")
|
||||||
|
|
||||||
|
child_prefix = " " if is_last_dir else "│ "
|
||||||
|
max_show = 12
|
||||||
|
for j, fname in enumerate(md_files[:max_show]):
|
||||||
|
is_last_file = (j == len(md_files[:max_show]) - 1) and len(md_files) <= max_show
|
||||||
|
fb = "└── " if is_last_file else "├── "
|
||||||
|
name = fname.replace(".md", "")
|
||||||
|
tree.append(f"{child_prefix}{fb}{name}")
|
||||||
|
if len(md_files) > max_show:
|
||||||
|
tree.append(f"{child_prefix}└── ... +{len(md_files) - max_show} more")
|
||||||
|
|
||||||
|
if not subdirs:
|
||||||
|
tree.append("(空)")
|
||||||
|
|
||||||
|
return "```\n" + "\n".join(tree) + "\n```"
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Helpers
|
# Helpers
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user